summaryrefslogtreecommitdiffstats
path: root/vcl/source
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/source
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source')
-rw-r--r--vcl/source/animate/Animation.cxx677
-rw-r--r--vcl/source/animate/AnimationBitmap.cxx54
-rw-r--r--vcl/source/app/ITiledRenderable.cxx77
-rw-r--r--vcl/source/app/IconThemeInfo.cxx180
-rw-r--r--vcl/source/app/IconThemeScanner.cxx216
-rw-r--r--vcl/source/app/IconThemeSelector.cxx186
-rw-r--r--vcl/source/app/brand.cxx78
-rw-r--r--vcl/source/app/customweld.cxx112
-rw-r--r--vcl/source/app/dbggui.cxx50
-rw-r--r--vcl/source/app/dndhelp.cxx167
-rw-r--r--vcl/source/app/help.cxx675
-rw-r--r--vcl/source/app/i18nhelp.cxx155
-rw-r--r--vcl/source/app/idle.cxx66
-rw-r--r--vcl/source/app/salplug.cxx450
-rw-r--r--vcl/source/app/salusereventlist.cxx164
-rw-r--r--vcl/source/app/salvtables.cxx7517
-rw-r--r--vcl/source/app/scheduler.cxx664
-rw-r--r--vcl/source/app/session.cxx399
-rw-r--r--vcl/source/app/settings.cxx3250
-rw-r--r--vcl/source/app/sound.cxx38
-rw-r--r--vcl/source/app/stdtext.cxx124
-rw-r--r--vcl/source/app/svapp.cxx1834
-rw-r--r--vcl/source/app/svdata.cxx506
-rw-r--r--vcl/source/app/svmain.cxx692
-rw-r--r--vcl/source/app/timer.cxx103
-rw-r--r--vcl/source/app/unohelp.cxx213
-rw-r--r--vcl/source/app/unohelp2.cxx112
-rw-r--r--vcl/source/app/vclevent.cxx94
-rw-r--r--vcl/source/app/watchdog.cxx166
-rw-r--r--vcl/source/app/weldutils.cxx662
-rw-r--r--vcl/source/app/winscheduler.cxx46
-rw-r--r--vcl/source/bitmap/BitmapAlphaClampFilter.cxx45
-rw-r--r--vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx371
-rw-r--r--vcl/source/bitmap/BitmapColorQuantizationFilter.cxx224
-rw-r--r--vcl/source/bitmap/BitmapColorizeFilter.cxx92
-rw-r--r--vcl/source/bitmap/BitmapConvolutionMatrixFilter.cxx209
-rw-r--r--vcl/source/bitmap/BitmapDisabledImageFilter.cxx60
-rw-r--r--vcl/source/bitmap/BitmapDuoToneFilter.cxx60
-rw-r--r--vcl/source/bitmap/BitmapEmbossGreyFilter.cxx160
-rw-r--r--vcl/source/bitmap/BitmapEx.cxx1505
-rw-r--r--vcl/source/bitmap/BitmapFastScaleFilter.cxx129
-rw-r--r--vcl/source/bitmap/BitmapFilterStackBlur.cxx658
-rw-r--r--vcl/source/bitmap/BitmapGaussianSeparableBlurFilter.cxx215
-rw-r--r--vcl/source/bitmap/BitmapInfoAccess.cxx80
-rw-r--r--vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx243
-rw-r--r--vcl/source/bitmap/BitmapLightenFilter.cxx67
-rw-r--r--vcl/source/bitmap/BitmapMaskToAlphaFilter.cxx58
-rw-r--r--vcl/source/bitmap/BitmapMedianFilter.cxx217
-rw-r--r--vcl/source/bitmap/BitmapMonochromeFilter.cxx100
-rw-r--r--vcl/source/bitmap/BitmapMosaicFilter.cxx186
-rw-r--r--vcl/source/bitmap/BitmapPopArtFilter.cxx99
-rw-r--r--vcl/source/bitmap/BitmapReadAccess.cxx550
-rw-r--r--vcl/source/bitmap/BitmapScaleConvolutionFilter.cxx389
-rw-r--r--vcl/source/bitmap/BitmapScaleSuperFilter.cxx1046
-rw-r--r--vcl/source/bitmap/BitmapSeparableUnsharpenFilter.cxx75
-rw-r--r--vcl/source/bitmap/BitmapSepiaFilter.cxx110
-rw-r--r--vcl/source/bitmap/BitmapShadowFilter.cxx48
-rw-r--r--vcl/source/bitmap/BitmapSimpleColorQuantizationFilter.cxx110
-rw-r--r--vcl/source/bitmap/BitmapSmoothenFilter.cxx32
-rw-r--r--vcl/source/bitmap/BitmapSobelGreyFilter.cxx171
-rw-r--r--vcl/source/bitmap/BitmapSolarizeFilter.cxx71
-rw-r--r--vcl/source/bitmap/BitmapSymmetryCheck.cxx83
-rw-r--r--vcl/source/bitmap/BitmapTools.cxx1307
-rw-r--r--vcl/source/bitmap/BitmapWriteAccess.cxx397
-rw-r--r--vcl/source/bitmap/Octree.cxx280
-rw-r--r--vcl/source/bitmap/alpha.cxx124
-rw-r--r--vcl/source/bitmap/bitmap.cxx1737
-rw-r--r--vcl/source/bitmap/bitmapfilter.cxx58
-rw-r--r--vcl/source/bitmap/bitmappaint.cxx1123
-rw-r--r--vcl/source/bitmap/bitmappalette.cxx212
-rw-r--r--vcl/source/bitmap/bmpfast.cxx828
-rw-r--r--vcl/source/bitmap/checksum.cxx154
-rw-r--r--vcl/source/bitmap/dibtools.cxx1873
-rw-r--r--vcl/source/bitmap/floyd.hxx197
-rw-r--r--vcl/source/bitmap/impvect.cxx997
-rw-r--r--vcl/source/bitmap/impvect.hxx32
-rw-r--r--vcl/source/bitmap/salbmp.cxx334
-rw-r--r--vcl/source/cnttype/mcnttfactory.cxx67
-rw-r--r--vcl/source/cnttype/mcnttfactory.hxx49
-rw-r--r--vcl/source/cnttype/mcnttype.cxx88
-rw-r--r--vcl/source/cnttype/mcnttype.hxx58
-rw-r--r--vcl/source/components/dtranscomp.cxx463
-rw-r--r--vcl/source/components/factory.cxx69
-rw-r--r--vcl/source/components/fontident.cxx176
-rw-r--r--vcl/source/control/ContextVBox.cxx62
-rw-r--r--vcl/source/control/DropdownBox.cxx115
-rw-r--r--vcl/source/control/InterimItemWindow.cxx195
-rw-r--r--vcl/source/control/NotebookbarPopup.cxx161
-rw-r--r--vcl/source/control/PriorityHBox.cxx197
-rw-r--r--vcl/source/control/PriorityMergedHBox.cxx203
-rw-r--r--vcl/source/control/WeldedTabbedNotebookbar.cxx23
-rw-r--r--vcl/source/control/button.cxx3826
-rw-r--r--vcl/source/control/calendar.cxx1723
-rw-r--r--vcl/source/control/combobox.cxx1572
-rw-r--r--vcl/source/control/ctrl.cxx505
-rw-r--r--vcl/source/control/edit.cxx2929
-rw-r--r--vcl/source/control/field.cxx1885
-rw-r--r--vcl/source/control/field2.cxx3183
-rw-r--r--vcl/source/control/fixed.cxx995
-rw-r--r--vcl/source/control/fixedhyper.cxx186
-rw-r--r--vcl/source/control/fmtfield.cxx1367
-rw-r--r--vcl/source/control/hyperlabel.cxx176
-rw-r--r--vcl/source/control/imgctrl.cxx184
-rw-r--r--vcl/source/control/imivctl.hxx513
-rw-r--r--vcl/source/control/imivctl1.cxx2927
-rw-r--r--vcl/source/control/imivctl2.cxx715
-rw-r--r--vcl/source/control/imp_listbox.cxx3012
-rw-r--r--vcl/source/control/ivctrl.cxx634
-rw-r--r--vcl/source/control/listbox.cxx1427
-rw-r--r--vcl/source/control/longcurr.cxx429
-rw-r--r--vcl/source/control/managedmenubutton.cxx100
-rw-r--r--vcl/source/control/menubtn.cxx290
-rw-r--r--vcl/source/control/notebookbar.cxx347
-rw-r--r--vcl/source/control/prgsbar.cxx232
-rw-r--r--vcl/source/control/quickselectionengine.cxx158
-rw-r--r--vcl/source/control/roadmap.cxx845
-rw-r--r--vcl/source/control/roadmapwizard.cxx798
-rw-r--r--vcl/source/control/scrbar.cxx1467
-rw-r--r--vcl/source/control/slider.cxx905
-rw-r--r--vcl/source/control/spinbtn.cxx468
-rw-r--r--vcl/source/control/spinfld.cxx1034
-rw-r--r--vcl/source/control/tabctrl.cxx2459
-rw-r--r--vcl/source/control/throbber.cxx235
-rw-r--r--vcl/source/control/thumbpos.hxx24
-rw-r--r--vcl/source/control/wizardmachine.cxx1418
-rw-r--r--vcl/source/control/wizimpldata.hxx64
-rw-r--r--vcl/source/edit/textdat2.hxx284
-rw-r--r--vcl/source/edit/textdata.cxx344
-rw-r--r--vcl/source/edit/textdoc.cxx536
-rw-r--r--vcl/source/edit/textdoc.hxx129
-rw-r--r--vcl/source/edit/texteng.cxx2895
-rw-r--r--vcl/source/edit/textund2.hxx108
-rw-r--r--vcl/source/edit/textundo.cxx329
-rw-r--r--vcl/source/edit/textundo.hxx80
-rw-r--r--vcl/source/edit/textview.cxx2237
-rw-r--r--vcl/source/edit/txtattr.cxx93
-rw-r--r--vcl/source/edit/vclmedit.cxx1494
-rw-r--r--vcl/source/edit/xtextedt.cxx227
-rw-r--r--vcl/source/filter/FilterConfigCache.cxx489
-rw-r--r--vcl/source/filter/FilterConfigCache.hxx100
-rw-r--r--vcl/source/filter/FilterConfigItem.cxx389
-rw-r--r--vcl/source/filter/GraphicFormatDetector.cxx859
-rw-r--r--vcl/source/filter/GraphicNativeMetadata.cxx59
-rw-r--r--vcl/source/filter/GraphicNativeTransform.cxx164
-rw-r--r--vcl/source/filter/bmp/BmpReader.cxx30
-rw-r--r--vcl/source/filter/bmp/BmpWriter.cxx40
-rw-r--r--vcl/source/filter/egif/egif.cxx549
-rw-r--r--vcl/source/filter/egif/giflzwc.cxx225
-rw-r--r--vcl/source/filter/egif/giflzwc.hxx55
-rw-r--r--vcl/source/filter/eps/eps.cxx2667
-rw-r--r--vcl/source/filter/etiff/etiff.cxx586
-rw-r--r--vcl/source/filter/graphicfilter.cxx2056
-rw-r--r--vcl/source/filter/graphicfilter2.cxx1229
-rw-r--r--vcl/source/filter/graphicfilter_internal.hxx32
-rw-r--r--vcl/source/filter/idxf/dxf2mtf.cxx902
-rw-r--r--vcl/source/filter/idxf/dxf2mtf.hxx119
-rw-r--r--vcl/source/filter/idxf/dxfblkrd.cxx125
-rw-r--r--vcl/source/filter/idxf/dxfblkrd.hxx83
-rw-r--r--vcl/source/filter/idxf/dxfentrd.cxx848
-rw-r--r--vcl/source/filter/idxf/dxfentrd.hxx538
-rw-r--r--vcl/source/filter/idxf/dxfgrprd.cxx213
-rw-r--r--vcl/source/filter/idxf/dxfgrprd.hxx115
-rw-r--r--vcl/source/filter/idxf/dxfreprd.cxx480
-rw-r--r--vcl/source/filter/idxf/dxfreprd.hxx129
-rw-r--r--vcl/source/filter/idxf/dxftblrd.cxx381
-rw-r--r--vcl/source/filter/idxf/dxftblrd.hxx175
-rw-r--r--vcl/source/filter/idxf/dxfvec.cxx232
-rw-r--r--vcl/source/filter/idxf/dxfvec.hxx218
-rw-r--r--vcl/source/filter/idxf/idxf.cxx43
-rw-r--r--vcl/source/filter/ieps/ieps.cxx825
-rw-r--r--vcl/source/filter/igif/decode.cxx214
-rw-r--r--vcl/source/filter/igif/decode.hxx66
-rw-r--r--vcl/source/filter/igif/gifread.cxx1002
-rw-r--r--vcl/source/filter/igif/gifread.hxx30
-rw-r--r--vcl/source/filter/imet/ios2met.cxx2883
-rw-r--r--vcl/source/filter/ipbm/ipbm.cxx541
-rw-r--r--vcl/source/filter/ipcd/ipcd.cxx352
-rw-r--r--vcl/source/filter/ipcx/ipcx.cxx411
-rw-r--r--vcl/source/filter/ipdf/pdfcompat.cxx114
-rw-r--r--vcl/source/filter/ipdf/pdfdocument.cxx3325
-rw-r--r--vcl/source/filter/ipdf/pdfread.cxx399
-rw-r--r--vcl/source/filter/ipict/ipict.cxx2041
-rw-r--r--vcl/source/filter/ipict/shape.cxx259
-rw-r--r--vcl/source/filter/ipict/shape.hxx57
-rw-r--r--vcl/source/filter/ipsd/ipsd.cxx776
-rw-r--r--vcl/source/filter/iras/iras.cxx414
-rw-r--r--vcl/source/filter/itga/itga.cxx790
-rw-r--r--vcl/source/filter/itiff/itiff.cxx294
-rw-r--r--vcl/source/filter/ixbm/xbmread.cxx396
-rw-r--r--vcl/source/filter/ixbm/xbmread.hxx29
-rw-r--r--vcl/source/filter/ixpm/rgbtable.hxx696
-rw-r--r--vcl/source/filter/ixpm/xpmread.cxx697
-rw-r--r--vcl/source/filter/jpeg/Exif.cxx288
-rw-r--r--vcl/source/filter/jpeg/Exif.hxx83
-rw-r--r--vcl/source/filter/jpeg/JpegReader.cxx331
-rw-r--r--vcl/source/filter/jpeg/JpegReader.hxx74
-rw-r--r--vcl/source/filter/jpeg/JpegTransform.cxx42
-rw-r--r--vcl/source/filter/jpeg/JpegTransform.hxx40
-rw-r--r--vcl/source/filter/jpeg/JpegWriter.cxx265
-rw-r--r--vcl/source/filter/jpeg/JpegWriter.hxx57
-rw-r--r--vcl/source/filter/jpeg/jinclude.h79
-rw-r--r--vcl/source/filter/jpeg/jpeg.cxx62
-rw-r--r--vcl/source/filter/jpeg/jpeg.h67
-rw-r--r--vcl/source/filter/jpeg/jpeg.hxx39
-rw-r--r--vcl/source/filter/jpeg/jpegc.cxx520
-rw-r--r--vcl/source/filter/jpeg/jpegcomp.h18
-rw-r--r--vcl/source/filter/jpeg/transupp.c1570
-rw-r--r--vcl/source/filter/jpeg/transupp.h212
-rw-r--r--vcl/source/filter/png/PngImageReader.cxx532
-rw-r--r--vcl/source/filter/png/png.hxx33
-rw-r--r--vcl/source/filter/png/pngwrite.cxx660
-rw-r--r--vcl/source/filter/svm/SvmConverter.cxx1290
-rw-r--r--vcl/source/filter/svm/SvmConverter.hxx92
-rw-r--r--vcl/source/filter/svm/SvmReader.cxx1438
-rw-r--r--vcl/source/filter/svm/SvmWriter.cxx1421
-rw-r--r--vcl/source/filter/webp/reader.cxx318
-rw-r--r--vcl/source/filter/webp/writer.cxx205
-rw-r--r--vcl/source/filter/wmf/emfwr.cxx1511
-rw-r--r--vcl/source/filter/wmf/emfwr.hxx114
-rw-r--r--vcl/source/filter/wmf/wmf.cxx134
-rw-r--r--vcl/source/filter/wmf/wmfexternal.cxx74
-rw-r--r--vcl/source/filter/wmf/wmfwr.cxx1904
-rw-r--r--vcl/source/filter/wmf/wmfwr.hxx205
-rw-r--r--vcl/source/font/DirectFontSubstitution.cxx71
-rw-r--r--vcl/source/font/Feature.cxx162
-rw-r--r--vcl/source/font/FeatureCollector.cxx158
-rw-r--r--vcl/source/font/FeatureParser.cxx70
-rw-r--r--vcl/source/font/FontSelectPattern.cxx155
-rw-r--r--vcl/source/font/OpenTypeFeatureDefinitionList.cxx200
-rw-r--r--vcl/source/font/PhysicalFontCollection.cxx1279
-rw-r--r--vcl/source/font/PhysicalFontFace.cxx206
-rw-r--r--vcl/source/font/PhysicalFontFamily.cxx269
-rw-r--r--vcl/source/font/font.cxx1155
-rw-r--r--vcl/source/font/fontattributes.cxx62
-rw-r--r--vcl/source/font/fontcache.cxx279
-rw-r--r--vcl/source/font/fontcharmap.cxx639
-rw-r--r--vcl/source/font/fontinstance.cxx192
-rw-r--r--vcl/source/font/fontmetric.cxx468
-rw-r--r--vcl/source/fontsubset/cff.cxx2240
-rw-r--r--vcl/source/fontsubset/fontsubset.cxx163
-rw-r--r--vcl/source/fontsubset/list.cxx229
-rw-r--r--vcl/source/fontsubset/list.h89
-rw-r--r--vcl/source/fontsubset/sft.cxx2468
-rw-r--r--vcl/source/fontsubset/ttcr.cxx1526
-rw-r--r--vcl/source/fontsubset/ttcr.hxx220
-rw-r--r--vcl/source/fontsubset/xlat.cxx144
-rw-r--r--vcl/source/fontsubset/xlat.hxx40
-rw-r--r--vcl/source/gdi/CommonSalLayout.cxx861
-rw-r--r--vcl/source/gdi/FileDefinitionWidgetDraw.cxx1129
-rw-r--r--vcl/source/gdi/TypeSerializer.cxx486
-rw-r--r--vcl/source/gdi/VerticalOrientationData.cxx78
-rw-r--r--vcl/source/gdi/WidgetDefinition.cxx186
-rw-r--r--vcl/source/gdi/WidgetDefinitionReader.cxx495
-rw-r--r--vcl/source/gdi/configsettings.cxx135
-rw-r--r--vcl/source/gdi/cvtgrf.cxx69
-rw-r--r--vcl/source/gdi/embeddedfontshelper.cxx335
-rw-r--r--vcl/source/gdi/extoutdevdata.cxx27
-rw-r--r--vcl/source/gdi/gdimetafiletools.cxx1086
-rw-r--r--vcl/source/gdi/gdimtf.cxx2347
-rwxr-xr-xvcl/source/gdi/genVerticalOrientationData.pl206
-rw-r--r--vcl/source/gdi/gfxlink.cxx182
-rw-r--r--vcl/source/gdi/gradient.cxx681
-rw-r--r--vcl/source/gdi/graph.cxx564
-rw-r--r--vcl/source/gdi/graphictools.cxx288
-rw-r--r--vcl/source/gdi/hatch.cxx114
-rw-r--r--vcl/source/gdi/impanmvw.cxx324
-rw-r--r--vcl/source/gdi/impglyphitem.cxx560
-rw-r--r--vcl/source/gdi/impgraph.cxx1759
-rw-r--r--vcl/source/gdi/jobset.cxx410
-rw-r--r--vcl/source/gdi/lineinfo.cxx293
-rw-r--r--vcl/source/gdi/mapmod.cxx160
-rw-r--r--vcl/source/gdi/metaact.cxx2122
-rw-r--r--vcl/source/gdi/mtfxmldump.cxx1491
-rw-r--r--vcl/source/gdi/oldprintadaptor.cxx109
-rw-r--r--vcl/source/gdi/pdfbuildin_fonts.cxx764
-rw-r--r--vcl/source/gdi/pdfextoutdevdata.cxx893
-rw-r--r--vcl/source/gdi/pdffontcache.cxx66
-rw-r--r--vcl/source/gdi/pdfobjectcopier.cxx318
-rw-r--r--vcl/source/gdi/pdfwriter.cxx468
-rw-r--r--vcl/source/gdi/pdfwriter_impl.cxx11182
-rw-r--r--vcl/source/gdi/pdfwriter_impl2.cxx1990
-rw-r--r--vcl/source/gdi/print.cxx1694
-rw-r--r--vcl/source/gdi/print2.cxx70
-rw-r--r--vcl/source/gdi/print3.cxx2146
-rw-r--r--vcl/source/gdi/regband.cxx885
-rw-r--r--vcl/source/gdi/region.cxx1792
-rw-r--r--vcl/source/gdi/regionband.cxx1362
-rw-r--r--vcl/source/gdi/salgdiimpl.cxx24
-rw-r--r--vcl/source/gdi/salgdilayout.cxx1097
-rw-r--r--vcl/source/gdi/sallayout.cxx1170
-rw-r--r--vcl/source/gdi/salmisc.cxx435
-rw-r--r--vcl/source/gdi/scrptrun.cxx251
-rw-r--r--vcl/source/gdi/textlayout.cxx348
-rw-r--r--vcl/source/gdi/vectorgraphicdata.cxx362
-rw-r--r--vcl/source/gdi/virdev.cxx524
-rw-r--r--vcl/source/gdi/wall.cxx271
-rw-r--r--vcl/source/graphic/BinaryDataContainer.cxx36
-rw-r--r--vcl/source/graphic/BinaryDataContainerTools.cxx28
-rw-r--r--vcl/source/graphic/GraphicID.cxx100
-rw-r--r--vcl/source/graphic/GraphicLoader.cxx44
-rw-r--r--vcl/source/graphic/GraphicObject.cxx934
-rw-r--r--vcl/source/graphic/GraphicObject2.cxx503
-rw-r--r--vcl/source/graphic/GraphicReader.cxx29
-rw-r--r--vcl/source/graphic/Manager.cxx293
-rw-r--r--vcl/source/graphic/UnoBinaryDataContainer.cxx34
-rw-r--r--vcl/source/graphic/UnoGraphic.cxx276
-rw-r--r--vcl/source/graphic/UnoGraphicDescriptor.cxx427
-rw-r--r--vcl/source/graphic/UnoGraphicMapper.cxx87
-rw-r--r--vcl/source/graphic/UnoGraphicObject.cxx101
-rw-r--r--vcl/source/graphic/UnoGraphicProvider.cxx836
-rw-r--r--vcl/source/graphic/VectorGraphicLoader.cxx26
-rw-r--r--vcl/source/graphic/VectorGraphicSearch.cxx306
-rw-r--r--vcl/source/helper/canvasbitmap.cxx1348
-rw-r--r--vcl/source/helper/canvastools.cxx624
-rw-r--r--vcl/source/helper/commandinfoprovider.cxx480
-rw-r--r--vcl/source/helper/displayconnectiondispatch.cxx111
-rw-r--r--vcl/source/helper/driverblocklist.cxx768
-rw-r--r--vcl/source/helper/errcode.cxx147
-rw-r--r--vcl/source/helper/evntpost.cxx57
-rw-r--r--vcl/source/helper/lazydelete.cxx58
-rw-r--r--vcl/source/helper/strhelper.cxx380
-rw-r--r--vcl/source/helper/svtaccessiblefactory.cxx289
-rw-r--r--vcl/source/helper/threadex.cxx68
-rw-r--r--vcl/source/image/Image.cxx165
-rw-r--r--vcl/source/image/ImageRepository.cxx37
-rw-r--r--vcl/source/image/ImageTree.cxx67
-rw-r--r--vcl/source/image/ImplImage.cxx176
-rw-r--r--vcl/source/image/ImplImageTree.cxx714
-rw-r--r--vcl/source/opengl/GLMHelper.hxx21
-rw-r--r--vcl/source/opengl/OpenGLContext.cxx495
-rw-r--r--vcl/source/opengl/OpenGLHelper.cxx939
-rw-r--r--vcl/source/opengl/README.deprecated23
-rw-r--r--vcl/source/opengl/opengl_denylist_windows.xml29
-rw-r--r--vcl/source/opengl/win/WinDeviceInfo.cxx491
-rw-r--r--vcl/source/opengl/win/context.cxx663
-rw-r--r--vcl/source/opengl/x11/context.cxx519
-rw-r--r--vcl/source/outdev/background.cxx77
-rw-r--r--vcl/source/outdev/bitmap.cxx1039
-rw-r--r--vcl/source/outdev/bitmapex.cxx682
-rw-r--r--vcl/source/outdev/clipping.cxx227
-rw-r--r--vcl/source/outdev/curvedshapes.cxx214
-rw-r--r--vcl/source/outdev/eps.cxx84
-rw-r--r--vcl/source/outdev/fill.cxx99
-rw-r--r--vcl/source/outdev/font.cxx1408
-rw-r--r--vcl/source/outdev/gradient.cxx631
-rw-r--r--vcl/source/outdev/hatch.cxx442
-rw-r--r--vcl/source/outdev/line.cxx358
-rw-r--r--vcl/source/outdev/map.cxx1910
-rw-r--r--vcl/source/outdev/mask.cxx164
-rw-r--r--vcl/source/outdev/nativecontrols.cxx326
-rw-r--r--vcl/source/outdev/outdev.cxx836
-rw-r--r--vcl/source/outdev/pixel.cxx120
-rw-r--r--vcl/source/outdev/polygon.cxx518
-rw-r--r--vcl/source/outdev/polyline.cxx408
-rw-r--r--vcl/source/outdev/rect.cxx439
-rw-r--r--vcl/source/outdev/stack.cxx200
-rw-r--r--vcl/source/outdev/text.cxx2532
-rw-r--r--vcl/source/outdev/textline.cxx1117
-rw-r--r--vcl/source/outdev/transparent.cxx1914
-rw-r--r--vcl/source/outdev/vclreferencebase.cxx44
-rw-r--r--vcl/source/outdev/wallpaper.cxx396
-rw-r--r--vcl/source/pdf/DummyPDFiumLibrary.cxx23
-rw-r--r--vcl/source/pdf/ExternalPDFStreams.cxx42
-rw-r--r--vcl/source/pdf/Matrix3.cxx116
-rw-r--r--vcl/source/pdf/PDFiumLibrary.cxx1333
-rw-r--r--vcl/source/pdf/PDFiumTools.cxx73
-rw-r--r--vcl/source/pdf/PdfConfig.cxx36
-rw-r--r--vcl/source/pdf/ResourceDict.cxx60
-rw-r--r--vcl/source/pdf/XmpMetadata.cxx190
-rw-r--r--vcl/source/printer/Options.cxx110
-rw-r--r--vcl/source/printer/QueueInfo.cxx35
-rw-r--r--vcl/source/rendercontext/drawmode.cxx262
-rw-r--r--vcl/source/salmain/salmain.cxx35
-rw-r--r--vcl/source/text/ImplLayoutArgs.cxx343
-rw-r--r--vcl/source/text/ImplLayoutRuns.cxx179
-rw-r--r--vcl/source/text/TextLayoutCache.cxx75
-rw-r--r--vcl/source/toolkit/README2
-rw-r--r--vcl/source/toolkit/group.cxx255
-rw-r--r--vcl/source/toolkit/morebtn.cxx123
-rw-r--r--vcl/source/treelist/headbar.cxx1302
-rw-r--r--vcl/source/treelist/iconview.cxx315
-rw-r--r--vcl/source/treelist/iconviewimpl.cxx741
-rw-r--r--vcl/source/treelist/iconviewimpl.hxx91
-rw-r--r--vcl/source/treelist/imap.cxx994
-rw-r--r--vcl/source/treelist/imap2.cxx530
-rw-r--r--vcl/source/treelist/imap3.cxx84
-rw-r--r--vcl/source/treelist/inetimg.cxx136
-rw-r--r--vcl/source/treelist/svimpbox.cxx3142
-rw-r--r--vcl/source/treelist/svlbitm.cxx550
-rw-r--r--vcl/source/treelist/svtabbx.cxx1078
-rw-r--r--vcl/source/treelist/transfer.cxx2227
-rw-r--r--vcl/source/treelist/transfer2.cxx516
-rw-r--r--vcl/source/treelist/treelist.cxx1524
-rw-r--r--vcl/source/treelist/treelistbox.cxx3612
-rw-r--r--vcl/source/treelist/treelistentry.cxx235
-rw-r--r--vcl/source/treelist/uiobject.cxx185
-rw-r--r--vcl/source/treelist/viewdataentry.cxx87
-rw-r--r--vcl/source/uitest/logger.cxx624
-rw-r--r--vcl/source/uitest/uiobject.cxx1858
-rw-r--r--vcl/source/uitest/uitest.cxx80
-rw-r--r--vcl/source/uitest/uno/uiobject_uno.cxx224
-rw-r--r--vcl/source/uitest/uno/uiobject_uno.hxx55
-rw-r--r--vcl/source/uitest/uno/uitest_uno.cxx121
-rw-r--r--vcl/source/window/EnumContext.cxx215
-rw-r--r--vcl/source/window/NotebookBarAddonsMerger.cxx184
-rw-r--r--vcl/source/window/OptionalBox.cxx62
-rw-r--r--vcl/source/window/abstdlg.cxx85
-rw-r--r--vcl/source/window/accel.cxx279
-rw-r--r--vcl/source/window/accessibility.cxx612
-rw-r--r--vcl/source/window/accmgr.cxx228
-rw-r--r--vcl/source/window/brdwin.cxx2003
-rw-r--r--vcl/source/window/bubblewindow.cxx557
-rw-r--r--vcl/source/window/bufferdevice.cxx45
-rw-r--r--vcl/source/window/bufferdevice.hxx35
-rw-r--r--vcl/source/window/builder.cxx4402
-rw-r--r--vcl/source/window/clipping.cxx709
-rw-r--r--vcl/source/window/commandevent.cxx198
-rw-r--r--vcl/source/window/cursor.cxx485
-rw-r--r--vcl/source/window/debug.cxx48
-rw-r--r--vcl/source/window/debugevent.cxx271
-rw-r--r--vcl/source/window/decoview.cxx1046
-rw-r--r--vcl/source/window/dialog.cxx1711
-rw-r--r--vcl/source/window/dlgctrl.cxx1159
-rw-r--r--vcl/source/window/dlgctrl.hxx34
-rw-r--r--vcl/source/window/dndeventdispatcher.cxx412
-rw-r--r--vcl/source/window/dndlistenercontainer.cxx487
-rw-r--r--vcl/source/window/dockingarea.cxx257
-rw-r--r--vcl/source/window/dockmgr.cxx1075
-rw-r--r--vcl/source/window/dockwin.cxx1147
-rw-r--r--vcl/source/window/errinf.cxx329
-rw-r--r--vcl/source/window/event.cxx673
-rw-r--r--vcl/source/window/floatwin.cxx973
-rw-r--r--vcl/source/window/globalization.cxx44
-rw-r--r--vcl/source/window/impldockingwrapper.hxx131
-rw-r--r--vcl/source/window/introwin.cxx49
-rw-r--r--vcl/source/window/keycod.cxx95
-rw-r--r--vcl/source/window/keyevent.cxx67
-rw-r--r--vcl/source/window/layout.cxx3028
-rw-r--r--vcl/source/window/legacyaccessibility.cxx241
-rw-r--r--vcl/source/window/menu.cxx3077
-rw-r--r--vcl/source/window/menubarwindow.cxx1222
-rw-r--r--vcl/source/window/menubarwindow.hxx142
-rw-r--r--vcl/source/window/menufloatingwindow.cxx1318
-rw-r--r--vcl/source/window/menufloatingwindow.hxx128
-rw-r--r--vcl/source/window/menuitemlist.cxx314
-rw-r--r--vcl/source/window/menuitemlist.hxx150
-rw-r--r--vcl/source/window/menuwindow.cxx111
-rw-r--r--vcl/source/window/menuwindow.hxx56
-rw-r--r--vcl/source/window/mnemonic.cxx348
-rw-r--r--vcl/source/window/mouse.cxx768
-rw-r--r--vcl/source/window/paint.cxx1809
-rw-r--r--vcl/source/window/printdlg.cxx2267
-rw-r--r--vcl/source/window/scrwnd.cxx379
-rw-r--r--vcl/source/window/seleng.cxx421
-rw-r--r--vcl/source/window/settings.cxx266
-rw-r--r--vcl/source/window/split.cxx698
-rw-r--r--vcl/source/window/splitwin.cxx2719
-rw-r--r--vcl/source/window/stacking.cxx1151
-rw-r--r--vcl/source/window/status.cxx1464
-rw-r--r--vcl/source/window/syschild.cxx200
-rw-r--r--vcl/source/window/syswin.cxx1183
-rw-r--r--vcl/source/window/tabdlg.cxx184
-rw-r--r--vcl/source/window/tabpage.cxx310
-rw-r--r--vcl/source/window/taskpanelist.cxx287
-rw-r--r--vcl/source/window/toolbox.cxx4846
-rw-r--r--vcl/source/window/toolbox2.cxx1781
-rw-r--r--vcl/source/window/window.cxx3978
-rw-r--r--vcl/source/window/window2.cxx2043
-rw-r--r--vcl/source/window/window3.cxx220
-rw-r--r--vcl/source/window/winproc.cxx2884
-rw-r--r--vcl/source/window/wrkwin.cxx272
471 files changed, 300958 insertions, 0 deletions
diff --git a/vcl/source/animate/Animation.cxx b/vcl/source/animate/Animation.cxx
new file mode 100644
index 000000000..ae7fc1a21
--- /dev/null
+++ b/vcl/source/animate/Animation.cxx
@@ -0,0 +1,677 @@
+/* -*- 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 <algorithm>
+#include <sal/config.h>
+
+#include <tools/stream.hxx>
+#include <tools/GenericTypeSerializer.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/animate/Animation.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/BitmapColorQuantizationFilter.hxx>
+
+#include <impanmvw.hxx>
+
+sal_uLong Animation::mnAnimCount = 0;
+
+Animation::Animation()
+ : maTimer("vcl::Animation")
+ , mnLoopCount(0)
+ , mnLoops(0)
+ , mnPos(0)
+ , mbIsInAnimation(false)
+ , mbLoopTerminated(false)
+{
+ maTimer.SetInvokeHandler(LINK(this, Animation, ImplTimeoutHdl));
+}
+
+Animation::Animation(const Animation& rAnimation)
+ : maBitmapEx(rAnimation.maBitmapEx)
+ , maTimer("vcl::Animation")
+ , maGlobalSize(rAnimation.maGlobalSize)
+ , mnLoopCount(rAnimation.mnLoopCount)
+ , mnPos(rAnimation.mnPos)
+ , mbIsInAnimation(false)
+ , mbLoopTerminated(rAnimation.mbLoopTerminated)
+{
+ for (auto const& i : rAnimation.maList)
+ maList.emplace_back(new AnimationBitmap(*i));
+
+ maTimer.SetInvokeHandler(LINK(this, Animation, ImplTimeoutHdl));
+ mnLoops = mbLoopTerminated ? 0 : mnLoopCount;
+}
+
+Animation::~Animation()
+{
+ if (mbIsInAnimation)
+ Stop();
+}
+
+Animation& Animation::operator=(const Animation& rAnimation)
+{
+ if (this != &rAnimation)
+ {
+ Clear();
+
+ for (auto const& i : rAnimation.maList)
+ maList.emplace_back(new AnimationBitmap(*i));
+
+ maGlobalSize = rAnimation.maGlobalSize;
+ maBitmapEx = rAnimation.maBitmapEx;
+ mnLoopCount = rAnimation.mnLoopCount;
+ mnPos = rAnimation.mnPos;
+ mbLoopTerminated = rAnimation.mbLoopTerminated;
+ mnLoops = mbLoopTerminated ? 0 : mnLoopCount;
+ }
+ return *this;
+}
+
+bool Animation::operator==(const Animation& rAnimation) const
+{
+ return maList.size() == rAnimation.maList.size() && maBitmapEx == rAnimation.maBitmapEx
+ && maGlobalSize == rAnimation.maGlobalSize
+ && std::equal(maList.begin(), maList.end(), rAnimation.maList.begin(),
+ [](const std::unique_ptr<AnimationBitmap>& pAnim1,
+ const std::unique_ptr<AnimationBitmap>& pAnim2) -> bool {
+ return *pAnim1 == *pAnim2;
+ });
+}
+
+void Animation::Clear()
+{
+ maTimer.Stop();
+ mbIsInAnimation = false;
+ maGlobalSize = Size();
+ maBitmapEx.SetEmpty();
+ maList.clear();
+ maViewList.clear();
+}
+
+bool Animation::IsTransparent() const
+{
+ tools::Rectangle aRect{ Point(), maGlobalSize };
+
+ // If some small bitmap needs to be replaced by the background,
+ // we need to be transparent, in order to be displayed correctly
+ // as the application (?) does not invalidate on non-transparent
+ // graphics due to performance reasons.
+
+ return maBitmapEx.IsAlpha()
+ || std::any_of(maList.begin(), maList.end(),
+ [&aRect](const std::unique_ptr<AnimationBitmap>& pAnim) -> bool {
+ return pAnim->meDisposal == Disposal::Back
+ && tools::Rectangle{ pAnim->maPositionPixel,
+ pAnim->maSizePixel }
+ != aRect;
+ });
+}
+
+sal_uLong Animation::GetSizeBytes() const
+{
+ sal_uLong nSizeBytes = GetBitmapEx().GetSizeBytes();
+
+ for (auto const& pAnimationBitmap : maList)
+ {
+ nSizeBytes += pAnimationBitmap->maBitmapEx.GetSizeBytes();
+ }
+
+ return nSizeBytes;
+}
+
+BitmapChecksum Animation::GetChecksum() const
+{
+ SVBT32 aBT32;
+ BitmapChecksumOctetArray aBCOA;
+ BitmapChecksum nCrc = GetBitmapEx().GetChecksum();
+
+ UInt32ToSVBT32(maList.size(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(maGlobalSize.Width(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(maGlobalSize.Height(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ for (auto const& i : maList)
+ {
+ BCToBCOA(i->GetChecksum(), aBCOA);
+ nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+ }
+
+ return nCrc;
+}
+
+bool Animation::Start(OutputDevice& rOut, const Point& rDestPt, const Size& rDestSz,
+ tools::Long nExtraData, OutputDevice* pFirstFrameOutDev)
+{
+ bool bRet = false;
+
+ if (!maList.empty())
+ {
+ if ((rOut.GetOutDevType() == OUTDEV_WINDOW) && !mbLoopTerminated
+ && (ANIMATION_TIMEOUT_ON_CLICK != maList[mnPos]->mnWait))
+ {
+ bool differs = true;
+
+ auto itAnimView = std::find_if(
+ maViewList.begin(), maViewList.end(),
+ [&rOut, nExtraData](const std::unique_ptr<ImplAnimView>& pAnimView) -> bool {
+ return pAnimView->matches(&rOut, nExtraData);
+ });
+
+ if (itAnimView != maViewList.end())
+ {
+ if ((*itAnimView)->getOutPos() == rDestPt
+ && (*itAnimView)->getOutSizePix() == rOut.LogicToPixel(rDestSz))
+ {
+ (*itAnimView)->repaint();
+ differs = false;
+ }
+ else
+ maViewList.erase(itAnimView);
+ }
+
+ if (maViewList.empty())
+ {
+ maTimer.Stop();
+ mbIsInAnimation = false;
+ mnPos = 0;
+ }
+
+ if (differs)
+ maViewList.emplace_back(
+ new ImplAnimView(this, &rOut, rDestPt, rDestSz, nExtraData, pFirstFrameOutDev));
+
+ if (!mbIsInAnimation)
+ {
+ ImplRestartTimer(maList[mnPos]->mnWait);
+ mbIsInAnimation = true;
+ }
+ }
+ else
+ Draw(rOut, rDestPt, rDestSz);
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+void Animation::Stop(const OutputDevice* pOut, tools::Long nExtraData)
+{
+ maViewList.erase(std::remove_if(maViewList.begin(), maViewList.end(),
+ [=](const std::unique_ptr<ImplAnimView>& pAnimView) -> bool {
+ return pAnimView->matches(pOut, nExtraData);
+ }),
+ maViewList.end());
+
+ if (maViewList.empty())
+ {
+ maTimer.Stop();
+ mbIsInAnimation = false;
+ }
+}
+
+void Animation::Draw(OutputDevice& rOut, const Point& rDestPt) const
+{
+ Draw(rOut, rDestPt, rOut.PixelToLogic(maGlobalSize));
+}
+
+void Animation::Draw(OutputDevice& rOut, const Point& rDestPt, const Size& rDestSz) const
+{
+ const size_t nCount = maList.size();
+
+ if (!nCount)
+ return;
+
+ AnimationBitmap* pObj = maList[std::min(mnPos, nCount - 1)].get();
+
+ if (rOut.GetConnectMetaFile() || (rOut.GetOutDevType() == OUTDEV_PRINTER))
+ maList[0]->maBitmapEx.Draw(&rOut, rDestPt, rDestSz);
+ else if (ANIMATION_TIMEOUT_ON_CLICK == pObj->mnWait)
+ pObj->maBitmapEx.Draw(&rOut, rDestPt, rDestSz);
+ else
+ {
+ const size_t nOldPos = mnPos;
+ if (mbLoopTerminated)
+ const_cast<Animation*>(this)->mnPos = nCount - 1;
+
+ {
+ ImplAnimView{ const_cast<Animation*>(this), &rOut, rDestPt, rDestSz, 0 };
+ }
+
+ const_cast<Animation*>(this)->mnPos = nOldPos;
+ }
+}
+
+namespace
+{
+constexpr sal_uLong constMinTimeout = 2;
+}
+
+void Animation::ImplRestartTimer(sal_uLong nTimeout)
+{
+ maTimer.SetTimeout(std::max(nTimeout, constMinTimeout) * 10);
+ maTimer.Start();
+}
+
+IMPL_LINK_NOARG(Animation, ImplTimeoutHdl, Timer*, void)
+{
+ const size_t nAnimCount = maList.size();
+
+ if (nAnimCount)
+ {
+ bool bGlobalPause = false;
+
+ if (maNotifyLink.IsSet())
+ {
+ std::vector<std::unique_ptr<AInfo>> aAInfoList;
+ // create AInfo-List
+ for (auto const& i : maViewList)
+ aAInfoList.emplace_back(i->createAInfo());
+
+ maNotifyLink.Call(this);
+
+ // set view state from AInfo structure
+ for (auto& pAInfo : aAInfoList)
+ {
+ ImplAnimView* pView = nullptr;
+ if (!pAInfo->pViewData)
+ {
+ pView = new ImplAnimView(this, pAInfo->pOutDev, pAInfo->aStartOrg,
+ pAInfo->aStartSize, pAInfo->nExtraData);
+
+ maViewList.push_back(std::unique_ptr<ImplAnimView>(pView));
+ }
+ else
+ pView = static_cast<ImplAnimView*>(pAInfo->pViewData);
+
+ pView->pause(pAInfo->bPause);
+ pView->setMarked(true);
+ }
+
+ // delete all unmarked views
+ auto removeStart = std::remove_if(maViewList.begin(), maViewList.end(),
+ [](const auto& pView) { return !pView->isMarked(); });
+ maViewList.erase(removeStart, maViewList.cend());
+
+ // check if every remaining view is paused
+ bGlobalPause = std::all_of(maViewList.cbegin(), maViewList.cend(),
+ [](const auto& pView) { return pView->isPause(); });
+
+ // reset marked state
+ std::for_each(maViewList.cbegin(), maViewList.cend(),
+ [](const auto& pView) { pView->setMarked(false); });
+ }
+
+ if (maViewList.empty())
+ Stop();
+ else if (bGlobalPause)
+ ImplRestartTimer(10);
+ else
+ {
+ AnimationBitmap* pStepBmp = (++mnPos < maList.size()) ? maList[mnPos].get() : nullptr;
+
+ if (!pStepBmp)
+ {
+ if (mnLoops == 1)
+ {
+ Stop();
+ mbLoopTerminated = true;
+ mnPos = nAnimCount - 1;
+ maBitmapEx = maList[mnPos]->maBitmapEx;
+ return;
+ }
+ else
+ {
+ if (mnLoops)
+ mnLoops--;
+
+ mnPos = 0;
+ pStepBmp = maList[mnPos].get();
+ }
+ }
+
+ // Paint all views.
+ std::for_each(maViewList.cbegin(), maViewList.cend(),
+ [this](const auto& pView) { pView->draw(mnPos); });
+ /*
+ * If a view is marked, remove the view, because
+ * area of output lies out of display area of window.
+ * Mark state is set from view itself.
+ */
+ auto removeStart = std::remove_if(maViewList.begin(), maViewList.end(),
+ [](const auto& pView) { return pView->isMarked(); });
+ maViewList.erase(removeStart, maViewList.cend());
+
+ // stop or restart timer
+ if (maViewList.empty())
+ Stop();
+ else
+ ImplRestartTimer(pStepBmp->mnWait);
+ }
+ }
+ else
+ Stop();
+}
+
+bool Animation::Insert(const AnimationBitmap& rStepBmp)
+{
+ bool bRet = false;
+
+ if (!IsInAnimation())
+ {
+ tools::Rectangle aGlobalRect(Point(), maGlobalSize);
+
+ maGlobalSize
+ = aGlobalRect.Union(tools::Rectangle(rStepBmp.maPositionPixel, rStepBmp.maSizePixel))
+ .GetSize();
+ maList.emplace_back(new AnimationBitmap(rStepBmp));
+
+ // As a start, we make the first BitmapEx the replacement BitmapEx
+ if (maList.size() == 1)
+ maBitmapEx = rStepBmp.maBitmapEx;
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+const AnimationBitmap& Animation::Get(sal_uInt16 nAnimation) const
+{
+ SAL_WARN_IF((nAnimation >= maList.size()), "vcl", "No object at this position");
+ return *maList[nAnimation];
+}
+
+void Animation::Replace(const AnimationBitmap& rNewAnimationBitmap, sal_uInt16 nAnimation)
+{
+ SAL_WARN_IF((nAnimation >= maList.size()), "vcl", "No object at this position");
+
+ maList[nAnimation].reset(new AnimationBitmap(rNewAnimationBitmap));
+
+ // If we insert at first position we also need to
+ // update the replacement BitmapEx
+ if ((!nAnimation && (!mbLoopTerminated || (maList.size() == 1)))
+ || ((nAnimation == maList.size() - 1) && mbLoopTerminated))
+ {
+ maBitmapEx = rNewAnimationBitmap.maBitmapEx;
+ }
+}
+
+void Animation::SetLoopCount(const sal_uInt32 nLoopCount)
+{
+ mnLoopCount = nLoopCount;
+ ResetLoopCount();
+}
+
+void Animation::ResetLoopCount()
+{
+ mnLoops = mnLoopCount;
+ mbLoopTerminated = false;
+}
+
+void Animation::Convert(BmpConversion eConversion)
+{
+ SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
+
+ bool bRet;
+
+ if (!IsInAnimation() && !maList.empty())
+ {
+ bRet = true;
+
+ for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
+ bRet = maList[i]->maBitmapEx.Convert(eConversion);
+
+ maBitmapEx.Convert(eConversion);
+ }
+}
+
+bool Animation::ReduceColors(sal_uInt16 nNewColorCount)
+{
+ SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
+
+ bool bRet;
+
+ if (!IsInAnimation() && !maList.empty())
+ {
+ bRet = true;
+
+ for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
+ {
+ bRet = BitmapFilter::Filter(maList[i]->maBitmapEx,
+ BitmapColorQuantizationFilter(nNewColorCount));
+ }
+
+ BitmapFilter::Filter(maBitmapEx, BitmapColorQuantizationFilter(nNewColorCount));
+ }
+ else
+ {
+ bRet = false;
+ }
+
+ return bRet;
+}
+
+bool Animation::Invert()
+{
+ SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
+
+ bool bRet;
+
+ if (!IsInAnimation() && !maList.empty())
+ {
+ bRet = true;
+
+ for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
+ bRet = maList[i]->maBitmapEx.Invert();
+
+ maBitmapEx.Invert();
+ }
+ else
+ bRet = false;
+
+ return bRet;
+}
+
+void Animation::Mirror(BmpMirrorFlags nMirrorFlags)
+{
+ SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
+
+ bool bRet;
+
+ if (IsInAnimation() || maList.empty())
+ return;
+
+ bRet = true;
+
+ if (nMirrorFlags == BmpMirrorFlags::NONE)
+ return;
+
+ for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
+ {
+ AnimationBitmap* pStepBmp = maList[i].get();
+ bRet = pStepBmp->maBitmapEx.Mirror(nMirrorFlags);
+ if (bRet)
+ {
+ if (nMirrorFlags & BmpMirrorFlags::Horizontal)
+ pStepBmp->maPositionPixel.setX(maGlobalSize.Width() - pStepBmp->maPositionPixel.X()
+ - pStepBmp->maSizePixel.Width());
+
+ if (nMirrorFlags & BmpMirrorFlags::Vertical)
+ pStepBmp->maPositionPixel.setY(maGlobalSize.Height() - pStepBmp->maPositionPixel.Y()
+ - pStepBmp->maSizePixel.Height());
+ }
+ }
+
+ maBitmapEx.Mirror(nMirrorFlags);
+}
+
+void Animation::Adjust(short nLuminancePercent, short nContrastPercent, short nChannelRPercent,
+ short nChannelGPercent, short nChannelBPercent, double fGamma, bool bInvert)
+{
+ SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
+
+ bool bRet;
+
+ if (IsInAnimation() || maList.empty())
+ return;
+
+ bRet = true;
+
+ for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
+ {
+ bRet = maList[i]->maBitmapEx.Adjust(nLuminancePercent, nContrastPercent, nChannelRPercent,
+ nChannelGPercent, nChannelBPercent, fGamma, bInvert);
+ }
+
+ maBitmapEx.Adjust(nLuminancePercent, nContrastPercent, nChannelRPercent, nChannelGPercent,
+ nChannelBPercent, fGamma, bInvert);
+}
+
+SvStream& WriteAnimation(SvStream& rOStm, const Animation& rAnimation)
+{
+ const sal_uInt16 nCount = rAnimation.Count();
+
+ if (nCount)
+ {
+ const sal_uInt32 nDummy32 = 0;
+
+ // If no BitmapEx was set we write the first Bitmap of
+ // the Animation
+ if (rAnimation.GetBitmapEx().GetBitmap().IsEmpty())
+ WriteDIBBitmapEx(rAnimation.Get(0).maBitmapEx, rOStm);
+ else
+ WriteDIBBitmapEx(rAnimation.GetBitmapEx(), rOStm);
+
+ // Write identifier ( SDANIMA1 )
+ rOStm.WriteUInt32(0x5344414e).WriteUInt32(0x494d4931);
+
+ for (sal_uInt16 i = 0; i < nCount; i++)
+ {
+ const AnimationBitmap& rAnimationBitmap = rAnimation.Get(i);
+ const sal_uInt16 nRest = nCount - i - 1;
+
+ // Write AnimationBitmap
+ WriteDIBBitmapEx(rAnimationBitmap.maBitmapEx, rOStm);
+ tools::GenericTypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(rAnimationBitmap.maPositionPixel);
+ aSerializer.writeSize(rAnimationBitmap.maSizePixel);
+ aSerializer.writeSize(rAnimation.maGlobalSize);
+ rOStm.WriteUInt16((ANIMATION_TIMEOUT_ON_CLICK == rAnimationBitmap.mnWait)
+ ? 65535
+ : rAnimationBitmap.mnWait);
+ rOStm.WriteUInt16(static_cast<sal_uInt16>(rAnimationBitmap.meDisposal));
+ rOStm.WriteBool(rAnimationBitmap.mbUserInput);
+ rOStm.WriteUInt32(rAnimation.mnLoopCount);
+ rOStm.WriteUInt32(nDummy32); // Unused
+ rOStm.WriteUInt32(nDummy32); // Unused
+ rOStm.WriteUInt32(nDummy32); // Unused
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStm, ""); // dummy
+ rOStm.WriteUInt16(nRest); // Count of remaining structures
+ }
+ }
+
+ return rOStm;
+}
+
+SvStream& ReadAnimation(SvStream& rIStm, Animation& rAnimation)
+{
+ sal_uLong nStmPos;
+ sal_uInt32 nAnimMagic1, nAnimMagic2;
+ SvStreamEndian nOldFormat = rIStm.GetEndian();
+ bool bReadAnimations = false;
+
+ rIStm.SetEndian(SvStreamEndian::LITTLE);
+ nStmPos = rIStm.Tell();
+ rIStm.ReadUInt32(nAnimMagic1).ReadUInt32(nAnimMagic2);
+
+ rAnimation.Clear();
+
+ // If the BitmapEx at the beginning have already been read (by Graphic)
+ // we can start reading the AnimationBitmaps right away
+ if ((nAnimMagic1 == 0x5344414e) && (nAnimMagic2 == 0x494d4931) && !rIStm.GetError())
+ bReadAnimations = true;
+ // Else, we try reading the Bitmap(-Ex)
+ else
+ {
+ rIStm.Seek(nStmPos);
+ ReadDIBBitmapEx(rAnimation.maBitmapEx, rIStm);
+ nStmPos = rIStm.Tell();
+ rIStm.ReadUInt32(nAnimMagic1).ReadUInt32(nAnimMagic2);
+
+ if ((nAnimMagic1 == 0x5344414e) && (nAnimMagic2 == 0x494d4931) && !rIStm.GetError())
+ bReadAnimations = true;
+ else
+ rIStm.Seek(nStmPos);
+ }
+
+ // Read AnimationBitmaps
+ if (bReadAnimations)
+ {
+ AnimationBitmap aAnimationBitmap;
+ sal_uInt32 nTmp32;
+ sal_uInt16 nTmp16;
+ bool cTmp;
+
+ do
+ {
+ ReadDIBBitmapEx(aAnimationBitmap.maBitmapEx, rIStm);
+ tools::GenericTypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(aAnimationBitmap.maPositionPixel);
+ aSerializer.readSize(aAnimationBitmap.maSizePixel);
+ aSerializer.readSize(rAnimation.maGlobalSize);
+ rIStm.ReadUInt16(nTmp16);
+ aAnimationBitmap.mnWait = ((65535 == nTmp16) ? ANIMATION_TIMEOUT_ON_CLICK : nTmp16);
+ rIStm.ReadUInt16(nTmp16);
+ aAnimationBitmap.meDisposal = static_cast<Disposal>(nTmp16);
+ rIStm.ReadCharAsBool(cTmp);
+ aAnimationBitmap.mbUserInput = cTmp;
+ rIStm.ReadUInt32(rAnimation.mnLoopCount);
+ rIStm.ReadUInt32(nTmp32); // Unused
+ rIStm.ReadUInt32(nTmp32); // Unused
+ rIStm.ReadUInt32(nTmp32); // Unused
+ read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm); // Unused
+ rIStm.ReadUInt16(nTmp16); // The rest to read
+
+ rAnimation.Insert(aAnimationBitmap);
+ } while (nTmp16 && !rIStm.GetError());
+
+ rAnimation.ResetLoopCount();
+ }
+
+ rIStm.SetEndian(nOldFormat);
+
+ return rIStm;
+}
+
+AInfo::AInfo()
+ : pOutDev(nullptr)
+ , pViewData(nullptr)
+ , nExtraData(0)
+ , bPause(false)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/animate/AnimationBitmap.cxx b/vcl/source/animate/AnimationBitmap.cxx
new file mode 100644
index 000000000..8dfb466e1
--- /dev/null
+++ b/vcl/source/animate/AnimationBitmap.cxx
@@ -0,0 +1,54 @@
+/* -*- 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 <o3tl/underlyingenumvalue.hxx>
+#include <tools/solar.h>
+#include <vcl/animate/AnimationBitmap.hxx>
+
+BitmapChecksum AnimationBitmap::GetChecksum() const
+{
+ BitmapChecksum nCrc = maBitmapEx.GetChecksum();
+ SVBT32 aBT32;
+
+ Int32ToSVBT32(maPositionPixel.X(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(maPositionPixel.Y(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(maSizePixel.Width(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(maSizePixel.Height(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(mnWait, aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ UInt32ToSVBT32(o3tl::to_underlying(meDisposal), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ UInt32ToSVBT32(sal_uInt32(mbUserInput), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ return nCrc;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/ITiledRenderable.cxx b/vcl/source/app/ITiledRenderable.cxx
new file mode 100644
index 000000000..52a4ab0f5
--- /dev/null
+++ b/vcl/source/app/ITiledRenderable.cxx
@@ -0,0 +1,77 @@
+/* -*- 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/.
+ */
+
+#include <vcl/ITiledRenderable.hxx>
+
+namespace vcl
+{
+/*
+ * Map directly to css cursor styles to avoid further mapping in the client.
+ * Gtk (via gdk_cursor_new_from_name) also supports the same css cursor styles.
+ *
+ * This was created partially with help of the mappings in gtkdata.cxx.
+ * The list is incomplete as some cursor style simply aren't supported
+ * by css, it might turn out to be worth mapping some of these missing cursors
+ * to available cursors?
+ */
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4592)
+#endif
+const std::map<PointerStyle, OString> gaLOKPointerMap{
+ { PointerStyle::Arrow, "default" },
+ // PointerStyle::Null ?
+ { PointerStyle::Wait, "wait" },
+ { PointerStyle::Text, "text" },
+ { PointerStyle::Help, "help" },
+ { PointerStyle::Cross, "crosshair" },
+ { PointerStyle::Fill, "fill" },
+ { PointerStyle::Move, "move" },
+ { PointerStyle::NSize, "n-resize" },
+ { PointerStyle::SSize, "s-resize" },
+ { PointerStyle::WSize, "w-resize" },
+ { PointerStyle::ESize, "e-resize" },
+ { PointerStyle::NWSize, "ne-resize" },
+ { PointerStyle::NESize, "ne-resize" },
+ { PointerStyle::SWSize, "sw-resize" },
+ { PointerStyle::SESize, "se-resize" },
+ // WindowNSize through WindowSESize
+ { PointerStyle::HSplit, "col-resize" },
+ { PointerStyle::VSplit, "row-resize" },
+ { PointerStyle::HSizeBar, "col-resize" },
+ { PointerStyle::VSizeBar, "row-resize" },
+ { PointerStyle::Hand, "grab" },
+ { PointerStyle::RefHand, "pointer" },
+ // Pen, Magnify, Fill, Rotate
+ // HShear, VShear
+ // Mirror, Crook, Crop, MovePoint, MoveBezierWeight
+ // MoveData
+ { PointerStyle::CopyData, "copy" },
+ { PointerStyle::LinkData, "alias" },
+ // MoveDataLink, CopyDataLink
+ //MoveFile, CopyFile, LinkFile
+ // MoveFileLink, CopyFileLink, MoveFiless, CopyFiles
+ { PointerStyle::NotAllowed, "not-allowed" },
+ // DrawLine through DrawCaption
+ // Chart, Detective, PivotCol, PivotRow, PivotField, Chain, ChainNotAllowed
+ // TimeEventMove, TimeEventSize
+ // AutoScrollN through AutoScrollNSWE
+ // Airbrush
+ { PointerStyle::TextVertical, "vertical-text" }
+ // Pivot Delete, TabSelectS through TabSelectSW
+ // PaintBrush, HideWhiteSpace, ShowWhiteSpace
+};
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+ITiledRenderable::~ITiledRenderable() {}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/IconThemeInfo.cxx b/vcl/source/app/IconThemeInfo.cxx
new file mode 100644
index 000000000..4166ae084
--- /dev/null
+++ b/vcl/source/app/IconThemeInfo.cxx
@@ -0,0 +1,180 @@
+/* -*- 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/.
+ */
+
+#include <vcl/IconThemeInfo.hxx>
+#include <rtl/character.hxx>
+
+#include <stdexcept>
+#include <algorithm>
+
+// constants for theme ids and display names. (The theme id for high contrast is used
+// outside of this class and hence made public in IconThemeInfo.)
+
+namespace {
+
+constexpr OUStringLiteral HELPIMG_FAKE_THEME(u"helpimg");
+
+OUString
+filename_from_url(std::u16string_view url)
+{
+ size_t slashPosition = url.rfind( '/' );
+ if (slashPosition == std::u16string_view::npos) {
+ return OUString();
+ }
+ OUString filename( url.substr( slashPosition+1 ) );
+ return filename;
+}
+
+} // end anonymous namespace
+
+namespace vcl {
+
+const sal_Unicode ICON_THEME_PACKAGE_PREFIX[] = u"images_";
+
+const sal_Unicode EXTENSION_FOR_ICON_PACKAGES[] = u".zip";
+
+IconThemeInfo::IconThemeInfo()
+{
+}
+
+IconThemeInfo::IconThemeInfo(const OUString& urlToFile)
+: mUrlToFile(urlToFile)
+{
+ OUString filename = filename_from_url(urlToFile);
+ if (filename.isEmpty()) {
+ throw std::runtime_error("invalid URL passed to IconThemeInfo()");
+ }
+
+ mThemeId = FileNameToThemeId(filename);
+ mDisplayName = ThemeIdToDisplayName(mThemeId);
+
+}
+
+/*static*/ Size
+IconThemeInfo::SizeByThemeName(std::u16string_view themeName)
+{
+ if (themeName == u"galaxy") { //kept for compiler because of unused parameter 'themeName'
+ return Size( 26, 26 );
+ }
+ else {
+ return Size( 24, 24 );
+ }
+}
+
+/*static*/ bool
+IconThemeInfo::UrlCanBeParsed(std::u16string_view url)
+{
+ OUString fname = filename_from_url(url);
+ if (fname.isEmpty()) {
+ return false;
+ }
+
+ if (!fname.startsWithIgnoreAsciiCase(ICON_THEME_PACKAGE_PREFIX)) {
+ return false;
+ }
+
+ if (!fname.endsWithIgnoreAsciiCase(EXTENSION_FOR_ICON_PACKAGES)) {
+ return false;
+ }
+
+ if (fname.indexOf(HELPIMG_FAKE_THEME) != -1 ) {
+ return false;
+ }
+
+ return true;
+}
+
+/*static*/ OUString
+IconThemeInfo::FileNameToThemeId(std::u16string_view filename)
+{
+ OUString r;
+ size_t positionOfLastDot = filename.rfind(EXTENSION_FOR_ICON_PACKAGES);
+ if (positionOfLastDot == std::u16string_view::npos) { // means index not found
+ throw std::runtime_error("IconThemeInfo::FileNameToThemeId() called with invalid filename.");
+ }
+ size_t positionOfFirstUnderscore = filename.find(ICON_THEME_PACKAGE_PREFIX);
+ if (positionOfFirstUnderscore == std::u16string_view::npos) { // means index not found. Use the whole name instead
+ throw std::runtime_error("IconThemeInfo::FileNameToThemeId() called with invalid filename.");
+ }
+ positionOfFirstUnderscore += RTL_CONSTASCII_LENGTH(ICON_THEME_PACKAGE_PREFIX);
+ r = filename.substr(positionOfFirstUnderscore, positionOfLastDot - positionOfFirstUnderscore);
+ return r;
+}
+
+/*static*/ OUString
+IconThemeInfo::ThemeIdToDisplayName(const OUString& themeId)
+{
+ if (themeId.isEmpty()) {
+ throw std::runtime_error("IconThemeInfo::ThemeIdToDisplayName() called with invalid id.");
+ }
+
+ // Strip _svg and _dark filename "extensions"
+ OUString aDisplayName = themeId;
+
+ bool bIsSvg = aDisplayName.endsWith("_svg", &aDisplayName);
+ bool bIsDark = aDisplayName.endsWith("_dark", &aDisplayName);
+ if (!bIsSvg && bIsDark)
+ bIsSvg = aDisplayName.endsWith("_svg", &aDisplayName);
+
+ // make the first letter uppercase
+ sal_Unicode firstLetter = aDisplayName[0];
+ if (rtl::isAsciiLowerCase(firstLetter))
+ {
+ aDisplayName = OUStringChar(sal_Unicode(rtl::toAsciiUpperCase(firstLetter))) + aDisplayName.subView(1);
+ }
+
+ // replacing underscores with spaces of multi words pack name.
+ aDisplayName = aDisplayName.replace('_', ' ');
+
+ if (bIsSvg && bIsDark)
+ aDisplayName += " (SVG + dark)";
+ else if (bIsSvg)
+ aDisplayName += " (SVG)";
+ else if (bIsDark)
+ aDisplayName += " (dark)";
+
+ return aDisplayName;
+}
+
+namespace
+{
+ class SameTheme
+ {
+ private:
+ const OUString& m_rThemeId;
+ public:
+ explicit SameTheme(const OUString &rThemeId) : m_rThemeId(rThemeId) {}
+ bool operator()(const vcl::IconThemeInfo &rInfo)
+ {
+ return m_rThemeId == rInfo.GetThemeId();
+ }
+ };
+}
+
+/*static*/ const vcl::IconThemeInfo&
+IconThemeInfo::FindIconThemeById(const std::vector<vcl::IconThemeInfo>& themes, const OUString& themeId)
+{
+ std::vector<vcl::IconThemeInfo>::const_iterator it = std::find_if(themes.begin(), themes.end(),
+ SameTheme(themeId));
+ if (it == themes.end())
+ {
+ throw std::runtime_error("Could not find theme id in theme vector.");
+ }
+ return *it;
+}
+
+/*static*/ bool
+IconThemeInfo::IconThemeIsInVector(const std::vector<vcl::IconThemeInfo>& themes, const OUString& themeId)
+{
+ return std::any_of(themes.begin(), themes.end(), SameTheme(themeId));
+}
+
+} // end namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/IconThemeScanner.cxx b/vcl/source/app/IconThemeScanner.cxx
new file mode 100644
index 000000000..c8f6a1ac7
--- /dev/null
+++ b/vcl/source/app/IconThemeScanner.cxx
@@ -0,0 +1,216 @@
+/* -*- 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/.
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <deque>
+
+#include <IconThemeScanner.hxx>
+
+#include <osl/file.hxx>
+#include <salhelper/linkhelper.hxx>
+#include <unotools/pathoptions.hxx>
+#include <vcl/IconThemeInfo.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace vcl {
+
+namespace {
+
+// set the status of a file. Returns false if the status could not be determined.
+bool set_file_status(osl::FileStatus& status, const OUString& file)
+{
+ osl::DirectoryItem dirItem;
+ osl::FileBase::RC retvalGet = osl::DirectoryItem::get(file, dirItem);
+ if (retvalGet != osl::FileBase::E_None) {
+ SAL_WARN("vcl.app", "Could not determine status for file '" << file << "'.");
+ return false;
+ }
+ osl::FileBase::RC retvalStatus = dirItem.getFileStatus(status);
+ if (retvalStatus != osl::FileBase::E_None) {
+ SAL_WARN("vcl.app", "Could not determine status for file '" << file << "'.");
+ return false;
+ }
+ return true;
+}
+
+OUString convert_to_absolute_path(const OUString& path)
+{
+ salhelper::LinkResolver resolver(0);
+ osl::FileBase::RC rc = resolver.fetchFileStatus(path);
+ if (rc != osl::FileBase::E_None) {
+ SAL_WARN("vcl.app", "Could not resolve path '" << path << "' to search for icon themes.");
+ if (rc == osl::FileBase::E_MULTIHOP)
+ {
+ throw std::runtime_error("Provided a recursive symlink to an icon theme directory that could not be resolved.");
+ }
+ }
+ return resolver.m_aStatus.getFileURL();
+}
+
+}
+
+IconThemeScanner::IconThemeScanner()
+{}
+
+void IconThemeScanner::ScanDirectoryForIconThemes(std::u16string_view paths)
+{
+ mFoundIconThemes.clear();
+
+ std::deque<OUString> aPaths;
+
+ sal_Int32 nIndex = 0;
+ do
+ {
+ aPaths.push_front(OUString(o3tl::getToken(paths, 0, ';', nIndex)));
+ }
+ while (nIndex >= 0);
+
+ for (const auto& path : aPaths)
+ {
+ osl::FileStatus fileStatus(osl_FileStatus_Mask_Type);
+ bool couldSetFileStatus = set_file_status(fileStatus, path);
+ if (!couldSetFileStatus) {
+ continue;
+ }
+
+ if (!fileStatus.isDirectory()) {
+ SAL_INFO("vcl.app", "Cannot search for icon themes in '"<< path << "'. It is not a directory.");
+ continue;
+ }
+
+ std::vector<OUString> iconThemePaths = ReadIconThemesFromPath(path);
+ if (iconThemePaths.empty()) {
+ SAL_WARN("vcl.app", "Could not find any icon themes in the provided directory ('" <<path<<"'.");
+ continue;
+ }
+ for (auto const& iconThemePath : iconThemePaths)
+ {
+ AddIconThemeByPath(iconThemePath);
+ }
+ }
+}
+
+bool
+IconThemeScanner::AddIconThemeByPath(const OUString &url)
+{
+ if (!IconThemeInfo::UrlCanBeParsed(url)) {
+ return false;
+ }
+ SAL_INFO("vcl.app", "Found a file that seems to be an icon theme: '" << url << "'" );
+ IconThemeInfo newTheme(url);
+ mFoundIconThemes.push_back(newTheme);
+ SAL_INFO("vcl.app", "Adding the file as '" << newTheme.GetDisplayName() <<
+ "' with id '" << newTheme.GetThemeId() << "'.");
+ return true;
+}
+
+/*static*/ std::vector<OUString>
+IconThemeScanner::ReadIconThemesFromPath(const OUString& dir)
+{
+ std::vector<OUString> found;
+ SAL_INFO("vcl.app", "Scanning directory '" << dir << " for icon themes.");
+
+ osl::Directory dirToScan(dir);
+ osl::FileBase::RC retvalOpen = dirToScan.open();
+ if (retvalOpen != osl::FileBase::E_None) {
+ return found;
+ }
+
+ osl::DirectoryItem directoryItem;
+ while (dirToScan.getNextItem(directoryItem) == osl::FileBase::E_None) {
+ osl::FileStatus status(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL | osl_FileStatus_Mask_FileName);
+ osl::FileBase::RC retvalStatus = directoryItem.getFileStatus(status);
+ if (retvalStatus != osl::FileBase::E_None) {
+ continue;
+ }
+
+ OUString filename = convert_to_absolute_path(status.getFileURL());
+ if (!FileIsValidIconTheme(filename)) {
+ continue;
+ }
+ found.push_back(filename);
+ }
+ return found;
+}
+
+/*static*/ bool
+IconThemeScanner::FileIsValidIconTheme(const OUString& filename)
+{
+ // check whether we can construct an IconThemeInfo from it
+ if (!IconThemeInfo::UrlCanBeParsed(filename)) {
+ SAL_INFO("vcl.app", "File '" << filename << "' does not seem to be an icon theme.");
+ return false;
+ }
+
+ osl::FileStatus fileStatus(osl_FileStatus_Mask_Type);
+ bool couldSetFileStatus = set_file_status(fileStatus, filename);
+ if (!couldSetFileStatus) {
+ return false;
+ }
+
+ if (!fileStatus.isRegular()) {
+ return false;
+ }
+ return true;
+}
+
+bool
+IconThemeScanner::IconThemeIsInstalled(const OUString& themeId) const
+{
+ return IconThemeInfo::IconThemeIsInVector(mFoundIconThemes, themeId);
+}
+
+/*static*/ std::shared_ptr<IconThemeScanner>
+IconThemeScanner::Create(std::u16string_view path)
+{
+ std::shared_ptr<IconThemeScanner> retval(new IconThemeScanner);
+ retval->ScanDirectoryForIconThemes(path);
+ return retval;
+}
+
+/*static*/ OUString
+IconThemeScanner::GetStandardIconThemePath()
+{
+ SvtPathOptions aPathOptions;
+ return aPathOptions.GetIconsetPath();
+}
+
+namespace
+{
+ class SameTheme
+ {
+ private:
+ const OUString& m_rThemeId;
+ public:
+ explicit SameTheme(const OUString &rThemeId) : m_rThemeId(rThemeId) {}
+ bool operator()(const vcl::IconThemeInfo &rInfo)
+ {
+ return m_rThemeId == rInfo.GetThemeId();
+ }
+ };
+}
+
+const vcl::IconThemeInfo&
+IconThemeScanner::GetIconThemeInfo(const OUString& themeId)
+{
+ std::vector<IconThemeInfo>::iterator info = std::find_if(mFoundIconThemes.begin(), mFoundIconThemes.end(),
+ SameTheme(themeId));
+ if (info == mFoundIconThemes.end()) {
+ SAL_WARN("vcl.app", "Requested information for icon theme with id '" << themeId
+ << "' which does not exist.");
+ throw std::runtime_error("Requested information on not-installed icon theme");
+ }
+ return *info;
+}
+
+} // end namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/IconThemeSelector.cxx b/vcl/source/app/IconThemeSelector.cxx
new file mode 100644
index 000000000..fd32008d9
--- /dev/null
+++ b/vcl/source/app/IconThemeSelector.cxx
@@ -0,0 +1,186 @@
+/* -*- 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/.
+ */
+
+#include <comphelper/lok.hxx>
+
+#include <IconThemeSelector.hxx>
+
+#include <tools/color.hxx>
+#include <vcl/IconThemeInfo.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <config_mpl.h>
+
+#include <algorithm>
+
+namespace vcl {
+
+namespace {
+
+ class SameTheme
+ {
+ private:
+ const OUString& m_rThemeId;
+ public:
+ explicit SameTheme(const OUString &rThemeId) : m_rThemeId(rThemeId) {}
+ bool operator()(const vcl::IconThemeInfo &rInfo)
+ {
+ return m_rThemeId == rInfo.GetThemeId();
+ }
+ };
+
+bool icon_theme_is_in_installed_themes(const OUString& theme,
+ const std::vector<IconThemeInfo>& installedThemes)
+{
+ return std::any_of(installedThemes.begin(), installedThemes.end(),
+ SameTheme(theme));
+}
+
+} // end anonymous namespace
+
+IconThemeSelector::IconThemeSelector()
+ : mUseHighContrastTheme(false)
+ , mPreferDarkIconTheme(false)
+{
+}
+
+/*static*/ OUString
+IconThemeSelector::GetIconThemeForDesktopEnvironment(const OUString& desktopEnvironment)
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return "colibre";
+
+#ifdef _WIN32
+ (void)desktopEnvironment;
+ return "colibre";
+#else
+ OUString r;
+ if ( desktopEnvironment.equalsIgnoreAsciiCase("plasma5") ||
+ desktopEnvironment.equalsIgnoreAsciiCase("lxqt") ) {
+ r = "breeze";
+ }
+ else if ( desktopEnvironment.equalsIgnoreAsciiCase("macosx") ) {
+ r = "sukapura";
+ }
+ else if ( desktopEnvironment.equalsIgnoreAsciiCase("gnome") ||
+ desktopEnvironment.equalsIgnoreAsciiCase("mate") ||
+ desktopEnvironment.equalsIgnoreAsciiCase("unity") ) {
+ r = "elementary";
+ } else
+ {
+ r = FALLBACK_ICON_THEME_ID;
+ }
+ return r;
+#endif // _WIN32
+}
+
+OUString
+IconThemeSelector::SelectIconThemeForDesktopEnvironment(
+ const std::vector<IconThemeInfo>& installedThemes,
+ const OUString& desktopEnvironment) const
+{
+ if (!mPreferredIconTheme.isEmpty()) {
+ if (icon_theme_is_in_installed_themes(mPreferredIconTheme, installedThemes)) {
+ return mPreferredIconTheme;
+ }
+ //if a dark variant is preferred, and we didn't have an exact match, then try our one and only dark theme
+ if (mPreferDarkIconTheme && icon_theme_is_in_installed_themes("breeze_dark", installedThemes)) {
+ return "breeze_dark";
+ }
+ }
+
+ OUString themeForDesktop = GetIconThemeForDesktopEnvironment(desktopEnvironment);
+ if (icon_theme_is_in_installed_themes(themeForDesktop, installedThemes)) {
+ return themeForDesktop;
+ }
+
+ return ReturnFallback(installedThemes);
+}
+
+OUString
+IconThemeSelector::SelectIconTheme(
+ const std::vector<IconThemeInfo>& installedThemes,
+ const OUString& theme) const
+{
+ if (mUseHighContrastTheme) {
+ const Color aCol(Application::GetSettings().GetStyleSettings().GetWindowColor());
+ const OUString name(aCol.IsDark() ? OUString(IconThemeInfo::HIGH_CONTRAST_ID_DARK)
+ : OUString(IconThemeInfo::HIGH_CONTRAST_ID_BRIGHT));
+ if (icon_theme_is_in_installed_themes(name, installedThemes)) {
+ return name;
+ }
+ }
+
+ if (icon_theme_is_in_installed_themes(theme, installedThemes)) {
+ return theme;
+ }
+
+ return ReturnFallback(installedThemes);
+}
+
+void
+IconThemeSelector::SetUseHighContrastTheme(bool v)
+{
+ mUseHighContrastTheme = v;
+}
+
+bool
+IconThemeSelector::SetPreferredIconTheme(const OUString& theme, bool bDarkIconTheme)
+{
+ // lower case theme name, and (tdf#120175) replace - with _
+ // see icon-themes/README
+ OUString sIconTheme = theme.toAsciiLowerCase().replace('-','_');
+
+ const bool bChanged = mPreferredIconTheme != sIconTheme || mPreferDarkIconTheme != bDarkIconTheme;
+ if (bChanged)
+ {
+ mPreferredIconTheme = sIconTheme;
+ mPreferDarkIconTheme = bDarkIconTheme;
+ }
+ return bChanged;
+}
+
+bool
+IconThemeSelector::operator==(const vcl::IconThemeSelector& other) const
+{
+ if (this == &other) {
+ return true;
+ }
+ if (mPreferredIconTheme != other.mPreferredIconTheme) {
+ return false;
+ }
+ if (mPreferDarkIconTheme != other.mPreferDarkIconTheme) {
+ return false;
+ }
+ if (mUseHighContrastTheme != other.mUseHighContrastTheme) {
+ return false;
+ }
+ return true;
+}
+
+bool
+IconThemeSelector::operator!=(const vcl::IconThemeSelector& other) const
+{
+ return !(*this == other);
+}
+
+/*static*/ OUString
+IconThemeSelector::ReturnFallback(const std::vector<IconThemeInfo>& installedThemes)
+{
+ if (!installedThemes.empty()) {
+ return installedThemes.front().GetThemeId();
+ }
+ else {
+ return FALLBACK_ICON_THEME_ID;
+ }
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/brand.cxx b/vcl/source/app/brand.cxx
new file mode 100644
index 000000000..57db30d45
--- /dev/null
+++ b/vcl/source/app/brand.cxx
@@ -0,0 +1,78 @@
+/* -*- 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 <string_view>
+
+#include <config_folders.h>
+
+#include <rtl/ustring.hxx>
+#include <rtl/bootstrap.hxx>
+#include <osl/process.h>
+#include <tools/urlobj.hxx>
+#include <tools/stream.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/svapp.hxx>
+
+namespace {
+ bool loadPng( std::u16string_view rPath, BitmapEx &rBitmap)
+ {
+ INetURLObject aObj( rPath );
+ SvFileStream aStrm( aObj.PathToFileName(), StreamMode::STD_READ );
+ if ( !aStrm.GetError() ) {
+ vcl::PngImageReader aReader( aStrm );
+ rBitmap = aReader.read();
+ return !rBitmap.IsEmpty();
+ }
+ else
+ return false;
+ }
+ bool tryLoadPng( std::u16string_view rBaseDir, std::u16string_view rName, BitmapEx& rBitmap )
+ {
+ return loadPng( rtl::OUStringConcatenation(OUString::Concat(rBaseDir) + "/" LIBO_ETC_FOLDER + rName), rBitmap);
+ }
+}
+
+bool Application::LoadBrandBitmap (const char* pName, BitmapEx &rBitmap)
+{
+ // TODO - if we want more flexibility we could add a branding path
+ // in an rc file perhaps fallback to "about.bmp"
+ OUString aBaseDir( "$BRAND_BASE_DIR");
+ rtl::Bootstrap::expandMacros( aBaseDir );
+ OUString aBaseName( "/" + OUString::createFromAscii( pName ) );
+ OUString aPng( ".png" );
+
+ rtl_Locale *pLoc = nullptr;
+ osl_getProcessLocale (&pLoc);
+ LanguageTag aLanguageTag( *pLoc);
+
+ ::std::vector< OUString > aFallbacks( aLanguageTag.getFallbackStrings( true));
+ for (const OUString & aFallback : aFallbacks)
+ {
+ if (tryLoadPng( aBaseDir, OUStringConcatenation(aBaseName + "-" + aFallback + aPng), rBitmap))
+ return true;
+ }
+
+ return tryLoadPng( aBaseDir, OUStringConcatenation(aBaseName + aPng), rBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/customweld.cxx b/vcl/source/app/customweld.cxx
new file mode 100644
index 000000000..14fbef077
--- /dev/null
+++ b/vcl/source/app/customweld.cxx
@@ -0,0 +1,112 @@
+/* -*- 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/.
+ */
+
+#include <vcl/customweld.hxx>
+
+namespace weld
+{
+CustomWidgetController::~CustomWidgetController() {}
+
+IMPL_LINK_NOARG(CustomWidgetController, DragBeginHdl, weld::DrawingArea&, bool)
+{
+ return StartDrag();
+}
+
+CustomWeld::CustomWeld(weld::Builder& rBuilder, const OString& rDrawingId,
+ CustomWidgetController& rWidgetController)
+ : m_rWidgetController(rWidgetController)
+ , m_xDrawingArea(rBuilder.weld_drawing_area(rDrawingId, rWidgetController.CreateAccessible(),
+ rWidgetController.GetUITestFactory(),
+ &rWidgetController))
+{
+ m_rWidgetController.SetDrawingArea(m_xDrawingArea.get());
+ m_xDrawingArea->connect_size_allocate(LINK(this, CustomWeld, DoResize));
+ m_xDrawingArea->connect_draw(LINK(this, CustomWeld, DoPaint));
+ m_xDrawingArea->connect_mouse_press(LINK(this, CustomWeld, DoMouseButtonDown));
+ m_xDrawingArea->connect_mouse_move(LINK(this, CustomWeld, DoMouseMove));
+ m_xDrawingArea->connect_mouse_release(LINK(this, CustomWeld, DoMouseButtonUp));
+ m_xDrawingArea->connect_focus_in(LINK(this, CustomWeld, DoGetFocus));
+ m_xDrawingArea->connect_focus_out(LINK(this, CustomWeld, DoLoseFocus));
+ m_xDrawingArea->connect_key_press(LINK(this, CustomWeld, DoKeyPress));
+ m_xDrawingArea->connect_focus_rect(LINK(this, CustomWeld, DoFocusRect));
+ m_xDrawingArea->connect_style_updated(LINK(this, CustomWeld, DoStyleUpdated));
+ m_xDrawingArea->connect_command(LINK(this, CustomWeld, DoCommand));
+ m_xDrawingArea->connect_query_tooltip(LINK(this, CustomWeld, DoRequestHelp));
+ m_xDrawingArea->connect_im_context_get_surrounding(LINK(this, CustomWeld, DoGetSurrounding));
+ m_xDrawingArea->connect_im_context_delete_surrounding(
+ LINK(this, CustomWeld, DoDeleteSurrounding));
+}
+
+IMPL_LINK(CustomWeld, DoResize, const Size&, rSize, void)
+{
+ m_rWidgetController.SetOutputSizePixel(rSize);
+ m_rWidgetController.Resize();
+}
+
+IMPL_LINK(CustomWeld, DoPaint, weld::DrawingArea::draw_args, aPayload, void)
+{
+ m_rWidgetController.Paint(aPayload.first, aPayload.second);
+}
+
+IMPL_LINK(CustomWeld, DoMouseButtonDown, const MouseEvent&, rMEvt, bool)
+{
+ return m_rWidgetController.MouseButtonDown(rMEvt);
+}
+
+IMPL_LINK(CustomWeld, DoMouseMove, const MouseEvent&, rMEvt, bool)
+{
+ return m_rWidgetController.MouseMove(rMEvt);
+}
+
+IMPL_LINK(CustomWeld, DoMouseButtonUp, const MouseEvent&, rMEvt, bool)
+{
+ return m_rWidgetController.MouseButtonUp(rMEvt);
+}
+
+IMPL_LINK_NOARG(CustomWeld, DoGetFocus, weld::Widget&, void) { m_rWidgetController.GetFocus(); }
+
+IMPL_LINK_NOARG(CustomWeld, DoLoseFocus, weld::Widget&, void) { m_rWidgetController.LoseFocus(); }
+
+IMPL_LINK(CustomWeld, DoKeyPress, const KeyEvent&, rKEvt, bool)
+{
+ return m_rWidgetController.KeyInput(rKEvt);
+}
+
+IMPL_LINK_NOARG(CustomWeld, DoFocusRect, weld::Widget&, tools::Rectangle)
+{
+ return m_rWidgetController.GetFocusRect();
+}
+
+IMPL_LINK_NOARG(CustomWeld, DoStyleUpdated, weld::Widget&, void)
+{
+ m_rWidgetController.StyleUpdated();
+}
+
+IMPL_LINK(CustomWeld, DoCommand, const CommandEvent&, rPos, bool)
+{
+ return m_rWidgetController.Command(rPos);
+}
+
+IMPL_LINK(CustomWeld, DoRequestHelp, tools::Rectangle&, rHelpArea, OUString)
+{
+ return m_rWidgetController.RequestHelp(rHelpArea);
+}
+
+IMPL_LINK(CustomWeld, DoGetSurrounding, OUString&, rSurrounding, int)
+{
+ return m_rWidgetController.GetSurroundingText(rSurrounding);
+}
+
+IMPL_LINK(CustomWeld, DoDeleteSurrounding, const Selection&, rSelection, bool)
+{
+ return m_rWidgetController.DeleteSurroundingText(rSelection);
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/dbggui.cxx b/vcl/source/app/dbggui.cxx
new file mode 100644
index 000000000..cfeeeaf5d
--- /dev/null
+++ b/vcl/source/app/dbggui.cxx
@@ -0,0 +1,50 @@
+/* -*- 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>
+
+#ifndef NDEBUG
+
+#include <tools/debug.hxx>
+
+#include <svdata.hxx>
+#include <dbggui.hxx>
+
+#include <salinst.hxx>
+
+using namespace ::com::sun::star;
+
+static void ImplDbgTestSolarMutex()
+{
+ assert(ImplGetSVData()->mpDefInst->GetYieldMutex()->IsCurrentThread() && "SolarMutex not owned!");
+}
+
+void DbgGUIInitSolarMutexCheck()
+{
+ DbgSetTestSolarMutex( ImplDbgTestSolarMutex );
+}
+
+void DbgGUIDeInitSolarMutexCheck()
+{
+ DbgSetTestSolarMutex( nullptr );
+}
+
+#endif // NDEBUG
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/dndhelp.cxx b/vcl/source/app/dndhelp.cxx
new file mode 100644
index 000000000..bf0e897df
--- /dev/null
+++ b/vcl/source/app/dndhelp.cxx
@@ -0,0 +1,167 @@
+/* -*- 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 <vcl/dndhelp.hxx>
+
+#include <vcl/svapp.hxx>
+#include <dndhelper.hxx>
+
+#include <cppuhelper/queryinterface.hxx>
+
+#include <com/sun/star/awt/XDisplayConnection.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+
+using namespace ::com::sun::star;
+
+vcl::unohelper::DragAndDropClient::~DragAndDropClient() COVERITY_NOEXCEPT_FALSE {}
+
+void vcl::unohelper::DragAndDropClient::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& /*dge*/ )
+{
+}
+
+void vcl::unohelper::DragAndDropClient::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& /*dsde*/ )
+{
+}
+
+void vcl::unohelper::DragAndDropClient::drop( const css::datatransfer::dnd::DropTargetDropEvent& /*dtde*/ )
+{
+}
+
+void vcl::unohelper::DragAndDropClient::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& /*dtdee*/ )
+{
+}
+
+void vcl::unohelper::DragAndDropClient::dragExit( const css::datatransfer::dnd::DropTargetEvent& /*dte*/ )
+{
+}
+
+void vcl::unohelper::DragAndDropClient::dragOver( const css::datatransfer::dnd::DropTargetDragEvent& /*dtde*/ )
+{
+}
+
+vcl::unohelper::DragAndDropWrapper::DragAndDropWrapper( DragAndDropClient* pClient )
+{
+ mpClient = pClient;
+}
+
+vcl::unohelper::DragAndDropWrapper::~DragAndDropWrapper()
+{
+}
+
+// uno::XInterface
+uno::Any vcl::unohelper::DragAndDropWrapper::queryInterface( const uno::Type & rType )
+{
+ uno::Any aRet = ::cppu::queryInterface( rType,
+ static_cast< css::lang::XEventListener* >( static_cast<css::datatransfer::dnd::XDragGestureListener*>(this) ),
+ static_cast< css::datatransfer::dnd::XDragGestureListener* >(this),
+ static_cast< css::datatransfer::dnd::XDragSourceListener* >(this),
+ static_cast< css::datatransfer::dnd::XDropTargetListener* >(this) );
+ return (aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ));
+}
+
+// css::lang::XEventListener
+void vcl::unohelper::DragAndDropWrapper::disposing( const css::lang::EventObject& rEvent )
+{
+ // Empty Source means it's the client, because the client is not a XInterface
+ if ( !rEvent.Source.is() )
+ mpClient = nullptr;
+}
+
+// css::datatransfer::dnd::XDragGestureListener
+void vcl::unohelper::DragAndDropWrapper::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& rDGE )
+{
+ if ( mpClient )
+ mpClient->dragGestureRecognized( rDGE );
+}
+
+// css::datatransfer::dnd::XDragSourceListener
+void vcl::unohelper::DragAndDropWrapper::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& rDSDE )
+{
+ if ( mpClient )
+ mpClient->dragDropEnd( rDSDE );
+}
+
+void vcl::unohelper::DragAndDropWrapper::dragEnter( const css::datatransfer::dnd::DragSourceDragEvent& )
+{
+}
+
+void vcl::unohelper::DragAndDropWrapper::dragExit( const css::datatransfer::dnd::DragSourceEvent& )
+{
+}
+
+void vcl::unohelper::DragAndDropWrapper::dragOver( const css::datatransfer::dnd::DragSourceDragEvent& )
+{
+}
+
+void vcl::unohelper::DragAndDropWrapper::dropActionChanged( const css::datatransfer::dnd::DragSourceDragEvent& )
+{
+}
+
+// css::datatransfer::dnd::XDropTargetListener
+void vcl::unohelper::DragAndDropWrapper::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE )
+{
+ if ( mpClient )
+ mpClient->drop( rDTDE );
+}
+
+void vcl::unohelper::DragAndDropWrapper::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& rDTDEE )
+{
+ if ( mpClient )
+ mpClient->dragEnter( rDTDEE );
+}
+
+void vcl::unohelper::DragAndDropWrapper::dragExit( const css::datatransfer::dnd::DropTargetEvent& dte )
+{
+ if ( mpClient )
+ mpClient->dragExit( dte );
+}
+
+void vcl::unohelper::DragAndDropWrapper::dragOver( const css::datatransfer::dnd::DropTargetDragEvent& rDTDE )
+{
+ if ( mpClient )
+ mpClient->dragOver( rDTDE );
+}
+
+void vcl::unohelper::DragAndDropWrapper::dropActionChanged( const css::datatransfer::dnd::DropTargetDragEvent& )
+{
+}
+
+css::uno::Reference<css::uno::XInterface>
+vcl::OleDnDHelper(const css::uno::Reference<css::lang::XInitialization>& xDnD, const sal_IntPtr pWin, DragOrDrop eDoD)
+{
+ if (pWin && xDnD)
+ {
+ if (eDoD == vcl::DragOrDrop::Drag)
+ xDnD->initialize({ uno::Any(), uno::Any(static_cast<sal_uInt64>(pWin)) });
+ else
+ xDnD->initialize({ uno::Any(static_cast<sal_uInt64>(pWin)), uno::Any() });
+ }
+ return xDnD;
+}
+
+css::uno::Reference<css::uno::XInterface>
+vcl::X11DnDHelper(const css::uno::Reference<css::lang::XInitialization>& xDnD, const sal_IntPtr pWin)
+{
+ if (pWin && xDnD)
+ xDnD->initialize({ uno::Any(Application::GetDisplayConnection()),
+ uno::Any(static_cast<sal_uInt64>(pWin)) });
+ return xDnD;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/help.cxx b/vcl/source/app/help.cxx
new file mode 100644
index 000000000..611bf3422
--- /dev/null
+++ b/vcl/source/app/help.cxx
@@ -0,0 +1,675 @@
+/* -*- 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 <comphelper/string.hxx>
+#include <sal/log.hxx>
+
+#include <tools/diagnose_ex.h>
+#include <tools/time.hxx>
+
+#include <vcl/window.hxx>
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/help.hxx>
+#include <vcl/settings.hxx>
+
+#include <helpwin.hxx>
+#include <salframe.hxx>
+#include <svdata.hxx>
+
+#define HELPWINSTYLE_QUICK 0
+#define HELPWINSTYLE_BALLOON 1
+
+#define HELPTEXTMARGIN_QUICK 3
+#define HELPTEXTMARGIN_BALLOON 6
+
+#define HELPTEXTMAXLEN 150
+
+Help::Help()
+{
+}
+
+Help::~Help()
+{
+}
+
+bool Help::Start( const OUString&, const vcl::Window* )
+{
+ return false;
+}
+
+bool Help::Start(const OUString&, weld::Widget*)
+{
+ return false;
+}
+
+void Help::SearchKeyword( const OUString& )
+{
+}
+
+OUString Help::GetHelpText( const OUString&, const vcl::Window* )
+{
+ return OUString();
+}
+
+OUString Help::GetHelpText( const OUString&, const weld::Widget* )
+{
+ return OUString();
+}
+
+void Help::EnableContextHelp()
+{
+ ImplGetSVHelpData().mbContextHelp = true;
+}
+
+void Help::DisableContextHelp()
+{
+ ImplGetSVHelpData().mbContextHelp = false;
+}
+
+bool Help::IsContextHelpEnabled()
+{
+ return ImplGetSVHelpData().mbContextHelp;
+}
+
+void Help::EnableExtHelp()
+{
+ ImplGetSVHelpData().mbExtHelp = true;
+}
+
+void Help::DisableExtHelp()
+{
+ ImplGetSVHelpData().mbExtHelp = false;
+}
+
+bool Help::IsExtHelpEnabled()
+{
+ return ImplGetSVHelpData().mbExtHelp;
+}
+
+bool Help::StartExtHelp()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ ImplSVHelpData& aHelpData = ImplGetSVHelpData();
+
+ if ( aHelpData.mbExtHelp && !aHelpData.mbExtHelpMode )
+ {
+ aHelpData.mbExtHelpMode = true;
+ aHelpData.mbOldBalloonMode = aHelpData.mbBalloonHelp;
+ aHelpData.mbBalloonHelp = true;
+ if (pSVData->maFrameData.mpAppWin)
+ pSVData->maFrameData.mpAppWin->ImplGenerateMouseMove();
+ return true;
+ }
+
+ return false;
+}
+
+bool Help::EndExtHelp()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ ImplSVHelpData& aHelpData = ImplGetSVHelpData();
+
+ if ( aHelpData.mbExtHelp && aHelpData.mbExtHelpMode )
+ {
+ aHelpData.mbExtHelpMode = false;
+ aHelpData.mbBalloonHelp = aHelpData.mbOldBalloonMode;
+ if (pSVData->maFrameData.mpAppWin)
+ pSVData->maFrameData.mpAppWin->ImplGenerateMouseMove();
+ return true;
+ }
+
+ return false;
+}
+
+void Help::EnableBalloonHelp()
+{
+ ImplGetSVHelpData().mbBalloonHelp = true;
+}
+
+void Help::DisableBalloonHelp()
+{
+ ImplGetSVHelpData().mbBalloonHelp = false;
+}
+
+bool Help::IsBalloonHelpEnabled()
+{
+ return ImplGetSVHelpData().mbBalloonHelp;
+}
+
+void Help::ShowBalloon( vcl::Window* pParent,
+ const Point& rScreenPos, const tools::Rectangle& rRect,
+ const OUString& rHelpText )
+{
+ ImplShowHelpWindow( pParent, HELPWINSTYLE_BALLOON, QuickHelpFlags::NONE,
+ rHelpText, rScreenPos, rRect );
+}
+
+void Help::EnableQuickHelp()
+{
+ ImplGetSVHelpData().mbQuickHelp = true;
+}
+
+void Help::DisableQuickHelp()
+{
+ ImplGetSVHelpData().mbQuickHelp = false;
+}
+
+bool Help::IsQuickHelpEnabled()
+{
+ return ImplGetSVHelpData().mbQuickHelp;
+}
+
+void Help::ShowQuickHelp( vcl::Window* pParent,
+ const tools::Rectangle& rScreenRect,
+ const OUString& rHelpText,
+ QuickHelpFlags nStyle )
+{
+ sal_uInt16 nHelpWinStyle = ( nStyle & QuickHelpFlags::TipStyleBalloon ) ? HELPWINSTYLE_BALLOON : HELPWINSTYLE_QUICK;
+ ImplShowHelpWindow( pParent, nHelpWinStyle, nStyle,
+ rHelpText,
+ pParent->OutputToScreenPixel( pParent->GetPointerPosPixel() ), rScreenRect );
+}
+
+void Help::HideBalloonAndQuickHelp()
+{
+ HelpTextWindow const * pHelpWin = ImplGetSVHelpData().mpHelpWin;
+ bool const bIsVisible = ( pHelpWin != nullptr ) && pHelpWin->IsVisible();
+ ImplDestroyHelpWindow( bIsVisible );
+}
+
+void* Help::ShowPopover(vcl::Window* pParent, const tools::Rectangle& rScreenRect,
+ const OUString& rText, QuickHelpFlags nStyle)
+{
+ void* nId = pParent->ImplGetFrame()->ShowPopover(rText, pParent, rScreenRect, nStyle);
+ if (nId)
+ {
+ //popovers are handled natively, return early
+ return nId;
+ }
+
+ sal_uInt16 nHelpWinStyle = ( nStyle & QuickHelpFlags::TipStyleBalloon ) ? HELPWINSTYLE_BALLOON : HELPWINSTYLE_QUICK;
+ VclPtrInstance<HelpTextWindow> pHelpWin( pParent, rText, nHelpWinStyle, nStyle );
+
+ nId = pHelpWin.get();
+ UpdatePopover(nId, pParent, rScreenRect, rText);
+
+ pHelpWin->ShowHelp(true);
+ return nId;
+}
+
+void Help::UpdatePopover(void* nId, vcl::Window* pParent, const tools::Rectangle& rScreenRect,
+ const OUString& rText)
+{
+ if (pParent->ImplGetFrame()->UpdatePopover(nId, rText, pParent, rScreenRect))
+ {
+ //popovers are handled natively, return early
+ return;
+ }
+
+ HelpTextWindow* pHelpWin = static_cast< HelpTextWindow* >( nId );
+ ENSURE_OR_RETURN_VOID( pHelpWin != nullptr, "Help::UpdatePopover: invalid ID!" );
+
+ Size aSz = pHelpWin->CalcOutSize();
+ pHelpWin->SetOutputSizePixel( aSz );
+ ImplSetHelpWindowPos( pHelpWin, pHelpWin->GetWinStyle(), pHelpWin->GetStyle(),
+ pParent->OutputToScreenPixel( pParent->GetPointerPosPixel() ), rScreenRect );
+
+ pHelpWin->SetHelpText( rText );
+ pHelpWin->Invalidate();
+}
+
+void Help::HidePopover(vcl::Window const * pParent, void* nId)
+{
+ if (pParent->ImplGetFrame()->HidePopover(nId))
+ {
+ //popovers are handled natively, return early
+ return;
+ }
+
+ VclPtr<HelpTextWindow> pHelpWin = static_cast<HelpTextWindow*>(nId);
+ vcl::Window* pFrameWindow = pHelpWin->ImplGetFrameWindow();
+ pHelpWin->Hide();
+ // trigger update, so that a Paint is instantly triggered since we do not save the background
+ pFrameWindow->ImplUpdateAll();
+ pHelpWin.disposeAndClear();
+ ImplGetSVHelpData().mnLastHelpHideTime = tools::Time::GetSystemTicks();
+}
+
+HelpTextWindow::HelpTextWindow( vcl::Window* pParent, const OUString& rText, sal_uInt16 nHelpWinStyle, QuickHelpFlags nStyle ) :
+ FloatingWindow( pParent, WB_SYSTEMWINDOW|WB_TOOLTIPWIN ), // #105827# if we change the parent, mirroring will not work correctly when positioning this window
+ maHelpText( rText ),
+ maShowTimer( "vcl::HelpTextWindow maShowTimer" ),
+ maHideTimer( "vcl::HelpTextWindow maHideTimer" )
+{
+ SetType( WindowType::HELPTEXTWINDOW );
+ ImplSetMouseTransparent( true );
+ mnHelpWinStyle = nHelpWinStyle;
+ mnStyle = nStyle;
+
+ if( mnStyle & QuickHelpFlags::BiDiRtl )
+ {
+ vcl::text::ComplexTextLayoutFlags nLayoutMode = GetOutDev()->GetLayoutMode();
+ nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
+ GetOutDev()->SetLayoutMode( nLayoutMode );
+ }
+ SetHelpText( rText );
+ Window::SetHelpText( rText );
+
+ if ( ImplGetSVHelpData().mbSetKeyboardHelp )
+ ImplGetSVHelpData().mbKeyboardHelp = true;
+
+
+ maShowTimer.SetInvokeHandler( LINK( this, HelpTextWindow, TimerHdl ) );
+
+ const HelpSettings& rHelpSettings = pParent->GetSettings().GetHelpSettings();
+ maHideTimer.SetTimeout( rHelpSettings.GetTipTimeout() );
+ maHideTimer.SetInvokeHandler( LINK( this, HelpTextWindow, TimerHdl ) );
+}
+
+void HelpTextWindow::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ SetPointFont(rRenderContext, rStyleSettings.GetHelpFont());
+ rRenderContext.SetTextColor(rStyleSettings.GetHelpTextColor());
+ rRenderContext.SetTextAlign(ALIGN_TOP);
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::Tooltip, ControlPart::Entire))
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode(ParentClipMode::NoClip);
+ SetPaintTransparent(true);
+ rRenderContext.SetBackground();
+ }
+ else
+ rRenderContext.SetBackground(Wallpaper(rStyleSettings.GetHelpColor()));
+
+ if (rStyleSettings.GetHelpColor().IsDark())
+ rRenderContext.SetLineColor(COL_WHITE);
+ else
+ rRenderContext.SetLineColor(COL_BLACK);
+ rRenderContext.SetFillColor();
+}
+
+HelpTextWindow::~HelpTextWindow()
+{
+ disposeOnce();
+}
+
+void HelpTextWindow::dispose()
+{
+ maShowTimer.Stop();
+ maHideTimer.Stop();
+
+ if( this == ImplGetSVHelpData().mpHelpWin )
+ ImplGetSVHelpData().mpHelpWin = nullptr;
+ FloatingWindow::dispose();
+}
+
+void HelpTextWindow::SetHelpText( const OUString& rHelpText )
+{
+ maHelpText = rHelpText;
+ ApplySettings(*GetOutDev());
+ if ( mnHelpWinStyle == HELPWINSTYLE_QUICK && maHelpText.getLength() < HELPTEXTMAXLEN && maHelpText.indexOf('\n') < 0)
+ {
+ Size aSize;
+ aSize.setHeight( GetTextHeight() );
+ if ( mnStyle & QuickHelpFlags::CtrlText )
+ aSize.setWidth( GetOutDev()->GetCtrlTextWidth( maHelpText ) );
+ else
+ aSize.setWidth( GetTextWidth( maHelpText ) );
+ maTextRect = tools::Rectangle( Point( HELPTEXTMARGIN_QUICK, HELPTEXTMARGIN_QUICK ), aSize );
+ }
+ else // HELPWINSTYLE_BALLOON
+ {
+ sal_Int32 nCharsInLine = 35 + ((maHelpText.getLength()/100)*5);
+ // average width to have all windows consistent
+ OUStringBuffer aBuf(nCharsInLine);
+ comphelper::string::padToLength(aBuf, nCharsInLine, 'x');
+ tools::Long nWidth = GetTextWidth( OUString::unacquired(aBuf) );
+ Size aTmpSize( nWidth, 0x7FFFFFFF );
+ tools::Rectangle aTry1( Point(), aTmpSize );
+ DrawTextFlags nDrawFlags = DrawTextFlags::MultiLine | DrawTextFlags::WordBreak |
+ DrawTextFlags::Left | DrawTextFlags::Top;
+ if ( mnStyle & QuickHelpFlags::CtrlText )
+ nDrawFlags |= DrawTextFlags::Mnemonic;
+ tools::Rectangle aTextRect = GetTextRect( aTry1, maHelpText, nDrawFlags );
+
+ // get a better width later...
+ maTextRect = aTextRect;
+
+ // safety distance...
+ maTextRect.SetPos( Point( HELPTEXTMARGIN_BALLOON, HELPTEXTMARGIN_BALLOON ) );
+ }
+
+ Size aSize( CalcOutSize() );
+ SetOutputSizePixel( aSize );
+}
+
+void HelpTextWindow::ImplShow()
+{
+ VclPtr<HelpTextWindow> xWindow( this );
+ Show( true, ShowFlags::NoActivate );
+ if( !xWindow->isDisposed() )
+ PaintImmediately();
+}
+
+void HelpTextWindow::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ // paint native background
+ bool bNativeOK = false;
+ if (rRenderContext.IsNativeControlSupported(ControlType::Tooltip, ControlPart::Entire))
+ {
+ tools::Rectangle aCtrlRegion(Point(0, 0), GetOutputSizePixel());
+ ImplControlValue aControlValue;
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Tooltip, ControlPart::Entire, aCtrlRegion,
+ ControlState::NONE, aControlValue, OUString());
+ }
+
+ // paint text
+ if (mnHelpWinStyle == HELPWINSTYLE_QUICK && maHelpText.getLength() < HELPTEXTMAXLEN && maHelpText.indexOf('\n') < 0)
+ {
+ if ( mnStyle & QuickHelpFlags::CtrlText )
+ rRenderContext.DrawCtrlText(maTextRect.TopLeft(), maHelpText);
+ else
+ rRenderContext.DrawText(maTextRect.TopLeft(), maHelpText);
+ }
+ else // HELPWINSTYLE_BALLOON
+ {
+ DrawTextFlags nDrawFlags = DrawTextFlags::MultiLine|DrawTextFlags::WordBreak|
+ DrawTextFlags::Left|DrawTextFlags::Top;
+ if (mnStyle & QuickHelpFlags::CtrlText)
+ nDrawFlags |= DrawTextFlags::Mnemonic;
+ rRenderContext.DrawText(maTextRect, maHelpText, nDrawFlags);
+ }
+
+ // border
+ if (bNativeOK)
+ return;
+
+ Size aSz = GetOutputSizePixel();
+ rRenderContext.DrawRect(tools::Rectangle(Point(), aSz));
+ if (mnHelpWinStyle == HELPWINSTYLE_BALLOON)
+ {
+ aSz.AdjustWidth( -2 );
+ aSz.AdjustHeight( -2 );
+ Color aColor(rRenderContext.GetLineColor());
+ rRenderContext.SetLineColor(COL_GRAY);
+ rRenderContext.DrawRect(tools::Rectangle(Point(1, 1), aSz));
+ rRenderContext.SetLineColor(aColor);
+ }
+}
+
+void HelpTextWindow::ShowHelp(bool bNoDelay)
+{
+ sal_uLong nTimeout = 0;
+ if (!bNoDelay)
+ {
+ // In case of ExtendedHelp display help sooner
+ if ( ImplGetSVHelpData().mbExtHelpMode )
+ nTimeout = 15;
+ else
+ {
+ if ( mnHelpWinStyle == HELPWINSTYLE_QUICK )
+ nTimeout = HelpSettings::GetTipDelay();
+ else
+ nTimeout = HelpSettings::GetBalloonDelay();
+ }
+ }
+
+ maShowTimer.SetTimeout( nTimeout );
+ maShowTimer.Start();
+}
+
+IMPL_LINK( HelpTextWindow, TimerHdl, Timer*, pTimer, void)
+{
+ if ( pTimer == &maShowTimer )
+ {
+ if ( mnHelpWinStyle == HELPWINSTYLE_QUICK )
+ {
+ // start auto-hide-timer for non-ShowTip windows
+ if ( this == ImplGetSVHelpData().mpHelpWin )
+ maHideTimer.Start();
+ }
+ ImplShow();
+ }
+ else
+ {
+ SAL_WARN_IF( pTimer != &maHideTimer, "vcl", "HelpTextWindow::TimerHdl with bad Timer" );
+ ImplDestroyHelpWindow( true );
+ }
+}
+
+Size HelpTextWindow::CalcOutSize() const
+{
+ Size aSz = maTextRect.GetSize();
+ aSz.AdjustWidth(2*maTextRect.Left() );
+ aSz.AdjustHeight(2*maTextRect.Top() );
+ return aSz;
+}
+
+void HelpTextWindow::RequestHelp( const HelpEvent& /*rHEvt*/ )
+{
+ // Just to assure that Window::RequestHelp() is not called by
+ // ShowQuickHelp/ShowBalloonHelp in the HelpTextWindow.
+}
+
+OUString HelpTextWindow::GetText() const
+{
+ return maHelpText;
+}
+
+void ImplShowHelpWindow( vcl::Window* pParent, sal_uInt16 nHelpWinStyle, QuickHelpFlags nStyle,
+ const OUString& rHelpText,
+ const Point& rScreenPos, const tools::Rectangle& rHelpArea )
+{
+ if (pParent->ImplGetFrame()->ShowTooltip(rHelpText, rHelpArea))
+ {
+ //tooltips are handled natively, return early
+ return;
+ }
+
+ ImplSVHelpData& aHelpData = ImplGetSVHelpData();
+
+ if (rHelpText.isEmpty() && !aHelpData.mbRequestingHelp)
+ return;
+
+ VclPtr<HelpTextWindow> pHelpWin = aHelpData.mpHelpWin;
+ bool bNoDelay = false;
+ if ( pHelpWin )
+ {
+ SAL_WARN_IF( pHelpWin == pParent, "vcl", "HelpInHelp ?!" );
+
+ bool bRemoveHelp = (rHelpText.isEmpty() || (pHelpWin->GetWinStyle() != nHelpWinStyle))
+ && aHelpData.mbRequestingHelp;
+
+ if (!bRemoveHelp && pHelpWin->GetParent() == pParent)
+ {
+ bool const bUpdate = (pHelpWin->GetHelpText() != rHelpText) ||
+ ((pHelpWin->GetHelpArea() != rHelpArea) && aHelpData.mbRequestingHelp);
+ if (bUpdate)
+ {
+ pHelpWin->SetHelpText( rHelpText );
+ // approach mouse position
+ ImplSetHelpWindowPos( pHelpWin, nHelpWinStyle, nStyle, rScreenPos, rHelpArea );
+ if( pHelpWin->IsVisible() )
+ pHelpWin->Invalidate();
+ }
+ }
+ else
+ {
+ // remove help window if no HelpText or
+ // other help mode. but keep it if we are scrolling, ie not requesting help
+ bool bWasVisible = pHelpWin->IsVisible();
+ if ( bWasVisible )
+ bNoDelay = true; // display it quickly if we were already in quick help mode
+ pHelpWin = nullptr;
+ ImplDestroyHelpWindow( bWasVisible );
+ }
+ }
+
+ if (pHelpWin || rHelpText.isEmpty())
+ return;
+
+ sal_uInt64 nCurTime = tools::Time::GetSystemTicks();
+ if ( ( nCurTime - aHelpData.mnLastHelpHideTime ) < o3tl::make_unsigned(HelpSettings::GetTipDelay()) )
+ bNoDelay = true;
+
+ pHelpWin = VclPtr<HelpTextWindow>::Create( pParent, rHelpText, nHelpWinStyle, nStyle );
+ aHelpData.mpHelpWin = pHelpWin;
+ pHelpWin->SetHelpArea( rHelpArea );
+
+ // positioning
+ Size aSz = pHelpWin->CalcOutSize();
+ pHelpWin->SetOutputSizePixel( aSz );
+ ImplSetHelpWindowPos( pHelpWin, nHelpWinStyle, nStyle, rScreenPos, rHelpArea );
+ // if not called from Window::RequestHelp, then without delay...
+ if ( !aHelpData.mbRequestingHelp )
+ bNoDelay = true;
+ pHelpWin->ShowHelp(bNoDelay);
+
+}
+
+void ImplDestroyHelpWindow( bool bUpdateHideTime )
+{
+ ImplDestroyHelpWindow(ImplGetSVHelpData(), bUpdateHideTime);
+}
+
+void ImplDestroyHelpWindow(ImplSVHelpData& rHelpData, bool bUpdateHideTime)
+{
+ VclPtr<HelpTextWindow> pHelpWin = rHelpData.mpHelpWin;
+ if( pHelpWin )
+ {
+ rHelpData.mpHelpWin = nullptr;
+ rHelpData.mbKeyboardHelp = false;
+ pHelpWin->Hide();
+ pHelpWin.disposeAndClear();
+ if( bUpdateHideTime )
+ rHelpData.mnLastHelpHideTime = tools::Time::GetSystemTicks();
+ }
+}
+
+void ImplSetHelpWindowPos( vcl::Window* pHelpWin, sal_uInt16 nHelpWinStyle, QuickHelpFlags nStyle,
+ const Point& rPos, const tools::Rectangle& rHelpArea )
+{
+ Point aPos = rPos;
+ Size aSz = pHelpWin->GetSizePixel();
+ tools::Rectangle aScreenRect = pHelpWin->ImplGetFrameWindow()->GetDesktopRectPixel();
+ aPos = pHelpWin->GetParent()->ImplGetFrameWindow()->OutputToAbsoluteScreenPixel( aPos );
+ // get mouse screen coords
+ Point aMousePos( pHelpWin->GetParent()->ImplGetFrameWindow()->GetPointerPosPixel() );
+ aMousePos = pHelpWin->GetParent()->ImplGetFrameWindow()->OutputToAbsoluteScreenPixel( aMousePos );
+
+ if ( nHelpWinStyle == HELPWINSTYLE_QUICK )
+ {
+ if ( !(nStyle & QuickHelpFlags::NoAutoPos) )
+ {
+ tools::Long nScreenHeight = aScreenRect.GetHeight();
+ aPos.AdjustX( -4 );
+ if ( aPos.Y() > aScreenRect.Top()+nScreenHeight-(nScreenHeight/4) )
+ aPos.AdjustY( -(aSz.Height()+4) );
+ else
+ aPos.AdjustY(21 );
+ }
+ }
+ else
+ {
+ // If it's the mouse position, move the window slightly
+ // so the mouse pointer does not cover it
+ if ( aPos == aMousePos )
+ {
+ aPos.AdjustX(12 );
+ aPos.AdjustY(16 );
+ }
+ }
+
+ if ( nStyle & QuickHelpFlags::NoAutoPos )
+ {
+ // convert help area to screen coords
+ tools::Rectangle devHelpArea(
+ pHelpWin->GetParent()->ImplGetFrameWindow()->OutputToAbsoluteScreenPixel( rHelpArea.TopLeft() ),
+ pHelpWin->GetParent()->ImplGetFrameWindow()->OutputToAbsoluteScreenPixel( rHelpArea.BottomRight() ) );
+
+ // which position of the rectangle?
+ aPos = devHelpArea.Center();
+
+ if ( nStyle & QuickHelpFlags::Left )
+ aPos.setX( devHelpArea.Left() );
+ else if ( nStyle & QuickHelpFlags::Right )
+ aPos.setX( devHelpArea.Right() );
+
+ if ( nStyle & QuickHelpFlags::Top )
+ aPos.setY( devHelpArea.Top() );
+ else if ( nStyle & QuickHelpFlags::Bottom )
+ aPos.setY( devHelpArea.Bottom() );
+
+ // which direction?
+ if ( nStyle & QuickHelpFlags::Left )
+ ;
+ else if ( nStyle & QuickHelpFlags::Right )
+ aPos.AdjustX( -(aSz.Width()) );
+ else
+ aPos.AdjustX( -(aSz.Width()/2) );
+
+ if ( nStyle & QuickHelpFlags::Top )
+ ;
+ else if ( nStyle & QuickHelpFlags::Bottom )
+ aPos.AdjustY( -(aSz.Height()) );
+ else
+ aPos.AdjustY( -(aSz.Height()/2) );
+ }
+
+ if ( aPos.X() < aScreenRect.Left() )
+ aPos.setX( aScreenRect.Left() );
+ else if ( ( aPos.X() + aSz.Width() ) > aScreenRect.Right() )
+ aPos.setX( aScreenRect.Right() - aSz.Width() );
+ if ( aPos.Y() < aScreenRect.Top() )
+ aPos.setY( aScreenRect.Top() );
+ else if ( ( aPos.Y() + aSz.Height() ) > aScreenRect.Bottom() )
+ aPos.setY( aScreenRect.Bottom() - aSz.Height() );
+
+ if( ! (nStyle & QuickHelpFlags::NoEvadePointer) )
+ {
+ /* the remark below should be obsolete by now as the helpwindow should
+ not be focusable, leaving it as a hint. However it is sensible in most
+ conditions to evade the mouse pointer so the content window is fully visible.
+
+ // the popup must not appear under the mouse
+ // otherwise it would directly be closed due to a focus change...
+ */
+ tools::Rectangle aHelpRect( aPos, aSz );
+ if( aHelpRect.Contains( aMousePos ) )
+ {
+ Point delta(2,2);
+ Point aSize( aSz.Width(), aSz.Height() );
+ Point aTest( aMousePos - aSize - delta );
+ if( aTest.X() > aScreenRect.Left() && aTest.Y() > aScreenRect.Top() )
+ aPos = aTest;
+ else
+ aPos = aMousePos + delta;
+ }
+ }
+
+ vcl::Window* pWindow = pHelpWin->GetParent()->ImplGetFrameWindow();
+ aPos = pWindow->AbsoluteScreenToOutputPixel( aPos );
+ pHelpWin->SetPosPixel( aPos );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/i18nhelp.cxx b/vcl/source/app/i18nhelp.cxx
new file mode 100644
index 000000000..1ed017b77
--- /dev/null
+++ b/vcl/source/app/i18nhelp.cxx
@@ -0,0 +1,155 @@
+/* -*- 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 <unotools/localedatawrapper.hxx>
+#include <unotools/transliterationwrapper.hxx>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nutil/transliteration.hxx>
+
+#include <rtl/ustrbuf.hxx>
+
+#include <vcl/i18nhelp.hxx>
+
+using namespace ::com::sun::star;
+
+vcl::I18nHelper::I18nHelper( const css::uno::Reference< css::uno::XComponentContext >& rxContext, const LanguageTag& rLanguageTag )
+ :
+ maLanguageTag( rLanguageTag)
+{
+ m_xContext = rxContext;
+ mpLocaleDataWrapper = nullptr;
+ mpTransliterationWrapper= nullptr;
+ mbTransliterateIgnoreCase = false;
+}
+
+vcl::I18nHelper::~I18nHelper()
+{
+ ImplDestroyWrappers();
+}
+
+void vcl::I18nHelper::ImplDestroyWrappers()
+{
+ mpLocaleDataWrapper.reset();
+ mpTransliterationWrapper.reset();
+}
+
+utl::TransliterationWrapper& vcl::I18nHelper::ImplGetTransliterationWrapper() const
+{
+ if ( !mpTransliterationWrapper )
+ {
+ TransliterationFlags nModules = TransliterationFlags::IGNORE_WIDTH;
+ if ( mbTransliterateIgnoreCase )
+ nModules |= TransliterationFlags::IGNORE_CASE;
+
+ const_cast<vcl::I18nHelper*>(this)->mpTransliterationWrapper.reset(new utl::TransliterationWrapper( m_xContext, nModules ));
+ const_cast<vcl::I18nHelper*>(this)->mpTransliterationWrapper->loadModuleIfNeeded( maLanguageTag.getLanguageType() );
+ }
+ return *mpTransliterationWrapper;
+}
+
+LocaleDataWrapper& vcl::I18nHelper::ImplGetLocaleDataWrapper() const
+{
+ if ( !mpLocaleDataWrapper )
+ {
+ const_cast<vcl::I18nHelper*>(this)->mpLocaleDataWrapper.reset(new LocaleDataWrapper( m_xContext, maLanguageTag ));
+ }
+ return *mpLocaleDataWrapper;
+}
+
+static bool is_formatting_mark( sal_Unicode c )
+{
+ if( (c >= 0x200B) && (c <= 0x200F) ) // BiDi and zero-width-markers
+ return true;
+ if( (c >= 0x2028) && (c <= 0x202E) ) // BiDi and paragraph-markers
+ return true;
+ return false;
+}
+
+/* #i100057# filter formatting marks out of strings before passing them to
+ the transliteration. The real solution would have been an additional TransliterationModule
+ to ignore these marks during transliteration; however changing the code in i18npool that actually
+ implements this could produce unwanted side effects.
+
+ Of course this copying around is not really good, but looking at i18npool, one more time
+ will not hurt.
+*/
+OUString vcl::I18nHelper::filterFormattingChars( const OUString& rStr )
+{
+ sal_Int32 nLength = rStr.getLength();
+ OUStringBuffer aBuf( nLength );
+ const sal_Unicode* pStr = rStr.getStr();
+ while( nLength-- )
+ {
+ if( ! is_formatting_mark( *pStr ) )
+ aBuf.append( *pStr );
+ pStr++;
+ }
+ return aBuf.makeStringAndClear();
+}
+
+sal_Int32 vcl::I18nHelper::CompareString( const OUString& rStr1, const OUString& rStr2 ) const
+{
+ std::unique_lock aGuard( maMutex );
+
+ if ( mbTransliterateIgnoreCase )
+ {
+ // Change mbTransliterateIgnoreCase and destroy the wrapper, next call to
+ // ImplGetTransliterationWrapper() will create a wrapper with the correct bIgnoreCase
+ const_cast<vcl::I18nHelper*>(this)->mbTransliterateIgnoreCase = false;
+ const_cast<vcl::I18nHelper*>(this)->mpTransliterationWrapper.reset();
+ }
+
+ OUString aStr1( filterFormattingChars(rStr1) );
+ OUString aStr2( filterFormattingChars(rStr2) );
+ return ImplGetTransliterationWrapper().compareString( aStr1, aStr2 );
+}
+
+bool vcl::I18nHelper::MatchString( const OUString& rStr1, const OUString& rStr2 ) const
+{
+ std::unique_lock aGuard( maMutex );
+
+ if ( !mbTransliterateIgnoreCase )
+ {
+ // Change mbTransliterateIgnoreCase and destroy the wrapper, next call to
+ // ImplGetTransliterationWrapper() will create a wrapper with the correct bIgnoreCase
+ const_cast<vcl::I18nHelper*>(this)->mbTransliterateIgnoreCase = true;
+ const_cast<vcl::I18nHelper*>(this)->mpTransliterationWrapper.reset();
+ }
+
+ OUString aStr1( filterFormattingChars(rStr1) );
+ OUString aStr2( filterFormattingChars(rStr2) );
+ return ImplGetTransliterationWrapper().isMatch( aStr1, aStr2 );
+}
+
+bool vcl::I18nHelper::MatchMnemonic( std::u16string_view rString, sal_Unicode cMnemonicChar ) const
+{
+ size_t n = rString.find( '~' );
+ if ( n == std::u16string_view::npos )
+ return false;
+ OUString aMatchStr( rString.substr( n+1 ) ); // not only one char, because of transliteration...
+ return MatchString( OUString(cMnemonicChar), aMatchStr );
+}
+
+OUString vcl::I18nHelper::GetNum( tools::Long nNumber, sal_uInt16 nDecimals, bool bUseThousandSep, bool bTrailingZeros ) const
+{
+ return ImplGetLocaleDataWrapper().getNum( nNumber, nDecimals, bUseThousandSep, bTrailingZeros );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/idle.cxx b/vcl/source/app/idle.cxx
new file mode 100644
index 000000000..7e5756537
--- /dev/null
+++ b/vcl/source/app/idle.cxx
@@ -0,0 +1,66 @@
+/* -*- 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 <vcl/idle.hxx>
+#include <vcl/scheduler.hxx>
+
+Idle::Idle( bool bAuto, const char *pDebugName )
+ : Timer( bAuto, pDebugName )
+{
+ SetPriority( TaskPriority::DEFAULT_IDLE );
+}
+
+Idle::Idle( const char *pDebugName )
+ : Idle( false, pDebugName )
+{
+}
+
+void Idle::Start(const bool bStartTimer)
+{
+ Task::Start(false);
+
+ sal_uInt64 nPeriod = Scheduler::ImmediateTimeoutMs;
+ if (Scheduler::GetDeterministicMode())
+ {
+ switch ( GetPriority() )
+ {
+ case TaskPriority::DEFAULT_IDLE:
+ case TaskPriority::LOWEST:
+ nPeriod = Scheduler::InfiniteTimeoutMs;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (bStartTimer)
+ Task::StartTimer(nPeriod);
+}
+
+sal_uInt64 Idle::UpdateMinPeriod( sal_uInt64 /* nTimeNow */ ) const
+{
+ return Scheduler::ImmediateTimeoutMs;
+}
+
+AutoIdle::AutoIdle( const char *pDebugName )
+ : Idle( true, pDebugName )
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/salplug.cxx b/vcl/source/app/salplug.cxx
new file mode 100644
index 000000000..378187dbf
--- /dev/null
+++ b/vcl/source/app/salplug.cxx
@@ -0,0 +1,450 @@
+/* -*- 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 .
+ */
+
+// Th current high-level preprocessor structure is:
+//
+// if !HAVE_FEATURE_UI
+// => STATIC_SAL_INSTANCE
+// else
+// ? !STATIC_SAL_INSTANCE
+// ? UNIX_DESKTOP_DETECT
+// endif
+//
+// ENABLE_HEADLESS just signifies the use of the SVP plugin!
+
+#include <config_features.h>
+#include <config_vclplug.h>
+
+#include <cstdio>
+#include <desktop/crashreport.hxx>
+#include <rtl/bootstrap.hxx>
+#include <rtl/process.h>
+#include <salinst.hxx>
+#include <sal/log.hxx>
+#include <svdata.hxx>
+#include <vcl/svapp.hxx>
+
+#if USING_X11
+#define UNIX_DESKTOP_DETECT 1
+#include <unx/desktops.hxx>
+#else
+#define UNIX_DESKTOP_DETECT 0
+#endif
+
+#if defined(DISABLE_DYNLOADING) || !HAVE_FEATURE_UI
+#define STATIC_SAL_INSTANCE 1
+extern "C" SalInstance* create_SalInstance();
+#else
+#define STATIC_SAL_INSTANCE 0
+#include <osl/module.hxx>
+#endif
+
+#if defined(iOS)
+#include <premac.h>
+#include <UIKit/UIKit.h>
+#include <postmac.h>
+
+#elif defined(ANDROID)
+#include <android/androidinst.hxx>
+#endif
+
+#if defined(_WIN32)
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <salframe.hxx>
+#include <Windows.h>
+#else
+#include <unistd.h>
+#endif
+
+#if ENABLE_HEADLESS
+#include <headless/svpdata.hxx>
+#include <headless/svpinst.hxx>
+#endif
+
+namespace {
+
+#if ENABLE_HEADLESS
+SalInstance* svp_create_SalInstance()
+{
+ SvpSalInstance* pInstance = new SvpSalInstance(std::make_unique<SvpSalYieldMutex>());
+ new SvpSalData();
+ return pInstance;
+}
+#endif
+
+#if HAVE_FEATURE_UI
+
+#if !STATIC_SAL_INSTANCE
+oslModule pCloseModule = nullptr;
+
+extern "C" typedef SalInstance* (*salFactoryProc)();
+
+SalInstance* tryInstance( const OUString& rModuleBase, bool bForce = false )
+{
+#if ENABLE_HEADLESS
+ if (rModuleBase == "svp")
+ return svp_create_SalInstance();
+#endif
+
+ SalInstance* pInst = nullptr;
+ OUString aUsedModuleBase(rModuleBase);
+ if (aUsedModuleBase == "kde5")
+ aUsedModuleBase = "kf5";
+ OUString aModule(
+#ifdef SAL_DLLPREFIX
+ SAL_DLLPREFIX
+#endif
+ "vclplug_" + aUsedModuleBase + "lo" SAL_DLLEXTENSION );
+
+ osl::Module aMod;
+ if (aMod.loadRelative(reinterpret_cast<oslGenericFunction>(&tryInstance), aModule, SAL_LOADMODULE_GLOBAL))
+ {
+ salFactoryProc aProc = reinterpret_cast<salFactoryProc>(aMod.getFunctionSymbol("create_SalInstance"));
+ if (aProc)
+ {
+ pInst = aProc();
+ SAL_INFO(
+ "vcl.plugadapt",
+ "sal plugin " << aModule << " produced instance " << pInst);
+ if (pInst)
+ {
+ pCloseModule = static_cast<oslModule>(aMod);
+ aMod.release();
+
+ /*
+ * Recent GTK+ versions load their modules with RTLD_LOCAL, so we can
+ * not access the 'gnome_accessibility_module_shutdown' anymore.
+ * So make sure libgtk+ & co are still mapped into memory when
+ * atk-bridge's atexit handler gets called.
+ */
+ if (aUsedModuleBase == "gtk4" || aUsedModuleBase == "gtk3" ||
+ aUsedModuleBase == "gtk3_kde5" || aUsedModuleBase == "kf5" ||
+ aUsedModuleBase == "qt5" || aUsedModuleBase == "qt6" ||
+ aUsedModuleBase == "win")
+ {
+ pCloseModule = nullptr;
+ }
+ }
+ }
+ else
+ {
+ SAL_WARN(
+ "vcl.plugadapt",
+ "could not load symbol create_SalInstance from shared object "
+ << aModule);
+ }
+ }
+ else if (bForce)
+ {
+ SAL_WARN("vcl.plugadapt", "could not load shared object " << aModule);
+ }
+ else
+ {
+ SAL_INFO("vcl.plugadapt", "could not load shared object " << aModule);
+ }
+
+ // coverity[leaked_storage] - this is on purpose
+ return pInst;
+}
+#endif // !STATIC_SAL_INSTANCE
+
+#if UNIX_DESKTOP_DETECT
+#ifndef DISABLE_DYNLOADING
+extern "C" typedef DesktopType Fn_get_desktop_environment();
+#else
+extern "C" DesktopType get_desktop_environment();
+#endif
+
+DesktopType lcl_get_desktop_environment()
+{
+ DesktopType ret = DESKTOP_UNKNOWN;
+#ifdef DISABLE_DYNLOADING
+ ret = get_desktop_environment();
+#else
+ OUString aModule(DESKTOP_DETECTOR_DLL_NAME);
+ oslModule aMod = osl_loadModuleRelative(
+ reinterpret_cast< oslGenericFunction >( &tryInstance ), aModule.pData,
+ SAL_LOADMODULE_DEFAULT );
+ if( aMod )
+ {
+ Fn_get_desktop_environment * pSym
+ = reinterpret_cast<Fn_get_desktop_environment *>(
+ osl_getAsciiFunctionSymbol(aMod, "get_desktop_environment"));
+ if( pSym )
+ ret = pSym();
+ }
+ osl_unloadModule( aMod );
+#endif
+ return ret;
+}
+
+#if !STATIC_SAL_INSTANCE
+const char* const* autodetect_plugin_list()
+{
+ static const char* const pKDEFallbackList[] =
+ {
+#if ENABLE_KF5
+ "kf5",
+#endif
+#if ENABLE_GTK3_KDE5
+ "gtk3_kde5",
+#endif
+#if ENABLE_GTK3
+ "gtk3",
+#endif
+#if ENABLE_GEN
+ "gen",
+#endif
+ nullptr
+ };
+
+ static const char* const pStandardFallbackList[] =
+ {
+#if ENABLE_GTK3
+ "gtk3",
+#endif
+#if ENABLE_GEN
+ "gen",
+#endif
+ nullptr
+ };
+
+#if ENABLE_HEADLESS
+ static const char* const pHeadlessFallbackList[] =
+ {
+ "svp",
+ nullptr
+ };
+#endif
+
+ DesktopType desktop = lcl_get_desktop_environment();
+ const char * const * pList = pStandardFallbackList;
+
+#if ENABLE_HEADLESS
+ // no server at all: dummy plugin
+ if ( desktop == DESKTOP_NONE )
+ pList = pHeadlessFallbackList;
+ else
+#endif
+ if ( desktop == DESKTOP_GNOME ||
+ desktop == DESKTOP_UNITY ||
+ desktop == DESKTOP_XFCE ||
+ desktop == DESKTOP_MATE )
+ pList = pStandardFallbackList;
+ else if (desktop == DESKTOP_PLASMA5 || desktop == DESKTOP_LXQT)
+ pList = pKDEFallbackList;
+
+ return pList;
+}
+#endif // !STATIC_SAL_INSTANCE
+#endif // UNIX_DESKTOP_DETECT
+
+#endif // HAVE_FEATURE_UI
+
+// HACK to obtain Application::IsHeadlessModeEnabled early on, before
+// Application::EnableHeadlessMode has potentially been called:
+bool IsHeadlessModeRequested()
+{
+ if (Application::IsHeadlessModeEnabled()) {
+ return true;
+ }
+ sal_uInt32 n = rtl_getAppCommandArgCount();
+ for (sal_uInt32 i = 0; i < n; ++i) {
+ OUString arg;
+ rtl_getAppCommandArg(i, &arg.pData);
+ if ( arg == "--headless" || arg == "-headless" ) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // anonymous namespace
+
+SalInstance *CreateSalInstance()
+{
+ OUString aUsePlugin;
+ rtl::Bootstrap::get("SAL_USE_VCLPLUGIN", aUsePlugin);
+ SAL_INFO_IF(!aUsePlugin.isEmpty(), "vcl.plugadapt", "Requested VCL plugin: " << aUsePlugin);
+
+ if (Application::IsBitmapRendering() || (aUsePlugin.isEmpty() && IsHeadlessModeRequested()))
+ aUsePlugin = "svp";
+
+ if (aUsePlugin == "svp")
+ {
+ Application::EnableBitmapRendering();
+#if ENABLE_HEADLESS
+ return svp_create_SalInstance();
+#else
+ aUsePlugin.clear();
+#endif
+ }
+
+#if STATIC_SAL_INSTANCE
+ return create_SalInstance();
+
+#else // !STATIC_SAL_INSTANCE
+ SalInstance *pInst = nullptr;
+
+ if( !aUsePlugin.isEmpty() )
+ pInst = tryInstance( aUsePlugin, true );
+
+#if UNIX_DESKTOP_DETECT
+ const char* const* pPluginList = pInst ? nullptr : autodetect_plugin_list();
+ for (int i = 0; !pInst && pPluginList[i]; ++i)
+ {
+ pInst = tryInstance(OUString::createFromAscii(pPluginList[i]));
+ SAL_INFO_IF(pInst, "vcl.plugadapt", "plugin autodetection: " << pPluginList[i]);
+ }
+#endif
+
+ // fallback, try everything
+ static const char* const pPlugin[] = {
+#ifdef _WIN32
+ "win",
+#elif defined(MACOSX)
+ "osx",
+#else // !_WIN32 && !MACOSX
+#if ENABLE_GTK3
+ "gtk3",
+#endif
+#if ENABLE_KF5
+ "kf5",
+#endif
+#if ENABLE_GTK3_KDE5
+ "gtk3_kde5",
+#endif
+#if ENABLE_GEN
+ "gen",
+#endif
+#if ENABLE_QT5
+ "qt5",
+#endif
+#if ENABLE_QT6
+ "qt6",
+#endif
+#endif // !_WIN32 && !MACOSX
+ nullptr
+ };
+
+ for (int i = 0; !pInst && pPlugin[i]; ++i)
+ pInst = tryInstance( OUString::createFromAscii( pPlugin[ i ] ) );
+
+ if( ! pInst )
+ {
+ std::fprintf( stderr, "no suitable windowing system found, exiting.\n" );
+ _exit( 1 );
+ }
+
+ return pInst;
+#endif // !STATIC_SAL_INSTANCE
+}
+
+void DestroySalInstance( SalInstance *pInst )
+{
+ delete pInst;
+#if !STATIC_SAL_INSTANCE
+ if( pCloseModule )
+ osl_unloadModule( pCloseModule );
+#endif
+}
+
+void SalAbort( const OUString& rErrorText, bool bDumpCore )
+{
+ if (GetSalInstance())
+ GetSalInstance()->BeforeAbort(rErrorText, bDumpCore);
+
+#if defined _WIN32
+ if( rErrorText.isEmpty() )
+ {
+ // make sure crash reporter is triggered
+ RaiseException( 0, EXCEPTION_NONCONTINUABLE, 0, nullptr );
+ FatalAppExitW( 0, L"Application Error" );
+ }
+ else
+ {
+ CrashReporter::addKeyValue("AbortMessage", rErrorText, CrashReporter::Write);
+ // make sure crash reporter is triggered
+ RaiseException( 0, EXCEPTION_NONCONTINUABLE, 0, nullptr );
+ FatalAppExitW( 0, o3tl::toW(rErrorText.getStr()) );
+ }
+#else // !_WIN32
+#if defined ANDROID
+ OUString aError(rErrorText.isEmpty() ? "Unspecified application error" : rErrorText);
+ LOGE("SalAbort: '%s'", OUStringToOString(aError, osl_getThreadTextEncoding()).getStr());
+#elif defined(iOS)
+ NSLog(@"SalAbort: %s", OUStringToOString(rErrorText, osl_getThreadTextEncoding()).getStr());
+#else
+ if( rErrorText.isEmpty() )
+ std::fprintf( stderr, "Unspecified Application Error\n" );
+ else
+ {
+ CrashReporter::addKeyValue("AbortMessage", rErrorText, CrashReporter::Write);
+ std::fprintf( stderr, "%s\n", OUStringToOString(rErrorText, osl_getThreadTextEncoding()).getStr() );
+ }
+#endif
+ if( bDumpCore )
+ abort();
+ else
+ _exit(1);
+#endif // !_WIN32
+}
+
+const OUString& SalGetDesktopEnvironment()
+{
+#if !HAVE_FEATURE_UI
+ static OUString aDesktopEnvironment("headless");
+#elif defined(_WIN32)
+ static OUString aDesktopEnvironment( "Windows" );
+#elif defined(MACOSX)
+ static OUString aDesktopEnvironment( "MacOSX" );
+#elif defined(EMSCRIPTEN)
+ static OUString aDesktopEnvironment("WASM");
+#elif defined(ANDROID)
+ static OUString aDesktopEnvironment("android");
+#elif defined(iOS)
+ static OUString aDesktopEnvironment("iOS");
+#elif UNIX_DESKTOP_DETECT
+ // Order to match desktops.hxx' DesktopType
+ static const char * const desktop_strings[] = {
+ "none", "unknown", "GNOME", "UNITY",
+ "XFCE", "MATE", "PLASMA5", "LXQT" };
+ static OUString aDesktopEnvironment;
+ if( aDesktopEnvironment.isEmpty())
+ {
+ aDesktopEnvironment = OUString::createFromAscii(
+ desktop_strings[lcl_get_desktop_environment()]);
+ }
+#else
+ static OUString aDesktopEnvironment("unknown");
+#endif
+ return aDesktopEnvironment;
+}
+
+#ifdef _WIN32
+bool HasAtHook()
+{
+ BOOL bIsRunning = FALSE;
+ // pvParam must be BOOL
+ return SystemParametersInfoW(SPI_GETSCREENREADER, 0, &bIsRunning, 0)
+ && bIsRunning;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/salusereventlist.cxx b/vcl/source/app/salusereventlist.cxx
new file mode 100644
index 000000000..1e3f78cc4
--- /dev/null
+++ b/vcl/source/app/salusereventlist.cxx
@@ -0,0 +1,164 @@
+/* -*- 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 <salusereventlist.hxx>
+#include <salwtype.hxx>
+
+#include <algorithm>
+#include <cstdlib>
+#include <exception>
+#include <typeinfo>
+
+#include <com/sun/star/uno/Exception.hpp>
+#include <tools/debug.hxx>
+#include <tools/diagnose_ex.h>
+#include <sal/log.hxx>
+#include <sal/types.h>
+#include <svdata.hxx>
+
+SalUserEventList::SalUserEventList()
+ : m_bAllUserEventProcessedSignaled( true )
+ , m_aProcessingThread(0)
+{
+}
+
+SalUserEventList::~SalUserEventList() COVERITY_NOEXCEPT_FALSE
+{
+}
+
+void SalUserEventList::insertFrame( SalFrame* pFrame )
+{
+ auto aPair = m_aFrames.insert( pFrame );
+ assert( aPair.second ); (void) aPair;
+}
+
+void SalUserEventList::eraseFrame( SalFrame* pFrame )
+{
+ auto it = m_aFrames.find( pFrame );
+ assert( it != m_aFrames.end() );
+ if ( it != m_aFrames.end() )
+ m_aFrames.erase( it );
+}
+
+bool SalUserEventList::DispatchUserEvents( bool bHandleAllCurrentEvents )
+{
+ bool bWasEvent = false;
+ oslThreadIdentifier aCurId = osl::Thread::getCurrentIdentifier();
+
+ DBG_TESTSOLARMUTEX();
+ std::unique_lock aResettableListGuard(m_aUserEventsMutex);
+
+ if (!m_aUserEvents.empty())
+ {
+ if (bHandleAllCurrentEvents)
+ {
+ if (m_aProcessingUserEvents.empty())
+ m_aProcessingUserEvents.swap(m_aUserEvents);
+ else
+ m_aProcessingUserEvents.splice(m_aProcessingUserEvents.end(), m_aUserEvents);
+ }
+ else if (m_aProcessingUserEvents.empty())
+ {
+ m_aProcessingUserEvents.push_back( m_aUserEvents.front() );
+ m_aUserEvents.pop_front();
+ }
+ }
+
+ if (HasUserEvents_NoLock())
+ {
+ bWasEvent = true;
+ m_aProcessingThread = aCurId;
+
+ SalUserEvent aEvent( nullptr, nullptr, SalEvent::NONE );
+ do {
+ if (m_aProcessingUserEvents.empty() || aCurId != m_aProcessingThread)
+ break;
+ aEvent = m_aProcessingUserEvents.front();
+ m_aProcessingUserEvents.pop_front();
+
+ // remember to reset the guard before break or continue the loop
+ aResettableListGuard.unlock();
+
+ if ( !isFrameAlive( aEvent.m_pFrame ) )
+ {
+ if ( aEvent.m_nEvent == SalEvent::UserEvent )
+ delete static_cast< ImplSVEvent* >( aEvent.m_pData );
+ aResettableListGuard.lock();
+ continue;
+ }
+
+ /*
+ * Current policy is that scheduler tasks aren't allowed to throw an exception.
+ * Because otherwise the exception is caught somewhere totally unrelated.
+ * TODO Ideally we could capture a proper backtrace and feed this into breakpad,
+ * which is do-able, but requires writing some assembly.
+ * See also Scheduler::CallbackTaskScheduling
+ */
+#ifdef IOS
+ ProcessEvent( aEvent );
+#else
+ // the noexcept here means that (a) we abort and (b) debuggers will
+ // likely trigger at the throw site instead of here, making the debugging
+ // experience better when something goes wrong.
+ auto process = [&aEvent, this] () noexcept { ProcessEvent(aEvent); };
+ process();
+#endif
+ aResettableListGuard.lock();
+ if (!bHandleAllCurrentEvents)
+ break;
+ }
+ while( true );
+ }
+
+ if ( !m_bAllUserEventProcessedSignaled && !HasUserEvents_NoLock() )
+ {
+ m_bAllUserEventProcessedSignaled = true;
+ TriggerAllUserEventsProcessed();
+ }
+
+ return bWasEvent;
+}
+
+void SalUserEventList::RemoveEvent( SalFrame* pFrame, void* pData, SalEvent nEvent )
+{
+ SalUserEvent aEvent( pFrame, pData, nEvent );
+
+ std::unique_lock aGuard( m_aUserEventsMutex );
+ auto it = std::find( m_aUserEvents.begin(), m_aUserEvents.end(), aEvent );
+ if ( it != m_aUserEvents.end() )
+ {
+ m_aUserEvents.erase( it );
+ }
+ else
+ {
+ it = std::find( m_aProcessingUserEvents.begin(), m_aProcessingUserEvents.end(), aEvent );
+ if ( it != m_aProcessingUserEvents.end() )
+ {
+ m_aProcessingUserEvents.erase( it );
+ }
+ }
+
+ if ( !m_bAllUserEventProcessedSignaled && !HasUserEvents_NoLock() )
+ {
+ m_bAllUserEventProcessedSignaled = true;
+ TriggerAllUserEventsProcessed();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/salvtables.cxx b/vcl/source/app/salvtables.cxx
new file mode 100644
index 000000000..343f37743
--- /dev/null
+++ b/vcl/source/app/salvtables.cxx
@@ -0,0 +1,7517 @@
+/* -*- 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 <limits>
+#include <string_view>
+
+#include <com/sun/star/accessibility/AccessibleRelationType.hpp>
+#include <com/sun/star/awt/XWindow.hpp>
+#include <com/sun/star/awt/XWindowPeer.hpp>
+#include <o3tl/safeint.hxx>
+#include <o3tl/sorted_vector.hxx>
+#include <o3tl/string_view.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <salframe.hxx>
+#include <salinst.hxx>
+#include <salvd.hxx>
+#include <salprn.hxx>
+#include <saltimer.hxx>
+#include <salsession.hxx>
+#include <salsys.hxx>
+#include <salbmp.hxx>
+#include <salobj.hxx>
+#include <salmenu.hxx>
+#include <strings.hrc>
+#include <svdata.hxx>
+#include <svimpbox.hxx>
+#include <messagedialog.hxx>
+#include <treeglue.hxx>
+#include <unotools/accessiblerelationsethelper.hxx>
+#include <unotools/configmgr.hxx>
+#include <utility>
+#include <tools/helpers.hxx>
+#include <vcl/abstdlg.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/toolkit/combobox.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/toolkit/fixed.hxx>
+#include <vcl/toolkit/fixedhyper.hxx>
+#include <vcl/toolkit/fmtfield.hxx>
+#include <vcl/headbar.hxx>
+#include <vcl/toolkit/ivctrl.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/toolkit/menubtn.hxx>
+#include <vcl/toolkit/prgsbar.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <slider.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/toolkit/svlbitm.hxx>
+#include <vcl/toolkit/svtabbx.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/tabpage.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/toolkit/treelistentry.hxx>
+#include <vcl/toolkit/throbber.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/weldutils.hxx>
+#include <vcl/toolkit/vclmedit.hxx>
+#include <vcl/toolkit/viewdataentry.hxx>
+#include <vcl/virdev.hxx>
+#include <bitmaps.hlst>
+#include <calendar.hxx>
+#include <menutogglebutton.hxx>
+#include <verticaltabctrl.hxx>
+#include <window.h>
+#include <wizdlg.hxx>
+#include <salvtables.hxx>
+#include <comphelper/lok.hxx>
+
+SalFrame::SalFrame()
+ : m_pWindow(nullptr)
+ , m_pProc(nullptr)
+{
+}
+
+// this file contains the virtual destructors of the sal interface
+// compilers usually put their vtables where the destructor is
+
+SalFrame::~SalFrame() {}
+
+void SalFrame::SetCallback(vcl::Window* pWindow, SALFRAMEPROC pProc)
+{
+ m_pWindow = pWindow;
+ m_pProc = pProc;
+}
+
+// default to full-frame flushes
+// on ports where partial-flushes are much cheaper this method should be overridden
+void SalFrame::Flush(const tools::Rectangle&) { Flush(); }
+
+void SalFrame::SetRepresentedURL(const OUString&)
+{
+ // currently this is Mac only functionality
+}
+
+SalInstance::SalInstance(std::unique_ptr<comphelper::SolarMutex> pMutex)
+ : m_pYieldMutex(std::move(pMutex))
+{
+}
+
+SalInstance::~SalInstance() {}
+
+comphelper::SolarMutex* SalInstance::GetYieldMutex() { return m_pYieldMutex.get(); }
+
+sal_uInt32 SalInstance::ReleaseYieldMutexAll() { return m_pYieldMutex->release(true); }
+
+void SalInstance::AcquireYieldMutex(sal_uInt32 nCount) { m_pYieldMutex->acquire(nCount); }
+
+std::unique_ptr<SalSession> SalInstance::CreateSalSession() { return nullptr; }
+
+OpenGLContext* SalInstance::CreateOpenGLContext()
+{
+ assert(!m_bSupportsOpenGL);
+ std::abort();
+}
+
+std::unique_ptr<SalMenu> SalInstance::CreateMenu(bool, Menu*)
+{
+ // default: no native menus
+ return nullptr;
+}
+
+std::unique_ptr<SalMenuItem> SalInstance::CreateMenuItem(const SalItemParams&) { return nullptr; }
+
+bool SalInstance::CallEventCallback(void const* pEvent, int nBytes)
+{
+ return m_pEventInst.is() && m_pEventInst->dispatchEvent(pEvent, nBytes);
+}
+
+bool SalInstance::DoExecute(int&)
+{
+ // can't run on system event loop without implementing DoExecute and DoQuit
+ if (Application::IsOnSystemEventLoop())
+ std::abort();
+ return false;
+}
+
+void SalInstance::DoQuit()
+{
+ if (Application::IsOnSystemEventLoop())
+ std::abort();
+}
+
+SalTimer::~SalTimer() COVERITY_NOEXCEPT_FALSE {}
+
+void SalBitmap::DropScaledCache()
+{
+ if (ImplSVData* pSVData = ImplGetSVData())
+ {
+ auto& rCache = pSVData->maGDIData.maScaleCache;
+
+ rCache.remove_if([this](const lru_scale_cache::key_value_pair_t& rKeyValuePair) {
+ return rKeyValuePair.first.mpBitmap == this;
+ });
+ }
+}
+
+SalBitmap::~SalBitmap() { DropScaledCache(); }
+
+SalSystem::~SalSystem() {}
+
+SalPrinter::~SalPrinter() {}
+
+bool SalPrinter::StartJob(const OUString*, const OUString&, const OUString&, ImplJobSetup*,
+ vcl::PrinterController&)
+{
+ return false;
+}
+
+SalInfoPrinter::~SalInfoPrinter() {}
+
+SalVirtualDevice::~SalVirtualDevice() {}
+
+SalObject::~SalObject() {}
+
+SalMenu::~SalMenu() {}
+
+bool SalMenu::ShowNativePopupMenu(FloatingWindow*, const tools::Rectangle&, FloatWinPopupFlags)
+{
+ return false;
+}
+
+void SalMenu::ShowCloseButton(bool) {}
+
+bool SalMenu::AddMenuBarButton(const SalMenuButtonItem&) { return false; }
+
+void SalMenu::RemoveMenuBarButton(sal_uInt16) {}
+
+tools::Rectangle SalMenu::GetMenuBarButtonRectPixel(sal_uInt16, SalFrame*)
+{
+ return tools::Rectangle();
+}
+
+int SalMenu::GetMenuBarHeight() const { return 0; }
+
+void SalMenu::ApplyPersona() {}
+
+SalMenuItem::~SalMenuItem() {}
+
+void SalInstanceWidget::ensure_event_listener()
+{
+ if (!m_bEventListener)
+ {
+ m_xWidget->AddEventListener(LINK(this, SalInstanceWidget, EventListener));
+ m_bEventListener = true;
+ }
+}
+
+// we want the ability to mark key events as handled, so use this variant
+// for those, we get all keystrokes in this case, so we will need to filter
+// them later
+void SalInstanceWidget::ensure_key_listener()
+{
+ if (!m_bKeyEventListener)
+ {
+ Application::AddKeyListener(LINK(this, SalInstanceWidget, KeyEventListener));
+ m_bKeyEventListener = true;
+ }
+}
+
+// we want the ability to know about mouse events that happen in our children
+// so use this variant, we will need to filter them later
+void SalInstanceWidget::ensure_mouse_listener()
+{
+ if (!m_bMouseEventListener)
+ {
+ m_xWidget->AddChildEventListener(LINK(this, SalInstanceWidget, MouseEventListener));
+ m_bMouseEventListener = true;
+ }
+}
+
+void SalInstanceWidget::set_background(const Color& rColor)
+{
+ m_xWidget->SetControlBackground(rColor);
+ m_xWidget->SetBackground(m_xWidget->GetControlBackground());
+ if (m_xWidget->GetStyle() & WB_CLIPCHILDREN)
+ {
+ // turn off WB_CLIPCHILDREN otherwise the bg won't extend "under"
+ // transparent children of the widget e.g. expander in sidebar panel header
+ m_xWidget->SetStyle(m_xWidget->GetStyle() & ~WB_CLIPCHILDREN);
+ // and toggle mbClipChildren on instead otherwise the bg won't fill e.g.
+ // deck titlebar header when its width is stretched
+ WindowImpl* pImpl = m_xWidget->ImplGetWindowImpl();
+ pImpl->mbClipChildren = true;
+ }
+}
+
+SalInstanceWidget::SalInstanceWidget(vcl::Window* pWidget, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : m_xWidget(pWidget)
+ , m_pBuilder(pBuilder)
+ , m_bTakeOwnership(bTakeOwnership)
+ , m_bEventListener(false)
+ , m_bKeyEventListener(false)
+ , m_bMouseEventListener(false)
+ , m_nBlockNotify(0)
+ , m_nFreezeCount(0)
+{
+}
+
+void SalInstanceWidget::set_sensitive(bool sensitive) { m_xWidget->Enable(sensitive); }
+
+bool SalInstanceWidget::get_sensitive() const { return m_xWidget->IsEnabled(); }
+
+bool SalInstanceWidget::get_visible() const { return m_xWidget->IsVisible(); }
+
+bool SalInstanceWidget::is_visible() const { return m_xWidget->IsReallyVisible(); }
+
+void SalInstanceWidget::set_can_focus(bool bCanFocus)
+{
+ auto nStyle = m_xWidget->GetStyle() & ~(WB_TABSTOP | WB_NOTABSTOP);
+ if (bCanFocus)
+ nStyle |= WB_TABSTOP;
+ else
+ nStyle |= WB_NOTABSTOP;
+ m_xWidget->SetStyle(nStyle);
+}
+
+void SalInstanceWidget::grab_focus()
+{
+ if (has_focus())
+ return;
+ m_xWidget->GrabFocus();
+}
+
+bool SalInstanceWidget::has_focus() const { return m_xWidget->HasFocus(); }
+
+bool SalInstanceWidget::is_active() const { return m_xWidget->IsActive(); }
+
+bool SalInstanceWidget::has_child_focus() const { return m_xWidget->HasChildPathFocus(true); }
+
+void SalInstanceWidget::show() { m_xWidget->Show(); }
+
+void SalInstanceWidget::hide() { m_xWidget->Hide(); }
+
+void SalInstanceWidget::set_size_request(int nWidth, int nHeight)
+{
+ m_xWidget->set_width_request(nWidth);
+ m_xWidget->set_height_request(nHeight);
+}
+
+Size SalInstanceWidget::get_size_request() const
+{
+ return Size(m_xWidget->get_width_request(), m_xWidget->get_height_request());
+}
+
+Size SalInstanceWidget::get_preferred_size() const { return m_xWidget->get_preferred_size(); }
+
+float SalInstanceWidget::get_approximate_digit_width() const
+{
+ return m_xWidget->approximate_digit_width();
+}
+
+int SalInstanceWidget::get_text_height() const { return m_xWidget->GetTextHeight(); }
+
+Size SalInstanceWidget::get_pixel_size(const OUString& rText) const
+{
+ //TODO, or do I want GetTextBoundRect ?, just using width at the moment anyway
+ return Size(m_xWidget->GetTextWidth(rText), m_xWidget->GetTextHeight());
+}
+
+vcl::Font SalInstanceWidget::get_font() { return m_xWidget->GetPointFont(*m_xWidget->GetOutDev()); }
+
+OString SalInstanceWidget::get_buildable_name() const { return m_xWidget->get_id().toUtf8(); }
+
+void SalInstanceWidget::set_buildable_name(const OString& rId)
+{
+ return m_xWidget->set_id(OUString::fromUtf8(rId));
+}
+
+void SalInstanceWidget::set_help_id(const OString& rId) { return m_xWidget->SetHelpId(rId); }
+
+OString SalInstanceWidget::get_help_id() const { return m_xWidget->GetHelpId(); }
+
+void SalInstanceWidget::set_grid_left_attach(int nAttach)
+{
+ m_xWidget->set_grid_left_attach(nAttach);
+}
+
+int SalInstanceWidget::get_grid_left_attach() const { return m_xWidget->get_grid_left_attach(); }
+
+void SalInstanceWidget::set_grid_width(int nCols) { m_xWidget->set_grid_width(nCols); }
+
+void SalInstanceWidget::set_grid_top_attach(int nAttach)
+{
+ m_xWidget->set_grid_top_attach(nAttach);
+}
+
+int SalInstanceWidget::get_grid_top_attach() const { return m_xWidget->get_grid_top_attach(); }
+
+void SalInstanceWidget::set_hexpand(bool bExpand) { m_xWidget->set_hexpand(bExpand); }
+
+bool SalInstanceWidget::get_hexpand() const { return m_xWidget->get_hexpand(); }
+
+void SalInstanceWidget::set_vexpand(bool bExpand) { m_xWidget->set_vexpand(bExpand); }
+
+bool SalInstanceWidget::get_vexpand() const { return m_xWidget->get_vexpand(); }
+
+void SalInstanceWidget::set_margin_top(int nMargin) { m_xWidget->set_margin_top(nMargin); }
+
+void SalInstanceWidget::set_margin_bottom(int nMargin) { m_xWidget->set_margin_bottom(nMargin); }
+
+void SalInstanceWidget::set_margin_start(int nMargin) { m_xWidget->set_margin_start(nMargin); }
+
+void SalInstanceWidget::set_margin_end(int nMargin) { m_xWidget->set_margin_end(nMargin); }
+
+int SalInstanceWidget::get_margin_top() const { return m_xWidget->get_margin_top(); }
+
+int SalInstanceWidget::get_margin_bottom() const { return m_xWidget->get_margin_bottom(); }
+
+int SalInstanceWidget::get_margin_start() const { return m_xWidget->get_margin_start(); }
+
+int SalInstanceWidget::get_margin_end() const { return m_xWidget->get_margin_end(); }
+
+void SalInstanceWidget::set_accessible_name(const OUString& rName)
+{
+ m_xWidget->SetAccessibleName(rName);
+}
+
+void SalInstanceWidget::set_accessible_description(const OUString& rDescription)
+{
+ m_xWidget->SetAccessibleDescription(rDescription);
+}
+
+OUString SalInstanceWidget::get_accessible_name() const { return m_xWidget->GetAccessibleName(); }
+
+OUString SalInstanceWidget::get_accessible_description() const
+{
+ return m_xWidget->GetAccessibleDescription();
+}
+
+void SalInstanceWidget::set_accessible_relation_labeled_by(weld::Widget* pLabel)
+{
+ if (vcl::Window* pOldLabel = m_xWidget->GetAccessibleRelationLabeledBy())
+ pOldLabel->SetAccessibleRelationLabelFor(nullptr);
+ vcl::Window* pA11yLabel
+ = pLabel ? dynamic_cast<SalInstanceWidget&>(*pLabel).getWidget() : nullptr;
+ m_xWidget->SetAccessibleRelationLabeledBy(pA11yLabel);
+ if (pA11yLabel)
+ pA11yLabel->SetAccessibleRelationLabelFor(m_xWidget);
+}
+
+void SalInstanceWidget::set_tooltip_text(const OUString& rTip)
+{
+ m_xWidget->SetQuickHelpText(rTip);
+}
+
+OUString SalInstanceWidget::get_tooltip_text() const { return m_xWidget->GetQuickHelpText(); }
+
+void SalInstanceWidget::connect_focus_in(const Link<Widget&, void>& rLink)
+{
+ ensure_event_listener();
+ weld::Widget::connect_focus_in(rLink);
+}
+
+void SalInstanceWidget::connect_mnemonic_activate(const Link<Widget&, bool>& rLink)
+{
+ m_xWidget->SetMnemonicActivateHdl(LINK(this, SalInstanceWidget, MnemonicActivateHdl));
+ weld::Widget::connect_mnemonic_activate(rLink);
+}
+
+void SalInstanceWidget::connect_focus_out(const Link<Widget&, void>& rLink)
+{
+ ensure_event_listener();
+ weld::Widget::connect_focus_out(rLink);
+}
+
+void SalInstanceWidget::connect_size_allocate(const Link<const Size&, void>& rLink)
+{
+ ensure_event_listener();
+ weld::Widget::connect_size_allocate(rLink);
+}
+
+void SalInstanceWidget::connect_mouse_press(const Link<const MouseEvent&, bool>& rLink)
+{
+ ensure_mouse_listener();
+ weld::Widget::connect_mouse_press(rLink);
+}
+
+void SalInstanceWidget::connect_mouse_move(const Link<const MouseEvent&, bool>& rLink)
+{
+ ensure_mouse_listener();
+ weld::Widget::connect_mouse_move(rLink);
+}
+
+void SalInstanceWidget::connect_mouse_release(const Link<const MouseEvent&, bool>& rLink)
+{
+ ensure_mouse_listener();
+ weld::Widget::connect_mouse_release(rLink);
+}
+
+void SalInstanceWidget::connect_key_press(const Link<const KeyEvent&, bool>& rLink)
+{
+ ensure_key_listener();
+ weld::Widget::connect_key_press(rLink);
+}
+
+void SalInstanceWidget::connect_key_release(const Link<const KeyEvent&, bool>& rLink)
+{
+ ensure_key_listener();
+ weld::Widget::connect_key_release(rLink);
+}
+
+bool SalInstanceWidget::get_extents_relative_to(const Widget& rRelative, int& x, int& y, int& width,
+ int& height) const
+{
+ tools::Rectangle aRect(m_xWidget->GetWindowExtentsRelative(
+ dynamic_cast<const SalInstanceWidget&>(rRelative).getWidget()));
+ x = aRect.Left();
+ y = aRect.Top();
+ width = aRect.GetWidth();
+ height = aRect.GetHeight();
+ return true;
+}
+
+void SalInstanceWidget::grab_add() { m_xWidget->CaptureMouse(); }
+
+bool SalInstanceWidget::has_grab() const { return m_xWidget->IsMouseCaptured(); }
+
+void SalInstanceWidget::grab_remove() { m_xWidget->ReleaseMouse(); }
+
+bool SalInstanceWidget::get_direction() const { return m_xWidget->IsRTLEnabled(); }
+
+void SalInstanceWidget::set_direction(bool bRTL) { m_xWidget->EnableRTL(bRTL); }
+
+void SalInstanceWidget::freeze()
+{
+ if (m_nFreezeCount == 0)
+ m_xWidget->SetUpdateMode(false);
+ ++m_nFreezeCount;
+}
+
+void SalInstanceWidget::thaw()
+{
+ --m_nFreezeCount;
+ if (m_nFreezeCount == 0)
+ m_xWidget->SetUpdateMode(true);
+}
+
+void SalInstanceWidget::set_busy_cursor(bool bBusy)
+{
+ if (!m_xWidget)
+ {
+ return;
+ }
+
+ if (bBusy)
+ m_xWidget->EnterWait();
+ else
+ m_xWidget->LeaveWait();
+}
+
+void SalInstanceWidget::queue_resize() { m_xWidget->queue_resize(); }
+
+SalInstanceWidget::~SalInstanceWidget()
+{
+ if (m_aMnemonicActivateHdl.IsSet())
+ m_xWidget->SetMnemonicActivateHdl(Link<vcl::Window&, bool>());
+ if (m_bMouseEventListener)
+ m_xWidget->RemoveChildEventListener(LINK(this, SalInstanceWidget, MouseEventListener));
+ if (m_bKeyEventListener)
+ Application::RemoveKeyListener(LINK(this, SalInstanceWidget, KeyEventListener));
+ if (m_bEventListener)
+ m_xWidget->RemoveEventListener(LINK(this, SalInstanceWidget, EventListener));
+ if (m_bTakeOwnership)
+ m_xWidget.disposeAndClear();
+}
+
+vcl::Window* SalInstanceWidget::getWidget() const { return m_xWidget; }
+
+void SalInstanceWidget::disable_notify_events() { ++m_nBlockNotify; }
+
+bool SalInstanceWidget::notify_events_disabled() const { return m_nBlockNotify != 0; }
+
+void SalInstanceWidget::enable_notify_events() { --m_nBlockNotify; }
+
+OUString SalInstanceWidget::strip_mnemonic(const OUString& rLabel) const
+{
+ return rLabel.replaceFirst("~", "");
+}
+
+VclPtr<VirtualDevice> SalInstanceWidget::create_virtual_device() const
+{
+ // create with (annoying) separate alpha layer that LibreOffice itself uses
+ return VclPtr<VirtualDevice>::Create(*Application::GetDefaultDevice(), DeviceFormat::DEFAULT,
+ DeviceFormat::DEFAULT);
+}
+
+class SalFlashAttention
+{
+private:
+ VclPtr<vcl::Window> m_xWidget;
+ Timer m_aFlashTimer;
+ Color m_aOrigControlBackground;
+ Wallpaper m_aOrigBackground;
+ bool m_bOrigControlBackground;
+ int m_nFlashCount;
+
+ void SetFlash()
+ {
+ Color aColor(Application::GetSettings().GetStyleSettings().GetHighlightColor());
+ m_xWidget->SetControlBackground(aColor);
+ }
+
+ void ClearFlash()
+ {
+ if (m_bOrigControlBackground)
+ m_xWidget->SetControlBackground(m_aOrigControlBackground);
+ else
+ m_xWidget->SetControlBackground();
+ }
+
+ void Flash()
+ {
+ constexpr int FlashesWanted = 1;
+
+ if (m_nFlashCount % 2 == 0)
+ ClearFlash();
+ else
+ SetFlash();
+
+ if (m_nFlashCount == FlashesWanted * 2)
+ return;
+
+ ++m_nFlashCount;
+
+ m_aFlashTimer.Start();
+ }
+
+ DECL_LINK(FlashTimeout, Timer*, void);
+
+public:
+ SalFlashAttention(VclPtr<vcl::Window> xWidget)
+ : m_xWidget(xWidget)
+ , m_aFlashTimer("SalFlashAttention")
+ , m_bOrigControlBackground(false)
+ , m_nFlashCount(1)
+ {
+ m_aFlashTimer.SetTimeout(150);
+ m_aFlashTimer.SetInvokeHandler(LINK(this, SalFlashAttention, FlashTimeout));
+ }
+
+ void Start()
+ {
+ m_bOrigControlBackground = m_xWidget->IsControlBackground();
+ if (m_bOrigControlBackground)
+ m_aOrigControlBackground = m_xWidget->GetControlBackground();
+ m_aFlashTimer.Start();
+ }
+
+ ~SalFlashAttention() { ClearFlash(); }
+};
+
+IMPL_LINK_NOARG(SalFlashAttention, FlashTimeout, Timer*, void) { Flash(); }
+
+void SalInstanceWidget::call_attention_to()
+{
+ m_xFlashAttention.reset(new SalFlashAttention(m_xWidget));
+ m_xFlashAttention->Start();
+}
+
+css::uno::Reference<css::datatransfer::dnd::XDropTarget> SalInstanceWidget::get_drop_target()
+{
+ return m_xWidget->GetDropTarget();
+}
+
+css::uno::Reference<css::datatransfer::clipboard::XClipboard>
+SalInstanceWidget::get_clipboard() const
+{
+ return m_xWidget->GetClipboard();
+}
+
+void SalInstanceWidget::connect_get_property_tree(const Link<tools::JsonWriter&, void>& rLink)
+{
+ m_xWidget->SetDumpAsPropertyTreeHdl(rLink);
+}
+
+void SalInstanceWidget::get_property_tree(tools::JsonWriter& rJsonWriter)
+{
+ m_xWidget->DumpAsPropertyTree(rJsonWriter);
+}
+
+void SalInstanceWidget::set_stack_background()
+{
+ set_background(m_xWidget->GetSettings().GetStyleSettings().GetWindowColor());
+}
+
+void SalInstanceWidget::set_title_background()
+{
+ set_background(m_xWidget->GetSettings().GetStyleSettings().GetShadowColor());
+}
+
+void SalInstanceWidget::set_toolbar_background()
+{
+ m_xWidget->SetBackground();
+ m_xWidget->SetPaintTransparent(true);
+}
+
+void SalInstanceWidget::set_highlight_background()
+{
+ set_background(m_xWidget->GetSettings().GetStyleSettings().GetHighlightColor());
+}
+
+SystemWindow* SalInstanceWidget::getSystemWindow() { return m_xWidget->GetSystemWindow(); }
+
+void SalInstanceWidget::HandleEventListener(VclWindowEvent& rEvent)
+{
+ if (rEvent.GetId() == VclEventId::WindowGetFocus)
+ m_aFocusInHdl.Call(*this);
+ else if (rEvent.GetId() == VclEventId::WindowLoseFocus)
+ m_aFocusOutHdl.Call(*this);
+ else if (rEvent.GetId() == VclEventId::WindowResize)
+ m_aSizeAllocateHdl.Call(m_xWidget->GetSizePixel());
+}
+
+namespace
+{
+MouseEvent TransformEvent(const MouseEvent& rEvent, const vcl::Window* pParent,
+ const vcl::Window* pChild)
+{
+ return MouseEvent(
+ pParent->ScreenToOutputPixel(pChild->OutputToScreenPixel(rEvent.GetPosPixel())),
+ rEvent.GetClicks(), rEvent.GetMode(), rEvent.GetButtons(), rEvent.GetModifier());
+}
+}
+
+void SalInstanceWidget::HandleMouseEventListener(VclWindowEvent& rWinEvent)
+{
+ if (rWinEvent.GetId() == VclEventId::WindowMouseButtonDown)
+ {
+ if (m_xWidget == rWinEvent.GetWindow())
+ {
+ const MouseEvent* pMouseEvent = static_cast<const MouseEvent*>(rWinEvent.GetData());
+ m_aMousePressHdl.Call(*pMouseEvent);
+ }
+ else if (m_xWidget->ImplIsChild(rWinEvent.GetWindow()))
+ {
+ const MouseEvent* pMouseEvent = static_cast<const MouseEvent*>(rWinEvent.GetData());
+ const MouseEvent aTransformedEvent(
+ TransformEvent(*pMouseEvent, m_xWidget, rWinEvent.GetWindow()));
+ m_aMousePressHdl.Call(aTransformedEvent);
+ }
+ }
+ else if (rWinEvent.GetId() == VclEventId::WindowMouseButtonUp)
+ {
+ if (m_xWidget == rWinEvent.GetWindow())
+ {
+ const MouseEvent* pMouseEvent = static_cast<const MouseEvent*>(rWinEvent.GetData());
+ m_aMouseReleaseHdl.Call(*pMouseEvent);
+ }
+ else if (m_xWidget->ImplIsChild(rWinEvent.GetWindow()))
+ {
+ const MouseEvent* pMouseEvent = static_cast<const MouseEvent*>(rWinEvent.GetData());
+ const MouseEvent aTransformedEvent(
+ TransformEvent(*pMouseEvent, m_xWidget, rWinEvent.GetWindow()));
+ m_aMouseReleaseHdl.Call(aTransformedEvent);
+ }
+ }
+ else if (rWinEvent.GetId() == VclEventId::WindowMouseMove)
+ {
+ if (m_xWidget == rWinEvent.GetWindow())
+ {
+ const MouseEvent* pMouseEvent = static_cast<const MouseEvent*>(rWinEvent.GetData());
+ m_aMouseMotionHdl.Call(*pMouseEvent);
+ }
+ else if (m_xWidget->ImplIsChild(rWinEvent.GetWindow()))
+ {
+ const MouseEvent* pMouseEvent = static_cast<const MouseEvent*>(rWinEvent.GetData());
+ const MouseEvent aTransformedEvent(
+ TransformEvent(*pMouseEvent, m_xWidget, rWinEvent.GetWindow()));
+ m_aMouseMotionHdl.Call(aTransformedEvent);
+ }
+ }
+}
+
+bool SalInstanceWidget::HandleKeyEventListener(VclWindowEvent& rEvent)
+{
+ // we get all key events here, ignore them unless we have focus
+ if (!m_xWidget->HasChildPathFocus())
+ return false;
+ if (rEvent.GetId() == VclEventId::WindowKeyInput)
+ {
+ const KeyEvent* pKeyEvent = static_cast<const KeyEvent*>(rEvent.GetData());
+ return m_aKeyPressHdl.Call(*pKeyEvent);
+ }
+ else if (rEvent.GetId() == VclEventId::WindowKeyUp)
+ {
+ const KeyEvent* pKeyEvent = static_cast<const KeyEvent*>(rEvent.GetData());
+ return m_aKeyReleaseHdl.Call(*pKeyEvent);
+ }
+ return false;
+}
+
+IMPL_LINK(SalInstanceWidget, EventListener, VclWindowEvent&, rEvent, void)
+{
+ HandleEventListener(rEvent);
+}
+
+IMPL_LINK(SalInstanceWidget, KeyEventListener, VclWindowEvent&, rEvent, bool)
+{
+ return HandleKeyEventListener(rEvent);
+}
+
+IMPL_LINK(SalInstanceWidget, MouseEventListener, VclWindowEvent&, rEvent, void)
+{
+ HandleMouseEventListener(rEvent);
+}
+
+IMPL_LINK_NOARG(SalInstanceWidget, MnemonicActivateHdl, vcl::Window&, bool)
+{
+ return m_aMnemonicActivateHdl.Call(*this);
+}
+
+namespace
+{
+Image createImage(const OUString& rImage)
+{
+ if (rImage.isEmpty())
+ return Image();
+ if (rImage.lastIndexOf('.') != rImage.getLength() - 4)
+ {
+ assert((rImage == "dialog-warning" || rImage == "dialog-error"
+ || rImage == "dialog-information")
+ && "unknown stock image");
+ if (rImage == "dialog-warning")
+ return Image(StockImage::Yes, IMG_WARN);
+ else if (rImage == "dialog-error")
+ return Image(StockImage::Yes, IMG_ERROR);
+ else if (rImage == "dialog-information")
+ return Image(StockImage::Yes, IMG_INFO);
+ }
+ return Image(StockImage::Yes, rImage);
+}
+
+Image createImage(const VirtualDevice& rDevice)
+{
+ return Image(rDevice.GetBitmapEx(Point(), rDevice.GetOutputSizePixel()));
+}
+
+sal_uInt16 insert_to_menu(sal_uInt16 nLastId, PopupMenu* pMenu, int pos, std::u16string_view rId,
+ const OUString& rStr, const OUString* pIconName,
+ const VirtualDevice* pImageSurface,
+ const css::uno::Reference<css::graphic::XGraphic>& rImage,
+ TriState eCheckRadioFalse)
+{
+ const sal_uInt16 nNewid = nLastId + 1;
+
+ MenuItemBits nBits;
+ if (eCheckRadioFalse == TRISTATE_TRUE)
+ nBits = MenuItemBits::CHECKABLE;
+ else if (eCheckRadioFalse == TRISTATE_FALSE)
+ nBits = MenuItemBits::CHECKABLE | MenuItemBits::RADIOCHECK;
+ else
+ nBits = MenuItemBits::NONE;
+
+ pMenu->InsertItem(nNewid, rStr, nBits, OUStringToOString(rId, RTL_TEXTENCODING_UTF8),
+ pos == -1 ? MENU_APPEND : pos);
+ if (pIconName)
+ {
+ pMenu->SetItemImage(nNewid, createImage(*pIconName));
+ }
+ else if (pImageSurface)
+ {
+ pMenu->SetItemImage(nNewid, createImage(*pImageSurface));
+ }
+ else if (rImage)
+ {
+ pMenu->SetItemImage(nNewid, Image(rImage));
+ }
+ return nNewid;
+}
+}
+
+SalInstanceMenu::SalInstanceMenu(PopupMenu* pMenu, bool bTakeOwnership)
+ : m_xMenu(pMenu)
+ , m_bTakeOwnership(bTakeOwnership)
+{
+ const auto nCount = m_xMenu->GetItemCount();
+ m_nLastId = nCount ? pMenu->GetItemId(nCount - 1) : 0;
+ m_xMenu->SetSelectHdl(LINK(this, SalInstanceMenu, SelectMenuHdl));
+}
+OString SalInstanceMenu::popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect,
+ weld::Placement ePlace)
+{
+ SalInstanceWidget* pVclWidget = dynamic_cast<SalInstanceWidget*>(pParent);
+ assert(pVclWidget);
+ PopupMenuFlags eFlags = PopupMenuFlags::NoMouseUpClose;
+ if (ePlace == weld::Placement::Under)
+ eFlags = eFlags | PopupMenuFlags::ExecuteDown;
+ else
+ eFlags = eFlags | PopupMenuFlags::ExecuteRight;
+ m_xMenu->Execute(pVclWidget->getWidget(), rRect, eFlags);
+ return m_xMenu->GetCurItemIdent();
+}
+void SalInstanceMenu::set_sensitive(const OString& rIdent, bool bSensitive)
+{
+ m_xMenu->EnableItem(rIdent, bSensitive);
+}
+bool SalInstanceMenu::get_sensitive(const OString& rIdent) const
+{
+ return m_xMenu->IsItemEnabled(m_xMenu->GetItemId(rIdent));
+}
+void SalInstanceMenu::set_active(const OString& rIdent, bool bActive)
+{
+ m_xMenu->CheckItem(rIdent, bActive);
+}
+bool SalInstanceMenu::get_active(const OString& rIdent) const
+{
+ return m_xMenu->IsItemChecked(m_xMenu->GetItemId(rIdent));
+}
+void SalInstanceMenu::set_label(const OString& rIdent, const OUString& rLabel)
+{
+ m_xMenu->SetItemText(m_xMenu->GetItemId(rIdent), rLabel);
+}
+OUString SalInstanceMenu::get_label(const OString& rIdent) const
+{
+ return m_xMenu->GetItemText(m_xMenu->GetItemId(rIdent));
+}
+void SalInstanceMenu::set_visible(const OString& rIdent, bool bShow)
+{
+ m_xMenu->ShowItem(m_xMenu->GetItemId(rIdent), bShow);
+}
+void SalInstanceMenu::clear() { m_xMenu->Clear(); }
+void SalInstanceMenu::insert(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, VirtualDevice* pImageSurface,
+ const css::uno::Reference<css::graphic::XGraphic>& rImage,
+ TriState eCheckRadioFalse)
+{
+ m_nLastId = insert_to_menu(m_nLastId, m_xMenu, pos, rId, rStr, pIconName, pImageSurface, rImage,
+ eCheckRadioFalse);
+}
+void SalInstanceMenu::insert_separator(int pos, const OUString& rId)
+{
+ auto nInsertPos = pos == -1 ? MENU_APPEND : pos;
+ m_xMenu->InsertSeparator(rId.toUtf8(), nInsertPos);
+}
+void SalInstanceMenu::remove(const OString& rId)
+{
+ m_xMenu->RemoveItem(m_xMenu->GetItemPos(m_xMenu->GetItemId(rId)));
+}
+int SalInstanceMenu::n_children() const { return m_xMenu->GetItemCount(); }
+OString SalInstanceMenu::get_id(int pos) const
+{
+ return m_xMenu->GetItemIdent(m_xMenu->GetItemId(pos));
+}
+PopupMenu* SalInstanceMenu::getMenu() const { return m_xMenu.get(); }
+SalInstanceMenu::~SalInstanceMenu()
+{
+ m_xMenu->SetSelectHdl(Link<::Menu*, bool>());
+ if (m_bTakeOwnership)
+ m_xMenu.disposeAndClear();
+}
+
+IMPL_LINK_NOARG(SalInstanceMenu, SelectMenuHdl, ::Menu*, bool)
+{
+ signal_activate(m_xMenu->GetCurItemIdent());
+ /* tdf#131333 Menu::Select depends on a false here to allow
+ propagating a submens's selected id to its parent menu to become its
+ selected id.
+
+ without this, while gen menus already have propagated this to its parent
+ in MenuFloatingWindow::EndExecute, SalMenus as used under kf5/macOS
+ won't propagate the selected id
+ */
+ return false;
+}
+
+SalInstanceToolbar::SalInstanceToolbar(ToolBox* pToolBox, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceWidget(pToolBox, pBuilder, bTakeOwnership)
+ , m_xToolBox(pToolBox)
+{
+ m_xToolBox->SetSelectHdl(LINK(this, SalInstanceToolbar, ClickHdl));
+ m_xToolBox->SetDropdownClickHdl(LINK(this, SalInstanceToolbar, DropdownClick));
+}
+
+void SalInstanceToolbar::set_item_sensitive(const OString& rIdent, bool bSensitive)
+{
+ m_xToolBox->EnableItem(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)), bSensitive);
+}
+
+bool SalInstanceToolbar::get_item_sensitive(const OString& rIdent) const
+{
+ return m_xToolBox->IsItemEnabled(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)));
+}
+
+void SalInstanceToolbar::set_item_visible(const OString& rIdent, bool bVisible)
+{
+ m_xToolBox->ShowItem(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)), bVisible);
+}
+
+void SalInstanceToolbar::set_item_help_id(const OString& rIdent, const OString& rHelpId)
+{
+ m_xToolBox->SetHelpId(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)), rHelpId);
+}
+
+bool SalInstanceToolbar::get_item_visible(const OString& rIdent) const
+{
+ return m_xToolBox->IsItemVisible(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)));
+}
+
+void SalInstanceToolbar::set_item_active(const OString& rIdent, bool bActive)
+{
+ ToolBoxItemId nItemId = m_xToolBox->GetItemId(OUString::fromUtf8(rIdent));
+ m_xToolBox->CheckItem(nItemId, bActive);
+}
+
+bool SalInstanceToolbar::get_item_active(const OString& rIdent) const
+{
+ return m_xToolBox->IsItemChecked(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)));
+}
+
+void SalInstanceToolbar::set_menu_item_active(const OString& rIdent, bool bActive)
+{
+ ToolBoxItemId nItemId = m_xToolBox->GetItemId(OUString::fromUtf8(rIdent));
+ assert(m_xToolBox->GetItemBits(nItemId) & ToolBoxItemBits::DROPDOWN);
+
+ if (bActive)
+ {
+ m_sStartShowIdent = m_xToolBox->GetItemCommand(nItemId).toUtf8();
+ signal_toggle_menu(m_sStartShowIdent);
+ }
+
+ auto pFloat = m_aFloats[nItemId];
+ if (pFloat)
+ {
+ if (bActive)
+ vcl::Window::GetDockingManager()->StartPopupMode(m_xToolBox, pFloat,
+ FloatWinPopupFlags::GrabFocus);
+ else
+ vcl::Window::GetDockingManager()->EndPopupMode(pFloat);
+ }
+ auto pPopup = m_aMenus[nItemId];
+ if (pPopup)
+ {
+ if (bActive)
+ {
+ tools::Rectangle aRect = m_xToolBox->GetItemRect(nItemId);
+ pPopup->Execute(m_xToolBox, aRect, PopupMenuFlags::ExecuteDown);
+ }
+ else
+ pPopup->EndExecute();
+ }
+
+ m_sStartShowIdent.clear();
+}
+
+bool SalInstanceToolbar::get_menu_item_active(const OString& rIdent) const
+{
+ ToolBoxItemId nItemId = m_xToolBox->GetItemId(OUString::fromUtf8(rIdent));
+ assert(m_xToolBox->GetItemBits(nItemId) & ToolBoxItemBits::DROPDOWN);
+
+ if (rIdent == m_sStartShowIdent)
+ return true;
+
+ auto aFloat = m_aFloats.find(nItemId);
+ if (aFloat != m_aFloats.end())
+ {
+ return vcl::Window::GetDockingManager()->IsInPopupMode(aFloat->second);
+ }
+
+ auto aPopup = m_aMenus.find(nItemId);
+ if (aPopup != m_aMenus.end())
+ {
+ return PopupMenu::GetActivePopupMenu() == aPopup->second;
+ }
+
+ return false;
+}
+
+void SalInstanceToolbar::set_item_popover(const OString& rIdent, weld::Widget* pPopover)
+{
+ SalInstanceWidget* pPopoverWidget = dynamic_cast<SalInstanceWidget*>(pPopover);
+
+ vcl::Window* pFloat = pPopoverWidget ? pPopoverWidget->getWidget() : nullptr;
+ if (pFloat)
+ {
+ pFloat->AddEventListener(LINK(this, SalInstanceToolbar, MenuToggleListener));
+ pFloat->EnableDocking();
+ }
+
+ ToolBoxItemId nId = m_xToolBox->GetItemId(OUString::fromUtf8(rIdent));
+ auto xOldFloat = m_aFloats[nId];
+ if (xOldFloat)
+ {
+ xOldFloat->RemoveEventListener(LINK(this, SalInstanceToolbar, MenuToggleListener));
+ }
+ m_aFloats[nId] = pFloat;
+ m_aMenus[nId] = nullptr;
+}
+
+void SalInstanceToolbar::set_item_menu(const OString& rIdent, weld::Menu* pMenu)
+{
+ SalInstanceMenu* pInstanceMenu = dynamic_cast<SalInstanceMenu*>(pMenu);
+
+ PopupMenu* pPopup = pInstanceMenu ? pInstanceMenu->getMenu() : nullptr;
+
+ ToolBoxItemId nId = m_xToolBox->GetItemId(OUString::fromUtf8(rIdent));
+ m_aMenus[nId] = pPopup;
+ m_aFloats[nId] = nullptr;
+}
+
+void SalInstanceToolbar::insert_item(int pos, const OUString& rId)
+{
+ ToolBoxItemId nId(pos);
+ m_xToolBox->InsertItem(nId, rId, ToolBoxItemBits::ICON_ONLY);
+ m_xToolBox->SetItemCommand(nId, rId);
+}
+
+void SalInstanceToolbar::insert_separator(int pos, const OUString& /*rId*/)
+{
+ auto nInsertPos = pos == -1 ? ToolBox::APPEND : pos;
+ m_xToolBox->InsertSeparator(nInsertPos, 5);
+}
+
+int SalInstanceToolbar::get_n_items() const { return m_xToolBox->GetItemCount(); }
+
+OString SalInstanceToolbar::get_item_ident(int nIndex) const
+{
+ return m_xToolBox->GetItemCommand(m_xToolBox->GetItemId(nIndex)).toUtf8();
+}
+
+void SalInstanceToolbar::set_item_ident(int nIndex, const OString& rIdent)
+{
+ return m_xToolBox->SetItemCommand(m_xToolBox->GetItemId(nIndex), OUString::fromUtf8(rIdent));
+}
+
+void SalInstanceToolbar::set_item_label(int nIndex, const OUString& rLabel)
+{
+ m_xToolBox->SetItemText(m_xToolBox->GetItemId(nIndex), rLabel);
+}
+
+OUString SalInstanceToolbar::get_item_label(const OString& rIdent) const
+{
+ return m_xToolBox->GetItemText(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)));
+}
+
+void SalInstanceToolbar::set_item_label(const OString& rIdent, const OUString& rLabel)
+{
+ m_xToolBox->SetItemText(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)), rLabel);
+}
+
+void SalInstanceToolbar::set_item_icon_name(const OString& rIdent, const OUString& rIconName)
+{
+ m_xToolBox->SetItemImage(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)),
+ Image(StockImage::Yes, rIconName));
+}
+
+void SalInstanceToolbar::set_item_image(const OString& rIdent,
+ const css::uno::Reference<css::graphic::XGraphic>& rIcon)
+{
+ m_xToolBox->SetItemImage(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)), Image(rIcon));
+}
+
+void SalInstanceToolbar::set_item_image(const OString& rIdent, VirtualDevice* pDevice)
+{
+ if (pDevice)
+ m_xToolBox->SetItemImage(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)),
+ createImage(*pDevice));
+ else
+ m_xToolBox->SetItemImage(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)), Image());
+}
+
+void SalInstanceToolbar::set_item_image(int nIndex,
+ const css::uno::Reference<css::graphic::XGraphic>& rIcon)
+{
+ m_xToolBox->SetItemImage(m_xToolBox->GetItemId(nIndex), Image(rIcon));
+}
+
+void SalInstanceToolbar::set_item_tooltip_text(int nIndex, const OUString& rTip)
+{
+ m_xToolBox->SetQuickHelpText(m_xToolBox->GetItemId(nIndex), rTip);
+}
+
+void SalInstanceToolbar::set_item_tooltip_text(const OString& rIdent, const OUString& rTip)
+{
+ m_xToolBox->SetQuickHelpText(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)), rTip);
+}
+
+OUString SalInstanceToolbar::get_item_tooltip_text(const OString& rIdent) const
+{
+ return m_xToolBox->GetQuickHelpText(m_xToolBox->GetItemId(OUString::fromUtf8(rIdent)));
+}
+
+vcl::ImageType SalInstanceToolbar::get_icon_size() const { return m_xToolBox->GetImageSize(); }
+
+void SalInstanceToolbar::set_icon_size(vcl::ImageType eType)
+{
+ ToolBoxButtonSize eButtonSize = ToolBoxButtonSize::DontCare;
+ switch (eType)
+ {
+ case vcl::ImageType::Size16:
+ eButtonSize = ToolBoxButtonSize::Small;
+ break;
+ case vcl::ImageType::Size26:
+ eButtonSize = ToolBoxButtonSize::Large;
+ break;
+ case vcl::ImageType::Size32:
+ eButtonSize = ToolBoxButtonSize::Size32;
+ break;
+ }
+ if (m_xToolBox->GetToolboxButtonSize() != eButtonSize)
+ {
+ m_xToolBox->SetToolboxButtonSize(eButtonSize);
+ m_xToolBox->queue_resize();
+ }
+}
+
+sal_uInt16 SalInstanceToolbar::get_modifier_state() const { return m_xToolBox->GetModifier(); }
+
+int SalInstanceToolbar::get_drop_index(const Point& rPoint) const
+{
+ auto nRet = m_xToolBox->GetItemPos(rPoint);
+ if (nRet == ToolBox::ITEM_NOTFOUND)
+ return 0;
+ return nRet;
+}
+
+SalInstanceToolbar::~SalInstanceToolbar()
+{
+ m_xToolBox->SetDropdownClickHdl(Link<ToolBox*, void>());
+ m_xToolBox->SetSelectHdl(Link<ToolBox*, void>());
+}
+
+IMPL_LINK_NOARG(SalInstanceToolbar, ClickHdl, ToolBox*, void)
+{
+ ToolBoxItemId nItemId = m_xToolBox->GetCurItemId();
+ signal_clicked(m_xToolBox->GetItemCommand(nItemId).toUtf8());
+}
+
+IMPL_LINK_NOARG(SalInstanceToolbar, DropdownClick, ToolBox*, void)
+{
+ ToolBoxItemId nItemId = m_xToolBox->GetCurItemId();
+ set_menu_item_active(m_xToolBox->GetItemCommand(nItemId).toUtf8(), true);
+}
+
+IMPL_LINK(SalInstanceToolbar, MenuToggleListener, VclWindowEvent&, rEvent, void)
+{
+ if (rEvent.GetId() == VclEventId::WindowEndPopupMode)
+ {
+ for (const auto& rFloat : m_aFloats)
+ {
+ if (rEvent.GetWindow() == rFloat.second)
+ {
+ ToolBoxItemId nItemId = rFloat.first;
+ signal_toggle_menu(m_xToolBox->GetItemCommand(nItemId).toUtf8());
+ break;
+ }
+ }
+ }
+}
+
+namespace
+{
+class SalInstanceSizeGroup : public weld::SizeGroup
+{
+private:
+ std::shared_ptr<VclSizeGroup> m_xGroup;
+
+public:
+ SalInstanceSizeGroup()
+ : m_xGroup(std::make_shared<VclSizeGroup>())
+ {
+ }
+ virtual void add_widget(weld::Widget* pWidget) override
+ {
+ SalInstanceWidget* pVclWidget = dynamic_cast<SalInstanceWidget*>(pWidget);
+ assert(pVclWidget && pVclWidget->getWidget());
+ pVclWidget->getWidget()->add_to_size_group(m_xGroup);
+ }
+ virtual void set_mode(VclSizeGroupMode eMode) override { m_xGroup->set_mode(eMode); }
+};
+}
+
+void SalInstanceContainer::connect_container_focus_changed(const Link<Container&, void>& rLink)
+{
+ ensure_event_listener();
+ weld::Container::connect_container_focus_changed(rLink);
+}
+
+void SalInstanceContainer::HandleEventListener(VclWindowEvent& rEvent)
+{
+ if (rEvent.GetId() == VclEventId::WindowActivate
+ || rEvent.GetId() == VclEventId::WindowDeactivate)
+ {
+ signal_container_focus_changed();
+ return;
+ }
+ SalInstanceWidget::HandleEventListener(rEvent);
+}
+
+SalInstanceContainer::SalInstanceContainer(vcl::Window* pContainer, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceWidget(pContainer, pBuilder, bTakeOwnership)
+ , m_xContainer(pContainer)
+{
+}
+
+void SalInstanceContainer::move(weld::Widget* pWidget, weld::Container* pNewParent)
+{
+ SalInstanceWidget* pVclWidget = dynamic_cast<SalInstanceWidget*>(pWidget);
+ assert(pVclWidget);
+ SalInstanceContainer* pNewVclParent = dynamic_cast<SalInstanceContainer*>(pNewParent);
+ assert(!pNewParent || pNewVclParent);
+ vcl::Window* pVclWindow = pVclWidget->getWidget();
+ if (pNewVclParent)
+ {
+ vcl::Window* pNew = pNewVclParent->getWidget();
+ if (!pNew->isDisposed())
+ pVclWindow->SetParent(pNewVclParent->getWidget());
+ else
+ SAL_WARN("vcl", "ignoring move because new parent is already disposed");
+ }
+ else
+ {
+ pVclWindow->Hide();
+ pVclWindow->SetParent(ImplGetDefaultWindow());
+ }
+}
+
+void SalInstanceContainer::child_grab_focus()
+{
+ m_xContainer->GrabFocus();
+ if (vcl::Window* pFirstChild = m_xContainer->ImplGetDlgWindow(0, GetDlgWindowType::First))
+ pFirstChild->ImplControlFocus();
+}
+
+css::uno::Reference<css::awt::XWindow> SalInstanceContainer::CreateChildFrame()
+{
+ auto xPage = VclPtr<VclBin>::Create(m_xContainer.get());
+ xPage->set_expand(true);
+ xPage->Show();
+ return css::uno::Reference<css::awt::XWindow>(xPage->GetComponentInterface(),
+ css::uno::UNO_QUERY);
+}
+
+std::unique_ptr<weld::Container> SalInstanceWidget::weld_parent() const
+{
+ vcl::Window* pParent = m_xWidget->GetParent();
+ if (!pParent)
+ return nullptr;
+ return std::make_unique<SalInstanceContainer>(pParent, m_pBuilder, false);
+}
+
+void SalInstanceWidget::DoRecursivePaint(vcl::Window* pWindow, const Point& rRenderLogicPos,
+ OutputDevice& rOutput)
+{
+ rOutput.Push();
+ bool bOldMapModeEnabled = pWindow->IsMapModeEnabled();
+
+ if (pWindow->GetMapMode().GetMapUnit() != rOutput.GetMapMode().GetMapUnit())
+ {
+ // This is needed for e.g. the scrollbar in writer comments in margins that has its map unit in pixels
+ // as seen with bin/run gtktiledviewer --enable-tiled-annotations on a document containing a comment
+ // long enough to need a scrollbar
+ pWindow->EnableMapMode();
+ MapMode aMapMode = pWindow->GetMapMode();
+ aMapMode.SetMapUnit(rOutput.GetMapMode().GetMapUnit());
+ aMapMode.SetScaleX(rOutput.GetMapMode().GetScaleX());
+ aMapMode.SetScaleY(rOutput.GetMapMode().GetScaleY());
+ pWindow->SetMapMode(aMapMode);
+ }
+
+ VclPtr<VirtualDevice> xOutput(VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT));
+ Size aChildSizePixel(pWindow->GetSizePixel());
+ xOutput->SetOutputSizePixel(aChildSizePixel);
+
+ MapMode aMapMode(xOutput->GetMapMode());
+ aMapMode.SetMapUnit(rOutput.GetMapMode().GetMapUnit());
+ aMapMode.SetScaleX(rOutput.GetMapMode().GetScaleX());
+ aMapMode.SetScaleY(rOutput.GetMapMode().GetScaleY());
+ xOutput->SetMapMode(aMapMode);
+
+ Size aTempLogicSize(xOutput->PixelToLogic(aChildSizePixel));
+ Size aRenderLogicSize(rOutput.PixelToLogic(aChildSizePixel));
+
+ switch (rOutput.GetOutDevType())
+ {
+ case OUTDEV_WINDOW:
+ case OUTDEV_VIRDEV:
+ xOutput->DrawOutDev(Point(), aTempLogicSize, rRenderLogicPos, aRenderLogicSize,
+ rOutput);
+ break;
+ case OUTDEV_PRINTER:
+ case OUTDEV_PDF:
+ xOutput->SetBackground(rOutput.GetBackground());
+ xOutput->Erase();
+ break;
+ }
+
+ //set ReallyVisible to match Visible, we restore the original state after Paint
+ WindowImpl* pImpl = pWindow->ImplGetWindowImpl();
+ bool bRVisible = pImpl->mbReallyVisible;
+ pImpl->mbReallyVisible = pWindow->IsVisible();
+
+ pWindow->ApplySettings(*xOutput);
+ pWindow->Paint(*xOutput, tools::Rectangle(Point(), pWindow->PixelToLogic(aChildSizePixel)));
+
+ pImpl->mbReallyVisible = bRVisible;
+
+ switch (rOutput.GetOutDevType())
+ {
+ case OUTDEV_WINDOW:
+ case OUTDEV_VIRDEV:
+ rOutput.DrawOutDev(rRenderLogicPos, aRenderLogicSize, Point(), aTempLogicSize,
+ *xOutput);
+ break;
+ case OUTDEV_PRINTER:
+ case OUTDEV_PDF:
+ rOutput.DrawBitmapEx(rRenderLogicPos, aRenderLogicSize,
+ xOutput->GetBitmapEx(Point(), aTempLogicSize));
+ break;
+ }
+
+ bool bHasMirroredGraphics = pWindow->GetOutDev()->HasMirroredGraphics();
+
+ xOutput.disposeAndClear();
+
+ pWindow->EnableMapMode(bOldMapModeEnabled);
+ rOutput.Pop();
+
+ for (vcl::Window* pChild = pWindow->GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+
+ tools::Long nDeltaX
+ = pChild->GetOutDev()->GetOutOffXPixel() - pWindow->GetOutDev()->GetOutOffXPixel();
+ if (bHasMirroredGraphics)
+ nDeltaX = pWindow->GetOutDev()->GetOutputWidthPixel() - nDeltaX
+ - pChild->GetOutDev()->GetOutputWidthPixel();
+
+ tools::Long nDeltaY
+ = pChild->GetOutDev()->GetOutOffYPixel() - pWindow->GetOutDev()->GetOutOffYPixel();
+
+ Point aPos(rRenderLogicPos);
+ aPos += Point(nDeltaX, nDeltaY);
+
+ DoRecursivePaint(pChild, aPos, rOutput);
+ }
+}
+
+void SalInstanceWidget::draw(OutputDevice& rOutput, const Point& rPos, const Size& rSizePixel)
+{
+ Size aOrigSize(m_xWidget->GetSizePixel());
+ bool bChangeSize = aOrigSize != rSizePixel;
+ if (bChangeSize)
+ m_xWidget->SetSizePixel(rSizePixel);
+
+ DoRecursivePaint(m_xWidget, rPos, rOutput);
+
+ if (bChangeSize)
+ m_xWidget->SetSizePixel(aOrigSize);
+}
+
+SalInstanceBox::SalInstanceBox(VclBox* pContainer, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceContainer(pContainer, pBuilder, bTakeOwnership)
+ , m_xBox(pContainer)
+{
+}
+void SalInstanceBox::reorder_child(weld::Widget* pWidget, int nNewPosition)
+{
+ SalInstanceWidget* pVclWidget = dynamic_cast<SalInstanceWidget*>(pWidget);
+ assert(pVclWidget);
+ pVclWidget->getWidget()->reorderWithinParent(nNewPosition);
+}
+
+void SalInstanceBox::sort_native_button_order() { ::sort_native_button_order(*m_xBox); }
+
+namespace
+{
+void CollectChildren(const vcl::Window& rCurrent, const basegfx::B2IPoint& rTopLeft,
+ weld::ScreenShotCollection& rControlDataCollection)
+{
+ if (!rCurrent.IsVisible())
+ return;
+
+ const Point aCurrentPos(rCurrent.GetPosPixel());
+ const Size aCurrentSize(rCurrent.GetSizePixel());
+ const basegfx::B2IPoint aCurrentTopLeft(rTopLeft.getX() + aCurrentPos.X(),
+ rTopLeft.getY() + aCurrentPos.Y());
+ const basegfx::B2IRange aCurrentRange(
+ aCurrentTopLeft,
+ aCurrentTopLeft + basegfx::B2IPoint(aCurrentSize.Width(), aCurrentSize.Height()));
+
+ if (!aCurrentRange.isEmpty())
+ {
+ rControlDataCollection.emplace_back(rCurrent.GetHelpId(), aCurrentRange);
+ }
+
+ for (sal_uInt16 a(0); a < rCurrent.GetChildCount(); a++)
+ {
+ vcl::Window* pChild = rCurrent.GetChild(a);
+ if (nullptr != pChild)
+ {
+ CollectChildren(*pChild, aCurrentTopLeft, rControlDataCollection);
+ }
+ }
+}
+}
+
+void SalInstanceWindow::override_child_help(vcl::Window* pParent)
+{
+ for (vcl::Window* pChild = pParent->GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ override_child_help(pChild);
+ pParent->SetHelpHdl(LINK(this, SalInstanceWindow, HelpHdl));
+}
+
+void SalInstanceWindow::clear_child_help(vcl::Window* pParent)
+{
+ for (vcl::Window* pChild = pParent->GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ clear_child_help(pChild);
+ pParent->SetHelpHdl(Link<vcl::Window&, bool>());
+}
+
+SalInstanceWindow::SalInstanceWindow(vcl::Window* pWindow, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceContainer(pWindow, pBuilder, bTakeOwnership)
+ , m_xWindow(pWindow)
+{
+ // tdf#129745 only override child help for the normal case, not for
+ // m_pBuilder of null which is the toplevel application frame case.
+ if (m_pBuilder)
+ override_child_help(m_xWindow);
+}
+
+void SalInstanceWindow::set_title(const OUString& rTitle) { m_xWindow->SetText(rTitle); }
+
+OUString SalInstanceWindow::get_title() const { return m_xWindow->GetText(); }
+
+css::uno::Reference<css::awt::XWindow> SalInstanceWindow::GetXWindow()
+{
+ css::uno::Reference<css::awt::XWindow> xWindow(m_xWindow->GetComponentInterface(),
+ css::uno::UNO_QUERY);
+ return xWindow;
+}
+
+namespace
+{
+void resize_to_request(vcl::Window* pWindow)
+{
+ if (SystemWindow* pSysWin = dynamic_cast<SystemWindow*>(pWindow))
+ {
+ pSysWin->setOptimalLayoutSize(true);
+ return;
+ }
+ if (DockingWindow* pDockWin = dynamic_cast<DockingWindow*>(pWindow))
+ {
+ pDockWin->setOptimalLayoutSize();
+ return;
+ }
+ assert(false && "must be system or docking window");
+}
+}
+
+void SalInstanceWindow::resize_to_request() { ::resize_to_request(m_xWindow.get()); }
+
+void SalInstanceWindow::set_modal(bool bModal) { m_xWindow->ImplGetFrame()->SetModal(bModal); }
+
+bool SalInstanceWindow::get_modal() const { return m_xWindow->ImplGetFrame()->GetModal(); }
+
+void SalInstanceWindow::window_move(int x, int y) { m_xWindow->SetPosPixel(Point(x, y)); }
+
+Size SalInstanceWindow::get_size() const { return m_xWindow->GetSizePixel(); }
+
+Point SalInstanceWindow::get_position() const { return m_xWindow->GetPosPixel(); }
+
+tools::Rectangle SalInstanceWindow::get_monitor_workarea() const
+{
+ return m_xWindow->GetDesktopRectPixel();
+}
+
+void SalInstanceWindow::set_centered_on_parent(bool /*bTrackGeometryRequests*/)
+{
+ if (vcl::Window* pParent = m_xWidget->GetParent())
+ {
+ Size aParentGeometry(pParent->GetSizePixel());
+ Size aGeometry(m_xWidget->get_preferred_size());
+ auto nX = (aParentGeometry.Width() - aGeometry.Width()) / 2;
+ auto nY = (aParentGeometry.Height() - aGeometry.Height()) / 2;
+ m_xWidget->SetPosPixel(Point(nX, nY));
+ }
+}
+
+bool SalInstanceWindow::get_resizable() const { return m_xWindow->GetStyle() & WB_SIZEABLE; }
+
+bool SalInstanceWindow::has_toplevel_focus() const { return m_xWindow->HasChildPathFocus(); }
+
+void SalInstanceWindow::present()
+{
+ m_xWindow->ToTop(ToTopFlags::RestoreWhenMin | ToTopFlags::ForegroundTask);
+}
+
+void SalInstanceWindow::implResetDefault(const vcl::Window* _pWindow)
+{
+ vcl::Window* pChildLoop = _pWindow->GetWindow(GetWindowType::FirstChild);
+ while (pChildLoop)
+ {
+ // does the window participate in the tabbing order?
+ if (pChildLoop->GetStyle() & WB_DIALOGCONTROL)
+ implResetDefault(pChildLoop);
+
+ // is it a button?
+ WindowType eType = pChildLoop->GetType();
+ if ((WindowType::PUSHBUTTON == eType) || (WindowType::OKBUTTON == eType)
+ || (WindowType::CANCELBUTTON == eType) || (WindowType::HELPBUTTON == eType)
+ || (WindowType::IMAGEBUTTON == eType) || (WindowType::MENUBUTTON == eType)
+ || (WindowType::MOREBUTTON == eType))
+ {
+ pChildLoop->SetStyle(pChildLoop->GetStyle() & ~WB_DEFBUTTON);
+ }
+
+ // the next one ...
+ pChildLoop = pChildLoop->GetWindow(GetWindowType::Next);
+ }
+}
+
+void SalInstanceWindow::recursively_unset_default_buttons() { implResetDefault(m_xWindow.get()); }
+
+void SalInstanceWindow::change_default_widget(weld::Widget* pOld, weld::Widget* pNew)
+{
+ SalInstanceWidget* pVclNew = dynamic_cast<SalInstanceWidget*>(pNew);
+ vcl::Window* pWidgetNew = pVclNew ? pVclNew->getWidget() : nullptr;
+ SalInstanceWidget* pVclOld = dynamic_cast<SalInstanceWidget*>(pOld);
+ vcl::Window* pWidgetOld = pVclOld ? pVclOld->getWidget() : nullptr;
+ if (pWidgetOld)
+ pWidgetOld->set_property("has-default", OUString::boolean(false));
+ else
+ recursively_unset_default_buttons();
+ if (pWidgetNew)
+ pWidgetNew->set_property("has-default", OUString::boolean(true));
+}
+
+bool SalInstanceWindow::is_default_widget(const weld::Widget* pCandidate) const
+{
+ const SalInstanceWidget* pVclCandidate = dynamic_cast<const SalInstanceWidget*>(pCandidate);
+ vcl::Window* pWidget = pVclCandidate ? pVclCandidate->getWidget() : nullptr;
+ return pWidget && pWidget->GetStyle() & WB_DEFBUTTON;
+}
+
+void SalInstanceWindow::set_window_state(const OString& rStr)
+{
+ SystemWindow* pSysWin = dynamic_cast<SystemWindow*>(m_xWindow.get());
+ assert(pSysWin);
+ pSysWin->SetWindowState(rStr);
+}
+
+OString SalInstanceWindow::get_window_state(WindowStateMask nMask) const
+{
+ SystemWindow* pSysWin = dynamic_cast<SystemWindow*>(m_xWindow.get());
+ assert(pSysWin);
+ return pSysWin->GetWindowState(nMask);
+}
+
+SystemEnvData SalInstanceWindow::get_system_data() const { return *m_xWindow->GetSystemData(); }
+
+VclPtr<VirtualDevice> SalInstanceWindow::screenshot()
+{
+ SystemWindow* pSysWin = dynamic_cast<SystemWindow*>(m_xWindow.get());
+ assert(pSysWin);
+ return pSysWin->createScreenshot();
+}
+
+weld::ScreenShotCollection SalInstanceWindow::collect_screenshot_data()
+{
+ weld::ScreenShotCollection aRet;
+
+ // collect all children. Choose start pos to be negative
+ // of target dialog's position to get all positions relative to (0,0)
+ const Point aParentPos(m_xWindow->GetPosPixel());
+ const basegfx::B2IPoint aTopLeft(-aParentPos.X(), -aParentPos.Y());
+ CollectChildren(*m_xWindow, aTopLeft, aRet);
+
+ return aRet;
+}
+
+SalInstanceWindow::~SalInstanceWindow()
+{
+ // tdf#129745 only undo overriding child help for the normal case, not for
+ // m_pBuilder of null which is the toplevel application frame case.
+ if (m_pBuilder)
+ clear_child_help(m_xWindow);
+}
+
+IMPL_LINK_NOARG(SalInstanceWindow, HelpHdl, vcl::Window&, bool)
+{
+ help();
+ return false;
+}
+
+typedef std::set<VclPtr<vcl::Window>> winset;
+
+namespace
+{
+void hideUnless(const vcl::Window* pTop, const winset& rVisibleWidgets,
+ std::vector<VclPtr<vcl::Window>>& rWasVisibleWidgets)
+{
+ for (vcl::Window* pChild = pTop->GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ if (rVisibleWidgets.find(pChild) == rVisibleWidgets.end())
+ {
+ rWasVisibleWidgets.emplace_back(pChild);
+ pChild->Hide();
+ }
+ else if (isContainerWindow(pChild))
+ {
+ hideUnless(pChild, rVisibleWidgets, rWasVisibleWidgets);
+ }
+ }
+}
+}
+
+SalInstanceDialog::SalInstanceDialog(::Dialog* pDialog, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceWindow(pDialog, pBuilder, bTakeOwnership)
+ , m_xDialog(pDialog)
+ , m_nOldEditWidthReq(0)
+ , m_nOldBorderWidth(0)
+{
+ const bool bScreenshotMode(officecfg::Office::Common::Misc::ScreenshotMode::get());
+ if (bScreenshotMode)
+ {
+ m_xDialog->SetPopupMenuHdl(LINK(this, SalInstanceDialog, PopupScreenShotMenuHdl));
+ }
+}
+
+bool SalInstanceDialog::runAsync(std::shared_ptr<weld::DialogController> aOwner,
+ const std::function<void(sal_Int32)>& rEndDialogFn)
+{
+ VclAbstractDialog::AsyncContext aCtx;
+ aCtx.mxOwnerDialogController = aOwner;
+ aCtx.maEndDialogFn = rEndDialogFn;
+ VclButtonBox* pActionArea = m_xDialog->get_action_area();
+ if (pActionArea)
+ sort_native_button_order(*pActionArea);
+ return m_xDialog->StartExecuteAsync(aCtx);
+}
+
+bool SalInstanceDialog::runAsync(std::shared_ptr<Dialog> const& rxSelf,
+ const std::function<void(sal_Int32)>& rEndDialogFn)
+{
+ assert(rxSelf.get() == this);
+ VclAbstractDialog::AsyncContext aCtx;
+ // In order to store a shared_ptr to ourself, we have to have been constructed by make_shared,
+ // which is that rxSelf enforces.
+ aCtx.mxOwnerSelf = rxSelf;
+ aCtx.maEndDialogFn = rEndDialogFn;
+ VclButtonBox* pActionArea = m_xDialog->get_action_area();
+ if (pActionArea)
+ sort_native_button_order(*pActionArea);
+ return m_xDialog->StartExecuteAsync(aCtx);
+}
+
+void SalInstanceDialog::collapse(weld::Widget* pEdit, weld::Widget* pButton)
+{
+ SalInstanceWidget* pVclEdit = dynamic_cast<SalInstanceWidget*>(pEdit);
+ assert(pVclEdit);
+ SalInstanceWidget* pVclButton = dynamic_cast<SalInstanceWidget*>(pButton);
+
+ vcl::Window* pRefEdit = pVclEdit->getWidget();
+ vcl::Window* pRefBtn = pVclButton ? pVclButton->getWidget() : nullptr;
+
+ auto nOldEditWidth = pRefEdit->GetSizePixel().Width();
+ m_nOldEditWidthReq = pRefEdit->get_width_request();
+
+ //We want just pRefBtn and pRefEdit to be shown
+ //mark widgets we want to be visible, starting with pRefEdit
+ //and all its direct parents.
+ winset aVisibleWidgets;
+ vcl::Window* pContentArea = m_xDialog->get_content_area();
+ for (vcl::Window* pCandidate = pRefEdit;
+ pCandidate && (pCandidate != pContentArea && pCandidate->IsVisible());
+ pCandidate = pCandidate->GetWindow(GetWindowType::RealParent))
+ {
+ aVisibleWidgets.insert(pCandidate);
+ }
+ //same again with pRefBtn, except stop if there's a
+ //shared parent in the existing widgets
+ for (vcl::Window* pCandidate = pRefBtn;
+ pCandidate && (pCandidate != pContentArea && pCandidate->IsVisible());
+ pCandidate = pCandidate->GetWindow(GetWindowType::RealParent))
+ {
+ if (aVisibleWidgets.insert(pCandidate).second)
+ break;
+ }
+
+ //hide everything except the aVisibleWidgets
+ hideUnless(pContentArea, aVisibleWidgets, m_aHiddenWidgets);
+
+ // the insert function case has an initially hidden edit widget, so it has
+ // not start size, so take larger of actual size and size request
+ pRefEdit->set_width_request(std::max(nOldEditWidth, m_nOldEditWidthReq));
+ m_nOldBorderWidth = m_xDialog->get_border_width();
+ m_xDialog->set_border_width(0);
+ if (vcl::Window* pActionArea = m_xDialog->get_action_area())
+ pActionArea->Hide();
+ m_xDialog->setOptimalLayoutSize(true);
+ m_xRefEdit = pRefEdit;
+}
+
+void SalInstanceDialog::undo_collapse()
+{
+ // All others: Show();
+ for (VclPtr<vcl::Window> const& pWindow : m_aHiddenWidgets)
+ {
+ pWindow->Show();
+ }
+ m_aHiddenWidgets.clear();
+
+ m_xRefEdit->set_width_request(m_nOldEditWidthReq);
+ m_xRefEdit.clear();
+ m_xDialog->set_border_width(m_nOldBorderWidth);
+ if (vcl::Window* pActionArea = m_xDialog->get_action_area())
+ pActionArea->Show();
+ m_xDialog->setOptimalLayoutSize(true);
+}
+
+void SalInstanceDialog::SetInstallLOKNotifierHdl(
+ const Link<void*, vcl::ILibreOfficeKitNotifier*>& rLink)
+{
+ m_xDialog->SetInstallLOKNotifierHdl(rLink);
+}
+
+int SalInstanceDialog::run()
+{
+ VclButtonBox* pActionArea = m_xDialog->get_action_area();
+ if (pActionArea)
+ sort_native_button_order(*pActionArea);
+ return m_xDialog->Execute();
+}
+
+void SalInstanceDialog::response(int nResponse) { m_xDialog->EndDialog(nResponse); }
+
+void SalInstanceDialog::add_button(const OUString& rText, int nResponse, const OString& rHelpId)
+{
+ VclButtonBox* pBox = m_xDialog->get_action_area();
+ VclPtr<PushButton> xButton(
+ VclPtr<PushButton>::Create(pBox, WB_CLIPCHILDREN | WB_CENTER | WB_VCENTER));
+ xButton->SetText(rText);
+ xButton->SetHelpId(rHelpId);
+
+ switch (nResponse)
+ {
+ case RET_OK:
+ xButton->set_id("ok");
+ break;
+ case RET_CLOSE:
+ xButton->set_id("close");
+ break;
+ case RET_CANCEL:
+ xButton->set_id("cancel");
+ break;
+ case RET_YES:
+ xButton->set_id("yes");
+ break;
+ case RET_NO:
+ xButton->set_id("no");
+ break;
+ }
+
+ xButton->Show();
+ m_xDialog->add_button(xButton, nResponse, true);
+}
+
+void SalInstanceDialog::set_modal(bool bModal)
+{
+ if (get_modal() == bModal)
+ return;
+ m_xDialog->SetModalInputMode(bModal);
+}
+
+bool SalInstanceDialog::get_modal() const { return m_xDialog->IsModalInputMode(); }
+
+void SalInstanceDialog::set_default_response(int nResponse)
+{
+ m_xDialog->set_default_response(nResponse);
+}
+
+weld::Container* SalInstanceDialog::weld_content_area()
+{
+ return new SalInstanceContainer(m_xDialog->get_content_area(), m_pBuilder, false);
+}
+
+IMPL_LINK(SalInstanceDialog, PopupScreenShotMenuHdl, const CommandEvent&, rCEvt, bool)
+{
+ if (CommandEventId::ContextMenu == rCEvt.GetCommand())
+ {
+ const Point aMenuPos(rCEvt.GetMousePosPixel());
+ ScopedVclPtrInstance<PopupMenu> aMenu;
+ sal_uInt16 nLocalID(1);
+
+ aMenu->InsertItem(nLocalID, VclResId(SV_BUTTONTEXT_SCREENSHOT));
+ aMenu->SetHelpText(nLocalID, VclResId(SV_HELPTEXT_SCREENSHOT));
+ aMenu->SetHelpId(nLocalID, "InteractiveScreenshotMode");
+ aMenu->EnableItem(nLocalID);
+
+ const sal_uInt16 nId(aMenu->Execute(m_xDialog, aMenuPos));
+
+ // 0 == no selection (so not usable as ID)
+ if (0 != nId)
+ {
+ // open screenshot annotation dialog
+ VclAbstractDialogFactory* pFact = VclAbstractDialogFactory::Create();
+ VclPtr<AbstractScreenshotAnnotationDlg> pTmp
+ = pFact->CreateScreenshotAnnotationDlg(*this);
+ ScopedVclPtr<AbstractScreenshotAnnotationDlg> pDialog(pTmp);
+
+ if (pDialog)
+ {
+ // currently just execute the dialog, no need to do
+ // different things for ok/cancel. This may change later,
+ // for that case use 'if (pDlg->Execute() == RET_OK)'
+ pDialog->Execute();
+ }
+ }
+
+ // consume event when:
+ // - CommandEventId::ContextMenu
+ // - bScreenshotMode
+ return true;
+ }
+
+ return false;
+}
+
+SalInstanceMessageDialog::SalInstanceMessageDialog(::MessageDialog* pDialog,
+ SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceDialog(pDialog, pBuilder, bTakeOwnership)
+ , m_xMessageDialog(pDialog)
+{
+}
+
+void SalInstanceMessageDialog::set_primary_text(const OUString& rText)
+{
+ m_xMessageDialog->set_primary_text(rText);
+}
+
+OUString SalInstanceMessageDialog::get_primary_text() const
+{
+ return m_xMessageDialog->get_primary_text();
+}
+
+void SalInstanceMessageDialog::set_secondary_text(const OUString& rText)
+{
+ m_xMessageDialog->set_secondary_text(rText);
+}
+
+OUString SalInstanceMessageDialog::get_secondary_text() const
+{
+ return m_xMessageDialog->get_secondary_text();
+}
+
+weld::Container* SalInstanceMessageDialog::weld_message_area()
+{
+ return new SalInstanceContainer(m_xMessageDialog->get_message_area(), m_pBuilder, false);
+}
+
+namespace
+{
+class SalInstanceAssistant : public SalInstanceDialog, public virtual weld::Assistant
+{
+private:
+ VclPtr<vcl::RoadmapWizard> m_xWizard;
+ std::vector<std::unique_ptr<SalInstanceContainer>> m_aPages;
+ std::vector<VclPtr<TabPage>> m_aAddedPages;
+ std::vector<int> m_aIds;
+ std::vector<VclPtr<VclGrid>> m_aAddedGrids;
+ Idle m_aUpdateRoadmapIdle;
+
+ int find_page(std::string_view rIdent) const
+ {
+ for (size_t i = 0; i < m_aAddedPages.size(); ++i)
+ {
+ if (m_aAddedPages[i]->get_id().toUtf8() == rIdent)
+ return i;
+ }
+ return -1;
+ }
+
+ int find_id(int nId) const
+ {
+ for (size_t i = 0; i < m_aIds.size(); ++i)
+ {
+ if (nId == m_aIds[i])
+ return i;
+ }
+ return -1;
+ }
+
+ DECL_LINK(OnRoadmapItemSelected, LinkParamNone*, void);
+ DECL_LINK(UpdateRoadmap_Hdl, Timer*, void);
+
+public:
+ SalInstanceAssistant(vcl::RoadmapWizard* pDialog, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceDialog(pDialog, pBuilder, bTakeOwnership)
+ , m_xWizard(pDialog)
+ , m_aUpdateRoadmapIdle("SalInstanceAssistant m_aUpdateRoadmapIdle")
+ {
+ m_xWizard->SetItemSelectHdl(LINK(this, SalInstanceAssistant, OnRoadmapItemSelected));
+
+ m_aUpdateRoadmapIdle.SetInvokeHandler(LINK(this, SalInstanceAssistant, UpdateRoadmap_Hdl));
+ m_aUpdateRoadmapIdle.SetPriority(TaskPriority::HIGHEST);
+ }
+
+ virtual int get_current_page() const override { return find_id(m_xWizard->GetCurLevel()); }
+
+ virtual int get_n_pages() const override { return m_aAddedPages.size(); }
+
+ virtual OString get_page_ident(int nPage) const override
+ {
+ return m_aAddedPages[nPage]->get_id().toUtf8();
+ }
+
+ virtual OString get_current_page_ident() const override
+ {
+ return get_page_ident(get_current_page());
+ }
+
+ virtual void set_current_page(int nPage) override
+ {
+ disable_notify_events();
+
+ // take the first shown page as the size for all pages
+ if (m_xWizard->GetPageSizePixel().Width() == 0)
+ {
+ Size aFinalSize;
+ for (int i = 0, nPages = get_n_pages(); i < nPages; ++i)
+ {
+ TabPage* pPage = m_xWizard->GetPage(m_aIds[i]);
+ assert(pPage);
+ Size aPageSize(pPage->get_preferred_size());
+ if (aPageSize.Width() > aFinalSize.Width())
+ aFinalSize.setWidth(aPageSize.Width());
+ if (aPageSize.Height() > aFinalSize.Height())
+ aFinalSize.setHeight(aPageSize.Height());
+ }
+ m_xWizard->SetPageSizePixel(aFinalSize);
+ }
+
+ (void)m_xWizard->ShowPage(m_aIds[nPage]);
+ enable_notify_events();
+ }
+
+ virtual void set_current_page(const OString& rIdent) override
+ {
+ int nIndex = find_page(rIdent);
+ if (nIndex == -1)
+ return;
+ set_current_page(nIndex);
+ }
+
+ virtual void set_page_index(const OString& rIdent, int nNewIndex) override
+ {
+ int nOldIndex = find_page(rIdent);
+
+ if (nOldIndex == -1)
+ return;
+
+ if (nOldIndex == nNewIndex)
+ return;
+
+ disable_notify_events();
+
+ auto entry = std::move(m_aAddedPages[nOldIndex]);
+ m_aAddedPages.erase(m_aAddedPages.begin() + nOldIndex);
+ m_aAddedPages.insert(m_aAddedPages.begin() + nNewIndex, std::move(entry));
+
+ int nId = m_aIds[nOldIndex];
+ m_aIds.erase(m_aIds.begin() + nOldIndex);
+ m_aIds.insert(m_aIds.begin() + nNewIndex, nId);
+
+ m_aUpdateRoadmapIdle.Start();
+
+ enable_notify_events();
+ }
+
+ virtual weld::Container* append_page(const OString& rIdent) override
+ {
+ VclPtrInstance<TabPage> xPage(m_xWizard);
+ VclPtrInstance<VclGrid> xGrid(xPage);
+ xPage->set_id(OUString::fromUtf8(rIdent));
+ xPage->Show();
+ xGrid->set_hexpand(true);
+ xGrid->set_vexpand(true);
+ xGrid->Show();
+ m_xWizard->AddPage(xPage);
+ m_aIds.push_back(m_aAddedPages.size());
+ m_xWizard->SetPage(m_aIds.back(), xPage);
+ m_aAddedPages.push_back(xPage);
+ m_aAddedGrids.push_back(xGrid);
+
+ m_aUpdateRoadmapIdle.Start();
+
+ m_aPages.emplace_back(new SalInstanceContainer(xGrid, m_pBuilder, false));
+ return m_aPages.back().get();
+ }
+
+ virtual OUString get_page_title(const OString& rIdent) const override
+ {
+ int nIndex = find_page(rIdent);
+ if (nIndex == -1)
+ return OUString();
+ return m_aAddedPages[nIndex]->GetText();
+ }
+
+ virtual void set_page_title(const OString& rIdent, const OUString& rTitle) override
+ {
+ int nIndex = find_page(rIdent);
+ if (nIndex == -1)
+ return;
+ if (m_aAddedPages[nIndex]->GetText() != rTitle)
+ {
+ disable_notify_events();
+ m_aAddedPages[nIndex]->SetText(rTitle);
+ m_aUpdateRoadmapIdle.Start();
+ enable_notify_events();
+ }
+ }
+
+ virtual void set_page_sensitive(const OString& rIdent, bool bSensitive) override
+ {
+ int nIndex = find_page(rIdent);
+ if (nIndex == -1)
+ return;
+ if (m_aAddedPages[nIndex]->IsEnabled() != bSensitive)
+ {
+ disable_notify_events();
+ m_aAddedPages[nIndex]->Enable(bSensitive);
+ m_aUpdateRoadmapIdle.Start();
+ enable_notify_events();
+ }
+ }
+
+ virtual void set_page_side_help_id(const OString& rHelpId) override
+ {
+ m_xWizard->SetRoadmapHelpId(rHelpId);
+ }
+
+ weld::Button* weld_widget_for_response(int nResponse) override;
+
+ virtual ~SalInstanceAssistant() override
+ {
+ for (auto& rGrid : m_aAddedGrids)
+ rGrid.disposeAndClear();
+ for (auto& rPage : m_aAddedPages)
+ rPage.disposeAndClear();
+ }
+};
+}
+
+IMPL_LINK_NOARG(SalInstanceAssistant, OnRoadmapItemSelected, LinkParamNone*, void)
+{
+ if (notify_events_disabled())
+ return;
+ auto nCurItemId = m_xWizard->GetCurrentRoadmapItemID();
+ int nPageIndex(find_id(nCurItemId));
+ if (!signal_jump_page(get_page_ident(nPageIndex)) && nCurItemId != m_xWizard->GetCurLevel())
+ m_xWizard->SelectRoadmapItemByID(m_xWizard->GetCurLevel());
+}
+
+IMPL_LINK_NOARG(SalInstanceAssistant, UpdateRoadmap_Hdl, Timer*, void)
+{
+ disable_notify_events();
+
+ m_xWizard->DeleteRoadmapItems();
+
+ int nPos = 0;
+ for (size_t i = 0; i < m_aAddedPages.size(); ++i)
+ {
+ const OUString& rLabel = m_aAddedPages[i]->GetText();
+ bool bSensitive = m_aAddedPages[i]->IsEnabled();
+ if (rLabel.isEmpty())
+ continue;
+ m_xWizard->InsertRoadmapItem(nPos++, rLabel, m_aIds[i], bSensitive);
+ }
+
+ m_xWizard->SelectRoadmapItemByID(m_aIds[get_current_page()], false);
+
+ m_xWizard->ShowRoadmap(nPos != 0);
+
+ enable_notify_events();
+}
+
+SalInstanceFrame::SalInstanceFrame(VclFrame* pFrame, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceContainer(pFrame, pBuilder, bTakeOwnership)
+ , m_xFrame(pFrame)
+{
+}
+
+void SalInstanceFrame::set_label(const OUString& rText) { m_xFrame->set_label(rText); }
+
+OUString SalInstanceFrame::get_label() const { return m_xFrame->get_label(); }
+
+namespace
+{
+class SalInstancePaned : public SalInstanceContainer, public virtual weld::Paned
+{
+private:
+ VclPtr<VclPaned> m_xPaned;
+
+public:
+ SalInstancePaned(VclPaned* pPaned, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceContainer(pPaned, pBuilder, bTakeOwnership)
+ , m_xPaned(pPaned)
+ {
+ }
+
+ virtual void set_position(int nPos) override { m_xPaned->set_position(nPos); }
+
+ virtual int get_position() const override { return m_xPaned->get_position(); }
+};
+
+class SalInstanceScrolledWindow : public SalInstanceContainer, public virtual weld::ScrolledWindow
+{
+private:
+ VclPtr<VclScrolledWindow> m_xScrolledWindow;
+ Link<ScrollBar*, void> m_aOrigVScrollHdl;
+ Link<ScrollBar*, void> m_aOrigHScrollHdl;
+ bool m_bUserManagedScrolling;
+
+ DECL_LINK(VscrollHdl, ScrollBar*, void);
+ DECL_LINK(HscrollHdl, ScrollBar*, void);
+
+ static void customize_scrollbars(ScrollBar& rScrollBar, const Color& rButtonTextColor,
+ const Color& rBackgroundColor, const Color& rShadowColor,
+ const Color& rFaceColor)
+ {
+ rScrollBar.EnableNativeWidget(false);
+ AllSettings aSettings = rScrollBar.GetSettings();
+ StyleSettings aStyleSettings = aSettings.GetStyleSettings();
+ aStyleSettings.SetButtonTextColor(rButtonTextColor);
+ aStyleSettings.SetCheckedColor(rBackgroundColor); // background
+ aStyleSettings.SetShadowColor(rShadowColor);
+ aStyleSettings.SetFaceColor(rFaceColor);
+ aSettings.SetStyleSettings(aStyleSettings);
+ rScrollBar.SetSettings(aSettings);
+ }
+
+public:
+ SalInstanceScrolledWindow(VclScrolledWindow* pScrolledWindow, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership, bool bUserManagedScrolling)
+ : SalInstanceContainer(pScrolledWindow, pBuilder, bTakeOwnership)
+ , m_xScrolledWindow(pScrolledWindow)
+ , m_bUserManagedScrolling(bUserManagedScrolling)
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ m_aOrigVScrollHdl = rVertScrollBar.GetScrollHdl();
+ rVertScrollBar.SetScrollHdl(LINK(this, SalInstanceScrolledWindow, VscrollHdl));
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ m_aOrigHScrollHdl = rHorzScrollBar.GetScrollHdl();
+ rHorzScrollBar.SetScrollHdl(LINK(this, SalInstanceScrolledWindow, HscrollHdl));
+ m_xScrolledWindow->setUserManagedScrolling(m_bUserManagedScrolling);
+ }
+
+ virtual void hadjustment_configure(int value, int lower, int upper, int step_increment,
+ int page_increment, int page_size) override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ rHorzScrollBar.SetRangeMin(lower);
+ rHorzScrollBar.SetRangeMax(upper);
+ rHorzScrollBar.SetLineSize(step_increment);
+ rHorzScrollBar.SetPageSize(page_increment);
+ rHorzScrollBar.SetThumbPos(value);
+ rHorzScrollBar.SetVisibleSize(page_size);
+ }
+
+ virtual int hadjustment_get_value() const override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.GetThumbPos();
+ }
+
+ virtual void hadjustment_set_value(int value) override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ rHorzScrollBar.SetThumbPos(value);
+ if (!m_bUserManagedScrolling)
+ m_aOrigHScrollHdl.Call(&rHorzScrollBar);
+ }
+
+ virtual int hadjustment_get_upper() const override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.GetRangeMax();
+ }
+
+ virtual void hadjustment_set_upper(int upper) override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ rHorzScrollBar.SetRangeMax(upper);
+ }
+
+ virtual int hadjustment_get_page_size() const override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.GetVisibleSize();
+ }
+
+ virtual void hadjustment_set_page_size(int size) override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.SetVisibleSize(size);
+ }
+
+ virtual void hadjustment_set_page_increment(int size) override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.SetPageSize(size);
+ }
+
+ virtual void hadjustment_set_step_increment(int size) override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ return rHorzScrollBar.SetLineSize(size);
+ }
+
+ virtual void set_hpolicy(VclPolicyType eHPolicy) override
+ {
+ WinBits nWinBits = m_xScrolledWindow->GetStyle() & ~(WB_AUTOHSCROLL | WB_HSCROLL);
+ if (eHPolicy == VclPolicyType::ALWAYS)
+ nWinBits |= WB_HSCROLL;
+ else if (eHPolicy == VclPolicyType::AUTOMATIC)
+ nWinBits |= WB_AUTOHSCROLL;
+ m_xScrolledWindow->SetStyle(nWinBits);
+ m_xScrolledWindow->queue_resize();
+ }
+
+ virtual VclPolicyType get_hpolicy() const override
+ {
+ WinBits nWinBits = m_xScrolledWindow->GetStyle();
+ if (nWinBits & WB_AUTOHSCROLL)
+ return VclPolicyType::AUTOMATIC;
+ else if (nWinBits & WB_HSCROLL)
+ return VclPolicyType::ALWAYS;
+ return VclPolicyType::NEVER;
+ }
+
+ virtual void vadjustment_configure(int value, int lower, int upper, int step_increment,
+ int page_increment, int page_size) override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ rVertScrollBar.SetRangeMin(lower);
+ rVertScrollBar.SetRangeMax(upper);
+ rVertScrollBar.SetLineSize(step_increment);
+ rVertScrollBar.SetPageSize(page_increment);
+ rVertScrollBar.SetThumbPos(value);
+ rVertScrollBar.SetVisibleSize(page_size);
+ }
+
+ virtual int vadjustment_get_value() const override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.GetThumbPos();
+ }
+
+ virtual void vadjustment_set_value(int value) override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ rVertScrollBar.SetThumbPos(value);
+ if (!m_bUserManagedScrolling)
+ m_aOrigVScrollHdl.Call(&rVertScrollBar);
+ }
+
+ virtual int vadjustment_get_upper() const override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.GetRangeMax();
+ }
+
+ virtual void vadjustment_set_upper(int upper) override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ rVertScrollBar.SetRangeMax(upper);
+ }
+
+ virtual int vadjustment_get_lower() const override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.GetRangeMin();
+ }
+
+ virtual void vadjustment_set_lower(int lower) override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ rVertScrollBar.SetRangeMin(lower);
+ }
+
+ virtual int vadjustment_get_page_size() const override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.GetVisibleSize();
+ }
+
+ virtual void vadjustment_set_page_size(int size) override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.SetVisibleSize(size);
+ }
+
+ virtual void vadjustment_set_page_increment(int size) override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.SetPageSize(size);
+ }
+
+ virtual void vadjustment_set_step_increment(int size) override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ return rVertScrollBar.SetLineSize(size);
+ }
+
+ virtual void set_vpolicy(VclPolicyType eVPolicy) override
+ {
+ WinBits nWinBits = m_xScrolledWindow->GetStyle() & ~(WB_AUTOVSCROLL | WB_VSCROLL);
+ if (eVPolicy == VclPolicyType::ALWAYS)
+ nWinBits |= WB_VSCROLL;
+ else if (eVPolicy == VclPolicyType::AUTOMATIC)
+ nWinBits |= WB_AUTOVSCROLL;
+ m_xScrolledWindow->SetStyle(nWinBits);
+ m_xScrolledWindow->queue_resize();
+ }
+
+ virtual VclPolicyType get_vpolicy() const override
+ {
+ WinBits nWinBits = m_xScrolledWindow->GetStyle();
+ if (nWinBits & WB_AUTOVSCROLL)
+ return VclPolicyType::AUTOMATIC;
+ else if (nWinBits & WB_VSCROLL)
+ return VclPolicyType::ALWAYS;
+ return VclPolicyType::NEVER;
+ }
+
+ virtual int get_scroll_thickness() const override
+ {
+ return m_xScrolledWindow->getVertScrollBar().get_preferred_size().Width();
+ }
+
+ virtual void set_scroll_thickness(int nThickness) override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ rHorzScrollBar.set_height_request(nThickness);
+ rVertScrollBar.set_width_request(nThickness);
+ }
+
+ virtual void customize_scrollbars(const Color& rBackgroundColor, const Color& rShadowColor,
+ const Color& rFaceColor) override
+ {
+ ScrollBar& rHorzScrollBar = m_xScrolledWindow->getHorzScrollBar();
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ customize_scrollbars(rHorzScrollBar, Color(0, 0, 0), rBackgroundColor, rShadowColor,
+ rFaceColor);
+ customize_scrollbars(rVertScrollBar, Color(0, 0, 0), rBackgroundColor, rShadowColor,
+ rFaceColor);
+ }
+
+ virtual ~SalInstanceScrolledWindow() override
+ {
+ ScrollBar& rVertScrollBar = m_xScrolledWindow->getVertScrollBar();
+ rVertScrollBar.SetScrollHdl(m_aOrigVScrollHdl);
+ }
+};
+}
+
+IMPL_LINK(SalInstanceScrolledWindow, VscrollHdl, ScrollBar*, pScrollBar, void)
+{
+ signal_vadjustment_changed();
+ if (!m_bUserManagedScrolling)
+ m_aOrigVScrollHdl.Call(pScrollBar);
+}
+
+IMPL_LINK_NOARG(SalInstanceScrolledWindow, HscrollHdl, ScrollBar*, void)
+{
+ signal_hadjustment_changed();
+ if (!m_bUserManagedScrolling)
+ m_aOrigHScrollHdl.Call(&m_xScrolledWindow->getHorzScrollBar());
+}
+
+SalInstanceNotebook::SalInstanceNotebook(TabControl* pNotebook, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceWidget(pNotebook, pBuilder, bTakeOwnership)
+ , m_xNotebook(pNotebook)
+{
+ m_xNotebook->SetActivatePageHdl(LINK(this, SalInstanceNotebook, ActivatePageHdl));
+ m_xNotebook->SetDeactivatePageHdl(LINK(this, SalInstanceNotebook, DeactivatePageHdl));
+}
+
+int SalInstanceNotebook::get_current_page() const
+{
+ return m_xNotebook->GetPagePos(m_xNotebook->GetCurPageId());
+}
+
+OString SalInstanceNotebook::get_page_ident(int nPage) const
+{
+ return m_xNotebook->GetPageName(m_xNotebook->GetPageId(nPage));
+}
+
+OString SalInstanceNotebook::get_current_page_ident() const
+{
+ return m_xNotebook->GetPageName(m_xNotebook->GetCurPageId());
+}
+
+int SalInstanceNotebook::get_page_index(const OString& rIdent) const
+{
+ sal_uInt16 nPageId = m_xNotebook->GetPageId(rIdent);
+ sal_uInt16 nPageIndex = m_xNotebook->GetPagePos(nPageId);
+ if (nPageIndex == TAB_PAGE_NOTFOUND)
+ return -1;
+ return nPageIndex;
+}
+
+weld::Container* SalInstanceNotebook::get_page(const OString& rIdent) const
+{
+ int nPageIndex = get_page_index(rIdent);
+ if (nPageIndex == -1)
+ return nullptr;
+ sal_uInt16 nPageId = m_xNotebook->GetPageId(rIdent);
+ TabPage* pPage = m_xNotebook->GetTabPage(nPageId);
+ vcl::Window* pChild = pPage->GetChild(0);
+ if (m_aPages.size() < nPageIndex + 1U)
+ m_aPages.resize(nPageIndex + 1U);
+ if (!m_aPages[nPageIndex])
+ m_aPages[nPageIndex] = std::make_shared<SalInstanceContainer>(pChild, m_pBuilder, false);
+ return m_aPages[nPageIndex].get();
+}
+
+void SalInstanceNotebook::set_current_page(int nPage)
+{
+ m_xNotebook->SetCurPageId(m_xNotebook->GetPageId(nPage));
+}
+
+void SalInstanceNotebook::set_current_page(const OString& rIdent)
+{
+ m_xNotebook->SetCurPageId(m_xNotebook->GetPageId(rIdent));
+}
+
+void SalInstanceNotebook::remove_page(const OString& rIdent)
+{
+ sal_uInt16 nPageId = m_xNotebook->GetPageId(rIdent);
+ sal_uInt16 nPageIndex = m_xNotebook->GetPagePos(nPageId);
+ if (nPageIndex == TAB_PAGE_NOTFOUND)
+ return;
+
+ m_xNotebook->RemovePage(nPageId);
+ if (nPageIndex < m_aPages.size())
+ m_aPages.erase(m_aPages.begin() + nPageIndex);
+
+ auto iter = m_aAddedPages.find(rIdent);
+ if (iter != m_aAddedPages.end())
+ {
+ iter->second.second.disposeAndClear();
+ iter->second.first.disposeAndClear();
+ m_aAddedPages.erase(iter);
+ }
+}
+
+void SalInstanceNotebook::insert_page(const OString& rIdent, const OUString& rLabel, int nPos)
+{
+ sal_uInt16 nPageCount = m_xNotebook->GetPageCount();
+ sal_uInt16 nLastPageId = nPageCount ? m_xNotebook->GetPageId(nPageCount - 1) : 0;
+ sal_uInt16 nNewPageId = nLastPageId + 1;
+ while (m_xNotebook->GetPagePos(nNewPageId) != TAB_PAGE_NOTFOUND)
+ ++nNewPageId;
+ m_xNotebook->InsertPage(nNewPageId, rLabel, nPos == -1 ? TAB_APPEND : nPos);
+ VclPtrInstance<TabPage> xPage(m_xNotebook);
+ VclPtrInstance<VclGrid> xGrid(xPage);
+ xPage->Show();
+ xGrid->set_hexpand(true);
+ xGrid->set_vexpand(true);
+ xGrid->Show();
+ m_xNotebook->SetTabPage(nNewPageId, xPage);
+ m_xNotebook->SetPageName(nNewPageId, rIdent);
+ m_aAddedPages.try_emplace(rIdent, xPage, xGrid);
+
+ if (nPos != -1)
+ {
+ unsigned int nPageIndex = static_cast<unsigned int>(nPos);
+ if (nPageIndex < m_aPages.size())
+ m_aPages.insert(m_aPages.begin() + nPageIndex, nullptr);
+ }
+}
+
+int SalInstanceNotebook::get_n_pages() const { return m_xNotebook->GetPageCount(); }
+
+OUString SalInstanceNotebook::get_tab_label_text(const OString& rIdent) const
+{
+ return m_xNotebook->GetPageText(m_xNotebook->GetPageId(rIdent));
+}
+
+void SalInstanceNotebook::set_tab_label_text(const OString& rIdent, const OUString& rText)
+{
+ return m_xNotebook->SetPageText(m_xNotebook->GetPageId(rIdent), rText);
+}
+
+void SalInstanceNotebook::set_show_tabs(bool bShow)
+{
+ m_xNotebook->set_property("show-tabs", OUString::boolean(bShow));
+}
+
+SalInstanceNotebook::~SalInstanceNotebook()
+{
+ for (auto& rItem : m_aAddedPages)
+ {
+ rItem.second.second.disposeAndClear();
+ rItem.second.first.disposeAndClear();
+ }
+ m_xNotebook->SetActivatePageHdl(Link<TabControl*, void>());
+ m_xNotebook->SetDeactivatePageHdl(Link<TabControl*, bool>());
+}
+
+IMPL_LINK_NOARG(SalInstanceNotebook, DeactivatePageHdl, TabControl*, bool)
+{
+ return !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
+}
+
+IMPL_LINK_NOARG(SalInstanceNotebook, ActivatePageHdl, TabControl*, void)
+{
+ m_aEnterPageHdl.Call(get_current_page_ident());
+}
+
+namespace
+{
+class SalInstanceVerticalNotebook : public SalInstanceWidget, public virtual weld::Notebook
+{
+private:
+ VclPtr<VerticalTabControl> m_xNotebook;
+ mutable std::vector<std::unique_ptr<SalInstanceContainer>> m_aPages;
+
+ DECL_LINK(DeactivatePageHdl, VerticalTabControl*, bool);
+ DECL_LINK(ActivatePageHdl, VerticalTabControl*, void);
+
+public:
+ SalInstanceVerticalNotebook(VerticalTabControl* pNotebook, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceWidget(pNotebook, pBuilder, bTakeOwnership)
+ , m_xNotebook(pNotebook)
+ {
+ m_xNotebook->SetActivatePageHdl(LINK(this, SalInstanceVerticalNotebook, ActivatePageHdl));
+ m_xNotebook->SetDeactivatePageHdl(
+ LINK(this, SalInstanceVerticalNotebook, DeactivatePageHdl));
+ }
+
+ virtual int get_current_page() const override
+ {
+ return m_xNotebook->GetPagePos(m_xNotebook->GetCurPageId());
+ }
+
+ virtual OString get_page_ident(int nPage) const override
+ {
+ return m_xNotebook->GetPageId(nPage);
+ }
+
+ virtual OString get_current_page_ident() const override { return m_xNotebook->GetCurPageId(); }
+
+ virtual int get_page_index(const OString& rIdent) const override
+ {
+ sal_uInt16 nPageIndex = m_xNotebook->GetPagePos(rIdent);
+ if (nPageIndex == TAB_PAGE_NOTFOUND)
+ return -1;
+ return nPageIndex;
+ }
+
+ virtual weld::Container* get_page(const OString& rIdent) const override
+ {
+ int nPageIndex = get_page_index(rIdent);
+ if (nPageIndex == -1)
+ return nullptr;
+ auto pChild = m_xNotebook->GetPage(rIdent);
+ if (m_aPages.size() < nPageIndex + 1U)
+ m_aPages.resize(nPageIndex + 1U);
+ if (!m_aPages[nPageIndex])
+ m_aPages[nPageIndex].reset(new SalInstanceContainer(pChild, m_pBuilder, false));
+ return m_aPages[nPageIndex].get();
+ }
+
+ virtual void set_current_page(int nPage) override
+ {
+ m_xNotebook->SetCurPageId(m_xNotebook->GetPageId(nPage));
+ }
+
+ virtual void set_current_page(const OString& rIdent) override
+ {
+ m_xNotebook->SetCurPageId(rIdent);
+ }
+
+ virtual void remove_page(const OString& rIdent) override
+ {
+ sal_uInt16 nPageIndex = m_xNotebook->GetPagePos(rIdent);
+ if (nPageIndex == TAB_PAGE_NOTFOUND)
+ return;
+ m_xNotebook->RemovePage(rIdent);
+ if (nPageIndex < m_aPages.size())
+ m_aPages.erase(m_aPages.begin() + nPageIndex);
+ }
+
+ virtual void insert_page(const OString& rIdent, const OUString& rLabel, int nPos) override
+ {
+ VclPtrInstance<VclGrid> xGrid(m_xNotebook->GetPageParent());
+ xGrid->set_hexpand(true);
+ xGrid->set_vexpand(true);
+ m_xNotebook->InsertPage(rIdent, rLabel, Image(), "", xGrid, nPos);
+
+ if (nPos != -1)
+ {
+ unsigned int nPageIndex = static_cast<unsigned int>(nPos);
+ if (nPageIndex < m_aPages.size())
+ m_aPages.insert(m_aPages.begin() + nPageIndex, nullptr);
+ }
+ }
+
+ virtual int get_n_pages() const override { return m_xNotebook->GetPageCount(); }
+
+ virtual void set_tab_label_text(const OString& rIdent, const OUString& rText) override
+ {
+ return m_xNotebook->SetPageText(rIdent, rText);
+ }
+
+ virtual OUString get_tab_label_text(const OString& rIdent) const override
+ {
+ return m_xNotebook->GetPageText(rIdent);
+ }
+
+ virtual void set_show_tabs(bool /*bShow*/) override
+ {
+ // if someone needs this they will have to implement it in VerticalTabControl
+ assert(false && "not implemented");
+ }
+
+ virtual ~SalInstanceVerticalNotebook() override
+ {
+ m_xNotebook->SetActivatePageHdl(Link<VerticalTabControl*, void>());
+ m_xNotebook->SetDeactivatePageHdl(Link<VerticalTabControl*, bool>());
+ }
+};
+}
+
+IMPL_LINK_NOARG(SalInstanceVerticalNotebook, DeactivatePageHdl, VerticalTabControl*, bool)
+{
+ return !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
+}
+
+IMPL_LINK_NOARG(SalInstanceVerticalNotebook, ActivatePageHdl, VerticalTabControl*, void)
+{
+ m_aEnterPageHdl.Call(get_current_page_ident());
+}
+
+SalInstanceButton::SalInstanceButton(::Button* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceWidget(pButton, pBuilder, bTakeOwnership)
+ , m_xButton(pButton)
+ , m_aOldClickHdl(pButton->GetClickHdl())
+{
+ m_xButton->SetClickHdl(LINK(this, SalInstanceButton, ClickHdl));
+}
+
+void SalInstanceButton::set_label(const OUString& rText) { m_xButton->SetText(rText); }
+
+void SalInstanceButton::set_image(VirtualDevice* pDevice)
+{
+ m_xButton->SetImageAlign(ImageAlign::Left);
+ if (pDevice)
+ m_xButton->SetModeImage(createImage(*pDevice));
+ else
+ m_xButton->SetModeImage(Image());
+}
+
+void SalInstanceButton::set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage)
+{
+ m_xButton->SetImageAlign(ImageAlign::Left);
+ m_xButton->SetModeImage(Image(rImage));
+}
+
+void SalInstanceButton::set_from_icon_name(const OUString& rIconName)
+{
+ m_xButton->SetModeImage(Image(StockImage::Yes, rIconName));
+}
+
+void SalInstanceButton::set_label_wrap(bool wrap)
+{
+ WinBits nBits = m_xButton->GetStyle();
+ nBits &= ~WB_WORDBREAK;
+ if (wrap)
+ nBits |= WB_WORDBREAK;
+ m_xButton->SetStyle(nBits);
+ m_xButton->queue_resize();
+}
+
+void SalInstanceButton::set_font(const vcl::Font& rFont)
+{
+ m_xButton->SetControlFont(rFont);
+ m_xButton->Invalidate();
+}
+
+void SalInstanceButton::set_custom_button(VirtualDevice* pDevice)
+{
+ if (pDevice)
+ m_xButton->SetCustomButtonImage(createImage(*pDevice));
+ else
+ m_xButton->SetCustomButtonImage(Image());
+ m_xButton->Invalidate();
+}
+
+OUString SalInstanceButton::get_label() const { return m_xButton->GetText(); }
+
+SalInstanceButton::~SalInstanceButton() { m_xButton->SetClickHdl(Link<::Button*, void>()); }
+
+IMPL_LINK(SalInstanceButton, ClickHdl, ::Button*, pButton, void)
+{
+ //if there's no handler set, disengage our intercept and
+ //run the click again to get default behaviour for cancel/ok
+ //etc buttons.
+ if (!m_aClickHdl.IsSet())
+ {
+ pButton->SetClickHdl(m_aOldClickHdl);
+ pButton->Click();
+ pButton->SetClickHdl(LINK(this, SalInstanceButton, ClickHdl));
+ return;
+ }
+ signal_clicked();
+}
+
+weld::Button* SalInstanceDialog::weld_widget_for_response(int nResponse)
+{
+ PushButton* pButton = dynamic_cast<PushButton*>(m_xDialog->get_widget_for_response(nResponse));
+ return pButton ? new SalInstanceButton(pButton, nullptr, false) : nullptr;
+}
+
+weld::Button* SalInstanceAssistant::weld_widget_for_response(int nResponse)
+{
+ PushButton* pButton = nullptr;
+ if (nResponse == RET_YES)
+ pButton = m_xWizard->m_pNextPage;
+ else if (nResponse == RET_NO)
+ pButton = m_xWizard->m_pPrevPage;
+ else if (nResponse == RET_OK)
+ pButton = m_xWizard->m_pFinish;
+ else if (nResponse == RET_CANCEL)
+ pButton = m_xWizard->m_pCancel;
+ else if (nResponse == RET_HELP)
+ pButton = m_xWizard->m_pHelp;
+ if (pButton)
+ return new SalInstanceButton(pButton, nullptr, false);
+ return nullptr;
+}
+
+SalInstanceMenuButton::SalInstanceMenuButton(::MenuButton* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceButton(pButton, pBuilder, bTakeOwnership)
+ , m_xMenuButton(pButton)
+ , m_nLastId(0)
+{
+ m_xMenuButton->SetActivateHdl(LINK(this, SalInstanceMenuButton, ActivateHdl));
+ m_xMenuButton->SetSelectHdl(LINK(this, SalInstanceMenuButton, MenuSelectHdl));
+ if (PopupMenu* pMenu = m_xMenuButton->GetPopupMenu())
+ {
+ pMenu->SetMenuFlags(MenuFlags::NoAutoMnemonics);
+ const auto nCount = pMenu->GetItemCount();
+ m_nLastId = nCount ? pMenu->GetItemId(nCount - 1) : 0;
+ }
+}
+
+void SalInstanceMenuButton::set_active(bool active)
+{
+ if (active == get_active())
+ return;
+ if (active)
+ m_xMenuButton->ExecuteMenu();
+ else
+ m_xMenuButton->CancelMenu();
+}
+
+bool SalInstanceMenuButton::get_active() const { return m_xMenuButton->InPopupMode(); }
+
+void SalInstanceMenuButton::set_inconsistent(bool /*inconsistent*/)
+{
+ //not available
+}
+
+bool SalInstanceMenuButton::get_inconsistent() const { return false; }
+
+void SalInstanceMenuButton::insert_item(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, VirtualDevice* pImageSurface,
+ TriState eCheckRadioFalse)
+{
+ m_nLastId = insert_to_menu(m_nLastId, m_xMenuButton->GetPopupMenu(), pos, rId, rStr, pIconName,
+ pImageSurface, nullptr, eCheckRadioFalse);
+}
+
+void SalInstanceMenuButton::insert_separator(int pos, const OUString& rId)
+{
+ auto nInsertPos = pos == -1 ? MENU_APPEND : pos;
+ m_xMenuButton->GetPopupMenu()->InsertSeparator(rId.toUtf8(), nInsertPos);
+}
+
+void SalInstanceMenuButton::set_item_sensitive(const OString& rIdent, bool bSensitive)
+{
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->EnableItem(rIdent, bSensitive);
+}
+
+void SalInstanceMenuButton::remove_item(const OString& rId)
+{
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->RemoveItem(pMenu->GetItemPos(pMenu->GetItemId(rId)));
+}
+
+void SalInstanceMenuButton::clear()
+{
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->Clear();
+}
+
+void SalInstanceMenuButton::set_item_active(const OString& rIdent, bool bActive)
+{
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->CheckItem(rIdent, bActive);
+}
+
+void SalInstanceMenuButton::set_item_label(const OString& rIdent, const OUString& rText)
+{
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->SetItemText(pMenu->GetItemId(rIdent), rText);
+}
+
+OUString SalInstanceMenuButton::get_item_label(const OString& rIdent) const
+{
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ return pMenu->GetItemText(pMenu->GetItemId(rIdent));
+}
+
+void SalInstanceMenuButton::set_item_visible(const OString& rIdent, bool bShow)
+{
+ PopupMenu* pMenu = m_xMenuButton->GetPopupMenu();
+ pMenu->ShowItem(pMenu->GetItemId(rIdent), bShow);
+}
+
+void SalInstanceMenuButton::set_popover(weld::Widget* pPopover)
+{
+ SalInstanceWidget* pPopoverWidget = dynamic_cast<SalInstanceWidget*>(pPopover);
+ m_xMenuButton->SetPopover(pPopoverWidget ? pPopoverWidget->getWidget() : nullptr);
+}
+
+SalInstanceMenuButton::~SalInstanceMenuButton()
+{
+ m_xMenuButton->SetSelectHdl(Link<::MenuButton*, void>());
+ m_xMenuButton->SetActivateHdl(Link<::MenuButton*, void>());
+}
+
+IMPL_LINK_NOARG(SalInstanceMenuButton, MenuSelectHdl, ::MenuButton*, void)
+{
+ signal_selected(m_xMenuButton->GetCurItemIdent());
+}
+
+IMPL_LINK_NOARG(SalInstanceMenuButton, ActivateHdl, ::MenuButton*, void)
+{
+ if (notify_events_disabled())
+ return;
+ signal_toggled();
+}
+
+namespace
+{
+class SalInstanceMenuToggleButton : public SalInstanceMenuButton,
+ public virtual weld::MenuToggleButton
+{
+private:
+ VclPtr<::MenuToggleButton> m_xMenuToggleButton;
+
+public:
+ SalInstanceMenuToggleButton(::MenuToggleButton* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceMenuButton(pButton, pBuilder, bTakeOwnership)
+ , m_xMenuToggleButton(pButton)
+ {
+ m_xMenuToggleButton->SetDelayMenu(true);
+ m_xMenuToggleButton->SetDropDown(PushButtonDropdownStyle::SplitMenuButton);
+ }
+
+ virtual void set_active(bool active) override
+ {
+ disable_notify_events();
+ m_xMenuToggleButton->SetActive(active);
+ enable_notify_events();
+ }
+
+ virtual bool get_active() const override { return m_xMenuToggleButton->GetActive(); }
+};
+}
+
+namespace
+{
+class SalInstanceLinkButton : public SalInstanceWidget, public virtual weld::LinkButton
+{
+private:
+ VclPtr<FixedHyperlink> m_xButton;
+ Link<FixedHyperlink&, void> m_aOrigClickHdl;
+
+ DECL_LINK(ClickHdl, FixedHyperlink&, void);
+
+public:
+ SalInstanceLinkButton(FixedHyperlink* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceWidget(pButton, pBuilder, bTakeOwnership)
+ , m_xButton(pButton)
+ {
+ m_aOrigClickHdl = m_xButton->GetClickHdl();
+ m_xButton->SetClickHdl(LINK(this, SalInstanceLinkButton, ClickHdl));
+ }
+
+ virtual void set_label(const OUString& rText) override { m_xButton->SetText(rText); }
+
+ virtual OUString get_label() const override { return m_xButton->GetText(); }
+
+ virtual void set_uri(const OUString& rUri) override { m_xButton->SetURL(rUri); }
+
+ virtual OUString get_uri() const override { return m_xButton->GetURL(); }
+
+ virtual ~SalInstanceLinkButton() override { m_xButton->SetClickHdl(m_aOrigClickHdl); }
+};
+}
+
+IMPL_LINK(SalInstanceLinkButton, ClickHdl, FixedHyperlink&, rButton, void)
+{
+ bool bConsumed = signal_activate_link();
+ if (!bConsumed)
+ m_aOrigClickHdl.Call(rButton);
+}
+
+SalInstanceRadioButton::SalInstanceRadioButton(::RadioButton* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceButton(pButton, pBuilder, bTakeOwnership)
+ , m_xRadioButton(pButton)
+{
+ m_xRadioButton->SetToggleHdl(LINK(this, SalInstanceRadioButton, ToggleHdl));
+}
+
+void SalInstanceRadioButton::set_active(bool active)
+{
+ disable_notify_events();
+ m_xRadioButton->Check(active);
+ enable_notify_events();
+}
+
+bool SalInstanceRadioButton::get_active() const { return m_xRadioButton->IsChecked(); }
+
+void SalInstanceRadioButton::set_image(VirtualDevice* pDevice)
+{
+ m_xRadioButton->SetImageAlign(ImageAlign::Center);
+ if (pDevice)
+ m_xRadioButton->SetModeImage(createImage(*pDevice));
+ else
+ m_xRadioButton->SetModeImage(Image());
+}
+
+void SalInstanceRadioButton::set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage)
+{
+ m_xRadioButton->SetImageAlign(ImageAlign::Center);
+ m_xRadioButton->SetModeImage(Image(rImage));
+}
+
+void SalInstanceRadioButton::set_from_icon_name(const OUString& rIconName)
+{
+ m_xRadioButton->SetModeRadioImage(Image(StockImage::Yes, rIconName));
+}
+
+void SalInstanceRadioButton::set_inconsistent(bool /*inconsistent*/)
+{
+ //not available
+}
+
+bool SalInstanceRadioButton::get_inconsistent() const { return false; }
+
+SalInstanceRadioButton::~SalInstanceRadioButton()
+{
+ m_xRadioButton->SetToggleHdl(Link<::RadioButton&, void>());
+}
+
+IMPL_LINK_NOARG(SalInstanceRadioButton, ToggleHdl, ::RadioButton&, void)
+{
+ if (notify_events_disabled())
+ return;
+ signal_toggled();
+}
+
+namespace
+{
+class SalInstanceToggleButton : public SalInstanceButton, public virtual weld::ToggleButton
+{
+private:
+ VclPtr<PushButton> m_xToggleButton;
+
+ DECL_LINK(ToggleListener, VclWindowEvent&, void);
+
+public:
+ SalInstanceToggleButton(PushButton* pButton, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceButton(pButton, pBuilder, bTakeOwnership)
+ , m_xToggleButton(pButton)
+ {
+ }
+
+ virtual void connect_toggled(const Link<Toggleable&, void>& rLink) override
+ {
+ assert(!m_aToggleHdl.IsSet());
+ m_xToggleButton->AddEventListener(LINK(this, SalInstanceToggleButton, ToggleListener));
+ weld::ToggleButton::connect_toggled(rLink);
+ }
+
+ virtual void set_active(bool active) override
+ {
+ disable_notify_events();
+ m_xToggleButton->Check(active);
+ enable_notify_events();
+ }
+
+ virtual bool get_active() const override { return m_xToggleButton->IsChecked(); }
+
+ virtual void set_inconsistent(bool inconsistent) override
+ {
+ disable_notify_events();
+ m_xToggleButton->SetState(inconsistent ? TRISTATE_INDET : TRISTATE_FALSE);
+ enable_notify_events();
+ }
+
+ virtual bool get_inconsistent() const override
+ {
+ return m_xToggleButton->GetState() == TRISTATE_INDET;
+ }
+
+ virtual ~SalInstanceToggleButton() override
+ {
+ if (m_aToggleHdl.IsSet())
+ m_xToggleButton->RemoveEventListener(
+ LINK(this, SalInstanceToggleButton, ToggleListener));
+ }
+};
+}
+
+IMPL_LINK(SalInstanceToggleButton, ToggleListener, VclWindowEvent&, rEvent, void)
+{
+ if (notify_events_disabled())
+ return;
+ if (rEvent.GetId() == VclEventId::PushbuttonToggle)
+ signal_toggled();
+}
+
+SalInstanceCheckButton::SalInstanceCheckButton(CheckBox* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceButton(pButton, pBuilder, bTakeOwnership)
+ , m_xCheckButton(pButton)
+{
+ m_xCheckButton->SetToggleHdl(LINK(this, SalInstanceCheckButton, ToggleHdl));
+}
+
+void SalInstanceCheckButton::set_active(bool active)
+{
+ disable_notify_events();
+ m_xCheckButton->EnableTriState(false);
+ m_xCheckButton->Check(active);
+ enable_notify_events();
+}
+
+bool SalInstanceCheckButton::get_active() const { return m_xCheckButton->IsChecked(); }
+
+void SalInstanceCheckButton::set_inconsistent(bool inconsistent)
+{
+ disable_notify_events();
+ m_xCheckButton->EnableTriState(true);
+ m_xCheckButton->SetState(inconsistent ? TRISTATE_INDET : TRISTATE_FALSE);
+ enable_notify_events();
+}
+
+bool SalInstanceCheckButton::get_inconsistent() const
+{
+ return m_xCheckButton->GetState() == TRISTATE_INDET;
+}
+
+SalInstanceCheckButton::~SalInstanceCheckButton()
+{
+ m_xCheckButton->SetToggleHdl(Link<CheckBox&, void>());
+}
+
+IMPL_LINK_NOARG(SalInstanceCheckButton, ToggleHdl, CheckBox&, void)
+{
+ if (notify_events_disabled())
+ return;
+ m_xCheckButton->EnableTriState(false);
+ signal_toggled();
+}
+
+namespace
+{
+class SalInstanceScale : public SalInstanceWidget, public virtual weld::Scale
+{
+private:
+ VclPtr<Slider> m_xScale;
+
+ DECL_LINK(SlideHdl, Slider*, void);
+
+public:
+ SalInstanceScale(Slider* pScale, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceWidget(pScale, pBuilder, bTakeOwnership)
+ , m_xScale(pScale)
+ {
+ m_xScale->SetSlideHdl(LINK(this, SalInstanceScale, SlideHdl));
+ }
+
+ virtual void set_value(int value) override { m_xScale->SetThumbPos(value); }
+
+ virtual void set_range(int min, int max) override
+ {
+ m_xScale->SetRangeMin(min);
+ m_xScale->SetRangeMax(max);
+ }
+
+ virtual int get_value() const override { return m_xScale->GetThumbPos(); }
+
+ virtual void set_increments(int step, int page) override
+ {
+ m_xScale->SetLineSize(step);
+ m_xScale->SetPageSize(page);
+ }
+
+ virtual void get_increments(int& step, int& page) const override
+ {
+ step = m_xScale->GetLineSize();
+ page = m_xScale->GetPageSize();
+ }
+
+ virtual ~SalInstanceScale() override { m_xScale->SetSlideHdl(Link<Slider*, void>()); }
+};
+}
+
+IMPL_LINK_NOARG(SalInstanceScale, SlideHdl, Slider*, void) { signal_value_changed(); }
+
+namespace
+{
+class SalInstanceSpinner : public SalInstanceWidget, public virtual weld::Spinner
+{
+private:
+ VclPtr<Throbber> m_xThrobber;
+
+public:
+ SalInstanceSpinner(Throbber* pThrobber, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceWidget(pThrobber, pBuilder, bTakeOwnership)
+ , m_xThrobber(pThrobber)
+ {
+ }
+
+ virtual void start() override { m_xThrobber->start(); }
+
+ virtual void stop() override { m_xThrobber->stop(); }
+};
+
+class SalInstanceProgressBar : public SalInstanceWidget, public virtual weld::ProgressBar
+{
+private:
+ VclPtr<::ProgressBar> m_xProgressBar;
+
+public:
+ SalInstanceProgressBar(::ProgressBar* pProgressBar, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceWidget(pProgressBar, pBuilder, bTakeOwnership)
+ , m_xProgressBar(pProgressBar)
+ {
+ }
+
+ virtual void set_percentage(int value) override { m_xProgressBar->SetValue(value); }
+
+ virtual OUString get_text() const override { return m_xProgressBar->GetText(); }
+
+ virtual void set_text(const OUString& rText) override { m_xProgressBar->SetText(rText); }
+};
+
+class SalInstanceCalendar : public SalInstanceWidget, public virtual weld::Calendar
+{
+private:
+ VclPtr<::Calendar> m_xCalendar;
+
+ DECL_LINK(SelectHdl, ::Calendar*, void);
+ DECL_LINK(ActivateHdl, ::Calendar*, void);
+
+public:
+ SalInstanceCalendar(::Calendar* pCalendar, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceWidget(pCalendar, pBuilder, bTakeOwnership)
+ , m_xCalendar(pCalendar)
+ {
+ m_xCalendar->SetSelectHdl(LINK(this, SalInstanceCalendar, SelectHdl));
+ m_xCalendar->SetActivateHdl(LINK(this, SalInstanceCalendar, ActivateHdl));
+ }
+
+ virtual void set_date(const Date& rDate) override { m_xCalendar->SetCurDate(rDate); }
+
+ virtual Date get_date() const override { return m_xCalendar->GetFirstSelectedDate(); }
+
+ virtual ~SalInstanceCalendar() override
+ {
+ m_xCalendar->SetSelectHdl(Link<::Calendar*, void>());
+ m_xCalendar->SetActivateHdl(Link<::Calendar*, void>());
+ }
+};
+}
+
+IMPL_LINK_NOARG(SalInstanceCalendar, SelectHdl, ::Calendar*, void)
+{
+ if (notify_events_disabled())
+ return;
+ signal_selected();
+}
+
+IMPL_LINK_NOARG(SalInstanceCalendar, ActivateHdl, ::Calendar*, void)
+{
+ if (notify_events_disabled())
+ return;
+ signal_activated();
+}
+
+SalInstanceImage::SalInstanceImage(FixedImage* pImage, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceWidget(pImage, pBuilder, bTakeOwnership)
+ , m_xImage(pImage)
+{
+}
+
+void SalInstanceImage::set_from_icon_name(const OUString& rIconName)
+{
+ m_xImage->SetImage(::Image(StockImage::Yes, rIconName));
+}
+
+void SalInstanceImage::set_image(VirtualDevice* pDevice)
+{
+ if (pDevice)
+ m_xImage->SetImage(createImage(*pDevice));
+ else
+ m_xImage->SetImage(::Image());
+}
+
+void SalInstanceImage::set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage)
+{
+ m_xImage->SetImage(::Image(rImage));
+}
+
+WeldTextFilter::WeldTextFilter(Link<OUString&, bool>& rInsertTextHdl)
+ : TextFilter(OUString())
+ , m_rInsertTextHdl(rInsertTextHdl)
+{
+}
+
+OUString WeldTextFilter::filter(const OUString& rText)
+{
+ if (!m_rInsertTextHdl.IsSet())
+ return rText;
+ OUString sText(rText);
+ const bool bContinue = m_rInsertTextHdl.Call(sText);
+ if (!bContinue)
+ return OUString();
+ return sText;
+}
+
+SalInstanceEntry::SalInstanceEntry(Edit* pEntry, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : SalInstanceWidget(pEntry, pBuilder, bTakeOwnership)
+ , m_xEntry(pEntry)
+ , m_aTextFilter(m_aInsertTextHdl)
+{
+ m_xEntry->SetModifyHdl(LINK(this, SalInstanceEntry, ChangeHdl));
+ m_xEntry->SetActivateHdl(LINK(this, SalInstanceEntry, ActivateHdl));
+ m_xEntry->SetTextFilter(&m_aTextFilter);
+}
+
+void SalInstanceEntry::set_text(const OUString& rText)
+{
+ disable_notify_events();
+ m_xEntry->SetText(rText);
+ enable_notify_events();
+}
+
+OUString SalInstanceEntry::get_text() const { return m_xEntry->GetText(); }
+
+void SalInstanceEntry::set_width_chars(int nChars) { m_xEntry->SetWidthInChars(nChars); }
+
+int SalInstanceEntry::get_width_chars() const { return m_xEntry->GetWidthInChars(); }
+
+void SalInstanceEntry::set_max_length(int nChars) { m_xEntry->SetMaxTextLen(nChars); }
+
+void SalInstanceEntry::select_region(int nStartPos, int nEndPos)
+{
+ disable_notify_events();
+ tools::Long nStart = nStartPos < 0 ? SELECTION_MAX : nStartPos;
+ tools::Long nEnd = nEndPos < 0 ? SELECTION_MAX : nEndPos;
+ m_xEntry->SetSelection(Selection(nStart, nEnd));
+ enable_notify_events();
+}
+
+bool SalInstanceEntry::get_selection_bounds(int& rStartPos, int& rEndPos)
+{
+ const Selection& rSelection = m_xEntry->GetSelection();
+ rStartPos = rSelection.Min();
+ rEndPos = rSelection.Max();
+ return rSelection.Len();
+}
+
+void SalInstanceEntry::replace_selection(const OUString& rText)
+{
+ m_xEntry->ReplaceSelected(rText);
+}
+
+void SalInstanceEntry::set_position(int nCursorPos)
+{
+ disable_notify_events();
+ if (nCursorPos < 0)
+ m_xEntry->SetCursorAtLast();
+ else
+ m_xEntry->SetSelection(Selection(nCursorPos, nCursorPos));
+ enable_notify_events();
+}
+
+int SalInstanceEntry::get_position() const { return m_xEntry->GetSelection().Max(); }
+
+void SalInstanceEntry::set_editable(bool bEditable) { m_xEntry->SetReadOnly(!bEditable); }
+
+bool SalInstanceEntry::get_editable() const { return !m_xEntry->IsReadOnly(); }
+
+void SalInstanceEntry::set_overwrite_mode(bool bOn) { m_xEntry->SetInsertMode(!bOn); }
+
+bool SalInstanceEntry::get_overwrite_mode() const { return !m_xEntry->IsInsertMode(); }
+
+namespace
+{
+void set_message_type(Edit* pEntry, weld::EntryMessageType eType)
+{
+ switch (eType)
+ {
+ case weld::EntryMessageType::Normal:
+ pEntry->SetForceControlBackground(false);
+ pEntry->SetControlForeground();
+ pEntry->SetControlBackground();
+ break;
+ case weld::EntryMessageType::Warning:
+ // tdf#114603: enable setting the background to a different color;
+ // relevant for GTK; see also #i75179#
+ pEntry->SetForceControlBackground(true);
+ pEntry->SetControlForeground(COL_BLACK);
+ pEntry->SetControlBackground(COL_YELLOW);
+ break;
+ case weld::EntryMessageType::Error:
+ // tdf#114603: enable setting the background to a different color;
+ // relevant for GTK; see also #i75179#
+ pEntry->SetForceControlBackground(true);
+ pEntry->SetControlForeground(COL_WHITE);
+ pEntry->SetControlBackground(0xff6563);
+ break;
+ }
+}
+}
+
+void SalInstanceEntry::set_message_type(weld::EntryMessageType eType)
+{
+ ::set_message_type(m_xEntry, eType);
+}
+
+void SalInstanceEntry::set_font(const vcl::Font& rFont)
+{
+ m_xEntry->SetControlFont(rFont);
+ m_xEntry->Invalidate();
+}
+
+void SalInstanceEntry::set_font_color(const Color& rColor)
+{
+ if (rColor == COL_AUTO)
+ m_xEntry->SetControlForeground();
+ else
+ m_xEntry->SetControlForeground(rColor);
+}
+
+void SalInstanceEntry::connect_cursor_position(const Link<Entry&, void>& rLink)
+{
+ assert(!m_aCursorPositionHdl.IsSet());
+ m_xEntry->AddEventListener(LINK(this, SalInstanceEntry, CursorListener));
+ weld::Entry::connect_cursor_position(rLink);
+}
+
+void SalInstanceEntry::set_placeholder_text(const OUString& rText)
+{
+ m_xEntry->SetPlaceholderText(rText);
+}
+
+Edit& SalInstanceEntry::getEntry() { return *m_xEntry; }
+
+void SalInstanceEntry::fire_signal_changed() { signal_changed(); }
+
+void SalInstanceEntry::cut_clipboard()
+{
+ m_xEntry->Cut();
+ m_xEntry->Modify();
+}
+
+void SalInstanceEntry::copy_clipboard() { m_xEntry->Copy(); }
+
+void SalInstanceEntry::paste_clipboard()
+{
+ m_xEntry->Paste();
+ m_xEntry->Modify();
+}
+
+namespace
+{
+void set_alignment(Edit& rEntry, TxtAlign eXAlign)
+{
+ WinBits nAlign(0);
+ switch (eXAlign)
+ {
+ case TxtAlign::Left:
+ nAlign = WB_LEFT;
+ break;
+ case TxtAlign::Center:
+ nAlign = WB_CENTER;
+ break;
+ case TxtAlign::Right:
+ nAlign = WB_RIGHT;
+ break;
+ }
+ WinBits nBits = rEntry.GetStyle();
+ nBits &= ~(WB_LEFT | WB_CENTER | WB_RIGHT);
+ rEntry.SetStyle(nBits | nAlign);
+}
+}
+
+void SalInstanceEntry::set_alignment(TxtAlign eXAlign) { ::set_alignment(*m_xEntry, eXAlign); }
+
+SalInstanceEntry::~SalInstanceEntry()
+{
+ if (m_aCursorPositionHdl.IsSet())
+ m_xEntry->RemoveEventListener(LINK(this, SalInstanceEntry, CursorListener));
+ m_xEntry->SetTextFilter(nullptr);
+ m_xEntry->SetActivateHdl(Link<Edit&, bool>());
+ m_xEntry->SetModifyHdl(Link<Edit&, void>());
+}
+
+IMPL_LINK_NOARG(SalInstanceEntry, ChangeHdl, Edit&, void) { signal_changed(); }
+
+IMPL_LINK(SalInstanceEntry, CursorListener, VclWindowEvent&, rEvent, void)
+{
+ if (notify_events_disabled())
+ return;
+ if (rEvent.GetId() == VclEventId::EditSelectionChanged
+ || rEvent.GetId() == VclEventId::EditCaretChanged)
+ signal_cursor_position();
+}
+
+IMPL_LINK_NOARG(SalInstanceEntry, ActivateHdl, Edit&, bool) { return m_aActivateHdl.Call(*this); }
+
+class SalInstanceTreeView;
+
+static SalInstanceTreeView* g_DragSource;
+
+namespace
+{
+// tdf#131581 if the TreeView is hidden then there are possibly additional
+// optimizations available
+class UpdateGuardIfHidden
+{
+private:
+ SvTabListBox& m_rTreeView;
+ bool m_bOrigUpdate;
+ bool m_bOrigEnableInvalidate;
+
+public:
+ UpdateGuardIfHidden(SvTabListBox& rTreeView)
+ : m_rTreeView(rTreeView)
+ // tdf#136962 only do SetUpdateMode(false) optimization if the widget is currently hidden
+ , m_bOrigUpdate(!m_rTreeView.IsVisible() && m_rTreeView.IsUpdateMode())
+ // tdf#137432 only do EnableInvalidate(false) optimization if the widget is currently hidden
+ , m_bOrigEnableInvalidate(!m_rTreeView.IsVisible()
+ && m_rTreeView.GetModel()->IsEnableInvalidate())
+ {
+ if (m_bOrigUpdate)
+ m_rTreeView.SetUpdateMode(false);
+ if (m_bOrigEnableInvalidate)
+ m_rTreeView.GetModel()->EnableInvalidate(false);
+ }
+
+ ~UpdateGuardIfHidden()
+ {
+ if (m_bOrigEnableInvalidate)
+ m_rTreeView.GetModel()->EnableInvalidate(true);
+ if (m_bOrigUpdate)
+ m_rTreeView.SetUpdateMode(true);
+ }
+};
+}
+
+// Each row has a cell for the expander image, (and an optional cell for a
+// checkbutton if enable_toggle_buttons has been called) which precede
+// index 0
+int SalInstanceTreeView::to_internal_model(int col) const
+{
+ if (m_xTreeView->nTreeFlags & SvTreeFlags::CHKBTN)
+ ++col; // skip checkbutton column
+ ++col; //skip expander column
+ return col;
+}
+
+int SalInstanceTreeView::to_external_model(int col) const
+{
+ if (m_xTreeView->nTreeFlags & SvTreeFlags::CHKBTN)
+ --col; // skip checkbutton column
+ --col; //skip expander column
+ return col;
+}
+
+bool SalInstanceTreeView::IsDummyEntry(SvTreeListEntry* pEntry) const
+{
+ return o3tl::trim(m_xTreeView->GetEntryText(pEntry)) == u"<dummy>";
+}
+
+SvTreeListEntry* SalInstanceTreeView::GetPlaceHolderChild(SvTreeListEntry* pEntry) const
+{
+ if (pEntry->HasChildren())
+ {
+ auto pChild = m_xTreeView->FirstChild(pEntry);
+ assert(pChild);
+ if (IsDummyEntry(pChild))
+ return pChild;
+ }
+ return nullptr;
+}
+
+void SalInstanceTreeView::set_font_color(SvTreeListEntry* pEntry, const Color& rColor)
+{
+ if (rColor == COL_AUTO)
+ pEntry->SetTextColor(std::optional<Color>());
+ else
+ pEntry->SetTextColor(rColor);
+}
+
+void SalInstanceTreeView::AddStringItem(SvTreeListEntry* pEntry, const OUString& rStr, int nCol)
+{
+ auto xCell = std::make_unique<SvLBoxString>(rStr);
+ if (m_aCustomRenders.count(nCol))
+ xCell->SetCustomRender();
+ pEntry->AddItem(std::move(xCell));
+}
+
+void SalInstanceTreeView::do_insert(const weld::TreeIter* pParent, int pos, const OUString* pStr,
+ const OUString* pId, const OUString* pIconName,
+ const VirtualDevice* pImageSurface, bool bChildrenOnDemand,
+ weld::TreeIter* pRet, bool bIsSeparator)
+{
+ disable_notify_events();
+ const SalInstanceTreeIter* pVclIter = static_cast<const SalInstanceTreeIter*>(pParent);
+ SvTreeListEntry* iter = pVclIter ? pVclIter->iter : nullptr;
+ auto nInsertPos = pos == -1 ? TREELIST_APPEND : pos;
+ void* pUserData;
+ if (pId)
+ {
+ m_aUserData.emplace_back(std::make_unique<OUString>(*pId));
+ pUserData = m_aUserData.back().get();
+ }
+ else
+ pUserData = nullptr;
+
+ SvTreeListEntry* pEntry = new SvTreeListEntry;
+ if (bIsSeparator)
+ pEntry->SetFlags(pEntry->GetFlags() | SvTLEntryFlags::IS_SEPARATOR);
+
+ if (m_xTreeView->nTreeFlags & SvTreeFlags::CHKBTN)
+ AddStringItem(pEntry, "", -1);
+
+ if (pIconName || pImageSurface)
+ {
+ Image aImage(pIconName ? createImage(*pIconName) : createImage(*pImageSurface));
+ pEntry->AddItem(std::make_unique<SvLBoxContextBmp>(aImage, aImage, false));
+ }
+ else
+ {
+ Image aDummy;
+ pEntry->AddItem(std::make_unique<SvLBoxContextBmp>(aDummy, aDummy, false));
+ }
+ if (pStr)
+ AddStringItem(pEntry, *pStr, 0);
+ pEntry->SetUserData(pUserData);
+ m_xTreeView->Insert(pEntry, iter, nInsertPos);
+
+ if (pRet)
+ {
+ SalInstanceTreeIter* pVclRetIter = static_cast<SalInstanceTreeIter*>(pRet);
+ pVclRetIter->iter = pEntry;
+ }
+
+ if (bChildrenOnDemand)
+ {
+ SvTreeListEntry* pPlaceHolder
+ = m_xTreeView->InsertEntry("<dummy>", pEntry, false, 0, nullptr);
+ SvViewDataEntry* pViewData = m_xTreeView->GetViewDataEntry(pPlaceHolder);
+ pViewData->SetSelectable(false);
+ }
+
+ if (bIsSeparator)
+ {
+ SvViewDataEntry* pViewData = m_xTreeView->GetViewDataEntry(pEntry);
+ pViewData->SetSelectable(false);
+ }
+
+ enable_notify_events();
+}
+
+void SalInstanceTreeView::update_checkbutton_column_width(SvTreeListEntry* pEntry)
+{
+ SvViewDataEntry* pViewData = m_xTreeView->GetViewDataEntry(pEntry);
+ m_xTreeView->InitViewData(pViewData, pEntry);
+ m_xTreeView->CheckBoxInserted(pEntry);
+}
+
+void SalInstanceTreeView::InvalidateModelEntry(SvTreeListEntry* pEntry)
+{
+ if (!m_xTreeView->GetModel()->IsEnableInvalidate())
+ return;
+ m_xTreeView->ModelHasEntryInvalidated(pEntry);
+}
+
+void SalInstanceTreeView::do_set_toggle(SvTreeListEntry* pEntry, TriState eState, int col)
+{
+ assert(col >= 0 && o3tl::make_unsigned(col) < pEntry->ItemCount());
+ // if it's the placeholder to allow a blank column, replace it now
+ if (pEntry->GetItem(col).GetType() != SvLBoxItemType::Button)
+ {
+ SvLBoxButtonData* pData = m_bTogglesAsRadio ? &m_aRadioButtonData : &m_aCheckButtonData;
+ pEntry->ReplaceItem(std::make_unique<SvLBoxButton>(pData), 0);
+ update_checkbutton_column_width(pEntry);
+ }
+ SvLBoxItem& rItem = pEntry->GetItem(col);
+ assert(dynamic_cast<SvLBoxButton*>(&rItem));
+ switch (eState)
+ {
+ case TRISTATE_TRUE:
+ static_cast<SvLBoxButton&>(rItem).SetStateChecked();
+ break;
+ case TRISTATE_FALSE:
+ static_cast<SvLBoxButton&>(rItem).SetStateUnchecked();
+ break;
+ case TRISTATE_INDET:
+ static_cast<SvLBoxButton&>(rItem).SetStateTristate();
+ break;
+ }
+
+ InvalidateModelEntry(pEntry);
+}
+
+TriState SalInstanceTreeView::do_get_toggle(SvTreeListEntry* pEntry, int col)
+{
+ if (static_cast<size_t>(col) == pEntry->ItemCount())
+ return TRISTATE_FALSE;
+
+ assert(col >= 0 && o3tl::make_unsigned(col) < pEntry->ItemCount());
+ SvLBoxItem& rItem = pEntry->GetItem(col);
+ assert(dynamic_cast<SvLBoxButton*>(&rItem));
+ SvLBoxButton& rToggle = static_cast<SvLBoxButton&>(rItem);
+ if (rToggle.IsStateTristate())
+ return TRISTATE_INDET;
+ else if (rToggle.IsStateChecked())
+ return TRISTATE_TRUE;
+ return TRISTATE_FALSE;
+}
+
+TriState SalInstanceTreeView::get_toggle(SvTreeListEntry* pEntry, int col) const
+{
+ if (col == -1)
+ {
+ assert(m_xTreeView->nTreeFlags & SvTreeFlags::CHKBTN);
+ return do_get_toggle(pEntry, 0);
+ }
+ col = to_internal_model(col);
+ return do_get_toggle(pEntry, col);
+}
+
+void SalInstanceTreeView::set_toggle(SvTreeListEntry* pEntry, TriState eState, int col)
+{
+ if (col == -1)
+ {
+ assert(m_xTreeView->nTreeFlags & SvTreeFlags::CHKBTN);
+ do_set_toggle(pEntry, eState, 0);
+ return;
+ }
+
+ col = to_internal_model(col);
+
+ // blank out missing entries
+ for (int i = pEntry->ItemCount(); i < col; ++i)
+ AddStringItem(pEntry, "", i - 1);
+
+ if (static_cast<size_t>(col) == pEntry->ItemCount())
+ {
+ SvLBoxButtonData* pData = m_bTogglesAsRadio ? &m_aRadioButtonData : &m_aCheckButtonData;
+ pEntry->AddItem(std::make_unique<SvLBoxButton>(pData));
+ update_checkbutton_column_width(pEntry);
+ }
+
+ do_set_toggle(pEntry, eState, col);
+}
+
+bool SalInstanceTreeView::get_text_emphasis(SvTreeListEntry* pEntry, int col) const
+{
+ col = to_internal_model(col);
+
+ assert(col >= 0 && o3tl::make_unsigned(col) < pEntry->ItemCount());
+ SvLBoxItem& rItem = pEntry->GetItem(col);
+ assert(dynamic_cast<SvLBoxString*>(&rItem));
+ return static_cast<SvLBoxString&>(rItem).IsEmphasized();
+}
+
+void SalInstanceTreeView::set_header_item_width(const std::vector<int>& rWidths)
+{
+ LclHeaderTabListBox* pHeaderBox = dynamic_cast<LclHeaderTabListBox*>(m_xTreeView.get());
+ if (HeaderBar* pHeaderBar = pHeaderBox ? pHeaderBox->GetHeaderBar() : nullptr)
+ {
+ for (size_t i = 0; i < rWidths.size(); ++i)
+ pHeaderBar->SetItemSize(pHeaderBar->GetItemId(i), rWidths[i]);
+ }
+}
+
+SalInstanceTreeView::SalInstanceTreeView(SvTabListBox* pTreeView, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceWidget(pTreeView, pBuilder, bTakeOwnership)
+ , m_xTreeView(pTreeView)
+ , m_aCheckButtonData(pTreeView, false)
+ , m_aRadioButtonData(pTreeView, true)
+ , m_bTogglesAsRadio(false)
+ , m_nSortColumn(-1)
+{
+ m_xTreeView->SetNodeDefaultImages();
+ m_xTreeView->SetForceMakeVisible(true);
+ m_xTreeView->SetSelectHdl(LINK(this, SalInstanceTreeView, SelectHdl));
+ m_xTreeView->SetDeselectHdl(LINK(this, SalInstanceTreeView, DeSelectHdl));
+ m_xTreeView->SetDoubleClickHdl(LINK(this, SalInstanceTreeView, DoubleClickHdl));
+ m_xTreeView->SetExpandingHdl(LINK(this, SalInstanceTreeView, ExpandingHdl));
+ m_xTreeView->SetPopupMenuHdl(LINK(this, SalInstanceTreeView, PopupMenuHdl));
+ m_xTreeView->SetCustomRenderHdl(LINK(this, SalInstanceTreeView, CustomRenderHdl));
+ m_xTreeView->SetCustomMeasureHdl(LINK(this, SalInstanceTreeView, CustomMeasureHdl));
+ const tools::Long aTabPositions[] = { 0 };
+ m_xTreeView->SetTabs(SAL_N_ELEMENTS(aTabPositions), aTabPositions);
+ LclHeaderTabListBox* pHeaderBox = dynamic_cast<LclHeaderTabListBox*>(m_xTreeView.get());
+
+ if (pHeaderBox)
+ {
+ if (HeaderBar* pHeaderBar = pHeaderBox->GetHeaderBar())
+ {
+ //make the last entry fill available space
+ pHeaderBar->SetItemSize(pHeaderBar->GetItemId(pHeaderBar->GetItemCount() - 1),
+ HEADERBAR_FULLSIZE);
+ pHeaderBar->SetEndDragHdl(LINK(this, SalInstanceTreeView, EndDragHdl));
+ pHeaderBar->SetSelectHdl(LINK(this, SalInstanceTreeView, HeaderBarClickedHdl));
+ }
+ pHeaderBox->SetEditingEntryHdl(LINK(this, SalInstanceTreeView, EditingEntryHdl));
+ pHeaderBox->SetEditedEntryHdl(LINK(this, SalInstanceTreeView, EditedEntryHdl));
+ }
+ else
+ {
+ static_cast<LclTabListBox&>(*m_xTreeView)
+ .SetModelChangedHdl(LINK(this, SalInstanceTreeView, ModelChangedHdl));
+ static_cast<LclTabListBox&>(*m_xTreeView)
+ .SetStartDragHdl(LINK(this, SalInstanceTreeView, StartDragHdl));
+ static_cast<LclTabListBox&>(*m_xTreeView)
+ .SetEndDragHdl(LINK(this, SalInstanceTreeView, FinishDragHdl));
+ static_cast<LclTabListBox&>(*m_xTreeView)
+ .SetEditingEntryHdl(LINK(this, SalInstanceTreeView, EditingEntryHdl));
+ static_cast<LclTabListBox&>(*m_xTreeView)
+ .SetEditedEntryHdl(LINK(this, SalInstanceTreeView, EditedEntryHdl));
+ }
+ m_aCheckButtonData.SetLink(LINK(this, SalInstanceTreeView, ToggleHdl));
+ m_aRadioButtonData.SetLink(LINK(this, SalInstanceTreeView, ToggleHdl));
+}
+
+void SalInstanceTreeView::connect_query_tooltip(const Link<const weld::TreeIter&, OUString>& rLink)
+{
+ weld::TreeView::connect_query_tooltip(rLink);
+ m_xTreeView->SetTooltipHdl(LINK(this, SalInstanceTreeView, TooltipHdl));
+}
+
+void SalInstanceTreeView::columns_autosize()
+{
+ std::vector<tools::Long> aWidths;
+ m_xTreeView->getPreferredDimensions(aWidths);
+ if (aWidths.size() > 2)
+ {
+ std::vector<int> aColWidths;
+ for (size_t i = 1; i < aWidths.size() - 1; ++i)
+ aColWidths.push_back(aWidths[i] - aWidths[i - 1]);
+ set_column_fixed_widths(aColWidths);
+ }
+}
+
+void SalInstanceTreeView::freeze()
+{
+ bool bIsFirstFreeze = IsFirstFreeze();
+ SalInstanceWidget::freeze();
+ if (bIsFirstFreeze)
+ {
+ m_xTreeView->SetUpdateMode(false);
+ m_xTreeView->GetModel()->EnableInvalidate(false);
+ }
+}
+
+void SalInstanceTreeView::thaw()
+{
+ bool bIsLastThaw = IsLastThaw();
+ if (bIsLastThaw)
+ {
+ m_xTreeView->GetModel()->EnableInvalidate(true);
+ m_xTreeView->SetUpdateMode(true);
+ }
+ SalInstanceWidget::thaw();
+}
+
+void SalInstanceTreeView::set_column_fixed_widths(const std::vector<int>& rWidths)
+{
+ std::vector<tools::Long> aTabPositions{ 0 };
+ for (size_t i = 0; i < rWidths.size(); ++i)
+ aTabPositions.push_back(aTabPositions[i] + rWidths[i]);
+ m_xTreeView->SetTabs(aTabPositions.size(), aTabPositions.data(), MapUnit::MapPixel);
+ set_header_item_width(rWidths);
+ // call Resize to recalculate based on the new tabs
+ m_xTreeView->Resize();
+}
+
+void SalInstanceTreeView::set_column_editables(const std::vector<bool>& rEditables)
+{
+ size_t nTabCount = rEditables.size();
+ for (size_t i = 0; i < nTabCount; ++i)
+ m_xTreeView->SetTabEditable(i, rEditables[i]);
+}
+
+void SalInstanceTreeView::set_centered_column(int nCol)
+{
+ m_xTreeView->SetTabJustify(nCol, SvTabJustify::AdjustCenter);
+}
+
+int SalInstanceTreeView::get_column_width(int nColumn) const
+{
+ LclHeaderTabListBox* pHeaderBox = dynamic_cast<LclHeaderTabListBox*>(m_xTreeView.get());
+ if (HeaderBar* pHeaderBar = pHeaderBox ? pHeaderBox->GetHeaderBar() : nullptr)
+ return pHeaderBar->GetItemSize(pHeaderBar->GetItemId(nColumn));
+ // GetTab(0) gives the position of the bitmap which is automatically inserted by the TabListBox.
+ // So the first text column's width is Tab(2)-Tab(1).
+ auto nWidthPixel
+ = m_xTreeView->GetLogicTab(nColumn + 2) - m_xTreeView->GetLogicTab(nColumn + 1);
+ nWidthPixel -= SV_TAB_BORDER;
+ return nWidthPixel;
+}
+
+OUString SalInstanceTreeView::get_column_title(int nColumn) const
+{
+ LclHeaderTabListBox* pHeaderBox = dynamic_cast<LclHeaderTabListBox*>(m_xTreeView.get());
+ if (HeaderBar* pHeaderBar = pHeaderBox ? pHeaderBox->GetHeaderBar() : nullptr)
+ {
+ return pHeaderBar->GetItemText(pHeaderBar->GetItemId(nColumn));
+ }
+ return OUString();
+}
+
+void SalInstanceTreeView::set_column_title(int nColumn, const OUString& rTitle)
+{
+ LclHeaderTabListBox* pHeaderBox = dynamic_cast<LclHeaderTabListBox*>(m_xTreeView.get());
+ if (HeaderBar* pHeaderBar = pHeaderBox ? pHeaderBox->GetHeaderBar() : nullptr)
+ {
+ return pHeaderBar->SetItemText(pHeaderBar->GetItemId(nColumn), rTitle);
+ }
+}
+
+void SalInstanceTreeView::set_column_custom_renderer(int nColumn, bool bEnable)
+{
+ assert(n_children() == 0 && "tree must be empty");
+ if (bEnable)
+ m_aCustomRenders.insert(nColumn);
+ else
+ m_aCustomRenders.erase(nColumn);
+}
+
+void SalInstanceTreeView::queue_draw()
+{
+ // invalidate the entries
+ SvTreeList* pModel = m_xTreeView->GetModel();
+ for (SvTreeListEntry* pEntry = m_xTreeView->First(); pEntry; pEntry = m_xTreeView->Next(pEntry))
+ pModel->InvalidateEntry(pEntry);
+}
+
+void SalInstanceTreeView::show()
+{
+ if (LclHeaderTabListBox* pHeaderBox = dynamic_cast<LclHeaderTabListBox*>(m_xTreeView.get()))
+ pHeaderBox->GetParent()->Show();
+ SalInstanceWidget::show();
+}
+
+void SalInstanceTreeView::hide()
+{
+ if (LclHeaderTabListBox* pHeaderBox = dynamic_cast<LclHeaderTabListBox*>(m_xTreeView.get()))
+ pHeaderBox->GetParent()->Hide();
+ SalInstanceWidget::hide();
+}
+
+void SalInstanceTreeView::insert(const weld::TreeIter* pParent, int pos, const OUString* pStr,
+ const OUString* pId, const OUString* pIconName,
+ VirtualDevice* pImageSurface, bool bChildrenOnDemand,
+ weld::TreeIter* pRet)
+{
+ do_insert(pParent, pos, pStr, pId, pIconName, pImageSurface, bChildrenOnDemand, pRet, false);
+}
+
+void SalInstanceTreeView::insert_separator(int pos, const OUString& /*rId*/)
+{
+ OUString sSep(VclResId(STR_SEPARATOR));
+ do_insert(nullptr, pos, &sSep, nullptr, nullptr, nullptr, false, nullptr, true);
+}
+
+void SalInstanceTreeView::bulk_insert_for_each(
+ int nSourceCount, const std::function<void(weld::TreeIter&, int nSourceIndex)>& func,
+ const weld::TreeIter* pParent, const std::vector<int>* pFixedWidths)
+{
+ const SalInstanceTreeIter* pVclIter = static_cast<const SalInstanceTreeIter*>(pParent);
+ SvTreeListEntry* pVclParent = pVclIter ? pVclIter->iter : nullptr;
+
+ freeze();
+ if (!pVclParent)
+ clear();
+ else
+ {
+ while (SvTreeListEntry* pChild = m_xTreeView->FirstChild(pVclParent))
+ m_xTreeView->RemoveEntry(pChild);
+ }
+ SalInstanceTreeIter aVclIter(static_cast<SvTreeListEntry*>(nullptr));
+
+ m_xTreeView->nTreeFlags |= SvTreeFlags::MANINS;
+
+ if (pFixedWidths)
+ set_header_item_width(*pFixedWidths);
+
+ bool bHasAutoCheckButton(m_xTreeView->nTreeFlags & SvTreeFlags::CHKBTN);
+ size_t nExtraCols = bHasAutoCheckButton ? 2 : 1;
+
+ Image aDummy;
+ for (int i = 0; i < nSourceCount; ++i)
+ {
+ aVclIter.iter = new SvTreeListEntry;
+ if (bHasAutoCheckButton)
+ AddStringItem(aVclIter.iter, "", -1);
+ aVclIter.iter->AddItem(std::make_unique<SvLBoxContextBmp>(aDummy, aDummy, false));
+ m_xTreeView->Insert(aVclIter.iter, pVclParent, TREELIST_APPEND);
+ func(aVclIter, i);
+
+ if (!pFixedWidths)
+ continue;
+
+ size_t nFixedWidths = std::min(pFixedWidths->size(), aVclIter.iter->ItemCount());
+ for (size_t j = 0; j < nFixedWidths; ++j)
+ {
+ SvLBoxItem& rItem = aVclIter.iter->GetItem(j + nExtraCols);
+ SvViewDataItem* pViewDataItem = m_xTreeView->GetViewDataItem(aVclIter.iter, &rItem);
+ pViewDataItem->mnWidth = (*pFixedWidths)[j];
+ }
+ }
+
+ m_xTreeView->nTreeFlags &= ~SvTreeFlags::MANINS;
+
+ thaw();
+}
+
+void SalInstanceTreeView::set_font_color(int pos, const Color& rColor)
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ set_font_color(pEntry, rColor);
+}
+
+void SalInstanceTreeView::set_font_color(const weld::TreeIter& rIter, const Color& rColor)
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_font_color(rVclIter.iter, rColor);
+}
+
+void SalInstanceTreeView::remove(int pos)
+{
+ disable_notify_events();
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ m_xTreeView->RemoveEntry(pEntry);
+ enable_notify_events();
+}
+
+int SalInstanceTreeView::find_text(const OUString& rText) const
+{
+ for (SvTreeListEntry* pEntry = m_xTreeView->First(); pEntry; pEntry = m_xTreeView->Next(pEntry))
+ {
+ if (SvTabListBox::GetEntryText(pEntry, 0) == rText)
+ return SvTreeList::GetRelPos(pEntry);
+ }
+ return -1;
+}
+
+int SalInstanceTreeView::find_id(const OUString& rId) const
+{
+ for (SvTreeListEntry* pEntry = m_xTreeView->First(); pEntry; pEntry = m_xTreeView->Next(pEntry))
+ {
+ const OUString* pId = static_cast<const OUString*>(pEntry->GetUserData());
+ if (!pId)
+ continue;
+ if (rId == *pId)
+ return SvTreeList::GetRelPos(pEntry);
+ }
+ return -1;
+}
+
+void SalInstanceTreeView::swap(int pos1, int pos2)
+{
+ int min = std::min(pos1, pos2);
+ int max = std::max(pos1, pos2);
+ SvTreeList* pModel = m_xTreeView->GetModel();
+ SvTreeListEntry* pEntry1 = pModel->GetEntry(nullptr, min);
+ SvTreeListEntry* pEntry2 = pModel->GetEntry(nullptr, max);
+ pModel->Move(pEntry1, pEntry2);
+}
+
+void SalInstanceTreeView::clear()
+{
+ disable_notify_events();
+ m_xTreeView->Clear();
+ m_aUserData.clear();
+ enable_notify_events();
+}
+
+int SalInstanceTreeView::n_children() const
+{
+ return m_xTreeView->GetModel()->GetChildList(nullptr).size();
+}
+
+int SalInstanceTreeView::iter_n_children(const weld::TreeIter& rIter) const
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return m_xTreeView->GetModel()->GetChildList(rVclIter.iter).size();
+}
+
+void SalInstanceTreeView::select(int pos)
+{
+ assert(m_xTreeView->IsUpdateMode()
+ && "don't select when frozen, select after thaw. Note selection doesn't survive a "
+ "freeze");
+ disable_notify_events();
+ if (pos == -1 || (pos == 0 && n_children() == 0))
+ m_xTreeView->SelectAll(false);
+ else
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ assert(pEntry && "bad pos?");
+ m_xTreeView->Select(pEntry, true);
+ m_xTreeView->MakeVisible(pEntry);
+ }
+ enable_notify_events();
+}
+
+int SalInstanceTreeView::get_cursor_index() const
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetCurEntry();
+ if (!pEntry)
+ return -1;
+ return SvTreeList::GetRelPos(pEntry);
+}
+
+void SalInstanceTreeView::set_cursor(int pos)
+{
+ disable_notify_events();
+ if (pos == -1)
+ m_xTreeView->SetCurEntry(nullptr);
+ else
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ m_xTreeView->SetCurEntry(pEntry);
+ }
+ enable_notify_events();
+}
+
+void SalInstanceTreeView::scroll_to_row(int pos)
+{
+ assert(m_xTreeView->IsUpdateMode()
+ && "don't select when frozen, select after thaw. Note selection doesn't survive a "
+ "freeze");
+ disable_notify_events();
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ m_xTreeView->MakeVisible(pEntry);
+ enable_notify_events();
+}
+
+bool SalInstanceTreeView::is_selected(int pos) const
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ return m_xTreeView->IsSelected(pEntry);
+}
+
+void SalInstanceTreeView::unselect(int pos)
+{
+ assert(m_xTreeView->IsUpdateMode()
+ && "don't select when frozen, select after thaw. Note selection doesn't survive a "
+ "freeze");
+ disable_notify_events();
+ if (pos == -1)
+ m_xTreeView->SelectAll(true);
+ else
+ {
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ m_xTreeView->Select(pEntry, false);
+ }
+ enable_notify_events();
+}
+
+std::vector<int> SalInstanceTreeView::get_selected_rows() const
+{
+ std::vector<int> aRows;
+
+ aRows.reserve(m_xTreeView->GetSelectionCount());
+ for (SvTreeListEntry* pEntry = m_xTreeView->FirstSelected(); pEntry;
+ pEntry = m_xTreeView->NextSelected(pEntry))
+ aRows.push_back(SvTreeList::GetRelPos(pEntry));
+
+ return aRows;
+}
+
+OUString SalInstanceTreeView::get_text(SvTreeListEntry* pEntry, int col) const
+{
+ if (col == -1)
+ return SvTabListBox::GetEntryText(pEntry, 0);
+
+ col = to_internal_model(col);
+
+ if (static_cast<size_t>(col) == pEntry->ItemCount())
+ return OUString();
+
+ assert(col >= 0 && o3tl::make_unsigned(col) < pEntry->ItemCount());
+ SvLBoxItem& rItem = pEntry->GetItem(col);
+ assert(dynamic_cast<SvLBoxString*>(&rItem));
+ return static_cast<SvLBoxString&>(rItem).GetText();
+}
+
+OUString SalInstanceTreeView::get_text(int pos, int col) const
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ return get_text(pEntry, col);
+}
+
+void SalInstanceTreeView::set_text(SvTreeListEntry* pEntry, const OUString& rText, int col)
+{
+ if (col == -1)
+ {
+ m_xTreeView->SetEntryText(pEntry, rText);
+ return;
+ }
+
+ col = to_internal_model(col);
+
+ // blank out missing entries
+ for (int i = pEntry->ItemCount(); i < col; ++i)
+ AddStringItem(pEntry, "", i - 1);
+
+ if (static_cast<size_t>(col) == pEntry->ItemCount())
+ {
+ AddStringItem(pEntry, rText, col - 1);
+ SvViewDataEntry* pViewData = m_xTreeView->GetViewDataEntry(pEntry);
+ m_xTreeView->InitViewData(pViewData, pEntry);
+ }
+ else
+ {
+ assert(col >= 0 && o3tl::make_unsigned(col) < pEntry->ItemCount());
+ SvLBoxItem& rItem = pEntry->GetItem(col);
+ assert(dynamic_cast<SvLBoxString*>(&rItem));
+ static_cast<SvLBoxString&>(rItem).SetText(rText);
+ }
+
+ InvalidateModelEntry(pEntry);
+}
+
+void SalInstanceTreeView::set_text(int pos, const OUString& rText, int col)
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ set_text(pEntry, rText, col);
+}
+
+void SalInstanceTreeView::set_sensitive(SvTreeListEntry* pEntry, bool bSensitive, int col)
+{
+ if (col == -1)
+ {
+ auto nFlags = pEntry->GetFlags() & ~SvTLEntryFlags::SEMITRANSPARENT;
+ if (!bSensitive)
+ nFlags = nFlags | SvTLEntryFlags::SEMITRANSPARENT;
+ pEntry->SetFlags(nFlags);
+ const sal_uInt16 nCount = pEntry->ItemCount();
+ for (sal_uInt16 nCur = 0; nCur < nCount; ++nCur)
+ {
+ SvLBoxItem& rItem = pEntry->GetItem(nCur);
+ if (rItem.GetType() == SvLBoxItemType::String)
+ {
+ rItem.Enable(bSensitive);
+ InvalidateModelEntry(pEntry);
+ break;
+ }
+ }
+ return;
+ }
+
+ col = to_internal_model(col);
+
+ assert(col >= 0 && o3tl::make_unsigned(col) < pEntry->ItemCount());
+ SvLBoxItem& rItem = pEntry->GetItem(col);
+ rItem.Enable(bSensitive);
+
+ InvalidateModelEntry(pEntry);
+}
+
+void SalInstanceTreeView::set_sensitive(int pos, bool bSensitive, int col)
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ set_sensitive(pEntry, bSensitive, col);
+}
+
+void SalInstanceTreeView::set_sensitive(const weld::TreeIter& rIter, bool bSensitive, int col)
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_sensitive(rVclIter.iter, bSensitive, col);
+}
+
+TriState SalInstanceTreeView::get_toggle(int pos, int col) const
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ return get_toggle(pEntry, col);
+}
+
+TriState SalInstanceTreeView::get_toggle(const weld::TreeIter& rIter, int col) const
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return get_toggle(rVclIter.iter, col);
+}
+
+void SalInstanceTreeView::enable_toggle_buttons(weld::ColumnToggleType eType)
+{
+ assert(n_children() == 0 && "tree must be empty");
+ m_bTogglesAsRadio = eType == weld::ColumnToggleType::Radio;
+
+ SvLBoxButtonData* pData = m_bTogglesAsRadio ? &m_aRadioButtonData : &m_aCheckButtonData;
+ m_xTreeView->EnableCheckButton(pData);
+ // EnableCheckButton clobbered this, restore it
+ pData->SetLink(LINK(this, SalInstanceTreeView, ToggleHdl));
+}
+
+void SalInstanceTreeView::set_toggle(int pos, TriState eState, int col)
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ set_toggle(pEntry, eState, col);
+}
+
+void SalInstanceTreeView::set_toggle(const weld::TreeIter& rIter, TriState eState, int col)
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_toggle(rVclIter.iter, eState, col);
+}
+
+void SalInstanceTreeView::set_clicks_to_toggle(int nToggleBehavior)
+{
+ m_xTreeView->SetClicksToToggle(nToggleBehavior);
+}
+
+void SalInstanceTreeView::set_extra_row_indent(const weld::TreeIter& rIter, int nIndentLevel)
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ rVclIter.iter->SetExtraIndent(nIndentLevel);
+}
+
+void SalInstanceTreeView::set_text_emphasis(SvTreeListEntry* pEntry, bool bOn, int col)
+{
+ col = to_internal_model(col);
+
+ assert(col >= 0 && o3tl::make_unsigned(col) < pEntry->ItemCount());
+ SvLBoxItem& rItem = pEntry->GetItem(col);
+ assert(dynamic_cast<SvLBoxString*>(&rItem));
+ static_cast<SvLBoxString&>(rItem).Emphasize(bOn);
+
+ InvalidateModelEntry(pEntry);
+}
+
+void SalInstanceTreeView::set_text_emphasis(const weld::TreeIter& rIter, bool bOn, int col)
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_text_emphasis(rVclIter.iter, bOn, col);
+}
+
+void SalInstanceTreeView::set_text_emphasis(int pos, bool bOn, int col)
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ set_text_emphasis(pEntry, bOn, col);
+}
+
+bool SalInstanceTreeView::get_text_emphasis(const weld::TreeIter& rIter, int col) const
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return get_text_emphasis(rVclIter.iter, col);
+}
+
+bool SalInstanceTreeView::get_text_emphasis(int pos, int col) const
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ return get_text_emphasis(pEntry, col);
+}
+
+void SalInstanceTreeView::set_text_align(SvTreeListEntry* pEntry, double fAlign, int col)
+{
+ col = to_internal_model(col);
+
+ assert(col >= 0 && o3tl::make_unsigned(col) < pEntry->ItemCount());
+ SvLBoxItem& rItem = pEntry->GetItem(col);
+ assert(dynamic_cast<SvLBoxString*>(&rItem));
+ static_cast<SvLBoxString&>(rItem).Align(fAlign);
+
+ InvalidateModelEntry(pEntry);
+}
+
+void SalInstanceTreeView::set_text_align(const weld::TreeIter& rIter, double fAlign, int col)
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_text_align(rVclIter.iter, fAlign, col);
+}
+
+void SalInstanceTreeView::set_text_align(int pos, double fAlign, int col)
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ set_text_align(pEntry, fAlign, col);
+}
+
+void SalInstanceTreeView::connect_editing(const Link<const weld::TreeIter&, bool>& rStartLink,
+ const Link<const iter_string&, bool>& rEndLink)
+{
+ m_xTreeView->EnableInplaceEditing(rStartLink.IsSet() || rEndLink.IsSet());
+ weld::TreeView::connect_editing(rStartLink, rEndLink);
+}
+
+void SalInstanceTreeView::start_editing(const weld::TreeIter& rIter)
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ m_xTreeView->EditEntry(rVclIter.iter);
+}
+
+void SalInstanceTreeView::end_editing() { m_xTreeView->EndEditing(); }
+
+void SalInstanceTreeView::set_image(SvTreeListEntry* pEntry, const Image& rImage, int col)
+{
+ if (col == -1)
+ {
+ m_xTreeView->SetExpandedEntryBmp(pEntry, rImage);
+ m_xTreeView->SetCollapsedEntryBmp(pEntry, rImage);
+ return;
+ }
+
+ col = to_internal_model(col);
+
+ // blank out missing entries
+ for (int i = pEntry->ItemCount(); i < col; ++i)
+ AddStringItem(pEntry, "", i - 1);
+
+ if (static_cast<size_t>(col) == pEntry->ItemCount())
+ {
+ pEntry->AddItem(std::make_unique<SvLBoxContextBmp>(rImage, rImage, false));
+ SvViewDataEntry* pViewData = m_xTreeView->GetViewDataEntry(pEntry);
+ m_xTreeView->InitViewData(pViewData, pEntry);
+ }
+ else
+ {
+ assert(col >= 0 && o3tl::make_unsigned(col) < pEntry->ItemCount());
+ SvLBoxItem& rItem = pEntry->GetItem(col);
+ assert(dynamic_cast<SvLBoxContextBmp*>(&rItem));
+ static_cast<SvLBoxContextBmp&>(rItem).SetBitmap1(rImage);
+ static_cast<SvLBoxContextBmp&>(rItem).SetBitmap2(rImage);
+ }
+
+ m_xTreeView->CalcEntryHeight(pEntry);
+ InvalidateModelEntry(pEntry);
+}
+
+void SalInstanceTreeView::set_image(int pos, const OUString& rImage, int col)
+{
+ set_image(m_xTreeView->GetEntry(nullptr, pos), createImage(rImage), col);
+}
+
+void SalInstanceTreeView::set_image(int pos,
+ const css::uno::Reference<css::graphic::XGraphic>& rImage,
+ int col)
+{
+ set_image(m_xTreeView->GetEntry(nullptr, pos), Image(rImage), col);
+}
+
+void SalInstanceTreeView::set_image(int pos, VirtualDevice& rImage, int col)
+{
+ set_image(m_xTreeView->GetEntry(nullptr, pos), createImage(rImage), col);
+}
+
+void SalInstanceTreeView::set_image(const weld::TreeIter& rIter, const OUString& rImage, int col)
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_image(rVclIter.iter, createImage(rImage), col);
+}
+
+void SalInstanceTreeView::set_image(const weld::TreeIter& rIter,
+ const css::uno::Reference<css::graphic::XGraphic>& rImage,
+ int col)
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_image(rVclIter.iter, Image(rImage), col);
+}
+
+void SalInstanceTreeView::set_image(const weld::TreeIter& rIter, VirtualDevice& rImage, int col)
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_image(rVclIter.iter, createImage(rImage), col);
+}
+
+const OUString* SalInstanceTreeView::getEntryData(int index) const
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, index);
+ return pEntry ? static_cast<const OUString*>(pEntry->GetUserData()) : nullptr;
+}
+
+OUString SalInstanceTreeView::get_id(int pos) const
+{
+ const OUString* pRet = getEntryData(pos);
+ if (!pRet)
+ return OUString();
+ return *pRet;
+}
+
+void SalInstanceTreeView::set_id(SvTreeListEntry* pEntry, const OUString& rId)
+{
+ m_aUserData.emplace_back(std::make_unique<OUString>(rId));
+ pEntry->SetUserData(m_aUserData.back().get());
+}
+
+void SalInstanceTreeView::set_id(int pos, const OUString& rId)
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(nullptr, pos);
+ set_id(pEntry, rId);
+}
+
+int SalInstanceTreeView::get_selected_index() const
+{
+ assert(m_xTreeView->IsUpdateMode() && "don't request selection when frozen");
+ SvTreeListEntry* pEntry = m_xTreeView->FirstSelected();
+ if (!pEntry)
+ return -1;
+ return SvTreeList::GetRelPos(pEntry);
+}
+
+OUString SalInstanceTreeView::get_selected_text() const
+{
+ assert(m_xTreeView->IsUpdateMode() && "don't request selection when frozen");
+ if (SvTreeListEntry* pEntry = m_xTreeView->FirstSelected())
+ return SvTabListBox::GetEntryText(pEntry, 0);
+ return OUString();
+}
+
+OUString SalInstanceTreeView::get_selected_id() const
+{
+ assert(m_xTreeView->IsUpdateMode() && "don't request selection when frozen");
+ if (SvTreeListEntry* pEntry = m_xTreeView->FirstSelected())
+ {
+ if (const OUString* pStr = static_cast<const OUString*>(pEntry->GetUserData()))
+ return *pStr;
+ }
+ return OUString();
+}
+
+std::unique_ptr<weld::TreeIter>
+SalInstanceTreeView::make_iterator(const weld::TreeIter* pOrig) const
+{
+ return std::unique_ptr<weld::TreeIter>(
+ new SalInstanceTreeIter(static_cast<const SalInstanceTreeIter*>(pOrig)));
+}
+
+void SalInstanceTreeView::copy_iterator(const weld::TreeIter& rSource, weld::TreeIter& rDest) const
+{
+ const SalInstanceTreeIter& rVclSource(static_cast<const SalInstanceTreeIter&>(rSource));
+ SalInstanceTreeIter& rVclDest(static_cast<SalInstanceTreeIter&>(rDest));
+ rVclDest.iter = rVclSource.iter;
+}
+
+bool SalInstanceTreeView::get_selected(weld::TreeIter* pIter) const
+{
+ SvTreeListEntry* pEntry = m_xTreeView->FirstSelected();
+ auto pVclIter = static_cast<SalInstanceTreeIter*>(pIter);
+ if (pVclIter)
+ pVclIter->iter = pEntry;
+ return pEntry != nullptr;
+}
+
+bool SalInstanceTreeView::get_cursor(weld::TreeIter* pIter) const
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetCurEntry();
+ auto pVclIter = static_cast<SalInstanceTreeIter*>(pIter);
+ if (pVclIter)
+ pVclIter->iter = pEntry;
+ return pEntry != nullptr;
+}
+
+void SalInstanceTreeView::set_cursor(const weld::TreeIter& rIter)
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ disable_notify_events();
+ m_xTreeView->SetCurEntry(rVclIter.iter);
+ enable_notify_events();
+}
+
+bool SalInstanceTreeView::get_iter_first(weld::TreeIter& rIter) const
+{
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rIter);
+ rVclIter.iter = m_xTreeView->GetEntry(0);
+ return rVclIter.iter != nullptr;
+}
+
+bool SalInstanceTreeView::get_iter_abs_pos(weld::TreeIter& rIter, int nAbsPos) const
+{
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rIter);
+ rVclIter.iter = m_xTreeView->GetEntryAtAbsPos(nAbsPos);
+ return rVclIter.iter != nullptr;
+}
+
+bool SalInstanceTreeView::iter_next_sibling(weld::TreeIter& rIter) const
+{
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rIter);
+ rVclIter.iter = rVclIter.iter->NextSibling();
+ return rVclIter.iter != nullptr;
+}
+
+bool SalInstanceTreeView::iter_previous_sibling(weld::TreeIter& rIter) const
+{
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rIter);
+ rVclIter.iter = rVclIter.iter->PrevSibling();
+ return rVclIter.iter != nullptr;
+}
+
+bool SalInstanceTreeView::iter_next(weld::TreeIter& rIter) const
+{
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rIter);
+ rVclIter.iter = m_xTreeView->Next(rVclIter.iter);
+ if (rVclIter.iter && IsDummyEntry(rVclIter.iter))
+ return iter_next(rVclIter);
+ return rVclIter.iter != nullptr;
+}
+
+bool SalInstanceTreeView::iter_previous(weld::TreeIter& rIter) const
+{
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rIter);
+ rVclIter.iter = m_xTreeView->Prev(rVclIter.iter);
+ if (rVclIter.iter && IsDummyEntry(rVclIter.iter))
+ return iter_previous(rVclIter);
+ return rVclIter.iter != nullptr;
+}
+
+bool SalInstanceTreeView::iter_children(weld::TreeIter& rIter) const
+{
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rIter);
+ rVclIter.iter = m_xTreeView->FirstChild(rVclIter.iter);
+ bool bRet = rVclIter.iter != nullptr;
+ if (bRet)
+ {
+ //on-demand dummy entry doesn't count
+ return !IsDummyEntry(rVclIter.iter);
+ }
+ return bRet;
+}
+
+bool SalInstanceTreeView::iter_parent(weld::TreeIter& rIter) const
+{
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rIter);
+ rVclIter.iter = m_xTreeView->GetParent(rVclIter.iter);
+ return rVclIter.iter != nullptr;
+}
+
+void SalInstanceTreeView::remove(const weld::TreeIter& rIter)
+{
+ disable_notify_events();
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ m_xTreeView->RemoveEntry(rVclIter.iter);
+ enable_notify_events();
+}
+
+void SalInstanceTreeView::select(const weld::TreeIter& rIter)
+{
+ assert(m_xTreeView->IsUpdateMode()
+ && "don't select when frozen, select after thaw. Note selection doesn't survive a "
+ "freeze");
+ disable_notify_events();
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ m_xTreeView->Select(rVclIter.iter, true);
+ enable_notify_events();
+}
+
+void SalInstanceTreeView::scroll_to_row(const weld::TreeIter& rIter)
+{
+ assert(m_xTreeView->IsUpdateMode()
+ && "don't select when frozen, select after thaw. Note selection doesn't survive a "
+ "freeze");
+ disable_notify_events();
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ m_xTreeView->MakeVisible(rVclIter.iter);
+ enable_notify_events();
+}
+
+void SalInstanceTreeView::unselect(const weld::TreeIter& rIter)
+{
+ assert(m_xTreeView->IsUpdateMode() && "don't unselect when frozen");
+ disable_notify_events();
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ m_xTreeView->Select(rVclIter.iter, false);
+ enable_notify_events();
+}
+
+int SalInstanceTreeView::get_iter_depth(const weld::TreeIter& rIter) const
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return m_xTreeView->GetModel()->GetDepth(rVclIter.iter);
+}
+
+bool SalInstanceTreeView::iter_has_child(const weld::TreeIter& rIter) const
+{
+ SalInstanceTreeIter aTempCopy(static_cast<const SalInstanceTreeIter*>(&rIter));
+ return iter_children(aTempCopy);
+}
+
+bool SalInstanceTreeView::get_row_expanded(const weld::TreeIter& rIter) const
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return m_xTreeView->IsExpanded(rVclIter.iter);
+}
+
+bool SalInstanceTreeView::get_children_on_demand(const weld::TreeIter& rIter) const
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ if (m_aExpandingPlaceHolderParents.count(rVclIter.iter))
+ return true;
+ return GetPlaceHolderChild(rVclIter.iter) != nullptr;
+}
+
+void SalInstanceTreeView::set_children_on_demand(const weld::TreeIter& rIter,
+ bool bChildrenOnDemand)
+{
+ disable_notify_events();
+
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+
+ SvTreeListEntry* pPlaceHolder = GetPlaceHolderChild(rVclIter.iter);
+
+ if (bChildrenOnDemand && !pPlaceHolder)
+ {
+ pPlaceHolder = m_xTreeView->InsertEntry("<dummy>", rVclIter.iter, false, 0, nullptr);
+ SvViewDataEntry* pViewData = m_xTreeView->GetViewDataEntry(pPlaceHolder);
+ pViewData->SetSelectable(false);
+ }
+ else if (!bChildrenOnDemand && pPlaceHolder)
+ m_xTreeView->RemoveEntry(pPlaceHolder);
+
+ enable_notify_events();
+}
+
+void SalInstanceTreeView::expand_row(const weld::TreeIter& rIter)
+{
+ assert(m_xTreeView->IsUpdateMode() && "don't expand when frozen");
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ if (!m_xTreeView->IsExpanded(rVclIter.iter) && ExpandRow(rVclIter))
+ m_xTreeView->Expand(rVclIter.iter);
+}
+
+void SalInstanceTreeView::collapse_row(const weld::TreeIter& rIter)
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ if (m_xTreeView->IsExpanded(rVclIter.iter) && signal_collapsing(rIter))
+ m_xTreeView->Collapse(rVclIter.iter);
+}
+
+OUString SalInstanceTreeView::get_text(const weld::TreeIter& rIter, int col) const
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return get_text(rVclIter.iter, col);
+}
+
+void SalInstanceTreeView::set_text(const weld::TreeIter& rIter, const OUString& rText, int col)
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_text(rVclIter.iter, rText, col);
+}
+
+OUString SalInstanceTreeView::get_id(const weld::TreeIter& rIter) const
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ if (!rVclIter.iter)
+ return OUString();
+ const OUString* pStr = static_cast<const OUString*>(rVclIter.iter->GetUserData());
+ if (pStr)
+ return *pStr;
+ return OUString();
+}
+
+void SalInstanceTreeView::set_id(const weld::TreeIter& rIter, const OUString& rId)
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ set_id(rVclIter.iter, rId);
+}
+
+void SalInstanceTreeView::enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper,
+ sal_uInt8 eDNDConstants)
+{
+ m_xTreeView->SetDragHelper(rHelper, eDNDConstants);
+}
+
+void SalInstanceTreeView::set_selection_mode(SelectionMode eMode)
+{
+ m_xTreeView->SetSelectionMode(eMode);
+}
+
+void SalInstanceTreeView::all_foreach(const std::function<bool(weld::TreeIter&)>& func)
+{
+ UpdateGuardIfHidden aGuard(*m_xTreeView);
+
+ SalInstanceTreeIter aVclIter(m_xTreeView->First());
+ while (aVclIter.iter)
+ {
+ if (func(aVclIter))
+ return;
+ iter_next(aVclIter);
+ }
+}
+
+void SalInstanceTreeView::selected_foreach(const std::function<bool(weld::TreeIter&)>& func)
+{
+ UpdateGuardIfHidden aGuard(*m_xTreeView);
+
+ SalInstanceTreeIter aVclIter(m_xTreeView->FirstSelected());
+ while (aVclIter.iter)
+ {
+ if (func(aVclIter))
+ return;
+ aVclIter.iter = m_xTreeView->NextSelected(aVclIter.iter);
+ }
+}
+
+void SalInstanceTreeView::visible_foreach(const std::function<bool(weld::TreeIter&)>& func)
+{
+ UpdateGuardIfHidden aGuard(*m_xTreeView);
+
+ SalInstanceTreeIter aVclIter(m_xTreeView->GetFirstEntryInView());
+ while (aVclIter.iter)
+ {
+ if (func(aVclIter))
+ return;
+ aVclIter.iter = m_xTreeView->GetNextEntryInView(aVclIter.iter);
+ }
+}
+
+void SalInstanceTreeView::connect_visible_range_changed(const Link<weld::TreeView&, void>& rLink)
+{
+ weld::TreeView::connect_visible_range_changed(rLink);
+ m_xTreeView->SetScrolledHdl(LINK(this, SalInstanceTreeView, VisibleRangeChangedHdl));
+}
+
+void SalInstanceTreeView::remove_selection()
+{
+ disable_notify_events();
+ SvTreeListEntry* pSelected = m_xTreeView->FirstSelected();
+ while (pSelected)
+ {
+ SvTreeListEntry* pNextSelected = m_xTreeView->NextSelected(pSelected);
+ m_xTreeView->RemoveEntry(pSelected);
+ pSelected = pNextSelected;
+ }
+ enable_notify_events();
+}
+
+bool SalInstanceTreeView::is_selected(const weld::TreeIter& rIter) const
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return m_xTreeView->IsSelected(rVclIter.iter);
+}
+
+int SalInstanceTreeView::get_iter_index_in_parent(const weld::TreeIter& rIter) const
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ return SvTreeList::GetRelPos(rVclIter.iter);
+}
+
+int SalInstanceTreeView::iter_compare(const weld::TreeIter& a, const weld::TreeIter& b) const
+{
+ const SalInstanceTreeIter& rVclIterA = static_cast<const SalInstanceTreeIter&>(a);
+ const SalInstanceTreeIter& rVclIterB = static_cast<const SalInstanceTreeIter&>(b);
+ const SvTreeList* pModel = m_xTreeView->GetModel();
+ auto nAbsPosA = pModel->GetAbsPos(rVclIterA.iter);
+ auto nAbsPosB = pModel->GetAbsPos(rVclIterB.iter);
+ if (nAbsPosA < nAbsPosB)
+ return -1;
+ if (nAbsPosA > nAbsPosB)
+ return 1;
+ return 0;
+}
+
+void SalInstanceTreeView::move_subtree(weld::TreeIter& rNode, const weld::TreeIter* pNewParent,
+ int nIndexInNewParent)
+{
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rNode);
+ const SalInstanceTreeIter* pVclParentIter = static_cast<const SalInstanceTreeIter*>(pNewParent);
+ m_xTreeView->GetModel()->Move(rVclIter.iter, pVclParentIter ? pVclParentIter->iter : nullptr,
+ nIndexInNewParent);
+}
+
+int SalInstanceTreeView::count_selected_rows() const { return m_xTreeView->GetSelectionCount(); }
+
+int SalInstanceTreeView::get_height_rows(int nRows) const
+{
+ int nHeight = m_xTreeView->GetEntryHeight() * nRows;
+
+ sal_Int32 nLeftBorder(0), nTopBorder(0), nRightBorder(0), nBottomBorder(0);
+ m_xTreeView->GetBorder(nLeftBorder, nTopBorder, nRightBorder, nBottomBorder);
+ nHeight += nTopBorder + nBottomBorder;
+
+ return nHeight;
+}
+
+void SalInstanceTreeView::make_sorted()
+{
+ assert(m_xTreeView->IsUpdateMode() && "don't sort when frozen");
+ m_xTreeView->SetStyle(m_xTreeView->GetStyle() | WB_SORT);
+ m_xTreeView->GetModel()->SetCompareHdl(LINK(this, SalInstanceTreeView, CompareHdl));
+ set_sort_order(true);
+}
+
+void SalInstanceTreeView::set_sort_func(
+ const std::function<int(const weld::TreeIter&, const weld::TreeIter&)>& func)
+{
+ weld::TreeView::set_sort_func(func);
+ SvTreeList* pListModel = m_xTreeView->GetModel();
+ pListModel->Resort();
+}
+
+void SalInstanceTreeView::make_unsorted()
+{
+ m_xTreeView->SetStyle(m_xTreeView->GetStyle() & ~WB_SORT);
+}
+
+void SalInstanceTreeView::set_sort_order(bool bAscending)
+{
+ SvTreeList* pListModel = m_xTreeView->GetModel();
+ pListModel->SetSortMode(bAscending ? SvSortMode::Ascending : SvSortMode::Descending);
+ pListModel->Resort();
+}
+
+bool SalInstanceTreeView::get_sort_order() const
+{
+ return m_xTreeView->GetModel()->GetSortMode() == SvSortMode::Ascending;
+}
+
+void SalInstanceTreeView::set_sort_indicator(TriState eState, int col)
+{
+ assert(col >= 0 && "cannot sort on expander column");
+
+ LclHeaderTabListBox* pHeaderBox = dynamic_cast<LclHeaderTabListBox*>(m_xTreeView.get());
+ HeaderBar* pHeaderBar = pHeaderBox ? pHeaderBox->GetHeaderBar() : nullptr;
+ if (!pHeaderBar)
+ return;
+
+ sal_uInt16 nTextId = pHeaderBar->GetItemId(col);
+ HeaderBarItemBits nBits = pHeaderBar->GetItemBits(nTextId);
+ nBits &= ~HeaderBarItemBits::UPARROW;
+ nBits &= ~HeaderBarItemBits::DOWNARROW;
+ if (eState != TRISTATE_INDET)
+ {
+ if (eState == TRISTATE_TRUE)
+ nBits |= HeaderBarItemBits::DOWNARROW;
+ else
+ nBits |= HeaderBarItemBits::UPARROW;
+ }
+ pHeaderBar->SetItemBits(nTextId, nBits);
+}
+
+TriState SalInstanceTreeView::get_sort_indicator(int col) const
+{
+ assert(col >= 0 && "cannot sort on expander column");
+
+ LclHeaderTabListBox* pHeaderBox = dynamic_cast<LclHeaderTabListBox*>(m_xTreeView.get());
+ if (HeaderBar* pHeaderBar = pHeaderBox ? pHeaderBox->GetHeaderBar() : nullptr)
+ {
+ sal_uInt16 nTextId = pHeaderBar->GetItemId(col);
+ HeaderBarItemBits nBits = pHeaderBar->GetItemBits(nTextId);
+ if (nBits & HeaderBarItemBits::DOWNARROW)
+ return TRISTATE_TRUE;
+ if (nBits & HeaderBarItemBits::UPARROW)
+ return TRISTATE_FALSE;
+ }
+
+ return TRISTATE_INDET;
+}
+
+int SalInstanceTreeView::get_sort_column() const { return m_nSortColumn; }
+
+void SalInstanceTreeView::set_sort_column(int nColumn)
+{
+ if (nColumn == -1)
+ {
+ make_unsorted();
+ m_nSortColumn = -1;
+ return;
+ }
+
+ if (nColumn != m_nSortColumn)
+ {
+ m_nSortColumn = nColumn;
+ m_xTreeView->GetModel()->Resort();
+ }
+}
+
+SvTabListBox& SalInstanceTreeView::getTreeView() { return *m_xTreeView; }
+
+bool SalInstanceTreeView::get_dest_row_at_pos(const Point& rPos, weld::TreeIter* pResult,
+ bool bDnDMode)
+{
+ LclTabListBox* pTreeView
+ = !bDnDMode ? dynamic_cast<LclTabListBox*>(m_xTreeView.get()) : nullptr;
+ SvTreeListEntry* pTarget
+ = pTreeView ? pTreeView->GetTargetAtPoint(rPos, false) : m_xTreeView->GetDropTarget(rPos);
+
+ if (pTarget && pResult)
+ {
+ SalInstanceTreeIter& rSalIter = static_cast<SalInstanceTreeIter&>(*pResult);
+ rSalIter.iter = pTarget;
+ }
+
+ return pTarget != nullptr;
+}
+
+void SalInstanceTreeView::unset_drag_dest_row() { m_xTreeView->UnsetDropTarget(); }
+
+tools::Rectangle SalInstanceTreeView::get_row_area(const weld::TreeIter& rIter) const
+{
+ return m_xTreeView->GetBoundingRect(static_cast<const SalInstanceTreeIter&>(rIter).iter);
+}
+
+weld::TreeView* SalInstanceTreeView::get_drag_source() const { return g_DragSource; }
+
+int SalInstanceTreeView::vadjustment_get_value() const
+{
+ int nValue = -1;
+ const SvTreeListEntry* pEntry = m_xTreeView->GetFirstEntryInView();
+ if (pEntry)
+ nValue = m_xTreeView->GetAbsPos(pEntry);
+ return nValue;
+}
+
+void SalInstanceTreeView::vadjustment_set_value(int nValue)
+{
+ if (nValue == -1)
+ return;
+ bool bUpdate = m_xTreeView->IsUpdateMode();
+ if (bUpdate)
+ m_xTreeView->SetUpdateMode(false);
+ m_xTreeView->ScrollToAbsPos(nValue);
+ if (bUpdate)
+ m_xTreeView->SetUpdateMode(true);
+}
+
+void SalInstanceTreeView::set_show_expanders(bool bShow)
+{
+ m_xTreeView->set_property("show-expanders", OUString::boolean(bShow));
+}
+
+bool SalInstanceTreeView::changed_by_hover() const { return m_xTreeView->IsSelectDueToHover(); }
+
+SalInstanceTreeView::~SalInstanceTreeView()
+{
+ LclHeaderTabListBox* pHeaderBox = dynamic_cast<LclHeaderTabListBox*>(m_xTreeView.get());
+ if (pHeaderBox)
+ {
+ if (HeaderBar* pHeaderBar = pHeaderBox->GetHeaderBar())
+ {
+ pHeaderBar->SetSelectHdl(Link<HeaderBar*, void>());
+ pHeaderBar->SetEndDragHdl(Link<HeaderBar*, void>());
+ }
+ }
+ else
+ {
+ static_cast<LclTabListBox&>(*m_xTreeView).SetEndDragHdl(Link<SvTreeListBox*, void>());
+ static_cast<LclTabListBox&>(*m_xTreeView).SetStartDragHdl(Link<SvTreeListBox*, bool>());
+ static_cast<LclTabListBox&>(*m_xTreeView).SetModelChangedHdl(Link<SvTreeListBox*, void>());
+ }
+ m_xTreeView->SetPopupMenuHdl(Link<const CommandEvent&, bool>());
+ m_xTreeView->SetExpandingHdl(Link<SvTreeListBox*, bool>());
+ m_xTreeView->SetDoubleClickHdl(Link<SvTreeListBox*, bool>());
+ m_xTreeView->SetSelectHdl(Link<SvTreeListBox*, void>());
+ m_xTreeView->SetDeselectHdl(Link<SvTreeListBox*, void>());
+ m_xTreeView->SetScrolledHdl(Link<SvTreeListBox*, void>());
+ m_xTreeView->SetTooltipHdl(Link<const HelpEvent&, bool>());
+ m_xTreeView->SetCustomRenderHdl(Link<svtree_render_args, void>());
+ m_xTreeView->SetCustomMeasureHdl(Link<svtree_measure_args, Size>());
+}
+
+IMPL_LINK(SalInstanceTreeView, TooltipHdl, const HelpEvent&, rHEvt, bool)
+{
+ if (notify_events_disabled())
+ return false;
+ Point aPos(m_xTreeView->ScreenToOutputPixel(rHEvt.GetMousePosPixel()));
+ SvTreeListEntry* pEntry = m_xTreeView->GetEntry(aPos);
+ if (pEntry)
+ {
+ SalInstanceTreeIter aIter(pEntry);
+ OUString aTooltip = signal_query_tooltip(aIter);
+ if (aTooltip.isEmpty())
+ return false;
+ Size aSize(m_xTreeView->GetOutputSizePixel().Width(), m_xTreeView->GetEntryHeight());
+ tools::Rectangle aScreenRect(
+ m_xTreeView->OutputToScreenPixel(m_xTreeView->GetEntryPosition(pEntry)), aSize);
+ Help::ShowQuickHelp(m_xTreeView, aScreenRect, aTooltip);
+ }
+ return true;
+}
+
+IMPL_LINK(SalInstanceTreeView, CustomRenderHdl, svtree_render_args, payload, void)
+{
+ vcl::RenderContext& rRenderDevice = std::get<0>(payload);
+ const tools::Rectangle& rRect = std::get<1>(payload);
+ const SvTreeListEntry& rEntry = std::get<2>(payload);
+ const OUString* pId = static_cast<const OUString*>(rEntry.GetUserData());
+ if (!pId)
+ return;
+ signal_custom_render(rRenderDevice, rRect, m_xTreeView->IsSelected(&rEntry), *pId);
+}
+
+IMPL_LINK(SalInstanceTreeView, CustomMeasureHdl, svtree_measure_args, payload, Size)
+{
+ vcl::RenderContext& rRenderDevice = payload.first;
+ const SvTreeListEntry& rEntry = payload.second;
+ const OUString* pId = static_cast<const OUString*>(rEntry.GetUserData());
+ if (!pId)
+ return Size();
+ return signal_custom_get_size(rRenderDevice, *pId);
+}
+
+IMPL_LINK(SalInstanceTreeView, CompareHdl, const SvSortData&, rSortData, sal_Int32)
+{
+ const SvTreeListEntry* pLHS = rSortData.pLeft;
+ const SvTreeListEntry* pRHS = rSortData.pRight;
+ assert(pLHS && pRHS);
+
+ if (m_aCustomSort)
+ return m_aCustomSort(SalInstanceTreeIter(const_cast<SvTreeListEntry*>(pLHS)),
+ SalInstanceTreeIter(const_cast<SvTreeListEntry*>(pRHS)));
+
+ const SvLBoxString* pLeftTextItem;
+ const SvLBoxString* pRightTextItem;
+
+ if (m_nSortColumn != -1)
+ {
+ size_t col = to_internal_model(m_nSortColumn);
+
+ if (col < pLHS->ItemCount())
+ {
+ const SvLBoxString& rLeftTextItem
+ = static_cast<const SvLBoxString&>(pLHS->GetItem(col));
+ pLeftTextItem = &rLeftTextItem;
+ }
+ else
+ pLeftTextItem = nullptr;
+ if (col < pRHS->ItemCount())
+ {
+ const SvLBoxString& rRightTextItem
+ = static_cast<const SvLBoxString&>(pRHS->GetItem(col));
+ pRightTextItem = &rRightTextItem;
+ }
+ else
+ pRightTextItem = nullptr;
+ }
+ else
+ {
+ pLeftTextItem
+ = static_cast<const SvLBoxString*>(pLHS->GetFirstItem(SvLBoxItemType::String));
+ pRightTextItem
+ = static_cast<const SvLBoxString*>(pRHS->GetFirstItem(SvLBoxItemType::String));
+ }
+
+ return m_xTreeView->DefaultCompare(pLeftTextItem, pRightTextItem);
+}
+
+IMPL_LINK_NOARG(SalInstanceTreeView, VisibleRangeChangedHdl, SvTreeListBox*, void)
+{
+ if (notify_events_disabled())
+ return;
+ signal_visible_range_changed();
+}
+
+IMPL_LINK_NOARG(SalInstanceTreeView, ModelChangedHdl, SvTreeListBox*, void)
+{
+ if (notify_events_disabled())
+ return;
+ signal_model_changed();
+}
+
+IMPL_LINK_NOARG(SalInstanceTreeView, StartDragHdl, SvTreeListBox*, bool)
+{
+ bool bUnsetDragIcon(false); // ignored for vcl
+ if (m_aDragBeginHdl.Call(bUnsetDragIcon))
+ return true;
+ g_DragSource = this;
+ return false;
+}
+
+IMPL_STATIC_LINK_NOARG(SalInstanceTreeView, FinishDragHdl, SvTreeListBox*, void)
+{
+ g_DragSource = nullptr;
+}
+
+IMPL_LINK(SalInstanceTreeView, ToggleHdl, SvLBoxButtonData*, pData, void)
+{
+ SvTreeListEntry* pEntry = pData->GetActEntry();
+ SvLBoxButton* pBox = pData->GetActBox();
+
+ // tdf#122874 Select the row, calling SelectHdl, before handling
+ // the toggle
+ if (!m_xTreeView->IsSelected(pEntry))
+ {
+ m_xTreeView->SelectAll(false);
+ m_xTreeView->Select(pEntry, true);
+ }
+
+ // additionally set the cursor into the row the toggled element is in
+ m_xTreeView->pImpl->m_pCursor = pEntry;
+
+ for (int i = 0, nCount = pEntry->ItemCount(); i < nCount; ++i)
+ {
+ SvLBoxItem& rItem = pEntry->GetItem(i);
+ if (&rItem == pBox)
+ {
+ int nCol = to_external_model(i);
+ signal_toggled(iter_col(SalInstanceTreeIter(pEntry), nCol));
+ break;
+ }
+ }
+}
+
+IMPL_LINK_NOARG(SalInstanceTreeView, SelectHdl, SvTreeListBox*, void)
+{
+ if (notify_events_disabled())
+ return;
+ signal_changed();
+}
+
+IMPL_LINK_NOARG(SalInstanceTreeView, DeSelectHdl, SvTreeListBox*, void)
+{
+ if (notify_events_disabled())
+ return;
+ if (m_xTreeView->GetSelectionMode() == SelectionMode::Single
+ && !m_xTreeView->GetHoverSelection())
+ return;
+ signal_changed();
+}
+
+IMPL_LINK_NOARG(SalInstanceTreeView, DoubleClickHdl, SvTreeListBox*, bool)
+{
+ if (notify_events_disabled())
+ return false;
+ return !signal_row_activated();
+}
+
+IMPL_LINK(SalInstanceTreeView, EndDragHdl, HeaderBar*, pHeaderBar, void)
+{
+ std::vector<tools::Long> aTabPositions{ 0 };
+ for (int i = 0; i < pHeaderBar->GetItemCount() - 1; ++i)
+ aTabPositions.push_back(aTabPositions[i]
+ + pHeaderBar->GetItemSize(pHeaderBar->GetItemId(i)));
+ m_xTreeView->SetTabs(aTabPositions.size(), aTabPositions.data(), MapUnit::MapPixel);
+}
+
+IMPL_LINK(SalInstanceTreeView, HeaderBarClickedHdl, HeaderBar*, pHeaderBar, void)
+{
+ sal_uInt16 nId = pHeaderBar->GetCurItemId();
+ if (!(pHeaderBar->GetItemBits(nId) & HeaderBarItemBits::CLICKABLE))
+ return;
+ signal_column_clicked(pHeaderBar->GetItemPos(nId));
+}
+
+IMPL_LINK_NOARG(SalInstanceTreeView, ExpandingHdl, SvTreeListBox*, bool)
+{
+ SvTreeListEntry* pEntry = m_xTreeView->GetHdlEntry();
+ SalInstanceTreeIter aIter(pEntry);
+
+ if (m_xTreeView->IsExpanded(pEntry))
+ {
+ //collapsing;
+ return signal_collapsing(aIter);
+ }
+
+ // expanding
+ return ExpandRow(aIter);
+}
+
+bool SalInstanceTreeView::ExpandRow(const SalInstanceTreeIter& rIter)
+{
+ SvTreeListEntry* pEntry = rIter.iter;
+ // if there's a preexisting placeholder child, required to make this
+ // potentially expandable in the first place, now we remove it
+ SvTreeListEntry* pPlaceHolder = GetPlaceHolderChild(pEntry);
+ if (pPlaceHolder)
+ {
+ m_aExpandingPlaceHolderParents.insert(pEntry);
+ m_xTreeView->RemoveEntry(pPlaceHolder);
+ }
+
+ bool bRet = signal_expanding(rIter);
+
+ if (pPlaceHolder)
+ {
+ //expand disallowed, restore placeholder
+ if (!bRet)
+ {
+ pPlaceHolder = m_xTreeView->InsertEntry("<dummy>", pEntry, false, 0, nullptr);
+ SvViewDataEntry* pViewData = m_xTreeView->GetViewDataEntry(pPlaceHolder);
+ pViewData->SetSelectable(false);
+ }
+ m_aExpandingPlaceHolderParents.erase(pEntry);
+ }
+
+ return bRet;
+}
+
+IMPL_LINK(SalInstanceTreeView, PopupMenuHdl, const CommandEvent&, rEvent, bool)
+{
+ return m_aPopupMenuHdl.Call(rEvent);
+}
+
+IMPL_LINK(SalInstanceTreeView, EditingEntryHdl, SvTreeListEntry*, pEntry, bool)
+{
+ return signal_editing_started(SalInstanceTreeIter(pEntry));
+}
+
+IMPL_LINK(SalInstanceTreeView, EditedEntryHdl, IterString, rIterString, bool)
+{
+ return signal_editing_done(
+ iter_string(SalInstanceTreeIter(rIterString.first), rIterString.second));
+}
+
+SalInstanceIconView::SalInstanceIconView(::IconView* pIconView, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceWidget(pIconView, pBuilder, bTakeOwnership)
+ , m_xIconView(pIconView)
+{
+ m_xIconView->SetSelectHdl(LINK(this, SalInstanceIconView, SelectHdl));
+ m_xIconView->SetDeselectHdl(LINK(this, SalInstanceIconView, DeSelectHdl));
+ m_xIconView->SetDoubleClickHdl(LINK(this, SalInstanceIconView, DoubleClickHdl));
+ m_xIconView->SetPopupMenuHdl(LINK(this, SalInstanceIconView, CommandHdl));
+
+ m_xIconView->SetEntryAccessibleDescriptionHdl(
+ LINK(this, SalInstanceIconView, EntryAccessibleDescriptionHdl));
+ m_xIconView->SetAccessible(m_xIconView->CreateAccessible());
+}
+
+int SalInstanceIconView::get_item_width() const { return m_xIconView->GetEntryWidth(); }
+void SalInstanceIconView::set_item_width(int width)
+{
+ m_xIconView->SetEntryWidth(width);
+ m_xIconView->Resize();
+}
+
+void SalInstanceIconView::freeze()
+{
+ bool bIsFirstFreeze = IsFirstFreeze();
+ SalInstanceWidget::freeze();
+ if (bIsFirstFreeze)
+ m_xIconView->SetUpdateMode(false);
+}
+
+void SalInstanceIconView::thaw()
+{
+ bool bIsLastThaw = IsLastThaw();
+ if (bIsLastThaw)
+ m_xIconView->SetUpdateMode(true);
+ SalInstanceWidget::thaw();
+}
+
+void SalInstanceIconView::insert(int pos, const OUString* pStr, const OUString* pId,
+ const OUString* pIconName, weld::TreeIter* pRet)
+{
+ disable_notify_events();
+ auto nInsertPos = pos == -1 ? TREELIST_APPEND : pos;
+ void* pUserData;
+ if (pId)
+ {
+ m_aUserData.emplace_back(std::make_unique<OUString>(*pId));
+ pUserData = m_aUserData.back().get();
+ }
+ else
+ pUserData = nullptr;
+
+ SvTreeListEntry* pEntry = new SvTreeListEntry;
+ if (pIconName)
+ {
+ Image aImage(createImage(*pIconName));
+ pEntry->AddItem(std::make_unique<SvLBoxContextBmp>(aImage, aImage, false));
+ }
+ else
+ {
+ Image aDummy;
+ pEntry->AddItem(std::make_unique<SvLBoxContextBmp>(aDummy, aDummy, false));
+ }
+ if (pStr)
+ pEntry->AddItem(std::make_unique<SvLBoxString>(*pStr));
+ pEntry->SetUserData(pUserData);
+ m_xIconView->Insert(pEntry, nullptr, nInsertPos);
+
+ if (pRet)
+ {
+ SalInstanceTreeIter* pVclRetIter = static_cast<SalInstanceTreeIter*>(pRet);
+ pVclRetIter->iter = pEntry;
+ }
+
+ enable_notify_events();
+}
+
+void SalInstanceIconView::insert(int pos, const OUString* pStr, const OUString* pId,
+ const VirtualDevice* pIcon, weld::TreeIter* pRet)
+{
+ disable_notify_events();
+ auto nInsertPos = pos == -1 ? TREELIST_APPEND : pos;
+ void* pUserData;
+ if (pId)
+ {
+ m_aUserData.emplace_back(std::make_unique<OUString>(*pId));
+ pUserData = m_aUserData.back().get();
+ }
+ else
+ pUserData = nullptr;
+
+ SvTreeListEntry* pEntry = new SvTreeListEntry;
+ if (pIcon)
+ {
+ const Point aNull(0, 0);
+ const Size aSize = pIcon->GetOutputSize();
+ Image aImage(pIcon->GetBitmapEx(aNull, aSize));
+ pEntry->AddItem(std::make_unique<SvLBoxContextBmp>(aImage, aImage, false));
+ }
+ else
+ {
+ Image aDummy;
+ pEntry->AddItem(std::make_unique<SvLBoxContextBmp>(aDummy, aDummy, false));
+ }
+ if (pStr)
+ pEntry->AddItem(std::make_unique<SvLBoxString>(*pStr));
+ pEntry->SetUserData(pUserData);
+ m_xIconView->Insert(pEntry, nullptr, nInsertPos);
+
+ if (pRet)
+ {
+ SalInstanceTreeIter* pVclRetIter = static_cast<SalInstanceTreeIter*>(pRet);
+ pVclRetIter->iter = pEntry;
+ }
+
+ enable_notify_events();
+}
+
+void SalInstanceIconView::insert_separator(int pos, const OUString* /* pId */)
+{
+ const auto nInsertPos = pos == -1 ? TREELIST_APPEND : pos;
+ const OUString sSep(VclResId(STR_SEPARATOR));
+ SvTreeListEntry* pEntry = new SvTreeListEntry;
+ pEntry->SetFlags(pEntry->GetFlags() | SvTLEntryFlags::IS_SEPARATOR);
+ const Image aDummy;
+ pEntry->AddItem(std::make_unique<SvLBoxContextBmp>(aDummy, aDummy, false));
+ pEntry->AddItem(std::make_unique<SvLBoxString>(sSep));
+ pEntry->SetUserData(nullptr);
+ m_xIconView->Insert(pEntry, nullptr, nInsertPos);
+ SvViewDataEntry* pViewData = m_xIconView->GetViewDataEntry(pEntry);
+ pViewData->SetSelectable(false);
+}
+
+IMPL_LINK(SalInstanceIconView, TooltipHdl, const HelpEvent&, rHEvt, bool)
+{
+ if (notify_events_disabled())
+ return false;
+ Point aPos(m_xIconView->ScreenToOutputPixel(rHEvt.GetMousePosPixel()));
+ SvTreeListEntry* pEntry = m_xIconView->GetEntry(aPos);
+ if (pEntry)
+ {
+ SalInstanceTreeIter aIter(pEntry);
+ OUString aTooltip = signal_query_tooltip(aIter);
+ if (aTooltip.isEmpty())
+ return false;
+ Size aSize(m_xIconView->GetOutputSizePixel().Width(), m_xIconView->GetEntryHeight());
+ tools::Rectangle aScreenRect(
+ m_xIconView->OutputToScreenPixel(m_xIconView->GetEntryPosition(pEntry)), aSize);
+ Help::ShowQuickHelp(m_xIconView, aScreenRect, aTooltip);
+ }
+ return true;
+}
+
+IMPL_LINK(SalInstanceIconView, EntryAccessibleDescriptionHdl, SvTreeListEntry*, pEntry, OUString)
+{
+ OUString s = SvTreeListBox::SearchEntryTextWithHeadTitle(pEntry);
+ if (s.isEmpty())
+ s = signal_query_tooltip(SalInstanceTreeIter(pEntry));
+ return s;
+}
+
+void SalInstanceIconView::connect_query_tooltip(const Link<const weld::TreeIter&, OUString>& rLink)
+{
+ weld::IconView::connect_query_tooltip(rLink);
+ m_xIconView->SetTooltipHdl(LINK(this, SalInstanceIconView, TooltipHdl));
+}
+
+OUString SalInstanceIconView::get_selected_id() const
+{
+ assert(m_xIconView->IsUpdateMode() && "don't request selection when frozen");
+ if (SvTreeListEntry* pEntry = m_xIconView->FirstSelected())
+ {
+ if (const OUString* pStr = static_cast<const OUString*>(pEntry->GetUserData()))
+ return *pStr;
+ }
+ return OUString();
+}
+
+OUString SalInstanceIconView::get_selected_text() const
+{
+ assert(m_xIconView->IsUpdateMode() && "don't request selection when frozen");
+ if (SvTreeListEntry* pEntry = m_xIconView->FirstSelected())
+ return m_xIconView->GetEntryText(pEntry);
+ return OUString();
+}
+
+int SalInstanceIconView::count_selected_items() const { return m_xIconView->GetSelectionCount(); }
+
+void SalInstanceIconView::select(int pos)
+{
+ assert(m_xIconView->IsUpdateMode()
+ && "don't select when frozen, select after thaw. Note selection doesn't survive a "
+ "freeze");
+ disable_notify_events();
+ if (pos == -1 || (pos == 0 && n_children() == 0))
+ m_xIconView->SelectAll(false);
+ else
+ {
+ SvTreeListEntry* pEntry = m_xIconView->GetEntry(nullptr, pos);
+ m_xIconView->Select(pEntry, true);
+ m_xIconView->MakeVisible(pEntry);
+ }
+ enable_notify_events();
+}
+
+void SalInstanceIconView::unselect(int pos)
+{
+ assert(m_xIconView->IsUpdateMode()
+ && "don't select when frozen, select after thaw. Note selection doesn't survive a "
+ "freeze");
+ disable_notify_events();
+ if (pos == -1)
+ m_xIconView->SelectAll(true);
+ else
+ {
+ SvTreeListEntry* pEntry = m_xIconView->GetEntry(nullptr, pos);
+ m_xIconView->Select(pEntry, false);
+ }
+ enable_notify_events();
+}
+
+int SalInstanceIconView::n_children() const
+{
+ return m_xIconView->GetModel()->GetChildList(nullptr).size();
+}
+
+std::unique_ptr<weld::TreeIter>
+SalInstanceIconView::make_iterator(const weld::TreeIter* pOrig) const
+{
+ return std::unique_ptr<weld::TreeIter>(
+ new SalInstanceTreeIter(static_cast<const SalInstanceTreeIter*>(pOrig)));
+}
+
+bool SalInstanceIconView::get_selected(weld::TreeIter* pIter) const
+{
+ SvTreeListEntry* pEntry = m_xIconView->FirstSelected();
+ auto pVclIter = static_cast<SalInstanceTreeIter*>(pIter);
+ if (pVclIter)
+ pVclIter->iter = pEntry;
+ return pEntry != nullptr;
+}
+
+bool SalInstanceIconView::get_cursor(weld::TreeIter* pIter) const
+{
+ SvTreeListEntry* pEntry = m_xIconView->GetCurEntry();
+ auto pVclIter = static_cast<SalInstanceTreeIter*>(pIter);
+ if (pVclIter)
+ pVclIter->iter = pEntry;
+ return pEntry != nullptr;
+}
+
+void SalInstanceIconView::set_cursor(const weld::TreeIter& rIter)
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ disable_notify_events();
+ m_xIconView->SetCurEntry(rVclIter.iter);
+ enable_notify_events();
+}
+
+bool SalInstanceIconView::get_iter_first(weld::TreeIter& rIter) const
+{
+ SalInstanceTreeIter& rVclIter = static_cast<SalInstanceTreeIter&>(rIter);
+ rVclIter.iter = m_xIconView->GetEntry(0);
+ return rVclIter.iter != nullptr;
+}
+
+void SalInstanceIconView::scroll_to_item(const weld::TreeIter& rIter)
+{
+ assert(m_xIconView->IsUpdateMode()
+ && "don't select when frozen, select after thaw. Note selection doesn't survive a "
+ "freeze");
+ disable_notify_events();
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ m_xIconView->MakeVisible(rVclIter.iter);
+ enable_notify_events();
+}
+
+void SalInstanceIconView::selected_foreach(const std::function<bool(weld::TreeIter&)>& func)
+{
+ SalInstanceTreeIter aVclIter(m_xIconView->FirstSelected());
+ while (aVclIter.iter)
+ {
+ if (func(aVclIter))
+ return;
+ aVclIter.iter = m_xIconView->NextSelected(aVclIter.iter);
+ }
+}
+
+OUString SalInstanceIconView::get_id(const weld::TreeIter& rIter) const
+{
+ const SalInstanceTreeIter& rVclIter = static_cast<const SalInstanceTreeIter&>(rIter);
+ const OUString* pStr = static_cast<const OUString*>(rVclIter.iter->GetUserData());
+ if (pStr)
+ return *pStr;
+ return OUString();
+}
+
+void SalInstanceIconView::clear()
+{
+ disable_notify_events();
+ m_xIconView->Clear();
+ m_aUserData.clear();
+ enable_notify_events();
+}
+
+SalInstanceIconView::~SalInstanceIconView()
+{
+ m_xIconView->SetDoubleClickHdl(Link<SvTreeListBox*, bool>());
+ m_xIconView->SetSelectHdl(Link<SvTreeListBox*, void>());
+ m_xIconView->SetDeselectHdl(Link<SvTreeListBox*, void>());
+}
+
+IMPL_LINK_NOARG(SalInstanceIconView, SelectHdl, SvTreeListBox*, void)
+{
+ if (notify_events_disabled())
+ return;
+ signal_selection_changed();
+}
+
+IMPL_LINK_NOARG(SalInstanceIconView, DeSelectHdl, SvTreeListBox*, void)
+{
+ if (notify_events_disabled())
+ return;
+ if (m_xIconView->GetSelectionMode() == SelectionMode::Single)
+ return;
+ signal_selection_changed();
+}
+
+IMPL_LINK_NOARG(SalInstanceIconView, DoubleClickHdl, SvTreeListBox*, bool)
+{
+ if (notify_events_disabled())
+ return false;
+ return !signal_item_activated();
+}
+
+IMPL_LINK(SalInstanceIconView, CommandHdl, const CommandEvent&, rEvent, bool)
+{
+ return m_aCommandHdl.Call(rEvent);
+}
+
+double SalInstanceSpinButton::toField(sal_Int64 nValue) const
+{
+ return static_cast<double>(nValue) / Power10(get_digits());
+}
+
+sal_Int64 SalInstanceSpinButton::fromField(double fValue) const
+{
+ auto const x = fValue * Power10(get_digits());
+ return x == double(std::numeric_limits<sal_Int64>::max())
+ ? std::numeric_limits<sal_Int64>::max()
+ : sal_Int64(std::round(x));
+}
+
+SalInstanceSpinButton::SalInstanceSpinButton(FormattedField* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceEntry(pButton, pBuilder, bTakeOwnership)
+ , m_xButton(pButton)
+ , m_rFormatter(m_xButton->GetFormatter())
+{
+ m_rFormatter.SetThousandsSep(false); //off by default, MetricSpinButton enables it
+ m_xButton->SetUpHdl(LINK(this, SalInstanceSpinButton, UpDownHdl));
+ m_xButton->SetDownHdl(LINK(this, SalInstanceSpinButton, UpDownHdl));
+ m_xButton->SetLoseFocusHdl(LINK(this, SalInstanceSpinButton, LoseFocusHdl));
+ m_rFormatter.SetOutputHdl(LINK(this, SalInstanceSpinButton, OutputHdl));
+ m_rFormatter.SetInputHdl(LINK(this, SalInstanceSpinButton, InputHdl));
+ if (Edit* pEdit = m_xButton->GetSubEdit())
+ pEdit->SetActivateHdl(LINK(this, SalInstanceSpinButton, ActivateHdl));
+ else
+ m_xButton->SetActivateHdl(LINK(this, SalInstanceSpinButton, ActivateHdl));
+}
+
+sal_Int64 SalInstanceSpinButton::get_value() const { return fromField(m_rFormatter.GetValue()); }
+
+void SalInstanceSpinButton::set_value(sal_Int64 value) { m_rFormatter.SetValue(toField(value)); }
+
+void SalInstanceSpinButton::set_range(sal_Int64 min, sal_Int64 max)
+{
+ m_rFormatter.SetMinValue(toField(min));
+ m_rFormatter.SetMaxValue(toField(max));
+}
+
+void SalInstanceSpinButton::get_range(sal_Int64& min, sal_Int64& max) const
+{
+ min = fromField(m_rFormatter.GetMinValue());
+ max = fromField(m_rFormatter.GetMaxValue());
+}
+
+void SalInstanceSpinButton::set_increments(int step, int /*page*/)
+{
+ m_rFormatter.SetSpinSize(toField(step));
+}
+
+void SalInstanceSpinButton::get_increments(int& step, int& page) const
+{
+ step = fromField(m_rFormatter.GetSpinSize());
+ page = fromField(m_rFormatter.GetSpinSize());
+}
+
+void SalInstanceSpinButton::set_digits(unsigned int digits)
+{
+ m_rFormatter.SetDecimalDigits(digits);
+}
+
+// SpinButton may be comprised of multiple subwidgets, consider the lot as
+// one thing for focus
+bool SalInstanceSpinButton::has_focus() const { return m_xWidget->HasChildPathFocus(); }
+
+//off by default for direct SpinButtons, MetricSpinButton enables it
+void SalInstanceSpinButton::SetUseThousandSep() { m_rFormatter.SetThousandsSep(true); }
+
+unsigned int SalInstanceSpinButton::get_digits() const { return m_rFormatter.GetDecimalDigits(); }
+
+SalInstanceSpinButton::~SalInstanceSpinButton()
+{
+ if (Edit* pEdit = m_xButton->GetSubEdit())
+ pEdit->SetActivateHdl(Link<Edit&, bool>());
+ else
+ m_xButton->SetActivateHdl(Link<Edit&, bool>());
+ m_rFormatter.SetInputHdl(Link<sal_Int64*, TriState>());
+ m_rFormatter.SetOutputHdl(Link<LinkParamNone*, bool>());
+ m_xButton->SetLoseFocusHdl(Link<Control&, void>());
+ m_xButton->SetDownHdl(Link<SpinField&, void>());
+ m_xButton->SetUpHdl(Link<SpinField&, void>());
+}
+
+IMPL_LINK_NOARG(SalInstanceSpinButton, ActivateHdl, Edit&, bool)
+{
+ // tdf#122348 return pressed to end dialog
+ signal_value_changed();
+ return m_aActivateHdl.Call(*this);
+}
+
+IMPL_LINK_NOARG(SalInstanceSpinButton, UpDownHdl, SpinField&, void) { signal_value_changed(); }
+
+IMPL_LINK_NOARG(SalInstanceSpinButton, LoseFocusHdl, Control&, void) { signal_value_changed(); }
+
+IMPL_LINK_NOARG(SalInstanceSpinButton, OutputHdl, LinkParamNone*, bool) { return signal_output(); }
+
+IMPL_LINK(SalInstanceSpinButton, InputHdl, sal_Int64*, pResult, TriState)
+{
+ int nResult;
+ TriState eRet = signal_input(&nResult);
+ if (eRet == TRISTATE_TRUE)
+ *pResult = nResult;
+ return eRet;
+}
+
+namespace
+{
+class SalInstanceFormattedSpinButton : public SalInstanceEntry,
+ public virtual weld::FormattedSpinButton
+{
+private:
+ VclPtr<FormattedField> m_xButton;
+ weld::EntryFormatter* m_pFormatter;
+ Link<weld::Widget&, void> m_aLoseFocusHdl;
+
+ DECL_LINK(UpDownHdl, SpinField&, void);
+ DECL_LINK(LoseFocusHdl, Control&, void);
+
+public:
+ SalInstanceFormattedSpinButton(FormattedField* pButton, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceEntry(pButton, pBuilder, bTakeOwnership)
+ , m_xButton(pButton)
+ , m_pFormatter(nullptr)
+ {
+ m_xButton->SetUpHdl(LINK(this, SalInstanceFormattedSpinButton, UpDownHdl));
+ m_xButton->SetDownHdl(LINK(this, SalInstanceFormattedSpinButton, UpDownHdl));
+ m_xButton->SetLoseFocusHdl(LINK(this, SalInstanceFormattedSpinButton, LoseFocusHdl));
+ }
+
+ virtual void set_text(const OUString& rText) override
+ {
+ disable_notify_events();
+ m_xButton->SpinField::SetText(rText);
+ enable_notify_events();
+ }
+
+ virtual void connect_changed(const Link<weld::Entry&, void>& rLink) override
+ {
+ if (!m_pFormatter) // once a formatter is set, it takes over "changed"
+ {
+ SalInstanceEntry::connect_changed(rLink);
+ return;
+ }
+ m_pFormatter->connect_changed(rLink);
+ }
+
+ virtual void connect_focus_out(const Link<weld::Widget&, void>& rLink) override
+ {
+ if (!m_pFormatter) // once a formatter is set, it takes over "focus-out"
+ {
+ m_aLoseFocusHdl = rLink;
+ return;
+ }
+ m_pFormatter->connect_focus_out(rLink);
+ }
+
+ virtual void SetFormatter(weld::EntryFormatter* pFormatter) override
+ {
+ m_pFormatter = pFormatter;
+ m_xButton->SetFormatter(pFormatter);
+ }
+
+ virtual void sync_value_from_formatter() override
+ {
+ // no-op for gen
+ }
+
+ virtual void sync_range_from_formatter() override
+ {
+ // no-op for gen
+ }
+
+ virtual void sync_increments_from_formatter() override
+ {
+ // no-op for gen
+ }
+
+ virtual Formatter& GetFormatter() override { return m_xButton->GetFormatter(); }
+
+ virtual ~SalInstanceFormattedSpinButton() override
+ {
+ m_xButton->SetLoseFocusHdl(Link<Control&, void>());
+ m_xButton->SetDownHdl(Link<SpinField&, void>());
+ m_xButton->SetUpHdl(Link<SpinField&, void>());
+ }
+};
+
+IMPL_LINK_NOARG(SalInstanceFormattedSpinButton, UpDownHdl, SpinField&, void)
+{
+ signal_value_changed();
+}
+
+IMPL_LINK_NOARG(SalInstanceFormattedSpinButton, LoseFocusHdl, Control&, void)
+{
+ if (!m_pFormatter)
+ signal_value_changed();
+ m_aLoseFocusHdl.Call(*this);
+}
+}
+
+SalInstanceLabel::SalInstanceLabel(Control* pLabel, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceWidget(pLabel, pBuilder, bTakeOwnership)
+ , m_xLabel(pLabel)
+{
+}
+
+void SalInstanceLabel::set_label(const OUString& rText) { m_xLabel->SetText(rText); }
+
+OUString SalInstanceLabel::get_label() const { return m_xLabel->GetText(); }
+
+void SalInstanceLabel::set_mnemonic_widget(Widget* pTarget)
+{
+ FixedText* pLabel = dynamic_cast<FixedText*>(m_xLabel.get());
+ assert(pLabel && "can't use set_mnemonic_widget on SelectableFixedText");
+ SalInstanceWidget* pTargetWidget = dynamic_cast<SalInstanceWidget*>(pTarget);
+ pLabel->set_mnemonic_widget(pTargetWidget ? pTargetWidget->getWidget() : nullptr);
+}
+
+void SalInstanceLabel::set_label_type(weld::LabelType eType)
+{
+ switch (eType)
+ {
+ case weld::LabelType::Normal:
+ m_xLabel->SetControlForeground();
+ m_xLabel->SetControlBackground();
+ break;
+ case weld::LabelType::Warning:
+ m_xLabel->SetControlForeground();
+ m_xLabel->SetControlBackground(
+ m_xLabel->GetSettings().GetStyleSettings().GetWarningColor());
+ break;
+ case weld::LabelType::Error:
+ m_xLabel->SetControlForeground();
+ m_xLabel->SetControlBackground(
+ m_xLabel->GetSettings().GetStyleSettings().GetHighlightColor());
+ break;
+ case weld::LabelType::Title:
+ m_xLabel->SetControlForeground(
+ m_xLabel->GetSettings().GetStyleSettings().GetLightColor());
+ m_xLabel->SetControlBackground();
+ break;
+ }
+}
+
+void SalInstanceLabel::set_font_color(const Color& rColor)
+{
+ if (rColor != COL_AUTO)
+ m_xLabel->SetControlForeground(rColor);
+ else
+ m_xLabel->SetControlForeground();
+}
+
+void SalInstanceLabel::set_font(const vcl::Font& rFont)
+{
+ m_xLabel->SetControlFont(rFont);
+ m_xLabel->Invalidate();
+}
+
+std::unique_ptr<weld::Label> SalInstanceFrame::weld_label_widget() const
+{
+ FixedText* pLabel = dynamic_cast<FixedText*>(m_xFrame->get_label_widget());
+ if (!pLabel)
+ return nullptr;
+ return std::make_unique<SalInstanceLabel>(pLabel, m_pBuilder, false);
+}
+
+SalInstanceTextView::SalInstanceTextView(VclMultiLineEdit* pTextView, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceWidget(pTextView, pBuilder, bTakeOwnership)
+ , m_xTextView(pTextView)
+{
+ m_xTextView->SetModifyHdl(LINK(this, SalInstanceTextView, ChangeHdl));
+ ScrollBar& rVertScrollBar = m_xTextView->GetVScrollBar();
+ m_aOrigVScrollHdl = rVertScrollBar.GetScrollHdl();
+ rVertScrollBar.SetScrollHdl(LINK(this, SalInstanceTextView, VscrollHdl));
+}
+
+void SalInstanceTextView::set_text(const OUString& rText)
+{
+ disable_notify_events();
+ m_xTextView->SetText(rText);
+ enable_notify_events();
+}
+
+void SalInstanceTextView::replace_selection(const OUString& rText)
+{
+ disable_notify_events();
+ m_xTextView->ReplaceSelected(rText);
+ enable_notify_events();
+}
+
+OUString SalInstanceTextView::get_text() const { return m_xTextView->GetText(); }
+
+bool SalInstanceTextView::get_selection_bounds(int& rStartPos, int& rEndPos)
+{
+ const Selection& rSelection = m_xTextView->GetSelection();
+ rStartPos = rSelection.Min();
+ rEndPos = rSelection.Max();
+ return rSelection.Len();
+}
+
+void SalInstanceTextView::select_region(int nStartPos, int nEndPos)
+{
+ disable_notify_events();
+ tools::Long nStart = nStartPos < 0 ? SELECTION_MAX : nStartPos;
+ tools::Long nEnd = nEndPos < 0 ? SELECTION_MAX : nEndPos;
+ m_xTextView->SetSelection(Selection(nStart, nEnd));
+ enable_notify_events();
+}
+
+void SalInstanceTextView::set_editable(bool bEditable) { m_xTextView->SetReadOnly(!bEditable); }
+bool SalInstanceTextView::get_editable() const { return !m_xTextView->IsReadOnly(); }
+void SalInstanceTextView::set_max_length(int nChars) { m_xTextView->SetMaxTextLen(nChars); }
+
+void SalInstanceTextView::set_monospace(bool bMonospace)
+{
+ vcl::Font aOrigFont = m_xTextView->GetControlFont();
+ vcl::Font aFont;
+ if (bMonospace)
+ aFont
+ = OutputDevice::GetDefaultFont(DefaultFontType::UI_FIXED, LANGUAGE_DONTKNOW,
+ GetDefaultFontFlags::OnlyOne, m_xTextView->GetOutDev());
+ else
+ aFont = Application::GetSettings().GetStyleSettings().GetFieldFont();
+ aFont.SetFontHeight(aOrigFont.GetFontHeight());
+ set_font(aFont);
+}
+
+void SalInstanceTextView::set_font_color(const Color& rColor)
+{
+ if (rColor != COL_AUTO)
+ m_xTextView->SetControlForeground(rColor);
+ else
+ m_xTextView->SetControlForeground();
+}
+
+void SalInstanceTextView::set_font(const vcl::Font& rFont)
+{
+ m_xTextView->SetFont(rFont);
+ m_xTextView->SetControlFont(rFont);
+ m_xTextView->Invalidate();
+}
+
+void SalInstanceTextView::connect_cursor_position(const Link<TextView&, void>& rLink)
+{
+ assert(!m_aCursorPositionHdl.IsSet());
+ m_xTextView->AddEventListener(LINK(this, SalInstanceTextView, CursorListener));
+ weld::TextView::connect_cursor_position(rLink);
+}
+
+bool SalInstanceTextView::can_move_cursor_with_up() const
+{
+ bool bNoSelection = !m_xTextView->GetSelection();
+ return !bNoSelection || m_xTextView->CanUp();
+}
+
+bool SalInstanceTextView::can_move_cursor_with_down() const
+{
+ bool bNoSelection = !m_xTextView->GetSelection();
+ return !bNoSelection || m_xTextView->CanDown();
+}
+
+void SalInstanceTextView::cut_clipboard() { m_xTextView->Cut(); }
+
+void SalInstanceTextView::copy_clipboard() { m_xTextView->Copy(); }
+
+void SalInstanceTextView::paste_clipboard() { m_xTextView->Paste(); }
+
+void SalInstanceTextView::set_alignment(TxtAlign eXAlign)
+{
+ ::set_alignment(*m_xTextView, eXAlign);
+}
+
+int SalInstanceTextView::vadjustment_get_value() const
+{
+ ScrollBar& rVertScrollBar = m_xTextView->GetVScrollBar();
+ return rVertScrollBar.GetThumbPos();
+}
+
+void SalInstanceTextView::vadjustment_set_value(int value)
+{
+ ScrollBar& rVertScrollBar = m_xTextView->GetVScrollBar();
+ rVertScrollBar.SetThumbPos(value);
+ m_aOrigVScrollHdl.Call(&rVertScrollBar);
+}
+
+int SalInstanceTextView::vadjustment_get_upper() const
+{
+ ScrollBar& rVertScrollBar = m_xTextView->GetVScrollBar();
+ return rVertScrollBar.GetRangeMax();
+}
+
+int SalInstanceTextView::vadjustment_get_lower() const
+{
+ ScrollBar& rVertScrollBar = m_xTextView->GetVScrollBar();
+ return rVertScrollBar.GetRangeMin();
+}
+
+int SalInstanceTextView::vadjustment_get_page_size() const
+{
+ ScrollBar& rVertScrollBar = m_xTextView->GetVScrollBar();
+ return rVertScrollBar.GetVisibleSize();
+}
+
+bool SalInstanceTextView::has_focus() const { return m_xTextView->HasChildPathFocus(); }
+
+SalInstanceTextView::~SalInstanceTextView()
+{
+ if (!m_xTextView->isDisposed())
+ {
+ if (m_aCursorPositionHdl.IsSet())
+ m_xTextView->RemoveEventListener(LINK(this, SalInstanceTextView, CursorListener));
+ m_xTextView->SetModifyHdl(Link<Edit&, void>());
+ ScrollBar& rVertScrollBar = m_xTextView->GetVScrollBar();
+ rVertScrollBar.SetScrollHdl(m_aOrigVScrollHdl);
+ }
+}
+
+IMPL_LINK(SalInstanceTextView, VscrollHdl, ScrollBar*, pScrollBar, void)
+{
+ signal_vadjustment_changed();
+ m_aOrigVScrollHdl.Call(pScrollBar);
+}
+
+IMPL_LINK_NOARG(SalInstanceTextView, ChangeHdl, Edit&, void) { signal_changed(); }
+
+IMPL_LINK(SalInstanceTextView, CursorListener, VclWindowEvent&, rEvent, void)
+{
+ if (notify_events_disabled())
+ return;
+ if (rEvent.GetId() == VclEventId::EditSelectionChanged
+ || rEvent.GetId() == VclEventId::EditCaretChanged)
+ signal_cursor_position();
+}
+
+SalInstanceExpander::SalInstanceExpander(VclExpander* pExpander, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceWidget(pExpander, pBuilder, bTakeOwnership)
+ , m_xExpander(pExpander)
+{
+ m_xExpander->SetExpandedHdl(LINK(this, SalInstanceExpander, ExpandedHdl));
+}
+
+void SalInstanceExpander::set_label(const OUString& rText) { m_xExpander->set_label(rText); }
+
+OUString SalInstanceExpander::get_label() const { return m_xExpander->get_label(); }
+
+bool SalInstanceExpander::get_expanded() const { return m_xExpander->get_expanded(); }
+
+void SalInstanceExpander::set_expanded(bool bExpand) { m_xExpander->set_expanded(bExpand); }
+
+bool SalInstanceExpander::has_focus() const
+{
+ return m_xExpander->get_label_widget()->HasFocus() || SalInstanceWidget::has_focus();
+}
+
+void SalInstanceExpander::grab_focus() { return m_xExpander->get_label_widget()->GrabFocus(); }
+
+SalInstanceExpander::~SalInstanceExpander()
+{
+ m_xExpander->SetExpandedHdl(Link<VclExpander&, void>());
+}
+
+IMPL_LINK_NOARG(SalInstanceExpander, ExpandedHdl, VclExpander&, void) { signal_expanded(); }
+
+// SalInstanceWidget has a generic listener for all these
+// events, ignore the ones we have specializations for
+// in VclDrawingArea
+void SalInstanceDrawingArea::HandleEventListener(VclWindowEvent& rEvent)
+{
+ if (rEvent.GetId() == VclEventId::WindowResize)
+ return;
+ SalInstanceWidget::HandleEventListener(rEvent);
+}
+
+void SalInstanceDrawingArea::HandleMouseEventListener(VclWindowEvent& rEvent)
+{
+ if (rEvent.GetId() == VclEventId::WindowMouseButtonDown
+ || rEvent.GetId() == VclEventId::WindowMouseButtonUp
+ || rEvent.GetId() == VclEventId::WindowMouseMove)
+ {
+ return;
+ }
+ SalInstanceWidget::HandleMouseEventListener(rEvent);
+}
+
+bool SalInstanceDrawingArea::HandleKeyEventListener(VclWindowEvent& /*rEvent*/) { return false; }
+
+SalInstanceDrawingArea::SalInstanceDrawingArea(VclDrawingArea* pDrawingArea,
+ SalInstanceBuilder* pBuilder, const a11yref& rAlly,
+ FactoryFunction pUITestFactoryFunction,
+ void* pUserData, bool bTakeOwnership)
+ : SalInstanceWidget(pDrawingArea, pBuilder, bTakeOwnership)
+ , m_xDrawingArea(pDrawingArea)
+{
+ m_xDrawingArea->SetAccessible(rAlly);
+ m_xDrawingArea->SetUITestFactory(std::move(pUITestFactoryFunction), pUserData);
+ m_xDrawingArea->SetPaintHdl(LINK(this, SalInstanceDrawingArea, PaintHdl));
+ m_xDrawingArea->SetResizeHdl(LINK(this, SalInstanceDrawingArea, ResizeHdl));
+ m_xDrawingArea->SetMousePressHdl(LINK(this, SalInstanceDrawingArea, MousePressHdl));
+ m_xDrawingArea->SetMouseMoveHdl(LINK(this, SalInstanceDrawingArea, MouseMoveHdl));
+ m_xDrawingArea->SetMouseReleaseHdl(LINK(this, SalInstanceDrawingArea, MouseReleaseHdl));
+ m_xDrawingArea->SetKeyPressHdl(LINK(this, SalInstanceDrawingArea, KeyPressHdl));
+ m_xDrawingArea->SetKeyReleaseHdl(LINK(this, SalInstanceDrawingArea, KeyReleaseHdl));
+ m_xDrawingArea->SetStyleUpdatedHdl(LINK(this, SalInstanceDrawingArea, StyleUpdatedHdl));
+ m_xDrawingArea->SetCommandHdl(LINK(this, SalInstanceDrawingArea, CommandHdl));
+ m_xDrawingArea->SetQueryTooltipHdl(LINK(this, SalInstanceDrawingArea, QueryTooltipHdl));
+ m_xDrawingArea->SetGetSurroundingHdl(LINK(this, SalInstanceDrawingArea, GetSurroundingHdl));
+ m_xDrawingArea->SetDeleteSurroundingHdl(
+ LINK(this, SalInstanceDrawingArea, DeleteSurroundingHdl));
+ m_xDrawingArea->SetStartDragHdl(LINK(this, SalInstanceDrawingArea, StartDragHdl));
+}
+
+void SalInstanceDrawingArea::queue_draw() { m_xDrawingArea->Invalidate(); }
+
+void SalInstanceDrawingArea::queue_draw_area(int x, int y, int width, int height)
+{
+ m_xDrawingArea->Invalidate(tools::Rectangle(Point(x, y), Size(width, height)));
+}
+
+void SalInstanceDrawingArea::connect_size_allocate(const Link<const Size&, void>& rLink)
+{
+ weld::Widget::connect_size_allocate(rLink);
+}
+
+void SalInstanceDrawingArea::connect_key_press(const Link<const KeyEvent&, bool>& rLink)
+{
+ weld::Widget::connect_key_press(rLink);
+}
+
+void SalInstanceDrawingArea::connect_key_release(const Link<const KeyEvent&, bool>& rLink)
+{
+ weld::Widget::connect_key_release(rLink);
+}
+
+void SalInstanceDrawingArea::set_cursor(PointerStyle ePointerStyle)
+{
+ m_xDrawingArea->SetPointer(ePointerStyle);
+}
+
+void SalInstanceDrawingArea::set_input_context(const InputContext& rInputContext)
+{
+ m_xDrawingArea->SetInputContext(rInputContext);
+}
+
+void SalInstanceDrawingArea::im_context_set_cursor_location(const tools::Rectangle& rCursorRect,
+ int nExtTextInputWidth)
+{
+ tools::Rectangle aCursorRect = m_xDrawingArea->PixelToLogic(rCursorRect);
+ m_xDrawingArea->SetCursorRect(
+ &aCursorRect, m_xDrawingArea->PixelToLogic(Size(nExtTextInputWidth, 0)).Width());
+}
+
+a11yref SalInstanceDrawingArea::get_accessible_parent()
+{
+ vcl::Window* pParent = m_xDrawingArea->GetParent();
+ if (pParent)
+ return pParent->GetAccessible();
+ return css::uno::Reference<css::accessibility::XAccessible>();
+}
+
+a11yrelationset SalInstanceDrawingArea::get_accessible_relation_set()
+{
+ rtl::Reference<utl::AccessibleRelationSetHelper> pRelationSetHelper
+ = new utl::AccessibleRelationSetHelper;
+ vcl::Window* pWindow = m_xDrawingArea.get();
+ if (pWindow)
+ {
+ vcl::Window* pLabeledBy = pWindow->GetAccessibleRelationLabeledBy();
+ if (pLabeledBy && pLabeledBy != pWindow)
+ {
+ css::uno::Sequence<css::uno::Reference<css::uno::XInterface>> aSequence{
+ pLabeledBy->GetAccessible()
+ };
+ pRelationSetHelper->AddRelation(css::accessibility::AccessibleRelation(
+ css::accessibility::AccessibleRelationType::LABELED_BY, aSequence));
+ }
+ vcl::Window* pMemberOf = pWindow->GetAccessibleRelationMemberOf();
+ if (pMemberOf && pMemberOf != pWindow)
+ {
+ css::uno::Sequence<css::uno::Reference<css::uno::XInterface>> aSequence{
+ pMemberOf->GetAccessible()
+ };
+ pRelationSetHelper->AddRelation(css::accessibility::AccessibleRelation(
+ css::accessibility::AccessibleRelationType::MEMBER_OF, aSequence));
+ }
+ }
+ return pRelationSetHelper;
+}
+
+Point SalInstanceDrawingArea::get_accessible_location_on_screen()
+{
+ return m_xDrawingArea->OutputToAbsoluteScreenPixel(Point());
+}
+
+Point SalInstanceDrawingArea::get_pointer_position() const
+{
+ return m_xDrawingArea->GetPointerPosPixel();
+}
+
+void SalInstanceDrawingArea::enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper,
+ sal_uInt8 eDNDConstants)
+{
+ m_xDrawingArea->SetDragHelper(rHelper, eDNDConstants);
+}
+
+SalInstanceDrawingArea::~SalInstanceDrawingArea()
+{
+ m_xDrawingArea->SetDeleteSurroundingHdl(Link<const Selection&, bool>());
+ m_xDrawingArea->SetGetSurroundingHdl(Link<OUString&, int>());
+ m_xDrawingArea->SetQueryTooltipHdl(Link<tools::Rectangle&, OUString>());
+ m_xDrawingArea->SetCommandHdl(Link<const CommandEvent&, bool>());
+ m_xDrawingArea->SetStyleUpdatedHdl(Link<VclDrawingArea&, void>());
+ m_xDrawingArea->SetMousePressHdl(Link<const MouseEvent&, bool>());
+ m_xDrawingArea->SetMouseMoveHdl(Link<const MouseEvent&, bool>());
+ m_xDrawingArea->SetMouseReleaseHdl(Link<const MouseEvent&, bool>());
+ m_xDrawingArea->SetKeyPressHdl(Link<const KeyEvent&, bool>());
+ m_xDrawingArea->SetKeyReleaseHdl(Link<const KeyEvent&, bool>());
+ m_xDrawingArea->SetResizeHdl(Link<const Size&, void>());
+ m_xDrawingArea->SetPaintHdl(
+ Link<std::pair<vcl::RenderContext&, const tools::Rectangle&>, void>());
+}
+
+OutputDevice& SalInstanceDrawingArea::get_ref_device() { return *m_xDrawingArea->GetOutDev(); }
+
+void SalInstanceDrawingArea::click(const Point& rPos)
+{
+ MouseEvent aEvent(rPos, 1, MouseEventModifiers::NONE, MOUSE_LEFT, 0);
+ m_xDrawingArea->MouseButtonDown(aEvent);
+ m_xDrawingArea->MouseButtonUp(aEvent);
+}
+
+IMPL_LINK(SalInstanceDrawingArea, PaintHdl, target_and_area, aPayload, void)
+{
+ m_aDrawHdl.Call(aPayload);
+ tools::Rectangle aFocusRect(m_aGetFocusRectHdl.Call(*this));
+ if (!aFocusRect.IsEmpty())
+ InvertFocusRect(aPayload.first, aFocusRect);
+}
+
+IMPL_LINK(SalInstanceDrawingArea, ResizeHdl, const Size&, rSize, void)
+{
+ m_aSizeAllocateHdl.Call(rSize);
+}
+
+IMPL_LINK(SalInstanceDrawingArea, MousePressHdl, const MouseEvent&, rEvent, bool)
+{
+ return m_aMousePressHdl.Call(rEvent);
+}
+
+IMPL_LINK(SalInstanceDrawingArea, MouseMoveHdl, const MouseEvent&, rEvent, bool)
+{
+ return m_aMouseMotionHdl.Call(rEvent);
+}
+
+IMPL_LINK(SalInstanceDrawingArea, MouseReleaseHdl, const MouseEvent&, rEvent, bool)
+{
+ return m_aMouseReleaseHdl.Call(rEvent);
+}
+
+IMPL_LINK(SalInstanceDrawingArea, KeyPressHdl, const KeyEvent&, rEvent, bool)
+{
+ return m_aKeyPressHdl.Call(rEvent);
+}
+
+IMPL_LINK(SalInstanceDrawingArea, KeyReleaseHdl, const KeyEvent&, rEvent, bool)
+{
+ return m_aKeyReleaseHdl.Call(rEvent);
+}
+
+IMPL_LINK_NOARG(SalInstanceDrawingArea, StyleUpdatedHdl, VclDrawingArea&, void)
+{
+ m_aStyleUpdatedHdl.Call(*this);
+}
+
+IMPL_LINK(SalInstanceDrawingArea, CommandHdl, const CommandEvent&, rEvent, bool)
+{
+ return m_aCommandHdl.Call(rEvent);
+}
+
+IMPL_LINK(SalInstanceDrawingArea, GetSurroundingHdl, OUString&, rSurrounding, int)
+{
+ return m_aGetSurroundingHdl.Call(rSurrounding);
+}
+
+IMPL_LINK(SalInstanceDrawingArea, DeleteSurroundingHdl, const Selection&, rSelection, bool)
+{
+ return m_aDeleteSurroundingHdl.Call(rSelection);
+}
+
+IMPL_LINK(SalInstanceDrawingArea, QueryTooltipHdl, tools::Rectangle&, rHelpArea, OUString)
+{
+ return m_aQueryTooltipHdl.Call(rHelpArea);
+}
+
+IMPL_LINK_NOARG(SalInstanceDrawingArea, StartDragHdl, VclDrawingArea*, bool)
+{
+ if (m_aDragBeginHdl.Call(*this))
+ return true;
+ return false;
+}
+
+SalInstanceComboBoxWithoutEdit::SalInstanceComboBoxWithoutEdit(ListBox* pListBox,
+ SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceComboBox<ListBox>(pListBox, pBuilder, bTakeOwnership)
+{
+ m_xComboBox->SetSelectHdl(LINK(this, SalInstanceComboBoxWithoutEdit, SelectHdl));
+}
+
+OUString SalInstanceComboBoxWithoutEdit::get_active_text() const
+{
+ return m_xComboBox->GetSelectedEntry();
+}
+
+void SalInstanceComboBoxWithoutEdit::remove(int pos) { m_xComboBox->RemoveEntry(pos); }
+
+void SalInstanceComboBoxWithoutEdit::insert(int pos, const OUString& rStr, const OUString* pId,
+ const OUString* pIconName, VirtualDevice* pImageSurface)
+{
+ auto nInsertPos = pos == -1 ? COMBOBOX_APPEND : pos;
+ sal_Int32 nInsertedAt;
+ if (!pIconName && !pImageSurface)
+ nInsertedAt = m_xComboBox->InsertEntry(rStr, nInsertPos);
+ else if (pIconName)
+ nInsertedAt = m_xComboBox->InsertEntry(rStr, createImage(*pIconName), nInsertPos);
+ else
+ nInsertedAt = m_xComboBox->InsertEntry(rStr, createImage(*pImageSurface), nInsertPos);
+ if (pId)
+ {
+ m_aUserData.emplace_back(std::make_unique<OUString>(*pId));
+ m_xComboBox->SetEntryData(nInsertedAt, m_aUserData.back().get());
+ }
+}
+
+void SalInstanceComboBoxWithoutEdit::insert_separator(int pos, const OUString& /*rId*/)
+{
+ auto nInsertPos = pos == -1 ? m_xComboBox->GetEntryCount() : pos;
+ m_xComboBox->AddSeparator(nInsertPos - 1);
+}
+
+bool SalInstanceComboBoxWithoutEdit::has_entry() const { return false; }
+
+bool SalInstanceComboBoxWithoutEdit::changed_by_direct_pick() const { return true; }
+
+void SalInstanceComboBoxWithoutEdit::set_entry_message_type(weld::EntryMessageType /*eType*/)
+{
+ assert(false);
+}
+
+void SalInstanceComboBoxWithoutEdit::set_entry_text(const OUString& /*rText*/) { assert(false); }
+
+void SalInstanceComboBoxWithoutEdit::select_entry_region(int /*nStartPos*/, int /*nEndPos*/)
+{
+ assert(false);
+}
+
+bool SalInstanceComboBoxWithoutEdit::get_entry_selection_bounds(int& /*rStartPos*/,
+ int& /*rEndPos*/)
+{
+ assert(false);
+ return false;
+}
+
+void SalInstanceComboBoxWithoutEdit::set_entry_width_chars(int /*nChars*/) { assert(false); }
+
+void SalInstanceComboBoxWithoutEdit::set_entry_max_length(int /*nChars*/) { assert(false); }
+
+void SalInstanceComboBoxWithoutEdit::set_entry_completion(bool, bool) { assert(false); }
+
+void SalInstanceComboBoxWithoutEdit::set_entry_placeholder_text(const OUString&) { assert(false); }
+
+void SalInstanceComboBoxWithoutEdit::set_entry_editable(bool /*bEditable*/) { assert(false); }
+
+void SalInstanceComboBoxWithoutEdit::cut_entry_clipboard() { assert(false); }
+
+void SalInstanceComboBoxWithoutEdit::copy_entry_clipboard() { assert(false); }
+
+void SalInstanceComboBoxWithoutEdit::paste_entry_clipboard() { assert(false); }
+
+void SalInstanceComboBoxWithoutEdit::set_font(const vcl::Font& rFont)
+{
+ m_xComboBox->SetControlFont(rFont);
+ m_xComboBox->Invalidate();
+}
+
+void SalInstanceComboBoxWithoutEdit::set_entry_font(const vcl::Font&) { assert(false); }
+
+vcl::Font SalInstanceComboBoxWithoutEdit::get_entry_font()
+{
+ assert(false);
+ return vcl::Font();
+}
+
+void SalInstanceComboBoxWithoutEdit::set_custom_renderer(bool /*bOn*/)
+{
+ assert(false && "not implemented");
+}
+
+int SalInstanceComboBoxWithoutEdit::get_max_mru_count() const
+{
+ assert(false && "not implemented");
+ return 0;
+}
+
+void SalInstanceComboBoxWithoutEdit::set_max_mru_count(int) { assert(false && "not implemented"); }
+
+OUString SalInstanceComboBoxWithoutEdit::get_mru_entries() const
+{
+ assert(false && "not implemented");
+ return OUString();
+}
+
+void SalInstanceComboBoxWithoutEdit::set_mru_entries(const OUString&)
+{
+ assert(false && "not implemented");
+}
+
+void SalInstanceComboBoxWithoutEdit::HandleEventListener(VclWindowEvent& rEvent)
+{
+ CallHandleEventListener(rEvent);
+}
+
+SalInstanceComboBoxWithoutEdit::~SalInstanceComboBoxWithoutEdit()
+{
+ m_xComboBox->SetSelectHdl(Link<ListBox&, void>());
+}
+
+IMPL_LINK_NOARG(SalInstanceComboBoxWithoutEdit, SelectHdl, ListBox&, void)
+{
+ return signal_changed();
+}
+
+SalInstanceComboBoxWithEdit::SalInstanceComboBoxWithEdit(::ComboBox* pComboBox,
+ SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceComboBox<::ComboBox>(pComboBox, pBuilder, bTakeOwnership)
+ , m_aTextFilter(m_aEntryInsertTextHdl)
+ , m_bInSelect(false)
+{
+ m_xComboBox->SetModifyHdl(LINK(this, SalInstanceComboBoxWithEdit, ChangeHdl));
+ m_xComboBox->SetSelectHdl(LINK(this, SalInstanceComboBoxWithEdit, SelectHdl));
+ m_xComboBox->SetEntryActivateHdl(LINK(this, SalInstanceComboBoxWithEdit, EntryActivateHdl));
+ m_xComboBox->SetTextFilter(&m_aTextFilter);
+}
+
+bool SalInstanceComboBoxWithEdit::has_entry() const { return true; }
+
+void SalInstanceComboBoxWithEdit::call_attention_to()
+{
+ Edit* pEdit = m_xComboBox->GetSubEdit();
+ assert(pEdit);
+ m_xFlashAttention.reset(new SalFlashAttention(pEdit));
+ m_xFlashAttention->Start();
+}
+
+bool SalInstanceComboBoxWithEdit::changed_by_direct_pick() const
+{
+ return m_bInSelect && !m_xComboBox->IsModifyByKeyboard() && !m_xComboBox->IsTravelSelect();
+}
+
+void SalInstanceComboBoxWithEdit::set_entry_message_type(weld::EntryMessageType eType)
+{
+ Edit* pEdit = m_xComboBox->GetSubEdit();
+ assert(pEdit);
+ ::set_message_type(pEdit, eType);
+}
+
+OUString SalInstanceComboBoxWithEdit::get_active_text() const { return m_xComboBox->GetText(); }
+
+void SalInstanceComboBoxWithEdit::remove(int pos) { m_xComboBox->RemoveEntryAt(pos); }
+
+void SalInstanceComboBoxWithEdit::insert(int pos, const OUString& rStr, const OUString* pId,
+ const OUString* pIconName, VirtualDevice* pImageSurface)
+{
+ auto nInsertPos = pos == -1 ? COMBOBOX_APPEND : pos;
+ sal_Int32 nInsertedAt;
+ if (!pIconName && !pImageSurface)
+ nInsertedAt = m_xComboBox->InsertEntry(rStr, nInsertPos);
+ else if (pIconName)
+ nInsertedAt = m_xComboBox->InsertEntryWithImage(rStr, createImage(*pIconName), nInsertPos);
+ else
+ nInsertedAt
+ = m_xComboBox->InsertEntryWithImage(rStr, createImage(*pImageSurface), nInsertPos);
+ if (pId)
+ {
+ m_aUserData.emplace_back(std::make_unique<OUString>(*pId));
+ m_xComboBox->SetEntryData(nInsertedAt, m_aUserData.back().get());
+ }
+}
+
+void SalInstanceComboBoxWithEdit::insert_separator(int pos, const OUString& /*rId*/)
+{
+ auto nInsertPos = pos == -1 ? m_xComboBox->GetEntryCount() : pos;
+ m_xComboBox->AddSeparator(nInsertPos - 1);
+}
+
+void SalInstanceComboBoxWithEdit::set_entry_text(const OUString& rText)
+{
+ m_xComboBox->SetText(rText);
+}
+
+void SalInstanceComboBoxWithEdit::set_entry_width_chars(int nChars)
+{
+ m_xComboBox->SetWidthInChars(nChars);
+}
+
+void SalInstanceComboBoxWithEdit::set_entry_max_length(int nChars)
+{
+ m_xComboBox->SetMaxTextLen(nChars);
+}
+
+void SalInstanceComboBoxWithEdit::set_entry_completion(bool bEnable, bool bCaseSensitive)
+{
+ m_xComboBox->EnableAutocomplete(bEnable, bCaseSensitive);
+}
+
+void SalInstanceComboBoxWithEdit::set_entry_placeholder_text(const OUString& rText)
+{
+ m_xComboBox->SetPlaceholderText(rText);
+}
+
+void SalInstanceComboBoxWithEdit::set_entry_editable(bool bEditable)
+{
+ m_xComboBox->SetReadOnly(!bEditable);
+}
+
+void SalInstanceComboBoxWithEdit::cut_entry_clipboard() { m_xComboBox->Cut(); }
+
+void SalInstanceComboBoxWithEdit::copy_entry_clipboard() { m_xComboBox->Copy(); }
+
+void SalInstanceComboBoxWithEdit::paste_entry_clipboard() { m_xComboBox->Paste(); }
+
+void SalInstanceComboBoxWithEdit::select_entry_region(int nStartPos, int nEndPos)
+{
+ m_xComboBox->SetSelection(Selection(nStartPos, nEndPos < 0 ? SELECTION_MAX : nEndPos));
+}
+
+bool SalInstanceComboBoxWithEdit::get_entry_selection_bounds(int& rStartPos, int& rEndPos)
+{
+ const Selection& rSelection = m_xComboBox->GetSelection();
+ rStartPos = rSelection.Min();
+ rEndPos = rSelection.Max();
+ return rSelection.Len();
+}
+
+void SalInstanceComboBoxWithEdit::set_font(const vcl::Font& rFont)
+{
+ m_xComboBox->SetControlFont(rFont);
+ m_xComboBox->Invalidate();
+}
+
+void SalInstanceComboBoxWithEdit::set_entry_font(const vcl::Font& rFont)
+{
+ Edit* pEdit = m_xComboBox->GetSubEdit();
+ assert(pEdit);
+ pEdit->SetControlFont(rFont); // tdf#134601 set it as control font to take effect properly
+ pEdit->Invalidate();
+}
+
+vcl::Font SalInstanceComboBoxWithEdit::get_entry_font()
+{
+ Edit* pEdit = m_xComboBox->GetSubEdit();
+ assert(pEdit);
+ return pEdit->GetPointFont(*pEdit->GetOutDev());
+}
+
+void SalInstanceComboBoxWithEdit::set_custom_renderer(bool bOn)
+{
+ if (m_xComboBox->IsUserDrawEnabled() == bOn)
+ return;
+
+ auto nOldEntryHeight = m_xComboBox->GetDropDownEntryHeight();
+ auto nDropDownLineCount = m_xComboBox->GetDropDownLineCount();
+
+ m_xComboBox->EnableUserDraw(bOn);
+ if (bOn)
+ m_xComboBox->SetUserDrawHdl(LINK(this, SalInstanceComboBoxWithEdit, UserDrawHdl));
+ else
+ m_xComboBox->SetUserDrawHdl(Link<UserDrawEvent*, void>());
+
+ // adjust the line count to fit approx the height it would have been before
+ // changing the renderer
+ auto nNewEntryHeight = m_xComboBox->GetDropDownEntryHeight();
+ double fRatio = nOldEntryHeight / static_cast<double>(nNewEntryHeight);
+ m_xComboBox->SetDropDownLineCount(nDropDownLineCount * fRatio);
+}
+
+int SalInstanceComboBoxWithEdit::get_max_mru_count() const { return m_xComboBox->GetMaxMRUCount(); }
+
+void SalInstanceComboBoxWithEdit::set_max_mru_count(int nCount)
+{
+ return m_xComboBox->SetMaxMRUCount(nCount);
+}
+
+OUString SalInstanceComboBoxWithEdit::get_mru_entries() const
+{
+ return m_xComboBox->GetMRUEntries();
+}
+
+void SalInstanceComboBoxWithEdit::set_mru_entries(const OUString& rEntries)
+{
+ m_xComboBox->SetMRUEntries(rEntries);
+}
+
+void SalInstanceComboBoxWithEdit::HandleEventListener(VclWindowEvent& rEvent)
+{
+ if (rEvent.GetId() == VclEventId::DropdownPreOpen)
+ {
+ Size aRowSize(signal_custom_get_size(*m_xComboBox->GetOutDev()));
+ m_xComboBox->SetUserItemSize(aRowSize);
+ }
+ CallHandleEventListener(rEvent);
+}
+
+SalInstanceComboBoxWithEdit::~SalInstanceComboBoxWithEdit()
+{
+ m_xComboBox->SetTextFilter(nullptr);
+ m_xComboBox->SetEntryActivateHdl(Link<Edit&, bool>());
+ m_xComboBox->SetModifyHdl(Link<Edit&, void>());
+ m_xComboBox->SetSelectHdl(Link<::ComboBox&, void>());
+}
+
+IMPL_LINK_NOARG(SalInstanceComboBoxWithEdit, ChangeHdl, Edit&, void)
+{
+ if (!m_xComboBox->IsSyntheticModify()) // SelectHdl will be called
+ signal_changed();
+}
+
+IMPL_LINK_NOARG(SalInstanceComboBoxWithEdit, SelectHdl, ::ComboBox&, void)
+{
+ m_bInSelect = true;
+ signal_changed();
+ m_bInSelect = false;
+}
+
+IMPL_LINK_NOARG(SalInstanceComboBoxWithEdit, EntryActivateHdl, Edit&, bool)
+{
+ return m_aEntryActivateHdl.Call(*this);
+}
+
+IMPL_LINK(SalInstanceComboBoxWithEdit, UserDrawHdl, UserDrawEvent*, pEvent, void)
+{
+ call_signal_custom_render(pEvent);
+}
+
+class SalInstanceEntryTreeView : public SalInstanceContainer, public virtual weld::EntryTreeView
+{
+private:
+ DECL_LINK(AutocompleteHdl, Edit&, void);
+ DECL_LINK(KeyPressListener, VclWindowEvent&, void);
+ SalInstanceEntry* m_pEntry;
+ SalInstanceTreeView* m_pTreeView;
+ bool m_bTreeChange;
+
+public:
+ SalInstanceEntryTreeView(vcl::Window* pContainer, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership, std::unique_ptr<weld::Entry> xEntry,
+ std::unique_ptr<weld::TreeView> xTreeView)
+ : EntryTreeView(std::move(xEntry), std::move(xTreeView))
+ , SalInstanceContainer(pContainer, pBuilder, bTakeOwnership)
+ , m_pEntry(dynamic_cast<SalInstanceEntry*>(m_xEntry.get()))
+ , m_pTreeView(dynamic_cast<SalInstanceTreeView*>(m_xTreeView.get()))
+ , m_bTreeChange(false)
+ {
+ assert(m_pEntry && m_pTreeView);
+
+ Edit& rEntry = m_pEntry->getEntry();
+ rEntry.SetAutocompleteHdl(LINK(this, SalInstanceEntryTreeView, AutocompleteHdl));
+ rEntry.AddEventListener(LINK(this, SalInstanceEntryTreeView, KeyPressListener));
+ }
+
+ virtual void insert_separator(int /*pos*/, const OUString& /*rId*/) override { assert(false); }
+
+ virtual void make_sorted() override
+ {
+ vcl::Window* pTreeView = m_pTreeView->getWidget();
+ pTreeView->SetStyle(pTreeView->GetStyle() | WB_SORT);
+ }
+
+ virtual void set_entry_completion(bool bEnable, bool /*bCaseSensitive*/) override
+ {
+ assert(!bEnable && "not implemented yet");
+ (void)bEnable;
+ Edit& rEntry = m_pEntry->getEntry();
+ rEntry.SetAutocompleteHdl(Link<Edit&, void>());
+ }
+
+ virtual void set_font(const vcl::Font&) override { assert(false && "not implemented"); }
+
+ virtual void set_entry_font(const vcl::Font& rFont) override { m_pEntry->set_font(rFont); }
+
+ virtual vcl::Font get_entry_font() override
+ {
+ Edit& rEntry = m_pEntry->getEntry();
+ return rEntry.GetPointFont(*rEntry.GetOutDev());
+ }
+
+ virtual void set_entry_placeholder_text(const OUString& rText) override
+ {
+ Edit& rEntry = m_pEntry->getEntry();
+ rEntry.SetPlaceholderText(rText);
+ }
+
+ virtual void set_entry_editable(bool bEditable) override
+ {
+ Edit& rEntry = m_pEntry->getEntry();
+ rEntry.SetReadOnly(!bEditable);
+ }
+
+ virtual void cut_entry_clipboard() override
+ {
+ Edit& rEntry = m_pEntry->getEntry();
+ rEntry.Cut();
+ }
+
+ virtual void copy_entry_clipboard() override
+ {
+ Edit& rEntry = m_pEntry->getEntry();
+ rEntry.Copy();
+ }
+
+ virtual void paste_entry_clipboard() override
+ {
+ Edit& rEntry = m_pEntry->getEntry();
+ rEntry.Paste();
+ }
+
+ virtual void grab_focus() override { m_xEntry->grab_focus(); }
+
+ virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
+ {
+ m_xEntry->connect_focus_in(rLink);
+ }
+
+ virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
+ {
+ m_xEntry->connect_focus_out(rLink);
+ }
+
+ virtual bool changed_by_direct_pick() const override { return m_bTreeChange; }
+
+ virtual void set_custom_renderer(bool /*bOn*/) override { assert(false && "not implemented"); }
+
+ virtual int get_max_mru_count() const override
+ {
+ assert(false && "not implemented");
+ return 0;
+ }
+
+ virtual void set_max_mru_count(int) override { assert(false && "not implemented"); }
+
+ virtual OUString get_mru_entries() const override
+ {
+ assert(false && "not implemented");
+ return OUString();
+ }
+
+ virtual void set_mru_entries(const OUString&) override { assert(false && "not implemented"); }
+
+ virtual void set_item_menu(const OString&, weld::Menu*) override
+ {
+ assert(false && "not implemented");
+ }
+
+ int get_menu_button_width() const override
+ {
+ assert(false && "not implemented");
+ return 0;
+ }
+
+ VclPtr<VirtualDevice> create_render_virtual_device() const override
+ {
+ return VclPtr<VirtualDevice>::Create();
+ }
+
+ virtual ~SalInstanceEntryTreeView() override
+ {
+ Edit& rEntry = m_pEntry->getEntry();
+ rEntry.RemoveEventListener(LINK(this, SalInstanceEntryTreeView, KeyPressListener));
+ rEntry.SetAutocompleteHdl(Link<Edit&, void>());
+ }
+};
+
+IMPL_LINK(SalInstanceEntryTreeView, KeyPressListener, VclWindowEvent&, rEvent, void)
+{
+ if (rEvent.GetId() != VclEventId::WindowKeyInput)
+ return;
+ const KeyEvent& rKeyEvent = *static_cast<KeyEvent*>(rEvent.GetData());
+ sal_uInt16 nKeyCode = rKeyEvent.GetKeyCode().GetCode();
+ if (!(nKeyCode == KEY_UP || nKeyCode == KEY_DOWN || nKeyCode == KEY_PAGEUP
+ || nKeyCode == KEY_PAGEDOWN))
+ return;
+
+ m_pTreeView->disable_notify_events();
+ auto& rListBox = m_pTreeView->getTreeView();
+ if (!rListBox.FirstSelected())
+ {
+ if (SvTreeListEntry* pEntry = rListBox.First())
+ rListBox.Select(pEntry, true);
+ }
+ else
+ rListBox.KeyInput(rKeyEvent);
+ m_xEntry->set_text(m_xTreeView->get_selected_text());
+ m_xEntry->select_region(0, -1);
+ m_pTreeView->enable_notify_events();
+ m_bTreeChange = true;
+ m_pEntry->fire_signal_changed();
+ m_bTreeChange = false;
+}
+
+IMPL_LINK(SalInstanceEntryTreeView, AutocompleteHdl, Edit&, rEdit, void)
+{
+ Selection aSel = rEdit.GetSelection();
+
+ OUString aFullText = rEdit.GetText();
+ OUString aStartText = aFullText.copy(0, static_cast<sal_Int32>(aSel.Max()));
+
+ int nPos = -1;
+ int nCount = m_xTreeView->n_children();
+ for (int i = 0; i < nCount; ++i)
+ {
+ if (m_xTreeView->get_text(i).startsWithIgnoreAsciiCase(aStartText))
+ {
+ nPos = i;
+ break;
+ }
+ }
+
+ m_xTreeView->select(nPos);
+
+ if (nPos != -1)
+ {
+ OUString aText = m_xTreeView->get_text(nPos);
+ Selection aSelection(aText.getLength(), aStartText.getLength());
+ rEdit.SetText(aText, aSelection);
+ }
+}
+
+SalInstancePopover::SalInstancePopover(DockingWindow* pPopover, SalInstanceBuilder* pBuilder,
+ bool bTakeOwnership)
+ : SalInstanceContainer(pPopover, pBuilder, bTakeOwnership)
+ , m_xPopover(pPopover)
+{
+}
+
+SalInstancePopover::~SalInstancePopover()
+{
+ DockingManager* pDockingManager = vcl::Window::GetDockingManager();
+ if (pDockingManager->IsInPopupMode(m_xPopover))
+ ImplPopDown();
+}
+
+void SalInstancePopover::popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect,
+ weld::Placement ePlace)
+{
+ SalInstanceWidget* pVclWidget = dynamic_cast<SalInstanceWidget*>(pParent);
+ assert(pVclWidget);
+ vcl::Window* pWidget = pVclWidget->getWidget();
+
+ tools::Rectangle aRect;
+ Point aPt = pWidget->OutputToScreenPixel(rRect.TopLeft());
+ aRect.SetLeft(aPt.X());
+ aRect.SetTop(aPt.Y());
+ aPt = pWidget->OutputToScreenPixel(rRect.BottomRight());
+ aRect.SetRight(aPt.X());
+ aRect.SetBottom(aPt.Y());
+
+ FloatWinPopupFlags nFlags = FloatWinPopupFlags::GrabFocus | FloatWinPopupFlags::NoMouseUpClose;
+ if (ePlace == weld::Placement::Under)
+ nFlags = nFlags | FloatWinPopupFlags::Down;
+ else
+ nFlags = nFlags | FloatWinPopupFlags::Right;
+
+ m_xPopover->EnableDocking();
+ DockingManager* pDockingManager = vcl::Window::GetDockingManager();
+ pDockingManager->SetPopupModeEndHdl(m_xPopover,
+ LINK(this, SalInstancePopover, PopupModeEndHdl));
+ pDockingManager->StartPopupMode(m_xPopover, aRect, nFlags);
+}
+
+void SalInstancePopover::ImplPopDown()
+{
+ vcl::Window::GetDockingManager()->EndPopupMode(m_xPopover);
+ m_xPopover->EnableDocking(false);
+ signal_closed();
+}
+
+void SalInstancePopover::popdown() { ImplPopDown(); }
+
+void SalInstancePopover::resize_to_request()
+{
+ ::resize_to_request(m_xPopover.get());
+
+ DockingManager* pDockingManager = vcl::Window::GetDockingManager();
+ if (pDockingManager->IsInPopupMode(m_xPopover.get()))
+ {
+ Size aSize = m_xPopover->get_preferred_size();
+ tools::Rectangle aRect = pDockingManager->GetPosSizePixel(m_xPopover.get());
+ pDockingManager->SetPosSizePixel(m_xPopover.get(), aRect.Left(), aRect.Top(), aSize.Width(),
+ aSize.Height(), PosSizeFlags::Size);
+ }
+}
+
+IMPL_LINK_NOARG(SalInstancePopover, PopupModeEndHdl, FloatingWindow*, void) { signal_closed(); }
+
+SalInstanceBuilder::SalInstanceBuilder(vcl::Window* pParent, const OUString& rUIRoot,
+ const OUString& rUIFile,
+ const css::uno::Reference<css::frame::XFrame>& rFrame)
+ : weld::Builder()
+ , m_xBuilder(new VclBuilder(pParent, rUIRoot, rUIFile, OString(), rFrame, false))
+{
+}
+
+std::unique_ptr<weld::MessageDialog> SalInstanceBuilder::weld_message_dialog(const OString& id)
+{
+ MessageDialog* pMessageDialog = m_xBuilder->get<MessageDialog>(id);
+ std::unique_ptr<weld::MessageDialog> pRet(
+ pMessageDialog ? new SalInstanceMessageDialog(pMessageDialog, this, false) : nullptr);
+ if (pMessageDialog)
+ {
+ assert(!m_aOwnedToplevel && "only one toplevel per .ui allowed");
+ m_aOwnedToplevel.set(pMessageDialog);
+ m_xBuilder->drop_ownership(pMessageDialog);
+ }
+ return pRet;
+}
+
+std::unique_ptr<weld::Dialog> SalInstanceBuilder::weld_dialog(const OString& id)
+{
+ Dialog* pDialog = m_xBuilder->get<Dialog>(id);
+ std::unique_ptr<weld::Dialog> pRet(pDialog ? new SalInstanceDialog(pDialog, this, false)
+ : nullptr);
+ if (pDialog)
+ {
+ assert(!m_aOwnedToplevel && "only one toplevel per .ui allowed");
+ m_aOwnedToplevel.set(pDialog);
+ m_xBuilder->drop_ownership(pDialog);
+ }
+ return pRet;
+}
+
+std::unique_ptr<weld::Assistant> SalInstanceBuilder::weld_assistant(const OString& id)
+{
+ vcl::RoadmapWizard* pDialog = m_xBuilder->get<vcl::RoadmapWizard>(id);
+ std::unique_ptr<weld::Assistant> pRet(pDialog ? new SalInstanceAssistant(pDialog, this, false)
+ : nullptr);
+ if (pDialog)
+ {
+ assert(!m_aOwnedToplevel && "only one toplevel per .ui allowed");
+ m_aOwnedToplevel.set(pDialog);
+ m_xBuilder->drop_ownership(pDialog);
+ }
+ return pRet;
+}
+
+std::unique_ptr<weld::Window> SalInstanceBuilder::create_screenshot_window()
+{
+ assert(!m_aOwnedToplevel && "only one toplevel per .ui allowed");
+
+ vcl::Window* pRoot = m_xBuilder->get_widget_root();
+ if (SystemWindow* pWindow = dynamic_cast<SystemWindow*>(pRoot))
+ {
+ std::unique_ptr<weld::Window> xRet(new SalInstanceWindow(pWindow, this, false));
+ m_aOwnedToplevel.set(pWindow);
+ m_xBuilder->drop_ownership(pWindow);
+ return xRet;
+ }
+
+ VclPtrInstance<Dialog> xDialog(nullptr, WB_HIDE | WB_STDDIALOG | WB_SIZEABLE | WB_CLOSEABLE,
+ Dialog::InitFlag::NoParent);
+ xDialog->SetText(utl::ConfigManager::getProductName());
+
+ auto xContentArea = VclPtr<VclVBox>::Create(xDialog, false, 12);
+ pRoot->SetParent(xContentArea);
+ assert(pRoot == xContentArea->GetWindow(GetWindowType::FirstChild));
+ xContentArea->Show();
+ pRoot->Show();
+ xDialog->SetHelpId(pRoot->GetHelpId());
+
+ m_aOwnedToplevel.set(xDialog);
+
+ return std::unique_ptr<weld::Dialog>(new SalInstanceDialog(xDialog, this, false));
+}
+
+std::unique_ptr<weld::Widget> SalInstanceBuilder::weld_widget(const OString& id)
+{
+ vcl::Window* pWidget = m_xBuilder->get(id);
+ return pWidget ? std::make_unique<SalInstanceWidget>(pWidget, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::Container> SalInstanceBuilder::weld_container(const OString& id)
+{
+ vcl::Window* pContainer = m_xBuilder->get(id);
+ return pContainer ? std::make_unique<SalInstanceContainer>(pContainer, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::Box> SalInstanceBuilder::weld_box(const OString& id)
+{
+ VclBox* pContainer = m_xBuilder->get<VclBox>(id);
+ return pContainer ? std::make_unique<SalInstanceBox>(pContainer, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::Paned> SalInstanceBuilder::weld_paned(const OString& id)
+{
+ VclPaned* pPaned = m_xBuilder->get<VclPaned>(id);
+ return pPaned ? std::make_unique<SalInstancePaned>(pPaned, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::Frame> SalInstanceBuilder::weld_frame(const OString& id)
+{
+ VclFrame* pFrame = m_xBuilder->get<VclFrame>(id);
+ std::unique_ptr<weld::Frame> pRet(pFrame ? new SalInstanceFrame(pFrame, this, false) : nullptr);
+ return pRet;
+}
+
+std::unique_ptr<weld::ScrolledWindow>
+SalInstanceBuilder::weld_scrolled_window(const OString& id, bool bUserManagedScrolling)
+{
+ VclScrolledWindow* pScrolledWindow = m_xBuilder->get<VclScrolledWindow>(id);
+ return pScrolledWindow ? std::make_unique<SalInstanceScrolledWindow>(
+ pScrolledWindow, this, false, bUserManagedScrolling)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Notebook> SalInstanceBuilder::weld_notebook(const OString& id)
+{
+ vcl::Window* pNotebook = m_xBuilder->get(id);
+ if (!pNotebook)
+ return nullptr;
+ if (pNotebook->GetType() == WindowType::TABCONTROL)
+ return std::make_unique<SalInstanceNotebook>(static_cast<TabControl*>(pNotebook), this,
+ false);
+ if (pNotebook->GetType() == WindowType::VERTICALTABCONTROL)
+ return std::make_unique<SalInstanceVerticalNotebook>(
+ static_cast<VerticalTabControl*>(pNotebook), this, false);
+ return nullptr;
+}
+
+std::unique_ptr<weld::Button> SalInstanceBuilder::weld_button(const OString& id)
+{
+ Button* pButton = m_xBuilder->get<Button>(id);
+ return pButton ? std::make_unique<SalInstanceButton>(pButton, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::MenuButton> SalInstanceBuilder::weld_menu_button(const OString& id)
+{
+ MenuButton* pButton = m_xBuilder->get<MenuButton>(id);
+ return pButton ? std::make_unique<SalInstanceMenuButton>(pButton, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::MenuToggleButton>
+SalInstanceBuilder::weld_menu_toggle_button(const OString& id)
+{
+ MenuToggleButton* pButton = m_xBuilder->get<MenuToggleButton>(id);
+ return pButton ? std::make_unique<SalInstanceMenuToggleButton>(pButton, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::LinkButton> SalInstanceBuilder::weld_link_button(const OString& id)
+{
+ FixedHyperlink* pButton = m_xBuilder->get<FixedHyperlink>(id);
+ return pButton ? std::make_unique<SalInstanceLinkButton>(pButton, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::ToggleButton> SalInstanceBuilder::weld_toggle_button(const OString& id)
+{
+ PushButton* pToggleButton = m_xBuilder->get<PushButton>(id);
+ return pToggleButton ? std::make_unique<SalInstanceToggleButton>(pToggleButton, this, false)
+ : nullptr;
+}
+
+std::unique_ptr<weld::RadioButton> SalInstanceBuilder::weld_radio_button(const OString& id)
+{
+ RadioButton* pRadioButton = m_xBuilder->get<RadioButton>(id);
+ return pRadioButton ? std::make_unique<SalInstanceRadioButton>(pRadioButton, this, false)
+ : nullptr;
+}
+
+std::unique_ptr<weld::CheckButton> SalInstanceBuilder::weld_check_button(const OString& id)
+{
+ CheckBox* pCheckButton = m_xBuilder->get<CheckBox>(id);
+ return pCheckButton ? std::make_unique<SalInstanceCheckButton>(pCheckButton, this, false)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Scale> SalInstanceBuilder::weld_scale(const OString& id)
+{
+ Slider* pSlider = m_xBuilder->get<Slider>(id);
+ return pSlider ? std::make_unique<SalInstanceScale>(pSlider, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::ProgressBar> SalInstanceBuilder::weld_progress_bar(const OString& id)
+{
+ ::ProgressBar* pProgress = m_xBuilder->get<::ProgressBar>(id);
+ return pProgress ? std::make_unique<SalInstanceProgressBar>(pProgress, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::Spinner> SalInstanceBuilder::weld_spinner(const OString& id)
+{
+ Throbber* pThrobber = m_xBuilder->get<Throbber>(id);
+ return pThrobber ? std::make_unique<SalInstanceSpinner>(pThrobber, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::Image> SalInstanceBuilder::weld_image(const OString& id)
+{
+ FixedImage* pImage = m_xBuilder->get<FixedImage>(id);
+ return pImage ? std::make_unique<SalInstanceImage>(pImage, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::Calendar> SalInstanceBuilder::weld_calendar(const OString& id)
+{
+ Calendar* pCalendar = m_xBuilder->get<Calendar>(id);
+ return pCalendar ? std::make_unique<SalInstanceCalendar>(pCalendar, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::Entry> SalInstanceBuilder::weld_entry(const OString& id)
+{
+ Edit* pEntry = m_xBuilder->get<Edit>(id);
+ return pEntry ? std::make_unique<SalInstanceEntry>(pEntry, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::SpinButton> SalInstanceBuilder::weld_spin_button(const OString& id)
+{
+ FormattedField* pSpinButton = m_xBuilder->get<FormattedField>(id);
+ return pSpinButton ? std::make_unique<SalInstanceSpinButton>(pSpinButton, this, false)
+ : nullptr;
+}
+
+std::unique_ptr<weld::MetricSpinButton>
+SalInstanceBuilder::weld_metric_spin_button(const OString& id, FieldUnit eUnit)
+{
+ std::unique_ptr<weld::SpinButton> xButton(weld_spin_button(id));
+ if (xButton)
+ {
+ SalInstanceSpinButton& rButton = dynamic_cast<SalInstanceSpinButton&>(*xButton);
+ rButton.SetUseThousandSep();
+ }
+ return std::make_unique<weld::MetricSpinButton>(std::move(xButton), eUnit);
+}
+
+std::unique_ptr<weld::FormattedSpinButton>
+SalInstanceBuilder::weld_formatted_spin_button(const OString& id)
+{
+ FormattedField* pSpinButton = m_xBuilder->get<FormattedField>(id);
+ return pSpinButton ? std::make_unique<SalInstanceFormattedSpinButton>(pSpinButton, this, false)
+ : nullptr;
+}
+
+std::unique_ptr<weld::ComboBox> SalInstanceBuilder::weld_combo_box(const OString& id)
+{
+ vcl::Window* pWidget = m_xBuilder->get(id);
+ ::ComboBox* pComboBox = dynamic_cast<::ComboBox*>(pWidget);
+ if (pComboBox)
+ return std::make_unique<SalInstanceComboBoxWithEdit>(pComboBox, this, false);
+ ListBox* pListBox = dynamic_cast<ListBox*>(pWidget);
+ return pListBox ? std::make_unique<SalInstanceComboBoxWithoutEdit>(pListBox, this, false)
+ : nullptr;
+}
+
+std::unique_ptr<weld::EntryTreeView>
+SalInstanceBuilder::weld_entry_tree_view(const OString& containerid, const OString& entryid,
+ const OString& treeviewid)
+{
+ vcl::Window* pContainer = m_xBuilder->get(containerid);
+ return pContainer ? std::make_unique<SalInstanceEntryTreeView>(pContainer, this, false,
+ weld_entry(entryid),
+ weld_tree_view(treeviewid))
+ : nullptr;
+}
+
+std::unique_ptr<weld::TreeView> SalInstanceBuilder::weld_tree_view(const OString& id)
+{
+ SvTabListBox* pTreeView = m_xBuilder->get<SvTabListBox>(id);
+ return pTreeView ? std::make_unique<SalInstanceTreeView>(pTreeView, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::IconView> SalInstanceBuilder::weld_icon_view(const OString& id)
+{
+ IconView* pIconView = m_xBuilder->get<IconView>(id);
+ return pIconView ? std::make_unique<SalInstanceIconView>(pIconView, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::Label> SalInstanceBuilder::weld_label(const OString& id)
+{
+ Control* pLabel = m_xBuilder->get<Control>(id);
+ return pLabel ? std::make_unique<SalInstanceLabel>(pLabel, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::TextView> SalInstanceBuilder::weld_text_view(const OString& id)
+{
+ VclMultiLineEdit* pTextView = m_xBuilder->get<VclMultiLineEdit>(id);
+ return pTextView ? std::make_unique<SalInstanceTextView>(pTextView, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::Expander> SalInstanceBuilder::weld_expander(const OString& id)
+{
+ VclExpander* pExpander = m_xBuilder->get<VclExpander>(id);
+ return pExpander ? std::make_unique<SalInstanceExpander>(pExpander, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::DrawingArea>
+SalInstanceBuilder::weld_drawing_area(const OString& id, const a11yref& rA11yImpl,
+ FactoryFunction pUITestFactoryFunction, void* pUserData)
+{
+ VclDrawingArea* pDrawingArea = m_xBuilder->get<VclDrawingArea>(id);
+ return pDrawingArea
+ ? std::make_unique<SalInstanceDrawingArea>(pDrawingArea, this, rA11yImpl,
+ pUITestFactoryFunction, pUserData, false)
+ : nullptr;
+}
+
+std::unique_ptr<weld::Menu> SalInstanceBuilder::weld_menu(const OString& id)
+{
+ PopupMenu* pMenu = m_xBuilder->get_menu(id);
+ return pMenu ? std::make_unique<SalInstanceMenu>(pMenu, true) : nullptr;
+}
+
+std::unique_ptr<weld::Popover> SalInstanceBuilder::weld_popover(const OString& id)
+{
+ DockingWindow* pDockingWindow = m_xBuilder->get<DockingWindow>(id);
+ std::unique_ptr<weld::Popover> pRet(
+ pDockingWindow ? new SalInstancePopover(pDockingWindow, this, false) : nullptr);
+ if (pDockingWindow)
+ {
+ assert(!m_aOwnedToplevel && "only one toplevel per .ui allowed");
+ m_aOwnedToplevel.set(pDockingWindow);
+ m_xBuilder->drop_ownership(pDockingWindow);
+ }
+ return pRet;
+}
+
+std::unique_ptr<weld::Toolbar> SalInstanceBuilder::weld_toolbar(const OString& id)
+{
+ ToolBox* pToolBox = m_xBuilder->get<ToolBox>(id);
+ return pToolBox ? std::make_unique<SalInstanceToolbar>(pToolBox, this, false) : nullptr;
+}
+
+std::unique_ptr<weld::SizeGroup> SalInstanceBuilder::create_size_group()
+{
+ return std::make_unique<SalInstanceSizeGroup>();
+}
+
+OString SalInstanceBuilder::get_current_page_help_id() const
+{
+ vcl::Window* pCtrl = m_xBuilder->get("tabcontrol");
+ if (!pCtrl)
+ return OString();
+ VclPtr<vcl::Window> xTabPage;
+ if (pCtrl->GetType() == WindowType::TABCONTROL)
+ {
+ TabControl* pTabCtrl = static_cast<TabControl*>(pCtrl);
+ xTabPage = pTabCtrl->GetTabPage(pTabCtrl->GetCurPageId());
+ }
+ else if (pCtrl->GetType() == WindowType::VERTICALTABCONTROL)
+ {
+ VerticalTabControl* pTabCtrl = static_cast<VerticalTabControl*>(pCtrl);
+ xTabPage = pTabCtrl->GetPage(pTabCtrl->GetCurPageId());
+ }
+ vcl::Window* pTabChild = xTabPage ? xTabPage->GetWindow(GetWindowType::FirstChild) : nullptr;
+ pTabChild = pTabChild ? pTabChild->GetWindow(GetWindowType::FirstChild) : nullptr;
+ if (pTabChild)
+ return pTabChild->GetHelpId();
+ return OString();
+}
+
+SalInstanceBuilder::~SalInstanceBuilder()
+{
+ if (VclBuilderContainer* pOwnedToplevel
+ = dynamic_cast<VclBuilderContainer*>(m_aOwnedToplevel.get()))
+ pOwnedToplevel->m_pUIBuilder = std::move(m_xBuilder);
+ else
+ m_xBuilder.reset();
+ m_aOwnedToplevel.disposeAndClear();
+}
+
+std::unique_ptr<weld::Builder>
+SalInstance::CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile)
+{
+ SalInstanceWidget* pParentInstance = dynamic_cast<SalInstanceWidget*>(pParent);
+ vcl::Window* pParentWidget = pParentInstance ? pParentInstance->getWidget() : nullptr;
+ return std::make_unique<SalInstanceBuilder>(pParentWidget, rUIRoot, rUIFile);
+}
+
+std::unique_ptr<weld::Builder> SalInstance::CreateInterimBuilder(vcl::Window* pParent,
+ const OUString& rUIRoot,
+ const OUString& rUIFile, bool,
+ sal_uInt64)
+{
+ return std::make_unique<SalInstanceBuilder>(pParent, rUIRoot, rUIFile);
+}
+
+void SalInstanceWindow::help()
+{
+ //show help for widget with keyboard focus
+ vcl::Window* pWidget = ImplGetSVData()->mpWinData->mpFocusWin;
+ if (!pWidget)
+ pWidget = m_xWindow;
+ if (comphelper::LibreOfficeKit::isActive() && m_xWindow->GetFocusedWindow())
+ pWidget = m_xWindow->GetFocusedWindow().get();
+ OString sHelpId = pWidget->GetHelpId();
+ while (sHelpId.isEmpty())
+ {
+ pWidget = pWidget->GetParent();
+ if (!pWidget)
+ break;
+ sHelpId = pWidget->GetHelpId();
+ }
+ std::unique_ptr<weld::Widget> xTemp(
+ pWidget != m_xWindow ? new SalInstanceWidget(pWidget, m_pBuilder, false) : nullptr);
+ weld::Widget* pSource = xTemp ? xTemp.get() : this;
+ bool bRunNormalHelpRequest = !m_aHelpRequestHdl.IsSet() || m_aHelpRequestHdl.Call(*pSource);
+ Help* pHelp = bRunNormalHelpRequest ? Application::GetHelp() : nullptr;
+ if (!pHelp)
+ return;
+
+ // tdf#126007, there's a nice fallback route for offline help where
+ // the current page of a notebook will get checked when the help
+ // button is pressed and there was no help for the dialog found.
+ //
+ // But for online help that route doesn't get taken, so bodge this here
+ // by using the page help id if available and if the help button itself
+ // was the original id
+ if (m_pBuilder && sHelpId.endsWith("/help"))
+ {
+ OString sPageId = m_pBuilder->get_current_page_help_id();
+ if (!sPageId.isEmpty())
+ sHelpId = sPageId;
+ else
+ {
+ // tdf#129068 likewise the help for the wrapping dialog is less
+ // helpful than the help for the content area could be
+ vcl::Window* pContentArea = nullptr;
+ if (::Dialog* pDialog = dynamic_cast<::Dialog*>(m_xWindow.get()))
+ pContentArea = pDialog->get_content_area();
+ if (pContentArea)
+ {
+ vcl::Window* pContentWidget = pContentArea->GetWindow(GetWindowType::LastChild);
+ if (pContentWidget)
+ sHelpId = pContentWidget->GetHelpId();
+ }
+ }
+ }
+ pHelp->Start(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), pSource);
+}
+
+//iterate upwards through the hierarchy from this widgets through its parents
+//calling func with their helpid until func returns true or we run out of parents
+void SalInstanceWidget::help_hierarchy_foreach(const std::function<bool(const OString&)>& func)
+{
+ vcl::Window* pParent = m_xWidget;
+ while ((pParent = pParent->GetParent()))
+ {
+ if (func(pParent->GetHelpId()))
+ return;
+ }
+}
+
+weld::MessageDialog* SalInstance::CreateMessageDialog(weld::Widget* pParent,
+ VclMessageType eMessageType,
+ VclButtonsType eButtonsType,
+ const OUString& rPrimaryMessage)
+{
+ SalInstanceWidget* pParentInstance = dynamic_cast<SalInstanceWidget*>(pParent);
+ SystemWindow* pParentWidget = pParentInstance ? pParentInstance->getSystemWindow() : nullptr;
+ VclPtrInstance<MessageDialog> xMessageDialog(pParentWidget, rPrimaryMessage, eMessageType,
+ eButtonsType);
+ return new SalInstanceMessageDialog(xMessageDialog, nullptr, true);
+}
+
+weld::Window* SalInstance::GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow)
+{
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper();
+ if (!pWrapper)
+ return nullptr;
+ VclPtr<vcl::Window> xWindow = pWrapper->GetWindow(rWindow);
+ if (!xWindow)
+ return nullptr;
+ return xWindow->GetFrameWeld();
+}
+
+weld::Window* SalFrame::GetFrameWeld() const
+{
+ if (!m_xFrameWeld)
+ {
+ vcl::Window* pWindow = GetWindow();
+ if (pWindow)
+ {
+ assert(pWindow == pWindow->GetFrameWindow());
+ m_xFrameWeld.reset(new SalInstanceWindow(pWindow, nullptr, false));
+ }
+ }
+ return m_xFrameWeld.get();
+}
+
+Selection SalFrame::CalcDeleteSurroundingSelection(const OUString& rSurroundingText,
+ sal_Int32 nCursorIndex, int nOffset, int nChars)
+{
+ Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32);
+
+ if (nCursorIndex == -1)
+ return aInvalid;
+
+ if (nOffset > 0)
+ {
+ while (nOffset && nCursorIndex < rSurroundingText.getLength())
+ {
+ rSurroundingText.iterateCodePoints(&nCursorIndex, 1);
+ --nOffset;
+ }
+ }
+ else if (nOffset < 0)
+ {
+ while (nOffset && nCursorIndex > 0)
+ {
+ rSurroundingText.iterateCodePoints(&nCursorIndex, -1);
+ ++nOffset;
+ }
+ }
+
+ if (nOffset)
+ {
+ SAL_WARN("vcl",
+ "SalFrame::CalcDeleteSurroundingSelection, unable to move to offset: " << nOffset);
+ return aInvalid;
+ }
+
+ sal_Int32 nCursorEndIndex(nCursorIndex);
+ sal_Int32 nCount(0);
+ while (nCount < nChars && nCursorEndIndex < rSurroundingText.getLength())
+ {
+ rSurroundingText.iterateCodePoints(&nCursorEndIndex, 1);
+ ++nCount;
+ }
+
+ if (nCount != nChars)
+ {
+ SAL_WARN("vcl", "SalFrame::CalcDeleteSurroundingSelection, unable to select: "
+ << nChars << " characters");
+ return aInvalid;
+ }
+
+ return Selection(nCursorIndex, nCursorEndIndex);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/scheduler.cxx b/vcl/source/app/scheduler.cxx
new file mode 100644
index 000000000..927de71c4
--- /dev/null
+++ b/vcl/source/app/scheduler.cxx
@@ -0,0 +1,664 @@
+/* -*- 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 <cassert>
+#include <cstdlib>
+#include <exception>
+#include <typeinfo>
+
+#include <com/sun/star/uno/Exception.hpp>
+#include <sal/log.hxx>
+#include <sal/types.h>
+#include <svdata.hxx>
+#include <tools/time.hxx>
+#include <tools/debug.hxx>
+#include <tools/diagnose_ex.h>
+#include <unotools/configmgr.hxx>
+#include <vcl/TaskStopwatch.hxx>
+#include <vcl/scheduler.hxx>
+#include <vcl/idle.hxx>
+#include <saltimer.hxx>
+#include <salinst.hxx>
+#include <comphelper/profilezone.hxx>
+#include <schedulerimpl.hxx>
+
+namespace {
+
+template< typename charT, typename traits >
+std::basic_ostream<charT, traits> & operator <<(
+ std::basic_ostream<charT, traits> & stream, const Task& task )
+{
+ stream << "a: " << task.IsActive() << " p: " << static_cast<int>(task.GetPriority());
+ const char *name = task.GetDebugName();
+ if( nullptr == name )
+ return stream << " (nullptr)";
+ else
+ return stream << " " << name;
+}
+
+/**
+ * clang won't compile this in the Timer.hxx header, even with a class Idle
+ * forward definition, due to the incomplete Idle type in the template.
+ * Currently the code is just used in the Scheduler, so we keep it local.
+ *
+ * @see http://clang.llvm.org/compatibility.html#undep_incomplete
+ */
+template< typename charT, typename traits >
+std::basic_ostream<charT, traits> & operator <<(
+ std::basic_ostream<charT, traits> & stream, const Timer& timer )
+{
+ bool bIsIdle = (dynamic_cast<const Idle*>( &timer ) != nullptr);
+ stream << (bIsIdle ? "Idle " : "Timer")
+ << " a: " << timer.IsActive() << " p: " << static_cast<int>(timer.GetPriority());
+ const char *name = timer.GetDebugName();
+ if ( nullptr == name )
+ stream << " (nullptr)";
+ else
+ stream << " " << name;
+ if ( !bIsIdle )
+ stream << " " << timer.GetTimeout() << "ms";
+ stream << " (" << &timer << ")";
+ return stream;
+}
+
+template< typename charT, typename traits >
+std::basic_ostream<charT, traits> & operator <<(
+ std::basic_ostream<charT, traits> & stream, const Idle& idle )
+{
+ return stream << static_cast<const Timer*>( &idle );
+}
+
+template< typename charT, typename traits >
+std::basic_ostream<charT, traits> & operator <<(
+ std::basic_ostream<charT, traits> & stream, const ImplSchedulerData& data )
+{
+ stream << " i: " << data.mbInScheduler;
+ return stream;
+}
+
+} // end anonymous namespace
+
+unsigned int TaskStopwatch::m_nTimeSlice = TaskStopwatch::nDefaultTimeSlice;
+
+void Scheduler::ImplDeInitScheduler()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ assert( pSVData != nullptr );
+ ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx;
+
+ DBG_TESTSOLARMUTEX();
+
+ SchedulerGuard aSchedulerGuard;
+
+ int nTaskPriority = 0;
+#if OSL_DEBUG_LEVEL > 0
+ sal_uInt32 nTasks = 0;
+ for (nTaskPriority = 0; nTaskPriority < PRIO_COUNT; ++nTaskPriority)
+ {
+ ImplSchedulerData* pSchedulerData = rSchedCtx.mpFirstSchedulerData[nTaskPriority];
+ while ( pSchedulerData )
+ {
+ ++nTasks;
+ pSchedulerData = pSchedulerData->mpNext;
+ }
+ }
+ SAL_INFO( "vcl.schedule.deinit",
+ "DeInit the scheduler - pending tasks: " << nTasks );
+
+ // clean up all the sfx::SfxItemDisruptor_Impl Idles
+ Unlock();
+ ProcessEventsToIdle();
+ Lock();
+#endif
+ rSchedCtx.mbActive = false;
+
+ assert( nullptr == rSchedCtx.mpSchedulerStack );
+
+ if (rSchedCtx.mpSalTimer) rSchedCtx.mpSalTimer->Stop();
+ delete rSchedCtx.mpSalTimer;
+ rSchedCtx.mpSalTimer = nullptr;
+
+#if OSL_DEBUG_LEVEL > 0
+ sal_uInt32 nActiveTasks = 0, nIgnoredTasks = 0;
+#endif
+ nTaskPriority = 0;
+ ImplSchedulerData* pSchedulerData = nullptr;
+
+next_priority:
+ pSchedulerData = rSchedCtx.mpFirstSchedulerData[nTaskPriority];
+ while ( pSchedulerData )
+ {
+ Task *pTask = pSchedulerData->mpTask;
+ if ( pTask )
+ {
+ if ( pTask->mbActive )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ const char *sIgnored = "";
+ ++nActiveTasks;
+ // TODO: shutdown these timers before Scheduler de-init
+ // TODO: remove Task from static object
+ if ( pTask->GetDebugName() && ( false
+ || !strcmp( pTask->GetDebugName(), "desktop::Desktop m_firstRunTimer" )
+ || !strcmp( pTask->GetDebugName(), "DrawWorkStartupTimer" )
+ || !strcmp( pTask->GetDebugName(), "editeng::ImpEditEngine aOnlineSpellTimer" )
+ || !strcmp( pTask->GetDebugName(), "ImplHandleMouseMsg SalData::mpMouseLeaveTimer" )
+ || !strcmp( pTask->GetDebugName(), "sc ScModule IdleTimer" )
+ || !strcmp( pTask->GetDebugName(), "sd::CacheConfiguration maReleaseTimer" )
+ || !strcmp( pTask->GetDebugName(), "svtools::GraphicCache maReleaseTimer" )
+ || !strcmp( pTask->GetDebugName(), "svtools::GraphicObject mpSwapOutTimer" )
+ || !strcmp( pTask->GetDebugName(), "svx OLEObjCache pTimer UnloadCheck" )
+ || !strcmp( pTask->GetDebugName(), "vcl SystemDependentDataBuffer aSystemDependentDataBuffer" )
+ ))
+ {
+ sIgnored = " (ignored)";
+ ++nIgnoredTasks;
+ }
+ const Timer *timer = dynamic_cast<Timer*>( pTask );
+ if ( timer )
+ SAL_WARN( "vcl.schedule.deinit", "DeInit task: " << *timer << sIgnored );
+ else
+ SAL_WARN( "vcl.schedule.deinit", "DeInit task: " << *pTask << sIgnored );
+#endif
+ pTask->mbActive = false;
+ }
+ pTask->mpSchedulerData = nullptr;
+ pTask->SetStatic();
+ }
+ ImplSchedulerData* pDeleteSchedulerData = pSchedulerData;
+ pSchedulerData = pSchedulerData->mpNext;
+ delete pDeleteSchedulerData;
+ }
+
+ ++nTaskPriority;
+ if (nTaskPriority < PRIO_COUNT)
+ goto next_priority;
+
+#if OSL_DEBUG_LEVEL > 0
+ SAL_INFO( "vcl.schedule.deinit", "DeInit the scheduler - finished" );
+ SAL_WARN_IF( 0 != nActiveTasks, "vcl.schedule.deinit", "DeInit active tasks: "
+ << nActiveTasks << " (ignored: " << nIgnoredTasks << ")" );
+// assert( nIgnoredTasks == nActiveTasks );
+#endif
+
+ for (nTaskPriority = 0; nTaskPriority < PRIO_COUNT; ++nTaskPriority)
+ {
+ rSchedCtx.mpFirstSchedulerData[nTaskPriority] = nullptr;
+ rSchedCtx.mpLastSchedulerData[nTaskPriority] = nullptr;
+ }
+ rSchedCtx.mnTimerPeriod = InfiniteTimeoutMs;
+}
+
+void Scheduler::Lock()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ assert( pSVData != nullptr );
+ pSVData->maSchedCtx.maMutex.lock();
+}
+
+void Scheduler::Unlock()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ assert( pSVData != nullptr );
+ pSVData->maSchedCtx.maMutex.unlock();
+}
+
+/**
+ * Start a new timer if we need to for nMS duration.
+ *
+ * if this is longer than the existing duration we're
+ * waiting for, do nothing - unless bForce - which means
+ * to reset the minimum period; used by the scheduled itself.
+ */
+void Scheduler::ImplStartTimer(sal_uInt64 nMS, bool bForce, sal_uInt64 nTime)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx;
+ if ( !rSchedCtx.mbActive )
+ return;
+
+ if (!rSchedCtx.mpSalTimer)
+ {
+ rSchedCtx.mnTimerStart = 0;
+ rSchedCtx.mnTimerPeriod = InfiniteTimeoutMs;
+ rSchedCtx.mpSalTimer = pSVData->mpDefInst->CreateSalTimer();
+ rSchedCtx.mpSalTimer->SetCallback(Scheduler::CallbackTaskScheduling);
+ }
+
+ assert(SAL_MAX_UINT64 - nMS >= nTime);
+
+ sal_uInt64 nProposedTimeout = nTime + nMS;
+ sal_uInt64 nCurTimeout = ( rSchedCtx.mnTimerPeriod == InfiniteTimeoutMs )
+ ? SAL_MAX_UINT64 : rSchedCtx.mnTimerStart + rSchedCtx.mnTimerPeriod;
+
+ // Only if smaller timeout, to avoid skipping.
+ // Force instant wakeup on 0ms, if the previous period was not 0ms
+ if (bForce || nProposedTimeout < nCurTimeout || (!nMS && rSchedCtx.mnTimerPeriod))
+ {
+ SAL_INFO( "vcl.schedule", " Starting scheduler system timer (" << nMS << "ms)" );
+ rSchedCtx.mnTimerStart = nTime;
+ rSchedCtx.mnTimerPeriod = nMS;
+ rSchedCtx.mpSalTimer->Start( nMS );
+ }
+}
+
+static bool g_bDeterministicMode = false;
+
+void Scheduler::SetDeterministicMode(bool bDeterministic)
+{
+ g_bDeterministicMode = bDeterministic;
+}
+
+bool Scheduler::GetDeterministicMode()
+{
+ return g_bDeterministicMode;
+}
+
+inline void Scheduler::UpdateSystemTimer( ImplSchedulerContext &rSchedCtx,
+ const sal_uInt64 nMinPeriod,
+ const bool bForce, const sal_uInt64 nTime )
+{
+ if ( InfiniteTimeoutMs == nMinPeriod )
+ {
+ SAL_INFO("vcl.schedule", " Stopping system timer");
+ if ( rSchedCtx.mpSalTimer )
+ rSchedCtx.mpSalTimer->Stop();
+ rSchedCtx.mnTimerPeriod = nMinPeriod;
+ }
+ else
+ Scheduler::ImplStartTimer( nMinPeriod, bForce, nTime );
+}
+
+static void AppendSchedulerData( ImplSchedulerContext &rSchedCtx,
+ ImplSchedulerData * const pSchedulerData)
+{
+ assert(pSchedulerData->mpTask);
+ pSchedulerData->mePriority = pSchedulerData->mpTask->GetPriority();
+ pSchedulerData->mpNext = nullptr;
+
+ const int nTaskPriority = static_cast<int>(pSchedulerData->mePriority);
+ if (!rSchedCtx.mpLastSchedulerData[nTaskPriority])
+ {
+ rSchedCtx.mpFirstSchedulerData[nTaskPriority] = pSchedulerData;
+ rSchedCtx.mpLastSchedulerData[nTaskPriority] = pSchedulerData;
+ }
+ else
+ {
+ rSchedCtx.mpLastSchedulerData[nTaskPriority]->mpNext = pSchedulerData;
+ rSchedCtx.mpLastSchedulerData[nTaskPriority] = pSchedulerData;
+ }
+}
+
+static ImplSchedulerData* DropSchedulerData(
+ ImplSchedulerContext &rSchedCtx, ImplSchedulerData * const pPrevSchedulerData,
+ const ImplSchedulerData * const pSchedulerData, const int nTaskPriority)
+{
+ assert( pSchedulerData );
+ if ( pPrevSchedulerData )
+ assert( pPrevSchedulerData->mpNext == pSchedulerData );
+ else
+ assert(rSchedCtx.mpFirstSchedulerData[nTaskPriority] == pSchedulerData);
+
+ ImplSchedulerData * const pSchedulerDataNext = pSchedulerData->mpNext;
+ if ( pPrevSchedulerData )
+ pPrevSchedulerData->mpNext = pSchedulerDataNext;
+ else
+ rSchedCtx.mpFirstSchedulerData[nTaskPriority] = pSchedulerDataNext;
+ if ( !pSchedulerDataNext )
+ rSchedCtx.mpLastSchedulerData[nTaskPriority] = pPrevSchedulerData;
+ return pSchedulerDataNext;
+}
+
+void Scheduler::CallbackTaskScheduling()
+{
+ ImplSVData *pSVData = ImplGetSVData();
+ ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx;
+
+ DBG_TESTSOLARMUTEX();
+
+ SchedulerGuard aSchedulerGuard;
+ if ( !rSchedCtx.mbActive || InfiniteTimeoutMs == rSchedCtx.mnTimerPeriod )
+ return;
+
+ sal_uInt64 nTime = tools::Time::GetSystemTicks();
+ // Allow for decimals, so subtract in the compare (needed at least on iOS)
+ if ( nTime < rSchedCtx.mnTimerStart + rSchedCtx.mnTimerPeriod -1)
+ {
+ int nSleep = rSchedCtx.mnTimerStart + rSchedCtx.mnTimerPeriod - nTime;
+ UpdateSystemTimer(rSchedCtx, nSleep, true, nTime);
+ return;
+ }
+
+ ImplSchedulerData* pSchedulerData = nullptr;
+ ImplSchedulerData* pPrevSchedulerData = nullptr;
+ ImplSchedulerData *pMostUrgent = nullptr;
+ ImplSchedulerData *pPrevMostUrgent = nullptr;
+ int nMostUrgentPriority = 0;
+ sal_uInt64 nMinPeriod = InfiniteTimeoutMs;
+ sal_uInt64 nReadyPeriod = InfiniteTimeoutMs;
+ unsigned nTasks = 0;
+ int nTaskPriority = 0;
+
+ for (; nTaskPriority < PRIO_COUNT; ++nTaskPriority)
+ {
+ pSchedulerData = rSchedCtx.mpFirstSchedulerData[nTaskPriority];
+ pPrevSchedulerData = nullptr;
+ while (pSchedulerData)
+ {
+ ++nTasks;
+ const Timer *timer = dynamic_cast<Timer*>( pSchedulerData->mpTask );
+ if ( timer )
+ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " "
+ << pSchedulerData << " " << *pSchedulerData << " " << *timer );
+ else if ( pSchedulerData->mpTask )
+ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " "
+ << pSchedulerData << " " << *pSchedulerData
+ << " " << *pSchedulerData->mpTask );
+ else
+ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " "
+ << pSchedulerData << " " << *pSchedulerData << " (to be deleted)" );
+
+ // Should the Task be released from scheduling?
+ assert(!pSchedulerData->mbInScheduler);
+ if (!pSchedulerData->mpTask || !pSchedulerData->mpTask->IsActive())
+ {
+ ImplSchedulerData * const pSchedulerDataNext =
+ DropSchedulerData(rSchedCtx, pPrevSchedulerData, pSchedulerData, nTaskPriority);
+ if ( pSchedulerData->mpTask )
+ pSchedulerData->mpTask->mpSchedulerData = nullptr;
+ delete pSchedulerData;
+ pSchedulerData = pSchedulerDataNext;
+ continue;
+ }
+
+ assert(pSchedulerData->mpTask);
+ if (pSchedulerData->mpTask->IsActive())
+ {
+ nReadyPeriod = pSchedulerData->mpTask->UpdateMinPeriod( nTime );
+ if (ImmediateTimeoutMs == nReadyPeriod)
+ {
+ if (!pMostUrgent)
+ {
+ pPrevMostUrgent = pPrevSchedulerData;
+ pMostUrgent = pSchedulerData;
+ nMostUrgentPriority = nTaskPriority;
+ }
+ else
+ {
+ nMinPeriod = ImmediateTimeoutMs;
+ break;
+ }
+ }
+ else if (nMinPeriod > nReadyPeriod)
+ nMinPeriod = nReadyPeriod;
+ }
+
+ pPrevSchedulerData = pSchedulerData;
+ pSchedulerData = pSchedulerData->mpNext;
+ }
+
+ if (ImmediateTimeoutMs == nMinPeriod)
+ break;
+ }
+
+// tdf#148435 Apparently calling AnyInput on Mac and filtering for just input, gives
+// you window creation / re-sizing events which then trigger idle paint
+// events which then deadlock if called with the scheduler lock.
+// So since this is an optimisation, just don't do this on mac.
+#ifndef MACOSX
+ // Delay invoking tasks with idle priorities as long as there are user input or repaint events
+ // in the OS event queue. This will often effectively compress such events and repaint only
+ // once at the end, improving performance in cases such as repeated zooming with a complex document.
+ if ( pMostUrgent && pMostUrgent->mePriority >= TaskPriority::HIGH_IDLE
+ && Application::AnyInput( VclInputFlags::MOUSE | VclInputFlags::KEYBOARD | VclInputFlags::PAINT ))
+ {
+ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks()
+ << " idle priority task " << pMostUrgent << " delayed, system events pending" );
+ pMostUrgent = nullptr;
+ nMinPeriod = 0;
+ }
+#endif
+
+ if (InfiniteTimeoutMs != nMinPeriod)
+ SAL_INFO("vcl.schedule",
+ "Calculated minimum timeout as " << nMinPeriod << " of " << nTasks << " tasks");
+ UpdateSystemTimer(rSchedCtx, nMinPeriod, true, nTime);
+
+ if ( !pMostUrgent )
+ return;
+
+ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " "
+ << pMostUrgent << " invoke-in " << *pMostUrgent->mpTask );
+
+ Task *pTask = pMostUrgent->mpTask;
+
+ comphelper::ProfileZone aZone( pTask->GetDebugName() );
+
+ // prepare Scheduler object for deletion after handling
+ pTask->SetDeletionFlags();
+
+ assert(!pMostUrgent->mbInScheduler);
+ pMostUrgent->mbInScheduler = true;
+
+ // always push the stack, as we don't traverse the whole list to push later
+ DropSchedulerData(rSchedCtx, pPrevMostUrgent, pMostUrgent, nMostUrgentPriority);
+ pMostUrgent->mpNext = rSchedCtx.mpSchedulerStack;
+ rSchedCtx.mpSchedulerStack = pMostUrgent;
+ rSchedCtx.mpSchedulerStackTop = pMostUrgent;
+
+ // invoke the task
+ Unlock();
+ /*
+ * Current policy is that scheduler tasks aren't allowed to throw an exception.
+ * Because otherwise the exception is caught somewhere totally unrelated.
+ * TODO Ideally we could capture a proper backtrace and feed this into breakpad,
+ * which is do-able, but requires writing some assembly.
+ * See also SalUserEventList::DispatchUserEvents
+ */
+ try
+ {
+ pTask->Invoke();
+ }
+ catch (css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("vcl.schedule", "Uncaught");
+ std::abort();
+ }
+ catch (std::exception& e)
+ {
+ SAL_WARN("vcl.schedule", "Uncaught " << typeid(e).name() << " " << e.what());
+ std::abort();
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl.schedule", "Uncaught exception during Task::Invoke()!");
+ std::abort();
+ }
+ Lock();
+
+ assert(pMostUrgent->mbInScheduler);
+ pMostUrgent->mbInScheduler = false;
+
+ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " "
+ << pMostUrgent << " invoke-out" );
+
+ // pop the scheduler stack
+ pSchedulerData = rSchedCtx.mpSchedulerStack;
+ assert(pSchedulerData == pMostUrgent);
+ rSchedCtx.mpSchedulerStack = pSchedulerData->mpNext;
+
+ // coverity[check_after_deref : FALSE] - pMostUrgent->mpTask is initially pMostUrgent->mpTask, but Task::Invoke can clear it
+ const bool bTaskAlive = pMostUrgent->mpTask && pMostUrgent->mpTask->IsActive();
+ if (!bTaskAlive)
+ {
+ if (pMostUrgent->mpTask)
+ pMostUrgent->mpTask->mpSchedulerData = nullptr;
+ delete pMostUrgent;
+ }
+ else
+ AppendSchedulerData(rSchedCtx, pMostUrgent);
+
+ // this just happens for nested calls, which renders all accounting
+ // invalid, so we just enforce a rescheduling!
+ if (rSchedCtx.mpSchedulerStackTop != pSchedulerData)
+ {
+ UpdateSystemTimer( rSchedCtx, ImmediateTimeoutMs, true,
+ tools::Time::GetSystemTicks() );
+ }
+ else if (bTaskAlive)
+ {
+ pMostUrgent->mnUpdateTime = nTime;
+ nReadyPeriod = pMostUrgent->mpTask->UpdateMinPeriod( nTime );
+ if ( nMinPeriod > nReadyPeriod )
+ nMinPeriod = nReadyPeriod;
+ UpdateSystemTimer( rSchedCtx, nMinPeriod, false, nTime );
+ }
+}
+
+void Scheduler::Wakeup()
+{
+ Scheduler::ImplStartTimer( 0, false, tools::Time::GetSystemTicks() );
+}
+
+void Task::StartTimer( sal_uInt64 nMS )
+{
+ Scheduler::ImplStartTimer( nMS, false, tools::Time::GetSystemTicks() );
+}
+
+void Task::SetDeletionFlags()
+{
+ mbActive = false;
+}
+
+void Task::Start(const bool bStartTimer)
+{
+ ImplSVData *const pSVData = ImplGetSVData();
+ ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx;
+
+ SchedulerGuard aSchedulerGuard;
+ if ( !rSchedCtx.mbActive )
+ return;
+
+ // is the task scheduled in the correct priority queue?
+ // if not we have to get a new data object, as we don't want to traverse
+ // the whole list to move the data to the correct list, as the task list
+ // is just single linked.
+ // Task priority doesn't change that often AFAIK, or we might need to
+ // start caching ImplSchedulerData objects.
+ if (mpSchedulerData && mpSchedulerData->mePriority != mePriority)
+ {
+ mpSchedulerData->mpTask = nullptr;
+ mpSchedulerData = nullptr;
+ }
+ mbActive = true;
+
+ if ( !mpSchedulerData )
+ {
+ // insert Task
+ ImplSchedulerData* pSchedulerData = new ImplSchedulerData;
+ pSchedulerData->mpTask = this;
+ pSchedulerData->mbInScheduler = false;
+ // mePriority is set in AppendSchedulerData
+ mpSchedulerData = pSchedulerData;
+
+ AppendSchedulerData( rSchedCtx, pSchedulerData );
+ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks()
+ << " " << mpSchedulerData << " added " << *this );
+ }
+ else
+ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks()
+ << " " << mpSchedulerData << " restarted " << *this );
+
+ mpSchedulerData->mnUpdateTime = tools::Time::GetSystemTicks();
+
+ if (bStartTimer)
+ Task::StartTimer(0);
+}
+
+void Task::Stop()
+{
+ SAL_INFO_IF( mbActive, "vcl.schedule", tools::Time::GetSystemTicks()
+ << " " << mpSchedulerData << " stopped " << *this );
+ mbActive = false;
+}
+
+void Task::SetPriority(TaskPriority ePriority)
+{
+ // you don't actually need to call Stop() before but Start() after, but we
+ // can't check that and don't know when Start() should be called.
+ SAL_WARN_IF(mpSchedulerData && mbActive, "vcl.schedule",
+ "Stop the task before changing the priority, as it will just "
+ "change after the task was scheduled with the old prio!");
+ mePriority = ePriority;
+}
+
+Task& Task::operator=( const Task& rTask )
+{
+ if(this == &rTask)
+ return *this;
+
+ if ( IsActive() )
+ Stop();
+
+ mbActive = false;
+ mePriority = rTask.mePriority;
+
+ if ( rTask.IsActive() )
+ Start();
+
+ return *this;
+}
+
+Task::Task( const char *pDebugName )
+ : mpSchedulerData( nullptr )
+ , mpDebugName( pDebugName )
+ , mePriority( TaskPriority::DEFAULT )
+ , mbActive( false )
+ , mbStatic( false )
+{
+ assert(mpDebugName);
+}
+
+Task::Task( const Task& rTask )
+ : mpSchedulerData( nullptr )
+ , mpDebugName( rTask.mpDebugName )
+ , mePriority( rTask.mePriority )
+ , mbActive( false )
+ , mbStatic( false )
+{
+ assert(mpDebugName);
+ if ( rTask.IsActive() )
+ Start();
+}
+
+Task::~Task() COVERITY_NOEXCEPT_FALSE
+{
+ if ( !IsStatic() )
+ {
+ SchedulerGuard aSchedulerGuard;
+ if ( mpSchedulerData )
+ mpSchedulerData->mpTask = nullptr;
+ }
+ else
+ assert(nullptr == mpSchedulerData || utl::ConfigManager::IsFuzzing());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/session.cxx b/vcl/source/app/session.cxx
new file mode 100644
index 000000000..a1b37b27d
--- /dev/null
+++ b/vcl/source/app/session.cxx
@@ -0,0 +1,399 @@
+/* -*- 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 <memory>
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/compbase.hxx>
+
+#include <tools/diagnose_ex.h>
+#include <vcl/svapp.hxx>
+
+#include <factory.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+#include <salsession.hxx>
+
+#include <com/sun/star/frame/XSessionManagerClient.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/frame/XSessionManagerListener2.hpp>
+
+#include <vector>
+
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::frame;
+
+SalSession::~SalSession()
+{
+}
+
+namespace {
+
+class VCLSession:
+ private cppu::BaseMutex,
+ public cppu::WeakComponentImplHelper < XSessionManagerClient >
+{
+ struct Listener
+ {
+ css::uno::Reference< XSessionManagerListener > m_xListener;
+ bool m_bInteractionRequested;
+ bool m_bInteractionDone;
+ bool m_bSaveDone;
+
+ explicit Listener( const css::uno::Reference< XSessionManagerListener >& xListener )
+ : m_xListener( xListener ),
+ m_bInteractionRequested( false ),
+ m_bInteractionDone( false ),
+ m_bSaveDone( false )
+ {}
+ };
+
+ std::vector< Listener > m_aListeners;
+ std::unique_ptr< SalSession > m_xSession;
+ bool m_bInteractionRequested;
+ bool m_bInteractionGranted;
+ bool m_bInteractionDone;
+ bool m_bSaveDone;
+
+ static void SalSessionEventProc( void* pData, SalSessionEvent* pEvent );
+
+ virtual ~VCLSession() override {}
+
+ virtual void SAL_CALL addSessionManagerListener( const css::uno::Reference< XSessionManagerListener >& xListener ) override;
+ virtual void SAL_CALL removeSessionManagerListener( const css::uno::Reference< XSessionManagerListener>& xListener ) override;
+ virtual void SAL_CALL queryInteraction( const css::uno::Reference< XSessionManagerListener >& xListener ) override;
+ virtual void SAL_CALL interactionDone( const css::uno::Reference< XSessionManagerListener >& xListener ) override;
+ virtual void SAL_CALL saveDone( const css::uno::Reference< XSessionManagerListener >& xListener ) override;
+ virtual sal_Bool SAL_CALL cancelShutdown() override;
+
+ void SAL_CALL disposing() override;
+
+ void callSaveRequested( bool bShutdown );
+ void callShutdownCancelled();
+ void callInteractionGranted( bool bGranted );
+ void callQuit();
+
+public:
+ VCLSession();
+};
+
+}
+
+VCLSession::VCLSession()
+ : cppu::WeakComponentImplHelper< XSessionManagerClient >( m_aMutex ),
+ m_xSession( ImplGetSVData()->mpDefInst->CreateSalSession() ),
+ m_bInteractionRequested( false ),
+ m_bInteractionGranted( false ),
+ m_bInteractionDone( false ),
+ m_bSaveDone( false )
+{
+ SAL_INFO("vcl.se", "VCLSession::VCLSession" );
+
+ if (m_xSession)
+ m_xSession->SetCallback( SalSessionEventProc, this );
+}
+
+void VCLSession::callSaveRequested( bool bShutdown )
+{
+ SAL_INFO("vcl.se", "VCLSession::callSaveRequested" );
+
+ std::vector< Listener > aListeners;
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ // reset listener states
+ for (auto & listener : m_aListeners) {
+ listener.m_bSaveDone = listener.m_bInteractionRequested = listener.m_bInteractionDone = false;
+ }
+
+ // copy listener vector since calling a listener may remove it.
+ aListeners = m_aListeners;
+ // set back interaction state
+ m_bSaveDone = false;
+ m_bInteractionDone = false;
+ // without session we assume UI is always possible,
+ // so it was requested and granted
+ m_bInteractionRequested = m_bInteractionGranted = !m_xSession;
+
+ // answer the session manager even if no listeners available anymore
+ SAL_WARN_IF( aListeners.empty(), "vcl.se", "saveRequested but no listeners !" );
+
+ SAL_INFO("vcl.se.debug", " aListeners.empty() = " << (aListeners.empty() ? "true" : "false") <<
+ ", bShutdown = " << (bShutdown ? "true" : "false"));
+ if( aListeners.empty() )
+ {
+ if (m_xSession)
+ m_xSession->saveDone();
+ return;
+ }
+ }
+
+ SolarMutexReleaser aReleaser;
+ for (auto const & listener: aListeners)
+ listener.m_xListener->doSave( bShutdown, false/*bCancelable*/ );
+}
+
+void VCLSession::callInteractionGranted( bool bInteractionGranted )
+{
+ SAL_INFO("vcl.se", "VCLSession::callInteractionGranted" );
+
+ std::vector< Listener > aListeners;
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ // copy listener vector since calling a listener may remove it.
+ for (auto const & listener: m_aListeners)
+ if( listener.m_bInteractionRequested )
+ aListeners.push_back( listener );
+
+ m_bInteractionGranted = bInteractionGranted;
+
+ // answer the session manager even if no listeners available anymore
+ SAL_WARN_IF( aListeners.empty(), "vcl.se", "interactionGranted but no listeners !" );
+
+ SAL_INFO("vcl.se.debug", " aListeners.empty() = " << (aListeners.empty() ? "true" : "false") <<
+ ", bInteractionGranted = " << (bInteractionGranted ? "true" : "false"));
+ if( aListeners.empty() )
+ {
+ if (m_xSession)
+ m_xSession->interactionDone();
+ return;
+ }
+ }
+
+ SolarMutexReleaser aReleaser;
+ for (auto const & listener: aListeners)
+ listener.m_xListener->approveInteraction( bInteractionGranted );
+}
+
+void VCLSession::callShutdownCancelled()
+{
+ SAL_INFO("vcl.se", "VCLSession::callShutdownCancelled");
+
+ std::vector< Listener > aListeners;
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ // copy listener vector since calling a listener may remove it.
+ aListeners = m_aListeners;
+ // set back interaction state
+ m_bInteractionRequested = m_bInteractionDone = m_bInteractionGranted = false;
+ }
+
+ SolarMutexReleaser aReleaser;
+ for (auto const & listener: aListeners)
+ listener.m_xListener->shutdownCanceled();
+}
+
+void VCLSession::callQuit()
+{
+ SAL_INFO("vcl.se", "VCLSession::callQuit");
+
+ std::vector< Listener > aListeners;
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ // copy listener vector since calling a listener may remove it.
+ aListeners = m_aListeners;
+ // set back interaction state
+ m_bInteractionRequested = m_bInteractionDone = m_bInteractionGranted = false;
+ }
+
+ SolarMutexReleaser aReleaser;
+ for (auto const & listener: aListeners)
+ {
+ css::uno::Reference< XSessionManagerListener2 > xListener2( listener.m_xListener, UNO_QUERY );
+ if( xListener2.is() )
+ xListener2->doQuit();
+ }
+}
+
+void VCLSession::SalSessionEventProc( void* pData, SalSessionEvent* pEvent )
+{
+ SAL_INFO("vcl.se", "VCLSession::SalSessionEventProc");
+
+ VCLSession * pThis = static_cast< VCLSession * >( pData );
+ switch( pEvent->m_eType )
+ {
+ case Interaction:
+ {
+ SAL_INFO("vcl.se.debug", " EventProcType = Interaction");
+ SalSessionInteractionEvent* pIEv = static_cast<SalSessionInteractionEvent*>(pEvent);
+ pThis->callInteractionGranted( pIEv->m_bInteractionGranted );
+ }
+ break;
+ case SaveRequest:
+ {
+ SAL_INFO("vcl.se.debug", " EventProcType = SaveRequest");
+ SalSessionSaveRequestEvent* pSEv = static_cast<SalSessionSaveRequestEvent*>(pEvent);
+ pThis->callSaveRequested( pSEv->m_bShutdown );
+ }
+ break;
+ case ShutdownCancel:
+ SAL_INFO("vcl.se.debug", " EventProcType = ShutdownCancel");
+ pThis->callShutdownCancelled();
+ break;
+ case Quit:
+ SAL_INFO("vcl.se.debug", " EventProcType = Quit");
+ pThis->callQuit();
+ break;
+ }
+}
+
+void SAL_CALL VCLSession::addSessionManagerListener( const css::uno::Reference<XSessionManagerListener>& xListener )
+{
+ SAL_INFO("vcl.se", "VCLSession::addSessionManagerListener" );
+
+ osl::MutexGuard aGuard( m_aMutex );
+
+ SAL_INFO("vcl.se.debug", " m_aListeners.size() = " << m_aListeners.size() );
+ m_aListeners.emplace_back( xListener );
+}
+
+void SAL_CALL VCLSession::removeSessionManagerListener( const css::uno::Reference<XSessionManagerListener>& xListener )
+{
+ SAL_INFO("vcl.se", "VCLSession::removeSessionManagerListener" );
+
+ osl::MutexGuard aGuard( m_aMutex );
+
+ SAL_INFO("vcl.se.debug", " m_aListeners.size() = " << m_aListeners.size() );
+
+ m_aListeners.erase(std::remove_if(m_aListeners.begin(), m_aListeners.end(), [&](Listener& listener) {return xListener == listener.m_xListener;}), m_aListeners.end());
+}
+
+void SAL_CALL VCLSession::queryInteraction( const css::uno::Reference<XSessionManagerListener>& xListener )
+{
+ SAL_INFO("vcl.se", "VCLSession::queryInteraction");
+
+ SAL_INFO("vcl.se.debug", " m_bInteractionGranted = " << (m_bInteractionGranted ? "true" : "false") <<
+ ", m_bInteractionRequested = "<< (m_bInteractionRequested ? "true" : "false"));
+ if( m_bInteractionGranted )
+ {
+ if( m_bInteractionDone )
+ xListener->approveInteraction( false );
+ else
+ xListener->approveInteraction( true );
+ return;
+ }
+
+ osl::MutexGuard aGuard( m_aMutex );
+ if( ! m_bInteractionRequested )
+ {
+ m_xSession->queryInteraction();
+ m_bInteractionRequested = true;
+ }
+ for (auto & listener: m_aListeners)
+ {
+ if( listener.m_xListener == xListener )
+ {
+ SAL_INFO("vcl.se.debug", " listener.m_xListener == xListener");
+ listener.m_bInteractionRequested = true;
+ listener.m_bInteractionDone = false;
+ }
+ }
+}
+
+void SAL_CALL VCLSession::interactionDone( const css::uno::Reference< XSessionManagerListener >& xListener )
+{
+ SAL_INFO("vcl.se", "VCLSession::interactionDone");
+
+ osl::MutexGuard aGuard( m_aMutex );
+ int nRequested = 0, nDone = 0;
+ for (auto & listener: m_aListeners)
+ {
+ if( listener.m_bInteractionRequested )
+ {
+ nRequested++;
+ if( xListener == listener.m_xListener )
+ listener.m_bInteractionDone = true;
+ }
+ if( listener.m_bInteractionDone )
+ nDone++;
+ }
+
+ SAL_INFO("vcl.se.debug", " nDone = " << nDone <<
+ ", nRequested =" << nRequested);
+ if( nDone == nRequested && nDone > 0 )
+ {
+ m_bInteractionDone = true;
+ if (m_xSession)
+ m_xSession->interactionDone();
+ }
+}
+
+void SAL_CALL VCLSession::saveDone( const css::uno::Reference< XSessionManagerListener >& xListener )
+{
+ SAL_INFO("vcl.se", "VCLSession::saveDone");
+
+ osl::MutexGuard aGuard( m_aMutex );
+
+ bool bSaveDone = true;
+ for (auto & listener: m_aListeners)
+ {
+ if( listener.m_xListener == xListener )
+ listener.m_bSaveDone = true;
+ if( ! listener.m_bSaveDone )
+ bSaveDone = false;
+ }
+
+ SAL_INFO("vcl.se.debug", " bSaveDone = " << (bSaveDone ? "true" : "false"));
+
+ if( bSaveDone && !m_bSaveDone )
+ {
+ m_bSaveDone = true;
+ if (m_xSession)
+ m_xSession->saveDone();
+ }
+}
+
+sal_Bool SAL_CALL VCLSession::cancelShutdown()
+{
+ SAL_INFO("vcl.se", "VCLSession::cancelShutdown");
+
+ return m_xSession && m_xSession->cancelShutdown();
+}
+
+void VCLSession::disposing() {
+ SAL_INFO("vcl.se", "VCLSession::disposing");
+
+ std::vector<Listener> vector;
+ {
+ osl::MutexGuard g(m_aMutex);
+ vector.swap(m_aListeners);
+ }
+ css::lang::EventObject src(static_cast<OWeakObject *>(this));
+ for (auto const & listener: vector) {
+ try {
+ listener.m_xListener->disposing(src);
+ SAL_INFO("vcl.se.debug", " call Listener disposing");
+ } catch (css::uno::RuntimeException &) {
+ TOOLS_WARN_EXCEPTION("vcl.se", "ignoring");
+ }
+ }
+}
+
+// service implementation
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_frame_VCLSessionManagerClient_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new VCLSession);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/settings.cxx b/vcl/source/app/settings.cxx
new file mode 100644
index 000000000..f17c8286f
--- /dev/null
+++ b/vcl/source/app/settings.cxx
@@ -0,0 +1,3250 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <config_folders.h>
+
+#include <officecfg/Office/Common.hxx>
+
+#ifdef _WIN32
+#include <win/svsys.h>
+#endif
+
+#include <comphelper/processfactory.hxx>
+#include <rtl/bootstrap.hxx>
+
+#include <i18nlangtag/mslangid.hxx>
+#include <i18nlangtag/languagetag.hxx>
+
+#include <comphelper/lok.hxx>
+
+#include <vcl/graphicfilter.hxx>
+#include <IconThemeScanner.hxx>
+#include <IconThemeSelector.hxx>
+#include <vcl/IconThemeInfo.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/event.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/i18nhelp.hxx>
+#include <configsettings.hxx>
+#include <vcl/outdev.hxx>
+
+#include <unotools/fontcfg.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/confignode.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/syslocale.hxx>
+#include <unotools/syslocaleoptions.hxx>
+
+using namespace ::com::sun::star;
+
+#include <svdata.hxx>
+
+struct ImplMouseData
+{
+ MouseSettingsOptions mnOptions = MouseSettingsOptions::NONE;
+ sal_uInt64 mnDoubleClkTime = 500;
+ sal_Int32 mnDoubleClkWidth = 2;
+ sal_Int32 mnDoubleClkHeight = 2;
+ sal_Int32 mnStartDragWidth = 2 ;
+ sal_Int32 mnStartDragHeight = 2;
+ sal_Int32 mnButtonRepeat = 90;
+ sal_Int32 mnMenuDelay = 150;
+ MouseFollowFlags mnFollow = MouseFollowFlags::Menu;
+ MouseMiddleButtonAction mnMiddleButtonAction= MouseMiddleButtonAction::AutoScroll;
+ MouseWheelBehaviour mnWheelBehavior = MouseWheelBehaviour::ALWAYS;
+};
+
+struct ImplStyleData
+{
+ ImplStyleData();
+ ImplStyleData( const ImplStyleData& rData );
+
+ void SetStandardStyles();
+
+ Color maActiveBorderColor;
+ Color maActiveColor;
+ Color maActiveTextColor;
+ Color maAlternatingRowColor;
+ Color maDefaultButtonTextColor;
+ Color maButtonTextColor;
+ Color maDefaultActionButtonTextColor;
+ Color maActionButtonTextColor;
+ Color maFlatButtonTextColor;
+ Color maDefaultButtonRolloverTextColor;
+ Color maButtonRolloverTextColor;
+ Color maDefaultActionButtonRolloverTextColor;
+ Color maActionButtonRolloverTextColor;
+ Color maFlatButtonRolloverTextColor;
+ Color maDefaultButtonPressedRolloverTextColor;
+ Color maButtonPressedRolloverTextColor;
+ Color maDefaultActionButtonPressedRolloverTextColor;
+ Color maActionButtonPressedRolloverTextColor;
+ Color maFlatButtonPressedRolloverTextColor;
+ Color maCheckedColor;
+ Color maDarkShadowColor;
+ Color maDeactiveBorderColor;
+ Color maDeactiveColor;
+ Color maDeactiveTextColor;
+ Color maDialogColor;
+ Color maDialogTextColor;
+ Color maDisableColor;
+ Color maFaceColor;
+ Color maFieldColor;
+ Color maFieldTextColor;
+ Color maFieldRolloverTextColor;
+ Color maFontColor;
+ Color maGroupTextColor;
+ Color maHelpColor;
+ Color maHelpTextColor;
+ Color maHighlightColor;
+ Color maHighlightTextColor;
+ Color maLabelTextColor;
+ Color maLightBorderColor;
+ Color maLightColor;
+ Color maLinkColor;
+ Color maMenuBarColor;
+ Color maMenuBarRolloverColor;
+ Color maMenuBorderColor;
+ Color maMenuColor;
+ Color maMenuHighlightColor;
+ Color maMenuHighlightTextColor;
+ Color maMenuTextColor;
+ Color maMenuBarTextColor;
+ Color maMenuBarRolloverTextColor;
+ Color maMenuBarHighlightTextColor;
+ Color maMonoColor;
+ Color maRadioCheckTextColor;
+ Color maShadowColor;
+ Color maWarningColor;
+ Color maVisitedLinkColor;
+ Color maToolTextColor;
+ Color maWindowColor;
+ Color maWindowTextColor;
+ Color maWorkspaceColor;
+ Color maActiveTabColor;
+ Color maInactiveTabColor;
+ Color maTabTextColor;
+ Color maTabRolloverTextColor;
+ Color maTabHighlightTextColor;
+ vcl::Font maAppFont;
+ vcl::Font maHelpFont;
+ vcl::Font maTitleFont;
+ vcl::Font maFloatTitleFont;
+ vcl::Font maMenuFont;
+ vcl::Font maToolFont;
+ vcl::Font maLabelFont;
+ vcl::Font maRadioCheckFont;
+ vcl::Font maPushButtonFont;
+ vcl::Font maFieldFont;
+ vcl::Font maIconFont;
+ vcl::Font maTabFont;
+ vcl::Font maGroupFont;
+ sal_Int32 mnTitleHeight;
+ sal_Int32 mnFloatTitleHeight;
+ sal_Int32 mnScrollBarSize;
+ sal_Int32 mnSpinSize;
+ sal_Int32 mnCursorSize;
+ sal_Int32 mnAntialiasedMin;
+ sal_uInt64 mnCursorBlinkTime;
+ DragFullOptions mnDragFullOptions;
+ SelectionOptions mnSelectionOptions;
+ DisplayOptions mnDisplayOptions;
+ ToolbarIconSize mnToolbarIconSize;
+ bool mnUseFlatMenus;
+ StyleSettingsOptions mnOptions;
+ bool mbHighContrast;
+ bool mbUseSystemUIFonts;
+ /**
+ * Disabling AA doesn't actually disable AA of fonts, instead it is taken
+ * from system settings.
+ */
+ bool mbUseFontAAFromSystem;
+ bool mbAutoMnemonic;
+ TriState meUseImagesInMenus;
+ bool mnUseFlatBorders;
+ bool mbPreferredUseImagesInMenus;
+ sal_Int32 mnMinThumbSize;
+ std::shared_ptr<vcl::IconThemeScanner>
+ mIconThemeScanner;
+ std::shared_ptr<vcl::IconThemeSelector>
+ mIconThemeSelector;
+
+ OUString mIconTheme;
+ bool mbSkipDisabledInMenus;
+ bool mbHideDisabledMenuItems;
+ bool mbPreferredContextMenuShortcuts;
+ TriState meContextMenuShortcuts;
+ //mbPrimaryButtonWarpsSlider == true for "jump to here" behavior for primary button, otherwise
+ //primary means scroll by single page. Secondary button takes the alternative behaviour
+ bool mbPrimaryButtonWarpsSlider;
+ DialogStyle maDialogStyle;
+
+ sal_uInt16 mnEdgeBlending;
+ Color maEdgeBlendingTopLeftColor;
+ Color maEdgeBlendingBottomRightColor;
+ sal_uInt16 mnListBoxMaximumLineCount;
+ sal_uInt16 mnColorValueSetColumnCount;
+ Size maListBoxPreviewDefaultLogicSize;
+ Size maListBoxPreviewDefaultPixelSize;
+ bool mbPreviewUsesCheckeredBackground;
+
+ OUString maPersonaHeaderFooter; ///< Cache the settings to detect changes.
+
+ BitmapEx maPersonaHeaderBitmap; ///< Cache the header bitmap.
+ BitmapEx maPersonaFooterBitmap; ///< Cache the footer bitmap.
+ std::optional<Color> maPersonaMenuBarTextColor; ///< Cache the menubar color.
+};
+
+struct ImplMiscData
+{
+ ImplMiscData();
+ TriState mnEnableATT;
+ bool mbEnableLocalizedDecimalSep;
+ TriState mnDisablePrinting;
+};
+
+struct ImplHelpData
+{
+ sal_Int32 mnTipTimeout = 3000;
+};
+
+struct ImplAllSettingsData
+{
+ ImplAllSettingsData();
+ ImplAllSettingsData( const ImplAllSettingsData& rData );
+ ~ImplAllSettingsData();
+
+ MouseSettings maMouseSettings;
+ StyleSettings maStyleSettings;
+ MiscSettings maMiscSettings;
+ HelpSettings maHelpSettings;
+ LanguageTag maLocale;
+ LanguageTag maUILocale;
+ std::unique_ptr<LocaleDataWrapper> mpLocaleDataWrapper;
+ std::unique_ptr<LocaleDataWrapper> mpUILocaleDataWrapper;
+ std::unique_ptr<LocaleDataWrapper> mpNeutralLocaleDataWrapper;
+ std::unique_ptr<vcl::I18nHelper> mpI18nHelper;
+ std::unique_ptr<vcl::I18nHelper> mpUII18nHelper;
+ SvtSysLocale maSysLocale;
+};
+
+void
+MouseSettings::SetOptions(MouseSettingsOptions nOptions)
+{
+ CopyData();
+ mxData->mnOptions = nOptions;
+}
+
+MouseSettingsOptions
+MouseSettings::GetOptions() const
+{
+ return mxData->mnOptions;
+}
+
+void
+MouseSettings::SetDoubleClickTime( sal_uInt64 nDoubleClkTime )
+{
+ CopyData();
+ mxData->mnDoubleClkTime = nDoubleClkTime;
+}
+
+sal_uInt64
+MouseSettings::GetDoubleClickTime() const
+{
+ return mxData->mnDoubleClkTime;
+}
+
+void
+MouseSettings::SetDoubleClickWidth( sal_Int32 nDoubleClkWidth )
+{
+ CopyData();
+ mxData->mnDoubleClkWidth = nDoubleClkWidth;
+}
+
+sal_Int32
+MouseSettings::GetDoubleClickWidth() const
+{
+ return mxData->mnDoubleClkWidth;
+}
+
+void
+MouseSettings::SetDoubleClickHeight( sal_Int32 nDoubleClkHeight )
+{
+ CopyData();
+ mxData->mnDoubleClkHeight = nDoubleClkHeight;
+}
+
+sal_Int32
+MouseSettings::GetDoubleClickHeight() const
+{
+ return mxData->mnDoubleClkHeight;
+}
+
+void
+MouseSettings::SetStartDragWidth( sal_Int32 nDragWidth )
+{
+ CopyData();
+ mxData->mnStartDragWidth = nDragWidth;
+}
+
+sal_Int32
+MouseSettings::GetStartDragWidth() const
+{
+ return mxData->mnStartDragWidth;
+}
+
+void
+MouseSettings::SetStartDragHeight( sal_Int32 nDragHeight )
+{
+ CopyData();
+ mxData->mnStartDragHeight = nDragHeight;
+}
+
+sal_Int32
+MouseSettings::GetStartDragHeight() const
+{
+ return mxData->mnStartDragHeight;
+}
+
+sal_uInt16
+MouseSettings::GetStartDragCode()
+{
+ return MOUSE_LEFT;
+}
+
+sal_uInt16
+MouseSettings::GetContextMenuCode()
+{
+ return MOUSE_RIGHT;
+}
+
+sal_uInt16
+MouseSettings::GetContextMenuClicks()
+{
+ return 1;
+}
+
+sal_Int32
+MouseSettings::GetScrollRepeat()
+{
+ return 100;
+}
+
+sal_Int32
+MouseSettings::GetButtonStartRepeat()
+{
+ return 370;
+}
+
+void
+MouseSettings::SetButtonRepeat( sal_Int32 nRepeat )
+{
+ CopyData();
+ mxData->mnButtonRepeat = nRepeat;
+}
+
+sal_Int32
+MouseSettings::GetButtonRepeat() const
+{
+ return mxData->mnButtonRepeat;
+}
+
+sal_Int32
+MouseSettings::GetActionDelay()
+{
+ return 250;
+}
+
+void
+MouseSettings::SetMenuDelay( sal_Int32 nDelay )
+{
+ CopyData();
+ mxData->mnMenuDelay = nDelay;
+}
+
+sal_Int32
+MouseSettings::GetMenuDelay() const
+{
+ return mxData->mnMenuDelay;
+}
+
+void
+MouseSettings::SetFollow( MouseFollowFlags nFollow )
+{
+ CopyData();
+ mxData->mnFollow = nFollow;
+}
+
+MouseFollowFlags
+MouseSettings::GetFollow() const
+{
+ return mxData->mnFollow;
+}
+
+void
+MouseSettings::SetMiddleButtonAction( MouseMiddleButtonAction nAction )
+{
+ CopyData();
+ mxData->mnMiddleButtonAction = nAction;
+}
+
+MouseMiddleButtonAction
+MouseSettings::GetMiddleButtonAction() const
+{
+ return mxData->mnMiddleButtonAction;
+}
+
+void
+MouseSettings::SetWheelBehavior( MouseWheelBehaviour nBehavior )
+{
+ CopyData();
+ mxData->mnWheelBehavior = nBehavior;
+}
+
+MouseWheelBehaviour
+MouseSettings::GetWheelBehavior() const
+{
+ return mxData->mnWheelBehavior;
+}
+
+bool
+MouseSettings::operator !=( const MouseSettings& rSet ) const
+{
+ return !(*this == rSet);
+}
+
+MouseSettings::MouseSettings()
+ : mxData(std::make_shared<ImplMouseData>())
+{
+}
+
+void MouseSettings::CopyData()
+{
+ // copy if other references exist
+ if (mxData.use_count() > 1)
+ {
+ mxData = std::make_shared<ImplMouseData>(*mxData);
+ }
+}
+
+bool MouseSettings::operator ==( const MouseSettings& rSet ) const
+{
+ if ( mxData == rSet.mxData )
+ return true;
+
+ return
+ (mxData->mnOptions == rSet.mxData->mnOptions) &&
+ (mxData->mnDoubleClkTime == rSet.mxData->mnDoubleClkTime) &&
+ (mxData->mnDoubleClkWidth == rSet.mxData->mnDoubleClkWidth) &&
+ (mxData->mnDoubleClkHeight == rSet.mxData->mnDoubleClkHeight) &&
+ (mxData->mnStartDragWidth == rSet.mxData->mnStartDragWidth) &&
+ (mxData->mnStartDragHeight == rSet.mxData->mnStartDragHeight) &&
+ (mxData->mnMiddleButtonAction == rSet.mxData->mnMiddleButtonAction) &&
+ (mxData->mnButtonRepeat == rSet.mxData->mnButtonRepeat) &&
+ (mxData->mnMenuDelay == rSet.mxData->mnMenuDelay) &&
+ (mxData->mnFollow == rSet.mxData->mnFollow) &&
+ (mxData->mnWheelBehavior == rSet.mxData->mnWheelBehavior );
+}
+
+ImplStyleData::ImplStyleData() :
+ mnScrollBarSize(16),
+ mnSpinSize(16),
+ mnCursorSize(2),
+ mnAntialiasedMin(0),
+ mnCursorBlinkTime(STYLE_CURSOR_NOBLINKTIME),
+ mnDragFullOptions(DragFullOptions::All),
+ mnSelectionOptions(SelectionOptions::NONE),
+ mnDisplayOptions(DisplayOptions::NONE),
+ mnToolbarIconSize(ToolbarIconSize::Unknown),
+ mnOptions(StyleSettingsOptions::NONE),
+ mbAutoMnemonic(true),
+ meUseImagesInMenus(TRISTATE_INDET),
+ mnMinThumbSize(16),
+ mIconThemeSelector(std::make_shared<vcl::IconThemeSelector>()),
+ meContextMenuShortcuts(TRISTATE_INDET),
+ mnEdgeBlending(35),
+ maEdgeBlendingTopLeftColor(Color(0xC0, 0xC0, 0xC0)),
+ maEdgeBlendingBottomRightColor(Color(0x40, 0x40, 0x40)),
+ mnListBoxMaximumLineCount(25),
+ // For some reason this isn't actually the column count that gets used, at least on iOS, but
+ // instead what SvtAccessibilityOptions_Impl::GetColorValueSetColumnCount() in
+ // svtools/source/config/accessibilityoptions.cxx returns.
+ mnColorValueSetColumnCount(12),
+#ifdef IOS
+ maListBoxPreviewDefaultLogicSize(Size(30, 30)),
+#else
+ maListBoxPreviewDefaultLogicSize(Size(15, 7)),
+#endif
+ maListBoxPreviewDefaultPixelSize(Size(0, 0)), // on-demand calculated in GetListBoxPreviewDefaultPixelSize(),
+ mbPreviewUsesCheckeredBackground(true)
+{
+ SetStandardStyles();
+}
+
+ImplStyleData::ImplStyleData( const ImplStyleData& rData ) :
+ maActiveBorderColor( rData.maActiveBorderColor ),
+ maActiveColor( rData.maActiveColor ),
+ maActiveTextColor( rData.maActiveTextColor ),
+ maAlternatingRowColor( rData.maAlternatingRowColor ),
+ maDefaultButtonTextColor( rData.maDefaultButtonTextColor ),
+ maButtonTextColor( rData.maButtonTextColor ),
+ maDefaultActionButtonTextColor( rData.maDefaultActionButtonTextColor ),
+ maActionButtonTextColor( rData.maActionButtonTextColor ),
+ maFlatButtonTextColor( rData.maFlatButtonTextColor ),
+ maDefaultButtonRolloverTextColor( rData.maDefaultButtonRolloverTextColor ),
+ maButtonRolloverTextColor( rData.maButtonRolloverTextColor ),
+ maDefaultActionButtonRolloverTextColor( rData.maDefaultActionButtonRolloverTextColor ),
+ maActionButtonRolloverTextColor( rData.maActionButtonRolloverTextColor ),
+ maFlatButtonRolloverTextColor( rData.maFlatButtonRolloverTextColor ),
+ maDefaultButtonPressedRolloverTextColor( rData.maDefaultButtonPressedRolloverTextColor ),
+ maButtonPressedRolloverTextColor( rData.maButtonPressedRolloverTextColor ),
+ maDefaultActionButtonPressedRolloverTextColor( rData.maDefaultActionButtonPressedRolloverTextColor ),
+ maActionButtonPressedRolloverTextColor( rData.maActionButtonPressedRolloverTextColor ),
+ maFlatButtonPressedRolloverTextColor( rData.maFlatButtonPressedRolloverTextColor ),
+ maCheckedColor( rData.maCheckedColor ),
+ maDarkShadowColor( rData.maDarkShadowColor ),
+ maDeactiveBorderColor( rData.maDeactiveBorderColor ),
+ maDeactiveColor( rData.maDeactiveColor ),
+ maDeactiveTextColor( rData.maDeactiveTextColor ),
+ maDialogColor( rData.maDialogColor ),
+ maDialogTextColor( rData.maDialogTextColor ),
+ maDisableColor( rData.maDisableColor ),
+ maFaceColor( rData.maFaceColor ),
+ maFieldColor( rData.maFieldColor ),
+ maFieldTextColor( rData.maFieldTextColor ),
+ maFieldRolloverTextColor( rData.maFieldRolloverTextColor ),
+ maFontColor( rData.maFontColor ),
+ maGroupTextColor( rData.maGroupTextColor ),
+ maHelpColor( rData.maHelpColor ),
+ maHelpTextColor( rData.maHelpTextColor ),
+ maHighlightColor( rData.maHighlightColor ),
+ maHighlightTextColor( rData.maHighlightTextColor ),
+ maLabelTextColor( rData.maLabelTextColor ),
+ maLightBorderColor( rData.maLightBorderColor ),
+ maLightColor( rData.maLightColor ),
+ maLinkColor( rData.maLinkColor ),
+ maMenuBarColor( rData.maMenuBarColor ),
+ maMenuBarRolloverColor( rData.maMenuBarRolloverColor ),
+ maMenuBorderColor( rData.maMenuBorderColor ),
+ maMenuColor( rData.maMenuColor ),
+ maMenuHighlightColor( rData.maMenuHighlightColor ),
+ maMenuHighlightTextColor( rData.maMenuHighlightTextColor ),
+ maMenuTextColor( rData.maMenuTextColor ),
+ maMenuBarTextColor( rData.maMenuBarTextColor ),
+ maMenuBarRolloverTextColor( rData.maMenuBarRolloverTextColor ),
+ maMenuBarHighlightTextColor( rData.maMenuBarHighlightTextColor ),
+ maMonoColor( rData.maMonoColor ),
+ maRadioCheckTextColor( rData.maRadioCheckTextColor ),
+ maShadowColor( rData.maShadowColor ),
+ maWarningColor( rData.maWarningColor ),
+ maVisitedLinkColor( rData.maVisitedLinkColor ),
+ maToolTextColor( rData.maToolTextColor ),
+ maWindowColor( rData.maWindowColor ),
+ maWindowTextColor( rData.maWindowTextColor ),
+ maWorkspaceColor( rData.maWorkspaceColor ),
+ maActiveTabColor( rData.maActiveTabColor ),
+ maInactiveTabColor( rData.maInactiveTabColor ),
+ maTabTextColor( rData.maTabTextColor ),
+ maTabRolloverTextColor( rData.maTabRolloverTextColor ),
+ maTabHighlightTextColor( rData.maTabHighlightTextColor ),
+ maAppFont( rData.maAppFont ),
+ maHelpFont( rData.maAppFont ),
+ maTitleFont( rData.maTitleFont ),
+ maFloatTitleFont( rData.maFloatTitleFont ),
+ maMenuFont( rData.maMenuFont ),
+ maToolFont( rData.maToolFont ),
+ maLabelFont( rData.maLabelFont ),
+ maRadioCheckFont( rData.maRadioCheckFont ),
+ maPushButtonFont( rData.maPushButtonFont ),
+ maFieldFont( rData.maFieldFont ),
+ maIconFont( rData.maIconFont ),
+ maTabFont( rData.maTabFont ),
+ maGroupFont( rData.maGroupFont ),
+ mnTitleHeight(rData.mnTitleHeight),
+ mnFloatTitleHeight(rData.mnFloatTitleHeight),
+ mnScrollBarSize(rData.mnScrollBarSize),
+ mnSpinSize(rData.mnSpinSize),
+ mnCursorSize(rData.mnCursorSize),
+ mnAntialiasedMin(rData.mnAntialiasedMin),
+ mnCursorBlinkTime(rData.mnCursorBlinkTime),
+ mnDragFullOptions(rData.mnDragFullOptions),
+ mnSelectionOptions(rData.mnSelectionOptions),
+ mnDisplayOptions(rData.mnDisplayOptions),
+ mnToolbarIconSize(rData.mnToolbarIconSize),
+ mnUseFlatMenus(rData.mnUseFlatMenus),
+ mnOptions(rData.mnOptions),
+ mbHighContrast(rData.mbHighContrast),
+ mbUseSystemUIFonts(rData.mbUseSystemUIFonts),
+ mbUseFontAAFromSystem(rData.mbUseFontAAFromSystem),
+ mbAutoMnemonic(rData.mbAutoMnemonic),
+ meUseImagesInMenus(rData.meUseImagesInMenus),
+ mnUseFlatBorders(rData.mnUseFlatBorders),
+ mbPreferredUseImagesInMenus(rData.mbPreferredUseImagesInMenus),
+ mnMinThumbSize(rData.mnMinThumbSize),
+ mIconThemeSelector(std::make_shared<vcl::IconThemeSelector>(*rData.mIconThemeSelector)),
+ mIconTheme(rData.mIconTheme),
+ mbSkipDisabledInMenus(rData.mbSkipDisabledInMenus),
+ mbHideDisabledMenuItems(rData.mbHideDisabledMenuItems),
+ mbPreferredContextMenuShortcuts(rData.mbPreferredContextMenuShortcuts),
+ meContextMenuShortcuts(rData.meContextMenuShortcuts),
+ mbPrimaryButtonWarpsSlider(rData.mbPrimaryButtonWarpsSlider),
+ maDialogStyle( rData.maDialogStyle ),
+ mnEdgeBlending(rData.mnEdgeBlending),
+ maEdgeBlendingTopLeftColor(rData.maEdgeBlendingTopLeftColor),
+ maEdgeBlendingBottomRightColor(rData.maEdgeBlendingBottomRightColor),
+ mnListBoxMaximumLineCount(rData.mnListBoxMaximumLineCount),
+ mnColorValueSetColumnCount(rData.mnColorValueSetColumnCount),
+ maListBoxPreviewDefaultLogicSize(rData.maListBoxPreviewDefaultLogicSize),
+ maListBoxPreviewDefaultPixelSize(rData.maListBoxPreviewDefaultPixelSize),
+ mbPreviewUsesCheckeredBackground(rData.mbPreviewUsesCheckeredBackground),
+ maPersonaHeaderFooter( rData.maPersonaHeaderFooter ),
+ maPersonaHeaderBitmap( rData.maPersonaHeaderBitmap ),
+ maPersonaFooterBitmap( rData.maPersonaFooterBitmap ),
+ maPersonaMenuBarTextColor( rData.maPersonaMenuBarTextColor )
+{
+ if (rData.mIconThemeScanner)
+ mIconThemeScanner = std::make_shared<vcl::IconThemeScanner>(*rData.mIconThemeScanner);
+}
+
+void ImplStyleData::SetStandardStyles()
+{
+ vcl::Font aStdFont( FAMILY_SWISS, Size( 0, 8 ) );
+ aStdFont.SetCharSet( osl_getThreadTextEncoding() );
+ aStdFont.SetWeight( WEIGHT_NORMAL );
+ if (!utl::ConfigManager::IsFuzzing())
+ aStdFont.SetFamilyName(utl::DefaultFontConfiguration::get().getUserInterfaceFont(LanguageTag("en")));
+ else
+ aStdFont.SetFamilyName("Liberation Sans");
+ maAppFont = aStdFont;
+ maHelpFont = aStdFont;
+ maMenuFont = aStdFont;
+ maToolFont = aStdFont;
+ maGroupFont = aStdFont;
+ maLabelFont = aStdFont;
+ maRadioCheckFont = aStdFont;
+ maPushButtonFont = aStdFont;
+ maFieldFont = aStdFont;
+ maIconFont = aStdFont;
+ maTabFont = aStdFont;
+ aStdFont.SetWeight( WEIGHT_BOLD );
+ maFloatTitleFont = aStdFont;
+ maTitleFont = aStdFont;
+
+ maFaceColor = COL_LIGHTGRAY;
+ maCheckedColor = Color( 0xCC, 0xCC, 0xCC );
+ maLightColor = COL_WHITE;
+ maLightBorderColor = COL_LIGHTGRAY;
+ maShadowColor = COL_GRAY;
+ maDarkShadowColor = COL_BLACK;
+
+ maWarningColor = COL_YELLOW;
+
+ maDefaultButtonTextColor = COL_BLACK;
+ maButtonTextColor = COL_BLACK;
+ maDefaultActionButtonTextColor = COL_BLACK;
+ maActionButtonTextColor = COL_BLACK;
+ maFlatButtonTextColor = COL_BLACK;
+ maDefaultButtonRolloverTextColor = COL_BLACK;
+ maButtonRolloverTextColor = COL_BLACK;
+ maDefaultActionButtonRolloverTextColor = COL_BLACK;
+ maActionButtonRolloverTextColor = COL_BLACK;
+ maFlatButtonRolloverTextColor = COL_BLACK;
+ maDefaultButtonPressedRolloverTextColor = COL_BLACK;
+ maButtonPressedRolloverTextColor = COL_BLACK;
+ maDefaultActionButtonPressedRolloverTextColor = COL_BLACK;
+ maActionButtonPressedRolloverTextColor = COL_BLACK;
+ maFlatButtonPressedRolloverTextColor = COL_BLACK;
+
+ maRadioCheckTextColor = COL_BLACK;
+ maGroupTextColor = COL_BLACK;
+ maLabelTextColor = COL_BLACK;
+ maWindowColor = COL_WHITE;
+ maWindowTextColor = COL_BLACK;
+ maDialogColor = COL_LIGHTGRAY;
+ maDialogTextColor = COL_BLACK;
+ maWorkspaceColor = Color( 0xDF, 0xDF, 0xDE );
+ maMonoColor = COL_BLACK;
+ maFieldColor = COL_WHITE;
+ maFieldTextColor = COL_BLACK;
+ maFieldRolloverTextColor = COL_BLACK;
+ maActiveBorderColor = COL_LIGHTGRAY;
+ maDeactiveColor = COL_GRAY;
+ maDeactiveTextColor = COL_LIGHTGRAY;
+ maDeactiveBorderColor = COL_LIGHTGRAY;
+ maMenuColor = COL_LIGHTGRAY;
+ maMenuBarColor = COL_LIGHTGRAY;
+ maMenuBarRolloverColor = COL_BLUE;
+ maMenuBorderColor = COL_LIGHTGRAY;
+ maMenuTextColor = COL_BLACK;
+ maMenuBarTextColor = COL_BLACK;
+ maMenuBarRolloverTextColor = COL_WHITE;
+ maMenuBarHighlightTextColor = COL_WHITE;
+ maMenuHighlightColor = COL_BLUE;
+ maMenuHighlightTextColor = COL_WHITE;
+ maHighlightColor = COL_BLUE;
+ maHighlightTextColor = COL_WHITE;
+ // make active like highlight, except with a small contrast
+ maActiveColor = maHighlightColor;
+ maActiveColor.IncreaseLuminance(32);
+ maActiveTextColor = maHighlightTextColor;
+ maActiveTabColor = COL_WHITE;
+ maInactiveTabColor = COL_LIGHTGRAY;
+ maTabTextColor = COL_BLACK;
+ maTabRolloverTextColor = COL_BLACK;
+ maTabHighlightTextColor = COL_BLACK;
+ maDisableColor = COL_GRAY;
+ maHelpColor = Color( 0xFF, 0xFF, 0xE0 );
+ maHelpTextColor = COL_BLACK;
+ maLinkColor = COL_BLUE;
+ maVisitedLinkColor = Color( 0x00, 0x00, 0xCC );
+ maToolTextColor = COL_BLACK;
+ maFontColor = COL_BLACK;
+ maAlternatingRowColor = Color( 0xEE, 0xEE, 0xEE );
+
+ mnTitleHeight = 18;
+ mnFloatTitleHeight = 13;
+ mbHighContrast = false;
+ mbUseSystemUIFonts = true;
+ mbUseFontAAFromSystem = true;
+ mnUseFlatBorders = false;
+ mnUseFlatMenus = false;
+ mbPreferredUseImagesInMenus = true;
+ mbSkipDisabledInMenus = false;
+ mbHideDisabledMenuItems = false;
+ mbPreferredContextMenuShortcuts = true;
+ mbPrimaryButtonWarpsSlider = false;
+}
+
+StyleSettings::StyleSettings()
+ : mxData(std::make_shared<ImplStyleData>())
+{
+}
+
+void
+StyleSettings::SetFaceColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFaceColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFaceColor() const
+{
+ return mxData->maFaceColor;
+}
+
+void
+StyleSettings::SetCheckedColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maCheckedColor = rColor;
+}
+
+const Color&
+StyleSettings::GetCheckedColor() const
+{
+ return mxData->maCheckedColor;
+}
+
+void
+StyleSettings::SetLightColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maLightColor = rColor;
+}
+
+const Color&
+StyleSettings::GetLightColor() const
+{
+ return mxData->maLightColor;
+}
+
+void
+StyleSettings::SetLightBorderColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maLightBorderColor = rColor;
+}
+
+const Color&
+StyleSettings::GetLightBorderColor() const
+{
+ return mxData->maLightBorderColor;
+}
+
+void
+StyleSettings::SetWarningColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maWarningColor = rColor;
+}
+
+const Color&
+StyleSettings::GetWarningColor() const
+{
+ return mxData->maWarningColor;
+}
+
+void
+StyleSettings::SetShadowColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maShadowColor = rColor;
+}
+
+const Color&
+StyleSettings::GetShadowColor() const
+{
+ return mxData->maShadowColor;
+}
+
+void
+StyleSettings::SetDarkShadowColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDarkShadowColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDarkShadowColor() const
+{
+ return mxData->maDarkShadowColor;
+}
+
+void
+StyleSettings::SetDefaultButtonTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDefaultButtonTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDefaultButtonTextColor() const
+{
+ return mxData->maDefaultButtonTextColor;
+}
+
+void
+StyleSettings::SetButtonTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maButtonTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetButtonTextColor() const
+{
+ return mxData->maButtonTextColor;
+}
+
+void
+StyleSettings::SetDefaultActionButtonTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDefaultActionButtonTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDefaultActionButtonTextColor() const
+{
+ return mxData->maDefaultActionButtonTextColor;
+}
+
+void
+StyleSettings::SetActionButtonTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maActionButtonTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetActionButtonTextColor() const
+{
+ return mxData->maActionButtonTextColor;
+}
+
+void
+StyleSettings::SetFlatButtonTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFlatButtonTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFlatButtonTextColor() const
+{
+ return mxData->maFlatButtonTextColor;
+}
+
+void
+StyleSettings::SetDefaultButtonRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDefaultButtonRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDefaultButtonRolloverTextColor() const
+{
+ return mxData->maDefaultButtonRolloverTextColor;
+}
+
+void
+StyleSettings::SetButtonRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maButtonRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetButtonRolloverTextColor() const
+{
+ return mxData->maButtonRolloverTextColor;
+}
+
+void
+StyleSettings::SetDefaultActionButtonRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDefaultActionButtonRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDefaultActionButtonRolloverTextColor() const
+{
+ return mxData->maDefaultActionButtonRolloverTextColor;
+}
+
+void
+StyleSettings::SetActionButtonRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maActionButtonRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetActionButtonRolloverTextColor() const
+{
+ return mxData->maActionButtonRolloverTextColor;
+}
+
+void
+StyleSettings::SetFlatButtonRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFlatButtonRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFlatButtonRolloverTextColor() const
+{
+ return mxData->maFlatButtonRolloverTextColor;
+}
+
+void
+StyleSettings::SetDefaultButtonPressedRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDefaultButtonPressedRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDefaultButtonPressedRolloverTextColor() const
+{
+ return mxData->maDefaultButtonPressedRolloverTextColor;
+}
+
+void
+StyleSettings::SetButtonPressedRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maButtonPressedRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetButtonPressedRolloverTextColor() const
+{
+ return mxData->maButtonPressedRolloverTextColor;
+}
+
+void
+StyleSettings::SetDefaultActionButtonPressedRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDefaultActionButtonPressedRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDefaultActionButtonPressedRolloverTextColor() const
+{
+ return mxData->maDefaultActionButtonPressedRolloverTextColor;
+}
+
+void
+StyleSettings::SetActionButtonPressedRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maActionButtonPressedRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetActionButtonPressedRolloverTextColor() const
+{
+ return mxData->maActionButtonPressedRolloverTextColor;
+}
+
+void
+StyleSettings::SetFlatButtonPressedRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFlatButtonPressedRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFlatButtonPressedRolloverTextColor() const
+{
+ return mxData->maFlatButtonPressedRolloverTextColor;
+}
+
+void
+StyleSettings::SetRadioCheckTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maRadioCheckTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetRadioCheckTextColor() const
+{
+ return mxData->maRadioCheckTextColor;
+}
+
+void
+StyleSettings::SetGroupTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maGroupTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetGroupTextColor() const
+{
+ return mxData->maGroupTextColor;
+}
+
+void
+StyleSettings::SetLabelTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maLabelTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetLabelTextColor() const
+{
+ return mxData->maLabelTextColor;
+}
+
+void
+StyleSettings::SetWindowColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maWindowColor = rColor;
+}
+
+const Color&
+StyleSettings::GetWindowColor() const
+{
+ return mxData->maWindowColor;
+}
+
+void
+StyleSettings::SetWindowTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maWindowTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetWindowTextColor() const
+{
+ return mxData->maWindowTextColor;
+}
+
+void
+StyleSettings::SetDialogColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDialogColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDialogColor() const
+{
+ return mxData->maDialogColor;
+}
+
+void
+StyleSettings::SetDialogTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDialogTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDialogTextColor() const
+{
+ return mxData->maDialogTextColor;
+}
+
+void
+StyleSettings::SetWorkspaceColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maWorkspaceColor = rColor;
+}
+
+const Color&
+StyleSettings::GetWorkspaceColor() const
+{
+ return mxData->maWorkspaceColor;
+}
+
+void
+StyleSettings::SetFieldColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFieldColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFieldColor() const
+{
+ return mxData->maFieldColor;
+}
+
+void
+StyleSettings::SetFieldTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFieldTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFieldTextColor() const
+{
+ return mxData->maFieldTextColor;
+}
+
+void
+StyleSettings::SetFieldRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFieldRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFieldRolloverTextColor() const
+{
+ return mxData->maFieldRolloverTextColor;
+}
+
+void
+StyleSettings::SetActiveColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maActiveColor = rColor;
+}
+
+const Color&
+StyleSettings::GetActiveColor() const
+{
+ return mxData->maActiveColor;
+}
+
+void
+StyleSettings::SetActiveTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maActiveTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetActiveTextColor() const
+{
+ return mxData->maActiveTextColor;
+}
+
+void
+StyleSettings::SetActiveBorderColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maActiveBorderColor = rColor;
+}
+
+const Color&
+StyleSettings::GetActiveBorderColor() const
+{
+ return mxData->maActiveBorderColor;
+}
+
+void
+StyleSettings::SetDeactiveColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDeactiveColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDeactiveColor() const
+{
+ return mxData->maDeactiveColor;
+}
+
+void
+StyleSettings::SetDeactiveTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDeactiveTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDeactiveTextColor() const
+{
+ return mxData->maDeactiveTextColor;
+}
+
+void
+StyleSettings::SetDeactiveBorderColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDeactiveBorderColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDeactiveBorderColor() const
+{
+ return mxData->maDeactiveBorderColor;
+}
+
+void
+StyleSettings::SetHighlightColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maHighlightColor = rColor;
+}
+
+const Color&
+StyleSettings::GetHighlightColor() const
+{
+ return mxData->maHighlightColor;
+}
+
+void
+StyleSettings::SetHighlightTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maHighlightTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetHighlightTextColor() const
+{
+ return mxData->maHighlightTextColor;
+}
+
+void
+StyleSettings::SetDisableColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maDisableColor = rColor;
+}
+
+const Color&
+StyleSettings::GetDisableColor() const
+{
+ return mxData->maDisableColor;
+}
+
+void
+StyleSettings::SetHelpColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maHelpColor = rColor;
+}
+
+const Color&
+StyleSettings::GetHelpColor() const
+{
+ return mxData->maHelpColor;
+}
+
+void
+StyleSettings::SetHelpTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maHelpTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetHelpTextColor() const
+{
+ return mxData->maHelpTextColor;
+}
+
+void
+StyleSettings::SetMenuColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuColor() const
+{
+ return mxData->maMenuColor;
+}
+
+void
+StyleSettings::SetMenuBarColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuBarColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuBarColor() const
+{
+ return mxData->maMenuBarColor;
+}
+
+void
+StyleSettings::SetMenuBarRolloverColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuBarRolloverColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuBarRolloverColor() const
+{
+ return mxData->maMenuBarRolloverColor;
+}
+
+void
+StyleSettings::SetMenuBorderColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuBorderColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuBorderColor() const
+{
+ return mxData->maMenuBorderColor;
+}
+
+void
+StyleSettings::SetMenuTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuTextColor() const
+{
+ return mxData->maMenuTextColor;
+}
+
+void
+StyleSettings::SetMenuBarTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuBarTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuBarTextColor() const
+{
+ return mxData->maMenuBarTextColor;
+}
+
+void
+StyleSettings::SetMenuBarRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuBarRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuBarRolloverTextColor() const
+{
+ return mxData->maMenuBarRolloverTextColor;
+}
+
+void
+StyleSettings::SetMenuBarHighlightTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuBarHighlightTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuBarHighlightTextColor() const
+{
+ return mxData->maMenuBarHighlightTextColor;
+}
+
+void
+StyleSettings::SetMenuHighlightColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuHighlightColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuHighlightColor() const
+{
+ return mxData->maMenuHighlightColor;
+}
+
+void
+StyleSettings::SetMenuHighlightTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMenuHighlightTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMenuHighlightTextColor() const
+{
+ return mxData->maMenuHighlightTextColor;
+}
+
+void
+StyleSettings::SetTabTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maTabTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetTabTextColor() const
+{
+ return mxData->maTabTextColor;
+}
+
+void
+StyleSettings::SetTabRolloverTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maTabRolloverTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetTabRolloverTextColor() const
+{
+ return mxData->maTabRolloverTextColor;
+}
+
+void
+StyleSettings::SetTabHighlightTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maTabHighlightTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetTabHighlightTextColor() const
+{
+ return mxData->maTabHighlightTextColor;
+}
+
+void
+StyleSettings::SetLinkColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maLinkColor = rColor;
+}
+
+const Color&
+StyleSettings::GetLinkColor() const
+{
+ return mxData->maLinkColor;
+}
+
+void
+StyleSettings::SetVisitedLinkColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maVisitedLinkColor = rColor;
+}
+
+const Color&
+StyleSettings::GetVisitedLinkColor() const
+{
+ return mxData->maVisitedLinkColor;
+}
+
+void
+StyleSettings::SetToolTextColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maToolTextColor = rColor;
+}
+
+const Color&
+StyleSettings::GetToolTextColor() const
+{
+ return mxData->maToolTextColor;
+}
+
+void
+StyleSettings::SetMonoColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maMonoColor = rColor;
+}
+
+const Color&
+StyleSettings::GetMonoColor() const
+{
+ return mxData->maMonoColor;
+}
+
+void
+StyleSettings::SetActiveTabColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maActiveTabColor = rColor;
+}
+
+const Color&
+StyleSettings::GetActiveTabColor() const
+{
+ return mxData->maActiveTabColor;
+}
+
+void
+StyleSettings::SetInactiveTabColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maInactiveTabColor = rColor;
+}
+
+const Color&
+StyleSettings::GetInactiveTabColor() const
+{
+ return mxData->maInactiveTabColor;
+}
+
+void StyleSettings::SetAlternatingRowColor(const Color& rColor)
+{
+ CopyData();
+ mxData->maAlternatingRowColor = rColor;
+}
+
+const Color&
+StyleSettings::GetAlternatingRowColor() const
+{
+ return mxData->maAlternatingRowColor;
+}
+
+void
+StyleSettings::SetUseSystemUIFonts( bool bUseSystemUIFonts )
+{
+ CopyData();
+ mxData->mbUseSystemUIFonts = bUseSystemUIFonts;
+}
+
+bool
+StyleSettings::GetUseSystemUIFonts() const
+{
+ return mxData->mbUseSystemUIFonts;
+}
+
+void StyleSettings::SetUseFontAAFromSystem(bool bUseFontAAFromSystem)
+{
+ CopyData();
+ mxData->mbUseFontAAFromSystem = bUseFontAAFromSystem;
+}
+
+bool StyleSettings::GetUseFontAAFromSystem() const
+{
+ return mxData->mbUseFontAAFromSystem;
+}
+
+void
+StyleSettings::SetUseFlatBorders( bool bUseFlatBorders )
+{
+ CopyData();
+ mxData->mnUseFlatBorders = bUseFlatBorders;
+}
+
+bool
+StyleSettings::GetUseFlatBorders() const
+{
+ return mxData->mnUseFlatBorders;
+}
+
+void
+StyleSettings::SetUseFlatMenus( bool bUseFlatMenus )
+{
+ CopyData();
+ mxData->mnUseFlatMenus = bUseFlatMenus;
+}
+
+bool
+StyleSettings::GetUseFlatMenus() const
+{
+ return mxData->mnUseFlatMenus;
+}
+
+void
+StyleSettings::SetUseImagesInMenus( TriState eUseImagesInMenus )
+{
+ CopyData();
+ mxData->meUseImagesInMenus = eUseImagesInMenus;
+}
+
+void
+StyleSettings::SetPreferredUseImagesInMenus( bool bPreferredUseImagesInMenus )
+{
+ CopyData();
+ mxData->mbPreferredUseImagesInMenus = bPreferredUseImagesInMenus;
+}
+
+bool
+StyleSettings::GetPreferredUseImagesInMenus() const
+{
+ return mxData->mbPreferredUseImagesInMenus;
+}
+
+void
+StyleSettings::SetSkipDisabledInMenus( bool bSkipDisabledInMenus )
+{
+ CopyData();
+ mxData->mbSkipDisabledInMenus = bSkipDisabledInMenus;
+}
+
+bool
+StyleSettings::GetSkipDisabledInMenus() const
+{
+ return mxData->mbSkipDisabledInMenus;
+}
+
+void
+StyleSettings::SetHideDisabledMenuItems( bool bHideDisabledMenuItems )
+{
+ CopyData();
+ mxData->mbHideDisabledMenuItems = bHideDisabledMenuItems;
+}
+
+bool
+StyleSettings::GetHideDisabledMenuItems() const
+{
+ return mxData->mbHideDisabledMenuItems;
+}
+
+void
+StyleSettings::SetContextMenuShortcuts( TriState eContextMenuShortcuts )
+{
+ CopyData();
+ mxData->meContextMenuShortcuts = eContextMenuShortcuts;
+}
+
+bool
+StyleSettings::GetContextMenuShortcuts() const
+{
+ switch (mxData->meContextMenuShortcuts)
+ {
+ case TRISTATE_FALSE:
+ return false;
+ case TRISTATE_TRUE:
+ return true;
+ default: // TRISTATE_INDET:
+ return GetPreferredContextMenuShortcuts();
+ }
+}
+
+void
+StyleSettings::SetPreferredContextMenuShortcuts( bool bContextMenuShortcuts )
+{
+ CopyData();
+ mxData->mbPreferredContextMenuShortcuts = bContextMenuShortcuts;
+}
+
+bool
+StyleSettings::GetPreferredContextMenuShortcuts() const
+{
+ return mxData->mbPreferredContextMenuShortcuts;
+}
+
+void
+StyleSettings::SetPrimaryButtonWarpsSlider( bool bPrimaryButtonWarpsSlider )
+{
+ CopyData();
+ mxData->mbPrimaryButtonWarpsSlider = bPrimaryButtonWarpsSlider;
+}
+
+bool
+StyleSettings::GetPrimaryButtonWarpsSlider() const
+{
+ return mxData->mbPrimaryButtonWarpsSlider;
+}
+
+void
+StyleSettings::SetAppFont( const vcl::Font& rFont )
+{
+ CopyData();
+ mxData->maAppFont = rFont;
+}
+
+const vcl::Font&
+StyleSettings::GetAppFont() const
+{
+ return mxData->maAppFont;
+}
+
+void
+StyleSettings::SetHelpFont( const vcl::Font& rFont )
+{
+ CopyData();
+ mxData->maHelpFont = rFont;
+}
+
+const vcl::Font&
+StyleSettings::GetHelpFont() const
+{
+ return mxData->maHelpFont;
+}
+
+void
+StyleSettings::SetTitleFont( const vcl::Font& rFont )
+{
+ CopyData();
+ mxData->maTitleFont = rFont;
+}
+
+const vcl::Font&
+StyleSettings::GetTitleFont() const
+{
+ return mxData->maTitleFont;
+}
+
+void
+StyleSettings::SetFloatTitleFont( const vcl::Font& rFont )
+{
+ CopyData();
+ mxData->maFloatTitleFont = rFont;
+}
+
+const vcl::Font&
+StyleSettings::GetFloatTitleFont() const
+{
+ return mxData->maFloatTitleFont;
+}
+
+void
+StyleSettings::SetMenuFont( const vcl::Font& rFont )
+{
+ CopyData();
+ mxData->maMenuFont = rFont;
+}
+
+const vcl::Font&
+StyleSettings::GetMenuFont() const
+{
+ return mxData->maMenuFont;
+}
+
+void
+StyleSettings::SetToolFont( const vcl::Font& rFont )
+{
+ CopyData();
+ mxData->maToolFont = rFont;
+}
+
+const vcl::Font&
+StyleSettings::GetToolFont() const
+{
+ return mxData->maToolFont;
+}
+
+void
+StyleSettings::SetGroupFont( const vcl::Font& rFont )
+{
+ CopyData();
+ mxData->maGroupFont = rFont;
+}
+
+const vcl::Font&
+StyleSettings::GetGroupFont() const
+{
+ return mxData->maGroupFont;
+}
+
+void
+StyleSettings::SetLabelFont( const vcl::Font& rFont )
+{
+ CopyData();
+ mxData->maLabelFont = rFont;
+}
+
+const vcl::Font&
+StyleSettings::GetLabelFont() const
+{
+ return mxData->maLabelFont;
+}
+
+void
+StyleSettings::SetRadioCheckFont( const vcl::Font& rFont )
+{
+ CopyData();
+ mxData->maRadioCheckFont = rFont;
+}
+
+const vcl::Font&
+StyleSettings::GetRadioCheckFont() const
+{
+ return mxData->maRadioCheckFont;
+}
+
+void
+StyleSettings::SetPushButtonFont( const vcl::Font& rFont )
+{
+ CopyData();
+ mxData->maPushButtonFont = rFont;
+}
+
+const vcl::Font&
+StyleSettings::GetPushButtonFont() const
+{
+ return mxData->maPushButtonFont;
+}
+
+void
+StyleSettings::SetFieldFont( const vcl::Font& rFont )
+{
+ CopyData();
+ mxData->maFieldFont = rFont;
+}
+
+const vcl::Font&
+StyleSettings::GetFieldFont() const
+{
+ return mxData->maFieldFont;
+}
+
+void
+StyleSettings::SetIconFont( const vcl::Font& rFont )
+{
+ CopyData();
+ mxData->maIconFont = rFont;
+}
+
+const vcl::Font&
+StyleSettings::GetIconFont() const
+{
+ return mxData->maIconFont;
+}
+
+void
+StyleSettings::SetTabFont( const vcl::Font& rFont )
+{
+ CopyData();
+ mxData->maTabFont = rFont;
+}
+
+const vcl::Font&
+StyleSettings::GetTabFont() const
+{
+ return mxData->maTabFont;
+}
+
+sal_Int32
+StyleSettings::GetBorderSize()
+{
+ return 1;
+}
+
+void
+StyleSettings::SetTitleHeight( sal_Int32 nSize )
+{
+ CopyData();
+ mxData->mnTitleHeight = nSize;
+}
+
+sal_Int32
+StyleSettings::GetTitleHeight() const
+{
+ return mxData->mnTitleHeight;
+}
+
+void
+StyleSettings::SetFloatTitleHeight( sal_Int32 nSize )
+{
+ CopyData();
+ mxData->mnFloatTitleHeight = nSize;
+}
+
+sal_Int32
+StyleSettings::GetFloatTitleHeight() const
+{
+ return mxData->mnFloatTitleHeight;
+}
+
+void
+StyleSettings::SetScrollBarSize( sal_Int32 nSize )
+{
+ CopyData();
+ mxData->mnScrollBarSize = nSize;
+}
+
+sal_Int32
+StyleSettings::GetScrollBarSize() const
+{
+ return mxData->mnScrollBarSize;
+}
+
+void
+StyleSettings::SetMinThumbSize( sal_Int32 nSize )
+{
+ CopyData();
+ mxData->mnMinThumbSize = nSize;
+}
+
+sal_Int32
+StyleSettings::GetMinThumbSize() const
+{
+ return mxData->mnMinThumbSize;
+}
+
+void
+StyleSettings::SetSpinSize( sal_Int32 nSize )
+{
+ CopyData();
+ mxData->mnSpinSize = nSize;
+}
+
+sal_Int32
+StyleSettings::GetSpinSize() const
+{
+ return mxData->mnSpinSize;
+}
+
+sal_Int32
+StyleSettings::GetSplitSize()
+{
+ return 3;
+}
+
+void
+StyleSettings::SetCursorSize( sal_Int32 nSize )
+{
+ CopyData();
+ mxData->mnCursorSize = nSize;
+}
+
+sal_Int32
+StyleSettings::GetCursorSize() const
+{
+ return mxData->mnCursorSize;
+}
+
+void
+StyleSettings::SetCursorBlinkTime( sal_uInt64 nBlinkTime )
+{
+ CopyData();
+ mxData->mnCursorBlinkTime = nBlinkTime;
+}
+
+sal_uInt64
+StyleSettings::GetCursorBlinkTime() const
+{
+ return mxData->mnCursorBlinkTime;
+}
+
+void
+StyleSettings::SetDragFullOptions( DragFullOptions nOptions )
+{
+ CopyData();
+ mxData->mnDragFullOptions = nOptions;
+}
+
+DragFullOptions
+StyleSettings::GetDragFullOptions() const
+{
+ return mxData->mnDragFullOptions;
+}
+
+void
+StyleSettings::SetSelectionOptions( SelectionOptions nOptions )
+{
+ CopyData();
+ mxData->mnSelectionOptions = nOptions;
+}
+
+SelectionOptions
+StyleSettings::GetSelectionOptions() const
+{
+ return mxData->mnSelectionOptions;
+}
+
+void
+StyleSettings::SetDisplayOptions( DisplayOptions nOptions )
+{
+ CopyData();
+ mxData->mnDisplayOptions = nOptions;
+}
+
+DisplayOptions
+StyleSettings::GetDisplayOptions() const
+{
+ return mxData->mnDisplayOptions;
+}
+
+void
+StyleSettings::SetAntialiasingMinPixelHeight( sal_Int32 nMinPixel )
+{
+ CopyData();
+ mxData->mnAntialiasedMin = nMinPixel;
+}
+
+sal_Int32
+StyleSettings::GetAntialiasingMinPixelHeight() const
+{
+ return mxData->mnAntialiasedMin;
+}
+
+void
+StyleSettings::SetOptions( StyleSettingsOptions nOptions )
+{
+ CopyData();
+ mxData->mnOptions = nOptions;
+}
+
+void
+StyleSettings::SetAutoMnemonic( bool bAutoMnemonic )
+{
+ CopyData();
+ mxData->mbAutoMnemonic = bAutoMnemonic;
+}
+
+bool
+StyleSettings::GetAutoMnemonic() const
+{
+ return mxData->mbAutoMnemonic;
+}
+
+bool
+StyleSettings::GetDockingFloatsSupported()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return pSVData->maNWFData.mbCanDetermineWindowPosition;
+}
+
+void
+StyleSettings::SetFontColor( const Color& rColor )
+{
+ CopyData();
+ mxData->maFontColor = rColor;
+}
+
+const Color&
+StyleSettings::GetFontColor() const
+{
+ return mxData->maFontColor;
+}
+
+void
+StyleSettings::SetToolbarIconSize( ToolbarIconSize nSize )
+{
+ CopyData();
+ mxData->mnToolbarIconSize = nSize;
+}
+
+ToolbarIconSize
+StyleSettings::GetToolbarIconSize() const
+{
+ return mxData->mnToolbarIconSize;
+}
+
+Size StyleSettings::GetToolbarIconSizePixel() const
+{
+ switch (GetToolbarIconSize())
+ {
+ case ToolbarIconSize::Large:
+ return Size(24, 24);
+ case ToolbarIconSize::Size32:
+ return Size(32, 32);
+ case ToolbarIconSize::Small:
+ default:
+ return Size(16, 16);
+ }
+}
+
+const DialogStyle&
+StyleSettings::GetDialogStyle() const
+{
+ return mxData->maDialogStyle;
+}
+
+void
+StyleSettings::SetEdgeBlending(sal_uInt16 nCount)
+{
+ CopyData();
+ mxData->mnEdgeBlending = nCount;
+}
+
+sal_uInt16
+StyleSettings::GetEdgeBlending() const
+{
+ return mxData->mnEdgeBlending;
+}
+
+const Color&
+StyleSettings::GetEdgeBlendingTopLeftColor() const
+{
+ return mxData->maEdgeBlendingTopLeftColor;
+}
+
+const Color&
+StyleSettings::GetEdgeBlendingBottomRightColor() const
+{
+ return mxData->maEdgeBlendingBottomRightColor;
+}
+
+void
+StyleSettings::SetListBoxMaximumLineCount(sal_uInt16 nCount)
+{
+ CopyData();
+ mxData->mnListBoxMaximumLineCount = nCount;
+}
+
+sal_uInt16
+StyleSettings::GetListBoxMaximumLineCount() const
+{
+ return mxData->mnListBoxMaximumLineCount;
+}
+
+void
+StyleSettings::SetColorValueSetColumnCount(sal_uInt16 nCount)
+{
+ CopyData();
+ mxData->mnColorValueSetColumnCount = nCount;
+}
+
+sal_uInt16
+StyleSettings::GetColorValueSetColumnCount() const
+{
+ return mxData->mnColorValueSetColumnCount;
+}
+
+sal_uInt16
+StyleSettings::GetListBoxPreviewDefaultLineWidth()
+{
+ return 1;
+}
+
+void
+StyleSettings::SetPreviewUsesCheckeredBackground(bool bNew)
+{
+ CopyData();
+ mxData->mbPreviewUsesCheckeredBackground = bNew;
+}
+
+bool
+StyleSettings::GetPreviewUsesCheckeredBackground() const
+{
+ return mxData->mbPreviewUsesCheckeredBackground;
+}
+
+bool
+StyleSettings::operator !=( const StyleSettings& rSet ) const
+{
+ return !(*this == rSet);
+}
+
+void StyleSettings::SetListBoxPreviewDefaultLogicSize(Size const& rSize)
+{
+ mxData->maListBoxPreviewDefaultLogicSize = rSize;
+}
+
+const Size& StyleSettings::GetListBoxPreviewDefaultPixelSize() const
+{
+ if(0 == mxData->maListBoxPreviewDefaultPixelSize.Width() || 0 == mxData->maListBoxPreviewDefaultPixelSize.Height())
+ {
+ const_cast< StyleSettings* >(this)->mxData->maListBoxPreviewDefaultPixelSize =
+ Application::GetDefaultDevice()->LogicToPixel(mxData->maListBoxPreviewDefaultLogicSize, MapMode(MapUnit::MapAppFont));
+ }
+
+ return mxData->maListBoxPreviewDefaultPixelSize;
+}
+
+void StyleSettings::Set3DColors( const Color& rColor )
+{
+ CopyData();
+ mxData->maFaceColor = rColor;
+ mxData->maLightBorderColor = rColor;
+ mxData->maMenuBorderColor = rColor;
+ mxData->maDarkShadowColor = COL_BLACK;
+ if ( rColor != COL_LIGHTGRAY )
+ {
+ mxData->maLightColor = rColor;
+ mxData->maShadowColor = rColor;
+ mxData->maDarkShadowColor=rColor;
+ mxData->maLightColor.IncreaseLuminance( 64 );
+ mxData->maShadowColor.DecreaseLuminance( 64 );
+ mxData->maDarkShadowColor.DecreaseLuminance( 100 );
+ sal_uLong nRed = mxData->maLightColor.GetRed();
+ sal_uLong nGreen = mxData->maLightColor.GetGreen();
+ sal_uLong nBlue = mxData->maLightColor.GetBlue();
+ nRed += static_cast<sal_uLong>(mxData->maShadowColor.GetRed());
+ nGreen += static_cast<sal_uLong>(mxData->maShadowColor.GetGreen());
+ nBlue += static_cast<sal_uLong>(mxData->maShadowColor.GetBlue());
+ mxData->maCheckedColor = Color( static_cast<sal_uInt8>(nRed/2), static_cast<sal_uInt8>(nGreen/2), static_cast<sal_uInt8>(nBlue/2) );
+ }
+ else
+ {
+ mxData->maCheckedColor = Color( 0x99, 0x99, 0x99 );
+ mxData->maLightColor = COL_WHITE;
+ mxData->maShadowColor = COL_GRAY;
+ }
+}
+
+void StyleSettings::SetCheckedColorSpecialCase( )
+{
+ CopyData();
+ // Light gray checked color special case
+ if ( GetFaceColor() == COL_LIGHTGRAY )
+ mxData->maCheckedColor = Color( 0xCC, 0xCC, 0xCC );
+ else
+ {
+ sal_uInt8 nRed = static_cast<sal_uInt8>((static_cast<sal_uInt16>(mxData->maFaceColor.GetRed()) + static_cast<sal_uInt16>(mxData->maLightColor.GetRed()))/2);
+ sal_uInt8 nGreen = static_cast<sal_uInt8>((static_cast<sal_uInt16>(mxData->maFaceColor.GetGreen()) + static_cast<sal_uInt16>(mxData->maLightColor.GetGreen()))/2);
+ sal_uInt8 nBlue = static_cast<sal_uInt8>((static_cast<sal_uInt16>(mxData->maFaceColor.GetBlue()) + static_cast<sal_uInt16>(mxData->maLightColor.GetBlue()))/2);
+ mxData->maCheckedColor = Color( nRed, nGreen, nBlue );
+ }
+}
+
+bool StyleSettings::GetUseImagesInMenus() const
+{
+ // icon mode selected in Tools -> Options... -> OpenOffice.org -> View
+ switch (mxData->meUseImagesInMenus) {
+ case TRISTATE_FALSE:
+ return false;
+ case TRISTATE_TRUE:
+ return true;
+ default: // TRISTATE_INDET:
+ return GetPreferredUseImagesInMenus();
+ }
+}
+
+static BitmapEx readBitmapEx( const OUString& rPath )
+{
+ OUString aPath( rPath );
+ rtl::Bootstrap::expandMacros( aPath );
+
+ // import the image
+ Graphic aGraphic;
+ if ( GraphicFilter::LoadGraphic( aPath, OUString(), aGraphic ) != ERRCODE_NONE )
+ return BitmapEx();
+ return aGraphic.GetBitmapEx();
+}
+
+namespace {
+
+enum WhichPersona { PERSONA_HEADER, PERSONA_FOOTER };
+
+}
+
+/** Update the setting of the Persona header / footer in ImplStyleData */
+static void setupPersonaHeaderFooter( WhichPersona eWhich, OUString& rHeaderFooter, BitmapEx& rHeaderFooterBitmap, std::optional<Color>& rMenuBarTextColor )
+{
+ // don't burn time loading images we don't need.
+ if ( Application::IsHeadlessModeEnabled() )
+ return;
+
+ // read from the configuration
+ OUString aPersona( officecfg::Office::Common::Misc::Persona::get() );
+ OUString aPersonaSettings( officecfg::Office::Common::Misc::PersonaSettings::get() );
+ if (aPersona == "no")
+ return;
+
+ // have the settings changed? marks if header /footer prepared before
+ //should maybe extended to a flag that marks if header /footer /both are loaded
+ OUString aOldValue= eWhich==PERSONA_HEADER?OUString(aPersona + ";" + aPersonaSettings+";h" ):OUString(aPersona + ";" + aPersonaSettings+";f" );
+ if ( rHeaderFooter == aOldValue )
+ return;
+
+ rHeaderFooter = aOldValue;
+ rHeaderFooterBitmap = BitmapEx();
+ rMenuBarTextColor.reset();
+
+ // now read the new values and setup bitmaps
+ OUString aHeader, aFooter;
+ if ( aPersona == "own" || aPersona == "default" )
+ {
+ sal_Int32 nIndex = 0;
+
+ // Skip the persona slug, name, and preview
+ aHeader = aPersonaSettings.getToken( 3, ';', nIndex );
+
+ if ( nIndex > 0 )
+ aFooter = aPersonaSettings.getToken( 0, ';', nIndex );
+
+ // change menu text color, advance nIndex to skip the '#'
+ if ( nIndex > 0 )
+ {
+ OUString aColor = aPersonaSettings.getToken( 0, ';', ++nIndex );
+ rMenuBarTextColor = Color( ColorTransparency, aColor.toUInt32( 16 ) );
+ }
+ }
+
+ OUString aName;
+ switch ( eWhich ) {
+ case PERSONA_HEADER: aName = aHeader; break;
+ case PERSONA_FOOTER: aName = aFooter; break;
+ }
+
+ if ( !aName.isEmpty() )
+ {
+ OUString gallery("");
+ // try the gallery first, then the program path:
+ if ( aPersona == "own" && !aPersonaSettings.startsWith( "vnd.sun.star.expand" ) )
+ {
+ gallery = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
+ rtl::Bootstrap::expandMacros( gallery );
+ gallery += "/user/gallery/personas/";
+ }
+ else if ( aPersona == "default" )
+ {
+ gallery = "$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/gallery/personas/";
+ }
+ rHeaderFooterBitmap = readBitmapEx( gallery + aName );
+
+ if ( rHeaderFooterBitmap.IsEmpty() )
+ rHeaderFooterBitmap = readBitmapEx( "$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" + aName );
+ }
+
+ // Something went wrong. Probably, the images are missing. Clear the persona properties in the registry.
+
+ if( rHeaderFooterBitmap.IsEmpty() )
+ {
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Misc::Persona::set( "no", batch );
+ officecfg::Office::Common::Misc::PersonaSettings::set( "", batch );
+ batch->commit();
+ }
+}
+
+BitmapEx const & StyleSettings::GetPersonaHeader() const
+{
+ setupPersonaHeaderFooter( PERSONA_HEADER, mxData->maPersonaHeaderFooter, mxData->maPersonaHeaderBitmap, mxData->maPersonaMenuBarTextColor );
+ return mxData->maPersonaHeaderBitmap;
+}
+
+BitmapEx const & StyleSettings::GetPersonaFooter() const
+{
+ setupPersonaHeaderFooter( PERSONA_FOOTER, mxData->maPersonaHeaderFooter, mxData->maPersonaFooterBitmap, mxData->maPersonaMenuBarTextColor );
+ return mxData->maPersonaFooterBitmap;
+}
+
+const std::optional<Color>& StyleSettings::GetPersonaMenuBarTextColor() const
+{
+ GetPersonaHeader();
+ return mxData->maPersonaMenuBarTextColor;
+}
+
+void StyleSettings::SetStandardStyles()
+{
+ CopyData();
+ mxData->SetStandardStyles();
+}
+
+Color StyleSettings::GetFaceGradientColor() const
+{
+ // compute a brighter face color that can be used in gradients
+ // for a convex look (eg toolbars)
+
+ sal_uInt16 h, s, b;
+ GetFaceColor().RGBtoHSB( h, s, b );
+ if( s > 1) s=1;
+ if( b < 98) b=98;
+ return Color::HSBtoRGB( h, s, b );
+}
+
+Color StyleSettings::GetSeparatorColor() const
+{
+ // compute a brighter shadow color for separators (used in toolbars or between menubar and toolbars on Windows XP)
+ sal_uInt16 h, s, b;
+ GetShadowColor().RGBtoHSB( h, s, b );
+ b += b/4;
+ s -= s/4;
+ return Color::HSBtoRGB( h, s, b );
+}
+
+void StyleSettings::CopyData()
+{
+ // copy if other references exist
+ if (mxData.use_count() > 1)
+ {
+ mxData = std::make_shared<ImplStyleData>(*mxData);
+ }
+}
+
+bool StyleSettings::operator ==( const StyleSettings& rSet ) const
+{
+ if ( mxData == rSet.mxData )
+ return true;
+
+ if (mxData->mIconTheme != rSet.mxData->mIconTheme) {
+ return false;
+ }
+
+ if (*mxData->mIconThemeSelector != *rSet.mxData->mIconThemeSelector) {
+ return false;
+ }
+
+ return (mxData->mnOptions == rSet.mxData->mnOptions) &&
+ (mxData->mbAutoMnemonic == rSet.mxData->mbAutoMnemonic) &&
+ (mxData->mnDragFullOptions == rSet.mxData->mnDragFullOptions) &&
+ (mxData->mnSelectionOptions == rSet.mxData->mnSelectionOptions) &&
+ (mxData->mnDisplayOptions == rSet.mxData->mnDisplayOptions) &&
+ (mxData->mnCursorSize == rSet.mxData->mnCursorSize) &&
+ (mxData->mnCursorBlinkTime == rSet.mxData->mnCursorBlinkTime) &&
+ (mxData->mnTitleHeight == rSet.mxData->mnTitleHeight) &&
+ (mxData->mnFloatTitleHeight == rSet.mxData->mnFloatTitleHeight) &&
+ (mxData->mnScrollBarSize == rSet.mxData->mnScrollBarSize) &&
+ (mxData->mnMinThumbSize == rSet.mxData->mnMinThumbSize) &&
+ (mxData->mnSpinSize == rSet.mxData->mnSpinSize) &&
+ (mxData->mnAntialiasedMin == rSet.mxData->mnAntialiasedMin) &&
+ (mxData->mbHighContrast == rSet.mxData->mbHighContrast) &&
+ (mxData->mbUseSystemUIFonts == rSet.mxData->mbUseSystemUIFonts) &&
+ (mxData->mbUseFontAAFromSystem == rSet.mxData->mbUseFontAAFromSystem) &&
+ (mxData->mnUseFlatBorders == rSet.mxData->mnUseFlatBorders) &&
+ (mxData->mnUseFlatMenus == rSet.mxData->mnUseFlatMenus) &&
+ (mxData->maFaceColor == rSet.mxData->maFaceColor) &&
+ (mxData->maCheckedColor == rSet.mxData->maCheckedColor) &&
+ (mxData->maLightColor == rSet.mxData->maLightColor) &&
+ (mxData->maLightBorderColor == rSet.mxData->maLightBorderColor) &&
+ (mxData->maShadowColor == rSet.mxData->maShadowColor) &&
+ (mxData->maDarkShadowColor == rSet.mxData->maDarkShadowColor) &&
+ (mxData->maWarningColor == rSet.mxData->maWarningColor) &&
+ (mxData->maButtonTextColor == rSet.mxData->maButtonTextColor) &&
+ (mxData->maDefaultActionButtonTextColor == rSet.mxData->maDefaultActionButtonTextColor) &&
+ (mxData->maActionButtonTextColor == rSet.mxData->maActionButtonTextColor) &&
+ (mxData->maButtonRolloverTextColor == rSet.mxData->maButtonRolloverTextColor) &&
+ (mxData->maActionButtonRolloverTextColor == rSet.mxData->maActionButtonRolloverTextColor) &&
+ (mxData->maRadioCheckTextColor == rSet.mxData->maRadioCheckTextColor) &&
+ (mxData->maGroupTextColor == rSet.mxData->maGroupTextColor) &&
+ (mxData->maLabelTextColor == rSet.mxData->maLabelTextColor) &&
+ (mxData->maWindowColor == rSet.mxData->maWindowColor) &&
+ (mxData->maWindowTextColor == rSet.mxData->maWindowTextColor) &&
+ (mxData->maDialogColor == rSet.mxData->maDialogColor) &&
+ (mxData->maDialogTextColor == rSet.mxData->maDialogTextColor) &&
+ (mxData->maWorkspaceColor == rSet.mxData->maWorkspaceColor) &&
+ (mxData->maMonoColor == rSet.mxData->maMonoColor) &&
+ (mxData->maFieldColor == rSet.mxData->maFieldColor) &&
+ (mxData->maFieldTextColor == rSet.mxData->maFieldTextColor) &&
+ (mxData->maActiveColor == rSet.mxData->maActiveColor) &&
+ (mxData->maActiveTextColor == rSet.mxData->maActiveTextColor) &&
+ (mxData->maActiveBorderColor == rSet.mxData->maActiveBorderColor) &&
+ (mxData->maDeactiveColor == rSet.mxData->maDeactiveColor) &&
+ (mxData->maDeactiveTextColor == rSet.mxData->maDeactiveTextColor) &&
+ (mxData->maDeactiveBorderColor == rSet.mxData->maDeactiveBorderColor) &&
+ (mxData->maMenuColor == rSet.mxData->maMenuColor) &&
+ (mxData->maMenuBarColor == rSet.mxData->maMenuBarColor) &&
+ (mxData->maMenuBarRolloverColor == rSet.mxData->maMenuBarRolloverColor) &&
+ (mxData->maMenuBorderColor == rSet.mxData->maMenuBorderColor) &&
+ (mxData->maMenuTextColor == rSet.mxData->maMenuTextColor) &&
+ (mxData->maMenuBarTextColor == rSet.mxData->maMenuBarTextColor) &&
+ (mxData->maMenuBarRolloverTextColor == rSet.mxData->maMenuBarRolloverTextColor) &&
+ (mxData->maMenuHighlightColor == rSet.mxData->maMenuHighlightColor) &&
+ (mxData->maMenuHighlightTextColor == rSet.mxData->maMenuHighlightTextColor) &&
+ (mxData->maHighlightColor == rSet.mxData->maHighlightColor) &&
+ (mxData->maHighlightTextColor == rSet.mxData->maHighlightTextColor) &&
+ (mxData->maTabTextColor == rSet.mxData->maTabTextColor) &&
+ (mxData->maTabRolloverTextColor == rSet.mxData->maTabRolloverTextColor) &&
+ (mxData->maTabHighlightTextColor == rSet.mxData->maTabHighlightTextColor) &&
+ (mxData->maActiveTabColor == rSet.mxData->maActiveTabColor) &&
+ (mxData->maInactiveTabColor == rSet.mxData->maInactiveTabColor) &&
+ (mxData->maDisableColor == rSet.mxData->maDisableColor) &&
+ (mxData->maHelpColor == rSet.mxData->maHelpColor) &&
+ (mxData->maHelpTextColor == rSet.mxData->maHelpTextColor) &&
+ (mxData->maLinkColor == rSet.mxData->maLinkColor) &&
+ (mxData->maVisitedLinkColor == rSet.mxData->maVisitedLinkColor) &&
+ (mxData->maToolTextColor == rSet.mxData->maToolTextColor) &&
+ (mxData->maAppFont == rSet.mxData->maAppFont) &&
+ (mxData->maHelpFont == rSet.mxData->maHelpFont) &&
+ (mxData->maTitleFont == rSet.mxData->maTitleFont) &&
+ (mxData->maFloatTitleFont == rSet.mxData->maFloatTitleFont) &&
+ (mxData->maMenuFont == rSet.mxData->maMenuFont) &&
+ (mxData->maToolFont == rSet.mxData->maToolFont) &&
+ (mxData->maGroupFont == rSet.mxData->maGroupFont) &&
+ (mxData->maLabelFont == rSet.mxData->maLabelFont) &&
+ (mxData->maRadioCheckFont == rSet.mxData->maRadioCheckFont) &&
+ (mxData->maPushButtonFont == rSet.mxData->maPushButtonFont) &&
+ (mxData->maFieldFont == rSet.mxData->maFieldFont) &&
+ (mxData->maIconFont == rSet.mxData->maIconFont) &&
+ (mxData->maTabFont == rSet.mxData->maTabFont) &&
+ (mxData->meUseImagesInMenus == rSet.mxData->meUseImagesInMenus) &&
+ (mxData->mbPreferredUseImagesInMenus == rSet.mxData->mbPreferredUseImagesInMenus) &&
+ (mxData->mbSkipDisabledInMenus == rSet.mxData->mbSkipDisabledInMenus) &&
+ (mxData->mbHideDisabledMenuItems == rSet.mxData->mbHideDisabledMenuItems) &&
+ (mxData->mbPreferredContextMenuShortcuts == rSet.mxData->mbPreferredContextMenuShortcuts)&&
+ (mxData->meContextMenuShortcuts == rSet.mxData->meContextMenuShortcuts) &&
+ (mxData->mbPrimaryButtonWarpsSlider == rSet.mxData->mbPrimaryButtonWarpsSlider) &&
+ (mxData->maFontColor == rSet.mxData->maFontColor) &&
+ (mxData->mnEdgeBlending == rSet.mxData->mnEdgeBlending) &&
+ (mxData->maEdgeBlendingTopLeftColor == rSet.mxData->maEdgeBlendingTopLeftColor) &&
+ (mxData->maEdgeBlendingBottomRightColor == rSet.mxData->maEdgeBlendingBottomRightColor) &&
+ (mxData->mnListBoxMaximumLineCount == rSet.mxData->mnListBoxMaximumLineCount) &&
+ (mxData->mnColorValueSetColumnCount == rSet.mxData->mnColorValueSetColumnCount) &&
+ (mxData->maListBoxPreviewDefaultLogicSize == rSet.mxData->maListBoxPreviewDefaultLogicSize) &&
+ (mxData->maListBoxPreviewDefaultPixelSize == rSet.mxData->maListBoxPreviewDefaultPixelSize) &&
+ (mxData->mbPreviewUsesCheckeredBackground == rSet.mxData->mbPreviewUsesCheckeredBackground);
+}
+
+ImplMiscData::ImplMiscData() :
+ mnEnableATT(TRISTATE_INDET),
+ mnDisablePrinting(TRISTATE_INDET)
+{
+ static const char* pEnv = getenv("SAL_DECIMALSEP_ENABLED" ); // set default without UI
+ mbEnableLocalizedDecimalSep = (pEnv != nullptr);
+}
+
+MiscSettings::MiscSettings()
+ : mxData(std::make_shared<ImplMiscData>())
+{
+}
+
+bool MiscSettings::operator ==( const MiscSettings& rSet ) const
+{
+ if ( mxData == rSet.mxData )
+ return true;
+
+ return (mxData->mnEnableATT == rSet.mxData->mnEnableATT ) &&
+ (mxData->mnDisablePrinting == rSet.mxData->mnDisablePrinting ) &&
+ (mxData->mbEnableLocalizedDecimalSep == rSet.mxData->mbEnableLocalizedDecimalSep );
+}
+
+bool
+MiscSettings::operator !=( const MiscSettings& rSet ) const
+{
+ return !(*this == rSet);
+}
+
+bool MiscSettings::GetDisablePrinting() const
+{
+ if( mxData->mnDisablePrinting == TRISTATE_INDET )
+ {
+ OUString aEnable =
+ vcl::SettingsConfigItem::get()->
+ getValue( "DesktopManagement",
+ "DisablePrinting" );
+ mxData->mnDisablePrinting = aEnable.equalsIgnoreAsciiCase("true") ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+
+ return mxData->mnDisablePrinting != TRISTATE_FALSE;
+}
+
+bool MiscSettings::GetEnableATToolSupport() const
+{
+
+#ifdef _WIN32
+ if( mxData->mnEnableATT == TRISTATE_INDET )
+ {
+ // Check in the Windows registry if an AT tool wants Accessibility support to
+ // be activated ..
+ HKEY hkey;
+
+ if( ERROR_SUCCESS == RegOpenKeyW(HKEY_CURRENT_USER,
+ L"Software\\LibreOffice\\Accessibility\\AtToolSupport",
+ &hkey) )
+ {
+ DWORD dwType;
+ wchar_t Data[6]; // possible values: "true", "false", "1", "0", DWORD
+ DWORD cbData = sizeof(Data);
+
+ if( ERROR_SUCCESS == RegQueryValueExW(hkey, L"SupportAssistiveTechnology",
+ nullptr, &dwType, reinterpret_cast<LPBYTE>(Data), &cbData) )
+ {
+ switch (dwType)
+ {
+ case REG_SZ:
+ mxData->mnEnableATT = ((0 == wcsicmp(Data, L"1")) || (0 == wcsicmp(Data, L"true"))) ? TRISTATE_TRUE : TRISTATE_FALSE;
+ break;
+ case REG_DWORD:
+ switch (reinterpret_cast<DWORD *>(Data)[0]) {
+ case 0:
+ mxData->mnEnableATT = TRISTATE_FALSE;
+ break;
+ case 1:
+ mxData->mnEnableATT = TRISTATE_TRUE;
+ break;
+ default:
+ mxData->mnEnableATT = TRISTATE_INDET;
+ //TODO: or TRISTATE_TRUE?
+ break;
+ }
+ break;
+ default:
+ // Unsupported registry type
+ break;
+ }
+ }
+
+ RegCloseKey(hkey);
+ }
+ }
+#endif
+
+ if( mxData->mnEnableATT == TRISTATE_INDET )
+ {
+ static const char* pEnv = getenv("SAL_ACCESSIBILITY_ENABLED" );
+ if( !pEnv || !*pEnv )
+ {
+ OUString aEnable =
+ vcl::SettingsConfigItem::get()->
+ getValue( "Accessibility",
+ "EnableATToolSupport" );
+ mxData->mnEnableATT = aEnable.equalsIgnoreAsciiCase("true") ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+ else
+ {
+ mxData->mnEnableATT = TRISTATE_TRUE;
+ }
+ }
+
+ return mxData->mnEnableATT != TRISTATE_FALSE;
+}
+
+#ifdef _WIN32
+void MiscSettings::SetEnableATToolSupport( bool bEnable )
+{
+ if ( (bEnable ? TRISTATE_TRUE : TRISTATE_FALSE) != mxData->mnEnableATT )
+ {
+ if( bEnable && !ImplInitAccessBridge() )
+ return;
+
+ HKEY hkey;
+
+ // If the accessibility key in the Windows registry exists, change it synchronously
+ if( ERROR_SUCCESS == RegOpenKeyW(HKEY_CURRENT_USER,
+ L"Software\\LibreOffice\\Accessibility\\AtToolSupport",
+ &hkey) )
+ {
+ DWORD dwType;
+ wchar_t Data[6]; // possible values: "true", "false", 1, 0
+ DWORD cbData = sizeof(Data);
+
+ if( ERROR_SUCCESS == RegQueryValueExW(hkey, L"SupportAssistiveTechnology",
+ nullptr, &dwType, reinterpret_cast<LPBYTE>(Data), &cbData) )
+ {
+ switch (dwType)
+ {
+ case REG_SZ:
+ RegSetValueExW(hkey, L"SupportAssistiveTechnology",
+ 0, dwType,
+ reinterpret_cast<const BYTE*>(bEnable ? L"true" : L"false"),
+ bEnable ? sizeof(L"true") : sizeof(L"false"));
+ break;
+ case REG_DWORD:
+ reinterpret_cast<DWORD *>(Data)[0] = bEnable ? 1 : 0;
+ RegSetValueExW(hkey, L"SupportAssistiveTechnology",
+ 0, dwType, reinterpret_cast<const BYTE*>(Data), sizeof(DWORD));
+ break;
+ default:
+ // Unsupported registry type
+ break;
+ }
+ }
+
+ RegCloseKey(hkey);
+ }
+
+ vcl::SettingsConfigItem::get()->
+ setValue( "Accessibility",
+ "EnableATToolSupport",
+ bEnable ? OUString("true") : OUString("false" ) );
+ mxData->mnEnableATT = bEnable ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+}
+#endif
+
+void MiscSettings::SetEnableLocalizedDecimalSep( bool bEnable )
+{
+ // copy if other references exist
+ if (mxData.use_count() > 1)
+ {
+ mxData = std::make_shared<ImplMiscData>(*mxData);
+ }
+ mxData->mbEnableLocalizedDecimalSep = bEnable;
+}
+
+bool MiscSettings::GetEnableLocalizedDecimalSep() const
+{
+ return mxData->mbEnableLocalizedDecimalSep;
+}
+
+HelpSettings::HelpSettings()
+ : mxData(std::make_shared<ImplHelpData>())
+{
+}
+
+bool HelpSettings::operator ==( const HelpSettings& rSet ) const
+{
+ if ( mxData == rSet.mxData )
+ return true;
+
+ return (mxData->mnTipTimeout == rSet.mxData->mnTipTimeout );
+}
+
+sal_Int32
+HelpSettings::GetTipDelay()
+{
+ return 500;
+}
+
+void
+HelpSettings::SetTipTimeout( sal_Int32 nTipTimeout )
+{
+ // copy if other references exist
+ if (mxData.use_count() > 1)
+ {
+ mxData = std::make_shared<ImplHelpData>(*mxData);
+ }
+ mxData->mnTipTimeout = nTipTimeout;
+}
+
+sal_Int32
+HelpSettings::GetTipTimeout() const
+{
+ return mxData->mnTipTimeout;
+}
+
+sal_Int32
+HelpSettings::GetBalloonDelay()
+{
+ return 1500;
+}
+
+bool
+HelpSettings::operator !=( const HelpSettings& rSet ) const
+{
+ return !(*this == rSet);
+}
+
+ImplAllSettingsData::ImplAllSettingsData()
+ :
+ maLocale( LANGUAGE_SYSTEM ),
+ maUILocale( LANGUAGE_SYSTEM )
+{
+ if (!utl::ConfigManager::IsFuzzing())
+ maMiscSettings.SetEnableLocalizedDecimalSep( maSysLocale.GetOptions().IsDecimalSeparatorAsLocale() );
+}
+
+ImplAllSettingsData::ImplAllSettingsData( const ImplAllSettingsData& rData ) :
+ maMouseSettings( rData.maMouseSettings ),
+ maStyleSettings( rData.maStyleSettings ),
+ maMiscSettings( rData.maMiscSettings ),
+ maHelpSettings( rData.maHelpSettings ),
+ maLocale( rData.maLocale ),
+ maUILocale( rData.maUILocale )
+{
+ // Create the cache objects new when their getter is called.
+}
+
+ImplAllSettingsData::~ImplAllSettingsData()
+{
+ mpLocaleDataWrapper.reset();
+ mpUILocaleDataWrapper.reset();
+ mpNeutralLocaleDataWrapper.reset();
+ mpI18nHelper.reset();
+ mpUII18nHelper.reset();
+}
+
+AllSettings::AllSettings()
+ : mxData(std::make_shared<ImplAllSettingsData>())
+{
+}
+
+void AllSettings::CopyData()
+{
+ // copy if other references exist
+ if (mxData.use_count() > 1)
+ {
+ mxData = std::make_shared<ImplAllSettingsData>(*mxData);
+ }
+
+}
+
+AllSettingsFlags AllSettings::Update( AllSettingsFlags nFlags, const AllSettings& rSet )
+{
+
+ AllSettingsFlags nChangeFlags = AllSettingsFlags::NONE;
+
+ if ( nFlags & AllSettingsFlags::MOUSE )
+ {
+ if ( mxData->maMouseSettings != rSet.mxData->maMouseSettings )
+ {
+ CopyData();
+ mxData->maMouseSettings = rSet.mxData->maMouseSettings;
+ nChangeFlags |= AllSettingsFlags::MOUSE;
+ }
+ }
+
+ if ( nFlags & AllSettingsFlags::STYLE )
+ {
+ if ( mxData->maStyleSettings != rSet.mxData->maStyleSettings )
+ {
+ CopyData();
+ mxData->maStyleSettings = rSet.mxData->maStyleSettings;
+ nChangeFlags |= AllSettingsFlags::STYLE;
+ }
+ }
+
+ if ( nFlags & AllSettingsFlags::MISC )
+ {
+ if ( mxData->maMiscSettings != rSet.mxData->maMiscSettings )
+ {
+ CopyData();
+ mxData->maMiscSettings = rSet.mxData->maMiscSettings;
+ nChangeFlags |= AllSettingsFlags::MISC;
+ }
+ }
+
+ if ( nFlags & AllSettingsFlags::LOCALE )
+ {
+ if ( mxData->maLocale != rSet.mxData->maLocale )
+ {
+ SetLanguageTag( rSet.mxData->maLocale );
+ nChangeFlags |= AllSettingsFlags::LOCALE;
+ }
+ }
+
+ return nChangeFlags;
+}
+
+AllSettingsFlags AllSettings::GetChangeFlags( const AllSettings& rSet ) const
+{
+
+ AllSettingsFlags nChangeFlags = AllSettingsFlags::NONE;
+
+ if ( mxData->maStyleSettings != rSet.mxData->maStyleSettings )
+ nChangeFlags |= AllSettingsFlags::STYLE;
+
+ if ( mxData->maMiscSettings != rSet.mxData->maMiscSettings )
+ nChangeFlags |= AllSettingsFlags::MISC;
+
+ if ( mxData->maLocale != rSet.mxData->maLocale )
+ nChangeFlags |= AllSettingsFlags::LOCALE;
+
+ return nChangeFlags;
+}
+
+bool AllSettings::operator ==( const AllSettings& rSet ) const
+{
+ if ( mxData == rSet.mxData )
+ return true;
+
+ if ( (mxData->maMouseSettings == rSet.mxData->maMouseSettings) &&
+ (mxData->maStyleSettings == rSet.mxData->maStyleSettings) &&
+ (mxData->maMiscSettings == rSet.mxData->maMiscSettings) &&
+ (mxData->maHelpSettings == rSet.mxData->maHelpSettings) &&
+ (mxData->maLocale == rSet.mxData->maLocale) )
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void AllSettings::SetLanguageTag(const OUString& rLanguage, bool bCanonicalize)
+{
+ SetLanguageTag(LanguageTag(rLanguage, bCanonicalize));
+}
+
+void AllSettings::SetLanguageTag( const LanguageTag& rLanguageTag )
+{
+ if (mxData->maLocale == rLanguageTag)
+ return;
+
+ CopyData();
+
+ mxData->maLocale = rLanguageTag;
+
+ if ( mxData->mpLocaleDataWrapper )
+ {
+ mxData->mpLocaleDataWrapper.reset();
+ }
+ if ( mxData->mpI18nHelper )
+ {
+ mxData->mpI18nHelper.reset();
+ }
+}
+
+namespace
+{
+ bool GetConfigLayoutRTL(bool bMath)
+ {
+ static const char* pEnv = getenv("SAL_RTL_ENABLED" );
+ static int nUIMirroring = -1; // -1: undef, 0: auto, 1: on 2: off
+
+ // environment always overrides
+ if( pEnv )
+ return true;
+
+ bool bRTL = false;
+
+ if( nUIMirroring == -1 )
+ {
+ nUIMirroring = 0; // ask configuration only once
+ utl::OConfigurationNode aNode = utl::OConfigurationTreeRoot::tryCreateWithComponentContext(
+ comphelper::getProcessComponentContext(),
+ "org.openoffice.Office.Common/I18N/CTL" ); // note: case sensitive !
+ if ( aNode.isValid() )
+ {
+ bool bTmp = bool();
+ css::uno::Any aValue = aNode.getNodeValue( "UIMirroring" );
+ if( aValue >>= bTmp )
+ {
+ // found true or false; if it was nil, nothing is changed
+ nUIMirroring = bTmp ? 1 : 2;
+ }
+ }
+ }
+
+ if( nUIMirroring == 0 ) // no config found (eg, setup) or default (nil) was set: check language
+ {
+ LanguageType aLang = SvtSysLocaleOptions().GetRealUILanguageTag().getLanguageType();
+ if (bMath)
+ bRTL = MsLangId::isRightToLeftMath( aLang );
+ else
+ bRTL = MsLangId::isRightToLeft( aLang );
+ }
+ else
+ bRTL = (nUIMirroring == 1);
+
+ return bRTL;
+ }
+}
+
+bool AllSettings::GetLayoutRTL()
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return false;
+ return GetConfigLayoutRTL(false);
+}
+
+bool AllSettings::GetMathLayoutRTL()
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return false;
+ return GetConfigLayoutRTL(true);
+}
+
+const LanguageTag& AllSettings::GetLanguageTag() const
+{
+ if (utl::ConfigManager::IsFuzzing())
+ {
+ static LanguageTag aRet("en-US");
+ return aRet;
+ }
+
+ if (comphelper::LibreOfficeKit::isActive())
+ return comphelper::LibreOfficeKit::getLanguageTag();
+
+ // SYSTEM locale means: use settings from SvtSysLocale that is resolved
+ if ( mxData->maLocale.isSystemLocale() )
+ mxData->maLocale = mxData->maSysLocale.GetLanguageTag();
+
+ return mxData->maLocale;
+}
+
+const LanguageTag& AllSettings::GetUILanguageTag() const
+{
+ if (utl::ConfigManager::IsFuzzing())
+ {
+ static LanguageTag aRet("en-US");
+ return aRet;
+ }
+
+ if (comphelper::LibreOfficeKit::isActive())
+ return comphelper::LibreOfficeKit::getLanguageTag();
+
+ // the UILocale is never changed
+ if ( mxData->maUILocale.isSystemLocale() )
+ mxData->maUILocale = mxData->maSysLocale.GetUILanguageTag();
+
+ return mxData->maUILocale;
+}
+
+const LocaleDataWrapper& AllSettings::GetLocaleDataWrapper() const
+{
+ if ( !mxData->mpLocaleDataWrapper )
+ const_cast<AllSettings*>(this)->mxData->mpLocaleDataWrapper.reset( new LocaleDataWrapper(
+ comphelper::getProcessComponentContext(), GetLanguageTag() ) );
+ return *mxData->mpLocaleDataWrapper;
+}
+
+const LocaleDataWrapper& AllSettings::GetUILocaleDataWrapper() const
+{
+ if ( !mxData->mpUILocaleDataWrapper )
+ const_cast<AllSettings*>(this)->mxData->mpUILocaleDataWrapper.reset( new LocaleDataWrapper(
+ comphelper::getProcessComponentContext(), GetUILanguageTag() ) );
+ return *mxData->mpUILocaleDataWrapper;
+}
+
+const LocaleDataWrapper& AllSettings::GetNeutralLocaleDataWrapper() const
+{
+ if ( !mxData->mpNeutralLocaleDataWrapper )
+ const_cast<AllSettings*>(this)->mxData->mpNeutralLocaleDataWrapper.reset( new LocaleDataWrapper(
+ comphelper::getProcessComponentContext(), LanguageTag("en-US") ) );
+ return *mxData->mpNeutralLocaleDataWrapper;
+}
+
+const vcl::I18nHelper& AllSettings::GetLocaleI18nHelper() const
+{
+ if ( !mxData->mpI18nHelper ) {
+ const_cast<AllSettings*>(this)->mxData->mpI18nHelper.reset( new vcl::I18nHelper(
+ comphelper::getProcessComponentContext(), GetLanguageTag() ) );
+ }
+ return *mxData->mpI18nHelper;
+}
+
+const vcl::I18nHelper& AllSettings::GetUILocaleI18nHelper() const
+{
+ if ( !mxData->mpUII18nHelper ) {
+ const_cast<AllSettings*>(this)->mxData->mpUII18nHelper.reset( new vcl::I18nHelper(
+ comphelper::getProcessComponentContext(), GetUILanguageTag() ) );
+ }
+ return *mxData->mpUII18nHelper;
+}
+
+void AllSettings::LocaleSettingsChanged( ConfigurationHints nHint )
+{
+ AllSettings aAllSettings( Application::GetSettings() );
+ if ( nHint & ConfigurationHints::DecSep )
+ {
+ MiscSettings aMiscSettings = aAllSettings.GetMiscSettings();
+ bool bIsDecSepAsLocale = aAllSettings.mxData->maSysLocale.GetOptions().IsDecimalSeparatorAsLocale();
+ if ( aMiscSettings.GetEnableLocalizedDecimalSep() != bIsDecSepAsLocale )
+ {
+ aMiscSettings.SetEnableLocalizedDecimalSep( bIsDecSepAsLocale );
+ aAllSettings.SetMiscSettings( aMiscSettings );
+ }
+ }
+
+ if ( nHint & ConfigurationHints::Locale )
+ aAllSettings.SetLanguageTag( aAllSettings.mxData->maSysLocale.GetOptions().GetLanguageTag() );
+
+ Application::SetSettings( aAllSettings );
+}
+
+const StyleSettings&
+AllSettings::GetStyleSettings() const
+{
+ return mxData->maStyleSettings;
+}
+
+StyleSettingsOptions
+StyleSettings::GetOptions() const
+{
+ return mxData->mnOptions;
+}
+
+std::vector<vcl::IconThemeInfo> const &
+StyleSettings::GetInstalledIconThemes() const
+{
+ if (!mxData->mIconThemeScanner) {
+ const_cast<StyleSettings*>(this)->mxData->mIconThemeScanner = vcl::IconThemeScanner::Create(vcl::IconThemeScanner::GetStandardIconThemePath());
+ }
+ return mxData->mIconThemeScanner->GetFoundIconThemes();
+}
+
+/*static*/ OUString
+StyleSettings::GetAutomaticallyChosenIconTheme() const
+{
+ OUString desktopEnvironment = Application::GetDesktopEnvironment();
+ if (!mxData->mIconThemeScanner) {
+ const_cast<StyleSettings*>(this)->mxData->mIconThemeScanner = vcl::IconThemeScanner::Create(vcl::IconThemeScanner::GetStandardIconThemePath());
+ }
+ OUString themeName = mxData->mIconThemeSelector->SelectIconThemeForDesktopEnvironment(
+ mxData->mIconThemeScanner->GetFoundIconThemes(),
+ desktopEnvironment
+ );
+ return themeName;
+}
+
+void
+StyleSettings::SetIconTheme(const OUString& theme)
+{
+ CopyData();
+ mxData->mIconTheme = theme;
+}
+
+OUString
+StyleSettings::DetermineIconTheme() const
+{
+ OUString sTheme(mxData->mIconTheme);
+ if (sTheme.isEmpty())
+ {
+ if (utl::ConfigManager::IsFuzzing())
+ sTheme = "colibre";
+ else
+ {
+ // read from the configuration, or fallback to what the desktop wants
+ sTheme = officecfg::Office::Common::Misc::SymbolStyle::get();
+
+ if (sTheme.isEmpty() || sTheme == "auto")
+ sTheme = GetAutomaticallyChosenIconTheme();
+ }
+ }
+
+ if (!mxData->mIconThemeScanner) {
+ const_cast<StyleSettings*>(this)->mxData->mIconThemeScanner = vcl::IconThemeScanner::Create(vcl::IconThemeScanner::GetStandardIconThemePath());
+ }
+ OUString r = mxData->mIconThemeSelector->SelectIconTheme(
+ mxData->mIconThemeScanner->GetFoundIconThemes(),
+ sTheme);
+ return r;
+}
+
+void
+StyleSettings::SetHighContrastMode(bool bHighContrast )
+{
+ if (mxData->mbHighContrast == bHighContrast) {
+ return;
+ }
+
+ CopyData();
+ mxData->mbHighContrast = bHighContrast;
+ mxData->mIconThemeSelector->SetUseHighContrastTheme(bHighContrast);
+}
+
+bool
+StyleSettings::GetHighContrastMode() const
+{
+ return mxData->mbHighContrast;
+}
+
+void
+StyleSettings::SetPreferredIconTheme(const OUString& theme, bool bDarkIconTheme)
+{
+ const bool bChanged = mxData->mIconThemeSelector->SetPreferredIconTheme(theme, bDarkIconTheme);
+ if (bChanged)
+ {
+ // clear this so it is recalculated if it was selected as the automatic theme
+ mxData->mIconTheme.clear();
+ }
+}
+
+void
+AllSettings::SetMouseSettings( const MouseSettings& rSet )
+{
+ CopyData();
+ mxData->maMouseSettings = rSet;
+}
+
+const MouseSettings&
+AllSettings::GetMouseSettings() const
+{
+ return mxData->maMouseSettings;
+}
+
+void
+AllSettings::SetStyleSettings( const StyleSettings& rSet )
+{
+ CopyData();
+ mxData->maStyleSettings = rSet;
+}
+
+void
+AllSettings::SetMiscSettings( const MiscSettings& rSet )
+{
+ CopyData();
+ mxData->maMiscSettings = rSet;
+}
+
+const MiscSettings&
+AllSettings::GetMiscSettings() const
+{
+ return mxData->maMiscSettings;
+}
+
+void
+AllSettings::SetHelpSettings( const HelpSettings& rSet )
+{
+ CopyData();
+ mxData->maHelpSettings = rSet;
+}
+
+const HelpSettings&
+AllSettings::GetHelpSettings() const
+{
+ return mxData->maHelpSettings;
+}
+
+bool
+AllSettings::operator !=( const AllSettings& rSet ) const
+{
+ return !(*this == rSet);
+}
+
+SvtSysLocale&
+AllSettings::GetSysLocale()
+{
+ return mxData->maSysLocale;
+}
+
+
+void StyleSettings::BatchSetBackgrounds( const Color &aBackColor,
+ bool bCheckedColorSpecialCase )
+{
+ Set3DColors( aBackColor );
+ SetFaceColor( aBackColor );
+ SetDialogColor( aBackColor );
+ SetWorkspaceColor( aBackColor );
+
+ if (bCheckedColorSpecialCase)
+ SetCheckedColorSpecialCase();
+}
+
+void StyleSettings::BatchSetFonts( const vcl::Font& aAppFont,
+ const vcl::Font& aLabelFont )
+{
+ SetAppFont( aAppFont );
+ SetPushButtonFont( aAppFont );
+ SetToolFont( aAppFont );
+ SetHelpFont( aAppFont );
+
+ SetMenuFont( aLabelFont );
+ SetTabFont( aLabelFont );
+ SetLabelFont( aLabelFont );
+ SetRadioCheckFont( aLabelFont );
+ SetFieldFont( aLabelFont );
+ SetGroupFont( aLabelFont );
+ SetIconFont( aLabelFont );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/sound.cxx b/vcl/source/app/sound.cxx
new file mode 100644
index 000000000..024ba9ebc
--- /dev/null
+++ b/vcl/source/app/sound.cxx
@@ -0,0 +1,38 @@
+/* -*- 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 <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/sound.hxx>
+
+#include <salframe.hxx>
+#include <svdata.hxx>
+
+void Sound::Beep()
+{
+ // #i91990#
+ if (Application::IsHeadlessModeEnabled())
+ return;
+
+ vcl::Window* pWindow = ImplGetDefaultWindow();
+
+ pWindow->ImplGetFrame()->Beep();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/stdtext.cxx b/vcl/source/app/stdtext.cxx
new file mode 100644
index 000000000..bef130dc7
--- /dev/null
+++ b/vcl/source/app/stdtext.cxx
@@ -0,0 +1,124 @@
+/* -*- 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 <vcl/stdtext.hxx>
+#include <vcl/image.hxx>
+#include <vcl/weld.hxx>
+#include <bitmaps.hlst>
+#include <strings.hrc>
+#include <svdata.hxx>
+
+void ShowServiceNotAvailableError(weld::Widget* pParent,
+ std::u16string_view rServiceName, bool bError)
+{
+ OUString aText = VclResId(SV_STDTEXT_SERVICENOTAVAILABLE).replaceAll("%s", rServiceName);
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent,
+ bError ? VclMessageType::Error : VclMessageType::Warning, VclButtonsType::Ok, aText));
+ xBox->run();
+}
+
+static void ImplInitMsgBoxImageList()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ std::vector<Image> &rImages = pSVData->mpWinData->maMsgBoxImgList;
+ if (rImages.empty())
+ {
+ rImages.emplace_back(StockImage::Yes, SV_RESID_BITMAP_ERRORBOX);
+ rImages.emplace_back(StockImage::Yes, SV_RESID_BITMAP_QUERYBOX);
+ rImages.emplace_back(StockImage::Yes, SV_RESID_BITMAP_WARNINGBOX);
+ rImages.emplace_back(StockImage::Yes, SV_RESID_BITMAP_INFOBOX);
+ }
+}
+
+Image const & GetStandardInfoBoxImage()
+{
+ ImplInitMsgBoxImageList();
+ return ImplGetSVData()->mpWinData->maMsgBoxImgList[3];
+}
+
+OUString GetStandardInfoBoxText()
+{
+ return VclResId(SV_MSGBOX_INFO);
+}
+
+Image const & GetStandardWarningBoxImage()
+{
+ ImplInitMsgBoxImageList();
+ return ImplGetSVData()->mpWinData->maMsgBoxImgList[2];
+}
+
+OUString GetStandardWarningBoxText()
+{
+ return VclResId(SV_MSGBOX_WARNING);
+}
+
+Image const & GetStandardErrorBoxImage()
+{
+ ImplInitMsgBoxImageList();
+ return ImplGetSVData()->mpWinData->maMsgBoxImgList[0];
+}
+
+OUString GetStandardErrorBoxText()
+{
+ return VclResId(SV_MSGBOX_ERROR);
+}
+
+Image const & GetStandardQueryBoxImage()
+{
+ ImplInitMsgBoxImageList();
+ return ImplGetSVData()->mpWinData->maMsgBoxImgList[1];
+}
+
+OUString GetStandardQueryBoxText()
+{
+ return VclResId(SV_MSGBOX_QUERY);
+}
+
+OUString GetStandardText(StandardButtonType eButton)
+{
+ static TranslateId aResIdAry[static_cast<int>(StandardButtonType::Count)] =
+ {
+ // http://lists.freedesktop.org/archives/libreoffice/2013-January/044513.html
+ // Under windows we don't want accelerators on ok/cancel but do on other
+ // buttons
+#ifdef _WIN32
+ SV_BUTTONTEXT_OK_NOMNEMONIC,
+ SV_BUTTONTEXT_CANCEL_NOMNEMONIC,
+#else
+ SV_BUTTONTEXT_OK,
+ SV_BUTTONTEXT_CANCEL,
+#endif
+ SV_BUTTONTEXT_YES,
+ SV_BUTTONTEXT_NO,
+ SV_BUTTONTEXT_RETRY,
+ SV_BUTTONTEXT_HELP,
+ SV_BUTTONTEXT_CLOSE,
+ SV_BUTTONTEXT_MORE,
+ SV_BUTTONTEXT_IGNORE,
+ SV_BUTTONTEXT_ABORT,
+ SV_BUTTONTEXT_LESS,
+ STR_WIZDLG_PREVIOUS,
+ STR_WIZDLG_NEXT,
+ STR_WIZDLG_FINISH,
+ };
+
+ return VclResId(aResIdAry[static_cast<sal_uInt16>(eButton)]);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/svapp.cxx b/vcl/source/app/svapp.cxx
new file mode 100644
index 000000000..d63b8d34b
--- /dev/null
+++ b/vcl/source/app/svapp.cxx
@@ -0,0 +1,1834 @@
+/* -*- 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 <config_features.h>
+
+#include <osl/file.hxx>
+#include <osl/thread.hxx>
+#include <osl/module.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <sal/log.hxx>
+
+#include <tools/debug.hxx>
+#include <tools/time.hxx>
+#include <tools/stream.hxx>
+
+#include <unotools/configmgr.hxx>
+#include <unotools/resmgr.hxx>
+#include <unotools/syslocale.hxx>
+#include <unotools/syslocaleoptions.hxx>
+
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/dialoghelper.hxx>
+#include <vcl/lok.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/keycod.hxx>
+#include <vcl/event.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/scheduler.hxx>
+#include <vcl/skia/SkiaHelper.hxx>
+
+#include <salinst.hxx>
+#include <salframe.hxx>
+#include <salsys.hxx>
+#include <svdata.hxx>
+#include <displayconnectiondispatch.hxx>
+#include <window.h>
+#include <accmgr.hxx>
+#include <strings.hrc>
+#include <strings.hxx>
+#if OSL_DEBUG_LEVEL > 0
+#include <schedulerimpl.hxx>
+#endif
+
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/awt/XToolkit.hpp>
+#include <comphelper/lok.hxx>
+#include <comphelper/solarmutex.hxx>
+#include <osl/process.h>
+
+#include <cassert>
+#include <limits>
+#include <string_view>
+#include <utility>
+#include <thread>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+namespace {
+void InitSettings(ImplSVData* pSVData);
+}
+
+// keycodes handled internally by VCL
+vcl::KeyCode const ReservedKeys[]
+{
+ vcl::KeyCode(KEY_F1,0) ,
+ vcl::KeyCode(KEY_F1,KEY_SHIFT) ,
+ vcl::KeyCode(KEY_F1,KEY_MOD1) ,
+ vcl::KeyCode(KEY_F2,KEY_SHIFT) ,
+ vcl::KeyCode(KEY_F4,KEY_MOD1) ,
+ vcl::KeyCode(KEY_F4,KEY_MOD2) ,
+ vcl::KeyCode(KEY_F4,KEY_MOD1|KEY_MOD2) ,
+ vcl::KeyCode(KEY_F6,0) ,
+ vcl::KeyCode(KEY_F6,KEY_MOD1) ,
+ vcl::KeyCode(KEY_F6,KEY_SHIFT) ,
+ vcl::KeyCode(KEY_F6,KEY_MOD1|KEY_SHIFT) ,
+ vcl::KeyCode(KEY_F10,0)
+#ifdef UNX
+ ,
+ vcl::KeyCode(KEY_1,KEY_SHIFT|KEY_MOD1),
+ vcl::KeyCode(KEY_2,KEY_SHIFT|KEY_MOD1),
+ vcl::KeyCode(KEY_3,KEY_SHIFT|KEY_MOD1),
+ vcl::KeyCode(KEY_4,KEY_SHIFT|KEY_MOD1),
+ vcl::KeyCode(KEY_5,KEY_SHIFT|KEY_MOD1),
+ vcl::KeyCode(KEY_6,KEY_SHIFT|KEY_MOD1),
+ vcl::KeyCode(KEY_7,KEY_SHIFT|KEY_MOD1),
+ vcl::KeyCode(KEY_8,KEY_SHIFT|KEY_MOD1),
+ vcl::KeyCode(KEY_9,KEY_SHIFT|KEY_MOD1),
+ vcl::KeyCode(KEY_0,KEY_SHIFT|KEY_MOD1),
+ vcl::KeyCode(KEY_ADD,KEY_SHIFT|KEY_MOD1)
+#endif
+};
+
+extern "C" {
+ typedef UnoWrapperBase* (*FN_TkCreateUnoWrapper)();
+}
+
+struct ImplPostEventData
+{
+ VclPtr<vcl::Window> mpWin;
+ ImplSVEvent * mnEventId;
+ MouseEvent maMouseEvent;
+ VclEventId mnEvent;
+ KeyEvent maKeyEvent;
+ GestureEvent maGestureEvent;
+
+ ImplPostEventData(VclEventId nEvent, vcl::Window* pWin, const KeyEvent& rKeyEvent)
+ : mpWin(pWin)
+ , mnEventId(nullptr)
+ , mnEvent(nEvent)
+ , maKeyEvent(rKeyEvent)
+ {}
+ ImplPostEventData(VclEventId nEvent, vcl::Window* pWin, const MouseEvent& rMouseEvent)
+ : mpWin(pWin)
+ , mnEventId(nullptr)
+ , maMouseEvent(rMouseEvent)
+ , mnEvent(nEvent)
+ {}
+ ImplPostEventData(VclEventId nEvent, vcl::Window* pWin, const GestureEvent& rGestureEvent)
+ : mpWin(pWin)
+ , mnEventId(nullptr)
+ , mnEvent(nEvent)
+ , maGestureEvent(rGestureEvent)
+ {}
+};
+
+Application* GetpApp()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData )
+ return nullptr;
+ return pSVData->mpApp;
+}
+
+Application::Application()
+{
+ // useful for themes at least, perhaps extensions too
+ OUString aVar("LIBO_VERSION"), aValue(LIBO_VERSION_DOTTED);
+ osl_setEnvironment(aVar.pData, aValue.pData);
+
+ ImplGetSVData()->mpApp = this;
+ m_pCallbackData = nullptr;
+ m_pCallback = nullptr;
+}
+
+Application::~Application()
+{
+ ImplDeInitSVData();
+ ImplGetSVData()->mpApp = nullptr;
+}
+
+int Application::Main()
+{
+ SAL_WARN("vcl", "Application is a base class and should be overridden.");
+ return EXIT_SUCCESS;
+}
+
+bool Application::QueryExit()
+{
+ WorkWindow* pAppWin = ImplGetSVData()->maFrameData.mpAppWin;
+
+ // call the close handler of the application window
+ if ( pAppWin )
+ return pAppWin->Close();
+ else
+ return true;
+}
+
+void Application::Shutdown()
+{
+}
+
+void Application::Init()
+{
+}
+
+void Application::InitFinished()
+{
+}
+
+void Application::DeInit()
+{
+}
+
+sal_uInt16 Application::GetCommandLineParamCount()
+{
+ return static_cast<sal_uInt16>(osl_getCommandArgCount());
+}
+
+OUString Application::GetCommandLineParam( sal_uInt16 nParam )
+{
+ OUString aParam;
+ osl_getCommandArg( nParam, &aParam.pData );
+ return aParam;
+}
+
+OUString Application::GetAppFileName()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ SAL_WARN_IF( !pSVData->maAppData.mxAppFileName, "vcl", "AppFileName should be set to something after SVMain!" );
+ if ( pSVData->maAppData.mxAppFileName )
+ return *pSVData->maAppData.mxAppFileName;
+
+ /*
+ * provide a fallback for people without initialized vcl here (like setup
+ * in responsefile mode)
+ */
+ OUString aAppFileName;
+ OUString aExeFileName;
+ osl_getExecutableFile(&aExeFileName.pData);
+
+ // convert path to native file format
+ osl::FileBase::getSystemPathFromFileURL(aExeFileName, aAppFileName);
+
+ return aAppFileName;
+}
+
+void Application::Exception( ExceptionCategory nCategory )
+{
+ switch ( nCategory )
+ {
+ // System has precedence (so do nothing)
+ case ExceptionCategory::System:
+ case ExceptionCategory::UserInterface:
+ break;
+ default:
+ Abort("Unknown Error");
+ break;
+ }
+}
+
+void Application::Abort( const OUString& rErrorText )
+{
+ //HACK: Dump core iff --norestore command line argument is given (assuming
+ // this process is run by developers who are interested in cores, vs. end
+ // users who are not):
+#if OSL_DEBUG_LEVEL > 0
+ bool dumpCore = true;
+#else
+ bool dumpCore = false;
+ sal_uInt16 n = GetCommandLineParamCount();
+ for (sal_uInt16 i = 0; i != n; ++i) {
+ if (GetCommandLineParam(i) == "--norestore") {
+ dumpCore = true;
+ break;
+ }
+ }
+#endif
+
+ SalAbort( rErrorText, dumpCore );
+}
+
+size_t Application::GetReservedKeyCodeCount()
+{
+ return SAL_N_ELEMENTS(ReservedKeys);
+}
+
+const vcl::KeyCode* Application::GetReservedKeyCode( size_t i )
+{
+ if( i >= GetReservedKeyCodeCount() )
+ return nullptr;
+ else
+ return &ReservedKeys[i];
+}
+
+IMPL_STATIC_LINK_NOARG( ImplSVAppData, ImplEndAllPopupsMsg, void*, void )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ while (pSVData->mpWinData->mpFirstFloat)
+ pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel);
+}
+
+IMPL_STATIC_LINK_NOARG( ImplSVAppData, ImplEndAllDialogsMsg, void*, void )
+{
+ vcl::Window* pAppWindow = Application::GetFirstTopLevelWindow();
+ while (pAppWindow)
+ {
+ vcl::EndAllDialogs(pAppWindow);
+ pAppWindow = Application::GetNextTopLevelWindow(pAppWindow);
+ }
+}
+
+void Application::EndAllDialogs()
+{
+ Application::PostUserEvent( LINK( nullptr, ImplSVAppData, ImplEndAllDialogsMsg ) );
+}
+
+void Application::EndAllPopups()
+{
+ Application::PostUserEvent( LINK( nullptr, ImplSVAppData, ImplEndAllPopupsMsg ) );
+}
+
+void Application::notifyWindow(vcl::LOKWindowId /*nLOKWindowId*/,
+ const OUString& /*rAction*/,
+ const std::vector<vcl::LOKPayloadItem>& /*rPayload = std::vector<LOKPayloadItem>()*/) const
+{
+}
+
+void Application::libreOfficeKitViewCallback(int nType, const char* pPayload) const
+{
+ if (!comphelper::LibreOfficeKit::isActive())
+ return;
+
+ if (m_pCallback)
+ {
+ m_pCallback(nType, pPayload, m_pCallbackData);
+ }
+}
+
+
+namespace
+{
+ VclPtr<vcl::Window> GetEventWindow()
+ {
+ VclPtr<vcl::Window> xWin(Application::GetFirstTopLevelWindow());
+ while (xWin)
+ {
+ if (xWin->IsVisible())
+ break;
+ xWin.reset(Application::GetNextTopLevelWindow(xWin));
+ }
+ return xWin;
+ }
+
+ bool InjectKeyEvent(SvStream& rStream)
+ {
+ VclPtr<vcl::Window> xWin(GetEventWindow());
+ if (!xWin)
+ return false;
+
+ // skip the first available cycle and insert on the next one when we
+ // are trying the initial event, flagged by a triggered but undeleted
+ // mpEventTestingIdle
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->maAppData.mpEventTestingIdle)
+ {
+ delete pSVData->maAppData.mpEventTestingIdle;
+ pSVData->maAppData.mpEventTestingIdle = nullptr;
+ return false;
+ }
+
+ sal_uInt16 nCode, nCharCode;
+ rStream.ReadUInt16(nCode);
+ rStream.ReadUInt16(nCharCode);
+ if (!rStream.good())
+ return false;
+
+ KeyEvent aVCLKeyEvt(nCharCode, nCode);
+ Application::PostKeyEvent(VclEventId::WindowKeyInput, xWin.get(), &aVCLKeyEvt);
+ Application::PostKeyEvent(VclEventId::WindowKeyUp, xWin.get(), &aVCLKeyEvt);
+ return true;
+ }
+
+ void CloseDialogsAndQuit()
+ {
+ Application::EndAllPopups();
+ Application::EndAllDialogs();
+ Application::PostUserEvent( LINK( nullptr, ImplSVAppData, ImplPrepareExitMsg ) );
+ }
+}
+
+IMPL_LINK_NOARG(ImplSVAppData, VclEventTestingHdl, Timer *, void)
+{
+ if (Application::AnyInput())
+ {
+ mpEventTestingIdle->Start();
+ }
+ else
+ {
+ Application::PostUserEvent( LINK( nullptr, ImplSVAppData, ImplVclEventTestingHdl ) );
+ }
+}
+
+IMPL_STATIC_LINK_NOARG( ImplSVAppData, ImplVclEventTestingHdl, void*, void )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ SAL_INFO("vcl.eventtesting", "EventTestLimit is " << pSVData->maAppData.mnEventTestLimit);
+ if (pSVData->maAppData.mnEventTestLimit == 0)
+ {
+ delete pSVData->maAppData.mpEventTestInput;
+ SAL_INFO("vcl.eventtesting", "Event Limit reached, exiting" << pSVData->maAppData.mnEventTestLimit);
+ CloseDialogsAndQuit();
+ }
+ else
+ {
+ if (InjectKeyEvent(*pSVData->maAppData.mpEventTestInput))
+ --pSVData->maAppData.mnEventTestLimit;
+ if (!pSVData->maAppData.mpEventTestInput->good())
+ {
+ SAL_INFO("vcl.eventtesting", "Event Input exhausted, exit next cycle");
+ pSVData->maAppData.mnEventTestLimit = 0;
+ }
+ Application::PostUserEvent( LINK( nullptr, ImplSVAppData, ImplVclEventTestingHdl ) );
+ }
+}
+
+IMPL_STATIC_LINK_NOARG( ImplSVAppData, ImplPrepareExitMsg, void*, void )
+{
+ //now close top level frames
+ (void)GetpApp()->QueryExit();
+}
+
+void Application::Execute()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mbInAppExecute = true;
+ pSVData->maAppData.mbAppQuit = false;
+
+ if (Application::IsEventTestingModeEnabled())
+ {
+ pSVData->maAppData.mnEventTestLimit = 50;
+ pSVData->maAppData.mpEventTestingIdle = new Idle("eventtesting");
+ pSVData->maAppData.mpEventTestingIdle->SetInvokeHandler(LINK(&(pSVData->maAppData), ImplSVAppData, VclEventTestingHdl));
+ pSVData->maAppData.mpEventTestingIdle->SetPriority(TaskPriority::HIGH_IDLE);
+ pSVData->maAppData.mpEventTestInput = new SvFileStream("eventtesting", StreamMode::READ);
+ pSVData->maAppData.mpEventTestingIdle->Start();
+ }
+
+ int nExitCode = 0;
+ if (!pSVData->mpDefInst->DoExecute(nExitCode))
+ {
+ if (Application::IsOnSystemEventLoop())
+ {
+ SAL_WARN("vcl.schedule", "Can't omit DoExecute when running on system event loop!");
+ std::abort();
+ }
+ while (!pSVData->maAppData.mbAppQuit)
+ Application::Yield();
+ }
+
+ pSVData->maAppData.mbInAppExecute = false;
+
+ GetpApp()->Shutdown();
+}
+
+static bool ImplYield(bool i_bWait, bool i_bAllEvents)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ SAL_INFO("vcl.schedule", "Enter ImplYield: " << (i_bWait ? "wait" : "no wait") <<
+ ": " << (i_bAllEvents ? "all events" : "one event"));
+
+ // there's a data race here on WNT only because ImplYield may be
+ // called without SolarMutex; but the only remaining use of mnDispatchLevel
+ // is in OSX specific code
+ pSVData->maAppData.mnDispatchLevel++;
+
+ // do not wait for events if application was already quit; in that
+ // case only dispatch events already available
+ bool bProcessedEvent = pSVData->mpDefInst->DoYield(
+ i_bWait && !pSVData->maAppData.mbAppQuit, i_bAllEvents );
+
+ pSVData->maAppData.mnDispatchLevel--;
+
+ DBG_TESTSOLARMUTEX(); // must be locked on return from Yield
+
+ SAL_INFO("vcl.schedule", "Leave ImplYield with return " << bProcessedEvent );
+ return bProcessedEvent;
+}
+
+bool Application::Reschedule( bool i_bAllEvents )
+{
+ static const bool bAbort = Application::IsOnSystemEventLoop();
+ if (bAbort)
+ {
+ SAL_WARN("vcl.schedule", "Application::Reschedule(" << i_bAllEvents << ")");
+ std::abort();
+ }
+ return ImplYield(false, i_bAllEvents);
+}
+
+bool Application::IsOnSystemEventLoop()
+{
+ return ImplGetSVData()->maAppData.m_bUseSystemLoop;
+}
+
+void Scheduler::ProcessEventsToIdle()
+{
+ int nSanity = 1;
+ while (ImplYield(false, true))
+ {
+ if (0 == ++nSanity % 1000)
+ {
+ SAL_WARN("vcl.schedule", "ProcessEventsToIdle: " << nSanity);
+ }
+ }
+#if OSL_DEBUG_LEVEL > 0
+ // If we yield from a non-main thread we just can guarantee that all idle
+ // events were processed at some point, but our check can't prevent further
+ // processing in the main thread, which may add new events, so skip it.
+ const ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData->mpDefInst->IsMainThread() )
+ return;
+ for (int nTaskPriority = 0; nTaskPriority < PRIO_COUNT; ++nTaskPriority)
+ {
+ const ImplSchedulerData* pSchedulerData = pSVData->maSchedCtx.mpFirstSchedulerData[nTaskPriority];
+ while (pSchedulerData)
+ {
+ assert(!pSchedulerData->mbInScheduler);
+ if (pSchedulerData->mpTask)
+ {
+ Idle *pIdle = dynamic_cast<Idle*>(pSchedulerData->mpTask);
+ if (pIdle && pIdle->IsActive())
+ {
+ SAL_WARN("vcl.schedule",
+ "Unprocessed Idle: "
+ << pIdle << " "
+ << (pIdle->GetDebugName() ? pIdle->GetDebugName() : "(nullptr)"));
+ }
+ }
+ pSchedulerData = pSchedulerData->mpNext;
+ }
+ }
+#endif
+}
+
+extern "C" {
+/// used by unit tests that test only via the LOK API
+SAL_DLLPUBLIC_EXPORT void unit_lok_process_events_to_idle()
+{
+ const SolarMutexGuard aGuard;
+ Scheduler::ProcessEventsToIdle();
+}
+}
+
+void Application::Yield()
+{
+ static const bool bAbort = Application::IsOnSystemEventLoop();
+ if (bAbort)
+ {
+ SAL_WARN("vcl.schedule", "Application::Yield()");
+ std::abort();
+ }
+ ImplYield(true, false);
+}
+
+IMPL_STATIC_LINK_NOARG( ImplSVAppData, ImplQuitMsg, void*, void )
+{
+ assert(ImplGetSVData()->maAppData.mbAppQuit);
+ ImplGetSVData()->mpDefInst->DoQuit();
+}
+
+void Application::Quit()
+{
+ ImplGetSVData()->maAppData.mbAppQuit = true;
+ Application::PostUserEvent( LINK( nullptr, ImplSVAppData, ImplQuitMsg ) );
+}
+
+comphelper::SolarMutex& Application::GetSolarMutex()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return *(pSVData->mpDefInst->GetYieldMutex());
+}
+
+bool Application::IsMainThread()
+{
+ return ImplGetSVData()->mnMainThreadId == osl::Thread::getCurrentIdentifier();
+}
+
+sal_uInt32 Application::ReleaseSolarMutex()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return pSVData->mpDefInst->ReleaseYieldMutexAll();
+}
+
+void Application::AcquireSolarMutex( sal_uInt32 nCount )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->mpDefInst->AcquireYieldMutex( nCount );
+}
+
+bool Application::IsInMain()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return pSVData && pSVData->maAppData.mbInAppMain;
+}
+
+bool Application::IsInExecute()
+{
+ return ImplGetSVData()->maAppData.mbInAppExecute;
+}
+
+bool Application::IsQuit()
+{
+ return ImplGetSVData()->maAppData.mbAppQuit;
+}
+
+bool Application::IsInModalMode()
+{
+ return (ImplGetSVData()->maAppData.mnModalMode != 0);
+}
+
+sal_uInt16 Application::GetDispatchLevel()
+{
+ return ImplGetSVData()->maAppData.mnDispatchLevel;
+}
+
+bool Application::AnyInput( VclInputFlags nType )
+{
+ return ImplGetSVData()->mpDefInst->AnyInput( nType );
+}
+
+sal_uInt64 Application::GetLastInputInterval()
+{
+ return (tools::Time::GetSystemTicks()-ImplGetSVData()->maAppData.mnLastInputTime);
+}
+
+bool Application::IsUICaptured()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // If mouse was captured, or if in tracking- or in select-mode of a floatingwindow (e.g. menus
+ // or pulldown toolboxes) another window should be created
+ // D&D active !!!
+ return pSVData->mpWinData->mpCaptureWin || pSVData->mpWinData->mpTrackWin
+ || pSVData->mpWinData->mpFirstFloat || nImplSysDialog;
+}
+
+void Application::OverrideSystemSettings( AllSettings& /*rSettings*/ )
+{
+}
+
+void Application::MergeSystemSettings( AllSettings& rSettings )
+{
+ vcl::Window* pWindow = ImplGetSVData()->maFrameData.mpFirstFrame;
+ if( ! pWindow )
+ pWindow = ImplGetDefaultWindow();
+ if( pWindow )
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData->maAppData.mbSettingsInit )
+ {
+ // side effect: ImplUpdateGlobalSettings does an ImplGetFrame()->UpdateSettings
+ pWindow->ImplUpdateGlobalSettings( *pSVData->maAppData.mxSettings );
+ pSVData->maAppData.mbSettingsInit = true;
+ }
+ // side effect: ImplUpdateGlobalSettings does an ImplGetFrame()->UpdateSettings
+ pWindow->ImplUpdateGlobalSettings( rSettings, false );
+ }
+}
+
+void Application::SetSettings( const AllSettings& rSettings )
+{
+ const SolarMutexGuard aGuard;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData->maAppData.mxSettings )
+ {
+ InitSettings(pSVData);
+ *pSVData->maAppData.mxSettings = rSettings;
+ }
+ else
+ {
+ AllSettings aOldSettings = *pSVData->maAppData.mxSettings;
+ if (aOldSettings.GetUILanguageTag().getLanguageType() != rSettings.GetUILanguageTag().getLanguageType() &&
+ pSVData->mbResLocaleSet)
+ {
+ pSVData->mbResLocaleSet = false;
+ }
+ *pSVData->maAppData.mxSettings = rSettings;
+ AllSettingsFlags nChangeFlags = aOldSettings.GetChangeFlags( *pSVData->maAppData.mxSettings );
+ if ( bool(nChangeFlags) )
+ {
+ DataChangedEvent aDCEvt( DataChangedEventType::SETTINGS, &aOldSettings, nChangeFlags );
+
+ // notify data change handler
+ ImplCallEventListenersApplicationDataChanged( &aDCEvt);
+
+ // Update all windows
+ vcl::Window* pFirstFrame = pSVData->maFrameData.mpFirstFrame;
+ // Reset data that needs to be re-calculated
+ tools::Long nOldDPIX = 0;
+ tools::Long nOldDPIY = 0;
+ if ( pFirstFrame )
+ {
+ nOldDPIX = pFirstFrame->GetOutDev()->GetDPIX();
+ nOldDPIY = pFirstFrame->GetOutDev()->GetDPIY();
+ vcl::Window::ImplInitAppFontData(pFirstFrame);
+ }
+ vcl::Window* pFrame = pFirstFrame;
+ while ( pFrame )
+ {
+ // call UpdateSettings from ClientWindow in order to prevent updating data twice
+ vcl::Window* pClientWin = pFrame;
+ while ( pClientWin->ImplGetClientWindow() )
+ pClientWin = pClientWin->ImplGetClientWindow();
+ pClientWin->UpdateSettings( rSettings, true );
+
+ vcl::Window* pTempWin = pFrame->mpWindowImpl->mpFrameData->mpFirstOverlap;
+ while ( pTempWin )
+ {
+ // call UpdateSettings from ClientWindow in order to prevent updating data twice
+ pClientWin = pTempWin;
+ while ( pClientWin->ImplGetClientWindow() )
+ pClientWin = pClientWin->ImplGetClientWindow();
+ pClientWin->UpdateSettings( rSettings, true );
+ pTempWin = pTempWin->mpWindowImpl->mpNextOverlap;
+ }
+
+ pFrame = pFrame->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+
+ // if DPI resolution for screen output was changed set the new resolution for all
+ // screen compatible VirDev's
+ pFirstFrame = pSVData->maFrameData.mpFirstFrame;
+ if ( pFirstFrame )
+ {
+ if ( (pFirstFrame->GetOutDev()->GetDPIX() != nOldDPIX) ||
+ (pFirstFrame->GetOutDev()->GetDPIY() != nOldDPIY) )
+ {
+ VirtualDevice* pVirDev = pSVData->maGDIData.mpFirstVirDev;
+ while ( pVirDev )
+ {
+ if ( pVirDev->mbScreenComp &&
+ (pVirDev->GetDPIX() == nOldDPIX) &&
+ (pVirDev->GetDPIY() == nOldDPIY) )
+ {
+ pVirDev->SetDPIX( pFirstFrame->GetOutDev()->GetDPIX() );
+ pVirDev->SetDPIY( pFirstFrame->GetOutDev()->GetDPIY() );
+ if ( pVirDev->IsMapModeEnabled() )
+ {
+ MapMode aMapMode = pVirDev->GetMapMode();
+ pVirDev->SetMapMode();
+ pVirDev->SetMapMode( aMapMode );
+ }
+ }
+
+ pVirDev = pVirDev->mpNext;
+ }
+ }
+ }
+ }
+ }
+}
+
+const AllSettings& Application::GetSettings()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData->maAppData.mxSettings )
+ {
+ InitSettings(pSVData);
+ }
+
+ return *(pSVData->maAppData.mxSettings);
+}
+
+namespace {
+
+void InitSettings(ImplSVData* pSVData)
+{
+ assert(!pSVData->maAppData.mxSettings && "initialization should not happen twice!");
+
+ pSVData->maAppData.mxSettings.emplace();
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ pSVData->maAppData.mpCfgListener = new LocaleConfigurationListener;
+ pSVData->maAppData.mxSettings->GetSysLocale().GetOptions().AddListener( pSVData->maAppData.mpCfgListener );
+ }
+}
+
+}
+
+void Application::NotifyAllWindows( DataChangedEvent& rDCEvt )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pFrame = pSVData->maFrameData.mpFirstFrame;
+ while ( pFrame )
+ {
+ pFrame->NotifyAllChildren( rDCEvt );
+
+ vcl::Window* pSysWin = pFrame->mpWindowImpl->mpFrameData->mpFirstOverlap;
+ while ( pSysWin )
+ {
+ pSysWin->NotifyAllChildren( rDCEvt );
+ pSysWin = pSysWin->mpWindowImpl->mpNextOverlap;
+ }
+
+ pFrame = pFrame->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+}
+
+void Application::ImplCallEventListenersApplicationDataChanged( void* pData )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ VclWindowEvent aEvent( nullptr, VclEventId::ApplicationDataChanged, pData );
+
+ pSVData->maAppData.maEventListeners.Call( aEvent );
+}
+
+void Application::ImplCallEventListeners( VclSimpleEvent& rEvent )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.maEventListeners.Call( rEvent );
+}
+
+void Application::AddEventListener( const Link<VclSimpleEvent&,void>& rEventListener )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.maEventListeners.addListener( rEventListener );
+}
+
+void Application::RemoveEventListener( const Link<VclSimpleEvent&,void>& rEventListener )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.maEventListeners.removeListener( rEventListener );
+}
+
+void Application::AddKeyListener( const Link<VclWindowEvent&,bool>& rKeyListener )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.maKeyListeners.push_back( rKeyListener );
+}
+
+void Application::RemoveKeyListener( const Link<VclWindowEvent&,bool>& rKeyListener )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ auto & rVec = pSVData->maAppData.maKeyListeners;
+ rVec.erase( std::remove(rVec.begin(), rVec.end(), rKeyListener ), rVec.end() );
+}
+
+bool Application::HandleKey( VclEventId nEvent, vcl::Window *pWin, KeyEvent* pKeyEvent )
+{
+ // let listeners process the key event
+ VclWindowEvent aEvent( pWin, nEvent, static_cast<void *>(pKeyEvent) );
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( pSVData->maAppData.maKeyListeners.empty() )
+ return false;
+
+ bool bProcessed = false;
+ // Copy the list, because this can be destroyed when calling a Link...
+ std::vector<Link<VclWindowEvent&,bool>> aCopy( pSVData->maAppData.maKeyListeners );
+ for ( const Link<VclWindowEvent&,bool>& rLink : aCopy )
+ {
+ if( rLink.Call( aEvent ) )
+ {
+ bProcessed = true;
+ break;
+ }
+ }
+ return bProcessed;
+}
+
+ImplSVEvent * Application::PostKeyEvent( VclEventId nEvent, vcl::Window *pWin, KeyEvent const * pKeyEvent )
+{
+ const SolarMutexGuard aGuard;
+ ImplSVEvent * nEventId = nullptr;
+
+ if( pWin && pKeyEvent )
+ {
+ std::unique_ptr<ImplPostEventData> pPostEventData(new ImplPostEventData( nEvent, pWin, *pKeyEvent ));
+
+ nEventId = PostUserEvent(
+ LINK( nullptr, Application, PostEventHandler ),
+ pPostEventData.get() );
+
+ if( nEventId )
+ {
+ pPostEventData->mnEventId = nEventId;
+ ImplGetSVData()->maAppData.maPostedEventList.emplace_back( pWin, pPostEventData.release() );
+ }
+ }
+
+ return nEventId;
+}
+
+ImplSVEvent* Application::PostGestureEvent(VclEventId nEvent, vcl::Window* pWin, GestureEvent const * pGestureEvent)
+{
+ const SolarMutexGuard aGuard;
+ ImplSVEvent * nEventId = nullptr;
+
+ if (pWin && pGestureEvent)
+ {
+ Point aTransformedPosition(pGestureEvent->mnX, pGestureEvent->mnY);
+
+ aTransformedPosition.AdjustX(pWin->GetOutOffXPixel());
+ aTransformedPosition.AdjustY(pWin->GetOutOffYPixel());
+
+ const GestureEvent aGestureEvent(
+ sal_Int32(aTransformedPosition.X()),
+ sal_Int32(aTransformedPosition.Y()),
+ pGestureEvent->meEventType,
+ pGestureEvent->mnOffset,
+ pGestureEvent->meOrientation
+ );
+
+ std::unique_ptr<ImplPostEventData> pPostEventData(new ImplPostEventData(nEvent, pWin, aGestureEvent));
+
+ nEventId = PostUserEvent(
+ LINK( nullptr, Application, PostEventHandler ),
+ pPostEventData.get());
+
+ if (nEventId)
+ {
+ pPostEventData->mnEventId = nEventId;
+ ImplGetSVData()->maAppData.maPostedEventList.emplace_back(pWin, pPostEventData.release());
+ }
+ }
+
+ return nEventId;
+}
+
+bool Application::LOKHandleMouseEvent(VclEventId nEvent, vcl::Window* pWindow, const MouseEvent* pEvent)
+{
+ bool bSuccess = false;
+ SalMouseEvent aMouseEvent;
+
+ if (!pWindow)
+ return false;
+
+ if (!pEvent)
+ return false;
+
+ aMouseEvent.mnTime = tools::Time::GetSystemTicks();
+ aMouseEvent.mnX = pEvent->GetPosPixel().X();
+ aMouseEvent.mnY = pEvent->GetPosPixel().Y();
+ aMouseEvent.mnCode = pEvent->GetButtons() | pEvent->GetModifier();
+
+ switch (nEvent)
+ {
+ case VclEventId::WindowMouseMove:
+ aMouseEvent.mnButton = 0;
+ bSuccess = ImplLOKHandleMouseEvent(pWindow, MouseNotifyEvent::MOUSEMOVE, false,
+ aMouseEvent.mnX, aMouseEvent.mnY,
+ aMouseEvent.mnTime, aMouseEvent.mnCode,
+ ImplGetMouseMoveMode(&aMouseEvent),
+ pEvent->GetClicks());
+ break;
+
+ case VclEventId::WindowMouseButtonDown:
+ aMouseEvent.mnButton = pEvent->GetButtons();
+ bSuccess = ImplLOKHandleMouseEvent(pWindow, MouseNotifyEvent::MOUSEBUTTONDOWN, false,
+ aMouseEvent.mnX, aMouseEvent.mnY,
+ aMouseEvent.mnTime,
+#ifdef MACOSX
+ aMouseEvent.mnButton |
+ (aMouseEvent.mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)),
+#else
+ aMouseEvent.mnButton |
+ (aMouseEvent.mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2)),
+#endif
+ ImplGetMouseButtonMode(&aMouseEvent),
+ pEvent->GetClicks());
+ break;
+
+ case VclEventId::WindowMouseButtonUp:
+ aMouseEvent.mnButton = pEvent->GetButtons();
+ bSuccess = ImplLOKHandleMouseEvent(pWindow, MouseNotifyEvent::MOUSEBUTTONUP, false,
+ aMouseEvent.mnX, aMouseEvent.mnY,
+ aMouseEvent.mnTime,
+#ifdef MACOSX
+ aMouseEvent.mnButton |
+ (aMouseEvent.mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)),
+#else
+ aMouseEvent.mnButton |
+ (aMouseEvent.mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2)),
+#endif
+ ImplGetMouseButtonMode(&aMouseEvent),
+ pEvent->GetClicks());
+ break;
+
+ default:
+ SAL_WARN( "vcl.layout", "Application::HandleMouseEvent unknown event (" << static_cast<int>(nEvent) << ")" );
+ break;
+ }
+
+ return bSuccess;
+}
+
+
+ImplSVEvent* Application::PostMouseEvent( VclEventId nEvent, vcl::Window *pWin, MouseEvent const * pMouseEvent )
+{
+ const SolarMutexGuard aGuard;
+ ImplSVEvent * nEventId = nullptr;
+
+ if( pWin && pMouseEvent )
+ {
+ Point aTransformedPos( pMouseEvent->GetPosPixel() );
+
+ // LOK uses (0, 0) as the origin of all windows; don't offset.
+ if (!comphelper::LibreOfficeKit::isActive())
+ {
+ aTransformedPos.AdjustX(pWin->GetOutOffXPixel());
+ aTransformedPos.AdjustY(pWin->GetOutOffYPixel());
+ }
+
+ const MouseEvent aTransformedEvent( aTransformedPos, pMouseEvent->GetClicks(), pMouseEvent->GetMode(),
+ pMouseEvent->GetButtons(), pMouseEvent->GetModifier() );
+
+ std::unique_ptr<ImplPostEventData> pPostEventData(new ImplPostEventData( nEvent, pWin, aTransformedEvent ));
+
+ nEventId = PostUserEvent(
+ LINK( nullptr, Application, PostEventHandler ),
+ pPostEventData.get() );
+
+ if( nEventId )
+ {
+ pPostEventData->mnEventId = nEventId;
+ ImplGetSVData()->maAppData.maPostedEventList.emplace_back( pWin, pPostEventData.release() );
+ }
+ }
+
+ return nEventId;
+}
+
+
+IMPL_STATIC_LINK( Application, PostEventHandler, void*, pCallData, void )
+{
+ const SolarMutexGuard aGuard;
+ ImplPostEventData* pData = static_cast< ImplPostEventData * >( pCallData );
+ const void* pEventData;
+ SalEvent nEvent;
+ ImplSVEvent * const nEventId = pData->mnEventId;
+
+ switch( pData->mnEvent )
+ {
+ case VclEventId::WindowMouseMove:
+ nEvent = SalEvent::ExternalMouseMove;
+ pEventData = &pData->maMouseEvent;
+ break;
+
+ case VclEventId::WindowMouseButtonDown:
+ nEvent = SalEvent::ExternalMouseButtonDown;
+ pEventData = &pData->maMouseEvent;
+ break;
+
+ case VclEventId::WindowMouseButtonUp:
+ nEvent = SalEvent::ExternalMouseButtonUp;
+ pEventData = &pData->maMouseEvent;
+ break;
+
+ case VclEventId::WindowKeyInput:
+ nEvent = SalEvent::ExternalKeyInput;
+ pEventData = &pData->maKeyEvent;
+ break;
+
+ case VclEventId::WindowKeyUp:
+ nEvent = SalEvent::ExternalKeyUp;
+ pEventData = &pData->maKeyEvent;
+ break;
+
+ case VclEventId::WindowGestureEvent:
+ nEvent = SalEvent::ExternalGesture;
+ pEventData = &pData->maGestureEvent;
+ break;
+
+ default:
+ nEvent = SalEvent::NONE;
+ pEventData = nullptr;
+ break;
+ }
+
+ if( pData->mpWin && pData->mpWin->mpWindowImpl->mpFrameWindow && pEventData )
+ ImplWindowFrameProc( pData->mpWin->mpWindowImpl->mpFrameWindow.get(), nEvent, pEventData );
+
+ // remove this event from list of posted events, watch for destruction of internal data
+ auto svdata = ImplGetSVData();
+ ::std::vector< ImplPostEventPair >::iterator aIter( svdata->maAppData.maPostedEventList.begin() );
+
+ while( aIter != svdata->maAppData.maPostedEventList.end() )
+ {
+ if( nEventId == (*aIter).second->mnEventId )
+ {
+ delete (*aIter).second;
+ aIter = svdata->maAppData.maPostedEventList.erase( aIter );
+ }
+ else
+ ++aIter;
+ }
+}
+
+void Application::RemoveMouseAndKeyEvents( vcl::Window* pWin )
+{
+ const SolarMutexGuard aGuard;
+
+ // remove all events for specific window, watch for destruction of internal data
+ auto svdata = ImplGetSVData();
+ ::std::vector< ImplPostEventPair >::iterator aIter( svdata->maAppData.maPostedEventList.begin() );
+
+ while( aIter != svdata->maAppData.maPostedEventList.end() )
+ {
+ if( pWin == (*aIter).first )
+ {
+ if( (*aIter).second->mnEventId )
+ RemoveUserEvent( (*aIter).second->mnEventId );
+
+ delete (*aIter).second;
+ aIter = svdata->maAppData.maPostedEventList.erase( aIter );
+ }
+ else
+ ++aIter;
+ }
+}
+
+ImplSVEvent * Application::PostUserEvent( const Link<void*,void>& rLink, void* pCaller,
+ bool bReferenceLink )
+{
+ vcl::Window* pDefWindow = ImplGetDefaultWindow();
+ if ( pDefWindow == nullptr )
+ return nullptr;
+
+ std::unique_ptr<ImplSVEvent> pSVEvent(new ImplSVEvent);
+ pSVEvent->mpData = pCaller;
+ pSVEvent->maLink = rLink;
+ pSVEvent->mpWindow = nullptr;
+ pSVEvent->mbCall = true;
+ if (bReferenceLink)
+ {
+ SolarMutexGuard aGuard;
+ pSVEvent->mpInstanceRef = static_cast<vcl::Window *>(rLink.GetInstance());
+ }
+
+ auto pTmpEvent = pSVEvent.get();
+ if (!pDefWindow->ImplGetFrame()->PostEvent( std::move(pSVEvent) ))
+ return nullptr;
+ return pTmpEvent;
+}
+
+void Application::RemoveUserEvent( ImplSVEvent * nUserEvent )
+{
+ if(nUserEvent)
+ {
+ SAL_WARN_IF( nUserEvent->mpWindow, "vcl",
+ "Application::RemoveUserEvent(): Event is send to a window" );
+ SAL_WARN_IF( !nUserEvent->mbCall, "vcl",
+ "Application::RemoveUserEvent(): Event is already removed" );
+
+ nUserEvent->mpWindow.clear();
+ nUserEvent->mpInstanceRef.clear();
+ nUserEvent->mbCall = false;
+ }
+}
+
+vcl::Window* Application::GetFocusWindow()
+{
+ return ImplGetSVData()->mpWinData->mpFocusWin;
+}
+
+OutputDevice* Application::GetDefaultDevice()
+{
+ return ImplGetDefaultWindow()->GetOutDev();
+}
+
+vcl::Window* Application::GetFirstTopLevelWindow()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return pSVData->maFrameData.mpFirstFrame;
+}
+
+vcl::Window* Application::GetNextTopLevelWindow( vcl::Window const * pWindow )
+{
+ return pWindow->mpWindowImpl->mpFrameData->mpNextFrame;
+}
+
+tools::Long Application::GetTopWindowCount()
+{
+ tools::Long nRet = 0;
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window *pWin = pSVData ? pSVData->maFrameData.mpFirstFrame.get() : nullptr;
+ while( pWin )
+ {
+ if( pWin->ImplGetWindow()->IsTopWindow() )
+ nRet++;
+ pWin = pWin->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+ return nRet;
+}
+
+vcl::Window* Application::GetTopWindow( tools::Long nIndex )
+{
+ tools::Long nIdx = 0;
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window *pWin = pSVData ? pSVData->maFrameData.mpFirstFrame.get() : nullptr;
+ while( pWin )
+ {
+ if( pWin->ImplGetWindow()->IsTopWindow() )
+ {
+ if( nIdx == nIndex )
+ return pWin->ImplGetWindow();
+ else
+ nIdx++;
+ }
+ pWin = pWin->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+ return nullptr;
+}
+
+vcl::Window* Application::GetActiveTopWindow()
+{
+ vcl::Window *pWin = ImplGetSVData()->mpWinData->mpFocusWin;
+ while( pWin )
+ {
+ if( pWin->IsTopWindow() )
+ return pWin;
+ pWin = pWin->mpWindowImpl->mpParent;
+ }
+ return nullptr;
+}
+
+void Application::SetAppName( const OUString& rUniqueName )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mxAppName = rUniqueName;
+}
+
+OUString Application::GetAppName()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( pSVData->maAppData.mxAppName )
+ return *(pSVData->maAppData.mxAppName);
+ else
+ return OUString();
+}
+
+enum {hwAll=0, hwEnv=1, hwUI=2};
+
+static OUString Localize(TranslateId aId, const bool bLocalize)
+{
+ if (bLocalize)
+ return VclResId(aId);
+ else
+ return Translate::get(aId, Translate::Create("vcl", LanguageTag("en-US")));
+}
+
+OUString Application::GetOSVersion()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ OUString aVersion;
+ if (pSVData && pSVData->mpDefInst)
+ aVersion = pSVData->mpDefInst->getOSVersion();
+ else
+ aVersion = "-";
+ return aVersion;
+}
+
+OUString Application::GetHWOSConfInfo(const int bSelection, const bool bLocalize)
+{
+ OUStringBuffer aDetails;
+
+ const auto appendDetails = [&aDetails](std::u16string_view sep, auto&& val) {
+ if (!aDetails.isEmpty() && !sep.empty())
+ aDetails.append(sep);
+ aDetails.append(std::move(val));
+ };
+
+ if (bSelection != hwUI) {
+ appendDetails(u"; ", Localize(SV_APP_CPUTHREADS, bLocalize)
+ + OUString::number(std::thread::hardware_concurrency()));
+
+ OUString aVersion = GetOSVersion();
+
+ appendDetails(u"; ", Localize(SV_APP_OSVERSION, bLocalize) + aVersion);
+ }
+
+ if (bSelection != hwEnv) {
+ appendDetails(u"; ", Localize(SV_APP_UIRENDER, bLocalize));
+#if HAVE_FEATURE_SKIA
+ if ( SkiaHelper::isVCLSkiaEnabled() )
+ {
+ switch(SkiaHelper::renderMethodToUse())
+ {
+ case SkiaHelper::RenderVulkan:
+ appendDetails(u"", Localize(SV_APP_SKIA_VULKAN, bLocalize));
+ break;
+ case SkiaHelper::RenderMetal:
+ appendDetails(u"", Localize(SV_APP_SKIA_METAL, bLocalize));
+ break;
+ case SkiaHelper::RenderRaster:
+ appendDetails(u"", Localize(SV_APP_SKIA_RASTER, bLocalize));
+ break;
+ }
+ }
+ else
+#endif
+ appendDetails(u"", Localize(SV_APP_DEFAULT, bLocalize));
+
+#if (defined LINUX || defined _WIN32 || defined MACOSX || defined __FreeBSD__ || defined EMSCRIPTEN)
+ appendDetails(u"; ", SV_APP_VCLBACKEND + GetToolkitName());
+#endif
+ }
+
+ return aDetails.makeStringAndClear();
+}
+
+void Application::SetDisplayName( const OUString& rName )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mxDisplayName = rName;
+}
+
+OUString Application::GetDisplayName()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( pSVData->maAppData.mxDisplayName )
+ return *(pSVData->maAppData.mxDisplayName);
+ else if (pSVData->maFrameData.mpAppWin)
+ return pSVData->maFrameData.mpAppWin->GetText();
+ else
+ return OUString();
+}
+
+unsigned int Application::GetScreenCount()
+{
+ SalSystem* pSys = ImplGetSalSystem();
+ return pSys ? pSys->GetDisplayScreenCount() : 0;
+}
+
+bool Application::IsUnifiedDisplay()
+{
+ SalSystem* pSys = ImplGetSalSystem();
+ return pSys == nullptr || pSys->IsUnifiedDisplay();
+}
+
+unsigned int Application::GetDisplayBuiltInScreen()
+{
+ SalSystem* pSys = ImplGetSalSystem();
+ return pSys ? pSys->GetDisplayBuiltInScreen() : 0;
+}
+
+unsigned int Application::GetDisplayExternalScreen()
+{
+ // This is really unpleasant, in theory we could have multiple
+ // external displays etc.
+ int nExternal(0);
+ switch (GetDisplayBuiltInScreen())
+ {
+ case 0:
+ nExternal = 1;
+ break;
+ case 1:
+ nExternal = 0;
+ break;
+ default:
+ // When the built-in display is neither 0 nor 1
+ // then place the full-screen presentation on the
+ // first available screen.
+ nExternal = 0;
+ break;
+ }
+ return nExternal;
+}
+
+tools::Rectangle Application::GetScreenPosSizePixel( unsigned int nScreen )
+{
+ SalSystem* pSys = ImplGetSalSystem();
+ if (!pSys)
+ {
+ SAL_WARN("vcl", "Requesting screen size/pos for screen #" << nScreen << " failed");
+ assert(false);
+ return tools::Rectangle();
+ }
+ tools::Rectangle aRect = pSys->GetDisplayScreenPosSizePixel(nScreen);
+ if (aRect.getHeight() == 0)
+ SAL_WARN("vcl", "Requesting screen size/pos for screen #" << nScreen << " returned 0 height.");
+ return aRect;
+}
+
+namespace {
+tools::Long calcDistSquare( const Point& i_rPoint, const tools::Rectangle& i_rRect )
+{
+ const Point aRectCenter( (i_rRect.Left() + i_rRect.Right())/2,
+ (i_rRect.Top() + i_rRect.Bottom())/ 2 );
+ const tools::Long nDX = aRectCenter.X() - i_rPoint.X();
+ const tools::Long nDY = aRectCenter.Y() - i_rPoint.Y();
+ return nDX*nDX + nDY*nDY;
+}
+}
+
+unsigned int Application::GetBestScreen( const tools::Rectangle& i_rRect )
+{
+ if( !IsUnifiedDisplay() )
+ return GetDisplayBuiltInScreen();
+
+ const unsigned int nScreens = GetScreenCount();
+ unsigned int nBestMatchScreen = 0;
+ unsigned long nOverlap = 0;
+ for( unsigned int i = 0; i < nScreens; i++ )
+ {
+ const tools::Rectangle aCurScreenRect( GetScreenPosSizePixel( i ) );
+ // if a screen contains the rectangle completely it is obviously the best screen
+ if( aCurScreenRect.Contains( i_rRect ) )
+ return i;
+ // next the screen which contains most of the area of the rect is the best
+ tools::Rectangle aIntersection( aCurScreenRect.GetIntersection( i_rRect ) );
+ if( ! aIntersection.IsEmpty() )
+ {
+ const unsigned long nCurOverlap( aIntersection.GetWidth() * aIntersection.GetHeight() );
+ if( nCurOverlap > nOverlap )
+ {
+ nOverlap = nCurOverlap;
+ nBestMatchScreen = i;
+ }
+ }
+ }
+ if( nOverlap > 0 )
+ return nBestMatchScreen;
+
+ // finally the screen which center is nearest to the rect is the best
+ const Point aCenter( (i_rRect.Left() + i_rRect.Right())/2,
+ (i_rRect.Top() + i_rRect.Bottom())/2 );
+ tools::Long nDist = std::numeric_limits<tools::Long>::max();
+ for( unsigned int i = 0; i < nScreens; i++ )
+ {
+ const tools::Rectangle aCurScreenRect( GetScreenPosSizePixel( i ) );
+ const tools::Long nCurDist( calcDistSquare( aCenter, aCurScreenRect ) );
+ if( nCurDist < nDist )
+ {
+ nBestMatchScreen = i;
+ nDist = nCurDist;
+ }
+ }
+ return nBestMatchScreen;
+}
+
+bool Application::InsertAccel( Accelerator* pAccel )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( !pSVData->maAppData.mpAccelMgr )
+ pSVData->maAppData.mpAccelMgr = new ImplAccelManager();
+ return pSVData->maAppData.mpAccelMgr->InsertAccel( pAccel );
+}
+
+void Application::RemoveAccel( Accelerator const * pAccel )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( pSVData->maAppData.mpAccelMgr )
+ pSVData->maAppData.mpAccelMgr->RemoveAccel( pAccel );
+}
+
+void Application::SetHelp( Help* pHelp )
+{
+ ImplGetSVData()->maAppData.mpHelp = pHelp;
+}
+
+void Application::UpdateMainThread()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData && pSVData->mpDefInst)
+ pSVData->mpDefInst->updateMainThread();
+}
+
+Help* Application::GetHelp()
+{
+ return ImplGetSVData()->maAppData.mpHelp;
+}
+
+OUString Application::GetToolkitName()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( pSVData->maAppData.mxToolkitName )
+ return *(pSVData->maAppData.mxToolkitName);
+ else
+ return OUString();
+}
+
+vcl::Window* Dialog::GetDefDialogParent()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ // find some useful dialog parent
+
+ // always use the topmost parent of the candidate
+ // window to avoid using dialogs or floaters
+ // as DefDialogParent
+
+ // current focus frame
+ vcl::Window *pWin = pSVData->mpWinData->mpFocusWin;
+ if (pWin && !pWin->IsMenuFloatingWindow())
+ {
+ while (pWin->mpWindowImpl && pWin->mpWindowImpl->mpParent)
+ pWin = pWin->mpWindowImpl->mpParent;
+
+ // check for corrupted window hierarchy, #122232#, may be we now crash somewhere else
+ if (!pWin->mpWindowImpl)
+ {
+ OSL_FAIL( "Window hierarchy corrupted!" );
+ pSVData->mpWinData->mpFocusWin = nullptr; // avoid further access
+ return nullptr;
+ }
+
+ if ((pWin->mpWindowImpl->mnStyle & WB_INTROWIN) == 0)
+ {
+ return pWin->mpWindowImpl->mpFrameWindow->ImplGetWindow();
+ }
+ }
+
+ // last active application frame
+ pWin = pSVData->maFrameData.mpActiveApplicationFrame;
+ if (pWin)
+ {
+ return pWin->mpWindowImpl->mpFrameWindow->ImplGetWindow();
+ }
+
+ // first visible top window (may be totally wrong...)
+ pWin = pSVData->maFrameData.mpFirstFrame;
+ while (pWin)
+ {
+ if( pWin->ImplGetWindow()->IsTopWindow() &&
+ pWin->mpWindowImpl->mbReallyVisible &&
+ (pWin->mpWindowImpl->mnStyle & WB_INTROWIN) == 0
+ )
+ {
+ while( pWin->mpWindowImpl->mpParent )
+ pWin = pWin->mpWindowImpl->mpParent;
+ return pWin->mpWindowImpl->mpFrameWindow->ImplGetWindow();
+ }
+ pWin = pWin->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+
+ // use the desktop
+ return nullptr;
+}
+
+weld::Window* Application::GetDefDialogParent()
+{
+ vcl::Window* pWindow = Dialog::GetDefDialogParent();
+ return pWindow ? pWindow->GetFrameWeld() : nullptr;
+}
+
+DialogCancelMode Application::GetDialogCancelMode()
+{
+ return ImplGetSVData()->maAppData.meDialogCancel;
+}
+
+void Application::SetDialogCancelMode( DialogCancelMode mode )
+{
+ ImplGetSVData()->maAppData.meDialogCancel = mode;
+}
+
+bool Application::IsDialogCancelEnabled()
+{
+ return ImplGetSVData()->maAppData.meDialogCancel != DialogCancelMode::Off;
+}
+
+void Application::SetSystemWindowMode( SystemWindowFlags nMode )
+{
+ ImplGetSVData()->maAppData.mnSysWinMode = nMode;
+}
+
+SystemWindowFlags Application::GetSystemWindowMode()
+{
+ return ImplGetSVData()->maAppData.mnSysWinMode;
+}
+
+css::uno::Reference< css::awt::XToolkit > Application::GetVCLToolkit()
+{
+ css::uno::Reference< css::awt::XToolkit > xT;
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper();
+ if ( pWrapper )
+ xT = pWrapper->GetVCLToolkit();
+ return xT;
+}
+
+#ifdef DISABLE_DYNLOADING
+
+extern "C" { UnoWrapperBase* CreateUnoWrapper(); }
+
+#else
+
+extern "C" { static void thisModule() {} }
+
+#endif
+
+UnoWrapperBase* UnoWrapperBase::GetUnoWrapper( bool bCreateIfNotExist )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ static bool bAlreadyTriedToCreate = false;
+ if ( !pSVData->mpUnoWrapper && bCreateIfNotExist && !bAlreadyTriedToCreate )
+ {
+#ifndef DISABLE_DYNLOADING
+ osl::Module aTkLib;
+ aTkLib.loadRelative(&thisModule, TK_DLL_NAME);
+ if (aTkLib.is())
+ {
+ FN_TkCreateUnoWrapper fnCreateWrapper = reinterpret_cast<FN_TkCreateUnoWrapper>(aTkLib.getFunctionSymbol("CreateUnoWrapper"));
+ if ( fnCreateWrapper )
+ {
+ pSVData->mpUnoWrapper = fnCreateWrapper();
+ }
+ aTkLib.release();
+ }
+ SAL_WARN_IF( !pSVData->mpUnoWrapper, "vcl", "UnoWrapper could not be created!" );
+#else
+ pSVData->mpUnoWrapper = CreateUnoWrapper();
+#endif
+ bAlreadyTriedToCreate = true;
+ }
+ return pSVData->mpUnoWrapper;
+}
+
+void UnoWrapperBase::SetUnoWrapper( UnoWrapperBase* pWrapper )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ SAL_WARN_IF( pSVData->mpUnoWrapper, "vcl", "SetUnoWrapper: Wrapper already exists" );
+ pSVData->mpUnoWrapper = pWrapper;
+}
+
+css::uno::Reference< css::awt::XDisplayConnection > Application::GetDisplayConnection()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if( !pSVData->mxDisplayConnection.is() )
+ {
+ pSVData->mxDisplayConnection.set( new vcl::DisplayConnectionDispatch );
+ pSVData->mxDisplayConnection->start();
+ }
+
+ return pSVData->mxDisplayConnection;
+}
+
+void Application::SetFilterHdl( const Link<ConvertData&,bool>& rLink )
+{
+ ImplGetSVData()->maGDIData.mxGrfConverter->SetFilterHdl( rLink );
+}
+
+const LocaleDataWrapper& Application::GetAppLocaleDataWrapper()
+{
+ return GetSettings().GetLocaleDataWrapper();
+}
+
+void Application::EnableHeadlessMode( bool dialogsAreFatal )
+{
+ DialogCancelMode eNewMode = dialogsAreFatal ? DialogCancelMode::Fatal : DialogCancelMode::Silent;
+ DialogCancelMode eOldMode = GetDialogCancelMode();
+ assert(eOldMode == DialogCancelMode::Off || GetDialogCancelMode() == eNewMode);
+ if (eOldMode != eNewMode)
+ SetDialogCancelMode( eNewMode );
+}
+
+bool Application::IsHeadlessModeEnabled()
+{
+ return IsDialogCancelEnabled() || comphelper::LibreOfficeKit::isActive();
+}
+
+void Application::EnableBitmapRendering()
+{
+ ImplGetSVData()->maAppData.mbRenderToBitmaps = true;
+}
+
+bool Application::IsBitmapRendering()
+{
+ return ImplGetSVData()->maAppData.mbRenderToBitmaps;
+}
+
+void Application::EnableConsoleOnly()
+{
+ EnableHeadlessMode(true);
+ EnableBitmapRendering();
+}
+
+static bool bEventTestingMode = false;
+
+bool Application::IsEventTestingModeEnabled()
+{
+ return bEventTestingMode;
+}
+
+void Application::EnableEventTestingMode()
+{
+ bEventTestingMode = true;
+}
+
+static bool bSafeMode = false;
+
+bool Application::IsSafeModeEnabled()
+{
+ return bSafeMode;
+}
+
+void Application::EnableSafeMode()
+{
+ bSafeMode = true;
+}
+
+void Application::ShowNativeErrorBox(const OUString& sTitle ,
+ const OUString& sMessage)
+{
+ int btn = ImplGetSalSystem()->ShowNativeMessageBox(
+ sTitle,
+ sMessage);
+ if (btn != SALSYSTEM_SHOWNATIVEMSGBOX_BTN_OK) {
+ SAL_WARN( "vcl", "ShowNativeMessageBox returned " << btn);
+ }
+}
+
+const OUString& Application::GetDesktopEnvironment()
+{
+ if (IsHeadlessModeEnabled())
+ {
+ static const OUString aNone("none");
+ return aNone;
+ }
+ else
+ return SalGetDesktopEnvironment();
+}
+
+void Application::AddToRecentDocumentList(const OUString& rFileUrl, const OUString& rMimeType, const OUString& rDocumentService)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->mpDefInst->AddToRecentDocumentList(rFileUrl, rMimeType, rDocumentService);
+}
+
+bool InitAccessBridge()
+{
+// Disable MSAA bridge on UNIX
+#if defined UNX
+ return true;
+#else
+ bool bRet = ImplInitAccessBridge();
+
+ if( !bRet )
+ {
+ // disable accessibility if the user chooses to continue
+ AllSettings aSettings = Application::GetSettings();
+ MiscSettings aMisc = aSettings.GetMiscSettings();
+ aMisc.SetEnableATToolSupport( false );
+ aSettings.SetMiscSettings( aMisc );
+ Application::SetSettings( aSettings );
+ }
+ return bRet;
+#endif // !UNX
+}
+
+// MT: AppEvent was in oldsv.cxx, but is still needed...
+void Application::AppEvent( const ApplicationEvent& /*rAppEvent*/ )
+{
+}
+
+bool Application::hasNativeFileSelection()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return pSVData->mpDefInst->hasNativeFileSelection();
+}
+
+Reference< ui::dialogs::XFilePicker2 >
+Application::createFilePicker( const Reference< uno::XComponentContext >& xSM )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return pSVData->mpDefInst->createFilePicker( xSM );
+}
+
+Reference< ui::dialogs::XFolderPicker2 >
+Application::createFolderPicker( const Reference< uno::XComponentContext >& xSM )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return pSVData->mpDefInst->createFolderPicker( xSM );
+}
+
+void Application::setDeInitHook(Link<LinkParamNone*,void> const & hook) {
+ ImplSVData * pSVData = ImplGetSVData();
+ assert(!pSVData->maDeInitHook.IsSet());
+ pSVData->maDeInitHook = hook;
+ // Fake this for VCLXToolkit ctor instantiated from
+ // postprocess/CppunitTest_services.mk:
+ pSVData->maAppData.mbInAppMain = true;
+}
+
+namespace vcl::lok {
+
+void registerPollCallbacks(
+ LibreOfficeKitPollCallback pPollCallback,
+ LibreOfficeKitWakeCallback pWakeCallback,
+ void *pData) {
+
+ ImplSVData * pSVData = ImplGetSVData();
+ if (pSVData)
+ {
+ pSVData->mpPollCallback = pPollCallback;
+ pSVData->mpWakeCallback = pWakeCallback;
+ pSVData->mpPollClosure = pData;
+ }
+}
+
+void unregisterPollCallbacks()
+{
+ ImplSVData * pSVData = ImplGetSVData();
+ if (!pSVData)
+ return;
+
+ // Not hyper-elegant - but in the case of Android & unipoll we need to detach
+ // this thread from the JVM's clutches to avoid a crash closing document
+ if (pSVData->mpPollClosure && pSVData->mpDefInst)
+ pSVData->mpDefInst->releaseMainThread();
+
+ // Just set mpPollClosure to null as that is what calling this means, that the callback data
+ // points to an object that no longer exists. In particular, don't set
+ // pSVData->mpPollCallback to nullptr as that is used to detect whether Unipoll is in use in
+ // isUnipoll().
+ pSVData->mpPollClosure = nullptr;
+}
+
+bool isUnipoll()
+{
+ ImplSVData * pSVData = ImplGetSVData();
+ return pSVData && pSVData->mpPollCallback != nullptr;
+}
+
+void numberOfViewsChanged(int count)
+{
+ ImplSVData * pSVData = ImplGetSVData();
+ auto& rCache = pSVData->maGDIData.maScaleCache;
+ // Normally the cache size is set to 10, scale according to the number of users.
+ rCache.setMaxSize(count * 10);
+}
+
+} // namespace lok, namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/svdata.cxx b/vcl/source/app/svdata.cxx
new file mode 100644
index 000000000..b56e8c05e
--- /dev/null
+++ b/vcl/source/app/svdata.cxx
@@ -0,0 +1,506 @@
+/* -*- 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 <string.h>
+
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <tools/diagnose_ex.h>
+#include <unotools/resmgr.hxx>
+#include <sal/log.hxx>
+
+#include <configsettings.hxx>
+#include <vcl/QueueInfo.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/dockwin.hxx>
+#include <vcl/fieldvalues.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/print.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <salframe.hxx>
+#include <scrwnd.hxx>
+#include <helpwin.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <salinst.hxx>
+#include <salgdi.hxx>
+#include <svdata.hxx>
+#include <salsys.hxx>
+#include <windowdev.hxx>
+#include <units.hrc>
+#include <print.h>
+
+#include <com/sun/star/accessibility/MSAAService.hpp>
+
+#include <config_features.h>
+#include <basegfx/utils/systemdependentdata.hxx>
+#include <mutex>
+
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::awt;
+
+namespace
+{
+ struct private_aImplSVData :
+ public rtl::Static<ImplSVData, private_aImplSVData> {};
+ /// Default instance ensures that ImplSVData::mpHelpData is never null.
+ struct private_aImplSVHelpData :
+ public rtl::Static<ImplSVHelpData, private_aImplSVHelpData> {};
+
+ /// Default instance ensures that ImplSVData::mpWinData is never null.
+ struct private_aImplSVWinData :
+ public rtl::Static<ImplSVWinData, private_aImplSVWinData> {};
+
+}
+
+ImplSVData* ImplGetSVData() {
+ return &private_aImplSVData::get();
+}
+
+SalSystem* ImplGetSalSystem()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( ! pSVData->mpSalSystem )
+ pSVData->mpSalSystem.reset( pSVData->mpDefInst->CreateSalSystem() );
+ return pSVData->mpSalSystem.get();
+}
+
+void ImplDeInitSVData()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // delete global instance data
+ pSVData->mpSettingsConfigItem.reset();
+
+ pSVData->mpDockingManager.reset();
+
+ pSVData->maCtrlData.maFieldUnitStrings.clear();
+ pSVData->maCtrlData.maCleanUnitStrings.clear();
+ pSVData->maPaperNames.clear();
+}
+
+namespace
+{
+ typedef ::std::map< basegfx::SystemDependentData_SharedPtr, sal_uInt32 > EntryMap;
+
+ class SystemDependentDataBuffer final : public basegfx::SystemDependentDataManager
+ {
+ private:
+ std::mutex m_aMutex;
+ std::unique_ptr<AutoTimer> maTimer;
+ EntryMap maEntries;
+
+ DECL_LINK(implTimeoutHdl, Timer *, void);
+
+ public:
+ SystemDependentDataBuffer(const char* pDebugName)
+ : maTimer(std::make_unique<AutoTimer>(pDebugName))
+ {
+ maTimer->SetTimeout(1000);
+ maTimer->SetInvokeHandler(LINK(this, SystemDependentDataBuffer, implTimeoutHdl));
+ }
+
+ virtual ~SystemDependentDataBuffer() override
+ {
+ flushAll();
+ }
+
+ void startUsage(basegfx::SystemDependentData_SharedPtr& rData) override
+ {
+ std::unique_lock aGuard(m_aMutex);
+ EntryMap::iterator aFound(maEntries.find(rData));
+
+ if(aFound == maEntries.end())
+ {
+ if(maTimer && !maTimer->IsActive())
+ {
+ maTimer->Start();
+ }
+
+ maEntries[rData] = rData->calculateCombinedHoldCyclesInSeconds();
+ }
+ }
+
+ void endUsage(basegfx::SystemDependentData_SharedPtr& rData) override
+ {
+ std::unique_lock aGuard(m_aMutex);
+ EntryMap::iterator aFound(maEntries.find(rData));
+
+ if(aFound != maEntries.end())
+ {
+ maEntries.erase(aFound);
+ }
+ }
+
+ void touchUsage(basegfx::SystemDependentData_SharedPtr& rData) override
+ {
+ std::unique_lock aGuard(m_aMutex);
+ EntryMap::iterator aFound(maEntries.find(rData));
+
+ if(aFound != maEntries.end())
+ {
+ aFound->second = rData->calculateCombinedHoldCyclesInSeconds();
+ }
+ }
+
+ void flushAll() override
+ {
+ std::unique_lock aGuard(m_aMutex);
+
+ if(maTimer)
+ {
+ maTimer->Stop();
+ maTimer.reset();
+ }
+
+ maEntries.clear();
+ }
+ };
+
+ IMPL_LINK_NOARG(SystemDependentDataBuffer, implTimeoutHdl, Timer *, void)
+ {
+ std::unique_lock aGuard(m_aMutex);
+ EntryMap::iterator aIter(maEntries.begin());
+
+ while(aIter != maEntries.end())
+ {
+ if(aIter->second)
+ {
+ aIter->second--;
+ ++aIter;
+ }
+ else
+ {
+ aIter = maEntries.erase(aIter);
+ }
+ }
+
+ if (maEntries.empty())
+ maTimer->Stop();
+ }
+}
+
+basegfx::SystemDependentDataManager& ImplGetSystemDependentDataManager()
+{
+ static SystemDependentDataBuffer aSystemDependentDataBuffer("vcl SystemDependentDataBuffer aSystemDependentDataBuffer");
+
+ return aSystemDependentDataBuffer;
+}
+
+/// Returns either the application window, or the default GL context window
+vcl::Window* ImplGetDefaultWindow()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->maFrameData.mpAppWin)
+ return pSVData->maFrameData.mpAppWin;
+ else
+ return ImplGetDefaultContextWindow();
+}
+
+/// returns the default window created to hold the persistent VCL GL context.
+vcl::Window *ImplGetDefaultContextWindow()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // Double check locking on mpDefaultWin.
+ if ( !pSVData->mpDefaultWin )
+ {
+ SolarMutexGuard aGuard;
+
+ if (!pSVData->mpDefaultWin && !pSVData->mbDeInit)
+ {
+ try
+ {
+ SAL_INFO( "vcl", "ImplGetDefaultWindow(): No AppWindow" );
+
+ pSVData->mpDefaultWin = VclPtr<WorkWindow>::Create(nullptr, WB_DEFAULTWIN);
+ pSVData->mpDefaultWin->SetText( "VCL ImplGetDefaultWindow" );
+ }
+ catch (const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("vcl", "unable to create Default Window");
+ }
+ }
+ }
+
+ return pSVData->mpDefaultWin;
+}
+
+const std::locale& ImplGetResLocale()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if (!pSVData->mbResLocaleSet || comphelper::LibreOfficeKit::isActive())
+ {
+ pSVData->maResLocale = Translate::Create("vcl");
+ pSVData->mbResLocaleSet = true;
+ }
+ return pSVData->maResLocale;
+}
+
+OUString VclResId(TranslateId aId)
+{
+ return Translate::get(aId, ImplGetResLocale());
+}
+
+const FieldUnitStringList& ImplGetFieldUnits()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->maCtrlData.maFieldUnitStrings.empty() )
+ {
+ sal_uInt32 nUnits = SAL_N_ELEMENTS(SV_FUNIT_STRINGS);
+ pSVData->maCtrlData.maFieldUnitStrings.reserve( nUnits );
+ for (sal_uInt32 i = 0; i < nUnits; i++)
+ {
+ std::pair<OUString, FieldUnit> aElement(VclResId(SV_FUNIT_STRINGS[i].first), SV_FUNIT_STRINGS[i].second);
+ pSVData->maCtrlData.maFieldUnitStrings.push_back( aElement );
+ }
+ }
+ return pSVData->maCtrlData.maFieldUnitStrings;
+}
+
+namespace vcl
+{
+ FieldUnit EnglishStringToMetric(std::string_view rEnglishMetricString)
+ {
+ sal_uInt32 nUnits = SAL_N_ELEMENTS(SV_FUNIT_STRINGS);
+ for (sal_uInt32 i = 0; i < nUnits; ++i)
+ {
+ if (rEnglishMetricString == SV_FUNIT_STRINGS[i].first.mpId)
+ return SV_FUNIT_STRINGS[i].second;
+ }
+ return FieldUnit::NONE;
+ }
+}
+
+const FieldUnitStringList& ImplGetCleanedFieldUnits()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->maCtrlData.maCleanUnitStrings.empty() )
+ {
+ const FieldUnitStringList& rUnits = ImplGetFieldUnits();
+ size_t nUnits = rUnits.size();
+ pSVData->maCtrlData.maCleanUnitStrings.reserve(nUnits);
+ for (size_t i = 0; i < nUnits; ++i)
+ {
+ OUString aUnit(rUnits[i].first);
+ aUnit = aUnit.replaceAll(" ", "");
+ aUnit = aUnit.toAsciiLowerCase();
+ std::pair<OUString, FieldUnit> aElement(aUnit, rUnits[i].second);
+ pSVData->maCtrlData.maCleanUnitStrings.push_back(aElement);
+ }
+ }
+ return pSVData->maCtrlData.maCleanUnitStrings;
+}
+
+DockingManager* ImplGetDockingManager()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData->mpDockingManager )
+ pSVData->mpDockingManager.reset(new DockingManager());
+
+ return pSVData->mpDockingManager.get();
+}
+
+BlendFrameCache* ImplGetBlendFrameCache()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData->mpBlendFrameCache)
+ pSVData->mpBlendFrameCache.reset( new BlendFrameCache() );
+
+ return pSVData->mpBlendFrameCache.get();
+}
+
+#ifdef _WIN32
+bool ImplInitAccessBridge()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( ! pSVData->mxAccessBridge.is() )
+ {
+ css::uno::Reference< XComponentContext > xContext(comphelper::getProcessComponentContext());
+
+ if (!HasAtHook() && !getenv("SAL_FORCE_IACCESSIBLE2"))
+ {
+ SAL_INFO("vcl", "Apparently no running AT -> "
+ "not enabling IAccessible2 integration");
+ }
+ else
+ {
+ try {
+ pSVData->mxAccessBridge
+ = css::accessibility::MSAAService::create(xContext);
+ SAL_INFO("vcl", "got IAccessible2 bridge");
+ return true;
+ } catch (css::uno::DeploymentException &) {
+ TOOLS_WARN_EXCEPTION(
+ "vcl",
+ "got no IAccessible2 bridge");
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+#endif
+
+void LocaleConfigurationListener::ConfigurationChanged( utl::ConfigurationBroadcaster*, ConfigurationHints nHint )
+{
+ AllSettings::LocaleSettingsChanged( nHint );
+}
+
+ImplSVWinData* CreateSVWinData()
+{
+ if (!comphelper::LibreOfficeKit::isActive())
+ return nullptr;
+
+ ImplSVWinData* p = new ImplSVWinData;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ assert(pSVData && pSVData->mpWinData);
+
+ p->mpFocusWin = pSVData->mpWinData->mpFocusWin;
+ return p;
+}
+
+void DestroySVWinData(ImplSVWinData* pData)
+{
+ delete pData;
+}
+
+void SetSVWinData(ImplSVWinData* pSVWinData)
+{
+ if (!comphelper::LibreOfficeKit::isActive())
+ return;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ assert(pSVData != nullptr);
+
+ if (pSVData->mpWinData == pSVWinData)
+ return;
+
+ // If current one is the static, clean it up to avoid having lingering references.
+ if (pSVData->mpWinData == &private_aImplSVWinData::get())
+ {
+ pSVData->mpWinData->mpFocusWin.reset();
+ }
+
+ pSVData->mpWinData = pSVWinData;
+ if (pSVData->mpWinData == nullptr)
+ {
+ pSVData->mpWinData = &private_aImplSVWinData::get(); // Never leave it null.
+ }
+}
+
+ImplSVData::ImplSVData()
+{
+ mpHelpData = &private_aImplSVHelpData::get();
+ mpWinData = &private_aImplSVWinData::get();
+}
+
+ImplSVHelpData* CreateSVHelpData()
+{
+ if (!comphelper::LibreOfficeKit::isActive())
+ return nullptr;
+
+ ImplSVHelpData* pNewData = new ImplSVHelpData;
+
+ // Set options set globally
+ ImplSVHelpData& aStaticHelpData = private_aImplSVHelpData::get();
+ pNewData->mbContextHelp = aStaticHelpData.mbContextHelp;
+ pNewData->mbExtHelp = aStaticHelpData.mbExtHelp;
+ pNewData->mbExtHelpMode = aStaticHelpData.mbExtHelpMode;
+ pNewData->mbOldBalloonMode = aStaticHelpData.mbOldBalloonMode;
+ pNewData->mbBalloonHelp = aStaticHelpData.mbBalloonHelp;
+ pNewData->mbQuickHelp = aStaticHelpData.mbQuickHelp;
+
+ return pNewData;
+}
+
+void DestroySVHelpData(ImplSVHelpData* pSVHelpData)
+{
+ if (!comphelper::LibreOfficeKit::isActive())
+ return;
+
+ // Change the SVData's help date if necessary
+ if(ImplGetSVData()->mpHelpData == pSVHelpData)
+ {
+ ImplGetSVData()->mpHelpData = &private_aImplSVHelpData::get();
+ }
+
+ if(pSVHelpData)
+ {
+ ImplDestroyHelpWindow(*pSVHelpData, false);
+ delete pSVHelpData;
+ }
+}
+
+void SetSVHelpData(ImplSVHelpData* pSVHelpData)
+{
+ if (!comphelper::LibreOfficeKit::isActive())
+ return;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->mpHelpData == pSVHelpData)
+ return;
+
+ // If current one is the static, clean it up to avoid having lingering references.
+ if (pSVData->mpHelpData == &private_aImplSVHelpData::get())
+ {
+ pSVData->mpHelpData->mpHelpWin.reset();
+ }
+
+ pSVData->mpHelpData = pSVHelpData;
+ if (pSVData->mpHelpData == nullptr)
+ {
+ pSVData->mpHelpData = &private_aImplSVHelpData::get(); // Never leave it null.
+ }
+}
+
+ImplSVHelpData& ImplGetSVHelpData()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if(pSVData->mpHelpData)
+ {
+ return *pSVData->mpHelpData;
+ }
+ else
+ {
+ return private_aImplSVHelpData::get();
+ }
+}
+
+ImplSVData::~ImplSVData() {}
+
+ImplSVAppData::ImplSVAppData()
+{
+ m_bUseSystemLoop = getenv("SAL_USE_SYSTEM_LOOP") != nullptr;
+ SAL_WARN_IF(m_bUseSystemLoop, "vcl.schedule", "Overriding to run LO on system event loop!");
+}
+
+ImplSVAppData::~ImplSVAppData() {}
+ImplSVGDIData::~ImplSVGDIData() {}
+ImplSVFrameData::~ImplSVFrameData() {}
+ImplSVWinData::~ImplSVWinData() {}
+ImplSVHelpData::~ImplSVHelpData() {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/svmain.cxx b/vcl/source/app/svmain.cxx
new file mode 100644
index 000000000..a88a62611
--- /dev/null
+++ b/vcl/source/app/svmain.cxx
@@ -0,0 +1,692 @@
+/* -*- 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 <sal/log.hxx>
+
+#include <cassert>
+
+#include <osl/file.hxx>
+#include <osl/signal.h>
+
+#include <desktop/exithelper.h>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/asyncnotification.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <unotools/syslocale.hxx>
+#include <unotools/syslocaleoptions.hxx>
+#include <vcl/QueueInfo.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/vclmain.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/scheduler.hxx>
+#include <vcl/image.hxx>
+#include <vcl/ImageTree.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+#include <configsettings.hxx>
+#include <vcl/lazydelete.hxx>
+#include <vcl/embeddedfontshelper.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/print.hxx>
+#include <debugevent.hxx>
+#include <scrwnd.hxx>
+#include <windowdev.hxx>
+#include <svdata.hxx>
+
+#ifdef _WIN32
+#include <svsys.h>
+#include <process.h>
+#include <ole2.h>
+#else
+#include <stdlib.h>
+#endif
+
+#ifdef ANDROID
+#include <cppuhelper/bootstrap.hxx>
+#include <jni.h>
+#endif
+
+#include <impfontcache.hxx>
+#include <salinst.hxx>
+#include <vcl/svmain.hxx>
+#include <dbggui.hxx>
+#include <accmgr.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <print.h>
+#include <salsys.hxx>
+#include <saltimer.hxx>
+#include <displayconnectiondispatch.hxx>
+
+#include <config_features.h>
+#include <config_feature_opencl.h>
+
+#include <osl/process.h>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+
+#ifdef _WIN32
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+#endif
+
+#include <comphelper/lok.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <uno/current_context.hxx>
+
+#include <opencl/OpenCLZone.hxx>
+#include <opengl/zone.hxx>
+#include <skia/zone.hxx>
+#include <watchdog.hxx>
+
+#include <basegfx/utils/systemdependentdata.hxx>
+#include <tools/diagnose_ex.h>
+
+#if OSL_DEBUG_LEVEL > 0
+#include <typeinfo>
+#include <rtl/strbuf.hxx>
+#endif
+
+#ifdef LINUX
+#include <unx/gendata.hxx>
+#endif
+
+using namespace ::com::sun::star;
+
+static bool g_bIsLeanException;
+
+static oslSignalAction VCLExceptionSignal_impl( void* /*pData*/, oslSignalInfo* pInfo)
+{
+ static volatile bool bIn = false;
+
+ // if we crash again, bail out immediately
+ if ( bIn || g_bIsLeanException)
+ return osl_Signal_ActCallNextHdl;
+
+ ExceptionCategory nVCLException = ExceptionCategory::NONE;
+
+ // UAE
+ if ( (pInfo->Signal == osl_Signal_AccessViolation) ||
+ (pInfo->Signal == osl_Signal_IntegerDivideByZero) ||
+ (pInfo->Signal == osl_Signal_FloatDivideByZero) ||
+ (pInfo->Signal == osl_Signal_DebugBreak) )
+ {
+ nVCLException = ExceptionCategory::System;
+#if HAVE_FEATURE_OPENGL
+ if (OpenGLZone::isInZone())
+ OpenGLZone::hardDisable();
+#endif
+#if HAVE_FEATURE_SKIA
+ if (SkiaZone::isInZone())
+ SkiaZone::hardDisable();
+#endif
+#if HAVE_FEATURE_OPENCL
+ if (OpenCLZone::isInZone())
+ {
+ OpenCLZone::hardDisable();
+#ifdef _WIN32
+ if (OpenCLInitialZone::isInZone())
+ TerminateProcess(GetCurrentProcess(), EXITHELPER_NORMAL_RESTART);
+#endif
+ }
+#endif
+ }
+
+ // DISPLAY-Unix
+ if ((pInfo->Signal == osl_Signal_User) &&
+ (pInfo->UserSignal == OSL_SIGNAL_USER_X11SUBSYSTEMERROR) )
+ nVCLException = ExceptionCategory::UserInterface;
+
+ if ( nVCLException != ExceptionCategory::NONE )
+ {
+ bIn = true;
+
+ vcl::SolarMutexTryAndBuyGuard aLock;
+ if( aLock.isAcquired())
+ {
+ // do not stop timer because otherwise the UAE-Box will not be painted as well
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( pSVData->mpApp )
+ {
+ SystemWindowFlags nOldMode = Application::GetSystemWindowMode();
+ Application::SetSystemWindowMode( nOldMode & ~SystemWindowFlags::NOAUTOMODE );
+ pSVData->mpApp->Exception( nVCLException );
+ Application::SetSystemWindowMode( nOldMode );
+ }
+ }
+ bIn = false;
+ }
+
+ return osl_Signal_ActCallNextHdl;
+
+}
+
+int ImplSVMain()
+{
+ // The 'real' SVMain()
+ ImplSVData* pSVData = ImplGetSVData();
+
+ SAL_WARN_IF( !pSVData->mpApp, "vcl", "no instance of class Application" );
+
+ int nReturn = EXIT_FAILURE;
+
+ const bool bWasInitVCL = IsVCLInit();
+ const bool bInit = bWasInitVCL || InitVCL();
+ int nRet = 0;
+ if (!bWasInitVCL && bInit && pSVData->mpDefInst->SVMainHook(&nRet))
+ return nRet;
+
+ if( bInit )
+ {
+ // call application main
+ pSVData->maAppData.mbInAppMain = true;
+ nReturn = pSVData->mpApp->Main();
+ pSVData->maAppData.mbInAppMain = false;
+ }
+
+ if( pSVData->mxDisplayConnection.is() )
+ {
+ pSVData->mxDisplayConnection->terminate();
+ pSVData->mxDisplayConnection.clear();
+ }
+
+ // This is a hack to work around the problem of the asynchronous nature
+ // of bridging accessibility through Java: on shutdown there might still
+ // be some events in the AWT EventQueue, which need the SolarMutex which
+ // - on the other hand - is destroyed in DeInitVCL(). So empty the queue
+ // here ..
+ if( pSVData->mxAccessBridge.is() )
+ {
+ {
+ SolarMutexReleaser aReleaser;
+ pSVData->mxAccessBridge->dispose();
+ }
+ pSVData->mxAccessBridge.clear();
+ }
+
+ WatchdogThread::stop();
+ DeInitVCL();
+
+ return nReturn;
+}
+
+int SVMain()
+{
+ return ImplSVMain();
+}
+
+// This variable is set when no Application object has been instantiated
+// before InitVCL is called
+static Application * pOwnSvApp = nullptr;
+
+// Exception handler. pExceptionHandler != NULL => VCL already inited
+static oslSignalHandler pExceptionHandler = nullptr;
+
+namespace {
+
+class DesktopEnvironmentContext: public cppu::WeakImplHelper< css::uno::XCurrentContext >
+{
+public:
+ explicit DesktopEnvironmentContext( const css::uno::Reference< css::uno::XCurrentContext > & ctx)
+ : m_xNextContext( ctx ) {}
+
+ // XCurrentContext
+ virtual css::uno::Any SAL_CALL getValueByName( const OUString& Name ) override;
+
+private:
+ css::uno::Reference< css::uno::XCurrentContext > m_xNextContext;
+};
+
+}
+
+uno::Any SAL_CALL DesktopEnvironmentContext::getValueByName( const OUString& Name)
+{
+ uno::Any retVal;
+
+ if ( Name == "system.desktop-environment" )
+ {
+ retVal <<= Application::GetDesktopEnvironment();
+ }
+ else if( m_xNextContext.is() )
+ {
+ // Call next context in chain if found
+ retVal = m_xNextContext->getValueByName( Name );
+ }
+ return retVal;
+}
+
+bool IsVCLInit()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return pExceptionHandler != nullptr &&
+ pSVData->mpApp != nullptr &&
+ pSVData->mpDefInst != nullptr;
+}
+
+#ifdef DBG_UTIL
+namespace vclmain
+{
+ bool isAlive()
+ {
+ return ImplGetSVData()->mpDefInst;
+ }
+}
+#endif
+
+
+bool InitVCL()
+{
+ if (IsVCLInit())
+ {
+ SAL_INFO("vcl.app", "Double initialization of vcl");
+ return true;
+ }
+
+ if( pExceptionHandler != nullptr )
+ return false;
+
+ EmbeddedFontsHelper::clearTemporaryFontFiles();
+
+ if( !ImplGetSVData()->mpApp )
+ {
+ pOwnSvApp = new Application();
+ }
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // remember Main-Thread-Id
+ pSVData->mnMainThreadId = ::osl::Thread::getCurrentIdentifier();
+
+ // Initialize Sal
+ pSVData->mpDefInst = CreateSalInstance();
+ if ( !pSVData->mpDefInst )
+ return false;
+ pSVData->mpDefInst->AcquireYieldMutex();
+
+ // Desktop Environment context (to be able to get value of "system.desktop-environment" as soon as possible)
+ css::uno::setCurrentContext(
+ new DesktopEnvironmentContext( css::uno::getCurrentContext() ) );
+
+ // Initialize application instance (should be done after initialization of VCL SAL part)
+ if (pSVData->mpApp)
+ {
+ // call init to initialize application class
+ // soffice/sfx implementation creates the global service manager
+ pSVData->mpApp->Init();
+ }
+
+ try
+ {
+ //Now that uno has been bootstrapped we can ask the config what the UI language is so that we can
+ //force that in as $LANGUAGE. That way we can get gtk to render widgets RTL
+ //if we have a RTL UI in an otherwise LTR locale and get gettext using externals (e.g. python)
+ //to match their translations to our preferred UI language
+ OUString aLocaleString(SvtSysLocaleOptions().GetRealUILanguageTag().getGlibcLocaleString(u".UTF-8"));
+ if (!aLocaleString.isEmpty())
+ {
+ MsLangId::getSystemUILanguage(); //call this now to pin what the system UI really was
+ OUString envVar("LANGUAGE");
+ osl_setEnvironment(envVar.pData, aLocaleString.pData);
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_INFO_EXCEPTION("vcl.app", "Unable to get ui language:");
+ }
+
+ pSVData->mpDefInst->AfterAppInit();
+
+ // Fetch AppFileName and make it absolute before the workdir changes...
+ OUString aExeFileName;
+ osl_getExecutableFile( &aExeFileName.pData );
+
+ // convert path to native file format
+ OUString aNativeFileName;
+ osl::FileBase::getSystemPathFromFileURL( aExeFileName, aNativeFileName );
+ pSVData->maAppData.mxAppFileName = aNativeFileName;
+
+ // Initialize global data
+ pSVData->maGDIData.mxScreenFontList = std::make_shared<vcl::font::PhysicalFontCollection>();
+ pSVData->maGDIData.mxScreenFontCache = std::make_shared<ImplFontCache>();
+ pSVData->maGDIData.mxGrfConverter.reset(new GraphicConverter);
+
+ g_bIsLeanException = getenv("LO_LEAN_EXCEPTION") != nullptr;
+ // Set exception handler
+ pExceptionHandler = osl_addSignalHandler(VCLExceptionSignal_impl, nullptr);
+
+#ifndef NDEBUG
+ DbgGUIInitSolarMutexCheck();
+#endif
+
+#if OSL_DEBUG_LEVEL > 0
+ DebugEventInjector::getCreate();
+#endif
+
+#ifndef _WIN32
+ // Clear startup notification details for child processes
+ // See https://bugs.freedesktop.org/show_bug.cgi?id=11375 for discussion
+ unsetenv("DESKTOP_STARTUP_ID");
+#endif
+
+ return true;
+}
+
+namespace
+{
+
+/** Serves for destroying the VCL UNO wrapper as late as possible. This avoids
+ crash at exit in some special cases when a11y is enabled (e.g., when
+ a bundled extension is registered/deregistered during startup, forcing exit
+ while the app is still in splash screen.)
+ */
+class VCLUnoWrapperDeleter : public cppu::WeakImplHelper<css::lang::XEventListener>
+{
+ virtual void SAL_CALL disposing(lang::EventObject const& rSource) override;
+};
+
+void
+VCLUnoWrapperDeleter::disposing(lang::EventObject const& /* rSource */)
+{
+ ImplSVData* const pSVData = ImplGetSVData();
+ if (pSVData && pSVData->mpUnoWrapper)
+ {
+ pSVData->mpUnoWrapper->Destroy();
+ pSVData->mpUnoWrapper = nullptr;
+ }
+}
+
+}
+
+void DeInitVCL()
+{
+ // The LOK Windows map container should be empty
+ assert(vcl::Window::IsLOKWindowsEmpty());
+
+ //rhbz#1444437, when using LibreOffice like a library you can't realistically
+ //tear everything down and recreate them on the next call, there's too many
+ //(c++) singletons that point to stuff that gets deleted during shutdown
+ //which won't be recreated on restart.
+ if (comphelper::LibreOfficeKit::isActive())
+ return;
+
+ {
+ SolarMutexReleaser r; // unblock threads blocked on that so we can join
+ ::comphelper::JoinAsyncEventNotifiers();
+ }
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // lp#1560328: clear cache before disposing rest of VCL
+ if(pSVData->mpBlendFrameCache)
+ pSVData->mpBlendFrameCache->m_aLastResult.Clear();
+ pSVData->mbDeInit = true;
+
+ vcl::DeleteOnDeinitBase::ImplDeleteOnDeInit();
+
+#if OSL_DEBUG_LEVEL > 0
+ OStringBuffer aBuf( 256 );
+ aBuf.append( "DeInitVCL: some top Windows are still alive\n" );
+ tools::Long nTopWindowCount = Application::GetTopWindowCount();
+ tools::Long nBadTopWindows = nTopWindowCount;
+ for( tools::Long i = 0; i < nTopWindowCount; i++ )
+ {
+ vcl::Window* pWin = Application::GetTopWindow( i );
+ // default window will be destroyed further down
+ // but may still be useful during deinit up to that point
+ if( pWin == pSVData->mpDefaultWin )
+ nBadTopWindows--;
+ else
+ {
+ aBuf.append( "text = \"" );
+ aBuf.append( OUStringToOString( pWin->GetText(), osl_getThreadTextEncoding() ) );
+ aBuf.append( "\" type = \"" );
+ aBuf.append( typeid(*pWin).name() );
+ aBuf.append( "\", ptr = 0x" );
+ aBuf.append( reinterpret_cast<sal_Int64>( pWin ), 16 );
+ aBuf.append( "\n" );
+ }
+ }
+ SAL_WARN_IF( nBadTopWindows!=0, "vcl", aBuf.getStr() );
+#endif
+
+ ImageTree::get().shutdown();
+
+ osl_removeSignalHandler( pExceptionHandler);
+ pExceptionHandler = nullptr;
+
+ // free global data
+ pSVData->maGDIData.mxGrfConverter.reset();
+ pSVData->mpSettingsConfigItem.reset();
+
+ // prevent unnecessary painting during Scheduler shutdown
+ // as this processes all pending events in debug builds.
+ ImplGetSystemDependentDataManager().flushAll();
+
+ Scheduler::ImplDeInitScheduler();
+
+ pSVData->mpWinData->maMsgBoxImgList.clear();
+ pSVData->maCtrlData.maCheckImgList.clear();
+ pSVData->maCtrlData.maRadioImgList.clear();
+ pSVData->maCtrlData.mpDisclosurePlus.reset();
+ pSVData->maCtrlData.mpDisclosureMinus.reset();
+ pSVData->mpDefaultWin.disposeAndClear();
+
+#if defined _WIN32
+ // See GetSystemClipboard (vcl/source/treelist/transfer2.cxx):
+ if (auto const comp = css::uno::Reference<css::lang::XComponent>(
+ pSVData->m_xSystemClipboard, css::uno::UNO_QUERY))
+ {
+ SolarMutexReleaser r; // unblock pending "clipboard content changed" notifications
+ comp->dispose(); // will use s_aClipboardSingletonMutex for CWinClipboard
+ }
+ pSVData->m_xSystemClipboard.clear();
+#endif
+
+#ifndef NDEBUG
+ DbgGUIDeInitSolarMutexCheck();
+#endif
+
+ if ( pSVData->mpUnoWrapper )
+ {
+ try
+ {
+ uno::Reference<frame::XDesktop2> const xDesktop = frame::Desktop::create(
+ comphelper::getProcessComponentContext() );
+ xDesktop->addEventListener(new VCLUnoWrapperDeleter);
+ }
+ catch (uno::Exception const&)
+ {
+ // ignore
+ }
+ }
+
+ if( pSVData->mpApp || pSVData->maDeInitHook.IsSet() )
+ {
+ SolarMutexReleaser aReleaser;
+ // call deinit to deinitialize application class
+ // soffice/sfx implementation disposes the global service manager
+ // Warning: After this call you can't call uno services
+ if( pSVData->mpApp )
+ {
+ pSVData->mpApp->DeInit();
+ }
+ if( pSVData->maDeInitHook.IsSet() )
+ {
+ pSVData->maDeInitHook.Call(nullptr);
+ }
+ }
+
+ if ( pSVData->maAppData.mxSettings )
+ {
+ if ( pSVData->maAppData.mpCfgListener )
+ {
+ pSVData->maAppData.mxSettings->GetSysLocale().GetOptions().RemoveListener( pSVData->maAppData.mpCfgListener );
+ delete pSVData->maAppData.mpCfgListener;
+ }
+
+ pSVData->maAppData.mxSettings.reset();
+ }
+ if ( pSVData->maAppData.mpAccelMgr )
+ {
+ delete pSVData->maAppData.mpAccelMgr;
+ pSVData->maAppData.mpAccelMgr = nullptr;
+ }
+ pSVData->maAppData.maKeyListeners.clear();
+ pSVData->mpBlendFrameCache.reset();
+
+ ImplDeletePrnQueueList();
+
+ // destroy all Sal interfaces before destroying the instance
+ // and thereby unloading the plugin
+ pSVData->mpSalSystem.reset();
+ assert( !pSVData->maSchedCtx.mpSalTimer );
+ delete pSVData->maSchedCtx.mpSalTimer;
+ pSVData->maSchedCtx.mpSalTimer = nullptr;
+
+ pSVData->mpDefaultWin = nullptr;
+ pSVData->mpIntroWindow = nullptr;
+ pSVData->maAppData.mpActivePopupMenu = nullptr;
+ pSVData->maAppData.mpWheelWindow = nullptr;
+ pSVData->maGDIData.mpFirstWinGraphics = nullptr;
+ pSVData->maGDIData.mpLastWinGraphics = nullptr;
+ pSVData->maGDIData.mpFirstVirGraphics = nullptr;
+ pSVData->maGDIData.mpLastVirGraphics = nullptr;
+ pSVData->maGDIData.mpFirstPrnGraphics = nullptr;
+ pSVData->maGDIData.mpLastPrnGraphics = nullptr;
+ pSVData->maGDIData.mpFirstVirDev = nullptr;
+ pSVData->maGDIData.mpFirstPrinter = nullptr;
+ pSVData->maFrameData.mpFirstFrame = nullptr;
+ pSVData->maFrameData.mpAppWin = nullptr;
+ pSVData->maFrameData.mpActiveApplicationFrame = nullptr;
+ pSVData->mpWinData->mpCaptureWin = nullptr;
+ pSVData->mpWinData->mpLastDeacWin = nullptr;
+ pSVData->mpWinData->mpFirstFloat = nullptr;
+ pSVData->mpWinData->mpExecuteDialogs.clear();
+ pSVData->mpWinData->mpExtTextInputWin = nullptr;
+ pSVData->mpWinData->mpTrackWin = nullptr;
+ pSVData->mpWinData->mpAutoScrollWin = nullptr;
+ pSVData->mpWinData->mpLastWheelWindow = nullptr;
+
+ pSVData->maGDIData.mxScreenFontList.reset();
+ pSVData->maGDIData.mxScreenFontCache.reset();
+ // we are iterating over a map and doing erase while inside a loop which is doing erase
+ // hence we can't use clear() here
+ pSVData->maGDIData.maScaleCache.remove_if([](const lru_scale_cache::key_value_pair_t&)
+ { return true; });
+
+ pSVData->maGDIData.maThemeDrawCommandsCache.clear();
+ pSVData->maGDIData.maThemeImageCache.clear();
+
+ // Deinit Sal
+ if (pSVData->mpDefInst)
+ {
+ pSVData->mpDefInst->ReleaseYieldMutexAll();
+ DestroySalInstance( pSVData->mpDefInst );
+ pSVData->mpDefInst = nullptr;
+ }
+
+ // This only works on Linux. On Mac and Windows I get very
+ // weird segment violations.
+#if defined LINUX
+ delete pSVData->mpSalData;
+#endif
+
+ if( pOwnSvApp )
+ {
+ delete pOwnSvApp;
+ pOwnSvApp = nullptr;
+ }
+
+ EmbeddedFontsHelper::clearTemporaryFontFiles();
+}
+
+namespace {
+
+// only one call is allowed
+struct WorkerThreadData
+{
+ oslWorkerFunction pWorker;
+ void * pThreadData;
+ WorkerThreadData( oslWorkerFunction pWorker_, void * pThreadData_ )
+ : pWorker( pWorker_ )
+ , pThreadData( pThreadData_ )
+ {
+ }
+};
+
+}
+
+#ifdef _WIN32
+static HANDLE hThreadID = nullptr;
+static DWORD WINAPI threadmain( _In_ LPVOID pArgs )
+{
+ OleInitialize( nullptr );
+ static_cast<WorkerThreadData*>(pArgs)->pWorker( static_cast<WorkerThreadData*>(pArgs)->pThreadData );
+ delete static_cast<WorkerThreadData*>(pArgs);
+ OleUninitialize();
+ hThreadID = nullptr;
+ return 0;
+}
+#else
+static oslThread hThreadID = nullptr;
+extern "C"
+{
+static void MainWorkerFunction( void* pArgs )
+{
+ static_cast<WorkerThreadData*>(pArgs)->pWorker( static_cast<WorkerThreadData*>(pArgs)->pThreadData );
+ delete static_cast<WorkerThreadData*>(pArgs);
+ hThreadID = nullptr;
+}
+} // extern "C"
+#endif
+
+void CreateMainLoopThread( oslWorkerFunction pWorker, void * pThreadData )
+{
+#ifdef _WIN32
+ // sal thread always call CoInitializeEx, so a system dependent implementation is necessary
+
+ DWORD uThreadID;
+ hThreadID = CreateThread(
+ nullptr, // no security handle
+ 0, // stacksize 0 means default
+ threadmain, // thread worker function
+ new WorkerThreadData( pWorker, pThreadData ), // arguments for worker function
+ 0, // 0 means: create immediately otherwise use CREATE_SUSPENDED
+ &uThreadID ); // thread id to fill
+#else
+ hThreadID = osl_createThread( MainWorkerFunction, new WorkerThreadData( pWorker, pThreadData ) );
+#endif
+}
+
+void JoinMainLoopThread()
+{
+ if( hThreadID )
+ {
+#ifdef _WIN32
+ WaitForSingleObject(hThreadID, INFINITE);
+#else
+ osl_joinWithThread(hThreadID);
+ osl_destroyThread( hThreadID );
+#endif
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/timer.cxx b/vcl/source/app/timer.cxx
new file mode 100644
index 000000000..183cd41a4
--- /dev/null
+++ b/vcl/source/app/timer.cxx
@@ -0,0 +1,103 @@
+/* -*- 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/log.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/scheduler.hxx>
+#include <schedulerimpl.hxx>
+
+void Timer::SetDeletionFlags()
+{
+ // If no AutoTimer, then stop.
+ if ( !mbAuto )
+ Task::SetDeletionFlags();
+}
+
+sal_uInt64 Timer::UpdateMinPeriod( sal_uInt64 nTimeNow ) const
+{
+ sal_uInt64 nWakeupTime = GetSchedulerData()->mnUpdateTime + mnTimeout;
+ return ( nWakeupTime <= nTimeNow )
+ ? Scheduler::ImmediateTimeoutMs : nWakeupTime - nTimeNow;
+}
+
+Timer::Timer( bool bAuto, const char *pDebugName )
+ : Task( pDebugName )
+ , mnTimeout( Scheduler::ImmediateTimeoutMs )
+ , mbAuto( bAuto )
+{
+ SetPriority( TaskPriority::DEFAULT );
+}
+
+Timer::Timer( const char *pDebugName )
+ : Timer( false, pDebugName )
+{
+}
+
+Timer::Timer( const Timer& rTimer )
+ : Timer( rTimer.mbAuto, rTimer.GetDebugName() )
+{
+ maInvokeHandler = rTimer.maInvokeHandler;
+ mnTimeout = rTimer.mnTimeout;
+}
+
+Timer::~Timer()
+{
+}
+
+Timer& Timer::operator=( const Timer& rTimer )
+{
+ Task::operator=( rTimer );
+ maInvokeHandler = rTimer.maInvokeHandler;
+ mnTimeout = rTimer.mnTimeout;
+ SAL_WARN_IF( mbAuto != rTimer.mbAuto, "vcl.schedule",
+ "Copying Timer with different mbAuto value!" );
+ return *this;
+}
+
+void Timer::Invoke()
+{
+ maInvokeHandler.Call( this );
+}
+
+void Timer::Invoke( Timer *arg )
+{
+ maInvokeHandler.Call( arg );
+}
+
+void Timer::Start(const bool bStartTimer)
+{
+ Task::Start(false);
+ if (bStartTimer)
+ Task::StartTimer(mnTimeout);
+}
+
+void Timer::SetTimeout( sal_uInt64 nNewTimeout )
+{
+ mnTimeout = nNewTimeout;
+ // If timer is active, then renew clock.
+ if ( IsActive() )
+ StartTimer( mnTimeout );
+}
+
+AutoTimer::AutoTimer( const char *pDebugName )
+ : Timer( true, pDebugName )
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/unohelp.cxx b/vcl/source/app/unohelp.cxx
new file mode 100644
index 000000000..89b526c91
--- /dev/null
+++ b/vcl/source/app/unohelp.cxx
@@ -0,0 +1,213 @@
+/* -*- 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 <vcl/svapp.hxx>
+#include <vcl/unohelp.hxx>
+
+#include <osl/diagnose.h>
+
+#include <comphelper/processfactory.hxx>
+
+#include <com/sun/star/i18n/BreakIterator.hpp>
+#include <com/sun/star/i18n/CharacterClassification.hpp>
+#include <com/sun/star/awt/FontWeight.hpp>
+#include <com/sun/star/awt/FontWidth.hpp>
+#include <com/sun/star/awt/XExtendedToolkit.hpp>
+#include <com/sun/star/accessibility/AccessibleEventObject.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+
+using namespace ::com::sun::star;
+
+uno::Reference < i18n::XBreakIterator > vcl::unohelper::CreateBreakIterator()
+{
+ uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext();
+ return i18n::BreakIterator::create(xContext);
+}
+
+uno::Reference < i18n::XCharacterClassification > vcl::unohelper::CreateCharacterClassification()
+{
+ return i18n::CharacterClassification::create( comphelper::getProcessComponentContext() );
+}
+
+float vcl::unohelper::ConvertFontWidth( FontWidth eWidth )
+{
+ if( eWidth == WIDTH_DONTKNOW )
+ return css::awt::FontWidth::DONTKNOW;
+ else if( eWidth == WIDTH_ULTRA_CONDENSED )
+ return css::awt::FontWidth::ULTRACONDENSED;
+ else if( eWidth == WIDTH_EXTRA_CONDENSED )
+ return css::awt::FontWidth::EXTRACONDENSED;
+ else if( eWidth == WIDTH_CONDENSED )
+ return css::awt::FontWidth::CONDENSED;
+ else if( eWidth == WIDTH_SEMI_CONDENSED )
+ return css::awt::FontWidth::SEMICONDENSED;
+ else if( eWidth == WIDTH_NORMAL )
+ return css::awt::FontWidth::NORMAL;
+ else if( eWidth == WIDTH_SEMI_EXPANDED )
+ return css::awt::FontWidth::SEMIEXPANDED;
+ else if( eWidth == WIDTH_EXPANDED )
+ return css::awt::FontWidth::EXPANDED;
+ else if( eWidth == WIDTH_EXTRA_EXPANDED )
+ return css::awt::FontWidth::EXTRAEXPANDED;
+ else if( eWidth == WIDTH_ULTRA_EXPANDED )
+ return css::awt::FontWidth::ULTRAEXPANDED;
+
+ OSL_FAIL( "Unknown FontWidth" );
+ return css::awt::FontWidth::DONTKNOW;
+}
+
+FontWidth vcl::unohelper::ConvertFontWidth( float f )
+{
+ if( f <= css::awt::FontWidth::DONTKNOW )
+ return WIDTH_DONTKNOW;
+ else if( f <= css::awt::FontWidth::ULTRACONDENSED )
+ return WIDTH_ULTRA_CONDENSED;
+ else if( f <= css::awt::FontWidth::EXTRACONDENSED )
+ return WIDTH_EXTRA_CONDENSED;
+ else if( f <= css::awt::FontWidth::CONDENSED )
+ return WIDTH_CONDENSED;
+ else if( f <= css::awt::FontWidth::SEMICONDENSED )
+ return WIDTH_SEMI_CONDENSED;
+ else if( f <= css::awt::FontWidth::NORMAL )
+ return WIDTH_NORMAL;
+ else if( f <= css::awt::FontWidth::SEMIEXPANDED )
+ return WIDTH_SEMI_EXPANDED;
+ else if( f <= css::awt::FontWidth::EXPANDED )
+ return WIDTH_EXPANDED;
+ else if( f <= css::awt::FontWidth::EXTRAEXPANDED )
+ return WIDTH_EXTRA_EXPANDED;
+ else if( f <= css::awt::FontWidth::ULTRAEXPANDED )
+ return WIDTH_ULTRA_EXPANDED;
+
+ OSL_FAIL( "Unknown FontWidth" );
+ return WIDTH_DONTKNOW;
+}
+
+float vcl::unohelper::ConvertFontWeight( FontWeight eWeight )
+{
+ if( eWeight == WEIGHT_DONTKNOW )
+ return css::awt::FontWeight::DONTKNOW;
+ else if( eWeight == WEIGHT_THIN )
+ return css::awt::FontWeight::THIN;
+ else if( eWeight == WEIGHT_ULTRALIGHT )
+ return css::awt::FontWeight::ULTRALIGHT;
+ else if( eWeight == WEIGHT_LIGHT )
+ return css::awt::FontWeight::LIGHT;
+ else if( eWeight == WEIGHT_SEMILIGHT )
+ return css::awt::FontWeight::SEMILIGHT;
+ else if( ( eWeight == WEIGHT_NORMAL ) || ( eWeight == WEIGHT_MEDIUM ) )
+ return css::awt::FontWeight::NORMAL;
+ else if( eWeight == WEIGHT_SEMIBOLD )
+ return css::awt::FontWeight::SEMIBOLD;
+ else if( eWeight == WEIGHT_BOLD )
+ return css::awt::FontWeight::BOLD;
+ else if( eWeight == WEIGHT_ULTRABOLD )
+ return css::awt::FontWeight::ULTRABOLD;
+ else if( eWeight == WEIGHT_BLACK )
+ return css::awt::FontWeight::BLACK;
+
+ OSL_FAIL( "Unknown FontWeight" );
+ return css::awt::FontWeight::DONTKNOW;
+}
+
+FontWeight vcl::unohelper::ConvertFontWeight( float f )
+{
+ if( f <= css::awt::FontWeight::DONTKNOW )
+ return WEIGHT_DONTKNOW;
+ else if( f <= css::awt::FontWeight::THIN )
+ return WEIGHT_THIN;
+ else if( f <= css::awt::FontWeight::ULTRALIGHT )
+ return WEIGHT_ULTRALIGHT;
+ else if( f <= css::awt::FontWeight::LIGHT )
+ return WEIGHT_LIGHT;
+ else if( f <= css::awt::FontWeight::SEMILIGHT )
+ return WEIGHT_SEMILIGHT;
+ else if( f <= css::awt::FontWeight::NORMAL )
+ return WEIGHT_NORMAL;
+ else if( f <= css::awt::FontWeight::SEMIBOLD )
+ return WEIGHT_SEMIBOLD;
+ else if( f <= css::awt::FontWeight::BOLD )
+ return WEIGHT_BOLD;
+ else if( f <= css::awt::FontWeight::ULTRABOLD )
+ return WEIGHT_ULTRABOLD;
+ else if( f <= css::awt::FontWeight::BLACK )
+ return WEIGHT_BLACK;
+
+ OSL_FAIL( "Unknown FontWeight" );
+ return WEIGHT_DONTKNOW;
+}
+
+css::awt::FontSlant vcl::unohelper::ConvertFontSlant(FontItalic eItalic)
+{
+ css::awt::FontSlant eRet(css::awt::FontSlant_DONTKNOW);
+ switch (eItalic)
+ {
+ case ITALIC_NONE:
+ eRet = css::awt::FontSlant_NONE;
+ break;
+ case ITALIC_OBLIQUE:
+ eRet = css::awt::FontSlant_OBLIQUE;
+ break;
+ case ITALIC_NORMAL:
+ eRet = css::awt::FontSlant_ITALIC;
+ break;
+ case ITALIC_DONTKNOW:
+ eRet = css::awt::FontSlant_DONTKNOW;
+ break;
+ case FontItalic_FORCE_EQUAL_SIZE:
+ eRet = css::awt::FontSlant::FontSlant_MAKE_FIXED_SIZE;
+ break;
+ }
+ return eRet;
+}
+
+FontItalic vcl::unohelper::ConvertFontSlant(css::awt::FontSlant eSlant)
+{
+ FontItalic eRet = ITALIC_DONTKNOW;
+ switch (eSlant)
+ {
+ case css::awt::FontSlant_NONE:
+ eRet = ITALIC_NONE;
+ break;
+ case css::awt::FontSlant_OBLIQUE:
+ eRet = ITALIC_OBLIQUE;
+ break;
+ case css::awt::FontSlant_ITALIC:
+ eRet = ITALIC_NORMAL;
+ break;
+ case css::awt::FontSlant_DONTKNOW:
+ eRet = ITALIC_DONTKNOW;
+ break;
+ case css::awt::FontSlant_REVERSE_OBLIQUE:
+ //there is no vcl reverse oblique
+ eRet = ITALIC_OBLIQUE;
+ break;
+ case css::awt::FontSlant_REVERSE_ITALIC:
+ //there is no vcl reverse normal
+ eRet = ITALIC_NORMAL;
+ break;
+ case css::awt::FontSlant::FontSlant_MAKE_FIXED_SIZE:
+ eRet = FontItalic_FORCE_EQUAL_SIZE;
+ break;
+ }
+ return eRet;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/unohelp2.cxx b/vcl/source/app/unohelp2.cxx
new file mode 100644
index 000000000..47cfab4bc
--- /dev/null
+++ b/vcl/source/app/unohelp2.cxx
@@ -0,0 +1,112 @@
+/* -*- 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/log.hxx>
+#include <vcl/unohelp2.hxx>
+#include <sot/exchange.hxx>
+#include <sot/formats.hxx>
+#include <vcl/svapp.hxx>
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
+#include <cppuhelper/queryinterface.hxx>
+#include <boost/property_tree/json_parser.hpp>
+#include <comphelper/lok.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+
+using namespace ::com::sun::star;
+
+namespace vcl::unohelper {
+
+ TextDataObject::TextDataObject( const OUString& rText ) : maText( rText )
+ {
+ }
+
+ TextDataObject::~TextDataObject()
+ {
+ }
+
+ void TextDataObject::CopyStringTo( const OUString& rContent,
+ const uno::Reference< datatransfer::clipboard::XClipboard >& rxClipboard,
+ const vcl::ILibreOfficeKitNotifier* pNotifier)
+ {
+ SAL_WARN_IF( !rxClipboard.is(), "vcl", "TextDataObject::CopyStringTo: invalid clipboard!" );
+ if ( !rxClipboard.is() )
+ return;
+
+ rtl::Reference<TextDataObject> pDataObj = new TextDataObject( rContent );
+
+ SolarMutexReleaser aReleaser;
+ try
+ {
+ rxClipboard->setContents( pDataObj, nullptr );
+
+ uno::Reference< datatransfer::clipboard::XFlushableClipboard > xFlushableClipboard( rxClipboard, uno::UNO_QUERY );
+ if( xFlushableClipboard.is() )
+ xFlushableClipboard->flushClipboard();
+
+ if (pNotifier != nullptr && comphelper::LibreOfficeKit::isActive())
+ {
+ boost::property_tree::ptree aTree;
+ aTree.put("content", rContent);
+ aTree.put("mimeType", "text/plain");
+ std::stringstream aStream;
+ boost::property_tree::write_json(aStream, aTree);
+ pNotifier->libreOfficeKitViewCallback(LOK_CALLBACK_CLIPBOARD_CHANGED, aStream.str().c_str());
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ }
+ }
+
+ // css::uno::XInterface
+ uno::Any TextDataObject::queryInterface( const uno::Type & rType )
+ {
+ uno::Any aRet = ::cppu::queryInterface( rType, static_cast< datatransfer::XTransferable* >(this) );
+ return (aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ));
+ }
+
+ // css::datatransfer::XTransferable
+ uno::Any TextDataObject::getTransferData( const datatransfer::DataFlavor& rFlavor )
+ {
+ SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
+ if ( nT != SotClipboardFormatId::STRING )
+ {
+ throw datatransfer::UnsupportedFlavorException();
+ }
+ return uno::Any(maText);
+ }
+
+ uno::Sequence< datatransfer::DataFlavor > TextDataObject::getTransferDataFlavors( )
+ {
+ uno::Sequence< datatransfer::DataFlavor > aDataFlavors(1);
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aDataFlavors.getArray()[0] );
+ return aDataFlavors;
+ }
+
+ sal_Bool TextDataObject::isDataFlavorSupported( const datatransfer::DataFlavor& rFlavor )
+ {
+ SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
+ return ( nT == SotClipboardFormatId::STRING );
+ }
+
+} // namespace vcl::unohelper
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/vclevent.cxx b/vcl/source/app/vclevent.cxx
new file mode 100644
index 000000000..b99c6987f
--- /dev/null
+++ b/vcl/source/app/vclevent.cxx
@@ -0,0 +1,94 @@
+/* -*- 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 <vcl/vclevent.hxx>
+#include <vcl/window.hxx>
+#include <vcl/menu.hxx>
+
+#include <vcleventlisteners.hxx>
+
+void VclEventListeners::Call( VclSimpleEvent& rEvent ) const
+{
+ if ( m_aListeners.empty() )
+ return;
+
+ // Copy the list, because this can be destroyed when calling a Link...
+ std::vector<Link<VclSimpleEvent&,void>> aCopy( m_aListeners );
+ std::vector<Link<VclSimpleEvent&,void>>::iterator aIter( aCopy.begin() );
+ std::vector<Link<VclSimpleEvent&,void>>::const_iterator aEnd( aCopy.end() );
+ m_updated = false;
+ if (VclWindowEvent* pWindowEvent = dynamic_cast<VclWindowEvent*>(&rEvent))
+ {
+ VclPtr<vcl::Window> xWin(pWindowEvent->GetWindow());
+ // checking mpWindowImpl to see if disposal is complete yet
+ while ( aIter != aEnd && (!xWin || xWin->mpWindowImpl) )
+ {
+ Link<VclSimpleEvent&,void> &rLink = *aIter;
+ // check this hasn't been removed in some re-enterancy scenario fdo#47368
+ // But only check if the list actually has been changed.
+ if( !m_updated || std::find(m_aListeners.begin(), m_aListeners.end(), rLink) != m_aListeners.end() )
+ rLink.Call( rEvent );
+ ++aIter;
+ }
+ }
+ else
+ {
+ while ( aIter != aEnd )
+ {
+ Link<VclSimpleEvent&,void> &rLink = *aIter;
+ if( !m_updated || std::find(m_aListeners.begin(), m_aListeners.end(), rLink) != m_aListeners.end() )
+ rLink.Call( rEvent );
+ ++aIter;
+ }
+ }
+}
+
+void VclEventListeners::addListener( const Link<VclSimpleEvent&,void>& rListener )
+{
+ m_aListeners.push_back( rListener );
+ m_updated = true;
+}
+
+void VclEventListeners::removeListener( const Link<VclSimpleEvent&,void>& rListener )
+{
+ m_aListeners.erase( std::remove(m_aListeners.begin(), m_aListeners.end(), rListener ), m_aListeners.end() );
+ m_updated = true;
+}
+
+VclWindowEvent::VclWindowEvent( vcl::Window* pWin, VclEventId n, void* pDat ) : VclSimpleEvent(n)
+{
+ pWindow = pWin; pData = pDat;
+}
+
+VclWindowEvent::~VclWindowEvent() {}
+
+VclMenuEvent::VclMenuEvent( Menu* pM, VclEventId n, sal_uInt16 nPos )
+ : VclSimpleEvent(n), pMenu(pM), mnPos(nPos)
+{}
+
+VclMenuEvent::~VclMenuEvent()
+{}
+
+Menu* VclMenuEvent::GetMenu() const
+{
+ return pMenu;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/watchdog.cxx b/vcl/source/app/watchdog.cxx
new file mode 100644
index 000000000..b14611e22
--- /dev/null
+++ b/vcl/source/app/watchdog.cxx
@@ -0,0 +1,166 @@
+/* -*- 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/.
+ */
+
+#include <watchdog.hxx>
+
+#include <config_features.h>
+
+#include <osl/conditn.hxx>
+#include <rtl/ref.hxx>
+#include <rtl/string.hxx>
+#include <sal/log.hxx>
+#include <comphelper/debuggerinfo.hxx>
+#include <opengl/zone.hxx>
+#include <skia/zone.hxx>
+
+#include <stdlib.h>
+
+#if defined HAVE_VALGRIND_HEADERS
+#include <valgrind/memcheck.h>
+#endif
+
+namespace
+{
+volatile bool gbWatchdogFiring = false;
+osl::Condition* gpWatchdogExit = nullptr;
+rtl::Reference<WatchdogThread> gxWatchdog;
+
+template <typename Zone> struct WatchdogHelper
+{
+ static inline sal_uInt64 nLastEnters = 0;
+ static inline int nUnchanged = 0; // how many unchanged nEnters
+ static inline bool bFired = false;
+ static inline bool bAbortFired = false;
+ static void setLastEnters() { nLastEnters = Zone::enterCount(); }
+ static void check()
+ {
+ if (Zone::isInZone())
+ {
+ const CrashWatchdogTimingsValues& aTimingValues = Zone::getCrashWatchdogTimingsValues();
+
+ if (nLastEnters == Zone::enterCount())
+ nUnchanged++;
+ else
+ nUnchanged = 0;
+ Zone::checkDebug(nUnchanged, aTimingValues);
+
+ // Not making progress
+ if (nUnchanged >= aTimingValues.mnDisableEntries)
+ {
+ if (!bFired)
+ {
+ gbWatchdogFiring = true;
+ SAL_WARN("vcl.watchdog", "Watchdog triggered: hard disable " << Zone::name());
+#ifdef DBG_UTIL
+ std::abort();
+#else
+ Zone::hardDisable();
+ gbWatchdogFiring = false;
+#endif
+ }
+ bFired = true;
+
+ // we can hang using VCL in the abort handling -> be impatient
+ if (bAbortFired)
+ {
+ SAL_WARN("vcl.watchdog", "Watchdog gave up: hard exiting " << Zone::name());
+ _Exit(1);
+ }
+ }
+
+ // Not making even more progress
+ if (nUnchanged >= aTimingValues.mnAbortAfter)
+ {
+ if (!bAbortFired)
+ {
+ SAL_WARN("vcl.watchdog", "Watchdog gave up: aborting " << Zone::name());
+ gbWatchdogFiring = true;
+ std::abort();
+ }
+ // coverity[dead_error_line] - we might have caught SIGABRT and failed to exit yet
+ bAbortFired = true;
+ }
+ }
+ else
+ {
+ nUnchanged = 0;
+ }
+ }
+};
+
+} // namespace
+
+WatchdogThread::WatchdogThread()
+ : salhelper::Thread("Crash Watchdog")
+{
+}
+
+void WatchdogThread::execute()
+{
+ TimeValue aQuarterSecond(0, 1000 * 1000 * 1000 * 0.25);
+ do
+ {
+#if HAVE_FEATURE_OPENGL
+ WatchdogHelper<OpenGLZone>::setLastEnters();
+#endif
+#if HAVE_FEATURE_SKIA
+ WatchdogHelper<SkiaZone>::setLastEnters();
+#endif
+
+ gpWatchdogExit->wait(&aQuarterSecond);
+
+#if defined HAVE_VALGRIND_HEADERS
+ if (RUNNING_ON_VALGRIND)
+ continue;
+#endif
+#if defined DBG_UTIL
+ if (comphelper::isDebuggerAttached())
+ continue;
+#endif
+
+#if HAVE_FEATURE_OPENGL
+ WatchdogHelper<OpenGLZone>::check();
+#endif
+#if HAVE_FEATURE_SKIA
+ WatchdogHelper<SkiaZone>::check();
+#endif
+
+ } while (!gpWatchdogExit->check());
+}
+
+void WatchdogThread::start()
+{
+ if (gxWatchdog != nullptr)
+ return; // already running
+ if (getenv("SAL_DISABLE_WATCHDOG"))
+ return;
+ gpWatchdogExit = new osl::Condition();
+ gxWatchdog.set(new WatchdogThread());
+ gxWatchdog->launch();
+}
+
+void WatchdogThread::stop()
+{
+ if (gbWatchdogFiring)
+ return; // in watchdog thread
+
+ if (gpWatchdogExit)
+ gpWatchdogExit->set();
+
+ if (gxWatchdog.is())
+ {
+ gxWatchdog->join();
+ gxWatchdog.clear();
+ }
+
+ delete gpWatchdogExit;
+ gpWatchdogExit = nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/weldutils.cxx b/vcl/source/app/weldutils.cxx
new file mode 100644
index 000000000..f17aed238
--- /dev/null
+++ b/vcl/source/app/weldutils.cxx
@@ -0,0 +1,662 @@
+/* -*- 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/.
+ */
+
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <comphelper/processfactory.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/zformat.hxx>
+#include <vcl/builderpage.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/commandinfoprovider.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weldutils.hxx>
+
+BuilderPage::BuilderPage(weld::Widget* pParent, weld::DialogController* pController,
+ const OUString& rUIXMLDescription, const OString& rID, bool bIsMobile)
+ : m_pDialogController(pController)
+ , m_xBuilder(Application::CreateBuilder(pParent, rUIXMLDescription, bIsMobile))
+ , m_xContainer(m_xBuilder->weld_container(rID))
+{
+}
+
+void BuilderPage::Activate() {}
+
+void BuilderPage::Deactivate() {}
+
+BuilderPage::~BuilderPage() COVERITY_NOEXCEPT_FALSE {}
+
+namespace weld
+{
+bool DialogController::runAsync(const std::shared_ptr<DialogController>& rController,
+ const std::function<void(sal_Int32)>& func)
+{
+ return rController->getDialog()->runAsync(rController, func);
+}
+
+DialogController::~DialogController() COVERITY_NOEXCEPT_FALSE {}
+
+Dialog* GenericDialogController::getDialog() { return m_xDialog.get(); }
+
+GenericDialogController::GenericDialogController(weld::Widget* pParent, const OUString& rUIFile,
+ const OString& rDialogId, bool bMobile)
+ : m_xBuilder(Application::CreateBuilder(pParent, rUIFile, bMobile))
+ , m_xDialog(m_xBuilder->weld_dialog(rDialogId))
+{
+}
+
+GenericDialogController::~GenericDialogController() COVERITY_NOEXCEPT_FALSE {}
+
+Dialog* MessageDialogController::getDialog() { return m_xDialog.get(); }
+
+MessageDialogController::MessageDialogController(weld::Widget* pParent, const OUString& rUIFile,
+ const OString& rDialogId,
+ const OString& rRelocateId)
+ : m_xBuilder(Application::CreateBuilder(pParent, rUIFile))
+ , m_xDialog(m_xBuilder->weld_message_dialog(rDialogId))
+ , m_xContentArea(m_xDialog->weld_message_area())
+{
+ if (!rRelocateId.isEmpty())
+ {
+ m_xRelocate = m_xBuilder->weld_container(rRelocateId);
+ m_xOrigParent = m_xRelocate->weld_parent();
+ //fdo#75121, a bit tricky because the widgets we want to align with
+ //don't actually exist in the ui description, they're implied
+ m_xOrigParent->move(m_xRelocate.get(), m_xContentArea.get());
+ }
+}
+
+MessageDialogController::~MessageDialogController()
+{
+ if (m_xRelocate)
+ {
+ m_xContentArea->move(m_xRelocate.get(), m_xOrigParent.get());
+ }
+}
+
+AssistantController::AssistantController(weld::Widget* pParent, const OUString& rUIFile,
+ const OString& rDialogId)
+ : m_xBuilder(Application::CreateBuilder(pParent, rUIFile))
+ , m_xAssistant(m_xBuilder->weld_assistant(rDialogId))
+{
+}
+
+Dialog* AssistantController::getDialog() { return m_xAssistant.get(); }
+
+AssistantController::~AssistantController() {}
+
+void TriStateEnabled::ButtonToggled(weld::Toggleable& rToggle)
+{
+ if (bTriStateEnabled)
+ {
+ switch (eState)
+ {
+ case TRISTATE_INDET:
+ rToggle.set_state(TRISTATE_FALSE);
+ break;
+ case TRISTATE_TRUE:
+ rToggle.set_state(TRISTATE_INDET);
+ break;
+ case TRISTATE_FALSE:
+ rToggle.set_state(TRISTATE_TRUE);
+ break;
+ }
+ }
+ eState = rToggle.get_state();
+}
+
+void RemoveParentKeepChildren(weld::TreeView& rTreeView, const weld::TreeIter& rParent)
+{
+ if (rTreeView.iter_has_child(rParent))
+ {
+ std::unique_ptr<weld::TreeIter> xNewParent(rTreeView.make_iterator(&rParent));
+ if (!rTreeView.iter_parent(*xNewParent))
+ xNewParent.reset();
+
+ while (true)
+ {
+ std::unique_ptr<weld::TreeIter> xChild(rTreeView.make_iterator(&rParent));
+ if (!rTreeView.iter_children(*xChild))
+ break;
+ rTreeView.move_subtree(*xChild, xNewParent.get(), -1);
+ }
+ }
+ rTreeView.remove(rParent);
+}
+
+EntryFormatter::EntryFormatter(weld::FormattedSpinButton& rSpinButton)
+ : m_rEntry(rSpinButton)
+ , m_pSpinButton(&rSpinButton)
+ , m_eOptions(Application::GetSettings().GetStyleSettings().GetSelectionOptions())
+{
+ Init();
+}
+
+EntryFormatter::EntryFormatter(weld::Entry& rEntry)
+ : m_rEntry(rEntry)
+ , m_pSpinButton(nullptr)
+ , m_eOptions(Application::GetSettings().GetStyleSettings().GetSelectionOptions())
+{
+ Init();
+}
+
+EntryFormatter::~EntryFormatter()
+{
+ m_rEntry.connect_changed(Link<weld::Entry&, void>());
+ m_rEntry.connect_focus_out(Link<weld::Widget&, void>());
+ if (m_pSpinButton)
+ m_pSpinButton->SetFormatter(nullptr);
+}
+
+void EntryFormatter::Init()
+{
+ m_rEntry.connect_changed(LINK(this, EntryFormatter, ModifyHdl));
+ m_rEntry.connect_focus_out(LINK(this, EntryFormatter, FocusOutHdl));
+ if (m_pSpinButton)
+ m_pSpinButton->SetFormatter(this);
+}
+
+Selection EntryFormatter::GetEntrySelection() const
+{
+ int nStartPos, nEndPos;
+ m_rEntry.get_selection_bounds(nStartPos, nEndPos);
+ return Selection(nStartPos, nEndPos);
+}
+
+OUString EntryFormatter::GetEntryText() const { return m_rEntry.get_text(); }
+
+void EntryFormatter::SetEntryText(const OUString& rText, const Selection& rSel)
+{
+ m_rEntry.set_text(rText);
+ auto nMin = rSel.Min();
+ auto nMax = rSel.Max();
+ m_rEntry.select_region(nMin < 0 ? 0 : nMin, nMax == SELECTION_MAX ? -1 : nMax);
+}
+
+void EntryFormatter::SetEntryTextColor(const Color* pColor)
+{
+ m_rEntry.set_font_color(pColor ? *pColor : COL_AUTO);
+}
+
+void EntryFormatter::UpdateCurrentValue(double dCurrentValue)
+{
+ Formatter::UpdateCurrentValue(dCurrentValue);
+ if (m_pSpinButton)
+ m_pSpinButton->sync_value_from_formatter();
+}
+
+void EntryFormatter::ClearMinValue()
+{
+ Formatter::ClearMinValue();
+ if (m_pSpinButton)
+ m_pSpinButton->sync_range_from_formatter();
+}
+
+void EntryFormatter::SetMinValue(double dMin)
+{
+ Formatter::SetMinValue(dMin);
+ if (m_pSpinButton)
+ m_pSpinButton->sync_range_from_formatter();
+}
+
+void EntryFormatter::ClearMaxValue()
+{
+ Formatter::ClearMaxValue();
+ if (m_pSpinButton)
+ m_pSpinButton->sync_range_from_formatter();
+}
+
+void EntryFormatter::SetMaxValue(double dMin)
+{
+ Formatter::SetMaxValue(dMin);
+ if (m_pSpinButton)
+ m_pSpinButton->sync_range_from_formatter();
+}
+
+void EntryFormatter::SetSpinSize(double dStep)
+{
+ Formatter::SetSpinSize(dStep);
+ if (m_pSpinButton)
+ m_pSpinButton->sync_increments_from_formatter();
+}
+
+SelectionOptions EntryFormatter::GetEntrySelectionOptions() const { return m_eOptions; }
+
+void EntryFormatter::FieldModified() { m_aModifyHdl.Call(m_rEntry); }
+
+IMPL_LINK_NOARG(EntryFormatter, ModifyHdl, weld::Entry&, void)
+{
+ // This leads to FieldModified getting called at the end of Modify() and
+ // FieldModified then calls any modification callback
+ Modify();
+}
+
+IMPL_LINK_NOARG(EntryFormatter, FocusOutHdl, weld::Widget&, void)
+{
+ EntryLostFocus();
+ if (m_pSpinButton)
+ m_pSpinButton->signal_value_changed();
+ m_aFocusOutHdl.Call(m_rEntry);
+}
+
+DoubleNumericFormatter::DoubleNumericFormatter(weld::Entry& rEntry)
+ : EntryFormatter(rEntry)
+{
+ ResetConformanceTester();
+}
+
+DoubleNumericFormatter::DoubleNumericFormatter(weld::FormattedSpinButton& rSpinButton)
+ : EntryFormatter(rSpinButton)
+{
+ ResetConformanceTester();
+}
+
+DoubleNumericFormatter::~DoubleNumericFormatter() = default;
+
+void DoubleNumericFormatter::FormatChanged(FORMAT_CHANGE_TYPE nWhat)
+{
+ ResetConformanceTester();
+ EntryFormatter::FormatChanged(nWhat);
+}
+
+bool DoubleNumericFormatter::CheckText(const OUString& sText) const
+{
+ // We'd like to implement this using the NumberFormatter::IsNumberFormat, but unfortunately, this doesn't
+ // recognize fragments of numbers (like, for instance "1e", which happens during entering e.g. "1e10")
+ // Thus, the roundabout way via a regular expression
+ return m_pNumberValidator->isValidNumericFragment(sText);
+}
+
+void DoubleNumericFormatter::ResetConformanceTester()
+{
+ // the thousands and the decimal separator are language dependent
+ const SvNumberformat* pFormatEntry = GetOrCreateFormatter()->GetEntry(m_nFormatKey);
+
+ sal_Unicode cSeparatorThousand = ',';
+ sal_Unicode cSeparatorDecimal = '.';
+ if (pFormatEntry)
+ {
+ LocaleDataWrapper aLocaleInfo(LanguageTag(pFormatEntry->GetLanguage()));
+
+ OUString sSeparator = aLocaleInfo.getNumThousandSep();
+ if (!sSeparator.isEmpty())
+ cSeparatorThousand = sSeparator[0];
+
+ sSeparator = aLocaleInfo.getNumDecimalSep();
+ if (!sSeparator.isEmpty())
+ cSeparatorDecimal = sSeparator[0];
+ }
+
+ m_pNumberValidator.reset(
+ new validation::NumberValidator(cSeparatorThousand, cSeparatorDecimal));
+}
+
+LongCurrencyFormatter::LongCurrencyFormatter(weld::Entry& rEntry)
+ : EntryFormatter(rEntry)
+ , m_bThousandSep(true)
+{
+ Init();
+}
+
+LongCurrencyFormatter::LongCurrencyFormatter(weld::FormattedSpinButton& rSpinButton)
+ : EntryFormatter(rSpinButton)
+ , m_bThousandSep(true)
+{
+ Init();
+}
+
+void LongCurrencyFormatter::Init()
+{
+ SetOutputHdl(LINK(this, LongCurrencyFormatter, FormatOutputHdl));
+ SetInputHdl(LINK(this, LongCurrencyFormatter, ParseInputHdl));
+}
+
+void LongCurrencyFormatter::SetUseThousandSep(bool b)
+{
+ m_bThousandSep = b;
+ ReFormat();
+}
+
+void LongCurrencyFormatter::SetCurrencySymbol(const OUString& rStr)
+{
+ m_aCurrencySymbol = rStr;
+ ReFormat();
+}
+
+LongCurrencyFormatter::~LongCurrencyFormatter() = default;
+
+TimeFormatter::TimeFormatter(weld::Entry& rEntry)
+ : EntryFormatter(rEntry)
+ , m_eFormat(TimeFieldFormat::F_NONE)
+ , m_eTimeFormat(TimeFormat::Hour24)
+ , m_bDuration(false)
+{
+ Init();
+}
+
+TimeFormatter::TimeFormatter(weld::FormattedSpinButton& rSpinButton)
+ : EntryFormatter(rSpinButton)
+ , m_eFormat(TimeFieldFormat::F_NONE)
+ , m_eTimeFormat(TimeFormat::Hour24)
+ , m_bDuration(false)
+{
+ Init();
+}
+
+void TimeFormatter::Init()
+{
+ DisableRemainderFactor(); //so with hh::mm::ss, incrementing mm will not reset ss
+
+ SetOutputHdl(LINK(this, TimeFormatter, FormatOutputHdl));
+ SetInputHdl(LINK(this, TimeFormatter, ParseInputHdl));
+
+ SetMin(tools::Time(0, 0));
+ SetMax(tools::Time(23, 59, 59, 999999999));
+
+ // so the spin size can depend on which zone the cursor is in
+ get_widget().connect_cursor_position(LINK(this, TimeFormatter, CursorChangedHdl));
+ // and set the initial spin size
+ CursorChangedHdl(get_widget());
+}
+
+void TimeFormatter::SetExtFormat(ExtTimeFieldFormat eFormat)
+{
+ switch (eFormat)
+ {
+ case ExtTimeFieldFormat::Short24H:
+ {
+ m_eTimeFormat = TimeFormat::Hour24;
+ m_bDuration = false;
+ m_eFormat = TimeFieldFormat::F_NONE;
+ }
+ break;
+ case ExtTimeFieldFormat::Long24H:
+ {
+ m_eTimeFormat = TimeFormat::Hour24;
+ m_bDuration = false;
+ m_eFormat = TimeFieldFormat::F_SEC;
+ }
+ break;
+ case ExtTimeFieldFormat::Short12H:
+ {
+ m_eTimeFormat = TimeFormat::Hour12;
+ m_bDuration = false;
+ m_eFormat = TimeFieldFormat::F_NONE;
+ }
+ break;
+ case ExtTimeFieldFormat::Long12H:
+ {
+ m_eTimeFormat = TimeFormat::Hour12;
+ m_bDuration = false;
+ m_eFormat = TimeFieldFormat::F_SEC;
+ }
+ break;
+ case ExtTimeFieldFormat::ShortDuration:
+ {
+ m_bDuration = true;
+ m_eFormat = TimeFieldFormat::F_NONE;
+ }
+ break;
+ case ExtTimeFieldFormat::LongDuration:
+ {
+ m_bDuration = true;
+ m_eFormat = TimeFieldFormat::F_SEC;
+ }
+ break;
+ }
+
+ ReFormat();
+}
+
+void TimeFormatter::SetDuration(bool bDuration)
+{
+ m_bDuration = bDuration;
+ ReFormat();
+}
+
+void TimeFormatter::SetTimeFormat(TimeFieldFormat eTimeFormat)
+{
+ m_eFormat = eTimeFormat;
+ ReFormat();
+}
+
+TimeFormatter::~TimeFormatter() = default;
+
+DateFormatter::DateFormatter(weld::Entry& rEntry)
+ : EntryFormatter(rEntry)
+ , m_eFormat(ExtDateFieldFormat::SystemShort)
+{
+ Init();
+}
+
+void DateFormatter::Init()
+{
+ SetOutputHdl(LINK(this, DateFormatter, FormatOutputHdl));
+ SetInputHdl(LINK(this, DateFormatter, ParseInputHdl));
+
+ SetMin(Date(1, 1, 1900));
+ SetMax(Date(31, 12, 2200));
+}
+
+void DateFormatter::SetExtDateFormat(ExtDateFieldFormat eFormat)
+{
+ m_eFormat = eFormat;
+ ReFormat();
+}
+
+DateFormatter::~DateFormatter() = default;
+
+PatternFormatter::PatternFormatter(weld::Entry& rEntry)
+ : m_rEntry(rEntry)
+ , m_bStrictFormat(false)
+ , m_bSameMask(true)
+ , m_bReformat(false)
+ , m_bInPattKeyInput(false)
+{
+ m_rEntry.connect_changed(LINK(this, PatternFormatter, ModifyHdl));
+ m_rEntry.connect_focus_in(LINK(this, PatternFormatter, FocusInHdl));
+ m_rEntry.connect_focus_out(LINK(this, PatternFormatter, FocusOutHdl));
+ m_rEntry.connect_key_press(LINK(this, PatternFormatter, KeyInputHdl));
+}
+
+IMPL_LINK_NOARG(PatternFormatter, ModifyHdl, weld::Entry&, void) { Modify(); }
+
+IMPL_LINK_NOARG(PatternFormatter, FocusOutHdl, weld::Widget&, void)
+{
+ EntryLostFocus();
+ m_aFocusOutHdl.Call(m_rEntry);
+}
+
+IMPL_LINK_NOARG(PatternFormatter, FocusInHdl, weld::Widget&, void)
+{
+ EntryGainFocus();
+ m_aFocusInHdl.Call(m_rEntry);
+}
+
+PatternFormatter::~PatternFormatter()
+{
+ m_rEntry.connect_changed(Link<weld::Entry&, void>());
+ m_rEntry.connect_focus_out(Link<weld::Widget&, void>());
+}
+
+int GetMinimumEditHeight()
+{
+ // load this little .ui just to measure the height of an Entry
+ std::unique_ptr<weld::Builder> xBuilder(
+ Application::CreateBuilder(nullptr, "cui/ui/namedialog.ui"));
+ std::unique_ptr<weld::Entry> xEntry(xBuilder->weld_entry("name_entry"));
+ return xEntry->get_preferred_size().Height();
+}
+
+WidgetStatusListener::WidgetStatusListener(weld::Widget* widget, const OUString& aCommand)
+ : mWidget(widget)
+{
+ css::uno::Reference<css::uno::XComponentContext> xContext
+ = ::comphelper::getProcessComponentContext();
+ css::uno::Reference<css::frame::XDesktop2> xDesktop = css::frame::Desktop::create(xContext);
+
+ css::uno::Reference<css::frame::XFrame> xFrame(xDesktop->getActiveFrame());
+ if (!xFrame.is())
+ xFrame = xDesktop;
+
+ mxFrame = xFrame;
+
+ maCommandURL.Complete = aCommand;
+ css::uno::Reference<css::util::XURLTransformer> xParser
+ = css::util::URLTransformer::create(xContext);
+ xParser->parseStrict(maCommandURL);
+}
+
+void WidgetStatusListener::startListening()
+{
+ if (mxDispatch.is())
+ mxDispatch->removeStatusListener(this, maCommandURL);
+
+ css::uno::Reference<css::frame::XDispatchProvider> xDispatchProvider(mxFrame,
+ css::uno::UNO_QUERY);
+ if (!xDispatchProvider.is())
+ return;
+
+ mxDispatch = xDispatchProvider->queryDispatch(maCommandURL, "", 0);
+ if (mxDispatch.is())
+ mxDispatch->addStatusListener(this, maCommandURL);
+}
+
+void WidgetStatusListener::statusChanged(const css::frame::FeatureStateEvent& rEvent)
+{
+ mWidget->set_sensitive(rEvent.IsEnabled);
+}
+
+void WidgetStatusListener::disposing(const css::lang::EventObject& /*Source*/)
+{
+ mxDispatch.clear();
+}
+
+void WidgetStatusListener::dispose()
+{
+ if (mxDispatch.is())
+ {
+ mxDispatch->removeStatusListener(this, maCommandURL);
+ mxDispatch.clear();
+ }
+ mxFrame.clear();
+ mWidget = nullptr;
+}
+
+ButtonPressRepeater::ButtonPressRepeater(weld::Button& rButton, const Link<Button&, void>& rLink,
+ const Link<const CommandEvent&, void>& rContextLink)
+ : m_rButton(rButton)
+ , m_aRepeat("vcl ButtonPressRepeater m_aRepeat")
+ , m_aLink(rLink)
+ , m_aContextLink(rContextLink)
+ , m_bModKey(false)
+{
+ // instead of connect_clicked because we want a button held down to
+ // repeat the next/prev
+ m_rButton.connect_mouse_press(LINK(this, ButtonPressRepeater, MousePressHdl));
+ m_rButton.connect_mouse_release(LINK(this, ButtonPressRepeater, MouseReleaseHdl));
+
+ m_aRepeat.SetInvokeHandler(LINK(this, ButtonPressRepeater, RepeatTimerHdl));
+}
+
+IMPL_LINK(ButtonPressRepeater, MousePressHdl, const MouseEvent&, rMouseEvent, bool)
+{
+ if (rMouseEvent.IsRight())
+ {
+ m_aContextLink.Call(
+ CommandEvent(rMouseEvent.GetPosPixel(), CommandEventId::ContextMenu, true));
+ return false;
+ }
+ m_bModKey = rMouseEvent.IsMod1();
+ if (!m_rButton.get_sensitive())
+ return false;
+ auto self = weak_from_this();
+ RepeatTimerHdl(nullptr);
+ if (!self.lock())
+ return false;
+ if (!m_rButton.get_sensitive())
+ return false;
+ m_aRepeat.SetTimeout(MouseSettings::GetButtonStartRepeat());
+ m_aRepeat.Start();
+ return true;
+}
+
+IMPL_LINK_NOARG(ButtonPressRepeater, MouseReleaseHdl, const MouseEvent&, bool)
+{
+ m_bModKey = false;
+ m_aRepeat.Stop();
+ return true;
+}
+
+IMPL_LINK_NOARG(ButtonPressRepeater, RepeatTimerHdl, Timer*, void)
+{
+ m_aRepeat.SetTimeout(Application::GetSettings().GetMouseSettings().GetButtonRepeat());
+ m_aLink.Call(m_rButton);
+}
+
+weld::Window* GetPopupParent(vcl::Window& rOutWin, tools::Rectangle& rRect)
+{
+ rRect.SetPos(rOutWin.OutputToScreenPixel(rRect.TopLeft()));
+ rRect = FloatingWindow::ImplConvertToAbsPos(&rOutWin, rRect);
+
+ vcl::Window* pWin = rOutWin.GetFrameWindow();
+
+ rRect = FloatingWindow::ImplConvertToRelPos(pWin, rRect);
+ rRect.SetPos(pWin->ScreenToOutputPixel(rRect.TopLeft()));
+
+ return rOutWin.GetFrameWeld();
+}
+
+void SetPointFont(OutputDevice& rDevice, const vcl::Font& rFont)
+{
+ auto pDefaultDevice = Application::GetDefaultDevice();
+ if (pDefaultDevice)
+ if (vcl::Window* pDefaultWindow = pDefaultDevice->GetOwnerWindow())
+ pDefaultWindow->SetPointFont(rDevice, rFont);
+}
+
+ReorderingDropTarget::ReorderingDropTarget(weld::TreeView& rTreeView)
+ : DropTargetHelper(rTreeView.get_drop_target())
+ , m_rTreeView(rTreeView)
+{
+}
+
+sal_Int8 ReorderingDropTarget::AcceptDrop(const AcceptDropEvent& rEvt)
+{
+ // to enable the autoscroll when we're close to the edges
+ m_rTreeView.get_dest_row_at_pos(rEvt.maPosPixel, nullptr, true);
+ return DND_ACTION_MOVE;
+}
+
+sal_Int8 ReorderingDropTarget::ExecuteDrop(const ExecuteDropEvent& rEvt)
+{
+ weld::TreeView* pSource = m_rTreeView.get_drag_source();
+ // only dragging within the same widget allowed
+ if (!pSource || pSource != &m_rTreeView)
+ return DND_ACTION_NONE;
+
+ std::unique_ptr<weld::TreeIter> xSource(m_rTreeView.make_iterator());
+ if (!m_rTreeView.get_selected(xSource.get()))
+ return DND_ACTION_NONE;
+
+ std::unique_ptr<weld::TreeIter> xTarget(m_rTreeView.make_iterator());
+ int nTargetPos = -1;
+ if (m_rTreeView.get_dest_row_at_pos(rEvt.maPosPixel, xTarget.get(), true))
+ nTargetPos = m_rTreeView.get_iter_index_in_parent(*xTarget);
+ m_rTreeView.move_subtree(*xSource, nullptr, nTargetPos);
+
+ return DND_ACTION_NONE;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/app/winscheduler.cxx b/vcl/source/app/winscheduler.cxx
new file mode 100644
index 000000000..0398faaa4
--- /dev/null
+++ b/vcl/source/app/winscheduler.cxx
@@ -0,0 +1,46 @@
+/* -*- 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 .
+ */
+
+#ifdef _WIN32
+
+#include <sal/config.h>
+
+#include <sal/log.hxx>
+#include <vcl/winscheduler.hxx>
+
+#include <svsys.h>
+#include <win/saldata.hxx>
+#include <win/salinst.h>
+
+namespace
+{
+void PostMessageToComWnd(UINT nMsg)
+{
+ bool const ret = PostMessageW(GetSalData()->mpInstance->mhComWnd, nMsg, 0, 0);
+ SAL_WARN_IF(!ret, "vcl.schedule", "ERROR: PostMessage() failed!");
+}
+}
+
+void WinScheduler::SetForceRealTimer() { PostMessageToComWnd(SAL_MSG_FORCE_REAL_TIMER); }
+
+void WinScheduler::PostDummyMessage() { PostMessageToComWnd(SAL_MSG_DUMMY); }
+
+#endif // _WIN32
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapAlphaClampFilter.cxx b/vcl/source/bitmap/BitmapAlphaClampFilter.cxx
new file mode 100644
index 000000000..69691d51c
--- /dev/null
+++ b/vcl/source/bitmap/BitmapAlphaClampFilter.cxx
@@ -0,0 +1,45 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapAlphaClampFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapAlphaClampFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ if (!rBitmapEx.IsAlpha())
+ return rBitmapEx;
+
+ AlphaMask aBitmapAlpha(rBitmapEx.GetAlpha());
+ {
+ AlphaScopedWriteAccess pWriteAlpha(aBitmapAlpha);
+ const Size aSize(rBitmapEx.GetSizePixel());
+
+ for (sal_Int32 nY = 0; nY < sal_Int32(aSize.Height()); ++nY)
+ {
+ Scanline pScanAlpha = pWriteAlpha->GetScanline(nY);
+
+ for (sal_Int32 nX = 0; nX < sal_Int32(aSize.Width()); ++nX)
+ {
+ BitmapColor aBitmapAlphaValue(pWriteAlpha->GetPixelFromData(pScanAlpha, nX));
+ if (aBitmapAlphaValue.GetIndex() > mcThreshold)
+ {
+ aBitmapAlphaValue.SetIndex(255);
+ pWriteAlpha->SetPixelOnData(pScanAlpha, nX, aBitmapAlphaValue);
+ }
+ }
+ }
+ }
+
+ return BitmapEx(rBitmapEx.GetBitmap(), aBitmapAlpha);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx b/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx
new file mode 100644
index 000000000..8516f5861
--- /dev/null
+++ b/vcl/source/bitmap/BitmapBasicMorphologyFilter.cxx
@@ -0,0 +1,371 @@
+/* -*- 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/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <comphelper/threadpool.hxx>
+#include <sal/log.hxx>
+#include <vcl/BitmapBasicMorphologyFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+#include <algorithm>
+
+/* TODO: Use round kernel instead of square one.
+ This would make the result more natural, e.g. not making rounded square out of circle.
+ */
+
+namespace
+{
+struct FilterSharedData
+{
+ BitmapReadAccess* mpReadAccess;
+ BitmapWriteAccess* mpWriteAccess;
+ sal_Int32 mnRadius;
+ sal_uInt8 mnOutsideVal;
+ Color maOutsideColor;
+
+ FilterSharedData(Bitmap::ScopedReadAccess& rReadAccess, BitmapScopedWriteAccess& rWriteAccess,
+ sal_Int32 nRadius, sal_uInt8 nOutsideVal)
+ : mpReadAccess(rReadAccess.get())
+ , mpWriteAccess(rWriteAccess.get())
+ , mnRadius(nRadius)
+ , mnOutsideVal(nOutsideVal)
+ , maOutsideColor(ColorTransparency, nOutsideVal, nOutsideVal, nOutsideVal, nOutsideVal)
+ {
+ }
+};
+
+// Black is foreground, white is background
+
+struct ErodeOp
+{
+ static sal_uInt8 apply(sal_uInt8 v1, sal_uInt8 v2) { return std::max(v1, v2); }
+ static constexpr sal_uInt8 initVal = 0;
+};
+
+struct DilateOp
+{
+ static sal_uInt8 apply(sal_uInt8 v1, sal_uInt8 v2) { return std::min(v1, v2); }
+ static constexpr sal_uInt8 initVal = SAL_MAX_UINT8;
+};
+
+// 8 bit per channel case
+
+template <typename MorphologyOp, int nComponentWidth> struct Value
+{
+ static constexpr int nWidthBytes = nComponentWidth / 8;
+ static_assert(nWidthBytes * 8 == nComponentWidth);
+
+ sal_uInt8 aResult[nWidthBytes];
+
+ // If we are at the start or at the end of the line, consider outside value
+ Value(FilterSharedData const& rShared, bool bLookOutside)
+ {
+ std::fill_n(aResult, nWidthBytes,
+ bLookOutside ? rShared.mnOutsideVal : MorphologyOp::initVal);
+ }
+
+ void apply(const BitmapReadAccess* pReadAccess, sal_Int32 x, sal_Int32 y,
+ sal_uInt8* pHint = nullptr)
+ {
+ sal_uInt8* pSource = (pHint ? pHint : pReadAccess->GetScanline(y)) + nWidthBytes * x;
+ std::transform(pSource, pSource + nWidthBytes, aResult, aResult, MorphologyOp::apply);
+ }
+
+ void copy(const BitmapWriteAccess* pWriteAccess, sal_Int32 x, sal_Int32 y,
+ sal_uInt8* pHint = nullptr)
+ {
+ sal_uInt8* pDest = (pHint ? pHint : pWriteAccess->GetScanline(y)) + nWidthBytes * x;
+ std::copy_n(aResult, nWidthBytes, pDest);
+ }
+};
+
+// Partial specializations for nComponentWidth == 0, using access' GetColor/SetPixel
+
+template <typename MorphologyOp> struct Value<MorphologyOp, 0>
+{
+ static constexpr Color initColor{ ColorTransparency, MorphologyOp::initVal,
+ MorphologyOp::initVal, MorphologyOp::initVal,
+ MorphologyOp::initVal };
+
+ Color aResult;
+
+ // If we are at the start or at the end of the line, consider outside value
+ Value(FilterSharedData const& rShared, bool bLookOutside)
+ : aResult(bLookOutside ? rShared.maOutsideColor : initColor)
+ {
+ }
+
+ void apply(const BitmapReadAccess* pReadAccess, sal_Int32 x, sal_Int32 y,
+ sal_uInt8* /*pHint*/ = nullptr)
+ {
+ const auto& rSource = pReadAccess->GetColor(y, x);
+ aResult = Color(ColorAlpha, MorphologyOp::apply(rSource.GetAlpha(), aResult.GetAlpha()),
+ MorphologyOp::apply(rSource.GetRed(), aResult.GetRed()),
+ MorphologyOp::apply(rSource.GetGreen(), aResult.GetGreen()),
+ MorphologyOp::apply(rSource.GetBlue(), aResult.GetBlue()));
+ }
+
+ void copy(BitmapWriteAccess* pWriteAccess, sal_Int32 x, sal_Int32 y,
+ sal_uInt8* /*pHint*/ = nullptr)
+ {
+ pWriteAccess->SetPixel(y, x, aResult);
+ }
+};
+
+bool GetMinMax(sal_Int32 nCenter, sal_Int32 nRadius, sal_Int32 nMaxLimit, sal_Int32& nMin,
+ sal_Int32& nMax)
+{
+ nMin = nCenter - nRadius;
+ nMax = nCenter + nRadius;
+ bool bLookOutside = false;
+ if (nMin < 0)
+ {
+ bLookOutside = true;
+ nMin = 0;
+ }
+ if (nMax > nMaxLimit)
+ {
+ bLookOutside = true;
+ nMax = nMaxLimit;
+ }
+ return bLookOutside;
+}
+
+template <typename MorphologyOp, int nComponentWidth> struct pass
+{
+ static void Horizontal(FilterSharedData const& rShared, const sal_Int32 nStart,
+ const sal_Int32 nEnd)
+ {
+ BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
+ BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
+
+ const sal_Int32 nLastIndex = pReadAccess->Width() - 1;
+
+ for (sal_Int32 y = nStart; y <= nEnd; y++)
+ {
+ // Optimization
+ sal_uInt8* const pSourceHint = pReadAccess->GetScanline(y);
+ sal_uInt8* const pDestHint = pWriteAccess->GetScanline(y);
+ for (sal_Int32 x = 0; x <= nLastIndex; x++)
+ {
+ // This processes [nRadius * 2 + 1] pixels of source per resulting pixel
+ // TODO: try to optimize this to not process same pixels repeatedly
+ sal_Int32 iMin, iMax;
+ const bool bLookOutside = GetMinMax(x, rShared.mnRadius, nLastIndex, iMin, iMax);
+ Value<MorphologyOp, nComponentWidth> aResult(rShared, bLookOutside);
+ for (sal_Int32 i = iMin; i <= iMax; ++i)
+ aResult.apply(pReadAccess, i, y, pSourceHint);
+
+ aResult.copy(pWriteAccess, x, y, pDestHint);
+ }
+ }
+ }
+
+ static void Vertical(FilterSharedData const& rShared, const sal_Int32 nStart,
+ const sal_Int32 nEnd)
+ {
+ BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
+ BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
+
+ const sal_Int32 nLastIndex = pReadAccess->Height() - 1;
+
+ for (sal_Int32 x = nStart; x <= nEnd; x++)
+ {
+ for (sal_Int32 y = 0; y <= nLastIndex; y++)
+ {
+ // This processes [nRadius * 2 + 1] pixels of source per resulting pixel
+ // TODO: try to optimize this to not process same pixels repeatedly
+ sal_Int32 iMin, iMax;
+ const bool bLookOutside = GetMinMax(y, rShared.mnRadius, nLastIndex, iMin, iMax);
+ Value<MorphologyOp, nComponentWidth> aResult(rShared, bLookOutside);
+ for (sal_Int32 i = iMin; i <= iMax; ++i)
+ aResult.apply(pReadAccess, x, i);
+
+ aResult.copy(pWriteAccess, x, y);
+ }
+ }
+ }
+};
+
+typedef void (*passFn)(FilterSharedData const& rShared, sal_Int32 nStart, sal_Int32 nEnd);
+
+class FilterTask : public comphelper::ThreadTask
+{
+ passFn mpFunction;
+ FilterSharedData& mrShared;
+ sal_Int32 mnStart;
+ sal_Int32 mnEnd;
+
+public:
+ explicit FilterTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, passFn pFunction,
+ FilterSharedData& rShared, sal_Int32 nStart, sal_Int32 nEnd)
+ : comphelper::ThreadTask(pTag)
+ , mpFunction(pFunction)
+ , mrShared(rShared)
+ , mnStart(nStart)
+ , mnEnd(nEnd)
+ {
+ }
+
+ virtual void doWork() override { mpFunction(mrShared, mnStart, mnEnd); }
+};
+
+constexpr sal_Int32 nThreadStrip = 16;
+
+template <typename MorphologyOp, int nComponentWidth>
+void runFilter(Bitmap& rBitmap, const sal_Int32 nRadius, const bool bParallel,
+ bool bUseValueOutside, sal_uInt8 nValueOutside)
+{
+ using myPass = pass<MorphologyOp, nComponentWidth>;
+ const sal_uInt8 nOutsideVal = bUseValueOutside ? nValueOutside : MorphologyOp::initVal;
+ if (bParallel)
+ {
+ try
+ {
+ comphelper::ThreadPool& rShared = comphelper::ThreadPool::getSharedOptimalPool();
+ auto pTag = comphelper::ThreadPool::createThreadTaskTag();
+
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
+
+ const sal_Int32 nLastIndex = pReadAccess->Height() - 1;
+ sal_Int32 nStripStart = 0;
+ for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
+ {
+ sal_Int32 nStripEnd = nStripStart + nThreadStrip - 1;
+ auto pTask(std::make_unique<FilterTask>(pTag, myPass::Horizontal, aSharedData,
+ nStripStart, nStripEnd));
+ rShared.pushTask(std::move(pTask));
+ }
+ // Do the last (or the only) strip in main thread without threading overhead
+ myPass::Horizontal(aSharedData, nStripStart, nLastIndex);
+ rShared.waitUntilDone(pTag);
+ }
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
+
+ const sal_Int32 nLastIndex = pReadAccess->Width() - 1;
+ sal_Int32 nStripStart = 0;
+ for (; nStripStart < nLastIndex - nThreadStrip; nStripStart += nThreadStrip)
+ {
+ sal_Int32 nStripEnd = nStripStart + nThreadStrip - 1;
+ auto pTask(std::make_unique<FilterTask>(pTag, myPass::Vertical, aSharedData,
+ nStripStart, nStripEnd));
+ rShared.pushTask(std::move(pTask));
+ }
+ // Do the last (or the only) strip in main thread without threading overhead
+ myPass::Vertical(aSharedData, nStripStart, nLastIndex);
+ rShared.waitUntilDone(pTag);
+ }
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl.gdi", "threaded bitmap blurring failed");
+ }
+ }
+ else
+ {
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
+ sal_Int32 nFirstIndex = 0;
+ sal_Int32 nLastIndex = pReadAccess->Height() - 1;
+ myPass::Horizontal(aSharedData, nFirstIndex, nLastIndex);
+ }
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ FilterSharedData aSharedData(pReadAccess, pWriteAccess, nRadius, nOutsideVal);
+ sal_Int32 nFirstIndex = 0;
+ sal_Int32 nLastIndex = pReadAccess->Width() - 1;
+ myPass::Vertical(aSharedData, nFirstIndex, nLastIndex);
+ }
+ }
+}
+
+template <int nComponentWidth>
+void runFilter(Bitmap& rBitmap, BasicMorphologyOp op, sal_Int32 nRadius, bool bUseValueOutside,
+ sal_uInt8 nValueOutside)
+{
+ const bool bParallel = true;
+
+ if (op == BasicMorphologyOp::erode)
+ runFilter<ErodeOp, nComponentWidth>(rBitmap, nRadius, bParallel, bUseValueOutside,
+ nValueOutside);
+ else if (op == BasicMorphologyOp::dilate)
+ runFilter<DilateOp, nComponentWidth>(rBitmap, nRadius, bParallel, bUseValueOutside,
+ nValueOutside);
+}
+
+} // end anonymous namespace
+
+BitmapBasicMorphologyFilter::BitmapBasicMorphologyFilter(BasicMorphologyOp op, sal_Int32 nRadius)
+ : m_eOp(op)
+ , m_nRadius(nRadius)
+{
+}
+
+BitmapBasicMorphologyFilter::BitmapBasicMorphologyFilter(BasicMorphologyOp op, sal_Int32 nRadius,
+ sal_uInt8 nValueOutside)
+ : m_eOp(op)
+ , m_nRadius(nRadius)
+ , m_nValueOutside(nValueOutside)
+ , m_bUseValueOutside(true)
+{
+}
+
+BitmapBasicMorphologyFilter::~BitmapBasicMorphologyFilter() = default;
+
+BitmapEx BitmapBasicMorphologyFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap result = filter(rBitmapEx.GetBitmap());
+ return BitmapEx(result, rBitmapEx.GetAlpha());
+}
+
+Bitmap BitmapBasicMorphologyFilter::filter(Bitmap const& rBitmap) const
+{
+ Bitmap bitmapCopy(rBitmap);
+ ScanlineFormat nScanlineFormat;
+ {
+ Bitmap::ScopedReadAccess pReadAccess(bitmapCopy);
+ nScanlineFormat = pReadAccess ? pReadAccess->GetScanlineFormat() : ScanlineFormat::NONE;
+ }
+
+ switch (nScanlineFormat)
+ {
+ case ScanlineFormat::N24BitTcRgb:
+ case ScanlineFormat::N24BitTcBgr:
+ runFilter<24>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
+ break;
+ case ScanlineFormat::N32BitTcMask:
+ case ScanlineFormat::N32BitTcBgra:
+ runFilter<32>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
+ break;
+ case ScanlineFormat::N8BitPal:
+ runFilter<8>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
+ break;
+ // TODO: handle 1-bit images
+ default:
+ // Use access' GetColor/SetPixel fallback
+ runFilter<0>(bitmapCopy, m_eOp, m_nRadius, m_bUseValueOutside, m_nValueOutside);
+ break;
+ }
+
+ return bitmapCopy;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapColorQuantizationFilter.cxx b/vcl/source/bitmap/BitmapColorQuantizationFilter.cxx
new file mode 100644
index 000000000..45d39add3
--- /dev/null
+++ b/vcl/source/bitmap/BitmapColorQuantizationFilter.cxx
@@ -0,0 +1,224 @@
+/* -*- 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/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapColorQuantizationFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+#include <algorithm>
+#include <cstdlib>
+
+BitmapEx BitmapColorQuantizationFilter::execute(BitmapEx const& aBitmapEx) const
+{
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+
+ bool bRet = false;
+
+ if (vcl::numberOfColors(aBitmap.getPixelFormat()) <= sal_Int64(mnNewColorCount))
+ {
+ bRet = true;
+ }
+ else
+ {
+ Bitmap::ScopedReadAccess pRAcc(aBitmap);
+
+ auto const cappedNewColorCount = std::min(mnNewColorCount, sal_uInt16(256));
+
+ if (pRAcc)
+ {
+ const sal_uInt32 nValidBits = 4;
+ const sal_uInt32 nRightShiftBits = 8 - nValidBits;
+ const sal_uInt32 nLeftShiftBits1 = nValidBits;
+ const sal_uInt32 nLeftShiftBits2 = nValidBits << 1;
+ const sal_uInt32 nColorsPerComponent = 1 << nValidBits;
+ const sal_uInt32 nColorOffset = 256 / nColorsPerComponent;
+ const sal_uInt32 nTotalColors
+ = nColorsPerComponent * nColorsPerComponent * nColorsPerComponent;
+ const sal_Int32 nWidth = pRAcc->Width();
+ const sal_Int32 nHeight = pRAcc->Height();
+ std::unique_ptr<PopularColorCount[]> pCountTable(new PopularColorCount[nTotalColors]);
+
+ memset(pCountTable.get(), 0, nTotalColors * sizeof(PopularColorCount));
+
+ for (sal_Int32 nR = 0, nIndex = 0; nR < 256; nR += nColorOffset)
+ {
+ for (sal_Int32 nG = 0; nG < 256; nG += nColorOffset)
+ {
+ for (sal_Int32 nB = 0; nB < 256; nB += nColorOffset)
+ {
+ pCountTable[nIndex].mnIndex = nIndex;
+ nIndex++;
+ }
+ }
+ }
+
+ if (pRAcc->HasPalette())
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ const BitmapColor& rCol
+ = pRAcc->GetPaletteColor(pRAcc->GetIndexFromData(pScanlineRead, nX));
+ pCountTable[((static_cast<sal_uInt32>(rCol.GetRed()) >> nRightShiftBits)
+ << nLeftShiftBits2)
+ | ((static_cast<sal_uInt32>(rCol.GetGreen()) >> nRightShiftBits)
+ << nLeftShiftBits1)
+ | (static_cast<sal_uInt32>(rCol.GetBlue()) >> nRightShiftBits)]
+ .mnCount++;
+ }
+ }
+ }
+ else
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ const BitmapColor aCol(pRAcc->GetPixelFromData(pScanlineRead, nX));
+ pCountTable[((static_cast<sal_uInt32>(aCol.GetRed()) >> nRightShiftBits)
+ << nLeftShiftBits2)
+ | ((static_cast<sal_uInt32>(aCol.GetGreen()) >> nRightShiftBits)
+ << nLeftShiftBits1)
+ | (static_cast<sal_uInt32>(aCol.GetBlue()) >> nRightShiftBits)]
+ .mnCount++;
+ }
+ }
+ }
+
+ BitmapPalette aNewPal(cappedNewColorCount);
+
+ std::qsort(pCountTable.get(), nTotalColors, sizeof(PopularColorCount),
+ [](const void* p1, const void* p2) {
+ int nRet;
+
+ if (static_cast<PopularColorCount const*>(p1)->mnCount
+ < static_cast<PopularColorCount const*>(p2)->mnCount)
+ nRet = 1;
+ else if (static_cast<PopularColorCount const*>(p1)->mnCount
+ == static_cast<PopularColorCount const*>(p2)->mnCount)
+ nRet = 0;
+ else
+ nRet = -1;
+
+ return nRet;
+ });
+
+ for (sal_uInt16 n = 0; n < cappedNewColorCount; n++)
+ {
+ const PopularColorCount& rPop = pCountTable[n];
+ aNewPal[n] = BitmapColor(
+ static_cast<sal_uInt8>((rPop.mnIndex >> nLeftShiftBits2) << nRightShiftBits),
+ static_cast<sal_uInt8>(
+ ((rPop.mnIndex >> nLeftShiftBits1) & (nColorsPerComponent - 1))
+ << nRightShiftBits),
+ static_cast<sal_uInt8>((rPop.mnIndex & (nColorsPerComponent - 1))
+ << nRightShiftBits));
+ }
+
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N8_BPP, &aNewPal);
+ BitmapScopedWriteAccess pWAcc(aNewBmp);
+
+ if (pWAcc)
+ {
+ BitmapColor aDstCol(sal_uInt8(0));
+ std::unique_ptr<sal_uInt8[]> pIndexMap(new sal_uInt8[nTotalColors]);
+
+ for (sal_Int32 nR = 0, nIndex = 0; nR < 256; nR += nColorOffset)
+ {
+ for (sal_Int32 nG = 0; nG < 256; nG += nColorOffset)
+ {
+ for (sal_Int32 nB = 0; nB < 256; nB += nColorOffset)
+ {
+ pIndexMap[nIndex++] = static_cast<sal_uInt8>(aNewPal.GetBestIndex(
+ BitmapColor(static_cast<sal_uInt8>(nR), static_cast<sal_uInt8>(nG),
+ static_cast<sal_uInt8>(nB))));
+ }
+ }
+ }
+
+ if (pRAcc->HasPalette())
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWAcc->GetScanline(nY);
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ const BitmapColor& rCol = pRAcc->GetPaletteColor(
+ pRAcc->GetIndexFromData(pScanlineRead, nX));
+ aDstCol.SetIndex(pIndexMap[((static_cast<sal_uInt32>(rCol.GetRed())
+ >> nRightShiftBits)
+ << nLeftShiftBits2)
+ | ((static_cast<sal_uInt32>(rCol.GetGreen())
+ >> nRightShiftBits)
+ << nLeftShiftBits1)
+ | (static_cast<sal_uInt32>(rCol.GetBlue())
+ >> nRightShiftBits)]);
+ pWAcc->SetPixelOnData(pScanline, nX, aDstCol);
+ }
+ }
+ }
+ else
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWAcc->GetScanline(nY);
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ const BitmapColor aCol(pRAcc->GetPixelFromData(pScanlineRead, nX));
+ aDstCol.SetIndex(pIndexMap[((static_cast<sal_uInt32>(aCol.GetRed())
+ >> nRightShiftBits)
+ << nLeftShiftBits2)
+ | ((static_cast<sal_uInt32>(aCol.GetGreen())
+ >> nRightShiftBits)
+ << nLeftShiftBits1)
+ | (static_cast<sal_uInt32>(aCol.GetBlue())
+ >> nRightShiftBits)]);
+ pWAcc->SetPixelOnData(pScanline, nX, aDstCol);
+ }
+ }
+ }
+
+ pWAcc.reset();
+ bRet = true;
+ }
+
+ pCountTable.reset();
+ pRAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aSize);
+ }
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapColorizeFilter.cxx b/vcl/source/bitmap/BitmapColorizeFilter.cxx
new file mode 100644
index 000000000..5f58f6147
--- /dev/null
+++ b/vcl/source/bitmap/BitmapColorizeFilter.cxx
@@ -0,0 +1,92 @@
+/* -*- 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/.
+ *
+ */
+
+#include <tools/color.hxx>
+#include <tools/helpers.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapColorizeFilter.hxx>
+
+BitmapEx BitmapColorizeFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap = rBitmapEx.GetBitmap();
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+
+ if (!pWriteAccess)
+ return rBitmapEx;
+
+ BitmapColor aBitmapColor;
+ const sal_Int32 nW = pWriteAccess->Width();
+ const sal_Int32 nH = pWriteAccess->Height();
+ std::vector<sal_uInt8> aMapR(256);
+ std::vector<sal_uInt8> aMapG(256);
+ std::vector<sal_uInt8> aMapB(256);
+ sal_Int32 nX;
+ sal_Int32 nY;
+
+ const sal_uInt8 cR = maColor.GetRed();
+ const sal_uInt8 cG = maColor.GetGreen();
+ const sal_uInt8 cB = maColor.GetBlue();
+
+ for (nX = 0; nX < 256; ++nX)
+ {
+ aMapR[nX] = MinMax((nX + cR) / 2, 0, 255);
+ aMapG[nX] = MinMax((nX + cG) / 2, 0, 255);
+ aMapB[nX] = MinMax((nX + cB) / 2, 0, 255);
+ }
+
+ if (pWriteAccess->HasPalette())
+ {
+ for (sal_uInt16 i = 0, nCount = pWriteAccess->GetPaletteEntryCount(); i < nCount; i++)
+ {
+ const BitmapColor& rCol = pWriteAccess->GetPaletteColor(i);
+ aBitmapColor.SetRed(aMapR[rCol.GetRed()]);
+ aBitmapColor.SetGreen(aMapG[rCol.GetGreen()]);
+ aBitmapColor.SetBlue(aMapB[rCol.GetBlue()]);
+ pWriteAccess->SetPaletteColor(i, aBitmapColor);
+ }
+ }
+ else if (pWriteAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr)
+ {
+ for (nY = 0; nY < nH; ++nY)
+ {
+ Scanline pScan = pWriteAccess->GetScanline(nY);
+
+ for (nX = 0; nX < nW; ++nX)
+ {
+ *pScan = aMapB[*pScan];
+ pScan++;
+ *pScan = aMapG[*pScan];
+ pScan++;
+ *pScan = aMapR[*pScan];
+ pScan++;
+ }
+ }
+ }
+ else
+ {
+ for (nY = 0; nY < nH; ++nY)
+ {
+ Scanline pScanline = pWriteAccess->GetScanline(nY);
+ for (nX = 0; nX < nW; ++nX)
+ {
+ aBitmapColor = pWriteAccess->GetPixelFromData(pScanline, nX);
+ aBitmapColor.SetRed(aMapR[aBitmapColor.GetRed()]);
+ aBitmapColor.SetGreen(aMapG[aBitmapColor.GetGreen()]);
+ aBitmapColor.SetBlue(aMapB[aBitmapColor.GetBlue()]);
+ pWriteAccess->SetPixelOnData(pScanline, nX, aBitmapColor);
+ }
+ }
+ }
+
+ return rBitmapEx;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapConvolutionMatrixFilter.cxx b/vcl/source/bitmap/BitmapConvolutionMatrixFilter.cxx
new file mode 100644
index 000000000..db68bff1d
--- /dev/null
+++ b/vcl/source/bitmap/BitmapConvolutionMatrixFilter.cxx
@@ -0,0 +1,209 @@
+/* -*- 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/.
+ *
+ */
+
+#include <tools/helpers.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapConvolutionMatrixFilter.hxx>
+#include <vcl/BitmapSharpenFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+#include <array>
+
+BitmapEx BitmapConvolutionMatrixFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const sal_Int32 nDivisor = 8;
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+ bool bRet = false;
+
+ if (pReadAcc)
+ {
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const sal_Int32 nWidth = pWriteAcc->Width(), nWidth2 = nWidth + 2;
+ const sal_Int32 nHeight = pWriteAcc->Height(), nHeight2 = nHeight + 2;
+ std::unique_ptr<sal_Int32[]> pColm(new sal_Int32[nWidth2]);
+ std::unique_ptr<sal_Int32[]> pRows(new sal_Int32[nHeight2]);
+ std::unique_ptr<BitmapColor[]> pColRow1(new BitmapColor[nWidth2]);
+ std::unique_ptr<BitmapColor[]> pColRow2(new BitmapColor[nWidth2]);
+ std::unique_ptr<BitmapColor[]> pColRow3(new BitmapColor[nWidth2]);
+ BitmapColor* pRowTmp1 = pColRow1.get();
+ BitmapColor* pRowTmp2 = pColRow2.get();
+ BitmapColor* pRowTmp3 = pColRow3.get();
+ BitmapColor* pColor;
+ sal_Int32 nY, nX, i, nSumR, nSumG, nSumB, nMatrixVal, nTmp;
+ std::array<std::array<sal_Int32, 256>, 9> aKoeff;
+ sal_Int32* pTmp;
+
+ // create LUT of products of matrix value and possible color component values
+ for (nY = 0; nY < 9; nY++)
+ {
+ for (nX = nTmp = 0, nMatrixVal = mrMatrix[nY]; nX < 256; nX++, nTmp += nMatrixVal)
+ {
+ aKoeff[nY][nX] = nTmp;
+ }
+ }
+
+ // create column LUT
+ for (i = 0; i < nWidth2; i++)
+ {
+ pColm[i] = (i > 0) ? (i - 1) : 0;
+ }
+
+ pColm[nWidth + 1] = pColm[nWidth];
+
+ // create row LUT
+ for (i = 0; i < nHeight2; i++)
+ {
+ pRows[i] = (i > 0) ? (i - 1) : 0;
+ }
+
+ pRows[nHeight + 1] = pRows[nHeight];
+
+ // read first three rows of bitmap color
+ for (i = 0; i < nWidth2; i++)
+ {
+ pColRow1[i] = pReadAcc->GetColor(pRows[0], pColm[i]);
+ pColRow2[i] = pReadAcc->GetColor(pRows[1], pColm[i]);
+ pColRow3[i] = pReadAcc->GetColor(pRows[2], pColm[i]);
+ }
+
+ // do convolution
+ for (nY = 0; nY < nHeight;)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (nX = 0; nX < nWidth; nX++)
+ {
+ // first row
+ pTmp = aKoeff[0].data();
+ pColor = pRowTmp1 + nX;
+ nSumR = pTmp[pColor->GetRed()];
+ nSumG = pTmp[pColor->GetGreen()];
+ nSumB = pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[1].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[2].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ // second row
+ pTmp = aKoeff[3].data();
+ pColor = pRowTmp2 + nX;
+ nSumR += pTmp[pColor->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[4].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[5].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ // third row
+ pTmp = aKoeff[6].data();
+ pColor = pRowTmp3 + nX;
+ nSumR += pTmp[pColor->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[7].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ pTmp = aKoeff[8].data();
+ nSumR += pTmp[(++pColor)->GetRed()];
+ nSumG += pTmp[pColor->GetGreen()];
+ nSumB += pTmp[pColor->GetBlue()];
+
+ // calculate destination color
+ pWriteAcc->SetPixelOnData(
+ pScanline, nX,
+ BitmapColor(static_cast<sal_uInt8>(MinMax(nSumR / nDivisor, 0, 255)),
+ static_cast<sal_uInt8>(MinMax(nSumG / nDivisor, 0, 255)),
+ static_cast<sal_uInt8>(MinMax(nSumB / nDivisor, 0, 255))));
+ }
+
+ if (++nY < nHeight)
+ {
+ if (pRowTmp1 == pColRow1.get())
+ {
+ pRowTmp1 = pColRow2.get();
+ pRowTmp2 = pColRow3.get();
+ pRowTmp3 = pColRow1.get();
+ }
+ else if (pRowTmp1 == pColRow2.get())
+ {
+ pRowTmp1 = pColRow3.get();
+ pRowTmp2 = pColRow1.get();
+ pRowTmp3 = pColRow2.get();
+ }
+ else
+ {
+ pRowTmp1 = pColRow1.get();
+ pRowTmp2 = pColRow2.get();
+ pRowTmp3 = pColRow3.get();
+ }
+
+ for (i = 0; i < nWidth2; i++)
+ {
+ pRowTmp3[i] = pReadAcc->GetColor(pRows[nY + 2], pColm[i]);
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+const sal_Int32 g_SharpenMatrix[] = { -1, -1, -1, -1, 16, -1, -1, -1, -1 };
+
+BitmapSharpenFilter::BitmapSharpenFilter()
+ : BitmapConvolutionMatrixFilter(g_SharpenMatrix)
+{
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapDisabledImageFilter.cxx b/vcl/source/bitmap/BitmapDisabledImageFilter.cxx
new file mode 100644
index 000000000..3bcf4cf9d
--- /dev/null
+++ b/vcl/source/bitmap/BitmapDisabledImageFilter.cxx
@@ -0,0 +1,60 @@
+/* -*- 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/.
+ *
+ */
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapDisabledImageFilter.hxx>
+
+BitmapEx BitmapDisabledImageFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ const Size aSize(rBitmapEx.GetSizePixel());
+
+ // keep disable image at same depth as original where possible, otherwise
+ // use 8 bit
+ auto ePixelFormat = rBitmapEx.getPixelFormat();
+ if (sal_uInt16(ePixelFormat) < 8)
+ ePixelFormat = vcl::PixelFormat::N8_BPP;
+
+ const BitmapPalette* pPal
+ = vcl::isPalettePixelFormat(ePixelFormat) ? &Bitmap::GetGreyPalette(256) : nullptr;
+ Bitmap aGrey(aSize, ePixelFormat, pPal);
+ BitmapScopedWriteAccess pGrey(aGrey);
+
+ BitmapEx aReturnBitmap;
+ Bitmap aReadBitmap(rBitmapEx.GetBitmap());
+ Bitmap::ScopedReadAccess pRead(aReadBitmap);
+ if (pRead && pGrey)
+ {
+ for (sal_Int32 nY = 0; nY < sal_Int32(aSize.Height()); ++nY)
+ {
+ Scanline pGreyScan = pGrey->GetScanline(nY);
+ Scanline pReadScan = pRead->GetScanline(nY);
+
+ for (sal_Int32 nX = 0; nX < sal_Int32(aSize.Width()); ++nX)
+ {
+ // Get the luminance from RGB color and remap the value from 0-255 to 160-224
+ const BitmapColor aColor = pRead->GetPixelFromData(pReadScan, nX);
+ sal_uInt8 nLum(aColor.GetLuminance() / 4 + 160);
+ BitmapColor aGreyValue(ColorAlpha, nLum, nLum, nLum, aColor.GetAlpha());
+ pGrey->SetPixelOnData(pGreyScan, nX, aGreyValue);
+ }
+ }
+ }
+
+ if (rBitmapEx.IsAlpha())
+ {
+ aReturnBitmap = BitmapEx(aGrey, rBitmapEx.GetAlpha());
+ }
+ else
+ aReturnBitmap = BitmapEx(aGrey);
+
+ return aReturnBitmap;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapDuoToneFilter.cxx b/vcl/source/bitmap/BitmapDuoToneFilter.cxx
new file mode 100644
index 000000000..41c3f60fc
--- /dev/null
+++ b/vcl/source/bitmap/BitmapDuoToneFilter.cxx
@@ -0,0 +1,60 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapDuoToneFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+static sal_uInt8 lcl_getDuotoneColorComponent(sal_uInt8 base, sal_uInt16 color1, sal_uInt16 color2)
+{
+ color2 = color2 * base / 0xFF;
+ color1 = color1 * (0xFF - base) / 0xFF;
+
+ return static_cast<sal_uInt8>(color1 + color2);
+}
+
+BitmapEx BitmapDuoToneFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const sal_Int32 nWidth = aBitmap.GetSizePixel().Width();
+ const sal_Int32 nHeight = aBitmap.GetSizePixel().Height();
+
+ Bitmap aResultBitmap(aBitmap.GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+ BitmapScopedWriteAccess pWriteAcc(aResultBitmap);
+ const BitmapColor aColorOne(mnColorOne);
+ const BitmapColor aColorTwo(mnColorTwo);
+
+ for (sal_Int32 x = 0; x < nWidth; x++)
+ {
+ for (sal_Int32 y = 0; y < nHeight; y++)
+ {
+ BitmapColor aColor = pReadAcc->GetColor(y, x);
+ sal_uInt8 nLuminance = aColor.GetLuminance();
+ BitmapColor aResultColor(
+ lcl_getDuotoneColorComponent(nLuminance, aColorOne.GetRed(), aColorTwo.GetRed()),
+ lcl_getDuotoneColorComponent(nLuminance, aColorOne.GetGreen(),
+ aColorTwo.GetGreen()),
+ lcl_getDuotoneColorComponent(nLuminance, aColorOne.GetBlue(), aColorTwo.GetBlue()));
+ pWriteAcc->SetPixel(y, x, aResultColor);
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+ aBitmap.ReassignWithSize(aResultBitmap);
+
+ return BitmapEx(aBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapEmbossGreyFilter.cxx b/vcl/source/bitmap/BitmapEmbossGreyFilter.cxx
new file mode 100644
index 000000000..a8705176f
--- /dev/null
+++ b/vcl/source/bitmap/BitmapEmbossGreyFilter.cxx
@@ -0,0 +1,160 @@
+/* -*- 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/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <tools/helpers.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapEmbossGreyFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+#include <algorithm>
+
+BitmapEx BitmapEmbossGreyFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ bool bRet = aBitmap.ImplMakeGreyscales();
+
+ if (bRet)
+ {
+ bRet = false;
+
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+
+ if (pReadAcc)
+ {
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N8_BPP,
+ &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ BitmapColor aGrey(sal_uInt8(0));
+ const sal_Int32 nWidth = pWriteAcc->Width();
+ const sal_Int32 nHeight = pWriteAcc->Height();
+ sal_Int32 nGrey11, nGrey12, nGrey13;
+ sal_Int32 nGrey21, nGrey22, nGrey23;
+ sal_Int32 nGrey31, nGrey32, nGrey33;
+ double fAzim = basegfx::deg2rad<100>(mnAzimuthAngle100);
+ double fElev = basegfx::deg2rad<100>(mnElevationAngle100);
+ std::unique_ptr<sal_Int32[]> pHMap(new sal_Int32[nWidth + 2]);
+ std::unique_ptr<sal_Int32[]> pVMap(new sal_Int32[nHeight + 2]);
+ sal_Int32 nX, nY, nNx, nNy, nDotL;
+ const sal_Int32 nLx = FRound(cos(fAzim) * cos(fElev) * 255.0);
+ const sal_Int32 nLy = FRound(sin(fAzim) * cos(fElev) * 255.0);
+ const sal_Int32 nLz = FRound(sin(fElev) * 255.0);
+ const auto nZ2 = ((6 * 255) / 4) * ((6 * 255) / 4);
+ const sal_Int32 nNzLz = ((6 * 255) / 4) * nLz;
+ const sal_uInt8 cLz
+ = static_cast<sal_uInt8>(std::clamp(nLz, sal_Int32(0), sal_Int32(255)));
+
+ // fill mapping tables
+ pHMap[0] = 0;
+
+ for (nX = 1; nX <= nWidth; nX++)
+ {
+ pHMap[nX] = nX - 1;
+ }
+
+ pHMap[nWidth + 1] = nWidth - 1;
+
+ pVMap[0] = 0;
+
+ for (nY = 1; nY <= nHeight; nY++)
+ {
+ pVMap[nY] = nY - 1;
+ }
+
+ pVMap[nHeight + 1] = nHeight - 1;
+
+ for (nY = 0; nY < nHeight; nY++)
+ {
+ nGrey11 = pReadAcc->GetPixel(pVMap[nY], pHMap[0]).GetIndex();
+ nGrey12 = pReadAcc->GetPixel(pVMap[nY], pHMap[1]).GetIndex();
+ nGrey13 = pReadAcc->GetPixel(pVMap[nY], pHMap[2]).GetIndex();
+ nGrey21 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[0]).GetIndex();
+ nGrey22 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[1]).GetIndex();
+ nGrey23 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[2]).GetIndex();
+ nGrey31 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[0]).GetIndex();
+ nGrey32 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[1]).GetIndex();
+ nGrey33 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[2]).GetIndex();
+
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (nX = 0; nX < nWidth; nX++)
+ {
+ nNx = nGrey11 + nGrey21 + nGrey31 - nGrey13 - nGrey23 - nGrey33;
+ nNy = nGrey31 + nGrey32 + nGrey33 - nGrey11 - nGrey12 - nGrey13;
+
+ if (!nNx && !nNy)
+ {
+ aGrey.SetIndex(cLz);
+ }
+ else if ((nDotL = nNx * nLx + nNy * nLy + nNzLz) < 0)
+ {
+ aGrey.SetIndex(0);
+ }
+ else
+ {
+ const double fGrey
+ = nDotL / sqrt(static_cast<double>(nNx * nNx + nNy * nNy + nZ2));
+ aGrey.SetIndex(static_cast<sal_uInt8>(std::clamp(fGrey, 0.0, 255.0)));
+ }
+
+ pWriteAcc->SetPixelOnData(pScanline, nX, aGrey);
+
+ if (nX < (nWidth - 1))
+ {
+ const sal_Int32 nNextX = pHMap[nX + 3];
+
+ nGrey11 = nGrey12;
+ nGrey12 = nGrey13;
+ nGrey13 = pReadAcc->GetPixel(pVMap[nY], nNextX).GetIndex();
+ nGrey21 = nGrey22;
+ nGrey22 = nGrey23;
+ nGrey23 = pReadAcc->GetPixel(pVMap[nY + 1], nNextX).GetIndex();
+ nGrey31 = nGrey32;
+ nGrey32 = nGrey33;
+ nGrey33 = pReadAcc->GetPixel(pVMap[nY + 2], nNextX).GetIndex();
+ }
+ }
+ }
+
+ pHMap.reset();
+ pVMap.reset();
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+ }
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapEx.cxx b/vcl/source/bitmap/BitmapEx.cxx
new file mode 100644
index 000000000..5a0502bab
--- /dev/null
+++ b/vcl/source/bitmap/BitmapEx.cxx
@@ -0,0 +1,1505 @@
+/* -*- 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/log.hxx>
+#include <rtl/math.hxx>
+#include <o3tl/underlyingenumvalue.hxx>
+#include <osl/diagnose.h>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/color/bcolormodifier.hxx>
+
+#include <vcl/ImageTree.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/alpha.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/BitmapMonochromeFilter.hxx>
+
+// BitmapEx::Create
+#include <salbmp.hxx>
+#include <salinst.hxx>
+#include <svdata.hxx>
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapMaskToAlphaFilter.hxx>
+
+#include <o3tl/any.hxx>
+
+#include <com/sun/star/beans/XFastPropertySet.hpp>
+
+#include <memory>
+
+using namespace ::com::sun::star;
+
+BitmapEx::BitmapEx()
+{
+}
+
+BitmapEx::BitmapEx( const BitmapEx& ) = default;
+
+BitmapEx::BitmapEx( const BitmapEx& rBitmapEx, Point aSrc, Size aSize )
+{
+ if( rBitmapEx.IsEmpty() || aSize.IsEmpty() )
+ return;
+
+ maBitmap = Bitmap(aSize, rBitmapEx.maBitmap.getPixelFormat());
+ SetSizePixel(aSize);
+ if( rBitmapEx.IsAlpha() )
+ maAlphaMask = AlphaMask( aSize ).ImplGetBitmap();
+
+ tools::Rectangle aDestRect( Point( 0, 0 ), aSize );
+ tools::Rectangle aSrcRect( aSrc, aSize );
+ CopyPixel( aDestRect, aSrcRect, &rBitmapEx );
+}
+
+BitmapEx::BitmapEx(Size aSize, vcl::PixelFormat ePixelFormat)
+{
+ maBitmap = Bitmap(aSize, ePixelFormat);
+ SetSizePixel(aSize);
+}
+
+BitmapEx::BitmapEx( const OUString& rIconName )
+{
+ loadFromIconTheme( rIconName );
+}
+
+void BitmapEx::loadFromIconTheme( const OUString& rIconName )
+{
+ bool bSuccess;
+ OUString aIconTheme;
+
+ try
+ {
+ aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ bSuccess = ImageTree::get().loadImage(rIconName, aIconTheme, *this, true);
+ }
+ catch (...)
+ {
+ bSuccess = false;
+ }
+
+ SAL_WARN_IF( !bSuccess, "vcl", "BitmapEx::BitmapEx(): could not load image " << rIconName << " via icon theme " << aIconTheme);
+}
+
+BitmapEx::BitmapEx( const Bitmap& rBmp ) :
+ maBitmap ( rBmp ),
+ maBitmapSize ( maBitmap.GetSizePixel() )
+{
+}
+
+BitmapEx::BitmapEx( const Bitmap& rBmp, const Bitmap& rMask ) :
+ maBitmap ( rBmp ),
+ maBitmapSize ( maBitmap.GetSizePixel() )
+{
+ if (rMask.IsEmpty())
+ return;
+
+ if( rMask.getPixelFormat() == vcl::PixelFormat::N8_BPP && rMask.HasGreyPalette8Bit() )
+ maAlphaMask = rMask;
+ else if (rMask.getPixelFormat() == vcl::PixelFormat::N1_BPP)
+ {
+ // convert 1-bit mask to alpha bitmap
+ BitmapEx aBmpEx(rMask);
+ BitmapFilter::Filter(aBmpEx, BitmapMaskToAlphaFilter());
+ maAlphaMask = aBmpEx.GetBitmap();
+ }
+ else
+ {
+ // convert to alpha bitmap
+ SAL_WARN( "vcl", "BitmapEx: forced mask to monochrome");
+ BitmapEx aMaskEx(rMask);
+ BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(255));
+ BitmapFilter::Filter(aMaskEx, BitmapMaskToAlphaFilter());
+ maAlphaMask = aMaskEx.GetBitmap();
+ }
+
+ if (!maBitmap.IsEmpty() && maBitmap.GetSizePixel() != maAlphaMask.GetSizePixel())
+ {
+ OSL_ENSURE(false, "Mask size differs from Bitmap size, corrected Mask (!)");
+ maAlphaMask.Scale(maBitmap.GetSizePixel(), BmpScaleFlag::Fast);
+ }
+}
+
+BitmapEx::BitmapEx( const Bitmap& rBmp, const AlphaMask& rAlphaMask ) :
+ maBitmap ( rBmp ),
+ maAlphaMask ( rAlphaMask.ImplGetBitmap() ),
+ maBitmapSize ( maBitmap.GetSizePixel() )
+{
+ if (!maBitmap.IsEmpty() && !maAlphaMask.IsEmpty() && maBitmap.GetSizePixel() != maAlphaMask.GetSizePixel())
+ {
+ OSL_ENSURE(false, "Alpha size differs from Bitmap size, corrected Mask (!)");
+ maAlphaMask.Scale(rBmp.GetSizePixel(), BmpScaleFlag::Fast);
+ }
+}
+
+
+BitmapEx::BitmapEx( const Bitmap& rBmp, const Color& rTransparentColor ) :
+ maBitmap ( rBmp ),
+ maBitmapSize ( maBitmap.GetSizePixel() )
+{
+ maAlphaMask = maBitmap.CreateMask( rTransparentColor );
+
+ SAL_WARN_IF(rBmp.GetSizePixel() != maAlphaMask.GetSizePixel(), "vcl",
+ "BitmapEx::BitmapEx(): size mismatch for bitmap and alpha mask.");
+}
+
+
+BitmapEx& BitmapEx::operator=( const BitmapEx& ) = default;
+
+bool BitmapEx::operator==( const BitmapEx& rBitmapEx ) const
+{
+ if (GetSizePixel() != rBitmapEx.GetSizePixel())
+ return false;
+
+ if (maBitmap != rBitmapEx.maBitmap)
+ return false;
+
+ return maAlphaMask == rBitmapEx.maAlphaMask;
+}
+
+bool BitmapEx::IsEmpty() const
+{
+ return( maBitmap.IsEmpty() && maAlphaMask.IsEmpty() );
+}
+
+void BitmapEx::SetEmpty()
+{
+ maBitmap.SetEmpty();
+ maAlphaMask.SetEmpty();
+}
+
+void BitmapEx::Clear()
+{
+ SetEmpty();
+}
+
+bool BitmapEx::IsAlpha() const
+{
+ return !maAlphaMask.IsEmpty();
+}
+
+const Bitmap& BitmapEx::GetBitmap() const
+{
+ return maBitmap;
+}
+
+Bitmap BitmapEx::GetBitmap( Color aTransparentReplaceColor ) const
+{
+ Bitmap aRetBmp( maBitmap );
+
+ if( !maAlphaMask.IsEmpty() )
+ {
+ aRetBmp.Replace( maAlphaMask, aTransparentReplaceColor );
+ }
+
+ return aRetBmp;
+}
+
+AlphaMask BitmapEx::GetAlpha() const
+{
+ return AlphaMask(maAlphaMask);
+}
+
+sal_Int64 BitmapEx::GetSizeBytes() const
+{
+ sal_Int64 nSizeBytes = maBitmap.GetSizeBytes();
+
+ if( !maAlphaMask.IsEmpty() )
+ nSizeBytes += maAlphaMask.GetSizeBytes();
+
+ return nSizeBytes;
+}
+
+BitmapChecksum BitmapEx::GetChecksum() const
+{
+ BitmapChecksum nCrc = maBitmap.GetChecksum();
+
+ if( !maAlphaMask.IsEmpty() )
+ {
+ BitmapChecksumOctetArray aBCOA;
+ BCToBCOA( maAlphaMask.GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+ }
+
+ return nCrc;
+}
+
+void BitmapEx::SetSizePixel(const Size& rNewSize)
+{
+ maBitmapSize = rNewSize;
+}
+
+bool BitmapEx::Invert()
+{
+ bool bRet = false;
+
+ if (!maBitmap.IsEmpty())
+ bRet = maBitmap.Invert();
+
+ return bRet;
+}
+
+bool BitmapEx::Mirror( BmpMirrorFlags nMirrorFlags )
+{
+ bool bRet = false;
+
+ if( !maBitmap.IsEmpty() )
+ {
+ bRet = maBitmap.Mirror( nMirrorFlags );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ maAlphaMask.Mirror( nMirrorFlags );
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag )
+{
+ bool bRet = false;
+
+ if( !maBitmap.IsEmpty() )
+ {
+ bRet = maBitmap.Scale( rScaleX, rScaleY, nScaleFlag );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ {
+ maAlphaMask.Scale( rScaleX, rScaleY, nScaleFlag );
+ }
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF( !maAlphaMask.IsEmpty() && maBitmap.GetSizePixel() != maAlphaMask.GetSizePixel(), "vcl",
+ "BitmapEx::Scale(): size mismatch for bitmap and alpha mask." );
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Scale( const Size& rNewSize, BmpScaleFlag nScaleFlag )
+{
+ bool bRet;
+
+ if (GetSizePixel().Width() && GetSizePixel().Height()
+ && (rNewSize.Width() != GetSizePixel().Width()
+ || rNewSize.Height() != GetSizePixel().Height() ) )
+ {
+ bRet = Scale( static_cast<double>(rNewSize.Width()) / GetSizePixel().Width(),
+ static_cast<double>(rNewSize.Height()) / GetSizePixel().Height(),
+ nScaleFlag );
+ }
+ else
+ {
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Rotate( Degree10 nAngle10, const Color& rFillColor )
+{
+ bool bRet = false;
+
+ if( !maBitmap.IsEmpty() )
+ {
+ const bool bTransRotate = ( COL_TRANSPARENT == rFillColor );
+
+ if( bTransRotate )
+ {
+ bRet = maBitmap.Rotate( nAngle10, COL_BLACK );
+
+ if( maAlphaMask.IsEmpty() )
+ {
+ maAlphaMask = Bitmap(GetSizePixel(), vcl::PixelFormat::N8_BPP, &Bitmap::GetGreyPalette(256));
+ maAlphaMask.Erase( COL_BLACK );
+ }
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ maAlphaMask.Rotate( nAngle10, COL_WHITE );
+ }
+ else
+ {
+ bRet = maBitmap.Rotate( nAngle10, rFillColor );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ maAlphaMask.Rotate( nAngle10, COL_WHITE );
+ }
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF(!maAlphaMask.IsEmpty() && maBitmap.GetSizePixel() != maAlphaMask.GetSizePixel(), "vcl",
+ "BitmapEx::Rotate(): size mismatch for bitmap and alpha mask.");
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Crop( const tools::Rectangle& rRectPixel )
+{
+ bool bRet = false;
+
+ if( !maBitmap.IsEmpty() )
+ {
+ bRet = maBitmap.Crop( rRectPixel );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ maAlphaMask.Crop( rRectPixel );
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF(!maAlphaMask.IsEmpty() && maBitmap.GetSizePixel() != maAlphaMask.GetSizePixel(), "vcl",
+ "BitmapEx::Crop(): size mismatch for bitmap and alpha mask.");
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Convert( BmpConversion eConversion )
+{
+ return !maBitmap.IsEmpty() && maBitmap.Convert( eConversion );
+}
+
+void BitmapEx::Expand( sal_Int32 nDX, sal_Int32 nDY, bool bExpandTransparent )
+{
+ bool bRet = false;
+
+ if( maBitmap.IsEmpty() )
+ return;
+
+ bRet = maBitmap.Expand( nDX, nDY );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ {
+ Color aColor( bExpandTransparent ? COL_WHITE : COL_BLACK );
+ maAlphaMask.Expand( nDX, nDY, &aColor );
+ }
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF(!maAlphaMask.IsEmpty() && maBitmap.GetSizePixel() != maAlphaMask.GetSizePixel(), "vcl",
+ "BitmapEx::Expand(): size mismatch for bitmap and alpha mask.");
+}
+
+bool BitmapEx::CopyPixel( const tools::Rectangle& rRectDst, const tools::Rectangle& rRectSrc,
+ const BitmapEx* pBmpExSrc )
+{
+ bool bRet = false;
+
+ if( !pBmpExSrc || pBmpExSrc->IsEmpty() )
+ {
+ if( !maBitmap.IsEmpty() )
+ {
+ bRet = maBitmap.CopyPixel( rRectDst, rRectSrc );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ maAlphaMask.CopyPixel( rRectDst, rRectSrc );
+ }
+ }
+ else
+ {
+ if( !maBitmap.IsEmpty() )
+ {
+ bRet = maBitmap.CopyPixel( rRectDst, rRectSrc, &pBmpExSrc->maBitmap );
+
+ if( bRet )
+ {
+ if( pBmpExSrc->IsAlpha() )
+ {
+ if( IsAlpha() )
+ // cast to use the optimized AlphaMask::CopyPixel
+ maAlphaMask.CopyPixel_AlphaOptimized( rRectDst, rRectSrc, &pBmpExSrc->maAlphaMask );
+ else
+ {
+ sal_uInt8 cBlack = 0;
+ std::unique_ptr<AlphaMask> pAlpha(new AlphaMask(GetSizePixel(), &cBlack));
+
+ maAlphaMask = pAlpha->ImplGetBitmap();
+ pAlpha.reset();
+ maAlphaMask.CopyPixel( rRectDst, rRectSrc, &pBmpExSrc->maAlphaMask );
+ }
+ }
+ else if (IsAlpha())
+ {
+ sal_uInt8 cBlack = 0;
+ const AlphaMask aAlphaSrc(pBmpExSrc->GetSizePixel(), &cBlack);
+
+ maAlphaMask.CopyPixel( rRectDst, rRectSrc, &aAlphaSrc.ImplGetBitmap() );
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Erase( const Color& rFillColor )
+{
+ bool bRet = false;
+
+ if( !maBitmap.IsEmpty() )
+ {
+ bRet = maBitmap.Erase( rFillColor );
+
+ if( bRet && !maAlphaMask.IsEmpty() )
+ {
+ // Respect transparency on fill color
+ if( rFillColor.IsTransparent() )
+ {
+ const Color aFill( 255 - rFillColor.GetAlpha(), 255 - rFillColor.GetAlpha(), 255 - rFillColor.GetAlpha() );
+ maAlphaMask.Erase( aFill );
+ }
+ else
+ {
+ const Color aBlack( COL_BLACK );
+ maAlphaMask.Erase( aBlack );
+ }
+ }
+ }
+
+ return bRet;
+}
+
+void BitmapEx::Replace( const Color& rSearchColor, const Color& rReplaceColor )
+{
+ if (!maBitmap.IsEmpty())
+ maBitmap.Replace( rSearchColor, rReplaceColor );
+}
+
+void BitmapEx::Replace( const Color* pSearchColors, const Color* pReplaceColors, size_t nColorCount )
+{
+ if (!maBitmap.IsEmpty())
+ maBitmap.Replace( pSearchColors, pReplaceColors, nColorCount, /*pTols*/nullptr );
+}
+
+bool BitmapEx::Adjust( short nLuminancePercent, short nContrastPercent,
+ short nChannelRPercent, short nChannelGPercent, short nChannelBPercent,
+ double fGamma, bool bInvert, bool msoBrightness )
+{
+ return !maBitmap.IsEmpty() && maBitmap.Adjust( nLuminancePercent, nContrastPercent,
+ nChannelRPercent, nChannelGPercent, nChannelBPercent,
+ fGamma, bInvert, msoBrightness );
+}
+
+void BitmapEx::Draw( OutputDevice* pOutDev, const Point& rDestPt ) const
+{
+ pOutDev->DrawBitmapEx( rDestPt, *this );
+}
+
+void BitmapEx::Draw( OutputDevice* pOutDev,
+ const Point& rDestPt, const Size& rDestSize ) const
+{
+ pOutDev->DrawBitmapEx( rDestPt, rDestSize, *this );
+}
+
+BitmapEx BitmapEx:: AutoScaleBitmap(BitmapEx const & aBitmap, const tools::Long aStandardSize)
+{
+ Point aEmptyPoint(0,0);
+ double imgposX = 0;
+ double imgposY = 0;
+ BitmapEx aRet = aBitmap;
+ double imgOldWidth = aRet.GetSizePixel().Width();
+ double imgOldHeight = aRet.GetSizePixel().Height();
+
+ if (imgOldWidth >= aStandardSize || imgOldHeight >= aStandardSize)
+ {
+ sal_Int32 imgNewWidth = 0;
+ sal_Int32 imgNewHeight = 0;
+ if (imgOldWidth >= imgOldHeight)
+ {
+ imgNewWidth = aStandardSize;
+ imgNewHeight = sal_Int32(imgOldHeight / (imgOldWidth / aStandardSize) + 0.5);
+ imgposX = 0;
+ imgposY = (aStandardSize - (imgOldHeight / (imgOldWidth / aStandardSize) + 0.5)) / 2 + 0.5;
+ }
+ else
+ {
+ imgNewHeight = aStandardSize;
+ imgNewWidth = sal_Int32(imgOldWidth / (imgOldHeight / aStandardSize) + 0.5);
+ imgposY = 0;
+ imgposX = (aStandardSize - (imgOldWidth / (imgOldHeight / aStandardSize) + 0.5)) / 2 + 0.5;
+ }
+
+ Size aScaledSize( imgNewWidth, imgNewHeight );
+ aRet.Scale( aScaledSize, BmpScaleFlag::BestQuality );
+ }
+ else
+ {
+ imgposX = (aStandardSize - imgOldWidth) / 2 + 0.5;
+ imgposY = (aStandardSize - imgOldHeight) / 2 + 0.5;
+ }
+
+ Size aStdSize( aStandardSize, aStandardSize );
+ tools::Rectangle aRect(aEmptyPoint, aStdSize );
+
+ ScopedVclPtrInstance< VirtualDevice > aVirDevice(*Application::GetDefaultDevice());
+ aVirDevice->SetOutputSizePixel( aStdSize );
+ aVirDevice->SetFillColor( COL_TRANSPARENT );
+ aVirDevice->SetLineColor( COL_TRANSPARENT );
+
+ // Draw a rect into virDevice
+ aVirDevice->DrawRect( aRect );
+ Point aPointPixel( static_cast<tools::Long>(imgposX), static_cast<tools::Long>(imgposY) );
+ aVirDevice->DrawBitmapEx( aPointPixel, aRet );
+ aRet = aVirDevice->GetBitmapEx( aEmptyPoint, aStdSize );
+
+ return aRet;
+}
+
+sal_uInt8 BitmapEx::GetAlpha(sal_Int32 nX, sal_Int32 nY) const
+{
+ if(maBitmap.IsEmpty())
+ return 0;
+
+ if (nX < 0 || nX >= GetSizePixel().Width() || nY < 0 || nY >= GetSizePixel().Height())
+ return 0;
+
+ if (maBitmap.getPixelFormat() == vcl::PixelFormat::N32_BPP)
+ return GetPixelColor(nX, nY).GetAlpha();
+
+ sal_uInt8 nAlpha(0);
+ if (maAlphaMask.IsEmpty())
+ {
+ // Not transparent, ergo all covered
+ nAlpha = 255;
+ }
+ else
+ {
+ Bitmap aTestBitmap(maAlphaMask);
+ Bitmap::ScopedReadAccess pRead(aTestBitmap);
+
+ if(pRead)
+ {
+ const BitmapColor aBitmapColor(pRead->GetPixel(nY, nX));
+ nAlpha = 255 - aBitmapColor.GetIndex();
+ }
+ }
+ return nAlpha;
+}
+
+
+Color BitmapEx::GetPixelColor(sal_Int32 nX, sal_Int32 nY) const
+{
+ Bitmap::ScopedReadAccess pReadAccess( const_cast<Bitmap&>(maBitmap) );
+ assert(pReadAccess);
+
+ BitmapColor aColor = pReadAccess->GetColor(nY, nX);
+
+ if (IsAlpha())
+ {
+ AlphaMask aAlpha = GetAlpha();
+ AlphaMask::ScopedReadAccess pAlphaReadAccess(aAlpha);
+ aColor.SetAlpha(255 - pAlphaReadAccess->GetPixel(nY, nX).GetIndex());
+ }
+ else if (maBitmap.getPixelFormat() != vcl::PixelFormat::N32_BPP)
+ {
+ aColor.SetAlpha(255);
+ }
+ return aColor;
+}
+
+// Shift alpha transparent pixels between cppcanvas/ implementations
+// and vcl in a generally grotesque and under-performing fashion
+bool BitmapEx::Create( const css::uno::Reference< css::rendering::XBitmapCanvas > &xBitmapCanvas,
+ const Size &rSize )
+{
+ uno::Reference< beans::XFastPropertySet > xFastPropertySet( xBitmapCanvas, uno::UNO_QUERY );
+ if( xFastPropertySet )
+ {
+ // 0 means get BitmapEx
+ uno::Any aAny = xFastPropertySet->getFastPropertyValue( 0 );
+ std::unique_ptr<BitmapEx> xBitmapEx(reinterpret_cast<BitmapEx*>(*o3tl::doAccess<sal_Int64>(aAny)));
+ if( xBitmapEx )
+ {
+ *this = *xBitmapEx;
+ return true;
+ }
+ }
+
+ std::shared_ptr<SalBitmap> pSalBmp;
+ std::shared_ptr<SalBitmap> pSalMask;
+
+ pSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
+
+ Size aLocalSize(rSize);
+ if( pSalBmp->Create( xBitmapCanvas, aLocalSize ) )
+ {
+ pSalMask = ImplGetSVData()->mpDefInst->CreateSalBitmap();
+ if ( pSalMask->Create( xBitmapCanvas, aLocalSize, true ) )
+ {
+ *this = BitmapEx(Bitmap(pSalBmp), Bitmap(pSalMask) );
+ return true;
+ }
+ else
+ {
+ *this = BitmapEx(Bitmap(pSalBmp));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+namespace
+{
+ Bitmap impTransformBitmap(
+ const Bitmap& rSource,
+ const Size& rDestinationSize,
+ const basegfx::B2DHomMatrix& rTransform,
+ bool bSmooth)
+ {
+ Bitmap aDestination(rDestinationSize, vcl::PixelFormat::N24_BPP);
+ BitmapScopedWriteAccess xWrite(aDestination);
+
+ if(xWrite)
+ {
+ Bitmap::ScopedReadAccess xRead(const_cast< Bitmap& >(rSource));
+
+ if (xRead)
+ {
+ const Size aDestinationSizePixel(aDestination.GetSizePixel());
+ const BitmapColor aOutside(BitmapColor(0xff, 0xff, 0xff));
+
+ for(tools::Long y(0); y < aDestinationSizePixel.getHeight(); y++)
+ {
+ Scanline pScanline = xWrite->GetScanline( y );
+ for(tools::Long x(0); x < aDestinationSizePixel.getWidth(); x++)
+ {
+ const basegfx::B2DPoint aSourceCoor(rTransform * basegfx::B2DPoint(x, y));
+
+ if(bSmooth)
+ {
+ xWrite->SetPixelOnData(
+ pScanline,
+ x,
+ xRead->GetInterpolatedColorWithFallback(
+ aSourceCoor.getY(),
+ aSourceCoor.getX(),
+ aOutside));
+ }
+ else
+ {
+ // this version does the correct <= 0.0 checks, so no need
+ // to do the static_cast< sal_Int32 > self and make an error
+ xWrite->SetPixelOnData(
+ pScanline,
+ x,
+ xRead->GetColorWithFallback(
+ aSourceCoor.getY(),
+ aSourceCoor.getX(),
+ aOutside));
+ }
+ }
+ }
+ }
+ }
+ xWrite.reset();
+
+ rSource.AdaptBitCount(aDestination);
+
+ return aDestination;
+ }
+
+ /// Decides if rTransformation needs smoothing or not (e.g. 180 deg rotation doesn't need it).
+ bool implTransformNeedsSmooth(const basegfx::B2DHomMatrix& rTransformation)
+ {
+ basegfx::B2DVector aScale, aTranslate;
+ double fRotate, fShearX;
+ rTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
+ if (aScale != basegfx::B2DVector(1, 1))
+ {
+ return true;
+ }
+
+ fRotate = fmod( fRotate, 2 * M_PI );
+ if (fRotate < 0)
+ {
+ fRotate += 2 * M_PI;
+ }
+ if (!rtl::math::approxEqual(fRotate, 0)
+ && !rtl::math::approxEqual(fRotate, M_PI_2)
+ && !rtl::math::approxEqual(fRotate, M_PI)
+ && !rtl::math::approxEqual(fRotate, 3 * M_PI_2))
+ {
+ return true;
+ }
+
+ if (!rtl::math::approxEqual(fShearX, 0))
+ {
+ return true;
+ }
+
+ return false;
+ }
+} // end of anonymous namespace
+
+BitmapEx BitmapEx::TransformBitmapEx(
+ double fWidth,
+ double fHeight,
+ const basegfx::B2DHomMatrix& rTransformation) const
+{
+ if(fWidth <= 1 || fHeight <= 1)
+ return BitmapEx();
+
+ // force destination to 24 bit, we want to smooth output
+ const Size aDestinationSize(basegfx::fround(fWidth), basegfx::fround(fHeight));
+ bool bSmooth = implTransformNeedsSmooth(rTransformation);
+ const Bitmap aDestination(impTransformBitmap(GetBitmap(), aDestinationSize, rTransformation, bSmooth));
+
+ // create mask
+ if(IsAlpha())
+ {
+ const Bitmap aAlpha(impTransformBitmap(GetAlpha().GetBitmap(), aDestinationSize, rTransformation, bSmooth));
+ return BitmapEx(aDestination, AlphaMask(aAlpha));
+ }
+
+ return BitmapEx(aDestination);
+}
+
+BitmapEx BitmapEx::getTransformed(
+ const basegfx::B2DHomMatrix& rTransformation,
+ const basegfx::B2DRange& rVisibleRange,
+ double fMaximumArea) const
+{
+ BitmapEx aRetval;
+
+ if(IsEmpty())
+ return aRetval;
+
+ const sal_uInt32 nSourceWidth(GetSizePixel().Width());
+ const sal_uInt32 nSourceHeight(GetSizePixel().Height());
+
+ if(!nSourceWidth || !nSourceHeight)
+ return aRetval;
+
+ // Get aOutlineRange
+ basegfx::B2DRange aOutlineRange(0.0, 0.0, 1.0, 1.0);
+
+ aOutlineRange.transform(rTransformation);
+
+ // create visible range from it by moving from relative to absolute
+ basegfx::B2DRange aVisibleRange(rVisibleRange);
+
+ aVisibleRange.transform(
+ basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aOutlineRange.getRange(),
+ aOutlineRange.getMinimum()));
+
+ // get target size (which is visible range's size)
+ double fWidth(aVisibleRange.getWidth());
+ double fHeight(aVisibleRange.getHeight());
+
+ if(fWidth < 1.0 || fHeight < 1.0)
+ {
+ return aRetval;
+ }
+
+ // test if discrete size (pixel) maybe too big and limit it
+ const double fArea(fWidth * fHeight);
+ const bool bNeedToReduce(basegfx::fTools::more(fArea, fMaximumArea));
+ double fReduceFactor(1.0);
+
+ if(bNeedToReduce)
+ {
+ fReduceFactor = sqrt(fMaximumArea / fArea);
+ fWidth *= fReduceFactor;
+ fHeight *= fReduceFactor;
+ }
+
+ // Build complete transform from source pixels to target pixels.
+ // Start by scaling from source pixel size to unit coordinates
+ basegfx::B2DHomMatrix aTransform(
+ basegfx::utils::createScaleB2DHomMatrix(
+ 1.0 / nSourceWidth,
+ 1.0 / nSourceHeight));
+
+ // multiply with given transform which leads from unit coordinates inside
+ // aOutlineRange
+ aTransform = rTransformation * aTransform;
+
+ // subtract top-left of absolute VisibleRange
+ aTransform.translate(
+ -aVisibleRange.getMinX(),
+ -aVisibleRange.getMinY());
+
+ // scale to target pixels (if needed)
+ if(bNeedToReduce)
+ {
+ aTransform.scale(fReduceFactor, fReduceFactor);
+ }
+
+ // invert to get transformation from target pixel coordinates to source pixels
+ aTransform.invert();
+
+ // create bitmap using source, destination and linear back-transformation
+ aRetval = TransformBitmapEx(fWidth, fHeight, aTransform);
+
+ return aRetval;
+}
+
+BitmapEx BitmapEx::ModifyBitmapEx(const basegfx::BColorModifierStack& rBColorModifierStack) const
+{
+ Bitmap aChangedBitmap(GetBitmap());
+ bool bDone(false);
+
+ for(sal_uInt32 a(rBColorModifierStack.count()); a && !bDone; )
+ {
+ const basegfx::BColorModifierSharedPtr& rModifier = rBColorModifierStack.getBColorModifier(--a);
+ const basegfx::BColorModifier_replace* pReplace = dynamic_cast< const basegfx::BColorModifier_replace* >(rModifier.get());
+
+ if(pReplace)
+ {
+ // complete replace
+ if(IsAlpha())
+ {
+ // clear bitmap with dest color
+ if (vcl::isPalettePixelFormat(aChangedBitmap.getPixelFormat()))
+ {
+ // For e.g. 8bit Bitmaps, the nearest color to the given erase color is
+ // determined and used -> this may be different from what is wanted here.
+ // Better create a new bitmap with the needed color explicitly.
+ Bitmap::ScopedReadAccess xReadAccess(aChangedBitmap);
+ OSL_ENSURE(xReadAccess, "Got no Bitmap ReadAccess ?!?");
+
+ if(xReadAccess)
+ {
+ BitmapPalette aNewPalette(xReadAccess->GetPalette());
+ aNewPalette[0] = BitmapColor(Color(pReplace->getBColor()));
+ aChangedBitmap = Bitmap(
+ aChangedBitmap.GetSizePixel(),
+ aChangedBitmap.getPixelFormat(),
+ &aNewPalette);
+ }
+ }
+ aChangedBitmap.Erase(Color(pReplace->getBColor()));
+ }
+ else
+ {
+ // erase bitmap, caller will know to paint direct
+ aChangedBitmap.SetEmpty();
+ }
+
+ bDone = true;
+ }
+ else
+ {
+ BitmapScopedWriteAccess xContent(aChangedBitmap);
+
+ if(xContent)
+ {
+ const double fConvertColor(1.0 / 255.0);
+
+ if(xContent->HasPalette())
+ {
+ const sal_uInt16 nCount(xContent->GetPaletteEntryCount());
+
+ for(sal_uInt16 b(0); b < nCount; b++)
+ {
+ const BitmapColor& rCol = xContent->GetPaletteColor(b);
+ const basegfx::BColor aBSource(
+ rCol.GetRed() * fConvertColor,
+ rCol.GetGreen() * fConvertColor,
+ rCol.GetBlue() * fConvertColor);
+ const basegfx::BColor aBDest(rModifier->getModifiedColor(aBSource));
+ xContent->SetPaletteColor(b, BitmapColor(Color(aBDest)));
+ }
+ }
+ else if(ScanlineFormat::N24BitTcBgr == xContent->GetScanlineFormat())
+ {
+ for(tools::Long y(0); y < xContent->Height(); y++)
+ {
+ Scanline pScan = xContent->GetScanline(y);
+
+ for(tools::Long x(0); x < xContent->Width(); x++)
+ {
+ const basegfx::BColor aBSource(
+ *(pScan + 2)* fConvertColor,
+ *(pScan + 1) * fConvertColor,
+ *pScan * fConvertColor);
+ const basegfx::BColor aBDest(rModifier->getModifiedColor(aBSource));
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getBlue() * 255.0);
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getGreen() * 255.0);
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getRed() * 255.0);
+ }
+ }
+ }
+ else if(ScanlineFormat::N24BitTcRgb == xContent->GetScanlineFormat())
+ {
+ for(tools::Long y(0); y < xContent->Height(); y++)
+ {
+ Scanline pScan = xContent->GetScanline(y);
+
+ for(tools::Long x(0); x < xContent->Width(); x++)
+ {
+ const basegfx::BColor aBSource(
+ *pScan * fConvertColor,
+ *(pScan + 1) * fConvertColor,
+ *(pScan + 2) * fConvertColor);
+ const basegfx::BColor aBDest(rModifier->getModifiedColor(aBSource));
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getRed() * 255.0);
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getGreen() * 255.0);
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getBlue() * 255.0);
+ }
+ }
+ }
+ else
+ {
+ for(tools::Long y(0); y < xContent->Height(); y++)
+ {
+ Scanline pScanline = xContent->GetScanline( y );
+ for(tools::Long x(0); x < xContent->Width(); x++)
+ {
+ const BitmapColor aBMCol(xContent->GetColor(y, x));
+ const basegfx::BColor aBSource(
+ static_cast<double>(aBMCol.GetRed()) * fConvertColor,
+ static_cast<double>(aBMCol.GetGreen()) * fConvertColor,
+ static_cast<double>(aBMCol.GetBlue()) * fConvertColor);
+ const basegfx::BColor aBDest(rModifier->getModifiedColor(aBSource));
+
+ xContent->SetPixelOnData(pScanline, x, BitmapColor(Color(aBDest)));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if(aChangedBitmap.IsEmpty())
+ {
+ return BitmapEx();
+ }
+ else
+ {
+ if(IsAlpha())
+ {
+ return BitmapEx(aChangedBitmap, GetAlpha());
+ }
+ else
+ {
+ return BitmapEx(aChangedBitmap);
+ }
+ }
+}
+
+BitmapEx createBlendFrame(
+ const Size& rSize,
+ sal_uInt8 nAlpha,
+ Color aColorTopLeft,
+ Color aColorBottomRight)
+{
+ const sal_uInt32 nW(rSize.Width());
+ const sal_uInt32 nH(rSize.Height());
+
+ if(nW || nH)
+ {
+ Color aColTopRight(aColorTopLeft);
+ Color aColBottomLeft(aColorTopLeft);
+ const sal_uInt32 nDE(nW + nH);
+
+ aColTopRight.Merge(aColorBottomRight, 255 - sal_uInt8((nW * 255) / nDE));
+ aColBottomLeft.Merge(aColorBottomRight, 255 - sal_uInt8((nH * 255) / nDE));
+
+ return createBlendFrame(rSize, nAlpha, aColorTopLeft, aColTopRight, aColorBottomRight, aColBottomLeft);
+ }
+
+ return BitmapEx();
+}
+
+BitmapEx createBlendFrame(
+ const Size& rSize,
+ sal_uInt8 nAlpha,
+ Color aColorTopLeft,
+ Color aColorTopRight,
+ Color aColorBottomRight,
+ Color aColorBottomLeft)
+{
+ BlendFrameCache* pBlendFrameCache = ImplGetBlendFrameCache();
+
+ if(pBlendFrameCache->m_aLastSize == rSize
+ && pBlendFrameCache->m_nLastAlpha == nAlpha
+ && pBlendFrameCache->m_aLastColorTopLeft == aColorTopLeft
+ && pBlendFrameCache->m_aLastColorTopRight == aColorTopRight
+ && pBlendFrameCache->m_aLastColorBottomRight == aColorBottomRight
+ && pBlendFrameCache->m_aLastColorBottomLeft == aColorBottomLeft)
+ {
+ return pBlendFrameCache->m_aLastResult;
+ }
+
+ pBlendFrameCache->m_aLastSize = rSize;
+ pBlendFrameCache->m_nLastAlpha = nAlpha;
+ pBlendFrameCache->m_aLastColorTopLeft = aColorTopLeft;
+ pBlendFrameCache->m_aLastColorTopRight = aColorTopRight;
+ pBlendFrameCache->m_aLastColorBottomRight = aColorBottomRight;
+ pBlendFrameCache->m_aLastColorBottomLeft = aColorBottomLeft;
+ pBlendFrameCache->m_aLastResult.Clear();
+
+ const tools::Long nW(rSize.Width());
+ const tools::Long nH(rSize.Height());
+
+ if(nW > 1 && nH > 1)
+ {
+ sal_uInt8 aEraseTrans(0xff);
+ Bitmap aContent(rSize, vcl::PixelFormat::N24_BPP);
+ AlphaMask aAlpha(rSize, &aEraseTrans);
+
+ aContent.Erase(COL_BLACK);
+
+ BitmapScopedWriteAccess pContent(aContent);
+ AlphaScopedWriteAccess pAlpha(aAlpha);
+
+ if(pContent && pAlpha)
+ {
+ tools::Long x(0);
+ tools::Long y(0);
+ Scanline pScanContent = pContent->GetScanline( 0 );
+ Scanline pScanAlpha = pContent->GetScanline( 0 );
+
+ // x == 0, y == 0, top-left corner
+ pContent->SetPixelOnData(pScanContent, 0, aColorTopLeft);
+ pAlpha->SetPixelOnData(pScanAlpha, 0, BitmapColor(nAlpha));
+
+ // y == 0, top line left to right
+ for(x = 1; x < nW - 1; x++)
+ {
+ Color aMix(aColorTopLeft);
+
+ aMix.Merge(aColorTopRight, 255 - sal_uInt8((x * 255) / nW));
+ pContent->SetPixelOnData(pScanContent, x, aMix);
+ pAlpha->SetPixelOnData(pScanAlpha, x, BitmapColor(nAlpha));
+ }
+
+ // x == nW - 1, y == 0, top-right corner
+ // #i123690# Caution! When nW is 1, x == nW is possible (!)
+ if(x < nW)
+ {
+ pContent->SetPixelOnData(pScanContent, x, aColorTopRight);
+ pAlpha->SetPixelOnData(pScanAlpha, x, BitmapColor(nAlpha));
+ }
+
+ // x == 0 and nW - 1, left and right line top-down
+ for(y = 1; y < nH - 1; y++)
+ {
+ pScanContent = pContent->GetScanline( y );
+ pScanAlpha = pContent->GetScanline( y );
+ Color aMixA(aColorTopLeft);
+
+ aMixA.Merge(aColorBottomLeft, 255 - sal_uInt8((y * 255) / nH));
+ pContent->SetPixelOnData(pScanContent, 0, aMixA);
+ pAlpha->SetPixelOnData(pScanAlpha, 0, BitmapColor(nAlpha));
+
+ // #i123690# Caution! When nW is 1, x == nW is possible (!)
+ if(x < nW)
+ {
+ Color aMixB(aColorTopRight);
+
+ aMixB.Merge(aColorBottomRight, 255 - sal_uInt8((y * 255) / nH));
+ pContent->SetPixelOnData(pScanContent, x, aMixB);
+ pAlpha->SetPixelOnData(pScanAlpha, x, BitmapColor(nAlpha));
+ }
+ }
+
+ // #i123690# Caution! When nH is 1, y == nH is possible (!)
+ if(y < nH)
+ {
+ // x == 0, y == nH - 1, bottom-left corner
+ pContent->SetPixelOnData(pScanContent, 0, aColorBottomLeft);
+ pAlpha->SetPixelOnData(pScanAlpha, 0, BitmapColor(nAlpha));
+
+ // y == nH - 1, bottom line left to right
+ for(x = 1; x < nW - 1; x++)
+ {
+ Color aMix(aColorBottomLeft);
+
+ aMix.Merge(aColorBottomRight, 255 - sal_uInt8(((x - 0)* 255) / nW));
+ pContent->SetPixelOnData(pScanContent, x, aMix);
+ pAlpha->SetPixelOnData(pScanAlpha, x, BitmapColor(nAlpha));
+ }
+
+ // x == nW - 1, y == nH - 1, bottom-right corner
+ // #i123690# Caution! When nW is 1, x == nW is possible (!)
+ if(x < nW)
+ {
+ pContent->SetPixelOnData(pScanContent, x, aColorBottomRight);
+ pAlpha->SetPixelOnData(pScanAlpha, x, BitmapColor(nAlpha));
+ }
+ }
+
+ pContent.reset();
+ pAlpha.reset();
+
+ pBlendFrameCache->m_aLastResult = BitmapEx(aContent, aAlpha);
+ }
+ }
+
+ return pBlendFrameCache->m_aLastResult;
+}
+
+void BitmapEx::Replace(const Color& rSearchColor,
+ const Color& rReplaceColor,
+ sal_uInt8 nTolerance)
+{
+ maBitmap.Replace(rSearchColor, rReplaceColor, nTolerance);
+}
+
+void BitmapEx::Replace( const Color* pSearchColors,
+ const Color* pReplaceColors,
+ size_t nColorCount,
+ sal_uInt8 const * pTols )
+{
+ maBitmap.Replace( pSearchColors, pReplaceColors, nColorCount, pTols );
+}
+
+void BitmapEx::ReplaceTransparency(const Color& rColor)
+{
+ if( IsAlpha() )
+ {
+ maBitmap.Replace( GetAlpha(), rColor );
+ maAlphaMask = Bitmap();
+ maBitmapSize = maBitmap.GetSizePixel();
+ }
+}
+
+static Bitmap DetectEdges( const Bitmap& rBmp )
+{
+ constexpr sal_uInt8 cEdgeDetectThreshold = 128;
+ const Size aSize( rBmp.GetSizePixel() );
+ Bitmap aRetBmp;
+
+ if( ( aSize.Width() > 2 ) && ( aSize.Height() > 2 ) )
+ {
+ Bitmap aWorkBmp( rBmp );
+
+ if( aWorkBmp.Convert( BmpConversion::N8BitGreys ) )
+ {
+ bool bRet = false;
+
+ ScopedVclPtr<VirtualDevice> pVirDev(VclPtr<VirtualDevice>::Create());
+ pVirDev->SetOutputSizePixel(aSize);
+ Bitmap::ScopedReadAccess pReadAcc(aWorkBmp);
+
+ if( pReadAcc )
+ {
+ const tools::Long nWidth = aSize.Width();
+ const tools::Long nWidth2 = nWidth - 2;
+ const tools::Long nHeight = aSize.Height();
+ const tools::Long nHeight2 = nHeight - 2;
+ const tools::Long lThres2 = static_cast<tools::Long>(cEdgeDetectThreshold) * cEdgeDetectThreshold;
+ tools::Long nSum1;
+ tools::Long nSum2;
+ tools::Long lGray;
+
+ // initialize border with white pixels
+ pVirDev->SetLineColor( COL_WHITE );
+ pVirDev->DrawLine( Point(), Point( nWidth - 1, 0L ) );
+ pVirDev->DrawLine( Point( nWidth - 1, 0L ), Point( nWidth - 1, nHeight - 1 ) );
+ pVirDev->DrawLine( Point( nWidth - 1, nHeight - 1 ), Point( 0L, nHeight - 1 ) );
+ pVirDev->DrawLine( Point( 0, nHeight - 1 ), Point() );
+
+ for( tools::Long nY = 0, nY1 = 1, nY2 = 2; nY < nHeight2; nY++, nY1++, nY2++ )
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline( nY );
+ Scanline pScanlineRead1 = pReadAcc->GetScanline( nY1 );
+ Scanline pScanlineRead2 = pReadAcc->GetScanline( nY2 );
+ for( tools::Long nX = 0, nXDst = 1, nXTmp; nX < nWidth2; nX++, nXDst++ )
+ {
+ nXTmp = nX;
+
+ nSum2 = pReadAcc->GetIndexFromData( pScanlineRead, nXTmp++ );
+ nSum1 = -nSum2;
+ nSum2 += static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead, nXTmp++ )) << 1;
+ lGray = pReadAcc->GetIndexFromData( pScanlineRead, nXTmp );
+ nSum1 += lGray;
+ nSum2 += lGray;
+
+ nSum1 += static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead1, nXTmp )) << 1;
+ nXTmp -= 2;
+ nSum1 -= static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead1, nXTmp )) << 1;
+
+ lGray = -static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead2, nXTmp++ ));
+ nSum1 += lGray;
+ nSum2 += lGray;
+ nSum2 -= static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead2, nXTmp++ )) << 1;
+ lGray = static_cast<tools::Long>(pReadAcc->GetIndexFromData( pScanlineRead2, nXTmp ));
+ nSum1 += lGray;
+ nSum2 -= lGray;
+
+ if( ( nSum1 * nSum1 + nSum2 * nSum2 ) < lThres2 )
+ pVirDev->DrawPixel( Point(nXDst, nY), COL_WHITE );
+ else
+ pVirDev->DrawPixel( Point(nXDst, nY), COL_BLACK );
+ }
+ }
+
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if( bRet )
+ aRetBmp = pVirDev->GetBitmap(Point(0,0), aSize);
+ }
+ }
+
+ if( aRetBmp.IsEmpty() )
+ aRetBmp = rBmp;
+ else
+ {
+ aRetBmp.SetPrefMapMode( rBmp.GetPrefMapMode() );
+ aRetBmp.SetPrefSize( rBmp.GetPrefSize() );
+ }
+
+ return aRetBmp;
+}
+
+/** Get contours in image */
+tools::Polygon BitmapEx::GetContour( bool bContourEdgeDetect,
+ const tools::Rectangle* pWorkRectPixel )
+{
+ Bitmap aWorkBmp;
+ tools::Polygon aRetPoly;
+ tools::Rectangle aWorkRect( Point(), maBitmap.GetSizePixel() );
+
+ if( pWorkRectPixel )
+ aWorkRect.Intersection( *pWorkRectPixel );
+
+ aWorkRect.Justify();
+
+ if( ( aWorkRect.GetWidth() > 4 ) && ( aWorkRect.GetHeight() > 4 ) )
+ {
+ // if the flag is set, we need to detect edges
+ if( bContourEdgeDetect )
+ aWorkBmp = DetectEdges( maBitmap );
+ else
+ aWorkBmp = maBitmap;
+
+ BitmapReadAccess* pAcc = aWorkBmp.AcquireReadAccess();
+
+ const tools::Long nWidth = pAcc ? pAcc->Width() : 0;
+ const tools::Long nHeight = pAcc ? pAcc->Height() : 0;
+
+ if (pAcc && nWidth && nHeight)
+ {
+ const Size& rPrefSize = aWorkBmp.GetPrefSize();
+ const double fFactorX = static_cast<double>(rPrefSize.Width()) / nWidth;
+ const double fFactorY = static_cast<double>(rPrefSize.Height()) / nHeight;
+ const tools::Long nStartX1 = aWorkRect.Left() + 1;
+ const tools::Long nEndX1 = aWorkRect.Right();
+ const tools::Long nStartX2 = nEndX1 - 1;
+ const tools::Long nStartY1 = aWorkRect.Top() + 1;
+ const tools::Long nEndY1 = aWorkRect.Bottom();
+ std::unique_ptr<Point[]> pPoints1;
+ std::unique_ptr<Point[]> pPoints2;
+ tools::Long nX, nY;
+ sal_uInt16 nPolyPos = 0;
+ const BitmapColor aBlack = pAcc->GetBestMatchingColor( COL_BLACK );
+
+ pPoints1.reset(new Point[ nHeight ]);
+ pPoints2.reset(new Point[ nHeight ]);
+
+ for ( nY = nStartY1; nY < nEndY1; nY++ )
+ {
+ nX = nStartX1;
+ Scanline pScanline = pAcc->GetScanline( nY );
+
+ // scan row from left to right
+ while( nX < nEndX1 )
+ {
+ if( aBlack == pAcc->GetPixelFromData( pScanline, nX ) )
+ {
+ pPoints1[ nPolyPos ] = Point( nX, nY );
+ nX = nStartX2;
+
+ // this loop always breaks eventually as there is at least one pixel
+ while( true )
+ {
+ if( aBlack == pAcc->GetPixelFromData( pScanline, nX ) )
+ {
+ pPoints2[ nPolyPos ] = Point( nX, nY );
+ break;
+ }
+
+ nX--;
+ }
+
+ nPolyPos++;
+ break;
+ }
+
+ nX++;
+ }
+ }
+
+ const sal_uInt16 nNewSize1 = nPolyPos << 1;
+
+ aRetPoly = tools::Polygon( nPolyPos, pPoints1.get() );
+ aRetPoly.SetSize( nNewSize1 + 1 );
+ aRetPoly[ nNewSize1 ] = aRetPoly[ 0 ];
+
+ for( sal_uInt16 j = nPolyPos; nPolyPos < nNewSize1; )
+ aRetPoly[ nPolyPos++ ] = pPoints2[ --j ];
+
+ if( ( fFactorX != 0. ) && ( fFactorY != 0. ) )
+ aRetPoly.Scale( fFactorX, fFactorY );
+ }
+
+ Bitmap::ReleaseAccess(pAcc);
+ }
+
+ return aRetPoly;
+}
+
+void BitmapEx::setAlphaFrom( sal_uInt8 cIndexFrom, sal_Int8 nAlphaTo )
+{
+ AlphaMask aAlphaMask(GetAlpha());
+ BitmapScopedWriteAccess pWriteAccess(aAlphaMask);
+ Bitmap::ScopedReadAccess pReadAccess(maBitmap);
+ assert( pReadAccess.get() && pWriteAccess.get() );
+ if ( !(pReadAccess.get() && pWriteAccess.get()) )
+ return;
+
+ for ( tools::Long nY = 0; nY < pReadAccess->Height(); nY++ )
+ {
+ Scanline pScanline = pWriteAccess->GetScanline( nY );
+ Scanline pScanlineRead = pReadAccess->GetScanline( nY );
+ for ( tools::Long nX = 0; nX < pReadAccess->Width(); nX++ )
+ {
+ const sal_uInt8 cIndex = pReadAccess->GetPixelFromData( pScanlineRead, nX ).GetIndex();
+ if ( cIndex == cIndexFrom )
+ pWriteAccess->SetPixelOnData( pScanline, nX, BitmapColor(nAlphaTo) );
+ }
+ }
+ *this = BitmapEx( GetBitmap(), aAlphaMask );
+}
+
+void BitmapEx::AdjustTransparency(sal_uInt8 cTrans)
+{
+ AlphaMask aAlpha;
+
+ if (!IsAlpha())
+ {
+ aAlpha = AlphaMask(GetSizePixel(), &cTrans);
+ }
+ else
+ {
+ aAlpha = GetAlpha();
+ BitmapScopedWriteAccess pA(aAlpha);
+ assert(pA);
+
+ if( !pA )
+ return;
+
+ sal_uLong nTrans = cTrans, nNewTrans;
+ const tools::Long nWidth = pA->Width(), nHeight = pA->Height();
+
+ if( pA->GetScanlineFormat() == ScanlineFormat::N8BitPal )
+ {
+ for( tools::Long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pAScan = pA->GetScanline( nY );
+
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ nNewTrans = nTrans + *pAScan;
+ *pAScan++ = static_cast<sal_uInt8>( ( nNewTrans & 0xffffff00 ) ? 255 : nNewTrans );
+ }
+ }
+ }
+ else
+ {
+ BitmapColor aAlphaValue( 0 );
+
+ for( tools::Long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pA->GetScanline( nY );
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ nNewTrans = nTrans + pA->GetIndexFromData( pScanline, nX );
+ aAlphaValue.SetIndex( static_cast<sal_uInt8>( ( nNewTrans & 0xffffff00 ) ? 255 : nNewTrans ) );
+ pA->SetPixelOnData( pScanline, nX, aAlphaValue );
+ }
+ }
+ }
+ }
+ *this = BitmapEx( GetBitmap(), aAlpha );
+}
+
+void BitmapEx::CombineMaskOr(Color maskColor, sal_uInt8 nTol)
+{
+ Bitmap aNewMask = maBitmap.CreateMask( maskColor, nTol );
+ if ( IsAlpha() )
+ aNewMask.CombineOr( maAlphaMask );
+ maAlphaMask = aNewMask;
+}
+
+/**
+ * Retrieves the color model data we need for the XImageConsumer stuff.
+ */
+void BitmapEx::GetColorModel(css::uno::Sequence< sal_Int32 >& rRGBPalette,
+ sal_uInt32& rnRedMask, sal_uInt32& rnGreenMask, sal_uInt32& rnBlueMask, sal_uInt32& rnAlphaMask, sal_uInt32& rnTransparencyIndex,
+ sal_uInt32& rnWidth, sal_uInt32& rnHeight, sal_uInt8& rnBitCount)
+{
+ Bitmap::ScopedReadAccess pReadAccess( maBitmap );
+ assert( pReadAccess );
+
+ if( pReadAccess->HasPalette() )
+ {
+ sal_uInt16 nPalCount = pReadAccess->GetPaletteEntryCount();
+
+ if( nPalCount )
+ {
+ rRGBPalette = css::uno::Sequence< sal_Int32 >( nPalCount + 1 );
+
+ sal_Int32* pTmp = rRGBPalette.getArray();
+
+ for( sal_uInt32 i = 0; i < nPalCount; i++, pTmp++ )
+ {
+ const BitmapColor& rCol = pReadAccess->GetPaletteColor( static_cast<sal_uInt16>(i) );
+
+ *pTmp = static_cast<sal_Int32>(rCol.GetRed()) << sal_Int32(24);
+ *pTmp |= static_cast<sal_Int32>(rCol.GetGreen()) << sal_Int32(16);
+ *pTmp |= static_cast<sal_Int32>(rCol.GetBlue()) << sal_Int32(8);
+ *pTmp |= sal_Int32(0x000000ffL);
+ }
+
+ if( IsAlpha() )
+ {
+ // append transparent entry
+ *pTmp = sal_Int32(0xffffff00L);
+ rnTransparencyIndex = nPalCount;
+ nPalCount++;
+ }
+ else
+ rnTransparencyIndex = 0;
+ }
+ }
+ else
+ {
+ rnRedMask = 0xff000000UL;
+ rnGreenMask = 0x00ff0000UL;
+ rnBlueMask = 0x0000ff00UL;
+ rnAlphaMask = 0x000000ffUL;
+ rnTransparencyIndex = 0;
+ }
+
+ rnWidth = pReadAccess->Width();
+ rnHeight = pReadAccess->Height();
+ rnBitCount = pReadAccess->GetBitCount();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapFastScaleFilter.cxx b/vcl/source/bitmap/BitmapFastScaleFilter.cxx
new file mode 100644
index 000000000..14fa2464f
--- /dev/null
+++ b/vcl/source/bitmap/BitmapFastScaleFilter.cxx
@@ -0,0 +1,129 @@
+/* -*- 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/log.hxx>
+#include <tools/helpers.hxx>
+
+#include <vcl/bitmapex.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapFastScaleFilter.hxx>
+
+BitmapEx BitmapFastScaleFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ SAL_INFO("vcl.gdi", "BitmapFastScaleFilter::execute()");
+
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const Size aSizePix(aBitmap.GetSizePixel());
+ const sal_Int32 nNewWidth = FRound(aSizePix.Width() * mfScaleX);
+ const sal_Int32 nNewHeight = FRound(aSizePix.Height() * mfScaleY);
+ bool bRet = false;
+
+ SAL_INFO("vcl.gdi", "New width: " << nNewWidth << "\nNew height: " << nNewHeight);
+
+ if (nNewWidth > 0 && nNewHeight > 0)
+ {
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+
+ if (pReadAcc)
+ {
+ Bitmap aNewBmp(Size(nNewWidth, nNewHeight), aBitmap.getPixelFormat(),
+ &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const sal_Int32 nScanlineSize = pWriteAcc->GetScanlineSize();
+ const sal_Int32 nNewHeight1 = nNewHeight - 1;
+
+ if (nNewWidth && nNewHeight)
+ {
+ const double nWidth = pReadAcc->Width();
+ const double nHeight = pReadAcc->Height();
+ std::unique_ptr<sal_Int32[]> pLutX(new sal_Int32[nNewWidth]);
+ std::unique_ptr<sal_Int32[]> pLutY(new sal_Int32[nNewHeight]);
+
+ for (sal_Int32 nX = 0; nX < nNewWidth; nX++)
+ {
+ pLutX[nX] = sal_Int32(nX * nWidth / nNewWidth);
+ }
+
+ for (sal_Int32 nY = 0; nY < nNewHeight; nY++)
+ {
+ pLutY[nY] = sal_Int32(nY * nHeight / nNewHeight);
+ }
+
+ sal_Int32 nActY = 0;
+ while (nActY < nNewHeight)
+ {
+ sal_Int32 nMapY = pLutY[nActY];
+ Scanline pScanline = pWriteAcc->GetScanline(nActY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nMapY);
+
+ for (sal_Int32 nX = 0; nX < nNewWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(
+ pScanline, nX,
+ pReadAcc->GetPixelFromData(pScanlineRead, pLutX[nX]));
+ }
+
+ while ((nActY < nNewHeight1) && (pLutY[nActY + 1] == nMapY))
+ {
+ memcpy(pWriteAcc->GetScanline(nActY + 1), pWriteAcc->GetScanline(nActY),
+ nScanlineSize);
+ nActY++;
+ }
+ nActY++;
+ }
+
+ bRet = true;
+ }
+
+ pWriteAcc.reset();
+ }
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ aBitmap.ReassignWithSize(aNewBmp);
+ SAL_INFO("vcl.gdi", "Bitmap size: " << aBitmap.GetSizePixel());
+ }
+ else
+ {
+ SAL_WARN("vcl.gdi", "no resize");
+ }
+ }
+ }
+
+ AlphaMask aMask(rBitmapEx.GetAlpha());
+
+ if (bRet && !aMask.IsEmpty())
+ bRet = aMask.Scale(maSize, BmpScaleFlag::Fast);
+
+ SAL_WARN_IF(!aMask.IsEmpty() && aBitmap.GetSizePixel() != aMask.GetSizePixel(), "vcl",
+ "BitmapEx::Scale(): size mismatch for bitmap and alpha mask.");
+
+ if (bRet)
+ return BitmapEx(aBitmap, aMask);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapFilterStackBlur.cxx b/vcl/source/bitmap/BitmapFilterStackBlur.cxx
new file mode 100644
index 000000000..eeb5810f5
--- /dev/null
+++ b/vcl/source/bitmap/BitmapFilterStackBlur.cxx
@@ -0,0 +1,658 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/BitmapFilterStackBlur.hxx>
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <sal/log.hxx>
+
+#include <comphelper/threadpool.hxx>
+
+namespace
+{
+const sal_Int16 constMultiplyTable[255]
+ = { 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, 454,
+ 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512, 482, 454,
+ 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, 437, 420, 404,
+ 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512, 497, 482, 468, 454,
+ 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, 320, 312, 305, 298, 291,
+ 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456, 446, 437, 428, 420, 412, 404,
+ 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, 329, 323, 318, 312, 307, 302, 297,
+ 292, 287, 282, 278, 273, 269, 265, 261, 512, 505, 497, 489, 482, 475, 468, 461, 454,
+ 447, 441, 435, 428, 422, 417, 411, 405, 399, 394, 389, 383, 378, 373, 368, 364, 359,
+ 354, 350, 345, 341, 337, 332, 328, 324, 320, 316, 312, 309, 305, 301, 298, 294, 291,
+ 287, 284, 281, 278, 274, 271, 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480,
+ 475, 470, 465, 460, 456, 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404,
+ 400, 396, 392, 388, 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344,
+ 341, 338, 335, 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297,
+ 294, 292, 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259 };
+
+const sal_Int16 constShiftTable[255]
+ = { 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
+ 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 };
+
+struct BlurSharedData
+{
+ BitmapReadAccess* mpReadAccess;
+ BitmapWriteAccess* mpWriteAccess;
+ sal_Int32 mnRadius;
+ sal_Int32 mnComponentWidth;
+ sal_Int32 mnDiv;
+ sal_Int32 mnColorChannels;
+
+ BlurSharedData(BitmapReadAccess* pReadAccess, BitmapWriteAccess* pWriteAccess,
+ sal_Int32 aRadius, sal_Int32 nComponentWidth, sal_Int32 nColorChannels)
+ : mpReadAccess(pReadAccess)
+ , mpWriteAccess(pWriteAccess)
+ , mnRadius(aRadius)
+ , mnComponentWidth(nComponentWidth)
+ , mnDiv(aRadius + aRadius + 1)
+ , mnColorChannels(nColorChannels)
+ {
+ }
+};
+
+struct BlurArrays
+{
+ BlurSharedData maShared;
+
+ std::vector<sal_uInt8> maStackBuffer;
+ std::vector<sal_Int32> maPositionTable;
+ std::vector<sal_Int32> maWeightTable;
+
+ std::vector<sal_Int32> mnSumVector;
+ std::vector<sal_Int32> mnInSumVector;
+ std::vector<sal_Int32> mnOutSumVector;
+
+ BlurArrays(BlurSharedData const& rShared)
+ : maShared(rShared)
+ , maStackBuffer(maShared.mnDiv * maShared.mnComponentWidth)
+ , maPositionTable(maShared.mnDiv)
+ , maWeightTable(maShared.mnDiv)
+ , mnSumVector(maShared.mnColorChannels)
+ , mnInSumVector(maShared.mnColorChannels)
+ , mnOutSumVector(maShared.mnColorChannels)
+ {
+ }
+
+ void initializeWeightAndPositions(sal_Int32 nLastIndex)
+ {
+ for (sal_Int32 i = 0; i < maShared.mnDiv; i++)
+ {
+ maPositionTable[i] = std::clamp(i - maShared.mnRadius, sal_Int32(0), nLastIndex);
+ maWeightTable[i] = maShared.mnRadius + 1 - std::abs(i - maShared.mnRadius);
+ }
+ }
+
+ sal_Int32 getMultiplyValue() const
+ {
+ return static_cast<sal_Int32>(constMultiplyTable[maShared.mnRadius]);
+ }
+
+ sal_Int32 getShiftValue() const
+ {
+ return static_cast<sal_Int32>(constShiftTable[maShared.mnRadius]);
+ }
+};
+
+typedef void (*BlurRangeFn)(BlurSharedData const& rShared, sal_Int32 nStartY, sal_Int32 nEndY);
+
+class BlurTask : public comphelper::ThreadTask
+{
+ BlurRangeFn mpBlurFunction;
+ BlurSharedData& mrShared;
+ sal_Int32 mnStartY;
+ sal_Int32 mnEndY;
+
+public:
+ explicit BlurTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag,
+ BlurRangeFn pBlurFunction, BlurSharedData& rShared, sal_Int32 nStartY,
+ sal_Int32 nEndY)
+ : comphelper::ThreadTask(pTag)
+ , mpBlurFunction(pBlurFunction)
+ , mrShared(rShared)
+ , mnStartY(nStartY)
+ , mnEndY(nEndY)
+ {
+ }
+
+ virtual void doWork() override { mpBlurFunction(mrShared, mnStartY, mnEndY); }
+};
+
+struct SumFunction24
+{
+ static inline void add(sal_Int32*& pValue1, sal_Int32 nConstant)
+ {
+ pValue1[0] += nConstant;
+ pValue1[1] += nConstant;
+ pValue1[2] += nConstant;
+ }
+
+ static inline void set(sal_Int32*& pValue1, sal_Int32 nConstant)
+ {
+ pValue1[0] = nConstant;
+ pValue1[1] = nConstant;
+ pValue1[2] = nConstant;
+ }
+
+ static inline void add(sal_Int32*& pValue1, const sal_uInt8* pValue2)
+ {
+ pValue1[0] += pValue2[0];
+ pValue1[1] += pValue2[1];
+ pValue1[2] += pValue2[2];
+ }
+
+ static inline void add(sal_Int32*& pValue1, const sal_Int32* pValue2)
+ {
+ pValue1[0] += pValue2[0];
+ pValue1[1] += pValue2[1];
+ pValue1[2] += pValue2[2];
+ }
+
+ static inline void sub(sal_Int32*& pValue1, const sal_uInt8* pValue2)
+ {
+ pValue1[0] -= pValue2[0];
+ pValue1[1] -= pValue2[1];
+ pValue1[2] -= pValue2[2];
+ }
+
+ static inline void sub(sal_Int32*& pValue1, const sal_Int32* pValue2)
+ {
+ pValue1[0] -= pValue2[0];
+ pValue1[1] -= pValue2[1];
+ pValue1[2] -= pValue2[2];
+ }
+
+ static inline void assignPtr(sal_uInt8*& pValue1, const sal_uInt8* pValue2)
+ {
+ pValue1[0] = pValue2[0];
+ pValue1[1] = pValue2[1];
+ pValue1[2] = pValue2[2];
+ }
+
+ static inline void assignMulAndShr(sal_uInt8*& result, const sal_Int32* sum, sal_Int32 multiply,
+ sal_Int32 shift)
+ {
+ result[0] = (multiply * sum[0]) >> shift;
+ result[1] = (multiply * sum[1]) >> shift;
+ result[2] = (multiply * sum[2]) >> shift;
+ }
+};
+
+struct SumFunction8
+{
+ static inline void add(sal_Int32*& pValue1, sal_Int32 nConstant) { pValue1[0] += nConstant; }
+
+ static inline void set(sal_Int32*& pValue1, sal_Int32 nConstant) { pValue1[0] = nConstant; }
+
+ static inline void add(sal_Int32*& pValue1, const sal_uInt8* pValue2)
+ {
+ pValue1[0] += pValue2[0];
+ }
+
+ static inline void add(sal_Int32*& pValue1, const sal_Int32* pValue2)
+ {
+ pValue1[0] += pValue2[0];
+ }
+
+ static inline void sub(sal_Int32*& pValue1, const sal_uInt8* pValue2)
+ {
+ pValue1[0] -= pValue2[0];
+ }
+
+ static inline void sub(sal_Int32*& pValue1, const sal_Int32* pValue2)
+ {
+ pValue1[0] -= pValue2[0];
+ }
+
+ static inline void assignPtr(sal_uInt8*& pValue1, const sal_uInt8* pValue2)
+ {
+ pValue1[0] = pValue2[0];
+ }
+
+ static inline void assignMulAndShr(sal_uInt8*& result, const sal_Int32* sum, sal_Int32 multiply,
+ sal_Int32 shift)
+ {
+ result[0] = (multiply * sum[0]) >> shift;
+ }
+};
+
+template <typename SumFunction>
+void stackBlurHorizontal(BlurSharedData const& rShared, sal_Int32 nStart, sal_Int32 nEnd)
+{
+ BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
+ BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
+
+ BlurArrays aArrays(rShared);
+
+ sal_uInt8* pStack = aArrays.maStackBuffer.data();
+ sal_uInt8* pStackPtr;
+
+ sal_Int32 nWidth = pReadAccess->Width();
+ sal_Int32 nLastIndexX = nWidth - 1;
+
+ sal_Int32 nMultiplyValue = aArrays.getMultiplyValue();
+ sal_Int32 nShiftValue = aArrays.getShiftValue();
+
+ sal_Int32 nRadius = rShared.mnRadius;
+ sal_Int32 nComponentWidth = rShared.mnComponentWidth;
+ sal_Int32 nDiv = rShared.mnDiv;
+
+ Scanline pSourcePointer;
+ Scanline pDestinationPointer;
+
+ sal_Int32 nXPosition;
+ sal_Int32 nStackIndex;
+ sal_Int32 nStackIndexStart;
+ sal_Int32 nWeight;
+
+ aArrays.initializeWeightAndPositions(nLastIndexX);
+
+ sal_Int32* nSum = aArrays.mnSumVector.data();
+ sal_Int32* nInSum = aArrays.mnInSumVector.data();
+ sal_Int32* nOutSum = aArrays.mnOutSumVector.data();
+
+ sal_Int32* pPositionPointer = aArrays.maPositionTable.data();
+ sal_Int32* pWeightPointer = aArrays.maWeightTable.data();
+
+ for (sal_Int32 y = nStart; y <= nEnd; y++)
+ {
+ SumFunction::set(nSum, 0);
+ SumFunction::set(nInSum, 0);
+ SumFunction::set(nOutSum, 0);
+
+ // Pre-initialize blur data for first pixel.
+ // aArrays.maPositionTable contains values like (for radius of 5): [0,0,0,0,0,0,1,2,3,4,5],
+ // which are used as pixels indices in the current row that we use to prepare information
+ // for the first pixel; aArrays.maWeightTable has [1,2,3,4,5,6,5,4,3,2,1]. Before looking at
+ // the first row pixel, we pretend to have processed fake previous pixels, as if the row was
+ // extended to the left with the same color as that of the first pixel.
+ for (sal_Int32 i = 0; i < nDiv; i++)
+ {
+ pSourcePointer = pReadAccess->GetScanline(y) + nComponentWidth * pPositionPointer[i];
+
+ pStackPtr = &pStack[nComponentWidth * i];
+
+ SumFunction::assignPtr(pStackPtr, pSourcePointer);
+
+ nWeight = pWeightPointer[i];
+
+ SumFunction::add(nSum, pSourcePointer[0] * nWeight);
+
+ if (i - nRadius > 0)
+ {
+ SumFunction::add(nInSum, pSourcePointer);
+ }
+ else
+ {
+ SumFunction::add(nOutSum, pSourcePointer);
+ }
+ }
+
+ nStackIndex = nRadius;
+ nXPosition = std::min(nRadius, nLastIndexX);
+
+ pSourcePointer = pReadAccess->GetScanline(y) + nComponentWidth * nXPosition;
+
+ for (sal_Int32 x = 0; x < nWidth; x++)
+ {
+ pDestinationPointer = pWriteAccess->GetScanline(y) + nComponentWidth * x;
+
+ SumFunction::assignMulAndShr(pDestinationPointer, nSum, nMultiplyValue, nShiftValue);
+
+ SumFunction::sub(nSum, nOutSum);
+
+ nStackIndexStart = nStackIndex + nDiv - nRadius;
+ if (nStackIndexStart >= nDiv)
+ {
+ nStackIndexStart -= nDiv;
+ }
+ pStackPtr = &pStack[nComponentWidth * nStackIndexStart];
+
+ SumFunction::sub(nOutSum, pStackPtr);
+
+ if (nXPosition < nLastIndexX)
+ {
+ nXPosition++;
+ pSourcePointer = pReadAccess->GetScanline(y) + nComponentWidth * nXPosition;
+ }
+
+ SumFunction::assignPtr(pStackPtr, pSourcePointer);
+
+ SumFunction::add(nInSum, pSourcePointer);
+
+ SumFunction::add(nSum, nInSum);
+
+ nStackIndex++;
+ if (nStackIndex >= nDiv)
+ {
+ nStackIndex = 0;
+ }
+
+ pStackPtr = &pStack[nStackIndex * nComponentWidth];
+
+ SumFunction::add(nOutSum, pStackPtr);
+ SumFunction::sub(nInSum, pStackPtr);
+ }
+ }
+}
+
+template <typename SumFunction>
+void stackBlurVertical(BlurSharedData const& rShared, sal_Int32 nStart, sal_Int32 nEnd)
+{
+ BitmapReadAccess* pReadAccess = rShared.mpReadAccess;
+ BitmapWriteAccess* pWriteAccess = rShared.mpWriteAccess;
+
+ BlurArrays aArrays(rShared);
+
+ sal_uInt8* pStack = aArrays.maStackBuffer.data();
+ sal_uInt8* pStackPtr;
+
+ sal_Int32 nHeight = pReadAccess->Height();
+ sal_Int32 nLastIndexY = nHeight - 1;
+
+ sal_Int32 nMultiplyValue = aArrays.getMultiplyValue();
+ sal_Int32 nShiftValue = aArrays.getShiftValue();
+
+ sal_Int32 nRadius = rShared.mnRadius;
+ sal_Int32 nComponentWidth = rShared.mnComponentWidth;
+ sal_Int32 nDiv = rShared.mnDiv;
+
+ Scanline pSourcePointer;
+ Scanline pDestinationPointer;
+
+ sal_Int32 nYPosition;
+ sal_Int32 nStackIndex;
+ sal_Int32 nStackIndexStart;
+ sal_Int32 nWeight;
+
+ aArrays.initializeWeightAndPositions(nLastIndexY);
+
+ sal_Int32* nSum = aArrays.mnSumVector.data();
+ sal_Int32* nInSum = aArrays.mnInSumVector.data();
+ sal_Int32* nOutSum = aArrays.mnOutSumVector.data();
+ sal_Int32* pPositionPointer = aArrays.maPositionTable.data();
+ sal_Int32* pWeightPointer = aArrays.maWeightTable.data();
+
+ for (sal_Int32 x = nStart; x <= nEnd; x++)
+ {
+ SumFunction::set(nSum, 0);
+ SumFunction::set(nInSum, 0);
+ SumFunction::set(nOutSum, 0);
+
+ // Pre-initialize blur data for first pixel.
+ // aArrays.maPositionTable contains values like (for radius of 5): [0,0,0,0,0,0,1,2,3,4,5],
+ // which are used as pixels indices in the current column that we use to prepare information
+ // for the first pixel; aArrays.maWeightTable has [1,2,3,4,5,6,5,4,3,2,1]. Before looking at
+ // the first column pixels, we pretend to have processed fake previous pixels, as if the
+ // column was extended to the top with the same color as that of the first pixel.
+ for (sal_Int32 i = 0; i < nDiv; i++)
+ {
+ pSourcePointer = pReadAccess->GetScanline(pPositionPointer[i]) + nComponentWidth * x;
+
+ pStackPtr = &pStack[nComponentWidth * i];
+
+ SumFunction::assignPtr(pStackPtr, pSourcePointer);
+
+ nWeight = pWeightPointer[i];
+
+ SumFunction::add(nSum, pSourcePointer[0] * nWeight);
+
+ if (i - nRadius > 0)
+ {
+ SumFunction::add(nInSum, pSourcePointer);
+ }
+ else
+ {
+ SumFunction::add(nOutSum, pSourcePointer);
+ }
+ }
+
+ nStackIndex = nRadius;
+ nYPosition = std::min(nRadius, nLastIndexY);
+
+ pSourcePointer = pReadAccess->GetScanline(nYPosition) + nComponentWidth * x;
+
+ for (sal_Int32 y = 0; y < nHeight; y++)
+ {
+ pDestinationPointer = pWriteAccess->GetScanline(y) + nComponentWidth * x;
+
+ SumFunction::assignMulAndShr(pDestinationPointer, nSum, nMultiplyValue, nShiftValue);
+
+ SumFunction::sub(nSum, nOutSum);
+
+ nStackIndexStart = nStackIndex + nDiv - nRadius;
+ if (nStackIndexStart >= nDiv)
+ {
+ nStackIndexStart -= nDiv;
+ }
+ pStackPtr = &pStack[nComponentWidth * nStackIndexStart];
+
+ SumFunction::sub(nOutSum, pStackPtr);
+
+ if (nYPosition < nLastIndexY)
+ {
+ nYPosition++;
+ pSourcePointer = pReadAccess->GetScanline(nYPosition) + nComponentWidth * x;
+ }
+
+ SumFunction::assignPtr(pStackPtr, pSourcePointer);
+ SumFunction::add(nInSum, pSourcePointer);
+ SumFunction::add(nSum, nInSum);
+
+ nStackIndex++;
+ if (nStackIndex >= nDiv)
+ {
+ nStackIndex = 0;
+ }
+
+ pStackPtr = &pStack[nStackIndex * nComponentWidth];
+
+ SumFunction::add(nOutSum, pStackPtr);
+ SumFunction::sub(nInSum, pStackPtr);
+ }
+ }
+}
+
+constexpr sal_Int32 nThreadStrip = 16;
+
+void runStackBlur(Bitmap& rBitmap, const sal_Int32 nRadius, const sal_Int32 nComponentWidth,
+ const sal_Int32 nColorChannels, BlurRangeFn pBlurHorizontalFn,
+ BlurRangeFn pBlurVerticalFn, const bool bParallel)
+{
+ if (bParallel)
+ {
+ try
+ {
+ comphelper::ThreadPool& rShared = comphelper::ThreadPool::getSharedOptimalPool();
+ auto pTag = comphelper::ThreadPool::createThreadTaskTag();
+
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius,
+ nComponentWidth, nColorChannels);
+
+ const sal_Int32 nFirstIndex = 0;
+ const sal_Int32 nLastIndex = pReadAccess->Height() - 1;
+
+ vcl::bitmap::generateStripRanges<nThreadStrip>(
+ nFirstIndex, nLastIndex,
+ [&](sal_Int32 const nStart, sal_Int32 const nEnd, bool const bLast) {
+ if (!bLast)
+ {
+ auto pTask(std::make_unique<BlurTask>(pTag, pBlurHorizontalFn,
+ aSharedData, nStart, nEnd));
+ rShared.pushTask(std::move(pTask));
+ }
+ else
+ pBlurHorizontalFn(aSharedData, nStart, nEnd);
+ });
+ rShared.waitUntilDone(pTag);
+ }
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius,
+ nComponentWidth, nColorChannels);
+
+ const sal_Int32 nFirstIndex = 0;
+ const sal_Int32 nLastIndex = pReadAccess->Width() - 1;
+
+ vcl::bitmap::generateStripRanges<nThreadStrip>(
+ nFirstIndex, nLastIndex,
+ [&](sal_Int32 const nStart, sal_Int32 const nEnd, bool const bLast) {
+ if (!bLast)
+ {
+ auto pTask(std::make_unique<BlurTask>(pTag, pBlurVerticalFn,
+ aSharedData, nStart, nEnd));
+ rShared.pushTask(std::move(pTask));
+ }
+ else
+ pBlurVerticalFn(aSharedData, nStart, nEnd);
+ });
+
+ rShared.waitUntilDone(pTag);
+ }
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl.gdi", "threaded bitmap blurring failed");
+ }
+ }
+ else
+ {
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius,
+ nComponentWidth, nColorChannels);
+ sal_Int32 nFirstIndex = 0;
+ sal_Int32 nLastIndex = pReadAccess->Height() - 1;
+ pBlurHorizontalFn(aSharedData, nFirstIndex, nLastIndex);
+ }
+ {
+ Bitmap::ScopedReadAccess pReadAccess(rBitmap);
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ BlurSharedData aSharedData(pReadAccess.get(), pWriteAccess.get(), nRadius,
+ nComponentWidth, nColorChannels);
+ sal_Int32 nFirstIndex = 0;
+ sal_Int32 nLastIndex = pReadAccess->Width() - 1;
+ pBlurVerticalFn(aSharedData, nFirstIndex, nLastIndex);
+ }
+ }
+}
+
+void stackBlur24(Bitmap& rBitmap, sal_Int32 nRadius, sal_Int32 nComponentWidth)
+{
+ const bool bParallel = true;
+ // Limit radius
+ nRadius = std::clamp<sal_Int32>(nRadius, 2, 254);
+ const sal_Int32 nColorChannels = 3; // 3 color channel
+
+ BlurRangeFn pBlurHorizontalFn = stackBlurHorizontal<SumFunction24>;
+ BlurRangeFn pBlurVerticalFn = stackBlurVertical<SumFunction24>;
+
+ runStackBlur(rBitmap, nRadius, nComponentWidth, nColorChannels, pBlurHorizontalFn,
+ pBlurVerticalFn, bParallel);
+}
+
+void stackBlur8(Bitmap& rBitmap, sal_Int32 nRadius, sal_Int32 nComponentWidth)
+{
+ const bool bParallel = true;
+ // Limit radius
+ nRadius = std::clamp<sal_Int32>(nRadius, 2, 254);
+ const sal_Int32 nColorChannels = 1; // 1 color channel
+
+ BlurRangeFn pBlurHorizontalFn = stackBlurHorizontal<SumFunction8>;
+ BlurRangeFn pBlurVerticalFn = stackBlurVertical<SumFunction8>;
+
+ runStackBlur(rBitmap, nRadius, nComponentWidth, nColorChannels, pBlurHorizontalFn,
+ pBlurVerticalFn, bParallel);
+}
+
+} // end anonymous namespace
+
+/**
+ * Implementation of stack blur - a fast Gaussian blur approximation.
+ * nRadius - blur radius, valid values are between 2 and 254
+ * bExtend - extend the bitmap in all directions by the radius
+ *
+ * Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>
+ * (http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html)
+ *
+ * Additionally references and implementations:
+ * - Blur.js by Jacob Kelley
+ * (http://www.blurjs.com)
+ * - BlurEffectForAndroidDesign by Nicolas Pomepuy
+ * (https://github.com/PomepuyN/BlurEffectForAndroidDesign)
+ * - StackBluriOS by Thomas Landspurg
+ * (https://github.com/tomsoft1/StackBluriOS)
+ * - stackblur.cpp by Benjamin Yates
+ * (https://gist.github.com/benjamin9999/3809142)
+ * - stack blur in fog 2D graphic library by Petr Kobalicek
+ * (https://code.google.com/p/fog/)
+ *
+ */
+BitmapFilterStackBlur::BitmapFilterStackBlur(sal_Int32 nRadius)
+ : mnRadius(nRadius)
+{
+}
+
+BitmapFilterStackBlur::~BitmapFilterStackBlur() {}
+
+BitmapEx BitmapFilterStackBlur::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap = rBitmapEx.GetBitmap();
+ Bitmap result = filter(aBitmap);
+ return BitmapEx(result, rBitmapEx.GetAlpha());
+}
+
+Bitmap BitmapFilterStackBlur::filter(Bitmap const& rBitmap) const
+{
+ Bitmap bitmapCopy(rBitmap);
+ ScanlineFormat nScanlineFormat;
+ {
+ Bitmap::ScopedReadAccess pReadAccess(bitmapCopy);
+ nScanlineFormat = pReadAccess ? pReadAccess->GetScanlineFormat() : ScanlineFormat::NONE;
+ }
+
+ if (nScanlineFormat == ScanlineFormat::N24BitTcRgb
+ || nScanlineFormat == ScanlineFormat::N24BitTcBgr
+ || nScanlineFormat == ScanlineFormat::N32BitTcMask
+ || nScanlineFormat == ScanlineFormat::N32BitTcBgra)
+ {
+ int nComponentWidth = (nScanlineFormat == ScanlineFormat::N32BitTcMask
+ || nScanlineFormat == ScanlineFormat::N32BitTcBgra)
+ ? 4
+ : 3;
+
+ stackBlur24(bitmapCopy, mnRadius, nComponentWidth);
+ }
+ else if (nScanlineFormat == ScanlineFormat::N8BitPal)
+ {
+ int nComponentWidth = 1;
+
+ stackBlur8(bitmapCopy, mnRadius, nComponentWidth);
+ }
+
+ return bitmapCopy;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapGaussianSeparableBlurFilter.cxx b/vcl/source/bitmap/BitmapGaussianSeparableBlurFilter.cxx
new file mode 100644
index 000000000..85c790422
--- /dev/null
+++ b/vcl/source/bitmap/BitmapGaussianSeparableBlurFilter.cxx
@@ -0,0 +1,215 @@
+/* -*- 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/.
+ *
+ */
+
+#include <tools/helpers.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapGaussianSeparableBlurFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapGaussianSeparableBlurFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const sal_Int32 nWidth = aBitmap.GetSizePixel().Width();
+ const sal_Int32 nHeight = aBitmap.GetSizePixel().Height();
+
+ // Prepare Blur Vector
+ int aNumberOfContributions;
+ std::vector<double> aBlurVector(makeBlurKernel(mfRadius, aNumberOfContributions));
+ std::vector<double> aWeights;
+ std::vector<int> aPixels;
+ std::vector<int> aCounts;
+
+ // Do horizontal filtering
+ blurContributions(nWidth, aNumberOfContributions, aBlurVector, aWeights, aPixels, aCounts);
+
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+
+ // switch coordinates as convolution pass transposes result
+ Bitmap aNewBitmap(Size(nHeight, nWidth), vcl::PixelFormat::N24_BPP);
+
+ bool bResult = convolutionPass(aBitmap, aNewBitmap, pReadAcc.get(), aNumberOfContributions,
+ aWeights.data(), aPixels.data(), aCounts.data());
+
+ // Cleanup
+ pReadAcc.reset();
+ aWeights.clear();
+ aPixels.clear();
+ aCounts.clear();
+
+ if (!bResult)
+ {
+ aBlurVector.clear();
+ }
+ else
+ {
+ // Swap current bitmap with new bitmap
+ aBitmap.ReassignWithSize(aNewBitmap);
+
+ // Do vertical filtering
+ blurContributions(nHeight, aNumberOfContributions, aBlurVector, aWeights, aPixels, aCounts);
+
+ pReadAcc = Bitmap::ScopedReadAccess(aBitmap);
+ aNewBitmap = Bitmap(Size(nWidth, nHeight), vcl::PixelFormat::N24_BPP);
+ bResult = convolutionPass(aBitmap, aNewBitmap, pReadAcc.get(), aNumberOfContributions,
+ aWeights.data(), aPixels.data(), aCounts.data());
+
+ // Cleanup
+ pReadAcc.reset();
+ aWeights.clear();
+ aCounts.clear();
+ aPixels.clear();
+ aBlurVector.clear();
+
+ if (bResult)
+ aBitmap.ReassignWithSize(aNewBitmap); // swap current bitmap with new bitmap
+ }
+
+ if (bResult)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+bool BitmapGaussianSeparableBlurFilter::convolutionPass(const Bitmap& rBitmap, Bitmap& aNewBitmap,
+ BitmapReadAccess const* pReadAcc,
+ int aNumberOfContributions,
+ const double* pWeights, int const* pPixels,
+ const int* pCount)
+{
+ if (!pReadAcc)
+ return false;
+
+ BitmapScopedWriteAccess pWriteAcc(aNewBitmap);
+ if (!pWriteAcc)
+ return false;
+
+ const int nHeight = rBitmap.GetSizePixel().Height();
+ assert(rBitmap.GetSizePixel().Height() == aNewBitmap.GetSizePixel().Width());
+ const int nWidth = rBitmap.GetSizePixel().Width();
+ assert(rBitmap.GetSizePixel().Width() == aNewBitmap.GetSizePixel().Height());
+
+ BitmapColor aColor;
+ double aValueRed, aValueGreen, aValueBlue;
+ double aSum, aWeight;
+ int aBaseIndex, aIndex;
+
+ for (int nSourceY = 0; nSourceY < nHeight; ++nSourceY)
+ {
+ for (int nSourceX = 0; nSourceX < nWidth; ++nSourceX)
+ {
+ aBaseIndex = nSourceX * aNumberOfContributions;
+ aSum = aValueRed = aValueGreen = aValueBlue = 0.0;
+
+ for (int j = 0; j < pCount[nSourceX]; ++j)
+ {
+ aIndex = aBaseIndex + j;
+ aWeight = pWeights[aIndex];
+ aSum += aWeight;
+
+ aColor = pReadAcc->GetColor(nSourceY, pPixels[aIndex]);
+
+ aValueRed += aWeight * aColor.GetRed();
+ aValueGreen += aWeight * aColor.GetGreen();
+ aValueBlue += aWeight * aColor.GetBlue();
+ }
+
+ BitmapColor aResultColor(static_cast<sal_uInt8>(MinMax(aValueRed / aSum, 0, 255)),
+ static_cast<sal_uInt8>(MinMax(aValueGreen / aSum, 0, 255)),
+ static_cast<sal_uInt8>(MinMax(aValueBlue / aSum, 0, 255)));
+
+ int nDestX = nSourceY;
+ int nDestY = nSourceX;
+
+ pWriteAcc->SetPixel(nDestY, nDestX, aResultColor);
+ }
+ }
+ return true;
+}
+
+std::vector<double> BitmapGaussianSeparableBlurFilter::makeBlurKernel(const double radius,
+ int& rows)
+{
+ int intRadius = static_cast<int>(radius + 1.0);
+ rows = intRadius * 2 + 1;
+ std::vector<double> matrix(rows);
+
+ double sigma = radius / 3;
+ double radius2 = radius * radius;
+ int index = 0;
+ for (int row = -intRadius; row <= intRadius; row++)
+ {
+ double distance = row * row;
+ if (distance > radius2)
+ {
+ matrix[index] = 0.0;
+ }
+ else
+ {
+ matrix[index] = exp(-distance / (2.0 * sigma * sigma)) / sqrt(2.0 * M_PI * sigma);
+ }
+ index++;
+ }
+ return matrix;
+}
+
+void BitmapGaussianSeparableBlurFilter::blurContributions(
+ const int aSize, const int aNumberOfContributions, const std::vector<double>& rBlurVector,
+ std::vector<double>& rWeights, std::vector<int>& rPixels, std::vector<int>& rCounts)
+{
+ rWeights.resize(aSize * aNumberOfContributions);
+ rPixels.resize(aSize * aNumberOfContributions);
+ rCounts.resize(aSize);
+
+ int aLeft, aRight, aCurrentCount, aPixelIndex;
+ double aWeight;
+
+ for (int i = 0; i < aSize; i++)
+ {
+ aLeft = i - aNumberOfContributions / 2;
+ aRight = i + aNumberOfContributions / 2;
+ aCurrentCount = 0;
+ for (int j = aLeft; j <= aRight; j++)
+ {
+ aWeight = rBlurVector[aCurrentCount];
+
+ // Mirror edges
+ if (j < 0)
+ {
+ aPixelIndex = -j;
+ }
+ else if (j >= aSize)
+ {
+ aPixelIndex = (aSize - j) + aSize - 1;
+ }
+ else
+ {
+ aPixelIndex = j;
+ }
+
+ // Edge case for small bitmaps
+ if (aPixelIndex < 0 || aPixelIndex >= aSize)
+ {
+ aWeight = 0.0;
+ }
+
+ rWeights[i * aNumberOfContributions + aCurrentCount] = aWeight;
+ rPixels[i * aNumberOfContributions + aCurrentCount] = aPixelIndex;
+
+ aCurrentCount++;
+ }
+ rCounts[i] = aCurrentCount;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapInfoAccess.cxx b/vcl/source/bitmap/BitmapInfoAccess.cxx
new file mode 100644
index 000000000..50607e94d
--- /dev/null
+++ b/vcl/source/bitmap/BitmapInfoAccess.cxx
@@ -0,0 +1,80 @@
+/* -*- 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 <vcl/BitmapInfoAccess.hxx>
+
+#include <salbmp.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+BitmapInfoAccess::BitmapInfoAccess(Bitmap& rBitmap, BitmapAccessMode nMode)
+ : mpBuffer(nullptr)
+ , mnAccessMode(nMode)
+{
+ std::shared_ptr<SalBitmap> xImpBmp = rBitmap.ImplGetSalBitmap();
+
+ if (!xImpBmp)
+ return;
+
+ if (mnAccessMode == BitmapAccessMode::Write)
+ {
+ xImpBmp->DropScaledCache();
+
+ if (xImpBmp.use_count() > 2)
+ {
+ xImpBmp.reset();
+ rBitmap.ImplMakeUnique();
+ xImpBmp = rBitmap.ImplGetSalBitmap();
+ }
+ }
+
+ mpBuffer = xImpBmp->AcquireBuffer(mnAccessMode);
+
+ if (!mpBuffer)
+ {
+ std::shared_ptr<SalBitmap> xNewImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xNewImpBmp->Create(*xImpBmp, rBitmap.getPixelFormat()))
+ {
+ xImpBmp = xNewImpBmp;
+ rBitmap.ImplSetSalBitmap(xImpBmp);
+ mpBuffer = xImpBmp->AcquireBuffer(mnAccessMode);
+ }
+ }
+
+ maBitmap = rBitmap;
+}
+
+BitmapInfoAccess::~BitmapInfoAccess()
+{
+ std::shared_ptr<SalBitmap> xImpBmp = maBitmap.ImplGetSalBitmap();
+
+ if (mpBuffer && xImpBmp)
+ {
+ xImpBmp->ReleaseBuffer(mpBuffer, mnAccessMode);
+ }
+}
+
+sal_uInt16 BitmapInfoAccess::GetBestPaletteIndex(const BitmapColor& rBitmapColor) const
+{
+ const BitmapBuffer* pBuffer = mpBuffer;
+
+ return (HasPalette() ? pBuffer->maPalette.GetBestIndex(rBitmapColor) : 0);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx b/vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx
new file mode 100644
index 000000000..ce32f2512
--- /dev/null
+++ b/vcl/source/bitmap/BitmapInterpolateScaleFilter.cxx
@@ -0,0 +1,243 @@
+/* -*- 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 <tools/helpers.hxx>
+#include <osl/diagnose.h>
+
+#include <vcl/bitmapex.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapFastScaleFilter.hxx>
+#include <bitmap/BitmapInterpolateScaleFilter.hxx>
+
+BitmapEx BitmapInterpolateScaleFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const Size aSizePix(aBitmap.GetSizePixel());
+ const sal_Int32 nNewWidth = FRound(aSizePix.Width() * mfScaleX);
+ const sal_Int32 nNewHeight = FRound(aSizePix.Height() * mfScaleY);
+ bool bRet = false;
+
+ if ((nNewWidth > 1) && (nNewHeight > 1))
+ {
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+ if (pReadAcc)
+ {
+ sal_Int32 nWidth = pReadAcc->Width();
+ sal_Int32 nHeight = pReadAcc->Height();
+ Bitmap aNewBmp(Size(nNewWidth, nHeight), vcl::PixelFormat::N24_BPP);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ if (1 == nWidth)
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ BitmapColor aCol0;
+ if (pReadAcc->HasPalette())
+ {
+ aCol0 = pReadAcc->GetPaletteColor(
+ pReadAcc->GetIndexFromData(pScanlineRead, 0));
+ }
+ else
+ {
+ aCol0 = pReadAcc->GetPixelFromData(pScanlineRead, 0);
+ }
+
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nNewWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol0);
+ }
+ }
+ }
+ else
+ {
+ const sal_Int32 nNewWidth1 = nNewWidth - 1;
+ const sal_Int32 nWidth1 = pReadAcc->Width() - 1;
+ const double fRevScaleX = static_cast<double>(nWidth1) / nNewWidth1;
+
+ std::unique_ptr<sal_Int32[]> pLutInt(new sal_Int32[nNewWidth]);
+ std::unique_ptr<sal_Int32[]> pLutFrac(new sal_Int32[nNewWidth]);
+
+ for (sal_Int32 nX = 0, nTemp = nWidth - 2; nX < nNewWidth; nX++)
+ {
+ double fTemp = nX * fRevScaleX;
+ pLutInt[nX] = MinMax(static_cast<sal_Int32>(fTemp), 0, nTemp);
+ fTemp -= pLutInt[nX];
+ pLutFrac[nX] = static_cast<sal_Int32>(fTemp * 1024.);
+ }
+
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nNewWidth; nX++)
+ {
+ sal_Int32 nTemp = pLutInt[nX];
+
+ BitmapColor aCol0, aCol1;
+ if (pReadAcc->HasPalette())
+ {
+ aCol0 = pReadAcc->GetPaletteColor(
+ pReadAcc->GetIndexFromData(pScanlineRead, nTemp++));
+ aCol1 = pReadAcc->GetPaletteColor(
+ pReadAcc->GetIndexFromData(pScanlineRead, nTemp));
+ }
+ else
+ {
+ aCol0 = pReadAcc->GetPixelFromData(pScanlineRead, nTemp++);
+ aCol1 = pReadAcc->GetPixelFromData(pScanlineRead, nTemp);
+ }
+
+ nTemp = pLutFrac[nX];
+
+ sal_Int32 lXR0 = aCol0.GetRed();
+ sal_Int32 lXG0 = aCol0.GetGreen();
+ sal_Int32 lXB0 = aCol0.GetBlue();
+ sal_Int32 lXR1 = aCol1.GetRed() - lXR0;
+ sal_Int32 lXG1 = aCol1.GetGreen() - lXG0;
+ sal_Int32 lXB1 = aCol1.GetBlue() - lXB0;
+
+ aCol0.SetRed(
+ static_cast<sal_uInt8>((lXR1 * nTemp + (lXR0 << 10)) >> 10));
+ aCol0.SetGreen(
+ static_cast<sal_uInt8>((lXG1 * nTemp + (lXG0 << 10)) >> 10));
+ aCol0.SetBlue(
+ static_cast<sal_uInt8>((lXB1 * nTemp + (lXB0 << 10)) >> 10));
+
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol0);
+ }
+ }
+ }
+
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+ pWriteAcc.reset();
+
+ if (bRet)
+ {
+ bRet = false;
+ const Bitmap aOriginal(aBitmap);
+ aBitmap = aNewBmp;
+ aNewBmp = Bitmap(Size(nNewWidth, nNewHeight), vcl::PixelFormat::N24_BPP);
+ pReadAcc = Bitmap::ScopedReadAccess(aBitmap);
+ pWriteAcc = BitmapScopedWriteAccess(aNewBmp);
+
+ if (pReadAcc && pWriteAcc)
+ {
+ // after 1st step, bitmap *is* 24bit format (see above)
+ OSL_ENSURE(!pReadAcc->HasPalette(), "OOps, somehow ImplScaleInterpolate "
+ "in-between format has palette, should not "
+ "happen (!)");
+
+ if (1 == nHeight)
+ {
+ for (sal_Int32 nX = 0; nX < nNewWidth; nX++)
+ {
+ BitmapColor aCol0 = pReadAcc->GetPixel(0, nX);
+
+ for (sal_Int32 nY = 0; nY < nNewHeight; nY++)
+ {
+ pWriteAcc->SetPixel(nY, nX, aCol0);
+ }
+ }
+ }
+ else
+ {
+ const sal_Int32 nNewHeight1 = nNewHeight - 1;
+ const sal_Int32 nHeight1 = pReadAcc->Height() - 1;
+ const double fRevScaleY = static_cast<double>(nHeight1) / nNewHeight1;
+
+ std::unique_ptr<sal_Int32[]> pLutInt(new sal_Int32[nNewHeight]);
+ std::unique_ptr<sal_Int32[]> pLutFrac(new sal_Int32[nNewHeight]);
+
+ for (sal_Int32 nY = 0, nTemp = nHeight - 2; nY < nNewHeight; nY++)
+ {
+ double fTemp = nY * fRevScaleY;
+ pLutInt[nY] = MinMax(static_cast<sal_Int32>(fTemp), 0, nTemp);
+ fTemp -= pLutInt[nY];
+ pLutFrac[nY] = static_cast<sal_Int32>(fTemp * 1024.);
+ }
+
+ for (sal_Int32 nX = 0; nX < nNewWidth; nX++)
+ {
+ for (sal_Int32 nY = 0; nY < nNewHeight; nY++)
+ {
+ sal_Int32 nTemp = pLutInt[nY];
+
+ BitmapColor aCol0 = pReadAcc->GetPixel(nTemp++, nX);
+ BitmapColor aCol1 = pReadAcc->GetPixel(nTemp, nX);
+
+ nTemp = pLutFrac[nY];
+
+ sal_Int32 lXR0 = aCol0.GetRed();
+ sal_Int32 lXG0 = aCol0.GetGreen();
+ sal_Int32 lXB0 = aCol0.GetBlue();
+ sal_Int32 lXR1 = aCol1.GetRed() - lXR0;
+ sal_Int32 lXG1 = aCol1.GetGreen() - lXG0;
+ sal_Int32 lXB1 = aCol1.GetBlue() - lXB0;
+
+ aCol0.SetRed(
+ static_cast<sal_uInt8>((lXR1 * nTemp + (lXR0 << 10)) >> 10));
+ aCol0.SetGreen(
+ static_cast<sal_uInt8>((lXG1 * nTemp + (lXG0 << 10)) >> 10));
+ aCol0.SetBlue(
+ static_cast<sal_uInt8>((lXB1 * nTemp + (lXB0 << 10)) >> 10));
+
+ pWriteAcc->SetPixel(nY, nX, aCol0);
+ }
+ }
+ }
+
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+ pWriteAcc.reset();
+
+ if (bRet)
+ {
+ aOriginal.AdaptBitCount(aNewBmp);
+ aBitmap = aNewBmp;
+ }
+ }
+ }
+ }
+
+ if (!bRet)
+ {
+ // fallback to fast scale filter
+ BitmapEx aBmpEx(aBitmap);
+ bRet = BitmapFilter::Filter(aBmpEx, BitmapFastScaleFilter(mfScaleX, mfScaleY));
+ aBitmap = aBmpEx.GetBitmap();
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapLightenFilter.cxx b/vcl/source/bitmap/BitmapLightenFilter.cxx
new file mode 100644
index 000000000..47fa49a70
--- /dev/null
+++ b/vcl/source/bitmap/BitmapLightenFilter.cxx
@@ -0,0 +1,67 @@
+/* -*- 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/.
+ *
+ */
+
+#include <basegfx/color/bcolortools.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapLightenFilter.hxx>
+
+BitmapEx BitmapLightenFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ const Size aSize(rBitmapEx.GetSizePixel());
+
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+ Bitmap aDarkBitmap(aSize, vcl::PixelFormat::N24_BPP);
+
+ Bitmap::ScopedReadAccess pRead(aBitmap);
+ BitmapScopedWriteAccess pWrite(aDarkBitmap);
+
+ if (pRead && pWrite)
+ {
+ for (sal_Int32 nY = 0; nY < sal_Int32(aSize.Height()); ++nY)
+ {
+ Scanline pScanline = pWrite->GetScanline(nY);
+ Scanline pScanlineRead = pRead->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < sal_Int32(aSize.Width()); ++nX)
+ {
+ BitmapColor aBmpColor
+ = pRead->HasPalette()
+ ? pRead->GetPaletteColor(pRead->GetIndexFromData(pScanlineRead, nX))
+ : pRead->GetPixelFromData(pScanlineRead, nX);
+ aBmpColor.Invert();
+ basegfx::BColor aBColor(aBmpColor.getBColor());
+ aBColor = basegfx::utils::rgb2hsl(aBColor);
+
+ double fHue = aBColor.getRed();
+ fHue += 180.0;
+
+ while (fHue > 360.0)
+ {
+ fHue -= 360.0;
+ }
+
+ aBColor.setRed(fHue);
+
+ aBColor = basegfx::utils::hsl2rgb(aBColor);
+ aBmpColor.SetRed((aBColor.getRed() * 255.0) + 0.5);
+ aBmpColor.SetGreen((aBColor.getGreen() * 255.0) + 0.5);
+ aBmpColor.SetBlue((aBColor.getBlue() * 255.0) + 0.5);
+
+ pWrite->SetPixelOnData(pScanline, nX, aBmpColor);
+ }
+ }
+ }
+ pWrite.reset();
+ pRead.reset();
+
+ return BitmapEx(aDarkBitmap, rBitmapEx.GetAlpha());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapMaskToAlphaFilter.cxx b/vcl/source/bitmap/BitmapMaskToAlphaFilter.cxx
new file mode 100644
index 000000000..c9ec102d9
--- /dev/null
+++ b/vcl/source/bitmap/BitmapMaskToAlphaFilter.cxx
@@ -0,0 +1,58 @@
+/* -*- 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/.
+ *
+ */
+
+#include <basegfx/color/bcolortools.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapMaskToAlphaFilter.hxx>
+
+/**
+ * Convert a 1-bit mask to an alpha layer
+ */
+BitmapEx BitmapMaskToAlphaFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ const Size aSize(rBitmapEx.GetSizePixel());
+
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+ Bitmap aOutBitmap(aSize, vcl::PixelFormat::N8_BPP, &Bitmap::GetGreyPalette(256));
+
+ Bitmap::ScopedReadAccess pRead(aBitmap);
+ BitmapScopedWriteAccess pWrite(aOutBitmap);
+
+ if (pRead && pWrite)
+ {
+ assert(pRead->HasPalette() && "only supposed to be called with 1-bit mask");
+ assert(pRead->GetPaletteEntryCount() == 2);
+ for (sal_Int32 nY = 0; nY < sal_Int32(aSize.Height()); ++nY)
+ {
+ Scanline pScanline = pWrite->GetScanline(nY);
+ Scanline pScanlineRead = pRead->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < sal_Int32(aSize.Width()); ++nX)
+ {
+ BitmapColor aBmpColor = pRead->GetPixelFromData(pScanlineRead, nX);
+ if (aBmpColor == COL_BLACK)
+ aBmpColor = COL_BLACK;
+ else if (aBmpColor == COL_WHITE)
+ aBmpColor = COL_WHITE;
+ else if (aBmpColor == Color(0, 0, 1))
+ aBmpColor = COL_WHITE;
+ else
+ assert(false);
+ pWrite->SetPixelOnData(pScanline, nX, aBmpColor);
+ }
+ }
+ }
+ pWrite.reset();
+ pRead.reset();
+
+ return BitmapEx(aOutBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapMedianFilter.cxx b/vcl/source/bitmap/BitmapMedianFilter.cxx
new file mode 100644
index 000000000..567bc6ef2
--- /dev/null
+++ b/vcl/source/bitmap/BitmapMedianFilter.cxx
@@ -0,0 +1,217 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapMedianFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+#define S2(a, b) \
+ { \
+ sal_Int32 t; \
+ if ((t = b - a) < 0) \
+ { \
+ a += t; \
+ b -= t; \
+ } \
+ }
+#define MN3(a, b, c) \
+ S2(a, b); \
+ S2(a, c);
+#define MX3(a, b, c) \
+ S2(b, c); \
+ S2(a, c);
+#define MNMX3(a, b, c) \
+ MX3(a, b, c); \
+ S2(a, b);
+#define MNMX4(a, b, c, d) \
+ S2(a, b); \
+ S2(c, d); \
+ S2(a, c); \
+ S2(b, d);
+#define MNMX5(a, b, c, d, e) \
+ S2(a, b); \
+ S2(c, d); \
+ MN3(a, c, e); \
+ MX3(b, d, e);
+#define MNMX6(a, b, c, d, e, f) \
+ S2(a, d); \
+ S2(b, e); \
+ S2(c, f); \
+ MN3(a, b, c); \
+ MX3(d, e, f);
+
+BitmapEx BitmapMedianFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+ bool bRet = false;
+
+ if (pReadAcc)
+ {
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const sal_Int32 nWidth = pWriteAcc->Width(), nWidth2 = nWidth + 2;
+ const sal_Int32 nHeight = pWriteAcc->Height(), nHeight2 = nHeight + 2;
+ std::unique_ptr<sal_Int32[]> pColm(new sal_Int32[nWidth2]);
+ std::unique_ptr<sal_Int32[]> pRows(new sal_Int32[nHeight2]);
+ std::unique_ptr<BitmapColor[]> pColRow1(new BitmapColor[nWidth2]);
+ std::unique_ptr<BitmapColor[]> pColRow2(new BitmapColor[nWidth2]);
+ std::unique_ptr<BitmapColor[]> pColRow3(new BitmapColor[nWidth2]);
+ BitmapColor* pRowTmp1 = pColRow1.get();
+ BitmapColor* pRowTmp2 = pColRow2.get();
+ BitmapColor* pRowTmp3 = pColRow3.get();
+ BitmapColor* pColor;
+ sal_Int32 nY, nX, i;
+ sal_Int32 nR1, nR2, nR3, nR4, nR5, nR6, nR7, nR8, nR9;
+ sal_Int32 nG1, nG2, nG3, nG4, nG5, nG6, nG7, nG8, nG9;
+ sal_Int32 nB1, nB2, nB3, nB4, nB5, nB6, nB7, nB8, nB9;
+
+ // create column LUT
+ for (i = 0; i < nWidth2; i++)
+ pColm[i] = (i > 0) ? (i - 1) : 0;
+
+ pColm[nWidth + 1] = pColm[nWidth];
+
+ // create row LUT
+ for (i = 0; i < nHeight2; i++)
+ pRows[i] = (i > 0) ? (i - 1) : 0;
+
+ pRows[nHeight + 1] = pRows[nHeight];
+
+ // read first three rows of bitmap color
+ if (nHeight2 > 2)
+ {
+ for (i = 0; i < nWidth2; i++)
+ {
+ pColRow1[i] = pReadAcc->GetColor(pRows[0], pColm[i]);
+ pColRow2[i] = pReadAcc->GetColor(pRows[1], pColm[i]);
+ pColRow3[i] = pReadAcc->GetColor(pRows[2], pColm[i]);
+ }
+ }
+
+ // do median filtering
+ for (nY = 0; nY < nHeight;)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (nX = 0; nX < nWidth; nX++)
+ {
+ pColor = pRowTmp1 + nX;
+ nR1 = pColor->GetRed();
+ nG1 = pColor->GetGreen();
+ nB1 = pColor->GetBlue();
+ nR2 = (++pColor)->GetRed();
+ nG2 = pColor->GetGreen();
+ nB2 = pColor->GetBlue();
+ nR3 = (++pColor)->GetRed();
+ nG3 = pColor->GetGreen();
+ nB3 = pColor->GetBlue();
+
+ pColor = pRowTmp2 + nX;
+ nR4 = pColor->GetRed();
+ nG4 = pColor->GetGreen();
+ nB4 = pColor->GetBlue();
+ nR5 = (++pColor)->GetRed();
+ nG5 = pColor->GetGreen();
+ nB5 = pColor->GetBlue();
+ nR6 = (++pColor)->GetRed();
+ nG6 = pColor->GetGreen();
+ nB6 = pColor->GetBlue();
+
+ pColor = pRowTmp3 + nX;
+ nR7 = pColor->GetRed();
+ nG7 = pColor->GetGreen();
+ nB7 = pColor->GetBlue();
+ nR8 = (++pColor)->GetRed();
+ nG8 = pColor->GetGreen();
+ nB8 = pColor->GetBlue();
+ nR9 = (++pColor)->GetRed();
+ nG9 = pColor->GetGreen();
+ nB9 = pColor->GetBlue();
+
+ MNMX6(nR1, nR2, nR3, nR4, nR5, nR6);
+ MNMX5(nR7, nR2, nR3, nR4, nR5);
+ MNMX4(nR8, nR2, nR3, nR4);
+ MNMX3(nR9, nR2, nR3);
+
+ MNMX6(nG1, nG2, nG3, nG4, nG5, nG6);
+ MNMX5(nG7, nG2, nG3, nG4, nG5);
+ MNMX4(nG8, nG2, nG3, nG4);
+ MNMX3(nG9, nG2, nG3);
+
+ MNMX6(nB1, nB2, nB3, nB4, nB5, nB6);
+ MNMX5(nB7, nB2, nB3, nB4, nB5);
+ MNMX4(nB8, nB2, nB3, nB4);
+ MNMX3(nB9, nB2, nB3);
+
+ // set destination color
+ pWriteAcc->SetPixelOnData(pScanline, nX,
+ BitmapColor(static_cast<sal_uInt8>(nR2),
+ static_cast<sal_uInt8>(nG2),
+ static_cast<sal_uInt8>(nB2)));
+ }
+
+ if (++nY < nHeight)
+ {
+ if (pRowTmp1 == pColRow1.get())
+ {
+ pRowTmp1 = pColRow2.get();
+ pRowTmp2 = pColRow3.get();
+ pRowTmp3 = pColRow1.get();
+ }
+ else if (pRowTmp1 == pColRow2.get())
+ {
+ pRowTmp1 = pColRow3.get();
+ pRowTmp2 = pColRow1.get();
+ pRowTmp3 = pColRow2.get();
+ }
+ else
+ {
+ pRowTmp1 = pColRow1.get();
+ pRowTmp2 = pColRow2.get();
+ pRowTmp3 = pColRow3.get();
+ }
+
+ for (i = 0; i < nWidth2; i++)
+ pRowTmp3[i] = pReadAcc->GetColor(pRows[nY + 2], pColm[i]);
+ }
+ }
+
+ pWriteAcc.reset();
+
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapMonochromeFilter.cxx b/vcl/source/bitmap/BitmapMonochromeFilter.cxx
new file mode 100644
index 000000000..1e4e6edf4
--- /dev/null
+++ b/vcl/source/bitmap/BitmapMonochromeFilter.cxx
@@ -0,0 +1,100 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapMonochromeFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapMonochromeFilter::execute(BitmapEx const& aBitmapEx) const
+{
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+ bool bRet = false;
+
+ if (pReadAcc)
+ {
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N1_BPP);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const BitmapColor aBlack(pWriteAcc->GetBestMatchingColor(COL_BLACK));
+ const BitmapColor aWhite(pWriteAcc->GetBestMatchingColor(COL_WHITE));
+ const sal_Int32 nWidth = pWriteAcc->Width();
+ const sal_Int32 nHeight = pWriteAcc->Height();
+
+ if (pReadAcc->HasPalette())
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ const sal_uInt8 cIndex = pReadAcc->GetIndexFromData(pScanlineRead, nX);
+ if (pReadAcc->GetPaletteColor(cIndex).GetLuminance() >= mcThreshold)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aWhite);
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+ }
+ else
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ if (pReadAcc->GetPixelFromData(pScanlineRead, nX).GetLuminance()
+ >= mcThreshold)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aWhite);
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aSize);
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapMosaicFilter.cxx b/vcl/source/bitmap/BitmapMosaicFilter.cxx
new file mode 100644
index 000000000..a146a142b
--- /dev/null
+++ b/vcl/source/bitmap/BitmapMosaicFilter.cxx
@@ -0,0 +1,186 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapMosaicFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapMosaicFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ bool bRet = false;
+
+ if (mnTileWidth > 1 || mnTileHeight > 1)
+ {
+ std::unique_ptr<Bitmap> pNewBmp;
+ BitmapReadAccess* pReadAcc;
+ BitmapWriteAccess* pWriteAcc;
+
+ if (!isPalettePixelFormat(aBitmap.getPixelFormat()))
+ {
+ pReadAcc = pWriteAcc = aBitmap.AcquireWriteAccess();
+ }
+ else
+ {
+ pNewBmp.reset(new Bitmap(aBitmap.GetSizePixel(), vcl::PixelFormat::N24_BPP));
+ pReadAcc = aBitmap.AcquireReadAccess();
+ pWriteAcc = pNewBmp->AcquireWriteAccess();
+ }
+
+ bool bConditionsMet = false;
+ sal_Int32 nWidth(0);
+ sal_Int32 nHeight(0);
+ if (pReadAcc && pWriteAcc)
+ {
+ nWidth = pReadAcc->Width();
+ nHeight = pReadAcc->Height();
+ bConditionsMet = (nWidth > 0 && nHeight > 0);
+ }
+
+ if (bConditionsMet)
+ {
+ BitmapColor aCol;
+ sal_Int32 nX, nY, nX1, nX2, nY1, nY2, nSumR, nSumG, nSumB;
+ double fArea_1;
+
+ nY1 = 0;
+ nY2 = mnTileHeight - 1;
+
+ if (nY2 >= nHeight)
+ nY2 = nHeight - 1;
+
+ do
+ {
+ nX1 = 0;
+ nX2 = mnTileWidth - 1;
+
+ if (nX2 >= nWidth)
+ nX2 = nWidth - 1;
+
+ fArea_1 = 1.0 / ((nX2 - nX1 + 1) * (nY2 - nY1 + 1));
+
+ if (!pNewBmp)
+ {
+ do
+ {
+ for (nY = nY1, nSumR = nSumG = nSumB = 0; nY <= nY2; nY++)
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (nX = nX1; nX <= nX2; nX++)
+ {
+ aCol = pReadAcc->GetPixelFromData(pScanlineRead, nX);
+ nSumR += aCol.GetRed();
+ nSumG += aCol.GetGreen();
+ nSumB += aCol.GetBlue();
+ }
+ }
+
+ aCol.SetRed(static_cast<sal_uInt8>(nSumR * fArea_1));
+ aCol.SetGreen(static_cast<sal_uInt8>(nSumG * fArea_1));
+ aCol.SetBlue(static_cast<sal_uInt8>(nSumB * fArea_1));
+
+ for (nY = nY1; nY <= nY2; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (nX = nX1; nX <= nX2; nX++)
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+
+ nX1 += mnTileWidth;
+ nX2 += mnTileWidth;
+
+ if (nX2 >= nWidth)
+ {
+ nX2 = nWidth - 1;
+ fArea_1 = 1.0 / ((nX2 - nX1 + 1) * (nY2 - nY1 + 1));
+ }
+ } while (nX1 < nWidth);
+ }
+ else
+ {
+ do
+ {
+ for (nY = nY1, nSumR = nSumG = nSumB = 0; nY <= nY2; nY++)
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (nX = nX1; nX <= nX2; nX++)
+ {
+ const BitmapColor& rCol = pReadAcc->GetPaletteColor(
+ pReadAcc->GetIndexFromData(pScanlineRead, nX));
+ nSumR += rCol.GetRed();
+ nSumG += rCol.GetGreen();
+ nSumB += rCol.GetBlue();
+ }
+ }
+
+ aCol.SetRed(static_cast<sal_uInt8>(nSumR * fArea_1));
+ aCol.SetGreen(static_cast<sal_uInt8>(nSumG * fArea_1));
+ aCol.SetBlue(static_cast<sal_uInt8>(nSumB * fArea_1));
+
+ for (nY = nY1; nY <= nY2; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (nX = nX1; nX <= nX2; nX++)
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+
+ nX1 += mnTileWidth;
+ nX2 += mnTileWidth;
+
+ if (nX2 >= nWidth)
+ {
+ nX2 = nWidth - 1;
+ fArea_1 = 1.0 / ((nX2 - nX1 + 1) * (nY2 - nY1 + 1));
+ }
+ } while (nX1 < nWidth);
+ }
+
+ nY1 += mnTileHeight;
+ nY2 += mnTileHeight;
+
+ if (nY2 >= nHeight)
+ nY2 = nHeight - 1;
+
+ } while (nY1 < nHeight);
+
+ bRet = true;
+ }
+
+ if (pWriteAcc == pReadAcc)
+ pWriteAcc = nullptr;
+ Bitmap::ReleaseAccess(pReadAcc);
+
+ if (pNewBmp)
+ {
+ Bitmap::ReleaseAccess(pWriteAcc);
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = *pNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+ }
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapPopArtFilter.cxx b/vcl/source/bitmap/BitmapPopArtFilter.cxx
new file mode 100644
index 000000000..ce37c91fd
--- /dev/null
+++ b/vcl/source/bitmap/BitmapPopArtFilter.cxx
@@ -0,0 +1,99 @@
+/* -*- 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/.
+ *
+ */
+
+#include <tools/solar.h>
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapPopArtFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapPopArtFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ bool bRet = isPalettePixelFormat(aBitmap.getPixelFormat())
+ || aBitmap.Convert(BmpConversion::N8BitColors);
+
+ if (bRet)
+ {
+ bRet = false;
+
+ BitmapScopedWriteAccess pWriteAcc(aBitmap);
+
+ if (pWriteAcc)
+ {
+ const sal_Int32 nWidth = pWriteAcc->Width();
+ const sal_Int32 nHeight = pWriteAcc->Height();
+ const int nEntryCount = 1 << pWriteAcc->GetBitCount();
+ int n = 0;
+ std::vector<PopArtEntry> aPopArtTable(nEntryCount);
+
+ for (n = 0; n < nEntryCount; n++)
+ {
+ PopArtEntry& rEntry = aPopArtTable[n];
+ rEntry.mnIndex = static_cast<sal_uInt16>(n);
+ rEntry.mnCount = 0;
+ }
+
+ // get pixel count for each palette entry
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ aPopArtTable[pWriteAcc->GetIndexFromData(pScanline, nX)].mnCount++;
+ }
+ }
+
+ // sort table
+ std::sort(aPopArtTable.begin(), aPopArtTable.end(),
+ [](const PopArtEntry& lhs, const PopArtEntry& rhs) {
+ return lhs.mnCount > rhs.mnCount;
+ });
+
+ // get last used entry
+ sal_uLong nFirstEntry;
+ sal_uLong nLastEntry = 0;
+
+ for (n = 0; n < nEntryCount; n++)
+ {
+ if (aPopArtTable[n].mnCount)
+ nLastEntry = n;
+ }
+
+ // rotate palette (one entry)
+ const BitmapColor aFirstCol(pWriteAcc->GetPaletteColor(
+ sal::static_int_cast<sal_uInt16>(aPopArtTable[0].mnIndex)));
+
+ for (nFirstEntry = 0; nFirstEntry < nLastEntry; nFirstEntry++)
+ {
+ pWriteAcc->SetPaletteColor(
+ sal::static_int_cast<sal_uInt16>(aPopArtTable[nFirstEntry].mnIndex),
+ pWriteAcc->GetPaletteColor(
+ sal::static_int_cast<sal_uInt16>(aPopArtTable[nFirstEntry + 1].mnIndex)));
+ }
+
+ pWriteAcc->SetPaletteColor(
+ sal::static_int_cast<sal_uInt16>(aPopArtTable[nLastEntry].mnIndex), aFirstCol);
+
+ // cleanup
+ pWriteAcc.reset();
+ bRet = true;
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapReadAccess.cxx b/vcl/source/bitmap/BitmapReadAccess.cxx
new file mode 100644
index 000000000..6e87debd1
--- /dev/null
+++ b/vcl/source/bitmap/BitmapReadAccess.cxx
@@ -0,0 +1,550 @@
+/* -*- 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 <vcl/BitmapReadAccess.hxx>
+#include <vcl/BitmapTools.hxx>
+
+#include <salbmp.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+BitmapReadAccess::BitmapReadAccess(Bitmap& rBitmap, BitmapAccessMode nMode)
+ : BitmapInfoAccess(rBitmap, nMode)
+ , mFncGetPixel(nullptr)
+ , mFncSetPixel(nullptr)
+{
+ if (!mpBuffer)
+ return;
+
+ const std::shared_ptr<SalBitmap>& xImpBmp = rBitmap.ImplGetSalBitmap();
+ if (!xImpBmp)
+ return;
+
+ maColorMask = mpBuffer->maColorMask;
+
+ mFncGetPixel = GetPixelFunction(mpBuffer->mnFormat);
+ mFncSetPixel = SetPixelFunction(mpBuffer->mnFormat);
+
+ if (!mFncGetPixel || !mFncSetPixel)
+ {
+ xImpBmp->ReleaseBuffer(mpBuffer, mnAccessMode);
+ mpBuffer = nullptr;
+ }
+}
+
+BitmapReadAccess::~BitmapReadAccess() {}
+
+bool Bitmap32IsPreMultipled() { return ImplGetSVData()->mpDefInst->supportsBitmap32(); }
+
+FncGetPixel BitmapReadAccess::GetPixelFunction(ScanlineFormat nFormat)
+{
+ switch (RemoveScanline(nFormat))
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ return GetPixelForN1BitMsbPal;
+ case ScanlineFormat::N1BitLsbPal:
+ return GetPixelForN1BitLsbPal;
+ case ScanlineFormat::N8BitPal:
+ return GetPixelForN8BitPal;
+ case ScanlineFormat::N24BitTcBgr:
+ return GetPixelForN24BitTcBgr;
+ case ScanlineFormat::N24BitTcRgb:
+ return GetPixelForN24BitTcRgb;
+ case ScanlineFormat::N32BitTcAbgr:
+ if (Bitmap32IsPreMultipled())
+ return GetPixelForN32BitTcAbgr;
+ else
+ return GetPixelForN32BitTcXbgr;
+ case ScanlineFormat::N32BitTcArgb:
+ if (Bitmap32IsPreMultipled())
+ return GetPixelForN32BitTcArgb;
+ else
+ return GetPixelForN32BitTcXrgb;
+ case ScanlineFormat::N32BitTcBgra:
+ if (Bitmap32IsPreMultipled())
+ return GetPixelForN32BitTcBgra;
+ else
+ return GetPixelForN32BitTcBgrx;
+ case ScanlineFormat::N32BitTcRgba:
+ if (Bitmap32IsPreMultipled())
+ return GetPixelForN32BitTcRgba;
+ else
+ return GetPixelForN32BitTcRgbx;
+ case ScanlineFormat::N32BitTcMask:
+ return GetPixelForN32BitTcMask;
+
+ default:
+ return nullptr;
+ }
+}
+
+FncSetPixel BitmapReadAccess::SetPixelFunction(ScanlineFormat nFormat)
+{
+ switch (RemoveScanline(nFormat))
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ return SetPixelForN1BitMsbPal;
+ case ScanlineFormat::N1BitLsbPal:
+ return SetPixelForN1BitLsbPal;
+ case ScanlineFormat::N8BitPal:
+ return SetPixelForN8BitPal;
+ case ScanlineFormat::N24BitTcBgr:
+ return SetPixelForN24BitTcBgr;
+ case ScanlineFormat::N24BitTcRgb:
+ return SetPixelForN24BitTcRgb;
+ case ScanlineFormat::N32BitTcAbgr:
+ if (Bitmap32IsPreMultipled())
+ return SetPixelForN32BitTcAbgr;
+ else
+ return SetPixelForN32BitTcXbgr;
+ case ScanlineFormat::N32BitTcArgb:
+ if (Bitmap32IsPreMultipled())
+ return SetPixelForN32BitTcArgb;
+ else
+ return SetPixelForN32BitTcXrgb;
+ case ScanlineFormat::N32BitTcBgra:
+ if (Bitmap32IsPreMultipled())
+ return SetPixelForN32BitTcBgra;
+ else
+ return SetPixelForN32BitTcBgrx;
+ case ScanlineFormat::N32BitTcRgba:
+ if (Bitmap32IsPreMultipled())
+ return SetPixelForN32BitTcRgba;
+ else
+ return SetPixelForN32BitTcRgbx;
+ case ScanlineFormat::N32BitTcMask:
+ return SetPixelForN32BitTcMask;
+
+ default:
+ return nullptr;
+ }
+}
+
+BitmapColor BitmapReadAccess::GetInterpolatedColorWithFallback(double fY, double fX,
+ const BitmapColor& rFallback) const
+{
+ // ask directly doubles >= 0.0 here to avoid rounded values of 0 at small negative
+ // double values, e.g. static_cast< sal_Int32 >(-0.25) is 0, not -1, but *has* to be outside (!)
+ if (mpBuffer && fX >= 0.0 && fY >= 0.0)
+ {
+ const sal_Int64 nX(static_cast<sal_Int64>(fX));
+ const sal_Int64 nY(static_cast<sal_Int64>(fY));
+
+ if (nX < mpBuffer->mnWidth && nY < mpBuffer->mnHeight)
+ {
+ // get base-return value from inside pixel
+ BitmapColor aRetval(GetColor(nY, nX));
+
+ // calculate deltas and indices for neighbour accesses
+ sal_Int16 nDeltaX((fX - (nX + 0.5)) * 255.0); // [-255 .. 255]
+ sal_Int16 nDeltaY((fY - (nY + 0.5)) * 255.0); // [-255 .. 255]
+ sal_Int16 nIndX(0);
+ sal_Int16 nIndY(0);
+
+ if (nDeltaX > 0)
+ {
+ nIndX = nX + 1;
+ }
+ else
+ {
+ nIndX = nX - 1;
+ nDeltaX = -nDeltaX;
+ }
+
+ if (nDeltaY > 0)
+ {
+ nIndY = nY + 1;
+ }
+ else
+ {
+ nIndY = nY - 1;
+ nDeltaY = -nDeltaY;
+ }
+
+ // get right/left neighbour
+ BitmapColor aXCol(rFallback);
+
+ if (nDeltaX && nIndX >= 0 && nIndX < mpBuffer->mnWidth)
+ {
+ aXCol = GetColor(nY, nIndX);
+ }
+
+ // get top/bottom neighbour
+ BitmapColor aYCol(rFallback);
+
+ if (nDeltaY && nIndY >= 0 && nIndY < mpBuffer->mnHeight)
+ {
+ aYCol = GetColor(nIndY, nX);
+ }
+
+ // get one of four edge neighbours
+ BitmapColor aXYCol(rFallback);
+
+ if (nDeltaX && nDeltaY && nIndX >= 0 && nIndY >= 0 && nIndX < mpBuffer->mnWidth
+ && nIndY < mpBuffer->mnHeight)
+ {
+ aXYCol = GetColor(nIndY, nIndX);
+ }
+
+ // merge return value with right/left neighbour
+ if (aXCol != aRetval)
+ {
+ aRetval.Merge(aXCol, 255 - nDeltaX);
+ }
+
+ // merge top/bottom neighbour with edge
+ if (aYCol != aXYCol)
+ {
+ aYCol.Merge(aXYCol, 255 - nDeltaX);
+ }
+
+ // merge return value with already merged top/bottom neighbour
+ if (aRetval != aYCol)
+ {
+ aRetval.Merge(aYCol, 255 - nDeltaY);
+ }
+
+ return aRetval;
+ }
+ }
+
+ return rFallback;
+}
+
+BitmapColor BitmapReadAccess::GetColorWithFallback(double fY, double fX,
+ const BitmapColor& rFallback) const
+{
+ // ask directly doubles >= 0.0 here to avoid rounded values of 0 at small negative
+ // double values, e.g. static_cast< sal_Int32 >(-0.25) is 0, not -1, but *has* to be outside (!)
+ if (mpBuffer && fX >= 0.0 && fY >= 0.0)
+ {
+ const sal_Int32 nX(static_cast<sal_Int32>(fX));
+ const sal_Int32 nY(static_cast<sal_Int32>(fY));
+
+ if (nX < mpBuffer->mnWidth && nY < mpBuffer->mnHeight)
+ {
+ return GetColor(nY, nX);
+ }
+ }
+
+ return rFallback;
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN1BitMsbPal(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ return BitmapColor(pScanline[nX >> 3] & (1 << (7 - (nX & 7))) ? 1 : 0);
+}
+
+void BitmapReadAccess::SetPixelForN1BitMsbPal(const Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ sal_uInt8& rByte = pScanline[nX >> 3];
+
+ if (rBitmapColor.GetIndex() & 1)
+ rByte |= 1 << (7 - (nX & 7));
+ else
+ rByte &= ~(1 << (7 - (nX & 7)));
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN1BitLsbPal(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ return BitmapColor(pScanline[nX >> 3] & (1 << (nX & 7)) ? 1 : 0);
+}
+
+void BitmapReadAccess::SetPixelForN1BitLsbPal(const Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ sal_uInt8& rByte = pScanline[nX >> 3];
+
+ if (rBitmapColor.GetIndex() & 1)
+ rByte |= 1 << (nX & 7);
+ else
+ rByte &= ~(1 << (nX & 7));
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN8BitPal(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ return BitmapColor(pScanline[nX]);
+}
+
+void BitmapReadAccess::SetPixelForN8BitPal(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline[nX] = rBitmapColor.GetIndex();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN24BitTcBgr(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + nX * 3;
+ aBitmapColor.SetBlue(*pScanline++);
+ aBitmapColor.SetGreen(*pScanline++);
+ aBitmapColor.SetRed(*pScanline);
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN24BitTcBgr(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 3;
+ *pScanline++ = rBitmapColor.GetBlue();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline = rBitmapColor.GetRed();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN24BitTcRgb(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + nX * 3;
+ aBitmapColor.SetRed(*pScanline++);
+ aBitmapColor.SetGreen(*pScanline++);
+ aBitmapColor.SetBlue(*pScanline);
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN24BitTcRgb(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 3;
+ *pScanline++ = rBitmapColor.GetRed();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline = rBitmapColor.GetBlue();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcAbgr(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 a = *pScanline++;
+ sal_uInt8 b = *pScanline++;
+ sal_uInt8 g = *pScanline++;
+ sal_uInt8 r = *pScanline;
+
+ return BitmapColor(ColorAlpha, vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a), vcl::bitmap::unpremultiply(b, a), a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcXbgr(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + (nX << 2) + 1;
+ aBitmapColor.SetBlue(*pScanline++);
+ aBitmapColor.SetGreen(*pScanline++);
+ aBitmapColor.SetRed(*pScanline);
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcAbgr(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = rBitmapColor.GetAlpha();
+ *pScanline++ = alpha;
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetBlue(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetGreen(), alpha);
+ *pScanline = vcl::bitmap::premultiply(rBitmapColor.GetRed(), alpha);
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcXbgr(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + (nX << 2);
+ *pScanline++ = 0xFF;
+ *pScanline++ = rBitmapColor.GetBlue();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline = rBitmapColor.GetRed();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcArgb(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 a = *pScanline++;
+ sal_uInt8 r = *pScanline++;
+ sal_uInt8 g = *pScanline++;
+ sal_uInt8 b = *pScanline;
+
+ return BitmapColor(ColorAlpha, vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a), vcl::bitmap::unpremultiply(b, a), a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcXrgb(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + (nX << 2) + 1;
+ aBitmapColor.SetRed(*pScanline++);
+ aBitmapColor.SetGreen(*pScanline++);
+ aBitmapColor.SetBlue(*pScanline);
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcArgb(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = rBitmapColor.GetAlpha();
+ *pScanline++ = alpha;
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetRed(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetGreen(), alpha);
+ *pScanline = vcl::bitmap::premultiply(rBitmapColor.GetBlue(), alpha);
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcXrgb(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + (nX << 2);
+ *pScanline++ = 0xFF;
+ *pScanline++ = rBitmapColor.GetRed();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline = rBitmapColor.GetBlue();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcBgra(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 b = *pScanline++;
+ sal_uInt8 g = *pScanline++;
+ sal_uInt8 r = *pScanline++;
+ sal_uInt8 a = *pScanline;
+
+ return BitmapColor(ColorAlpha, vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a), vcl::bitmap::unpremultiply(b, a), a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcBgrx(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + (nX << 2);
+ aBitmapColor.SetBlue(*pScanline++);
+ aBitmapColor.SetGreen(*pScanline++);
+ aBitmapColor.SetRed(*pScanline);
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcBgra(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = rBitmapColor.GetAlpha();
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetBlue(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetGreen(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetRed(), alpha);
+ *pScanline = alpha;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcBgrx(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + (nX << 2);
+ *pScanline++ = rBitmapColor.GetBlue();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline++ = rBitmapColor.GetRed();
+ *pScanline = 0xFF;
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcRgba(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 r = *pScanline++;
+ sal_uInt8 g = *pScanline++;
+ sal_uInt8 b = *pScanline++;
+ sal_uInt8 a = *pScanline;
+
+ return BitmapColor(ColorAlpha, vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a), vcl::bitmap::unpremultiply(b, a), a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcRgbx(ConstScanline pScanline, tools::Long nX,
+ const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + (nX << 2);
+ aBitmapColor.SetRed(*pScanline++);
+ aBitmapColor.SetGreen(*pScanline++);
+ aBitmapColor.SetBlue(*pScanline);
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcRgba(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = rBitmapColor.GetAlpha();
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetRed(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetGreen(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetBlue(), alpha);
+ *pScanline = alpha;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcRgbx(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + (nX << 2);
+ *pScanline++ = rBitmapColor.GetRed();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline++ = rBitmapColor.GetBlue();
+ *pScanline = 0xFF;
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcMask(ConstScanline pScanline, tools::Long nX,
+ const ColorMask& rMask)
+{
+ BitmapColor aColor;
+ rMask.GetColorFor32Bit(aColor, pScanline + (nX << 2));
+ return aColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcMask(Scanline pScanline, tools::Long nX,
+ const BitmapColor& rBitmapColor,
+ const ColorMask& rMask)
+{
+ rMask.SetColorFor32Bit(rBitmapColor, pScanline + (nX << 2));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapScaleConvolutionFilter.cxx b/vcl/source/bitmap/BitmapScaleConvolutionFilter.cxx
new file mode 100644
index 000000000..bb40e9846
--- /dev/null
+++ b/vcl/source/bitmap/BitmapScaleConvolutionFilter.cxx
@@ -0,0 +1,389 @@
+/* -*- 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 <osl/diagnose.h>
+#include <tools/helpers.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapScaleConvolutionFilter.hxx>
+
+#include <algorithm>
+#include <memory>
+
+namespace vcl
+{
+
+namespace
+{
+
+void ImplCalculateContributions(
+ const sal_Int32 aSourceSize,
+ const sal_Int32 aDestinationSize,
+ sal_Int32& aNumberOfContributions,
+ std::vector<sal_Int16>& rWeights,
+ std::vector<sal_Int32>& rPixels,
+ std::vector<sal_Int32>& rCounts,
+ const Kernel& aKernel)
+{
+ const double fSamplingRadius(aKernel.GetWidth());
+ const double fScale(aDestinationSize / static_cast< double >(aSourceSize));
+ const double fScaledRadius((fScale < 1.0) ? fSamplingRadius / fScale : fSamplingRadius);
+ const double fFilterFactor(std::min(fScale, 1.0));
+
+ aNumberOfContributions = (sal_Int32(fabs(ceil(fScaledRadius))) * 2) + 1;
+ const sal_Int32 nAllocSize(aDestinationSize * aNumberOfContributions);
+ rWeights.resize(nAllocSize);
+ rPixels.resize(nAllocSize);
+ rCounts.resize(aDestinationSize);
+
+ for(sal_Int32 i(0); i < aDestinationSize; i++)
+ {
+ const sal_Int32 aIndex(i * aNumberOfContributions);
+ const double aCenter(i / fScale);
+ const sal_Int32 aLeft(static_cast< sal_Int32 >(floor(aCenter - fScaledRadius)));
+ const sal_Int32 aRight(static_cast< sal_Int32 >(ceil(aCenter + fScaledRadius)));
+ sal_Int32 aCurrentCount(0);
+
+ for(sal_Int32 j(aLeft); j <= aRight; j++)
+ {
+ const double aWeight(aKernel.Calculate(fFilterFactor * (aCenter - static_cast< double>(j))));
+
+ // Reduce calculations with ignoring weights of 0.0
+ if(fabs(aWeight) < 0.0001)
+ {
+ continue;
+ }
+
+ // Handling on edges
+ const sal_Int32 aPixelIndex(MinMax(j, 0, aSourceSize - 1));
+ const sal_Int32 nIndex(aIndex + aCurrentCount);
+
+ // scale the weight by 255 since we're converting from float to int
+ rWeights[nIndex] = aWeight * 255;
+ rPixels[nIndex] = aPixelIndex;
+
+ aCurrentCount++;
+ }
+
+ rCounts[i] = aCurrentCount;
+ }
+}
+
+bool ImplScaleConvolutionHor(Bitmap& rSource, Bitmap& rTarget, const double& rScaleX, const Kernel& aKernel)
+{
+ // Do horizontal filtering
+ OSL_ENSURE(rScaleX > 0.0, "Error in scaling: Mirror given in non-mirror-capable method (!)");
+ const sal_Int32 nWidth(rSource.GetSizePixel().Width());
+ const sal_Int32 nNewWidth(FRound(nWidth * rScaleX));
+
+ if(nWidth == nNewWidth)
+ {
+ return true;
+ }
+
+ Bitmap::ScopedReadAccess pReadAcc(rSource);
+
+ if(pReadAcc)
+ {
+ std::vector<sal_Int16> aWeights;
+ std::vector<sal_Int32> aPixels;
+ std::vector<sal_Int32> aCounts;
+ sal_Int32 aNumberOfContributions(0);
+
+ const sal_Int32 nHeight(rSource.GetSizePixel().Height());
+ ImplCalculateContributions(nWidth, nNewWidth, aNumberOfContributions, aWeights, aPixels, aCounts, aKernel);
+ rTarget = Bitmap(Size(nNewWidth, nHeight), vcl::PixelFormat::N24_BPP);
+ BitmapScopedWriteAccess pWriteAcc(rTarget);
+ bool bResult(pWriteAcc);
+
+ if(bResult)
+ {
+ for(sal_Int32 y(0); y < nHeight; y++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline( y );
+ Scanline pScanlineRead = pReadAcc->GetScanline( y );
+ for(sal_Int32 x(0); x < nNewWidth; x++)
+ {
+ const sal_Int32 aBaseIndex(x * aNumberOfContributions);
+ sal_Int32 aSum(0);
+ sal_Int32 aValueRed(0);
+ sal_Int32 aValueGreen(0);
+ sal_Int32 aValueBlue(0);
+
+ for(sal_Int32 j(0); j < aCounts[x]; j++)
+ {
+ const sal_Int32 aIndex(aBaseIndex + j);
+ const sal_Int16 aWeight(aWeights[aIndex]);
+ BitmapColor aColor;
+
+ aSum += aWeight;
+
+ if(pReadAcc->HasPalette())
+ {
+ aColor = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, aPixels[aIndex]));
+ }
+ else
+ {
+ aColor = pReadAcc->GetPixelFromData(pScanlineRead, aPixels[aIndex]);
+ }
+
+ aValueRed += aWeight * aColor.GetRed();
+ aValueGreen += aWeight * aColor.GetGreen();
+ aValueBlue += aWeight * aColor.GetBlue();
+ }
+
+ assert(aSum != 0);
+
+ const BitmapColor aResultColor(
+ static_cast< sal_uInt8 >(MinMax(static_cast< sal_Int32 >(aValueRed / aSum), 0, 255)),
+ static_cast< sal_uInt8 >(MinMax(static_cast< sal_Int32 >(aValueGreen / aSum), 0, 255)),
+ static_cast< sal_uInt8 >(MinMax(static_cast< sal_Int32 >(aValueBlue / aSum), 0, 255)));
+
+ pWriteAcc->SetPixelOnData(pScanline, x, aResultColor);
+ }
+ }
+
+ pWriteAcc.reset();
+ }
+
+ aWeights.clear();
+ aCounts.clear();
+ aPixels.clear();
+
+ if(bResult)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ImplScaleConvolutionVer(Bitmap& rSource, Bitmap& rTarget, const double& rScaleY, const Kernel& aKernel)
+{
+ // Do vertical filtering
+ OSL_ENSURE(rScaleY > 0.0, "Error in scaling: Mirror given in non-mirror-capable method (!)");
+ const sal_Int32 nHeight(rSource.GetSizePixel().Height());
+ const sal_Int32 nNewHeight(FRound(nHeight * rScaleY));
+
+ if(nHeight == nNewHeight)
+ {
+ return true;
+ }
+
+ Bitmap::ScopedReadAccess pReadAcc(rSource);
+
+ if(pReadAcc)
+ {
+ std::vector<sal_Int16> aWeights;
+ std::vector<sal_Int32> aPixels;
+ std::vector<sal_Int32> aCounts;
+ sal_Int32 aNumberOfContributions(0);
+
+ const sal_Int32 nWidth(rSource.GetSizePixel().Width());
+ ImplCalculateContributions(nHeight, nNewHeight, aNumberOfContributions, aWeights, aPixels, aCounts, aKernel);
+ rTarget = Bitmap(Size(nWidth, nNewHeight), vcl::PixelFormat::N24_BPP);
+ BitmapScopedWriteAccess pWriteAcc(rTarget);
+ bool bResult(pWriteAcc);
+
+ if(pWriteAcc)
+ {
+ std::vector<BitmapColor> aScanline(nHeight);
+ for(sal_Int32 x(0); x < nWidth; x++)
+ {
+ for(sal_Int32 y(0); y < nHeight; y++)
+ if(pReadAcc->HasPalette())
+ aScanline[y] = pReadAcc->GetPaletteColor(pReadAcc->GetPixelIndex(y, x));
+ else
+ aScanline[y] = pReadAcc->GetPixel(y, x);
+ for(sal_Int32 y(0); y < nNewHeight; y++)
+ {
+ const sal_Int32 aBaseIndex(y * aNumberOfContributions);
+ sal_Int32 aSum(0);
+ sal_Int32 aValueRed(0);
+ sal_Int32 aValueGreen(0);
+ sal_Int32 aValueBlue(0);
+
+ for(sal_Int32 j(0); j < aCounts[y]; j++)
+ {
+ const sal_Int32 aIndex(aBaseIndex + j);
+ const sal_Int16 aWeight(aWeights[aIndex]);
+ aSum += aWeight;
+ const BitmapColor & aColor = aScanline[aPixels[aIndex]];
+ aValueRed += aWeight * aColor.GetRed();
+ aValueGreen += aWeight * aColor.GetGreen();
+ aValueBlue += aWeight * aColor.GetBlue();
+ }
+
+ assert(aSum != 0);
+
+ const BitmapColor aResultColor(
+ static_cast< sal_uInt8 >(MinMax(static_cast< sal_Int32 >(aValueRed / aSum), 0, 255)),
+ static_cast< sal_uInt8 >(MinMax(static_cast< sal_Int32 >(aValueGreen / aSum), 0, 255)),
+ static_cast< sal_uInt8 >(MinMax(static_cast< sal_Int32 >(aValueBlue / aSum), 0, 255)));
+
+ if(pWriteAcc->HasPalette())
+ {
+ pWriteAcc->SetPixelIndex(y, x, static_cast< sal_uInt8 >(pWriteAcc->GetBestPaletteIndex(aResultColor)));
+ }
+ else
+ {
+ pWriteAcc->SetPixel(y, x, aResultColor);
+ }
+ }
+ }
+ }
+
+ aWeights.clear();
+ aCounts.clear();
+ aPixels.clear();
+
+ if(bResult)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ImplScaleConvolution(Bitmap& rBitmap, const double& rScaleX, const double& rScaleY, const Kernel& aKernel)
+{
+ const bool bMirrorHor(rScaleX < 0.0);
+ const bool bMirrorVer(rScaleY < 0.0);
+ const double fScaleX(bMirrorHor ? -rScaleX : rScaleX);
+ const double fScaleY(bMirrorVer ? -rScaleY : rScaleY);
+ const sal_Int32 nWidth(rBitmap.GetSizePixel().Width());
+ const sal_Int32 nHeight(rBitmap.GetSizePixel().Height());
+ const sal_Int32 nNewWidth(FRound(nWidth * fScaleX));
+ const sal_Int32 nNewHeight(FRound(nHeight * fScaleY));
+ const bool bScaleHor(nWidth != nNewWidth);
+ const bool bScaleVer(nHeight != nNewHeight);
+ const bool bMirror(bMirrorHor || bMirrorVer);
+
+ if (!bMirror && !bScaleHor && !bScaleVer)
+ {
+ return true;
+ }
+
+ bool bResult(true);
+ BmpMirrorFlags nMirrorFlags(BmpMirrorFlags::NONE);
+ bool bMirrorAfter(false);
+
+ if (bMirror)
+ {
+ if(bMirrorHor)
+ {
+ nMirrorFlags |= BmpMirrorFlags::Horizontal;
+ }
+
+ if(bMirrorVer)
+ {
+ nMirrorFlags |= BmpMirrorFlags::Vertical;
+ }
+
+ const sal_Int32 nStartSize(nWidth * nHeight);
+ const sal_Int32 nEndSize(nNewWidth * nNewHeight);
+
+ bMirrorAfter = nStartSize > nEndSize;
+
+ if(!bMirrorAfter)
+ {
+ bResult = rBitmap.Mirror(nMirrorFlags);
+ }
+ }
+
+ Bitmap aResult;
+
+ if (bResult)
+ {
+ const sal_Int32 nInBetweenSizeHorFirst(nHeight * nNewWidth);
+ const sal_Int32 nInBetweenSizeVerFirst(nNewHeight * nWidth);
+ Bitmap aSource(rBitmap);
+
+ if(nInBetweenSizeHorFirst < nInBetweenSizeVerFirst)
+ {
+ if(bScaleHor)
+ {
+ bResult = ImplScaleConvolutionHor(aSource, aResult, fScaleX, aKernel);
+ }
+
+ if(bResult && bScaleVer)
+ {
+ if(bScaleHor)
+ {
+ // copy partial result, independent of color depth
+ aSource = aResult;
+ }
+
+ bResult = ImplScaleConvolutionVer(aSource, aResult, fScaleY, aKernel);
+ }
+ }
+ else
+ {
+ if(bScaleVer)
+ {
+ bResult = ImplScaleConvolutionVer(aSource, aResult, fScaleY, aKernel);
+ }
+
+ if(bResult && bScaleHor)
+ {
+ if(bScaleVer)
+ {
+ // copy partial result, independent of color depth
+ aSource = aResult;
+ }
+
+ bResult = ImplScaleConvolutionHor(aSource, aResult, fScaleX, aKernel);
+ }
+ }
+ }
+
+ if(bResult && bMirrorAfter)
+ {
+ bResult = aResult.Mirror(nMirrorFlags);
+ }
+
+ if(bResult)
+ {
+ rBitmap.AdaptBitCount(aResult);
+ rBitmap = aResult;
+ }
+
+ return bResult;
+}
+
+} // end anonymous namespace
+
+BitmapEx BitmapScaleConvolutionFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ bool bRetval = false;
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ bRetval = ImplScaleConvolution(aBitmap, mrScaleX, mrScaleY, *mxKernel);
+
+ if (bRetval)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapScaleSuperFilter.cxx b/vcl/source/bitmap/BitmapScaleSuperFilter.cxx
new file mode 100644
index 000000000..59ed78c8d
--- /dev/null
+++ b/vcl/source/bitmap/BitmapScaleSuperFilter.cxx
@@ -0,0 +1,1046 @@
+/* -*- 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 <comphelper/profilezone.hxx>
+#include <comphelper/threadpool.hxx>
+#include <tools/helpers.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <bitmap/BitmapScaleSuperFilter.hxx>
+
+#include <algorithm>
+#include <memory>
+#include <svdata.hxx>
+#include <sal/log.hxx>
+
+/*
+A scaling algorithm that uses bilinear if not downscaling too much,
+and averaging otherwise (bilinear would produce poor results for big downscaling).
+
+By default the combination of two filters is used: bilinear and averaging algorithm.
+Bilinear filtering is used for bitmap enlarging and shrinking till factor 0.6. Below
+this bilinear gives bad results because of limited sampling. For such cases averaging
+is used which is a simple algorithm for shrinking. In averaging the algorithm
+calculates the average of samples which result is the new pixel.
+*/
+
+namespace {
+
+constexpr int MAP_PRECISION = 7;
+
+typedef sal_Int32 BilinearWeightType;
+
+constexpr BilinearWeightType lclMaxWeight()
+{
+ return BilinearWeightType(1) << MAP_PRECISION;
+}
+
+constexpr sal_uInt8 MAP(sal_uInt8 cVal0, sal_uInt8 cVal1, BilinearWeightType nFrac)
+{
+ return sal_uInt8(((BilinearWeightType(cVal0) << MAP_PRECISION) + nFrac * (BilinearWeightType(cVal1) - BilinearWeightType(cVal0))) >> MAP_PRECISION);
+}
+
+struct ScaleContext
+{
+ BitmapReadAccess* mpSrc;
+ BitmapWriteAccess* mpDest;
+ sal_Int32 mnDestW;
+ bool mbHMirr;
+ bool mbVMirr;
+ std::vector<sal_Int32> maMapIX;
+ std::vector<sal_Int32> maMapIY;
+ std::vector<BilinearWeightType> maMapFX;
+ std::vector<BilinearWeightType> maMapFY;
+
+ ScaleContext( BitmapReadAccess *pSrc,
+ BitmapWriteAccess *pDest,
+ sal_Int32 nSrcW, sal_Int32 nDestW,
+ sal_Int32 nSrcH, sal_Int32 nDestH,
+ bool bHMirr, bool bVMirr)
+ : mpSrc(pSrc)
+ , mpDest(pDest)
+ , mnDestW(nDestW)
+ , mbHMirr(bHMirr)
+ , mbVMirr(bVMirr)
+ , maMapIX(nDestW)
+ , maMapIY(nDestH)
+ , maMapFX(nDestW)
+ , maMapFY(nDestH)
+ {
+ generateMap(nSrcW, nDestW, bHMirr, maMapIX, maMapFX);
+ generateMap(nSrcH, nDestH, bVMirr, maMapIY, maMapFY);
+ }
+
+ static void generateMap(sal_Int32 nSourceLength, sal_Int32 nDestinationLength, bool bMirrored,
+ std::vector<sal_Int32> & rMapIX, std::vector<BilinearWeightType> & rMapFX)
+ {
+ const double fRevScale = (nDestinationLength > 1) ? double(nSourceLength - 1) / (nDestinationLength - 1) : 0.0;
+
+ sal_Int32 nTemp = nSourceLength - 2;
+ sal_Int32 nTempX = nSourceLength - 1;
+
+ for (sal_Int32 i = 0; i < nDestinationLength; i++)
+ {
+ double fTemp = i * fRevScale;
+ if (bMirrored)
+ fTemp = nTempX - fTemp;
+ rMapIX[i] = MinMax(sal_Int32(fTemp), 0, nTemp);
+ rMapFX[i] = BilinearWeightType((fTemp - rMapIX[i]) * (BilinearWeightType(1) << MAP_PRECISION));
+ }
+ }
+};
+
+constexpr sal_Int32 constScaleThreadStrip = 32;
+
+typedef void (*ScaleRangeFn)(const ScaleContext & rContext, sal_Int32 nStartY, sal_Int32 nEndY);
+
+template <size_t nSize> struct ScaleFunc
+{
+ // for scale down
+
+ static inline void generateSumRows(Scanline& pTmpX, std::array<int, nSize>& sumRows)
+ {
+ for (int& n : sumRows)
+ n += (*pTmpX++) << MAP_PRECISION;
+ }
+
+ static inline void generateSumRows(BilinearWeightType const nWeightX, Scanline& pTmpX,
+ std::array<int, nSize>& sumRows)
+ {
+ for (int& n : sumRows)
+ n += (nWeightX * (*pTmpX++));
+ }
+
+ static inline void generateSumRows(BilinearWeightType const nTotalWeightX,
+ std::array<int, nSize>& sumRows)
+ {
+ for (int& n : sumRows)
+ n /= nTotalWeightX;
+ }
+
+ static inline void generateSumNumbers(BilinearWeightType const nWeightY,
+ std::array<int, nSize>& sumRows,
+ std::array<int, nSize>& sumNumbers)
+ {
+ std::transform(sumRows.begin(), sumRows.end(), sumNumbers.begin(), sumNumbers.begin(),
+ [nWeightY](int n1, int n2) { return nWeightY * n1 + n2; });
+ }
+
+ static inline void generateSumNumbers(BilinearWeightType const nTotalWeightY,
+ std::array<int, nSize>& sumNumbers)
+ {
+ for (int& n : sumNumbers)
+ n /= nTotalWeightY;
+ }
+
+ static inline void calculateDestination(Scanline& pScanDest, std::array<int, nSize>& sumNumbers)
+ {
+ pScanDest = std::copy(sumNumbers.begin(), sumNumbers.end(), pScanDest);
+ }
+
+ // for scale up
+
+ static inline void generateComponent(Scanline pColorPtr0, Scanline pColorPtr1,
+ BilinearWeightType const nTempFX,
+ std::array<sal_uInt8, nSize>& nComponents)
+ {
+ for (sal_uInt8& rComponent : nComponents)
+ rComponent = MAP(*pColorPtr0++, *pColorPtr1++, nTempFX);
+ }
+
+ static inline void calculateDestination(Scanline& pScanDest, BilinearWeightType const nTempFY,
+ const std::array<sal_uInt8, nSize>& nComponents1,
+ const std::array<sal_uInt8, nSize>& nComponents2)
+ {
+ pScanDest = std::transform(
+ nComponents1.begin(), nComponents1.end(), nComponents2.begin(), pScanDest,
+ [nTempFY](sal_uInt8 c1, sal_uInt8 c2) { return MAP(c1, c2, nTempFY); });
+ }
+};
+
+template <int nColorBits>
+void scaleDown (const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ comphelper::ProfileZone pz("BitmapScaleSuperFilter::scaleDown");
+ constexpr int nColorComponents = nColorBits / 8;
+ static_assert(nColorComponents * 8 == nColorBits, "nColorBits must be divisible by 8");
+ using ScaleFunction = ScaleFunc<nColorComponents>;
+ const sal_Int32 nStartX = 0;
+ const sal_Int32 nEndX = rCtx.mnDestW - 1;
+
+ for (sal_Int32 nY = nStartY; nY <= nEndY; nY++)
+ {
+ sal_Int32 nTop = rCtx.mbVMirr ? (nY + 1) : nY;
+ sal_Int32 nBottom = rCtx.mbVMirr ? nY : (nY + 1);
+
+ sal_Int32 nLineStart;
+ sal_Int32 nLineRange;
+ if (nY == nEndY)
+ {
+ nLineStart = rCtx.maMapIY[nY];
+ nLineRange = 0;
+ }
+ else
+ {
+ nLineStart = rCtx.maMapIY[nTop];
+ nLineRange = (rCtx.maMapIY[nBottom] == rCtx.maMapIY[nTop]) ?
+ 1 : (rCtx.maMapIY[nBottom] - rCtx.maMapIY[nTop]);
+ }
+
+ Scanline pScanDest = rCtx.mpDest->GetScanline(nY);
+ for (sal_Int32 nX = nStartX; nX <= nEndX; nX++)
+ {
+ sal_Int32 nLeft = rCtx.mbHMirr ? (nX + 1) : nX;
+ sal_Int32 nRight = rCtx.mbHMirr ? nX : (nX + 1);
+
+ sal_Int32 nRowStart;
+ sal_Int32 nRowRange;
+ if (nX == nEndX)
+ {
+ nRowStart = rCtx.maMapIX[nX];
+ nRowRange = 0;
+ }
+ else
+ {
+ nRowStart = rCtx.maMapIX[nLeft];
+ nRowRange = (rCtx.maMapIX[nRight] == rCtx.maMapIX[nLeft]) ?
+ 1 : (rCtx.maMapIX[nRight] - rCtx.maMapIX[nLeft]);
+ }
+
+ std::array<int, nColorComponents> sumNumbers{}; // zero-initialize
+ BilinearWeightType nTotalWeightY = 0;
+
+ for (sal_Int32 i = 0; i<= nLineRange; i++)
+ {
+ Scanline pTmpY = rCtx.mpSrc->GetScanline(nLineStart + i);
+ Scanline pTmpX = pTmpY + nColorComponents * nRowStart;
+
+ std::array<int, nColorComponents> sumRows{}; // zero-initialize
+ BilinearWeightType nTotalWeightX = 0;
+
+ for (sal_Int32 j = 0; j <= nRowRange; j++)
+ {
+ if (nX == nEndX)
+ {
+ ScaleFunction::generateSumRows(pTmpX, sumRows);
+ nTotalWeightX += lclMaxWeight();
+ }
+ else if(j == 0)
+ {
+ BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[nLeft];
+ ScaleFunction::generateSumRows(nWeightX, pTmpX, sumRows);
+ nTotalWeightX += nWeightX;
+ }
+ else if ( nRowRange == j )
+ {
+ BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
+ ScaleFunction::generateSumRows(nWeightX, pTmpX, sumRows);
+ nTotalWeightX += nWeightX;
+ }
+ else
+ {
+ ScaleFunction::generateSumRows(pTmpX, sumRows);
+ nTotalWeightX += lclMaxWeight();
+ }
+ }
+
+ BilinearWeightType nWeightY = lclMaxWeight();
+ if (nY == nEndY)
+ nWeightY = lclMaxWeight();
+ else if (i == 0)
+ nWeightY = lclMaxWeight() - rCtx.maMapFY[nTop];
+ else if (nLineRange == 1)
+ nWeightY = rCtx.maMapFY[nTop];
+ else if (nLineRange == i)
+ nWeightY = rCtx.maMapFY[nBottom];
+
+ if (nTotalWeightX)
+ {
+ ScaleFunction::generateSumRows(nTotalWeightX, sumRows);
+ }
+ ScaleFunction::generateSumNumbers(nWeightY, sumRows, sumNumbers);
+ nTotalWeightY += nWeightY;
+
+ }
+
+ if (nTotalWeightY)
+ {
+ ScaleFunction::generateSumNumbers(nTotalWeightY, sumNumbers);
+ }
+
+ // Write the calculated color components to the destination
+ ScaleFunction::calculateDestination(pScanDest, sumNumbers);
+ }
+ }
+}
+
+template <int nColorBits>
+void scaleUp(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ comphelper::ProfileZone pz("BitmapScaleSuperFilter::scaleUp");
+ constexpr int nColorComponents = nColorBits / 8;
+ static_assert(nColorComponents * 8 == nColorBits, "nColorBits must be divisible by 8");
+ using ScaleFunction = ScaleFunc<nColorComponents>;
+ const sal_Int32 nStartX = 0;
+ const sal_Int32 nEndX = rCtx.mnDestW - 1;
+
+ for (sal_Int32 nY = nStartY; nY <= nEndY; nY++)
+ {
+ sal_Int32 nTempY = rCtx.maMapIY[nY];
+ BilinearWeightType nTempFY = rCtx.maMapFY[nY];
+
+ Scanline pLine0 = rCtx.mpSrc->GetScanline(nTempY+0);
+ Scanline pLine1 = rCtx.mpSrc->GetScanline(nTempY+1);
+ Scanline pScanDest = rCtx.mpDest->GetScanline(nY);
+
+ std::array<sal_uInt8, nColorComponents> nComponents1; // no need to initialize since it's
+ std::array<sal_uInt8, nColorComponents> nComponents2; // initialized in generateComponent
+
+ Scanline pColorPtr0;
+ Scanline pColorPtr1;
+
+ for (sal_Int32 nX = nStartX; nX <= nEndX; nX++)
+ {
+ sal_Int32 nTempX = rCtx.maMapIX[nX];
+ BilinearWeightType nTempFX = rCtx.maMapFX[nX];
+
+ pColorPtr0 = pLine0 + nTempX * nColorComponents;
+ pColorPtr1 = pColorPtr0 + nColorComponents;
+
+ ScaleFunction::generateComponent(pColorPtr0, pColorPtr1, nTempFX, nComponents1);
+
+ pColorPtr0 = pLine1 + nTempX * nColorComponents;
+ pColorPtr1 = pColorPtr0 + nColorComponents;
+
+ ScaleFunction::generateComponent(pColorPtr0, pColorPtr1, nTempFX, nComponents2);
+ ScaleFunction::calculateDestination(pScanDest, nTempFY, nComponents1, nComponents2);
+ }
+ }
+}
+
+class ScaleTask : public comphelper::ThreadTask
+{
+ ScaleRangeFn mpScaleRangeFunction;
+ const ScaleContext& mrContext;
+ sal_Int32 mnStartY;
+ sal_Int32 mnEndY;
+
+public:
+ explicit ScaleTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag,
+ ScaleRangeFn pScaleRangeFunction,
+ const ScaleContext& rContext,
+ sal_Int32 nStartY, sal_Int32 nEndY)
+ : comphelper::ThreadTask(pTag)
+ , mpScaleRangeFunction(pScaleRangeFunction)
+ , mrContext(rContext)
+ , mnStartY(nStartY)
+ , mnEndY(nEndY)
+ {}
+
+ virtual void doWork() override
+ {
+ mpScaleRangeFunction(mrContext, mnStartY, mnEndY);
+ }
+};
+
+void scaleUpPalette8bit(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
+ {
+ sal_Int32 nTempY = rCtx.maMapIY[ nY ];
+ BilinearWeightType nTempFY = rCtx.maMapFY[ nY ];
+ Scanline pLine0 = rCtx.mpSrc->GetScanline( nTempY );
+ Scanline pLine1 = rCtx.mpSrc->GetScanline( ++nTempY );
+ Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
+
+ for(sal_Int32 nX = nStartX, nXDst = 0; nX <= nEndX; nX++ )
+ {
+ sal_Int32 nTempX = rCtx.maMapIX[ nX ];
+ BilinearWeightType nTempFX = rCtx.maMapFX[ nX ];
+
+ const BitmapColor& rCol0 = rCtx.mpSrc->GetPaletteColor( pLine0[ nTempX ] );
+ const BitmapColor& rCol2 = rCtx.mpSrc->GetPaletteColor( pLine1[ nTempX ] );
+ const BitmapColor& rCol1 = rCtx.mpSrc->GetPaletteColor( pLine0[ ++nTempX ] );
+ const BitmapColor& rCol3 = rCtx.mpSrc->GetPaletteColor( pLine1[ nTempX ] );
+
+ sal_uInt8 cR0 = MAP( rCol0.GetRed(), rCol1.GetRed(), nTempFX );
+ sal_uInt8 cG0 = MAP( rCol0.GetGreen(), rCol1.GetGreen(), nTempFX );
+ sal_uInt8 cB0 = MAP( rCol0.GetBlue(), rCol1.GetBlue(), nTempFX );
+
+ sal_uInt8 cR1 = MAP( rCol2.GetRed(), rCol3.GetRed(), nTempFX );
+ sal_uInt8 cG1 = MAP( rCol2.GetGreen(), rCol3.GetGreen(), nTempFX );
+ sal_uInt8 cB1 = MAP( rCol2.GetBlue(), rCol3.GetBlue(), nTempFX );
+
+ BitmapColor aColRes( MAP( cR0, cR1, nTempFY ),
+ MAP( cG0, cG1, nTempFY ),
+ MAP( cB0, cB1, nTempFY ) );
+ rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
+ }
+ }
+}
+
+void scaleUpPaletteGeneral(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
+ {
+ sal_Int32 nTempY = rCtx.maMapIY[ nY ];
+ BilinearWeightType nTempFY = rCtx.maMapFY[ nY ];
+ Scanline pScanline = rCtx.mpDest->GetScanline( nY );
+
+ for( sal_Int32 nX = nStartX, nXDst = 0; nX <= nEndX; nX++ )
+ {
+ sal_Int32 nTempX = rCtx.maMapIX[ nX ];
+ BilinearWeightType nTempFX = rCtx.maMapFX[ nX ];
+
+ BitmapColor aCol0 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( nTempY, nTempX ) );
+ BitmapColor aCol1 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( nTempY, ++nTempX ) );
+ sal_uInt8 cR0 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX );
+ sal_uInt8 cG0 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX );
+ sal_uInt8 cB0 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX );
+
+ aCol1 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( ++nTempY, nTempX ) );
+ aCol0 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( nTempY--, --nTempX ) );
+ sal_uInt8 cR1 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX );
+ sal_uInt8 cG1 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX );
+ sal_uInt8 cB1 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX );
+
+ BitmapColor aColRes( MAP( cR0, cR1, nTempFY ),
+ MAP( cG0, cG1, nTempFY ),
+ MAP( cB0, cB1, nTempFY ) );
+ rCtx.mpDest->SetPixelOnData( pScanline, nXDst++, aColRes );
+ }
+ }
+}
+
+void scaleUpNonPaletteGeneral(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
+ {
+ sal_Int32 nTempY = rCtx.maMapIY[ nY ];
+ BilinearWeightType nTempFY = rCtx.maMapFY[ nY ];
+ Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
+
+ for( sal_Int32 nX = nStartX, nXDst = 0; nX <= nEndX; nX++ )
+ {
+ sal_Int32 nTempX = rCtx.maMapIX[ nX ];
+ BilinearWeightType nTempFX = rCtx.maMapFX[ nX ];
+
+ BitmapColor aCol0 = rCtx.mpSrc->GetPixel( nTempY, nTempX );
+ BitmapColor aCol1 = rCtx.mpSrc->GetPixel( nTempY, ++nTempX );
+ sal_uInt8 cR0 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX );
+ sal_uInt8 cG0 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX );
+ sal_uInt8 cB0 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX );
+
+ aCol1 = rCtx.mpSrc->GetPixel( ++nTempY, nTempX );
+ aCol0 = rCtx.mpSrc->GetPixel( nTempY--, --nTempX );
+ sal_uInt8 cR1 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX );
+ sal_uInt8 cG1 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX );
+ sal_uInt8 cB1 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX );
+
+ BitmapColor aColRes( MAP( cR0, cR1, nTempFY ),
+ MAP( cG0, cG1, nTempFY ),
+ MAP( cB0, cB1, nTempFY ) );
+ rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
+ }
+ }
+}
+
+void scaleDownPalette8bit(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
+ {
+ sal_Int32 nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY;
+ sal_Int32 nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ;
+
+ sal_Int32 nLineStart, nLineRange;
+ if( nY == nEndY )
+ {
+ nLineStart = rCtx.maMapIY[ nY ];
+ nLineRange = 0;
+ }
+ else
+ {
+ nLineStart = rCtx.maMapIY[ nTop ] ;
+ nLineRange = ( rCtx.maMapIY[ nBottom ] == rCtx.maMapIY[ nTop ] ) ? 1 :( rCtx.maMapIY[ nBottom ] - rCtx.maMapIY[ nTop ] );
+ }
+
+ Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
+ for( sal_Int32 nX = nStartX , nXDst = 0; nX <= nEndX; nX++ )
+ {
+ sal_Int32 nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX;
+ sal_Int32 nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ;
+
+ sal_Int32 nRowStart;
+ sal_Int32 nRowRange;
+ if( nX == nEndX )
+ {
+ nRowStart = rCtx.maMapIX[ nX ];
+ nRowRange = 0;
+ }
+ else
+ {
+ nRowStart = rCtx.maMapIX[ nLeft ];
+ nRowRange = ( rCtx.maMapIX[ nRight ] == rCtx.maMapIX[ nLeft ] )? 1 : ( rCtx.maMapIX[ nRight ] - rCtx.maMapIX[ nLeft ] );
+ }
+
+ int nSumR = 0;
+ int nSumG = 0;
+ int nSumB = 0;
+ BilinearWeightType nTotalWeightY = 0;
+
+ for(sal_Int32 i = 0; i<= nLineRange; i++)
+ {
+ Scanline pTmpY = rCtx.mpSrc->GetScanline( nLineStart + i );
+ int nSumRowR = 0;
+ int nSumRowG = 0;
+ int nSumRowB = 0;
+ BilinearWeightType nTotalWeightX = 0;
+
+ for(sal_Int32 j = 0; j <= nRowRange; j++)
+ {
+ const BitmapColor& rCol = rCtx.mpSrc->GetPaletteColor( pTmpY[ nRowStart + j ] );
+
+ if(nX == nEndX )
+ {
+ nSumRowB += rCol.GetBlue() << MAP_PRECISION;
+ nSumRowG += rCol.GetGreen() << MAP_PRECISION;
+ nSumRowR += rCol.GetRed() << MAP_PRECISION;
+ nTotalWeightX += lclMaxWeight();
+ }
+ else if( j == 0 )
+ {
+ BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[ nLeft ];
+ nSumRowB += ( nWeightX *rCol.GetBlue()) ;
+ nSumRowG += ( nWeightX *rCol.GetGreen()) ;
+ nSumRowR += ( nWeightX *rCol.GetRed()) ;
+ nTotalWeightX += nWeightX;
+ }
+ else if ( nRowRange == j )
+ {
+ BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
+ nSumRowB += ( nWeightX *rCol.GetBlue() );
+ nSumRowG += ( nWeightX *rCol.GetGreen() );
+ nSumRowR += ( nWeightX *rCol.GetRed() );
+ nTotalWeightX += nWeightX;
+ }
+ else
+ {
+ nSumRowB += rCol.GetBlue() << MAP_PRECISION;
+ nSumRowG += rCol.GetGreen() << MAP_PRECISION;
+ nSumRowR += rCol.GetRed() << MAP_PRECISION;
+ nTotalWeightX += lclMaxWeight();
+ }
+ }
+
+ BilinearWeightType nWeightY = lclMaxWeight();
+ if( nY == nEndY )
+ nWeightY = lclMaxWeight();
+ else if( i == 0 )
+ nWeightY = lclMaxWeight() - rCtx.maMapFY[ nTop ];
+ else if( nLineRange == 1 )
+ nWeightY = rCtx.maMapFY[ nTop ];
+ else if ( nLineRange == i )
+ nWeightY = rCtx.maMapFY[ nBottom ];
+
+ if (nTotalWeightX)
+ {
+ nSumRowB /= nTotalWeightX;
+ nSumRowG /= nTotalWeightX;
+ nSumRowR /= nTotalWeightX;
+ }
+
+ nSumB += nWeightY * nSumRowB;
+ nSumG += nWeightY * nSumRowG;
+ nSumR += nWeightY * nSumRowR;
+ nTotalWeightY += nWeightY;
+ }
+
+ if (nTotalWeightY)
+ {
+ nSumR /= nTotalWeightY;
+ nSumG /= nTotalWeightY;
+ nSumB /= nTotalWeightY;
+ }
+
+ BitmapColor aColRes(static_cast<sal_uInt8>(nSumR), static_cast<sal_uInt8>(nSumG), static_cast<sal_uInt8>(nSumB));
+ rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
+ }
+ }
+}
+
+void scaleDownPaletteGeneral(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
+ {
+ sal_Int32 nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY;
+ sal_Int32 nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ;
+
+ sal_Int32 nLineStart, nLineRange;
+ if( nY ==nEndY )
+ {
+ nLineStart = rCtx.maMapIY[ nY ];
+ nLineRange = 0;
+ }
+ else
+ {
+ nLineStart = rCtx.maMapIY[ nTop ] ;
+ nLineRange = ( rCtx.maMapIY[ nBottom ] == rCtx.maMapIY[ nTop ] ) ? 1 :( rCtx.maMapIY[ nBottom ] - rCtx.maMapIY[ nTop ] );
+ }
+
+ Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
+ for( sal_Int32 nX = nStartX , nXDst = 0; nX <= nEndX; nX++ )
+ {
+ sal_Int32 nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX;
+ sal_Int32 nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ;
+
+ sal_Int32 nRowStart, nRowRange;
+ if( nX == nEndX )
+ {
+ nRowStart = rCtx.maMapIX[ nX ];
+ nRowRange = 0;
+ }
+ else
+ {
+ nRowStart = rCtx.maMapIX[ nLeft ];
+ nRowRange = ( rCtx.maMapIX[ nRight ] == rCtx.maMapIX[ nLeft ] )? 1 : ( rCtx.maMapIX[ nRight ] - rCtx.maMapIX[ nLeft ] );
+ }
+
+ int nSumR = 0;
+ int nSumG = 0;
+ int nSumB = 0;
+ BilinearWeightType nTotalWeightY = 0;
+
+ for(sal_Int32 i = 0; i<= nLineRange; i++)
+ {
+ int nSumRowR = 0;
+ int nSumRowG = 0;
+ int nSumRowB = 0;
+ BilinearWeightType nTotalWeightX = 0;
+
+ Scanline pScanlineSrc = rCtx.mpSrc->GetScanline( nLineStart + i );
+ for(sal_Int32 j = 0; j <= nRowRange; j++)
+ {
+ BitmapColor aCol0 = rCtx.mpSrc->GetPaletteColor ( rCtx.mpSrc->GetIndexFromData( pScanlineSrc, nRowStart + j ) );
+
+ if(nX == nEndX )
+ {
+
+ nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
+ nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
+ nSumRowR += aCol0.GetRed() << MAP_PRECISION;
+ nTotalWeightX += lclMaxWeight();
+ }
+ else if( j == 0 )
+ {
+
+ BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[ nLeft ];
+ nSumRowB += ( nWeightX *aCol0.GetBlue()) ;
+ nSumRowG += ( nWeightX *aCol0.GetGreen()) ;
+ nSumRowR += ( nWeightX *aCol0.GetRed()) ;
+ nTotalWeightX += nWeightX;
+ }
+ else if ( nRowRange == j )
+ {
+
+ BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
+ nSumRowB += ( nWeightX *aCol0.GetBlue() );
+ nSumRowG += ( nWeightX *aCol0.GetGreen() );
+ nSumRowR += ( nWeightX *aCol0.GetRed() );
+ nTotalWeightX += nWeightX;
+ }
+ else
+ {
+
+ nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
+ nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
+ nSumRowR += aCol0.GetRed() << MAP_PRECISION;
+ nTotalWeightX += lclMaxWeight();
+ }
+ }
+
+ sal_Int32 nWeightY = lclMaxWeight();
+ if( nY == nEndY )
+ nWeightY = lclMaxWeight();
+ else if( i == 0 )
+ nWeightY = lclMaxWeight() - rCtx.maMapFY[ nTop ];
+ else if( nLineRange == 1 )
+ nWeightY = rCtx.maMapFY[ nTop ];
+ else if ( nLineRange == i )
+ nWeightY = rCtx.maMapFY[ nBottom ];
+
+ if (nTotalWeightX)
+ {
+ nSumRowB /= nTotalWeightX;
+ nSumRowG /= nTotalWeightX;
+ nSumRowR /= nTotalWeightX;
+ }
+
+ nSumB += nWeightY * nSumRowB;
+ nSumG += nWeightY * nSumRowG;
+ nSumR += nWeightY * nSumRowR;
+ nTotalWeightY += nWeightY;
+ }
+
+ if (nTotalWeightY)
+ {
+ nSumR /= nTotalWeightY;
+ nSumG /= nTotalWeightY;
+ nSumB /= nTotalWeightY;
+ }
+
+ BitmapColor aColRes(static_cast<sal_uInt8>(nSumR), static_cast<sal_uInt8>(nSumG), static_cast<sal_uInt8>(nSumB));
+ rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
+ }
+ }
+}
+
+void scaleDownNonPaletteGeneral(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
+{
+ const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
+
+ for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
+ {
+ sal_Int32 nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY;
+ sal_Int32 nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ;
+
+ sal_Int32 nLineStart, nLineRange;
+ if( nY ==nEndY )
+ {
+ nLineStart = rCtx.maMapIY[ nY ];
+ nLineRange = 0;
+ }
+ else
+ {
+ nLineStart = rCtx.maMapIY[ nTop ] ;
+ nLineRange = ( rCtx.maMapIY[ nBottom ] == rCtx.maMapIY[ nTop ] ) ? 1 :( rCtx.maMapIY[ nBottom ] - rCtx.maMapIY[ nTop ] );
+ }
+
+ Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
+ for( sal_Int32 nX = nStartX , nXDst = 0; nX <= nEndX; nX++ )
+ {
+ sal_Int32 nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX;
+ sal_Int32 nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ;
+
+ sal_Int32 nRowStart, nRowRange;
+ if( nX == nEndX )
+ {
+ nRowStart = rCtx.maMapIX[ nX ];
+ nRowRange = 0;
+ }
+ else
+ {
+ nRowStart = rCtx.maMapIX[ nLeft ];
+ nRowRange = ( rCtx.maMapIX[ nRight ] == rCtx.maMapIX[ nLeft ] )? 1 : ( rCtx.maMapIX[ nRight ] - rCtx.maMapIX[ nLeft ] );
+ }
+
+ int nSumR = 0;
+ int nSumG = 0;
+ int nSumB = 0;
+ BilinearWeightType nTotalWeightY = 0;
+
+ for(sal_Int32 i = 0; i<= nLineRange; i++)
+ {
+ int nSumRowR = 0;
+ int nSumRowG = 0;
+ int nSumRowB = 0;
+ BilinearWeightType nTotalWeightX = 0;
+
+ Scanline pScanlineSrc = rCtx.mpSrc->GetScanline( nLineStart + i );
+ for(sal_Int32 j = 0; j <= nRowRange; j++)
+ {
+ BitmapColor aCol0 = rCtx.mpSrc->GetPixelFromData( pScanlineSrc, nRowStart + j );
+
+ if(nX == nEndX )
+ {
+
+ nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
+ nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
+ nSumRowR += aCol0.GetRed() << MAP_PRECISION;
+ nTotalWeightX += lclMaxWeight();
+ }
+ else if( j == 0 )
+ {
+
+ BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[ nLeft ];
+ nSumRowB += ( nWeightX *aCol0.GetBlue()) ;
+ nSumRowG += ( nWeightX *aCol0.GetGreen()) ;
+ nSumRowR += ( nWeightX *aCol0.GetRed()) ;
+ nTotalWeightX += nWeightX;
+ }
+ else if ( nRowRange == j )
+ {
+
+ BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
+ nSumRowB += ( nWeightX *aCol0.GetBlue() );
+ nSumRowG += ( nWeightX *aCol0.GetGreen() );
+ nSumRowR += ( nWeightX *aCol0.GetRed() );
+ nTotalWeightX += nWeightX;
+ }
+ else
+ {
+ nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
+ nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
+ nSumRowR += aCol0.GetRed() << MAP_PRECISION;
+ nTotalWeightX += lclMaxWeight();
+ }
+ }
+
+ BilinearWeightType nWeightY = lclMaxWeight();
+ if( nY == nEndY )
+ nWeightY = lclMaxWeight();
+ else if( i == 0 )
+ nWeightY = lclMaxWeight() - rCtx.maMapFY[ nTop ];
+ else if( nLineRange == 1 )
+ nWeightY = rCtx.maMapFY[ nTop ];
+ else if ( nLineRange == i )
+ nWeightY = rCtx.maMapFY[ nBottom ];
+
+ if (nTotalWeightX)
+ {
+ nSumRowB /= nTotalWeightX;
+ nSumRowG /= nTotalWeightX;
+ nSumRowR /= nTotalWeightX;
+ }
+
+ nSumB += nWeightY * nSumRowB;
+ nSumG += nWeightY * nSumRowG;
+ nSumR += nWeightY * nSumRowR;
+ nTotalWeightY += nWeightY;
+ }
+
+ if (nTotalWeightY)
+ {
+ nSumR /= nTotalWeightY;
+ nSumG /= nTotalWeightY;
+ nSumB /= nTotalWeightY;
+ }
+
+ BitmapColor aColRes(static_cast<sal_uInt8>(nSumR), static_cast<sal_uInt8>(nSumG), static_cast<sal_uInt8>(nSumB));
+ rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
+ }
+ }
+}
+
+} // end anonymous namespace
+
+BitmapScaleSuperFilter::BitmapScaleSuperFilter(const double& rScaleX, const double& rScaleY) :
+ mrScaleX(rScaleX),
+ mrScaleY(rScaleY)
+{}
+
+BitmapScaleSuperFilter::~BitmapScaleSuperFilter()
+{}
+
+BitmapEx BitmapScaleSuperFilter::execute(BitmapEx const& rBitmap) const
+{
+ Bitmap aBitmap(rBitmap.GetBitmap());
+ bool bRet = false;
+
+ const Size aSizePix(rBitmap.GetSizePixel());
+
+ bool bHMirr = mrScaleX < 0;
+ bool bVMirr = mrScaleY < 0;
+
+ double fScaleX = std::fabs(mrScaleX);
+ double fScaleY = std::fabs(mrScaleY);
+
+ const sal_Int32 nDstW = FRound(aSizePix.Width() * fScaleX);
+ const sal_Int32 nDstH = FRound(aSizePix.Height() * fScaleY);
+
+ constexpr double fScaleThresh = 0.6;
+
+ if (nDstW <= 1 || nDstH <= 1)
+ return BitmapEx();
+
+ // check cache for a previously scaled version of this
+ ScaleCacheKey aKey(aBitmap.ImplGetSalBitmap().get(),
+ Size(nDstW, nDstH));
+
+ ImplSVData* pSVData = ImplGetSVData();
+ auto& rCache = pSVData->maGDIData.maScaleCache;
+ auto aFind = rCache.find(aKey);
+ if (aFind != rCache.end())
+ {
+ if (aFind->second.GetSizePixel().Width() == nDstW && aFind->second.GetSizePixel().Height() == nDstH)
+ return aFind->second;
+ else
+ SAL_WARN("vcl.gdi", "Error: size mismatch in scale cache");
+ }
+
+ {
+ Bitmap::ScopedReadAccess pReadAccess(aBitmap);
+
+ // If source format is less than 24BPP, use 24BPP
+ auto eSourcePixelFormat = aBitmap.getPixelFormat();
+ auto ePixelFormat = eSourcePixelFormat;
+ if (sal_uInt16(eSourcePixelFormat) < 24)
+ ePixelFormat = vcl::PixelFormat::N24_BPP;
+
+ Bitmap aOutBmp(Size(nDstW, nDstH), ePixelFormat);
+ Size aOutSize = aOutBmp.GetSizePixel();
+ auto eTargetPixelFormat = aOutBmp.getPixelFormat();
+
+ if (!aOutSize.Width() || !aOutSize.Height())
+ {
+ SAL_WARN("vcl.gdi", "bmp creation failed");
+ return BitmapEx();
+ }
+
+ BitmapScopedWriteAccess pWriteAccess(aOutBmp);
+
+ const sal_Int32 nEndY = nDstH - 1;
+
+ if (pReadAccess && pWriteAccess)
+ {
+ ScaleRangeFn pScaleRangeFn;
+ const ScaleContext aContext( pReadAccess.get(),
+ pWriteAccess.get(),
+ pReadAccess->Width(),
+ pWriteAccess->Width(),
+ pReadAccess->Height(),
+ pWriteAccess->Height(),
+ bVMirr, bHMirr );
+
+ bool bScaleUp = fScaleX >= fScaleThresh && fScaleY >= fScaleThresh;
+ // If we have a source bitmap with a palette the scaling converts
+ // from up to 8 bit image -> 24 bit non-palette, which is then
+ // adapted back to the same type as original.
+ if (pReadAccess->HasPalette())
+ {
+ switch( pReadAccess->GetScanlineFormat() )
+ {
+ case ScanlineFormat::N8BitPal:
+ pScaleRangeFn = bScaleUp ? scaleUpPalette8bit
+ : scaleDownPalette8bit;
+ break;
+ default:
+ pScaleRangeFn = bScaleUp ? scaleUpPaletteGeneral
+ : scaleDownPaletteGeneral;
+ break;
+ }
+ }
+ // Here we know that we are dealing with a non-palette source bitmap.
+ // The target is either 24 or 32 bit, depending on the image and
+ // the capabilities of the backend. If for some reason the destination
+ // is not the same bit-depth as the source, then we can't use
+ // a fast path, so we always need to process with a general scaler.
+ else if (eSourcePixelFormat != eTargetPixelFormat)
+ {
+ pScaleRangeFn = bScaleUp ? scaleUpNonPaletteGeneral : scaleDownNonPaletteGeneral;
+ }
+ // If we get here then we can only use a fast path, but let's
+ // still keep the fallback to the general scaler alive.
+ else
+ {
+ switch( pReadAccess->GetScanlineFormat() )
+ {
+ case ScanlineFormat::N24BitTcBgr:
+ case ScanlineFormat::N24BitTcRgb:
+ pScaleRangeFn = bScaleUp ? scaleUp<24> : scaleDown<24>;
+ break;
+ case ScanlineFormat::N32BitTcRgba:
+ case ScanlineFormat::N32BitTcBgra:
+ case ScanlineFormat::N32BitTcArgb:
+ case ScanlineFormat::N32BitTcAbgr:
+ pScaleRangeFn = bScaleUp ? scaleUp<32> : scaleDown<32>;
+ break;
+ default:
+ pScaleRangeFn = bScaleUp ? scaleUpNonPaletteGeneral
+ : scaleDownNonPaletteGeneral;
+ break;
+ }
+ }
+
+ // We want to thread - only if there is a lot of work to do:
+ // We work hard when there is a large destination image, or
+ // A large source image.
+ bool bHorizontalWork = pReadAccess->Height() >= 512 && pReadAccess->Width() >= 512;
+ bool bUseThreads = true;
+ const sal_Int32 nStartY = 0;
+
+ static bool bDisableThreadedScaling = getenv ("VCL_NO_THREAD_SCALE");
+ if (bDisableThreadedScaling || !bHorizontalWork)
+ {
+ SAL_INFO("vcl.gdi", "Scale in main thread");
+ bUseThreads = false;
+ }
+
+ if (bUseThreads)
+ {
+ try
+ {
+ // partition and queue work
+ comphelper::ThreadPool &rShared = comphelper::ThreadPool::getSharedOptimalPool();
+ std::shared_ptr<comphelper::ThreadTaskTag> pTag = comphelper::ThreadPool::createThreadTaskTag();
+
+ vcl::bitmap::generateStripRanges<constScaleThreadStrip>(nStartY, nEndY,
+ [&] (sal_Int32 const nStart, sal_Int32 const nEnd, bool const bLast)
+ {
+ if (!bLast)
+ {
+ auto pTask(std::make_unique<ScaleTask>(pTag, pScaleRangeFn, aContext, nStart, nEnd));
+ rShared.pushTask(std::move(pTask));
+ }
+ else
+ pScaleRangeFn(aContext, nStart, nEnd);
+ });
+ rShared.waitUntilDone(pTag);
+ SAL_INFO("vcl.gdi", "All threaded scaling tasks complete");
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl.gdi", "threaded bitmap scaling failed");
+ bUseThreads = false;
+ }
+ }
+
+ if (!bUseThreads)
+ pScaleRangeFn( aContext, nStartY, nEndY );
+
+ pWriteAccess.reset();
+ bRet = true;
+ aBitmap.AdaptBitCount(aOutBmp);
+ aBitmap = aOutBmp;
+ }
+ }
+
+ if (bRet)
+ {
+ tools::Rectangle aRect(Point(0, 0), Point(nDstW, nDstH));
+ aBitmap.Crop(aRect);
+ BitmapEx aRet(aBitmap);
+ rCache.insert(std::make_pair(aKey, aRet));
+ return aRet;
+ }
+
+ return BitmapEx();
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSeparableUnsharpenFilter.cxx b/vcl/source/bitmap/BitmapSeparableUnsharpenFilter.cxx
new file mode 100644
index 000000000..bbf5950dc
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSeparableUnsharpenFilter.cxx
@@ -0,0 +1,75 @@
+/* -*- 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/.
+ *
+ */
+
+#include <tools/helpers.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapGaussianSeparableBlurFilter.hxx>
+#include <vcl/BitmapSeparableUnsharpenFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapSeparableUnsharpenFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ const sal_Int32 nWidth = aBitmap.GetSizePixel().Width();
+ const sal_Int32 nHeight = aBitmap.GetSizePixel().Height();
+
+ Bitmap aBlur(aBitmap);
+ BitmapEx aBlurEx(aBlur);
+
+ BitmapFilter::Filter(aBlurEx, BitmapGaussianSeparableBlurFilter(-mfRadius));
+ aBlur = aBlurEx.GetBitmap();
+
+ // Amount of unsharpening effect on image - currently set to a fixed value
+ double aAmount = 2.0;
+
+ Bitmap aResultBitmap(Size(nWidth, nHeight), vcl::PixelFormat::N24_BPP);
+
+ Bitmap::ScopedReadAccess pReadAccBlur(aBlur);
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+ BitmapScopedWriteAccess pWriteAcc(aResultBitmap);
+
+ BitmapColor aColor, aColorBlur;
+
+ // For all pixels in original image subtract pixels values from blurred image
+ for (sal_Int32 y = 0; y < nHeight; y++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(y);
+ for (sal_Int32 x = 0; x < nWidth; x++)
+ {
+ aColorBlur = pReadAccBlur->GetColor(y, x);
+ aColor = pReadAcc->GetColor(y, x);
+
+ BitmapColor aResultColor(
+ static_cast<sal_uInt8>(MinMax(
+ aColor.GetRed() + (aColor.GetRed() - aColorBlur.GetRed()) * aAmount, 0, 255)),
+ static_cast<sal_uInt8>(MinMax(
+ aColor.GetGreen() + (aColor.GetGreen() - aColorBlur.GetGreen()) * aAmount, 0,
+ 255)),
+ static_cast<sal_uInt8>(
+ MinMax(aColor.GetBlue() + (aColor.GetBlue() - aColorBlur.GetBlue()) * aAmount,
+ 0, 255)));
+
+ pWriteAcc->SetPixelOnData(pScanline, x, aResultColor);
+ }
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+ pReadAccBlur.reset();
+ aBitmap.ReassignWithSize(aResultBitmap);
+
+ return BitmapEx(aBitmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSepiaFilter.cxx b/vcl/source/bitmap/BitmapSepiaFilter.cxx
new file mode 100644
index 000000000..1554def4d
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSepiaFilter.cxx
@@ -0,0 +1,110 @@
+/* -*- 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/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapSepiaFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+#include <algorithm>
+
+BitmapEx BitmapSepiaFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+ bool bRet = false;
+
+ if (pReadAcc)
+ {
+ const sal_Int32 nSepia
+ = 10000 - 100 * std::clamp(mnSepiaPercent, sal_uInt16(0), sal_uInt16(100));
+ BitmapPalette aSepiaPal(256);
+
+ for (sal_uInt16 i = 0; i < 256; i++)
+ {
+ BitmapColor& rCol = aSepiaPal[i];
+ const sal_uInt8 cSepiaValue = static_cast<sal_uInt8>(nSepia * i / 10000);
+
+ rCol.SetRed(static_cast<sal_uInt8>(i));
+ rCol.SetGreen(cSepiaValue);
+ rCol.SetBlue(cSepiaValue);
+ }
+
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N8_BPP, &aSepiaPal);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ BitmapColor aCol(sal_uInt8(0));
+ const sal_Int32 nWidth = pWriteAcc->Width();
+ const sal_Int32 nHeight = pWriteAcc->Height();
+
+ if (pReadAcc->HasPalette())
+ {
+ const sal_uInt16 nPalCount = pReadAcc->GetPaletteEntryCount();
+ std::unique_ptr<sal_uInt8[]> pIndexMap(new sal_uInt8[nPalCount]);
+ for (sal_uInt16 i = 0; i < nPalCount; i++)
+ {
+ pIndexMap[i] = pReadAcc->GetPaletteColor(i).GetLuminance();
+ }
+
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ aCol.SetIndex(pIndexMap[pReadAcc->GetIndexFromData(pScanlineRead, nX)]);
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+ }
+ }
+ else
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ aCol.SetIndex(pReadAcc->GetPixelFromData(pScanlineRead, nX).GetLuminance());
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapShadowFilter.cxx b/vcl/source/bitmap/BitmapShadowFilter.cxx
new file mode 100644
index 000000000..3dd48556b
--- /dev/null
+++ b/vcl/source/bitmap/BitmapShadowFilter.cxx
@@ -0,0 +1,48 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapColor.hxx>
+#include <vcl/BitmapShadowFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapShadowFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ BitmapEx aBitmapEx(rBitmapEx);
+ BitmapScopedWriteAccess pWriteAccess(const_cast<Bitmap&>(aBitmapEx.GetBitmap()));
+
+ if (!pWriteAccess)
+ return rBitmapEx;
+
+ for (sal_Int32 y(0); y < sal_Int32(pWriteAccess->Height()); y++)
+ {
+ Scanline pScanline = pWriteAccess->GetScanline(y);
+
+ for (sal_Int32 x(0); x < sal_Int32(pWriteAccess->Width()); x++)
+ {
+ const BitmapColor aColor = pWriteAccess->GetColor(y, x);
+ sal_uInt16 nLuminance(static_cast<sal_uInt16>(aColor.GetLuminance()) + 1);
+ const BitmapColor aDestColor(
+ static_cast<sal_uInt8>(
+ (nLuminance * static_cast<sal_uInt16>(maShadowColor.GetRed())) >> 8),
+ static_cast<sal_uInt8>(
+ (nLuminance * static_cast<sal_uInt16>(maShadowColor.GetGreen())) >> 8),
+ static_cast<sal_uInt8>(
+ (nLuminance * static_cast<sal_uInt16>(maShadowColor.GetBlue())) >> 8));
+
+ pWriteAccess->SetPixelOnData(pScanline, x, aDestColor);
+ }
+ }
+
+ return aBitmapEx;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSimpleColorQuantizationFilter.cxx b/vcl/source/bitmap/BitmapSimpleColorQuantizationFilter.cxx
new file mode 100644
index 000000000..f825013d4
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSimpleColorQuantizationFilter.cxx
@@ -0,0 +1,110 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapSimpleColorQuantizationFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <bitmap/Octree.hxx>
+
+BitmapEx BitmapSimpleColorQuantizationFilter::execute(BitmapEx const& aBitmapEx) const
+{
+ Bitmap aBitmap = aBitmapEx.GetBitmap();
+
+ bool bRet = false;
+
+ if (vcl::numberOfColors(aBitmap.getPixelFormat()) <= sal_Int64(mnNewColorCount))
+ {
+ bRet = true;
+ }
+ else
+ {
+ Bitmap aNewBmp;
+ Bitmap::ScopedReadAccess pRAcc(aBitmap);
+ const sal_uInt16 nColorCount = std::min(mnNewColorCount, sal_uInt16(256));
+ auto ePixelFormat = vcl::PixelFormat::INVALID;
+
+ if (nColorCount <= 2)
+ ePixelFormat = vcl::PixelFormat::N1_BPP;
+ else
+ ePixelFormat = vcl::PixelFormat::N8_BPP;
+
+ if (pRAcc)
+ {
+ Octree aOct(*pRAcc, nColorCount);
+ const BitmapPalette& rPal = aOct.GetPalette();
+
+ aNewBmp = Bitmap(aBitmap.GetSizePixel(), ePixelFormat, &rPal);
+ BitmapScopedWriteAccess pWAcc(aNewBmp);
+
+ if (pWAcc)
+ {
+ const sal_Int32 nWidth = pRAcc->Width();
+ const sal_Int32 nHeight = pRAcc->Height();
+
+ if (pRAcc->HasPalette())
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWAcc->GetScanline(nY);
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ auto c = pRAcc->GetPaletteColor(
+ pRAcc->GetIndexFromData(pScanlineRead, nX));
+ pWAcc->SetPixelOnData(
+ pScanline, nX,
+ BitmapColor(static_cast<sal_uInt8>(aOct.GetBestPaletteIndex(c))));
+ }
+ }
+ }
+ else
+ {
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWAcc->GetScanline(nY);
+ Scanline pScanlineRead = pRAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ auto c = pRAcc->GetPixelFromData(pScanlineRead, nX);
+ pWAcc->SetPixelOnData(
+ pScanline, nX,
+ BitmapColor(static_cast<sal_uInt8>(aOct.GetBestPaletteIndex(c))));
+ }
+ }
+ }
+
+ pWAcc.reset();
+ bRet = true;
+ }
+
+ pRAcc.reset();
+ }
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aSize);
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSmoothenFilter.cxx b/vcl/source/bitmap/BitmapSmoothenFilter.cxx
new file mode 100644
index 000000000..e9c135f8e
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSmoothenFilter.cxx
@@ -0,0 +1,32 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapGaussianSeparableBlurFilter.hxx>
+#include <vcl/BitmapSeparableUnsharpenFilter.hxx>
+#include <vcl/BitmapSmoothenFilter.hxx>
+
+BitmapEx BitmapSmoothenFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ BitmapEx aBitmapEx(rBitmapEx);
+ bool bRet = false;
+
+ if (mfRadius > 0.0) // Blur for positive values of mnRadius
+ bRet = BitmapFilter::Filter(aBitmapEx, BitmapGaussianSeparableBlurFilter(mfRadius));
+ else if (mfRadius < 0.0) // Unsharpen mask for negative values of mnRadius
+ bRet = BitmapFilter::Filter(aBitmapEx, BitmapSeparableUnsharpenFilter(mfRadius));
+
+ if (bRet)
+ return aBitmapEx;
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSobelGreyFilter.cxx b/vcl/source/bitmap/BitmapSobelGreyFilter.cxx
new file mode 100644
index 000000000..dd77d633a
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSobelGreyFilter.cxx
@@ -0,0 +1,171 @@
+/* -*- 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/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapSobelGreyFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+#include <algorithm>
+
+BitmapEx BitmapSobelGreyFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ bool bRet = aBitmap.ImplMakeGreyscales();
+
+ if (bRet)
+ {
+ bRet = false;
+
+ Bitmap::ScopedReadAccess pReadAcc(aBitmap);
+
+ if (pReadAcc)
+ {
+ Bitmap aNewBmp(aBitmap.GetSizePixel(), vcl::PixelFormat::N8_BPP,
+ &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ BitmapColor aGrey(sal_uInt8(0));
+ const sal_Int32 nWidth = pWriteAcc->Width();
+ const sal_Int32 nHeight = pWriteAcc->Height();
+ const sal_Int32 nMask111 = -1, nMask121 = 0, nMask131 = 1;
+ const sal_Int32 nMask211 = -2, nMask221 = 0, nMask231 = 2;
+ const sal_Int32 nMask311 = -1, nMask321 = 0, nMask331 = 1;
+ const sal_Int32 nMask112 = 1, nMask122 = 2, nMask132 = 1;
+ const sal_Int32 nMask212 = 0, nMask222 = 0, nMask232 = 0;
+ const sal_Int32 nMask312 = -1, nMask322 = -2, nMask332 = -1;
+ sal_Int32 nGrey11, nGrey12, nGrey13;
+ sal_Int32 nGrey21, nGrey22, nGrey23;
+ sal_Int32 nGrey31, nGrey32, nGrey33;
+ std::unique_ptr<long[]> pHMap(new long[nWidth + 2]);
+ std::unique_ptr<long[]> pVMap(new long[nHeight + 2]);
+ sal_Int32 nX, nY, nSum1, nSum2;
+
+ // fill mapping tables
+ pHMap[0] = 0;
+
+ for (nX = 1; nX <= nWidth; nX++)
+ {
+ pHMap[nX] = nX - 1;
+ }
+
+ pHMap[nWidth + 1] = nWidth - 1;
+
+ pVMap[0] = 0;
+
+ for (nY = 1; nY <= nHeight; nY++)
+ {
+ pVMap[nY] = nY - 1;
+ }
+
+ pVMap[nHeight + 1] = nHeight - 1;
+
+ for (nY = 0; nY < nHeight; nY++)
+ {
+ nGrey11 = pReadAcc->GetPixel(pVMap[nY], pHMap[0]).GetIndex();
+ nGrey12 = pReadAcc->GetPixel(pVMap[nY], pHMap[1]).GetIndex();
+ nGrey13 = pReadAcc->GetPixel(pVMap[nY], pHMap[2]).GetIndex();
+ nGrey21 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[0]).GetIndex();
+ nGrey22 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[1]).GetIndex();
+ nGrey23 = pReadAcc->GetPixel(pVMap[nY + 1], pHMap[2]).GetIndex();
+ nGrey31 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[0]).GetIndex();
+ nGrey32 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[1]).GetIndex();
+ nGrey33 = pReadAcc->GetPixel(pVMap[nY + 2], pHMap[2]).GetIndex();
+
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (nX = 0; nX < nWidth; nX++)
+ {
+ nSum1 = nSum2 = 0;
+
+ nSum1 += nMask111 * nGrey11;
+ nSum2 += nMask112 * nGrey11;
+
+ nSum1 += nMask121 * nGrey12;
+ nSum2 += nMask122 * nGrey12;
+
+ nSum1 += nMask131 * nGrey13;
+ nSum2 += nMask132 * nGrey13;
+
+ nSum1 += nMask211 * nGrey21;
+ nSum2 += nMask212 * nGrey21;
+
+ nSum1 += nMask221 * nGrey22;
+ nSum2 += nMask222 * nGrey22;
+
+ nSum1 += nMask231 * nGrey23;
+ nSum2 += nMask232 * nGrey23;
+
+ nSum1 += nMask311 * nGrey31;
+ nSum2 += nMask312 * nGrey31;
+
+ nSum1 += nMask321 * nGrey32;
+ nSum2 += nMask322 * nGrey32;
+
+ nSum1 += nMask331 * nGrey33;
+ nSum2 += nMask332 * nGrey33;
+
+ nSum1 = static_cast<sal_Int32>(
+ sqrt(static_cast<double>(nSum1 * nSum1 + nSum2 * nSum2)));
+
+ aGrey.SetIndex(~static_cast<sal_uInt8>(
+ std::clamp(nSum1, sal_Int32(0), sal_Int32(255))));
+ pWriteAcc->SetPixelOnData(pScanline, nX, aGrey);
+
+ if (nX < (nWidth - 1))
+ {
+ const sal_Int32 nNextX = pHMap[nX + 3];
+
+ nGrey11 = nGrey12;
+ nGrey12 = nGrey13;
+ nGrey13 = pReadAcc->GetPixel(pVMap[nY], nNextX).GetIndex();
+ nGrey21 = nGrey22;
+ nGrey22 = nGrey23;
+ nGrey23 = pReadAcc->GetPixel(pVMap[nY + 1], nNextX).GetIndex();
+ nGrey31 = nGrey32;
+ nGrey32 = nGrey33;
+ nGrey33 = pReadAcc->GetPixel(pVMap[nY + 2], nNextX).GetIndex();
+ }
+ }
+ }
+
+ pHMap.reset();
+ pVMap.reset();
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(aBitmap.GetPrefMapMode());
+ const Size aPrefSize(aBitmap.GetPrefSize());
+
+ aBitmap = aNewBmp;
+
+ aBitmap.SetPrefMapMode(aMap);
+ aBitmap.SetPrefSize(aPrefSize);
+ }
+ }
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSolarizeFilter.cxx b/vcl/source/bitmap/BitmapSolarizeFilter.cxx
new file mode 100644
index 000000000..bfc502fe0
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSolarizeFilter.cxx
@@ -0,0 +1,71 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapSolarizeFilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+BitmapEx BitmapSolarizeFilter::execute(BitmapEx const& rBitmapEx) const
+{
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+ bool bRet = false;
+ BitmapScopedWriteAccess pWriteAcc(aBitmap);
+
+ if (pWriteAcc)
+ {
+ if (pWriteAcc->HasPalette())
+ {
+ const BitmapPalette& rPal = pWriteAcc->GetPalette();
+
+ for (sal_uInt16 i = 0, nCount = rPal.GetEntryCount(); i < nCount; i++)
+ {
+ if (rPal[i].GetLuminance() >= mcSolarGreyThreshold)
+ {
+ BitmapColor aCol(rPal[i]);
+ aCol.Invert();
+ pWriteAcc->SetPaletteColor(i, aCol);
+ }
+ }
+ }
+ else
+ {
+ BitmapColor aCol;
+ const sal_Int32 nWidth = pWriteAcc->Width();
+ const sal_Int32 nHeight = pWriteAcc->Height();
+
+ for (sal_Int32 nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (sal_Int32 nX = 0; nX < nWidth; nX++)
+ {
+ aCol = pWriteAcc->GetPixelFromData(pScanline, nX);
+
+ if (aCol.GetLuminance() >= mcSolarGreyThreshold)
+ {
+ aCol.Invert();
+ pWriteAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ if (bRet)
+ return BitmapEx(aBitmap);
+
+ return BitmapEx();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapSymmetryCheck.cxx b/vcl/source/bitmap/BitmapSymmetryCheck.cxx
new file mode 100644
index 000000000..1bd16e815
--- /dev/null
+++ b/vcl/source/bitmap/BitmapSymmetryCheck.cxx
@@ -0,0 +1,83 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/BitmapReadAccess.hxx>
+
+#include <BitmapSymmetryCheck.hxx>
+
+BitmapSymmetryCheck::BitmapSymmetryCheck()
+{}
+
+bool BitmapSymmetryCheck::check(Bitmap& rBitmap)
+{
+ Bitmap::ScopedReadAccess aReadAccess(rBitmap);
+ return checkImpl(aReadAccess.get());
+}
+
+bool BitmapSymmetryCheck::checkImpl(BitmapReadAccess const * pReadAccess)
+{
+ tools::Long nHeight = pReadAccess->Height();
+ tools::Long nWidth = pReadAccess->Width();
+
+ tools::Long nHeightHalf = nHeight / 2;
+ tools::Long nWidthHalf = nWidth / 2;
+
+ bool bHeightEven = (nHeight % 2) == 0;
+ bool bWidthEven = (nWidth % 2) == 0;
+
+ for (tools::Long y = 0; y < nHeightHalf; ++y)
+ {
+ Scanline pScanlineRead = pReadAccess->GetScanline( y );
+ Scanline pScanlineRead2 = pReadAccess->GetScanline( nHeight - y - 1 );
+ for (tools::Long x = 0; x < nWidthHalf; ++x)
+ {
+ if (pReadAccess->GetPixelFromData(pScanlineRead, x) != pReadAccess->GetPixelFromData(pScanlineRead2, x))
+ {
+ return false;
+ }
+ if (pReadAccess->GetPixelFromData(pScanlineRead, x) != pReadAccess->GetPixelFromData(pScanlineRead, nWidth - x - 1))
+ {
+ return false;
+ }
+ if (pReadAccess->GetPixelFromData(pScanlineRead, x) != pReadAccess->GetPixelFromData(pScanlineRead2, nWidth - x - 1))
+ {
+ return false;
+ }
+ }
+ }
+
+ if (bWidthEven)
+ {
+ for (tools::Long y = 0; y < nHeightHalf; ++y)
+ {
+ if (pReadAccess->GetPixel(y, nWidthHalf) != pReadAccess->GetPixel(nHeight - y - 1, nWidthHalf))
+ {
+ return false;
+ }
+ }
+ }
+
+ if (bHeightEven)
+ {
+ Scanline pScanlineRead = pReadAccess->GetScanline( nHeightHalf );
+ for (tools::Long x = 0; x < nWidthHalf; ++x)
+ {
+ if (pReadAccess->GetPixelFromData(pScanlineRead, x) != pReadAccess->GetPixelFromData(pScanlineRead, nWidth - x - 1))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapTools.cxx b/vcl/source/bitmap/BitmapTools.cxx
new file mode 100644
index 000000000..da9a88bf6
--- /dev/null
+++ b/vcl/source/bitmap/BitmapTools.cxx
@@ -0,0 +1,1307 @@
+/* -*- 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/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <array>
+#include <utility>
+
+#include <tools/helpers.hxx>
+#include <vcl/BitmapTools.hxx>
+
+#include <sal/log.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/seqstream.hxx>
+#include <vcl/canvastools.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+#include <com/sun/star/graphic/SvgTools.hpp>
+#include <com/sun/star/graphic/Primitive2DTools.hpp>
+
+#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
+
+#include <com/sun/star/rendering/XIntegerReadOnlyBitmap.hpp>
+
+#include <vcl/dibtools.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#if ENABLE_CAIRO_CANVAS
+#include <cairo.h>
+#endif
+#include <tools/diagnose_ex.h>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <bitmap/BitmapWriteAccess.hxx>
+
+using namespace css;
+
+using drawinglayer::primitive2d::Primitive2DSequence;
+using drawinglayer::primitive2d::Primitive2DReference;
+
+namespace vcl::bitmap
+{
+
+BitmapEx loadFromName(const OUString& rFileName, const ImageLoadFlags eFlags)
+{
+ bool bSuccess = true;
+ OUString aIconTheme;
+ BitmapEx aBitmapEx;
+ try
+ {
+ aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ ImageTree::get().loadImage(rFileName, aIconTheme, aBitmapEx, true, eFlags);
+ }
+ catch (...)
+ {
+ bSuccess = false;
+ }
+
+ SAL_WARN_IF(!bSuccess, "vcl", "vcl::bitmap::loadFromName : could not load image " << rFileName << " via icon theme " << aIconTheme);
+
+ return aBitmapEx;
+}
+
+void loadFromSvg(SvStream& rStream, const OUString& sPath, BitmapEx& rBitmapEx, double fScalingFactor)
+{
+ uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
+ const uno::Reference<graphic::XSvgParser> xSvgParser = graphic::SvgTools::create(xContext);
+
+ std::size_t nSize = rStream.remainingSize();
+ std::vector<sal_Int8> aBuffer(nSize + 1);
+ rStream.ReadBytes(aBuffer.data(), nSize);
+ aBuffer[nSize] = 0;
+
+ uno::Sequence<sal_Int8> aData(aBuffer.data(), nSize + 1);
+ uno::Reference<io::XInputStream> aInputStream(new comphelper::SequenceInputStream(aData));
+
+ const Primitive2DSequence aPrimitiveSequence = xSvgParser->getDecomposition(aInputStream, sPath);
+
+ if (!aPrimitiveSequence.hasElements())
+ return;
+
+ uno::Sequence<beans::PropertyValue> aViewParameters;
+
+ geometry::RealRectangle2D aRealRect;
+ basegfx::B2DRange aRange;
+ for (css::uno::Reference<css::graphic::XPrimitive2D> const & xReference : aPrimitiveSequence)
+ {
+ if (xReference.is())
+ {
+ aRealRect = xReference->getRange(aViewParameters);
+ aRange.expand(basegfx::B2DRange(aRealRect.X1, aRealRect.Y1, aRealRect.X2, aRealRect.Y2));
+ }
+ }
+
+ aRealRect.X1 = aRange.getMinX();
+ aRealRect.Y1 = aRange.getMinY();
+ aRealRect.X2 = aRange.getMaxX();
+ aRealRect.Y2 = aRange.getMaxY();
+
+ double nDPI = 96 * fScalingFactor;
+
+ const css::uno::Reference<css::graphic::XPrimitive2DRenderer> xPrimitive2DRenderer = css::graphic::Primitive2DTools::create(xContext);
+ const css::uno::Reference<css::rendering::XBitmap> xBitmap(
+ xPrimitive2DRenderer->rasterize(aPrimitiveSequence, aViewParameters, nDPI, nDPI, aRealRect, 256*256));
+
+ if (xBitmap.is())
+ {
+ const css::uno::Reference<css::rendering::XIntegerReadOnlyBitmap> xIntBmp(xBitmap, uno::UNO_QUERY_THROW);
+ rBitmapEx = vcl::unotools::bitmapExFromXBitmap(xIntBmp);
+ }
+
+}
+
+/** Copy block of image data into the bitmap.
+ Assumes that the Bitmap has been constructed with the desired size.
+
+ @param pData
+ The block of data to copy
+ @param nStride
+ The number of bytes in a scanline, must >= (width * nBitCount / 8)
+ @param bReversColors
+ In case the endianness of pData is wrong, you could reverse colors
+*/
+BitmapEx CreateFromData(sal_uInt8 const *pData, sal_Int32 nWidth, sal_Int32 nHeight,
+ sal_Int32 nStride, vcl::PixelFormat ePixelFormat,
+ bool bReversColors, bool bReverseAlpha)
+{
+ auto nBitCount = sal_uInt16(ePixelFormat);
+
+ assert(nStride >= (nWidth * nBitCount / 8));
+ assert(nBitCount == 1 || nBitCount == 24 || nBitCount == 32);
+
+ Bitmap aBmp(Size(nWidth, nHeight), ePixelFormat);
+
+ BitmapScopedWriteAccess pWrite(aBmp);
+ assert(pWrite.get());
+ if( !pWrite )
+ return BitmapEx();
+ std::unique_ptr<AlphaMask> pAlphaMask;
+ AlphaScopedWriteAccess xMaskAcc;
+ if (nBitCount == 32)
+ {
+ pAlphaMask.reset( new AlphaMask( Size(nWidth, nHeight) ) );
+ xMaskAcc = AlphaScopedWriteAccess(*pAlphaMask);
+ }
+ if (nBitCount == 1)
+ {
+ for( tools::Long y = 0; y < nHeight; ++y )
+ {
+ sal_uInt8 const *p = pData + y * nStride / 8;
+ Scanline pScanline = pWrite->GetScanline(y);
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ int bitIndex = (y * nStride + x) % 8;
+
+ pWrite->SetPixelOnData(pScanline, x, BitmapColor((*p >> bitIndex) & 1));
+ }
+ }
+ }
+ else
+ {
+ for( tools::Long y = 0; y < nHeight; ++y )
+ {
+ sal_uInt8 const *p = pData + (y * nStride);
+ Scanline pScanline = pWrite->GetScanline(y);
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ BitmapColor col;
+ if ( bReversColors )
+ col = BitmapColor( p[2], p[1], p[0] );
+ else
+ col = BitmapColor( p[0], p[1], p[2] );
+ pWrite->SetPixelOnData(pScanline, x, col);
+ p += nBitCount/8;
+ }
+ if (nBitCount == 32)
+ {
+ p = pData + (y * nStride) + 3;
+ Scanline pMaskScanLine = xMaskAcc->GetScanline(y);
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ const sal_uInt8 nValue = bReverseAlpha ? 0xff - *p : *p;
+ xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(nValue));
+ p += 4;
+ }
+ }
+ }
+ }
+ if (nBitCount == 32)
+ return BitmapEx(aBmp, *pAlphaMask);
+ else
+ return BitmapEx(aBmp);
+}
+
+/** Copy block of image data into the bitmap.
+ Assumes that the Bitmap has been constructed with the desired size.
+*/
+BitmapEx CreateFromData( RawBitmap&& rawBitmap )
+{
+ auto nBitCount = rawBitmap.GetBitCount();
+ assert( nBitCount == 24 || nBitCount == 32);
+
+ auto ePixelFormat = vcl::PixelFormat::INVALID;
+
+ if (nBitCount == 24)
+ ePixelFormat = vcl::PixelFormat::N24_BPP;
+ else if (nBitCount == 32)
+ ePixelFormat = vcl::PixelFormat::N32_BPP;
+
+ assert(ePixelFormat != vcl::PixelFormat::INVALID);
+
+ Bitmap aBmp(rawBitmap.maSize, ePixelFormat);
+
+ BitmapScopedWriteAccess pWrite(aBmp);
+ assert(pWrite.get());
+ if( !pWrite )
+ return BitmapEx();
+ std::unique_ptr<AlphaMask> pAlphaMask;
+ AlphaScopedWriteAccess xMaskAcc;
+ if (nBitCount == 32)
+ {
+ pAlphaMask.reset( new AlphaMask( rawBitmap.maSize ) );
+ xMaskAcc = AlphaScopedWriteAccess(*pAlphaMask);
+ }
+
+ auto nHeight = rawBitmap.maSize.getHeight();
+ auto nWidth = rawBitmap.maSize.getWidth();
+ auto nStride = nWidth * nBitCount / 8;
+ for( tools::Long y = 0; y < nHeight; ++y )
+ {
+ sal_uInt8 const *p = rawBitmap.mpData.get() + (y * nStride);
+ Scanline pScanline = pWrite->GetScanline(y);
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ BitmapColor col(p[0], p[1], p[2]);
+ pWrite->SetPixelOnData(pScanline, x, col);
+ p += nBitCount/8;
+ }
+ if (nBitCount == 32)
+ {
+ p = rawBitmap.mpData.get() + (y * nStride) + 3;
+ Scanline pMaskScanLine = xMaskAcc->GetScanline(y);
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(255 - *p));
+ p += 4;
+ }
+ }
+ }
+ if (nBitCount == 32)
+ return BitmapEx(aBmp, *pAlphaMask);
+ else
+ return BitmapEx(aBmp);
+}
+
+#if ENABLE_CAIRO_CANVAS
+BitmapEx* CreateFromCairoSurface(Size aSize, cairo_surface_t * pSurface)
+{
+ // FIXME: if we could teach VCL/ about cairo handles, life could
+ // be significantly better here perhaps.
+
+#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
+ cairo_surface_t *pPixels = cairo_surface_create_similar_image(pSurface,
+#else
+ cairo_surface_t *pPixels = cairo_image_surface_create(
+#endif
+ CAIRO_FORMAT_ARGB32, aSize.Width(), aSize.Height());
+ cairo_t *pCairo = cairo_create( pPixels );
+ if( !pPixels || !pCairo || cairo_status(pCairo) != CAIRO_STATUS_SUCCESS )
+ return nullptr;
+
+ // suck ourselves from the X server to this buffer so then we can fiddle with
+ // Alpha to turn it into the ultra-lame vcl required format and then push it
+ // all back again later at vast expense [ urgh ]
+ cairo_set_source_surface( pCairo, pSurface, 0, 0 );
+ cairo_set_operator( pCairo, CAIRO_OPERATOR_SOURCE );
+ cairo_paint( pCairo );
+
+ Bitmap aRGB(aSize, vcl::PixelFormat::N24_BPP);
+ ::AlphaMask aMask( aSize );
+
+ BitmapScopedWriteAccess pRGBWrite(aRGB);
+ assert(pRGBWrite);
+ if (!pRGBWrite)
+ return nullptr;
+
+ AlphaScopedWriteAccess pMaskWrite(aMask);
+ assert(pMaskWrite);
+ if (!pMaskWrite)
+ return nullptr;
+
+ cairo_surface_flush(pPixels);
+ unsigned char *pSrc = cairo_image_surface_get_data( pPixels );
+ unsigned int nStride = cairo_image_surface_get_stride( pPixels );
+#if !ENABLE_WASM_STRIP_PREMULTIPLY
+ vcl::bitmap::lookup_table const & unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
+#endif
+ for( tools::Long y = 0; y < aSize.Height(); y++ )
+ {
+ sal_uInt32 *pPix = reinterpret_cast<sal_uInt32 *>(pSrc + nStride * y);
+ for( tools::Long x = 0; x < aSize.Width(); x++ )
+ {
+#if defined OSL_BIGENDIAN
+ sal_uInt8 nB = (*pPix >> 24);
+ sal_uInt8 nG = (*pPix >> 16) & 0xff;
+ sal_uInt8 nR = (*pPix >> 8) & 0xff;
+ sal_uInt8 nAlpha = *pPix & 0xff;
+#else
+ sal_uInt8 nAlpha = (*pPix >> 24);
+ sal_uInt8 nR = (*pPix >> 16) & 0xff;
+ sal_uInt8 nG = (*pPix >> 8) & 0xff;
+ sal_uInt8 nB = *pPix & 0xff;
+#endif
+ if( nAlpha != 0 && nAlpha != 255 )
+ {
+ // Cairo uses pre-multiplied alpha - we do not => re-multiply
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ nR = vcl::bitmap::unpremultiply(nAlpha, nR);
+ nG = vcl::bitmap::unpremultiply(nAlpha, nG);
+ nB = vcl::bitmap::unpremultiply(nAlpha, nB);
+#else
+ nR = unpremultiply_table[nAlpha][nR];
+ nG = unpremultiply_table[nAlpha][nG];
+ nB = unpremultiply_table[nAlpha][nB];
+#endif
+ }
+ pRGBWrite->SetPixel( y, x, BitmapColor( nR, nG, nB ) );
+ pMaskWrite->SetPixelIndex( y, x, 255 - nAlpha );
+ pPix++;
+ }
+ }
+
+ // ignore potential errors above. will get caller a
+ // uniformly white bitmap, but not that there would
+ // be error handling in calling code ...
+ ::BitmapEx *pBitmapEx = new ::BitmapEx( aRGB, aMask );
+
+ cairo_destroy( pCairo );
+ cairo_surface_destroy( pPixels );
+ return pBitmapEx;
+}
+#endif
+
+BitmapEx CanvasTransformBitmap( const BitmapEx& rBitmap,
+ const ::basegfx::B2DHomMatrix& rTransform,
+ ::basegfx::B2DRectangle const & rDestRect,
+ ::basegfx::B2DHomMatrix const & rLocalTransform )
+{
+ const Size aBmpSize( rBitmap.GetSizePixel() );
+ Bitmap aSrcBitmap( rBitmap.GetBitmap() );
+ Bitmap aSrcAlpha;
+
+ // differentiate mask and alpha channel (on-off
+ // vs. multi-level transparency)
+ if( rBitmap.IsAlpha() )
+ {
+ aSrcAlpha = rBitmap.GetAlpha().GetBitmap();
+ }
+
+ Bitmap::ScopedReadAccess pReadAccess( aSrcBitmap );
+ Bitmap::ScopedReadAccess pAlphaReadAccess( rBitmap.IsAlpha() ?
+ aSrcAlpha.AcquireReadAccess() :
+ nullptr,
+ aSrcAlpha );
+
+ if( pReadAccess.get() == nullptr ||
+ (pAlphaReadAccess.get() == nullptr && rBitmap.IsAlpha()) )
+ {
+ // TODO(E2): Error handling!
+ ENSURE_OR_THROW( false,
+ "transformBitmap(): could not access source bitmap" );
+ }
+
+ // mapping table, to translate pAlphaReadAccess' pixel
+ // values into destination alpha values (needed e.g. for
+ // paletted 1-bit masks).
+ sal_uInt8 aAlphaMap[256];
+
+ if( rBitmap.IsAlpha() )
+ {
+ // source already has alpha channel - 1:1 mapping,
+ // i.e. aAlphaMap[0]=0,...,aAlphaMap[255]=255.
+ sal_uInt8 val=0;
+ sal_uInt8* pCur=aAlphaMap;
+ sal_uInt8* const pEnd=&aAlphaMap[256];
+ while(pCur != pEnd)
+ *pCur++ = val++;
+ }
+ // else: mapping table is not used
+
+ const Size aDestBmpSize( ::basegfx::fround( rDestRect.getWidth() ),
+ ::basegfx::fround( rDestRect.getHeight() ) );
+
+ if( aDestBmpSize.IsEmpty() )
+ return BitmapEx();
+
+ Bitmap aDstBitmap(aDestBmpSize, aSrcBitmap.getPixelFormat(), &pReadAccess->GetPalette());
+ Bitmap aDstAlpha( AlphaMask( aDestBmpSize ).GetBitmap() );
+
+ {
+ // just to be on the safe side: let the
+ // ScopedAccessors get destructed before
+ // copy-constructing the resulting bitmap. This will
+ // rule out the possibility that cached accessor data
+ // is not yet written back.
+ BitmapScopedWriteAccess pWriteAccess( aDstBitmap );
+ BitmapScopedWriteAccess pAlphaWriteAccess( aDstAlpha );
+
+
+ if( pWriteAccess.get() != nullptr &&
+ pAlphaWriteAccess.get() != nullptr &&
+ rTransform.isInvertible() )
+ {
+ // we're doing inverse mapping here, i.e. mapping
+ // points from the destination bitmap back to the
+ // source
+ ::basegfx::B2DHomMatrix aTransform( rLocalTransform );
+ aTransform.invert();
+
+ // for the time being, always read as ARGB
+ for( tools::Long y=0; y<aDestBmpSize.Height(); ++y )
+ {
+ // differentiate mask and alpha channel (on-off
+ // vs. multi-level transparency)
+ if( rBitmap.IsAlpha() )
+ {
+ Scanline pScan = pWriteAccess->GetScanline( y );
+ Scanline pScanAlpha = pAlphaWriteAccess->GetScanline( y );
+ // Handling alpha and mask just the same...
+ for( tools::Long x=0; x<aDestBmpSize.Width(); ++x )
+ {
+ ::basegfx::B2DPoint aPoint(x,y);
+ aPoint *= aTransform;
+
+ const int nSrcX( ::basegfx::fround( aPoint.getX() ) );
+ const int nSrcY( ::basegfx::fround( aPoint.getY() ) );
+ if( nSrcX < 0 || nSrcX >= aBmpSize.Width() ||
+ nSrcY < 0 || nSrcY >= aBmpSize.Height() )
+ {
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(255) );
+ }
+ else
+ {
+ const sal_uInt8 cAlphaIdx = pAlphaReadAccess->GetPixelIndex( nSrcY, nSrcX );
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(aAlphaMap[ cAlphaIdx ]) );
+ pWriteAccess->SetPixelOnData( pScan, x, pReadAccess->GetPixel( nSrcY, nSrcX ) );
+ }
+ }
+ }
+ else
+ {
+ Scanline pScan = pWriteAccess->GetScanline( y );
+ Scanline pScanAlpha = pAlphaWriteAccess->GetScanline( y );
+ for( tools::Long x=0; x<aDestBmpSize.Width(); ++x )
+ {
+ ::basegfx::B2DPoint aPoint(x,y);
+ aPoint *= aTransform;
+
+ const int nSrcX( ::basegfx::fround( aPoint.getX() ) );
+ const int nSrcY( ::basegfx::fround( aPoint.getY() ) );
+ if( nSrcX < 0 || nSrcX >= aBmpSize.Width() ||
+ nSrcY < 0 || nSrcY >= aBmpSize.Height() )
+ {
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(255) );
+ }
+ else
+ {
+ pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(0) );
+ pWriteAccess->SetPixelOnData( pScan, x, pReadAccess->GetPixel( nSrcY,
+ nSrcX ) );
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // TODO(E2): Error handling!
+ ENSURE_OR_THROW( false,
+ "transformBitmap(): could not access bitmap" );
+ }
+ }
+
+ return BitmapEx(aDstBitmap, AlphaMask(aDstAlpha));
+}
+
+
+void DrawAlphaBitmapAndAlphaGradient(BitmapEx & rBitmapEx, bool bFixedTransparence, float fTransparence, AlphaMask & rNewMask)
+{
+ // mix existing and new alpha mask
+ AlphaMask aOldMask;
+
+ if(rBitmapEx.IsAlpha())
+ {
+ aOldMask = rBitmapEx.GetAlpha();
+ }
+
+ {
+ AlphaScopedWriteAccess pOld(aOldMask);
+
+ assert(pOld && "Got no access to old alpha mask (!)");
+
+ const double fFactor(1.0 / 255.0);
+
+ if(bFixedTransparence)
+ {
+ const double fOpNew(1.0 - fTransparence);
+
+ for(tools::Long y(0); y < pOld->Height(); y++)
+ {
+ Scanline pScanline = pOld->GetScanline( y );
+ for(tools::Long x(0); x < pOld->Width(); x++)
+ {
+ const double fOpOld(1.0 - (pOld->GetIndexFromData(pScanline, x) * fFactor));
+ const sal_uInt8 aCol(basegfx::fround((1.0 - (fOpOld * fOpNew)) * 255.0));
+
+ pOld->SetPixelOnData(pScanline, x, BitmapColor(aCol));
+ }
+ }
+ }
+ else
+ {
+ AlphaMask::ScopedReadAccess pNew(rNewMask);
+
+ assert(pNew && "Got no access to new alpha mask (!)");
+
+ assert(pOld->Width() == pNew->Width() && pOld->Height() == pNew->Height() &&
+ "Alpha masks have different sizes (!)");
+
+ for(tools::Long y(0); y < pOld->Height(); y++)
+ {
+ Scanline pScanline = pOld->GetScanline( y );
+ for(tools::Long x(0); x < pOld->Width(); x++)
+ {
+ const double fOpOld(1.0 - (pOld->GetIndexFromData(pScanline, x) * fFactor));
+ const double fOpNew(1.0 - (pNew->GetIndexFromData(pScanline, x) * fFactor));
+ const sal_uInt8 aCol(basegfx::fround((1.0 - (fOpOld * fOpNew)) * 255.0));
+
+ pOld->SetPixelOnData(pScanline, x, BitmapColor(aCol));
+ }
+ }
+ }
+
+ }
+
+ // apply combined bitmap as mask
+ rBitmapEx = BitmapEx(rBitmapEx.GetBitmap(), aOldMask);
+}
+
+
+void DrawAndClipBitmap(const Point& rPos, const Size& rSize, const BitmapEx& rBitmap, BitmapEx & aBmpEx, basegfx::B2DPolyPolygon const & rClipPath)
+{
+ ScopedVclPtrInstance< VirtualDevice > pVDev;
+ MapMode aMapMode( MapUnit::Map100thMM );
+ aMapMode.SetOrigin( Point( -rPos.X(), -rPos.Y() ) );
+ const Size aOutputSizePixel( pVDev->LogicToPixel( rSize, aMapMode ) );
+ const Size aSizePixel( rBitmap.GetSizePixel() );
+ if ( aOutputSizePixel.Width() && aOutputSizePixel.Height() )
+ {
+ aMapMode.SetScaleX( Fraction( aSizePixel.Width(), aOutputSizePixel.Width() ) );
+ aMapMode.SetScaleY( Fraction( aSizePixel.Height(), aOutputSizePixel.Height() ) );
+ }
+ pVDev->SetMapMode( aMapMode );
+ pVDev->SetOutputSizePixel( aSizePixel );
+ pVDev->SetFillColor( COL_BLACK );
+ const tools::PolyPolygon aClip( rClipPath );
+ pVDev->DrawPolyPolygon( aClip );
+
+ // #i50672# Extract whole VDev content (to match size of rBitmap)
+ pVDev->EnableMapMode( false );
+ const Bitmap aVDevMask(pVDev->GetBitmap(Point(), aSizePixel));
+
+ if(aBmpEx.IsAlpha())
+ {
+ // bitmap already uses a Mask or Alpha, we need to blend that with
+ // the new masking in pVDev.
+ // need to blend in AlphaMask quality (8Bit)
+ AlphaMask fromVDev(aVDevMask);
+ AlphaMask fromBmpEx(aBmpEx.GetAlpha());
+ AlphaMask::ScopedReadAccess pR(fromVDev);
+ AlphaScopedWriteAccess pW(fromBmpEx);
+
+ if(pR && pW)
+ {
+ const tools::Long nWidth(std::min(pR->Width(), pW->Width()));
+ const tools::Long nHeight(std::min(pR->Height(), pW->Height()));
+
+ for(tools::Long nY(0); nY < nHeight; nY++)
+ {
+ Scanline pScanlineR = pR->GetScanline( nY );
+ Scanline pScanlineW = pW->GetScanline( nY );
+ for(tools::Long nX(0); nX < nWidth; nX++)
+ {
+ const sal_uInt8 nIndR(pR->GetIndexFromData(pScanlineR, nX));
+ const sal_uInt8 nIndW(pW->GetIndexFromData(pScanlineW, nX));
+
+ // these values represent transparency (0 == no, 255 == fully transparent),
+ // so to blend these we have to multiply the inverse (opacity)
+ // and re-invert the result to transparence
+ const sal_uInt8 nCombined(0x00ff - (((0x00ff - nIndR) * (0x00ff - nIndW)) >> 8));
+
+ pW->SetPixelOnData(pScanlineW, nX, BitmapColor(nCombined));
+ }
+ }
+ }
+
+ pR.reset();
+ pW.reset();
+ aBmpEx = BitmapEx(aBmpEx.GetBitmap(), fromBmpEx);
+ }
+ else
+ {
+ // no mask yet, create and add new mask. For better quality, use Alpha,
+ // this allows the drawn mask being processed with AntiAliasing (AAed)
+ aBmpEx = BitmapEx(rBitmap.GetBitmap(), aVDevMask);
+ }
+}
+
+
+css::uno::Sequence< sal_Int8 > GetMaskDIB(BitmapEx const & aBmpEx)
+{
+ if ( aBmpEx.IsAlpha() )
+ {
+ SvMemoryStream aMem;
+ WriteDIB(aBmpEx.GetAlpha().GetBitmap(), aMem, false, true);
+ return css::uno::Sequence< sal_Int8 >( static_cast<sal_Int8 const *>(aMem.GetData()), aMem.Tell() );
+ }
+
+ return css::uno::Sequence< sal_Int8 >();
+}
+
+static bool readAlpha( BitmapReadAccess const * pAlphaReadAcc, tools::Long nY, const tools::Long nWidth, unsigned char* data, tools::Long nOff )
+{
+ bool bIsAlpha = false;
+ tools::Long nX;
+ int nAlpha;
+ Scanline pReadScan;
+
+ nOff += 3;
+
+ switch( pAlphaReadAcc->GetScanlineFormat() )
+ {
+ case ScanlineFormat::N8BitPal:
+ pReadScan = pAlphaReadAcc->GetScanline( nY );
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+ BitmapColor const& rColor(
+ pAlphaReadAcc->GetPaletteColor(*pReadScan));
+ pReadScan++;
+ nAlpha = data[ nOff ] = 255 - rColor.GetIndex();
+ if( nAlpha != 255 )
+ bIsAlpha = true;
+ nOff += 4;
+ }
+ break;
+ default:
+ SAL_INFO( "canvas.cairo", "fallback to GetColor for alpha - slow, format: " << static_cast<int>(pAlphaReadAcc->GetScanlineFormat()) );
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+ nAlpha = data[ nOff ] = 255 - pAlphaReadAcc->GetColor( nY, nX ).GetIndex();
+ if( nAlpha != 255 )
+ bIsAlpha = true;
+ nOff += 4;
+ }
+ }
+
+ return bIsAlpha;
+}
+
+
+
+/**
+ * @param data will be filled with alpha data, if xBitmap is alpha/transparent image
+ * @param bHasAlpha will be set to true if resulting surface has alpha
+ **/
+void CanvasCairoExtractBitmapData( BitmapEx const & aBmpEx, Bitmap & aBitmap, unsigned char*& data, bool& bHasAlpha, tools::Long& rnWidth, tools::Long& rnHeight )
+{
+ AlphaMask aAlpha = aBmpEx.GetAlpha();
+
+ ::BitmapReadAccess* pBitmapReadAcc = aBitmap.AcquireReadAccess();
+ ::BitmapReadAccess* pAlphaReadAcc = nullptr;
+ const tools::Long nWidth = rnWidth = pBitmapReadAcc->Width();
+ const tools::Long nHeight = rnHeight = pBitmapReadAcc->Height();
+ tools::Long nX, nY;
+ bool bIsAlpha = false;
+
+ if( aBmpEx.IsAlpha() )
+ pAlphaReadAcc = aAlpha.AcquireReadAccess();
+
+ data = static_cast<unsigned char*>(malloc( nWidth*nHeight*4 ));
+
+ tools::Long nOff = 0;
+ ::Color aColor;
+ unsigned int nAlpha = 255;
+
+#if !ENABLE_WASM_STRIP_PREMULTIPLY
+ vcl::bitmap::lookup_table const & premultiply_table = vcl::bitmap::get_premultiply_table();
+#endif
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ ::Scanline pReadScan;
+
+ switch( pBitmapReadAcc->GetScanlineFormat() )
+ {
+ case ScanlineFormat::N8BitPal:
+ pReadScan = pBitmapReadAcc->GetScanline( nY );
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff++ ];
+ else
+ nAlpha = data[ nOff++ ] = 255;
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+#endif
+ aColor = pBitmapReadAcc->GetPaletteColor(*pReadScan++);
+
+#ifdef OSL_BIGENDIAN
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetRed());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetGreen());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetBlue());
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()];
+#endif
+#else
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetBlue());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetGreen());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetRed());
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()];
+#endif
+ nOff++;
+#endif
+ }
+ break;
+ case ScanlineFormat::N24BitTcBgr:
+ pReadScan = pBitmapReadAcc->GetScanline( nY );
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff ];
+ else
+ nAlpha = data[ nOff ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff + 3 ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff + 2 ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff + 1 ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+#else
+ data[ nOff + 3 ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff + 2 ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff + 1 ] = premultiply_table[nAlpha][*pReadScan++];
+#endif
+ nOff += 4;
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+#endif
+ nOff++;
+#endif
+ }
+ break;
+ case ScanlineFormat::N24BitTcRgb:
+ pReadScan = pBitmapReadAcc->GetScanline( nY );
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff++ ];
+ else
+ nAlpha = data[ nOff++ ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+#endif
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 2 ]);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 1 ]);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 0 ]);
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]];
+#endif
+ pReadScan += 3;
+ nOff++;
+#endif
+ }
+ break;
+ case ScanlineFormat::N32BitTcBgra:
+ pReadScan = pBitmapReadAcc->GetScanline( nY );
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff++ ];
+ else
+ nAlpha = data[ nOff++ ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 2 ]);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 1 ]);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 0 ]);
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]];
+#endif
+ pReadScan += 4;
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+#endif
+ pReadScan++;
+ nOff++;
+#endif
+ }
+ break;
+ case ScanlineFormat::N32BitTcRgba:
+ pReadScan = pBitmapReadAcc->GetScanline( nY );
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff ++ ];
+ else
+ nAlpha = data[ nOff ++ ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++);
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+ data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++];
+#endif
+ pReadScan++;
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 2 ]);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 1 ]);
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 0 ]);
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]];
+ data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]];
+#endif
+ pReadScan += 4;
+ nOff++;
+#endif
+ }
+ break;
+ default:
+ SAL_INFO( "canvas.cairo", "fallback to GetColor - slow, format: " << static_cast<int>(pBitmapReadAcc->GetScanlineFormat()) );
+
+ if( pAlphaReadAcc )
+ if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) )
+ bIsAlpha = true;
+
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+ aColor = pBitmapReadAcc->GetColor( nY, nX );
+
+ // cairo need premultiplied color values
+ // TODO(rodo) handle endianness
+#ifdef OSL_BIGENDIAN
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff++ ];
+ else
+ nAlpha = data[ nOff++ ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetRed());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetGreen());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetBlue());
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()];
+#endif
+#else
+ if( pAlphaReadAcc )
+ nAlpha = data[ nOff + 3 ];
+ else
+ nAlpha = data[ nOff + 3 ] = 255;
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetBlue());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetGreen());
+ data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetRed());
+#else
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()];
+ data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()];
+#endif
+ nOff ++;
+#endif
+ }
+ }
+ }
+
+ ::Bitmap::ReleaseAccess( pBitmapReadAcc );
+ if( pAlphaReadAcc )
+ aAlpha.ReleaseAccess( pAlphaReadAcc );
+
+ bHasAlpha = bIsAlpha;
+
+}
+
+ uno::Sequence< sal_Int8 > CanvasExtractBitmapData(BitmapEx const & rBitmapEx, const geometry::IntegerRectangle2D& rect)
+ {
+ Bitmap aBitmap( rBitmapEx.GetBitmap() );
+ Bitmap aAlpha( rBitmapEx.GetAlpha().GetBitmap() );
+
+ Bitmap::ScopedReadAccess pReadAccess( aBitmap );
+ Bitmap::ScopedReadAccess pAlphaReadAccess( aAlpha.IsEmpty() ?
+ nullptr : aAlpha.AcquireReadAccess(),
+ aAlpha );
+
+ assert( pReadAccess );
+
+ // TODO(F1): Support more formats.
+ const Size aBmpSize( aBitmap.GetSizePixel() );
+
+ // for the time being, always return as BGRA
+ uno::Sequence< sal_Int8 > aRes( 4*aBmpSize.Width()*aBmpSize.Height() );
+ sal_Int8* pRes = aRes.getArray();
+
+ int nCurrPos(0);
+ for( tools::Long y=rect.Y1;
+ y<aBmpSize.Height() && y<rect.Y2;
+ ++y )
+ {
+ if( pAlphaReadAccess.get() != nullptr )
+ {
+ Scanline pScanlineReadAlpha = pAlphaReadAccess->GetScanline( y );
+ for( tools::Long x=rect.X1;
+ x<aBmpSize.Width() && x<rect.X2;
+ ++x )
+ {
+ pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetRed();
+ pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetGreen();
+ pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetBlue();
+ pRes[ nCurrPos++ ] = pAlphaReadAccess->GetIndexFromData( pScanlineReadAlpha, x );
+ }
+ }
+ else
+ {
+ for( tools::Long x=rect.X1;
+ x<aBmpSize.Width() && x<rect.X2;
+ ++x )
+ {
+ pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetRed();
+ pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetGreen();
+ pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetBlue();
+ pRes[ nCurrPos++ ] = sal_uInt8(255);
+ }
+ }
+ }
+ return aRes;
+ }
+
+ BitmapEx createHistorical8x8FromArray(std::array<sal_uInt8,64> const & pArray, Color aColorPix, Color aColorBack)
+ {
+ BitmapPalette aPalette(2);
+
+ aPalette[0] = BitmapColor(aColorBack);
+ aPalette[1] = BitmapColor(aColorPix);
+
+ Bitmap aBitmap(Size(8, 8), vcl::PixelFormat::N1_BPP, &aPalette);
+ BitmapScopedWriteAccess pContent(aBitmap);
+
+ for(sal_uInt16 a(0); a < 8; a++)
+ {
+ for(sal_uInt16 b(0); b < 8; b++)
+ {
+ if(pArray[(a * 8) + b])
+ {
+ pContent->SetPixelIndex(a, b, 1);
+ }
+ else
+ {
+ pContent->SetPixelIndex(a, b, 0);
+ }
+ }
+ }
+
+ return BitmapEx(aBitmap);
+ }
+
+ bool isHistorical8x8(const BitmapEx& rBitmapEx, Color& o_rBack, Color& o_rFront)
+ {
+ bool bRet(false);
+
+ if(!rBitmapEx.IsAlpha())
+ {
+ Bitmap aBitmap(rBitmapEx.GetBitmap());
+
+ if(8 == aBitmap.GetSizePixel().Width() && 8 == aBitmap.GetSizePixel().Height())
+ {
+ if (aBitmap.getPixelFormat() == vcl::PixelFormat::N1_BPP)
+ {
+ BitmapReadAccess* pRead = aBitmap.AcquireReadAccess();
+
+ if(pRead)
+ {
+ if(pRead->HasPalette() && 2 == pRead->GetPaletteEntryCount())
+ {
+ const BitmapPalette& rPalette = pRead->GetPalette();
+ o_rFront = rPalette[1];
+ o_rBack = rPalette[0];
+
+ bRet = true;
+ }
+
+ Bitmap::ReleaseAccess(pRead);
+ }
+ }
+ else
+ {
+ // Historical 1bpp images are getting really historical,
+ // even to the point that e.g. the png loader actually loads
+ // them as RGB. But the pattern code in svx relies on this
+ // assumption that any 2-color 1bpp bitmap is a pattern, and so it would
+ // get confused by RGB. Try to detect if this image is really
+ // just two colors and say it's a pattern bitmap if so.
+ Bitmap::ScopedReadAccess access(aBitmap);
+ o_rBack = access->GetColor(0,0);
+ bool foundSecondColor = false;;
+ for(tools::Long y = 0; y < access->Height(); ++y)
+ for(tools::Long x = 0; x < access->Width(); ++x)
+ {
+ if(!foundSecondColor)
+ {
+ if( access->GetColor(y,x) != o_rBack )
+ {
+ o_rFront = access->GetColor(y,x);
+ foundSecondColor = true;
+ // Hard to know which of the two colors is the background,
+ // select the lighter one.
+ if( o_rFront.GetLuminance() > o_rBack.GetLuminance())
+ std::swap( o_rFront, o_rBack );
+ }
+ }
+ else
+ {
+ if( access->GetColor(y,x) != o_rBack && access->GetColor(y,x) != o_rFront)
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ }
+
+ return bRet;
+ }
+
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ sal_uInt8 unpremultiply(sal_uInt8 c, sal_uInt8 a)
+ {
+ return (a == 0) ? 0 : (c * 255 + a / 2) / a;
+ }
+
+ sal_uInt8 premultiply(sal_uInt8 c, sal_uInt8 a)
+ {
+ return (c * a + 127) / 255;
+ }
+#else
+ sal_uInt8 unpremultiply(sal_uInt8 c, sal_uInt8 a)
+ {
+ return get_unpremultiply_table()[a][c];
+ }
+
+ static constexpr sal_uInt8 unpremultiplyImpl(sal_uInt8 c, sal_uInt8 a)
+ {
+ return (a == 0) ? 0 : (c * 255 + a / 2) / a;
+ }
+
+ sal_uInt8 premultiply(sal_uInt8 c, sal_uInt8 a)
+ {
+ return get_premultiply_table()[a][c];
+ }
+
+ static constexpr sal_uInt8 premultiplyImpl(sal_uInt8 c, sal_uInt8 a)
+ {
+ return (c * a + 127) / 255;
+ }
+
+ template<int... Is> static constexpr std::array<sal_uInt8, 256> make_unpremultiply_table_row_(
+ int a, std::integer_sequence<int, Is...>)
+ {
+ return {unpremultiplyImpl(Is, a)...};
+ }
+
+ template<int... Is> static constexpr lookup_table make_unpremultiply_table_(
+ std::integer_sequence<int, Is...>)
+ {
+ return {make_unpremultiply_table_row_(Is, std::make_integer_sequence<int, 256>{})...};
+ }
+
+ lookup_table const & get_unpremultiply_table()
+ {
+ static constexpr auto unpremultiply_table = make_unpremultiply_table_(
+ std::make_integer_sequence<int, 256>{});
+ return unpremultiply_table;
+ }
+
+ template<int... Is> static constexpr std::array<sal_uInt8, 256> make_premultiply_table_row_(
+ int a, std::integer_sequence<int, Is...>)
+ {
+ return {premultiplyImpl(Is, a)...};
+ }
+
+ template<int... Is> static constexpr lookup_table make_premultiply_table_(
+ std::integer_sequence<int, Is...>)
+ {
+ return {make_premultiply_table_row_(Is, std::make_integer_sequence<int, 256>{})...};
+ }
+
+ lookup_table const & get_premultiply_table()
+ {
+ static constexpr auto premultiply_table = make_premultiply_table_(
+ std::make_integer_sequence<int, 256>{});
+ return premultiply_table;
+ }
+#endif
+
+bool convertBitmap32To24Plus8(BitmapEx const & rInput, BitmapEx & rResult)
+{
+ Bitmap aBitmap(rInput.GetBitmap());
+ if (aBitmap.getPixelFormat() != vcl::PixelFormat::N32_BPP)
+ return false;
+
+ Size aSize = aBitmap.GetSizePixel();
+ Bitmap aResultBitmap(aSize, vcl::PixelFormat::N24_BPP);
+ AlphaMask aResultAlpha(aSize);
+ {
+ BitmapScopedWriteAccess pResultBitmapAccess(aResultBitmap);
+ AlphaScopedWriteAccess pResultAlphaAccess(aResultAlpha);
+
+ Bitmap::ScopedReadAccess pReadAccess(aBitmap);
+
+ for (tools::Long nY = 0; nY < aSize.Height(); ++nY)
+ {
+ Scanline aResultScan = pResultBitmapAccess->GetScanline(nY);
+ Scanline aResultScanAlpha = pResultAlphaAccess->GetScanline(nY);
+
+ Scanline aReadScan = pReadAccess->GetScanline(nY);
+
+ for (tools::Long nX = 0; nX < aSize.Width(); ++nX)
+ {
+ const BitmapColor aColor = pReadAccess->GetPixelFromData(aReadScan, nX);
+ BitmapColor aResultColor(aColor.GetRed(), aColor.GetGreen(), aColor.GetBlue());
+ BitmapColor aResultColorAlpha(255 - aColor.GetAlpha(), 255 - aColor.GetAlpha(), 255 - aColor.GetAlpha());
+
+ pResultBitmapAccess->SetPixelOnData(aResultScan, nX, aResultColor);
+ pResultAlphaAccess->SetPixelOnData(aResultScanAlpha, nX, aResultColorAlpha);
+ }
+ }
+ }
+ if (rInput.IsAlpha())
+ rResult = BitmapEx(aResultBitmap, rInput.GetAlpha());
+ else
+ rResult = BitmapEx(aResultBitmap, aResultAlpha);
+ return true;
+}
+
+Bitmap GetDownsampledBitmap(Size const& rDstSizeTwip, Point const& rSrcPt, Size const& rSrcSz,
+ Bitmap const& rBmp, tools::Long nMaxBmpDPIX, tools::Long nMaxBmpDPIY)
+{
+ Bitmap aBmp(rBmp);
+
+ if (!aBmp.IsEmpty())
+ {
+ const tools::Rectangle aBmpRect( Point(), aBmp.GetSizePixel() );
+ tools::Rectangle aSrcRect( rSrcPt, rSrcSz );
+
+ // do cropping if necessary
+ if( aSrcRect.Intersection( aBmpRect ) != aBmpRect )
+ {
+ if( !aSrcRect.IsEmpty() )
+ aBmp.Crop( aSrcRect );
+ else
+ aBmp.SetEmpty();
+ }
+
+ if( !aBmp.IsEmpty() )
+ {
+ // do downsampling if necessary
+ // #103209# Normalize size (mirroring has to happen outside of this method)
+ Size aDstSizeTwip(std::abs(rDstSizeTwip.Width()), std::abs(rDstSizeTwip.Height()));
+
+ const Size aBmpSize( aBmp.GetSizePixel() );
+ const double fBmpPixelX = aBmpSize.Width();
+ const double fBmpPixelY = aBmpSize.Height();
+ const double fMaxPixelX
+ = o3tl::convert<double>(aDstSizeTwip.Width(), o3tl::Length::twip, o3tl::Length::in)
+ * nMaxBmpDPIX;
+ const double fMaxPixelY
+ = o3tl::convert<double>(aDstSizeTwip.Height(), o3tl::Length::twip, o3tl::Length::in)
+ * nMaxBmpDPIY;
+
+ // check, if the bitmap DPI exceeds the maximum DPI (allow 4 pixel rounding tolerance)
+ if (((fBmpPixelX > (fMaxPixelX + 4)) ||
+ (fBmpPixelY > (fMaxPixelY + 4))) &&
+ (fBmpPixelY > 0.0) && (fMaxPixelY > 0.0))
+ {
+ // do scaling
+ Size aNewBmpSize;
+ const double fBmpWH = fBmpPixelX / fBmpPixelY;
+ const double fMaxWH = fMaxPixelX / fMaxPixelY;
+
+ if (fBmpWH < fMaxWH)
+ {
+ aNewBmpSize.setWidth(FRound(fMaxPixelY * fBmpWH));
+ aNewBmpSize.setHeight(FRound(fMaxPixelY));
+ }
+ else if (fBmpWH > 0.0)
+ {
+ aNewBmpSize.setWidth(FRound(fMaxPixelX));
+ aNewBmpSize.setHeight(FRound(fMaxPixelX / fBmpWH));
+ }
+
+ if( aNewBmpSize.Width() && aNewBmpSize.Height() )
+ aBmp.Scale(aNewBmpSize);
+ else
+ aBmp.SetEmpty();
+ }
+ }
+ }
+
+ return aBmp;
+}
+
+} // end vcl::bitmap
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/BitmapWriteAccess.cxx b/vcl/source/bitmap/BitmapWriteAccess.cxx
new file mode 100644
index 000000000..c7fb31cc1
--- /dev/null
+++ b/vcl/source/bitmap/BitmapWriteAccess.cxx
@@ -0,0 +1,397 @@
+/* -*- 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 <sal/log.hxx>
+#include <tools/debug.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <bitmap/bmpfast.hxx>
+
+BitmapWriteAccess::BitmapWriteAccess(Bitmap& rBitmap)
+ : BitmapReadAccess(rBitmap, BitmapAccessMode::Write)
+{
+}
+
+BitmapWriteAccess::~BitmapWriteAccess() {}
+
+void BitmapWriteAccess::CopyScanline(tools::Long nY, const BitmapReadAccess& rReadAcc)
+{
+ assert(nY >= 0 && nY < mpBuffer->mnHeight && "y-coordinate in destination out of range!");
+ SAL_WARN_IF(nY >= rReadAcc.Height(), "vcl", "y-coordinate in source out of range!");
+ SAL_WARN_IF((!HasPalette() || !rReadAcc.HasPalette())
+ && (HasPalette() || rReadAcc.HasPalette()),
+ "vcl", "No copying possible between palette bitmap and TC bitmap!");
+
+ if ((GetScanlineFormat() == rReadAcc.GetScanlineFormat())
+ && (GetScanlineSize() >= rReadAcc.GetScanlineSize()))
+ {
+ memcpy(GetScanline(nY), rReadAcc.GetScanline(nY), rReadAcc.GetScanlineSize());
+ }
+ else
+ {
+ tools::Long nWidth = std::min(mpBuffer->mnWidth, rReadAcc.Width());
+ if (!ImplFastCopyScanline(nY, *ImplGetBitmapBuffer(), *rReadAcc.ImplGetBitmapBuffer()))
+ {
+ Scanline pScanline = GetScanline(nY);
+ Scanline pScanlineRead = rReadAcc.GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ SetPixelOnData(pScanline, nX, rReadAcc.GetPixelFromData(pScanlineRead, nX));
+ }
+ }
+}
+
+void BitmapWriteAccess::CopyScanline(tools::Long nY, ConstScanline aSrcScanline,
+ ScanlineFormat nSrcScanlineFormat, sal_uInt32 nSrcScanlineSize)
+{
+ const ScanlineFormat nFormat = RemoveScanline(nSrcScanlineFormat);
+
+ assert(nY >= 0 && nY < mpBuffer->mnHeight && "y-coordinate in destination out of range!");
+ DBG_ASSERT((HasPalette() && nFormat <= ScanlineFormat::N8BitPal)
+ || (!HasPalette() && nFormat > ScanlineFormat::N8BitPal),
+ "No copying possible between palette and non palette scanlines!");
+
+ const sal_uInt32 nCount = std::min(GetScanlineSize(), nSrcScanlineSize);
+
+ if (!nCount)
+ return;
+
+ if (GetScanlineFormat() == RemoveScanline(nSrcScanlineFormat))
+ memcpy(GetScanline(nY), aSrcScanline, nCount);
+ else
+ {
+ if (ImplFastCopyScanline(nY, *ImplGetBitmapBuffer(), aSrcScanline, nSrcScanlineFormat,
+ nSrcScanlineSize))
+ return;
+
+ DBG_ASSERT(nFormat != ScanlineFormat::N32BitTcMask,
+ "No support for pixel formats with color masks yet!");
+ FncGetPixel pFncGetPixel;
+ switch (nFormat)
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ pFncGetPixel = GetPixelForN1BitMsbPal;
+ break;
+ case ScanlineFormat::N1BitLsbPal:
+ pFncGetPixel = GetPixelForN1BitLsbPal;
+ break;
+ case ScanlineFormat::N8BitPal:
+ pFncGetPixel = GetPixelForN8BitPal;
+ break;
+ case ScanlineFormat::N24BitTcBgr:
+ pFncGetPixel = GetPixelForN24BitTcBgr;
+ break;
+ case ScanlineFormat::N24BitTcRgb:
+ pFncGetPixel = GetPixelForN24BitTcRgb;
+ break;
+ case ScanlineFormat::N32BitTcAbgr:
+ if (Bitmap32IsPreMultipled())
+ pFncGetPixel = GetPixelForN32BitTcAbgr;
+ else
+ pFncGetPixel = GetPixelForN32BitTcXbgr;
+ break;
+ case ScanlineFormat::N32BitTcArgb:
+ if (Bitmap32IsPreMultipled())
+ pFncGetPixel = GetPixelForN32BitTcArgb;
+ else
+ pFncGetPixel = GetPixelForN32BitTcXrgb;
+ break;
+ case ScanlineFormat::N32BitTcBgra:
+ if (Bitmap32IsPreMultipled())
+ pFncGetPixel = GetPixelForN32BitTcBgra;
+ else
+ pFncGetPixel = GetPixelForN32BitTcBgrx;
+ break;
+ case ScanlineFormat::N32BitTcRgba:
+ if (Bitmap32IsPreMultipled())
+ pFncGetPixel = GetPixelForN32BitTcRgba;
+ else
+ pFncGetPixel = GetPixelForN32BitTcRgbx;
+ break;
+ case ScanlineFormat::N32BitTcMask:
+ pFncGetPixel = GetPixelForN32BitTcMask;
+ break;
+
+ default:
+ assert(false);
+ pFncGetPixel = nullptr;
+ break;
+ }
+
+ if (pFncGetPixel)
+ {
+ const ColorMask aDummyMask;
+ Scanline pScanline = GetScanline(nY);
+ for (tools::Long nX = 0, nWidth = mpBuffer->mnWidth; nX < nWidth; ++nX)
+ SetPixelOnData(pScanline, nX, pFncGetPixel(aSrcScanline, nX, aDummyMask));
+ }
+ }
+}
+
+void BitmapWriteAccess::SetLineColor(const Color& rColor)
+{
+ if (rColor.GetAlpha() == 0)
+ {
+ mpLineColor.reset();
+ }
+ else
+ {
+ if (HasPalette())
+ {
+ mpLineColor = BitmapColor(static_cast<sal_uInt8>(GetBestPaletteIndex(rColor)));
+ }
+ else
+ {
+ mpLineColor = BitmapColor(rColor);
+ }
+ }
+}
+
+void BitmapWriteAccess::SetFillColor() { mpFillColor.reset(); }
+
+void BitmapWriteAccess::SetFillColor(const Color& rColor)
+{
+ if (rColor.GetAlpha() == 0)
+ {
+ mpFillColor.reset();
+ }
+ else
+ {
+ if (HasPalette())
+ {
+ mpFillColor = BitmapColor(static_cast<sal_uInt8>(GetBestPaletteIndex(rColor)));
+ }
+ else
+ {
+ mpFillColor = BitmapColor(rColor);
+ }
+ }
+}
+
+void BitmapWriteAccess::Erase(const Color& rColor)
+{
+ // convert the color format from RGB to palette index if needed
+ // TODO: provide and use Erase( BitmapColor& method)
+ BitmapColor aColor = rColor;
+ if (HasPalette())
+ {
+ aColor = BitmapColor(static_cast<sal_uInt8>(GetBestPaletteIndex(rColor)));
+ }
+
+ // try fast bitmap method first
+ if (ImplFastEraseBitmap(*mpBuffer, aColor))
+ return;
+
+ tools::Rectangle aRect(Point(), maBitmap.GetSizePixel());
+ if (aRect.IsEmpty())
+ return;
+ // clear the bitmap by filling the first line pixel by pixel,
+ // then dup the first line over each other line
+ Scanline pFirstScanline = GetScanline(0);
+ const tools::Long nEndX = aRect.Right();
+ for (tools::Long nX = 0; nX <= nEndX; ++nX)
+ SetPixelOnData(pFirstScanline, nX, rColor);
+ const auto nScanlineSize = GetScanlineSize();
+ const tools::Long nEndY = aRect.Bottom();
+ for (tools::Long nY = 1; nY <= nEndY; nY++)
+ {
+ Scanline pDestScanline = GetScanline(nY);
+ memcpy(pDestScanline, pFirstScanline, nScanlineSize);
+ }
+}
+
+void BitmapWriteAccess::DrawLine(const Point& rStart, const Point& rEnd)
+{
+ if (!mpLineColor)
+ return;
+
+ const BitmapColor& rLineColor = *mpLineColor;
+ tools::Long nX, nY;
+
+ if (rStart.X() == rEnd.X())
+ {
+ // Vertical Line
+ const tools::Long nEndY = rEnd.Y();
+
+ nX = rStart.X();
+ nY = rStart.Y();
+
+ if (nEndY > nY)
+ {
+ for (; nY <= nEndY; nY++)
+ SetPixel(nY, nX, rLineColor);
+ }
+ else
+ {
+ for (; nY >= nEndY; nY--)
+ SetPixel(nY, nX, rLineColor);
+ }
+ }
+ else if (rStart.Y() == rEnd.Y())
+ {
+ // Horizontal Line
+ const tools::Long nEndX = rEnd.X();
+
+ nX = rStart.X();
+ nY = rStart.Y();
+
+ if (nEndX > nX)
+ {
+ for (; nX <= nEndX; nX++)
+ SetPixel(nY, nX, rLineColor);
+ }
+ else
+ {
+ for (; nX >= nEndX; nX--)
+ SetPixel(nY, nX, rLineColor);
+ }
+ }
+ else
+ {
+ const tools::Long nDX = std::abs(rEnd.X() - rStart.X());
+ const tools::Long nDY = std::abs(rEnd.Y() - rStart.Y());
+ tools::Long nX1;
+ tools::Long nY1;
+ tools::Long nX2;
+ tools::Long nY2;
+
+ if (nDX >= nDY)
+ {
+ if (rStart.X() < rEnd.X())
+ {
+ nX1 = rStart.X();
+ nY1 = rStart.Y();
+ nX2 = rEnd.X();
+ nY2 = rEnd.Y();
+ }
+ else
+ {
+ nX1 = rEnd.X();
+ nY1 = rEnd.Y();
+ nX2 = rStart.X();
+ nY2 = rStart.Y();
+ }
+
+ const tools::Long nDYX = (nDY - nDX) << 1;
+ const tools::Long nDY2 = nDY << 1;
+ tools::Long nD = nDY2 - nDX;
+ bool bPos = nY1 < nY2;
+
+ for (nX = nX1, nY = nY1; nX <= nX2; nX++)
+ {
+ SetPixel(nY, nX, rLineColor);
+
+ if (nD < 0)
+ nD += nDY2;
+ else
+ {
+ nD += nDYX;
+
+ if (bPos)
+ nY++;
+ else
+ nY--;
+ }
+ }
+ }
+ else
+ {
+ if (rStart.Y() < rEnd.Y())
+ {
+ nX1 = rStart.X();
+ nY1 = rStart.Y();
+ nX2 = rEnd.X();
+ nY2 = rEnd.Y();
+ }
+ else
+ {
+ nX1 = rEnd.X();
+ nY1 = rEnd.Y();
+ nX2 = rStart.X();
+ nY2 = rStart.Y();
+ }
+
+ const tools::Long nDYX = (nDX - nDY) << 1;
+ const tools::Long nDY2 = nDX << 1;
+ tools::Long nD = nDY2 - nDY;
+ bool bPos = nX1 < nX2;
+
+ for (nX = nX1, nY = nY1; nY <= nY2; nY++)
+ {
+ SetPixel(nY, nX, rLineColor);
+
+ if (nD < 0)
+ nD += nDY2;
+ else
+ {
+ nD += nDYX;
+
+ if (bPos)
+ nX++;
+ else
+ nX--;
+ }
+ }
+ }
+ }
+}
+
+void BitmapWriteAccess::FillRect(const tools::Rectangle& rRect)
+{
+ if (!mpFillColor)
+ return;
+
+ const BitmapColor& rFillColor = *mpFillColor;
+ tools::Rectangle aRect(Point(), maBitmap.GetSizePixel());
+
+ aRect.Intersection(rRect);
+
+ if (aRect.IsEmpty())
+ return;
+
+ const tools::Long nStartX = rRect.Left();
+ const tools::Long nStartY = rRect.Top();
+ const tools::Long nEndX = rRect.Right();
+ const tools::Long nEndY = rRect.Bottom();
+
+ for (tools::Long nY = nStartY; nY <= nEndY; nY++)
+ {
+ Scanline pScanline = GetScanline(nY);
+ for (tools::Long nX = nStartX; nX <= nEndX; nX++)
+ {
+ SetPixelOnData(pScanline, nX, rFillColor);
+ }
+ }
+}
+
+void BitmapWriteAccess::DrawRect(const tools::Rectangle& rRect)
+{
+ if (mpFillColor)
+ FillRect(rRect);
+
+ if (mpLineColor && (!mpFillColor || (*mpFillColor != *mpLineColor)))
+ {
+ DrawLine(rRect.TopLeft(), rRect.TopRight());
+ DrawLine(rRect.TopRight(), rRect.BottomRight());
+ DrawLine(rRect.BottomRight(), rRect.BottomLeft());
+ DrawLine(rRect.BottomLeft(), rRect.TopLeft());
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/Octree.cxx b/vcl/source/bitmap/Octree.cxx
new file mode 100644
index 000000000..98ad8c9fc
--- /dev/null
+++ b/vcl/source/bitmap/Octree.cxx
@@ -0,0 +1,280 @@
+/* -*- 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 <vcl/BitmapReadAccess.hxx>
+
+#include <bitmap/Octree.hxx>
+
+namespace
+{
+constexpr size_t OCTREE_BITS = 5;
+constexpr size_t OCTREE_BITS_1 = 10;
+
+constexpr sal_uLong gnBits = 8 - OCTREE_BITS;
+
+} // end anonymous namespace
+
+Octree::Octree(const BitmapReadAccess& rReadAcc, sal_uLong nColors)
+ : mnLeafCount(0)
+ , mnLevel(0)
+ , mpReduce(OCTREE_BITS + 1, nullptr)
+ , mnPalIndex(0)
+{
+ const BitmapReadAccess* pAccess = &rReadAcc;
+ sal_uLong nMax(nColors);
+
+ if (!*pAccess)
+ return;
+
+ const tools::Long nWidth = pAccess->Width();
+ const tools::Long nHeight = pAccess->Height();
+
+ if (pAccess->HasPalette())
+ {
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAccess->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ mnLevel = 0;
+ add(pTree, pAccess->GetPaletteColor(pAccess->GetIndexFromData(pScanline, nX)));
+
+ while (mnLeafCount > nMax)
+ reduce();
+ }
+ }
+ }
+ else
+ {
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAccess->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ mnLevel = 0;
+ add(pTree, pAccess->GetPixelFromData(pScanline, nX));
+
+ while (mnLeafCount > nMax)
+ reduce();
+ }
+ }
+ }
+}
+
+Octree::~Octree() {}
+
+void Octree::add(std::unique_ptr<OctreeNode>& rpNode, BitmapColor const& color)
+{
+ // possibly generate new nodes
+ if (!rpNode)
+ {
+ rpNode.reset(new OctreeNode);
+ rpNode->bLeaf = (OCTREE_BITS == mnLevel);
+
+ if (rpNode->bLeaf)
+ mnLeafCount++;
+ else
+ {
+ rpNode->pNext = mpReduce[mnLevel];
+ mpReduce[mnLevel] = rpNode.get();
+ }
+ }
+
+ if (rpNode->bLeaf)
+ {
+ rpNode->nCount++;
+ rpNode->nRed += color.GetRed();
+ rpNode->nGreen += color.GetGreen();
+ rpNode->nBlue += color.GetBlue();
+ }
+ else
+ {
+ const sal_uLong nShift = 7 - mnLevel;
+ const sal_uInt8 cMask = 0x80 >> mnLevel;
+ const sal_uLong nIndex = (((color.GetRed() & cMask) >> nShift) << 2)
+ | (((color.GetGreen() & cMask) >> nShift) << 1)
+ | ((color.GetBlue() & cMask) >> nShift);
+
+ mnLevel++;
+ add(rpNode->pChild[nIndex], color);
+ }
+}
+
+void Octree::reduce()
+{
+ OctreeNode* pNode;
+ sal_uLong nRedSum = 0;
+ sal_uLong nGreenSum = 0;
+ sal_uLong nBlueSum = 0;
+ sal_uLong nChildren = 0;
+
+ sal_uLong nIndex = OCTREE_BITS - 1;
+ while (nIndex > 0 && !mpReduce[nIndex])
+ {
+ nIndex--;
+ }
+
+ pNode = mpReduce[nIndex];
+ mpReduce[nIndex] = pNode->pNext;
+
+ for (unsigned int i = 0; i < 8; i++)
+ {
+ if (pNode->pChild[i])
+ {
+ OctreeNode* pChild = pNode->pChild[i].get();
+
+ nRedSum += pChild->nRed;
+ nGreenSum += pChild->nGreen;
+ nBlueSum += pChild->nBlue;
+ pNode->nCount += pChild->nCount;
+
+ pNode->pChild[i].reset();
+ nChildren++;
+ }
+ }
+
+ pNode->bLeaf = true;
+ pNode->nRed = nRedSum;
+ pNode->nGreen = nGreenSum;
+ pNode->nBlue = nBlueSum;
+ mnLeafCount -= --nChildren;
+}
+
+void Octree::CreatePalette(OctreeNode* pNode)
+{
+ if (pNode->bLeaf)
+ {
+ pNode->nPalIndex = mnPalIndex;
+ maPalette[mnPalIndex++] = BitmapColor(sal_uInt8(double(pNode->nRed) / pNode->nCount),
+ sal_uInt8(double(pNode->nGreen) / pNode->nCount),
+ sal_uInt8(double(pNode->nBlue) / pNode->nCount));
+ }
+ else
+ {
+ for (auto const& i : pNode->pChild)
+ {
+ if (i)
+ {
+ CreatePalette(i.get());
+ }
+ }
+ }
+}
+
+void Octree::GetPalIndex(const OctreeNode* pNode, BitmapColor const& color)
+{
+ if (pNode->bLeaf)
+ mnPalIndex = pNode->nPalIndex;
+ else
+ {
+ const sal_uLong nShift = 7 - mnLevel;
+ const sal_uInt8 cMask = 0x80 >> mnLevel;
+ mnLevel++;
+ const sal_uLong nIndex = (((color.GetRed() & cMask) >> nShift) << 2)
+ | (((color.GetGreen() & cMask) >> nShift) << 1)
+ | ((color.GetBlue() & cMask) >> nShift);
+
+ GetPalIndex(pNode->pChild[nIndex].get(), color);
+ }
+}
+
+const BitmapPalette& Octree::GetPalette()
+{
+ maPalette.SetEntryCount(sal_uInt16(mnLeafCount));
+ mnPalIndex = 0;
+ CreatePalette(pTree.get());
+ return maPalette;
+}
+
+sal_uInt16 Octree::GetBestPaletteIndex(const BitmapColor& rColor)
+{
+ mnPalIndex = 65535;
+ mnLevel = 0;
+ GetPalIndex(pTree.get(), rColor);
+ return mnPalIndex;
+}
+
+constexpr int nColorMax = 1 << OCTREE_BITS;
+
+InverseColorMap::InverseColorMap(const BitmapPalette& rPal)
+{
+ const unsigned long xsqr = 1 << (gnBits << 1);
+ const unsigned long xsqr2 = xsqr << 1;
+ const int nColors = rPal.GetEntryCount();
+ const tools::Long x = 1 << gnBits;
+ const tools::Long x2 = x >> 1;
+ sal_uLong r, g, b;
+ tools::Long rxx, gxx, bxx;
+
+ ImplCreateBuffers();
+
+ for (int nIndex = 0; nIndex < nColors; nIndex++)
+ {
+ const BitmapColor& rColor = rPal[static_cast<sal_uInt16>(nIndex)];
+ const tools::Long cRed = rColor.GetRed();
+ const tools::Long cGreen = rColor.GetGreen();
+ const tools::Long cBlue = rColor.GetBlue();
+
+ tools::Long rdist = cRed - x2;
+ tools::Long gdist = cGreen - x2;
+ tools::Long bdist = cBlue - x2;
+ rdist = rdist * rdist + gdist * gdist + bdist * bdist;
+
+ const tools::Long crinc = (xsqr - (cRed << gnBits)) << 1;
+ const tools::Long cginc = (xsqr - (cGreen << gnBits)) << 1;
+ const tools::Long cbinc = (xsqr - (cBlue << gnBits)) << 1;
+
+ sal_uLong* cdp = reinterpret_cast<sal_uLong*>(mpBuffer.data());
+ sal_uInt8* crgbp = mpMap.data();
+
+ for (r = 0, rxx = crinc; r < nColorMax; rdist += rxx, r++, rxx += xsqr2)
+ {
+ for (g = 0, gdist = rdist, gxx = cginc; g < nColorMax; gdist += gxx, g++, gxx += xsqr2)
+ {
+ for (b = 0, bdist = gdist, bxx = cbinc; b < nColorMax;
+ bdist += bxx, b++, cdp++, crgbp++, bxx += xsqr2)
+ if (!nIndex || static_cast<tools::Long>(*cdp) > bdist)
+ {
+ *cdp = bdist;
+ *crgbp = static_cast<sal_uInt8>(nIndex);
+ }
+ }
+ }
+ }
+}
+
+InverseColorMap::~InverseColorMap() {}
+
+void InverseColorMap::ImplCreateBuffers()
+{
+ const sal_uLong nCount = nColorMax * nColorMax * nColorMax;
+ const sal_uLong nSize = nCount * sizeof(sal_uLong);
+
+ mpMap.resize(nCount, 0x00);
+ mpBuffer.resize(nSize, 0xff);
+}
+
+sal_uInt16 InverseColorMap::GetBestPaletteIndex(const BitmapColor& rColor)
+{
+ return mpMap[((static_cast<sal_uLong>(rColor.GetRed()) >> gnBits) << OCTREE_BITS_1)
+ | ((static_cast<sal_uLong>(rColor.GetGreen()) >> gnBits) << OCTREE_BITS)
+ | (static_cast<sal_uLong>(rColor.GetBlue()) >> gnBits)];
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/alpha.cxx b/vcl/source/bitmap/alpha.cxx
new file mode 100644
index 000000000..deeba1280
--- /dev/null
+++ b/vcl/source/bitmap/alpha.cxx
@@ -0,0 +1,124 @@
+/* -*- 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 <tools/color.hxx>
+#include <vcl/alpha.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <salinst.hxx>
+#include <svdata.hxx>
+#include <salbmp.hxx>
+#include <sal/log.hxx>
+
+AlphaMask::AlphaMask() = default;
+
+AlphaMask::AlphaMask( const Bitmap& rBitmap ) :
+ Bitmap( rBitmap )
+{
+ if( !rBitmap.IsEmpty() )
+ Convert( BmpConversion::N8BitNoConversion );
+}
+
+AlphaMask::AlphaMask( const AlphaMask& ) = default;
+
+AlphaMask::AlphaMask( AlphaMask&& ) = default;
+
+AlphaMask::AlphaMask( const Size& rSizePixel, const sal_uInt8* pEraseTransparency )
+ : Bitmap(rSizePixel, vcl::PixelFormat::N8_BPP, &Bitmap::GetGreyPalette(256))
+{
+ if( pEraseTransparency )
+ Bitmap::Erase( Color( *pEraseTransparency, *pEraseTransparency, *pEraseTransparency ) );
+}
+
+AlphaMask::~AlphaMask() = default;
+
+AlphaMask& AlphaMask::operator=( const Bitmap& rBitmap )
+{
+ *static_cast<Bitmap*>(this) = rBitmap;
+
+ if( !rBitmap.IsEmpty() )
+ Convert( BmpConversion::N8BitNoConversion );
+
+ return *this;
+}
+
+const Bitmap& AlphaMask::ImplGetBitmap() const
+{
+ return *this;
+}
+
+void AlphaMask::ImplSetBitmap( const Bitmap& rBitmap )
+{
+ SAL_WARN_IF(rBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP, "vcl.gdi", "Bitmap should be 8bpp, not " << vcl::pixelFormatBitCount(rBitmap.getPixelFormat()) << "bpp" );
+ SAL_WARN_IF( !rBitmap.HasGreyPalette8Bit(), "vcl.gdi", "Bitmap isn't greyscale" );
+ *static_cast<Bitmap*>(this) = rBitmap;
+}
+
+Bitmap const & AlphaMask::GetBitmap() const
+{
+ return ImplGetBitmap();
+}
+
+void AlphaMask::Erase( sal_uInt8 cTransparency )
+{
+ Bitmap::Erase( Color( cTransparency, cTransparency, cTransparency ) );
+}
+
+void AlphaMask::BlendWith(const Bitmap& rOther)
+{
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpBmp->Create(*ImplGetSalBitmap()) && xImpBmp->AlphaBlendWith(*rOther.ImplGetSalBitmap()))
+ {
+ ImplSetSalBitmap(xImpBmp);
+ return;
+ }
+ AlphaMask aOther(rOther); // to 8 bits
+ Bitmap::ScopedReadAccess pOtherAcc(aOther);
+ AlphaScopedWriteAccess pAcc(*this);
+ if (!(pOtherAcc && pAcc && pOtherAcc->GetBitCount() == 8 && pAcc->GetBitCount() == 8))
+ return;
+
+ const tools::Long nHeight = std::min(pOtherAcc->Height(), pAcc->Height());
+ const tools::Long nWidth = std::min(pOtherAcc->Width(), pAcc->Width());
+ for (tools::Long y = 0; y < nHeight; ++y)
+ {
+ Scanline scanline = pAcc->GetScanline( y );
+ ConstScanline otherScanline = pOtherAcc->GetScanline( y );
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ // Use sal_uInt16 for following multiplication
+ const sal_uInt16 nGrey1 = *scanline;
+ const sal_uInt16 nGrey2 = *otherScanline;
+ *scanline = static_cast<sal_uInt8>(nGrey1 + nGrey2 - nGrey1 * nGrey2 / 255);
+ ++scanline;
+ ++otherScanline;
+ }
+ }
+}
+
+void AlphaMask::ReleaseAccess( BitmapReadAccess* pAccess )
+{
+ if( pAccess )
+ {
+ Bitmap::ReleaseAccess( pAccess );
+ Convert( BmpConversion::N8BitNoConversion );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/bitmap.cxx b/vcl/source/bitmap/bitmap.cxx
new file mode 100644
index 000000000..3c40dd8f9
--- /dev/null
+++ b/vcl/source/bitmap/bitmap.cxx
@@ -0,0 +1,1737 @@
+/* -*- 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 <config_features.h>
+
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <tools/helpers.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/outdev.hxx>
+
+#include <svdata.hxx>
+#include <salinst.hxx>
+#include <salbmp.hxx>
+#if HAVE_FEATURE_SKIA
+#include <vcl/skia/SkiaHelper.hxx>
+#endif
+#include <vcl/BitmapMonochromeFilter.hxx>
+
+#include <bitmap/BitmapScaleSuperFilter.hxx>
+#include <bitmap/BitmapScaleConvolutionFilter.hxx>
+#include <bitmap/BitmapFastScaleFilter.hxx>
+#include <bitmap/BitmapInterpolateScaleFilter.hxx>
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <bitmap/impoctree.hxx>
+#include <bitmap/Octree.hxx>
+
+#include "impvect.hxx"
+#include "floyd.hxx"
+
+#include <math.h>
+#include <algorithm>
+#include <memory>
+
+#ifdef DBG_UTIL
+#include <cstdlib>
+#include <tools/stream.hxx>
+#include <vcl/graphicfilter.hxx>
+#endif
+
+Bitmap::Bitmap()
+{
+}
+
+Bitmap::Bitmap(const Bitmap& rBitmap)
+ : mxSalBmp(rBitmap.mxSalBmp)
+ , maPrefMapMode(rBitmap.maPrefMapMode)
+ , maPrefSize(rBitmap.maPrefSize)
+{
+}
+
+Bitmap::Bitmap(std::shared_ptr<SalBitmap> const & pSalBitmap)
+ : mxSalBmp(pSalBitmap)
+ , maPrefMapMode(MapMode(MapUnit::MapPixel))
+ , maPrefSize(mxSalBmp->GetSize())
+{
+}
+
+Bitmap::Bitmap( const Size& rSizePixel, vcl::PixelFormat ePixelFormat, const BitmapPalette* pPal )
+{
+ if (!(rSizePixel.Width() && rSizePixel.Height()))
+ return;
+
+ BitmapPalette aPal;
+ BitmapPalette* pRealPal = nullptr;
+
+ if (vcl::isPalettePixelFormat(ePixelFormat))
+ {
+ if( !pPal )
+ {
+ if (ePixelFormat == vcl::PixelFormat::N1_BPP)
+ {
+ aPal.SetEntryCount( 2 );
+ aPal[ 0 ] = COL_BLACK;
+ aPal[ 1 ] = COL_WHITE;
+ }
+ else if (ePixelFormat == vcl::PixelFormat::N8_BPP)
+ {
+ aPal.SetEntryCount(1 << sal_uInt16(ePixelFormat));
+ aPal[ 0 ] = COL_BLACK;
+ aPal[ 1 ] = COL_BLUE;
+ aPal[ 2 ] = COL_GREEN;
+ aPal[ 3 ] = COL_CYAN;
+ aPal[ 4 ] = COL_RED;
+ aPal[ 5 ] = COL_MAGENTA;
+ aPal[ 6 ] = COL_BROWN;
+ aPal[ 7 ] = COL_GRAY;
+ aPal[ 8 ] = COL_LIGHTGRAY;
+ aPal[ 9 ] = COL_LIGHTBLUE;
+ aPal[ 10 ] = COL_LIGHTGREEN;
+ aPal[ 11 ] = COL_LIGHTCYAN;
+ aPal[ 12 ] = COL_LIGHTRED;
+ aPal[ 13 ] = COL_LIGHTMAGENTA;
+ aPal[ 14 ] = COL_YELLOW;
+ aPal[ 15 ] = COL_WHITE;
+
+ // Create dither palette
+ if (ePixelFormat == vcl::PixelFormat::N8_BPP)
+ {
+ sal_uInt16 nActCol = 16;
+
+ for( sal_uInt16 nB = 0; nB < 256; nB += 51 )
+ for( sal_uInt16 nG = 0; nG < 256; nG += 51 )
+ for( sal_uInt16 nR = 0; nR < 256; nR += 51 )
+ aPal[ nActCol++ ] = BitmapColor( static_cast<sal_uInt8>(nR), static_cast<sal_uInt8>(nG), static_cast<sal_uInt8>(nB) );
+
+ // Set standard Office colors
+ aPal[ nActCol++ ] = BitmapColor( 0, 184, 255 );
+ }
+ }
+ }
+ else
+ pRealPal = const_cast<BitmapPalette*>(pPal);
+ }
+
+ mxSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
+ mxSalBmp->Create(rSizePixel, ePixelFormat, pRealPal ? *pRealPal : aPal);
+}
+
+#ifdef DBG_UTIL
+
+namespace
+{
+void savePNG(const OUString& sWhere, const Bitmap& rBmp)
+{
+ SvFileStream aStream(sWhere, StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(BitmapEx(rBmp), aStream);
+}
+}
+
+#endif
+
+Bitmap::~Bitmap()
+{
+#ifdef DBG_UTIL
+ // VCL_DUMP_BMP_PATH should be like C:/bmpDump.png or ~/bmpDump.png
+ static const OUString sDumpPath(OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
+ // Stepping into the dtor of a bitmap you need, and setting the volatile variable to true in
+ // debugger, would dump the bitmap in question
+ static volatile bool save(false);
+ if (!sDumpPath.isEmpty() && save)
+ {
+ save = false;
+ savePNG(sDumpPath, *this);
+ }
+#endif
+}
+
+const BitmapPalette& Bitmap::GetGreyPalette( int nEntries )
+{
+ // Create greyscale palette with 2, 4, 16 or 256 entries
+ switch (nEntries)
+ {
+ case 2:
+ {
+ static const BitmapPalette aGreyPalette2 = [] {
+ BitmapPalette aPalette(2);
+ aPalette[0] = BitmapColor(0, 0, 0);
+ aPalette[1] = BitmapColor(255, 255, 255);
+ return aPalette;
+ }();
+
+ return aGreyPalette2;
+ }
+ case 4:
+ {
+ static const BitmapPalette aGreyPalette4 = [] {
+ BitmapPalette aPalette(4);
+ aPalette[0] = BitmapColor(0, 0, 0);
+ aPalette[1] = BitmapColor(85, 85, 85);
+ aPalette[2] = BitmapColor(170, 170, 170);
+ aPalette[3] = BitmapColor(255, 255, 255);
+ return aPalette;
+ }();
+
+ return aGreyPalette4;
+ }
+ case 16:
+ {
+ static const BitmapPalette aGreyPalette16 = [] {
+ sal_uInt8 cGrey = 0;
+ sal_uInt8 const cGreyInc = 17;
+
+ BitmapPalette aPalette(16);
+
+ for (sal_uInt16 i = 0; i < 16; ++i, cGrey += cGreyInc)
+ aPalette[i] = BitmapColor(cGrey, cGrey, cGrey);
+
+ return aPalette;
+ }();
+
+ return aGreyPalette16;
+ }
+ case 256:
+ {
+ static const BitmapPalette aGreyPalette256 = [] {
+ BitmapPalette aPalette(256);
+
+ for (sal_uInt16 i = 0; i < 256; ++i)
+ aPalette[i] = BitmapColor(static_cast<sal_uInt8>(i), static_cast<sal_uInt8>(i),
+ static_cast<sal_uInt8>(i));
+
+ return aPalette;
+ }();
+
+ return aGreyPalette256;
+ }
+ }
+ OSL_FAIL("Bitmap::GetGreyPalette: invalid entry count (2/4/16/256 allowed)");
+ return GetGreyPalette(2);
+}
+
+Bitmap& Bitmap::operator=( const Bitmap& rBitmap )
+{
+ if (this == &rBitmap)
+ return *this;
+
+ maPrefSize = rBitmap.maPrefSize;
+ maPrefMapMode = rBitmap.maPrefMapMode;
+ mxSalBmp = rBitmap.mxSalBmp;
+
+ return *this;
+}
+
+Bitmap& Bitmap::operator=( Bitmap&& rBitmap ) noexcept
+{
+ maPrefSize = std::move(rBitmap.maPrefSize);
+ maPrefMapMode = std::move(rBitmap.maPrefMapMode);
+ mxSalBmp = std::move(rBitmap.mxSalBmp);
+
+ return *this;
+}
+
+bool Bitmap::operator==( const Bitmap& rBmp ) const
+{
+ if (rBmp.mxSalBmp == mxSalBmp) // Includes both are nullptr
+ return true;
+ if (!rBmp.mxSalBmp || !mxSalBmp)
+ return false;
+ if (rBmp.mxSalBmp->GetSize() != mxSalBmp->GetSize() ||
+ rBmp.mxSalBmp->GetBitCount() != mxSalBmp->GetBitCount())
+ return false;
+ BitmapChecksum aChecksum1 = rBmp.mxSalBmp->GetChecksum();
+ BitmapChecksum aChecksum2 = mxSalBmp->GetChecksum();
+ // If the bitmaps can't calculate a checksum, best to regard them as different.
+ if (aChecksum1 == 0 || aChecksum2 == 0)
+ return false;
+ return aChecksum1 == aChecksum2;
+}
+
+void Bitmap::SetEmpty()
+{
+ maPrefMapMode = MapMode();
+ maPrefSize = Size();
+ mxSalBmp.reset();
+}
+
+Size Bitmap::GetSizePixel() const
+{
+ return( mxSalBmp ? mxSalBmp->GetSize() : Size() );
+}
+
+vcl::PixelFormat Bitmap::getPixelFormat() const
+{
+ if (!mxSalBmp)
+ return vcl::PixelFormat::INVALID;
+
+ sal_uInt16 nBitCount = mxSalBmp->GetBitCount();
+ if (nBitCount <= 1)
+ return vcl::PixelFormat::N1_BPP;
+ if (nBitCount <= 8)
+ return vcl::PixelFormat::N8_BPP;
+ if (nBitCount <= 24)
+ return vcl::PixelFormat::N24_BPP;
+ if (nBitCount <= 32)
+ return vcl::PixelFormat::N32_BPP;
+
+ return vcl::PixelFormat::INVALID;
+}
+
+bool Bitmap::HasGreyPaletteAny() const
+{
+ bool bRet = getPixelFormat() == vcl::PixelFormat::N1_BPP;
+
+ ScopedInfoAccess pIAcc(const_cast<Bitmap&>(*this));
+
+ if( pIAcc )
+ {
+ bRet = pIAcc->HasPalette() && pIAcc->GetPalette().IsGreyPaletteAny();
+ }
+
+ return bRet;
+}
+
+bool Bitmap::HasGreyPalette8Bit() const
+{
+ bool bRet = false;
+ ScopedInfoAccess pIAcc(const_cast<Bitmap&>(*this));
+
+ if( pIAcc )
+ {
+ bRet = pIAcc->HasPalette() && pIAcc->GetPalette().IsGreyPalette8Bit();
+ }
+
+ return bRet;
+}
+
+BitmapChecksum Bitmap::GetChecksum() const
+{
+ BitmapChecksum nRet = 0;
+
+ if( mxSalBmp )
+ {
+ nRet = mxSalBmp->GetChecksum();
+
+ if (!nRet)
+ {
+ // nRet == 0 => probably, we were not able to acquire
+ // the buffer in SalBitmap::updateChecksum;
+ // so, we need to update the imp bitmap for this bitmap instance
+ // as we do in BitmapInfoAccess::ImplCreate
+ std::shared_ptr<SalBitmap> xNewImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xNewImpBmp->Create(*mxSalBmp, getPixelFormat()))
+ {
+ Bitmap* pThis = const_cast<Bitmap*>(this);
+ pThis->mxSalBmp = xNewImpBmp;
+ nRet = mxSalBmp->GetChecksum();
+ }
+ }
+ }
+
+ return nRet;
+}
+
+void Bitmap::ImplMakeUnique()
+{
+ if (mxSalBmp && mxSalBmp.use_count() > 1)
+ {
+ std::shared_ptr<SalBitmap> xOldImpBmp = mxSalBmp;
+ mxSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
+ (void)mxSalBmp->Create(*xOldImpBmp);
+ }
+}
+
+void Bitmap::ReassignWithSize(const Bitmap& rBitmap)
+{
+ const Size aOldSizePix(GetSizePixel());
+ const Size aNewSizePix(rBitmap.GetSizePixel());
+ const MapMode aOldMapMode(maPrefMapMode);
+ Size aNewPrefSize;
+
+ if ((aOldSizePix != aNewSizePix) && aOldSizePix.Width() && aOldSizePix.Height())
+ {
+ aNewPrefSize.setWidth(FRound(maPrefSize.Width() * aNewSizePix.Width() / aOldSizePix.Width()));
+ aNewPrefSize.setHeight(FRound(maPrefSize.Height() * aNewSizePix.Height() / aOldSizePix.Height()));
+ }
+ else
+ {
+ aNewPrefSize = maPrefSize;
+ }
+
+ *this = rBitmap;
+
+ maPrefSize = aNewPrefSize;
+ maPrefMapMode = aOldMapMode;
+}
+
+void Bitmap::ImplSetSalBitmap(const std::shared_ptr<SalBitmap>& xImpBmp)
+{
+ mxSalBmp = xImpBmp;
+}
+
+BitmapInfoAccess* Bitmap::AcquireInfoAccess()
+{
+ std::unique_ptr<BitmapInfoAccess> pInfoAccess(new BitmapInfoAccess( *this ));
+
+ if( !*pInfoAccess )
+ {
+ return nullptr;
+ }
+
+ return pInfoAccess.release();
+}
+
+BitmapReadAccess* Bitmap::AcquireReadAccess()
+{
+ std::unique_ptr<BitmapReadAccess> pReadAccess(new BitmapReadAccess( *this ));
+
+ if( !*pReadAccess )
+ {
+ return nullptr;
+ }
+
+ return pReadAccess.release();
+}
+
+BitmapWriteAccess* Bitmap::AcquireWriteAccess()
+{
+ std::unique_ptr<BitmapWriteAccess> pWriteAccess(new BitmapWriteAccess( *this ));
+
+ if( !*pWriteAccess )
+ {
+ return nullptr;
+ }
+
+ return pWriteAccess.release();
+}
+
+void Bitmap::ReleaseAccess( BitmapInfoAccess* pBitmapAccess )
+{
+ delete pBitmapAccess;
+}
+
+bool Bitmap::Crop( const tools::Rectangle& rRectPixel )
+{
+ const Size aSizePix( GetSizePixel() );
+ tools::Rectangle aRect( rRectPixel );
+ bool bRet = false;
+
+ aRect.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( !aRect.IsEmpty() && aSizePix != aRect.GetSize())
+ {
+ ScopedReadAccess pReadAcc(*this);
+
+ if( pReadAcc )
+ {
+ const tools::Rectangle aNewRect( Point(), aRect.GetSize() );
+ Bitmap aNewBmp(aNewRect.GetSize(), getPixelFormat(), &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if( pWriteAcc )
+ {
+ const tools::Long nOldX = aRect.Left();
+ const tools::Long nOldY = aRect.Top();
+ const tools::Long nNewWidth = aNewRect.GetWidth();
+ const tools::Long nNewHeight = aNewRect.GetHeight();
+
+ for( tools::Long nY = 0, nY2 = nOldY; nY < nNewHeight; nY++, nY2++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY2);
+ for( tools::Long nX = 0, nX2 = nOldX; nX < nNewWidth; nX++, nX2++ )
+ pWriteAcc->SetPixelOnData( pScanline, nX, pReadAcc->GetPixelFromData( pScanlineRead, nX2 ) );
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if( bRet )
+ ReassignWithSize( aNewBmp );
+ }
+ }
+
+ return bRet;
+};
+
+bool Bitmap::CopyPixel( const tools::Rectangle& rRectDst,
+ const tools::Rectangle& rRectSrc, const Bitmap* pBmpSrc )
+{
+ const Size aSizePix( GetSizePixel() );
+ tools::Rectangle aRectDst( rRectDst );
+ bool bRet = false;
+
+ aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( !aRectDst.IsEmpty() )
+ {
+ if( pBmpSrc && ( pBmpSrc->mxSalBmp != mxSalBmp ) )
+ {
+ Bitmap* pSrc = const_cast<Bitmap*>(pBmpSrc);
+ const Size aCopySizePix( pSrc->GetSizePixel() );
+ tools::Rectangle aRectSrc( rRectSrc );
+ const sal_uInt16 nSrcBitCount = vcl::pixelFormatBitCount(pBmpSrc->getPixelFormat());
+ const sal_uInt16 nDstBitCount = vcl::pixelFormatBitCount(getPixelFormat());
+
+ if( nSrcBitCount > nDstBitCount )
+ {
+ int nNextIndex = 0;
+
+ if (nSrcBitCount == 24)
+ Convert( BmpConversion::N24Bit );
+ else if (nSrcBitCount == 8)
+ {
+ Convert( BmpConversion::N8BitColors );
+ nNextIndex = 16;
+ }
+ else if (nSrcBitCount == 4)
+ {
+ assert(false);
+ }
+
+ if( nNextIndex )
+ {
+ ScopedReadAccess pSrcAcc(*pSrc);
+ BitmapScopedWriteAccess pDstAcc(*this);
+
+ if( pSrcAcc && pDstAcc )
+ {
+ const int nSrcCount = pSrcAcc->GetPaletteEntryCount();
+ const int nDstCount = 1 << nDstBitCount;
+
+ for (int i = 0; ( i < nSrcCount ) && ( nNextIndex < nDstCount ); ++i)
+ {
+ const BitmapColor& rSrcCol = pSrcAcc->GetPaletteColor( static_cast<sal_uInt16>(i) );
+
+ bool bFound = false;
+
+ for (int j = 0; j < nDstCount; ++j)
+ {
+ if( rSrcCol == pDstAcc->GetPaletteColor( static_cast<sal_uInt16>(j) ) )
+ {
+ bFound = true;
+ break;
+ }
+ }
+
+ if( !bFound )
+ pDstAcc->SetPaletteColor( static_cast<sal_uInt16>(nNextIndex++), rSrcCol );
+ }
+ }
+ }
+ }
+
+ aRectSrc.Intersection( tools::Rectangle( Point(), aCopySizePix ) );
+
+ if( !aRectSrc.IsEmpty() )
+ {
+ ScopedReadAccess pReadAcc(*pSrc);
+
+ if( pReadAcc )
+ {
+ BitmapScopedWriteAccess pWriteAcc(*this);
+
+ if( pWriteAcc )
+ {
+ const tools::Long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
+ const tools::Long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
+ const tools::Long nSrcEndX = aRectSrc.Left() + nWidth;
+ const tools::Long nSrcEndY = aRectSrc.Top() + nHeight;
+ tools::Long nDstY = aRectDst.Top();
+
+ if( pReadAcc->HasPalette() && pWriteAcc->HasPalette() )
+ {
+ const sal_uInt16 nCount = pReadAcc->GetPaletteEntryCount();
+ std::unique_ptr<sal_uInt8[]> pMap(new sal_uInt8[ nCount ]);
+
+ // Create index map for the color table, as the bitmap should be copied
+ // retaining it's color information relatively well
+ for( sal_uInt16 i = 0; i < nCount; i++ )
+ pMap[ i ] = static_cast<sal_uInt8>(pWriteAcc->GetBestPaletteIndex( pReadAcc->GetPaletteColor( i ) ));
+
+ for( tools::Long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nDstY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
+ for( tools::Long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nDstX, BitmapColor( pMap[ pReadAcc->GetIndexFromData( pScanlineRead, nSrcX ) ] ));
+ }
+ }
+ else if( pReadAcc->HasPalette() )
+ {
+ for( tools::Long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nDstY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
+ for( tools::Long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPaletteColor( pReadAcc->GetIndexFromData( pScanlineRead, nSrcX ) ) );
+ }
+ }
+ else
+ for( tools::Long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nDstY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
+ for( tools::Long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPixelFromData( pScanlineRead, nSrcX ) );
+ }
+
+ pWriteAcc.reset();
+ bRet = ( nWidth > 0 ) && ( nHeight > 0 );
+ }
+
+ pReadAcc.reset();
+ }
+ }
+ }
+ else
+ {
+ tools::Rectangle aRectSrc( rRectSrc );
+
+ aRectSrc.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( !aRectSrc.IsEmpty() && ( aRectSrc != aRectDst ) )
+ {
+ BitmapScopedWriteAccess pWriteAcc(*this);
+
+ if( pWriteAcc )
+ {
+ const tools::Long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
+ const tools::Long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
+ const tools::Long nSrcX = aRectSrc.Left();
+ const tools::Long nSrcY = aRectSrc.Top();
+ const tools::Long nSrcEndX1 = nSrcX + nWidth - 1;
+ const tools::Long nSrcEndY1 = nSrcY + nHeight - 1;
+ const tools::Long nDstX = aRectDst.Left();
+ const tools::Long nDstY = aRectDst.Top();
+ const tools::Long nDstEndX1 = nDstX + nWidth - 1;
+ const tools::Long nDstEndY1 = nDstY + nHeight - 1;
+
+ if( ( nDstX <= nSrcX ) && ( nDstY <= nSrcY ) )
+ {
+ for( tools::Long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else if( ( nDstX <= nSrcX ) && ( nDstY >= nSrcY ) )
+ {
+ for( tools::Long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else if( ( nDstX >= nSrcX ) && ( nDstY <= nSrcY ) )
+ {
+ for( tools::Long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else
+ {
+ for( tools::Long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+bool Bitmap::CopyPixel_AlphaOptimized( const tools::Rectangle& rRectDst, const tools::Rectangle& rRectSrc,
+ const Bitmap* pBmpSrc )
+{
+ // Note: this code is copied from Bitmap::CopyPixel but avoids any palette lookups
+ // This optimization is possible because the palettes of AlphaMasks are always identical (8bit GreyPalette, see ctor)
+ const Size aSizePix( GetSizePixel() );
+ tools::Rectangle aRectDst( rRectDst );
+ bool bRet = false;
+
+ aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( !aRectDst.IsEmpty() )
+ {
+ if( pBmpSrc && ( pBmpSrc->mxSalBmp != mxSalBmp ) )
+ {
+ Bitmap* pSrc = const_cast<Bitmap*>(pBmpSrc);
+ const Size aCopySizePix( pSrc->GetSizePixel() );
+ tools::Rectangle aRectSrc( rRectSrc );
+
+ aRectSrc.Intersection( tools::Rectangle( Point(), aCopySizePix ) );
+
+ if( !aRectSrc.IsEmpty() )
+ {
+ ScopedReadAccess pReadAcc(*pSrc);
+
+ if( pReadAcc )
+ {
+ BitmapScopedWriteAccess pWriteAcc(*this);
+
+ if( pWriteAcc )
+ {
+ const tools::Long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
+ const tools::Long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
+ const tools::Long nSrcEndX = aRectSrc.Left() + nWidth;
+ const tools::Long nSrcEndY = aRectSrc.Top() + nHeight;
+ tools::Long nDstY = aRectDst.Top();
+
+ for( tools::Long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nDstY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
+ for( tools::Long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPixelFromData( pScanlineRead, nSrcX ) );
+ }
+
+ pWriteAcc.reset();
+ bRet = ( nWidth > 0 ) && ( nHeight > 0 );
+ }
+
+ pReadAcc.reset();
+ }
+ }
+ }
+ else
+ {
+ tools::Rectangle aRectSrc( rRectSrc );
+
+ aRectSrc.Intersection( tools::Rectangle( Point(), aSizePix ) );
+
+ if( !aRectSrc.IsEmpty() && ( aRectSrc != aRectDst ) )
+ {
+ BitmapScopedWriteAccess pWriteAcc(*this);
+
+ if( pWriteAcc )
+ {
+ const tools::Long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
+ const tools::Long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
+ const tools::Long nSrcX = aRectSrc.Left();
+ const tools::Long nSrcY = aRectSrc.Top();
+ const tools::Long nSrcEndX1 = nSrcX + nWidth - 1;
+ const tools::Long nSrcEndY1 = nSrcY + nHeight - 1;
+ const tools::Long nDstX = aRectDst.Left();
+ const tools::Long nDstY = aRectDst.Top();
+ const tools::Long nDstEndX1 = nDstX + nWidth - 1;
+ const tools::Long nDstEndY1 = nDstY + nHeight - 1;
+
+ if( ( nDstX <= nSrcX ) && ( nDstY <= nSrcY ) )
+ {
+ for( tools::Long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else if( ( nDstX <= nSrcX ) && ( nDstY >= nSrcY ) )
+ {
+ for( tools::Long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else if( ( nDstX >= nSrcX ) && ( nDstY <= nSrcY ) )
+ {
+ for( tools::Long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+ else
+ {
+ for( tools::Long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nYN);
+ Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
+ for( tools::Long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
+ pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
+ }
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+ }
+ }
+ }
+
+ return bRet;
+
+}
+
+bool Bitmap::Expand( sal_Int32 nDX, sal_Int32 nDY, const Color* pInitColor )
+{
+ bool bRet = false;
+
+ if( nDX || nDY )
+ {
+ const Size aSizePixel( GetSizePixel() );
+ const tools::Long nWidth = aSizePixel.Width();
+ const tools::Long nHeight = aSizePixel.Height();
+ const Size aNewSize( nWidth + nDX, nHeight + nDY );
+ ScopedReadAccess pReadAcc(*this);
+
+ if( pReadAcc )
+ {
+ BitmapPalette aBmpPal( pReadAcc->GetPalette() );
+ Bitmap aNewBmp(aNewSize, getPixelFormat(), &aBmpPal);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if( pWriteAcc )
+ {
+ BitmapColor aColor;
+ const tools::Long nNewX = nWidth;
+ const tools::Long nNewY = nHeight;
+ const tools::Long nNewWidth = pWriteAcc->Width();
+ const tools::Long nNewHeight = pWriteAcc->Height();
+ tools::Long nX;
+ tools::Long nY;
+
+ if( pInitColor )
+ aColor = pWriteAcc->GetBestMatchingColor( *pInitColor );
+
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ pWriteAcc->CopyScanline( nY, *pReadAcc );
+
+ if( pInitColor && nDX )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for( nX = nNewX; nX < nNewWidth; nX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nX, aColor );
+ }
+ }
+
+ if( pInitColor && nDY )
+ for( nY = nNewY; nY < nNewHeight; nY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for( nX = 0; nX < nNewWidth; nX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nX, aColor );
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if (bRet)
+ ReassignWithSize(aNewBmp);
+ }
+ }
+
+ return bRet;
+}
+
+Bitmap Bitmap::CreateDisplayBitmap( OutputDevice* pDisplay ) const
+{
+ Bitmap aDispBmp( *this );
+
+ SalGraphics* pDispGraphics = pDisplay->GetGraphics();
+
+ if( mxSalBmp && pDispGraphics )
+ {
+ std::shared_ptr<SalBitmap> xImpDispBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpDispBmp->Create(*mxSalBmp, pDispGraphics))
+ aDispBmp.ImplSetSalBitmap(xImpDispBmp);
+ }
+
+ return aDispBmp;
+}
+
+bool Bitmap::GetSystemData( BitmapSystemData& rData ) const
+{
+ return mxSalBmp && mxSalBmp->GetSystemData(rData);
+}
+
+
+bool Bitmap::Convert( BmpConversion eConversion )
+{
+ // try to convert in backend
+ if (mxSalBmp)
+ {
+ // avoid large chunk of obsolete and hopefully rarely used conversions.
+ if (eConversion == BmpConversion::N8BitNoConversion)
+ {
+ if (mxSalBmp->GetBitCount() == 8 && HasGreyPalette8Bit())
+ return true;
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ // frequently used conversion for creating alpha masks
+ if (xImpBmp->Create(*mxSalBmp) && xImpBmp->InterpretAs8Bit())
+ {
+ ImplSetSalBitmap(xImpBmp);
+ SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
+ return true;
+ }
+ }
+ if (eConversion == BmpConversion::N8BitGreys)
+ {
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpBmp->Create(*mxSalBmp) && xImpBmp->ConvertToGreyscale())
+ {
+ ImplSetSalBitmap(xImpBmp);
+ SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
+ return true;
+ }
+ }
+ }
+
+ const sal_uInt16 nBitCount = vcl::pixelFormatBitCount(getPixelFormat());
+ bool bRet = false;
+
+ switch( eConversion )
+ {
+ case BmpConversion::N1BitThreshold:
+ {
+ BitmapEx aBmpEx(*this);
+ bRet = BitmapFilter::Filter(aBmpEx, BitmapMonochromeFilter(128));
+ *this = aBmpEx.GetBitmap();
+ }
+ break;
+
+ case BmpConversion::N8BitGreys:
+ case BmpConversion::N8BitNoConversion:
+ bRet = ImplMakeGreyscales();
+ break;
+
+ case BmpConversion::N8BitColors:
+ {
+ if( nBitCount < 8 )
+ bRet = ImplConvertUp(vcl::PixelFormat::N8_BPP);
+ else if( nBitCount > 8 )
+ bRet = ImplConvertDown8BPP();
+ else
+ bRet = true;
+ }
+ break;
+
+ case BmpConversion::N8BitTrans:
+ {
+ Color aTrans( BMP_COL_TRANS );
+
+ if( nBitCount < 8 )
+ bRet = ImplConvertUp(vcl::PixelFormat::N8_BPP, &aTrans );
+ else
+ bRet = ImplConvertDown8BPP(&aTrans );
+ }
+ break;
+
+ case BmpConversion::N24Bit:
+ {
+ if( nBitCount < 24 )
+ bRet = ImplConvertUp(vcl::PixelFormat::N24_BPP);
+ else
+ bRet = true;
+ }
+ break;
+
+ case BmpConversion::N32Bit:
+ {
+ if( nBitCount < 32 )
+ bRet = ImplConvertUp(vcl::PixelFormat::N32_BPP);
+ else
+ bRet = true;
+ }
+ break;
+
+ default:
+ OSL_FAIL( "Bitmap::Convert(): Unsupported conversion" );
+ break;
+ }
+
+ return bRet;
+}
+
+bool Bitmap::ImplMakeGreyscales()
+{
+ ScopedReadAccess pReadAcc(*this);
+ bool bRet = false;
+
+ if( pReadAcc )
+ {
+ const BitmapPalette& rPal = GetGreyPalette(256);
+ sal_uLong nShift = 0;
+ bool bPalDiffers = !pReadAcc->HasPalette() || ( rPal.GetEntryCount() != pReadAcc->GetPaletteEntryCount() );
+
+ if( !bPalDiffers )
+ bPalDiffers = ( rPal != pReadAcc->GetPalette() );
+
+ if( bPalDiffers )
+ {
+ const auto ePixelFormat = vcl::PixelFormat::N8_BPP;
+ Bitmap aNewBmp(GetSizePixel(), ePixelFormat, &rPal );
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if( pWriteAcc )
+ {
+ const tools::Long nWidth = pWriteAcc->Width();
+ const tools::Long nHeight = pWriteAcc->Height();
+
+ if( pReadAcc->HasPalette() )
+ {
+ for( tools::Long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ const sal_uInt8 cIndex = pReadAcc->GetIndexFromData( pScanlineRead, nX );
+ pWriteAcc->SetPixelOnData( pScanline, nX,
+ BitmapColor(pReadAcc->GetPaletteColor( cIndex ).GetLuminance() >> nShift) );
+ }
+ }
+ }
+ else if( pReadAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr &&
+ pWriteAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal )
+ {
+ nShift += 8;
+
+ for( tools::Long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pReadScan = pReadAcc->GetScanline( nY );
+ Scanline pWriteScan = pWriteAcc->GetScanline( nY );
+
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ const sal_uLong nB = *pReadScan++;
+ const sal_uLong nG = *pReadScan++;
+ const sal_uLong nR = *pReadScan++;
+
+ *pWriteScan++ = static_cast<sal_uInt8>( ( nB * 28UL + nG * 151UL + nR * 77UL ) >> nShift );
+ }
+ }
+ }
+ else if( pReadAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb &&
+ pWriteAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal )
+ {
+ nShift += 8;
+
+ for( tools::Long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pReadScan = pReadAcc->GetScanline( nY );
+ Scanline pWriteScan = pWriteAcc->GetScanline( nY );
+
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ const sal_uLong nR = *pReadScan++;
+ const sal_uLong nG = *pReadScan++;
+ const sal_uLong nB = *pReadScan++;
+
+ *pWriteScan++ = static_cast<sal_uInt8>( ( nB * 28UL + nG * 151UL + nR * 77UL ) >> nShift );
+ }
+ }
+ }
+ else
+ {
+ for( tools::Long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nX, BitmapColor(pReadAcc->GetPixelFromData( pScanlineRead, nX ).GetLuminance() >> nShift) );
+ }
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if( bRet )
+ {
+ const MapMode aMap( maPrefMapMode );
+ const Size aSize( maPrefSize );
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+ }
+ }
+ else
+ {
+ pReadAcc.reset();
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+bool Bitmap::ImplConvertUp(vcl::PixelFormat ePixelFormat, Color const * pExtColor)
+{
+ SAL_WARN_IF(ePixelFormat <= getPixelFormat(), "vcl", "New pixel format must be greater!" );
+
+ Bitmap::ScopedReadAccess pReadAcc(*this);
+ bool bRet = false;
+
+ if (pReadAcc)
+ {
+ BitmapPalette aPalette;
+ Bitmap aNewBmp(GetSizePixel(), ePixelFormat, pReadAcc->HasPalette() ? &pReadAcc->GetPalette() : &aPalette);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const tools::Long nWidth = pWriteAcc->Width();
+ const tools::Long nHeight = pWriteAcc->Height();
+
+ if (pWriteAcc->HasPalette())
+ {
+ const BitmapPalette& rOldPalette = pReadAcc->GetPalette();
+ const sal_uInt16 nOldCount = rOldPalette.GetEntryCount();
+ assert(nOldCount <= (1 << vcl::pixelFormatBitCount(getPixelFormat())));
+
+ aPalette.SetEntryCount(1 << vcl::pixelFormatBitCount(ePixelFormat));
+
+ for (sal_uInt16 i = 0; i < nOldCount; i++)
+ aPalette[i] = rOldPalette[i];
+
+ if (pExtColor)
+ aPalette[aPalette.GetEntryCount() - 1] = *pExtColor;
+
+ pWriteAcc->SetPalette(aPalette);
+
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPixelFromData(pScanlineRead, nX));
+ }
+ }
+ }
+ else
+ {
+ if (pReadAcc->HasPalette())
+ {
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX)));
+ }
+ }
+ }
+ else
+ {
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPixelFromData(pScanlineRead, nX));
+ }
+ }
+ }
+ }
+ bRet = true;
+ }
+
+ if (bRet)
+ {
+ const MapMode aMap(maPrefMapMode);
+ const Size aSize(maPrefSize);
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+ }
+ }
+
+ return bRet;
+}
+
+bool Bitmap::ImplConvertDown8BPP(Color const * pExtColor)
+{
+ SAL_WARN_IF(vcl::PixelFormat::N8_BPP > getPixelFormat(), "vcl", "New pixelformat must be lower ( or equal when pExtColor is set )!");
+
+ Bitmap::ScopedReadAccess pReadAcc(*this);
+ bool bRet = false;
+
+ if (pReadAcc)
+ {
+ BitmapPalette aPalette;
+ Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N8_BPP, &aPalette);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ sal_Int16 nNewBitCount = sal_Int16(vcl::PixelFormat::N8_BPP);
+ const sal_uInt16 nCount = 1 << nNewBitCount;
+ const tools::Long nWidth = pWriteAcc->Width();
+ const tools::Long nWidth1 = nWidth - 1;
+ const tools::Long nHeight = pWriteAcc->Height();
+ Octree aOctree(*pReadAcc, pExtColor ? (nCount - 1) : nCount);
+ aPalette = aOctree.GetPalette();
+ InverseColorMap aColorMap(aPalette);
+ BitmapColor aColor;
+ ImpErrorQuad aErrQuad;
+ std::vector<ImpErrorQuad> aErrQuad1(nWidth);
+ std::vector<ImpErrorQuad> aErrQuad2(nWidth);
+ ImpErrorQuad* pQLine1 = aErrQuad1.data();
+ ImpErrorQuad* pQLine2 = nullptr;
+ tools::Long nYTmp = 0;
+ sal_uInt8 cIndex;
+ bool bQ1 = true;
+
+ if (pExtColor)
+ {
+ aPalette.SetEntryCount(aPalette.GetEntryCount() + 1);
+ aPalette[aPalette.GetEntryCount() - 1] = *pExtColor;
+ }
+
+ // set Black/White always, if we have enough space
+ if (aPalette.GetEntryCount() < (nCount - 1))
+ {
+ aPalette.SetEntryCount(aPalette.GetEntryCount() + 2);
+ aPalette[aPalette.GetEntryCount() - 2] = COL_BLACK;
+ aPalette[aPalette.GetEntryCount() - 1] = COL_WHITE;
+ }
+
+ pWriteAcc->SetPalette(aPalette);
+
+ for (tools::Long nY = 0; nY < std::min(nHeight, tools::Long(2)); nY++, nYTmp++)
+ {
+ pQLine2 = !nY ? aErrQuad1.data() : aErrQuad2.data();
+ Scanline pScanlineRead = pReadAcc->GetScanline(nYTmp);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ if (pReadAcc->HasPalette())
+ pQLine2[nX] = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX));
+ else
+ pQLine2[nX] = pReadAcc->GetPixelFromData(pScanlineRead, nX);
+ }
+ }
+
+ assert(pQLine2 || nHeight == 0);
+
+ for (tools::Long nY = 0; nY < nHeight; nY++, nYTmp++)
+ {
+ // first pixel in the line
+ cIndex = static_cast<sal_uInt8>(aColorMap.GetBestPaletteIndex(pQLine1[0].ImplGetColor()));
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ pWriteAcc->SetPixelOnData(pScanline, 0, BitmapColor(cIndex));
+
+ tools::Long nX;
+ for (nX = 1; nX < nWidth1; nX++)
+ {
+ aColor = pQLine1[nX].ImplGetColor();
+ cIndex = static_cast<sal_uInt8>(aColorMap.GetBestPaletteIndex(aColor));
+ aErrQuad = (ImpErrorQuad(aColor) -= pWriteAcc->GetPaletteColor(cIndex));
+ pQLine1[++nX].ImplAddColorError7(aErrQuad);
+ pQLine2[nX--].ImplAddColorError1(aErrQuad);
+ pQLine2[nX--].ImplAddColorError5(aErrQuad);
+ pQLine2[nX++].ImplAddColorError3(aErrQuad);
+ pWriteAcc->SetPixelOnData(pScanline, nX, BitmapColor(cIndex));
+ }
+
+ // Last RowPixel
+ if (nX < nWidth)
+ {
+ cIndex = static_cast<sal_uInt8>(aColorMap.GetBestPaletteIndex(pQLine1[nWidth1].ImplGetColor()));
+ pWriteAcc->SetPixelOnData(pScanline, nX, BitmapColor(cIndex));
+ }
+
+ // Refill/copy row buffer
+ pQLine1 = pQLine2;
+ bQ1 = !bQ1;
+ pQLine2 = bQ1 ? aErrQuad2.data() : aErrQuad1.data();
+
+ if (nYTmp < nHeight)
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nYTmp);
+ for (nX = 0; nX < nWidth; nX++)
+ {
+ if (pReadAcc->HasPalette())
+ pQLine2[nX] = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX));
+ else
+ pQLine2[nX] = pReadAcc->GetPixelFromData(pScanlineRead, nX);
+ }
+ }
+ }
+
+ bRet = true;
+ }
+ pWriteAcc.reset();
+
+ if(bRet)
+ {
+ const MapMode aMap(maPrefMapMode);
+ const Size aSize(maPrefSize);
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+ }
+ }
+
+ return bRet;
+}
+
+bool Bitmap::Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag )
+{
+ if(basegfx::fTools::equalZero(rScaleX) || basegfx::fTools::equalZero(rScaleY))
+ {
+ // no scale
+ return true;
+ }
+
+ if(basegfx::fTools::equal(rScaleX, 1.0) && basegfx::fTools::equal(rScaleY, 1.0))
+ {
+ // no scale
+ return true;
+ }
+
+ const auto eStartPixelFormat = getPixelFormat();
+
+ if (mxSalBmp && mxSalBmp->ScalingSupported())
+ {
+ // implementation specific scaling
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Scale(rScaleX, rScaleY, nScaleFlag))
+ {
+ ImplSetSalBitmap(xImpBmp);
+ SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
+ maPrefMapMode = MapMode( MapUnit::MapPixel );
+ maPrefSize = xImpBmp->GetSize();
+ return true;
+ }
+ }
+
+ // fdo#33455
+ //
+ // If we start with a 1 bit image, then after scaling it in any mode except
+ // BmpScaleFlag::Fast we have a 24bit image which is perfectly correct, but we
+ // are going to down-shift it to mono again and Bitmap::MakeMonochrome just
+ // has "Bitmap aNewBmp( GetSizePixel(), 1 );" to create a 1 bit bitmap which
+ // will default to black/white and the colors mapped to which ever is closer
+ // to black/white
+ //
+ // So the easiest thing to do to retain the colors of 1 bit bitmaps is to
+ // just use the fast scale rather than attempting to count unique colors in
+ // the other converters and pass all the info down through
+ // Bitmap::MakeMonochrome
+ if (eStartPixelFormat == vcl::PixelFormat::N1_BPP)
+ nScaleFlag = BmpScaleFlag::Fast;
+
+ BitmapEx aBmpEx(*this);
+ bool bRetval(false);
+
+ switch(nScaleFlag)
+ {
+ case BmpScaleFlag::Default:
+ if (GetSizePixel().Width() < 2 || GetSizePixel().Height() < 2)
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapFastScaleFilter(rScaleX, rScaleY));
+ else
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapScaleSuperFilter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::Fast:
+ case BmpScaleFlag::NearestNeighbor:
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapFastScaleFilter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::Interpolate:
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapInterpolateScaleFilter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::BestQuality:
+ case BmpScaleFlag::Lanczos:
+ bRetval = BitmapFilter::Filter(aBmpEx, vcl::BitmapScaleLanczos3Filter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::BiCubic:
+ bRetval = BitmapFilter::Filter(aBmpEx, vcl::BitmapScaleBicubicFilter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::BiLinear:
+ bRetval = BitmapFilter::Filter(aBmpEx, vcl::BitmapScaleBilinearFilter(rScaleX, rScaleY));
+ break;
+ }
+
+ if (bRetval)
+ *this = aBmpEx.GetBitmap();
+
+ OSL_ENSURE(!bRetval || eStartPixelFormat == getPixelFormat(), "Bitmap::Scale has changed the ColorDepth, this should *not* happen (!)");
+ return bRetval;
+}
+
+bool Bitmap::Scale( const Size& rNewSize, BmpScaleFlag nScaleFlag )
+{
+ const Size aSize( GetSizePixel() );
+ bool bRet;
+
+ if( aSize.Width() && aSize.Height() )
+ {
+ bRet = Scale( static_cast<double>(rNewSize.Width()) / aSize.Width(),
+ static_cast<double>(rNewSize.Height()) / aSize.Height(),
+ nScaleFlag );
+ }
+ else
+ bRet = true;
+
+ return bRet;
+}
+
+bool Bitmap::HasFastScale()
+{
+#if HAVE_FEATURE_SKIA
+ if( SkiaHelper::isVCLSkiaEnabled() && SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster)
+ return true;
+#endif
+ return false;
+}
+
+void Bitmap::AdaptBitCount(Bitmap& rNew) const
+{
+ // aNew is the result of some operation; adapt it's BitCount to the original (this)
+ if (getPixelFormat() == rNew.getPixelFormat())
+ return;
+
+ switch (getPixelFormat())
+ {
+ case vcl::PixelFormat::N1_BPP:
+ {
+ rNew.Convert(BmpConversion::N1BitThreshold);
+ break;
+ }
+ case vcl::PixelFormat::N8_BPP:
+ {
+ if(HasGreyPaletteAny())
+ {
+ rNew.Convert(BmpConversion::N8BitGreys);
+ }
+ else
+ {
+ rNew.Convert(BmpConversion::N8BitColors);
+ }
+ break;
+ }
+ case vcl::PixelFormat::N24_BPP:
+ {
+ rNew.Convert(BmpConversion::N24Bit);
+ break;
+ }
+ case vcl::PixelFormat::N32_BPP:
+ {
+ rNew.Convert(BmpConversion::N32Bit);
+ break;
+ }
+ case vcl::PixelFormat::INVALID:
+ {
+ SAL_WARN("vcl", "Can't adapt the pixelformat as it is invalid.");
+ break;
+ }
+ }
+}
+
+static sal_Int32* shiftColor(sal_Int32* pColorArray, BitmapColor const& rColor)
+{
+ *pColorArray++ = static_cast<sal_Int32>(rColor.GetBlue()) << 12;
+ *pColorArray++ = static_cast<sal_Int32>(rColor.GetGreen()) << 12;
+ *pColorArray++ = static_cast<sal_Int32>(rColor.GetRed()) << 12;
+ return pColorArray;
+}
+static BitmapColor getColor(const BitmapReadAccess *pReadAcc, tools::Long nZ)
+{
+ Scanline pScanlineRead = pReadAcc->GetScanline(0);
+ if (pReadAcc->HasPalette())
+ return pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nZ));
+ else
+ return pReadAcc->GetPixelFromData(pScanlineRead, nZ);
+}
+
+bool Bitmap::Dither()
+{
+ const Size aSize( GetSizePixel() );
+ if( aSize.Width() == 1 || aSize.Height() == 1 )
+ return true;
+ if( ( aSize.Width() > 3 ) && ( aSize.Height() > 2 ) )
+ {
+ ScopedReadAccess pReadAcc(*this);
+ Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N8_BPP);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ if( pReadAcc && pWriteAcc )
+ {
+ tools::Long nWidth = pReadAcc->Width();
+ tools::Long nWidth1 = nWidth - 1;
+ tools::Long nHeight = pReadAcc->Height();
+ tools::Long nW = nWidth * 3;
+ tools::Long nW2 = nW - 3;
+ std::unique_ptr<sal_Int32[]> p1(new sal_Int32[ nW ]);
+ std::unique_ptr<sal_Int32[]> p2(new sal_Int32[ nW ]);
+ sal_Int32* p1T = p1.get();
+ sal_Int32* p2T = p2.get();
+ sal_Int32* pTmp = p2T;
+ for (tools::Long nZ = 0; nZ < nWidth; nZ++)
+ {
+ pTmp = shiftColor(pTmp, getColor(pReadAcc.get(), nZ));
+ }
+ tools::Long nRErr, nGErr, nBErr;
+ tools::Long nRC, nGC, nBC;
+ for( tools::Long nY = 1, nYAcc = 0; nY <= nHeight; nY++, nYAcc++ )
+ {
+ pTmp = p1T;
+ p1T = p2T;
+ p2T = pTmp;
+ if (nY < nHeight)
+ {
+ for (tools::Long nZ = 0; nZ < nWidth; nZ++)
+ {
+ pTmp = shiftColor(pTmp, getColor(pReadAcc.get(), nZ));
+ }
+ }
+ // Examine first Pixel separately
+ tools::Long nX = 0;
+ tools::Long nTemp;
+ CALC_ERRORS;
+ CALC_TABLES7;
+ nX -= 5;
+ CALC_TABLES5;
+ Scanline pScanline = pWriteAcc->GetScanline(nYAcc);
+ pWriteAcc->SetPixelOnData( pScanline, 0, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
+ // Get middle Pixels using a loop
+ tools::Long nXAcc;
+ for ( nX = 3, nXAcc = 1; nX < nW2; nXAcc++ )
+ {
+ CALC_ERRORS;
+ CALC_TABLES7;
+ nX -= 8;
+ CALC_TABLES3;
+ CALC_TABLES5;
+ pWriteAcc->SetPixelOnData( pScanline, nXAcc, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
+ }
+ // Treat last Pixel separately
+ CALC_ERRORS;
+ nX -= 5;
+ CALC_TABLES3;
+ CALC_TABLES5;
+ pWriteAcc->SetPixelOnData( pScanline, nWidth1, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
+ }
+ pReadAcc.reset();
+ pWriteAcc.reset();
+ const MapMode aMap( maPrefMapMode );
+ const Size aPrefSize( maPrefSize );
+ *this = aNewBmp;
+ maPrefMapMode = aMap;
+ maPrefSize = aPrefSize;
+ return true;
+ }
+ pReadAcc.reset();
+ pWriteAcc.reset();
+ }
+ return false;
+}
+
+void Bitmap::Vectorize( GDIMetaFile& rMtf, sal_uInt8 cReduce, const Link<tools::Long,void>* pProgress )
+{
+ ImplVectorizer::ImplVectorize( *this, rMtf, cReduce, pProgress );
+}
+
+bool Bitmap::Adjust( short nLuminancePercent, short nContrastPercent,
+ short nChannelRPercent, short nChannelGPercent, short nChannelBPercent,
+ double fGamma, bool bInvert, bool msoBrightness )
+{
+ bool bRet = false;
+
+ // nothing to do => return quickly
+ if( !nLuminancePercent && !nContrastPercent &&
+ !nChannelRPercent && !nChannelGPercent && !nChannelBPercent &&
+ ( fGamma == 1.0 ) && !bInvert )
+ {
+ bRet = true;
+ }
+ else
+ {
+ BitmapScopedWriteAccess pAcc(*this);
+
+ if( pAcc )
+ {
+ BitmapColor aCol;
+ const tools::Long nW = pAcc->Width();
+ const tools::Long nH = pAcc->Height();
+ std::unique_ptr<sal_uInt8[]> cMapR(new sal_uInt8[ 256 ]);
+ std::unique_ptr<sal_uInt8[]> cMapG(new sal_uInt8[ 256 ]);
+ std::unique_ptr<sal_uInt8[]> cMapB(new sal_uInt8[ 256 ]);
+ double fM, fROff, fGOff, fBOff, fOff;
+
+ // calculate slope
+ if( nContrastPercent >= 0 )
+ fM = 128.0 / ( 128.0 - 1.27 * MinMax( nContrastPercent, 0, 100 ) );
+ else
+ fM = ( 128.0 + 1.27 * MinMax( nContrastPercent, -100, 0 ) ) / 128.0;
+
+ if(!msoBrightness)
+ // total offset = luminance offset + contrast offset
+ fOff = MinMax( nLuminancePercent, -100, 100 ) * 2.55 + 128.0 - fM * 128.0;
+ else
+ fOff = MinMax( nLuminancePercent, -100, 100 ) * 2.55;
+
+ // channel offset = channel offset + total offset
+ fROff = nChannelRPercent * 2.55 + fOff;
+ fGOff = nChannelGPercent * 2.55 + fOff;
+ fBOff = nChannelBPercent * 2.55 + fOff;
+
+ // calculate gamma value
+ fGamma = ( fGamma <= 0.0 || fGamma > 10.0 ) ? 1.0 : ( 1.0 / fGamma );
+ const bool bGamma = ( fGamma != 1.0 );
+
+ // create mapping table
+ for( tools::Long nX = 0; nX < 256; nX++ )
+ {
+ if(!msoBrightness)
+ {
+ cMapR[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fROff ), 0, 255 ));
+ cMapG[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fGOff ), 0, 255 ));
+ cMapB[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fBOff ), 0, 255 ));
+ }
+ else
+ {
+ // LO simply uses (in a somewhat optimized form) "newcolor = (oldcolor-128)*contrast+brightness+128"
+ // as the formula, i.e. contrast first, brightness afterwards. MSOffice, for whatever weird reason,
+ // use neither first, but apparently it applies half of brightness before contrast and half afterwards.
+ cMapR[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fROff/2-128) * fM + 128 + fROff/2 ), 0, 255 ));
+ cMapG[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fGOff/2-128) * fM + 128 + fGOff/2 ), 0, 255 ));
+ cMapB[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fBOff/2-128) * fM + 128 + fBOff/2 ), 0, 255 ));
+ }
+ if( bGamma )
+ {
+ cMapR[ nX ] = GAMMA( cMapR[ nX ], fGamma );
+ cMapG[ nX ] = GAMMA( cMapG[ nX ], fGamma );
+ cMapB[ nX ] = GAMMA( cMapB[ nX ], fGamma );
+ }
+
+ if( bInvert )
+ {
+ cMapR[ nX ] = ~cMapR[ nX ];
+ cMapG[ nX ] = ~cMapG[ nX ];
+ cMapB[ nX ] = ~cMapB[ nX ];
+ }
+ }
+
+ // do modifying
+ if( pAcc->HasPalette() )
+ {
+ BitmapColor aNewCol;
+
+ for( sal_uInt16 i = 0, nCount = pAcc->GetPaletteEntryCount(); i < nCount; i++ )
+ {
+ const BitmapColor& rCol = pAcc->GetPaletteColor( i );
+ aNewCol.SetRed( cMapR[ rCol.GetRed() ] );
+ aNewCol.SetGreen( cMapG[ rCol.GetGreen() ] );
+ aNewCol.SetBlue( cMapB[ rCol.GetBlue() ] );
+ pAcc->SetPaletteColor( i, aNewCol );
+ }
+ }
+ else if( pAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr )
+ {
+ for( tools::Long nY = 0; nY < nH; nY++ )
+ {
+ Scanline pScan = pAcc->GetScanline( nY );
+
+ for( tools::Long nX = 0; nX < nW; nX++ )
+ {
+ *pScan = cMapB[ *pScan ]; pScan++;
+ *pScan = cMapG[ *pScan ]; pScan++;
+ *pScan = cMapR[ *pScan ]; pScan++;
+ }
+ }
+ }
+ else if( pAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
+ {
+ for( tools::Long nY = 0; nY < nH; nY++ )
+ {
+ Scanline pScan = pAcc->GetScanline( nY );
+
+ for( tools::Long nX = 0; nX < nW; nX++ )
+ {
+ *pScan = cMapR[ *pScan ]; pScan++;
+ *pScan = cMapG[ *pScan ]; pScan++;
+ *pScan = cMapB[ *pScan ]; pScan++;
+ }
+ }
+ }
+ else
+ {
+ for( tools::Long nY = 0; nY < nH; nY++ )
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for( tools::Long nX = 0; nX < nW; nX++ )
+ {
+ aCol = pAcc->GetPixelFromData( pScanline, nX );
+ aCol.SetRed( cMapR[ aCol.GetRed() ] );
+ aCol.SetGreen( cMapG[ aCol.GetGreen() ] );
+ aCol.SetBlue( cMapB[ aCol.GetBlue() ] );
+ pAcc->SetPixelOnData( pScanline, nX, aCol );
+ }
+ }
+ }
+
+ pAcc.reset();
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/bitmapfilter.cxx b/vcl/source/bitmap/bitmapfilter.cxx
new file mode 100644
index 000000000..f2020539e
--- /dev/null
+++ b/vcl/source/bitmap/bitmapfilter.cxx
@@ -0,0 +1,58 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/BitmapFilter.hxx>
+#include <vcl/animate/Animation.hxx>
+
+#include <sal/log.hxx>
+
+BitmapFilter::BitmapFilter() {}
+
+BitmapFilter::~BitmapFilter() {}
+
+bool BitmapFilter::Filter(BitmapEx& rBmpEx, BitmapFilter const& rFilter)
+{
+ BitmapEx aTmpBmpEx(rFilter.execute(rBmpEx));
+
+ if (aTmpBmpEx.IsEmpty())
+ {
+ SAL_WARN("vcl.gdi", "Bitmap filter failed " << typeid(rFilter).name());
+ return false;
+ }
+
+ rBmpEx = aTmpBmpEx;
+ return true;
+}
+
+bool BitmapFilter::Filter(Animation& rAnimation, BitmapFilter const& rFilter)
+{
+ SAL_WARN_IF(rAnimation.IsInAnimation(), "vcl", "Animation modified while it is animated");
+
+ bool bRet = false;
+
+ if (!rAnimation.IsInAnimation() && !rAnimation.Count())
+ {
+ bRet = true;
+
+ std::vector<std::unique_ptr<AnimationBitmap>>& aList = rAnimation.GetAnimationFrames();
+ for (size_t i = 0, n = aList.size(); (i < n) && bRet; ++i)
+ {
+ bRet = BitmapFilter::Filter(aList[i]->maBitmapEx, rFilter);
+ }
+
+ BitmapEx aBmpEx(rAnimation.GetBitmapEx());
+ BitmapFilter::Filter(aBmpEx, rFilter);
+ rAnimation.SetBitmapEx(aBmpEx);
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/bitmappaint.cxx b/vcl/source/bitmap/bitmappaint.cxx
new file mode 100644
index 000000000..22a2bfdbd
--- /dev/null
+++ b/vcl/source/bitmap/bitmappaint.cxx
@@ -0,0 +1,1123 @@
+/* -*- 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 <tools/poly.hxx>
+#include <tools/helpers.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/alpha.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <salbmp.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+#include <algorithm>
+#include <memory>
+
+bool Bitmap::Erase(const Color& rFillColor)
+{
+ if (IsEmpty())
+ return true;
+
+ // implementation specific replace
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Erase(rFillColor))
+ {
+ ImplSetSalBitmap(xImpBmp);
+ maPrefMapMode = MapMode(MapUnit::MapPixel);
+ maPrefSize = xImpBmp->GetSize();
+ return true;
+ }
+
+ BitmapScopedWriteAccess pWriteAcc(*this);
+ bool bRet = false;
+
+ if (pWriteAcc)
+ {
+ pWriteAcc->Erase(rFillColor);
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool Bitmap::Invert()
+{
+ BitmapScopedWriteAccess pAcc(*this);
+ bool bRet = false;
+
+ if (pAcc)
+ {
+ if (pAcc->HasPalette())
+ {
+ BitmapPalette aBmpPal(pAcc->GetPalette());
+ const sal_uInt16 nCount = aBmpPal.GetEntryCount();
+
+ for (sal_uInt16 i = 0; i < nCount; i++)
+ {
+ aBmpPal[i].Invert();
+ }
+
+ pAcc->SetPalette(aBmpPal);
+ }
+ else
+ {
+ const tools::Long nWidth = pAcc->Width();
+ const tools::Long nHeight = pAcc->Height();
+
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ BitmapColor aBmpColor = pAcc->GetPixelFromData(pScanline, nX);
+ aBmpColor.Invert();
+ pAcc->SetPixelOnData(pScanline, nX, aBmpColor);
+ }
+ }
+ }
+
+ mxSalBmp->InvalidateChecksum();
+ pAcc.reset();
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+namespace
+{
+// Put each scanline's content horizontally mirrored into the other one.
+// (optimized version accessing pixel values directly).
+template <int bitCount>
+void mirrorScanlines(Scanline scanline1, Scanline scanline2, tools::Long nWidth)
+{
+ constexpr int byteCount = bitCount / 8;
+ Scanline pos1 = scanline1;
+ Scanline pos2 = scanline2 + (nWidth - 1) * byteCount; // last in second scanline
+ sal_uInt8 tmp[byteCount];
+ for (tools::Long i = 0; i < nWidth; ++i)
+ {
+ memcpy(tmp, pos1, byteCount);
+ memcpy(pos1, pos2, byteCount);
+ memcpy(pos2, tmp, byteCount);
+ pos1 += byteCount;
+ pos2 -= byteCount;
+ }
+}
+}
+
+bool Bitmap::Mirror(BmpMirrorFlags nMirrorFlags)
+{
+ bool bHorz(nMirrorFlags & BmpMirrorFlags::Horizontal);
+ bool bVert(nMirrorFlags & BmpMirrorFlags::Vertical);
+ bool bRet = false;
+
+ if (bHorz && !bVert)
+ {
+ BitmapScopedWriteAccess pAcc(*this);
+
+ if (pAcc)
+ {
+ const tools::Long nWidth = pAcc->Width();
+ const tools::Long nHeight = pAcc->Height();
+ const tools::Long nWidth1 = nWidth - 1;
+ const tools::Long nWidth_2 = nWidth / 2;
+ const tools::Long nSecondHalf = nWidth - nWidth_2;
+
+ switch (pAcc->GetBitCount())
+ {
+ // Special-case these, swap the halves of scanlines while mirroring them.
+ case 32:
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ mirrorScanlines<32>(pAcc->GetScanline(nY),
+ pAcc->GetScanline(nY) + 4 * nSecondHalf, nWidth_2);
+ break;
+ case 24:
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ mirrorScanlines<24>(pAcc->GetScanline(nY),
+ pAcc->GetScanline(nY) + 3 * nSecondHalf, nWidth_2);
+ break;
+ case 8:
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ mirrorScanlines<8>(pAcc->GetScanline(nY),
+ pAcc->GetScanline(nY) + nSecondHalf, nWidth_2);
+ break;
+ default:
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for (tools::Long nX = 0, nOther = nWidth1; nX < nWidth_2; nX++, nOther--)
+ {
+ const BitmapColor aTemp(pAcc->GetPixelFromData(pScanline, nX));
+
+ pAcc->SetPixelOnData(pScanline, nX,
+ pAcc->GetPixelFromData(pScanline, nOther));
+ pAcc->SetPixelOnData(pScanline, nOther, aTemp);
+ }
+ }
+ }
+
+ pAcc.reset();
+ bRet = true;
+ }
+ }
+ else if (bVert && !bHorz)
+ {
+ BitmapScopedWriteAccess pAcc(*this);
+
+ if (pAcc)
+ {
+ const tools::Long nScanSize = pAcc->GetScanlineSize();
+ std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nScanSize]);
+ const tools::Long nHeight = pAcc->Height();
+ const tools::Long nHeight1 = nHeight - 1;
+ const tools::Long nHeight_2 = nHeight >> 1;
+
+ for (tools::Long nY = 0, nOther = nHeight1; nY < nHeight_2; nY++, nOther--)
+ {
+ memcpy(pBuffer.get(), pAcc->GetScanline(nY), nScanSize);
+ memcpy(pAcc->GetScanline(nY), pAcc->GetScanline(nOther), nScanSize);
+ memcpy(pAcc->GetScanline(nOther), pBuffer.get(), nScanSize);
+ }
+
+ pAcc.reset();
+ bRet = true;
+ }
+ }
+ else if (bHorz && bVert)
+ {
+ BitmapScopedWriteAccess pAcc(*this);
+
+ if (pAcc)
+ {
+ const tools::Long nWidth = pAcc->Width();
+ const tools::Long nWidth1 = nWidth - 1;
+ const tools::Long nHeight = pAcc->Height();
+ tools::Long nHeight_2 = nHeight / 2;
+ const tools::Long nWidth_2 = nWidth / 2;
+ const tools::Long nSecondHalf = nWidth - nWidth_2;
+
+ switch (pAcc->GetBitCount())
+ {
+ case 32:
+ for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--)
+ mirrorScanlines<32>(pAcc->GetScanline(nY), pAcc->GetScanline(nOtherY),
+ nWidth);
+ if (nHeight & 1)
+ mirrorScanlines<32>(pAcc->GetScanline(nHeight_2),
+ pAcc->GetScanline(nHeight_2) + 4 * nSecondHalf,
+ nWidth_2);
+ break;
+ case 24:
+ for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--)
+ mirrorScanlines<24>(pAcc->GetScanline(nY), pAcc->GetScanline(nOtherY),
+ nWidth);
+ if (nHeight & 1)
+ mirrorScanlines<24>(pAcc->GetScanline(nHeight_2),
+ pAcc->GetScanline(nHeight_2) + 3 * nSecondHalf,
+ nWidth_2);
+ break;
+ case 8:
+ for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--)
+ mirrorScanlines<8>(pAcc->GetScanline(nY), pAcc->GetScanline(nOtherY),
+ nWidth);
+ if (nHeight & 1)
+ mirrorScanlines<8>(pAcc->GetScanline(nHeight_2),
+ pAcc->GetScanline(nHeight_2) + nSecondHalf, nWidth_2);
+ break;
+ default:
+ for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ Scanline pScanlineOther = pAcc->GetScanline(nOtherY);
+ for (tools::Long nX = 0, nOtherX = nWidth1; nX < nWidth; nX++, nOtherX--)
+ {
+ const BitmapColor aTemp(pAcc->GetPixelFromData(pScanline, nX));
+
+ pAcc->SetPixelOnData(pScanline, nX,
+ pAcc->GetPixelFromData(pScanlineOther, nOtherX));
+ pAcc->SetPixelOnData(pScanlineOther, nOtherX, aTemp);
+ }
+ }
+
+ // if necessary, also mirror the middle line horizontally
+ if (nHeight & 1)
+ {
+ Scanline pScanline = pAcc->GetScanline(nHeight_2);
+ for (tools::Long nX = 0, nOtherX = nWidth1; nX < nWidth_2; nX++, nOtherX--)
+ {
+ const BitmapColor aTemp(pAcc->GetPixelFromData(pScanline, nX));
+ pAcc->SetPixelOnData(pScanline, nX,
+ pAcc->GetPixelFromData(pScanline, nOtherX));
+ pAcc->SetPixelOnData(pScanline, nOtherX, aTemp);
+ }
+ }
+ }
+
+ pAcc.reset();
+ bRet = true;
+ }
+ }
+ else
+ bRet = true;
+
+ return bRet;
+}
+
+bool Bitmap::Rotate(Degree10 nAngle10, const Color& rFillColor)
+{
+ bool bRet = false;
+
+ nAngle10 %= 3600_deg10;
+ nAngle10 = (nAngle10 < 0_deg10) ? (Degree10(3599) + nAngle10) : nAngle10;
+
+ if (!nAngle10)
+ bRet = true;
+ else if (nAngle10 == 1800_deg10)
+ bRet = Mirror(BmpMirrorFlags::Horizontal | BmpMirrorFlags::Vertical);
+ else
+ {
+ ScopedReadAccess pReadAcc(*this);
+ Bitmap aRotatedBmp;
+
+ if (pReadAcc)
+ {
+ const Size aSizePix(GetSizePixel());
+
+ if (nAngle10 == 900_deg10 || nAngle10 == 2700_deg10)
+ {
+ const Size aNewSizePix(aSizePix.Height(), aSizePix.Width());
+ Bitmap aNewBmp(aNewSizePix, getPixelFormat(), &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const tools::Long nWidth = aSizePix.Width();
+ const tools::Long nWidth1 = nWidth - 1;
+ const tools::Long nHeight = aSizePix.Height();
+ const tools::Long nHeight1 = nHeight - 1;
+ const tools::Long nNewWidth = aNewSizePix.Width();
+ const tools::Long nNewHeight = aNewSizePix.Height();
+
+ if (nAngle10 == 900_deg10)
+ {
+ for (tools::Long nY = 0, nOtherX = nWidth1; nY < nNewHeight;
+ nY++, nOtherX--)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (tools::Long nX = 0, nOtherY = 0; nX < nNewWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX,
+ pReadAcc->GetPixel(nOtherY++, nOtherX));
+ }
+ }
+ }
+ else if (nAngle10 == 2700_deg10)
+ {
+ for (tools::Long nY = 0, nOtherX = 0; nY < nNewHeight; nY++, nOtherX++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ for (tools::Long nX = 0, nOtherY = nHeight1; nX < nNewWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX,
+ pReadAcc->GetPixel(nOtherY--, nOtherX));
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ }
+
+ aRotatedBmp = aNewBmp;
+ }
+ else
+ {
+ Point aTmpPoint;
+ tools::Rectangle aTmpRectangle(aTmpPoint, aSizePix);
+ tools::Polygon aPoly(aTmpRectangle);
+ aPoly.Rotate(aTmpPoint, nAngle10);
+
+ tools::Rectangle aNewBound(aPoly.GetBoundRect());
+ const Size aNewSizePix(aNewBound.GetSize());
+ Bitmap aNewBmp(aNewSizePix, getPixelFormat(), &pReadAcc->GetPalette());
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const BitmapColor aFillColor(pWriteAcc->GetBestMatchingColor(rFillColor));
+ const double fCosAngle = cos(toRadians(nAngle10));
+ const double fSinAngle = sin(toRadians(nAngle10));
+ const double fXMin = aNewBound.Left();
+ const double fYMin = aNewBound.Top();
+ const sal_Int32 nWidth = aSizePix.Width();
+ const sal_Int32 nHeight = aSizePix.Height();
+ const sal_Int32 nNewWidth = aNewSizePix.Width();
+ const sal_Int32 nNewHeight = aNewSizePix.Height();
+ // we store alternating values of cos/sin. We do this instead of
+ // separate arrays to improve cache hit.
+ std::unique_ptr<sal_Int32[]> pCosSinX(new sal_Int32[nNewWidth * 2]);
+ std::unique_ptr<sal_Int32[]> pCosSinY(new sal_Int32[nNewHeight * 2]);
+
+ for (sal_Int32 nIdx = 0, nX = 0; nX < nNewWidth; nX++)
+ {
+ const double fTmp = (fXMin + nX) * 64;
+
+ pCosSinX[nIdx++] = std::round(fCosAngle * fTmp);
+ pCosSinX[nIdx++] = std::round(fSinAngle * fTmp);
+ }
+
+ for (sal_Int32 nIdx = 0, nY = 0; nY < nNewHeight; nY++)
+ {
+ const double fTmp = (fYMin + nY) * 64;
+
+ pCosSinY[nIdx++] = std::round(fCosAngle * fTmp);
+ pCosSinY[nIdx++] = std::round(fSinAngle * fTmp);
+ }
+
+ for (sal_Int32 nCosSinYIdx = 0, nY = 0; nY < nNewHeight; nY++)
+ {
+ sal_Int32 nCosY = pCosSinY[nCosSinYIdx++];
+ sal_Int32 nSinY = pCosSinY[nCosSinYIdx++];
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+
+ for (sal_Int32 nCosSinXIdx = 0, nX = 0; nX < nNewWidth; nX++)
+ {
+ sal_Int32 nRotX = (pCosSinX[nCosSinXIdx++] - nSinY) >> 6;
+ sal_Int32 nRotY = (pCosSinX[nCosSinXIdx++] + nCosY) >> 6;
+
+ if ((nRotX > -1) && (nRotX < nWidth) && (nRotY > -1)
+ && (nRotY < nHeight))
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX,
+ pReadAcc->GetPixel(nRotY, nRotX));
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aFillColor);
+ }
+ }
+ }
+
+ pWriteAcc.reset();
+ }
+
+ aRotatedBmp = aNewBmp;
+ }
+
+ pReadAcc.reset();
+ }
+
+ bRet = !aRotatedBmp.IsEmpty();
+ if (bRet)
+ ReassignWithSize(aRotatedBmp);
+ }
+
+ return bRet;
+};
+
+Bitmap Bitmap::CreateMask(const Color& rTransColor, sal_uInt8 nTol) const
+{
+ ScopedReadAccess pReadAcc(const_cast<Bitmap&>(*this));
+
+ // Historically LO used 1bpp masks, but 8bpp masks are much faster,
+ // better supported by hardware, and the memory savings are not worth
+ // it anymore.
+ // TODO: Possibly remove the 1bpp code later.
+ constexpr bool use8BitMask = true;
+
+ if (!nTol && pReadAcc
+ && (pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitLsbPal
+ || pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal)
+ && pReadAcc->GetBestMatchingColor(COL_WHITE) == pReadAcc->GetBestMatchingColor(rTransColor))
+ {
+ // if we're a 1 bit pixel already, and the transcolor matches the color that would replace it
+ // already, then just return a copy
+ return *this;
+ }
+
+ auto ePixelFormat = use8BitMask ? vcl::PixelFormat::N8_BPP : vcl::PixelFormat::N1_BPP;
+ Bitmap aNewBmp(GetSizePixel(), ePixelFormat,
+ use8BitMask ? &Bitmap::GetGreyPalette(256) : nullptr);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+ bool bRet = false;
+
+ if (pWriteAcc && pReadAcc)
+ {
+ const tools::Long nWidth = pReadAcc->Width();
+ const tools::Long nHeight = pReadAcc->Height();
+ const BitmapColor aBlack(pWriteAcc->GetBestMatchingColor(COL_BLACK));
+ const BitmapColor aWhite(pWriteAcc->GetBestMatchingColor(COL_WHITE));
+
+ if (!nTol)
+ {
+ const BitmapColor aTest(pReadAcc->GetBestMatchingColor(rTransColor));
+
+ if (pWriteAcc->GetBitCount() == 1
+ && pReadAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal)
+ {
+ // optimized for 8Bit source palette
+ const sal_uInt8 cTest = aTest.GetIndex();
+
+ if (pWriteAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal
+ && aWhite.GetIndex() == 1)
+ {
+ // optimized for 1Bit-MSB destination palette
+ for (tools::Long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pSrc = pReadAcc->GetScanline(nY);
+ Scanline pDst = pWriteAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; ++nX)
+ {
+ if (cTest == pSrc[nX])
+ pDst[nX >> 3] |= 1 << (7 - (nX & 7));
+ else
+ pDst[nX >> 3] &= ~(1 << (7 - (nX & 7)));
+ }
+ }
+ }
+ else
+ {
+ for (tools::Long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pSrc = pReadAcc->GetScanline(nY);
+ Scanline pDst = pWriteAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; ++nX)
+ {
+ if (cTest == pSrc[nX])
+ pWriteAcc->SetPixelOnData(pDst, nX, aWhite);
+ else
+ pWriteAcc->SetPixelOnData(pDst, nX, aBlack);
+ }
+ }
+ }
+ }
+ else if (pWriteAcc->GetScanlineFormat() == pReadAcc->GetScanlineFormat()
+ && aWhite.GetIndex() == 1
+ && (pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitLsbPal
+ || pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal))
+ {
+ for (tools::Long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pSrc = pReadAcc->GetScanline(nY);
+ Scanline pDst = pWriteAcc->GetScanline(nY);
+ assert(pWriteAcc->GetScanlineSize() == pReadAcc->GetScanlineSize());
+ const tools::Long nScanlineSize = pWriteAcc->GetScanlineSize();
+ for (tools::Long nX = 0; nX < nScanlineSize; ++nX)
+ pDst[nX] = ~pSrc[nX];
+ }
+ }
+ else if (use8BitMask && pWriteAcc->GetBitCount() == 8
+ && pReadAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal)
+ {
+ // optimized for 8Bit source palette
+ const sal_uInt8 cTest = aTest.GetIndex();
+
+ for (tools::Long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pSrc = pReadAcc->GetScanline(nY);
+ Scanline pDst = pWriteAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; ++nX)
+ {
+ if (cTest == pSrc[nX])
+ pDst[nX] = aWhite.GetIndex();
+ else
+ pDst[nX] = aBlack.GetIndex();
+ }
+ }
+ }
+ else
+ {
+ // not optimized
+ for (tools::Long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; ++nX)
+ {
+ if (aTest == pReadAcc->GetPixelFromData(pScanlineRead, nX))
+ pWriteAcc->SetPixelOnData(pScanline, nX, aWhite);
+ else
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+ }
+ else
+ {
+ BitmapColor aCol;
+ tools::Long nR, nG, nB;
+ const tools::Long nMinR = MinMax<tools::Long>(rTransColor.GetRed() - nTol, 0, 255);
+ const tools::Long nMaxR = MinMax<tools::Long>(rTransColor.GetRed() + nTol, 0, 255);
+ const tools::Long nMinG = MinMax<tools::Long>(rTransColor.GetGreen() - nTol, 0, 255);
+ const tools::Long nMaxG = MinMax<tools::Long>(rTransColor.GetGreen() + nTol, 0, 255);
+ const tools::Long nMinB = MinMax<tools::Long>(rTransColor.GetBlue() - nTol, 0, 255);
+ const tools::Long nMaxB = MinMax<tools::Long>(rTransColor.GetBlue() + nTol, 0, 255);
+
+ if (pReadAcc->HasPalette())
+ {
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ aCol = pReadAcc->GetPaletteColor(
+ pReadAcc->GetIndexFromData(pScanlineRead, nX));
+ nR = aCol.GetRed();
+ nG = aCol.GetGreen();
+ nB = aCol.GetBlue();
+
+ if (nMinR <= nR && nMaxR >= nR && nMinG <= nG && nMaxG >= nG && nMinB <= nB
+ && nMaxB >= nB)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aWhite);
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+ }
+ else
+ {
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ aCol = pReadAcc->GetPixelFromData(pScanlineRead, nX);
+ nR = aCol.GetRed();
+ nG = aCol.GetGreen();
+ nB = aCol.GetBlue();
+
+ if (nMinR <= nR && nMaxR >= nR && nMinG <= nG && nMaxG >= nG && nMinB <= nB
+ && nMaxB >= nB)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aWhite);
+ }
+ else
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+ }
+ }
+
+ bRet = true;
+ }
+
+ pWriteAcc.reset();
+ pReadAcc.reset();
+
+ if (bRet)
+ {
+ aNewBmp.maPrefSize = maPrefSize;
+ aNewBmp.maPrefMapMode = maPrefMapMode;
+ }
+ else
+ aNewBmp = Bitmap();
+
+ return aNewBmp;
+}
+
+vcl::Region Bitmap::CreateRegion(const Color& rColor, const tools::Rectangle& rRect) const
+{
+ vcl::Region aRegion;
+ tools::Rectangle aRect(rRect);
+ ScopedReadAccess pReadAcc(const_cast<Bitmap&>(*this));
+
+ aRect.Intersection(tools::Rectangle(Point(), GetSizePixel()));
+ aRect.Justify();
+
+ if (pReadAcc)
+ {
+ const tools::Long nLeft = aRect.Left();
+ const tools::Long nTop = aRect.Top();
+ const tools::Long nRight = aRect.Right();
+ const tools::Long nBottom = aRect.Bottom();
+ const BitmapColor aMatch(pReadAcc->GetBestMatchingColor(rColor));
+
+ std::vector<tools::Long> aLine;
+ tools::Long nYStart(nTop);
+ tools::Long nY(nTop);
+
+ for (; nY <= nBottom; nY++)
+ {
+ std::vector<tools::Long> aNewLine;
+ tools::Long nX(nLeft);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+
+ for (; nX <= nRight;)
+ {
+ while ((nX <= nRight) && (aMatch != pReadAcc->GetPixelFromData(pScanlineRead, nX)))
+ nX++;
+
+ if (nX <= nRight)
+ {
+ aNewLine.push_back(nX);
+
+ while ((nX <= nRight)
+ && (aMatch == pReadAcc->GetPixelFromData(pScanlineRead, nX)))
+ {
+ nX++;
+ }
+
+ aNewLine.push_back(nX - 1);
+ }
+ }
+
+ if (aNewLine != aLine)
+ {
+ // need to write aLine, it's different from the next line
+ if (!aLine.empty())
+ {
+ tools::Rectangle aSubRect;
+
+ // enter y values and proceed ystart
+ aSubRect.SetTop(nYStart);
+ aSubRect.SetBottom(nY ? nY - 1 : 0);
+
+ for (size_t a(0); a < aLine.size();)
+ {
+ aSubRect.SetLeft(aLine[a++]);
+ aSubRect.SetRight(aLine[a++]);
+ aRegion.Union(aSubRect);
+ }
+ }
+
+ // copy line as new line
+ aLine = aNewLine;
+ nYStart = nY;
+ }
+ }
+
+ // write last line if used
+ if (!aLine.empty())
+ {
+ tools::Rectangle aSubRect;
+
+ // enter y values
+ aSubRect.SetTop(nYStart);
+ aSubRect.SetBottom(nY ? nY - 1 : 0);
+
+ for (size_t a(0); a < aLine.size();)
+ {
+ aSubRect.SetLeft(aLine[a++]);
+ aSubRect.SetRight(aLine[a++]);
+ aRegion.Union(aSubRect);
+ }
+ }
+
+ pReadAcc.reset();
+ }
+ else
+ {
+ aRegion = aRect;
+ }
+
+ return aRegion;
+}
+
+bool Bitmap::Replace(const Bitmap& rMask, const Color& rReplaceColor)
+{
+ ScopedReadAccess pMaskAcc(const_cast<Bitmap&>(rMask));
+ BitmapScopedWriteAccess pAcc(*this);
+ bool bRet = false;
+
+ if (pMaskAcc && pAcc)
+ {
+ const tools::Long nWidth = std::min(pMaskAcc->Width(), pAcc->Width());
+ const tools::Long nHeight = std::min(pMaskAcc->Height(), pAcc->Height());
+ const BitmapColor aMaskWhite(pMaskAcc->GetBestMatchingColor(COL_WHITE));
+ BitmapColor aReplace;
+
+ if (pAcc->HasPalette())
+ {
+ const sal_uInt16 nActColors = pAcc->GetPaletteEntryCount();
+ const sal_uInt16 nMaxColors = 1 << pAcc->GetBitCount();
+
+ // default to the nearest color
+ aReplace = pAcc->GetBestMatchingColor(rReplaceColor);
+
+ // for paletted images without a matching palette entry
+ // look for an unused palette entry (NOTE: expensive!)
+ if (pAcc->GetPaletteColor(aReplace.GetIndex()) != BitmapColor(rReplaceColor))
+ {
+ // if the palette has empty entries use the last one
+ if (nActColors < nMaxColors)
+ {
+ pAcc->SetPaletteEntryCount(nActColors + 1);
+ pAcc->SetPaletteColor(nActColors, rReplaceColor);
+ aReplace = BitmapColor(static_cast<sal_uInt8>(nActColors));
+ }
+ else
+ {
+ std::unique_ptr<bool[]> pFlags(new bool[nMaxColors]);
+
+ // Set all entries to false
+ std::fill(pFlags.get(), pFlags.get() + nMaxColors, false);
+
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ pFlags[pAcc->GetIndexFromData(pScanline, nX)] = true;
+ }
+
+ for (sal_uInt16 i = 0; i < nMaxColors; i++)
+ {
+ // Hurray, we do have an unused entry
+ if (!pFlags[i])
+ {
+ pAcc->SetPaletteColor(i, rReplaceColor);
+ aReplace = BitmapColor(static_cast<sal_uInt8>(i));
+ }
+ }
+ }
+ }
+ }
+ else
+ aReplace = rReplaceColor;
+
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ Scanline pScanlineMask = pMaskAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ if (pMaskAcc->GetPixelFromData(pScanlineMask, nX) == aMaskWhite)
+ pAcc->SetPixelOnData(pScanline, nX, aReplace);
+ }
+ }
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool Bitmap::Replace(const AlphaMask& rAlpha, const Color& rMergeColor)
+{
+ Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N24_BPP);
+ ScopedReadAccess pAcc(*this);
+ AlphaMask::ScopedReadAccess pAlphaAcc(const_cast<AlphaMask&>(rAlpha));
+ BitmapScopedWriteAccess pNewAcc(aNewBmp);
+ bool bRet = false;
+
+ if (pAcc && pAlphaAcc && pNewAcc)
+ {
+ BitmapColor aCol;
+ const tools::Long nWidth = std::min(pAlphaAcc->Width(), pAcc->Width());
+ const tools::Long nHeight = std::min(pAlphaAcc->Height(), pAcc->Height());
+
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pNewAcc->GetScanline(nY);
+ Scanline pScanlineAlpha = pAlphaAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ aCol = pAcc->GetColor(nY, nX);
+ aCol.Merge(rMergeColor, 255 - pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX));
+ pNewAcc->SetPixelOnData(pScanline, nX, aCol);
+ }
+ }
+
+ bRet = true;
+ }
+
+ pAcc.reset();
+ pAlphaAcc.reset();
+ pNewAcc.reset();
+
+ if (bRet)
+ {
+ const MapMode aMap(maPrefMapMode);
+ const Size aSize(maPrefSize);
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+ }
+
+ return bRet;
+}
+
+bool Bitmap::Replace(const Color& rSearchColor, const Color& rReplaceColor, sal_uInt8 nTol)
+{
+ if (mxSalBmp)
+ {
+ // implementation specific replace
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Replace(rSearchColor, rReplaceColor, nTol))
+ {
+ ImplSetSalBitmap(xImpBmp);
+ maPrefMapMode = MapMode(MapUnit::MapPixel);
+ maPrefSize = xImpBmp->GetSize();
+ return true;
+ }
+ }
+
+ // Bitmaps with 1 bit color depth can cause problems if they have other entries than black/white
+ // in their palette
+ if (getPixelFormat() == vcl::PixelFormat::N1_BPP)
+ Convert(BmpConversion::N8BitColors);
+
+ BitmapScopedWriteAccess pAcc(*this);
+ bool bRet = false;
+
+ if (pAcc)
+ {
+ const tools::Long nMinR = MinMax<tools::Long>(rSearchColor.GetRed() - nTol, 0, 255);
+ const tools::Long nMaxR = MinMax<tools::Long>(rSearchColor.GetRed() + nTol, 0, 255);
+ const tools::Long nMinG = MinMax<tools::Long>(rSearchColor.GetGreen() - nTol, 0, 255);
+ const tools::Long nMaxG = MinMax<tools::Long>(rSearchColor.GetGreen() + nTol, 0, 255);
+ const tools::Long nMinB = MinMax<tools::Long>(rSearchColor.GetBlue() - nTol, 0, 255);
+ const tools::Long nMaxB = MinMax<tools::Long>(rSearchColor.GetBlue() + nTol, 0, 255);
+
+ if (pAcc->HasPalette())
+ {
+ for (sal_uInt16 i = 0, nPalCount = pAcc->GetPaletteEntryCount(); i < nPalCount; i++)
+ {
+ const BitmapColor& rCol = pAcc->GetPaletteColor(i);
+
+ if (nMinR <= rCol.GetRed() && nMaxR >= rCol.GetRed() && nMinG <= rCol.GetGreen()
+ && nMaxG >= rCol.GetGreen() && nMinB <= rCol.GetBlue()
+ && nMaxB >= rCol.GetBlue())
+ {
+ pAcc->SetPaletteColor(i, rReplaceColor);
+ }
+ }
+ }
+ else
+ {
+ BitmapColor aCol;
+ const BitmapColor aReplace(pAcc->GetBestMatchingColor(rReplaceColor));
+
+ for (tools::Long nY = 0, nHeight = pAcc->Height(); nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for (tools::Long nX = 0, nWidth = pAcc->Width(); nX < nWidth; nX++)
+ {
+ aCol = pAcc->GetPixelFromData(pScanline, nX);
+
+ if (nMinR <= aCol.GetRed() && nMaxR >= aCol.GetRed() && nMinG <= aCol.GetGreen()
+ && nMaxG >= aCol.GetGreen() && nMinB <= aCol.GetBlue()
+ && nMaxB >= aCol.GetBlue())
+ {
+ pAcc->SetPixelOnData(pScanline, nX, aReplace);
+ }
+ }
+ }
+ }
+
+ pAcc.reset();
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool Bitmap::Replace(const Color* pSearchColors, const Color* pReplaceColors, size_t nColorCount,
+ sal_uInt8 const* pTols)
+{
+ // Bitmaps with 1 bit color depth can cause problems if they have other entries than black/white
+ // in their palette
+ if (getPixelFormat() == vcl::PixelFormat::N1_BPP)
+ Convert(BmpConversion::N8BitColors);
+
+ BitmapScopedWriteAccess pAcc(*this);
+ bool bRet = false;
+
+ if (pAcc)
+ {
+ std::vector<sal_uInt8> aMinR(nColorCount);
+ std::vector<sal_uInt8> aMaxR(nColorCount);
+ std::vector<sal_uInt8> aMinG(nColorCount);
+ std::vector<sal_uInt8> aMaxG(nColorCount);
+ std::vector<sal_uInt8> aMinB(nColorCount);
+ std::vector<sal_uInt8> aMaxB(nColorCount);
+
+ if (pTols)
+ {
+ for (size_t i = 0; i < nColorCount; ++i)
+ {
+ const Color& rCol = pSearchColors[i];
+ const sal_uInt8 nTol = pTols[i];
+
+ aMinR[i] = std::clamp(rCol.GetRed() - nTol, 0, 255);
+ aMaxR[i] = std::clamp(rCol.GetRed() + nTol, 0, 255);
+ aMinG[i] = std::clamp(rCol.GetGreen() - nTol, 0, 255);
+ aMaxG[i] = std::clamp(rCol.GetGreen() + nTol, 0, 255);
+ aMinB[i] = std::clamp(rCol.GetBlue() - nTol, 0, 255);
+ aMaxB[i] = std::clamp(rCol.GetBlue() + nTol, 0, 255);
+ }
+ }
+ else
+ {
+ for (size_t i = 0; i < nColorCount; ++i)
+ {
+ const Color& rCol = pSearchColors[i];
+
+ aMinR[i] = rCol.GetRed();
+ aMaxR[i] = rCol.GetRed();
+ aMinG[i] = rCol.GetGreen();
+ aMaxG[i] = rCol.GetGreen();
+ aMinB[i] = rCol.GetBlue();
+ aMaxB[i] = rCol.GetBlue();
+ }
+ }
+
+ if (pAcc->HasPalette())
+ {
+ for (sal_uInt16 nEntry = 0, nPalCount = pAcc->GetPaletteEntryCount();
+ nEntry < nPalCount; nEntry++)
+ {
+ const BitmapColor& rCol = pAcc->GetPaletteColor(nEntry);
+
+ for (size_t i = 0; i < nColorCount; ++i)
+ {
+ if (aMinR[i] <= rCol.GetRed() && aMaxR[i] >= rCol.GetRed()
+ && aMinG[i] <= rCol.GetGreen() && aMaxG[i] >= rCol.GetGreen()
+ && aMinB[i] <= rCol.GetBlue() && aMaxB[i] >= rCol.GetBlue())
+ {
+ pAcc->SetPaletteColor(nEntry, pReplaceColors[i]);
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ std::vector<BitmapColor> aReplaces(nColorCount);
+
+ for (size_t i = 0; i < nColorCount; ++i)
+ aReplaces[i] = pAcc->GetBestMatchingColor(pReplaceColors[i]);
+
+ for (tools::Long nY = 0, nHeight = pAcc->Height(); nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for (tools::Long nX = 0, nWidth = pAcc->Width(); nX < nWidth; nX++)
+ {
+ BitmapColor aCol = pAcc->GetPixelFromData(pScanline, nX);
+
+ for (size_t i = 0; i < nColorCount; ++i)
+ {
+ if (aMinR[i] <= aCol.GetRed() && aMaxR[i] >= aCol.GetRed()
+ && aMinG[i] <= aCol.GetGreen() && aMaxG[i] >= aCol.GetGreen()
+ && aMinB[i] <= aCol.GetBlue() && aMaxB[i] >= aCol.GetBlue())
+ {
+ pAcc->SetPixelOnData(pScanline, nX, aReplaces[i]);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ pAcc.reset();
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool Bitmap::CombineOr(const Bitmap& rMask)
+{
+ ScopedReadAccess pMaskAcc(const_cast<Bitmap&>(rMask));
+ BitmapScopedWriteAccess pAcc(*this);
+ bool bRet = false;
+
+ if (pMaskAcc && pAcc)
+ {
+ const tools::Long nWidth = std::min(pMaskAcc->Width(), pAcc->Width());
+ const tools::Long nHeight = std::min(pMaskAcc->Height(), pAcc->Height());
+ const Color aColBlack(COL_BLACK);
+ const BitmapColor aWhite(pAcc->GetBestMatchingColor(COL_WHITE));
+ const BitmapColor aBlack(pAcc->GetBestMatchingColor(aColBlack));
+ const BitmapColor aMaskBlack(pMaskAcc->GetBestMatchingColor(aColBlack));
+
+ for (tools::Long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ Scanline pScanlineMask = pMaskAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; nX++)
+ {
+ if (pMaskAcc->GetPixelFromData(pScanlineMask, nX) != aMaskBlack
+ || pAcc->GetPixelFromData(pScanline, nX) != aBlack)
+ {
+ pAcc->SetPixelOnData(pScanline, nX, aWhite);
+ }
+ else
+ {
+ pAcc->SetPixelOnData(pScanline, nX, aBlack);
+ }
+ }
+ }
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+// TODO: Have a look at OutputDevice::ImplDrawAlpha() for some
+// optimizations. Might even consolidate the code here and there.
+bool Bitmap::Blend(const AlphaMask& rAlpha, const Color& rBackgroundColor)
+{
+ // Convert to a truecolor bitmap, if we're a paletted one. There's room for tradeoff decision here,
+ // maybe later for an overload (or a flag)
+ if (vcl::isPalettePixelFormat(getPixelFormat()))
+ Convert(BmpConversion::N24Bit);
+
+ AlphaMask::ScopedReadAccess pAlphaAcc(const_cast<AlphaMask&>(rAlpha));
+
+ BitmapScopedWriteAccess pAcc(*this);
+ bool bRet = false;
+
+ if (pAlphaAcc && pAcc)
+ {
+ const tools::Long nWidth = std::min(pAlphaAcc->Width(), pAcc->Width());
+ const tools::Long nHeight = std::min(pAlphaAcc->Height(), pAcc->Height());
+
+ for (tools::Long nY = 0; nY < nHeight; ++nY)
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ Scanline pScanlineAlpha = pAlphaAcc->GetScanline(nY);
+ for (tools::Long nX = 0; nX < nWidth; ++nX)
+ {
+ BitmapColor aBmpColor = pAcc->GetPixelFromData(pScanline, nX);
+ aBmpColor.Merge(rBackgroundColor,
+ 255 - pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX));
+ pAcc->SetPixelOnData(pScanline, nX, aBmpColor);
+ }
+ }
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/bitmappalette.cxx b/vcl/source/bitmap/bitmappalette.cxx
new file mode 100644
index 000000000..61a8a8252
--- /dev/null
+++ b/vcl/source/bitmap/bitmappalette.cxx
@@ -0,0 +1,212 @@
+/* -*- 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 <config_features.h>
+
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <tools/helpers.hxx>
+
+#include <vcl/BitmapPalette.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/outdev.hxx>
+
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+class ImplBitmapPalette
+{
+public:
+ ImplBitmapPalette(std::initializer_list<BitmapColor> aBitmapColor)
+ : maBitmapColor(aBitmapColor)
+ {
+ }
+ ImplBitmapPalette() {}
+ ImplBitmapPalette(sal_uInt16 nCount)
+ : maBitmapColor(nCount)
+ {
+ }
+ std::vector<BitmapColor>& GetBitmapData() { return maBitmapColor; }
+ const std::vector<BitmapColor>& GetBitmapData() const { return maBitmapColor; }
+ bool operator==(const ImplBitmapPalette& rBitmapPalette) const
+ {
+ return maBitmapColor == rBitmapPalette.maBitmapColor;
+ }
+
+private:
+ std::vector<BitmapColor> maBitmapColor;
+};
+
+namespace
+{
+BitmapPalette::ImplType& GetGlobalDefault()
+{
+ static BitmapPalette::ImplType gDefault;
+ return gDefault;
+}
+}
+
+BitmapPalette::BitmapPalette()
+ : mpImpl(GetGlobalDefault())
+{
+}
+
+BitmapPalette::BitmapPalette(const BitmapPalette& rOther)
+ : mpImpl(rOther.mpImpl)
+{
+}
+
+BitmapPalette::BitmapPalette(BitmapPalette&& rOther) noexcept
+ : mpImpl(std::move(rOther.mpImpl))
+{
+}
+
+BitmapPalette::BitmapPalette(std::initializer_list<BitmapColor> aBitmapColor)
+ : mpImpl(aBitmapColor)
+{
+}
+
+BitmapPalette::BitmapPalette(sal_uInt16 nCount)
+ : mpImpl(nCount)
+{
+}
+
+BitmapPalette::~BitmapPalette() {}
+
+BitmapPalette& BitmapPalette::operator=(const BitmapPalette& rOther)
+{
+ mpImpl = rOther.mpImpl;
+ return *this;
+}
+
+BitmapPalette& BitmapPalette::operator=(BitmapPalette&& rOther) noexcept
+{
+ mpImpl = std::move(rOther.mpImpl);
+ return *this;
+}
+
+const BitmapColor* BitmapPalette::ImplGetColorBuffer() const
+{
+ return mpImpl->GetBitmapData().data();
+}
+
+BitmapColor* BitmapPalette::ImplGetColorBuffer() { return mpImpl->GetBitmapData().data(); }
+
+BitmapChecksum BitmapPalette::GetChecksum() const
+{
+ auto const& rBitmapData = mpImpl->GetBitmapData();
+ return vcl_get_checksum(0, rBitmapData.data(), rBitmapData.size() * sizeof(BitmapColor));
+}
+
+bool BitmapPalette::operator==(const BitmapPalette& rOther) const
+{
+ return mpImpl == rOther.mpImpl;
+}
+
+bool BitmapPalette::operator!() const { return mpImpl->GetBitmapData().empty(); }
+
+sal_uInt16 BitmapPalette::GetEntryCount() const { return mpImpl->GetBitmapData().size(); }
+
+void BitmapPalette::SetEntryCount(sal_uInt16 nCount) { mpImpl->GetBitmapData().resize(nCount); }
+
+const BitmapColor& BitmapPalette::operator[](sal_uInt16 nIndex) const
+{
+ assert(nIndex < mpImpl->GetBitmapData().size() && "Palette index is out of range");
+ return mpImpl->GetBitmapData()[nIndex];
+}
+
+BitmapColor& BitmapPalette::operator[](sal_uInt16 nIndex)
+{
+ assert(nIndex < mpImpl->GetBitmapData().size() && "Palette index is out of range");
+ return mpImpl->GetBitmapData()[nIndex];
+}
+
+sal_uInt16 BitmapPalette::GetBestIndex(const BitmapColor& rCol) const
+{
+ auto const& rBitmapColor = mpImpl->GetBitmapData();
+ sal_uInt16 nRetIndex = 0;
+
+ if (!rBitmapColor.empty())
+ {
+ for (size_t j = 0; j < rBitmapColor.size(); ++j)
+ {
+ if (rCol == rBitmapColor[j])
+ {
+ return j;
+ }
+ }
+
+ sal_uInt16 nLastErr = SAL_MAX_UINT16;
+ for (size_t i = 0; i < rBitmapColor.size(); ++i)
+ {
+ const sal_uInt16 nActErr = rCol.GetColorError(rBitmapColor[i]);
+ if (nActErr < nLastErr)
+ {
+ nLastErr = nActErr;
+ nRetIndex = i;
+ }
+ }
+ }
+
+ return nRetIndex;
+}
+
+bool BitmapPalette::IsGreyPaletteAny() const
+{
+ auto const& rBitmapColor = mpImpl->GetBitmapData();
+ const int nEntryCount = GetEntryCount();
+ if (!nEntryCount) // NOTE: an empty palette means 1:1 mapping
+ return true;
+ // See above: only certain entry values will result in a valid call to GetGreyPalette
+ if (nEntryCount == 2 || nEntryCount == 4 || nEntryCount == 16 || nEntryCount == 256)
+ {
+ const BitmapPalette& rGreyPalette = Bitmap::GetGreyPalette(nEntryCount);
+ if (rGreyPalette == *this)
+ return true;
+ }
+
+ bool bRet = false;
+ // TODO: is it worth to compare the entries for the general case?
+ if (nEntryCount == 2)
+ {
+ const BitmapColor& rCol0(rBitmapColor[0]);
+ const BitmapColor& rCol1(rBitmapColor[1]);
+ bRet = rCol0.GetRed() == rCol0.GetGreen() && rCol0.GetRed() == rCol0.GetBlue()
+ && rCol1.GetRed() == rCol1.GetGreen() && rCol1.GetRed() == rCol1.GetBlue();
+ }
+ return bRet;
+}
+
+bool BitmapPalette::IsGreyPalette8Bit() const
+{
+ auto const& rBitmapColor = mpImpl->GetBitmapData();
+ const int nEntryCount = GetEntryCount();
+ if (!nEntryCount) // NOTE: an empty palette means 1:1 mapping
+ return true;
+ if (nEntryCount != 256)
+ return false;
+ for (sal_uInt16 i = 0; i < 256; ++i)
+ {
+ if (rBitmapColor[i] != BitmapColor(i, i, i))
+ return false;
+ }
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/bmpfast.cxx b/vcl/source/bitmap/bmpfast.cxx
new file mode 100644
index 000000000..5ee7b22d9
--- /dev/null
+++ b/vcl/source/bitmap/bmpfast.cxx
@@ -0,0 +1,828 @@
+/* -*- 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 <vcl/salgtype.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <bitmap/bmpfast.hxx>
+
+#include <sal/log.hxx>
+
+typedef unsigned char PIXBYTE;
+
+namespace {
+
+class BasePixelPtr
+{
+public:
+ explicit BasePixelPtr( PIXBYTE* p = nullptr ) : mpPixel( p ) {}
+ void SetRawPtr( PIXBYTE* pRawPtr ) { mpPixel = pRawPtr; }
+ void AddByteOffset( int nByteOffset ) { mpPixel += nByteOffset; }
+
+protected:
+ PIXBYTE* mpPixel;
+};
+
+template <ScanlineFormat PIXFMT>
+class TrueColorPixelPtr : public BasePixelPtr
+{
+public:
+ PIXBYTE GetRed() const;
+ PIXBYTE GetGreen() const;
+ PIXBYTE GetBlue() const;
+ PIXBYTE GetAlpha() const;
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const;
+ void SetAlpha( PIXBYTE a ) const;
+};
+
+// template specializations for truecolor pixel formats
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N24BitTcRgb> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 3; }
+
+ PIXBYTE GetRed() const { return mpPixel[0]; }
+ PIXBYTE GetGreen() const { return mpPixel[1]; }
+ PIXBYTE GetBlue() const { return mpPixel[2]; }
+ static PIXBYTE GetAlpha() { return 255; }
+ static void SetAlpha( PIXBYTE ) {}
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[0] = r;
+ mpPixel[1] = g;
+ mpPixel[2] = b;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N24BitTcBgr> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 3; }
+
+ PIXBYTE GetRed() const { return mpPixel[2]; }
+ PIXBYTE GetGreen() const { return mpPixel[1]; }
+ PIXBYTE GetBlue() const { return mpPixel[0]; }
+ static PIXBYTE GetAlpha() { return 255; }
+ static void SetAlpha( PIXBYTE ) {}
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[0] = b;
+ mpPixel[1] = g;
+ mpPixel[2] = r;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N32BitTcArgb> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 4; }
+
+ PIXBYTE GetRed() const { return mpPixel[1]; }
+ PIXBYTE GetGreen() const { return mpPixel[2]; }
+ PIXBYTE GetBlue() const { return mpPixel[3]; }
+ PIXBYTE GetAlpha() const { return mpPixel[0]; }
+ void SetAlpha( PIXBYTE a ) const { mpPixel[0] = a; }
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[1] = r;
+ mpPixel[2] = g;
+ mpPixel[3] = b;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N32BitTcAbgr> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 4; }
+
+ PIXBYTE GetRed() const { return mpPixel[3]; }
+ PIXBYTE GetGreen() const { return mpPixel[2]; }
+ PIXBYTE GetBlue() const { return mpPixel[1]; }
+ PIXBYTE GetAlpha() const { return mpPixel[0]; }
+ void SetAlpha( PIXBYTE a ) const { mpPixel[0] = a; }
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[1] = b;
+ mpPixel[2] = g;
+ mpPixel[3] = r;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N32BitTcRgba> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 4; }
+
+ PIXBYTE GetRed() const { return mpPixel[0]; }
+ PIXBYTE GetGreen() const { return mpPixel[1]; }
+ PIXBYTE GetBlue() const { return mpPixel[2]; }
+ PIXBYTE GetAlpha() const { return mpPixel[3]; }
+ void SetAlpha( PIXBYTE a ) const{ mpPixel[3] = a; }
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[0] = r;
+ mpPixel[1] = g;
+ mpPixel[2] = b;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N32BitTcBgra> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 4; }
+
+ PIXBYTE GetRed() const { return mpPixel[2]; }
+ PIXBYTE GetGreen() const { return mpPixel[1]; }
+ PIXBYTE GetBlue() const { return mpPixel[0]; }
+ PIXBYTE GetAlpha() const { return mpPixel[3]; }
+ void SetAlpha( PIXBYTE a ) const{ mpPixel[3] = a; }
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[0] = b;
+ mpPixel[1] = g;
+ mpPixel[2] = r;
+ }
+};
+
+// This assumes the content uses the grayscale palette (needs to be checked
+// by code allowing the use of the format).
+// Only reading color is implemented, since e.g. 24bpp input couldn't be
+// easily guaranteed to be grayscale.
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N8BitPal> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 1; }
+
+ PIXBYTE GetRed() const { return mpPixel[0]; }
+ PIXBYTE GetGreen() const { return mpPixel[0]; }
+ PIXBYTE GetBlue() const { return mpPixel[0]; }
+ static PIXBYTE GetAlpha() { return 255; }
+};
+
+}
+
+// converting truecolor formats
+template <ScanlineFormat SRCFMT, ScanlineFormat DSTFMT>
+static void ImplConvertPixel( const TrueColorPixelPtr<DSTFMT>& rDst,
+ const TrueColorPixelPtr<SRCFMT>& rSrc )
+{
+ rDst.SetColor( rSrc.GetRed(), rSrc.GetGreen(), rSrc.GetBlue() );
+ rDst.SetAlpha( rSrc.GetAlpha() );
+}
+
+template <ScanlineFormat SRCFMT, ScanlineFormat DSTFMT>
+static void ImplConvertLine( const TrueColorPixelPtr<DSTFMT>& rDst,
+ const TrueColorPixelPtr<SRCFMT>& rSrc, int nPixelCount )
+{
+ TrueColorPixelPtr<DSTFMT> aDst( rDst );
+ TrueColorPixelPtr<SRCFMT> aSrc( rSrc );
+ while( --nPixelCount >= 0 )
+ {
+ ImplConvertPixel( aDst, aSrc );
+ ++aSrc;
+ ++aDst;
+ }
+}
+
+// alpha blending truecolor pixels
+template <ScanlineFormat SRCFMT, ScanlineFormat DSTFMT>
+static void ImplBlendPixels( const TrueColorPixelPtr<DSTFMT>& rDst,
+ const TrueColorPixelPtr<SRCFMT>& rSrc, unsigned nAlphaVal )
+{
+ static const unsigned nAlphaShift = 8;
+ if( !nAlphaVal )
+ ImplConvertPixel( rDst, rSrc );
+ else if( nAlphaVal != ~(~0U << nAlphaShift) )
+ {
+ int nR = rDst.GetRed();
+ int nS = rSrc.GetRed();
+ nR = nS + (((nR - nS) * nAlphaVal) >> nAlphaShift);
+
+ int nG = rDst.GetGreen();
+ nS = rSrc.GetGreen();
+ nG = nS + (((nG - nS) * nAlphaVal) >> nAlphaShift);
+
+ int nB = rDst.GetBlue();
+ nS = rSrc.GetBlue();
+ nB = nS + (((nB - nS) * nAlphaVal) >> nAlphaShift);
+
+ rDst.SetColor( sal::static_int_cast<PIXBYTE>(nR),
+ sal::static_int_cast<PIXBYTE>(nG),
+ sal::static_int_cast<PIXBYTE>(nB) );
+ }
+}
+
+template <ScanlineFormat MASKFMT, ScanlineFormat SRCFMT, ScanlineFormat DSTFMT>
+static void ImplBlendLines( const TrueColorPixelPtr<DSTFMT>& rDst,
+ const TrueColorPixelPtr<SRCFMT>& rSrc, const TrueColorPixelPtr<MASKFMT>& rMsk,
+ int nPixelCount )
+{
+ TrueColorPixelPtr<MASKFMT> aMsk( rMsk );
+ TrueColorPixelPtr<DSTFMT> aDst( rDst );
+ TrueColorPixelPtr<SRCFMT> aSrc( rSrc );
+ while( --nPixelCount >= 0 )
+ {
+ // VCL masks store alpha as color, hence the GetRed() and not GetAlpha().
+ ImplBlendPixels(aDst, aSrc, aMsk.GetRed());
+ ++aDst;
+ ++aSrc;
+ ++aMsk;
+ }
+}
+
+static bool ImplCopyImage( BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer )
+{
+ const int nSrcLinestep = rSrcBuffer.mnScanlineSize;
+ int nDstLinestep = rDstBuffer.mnScanlineSize;
+
+ const PIXBYTE* pRawSrc = rSrcBuffer.mpBits;
+ PIXBYTE* pRawDst = rDstBuffer.mpBits;
+
+ // source and destination don't match upside down
+ if( ScanlineFormat::TopDown & (rSrcBuffer.mnFormat ^ rDstBuffer.mnFormat) )
+ {
+ pRawDst += (rSrcBuffer.mnHeight - 1) * nDstLinestep;
+ nDstLinestep = -rDstBuffer.mnScanlineSize;
+ }
+ else if( nSrcLinestep == nDstLinestep )
+ {
+ memcpy( pRawDst, pRawSrc, rSrcBuffer.mnHeight * nDstLinestep );
+ return true;
+ }
+
+ int nByteWidth = nSrcLinestep;
+ if( nByteWidth > rDstBuffer.mnScanlineSize )
+ nByteWidth = rDstBuffer.mnScanlineSize;
+
+ for( int y = rSrcBuffer.mnHeight; --y >= 0; )
+ {
+ memcpy( pRawDst, pRawSrc, nByteWidth );
+ pRawSrc += nSrcLinestep;
+ pRawDst += nDstLinestep;
+ }
+
+ return true;
+}
+
+template <ScanlineFormat DSTFMT,ScanlineFormat SRCFMT>
+static bool ImplConvertToBitmap( TrueColorPixelPtr<SRCFMT>& rSrcLine,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer )
+{
+ // help the compiler to avoid instantiations of unneeded conversions
+ SAL_WARN_IF( SRCFMT == DSTFMT, "vcl.gdi", "ImplConvertToBitmap into same format");
+ if( SRCFMT == DSTFMT )
+ return false;
+
+ const int nSrcLinestep = rSrcBuffer.mnScanlineSize;
+ int nDstLinestep = rDstBuffer.mnScanlineSize;
+
+ TrueColorPixelPtr<DSTFMT> aDstLine; aDstLine.SetRawPtr( rDstBuffer.mpBits );
+
+ // source and destination don't match upside down
+ if( ScanlineFormat::TopDown & (rSrcBuffer.mnFormat ^ rDstBuffer.mnFormat) )
+ {
+ aDstLine.AddByteOffset( (rSrcBuffer.mnHeight - 1) * nDstLinestep );
+ nDstLinestep = -nDstLinestep;
+ }
+
+ for( int y = rSrcBuffer.mnHeight; --y >= 0; )
+ {
+ ImplConvertLine( aDstLine, rSrcLine, rSrcBuffer.mnWidth );
+ rSrcLine.AddByteOffset( nSrcLinestep );
+ aDstLine.AddByteOffset( nDstLinestep );
+ }
+
+ return true;
+}
+
+template <ScanlineFormat SRCFMT>
+static bool ImplConvertFromBitmap( BitmapBuffer& rDst, const BitmapBuffer& rSrc )
+{
+ TrueColorPixelPtr<SRCFMT> aSrcType; aSrcType.SetRawPtr( rSrc.mpBits );
+
+ // select the matching instantiation for the destination's bitmap format
+ switch (RemoveScanline(rDst.mnFormat))
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ case ScanlineFormat::N8BitPal:
+ break;
+
+ case ScanlineFormat::N32BitTcMask:
+// return ImplConvertToBitmap<ScanlineFormat::N32BitTcMask>( aSrcType, rDst, rSrc );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplConvertToBitmap<ScanlineFormat::N24BitTcBgr>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplConvertToBitmap<ScanlineFormat::N24BitTcRgb>( aSrcType, rDst, rSrc );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcAbgr>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcArgb>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcBgra>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcRgba>( aSrcType, rDst, rSrc );
+ default: break;
+ }
+
+ static int nNotAccelerated = 0;
+ SAL_WARN_IF( rSrc.mnWidth * rSrc.mnHeight >= 4000 && ++nNotAccelerated == 100,
+ "vcl.gdi",
+ "ImplConvertFromBitmap for not accelerated case (" << std::hex << static_cast<int>(rSrc.mnFormat) << "->" << static_cast<int>(rDst.mnFormat) << ")" );
+
+ return false;
+}
+
+// A universal stretching conversion is overkill in most common situations
+// => performance benefits for speeding up the non-stretching cases
+bool ImplFastBitmapConversion( BitmapBuffer& rDst, const BitmapBuffer& rSrc,
+ const SalTwoRect& rTR )
+{
+ // TODO:horizontal mirroring not implemented yet
+ if( rTR.mnDestWidth < 0 )
+ return false;
+ // vertical mirroring
+ if( rTR.mnDestHeight < 0 )
+ // TODO: rDst.mnFormat ^= ScanlineFormat::TopDown;
+ return false;
+
+ // offsetted conversion is not implemented yet
+ if( rTR.mnSrcX || rTR.mnSrcY )
+ return false;
+ if( rTR.mnDestX || rTR.mnDestY )
+ return false;
+
+ // stretched conversion is not implemented yet
+ if( rTR.mnDestWidth != rTR.mnSrcWidth )
+ return false;
+ if( rTR.mnDestHeight!= rTR.mnSrcHeight )
+ return false;
+
+ // check source image size
+ if( rSrc.mnWidth < rTR.mnSrcX + rTR.mnSrcWidth )
+ return false;
+ if( rSrc.mnHeight < rTR.mnSrcY + rTR.mnSrcHeight )
+ return false;
+
+ // check dest image size
+ if( rDst.mnWidth < rTR.mnDestX + rTR.mnDestWidth )
+ return false;
+ if( rDst.mnHeight < rTR.mnDestY + rTR.mnDestHeight )
+ return false;
+
+ const ScanlineFormat nSrcFormat = RemoveScanline(rSrc.mnFormat);
+ const ScanlineFormat nDstFormat = RemoveScanline(rDst.mnFormat);
+
+ // special handling of trivial cases
+ if( nSrcFormat == nDstFormat )
+ {
+ // accelerated palette conversions not yet implemented
+ if( rSrc.maPalette != rDst.maPalette )
+ return false;
+ return ImplCopyImage( rDst, rSrc );
+ }
+
+ // select the matching instantiation for the source's bitmap format
+ switch( nSrcFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ break;
+
+ case ScanlineFormat::N32BitTcMask:
+// return ImplConvertFromBitmap<ScanlineFormat::N32BitTcMask>( rDst, rSrc );
+ break;
+
+ case ScanlineFormat::N8BitPal:
+ if(rSrc.maPalette.IsGreyPalette8Bit())
+ return ImplConvertFromBitmap<ScanlineFormat::N8BitPal>( rDst, rSrc );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplConvertFromBitmap<ScanlineFormat::N24BitTcBgr>( rDst, rSrc );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplConvertFromBitmap<ScanlineFormat::N24BitTcRgb>( rDst, rSrc );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplConvertFromBitmap<ScanlineFormat::N32BitTcAbgr>( rDst, rSrc );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplConvertFromBitmap<ScanlineFormat::N32BitTcArgb>( rDst, rSrc );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplConvertFromBitmap<ScanlineFormat::N32BitTcBgra>( rDst, rSrc );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplConvertFromBitmap<ScanlineFormat::N32BitTcRgba>( rDst, rSrc );
+ default: break;
+ }
+
+ static int nNotAccelerated = 0;
+ SAL_WARN_IF( rSrc.mnWidth * rSrc.mnHeight >= 4000 && ++nNotAccelerated == 100,
+ "vcl.gdi",
+ "ImplFastBitmapConversion for not accelerated case (" << std::hex << static_cast<int>(rSrc.mnFormat) << "->" << static_cast<int>(rDst.mnFormat) << ")" );
+
+ return false;
+}
+
+static inline ConstScanline ImplGetScanline( const BitmapBuffer& rBuf, tools::Long nY )
+{
+ if( rBuf.mnFormat & ScanlineFormat::TopDown )
+ return rBuf.mpBits + nY * rBuf.mnScanlineSize;
+ else
+ return rBuf.mpBits + (rBuf.mnHeight - 1 - nY) * rBuf.mnScanlineSize;
+}
+
+static inline Scanline ImplGetScanline( BitmapBuffer& rBuf, tools::Long nY )
+{
+ return const_cast<Scanline>(ImplGetScanline( const_cast<const BitmapBuffer&>(rBuf), nY ));
+}
+
+template <ScanlineFormat DSTFMT, ScanlineFormat SRCFMT>
+static bool ImplCopyToScanline( tools::Long nY, BitmapBuffer& rDst, TrueColorPixelPtr<SRCFMT>& rSrcLine, tools::Long nSrcWidth )
+{
+ TrueColorPixelPtr<DSTFMT> aDstType;
+ aDstType.SetRawPtr( ImplGetScanline( rDst, nY ));
+ ImplConvertLine( aDstType, rSrcLine, std::min( nSrcWidth, rDst.mnWidth ));
+ return true;
+}
+
+template <ScanlineFormat SRCFMT>
+static bool ImplCopyFromScanline( tools::Long nY, BitmapBuffer& rDst, ConstScanline aSrcScanline, tools::Long nSrcWidth )
+{
+ TrueColorPixelPtr<SRCFMT> aSrcType;
+ aSrcType.SetRawPtr( const_cast<Scanline>( aSrcScanline ));
+ // select the matching instantiation for the destination's bitmap format
+ switch( RemoveScanline( rDst.mnFormat ))
+ {
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplCopyToScanline<ScanlineFormat::N24BitTcBgr>( nY, rDst, aSrcType, nSrcWidth );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplCopyToScanline<ScanlineFormat::N24BitTcRgb>( nY, rDst, aSrcType, nSrcWidth );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplCopyToScanline<ScanlineFormat::N32BitTcAbgr>( nY, rDst, aSrcType, nSrcWidth );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplCopyToScanline<ScanlineFormat::N32BitTcArgb>( nY, rDst, aSrcType, nSrcWidth );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplCopyToScanline<ScanlineFormat::N32BitTcBgra>( nY, rDst, aSrcType, nSrcWidth );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplCopyToScanline<ScanlineFormat::N32BitTcRgba>( nY, rDst, aSrcType, nSrcWidth );
+ default:
+ break;
+ }
+ return false;
+
+}
+
+bool ImplFastCopyScanline( tools::Long nY, BitmapBuffer& rDst, ConstScanline aSrcScanline,
+ ScanlineFormat nSrcScanlineFormat, sal_uInt32 nSrcScanlineSize)
+{
+ if( rDst.mnHeight <= nY )
+ return false;
+
+ const ScanlineFormat nSrcFormat = RemoveScanline(nSrcScanlineFormat);
+ const ScanlineFormat nDstFormat = RemoveScanline(rDst.mnFormat);
+
+ // special handling of trivial cases
+ if( nSrcFormat == nDstFormat )
+ {
+ memcpy( ImplGetScanline( rDst, nY ), aSrcScanline, std::min<tools::Long>(nSrcScanlineSize, rDst.mnScanlineSize));
+ return true;
+ }
+
+ // select the matching instantiation for the source's bitmap format
+ switch( nSrcFormat )
+ {
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplCopyFromScanline<ScanlineFormat::N24BitTcBgr>( nY, rDst, aSrcScanline, nSrcScanlineSize / 3 );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplCopyFromScanline<ScanlineFormat::N24BitTcRgb>( nY, rDst, aSrcScanline, nSrcScanlineSize / 3 );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplCopyFromScanline<ScanlineFormat::N32BitTcAbgr>( nY, rDst, aSrcScanline, nSrcScanlineSize / 4 );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplCopyFromScanline<ScanlineFormat::N32BitTcArgb>( nY, rDst, aSrcScanline, nSrcScanlineSize / 4 );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplCopyFromScanline<ScanlineFormat::N32BitTcBgra>( nY, rDst, aSrcScanline, nSrcScanlineSize / 4 );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplCopyFromScanline<ScanlineFormat::N32BitTcRgba>( nY, rDst, aSrcScanline, nSrcScanlineSize / 4 );
+ default:
+ break;
+ }
+ return false;
+}
+
+bool ImplFastCopyScanline( tools::Long nY, BitmapBuffer& rDst, const BitmapBuffer& rSrc)
+{
+ if( nY >= rDst.mnHeight )
+ return false;
+ if( rSrc.maPalette != rDst.maPalette )
+ return false;
+ return ImplFastCopyScanline( nY, rDst, ImplGetScanline( rSrc, nY ), rSrc.mnFormat, rSrc.mnScanlineSize);
+}
+
+template <ScanlineFormat DSTFMT, ScanlineFormat SRCFMT> //,sal_uLong MSKFMT>
+static bool ImplBlendToBitmap( TrueColorPixelPtr<SRCFMT>& rSrcLine,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer,
+ const BitmapBuffer& rMskBuffer )
+{
+ SAL_WARN_IF(( rMskBuffer.mnFormat & ~ScanlineFormat::TopDown ) != ScanlineFormat::N8BitPal,
+ "vcl.gdi", "FastBmp BlendImage: unusual MSKFMT" );
+
+ const int nSrcLinestep = rSrcBuffer.mnScanlineSize;
+ int nMskLinestep = rMskBuffer.mnScanlineSize;
+ int nDstLinestep = rDstBuffer.mnScanlineSize;
+
+ TrueColorPixelPtr<ScanlineFormat::N8BitPal> aMskLine; aMskLine.SetRawPtr( rMskBuffer.mpBits );
+ TrueColorPixelPtr<DSTFMT> aDstLine; aDstLine.SetRawPtr( rDstBuffer.mpBits );
+
+ // special case for single line masks
+ if( rMskBuffer.mnHeight == 1 )
+ nMskLinestep = 0;
+
+ // source and mask don't match: upside down
+ if( (rSrcBuffer.mnFormat ^ rMskBuffer.mnFormat) & ScanlineFormat::TopDown )
+ {
+ aMskLine.AddByteOffset( (rSrcBuffer.mnHeight - 1) * nMskLinestep );
+ nMskLinestep = -nMskLinestep;
+ }
+
+ // source and destination don't match: upside down
+ if( (rSrcBuffer.mnFormat ^ rDstBuffer.mnFormat) & ScanlineFormat::TopDown )
+ {
+ aDstLine.AddByteOffset( (rDstBuffer.mnHeight - 1) * nDstLinestep );
+ nDstLinestep = -nDstLinestep;
+ }
+
+ assert(rDstBuffer.mnHeight <= rSrcBuffer.mnHeight && "not sure about that?");
+ for (int y = rDstBuffer.mnHeight; --y >= 0;)
+ {
+ ImplBlendLines(aDstLine, rSrcLine, aMskLine, rDstBuffer.mnWidth);
+ aDstLine.AddByteOffset( nDstLinestep );
+ rSrcLine.AddByteOffset( nSrcLinestep );
+ aMskLine.AddByteOffset( nMskLinestep );
+ }
+
+ return true;
+}
+
+// some specializations to reduce the code size
+template <>
+bool ImplBlendToBitmap<ScanlineFormat::N24BitTcBgr,ScanlineFormat::N24BitTcBgr>(
+ TrueColorPixelPtr<ScanlineFormat::N24BitTcBgr>&,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer,
+ const BitmapBuffer& rMskBuffer )
+ {
+ TrueColorPixelPtr<ScanlineFormat::N24BitTcRgb> aSrcType; aSrcType.SetRawPtr( rSrcBuffer.mpBits );
+ return ImplBlendToBitmap<ScanlineFormat::N24BitTcRgb>( aSrcType, rDstBuffer, rSrcBuffer, rMskBuffer );
+ }
+
+template <>
+bool ImplBlendToBitmap<ScanlineFormat::N32BitTcAbgr,ScanlineFormat::N32BitTcAbgr>(
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcAbgr>&,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer,
+ const BitmapBuffer& rMskBuffer )
+ {
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcArgb> aSrcType; aSrcType.SetRawPtr( rSrcBuffer.mpBits );
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcArgb>( aSrcType, rDstBuffer, rSrcBuffer, rMskBuffer );
+ }
+
+template <>
+bool ImplBlendToBitmap<ScanlineFormat::N32BitTcBgra,ScanlineFormat::N32BitTcBgra>(
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcBgra>&,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer,
+ const BitmapBuffer& rMskBuffer )
+ {
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcRgba> aSrcType; aSrcType.SetRawPtr( rSrcBuffer.mpBits );
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcRgba>( aSrcType, rDstBuffer, rSrcBuffer, rMskBuffer );
+ }
+
+template <ScanlineFormat SRCFMT>
+static bool ImplBlendFromBitmap( BitmapBuffer& rDst, const BitmapBuffer& rSrc, const BitmapBuffer& rMsk )
+{
+ TrueColorPixelPtr<SRCFMT> aSrcType; aSrcType.SetRawPtr( rSrc.mpBits );
+
+ // select the matching instantiation for the destination's bitmap format
+ switch (RemoveScanline(rDst.mnFormat))
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ case ScanlineFormat::N8BitPal:
+ break;
+
+ case ScanlineFormat::N32BitTcMask:
+// return ImplBlendToBitmap<ScanlineFormat::N32BitTcMask>( aSrcType, rDst, rSrc, rMsk );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplBlendToBitmap<ScanlineFormat::N24BitTcBgr>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplBlendToBitmap<ScanlineFormat::N24BitTcRgb>( aSrcType, rDst, rSrc, rMsk );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcAbgr>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcArgb>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcBgra>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcRgba>( aSrcType, rDst, rSrc, rMsk );
+ default: break;
+ }
+
+ static int nNotAccelerated = 0;
+ SAL_WARN_IF( rSrc.mnWidth * rSrc.mnHeight >= 4000 && ++nNotAccelerated == 100,
+ "vcl.gdi",
+ "ImplBlendFromBitmap for not accelerated case (" << std::hex << static_cast<int>(rSrc.mnFormat) << "*" << static_cast<int>(rMsk.mnFormat) << "->" << static_cast<int>(rDst.mnFormat) );
+ return false;
+}
+
+bool ImplFastBitmapBlending( BitmapWriteAccess const & rDstWA,
+ const BitmapReadAccess& rSrcRA, const BitmapReadAccess& rMskRA,
+ const SalTwoRect& rTR )
+{
+ // accelerated blending of paletted bitmaps not implemented yet
+ if( rSrcRA.HasPalette() )
+ return false;
+ if( rDstWA.HasPalette() )
+ return false;
+ // TODO: either get rid of mask's use of 8BIT_PAL or check the palette
+
+ // horizontal mirroring not implemented yet
+ if( rTR.mnDestWidth < 0 )
+ return false;
+ // vertical mirroring
+ if( rTR.mnDestHeight < 0 )
+ // TODO: rDst.mnFormat ^= ScanlineFormat::TopDown;
+ return false;
+
+ // offsetted blending is not implemented yet
+ if( rTR.mnSrcX || rTR.mnSrcY )
+ return false;
+ if( rTR.mnDestX || rTR.mnDestY )
+ return false;
+
+ // stretched blending is not implemented yet
+ if( rTR.mnDestWidth != rTR.mnSrcWidth )
+ return false;
+ if( rTR.mnDestHeight!= rTR.mnSrcHeight )
+ return false;
+
+ // check source image size
+ if( rSrcRA.Width() < rTR.mnSrcX + rTR.mnSrcWidth )
+ return false;
+ if( rSrcRA.Height() < rTR.mnSrcY + rTR.mnSrcHeight )
+ return false;
+
+ // check mask image size
+ if( rMskRA.Width() < rTR.mnSrcX + rTR.mnSrcWidth )
+ return false;
+ if( rMskRA.Height() < rTR.mnSrcY + rTR.mnSrcHeight )
+ if( rMskRA.Height() != 1 )
+ return false;
+
+ // check dest image size
+ if( rDstWA.Width() < rTR.mnDestX + rTR.mnDestWidth )
+ return false;
+ if( rDstWA.Height() < rTR.mnDestY + rTR.mnDestHeight )
+ return false;
+
+ BitmapBuffer& rDst = *rDstWA.ImplGetBitmapBuffer();
+ const BitmapBuffer& rSrc = *rSrcRA.ImplGetBitmapBuffer();
+ const BitmapBuffer& rMsk = *rMskRA.ImplGetBitmapBuffer();
+
+ const ScanlineFormat nSrcFormat = RemoveScanline(rSrc.mnFormat);
+
+ // select the matching instantiation for the source's bitmap format
+ switch( nSrcFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ break;
+
+ case ScanlineFormat::N32BitTcMask:
+// return ImplBlendFromBitmap<ScanlineFormat::N32BitTcMask>( rDst, rSrc );
+ break;
+
+ case ScanlineFormat::N8BitPal:
+ if(rSrc.maPalette.IsGreyPalette8Bit())
+ return ImplBlendFromBitmap<ScanlineFormat::N8BitPal>( rDst, rSrc, rMsk );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplBlendFromBitmap<ScanlineFormat::N24BitTcBgr>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplBlendFromBitmap<ScanlineFormat::N24BitTcRgb>( rDst, rSrc, rMsk );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcAbgr>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcArgb>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcBgra>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcRgba>( rDst, rSrc, rMsk );
+ default: break;
+ }
+
+ static int nNotAccelerated = 0;
+ SAL_WARN_IF( rSrc.mnWidth * rSrc.mnHeight >= 4000 && ++nNotAccelerated == 100,
+ "vcl.gdi",
+ "ImplFastBlend for not accelerated case (" << std::hex << static_cast<int>(rSrc.mnFormat) << "*" << static_cast<int>(rMsk.mnFormat) << "->" << static_cast<int>(rDst.mnFormat) << ")" );
+
+ return false;
+}
+
+bool ImplFastEraseBitmap( BitmapBuffer& rDst, const BitmapColor& rColor )
+{
+ const ScanlineFormat nDstFormat = RemoveScanline(rDst.mnFormat);
+
+ // erasing a bitmap is often just a byte-wise memory fill
+ bool bByteFill = true;
+ sal_uInt8 nFillByte;
+
+ switch( nDstFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ nFillByte = rColor.GetIndex();
+ nFillByte = static_cast<sal_uInt8>( -(nFillByte & 1) ); // 0x00 or 0xFF
+ break;
+ case ScanlineFormat::N8BitPal:
+ nFillByte = rColor.GetIndex();
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ case ScanlineFormat::N24BitTcRgb:
+ nFillByte = rColor.GetRed();
+ if( (nFillByte != rColor.GetGreen())
+ || (nFillByte != rColor.GetBlue()) )
+ bByteFill = false;
+ break;
+
+ default:
+ bByteFill = false;
+ nFillByte = 0x00;
+ break;
+ }
+
+ if( bByteFill )
+ {
+ tools::Long nByteCount = rDst.mnHeight * rDst.mnScanlineSize;
+ memset( rDst.mpBits, nFillByte, nByteCount );
+ return true;
+ }
+
+ // TODO: handle other bitmap formats
+ switch( nDstFormat )
+ {
+ case ScanlineFormat::N32BitTcMask:
+
+ case ScanlineFormat::N24BitTcBgr:
+ case ScanlineFormat::N24BitTcRgb:
+
+ case ScanlineFormat::N32BitTcAbgr:
+ case ScanlineFormat::N32BitTcArgb:
+ case ScanlineFormat::N32BitTcBgra:
+ case ScanlineFormat::N32BitTcRgba:
+ break;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/checksum.cxx b/vcl/source/bitmap/checksum.cxx
new file mode 100644
index 000000000..a5c92c555
--- /dev/null
+++ b/vcl/source/bitmap/checksum.cxx
@@ -0,0 +1,154 @@
+/* -*- 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/types.h>
+#include <vcl/checksum.hxx>
+
+
+/*========================================================================
+ *
+ * vcl_crc64Table (CRC polynomial 0x95AC9329AC4BC9B5ULL).
+ *
+ *======================================================================*/
+const sal_uInt64 vcl_crc64Table[256] = {
+ 0x0000000000000000ULL, 0x7ad870c830358979ULL, 0xf5b0e190606b12f2ULL,
+ 0x8f689158505e9b8bULL, 0xc038e5739841b68fULL, 0xbae095bba8743ff6ULL,
+ 0x358804e3f82aa47dULL, 0x4f50742bc81f2d04ULL, 0xab28ecb46814fe75ULL,
+ 0xd1f09c7c5821770cULL, 0x5e980d24087fec87ULL, 0x24407dec384a65feULL,
+ 0x6b1009c7f05548faULL, 0x11c8790fc060c183ULL, 0x9ea0e857903e5a08ULL,
+ 0xe478989fa00bd371ULL, 0x7d08ff3b88be6f81ULL, 0x07d08ff3b88be6f8ULL,
+ 0x88b81eabe8d57d73ULL, 0xf2606e63d8e0f40aULL, 0xbd301a4810ffd90eULL,
+ 0xc7e86a8020ca5077ULL, 0x4880fbd87094cbfcULL, 0x32588b1040a14285ULL,
+ 0xd620138fe0aa91f4ULL, 0xacf86347d09f188dULL, 0x2390f21f80c18306ULL,
+ 0x594882d7b0f40a7fULL, 0x1618f6fc78eb277bULL, 0x6cc0863448deae02ULL,
+ 0xe3a8176c18803589ULL, 0x997067a428b5bcf0ULL, 0xfa11fe77117cdf02ULL,
+ 0x80c98ebf2149567bULL, 0x0fa11fe77117cdf0ULL, 0x75796f2f41224489ULL,
+ 0x3a291b04893d698dULL, 0x40f16bccb908e0f4ULL, 0xcf99fa94e9567b7fULL,
+ 0xb5418a5cd963f206ULL, 0x513912c379682177ULL, 0x2be1620b495da80eULL,
+ 0xa489f35319033385ULL, 0xde51839b2936bafcULL, 0x9101f7b0e12997f8ULL,
+ 0xebd98778d11c1e81ULL, 0x64b116208142850aULL, 0x1e6966e8b1770c73ULL,
+ 0x8719014c99c2b083ULL, 0xfdc17184a9f739faULL, 0x72a9e0dcf9a9a271ULL,
+ 0x08719014c99c2b08ULL, 0x4721e43f0183060cULL, 0x3df994f731b68f75ULL,
+ 0xb29105af61e814feULL, 0xc849756751dd9d87ULL, 0x2c31edf8f1d64ef6ULL,
+ 0x56e99d30c1e3c78fULL, 0xd9810c6891bd5c04ULL, 0xa3597ca0a188d57dULL,
+ 0xec09088b6997f879ULL, 0x96d1784359a27100ULL, 0x19b9e91b09fcea8bULL,
+ 0x636199d339c963f2ULL, 0xdf7adabd7a6e2d6fULL, 0xa5a2aa754a5ba416ULL,
+ 0x2aca3b2d1a053f9dULL, 0x50124be52a30b6e4ULL, 0x1f423fcee22f9be0ULL,
+ 0x659a4f06d21a1299ULL, 0xeaf2de5e82448912ULL, 0x902aae96b271006bULL,
+ 0x74523609127ad31aULL, 0x0e8a46c1224f5a63ULL, 0x81e2d7997211c1e8ULL,
+ 0xfb3aa75142244891ULL, 0xb46ad37a8a3b6595ULL, 0xceb2a3b2ba0eececULL,
+ 0x41da32eaea507767ULL, 0x3b024222da65fe1eULL, 0xa2722586f2d042eeULL,
+ 0xd8aa554ec2e5cb97ULL, 0x57c2c41692bb501cULL, 0x2d1ab4dea28ed965ULL,
+ 0x624ac0f56a91f461ULL, 0x1892b03d5aa47d18ULL, 0x97fa21650afae693ULL,
+ 0xed2251ad3acf6feaULL, 0x095ac9329ac4bc9bULL, 0x7382b9faaaf135e2ULL,
+ 0xfcea28a2faafae69ULL, 0x8632586aca9a2710ULL, 0xc9622c4102850a14ULL,
+ 0xb3ba5c8932b0836dULL, 0x3cd2cdd162ee18e6ULL, 0x460abd1952db919fULL,
+ 0x256b24ca6b12f26dULL, 0x5fb354025b277b14ULL, 0xd0dbc55a0b79e09fULL,
+ 0xaa03b5923b4c69e6ULL, 0xe553c1b9f35344e2ULL, 0x9f8bb171c366cd9bULL,
+ 0x10e3202993385610ULL, 0x6a3b50e1a30ddf69ULL, 0x8e43c87e03060c18ULL,
+ 0xf49bb8b633338561ULL, 0x7bf329ee636d1eeaULL, 0x012b592653589793ULL,
+ 0x4e7b2d0d9b47ba97ULL, 0x34a35dc5ab7233eeULL, 0xbbcbcc9dfb2ca865ULL,
+ 0xc113bc55cb19211cULL, 0x5863dbf1e3ac9decULL, 0x22bbab39d3991495ULL,
+ 0xadd33a6183c78f1eULL, 0xd70b4aa9b3f20667ULL, 0x985b3e827bed2b63ULL,
+ 0xe2834e4a4bd8a21aULL, 0x6debdf121b863991ULL, 0x1733afda2bb3b0e8ULL,
+ 0xf34b37458bb86399ULL, 0x8993478dbb8deae0ULL, 0x06fbd6d5ebd3716bULL,
+ 0x7c23a61ddbe6f812ULL, 0x3373d23613f9d516ULL, 0x49aba2fe23cc5c6fULL,
+ 0xc6c333a67392c7e4ULL, 0xbc1b436e43a74e9dULL, 0x95ac9329ac4bc9b5ULL,
+ 0xef74e3e19c7e40ccULL, 0x601c72b9cc20db47ULL, 0x1ac40271fc15523eULL,
+ 0x5594765a340a7f3aULL, 0x2f4c0692043ff643ULL, 0xa02497ca54616dc8ULL,
+ 0xdafce7026454e4b1ULL, 0x3e847f9dc45f37c0ULL, 0x445c0f55f46abeb9ULL,
+ 0xcb349e0da4342532ULL, 0xb1eceec59401ac4bULL, 0xfebc9aee5c1e814fULL,
+ 0x8464ea266c2b0836ULL, 0x0b0c7b7e3c7593bdULL, 0x71d40bb60c401ac4ULL,
+ 0xe8a46c1224f5a634ULL, 0x927c1cda14c02f4dULL, 0x1d148d82449eb4c6ULL,
+ 0x67ccfd4a74ab3dbfULL, 0x289c8961bcb410bbULL, 0x5244f9a98c8199c2ULL,
+ 0xdd2c68f1dcdf0249ULL, 0xa7f41839ecea8b30ULL, 0x438c80a64ce15841ULL,
+ 0x3954f06e7cd4d138ULL, 0xb63c61362c8a4ab3ULL, 0xcce411fe1cbfc3caULL,
+ 0x83b465d5d4a0eeceULL, 0xf96c151de49567b7ULL, 0x76048445b4cbfc3cULL,
+ 0x0cdcf48d84fe7545ULL, 0x6fbd6d5ebd3716b7ULL, 0x15651d968d029fceULL,
+ 0x9a0d8ccedd5c0445ULL, 0xe0d5fc06ed698d3cULL, 0xaf85882d2576a038ULL,
+ 0xd55df8e515432941ULL, 0x5a3569bd451db2caULL, 0x20ed197575283bb3ULL,
+ 0xc49581ead523e8c2ULL, 0xbe4df122e51661bbULL, 0x3125607ab548fa30ULL,
+ 0x4bfd10b2857d7349ULL, 0x04ad64994d625e4dULL, 0x7e7514517d57d734ULL,
+ 0xf11d85092d094cbfULL, 0x8bc5f5c11d3cc5c6ULL, 0x12b5926535897936ULL,
+ 0x686de2ad05bcf04fULL, 0xe70573f555e26bc4ULL, 0x9ddd033d65d7e2bdULL,
+ 0xd28d7716adc8cfb9ULL, 0xa85507de9dfd46c0ULL, 0x273d9686cda3dd4bULL,
+ 0x5de5e64efd965432ULL, 0xb99d7ed15d9d8743ULL, 0xc3450e196da80e3aULL,
+ 0x4c2d9f413df695b1ULL, 0x36f5ef890dc31cc8ULL, 0x79a59ba2c5dc31ccULL,
+ 0x037deb6af5e9b8b5ULL, 0x8c157a32a5b7233eULL, 0xf6cd0afa9582aa47ULL,
+ 0x4ad64994d625e4daULL, 0x300e395ce6106da3ULL, 0xbf66a804b64ef628ULL,
+ 0xc5bed8cc867b7f51ULL, 0x8aeeace74e645255ULL, 0xf036dc2f7e51db2cULL,
+ 0x7f5e4d772e0f40a7ULL, 0x05863dbf1e3ac9deULL, 0xe1fea520be311aafULL,
+ 0x9b26d5e88e0493d6ULL, 0x144e44b0de5a085dULL, 0x6e963478ee6f8124ULL,
+ 0x21c640532670ac20ULL, 0x5b1e309b16452559ULL, 0xd476a1c3461bbed2ULL,
+ 0xaeaed10b762e37abULL, 0x37deb6af5e9b8b5bULL, 0x4d06c6676eae0222ULL,
+ 0xc26e573f3ef099a9ULL, 0xb8b627f70ec510d0ULL, 0xf7e653dcc6da3dd4ULL,
+ 0x8d3e2314f6efb4adULL, 0x0256b24ca6b12f26ULL, 0x788ec2849684a65fULL,
+ 0x9cf65a1b368f752eULL, 0xe62e2ad306bafc57ULL, 0x6946bb8b56e467dcULL,
+ 0x139ecb4366d1eea5ULL, 0x5ccebf68aecec3a1ULL, 0x2616cfa09efb4ad8ULL,
+ 0xa97e5ef8cea5d153ULL, 0xd3a62e30fe90582aULL, 0xb0c7b7e3c7593bd8ULL,
+ 0xca1fc72bf76cb2a1ULL, 0x45775673a732292aULL, 0x3faf26bb9707a053ULL,
+ 0x70ff52905f188d57ULL, 0x0a2722586f2d042eULL, 0x854fb3003f739fa5ULL,
+ 0xff97c3c80f4616dcULL, 0x1bef5b57af4dc5adULL, 0x61372b9f9f784cd4ULL,
+ 0xee5fbac7cf26d75fULL, 0x9487ca0fff135e26ULL, 0xdbd7be24370c7322ULL,
+ 0xa10fceec0739fa5bULL, 0x2e675fb4576761d0ULL, 0x54bf2f7c6752e8a9ULL,
+ 0xcdcf48d84fe75459ULL, 0xb71738107fd2dd20ULL, 0x387fa9482f8c46abULL,
+ 0x42a7d9801fb9cfd2ULL, 0x0df7adabd7a6e2d6ULL, 0x772fdd63e7936bafULL,
+ 0xf8474c3bb7cdf024ULL, 0x829f3cf387f8795dULL, 0x66e7a46c27f3aa2cULL,
+ 0x1c3fd4a417c62355ULL, 0x935745fc4798b8deULL, 0xe98f353477ad31a7ULL,
+ 0xa6df411fbfb21ca3ULL, 0xdc0731d78f8795daULL, 0x536fa08fdfd90e51ULL,
+ 0x29b7d047efec8728ULL
+};
+
+/*========================================================================
+ *
+ * vcl_crc64 implementation.
+ *
+ *======================================================================*/
+#define UPDCRC64(crc, octet) \
+ (vcl_crc64Table[((crc) ^ (octet)) & 0xff] ^ ((crc) >> 8))
+
+/*
+ * vcl_crc64.
+ */
+sal_uInt64 vcl_crc64 (
+ sal_uInt64 Crc,
+ const void *Data, sal_uInt32 DatLen) SAL_THROW_EXTERN_C()
+{
+ if (Data)
+ {
+ const sal_uInt8 *p = static_cast<const sal_uInt8 *>(Data);
+ const sal_uInt8 *q = p + DatLen;
+
+ Crc = ~Crc;
+ while (p < q)
+ Crc = UPDCRC64(Crc, *(p++));
+ Crc = ~Crc;
+ }
+ return Crc;
+}
+
+/*
+ * vcl_get_crc64_table.
+ */
+const sal_uInt64* vcl_get_crc64_table()
+{
+ return vcl_crc64Table;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/dibtools.cxx b/vcl/source/bitmap/dibtools.cxx
new file mode 100644
index 000000000..96b82b9a7
--- /dev/null
+++ b/vcl/source/bitmap/dibtools.cxx
@@ -0,0 +1,1873 @@
+/* -*- 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 <sal/log.hxx>
+
+#include <cassert>
+
+#include <o3tl/safeint.hxx>
+#include <vcl/dibtools.hxx>
+#include <comphelper/fileformat.h>
+#include <tools/zcodec.hxx>
+#include <tools/stream.hxx>
+#include <tools/fract.hxx>
+#include <tools/helpers.hxx>
+#include <tools/GenericTypeSerializer.hxx>
+#include <unotools/configmgr.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/outdev.hxx>
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <memory>
+
+#define DIBCOREHEADERSIZE ( 12UL )
+#define DIBINFOHEADERSIZE ( sizeof(DIBInfoHeader) )
+#define DIBV5HEADERSIZE ( sizeof(DIBV5Header) )
+
+// - DIBInfoHeader and DIBV5Header
+
+typedef sal_Int32 FXPT2DOT30;
+
+namespace
+{
+
+struct CIEXYZ
+{
+ FXPT2DOT30 aXyzX;
+ FXPT2DOT30 aXyzY;
+ FXPT2DOT30 aXyzZ;
+
+ CIEXYZ()
+ : aXyzX(0),
+ aXyzY(0),
+ aXyzZ(0)
+ {}
+};
+
+struct CIEXYZTriple
+{
+ CIEXYZ aXyzRed;
+ CIEXYZ aXyzGreen;
+ CIEXYZ aXyzBlue;
+
+ CIEXYZTriple()
+ {}
+};
+
+struct DIBInfoHeader
+{
+ sal_uInt32 nSize;
+ sal_Int32 nWidth;
+ sal_Int32 nHeight;
+ sal_uInt16 nPlanes;
+ sal_uInt16 nBitCount;
+ sal_uInt32 nCompression;
+ sal_uInt32 nSizeImage;
+ sal_Int32 nXPelsPerMeter;
+ sal_Int32 nYPelsPerMeter;
+ sal_uInt32 nColsUsed;
+ sal_uInt32 nColsImportant;
+
+ DIBInfoHeader()
+ : nSize(0),
+ nWidth(0),
+ nHeight(0),
+ nPlanes(0),
+ nBitCount(0),
+ nCompression(0),
+ nSizeImage(0),
+ nXPelsPerMeter(0),
+ nYPelsPerMeter(0),
+ nColsUsed(0),
+ nColsImportant(0)
+ {}
+};
+
+struct DIBV5Header : public DIBInfoHeader
+{
+ sal_uInt32 nV5RedMask;
+ sal_uInt32 nV5GreenMask;
+ sal_uInt32 nV5BlueMask;
+ sal_uInt32 nV5AlphaMask;
+ sal_uInt32 nV5CSType;
+ CIEXYZTriple aV5Endpoints;
+ sal_uInt32 nV5GammaRed;
+ sal_uInt32 nV5GammaGreen;
+ sal_uInt32 nV5GammaBlue;
+ sal_uInt32 nV5Intent;
+ sal_uInt32 nV5ProfileData;
+ sal_uInt32 nV5ProfileSize;
+ sal_uInt32 nV5Reserved;
+
+ DIBV5Header()
+ : nV5RedMask(0),
+ nV5GreenMask(0),
+ nV5BlueMask(0),
+ nV5AlphaMask(0),
+ nV5CSType(0),
+ nV5GammaRed(0),
+ nV5GammaGreen(0),
+ nV5GammaBlue(0),
+ nV5Intent(0),
+ nV5ProfileData(0),
+ nV5ProfileSize(0),
+ nV5Reserved(0)
+ {}
+};
+
+vcl::PixelFormat convertToBPP(sal_uInt16 nCount)
+{
+ return (nCount <= 1) ? vcl::PixelFormat::N1_BPP :
+ (nCount <= 8) ? vcl::PixelFormat::N8_BPP :
+ vcl::PixelFormat::N24_BPP;
+}
+
+bool isBitfieldCompression( ScanlineFormat nScanlineFormat )
+{
+ return ScanlineFormat::N32BitTcMask == nScanlineFormat;
+}
+
+bool ImplReadDIBInfoHeader(SvStream& rIStm, DIBV5Header& rHeader, bool& bTopDown, bool bMSOFormat)
+{
+ if (rIStm.remainingSize() <= 4)
+ return false;
+ // BITMAPINFOHEADER or BITMAPCOREHEADER or BITMAPV5HEADER
+ sal_uInt64 const aStartPos(rIStm.Tell());
+ rIStm.ReadUInt32( rHeader.nSize );
+
+ // BITMAPCOREHEADER
+ if ( rHeader.nSize == DIBCOREHEADERSIZE )
+ {
+ sal_Int16 nTmp16;
+
+ rIStm.ReadInt16( nTmp16 ); rHeader.nWidth = nTmp16;
+ rIStm.ReadInt16( nTmp16 ); rHeader.nHeight = nTmp16;
+ rIStm.ReadUInt16( rHeader.nPlanes );
+ rIStm.ReadUInt16( rHeader.nBitCount );
+ }
+ else if ( bMSOFormat && rHeader.nSize == DIBINFOHEADERSIZE )
+ {
+ sal_Int16 nTmp16(0);
+ rIStm.ReadInt16(nTmp16);
+ rHeader.nWidth = nTmp16;
+ rIStm.ReadInt16(nTmp16);
+ rHeader.nHeight = nTmp16;
+ sal_uInt8 nTmp8(0);
+ rIStm.ReadUChar(nTmp8);
+ rHeader.nPlanes = nTmp8;
+ rIStm.ReadUChar(nTmp8);
+ rHeader.nBitCount = nTmp8;
+ rIStm.ReadInt16(nTmp16);
+ rHeader.nSizeImage = nTmp16;
+ rIStm.ReadInt16(nTmp16);
+ rHeader.nCompression = nTmp16;
+ if ( !rHeader.nSizeImage ) // uncompressed?
+ rHeader.nSizeImage = ((rHeader.nWidth * rHeader.nBitCount + 31) & ~31) / 8 * rHeader.nHeight;
+ rIStm.ReadInt32( rHeader.nXPelsPerMeter );
+ rIStm.ReadInt32( rHeader.nYPelsPerMeter );
+ rIStm.ReadUInt32( rHeader.nColsUsed );
+ rIStm.ReadUInt32( rHeader.nColsImportant );
+ }
+ else
+ {
+ // BITMAPCOREHEADER, BITMAPV5HEADER or unknown. Read as far as possible
+ std::size_t nUsed(sizeof(rHeader.nSize));
+
+ auto readUInt16 = [&nUsed, &rHeader, &rIStm](sal_uInt16 & v) {
+ if (nUsed < rHeader.nSize) {
+ rIStm.ReadUInt16(v);
+ nUsed += sizeof(v);
+ }
+ };
+ auto readInt32 = [&nUsed, &rHeader, &rIStm](sal_Int32 & v) {
+ if (nUsed < rHeader.nSize) {
+ rIStm.ReadInt32(v);
+ nUsed += sizeof(v);
+ }
+ };
+ auto readUInt32 = [&nUsed, &rHeader, &rIStm](sal_uInt32 & v) {
+ if (nUsed < rHeader.nSize) {
+ rIStm.ReadUInt32(v);
+ nUsed += sizeof(v);
+ }
+ };
+
+ // read DIBInfoHeader entries
+ readInt32( rHeader.nWidth );
+ readInt32( rHeader.nHeight );
+ readUInt16( rHeader.nPlanes );
+ readUInt16( rHeader.nBitCount );
+ readUInt32( rHeader.nCompression );
+ readUInt32( rHeader.nSizeImage );
+ readInt32( rHeader.nXPelsPerMeter );
+ readInt32( rHeader.nYPelsPerMeter );
+ readUInt32( rHeader.nColsUsed );
+ readUInt32( rHeader.nColsImportant );
+
+ // read DIBV5HEADER members
+ readUInt32( rHeader.nV5RedMask );
+ readUInt32( rHeader.nV5GreenMask );
+ readUInt32( rHeader.nV5BlueMask );
+ readUInt32( rHeader.nV5AlphaMask );
+ readUInt32( rHeader.nV5CSType );
+
+ // read contained CIEXYZTriple's
+ readInt32( rHeader.aV5Endpoints.aXyzRed.aXyzX );
+ readInt32( rHeader.aV5Endpoints.aXyzRed.aXyzY );
+ readInt32( rHeader.aV5Endpoints.aXyzRed.aXyzZ );
+ readInt32( rHeader.aV5Endpoints.aXyzGreen.aXyzX );
+ readInt32( rHeader.aV5Endpoints.aXyzGreen.aXyzY );
+ readInt32( rHeader.aV5Endpoints.aXyzGreen.aXyzZ );
+ readInt32( rHeader.aV5Endpoints.aXyzBlue.aXyzX );
+ readInt32( rHeader.aV5Endpoints.aXyzBlue.aXyzY );
+ readInt32( rHeader.aV5Endpoints.aXyzBlue.aXyzZ );
+
+ readUInt32( rHeader.nV5GammaRed );
+ readUInt32( rHeader.nV5GammaGreen );
+ readUInt32( rHeader.nV5GammaBlue );
+ readUInt32( rHeader.nV5Intent );
+ readUInt32( rHeader.nV5ProfileData );
+ readUInt32( rHeader.nV5ProfileSize );
+ readUInt32( rHeader.nV5Reserved );
+
+ // Read color mask. An additional 12 bytes of color bitfields follow the info header (WinBMPv3-NT)
+ sal_uInt32 nColorMask = 0;
+ if (BITFIELDS == rHeader.nCompression && DIBINFOHEADERSIZE == rHeader.nSize)
+ {
+ rIStm.ReadUInt32( rHeader.nV5RedMask );
+ rIStm.ReadUInt32( rHeader.nV5GreenMask );
+ rIStm.ReadUInt32( rHeader.nV5BlueMask );
+ nColorMask = 12;
+ }
+
+ // seek to EndPos
+ if (!checkSeek(rIStm, aStartPos + rHeader.nSize + nColorMask))
+ return false;
+ }
+
+ if (!rIStm.good() || rHeader.nHeight == SAL_MIN_INT32)
+ return false;
+
+ if ( rHeader.nHeight < 0 )
+ {
+ bTopDown = true;
+ rHeader.nHeight *= -1;
+ }
+ else
+ {
+ bTopDown = false;
+ }
+
+ if ( rHeader.nWidth < 0 || rHeader.nXPelsPerMeter < 0 || rHeader.nYPelsPerMeter < 0 )
+ {
+ rIStm.SetError( SVSTREAM_FILEFORMAT_ERROR );
+ }
+
+ // #144105# protect a little against damaged files
+ assert(rHeader.nHeight >= 0);
+ if (rHeader.nHeight != 0 && rHeader.nWidth >= 0
+ && (rHeader.nSizeImage / 16 / static_cast<sal_uInt32>(rHeader.nHeight)
+ > o3tl::make_unsigned(rHeader.nWidth)))
+ {
+ rHeader.nSizeImage = 0;
+ }
+
+
+ if (rHeader.nPlanes != 1)
+ return false;
+
+ if (rHeader.nBitCount != 0 && rHeader.nBitCount != 1 &&
+ rHeader.nBitCount != 4 && rHeader.nBitCount != 8 &&
+ rHeader.nBitCount != 16 && rHeader.nBitCount != 24 &&
+ rHeader.nBitCount != 32)
+ {
+ return false;
+ }
+
+ return rIStm.good();
+}
+
+bool ImplReadDIBPalette(SvStream& rIStm, BitmapPalette& rPal, bool bQuad)
+{
+ const sal_uInt16 nColors = rPal.GetEntryCount();
+ const sal_uLong nPalSize = nColors * ( bQuad ? 4UL : 3UL );
+ BitmapColor aPalColor;
+
+ std::unique_ptr<sal_uInt8[]> pEntries(new sal_uInt8[ nPalSize ]);
+ if (rIStm.ReadBytes(pEntries.get(), nPalSize) != nPalSize)
+ {
+ return false;
+ }
+
+ sal_uInt8* pTmpEntry = pEntries.get();
+ for( sal_uInt16 i = 0; i < nColors; i++ )
+ {
+ aPalColor.SetBlue( *pTmpEntry++ );
+ aPalColor.SetGreen( *pTmpEntry++ );
+ aPalColor.SetRed( *pTmpEntry++ );
+
+ if( bQuad )
+ pTmpEntry++;
+
+ rPal[i] = aPalColor;
+ }
+
+ return rIStm.GetError() == ERRCODE_NONE;
+}
+
+BitmapColor SanitizePaletteIndex(sal_uInt8 nIndex, BitmapPalette& rPalette, bool bForceToMonoWhileReading)
+{
+ const sal_uInt16 nPaletteEntryCount = rPalette.GetEntryCount();
+ if (nPaletteEntryCount && nIndex >= nPaletteEntryCount)
+ {
+ auto nSanitizedIndex = nIndex % nPaletteEntryCount;
+ SAL_WARN_IF(nIndex != nSanitizedIndex, "vcl", "invalid colormap index: "
+ << static_cast<unsigned int>(nIndex) << ", colormap len is: "
+ << nPaletteEntryCount);
+ nIndex = nSanitizedIndex;
+ }
+
+ if (nPaletteEntryCount && bForceToMonoWhileReading)
+ {
+ return BitmapColor(static_cast<sal_uInt8>(rPalette[nIndex].GetLuminance() >= 255));
+ }
+
+ return BitmapColor(nIndex);
+}
+
+BitmapColor SanitizeColor(const BitmapColor &rColor, bool bForceToMonoWhileReading)
+{
+ if (!bForceToMonoWhileReading)
+ return rColor;
+ return BitmapColor(static_cast<sal_uInt8>(rColor.GetLuminance() >= 255));
+}
+
+bool ImplDecodeRLE(sal_uInt8* pBuffer, DIBV5Header const & rHeader, BitmapWriteAccess& rAcc, BitmapPalette& rPalette, bool bForceToMonoWhileReading, bool bRLE4)
+{
+ Scanline pRLE = pBuffer;
+ Scanline pEndRLE = pBuffer + rHeader.nSizeImage;
+ tools::Long nY = rHeader.nHeight - 1;
+ const sal_uLong nWidth = rAcc.Width();
+ sal_uLong nCountByte;
+ sal_uLong nRunByte;
+ sal_uLong nX = 0;
+ sal_uInt8 cTmp;
+ bool bEndDecoding = false;
+
+ do
+ {
+ if (pRLE == pEndRLE)
+ return false;
+ if( ( nCountByte = *pRLE++ ) == 0 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+ nRunByte = *pRLE++;
+
+ if( nRunByte > 2 )
+ {
+ Scanline pScanline = rAcc.GetScanline(nY);
+ if( bRLE4 )
+ {
+ nCountByte = nRunByte >> 1;
+
+ for( sal_uLong i = 0; i < nCountByte; i++ )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ cTmp = *pRLE++;
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette, bForceToMonoWhileReading));
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp & 0x0f, rPalette, bForceToMonoWhileReading));
+ }
+
+ if( nRunByte & 1 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(*pRLE >> 4, rPalette, bForceToMonoWhileReading));
+
+ pRLE++;
+ }
+
+ if( ( ( nRunByte + 1 ) >> 1 ) & 1 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ pRLE++;
+ }
+ }
+ else
+ {
+ for( sal_uLong i = 0; i < nRunByte; i++ )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(*pRLE, rPalette, bForceToMonoWhileReading));
+
+ pRLE++;
+ }
+
+ if( nRunByte & 1 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ pRLE++;
+ }
+ }
+ }
+ else if( !nRunByte )
+ {
+ nY--;
+ nX = 0;
+ }
+ else if( nRunByte == 1 )
+ bEndDecoding = true;
+ else
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ nX += *pRLE++;
+
+ if (pRLE == pEndRLE)
+ return false;
+
+ nY -= *pRLE++;
+ }
+ }
+ else
+ {
+ if (pRLE == pEndRLE)
+ return false;
+ cTmp = *pRLE++;
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ if( bRLE4 )
+ {
+ nRunByte = nCountByte >> 1;
+
+ for (sal_uLong i = 0; i < nRunByte && nX < nWidth; ++i)
+ {
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette, bForceToMonoWhileReading));
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp & 0x0f, rPalette, bForceToMonoWhileReading));
+ }
+
+ if( ( nCountByte & 1 ) && ( nX < nWidth ) )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette, bForceToMonoWhileReading));
+ }
+ else
+ {
+ for (sal_uLong i = 0; i < nCountByte && nX < nWidth; ++i)
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp, rPalette, bForceToMonoWhileReading));
+ }
+ }
+ }
+ while (!bEndDecoding && (nY >= 0));
+
+ return true;
+}
+
+bool ImplReadDIBBits(SvStream& rIStm, DIBV5Header& rHeader, BitmapWriteAccess& rAcc, BitmapPalette& rPalette, BitmapWriteAccess* pAccAlpha,
+ bool bTopDown, bool& rAlphaUsed, const sal_uInt64 nAlignedWidth,
+ const bool bForceToMonoWhileReading)
+{
+ sal_uInt32 nRMask(( rHeader.nBitCount == 16 ) ? 0x00007c00UL : 0x00ff0000UL);
+ sal_uInt32 nGMask(( rHeader.nBitCount == 16 ) ? 0x000003e0UL : 0x0000ff00UL);
+ sal_uInt32 nBMask(( rHeader.nBitCount == 16 ) ? 0x0000001fUL : 0x000000ffUL);
+ bool bNative(false);
+ bool bTCMask(!pAccAlpha && ((16 == rHeader.nBitCount) || (32 == rHeader.nBitCount)));
+ bool bRLE((RLE_8 == rHeader.nCompression && 8 == rHeader.nBitCount) || (RLE_4 == rHeader.nCompression && 4 == rHeader.nBitCount));
+
+ // Is native format?
+ switch(rAcc.GetScanlineFormat())
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N24BitTcBgr:
+ {
+ // we can't trust arbitrary-sourced index based formats to have correct indexes, so we exclude the pal formats
+ // from raw read and force checking their colormap indexes
+ bNative = ( ( rAcc.IsBottomUp() != bTopDown ) && !bRLE && !bTCMask && ( rAcc.GetScanlineSize() == nAlignedWidth ) );
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+
+ // Read data
+ if (bNative)
+ {
+ if (nAlignedWidth
+ > std::numeric_limits<std::size_t>::max() / rHeader.nHeight)
+ {
+ return false;
+ }
+ std::size_t n = nAlignedWidth * rHeader.nHeight;
+ if (rIStm.ReadBytes(rAcc.GetBuffer(), n) != n)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (rHeader.nV5RedMask > 0)
+ nRMask = rHeader.nV5RedMask;
+ if (rHeader.nV5GreenMask > 0)
+ nGMask = rHeader.nV5GreenMask;
+ if (rHeader.nV5BlueMask > 0)
+ nBMask = rHeader.nV5BlueMask;
+
+ const tools::Long nWidth(rHeader.nWidth);
+ const tools::Long nHeight(rHeader.nHeight);
+ tools::Long nResult = 0;
+ if (utl::ConfigManager::IsFuzzing() && (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000))
+ return false;
+
+ if (bRLE)
+ {
+ if(!rHeader.nSizeImage)
+ {
+ rHeader.nSizeImage = rIStm.remainingSize();
+ }
+
+ if (rHeader.nSizeImage > rIStm.remainingSize())
+ return false;
+ std::vector<sal_uInt8> aBuffer(rHeader.nSizeImage);
+ if (rIStm.ReadBytes(aBuffer.data(), rHeader.nSizeImage) != rHeader.nSizeImage)
+ return false;
+ if (!ImplDecodeRLE(aBuffer.data(), rHeader, rAcc, rPalette, bForceToMonoWhileReading, RLE_4 == rHeader.nCompression))
+ return false;
+ }
+ else
+ {
+ if (nAlignedWidth > rIStm.remainingSize())
+ {
+ // ofz#11188 avoid timeout
+ // all following paths will enter a case statement, and nCount
+ // is always at least 1, so we can check here before allocation
+ // if at least one row can be read
+ return false;
+ }
+ std::vector<sal_uInt8> aBuf(nAlignedWidth);
+
+ const tools::Long nI(bTopDown ? 1 : -1);
+ tools::Long nY(bTopDown ? 0 : nHeight - 1);
+ tools::Long nCount(nHeight);
+
+ switch(rHeader.nBitCount)
+ {
+ case 1:
+ {
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt8 * pTmp = aBuf.data();
+ if (rIStm.ReadBytes(pTmp, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+ sal_uInt8 cTmp = *pTmp++;
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( tools::Long nX = 0, nShift = 8; nX < nWidth; nX++ )
+ {
+ if( !nShift )
+ {
+ nShift = 8;
+ cTmp = *pTmp++;
+ }
+
+ auto nIndex = (cTmp >> --nShift) & 1;
+ rAcc.SetPixelOnData(pScanline, nX, SanitizePaletteIndex(nIndex, rPalette, bForceToMonoWhileReading));
+ }
+ }
+ }
+ break;
+
+ case 4:
+ {
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt8 * pTmp = aBuf.data();
+ if (rIStm.ReadBytes(pTmp, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+ sal_uInt8 cTmp = *pTmp++;
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( tools::Long nX = 0, nShift = 2; nX < nWidth; nX++ )
+ {
+ if( !nShift )
+ {
+ nShift = 2;
+ cTmp = *pTmp++;
+ }
+
+ auto nIndex = (cTmp >> ( --nShift << 2 ) ) & 0x0f;
+ rAcc.SetPixelOnData(pScanline, nX, SanitizePaletteIndex(nIndex, rPalette, bForceToMonoWhileReading));
+ }
+ }
+ }
+ break;
+
+ case 8:
+ {
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt8 * pTmp = aBuf.data();
+ if (rIStm.ReadBytes(pTmp, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ auto nIndex = *pTmp++;
+ rAcc.SetPixelOnData(pScanline, nX, SanitizePaletteIndex(nIndex, rPalette, bForceToMonoWhileReading));
+ }
+ }
+ }
+ break;
+
+ case 16:
+ {
+ ColorMaskElement aRedMask(nRMask);
+ if (!aRedMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aGreenMask(nGMask);
+ if (!aGreenMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aBlueMask(nBMask);
+ if (!aBlueMask.CalcMaskShift())
+ return false;
+
+ ColorMask aMask(aRedMask, aGreenMask, aBlueMask);
+ BitmapColor aColor;
+
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt16 * pTmp16 = reinterpret_cast<sal_uInt16*>(aBuf.data());
+ if (rIStm.ReadBytes(pTmp16, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ aMask.GetColorFor16BitLSB( aColor, reinterpret_cast<sal_uInt8*>(pTmp16++) );
+ rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aColor, bForceToMonoWhileReading));
+ }
+ }
+ }
+ break;
+
+ case 24:
+ {
+ BitmapColor aPixelColor;
+
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ if (rIStm.ReadBytes(pTmp, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ aPixelColor.SetBlue( *pTmp++ );
+ aPixelColor.SetGreen( *pTmp++ );
+ aPixelColor.SetRed( *pTmp++ );
+ rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aPixelColor, bForceToMonoWhileReading));
+ }
+ }
+ }
+ break;
+
+ case 32:
+ {
+ ColorMaskElement aRedMask(nRMask);
+ if (!aRedMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aGreenMask(nGMask);
+ if (!aGreenMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aBlueMask(nBMask);
+ if (!aBlueMask.CalcMaskShift())
+ return false;
+ ColorMask aMask(aRedMask, aGreenMask, aBlueMask);
+
+ BitmapColor aColor;
+ sal_uInt32* pTmp32;
+
+ if(pAccAlpha)
+ {
+ sal_uInt8 aAlpha;
+
+ for( ; nCount--; nY += nI )
+ {
+ pTmp32 = reinterpret_cast<sal_uInt32*>(aBuf.data());
+ if (rIStm.ReadBytes(pTmp32, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ Scanline pAlphaScanline = pAccAlpha->GetScanline(nY);
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ aMask.GetColorAndAlphaFor32Bit( aColor, aAlpha, reinterpret_cast<sal_uInt8*>(pTmp32++) );
+ rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aColor, bForceToMonoWhileReading));
+ pAccAlpha->SetPixelOnData(pAlphaScanline, nX, BitmapColor(sal_uInt8(0xff) - aAlpha));
+ rAlphaUsed |= 0xff != aAlpha;
+ }
+ }
+ }
+ else
+ {
+ for( ; nCount--; nY += nI )
+ {
+ pTmp32 = reinterpret_cast<sal_uInt32*>(aBuf.data());
+ if (rIStm.ReadBytes(pTmp32, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ aMask.GetColorFor32Bit( aColor, reinterpret_cast<sal_uInt8*>(pTmp32++) );
+ rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aColor, bForceToMonoWhileReading));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return rIStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplReadDIBBody(SvStream& rIStm, Bitmap& rBmp, AlphaMask* pBmpAlpha, sal_uInt64 nOffset, bool bIsMask, bool bMSOFormat)
+{
+ DIBV5Header aHeader;
+ const sal_uInt64 nStmPos = rIStm.Tell();
+ bool bTopDown(false);
+
+ if (!ImplReadDIBInfoHeader(rIStm, aHeader, bTopDown, bMSOFormat))
+ return false;
+
+ //BI_BITCOUNT_0 jpeg/png is unsupported
+ if (aHeader.nBitCount == 0)
+ return false;
+
+ if (aHeader.nWidth <= 0 || aHeader.nHeight <= 0)
+ return false;
+
+ // In case ImplReadDIB() didn't call ImplReadDIBFileHeader() before
+ // this method, nOffset is 0, that's OK.
+ if (nOffset && aHeader.nSize > nOffset)
+ {
+ // Header size claims to extend into the image data.
+ // Looks like an error.
+ return false;
+ }
+
+ sal_uInt16 nColors(0);
+ SvStream* pIStm;
+ std::unique_ptr<SvMemoryStream> pMemStm;
+ std::vector<sal_uInt8> aData;
+
+ if (aHeader.nBitCount <= 8)
+ {
+ if(aHeader.nColsUsed)
+ {
+ nColors = static_cast<sal_uInt16>(aHeader.nColsUsed);
+ }
+ else
+ {
+ nColors = ( 1 << aHeader.nBitCount );
+ }
+ }
+
+ if (ZCOMPRESS == aHeader.nCompression)
+ {
+ sal_uInt32 nCodedSize(0);
+ sal_uInt32 nUncodedSize(0);
+
+ // read coding information
+ rIStm.ReadUInt32( nCodedSize ).ReadUInt32( nUncodedSize ).ReadUInt32( aHeader.nCompression );
+ if (nCodedSize > rIStm.remainingSize())
+ nCodedSize = sal_uInt32(rIStm.remainingSize());
+
+ pMemStm.reset(new SvMemoryStream);
+ // There may be bytes left over or the codec might read more than
+ // necessary. So to preserve the correctness of the source stream copy
+ // the encoded block
+ pMemStm->WriteStream(rIStm, nCodedSize);
+ pMemStm->Seek(0);
+
+ size_t nSizeInc(4 * pMemStm->remainingSize());
+ if (nUncodedSize < nSizeInc)
+ nSizeInc = nUncodedSize;
+
+ if (nSizeInc > 0)
+ {
+ // decode buffer
+ ZCodec aCodec;
+ aCodec.BeginCompression();
+ aData.resize(nSizeInc);
+ size_t nDataPos(0);
+ while (nUncodedSize > nDataPos)
+ {
+ assert(aData.size() > nDataPos);
+ const size_t nToRead(std::min<size_t>(nUncodedSize - nDataPos, aData.size() - nDataPos));
+ assert(nToRead > 0);
+ assert(!aData.empty());
+ const tools::Long nRead = aCodec.Read(*pMemStm, aData.data() + nDataPos, sal_uInt32(nToRead));
+ if (nRead > 0)
+ {
+ nDataPos += static_cast<tools::ULong>(nRead);
+ // we haven't read everything yet: resize buffer and continue
+ if (nDataPos < nUncodedSize)
+ aData.resize(aData.size() + nSizeInc);
+ }
+ else
+ {
+ break;
+ }
+ }
+ // truncate the data buffer to actually read size
+ aData.resize(nDataPos);
+ // set the real uncoded size
+ nUncodedSize = sal_uInt32(aData.size());
+ aCodec.EndCompression();
+ }
+
+ if (aData.empty())
+ {
+ // add something so we can take address of the first element
+ aData.resize(1);
+ nUncodedSize = 0;
+ }
+
+ // set decoded bytes to memory stream,
+ // from which we will read the bitmap data
+ pMemStm.reset(new SvMemoryStream);
+ pIStm = pMemStm.get();
+ assert(!aData.empty());
+ pMemStm->SetBuffer(aData.data(), nUncodedSize, nUncodedSize);
+ nOffset = 0;
+ }
+ else
+ {
+ pIStm = &rIStm;
+ }
+
+ // read palette
+ BitmapPalette aPalette;
+ if (nColors)
+ {
+ aPalette.SetEntryCount(nColors);
+ ImplReadDIBPalette(*pIStm, aPalette, aHeader.nSize != DIBCOREHEADERSIZE);
+ }
+
+ if (pIStm->GetError())
+ return false;
+
+ if (nOffset)
+ {
+ // It is problematic to seek backwards. We are at the
+ // end of BITMAPINFOHEADER or 12 bytes further in case
+ // of WinBMPv3-NT format. It is possible to seek forward
+ // though because a gap may be there.
+ sal_Int32 nSeekRel = nOffset - (pIStm->Tell() - nStmPos);
+ if (nSeekRel > 0)
+ pIStm->SeekRel(nSeekRel);
+ }
+
+ const sal_Int64 nBitsPerLine (static_cast<sal_Int64>(aHeader.nWidth) * static_cast<sal_Int64>(aHeader.nBitCount));
+ if (nBitsPerLine > SAL_MAX_UINT32)
+ return false;
+ const sal_uInt64 nAlignedWidth(AlignedWidth4Bytes(static_cast<sal_uLong>(nBitsPerLine)));
+
+ switch (aHeader.nCompression)
+ {
+ case RLE_8:
+ {
+ if (aHeader.nBitCount != 8)
+ return false;
+ // (partially) check the image dimensions to avoid potential large bitmap allocation if the input is damaged
+ sal_uInt64 nMaxWidth = pIStm->remainingSize();
+ nMaxWidth *= 256; //assume generous compression ratio
+ nMaxWidth /= aHeader.nHeight;
+ if (nMaxWidth < o3tl::make_unsigned(aHeader.nWidth))
+ return false;
+ break;
+ }
+ case RLE_4:
+ {
+ if (aHeader.nBitCount != 4)
+ return false;
+ sal_uInt64 nMaxWidth = pIStm->remainingSize();
+ nMaxWidth *= 512; //assume generous compression ratio
+ nMaxWidth /= aHeader.nHeight;
+ if (nMaxWidth < o3tl::make_unsigned(aHeader.nWidth))
+ return false;
+ break;
+ }
+ default:
+ // tdf#122958 invalid compression value used
+ if (aHeader.nCompression & 0x000F)
+ {
+ // lets assume that there was an error in the generating application
+ // and allow through as COMPRESS_NONE if the bottom byte is 0
+ SAL_WARN( "vcl", "bad bmp compression scheme: " << aHeader.nCompression << ", rejecting bmp");
+ return false;
+ }
+ else
+ SAL_WARN( "vcl", "bad bmp compression scheme: " << aHeader.nCompression << ", assuming meant to be COMPRESS_NONE");
+ [[fallthrough]];
+ case BITFIELDS:
+ case ZCOMPRESS:
+ case COMPRESS_NONE:
+ {
+ // (partially) check the image dimensions to avoid potential large bitmap allocation if the input is damaged
+ sal_uInt64 nMaxWidth = pIStm->remainingSize();
+ nMaxWidth /= aHeader.nHeight;
+ if (nMaxWidth < nAlignedWidth)
+ return false;
+ break;
+ }
+ }
+
+ const Size aSizePixel(aHeader.nWidth, aHeader.nHeight);
+ AlphaMask aNewBmpAlpha;
+ AlphaScopedWriteAccess pAccAlpha;
+ bool bAlphaPossible(pBmpAlpha && aHeader.nBitCount == 32);
+
+ if (bAlphaPossible)
+ {
+ const bool bRedSet(0 != aHeader.nV5RedMask);
+ const bool bGreenSet(0 != aHeader.nV5GreenMask);
+ const bool bBlueSet(0 != aHeader.nV5BlueMask);
+
+ // some clipboard entries have alpha mask on zero to say that there is
+ // no alpha; do only use this when the other masks are set. The MS docu
+ // says that masks are only to be set when bV5Compression is set to
+ // BI_BITFIELDS, but there seem to exist a wild variety of usages...
+ if((bRedSet || bGreenSet || bBlueSet) && (0 == aHeader.nV5AlphaMask))
+ {
+ bAlphaPossible = false;
+ }
+ }
+
+ if (bAlphaPossible)
+ {
+ aNewBmpAlpha = AlphaMask(aSizePixel);
+ pAccAlpha = AlphaScopedWriteAccess(aNewBmpAlpha);
+ }
+
+ vcl::PixelFormat ePixelFormat(convertToBPP(aHeader.nBitCount));
+ const BitmapPalette* pPal = &aPalette;
+ //ofz#948 match the surrounding logic of case TransparentType::Bitmap of
+ //ReadDIBBitmapEx but do it while reading for performance
+ const bool bIsAlpha = (ePixelFormat == vcl::PixelFormat::N8_BPP &&
+ !!aPalette && aPalette.IsGreyPalette8Bit());
+ const bool bForceToMonoWhileReading = (bIsMask && !bIsAlpha && ePixelFormat != vcl::PixelFormat::N1_BPP);
+ if (bForceToMonoWhileReading)
+ {
+ pPal = nullptr;
+ ePixelFormat = vcl::PixelFormat::N1_BPP;
+ SAL_WARN( "vcl", "forcing mask to monochrome");
+ }
+
+ Bitmap aNewBmp(aSizePixel, ePixelFormat, pPal);
+ BitmapScopedWriteAccess pAcc(aNewBmp);
+ if (!pAcc)
+ return false;
+ if (pAcc->Width() != aHeader.nWidth || pAcc->Height() != aHeader.nHeight)
+ {
+ return false;
+ }
+
+ // read bits
+ bool bAlphaUsed(false);
+ bool bRet = ImplReadDIBBits(*pIStm, aHeader, *pAcc, aPalette, pAccAlpha.get(), bTopDown, bAlphaUsed, nAlignedWidth, bForceToMonoWhileReading);
+
+ if (bRet && aHeader.nXPelsPerMeter && aHeader.nYPelsPerMeter)
+ {
+ MapMode aMapMode(
+ MapUnit::MapMM,
+ Point(),
+ Fraction(1000, aHeader.nXPelsPerMeter),
+ Fraction(1000, aHeader.nYPelsPerMeter));
+
+ aNewBmp.SetPrefMapMode(aMapMode);
+ aNewBmp.SetPrefSize(Size(aHeader.nWidth, aHeader.nHeight));
+ }
+
+ pAcc.reset();
+
+ if (bAlphaPossible)
+ {
+ pAccAlpha.reset();
+
+ if(!bAlphaUsed)
+ {
+ bAlphaPossible = false;
+ }
+ }
+
+ if (bRet)
+ {
+ rBmp = aNewBmp;
+
+ if(bAlphaPossible)
+ {
+ *pBmpAlpha = aNewBmpAlpha;
+ }
+ }
+
+ return bRet;
+}
+
+bool ImplReadDIBFileHeader( SvStream& rIStm, sal_uLong& rOffset )
+{
+ bool bRet = false;
+
+ const sal_uInt64 nStreamLength = rIStm.TellEnd();
+
+ sal_uInt16 nTmp16 = 0;
+ rIStm.ReadUInt16( nTmp16 );
+
+ if ( ( 0x4D42 == nTmp16 ) || ( 0x4142 == nTmp16 ) )
+ {
+ sal_uInt32 nTmp32(0);
+ if ( 0x4142 == nTmp16 )
+ {
+ rIStm.SeekRel( 12 );
+ rIStm.ReadUInt16( nTmp16 );
+ rIStm.SeekRel( 8 );
+ rIStm.ReadUInt32( nTmp32 );
+ rOffset = nTmp32 - 28;
+ bRet = ( 0x4D42 == nTmp16 );
+ }
+ else // 0x4D42 == nTmp16, 'MB' from BITMAPFILEHEADER
+ {
+ rIStm.SeekRel( 8 ); // we are on bfSize member of BITMAPFILEHEADER, forward to bfOffBits
+ rIStm.ReadUInt32( nTmp32 ); // read bfOffBits
+ rOffset = nTmp32 - 14; // adapt offset by sizeof(BITMAPFILEHEADER)
+ bRet = rIStm.GetError() == ERRCODE_NONE;
+ }
+
+ if ( rOffset >= nStreamLength )
+ {
+ // Offset claims that image starts past the end of the
+ // stream. Unlikely.
+ rIStm.SetError( SVSTREAM_FILEFORMAT_ERROR );
+ bRet = false;
+ }
+ }
+ else
+ rIStm.SetError( SVSTREAM_FILEFORMAT_ERROR );
+
+ return bRet;
+}
+
+bool ImplWriteDIBPalette( SvStream& rOStm, BitmapReadAccess const & rAcc )
+{
+ const sal_uInt16 nColors = rAcc.GetPaletteEntryCount();
+ const sal_uLong nPalSize = nColors * 4UL;
+ std::unique_ptr<sal_uInt8[]> pEntries(new sal_uInt8[ nPalSize ]);
+ sal_uInt8* pTmpEntry = pEntries.get();
+
+ for( sal_uInt16 i = 0; i < nColors; i++ )
+ {
+ const BitmapColor& rPalColor = rAcc.GetPaletteColor( i );
+
+ *pTmpEntry++ = rPalColor.GetBlue();
+ *pTmpEntry++ = rPalColor.GetGreen();
+ *pTmpEntry++ = rPalColor.GetRed();
+ *pTmpEntry++ = 0;
+ }
+
+ rOStm.WriteBytes( pEntries.get(), nPalSize );
+
+ return rOStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplWriteRLE( SvStream& rOStm, BitmapReadAccess const & rAcc, bool bRLE4 )
+{
+ const sal_uLong nWidth = rAcc.Width();
+ const sal_uLong nHeight = rAcc.Height();
+ sal_uLong nX;
+ sal_uLong nSaveIndex;
+ sal_uLong nCount;
+ sal_uLong nBufCount;
+ std::vector<sal_uInt8> aBuf(( nWidth << 1 ) + 2);
+ sal_uInt8 cPix;
+ sal_uInt8 cLast;
+ bool bFound;
+
+ for ( tools::Long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ nX = nBufCount = 0;
+ Scanline pScanline = rAcc.GetScanline( nY );
+
+ while( nX < nWidth )
+ {
+ nCount = 1;
+ cPix = rAcc.GetIndexFromData( pScanline, nX++ );
+
+ while( ( nX < nWidth ) && ( nCount < 255 )
+ && ( cPix == rAcc.GetIndexFromData( pScanline, nX ) ) )
+ {
+ nX++;
+ nCount++;
+ }
+
+ if ( nCount > 1 )
+ {
+ *pTmp++ = static_cast<sal_uInt8>(nCount);
+ *pTmp++ = ( bRLE4 ? ( ( cPix << 4 ) | cPix ) : cPix );
+ nBufCount += 2;
+ }
+ else
+ {
+ cLast = cPix;
+ nSaveIndex = nX - 1;
+ bFound = false;
+
+ while( ( nX < nWidth ) && ( nCount < 256 ) )
+ {
+ cPix = rAcc.GetIndexFromData( pScanline, nX );
+ if (cPix == cLast)
+ break;
+ nX++; nCount++;
+ cLast = cPix;
+ bFound = true;
+ }
+
+ if ( bFound )
+ nX--;
+
+ if ( nCount > 3 )
+ {
+ *pTmp++ = 0;
+ *pTmp++ = static_cast<sal_uInt8>(--nCount);
+
+ if( bRLE4 )
+ {
+ for ( sal_uLong i = 0; i < nCount; i++, pTmp++ )
+ {
+ *pTmp = rAcc.GetIndexFromData( pScanline, nSaveIndex++ ) << 4;
+
+ if ( ++i < nCount )
+ *pTmp |= rAcc.GetIndexFromData( pScanline, nSaveIndex++ );
+ }
+
+ nCount = ( nCount + 1 ) >> 1;
+ }
+ else
+ {
+ for( sal_uLong i = 0; i < nCount; i++ )
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, nSaveIndex++ );
+ }
+
+ if ( nCount & 1 )
+ {
+ *pTmp++ = 0;
+ nBufCount += ( nCount + 3 );
+ }
+ else
+ nBufCount += ( nCount + 2 );
+ }
+ else
+ {
+ *pTmp++ = 1;
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, nSaveIndex ) << (bRLE4 ? 4 : 0);
+
+ if ( nCount == 3 )
+ {
+ *pTmp++ = 1;
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, ++nSaveIndex ) << ( bRLE4 ? 4 : 0 );
+ nBufCount += 4;
+ }
+ else
+ nBufCount += 2;
+ }
+ }
+ }
+
+ aBuf[ nBufCount++ ] = 0;
+ aBuf[ nBufCount++ ] = 0;
+
+ rOStm.WriteBytes(aBuf.data(), nBufCount);
+ }
+
+ rOStm.WriteUChar( 0 );
+ rOStm.WriteUChar( 1 );
+
+ return rOStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplWriteDIBBits(SvStream& rOStm, BitmapReadAccess const & rAcc, BitmapReadAccess const * pAccAlpha, sal_uLong nCompression, sal_uInt32& rImageSize)
+{
+ if(!pAccAlpha && BITFIELDS == nCompression)
+ {
+ const ColorMask& rMask = rAcc.GetColorMask();
+ SVBT32 aVal32;
+
+ UInt32ToSVBT32( rMask.GetRedMask(), aVal32 );
+ rOStm.WriteBytes( aVal32, 4UL );
+
+ UInt32ToSVBT32( rMask.GetGreenMask(), aVal32 );
+ rOStm.WriteBytes( aVal32, 4UL );
+
+ UInt32ToSVBT32( rMask.GetBlueMask(), aVal32 );
+ rOStm.WriteBytes( aVal32, 4UL );
+
+ rImageSize = rOStm.Tell();
+
+ if( rAcc.IsBottomUp() )
+ rOStm.WriteBytes(rAcc.GetBuffer(), rAcc.Height() * rAcc.GetScanlineSize());
+ else
+ {
+ for( tools::Long nY = rAcc.Height() - 1, nScanlineSize = rAcc.GetScanlineSize(); nY >= 0; nY-- )
+ rOStm.WriteBytes( rAcc.GetScanline(nY), nScanlineSize );
+ }
+ }
+ else if(!pAccAlpha && ((RLE_4 == nCompression) || (RLE_8 == nCompression)))
+ {
+ rImageSize = rOStm.Tell();
+ ImplWriteRLE( rOStm, rAcc, RLE_4 == nCompression );
+ }
+ else if(!nCompression)
+ {
+ // #i5xxx# Limit bitcount to 24bit, the 32 bit cases are not
+ // handled properly below (would have to set color masks, and
+ // nCompression=BITFIELDS - but color mask is not set for
+ // formats != *_TC_*). Note that this very problem might cause
+ // trouble at other places - the introduction of 32 bit RGBA
+ // bitmaps is relatively recent.
+ // #i59239# discretize bitcount for aligned width to 1,8,24
+ // (other cases are not written below)
+ const auto ePixelFormat(pAccAlpha ? vcl::PixelFormat::N32_BPP : convertToBPP(rAcc.GetBitCount()));
+ const sal_uLong nAlignedWidth(AlignedWidth4Bytes(rAcc.Width() * sal_Int32(ePixelFormat)));
+ bool bNative(false);
+
+ switch(rAcc.GetScanlineFormat())
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N8BitPal:
+ case ScanlineFormat::N24BitTcBgr:
+ {
+ if(!pAccAlpha && rAcc.IsBottomUp() && (rAcc.GetScanlineSize() == nAlignedWidth))
+ {
+ bNative = true;
+ }
+
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+
+ rImageSize = rOStm.Tell();
+
+ if(bNative)
+ {
+ rOStm.WriteBytes(rAcc.GetBuffer(), nAlignedWidth * rAcc.Height());
+ }
+ else
+ {
+ const tools::Long nWidth(rAcc.Width());
+ const tools::Long nHeight(rAcc.Height());
+ std::vector<sal_uInt8> aBuf(nAlignedWidth);
+ switch(ePixelFormat)
+ {
+ case vcl::PixelFormat::N1_BPP:
+ {
+ //valgrind, zero out the trailing unused alignment bytes
+ size_t nUnusedBytes = nAlignedWidth - ((nWidth+7) / 8);
+ memset(aBuf.data() + nAlignedWidth - nUnusedBytes, 0, nUnusedBytes);
+
+ for( tools::Long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ sal_uInt8 cTmp = 0;
+ Scanline pScanline = rAcc.GetScanline( nY );
+
+ for( tools::Long nX = 0, nShift = 8; nX < nWidth; nX++ )
+ {
+ if( !nShift )
+ {
+ nShift = 8;
+ *pTmp++ = cTmp;
+ cTmp = 0;
+ }
+
+ cTmp |= rAcc.GetIndexFromData( pScanline, nX ) << --nShift;
+ }
+
+ *pTmp = cTmp;
+ rOStm.WriteBytes(aBuf.data(), nAlignedWidth);
+ }
+ }
+ break;
+
+ case vcl::PixelFormat::N8_BPP:
+ {
+ for( tools::Long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ Scanline pScanline = rAcc.GetScanline( nY );
+
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, nX );
+
+ rOStm.WriteBytes(aBuf.data(), nAlignedWidth);
+ }
+ }
+ break;
+
+ case vcl::PixelFormat::N24_BPP:
+ {
+ //valgrind, zero out the trailing unused alignment bytes
+ size_t nUnusedBytes = nAlignedWidth - nWidth * 3;
+ memset(aBuf.data() + nAlignedWidth - nUnusedBytes, 0, nUnusedBytes);
+ }
+ [[fallthrough]];
+ // #i59239# fallback to 24 bit format, if bitcount is non-default
+ default:
+ {
+ BitmapColor aPixelColor;
+ const bool bWriteAlpha(ePixelFormat == vcl::PixelFormat::N32_BPP && pAccAlpha);
+
+ for( tools::Long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ Scanline pScanlineAlpha = bWriteAlpha ? pAccAlpha->GetScanline( nY ) : nullptr;
+
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ // when alpha is used, this may be non-24bit main bitmap, so use GetColor
+ // instead of GetPixel to ensure RGB value
+ aPixelColor = rAcc.GetColor( nY, nX );
+
+ *pTmp++ = aPixelColor.GetBlue();
+ *pTmp++ = aPixelColor.GetGreen();
+ *pTmp++ = aPixelColor.GetRed();
+
+ if(bWriteAlpha)
+ {
+ *pTmp++ = sal_uInt8(0xff) - pAccAlpha->GetIndexFromData( pScanlineAlpha, nX );
+ }
+ }
+
+ rOStm.WriteBytes(aBuf.data(), nAlignedWidth);
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ rImageSize = rOStm.Tell() - rImageSize;
+
+ return (!rOStm.GetError());
+}
+
+bool ImplWriteDIBBody(const Bitmap& rBitmap, SvStream& rOStm, BitmapReadAccess const & rAcc, BitmapReadAccess const * pAccAlpha, bool bCompressed)
+{
+ const MapMode aMapPixel(MapUnit::MapPixel);
+ DIBV5Header aHeader;
+ sal_uInt64 nImageSizePos(0);
+ sal_uInt64 nEndPos(0);
+ sal_uInt32 nCompression(COMPRESS_NONE);
+ bool bRet(false);
+
+ aHeader.nSize = pAccAlpha ? DIBV5HEADERSIZE : DIBINFOHEADERSIZE; // size dependent on CF_DIB type to use
+ aHeader.nWidth = rAcc.Width();
+ aHeader.nHeight = rAcc.Height();
+ aHeader.nPlanes = 1;
+
+ if(!pAccAlpha && isBitfieldCompression(rAcc.GetScanlineFormat()))
+ {
+ aHeader.nBitCount = 32;
+ aHeader.nSizeImage = rAcc.Height() * rAcc.GetScanlineSize();
+ nCompression = BITFIELDS;
+ }
+ else
+ {
+ // #i5xxx# Limit bitcount to 24bit, the 32 bit cases are
+ // not handled properly below (would have to set color
+ // masks, and nCompression=BITFIELDS - but color mask is
+ // not set for formats != *_TC_*). Note that this very
+ // problem might cause trouble at other places - the
+ // introduction of 32 bit RGBA bitmaps is relatively
+ // recent.
+ // #i59239# discretize bitcount to 1,8,24 (other cases
+ // are not written below)
+ const auto ePixelFormat(pAccAlpha ? vcl::PixelFormat::N32_BPP : convertToBPP(rAcc.GetBitCount()));
+ aHeader.nBitCount = sal_uInt16(ePixelFormat);
+ aHeader.nSizeImage = rAcc.Height() * AlignedWidth4Bytes(rAcc.Width() * aHeader.nBitCount);
+
+ if (bCompressed)
+ {
+ if (ePixelFormat == vcl::PixelFormat::N8_BPP)
+ nCompression = RLE_8;
+ }
+ }
+
+ if((rOStm.GetCompressMode() & SvStreamCompressFlags::ZBITMAP) && (rOStm.GetVersion() >= SOFFICE_FILEFORMAT_40))
+ {
+ aHeader.nCompression = ZCOMPRESS;
+ }
+ else
+ {
+ aHeader.nCompression = nCompression;
+ }
+
+ if(rBitmap.GetPrefSize().Width() && rBitmap.GetPrefSize().Height() && (rBitmap.GetPrefMapMode() != aMapPixel))
+ {
+ // #i48108# Try to recover xpels/ypels as previously stored on
+ // disk. The problem with just converting maPrefSize to 100th
+ // mm and then relating that to the bitmap pixel size is that
+ // MapMode is integer-based, and suffers from roundoffs,
+ // especially if maPrefSize is small. Trying to circumvent
+ // that by performing part of the math in floating point.
+ const Size aScale100000(OutputDevice::LogicToLogic(Size(100000, 100000), MapMode(MapUnit::Map100thMM), rBitmap.GetPrefMapMode()));
+ const double fBmpWidthM(static_cast<double>(rBitmap.GetPrefSize().Width()) / aScale100000.Width());
+ const double fBmpHeightM(static_cast<double>(rBitmap.GetPrefSize().Height()) / aScale100000.Height());
+
+ if(!basegfx::fTools::equalZero(fBmpWidthM) && !basegfx::fTools::equalZero(fBmpHeightM))
+ {
+ aHeader.nXPelsPerMeter = basegfx::fround(rAcc.Width() / fabs(fBmpWidthM));
+ aHeader.nYPelsPerMeter = basegfx::fround(rAcc.Height() / fabs(fBmpHeightM));
+ }
+ }
+
+ aHeader.nColsUsed = ((!pAccAlpha && aHeader.nBitCount <= 8) ? rAcc.GetPaletteEntryCount() : 0);
+ aHeader.nColsImportant = 0;
+
+ rOStm.WriteUInt32( aHeader.nSize );
+ rOStm.WriteInt32( aHeader.nWidth );
+ rOStm.WriteInt32( aHeader.nHeight );
+ rOStm.WriteUInt16( aHeader.nPlanes );
+ rOStm.WriteUInt16( aHeader.nBitCount );
+ rOStm.WriteUInt32( aHeader.nCompression );
+
+ nImageSizePos = rOStm.Tell();
+ rOStm.SeekRel( sizeof( aHeader.nSizeImage ) );
+
+ rOStm.WriteInt32( aHeader.nXPelsPerMeter );
+ rOStm.WriteInt32( aHeader.nYPelsPerMeter );
+ rOStm.WriteUInt32( aHeader.nColsUsed );
+ rOStm.WriteUInt32( aHeader.nColsImportant );
+
+ if(pAccAlpha) // only write DIBV5 when asked to do so
+ {
+ aHeader.nV5CSType = 0x57696E20; // LCS_WINDOWS_COLOR_SPACE
+ aHeader.nV5Intent = 0x00000004; // LCS_GM_IMAGES
+
+ rOStm.WriteUInt32( aHeader.nV5RedMask );
+ rOStm.WriteUInt32( aHeader.nV5GreenMask );
+ rOStm.WriteUInt32( aHeader.nV5BlueMask );
+ rOStm.WriteUInt32( aHeader.nV5AlphaMask );
+ rOStm.WriteUInt32( aHeader.nV5CSType );
+
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzRed.aXyzX );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzRed.aXyzY );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzRed.aXyzZ );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzGreen.aXyzX );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzGreen.aXyzY );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzGreen.aXyzZ );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzBlue.aXyzX );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzBlue.aXyzY );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzBlue.aXyzZ );
+
+ rOStm.WriteUInt32( aHeader.nV5GammaRed );
+ rOStm.WriteUInt32( aHeader.nV5GammaGreen );
+ rOStm.WriteUInt32( aHeader.nV5GammaBlue );
+ rOStm.WriteUInt32( aHeader.nV5Intent );
+ rOStm.WriteUInt32( aHeader.nV5ProfileData );
+ rOStm.WriteUInt32( aHeader.nV5ProfileSize );
+ rOStm.WriteUInt32( aHeader.nV5Reserved );
+ }
+
+ if(ZCOMPRESS == aHeader.nCompression)
+ {
+ ZCodec aCodec;
+ SvMemoryStream aMemStm(aHeader.nSizeImage + 4096, 65535);
+ sal_uInt64 nCodedPos(rOStm.Tell());
+ sal_uInt64 nLastPos(0);
+ sal_uInt32 nCodedSize(0);
+ sal_uInt32 nUncodedSize(0);
+
+ // write uncoded data palette
+ if(aHeader.nColsUsed)
+ {
+ ImplWriteDIBPalette(aMemStm, rAcc);
+ }
+
+ // write uncoded bits
+ bRet = ImplWriteDIBBits(aMemStm, rAcc, pAccAlpha, nCompression, aHeader.nSizeImage);
+
+ // get uncoded size
+ nUncodedSize = aMemStm.Tell();
+
+ // seek over compress info
+ rOStm.SeekRel(12);
+
+ // write compressed data
+ aCodec.BeginCompression(3);
+ aCodec.Write(rOStm, static_cast<sal_uInt8 const *>(aMemStm.GetData()), nUncodedSize);
+ aCodec.EndCompression();
+
+ // update compress info ( coded size, uncoded size, uncoded compression )
+ nLastPos = rOStm.Tell();
+ nCodedSize = nLastPos - nCodedPos - 12;
+ rOStm.Seek(nCodedPos);
+ rOStm.WriteUInt32( nCodedSize ).WriteUInt32( nUncodedSize ).WriteUInt32( nCompression );
+ rOStm.Seek(nLastPos);
+
+ if(bRet)
+ {
+ bRet = (ERRCODE_NONE == rOStm.GetError());
+ }
+ }
+ else
+ {
+ if(aHeader.nColsUsed)
+ {
+ ImplWriteDIBPalette(rOStm, rAcc);
+ }
+
+ bRet = ImplWriteDIBBits(rOStm, rAcc, pAccAlpha, aHeader.nCompression, aHeader.nSizeImage);
+ }
+
+ nEndPos = rOStm.Tell();
+ rOStm.Seek(nImageSizePos);
+ rOStm.WriteUInt32( aHeader.nSizeImage );
+ rOStm.Seek(nEndPos);
+
+ return bRet;
+}
+
+bool ImplWriteDIBFileHeader(SvStream& rOStm, BitmapReadAccess const & rAcc)
+{
+ const sal_uInt32 nPalCount((rAcc.HasPalette() ? rAcc.GetPaletteEntryCount() : isBitfieldCompression(rAcc.GetScanlineFormat()) ? 3UL : 0UL));
+ const sal_uInt32 nOffset(14 + DIBINFOHEADERSIZE + nPalCount * 4UL);
+
+ rOStm.WriteUInt16( 0x4D42 ); // 'MB' from BITMAPFILEHEADER
+ rOStm.WriteUInt32( nOffset + (rAcc.Height() * rAcc.GetScanlineSize()) );
+ rOStm.WriteUInt16( 0 );
+ rOStm.WriteUInt16( 0 );
+ rOStm.WriteUInt32( nOffset );
+
+ return rOStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplReadDIB(
+ Bitmap& rTarget,
+ AlphaMask* pTargetAlpha,
+ SvStream& rIStm,
+ bool bFileHeader,
+ bool bIsMask=false,
+ bool bMSOFormat=false)
+{
+ const SvStreamEndian nOldFormat(rIStm.GetEndian());
+ const auto nOldPos(rIStm.Tell());
+ sal_uLong nOffset(0);
+ bool bRet(false);
+
+ rIStm.SetEndian(SvStreamEndian::LITTLE);
+
+ if(bFileHeader)
+ {
+ if(ImplReadDIBFileHeader(rIStm, nOffset))
+ {
+ bRet = ImplReadDIBBody(rIStm, rTarget, nOffset >= DIBV5HEADERSIZE ? pTargetAlpha : nullptr, nOffset, bIsMask, bMSOFormat);
+ }
+ }
+ else
+ {
+ bRet = ImplReadDIBBody(rIStm, rTarget, nullptr, nOffset, bIsMask, bMSOFormat);
+ }
+
+ if(!bRet)
+ {
+ if(!rIStm.GetError()) // Set error and stop processing whole stream due to security reason
+ {
+ rIStm.SetError(SVSTREAM_GENERALERROR);
+ }
+
+ rIStm.Seek(nOldPos);
+ }
+
+ rIStm.SetEndian(nOldFormat);
+
+ return bRet;
+}
+
+bool ImplWriteDIB(
+ const Bitmap& rSource,
+ SvStream& rOStm,
+ bool bCompressed,
+ bool bFileHeader)
+{
+ const Size aSizePix(rSource.GetSizePixel());
+ bool bRet(false);
+
+ if(aSizePix.Width() && aSizePix.Height())
+ {
+ Bitmap::ScopedReadAccess pAcc(const_cast< Bitmap& >(rSource));
+ Bitmap::ScopedReadAccess pAccAlpha;
+ const SvStreamEndian nOldFormat(rOStm.GetEndian());
+ const sal_uInt64 nOldPos(rOStm.Tell());
+
+ rOStm.SetEndian(SvStreamEndian::LITTLE);
+
+ if (pAcc)
+ {
+ if(bFileHeader)
+ {
+ if(ImplWriteDIBFileHeader(rOStm, *pAcc))
+ {
+ bRet = ImplWriteDIBBody(rSource, rOStm, *pAcc, pAccAlpha.get(), bCompressed);
+ }
+ }
+ else
+ {
+ bRet = ImplWriteDIBBody(rSource, rOStm, *pAcc, pAccAlpha.get(), bCompressed);
+ }
+
+ pAcc.reset();
+ }
+
+ pAccAlpha.reset();
+
+ if(!bRet)
+ {
+ rOStm.SetError(SVSTREAM_GENERALERROR);
+ rOStm.Seek(nOldPos);
+ }
+
+ rOStm.SetEndian(nOldFormat);
+ }
+
+ return bRet;
+}
+
+} // unnamed namespace
+
+bool ReadDIB(
+ Bitmap& rTarget,
+ SvStream& rIStm,
+ bool bFileHeader,
+ bool bMSOFormat)
+{
+ return ImplReadDIB(rTarget, nullptr, rIStm, bFileHeader, false, bMSOFormat);
+}
+
+bool ReadDIBBitmapEx(
+ BitmapEx& rTarget,
+ SvStream& rIStm,
+ bool bFileHeader,
+ bool bMSOFormat)
+{
+ Bitmap aBmp;
+ bool bRetval(ImplReadDIB(aBmp, nullptr, rIStm, bFileHeader, /*bMask*/false, bMSOFormat) && !rIStm.GetError());
+
+ if(bRetval)
+ {
+ // base bitmap was read, set as return value and try to read alpha extra-data
+ const sal_uInt64 nStmPos(rIStm.Tell());
+ sal_uInt32 nMagic1(0);
+ sal_uInt32 nMagic2(0);
+
+ rTarget = BitmapEx(aBmp);
+ if (rIStm.remainingSize() >= 4)
+ rIStm.ReadUInt32( nMagic1 ).ReadUInt32( nMagic2 );
+ bRetval = (0x25091962 == nMagic1) && (0xACB20201 == nMagic2) && !rIStm.GetError();
+
+ if(bRetval)
+ {
+ sal_uInt8 tmp = 0;
+ rIStm.ReadUChar( tmp );
+ bRetval = !rIStm.GetError();
+
+ if(bRetval)
+ {
+ switch (tmp)
+ {
+ case 2: // TransparentType::Bitmap
+ {
+ Bitmap aMask;
+
+ bRetval = ImplReadDIB(aMask, nullptr, rIStm, true, true);
+
+ if(bRetval)
+ {
+ if(!aMask.IsEmpty())
+ {
+ // do we have an alpha mask?
+ if (aMask.getPixelFormat() == vcl::PixelFormat::N8_BPP && aMask.HasGreyPalette8Bit())
+ {
+ AlphaMask aAlpha;
+
+ // create alpha mask quickly (without greyscale conversion)
+ aAlpha.ImplSetBitmap(aMask);
+ rTarget = BitmapEx(aBmp, aAlpha);
+ }
+ else
+ {
+ rTarget = BitmapEx(aBmp, aMask);
+ }
+ }
+ }
+ break;
+ }
+ case 1: // backwards compat for old option TransparentType::Color
+ {
+ Color aTransparentColor;
+
+ tools::GenericTypeSerializer aSerializer(rIStm);
+ aSerializer.readColor(aTransparentColor);
+
+ bRetval = rIStm.good();
+
+ if(bRetval)
+ {
+ rTarget = BitmapEx(aBmp, aTransparentColor);
+ }
+ break;
+ }
+ default: break;
+ }
+ }
+ }
+
+ if(!bRetval)
+ {
+ // alpha extra data could not be read; reset, but use base bitmap as result
+ rIStm.ResetError();
+ rIStm.Seek(nStmPos);
+ bRetval = true;
+ }
+ }
+
+ return bRetval;
+}
+
+bool ReadDIBV5(
+ Bitmap& rTarget,
+ AlphaMask& rTargetAlpha,
+ SvStream& rIStm)
+{
+ return ImplReadDIB(rTarget, &rTargetAlpha, rIStm, true);
+}
+
+bool ReadRawDIB(
+ BitmapEx& rTarget,
+ const unsigned char* pBuf,
+ const ScanlineFormat nFormat,
+ const int nHeight,
+ const int nStride)
+{
+ BitmapScopedWriteAccess pWriteAccess(rTarget.maBitmap.AcquireWriteAccess(), rTarget.maBitmap);
+ for (int nRow = 0; nRow < nHeight; ++nRow)
+ {
+ pWriteAccess->CopyScanline(nRow, pBuf + (nStride * nRow), nFormat, nStride);
+ }
+
+ return true;
+}
+
+bool WriteDIB(
+ const Bitmap& rSource,
+ SvStream& rOStm,
+ bool bCompressed,
+ bool bFileHeader)
+{
+ return ImplWriteDIB(rSource, rOStm, bCompressed, bFileHeader);
+}
+
+bool WriteDIB(
+ const BitmapEx& rSource,
+ SvStream& rOStm,
+ bool bCompressed)
+{
+ return ImplWriteDIB(rSource.GetBitmap(), rOStm, bCompressed, /*bFileHeader*/true);
+}
+
+bool WriteDIBBitmapEx(
+ const BitmapEx& rSource,
+ SvStream& rOStm)
+{
+ if(ImplWriteDIB(rSource.GetBitmap(), rOStm, true, true))
+ {
+ rOStm.WriteUInt32( 0x25091962 );
+ rOStm.WriteUInt32( 0xACB20201 );
+ rOStm.WriteUChar( rSource.IsAlpha() ? 2 : 0 ); // Used to be TransparentType enum
+
+ if(rSource.IsAlpha())
+ {
+ return ImplWriteDIB(rSource.maAlphaMask, rOStm, true, true);
+ }
+ }
+
+ return false;
+}
+
+sal_uInt32 getDIBV5HeaderSize()
+{
+ return DIBV5HEADERSIZE;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/floyd.hxx b/vcl/source/bitmap/floyd.hxx
new file mode 100644
index 000000000..0617786a3
--- /dev/null
+++ b/vcl/source/bitmap/floyd.hxx
@@ -0,0 +1,197 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 .
+ */
+
+#pragma once
+
+#define CALC_ERRORS \
+ nTemp = p1T[nX++] >> 12; \
+ nBErr = MinMax( nTemp, 0, 255 ); \
+ nBErr = nBErr - FloydIndexMap[ nBC = FloydMap[nBErr] ]; \
+ nTemp = p1T[nX++] >> 12; \
+ nGErr = MinMax( nTemp, 0, 255 ); \
+ nGErr = nGErr - FloydIndexMap[ nGC = FloydMap[nGErr] ]; \
+ nTemp = p1T[nX] >> 12; \
+ nRErr = MinMax( nTemp, 0, 255 ); \
+ nRErr = nRErr - FloydIndexMap[ nRC = FloydMap[nRErr] ];
+
+#define CALC_TABLES3 \
+ p2T[nX++] += FloydError3[nBErr]; \
+ p2T[nX++] += FloydError3[nGErr]; \
+ p2T[nX++] += FloydError3[nRErr];
+
+#define CALC_TABLES5 \
+ p2T[nX++] += FloydError5[nBErr]; \
+ p2T[nX++] += FloydError5[nGErr]; \
+ p2T[nX++] += FloydError5[nRErr];
+
+#define CALC_TABLES7 \
+ p1T[++nX] += FloydError7[nBErr]; \
+ p2T[nX++] += FloydError1[nBErr]; \
+ p1T[nX] += FloydError7[nGErr]; \
+ p2T[nX++] += FloydError1[nGErr]; \
+ p1T[nX] += FloydError7[nRErr]; \
+ p2T[nX] += FloydError1[nRErr];
+
+const extern sal_uLong nVCLRLut[ 6 ] = { 16, 17, 18, 19, 20, 21 };
+const extern sal_uLong nVCLGLut[ 6 ] = { 0, 6, 12, 18, 24, 30 };
+const extern sal_uLong nVCLBLut[ 6 ] = { 0, 36, 72, 108, 144, 180 };
+
+const extern sal_uLong nVCLDitherLut[ 256 ] =
+{
+ 0, 49152, 12288, 61440, 3072, 52224, 15360, 64512, 768, 49920, 13056,
+ 62208, 3840, 52992, 16128, 65280, 32768, 16384, 45056, 28672, 35840, 19456,
+ 48128, 31744, 33536, 17152, 45824, 29440, 36608, 20224, 48896, 32512, 8192,
+ 57344, 4096, 53248, 11264, 60416, 7168, 56320, 8960, 58112, 4864, 54016,
+ 12032, 61184, 7936, 57088, 40960, 24576, 36864, 20480, 44032, 27648, 39936,
+ 23552, 41728, 25344, 37632, 21248, 44800, 28416, 40704, 24320, 2048, 51200,
+ 14336, 63488, 1024, 50176, 13312, 62464, 2816, 51968, 15104, 64256, 1792,
+ 50944, 14080, 63232, 34816, 18432, 47104, 30720, 33792, 17408, 46080, 29696,
+ 35584, 19200, 47872, 31488, 34560, 18176, 46848, 30464, 10240, 59392, 6144,
+ 55296, 9216, 58368, 5120, 54272, 11008, 60160, 6912, 56064, 9984, 59136,
+ 5888, 55040, 43008, 26624, 38912, 22528, 41984, 25600, 37888, 21504, 43776,
+ 27392, 39680, 23296, 42752, 26368, 38656, 22272, 512, 49664, 12800, 61952,
+ 3584, 52736, 15872, 65024, 256, 49408, 12544, 61696, 3328, 52480, 15616,
+ 64768, 33280, 16896, 45568, 29184, 36352, 19968, 48640, 32256, 33024, 16640,
+ 45312, 28928, 36096, 19712, 48384, 32000, 8704, 57856, 4608, 53760, 11776,
+ 60928, 7680, 56832, 8448, 57600, 4352, 53504, 11520, 60672, 7424, 56576,
+ 41472, 25088, 37376, 20992, 44544, 28160, 40448, 24064, 41216, 24832, 37120,
+ 20736, 44288, 27904, 40192, 23808, 2560, 51712, 14848, 64000, 1536, 50688,
+ 13824, 62976, 2304, 51456, 14592, 63744, 1280, 50432, 13568, 62720, 35328,
+ 18944, 47616, 31232, 34304, 17920, 46592, 30208, 35072, 18688, 47360, 30976,
+ 34048, 17664, 46336, 29952, 10752, 59904, 6656, 55808, 9728, 58880, 5632,
+ 54784, 10496, 59648, 6400, 55552, 9472, 58624, 5376, 54528, 43520, 27136,
+ 39424, 23040, 42496, 26112, 38400, 22016, 43264, 26880, 39168, 22784, 42240,
+ 25856, 38144, 21760
+};
+
+const extern sal_uLong nVCLLut[ 256 ] =
+{
+ 0, 1286, 2572, 3858, 5144, 6430, 7716, 9002,
+ 10288, 11574, 12860, 14146, 15432, 16718, 18004, 19290,
+ 20576, 21862, 23148, 24434, 25720, 27006, 28292, 29578,
+ 30864, 32150, 33436, 34722, 36008, 37294, 38580, 39866,
+ 41152, 42438, 43724, 45010, 46296, 47582, 48868, 50154,
+ 51440, 52726, 54012, 55298, 56584, 57870, 59156, 60442,
+ 61728, 63014, 64300, 65586, 66872, 68158, 69444, 70730,
+ 72016, 73302, 74588, 75874, 77160, 78446, 79732, 81018,
+ 82304, 83590, 84876, 86162, 87448, 88734, 90020, 91306,
+ 92592, 93878, 95164, 96450, 97736, 99022,100308,101594,
+ 102880,104166,105452,106738,108024,109310,110596,111882,
+ 113168,114454,115740,117026,118312,119598,120884,122170,
+ 123456,124742,126028,127314,128600,129886,131172,132458,
+ 133744,135030,136316,137602,138888,140174,141460,142746,
+ 144032,145318,146604,147890,149176,150462,151748,153034,
+ 154320,155606,156892,158178,159464,160750,162036,163322,
+ 164608,165894,167180,168466,169752,171038,172324,173610,
+ 174896,176182,177468,178754,180040,181326,182612,183898,
+ 185184,186470,187756,189042,190328,191614,192900,194186,
+ 195472,196758,198044,199330,200616,201902,203188,204474,
+ 205760,207046,208332,209618,210904,212190,213476,214762,
+ 216048,217334,218620,219906,221192,222478,223764,225050,
+ 226336,227622,228908,230194,231480,232766,234052,235338,
+ 236624,237910,239196,240482,241768,243054,244340,245626,
+ 246912,248198,249484,250770,252056,253342,254628,255914,
+ 257200,258486,259772,261058,262344,263630,264916,266202,
+ 267488,268774,270060,271346,272632,273918,275204,276490,
+ 277776,279062,280348,281634,282920,284206,285492,286778,
+ 288064,289350,290636,291922,293208,294494,295780,297066,
+ 298352,299638,300924,302210,303496,304782,306068,307354,
+ 308640,309926,311212,312498,313784,315070,316356,317642,
+ 318928,320214,321500,322786,324072,325358,326644,327930
+};
+
+const tools::Long FloydMap[256] =
+{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+};
+
+const tools::Long FloydError1[61] =
+{
+ -7680, -7424, -7168, -6912, -6656, -6400, -6144,
+ -5888, -5632, -5376, -5120, -4864, -4608, -4352,
+ -4096, -3840, -3584, -3328, -3072, -2816, -2560,
+ -2304, -2048, -1792, -1536, -1280, -1024, -768,
+ -512, -256, 0, 256, 512, 768, 1024, 1280, 1536,
+ 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584,
+ 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632,
+ 5888, 6144, 6400, 6656, 6912, 7168, 7424, 7680
+};
+
+const tools::Long FloydError3[61] =
+{
+ -23040, -22272, -21504, -20736, -19968, -19200,
+ -18432, -17664, -16896, -16128, -15360, -14592,
+ -13824, -13056, -12288, -11520, -10752, -9984,
+ -9216, -8448, -7680, -6912, -6144, -5376, -4608,
+ -3840, -3072, -2304, -1536, -768, 0, 768, 1536,
+ 2304, 3072, 3840, 4608, 5376, 6144, 6912, 7680,
+ 8448, 9216, 9984, 10752, 11520, 12288, 13056,
+ 13824, 14592, 15360, 16128, 16896, 17664, 18432,
+ 19200, 19968, 20736, 21504, 22272, 23040
+};
+
+const tools::Long FloydError5[61] =
+{
+ -38400, -37120, -35840, -34560, -33280, -32000,
+ -30720, -29440, -28160, -26880, -25600, -24320,
+ -23040, -21760, -20480, -19200, -17920, -16640,
+ -15360, -14080, -12800, -11520, -10240, -8960,
+ -7680, -6400, -5120, -3840, -2560, -1280, 0,
+ 1280, 2560, 3840, 5120, 6400, 7680, 8960, 10240,
+ 11520, 12800, 14080, 15360, 16640, 17920, 19200,
+ 20480, 21760, 23040, 24320, 25600, 26880, 28160,
+ 29440, 30720, 32000, 33280, 34560, 35840, 37120,
+ 38400
+};
+
+const tools::Long FloydError7[61] =
+{
+ -53760, -51968, -50176, -48384, -46592, -44800,
+ -43008, -41216, -39424, -37632, -35840, -34048,
+ -32256, -30464, -28672, -26880, -25088, -23296,
+ -21504, -19712, -17920, -16128, -14336, -12544,
+ -10752, -8960, -7168, -5376, -3584, -1792, 0,
+ 1792, 3584, 5376, 7168, 8960, 10752, 12544, 14336,
+ 16128, 17920, 19712, 21504, 23296, 25088, 26880,
+ 28672, 30464, 32256, 34048, 35840, 37632, 39424,
+ 41216, 43008, 44800, 46592, 48384, 50176, 51968,
+ 53760
+};
+
+const tools::Long FloydIndexMap[6] =
+{
+ -30, 21, 72, 123, 174, 225
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/bitmap/impvect.cxx b/vcl/source/bitmap/impvect.cxx
new file mode 100644
index 000000000..f41c3f72c
--- /dev/null
+++ b/vcl/source/bitmap/impvect.cxx
@@ -0,0 +1,997 @@
+/* -*- 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/log.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <tools/link.hxx>
+#include <tools/poly.hxx>
+#include <tools/helpers.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include "impvect.hxx"
+#include <array>
+#include <memory>
+
+#define VECT_POLY_MAX 8192
+
+#define VECT_FREE_INDEX 0
+#define VECT_CONT_INDEX 1
+#define VECT_DONE_INDEX 2
+
+#define VECT_POLY_INLINE_INNER 1UL
+#define VECT_POLY_INLINE_OUTER 2UL
+#define VECT_POLY_OUTLINE_INNER 4UL
+#define VECT_POLY_OUTLINE_OUTER 8UL
+
+static void VECT_MAP( const std::unique_ptr<sal_Int32 []> & pMapIn, const std::unique_ptr<sal_Int32 []>& pMapOut, tools::Long nVal )
+{
+ pMapIn[nVal] = (nVal * 4) + 1;
+ pMapOut[nVal] = pMapIn[nVal] + 5;
+}
+static constexpr tools::Long BACK_MAP( tools::Long _def_nVal )
+{
+ return ((_def_nVal + 2) >> 2) - 1;
+}
+static void VECT_PROGRESS( const Link<tools::Long, void>* pProgress, tools::Long _def_nVal )
+{
+ if(pProgress)
+ pProgress->Call(_def_nVal);
+}
+
+namespace {
+
+class ImplVectMap;
+class ImplChain;
+
+}
+
+namespace ImplVectorizer
+{
+ static void ImplExpand( std::optional<ImplVectMap>& rMap, const BitmapReadAccess* pRAcc, const Color& rColor );
+ static void ImplCalculate( ImplVectMap& rMap, tools::PolyPolygon& rPolyPoly, sal_uInt8 cReduce );
+ static bool ImplGetChain( ImplVectMap& rMap, const Point& rStartPt, ImplChain& rChain );
+ static bool ImplIsUp( ImplVectMap const & rMap, tools::Long nY, tools::Long nX );
+ static void ImplLimitPolyPoly( tools::PolyPolygon& rPolyPoly );
+}
+
+namespace {
+
+struct ChainMove { tools::Long nDX; tools::Long nDY; };
+
+}
+
+const ChainMove aImplMove[ 8 ] = {
+ { 1, 0 },
+ { 0, -1 },
+ { -1, 0 },
+ { 0, 1 },
+ { 1, -1 },
+ { -1, -1 },
+ { -1, 1 },
+ { 1, 1 }
+ };
+
+const ChainMove aImplMoveInner[ 8 ] = {
+ { 0, 1 },
+ { 1, 0 },
+ { 0, -1 },
+ { -1, 0 },
+ { 0, 1 },
+ { 1, 0 },
+ { 0, -1 },
+ { -1, 0 }
+ };
+
+const ChainMove aImplMoveOuter[ 8 ] = {
+ { 0, -1 },
+ { -1, 0 },
+ { 0, 1 },
+ { 1, 0 },
+ { -1, 0 },
+ { 0, 1 },
+ { 1, 0 },
+ { 0, -1 }
+ };
+
+namespace {
+
+struct ImplColorSet
+{
+ BitmapColor maColor;
+ sal_uInt16 mnIndex = 0;
+ bool mbSet = false;
+};
+
+}
+
+static bool ImplColorSetCmpFnc( const ImplColorSet& lhs, const ImplColorSet& rhs)
+{
+ if( lhs.mbSet && rhs.mbSet )
+ {
+ const sal_uInt8 cLum1 = lhs.maColor.GetLuminance();
+ const sal_uInt8 cLum2 = rhs.maColor.GetLuminance();
+ return cLum1 < cLum2;
+ }
+ return lhs.mbSet > rhs.mbSet;
+}
+
+namespace {
+
+class ImplPointArray
+{
+ std::unique_ptr<Point[]> mpArray;
+ sal_uLong mnSize;
+ sal_uLong mnRealSize;
+
+public:
+
+ ImplPointArray();
+
+ void ImplSetSize( sal_uLong nSize );
+ sal_uLong ImplGetRealSize() const { return mnRealSize; }
+ void ImplSetRealSize( sal_uLong nRealSize ) { mnRealSize = nRealSize; }
+ void ImplCreatePoly( tools::Polygon& rPoly ) const;
+
+ inline Point& operator[]( sal_uLong nPos );
+ inline const Point& operator[]( sal_uLong nPos ) const;
+
+};
+
+}
+
+ImplPointArray::ImplPointArray() :
+ mnSize ( 0 ),
+ mnRealSize ( 0 )
+
+{
+}
+
+void ImplPointArray::ImplSetSize( sal_uLong nSize )
+{
+ const sal_uLong nTotal = nSize * sizeof( Point );
+
+ mnSize = nSize;
+ mnRealSize = 0;
+
+ mpArray = std::make_unique<Point[]>( nTotal );
+}
+
+inline Point& ImplPointArray::operator[]( sal_uLong nPos )
+{
+ SAL_WARN_IF( nPos >= mnSize, "vcl", "ImplPointArray::operator[]: nPos out of range!" );
+ return mpArray[ nPos ];
+}
+
+inline const Point& ImplPointArray::operator[]( sal_uLong nPos ) const
+{
+ SAL_WARN_IF( nPos >= mnSize, "vcl", "ImplPointArray::operator[]: nPos out of range!" );
+ return mpArray[ nPos ];
+}
+
+void ImplPointArray::ImplCreatePoly( tools::Polygon& rPoly ) const
+{
+ rPoly = tools::Polygon( sal::static_int_cast<sal_uInt16>(mnRealSize), mpArray.get() );
+}
+
+namespace {
+
+class ImplVectMap
+{
+private:
+
+ Scanline mpBuf;
+ Scanline* mpScan;
+ tools::Long mnWidth;
+ tools::Long mnHeight;
+
+public:
+
+ ImplVectMap( tools::Long nWidth, tools::Long nHeight );
+ ~ImplVectMap();
+
+ tools::Long Width() const { return mnWidth; }
+ tools::Long Height() const { return mnHeight; }
+
+ inline void Set( tools::Long nY, tools::Long nX, sal_uInt8 cVal );
+ inline sal_uInt8 Get( tools::Long nY, tools::Long nX ) const;
+
+ inline bool IsFree( tools::Long nY, tools::Long nX ) const;
+ inline bool IsCont( tools::Long nY, tools::Long nX ) const;
+ inline bool IsDone( tools::Long nY, tools::Long nX ) const;
+
+};
+
+}
+
+ImplVectMap::ImplVectMap( tools::Long nWidth, tools::Long nHeight ) :
+ mpBuf ( static_cast<Scanline>(rtl_allocateZeroMemory(nWidth * nHeight)) ),
+ mpScan ( static_cast<Scanline*>(std::malloc(nHeight * sizeof(Scanline))) ),
+ mnWidth ( nWidth ),
+ mnHeight( nHeight )
+{
+ const tools::Long nWidthAl = ( nWidth >> 2 ) + 1;
+ Scanline pTmp = mpBuf;
+
+ for( tools::Long nY = 0; nY < nHeight; pTmp += nWidthAl )
+ mpScan[ nY++ ] = pTmp;
+}
+
+ImplVectMap::~ImplVectMap()
+{
+ std::free( mpBuf );
+ std::free( mpScan );
+}
+
+inline void ImplVectMap::Set( tools::Long nY, tools::Long nX, sal_uInt8 cVal )
+{
+ const sal_uInt8 cShift = sal::static_int_cast<sal_uInt8>(6 - ( ( nX & 3 ) << 1 ));
+ auto & rPixel = mpScan[ nY ][ nX >> 2 ];
+ rPixel = (rPixel & ~( 3 << cShift ) ) | ( cVal << cShift );
+}
+
+inline sal_uInt8 ImplVectMap::Get( tools::Long nY, tools::Long nX ) const
+{
+ return sal::static_int_cast<sal_uInt8>( ( ( mpScan[ nY ][ nX >> 2 ] ) >> ( 6 - ( ( nX & 3 ) << 1 ) ) ) & 3 );
+}
+
+inline bool ImplVectMap::IsFree( tools::Long nY, tools::Long nX ) const
+{
+ return( VECT_FREE_INDEX == Get( nY, nX ) );
+}
+
+inline bool ImplVectMap::IsCont( tools::Long nY, tools::Long nX ) const
+{
+ return( VECT_CONT_INDEX == Get( nY, nX ) );
+}
+
+inline bool ImplVectMap::IsDone( tools::Long nY, tools::Long nX ) const
+{
+ return( VECT_DONE_INDEX == Get( nY, nX ) );
+}
+
+namespace {
+
+class ImplChain
+{
+private:
+
+ tools::Polygon maPoly;
+ Point maStartPt;
+ sal_uLong mnArraySize;
+ sal_uLong mnCount;
+ std::unique_ptr<sal_uInt8[]>
+ mpCodes;
+
+ void ImplGetSpace();
+
+ void ImplPostProcess( const ImplPointArray& rArr );
+
+ ImplChain(const ImplChain&) = delete;
+ ImplChain& operator=(const ImplChain&) = delete;
+
+public:
+
+ ImplChain();
+
+ void ImplBeginAdd( const Point& rStartPt );
+ inline void ImplAdd( sal_uInt8 nCode );
+ void ImplEndAdd( sal_uLong nTypeFlag );
+
+ const tools::Polygon& ImplGetPoly() const { return maPoly; }
+};
+
+}
+
+ImplChain::ImplChain() :
+ mnArraySize ( 1024 ),
+ mnCount ( 0 ),
+ mpCodes ( new sal_uInt8[mnArraySize] )
+{
+}
+
+void ImplChain::ImplGetSpace()
+{
+ const sal_uLong nOldArraySize = mnArraySize;
+ sal_uInt8* pNewCodes;
+
+ mnArraySize = mnArraySize << 1;
+ pNewCodes = new sal_uInt8[ mnArraySize ];
+ memcpy( pNewCodes, mpCodes.get(), nOldArraySize );
+ mpCodes.reset( pNewCodes );
+}
+
+void ImplChain::ImplBeginAdd( const Point& rStartPt )
+{
+ maPoly = tools::Polygon();
+ maStartPt = rStartPt;
+ mnCount = 0;
+}
+
+inline void ImplChain::ImplAdd( sal_uInt8 nCode )
+{
+ if( mnCount == mnArraySize )
+ ImplGetSpace();
+
+ mpCodes[ mnCount++ ] = nCode;
+}
+
+void ImplChain::ImplEndAdd( sal_uLong nFlag )
+{
+ if( mnCount )
+ {
+ ImplPointArray aArr;
+
+ if( nFlag & VECT_POLY_INLINE_INNER )
+ {
+ tools::Long nFirstX, nFirstY;
+ tools::Long nLastX, nLastY;
+
+ nFirstX = nLastX = maStartPt.X();
+ nFirstY = nLastY = maStartPt.Y();
+ aArr.ImplSetSize( mnCount << 1 );
+
+ sal_uInt16 nPolyPos;
+ sal_uLong i;
+ for( i = 0, nPolyPos = 0; i < ( mnCount - 1 ); i++ )
+ {
+ const sal_uInt8 cMove = mpCodes[ i ];
+ const sal_uInt8 cNextMove = mpCodes[ i + 1 ];
+ const ChainMove& rMove = aImplMove[ cMove ];
+ const ChainMove& rMoveInner = aImplMoveInner[ cMove ];
+// Point& rPt = aArr[ nPolyPos ];
+ bool bDone = true;
+
+ nLastX += rMove.nDX;
+ nLastY += rMove.nDY;
+
+ if( cMove < 4 )
+ {
+ if( ( cMove == 0 && cNextMove == 3 ) ||
+ ( cMove == 3 && cNextMove == 2 ) ||
+ ( cMove == 2 && cNextMove == 1 ) ||
+ ( cMove == 1 && cNextMove == 0 ) )
+ {
+ }
+ else if( cMove == 2 && cNextMove == 3 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else if( cMove == 3 && cNextMove == 0 )
+ {
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+ }
+ else if( cMove == 0 && cNextMove == 1 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else if( cMove == 1 && cNextMove == 2 )
+ {
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+ }
+ else
+ bDone = false;
+ }
+ else if( cMove == 7 && cNextMove == 0 )
+ {
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+ }
+ else if( cMove == 4 && cNextMove == 1 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else
+ bDone = false;
+
+ if( !bDone )
+ {
+ aArr[ nPolyPos ].setX( nLastX + rMoveInner.nDX );
+ aArr[ nPolyPos++ ].setY( nLastY + rMoveInner.nDY );
+ }
+ }
+
+ aArr[ nPolyPos ].setX( nFirstX + 1 );
+ aArr[ nPolyPos++ ].setY( nFirstY + 1 );
+ aArr.ImplSetRealSize( nPolyPos );
+ }
+ else if( nFlag & VECT_POLY_INLINE_OUTER )
+ {
+ tools::Long nFirstX, nFirstY;
+ tools::Long nLastX, nLastY;
+
+ nFirstX = nLastX = maStartPt.X();
+ nFirstY = nLastY = maStartPt.Y();
+ aArr.ImplSetSize( mnCount << 1 );
+
+ sal_uInt16 nPolyPos;
+ sal_uLong i;
+ for( i = 0, nPolyPos = 0; i < ( mnCount - 1 ); i++ )
+ {
+ const sal_uInt8 cMove = mpCodes[ i ];
+ const sal_uInt8 cNextMove = mpCodes[ i + 1 ];
+ const ChainMove& rMove = aImplMove[ cMove ];
+ const ChainMove& rMoveOuter = aImplMoveOuter[ cMove ];
+// Point& rPt = aArr[ nPolyPos ];
+ bool bDone = true;
+
+ nLastX += rMove.nDX;
+ nLastY += rMove.nDY;
+
+ if( cMove < 4 )
+ {
+ if( ( cMove == 0 && cNextMove == 1 ) ||
+ ( cMove == 1 && cNextMove == 2 ) ||
+ ( cMove == 2 && cNextMove == 3 ) ||
+ ( cMove == 3 && cNextMove == 0 ) )
+ {
+ }
+ else if( cMove == 0 && cNextMove == 3 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else if( cMove == 3 && cNextMove == 2 )
+ {
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+ }
+ else if( cMove == 2 && cNextMove == 1 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else if( cMove == 1 && cNextMove == 0 )
+ {
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+ }
+ else
+ bDone = false;
+ }
+ else if( cMove == 7 && cNextMove == 3 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else if( cMove == 6 && cNextMove == 2 )
+ {
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+ }
+ else
+ bDone = false;
+
+ if( !bDone )
+ {
+ aArr[ nPolyPos ].setX( nLastX + rMoveOuter.nDX );
+ aArr[ nPolyPos++ ].setY( nLastY + rMoveOuter.nDY );
+ }
+ }
+
+ aArr[ nPolyPos ].setX( nFirstX - 1 );
+ aArr[ nPolyPos++ ].setY( nFirstY - 1 );
+ aArr.ImplSetRealSize( nPolyPos );
+ }
+ else
+ {
+ tools::Long nLastX = maStartPt.X(), nLastY = maStartPt.Y();
+
+ aArr.ImplSetSize( mnCount + 1 );
+ aArr[ 0 ] = Point( nLastX, nLastY );
+
+ for( sal_uLong i = 0; i < mnCount; )
+ {
+ const ChainMove& rMove = aImplMove[ mpCodes[ i ] ];
+ nLastX += rMove.nDX;
+ nLastY += rMove.nDY;
+ aArr[ ++i ] = Point( nLastX, nLastY );
+ }
+
+ aArr.ImplSetRealSize( mnCount + 1 );
+ }
+
+ ImplPostProcess( aArr );
+ }
+ else
+ maPoly.SetSize( 0 );
+}
+
+void ImplChain::ImplPostProcess( const ImplPointArray& rArr )
+{
+ ImplPointArray aNewArr1;
+ ImplPointArray aNewArr2;
+ Point* pLast;
+ Point* pLeast;
+ sal_uLong nNewPos;
+ sal_uLong nCount = rArr.ImplGetRealSize();
+ sal_uLong n;
+
+ // pass 1
+ aNewArr1.ImplSetSize( nCount );
+ pLast = &( aNewArr1[ 0 ] );
+ pLast->setX( BACK_MAP( rArr[ 0 ].X() ) );
+ pLast->setY( BACK_MAP( rArr[ 0 ].Y() ) );
+
+ for( n = nNewPos = 1; n < nCount; )
+ {
+ const Point& rPt = rArr[ n++ ];
+ const tools::Long nX = BACK_MAP( rPt.X() );
+ const tools::Long nY = BACK_MAP( rPt.Y() );
+
+ if( nX != pLast->X() || nY != pLast->Y() )
+ {
+ pLast = pLeast = &( aNewArr1[ nNewPos++ ] );
+ pLeast->setX( nX );
+ pLeast->setY( nY );
+ }
+ }
+
+ nCount = nNewPos;
+ aNewArr1.ImplSetRealSize( nCount );
+
+ // pass 2
+ aNewArr2.ImplSetSize( nCount );
+ pLast = &( aNewArr2[ 0 ] );
+ *pLast = aNewArr1[ 0 ];
+
+ for( n = nNewPos = 1; n < nCount; )
+ {
+ pLeast = &( aNewArr1[ n++ ] );
+
+ if( pLeast->X() == pLast->X() )
+ {
+ while( n < nCount && aNewArr1[ n ].X() == pLast->X() )
+ pLeast = &( aNewArr1[ n++ ] );
+ }
+ else if( pLeast->Y() == pLast->Y() )
+ {
+ while( n < nCount && aNewArr1[ n ].Y() == pLast->Y() )
+ pLeast = &( aNewArr1[ n++ ] );
+ }
+
+ pLast = pLeast;
+ aNewArr2[ nNewPos++ ] = *pLast;
+ }
+
+ aNewArr2.ImplSetRealSize( nNewPos );
+ aNewArr2.ImplCreatePoly( maPoly );
+}
+
+namespace ImplVectorizer {
+
+bool ImplVectorize( const Bitmap& rColorBmp, GDIMetaFile& rMtf,
+ sal_uInt8 cReduce, const Link<tools::Long,void>* pProgress )
+{
+ bool bRet = false;
+
+ VECT_PROGRESS( pProgress, 0 );
+
+ std::unique_ptr<Bitmap> xBmp(new Bitmap( rColorBmp ));
+ Bitmap::ScopedReadAccess pRAcc(*xBmp);
+
+ if( pRAcc )
+ {
+ tools::PolyPolygon aPolyPoly;
+ double fPercent = 0.0;
+ double fPercentStep_2 = 0.0;
+ const tools::Long nWidth = pRAcc->Width();
+ const tools::Long nHeight = pRAcc->Height();
+ const sal_uInt16 nColorCount = pRAcc->GetPaletteEntryCount();
+ sal_uInt16 n;
+ std::array<ImplColorSet, 256> aColorSet;
+
+ rMtf.Clear();
+
+ // get used palette colors and sort them from light to dark colors
+ for( n = 0; n < nColorCount; n++ )
+ {
+ aColorSet[ n ].mnIndex = n;
+ aColorSet[ n ].maColor = pRAcc->GetPaletteColor( n );
+ }
+
+ for( tools::Long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanlineRead = pRAcc->GetScanline( nY );
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ aColorSet[ pRAcc->GetIndexFromData( pScanlineRead, nX ) ].mbSet = true;
+ }
+
+ std::sort( aColorSet.begin(), aColorSet.end(), ImplColorSetCmpFnc );
+
+ for( n = 0; n < 256; n++ )
+ if( !aColorSet[ n ].mbSet )
+ break;
+
+ if( n )
+ fPercentStep_2 = 45.0 / n;
+
+ fPercent += 10.0;
+ VECT_PROGRESS( pProgress, FRound( fPercent ) );
+
+ for( sal_uInt16 i = 0; i < n; i++ )
+ {
+ const BitmapColor aBmpCol( pRAcc->GetPaletteColor( aColorSet[ i ].mnIndex ) );
+ const Color aFindColor( aBmpCol.GetRed(), aBmpCol.GetGreen(), aBmpCol.GetBlue() );
+ std::optional<ImplVectMap> oMap;
+ ImplExpand( oMap, pRAcc.get(), aFindColor );
+
+ fPercent += fPercentStep_2;
+ VECT_PROGRESS( pProgress, FRound( fPercent ) );
+
+ if( oMap )
+ {
+ aPolyPoly.Clear();
+ ImplCalculate( *oMap, aPolyPoly, cReduce );
+ oMap.reset();
+
+ if( aPolyPoly.Count() )
+ {
+ ImplLimitPolyPoly( aPolyPoly );
+
+ aPolyPoly.Optimize( PolyOptimizeFlags::EDGES );
+
+ if( aPolyPoly.Count() )
+ {
+ rMtf.AddAction( new MetaLineColorAction( aFindColor, true ) );
+ rMtf.AddAction( new MetaFillColorAction( aFindColor, true ) );
+ rMtf.AddAction( new MetaPolyPolygonAction( aPolyPoly ) );
+ }
+ }
+ }
+
+ fPercent += fPercentStep_2;
+ VECT_PROGRESS( pProgress, FRound( fPercent ) );
+ }
+
+ if( rMtf.GetActionSize() )
+ {
+ MapMode aMap( MapUnit::Map100thMM );
+ ScopedVclPtrInstance< VirtualDevice > aVDev;
+ const Size aLogSize1( aVDev->PixelToLogic( Size( 1, 1 ), aMap ) );
+
+ rMtf.SetPrefMapMode( aMap );
+ rMtf.SetPrefSize( Size( nWidth + 2, nHeight + 2 ) );
+ rMtf.Move( 1, 1 );
+ rMtf.Scale( aLogSize1.Width(), aLogSize1.Height() );
+ bRet = true;
+ }
+ }
+
+ pRAcc.reset();
+ xBmp.reset();
+ VECT_PROGRESS( pProgress, 100 );
+
+ return bRet;
+}
+
+void ImplLimitPolyPoly( tools::PolyPolygon& rPolyPoly )
+{
+ if( rPolyPoly.Count() <= VECT_POLY_MAX )
+ return;
+
+ tools::PolyPolygon aNewPolyPoly;
+ tools::Long nReduce = 0;
+ sal_uInt16 nNewCount;
+
+ do
+ {
+ aNewPolyPoly.Clear();
+ nReduce++;
+
+ for( sal_uInt16 i = 0, nCount = rPolyPoly.Count(); i < nCount; i++ )
+ {
+ const tools::Rectangle aBound( rPolyPoly[ i ].GetBoundRect() );
+
+ if( aBound.GetWidth() > nReduce && aBound.GetHeight() > nReduce )
+ {
+ if( rPolyPoly[ i ].GetSize() )
+ aNewPolyPoly.Insert( rPolyPoly[ i ] );
+ }
+ }
+
+ nNewCount = aNewPolyPoly.Count();
+ }
+ while( nNewCount > VECT_POLY_MAX );
+
+ rPolyPoly = aNewPolyPoly;
+}
+
+void ImplExpand( std::optional<ImplVectMap>& oMap, const BitmapReadAccess* pRAcc, const Color& rColor )
+{
+ if( !pRAcc || !pRAcc->Width() || !pRAcc->Height() )
+ return;
+
+ const tools::Long nOldWidth = pRAcc->Width();
+ const tools::Long nOldHeight = pRAcc->Height();
+ const tools::Long nNewWidth = ( nOldWidth << 2 ) + 4;
+ const tools::Long nNewHeight = ( nOldHeight << 2 ) + 4;
+ const BitmapColor aTest( pRAcc->GetBestMatchingColor( rColor ) );
+ std::unique_ptr<sal_Int32[]> pMapIn(new sal_Int32[ std::max( nOldWidth, nOldHeight ) ]);
+ std::unique_ptr<sal_Int32[]> pMapOut(new sal_Int32[ std::max( nOldWidth, nOldHeight ) ]);
+ tools::Long nX, nY, nTmpX, nTmpY;
+
+ oMap.emplace( nNewWidth, nNewHeight );
+
+ for( nX = 0; nX < nOldWidth; nX++ )
+ VECT_MAP( pMapIn, pMapOut, nX );
+
+ for( nY = 0, nTmpY = 5; nY < nOldHeight; nY++, nTmpY += 4 )
+ {
+ Scanline pScanlineRead = pRAcc->GetScanline( nY );
+ for( nX = 0; nX < nOldWidth; )
+ {
+ if( pRAcc->GetPixelFromData( pScanlineRead, nX ) == aTest )
+ {
+ nTmpX = pMapIn[ nX++ ];
+ nTmpY -= 3;
+
+ oMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ oMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ oMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX, VECT_CONT_INDEX );
+
+ while( nX < nOldWidth && pRAcc->GetPixelFromData( pScanlineRead, nX ) == aTest )
+ nX++;
+
+ nTmpX = pMapOut[ nX - 1 ];
+ nTmpY -= 3;
+
+ oMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ oMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ oMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX, VECT_CONT_INDEX );
+ }
+ else
+ nX++;
+ }
+ }
+
+ for( nY = 0; nY < nOldHeight; nY++ )
+ VECT_MAP( pMapIn, pMapOut, nY );
+
+ for( nX = 0, nTmpX = 5; nX < nOldWidth; nX++, nTmpX += 4 )
+ {
+ for( nY = 0; nY < nOldHeight; )
+ {
+ if( pRAcc->GetPixel( nY, nX ) == aTest )
+ {
+ nTmpX -= 3;
+ nTmpY = pMapIn[ nY++ ];
+
+ oMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX, VECT_CONT_INDEX );
+
+ while( nY < nOldHeight && pRAcc->GetPixel( nY, nX ) == aTest )
+ nY++;
+
+ nTmpX -= 3;
+ nTmpY = pMapOut[ nY - 1 ];
+
+ oMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ oMap->Set( nTmpY, nTmpX, VECT_CONT_INDEX );
+ }
+ else
+ nY++;
+ }
+ }
+}
+
+void ImplCalculate( ImplVectMap& rMap, tools::PolyPolygon& rPolyPoly, sal_uInt8 cReduce )
+{
+ const tools::Long nWidth = rMap.Width(), nHeight = rMap.Height();
+
+ for( tools::Long nY = 0; nY < nHeight; nY++ )
+ {
+ tools::Long nX = 0;
+ bool bInner = true;
+
+ while( nX < nWidth )
+ {
+ // skip free
+ while( ( nX < nWidth ) && rMap.IsFree( nY, nX ) )
+ nX++;
+
+ if( nX == nWidth )
+ break;
+
+ if( rMap.IsCont( nY, nX ) )
+ {
+ // new contour
+ ImplChain aChain;
+ const Point aStartPt( nX++, nY );
+
+ // get chain code
+ aChain.ImplBeginAdd( aStartPt );
+ ImplGetChain( rMap, aStartPt, aChain );
+
+ aChain.ImplEndAdd( bInner ? VECT_POLY_OUTLINE_INNER : VECT_POLY_OUTLINE_OUTER );
+
+ const tools::Polygon& rPoly = aChain.ImplGetPoly();
+
+ if( rPoly.GetSize() > 2 )
+ {
+ if( cReduce )
+ {
+ const tools::Rectangle aBound( rPoly.GetBoundRect() );
+
+ if( aBound.GetWidth() > cReduce && aBound.GetHeight() > cReduce )
+ rPolyPoly.Insert( rPoly );
+ }
+ else
+ rPolyPoly.Insert( rPoly );
+ }
+
+ // skip rest of detected contour
+ while( rMap.IsCont( nY, nX ) )
+ nX++;
+ }
+ else
+ {
+ // process done segment
+ const tools::Long nStartSegX = nX++;
+
+ while( rMap.IsDone( nY, nX ) )
+ nX++;
+
+ if( ( ( nX - nStartSegX ) == 1 ) || ( ImplIsUp( rMap, nY, nStartSegX ) != ImplIsUp( rMap, nY, nX - 1 ) ) )
+ bInner = !bInner;
+ }
+ }
+ }
+}
+
+bool ImplGetChain( ImplVectMap& rMap, const Point& rStartPt, ImplChain& rChain )
+{
+ tools::Long nActX = rStartPt.X();
+ tools::Long nActY = rStartPt.Y();
+ sal_uLong nFound;
+ sal_uLong nLastDir = 0;
+ sal_uLong nDir;
+
+ do
+ {
+ nFound = 0;
+
+ // first try last direction
+ tools::Long nTryX = nActX + aImplMove[ nLastDir ].nDX;
+ tools::Long nTryY = nActY + aImplMove[ nLastDir ].nDY;
+
+ if( rMap.IsCont( nTryY, nTryX ) )
+ {
+ rChain.ImplAdd( static_cast<sal_uInt8>(nLastDir) );
+ nActY = nTryY;
+ nActX = nTryX;
+ rMap.Set( nActY, nActX, VECT_DONE_INDEX );
+ nFound = 1;
+ }
+ else
+ {
+ // try other directions
+ for( nDir = 0; nDir < 8; nDir++ )
+ {
+ // we already tried nLastDir
+ if( nDir != nLastDir )
+ {
+ nTryX = nActX + aImplMove[ nDir ].nDX;
+ nTryY = nActY + aImplMove[ nDir ].nDY;
+
+ if( rMap.IsCont( nTryY, nTryX ) )
+ {
+ rChain.ImplAdd( static_cast<sal_uInt8>(nDir) );
+ nActY = nTryY;
+ nActX = nTryX;
+ rMap.Set( nActY, nActX, VECT_DONE_INDEX );
+ nFound = 1;
+ nLastDir = nDir;
+ break;
+ }
+ }
+ }
+ }
+ }
+ while( nFound );
+
+ return true;
+}
+
+bool ImplIsUp( ImplVectMap const & rMap, tools::Long nY, tools::Long nX )
+{
+ if( rMap.IsDone( nY - 1, nX ) )
+ return true;
+ else if( rMap.IsDone( nY + 1, nX ) )
+ return false;
+ else if( rMap.IsDone( nY - 1, nX - 1 ) || rMap.IsDone( nY - 1, nX + 1 ) )
+ return true;
+ else
+ return false;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/impvect.hxx b/vcl/source/bitmap/impvect.hxx
new file mode 100644
index 000000000..257d1b5e5
--- /dev/null
+++ b/vcl/source/bitmap/impvect.hxx
@@ -0,0 +1,32 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <vcl/gdimtf.hxx>
+
+namespace tools { class PolyPolygon; }
+
+namespace ImplVectorizer
+{
+ bool ImplVectorize( const Bitmap& rColorBmp, GDIMetaFile& rMtf,
+ sal_uInt8 cReduce, const Link<tools::Long,void>* pProgress );
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/bitmap/salbmp.cxx b/vcl/source/bitmap/salbmp.cxx
new file mode 100644
index 000000000..a1fc7de7a
--- /dev/null
+++ b/vcl/source/bitmap/salbmp.cxx
@@ -0,0 +1,334 @@
+/* -*- 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 <salbmp.hxx>
+#include <o3tl/enumarray.hxx>
+
+static BitmapChecksum scanlineChecksum(BitmapChecksum nCrc, const sal_uInt8* bits, int lineBitsCount, sal_uInt8 extraBitsMask)
+{
+ if( lineBitsCount / 8 > 0 )
+ nCrc = vcl_get_checksum( nCrc, bits, lineBitsCount / 8 );
+ if( extraBitsMask != 0 )
+ {
+ sal_uInt8 extraByte = bits[ lineBitsCount / 8 ] & extraBitsMask;
+ nCrc = vcl_get_checksum( nCrc, &extraByte, 1 );
+ }
+ return nCrc;
+}
+
+void SalBitmap::updateChecksum() const
+{
+ if (mbChecksumValid)
+ return;
+
+ BitmapChecksum nCrc = 0;
+ SalBitmap* pThis = const_cast<SalBitmap*>(this);
+ BitmapBuffer* pBuf = pThis->AcquireBuffer(BitmapAccessMode::Read);
+ if (pBuf)
+ {
+ nCrc = pBuf->maPalette.GetChecksum();
+ const int lineBitsCount = pBuf->mnWidth * pBuf->mnBitCount;
+ // With 1bpp/4bpp format we need to check only used bits in the last byte.
+ sal_uInt8 extraBitsMask = 0;
+ if( lineBitsCount % 8 != 0 )
+ {
+ const int extraBitsCount = lineBitsCount % 8;
+ switch( RemoveScanline( pBuf->mnFormat ))
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ {
+ static const sal_uInt8 mask1Bit[] = { 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
+ extraBitsMask = mask1Bit[ extraBitsCount ];
+ break;
+ }
+ case ScanlineFormat::N1BitLsbPal:
+ {
+ static const sal_uInt8 mask1Bit[] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff };
+ extraBitsMask = mask1Bit[ extraBitsCount ];
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ if( pBuf->mnFormat & ScanlineFormat::TopDown )
+ {
+ if( pBuf->mnScanlineSize == lineBitsCount / 8 )
+ nCrc = vcl_get_checksum(nCrc, pBuf->mpBits, pBuf->mnScanlineSize * pBuf->mnHeight);
+ else // Do not include padding with undefined content in the checksum.
+ for( tools::Long y = 0; y < pBuf->mnHeight; ++y )
+ nCrc = scanlineChecksum(nCrc, pBuf->mpBits + y * pBuf->mnScanlineSize, lineBitsCount, extraBitsMask);
+ }
+ else // Compute checksum in the order of scanlines, to make it consistent between different bitmap implementations.
+ {
+ for( tools::Long y = pBuf->mnHeight - 1; y >= 0; --y )
+ nCrc = scanlineChecksum(nCrc, pBuf->mpBits + y * pBuf->mnScanlineSize, lineBitsCount, extraBitsMask);
+ }
+ pThis->ReleaseBuffer(pBuf, BitmapAccessMode::Read);
+ pThis->mnChecksum = nCrc;
+ pThis->mbChecksumValid = true;
+ }
+ else
+ {
+ pThis->mbChecksumValid = false;
+ }
+}
+
+namespace
+{
+
+class ImplPixelFormat
+{
+protected:
+ const sal_uInt8* mpData;
+public:
+ static ImplPixelFormat* GetFormat( sal_uInt16 nBits, const BitmapPalette& rPalette );
+
+ virtual void StartLine( const sal_uInt8* pLine ) { mpData = pLine; }
+ virtual const BitmapColor& ReadPixel() = 0;
+ virtual ~ImplPixelFormat() { }
+};
+
+class ImplPixelFormat8 : public ImplPixelFormat
+{
+private:
+ const BitmapPalette& mrPalette;
+
+public:
+ explicit ImplPixelFormat8( const BitmapPalette& rPalette )
+ : mrPalette( rPalette )
+ {
+ }
+ virtual const BitmapColor& ReadPixel() override
+ {
+ assert( mrPalette.GetEntryCount() > *mpData );
+ return mrPalette[ *mpData++ ];
+ }
+};
+
+class ImplPixelFormat4 : public ImplPixelFormat
+{
+private:
+ const BitmapPalette& mrPalette;
+ sal_uInt32 mnX;
+ sal_uInt32 mnShift;
+
+public:
+ explicit ImplPixelFormat4( const BitmapPalette& rPalette )
+ : mrPalette( rPalette )
+ , mnX(0)
+ , mnShift(4)
+ {
+ }
+ virtual void StartLine( const sal_uInt8* pLine ) override
+ {
+ mpData = pLine;
+ mnX = 0;
+ mnShift = 4;
+ }
+ virtual const BitmapColor& ReadPixel() override
+ {
+ sal_uInt32 nIdx = ( mpData[mnX >> 1] >> mnShift) & 0x0f;
+ assert( mrPalette.GetEntryCount() > nIdx );
+ const BitmapColor& rColor = mrPalette[nIdx];
+ mnX++;
+ mnShift ^= 4;
+ return rColor;
+ }
+};
+
+class ImplPixelFormat1 : public ImplPixelFormat
+{
+private:
+ const BitmapPalette& mrPalette;
+ sal_uInt32 mnX;
+
+public:
+ explicit ImplPixelFormat1( const BitmapPalette& rPalette )
+ : mrPalette(rPalette)
+ , mnX(0)
+ {
+ }
+ virtual void StartLine( const sal_uInt8* pLine ) override
+ {
+ mpData = pLine;
+ mnX = 0;
+ }
+ virtual const BitmapColor& ReadPixel() override
+ {
+ const BitmapColor& rColor = mrPalette[ (mpData[mnX >> 3 ] >> ( 7 - ( mnX & 7 ) )) & 1];
+ mnX++;
+ return rColor;
+ }
+};
+
+ImplPixelFormat* ImplPixelFormat::GetFormat( sal_uInt16 nBits, const BitmapPalette& rPalette )
+{
+ switch( nBits )
+ {
+ case 1: return new ImplPixelFormat1( rPalette );
+ case 4: return new ImplPixelFormat4( rPalette );
+ case 8: return new ImplPixelFormat8( rPalette );
+ }
+
+ return nullptr;
+}
+
+// Optimized conversion from 1bpp. Currently LO uses 1bpp bitmaps for masks, which is nowadays
+// a lousy obsolete format, as the memory saved is just not worth the cost of fiddling with the bits.
+// Ideally LO should move to RGBA bitmaps. Until then, try to be faster with 1bpp bitmaps.
+typedef void(*WriteColorFunction)( sal_uInt8 color8Bit, sal_uInt8*& dst );
+void writeColorA8(sal_uInt8 color8Bit, sal_uInt8*& dst ) { *dst++ = color8Bit; };
+void writeColorRGBA(sal_uInt8 color8Bit, sal_uInt8*& dst ) { *dst++ = color8Bit; *dst++ = color8Bit; *dst++ = color8Bit; *dst++ = 0xff; };
+typedef void(*WriteBlackWhiteFunction)( sal_uInt8*& dst, int count );
+void writeBlackA8(sal_uInt8*& dst, int count ) { memset( dst, 0, count ); dst += count; };
+void writeWhiteA8(sal_uInt8*& dst, int count ) { memset( dst, 0xff, count ); dst += count; };
+void writeWhiteRGBA(sal_uInt8*& dst, int count ) { memset( dst, 0xff, count * 4 ); dst += count * 4; };
+void writeBlackRGBA(sal_uInt8*& dst, int count )
+{
+ for( int i = 0; i < count; ++i )
+ {
+ dst[0] = 0x00;
+ dst[1] = 0x00;
+ dst[2] = 0x00;
+ dst[3] = 0xff;
+ dst += 4;
+ }
+};
+
+template< WriteColorFunction func, WriteBlackWhiteFunction funcBlack, WriteBlackWhiteFunction funcWhite >
+void writeBlackWhiteData( const sal_uInt8* src, sal_uInt8* dst, int width, int height, int bytesPerRow )
+{
+ for( int y = 0; y < height; ++y )
+ {
+ const sal_uInt8* srcLine = src;
+ int xsize = width;
+ while( xsize >= 64 )
+ {
+ // TODO alignment?
+ const sal_uInt64* src64 = reinterpret_cast< const sal_uInt64* >( src );
+ if( *src64 == 0x00 )
+ funcBlack( dst, 64 );
+ else if( *src64 == static_cast< sal_uInt64 >( -1 ))
+ funcWhite( dst, 64 );
+ else
+ break;
+ src += sizeof( sal_uInt64 );
+ xsize -= 64;
+ }
+ while( xsize >= 8 )
+ {
+ if( *src == 0x00 ) // => eight black pixels
+ funcBlack( dst, 8 );
+ else if( *src == 0xff ) // => eight white pixels
+ funcWhite( dst, 8 );
+ else
+ for( int bit = 7; bit >= 0; --bit )
+ func(( *src >> bit ) & 1 ? 0xff : 0, dst );
+ ++src;
+ xsize -= 8;
+ }
+ for( int bit = 7; bit > 7 - xsize; --bit )
+ func(( *src >> bit ) & 1 ? 0xff : 0, dst );
+ ++src;
+ src = srcLine + bytesPerRow;
+ }
+}
+
+} // namespace
+
+std::unique_ptr< sal_uInt8[] > SalBitmap::convertDataBitCount( const sal_uInt8* src,
+ int width, int height, int bitCount, int bytesPerRow, const BitmapPalette& palette, BitConvert type )
+{
+ assert( bitCount == 1 || bitCount == 4 || bitCount == 8 );
+ static const o3tl::enumarray<BitConvert, int> bpp = { 1, 4, 4 };
+ std::unique_ptr< sal_uInt8[] > data( new sal_uInt8[width * height * bpp[ type ]] );
+
+ if(type == BitConvert::A8 && bitCount == 8 && palette.IsGreyPalette8Bit())
+ { // no actual data conversion
+ for( int y = 0; y < height; ++y )
+ memcpy( data.get() + y * width, src + y * bytesPerRow, width );
+ return data;
+ }
+
+ if(bitCount == 1 && palette.GetEntryCount() == 2 && palette[ 0 ] == COL_BLACK && palette[ 1 ] == COL_WHITE)
+ {
+ switch( type )
+ {
+ case BitConvert::A8 :
+ writeBlackWhiteData< writeColorA8, writeBlackA8, writeWhiteA8 >
+ ( src, data.get(), width, height, bytesPerRow );
+ return data;
+ case BitConvert::BGRA :
+ case BitConvert::RGBA :
+ // BGRA/RGBA is the same, all 3 values get the same value
+ writeBlackWhiteData< writeColorRGBA, writeBlackRGBA, writeWhiteRGBA >
+ ( src, data.get(), width, height, bytesPerRow );
+ return data;
+ }
+ }
+
+ std::unique_ptr<ImplPixelFormat> pSrcFormat(ImplPixelFormat::GetFormat(bitCount, palette));
+
+ const sal_uInt8* pSrcData = src;
+ sal_uInt8* pDstData = data.get();
+
+ sal_uInt32 nY = height;
+ while( nY-- )
+ {
+ pSrcFormat->StartLine( pSrcData );
+
+ sal_uInt32 nX = width;
+ switch( type )
+ {
+ case BitConvert::A8 :
+ while( nX-- )
+ {
+ const BitmapColor& c = pSrcFormat->ReadPixel();
+ *pDstData++ = c.GetBlue();
+ }
+ break;
+ case BitConvert::BGRA :
+ while( nX-- )
+ {
+ const BitmapColor& c = pSrcFormat->ReadPixel();
+ *pDstData++ = c.GetBlue();
+ *pDstData++ = c.GetGreen();
+ *pDstData++ = c.GetRed();
+ *pDstData++ = 0xff;
+ }
+ break;
+ case BitConvert::RGBA :
+ while( nX-- )
+ {
+ const BitmapColor& c = pSrcFormat->ReadPixel();
+ *pDstData++ = c.GetRed();
+ *pDstData++ = c.GetGreen();
+ *pDstData++ = c.GetBlue();
+ *pDstData++ = 0xff;
+ }
+ break;
+ }
+
+ pSrcData += bytesPerRow;
+ }
+ return data;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/cnttype/mcnttfactory.cxx b/vcl/source/cnttype/mcnttfactory.cxx
new file mode 100644
index 000000000..4a3e6fbb4
--- /dev/null
+++ b/vcl/source/cnttype/mcnttfactory.cxx
@@ -0,0 +1,67 @@
+/* -*- 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 <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include "mcnttfactory.hxx"
+#include "mcnttype.hxx"
+
+using namespace ::osl;
+using namespace ::cppu;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::datatransfer;
+
+CMimeContentTypeFactory::CMimeContentTypeFactory()
+{
+}
+
+Reference< XMimeContentType > CMimeContentTypeFactory::createMimeContentType( const OUString& aContentType )
+{
+ return Reference< XMimeContentType >( new CMimeContentType( aContentType ) );
+}
+
+// XServiceInfo
+
+OUString SAL_CALL CMimeContentTypeFactory::getImplementationName( )
+{
+ return "com.sun.star.datatransfer.MimeCntTypeFactory";
+}
+
+sal_Bool SAL_CALL CMimeContentTypeFactory::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SAL_CALL CMimeContentTypeFactory::getSupportedServiceNames( )
+{
+ return { "com.sun.star.datatransfer.MimeContentTypeFactory" };
+}
+
+
+// returns a factory to create XFilePicker-Services
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+dtrans_CMimeContentTypeFactory_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire( new CMimeContentTypeFactory() );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/cnttype/mcnttfactory.hxx b/vcl/source/cnttype/mcnttfactory.hxx
new file mode 100644
index 000000000..c9bd3518c
--- /dev/null
+++ b/vcl/source/cnttype/mcnttfactory.hxx
@@ -0,0 +1,49 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+
+class CMimeContentTypeFactory : public
+ cppu::WeakImplHelper< css::datatransfer::XMimeContentTypeFactory,
+ css::lang::XServiceInfo >
+{
+
+public:
+ CMimeContentTypeFactory();
+
+ // XMimeContentTypeFactory
+
+ virtual css::uno::Reference< css::datatransfer::XMimeContentType > SAL_CALL createMimeContentType( const OUString& aContentType ) override;
+
+ // XServiceInfo
+
+ virtual OUString SAL_CALL getImplementationName( ) override;
+
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/cnttype/mcnttype.cxx b/vcl/source/cnttype/mcnttype.cxx
new file mode 100644
index 000000000..f41f5d25e
--- /dev/null
+++ b/vcl/source/cnttype/mcnttype.cxx
@@ -0,0 +1,88 @@
+/* -*- 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 <com/sun/star/container/NoSuchElementException.hpp>
+#include <comphelper/sequence.hxx>
+#include <rtl/ustring.hxx>
+#include <tools/inetmime.hxx>
+
+#include "mcnttype.hxx"
+
+CMimeContentType::CMimeContentType( const OUString& aCntType )
+{
+ init( aCntType );
+}
+
+OUString SAL_CALL CMimeContentType::getMediaType( )
+{
+ return m_MediaType;
+}
+
+OUString SAL_CALL CMimeContentType::getMediaSubtype( )
+{
+ return m_MediaSubtype;
+}
+
+OUString SAL_CALL CMimeContentType::getFullMediaType( )
+{
+ return m_MediaType + "/" + m_MediaSubtype;
+}
+
+css::uno::Sequence< OUString > SAL_CALL CMimeContentType::getParameters( )
+{
+ return comphelper::mapKeysToSequence(m_ParameterMap);
+}
+
+sal_Bool SAL_CALL CMimeContentType::hasParameter( const OUString& aName )
+{
+ return ( m_ParameterMap.end( ) != m_ParameterMap.find( aName.toAsciiLowerCase() ) );
+}
+
+OUString SAL_CALL CMimeContentType::getParameterValue( const OUString& aName )
+{
+ auto const lower = aName.toAsciiLowerCase();
+
+ if ( !hasParameter( lower ) )
+ throw css::container::NoSuchElementException( );
+
+ return m_ParameterMap.find( lower )->second;
+}
+
+void CMimeContentType::init( const OUString& aCntType )
+{
+ INetContentTypeParameterList params;
+ if (INetMIME::scanContentType(aCntType, &m_MediaType, &m_MediaSubtype, &params)
+ != aCntType.getStr() + aCntType.getLength())
+ {
+ throw css::lang::IllegalArgumentException(
+ "illegal media type " + aCntType, css::uno::Reference<css::uno::XInterface>(), -1);
+ }
+ for (auto const & i: params) {
+ if (!i.second.m_bConverted) {
+ throw css::lang::IllegalArgumentException(
+ "illegal parameter value in media type " + aCntType,
+ css::uno::Reference<css::uno::XInterface>(), -1);
+ }
+ m_ParameterMap[OUString::fromUtf8(i.first)] = i.second.m_sValue;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/cnttype/mcnttype.hxx b/vcl/source/cnttype/mcnttype.hxx
new file mode 100644
index 000000000..c4053066b
--- /dev/null
+++ b/vcl/source/cnttype/mcnttype.hxx
@@ -0,0 +1,58 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <cppuhelper/implbase.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/datatransfer/XMimeContentType.hpp>
+
+#include <map>
+
+class CMimeContentType : public
+ cppu::WeakImplHelper< css::datatransfer::XMimeContentType >
+{
+public:
+ explicit CMimeContentType(const OUString& rCntType);
+
+ // XMimeContentType
+
+ virtual OUString SAL_CALL getMediaType( ) override;
+ virtual OUString SAL_CALL getMediaSubtype( ) override;
+ virtual OUString SAL_CALL getFullMediaType( ) override;
+
+ virtual css::uno::Sequence< OUString > SAL_CALL getParameters( ) override;
+
+ virtual sal_Bool SAL_CALL hasParameter( const OUString& aName ) override;
+
+ virtual OUString SAL_CALL getParameterValue( const OUString& aName ) override;
+
+private:
+ /// @throws css::lang::IllegalArgumentException
+ void init( const OUString& aCntType );
+
+private:
+ OUString m_MediaType;
+ OUString m_MediaSubtype;
+ std::map< OUString, OUString > m_ParameterMap;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/components/dtranscomp.cxx b/vcl/source/components/dtranscomp.cxx
new file mode 100644
index 000000000..643d9cbc1
--- /dev/null
+++ b/vcl/source/components/dtranscomp.cxx
@@ -0,0 +1,463 @@
+/* -*- 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 <comphelper/lok.hxx>
+#include <tools/debug.hxx>
+#include <vcl/svapp.hxx>
+
+#include <factory.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
+#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragSource.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+
+#include <comphelper/compbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+
+namespace vcl
+{
+namespace {
+
+// generic implementation to satisfy SalInstance
+class GenericClipboard :
+ public comphelper::WeakComponentImplHelper<
+ datatransfer::clipboard::XSystemClipboard,
+ XServiceInfo
+ >
+{
+ Reference< css::datatransfer::XTransferable > m_aContents;
+ Reference< css::datatransfer::clipboard::XClipboardOwner > m_aOwner;
+ std::vector< Reference< css::datatransfer::clipboard::XClipboardListener > > m_aListeners;
+
+public:
+
+ GenericClipboard()
+ {}
+
+ /*
+ * XServiceInfo
+ */
+
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ static Sequence< OUString > getSupportedServiceNames_static();
+
+ /*
+ * XClipboard
+ */
+
+ virtual Reference< css::datatransfer::XTransferable > SAL_CALL getContents() override;
+
+ virtual void SAL_CALL setContents(
+ const Reference< css::datatransfer::XTransferable >& xTrans,
+ const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner ) override;
+
+ virtual OUString SAL_CALL getName() override;
+
+ /*
+ * XClipboardEx
+ */
+
+ virtual sal_Int8 SAL_CALL getRenderingCapabilities() override;
+
+ /*
+ * XClipboardNotifier
+ */
+ virtual void SAL_CALL addClipboardListener(
+ const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
+
+ virtual void SAL_CALL removeClipboardListener(
+ const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
+};
+
+}
+
+Sequence< OUString > GenericClipboard::getSupportedServiceNames_static()
+{
+ Sequence< OUString > aRet { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
+ return aRet;
+}
+
+OUString GenericClipboard::getImplementationName()
+{
+ return "com.sun.star.datatransfer.VCLGenericClipboard";
+}
+
+Sequence< OUString > GenericClipboard::getSupportedServiceNames()
+{
+ return getSupportedServiceNames_static();
+}
+
+sal_Bool GenericClipboard::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Reference< css::datatransfer::XTransferable > GenericClipboard::getContents()
+{
+ return m_aContents;
+}
+
+void GenericClipboard::setContents(
+ const Reference< css::datatransfer::XTransferable >& xTrans,
+ const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner )
+{
+ std::unique_lock aGuard( m_aMutex );
+ Reference< datatransfer::clipboard::XClipboardOwner > xOldOwner( m_aOwner );
+ Reference< datatransfer::XTransferable > xOldContents( m_aContents );
+ m_aContents = xTrans;
+ m_aOwner = xClipboardOwner;
+
+ std::vector< Reference< datatransfer::clipboard::XClipboardListener > > aListeners( m_aListeners );
+ datatransfer::clipboard::ClipboardEvent aEv;
+ aEv.Contents = m_aContents;
+
+ aGuard.unlock();
+
+ if( xOldOwner.is() && xOldOwner != xClipboardOwner )
+ xOldOwner->lostOwnership( this, xOldContents );
+ for (auto const& listener : aListeners)
+ {
+ listener->changedContents( aEv );
+ }
+}
+
+OUString GenericClipboard::getName()
+{
+ return "CLIPBOARD";
+}
+
+sal_Int8 GenericClipboard::getRenderingCapabilities()
+{
+ return 0;
+}
+
+void GenericClipboard::addClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
+{
+ std::unique_lock aGuard(m_aMutex);
+
+ m_aListeners.push_back( listener );
+}
+
+void GenericClipboard::removeClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
+{
+ std::unique_lock aGuard(m_aMutex);
+
+ m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), listener), m_aListeners.end());
+}
+
+
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+vcl_SystemClipboard_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const& args)
+{
+ SolarMutexGuard aGuard;
+ auto xClipboard = ImplGetSVData()->mpDefInst->CreateClipboard( args );
+ if (xClipboard.is())
+ xClipboard->acquire();
+ return xClipboard.get();
+}
+
+namespace {
+
+/*
+* generic DragSource dummy
+*/
+class GenericDragSource : public ::comphelper::WeakComponentImplHelper<
+ datatransfer::dnd::XDragSource,
+ XInitialization,
+ css::lang::XServiceInfo
+ >
+{
+ css::uno::Reference<css::datatransfer::XTransferable> m_xTrans;
+public:
+ GenericDragSource() {}
+
+ // XDragSource
+ virtual sal_Bool SAL_CALL isDragImageSupported() override;
+ virtual sal_Int32 SAL_CALL getDefaultCursor( sal_Int8 dragAction ) override;
+ virtual void SAL_CALL startDrag(
+ const datatransfer::dnd::DragGestureEvent& trigger,
+ sal_Int8 sourceActions, sal_Int32 cursor, sal_Int32 image,
+ const Reference< datatransfer::XTransferable >& transferable,
+ const Reference< datatransfer::dnd::XDragSourceListener >& listener
+ ) override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const Sequence< Any >& arguments ) override;
+
+ OUString SAL_CALL getImplementationName() override
+ { return "com.sun.star.datatransfer.dnd.VclGenericDragSource"; }
+
+ sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
+ { return cppu::supportsService(this, ServiceName); }
+
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+ { return getSupportedServiceNames_static(); }
+
+ static Sequence< OUString > getSupportedServiceNames_static()
+ {
+ return { "com.sun.star.datatransfer.dnd.GenericDragSource" };
+ }
+};
+
+}
+
+sal_Bool GenericDragSource::isDragImageSupported()
+{
+ return false;
+}
+
+sal_Int32 GenericDragSource::getDefaultCursor( sal_Int8 )
+{
+ return 0;
+}
+
+void GenericDragSource::startDrag( const datatransfer::dnd::DragGestureEvent&,
+ sal_Int8 /*sourceActions*/, sal_Int32 /*cursor*/, sal_Int32 /*image*/,
+ const Reference< datatransfer::XTransferable >& rTrans,
+ const Reference< datatransfer::dnd::XDragSourceListener >& listener
+ )
+{
+ if (comphelper::LibreOfficeKit::isActive()) {
+ m_xTrans = rTrans;
+ return;
+ }
+
+ datatransfer::dnd::DragSourceDropEvent aEv;
+ aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_COPY;
+ aEv.DropSuccess = false;
+ listener->dragDropEnd( aEv );
+}
+
+void GenericDragSource::initialize( const Sequence< Any >& )
+{
+}
+
+Sequence< OUString > DragSource_getSupportedServiceNames()
+{
+#if defined MACOSX
+ return { "com.sun.star.datatransfer.dnd.OleDragSource" };
+#elif defined UNX
+ return { "com.sun.star.datatransfer.dnd.X11DragSource" };
+#else
+ return { "com.sun.star.datatransfer.dnd.VclGenericDragSource" };
+#endif
+}
+
+OUString DragSource_getImplementationName()
+{
+#if defined MACOSX
+ return "com.sun.star.comp.datatransfer.dnd.OleDragSource_V1";
+#elif defined UNX
+ return "com.sun.star.datatransfer.dnd.XdndSupport";
+#else
+ return "com.sun.star.datatransfer.dnd.VclGenericDragSource";
+#endif
+}
+
+Reference< XInterface > DragSource_createInstance( const Reference< XMultiServiceFactory >& )
+{
+ SolarMutexGuard aGuard;
+ Reference< XInterface > xResult = ImplGetSVData()->mpDefInst->CreateDragSource();
+ return xResult;
+}
+
+/*
+* generic DragSource dummy
+*/
+
+namespace {
+
+class GenericDropTarget : public comphelper::WeakComponentImplHelper<
+ datatransfer::dnd::XDropTarget,
+ XInitialization,
+ css::lang::XServiceInfo
+ >
+{
+public:
+ GenericDropTarget() {}
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const Sequence< Any >& args ) override;
+
+ // XDropTarget
+ virtual void SAL_CALL addDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& ) override;
+ virtual void SAL_CALL removeDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& ) override;
+ virtual sal_Bool SAL_CALL isActive() override;
+ virtual void SAL_CALL setActive( sal_Bool active ) override;
+ virtual sal_Int8 SAL_CALL getDefaultActions() override;
+ virtual void SAL_CALL setDefaultActions( sal_Int8 actions ) override;
+
+ OUString SAL_CALL getImplementationName() override
+ { return "com.sun.star.datatransfer.dnd.VclGenericDropTarget"; }
+
+ sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
+ { return cppu::supportsService(this, ServiceName); }
+
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+ { return getSupportedServiceNames_static(); }
+
+ static Sequence< OUString > getSupportedServiceNames_static()
+ {
+ return { "com.sun.star.datatransfer.dnd.GenericDropTarget" };
+ }
+};
+
+}
+
+void GenericDropTarget::initialize( const Sequence< Any >& )
+{
+}
+
+void GenericDropTarget::addDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& )
+{
+}
+
+void GenericDropTarget::removeDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& )
+{
+}
+
+sal_Bool GenericDropTarget::isActive()
+{
+ return false;
+}
+
+void GenericDropTarget::setActive( sal_Bool )
+{
+}
+
+sal_Int8 GenericDropTarget::getDefaultActions()
+{
+ return 0;
+}
+
+void GenericDropTarget::setDefaultActions( sal_Int8)
+{
+}
+
+Sequence< OUString > DropTarget_getSupportedServiceNames()
+{
+#if defined MACOSX
+ return { "com.sun.star.datatransfer.dnd.OleDropTarget" };
+#elif defined UNX
+ return { "com.sun.star.datatransfer.dnd.X11DropTarget" };
+#else
+ return GenericDropTarget::getSupportedServiceNames_static();
+#endif
+}
+
+OUString DropTarget_getImplementationName()
+{
+ return
+ #if defined MACOSX
+ "com.sun.star.comp.datatransfer.dnd.OleDropTarget_V1"
+ #elif defined UNX
+ "com.sun.star.datatransfer.dnd.XdndDropTarget"
+ #else
+ "com.sun.star.datatransfer.dnd.VclGenericDropTarget"
+ #endif
+ ;
+}
+
+Reference< XInterface > DropTarget_createInstance( const Reference< XMultiServiceFactory >& )
+{
+ SolarMutexGuard aGuard;
+ Reference< XInterface > xResult = ImplGetSVData()->mpDefInst->CreateDropTarget();
+ return xResult;
+}
+
+} // namespace vcl
+
+/*
+* SalInstance generic
+*/
+Reference< XInterface > SalInstance::CreateClipboard( const Sequence< Any >& arguments )
+{
+ if (arguments.hasElements()) {
+ throw css::lang::IllegalArgumentException(
+ "non-empty SalInstance::CreateClipboard arguments", {}, -1);
+ }
+#ifdef IOS
+ return Reference< XInterface >( static_cast<cppu::OWeakObject *>(new vcl::GenericClipboard()) );
+#else
+ if (comphelper::LibreOfficeKit::isActive()) {
+ // In LOK, each document view shall have its own clipboard instance, and the way that
+ // (happens to?) work is that apparently this function is called at most once for each such
+ // document view, so it is OK if we hand out a fresh instance on each call in LOK (whereas
+ // in non-LOK below we keep handing out one single instance; see also
+ // <https://lists.freedesktop.org/archives/libreoffice/2020-April/084824.html> "Re: Linux
+ // SAL_USE_VCLPLUGIN=svp and the clipboard"):
+ return Reference< XInterface >( static_cast<cppu::OWeakObject *>(new vcl::GenericClipboard()) );
+ }
+#endif
+ DBG_TESTSOLARMUTEX();
+ if (!m_clipboard.is()) {
+ m_clipboard = static_cast<cppu::OWeakObject *>(new vcl::GenericClipboard());
+ }
+ return m_clipboard;
+}
+
+uno::Reference<uno::XInterface> SalInstance::ImplCreateDragSource(const SystemEnvData*)
+{
+ return css::uno::Reference<css::uno::XInterface>();
+}
+
+Reference< XInterface > SalInstance::CreateDragSource(const SystemEnvData* pSysEnv)
+{
+ // We run unit tests in parallel, which is a problem when touching a shared resource
+ // the system clipboard, so rather use the dummy GenericClipboard.
+ if (Application::IsHeadlessModeEnabled() || IsRunningUnitTest())
+ return Reference<XInterface>(static_cast<cppu::OWeakObject*>(new vcl::GenericDragSource()));
+ return ImplCreateDragSource(pSysEnv);
+}
+
+uno::Reference<uno::XInterface> SalInstance::ImplCreateDropTarget(const SystemEnvData*)
+{
+ return css::uno::Reference<css::uno::XInterface>();
+}
+
+Reference< XInterface > SalInstance::CreateDropTarget(const SystemEnvData* pSysEnv)
+{
+ // see SalInstance::CreateDragSource
+ if (Application::IsHeadlessModeEnabled() || IsRunningUnitTest())
+ return Reference<XInterface>(static_cast<cppu::OWeakObject*>(new vcl::GenericDropTarget()));
+ return ImplCreateDropTarget(pSysEnv);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/components/factory.cxx b/vcl/source/components/factory.cxx
new file mode 100644
index 000000000..0a5b7e8f6
--- /dev/null
+++ b/vcl/source/components/factory.cxx
@@ -0,0 +1,69 @@
+/* -*- 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 <cppuhelper/factory.hxx>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+#include <vcl/dllapi.h>
+
+#include <factory.hxx>
+
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+
+extern "C" {
+
+ VCL_DLLPUBLIC void* vcl_component_getFactory(
+ const char* pImplementationName,
+ void* pXUnoSMgr,
+ void* /*pXUnoKey*/
+ )
+ {
+ void* pRet = nullptr;
+
+ if( pXUnoSMgr )
+ {
+ Reference< css::lang::XMultiServiceFactory > xMgr(
+ static_cast< css::lang::XMultiServiceFactory* >( pXUnoSMgr )
+ );
+ Reference< css::lang::XSingleServiceFactory > xFactory;
+ if( vcl::DragSource_getImplementationName().equalsAscii( pImplementationName ) )
+ {
+ xFactory = ::cppu::createSingleFactory(
+ xMgr, vcl::DragSource_getImplementationName(), vcl::DragSource_createInstance,
+ vcl::DragSource_getSupportedServiceNames() );
+ }
+ else if( vcl::DropTarget_getImplementationName().equalsAscii( pImplementationName ) )
+ {
+ xFactory = ::cppu::createSingleFactory(
+ xMgr, vcl::DropTarget_getImplementationName(), vcl::DropTarget_createInstance,
+ vcl::DropTarget_getSupportedServiceNames() );
+ }
+ if( xFactory.is() )
+ {
+ xFactory->acquire();
+ pRet = xFactory.get();
+ }
+ }
+ return pRet;
+ }
+
+} /* extern "C" */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/components/fontident.cxx b/vcl/source/components/fontident.cxx
new file mode 100644
index 000000000..78d5703c5
--- /dev/null
+++ b/vcl/source/components/fontident.cxx
@@ -0,0 +1,176 @@
+/* -*- 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 <vcl/svapp.hxx>
+#include <vcl/font.hxx>
+
+#include <factory.hxx>
+#include <svdata.hxx>
+
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/beans/XMaterialHolder.hpp>
+#include <com/sun/star/awt/FontDescriptor.hpp>
+#include <com/sun/star/awt/FontFamily.hpp>
+#include <com/sun/star/awt/FontPitch.hpp>
+#include <com/sun/star/awt/FontWeight.hpp>
+#include <com/sun/star/awt/FontSlant.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+
+#include <cppuhelper/implbase3.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::awt;
+
+namespace vcl
+{
+
+namespace {
+
+class FontIdentificator : public ::cppu::WeakAggImplHelper3< XMaterialHolder, XInitialization, XServiceInfo >
+{
+ Font m_aFont;
+public:
+ FontIdentificator() {}
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ) override;
+ virtual Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const Sequence< Any >& ) override;
+
+ // XMaterialHolder
+ virtual Any SAL_CALL getMaterial() override;
+
+};
+
+}
+
+void SAL_CALL FontIdentificator::initialize( const Sequence<Any>& i_rArgs )
+{
+ if( !ImplGetSVData() )
+ return; // VCL not initialized
+
+ Sequence< sal_Int8 > aFontBuf;
+ for( const auto& rArg : i_rArgs )
+ {
+ if( rArg >>= aFontBuf )
+ {
+ m_aFont = Font::identifyFont( aFontBuf.getConstArray(), aFontBuf.getLength() );
+ break;
+ }
+ }
+}
+
+Any SAL_CALL FontIdentificator::getMaterial()
+{
+ if( !ImplGetSVData() )
+ return Any(); // VCL not initialized
+
+ FontDescriptor aFD;
+ aFD.Name = m_aFont.GetFamilyName();
+ aFD.Height = 0;
+ aFD.Width = 0;
+ aFD.StyleName = m_aFont.GetStyleName();
+ aFD.CharSet = 0;
+ aFD.CharacterWidth = 0;
+ aFD.Underline = 0;
+ aFD.Strikeout = 0;
+ aFD.Orientation = 0;
+ aFD.Kerning = false;
+ aFD.WordLineMode = false;
+ aFD.Type = 0;
+ switch( m_aFont.GetFamilyType() )
+ {
+ case FAMILY_DECORATIVE: aFD.Family = css::awt::FontFamily::DECORATIVE;break;
+ case FAMILY_MODERN: aFD.Family = css::awt::FontFamily::MODERN;break;
+ case FAMILY_ROMAN: aFD.Family = css::awt::FontFamily::ROMAN;break;
+ case FAMILY_SCRIPT: aFD.Family = css::awt::FontFamily::SCRIPT;break;
+ case FAMILY_SWISS: aFD.Family = css::awt::FontFamily::SWISS;break;
+ case FAMILY_SYSTEM: aFD.Family = css::awt::FontFamily::SYSTEM;break;
+ default:
+ aFD.Family = css::awt::FontFamily::DONTKNOW;
+ break;
+ }
+ switch( m_aFont.GetPitch() )
+ {
+ case PITCH_VARIABLE: aFD.Pitch = css::awt::FontPitch::VARIABLE;break;
+ case PITCH_FIXED: aFD.Pitch = css::awt::FontPitch::FIXED;break;
+ default:
+ aFD.Pitch = css::awt::FontPitch::DONTKNOW;
+ break;
+ }
+ switch( m_aFont.GetWeight() )
+ {
+ case WEIGHT_THIN: aFD.Weight = css::awt::FontWeight::THIN;break;
+ case WEIGHT_ULTRALIGHT: aFD.Weight = css::awt::FontWeight::ULTRALIGHT;break;
+ case WEIGHT_LIGHT: aFD.Weight = css::awt::FontWeight::LIGHT;break;
+ case WEIGHT_SEMILIGHT: aFD.Weight = css::awt::FontWeight::SEMILIGHT;break;
+ case WEIGHT_MEDIUM:
+ case WEIGHT_NORMAL: aFD.Weight = css::awt::FontWeight::NORMAL;break;
+ case WEIGHT_SEMIBOLD: aFD.Weight = css::awt::FontWeight::SEMIBOLD;break;
+ case WEIGHT_BOLD: aFD.Weight = css::awt::FontWeight::BOLD;break;
+ case WEIGHT_ULTRABOLD: aFD.Weight = css::awt::FontWeight::ULTRABOLD;break;
+ case WEIGHT_BLACK: aFD.Weight = css::awt::FontWeight::BLACK;break;
+ default:
+ aFD.Weight = css::awt::FontWeight::DONTKNOW;
+ break;
+ }
+ switch( m_aFont.GetItalic() )
+ {
+ case ITALIC_OBLIQUE: aFD.Slant = css::awt::FontSlant_OBLIQUE;break;
+ case ITALIC_NORMAL: aFD.Slant = css::awt::FontSlant_ITALIC;break;
+ default:
+ aFD.Slant = css::awt::FontSlant_DONTKNOW;
+ break;
+ }
+ return Any( aFD );
+}
+
+// XServiceInfo
+OUString SAL_CALL FontIdentificator::getImplementationName()
+{
+ return "vcl::FontIdentificator";
+}
+
+sal_Bool SAL_CALL FontIdentificator::supportsService( const OUString& i_rServiceName )
+{
+ return cppu::supportsService(this, i_rServiceName);
+}
+
+Sequence< OUString > SAL_CALL FontIdentificator::getSupportedServiceNames()
+{
+ return { "com.sun.star.awt.FontIdentificator" };
+}
+
+} // namespace vcl
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+vcl_FontIdentificator_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new vcl::FontIdentificator());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/ContextVBox.cxx b/vcl/source/control/ContextVBox.cxx
new file mode 100644
index 000000000..94090d5cd
--- /dev/null
+++ b/vcl/source/control/ContextVBox.cxx
@@ -0,0 +1,62 @@
+/* -*- 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 <vcl/NotebookbarContextControl.hxx>
+#include <ContextVBox.hxx>
+
+ContextVBox::ContextVBox( vcl::Window *pParent )
+ : VclVBox( pParent )
+{
+}
+
+ContextVBox::~ContextVBox()
+{
+ disposeOnce();
+}
+
+void ContextVBox::SetContext( vcl::EnumContext::Context eContext )
+{
+ for (int nChild = 0; nChild < GetChildCount(); ++nChild)
+ {
+ if ( GetChild( nChild )->GetType() == WindowType::CONTAINER )
+ {
+ VclContainer* pChild = static_cast<VclContainer*>( GetChild( nChild ) );
+
+ if ( pChild->HasContext( eContext ) || pChild->HasContext( vcl::EnumContext::Context::Any ) )
+ {
+ Size aSize( pChild->GetOptimalSize() );
+ aSize.AdjustHeight(6 );
+ pChild->Show();
+ pChild->SetSizePixel( aSize );
+ }
+ else
+ {
+ pChild->Hide();
+ pChild->SetSizePixel( Size( 0, 0 ) );
+ }
+ }
+ }
+ Size aSize( GetOptimalSize() );
+ aSize.AdjustWidth(6 );
+ SetSizePixel( aSize );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/DropdownBox.cxx b/vcl/source/control/DropdownBox.cxx
new file mode 100644
index 000000000..6aaf2e553
--- /dev/null
+++ b/vcl/source/control/DropdownBox.cxx
@@ -0,0 +1,115 @@
+/* -*- 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 <vcl/toolkit/button.hxx>
+#include <vcl/layout.hxx>
+#include <DropdownBox.hxx>
+
+#define NOTEBOOK_HEADER_HEIGHT 30
+
+/*
+ * DropdownBox - shows content or moves it to the popup
+ * which can be opened by clicking on a button
+ */
+
+DropdownBox::DropdownBox(vcl::Window* pParent)
+ : VclHBox(pParent)
+ , m_bInFullView(true)
+{
+ m_pButton = VclPtr<PushButton>::Create(this, WB_FLATBUTTON);
+ m_pButton->SetClickHdl(LINK(this, DropdownBox, PBClickHdl));
+ m_pButton->SetSymbol(SymbolType::MENU);
+ m_pButton->set_width_request(15);
+ m_pButton->SetQuickHelpText(GetQuickHelpText());
+ m_pButton->Resize();
+}
+
+DropdownBox::~DropdownBox() { disposeOnce(); }
+
+void DropdownBox::dispose()
+{
+ m_pButton.disposeAndClear();
+ if (m_pPopup)
+ m_pPopup.disposeAndClear();
+
+ VclHBox::dispose();
+}
+
+void DropdownBox::HideContent()
+{
+ if (m_bInFullView)
+ {
+ m_bInFullView = false;
+
+ for (int i = 0; i < GetChildCount(); i++)
+ GetChild(i)->Hide();
+
+ m_pButton->Show();
+ SetOutputSizePixel(Size(m_pButton->GetSizePixel().Width(), GetSizePixel().Height()));
+ }
+}
+
+bool DropdownBox::IsHidden() { return !m_bInFullView; }
+
+void DropdownBox::ShowContent()
+{
+ if (!m_bInFullView)
+ {
+ m_bInFullView = true;
+
+ for (int i = 0; i < GetChildCount(); i++)
+ GetChild(i)->Show();
+
+ m_pButton->Hide();
+ }
+}
+
+IMPL_LINK(DropdownBox, PBClickHdl, Button*, /*pButton*/, void)
+{
+ if (m_pPopup)
+ m_pPopup.disposeAndClear();
+
+ m_pPopup = VclPtr<NotebookbarPopup>::Create(this);
+
+ for (int i = 0; i < GetChildCount(); i++)
+ {
+ if (GetChild(i) != m_pButton)
+ {
+ Window* pChild = GetChild(i);
+ pChild->Show();
+
+ pChild->SetParent(m_pPopup->getBox());
+ // count is decreased because we moved child
+ i--;
+ }
+ }
+
+ m_pPopup->hideSeparators(true);
+
+ m_pPopup->getBox()->set_height_request(GetSizePixel().Height());
+
+ tools::Long x = GetPosPixel().getX();
+ tools::Long y = GetPosPixel().getY() + NOTEBOOK_HEADER_HEIGHT + GetSizePixel().Height();
+ tools::Rectangle aRect(x, y, x, y);
+
+ m_pPopup->StartPopupMode(aRect, FloatWinPopupFlags::Down | FloatWinPopupFlags::GrabFocus
+ | FloatWinPopupFlags::AllMouseButtonClose);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/InterimItemWindow.cxx b/vcl/source/control/InterimItemWindow.cxx
new file mode 100644
index 000000000..0017065d7
--- /dev/null
+++ b/vcl/source/control/InterimItemWindow.cxx
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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/.
+ */
+
+#include <vcl/InterimItemWindow.hxx>
+#include <vcl/layout.hxx>
+#include <salobj.hxx>
+#include <window.h>
+
+InterimItemWindow::InterimItemWindow(vcl::Window* pParent, const OUString& rUIXMLDescription,
+ const OString& rID, bool bAllowCycleFocusOut,
+ sal_uInt64 nLOKWindowId)
+ : Control(pParent, WB_TABSTOP)
+ , m_pWidget(nullptr) // inheritors are expected to call InitControlBase
+ , m_aLayoutIdle("InterimItemWindow m_aLayoutIdle")
+{
+ m_aLayoutIdle.SetPriority(TaskPriority::RESIZE);
+ m_aLayoutIdle.SetInvokeHandler(LINK(this, InterimItemWindow, DoLayout));
+
+ m_xVclContentArea = VclPtr<VclVBox>::Create(this);
+ m_xVclContentArea->Show();
+ m_xBuilder = Application::CreateInterimBuilder(m_xVclContentArea, rUIXMLDescription,
+ bAllowCycleFocusOut, nLOKWindowId);
+ m_xContainer = m_xBuilder->weld_container(rID);
+
+ SetBackground();
+ SetPaintTransparent(true);
+}
+
+void InterimItemWindow::StateChanged(StateChangedType nStateChange)
+{
+ if (nStateChange == StateChangedType::Enable)
+ m_xContainer->set_sensitive(IsEnabled());
+ Control::StateChanged(nStateChange);
+}
+
+InterimItemWindow::~InterimItemWindow() { disposeOnce(); }
+
+void InterimItemWindow::dispose()
+{
+ m_pWidget = nullptr;
+
+ m_xContainer.reset();
+ m_xBuilder.reset();
+ m_xVclContentArea.disposeAndClear();
+
+ m_aLayoutIdle.Stop();
+
+ Control::dispose();
+}
+
+void InterimItemWindow::StartIdleLayout()
+{
+ if (!m_xVclContentArea)
+ return;
+ if (m_aLayoutIdle.IsActive())
+ return;
+ m_aLayoutIdle.Start();
+}
+
+void InterimItemWindow::queue_resize(StateChangedType eReason)
+{
+ Control::queue_resize(eReason);
+ StartIdleLayout();
+}
+
+void InterimItemWindow::Resize() { Layout(); }
+
+void InterimItemWindow::UnclipVisibleSysObj()
+{
+ if (!IsVisible())
+ return;
+ vcl::Window* pChild = m_xVclContentArea->GetWindow(GetWindowType::FirstChild);
+ if (!pChild)
+ return;
+ WindowImpl* pWindowImpl = pChild->ImplGetWindowImpl();
+ if (!pWindowImpl)
+ return;
+ if (!pWindowImpl->mpSysObj)
+ return;
+ pWindowImpl->mpSysObj->Show(true);
+ pWindowImpl->mpSysObj->ResetClipRegion();
+ // flag that sysobj clip is dirty and needs to be recalculated on next use
+ pWindowImpl->mbInitWinClipRegion = true;
+}
+
+IMPL_LINK_NOARG(InterimItemWindow, DoLayout, Timer*, void) { Layout(); }
+
+void InterimItemWindow::Layout()
+{
+ m_aLayoutIdle.Stop();
+ vcl::Window* pChild = GetWindow(GetWindowType::FirstChild);
+ assert(pChild);
+ VclContainer::setLayoutAllocation(*pChild, Point(0, 0), GetSizePixel());
+ Control::Resize();
+}
+
+Size InterimItemWindow::GetOptimalSize() const
+{
+ return VclContainer::getLayoutRequisition(*GetWindow(GetWindowType::FirstChild));
+}
+
+void InterimItemWindow::InvalidateChildSizeCache()
+{
+ // find the bottom vcl::Window of the hierarchy and queue_resize on that
+ // one will invalidate all the size caches upwards
+ vcl::Window* pChild = GetWindow(GetWindowType::FirstChild);
+ while (true)
+ {
+ vcl::Window* pSubChild = pChild->GetWindow(GetWindowType::FirstChild);
+ if (!pSubChild)
+ break;
+ pChild = pSubChild;
+ }
+ pChild->queue_resize();
+}
+
+bool InterimItemWindow::ControlHasFocus() const
+{
+ if (!m_pWidget)
+ return false;
+ return m_pWidget->has_focus();
+}
+
+void InterimItemWindow::InitControlBase(weld::Widget* pWidget) { m_pWidget = pWidget; }
+
+void InterimItemWindow::GetFocus()
+{
+ if (m_pWidget)
+ m_pWidget->grab_focus();
+
+ /* let toolbox know this item window has focus so it updates its mnHighItemId to point
+ to this toolitem in case tab means to move to another toolitem within
+ the toolbox
+ */
+ vcl::Window* pToolBox = GetParent();
+ NotifyEvent aNEvt(MouseNotifyEvent::GETFOCUS, this);
+ pToolBox->EventNotify(aNEvt);
+}
+
+bool InterimItemWindow::ChildKeyInput(const KeyEvent& rKEvt)
+{
+ sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
+ if (nCode != KEY_TAB)
+ return false;
+
+ /* if the native widget has focus, then no vcl window has focus.
+
+ We want to grab focus to this vcl widget so that pressing tab will traverse
+ to the next vcl widget.
+
+ But just using GrabFocus will, because no vcl widget has focus, trigger
+ bringing the toplevel to front with the expectation that a suitable widget
+ will be picked for focus when that happen, which is no use to us here.
+
+ SetFakeFocus avoids the problem, allowing GrabFocus to do the expected thing
+ then sending the Tab to our parent will do the right traversal
+ */
+ SetFakeFocus(true);
+ GrabFocus();
+
+ /* now give focus to our toolbox parent */
+ vcl::Window* pToolBox = GetParent();
+ pToolBox->GrabFocus();
+
+ /* let toolbox know this item window has focus so it updates its mnHighItemId to point
+ to this toolitem in case tab means to move to another toolitem within
+ the toolbox
+ */
+ NotifyEvent aNEvt(MouseNotifyEvent::GETFOCUS, this);
+ pToolBox->EventNotify(aNEvt);
+
+ /* send parent the tab */
+ pToolBox->KeyInput(rKEvt);
+
+ return true;
+}
+
+void InterimItemWindow::Draw(OutputDevice* pDevice, const Point& rPos,
+ SystemTextColorFlags /*nFlags*/)
+{
+ m_xContainer->draw(*pDevice, rPos, GetSizePixel());
+}
+
+void InterimItemWindow::ImplPaintToDevice(::OutputDevice* pTargetOutDev, const Point& rPos)
+{
+ Draw(pTargetOutDev, rPos, SystemTextColorFlags::NONE);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/control/NotebookbarPopup.cxx b/vcl/source/control/NotebookbarPopup.cxx
new file mode 100644
index 000000000..3cc21815a
--- /dev/null
+++ b/vcl/source/control/NotebookbarPopup.cxx
@@ -0,0 +1,161 @@
+/* -*- 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/.
+ */
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/layout.hxx>
+#include <IPrioritable.hxx>
+#include <NotebookbarPopup.hxx>
+
+NotebookbarPopup::NotebookbarPopup(const VclPtr<VclHBox>& pParent)
+ : FloatingWindow(pParent, "Popup", "sfx/ui/notebookbarpopup.ui")
+ , m_pParent(pParent)
+{
+ m_pUIBuilder->get(m_pBox, "box");
+ m_pBox->SetSizePixel(Size(100, 75));
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ const BitmapEx& aPersona = rStyleSettings.GetPersonaHeader();
+
+ if (!aPersona.IsEmpty())
+ m_pBox->SetBackground(Wallpaper(aPersona));
+ else
+ m_pBox->SetBackground(rStyleSettings.GetDialogColor());
+}
+
+NotebookbarPopup::~NotebookbarPopup() { disposeOnce(); }
+
+VclHBox* NotebookbarPopup::getBox() { return m_pBox.get(); }
+
+void NotebookbarPopup::PopupModeEnd()
+{
+ hideSeparators(false);
+ while (m_pBox->GetChildCount())
+ {
+ vcl::IPrioritable* pChild = dynamic_cast<vcl::IPrioritable*>(GetChild(0));
+ if (pChild)
+ pChild->HideContent();
+
+ vcl::Window* pWindow = m_pBox->GetChild(0);
+ pWindow->SetParent(m_pParent);
+
+ // resize after all children of box are empty
+ if (m_pParent && !m_pBox->GetChildCount())
+ m_pParent->Resize();
+ }
+
+ FloatingWindow::PopupModeEnd();
+}
+
+void NotebookbarPopup::hideSeparators(bool bHide)
+{
+ // separator on the beginning
+ vcl::Window* pWindow = m_pBox->GetChild(0);
+ while (pWindow && pWindow->GetType() == WindowType::CONTAINER)
+ {
+ pWindow = pWindow->GetChild(0);
+ }
+ if (pWindow && pWindow->GetType() == WindowType::FIXEDLINE)
+ {
+ if (bHide)
+ pWindow->Hide();
+ else
+ pWindow->Show();
+ }
+
+ // separator on the end
+ pWindow = m_pBox->GetChild(m_pBox->GetChildCount() - 1);
+ while (pWindow && pWindow->GetType() == WindowType::CONTAINER)
+ {
+ pWindow = pWindow->GetChild(pWindow->GetChildCount() - 1);
+ }
+ if (pWindow && pWindow->GetType() == WindowType::FIXEDLINE)
+ {
+ if (bHide)
+ pWindow->Hide();
+ else
+ pWindow->Show();
+ }
+
+ if (bHide)
+ {
+ sal_Int32 BoxId = 0;
+ while (BoxId <= m_pBox->GetChildCount() - 1)
+ {
+ if (m_pBox->GetChild(BoxId))
+ {
+ pWindow = m_pBox->GetChild(BoxId);
+ ApplyBackground(pWindow);
+ }
+ BoxId++;
+ }
+ }
+ else
+ {
+ sal_Int32 BoxId = m_pBox->GetChildCount() - 1;
+ while (BoxId >= 0)
+ {
+ if (m_pBox->GetChild(BoxId))
+ {
+ pWindow = m_pBox->GetChild(BoxId);
+ RemoveBackground(pWindow);
+ }
+ BoxId--;
+ }
+ }
+}
+
+void NotebookbarPopup::dispose()
+{
+ PopupModeEnd();
+ m_pBox.disposeAndClear();
+ m_pParent.clear();
+
+ FloatingWindow::dispose();
+}
+
+void NotebookbarPopup::ApplyBackground(vcl::Window* pWindow)
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ const BitmapEx& aPersona = rStyleSettings.GetPersonaHeader();
+
+ if (!aPersona.IsEmpty())
+ pWindow->SetBackground(Wallpaper(aPersona));
+ else
+ pWindow->SetBackground(rStyleSettings.GetDialogColor());
+
+ sal_Int32 nNext = 0;
+ VclPtr<vcl::Window> pChild = pWindow->GetChild(nNext);
+ while (pChild && pWindow->GetType() == WindowType::CONTAINER)
+ {
+ ApplyBackground(pChild);
+ nNext++;
+ if (pWindow->GetChild(nNext) && pWindow->GetType() == WindowType::CONTAINER)
+ pChild = pWindow->GetChild(nNext);
+ else
+ break;
+ }
+}
+
+void NotebookbarPopup::RemoveBackground(vcl::Window* pWindow)
+{
+ pWindow->SetBackground(Wallpaper(COL_TRANSPARENT));
+
+ sal_Int32 nNext = 0;
+ VclPtr<vcl::Window> pChild = pWindow->GetChild(nNext);
+ while (pChild && pWindow->GetType() == WindowType::CONTAINER)
+ {
+ RemoveBackground(pChild);
+ nNext++;
+ if (pWindow->GetChild(nNext) && pWindow->GetType() == WindowType::CONTAINER)
+ pChild = pWindow->GetChild(nNext);
+ else
+ break;
+ }
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/PriorityHBox.cxx b/vcl/source/control/PriorityHBox.cxx
new file mode 100644
index 000000000..c893cc833
--- /dev/null
+++ b/vcl/source/control/PriorityHBox.cxx
@@ -0,0 +1,197 @@
+/* -*- 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 <vcl/layout.hxx>
+#include <PriorityHBox.hxx>
+#include <comphelper/lok.hxx>
+
+namespace
+{
+bool lcl_comparePriority(const vcl::IPrioritable* a, const vcl::IPrioritable* b)
+{
+ return a->GetPriority() < b->GetPriority();
+}
+}
+
+PriorityHBox::PriorityHBox(vcl::Window* pParent)
+ : VclHBox(pParent)
+ , m_bInitialized(false)
+{
+}
+
+PriorityHBox::~PriorityHBox() { disposeOnce(); }
+
+void PriorityHBox::Initialize()
+{
+ m_bInitialized = true;
+
+ GetChildrenWithPriorities();
+ SetSizeFromParent();
+}
+
+int PriorityHBox::GetHiddenCount() const
+{
+ int nCount = 0;
+
+ for (auto pWindow : m_aSortedChildren)
+ if (pWindow->IsHidden())
+ nCount++;
+
+ return nCount;
+}
+
+void PriorityHBox::SetSizeFromParent()
+{
+ vcl::Window* pParent = GetParent();
+ if (pParent)
+ {
+ Size aParentSize = pParent->GetSizePixel();
+ SetSizePixel(Size(aParentSize.getWidth(), aParentSize.getHeight()));
+ }
+}
+
+Size PriorityHBox::calculateRequisition() const
+{
+ if (!m_bInitialized)
+ {
+ return VclHBox::calculateRequisition();
+ }
+
+ sal_uInt16 nVisibleChildren = 0;
+
+ Size aSize;
+ for (vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ ++nVisibleChildren;
+ Size aChildSize = getLayoutRequisition(*pChild);
+
+ bool bAlwaysExpanded = true;
+
+ vcl::IPrioritable* pPrioritable = dynamic_cast<vcl::IPrioritable*>(pChild);
+ if (pPrioritable && pPrioritable->GetPriority() != VCL_PRIORITY_DEFAULT)
+ bAlwaysExpanded = false;
+
+ if (bAlwaysExpanded)
+ {
+ tools::Long nPrimaryDimension = getPrimaryDimension(aChildSize);
+ nPrimaryDimension += pChild->get_padding() * 2;
+ setPrimaryDimension(aChildSize, nPrimaryDimension);
+ }
+ else
+ setPrimaryDimension(aChildSize, 0);
+
+ accumulateMaxes(aChildSize, aSize);
+ }
+
+ return finalizeMaxes(aSize, nVisibleChildren);
+}
+
+void PriorityHBox::Resize()
+{
+ if (!m_bInitialized)
+ Initialize();
+
+ if (!m_bInitialized || comphelper::LibreOfficeKit::isActive())
+ {
+ return VclHBox::Resize();
+ }
+
+ tools::Long nWidth = GetSizePixel().Width();
+ tools::Long nCurrentWidth = VclHBox::calculateRequisition().getWidth();
+
+ // Hide lower priority controls
+ for (vcl::IPrioritable* pPrioritable : m_aSortedChildren)
+ {
+ if (nCurrentWidth <= nWidth)
+ break;
+
+ vcl::Window* pWindow = dynamic_cast<vcl::Window*>(pPrioritable);
+
+ if (pWindow && pWindow->GetParent() == this)
+ {
+ nCurrentWidth -= pWindow->GetOutputSizePixel().Width() + get_spacing();
+ pWindow->Show();
+ pPrioritable->HideContent();
+ nCurrentWidth += pWindow->GetOutputSizePixel().Width() + get_spacing();
+ }
+ }
+
+ auto pChildR = m_aSortedChildren.rbegin();
+ // Show higher priority controls if we already have enough space
+ while (pChildR != m_aSortedChildren.rend())
+ {
+ vcl::Window* pWindow = dynamic_cast<vcl::Window*>(*pChildR);
+ vcl::IPrioritable* pPrioritable = *pChildR;
+
+ if (pWindow->GetParent() != this)
+ {
+ pChildR++;
+ continue;
+ }
+
+ if (pWindow)
+ {
+ nCurrentWidth -= pWindow->GetOutputSizePixel().Width() + get_spacing();
+ pWindow->Show();
+ pPrioritable->ShowContent();
+ nCurrentWidth += getLayoutRequisition(*pWindow).Width() + get_spacing();
+
+ if (nCurrentWidth > nWidth)
+ {
+ pPrioritable->HideContent();
+ break;
+ }
+ }
+
+ pChildR++;
+ }
+
+ VclHBox::Resize();
+}
+
+void PriorityHBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (!m_bInitialized)
+ Initialize();
+
+ VclHBox::Paint(rRenderContext, rRect);
+}
+
+void PriorityHBox::GetChildrenWithPriorities()
+{
+ for (sal_uInt16 i = 0; i < GetChildCount(); ++i)
+ {
+ vcl::Window* pChild = GetChild(i);
+
+ // Add only containers which have explicitly assigned priority.
+ vcl::IPrioritable* pPrioritable = dynamic_cast<vcl::IPrioritable*>(pChild);
+ if (pPrioritable && pPrioritable->GetPriority() != VCL_PRIORITY_DEFAULT)
+ m_aSortedChildren.push_back(pPrioritable);
+ }
+
+ if (m_aSortedChildren.empty())
+ m_bInitialized = false;
+
+ std::sort(m_aSortedChildren.begin(), m_aSortedChildren.end(), lcl_comparePriority);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/PriorityMergedHBox.cxx b/vcl/source/control/PriorityMergedHBox.cxx
new file mode 100644
index 000000000..65d51cce3
--- /dev/null
+++ b/vcl/source/control/PriorityMergedHBox.cxx
@@ -0,0 +1,203 @@
+/* -*- 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 <vcl/toolkit/button.hxx>
+#include <vcl/layout.hxx>
+#include <bitmaps.hlst>
+#include <NotebookbarPopup.hxx>
+#include <PriorityHBox.hxx>
+#include <PriorityMergedHBox.hxx>
+#include <comphelper/lok.hxx>
+
+#define DUMMY_WIDTH 50
+#define BUTTON_WIDTH 30
+
+/*
+* PriorityMergedHBox is a VclHBox which hides its own children if there is no sufficient space.
+*/
+
+PriorityMergedHBox::PriorityMergedHBox(vcl::Window* pParent)
+ : PriorityHBox(pParent)
+{
+ m_pButton = VclPtr<PushButton>::Create(this, WB_FLATBUTTON);
+ m_pButton->SetClickHdl(LINK(this, PriorityMergedHBox, PBClickHdl));
+ m_pButton->SetModeImage(Image(StockImage::Yes, CHEVRON));
+ m_pButton->set_width_request(25);
+ m_pButton->set_pack_type(VclPackType::End);
+ m_pButton->Show();
+}
+
+void PriorityMergedHBox::Resize()
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return VclHBox::Resize();
+
+ if (!m_bInitialized)
+ Initialize();
+
+ if (!m_bInitialized)
+ {
+ return VclHBox::Resize();
+ }
+
+ tools::Long nWidth = GetSizePixel().Width();
+ tools::Long nCurrentWidth = VclHBox::calculateRequisition().getWidth() + BUTTON_WIDTH;
+
+ // Hide lower priority controls
+ for (int i = GetChildCount() - 1; i >= 0; i--)
+ {
+ vcl::Window* pWindow = GetChild(i);
+
+ if (nCurrentWidth <= nWidth)
+ break;
+
+ if (pWindow && pWindow->GetParent() == this && pWindow->IsVisible())
+ {
+ if (pWindow->GetOutputSizePixel().Width())
+ nCurrentWidth -= pWindow->GetOutputSizePixel().Width();
+ else
+ nCurrentWidth -= DUMMY_WIDTH;
+ pWindow->Hide();
+ }
+ }
+
+ // Show higher priority controls if we already have enough space
+ for (int i = 0; i < GetChildCount(); i++)
+ {
+ vcl::Window* pWindow = GetChild(i);
+
+ if (pWindow->GetParent() != this)
+ {
+ continue;
+ }
+
+ if (pWindow && !pWindow->IsVisible())
+ {
+ pWindow->Show();
+ nCurrentWidth += getLayoutRequisition(*pWindow).Width() + get_spacing();
+
+ if (nCurrentWidth > nWidth)
+ {
+ pWindow->Hide();
+ break;
+ }
+ }
+ }
+
+ VclHBox::Resize();
+
+ if (GetHiddenCount())
+ m_pButton->Show();
+ else
+ m_pButton->Hide();
+}
+
+void PriorityMergedHBox::dispose()
+{
+ m_pButton.disposeAndClear();
+ if (m_pPopup)
+ m_pPopup.disposeAndClear();
+ PriorityHBox::dispose();
+}
+
+int PriorityMergedHBox::GetHiddenCount() const
+{
+ int nCount = 0;
+
+ for (int i = GetChildCount() - 1; i >= 0; i--)
+ {
+ vcl::Window* pWindow = GetChild(i);
+ if (pWindow && pWindow->GetParent() == this && !pWindow->IsVisible())
+ nCount++;
+ }
+
+ return nCount;
+}
+
+Size PriorityMergedHBox::calculateRequisition() const
+{
+ if (!m_bInitialized)
+ {
+ return VclHBox::calculateRequisition();
+ }
+
+ sal_uInt16 nVisibleChildren = 0;
+
+ Size aSize;
+ for (vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ ++nVisibleChildren;
+ Size aChildSize = getLayoutRequisition(*pChild);
+
+ tools::Long nPrimaryDimension = getPrimaryDimension(aChildSize);
+ nPrimaryDimension += pChild->get_padding() * 2;
+ setPrimaryDimension(aChildSize, nPrimaryDimension);
+
+ accumulateMaxes(aChildSize, aSize);
+ }
+
+ // find max height
+ for (vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ Size aChildSize = getLayoutRequisition(*pChild);
+ setPrimaryDimension(aChildSize, getPrimaryDimension(aSize));
+ accumulateMaxes(aChildSize, aSize);
+ }
+
+ setPrimaryDimension(aSize, 200);
+ return finalizeMaxes(aSize, nVisibleChildren);
+}
+
+IMPL_LINK(PriorityMergedHBox, PBClickHdl, Button*, /*pButton*/, void)
+{
+ if (m_pPopup)
+ m_pPopup.disposeAndClear();
+
+ m_pPopup = VclPtr<NotebookbarPopup>::Create(this);
+
+ for (int i = 0; i < GetChildCount(); i++)
+ {
+ vcl::Window* pWindow = GetChild(i);
+ if (pWindow != m_pButton)
+ {
+ if (!pWindow->IsVisible())
+ {
+ pWindow->Show();
+ pWindow->SetParent(m_pPopup->getBox());
+ // count is decreased because we moved child
+ i--;
+ }
+ }
+ }
+
+ m_pPopup->hideSeparators(true);
+
+ tools::Long x = m_pButton->GetPosPixel().getX();
+ tools::Long y = m_pButton->GetPosPixel().getY() + GetSizePixel().Height();
+ tools::Rectangle aRect(x, y, x, y);
+
+ m_pPopup->StartPopupMode(aRect, FloatWinPopupFlags::Down | FloatWinPopupFlags::GrabFocus
+ | FloatWinPopupFlags::AllMouseButtonClose);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/WeldedTabbedNotebookbar.cxx b/vcl/source/control/WeldedTabbedNotebookbar.cxx
new file mode 100644
index 000000000..1a3311de9
--- /dev/null
+++ b/vcl/source/control/WeldedTabbedNotebookbar.cxx
@@ -0,0 +1,23 @@
+/* -*- 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/.
+ */
+
+#include <vcl/WeldedTabbedNotebookbar.hxx>
+#include <vcl/svapp.hxx>
+#include <jsdialog/jsdialogbuilder.hxx>
+
+WeldedTabbedNotebookbar::WeldedTabbedNotebookbar(
+ const VclPtr<vcl::Window>& pContainerWindow, const OUString& rUIFilePath,
+ const css::uno::Reference<css::frame::XFrame>& rFrame, sal_uInt64 nWindowId)
+ : m_xBuilder(JSInstanceBuilder::CreateNotebookbarBuilder(
+ pContainerWindow, AllSettings::GetUIRootDir(), rUIFilePath, rFrame, nWindowId))
+{
+ m_xContainer = m_xBuilder->weld_container("NotebookBar");
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/button.cxx b/vcl/source/control/button.cxx
new file mode 100644
index 000000000..210d2b629
--- /dev/null
+++ b/vcl/source/control/button.cxx
@@ -0,0 +1,3826 @@
+/* -*- 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 <tools/poly.hxx>
+
+#include <vcl/builder.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/image.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/toolkit/fixed.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/stdtext.hxx>
+#include <vcl/uitest/uiobject.hxx>
+
+#include <bitmaps.hlst>
+#include <svdata.hxx>
+#include <window.h>
+#include <vclstatuslistener.hxx>
+#include <osl/diagnose.h>
+
+#include <comphelper/base64.hxx>
+#include <comphelper/dispatchcommand.hxx>
+#include <comphelper/lok.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <boost/property_tree/ptree.hpp>
+#include <tools/json_writer.hxx>
+#include <tools/stream.hxx>
+
+
+using namespace css;
+
+constexpr auto PUSHBUTTON_VIEW_STYLE = WB_3DLOOK |
+ WB_LEFT | WB_CENTER | WB_RIGHT |
+ WB_TOP | WB_VCENTER | WB_BOTTOM |
+ WB_WORDBREAK | WB_NOLABEL |
+ WB_DEFBUTTON | WB_NOLIGHTBORDER |
+ WB_RECTSTYLE | WB_SMALLSTYLE |
+ WB_TOGGLE;
+constexpr auto RADIOBUTTON_VIEW_STYLE = WB_3DLOOK |
+ WB_LEFT | WB_CENTER | WB_RIGHT |
+ WB_TOP | WB_VCENTER | WB_BOTTOM |
+ WB_WORDBREAK | WB_NOLABEL;
+constexpr auto CHECKBOX_VIEW_STYLE = WB_3DLOOK |
+ WB_LEFT | WB_CENTER | WB_RIGHT |
+ WB_TOP | WB_VCENTER | WB_BOTTOM |
+ WB_WORDBREAK | WB_NOLABEL;
+
+#define STYLE_RADIOBUTTON_MONO (sal_uInt16(0x0001)) // legacy
+#define STYLE_CHECKBOX_MONO (sal_uInt16(0x0001)) // legacy
+
+class ImplCommonButtonData
+{
+public:
+ ImplCommonButtonData();
+
+ tools::Rectangle maFocusRect;
+ tools::Long mnSeparatorX;
+ DrawButtonFlags mnButtonState;
+ bool mbSmallSymbol;
+ bool mbGeneratedTooltip;
+
+ Image maImage;
+ ImageAlign meImageAlign;
+ SymbolAlign meSymbolAlign;
+
+ Image maCustomContentImage;
+
+ /** StatusListener. Updates the button as the slot state changes */
+ rtl::Reference<VclStatusListener<Button>> mpStatusListener;
+};
+
+ImplCommonButtonData::ImplCommonButtonData() : mnSeparatorX(0), mnButtonState(DrawButtonFlags::NONE),
+mbSmallSymbol(false), mbGeneratedTooltip(false), meImageAlign(ImageAlign::Top), meSymbolAlign(SymbolAlign::LEFT)
+{
+}
+
+Button::Button( WindowType nType ) :
+ Control( nType ),
+ mpButtonData( std::make_unique<ImplCommonButtonData>() )
+{
+}
+
+Button::~Button()
+{
+ disposeOnce();
+}
+
+void Button::dispose()
+{
+ if (mpButtonData->mpStatusListener.is())
+ mpButtonData->mpStatusListener->dispose();
+ Control::dispose();
+}
+
+void Button::SetCommandHandler(const OUString& aCommand)
+{
+ maCommand = aCommand;
+ SetClickHdl( LINK( this, Button, dispatchCommandHandler) );
+
+ mpButtonData->mpStatusListener = new VclStatusListener<Button>(this, aCommand);
+ mpButtonData->mpStatusListener->startListening();
+}
+
+void Button::Click()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ButtonClick, [this] () { maClickHdl.Call(this); } );
+}
+
+void Button::SetModeImage( const Image& rImage )
+{
+ if ( rImage != mpButtonData->maImage )
+ {
+ mpButtonData->maImage = rImage;
+ StateChanged( StateChangedType::Data );
+ queue_resize();
+ }
+}
+
+Image const & Button::GetModeImage( ) const
+{
+ return mpButtonData->maImage;
+}
+
+bool Button::HasImage() const
+{
+ return !!(mpButtonData->maImage);
+}
+
+void Button::SetImageAlign( ImageAlign eAlign )
+{
+ if ( mpButtonData->meImageAlign != eAlign )
+ {
+ mpButtonData->meImageAlign = eAlign;
+ StateChanged( StateChangedType::Data );
+ }
+}
+
+ImageAlign Button::GetImageAlign() const
+{
+ return mpButtonData->meImageAlign;
+}
+
+void Button::SetCustomButtonImage(const Image& rImage)
+{
+ if (rImage != mpButtonData->maCustomContentImage)
+ {
+ mpButtonData->maCustomContentImage = rImage;
+ StateChanged( StateChangedType::Data );
+ }
+}
+
+Image const & Button::GetCustomButtonImage() const
+{
+ return mpButtonData->maCustomContentImage;
+}
+
+tools::Long Button::ImplGetSeparatorX() const
+{
+ return mpButtonData->mnSeparatorX;
+}
+
+void Button::ImplSetSeparatorX( tools::Long nX )
+{
+ mpButtonData->mnSeparatorX = nX;
+}
+
+DrawTextFlags Button::ImplGetTextStyle( WinBits nWinStyle, SystemTextColorFlags nSystemTextColorFlags ) const
+{
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ DrawTextFlags nTextStyle = FixedText::ImplGetTextStyle(nWinStyle & ~WB_DEFBUTTON);
+
+ if (!IsEnabled())
+ nTextStyle |= DrawTextFlags::Disable;
+
+ if ((nSystemTextColorFlags & SystemTextColorFlags::Mono) ||
+ (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
+ {
+ nTextStyle |= DrawTextFlags::Mono;
+ }
+
+ return nTextStyle;
+}
+
+void Button::ImplDrawAlignedImage(OutputDevice* pDev, Point& rPos,
+ Size& rSize,
+ sal_Int32 nImageSep,
+ DrawTextFlags nTextStyle, tools::Rectangle *pSymbolRect,
+ bool bAddImageSep)
+{
+ OUString aText(GetText());
+ bool bDrawImage = HasImage();
+ bool bDrawText = !aText.isEmpty();
+ bool bHasSymbol = pSymbolRect != nullptr;
+
+ // No text and no image => nothing to do => return
+ if (!bDrawImage && !bDrawText && !bHasSymbol)
+ return;
+
+ WinBits nWinStyle = GetStyle();
+ tools::Rectangle aOutRect( rPos, rSize );
+ ImageAlign eImageAlign = mpButtonData->meImageAlign;
+ Size aImageSize = mpButtonData->maImage.GetSizePixel();
+
+ aImageSize.setWidth( CalcZoom( aImageSize.Width() ) );
+ aImageSize.setHeight( CalcZoom( aImageSize.Height() ) );
+
+ // Drawing text or symbol only is simple, use style and output rectangle
+ if (bHasSymbol && !bDrawImage && !bDrawText)
+ {
+ *pSymbolRect = aOutRect;
+ return;
+ }
+ else if (bDrawText && !bDrawImage && !bHasSymbol)
+ {
+ aOutRect = DrawControlText(*pDev, aOutRect, aText, nTextStyle, nullptr, nullptr);
+ tools::Rectangle textRect = GetTextRect(
+ tools::Rectangle(Point(), Size(0x7fffffff, 0x7fffffff)), aText, nTextStyle);
+ // If the button text doesn't fit into it, put it into a tooltip (might happen in sidebar)
+ if (GetQuickHelpText()!= aText && mpButtonData->mbGeneratedTooltip)
+ SetQuickHelpText("");
+ if (GetQuickHelpText().isEmpty() && textRect.getWidth() > rSize.getWidth())
+ {
+ SetQuickHelpText(aText);
+ mpButtonData->mbGeneratedTooltip = true;
+ }
+
+ ImplSetFocusRect(aOutRect);
+ rSize = aOutRect.GetSize();
+ rPos = aOutRect.TopLeft();
+
+ return;
+ }
+
+ // check for HC mode ( image only! )
+ Image* pImage = &(mpButtonData->maImage);
+
+ Size aTextSize;
+ Size aSymbolSize;
+ Size aDeviceTextSize;
+ Point aImagePos = rPos;
+ Point aTextPos = rPos;
+ tools::Rectangle aUnion(aImagePos, aImageSize);
+ tools::Long nSymbolHeight = 0;
+
+ if (bDrawText || bHasSymbol)
+ {
+ // Get the size of the text output area ( the symbol will be drawn in
+ // this area as well, so the symbol rectangle will be calculated here, too )
+
+ tools::Rectangle aRect(Point(), rSize);
+ Size aTSSize;
+
+ if (bHasSymbol)
+ {
+ tools::Rectangle aSymbol;
+ if (bDrawText)
+ {
+ nSymbolHeight = pDev->GetTextHeight();
+ if (mpButtonData->mbSmallSymbol)
+ nSymbolHeight = nSymbolHeight * 3 / 4;
+
+ aSymbol = tools::Rectangle(Point(), Size(nSymbolHeight, nSymbolHeight));
+ ImplCalcSymbolRect(aSymbol);
+ aRect.AdjustLeft(3 * nSymbolHeight / 2 );
+ aTSSize.setWidth( 3 * nSymbolHeight / 2 );
+ }
+ else
+ {
+ aSymbol = tools::Rectangle(Point(), rSize);
+ ImplCalcSymbolRect(aSymbol);
+ aTSSize.setWidth( aSymbol.GetWidth() );
+ }
+ aTSSize.setHeight( aSymbol.GetHeight() );
+ aSymbolSize = aSymbol.GetSize();
+ }
+
+ if (bDrawText)
+ {
+ if ((eImageAlign == ImageAlign::LeftTop) ||
+ (eImageAlign == ImageAlign::Left ) ||
+ (eImageAlign == ImageAlign::LeftBottom) ||
+ (eImageAlign == ImageAlign::RightTop) ||
+ (eImageAlign == ImageAlign::Right) ||
+ (eImageAlign == ImageAlign::RightBottom))
+ {
+ aRect.AdjustRight( -sal_Int32(aImageSize.Width() + nImageSep) );
+ }
+ else if ((eImageAlign == ImageAlign::TopLeft) ||
+ (eImageAlign == ImageAlign::Top) ||
+ (eImageAlign == ImageAlign::TopRight) ||
+ (eImageAlign == ImageAlign::BottomLeft) ||
+ (eImageAlign == ImageAlign::Bottom) ||
+ (eImageAlign == ImageAlign::BottomRight))
+ {
+ aRect.AdjustBottom( -sal_Int32(aImageSize.Height() + nImageSep) );
+ }
+
+ aRect = GetControlTextRect(*pDev, aRect, aText, nTextStyle, &aDeviceTextSize);
+ aTextSize = aRect.GetSize();
+
+ aTSSize.AdjustWidth(aTextSize.Width() );
+
+ if (aTSSize.Height() < aTextSize.Height())
+ aTSSize.setHeight( aTextSize.Height() );
+
+ if (bAddImageSep && bDrawImage)
+ {
+ tools::Long nDiff = (aImageSize.Height() - aTextSize.Height()) / 3;
+ if (nDiff > 0)
+ nImageSep += nDiff;
+ }
+ }
+
+ Size aMax;
+ aMax.setWidth( std::max(aTSSize.Width(), aImageSize.Width()) );
+ aMax.setHeight( std::max(aTSSize.Height(), aImageSize.Height()) );
+
+ // Now calculate the output area for the image and the text according to the image align flags
+
+ if ((eImageAlign == ImageAlign::Left) ||
+ (eImageAlign == ImageAlign::Right))
+ {
+ aImagePos.setY( rPos.Y() + (aMax.Height() - aImageSize.Height()) / 2 );
+ aTextPos.setY( rPos.Y() + (aMax.Height() - aTSSize.Height()) / 2 );
+ }
+ else if ((eImageAlign == ImageAlign::LeftBottom) ||
+ (eImageAlign == ImageAlign::RightBottom))
+ {
+ aImagePos.setY( rPos.Y() + aMax.Height() - aImageSize.Height() );
+ aTextPos.setY( rPos.Y() + aMax.Height() - aTSSize.Height() );
+ }
+ else if ((eImageAlign == ImageAlign::Top) ||
+ (eImageAlign == ImageAlign::Bottom))
+ {
+ aImagePos.setX( rPos.X() + (aMax.Width() - aImageSize.Width()) / 2 );
+ aTextPos.setX( rPos.X() + (aMax.Width() - aTSSize.Width()) / 2 );
+ }
+ else if ((eImageAlign == ImageAlign::TopRight) ||
+ (eImageAlign == ImageAlign::BottomRight))
+ {
+ aImagePos.setX( rPos.X() + aMax.Width() - aImageSize.Width() );
+ aTextPos.setX( rPos.X() + aMax.Width() - aTSSize.Width() );
+ }
+
+ if ((eImageAlign == ImageAlign::LeftTop) ||
+ (eImageAlign == ImageAlign::Left) ||
+ (eImageAlign == ImageAlign::LeftBottom))
+ {
+ aTextPos.setX( rPos.X() + aImageSize.Width() + nImageSep );
+ }
+ else if ((eImageAlign == ImageAlign::RightTop) ||
+ (eImageAlign == ImageAlign::Right) ||
+ (eImageAlign == ImageAlign::RightBottom))
+ {
+ aImagePos.setX( rPos.X() + aTSSize.Width() + nImageSep );
+ }
+ else if ((eImageAlign == ImageAlign::TopLeft) ||
+ (eImageAlign == ImageAlign::Top) ||
+ (eImageAlign == ImageAlign::TopRight))
+ {
+ aTextPos.setY( rPos.Y() + aImageSize.Height() + nImageSep );
+ }
+ else if ((eImageAlign == ImageAlign::BottomLeft) ||
+ (eImageAlign == ImageAlign::Bottom) ||
+ (eImageAlign == ImageAlign::BottomRight))
+ {
+ aImagePos.setY( rPos.Y() + aTSSize.Height() + nImageSep );
+ }
+ else if (eImageAlign == ImageAlign::Center)
+ {
+ aImagePos.setX( rPos.X() + (aMax.Width() - aImageSize.Width()) / 2 );
+ aImagePos.setY( rPos.Y() + (aMax.Height() - aImageSize.Height()) / 2 );
+ aTextPos.setX( rPos.X() + (aMax.Width() - aTSSize.Width()) / 2 );
+ aTextPos.setY( rPos.Y() + (aMax.Height() - aTSSize.Height()) / 2 );
+ }
+ aUnion = tools::Rectangle(aImagePos, aImageSize);
+ aUnion.Union(tools::Rectangle(aTextPos, aTSSize));
+ }
+
+ // Now place the combination of text and image in the output area of the button
+ // according to the window style (WinBits)
+ tools::Long nXOffset = 0;
+ tools::Long nYOffset = 0;
+
+ if (nWinStyle & WB_CENTER)
+ {
+ nXOffset = (rSize.Width() - aUnion.GetWidth()) / 2;
+ }
+ else if (nWinStyle & WB_RIGHT)
+ {
+ nXOffset = rSize.Width() - aUnion.GetWidth();
+ }
+
+ if (nWinStyle & WB_VCENTER)
+ {
+ nYOffset = (rSize.Height() - aUnion.GetHeight()) / 2;
+ }
+ else if (nWinStyle & WB_BOTTOM)
+ {
+ nYOffset = rSize.Height() - aUnion.GetHeight();
+ }
+
+ // the top left corner should always be visible, so we don't allow negative offsets
+ if (nXOffset < 0) nXOffset = 0;
+ if (nYOffset < 0) nYOffset = 0;
+
+ aImagePos.AdjustX(nXOffset );
+ aImagePos.AdjustY(nYOffset );
+ aTextPos.AdjustX(nXOffset );
+ aTextPos.AdjustY(nYOffset );
+
+ // set rPos and rSize to the union
+ rSize = aUnion.GetSize();
+ rPos.AdjustX(nXOffset );
+ rPos.AdjustY(nYOffset );
+
+ if (bHasSymbol)
+ {
+ if (mpButtonData->meSymbolAlign == SymbolAlign::RIGHT)
+ {
+ Point aRightPos(aTextPos.X() + aTextSize.Width() + aSymbolSize.Width() / 2, aTextPos.Y());
+ *pSymbolRect = tools::Rectangle(aRightPos, aSymbolSize);
+ }
+ else
+ {
+ *pSymbolRect = tools::Rectangle(aTextPos, aSymbolSize);
+ aTextPos.AdjustX(3 * nSymbolHeight / 2 );
+ }
+ if (mpButtonData->mbSmallSymbol)
+ {
+ nYOffset = (aUnion.GetHeight() - aSymbolSize.Height()) / 2;
+ pSymbolRect->SetPosY(aTextPos.Y() + nYOffset);
+ }
+ }
+
+ DrawImageFlags nStyle = DrawImageFlags::NONE;
+
+ if (!IsEnabled())
+ {
+ nStyle |= DrawImageFlags::Disable;
+ }
+
+ if (IsZoom())
+ pDev->DrawImage(aImagePos, aImageSize, *pImage, nStyle);
+ else
+ pDev->DrawImage(aImagePos, *pImage, nStyle);
+
+ if (bDrawText)
+ {
+ const tools::Rectangle aTOutRect(aTextPos, aTextSize);
+ ImplSetFocusRect(aTOutRect);
+ DrawControlText(*pDev, aTOutRect, aText, nTextStyle, nullptr, nullptr, &aDeviceTextSize);
+ }
+ else
+ {
+ ImplSetFocusRect(tools::Rectangle(aImagePos, aImageSize));
+ }
+}
+
+void Button::ImplSetFocusRect(const tools::Rectangle &rFocusRect)
+{
+ tools::Rectangle aFocusRect = rFocusRect;
+ tools::Rectangle aOutputRect(Point(), GetOutputSizePixel());
+
+ if (!aFocusRect.IsEmpty())
+ {
+ aFocusRect.AdjustLeft( -1 );
+ aFocusRect.AdjustTop( -1 );
+ aFocusRect.AdjustRight( 1 );
+ aFocusRect.AdjustBottom( 1 );
+ }
+
+ if (aFocusRect.Left() < aOutputRect.Left())
+ aFocusRect.SetLeft( aOutputRect.Left() );
+ if (aFocusRect.Top() < aOutputRect.Top())
+ aFocusRect.SetTop( aOutputRect.Top() );
+ if (aFocusRect.Right() > aOutputRect.Right())
+ aFocusRect.SetRight( aOutputRect.Right() );
+ if (aFocusRect.Bottom() > aOutputRect.Bottom())
+ aFocusRect.SetBottom( aOutputRect.Bottom() );
+
+ mpButtonData->maFocusRect = aFocusRect;
+}
+
+const tools::Rectangle& Button::ImplGetFocusRect() const
+{
+ return mpButtonData->maFocusRect;
+}
+
+DrawButtonFlags& Button::GetButtonState()
+{
+ return mpButtonData->mnButtonState;
+}
+
+DrawButtonFlags Button::GetButtonState() const
+{
+ return mpButtonData->mnButtonState;
+}
+
+void Button::ImplSetSymbolAlign( SymbolAlign eAlign )
+{
+ if ( mpButtonData->meSymbolAlign != eAlign )
+ {
+ mpButtonData->meSymbolAlign = eAlign;
+ StateChanged( StateChangedType::Data );
+ }
+}
+
+void Button::SetSmallSymbol()
+{
+ mpButtonData->mbSmallSymbol = true;
+}
+
+bool Button::IsSmallSymbol () const
+{
+ return mpButtonData->mbSmallSymbol;
+}
+
+bool Button::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "image-position")
+ {
+ ImageAlign eAlign = ImageAlign::Left;
+ if (rValue == "left")
+ eAlign = ImageAlign::Left;
+ else if (rValue == "right")
+ eAlign = ImageAlign::Right;
+ else if (rValue == "top")
+ eAlign = ImageAlign::Top;
+ else if (rValue == "bottom")
+ eAlign = ImageAlign::Bottom;
+ SetImageAlign(eAlign);
+ }
+ else if (rKey == "focus-on-click")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~WB_NOPOINTERFOCUS;
+ if (!toBool(rValue))
+ nBits |= WB_NOPOINTERFOCUS;
+ SetStyle(nBits);
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+void Button::statusChanged(const css::frame::FeatureStateEvent& rEvent)
+{
+ Enable(rEvent.IsEnabled);
+}
+
+FactoryFunction Button::GetUITestFactory() const
+{
+ return ButtonUIObject::create;
+}
+
+namespace
+{
+
+const char* symbolTypeName(SymbolType eSymbolType)
+{
+ switch (eSymbolType)
+ {
+ case SymbolType::DONTKNOW: return "DONTKNOW";
+ case SymbolType::IMAGE: return "IMAGE";
+ case SymbolType::ARROW_UP: return "ARROW_UP";
+ case SymbolType::ARROW_DOWN: return "ARROW_DOWN";
+ case SymbolType::ARROW_LEFT: return "ARROW_LEFT";
+ case SymbolType::ARROW_RIGHT: return "ARROW_RIGHT";
+ case SymbolType::SPIN_UP: return "SPIN_UP";
+ case SymbolType::SPIN_DOWN: return "SPIN_DOWN";
+ case SymbolType::SPIN_LEFT: return "SPIN_LEFT";
+ case SymbolType::SPIN_RIGHT: return "SPIN_RIGHT";
+ case SymbolType::FIRST: return "FIRST";
+ case SymbolType::LAST: return "LAST";
+ case SymbolType::PREV: return "PREV";
+ case SymbolType::NEXT: return "NEXT";
+ case SymbolType::PAGEUP: return "PAGEUP";
+ case SymbolType::PAGEDOWN: return "PAGEDOWN";
+ case SymbolType::PLAY: return "PLAY";
+ case SymbolType::STOP: return "STOP";
+ case SymbolType::CLOSE: return "CLOSE";
+ case SymbolType::CHECKMARK: return "CHECKMARK";
+ case SymbolType::RADIOCHECKMARK: return "RADIOCHECKMARK";
+ case SymbolType::FLOAT: return "FLOAT";
+ case SymbolType::DOCK: return "DOCK";
+ case SymbolType::HIDE: return "HIDE";
+ case SymbolType::HELP: return "HELP";
+ case SymbolType::PLUS: return "PLUS";
+ }
+
+ return "UNKNOWN";
+}
+
+}
+
+void Button::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("text", GetText());
+ if (HasImage())
+ {
+ SvMemoryStream aOStm(6535, 6535);
+ if(GraphicConverter::Export(aOStm, GetModeImage().GetBitmapEx(), ConvertDataFormat::PNG) == ERRCODE_NONE)
+ {
+ css::uno::Sequence<sal_Int8> aSeq( static_cast<sal_Int8 const *>(aOStm.GetData()), aOStm.Tell());
+ OUStringBuffer aBuffer("data:image/png;base64,");
+ ::comphelper::Base64::encode(aBuffer, aSeq);
+ rJsonWriter.put("image", aBuffer.makeStringAndClear());
+ }
+ }
+
+ if (GetStyle() & WB_DEFBUTTON)
+ rJsonWriter.put("has_default", true);
+}
+
+void PushButton::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Button::DumpAsPropertyTree(rJsonWriter);
+ if (GetSymbol() != SymbolType::DONTKNOW)
+ rJsonWriter.put("symbol", symbolTypeName(GetSymbol()));
+}
+
+IMPL_STATIC_LINK( Button, dispatchCommandHandler, Button*, pButton, void )
+{
+ if (pButton == nullptr)
+ return;
+
+ comphelper::dispatchCommand(pButton->maCommand, uno::Sequence<beans::PropertyValue>());
+}
+
+void PushButton::ImplInitPushButtonData()
+{
+ mpWindowImpl->mbPushButton = true;
+
+ meSymbol = SymbolType::DONTKNOW;
+ meState = TRISTATE_FALSE;
+ mnDDStyle = PushButtonDropdownStyle::NONE;
+ mbIsActive = false;
+ mbPressed = false;
+ mbIsAction = false;
+}
+
+namespace
+{
+ vcl::Window* getPreviousSibling(vcl::Window const *pParent)
+ {
+ return pParent ? pParent->GetWindow(GetWindowType::LastChild) : nullptr;
+ }
+}
+
+void PushButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle(getPreviousSibling(pParent), nStyle);
+ Button::ImplInit( pParent, nStyle, nullptr );
+
+ if ( nStyle & WB_NOLIGHTBORDER )
+ GetButtonState() |= DrawButtonFlags::NoLightBorder;
+
+ ImplInitSettings( true );
+}
+
+WinBits PushButton::ImplInitStyle( const vcl::Window* pPrevWindow, WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+
+ // if no alignment is given, default to "vertically centered". This is because since
+ // #i26046#, we respect the vertical alignment flags (previously we didn't completely),
+ // but we of course want to look as before when no vertical alignment is specified
+ if ( ( nStyle & ( WB_TOP | WB_VCENTER | WB_BOTTOM ) ) == 0 )
+ nStyle |= WB_VCENTER;
+
+ if ( !(nStyle & WB_NOGROUP) &&
+ (!pPrevWindow ||
+ ((pPrevWindow->GetType() != WindowType::PUSHBUTTON ) &&
+ (pPrevWindow->GetType() != WindowType::OKBUTTON ) &&
+ (pPrevWindow->GetType() != WindowType::CANCELBUTTON) &&
+ (pPrevWindow->GetType() != WindowType::HELPBUTTON )) ) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+const vcl::Font& PushButton::GetCanonicalFont( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetPushButtonFont();
+}
+
+const Color& PushButton::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetButtonTextColor();
+}
+
+void PushButton::ImplInitSettings( bool bBackground )
+{
+ Button::ImplInitSettings();
+
+ if ( !bBackground )
+ return;
+
+ SetBackground();
+ // #i38498#: do not check for GetParent()->IsChildTransparentModeEnabled()
+ // otherwise the formcontrol button will be overdrawn due to ParentClipMode::NoClip
+ // for radio and checkbox this is ok as they should appear transparent in documents
+ if ( IsNativeControlSupported( ControlType::Pushbutton, ControlPart::Entire ) ||
+ (GetStyle() & WB_FLATBUTTON) != 0 )
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode( ParentClipMode::NoClip );
+ SetPaintTransparent( true );
+
+ if ((GetStyle() & WB_FLATBUTTON) == 0)
+ mpWindowImpl->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects;
+ else
+ mpWindowImpl->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRectsForFlatButtons;
+ }
+ else
+ {
+ EnableChildTransparentMode( false );
+ SetParentClipMode();
+ SetPaintTransparent( false );
+ }
+}
+
+void PushButton::ImplDrawPushButtonFrame(vcl::RenderContext& rRenderContext,
+ tools::Rectangle& rRect, DrawButtonFlags nStyle)
+{
+ if (!(GetStyle() & (WB_RECTSTYLE | WB_SMALLSTYLE)))
+ {
+ StyleSettings aStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ if (IsControlBackground())
+ aStyleSettings.Set3DColors(GetControlBackground());
+ }
+
+ DecorationView aDecoView(&rRenderContext);
+ if (IsControlBackground())
+ {
+ AllSettings aSettings = rRenderContext.GetSettings();
+ AllSettings aOldSettings = aSettings;
+ StyleSettings aStyleSettings = aSettings.GetStyleSettings();
+ if (nStyle & DrawButtonFlags::Highlight)
+ {
+ // with the custom background, native highlight do nothing, so code below mimic
+ // native highlight by changing luminance
+ Color controlBackgroundColorHighlighted = GetControlBackground();
+ sal_uInt8 colorLuminance = controlBackgroundColorHighlighted.GetLuminance();
+ if (colorLuminance < 205)
+ controlBackgroundColorHighlighted.IncreaseLuminance(50);
+ else
+ controlBackgroundColorHighlighted.DecreaseLuminance(50);
+ aStyleSettings.Set3DColors(controlBackgroundColorHighlighted);
+ }
+ else
+ aStyleSettings.Set3DColors(GetControlBackground());
+ aSettings.SetStyleSettings(aStyleSettings);
+
+ // Call OutputDevice::SetSettings() explicitly, as rRenderContext may
+ // be a vcl::Window in fact, and vcl::Window::SetSettings() will call
+ // Invalidate(), which is a problem, since we're in Paint().
+ rRenderContext.OutputDevice::SetSettings(aSettings);
+ rRect = aDecoView.DrawButton(rRect, nStyle);
+ rRenderContext.OutputDevice::SetSettings(aOldSettings);
+ }
+ else
+ rRect = aDecoView.DrawButton(rRect, nStyle);
+}
+
+bool PushButton::ImplHitTestPushButton( vcl::Window const * pDev,
+ const Point& rPos )
+{
+ tools::Rectangle aTestRect( Point(), pDev->GetOutputSizePixel() );
+
+ return aTestRect.Contains( rPos );
+}
+
+DrawTextFlags PushButton::ImplGetTextStyle( SystemTextColorFlags nSystemTextColorFlags ) const
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+
+ DrawTextFlags nTextStyle = DrawTextFlags::Mnemonic | DrawTextFlags::MultiLine | DrawTextFlags::EndEllipsis;
+
+ if ( ( rStyleSettings.GetOptions() & StyleSettingsOptions::Mono ) ||
+ ( nSystemTextColorFlags & SystemTextColorFlags::Mono ) )
+ nTextStyle |= DrawTextFlags::Mono;
+
+ if ( GetStyle() & WB_WORDBREAK )
+ nTextStyle |= DrawTextFlags::WordBreak;
+ if ( GetStyle() & WB_NOLABEL )
+ nTextStyle &= ~DrawTextFlags::Mnemonic;
+
+ if ( GetStyle() & WB_LEFT )
+ nTextStyle |= DrawTextFlags::Left;
+ else if ( GetStyle() & WB_RIGHT )
+ nTextStyle |= DrawTextFlags::Right;
+ else
+ nTextStyle |= DrawTextFlags::Center;
+
+ if ( GetStyle() & WB_TOP )
+ nTextStyle |= DrawTextFlags::Top;
+ else if ( GetStyle() & WB_BOTTOM )
+ nTextStyle |= DrawTextFlags::Bottom;
+ else
+ nTextStyle |= DrawTextFlags::VCenter;
+
+ if ( !IsEnabled() )
+ nTextStyle |= DrawTextFlags::Disable;
+
+ return nTextStyle;
+}
+
+void PushButton::ImplDrawPushButtonContent(OutputDevice *pDev, SystemTextColorFlags nSystemTextColorFlags,
+ const tools::Rectangle &rRect, bool bMenuBtnSep,
+ DrawButtonFlags nButtonFlags)
+{
+ const StyleSettings &rStyleSettings = GetSettings().GetStyleSettings();
+ tools::Rectangle aInRect = rRect;
+ Color aColor;
+ DrawTextFlags nTextStyle = ImplGetTextStyle(nSystemTextColorFlags);
+ DrawSymbolFlags nStyle;
+
+ if (aInRect.Right() < aInRect.Left() || aInRect.Bottom() < aInRect.Top())
+ return;
+
+ pDev->Push(vcl::PushFlags::CLIPREGION);
+ pDev->IntersectClipRegion(aInRect);
+
+ if (nSystemTextColorFlags & SystemTextColorFlags::Mono)
+ aColor = COL_BLACK;
+
+ else if (IsControlForeground())
+ aColor = GetControlForeground();
+
+ // Button types with possibly different text coloring are flat buttons and regular buttons. Regular buttons may be action
+ // buttons and may have an additional default status. Moreover all buttons may have an additional pressed and rollover
+ // (highlight) status. Pressed buttons are always in rollover status.
+
+ else if (GetStyle() & WB_FLATBUTTON)
+ if (nButtonFlags & DrawButtonFlags::Pressed)
+ aColor = rStyleSettings.GetFlatButtonPressedRolloverTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Highlight)
+ aColor = rStyleSettings.GetFlatButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetFlatButtonTextColor();
+ else
+ if (isAction() && (nButtonFlags & DrawButtonFlags::Default))
+ if (nButtonFlags & DrawButtonFlags::Pressed)
+ aColor = rStyleSettings.GetDefaultActionButtonPressedRolloverTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Highlight)
+ aColor = rStyleSettings.GetDefaultActionButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetDefaultActionButtonTextColor();
+ else if (isAction())
+ if (nButtonFlags & DrawButtonFlags::Pressed)
+ aColor = rStyleSettings.GetActionButtonPressedRolloverTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Highlight)
+ aColor = rStyleSettings.GetActionButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetActionButtonTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Default)
+ if (nButtonFlags & DrawButtonFlags::Pressed)
+ aColor = rStyleSettings.GetDefaultButtonPressedRolloverTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Highlight)
+ aColor = rStyleSettings.GetDefaultButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetDefaultButtonTextColor();
+ else
+ if (nButtonFlags & DrawButtonFlags::Pressed)
+ aColor = rStyleSettings.GetButtonPressedRolloverTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Highlight)
+ aColor = rStyleSettings.GetButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetButtonTextColor();
+
+ pDev->SetTextColor(aColor);
+
+ if ( IsEnabled() )
+ nStyle = DrawSymbolFlags::NONE;
+ else
+ nStyle = DrawSymbolFlags::Disable;
+
+ Size aSize = rRect.GetSize();
+ Point aPos = rRect.TopLeft();
+
+ sal_Int32 nImageSep = 1 + (pDev->GetTextHeight()-10)/2;
+ if( nImageSep < 1 )
+ nImageSep = 1;
+ if ( mnDDStyle == PushButtonDropdownStyle::MenuButton ||
+ mnDDStyle == PushButtonDropdownStyle::SplitMenuButton )
+ {
+ tools::Long nSeparatorX = 0;
+ tools::Rectangle aSymbolRect = aInRect;
+
+ // calculate symbol size
+ tools::Long nSymbolSize = pDev->GetTextHeight() / 2 + 1;
+ if (nSymbolSize > aSize.Width() / 2)
+ nSymbolSize = aSize.Width() / 2;
+
+ nSeparatorX = aInRect.Right() - 2*nSymbolSize;
+
+ // tdf#141761 Minimum width should be (1) Pixel, see comment
+ // with same task number above for more info
+ const tools::Long nWidthAdjust(2*nSymbolSize);
+ aSize.setWidth(std::max(static_cast<tools::Long>(1), aSize.getWidth() - nWidthAdjust));
+
+ // center symbol rectangle in the separated area
+ aSymbolRect.AdjustRight( -(nSymbolSize/2) );
+ aSymbolRect.SetLeft( aSymbolRect.Right() - nSymbolSize );
+
+ ImplDrawAlignedImage( pDev, aPos, aSize, nImageSep,
+ nTextStyle, nullptr, true );
+
+ tools::Long nDistance = (aSymbolRect.GetHeight() > 10) ? 2 : 1;
+ DecorationView aDecoView( pDev );
+ if( bMenuBtnSep && nSeparatorX > 0 )
+ {
+ Point aStartPt( nSeparatorX, aSymbolRect.Top()+nDistance );
+ Point aEndPt( nSeparatorX, aSymbolRect.Bottom()-nDistance );
+ aDecoView.DrawSeparator( aStartPt, aEndPt );
+ }
+ ImplSetSeparatorX( nSeparatorX );
+
+ aDecoView.DrawSymbol( aSymbolRect, SymbolType::SPIN_DOWN, aColor, nStyle );
+
+ }
+ else
+ {
+ tools::Rectangle aSymbolRect;
+ ImplDrawAlignedImage( pDev, aPos, aSize, nImageSep,
+ nTextStyle, IsSymbol() ? &aSymbolRect : nullptr, true );
+
+ if ( IsSymbol() )
+ {
+ DecorationView aDecoView( pDev );
+ aDecoView.DrawSymbol( aSymbolRect, meSymbol, aColor, nStyle );
+ }
+ }
+
+ pDev->Pop(); // restore clipregion
+}
+
+void PushButton::ImplDrawPushButton(vcl::RenderContext& rRenderContext)
+{
+ HideFocus();
+
+ DrawButtonFlags nButtonStyle = GetButtonState();
+ Size aOutSz(GetOutputSizePixel());
+ tools::Rectangle aRect(Point(), aOutSz);
+ tools::Rectangle aInRect = aRect;
+ bool bNativeOK = false;
+
+ // adjust style if button should be rendered 'pressed'
+ if (mbPressed || mbIsActive)
+ nButtonStyle |= DrawButtonFlags::Pressed;
+
+ // TODO: move this to Window class or make it a member !!!
+ ControlType aCtrlType = ControlType::Generic;
+ switch(GetParent()->GetType())
+ {
+ case WindowType::LISTBOX:
+ case WindowType::MULTILISTBOX:
+ case WindowType::TREELISTBOX:
+ aCtrlType = ControlType::Listbox;
+ break;
+
+ case WindowType::COMBOBOX:
+ case WindowType::PATTERNBOX:
+ case WindowType::NUMERICBOX:
+ case WindowType::METRICBOX:
+ case WindowType::CURRENCYBOX:
+ case WindowType::DATEBOX:
+ case WindowType::TIMEBOX:
+ case WindowType::LONGCURRENCYBOX:
+ aCtrlType = ControlType::Combobox;
+ break;
+ default:
+ break;
+ }
+
+ bool bDropDown = (IsSymbol() && (GetSymbol() == SymbolType::SPIN_DOWN) && GetText().isEmpty());
+
+ if( bDropDown && (aCtrlType == ControlType::Combobox || aCtrlType == ControlType::Listbox))
+ {
+ if (GetParent()->IsNativeControlSupported(aCtrlType, ControlPart::Entire))
+ {
+ // skip painting if the button was already drawn by the theme
+ if (aCtrlType == ControlType::Combobox)
+ {
+ Edit* pEdit = static_cast<Edit*>(GetParent());
+ if (pEdit->ImplUseNativeBorder(rRenderContext, pEdit->GetStyle()))
+ bNativeOK = true;
+ }
+ else if (GetParent()->IsNativeControlSupported(aCtrlType, ControlPart::HasBackgroundTexture))
+ {
+ bNativeOK = true;
+ }
+
+ if (!bNativeOK && GetParent()->IsNativeControlSupported(aCtrlType, ControlPart::ButtonDown))
+ {
+ // let the theme draw it, note we then need support
+ // for ControlType::Listbox/ControlPart::ButtonDown and ControlType::Combobox/ControlPart::ButtonDown
+
+ ImplControlValue aControlValue;
+ ControlState nState = ControlState::NONE;
+
+ if (mbPressed || mbIsActive)
+ nState |= ControlState::PRESSED;
+ if (GetButtonState() & DrawButtonFlags::Pressed)
+ nState |= ControlState::PRESSED;
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+ if (GetButtonState() & DrawButtonFlags::Default)
+ nState |= ControlState::DEFAULT;
+ if (Window::IsEnabled())
+ nState |= ControlState::ENABLED;
+
+ if (IsMouseOver() && aInRect.Contains(GetPointerPosPixel()))
+ nState |= ControlState::ROLLOVER;
+
+ if ( IsMouseOver() && aInRect.Contains(GetPointerPosPixel()) && mbIsActive)
+ {
+ nState |= ControlState::ROLLOVER;
+ nButtonStyle &= ~DrawButtonFlags::Pressed;
+ }
+
+ bNativeOK = rRenderContext.DrawNativeControl(aCtrlType, ControlPart::ButtonDown, aInRect, nState,
+ aControlValue, OUString());
+ }
+ }
+ }
+
+ if (bNativeOK)
+ return;
+
+ bool bRollOver = (IsMouseOver() && aInRect.Contains(GetPointerPosPixel()));
+ if (bRollOver)
+ nButtonStyle |= DrawButtonFlags::Highlight;
+ bool bDrawMenuSep = mnDDStyle == PushButtonDropdownStyle::SplitMenuButton;
+ if (GetStyle() & WB_FLATBUTTON)
+ {
+ if (!bRollOver && !HasFocus())
+ bDrawMenuSep = false;
+ }
+ // tdf#123175 if there is a custom control bg set, draw the button without outsourcing to the NWF
+ bNativeOK = !IsControlBackground() && rRenderContext.IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Entire);
+ if (bNativeOK)
+ {
+ PushButtonValue aControlValue;
+ aControlValue.mbIsAction = isAction();
+
+ tools::Rectangle aCtrlRegion(aInRect);
+ ControlState nState = ControlState::NONE;
+
+ if (mbPressed || IsChecked() || mbIsActive)
+ {
+ nState |= ControlState::PRESSED;
+ nButtonStyle |= DrawButtonFlags::Pressed;
+ }
+ if (GetButtonState() & DrawButtonFlags::Pressed)
+ nState |= ControlState::PRESSED;
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+ if (GetButtonState() & DrawButtonFlags::Default)
+ nState |= ControlState::DEFAULT;
+ if (Window::IsEnabled())
+ nState |= ControlState::ENABLED;
+
+ if (bRollOver || mbIsActive)
+ {
+ nButtonStyle |= DrawButtonFlags::Highlight;
+ nState |= ControlState::ROLLOVER;
+ }
+
+ if (mbIsActive && bRollOver)
+ {
+ nState &= ~ControlState::PRESSED;
+ nButtonStyle &= ~DrawButtonFlags::Pressed;
+ }
+
+ if (GetStyle() & WB_FLATBUTTON)
+ aControlValue.m_bFlatButton = true;
+ if (GetStyle() & WB_BEVELBUTTON)
+ aControlValue.mbBevelButton = true;
+
+ // draw frame into invisible window to have aInRect modified correctly
+ // but do not shift the inner rect for pressed buttons (ie remove DrawButtonFlags::Pressed)
+ // this assumes the theme has enough visual cues to signalize the button was pressed
+ //Window aWin( this );
+ //ImplDrawPushButtonFrame( &aWin, aInRect, nButtonStyle & ~DrawButtonFlags::Pressed );
+
+ // looks better this way as symbols were displaced slightly using the above approach
+ aInRect.AdjustTop(4 );
+ aInRect.AdjustBottom( -4 );
+ aInRect.AdjustLeft(4 );
+ aInRect.AdjustRight( -4 );
+
+ // prepare single line hint (needed on mac to decide between normal push button and
+ // rectangular bevel button look)
+ Size aFontSize(Application::GetSettings().GetStyleSettings().GetPushButtonFont().GetFontSize());
+ aFontSize = rRenderContext.LogicToPixel(aFontSize, MapMode(MapUnit::MapPoint));
+ Size aInRectSize(rRenderContext.LogicToPixel(Size(aInRect.GetWidth(), aInRect.GetHeight())));
+ aControlValue.mbSingleLine = (aInRectSize.Height() < 2 * aFontSize.Height());
+
+ if ((nState & ControlState::ROLLOVER) || !(GetStyle() & WB_FLATBUTTON)
+ || (HasFocus() && mpWindowImpl->mbUseNativeFocus
+ && !IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Focus)))
+ {
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Pushbutton, ControlPart::Entire, aCtrlRegion, nState,
+ aControlValue, OUString() /*PushButton::GetText()*/);
+ }
+ else
+ {
+ bNativeOK = true;
+ }
+
+ // draw content using the same aInRect as non-native VCL would do
+ ImplDrawPushButtonContent(&rRenderContext, SystemTextColorFlags::NONE,
+ aInRect, bDrawMenuSep, nButtonStyle);
+
+ if (HasFocus())
+ ShowFocus(ImplGetFocusRect());
+ }
+
+ if (bNativeOK)
+ return;
+
+ // draw PushButtonFrame, aInRect has content size afterwards
+ if (GetStyle() & WB_FLATBUTTON)
+ {
+ tools::Rectangle aTempRect(aInRect);
+ if (bRollOver)
+ ImplDrawPushButtonFrame(rRenderContext, aTempRect, nButtonStyle);
+ aInRect.AdjustLeft(2 );
+ aInRect.AdjustTop(2 );
+ aInRect.AdjustRight( -2 );
+ aInRect.AdjustBottom( -2 );
+ }
+ else
+ {
+ ImplDrawPushButtonFrame(rRenderContext, aInRect, nButtonStyle);
+ }
+
+ // draw content
+ ImplDrawPushButtonContent(&rRenderContext, SystemTextColorFlags::NONE, aInRect, bDrawMenuSep, nButtonStyle);
+
+ if (HasFocus())
+ {
+ ShowFocus(ImplGetFocusRect());
+ }
+}
+
+void PushButton::ImplSetDefButton( bool bSet )
+{
+ Size aSize( GetSizePixel() );
+ Point aPos( GetPosPixel() );
+ int dLeft(0), dRight(0), dTop(0), dBottom(0);
+ bool bSetPos = false;
+
+ if ( IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Entire) )
+ {
+ tools::Rectangle aBound, aCont;
+ tools::Rectangle aCtrlRegion( 0, 0, 80, 20 ); // use a constant size to avoid accumulating
+ // will not work if the theme has dynamic adornment sizes
+ ImplControlValue aControlValue;
+
+ // get native size of a 'default' button
+ // and adjust the VCL button if more space for adornment is required
+ if( GetNativeControlRegion( ControlType::Pushbutton, ControlPart::Entire, aCtrlRegion,
+ ControlState::DEFAULT|ControlState::ENABLED,
+ aControlValue,
+ aBound, aCont ) )
+ {
+ dLeft = aCont.Left() - aBound.Left();
+ dTop = aCont.Top() - aBound.Top();
+ dRight = aBound.Right() - aCont.Right();
+ dBottom = aBound.Bottom() - aCont.Bottom();
+ bSetPos = dLeft || dTop || dRight || dBottom;
+ }
+ }
+
+ if ( bSet )
+ {
+ if( !(GetButtonState() & DrawButtonFlags::Default) && bSetPos )
+ {
+ // adjust pos/size when toggling from non-default to default
+ aPos.Move(-dLeft, -dTop);
+ aSize.AdjustWidth(dLeft + dRight );
+ aSize.AdjustHeight(dTop + dBottom );
+ }
+ GetButtonState() |= DrawButtonFlags::Default;
+ }
+ else
+ {
+ if( (GetButtonState() & DrawButtonFlags::Default) && bSetPos )
+ {
+ // adjust pos/size when toggling from default to non-default
+ aPos.Move(dLeft, dTop);
+ aSize.AdjustWidth( -(dLeft + dRight) );
+ aSize.AdjustHeight( -(dTop + dBottom) );
+ }
+ GetButtonState() &= ~DrawButtonFlags::Default;
+ }
+ if( bSetPos )
+ setPosSizePixel( aPos.X(), aPos.Y(), aSize.Width(), aSize.Height() );
+
+ Invalidate();
+}
+
+bool PushButton::ImplIsDefButton() const
+{
+ return bool(GetButtonState() & DrawButtonFlags::Default);
+}
+
+PushButton::PushButton( WindowType nType ) :
+ Button( nType )
+{
+ ImplInitPushButtonData();
+}
+
+PushButton::PushButton( vcl::Window* pParent, WinBits nStyle ) :
+ Button( WindowType::PUSHBUTTON )
+{
+ ImplInitPushButtonData();
+ ImplInit( pParent, nStyle );
+}
+
+void PushButton::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( !(rMEvt.IsLeft() &&
+ ImplHitTestPushButton( this, rMEvt.GetPosPixel() )) )
+ return;
+
+ StartTrackingFlags nTrackFlags = StartTrackingFlags::NONE;
+
+ if ( ( GetStyle() & WB_REPEAT ) &&
+ ! ( GetStyle() & WB_TOGGLE ) )
+ nTrackFlags |= StartTrackingFlags::ButtonRepeat;
+
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ StartTracking( nTrackFlags );
+
+ if ( nTrackFlags & StartTrackingFlags::ButtonRepeat )
+ Click();
+}
+
+void PushButton::Tracking( const TrackingEvent& rTEvt )
+{
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ if ( !(GetStyle() & WB_NOPOINTERFOCUS) && !rTEvt.IsTrackingCanceled() )
+ GrabFocus();
+
+ if ( GetStyle() & WB_TOGGLE )
+ {
+ // Don't toggle, when aborted
+ if ( !rTEvt.IsTrackingCanceled() )
+ {
+ if ( IsChecked() )
+ {
+ Check( false );
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ }
+ else
+ Check();
+ }
+ }
+ else
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+
+ Invalidate();
+
+ // do not call Click handler if aborted
+ if ( !rTEvt.IsTrackingCanceled() )
+ {
+ if ( ! ( GetStyle() & WB_REPEAT ) || ( GetStyle() & WB_TOGGLE ) )
+ Click();
+ }
+ }
+ }
+ else
+ {
+ if ( ImplHitTestPushButton( this, rTEvt.GetMouseEvent().GetPosPixel() ) )
+ {
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ if ( rTEvt.IsTrackingRepeat() && (GetStyle() & WB_REPEAT) &&
+ ! ( GetStyle() & WB_TOGGLE ) )
+ Click();
+ }
+ else
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ else
+ {
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ }
+}
+
+void PushButton::KeyInput( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ if ( !aKeyCode.GetModifier() &&
+ ((aKeyCode.GetCode() == KEY_RETURN) || (aKeyCode.GetCode() == KEY_SPACE)) )
+ {
+ if ( !(GetButtonState() & DrawButtonFlags::Pressed) )
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+
+ if ( ( GetStyle() & WB_REPEAT ) &&
+ ! ( GetStyle() & WB_TOGGLE ) )
+ Click();
+ }
+ else if ( (GetButtonState() & DrawButtonFlags::Pressed) && (aKeyCode.GetCode() == KEY_ESCAPE) )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ else
+ Button::KeyInput( rKEvt );
+}
+
+void PushButton::KeyUp( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ if ( (GetButtonState() & DrawButtonFlags::Pressed) &&
+ ((aKeyCode.GetCode() == KEY_RETURN) || (aKeyCode.GetCode() == KEY_SPACE)) )
+ {
+ if ( GetStyle() & WB_TOGGLE )
+ {
+ if ( IsChecked() )
+ {
+ Check( false );
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ }
+ else
+ Check();
+
+ Toggle();
+ }
+ else
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+
+ Invalidate();
+
+ if ( !( GetStyle() & WB_REPEAT ) || ( GetStyle() & WB_TOGGLE ) )
+ Click();
+ }
+ else
+ Button::KeyUp( rKEvt );
+}
+
+void PushButton::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ const_cast<PushButton*>(this)->Invalidate();
+}
+
+void PushButton::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ const Image& rCustomButtonImage = GetCustomButtonImage();
+ if (!!rCustomButtonImage)
+ {
+ rRenderContext.DrawImage(Point(0, 0), rCustomButtonImage);
+ return;
+ }
+ ImplDrawPushButton(rRenderContext);
+}
+
+void PushButton::Draw( OutputDevice* pDev, const Point& rPos,
+ SystemTextColorFlags nFlags )
+{
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ tools::Rectangle aRect( aPos, aSize );
+ vcl::Font aFont = GetDrawPixelFont( pDev );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+
+ std::optional<StyleSettings> oOrigDevStyleSettings;
+
+ if ( nFlags & SystemTextColorFlags::Mono )
+ {
+ pDev->SetTextColor( COL_BLACK );
+ }
+ else
+ {
+ pDev->SetTextColor( GetTextColor() );
+ // DecoView uses the FaceColor...
+ AllSettings aSettings = pDev->GetSettings();
+ StyleSettings aStyleSettings = aSettings.GetStyleSettings();
+ oOrigDevStyleSettings = aStyleSettings;
+ if ( IsControlBackground() )
+ aStyleSettings.SetFaceColor( GetControlBackground() );
+ else
+ aStyleSettings.SetFaceColor( GetSettings().GetStyleSettings().GetFaceColor() );
+ aSettings.SetStyleSettings( aStyleSettings );
+ pDev->OutputDevice::SetSettings( aSettings );
+ }
+ pDev->SetTextFillColor();
+
+ DecorationView aDecoView( pDev );
+ DrawButtonFlags nButtonStyle = DrawButtonFlags::NONE;
+ if ( nFlags & SystemTextColorFlags::Mono )
+ nButtonStyle |= DrawButtonFlags::Mono;
+ if ( IsChecked() )
+ nButtonStyle |= DrawButtonFlags::Checked;
+ aRect = aDecoView.DrawButton( aRect, nButtonStyle );
+
+ ImplDrawPushButtonContent( pDev, nFlags, aRect, true, nButtonStyle );
+
+ // restore original settings (which are not affected by Push/Pop) after
+ // finished drawing
+ if (oOrigDevStyleSettings)
+ {
+ AllSettings aSettings = pDev->GetSettings();
+ aSettings.SetStyleSettings(*oOrigDevStyleSettings);
+ pDev->OutputDevice::SetSettings( aSettings );
+ }
+
+ pDev->Pop();
+}
+
+void PushButton::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void PushButton::GetFocus()
+{
+ ShowFocus( ImplGetFocusRect() );
+ SetInputContext( InputContext( GetFont() ) );
+ Button::GetFocus();
+}
+
+void PushButton::LoseFocus()
+{
+ EndSelection();
+ HideFocus();
+ Button::LoseFocus();
+}
+
+void PushButton::StateChanged( StateChangedType nType )
+{
+ Button::StateChanged( nType );
+
+ if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::Data) ||
+ (nType == StateChangedType::State) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetWindow( GetWindowType::Prev ), GetStyle() ) );
+
+ bool bIsDefButton = ( GetStyle() & WB_DEFBUTTON ) != 0;
+ bool bWasDefButton = ( GetPrevStyle() & WB_DEFBUTTON ) != 0;
+ if ( bIsDefButton != bWasDefButton )
+ ImplSetDefButton( bIsDefButton );
+
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ if ( (GetPrevStyle() & PUSHBUTTON_VIEW_STYLE) !=
+ (GetStyle() & PUSHBUTTON_VIEW_STYLE) )
+ Invalidate();
+ }
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+void PushButton::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Button::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+bool PushButton::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && (pMouseEvt->IsEnterWindow() || pMouseEvt->IsLeaveWindow()) )
+ {
+ // trigger redraw as mouse over state has changed
+
+ // TODO: move this to Window class or make it a member !!!
+ ControlType aCtrlType = ControlType::Generic;
+ switch( GetParent()->GetType() )
+ {
+ case WindowType::LISTBOX:
+ case WindowType::MULTILISTBOX:
+ case WindowType::TREELISTBOX:
+ aCtrlType = ControlType::Listbox;
+ break;
+
+ case WindowType::COMBOBOX:
+ case WindowType::PATTERNBOX:
+ case WindowType::NUMERICBOX:
+ case WindowType::METRICBOX:
+ case WindowType::CURRENCYBOX:
+ case WindowType::DATEBOX:
+ case WindowType::TIMEBOX:
+ case WindowType::LONGCURRENCYBOX:
+ aCtrlType = ControlType::Combobox;
+ break;
+ default:
+ break;
+ }
+
+ bool bDropDown = ( IsSymbol() && (GetSymbol()==SymbolType::SPIN_DOWN) && GetText().isEmpty() );
+
+ if( bDropDown && GetParent()->IsNativeControlSupported( aCtrlType, ControlPart::Entire) &&
+ !GetParent()->IsNativeControlSupported( aCtrlType, ControlPart::ButtonDown) )
+ {
+ vcl::Window *pBorder = GetParent()->GetWindow( GetWindowType::Border );
+ if(aCtrlType == ControlType::Combobox)
+ {
+ // only paint the button part to avoid flickering of the combobox text
+ tools::Rectangle aClipRect( Point(), GetOutputSizePixel() );
+ aClipRect.SetPos(pBorder->ScreenToOutputPixel(OutputToScreenPixel(aClipRect.TopLeft())));
+ pBorder->Invalidate( aClipRect );
+ }
+ else
+ {
+ pBorder->Invalidate( InvalidateFlags::NoErase );
+ }
+ }
+ else if( (GetStyle() & WB_FLATBUTTON) ||
+ IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Entire) )
+ {
+ Invalidate();
+ }
+ }
+ }
+
+ return Button::PreNotify(rNEvt);
+}
+
+void PushButton::Toggle()
+{
+ ImplCallEventListenersAndHandler( VclEventId::PushbuttonToggle, nullptr );
+}
+
+void PushButton::SetSymbol( SymbolType eSymbol )
+{
+ if ( meSymbol != eSymbol )
+ {
+ meSymbol = eSymbol;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+void PushButton::SetSymbolAlign( SymbolAlign eAlign )
+{
+ ImplSetSymbolAlign( eAlign );
+}
+
+void PushButton::SetDropDown( PushButtonDropdownStyle nStyle )
+{
+ if ( mnDDStyle != nStyle )
+ {
+ mnDDStyle = nStyle;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+void PushButton::SetState( TriState eState )
+{
+ if ( meState == eState )
+ return;
+
+ meState = eState;
+ if ( meState == TRISTATE_FALSE )
+ GetButtonState() &= ~DrawButtonFlags(DrawButtonFlags::Checked | DrawButtonFlags::DontKnow);
+ else if ( meState == TRISTATE_TRUE )
+ {
+ GetButtonState() &= ~DrawButtonFlags::DontKnow;
+ GetButtonState() |= DrawButtonFlags::Checked;
+ }
+ else // TRISTATE_INDET
+ {
+ GetButtonState() &= ~DrawButtonFlags::Checked;
+ GetButtonState() |= DrawButtonFlags::DontKnow;
+ }
+
+ CompatStateChanged( StateChangedType::State );
+ Toggle();
+}
+
+void PushButton::statusChanged(const css::frame::FeatureStateEvent& rEvent)
+{
+ Button::statusChanged(rEvent);
+ if (rEvent.State.has<bool>())
+ SetPressed(rEvent.State.get<bool>());
+}
+
+void PushButton::SetPressed( bool bPressed )
+{
+ if ( mbPressed != bPressed )
+ {
+ mbPressed = bPressed;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+void PushButton::EndSelection()
+{
+ EndTracking( TrackingEventFlags::Cancel );
+ if ( !isDisposed() &&
+ GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ if ( !mbPressed )
+ Invalidate();
+ }
+}
+
+Size PushButton::CalcMinimumSize() const
+{
+ Size aSize;
+
+ if ( IsSymbol() )
+ {
+ if ( IsSmallSymbol ())
+ aSize = Size( 16, 12 );
+ else
+ aSize = Size( 26, 24 );
+ }
+ else if ( Button::HasImage() )
+ aSize = GetModeImage().GetSizePixel();
+ if( mnDDStyle == PushButtonDropdownStyle::MenuButton ||
+ mnDDStyle == PushButtonDropdownStyle::SplitMenuButton )
+ {
+ tools::Long nSymbolSize = GetTextHeight() / 2 + 1;
+ aSize.AdjustWidth(2*nSymbolSize );
+ }
+ if (!PushButton::GetText().isEmpty())
+ {
+ Size textSize = GetTextRect( tools::Rectangle( Point(), Size( 0x7fffffff, 0x7fffffff ) ),
+ PushButton::GetText(), ImplGetTextStyle( SystemTextColorFlags::NONE ) ).GetSize();
+
+ tools::Long nTextHeight = textSize.Height() * 1.15;
+
+ ImageAlign eImageAlign = GetImageAlign();
+ // tdf#142337 only considering the simple top/bottom/left/right possibilities
+ if (eImageAlign == ImageAlign::Top || eImageAlign == ImageAlign::Bottom)
+ {
+ aSize.AdjustHeight(nTextHeight);
+ aSize.setWidth(std::max(aSize.Width(), textSize.Width()));
+ }
+ else
+ {
+ aSize.AdjustWidth(textSize.Width());
+ aSize.setHeight(std::max(aSize.Height(), nTextHeight));
+ }
+ }
+
+ // cf. ImplDrawPushButton ...
+ if( (GetStyle() & WB_SMALLSTYLE) == 0 )
+ {
+ aSize.AdjustWidth(24 );
+ aSize.AdjustHeight(12 );
+ }
+
+ return CalcWindowSize( aSize );
+}
+
+Size PushButton::GetOptimalSize() const
+{
+ return CalcMinimumSize();
+}
+
+bool PushButton::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "has-default")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~WB_DEFBUTTON;
+ if (toBool(rValue))
+ nBits |= WB_DEFBUTTON;
+ SetStyle(nBits);
+ }
+ else
+ return Button::set_property(rKey, rValue);
+ return true;
+}
+
+void PushButton::ShowFocus(const tools::Rectangle& rRect)
+{
+ if (IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Focus))
+ {
+ PushButtonValue aControlValue;
+ aControlValue.mbIsAction = isAction();
+ tools::Rectangle aInRect(Point(), GetOutputSizePixel());
+ GetOutDev()->DrawNativeControl(ControlType::Pushbutton, ControlPart::Focus, aInRect,
+ ControlState::FOCUSED, aControlValue, OUString());
+ }
+ Button::ShowFocus(rRect);
+}
+
+void OKButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ set_id("ok");
+ PushButton::ImplInit( pParent, nStyle );
+
+ SetText( GetStandardText( StandardButtonType::OK ) );
+}
+
+OKButton::OKButton( vcl::Window* pParent, WinBits nStyle ) :
+ PushButton( WindowType::OKBUTTON )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void OKButton::Click()
+{
+ // close parent if no link set
+ if ( !GetClickHdl() )
+ {
+ vcl::Window* pParent = getNonLayoutParent(this);
+ if ( pParent->IsSystemWindow() )
+ {
+ if ( pParent->IsDialog() )
+ {
+ VclPtr<Dialog> xParent( static_cast<Dialog*>(pParent) );
+ if ( xParent->IsInExecute() )
+ xParent->EndDialog( RET_OK );
+ // prevent recursive calls
+ else if ( !xParent->IsInClose() )
+ {
+ if ( pParent->GetStyle() & WB_CLOSEABLE )
+ xParent->Close();
+ }
+ }
+ else
+ {
+ if ( pParent->GetStyle() & WB_CLOSEABLE )
+ static_cast<SystemWindow*>(pParent)->Close();
+ }
+ }
+ }
+ else
+ {
+ PushButton::Click();
+ }
+}
+
+void CancelButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ set_id("cancel");
+ PushButton::ImplInit( pParent, nStyle );
+
+ SetText( GetStandardText( StandardButtonType::Cancel ) );
+}
+
+CancelButton::CancelButton( vcl::Window* pParent, WinBits nStyle ) :
+ PushButton( WindowType::CANCELBUTTON )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void CancelButton::Click()
+{
+ // close parent if link not set
+ if ( !GetClickHdl() )
+ {
+ vcl::Window* pParent = getNonLayoutParent(this);
+ if ( pParent->IsSystemWindow() )
+ {
+ if ( pParent->IsDialog() )
+ {
+ if ( static_cast<Dialog*>(pParent)->IsInExecute() )
+ static_cast<Dialog*>(pParent)->EndDialog();
+ // prevent recursive calls
+ else if ( !static_cast<Dialog*>(pParent)->IsInClose() )
+ {
+ if ( pParent->GetStyle() & WB_CLOSEABLE )
+ static_cast<Dialog*>(pParent)->Close();
+ }
+ }
+ else
+ {
+ if ( pParent->GetStyle() & WB_CLOSEABLE )
+ static_cast<SystemWindow*>(pParent)->Close();
+ }
+ }
+ }
+ else
+ {
+ PushButton::Click();
+ }
+}
+
+CloseButton::CloseButton( vcl::Window* pParent )
+ : CancelButton(pParent, 0)
+{
+ SetText( GetStandardText( StandardButtonType::Close ) );
+}
+
+void HelpButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ set_id("help");
+ PushButton::ImplInit( pParent, nStyle | WB_NOPOINTERFOCUS );
+
+ SetText( GetStandardText( StandardButtonType::Help ) );
+}
+
+HelpButton::HelpButton( vcl::Window* pParent, WinBits nStyle ) :
+ PushButton( WindowType::HELPBUTTON )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void HelpButton::Click()
+{
+ // trigger help if no link set
+ if ( !GetClickHdl() )
+ {
+ vcl::Window* pFocusWin = Application::GetFocusWindow();
+ if ( !pFocusWin || comphelper::LibreOfficeKit::isActive() )
+ pFocusWin = this;
+
+ HelpEvent aEvt( pFocusWin->GetPointerPosPixel(), HelpEventMode::CONTEXT );
+ pFocusWin->RequestHelp( aEvt );
+ }
+ PushButton::Click();
+}
+
+void HelpButton::StateChanged( StateChangedType nStateChange )
+{
+ // Hide when we have no help URL.
+ if (comphelper::LibreOfficeKit::isActive() &&
+ officecfg::Office::Common::Help::HelpRootURL::get().isEmpty())
+ Hide();
+ else
+ PushButton::StateChanged(nStateChange);
+}
+
+void RadioButton::ImplInitRadioButtonData()
+{
+ mbChecked = false;
+ mbRadioCheck = true;
+ mbStateChanged = false;
+}
+
+void RadioButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle(getPreviousSibling(pParent), nStyle);
+ Button::ImplInit( pParent, nStyle, nullptr );
+
+ ImplInitSettings( true );
+}
+
+WinBits RadioButton::ImplInitStyle( const vcl::Window* pPrevWindow, WinBits nStyle ) const
+{
+ if ( !(nStyle & WB_NOGROUP) &&
+ (!pPrevWindow || (pPrevWindow->GetType() != WindowType::RADIOBUTTON)) )
+ nStyle |= WB_GROUP;
+ if ( !(nStyle & WB_NOTABSTOP) )
+ {
+ if ( IsChecked() )
+ nStyle |= WB_TABSTOP;
+ else
+ nStyle &= ~WB_TABSTOP;
+ }
+
+ return nStyle;
+}
+
+const vcl::Font& RadioButton::GetCanonicalFont( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetRadioCheckFont();
+}
+
+const Color& RadioButton::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetRadioCheckTextColor();
+}
+
+void RadioButton::ImplInitSettings( bool bBackground )
+{
+ Button::ImplInitSettings();
+
+ if ( !bBackground )
+ return;
+
+ vcl::Window* pParent = GetParent();
+ if ( !IsControlBackground() &&
+ (pParent->IsChildTransparentModeEnabled() || IsNativeControlSupported( ControlType::Radiobutton, ControlPart::Entire ) ) )
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode( ParentClipMode::NoClip );
+ SetPaintTransparent( true );
+ SetBackground();
+ if( IsNativeControlSupported( ControlType::Radiobutton, ControlPart::Entire ) )
+ mpWindowImpl->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects;
+ }
+ else
+ {
+ EnableChildTransparentMode( false );
+ SetParentClipMode();
+ SetPaintTransparent( false );
+
+ if ( IsControlBackground() )
+ SetBackground( GetControlBackground() );
+ else
+ SetBackground( pParent->GetBackground() );
+ }
+}
+
+void RadioButton::ImplDrawRadioButtonState(vcl::RenderContext& rRenderContext)
+{
+ bool bNativeOK = false;
+
+ // no native drawing for image radio buttons
+ if (!maImage && rRenderContext.IsNativeControlSupported(ControlType::Radiobutton, ControlPart::Entire))
+ {
+ ImplControlValue aControlValue( mbChecked ? ButtonValue::On : ButtonValue::Off );
+ tools::Rectangle aCtrlRect(maStateRect.TopLeft(), maStateRect.GetSize());
+ ControlState nState = ControlState::NONE;
+
+ if (GetButtonState() & DrawButtonFlags::Pressed)
+ nState |= ControlState::PRESSED;
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+ if (GetButtonState() & DrawButtonFlags::Default)
+ nState |= ControlState::DEFAULT;
+ if (IsEnabled())
+ nState |= ControlState::ENABLED;
+
+ if (IsMouseOver() && maMouseRect.Contains(GetPointerPosPixel()))
+ nState |= ControlState::ROLLOVER;
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Radiobutton, ControlPart::Entire, aCtrlRect,
+ nState, aControlValue, OUString());
+ }
+
+ if (bNativeOK)
+ return;
+
+ if (!maImage)
+ {
+ DrawButtonFlags nStyle = GetButtonState();
+ if (!IsEnabled())
+ nStyle |= DrawButtonFlags::Disabled;
+ if (mbChecked)
+ nStyle |= DrawButtonFlags::Checked;
+ Image aImage = GetRadioImage(rRenderContext.GetSettings(), nStyle);
+ if (IsZoom())
+ rRenderContext.DrawImage(maStateRect.TopLeft(), maStateRect.GetSize(), aImage);
+ else
+ rRenderContext.DrawImage(maStateRect.TopLeft(), aImage);
+ }
+ else
+ {
+ HideFocus();
+
+ DecorationView aDecoView(&rRenderContext);
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ tools::Rectangle aImageRect = maStateRect;
+ Size aImageSize = maImage.GetSizePixel();
+ bool bEnabled = IsEnabled();
+
+ aImageSize.setWidth( CalcZoom(aImageSize.Width()) );
+ aImageSize.setHeight( CalcZoom(aImageSize.Height()) );
+
+ aImageRect.AdjustLeft( 1 );
+ aImageRect.AdjustTop( 1 );
+ aImageRect.AdjustRight( -1 );
+ aImageRect.AdjustBottom( -1 );
+
+ // display border and selection status
+ aImageRect = aDecoView.DrawFrame(aImageRect, DrawFrameStyle::DoubleIn);
+ if ((GetButtonState() & DrawButtonFlags::Pressed) || !bEnabled)
+ rRenderContext.SetFillColor( rStyleSettings.GetFaceColor());
+ else
+ rRenderContext.SetFillColor(rStyleSettings.GetFieldColor());
+ rRenderContext.SetLineColor();
+ rRenderContext.DrawRect(aImageRect);
+
+ // display image
+ DrawImageFlags nImageStyle = DrawImageFlags::NONE;
+ if (!bEnabled)
+ nImageStyle |= DrawImageFlags::Disable;
+
+ Image* pImage = &maImage;
+
+ Point aImagePos(aImageRect.TopLeft());
+ aImagePos.AdjustX((aImageRect.GetWidth() - aImageSize.Width()) / 2 );
+ aImagePos.AdjustY((aImageRect.GetHeight() - aImageSize.Height()) / 2 );
+ if (IsZoom())
+ rRenderContext.DrawImage(aImagePos, aImageSize, *pImage, nImageStyle);
+ else
+ rRenderContext.DrawImage(aImagePos, *pImage, nImageStyle);
+
+ aImageRect.AdjustLeft( 1 );
+ aImageRect.AdjustTop( 1 );
+ aImageRect.AdjustRight( -1 );
+ aImageRect.AdjustBottom( -1 );
+
+ ImplSetFocusRect(aImageRect);
+
+ if (mbChecked)
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetHighlightColor());
+ rRenderContext.SetFillColor();
+ if ((aImageSize.Width() >= 20) || (aImageSize.Height() >= 20))
+ {
+ aImageRect.AdjustLeft( 1 );
+ aImageRect.AdjustTop( 1 );
+ aImageRect.AdjustRight( -1 );
+ aImageRect.AdjustBottom( -1 );
+ }
+ rRenderContext.DrawRect(aImageRect);
+ aImageRect.AdjustLeft( 1 );
+ aImageRect.AdjustTop( 1 );
+ aImageRect.AdjustRight( -1 );
+ aImageRect.AdjustBottom( -1 );
+ rRenderContext.DrawRect(aImageRect);
+ }
+
+ if (HasFocus())
+ ShowFocus(ImplGetFocusRect());
+ }
+}
+
+// for drawing RadioButton or CheckButton that has Text and/or Image
+void Button::ImplDrawRadioCheck(OutputDevice* pDev, WinBits nWinStyle, SystemTextColorFlags nSystemTextColorFlags,
+ const Point& rPos, const Size& rSize,
+ const Size& rImageSize, tools::Rectangle& rStateRect,
+ tools::Rectangle& rMouseRect)
+{
+ DrawTextFlags nTextStyle = Button::ImplGetTextStyle( nWinStyle, nSystemTextColorFlags );
+
+ const tools::Long nImageSep = GetDrawPixel( pDev, ImplGetImageToTextDistance() );
+ Size aSize( rSize );
+ Point aPos( rPos );
+ aPos.AdjustX(rImageSize.Width() + nImageSep );
+
+ // tdf#141761 Old (convenience?) adjustment of width may lead to empty
+ // or negative(!) Size, that needs to be avoided. The coordinate context
+ // is pixel-oriented (all Paints of Controls are, historically), so
+ // the minimum width should be '1' Pixel.
+ // Hint: nImageSep is based on Zoom (using Window::CalcZoom) and
+ // MapModes (using Window::GetDrawPixel) - so potentially a wide range
+ // of unpredictable values is possible
+ const tools::Long nWidthAdjust(rImageSize.Width() + nImageSep);
+ aSize.setWidth(std::max(static_cast<tools::Long>(1), aSize.getWidth() - nWidthAdjust));
+
+ // if the text rect height is smaller than the height of the image
+ // then for single lines the default should be centered text
+ if( (nWinStyle & (WB_TOP|WB_VCENTER|WB_BOTTOM)) == 0 &&
+ (rImageSize.Height() > rSize.Height() || ! (nWinStyle & WB_WORDBREAK) ) )
+ {
+ nTextStyle &= ~DrawTextFlags(DrawTextFlags::Top|DrawTextFlags::Bottom);
+ nTextStyle |= DrawTextFlags::VCenter;
+ aSize.setHeight( rImageSize.Height() );
+ }
+
+ ImplDrawAlignedImage( pDev, aPos, aSize, 1, nTextStyle );
+
+ rMouseRect = tools::Rectangle( aPos, aSize );
+ rMouseRect.SetLeft( rPos.X() );
+
+ rStateRect.SetLeft( rPos.X() );
+ rStateRect.SetTop( rMouseRect.Top() );
+
+ if ( aSize.Height() > rImageSize.Height() )
+ rStateRect.AdjustTop(( aSize.Height() - rImageSize.Height() ) / 2 );
+ else
+ {
+ rStateRect.AdjustTop( -(( rImageSize.Height() - aSize.Height() ) / 2) );
+ if( rStateRect.Top() < 0 )
+ rStateRect.SetTop( 0 );
+ }
+
+ rStateRect.SetRight( rStateRect.Left()+rImageSize.Width()-1 );
+ rStateRect.SetBottom( rStateRect.Top()+rImageSize.Height()-1 );
+
+ if ( rStateRect.Bottom() > rMouseRect.Bottom() )
+ rMouseRect.SetBottom( rStateRect.Bottom() );
+}
+
+void RadioButton::ImplDraw( OutputDevice* pDev, SystemTextColorFlags nSystemTextColorFlags,
+ const Point& rPos, const Size& rSize,
+ const Size& rImageSize, tools::Rectangle& rStateRect,
+ tools::Rectangle& rMouseRect )
+{
+ WinBits nWinStyle = GetStyle();
+ OUString aText( GetText() );
+
+ pDev->Push( vcl::PushFlags::CLIPREGION );
+ pDev->IntersectClipRegion( tools::Rectangle( rPos, rSize ) );
+
+ // no image radio button
+ if ( !maImage )
+ {
+ if (!aText.isEmpty() || HasImage())
+ {
+ Button::ImplDrawRadioCheck(pDev, nWinStyle, nSystemTextColorFlags,
+ rPos, rSize, rImageSize,
+ rStateRect, rMouseRect);
+ }
+ else
+ {
+ rStateRect.SetLeft( rPos.X() );
+ if ( nWinStyle & WB_VCENTER )
+ rStateRect.SetTop( rPos.Y()+((rSize.Height()-rImageSize.Height())/2) );
+ else if ( nWinStyle & WB_BOTTOM )
+ rStateRect.SetTop( rPos.Y()+rSize.Height()-rImageSize.Height() ); //-1;
+ else
+ rStateRect.SetTop( rPos.Y() );
+ rStateRect.SetRight( rStateRect.Left()+rImageSize.Width()-1 );
+ rStateRect.SetBottom( rStateRect.Top()+rImageSize.Height()-1 );
+ rMouseRect = rStateRect;
+
+ ImplSetFocusRect( rStateRect );
+ }
+ }
+ else
+ {
+ bool bTopImage = (nWinStyle & WB_TOP) != 0;
+ Size aImageSize = maImage.GetSizePixel();
+ tools::Rectangle aImageRect( rPos, rSize );
+ tools::Long nTextHeight = pDev->GetTextHeight();
+ tools::Long nTextWidth = pDev->GetCtrlTextWidth( aText );
+
+ // calculate position and sizes
+ if (!aText.isEmpty())
+ {
+ Size aTmpSize( (aImageSize.Width()+8), (aImageSize.Height()+8) );
+ if ( bTopImage )
+ {
+ aImageRect.SetLeft( (rSize.Width()-aTmpSize.Width())/2 );
+ aImageRect.SetTop( (rSize.Height()-(aTmpSize.Height()+nTextHeight+6))/2 );
+ }
+ else
+ aImageRect.SetTop( (rSize.Height()-aTmpSize.Height())/2 );
+
+ aImageRect.SetRight( aImageRect.Left()+aTmpSize.Width() );
+ aImageRect.SetBottom( aImageRect.Top()+aTmpSize.Height() );
+
+ // display text
+ Point aTxtPos = rPos;
+ if ( bTopImage )
+ {
+ aTxtPos.AdjustX((rSize.Width()-nTextWidth)/2 );
+ aTxtPos.AdjustY(aImageRect.Bottom()+6 );
+ }
+ else
+ {
+ aTxtPos.AdjustX(aImageRect.Right()+8 );
+ aTxtPos.AdjustY((rSize.Height()-nTextHeight)/2 );
+ }
+ pDev->DrawCtrlText( aTxtPos, aText, 0, aText.getLength() );
+ }
+
+ rMouseRect = aImageRect;
+ rStateRect = aImageRect;
+ }
+
+ pDev->Pop();
+}
+
+void RadioButton::ImplDrawRadioButton(vcl::RenderContext& rRenderContext)
+{
+ HideFocus();
+
+ Size aImageSize;
+ if (!maImage)
+ aImageSize = ImplGetRadioImageSize();
+ else
+ aImageSize = maImage.GetSizePixel();
+
+ aImageSize.setWidth( CalcZoom(aImageSize.Width()) );
+ aImageSize.setHeight( CalcZoom(aImageSize.Height()) );
+
+ // Draw control text
+ ImplDraw(&rRenderContext, SystemTextColorFlags::NONE, Point(), GetOutputSizePixel(),
+ aImageSize, maStateRect, maMouseRect);
+
+ if (!maImage && HasFocus())
+ ShowFocus(ImplGetFocusRect());
+
+ ImplDrawRadioButtonState(rRenderContext);
+}
+
+void RadioButton::group(RadioButton &rOther)
+{
+ if (&rOther == this)
+ return;
+
+ if (!m_xGroup)
+ {
+ m_xGroup = std::make_shared<std::vector<VclPtr<RadioButton> >>();
+ m_xGroup->push_back(this);
+ }
+
+ auto aFind = std::find(m_xGroup->begin(), m_xGroup->end(), VclPtr<RadioButton>(&rOther));
+ if (aFind == m_xGroup->end())
+ {
+ m_xGroup->push_back(&rOther);
+
+ if (rOther.m_xGroup)
+ {
+ std::vector< VclPtr<RadioButton> > aOthers(rOther.GetRadioButtonGroup(false));
+ //make all members of the group share the same button group
+ for (auto const& elem : aOthers)
+ {
+ aFind = std::find(m_xGroup->begin(), m_xGroup->end(), elem);
+ if (aFind == m_xGroup->end())
+ m_xGroup->push_back(elem);
+ }
+ }
+
+ //make all members of the group share the same button group
+ for (VclPtr<RadioButton> const & pButton : *m_xGroup)
+ {
+ pButton->m_xGroup = m_xGroup;
+ }
+ }
+
+ //if this one is checked, uncheck all the others
+ if (mbChecked)
+ ImplUncheckAllOther();
+}
+
+std::vector< VclPtr<RadioButton> > RadioButton::GetRadioButtonGroup(bool bIncludeThis) const
+{
+ if (m_xGroup)
+ {
+ if (bIncludeThis)
+ return *m_xGroup;
+ std::vector< VclPtr<RadioButton> > aGroup;
+ for (VclPtr<RadioButton> const & pRadioButton : *m_xGroup)
+ {
+ if (pRadioButton == this)
+ continue;
+ aGroup.push_back(pRadioButton);
+ }
+ return aGroup;
+ }
+
+ std::vector<VclPtr<RadioButton>> aGroup;
+ if (mbUsesExplicitGroup)
+ return aGroup;
+
+ //old-school
+
+ // go back to first in group;
+ vcl::Window* pFirst = const_cast<RadioButton*>(this);
+ while( ( pFirst->GetStyle() & WB_GROUP ) == 0 )
+ {
+ vcl::Window* pWindow = pFirst->GetWindow( GetWindowType::Prev );
+ if( pWindow )
+ pFirst = pWindow;
+ else
+ break;
+ }
+ // insert radiobuttons up to next group
+ do
+ {
+ if( pFirst->GetType() == WindowType::RADIOBUTTON )
+ {
+ if( pFirst != this || bIncludeThis )
+ aGroup.emplace_back(static_cast<RadioButton*>(pFirst) );
+ }
+ pFirst = pFirst->GetWindow( GetWindowType::Next );
+ } while( pFirst && ( ( pFirst->GetStyle() & WB_GROUP ) == 0 ) );
+
+ return aGroup;
+}
+
+void RadioButton::ImplUncheckAllOther()
+{
+ mpWindowImpl->mnStyle |= WB_TABSTOP;
+
+ std::vector<VclPtr<RadioButton> > aGroup(GetRadioButtonGroup(false));
+ // iterate over radio button group and checked buttons
+ for (VclPtr<RadioButton>& pWindow : aGroup)
+ {
+ if ( pWindow->IsChecked() )
+ {
+ pWindow->SetState( false );
+ if ( pWindow->isDisposed() )
+ return;
+ }
+
+ // not inside if clause to always remove wrongly set WB_TABSTOPS
+ pWindow->mpWindowImpl->mnStyle &= ~WB_TABSTOP;
+ }
+}
+
+void RadioButton::ImplCallClick( bool bGrabFocus, GetFocusFlags nFocusFlags )
+{
+ mbStateChanged = !mbChecked;
+ mbChecked = true;
+ mpWindowImpl->mnStyle |= WB_TABSTOP;
+ Invalidate();
+ VclPtr<vcl::Window> xWindow = this;
+ if ( mbRadioCheck )
+ ImplUncheckAllOther();
+ if ( xWindow->isDisposed() )
+ return;
+ if ( bGrabFocus )
+ ImplGrabFocus( nFocusFlags );
+ if ( xWindow->isDisposed() )
+ return;
+ if ( mbStateChanged )
+ Toggle();
+ if ( xWindow->isDisposed() )
+ return;
+ Click();
+ if ( xWindow->isDisposed() )
+ return;
+ mbStateChanged = false;
+}
+
+RadioButton::RadioButton(vcl::Window* pParent, bool bUsesExplicitGroup, WinBits nStyle)
+ : Button(WindowType::RADIOBUTTON)
+ , mbUsesExplicitGroup(bUsesExplicitGroup)
+{
+ ImplInitRadioButtonData();
+ ImplInit( pParent, nStyle );
+}
+
+RadioButton::~RadioButton()
+{
+ disposeOnce();
+}
+
+void RadioButton::dispose()
+{
+ if (m_xGroup)
+ {
+ m_xGroup->erase(std::remove(m_xGroup->begin(), m_xGroup->end(), VclPtr<RadioButton>(this)),
+ m_xGroup->end());
+ m_xGroup.reset();
+ }
+ Button::dispose();
+}
+
+void RadioButton::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( rMEvt.IsLeft() && maMouseRect.Contains( rMEvt.GetPosPixel() ) )
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ StartTracking();
+ return;
+ }
+
+ Button::MouseButtonDown( rMEvt );
+}
+
+void RadioButton::Tracking( const TrackingEvent& rTEvt )
+{
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ if ( !(GetStyle() & WB_NOPOINTERFOCUS) && !rTEvt.IsTrackingCanceled() )
+ GrabFocus();
+
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+
+ // do not call click handler if aborted
+ if ( !rTEvt.IsTrackingCanceled() )
+ ImplCallClick();
+ else
+ {
+ Invalidate();
+ }
+ }
+ }
+ else
+ {
+ if ( maMouseRect.Contains( rTEvt.GetMouseEvent().GetPosPixel() ) )
+ {
+ if ( !(GetButtonState() & DrawButtonFlags::Pressed) )
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ else
+ {
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ }
+}
+
+void RadioButton::KeyInput( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ if ( !aKeyCode.GetModifier() && (aKeyCode.GetCode() == KEY_SPACE) )
+ {
+ if ( !(GetButtonState() & DrawButtonFlags::Pressed) )
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ else if ( (GetButtonState() & DrawButtonFlags::Pressed) && (aKeyCode.GetCode() == KEY_ESCAPE) )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ else
+ Button::KeyInput( rKEvt );
+}
+
+void RadioButton::KeyUp( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ if ( (GetButtonState() & DrawButtonFlags::Pressed) && (aKeyCode.GetCode() == KEY_SPACE) )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ ImplCallClick();
+ }
+ else
+ Button::KeyUp( rKEvt );
+}
+
+void RadioButton::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ const_cast<RadioButton*>(this)->Invalidate();
+}
+
+void RadioButton::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ ImplDrawRadioButton(rRenderContext);
+}
+
+void RadioButton::Draw( OutputDevice* pDev, const Point& rPos,
+ SystemTextColorFlags nFlags )
+{
+ if ( !maImage )
+ {
+ MapMode aResMapMode( MapUnit::Map100thMM );
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ Size aImageSize = pDev->LogicToPixel( Size( 300, 300 ), aResMapMode );
+ Size aBrd1Size = pDev->LogicToPixel( Size( 20, 20 ), aResMapMode );
+ Size aBrd2Size = pDev->LogicToPixel( Size( 60, 60 ), aResMapMode );
+ vcl::Font aFont = GetDrawPixelFont( pDev );
+ tools::Rectangle aStateRect;
+ tools::Rectangle aMouseRect;
+
+ aImageSize.setWidth( CalcZoom( aImageSize.Width() ) );
+ aImageSize.setHeight( CalcZoom( aImageSize.Height() ) );
+ aBrd1Size.setWidth( CalcZoom( aBrd1Size.Width() ) );
+ aBrd1Size.setHeight( CalcZoom( aBrd1Size.Height() ) );
+ aBrd2Size.setWidth( CalcZoom( aBrd2Size.Width() ) );
+ aBrd2Size.setHeight( CalcZoom( aBrd2Size.Height() ) );
+
+ if ( !aBrd1Size.Width() )
+ aBrd1Size.setWidth( 1 );
+ if ( !aBrd1Size.Height() )
+ aBrd1Size.setHeight( 1 );
+ if ( !aBrd2Size.Width() )
+ aBrd2Size.setWidth( 1 );
+ if ( !aBrd2Size.Height() )
+ aBrd2Size.setHeight( 1 );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ if ( nFlags & SystemTextColorFlags::Mono )
+ pDev->SetTextColor( COL_BLACK );
+ else
+ pDev->SetTextColor( GetTextColor() );
+ pDev->SetTextFillColor();
+
+ ImplDraw( pDev, nFlags, aPos, aSize,
+ aImageSize, aStateRect, aMouseRect );
+
+ Point aCenterPos = aStateRect.Center();
+ tools::Long nRadX = aImageSize.Width()/2;
+ tools::Long nRadY = aImageSize.Height()/2;
+
+ pDev->SetLineColor();
+ pDev->SetFillColor( COL_BLACK );
+ pDev->DrawPolygon( tools::Polygon( aCenterPos, nRadX, nRadY ) );
+ nRadX -= aBrd1Size.Width();
+ nRadY -= aBrd1Size.Height();
+ pDev->SetFillColor( COL_WHITE );
+ pDev->DrawPolygon( tools::Polygon( aCenterPos, nRadX, nRadY ) );
+ if ( mbChecked )
+ {
+ nRadX -= aBrd1Size.Width();
+ nRadY -= aBrd1Size.Height();
+ if ( !nRadX )
+ nRadX = 1;
+ if ( !nRadY )
+ nRadY = 1;
+ pDev->SetFillColor( COL_BLACK );
+ pDev->DrawPolygon( tools::Polygon( aCenterPos, nRadX, nRadY ) );
+ }
+
+ pDev->Pop();
+ }
+ else
+ {
+ OSL_FAIL( "RadioButton::Draw() - not implemented for RadioButton with Image" );
+ }
+}
+
+void RadioButton::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void RadioButton::GetFocus()
+{
+ ShowFocus( ImplGetFocusRect() );
+ SetInputContext( InputContext( GetFont() ) );
+ Button::GetFocus();
+}
+
+void RadioButton::LoseFocus()
+{
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+
+ HideFocus();
+ Button::LoseFocus();
+}
+
+void RadioButton::StateChanged( StateChangedType nType )
+{
+ Button::StateChanged( nType );
+
+ if ( nType == StateChangedType::State )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate( maStateRect );
+ }
+ else if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::Data) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetWindow( GetWindowType::Prev ), GetStyle() ) );
+
+ if ( (GetPrevStyle() & RADIOBUTTON_VIEW_STYLE) !=
+ (GetStyle() & RADIOBUTTON_VIEW_STYLE) )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+void RadioButton::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Button::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+bool RadioButton::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
+ {
+ // trigger redraw if mouse over state has changed
+ if( IsNativeControlSupported(ControlType::Radiobutton, ControlPart::Entire) )
+ {
+ if (maMouseRect.Contains(GetPointerPosPixel()) != maMouseRect.Contains(GetLastPointerPosPixel()) ||
+ pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
+ {
+ Invalidate( maStateRect );
+ }
+ }
+ }
+ }
+
+ return Button::PreNotify(rNEvt);
+}
+
+void RadioButton::Toggle()
+{
+ ImplCallEventListenersAndHandler( VclEventId::RadiobuttonToggle, [this] () { maToggleHdl.Call(*this); } );
+}
+
+void RadioButton::SetModeRadioImage( const Image& rImage )
+{
+ if ( rImage != maImage )
+ {
+ maImage = rImage;
+ CompatStateChanged( StateChangedType::Data );
+ queue_resize();
+ }
+}
+
+
+void RadioButton::SetState( bool bCheck )
+{
+ // carry the TabStop flag along correctly
+ if ( bCheck )
+ mpWindowImpl->mnStyle |= WB_TABSTOP;
+ else
+ mpWindowImpl->mnStyle &= ~WB_TABSTOP;
+
+ if ( mbChecked != bCheck )
+ {
+ mbChecked = bCheck;
+ CompatStateChanged( StateChangedType::State );
+ Toggle();
+ }
+}
+
+bool RadioButton::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "active")
+ SetState(toBool(rValue));
+ else if (rKey == "image-position")
+ {
+ WinBits nBits = GetStyle();
+ if (rValue == "left")
+ {
+ nBits &= ~(WB_CENTER | WB_RIGHT);
+ nBits |= WB_LEFT;
+ }
+ else if (rValue == "right")
+ {
+ nBits &= ~(WB_CENTER | WB_LEFT);
+ nBits |= WB_RIGHT;
+ }
+ else if (rValue == "top")
+ {
+ nBits &= ~(WB_VCENTER | WB_BOTTOM);
+ nBits |= WB_TOP;
+ }
+ else if (rValue == "bottom")
+ {
+ nBits &= ~(WB_VCENTER | WB_TOP);
+ nBits |= WB_BOTTOM;
+ }
+ //It's rather mad to have to set these bits when there is the other
+ //image align. Looks like e.g. the radiobuttons etc weren't converted
+ //over to image align fully.
+ SetStyle(nBits);
+ //Deliberate to set the sane ImageAlign property
+ return Button::set_property(rKey, rValue);
+ }
+ else
+ return Button::set_property(rKey, rValue);
+ return true;
+}
+
+void RadioButton::Check( bool bCheck )
+{
+ // TabStop-Flag richtig mitfuehren
+ if ( bCheck )
+ mpWindowImpl->mnStyle |= WB_TABSTOP;
+ else
+ mpWindowImpl->mnStyle &= ~WB_TABSTOP;
+
+ if ( mbChecked == bCheck )
+ return;
+
+ mbChecked = bCheck;
+ VclPtr<vcl::Window> xWindow = this;
+ CompatStateChanged( StateChangedType::State );
+ if ( xWindow->isDisposed() )
+ return;
+ if ( bCheck && mbRadioCheck )
+ ImplUncheckAllOther();
+ if ( xWindow->isDisposed() )
+ return;
+ Toggle();
+}
+
+tools::Long Button::ImplGetImageToTextDistance() const
+{
+ // 4 pixels, but take zoom into account, so the text doesn't "jump" relative to surrounding elements,
+ // which might have been aligned with the text of the check box
+ return CalcZoom( 4 );
+}
+
+Size RadioButton::ImplGetRadioImageSize() const
+{
+ Size aSize;
+ bool bDefaultSize = true;
+ if( IsNativeControlSupported( ControlType::Radiobutton, ControlPart::Entire ) )
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), GetSizePixel() );
+ tools::Rectangle aBoundingRgn, aContentRgn;
+
+ // get native size of a radio button
+ if( GetNativeControlRegion( ControlType::Radiobutton, ControlPart::Entire, aCtrlRegion,
+ ControlState::DEFAULT|ControlState::ENABLED,
+ aControlValue,
+ aBoundingRgn, aContentRgn ) )
+ {
+ aSize = aContentRgn.GetSize();
+ bDefaultSize = false;
+ }
+ }
+ if( bDefaultSize )
+ aSize = GetRadioImage( GetSettings(), DrawButtonFlags::NONE ).GetSizePixel();
+ return aSize;
+}
+
+static void LoadThemedImageList(const StyleSettings &rStyleSettings,
+ std::vector<Image>& rList, const std::vector<OUString> &rResources)
+{
+ Color aColorAry1[6];
+ Color aColorAry2[6];
+ aColorAry1[0] = Color( 0xC0, 0xC0, 0xC0 );
+ aColorAry1[1] = Color( 0xFF, 0xFF, 0x00 );
+ aColorAry1[2] = Color( 0xFF, 0xFF, 0xFF );
+ aColorAry1[3] = Color( 0x80, 0x80, 0x80 );
+ aColorAry1[4] = Color( 0x00, 0x00, 0x00 );
+ aColorAry1[5] = Color( 0x00, 0xFF, 0x00 );
+ aColorAry2[0] = rStyleSettings.GetFaceColor();
+ aColorAry2[1] = rStyleSettings.GetWindowColor();
+ aColorAry2[2] = rStyleSettings.GetLightColor();
+ aColorAry2[3] = rStyleSettings.GetShadowColor();
+ aColorAry2[4] = rStyleSettings.GetDarkShadowColor();
+ aColorAry2[5] = rStyleSettings.GetWindowTextColor();
+
+ static_assert( sizeof(aColorAry1) == sizeof(aColorAry2), "aColorAry1 must match aColorAry2" );
+
+ for (const auto &a : rResources)
+ {
+ BitmapEx aBmpEx(a);
+ aBmpEx.Replace(aColorAry1, aColorAry2, SAL_N_ELEMENTS(aColorAry1));
+ rList.emplace_back(aBmpEx);
+ }
+}
+
+Image RadioButton::GetRadioImage( const AllSettings& rSettings, DrawButtonFlags nFlags )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ const StyleSettings& rStyleSettings = rSettings.GetStyleSettings();
+ sal_uInt16 nStyle = 0;
+
+ if ( rStyleSettings.GetOptions() & StyleSettingsOptions::Mono )
+ nStyle = STYLE_RADIOBUTTON_MONO;
+
+ if ( pSVData->maCtrlData.maRadioImgList.empty() ||
+ (pSVData->maCtrlData.mnRadioStyle != nStyle) ||
+ (pSVData->maCtrlData.mnLastRadioFColor != rStyleSettings.GetFaceColor()) ||
+ (pSVData->maCtrlData.mnLastRadioWColor != rStyleSettings.GetWindowColor()) ||
+ (pSVData->maCtrlData.mnLastRadioLColor != rStyleSettings.GetLightColor()) )
+ {
+ pSVData->maCtrlData.maRadioImgList.clear();
+
+ pSVData->maCtrlData.mnLastRadioFColor = rStyleSettings.GetFaceColor();
+ pSVData->maCtrlData.mnLastRadioWColor = rStyleSettings.GetWindowColor();
+ pSVData->maCtrlData.mnLastRadioLColor = rStyleSettings.GetLightColor();
+
+ std::vector<OUString> aResources;
+ if (nStyle)
+ {
+ aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO1);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO2);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO3);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO4);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO5);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO6);
+ }
+ else
+ {
+ aResources.emplace_back(SV_RESID_BITMAP_RADIO1);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIO2);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIO3);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIO4);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIO5);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIO6);
+ }
+ LoadThemedImageList( rStyleSettings, pSVData->maCtrlData.maRadioImgList, aResources);
+ pSVData->maCtrlData.mnRadioStyle = nStyle;
+ }
+
+ sal_uInt16 nIndex;
+ if ( nFlags & DrawButtonFlags::Disabled )
+ {
+ if ( nFlags & DrawButtonFlags::Checked )
+ nIndex = 5;
+ else
+ nIndex = 4;
+ }
+ else if ( nFlags & DrawButtonFlags::Pressed )
+ {
+ if ( nFlags & DrawButtonFlags::Checked )
+ nIndex = 3;
+ else
+ nIndex = 2;
+ }
+ else
+ {
+ if ( nFlags & DrawButtonFlags::Checked )
+ nIndex = 1;
+ else
+ nIndex = 0;
+ }
+ return pSVData->maCtrlData.maRadioImgList[nIndex];
+}
+
+void RadioButton::ImplAdjustNWFSizes()
+{
+ GetOutDev()->Push( vcl::PushFlags::MAPMODE );
+ SetMapMode(MapMode(MapUnit::MapPixel));
+
+ ImplControlValue aControlValue;
+ Size aCurSize( GetSizePixel() );
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), aCurSize );
+ tools::Rectangle aBoundingRgn, aContentRgn;
+
+ // get native size of a radiobutton
+ if( GetNativeControlRegion( ControlType::Radiobutton, ControlPart::Entire, aCtrlRegion,
+ ControlState::DEFAULT|ControlState::ENABLED, aControlValue,
+ aBoundingRgn, aContentRgn ) )
+ {
+ Size aSize = aContentRgn.GetSize();
+
+ if( aSize.Height() > aCurSize.Height() )
+ {
+ aCurSize.setHeight( aSize.Height() );
+ SetSizePixel( aCurSize );
+ }
+ }
+
+ GetOutDev()->Pop();
+}
+
+Size RadioButton::CalcMinimumSize(tools::Long nMaxWidth) const
+{
+ Size aSize;
+ if ( !maImage )
+ aSize = ImplGetRadioImageSize();
+ else
+ {
+ aSize = maImage.GetSizePixel();
+ aSize.AdjustWidth(8);
+ aSize.AdjustHeight(8);
+ }
+
+ if (Button::HasImage())
+ {
+ Size aImgSize = GetModeImage().GetSizePixel();
+ aSize = Size(std::max(aImgSize.Width(), aSize.Width()),
+ std::max(aImgSize.Height(), aSize.Height()));
+ }
+
+ OUString aText = GetText();
+ if (!aText.isEmpty())
+ {
+ bool bTopImage = (GetStyle() & WB_TOP) != 0;
+
+ Size aTextSize = GetTextRect( tools::Rectangle( Point(), Size( nMaxWidth > 0 ? nMaxWidth : 0x7fffffff, 0x7fffffff ) ),
+ aText, FixedText::ImplGetTextStyle( GetStyle() ) ).GetSize();
+
+ aSize.AdjustWidth(2 ); // for focus rect
+
+ if (!bTopImage)
+ {
+ aSize.AdjustWidth(ImplGetImageToTextDistance() );
+ aSize.AdjustWidth(aTextSize.Width() );
+ if ( aSize.Height() < aTextSize.Height() )
+ aSize.setHeight( aTextSize.Height() );
+ }
+ else
+ {
+ aSize.AdjustHeight(6 );
+ aSize.AdjustHeight(GetTextHeight() );
+ if ( aSize.Width() < aTextSize.Width() )
+ aSize.setWidth( aTextSize.Width() );
+ }
+ }
+
+ return CalcWindowSize( aSize );
+}
+
+Size RadioButton::GetOptimalSize() const
+{
+ return CalcMinimumSize();
+}
+
+void RadioButton::ShowFocus(const tools::Rectangle& rRect)
+{
+ if (IsNativeControlSupported(ControlType::Radiobutton, ControlPart::Focus))
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aInRect(Point(0, 0), GetSizePixel());
+
+ aInRect.SetLeft( rRect.Left() ); // exclude the radio element itself from the focusrect
+
+ GetOutDev()->DrawNativeControl(ControlType::Radiobutton, ControlPart::Focus, aInRect,
+ ControlState::FOCUSED, aControlValue, OUString());
+ }
+ Button::ShowFocus(rRect);
+}
+
+void RadioButton::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Button::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("checked", IsChecked());
+
+ OUString sGroupId;
+ std::vector<VclPtr<RadioButton>> aGroup = GetRadioButtonGroup();
+ for(const auto& pButton : aGroup)
+ sGroupId += pButton->get_id();
+
+ if (!sGroupId.isEmpty())
+ rJsonWriter.put("group", sGroupId);
+
+ if (!!maImage)
+ {
+ SvMemoryStream aOStm(6535, 6535);
+ if(GraphicConverter::Export(aOStm, maImage.GetBitmapEx(), ConvertDataFormat::PNG) == ERRCODE_NONE)
+ {
+ css::uno::Sequence<sal_Int8> aSeq( static_cast<sal_Int8 const *>(aOStm.GetData()), aOStm.Tell());
+ OUStringBuffer aBuffer("data:image/png;base64,");
+ ::comphelper::Base64::encode(aBuffer, aSeq);
+ rJsonWriter.put("image", aBuffer.makeStringAndClear());
+ }
+ }
+}
+
+FactoryFunction RadioButton::GetUITestFactory() const
+{
+ return RadioButtonUIObject::create;
+}
+
+void CheckBox::ImplInitCheckBoxData()
+{
+ meState = TRISTATE_FALSE;
+ mbTriState = false;
+}
+
+void CheckBox::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle(getPreviousSibling(pParent), nStyle);
+ Button::ImplInit( pParent, nStyle, nullptr );
+
+ ImplInitSettings( true );
+}
+
+WinBits CheckBox::ImplInitStyle( const vcl::Window* pPrevWindow, WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+ if ( !(nStyle & WB_NOGROUP) &&
+ (!pPrevWindow || (pPrevWindow->GetType() != WindowType::CHECKBOX)) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+const vcl::Font& CheckBox::GetCanonicalFont( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetRadioCheckFont();
+}
+
+const Color& CheckBox::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetRadioCheckTextColor();
+}
+
+void CheckBox::ImplInitSettings( bool bBackground )
+{
+ Button::ImplInitSettings();
+
+ if ( !bBackground )
+ return;
+
+ vcl::Window* pParent = GetParent();
+ if ( !IsControlBackground() &&
+ (pParent->IsChildTransparentModeEnabled() || IsNativeControlSupported( ControlType::Checkbox, ControlPart::Entire ) ) )
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode( ParentClipMode::NoClip );
+ SetPaintTransparent( true );
+ SetBackground();
+ if( IsNativeControlSupported( ControlType::Checkbox, ControlPart::Entire ) )
+ ImplGetWindowImpl()->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects;
+ }
+ else
+ {
+ EnableChildTransparentMode( false );
+ SetParentClipMode();
+ SetPaintTransparent( false );
+
+ if ( IsControlBackground() )
+ SetBackground( GetControlBackground() );
+ else
+ SetBackground( pParent->GetBackground() );
+ }
+}
+
+void CheckBox::ImplDrawCheckBoxState(vcl::RenderContext& rRenderContext)
+{
+ bool bNativeOK = rRenderContext.IsNativeControlSupported(ControlType::Checkbox, ControlPart::Entire);
+ if (bNativeOK)
+ {
+ ImplControlValue aControlValue(meState == TRISTATE_TRUE ? ButtonValue::On : ButtonValue::Off);
+ tools::Rectangle aCtrlRegion(maStateRect);
+ ControlState nState = ControlState::NONE;
+
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+ if (GetButtonState() & DrawButtonFlags::Default)
+ nState |= ControlState::DEFAULT;
+ if (GetButtonState() & DrawButtonFlags::Pressed)
+ nState |= ControlState::PRESSED;
+ if (IsEnabled())
+ nState |= ControlState::ENABLED;
+
+ if (meState == TRISTATE_TRUE)
+ aControlValue.setTristateVal(ButtonValue::On);
+ else if (meState == TRISTATE_INDET)
+ aControlValue.setTristateVal(ButtonValue::Mixed);
+
+ if (IsMouseOver() && maMouseRect.Contains(GetPointerPosPixel()))
+ nState |= ControlState::ROLLOVER;
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Checkbox, ControlPart::Entire, aCtrlRegion,
+ nState, aControlValue, OUString());
+ }
+
+ if (bNativeOK)
+ return;
+
+ DrawButtonFlags nStyle = GetButtonState();
+ if (!IsEnabled())
+ nStyle |= DrawButtonFlags::Disabled;
+ if (meState == TRISTATE_INDET)
+ nStyle |= DrawButtonFlags::DontKnow;
+ else if (meState == TRISTATE_TRUE)
+ nStyle |= DrawButtonFlags::Checked;
+ Image aImage = GetCheckImage(GetSettings(), nStyle);
+ if (IsZoom())
+ rRenderContext.DrawImage(maStateRect.TopLeft(), maStateRect.GetSize(), aImage);
+ else
+ rRenderContext.DrawImage(maStateRect.TopLeft(), aImage);
+}
+
+void CheckBox::ImplDraw( OutputDevice* pDev, SystemTextColorFlags nSystemTextColorFlags,
+ const Point& rPos, const Size& rSize,
+ const Size& rImageSize, tools::Rectangle& rStateRect,
+ tools::Rectangle& rMouseRect )
+{
+ WinBits nWinStyle = GetStyle();
+ OUString aText( GetText() );
+
+ pDev->Push( vcl::PushFlags::CLIPREGION | vcl::PushFlags::LINECOLOR );
+ pDev->IntersectClipRegion( tools::Rectangle( rPos, rSize ) );
+
+ if (!aText.isEmpty() || HasImage())
+ {
+ Button::ImplDrawRadioCheck(pDev, nWinStyle, nSystemTextColorFlags,
+ rPos, rSize, rImageSize,
+ rStateRect, rMouseRect);
+ }
+ else
+ {
+ rStateRect.SetLeft( rPos.X() );
+ if ( nWinStyle & WB_VCENTER )
+ rStateRect.SetTop( rPos.Y()+((rSize.Height()-rImageSize.Height())/2) );
+ else if ( nWinStyle & WB_BOTTOM )
+ rStateRect.SetTop( rPos.Y()+rSize.Height()-rImageSize.Height() );
+ else
+ rStateRect.SetTop( rPos.Y() );
+ rStateRect.SetRight( rStateRect.Left()+rImageSize.Width()-1 );
+ rStateRect.SetBottom( rStateRect.Top()+rImageSize.Height()-1 );
+ // provide space for focusrect
+ // note: this assumes that the control's size was adjusted
+ // accordingly in Get/LoseFocus, so the onscreen position won't change
+ if( HasFocus() )
+ rStateRect.Move( 1, 1 );
+ rMouseRect = rStateRect;
+
+ ImplSetFocusRect( rStateRect );
+ }
+
+ pDev->Pop();
+}
+
+void CheckBox::ImplDrawCheckBox(vcl::RenderContext& rRenderContext)
+{
+ Size aImageSize = ImplGetCheckImageSize();
+ aImageSize.setWidth( CalcZoom( aImageSize.Width() ) );
+ aImageSize.setHeight( CalcZoom( aImageSize.Height() ) );
+
+ HideFocus();
+
+ ImplDraw(&rRenderContext, SystemTextColorFlags::NONE, Point(), GetOutputSizePixel(),
+ aImageSize, maStateRect, maMouseRect);
+
+ ImplDrawCheckBoxState(rRenderContext);
+ if (HasFocus())
+ ShowFocus(ImplGetFocusRect());
+}
+
+void CheckBox::ImplCheck()
+{
+ TriState eNewState;
+ if ( meState == TRISTATE_FALSE )
+ eNewState = TRISTATE_TRUE;
+ else if ( !mbTriState )
+ eNewState = TRISTATE_FALSE;
+ else if ( meState == TRISTATE_TRUE )
+ eNewState = TRISTATE_INDET;
+ else
+ eNewState = TRISTATE_FALSE;
+ meState = eNewState;
+
+ VclPtr<vcl::Window> xWindow = this;
+ Invalidate();
+ Toggle();
+ if ( xWindow->isDisposed() )
+ return;
+ Click();
+}
+
+CheckBox::CheckBox( vcl::Window* pParent, WinBits nStyle ) :
+ Button( WindowType::CHECKBOX )
+{
+ ImplInitCheckBoxData();
+ ImplInit( pParent, nStyle );
+}
+
+void CheckBox::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( rMEvt.IsLeft() && maMouseRect.Contains( rMEvt.GetPosPixel() ) )
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ StartTracking();
+ return;
+ }
+
+ Button::MouseButtonDown( rMEvt );
+}
+
+void CheckBox::Tracking( const TrackingEvent& rTEvt )
+{
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ if ( !(GetStyle() & WB_NOPOINTERFOCUS) && !rTEvt.IsTrackingCanceled() )
+ GrabFocus();
+
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+
+ // do not call click handler if aborted
+ if ( !rTEvt.IsTrackingCanceled() )
+ ImplCheck();
+ else
+ {
+ Invalidate();
+ }
+ }
+ }
+ else
+ {
+ if ( maMouseRect.Contains( rTEvt.GetMouseEvent().GetPosPixel() ) )
+ {
+ if ( !(GetButtonState() & DrawButtonFlags::Pressed) )
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ else
+ {
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ }
+}
+
+void CheckBox::KeyInput( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ if ( !aKeyCode.GetModifier() && (aKeyCode.GetCode() == KEY_SPACE) )
+ {
+ if ( !(GetButtonState() & DrawButtonFlags::Pressed) )
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ else if ( (GetButtonState() & DrawButtonFlags::Pressed) && (aKeyCode.GetCode() == KEY_ESCAPE) )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ else
+ Button::KeyInput( rKEvt );
+}
+
+void CheckBox::KeyUp( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ if ( (GetButtonState() & DrawButtonFlags::Pressed) && (aKeyCode.GetCode() == KEY_SPACE) )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ ImplCheck();
+ }
+ else
+ Button::KeyUp( rKEvt );
+}
+
+void CheckBox::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ const_cast<CheckBox*>(this)->Invalidate();
+}
+
+void CheckBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ ImplDrawCheckBox(rRenderContext);
+}
+
+void CheckBox::Draw( OutputDevice* pDev, const Point& rPos,
+ SystemTextColorFlags nFlags )
+{
+ MapMode aResMapMode( MapUnit::Map100thMM );
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ Size aImageSize = pDev->LogicToPixel( Size( 300, 300 ), aResMapMode );
+ Size aBrd1Size = pDev->LogicToPixel( Size( 20, 20 ), aResMapMode );
+ Size aBrd2Size = pDev->LogicToPixel( Size( 30, 30 ), aResMapMode );
+ tools::Long nCheckWidth = pDev->LogicToPixel( Size( 20, 20 ), aResMapMode ).Width();
+ vcl::Font aFont = GetDrawPixelFont( pDev );
+ tools::Rectangle aStateRect;
+ tools::Rectangle aMouseRect;
+
+ aImageSize.setWidth( CalcZoom( aImageSize.Width() ) );
+ aImageSize.setHeight( CalcZoom( aImageSize.Height() ) );
+ aBrd1Size.setWidth( CalcZoom( aBrd1Size.Width() ) );
+ aBrd1Size.setHeight( CalcZoom( aBrd1Size.Height() ) );
+ aBrd2Size.setWidth( CalcZoom( aBrd2Size.Width() ) );
+ aBrd2Size.setHeight( CalcZoom( aBrd2Size.Height() ) );
+
+ if ( !aBrd1Size.Width() )
+ aBrd1Size.setWidth( 1 );
+ if ( !aBrd1Size.Height() )
+ aBrd1Size.setHeight( 1 );
+ if ( !aBrd2Size.Width() )
+ aBrd2Size.setWidth( 1 );
+ if ( !aBrd2Size.Height() )
+ aBrd2Size.setHeight( 1 );
+ if ( !nCheckWidth )
+ nCheckWidth = 1;
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ if ( nFlags & SystemTextColorFlags::Mono )
+ pDev->SetTextColor( COL_BLACK );
+ else
+ pDev->SetTextColor( GetTextColor() );
+ pDev->SetTextFillColor();
+
+ ImplDraw( pDev, nFlags, aPos, aSize,
+ aImageSize, aStateRect, aMouseRect );
+
+ pDev->SetLineColor();
+ pDev->SetFillColor( COL_BLACK );
+ pDev->DrawRect( aStateRect );
+ aStateRect.AdjustLeft(aBrd1Size.Width() );
+ aStateRect.AdjustTop(aBrd1Size.Height() );
+ aStateRect.AdjustRight( -(aBrd1Size.Width()) );
+ aStateRect.AdjustBottom( -(aBrd1Size.Height()) );
+ if ( meState == TRISTATE_INDET )
+ pDev->SetFillColor( COL_LIGHTGRAY );
+ else
+ pDev->SetFillColor( COL_WHITE );
+ pDev->DrawRect( aStateRect );
+
+ if ( meState == TRISTATE_TRUE )
+ {
+ aStateRect.AdjustLeft(aBrd2Size.Width() );
+ aStateRect.AdjustTop(aBrd2Size.Height() );
+ aStateRect.AdjustRight( -(aBrd2Size.Width()) );
+ aStateRect.AdjustBottom( -(aBrd2Size.Height()) );
+ Point aPos11( aStateRect.TopLeft() );
+ Point aPos12( aStateRect.BottomRight() );
+ Point aPos21( aStateRect.TopRight() );
+ Point aPos22( aStateRect.BottomLeft() );
+ Point aTempPos11( aPos11 );
+ Point aTempPos12( aPos12 );
+ Point aTempPos21( aPos21 );
+ Point aTempPos22( aPos22 );
+ pDev->SetLineColor( COL_BLACK );
+ tools::Long nDX = 0;
+ for ( tools::Long i = 0; i < nCheckWidth; i++ )
+ {
+ if ( !(i % 2) )
+ {
+ aTempPos11.setX( aPos11.X()+nDX );
+ aTempPos12.setX( aPos12.X()+nDX );
+ aTempPos21.setX( aPos21.X()+nDX );
+ aTempPos22.setX( aPos22.X()+nDX );
+ }
+ else
+ {
+ nDX++;
+ aTempPos11.setX( aPos11.X()-nDX );
+ aTempPos12.setX( aPos12.X()-nDX );
+ aTempPos21.setX( aPos21.X()-nDX );
+ aTempPos22.setX( aPos22.X()-nDX );
+ }
+ pDev->DrawLine( aTempPos11, aTempPos12 );
+ pDev->DrawLine( aTempPos21, aTempPos22 );
+ }
+ }
+
+ pDev->Pop();
+}
+
+void CheckBox::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void CheckBox::GetFocus()
+{
+ if (GetText().isEmpty())
+ {
+ // increase button size to have space for focus rect
+ // checkboxes without text will draw focusrect around the check
+ // See CheckBox::ImplDraw()
+ Point aPos( GetPosPixel() );
+ Size aSize( GetSizePixel() );
+ aPos.Move(-1,-1);
+ aSize.AdjustHeight(2 );
+ aSize.AdjustWidth(2 );
+ setPosSizePixel( aPos.X(), aPos.Y(), aSize.Width(), aSize.Height() );
+ Invalidate();
+ // Trigger drawing to initialize the mouse rectangle, otherwise the mouse button down
+ // handler would ignore the mouse event.
+ PaintImmediately();
+ }
+ else
+ ShowFocus( ImplGetFocusRect() );
+
+ SetInputContext( InputContext( GetFont() ) );
+ Button::GetFocus();
+}
+
+void CheckBox::LoseFocus()
+{
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+
+ HideFocus();
+ Button::LoseFocus();
+
+ if (GetText().isEmpty())
+ {
+ // decrease button size again (see GetFocus())
+ // checkboxes without text will draw focusrect around the check
+ Point aPos( GetPosPixel() );
+ Size aSize( GetSizePixel() );
+ aPos.Move(1,1);
+ aSize.AdjustHeight( -2 );
+ aSize.AdjustWidth( -2 );
+ setPosSizePixel( aPos.X(), aPos.Y(), aSize.Width(), aSize.Height() );
+ Invalidate();
+ }
+}
+
+void CheckBox::StateChanged( StateChangedType nType )
+{
+ Button::StateChanged( nType );
+
+ if ( nType == StateChangedType::State )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate( maStateRect );
+ }
+ else if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::Data) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetWindow( GetWindowType::Prev ), GetStyle() ) );
+
+ if ( (GetPrevStyle() & CHECKBOX_VIEW_STYLE) !=
+ (GetStyle() & CHECKBOX_VIEW_STYLE) )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+void CheckBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Button::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+bool CheckBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
+ {
+ // trigger redraw if mouse over state has changed
+ if( IsNativeControlSupported(ControlType::Checkbox, ControlPart::Entire) )
+ {
+ if (maMouseRect.Contains(GetPointerPosPixel()) != maMouseRect.Contains(GetLastPointerPosPixel()) ||
+ pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
+ {
+ Invalidate( maStateRect );
+ }
+ }
+ }
+ }
+
+ return Button::PreNotify(rNEvt);
+}
+
+void CheckBox::Toggle()
+{
+ ImplCallEventListenersAndHandler( VclEventId::CheckboxToggle, [this] () { maToggleHdl.Call(*this); } );
+}
+
+void CheckBox::SetState( TriState eState )
+{
+ if ( !mbTriState && (eState == TRISTATE_INDET) )
+ eState = TRISTATE_FALSE;
+
+ if ( meState != eState )
+ {
+ meState = eState;
+ StateChanged( StateChangedType::State );
+ Toggle();
+ }
+}
+
+bool CheckBox::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "active")
+ SetState(toBool(rValue) ? TRISTATE_TRUE : TRISTATE_FALSE);
+ else
+ return Button::set_property(rKey, rValue);
+ return true;
+}
+
+void CheckBox::EnableTriState( bool bTriState )
+{
+ if ( mbTriState != bTriState )
+ {
+ mbTriState = bTriState;
+
+ if ( !bTriState && (meState == TRISTATE_INDET) )
+ SetState( TRISTATE_FALSE );
+ }
+}
+
+Size CheckBox::ImplGetCheckImageSize() const
+{
+ Size aSize;
+ bool bDefaultSize = true;
+ if( IsNativeControlSupported( ControlType::Checkbox, ControlPart::Entire ) )
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), GetSizePixel() );
+ tools::Rectangle aBoundingRgn, aContentRgn;
+
+ // get native size of a check box
+ if( GetNativeControlRegion( ControlType::Checkbox, ControlPart::Entire, aCtrlRegion,
+ ControlState::DEFAULT|ControlState::ENABLED,
+ aControlValue,
+ aBoundingRgn, aContentRgn ) )
+ {
+ aSize = aContentRgn.GetSize();
+ bDefaultSize = false;
+ }
+ }
+ if( bDefaultSize )
+ aSize = GetCheckImage( GetSettings(), DrawButtonFlags::NONE ).GetSizePixel();
+ return aSize;
+}
+
+Image CheckBox::GetCheckImage( const AllSettings& rSettings, DrawButtonFlags nFlags )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ const StyleSettings& rStyleSettings = rSettings.GetStyleSettings();
+ sal_uInt16 nStyle = 0;
+
+ if ( rStyleSettings.GetOptions() & StyleSettingsOptions::Mono )
+ nStyle = STYLE_CHECKBOX_MONO;
+
+ if ( pSVData->maCtrlData.maCheckImgList.empty() ||
+ (pSVData->maCtrlData.mnCheckStyle != nStyle) ||
+ (pSVData->maCtrlData.mnLastCheckFColor != rStyleSettings.GetFaceColor()) ||
+ (pSVData->maCtrlData.mnLastCheckWColor != rStyleSettings.GetWindowColor()) ||
+ (pSVData->maCtrlData.mnLastCheckLColor != rStyleSettings.GetLightColor()) )
+ {
+ pSVData->maCtrlData.maCheckImgList.clear();
+
+ pSVData->maCtrlData.mnLastCheckFColor = rStyleSettings.GetFaceColor();
+ pSVData->maCtrlData.mnLastCheckWColor = rStyleSettings.GetWindowColor();
+ pSVData->maCtrlData.mnLastCheckLColor = rStyleSettings.GetLightColor();
+
+ std::vector<OUString> aResources;
+ if (nStyle)
+ {
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO1);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO2);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO3);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO4);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO5);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO6);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO7);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO8);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO9);
+ }
+ else
+ {
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK1);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK2);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK3);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK4);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK5);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK6);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK7);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK8);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK9);
+ }
+ LoadThemedImageList(rStyleSettings, pSVData->maCtrlData.maCheckImgList, aResources);
+ pSVData->maCtrlData.mnCheckStyle = nStyle;
+ }
+
+ sal_uInt16 nIndex;
+ if ( nFlags & DrawButtonFlags::Disabled )
+ {
+ if ( nFlags & DrawButtonFlags::DontKnow )
+ nIndex = 8;
+ else if ( nFlags & DrawButtonFlags::Checked )
+ nIndex = 5;
+ else
+ nIndex = 4;
+ }
+ else if ( nFlags & DrawButtonFlags::Pressed )
+ {
+ if ( nFlags & DrawButtonFlags::DontKnow )
+ nIndex = 7;
+ else if ( nFlags & DrawButtonFlags::Checked )
+ nIndex = 3;
+ else
+ nIndex = 2;
+ }
+ else
+ {
+ if ( nFlags & DrawButtonFlags::DontKnow )
+ nIndex = 6;
+ else if ( nFlags & DrawButtonFlags::Checked )
+ nIndex = 1;
+ else
+ nIndex = 0;
+ }
+ return pSVData->maCtrlData.maCheckImgList[nIndex];
+}
+
+void CheckBox::ImplAdjustNWFSizes()
+{
+ GetOutDev()->Push( vcl::PushFlags::MAPMODE );
+ SetMapMode(MapMode(MapUnit::MapPixel));
+
+ ImplControlValue aControlValue;
+ Size aCurSize( GetSizePixel() );
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), aCurSize );
+ tools::Rectangle aBoundingRgn, aContentRgn;
+
+ // get native size of a radiobutton
+ if( GetNativeControlRegion( ControlType::Checkbox, ControlPart::Entire, aCtrlRegion,
+ ControlState::DEFAULT|ControlState::ENABLED, aControlValue,
+ aBoundingRgn, aContentRgn ) )
+ {
+ Size aSize = aContentRgn.GetSize();
+
+ if( aSize.Height() > aCurSize.Height() )
+ {
+ aCurSize.setHeight( aSize.Height() );
+ SetSizePixel( aCurSize );
+ }
+ }
+
+ GetOutDev()->Pop();
+}
+
+Size CheckBox::CalcMinimumSize( tools::Long nMaxWidth ) const
+{
+ Size aSize = ImplGetCheckImageSize();
+ nMaxWidth -= aSize.Width();
+
+ OUString aText = GetText();
+ if (!aText.isEmpty())
+ {
+ // subtract what will be added later
+ nMaxWidth-=2;
+ nMaxWidth -= ImplGetImageToTextDistance();
+
+ Size aTextSize = GetTextRect( tools::Rectangle( Point(), Size( nMaxWidth > 0 ? nMaxWidth : 0x7fffffff, 0x7fffffff ) ),
+ aText, FixedText::ImplGetTextStyle( GetStyle() ) ).GetSize();
+ aSize.AdjustWidth(2 ); // for focus rect
+ aSize.AdjustWidth(ImplGetImageToTextDistance() );
+ aSize.AdjustWidth(aTextSize.Width() );
+ if ( aSize.Height() < aTextSize.Height() )
+ aSize.setHeight( aTextSize.Height() );
+ }
+ else
+ {
+ // is this still correct ? since the checkbox now
+ // shows a focus rect it should be 2 pixels wider and longer
+/* since otherwise the controls in the Writer hang too far up
+ aSize.Width() += 2;
+ aSize.Height() += 2;
+*/
+ }
+
+ return CalcWindowSize( aSize );
+}
+
+Size CheckBox::GetOptimalSize() const
+{
+ int nWidthRequest(get_width_request());
+ return CalcMinimumSize(nWidthRequest != -1 ? nWidthRequest : 0);
+}
+
+void CheckBox::ShowFocus(const tools::Rectangle& rRect)
+{
+ if (IsNativeControlSupported(ControlType::Checkbox, ControlPart::Focus))
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aInRect(Point(0, 0), GetSizePixel());
+
+ aInRect.SetLeft( rRect.Left() ); // exclude the checkbox itself from the focusrect
+
+ GetOutDev()->DrawNativeControl(ControlType::Checkbox, ControlPart::Focus, aInRect,
+ ControlState::FOCUSED, aControlValue, OUString());
+ }
+ Button::ShowFocus(rRect);
+}
+
+void CheckBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Button::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("checked", IsChecked());
+}
+
+FactoryFunction CheckBox::GetUITestFactory() const
+{
+ return CheckBoxUIObject::create;
+}
+
+ImageButton::ImageButton( vcl::Window* pParent, WinBits nStyle ) :
+ PushButton( pParent, nStyle )
+{
+ ImplInitStyle();
+}
+
+void ImageButton::ImplInitStyle()
+{
+ WinBits nStyle = GetStyle();
+
+ if ( ! ( nStyle & ( WB_RIGHT | WB_LEFT ) ) )
+ nStyle |= WB_CENTER;
+
+ if ( ! ( nStyle & ( WB_TOP | WB_BOTTOM ) ) )
+ nStyle |= WB_VCENTER;
+
+ SetStyle( nStyle );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/calendar.cxx b/vcl/source/control/calendar.cxx
new file mode 100644
index 000000000..7554d0d99
--- /dev/null
+++ b/vcl/source/control/calendar.cxx
@@ -0,0 +1,1723 @@
+/* -*- 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 <vcl/builder.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/help.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/calendar.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/dockwin.hxx>
+#include <unotools/calendarwrapper.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <com/sun/star/i18n/Weekdays.hpp>
+#include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
+#include <com/sun/star/i18n/CalendarFieldIndex.hpp>
+#include <sal/log.hxx>
+
+#include <calendar.hxx>
+#include <svdata.hxx>
+#include <strings.hrc>
+#include <memory>
+
+#define DAY_OFFX 4
+#define DAY_OFFY 2
+#define MONTH_BORDERX 4
+#define MONTH_OFFY 3
+#define WEEKDAY_OFFY 3
+#define TITLE_OFFY 3
+#define TITLE_BORDERY 2
+#define SPIN_OFFX 4
+#define SPIN_OFFY TITLE_BORDERY
+
+#define CALENDAR_HITTEST_DAY (sal_uInt16(0x0001))
+#define CALENDAR_HITTEST_MONTHTITLE (sal_uInt16(0x0004))
+#define CALENDAR_HITTEST_PREV (sal_uInt16(0x0008))
+#define CALENDAR_HITTEST_NEXT (sal_uInt16(0x0010))
+
+#define MENU_YEAR_COUNT 3
+
+using namespace ::com::sun::star;
+
+static void ImplCalendarSelectDate( IntDateSet* pTable, const Date& rDate, bool bSelect )
+{
+ if ( bSelect )
+ pTable->insert( rDate.GetDate() );
+ else
+ pTable->erase( rDate.GetDate() );
+}
+
+
+
+void Calendar::ImplInit( WinBits nWinStyle )
+{
+ mpSelectTable.reset(new IntDateSet);
+ mnDayCount = 0;
+ mnWinStyle = nWinStyle;
+ mnFirstYear = 0;
+ mnLastYear = 0;
+ mbCalc = true;
+ mbFormat = true;
+ mbDrag = false;
+ mbMenuDown = false;
+ mbSpinDown = false;
+ mbPrevIn = false;
+ mbNextIn = false;
+
+ OUString aGregorian( "gregorian");
+ maCalendarWrapper.loadCalendar( aGregorian,
+ Application::GetAppLocaleDataWrapper().getLanguageTag().getLocale());
+ if (maCalendarWrapper.getUniqueID() != aGregorian)
+ {
+ SAL_WARN( "vcl.control", "Calendar::ImplInit: No ``gregorian'' calendar available for locale ``"
+ << Application::GetAppLocaleDataWrapper().getLanguageTag().getBcp47()
+ << "'' and other calendars aren't supported. Using en-US fallback." );
+
+ /* If we ever wanted to support other calendars than Gregorian a lot of
+ * rewrite would be necessary to internally replace use of class Date
+ * with proper class CalendarWrapper methods, get rid of fixed 12
+ * months, fixed 7 days, ... */
+ maCalendarWrapper.loadCalendar( aGregorian, lang::Locale( "en", "US", ""));
+ }
+
+ SetFirstDate( maCurDate );
+ ImplCalendarSelectDate( mpSelectTable.get(), maCurDate, true );
+
+ // Sonstige Strings erzeugen
+ maDayText = VclResId(STR_SVT_CALENDAR_DAY);
+ maWeekText = VclResId(STR_SVT_CALENDAR_WEEK);
+
+ // Tagestexte anlegen
+ for (sal_Int32 i = 0; i < 31; ++i)
+ maDayTexts[i] = OUString::number(i+1);
+
+ ImplInitSettings();
+}
+
+void Calendar::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ maSelColor = rStyleSettings.GetHighlightTextColor();
+ SetPointFont(rRenderContext, rStyleSettings.GetToolFont());
+ rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
+ rRenderContext.SetBackground(Wallpaper(rStyleSettings.GetFieldColor()));
+}
+
+void Calendar::ImplInitSettings()
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ maSelColor = rStyleSettings.GetHighlightTextColor();
+ SetPointFont(*GetOutDev(), rStyleSettings.GetToolFont());
+ SetTextColor(rStyleSettings.GetFieldTextColor());
+ SetBackground(Wallpaper(rStyleSettings.GetFieldColor()));
+}
+
+Calendar::Calendar( vcl::Window* pParent, WinBits nWinStyle ) :
+ Control( pParent, nWinStyle & (WB_TABSTOP | WB_GROUP | WB_BORDER | WB_3DLOOK) ),
+ maCalendarWrapper( Application::GetAppLocaleDataWrapper().getComponentContext() ),
+ maOldFormatFirstDate( 0, 0, 1900 ),
+ maOldFormatLastDate( 0, 0, 1900 ),
+ maFirstDate( 0, 0, 1900 ),
+ maOldFirstDate( 0, 0, 1900 ),
+ maCurDate( Date::SYSTEM ),
+ maOldCurDate( 0, 0, 1900 )
+{
+ ImplInit( nWinStyle );
+}
+
+Calendar::~Calendar()
+{
+ disposeOnce();
+}
+
+void Calendar::dispose()
+{
+ mpSelectTable.reset();
+ mpOldSelectTable.reset();
+ Control::dispose();
+}
+
+DayOfWeek Calendar::ImplGetWeekStart() const
+{
+ // Map i18n::Weekdays to Date DayOfWeek
+ DayOfWeek eDay;
+ sal_Int16 nDay = maCalendarWrapper.getFirstDayOfWeek();
+ switch (nDay)
+ {
+ case i18n::Weekdays::SUNDAY :
+ eDay = SUNDAY;
+ break;
+ case i18n::Weekdays::MONDAY :
+ eDay = MONDAY;
+ break;
+ case i18n::Weekdays::TUESDAY :
+ eDay = TUESDAY;
+ break;
+ case i18n::Weekdays::WEDNESDAY :
+ eDay = WEDNESDAY;
+ break;
+ case i18n::Weekdays::THURSDAY :
+ eDay = THURSDAY;
+ break;
+ case i18n::Weekdays::FRIDAY :
+ eDay = FRIDAY;
+ break;
+ case i18n::Weekdays::SATURDAY :
+ eDay = SATURDAY;
+ break;
+ default:
+ SAL_WARN( "vcl.control", "Calendar::ImplGetWeekStart: broken i18n Gregorian calendar (getFirstDayOfWeek())");
+ eDay = SUNDAY;
+ }
+ return eDay;
+}
+
+void Calendar::ImplFormat()
+{
+ if ( !mbFormat )
+ return;
+
+ if ( mbCalc )
+ {
+ Size aOutSize = GetOutputSizePixel();
+
+ if ( (aOutSize.Width() <= 1) || (aOutSize.Height() <= 1) )
+ return;
+
+ tools::Long n99TextWidth = GetTextWidth( "99" );
+ tools::Long nTextHeight = GetTextHeight();
+
+ // calculate width and x-position
+ mnDayWidth = n99TextWidth+DAY_OFFX;
+ mnMonthWidth = mnDayWidth*7;
+ mnMonthWidth += MONTH_BORDERX*2;
+ mnMonthPerLine = aOutSize.Width() / mnMonthWidth;
+ if ( !mnMonthPerLine )
+ mnMonthPerLine = 1;
+ tools::Long nOver = (aOutSize.Width()-(mnMonthPerLine*mnMonthWidth)) / mnMonthPerLine;
+ mnMonthWidth += nOver;
+ mnDaysOffX = MONTH_BORDERX;
+ mnDaysOffX += nOver/2;
+
+ // calculate height and y-position
+ mnDayHeight = nTextHeight + DAY_OFFY;
+ mnWeekDayOffY = nTextHeight + TITLE_OFFY + (TITLE_BORDERY*2);
+ mnDaysOffY = mnWeekDayOffY + nTextHeight + WEEKDAY_OFFY;
+ mnMonthHeight = (mnDayHeight*6) + mnDaysOffY;
+ mnMonthHeight += MONTH_OFFY;
+ mnLines = aOutSize.Height() / mnMonthHeight;
+ if ( !mnLines )
+ mnLines = 1;
+ mnMonthHeight += (aOutSize.Height()-(mnLines*mnMonthHeight)) / mnLines;
+
+ // calculate spinfields
+ tools::Long nSpinSize = nTextHeight+TITLE_BORDERY-SPIN_OFFY;
+ maPrevRect.SetLeft( SPIN_OFFX );
+ maPrevRect.SetTop( SPIN_OFFY );
+ maPrevRect.SetRight( maPrevRect.Left()+nSpinSize );
+ maPrevRect.SetBottom( maPrevRect.Top()+nSpinSize );
+ maNextRect.SetLeft( aOutSize.Width()-SPIN_OFFX-nSpinSize-1 );
+ maNextRect.SetTop( SPIN_OFFY );
+ maNextRect.SetRight( maNextRect.Left()+nSpinSize );
+ maNextRect.SetBottom( maNextRect.Top()+nSpinSize );
+
+ // Calculate DayOfWeekText (gets displayed in a narrow font)
+ maDayOfWeekText.clear();
+ tools::Long nStartOffX = 0;
+ sal_Int16 nDay = maCalendarWrapper.getFirstDayOfWeek();
+ for ( sal_Int16 nDayOfWeek = 0; nDayOfWeek < 7; nDayOfWeek++ )
+ {
+ // Use narrow name.
+ OUString aDayOfWeek( maCalendarWrapper.getDisplayName(
+ i18n::CalendarDisplayIndex::DAY, nDay, 2));
+ tools::Long nOffX = (mnDayWidth-GetTextWidth( aDayOfWeek ))/2;
+ if ( !nDayOfWeek )
+ nStartOffX = nOffX;
+ else
+ nOffX -= nStartOffX;
+ nOffX += nDayOfWeek * mnDayWidth;
+ mnDayOfWeekAry[nDayOfWeek] = nOffX;
+ maDayOfWeekText += aDayOfWeek;
+ nDay++;
+ nDay %= 7;
+ }
+
+ mbCalc = false;
+ }
+
+ // calculate number of days
+
+ DayOfWeek eStartDay = ImplGetWeekStart();
+
+ sal_uInt16 nWeekDay;
+ Date aTempDate = GetFirstMonth();
+ maFirstDate = aTempDate;
+ nWeekDay = static_cast<sal_uInt16>(aTempDate.GetDayOfWeek());
+ nWeekDay = (nWeekDay+(7-static_cast<sal_uInt16>(eStartDay))) % 7;
+ maFirstDate.AddDays( -nWeekDay );
+ mnDayCount = nWeekDay;
+ sal_uInt16 nDaysInMonth;
+ sal_uInt16 nMonthCount = static_cast<sal_uInt16>(mnMonthPerLine*mnLines);
+ for ( sal_uInt16 i = 0; i < nMonthCount; i++ )
+ {
+ nDaysInMonth = aTempDate.GetDaysInMonth();
+ mnDayCount += nDaysInMonth;
+ aTempDate.AddDays( nDaysInMonth );
+ }
+ Date aTempDate2 = aTempDate;
+ --aTempDate2;
+ nDaysInMonth = aTempDate2.GetDaysInMonth();
+ aTempDate2.AddDays( -(nDaysInMonth-1) );
+ nWeekDay = static_cast<sal_uInt16>(aTempDate2.GetDayOfWeek());
+ nWeekDay = (nWeekDay+(7-static_cast<sal_uInt16>(eStartDay))) % 7;
+ mnDayCount += 42-nDaysInMonth-nWeekDay;
+
+ // determine colours
+ maOtherColor = COL_LIGHTGRAY;
+ if ( maOtherColor.IsRGBEqual( GetBackground().GetColor() ) )
+ maOtherColor = COL_GRAY;
+
+ Date aLastDate = GetLastDate();
+ if ( (maOldFormatLastDate != aLastDate) ||
+ (maOldFormatFirstDate != maFirstDate) )
+ {
+ maOldFormatFirstDate = maFirstDate;
+ maOldFormatLastDate = aLastDate;
+ }
+
+ // get DateInfo
+ sal_Int16 nNewFirstYear = maFirstDate.GetYear();
+ sal_Int16 nNewLastYear = GetLastDate().GetYear();
+ if ( mnFirstYear )
+ {
+ if ( nNewFirstYear < mnFirstYear )
+ {
+ mnFirstYear = nNewFirstYear;
+ }
+ if ( nNewLastYear > mnLastYear )
+ {
+ mnLastYear = nNewLastYear;
+ }
+ }
+ else
+ {
+ mnFirstYear = nNewFirstYear;
+ mnLastYear = nNewLastYear;
+ }
+
+ mbFormat = false;
+}
+
+sal_uInt16 Calendar::ImplDoHitTest( const Point& rPos, Date& rDate ) const
+{
+ if ( mbFormat )
+ return 0;
+
+ if ( maPrevRect.Contains( rPos ) )
+ return CALENDAR_HITTEST_PREV;
+ else if ( maNextRect.Contains( rPos ) )
+ return CALENDAR_HITTEST_NEXT;
+
+ tools::Long nY;
+ tools::Long nOffX;
+ sal_Int32 nDay;
+ DayOfWeek eStartDay = ImplGetWeekStart();
+
+ rDate = GetFirstMonth();
+ nY = 0;
+ for ( tools::Long i = 0; i < mnLines; i++ )
+ {
+ if ( rPos.Y() < nY )
+ return 0;
+
+ tools::Long nX = 0;
+ tools::Long nYMonth = nY+mnMonthHeight;
+ for ( tools::Long j = 0; j < mnMonthPerLine; j++ )
+ {
+ if ( (rPos.X() < nX) && (rPos.Y() < nYMonth) )
+ return 0;
+
+ sal_uInt16 nDaysInMonth = rDate.GetDaysInMonth();
+
+ // matching month was found
+ if ( (rPos.X() > nX) && (rPos.Y() < nYMonth) &&
+ (rPos.X() < nX+mnMonthWidth) )
+ {
+ if ( rPos.Y() < (nY+(TITLE_BORDERY*2)+mnDayHeight))
+ return CALENDAR_HITTEST_MONTHTITLE;
+ else
+ {
+ tools::Long nDayX = nX+mnDaysOffX;
+ tools::Long nDayY = nY+mnDaysOffY;
+ if ( rPos.Y() < nDayY )
+ return 0;
+ sal_Int32 nDayIndex = static_cast<sal_Int32>(rDate.GetDayOfWeek());
+ nDayIndex = (nDayIndex+(7-static_cast<sal_Int32>(eStartDay))) % 7;
+ if ( (i == 0) && (j == 0) )
+ {
+ Date aTempDate = rDate;
+ aTempDate.AddDays( -nDayIndex );
+ for ( nDay = 0; nDay < nDayIndex; nDay++ )
+ {
+ nOffX = nDayX + (nDay*mnDayWidth);
+ if ( (rPos.Y() >= nDayY) && (rPos.Y() < nDayY+mnDayHeight) &&
+ (rPos.X() >= nOffX) && (rPos.X() < nOffX+mnDayWidth) )
+ {
+ rDate = aTempDate;
+ rDate.AddDays( nDay );
+ return CALENDAR_HITTEST_DAY;
+ }
+ }
+ }
+ for ( nDay = 1; nDay <= nDaysInMonth; nDay++ )
+ {
+ if ( rPos.Y() < nDayY )
+ {
+ rDate.AddDays( nDayIndex );
+ return 0;
+ }
+ nOffX = nDayX + (nDayIndex*mnDayWidth);
+ if ( (rPos.Y() >= nDayY) && (rPos.Y() < nDayY+mnDayHeight) &&
+ (rPos.X() >= nOffX) && (rPos.X() < nOffX+mnDayWidth) )
+ {
+ rDate.AddDays( nDay-1 );
+ return CALENDAR_HITTEST_DAY;
+ }
+ if ( nDayIndex == 6 )
+ {
+ nDayIndex = 0;
+ nDayY += mnDayHeight;
+ }
+ else
+ nDayIndex++;
+ }
+ if ( (i == mnLines-1) && (j == mnMonthPerLine-1) )
+ {
+ sal_uInt16 nWeekDay = static_cast<sal_uInt16>(rDate.GetDayOfWeek());
+ nWeekDay = (nWeekDay+(7-static_cast<sal_uInt16>(eStartDay))) % 7;
+ sal_Int32 nDayCount = 42-nDaysInMonth-nWeekDay;
+ Date aTempDate = rDate;
+ aTempDate.AddDays( nDaysInMonth );
+ for ( nDay = 1; nDay <= nDayCount; nDay++ )
+ {
+ if ( rPos.Y() < nDayY )
+ {
+ rDate.AddDays( nDayIndex );
+ return 0;
+ }
+ nOffX = nDayX + (nDayIndex*mnDayWidth);
+ if ( (rPos.Y() >= nDayY) && (rPos.Y() < nDayY+mnDayHeight) &&
+ (rPos.X() >= nOffX) && (rPos.X() < nOffX+mnDayWidth) )
+ {
+ rDate = aTempDate;
+ rDate.AddDays( nDay-1 );
+ return CALENDAR_HITTEST_DAY;
+ }
+ if ( nDayIndex == 6 )
+ {
+ nDayIndex = 0;
+ nDayY += mnDayHeight;
+ }
+ else
+ nDayIndex++;
+ }
+ }
+ }
+ }
+
+ rDate.AddDays( nDaysInMonth );
+ nX += mnMonthWidth;
+ }
+
+ nY += mnMonthHeight;
+ }
+
+ return 0;
+}
+
+namespace
+{
+
+void ImplDrawSpinArrow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, bool bPrev)
+{
+ tools::Long i;
+ tools::Long n;
+ tools::Long nLines;
+ tools::Long nHeight = rRect.GetHeight();
+ tools::Long nWidth = rRect.GetWidth();
+ if (nWidth < nHeight)
+ n = nWidth;
+ else
+ n = nHeight;
+ if (!(n & 0x01))
+ n--;
+ nLines = n/2;
+
+ tools::Rectangle aRect(Point( rRect.Left() + (nWidth / 2) - (nLines / 2),
+ rRect.Top() + (nHeight / 2) ),
+ Size(1, 1));
+ if (!bPrev)
+ {
+ aRect.AdjustLeft(nLines );
+ aRect.AdjustRight(nLines );
+ }
+
+ rRenderContext.DrawRect(aRect);
+ for (i = 0; i < nLines; i++)
+ {
+ if (bPrev)
+ {
+ aRect.AdjustLeft( 1 );
+ aRect.AdjustRight( 1 );
+ }
+ else
+ {
+ aRect.AdjustLeft( -1 );
+ aRect.AdjustRight( -1 );
+ }
+ aRect.AdjustTop( -1 );
+ aRect.AdjustBottom( 1 );
+ rRenderContext.DrawRect(aRect);
+ }
+}
+
+} //end anonymous namespace
+
+void Calendar::ImplDrawSpin(vcl::RenderContext& rRenderContext )
+{
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetButtonTextColor());
+ tools::Rectangle aOutRect = maPrevRect;
+ aOutRect.AdjustLeft(3 );
+ aOutRect.AdjustTop(3 );
+ aOutRect.AdjustRight( -3 );
+ aOutRect.AdjustBottom( -3 );
+ ImplDrawSpinArrow(rRenderContext, aOutRect, true);
+ aOutRect = maNextRect;
+ aOutRect.AdjustLeft(3 );
+ aOutRect.AdjustTop(3 );
+ aOutRect.AdjustRight( -3 );
+ aOutRect.AdjustBottom( -3 );
+ ImplDrawSpinArrow(rRenderContext, aOutRect, false);
+}
+
+void Calendar::ImplDrawDate(vcl::RenderContext& rRenderContext,
+ tools::Long nX, tools::Long nY,
+ sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear,
+ bool bOther, sal_Int32 nToday )
+{
+ Color const * pTextColor = nullptr;
+ const OUString& rDay = maDayTexts[nDay - 1];
+ tools::Rectangle aDateRect(nX, nY, nX + mnDayWidth - 1, nY + mnDayHeight - 1);
+
+ bool bSel = false;
+ bool bFocus = false;
+ // actual day
+ if ((nDay == maCurDate.GetDay()) &&
+ (nMonth == maCurDate.GetMonth()) &&
+ (nYear == maCurDate.GetYear()))
+ {
+ bFocus = true;
+ }
+ if (mpSelectTable)
+ {
+ if (mpSelectTable->find(Date(nDay, nMonth, nYear).GetDate()) != mpSelectTable->end())
+ bSel = true;
+ }
+
+ // get textcolour
+ if (bSel)
+ pTextColor = &maSelColor;
+ else if (bOther)
+ pTextColor = &maOtherColor;
+
+ if (bFocus)
+ HideFocus();
+
+ // display background
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ if (bSel)
+ {
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor(rStyleSettings.GetHighlightColor());
+ rRenderContext.DrawRect(aDateRect);
+ }
+
+ // display text
+ tools::Long nTextX = nX + (mnDayWidth - GetTextWidth(rDay)) - (DAY_OFFX / 2);
+ tools::Long nTextY = nY + (mnDayHeight - GetTextHeight()) / 2;
+ if (pTextColor)
+ {
+ Color aOldColor = rRenderContext.GetTextColor();
+ rRenderContext.SetTextColor(*pTextColor);
+ rRenderContext.DrawText(Point(nTextX, nTextY), rDay);
+ rRenderContext.SetTextColor(aOldColor);
+ }
+ else
+ rRenderContext.DrawText(Point(nTextX, nTextY), rDay);
+
+ // today
+ Date aTodayDate(maCurDate);
+ if (nToday)
+ aTodayDate.SetDate(nToday);
+ else
+ aTodayDate = Date(Date::SYSTEM);
+ if ((nDay == aTodayDate.GetDay()) &&
+ (nMonth == aTodayDate.GetMonth()) &&
+ (nYear == aTodayDate.GetYear()))
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetWindowTextColor());
+ rRenderContext.SetFillColor();
+ rRenderContext.DrawRect(aDateRect);
+ }
+
+ // if needed do FocusRect
+ if (bFocus && HasFocus())
+ ShowFocus(aDateRect);
+}
+
+void Calendar::ImplDraw(vcl::RenderContext& rRenderContext)
+{
+ ImplFormat();
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ Size aOutSize(GetOutputSizePixel());
+ tools::Long i;
+ tools::Long j;
+ tools::Long nY;
+ tools::Long nDeltaX;
+ tools::Long nDeltaY;
+ tools::Long nDayX;
+ tools::Long nDayY;
+ sal_Int32 nToday = Date(Date::SYSTEM).GetDate();
+ sal_uInt16 nDay;
+ sal_uInt16 nMonth;
+ sal_Int16 nYear;
+ Date aDate = GetFirstMonth();
+ DayOfWeek eStartDay = ImplGetWeekStart();
+
+ HideFocus();
+
+ nY = 0;
+ for (i = 0; i < mnLines; i++)
+ {
+ // display title bar
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor(rStyleSettings.GetFaceColor());
+ tools::Rectangle aTitleRect(0, nY, aOutSize.Width() - 1, nY + mnDayHeight - DAY_OFFY + TITLE_BORDERY * 2);
+ rRenderContext.DrawRect(aTitleRect);
+ Point aTopLeft1(aTitleRect.Left(), aTitleRect.Top());
+ Point aTopLeft2(aTitleRect.Left(), aTitleRect.Top() + 1);
+ Point aBottomRight1(aTitleRect.Right(), aTitleRect.Bottom());
+ Point aBottomRight2(aTitleRect.Right(), aTitleRect.Bottom() - 1);
+ rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
+ rRenderContext.DrawLine(aTopLeft1, Point(aBottomRight1.X(), aTopLeft1.Y()));
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor() );
+ rRenderContext.DrawLine(aTopLeft2, Point(aBottomRight2.X(), aTopLeft2.Y()));
+ rRenderContext.DrawLine(aTopLeft2, Point(aTopLeft2.X(), aBottomRight2.Y()));
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor() );
+ rRenderContext.DrawLine(Point(aTopLeft2.X(), aBottomRight2.Y()), aBottomRight2);
+ rRenderContext.DrawLine(Point(aBottomRight2.X(), aTopLeft2.Y()), aBottomRight2);
+ rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
+ rRenderContext.DrawLine(Point(aTopLeft1.X(), aBottomRight1.Y()), aBottomRight1);
+ Point aSepPos1(0, aTitleRect.Top() + TITLE_BORDERY);
+ Point aSepPos2(0, aTitleRect.Bottom() - TITLE_BORDERY);
+ for (j = 0; j < mnMonthPerLine-1; j++)
+ {
+ aSepPos1.AdjustX(mnMonthWidth-1 );
+ aSepPos2.setX( aSepPos1.X() );
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(aSepPos1, aSepPos2);
+ aSepPos1.AdjustX( 1 );
+ aSepPos2.setX( aSepPos1.X() );
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ rRenderContext.DrawLine(aSepPos1, aSepPos2);
+ }
+
+ tools::Long nX = 0;
+ for (j = 0; j < mnMonthPerLine; j++)
+ {
+ nMonth = aDate.GetMonth();
+ nYear = aDate.GetYear();
+
+ // display month in title bar
+ nDeltaX = nX;
+ nDeltaY = nY + TITLE_BORDERY;
+ OUString aMonthText = maCalendarWrapper.getDisplayName(i18n::CalendarDisplayIndex::MONTH, nMonth - 1, 1)
+ + " "
+ + OUString::number(nYear);
+ tools::Long nMonthTextWidth = rRenderContext.GetTextWidth(aMonthText);
+ tools::Long nMonthOffX1 = 0;
+ tools::Long nMonthOffX2 = 0;
+ if (i == 0)
+ {
+ if (j == 0)
+ nMonthOffX1 = maPrevRect.Right() + 1;
+ if (j == mnMonthPerLine - 1)
+ nMonthOffX2 = aOutSize.Width() - maNextRect.Left() + 1;
+ }
+ tools::Long nMaxMonthWidth = mnMonthWidth - nMonthOffX1 - nMonthOffX2 - 4;
+ if (nMonthTextWidth > nMaxMonthWidth)
+ {
+ // Abbreviated month name.
+ aMonthText = maCalendarWrapper.getDisplayName(i18n::CalendarDisplayIndex::MONTH, nMonth - 1, 0)
+ + " "
+ + OUString::number(nYear);
+ nMonthTextWidth = rRenderContext.GetTextWidth(aMonthText);
+ }
+ tools::Long nTempOff = (mnMonthWidth - nMonthTextWidth + 1) / 2;
+ if (nTempOff < nMonthOffX1)
+ nDeltaX += nMonthOffX1 + 1;
+ else
+ {
+ if (nTempOff + nMonthTextWidth > mnMonthWidth - nMonthOffX2)
+ nDeltaX += mnMonthWidth - nMonthOffX2 - nMonthTextWidth;
+ else
+ nDeltaX += nTempOff;
+ }
+ rRenderContext.SetTextColor(rStyleSettings.GetButtonTextColor());
+ rRenderContext.DrawText(Point(nDeltaX, nDeltaY), aMonthText);
+ rRenderContext.SetTextColor(rStyleSettings.GetWindowTextColor());
+
+ // display week bar
+ nDayX = nX + mnDaysOffX;
+ nDayY = nY + mnWeekDayOffY;
+ nDeltaY = nDayY + mnDayHeight;
+ rRenderContext.SetLineColor(rStyleSettings.GetWindowTextColor());
+ Point aStartPos(nDayX, nDeltaY);
+ rRenderContext.DrawLine(aStartPos, Point(nDayX + (7 * mnDayWidth), nDeltaY));
+ std::vector<sal_Int32> aTmp;
+ for (int k=0; k<6; ++k)
+ aTmp.push_back(mnDayOfWeekAry[k+1]);
+ rRenderContext.DrawTextArray(Point(nDayX + mnDayOfWeekAry[0], nDayY), maDayOfWeekText, aTmp);
+
+ // display days
+ sal_uInt16 nDaysInMonth = aDate.GetDaysInMonth();
+ nDayX = nX + mnDaysOffX;
+ nDayY = nY + mnDaysOffY;
+ sal_uInt16 nDayIndex = static_cast<sal_uInt16>(aDate.GetDayOfWeek());
+ nDayIndex = (nDayIndex + (7 - static_cast<sal_uInt16>(eStartDay))) % 7;
+ if (i == 0 && j == 0)
+ {
+ Date aTempDate = aDate;
+ aTempDate.AddDays( -nDayIndex );
+ for (nDay = 0; nDay < nDayIndex; ++nDay)
+ {
+ nDeltaX = nDayX + (nDay * mnDayWidth);
+ ImplDrawDate(rRenderContext, nDeltaX, nDayY, nDay + aTempDate.GetDay(),
+ aTempDate.GetMonth(), aTempDate.GetYear(),
+ true, nToday);
+ }
+ }
+ for (nDay = 1; nDay <= nDaysInMonth; nDay++)
+ {
+ nDeltaX = nDayX + (nDayIndex * mnDayWidth);
+ ImplDrawDate(rRenderContext, nDeltaX, nDayY, nDay, nMonth, nYear,
+ false, nToday);
+ if (nDayIndex == 6)
+ {
+ nDayIndex = 0;
+ nDayY += mnDayHeight;
+ }
+ else
+ nDayIndex++;
+ }
+ if ((i == mnLines - 1) && (j == mnMonthPerLine - 1))
+ {
+ sal_uInt16 nWeekDay = static_cast<sal_uInt16>(aDate.GetDayOfWeek());
+ nWeekDay = (nWeekDay + (7 - static_cast<sal_uInt16>(eStartDay))) % 7;
+ sal_uInt16 nDayCount = 42 - nDaysInMonth - nWeekDay;
+ Date aTempDate = aDate;
+ aTempDate.AddDays( nDaysInMonth );
+ for (nDay = 1; nDay <= nDayCount; ++nDay)
+ {
+ nDeltaX = nDayX + (nDayIndex * mnDayWidth);
+ ImplDrawDate(rRenderContext, nDeltaX, nDayY, nDay,
+ aTempDate.GetMonth(), aTempDate.GetYear(),
+ true, nToday);
+ if (nDayIndex == 6)
+ {
+ nDayIndex = 0;
+ nDayY += mnDayHeight;
+ }
+ else
+ nDayIndex++;
+ }
+ }
+
+ aDate.AddDays( nDaysInMonth );
+ nX += mnMonthWidth;
+ }
+
+ nY += mnMonthHeight;
+ }
+
+ // draw spin buttons
+ ImplDrawSpin(rRenderContext);
+}
+
+void Calendar::ImplUpdateDate( const Date& rDate )
+{
+ if (IsReallyVisible() && IsUpdateMode())
+ {
+ tools::Rectangle aDateRect(GetDateRect(rDate));
+ if (!aDateRect.IsEmpty())
+ {
+ Invalidate(aDateRect);
+ }
+ }
+}
+
+void Calendar::ImplUpdateSelection( IntDateSet* pOld )
+{
+ IntDateSet* pNew = mpSelectTable.get();
+
+ for (auto const& nKey : *pOld)
+ {
+ if ( pNew->find(nKey) == pNew->end() )
+ {
+ Date aTempDate(nKey);
+ ImplUpdateDate(aTempDate);
+ }
+ }
+
+ for (auto const& nKey : *pNew)
+ {
+ if ( pOld->find(nKey) == pOld->end() )
+ {
+ Date aTempDate(nKey);
+ ImplUpdateDate(aTempDate);
+ }
+ }
+}
+
+void Calendar::ImplMouseSelect( const Date& rDate, sal_uInt16 nHitTest )
+{
+ IntDateSet aOldSel( *mpSelectTable );
+ Date aOldDate = maCurDate;
+ Date aTempDate = rDate;
+
+ if ( !(nHitTest & CALENDAR_HITTEST_DAY) )
+ --aTempDate;
+
+ if ( !(nHitTest & CALENDAR_HITTEST_DAY) )
+ aTempDate = maOldCurDate;
+ if ( aTempDate != maCurDate )
+ {
+ maCurDate = aTempDate;
+ ImplCalendarSelectDate( mpSelectTable.get(), aOldDate, false );
+ ImplCalendarSelectDate( mpSelectTable.get(), maCurDate, true );
+ }
+
+ bool bNewSel = aOldSel != *mpSelectTable;
+ if ( (maCurDate != aOldDate) || bNewSel )
+ {
+ HideFocus();
+ if ( bNewSel )
+ ImplUpdateSelection( &aOldSel );
+ if ( !bNewSel || aOldSel.find( aOldDate.GetDate() ) == aOldSel.end() )
+ ImplUpdateDate( aOldDate );
+ // assure focus rectangle is displayed again
+ if ( HasFocus() || !bNewSel
+ || mpSelectTable->find( maCurDate.GetDate() ) == mpSelectTable->end() )
+ ImplUpdateDate( maCurDate );
+ }
+}
+
+void Calendar::ImplUpdate( bool bCalcNew )
+{
+ if (IsReallyVisible() && IsUpdateMode())
+ {
+ if (bCalcNew && !mbCalc)
+ {
+ Invalidate();
+ }
+ else if (!mbFormat && !mbCalc)
+ {
+ Invalidate();
+ }
+ }
+
+ if (bCalcNew)
+ mbCalc = true;
+ mbFormat = true;
+}
+
+void Calendar::ImplScroll( bool bPrev )
+{
+ Date aNewFirstMonth = GetFirstMonth();
+ if ( bPrev )
+ {
+ --aNewFirstMonth;
+ aNewFirstMonth.AddDays( -(aNewFirstMonth.GetDaysInMonth()-1));
+ }
+ else
+ aNewFirstMonth.AddDays( aNewFirstMonth.GetDaysInMonth());
+ SetFirstDate( aNewFirstMonth );
+}
+
+void Calendar::ImplShowMenu( const Point& rPos, const Date& rDate )
+{
+ EndSelection();
+
+ Date aOldFirstDate = GetFirstMonth();
+ ScopedVclPtrInstance<PopupMenu> aPopupMenu;
+ sal_uInt16 nMonthOff;
+ sal_uInt16 nCurItemId;
+ sal_uInt16 nYear = rDate.GetYear()-1;
+ sal_uInt16 i;
+ sal_uInt16 j;
+ sal_uInt16 nYearIdCount = 1000;
+
+ nMonthOff = (rDate.GetYear()-aOldFirstDate.GetYear())*12;
+ if ( aOldFirstDate.GetMonth() < rDate.GetMonth() )
+ nMonthOff += rDate.GetMonth()-aOldFirstDate.GetMonth();
+ else
+ nMonthOff -= aOldFirstDate.GetMonth()-rDate.GetMonth();
+
+ // construct menu (include years with different months)
+ for ( i = 0; i < MENU_YEAR_COUNT; i++ )
+ {
+ VclPtrInstance<PopupMenu> pYearPopupMenu;
+ for ( j = 1; j <= 12; j++ )
+ pYearPopupMenu->InsertItem( nYearIdCount+j,
+ maCalendarWrapper.getDisplayName(
+ i18n::CalendarDisplayIndex::MONTH, j-1, 1));
+ aPopupMenu->InsertItem( 10+i, OUString::number( nYear+i ) );
+ aPopupMenu->SetPopupMenu( 10+i, pYearPopupMenu );
+ nYearIdCount += 1000;
+ }
+
+ mbMenuDown = true;
+ nCurItemId = aPopupMenu->Execute( this, rPos );
+ mbMenuDown = false;
+
+ if ( !nCurItemId )
+ return;
+
+ sal_uInt16 nTempMonthOff = nMonthOff % 12;
+ sal_uInt16 nTempYearOff = nMonthOff / 12;
+ sal_uInt16 nNewMonth = nCurItemId % 1000;
+ sal_uInt16 nNewYear = nYear+((nCurItemId-1000)/1000);
+ if ( nTempMonthOff < nNewMonth )
+ nNewMonth = nNewMonth - nTempMonthOff;
+ else
+ {
+ nNewYear--;
+ nNewMonth = 12-(nTempMonthOff-nNewMonth);
+ }
+ nNewYear = nNewYear - nTempYearOff;
+ SetFirstDate( Date( 1, nNewMonth, nNewYear ) );
+}
+
+void Calendar::ImplTracking( const Point& rPos, bool bRepeat )
+{
+ Date aTempDate = maCurDate;
+ sal_uInt16 nHitTest = ImplDoHitTest( rPos, aTempDate );
+
+ if ( mbSpinDown )
+ {
+ mbPrevIn = (nHitTest & CALENDAR_HITTEST_PREV) != 0;
+ mbNextIn = (nHitTest & CALENDAR_HITTEST_NEXT) != 0;
+
+ if ( bRepeat && (mbPrevIn || mbNextIn) )
+ {
+ ImplScroll( mbPrevIn );
+ }
+ }
+ else
+ ImplMouseSelect( aTempDate, nHitTest );
+}
+
+void Calendar::ImplEndTracking( bool bCancel )
+{
+ bool bSelection = false;
+ bool bSpinDown = mbSpinDown;
+
+ mbDrag = false;
+ mbSpinDown = false;
+ mbPrevIn = false;
+ mbNextIn = false;
+
+ if ( bCancel )
+ {
+ if ( maOldFirstDate != maFirstDate )
+ SetFirstDate( maOldFirstDate );
+
+ if ( !bSpinDown )
+ {
+ IntDateSet aOldSel( *mpSelectTable );
+ Date aOldDate = maCurDate;
+ maCurDate = maOldCurDate;
+ *mpSelectTable = *mpOldSelectTable;
+ HideFocus();
+ ImplUpdateSelection( &aOldSel );
+ if ( aOldSel.find( aOldDate.GetDate() ) == aOldSel.end() )
+ ImplUpdateDate( aOldDate );
+ // assure focus rectangle is displayed again
+ if ( HasFocus() || mpSelectTable->find( maCurDate.GetDate() ) == mpSelectTable->end() )
+ ImplUpdateDate( maCurDate );
+ }
+ }
+
+ if ( bSpinDown )
+ return;
+
+ if ( !bCancel )
+ {
+ // determine if we should scroll the visible area
+ if ( !mpSelectTable->empty() )
+ {
+ Date aFirstSelDate( *mpSelectTable->begin() );
+ Date aLastSelDate( *mpSelectTable->rbegin() );
+ if ( aLastSelDate < GetFirstMonth() )
+ ImplScroll( true );
+ else if ( GetLastMonth() < aFirstSelDate )
+ ImplScroll( false );
+ }
+ }
+
+ if ( !bCancel && ((maCurDate != maOldCurDate) || (*mpOldSelectTable != *mpSelectTable)) )
+ Select();
+
+ if ( !bSelection && (mnWinStyle & WB_TABSTOP) && !bCancel )
+ GrabFocus();
+
+ mpOldSelectTable.reset();
+}
+
+void Calendar::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( rMEvt.IsLeft() && !mbMenuDown )
+ {
+ Date aTempDate = maCurDate;
+ sal_uInt16 nHitTest = ImplDoHitTest( rMEvt.GetPosPixel(), aTempDate );
+ if ( nHitTest )
+ {
+ if ( nHitTest & CALENDAR_HITTEST_MONTHTITLE )
+ ImplShowMenu( rMEvt.GetPosPixel(), aTempDate );
+ else
+ {
+ maOldFirstDate = maFirstDate;
+
+ mbPrevIn = (nHitTest & CALENDAR_HITTEST_PREV) != 0;
+ mbNextIn = (nHitTest & CALENDAR_HITTEST_NEXT) != 0;
+ if ( mbPrevIn || mbNextIn )
+ {
+ mbSpinDown = true;
+ ImplScroll( mbPrevIn );
+ // it should really read BUTTONREPEAT, therefore do not
+ // change it to SCROLLREPEAT, check with TH,
+ // why it could be different (71775)
+ StartTracking( StartTrackingFlags::ButtonRepeat );
+ }
+ else
+ {
+ if ( (rMEvt.GetClicks() != 2) || !(nHitTest & CALENDAR_HITTEST_DAY) )
+ {
+ maOldCurDate = maCurDate;
+ mpOldSelectTable.reset(new IntDateSet( *mpSelectTable ));
+
+ mbDrag = true;
+ StartTracking();
+
+ ImplMouseSelect( aTempDate, nHitTest );
+ }
+ if (rMEvt.GetClicks() == 2)
+ maActivateHdl.Call(this);
+ }
+ }
+ }
+
+ return;
+ }
+
+ Control::MouseButtonDown( rMEvt );
+}
+
+void Calendar::Tracking( const TrackingEvent& rTEvt )
+{
+ Point aMousePos = rTEvt.GetMouseEvent().GetPosPixel();
+
+ if ( rTEvt.IsTrackingEnded() )
+ ImplEndTracking( rTEvt.IsTrackingCanceled() );
+ else
+ ImplTracking( aMousePos, rTEvt.IsTrackingRepeat() );
+}
+
+void Calendar::KeyInput( const KeyEvent& rKEvt )
+{
+ Date aNewDate = maCurDate;
+
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_HOME:
+ aNewDate.SetDay( 1 );
+ break;
+
+ case KEY_END:
+ aNewDate.SetDay( aNewDate.GetDaysInMonth() );
+ break;
+
+ case KEY_LEFT:
+ --aNewDate;
+ break;
+
+ case KEY_RIGHT:
+ ++aNewDate;
+ break;
+
+ case KEY_UP:
+ aNewDate.AddDays( -7 );
+ break;
+
+ case KEY_DOWN:
+ aNewDate.AddDays( 7 );
+ break;
+
+ case KEY_PAGEUP:
+ {
+ Date aTempDate = aNewDate;
+ aTempDate.AddDays( -(aNewDate.GetDay()+1) );
+ aNewDate.AddDays( -aTempDate.GetDaysInMonth() );
+ }
+ break;
+
+ case KEY_PAGEDOWN:
+ aNewDate.AddDays( aNewDate.GetDaysInMonth() );
+ break;
+
+ case KEY_RETURN:
+ break;
+
+ default:
+ Control::KeyInput( rKEvt );
+ break;
+ }
+
+ if ( aNewDate != maCurDate )
+ {
+ SetCurDate( aNewDate );
+ Select();
+ }
+
+ if (rKEvt.GetKeyCode().GetCode() == KEY_RETURN)
+ {
+ if (maActivateHdl.IsSet())
+ maActivateHdl.Call(this);
+ else
+ Control::KeyInput(rKEvt);
+ }
+}
+
+void Calendar::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ ImplDraw(rRenderContext);
+}
+
+void Calendar::GetFocus()
+{
+ ImplUpdateDate( maCurDate );
+ Control::GetFocus();
+}
+
+void Calendar::LoseFocus()
+{
+ HideFocus();
+ Control::LoseFocus();
+}
+
+void Calendar::Resize()
+{
+ ImplUpdate( true );
+ Control::Resize();
+}
+
+void Calendar::RequestHelp( const HelpEvent& rHEvt )
+{
+ if ( rHEvt.GetMode() & (HelpEventMode::QUICK | HelpEventMode::BALLOON) )
+ {
+ Date aDate = maCurDate;
+ if ( GetDate( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ), aDate ) )
+ {
+ tools::Rectangle aDateRect = GetDateRect( aDate );
+ Point aPt = OutputToScreenPixel( aDateRect.TopLeft() );
+ aDateRect.SetLeft( aPt.X() );
+ aDateRect.SetTop( aPt.Y() );
+ aPt = OutputToScreenPixel( aDateRect.BottomRight() );
+ aDateRect.SetRight( aPt.X() );
+ aDateRect.SetBottom( aPt.Y() );
+
+ if ( rHEvt.GetMode() & HelpEventMode::QUICK )
+ {
+ maCalendarWrapper.setGregorianDateTime( aDate);
+ sal_uInt16 nWeek = static_cast<sal_uInt16>(maCalendarWrapper.getValue( i18n::CalendarFieldIndex::WEEK_OF_YEAR));
+ sal_uInt16 nMonth = aDate.GetMonth();
+ OUString aStr = maDayText
+ + ": "
+ + OUString::number(aDate.GetDayOfYear())
+ + " / "
+ + maWeekText
+ + ": "
+ + OUString::number(nWeek);
+ // if year is not the same, add it
+ if ( (nMonth == 12) && (nWeek == 1) )
+ {
+ aStr += ", " + OUString::number(aDate.GetNextYear());
+ }
+ else if ( (nMonth == 1) && (nWeek > 50) )
+ {
+ aStr += ", " + OUString::number(aDate.GetYear()-1);
+ }
+ Help::ShowQuickHelp( this, aDateRect, aStr );
+ return;
+ }
+ }
+ }
+
+ Control::RequestHelp( rHEvt );
+}
+
+void Calendar::Command( const CommandEvent& rCEvt )
+{
+ if ( rCEvt.GetCommand() == CommandEventId::ContextMenu )
+ {
+ if ( rCEvt.IsMouseEvent() )
+ {
+ Date aTempDate = maCurDate;
+ sal_uInt16 nHitTest = ImplDoHitTest( rCEvt.GetMousePosPixel(), aTempDate );
+ if ( nHitTest & CALENDAR_HITTEST_MONTHTITLE )
+ {
+ ImplShowMenu( rCEvt.GetMousePosPixel(), aTempDate );
+ return;
+ }
+ }
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::Wheel )
+ {
+ const CommandWheelData* pData = rCEvt.GetWheelData();
+ if ( pData->GetMode() == CommandWheelMode::SCROLL )
+ {
+ tools::Long nNotchDelta = pData->GetNotchDelta();
+ if ( nNotchDelta < 0 )
+ {
+ while ( nNotchDelta < 0 )
+ {
+ ImplScroll( true );
+ nNotchDelta++;
+ }
+ }
+ else
+ {
+ while ( nNotchDelta > 0 )
+ {
+ ImplScroll( false );
+ nNotchDelta--;
+ }
+ }
+
+ return;
+ }
+ }
+
+ Control::Command( rCEvt );
+}
+
+void Calendar::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ ImplFormat();
+}
+
+void Calendar::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+}
+
+void Calendar::Select()
+{
+ maSelectHdl.Call( this );
+}
+
+Date Calendar::GetFirstSelectedDate() const
+{
+ if ( !mpSelectTable->empty() )
+ return Date( *mpSelectTable->begin() );
+ else
+ {
+ Date aDate( 0, 0, 0 );
+ return aDate;
+ }
+}
+
+void Calendar::SetCurDate( const Date& rNewDate )
+{
+ if ( !rNewDate.IsValidAndGregorian() )
+ return;
+
+ if ( maCurDate == rNewDate )
+ return;
+
+ bool bUpdate = IsVisible() && IsUpdateMode();
+ Date aOldDate = maCurDate;
+ maCurDate = rNewDate;
+
+ ImplCalendarSelectDate( mpSelectTable.get(), aOldDate, false );
+ ImplCalendarSelectDate( mpSelectTable.get(), maCurDate, true );
+
+ // shift actual date in the visible area
+ if ( mbFormat || (maCurDate < GetFirstMonth()) )
+ SetFirstDate( maCurDate );
+ else if ( maCurDate > GetLastMonth() )
+ {
+ Date aTempDate = GetLastMonth();
+ tools::Long nDateOff = maCurDate-aTempDate;
+ if ( nDateOff < 365 )
+ {
+ Date aFirstDate = GetFirstMonth();
+ aFirstDate.AddDays( aFirstDate.GetDaysInMonth() );
+ ++aTempDate;
+ while ( nDateOff > aTempDate.GetDaysInMonth() )
+ {
+ aFirstDate.AddDays( aFirstDate.GetDaysInMonth() );
+ sal_Int32 nDaysInMonth = aTempDate.GetDaysInMonth();
+ aTempDate.AddDays( nDaysInMonth );
+ nDateOff -= nDaysInMonth;
+ }
+ SetFirstDate( aFirstDate );
+ }
+ else
+ SetFirstDate( maCurDate );
+ }
+ else
+ {
+ if ( bUpdate )
+ {
+ HideFocus();
+ ImplUpdateDate( aOldDate );
+ ImplUpdateDate( maCurDate );
+ }
+ }
+}
+
+void Calendar::SetFirstDate( const Date& rNewFirstDate )
+{
+ if ( maFirstDate != rNewFirstDate )
+ {
+ maFirstDate = Date( 1, rNewFirstDate.GetMonth(), rNewFirstDate.GetYear() );
+ ImplUpdate();
+ }
+}
+
+Date Calendar::GetFirstMonth() const
+{
+ if ( maFirstDate.GetDay() > 1 )
+ {
+ if ( maFirstDate.GetMonth() == 12 )
+ return Date( 1, 1, maFirstDate.GetNextYear() );
+ else
+ return Date( 1, maFirstDate.GetMonth()+1, maFirstDate.GetYear() );
+ }
+ else
+ return maFirstDate;
+}
+
+Date Calendar::GetLastMonth() const
+{
+ Date aDate = GetFirstMonth();
+ sal_uInt16 nMonthCount = GetMonthCount();
+ for ( sal_uInt16 i = 0; i < nMonthCount; i++ )
+ aDate.AddDays( aDate.GetDaysInMonth() );
+ --aDate;
+ return aDate;
+}
+
+sal_uInt16 Calendar::GetMonthCount() const
+{
+ if ( mbFormat )
+ return 1;
+ else
+ return static_cast<sal_uInt16>(mnMonthPerLine*mnLines);
+}
+
+bool Calendar::GetDate( const Point& rPos, Date& rDate ) const
+{
+ Date aDate = maCurDate;
+ sal_uInt16 nHitTest = ImplDoHitTest( rPos, aDate );
+ if ( nHitTest & CALENDAR_HITTEST_DAY )
+ {
+ rDate = aDate;
+ return true;
+ }
+ else
+ return false;
+}
+
+tools::Rectangle Calendar::GetDateRect( const Date& rDate ) const
+{
+ tools::Rectangle aRect;
+
+ if ( mbFormat || (rDate < maFirstDate) || (rDate > (maFirstDate+mnDayCount)) )
+ return aRect;
+
+ tools::Long nX;
+ tools::Long nY;
+ sal_Int32 nDaysOff;
+ sal_uInt16 nDayIndex;
+ Date aDate = GetFirstMonth();
+
+ if ( rDate < aDate )
+ {
+ aRect = GetDateRect( aDate );
+ nDaysOff = aDate-rDate;
+ nX = nDaysOff*mnDayWidth;
+ aRect.AdjustLeft( -nX );
+ aRect.AdjustRight( -nX );
+ return aRect;
+ }
+ else
+ {
+ Date aLastDate = GetLastMonth();
+ if ( rDate > aLastDate )
+ {
+ sal_Int32 nWeekDay = static_cast<sal_Int32>(aLastDate.GetDayOfWeek());
+ nWeekDay = (nWeekDay+(7-ImplGetWeekStart())) % 7;
+ aLastDate.AddDays( -nWeekDay );
+ aRect = GetDateRect( aLastDate );
+ nDaysOff = rDate-aLastDate;
+ nDayIndex = 0;
+ for ( sal_Int32 i = 0; i <= nDaysOff; i++ )
+ {
+ if ( aLastDate == rDate )
+ {
+ aRect.AdjustLeft(nDayIndex*mnDayWidth );
+ aRect.SetRight( aRect.Left()+mnDayWidth );
+ return aRect;
+ }
+ if ( nDayIndex == 6 )
+ {
+ nDayIndex = 0;
+ aRect.AdjustTop(mnDayHeight );
+ aRect.AdjustBottom(mnDayHeight );
+ }
+ else
+ nDayIndex++;
+ ++aLastDate;
+ }
+ }
+ }
+
+ nY = 0;
+ for ( tools::Long i = 0; i < mnLines; i++ )
+ {
+ nX = 0;
+ for ( tools::Long j = 0; j < mnMonthPerLine; j++ )
+ {
+ sal_uInt16 nDaysInMonth = aDate.GetDaysInMonth();
+
+ // month is called
+ if ( (aDate.GetMonth() == rDate.GetMonth()) &&
+ (aDate.GetYear() == rDate.GetYear()) )
+ {
+ tools::Long nDayX = nX+mnDaysOffX;
+ tools::Long nDayY = nY+mnDaysOffY;
+ nDayIndex = static_cast<sal_uInt16>(aDate.GetDayOfWeek());
+ nDayIndex = (nDayIndex+(7-static_cast<sal_uInt16>(ImplGetWeekStart()))) % 7;
+ for ( sal_uInt16 nDay = 1; nDay <= nDaysInMonth; nDay++ )
+ {
+ if ( nDay == rDate.GetDay() )
+ {
+ aRect.SetLeft( nDayX + (nDayIndex*mnDayWidth) );
+ aRect.SetTop( nDayY );
+ aRect.SetRight( aRect.Left()+mnDayWidth );
+ aRect.SetBottom( aRect.Top()+mnDayHeight );
+ break;
+ }
+ if ( nDayIndex == 6 )
+ {
+ nDayIndex = 0;
+ nDayY += mnDayHeight;
+ }
+ else
+ nDayIndex++;
+ }
+ }
+
+ aDate.AddDays( nDaysInMonth );
+ nX += mnMonthWidth;
+ }
+
+ nY += mnMonthHeight;
+ }
+
+ return aRect;
+}
+
+void Calendar::EndSelection()
+{
+ if ( mbDrag || mbSpinDown )
+ {
+ ReleaseMouse();
+
+ mbDrag = false;
+ mbSpinDown = false;
+ mbPrevIn = false;
+ mbNextIn = false;
+ }
+}
+
+Size Calendar::CalcWindowSizePixel() const
+{
+ Size aSize;
+ tools::Long n99TextWidth = GetTextWidth( "99" );
+ tools::Long nTextHeight = GetTextHeight();
+
+ aSize.AdjustWidth((n99TextWidth+DAY_OFFX)*7);
+ aSize.AdjustWidth(MONTH_BORDERX*2 );
+
+ aSize.setHeight( nTextHeight + TITLE_OFFY + (TITLE_BORDERY*2) );
+ aSize.AdjustHeight(nTextHeight + WEEKDAY_OFFY );
+ aSize.AdjustHeight((nTextHeight+DAY_OFFY)*6);
+ aSize.AdjustHeight(MONTH_OFFY );
+
+ return aSize;
+}
+
+Size Calendar::GetOptimalSize() const
+{
+ return CalcWindowSizePixel();
+}
+
+namespace
+{
+ class ImplCFieldFloat final
+ {
+ private:
+ std::unique_ptr<weld::Builder> mxBuilder;
+ std::unique_ptr<weld::Container> mxContainer;
+ std::unique_ptr<weld::Calendar> mxCalendar;
+ std::unique_ptr<weld::Button> mxTodayBtn;
+ std::unique_ptr<weld::Button> mxNoneBtn;
+
+ public:
+ ImplCFieldFloat(vcl::Window* pContainer)
+ : mxBuilder(Application::CreateInterimBuilder(pContainer, "svt/ui/calendar.ui", false))
+ , mxContainer(mxBuilder->weld_container("Calendar"))
+ , mxCalendar(mxBuilder->weld_calendar("date"))
+ , mxTodayBtn(mxBuilder->weld_button("today"))
+ , mxNoneBtn(mxBuilder->weld_button("none"))
+ {
+ }
+
+ weld::Calendar* GetCalendar() { return mxCalendar.get(); }
+ weld::Button* EnableTodayBtn(bool bEnable);
+ weld::Button* EnableNoneBtn(bool bEnable);
+
+ void GrabFocus()
+ {
+ mxCalendar->grab_focus();
+ }
+ };
+}
+
+struct ImplCFieldFloatWin : public DropdownDockingWindow
+{
+ explicit ImplCFieldFloatWin(vcl::Window* pParent);
+ virtual void dispose() override;
+ virtual ~ImplCFieldFloatWin() override;
+ virtual void GetFocus() override;
+
+ std::unique_ptr<ImplCFieldFloat> mxWidget;
+};
+
+ImplCFieldFloatWin::ImplCFieldFloatWin(vcl::Window* pParent)
+ : DropdownDockingWindow(pParent)
+{
+ setDeferredProperties();
+ mxWidget.reset(new ImplCFieldFloat(m_xBox.get()));
+}
+
+ImplCFieldFloatWin::~ImplCFieldFloatWin()
+{
+ disposeOnce();
+}
+
+void ImplCFieldFloatWin::dispose()
+{
+ mxWidget.reset();
+ DropdownDockingWindow::dispose();
+}
+
+void ImplCFieldFloatWin::GetFocus()
+{
+ DropdownDockingWindow::GetFocus();
+ if (!mxWidget)
+ return;
+ mxWidget->GrabFocus();
+}
+
+weld::Button* ImplCFieldFloat::EnableTodayBtn(bool bEnable)
+{
+ mxTodayBtn->set_visible(bEnable);
+ return bEnable ? mxTodayBtn.get() : nullptr;
+}
+
+weld::Button* ImplCFieldFloat::EnableNoneBtn(bool bEnable)
+{
+ mxNoneBtn->set_visible(bEnable);
+ return bEnable ? mxNoneBtn.get() : nullptr;
+}
+
+CalendarField::CalendarField(vcl::Window* pParent, WinBits nWinStyle)
+ : DateField(pParent, nWinStyle)
+ , mpFloatWin(nullptr)
+ , mpTodayBtn(nullptr)
+ , mpNoneBtn(nullptr)
+ , mbToday(false)
+ , mbNone(false)
+{
+}
+
+CalendarField::~CalendarField()
+{
+ disposeOnce();
+}
+
+void CalendarField::dispose()
+{
+ mpTodayBtn = nullptr;
+ mpNoneBtn = nullptr;
+ mpFloatWin.disposeAndClear();
+ DateField::dispose();
+}
+
+IMPL_LINK(CalendarField, ImplSelectHdl, weld::Calendar&, rCalendar, void)
+{
+ Date aNewDate = rCalendar.get_date();
+
+ vcl::Window::GetDockingManager()->EndPopupMode(mpFloatWin);
+ mpFloatWin->EnableDocking(false);
+ EndDropDown();
+ GrabFocus();
+ if ( IsEmptyDate() || ( aNewDate != GetDate() ) )
+ {
+ SetDate( aNewDate );
+ SetModifyFlag();
+ Modify();
+ }
+}
+
+IMPL_LINK(CalendarField, ImplClickHdl, weld::Button&, rBtn, void)
+{
+ vcl::Window::GetDockingManager()->EndPopupMode(mpFloatWin);
+ mpFloatWin->EnableDocking(false);
+ EndDropDown();
+ GrabFocus();
+
+ if (&rBtn == mpTodayBtn)
+ {
+ Date aToday( Date::SYSTEM );
+ if ( (aToday != GetDate()) || IsEmptyDate() )
+ {
+ SetDate( aToday );
+ SetModifyFlag();
+ Modify();
+ }
+ }
+ else if (&rBtn == mpNoneBtn)
+ {
+ if ( !IsEmptyDate() )
+ {
+ SetEmptyDate();
+ SetModifyFlag();
+ Modify();
+ }
+ }
+}
+
+IMPL_LINK_NOARG(CalendarField, ImplPopupModeEndHdl, FloatingWindow*, void)
+{
+ EndDropDown();
+ GrabFocus();
+}
+
+bool CalendarField::ShowDropDown( bool bShow )
+{
+ if ( bShow )
+ {
+ if ( !mpFloatWin )
+ mpFloatWin = VclPtr<ImplCFieldFloatWin>::Create( this );
+
+ Date aDate = GetDate();
+ if ( IsEmptyDate() || !aDate.IsValidAndGregorian() )
+ {
+ aDate = Date( Date::SYSTEM );
+ }
+ weld::Calendar* pCalendar = mpFloatWin->mxWidget->GetCalendar();
+ pCalendar->set_date( aDate );
+ pCalendar->connect_activated(LINK(this, CalendarField, ImplSelectHdl));
+ mpTodayBtn = mpFloatWin->mxWidget->EnableTodayBtn(mbToday);
+ mpNoneBtn = mpFloatWin->mxWidget->EnableNoneBtn(mbNone);
+ if (mpTodayBtn)
+ mpTodayBtn->connect_clicked( LINK( this, CalendarField, ImplClickHdl ) );
+ if (mpNoneBtn)
+ mpNoneBtn->connect_clicked( LINK( this, CalendarField, ImplClickHdl ) );
+ Point aPos(GetParent()->OutputToScreenPixel(GetPosPixel()));
+ tools::Rectangle aRect(aPos, GetSizePixel());
+ aRect.AdjustBottom( -1 );
+ DockingManager* pDockingManager = vcl::Window::GetDockingManager();
+ mpFloatWin->EnableDocking(true);
+ pDockingManager->SetPopupModeEndHdl(mpFloatWin, LINK(this, CalendarField, ImplPopupModeEndHdl));
+ pDockingManager->StartPopupMode(mpFloatWin, aRect, FloatWinPopupFlags::Down | FloatWinPopupFlags::GrabFocus);
+ }
+ else
+ {
+ vcl::Window::GetDockingManager()->EndPopupMode(mpFloatWin);
+ mpFloatWin->EnableDocking(false);
+ EndDropDown();
+ }
+ return true;
+}
+
+void CalendarField::StateChanged( StateChangedType nStateChange )
+{
+ DateField::StateChanged( nStateChange );
+
+ if ( ( nStateChange == StateChangedType::Style ) && GetSubEdit() )
+ {
+ WinBits nAllAlignmentBits = ( WB_LEFT | WB_CENTER | WB_RIGHT | WB_TOP | WB_VCENTER | WB_BOTTOM );
+ WinBits nMyAlignment = GetStyle() & nAllAlignmentBits;
+ GetSubEdit()->SetStyle( ( GetSubEdit()->GetStyle() & ~nAllAlignmentBits ) | nMyAlignment );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/combobox.cxx b/vcl/source/control/combobox.cxx
new file mode 100644
index 000000000..15e088d7f
--- /dev/null
+++ b/vcl/source/control/combobox.cxx
@@ -0,0 +1,1572 @@
+/* -*- 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 <vcl/toolkit/combobox.hxx>
+
+#include <set>
+
+#include <comphelper/string.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <sal/log.hxx>
+
+#include <listbox.hxx>
+#include <comphelper/lok.hxx>
+#include <tools/json_writer.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace {
+
+struct ComboBoxBounds
+{
+ Point aSubEditPos;
+ Size aSubEditSize;
+
+ Point aButtonPos;
+ Size aButtonSize;
+};
+
+}
+
+struct ComboBox::Impl
+{
+ ComboBox & m_rThis;
+ VclPtr<Edit> m_pSubEdit;
+ VclPtr<ImplListBox> m_pImplLB;
+ VclPtr<ImplBtn> m_pBtn;
+ VclPtr<ImplListBoxFloatingWindow> m_pFloatWin;
+ sal_uInt16 m_nDDHeight;
+ sal_Unicode m_cMultiSep;
+ bool m_isDDAutoSize : 1;
+ bool m_isSyntheticModify : 1;
+ bool m_isKeyBoardModify : 1;
+ bool m_isMatchCase : 1;
+ sal_Int32 m_nMaxWidthChars;
+ sal_Int32 m_nWidthInChars;
+ Link<ComboBox&,void> m_SelectHdl;
+
+ explicit Impl(ComboBox & rThis)
+ : m_rThis(rThis)
+ , m_nDDHeight(0)
+ , m_cMultiSep(0)
+ , m_isDDAutoSize(false)
+ , m_isSyntheticModify(false)
+ , m_isKeyBoardModify(false)
+ , m_isMatchCase(false)
+ , m_nMaxWidthChars(0)
+ , m_nWidthInChars(-1)
+ {
+ }
+
+ void ImplInitComboBoxData();
+ void ImplUpdateFloatSelection();
+ ComboBoxBounds calcComboBoxDropDownComponentBounds(
+ const Size &rOutSize, const Size &rBorderOutSize) const;
+
+ DECL_LINK( ImplSelectHdl, LinkParamNone*, void );
+ DECL_LINK( ImplCancelHdl, LinkParamNone*, void );
+ DECL_LINK( ImplDoubleClickHdl, ImplListBoxWindow*, void );
+ DECL_LINK( ImplClickBtnHdl, void*, void );
+ DECL_LINK( ImplPopupModeEndHdl, FloatingWindow*, void );
+ DECL_LINK( ImplSelectionChangedHdl, sal_Int32, void );
+ DECL_LINK( ImplAutocompleteHdl, Edit&, void );
+ DECL_LINK( ImplListItemSelectHdl , LinkParamNone*, void );
+};
+
+
+static void lcl_GetSelectedEntries( ::std::set< sal_Int32 >& rSelectedPos, std::u16string_view rText, sal_Unicode cTokenSep, const ImplEntryList& rEntryList )
+{
+ if (rText.empty())
+ return;
+
+ sal_Int32 nIdx{0};
+ do {
+ const sal_Int32 nPos = rEntryList.FindEntry(comphelper::string::strip(o3tl::getToken(rText, 0, cTokenSep, nIdx), ' '));
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ rSelectedPos.insert( nPos );
+ } while (nIdx>=0);
+}
+
+ComboBox::ComboBox(vcl::Window *const pParent, WinBits const nStyle)
+ : Edit( WindowType::COMBOBOX )
+ , m_pImpl(new Impl(*this))
+{
+ m_pImpl->ImplInitComboBoxData();
+ ImplInit( pParent, nStyle );
+ SetWidthInChars(-1);
+}
+
+ComboBox::~ComboBox()
+{
+ disposeOnce();
+}
+
+void ComboBox::dispose()
+{
+ m_pImpl->m_pSubEdit.disposeAndClear();
+
+ VclPtr< ImplListBox > pImplLB = m_pImpl->m_pImplLB;
+ m_pImpl->m_pImplLB.clear();
+ pImplLB.disposeAndClear();
+
+ m_pImpl->m_pFloatWin.disposeAndClear();
+ m_pImpl->m_pBtn.disposeAndClear();
+ Edit::dispose();
+}
+
+void ComboBox::Impl::ImplInitComboBoxData()
+{
+ m_pSubEdit.disposeAndClear();
+ m_pBtn = nullptr;
+ m_pImplLB = nullptr;
+ m_pFloatWin = nullptr;
+
+ m_nDDHeight = 0;
+ m_isDDAutoSize = true;
+ m_isSyntheticModify = false;
+ m_isKeyBoardModify = false;
+ m_isMatchCase = false;
+ m_cMultiSep = ';';
+ m_nMaxWidthChars = -1;
+ m_nWidthInChars = -1;
+}
+
+void ComboBox::ImplCalcEditHeight()
+{
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ GetBorder( nLeft, nTop, nRight, nBottom );
+ m_pImpl->m_nDDHeight = static_cast<sal_uInt16>(m_pImpl->m_pSubEdit->GetTextHeight() + nTop + nBottom + 4);
+ if ( !IsDropDownBox() )
+ m_pImpl->m_nDDHeight += 4;
+
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), Size( 10, 10 ) );
+ tools::Rectangle aBoundRegion, aContentRegion;
+ ImplControlValue aControlValue;
+ ControlType aType = IsDropDownBox() ? ControlType::Combobox : ControlType::Editbox;
+ if( GetNativeControlRegion( aType, ControlPart::Entire,
+ aCtrlRegion,
+ ControlState::ENABLED,
+ aControlValue,
+ aBoundRegion, aContentRegion ) )
+ {
+ const tools::Long nNCHeight = aBoundRegion.GetHeight();
+ if (m_pImpl->m_nDDHeight < nNCHeight)
+ m_pImpl->m_nDDHeight = sal::static_int_cast<sal_uInt16>(nNCHeight);
+ }
+}
+
+void ComboBox::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ bool bNoBorder = ( nStyle & WB_NOBORDER ) != 0;
+ if ( !(nStyle & WB_DROPDOWN) )
+ {
+ nStyle &= ~WB_BORDER;
+ nStyle |= WB_NOBORDER;
+ }
+ else
+ {
+ if ( !bNoBorder )
+ nStyle |= WB_BORDER;
+ }
+
+ Edit::ImplInit( pParent, nStyle );
+ SetBackground();
+
+ // DropDown ?
+ WinBits nEditStyle = nStyle & ( WB_LEFT | WB_RIGHT | WB_CENTER );
+ WinBits nListStyle = nStyle;
+ if( nStyle & WB_DROPDOWN )
+ {
+ m_pImpl->m_pFloatWin = VclPtr<ImplListBoxFloatingWindow>::Create( this );
+ if (!IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Focus))
+ m_pImpl->m_pFloatWin->RequestDoubleBuffering(true);
+ m_pImpl->m_pFloatWin->SetAutoWidth( true );
+ m_pImpl->m_pFloatWin->SetPopupModeEndHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplPopupModeEndHdl) );
+
+ m_pImpl->m_pBtn = VclPtr<ImplBtn>::Create( this, WB_NOLIGHTBORDER | WB_RECTSTYLE );
+ ImplInitDropDownButton( m_pImpl->m_pBtn );
+ m_pImpl->m_pBtn->SetMBDownHdl( LINK( m_pImpl.get(), ComboBox::Impl, ImplClickBtnHdl ) );
+ m_pImpl->m_pBtn->Show();
+
+ nEditStyle |= WB_NOBORDER;
+ nListStyle &= ~WB_BORDER;
+ nListStyle |= WB_NOBORDER;
+ }
+ else
+ {
+ if ( !bNoBorder )
+ {
+ nEditStyle |= WB_BORDER;
+ nListStyle &= ~WB_NOBORDER;
+ nListStyle |= WB_BORDER;
+ }
+ }
+
+ m_pImpl->m_pSubEdit.set( VclPtr<Edit>::Create( this, nEditStyle ) );
+ m_pImpl->m_pSubEdit->EnableRTL( false );
+ SetSubEdit( m_pImpl->m_pSubEdit );
+ m_pImpl->m_pSubEdit->SetPosPixel( Point() );
+ EnableAutocomplete( true );
+ m_pImpl->m_pSubEdit->Show();
+
+ vcl::Window* pLBParent = this;
+ if (m_pImpl->m_pFloatWin)
+ pLBParent = m_pImpl->m_pFloatWin;
+ m_pImpl->m_pImplLB = VclPtr<ImplListBox>::Create( pLBParent, nListStyle|WB_SIMPLEMODE|WB_AUTOHSCROLL );
+ m_pImpl->m_pImplLB->SetPosPixel( Point() );
+ m_pImpl->m_pImplLB->SetSelectHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplSelectHdl) );
+ m_pImpl->m_pImplLB->SetCancelHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplCancelHdl) );
+ m_pImpl->m_pImplLB->SetDoubleClickHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplDoubleClickHdl) );
+ m_pImpl->m_pImplLB->SetSelectionChangedHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplSelectionChangedHdl) );
+ m_pImpl->m_pImplLB->SetListItemSelectHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplListItemSelectHdl) );
+ m_pImpl->m_pImplLB->Show();
+
+ if (m_pImpl->m_pFloatWin)
+ m_pImpl->m_pFloatWin->SetImplListBox( m_pImpl->m_pImplLB );
+ else
+ GetMainWindow()->AllowGrabFocus( true );
+
+ ImplCalcEditHeight();
+
+ SetCompoundControl( true );
+}
+
+WinBits ComboBox::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+void ComboBox::EnableAutocomplete( bool bEnable, bool bMatchCase )
+{
+ m_pImpl->m_isMatchCase = bMatchCase;
+
+ if ( bEnable )
+ m_pImpl->m_pSubEdit->SetAutocompleteHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplAutocompleteHdl) );
+ else
+ m_pImpl->m_pSubEdit->SetAutocompleteHdl( Link<Edit&,void>() );
+}
+
+bool ComboBox::IsAutocompleteEnabled() const
+{
+ return m_pImpl->m_pSubEdit->GetAutocompleteHdl().IsSet();
+}
+
+IMPL_LINK_NOARG(ComboBox::Impl, ImplClickBtnHdl, void*, void)
+{
+ m_rThis.CallEventListeners( VclEventId::DropdownPreOpen );
+ m_pSubEdit->GrabFocus();
+ if (!m_pImplLB->GetEntryList().GetMRUCount())
+ ImplUpdateFloatSelection();
+ else
+ m_pImplLB->SelectEntry( 0 , true );
+ m_pBtn->SetPressed( true );
+ m_rThis.SetSelection( Selection( 0, SELECTION_MAX ) );
+ m_pFloatWin->StartFloat( true );
+ m_rThis.CallEventListeners( VclEventId::DropdownOpen );
+
+ m_rThis.ImplClearLayoutData();
+ if (m_pImplLB)
+ m_pImplLB->GetMainWindow()->ImplClearLayoutData();
+}
+
+IMPL_LINK_NOARG(ComboBox::Impl, ImplPopupModeEndHdl, FloatingWindow*, void)
+{
+ if (m_pFloatWin->IsPopupModeCanceled())
+ {
+ if (!m_pImplLB->GetEntryList().IsEntryPosSelected(
+ m_pFloatWin->GetPopupModeStartSaveSelection()))
+ {
+ m_pImplLB->SelectEntry(m_pFloatWin->GetPopupModeStartSaveSelection(), true);
+ bool bTravelSelect = m_pImplLB->IsTravelSelect();
+ m_pImplLB->SetTravelSelect( true );
+ m_rThis.Select();
+ m_pImplLB->SetTravelSelect( bTravelSelect );
+ }
+ }
+
+ m_rThis.ImplClearLayoutData();
+ if (m_pImplLB)
+ m_pImplLB->GetMainWindow()->ImplClearLayoutData();
+
+ m_pBtn->SetPressed( false );
+ m_rThis.CallEventListeners( VclEventId::DropdownClose );
+}
+
+IMPL_LINK(ComboBox::Impl, ImplAutocompleteHdl, Edit&, rEdit, void)
+{
+ Selection aSel = rEdit.GetSelection();
+
+ {
+ OUString aFullText = rEdit.GetText();
+ OUString aStartText = aFullText.copy( 0, static_cast<sal_Int32>(aSel.Max()) );
+ sal_Int32 nStart = m_pImplLB->GetCurrentPos();
+
+ if ( nStart == LISTBOX_ENTRY_NOTFOUND )
+ nStart = 0;
+
+ sal_Int32 nPos = LISTBOX_ENTRY_NOTFOUND;
+ if (!m_isMatchCase)
+ {
+ // Try match case insensitive from current position
+ nPos = m_pImplLB->GetEntryList().FindMatchingEntry(aStartText, nStart, true);
+ if ( nPos == LISTBOX_ENTRY_NOTFOUND )
+ // Try match case insensitive, but from start
+ nPos = m_pImplLB->GetEntryList().FindMatchingEntry(aStartText, 0, true);
+ }
+
+ if ( nPos == LISTBOX_ENTRY_NOTFOUND )
+ // Try match full from current position
+ nPos = m_pImplLB->GetEntryList().FindMatchingEntry(aStartText, nStart, false);
+ if ( nPos == LISTBOX_ENTRY_NOTFOUND )
+ // Match full, but from start
+ nPos = m_pImplLB->GetEntryList().FindMatchingEntry(aStartText, 0, false);
+
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ OUString aText = m_pImplLB->GetEntryList().GetEntryText( nPos );
+ Selection aSelection( aText.getLength(), aStartText.getLength() );
+ rEdit.SetText( aText, aSelection );
+ }
+ }
+}
+
+IMPL_LINK_NOARG(ComboBox::Impl, ImplSelectHdl, LinkParamNone*, void)
+{
+ bool bPopup = m_rThis.IsInDropDown();
+ bool bCallSelect = false;
+ if (m_pImplLB->IsSelectionChanged() || bPopup)
+ {
+ OUString aText;
+ if (m_rThis.IsMultiSelectionEnabled())
+ {
+ aText = m_pSubEdit->GetText();
+
+ // remove all entries to which there is an entry, but which is not selected
+ sal_Int32 nIndex = 0;
+ while ( nIndex >= 0 )
+ {
+ sal_Int32 nPrevIndex = nIndex;
+ std::u16string_view aToken = o3tl::getToken(aText, 0, m_cMultiSep, nIndex );
+ sal_Int32 nTokenLen = aToken.size();
+ aToken = comphelper::string::strip(aToken, ' ');
+ sal_Int32 nP = m_pImplLB->GetEntryList().FindEntry( aToken );
+ if ((nP != LISTBOX_ENTRY_NOTFOUND) && (!m_pImplLB->GetEntryList().IsEntryPosSelected(nP)))
+ {
+ aText = aText.replaceAt( nPrevIndex, nTokenLen, u"" );
+ nIndex = nIndex - nTokenLen;
+ sal_Int32 nSepCount=0;
+ if ((nPrevIndex+nSepCount < aText.getLength()) && (aText[nPrevIndex+nSepCount] == m_cMultiSep))
+ {
+ nIndex--;
+ ++nSepCount;
+ }
+ aText = aText.replaceAt( nPrevIndex, nSepCount, u"" );
+ }
+ aText = comphelper::string::strip(aText, ' ');
+ }
+
+ // attach missing entries
+ ::std::set< sal_Int32 > aSelInText;
+ lcl_GetSelectedEntries( aSelInText, aText, m_cMultiSep, m_pImplLB->GetEntryList() );
+ sal_Int32 nSelectedEntries = m_pImplLB->GetEntryList().GetSelectedEntryCount();
+ for ( sal_Int32 n = 0; n < nSelectedEntries; n++ )
+ {
+ sal_Int32 nP = m_pImplLB->GetEntryList().GetSelectedEntryPos( n );
+ if ( !aSelInText.count( nP ) )
+ {
+ if (!aText.isEmpty() && (aText[aText.getLength()-1] != m_cMultiSep))
+ aText += OUStringChar(m_cMultiSep);
+ if ( !aText.isEmpty() )
+ aText += " "; // slightly loosen
+ aText += m_pImplLB->GetEntryList().GetEntryText( nP ) +
+ OUStringChar(m_cMultiSep);
+ }
+ }
+ aText = comphelper::string::stripEnd( aText, m_cMultiSep );
+ }
+ else
+ {
+ aText = m_pImplLB->GetEntryList().GetSelectedEntry( 0 );
+ }
+
+ m_pSubEdit->SetText( aText );
+
+ Selection aNewSelection( 0, aText.getLength() );
+ if (m_rThis.IsMultiSelectionEnabled())
+ aNewSelection.Min() = aText.getLength();
+ m_pSubEdit->SetSelection( aNewSelection );
+
+ bCallSelect = true;
+ }
+
+ // #84652# Call GrabFocus and EndPopupMode before calling Select/Modify, but after changing the text
+ bool bMenuSelect = bPopup && !m_pImplLB->IsTravelSelect() && (!m_rThis.IsMultiSelectionEnabled() || !m_pImplLB->GetSelectModifier());
+ if (bMenuSelect)
+ {
+ m_pFloatWin->EndPopupMode();
+ m_rThis.GrabFocus();
+ }
+
+ if ( bCallSelect )
+ {
+ m_isKeyBoardModify = !bMenuSelect;
+ m_pSubEdit->SetModifyFlag();
+ m_isSyntheticModify = true;
+ m_rThis.Modify();
+ m_isSyntheticModify = false;
+ m_rThis.Select();
+ m_isKeyBoardModify = false;
+ }
+}
+
+bool ComboBox::IsSyntheticModify() const
+{
+ return m_pImpl->m_isSyntheticModify;
+}
+
+bool ComboBox::IsModifyByKeyboard() const
+{
+ return m_pImpl->m_isKeyBoardModify;
+}
+
+IMPL_LINK_NOARG( ComboBox::Impl, ImplListItemSelectHdl, LinkParamNone*, void )
+{
+ m_rThis.CallEventListeners( VclEventId::DropdownSelect );
+}
+
+IMPL_LINK_NOARG(ComboBox::Impl, ImplCancelHdl, LinkParamNone*, void)
+{
+ if (m_rThis.IsInDropDown())
+ m_pFloatWin->EndPopupMode();
+}
+
+IMPL_LINK( ComboBox::Impl, ImplSelectionChangedHdl, sal_Int32, nChanged, void )
+{
+ if (!m_pImplLB->IsTrackingSelect())
+ {
+ if (!m_pSubEdit->IsReadOnly() && m_pImplLB->GetEntryList().IsEntryPosSelected(nChanged))
+ m_pSubEdit->SetText(m_pImplLB->GetEntryList().GetEntryText(nChanged));
+ }
+}
+
+IMPL_LINK_NOARG(ComboBox::Impl, ImplDoubleClickHdl, ImplListBoxWindow*, void)
+{
+ m_rThis.DoubleClick();
+}
+
+void ComboBox::ToggleDropDown()
+{
+ if( !IsDropDownBox() )
+ return;
+
+ if (m_pImpl->m_pFloatWin->IsInPopupMode())
+ m_pImpl->m_pFloatWin->EndPopupMode();
+ else
+ {
+ m_pImpl->m_pSubEdit->GrabFocus();
+ if (!m_pImpl->m_pImplLB->GetEntryList().GetMRUCount())
+ m_pImpl->ImplUpdateFloatSelection();
+ else
+ m_pImpl->m_pImplLB->SelectEntry( 0 , true );
+ CallEventListeners( VclEventId::DropdownPreOpen );
+ m_pImpl->m_pBtn->SetPressed( true );
+ SetSelection( Selection( 0, SELECTION_MAX ) );
+ m_pImpl->m_pFloatWin->StartFloat( true );
+ CallEventListeners( VclEventId::DropdownOpen );
+ }
+}
+
+void ComboBox::Select()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ComboboxSelect, [this] () { m_pImpl->m_SelectHdl.Call(*this); } );
+}
+
+void ComboBox::DoubleClick()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ComboboxDoubleClick, [] () {} );
+}
+
+bool ComboBox::IsAutoSizeEnabled() const { return m_pImpl->m_isDDAutoSize; }
+
+void ComboBox::EnableAutoSize( bool bAuto )
+{
+ m_pImpl->m_isDDAutoSize = bAuto;
+ if (m_pImpl->m_pFloatWin)
+ {
+ if (bAuto && !m_pImpl->m_pFloatWin->GetDropDownLineCount())
+ {
+ // Adapt to GetListBoxMaximumLineCount here; was on fixed number of five before
+ AdaptDropDownLineCountToMaximum();
+ }
+ else if ( !bAuto )
+ {
+ m_pImpl->m_pFloatWin->SetDropDownLineCount( 0 );
+ }
+ }
+}
+
+void ComboBox::SetDropDownLineCount( sal_uInt16 nLines )
+{
+ if (m_pImpl->m_pFloatWin)
+ m_pImpl->m_pFloatWin->SetDropDownLineCount( nLines );
+}
+
+void ComboBox::AdaptDropDownLineCountToMaximum()
+{
+ // Adapt to maximum allowed number.
+ // Limit for LOK as we can't render outside of the dialog canvas.
+ if (comphelper::LibreOfficeKit::isActive())
+ SetDropDownLineCount(11);
+ else
+ SetDropDownLineCount(GetSettings().GetStyleSettings().GetListBoxMaximumLineCount());
+}
+
+sal_uInt16 ComboBox::GetDropDownLineCount() const
+{
+ sal_uInt16 nLines = 0;
+ if (m_pImpl->m_pFloatWin)
+ nLines = m_pImpl->m_pFloatWin->GetDropDownLineCount();
+ return nLines;
+}
+
+void ComboBox::setPosSizePixel( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ PosSizeFlags nFlags )
+{
+ if( IsDropDownBox() && ( nFlags & PosSizeFlags::Size ) )
+ {
+ Size aPrefSz = m_pImpl->m_pFloatWin->GetPrefSize();
+ if ((nFlags & PosSizeFlags::Height) && (nHeight >= 2*m_pImpl->m_nDDHeight))
+ aPrefSz.setHeight( nHeight-m_pImpl->m_nDDHeight );
+ if ( nFlags & PosSizeFlags::Width )
+ aPrefSz.setWidth( nWidth );
+ m_pImpl->m_pFloatWin->SetPrefSize( aPrefSz );
+
+ if (IsAutoSizeEnabled())
+ nHeight = m_pImpl->m_nDDHeight;
+ }
+
+ Edit::setPosSizePixel( nX, nY, nWidth, nHeight, nFlags );
+}
+
+void ComboBox::Resize()
+{
+ Control::Resize();
+
+ if (m_pImpl->m_pSubEdit)
+ {
+ Size aOutSz = GetOutputSizePixel();
+ if( IsDropDownBox() )
+ {
+ ComboBoxBounds aBounds(m_pImpl->calcComboBoxDropDownComponentBounds(aOutSz,
+ GetWindow(GetWindowType::Border)->GetOutputSizePixel()));
+ m_pImpl->m_pSubEdit->SetPosSizePixel(aBounds.aSubEditPos, aBounds.aSubEditSize);
+ m_pImpl->m_pBtn->SetPosSizePixel(aBounds.aButtonPos, aBounds.aButtonSize);
+ }
+ else
+ {
+ m_pImpl->m_pSubEdit->SetSizePixel(Size(aOutSz.Width(), m_pImpl->m_nDDHeight));
+ m_pImpl->m_pImplLB->setPosSizePixel(0, m_pImpl->m_nDDHeight,
+ aOutSz.Width(), aOutSz.Height() - m_pImpl->m_nDDHeight);
+ if ( !GetText().isEmpty() )
+ m_pImpl->ImplUpdateFloatSelection();
+ }
+ }
+
+ // adjust the size of the FloatingWindow even when invisible
+ // as KEY_PGUP/DOWN is being processed...
+ if (m_pImpl->m_pFloatWin)
+ m_pImpl->m_pFloatWin->SetSizePixel(m_pImpl->m_pFloatWin->CalcFloatSize());
+}
+
+bool ComboBox::IsDropDownBox() const { return m_pImpl->m_pFloatWin != nullptr; }
+
+void ComboBox::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ AppendLayoutData( *m_pImpl->m_pSubEdit );
+ m_pImpl->m_pSubEdit->SetLayoutDataParent( this );
+ ImplListBoxWindow* rMainWindow = GetMainWindow();
+ if (m_pImpl->m_pFloatWin)
+ {
+ // dropdown mode
+ if (m_pImpl->m_pFloatWin->IsReallyVisible())
+ {
+ AppendLayoutData( *rMainWindow );
+ rMainWindow->SetLayoutDataParent( this );
+ }
+ }
+ else
+ {
+ AppendLayoutData( *rMainWindow );
+ rMainWindow->SetLayoutDataParent( this );
+ }
+}
+
+void ComboBox::StateChanged( StateChangedType nType )
+{
+ Edit::StateChanged( nType );
+
+ if ( nType == StateChangedType::ReadOnly )
+ {
+ m_pImpl->m_pImplLB->SetReadOnly( IsReadOnly() );
+ if (m_pImpl->m_pBtn)
+ m_pImpl->m_pBtn->Enable( IsEnabled() && !IsReadOnly() );
+ }
+ else if ( nType == StateChangedType::Enable )
+ {
+ m_pImpl->m_pSubEdit->Enable( IsEnabled() );
+ m_pImpl->m_pImplLB->Enable( IsEnabled() && !IsReadOnly() );
+ if (m_pImpl->m_pBtn)
+ m_pImpl->m_pBtn->Enable( IsEnabled() && !IsReadOnly() );
+ Invalidate();
+ }
+ else if( nType == StateChangedType::UpdateMode )
+ {
+ m_pImpl->m_pImplLB->SetUpdateMode( IsUpdateMode() );
+ }
+ else if ( nType == StateChangedType::Zoom )
+ {
+ m_pImpl->m_pImplLB->SetZoom( GetZoom() );
+ m_pImpl->m_pSubEdit->SetZoom( GetZoom() );
+ ImplCalcEditHeight();
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlFont )
+ {
+ m_pImpl->m_pImplLB->SetControlFont( GetControlFont() );
+ m_pImpl->m_pSubEdit->SetControlFont( GetControlFont() );
+ ImplCalcEditHeight();
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ m_pImpl->m_pImplLB->SetControlForeground( GetControlForeground() );
+ m_pImpl->m_pSubEdit->SetControlForeground( GetControlForeground() );
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ m_pImpl->m_pImplLB->SetControlBackground( GetControlBackground() );
+ m_pImpl->m_pSubEdit->SetControlBackground( GetControlBackground() );
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ GetMainWindow()->EnableSort( ( GetStyle() & WB_SORT ) != 0 );
+ }
+ else if( nType == StateChangedType::Mirroring )
+ {
+ if (m_pImpl->m_pBtn)
+ {
+ m_pImpl->m_pBtn->EnableRTL( IsRTLEnabled() );
+ ImplInitDropDownButton( m_pImpl->m_pBtn );
+ }
+ m_pImpl->m_pSubEdit->CompatStateChanged( StateChangedType::Mirroring );
+ m_pImpl->m_pImplLB->EnableRTL( IsRTLEnabled() );
+ Resize();
+ }
+}
+
+void ComboBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( !((rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))) )
+ return;
+
+ if (m_pImpl->m_pBtn)
+ {
+ m_pImpl->m_pBtn->GetOutDev()->SetSettings( GetSettings() );
+ ImplInitDropDownButton( m_pImpl->m_pBtn );
+ }
+ Resize();
+ m_pImpl->m_pImplLB->Resize(); // not called by ComboBox::Resize() if ImplLB is unchanged
+
+ SetBackground(); // due to a hack in Window::UpdateSettings the background must be reset
+ // otherwise it will overpaint NWF drawn comboboxes
+}
+
+bool ComboBox::EventNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+ if ((rNEvt.GetType() == MouseNotifyEvent::KEYINPUT)
+ && (rNEvt.GetWindow() == m_pImpl->m_pSubEdit)
+ && !IsReadOnly())
+ {
+ KeyEvent aKeyEvt = *rNEvt.GetKeyEvent();
+ sal_uInt16 nKeyCode = aKeyEvt.GetKeyCode().GetCode();
+ switch( nKeyCode )
+ {
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ {
+ m_pImpl->ImplUpdateFloatSelection();
+ if ((nKeyCode == KEY_DOWN) && m_pImpl->m_pFloatWin
+ && !m_pImpl->m_pFloatWin->IsInPopupMode()
+ && aKeyEvt.GetKeyCode().IsMod2())
+ {
+ CallEventListeners( VclEventId::DropdownPreOpen );
+ m_pImpl->m_pBtn->SetPressed( true );
+ if (m_pImpl->m_pImplLB->GetEntryList().GetMRUCount())
+ m_pImpl->m_pImplLB->SelectEntry( 0 , true );
+ SetSelection( Selection( 0, SELECTION_MAX ) );
+ m_pImpl->m_pFloatWin->StartFloat( false );
+ CallEventListeners( VclEventId::DropdownOpen );
+ bDone = true;
+ }
+ else if ((nKeyCode == KEY_UP) && m_pImpl->m_pFloatWin
+ && m_pImpl->m_pFloatWin->IsInPopupMode()
+ && aKeyEvt.GetKeyCode().IsMod2())
+ {
+ m_pImpl->m_pFloatWin->EndPopupMode();
+ bDone = true;
+ }
+ else
+ {
+ bDone = m_pImpl->m_pImplLB->ProcessKeyInput( aKeyEvt );
+ }
+ }
+ break;
+
+ case KEY_RETURN:
+ {
+ if ((rNEvt.GetWindow() == m_pImpl->m_pSubEdit) && IsInDropDown())
+ {
+ m_pImpl->m_pImplLB->ProcessKeyInput( aKeyEvt );
+ bDone = true;
+ }
+ }
+ break;
+ }
+ }
+ else if ((rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS) && m_pImpl->m_pFloatWin)
+ {
+ if (m_pImpl->m_pFloatWin->HasChildPathFocus())
+ m_pImpl->m_pSubEdit->GrabFocus();
+ else if (m_pImpl->m_pFloatWin->IsInPopupMode() && !HasChildPathFocus(true))
+ m_pImpl->m_pFloatWin->EndPopupMode();
+ }
+ else if( (rNEvt.GetType() == MouseNotifyEvent::COMMAND) &&
+ (rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) &&
+ (rNEvt.GetWindow() == m_pImpl->m_pSubEdit) )
+ {
+ MouseWheelBehaviour nWheelBehavior( GetSettings().GetMouseSettings().GetWheelBehavior() );
+ if ( ( nWheelBehavior == MouseWheelBehaviour::ALWAYS )
+ || ( ( nWheelBehavior == MouseWheelBehaviour::FocusOnly )
+ && HasChildPathFocus()
+ )
+ )
+ {
+ bDone = m_pImpl->m_pImplLB->HandleWheelAsCursorTravel(*rNEvt.GetCommandEvent(), *this);
+ }
+ else
+ {
+ bDone = false; // don't eat this event, let the default handling happen (i.e. scroll the context)
+ }
+ }
+ else if ((rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONDOWN)
+ && (rNEvt.GetWindow() == GetMainWindow()))
+ {
+ m_pImpl->m_pSubEdit->GrabFocus();
+ }
+
+ return bDone || Edit::EventNotify( rNEvt );
+}
+
+void ComboBox::SetText( const OUString& rStr )
+{
+ CallEventListeners( VclEventId::ComboboxSetText );
+
+ Edit::SetText( rStr );
+ m_pImpl->ImplUpdateFloatSelection();
+}
+
+void ComboBox::SetText( const OUString& rStr, const Selection& rNewSelection )
+{
+ CallEventListeners( VclEventId::ComboboxSetText );
+
+ Edit::SetText( rStr, rNewSelection );
+ m_pImpl->ImplUpdateFloatSelection();
+}
+
+void ComboBox::Modify()
+{
+ if (!m_pImpl->m_isSyntheticModify)
+ m_pImpl->ImplUpdateFloatSelection();
+
+ Edit::Modify();
+}
+
+void ComboBox::Impl::ImplUpdateFloatSelection()
+{
+ if (!m_pImplLB || !m_pSubEdit)
+ return;
+
+ // move text in the ListBox into the visible region
+ m_pImplLB->SetCallSelectionChangedHdl( false );
+ if (!m_rThis.IsMultiSelectionEnabled())
+ {
+ OUString aSearchStr( m_pSubEdit->GetText() );
+ sal_Int32 nSelect = LISTBOX_ENTRY_NOTFOUND;
+ bool bSelect = true;
+
+ if (m_pImplLB->GetCurrentPos() != LISTBOX_ENTRY_NOTFOUND)
+ {
+ OUString aCurrent = m_pImplLB->GetEntryList().GetEntryText(
+ m_pImplLB->GetCurrentPos());
+ if ( aCurrent == aSearchStr )
+ nSelect = m_pImplLB->GetCurrentPos();
+ }
+
+ if ( nSelect == LISTBOX_ENTRY_NOTFOUND )
+ nSelect = m_pImplLB->GetEntryList().FindEntry( aSearchStr );
+ if ( nSelect == LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = m_pImplLB->GetEntryList().FindMatchingEntry( aSearchStr, 0, true );
+ bSelect = false;
+ }
+
+ if( nSelect != LISTBOX_ENTRY_NOTFOUND )
+ {
+ if (!m_pImplLB->IsVisible(nSelect))
+ m_pImplLB->ShowProminentEntry( nSelect );
+ m_pImplLB->SelectEntry( nSelect, bSelect );
+ }
+ else
+ {
+ nSelect = m_pImplLB->GetEntryList().GetSelectedEntryPos( 0 );
+ if( nSelect != LISTBOX_ENTRY_NOTFOUND )
+ m_pImplLB->SelectEntry( nSelect, false );
+ m_pImplLB->ResetCurrentPos();
+ }
+ }
+ else
+ {
+ ::std::set< sal_Int32 > aSelInText;
+ lcl_GetSelectedEntries(aSelInText, m_pSubEdit->GetText(), m_cMultiSep, m_pImplLB->GetEntryList());
+ for (sal_Int32 n = 0; n < m_pImplLB->GetEntryList().GetEntryCount(); n++)
+ m_pImplLB->SelectEntry( n, aSelInText.count( n ) != 0 );
+ }
+ m_pImplLB->SetCallSelectionChangedHdl( true );
+}
+
+sal_Int32 ComboBox::InsertEntry(const OUString& rStr, sal_Int32 const nPos)
+{
+ assert(nPos >= 0 && COMBOBOX_MAX_ENTRIES > m_pImpl->m_pImplLB->GetEntryList().GetEntryCount());
+
+ sal_Int32 nRealPos;
+ if (nPos == COMBOBOX_APPEND)
+ nRealPos = nPos;
+ else
+ {
+ const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ assert(nPos <= COMBOBOX_MAX_ENTRIES - nMRUCount);
+ nRealPos = nPos + nMRUCount;
+ }
+
+ nRealPos = m_pImpl->m_pImplLB->InsertEntry( nRealPos, rStr );
+ nRealPos -= m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ CallEventListeners( VclEventId::ComboboxItemAdded, reinterpret_cast<void*>(nRealPos) );
+ return nRealPos;
+}
+
+sal_Int32 ComboBox::InsertEntryWithImage(
+ const OUString& rStr, const Image& rImage, sal_Int32 const nPos)
+{
+ assert(nPos >= 0 && COMBOBOX_MAX_ENTRIES > m_pImpl->m_pImplLB->GetEntryList().GetEntryCount());
+
+ sal_Int32 nRealPos;
+ if (nPos == COMBOBOX_APPEND)
+ nRealPos = nPos;
+ else
+ {
+ const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ assert(nPos <= COMBOBOX_MAX_ENTRIES - nMRUCount);
+ nRealPos = nPos + nMRUCount;
+ }
+
+ nRealPos = m_pImpl->m_pImplLB->InsertEntry( nRealPos, rStr, rImage );
+ nRealPos -= m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ CallEventListeners( VclEventId::ComboboxItemAdded, reinterpret_cast<void*>(nRealPos) );
+ return nRealPos;
+}
+
+void ComboBox::RemoveEntryAt(sal_Int32 const nPos)
+{
+ const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ assert(nPos >= 0 && nPos <= COMBOBOX_MAX_ENTRIES - nMRUCount);
+ m_pImpl->m_pImplLB->RemoveEntry( nPos + nMRUCount );
+ CallEventListeners( VclEventId::ComboboxItemRemoved, reinterpret_cast<void*>(nPos) );
+}
+
+void ComboBox::Clear()
+{
+ if (!m_pImpl->m_pImplLB)
+ return;
+ m_pImpl->m_pImplLB->Clear();
+ CallEventListeners( VclEventId::ComboboxItemRemoved, reinterpret_cast<void*>(-1) );
+}
+
+Image ComboBox::GetEntryImage( sal_Int32 nPos ) const
+{
+ if (m_pImpl->m_pImplLB->GetEntryList().HasEntryImage(nPos))
+ return m_pImpl->m_pImplLB->GetEntryList().GetEntryImage( nPos );
+ return Image();
+}
+
+sal_Int32 ComboBox::GetEntryPos( std::u16string_view rStr ) const
+{
+ sal_Int32 nPos = m_pImpl->m_pImplLB->GetEntryList().FindEntry( rStr );
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ nPos -= m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ return nPos;
+}
+
+OUString ComboBox::GetEntry( sal_Int32 nPos ) const
+{
+ const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ if (nPos < 0 || nPos > COMBOBOX_MAX_ENTRIES - nMRUCount)
+ return OUString();
+
+ return m_pImpl->m_pImplLB->GetEntryList().GetEntryText( nPos + nMRUCount );
+}
+
+sal_Int32 ComboBox::GetEntryCount() const
+{
+ if (!m_pImpl->m_pImplLB)
+ return 0;
+ return m_pImpl->m_pImplLB->GetEntryList().GetEntryCount() - m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+}
+
+bool ComboBox::IsTravelSelect() const
+{
+ return m_pImpl->m_pImplLB->IsTravelSelect();
+}
+
+bool ComboBox::IsInDropDown() const
+{
+ // when the dropdown is dismissed, first mbInPopupMode is set to false, and on the next event iteration then
+ // mbPopupMode is set to false
+ return m_pImpl->m_pFloatWin && m_pImpl->m_pFloatWin->IsInPopupMode() && m_pImpl->m_pFloatWin->ImplIsInPrivatePopupMode();
+}
+
+bool ComboBox::IsMultiSelectionEnabled() const
+{
+ return m_pImpl->m_pImplLB->IsMultiSelectionEnabled();
+}
+
+void ComboBox::SetSelectHdl(const Link<ComboBox&,void>& rLink) { m_pImpl->m_SelectHdl = rLink; }
+
+void ComboBox::SetEntryActivateHdl(const Link<Edit&,bool>& rLink)
+{
+ if (!m_pImpl->m_pSubEdit)
+ return;
+ m_pImpl->m_pSubEdit->SetActivateHdl(rLink);
+}
+
+Size ComboBox::GetOptimalSize() const
+{
+ return CalcMinimumSize();
+}
+
+tools::Long ComboBox::getMaxWidthScrollBarAndDownButton() const
+{
+ tools::Long nButtonDownWidth = 0;
+
+ vcl::Window *pBorder = GetWindow( GetWindowType::Border );
+ ImplControlValue aControlValue;
+ tools::Rectangle aContent, aBound;
+
+ // use the full extent of the control
+ tools::Rectangle aArea( Point(), pBorder->GetOutputSizePixel() );
+
+ if ( GetNativeControlRegion(ControlType::Combobox, ControlPart::ButtonDown,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ nButtonDownWidth = aContent.getWidth();
+ }
+
+ tools::Long nScrollBarWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
+
+ return std::max(nScrollBarWidth, nButtonDownWidth);
+}
+
+Size ComboBox::CalcMinimumSize() const
+{
+ Size aSz;
+
+ if (!m_pImpl->m_pImplLB)
+ return aSz;
+
+ if (!IsDropDownBox())
+ {
+ aSz = m_pImpl->m_pImplLB->CalcSize( m_pImpl->m_pImplLB->GetEntryList().GetEntryCount() );
+ aSz.AdjustHeight(m_pImpl->m_nDDHeight );
+ }
+ else
+ {
+ aSz.setHeight( Edit::CalcMinimumSizeForText(GetText()).Height() );
+
+ if (m_pImpl->m_nWidthInChars!= -1)
+ aSz.setWidth(m_pImpl->m_nWidthInChars * approximate_digit_width());
+ else
+ aSz.setWidth(m_pImpl->m_pImplLB->GetMaxEntryWidth());
+ }
+
+ if (m_pImpl->m_nMaxWidthChars != -1)
+ {
+ tools::Long nMaxWidth = m_pImpl->m_nMaxWidthChars * approximate_char_width();
+ aSz.setWidth( std::min(aSz.Width(), nMaxWidth) );
+ }
+
+ if (IsDropDownBox())
+ aSz.AdjustWidth(getMaxWidthScrollBarAndDownButton() );
+
+ ComboBoxBounds aBounds(m_pImpl->calcComboBoxDropDownComponentBounds(
+ Size(0xFFFF, 0xFFFF), Size(0xFFFF, 0xFFFF)));
+ aSz.AdjustWidth(aBounds.aSubEditPos.X()*2 );
+
+ aSz.AdjustWidth(ImplGetExtraXOffset() * 2 );
+
+ aSz = CalcWindowSize( aSz );
+ return aSz;
+}
+
+Size ComboBox::CalcAdjustedSize( const Size& rPrefSize ) const
+{
+ Size aSz = rPrefSize;
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ static_cast<vcl::Window*>(const_cast<ComboBox *>(this))->GetBorder( nLeft, nTop, nRight, nBottom );
+ aSz.AdjustHeight( -(nTop+nBottom) );
+ if ( !IsDropDownBox() )
+ {
+ tools::Long nEntryHeight = CalcBlockSize( 1, 1 ).Height();
+ tools::Long nLines = aSz.Height() / nEntryHeight;
+ if ( nLines < 1 )
+ nLines = 1;
+ aSz.setHeight( nLines * nEntryHeight );
+ aSz.AdjustHeight(m_pImpl->m_nDDHeight );
+ }
+ else
+ {
+ aSz.setHeight( m_pImpl->m_nDDHeight );
+ }
+ aSz.AdjustHeight(nTop+nBottom );
+
+ aSz = CalcWindowSize( aSz );
+ return aSz;
+}
+
+Size ComboBox::CalcBlockSize( sal_uInt16 nColumns, sal_uInt16 nLines ) const
+{
+ // show ScrollBars where appropriate
+ Size aMinSz = CalcMinimumSize();
+ Size aSz;
+
+ // height
+ if ( nLines )
+ {
+ if ( !IsDropDownBox() )
+ aSz.setHeight( m_pImpl->m_pImplLB->CalcSize( nLines ).Height() + m_pImpl->m_nDDHeight );
+ else
+ aSz.setHeight( m_pImpl->m_nDDHeight );
+ }
+ else
+ aSz.setHeight( aMinSz.Height() );
+
+ // width
+ if ( nColumns )
+ aSz.setWidth( nColumns * approximate_char_width() );
+ else
+ aSz.setWidth( aMinSz.Width() );
+
+ if ( IsDropDownBox() )
+ aSz.AdjustWidth(getMaxWidthScrollBarAndDownButton() );
+
+ if ( !IsDropDownBox() )
+ {
+ if ( aSz.Width() < aMinSz.Width() )
+ aSz.AdjustHeight(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ if ( aSz.Height() < aMinSz.Height() )
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ }
+
+ aSz.AdjustWidth(ImplGetExtraXOffset() * 2 );
+
+ aSz = CalcWindowSize( aSz );
+ return aSz;
+}
+
+tools::Long ComboBox::GetDropDownEntryHeight() const
+{
+ return m_pImpl->m_pImplLB->GetEntryHeight();
+}
+
+void ComboBox::GetMaxVisColumnsAndLines( sal_uInt16& rnCols, sal_uInt16& rnLines ) const
+{
+ tools::Long nCharWidth = GetTextWidth(OUString(u'x'));
+ if ( !IsDropDownBox() )
+ {
+ Size aOutSz = GetMainWindow()->GetOutputSizePixel();
+ rnCols = (nCharWidth > 0) ? static_cast<sal_uInt16>(aOutSz.Width()/nCharWidth) : 1;
+ rnLines = static_cast<sal_uInt16>(aOutSz.Height()/GetDropDownEntryHeight());
+ }
+ else
+ {
+ Size aOutSz = m_pImpl->m_pSubEdit->GetOutputSizePixel();
+ rnCols = (nCharWidth > 0) ? static_cast<sal_uInt16>(aOutSz.Width()/nCharWidth) : 1;
+ rnLines = 1;
+ }
+}
+
+void ComboBox::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags )
+{
+ GetMainWindow()->ApplySettings(*pDev);
+
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ vcl::Font aFont = GetMainWindow()->GetDrawPixelFont( pDev );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ pDev->SetTextFillColor();
+
+ // Border/Background
+ pDev->SetLineColor();
+ pDev->SetFillColor();
+ bool bBorder = (GetStyle() & WB_BORDER);
+ bool bBackground = IsControlBackground();
+ if ( bBorder || bBackground )
+ {
+ tools::Rectangle aRect( aPos, aSize );
+ // aRect.Top() += nEditHeight;
+ if ( bBorder )
+ {
+ ImplDrawFrame( pDev, aRect );
+ }
+ if ( bBackground )
+ {
+ pDev->SetFillColor( GetControlBackground() );
+ pDev->DrawRect( aRect );
+ }
+ }
+
+ // contents
+ if ( !IsDropDownBox() )
+ {
+ tools::Long nOnePixel = GetDrawPixel( pDev, 1 );
+ tools::Long nTextHeight = pDev->GetTextHeight();
+ tools::Long nEditHeight = nTextHeight + 6*nOnePixel;
+ DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
+
+ // First, draw the edit part
+ Size aOrigSize(m_pImpl->m_pSubEdit->GetSizePixel());
+ m_pImpl->m_pSubEdit->SetSizePixel(Size(aSize.Width(), nEditHeight));
+ m_pImpl->m_pSubEdit->Draw( pDev, aPos, nFlags );
+ m_pImpl->m_pSubEdit->SetSizePixel(aOrigSize);
+
+ // Second, draw the listbox
+ if ( GetStyle() & WB_CENTER )
+ nTextStyle |= DrawTextFlags::Center;
+ else if ( GetStyle() & WB_RIGHT )
+ nTextStyle |= DrawTextFlags::Right;
+ else
+ nTextStyle |= DrawTextFlags::Left;
+
+ if ( nFlags & SystemTextColorFlags::Mono )
+ {
+ pDev->SetTextColor( COL_BLACK );
+ }
+ else
+ {
+ if ( !IsEnabled() )
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ pDev->SetTextColor( rStyleSettings.GetDisableColor() );
+ }
+ else
+ {
+ pDev->SetTextColor( GetTextColor() );
+ }
+ }
+
+ tools::Rectangle aClip( aPos, aSize );
+ pDev->IntersectClipRegion( aClip );
+ sal_Int32 nLines = static_cast<sal_Int32>( nTextHeight > 0 ? (aSize.Height()-nEditHeight)/nTextHeight : 1 );
+ if ( !nLines )
+ nLines = 1;
+ const sal_Int32 nTEntry = IsReallyVisible() ? m_pImpl->m_pImplLB->GetTopEntry() : 0;
+
+ tools::Rectangle aTextRect( aPos, aSize );
+
+ aTextRect.AdjustLeft(3*nOnePixel );
+ aTextRect.AdjustRight( -(3*nOnePixel) );
+ aTextRect.AdjustTop(nEditHeight + nOnePixel );
+ aTextRect.SetBottom( aTextRect.Top() + nTextHeight );
+
+ // the drawing starts here
+ for ( sal_Int32 n = 0; n < nLines; ++n )
+ {
+ pDev->DrawText( aTextRect, m_pImpl->m_pImplLB->GetEntryList().GetEntryText( n+nTEntry ), nTextStyle );
+ aTextRect.AdjustTop(nTextHeight );
+ aTextRect.AdjustBottom(nTextHeight );
+ }
+ }
+
+ pDev->Pop();
+
+ // Call Edit::Draw after restoring the MapMode...
+ if ( IsDropDownBox() )
+ {
+ Size aOrigSize(m_pImpl->m_pSubEdit->GetSizePixel());
+ m_pImpl->m_pSubEdit->SetSizePixel(GetSizePixel());
+ m_pImpl->m_pSubEdit->Draw( pDev, rPos, nFlags );
+ m_pImpl->m_pSubEdit->SetSizePixel(aOrigSize);
+ // DD-Button ?
+ }
+}
+
+void ComboBox::SetUserDrawHdl(const Link<UserDrawEvent*, void>& rLink)
+{
+ m_pImpl->m_pImplLB->SetUserDrawHdl(rLink);
+}
+
+void ComboBox::SetUserItemSize( const Size& rSz )
+{
+ GetMainWindow()->SetUserItemSize( rSz );
+}
+
+void ComboBox::EnableUserDraw( bool bUserDraw )
+{
+ GetMainWindow()->EnableUserDraw( bUserDraw );
+}
+
+bool ComboBox::IsUserDrawEnabled() const
+{
+ return GetMainWindow()->IsUserDrawEnabled();
+}
+
+void ComboBox::DrawEntry(const UserDrawEvent& rEvt)
+{
+ GetMainWindow()->DrawEntry(*rEvt.GetRenderContext(), rEvt.GetItemId(), /*bDrawImage*/false, /*bDrawText*/false);
+}
+
+void ComboBox::AddSeparator( sal_Int32 n )
+{
+ m_pImpl->m_pImplLB->AddSeparator( n );
+}
+
+void ComboBox::SetMRUEntries( std::u16string_view rEntries )
+{
+ m_pImpl->m_pImplLB->SetMRUEntries( rEntries, ';' );
+}
+
+OUString ComboBox::GetMRUEntries() const
+{
+ return m_pImpl->m_pImplLB ? m_pImpl->m_pImplLB->GetMRUEntries( ';' ) : OUString();
+}
+
+void ComboBox::SetMaxMRUCount( sal_Int32 n )
+{
+ m_pImpl->m_pImplLB->SetMaxMRUCount( n );
+}
+
+sal_Int32 ComboBox::GetMaxMRUCount() const
+{
+ return m_pImpl->m_pImplLB ? m_pImpl->m_pImplLB->GetMaxMRUCount() : 0;
+}
+
+sal_uInt16 ComboBox::GetDisplayLineCount() const
+{
+ return m_pImpl->m_pImplLB ? m_pImpl->m_pImplLB->GetDisplayLineCount() : 0;
+}
+
+void ComboBox::SetEntryData( sal_Int32 nPos, void* pNewData )
+{
+ m_pImpl->m_pImplLB->SetEntryData( nPos + m_pImpl->m_pImplLB->GetEntryList().GetMRUCount(), pNewData );
+}
+
+void* ComboBox::GetEntryData( sal_Int32 nPos ) const
+{
+ return m_pImpl->m_pImplLB->GetEntryList().GetEntryData(
+ nPos + m_pImpl->m_pImplLB->GetEntryList().GetMRUCount() );
+}
+
+sal_Int32 ComboBox::GetTopEntry() const
+{
+ sal_Int32 nPos = GetEntryCount() ? m_pImpl->m_pImplLB->GetTopEntry() : LISTBOX_ENTRY_NOTFOUND;
+ if (nPos < m_pImpl->m_pImplLB->GetEntryList().GetMRUCount())
+ nPos = 0;
+ return nPos;
+}
+
+tools::Rectangle ComboBox::GetDropDownPosSizePixel() const
+{
+ return m_pImpl->m_pFloatWin
+ ? m_pImpl->m_pFloatWin->GetWindowExtentsRelative(this)
+ : tools::Rectangle();
+}
+
+const Wallpaper& ComboBox::GetDisplayBackground() const
+{
+ if (!m_pImpl->m_pSubEdit->IsBackground())
+ return Control::GetDisplayBackground();
+
+ const Wallpaper& rBack = m_pImpl->m_pSubEdit->GetBackground();
+ if( ! rBack.IsBitmap() &&
+ ! rBack.IsGradient() &&
+ rBack == Wallpaper(COL_TRANSPARENT)
+ )
+ return Control::GetDisplayBackground();
+ return rBack;
+}
+
+sal_Int32 ComboBox::GetSelectedEntryCount() const
+{
+ return m_pImpl->m_pImplLB->GetEntryList().GetSelectedEntryCount();
+}
+
+sal_Int32 ComboBox::GetSelectedEntryPos( sal_Int32 nIndex ) const
+{
+ sal_Int32 nPos = m_pImpl->m_pImplLB->GetEntryList().GetSelectedEntryPos( nIndex );
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ if (nPos < m_pImpl->m_pImplLB->GetEntryList().GetMRUCount())
+ nPos = m_pImpl->m_pImplLB->GetEntryList().FindEntry(m_pImpl->m_pImplLB->GetEntryList().GetEntryText(nPos));
+ nPos = sal::static_int_cast<sal_Int32>(nPos - m_pImpl->m_pImplLB->GetEntryList().GetMRUCount());
+ }
+ return nPos;
+}
+
+bool ComboBox::IsEntryPosSelected( sal_Int32 nPos ) const
+{
+ return m_pImpl->m_pImplLB->GetEntryList().IsEntryPosSelected(
+ nPos + m_pImpl->m_pImplLB->GetEntryList().GetMRUCount() );
+}
+
+void ComboBox::SelectEntryPos( sal_Int32 nPos, bool bSelect)
+{
+ if (nPos < m_pImpl->m_pImplLB->GetEntryList().GetEntryCount())
+ m_pImpl->m_pImplLB->SelectEntry(
+ nPos + m_pImpl->m_pImplLB->GetEntryList().GetMRUCount(), bSelect);
+}
+
+void ComboBox::SetNoSelection()
+{
+ m_pImpl->m_pImplLB->SetNoSelection();
+ m_pImpl->m_pSubEdit->SetText( OUString() );
+}
+
+tools::Rectangle ComboBox::GetBoundingRectangle( sal_Int32 nItem ) const
+{
+ tools::Rectangle aRect = GetMainWindow()->GetBoundingRectangle( nItem );
+ tools::Rectangle aOffset = GetMainWindow()->GetWindowExtentsRelative( static_cast<vcl::Window*>(const_cast<ComboBox *>(this)) );
+ aRect.Move( aOffset.Left(), aOffset.Top() );
+ return aRect;
+}
+
+void ComboBox::SetBorderStyle( WindowBorderStyle nBorderStyle )
+{
+ Window::SetBorderStyle( nBorderStyle );
+ if ( !IsDropDownBox() )
+ {
+ m_pImpl->m_pSubEdit->SetBorderStyle( nBorderStyle );
+ m_pImpl->m_pImplLB->SetBorderStyle( nBorderStyle );
+ }
+}
+
+ImplListBoxWindow* ComboBox::GetMainWindow() const
+{
+ return m_pImpl->m_pImplLB->GetMainWindow();
+}
+
+tools::Long ComboBox::GetIndexForPoint( const Point& rPoint, sal_Int32& rPos ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+
+ // check whether rPoint fits at all
+ tools::Long nIndex = Control::GetIndexForPoint( rPoint );
+ if( nIndex != -1 )
+ {
+ // point must be either in main list window
+ // or in impl window (dropdown case)
+ ImplListBoxWindow* rMain = GetMainWindow();
+
+ // convert coordinates to ImplListBoxWindow pixel coordinate space
+ Point aConvPoint = LogicToPixel( rPoint );
+ aConvPoint = OutputToAbsoluteScreenPixel( aConvPoint );
+ aConvPoint = rMain->AbsoluteScreenToOutputPixel( aConvPoint );
+ aConvPoint = rMain->PixelToLogic( aConvPoint );
+
+ // try to find entry
+ sal_Int32 nEntry = rMain->GetEntryPosForPoint( aConvPoint );
+ if( nEntry == LISTBOX_ENTRY_NOTFOUND )
+ nIndex = -1;
+ else
+ rPos = nEntry;
+ }
+
+ // get line relative index
+ if( nIndex != -1 )
+ nIndex = ToRelativeLineIndex( nIndex );
+
+ return nIndex;
+}
+
+ComboBoxBounds ComboBox::Impl::calcComboBoxDropDownComponentBounds(
+ const Size &rOutSz, const Size &rBorderOutSz) const
+{
+ ComboBoxBounds aBounds;
+
+ tools::Long nTop = 0;
+ tools::Long nBottom = rOutSz.Height();
+
+ vcl::Window *pBorder = m_rThis.GetWindow( GetWindowType::Border );
+ ImplControlValue aControlValue;
+ Point aPoint;
+ tools::Rectangle aContent, aBound;
+
+ // use the full extent of the control
+ tools::Rectangle aArea( aPoint, rBorderOutSz );
+
+ if (m_rThis.GetNativeControlRegion(ControlType::Combobox, ControlPart::ButtonDown,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ // convert back from border space to local coordinates
+ aPoint = pBorder->ScreenToOutputPixel(m_rThis.OutputToScreenPixel(aPoint));
+ aContent.Move(-aPoint.X(), -aPoint.Y());
+
+ aBounds.aButtonPos = Point(aContent.Left(), nTop);
+ aBounds.aButtonSize = Size(aContent.getWidth(), (nBottom-nTop));
+
+ // adjust the size of the edit field
+ if (m_rThis.GetNativeControlRegion(ControlType::Combobox, ControlPart::SubEdit,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ // convert back from border space to local coordinates
+ aContent.Move(-aPoint.X(), -aPoint.Y());
+
+ // use the themes drop down size
+ aBounds.aSubEditPos = aContent.TopLeft();
+ aBounds.aSubEditSize = aContent.GetSize();
+ }
+ else
+ {
+ // use the themes drop down size for the button
+ aBounds.aSubEditSize = Size(rOutSz.Width() - aContent.getWidth(), rOutSz.Height());
+ }
+ }
+ else
+ {
+ tools::Long nSBWidth = m_rThis.GetSettings().GetStyleSettings().GetScrollBarSize();
+ nSBWidth = m_rThis.CalcZoom( nSBWidth );
+ aBounds.aSubEditSize = Size(rOutSz.Width() - nSBWidth, rOutSz.Height());
+ aBounds.aButtonPos = Point(rOutSz.Width() - nSBWidth, nTop);
+ aBounds.aButtonSize = Size(nSBWidth, (nBottom-nTop));
+ }
+ return aBounds;
+}
+
+void ComboBox::SetWidthInChars(sal_Int32 nWidthInChars)
+{
+ if (nWidthInChars != m_pImpl->m_nWidthInChars)
+ {
+ m_pImpl->m_nWidthInChars = nWidthInChars;
+ queue_resize();
+ }
+}
+
+void ComboBox::setMaxWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != m_pImpl->m_nMaxWidthChars)
+ {
+ m_pImpl->m_nMaxWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+bool ComboBox::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "width-chars")
+ SetWidthInChars(rValue.toInt32());
+ else if (rKey == "max-width-chars")
+ setMaxWidthChars(rValue.toInt32());
+ else if (rKey == "can-focus")
+ {
+ // as far as I can see in Gtk, setting a ComboBox as can.focus means
+ // the focus gets stuck in it, so try here to behave like gtk does
+ // with the settings that work, i.e. can.focus of false doesn't
+ // set the hard WB_NOTABSTOP
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_TABSTOP|WB_NOTABSTOP);
+ if (toBool(rValue))
+ nBits |= WB_TABSTOP;
+ SetStyle(nBits);
+ }
+ else if (rKey == "placeholder-text")
+ SetPlaceholderText(rValue);
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+FactoryFunction ComboBox::GetUITestFactory() const
+{
+ return ComboBoxUIObject::create;
+}
+
+void ComboBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+
+ {
+ auto entriesNode = rJsonWriter.startArray("entries");
+ for (int i = 0; i < GetEntryCount(); ++i)
+ {
+ rJsonWriter.putSimpleValue(GetEntry(i));
+ }
+ }
+
+ {
+ auto selectedNode = rJsonWriter.startArray("selectedEntries");
+ for (int i = 0; i < GetSelectedEntryCount(); ++i)
+ {
+ rJsonWriter.putSimpleValue(OUString::number(GetSelectedEntryPos(i)));
+ }
+ }
+
+ rJsonWriter.put("selectedCount", GetSelectedEntryCount());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/ctrl.cxx b/vcl/source/control/ctrl.cxx
new file mode 100644
index 000000000..6cb06bc11
--- /dev/null
+++ b/vcl/source/control/ctrl.cxx
@@ -0,0 +1,505 @@
+/* -*- 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 <o3tl/safeint.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/event.hxx>
+#include <vcl/ctrl.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <sal/log.hxx>
+
+#include <textlayout.hxx>
+#include <svdata.hxx>
+
+using namespace vcl;
+
+void Control::ImplInitControlData()
+{
+ mbHasControlFocus = false;
+ mbShowAccelerator = false;
+}
+
+Control::Control( WindowType nType ) :
+ Window( nType )
+{
+ ImplInitControlData();
+}
+
+Control::Control( vcl::Window* pParent, WinBits nStyle ) :
+ Window( WindowType::CONTROL )
+{
+ ImplInitControlData();
+ ImplInit( pParent, nStyle, nullptr );
+}
+
+Control::~Control()
+{
+ disposeOnce();
+}
+
+void Control::dispose()
+{
+ mxLayoutData.reset();
+ mpReferenceDevice.clear();
+ Window::dispose();
+}
+
+void Control::EnableRTL( bool bEnable )
+{
+ // convenience: for controls also switch layout mode
+ GetOutDev()->SetLayoutMode( bEnable ? vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft :
+ vcl::text::ComplexTextLayoutFlags::TextOriginLeft );
+ CompatStateChanged( StateChangedType::Mirroring );
+ Window::EnableRTL(bEnable);
+}
+
+void Control::Resize()
+{
+ ImplClearLayoutData();
+ Window::Resize();
+}
+
+void Control::FillLayoutData() const
+{
+}
+
+void Control::CreateLayoutData() const
+{
+ SAL_WARN_IF( mxLayoutData, "vcl", "Control::CreateLayoutData: should be called with non-existent layout data only!" );
+ mxLayoutData.emplace();
+}
+
+bool Control::HasLayoutData() const
+{
+ return bool(mxLayoutData);
+}
+
+void Control::SetText( const OUString& rStr )
+{
+ ImplClearLayoutData();
+ Window::SetText( rStr );
+}
+
+ControlLayoutData::ControlLayoutData() : m_pParent( nullptr )
+{
+}
+
+tools::Rectangle ControlLayoutData::GetCharacterBounds( tools::Long nIndex ) const
+{
+ return (nIndex >= 0 && o3tl::make_unsigned(nIndex) < m_aUnicodeBoundRects.size()) ? m_aUnicodeBoundRects[ nIndex ] : tools::Rectangle();
+}
+
+tools::Rectangle Control::GetCharacterBounds( tools::Long nIndex ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+ return mxLayoutData ? mxLayoutData->GetCharacterBounds( nIndex ) : tools::Rectangle();
+}
+
+tools::Long ControlLayoutData::GetIndexForPoint( const Point& rPoint ) const
+{
+ tools::Long nIndex = -1;
+ for( tools::Long i = m_aUnicodeBoundRects.size()-1; i >= 0; i-- )
+ {
+ Point aTopLeft = m_aUnicodeBoundRects[i].TopLeft();
+ Point aBottomRight = m_aUnicodeBoundRects[i].BottomRight();
+ if (rPoint.X() >= aTopLeft.X() && rPoint.Y() >= aTopLeft.Y() &&
+ rPoint.X() <= aBottomRight.X() && rPoint.Y() <= aBottomRight.Y())
+ {
+ nIndex = i;
+ break;
+ }
+ }
+ return nIndex;
+}
+
+tools::Long Control::GetIndexForPoint( const Point& rPoint ) const
+{
+ if( ! HasLayoutData() )
+ FillLayoutData();
+ return mxLayoutData ? mxLayoutData->GetIndexForPoint( rPoint ) : -1;
+}
+
+tools::Long ControlLayoutData::GetLineCount() const
+{
+ tools::Long nLines = m_aLineIndices.size();
+ if( nLines == 0 && !m_aDisplayText.isEmpty() )
+ nLines = 1;
+ return nLines;
+}
+
+Pair ControlLayoutData::GetLineStartEnd( tools::Long nLine ) const
+{
+ Pair aPair( -1, -1 );
+
+ int nDisplayLines = m_aLineIndices.size();
+ if( nLine >= 0 && nLine < nDisplayLines )
+ {
+ aPair.A() = m_aLineIndices[nLine];
+ if( nLine+1 < nDisplayLines )
+ aPair.B() = m_aLineIndices[nLine+1]-1;
+ else
+ aPair.B() = m_aDisplayText.getLength()-1;
+ }
+ else if( nLine == 0 && nDisplayLines == 0 && !m_aDisplayText.isEmpty() )
+ {
+ // special case for single line controls so the implementations
+ // in that case do not have to fill in the line indices
+ aPair.A() = 0;
+ aPair.B() = m_aDisplayText.getLength()-1;
+ }
+ return aPair;
+}
+
+Pair Control::GetLineStartEnd( tools::Long nLine ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+ return mxLayoutData ? mxLayoutData->GetLineStartEnd( nLine ) : Pair( -1, -1 );
+}
+
+tools::Long ControlLayoutData::ToRelativeLineIndex( tools::Long nIndex ) const
+{
+ // is the index sensible at all ?
+ if( nIndex >= 0 && nIndex < m_aDisplayText.getLength() )
+ {
+ int nDisplayLines = m_aLineIndices.size();
+ // if only 1 line exists, then absolute and relative index are
+ // identical -> do nothing
+ if( nDisplayLines > 1 )
+ {
+ int nLine;
+ for( nLine = nDisplayLines-1; nLine >= 0; nLine-- )
+ {
+ if( m_aLineIndices[nLine] <= nIndex )
+ {
+ nIndex -= m_aLineIndices[nLine];
+ break;
+ }
+ }
+ if( nLine < 0 )
+ {
+ SAL_WARN_IF( nLine < 0, "vcl", "ToRelativeLineIndex failed" );
+ nIndex = -1;
+ }
+ }
+ }
+ else
+ nIndex = -1;
+
+ return nIndex;
+}
+
+tools::Long Control::ToRelativeLineIndex( tools::Long nIndex ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+ return mxLayoutData ? mxLayoutData->ToRelativeLineIndex( nIndex ) : -1;
+}
+
+OUString Control::GetDisplayText() const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+ return mxLayoutData ? mxLayoutData->m_aDisplayText : GetText();
+}
+
+bool Control::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ {
+ if ( !mbHasControlFocus )
+ {
+ mbHasControlFocus = true;
+ CompatStateChanged( StateChangedType::ControlFocus );
+ if ( ImplCallEventListenersAndHandler( VclEventId::ControlGetFocus, {} ) )
+ // been destroyed within the handler
+ return true;
+ }
+ }
+ else
+ {
+ if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ vcl::Window* pFocusWin = Application::GetFocusWindow();
+ if ( !pFocusWin || !ImplIsWindowOrChild( pFocusWin ) )
+ {
+ mbHasControlFocus = false;
+ CompatStateChanged( StateChangedType::ControlFocus );
+ if ( ImplCallEventListenersAndHandler( VclEventId::ControlLoseFocus, [this] () { maLoseFocusHdl.Call(*this); } ) )
+ // been destroyed within the handler
+ return true;
+ }
+ }
+ }
+ return Window::EventNotify( rNEvt );
+}
+
+void Control::StateChanged( StateChangedType nStateChange )
+{
+ if( nStateChange == StateChangedType::InitShow ||
+ nStateChange == StateChangedType::Visible ||
+ nStateChange == StateChangedType::Zoom ||
+ nStateChange == StateChangedType::ControlFont
+ )
+ {
+ ImplClearLayoutData();
+ }
+ Window::StateChanged( nStateChange );
+}
+
+void Control::AppendLayoutData( const Control& rSubControl ) const
+{
+ if( !rSubControl.HasLayoutData() )
+ rSubControl.FillLayoutData();
+ if( !rSubControl.HasLayoutData() || rSubControl.mxLayoutData->m_aDisplayText.isEmpty() )
+ return;
+
+ tools::Long nCurrentIndex = mxLayoutData->m_aDisplayText.getLength();
+ mxLayoutData->m_aDisplayText += rSubControl.mxLayoutData->m_aDisplayText;
+ int nLines = rSubControl.mxLayoutData->m_aLineIndices.size();
+ int n;
+ mxLayoutData->m_aLineIndices.push_back( nCurrentIndex );
+ for( n = 1; n < nLines; n++ )
+ mxLayoutData->m_aLineIndices.push_back( rSubControl.mxLayoutData->m_aLineIndices[n] + nCurrentIndex );
+ int nRectangles = rSubControl.mxLayoutData->m_aUnicodeBoundRects.size();
+ tools::Rectangle aRel = rSubControl.GetWindowExtentsRelative(this);
+ for( n = 0; n < nRectangles; n++ )
+ {
+ tools::Rectangle aRect = rSubControl.mxLayoutData->m_aUnicodeBoundRects[n];
+ aRect.Move( aRel.Left(), aRel.Top() );
+ mxLayoutData->m_aUnicodeBoundRects.push_back( aRect );
+ }
+}
+
+void Control::CallEventListeners( VclEventId nEvent, void* pData)
+{
+ VclPtr<Control> xThis(this);
+ UITestLogger::getInstance().logAction(xThis, nEvent);
+
+ vcl::Window::CallEventListeners(nEvent, pData);
+}
+
+bool Control::ImplCallEventListenersAndHandler( VclEventId nEvent, std::function<void()> const & callHandler )
+{
+ VclPtr<Control> xThis(this);
+
+ Control::CallEventListeners( nEvent );
+
+ if ( !xThis->isDisposed() )
+ {
+ if (callHandler)
+ {
+ callHandler();
+ }
+
+ if ( !xThis->isDisposed() )
+ return false;
+ }
+ return true;
+}
+
+void Control::SetLayoutDataParent( const Control* pParent ) const
+{
+ if( HasLayoutData() )
+ mxLayoutData->m_pParent = pParent;
+}
+
+void Control::ImplClearLayoutData() const
+{
+ mxLayoutData.reset();
+}
+
+void Control::ImplDrawFrame( OutputDevice* pDev, tools::Rectangle& rRect )
+{
+ // use a deco view to draw the frame
+ // However, since there happens a lot of magic there, we need to fake some (style) settings
+ // on the device
+ AllSettings aOriginalSettings( pDev->GetSettings() );
+
+ AllSettings aNewSettings( aOriginalSettings );
+ StyleSettings aStyle( aNewSettings.GetStyleSettings() );
+
+ // The *only known* clients of the Draw methods of the various VCL-controls are form controls:
+ // During print preview, and during printing, Draw is called. Thus, drawing always happens with a
+ // mono (colored) border
+ aStyle.SetOptions( aStyle.GetOptions() | StyleSettingsOptions::Mono );
+ aStyle.SetMonoColor( GetSettings().GetStyleSettings().GetMonoColor() );
+
+ aNewSettings.SetStyleSettings( aStyle );
+ // #i67023# do not call data changed listeners for this fake
+ // since they may understandably invalidate on settings changed
+ pDev->OutputDevice::SetSettings( aNewSettings );
+
+ DecorationView aDecoView( pDev );
+ rRect = aDecoView.DrawFrame( rRect, DrawFrameStyle::Out, DrawFrameFlags::WindowBorder );
+
+ pDev->OutputDevice::SetSettings( aOriginalSettings );
+}
+
+void Control::SetShowAccelerator(bool bVal)
+{
+ mbShowAccelerator = bVal;
+};
+
+ControlLayoutData::~ControlLayoutData()
+{
+ if( m_pParent )
+ m_pParent->ImplClearLayoutData();
+}
+
+Size Control::GetOptimalSize() const
+{
+ return Size( GetTextWidth( GetText() ) + 2 * 12,
+ GetTextHeight() + 2 * 6 );
+}
+
+void Control::SetReferenceDevice( OutputDevice* _referenceDevice )
+{
+ if ( mpReferenceDevice == _referenceDevice )
+ return;
+
+ mpReferenceDevice = _referenceDevice;
+ Invalidate();
+}
+
+OutputDevice* Control::GetReferenceDevice() const
+{
+ // tdf#118377 It can happen that mpReferenceDevice is already disposed and
+ // stays disposed (see task, even when Dialog is closed). I have no idea if
+ // this may be very bad - someone who knows more about lifetime of OutputDevice's
+ // will have to decide.
+ // To secure this, I changed all accesses to mpControlData->mpReferenceDevice to
+ // use Control::GetReferenceDevice() - only use mpControlData->mpReferenceDevice
+ // inside Control::SetReferenceDevice and Control::GetReferenceDevice().
+ // Control::GetReferenceDevice() will now reset mpReferenceDevice if it is already
+ // disposed. This way all usages will do a kind of 'test-and-get' call.
+ if(nullptr != mpReferenceDevice && mpReferenceDevice->isDisposed())
+ {
+ const_cast<Control*>(this)->SetReferenceDevice(nullptr);
+ }
+
+ return mpReferenceDevice;
+}
+
+const vcl::Font& Control::GetCanonicalFont( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetLabelFont();
+}
+
+const Color& Control::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetLabelTextColor();
+}
+
+void Control::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ ApplyControlFont(rRenderContext, GetCanonicalFont(rStyleSettings));
+
+ ApplyControlForeground(rRenderContext, GetCanonicalTextColor(rStyleSettings));
+ rRenderContext.SetTextFillColor();
+}
+
+void Control::ImplInitSettings()
+{
+ ApplySettings(*GetOutDev());
+}
+
+tools::Rectangle Control::DrawControlText( OutputDevice& _rTargetDevice, const tools::Rectangle& rRect, const OUString& _rStr,
+ DrawTextFlags _nStyle, std::vector< tools::Rectangle >* _pVector, OUString* _pDisplayText, const Size* i_pDeviceSize ) const
+{
+ OUString rPStr = _rStr;
+ DrawTextFlags nPStyle = _nStyle;
+
+ bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
+
+ if (autoacc && !mbShowAccelerator)
+ {
+ rPStr = GetNonMnemonicString( _rStr );
+ nPStyle &= ~DrawTextFlags::HideMnemonic;
+ }
+
+ if( !GetReferenceDevice() || ( GetReferenceDevice() == &_rTargetDevice ) )
+ {
+ const tools::Rectangle aRet = _rTargetDevice.GetTextRect(rRect, rPStr, nPStyle);
+ _rTargetDevice.DrawText(aRet, rPStr, nPStyle, _pVector, _pDisplayText);
+ return aRet;
+ }
+
+ ControlTextRenderer aRenderer( *this, _rTargetDevice, *GetReferenceDevice() );
+ return aRenderer.DrawText(rRect, rPStr, nPStyle, _pVector, _pDisplayText, i_pDeviceSize);
+}
+
+tools::Rectangle Control::GetControlTextRect( OutputDevice& _rTargetDevice, const tools::Rectangle & rRect,
+ const OUString& _rStr, DrawTextFlags _nStyle, Size* o_pDeviceSize ) const
+{
+ OUString rPStr = _rStr;
+ DrawTextFlags nPStyle = _nStyle;
+
+ bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
+
+ if (autoacc && !mbShowAccelerator)
+ {
+ rPStr = GetNonMnemonicString( _rStr );
+ nPStyle &= ~DrawTextFlags::HideMnemonic;
+ }
+
+ if ( !GetReferenceDevice() || ( GetReferenceDevice() == &_rTargetDevice ) )
+ {
+ tools::Rectangle aRet = _rTargetDevice.GetTextRect( rRect, rPStr, nPStyle );
+ if (o_pDeviceSize)
+ {
+ *o_pDeviceSize = aRet.GetSize();
+ }
+ return aRet;
+ }
+
+ ControlTextRenderer aRenderer( *this, _rTargetDevice, *GetReferenceDevice() );
+ return aRenderer.GetTextRect(rRect, rPStr, nPStyle, o_pDeviceSize);
+}
+
+Font
+Control::GetUnzoomedControlPointFont() const
+{
+ Font aFont(GetCanonicalFont(GetSettings().GetStyleSettings()));
+ if (IsControlFont())
+ aFont.Merge(GetControlFont());
+ return aFont;
+}
+
+void Control::LogicMouseButtonDown(const MouseEvent& rEvent)
+{
+ MouseButtonDown(rEvent);
+}
+
+void Control::LogicMouseButtonUp(const MouseEvent& rEvent)
+{
+ MouseButtonUp(rEvent);
+}
+
+void Control::LogicMouseMove(const MouseEvent& rEvent)
+{
+ MouseMove(rEvent);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/edit.cxx b/vcl/source/control/edit.cxx
new file mode 100644
index 000000000..72b55c528
--- /dev/null
+++ b/vcl/source/control/edit.cxx
@@ -0,0 +1,2929 @@
+/* -*- 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 <vcl/builder.hxx>
+#include <vcl/event.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/specialchars.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/transfer.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/ptrstyle.hxx>
+
+#include <window.h>
+#include <svdata.hxx>
+#include <strings.hrc>
+
+#include <com/sun/star/i18n/BreakIterator.hpp>
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+
+#include <com/sun/star/i18n/InputSequenceChecker.hpp>
+#include <com/sun/star/i18n/InputSequenceCheckMode.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+
+#include <com/sun/star/uno/Any.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+
+#include <sot/exchange.hxx>
+#include <sot/formats.hxx>
+#include <sal/macros.h>
+#include <sal/log.hxx>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <vcl/unohelp2.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <tools/json_writer.hxx>
+
+#include <algorithm>
+#include <memory>
+#include <string_view>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+
+// - Redo
+// - if Tracking-Cancel recreate DefaultSelection
+
+static FncGetSpecialChars pImplFncGetSpecialChars = nullptr;
+
+#define EDIT_ALIGN_LEFT 1
+#define EDIT_ALIGN_CENTER 2
+#define EDIT_ALIGN_RIGHT 3
+
+#define EDIT_DEL_LEFT 1
+#define EDIT_DEL_RIGHT 2
+
+#define EDIT_DELMODE_SIMPLE 11
+#define EDIT_DELMODE_RESTOFWORD 12
+#define EDIT_DELMODE_RESTOFCONTENT 13
+
+struct DDInfo
+{
+ vcl::Cursor aCursor;
+ Selection aDndStartSel;
+ sal_Int32 nDropPos;
+ bool bStarterOfDD;
+ bool bDroppedInMe;
+ bool bVisCursor;
+ bool bIsStringSupported;
+
+ DDInfo()
+ {
+ aCursor.SetStyle( CURSOR_SHADOW );
+ nDropPos = 0;
+ bStarterOfDD = false;
+ bDroppedInMe = false;
+ bVisCursor = false;
+ bIsStringSupported = false;
+ }
+};
+
+struct Impl_IMEInfos
+{
+ OUString aOldTextAfterStartPos;
+ std::unique_ptr<ExtTextInputAttr[]>
+ pAttribs;
+ sal_Int32 nPos;
+ sal_Int32 nLen;
+ bool bCursor;
+ bool bWasCursorOverwrite;
+
+ Impl_IMEInfos(sal_Int32 nPos, const OUString& rOldTextAfterStartPos);
+
+ void CopyAttribs(const ExtTextInputAttr* pA, sal_Int32 nL);
+ void DestroyAttribs();
+};
+
+Impl_IMEInfos::Impl_IMEInfos(sal_Int32 nP, const OUString& rOldTextAfterStartPos)
+ : aOldTextAfterStartPos(rOldTextAfterStartPos),
+ nPos(nP),
+ nLen(0),
+ bCursor(true),
+ bWasCursorOverwrite(false)
+{
+}
+
+void Impl_IMEInfos::CopyAttribs(const ExtTextInputAttr* pA, sal_Int32 nL)
+{
+ nLen = nL;
+ pAttribs.reset(new ExtTextInputAttr[ nL ]);
+ memcpy( pAttribs.get(), pA, nL*sizeof(ExtTextInputAttr) );
+}
+
+void Impl_IMEInfos::DestroyAttribs()
+{
+ pAttribs.reset();
+ nLen = 0;
+}
+
+Edit::Edit( WindowType nType )
+ : Control( nType )
+{
+ ImplInitEditData();
+}
+
+Edit::Edit( vcl::Window* pParent, WinBits nStyle )
+ : Control( WindowType::EDIT )
+{
+ ImplInitEditData();
+ ImplInit( pParent, nStyle );
+}
+
+void Edit::SetWidthInChars(sal_Int32 nWidthInChars)
+{
+ if (mnWidthInChars != nWidthInChars)
+ {
+ mnWidthInChars = nWidthInChars;
+ queue_resize();
+ }
+}
+
+void Edit::setMaxWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != mnMaxWidthChars)
+ {
+ mnMaxWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+bool Edit::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "width-chars")
+ SetWidthInChars(rValue.toInt32());
+ else if (rKey == "max-width-chars")
+ setMaxWidthChars(rValue.toInt32());
+ else if (rKey == "max-length")
+ {
+ sal_Int32 nTextLen = rValue.toInt32();
+ SetMaxTextLen(nTextLen == 0 ? EDIT_NOLIMIT : nTextLen);
+ }
+ else if (rKey == "editable")
+ {
+ SetReadOnly(!toBool(rValue));
+ }
+ else if (rKey == "overwrite-mode")
+ {
+ SetInsertMode(!toBool(rValue));
+ }
+ else if (rKey == "visibility")
+ {
+ mbPassword = false;
+ if (!toBool(rValue))
+ mbPassword = true;
+ }
+ else if (rKey == "placeholder-text")
+ SetPlaceholderText(rValue);
+ else if (rKey == "shadow-type")
+ {
+ if (GetStyle() & WB_BORDER)
+ SetBorderStyle(rValue == "none" ? WindowBorderStyle::MONO : WindowBorderStyle::NORMAL);
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+Edit::~Edit()
+{
+ disposeOnce();
+}
+
+void Edit::dispose()
+{
+ mpUIBuilder.reset();
+ mpDDInfo.reset();
+
+ vcl::Cursor* pCursor = GetCursor();
+ if ( pCursor )
+ {
+ SetCursor( nullptr );
+ delete pCursor;
+ }
+
+ mpIMEInfos.reset();
+
+ if ( mxDnDListener.is() )
+ {
+ if ( GetDragGestureRecognizer().is() )
+ {
+ uno::Reference< datatransfer::dnd::XDragGestureListener> xDGL( mxDnDListener, uno::UNO_QUERY );
+ GetDragGestureRecognizer()->removeDragGestureListener( xDGL );
+ }
+ if ( GetDropTarget().is() )
+ {
+ uno::Reference< datatransfer::dnd::XDropTargetListener> xDTL( mxDnDListener, uno::UNO_QUERY );
+ GetDropTarget()->removeDropTargetListener( xDTL );
+ }
+
+ mxDnDListener->disposing( lang::EventObject() ); // #95154# #96585# Empty Source means it's the Client
+ mxDnDListener.clear();
+ }
+
+ SetType(WindowType::WINDOW);
+
+ mpSubEdit.disposeAndClear();
+ Control::dispose();
+}
+
+void Edit::ImplInitEditData()
+{
+ mpSubEdit = VclPtr<Edit>();
+ mpFilterText = nullptr;
+ mnXOffset = 0;
+ mnAlign = EDIT_ALIGN_LEFT;
+ mnMaxTextLen = EDIT_NOLIMIT;
+ mnWidthInChars = -1;
+ mnMaxWidthChars = -1;
+ mbInternModified = false;
+ mbReadOnly = false;
+ mbInsertMode = true;
+ mbClickedInSelection = false;
+ mbActivePopup = false;
+ mbIsSubEdit = false;
+ mbForceControlBackground = false;
+ mbPassword = false;
+ mpDDInfo = nullptr;
+ mpIMEInfos = nullptr;
+ mcEchoChar = 0;
+
+ // no default mirroring for Edit controls
+ // note: controls that use a subedit will revert this (SpinField, ComboBox)
+ EnableRTL( false );
+
+ mxDnDListener = new vcl::unohelper::DragAndDropWrapper( this );
+}
+
+bool Edit::ImplUseNativeBorder(vcl::RenderContext const & rRenderContext, WinBits nStyle) const
+{
+ bool bRet = rRenderContext.IsNativeControlSupported(ImplGetNativeControlType(),
+ ControlPart::HasBackgroundTexture)
+ && ((nStyle & WB_BORDER) && !(nStyle & WB_NOBORDER));
+ if (!bRet && mbIsSubEdit)
+ {
+ vcl::Window* pWindow = GetParent();
+ nStyle = pWindow->GetStyle();
+ bRet = pWindow->IsNativeControlSupported(ImplGetNativeControlType(),
+ ControlPart::HasBackgroundTexture)
+ && ((nStyle & WB_BORDER) && !(nStyle & WB_NOBORDER));
+ }
+ return bRet;
+}
+
+void Edit::ImplInit(vcl::Window* pParent, WinBits nStyle)
+{
+ nStyle = ImplInitStyle(nStyle);
+
+ if (!(nStyle & (WB_CENTER | WB_RIGHT)))
+ nStyle |= WB_LEFT;
+
+ Control::ImplInit(pParent, nStyle, nullptr);
+
+ mbReadOnly = (nStyle & WB_READONLY) != 0;
+
+ mnAlign = EDIT_ALIGN_LEFT;
+
+ // hack: right align until keyinput and cursor travelling works
+ if( IsRTLEnabled() )
+ mnAlign = EDIT_ALIGN_RIGHT;
+
+ if ( nStyle & WB_RIGHT )
+ mnAlign = EDIT_ALIGN_RIGHT;
+ else if ( nStyle & WB_CENTER )
+ mnAlign = EDIT_ALIGN_CENTER;
+
+ SetCursor( new vcl::Cursor );
+
+ SetPointer( PointerStyle::Text );
+ ApplySettings(*GetOutDev());
+
+ uno::Reference< datatransfer::dnd::XDragGestureListener> xDGL( mxDnDListener, uno::UNO_QUERY );
+ uno::Reference< datatransfer::dnd::XDragGestureRecognizer > xDGR = GetDragGestureRecognizer();
+ if ( xDGR.is() )
+ {
+ xDGR->addDragGestureListener( xDGL );
+ uno::Reference< datatransfer::dnd::XDropTargetListener> xDTL( mxDnDListener, uno::UNO_QUERY );
+ GetDropTarget()->addDropTargetListener( xDTL );
+ GetDropTarget()->setActive( true );
+ GetDropTarget()->setDefaultActions( datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE );
+ }
+}
+
+WinBits Edit::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+
+ return nStyle;
+}
+
+bool Edit::IsCharInput( const KeyEvent& rKeyEvent )
+{
+ // In the future we must use new Unicode functions for this
+ sal_Unicode cCharCode = rKeyEvent.GetCharCode();
+ return ((cCharCode >= 32) && (cCharCode != 127) &&
+ !rKeyEvent.GetKeyCode().IsMod3() &&
+ !rKeyEvent.GetKeyCode().IsMod2() &&
+ !rKeyEvent.GetKeyCode().IsMod1() );
+}
+
+void Edit::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ Control::ApplySettings(rRenderContext);
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ const vcl::Font& aFont = rStyleSettings.GetFieldFont();
+ ApplyControlFont(rRenderContext, aFont);
+
+ ImplClearLayoutData();
+
+ Color aTextColor = rStyleSettings.GetFieldTextColor();
+ ApplyControlForeground(rRenderContext, aTextColor);
+
+ if (IsControlBackground())
+ {
+ rRenderContext.SetBackground(GetControlBackground());
+ rRenderContext.SetFillColor(GetControlBackground());
+
+ if (ImplUseNativeBorder(rRenderContext, GetStyle()))
+ {
+ // indicates that no non-native drawing of background should take place
+ mpWindowImpl->mnNativeBackground = ControlPart::Entire;
+ }
+ }
+ else if (ImplUseNativeBorder(rRenderContext, GetStyle()))
+ {
+ // Transparent background
+ rRenderContext.SetBackground();
+ rRenderContext.SetFillColor();
+ }
+ else
+ {
+ rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
+ rRenderContext.SetFillColor(rStyleSettings.GetFieldColor());
+ }
+}
+
+tools::Long Edit::ImplGetExtraXOffset() const
+{
+ // MT 09/2002: nExtraOffsetX should become a member, instead of checking every time,
+ // but I need an incompatible update for this...
+ // #94095# Use extra offset only when edit has a border
+ tools::Long nExtraOffset = 0;
+ if( ( GetStyle() & WB_BORDER ) || ( mbIsSubEdit && ( GetParent()->GetStyle() & WB_BORDER ) ) )
+ nExtraOffset = 2;
+
+ return nExtraOffset;
+}
+
+tools::Long Edit::ImplGetExtraYOffset() const
+{
+ tools::Long nExtraOffset = 0;
+ ControlType eCtrlType = ImplGetNativeControlType();
+ if (eCtrlType != ControlType::EditboxNoBorder)
+ {
+ // add some space between text entry and border
+ nExtraOffset = 2;
+ }
+ return nExtraOffset;
+}
+
+OUString Edit::ImplGetText() const
+{
+ if ( mcEchoChar || mbPassword )
+ {
+ sal_Unicode cEchoChar;
+ if ( mcEchoChar )
+ cEchoChar = mcEchoChar;
+ else
+ cEchoChar = u'\x2022';
+ OUStringBuffer aText(maText.getLength());
+ comphelper::string::padToLength(aText, maText.getLength(), cEchoChar);
+ return aText.makeStringAndClear();
+ }
+ else
+ return maText.toString();
+}
+
+void Edit::ImplInvalidateOrRepaint()
+{
+ if( IsPaintTransparent() )
+ {
+ Invalidate();
+ // FIXME: this is currently only on macOS
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects )
+ PaintImmediately();
+ }
+ else
+ Invalidate();
+}
+
+tools::Long Edit::ImplGetTextYPosition() const
+{
+ if ( GetStyle() & WB_TOP )
+ return ImplGetExtraXOffset();
+ else if ( GetStyle() & WB_BOTTOM )
+ return GetOutputSizePixel().Height() - GetTextHeight() - ImplGetExtraXOffset();
+ return ( GetOutputSizePixel().Height() - GetTextHeight() ) / 2;
+}
+
+void Edit::ImplRepaint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRectangle)
+{
+ if (!IsReallyVisible())
+ return;
+
+ ApplySettings(rRenderContext);
+
+ const OUString aText = ImplGetText();
+ const sal_Int32 nLen = aText.getLength();
+
+ sal_Int32 nDXBuffer[256];
+ std::unique_ptr<sal_Int32[]> pDXBuffer;
+ sal_Int32* pDX = nDXBuffer;
+
+ if (nLen)
+ {
+ if (o3tl::make_unsigned(2 * nLen) > SAL_N_ELEMENTS(nDXBuffer))
+ {
+ pDXBuffer.reset(new sal_Int32[2 * (nLen + 1)]);
+ pDX = pDXBuffer.get();
+ }
+
+ GetOutDev()->GetCaretPositions(aText, pDX, 0, nLen);
+ }
+
+ tools::Long nTH = GetTextHeight();
+ Point aPos(mnXOffset, ImplGetTextYPosition());
+
+ vcl::Cursor* pCursor = GetCursor();
+ bool bVisCursor = pCursor && pCursor->IsVisible();
+ if (pCursor)
+ pCursor->Hide();
+
+ ImplClearBackground(rRenderContext, rRectangle, 0, GetOutputSizePixel().Width()-1);
+
+ bool bPaintPlaceholderText = aText.isEmpty() && !maPlaceholderText.isEmpty();
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ if (!IsEnabled() || bPaintPlaceholderText)
+ rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
+
+ // Set background color of the normal text
+ if (mbForceControlBackground && IsControlBackground())
+ {
+ // check if we need to set ControlBackground even in NWF case
+ rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR);
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor(GetControlBackground());
+ rRenderContext.DrawRect(tools::Rectangle(aPos, Size(GetOutputSizePixel().Width() - 2 * mnXOffset, GetOutputSizePixel().Height())));
+ rRenderContext.Pop();
+
+ rRenderContext.SetTextFillColor(GetControlBackground());
+ }
+ else if (IsPaintTransparent() || ImplUseNativeBorder(rRenderContext, GetStyle()))
+ rRenderContext.SetTextFillColor();
+ else
+ rRenderContext.SetTextFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
+
+ ImplPaintBorder(rRenderContext);
+
+ bool bDrawSelection = maSelection.Len() && (HasFocus() || (GetStyle() & WB_NOHIDESELECTION) || mbActivePopup);
+
+ aPos.setX( mnXOffset + ImplGetExtraXOffset() );
+ if (bPaintPlaceholderText)
+ {
+ rRenderContext.DrawText(aPos, maPlaceholderText);
+ }
+ else if (!bDrawSelection && !mpIMEInfos)
+ {
+ rRenderContext.DrawText(aPos, aText, 0, nLen);
+ }
+ else
+ {
+ // save graphics state
+ rRenderContext.Push();
+ // first calculate highlighted and non highlighted clip regions
+ vcl::Region aHighlightClipRegion;
+ vcl::Region aNormalClipRegion;
+ Selection aTmpSel(maSelection);
+ aTmpSel.Justify();
+ // selection is highlighted
+ for(sal_Int32 i = 0; i < nLen; ++i)
+ {
+ tools::Rectangle aRect(aPos, Size(10, nTH));
+ aRect.SetLeft( pDX[2 * i] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.SetRight( pDX[2 * i + 1] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.Justify();
+ bool bHighlight = false;
+ if (i >= aTmpSel.Min() && i < aTmpSel.Max())
+ bHighlight = true;
+
+ if (mpIMEInfos && mpIMEInfos->pAttribs &&
+ i >= mpIMEInfos->nPos && i < (mpIMEInfos->nPos+mpIMEInfos->nLen) &&
+ (mpIMEInfos->pAttribs[i - mpIMEInfos->nPos] & ExtTextInputAttr::Highlight))
+ {
+ bHighlight = true;
+ }
+
+ if (bHighlight)
+ aHighlightClipRegion.Union(aRect);
+ else
+ aNormalClipRegion.Union(aRect);
+ }
+ // draw normal text
+ Color aNormalTextColor = rRenderContext.GetTextColor();
+ rRenderContext.SetClipRegion(aNormalClipRegion);
+
+ if (IsPaintTransparent())
+ rRenderContext.SetTextFillColor();
+ else
+ {
+ // Set background color when part of the text is selected
+ if (ImplUseNativeBorder(rRenderContext, GetStyle()))
+ {
+ if( mbForceControlBackground && IsControlBackground() )
+ rRenderContext.SetTextFillColor(GetControlBackground());
+ else
+ rRenderContext.SetTextFillColor();
+ }
+ else
+ {
+ rRenderContext.SetTextFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
+ }
+ }
+ rRenderContext.DrawText(aPos, aText, 0, nLen);
+
+ // draw highlighted text
+ rRenderContext.SetClipRegion(aHighlightClipRegion);
+ rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
+ rRenderContext.SetTextFillColor(rStyleSettings.GetHighlightColor());
+ rRenderContext.DrawText(aPos, aText, 0, nLen);
+
+ // if IME info exists loop over portions and output different font attributes
+ if (mpIMEInfos && mpIMEInfos->pAttribs)
+ {
+ for(int n = 0; n < 2; n++)
+ {
+ vcl::Region aRegion;
+ if (n == 0)
+ {
+ rRenderContext.SetTextColor(aNormalTextColor);
+ if (IsPaintTransparent())
+ rRenderContext.SetTextFillColor();
+ else
+ rRenderContext.SetTextFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
+ aRegion = aNormalClipRegion;
+ }
+ else
+ {
+ rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
+ rRenderContext.SetTextFillColor(rStyleSettings.GetHighlightColor());
+ aRegion = aHighlightClipRegion;
+ }
+
+ for(int i = 0; i < mpIMEInfos->nLen; )
+ {
+ ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[i];
+ vcl::Region aClip;
+ int nIndex = i;
+ while (nIndex < mpIMEInfos->nLen && mpIMEInfos->pAttribs[nIndex] == nAttr) // #112631# check nIndex before using it
+ {
+ tools::Rectangle aRect( aPos, Size( 10, nTH ) );
+ aRect.SetLeft( pDX[2 * (nIndex + mpIMEInfos->nPos)] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.SetRight( pDX[2 * (nIndex + mpIMEInfos->nPos) + 1] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.Justify();
+ aClip.Union(aRect);
+ nIndex++;
+ }
+ i = nIndex;
+ aClip.Intersect(aRegion);
+ if (!aClip.IsEmpty() && nAttr != ExtTextInputAttr::NONE)
+ {
+ vcl::Font aFont = rRenderContext.GetFont();
+ if (nAttr & ExtTextInputAttr::Underline)
+ aFont.SetUnderline(LINESTYLE_SINGLE);
+ else if (nAttr & ExtTextInputAttr::DoubleUnderline)
+ aFont.SetUnderline(LINESTYLE_DOUBLE);
+ else if (nAttr & ExtTextInputAttr::BoldUnderline)
+ aFont.SetUnderline( LINESTYLE_BOLD);
+ else if (nAttr & ExtTextInputAttr::DottedUnderline)
+ aFont.SetUnderline( LINESTYLE_DOTTED);
+ else if (nAttr & ExtTextInputAttr::DashDotUnderline)
+ aFont.SetUnderline( LINESTYLE_DASHDOT);
+ else if (nAttr & ExtTextInputAttr::GrayWaveline)
+ {
+ aFont.SetUnderline(LINESTYLE_WAVE);
+ rRenderContext.SetTextLineColor(COL_LIGHTGRAY);
+ }
+ rRenderContext.SetFont(aFont);
+
+ if (nAttr & ExtTextInputAttr::RedText)
+ rRenderContext.SetTextColor(COL_RED);
+ else if (nAttr & ExtTextInputAttr::HalfToneText)
+ rRenderContext.SetTextColor(COL_LIGHTGRAY);
+
+ rRenderContext.SetClipRegion(aClip);
+ rRenderContext.DrawText(aPos, aText, 0, nLen);
+ }
+ }
+ }
+ }
+
+ // restore graphics state
+ rRenderContext.Pop();
+ }
+
+ if (bVisCursor && (!mpIMEInfos || mpIMEInfos->bCursor))
+ pCursor->Show();
+}
+
+void Edit::ImplDelete( const Selection& rSelection, sal_uInt8 nDirection, sal_uInt8 nMode )
+{
+ const sal_Int32 nTextLen = ImplGetText().getLength();
+
+ // deleting possible?
+ if ( !rSelection.Len() &&
+ (((rSelection.Min() == 0) && (nDirection == EDIT_DEL_LEFT)) ||
+ ((rSelection.Max() == nTextLen) && (nDirection == EDIT_DEL_RIGHT))) )
+ return;
+
+ ImplClearLayoutData();
+
+ Selection aSelection( rSelection );
+ aSelection.Justify();
+
+ if ( !aSelection.Len() )
+ {
+ uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
+ if ( nDirection == EDIT_DEL_LEFT )
+ {
+ if ( nMode == EDIT_DELMODE_RESTOFWORD )
+ {
+ const OUString sText = maText.toString();
+ i18n::Boundary aBoundary = xBI->getWordBoundary( sText, aSelection.Min(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
+ auto startPos = aBoundary.startPos;
+ if ( startPos == aSelection.Min() )
+ {
+ aBoundary = xBI->previousWord( sText, aSelection.Min(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
+ startPos = std::max(aBoundary.startPos, sal_Int32(0));
+ }
+ aSelection.Min() = startPos;
+ }
+ else if ( nMode == EDIT_DELMODE_RESTOFCONTENT )
+ {
+ aSelection.Min() = 0;
+ }
+ else
+ {
+ sal_Int32 nCount = 1;
+ aSelection.Min() = xBI->previousCharacters( maText.toString(), aSelection.Min(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
+ }
+ }
+ else
+ {
+ if ( nMode == EDIT_DELMODE_RESTOFWORD )
+ {
+ i18n::Boundary aBoundary = xBI->nextWord( maText.toString(), aSelection.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
+ aSelection.Max() = aBoundary.startPos;
+ }
+ else if ( nMode == EDIT_DELMODE_RESTOFCONTENT )
+ {
+ aSelection.Max() = nTextLen;
+ }
+ else
+ {
+ sal_Int32 nCount = 1;
+ aSelection.Max() = xBI->nextCharacters( maText.toString(), aSelection.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
+ }
+ }
+ }
+
+ const auto nSelectionMin = aSelection.Min();
+ maText.remove( nSelectionMin, aSelection.Len() );
+ maSelection.Min() = nSelectionMin;
+ maSelection.Max() = nSelectionMin;
+ ImplAlignAndPaint();
+ mbInternModified = true;
+}
+
+OUString Edit::ImplGetValidString( const OUString& rString )
+{
+ OUString aValidString = rString.replaceAll("\n", "").replaceAll("\r", "");
+ aValidString = aValidString.replace('\t', ' ');
+ return aValidString;
+}
+
+uno::Reference <i18n::XBreakIterator> const& Edit::ImplGetBreakIterator()
+{
+ if (!mxBreakIterator)
+ mxBreakIterator = i18n::BreakIterator::create(::comphelper::getProcessComponentContext());
+ return mxBreakIterator;
+}
+
+uno::Reference <i18n::XExtendedInputSequenceChecker> const& Edit::ImplGetInputSequenceChecker()
+{
+ if (!mxISC.is())
+ mxISC = i18n::InputSequenceChecker::create(::comphelper::getProcessComponentContext());
+ return mxISC;
+}
+
+void Edit::ShowTruncationWarning(weld::Widget* pParent)
+{
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent, VclMessageType::Warning,
+ VclButtonsType::Ok, VclResId(SV_EDIT_WARNING_STR)));
+ xBox->run();
+}
+
+bool Edit::ImplTruncateToMaxLen( OUString& rStr, sal_Int32 nSelectionLen ) const
+{
+ bool bWasTruncated = false;
+ if (maText.getLength() - nSelectionLen > mnMaxTextLen - rStr.getLength())
+ {
+ sal_Int32 nErasePos = mnMaxTextLen - maText.getLength() + nSelectionLen;
+ rStr = rStr.copy( 0, nErasePos );
+ bWasTruncated = true;
+ }
+ return bWasTruncated;
+}
+
+void Edit::ImplInsertText( const OUString& rStr, const Selection* pNewSel, bool bIsUserInput )
+{
+ Selection aSelection( maSelection );
+ aSelection.Justify();
+
+ OUString aNewText( ImplGetValidString( rStr ) );
+
+ // as below, if there's no selection, but we're in overwrite mode and not beyond
+ // the end of the existing text then that's like a selection of 1
+ auto nSelectionLen = aSelection.Len();
+ if (!nSelectionLen && !mbInsertMode && aSelection.Max() < maText.getLength())
+ nSelectionLen = 1;
+ ImplTruncateToMaxLen( aNewText, nSelectionLen );
+
+ ImplClearLayoutData();
+
+ if ( aSelection.Len() )
+ maText.remove( aSelection.Min(), aSelection.Len() );
+ else if (!mbInsertMode && aSelection.Max() < maText.getLength())
+ maText.remove( aSelection.Max(), 1 );
+
+ // take care of input-sequence-checking now
+ if (bIsUserInput && !rStr.isEmpty())
+ {
+ SAL_WARN_IF( rStr.getLength() != 1, "vcl", "unexpected string length. User input is expected to provide 1 char only!" );
+
+ // determine if input-sequence-checking should be applied or not
+
+ uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
+ bool bIsInputSequenceChecking = rStr.getLength() == 1 &&
+ officecfg::Office::Common::I18N::CTL::CTLFont::get() &&
+ officecfg::Office::Common::I18N::CTL::CTLSequenceChecking::get() &&
+ aSelection.Min() > 0 && /* first char needs not to be checked */
+ xBI.is() && i18n::ScriptType::COMPLEX == xBI->getScriptType( rStr, 0 );
+
+ if (bIsInputSequenceChecking)
+ {
+ uno::Reference < i18n::XExtendedInputSequenceChecker > xISC = ImplGetInputSequenceChecker();
+ if (xISC.is())
+ {
+ sal_Unicode cChar = rStr[0];
+ sal_Int32 nTmpPos = aSelection.Min();
+ sal_Int16 nCheckMode = officecfg::Office::Common::I18N::CTL::CTLSequenceCheckingRestricted::get()?
+ i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC;
+
+ // the text that needs to be checked is only the one
+ // before the current cursor position
+ const OUString aOldText( maText.getStr(), nTmpPos);
+ OUString aTmpText( aOldText );
+ if (officecfg::Office::Common::I18N::CTL::CTLSequenceCheckingTypeAndReplace::get())
+ {
+ xISC->correctInputSequence( aTmpText, nTmpPos - 1, cChar, nCheckMode );
+
+ // find position of first character that has changed
+ sal_Int32 nOldLen = aOldText.getLength();
+ sal_Int32 nTmpLen = aTmpText.getLength();
+ const sal_Unicode *pOldTxt = aOldText.getStr();
+ const sal_Unicode *pTmpTxt = aTmpText.getStr();
+ sal_Int32 nChgPos = 0;
+ while ( nChgPos < nOldLen && nChgPos < nTmpLen &&
+ pOldTxt[nChgPos] == pTmpTxt[nChgPos] )
+ ++nChgPos;
+
+ const OUString aChgText( aTmpText.copy( nChgPos ) );
+
+ // remove text from first pos to be changed to current pos
+ maText.remove( nChgPos, nTmpPos - nChgPos );
+
+ if (!aChgText.isEmpty())
+ {
+ aNewText = aChgText;
+ aSelection.Min() = nChgPos; // position for new text to be inserted
+ }
+ else
+ aNewText.clear();
+ }
+ else
+ {
+ // should the character be ignored (i.e. not get inserted) ?
+ if (!xISC->checkInputSequence( aOldText, nTmpPos - 1, cChar, nCheckMode ))
+ aNewText.clear();
+ }
+ }
+ }
+
+ // at this point now we will insert the non-empty text 'normally' some lines below...
+ }
+
+ if ( !aNewText.isEmpty() )
+ maText.insert( aSelection.Min(), aNewText );
+
+ if ( !pNewSel )
+ {
+ maSelection.Min() = aSelection.Min() + aNewText.getLength();
+ maSelection.Max() = maSelection.Min();
+ }
+ else
+ {
+ maSelection = *pNewSel;
+ if ( maSelection.Min() > maText.getLength() )
+ maSelection.Min() = maText.getLength();
+ if ( maSelection.Max() > maText.getLength() )
+ maSelection.Max() = maText.getLength();
+ }
+
+ ImplAlignAndPaint();
+ mbInternModified = true;
+}
+
+void Edit::ImplSetText( const OUString& rText, const Selection* pNewSelection )
+{
+ // we delete text by "selecting" the old text completely then calling InsertText; this is flicker free
+ if ( ( rText.getLength() > mnMaxTextLen ) ||
+ ( std::u16string_view(rText) == std::u16string_view(maText.getStr(), maText.getLength())
+ && (!pNewSelection || (*pNewSelection == maSelection)) ) )
+ return;
+
+ ImplClearLayoutData();
+ maSelection.Min() = 0;
+ maSelection.Max() = maText.getLength();
+ if ( mnXOffset || HasPaintEvent() )
+ {
+ mnXOffset = 0;
+ maText = ImplGetValidString( rText );
+
+ // #i54929# recalculate mnXOffset before ImplSetSelection,
+ // else cursor ends up in wrong position
+ ImplAlign();
+
+ if ( pNewSelection )
+ ImplSetSelection( *pNewSelection, false );
+
+ if ( mnXOffset && !pNewSelection )
+ maSelection.Max() = 0;
+
+ Invalidate();
+ }
+ else
+ ImplInsertText( rText, pNewSelection );
+
+ CallEventListeners( VclEventId::EditModify );
+}
+
+ControlType Edit::ImplGetNativeControlType() const
+{
+ ControlType nCtrl = ControlType::Generic;
+ const vcl::Window* pControl = mbIsSubEdit ? GetParent() : this;
+
+ switch (pControl->GetType())
+ {
+ case WindowType::COMBOBOX:
+ case WindowType::PATTERNBOX:
+ case WindowType::NUMERICBOX:
+ case WindowType::METRICBOX:
+ case WindowType::CURRENCYBOX:
+ case WindowType::DATEBOX:
+ case WindowType::TIMEBOX:
+ case WindowType::LONGCURRENCYBOX:
+ nCtrl = ControlType::Combobox;
+ break;
+
+ case WindowType::MULTILINEEDIT:
+ if ( GetWindow( GetWindowType::Border ) != this )
+ nCtrl = ControlType::MultilineEditbox;
+ else
+ nCtrl = ControlType::EditboxNoBorder;
+ break;
+
+ case WindowType::EDIT:
+ case WindowType::PATTERNFIELD:
+ case WindowType::METRICFIELD:
+ case WindowType::CURRENCYFIELD:
+ case WindowType::DATEFIELD:
+ case WindowType::TIMEFIELD:
+ case WindowType::SPINFIELD:
+ case WindowType::FORMATTEDFIELD:
+ if (pControl->GetStyle() & WB_SPIN)
+ nCtrl = ControlType::Spinbox;
+ else
+ {
+ if (GetWindow(GetWindowType::Border) != this)
+ nCtrl = ControlType::Editbox;
+ else
+ nCtrl = ControlType::EditboxNoBorder;
+ }
+ break;
+
+ default:
+ nCtrl = ControlType::Editbox;
+ }
+ return nCtrl;
+}
+
+void Edit::ImplClearBackground(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRectangle, tools::Long nXStart, tools::Long nXEnd )
+{
+ /*
+ * note: at this point the cursor must be switched off already
+ */
+ tools::Rectangle aRect(Point(), GetOutputSizePixel());
+ aRect.SetLeft( nXStart );
+ aRect.SetRight( nXEnd );
+
+ if( !(ImplUseNativeBorder(rRenderContext, GetStyle()) || IsPaintTransparent()))
+ rRenderContext.Erase(aRect);
+ else if (SupportsDoubleBuffering() && mbIsSubEdit)
+ {
+ // ImplPaintBorder() is a NOP, we have a native border, and this is a sub-edit of a control.
+ // That means we have to draw the parent native widget to paint the edit area to clear our background.
+ vcl::PaintBufferGuard g(ImplGetWindowImpl()->mpFrameData, GetParent());
+ GetParent()->Paint(rRenderContext, rRectangle);
+ }
+}
+
+void Edit::ImplPaintBorder(vcl::RenderContext const & rRenderContext)
+{
+ // this is not needed when double-buffering
+ if (SupportsDoubleBuffering())
+ return;
+
+ if (!(ImplUseNativeBorder(rRenderContext, GetStyle()) || IsPaintTransparent()))
+ return;
+
+ // draw the inner part by painting the whole control using its border window
+ vcl::Window* pBorder = GetWindow(GetWindowType::Border);
+ if (pBorder == this)
+ {
+ // we have no border, use parent
+ vcl::Window* pControl = mbIsSubEdit ? GetParent() : this;
+ pBorder = pControl->GetWindow(GetWindowType::Border);
+ if (pBorder == this)
+ pBorder = GetParent();
+ }
+
+ if (!pBorder)
+ return;
+
+ // set proper clipping region to not overdraw the whole control
+ vcl::Region aClipRgn = GetPaintRegion();
+ if (!aClipRgn.IsNull())
+ {
+ // transform clipping region to border window's coordinate system
+ if (IsRTLEnabled() != pBorder->IsRTLEnabled() && AllSettings::GetLayoutRTL())
+ {
+ // need to mirror in case border is not RTL but edit is (or vice versa)
+
+ // mirror
+ tools::Rectangle aBounds(aClipRgn.GetBoundRect());
+ int xNew = GetOutputSizePixel().Width() - aBounds.GetWidth() - aBounds.Left();
+ aClipRgn.Move(xNew - aBounds.Left(), 0);
+
+ // move offset of border window
+ Point aBorderOffs = pBorder->ScreenToOutputPixel(OutputToScreenPixel(Point()));
+ aClipRgn.Move(aBorderOffs.X(), aBorderOffs.Y());
+ }
+ else
+ {
+ // normal case
+ Point aBorderOffs = pBorder->ScreenToOutputPixel(OutputToScreenPixel(Point()));
+ aClipRgn.Move(aBorderOffs.X(), aBorderOffs.Y());
+ }
+
+ vcl::Region oldRgn(pBorder->GetOutDev()->GetClipRegion());
+ pBorder->GetOutDev()->SetClipRegion(aClipRgn);
+
+ pBorder->Paint(*pBorder->GetOutDev(), tools::Rectangle());
+
+ pBorder->GetOutDev()->SetClipRegion(oldRgn);
+ }
+ else
+ {
+ pBorder->Paint(*pBorder->GetOutDev(), tools::Rectangle());
+ }
+}
+
+void Edit::ImplShowCursor( bool bOnlyIfVisible )
+{
+ if ( !IsUpdateMode() || ( bOnlyIfVisible && !IsReallyVisible() ) )
+ return;
+
+ vcl::Cursor* pCursor = GetCursor();
+ OUString aText = ImplGetText();
+
+ tools::Long nTextPos = 0;
+
+ sal_Int32 nDXBuffer[256];
+ std::unique_ptr<sal_Int32[]> pDXBuffer;
+ sal_Int32* pDX = nDXBuffer;
+
+ if( !aText.isEmpty() )
+ {
+ if( o3tl::make_unsigned(2*aText.getLength()) > SAL_N_ELEMENTS(nDXBuffer) )
+ {
+ pDXBuffer.reset(new sal_Int32[2*(aText.getLength()+1)]);
+ pDX = pDXBuffer.get();
+ }
+
+ GetOutDev()->GetCaretPositions( aText, pDX, 0, aText.getLength() );
+
+ if( maSelection.Max() < aText.getLength() )
+ nTextPos = pDX[ 2*maSelection.Max() ];
+ else
+ nTextPos = pDX[ 2*aText.getLength()-1 ];
+ }
+
+ tools::Long nCursorWidth = 0;
+ if ( !mbInsertMode && !maSelection.Len() && (maSelection.Max() < aText.getLength()) )
+ nCursorWidth = GetTextWidth(aText, maSelection.Max(), 1);
+ tools::Long nCursorPosX = nTextPos + mnXOffset + ImplGetExtraXOffset();
+
+ // cursor should land in visible area
+ const Size aOutSize = GetOutputSizePixel();
+ if ( (nCursorPosX < 0) || (nCursorPosX >= aOutSize.Width()) )
+ {
+ tools::Long nOldXOffset = mnXOffset;
+
+ if ( nCursorPosX < 0 )
+ {
+ mnXOffset = - nTextPos;
+ tools::Long nMaxX = 0;
+ mnXOffset += aOutSize.Width() / 5;
+ if ( mnXOffset > nMaxX )
+ mnXOffset = nMaxX;
+ }
+ else
+ {
+ mnXOffset = (aOutSize.Width()-ImplGetExtraXOffset()) - nTextPos;
+ // Something more?
+ if ( (aOutSize.Width()-ImplGetExtraXOffset()) < nTextPos )
+ {
+ tools::Long nMaxNegX = (aOutSize.Width()-ImplGetExtraXOffset()) - GetTextWidth( aText );
+ mnXOffset -= aOutSize.Width() / 5;
+ if ( mnXOffset < nMaxNegX ) // both negative...
+ mnXOffset = nMaxNegX;
+ }
+ }
+
+ nCursorPosX = nTextPos + mnXOffset + ImplGetExtraXOffset();
+ if ( nCursorPosX == aOutSize.Width() ) // then invisible...
+ nCursorPosX--;
+
+ if ( mnXOffset != nOldXOffset )
+ ImplInvalidateOrRepaint();
+ }
+
+ const tools::Long nTextHeight = GetTextHeight();
+ const tools::Long nCursorPosY = ImplGetTextYPosition();
+ if (pCursor)
+ {
+ pCursor->SetPos( Point( nCursorPosX, nCursorPosY ) );
+ pCursor->SetSize( Size( nCursorWidth, nTextHeight ) );
+ pCursor->Show();
+ }
+}
+
+void Edit::ImplAlign()
+{
+ if (mnAlign == EDIT_ALIGN_LEFT && !mnXOffset)
+ {
+ // short circuit common case and avoid slow GetTextWidth() calc
+ return;
+ }
+
+ tools::Long nTextWidth = GetTextWidth( ImplGetText() );
+ tools::Long nOutWidth = GetOutputSizePixel().Width();
+
+ if ( mnAlign == EDIT_ALIGN_LEFT )
+ {
+ if (nTextWidth < nOutWidth)
+ mnXOffset = 0;
+ }
+ else if ( mnAlign == EDIT_ALIGN_RIGHT )
+ {
+ tools::Long nMinXOffset = nOutWidth - nTextWidth - 1 - ImplGetExtraXOffset();
+ bool bRTL = IsRTLEnabled();
+ if( mbIsSubEdit && GetParent() )
+ bRTL = GetParent()->IsRTLEnabled();
+ if( bRTL )
+ {
+ if( nTextWidth < nOutWidth )
+ mnXOffset = nMinXOffset;
+ }
+ else
+ {
+ if( nTextWidth < nOutWidth )
+ mnXOffset = nMinXOffset;
+ else if ( mnXOffset < nMinXOffset )
+ mnXOffset = nMinXOffset;
+ }
+ }
+ else if( mnAlign == EDIT_ALIGN_CENTER )
+ {
+ // would be nicer with check while scrolling but then it's not centred in scrolled state
+ mnXOffset = (nOutWidth - nTextWidth) / 2;
+ }
+}
+
+void Edit::ImplAlignAndPaint()
+{
+ ImplAlign();
+ ImplInvalidateOrRepaint();
+ ImplShowCursor();
+}
+
+sal_Int32 Edit::ImplGetCharPos( const Point& rWindowPos ) const
+{
+ sal_Int32 nIndex = EDIT_NOLIMIT;
+ OUString aText = ImplGetText();
+
+ sal_Int32 nDXBuffer[256];
+ std::unique_ptr<sal_Int32[]> pDXBuffer;
+ sal_Int32* pDX = nDXBuffer;
+ if( o3tl::make_unsigned(2*aText.getLength()) > SAL_N_ELEMENTS(nDXBuffer) )
+ {
+ pDXBuffer.reset(new sal_Int32[2*(aText.getLength()+1)]);
+ pDX = pDXBuffer.get();
+ }
+
+ GetOutDev()->GetCaretPositions( aText, pDX, 0, aText.getLength() );
+ tools::Long nX = rWindowPos.X() - mnXOffset - ImplGetExtraXOffset();
+ for (sal_Int32 i = 0; i < aText.getLength(); aText.iterateCodePoints(&i))
+ {
+ if( (pDX[2*i] >= nX && pDX[2*i+1] <= nX) ||
+ (pDX[2*i+1] >= nX && pDX[2*i] <= nX))
+ {
+ nIndex = i;
+ if( pDX[2*i] < pDX[2*i+1] )
+ {
+ if( nX > (pDX[2*i]+pDX[2*i+1])/2 )
+ aText.iterateCodePoints(&nIndex);
+ }
+ else
+ {
+ if( nX < (pDX[2*i]+pDX[2*i+1])/2 )
+ aText.iterateCodePoints(&nIndex);
+ }
+ break;
+ }
+ }
+ if( nIndex == EDIT_NOLIMIT )
+ {
+ nIndex = 0;
+ sal_Int32 nFinalIndex = 0;
+ tools::Long nDiff = std::abs( pDX[0]-nX );
+ sal_Int32 i = 0;
+ if (!aText.isEmpty())
+ {
+ aText.iterateCodePoints(&i); //skip the first character
+ }
+ while (i < aText.getLength())
+ {
+ tools::Long nNewDiff = std::abs( pDX[2*i]-nX );
+
+ if( nNewDiff < nDiff )
+ {
+ nIndex = i;
+ nDiff = nNewDiff;
+ }
+
+ nFinalIndex = i;
+
+ aText.iterateCodePoints(&i);
+ }
+ if (nIndex == nFinalIndex && std::abs( pDX[2*nIndex+1] - nX ) < nDiff)
+ nIndex = EDIT_NOLIMIT;
+ }
+
+ return nIndex;
+}
+
+void Edit::ImplSetCursorPos( sal_Int32 nChar, bool bSelect )
+{
+ Selection aSelection( maSelection );
+ aSelection.Max() = nChar;
+ if ( !bSelect )
+ aSelection.Min() = aSelection.Max();
+ ImplSetSelection( aSelection );
+}
+
+void Edit::ImplCopyToSelectionClipboard()
+{
+ if ( GetSelection().Len() )
+ {
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
+ ImplCopy( aSelection );
+ }
+}
+
+void Edit::ImplCopy( uno::Reference< datatransfer::clipboard::XClipboard > const & rxClipboard )
+{
+ vcl::unohelper::TextDataObject::CopyStringTo( GetSelected(), rxClipboard );
+}
+
+void Edit::ImplPaste( uno::Reference< datatransfer::clipboard::XClipboard > const & rxClipboard )
+{
+ if ( !rxClipboard.is() )
+ return;
+
+ uno::Reference< datatransfer::XTransferable > xDataObj;
+
+ try
+ {
+ SolarMutexReleaser aReleaser;
+ xDataObj = rxClipboard->getContents();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ if ( !xDataObj.is() )
+ return;
+
+ datatransfer::DataFlavor aFlavor;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
+ try
+ {
+ uno::Any aData = xDataObj->getTransferData( aFlavor );
+ OUString aText;
+ aData >>= aText;
+
+ Selection aSelection(maSelection);
+ aSelection.Justify();
+ if (ImplTruncateToMaxLen(aText, aSelection.Len()))
+ ShowTruncationWarning(GetFrameWeld());
+
+ ReplaceSelected( aText );
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+void Edit::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( mpSubEdit )
+ {
+ Control::MouseButtonDown( rMEvt );
+ return;
+ }
+
+ sal_Int32 nCharPos = ImplGetCharPos( rMEvt.GetPosPixel() );
+ Selection aSelection( maSelection );
+ aSelection.Justify();
+
+ if ( rMEvt.GetClicks() < 4 )
+ {
+ mbClickedInSelection = false;
+ if ( rMEvt.GetClicks() == 3 )
+ {
+ ImplSetSelection( Selection( 0, EDIT_NOLIMIT) );
+ ImplCopyToSelectionClipboard();
+
+ }
+ else if ( rMEvt.GetClicks() == 2 )
+ {
+ uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
+ i18n::Boundary aBoundary = xBI->getWordBoundary( maText.toString(), aSelection.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
+ ImplSetSelection( Selection( aBoundary.startPos, aBoundary.endPos ) );
+ ImplCopyToSelectionClipboard();
+ }
+ else if ( !rMEvt.IsShift() && HasFocus() && aSelection.Contains( nCharPos ) )
+ mbClickedInSelection = true;
+ else if ( rMEvt.IsLeft() )
+ ImplSetCursorPos( nCharPos, rMEvt.IsShift() );
+
+ if ( !mbClickedInSelection && rMEvt.IsLeft() && ( rMEvt.GetClicks() == 1 ) )
+ StartTracking( StartTrackingFlags::ScrollRepeat );
+ }
+
+ GrabFocus();
+}
+
+void Edit::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ if ( mbClickedInSelection && rMEvt.IsLeft() )
+ {
+ sal_Int32 nCharPos = ImplGetCharPos( rMEvt.GetPosPixel() );
+ ImplSetCursorPos( nCharPos, false );
+ mbClickedInSelection = false;
+ }
+ else if ( rMEvt.IsMiddle() && !mbReadOnly &&
+ ( GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) )
+ {
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
+ ImplPaste( aSelection );
+ Modify();
+ }
+}
+
+void Edit::Tracking( const TrackingEvent& rTEvt )
+{
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ if ( mbClickedInSelection )
+ {
+ sal_Int32 nCharPos = ImplGetCharPos( rTEvt.GetMouseEvent().GetPosPixel() );
+ ImplSetCursorPos( nCharPos, false );
+ mbClickedInSelection = false;
+ }
+ else if ( rTEvt.GetMouseEvent().IsLeft() )
+ {
+ ImplCopyToSelectionClipboard();
+ }
+ }
+ else
+ {
+ if( !mbClickedInSelection )
+ {
+ sal_Int32 nCharPos = ImplGetCharPos( rTEvt.GetMouseEvent().GetPosPixel() );
+ ImplSetCursorPos( nCharPos, true );
+ }
+ }
+}
+
+bool Edit::ImplHandleKeyEvent( const KeyEvent& rKEvt )
+{
+ bool bDone = false;
+ sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
+ KeyFuncType eFunc = rKEvt.GetKeyCode().GetFunction();
+
+ mbInternModified = false;
+
+ if ( eFunc != KeyFuncType::DONTKNOW )
+ {
+ switch ( eFunc )
+ {
+ case KeyFuncType::CUT:
+ {
+ if ( !mbReadOnly && maSelection.Len() && !mbPassword )
+ {
+ Cut();
+ Modify();
+ bDone = true;
+ }
+ }
+ break;
+
+ case KeyFuncType::COPY:
+ {
+ if ( !mbPassword )
+ {
+ Copy();
+ bDone = true;
+ }
+ }
+ break;
+
+ case KeyFuncType::PASTE:
+ {
+ if ( !mbReadOnly )
+ {
+ Paste();
+ bDone = true;
+ }
+ }
+ break;
+
+ case KeyFuncType::UNDO:
+ {
+ if ( !mbReadOnly )
+ {
+ Undo();
+ bDone = true;
+ }
+ }
+ break;
+
+ default:
+ eFunc = KeyFuncType::DONTKNOW;
+ }
+ }
+
+ if ( !bDone && rKEvt.GetKeyCode().IsMod1() && !rKEvt.GetKeyCode().IsMod2() )
+ {
+ if ( nCode == KEY_A )
+ {
+ ImplSetSelection( Selection( 0, maText.getLength() ) );
+ bDone = true;
+ }
+ else if ( rKEvt.GetKeyCode().IsShift() && (nCode == KEY_S) )
+ {
+ if ( pImplFncGetSpecialChars )
+ {
+ Selection aSaveSel = GetSelection(); // if someone changes the selection in Get/LoseFocus, e.g. URL bar
+ OUString aChars = pImplFncGetSpecialChars( GetFrameWeld(), GetFont() );
+ SetSelection( aSaveSel );
+ if ( !aChars.isEmpty() )
+ {
+ ImplInsertText( aChars );
+ Modify();
+ }
+ bDone = true;
+ }
+ }
+ }
+
+ if ( eFunc == KeyFuncType::DONTKNOW && ! bDone )
+ {
+ switch ( nCode )
+ {
+ case css::awt::Key::SELECT_ALL:
+ {
+ ImplSetSelection( Selection( 0, maText.getLength() ) );
+ bDone = true;
+ }
+ break;
+
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ case KEY_HOME:
+ case KEY_END:
+ case css::awt::Key::MOVE_WORD_FORWARD:
+ case css::awt::Key::SELECT_WORD_FORWARD:
+ case css::awt::Key::MOVE_WORD_BACKWARD:
+ case css::awt::Key::SELECT_WORD_BACKWARD:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
+ case css::awt::Key::MOVE_TO_END_OF_LINE:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
+ case css::awt::Key::SELECT_TO_END_OF_LINE:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
+ case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
+ case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
+ case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
+ case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
+ {
+ if ( !rKEvt.GetKeyCode().IsMod2() )
+ {
+ ImplClearLayoutData();
+ uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
+
+ Selection aSel( maSelection );
+ bool bWord = rKEvt.GetKeyCode().IsMod1();
+ bool bSelect = rKEvt.GetKeyCode().IsShift();
+ bool bGoLeft = (nCode == KEY_LEFT);
+ bool bGoRight = (nCode == KEY_RIGHT);
+ bool bGoHome = (nCode == KEY_HOME);
+ bool bGoEnd = (nCode == KEY_END);
+
+ switch( nCode )
+ {
+ case css::awt::Key::MOVE_WORD_FORWARD:
+ bGoRight = bWord = true;break;
+ case css::awt::Key::SELECT_WORD_FORWARD:
+ bGoRight = bSelect = bWord = true;break;
+ case css::awt::Key::MOVE_WORD_BACKWARD:
+ bGoLeft = bWord = true;break;
+ case css::awt::Key::SELECT_WORD_BACKWARD:
+ bGoLeft = bSelect = bWord = true;break;
+ case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
+ bSelect = true;
+ [[fallthrough]];
+ case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
+ bGoHome = true;break;
+ case css::awt::Key::SELECT_TO_END_OF_LINE:
+ case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
+ case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
+ bSelect = true;
+ [[fallthrough]];
+ case css::awt::Key::MOVE_TO_END_OF_LINE:
+ case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
+ case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
+ bGoEnd = true;break;
+ default:
+ break;
+ }
+
+ // range is checked in ImplSetSelection ...
+ if ( bGoLeft && aSel.Max() )
+ {
+ if ( bWord )
+ {
+ const OUString sText = maText.toString();
+ i18n::Boundary aBoundary = xBI->getWordBoundary( sText, aSel.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
+ if ( aBoundary.startPos == aSel.Max() )
+ aBoundary = xBI->previousWord( sText, aSel.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
+ aSel.Max() = aBoundary.startPos;
+ }
+ else
+ {
+ sal_Int32 nCount = 1;
+ aSel.Max() = xBI->previousCharacters( maText.toString(), aSel.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
+ }
+ }
+ else if ( bGoRight && ( aSel.Max() < maText.getLength() ) )
+ {
+ if ( bWord )
+ {
+ i18n::Boundary aBoundary = xBI->nextWord( maText.toString(), aSel.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
+ aSel.Max() = aBoundary.startPos;
+ }
+ else
+ {
+ sal_Int32 nCount = 1;
+ aSel.Max() = xBI->nextCharacters( maText.toString(), aSel.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
+ }
+ }
+ else if ( bGoHome )
+ {
+ aSel.Max() = 0;
+ }
+ else if ( bGoEnd )
+ {
+ aSel.Max() = EDIT_NOLIMIT;
+ }
+
+ if ( !bSelect )
+ aSel.Min() = aSel.Max();
+
+ if ( aSel != GetSelection() )
+ {
+ ImplSetSelection( aSel );
+ ImplCopyToSelectionClipboard();
+ }
+
+ if (bGoEnd && maAutocompleteHdl.IsSet() && !rKEvt.GetKeyCode().GetModifier())
+ {
+ if ( (maSelection.Min() == maSelection.Max()) && (maSelection.Min() == maText.getLength()) )
+ {
+ maAutocompleteHdl.Call(*this);
+ }
+ }
+
+ bDone = true;
+ }
+ }
+ break;
+
+ case css::awt::Key::DELETE_WORD_BACKWARD:
+ case css::awt::Key::DELETE_WORD_FORWARD:
+ case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
+ case css::awt::Key::DELETE_TO_END_OF_LINE:
+ case KEY_BACKSPACE:
+ case KEY_DELETE:
+ {
+ if ( !mbReadOnly && !rKEvt.GetKeyCode().IsMod2() )
+ {
+ sal_uInt8 nDel = (nCode == KEY_DELETE) ? EDIT_DEL_RIGHT : EDIT_DEL_LEFT;
+ sal_uInt8 nMode = rKEvt.GetKeyCode().IsMod1() ? EDIT_DELMODE_RESTOFWORD : EDIT_DELMODE_SIMPLE;
+ if ( (nMode == EDIT_DELMODE_RESTOFWORD) && rKEvt.GetKeyCode().IsShift() )
+ nMode = EDIT_DELMODE_RESTOFCONTENT;
+ switch( nCode )
+ {
+ case css::awt::Key::DELETE_WORD_BACKWARD:
+ nDel = EDIT_DEL_LEFT;
+ nMode = EDIT_DELMODE_RESTOFWORD;
+ break;
+ case css::awt::Key::DELETE_WORD_FORWARD:
+ nDel = EDIT_DEL_RIGHT;
+ nMode = EDIT_DELMODE_RESTOFWORD;
+ break;
+ case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
+ nDel = EDIT_DEL_LEFT;
+ nMode = EDIT_DELMODE_RESTOFCONTENT;
+ break;
+ case css::awt::Key::DELETE_TO_END_OF_LINE:
+ nDel = EDIT_DEL_RIGHT;
+ nMode = EDIT_DELMODE_RESTOFCONTENT;
+ break;
+ default: break;
+ }
+ sal_Int32 nOldLen = maText.getLength();
+ ImplDelete( maSelection, nDel, nMode );
+ if ( maText.getLength() != nOldLen )
+ Modify();
+ bDone = true;
+ }
+ }
+ break;
+
+ case KEY_INSERT:
+ {
+ if ( !mpIMEInfos && !mbReadOnly && !rKEvt.GetKeyCode().IsMod2() )
+ {
+ SetInsertMode( !mbInsertMode );
+ bDone = true;
+ }
+ }
+ break;
+
+ case KEY_RETURN:
+ if (maActivateHdl.IsSet() && !rKEvt.GetKeyCode().GetModifier())
+ bDone = maActivateHdl.Call(*this);
+ break;
+
+ default:
+ {
+ if ( IsCharInput( rKEvt ) )
+ {
+ bDone = true; // read characters also when in ReadOnly
+ if ( !mbReadOnly )
+ {
+ ImplInsertText(OUString(rKEvt.GetCharCode()), nullptr, true);
+ if (maAutocompleteHdl.IsSet())
+ {
+ if ( (maSelection.Min() == maSelection.Max()) && (maSelection.Min() == maText.getLength()) )
+ {
+ maAutocompleteHdl.Call(*this);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ( mbInternModified )
+ Modify();
+
+ return bDone;
+}
+
+void Edit::KeyInput( const KeyEvent& rKEvt )
+{
+ if ( mpSubEdit || !ImplHandleKeyEvent( rKEvt ) )
+ Control::KeyInput( rKEvt );
+}
+
+void Edit::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ const_cast<Edit*>(this)->Invalidate();
+}
+
+void Edit::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRectangle)
+{
+ if (!mpSubEdit)
+ ImplRepaint(rRenderContext, rRectangle);
+}
+
+void Edit::Resize()
+{
+ if ( !mpSubEdit && IsReallyVisible() )
+ {
+ Control::Resize();
+ // because of vertical centering...
+ mnXOffset = 0;
+ ImplAlign();
+ Invalidate();
+ ImplShowCursor();
+ }
+}
+
+void Edit::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags )
+{
+ ApplySettings(*pDev);
+
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ vcl::Font aFont = GetDrawPixelFont( pDev );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ pDev->SetTextFillColor();
+
+ // Border/Background
+ pDev->SetLineColor();
+ pDev->SetFillColor();
+ bool bBorder = (GetStyle() & WB_BORDER);
+ bool bBackground = IsControlBackground();
+ if ( bBorder || bBackground )
+ {
+ tools::Rectangle aRect( aPos, aSize );
+ if ( bBorder )
+ {
+ ImplDrawFrame( pDev, aRect );
+ }
+ if ( bBackground )
+ {
+ pDev->SetFillColor( GetControlBackground() );
+ pDev->DrawRect( aRect );
+ }
+ }
+
+ // Content
+ if ( nFlags & SystemTextColorFlags::Mono )
+ pDev->SetTextColor( COL_BLACK );
+ else
+ {
+ if ( !IsEnabled() )
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ pDev->SetTextColor( rStyleSettings.GetDisableColor() );
+ }
+ else
+ {
+ pDev->SetTextColor( GetTextColor() );
+ }
+ }
+
+ const tools::Long nOnePixel = GetDrawPixel( pDev, 1 );
+ const tools::Long nOffX = 3*nOnePixel;
+ DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
+ tools::Rectangle aTextRect( aPos, aSize );
+
+ if ( GetStyle() & WB_CENTER )
+ nTextStyle |= DrawTextFlags::Center;
+ else if ( GetStyle() & WB_RIGHT )
+ nTextStyle |= DrawTextFlags::Right;
+ else
+ nTextStyle |= DrawTextFlags::Left;
+
+ aTextRect.AdjustLeft(nOffX );
+ aTextRect.AdjustRight( -nOffX );
+
+ OUString aText = ImplGetText();
+ tools::Long nTextHeight = pDev->GetTextHeight();
+ tools::Long nTextWidth = pDev->GetTextWidth( aText );
+ tools::Long nOffY = (aSize.Height() - nTextHeight) / 2;
+
+ // Clipping?
+ if ( (nOffY < 0) ||
+ ((nOffY+nTextHeight) > aSize.Height()) ||
+ ((nOffX+nTextWidth) > aSize.Width()) )
+ {
+ tools::Rectangle aClip( aPos, aSize );
+ if ( nTextHeight > aSize.Height() )
+ aClip.AdjustBottom(nTextHeight-aSize.Height()+1 ); // prevent HP printers from 'optimizing'
+ pDev->IntersectClipRegion( aClip );
+ }
+
+ pDev->DrawText( aTextRect, aText, nTextStyle );
+ pDev->Pop();
+
+ if ( GetSubEdit() )
+ {
+ Size aOrigSize(GetSubEdit()->GetSizePixel());
+ GetSubEdit()->SetSizePixel(GetSizePixel());
+ GetSubEdit()->Draw(pDev, rPos, nFlags);
+ GetSubEdit()->SetSizePixel(aOrigSize);
+ }
+}
+
+void Edit::ImplInvalidateOutermostBorder( vcl::Window* pWin )
+{
+ // allow control to show focused state
+ vcl::Window *pInvalWin = pWin;
+ for (;;)
+ {
+ vcl::Window* pBorder = pInvalWin->GetWindow( GetWindowType::Border );
+ if (pBorder == pInvalWin || !pBorder ||
+ pInvalWin->ImplGetFrame() != pBorder->ImplGetFrame() )
+ break;
+ pInvalWin = pBorder;
+ }
+
+ pInvalWin->Invalidate( InvalidateFlags::Children | InvalidateFlags::Update );
+}
+
+void Edit::GetFocus()
+{
+ if ( mpSubEdit )
+ mpSubEdit->ImplGrabFocus( GetGetFocusFlags() );
+ else if ( !mbActivePopup )
+ {
+ maUndoText = maText.toString();
+ SelectionOptions nSelOptions = GetSettings().GetStyleSettings().GetSelectionOptions();
+ if ( !( GetStyle() & (WB_NOHIDESELECTION|WB_READONLY) )
+ && ( GetGetFocusFlags() & (GetFocusFlags::Init|GetFocusFlags::Tab|GetFocusFlags::CURSOR|GetFocusFlags::Mnemonic) ) )
+ {
+ if ( nSelOptions & SelectionOptions::ShowFirst )
+ {
+ maSelection.Min() = maText.getLength();
+ maSelection.Max() = 0;
+ }
+ else
+ {
+ maSelection.Min() = 0;
+ maSelection.Max() = maText.getLength();
+ }
+ if ( mbIsSubEdit )
+ static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::EditSelectionChanged );
+ else
+ CallEventListeners( VclEventId::EditSelectionChanged );
+ }
+
+ ImplShowCursor();
+
+ // FIXME: this is currently only on macOS
+ // check for other platforms that need similar handling
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects &&
+ IsNativeWidgetEnabled() &&
+ IsNativeControlSupported( ControlType::Editbox, ControlPart::Entire ) )
+ {
+ ImplInvalidateOutermostBorder( mbIsSubEdit ? GetParent() : this );
+ }
+ else if ( maSelection.Len() )
+ {
+ // paint the selection
+ if ( !HasPaintEvent() )
+ ImplInvalidateOrRepaint();
+ else
+ Invalidate();
+ }
+
+ SetInputContext( InputContext( GetFont(), !IsReadOnly() ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE ) );
+ }
+
+ Control::GetFocus();
+}
+
+void Edit::LoseFocus()
+{
+ if ( !mpSubEdit )
+ {
+ // FIXME: this is currently only on macOS
+ // check for other platforms that need similar handling
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects &&
+ IsNativeWidgetEnabled() &&
+ IsNativeControlSupported( ControlType::Editbox, ControlPart::Entire ) )
+ {
+ ImplInvalidateOutermostBorder( mbIsSubEdit ? GetParent() : this );
+ }
+
+ if ( !mbActivePopup && !( GetStyle() & WB_NOHIDESELECTION ) && maSelection.Len() )
+ ImplInvalidateOrRepaint(); // paint the selection
+ }
+
+ Control::LoseFocus();
+}
+
+void Edit::Command( const CommandEvent& rCEvt )
+{
+ if ( rCEvt.GetCommand() == CommandEventId::ContextMenu )
+ {
+ VclPtr<PopupMenu> pPopup = Edit::CreatePopupMenu();
+
+ bool bEnableCut = true;
+ bool bEnableCopy = true;
+ bool bEnableDelete = true;
+ bool bEnablePaste = true;
+ bool bEnableSpecialChar = true;
+
+ if ( !maSelection.Len() )
+ {
+ bEnableCut = false;
+ bEnableCopy = false;
+ bEnableDelete = false;
+ }
+
+ if ( IsReadOnly() )
+ {
+ bEnableCut = false;
+ bEnablePaste = false;
+ bEnableDelete = false;
+ bEnableSpecialChar = false;
+ }
+ else
+ {
+ // only paste if text available in clipboard
+ bool bData = false;
+ uno::Reference< datatransfer::clipboard::XClipboard > xClipboard = GetClipboard();
+
+ if ( xClipboard.is() )
+ {
+ uno::Reference< datatransfer::XTransferable > xDataObj;
+ {
+ SolarMutexReleaser aReleaser;
+ xDataObj = xClipboard->getContents();
+ }
+ if ( xDataObj.is() )
+ {
+ datatransfer::DataFlavor aFlavor;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
+ bData = xDataObj->isDataFlavorSupported( aFlavor );
+ }
+ }
+ bEnablePaste = bData;
+ }
+
+ pPopup->EnableItem(pPopup->GetItemId("cut"), bEnableCut);
+ pPopup->EnableItem(pPopup->GetItemId("copy"), bEnableCopy);
+ pPopup->EnableItem(pPopup->GetItemId("delete"), bEnableDelete);
+ pPopup->EnableItem(pPopup->GetItemId("paste"), bEnablePaste);
+ pPopup->EnableItem(pPopup->GetItemId("specialchar"), bEnableSpecialChar);
+ pPopup->EnableItem(
+ pPopup->GetItemId("undo"),
+ std::u16string_view(maUndoText)
+ != std::u16string_view(maText.getStr(), maText.getLength()));
+ bool bAllSelected = maSelection.Min() == 0 && maSelection.Max() == maText.getLength();
+ pPopup->EnableItem(pPopup->GetItemId("selectall"), !bAllSelected);
+ pPopup->ShowItem(pPopup->GetItemId("specialchar"), pImplFncGetSpecialChars != nullptr);
+
+ mbActivePopup = true;
+ Selection aSaveSel = GetSelection(); // if someone changes selection in Get/LoseFocus, e.g. URL bar
+ Point aPos = rCEvt.GetMousePosPixel();
+ if ( !rCEvt.IsMouseEvent() )
+ {
+ // Show menu eventually centered in selection
+ Size aSize = GetOutputSizePixel();
+ aPos = Point( aSize.Width()/2, aSize.Height()/2 );
+ }
+ sal_uInt16 n = pPopup->Execute( this, aPos );
+ SetSelection( aSaveSel );
+ OString sCommand = pPopup->GetItemIdent(n);
+ if (sCommand == "undo")
+ {
+ Undo();
+ Modify();
+ }
+ else if (sCommand == "cut")
+ {
+ Cut();
+ Modify();
+ }
+ else if (sCommand == "copy")
+ {
+ Copy();
+ }
+ else if (sCommand == "paste")
+ {
+ Paste();
+ Modify();
+ }
+ else if (sCommand == "delete")
+ {
+ DeleteSelected();
+ Modify();
+ }
+ else if (sCommand == "selectall")
+ {
+ ImplSetSelection( Selection( 0, maText.getLength() ) );
+ }
+ else if (sCommand == "specialchar" && pImplFncGetSpecialChars)
+ {
+ OUString aChars = pImplFncGetSpecialChars(GetFrameWeld(), GetFont());
+ if (!isDisposed()) // destroyed while the insert special character dialog was still open
+ {
+ SetSelection( aSaveSel );
+ if (!aChars.isEmpty())
+ {
+ ImplInsertText( aChars );
+ Modify();
+ }
+ }
+ }
+ pPopup.clear();
+ mbActivePopup = false;
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
+ {
+ DeleteSelected();
+ sal_Int32 nPos = maSelection.Max();
+ mpIMEInfos.reset(new Impl_IMEInfos( nPos, maText.copy(nPos).makeStringAndClear() ));
+ mpIMEInfos->bWasCursorOverwrite = !IsInsertMode();
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
+ {
+ bool bInsertMode = !mpIMEInfos->bWasCursorOverwrite;
+ mpIMEInfos.reset();
+
+ SetInsertMode(bInsertMode);
+ Modify();
+
+ Invalidate();
+
+ // #i25161# call auto complete handler for ext text commit also
+ if (maAutocompleteHdl.IsSet())
+ {
+ if ( (maSelection.Min() == maSelection.Max()) && (maSelection.Min() == maText.getLength()) )
+ {
+ maAutocompleteHdl.Call(*this);
+ }
+ }
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput )
+ {
+ const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData();
+
+ maText.remove( mpIMEInfos->nPos, mpIMEInfos->nLen );
+ maText.insert( mpIMEInfos->nPos, pData->GetText() );
+ if ( mpIMEInfos->bWasCursorOverwrite )
+ {
+ const sal_Int32 nOldIMETextLen = mpIMEInfos->nLen;
+ const sal_Int32 nNewIMETextLen = pData->GetText().getLength();
+ if ( ( nOldIMETextLen > nNewIMETextLen ) &&
+ ( nNewIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
+ {
+ // restore old characters
+ const sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen;
+ maText.insert( mpIMEInfos->nPos + nNewIMETextLen, mpIMEInfos->aOldTextAfterStartPos.subView( nNewIMETextLen, nRestore ) );
+ }
+ else if ( ( nOldIMETextLen < nNewIMETextLen ) &&
+ ( nOldIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
+ {
+ const sal_Int32 nOverwrite = ( nNewIMETextLen > mpIMEInfos->aOldTextAfterStartPos.getLength()
+ ? mpIMEInfos->aOldTextAfterStartPos.getLength() : nNewIMETextLen ) - nOldIMETextLen;
+ maText.remove( mpIMEInfos->nPos + nNewIMETextLen, nOverwrite );
+ }
+ }
+
+ if ( pData->GetTextAttr() )
+ {
+ mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() );
+ mpIMEInfos->bCursor = pData->IsCursorVisible();
+ }
+ else
+ {
+ mpIMEInfos->DestroyAttribs();
+ }
+
+ ImplAlignAndPaint();
+ sal_Int32 nCursorPos = mpIMEInfos->nPos + pData->GetCursorPos();
+ SetSelection( Selection( nCursorPos, nCursorPos ) );
+ SetInsertMode( !pData->IsCursorOverwrite() );
+
+ if ( pData->IsCursorVisible() )
+ GetCursor()->Show();
+ else
+ GetCursor()->Hide();
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
+ {
+ if ( mpIMEInfos )
+ {
+ sal_Int32 nCursorPos = GetSelection().Max();
+ SetCursorRect( nullptr, GetTextWidth( maText.toString(), nCursorPos, mpIMEInfos->nPos+mpIMEInfos->nLen-nCursorPos ) );
+ }
+ else
+ {
+ SetCursorRect();
+ }
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::SelectionChange )
+ {
+ const CommandSelectionChangeData *pData = rCEvt.GetSelectionChangeData();
+ Selection aSelection( pData->GetStart(), pData->GetEnd() );
+ SetSelection(aSelection);
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::QueryCharPosition )
+ {
+ if (mpIMEInfos && mpIMEInfos->nLen > 0)
+ {
+ OUString aText = ImplGetText();
+ std::vector<sal_Int32> aDX(2*(aText.getLength()+1));
+
+ GetOutDev()->GetCaretPositions( aText, aDX.data(), 0, aText.getLength() );
+
+ tools::Long nTH = GetTextHeight();
+ Point aPos( mnXOffset, ImplGetTextYPosition() );
+
+ std::vector<tools::Rectangle> aRects(mpIMEInfos->nLen);
+ for ( int nIndex = 0; nIndex < mpIMEInfos->nLen; ++nIndex )
+ {
+ tools::Rectangle aRect( aPos, Size( 10, nTH ) );
+ aRect.SetLeft( aDX[2*(nIndex+mpIMEInfos->nPos)] + mnXOffset + ImplGetExtraXOffset() );
+ aRects[ nIndex ] = aRect;
+ }
+ SetCompositionCharRect(aRects.data(), mpIMEInfos->nLen);
+ }
+ }
+ else
+ Control::Command( rCEvt );
+}
+
+void Edit::StateChanged( StateChangedType nType )
+{
+ if (nType == StateChangedType::InitShow)
+ {
+ if (!mpSubEdit)
+ {
+ mnXOffset = 0; // if GrabFocus before while size was still wrong
+ ImplAlign();
+ if (!mpSubEdit)
+ ImplShowCursor(false);
+ Invalidate();
+ }
+ }
+ else if (nType == StateChangedType::Enable)
+ {
+ if (!mpSubEdit)
+ {
+ // change text color only
+ ImplInvalidateOrRepaint();
+ }
+ }
+ else if (nType == StateChangedType::Style || nType == StateChangedType::Mirroring)
+ {
+ WinBits nStyle = GetStyle();
+ if (nType == StateChangedType::Style)
+ {
+ nStyle = ImplInitStyle(GetStyle());
+ SetStyle(nStyle);
+ }
+
+ sal_uInt16 nOldAlign = mnAlign;
+ mnAlign = EDIT_ALIGN_LEFT;
+
+ // hack: right align until keyinput and cursor travelling works
+ // edits are always RTL disabled
+ // however the parent edits contain the correct setting
+ if (mbIsSubEdit && GetParent()->IsRTLEnabled())
+ {
+ if (GetParent()->GetStyle() & WB_LEFT)
+ mnAlign = EDIT_ALIGN_RIGHT;
+ if (nType == StateChangedType::Mirroring)
+ GetOutDev()->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft);
+ }
+ else if (mbIsSubEdit && !GetParent()->IsRTLEnabled())
+ {
+ if (nType == StateChangedType::Mirroring)
+ GetOutDev()->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::TextOriginLeft);
+ }
+
+ if (nStyle & WB_RIGHT)
+ mnAlign = EDIT_ALIGN_RIGHT;
+ else if (nStyle & WB_CENTER)
+ mnAlign = EDIT_ALIGN_CENTER;
+ if (!maText.isEmpty() && (mnAlign != nOldAlign))
+ {
+ ImplAlign();
+ Invalidate();
+ }
+
+ }
+ else if ((nType == StateChangedType::Zoom) || (nType == StateChangedType::ControlFont))
+ {
+ if (!mpSubEdit)
+ {
+ ApplySettings(*GetOutDev());
+ ImplShowCursor();
+ Invalidate();
+ }
+ }
+ else if ((nType == StateChangedType::ControlForeground) || (nType == StateChangedType::ControlBackground))
+ {
+ if (!mpSubEdit)
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ }
+
+ Control::StateChanged(nType);
+}
+
+void Edit::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ if ( !mpSubEdit )
+ {
+ ApplySettings(*GetOutDev());
+ ImplShowCursor();
+ Invalidate();
+ }
+ }
+
+ Control::DataChanged( rDCEvt );
+}
+
+void Edit::ImplShowDDCursor()
+{
+ if (!mpDDInfo->bVisCursor)
+ {
+ tools::Long nTextWidth = GetTextWidth( maText.toString(), 0, mpDDInfo->nDropPos );
+ tools::Long nTextHeight = GetTextHeight();
+ tools::Rectangle aCursorRect( Point( nTextWidth + mnXOffset, (GetOutDev()->GetOutputSize().Height()-nTextHeight)/2 ), Size( 2, nTextHeight ) );
+ mpDDInfo->aCursor.SetWindow( this );
+ mpDDInfo->aCursor.SetPos( aCursorRect.TopLeft() );
+ mpDDInfo->aCursor.SetSize( aCursorRect.GetSize() );
+ mpDDInfo->aCursor.Show();
+ mpDDInfo->bVisCursor = true;
+ }
+}
+
+void Edit::ImplHideDDCursor()
+{
+ if ( mpDDInfo && mpDDInfo->bVisCursor )
+ {
+ mpDDInfo->aCursor.Hide();
+ mpDDInfo->bVisCursor = false;
+ }
+}
+
+TextFilter::TextFilter(const OUString &rForbiddenChars)
+ : sForbiddenChars(rForbiddenChars)
+{
+}
+
+TextFilter::~TextFilter()
+{
+}
+
+OUString TextFilter::filter(const OUString &rText)
+{
+ OUString sTemp(rText);
+ for (sal_Int32 i = 0; i < sForbiddenChars.getLength(); ++i)
+ {
+ sTemp = sTemp.replaceAll(OUStringChar(sForbiddenChars[i]), "");
+ }
+ return sTemp;
+}
+
+void Edit::filterText()
+{
+ Selection aSel = GetSelection();
+ const OUString sOrig = GetText();
+ const OUString sNew = mpFilterText->filter(GetText());
+ if (sOrig != sNew)
+ {
+ sal_Int32 nDiff = sOrig.getLength() - sNew.getLength();
+ if (nDiff)
+ {
+ aSel.setMin(aSel.getMin() - nDiff);
+ aSel.setMax(aSel.getMin());
+ }
+ SetText(sNew);
+ SetSelection(aSel);
+ }
+}
+
+void Edit::Modify()
+{
+ if (mpFilterText)
+ filterText();
+
+ if ( mbIsSubEdit )
+ {
+ static_cast<Edit*>(GetParent())->Modify();
+ }
+ else
+ {
+ if ( ImplCallEventListenersAndHandler( VclEventId::EditModify, [this] () { maModifyHdl.Call(*this); } ) )
+ // have been destroyed while calling into the handlers
+ return;
+
+ // #i13677# notify edit listeners about caret position change
+ CallEventListeners( VclEventId::EditCaretChanged );
+ // FIXME: this is currently only on macOS
+ // check for other platforms that need similar handling
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects &&
+ IsNativeWidgetEnabled() &&
+ IsNativeControlSupported( ControlType::Editbox, ControlPart::Entire ) )
+ {
+ ImplInvalidateOutermostBorder( this );
+ }
+ }
+}
+
+void Edit::SetEchoChar( sal_Unicode c )
+{
+ mcEchoChar = c;
+ if ( mpSubEdit )
+ mpSubEdit->SetEchoChar( c );
+}
+
+void Edit::SetReadOnly( bool bReadOnly )
+{
+ if ( mbReadOnly != bReadOnly )
+ {
+ mbReadOnly = bReadOnly;
+ if ( mpSubEdit )
+ mpSubEdit->SetReadOnly( bReadOnly );
+
+ CompatStateChanged( StateChangedType::ReadOnly );
+ }
+}
+
+void Edit::SetInsertMode( bool bInsert )
+{
+ if ( bInsert != mbInsertMode )
+ {
+ mbInsertMode = bInsert;
+ if ( mpSubEdit )
+ mpSubEdit->SetInsertMode( bInsert );
+ else
+ ImplShowCursor();
+ }
+}
+
+bool Edit::IsInsertMode() const
+{
+ if ( mpSubEdit )
+ return mpSubEdit->IsInsertMode();
+ else
+ return mbInsertMode;
+}
+
+void Edit::SetMaxTextLen(sal_Int32 nMaxLen)
+{
+ mnMaxTextLen = nMaxLen > 0 ? nMaxLen : EDIT_NOLIMIT;
+
+ if ( mpSubEdit )
+ mpSubEdit->SetMaxTextLen( mnMaxTextLen );
+ else
+ {
+ if ( maText.getLength() > mnMaxTextLen )
+ ImplDelete( Selection( mnMaxTextLen, maText.getLength() ), EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
+ }
+}
+
+void Edit::SetSelection( const Selection& rSelection )
+{
+ // If the selection was changed from outside, e.g. by MouseButtonDown, don't call Tracking()
+ // directly afterwards which would change the selection again
+ if ( IsTracking() )
+ EndTracking();
+ else if ( mpSubEdit && mpSubEdit->IsTracking() )
+ mpSubEdit->EndTracking();
+
+ ImplSetSelection( rSelection );
+}
+
+void Edit::ImplSetSelection( const Selection& rSelection, bool bPaint )
+{
+ if ( mpSubEdit )
+ mpSubEdit->ImplSetSelection( rSelection );
+ else
+ {
+ if ( rSelection != maSelection )
+ {
+ Selection aOld( maSelection );
+ Selection aNew( rSelection );
+
+ if ( aNew.Min() > maText.getLength() )
+ aNew.Min() = maText.getLength();
+ if ( aNew.Max() > maText.getLength() )
+ aNew.Max() = maText.getLength();
+ if ( aNew.Min() < 0 )
+ aNew.Min() = 0;
+ if ( aNew.Max() < 0 )
+ aNew.Max() = 0;
+
+ if ( aNew != maSelection )
+ {
+ ImplClearLayoutData();
+ Selection aTemp = maSelection;
+ maSelection = aNew;
+
+ if ( bPaint && ( aOld.Len() || aNew.Len() || IsPaintTransparent() ) )
+ ImplInvalidateOrRepaint();
+ ImplShowCursor();
+
+ bool bCaret = false, bSelection = false;
+ tools::Long nB=aNew.Max(), nA=aNew.Min(),oB=aTemp.Max(), oA=aTemp.Min();
+ tools::Long nGap = nB-nA, oGap = oB-oA;
+ if (nB != oB)
+ bCaret = true;
+ if (nGap != 0 || oGap != 0)
+ bSelection = true;
+
+ if (bSelection)
+ {
+ if ( mbIsSubEdit )
+ static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::EditSelectionChanged );
+ else
+ CallEventListeners( VclEventId::EditSelectionChanged );
+ }
+
+ if (bCaret)
+ {
+ if ( mbIsSubEdit )
+ static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::EditCaretChanged );
+ else
+ CallEventListeners( VclEventId::EditCaretChanged );
+ }
+
+ // #103511# notify combobox listeners of deselection
+ if( !maSelection && GetParent() && GetParent()->GetType() == WindowType::COMBOBOX )
+ static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::ComboboxDeselect );
+ }
+ }
+ }
+}
+
+const Selection& Edit::GetSelection() const
+{
+ if ( mpSubEdit )
+ return mpSubEdit->GetSelection();
+ else
+ return maSelection;
+}
+
+void Edit::ReplaceSelected( const OUString& rStr )
+{
+ if ( mpSubEdit )
+ mpSubEdit->ReplaceSelected( rStr );
+ else
+ ImplInsertText( rStr );
+}
+
+void Edit::DeleteSelected()
+{
+ if ( mpSubEdit )
+ mpSubEdit->DeleteSelected();
+ else
+ {
+ if ( maSelection.Len() )
+ ImplDelete( maSelection, EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
+ }
+}
+
+OUString Edit::GetSelected() const
+{
+ if ( mpSubEdit )
+ return mpSubEdit->GetSelected();
+ else
+ {
+ Selection aSelection( maSelection );
+ aSelection.Justify();
+ return OUString( maText.getStr() + aSelection.Min(), aSelection.Len() );
+ }
+}
+
+void Edit::Cut()
+{
+ if ( !mbPassword )
+ {
+ Copy();
+ ReplaceSelected( OUString() );
+ }
+}
+
+void Edit::Copy()
+{
+ if ( !mbPassword )
+ {
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetClipboard());
+ ImplCopy( aClipboard );
+ }
+}
+
+void Edit::Paste()
+{
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetClipboard());
+ ImplPaste( aClipboard );
+}
+
+void Edit::Undo()
+{
+ if ( mpSubEdit )
+ mpSubEdit->Undo();
+ else
+ {
+ const OUString aText( maText.toString() );
+ ImplDelete( Selection( 0, aText.getLength() ), EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
+ ImplInsertText( maUndoText );
+ ImplSetSelection( Selection( 0, maUndoText.getLength() ) );
+ maUndoText = aText;
+ }
+}
+
+void Edit::SetText( const OUString& rStr )
+{
+ if ( mpSubEdit )
+ mpSubEdit->SetText( rStr ); // not directly ImplSetText if SetText overridden
+ else
+ {
+ Selection aNewSel( 0, 0 ); // prevent scrolling
+ ImplSetText( rStr, &aNewSel );
+ }
+}
+
+void Edit::SetText( const OUString& rStr, const Selection& rSelection )
+{
+ if ( mpSubEdit )
+ mpSubEdit->SetText( rStr, rSelection );
+ else
+ ImplSetText( rStr, &rSelection );
+}
+
+OUString Edit::GetText() const
+{
+ if ( mpSubEdit )
+ return mpSubEdit->GetText();
+ else
+ return maText.toString();
+}
+
+void Edit::SetCursorAtLast(){
+ ImplSetCursorPos( GetText().getLength(), false );
+}
+
+void Edit::SetPlaceholderText( const OUString& rStr )
+{
+ if ( mpSubEdit )
+ mpSubEdit->SetPlaceholderText( rStr );
+ else if ( maPlaceholderText != rStr )
+ {
+ maPlaceholderText = rStr;
+ if ( GetText().isEmpty() )
+ Invalidate();
+ }
+}
+
+void Edit::SetModifyFlag()
+{
+}
+
+void Edit::SetSubEdit(Edit* pEdit)
+{
+ mpSubEdit.disposeAndClear();
+ mpSubEdit.set(pEdit);
+
+ if (mpSubEdit)
+ {
+ SetPointer(PointerStyle::Arrow); // Only SubEdit has the BEAM...
+ mpSubEdit->mbIsSubEdit = true;
+
+ mpSubEdit->SetReadOnly(mbReadOnly);
+ mpSubEdit->maAutocompleteHdl = maAutocompleteHdl;
+ }
+}
+
+Size Edit::CalcMinimumSizeForText(const OUString &rString) const
+{
+ ControlType eCtrlType = ImplGetNativeControlType();
+
+ Size aSize;
+ if (mnWidthInChars != -1)
+ {
+ //CalcSize calls CalcWindowSize, but we will call that also in this
+ //function, so undo the first one with CalcOutputSize
+ aSize = CalcOutputSize(CalcSize(mnWidthInChars));
+ }
+ else
+ {
+ OUString aString;
+ if (mnMaxWidthChars != -1 && mnMaxWidthChars < rString.getLength())
+ aString = rString.copy(0, mnMaxWidthChars);
+ else
+ aString = rString;
+
+ aSize.setHeight( GetTextHeight() );
+ aSize.setWidth( GetTextWidth(aString) );
+ aSize.AdjustWidth(ImplGetExtraXOffset() * 2 );
+
+ // do not create edit fields in which one cannot enter anything
+ // a default minimum width should exist for at least 3 characters
+
+ //CalcSize calls CalcWindowSize, but we will call that also in this
+ //function, so undo the first one with CalcOutputSize
+ Size aMinSize(CalcOutputSize(CalcSize(3)));
+ if (aSize.Width() < aMinSize.Width())
+ aSize.setWidth( aMinSize.Width() );
+ }
+
+ aSize.AdjustHeight(ImplGetExtraYOffset() * 2 );
+
+ aSize = CalcWindowSize( aSize );
+
+ // ask NWF what if it has an opinion, too
+ ImplControlValue aControlValue;
+ tools::Rectangle aRect( Point( 0, 0 ), aSize );
+ tools::Rectangle aContent, aBound;
+ if (GetNativeControlRegion(eCtrlType, ControlPart::Entire, aRect, ControlState::NONE,
+ aControlValue, aBound, aContent))
+ {
+ if (aBound.GetHeight() > aSize.Height())
+ aSize.setHeight( aBound.GetHeight() );
+ }
+ return aSize;
+}
+
+Size Edit::CalcMinimumSize() const
+{
+ return CalcMinimumSizeForText(GetText());
+}
+
+Size Edit::GetOptimalSize() const
+{
+ return CalcMinimumSize();
+}
+
+Size Edit::CalcSize(sal_Int32 nChars) const
+{
+ // width for N characters, independent from content.
+ // works only correct for fixed fonts, average otherwise
+ float fUnitWidth = std::max(approximate_char_width(), approximate_digit_width());
+ Size aSz(fUnitWidth * nChars, GetTextHeight());
+ aSz.AdjustWidth(ImplGetExtraXOffset() * 2 );
+ aSz = CalcWindowSize( aSz );
+ return aSz;
+}
+
+sal_Int32 Edit::GetMaxVisChars() const
+{
+ const vcl::Window* pW = mpSubEdit ? mpSubEdit : this;
+ sal_Int32 nOutWidth = pW->GetOutputSizePixel().Width();
+ float fUnitWidth = std::max(approximate_char_width(), approximate_digit_width());
+ return nOutWidth / fUnitWidth;
+}
+
+namespace vcl
+{
+ void SetGetSpecialCharsFunction( FncGetSpecialChars fn )
+ {
+ pImplFncGetSpecialChars = fn;
+ }
+
+ FncGetSpecialChars GetGetSpecialCharsFunction()
+ {
+ return pImplFncGetSpecialChars;
+ }
+}
+
+VclPtr<PopupMenu> Edit::CreatePopupMenu()
+{
+ if (!mpUIBuilder)
+ mpUIBuilder.reset(new VclBuilder(nullptr, AllSettings::GetUIRootDir(), "vcl/ui/editmenu.ui", ""));
+ VclPtr<PopupMenu> pPopup = mpUIBuilder->get_menu("menu");
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ if (rStyleSettings.GetHideDisabledMenuItems())
+ pPopup->SetMenuFlags( MenuFlags::HideDisabledEntries );
+ else
+ pPopup->SetMenuFlags ( MenuFlags::AlwaysShowDisabledEntries );
+ if (rStyleSettings.GetContextMenuShortcuts())
+ {
+ pPopup->SetAccelKey(pPopup->GetItemId("undo"), vcl::KeyCode( KeyFuncType::UNDO));
+ pPopup->SetAccelKey(pPopup->GetItemId("cut"), vcl::KeyCode( KeyFuncType::CUT));
+ pPopup->SetAccelKey(pPopup->GetItemId("copy"), vcl::KeyCode( KeyFuncType::COPY));
+ pPopup->SetAccelKey(pPopup->GetItemId("paste"), vcl::KeyCode( KeyFuncType::PASTE));
+ pPopup->SetAccelKey(pPopup->GetItemId("delete"), vcl::KeyCode( KeyFuncType::DELETE));
+ pPopup->SetAccelKey(pPopup->GetItemId("selectall"), vcl::KeyCode( KEY_A, false, true, false, false));
+ pPopup->SetAccelKey(pPopup->GetItemId("specialchar"), vcl::KeyCode( KEY_S, true, true, false, false));
+ }
+ return pPopup;
+}
+
+// css::datatransfer::dnd::XDragGestureListener
+void Edit::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& rDGE )
+{
+ SolarMutexGuard aVclGuard;
+
+ if ( !(!IsTracking() && maSelection.Len() &&
+ !mbPassword && (!mpDDInfo || !mpDDInfo->bStarterOfDD)) ) // no repeated D&D
+ return;
+
+ Selection aSel( maSelection );
+ aSel.Justify();
+
+ // only if mouse in the selection...
+ Point aMousePos( rDGE.DragOriginX, rDGE.DragOriginY );
+ sal_Int32 nCharPos = ImplGetCharPos( aMousePos );
+ if ( (nCharPos < aSel.Min()) || (nCharPos >= aSel.Max()) )
+ return;
+
+ if ( !mpDDInfo )
+ mpDDInfo.reset(new DDInfo);
+
+ mpDDInfo->bStarterOfDD = true;
+ mpDDInfo->aDndStartSel = aSel;
+
+ if ( IsTracking() )
+ EndTracking(); // before D&D disable tracking
+
+ rtl::Reference<vcl::unohelper::TextDataObject> pDataObj = new vcl::unohelper::TextDataObject( GetSelected() );
+ sal_Int8 nActions = datatransfer::dnd::DNDConstants::ACTION_COPY;
+ if ( !IsReadOnly() )
+ nActions |= datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ rDGE.DragSource->startDrag( rDGE, nActions, 0 /*cursor*/, 0 /*image*/, pDataObj, mxDnDListener );
+ if ( GetCursor() )
+ GetCursor()->Hide();
+}
+
+// css::datatransfer::dnd::XDragSourceListener
+void Edit::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& rDSDE )
+{
+ SolarMutexGuard aVclGuard;
+
+ if (rDSDE.DropSuccess && (rDSDE.DropAction & datatransfer::dnd::DNDConstants::ACTION_MOVE) && mpDDInfo)
+ {
+ Selection aSel( mpDDInfo->aDndStartSel );
+ if ( mpDDInfo->bDroppedInMe )
+ {
+ if ( aSel.Max() > mpDDInfo->nDropPos )
+ {
+ tools::Long nLen = aSel.Len();
+ aSel.Min() += nLen;
+ aSel.Max() += nLen;
+ }
+ }
+ ImplDelete( aSel, EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
+ Modify();
+ }
+
+ ImplHideDDCursor();
+ mpDDInfo.reset();
+}
+
+// css::datatransfer::dnd::XDropTargetListener
+void Edit::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE )
+{
+ SolarMutexGuard aVclGuard;
+
+ bool bChanges = false;
+ if ( !mbReadOnly && mpDDInfo )
+ {
+ ImplHideDDCursor();
+
+ Selection aSel( maSelection );
+ aSel.Justify();
+
+ if ( aSel.Len() && !mpDDInfo->bStarterOfDD )
+ ImplDelete( aSel, EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
+
+ mpDDInfo->bDroppedInMe = true;
+
+ aSel.Min() = mpDDInfo->nDropPos;
+ aSel.Max() = mpDDInfo->nDropPos;
+ ImplSetSelection( aSel );
+
+ uno::Reference< datatransfer::XTransferable > xDataObj = rDTDE.Transferable;
+ if ( xDataObj.is() )
+ {
+ datatransfer::DataFlavor aFlavor;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
+ if ( xDataObj->isDataFlavorSupported( aFlavor ) )
+ {
+ uno::Any aData = xDataObj->getTransferData( aFlavor );
+ OUString aText;
+ aData >>= aText;
+ ImplInsertText( aText );
+ bChanges = true;
+ Modify();
+ }
+ }
+
+ if ( !mpDDInfo->bStarterOfDD )
+ {
+ mpDDInfo.reset();
+ }
+ }
+
+ rDTDE.Context->dropComplete( bChanges );
+}
+
+void Edit::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& rDTDE )
+{
+ if ( !mpDDInfo )
+ {
+ mpDDInfo.reset(new DDInfo);
+ }
+ // search for string data type
+ const Sequence< css::datatransfer::DataFlavor >& rFlavors( rDTDE.SupportedDataFlavors );
+ mpDDInfo->bIsStringSupported = std::any_of(rFlavors.begin(), rFlavors.end(),
+ [](const css::datatransfer::DataFlavor& rFlavor) {
+ sal_Int32 nIndex = 0;
+ const std::u16string_view aMimetype = o3tl::getToken(rFlavor.MimeType, 0, ';', nIndex );
+ return aMimetype == u"text/plain";
+ });
+}
+
+void Edit::dragExit( const css::datatransfer::dnd::DropTargetEvent& )
+{
+ SolarMutexGuard aVclGuard;
+
+ ImplHideDDCursor();
+}
+
+void Edit::dragOver( const css::datatransfer::dnd::DropTargetDragEvent& rDTDE )
+{
+ SolarMutexGuard aVclGuard;
+
+ Point aMousePos( rDTDE.LocationX, rDTDE.LocationY );
+
+ sal_Int32 nPrevDropPos = mpDDInfo->nDropPos;
+ mpDDInfo->nDropPos = ImplGetCharPos( aMousePos );
+
+ /*
+ Size aOutSize = GetOutputSizePixel();
+ if ( ( aMousePos.X() < 0 ) || ( aMousePos.X() > aOutSize.Width() ) )
+ {
+ // Scroll?
+ // No, I will not receive events in this case...
+ }
+ */
+
+ Selection aSel( maSelection );
+ aSel.Justify();
+
+ // Don't accept drop in selection or read-only field...
+ if ( IsReadOnly() || aSel.Contains( mpDDInfo->nDropPos ) || ! mpDDInfo->bIsStringSupported )
+ {
+ ImplHideDDCursor();
+ rDTDE.Context->rejectDrag();
+ }
+ else
+ {
+ // draw the old cursor away...
+ if ( !mpDDInfo->bVisCursor || ( nPrevDropPos != mpDDInfo->nDropPos ) )
+ {
+ ImplHideDDCursor();
+ ImplShowDDCursor();
+ }
+ rDTDE.Context->acceptDrag( rDTDE.DropAction );
+ }
+}
+
+OUString Edit::GetSurroundingText() const
+{
+ if (mpSubEdit)
+ return mpSubEdit->GetSurroundingText();
+ return maText.toString();
+}
+
+Selection Edit::GetSurroundingTextSelection() const
+{
+ return GetSelection();
+}
+
+bool Edit::DeleteSurroundingText(const Selection& rSelection)
+{
+ SetSelection(rSelection);
+ DeleteSelected();
+ // maybe we should update mpIMEInfos here
+ return true;
+}
+
+FactoryFunction Edit::GetUITestFactory() const
+{
+ return EditUIObject::create;
+}
+
+
+void Edit::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+
+ if (!maPlaceholderText.isEmpty())
+ rJsonWriter.put("placeholder", maPlaceholderText);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/field.cxx b/vcl/source/control/field.cxx
new file mode 100644
index 000000000..90aef01f1
--- /dev/null
+++ b/vcl/source/control/field.cxx
@@ -0,0 +1,1885 @@
+/* -*- 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 <cmath>
+#include <string_view>
+
+#include <sal/log.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+
+#include <comphelper/string.hxx>
+#include <tools/UnitConversion.hxx>
+
+#include <vcl/builder.hxx>
+#include <vcl/fieldvalues.hxx>
+#include <vcl/toolkit/field.hxx>
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/uitest/metricfielduiobject.hxx>
+
+#include <svdata.hxx>
+
+#include <i18nutil/unicode.hxx>
+
+#include <rtl/math.hxx>
+
+#include <unotools/localedatawrapper.hxx>
+#include <boost/property_tree/ptree.hpp>
+#include <tools/json_writer.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::comphelper;
+
+namespace
+{
+
+std::string FieldUnitToString(FieldUnit unit)
+{
+ switch(unit)
+ {
+ case FieldUnit::NONE:
+ return "";
+
+ case FieldUnit::MM:
+ return "mm";
+
+ case FieldUnit::CM:
+ return "cm";
+
+ case FieldUnit::M:
+ return "m";
+
+ case FieldUnit::KM:
+ return "km";
+
+ case FieldUnit::TWIP:
+ return "twip";
+
+ case FieldUnit::POINT:
+ return "point";
+
+ case FieldUnit::PICA:
+ return "pica";
+
+ case FieldUnit::INCH:
+ return "inch";
+
+ case FieldUnit::FOOT:
+ return "foot";
+
+ case FieldUnit::MILE:
+ return "mile";
+
+ case FieldUnit::CHAR:
+ return "char";
+
+ case FieldUnit::LINE:
+ return "line";
+
+ case FieldUnit::CUSTOM:
+ return "custom";
+
+ case FieldUnit::PERCENT:
+ return "percent";
+
+ case FieldUnit::MM_100TH:
+ return "mm100th";
+
+ case FieldUnit::PIXEL:
+ return "pixel";
+
+ case FieldUnit::DEGREE:
+ return "degree";
+
+ case FieldUnit::SECOND:
+ return "second";
+
+ case FieldUnit::MILLISECOND:
+ return "millisecond";
+ }
+
+ return "";
+}
+
+sal_Int64 ImplPower10( sal_uInt16 n )
+{
+ sal_uInt16 i;
+ sal_Int64 nValue = 1;
+
+ for ( i=0; i < n; i++ )
+ nValue *= 10;
+
+ return nValue;
+}
+
+bool ImplNumericProcessKeyInput( const KeyEvent& rKEvt,
+ bool bStrictFormat, bool bThousandSep,
+ const LocaleDataWrapper& rLocaleDataWrapper )
+{
+ if ( !bStrictFormat )
+ return false;
+ else
+ {
+ sal_Unicode cChar = rKEvt.GetCharCode();
+ sal_uInt16 nGroup = rKEvt.GetKeyCode().GetGroup();
+
+ return !((nGroup == KEYGROUP_FKEYS) ||
+ (nGroup == KEYGROUP_CURSOR) ||
+ (nGroup == KEYGROUP_MISC) ||
+ ((cChar >= '0') && (cChar <= '9')) ||
+ rLocaleDataWrapper.getNumDecimalSep() == OUStringChar(cChar) ||
+ (bThousandSep && rLocaleDataWrapper.getNumThousandSep() == OUStringChar(cChar)) ||
+ rLocaleDataWrapper.getNumDecimalSepAlt() == OUStringChar(cChar) ||
+ (cChar == '-'));
+ }
+}
+
+bool ImplNumericGetValue( const OUString& rStr, sal_Int64& rValue,
+ sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper,
+ bool bCurrency = false )
+{
+ OUString aStr = rStr;
+ OUStringBuffer aStr1, aStr2, aStrNum, aStrDenom;
+ bool bNegative = false;
+ bool bFrac = false;
+ sal_Int32 nDecPos, nFracDivPos;
+ sal_Int64 nValue;
+
+ // react on empty string
+ if ( rStr.isEmpty() )
+ return false;
+
+ // remove leading and trailing spaces
+ aStr = aStr.trim();
+
+
+ // find position of decimal point
+ nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSep() );
+ if (nDecPos < 0 && !rLocaleDataWrapper.getNumDecimalSepAlt().isEmpty())
+ nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSepAlt() );
+ // find position of fraction
+ nFracDivPos = aStr.indexOf( '/' );
+
+ // parse fractional strings
+ if (nFracDivPos > 0)
+ {
+ bFrac = true;
+ sal_Int32 nFracNumPos = aStr.lastIndexOf(' ', nFracDivPos);
+
+ // If in "a b/c" format.
+ if(nFracNumPos != -1 )
+ {
+ aStr1.append(aStr.subView(0, nFracNumPos));
+ aStrNum.append(aStr.subView(nFracNumPos+1, nFracDivPos-nFracNumPos-1));
+ aStrDenom.append(aStr.subView(nFracDivPos+1));
+ }
+ // "a/b" format, or not a fraction at all
+ else
+ {
+ aStrNum.append(aStr.subView(0, nFracDivPos));
+ aStrDenom.append(aStr.subView(nFracDivPos+1));
+ }
+
+ }
+ // parse decimal strings
+ else if ( nDecPos >= 0)
+ {
+ aStr1.append(aStr.subView(0, nDecPos));
+ aStr2.append(aStr.subView(nDecPos+1));
+ }
+ else
+ aStr1 = aStr;
+
+ // negative?
+ if ( bCurrency )
+ {
+ if ( aStr.startsWith("(") && aStr.endsWith(")") )
+ bNegative = true;
+ if ( !bNegative )
+ {
+ for (sal_Int32 i=0; i < aStr.getLength(); i++ )
+ {
+ if ( (aStr[i] >= '0') && (aStr[i] <= '9') )
+ break;
+ else if ( aStr[i] == '-' )
+ {
+ bNegative = true;
+ break;
+ }
+ }
+ }
+ if (!bNegative && !aStr.isEmpty())
+ {
+ sal_uInt16 nFormat = rLocaleDataWrapper.getCurrNegativeFormat();
+ if ( (nFormat == 3) || (nFormat == 6) || // $1- || 1-$
+ (nFormat == 7) || (nFormat == 10) ) // 1$- || 1 $-
+ {
+ for (sal_Int32 i = aStr.getLength()-1; i > 0; --i )
+ {
+ if ( (aStr[i] >= '0') && (aStr[i] <= '9') )
+ break;
+ else if ( aStr[i] == '-' )
+ {
+ bNegative = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ if ( !aStr1.isEmpty() && aStr1[0] == '-')
+ bNegative = true;
+ if ( !aStrNum.isEmpty() && aStrNum[0] == '-') // For non-mixed fractions
+ bNegative = true;
+ }
+
+ // remove all unwanted characters
+ // For whole number
+ for (sal_Int32 i=0; i < aStr1.getLength(); )
+ {
+ if ( (aStr1[i] >= '0') && (aStr1[i] <= '9') )
+ i++;
+ else
+ aStr1.remove( i, 1 );
+ }
+ // For decimal
+ if (!bFrac) {
+ for (sal_Int32 i=0; i < aStr2.getLength(); )
+ {
+ if ((aStr2[i] >= '0') && (aStr2[i] <= '9'))
+ ++i;
+ else
+ aStr2.remove(i, 1);
+ }
+ }
+ else {
+ // for numerator
+ for (sal_Int32 i=0; i < aStrNum.getLength(); )
+ {
+ if ((aStrNum[i] >= '0') && (aStrNum[i] <= '9'))
+ ++i;
+ else
+ aStrNum.remove(i, 1);
+ }
+ // for denominator
+ for (sal_Int32 i=0; i < aStrDenom.getLength(); )
+ {
+ if ((aStrDenom[i] >= '0') && (aStrDenom[i] <= '9'))
+ ++i;
+ else
+ aStrDenom.remove(i, 1);
+ }
+ }
+
+
+ if ( !bFrac && aStr1.isEmpty() && aStr2.isEmpty() )
+ return false;
+ else if ( bFrac && aStr1.isEmpty() && (aStrNum.isEmpty() || aStrDenom.isEmpty()) )
+ return false;
+
+ if ( aStr1.isEmpty() )
+ aStr1 = "0";
+ if ( bNegative )
+ aStr1.insert(0, "-");
+
+ // Convert fractional strings
+ if (bFrac) {
+ // Convert to fraction
+ sal_Int64 nWholeNum = o3tl::toInt64(aStr1);
+ aStr1.setLength(0);
+ sal_Int64 nNum = o3tl::toInt64(aStrNum);
+ sal_Int64 nDenom = o3tl::toInt64(aStrDenom);
+ if (nDenom == 0) return false; // Division by zero
+ double nFrac2Dec = nWholeNum + static_cast<double>(nNum)/nDenom; // Convert to double for floating point precision
+ OUStringBuffer aStrFrac;
+ aStrFrac.append(nFrac2Dec);
+ // Reconvert division result to string and parse
+ nDecPos = aStrFrac.indexOf('.');
+ if ( nDecPos >= 0)
+ {
+ aStr1.append(aStrFrac.getStr(), nDecPos);
+ aStr2.append(aStrFrac.getStr()+nDecPos+1);
+ }
+ else
+ aStr1 = aStrFrac;
+ }
+
+ // prune and round fraction
+ bool bRound = false;
+ if (aStr2.getLength() > nDecDigits)
+ {
+ if (aStr2[nDecDigits] >= '5')
+ bRound = true;
+ string::truncateToLength(aStr2, nDecDigits);
+ }
+ if (aStr2.getLength() < nDecDigits)
+ string::padToLength(aStr2, nDecDigits, '0');
+
+ aStr = aStr1 + aStr2;
+
+ // check range
+ nValue = aStr.toInt64();
+ if( nValue == 0 )
+ {
+ // check if string is equivalent to zero
+ sal_Int32 nIndex = bNegative ? 1 : 0;
+ while (nIndex < aStr.getLength() && aStr[nIndex] == '0')
+ ++nIndex;
+ if( nIndex < aStr.getLength() )
+ {
+ rValue = bNegative ? SAL_MIN_INT64 : SAL_MAX_INT64;
+ return true;
+ }
+ }
+ if (bRound)
+ {
+ if ( !bNegative )
+ nValue++;
+ else
+ nValue--;
+ }
+
+ rValue = nValue;
+
+ return true;
+}
+
+void ImplUpdateSeparatorString( OUString& io_rText,
+ const OUString& rOldDecSep, std::u16string_view rNewDecSep,
+ const OUString& rOldThSep, std::u16string_view rNewThSep )
+{
+ OUStringBuffer aBuf( io_rText.getLength() );
+ sal_Int32 nIndexDec = 0, nIndexTh = 0, nIndex = 0;
+
+ const sal_Unicode* pBuffer = io_rText.getStr();
+ while( nIndex != -1 )
+ {
+ nIndexDec = io_rText.indexOf( rOldDecSep, nIndex );
+ nIndexTh = io_rText.indexOf( rOldThSep, nIndex );
+ if( (nIndexTh != -1 && nIndexDec != -1 && nIndexTh < nIndexDec )
+ || (nIndexTh != -1 && nIndexDec == -1)
+ )
+ {
+ aBuf.append( pBuffer + nIndex, nIndexTh - nIndex );
+ aBuf.append( rNewThSep );
+ nIndex = nIndexTh + rOldThSep.getLength();
+ }
+ else if( nIndexDec != -1 )
+ {
+ aBuf.append( pBuffer + nIndex, nIndexDec - nIndex );
+ aBuf.append( rNewDecSep );
+ nIndex = nIndexDec + rOldDecSep.getLength();
+ }
+ else
+ {
+ aBuf.append( pBuffer + nIndex );
+ nIndex = -1;
+ }
+ }
+
+ io_rText = aBuf.makeStringAndClear();
+}
+
+void ImplUpdateSeparators( const OUString& rOldDecSep, std::u16string_view rNewDecSep,
+ const OUString& rOldThSep, std::u16string_view rNewThSep,
+ Edit* pEdit )
+{
+ bool bChangeDec = (rOldDecSep != rNewDecSep);
+ bool bChangeTh = (rOldThSep != rNewThSep );
+
+ if( !(bChangeDec || bChangeTh) )
+ return;
+
+ bool bUpdateMode = pEdit->IsUpdateMode();
+ pEdit->SetUpdateMode( false );
+ OUString aText = pEdit->GetText();
+ ImplUpdateSeparatorString( aText, rOldDecSep, rNewDecSep, rOldThSep, rNewThSep );
+ pEdit->SetText( aText );
+
+ ComboBox* pCombo = dynamic_cast<ComboBox*>(pEdit);
+ if( pCombo )
+ {
+ // update box entries
+ sal_Int32 nEntryCount = pCombo->GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; i++ )
+ {
+ aText = pCombo->GetEntry( i );
+ void* pEntryData = pCombo->GetEntryData( i );
+ ImplUpdateSeparatorString( aText, rOldDecSep, rNewDecSep, rOldThSep, rNewThSep );
+ pCombo->RemoveEntryAt(i);
+ pCombo->InsertEntry( aText, i );
+ pCombo->SetEntryData( i, pEntryData );
+ }
+ }
+ if( bUpdateMode )
+ pEdit->SetUpdateMode( bUpdateMode );
+}
+
+} // namespace
+
+FormatterBase::FormatterBase(Edit* pField)
+{
+ mpField = pField;
+ mpLocaleDataWrapper = nullptr;
+ mbReformat = false;
+ mbStrictFormat = false;
+ mbEmptyFieldValue = false;
+ mbEmptyFieldValueEnabled = false;
+}
+
+FormatterBase::~FormatterBase()
+{
+}
+
+LocaleDataWrapper& FormatterBase::ImplGetLocaleDataWrapper() const
+{
+ if ( !mpLocaleDataWrapper )
+ {
+ mpLocaleDataWrapper.reset( new LocaleDataWrapper( GetLanguageTag() ) );
+ }
+ return *mpLocaleDataWrapper;
+}
+
+/** reset the LocaleDataWrapper when the language tag changes */
+void FormatterBase::ImplResetLocaleDataWrapper() const
+{
+ // just get rid of, the next time it is requested, it will get loaded with the right
+ // language tag
+ mpLocaleDataWrapper.reset();
+}
+
+const LocaleDataWrapper& FormatterBase::GetLocaleDataWrapper() const
+{
+ return ImplGetLocaleDataWrapper();
+}
+
+void FormatterBase::Reformat()
+{
+}
+
+void FormatterBase::ReformatAll()
+{
+ Reformat();
+};
+
+void FormatterBase::SetStrictFormat( bool bStrict )
+{
+ if ( bStrict != mbStrictFormat )
+ {
+ mbStrictFormat = bStrict;
+ if ( mbStrictFormat )
+ ReformatAll();
+ }
+}
+
+const lang::Locale& FormatterBase::GetLocale() const
+{
+ if ( mpField )
+ return mpField->GetSettings().GetLanguageTag().getLocale();
+ else
+ return Application::GetSettings().GetLanguageTag().getLocale();
+}
+
+const LanguageTag& FormatterBase::GetLanguageTag() const
+{
+ if ( mpField )
+ return mpField->GetSettings().GetLanguageTag();
+ else
+ return Application::GetSettings().GetLanguageTag();
+}
+
+void FormatterBase::ImplSetText( const OUString& rText, Selection const * pNewSelection )
+{
+ if ( mpField )
+ {
+ if (pNewSelection)
+ mpField->SetText(rText, *pNewSelection);
+ else
+ {
+ Selection aSel = mpField->GetSelection();
+ aSel.Min() = aSel.Max();
+ mpField->SetText(rText, aSel);
+ }
+ MarkToBeReformatted( false );
+ }
+}
+
+void FormatterBase::SetEmptyFieldValue()
+{
+ if ( mpField )
+ mpField->SetText( OUString() );
+ mbEmptyFieldValue = true;
+}
+
+bool FormatterBase::IsEmptyFieldValue() const
+{
+ return (!mpField || mpField->GetText().isEmpty());
+}
+
+void NumericFormatter::FormatValue(Selection const * pNewSelection)
+{
+ mbFormatting = true;
+ ImplSetText(CreateFieldText(mnLastValue), pNewSelection);
+ mbFormatting = false;
+}
+
+void NumericFormatter::ImplNumericReformat()
+{
+ mnLastValue = GetValue();
+ FormatValue();
+}
+
+NumericFormatter::NumericFormatter(Edit* pEdit)
+ : FormatterBase(pEdit)
+ , mnLastValue(0)
+ , mnMin(0)
+ // a "large" value substantially smaller than SAL_MAX_INT64, to avoid
+ // overflow in computations using this "dummy" value
+ , mnMax(SAL_MAX_INT32)
+ , mbFormatting(false)
+ , mnSpinSize(1)
+ // for fields
+ , mnFirst(mnMin)
+ , mnLast(mnMax)
+ , mnDecimalDigits(0)
+ , mbThousandSep(true)
+{
+ ReformatAll();
+}
+
+NumericFormatter::~NumericFormatter()
+{
+}
+
+void NumericFormatter::SetMin( sal_Int64 nNewMin )
+{
+ mnMin = nNewMin;
+ if ( !IsEmptyFieldValue() )
+ ReformatAll();
+}
+
+void NumericFormatter::SetMax( sal_Int64 nNewMax )
+{
+ mnMax = nNewMax;
+ if ( !IsEmptyFieldValue() )
+ ReformatAll();
+}
+
+void NumericFormatter::SetUseThousandSep( bool bValue )
+{
+ mbThousandSep = bValue;
+ ReformatAll();
+}
+
+void NumericFormatter::SetDecimalDigits( sal_uInt16 nDigits )
+{
+ mnDecimalDigits = nDigits;
+ ReformatAll();
+}
+
+void NumericFormatter::SetValue( sal_Int64 nNewValue )
+{
+ SetUserValue( nNewValue );
+ SetEmptyFieldValueData( false );
+}
+
+OUString NumericFormatter::CreateFieldText( sal_Int64 nValue ) const
+{
+ return ImplGetLocaleDataWrapper().getNum( nValue, GetDecimalDigits(), IsUseThousandSep(), /*ShowTrailingZeros*/true );
+}
+
+void NumericFormatter::ImplSetUserValue( sal_Int64 nNewValue, Selection const * pNewSelection )
+{
+ nNewValue = ClipAgainstMinMax(nNewValue);
+ mnLastValue = nNewValue;
+
+ if ( GetField() )
+ FormatValue(pNewSelection);
+}
+
+void NumericFormatter::SetUserValue( sal_Int64 nNewValue )
+{
+ ImplSetUserValue( nNewValue );
+}
+
+sal_Int64 NumericFormatter::GetValueFromString(const OUString& rStr) const
+{
+ sal_Int64 nTempValue;
+
+ if (ImplNumericGetValue(rStr, nTempValue,
+ GetDecimalDigits(), ImplGetLocaleDataWrapper()))
+ {
+ return ClipAgainstMinMax(nTempValue);
+ }
+ else
+ return mnLastValue;
+}
+
+OUString NumericFormatter::GetValueString() const
+{
+ return Application::GetSettings().GetNeutralLocaleDataWrapper().
+ getNum(GetValue(), GetDecimalDigits(), false, false);
+}
+
+// currently used by online
+void NumericFormatter::SetValueFromString(const OUString& rStr)
+{
+ sal_Int64 nValue;
+
+ if (ImplNumericGetValue(rStr, nValue, GetDecimalDigits(),
+ Application::GetSettings().GetNeutralLocaleDataWrapper()))
+ {
+ ImplNewFieldValue(nValue);
+ }
+ else
+ {
+ SAL_WARN("vcl", "fail to convert the value: " << rStr );
+ }
+}
+
+sal_Int64 NumericFormatter::GetValue() const
+{
+ if (mbFormatting) //don't parse the entry if we're currently formatting what to put in it
+ return mnLastValue;
+
+ return GetField() ? GetValueFromString(GetField()->GetText()) : 0;
+}
+
+sal_Int64 NumericFormatter::Normalize( sal_Int64 nValue ) const
+{
+ return (nValue * ImplPower10( GetDecimalDigits() ) );
+}
+
+sal_Int64 NumericFormatter::Denormalize( sal_Int64 nValue ) const
+{
+ sal_Int64 nFactor = ImplPower10( GetDecimalDigits() );
+
+ if ((nValue < ( SAL_MIN_INT64 + nFactor )) ||
+ (nValue > ( SAL_MAX_INT64 - nFactor )))
+ {
+ return ( nValue / nFactor );
+ }
+
+ if( nValue < 0 )
+ {
+ sal_Int64 nHalf = nFactor / 2;
+ return ((nValue - nHalf) / nFactor );
+ }
+ else
+ {
+ sal_Int64 nHalf = nFactor / 2;
+ return ((nValue + nHalf) / nFactor );
+ }
+}
+
+void NumericFormatter::Reformat()
+{
+ if ( !GetField() )
+ return;
+
+ if ( GetField()->GetText().isEmpty() && ImplGetEmptyFieldValue() )
+ return;
+
+ ImplNumericReformat();
+}
+
+void NumericFormatter::FieldUp()
+{
+ sal_Int64 nValue = GetValue();
+ sal_Int64 nRemainder = nValue % mnSpinSize;
+ if (nValue >= 0)
+ nValue = (nRemainder == 0) ? nValue + mnSpinSize : nValue + mnSpinSize - nRemainder;
+ else
+ nValue = (nRemainder == 0) ? nValue + mnSpinSize : nValue - nRemainder;
+
+ nValue = ClipAgainstMinMax(nValue);
+
+ ImplNewFieldValue( nValue );
+}
+
+void NumericFormatter::FieldDown()
+{
+ sal_Int64 nValue = GetValue();
+ sal_Int64 nRemainder = nValue % mnSpinSize;
+ if (nValue >= 0)
+ nValue = (nRemainder == 0) ? nValue - mnSpinSize : nValue - nRemainder;
+ else
+ nValue = (nRemainder == 0) ? nValue - mnSpinSize : nValue - mnSpinSize - nRemainder;
+
+ nValue = ClipAgainstMinMax(nValue);
+
+ ImplNewFieldValue( nValue );
+}
+
+void NumericFormatter::FieldFirst()
+{
+ ImplNewFieldValue( mnFirst );
+}
+
+void NumericFormatter::FieldLast()
+{
+ ImplNewFieldValue( mnLast );
+}
+
+void NumericFormatter::ImplNewFieldValue( sal_Int64 nNewValue )
+{
+ if ( !GetField() )
+ return;
+
+ // !!! We should check why we do not validate in ImplSetUserValue() if the value was
+ // changed. This should be done there as well since otherwise the call to Modify would not
+ // be allowed. Anyway, the paths from ImplNewFieldValue, ImplSetUserValue, and ImplSetText
+ // should be checked and clearly traced (with comment) in order to find out what happens.
+
+ Selection aSelection = GetField()->GetSelection();
+ aSelection.Justify();
+ OUString aText = GetField()->GetText();
+ // leave it as is if selected until end
+ if ( static_cast<sal_Int32>(aSelection.Max()) == aText.getLength() )
+ {
+ if ( !aSelection.Len() )
+ aSelection.Min() = SELECTION_MAX;
+ aSelection.Max() = SELECTION_MAX;
+ }
+
+ sal_Int64 nOldLastValue = mnLastValue;
+ ImplSetUserValue( nNewValue, &aSelection );
+ mnLastValue = nOldLastValue;
+
+ // Modify during Edit is only set during KeyInput
+ if ( GetField()->GetText() != aText )
+ {
+ GetField()->SetModifyFlag();
+ GetField()->Modify();
+ }
+}
+
+sal_Int64 NumericFormatter::ClipAgainstMinMax(sal_Int64 nValue) const
+{
+ if (nValue > mnMax)
+ nValue = mnMax;
+ else if (nValue < mnMin)
+ nValue = mnMin;
+ return nValue;
+}
+
+namespace
+{
+ Size calcMinimumSize(const Edit &rSpinField, const NumericFormatter &rFormatter)
+ {
+ OUStringBuffer aBuf;
+ sal_Int32 nTextLen;
+
+ nTextLen = OUString(OUString::number(rFormatter.GetMin())).getLength();
+ string::padToLength(aBuf, nTextLen, '9');
+ Size aMinTextSize = rSpinField.CalcMinimumSizeForText(
+ rFormatter.CreateFieldText(OUString::unacquired(aBuf).toInt64()));
+ aBuf.setLength(0);
+
+ nTextLen = OUString(OUString::number(rFormatter.GetMax())).getLength();
+ string::padToLength(aBuf, nTextLen, '9');
+ Size aMaxTextSize = rSpinField.CalcMinimumSizeForText(
+ rFormatter.CreateFieldText(OUString::unacquired(aBuf).toInt64()));
+ aBuf.setLength(0);
+
+ Size aRet(std::max(aMinTextSize.Width(), aMaxTextSize.Width()),
+ std::max(aMinTextSize.Height(), aMaxTextSize.Height()));
+
+ OUStringBuffer sBuf("999999999");
+ sal_uInt16 nDigits = rFormatter.GetDecimalDigits();
+ if (nDigits)
+ {
+ sBuf.append('.');
+ string::padToLength(aBuf, aBuf.getLength() + nDigits, '9');
+ }
+ aMaxTextSize = rSpinField.CalcMinimumSizeForText(sBuf.makeStringAndClear());
+ aRet.setWidth( std::min(aRet.Width(), aMaxTextSize.Width()) );
+
+ return aRet;
+ }
+}
+
+NumericBox::NumericBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox(pParent, nWinStyle)
+ , NumericFormatter(this)
+{
+ Reformat();
+ if ( !(nWinStyle & WB_HIDE ) )
+ Show();
+}
+
+void NumericBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+Size NumericBox::CalcMinimumSize() const
+{
+ Size aRet(calcMinimumSize(*this, *this));
+
+ if (IsDropDownBox())
+ {
+ Size aComboSugg(ComboBox::CalcMinimumSize());
+ aRet.setWidth( std::max(aRet.Width(), aComboSugg.Width()) );
+ aRet.setHeight( std::max(aRet.Height(), aComboSugg.Height()) );
+ }
+
+ return aRet;
+}
+
+bool NumericBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplNumericProcessKeyInput( *rNEvt.GetKeyEvent(), IsStrictFormat(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+bool NumericBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void NumericBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ ComboBox::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplResetLocaleDataWrapper();
+ OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
+ ReformatAll();
+ }
+}
+
+void NumericBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void NumericBox::ImplNumericReformat( const OUString& rStr, sal_Int64& rValue,
+ OUString& rOutStr )
+{
+ if (ImplNumericGetValue(rStr, rValue, GetDecimalDigits(), ImplGetLocaleDataWrapper()))
+ {
+ sal_Int64 nTempVal = ClipAgainstMinMax(rValue);
+ rOutStr = CreateFieldText( nTempVal );
+ }
+}
+
+void NumericBox::ReformatAll()
+{
+ sal_Int64 nValue;
+ OUString aStr;
+ SetUpdateMode( false );
+ sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; i++ )
+ {
+ ImplNumericReformat( GetEntry( i ), nValue, aStr );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ NumericFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+static bool ImplMetricProcessKeyInput( const KeyEvent& rKEvt,
+ bool bUseThousandSep, const LocaleDataWrapper& rWrapper )
+{
+ // no meaningful strict format; therefore allow all characters
+ return ImplNumericProcessKeyInput( rKEvt, false, bUseThousandSep, rWrapper );
+}
+
+static OUString ImplMetricGetUnitText(const OUString& rStr)
+{
+ // fetch unit text
+ OUStringBuffer aStr;
+ for (sal_Int32 i = rStr.getLength()-1; i >= 0; --i)
+ {
+ sal_Unicode c = rStr[i];
+ if ( (c == '\'') || (c == '\"') || (c == '%') || (c == 0x2032) || (c == 0x2033) || unicode::isAlpha(c) || unicode::isControl(c) )
+ aStr.insert(0, c);
+ else
+ {
+ if (!aStr.isEmpty())
+ break;
+ }
+ }
+ return aStr.makeStringAndClear();
+}
+
+// #104355# support localized measurements
+
+static OUString ImplMetricToString( FieldUnit rUnit )
+{
+ // return unit's default string (ie, the first one )
+ for (auto const& elem : ImplGetFieldUnits())
+ {
+ if (elem.second == rUnit)
+ return elem.first;
+ }
+
+ return OUString();
+}
+
+namespace
+{
+ FieldUnit StringToMetric(const OUString &rMetricString)
+ {
+ // return FieldUnit
+ OUString aStr = rMetricString.toAsciiLowerCase().replaceAll(" ", "");
+ for (auto const& elem : ImplGetCleanedFieldUnits())
+ {
+ if ( elem.first == aStr )
+ return elem.second;
+ }
+
+ return FieldUnit::NONE;
+ }
+}
+
+static FieldUnit ImplMetricGetUnit(const OUString& rStr)
+{
+ OUString aStr = ImplMetricGetUnitText(rStr);
+ return StringToMetric(aStr);
+}
+
+static FieldUnit ImplMap2FieldUnit( MapUnit meUnit, tools::Long& nDecDigits )
+{
+ switch( meUnit )
+ {
+ case MapUnit::Map100thMM :
+ nDecDigits -= 2;
+ return FieldUnit::MM;
+ case MapUnit::Map10thMM :
+ nDecDigits -= 1;
+ return FieldUnit::MM;
+ case MapUnit::MapMM :
+ return FieldUnit::MM;
+ case MapUnit::MapCM :
+ return FieldUnit::CM;
+ case MapUnit::Map1000thInch :
+ nDecDigits -= 3;
+ return FieldUnit::INCH;
+ case MapUnit::Map100thInch :
+ nDecDigits -= 2;
+ return FieldUnit::INCH;
+ case MapUnit::Map10thInch :
+ nDecDigits -= 1;
+ return FieldUnit::INCH;
+ case MapUnit::MapInch :
+ return FieldUnit::INCH;
+ case MapUnit::MapPoint :
+ return FieldUnit::POINT;
+ case MapUnit::MapTwip :
+ return FieldUnit::TWIP;
+ default:
+ OSL_FAIL( "default eInUnit" );
+ break;
+ }
+ return FieldUnit::NONE;
+}
+
+static double nonValueDoubleToValueDouble( double nValue )
+{
+ return std::isfinite( nValue ) ? nValue : 0.0;
+}
+
+namespace vcl
+{
+ sal_Int64 ConvertValue(sal_Int64 nValue, sal_Int64 mnBaseValue, sal_uInt16 nDecDigits,
+ FieldUnit eInUnit, FieldUnit eOutUnit)
+ {
+ double nDouble = nonValueDoubleToValueDouble(vcl::ConvertDoubleValue(
+ static_cast<double>(nValue), mnBaseValue, nDecDigits, eInUnit, eOutUnit));
+ sal_Int64 nLong ;
+
+ // caution: precision loss in double cast
+ if ( nDouble <= double(SAL_MIN_INT64) )
+ nLong = SAL_MIN_INT64;
+ else if ( nDouble >= double(SAL_MAX_INT64) )
+ nLong = SAL_MAX_INT64;
+ else
+ nLong = static_cast<sal_Int64>( std::round(nDouble) );
+
+ return nLong;
+ }
+}
+
+namespace {
+
+bool checkConversionUnits(MapUnit eInUnit, FieldUnit eOutUnit)
+{
+ return eOutUnit != FieldUnit::PERCENT
+ && eOutUnit != FieldUnit::CUSTOM
+ && eOutUnit != FieldUnit::NONE
+ && eInUnit != MapUnit::MapPixel
+ && eInUnit != MapUnit::MapSysFont
+ && eInUnit != MapUnit::MapAppFont
+ && eInUnit != MapUnit::MapRelative;
+}
+
+double convertValue( double nValue, tools::Long nDigits, FieldUnit eInUnit, FieldUnit eOutUnit )
+{
+ if ( nDigits < 0 )
+ {
+ while ( nDigits )
+ {
+ nValue += 5;
+ nValue /= 10;
+ nDigits++;
+ }
+ }
+ else
+ {
+ nValue *= ImplPower10(nDigits);
+ }
+
+ if ( eInUnit != eOutUnit )
+ {
+ const o3tl::Length eFrom = FieldToO3tlLength(eInUnit), eTo = FieldToO3tlLength(eOutUnit);
+ if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid)
+ nValue = o3tl::convert(nValue, eFrom, eTo);
+ }
+
+ return nValue;
+}
+
+}
+
+namespace vcl
+{
+ sal_Int64 ConvertValue( sal_Int64 nValue, sal_uInt16 nDigits,
+ MapUnit eInUnit, FieldUnit eOutUnit )
+ {
+ if ( !checkConversionUnits(eInUnit, eOutUnit) )
+ {
+ OSL_FAIL( "invalid parameters" );
+ return nValue;
+ }
+
+ tools::Long nDecDigits = nDigits;
+ FieldUnit eFieldUnit = ImplMap2FieldUnit( eInUnit, nDecDigits );
+
+ // Avoid sal_Int64 <-> double conversion issues if possible:
+ if (eFieldUnit == eOutUnit && nDigits == 0)
+ {
+ return nValue;
+ }
+
+ return static_cast<sal_Int64>(
+ nonValueDoubleToValueDouble(
+ convertValue( nValue, nDecDigits, eFieldUnit, eOutUnit ) ) );
+ }
+
+ double ConvertDoubleValue(double nValue, sal_Int64 mnBaseValue, sal_uInt16 nDecDigits,
+ FieldUnit eInUnit, FieldUnit eOutUnit)
+ {
+ if ( eInUnit != eOutUnit )
+ {
+ if (eInUnit == FieldUnit::PERCENT && mnBaseValue > 0 && nValue > 0)
+ {
+ sal_Int64 nDiv = 100 * ImplPower10(nDecDigits);
+
+ if (mnBaseValue != 1)
+ nValue *= mnBaseValue;
+
+ nValue += nDiv / 2;
+ nValue /= nDiv;
+ }
+ else
+ {
+ const o3tl::Length eFrom = FieldToO3tlLength(eInUnit, o3tl::Length::invalid);
+ const o3tl::Length eTo = FieldToO3tlLength(eOutUnit, o3tl::Length::invalid);
+ if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid)
+ nValue = o3tl::convert(nValue, eFrom, eTo);
+ }
+ }
+
+ return nValue;
+ }
+
+ double ConvertDoubleValue(double nValue, sal_uInt16 nDigits,
+ MapUnit eInUnit, FieldUnit eOutUnit)
+ {
+ if ( !checkConversionUnits(eInUnit, eOutUnit) )
+ {
+ OSL_FAIL( "invalid parameters" );
+ return nValue;
+ }
+
+ tools::Long nDecDigits = nDigits;
+ FieldUnit eFieldUnit = ImplMap2FieldUnit( eInUnit, nDecDigits );
+
+ return convertValue(nValue, nDecDigits, eFieldUnit, eOutUnit);
+ }
+
+ double ConvertDoubleValue(double nValue, sal_uInt16 nDigits,
+ FieldUnit eInUnit, MapUnit eOutUnit)
+ {
+ if ( eInUnit == FieldUnit::PERCENT ||
+ eInUnit == FieldUnit::CUSTOM ||
+ eInUnit == FieldUnit::NONE ||
+ eInUnit == FieldUnit::DEGREE ||
+ eInUnit == FieldUnit::SECOND ||
+ eInUnit == FieldUnit::MILLISECOND ||
+ eInUnit == FieldUnit::PIXEL ||
+ eOutUnit == MapUnit::MapPixel ||
+ eOutUnit == MapUnit::MapSysFont ||
+ eOutUnit == MapUnit::MapAppFont ||
+ eOutUnit == MapUnit::MapRelative )
+ {
+ OSL_FAIL( "invalid parameters" );
+ return nValue;
+ }
+
+ tools::Long nDecDigits = nDigits;
+ FieldUnit eFieldUnit = ImplMap2FieldUnit( eOutUnit, nDecDigits );
+
+ if ( nDecDigits < 0 )
+ {
+ nValue *= ImplPower10(-nDecDigits);
+ }
+ else
+ {
+ nValue /= ImplPower10(nDecDigits);
+ }
+
+ if ( eFieldUnit != eInUnit )
+ {
+ const o3tl::Length eFrom = FieldToO3tlLength(eInUnit, o3tl::Length::invalid);
+ const o3tl::Length eTo = FieldToO3tlLength(eFieldUnit, o3tl::Length::invalid);
+ if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid)
+ nValue = o3tl::convert(nValue, eFrom, eTo);
+ }
+ return nValue;
+ }
+}
+
+namespace vcl
+{
+ bool TextToValue(const OUString& rStr, double& rValue, sal_Int64 nBaseValue,
+ sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper, FieldUnit eUnit)
+ {
+ // Get value
+ sal_Int64 nValue;
+ if ( !ImplNumericGetValue( rStr, nValue, nDecDigits, rLocaleDataWrapper ) )
+ return false;
+
+ // Determine unit
+ FieldUnit eEntryUnit = ImplMetricGetUnit( rStr );
+
+ // Recalculate unit
+ // caution: conversion to double loses precision
+ rValue = vcl::ConvertDoubleValue(static_cast<double>(nValue), nBaseValue, nDecDigits, eEntryUnit, eUnit);
+
+ return true;
+ }
+}
+
+void MetricFormatter::ImplMetricReformat( const OUString& rStr, double& rValue, OUString& rOutStr )
+{
+ if (!vcl::TextToValue(rStr, rValue, 0, GetDecimalDigits(), ImplGetLocaleDataWrapper(), meUnit))
+ return;
+
+ double nTempVal = rValue;
+ // caution: precision loss in double cast
+ if ( nTempVal > GetMax() )
+ nTempVal = static_cast<double>(GetMax());
+ else if ( nTempVal < GetMin())
+ nTempVal = static_cast<double>(GetMin());
+ rOutStr = CreateFieldText( static_cast<sal_Int64>(nTempVal) );
+}
+
+MetricFormatter::MetricFormatter(Edit* pEdit)
+ : NumericFormatter(pEdit)
+ , meUnit(FieldUnit::NONE)
+{
+}
+
+MetricFormatter::~MetricFormatter()
+{
+}
+
+void MetricFormatter::SetUnit( FieldUnit eNewUnit )
+{
+ if (eNewUnit == FieldUnit::MM_100TH)
+ {
+ SetDecimalDigits( GetDecimalDigits() + 2 );
+ meUnit = FieldUnit::MM;
+ }
+ else
+ meUnit = eNewUnit;
+ ReformatAll();
+}
+
+void MetricFormatter::SetCustomUnitText( const OUString& rStr )
+{
+ maCustomUnitText = rStr;
+ ReformatAll();
+}
+
+void MetricFormatter::SetValue( sal_Int64 nNewValue, FieldUnit eInUnit )
+{
+ SetUserValue( nNewValue, eInUnit );
+}
+
+OUString MetricFormatter::CreateFieldText( sal_Int64 nValue ) const
+{
+ //whether percent is separated from its number is locale
+ //specific, pawn it off to icu to decide
+ if (meUnit == FieldUnit::PERCENT)
+ {
+ double dValue = nValue;
+ dValue /= ImplPower10(GetDecimalDigits());
+ return unicode::formatPercent(dValue, GetLanguageTag());
+ }
+
+ OUString aStr = NumericFormatter::CreateFieldText( nValue );
+
+ if( meUnit == FieldUnit::CUSTOM )
+ aStr += maCustomUnitText;
+ else
+ {
+ OUString aSuffix = ImplMetricToString( meUnit );
+ if (meUnit != FieldUnit::NONE && meUnit != FieldUnit::DEGREE && meUnit != FieldUnit::INCH && meUnit != FieldUnit::FOOT)
+ aStr += " ";
+ if (meUnit == FieldUnit::INCH)
+ {
+ OUString sDoublePrime = u"\u2033";
+ if (aSuffix != "\"" && aSuffix != sDoublePrime)
+ aStr += " ";
+ else
+ aSuffix = sDoublePrime;
+ }
+ else if (meUnit == FieldUnit::FOOT)
+ {
+ OUString sPrime = u"\u2032";
+ if (aSuffix != "'" && aSuffix != sPrime)
+ aStr += " ";
+ else
+ aSuffix = sPrime;
+ }
+
+ assert(meUnit != FieldUnit::PERCENT);
+ aStr += aSuffix;
+ }
+ return aStr;
+}
+
+void MetricFormatter::SetUserValue( sal_Int64 nNewValue, FieldUnit eInUnit )
+{
+ // convert to previously configured units
+ nNewValue = vcl::ConvertValue( nNewValue, 0, GetDecimalDigits(), eInUnit, meUnit );
+ NumericFormatter::SetUserValue( nNewValue );
+}
+
+sal_Int64 MetricFormatter::GetValueFromStringUnit(const OUString& rStr, FieldUnit eOutUnit) const
+{
+ double nTempValue;
+ // caution: precision loss in double cast
+ if (!vcl::TextToValue(rStr, nTempValue, 0, GetDecimalDigits(), ImplGetLocaleDataWrapper(), meUnit))
+ nTempValue = static_cast<double>(mnLastValue);
+
+ // caution: precision loss in double cast
+ if (nTempValue > mnMax)
+ nTempValue = static_cast<double>(mnMax);
+ else if (nTempValue < mnMin)
+ nTempValue = static_cast<double>(mnMin);
+
+ // convert to requested units
+ return vcl::ConvertValue(static_cast<sal_Int64>(nTempValue), 0, GetDecimalDigits(), meUnit, eOutUnit);
+}
+
+sal_Int64 MetricFormatter::GetValueFromString(const OUString& rStr) const
+{
+ return GetValueFromStringUnit(rStr, FieldUnit::NONE);
+}
+
+sal_Int64 MetricFormatter::GetValue( FieldUnit eOutUnit ) const
+{
+ return GetField() ? GetValueFromStringUnit(GetField()->GetText(), eOutUnit) : 0;
+}
+
+void MetricFormatter::SetValue( sal_Int64 nValue )
+{
+ // Implementation not inline, because it is a virtual Function
+ SetValue( nValue, FieldUnit::NONE );
+}
+
+void MetricFormatter::SetMin( sal_Int64 nNewMin, FieldUnit eInUnit )
+{
+ // convert to requested units
+ NumericFormatter::SetMin(vcl::ConvertValue(nNewMin, 0, GetDecimalDigits(), eInUnit, meUnit));
+}
+
+sal_Int64 MetricFormatter::GetMin( FieldUnit eOutUnit ) const
+{
+ // convert to requested units
+ return vcl::ConvertValue(NumericFormatter::GetMin(), 0, GetDecimalDigits(), meUnit, eOutUnit);
+}
+
+void MetricFormatter::SetMax( sal_Int64 nNewMax, FieldUnit eInUnit )
+{
+ // convert to requested units
+ NumericFormatter::SetMax(vcl::ConvertValue(nNewMax, 0, GetDecimalDigits(), eInUnit, meUnit));
+}
+
+sal_Int64 MetricFormatter::GetMax( FieldUnit eOutUnit ) const
+{
+ // convert to requested units
+ return vcl::ConvertValue(NumericFormatter::GetMax(), 0, GetDecimalDigits(), meUnit, eOutUnit);
+}
+
+void MetricFormatter::Reformat()
+{
+ if ( !GetField() )
+ return;
+
+ OUString aText = GetField()->GetText();
+
+ OUString aStr;
+ // caution: precision loss in double cast
+ double nTemp = static_cast<double>(mnLastValue);
+ ImplMetricReformat( aText, nTemp, aStr );
+ mnLastValue = static_cast<sal_Int64>(nTemp);
+
+ if ( !aStr.isEmpty() )
+ {
+ ImplSetText( aStr );
+ }
+ else
+ SetValue( mnLastValue );
+}
+
+sal_Int64 MetricFormatter::GetCorrectedValue( FieldUnit eOutUnit ) const
+{
+ // convert to requested units
+ return vcl::ConvertValue(0/*nCorrectedValue*/, 0, GetDecimalDigits(),
+ meUnit, eOutUnit);
+}
+
+MetricField::MetricField(vcl::Window* pParent, WinBits nWinStyle)
+ : SpinField(pParent, nWinStyle, WindowType::METRICFIELD)
+ , MetricFormatter(this)
+{
+ Reformat();
+}
+
+void MetricField::dispose()
+{
+ ClearField();
+ SpinField::dispose();
+}
+
+Size MetricField::CalcMinimumSize() const
+{
+ return calcMinimumSize(*this, *this);
+}
+
+bool MetricField::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "digits")
+ SetDecimalDigits(rValue.toInt32());
+ else if (rKey == "spin-size")
+ SetSpinSize(rValue.toInt32());
+ else
+ return SpinField::set_property(rKey, rValue);
+ return true;
+}
+
+void MetricField::SetUnit( FieldUnit nNewUnit )
+{
+ sal_Int64 nRawMax = GetMax( nNewUnit );
+ sal_Int64 nMax = Denormalize( nRawMax );
+ sal_Int64 nMin = Denormalize( GetMin( nNewUnit ) );
+ sal_Int64 nFirst = Denormalize( GetFirst( nNewUnit ) );
+ sal_Int64 nLast = Denormalize( GetLast( nNewUnit ) );
+
+ MetricFormatter::SetUnit( nNewUnit );
+
+ SetMax( Normalize( nMax ), nNewUnit );
+ SetMin( Normalize( nMin ), nNewUnit );
+ SetFirst( Normalize( nFirst ), nNewUnit );
+ SetLast( Normalize( nLast ), nNewUnit );
+}
+
+void MetricField::SetFirst( sal_Int64 nNewFirst, FieldUnit eInUnit )
+{
+ // convert
+ nNewFirst = vcl::ConvertValue(nNewFirst, 0, GetDecimalDigits(), eInUnit, meUnit);
+ mnFirst = nNewFirst;
+}
+
+sal_Int64 MetricField::GetFirst( FieldUnit eOutUnit ) const
+{
+ // convert
+ return vcl::ConvertValue(mnFirst, 0, GetDecimalDigits(), meUnit, eOutUnit);
+}
+
+void MetricField::SetLast( sal_Int64 nNewLast, FieldUnit eInUnit )
+{
+ // convert
+ nNewLast = vcl::ConvertValue(nNewLast, 0, GetDecimalDigits(), eInUnit, meUnit);
+ mnLast = nNewLast;
+}
+
+sal_Int64 MetricField::GetLast( FieldUnit eOutUnit ) const
+{
+ // convert
+ return vcl::ConvertValue(mnLast, 0, GetDecimalDigits(), meUnit, eOutUnit);
+}
+
+bool MetricField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplMetricProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool MetricField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void MetricField::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ SpinField::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplResetLocaleDataWrapper();
+ OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
+ ReformatAll();
+ }
+}
+
+void MetricField::Modify()
+{
+ MarkToBeReformatted( true );
+ SpinField::Modify();
+}
+
+void MetricField::Up()
+{
+ FieldUp();
+ SpinField::Up();
+}
+
+void MetricField::Down()
+{
+ FieldDown();
+ SpinField::Down();
+}
+
+void MetricField::First()
+{
+ FieldFirst();
+ SpinField::First();
+}
+
+void MetricField::Last()
+{
+ FieldLast();
+ SpinField::Last();
+}
+
+void MetricField::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ SpinField::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("min", GetMin());
+ rJsonWriter.put("max", GetMax());
+ rJsonWriter.put("unit", FieldUnitToString(GetUnit()));
+ OUString sValue = Application::GetSettings().GetNeutralLocaleDataWrapper().
+ getNum(GetValue(), GetDecimalDigits(), false, false);
+ rJsonWriter.put("value", sValue);
+}
+
+FactoryFunction MetricField::GetUITestFactory() const
+{
+ return MetricFieldUIObject::create;
+}
+
+MetricBox::MetricBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox(pParent, nWinStyle)
+ , MetricFormatter(this)
+{
+ Reformat();
+}
+
+void MetricBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+Size MetricBox::CalcMinimumSize() const
+{
+ Size aRet(calcMinimumSize(*this, *this));
+
+ if (IsDropDownBox())
+ {
+ Size aComboSugg(ComboBox::CalcMinimumSize());
+ aRet.setWidth( std::max(aRet.Width(), aComboSugg.Width()) );
+ aRet.setHeight( std::max(aRet.Height(), aComboSugg.Height()) );
+ }
+
+ return aRet;
+}
+
+bool MetricBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplMetricProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+bool MetricBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void MetricBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ ComboBox::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplResetLocaleDataWrapper();
+ OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
+ ReformatAll();
+ }
+}
+
+void MetricBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void MetricBox::ReformatAll()
+{
+ double nValue;
+ OUString aStr;
+ SetUpdateMode( false );
+ sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; i++ )
+ {
+ ImplMetricReformat( GetEntry( i ), nValue, aStr );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ MetricFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+static bool ImplCurrencyProcessKeyInput( const KeyEvent& rKEvt,
+ bool bUseThousandSep, const LocaleDataWrapper& rWrapper )
+{
+ // no strict format set; therefore allow all characters
+ return ImplNumericProcessKeyInput( rKEvt, false, bUseThousandSep, rWrapper );
+}
+
+static bool ImplCurrencyGetValue( const OUString& rStr, sal_Int64& rValue,
+ sal_uInt16 nDecDigits, const LocaleDataWrapper& rWrapper )
+{
+ // fetch number
+ return ImplNumericGetValue( rStr, rValue, nDecDigits, rWrapper, true );
+}
+
+void CurrencyFormatter::ImplCurrencyReformat( const OUString& rStr, OUString& rOutStr )
+{
+ sal_Int64 nValue;
+ if ( !ImplNumericGetValue( rStr, nValue, GetDecimalDigits(), ImplGetLocaleDataWrapper(), true ) )
+ return;
+
+ sal_Int64 nTempVal = nValue;
+ if ( nTempVal > GetMax() )
+ nTempVal = GetMax();
+ else if ( nTempVal < GetMin())
+ nTempVal = GetMin();
+ rOutStr = CreateFieldText( nTempVal );
+}
+
+CurrencyFormatter::CurrencyFormatter(Edit* pField)
+ : NumericFormatter(pField)
+{
+}
+
+CurrencyFormatter::~CurrencyFormatter()
+{
+}
+
+void CurrencyFormatter::SetValue( sal_Int64 nNewValue )
+{
+ SetUserValue( nNewValue );
+ SetEmptyFieldValueData( false );
+}
+
+OUString CurrencyFormatter::CreateFieldText( sal_Int64 nValue ) const
+{
+ return ImplGetLocaleDataWrapper().getCurr( nValue, GetDecimalDigits(),
+ ImplGetLocaleDataWrapper().getCurrSymbol(),
+ IsUseThousandSep() );
+}
+
+sal_Int64 CurrencyFormatter::GetValueFromString(const OUString& rStr) const
+{
+ sal_Int64 nTempValue;
+ if ( ImplCurrencyGetValue( rStr, nTempValue, GetDecimalDigits(), ImplGetLocaleDataWrapper() ) )
+ {
+ return ClipAgainstMinMax(nTempValue);
+ }
+ else
+ return mnLastValue;
+}
+
+void CurrencyFormatter::Reformat()
+{
+ if ( !GetField() )
+ return;
+
+ OUString aStr;
+ ImplCurrencyReformat( GetField()->GetText(), aStr );
+
+ if ( !aStr.isEmpty() )
+ {
+ ImplSetText( aStr );
+ sal_Int64 nTemp = mnLastValue;
+ ImplCurrencyGetValue( aStr, nTemp, GetDecimalDigits(), ImplGetLocaleDataWrapper() );
+ mnLastValue = nTemp;
+ }
+ else
+ SetValue( mnLastValue );
+}
+
+CurrencyField::CurrencyField(vcl::Window* pParent, WinBits nWinStyle)
+ : SpinField(pParent, nWinStyle)
+ , CurrencyFormatter(this)
+{
+ Reformat();
+}
+
+void CurrencyField::dispose()
+{
+ ClearField();
+ SpinField::dispose();
+}
+
+bool CurrencyField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplCurrencyProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool CurrencyField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void CurrencyField::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ SpinField::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplResetLocaleDataWrapper();
+ OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
+ ReformatAll();
+ }
+}
+
+void CurrencyField::Modify()
+{
+ MarkToBeReformatted( true );
+ SpinField::Modify();
+}
+
+void CurrencyField::Up()
+{
+ FieldUp();
+ SpinField::Up();
+}
+
+void CurrencyField::Down()
+{
+ FieldDown();
+ SpinField::Down();
+}
+
+void CurrencyField::First()
+{
+ FieldFirst();
+ SpinField::First();
+}
+
+void CurrencyField::Last()
+{
+ FieldLast();
+ SpinField::Last();
+}
+
+CurrencyBox::CurrencyBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox(pParent, nWinStyle)
+ , CurrencyFormatter(this)
+{
+ Reformat();
+}
+
+void CurrencyBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+bool CurrencyBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplCurrencyProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+bool CurrencyBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void CurrencyBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ ComboBox::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplResetLocaleDataWrapper();
+ OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
+ ReformatAll();
+ }
+}
+
+void CurrencyBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void CurrencyBox::ReformatAll()
+{
+ OUString aStr;
+ SetUpdateMode( false );
+ sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; i++ )
+ {
+ ImplCurrencyReformat( GetEntry( i ), aStr );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ CurrencyFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/field2.cxx b/vcl/source/control/field2.cxx
new file mode 100644
index 000000000..b7a5c2246
--- /dev/null
+++ b/vcl/source/control/field2.cxx
@@ -0,0 +1,3183 @@
+/* -*- 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 <algorithm>
+#include <string_view>
+
+#include <tools/diagnose_ex.h>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <o3tl/string_view.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/field.hxx>
+#include <vcl/unohelp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/weldutils.hxx>
+
+#include <svdata.hxx>
+
+#include <com/sun/star/i18n/XCharacterClassification.hpp>
+#include <com/sun/star/i18n/CalendarFieldIndex.hdl>
+
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/calendarwrapper.hxx>
+#include <unotools/charclass.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::comphelper;
+
+#define EDITMASK_LITERAL 'L'
+#define EDITMASK_ALPHA 'a'
+#define EDITMASK_UPPERALPHA 'A'
+#define EDITMASK_ALPHANUM 'c'
+#define EDITMASK_UPPERALPHANUM 'C'
+#define EDITMASK_NUM 'N'
+#define EDITMASK_NUMSPACE 'n'
+#define EDITMASK_ALLCHAR 'x'
+#define EDITMASK_UPPERALLCHAR 'X'
+
+uno::Reference< i18n::XCharacterClassification > const & ImplGetCharClass()
+{
+ ImplSVData *const pSVData = ImplGetSVData();
+ assert(pSVData);
+
+ if (!pSVData->m_xCharClass.is())
+ {
+ pSVData->m_xCharClass = vcl::unohelper::CreateCharacterClassification();
+ }
+
+ return pSVData->m_xCharClass;
+}
+
+static sal_Unicode* ImplAddString( sal_Unicode* pBuf, const OUString& rStr )
+{
+ memcpy( pBuf, rStr.getStr(), rStr.getLength() * sizeof(sal_Unicode) );
+ pBuf += rStr.getLength();
+ return pBuf;
+}
+
+static sal_Unicode* ImplAddNum( sal_Unicode* pBuf, sal_uLong nNumber, int nMinLen )
+{
+ // fill temp buffer with digits
+ sal_Unicode aTempBuf[30];
+ sal_Unicode* pTempBuf = aTempBuf;
+ do
+ {
+ *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0';
+ pTempBuf++;
+ nNumber /= 10;
+ if ( nMinLen )
+ nMinLen--;
+ }
+ while ( nNumber );
+
+ // fill with zeros up to the minimal length
+ while ( nMinLen > 0 )
+ {
+ *pBuf = '0';
+ pBuf++;
+ nMinLen--;
+ }
+
+ // copy temp buffer to real buffer
+ do
+ {
+ pTempBuf--;
+ *pBuf = *pTempBuf;
+ pBuf++;
+ }
+ while ( pTempBuf != aTempBuf );
+
+ return pBuf;
+}
+
+static sal_Unicode* ImplAddSNum( sal_Unicode* pBuf, sal_Int32 nNumber, int nMinLen )
+{
+ if (nNumber < 0)
+ {
+ *pBuf++ = '-';
+ nNumber = -nNumber;
+ }
+ return ImplAddNum( pBuf, nNumber, nMinLen);
+}
+
+static sal_uInt16 ImplGetNum( const sal_Unicode*& rpBuf, bool& rbError )
+{
+ if ( !*rpBuf )
+ {
+ rbError = true;
+ return 0;
+ }
+
+ sal_uInt16 nNumber = 0;
+ while( ( *rpBuf >= '0' ) && ( *rpBuf <= '9' ) )
+ {
+ nNumber *= 10;
+ nNumber += *rpBuf - '0';
+ rpBuf++;
+ }
+
+ return nNumber;
+}
+
+static void ImplSkipDelimiters( const sal_Unicode*& rpBuf )
+{
+ while( ( *rpBuf == ',' ) || ( *rpBuf == '.' ) || ( *rpBuf == ';' ) ||
+ ( *rpBuf == ':' ) || ( *rpBuf == '-' ) || ( *rpBuf == '/' ) )
+ {
+ rpBuf++;
+ }
+}
+
+static bool ImplIsPatternChar( sal_Unicode cChar, char cEditMask )
+{
+ sal_Int32 nType = 0;
+
+ try
+ {
+ OUString aCharStr(cChar);
+ nType = ImplGetCharClass()->getStringType( aCharStr, 0, aCharStr.getLength(),
+ Application::GetSettings().GetLanguageTag().getLocale() );
+ }
+ catch (const css::uno::Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("vcl.control");
+ return false;
+ }
+
+ if ( (cEditMask == EDITMASK_ALPHA) || (cEditMask == EDITMASK_UPPERALPHA) )
+ {
+ if( !CharClass::isLetterType( nType ) )
+ return false;
+ }
+ else if ( cEditMask == EDITMASK_NUM )
+ {
+ if( !CharClass::isNumericType( nType ) )
+ return false;
+ }
+ else if ( (cEditMask == EDITMASK_ALPHANUM) || (cEditMask == EDITMASK_UPPERALPHANUM) )
+ {
+ if( !CharClass::isLetterNumericType( nType ) )
+ return false;
+ }
+ else if ( (cEditMask == EDITMASK_ALLCHAR) || (cEditMask == EDITMASK_UPPERALLCHAR) )
+ {
+ if ( cChar < 32 )
+ return false;
+ }
+ else if ( cEditMask == EDITMASK_NUMSPACE )
+ {
+ if ( !CharClass::isNumericType( nType ) && ( cChar != ' ' ) )
+ return false;
+ }
+ else
+ return false;
+
+ return true;
+}
+
+static sal_Unicode ImplPatternChar( sal_Unicode cChar, char cEditMask )
+{
+ if ( ImplIsPatternChar( cChar, cEditMask ) )
+ {
+ if ( (cEditMask == EDITMASK_UPPERALPHA) ||
+ (cEditMask == EDITMASK_UPPERALPHANUM) ||
+ ( cEditMask == EDITMASK_UPPERALLCHAR ) )
+ {
+ cChar = ImplGetCharClass()->toUpper(OUString(cChar), 0, 1,
+ Application::GetSettings().GetLanguageTag().getLocale())[0];
+ }
+ return cChar;
+ }
+ else
+ return 0;
+}
+
+static bool ImplCommaPointCharEqual( sal_Unicode c1, sal_Unicode c2 )
+{
+ if ( c1 == c2 )
+ return true;
+ else if ( ((c1 == '.') || (c1 == ',')) &&
+ ((c2 == '.') || (c2 == ',')) )
+ return true;
+ else
+ return false;
+}
+
+static OUString ImplPatternReformat( const OUString& rStr,
+ const OString& rEditMask,
+ std::u16string_view rLiteralMask,
+ sal_uInt16 nFormatFlags )
+{
+ if (rEditMask.isEmpty())
+ return rStr;
+
+ OUStringBuffer aOutStr(rLiteralMask);
+ sal_Unicode cTempChar;
+ sal_Unicode cChar;
+ sal_Unicode cLiteral;
+ char cMask;
+ sal_Int32 nStrIndex = 0;
+ sal_Int32 i = 0;
+ sal_Int32 n;
+
+ while ( i < rEditMask.getLength() )
+ {
+ if ( nStrIndex >= rStr.getLength() )
+ break;
+
+ cChar = rStr[nStrIndex];
+ cLiteral = rLiteralMask[i];
+ cMask = rEditMask[i];
+
+ // current position is a literal
+ if ( cMask == EDITMASK_LITERAL )
+ {
+ // if it is a literal copy otherwise ignore because it might be the next valid
+ // character of the string
+ if ( ImplCommaPointCharEqual( cChar, cLiteral ) )
+ nStrIndex++;
+ else
+ {
+ // Otherwise we check if it is an invalid character. This is the case if it does not
+ // fit in the pattern of the next non-literal character.
+ n = i+1;
+ while ( n < rEditMask.getLength() )
+ {
+ if ( rEditMask[n] != EDITMASK_LITERAL )
+ {
+ if ( !ImplIsPatternChar( cChar, rEditMask[n] ) )
+ nStrIndex++;
+ break;
+ }
+
+ n++;
+ }
+ }
+ }
+ else
+ {
+ // valid character at this position
+ cTempChar = ImplPatternChar( cChar, cMask );
+ if ( cTempChar )
+ {
+ // use this character
+ aOutStr[i] = cTempChar;
+ nStrIndex++;
+ }
+ else
+ {
+ // copy if it is a literal character
+ if ( cLiteral == cChar )
+ nStrIndex++;
+ else
+ {
+ // If the invalid character might be the next literal character then we jump
+ // ahead to it, otherwise we ignore it. Do only if empty literals are allowed.
+ if ( nFormatFlags & PATTERN_FORMAT_EMPTYLITERALS )
+ {
+ n = i;
+ while ( n < rEditMask.getLength() )
+ {
+ if ( rEditMask[n] == EDITMASK_LITERAL )
+ {
+ if ( ImplCommaPointCharEqual( cChar, rLiteralMask[n] ) )
+ i = n+1;
+
+ break;
+ }
+
+ n++;
+ }
+ }
+
+ nStrIndex++;
+ continue;
+ }
+ }
+ }
+
+ i++;
+ }
+
+ return aOutStr.makeStringAndClear();
+}
+
+static void ImplPatternMaxPos( const OUString& rStr, const OString& rEditMask,
+ sal_uInt16 nFormatFlags, bool bSameMask,
+ sal_Int32 nCursorPos, sal_Int32& rPos )
+{
+
+ // last position must not be longer than the contained string
+ sal_Int32 nMaxPos = rStr.getLength();
+
+ // if non empty literals are allowed ignore blanks at the end as well
+ if ( bSameMask && !(nFormatFlags & PATTERN_FORMAT_EMPTYLITERALS) )
+ {
+ while ( nMaxPos )
+ {
+ if ( (rEditMask[nMaxPos-1] != EDITMASK_LITERAL) &&
+ (rStr[nMaxPos-1] != ' ') )
+ break;
+ nMaxPos--;
+ }
+
+ // if we are in front of a literal, continue search until first character after the literal
+ sal_Int32 nTempPos = nMaxPos;
+ while ( nTempPos < rEditMask.getLength() )
+ {
+ if ( rEditMask[nTempPos] != EDITMASK_LITERAL )
+ {
+ nMaxPos = nTempPos;
+ break;
+ }
+ nTempPos++;
+ }
+ }
+
+ if ( rPos > nMaxPos )
+ rPos = nMaxPos;
+
+ // character should not move left
+ if ( rPos < nCursorPos )
+ rPos = nCursorPos;
+}
+
+static OUString ImplPatternProcessStrictModify(const OUString& rText,
+ const OString& rEditMask,
+ std::u16string_view rLiteralMask,
+ bool bSameMask)
+{
+ OUString aText(rText);
+
+ // remove leading blanks
+ if (bSameMask && !rEditMask.isEmpty())
+ {
+ sal_Int32 i = 0;
+ sal_Int32 nMaxLen = aText.getLength();
+ while ( i < nMaxLen )
+ {
+ if ( (rEditMask[i] != EDITMASK_LITERAL) &&
+ (aText[i] != ' ') )
+ break;
+
+ i++;
+ }
+ // keep all literal characters
+ while ( i && (rEditMask[i] == EDITMASK_LITERAL) )
+ i--;
+ aText = aText.copy( i );
+ }
+
+ return ImplPatternReformat(aText, rEditMask, rLiteralMask, 0);
+}
+
+static void ImplPatternProcessStrictModify( Edit* pEdit,
+ const OString& rEditMask,
+ std::u16string_view rLiteralMask,
+ bool bSameMask )
+{
+ OUString aText = pEdit->GetText();
+ OUString aNewText = ImplPatternProcessStrictModify(aText,
+ rEditMask,
+ rLiteralMask,
+ bSameMask);
+
+ if ( aNewText == aText )
+ return;
+
+ // adjust selection such that it remains at the end if it was there before
+ Selection aSel = pEdit->GetSelection();
+ sal_Int64 nMaxSel = std::max( aSel.Min(), aSel.Max() );
+ if ( nMaxSel >= aText.getLength() )
+ {
+ sal_Int32 nMaxPos = aNewText.getLength();
+ ImplPatternMaxPos(aNewText, rEditMask, 0, bSameMask, nMaxSel, nMaxPos);
+ if ( aSel.Min() == aSel.Max() )
+ {
+ aSel.Min() = nMaxPos;
+ aSel.Max() = aSel.Min();
+ }
+ else if ( aSel.Min() > aSel.Max() )
+ aSel.Min() = nMaxPos;
+ else
+ aSel.Max() = nMaxPos;
+ }
+ pEdit->SetText( aNewText, aSel );
+}
+
+static void ImplPatternProcessStrictModify( weld::Entry& rEntry,
+ const OString& rEditMask,
+ std::u16string_view rLiteralMask,
+ bool bSameMask )
+{
+ OUString aText = rEntry.get_text();
+ OUString aNewText = ImplPatternProcessStrictModify(aText,
+ rEditMask,
+ rLiteralMask,
+ bSameMask);
+
+ if (aNewText == aText)
+ return;
+
+ // adjust selection such that it remains at the end if it was there before
+ int nStartPos, nEndPos;
+ rEntry.get_selection_bounds(nStartPos, nEndPos);
+
+ int nMaxSel = std::max(nStartPos, nEndPos);
+ if (nMaxSel >= aText.getLength())
+ {
+ sal_Int32 nMaxPos = aNewText.getLength();
+ ImplPatternMaxPos(aNewText, rEditMask, 0, bSameMask, nMaxSel, nMaxPos);
+ if (nStartPos == nEndPos)
+ {
+ nStartPos = nMaxPos;
+ nEndPos = nMaxPos;
+ }
+ else if (nStartPos > nMaxPos)
+ nStartPos = nMaxPos;
+ else
+ nEndPos = nMaxPos;
+ }
+ rEntry.set_text(aNewText);
+ rEntry.select_region(nStartPos, nEndPos);
+}
+
+static sal_Int32 ImplPatternLeftPos(std::string_view rEditMask, sal_Int32 nCursorPos)
+{
+ // search non-literal predecessor
+ sal_Int32 nNewPos = nCursorPos;
+ sal_Int32 nTempPos = nNewPos;
+ while ( nTempPos )
+ {
+ if ( rEditMask[nTempPos-1] != EDITMASK_LITERAL )
+ {
+ nNewPos = nTempPos-1;
+ break;
+ }
+ nTempPos--;
+ }
+ return nNewPos;
+}
+
+static sal_Int32 ImplPatternRightPos( const OUString& rStr, const OString& rEditMask,
+ sal_uInt16 nFormatFlags, bool bSameMask,
+ sal_Int32 nCursorPos )
+{
+ // search non-literal successor
+ sal_Int32 nNewPos = nCursorPos;
+ ;
+ for(sal_Int32 nTempPos = nNewPos+1; nTempPos < rEditMask.getLength(); ++nTempPos )
+ {
+ if ( rEditMask[nTempPos] != EDITMASK_LITERAL )
+ {
+ nNewPos = nTempPos;
+ break;
+ }
+ }
+ ImplPatternMaxPos( rStr, rEditMask, nFormatFlags, bSameMask, nCursorPos, nNewPos );
+ return nNewPos;
+}
+
+namespace
+{
+ class IEditImplementation
+ {
+ public:
+ virtual ~IEditImplementation() {}
+
+ virtual OUString GetText() const = 0;
+ virtual void SetText(const OUString& rStr, const Selection& rSelection) = 0;
+
+ virtual Selection GetSelection() const = 0;
+ virtual void SetSelection(const Selection& rSelection) = 0;
+
+ virtual bool IsInsertMode() const = 0;
+
+ virtual void SetModified() = 0;
+ };
+}
+
+static bool ImplPatternProcessKeyInput( IEditImplementation& rEdit, const KeyEvent& rKEvt,
+ const OString& rEditMask,
+ std::u16string_view rLiteralMask,
+ bool bStrictFormat,
+ bool bSameMask,
+ bool& rbInKeyInput )
+{
+ if ( rEditMask.isEmpty() || !bStrictFormat )
+ return false;
+
+ sal_uInt16 nFormatFlags = 0;
+ Selection aOldSel = rEdit.GetSelection();
+ vcl::KeyCode aCode = rKEvt.GetKeyCode();
+ sal_Unicode cChar = rKEvt.GetCharCode();
+ sal_uInt16 nKeyCode = aCode.GetCode();
+ bool bShift = aCode.IsShift();
+ sal_Int32 nCursorPos = static_cast<sal_Int32>(aOldSel.Max());
+ sal_Int32 nNewPos;
+ sal_Int32 nTempPos;
+
+ if ( nKeyCode && !aCode.IsMod1() && !aCode.IsMod2() )
+ {
+ if ( nKeyCode == KEY_LEFT )
+ {
+ Selection aSel( ImplPatternLeftPos( rEditMask, nCursorPos ) );
+ if ( bShift )
+ aSel.Min() = aOldSel.Min();
+ rEdit.SetSelection( aSel );
+ return true;
+ }
+ else if ( nKeyCode == KEY_RIGHT )
+ {
+ // Use the start of selection as minimum; even a small position is allowed in case that
+ // all was selected by the focus
+ Selection aSel( aOldSel );
+ aSel.Justify();
+ nCursorPos = aSel.Min();
+ aSel.Max() = ImplPatternRightPos( rEdit.GetText(), rEditMask, nFormatFlags, bSameMask, nCursorPos );
+ if ( bShift )
+ aSel.Min() = aOldSel.Min();
+ else
+ aSel.Min() = aSel.Max();
+ rEdit.SetSelection( aSel );
+ return true;
+ }
+ else if ( nKeyCode == KEY_HOME )
+ {
+ // Home is the position of the first non-literal character
+ nNewPos = 0;
+ while ( (nNewPos < rEditMask.getLength()) &&
+ (rEditMask[nNewPos] == EDITMASK_LITERAL) )
+ nNewPos++;
+
+ // Home should not move to the right
+ if ( nCursorPos < nNewPos )
+ nNewPos = nCursorPos;
+ Selection aSel( nNewPos );
+ if ( bShift )
+ aSel.Min() = aOldSel.Min();
+ rEdit.SetSelection( aSel );
+ return true;
+ }
+ else if ( nKeyCode == KEY_END )
+ {
+ // End is position of last non-literal character
+ nNewPos = rEditMask.getLength();
+ while ( nNewPos &&
+ (rEditMask[nNewPos-1] == EDITMASK_LITERAL) )
+ nNewPos--;
+ // Use the start of selection as minimum; even a small position is allowed in case that
+ // all was selected by the focus
+ Selection aSel( aOldSel );
+ aSel.Justify();
+ nCursorPos = static_cast<sal_Int32>(aSel.Min());
+ ImplPatternMaxPos( rEdit.GetText(), rEditMask, nFormatFlags, bSameMask, nCursorPos, nNewPos );
+ aSel.Max() = nNewPos;
+ if ( bShift )
+ aSel.Min() = aOldSel.Min();
+ else
+ aSel.Min() = aSel.Max();
+ rEdit.SetSelection( aSel );
+ return true;
+ }
+ else if ( (nKeyCode == KEY_BACKSPACE) || (nKeyCode == KEY_DELETE) )
+ {
+ OUString aOldStr( rEdit.GetText() );
+ OUStringBuffer aStr( aOldStr );
+ Selection aSel = aOldSel;
+
+ aSel.Justify();
+ nNewPos = static_cast<sal_Int32>(aSel.Min());
+
+ // if selection then delete it
+ if ( aSel.Len() )
+ {
+ if ( bSameMask )
+ aStr.remove( static_cast<sal_Int32>(aSel.Min()), static_cast<sal_Int32>(aSel.Len()) );
+ else
+ {
+ std::u16string_view aRep = rLiteralMask.substr( static_cast<sal_Int32>(aSel.Min()), static_cast<sal_Int32>(aSel.Len()) );
+ aStr.remove( aSel.Min(), aRep.size() );
+ aStr.insert( aSel.Min(), aRep );
+ }
+ }
+ else
+ {
+ if ( nKeyCode == KEY_BACKSPACE )
+ {
+ nTempPos = nNewPos;
+ nNewPos = ImplPatternLeftPos( rEditMask, nTempPos );
+ }
+ else
+ nTempPos = ImplPatternRightPos( aStr.toString(), rEditMask, nFormatFlags, bSameMask, nNewPos );
+
+ if ( nNewPos != nTempPos )
+ {
+ if ( bSameMask )
+ {
+ if ( rEditMask[nNewPos] != EDITMASK_LITERAL )
+ aStr.remove( nNewPos, 1 );
+ }
+ else
+ {
+ aStr[nNewPos] = rLiteralMask[nNewPos];
+ }
+ }
+ }
+
+ OUString sStr = aStr.makeStringAndClear();
+ if ( aOldStr != sStr )
+ {
+ if ( bSameMask )
+ sStr = ImplPatternReformat( sStr, rEditMask, rLiteralMask, nFormatFlags );
+ rbInKeyInput = true;
+ rEdit.SetText( sStr, Selection( nNewPos ) );
+ rEdit.SetModified();
+ rbInKeyInput = false;
+ }
+ else
+ rEdit.SetSelection( Selection( nNewPos ) );
+
+ return true;
+ }
+ else if ( nKeyCode == KEY_INSERT )
+ {
+ // you can only set InsertMode for a PatternField if the
+ // mask is equal at all input positions
+ if ( !bSameMask )
+ {
+ return true;
+ }
+ }
+ }
+
+ if ( rKEvt.GetKeyCode().IsMod2() || (cChar < 32) || (cChar == 127) )
+ return false;
+
+ Selection aSel = aOldSel;
+ aSel.Justify();
+ nNewPos = aSel.Min();
+
+ if ( nNewPos < rEditMask.getLength() )
+ {
+ sal_Unicode cPattChar = ImplPatternChar( cChar, rEditMask[nNewPos] );
+ if ( cPattChar )
+ cChar = cPattChar;
+ else
+ {
+ // If no valid character, check if the user wanted to jump to next literal. We do this
+ // only if we're after a character, so that literals that were skipped automatically
+ // do not influence the position anymore.
+ if ( nNewPos &&
+ (rEditMask[nNewPos-1] != EDITMASK_LITERAL) &&
+ !aSel.Len() )
+ {
+ // search for next character not being a literal
+ nTempPos = nNewPos;
+ while ( nTempPos < rEditMask.getLength() )
+ {
+ if ( rEditMask[nTempPos] == EDITMASK_LITERAL )
+ {
+ // only valid if no literal present
+ if ( (rEditMask[nTempPos+1] != EDITMASK_LITERAL ) &&
+ ImplCommaPointCharEqual( cChar, rLiteralMask[nTempPos] ) )
+ {
+ nTempPos++;
+ ImplPatternMaxPos( rEdit.GetText(), rEditMask, nFormatFlags, bSameMask, nNewPos, nTempPos );
+ if ( nTempPos > nNewPos )
+ {
+ rEdit.SetSelection( Selection( nTempPos ) );
+ return true;
+ }
+ }
+ break;
+ }
+ nTempPos++;
+ }
+ }
+
+ cChar = 0;
+ }
+ }
+ else
+ cChar = 0;
+ if ( cChar )
+ {
+ OUStringBuffer aStr(rEdit.GetText());
+ bool bError = false;
+ if ( bSameMask && rEdit.IsInsertMode() )
+ {
+ // crop spaces and literals at the end until current position
+ sal_Int32 n = aStr.getLength();
+ while ( n && (n > nNewPos) )
+ {
+ if ( (aStr[n-1] != ' ') &&
+ ((n > rEditMask.getLength()) || (rEditMask[n-1] != EDITMASK_LITERAL)) )
+ break;
+
+ n--;
+ }
+ aStr.truncate( n );
+
+ if ( aSel.Len() )
+ aStr.remove( aSel.Min(), aSel.Len() );
+
+ if ( aStr.getLength() < rEditMask.getLength() )
+ {
+ // possibly extend string until cursor position
+ if ( aStr.getLength() < nNewPos )
+ aStr.append( rLiteralMask.substr(aStr.getLength(), nNewPos-aStr.getLength()) );
+ if ( nNewPos < aStr.getLength() )
+ aStr.insert( cChar, nNewPos );
+ else if ( nNewPos < rEditMask.getLength() )
+ aStr.append(cChar);
+ aStr = ImplPatternReformat( aStr.toString(), rEditMask, rLiteralMask, nFormatFlags );
+ }
+ else
+ bError = true;
+ }
+ else
+ {
+ if ( aSel.Len() )
+ {
+ // delete selection
+ std::u16string_view aRep = rLiteralMask.substr( aSel.Min(), aSel.Len() );
+ aStr.remove( aSel.Min(), aRep.size() );
+ aStr.insert( aSel.Min(), aRep );
+ }
+
+ if ( nNewPos < aStr.getLength() )
+ aStr[nNewPos] = cChar;
+ else if ( nNewPos < rEditMask.getLength() )
+ aStr.append(cChar);
+ }
+
+ if ( !bError )
+ {
+ rbInKeyInput = true;
+ const OUString sStr = aStr.makeStringAndClear();
+ Selection aNewSel( ImplPatternRightPos( sStr, rEditMask, nFormatFlags, bSameMask, nNewPos ) );
+ rEdit.SetText( sStr, aNewSel );
+ rEdit.SetModified();
+ rbInKeyInput = false;
+ }
+ }
+
+ return true;
+}
+
+namespace
+{
+ bool ImplSetMask(const OString& rEditMask, OUString& rLiteralMask)
+ {
+ bool bSameMask = true;
+
+ if (rEditMask.getLength() != rLiteralMask.getLength())
+ {
+ OUStringBuffer aBuf(rLiteralMask);
+ if (rEditMask.getLength() < aBuf.getLength())
+ aBuf.setLength(rEditMask.getLength());
+ else
+ comphelper::string::padToLength(aBuf, rEditMask.getLength(), ' ');
+ rLiteralMask = aBuf.makeStringAndClear();
+ }
+
+ // Strict mode allows only the input mode if only equal characters are allowed as mask and if
+ // only spaces are specified which are not allowed by the mask
+ sal_Int32 i = 0;
+ char c = 0;
+ while ( i < rEditMask.getLength() )
+ {
+ char cTemp = rEditMask[i];
+ if ( cTemp != EDITMASK_LITERAL )
+ {
+ if ( (cTemp == EDITMASK_ALLCHAR) ||
+ (cTemp == EDITMASK_UPPERALLCHAR) ||
+ (cTemp == EDITMASK_NUMSPACE) )
+ {
+ bSameMask = false;
+ break;
+ }
+ if ( i < rLiteralMask.getLength() )
+ {
+ if ( rLiteralMask[i] != ' ' )
+ {
+ bSameMask = false;
+ break;
+ }
+ }
+ if ( !c )
+ c = cTemp;
+ if ( cTemp != c )
+ {
+ bSameMask = false;
+ break;
+ }
+ }
+ i++;
+ }
+
+ return bSameMask;
+ }
+}
+
+PatternFormatter::PatternFormatter(Edit* pEdit)
+ : FormatterBase(pEdit)
+{
+ mbSameMask = true;
+ mbInPattKeyInput = false;
+}
+
+PatternFormatter::~PatternFormatter()
+{
+}
+
+void PatternFormatter::SetMask( const OString& rEditMask,
+ const OUString& rLiteralMask )
+{
+ m_aEditMask = rEditMask;
+ maLiteralMask = rLiteralMask;
+ mbSameMask = ImplSetMask(m_aEditMask, maLiteralMask);
+ ReformatAll();
+}
+
+namespace
+{
+ class EntryImplementation : public IEditImplementation
+ {
+ public:
+ EntryImplementation(weld::PatternFormatter& rFormatter)
+ : m_rFormatter(rFormatter)
+ , m_rEntry(rFormatter.get_widget())
+ {
+ }
+
+ virtual OUString GetText() const override
+ {
+ return m_rEntry.get_text();
+ }
+
+ virtual void SetText(const OUString& rStr, const Selection& rSelection) override
+ {
+ m_rEntry.set_text(rStr);
+ SetSelection(rSelection);
+ }
+
+ virtual Selection GetSelection() const override
+ {
+ int nStartPos, nEndPos;
+ m_rEntry.get_selection_bounds(nStartPos, nEndPos);
+ return Selection(nStartPos, nEndPos);
+ }
+
+ virtual void SetSelection(const Selection& rSelection) override
+ {
+ auto nMin = rSelection.Min();
+ auto nMax = rSelection.Max();
+ m_rEntry.select_region(nMin < 0 ? 0 : nMin, nMax == SELECTION_MAX ? -1 : nMax);
+ }
+
+ virtual bool IsInsertMode() const override
+ {
+ return !m_rEntry.get_overwrite_mode();
+ }
+
+ virtual void SetModified() override
+ {
+ m_rFormatter.Modify();
+ }
+
+ private:
+ weld::PatternFormatter& m_rFormatter;
+ weld::Entry& m_rEntry;
+ };
+}
+
+namespace weld
+{
+ void PatternFormatter::SetStrictFormat(bool bStrict)
+ {
+ if (bStrict != m_bStrictFormat)
+ {
+ m_bStrictFormat = bStrict;
+ if (m_bStrictFormat)
+ ReformatAll();
+ }
+ }
+
+ void PatternFormatter::SetMask(const OString& rEditMask,
+ const OUString& rLiteralMask)
+ {
+ m_aEditMask = rEditMask;
+ m_aLiteralMask = rLiteralMask;
+ m_bSameMask = ImplSetMask(m_aEditMask, m_aLiteralMask);
+ ReformatAll();
+ }
+
+ void PatternFormatter::ReformatAll()
+ {
+ m_rEntry.set_text(ImplPatternReformat(m_rEntry.get_text(), m_aEditMask, m_aLiteralMask, 0/*nFormatFlags*/));
+ if (!m_bSameMask && m_bStrictFormat && m_rEntry.get_editable())
+ m_rEntry.set_overwrite_mode(true);
+ }
+
+ void PatternFormatter::EntryGainFocus()
+ {
+ m_bReformat = false;
+ }
+
+ void PatternFormatter::EntryLostFocus()
+ {
+ if (m_bReformat)
+ ReformatAll();
+ }
+
+ void PatternFormatter::Modify()
+ {
+ if (!m_bInPattKeyInput)
+ {
+ if (m_bStrictFormat)
+ ImplPatternProcessStrictModify(m_rEntry, m_aEditMask, m_aLiteralMask, m_bSameMask);
+ else
+ m_bReformat = true;
+ }
+ m_aModifyHdl.Call(m_rEntry);
+ }
+
+ IMPL_LINK(PatternFormatter, KeyInputHdl, const KeyEvent&, rKEvt, bool)
+ {
+ if (m_aKeyPressHdl.Call(rKEvt))
+ return true;
+ if (rKEvt.GetKeyCode().IsMod2())
+ return false;
+ EntryImplementation aAdapt(*this);
+ return ImplPatternProcessKeyInput(aAdapt, rKEvt, m_aEditMask, m_aLiteralMask,
+ m_bStrictFormat,
+ m_bSameMask, m_bInPattKeyInput);
+ }
+}
+
+void PatternFormatter::SetString( const OUString& rStr )
+{
+ if ( GetField() )
+ {
+ GetField()->SetText( rStr );
+ MarkToBeReformatted( false );
+ }
+}
+
+OUString PatternFormatter::GetString() const
+{
+ if ( !GetField() )
+ return OUString();
+ else
+ return ImplPatternReformat( GetField()->GetText(), m_aEditMask, maLiteralMask, 0/*nFormatFlags*/ );
+}
+
+void PatternFormatter::Reformat()
+{
+ if ( GetField() )
+ {
+ ImplSetText( ImplPatternReformat( GetField()->GetText(), m_aEditMask, maLiteralMask, 0/*nFormatFlags*/ ) );
+ if ( !mbSameMask && IsStrictFormat() && !GetField()->IsReadOnly() )
+ GetField()->SetInsertMode( false );
+ }
+}
+
+PatternField::PatternField(vcl::Window* pParent, WinBits nWinStyle)
+ : SpinField(pParent, nWinStyle)
+ , PatternFormatter(this)
+{
+ Reformat();
+}
+
+void PatternField::dispose()
+{
+ ClearField();
+ SpinField::dispose();
+}
+
+namespace
+{
+ class EditImplementation : public IEditImplementation
+ {
+ public:
+ EditImplementation(Edit& rEdit)
+ : m_rEdit(rEdit)
+ {
+ }
+
+ virtual OUString GetText() const override
+ {
+ return m_rEdit.GetText();
+ }
+
+ virtual void SetText(const OUString& rStr, const Selection& rSelection) override
+ {
+ m_rEdit.SetText(rStr, rSelection);
+ }
+
+ virtual Selection GetSelection() const override
+ {
+ return m_rEdit.GetSelection();
+ }
+
+ virtual void SetSelection(const Selection& rSelection) override
+ {
+ m_rEdit.SetSelection(rSelection);
+ }
+
+ virtual bool IsInsertMode() const override
+ {
+ return m_rEdit.IsInsertMode();
+ }
+
+ virtual void SetModified() override
+ {
+ m_rEdit.SetModifyFlag();
+ m_rEdit.Modify();
+ }
+
+ private:
+ Edit& m_rEdit;
+ };
+}
+
+bool PatternField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ EditImplementation aAdapt(*GetField());
+ if ( ImplPatternProcessKeyInput( aAdapt, *rNEvt.GetKeyEvent(), GetEditMask(), GetLiteralMask(),
+ IsStrictFormat(),
+ ImplIsSameMask(), ImplGetInPattKeyInput() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool PatternField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void PatternField::Modify()
+{
+ if ( !ImplGetInPattKeyInput() )
+ {
+ if ( IsStrictFormat() )
+ ImplPatternProcessStrictModify( GetField(), GetEditMask(), GetLiteralMask(), ImplIsSameMask() );
+ else
+ MarkToBeReformatted( true );
+ }
+
+ SpinField::Modify();
+}
+
+PatternBox::PatternBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox( pParent, nWinStyle )
+ , PatternFormatter(this)
+{
+ Reformat();
+}
+
+void PatternBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+bool PatternBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ EditImplementation aAdapt(*GetField());
+ if ( ImplPatternProcessKeyInput( aAdapt, *rNEvt.GetKeyEvent(), GetEditMask(), GetLiteralMask(),
+ IsStrictFormat(),
+ ImplIsSameMask(), ImplGetInPattKeyInput() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+bool PatternBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void PatternBox::Modify()
+{
+ if ( !ImplGetInPattKeyInput() )
+ {
+ if ( IsStrictFormat() )
+ ImplPatternProcessStrictModify( GetField(), GetEditMask(), GetLiteralMask(), ImplIsSameMask() );
+ else
+ MarkToBeReformatted( true );
+ }
+
+ ComboBox::Modify();
+}
+
+void PatternBox::ReformatAll()
+{
+ OUString aStr;
+ SetUpdateMode( false );
+ const sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; ++i )
+ {
+ aStr = ImplPatternReformat( GetEntry( i ), GetEditMask(), GetLiteralMask(), 0/*nFormatFlags*/ );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ PatternFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+static ExtDateFieldFormat ImplGetExtFormat( LongDateOrder eOld )
+{
+ switch( eOld )
+ {
+ case LongDateOrder::YDM:
+ case LongDateOrder::DMY: return ExtDateFieldFormat::ShortDDMMYY;
+ case LongDateOrder::MDY: return ExtDateFieldFormat::ShortMMDDYY;
+ case LongDateOrder::YMD:
+ default: return ExtDateFieldFormat::ShortYYMMDD;
+ }
+}
+
+static sal_uInt16 ImplCutNumberFromString( OUString& rStr )
+{
+ sal_Int32 i1 = 0;
+ while (i1 != rStr.getLength() && (rStr[i1] < '0' || rStr[i1] > '9')) {
+ ++i1;
+ }
+ sal_Int32 i2 = i1;
+ while (i2 != rStr.getLength() && rStr[i2] >= '0' && rStr[i2] <= '9') {
+ ++i2;
+ }
+ sal_Int32 nValue = o3tl::toInt32(rStr.subView(i1, i2-i1));
+ rStr = rStr.copy(std::min(i2+1, rStr.getLength()));
+ return nValue;
+}
+
+static bool ImplCutMonthName( OUString& rStr, std::u16string_view _rLookupMonthName )
+{
+ sal_Int32 index = 0;
+ rStr = rStr.replaceFirst(_rLookupMonthName, "", &index);
+ return index >= 0;
+}
+
+static sal_uInt16 ImplGetMonthFromCalendarItem( OUString& rStr, const uno::Sequence< i18n::CalendarItem2 >& rMonths )
+{
+ const sal_uInt16 nMonths = rMonths.getLength();
+ for (sal_uInt16 i=0; i < nMonths; ++i)
+ {
+ // long month name?
+ if ( ImplCutMonthName( rStr, rMonths[i].FullName ) )
+ return i+1;
+
+ // short month name?
+ if ( ImplCutMonthName( rStr, rMonths[i].AbbrevName ) )
+ return i+1;
+ }
+ return 0;
+}
+
+static sal_uInt16 ImplCutMonthFromString( OUString& rStr, OUString& rCalendarName,
+ const LocaleDataWrapper& rLocaleData, const CalendarWrapper& rCalendarWrapper )
+{
+ const OUString aDefaultCalendarName( rCalendarWrapper.getUniqueID());
+ rCalendarName = aDefaultCalendarName;
+
+ // Search for a month name of the loaded default calendar.
+ const uno::Sequence< i18n::CalendarItem2 > aMonths = rCalendarWrapper.getMonths();
+ sal_uInt16 nMonth = ImplGetMonthFromCalendarItem( rStr, aMonths);
+ if (nMonth > 0)
+ return nMonth;
+
+ // And also possessive genitive and partitive month names.
+ const uno::Sequence< i18n::CalendarItem2 > aGenitiveMonths = rCalendarWrapper.getGenitiveMonths();
+ if (aGenitiveMonths != aMonths)
+ {
+ nMonth = ImplGetMonthFromCalendarItem( rStr, aGenitiveMonths);
+ if (nMonth > 0)
+ return nMonth;
+ }
+ const uno::Sequence< i18n::CalendarItem2 > aPartitiveMonths = rCalendarWrapper.getPartitiveMonths();
+ if (aPartitiveMonths != aMonths)
+ {
+ nMonth = ImplGetMonthFromCalendarItem( rStr, aPartitiveMonths);
+ if (nMonth > 0)
+ return nMonth;
+ }
+
+ // Check if there are more calendars and try them if so, as the long date
+ // format is obtained from the number formatter this is possible (e.g.
+ // ar_DZ "[~hijri] ...")
+ const uno::Sequence< i18n::Calendar2 > aCalendars = rLocaleData.getAllCalendars();
+ if (aCalendars.getLength() > 1)
+ {
+ for (const auto& rCalendar : aCalendars)
+ {
+ if (rCalendar.Name != aDefaultCalendarName)
+ {
+ rCalendarName = rCalendar.Name;
+
+ nMonth = ImplGetMonthFromCalendarItem( rStr, rCalendar.Months);
+ if (nMonth > 0)
+ return nMonth;
+
+ if (rCalendar.Months != rCalendar.GenitiveMonths)
+ {
+ nMonth = ImplGetMonthFromCalendarItem( rStr, rCalendar.GenitiveMonths);
+ if (nMonth > 0)
+ return nMonth;
+ }
+
+ if (rCalendar.Months != rCalendar.PartitiveMonths)
+ {
+ nMonth = ImplGetMonthFromCalendarItem( rStr, rCalendar.PartitiveMonths);
+ if (nMonth > 0)
+ return nMonth;
+ }
+
+ rCalendarName = aDefaultCalendarName;
+ }
+ }
+ }
+
+ return ImplCutNumberFromString( rStr );
+}
+
+static OUString ImplGetDateSep( const LocaleDataWrapper& rLocaleDataWrapper, ExtDateFieldFormat eFormat )
+{
+ if ( ( eFormat == ExtDateFieldFormat::ShortYYMMDD_DIN5008 ) || ( eFormat == ExtDateFieldFormat::ShortYYYYMMDD_DIN5008 ) )
+ return "-";
+ else
+ return rLocaleDataWrapper.getDateSep();
+}
+
+static bool ImplDateProcessKeyInput( const KeyEvent& rKEvt, ExtDateFieldFormat eFormat,
+ const LocaleDataWrapper& rLocaleDataWrapper )
+{
+ sal_Unicode cChar = rKEvt.GetCharCode();
+ sal_uInt16 nGroup = rKEvt.GetKeyCode().GetGroup();
+ return !((nGroup == KEYGROUP_FKEYS) ||
+ (nGroup == KEYGROUP_CURSOR) ||
+ (nGroup == KEYGROUP_MISC)||
+ ((cChar >= '0') && (cChar <= '9')) ||
+ (cChar == ImplGetDateSep( rLocaleDataWrapper, eFormat )[0]));
+}
+
+bool DateFormatter::TextToDate(const OUString& rStr, Date& rDate, ExtDateFieldFormat eDateOrder,
+ const LocaleDataWrapper& rLocaleDataWrapper, const CalendarWrapper& rCalendarWrapper)
+{
+ sal_uInt16 nDay = 0;
+ sal_uInt16 nMonth = 0;
+ sal_uInt16 nYear = 0;
+ bool bError = false;
+ OUString aStr( rStr );
+
+ if ( eDateOrder == ExtDateFieldFormat::SystemLong )
+ {
+ OUString aCalendarName;
+ LongDateOrder eFormat = rLocaleDataWrapper.getLongDateOrder();
+ switch( eFormat )
+ {
+ case LongDateOrder::MDY:
+ nMonth = ImplCutMonthFromString( aStr, aCalendarName, rLocaleDataWrapper, rCalendarWrapper );
+ nDay = ImplCutNumberFromString( aStr );
+ nYear = ImplCutNumberFromString( aStr );
+ break;
+ case LongDateOrder::DMY:
+ nDay = ImplCutNumberFromString( aStr );
+ nMonth = ImplCutMonthFromString( aStr, aCalendarName, rLocaleDataWrapper, rCalendarWrapper );
+ nYear = ImplCutNumberFromString( aStr );
+ break;
+ case LongDateOrder::YDM:
+ nYear = ImplCutNumberFromString( aStr );
+ nDay = ImplCutNumberFromString( aStr );
+ nMonth = ImplCutMonthFromString( aStr, aCalendarName, rLocaleDataWrapper, rCalendarWrapper );
+ break;
+ case LongDateOrder::YMD:
+ default:
+ nYear = ImplCutNumberFromString( aStr );
+ nMonth = ImplCutMonthFromString( aStr, aCalendarName, rLocaleDataWrapper, rCalendarWrapper );
+ nDay = ImplCutNumberFromString( aStr );
+ break;
+ }
+ if (aCalendarName != "gregorian")
+ {
+ // Calendar widget is Gregorian, convert date.
+ // Need full date.
+ bError = !nDay || !nMonth || !nYear;
+ if (!bError)
+ {
+ CalendarWrapper aCW( rLocaleDataWrapper.getComponentContext());
+ aCW.loadCalendar( aCalendarName, rLocaleDataWrapper.getLoadedLanguageTag().getLocale());
+ aCW.setDateTime(0.5); // get rid of current time, set some day noon
+ aCW.setValue( i18n::CalendarFieldIndex::DAY_OF_MONTH, nDay);
+ aCW.setValue( i18n::CalendarFieldIndex::MONTH, nMonth - 1);
+ aCW.setValue( i18n::CalendarFieldIndex::YEAR, nYear);
+ bError = !aCW.isValid();
+ if (!bError)
+ {
+ Date aDate = aCW.getEpochStart() + aCW.getDateTime();
+ nYear = aDate.GetYear();
+ nMonth = aDate.GetMonth();
+ nDay = aDate.GetDay();
+ }
+ }
+ }
+ }
+ else
+ {
+ bool bYear = true;
+
+ // Check if year is present:
+ OUString aDateSep = ImplGetDateSep( rLocaleDataWrapper, eDateOrder );
+ sal_Int32 nSepPos = aStr.indexOf( aDateSep );
+ if ( nSepPos < 0 )
+ return false;
+ nSepPos = aStr.indexOf( aDateSep, nSepPos+1 );
+ if ( ( nSepPos < 0 ) || ( nSepPos == (aStr.getLength()-1) ) )
+ {
+ bYear = false;
+ nYear = Date( Date::SYSTEM ).GetYearUnsigned();
+ }
+
+ const sal_Unicode* pBuf = aStr.getStr();
+ ImplSkipDelimiters( pBuf );
+
+ switch ( eDateOrder )
+ {
+ case ExtDateFieldFormat::ShortDDMMYY:
+ case ExtDateFieldFormat::ShortDDMMYYYY:
+ {
+ nDay = ImplGetNum( pBuf, bError );
+ ImplSkipDelimiters( pBuf );
+ nMonth = ImplGetNum( pBuf, bError );
+ ImplSkipDelimiters( pBuf );
+ if ( bYear )
+ nYear = ImplGetNum( pBuf, bError );
+ }
+ break;
+ case ExtDateFieldFormat::ShortMMDDYY:
+ case ExtDateFieldFormat::ShortMMDDYYYY:
+ {
+ nMonth = ImplGetNum( pBuf, bError );
+ ImplSkipDelimiters( pBuf );
+ nDay = ImplGetNum( pBuf, bError );
+ ImplSkipDelimiters( pBuf );
+ if ( bYear )
+ nYear = ImplGetNum( pBuf, bError );
+ }
+ break;
+ case ExtDateFieldFormat::ShortYYMMDD:
+ case ExtDateFieldFormat::ShortYYYYMMDD:
+ case ExtDateFieldFormat::ShortYYMMDD_DIN5008:
+ case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008:
+ {
+ if ( bYear )
+ nYear = ImplGetNum( pBuf, bError );
+ ImplSkipDelimiters( pBuf );
+ nMonth = ImplGetNum( pBuf, bError );
+ ImplSkipDelimiters( pBuf );
+ nDay = ImplGetNum( pBuf, bError );
+ }
+ break;
+
+ default:
+ {
+ OSL_FAIL( "DateOrder???" );
+ }
+ }
+ }
+
+ if ( bError || !nDay || !nMonth )
+ return false;
+
+ Date aNewDate( nDay, nMonth, nYear );
+ DateFormatter::ExpandCentury( aNewDate, officecfg::Office::Common::DateFormat::TwoDigitYear::get() );
+ if ( aNewDate.IsValidDate() )
+ {
+ rDate = aNewDate;
+ return true;
+ }
+ return false;
+}
+
+void DateFormatter::ImplDateReformat( const OUString& rStr, OUString& rOutStr )
+{
+ Date aDate( Date::EMPTY );
+ if (!TextToDate(rStr, aDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper()))
+ return;
+
+ Date aTempDate = aDate;
+ if ( aTempDate > GetMax() )
+ aTempDate = GetMax();
+ else if ( aTempDate < GetMin() )
+ aTempDate = GetMin();
+
+ rOutStr = ImplGetDateAsText( aTempDate );
+}
+
+namespace
+{
+ ExtDateFieldFormat ResolveSystemFormat(ExtDateFieldFormat eDateFormat, const LocaleDataWrapper& rLocaleData)
+ {
+ if (eDateFormat <= ExtDateFieldFormat::SystemShortYYYY)
+ {
+ bool bShowCentury = (eDateFormat == ExtDateFieldFormat::SystemShortYYYY);
+ switch (rLocaleData.getDateOrder())
+ {
+ case DateOrder::DMY:
+ eDateFormat = bShowCentury ? ExtDateFieldFormat::ShortDDMMYYYY : ExtDateFieldFormat::ShortDDMMYY;
+ break;
+ case DateOrder::MDY:
+ eDateFormat = bShowCentury ? ExtDateFieldFormat::ShortMMDDYYYY : ExtDateFieldFormat::ShortMMDDYY;
+ break;
+ default:
+ eDateFormat = bShowCentury ? ExtDateFieldFormat::ShortYYYYMMDD : ExtDateFieldFormat::ShortYYMMDD;
+ }
+ }
+ return eDateFormat;
+ }
+}
+
+OUString DateFormatter::FormatDate(const Date& rDate, ExtDateFieldFormat eExtFormat,
+ const LocaleDataWrapper& rLocaleData,
+ const Formatter::StaticFormatter& rStaticFormatter)
+{
+ bool bShowCentury = false;
+ switch (eExtFormat)
+ {
+ case ExtDateFieldFormat::SystemShortYYYY:
+ case ExtDateFieldFormat::SystemLong:
+ case ExtDateFieldFormat::ShortDDMMYYYY:
+ case ExtDateFieldFormat::ShortMMDDYYYY:
+ case ExtDateFieldFormat::ShortYYYYMMDD:
+ case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008:
+ {
+ bShowCentury = true;
+ }
+ break;
+ default:
+ {
+ bShowCentury = false;
+ }
+ }
+
+ if ( !bShowCentury )
+ {
+ // Check if I have to use force showing the century
+ sal_uInt16 nTwoDigitYearStart = officecfg::Office::Common::DateFormat::TwoDigitYear::get();
+ sal_uInt16 nYear = rDate.GetYearUnsigned();
+
+ // If year is not in double digit range
+ if ( (nYear < nTwoDigitYearStart) || (nYear >= nTwoDigitYearStart+100) )
+ bShowCentury = true;
+ }
+
+ sal_Unicode aBuf[128];
+ sal_Unicode* pBuf = aBuf;
+
+ eExtFormat = ResolveSystemFormat(eExtFormat, rLocaleData);
+
+ OUString aDateSep = ImplGetDateSep( rLocaleData, eExtFormat );
+ sal_uInt16 nDay = rDate.GetDay();
+ sal_uInt16 nMonth = rDate.GetMonth();
+ sal_Int16 nYear = rDate.GetYear();
+ sal_uInt16 nYearLen = bShowCentury ? 4 : 2;
+
+ if ( !bShowCentury )
+ nYear %= 100;
+
+ switch (eExtFormat)
+ {
+ case ExtDateFieldFormat::SystemLong:
+ {
+ SvNumberFormatter* pFormatter = rStaticFormatter;
+ const LanguageTag aFormatterLang( pFormatter->GetLanguageTag());
+ const sal_uInt32 nIndex = pFormatter->GetFormatIndex( NF_DATE_SYSTEM_LONG,
+ rLocaleData.getLanguageTag().getLanguageType(false));
+ OUString aStr;
+ const Color* pCol;
+ pFormatter->GetOutputString( rDate - pFormatter->GetNullDate(), nIndex, aStr, &pCol);
+ // Reset to what other uses may expect.
+ pFormatter->ChangeIntl( aFormatterLang.getLanguageType(false));
+ return aStr;
+ }
+ case ExtDateFieldFormat::ShortDDMMYY:
+ case ExtDateFieldFormat::ShortDDMMYYYY:
+ {
+ pBuf = ImplAddNum( pBuf, nDay, 2 );
+ pBuf = ImplAddString( pBuf, aDateSep );
+ pBuf = ImplAddNum( pBuf, nMonth, 2 );
+ pBuf = ImplAddString( pBuf, aDateSep );
+ pBuf = ImplAddSNum( pBuf, nYear, nYearLen );
+ }
+ break;
+ case ExtDateFieldFormat::ShortMMDDYY:
+ case ExtDateFieldFormat::ShortMMDDYYYY:
+ {
+ pBuf = ImplAddNum( pBuf, nMonth, 2 );
+ pBuf = ImplAddString( pBuf, aDateSep );
+ pBuf = ImplAddNum( pBuf, nDay, 2 );
+ pBuf = ImplAddString( pBuf, aDateSep );
+ pBuf = ImplAddSNum( pBuf, nYear, nYearLen );
+ }
+ break;
+ case ExtDateFieldFormat::ShortYYMMDD:
+ case ExtDateFieldFormat::ShortYYYYMMDD:
+ case ExtDateFieldFormat::ShortYYMMDD_DIN5008:
+ case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008:
+ {
+ pBuf = ImplAddSNum( pBuf, nYear, nYearLen );
+ pBuf = ImplAddString( pBuf, aDateSep );
+ pBuf = ImplAddNum( pBuf, nMonth, 2 );
+ pBuf = ImplAddString( pBuf, aDateSep );
+ pBuf = ImplAddNum( pBuf, nDay, 2 );
+ }
+ break;
+ default:
+ {
+ OSL_FAIL( "DateOrder???" );
+ }
+ }
+
+ return OUString(aBuf, pBuf-aBuf);
+}
+
+OUString DateFormatter::ImplGetDateAsText( const Date& rDate ) const
+{
+ return DateFormatter::FormatDate(rDate, GetExtDateFormat(), ImplGetLocaleDataWrapper(), maStaticFormatter);
+}
+
+static void ImplDateIncrementDay( Date& rDate, bool bUp )
+{
+ DateFormatter::ExpandCentury( rDate );
+ rDate.AddDays( bUp ? 1 : -1 );
+}
+
+static void ImplDateIncrementMonth( Date& rDate, bool bUp )
+{
+ DateFormatter::ExpandCentury( rDate );
+ rDate.AddMonths( bUp ? 1 : -1 );
+}
+
+static void ImplDateIncrementYear( Date& rDate, bool bUp )
+{
+ DateFormatter::ExpandCentury( rDate );
+ rDate.AddYears( bUp ? 1 : -1 );
+}
+
+bool DateFormatter::ImplAllowMalformedInput() const
+{
+ return !IsEnforceValidValue();
+}
+
+int DateFormatter::GetDateArea(ExtDateFieldFormat& eFormat, std::u16string_view rText, int nCursor, const LocaleDataWrapper& rLocaleDataWrapper)
+{
+ sal_Int8 nDateArea = 0;
+
+ if ( eFormat == ExtDateFieldFormat::SystemLong )
+ {
+ eFormat = ImplGetExtFormat(rLocaleDataWrapper.getLongDateOrder());
+ nDateArea = 1;
+ }
+ else
+ {
+ // search area
+ size_t nPos = 0;
+ OUString aDateSep = ImplGetDateSep(rLocaleDataWrapper, eFormat);
+ for ( sal_Int8 i = 1; i <= 3; i++ )
+ {
+ nPos = rText.find( aDateSep, nPos );
+ if (nPos == std::u16string_view::npos || static_cast<sal_Int32>(nPos) >= nCursor)
+ {
+ nDateArea = i;
+ break;
+ }
+ else
+ nPos++;
+ }
+ }
+
+ return nDateArea;
+}
+
+void DateField::ImplDateSpinArea( bool bUp )
+{
+ // increment days if all is selected
+ if ( !GetField() )
+ return;
+
+ Date aDate( GetDate() );
+ Selection aSelection = GetField()->GetSelection();
+ aSelection.Justify();
+ OUString aText( GetText() );
+ if ( static_cast<sal_Int32>(aSelection.Len()) == aText.getLength() )
+ ImplDateIncrementDay( aDate, bUp );
+ else
+ {
+ ExtDateFieldFormat eFormat = GetExtDateFormat( true );
+ sal_Int8 nDateArea = GetDateArea(eFormat, aText, aSelection.Max(), ImplGetLocaleDataWrapper());
+
+ switch( eFormat )
+ {
+ case ExtDateFieldFormat::ShortMMDDYY:
+ case ExtDateFieldFormat::ShortMMDDYYYY:
+ switch( nDateArea )
+ {
+ case 1: ImplDateIncrementMonth( aDate, bUp );
+ break;
+ case 2: ImplDateIncrementDay( aDate, bUp );
+ break;
+ case 3: ImplDateIncrementYear( aDate, bUp );
+ break;
+ }
+ break;
+ case ExtDateFieldFormat::ShortDDMMYY:
+ case ExtDateFieldFormat::ShortDDMMYYYY:
+ switch( nDateArea )
+ {
+ case 1: ImplDateIncrementDay( aDate, bUp );
+ break;
+ case 2: ImplDateIncrementMonth( aDate, bUp );
+ break;
+ case 3: ImplDateIncrementYear( aDate, bUp );
+ break;
+ }
+ break;
+ case ExtDateFieldFormat::ShortYYMMDD:
+ case ExtDateFieldFormat::ShortYYYYMMDD:
+ case ExtDateFieldFormat::ShortYYMMDD_DIN5008:
+ case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008:
+ switch( nDateArea )
+ {
+ case 1: ImplDateIncrementYear( aDate, bUp );
+ break;
+ case 2: ImplDateIncrementMonth( aDate, bUp );
+ break;
+ case 3: ImplDateIncrementDay( aDate, bUp );
+ break;
+ }
+ break;
+ default:
+ OSL_FAIL( "invalid conversion" );
+ break;
+ }
+ }
+
+ ImplNewFieldValue( aDate );
+}
+
+DateFormatter::DateFormatter(Edit* pEdit)
+ : FormatterBase(pEdit)
+ , maFieldDate(0)
+ , maLastDate(0)
+ , maMin(1, 1, 1900)
+ , maMax(31, 12, 2200)
+ , mbLongFormat(false)
+ , mbShowDateCentury(true)
+ , mnExtDateFormat(ExtDateFieldFormat::SystemShort)
+ , mbEnforceValidValue(true)
+{
+}
+
+DateFormatter::~DateFormatter()
+{
+}
+
+CalendarWrapper& DateFormatter::GetCalendarWrapper() const
+{
+ if (!mxCalendarWrapper)
+ {
+ const_cast<DateFormatter*>(this)->mxCalendarWrapper.reset( new CalendarWrapper( comphelper::getProcessComponentContext() ) );
+ mxCalendarWrapper->loadDefaultCalendar( GetLocale() );
+ }
+
+ return *mxCalendarWrapper;
+}
+
+void DateFormatter::SetExtDateFormat( ExtDateFieldFormat eFormat )
+{
+ mnExtDateFormat = eFormat;
+ ReformatAll();
+}
+
+ExtDateFieldFormat DateFormatter::GetExtDateFormat( bool bResolveSystemFormat ) const
+{
+ ExtDateFieldFormat eDateFormat = mnExtDateFormat;
+
+ if (bResolveSystemFormat)
+ eDateFormat = ResolveSystemFormat(eDateFormat, ImplGetLocaleDataWrapper());
+
+ return eDateFormat;
+}
+
+void DateFormatter::ReformatAll()
+{
+ Reformat();
+}
+
+void DateFormatter::SetMin( const Date& rNewMin )
+{
+ maMin = rNewMin;
+ if ( !IsEmptyFieldValue() )
+ ReformatAll();
+}
+
+void DateFormatter::SetMax( const Date& rNewMax )
+{
+ maMax = rNewMax;
+ if ( !IsEmptyFieldValue() )
+ ReformatAll();
+}
+
+void DateFormatter::SetLongFormat( bool bLong )
+{
+ mbLongFormat = bLong;
+
+ // #91913# Remove LongFormat and DateShowCentury - redundant
+ if ( bLong )
+ {
+ SetExtDateFormat( ExtDateFieldFormat::SystemLong );
+ }
+ else
+ {
+ if( mnExtDateFormat == ExtDateFieldFormat::SystemLong )
+ SetExtDateFormat( ExtDateFieldFormat::SystemShort );
+ }
+
+ ReformatAll();
+}
+
+namespace
+{
+ ExtDateFieldFormat ChangeDateCentury(ExtDateFieldFormat eExtDateFormat, bool bShowDateCentury)
+ {
+ // #91913# Remove LongFormat and DateShowCentury - redundant
+ if (bShowDateCentury)
+ {
+ switch (eExtDateFormat)
+ {
+ case ExtDateFieldFormat::SystemShort:
+ case ExtDateFieldFormat::SystemShortYY:
+ eExtDateFormat = ExtDateFieldFormat::SystemShortYYYY; break;
+ case ExtDateFieldFormat::ShortDDMMYY:
+ eExtDateFormat = ExtDateFieldFormat::ShortDDMMYYYY; break;
+ case ExtDateFieldFormat::ShortMMDDYY:
+ eExtDateFormat = ExtDateFieldFormat::ShortMMDDYYYY; break;
+ case ExtDateFieldFormat::ShortYYMMDD:
+ eExtDateFormat = ExtDateFieldFormat::ShortYYYYMMDD; break;
+ case ExtDateFieldFormat::ShortYYMMDD_DIN5008:
+ eExtDateFormat = ExtDateFieldFormat::ShortYYYYMMDD_DIN5008; break;
+ default:
+ ;
+ }
+ }
+ else
+ {
+ switch (eExtDateFormat)
+ {
+ case ExtDateFieldFormat::SystemShort:
+ case ExtDateFieldFormat::SystemShortYYYY:
+ eExtDateFormat = ExtDateFieldFormat::SystemShortYY; break;
+ case ExtDateFieldFormat::ShortDDMMYYYY:
+ eExtDateFormat = ExtDateFieldFormat::ShortDDMMYY; break;
+ case ExtDateFieldFormat::ShortMMDDYYYY:
+ eExtDateFormat = ExtDateFieldFormat::ShortMMDDYY; break;
+ case ExtDateFieldFormat::ShortYYYYMMDD:
+ eExtDateFormat = ExtDateFieldFormat::ShortYYMMDD; break;
+ case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008:
+ eExtDateFormat = ExtDateFieldFormat::ShortYYMMDD_DIN5008; break;
+ default:
+ ;
+ }
+ }
+
+ return eExtDateFormat;
+ }
+}
+
+void DateFormatter::SetShowDateCentury( bool bShowDateCentury )
+{
+ mbShowDateCentury = bShowDateCentury;
+
+ SetExtDateFormat(ChangeDateCentury(GetExtDateFormat(), bShowDateCentury));
+
+ ReformatAll();
+}
+
+void DateFormatter::SetDate( const Date& rNewDate )
+{
+ ImplSetUserDate( rNewDate );
+ maFieldDate = maLastDate;
+ maLastDate = GetDate();
+}
+
+void DateFormatter::ImplSetUserDate( const Date& rNewDate, Selection const * pNewSelection )
+{
+ Date aNewDate = rNewDate;
+ if ( aNewDate > maMax )
+ aNewDate = maMax;
+ else if ( aNewDate < maMin )
+ aNewDate = maMin;
+ maLastDate = aNewDate;
+
+ if ( GetField() )
+ ImplSetText( ImplGetDateAsText( aNewDate ), pNewSelection );
+}
+
+void DateFormatter::ImplNewFieldValue( const Date& rDate )
+{
+ if ( !GetField() )
+ return;
+
+ Selection aSelection = GetField()->GetSelection();
+ aSelection.Justify();
+ OUString aText = GetField()->GetText();
+
+ // If selected until the end then keep it that way
+ if ( static_cast<sal_Int32>(aSelection.Max()) == aText.getLength() )
+ {
+ if ( !aSelection.Len() )
+ aSelection.Min() = SELECTION_MAX;
+ aSelection.Max() = SELECTION_MAX;
+ }
+
+ Date aOldLastDate = maLastDate;
+ ImplSetUserDate( rDate, &aSelection );
+ maLastDate = aOldLastDate;
+
+ // Modify at Edit is only set at KeyInput
+ if ( GetField()->GetText() != aText )
+ {
+ GetField()->SetModifyFlag();
+ GetField()->Modify();
+ }
+}
+
+Date DateFormatter::GetDate() const
+{
+ Date aDate( Date::EMPTY );
+
+ if ( GetField() )
+ {
+ if (TextToDate(GetField()->GetText(), aDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper()))
+ {
+ if ( aDate > maMax )
+ aDate = maMax;
+ else if ( aDate < maMin )
+ aDate = maMin;
+ }
+ else
+ {
+ // !!! We should find out why dates are treated differently than other fields (see
+ // also bug: 52384)
+
+ if ( !ImplAllowMalformedInput() )
+ {
+ if ( maLastDate.GetDate() )
+ aDate = maLastDate;
+ else if ( !IsEmptyFieldValueEnabled() )
+ aDate = Date( Date::SYSTEM );
+ }
+ else
+ aDate = Date( Date::EMPTY ); // set invalid date
+ }
+ }
+
+ return aDate;
+}
+
+void DateFormatter::SetEmptyDate()
+{
+ FormatterBase::SetEmptyFieldValue();
+}
+
+bool DateFormatter::IsEmptyDate() const
+{
+ bool bEmpty = FormatterBase::IsEmptyFieldValue();
+
+ if ( GetField() && MustBeReformatted() && IsEmptyFieldValueEnabled() )
+ {
+ if ( GetField()->GetText().isEmpty() )
+ {
+ bEmpty = true;
+ }
+ else if ( !maLastDate.GetDate() )
+ {
+ Date aDate( Date::EMPTY );
+ bEmpty = !TextToDate(GetField()->GetText(), aDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper());
+ }
+ }
+ return bEmpty;
+}
+
+void DateFormatter::Reformat()
+{
+ if ( !GetField() )
+ return;
+
+ if ( GetField()->GetText().isEmpty() && ImplGetEmptyFieldValue() )
+ return;
+
+ OUString aStr;
+ ImplDateReformat( GetField()->GetText(), aStr );
+
+ if ( !aStr.isEmpty() )
+ {
+ ImplSetText( aStr );
+ (void)TextToDate(aStr, maLastDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper());
+ }
+ else
+ {
+ if ( maLastDate.GetDate() )
+ SetDate( maLastDate );
+ else if ( !IsEmptyFieldValueEnabled() )
+ SetDate( Date( Date::SYSTEM ) );
+ else
+ {
+ ImplSetText( OUString() );
+ SetEmptyFieldValueData( true );
+ }
+ }
+}
+
+void DateFormatter::ExpandCentury( Date& rDate )
+{
+ ExpandCentury(rDate, officecfg::Office::Common::DateFormat::TwoDigitYear::get());
+}
+
+void DateFormatter::ExpandCentury( Date& rDate, sal_uInt16 nTwoDigitYearStart )
+{
+ sal_Int16 nDateYear = rDate.GetYear();
+ if ( 0 <= nDateYear && nDateYear < 100 )
+ {
+ sal_uInt16 nCentury = nTwoDigitYearStart / 100;
+ if ( nDateYear < (nTwoDigitYearStart % 100) )
+ nCentury++;
+ rDate.SetYear( nDateYear + (nCentury*100) );
+ }
+}
+
+DateField::DateField( vcl::Window* pParent, WinBits nWinStyle ) :
+ SpinField( pParent, nWinStyle ),
+ DateFormatter(this),
+ maFirst( GetMin() ),
+ maLast( GetMax() )
+{
+ SetText( ImplGetLocaleDataWrapper().getDate( ImplGetFieldDate() ) );
+ Reformat();
+ ResetLastDate();
+}
+
+void DateField::dispose()
+{
+ ClearField();
+ SpinField::dispose();
+}
+
+bool DateField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && IsStrictFormat() &&
+ ( GetExtDateFormat() != ExtDateFieldFormat::SystemLong ) &&
+ !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplDateProcessKeyInput( *rNEvt.GetKeyEvent(), GetExtDateFormat( true ), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool DateField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() )
+ {
+ // !!! We should find out why dates are treated differently than other fields (see
+ // also bug: 52384)
+
+ bool bTextLen = !GetText().isEmpty();
+ if ( bTextLen || !IsEmptyFieldValueEnabled() )
+ {
+ if ( !ImplAllowMalformedInput() )
+ Reformat();
+ else
+ {
+ Date aDate( 0, 0, 0 );
+ if (TextToDate(GetText(), aDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper()))
+ // even with strict text analysis, our text is a valid date -> do a complete
+ // reformat
+ Reformat();
+ }
+ }
+ else
+ {
+ ResetLastDate();
+ SetEmptyFieldValueData( true );
+ }
+ }
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void DateField::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ SpinField::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & (AllSettingsFlags::LOCALE|AllSettingsFlags::MISC)) )
+ {
+ if (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE)
+ ImplResetLocaleDataWrapper();
+ ReformatAll();
+ }
+}
+
+void DateField::Modify()
+{
+ MarkToBeReformatted( true );
+ SpinField::Modify();
+}
+
+void DateField::Up()
+{
+ ImplDateSpinArea( true );
+ SpinField::Up();
+}
+
+void DateField::Down()
+{
+ ImplDateSpinArea( false );
+ SpinField::Down();
+}
+
+void DateField::First()
+{
+ ImplNewFieldValue( maFirst );
+ SpinField::First();
+}
+
+void DateField::Last()
+{
+ ImplNewFieldValue( maLast );
+ SpinField::Last();
+}
+
+DateBox::DateBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox( pParent, nWinStyle )
+ , DateFormatter(this)
+{
+ SetText( ImplGetLocaleDataWrapper().getDate( ImplGetFieldDate() ) );
+ Reformat();
+}
+
+void DateBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+bool DateBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && IsStrictFormat() &&
+ ( GetExtDateFormat() != ExtDateFieldFormat::SystemLong ) &&
+ !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplDateProcessKeyInput( *rNEvt.GetKeyEvent(), GetExtDateFormat( true ), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+void DateBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ ComboBox::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ ImplResetLocaleDataWrapper();
+ ReformatAll();
+ }
+}
+
+bool DateBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() )
+ {
+ bool bTextLen = !GetText().isEmpty();
+ if ( bTextLen || !IsEmptyFieldValueEnabled() )
+ Reformat();
+ else
+ {
+ ResetLastDate();
+ SetEmptyFieldValueData( true );
+ }
+ }
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void DateBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void DateBox::ReformatAll()
+{
+ OUString aStr;
+ SetUpdateMode( false );
+ const sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; ++i )
+ {
+ ImplDateReformat( GetEntry( i ), aStr );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ DateFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+namespace weld
+{
+ CalendarWrapper& DateFormatter::GetCalendarWrapper() const
+ {
+ if (!m_xCalendarWrapper)
+ {
+ m_xCalendarWrapper.reset(new CalendarWrapper(comphelper::getProcessComponentContext()));
+ m_xCalendarWrapper->loadDefaultCalendar(Application::GetSettings().GetLanguageTag().getLocale());
+ }
+ return *m_xCalendarWrapper;
+ }
+
+ void DateFormatter::SetShowDateCentury(bool bShowDateCentury)
+ {
+ m_eFormat = ChangeDateCentury(m_eFormat, bShowDateCentury);
+
+ ReFormat();
+ }
+
+ void DateFormatter::SetDate(const Date& rDate)
+ {
+ auto nDate = rDate.GetDate();
+ bool bForceOutput = GetEntryText().isEmpty() && rDate == GetDate();
+ if (bForceOutput)
+ {
+ ImplSetValue(nDate, true);
+ return;
+ }
+ SetValue(nDate);
+ }
+
+ Date DateFormatter::GetDate()
+ {
+ return Date(GetValue());
+ }
+
+ void DateFormatter::SetMin(const Date& rNewMin)
+ {
+ SetMinValue(rNewMin.GetDate());
+ }
+
+ void DateFormatter::SetMax(const Date& rNewMax)
+ {
+ SetMaxValue(rNewMax.GetDate());
+ }
+
+ OUString DateFormatter::FormatNumber(int nValue) const
+ {
+ const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
+ return ::DateFormatter::FormatDate(Date(nValue), m_eFormat, rLocaleData, m_aStaticFormatter);
+ }
+
+ IMPL_LINK_NOARG(DateFormatter, FormatOutputHdl, LinkParamNone*, bool)
+ {
+ OUString sText = FormatNumber(GetValue());
+ ImplSetTextImpl(sText, nullptr);
+ return true;
+ }
+
+ IMPL_LINK(DateFormatter, ParseInputHdl, sal_Int64*, result, TriState)
+ {
+ const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
+
+ Date aResult(Date::EMPTY);
+ bool bRet = ::DateFormatter::TextToDate(GetEntryText(), aResult, ResolveSystemFormat(m_eFormat, rLocaleDataWrapper),
+ rLocaleDataWrapper, GetCalendarWrapper());
+ if (bRet)
+ *result = aResult.GetDate();
+
+ return bRet ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+}
+
+static bool ImplTimeProcessKeyInput( const KeyEvent& rKEvt,
+ bool bStrictFormat, bool bDuration,
+ TimeFieldFormat eFormat,
+ const LocaleDataWrapper& rLocaleDataWrapper )
+{
+ sal_Unicode cChar = rKEvt.GetCharCode();
+
+ if ( !bStrictFormat )
+ return false;
+ else
+ {
+ sal_uInt16 nGroup = rKEvt.GetKeyCode().GetGroup();
+ if ( (nGroup == KEYGROUP_FKEYS) || (nGroup == KEYGROUP_CURSOR) ||
+ (nGroup == KEYGROUP_MISC) ||
+ ((cChar >= '0') && (cChar <= '9')) ||
+ rLocaleDataWrapper.getTimeSep() == OUStringChar(cChar) ||
+ (rLocaleDataWrapper.getTimeAM().indexOf(cChar) != -1) ||
+ (rLocaleDataWrapper.getTimePM().indexOf(cChar) != -1) ||
+ // Accept AM/PM:
+ (cChar == 'a') || (cChar == 'A') || (cChar == 'm') || (cChar == 'M') || (cChar == 'p') || (cChar == 'P') ||
+ ((eFormat == TimeFieldFormat::F_SEC_CS) && rLocaleDataWrapper.getTime100SecSep() == OUStringChar(cChar)) ||
+ (bDuration && (cChar == '-')) )
+ return false;
+ else
+ return true;
+ }
+}
+
+static bool ImplIsOnlyDigits( const OUString& _rStr )
+{
+ const sal_Unicode* _pChr = _rStr.getStr();
+ for ( sal_Int32 i = 0; i < _rStr.getLength(); ++i, ++_pChr )
+ {
+ if ( *_pChr < '0' || *_pChr > '9' )
+ return false;
+ }
+ return true;
+}
+
+static bool ImplIsValidTimePortion( bool _bSkipInvalidCharacters, const OUString& _rStr )
+{
+ if ( !_bSkipInvalidCharacters )
+ {
+ if ( ( _rStr.getLength() > 2 ) || _rStr.isEmpty() || !ImplIsOnlyDigits( _rStr ) )
+ return false;
+ }
+ return true;
+}
+
+static bool ImplCutTimePortion( OUStringBuffer& _rStr, sal_Int32 _nSepPos, bool _bSkipInvalidCharacters, short* _pPortion )
+{
+ OUString sPortion(_rStr.getStr(), _nSepPos );
+
+ if (_nSepPos < _rStr.getLength())
+ _rStr.remove(0, _nSepPos + 1);
+ else
+ _rStr.truncate();
+
+ if ( !ImplIsValidTimePortion( _bSkipInvalidCharacters, sPortion ) )
+ return false;
+ *_pPortion = static_cast<short>(sPortion.toInt32());
+ return true;
+}
+
+bool TimeFormatter::TextToTime(std::u16string_view rStr, tools::Time& rTime,
+ TimeFieldFormat eFormat,
+ bool bDuration, const LocaleDataWrapper& rLocaleDataWrapper, bool _bSkipInvalidCharacters)
+{
+ OUStringBuffer aStr(rStr);
+ short nHour = 0;
+ short nMinute = 0;
+ short nSecond = 0;
+ sal_Int64 nNanoSec = 0;
+ tools::Time aTime( 0, 0, 0 );
+
+ if ( rStr.empty() )
+ return false;
+
+ // Search for separators
+ if (!rLocaleDataWrapper.getTimeSep().isEmpty())
+ {
+ OUStringBuffer aSepStr(",.;:/");
+ if ( !bDuration )
+ aSepStr.append('-');
+
+ // Replace characters above by the separator character
+ for (sal_Int32 i = 0; i < aSepStr.getLength(); ++i)
+ {
+ if (rLocaleDataWrapper.getTimeSep() == OUStringChar(aSepStr[i]))
+ continue;
+ for ( sal_Int32 j = 0; j < aStr.getLength(); j++ )
+ {
+ if (aStr[j] == aSepStr[i])
+ aStr[j] = rLocaleDataWrapper.getTimeSep()[0];
+ }
+ }
+ }
+
+ bool bNegative = false;
+ sal_Int32 nSepPos = aStr.indexOf( rLocaleDataWrapper.getTimeSep() );
+ if ( aStr[0] == '-' )
+ bNegative = true;
+ if ( eFormat != TimeFieldFormat::F_SEC_CS )
+ {
+ if ( nSepPos < 0 )
+ nSepPos = aStr.getLength();
+ if ( !ImplCutTimePortion( aStr, nSepPos, _bSkipInvalidCharacters, &nHour ) )
+ return false;
+
+ nSepPos = aStr.indexOf( rLocaleDataWrapper.getTimeSep() );
+ if ( !aStr.isEmpty() && aStr[0] == '-' )
+ bNegative = true;
+ if ( nSepPos >= 0 )
+ {
+ if ( !ImplCutTimePortion( aStr, nSepPos, _bSkipInvalidCharacters, &nMinute ) )
+ return false;
+
+ nSepPos = aStr.indexOf( rLocaleDataWrapper.getTimeSep() );
+ if ( !aStr.isEmpty() && aStr[0] == '-' )
+ bNegative = true;
+ if ( nSepPos >= 0 )
+ {
+ if ( !ImplCutTimePortion( aStr, nSepPos, _bSkipInvalidCharacters, &nSecond ) )
+ return false;
+ if ( !aStr.isEmpty() && aStr[0] == '-' )
+ bNegative = true;
+ nNanoSec = o3tl::toInt64(aStr);
+ }
+ else
+ nSecond = static_cast<short>(o3tl::toInt32(aStr));
+ }
+ else
+ nMinute = static_cast<short>(o3tl::toInt32(aStr));
+ }
+ else if ( nSepPos < 0 )
+ {
+ nSecond = static_cast<short>(o3tl::toInt32(aStr));
+ nMinute += nSecond / 60;
+ nSecond %= 60;
+ nHour += nMinute / 60;
+ nMinute %= 60;
+ }
+ else
+ {
+ nSecond = static_cast<short>(o3tl::toInt32(aStr.subView( 0, nSepPos )));
+ aStr.remove( 0, nSepPos+1 );
+
+ nSepPos = aStr.indexOf( rLocaleDataWrapper.getTimeSep() );
+ if ( !aStr.isEmpty() && aStr[0] == '-' )
+ bNegative = true;
+ if ( nSepPos >= 0 )
+ {
+ nMinute = nSecond;
+ nSecond = static_cast<short>(o3tl::toInt32(aStr.subView( 0, nSepPos )));
+ aStr.remove( 0, nSepPos+1 );
+
+ nSepPos = aStr.indexOf( rLocaleDataWrapper.getTimeSep() );
+ if ( !aStr.isEmpty() && aStr[0] == '-' )
+ bNegative = true;
+ if ( nSepPos >= 0 )
+ {
+ nHour = nMinute;
+ nMinute = nSecond;
+ nSecond = static_cast<short>(o3tl::toInt32(aStr.subView( 0, nSepPos )));
+ aStr.remove( 0, nSepPos+1 );
+ }
+ else
+ {
+ nHour += nMinute / 60;
+ nMinute %= 60;
+ }
+ }
+ else
+ {
+ nMinute += nSecond / 60;
+ nSecond %= 60;
+ nHour += nMinute / 60;
+ nMinute %= 60;
+ }
+ nNanoSec = o3tl::toInt64(aStr);
+ }
+
+ if ( nNanoSec )
+ {
+ assert(aStr.getLength() >= 1);
+
+ sal_Int32 nLen = 1; // at least one digit, otherwise nNanoSec==0
+
+ while ( aStr.getLength() > nLen && aStr[nLen] >= '0' && aStr[nLen] <= '9' )
+ nLen++;
+
+ while ( nLen < 9)
+ {
+ nNanoSec *= 10;
+ ++nLen;
+ }
+ while ( nLen > 9 )
+ {
+ // round if negative?
+ nNanoSec = (nNanoSec + 5) / 10;
+ --nLen;
+ }
+ }
+
+ assert(nNanoSec > -1000000000 && nNanoSec < 1000000000);
+ if ( (nMinute > 59) || (nSecond > 59) || (nNanoSec > 1000000000) )
+ return false;
+
+ if ( eFormat == TimeFieldFormat::F_NONE )
+ nSecond = nNanoSec = 0;
+ else if ( eFormat == TimeFieldFormat::F_SEC )
+ nNanoSec = 0;
+
+ if ( !bDuration )
+ {
+ if ( bNegative || (nHour < 0) || (nMinute < 0) ||
+ (nSecond < 0) || (nNanoSec < 0) )
+ return false;
+
+ OUString aUpperCaseStr = aStr.toString().toAsciiUpperCase();
+ OUString aAMlocalised(rLocaleDataWrapper.getTimeAM().toAsciiUpperCase());
+ OUString aPMlocalised(rLocaleDataWrapper.getTimePM().toAsciiUpperCase());
+
+ if ( (nHour < 12) && ( ( aUpperCaseStr.indexOf( "PM" ) >= 0 ) || ( aUpperCaseStr.indexOf( aPMlocalised ) >= 0 ) ) )
+ nHour += 12;
+
+ if ( (nHour == 12) && ( ( aUpperCaseStr.indexOf( "AM" ) >= 0 ) || ( aUpperCaseStr.indexOf( aAMlocalised ) >= 0 ) ) )
+ nHour = 0;
+
+ aTime = tools::Time( static_cast<sal_uInt16>(nHour), static_cast<sal_uInt16>(nMinute), static_cast<sal_uInt16>(nSecond),
+ static_cast<sal_uInt32>(nNanoSec) );
+ }
+ else
+ {
+ assert( !bNegative || (nHour < 0) || (nMinute < 0) ||
+ (nSecond < 0) || (nNanoSec < 0) );
+ if ( bNegative || (nHour < 0) || (nMinute < 0) ||
+ (nSecond < 0) || (nNanoSec < 0) )
+ {
+ // LEM TODO: this looks weird... I think buggy when parsing "05:-02:18"
+ bNegative = true;
+ nHour = nHour < 0 ? -nHour : nHour;
+ nMinute = nMinute < 0 ? -nMinute : nMinute;
+ nSecond = nSecond < 0 ? -nSecond : nSecond;
+ nNanoSec = nNanoSec < 0 ? -nNanoSec : nNanoSec;
+ }
+
+ aTime = tools::Time( static_cast<sal_uInt16>(nHour), static_cast<sal_uInt16>(nMinute), static_cast<sal_uInt16>(nSecond),
+ static_cast<sal_uInt32>(nNanoSec) );
+ if ( bNegative )
+ aTime = -aTime;
+ }
+
+ rTime = aTime;
+
+ return true;
+}
+
+void TimeFormatter::ImplTimeReformat( std::u16string_view rStr, OUString& rOutStr )
+{
+ tools::Time aTime( 0, 0, 0 );
+ if ( !TextToTime( rStr, aTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper() ) )
+ return;
+
+ tools::Time aTempTime = aTime;
+ if ( aTempTime > GetMax() )
+ aTempTime = GetMax() ;
+ else if ( aTempTime < GetMin() )
+ aTempTime = GetMin();
+
+ bool bSecond = false;
+ bool b100Sec = false;
+ if ( meFormat != TimeFieldFormat::F_NONE )
+ bSecond = true;
+
+ if ( meFormat == TimeFieldFormat::F_SEC_CS )
+ {
+ sal_uLong n = aTempTime.GetHour() * 3600L;
+ n += aTempTime.GetMin() * 60L;
+ n += aTempTime.GetSec();
+ rOutStr = OUString::number( n );
+ rOutStr += ImplGetLocaleDataWrapper().getTime100SecSep();
+ std::ostringstream ostr;
+ ostr.fill('0');
+ ostr.width(9);
+ ostr << aTempTime.GetNanoSec();
+ rOutStr += OUString::createFromAscii(ostr.str().c_str());
+ }
+ else if ( mbDuration )
+ rOutStr = ImplGetLocaleDataWrapper().getDuration( aTempTime, bSecond, b100Sec );
+ else
+ {
+ rOutStr = ImplGetLocaleDataWrapper().getTime( aTempTime, bSecond, b100Sec );
+ if ( GetTimeFormat() == TimeFormat::Hour12 )
+ {
+ if ( aTempTime.GetHour() > 12 )
+ {
+ tools::Time aT( aTempTime );
+ aT.SetHour( aT.GetHour() % 12 );
+ rOutStr = ImplGetLocaleDataWrapper().getTime( aT, bSecond, b100Sec );
+ }
+ // Don't use LocaleDataWrapper, we want AM/PM
+ if ( aTempTime.GetHour() < 12 )
+ rOutStr += "AM"; // ImplGetLocaleDataWrapper().getTimeAM();
+ else
+ rOutStr += "PM"; // ImplGetLocaleDataWrapper().getTimePM();
+ }
+ }
+}
+
+bool TimeFormatter::ImplAllowMalformedInput() const
+{
+ return !IsEnforceValidValue();
+}
+
+int TimeFormatter::GetTimeArea(TimeFieldFormat eFormat, std::u16string_view rText, int nCursor,
+ const LocaleDataWrapper& rLocaleDataWrapper)
+{
+ int nTimeArea = 0;
+
+ // Area search
+ if (eFormat != TimeFieldFormat::F_SEC_CS)
+ {
+ //Which area is the cursor in of HH:MM:SS.TT
+ for ( size_t i = 1, nPos = 0; i <= 4; i++ )
+ {
+ size_t nPos1 = rText.find(rLocaleDataWrapper.getTimeSep(), nPos);
+ size_t nPos2 = rText.find(rLocaleDataWrapper.getTime100SecSep(), nPos);
+ //which ever comes first, bearing in mind that one might not be there
+ if (nPos1 != std::u16string_view::npos && nPos2 != std::u16string_view::npos)
+ nPos = std::min(nPos1, nPos2);
+ else if (nPos1 != std::u16string_view::npos)
+ nPos = nPos1;
+ else
+ nPos = nPos2;
+ if (nPos == std::u16string_view::npos || static_cast<sal_Int32>(nPos) >= nCursor)
+ {
+ nTimeArea = i;
+ break;
+ }
+ else
+ nPos++;
+ }
+ }
+ else
+ {
+ size_t nPos = rText.find(rLocaleDataWrapper.getTime100SecSep());
+ if (nPos == std::u16string_view::npos || static_cast<sal_Int32>(nPos) >= nCursor)
+ nTimeArea = 3;
+ else
+ nTimeArea = 4;
+ }
+
+ return nTimeArea;
+}
+
+tools::Time TimeFormatter::SpinTime(bool bUp, const tools::Time& rTime, TimeFieldFormat eFormat,
+ bool bDuration, std::u16string_view rText, int nCursor,
+ const LocaleDataWrapper& rLocaleDataWrapper)
+{
+ tools::Time aTime(rTime);
+
+ int nTimeArea = GetTimeArea(eFormat, rText, nCursor, rLocaleDataWrapper);
+
+ if ( nTimeArea )
+ {
+ tools::Time aAddTime( 0, 0, 0 );
+ if ( nTimeArea == 1 )
+ aAddTime = tools::Time( 1, 0 );
+ else if ( nTimeArea == 2 )
+ aAddTime = tools::Time( 0, 1 );
+ else if ( nTimeArea == 3 )
+ aAddTime = tools::Time( 0, 0, 1 );
+ else if ( nTimeArea == 4 )
+ aAddTime = tools::Time( 0, 0, 0, 1 );
+
+ if ( !bUp )
+ aAddTime = -aAddTime;
+
+ aTime += aAddTime;
+ if (!bDuration)
+ {
+ tools::Time aAbsMaxTime( 23, 59, 59, 999999999 );
+ if ( aTime > aAbsMaxTime )
+ aTime = aAbsMaxTime;
+ tools::Time aAbsMinTime( 0, 0 );
+ if ( aTime < aAbsMinTime )
+ aTime = aAbsMinTime;
+ }
+ }
+
+ return aTime;
+}
+
+void TimeField::ImplTimeSpinArea( bool bUp )
+{
+ if ( GetField() )
+ {
+ tools::Time aTime( GetTime() );
+ OUString aText( GetText() );
+ Selection aSelection( GetField()->GetSelection() );
+
+ aTime = TimeFormatter::SpinTime(bUp, aTime, GetFormat(), IsDuration(), aText, aSelection.Max(), ImplGetLocaleDataWrapper());
+
+ ImplNewFieldValue( aTime );
+ }
+}
+
+TimeFormatter::TimeFormatter(Edit* pEdit)
+ : FormatterBase(pEdit)
+ , maLastTime(0, 0)
+ , maMin(0, 0)
+ , maMax(23, 59, 59, 999999999)
+ , meFormat(TimeFieldFormat::F_NONE)
+ , mnTimeFormat(TimeFormat::Hour24) // Should become an ExtTimeFieldFormat in next implementation, merge with mbDuration and meFormat
+ , mbDuration(false)
+ , mbEnforceValidValue(true)
+ , maFieldTime(0, 0)
+{
+}
+
+TimeFormatter::~TimeFormatter()
+{
+}
+
+void TimeFormatter::ReformatAll()
+{
+ Reformat();
+}
+
+void TimeFormatter::SetMin( const tools::Time& rNewMin )
+{
+ maMin = rNewMin;
+ if ( !IsEmptyFieldValue() )
+ ReformatAll();
+}
+
+void TimeFormatter::SetMax( const tools::Time& rNewMax )
+{
+ maMax = rNewMax;
+ if ( !IsEmptyFieldValue() )
+ ReformatAll();
+}
+
+void TimeFormatter::SetTimeFormat( TimeFormat eNewFormat )
+{
+ mnTimeFormat = eNewFormat;
+}
+
+
+void TimeFormatter::SetFormat( TimeFieldFormat eNewFormat )
+{
+ meFormat = eNewFormat;
+ ReformatAll();
+}
+
+void TimeFormatter::SetDuration( bool bNewDuration )
+{
+ mbDuration = bNewDuration;
+ ReformatAll();
+}
+
+void TimeFormatter::SetTime( const tools::Time& rNewTime )
+{
+ SetUserTime( rNewTime );
+ maFieldTime = maLastTime;
+ SetEmptyFieldValueData( false );
+}
+
+void TimeFormatter::ImplNewFieldValue( const tools::Time& rTime )
+{
+ if ( !GetField() )
+ return;
+
+ Selection aSelection = GetField()->GetSelection();
+ aSelection.Justify();
+ OUString aText = GetField()->GetText();
+
+ // If selected until the end then keep it that way
+ if ( static_cast<sal_Int32>(aSelection.Max()) == aText.getLength() )
+ {
+ if ( !aSelection.Len() )
+ aSelection.Min() = SELECTION_MAX;
+ aSelection.Max() = SELECTION_MAX;
+ }
+
+ tools::Time aOldLastTime = maLastTime;
+ ImplSetUserTime( rTime, &aSelection );
+ maLastTime = aOldLastTime;
+
+ // Modify at Edit is only set at KeyInput
+ if ( GetField()->GetText() != aText )
+ {
+ GetField()->SetModifyFlag();
+ GetField()->Modify();
+ }
+}
+
+OUString TimeFormatter::FormatTime(const tools::Time& rNewTime, TimeFieldFormat eFormat, TimeFormat eHourFormat, bool bDuration, const LocaleDataWrapper& rLocaleData)
+{
+ OUString aStr;
+ bool bSec = false;
+ bool b100Sec = false;
+ if ( eFormat != TimeFieldFormat::F_NONE )
+ bSec = true;
+ if ( eFormat == TimeFieldFormat::F_SEC_CS )
+ b100Sec = true;
+ if ( eFormat == TimeFieldFormat::F_SEC_CS )
+ {
+ sal_uLong n = rNewTime.GetHour() * 3600L;
+ n += rNewTime.GetMin() * 60L;
+ n += rNewTime.GetSec();
+ aStr = OUString::number( n ) + rLocaleData.getTime100SecSep();
+ std::ostringstream ostr;
+ ostr.fill('0');
+ ostr.width(9);
+ ostr << rNewTime.GetNanoSec();
+ aStr += OUString::createFromAscii(ostr.str().c_str());
+ }
+ else if ( bDuration )
+ {
+ aStr = rLocaleData.getDuration( rNewTime, bSec, b100Sec );
+ }
+ else
+ {
+ aStr = rLocaleData.getTime( rNewTime, bSec, b100Sec );
+ if ( eHourFormat == TimeFormat::Hour12 )
+ {
+ if ( rNewTime.GetHour() > 12 )
+ {
+ tools::Time aT( rNewTime );
+ aT.SetHour( aT.GetHour() % 12 );
+ aStr = rLocaleData.getTime( aT, bSec, b100Sec );
+ }
+ // Don't use LocaleDataWrapper, we want AM/PM
+ if ( rNewTime.GetHour() < 12 )
+ aStr += "AM"; // rLocaleData.getTimeAM();
+ else
+ aStr += "PM"; // rLocaleData.getTimePM();
+ }
+ }
+
+ return aStr;
+}
+
+void TimeFormatter::ImplSetUserTime( const tools::Time& rNewTime, Selection const * pNewSelection )
+{
+ tools::Time aNewTime = rNewTime;
+ if ( aNewTime > GetMax() )
+ aNewTime = GetMax();
+ else if ( aNewTime < GetMin() )
+ aNewTime = GetMin();
+ maLastTime = aNewTime;
+
+ if ( GetField() )
+ {
+ OUString aStr = TimeFormatter::FormatTime(aNewTime, meFormat, GetTimeFormat(), mbDuration, ImplGetLocaleDataWrapper());
+ ImplSetText( aStr, pNewSelection );
+ }
+}
+
+void TimeFormatter::SetUserTime( const tools::Time& rNewTime )
+{
+ ImplSetUserTime( rNewTime );
+}
+
+tools::Time TimeFormatter::GetTime() const
+{
+ tools::Time aTime( 0, 0, 0 );
+
+ if ( GetField() )
+ {
+ bool bAllowMalformed = ImplAllowMalformedInput();
+ if ( TextToTime( GetField()->GetText(), aTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper(), !bAllowMalformed ) )
+ {
+ if ( aTime > GetMax() )
+ aTime = GetMax();
+ else if ( aTime < GetMin() )
+ aTime = GetMin();
+ }
+ else
+ {
+ if ( bAllowMalformed )
+ aTime = tools::Time( 99, 99, 99 ); // set invalid time
+ else
+ aTime = maLastTime;
+ }
+ }
+
+ return aTime;
+}
+
+void TimeFormatter::Reformat()
+{
+ if ( !GetField() )
+ return;
+
+ if ( GetField()->GetText().isEmpty() && ImplGetEmptyFieldValue() )
+ return;
+
+ OUString aStr;
+ ImplTimeReformat( GetField()->GetText(), aStr );
+
+ if ( !aStr.isEmpty() )
+ {
+ ImplSetText( aStr );
+ (void)TextToTime(aStr, maLastTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper());
+ }
+ else
+ SetTime( maLastTime );
+}
+
+TimeField::TimeField( vcl::Window* pParent, WinBits nWinStyle ) :
+ SpinField( pParent, nWinStyle ),
+ TimeFormatter(this),
+ maFirst( GetMin() ),
+ maLast( GetMax() )
+{
+ SetText( ImplGetLocaleDataWrapper().getTime( maFieldTime, false ) );
+ Reformat();
+}
+
+void TimeField::dispose()
+{
+ ClearField();
+ SpinField::dispose();
+}
+
+bool TimeField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplTimeProcessKeyInput( *rNEvt.GetKeyEvent(), IsStrictFormat(), IsDuration(), GetFormat(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool TimeField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ {
+ if ( !ImplAllowMalformedInput() )
+ Reformat();
+ else
+ {
+ tools::Time aTime( 0, 0, 0 );
+ if ( TextToTime( GetText(), aTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper(), false ) )
+ // even with strict text analysis, our text is a valid time -> do a complete
+ // reformat
+ Reformat();
+ }
+ }
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void TimeField::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ SpinField::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ ImplResetLocaleDataWrapper();
+ ReformatAll();
+ }
+}
+
+void TimeField::Modify()
+{
+ MarkToBeReformatted( true );
+ SpinField::Modify();
+}
+
+void TimeField::Up()
+{
+ ImplTimeSpinArea( true );
+ SpinField::Up();
+}
+
+void TimeField::Down()
+{
+ ImplTimeSpinArea( false );
+ SpinField::Down();
+}
+
+void TimeField::First()
+{
+ ImplNewFieldValue( maFirst );
+ SpinField::First();
+}
+
+void TimeField::Last()
+{
+ ImplNewFieldValue( maLast );
+ SpinField::Last();
+}
+
+void TimeField::SetExtFormat( ExtTimeFieldFormat eFormat )
+{
+ switch ( eFormat )
+ {
+ case ExtTimeFieldFormat::Short24H:
+ {
+ SetTimeFormat( TimeFormat::Hour24 );
+ SetDuration( false );
+ SetFormat( TimeFieldFormat::F_NONE );
+ }
+ break;
+ case ExtTimeFieldFormat::Long24H:
+ {
+ SetTimeFormat( TimeFormat::Hour24 );
+ SetDuration( false );
+ SetFormat( TimeFieldFormat::F_SEC );
+ }
+ break;
+ case ExtTimeFieldFormat::Short12H:
+ {
+ SetTimeFormat( TimeFormat::Hour12 );
+ SetDuration( false );
+ SetFormat( TimeFieldFormat::F_NONE );
+ }
+ break;
+ case ExtTimeFieldFormat::Long12H:
+ {
+ SetTimeFormat( TimeFormat::Hour12 );
+ SetDuration( false );
+ SetFormat( TimeFieldFormat::F_SEC );
+ }
+ break;
+ case ExtTimeFieldFormat::ShortDuration:
+ {
+ SetDuration( true );
+ SetFormat( TimeFieldFormat::F_NONE );
+ }
+ break;
+ case ExtTimeFieldFormat::LongDuration:
+ {
+ SetDuration( true );
+ SetFormat( TimeFieldFormat::F_SEC );
+ }
+ break;
+ default: OSL_FAIL( "ExtTimeFieldFormat unknown!" );
+ }
+
+ if ( GetField() && !GetField()->GetText().isEmpty() )
+ SetUserTime( GetTime() );
+ ReformatAll();
+}
+
+TimeBox::TimeBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox(pParent, nWinStyle)
+ , TimeFormatter(this)
+{
+ SetText( ImplGetLocaleDataWrapper().getTime( maFieldTime, false ) );
+ Reformat();
+}
+
+void TimeBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+bool TimeBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplTimeProcessKeyInput( *rNEvt.GetKeyEvent(), IsStrictFormat(), IsDuration(), GetFormat(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+bool TimeBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void TimeBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ ComboBox::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ ImplResetLocaleDataWrapper();
+ ReformatAll();
+ }
+}
+
+void TimeBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void TimeBox::ReformatAll()
+{
+ OUString aStr;
+ SetUpdateMode( false );
+ const sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; ++i )
+ {
+ ImplTimeReformat( GetEntry( i ), aStr );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ TimeFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+namespace weld
+{
+ tools::Time TimeFormatter::ConvertValue(int nValue)
+ {
+ tools::Time aTime(0);
+ aTime.MakeTimeFromMS(nValue);
+ return aTime;
+ }
+
+ int TimeFormatter::ConvertValue(const tools::Time& rTime)
+ {
+ return rTime.GetMSFromTime();
+ }
+
+ void TimeFormatter::SetTime(const tools::Time& rTime)
+ {
+ auto nTime = ConvertValue(rTime);
+ bool bForceOutput = GetEntryText().isEmpty() && rTime == GetTime();
+ if (bForceOutput)
+ {
+ ImplSetValue(nTime, true);
+ return;
+ }
+ SetValue(nTime);
+ }
+
+ tools::Time TimeFormatter::GetTime()
+ {
+ return ConvertValue(GetValue());
+ }
+
+ void TimeFormatter::SetMin(const tools::Time& rNewMin)
+ {
+ SetMinValue(ConvertValue(rNewMin));
+ }
+
+ void TimeFormatter::SetMax(const tools::Time& rNewMax)
+ {
+ SetMaxValue(ConvertValue(rNewMax));
+ }
+
+ OUString TimeFormatter::FormatNumber(int nValue) const
+ {
+ const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
+ return ::TimeFormatter::FormatTime(ConvertValue(nValue), m_eFormat, m_eTimeFormat, m_bDuration, rLocaleData);
+ }
+
+ IMPL_LINK_NOARG(TimeFormatter, FormatOutputHdl, LinkParamNone*, bool)
+ {
+ OUString sText = FormatNumber(GetValue());
+ ImplSetTextImpl(sText, nullptr);
+ return true;
+ }
+
+ IMPL_LINK(TimeFormatter, ParseInputHdl, sal_Int64*, result, TriState)
+ {
+ const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
+
+ tools::Time aResult(0);
+ bool bRet = ::TimeFormatter::TextToTime(GetEntryText(), aResult, m_eFormat, m_bDuration, rLocaleDataWrapper);
+ if (bRet)
+ *result = ConvertValue(aResult);
+
+ return bRet ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+
+ IMPL_LINK(TimeFormatter, CursorChangedHdl, weld::Entry&, rEntry, void)
+ {
+ int nStartPos, nEndPos;
+ rEntry.get_selection_bounds(nStartPos, nEndPos);
+
+ const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
+ const int nTimeArea = ::TimeFormatter::GetTimeArea(m_eFormat, GetEntryText(), nEndPos, rLocaleData);
+
+ int nIncrements = 1;
+
+ if (nTimeArea == 1)
+ nIncrements = 1000 * 60 * 60;
+ else if (nTimeArea == 2)
+ nIncrements = 1000 * 60;
+ else if (nTimeArea == 3)
+ nIncrements = 1000;
+
+ SetSpinSize(nIncrements);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/fixed.cxx b/vcl/source/control/fixed.cxx
new file mode 100644
index 000000000..5b7038bbe
--- /dev/null
+++ b/vcl/source/control/fixed.cxx
@@ -0,0 +1,995 @@
+/* -*- 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 <vcl/cvtgrf.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/fixed.hxx>
+#include <vcl/settings.hxx>
+
+#include <comphelper/base64.hxx>
+#include <comphelper/string.hxx>
+#include <sal/log.hxx>
+#include <tools/json_writer.hxx>
+#include <tools/stream.hxx>
+
+#define FIXEDLINE_TEXT_BORDER 4
+
+constexpr auto FIXEDTEXT_VIEW_STYLE = WB_3DLOOK |
+ WB_LEFT | WB_CENTER | WB_RIGHT |
+ WB_TOP | WB_VCENTER | WB_BOTTOM |
+ WB_WORDBREAK | WB_NOLABEL |
+ WB_PATHELLIPSIS;
+constexpr auto FIXEDLINE_VIEW_STYLE = WB_3DLOOK | WB_NOLABEL;
+constexpr auto FIXEDBITMAP_VIEW_STYLE = WB_3DLOOK |
+ WB_LEFT | WB_CENTER | WB_RIGHT |
+ WB_TOP | WB_VCENTER | WB_BOTTOM |
+ WB_SCALE;
+constexpr auto FIXEDIMAGE_VIEW_STYLE = WB_3DLOOK |
+ WB_LEFT | WB_CENTER | WB_RIGHT |
+ WB_TOP | WB_VCENTER | WB_BOTTOM |
+ WB_SCALE;
+
+static Point ImplCalcPos( WinBits nStyle, const Point& rPos,
+ const Size& rObjSize, const Size& rWinSize )
+{
+ tools::Long nX;
+ tools::Long nY;
+
+ if ( nStyle & WB_LEFT )
+ nX = 0;
+ else if ( nStyle & WB_RIGHT )
+ nX = rWinSize.Width()-rObjSize.Width();
+ else
+ nX = (rWinSize.Width()-rObjSize.Width())/2;
+
+ if ( nStyle & WB_TOP )
+ nY = 0;
+ else if ( nStyle & WB_BOTTOM )
+ nY = rWinSize.Height()-rObjSize.Height();
+ else
+ nY = (rWinSize.Height()-rObjSize.Height())/2;
+
+ Point aPos( nX+rPos.X(), nY+rPos.Y() );
+ return aPos;
+}
+
+void FixedText::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle( nStyle );
+ Control::ImplInit( pParent, nStyle, nullptr );
+ ApplySettings(*GetOutDev());
+}
+
+WinBits FixedText::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+const vcl::Font& FixedText::GetCanonicalFont( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetLabelFont();
+}
+
+const Color& FixedText::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetLabelTextColor();
+}
+
+FixedText::FixedText( vcl::Window* pParent, WinBits nStyle )
+ : Control(WindowType::FIXEDTEXT)
+ , m_nMaxWidthChars(-1)
+ , m_nMinWidthChars(-1)
+ , m_pMnemonicWindow(nullptr)
+{
+ ImplInit( pParent, nStyle );
+}
+
+DrawTextFlags FixedText::ImplGetTextStyle( WinBits nWinStyle )
+{
+ DrawTextFlags nTextStyle = DrawTextFlags::Mnemonic | DrawTextFlags::EndEllipsis;
+
+ if( ! (nWinStyle & WB_NOMULTILINE) )
+ nTextStyle |= DrawTextFlags::MultiLine;
+
+ if ( nWinStyle & WB_RIGHT )
+ nTextStyle |= DrawTextFlags::Right;
+ else if ( nWinStyle & WB_CENTER )
+ nTextStyle |= DrawTextFlags::Center;
+ else
+ nTextStyle |= DrawTextFlags::Left;
+ if ( nWinStyle & WB_BOTTOM )
+ nTextStyle |= DrawTextFlags::Bottom;
+ else if ( nWinStyle & WB_VCENTER )
+ nTextStyle |= DrawTextFlags::VCenter;
+ else
+ nTextStyle |= DrawTextFlags::Top;
+ if ( nWinStyle & WB_WORDBREAK )
+ nTextStyle |= DrawTextFlags::WordBreak;
+ if ( nWinStyle & WB_NOLABEL )
+ nTextStyle &= ~DrawTextFlags::Mnemonic;
+
+ return nTextStyle;
+}
+
+void FixedText::ImplDraw(OutputDevice* pDev, SystemTextColorFlags nSystemTextColorFlags,
+ const Point& rPos, const Size& rSize,
+ bool bFillLayout) const
+{
+ const StyleSettings& rStyleSettings = pDev->GetSettings().GetStyleSettings();
+ WinBits nWinStyle = GetStyle();
+ OUString aText(GetText());
+ DrawTextFlags nTextStyle = FixedText::ImplGetTextStyle( nWinStyle );
+ Point aPos = rPos;
+
+ if ( nWinStyle & WB_EXTRAOFFSET )
+ aPos.AdjustX(2 );
+
+ if ( nWinStyle & WB_PATHELLIPSIS )
+ {
+ nTextStyle &= ~DrawTextFlags(DrawTextFlags::EndEllipsis | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak);
+ nTextStyle |= DrawTextFlags::PathEllipsis;
+ }
+ if ( !IsEnabled() )
+ nTextStyle |= DrawTextFlags::Disable;
+ if ( (nSystemTextColorFlags & SystemTextColorFlags::Mono) ||
+ (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) )
+ nTextStyle |= DrawTextFlags::Mono;
+
+ if( bFillLayout )
+ mxLayoutData->m_aDisplayText.clear();
+
+ const tools::Rectangle aRect(aPos, rSize);
+ DrawControlText(*pDev, aRect, aText, nTextStyle,
+ bFillLayout ? &mxLayoutData->m_aUnicodeBoundRects : nullptr,
+ bFillLayout ? &mxLayoutData->m_aDisplayText : nullptr);
+}
+
+void FixedText::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ Control::ApplySettings(rRenderContext);
+
+ vcl::Window* pParent = GetParent();
+ bool bEnableTransparent = true;
+ if (!pParent->IsChildTransparentModeEnabled() || IsControlBackground())
+ {
+ EnableChildTransparentMode(false);
+ SetParentClipMode();
+ SetPaintTransparent(false);
+
+ if (IsControlBackground())
+ rRenderContext.SetBackground(GetControlBackground());
+ else
+ rRenderContext.SetBackground(pParent->GetBackground());
+
+ if (rRenderContext.IsBackground())
+ bEnableTransparent = false;
+ }
+
+ if (bEnableTransparent)
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode(ParentClipMode::NoClip);
+ SetPaintTransparent(true);
+ rRenderContext.SetBackground();
+ }
+}
+
+void FixedText::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ ImplDraw(&rRenderContext, SystemTextColorFlags::NONE, Point(), GetOutputSizePixel());
+}
+
+void FixedText::Draw( OutputDevice* pDev, const Point& rPos,
+ SystemTextColorFlags nFlags )
+{
+ ApplySettings(*pDev);
+
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ vcl::Font aFont = GetDrawPixelFont( pDev );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ if ( nFlags & SystemTextColorFlags::Mono )
+ pDev->SetTextColor( COL_BLACK );
+ else
+ pDev->SetTextColor( GetTextColor() );
+ pDev->SetTextFillColor();
+
+ bool bBorder = (GetStyle() & WB_BORDER);
+ bool bBackground = IsControlBackground();
+ if ( bBorder || bBackground )
+ {
+ tools::Rectangle aRect( aPos, aSize );
+ if ( bBorder )
+ {
+ ImplDrawFrame( pDev, aRect );
+ }
+ if ( bBackground )
+ {
+ pDev->SetFillColor( GetControlBackground() );
+ pDev->DrawRect( aRect );
+ }
+ }
+
+ ImplDraw( pDev, nFlags, aPos, aSize );
+ pDev->Pop();
+}
+
+void FixedText::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void FixedText::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ if ( (GetPrevStyle() & FIXEDTEXT_VIEW_STYLE) !=
+ (GetStyle() & FIXEDTEXT_VIEW_STYLE) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+void FixedText::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+Size FixedText::getTextDimensions(Control const *pControl, const OUString &rTxt, tools::Long nMaxWidth)
+{
+ DrawTextFlags nStyle = ImplGetTextStyle( pControl->GetStyle() );
+ if ( !( pControl->GetStyle() & WB_NOLABEL ) )
+ nStyle |= DrawTextFlags::Mnemonic;
+
+ return pControl->GetTextRect(tools::Rectangle( Point(), Size(nMaxWidth, 0x7fffffff)),
+ rTxt, nStyle).GetSize();
+}
+
+Size FixedText::CalcMinimumTextSize( Control const *pControl, tools::Long nMaxWidth )
+{
+ Size aSize = getTextDimensions(pControl, pControl->GetText(), nMaxWidth);
+
+ if ( pControl->GetStyle() & WB_EXTRAOFFSET )
+ aSize.AdjustWidth(2 );
+
+ // GetTextRect cannot take an empty string
+ if ( aSize.Width() < 0 )
+ aSize.setWidth( 0 );
+ if ( aSize.Height() <= 0 )
+ aSize.setHeight( pControl->GetTextHeight() );
+
+ return aSize;
+}
+
+Size FixedText::CalcMinimumSize( tools::Long nMaxWidth ) const
+{
+ return CalcWindowSize( CalcMinimumTextSize ( this, nMaxWidth ) );
+}
+
+Size FixedText::GetOptimalSize() const
+{
+ sal_Int32 nMaxAvailWidth = 0x7fffffff;
+ if (m_nMaxWidthChars != -1)
+ {
+ OUStringBuffer aBuf(m_nMaxWidthChars);
+ comphelper::string::padToLength(aBuf, m_nMaxWidthChars, 'x');
+ nMaxAvailWidth = getTextDimensions(this,
+ aBuf.makeStringAndClear(), 0x7fffffff).Width();
+ }
+ Size aRet = CalcMinimumSize(nMaxAvailWidth);
+ if (m_nMinWidthChars != -1)
+ {
+ OUStringBuffer aBuf(m_nMinWidthChars);
+ comphelper::string::padToLength(aBuf, m_nMinWidthChars, 'x');
+ Size aMinAllowed = getTextDimensions(this,
+ aBuf.makeStringAndClear(), 0x7fffffff);
+ aRet.setWidth(std::max(aMinAllowed.Width(), aRet.Width()));
+ }
+ return aRet;
+}
+
+void FixedText::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ ImplDraw(const_cast<FixedText*>(this)->GetOutDev(), SystemTextColorFlags::NONE, Point(), GetOutputSizePixel(), true);
+ //const_cast<FixedText*>(this)->Invalidate();
+}
+
+void FixedText::setMaxWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != m_nMaxWidthChars)
+ {
+ m_nMaxWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+void FixedText::setMinWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != m_nMinWidthChars)
+ {
+ m_nMinWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+bool FixedText::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "max-width-chars")
+ setMaxWidthChars(rValue.toInt32());
+ else if (rKey == "width-chars")
+ setMinWidthChars(rValue.toInt32());
+ else if (rKey == "ellipsize")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~WB_PATHELLIPSIS;
+ if (rValue != "none")
+ {
+ SAL_WARN_IF(rValue != "end", "vcl.layout", "Only endellipsis support for now");
+ nBits |= WB_PATHELLIPSIS;
+ }
+ SetStyle(nBits);
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+vcl::Window* FixedText::getAccessibleRelationLabelFor() const
+{
+ vcl::Window *pWindow = Control::getAccessibleRelationLabelFor();
+ if (pWindow)
+ return pWindow;
+ return get_mnemonic_widget();
+}
+
+void FixedText::set_mnemonic_widget(vcl::Window *pWindow)
+{
+ if (pWindow == m_pMnemonicWindow)
+ return;
+ if (m_pMnemonicWindow)
+ {
+ vcl::Window *pTempReEntryGuard = m_pMnemonicWindow;
+ m_pMnemonicWindow = nullptr;
+ pTempReEntryGuard->remove_mnemonic_label(this);
+ }
+ m_pMnemonicWindow = pWindow;
+ if (m_pMnemonicWindow)
+ m_pMnemonicWindow->add_mnemonic_label(this);
+}
+
+FixedText::~FixedText()
+{
+ disposeOnce();
+}
+
+void FixedText::dispose()
+{
+ set_mnemonic_widget(nullptr);
+ m_pMnemonicWindow.clear();
+ Control::dispose();
+}
+
+SelectableFixedText::SelectableFixedText(vcl::Window* pParent, WinBits nStyle)
+ : Edit(pParent, nStyle)
+{
+ // no border
+ SetBorderStyle( WindowBorderStyle::NOBORDER );
+ // read-only
+ SetReadOnly();
+ // make it transparent
+ SetPaintTransparent(true);
+ SetControlBackground();
+}
+
+void SelectableFixedText::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.SetBackground();
+}
+
+void SelectableFixedText::LoseFocus()
+{
+ Edit::LoseFocus();
+ // clear cursor
+ Invalidate();
+}
+
+void SelectableFixedText::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Edit::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("type", "fixedtext");
+ rJsonWriter.put("selectable", "true");
+}
+
+void FixedLine::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle( nStyle );
+ Control::ImplInit( pParent, nStyle, nullptr );
+ ApplySettings(*GetOutDev());
+}
+
+WinBits FixedLine::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+const vcl::Font& FixedLine::GetCanonicalFont( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetGroupFont();
+}
+
+const Color& FixedLine::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetGroupTextColor();
+}
+
+void FixedLine::ImplDraw(vcl::RenderContext& rRenderContext)
+{
+ // we need to measure according to the window, not according to the
+ // RenderContext we paint to
+ Size aOutSize = GetOutputSizePixel();
+
+ OUString aText = GetText();
+ WinBits nWinStyle = GetStyle();
+
+ DecorationView aDecoView(&rRenderContext);
+ if (aText.isEmpty())
+ {
+ if (nWinStyle & WB_VERT)
+ {
+ tools::Long nX = (aOutSize.Width() - 1) / 2;
+ aDecoView.DrawSeparator(Point(nX, 0), Point(nX, aOutSize.Height() - 1));
+ }
+ else
+ {
+ tools::Long nY = (aOutSize.Height() - 1) / 2;
+ aDecoView.DrawSeparator(Point(0, nY), Point(aOutSize.Width() - 1, nY), false);
+ }
+ }
+ else if (nWinStyle & WB_VERT)
+ {
+ tools::Long nWidth = rRenderContext.GetTextWidth(aText);
+ rRenderContext.Push(vcl::PushFlags::FONT);
+ vcl::Font aFont(rRenderContext.GetFont());
+ aFont.SetOrientation(900_deg10);
+ SetFont(aFont);
+ Point aStartPt(aOutSize.Width() / 2, aOutSize.Height() - 1);
+ if (nWinStyle & WB_VCENTER)
+ aStartPt.AdjustY( -((aOutSize.Height() - nWidth) / 2) );
+ Point aTextPt(aStartPt);
+ aTextPt.AdjustX( -(GetTextHeight() / 2) );
+ rRenderContext.DrawText(aTextPt, aText, 0, aText.getLength());
+ rRenderContext.Pop();
+ if (aOutSize.Height() - aStartPt.Y() > FIXEDLINE_TEXT_BORDER)
+ aDecoView.DrawSeparator(Point(aStartPt.X(), aStartPt.Y() + FIXEDLINE_TEXT_BORDER),
+ Point(aStartPt.X(), aOutSize.Height() - 1));
+ if (aStartPt.Y() - nWidth - FIXEDLINE_TEXT_BORDER > 0)
+ aDecoView.DrawSeparator(Point(aStartPt.X(), 0),
+ Point(aStartPt.X(), aStartPt.Y() - nWidth - FIXEDLINE_TEXT_BORDER));
+ }
+ else
+ {
+ DrawTextFlags nStyle = DrawTextFlags::Mnemonic | DrawTextFlags::Left | DrawTextFlags::VCenter | DrawTextFlags::EndEllipsis;
+ tools::Rectangle aRect(0, 0, aOutSize.Width(), aOutSize.Height());
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ if (nWinStyle & WB_CENTER)
+ nStyle |= DrawTextFlags::Center;
+
+ if (!IsEnabled())
+ nStyle |= DrawTextFlags::Disable;
+ if (GetStyle() & WB_NOLABEL)
+ nStyle &= ~DrawTextFlags::Mnemonic;
+ if (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono)
+ nStyle |= DrawTextFlags::Mono;
+
+ aRect = DrawControlText(*GetOutDev(), aRect, aText, nStyle, nullptr, nullptr);
+
+ tools::Long nTop = aRect.Top() + ((aRect.GetHeight() - 1) / 2);
+ aDecoView.DrawSeparator(Point(aRect.Right() + FIXEDLINE_TEXT_BORDER, nTop), Point(aOutSize.Width() - 1, nTop), false);
+ if (aRect.Left() > FIXEDLINE_TEXT_BORDER)
+ aDecoView.DrawSeparator(Point(0, nTop), Point(aRect.Left() - FIXEDLINE_TEXT_BORDER, nTop), false);
+ }
+}
+
+FixedLine::FixedLine( vcl::Window* pParent, WinBits nStyle ) :
+ Control( WindowType::FIXEDLINE )
+{
+ ImplInit( pParent, nStyle );
+ SetSizePixel( Size( 2, 2 ) );
+}
+
+void FixedLine::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ const_cast<FixedLine*>(this)->Invalidate();
+}
+
+void FixedLine::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ Control::ApplySettings(rRenderContext);
+
+ vcl::Window* pParent = GetParent();
+ if (pParent->IsChildTransparentModeEnabled() && !IsControlBackground())
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode(ParentClipMode::NoClip);
+ SetPaintTransparent(true);
+ rRenderContext.SetBackground();
+ }
+ else
+ {
+ EnableChildTransparentMode(false);
+ SetParentClipMode();
+ SetPaintTransparent(false);
+
+ if (IsControlBackground())
+ rRenderContext.SetBackground(GetControlBackground());
+ else
+ rRenderContext.SetBackground(pParent->GetBackground());
+ }
+}
+
+void FixedLine::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ ImplDraw(rRenderContext);
+}
+
+void FixedLine::Draw( OutputDevice*, const Point&, SystemTextColorFlags )
+{
+}
+
+void FixedLine::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void FixedLine::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ if ( (GetPrevStyle() & FIXEDLINE_VIEW_STYLE) !=
+ (GetStyle() & FIXEDLINE_VIEW_STYLE) )
+ Invalidate();
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::Style) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+void FixedLine::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+Size FixedLine::GetOptimalSize() const
+{
+ return CalcWindowSize( FixedText::CalcMinimumTextSize ( this ) );
+}
+
+void FixedLine::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("type", "separator");
+ rJsonWriter.put("orientation", (GetStyle() & WB_VERT) ? "vertical" : "horizontal");
+}
+
+void FixedBitmap::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle( nStyle );
+ Control::ImplInit( pParent, nStyle, nullptr );
+ ApplySettings(*GetOutDev());
+}
+
+WinBits FixedBitmap::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+FixedBitmap::FixedBitmap( vcl::Window* pParent, WinBits nStyle ) :
+ Control( WindowType::FIXEDBITMAP )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void FixedBitmap::ImplDraw( OutputDevice* pDev, const Point& rPos, const Size& rSize )
+{
+ // do we have a Bitmap?
+ if ( !maBitmap.IsEmpty() )
+ {
+ if ( GetStyle() & WB_SCALE )
+ pDev->DrawBitmapEx( rPos, rSize, maBitmap );
+ else
+ {
+ Point aPos = ImplCalcPos( GetStyle(), rPos, maBitmap.GetSizePixel(), rSize );
+ pDev->DrawBitmapEx( aPos, maBitmap );
+ }
+ }
+}
+
+void FixedBitmap::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ vcl::Window* pParent = GetParent();
+ if (pParent->IsChildTransparentModeEnabled() && !IsControlBackground())
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode(ParentClipMode::NoClip);
+ SetPaintTransparent(true);
+ rRenderContext.SetBackground();
+ }
+ else
+ {
+ EnableChildTransparentMode(false);
+ SetParentClipMode();
+ SetPaintTransparent(false);
+
+ if (IsControlBackground())
+ rRenderContext.SetBackground(GetControlBackground());
+ else
+ rRenderContext.SetBackground(pParent->GetBackground());
+ }
+}
+
+void FixedBitmap::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ ImplDraw(&rRenderContext, Point(), GetOutputSizePixel());
+}
+
+void FixedBitmap::Draw( OutputDevice* pDev, const Point& rPos,
+ SystemTextColorFlags )
+{
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ tools::Rectangle aRect( aPos, aSize );
+
+ pDev->Push();
+ pDev->SetMapMode();
+
+ // Border
+ if ( GetStyle() & WB_BORDER )
+ {
+ DecorationView aDecoView( pDev );
+ aRect = aDecoView.DrawFrame( aRect, DrawFrameStyle::DoubleIn );
+ }
+ pDev->IntersectClipRegion( aRect );
+ ImplDraw( pDev, aRect.TopLeft(), aRect.GetSize() );
+
+ pDev->Pop();
+}
+
+void FixedBitmap::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void FixedBitmap::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( (nType == StateChangedType::Data) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ if ( (GetPrevStyle() & FIXEDBITMAP_VIEW_STYLE) !=
+ (GetStyle() & FIXEDBITMAP_VIEW_STYLE) )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+void FixedBitmap::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+void FixedBitmap::SetBitmap( const BitmapEx& rBitmap )
+{
+ maBitmap = rBitmap;
+ CompatStateChanged( StateChangedType::Data );
+ queue_resize();
+}
+
+void FixedImage::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle( nStyle );
+ Control::ImplInit( pParent, nStyle, nullptr );
+ ApplySettings(*GetOutDev());
+}
+
+WinBits FixedImage::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+FixedImage::FixedImage( vcl::Window* pParent, WinBits nStyle ) :
+ Control( WindowType::FIXEDIMAGE )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void FixedImage::ImplDraw( OutputDevice* pDev,
+ const Point& rPos, const Size& rSize )
+{
+ DrawImageFlags nStyle = DrawImageFlags::NONE;
+ if ( !IsEnabled() )
+ nStyle |= DrawImageFlags::Disable;
+
+ Image *pImage = &maImage;
+
+ // do we have an image?
+ if ( !(!(*pImage)) )
+ {
+ if ( GetStyle() & WB_SCALE )
+ pDev->DrawImage( rPos, rSize, *pImage, nStyle );
+ else
+ {
+ Point aPos = ImplCalcPos( GetStyle(), rPos, pImage->GetSizePixel(), rSize );
+ pDev->DrawImage( aPos, *pImage, nStyle );
+ }
+ }
+}
+
+void FixedImage::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ vcl::Window* pParent = GetParent();
+ if (pParent && pParent->IsChildTransparentModeEnabled() && !IsControlBackground())
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode(ParentClipMode::NoClip);
+ SetPaintTransparent(true);
+ rRenderContext.SetBackground();
+ }
+ else
+ {
+ EnableChildTransparentMode(false);
+ SetParentClipMode();
+ SetPaintTransparent(false);
+
+ if (IsControlBackground())
+ rRenderContext.SetBackground(GetControlBackground());
+ else if (pParent)
+ rRenderContext.SetBackground(pParent->GetBackground());
+ }
+}
+
+
+void FixedImage::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ ImplDraw(&rRenderContext, Point(), GetOutputSizePixel());
+}
+
+Size FixedImage::GetOptimalSize() const
+{
+ return maImage.GetSizePixel();
+}
+
+void FixedImage::Draw( OutputDevice* pDev, const Point& rPos,
+ SystemTextColorFlags )
+{
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ tools::Rectangle aRect( aPos, aSize );
+
+ pDev->Push();
+ pDev->SetMapMode();
+
+ // Border
+ if ( GetStyle() & WB_BORDER )
+ {
+ ImplDrawFrame( pDev, aRect );
+ }
+ pDev->IntersectClipRegion( aRect );
+ ImplDraw( pDev, aRect.TopLeft(), aRect.GetSize() );
+
+ pDev->Pop();
+}
+
+void FixedImage::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void FixedImage::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Data) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ if ( (GetPrevStyle() & FIXEDIMAGE_VIEW_STYLE) !=
+ (GetStyle() & FIXEDIMAGE_VIEW_STYLE) )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+void FixedImage::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+void FixedImage::SetImage( const Image& rImage )
+{
+ if ( rImage != maImage )
+ {
+ maImage = rImage;
+ CompatStateChanged( StateChangedType::Data );
+ queue_resize();
+ }
+}
+
+Image FixedImage::loadThemeImage(const OUString &rFileName)
+{
+ return Image(StockImage::Yes, rFileName);
+}
+
+bool FixedImage::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "icon-size")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~WB_SMALLSTYLE;
+ if (rValue == "2")
+ nBits |= WB_SMALLSTYLE;
+ SetStyle(nBits);
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+void FixedImage::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("id", get_id());
+ rJsonWriter.put("type", "image");
+ if (!!maImage)
+ {
+ SvMemoryStream aOStm(6535, 6535);
+ if(GraphicConverter::Export(aOStm, maImage.GetBitmapEx(), ConvertDataFormat::PNG) == ERRCODE_NONE)
+ {
+ css::uno::Sequence<sal_Int8> aSeq( static_cast<sal_Int8 const *>(aOStm.GetData()), aOStm.Tell());
+ OUStringBuffer aBuffer("data:image/png;base64,");
+ ::comphelper::Base64::encode(aBuffer, aSeq);
+ rJsonWriter.put("image", aBuffer.makeStringAndClear());
+ }
+ }
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/fixedhyper.cxx b/vcl/source/control/fixedhyper.cxx
new file mode 100644
index 000000000..8a451d2fa
--- /dev/null
+++ b/vcl/source/control/fixedhyper.cxx
@@ -0,0 +1,186 @@
+/* -*- 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 <vcl/event.hxx>
+#include <vcl/toolkit/fixedhyper.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <comphelper/anytostring.hxx>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+
+#include <com/sun/star/system/XSystemShellExecute.hpp>
+#include <com/sun/star/system/SystemShellExecuteFlags.hpp>
+#include <com/sun/star/system/SystemShellExecute.hpp>
+
+using namespace css;
+
+FixedHyperlink::FixedHyperlink(vcl::Window* pParent, WinBits nWinStyle)
+ : FixedText(pParent, nWinStyle)
+ , m_nTextLen(0)
+ , m_aOldPointer(PointerStyle::Arrow)
+{
+ Initialize();
+}
+
+void FixedHyperlink::Initialize()
+{
+ // saves the old pointer
+ m_aOldPointer = GetPointer();
+ // changes the font
+ vcl::Font aFont = GetControlFont( );
+ // to underline
+ aFont.SetUnderline( LINESTYLE_SINGLE );
+ SetControlFont( aFont );
+ // changes the color to link color
+ SetControlForeground( Application::GetSettings().GetStyleSettings().GetLinkColor() );
+ // calculates text len
+ m_nTextLen = GetOutDev()->GetCtrlTextWidth( GetText() );
+
+ SetClickHdl(LINK(this, FixedHyperlink, HandleClick));
+}
+
+bool FixedHyperlink::ImplIsOverText(Point aPosition) const
+{
+ Size aSize = GetOutputSizePixel();
+
+ bool bIsOver = false;
+
+ if (GetStyle() & WB_RIGHT)
+ {
+ return aPosition.X() > (aSize.Width() - m_nTextLen);
+ }
+ else if (GetStyle() & WB_CENTER)
+ {
+ bIsOver = aPosition.X() > (aSize.Width() / 2 - m_nTextLen / 2) &&
+ aPosition.X() < (aSize.Width() / 2 + m_nTextLen / 2);
+ }
+ else
+ {
+ bIsOver = aPosition.X() < m_nTextLen;
+ }
+
+ return bIsOver;
+}
+
+void FixedHyperlink::MouseMove( const MouseEvent& rMEvt )
+{
+ // changes the pointer if the control is enabled and the mouse is over the text.
+ if ( !rMEvt.IsLeaveWindow() && IsEnabled() && ImplIsOverText(GetPointerPosPixel()) )
+ SetPointer( PointerStyle::RefHand );
+ else
+ SetPointer( m_aOldPointer );
+}
+
+void FixedHyperlink::MouseButtonUp( const MouseEvent& )
+{
+ // calls the link if the control is enabled and the mouse is over the text.
+ if ( IsEnabled() && ImplIsOverText(GetPointerPosPixel()) )
+ ImplCallEventListenersAndHandler( VclEventId::ButtonClick, [this] () { m_aClickHdl.Call(*this); } );
+}
+
+void FixedHyperlink::RequestHelp( const HelpEvent& rHEvt )
+{
+ if ( IsEnabled() && ImplIsOverText(GetPointerPosPixel()) )
+ FixedText::RequestHelp( rHEvt );
+}
+
+void FixedHyperlink::GetFocus()
+{
+ Size aSize = GetSizePixel();
+ tools::Rectangle aFocusRect(Point(1, 1), Size(m_nTextLen + 4, aSize.Height() - 2));
+ if (GetStyle() & WB_RIGHT)
+ aFocusRect.Move(aSize.Width() - aFocusRect.getWidth(), 0);
+ else if (GetStyle() & WB_CENTER)
+ aFocusRect.Move((aSize.Width() - aFocusRect.getWidth()) / 2, 0);
+
+ Invalidate(aFocusRect);
+ ShowFocus(aFocusRect);
+}
+
+void FixedHyperlink::LoseFocus()
+{
+ SetTextColor( GetControlForeground() );
+ Invalidate(tools::Rectangle(Point(), GetSizePixel()));
+ HideFocus();
+}
+
+void FixedHyperlink::KeyInput( const KeyEvent& rKEvt )
+{
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_SPACE:
+ case KEY_RETURN:
+ m_aClickHdl.Call( *this );
+ break;
+
+ default:
+ FixedText::KeyInput( rKEvt );
+ }
+}
+
+void FixedHyperlink::SetURL( const OUString& rNewURL )
+{
+ m_sURL = rNewURL;
+ SetQuickHelpText( m_sURL );
+}
+
+
+void FixedHyperlink::SetText(const OUString& rNewDescription)
+{
+ FixedText::SetText(rNewDescription);
+ m_nTextLen = GetOutDev()->GetCtrlTextWidth(GetText());
+}
+
+bool FixedHyperlink::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "uri")
+ SetURL(rValue);
+ else
+ return FixedText::set_property(rKey, rValue);
+ return true;
+}
+
+IMPL_LINK(FixedHyperlink, HandleClick, FixedHyperlink&, rHyperlink, void)
+{
+ if ( rHyperlink.m_sURL.isEmpty() ) // Nothing to do, when the URL is empty
+ return;
+
+ try
+ {
+ uno::Reference< system::XSystemShellExecute > xSystemShellExecute(
+ system::SystemShellExecute::create(comphelper::getProcessComponentContext()));
+ //throws css::lang::IllegalArgumentException, css::system::SystemShellExecuteException
+ xSystemShellExecute->execute( rHyperlink.m_sURL, OUString(), system::SystemShellExecuteFlags::URIS_ONLY );
+ }
+ catch ( const uno::Exception& )
+ {
+ uno::Any exc(cppu::getCaughtException());
+ OUString msg(comphelper::anyToString(exc));
+ SolarMutexGuard g;
+ std::shared_ptr<weld::MessageDialog> xErrorBox(
+ Application::CreateMessageDialog(GetFrameWeld(), VclMessageType::Error, VclButtonsType::Ok, msg));
+ xErrorBox->set_title(rHyperlink.GetText());
+ xErrorBox->runAsync(xErrorBox, [](sal_Int32){});
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/fmtfield.cxx b/vcl/source/control/fmtfield.cxx
new file mode 100644
index 000000000..a88b6a9bb
--- /dev/null
+++ b/vcl/source/control/fmtfield.cxx
@@ -0,0 +1,1367 @@
+/* -*- 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 <tools/debug.hxx>
+#include <boost/property_tree/json_parser.hpp>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/event.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/commandevent.hxx>
+#include <svl/zformat.hxx>
+#include <vcl/toolkit/fmtfield.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/uitest/formattedfielduiobject.hxx>
+#include <vcl/weld.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <unotools/syslocale.hxx>
+#include <limits>
+#include <map>
+#include <rtl/math.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <svl/numformat.hxx>
+#include <osl/diagnose.h>
+#include <tools/json_writer.hxx>
+
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::util;
+
+// hmm. No support for regular expression. Well, I always (not really :) wanted to write a finite automat
+// so here comes a finite automat ...
+
+namespace validation
+{
+ static void lcl_insertStopTransition( StateTransitions& _rRow )
+ {
+ _rRow.insert( Transition( '_', END ) );
+ }
+
+ static void lcl_insertStartExponentTransition( StateTransitions& _rRow )
+ {
+ _rRow.insert( Transition( 'e', EXPONENT_START ) );
+ }
+
+ static void lcl_insertSignTransitions( StateTransitions& _rRow, const State eNextState )
+ {
+ _rRow.insert( Transition( '-', eNextState ) );
+ _rRow.insert( Transition( '+', eNextState ) );
+ }
+
+ static void lcl_insertDigitTransitions( StateTransitions& _rRow, const State eNextState )
+ {
+ for ( sal_Unicode aChar = '0'; aChar <= '9'; ++aChar )
+ _rRow.insert( Transition( aChar, eNextState ) );
+ }
+
+ static void lcl_insertCommonPreCommaTransitions( StateTransitions& _rRow, const sal_Unicode _cThSep, const sal_Unicode _cDecSep )
+ {
+ // digits are allowed
+ lcl_insertDigitTransitions( _rRow, DIGIT_PRE_COMMA );
+
+ // the thousand separator is allowed
+ _rRow.insert( Transition( _cThSep, DIGIT_PRE_COMMA ) );
+
+ // a comma is allowed
+ _rRow.insert( Transition( _cDecSep, DIGIT_POST_COMMA ) );
+ }
+
+ NumberValidator::NumberValidator( const sal_Unicode _cThSep, const sal_Unicode _cDecSep )
+ {
+ // build up our transition table
+
+ // how to proceed from START
+ {
+ StateTransitions& rRow = m_aTransitions[ START ];
+ rRow.insert( Transition( '_', NUM_START ) );
+ // if we encounter the normalizing character, we want to proceed with the number
+ }
+
+ // how to proceed from NUM_START
+ {
+ StateTransitions& rRow = m_aTransitions[ NUM_START ];
+
+ // a sign is allowed
+ lcl_insertSignTransitions( rRow, DIGIT_PRE_COMMA );
+
+ // common transitions for the two pre-comma states
+ lcl_insertCommonPreCommaTransitions( rRow, _cThSep, _cDecSep );
+
+ // the exponent may start here
+ // (this would mean string like "_+e10_", but this is a valid fragment, though no valid number)
+ lcl_insertStartExponentTransition( rRow );
+ }
+
+ // how to proceed from DIGIT_PRE_COMMA
+ {
+ StateTransitions& rRow = m_aTransitions[ DIGIT_PRE_COMMA ];
+
+ // common transitions for the two pre-comma states
+ lcl_insertCommonPreCommaTransitions( rRow, _cThSep, _cDecSep );
+
+ // the exponent may start here
+ lcl_insertStartExponentTransition( rRow );
+
+ // the final transition indicating the end of the string
+ // (if there is no comma and no post-comma, then the string may end here)
+ lcl_insertStopTransition( rRow );
+ }
+
+ // how to proceed from DIGIT_POST_COMMA
+ {
+ StateTransitions& rRow = m_aTransitions[ DIGIT_POST_COMMA ];
+
+ // there might be digits, which would keep the state at DIGIT_POST_COMMA
+ lcl_insertDigitTransitions( rRow, DIGIT_POST_COMMA );
+
+ // the exponent may start here
+ lcl_insertStartExponentTransition( rRow );
+
+ // the string may end here
+ lcl_insertStopTransition( rRow );
+ }
+
+ // how to proceed from EXPONENT_START
+ {
+ StateTransitions& rRow = m_aTransitions[ EXPONENT_START ];
+
+ // there may be a sign
+ lcl_insertSignTransitions( rRow, EXPONENT_DIGIT );
+
+ // there may be digits
+ lcl_insertDigitTransitions( rRow, EXPONENT_DIGIT );
+
+ // the string may end here
+ lcl_insertStopTransition( rRow );
+ }
+
+ // how to proceed from EXPONENT_DIGIT
+ {
+ StateTransitions& rRow = m_aTransitions[ EXPONENT_DIGIT ];
+
+ // there may be digits
+ lcl_insertDigitTransitions( rRow, EXPONENT_DIGIT );
+
+ // the string may end here
+ lcl_insertStopTransition( rRow );
+ }
+
+ // how to proceed from END
+ {
+ /*StateTransitions& rRow =*/ m_aTransitions[ EXPONENT_DIGIT ];
+ // no valid transition to leave this state
+ // (note that we, for consistency, nevertheless want to have a row in the table)
+ }
+ }
+
+ bool NumberValidator::implValidateNormalized( const OUString& _rText )
+ {
+ const sal_Unicode* pCheckPos = _rText.getStr();
+ State eCurrentState = START;
+
+ while ( END != eCurrentState )
+ {
+ // look up the transition row for the current state
+ TransitionTable::const_iterator aRow = m_aTransitions.find( eCurrentState );
+ DBG_ASSERT( m_aTransitions.end() != aRow,
+ "NumberValidator::implValidateNormalized: invalid transition table (row not found)!" );
+
+ if ( m_aTransitions.end() != aRow )
+ {
+ // look up the current character in this row
+ StateTransitions::const_iterator aTransition = aRow->second.find( *pCheckPos );
+ if ( aRow->second.end() != aTransition )
+ {
+ // there is a valid transition for this character
+ eCurrentState = aTransition->second;
+ ++pCheckPos;
+ continue;
+ }
+ }
+
+ // if we're here, there is no valid transition
+ break;
+ }
+
+ DBG_ASSERT( ( END != eCurrentState ) || ( 0 == *pCheckPos ),
+ "NumberValidator::implValidateNormalized: inconsistency!" );
+ // if we're at END, then the string should be done, too - the string should be normalized, means ending
+ // a "_" and not containing any other "_" (except at the start), and "_" is the only possibility
+ // to reach the END state
+
+ // the string is valid if and only if we reached the final state
+ return ( END == eCurrentState );
+ }
+
+ bool NumberValidator::isValidNumericFragment( std::u16string_view _rText )
+ {
+ if ( _rText.empty() )
+ // empty strings are always allowed
+ return true;
+
+ // normalize the string
+ OUString sNormalized = OUString::Concat("_") + _rText + "_";
+
+ return implValidateNormalized( sNormalized );
+ }
+}
+
+SvNumberFormatter* Formatter::StaticFormatter::s_cFormatter = nullptr;
+sal_uLong Formatter::StaticFormatter::s_nReferences = 0;
+
+SvNumberFormatter* Formatter::StaticFormatter::GetFormatter()
+{
+ if (!s_cFormatter)
+ {
+ // get the Office's locale and translate
+ LanguageType eSysLanguage = SvtSysLocale().GetLanguageTag().getLanguageType( false);
+ s_cFormatter = new SvNumberFormatter(
+ ::comphelper::getProcessComponentContext(),
+ eSysLanguage);
+ }
+ return s_cFormatter;
+}
+
+Formatter::StaticFormatter::StaticFormatter()
+{
+ ++s_nReferences;
+}
+
+Formatter::StaticFormatter::~StaticFormatter()
+{
+ if (--s_nReferences == 0)
+ {
+ delete s_cFormatter;
+ s_cFormatter = nullptr;
+ }
+}
+
+Formatter::Formatter()
+ :m_aLastSelection(0,0)
+ ,m_dMinValue(0)
+ ,m_dMaxValue(0)
+ ,m_bHasMin(false)
+ ,m_bHasMax(false)
+ ,m_bWrapOnLimits(false)
+ ,m_bStrictFormat(true)
+ ,m_bEnableEmptyField(true)
+ ,m_bAutoColor(false)
+ ,m_bEnableNaN(false)
+ ,m_bDisableRemainderFactor(false)
+ ,m_ValueState(valueDirty)
+ ,m_dCurrentValue(0)
+ ,m_dDefaultValue(0)
+ ,m_nFormatKey(0)
+ ,m_pFormatter(nullptr)
+ ,m_dSpinSize(1)
+ ,m_dSpinFirst(-1000000)
+ ,m_dSpinLast(1000000)
+ ,m_bTreatAsNumber(true)
+ ,m_pLastOutputColor(nullptr)
+ ,m_bUseInputStringForFormatting(false)
+{
+}
+
+Formatter::~Formatter()
+{
+}
+
+void Formatter::SetFieldText(const OUString& rStr, const Selection& rNewSelection)
+{
+ SetEntryText(rStr, rNewSelection);
+ m_ValueState = valueDirty;
+}
+
+void Formatter::SetTextFormatted(const OUString& rStr)
+{
+ SAL_INFO_IF(GetOrCreateFormatter()->IsTextFormat(m_nFormatKey), "svtools",
+ "FormattedField::SetTextFormatted : valid only with text formats !");
+
+ m_sCurrentTextValue = rStr;
+
+ OUString sFormatted;
+ double dNumber = 0.0;
+ // IsNumberFormat changes the format key parameter
+ sal_uInt32 nTempFormatKey = static_cast< sal_uInt32 >( m_nFormatKey );
+ if( IsUsingInputStringForFormatting() &&
+ GetOrCreateFormatter()->IsNumberFormat(m_sCurrentTextValue, nTempFormatKey, dNumber) )
+ {
+ GetOrCreateFormatter()->GetInputLineString(dNumber, m_nFormatKey, sFormatted);
+ }
+ else
+ {
+ GetOrCreateFormatter()->GetOutputString(m_sCurrentTextValue,
+ m_nFormatKey,
+ sFormatted,
+ &m_pLastOutputColor);
+ }
+
+ // calculate the new selection
+ Selection aSel(GetEntrySelection());
+ Selection aNewSel(aSel);
+ aNewSel.Justify();
+ sal_Int32 nNewLen = sFormatted.getLength();
+ sal_Int32 nCurrentLen = GetEntryText().getLength();
+ if ((nNewLen > nCurrentLen) && (aNewSel.Max() == nCurrentLen))
+ { // the new text is longer and the cursor was behind the last char (of the old text)
+ if (aNewSel.Min() == 0)
+ { // the whole text was selected -> select the new text on the whole, too
+ aNewSel.Max() = nNewLen;
+ if (!nCurrentLen)
+ { // there wasn't really a previous selection (as there was no previous text), we're setting a new one -> check the selection options
+ SelectionOptions nSelOptions = GetEntrySelectionOptions();
+ if (nSelOptions & SelectionOptions::ShowFirst)
+ { // selection should be from right to left -> swap min and max
+ aNewSel.Min() = aNewSel.Max();
+ aNewSel.Max() = 0;
+ }
+ }
+ }
+ else if (aNewSel.Max() == aNewSel.Min())
+ { // there was no selection -> set the cursor behind the new last char
+ aNewSel.Max() = nNewLen;
+ aNewSel.Min() = nNewLen;
+ }
+ }
+ else if (aNewSel.Max() > nNewLen)
+ aNewSel.Max() = nNewLen;
+ else
+ aNewSel = aSel; // don't use the justified version
+ SetEntryText(sFormatted, aNewSel);
+ m_ValueState = valueString;
+}
+
+OUString const & Formatter::GetTextValue() const
+{
+ if (m_ValueState != valueString )
+ {
+ const_cast<Formatter*>(this)->m_sCurrentTextValue = GetEntryText();
+ const_cast<Formatter*>(this)->m_ValueState = valueString;
+ }
+ return m_sCurrentTextValue;
+}
+
+void Formatter::EnableNotANumber(bool _bEnable)
+{
+ if ( m_bEnableNaN == _bEnable )
+ return;
+
+ m_bEnableNaN = _bEnable;
+}
+
+void Formatter::SetAutoColor(bool _bAutomatic)
+{
+ if (_bAutomatic == m_bAutoColor)
+ return;
+
+ m_bAutoColor = _bAutomatic;
+ if (m_bAutoColor)
+ {
+ // if auto color is switched on, adjust the current text color, too
+ SetEntryTextColor(m_pLastOutputColor);
+ }
+}
+
+void Formatter::Modify(bool makeValueDirty)
+{
+ if (!IsStrictFormat())
+ {
+ if(makeValueDirty)
+ m_ValueState = valueDirty;
+ FieldModified();
+ return;
+ }
+
+ OUString sCheck = GetEntryText();
+ if (CheckText(sCheck))
+ {
+ m_sLastValidText = sCheck;
+ m_aLastSelection = GetEntrySelection();
+ if(makeValueDirty)
+ m_ValueState = valueDirty;
+ }
+ else
+ {
+ ImplSetTextImpl(m_sLastValidText, &m_aLastSelection);
+ }
+
+ FieldModified();
+}
+
+void Formatter::ImplSetTextImpl(const OUString& rNew, Selection const * pNewSel)
+{
+ if (m_bAutoColor)
+ SetEntryTextColor(m_pLastOutputColor);
+
+ if (pNewSel)
+ SetEntryText(rNew, *pNewSel);
+ else
+ {
+ Selection aSel(GetEntrySelection());
+ aSel.Justify();
+
+ sal_Int32 nNewLen = rNew.getLength();
+ sal_Int32 nCurrentLen = GetEntryText().getLength();
+
+ if ((nNewLen > nCurrentLen) && (aSel.Max() == nCurrentLen))
+ { // new text is longer and the cursor is behind the last char
+ if (aSel.Min() == 0)
+ {
+ if (!nCurrentLen)
+ { // there wasn't really a previous selection (as there was no previous text)
+ aSel.Max() = 0;
+ }
+ else
+ { // the whole text was selected -> select the new text on the whole, too
+ aSel.Max() = nNewLen;
+ }
+ }
+ else if (aSel.Max() == aSel.Min())
+ { // there was no selection -> set the cursor behind the new last char
+ aSel.Max() = nNewLen;
+ aSel.Min() = nNewLen;
+ }
+ }
+ else if (aSel.Max() > nNewLen)
+ aSel.Max() = nNewLen;
+ SetEntryText(rNew, aSel);
+ }
+
+ m_ValueState = valueDirty; // not always necessary, but better re-evaluate for safety reasons
+}
+
+void Formatter::ImplSetFormatKey(sal_uLong nFormatKey)
+{
+ m_nFormatKey = nFormatKey;
+ bool bNeedFormatter = (m_pFormatter == nullptr) && (nFormatKey != 0);
+ if (bNeedFormatter)
+ {
+ GetOrCreateFormatter(); // this creates a standard formatter
+
+ // It might happen that the standard formatter makes no sense here, but it takes a default
+ // format. Thus, it is possible to set one of the other standard keys (which are spanning
+ // across multiple formatters).
+ m_nFormatKey = nFormatKey;
+ // When calling SetFormatKey without a formatter, the key must be one of the standard values
+ // that is available for all formatters (and, thus, also in this new one).
+ DBG_ASSERT(m_pFormatter->GetEntry(nFormatKey) != nullptr, "FormattedField::ImplSetFormatKey : invalid format key !");
+ }
+}
+
+void Formatter::SetFormatKey(sal_uLong nFormatKey)
+{
+ bool bNoFormatter = (m_pFormatter == nullptr);
+ ImplSetFormatKey(nFormatKey);
+ FormatChanged((bNoFormatter && (m_pFormatter != nullptr)) ? FORMAT_CHANGE_TYPE::FORMATTER : FORMAT_CHANGE_TYPE::KEYONLY);
+}
+
+void Formatter::SetFormatter(SvNumberFormatter* pFormatter, bool bResetFormat)
+{
+
+ if (bResetFormat)
+ {
+ m_pFormatter = pFormatter;
+
+ // calc the default format key from the Office's UI locale
+ if ( m_pFormatter )
+ {
+ // get the Office's locale and translate
+ LanguageType eSysLanguage = SvtSysLocale().GetLanguageTag().getLanguageType( false);
+ // get the standard numeric format for this language
+ m_nFormatKey = m_pFormatter->GetStandardFormat( SvNumFormatType::NUMBER, eSysLanguage );
+ }
+ else
+ m_nFormatKey = 0;
+ }
+ else
+ {
+ LanguageType aOldLang;
+ OUString sOldFormat = GetFormat(aOldLang);
+
+ sal_uInt32 nDestKey = pFormatter->TestNewString(sOldFormat);
+ if (nDestKey == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ // language of the new formatter
+ const SvNumberformat* pDefaultEntry = pFormatter->GetEntry(0);
+ LanguageType aNewLang = pDefaultEntry ? pDefaultEntry->GetLanguage() : LANGUAGE_DONTKNOW;
+
+ // convert the old format string into the new language
+ sal_Int32 nCheckPos;
+ SvNumFormatType nType;
+ pFormatter->PutandConvertEntry(sOldFormat, nCheckPos, nType, nDestKey, aOldLang, aNewLang, true);
+ m_nFormatKey = nDestKey;
+ }
+ m_pFormatter = pFormatter;
+ }
+
+ FormatChanged(FORMAT_CHANGE_TYPE::FORMATTER);
+}
+
+OUString Formatter::GetFormat(LanguageType& eLang) const
+{
+ const SvNumberformat* pFormatEntry = GetOrCreateFormatter()->GetEntry(m_nFormatKey);
+ DBG_ASSERT(pFormatEntry != nullptr, "FormattedField::GetFormat: no number format for the given format key.");
+ OUString sFormatString = pFormatEntry ? pFormatEntry->GetFormatstring() : OUString();
+ eLang = pFormatEntry ? pFormatEntry->GetLanguage() : LANGUAGE_DONTKNOW;
+
+ return sFormatString;
+}
+
+bool Formatter::SetFormat(const OUString& rFormatString, LanguageType eLang)
+{
+ sal_uInt32 nNewKey = GetOrCreateFormatter()->TestNewString(rFormatString, eLang);
+ if (nNewKey == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ sal_Int32 nCheckPos;
+ SvNumFormatType nType;
+ OUString rFormat(rFormatString);
+ if (!GetOrCreateFormatter()->PutEntry(rFormat, nCheckPos, nType, nNewKey, eLang))
+ return false;
+ DBG_ASSERT(nNewKey != NUMBERFORMAT_ENTRY_NOT_FOUND, "FormattedField::SetFormatString : PutEntry returned an invalid key !");
+ }
+
+ if (nNewKey != m_nFormatKey)
+ SetFormatKey(nNewKey);
+ return true;
+}
+
+bool Formatter::GetThousandsSep() const
+{
+ DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey),
+ "FormattedField::GetThousandsSep : Are you sure what you are doing when setting the precision of a text format?");
+
+ bool bThousand, IsRed;
+ sal_uInt16 nPrecision, nLeadingCnt;
+ GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nLeadingCnt);
+
+ return bThousand;
+}
+
+void Formatter::SetThousandsSep(bool _bUseSeparator)
+{
+ DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey),
+ "FormattedField::SetThousandsSep : Are you sure what you are doing when setting the precision of a text format?");
+
+ // get the current settings
+ bool bThousand, IsRed;
+ sal_uInt16 nPrecision, nLeadingCnt;
+ GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nLeadingCnt);
+ if (bThousand == _bUseSeparator)
+ return;
+
+ // we need the language for the following
+ LanguageType eLang;
+ GetFormat(eLang);
+
+ // generate a new format ...
+ OUString sFmtDescription = GetOrCreateFormatter()->GenerateFormat(m_nFormatKey, eLang, _bUseSeparator, IsRed, nPrecision, nLeadingCnt);
+ // ... and introduce it to the formatter
+ sal_Int32 nCheckPos = 0;
+ sal_uInt32 nNewKey;
+ SvNumFormatType nType;
+ GetOrCreateFormatter()->PutEntry(sFmtDescription, nCheckPos, nType, nNewKey, eLang);
+
+ // set the new key
+ ImplSetFormatKey(nNewKey);
+ FormatChanged(FORMAT_CHANGE_TYPE::THOUSANDSSEP);
+}
+
+sal_uInt16 Formatter::GetDecimalDigits() const
+{
+ DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey),
+ "FormattedField::GetDecimalDigits : Are you sure what you are doing when setting the precision of a text format?");
+
+ bool bThousand, IsRed;
+ sal_uInt16 nPrecision, nLeadingCnt;
+ GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nLeadingCnt);
+
+ return nPrecision;
+}
+
+void Formatter::SetDecimalDigits(sal_uInt16 _nPrecision)
+{
+ DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey),
+ "FormattedField::SetDecimalDigits : Are you sure what you are doing when setting the precision of a text format?");
+
+ // get the current settings
+ bool bThousand, IsRed;
+ sal_uInt16 nPrecision, nLeadingCnt;
+ GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nLeadingCnt);
+ if (nPrecision == _nPrecision)
+ return;
+
+ // we need the language for the following
+ LanguageType eLang;
+ GetFormat(eLang);
+
+ // generate a new format ...
+ OUString sFmtDescription = GetOrCreateFormatter()->GenerateFormat(m_nFormatKey, eLang, bThousand, IsRed, _nPrecision, nLeadingCnt);
+ // ... and introduce it to the formatter
+ sal_Int32 nCheckPos = 0;
+ sal_uInt32 nNewKey;
+ SvNumFormatType nType;
+ GetOrCreateFormatter()->PutEntry(sFmtDescription, nCheckPos, nType, nNewKey, eLang);
+
+ // set the new key
+ ImplSetFormatKey(nNewKey);
+ FormatChanged(FORMAT_CHANGE_TYPE::PRECISION);
+}
+
+void Formatter::FormatChanged(FORMAT_CHANGE_TYPE _nWhat)
+{
+ m_pLastOutputColor = nullptr;
+
+ if ( (_nWhat == FORMAT_CHANGE_TYPE::FORMATTER) && m_pFormatter )
+ m_pFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT_INTL );
+
+ ReFormat();
+}
+
+void Formatter::EntryLostFocus()
+{
+ // special treatment for empty texts
+ if (GetEntryText().isEmpty())
+ {
+ if (!IsEmptyFieldEnabled())
+ {
+ if (TreatingAsNumber())
+ {
+ ImplSetValue(m_dCurrentValue, true);
+ Modify();
+ m_ValueState = valueDouble;
+ }
+ else
+ {
+ OUString sNew = GetTextValue();
+ if (!sNew.isEmpty())
+ SetTextFormatted(sNew);
+ else
+ SetTextFormatted(m_sDefaultText);
+ m_ValueState = valueString;
+ }
+ }
+ }
+ else
+ {
+ Commit();
+ }
+}
+
+void Formatter::Commit()
+{
+ // remember the old text
+ OUString sOld(GetEntryText());
+
+ // do the reformat
+ ReFormat();
+
+ // did the text change?
+ if (GetEntryText() != sOld)
+ { // consider the field as modified,
+ // but we already have the most recent value;
+ // don't reparse it from the text
+ // (can lead to data loss when the format is lossy,
+ // as is e.g. our default date format: 2-digit year!)
+ Modify(false);
+ }
+}
+
+void Formatter::ReFormat()
+{
+ if (!IsEmptyFieldEnabled() || !GetEntryText().isEmpty())
+ {
+ if (TreatingAsNumber())
+ {
+ double dValue = GetValue();
+ if ( m_bEnableNaN && std::isnan( dValue ) )
+ return;
+ ImplSetValue( dValue, true );
+ }
+ else
+ SetTextFormatted(GetTextValue());
+ }
+}
+
+void Formatter::SetMinValue(double dMin)
+{
+ DBG_ASSERT(m_bTreatAsNumber, "FormattedField::SetMinValue : only to be used in numeric mode !");
+
+ m_dMinValue = dMin;
+ m_bHasMin = true;
+ // for checking the current value at the new border -> ImplSetValue
+ ReFormat();
+}
+
+void Formatter::SetMaxValue(double dMax)
+{
+ DBG_ASSERT(m_bTreatAsNumber, "FormattedField::SetMaxValue : only to be used in numeric mode !");
+
+ m_dMaxValue = dMax;
+ m_bHasMax = true;
+ // for checking the current value at the new border -> ImplSetValue
+ ReFormat();
+}
+
+void Formatter::SetTextValue(const OUString& rText)
+{
+ SetFieldText(rText, Selection(0, 0));
+ ReFormat();
+}
+
+void Formatter::EnableEmptyField(bool bEnable)
+{
+ if (bEnable == m_bEnableEmptyField)
+ return;
+
+ m_bEnableEmptyField = bEnable;
+ if (!m_bEnableEmptyField && GetEntryText().isEmpty())
+ ImplSetValue(m_dCurrentValue, true);
+}
+
+void Formatter::ImplSetValue(double dVal, bool bForce)
+{
+ if (m_bHasMin && (dVal<m_dMinValue))
+ {
+ dVal = m_bWrapOnLimits ? fmod(dVal + m_dMaxValue + 1 - m_dMinValue, m_dMaxValue + 1) + m_dMinValue
+ : m_dMinValue;
+ }
+ if (m_bHasMax && (dVal>m_dMaxValue))
+ {
+ dVal = m_bWrapOnLimits ? fmod(dVal - m_dMinValue, m_dMaxValue + 1) + m_dMinValue
+ : m_dMaxValue;
+ }
+ if (!bForce && (dVal == GetValue()))
+ return;
+
+ DBG_ASSERT(GetOrCreateFormatter() != nullptr, "FormattedField::ImplSetValue : can't set a value without a formatter !");
+
+ m_ValueState = valueDouble;
+ UpdateCurrentValue(dVal);
+
+ if (!m_aOutputHdl.IsSet() || !m_aOutputHdl.Call(nullptr))
+ {
+ OUString sNewText;
+ if (GetOrCreateFormatter()->IsTextFormat(m_nFormatKey))
+ {
+ // first convert the number as string in standard format
+ OUString sTemp;
+ GetOrCreateFormatter()->GetOutputString(dVal, 0, sTemp, &m_pLastOutputColor);
+ // then encode the string in the corresponding text format
+ GetOrCreateFormatter()->GetOutputString(sTemp, m_nFormatKey, sNewText, &m_pLastOutputColor);
+ }
+ else
+ {
+ if( IsUsingInputStringForFormatting())
+ {
+ GetOrCreateFormatter()->GetInputLineString(dVal, m_nFormatKey, sNewText);
+ }
+ else
+ {
+ GetOrCreateFormatter()->GetOutputString(dVal, m_nFormatKey, sNewText, &m_pLastOutputColor);
+ }
+ }
+ ImplSetTextImpl(sNewText, nullptr);
+ DBG_ASSERT(CheckText(sNewText), "FormattedField::ImplSetValue : formatted string doesn't match the criteria !");
+ }
+
+ m_ValueState = valueDouble;
+}
+
+bool Formatter::ImplGetValue(double& dNewVal)
+{
+ dNewVal = m_dCurrentValue;
+ if (m_ValueState == valueDouble)
+ return true;
+
+ dNewVal = m_dDefaultValue;
+ OUString sText(GetEntryText());
+ if (sText.isEmpty())
+ return true;
+
+ bool bUseExternalFormatterValue = false;
+ if (m_aInputHdl.IsSet())
+ {
+ sal_Int64 nResult;
+ auto eState = m_aInputHdl.Call(&nResult);
+ bUseExternalFormatterValue = eState != TRISTATE_INDET;
+ if (bUseExternalFormatterValue)
+ {
+ if (eState == TRISTATE_TRUE)
+ {
+ dNewVal = nResult;
+ dNewVal /= weld::SpinButton::Power10(GetDecimalDigits());
+ }
+ else
+ dNewVal = m_dCurrentValue;
+ }
+ }
+
+ if (!bUseExternalFormatterValue)
+ {
+ DBG_ASSERT(GetOrCreateFormatter() != nullptr, "FormattedField::ImplGetValue : can't give you a current value without a formatter !");
+
+ sal_uInt32 nFormatKey = m_nFormatKey; // IsNumberFormat changes the FormatKey!
+
+ if (GetOrCreateFormatter()->IsTextFormat(nFormatKey) && m_bTreatAsNumber)
+ // for detection of values like "1,1" in fields that are formatted as text
+ nFormatKey = 0;
+
+ // special treatment for percentage formatting
+ if (GetOrCreateFormatter()->GetType(m_nFormatKey) == SvNumFormatType::PERCENT)
+ {
+ // the language of our format
+ LanguageType eLanguage = m_pFormatter->GetEntry(m_nFormatKey)->GetLanguage();
+ // the default number format for this language
+ sal_uLong nStandardNumericFormat = m_pFormatter->GetStandardFormat(SvNumFormatType::NUMBER, eLanguage);
+
+ sal_uInt32 nTempFormat = nStandardNumericFormat;
+ double dTemp;
+ if (m_pFormatter->IsNumberFormat(sText, nTempFormat, dTemp) &&
+ SvNumFormatType::NUMBER == m_pFormatter->GetType(nTempFormat))
+ // the string is equivalent to a number formatted one (has no % sign) -> append it
+ sText += "%";
+ // (with this, an input of '3' becomes '3%', which then by the formatter is translated
+ // into 0.03. Without this, the formatter would give us the double 3 for an input '3',
+ // which equals 300 percent.
+ }
+ if (!GetOrCreateFormatter()->IsNumberFormat(sText, nFormatKey, dNewVal))
+ return false;
+ }
+
+ if (m_bHasMin && (dNewVal<m_dMinValue))
+ dNewVal = m_dMinValue;
+ if (m_bHasMax && (dNewVal>m_dMaxValue))
+ dNewVal = m_dMaxValue;
+ return true;
+}
+
+void Formatter::SetValue(double dVal)
+{
+ ImplSetValue(dVal, m_ValueState != valueDouble);
+}
+
+double Formatter::GetValue()
+{
+ if ( !ImplGetValue( m_dCurrentValue ) )
+ UpdateCurrentValue(m_bEnableNaN ? std::numeric_limits<double>::quiet_NaN() : m_dDefaultValue);
+
+ m_ValueState = valueDouble;
+ return m_dCurrentValue;
+}
+
+void Formatter::DisableRemainderFactor()
+{
+ m_bDisableRemainderFactor = true;
+}
+
+void Formatter::UseInputStringForFormatting()
+{
+ m_bUseInputStringForFormatting = true;
+}
+
+namespace
+{
+ class FieldFormatter : public Formatter
+ {
+ private:
+ FormattedField& m_rSpinButton;
+ public:
+ FieldFormatter(FormattedField& rSpinButton)
+ : m_rSpinButton(rSpinButton)
+ {
+ }
+
+ // Formatter overrides
+ virtual Selection GetEntrySelection() const override
+ {
+ return m_rSpinButton.GetSelection();
+ }
+
+ virtual OUString GetEntryText() const override
+ {
+ return m_rSpinButton.GetText();
+ }
+
+ void SetEntryText(const OUString& rText, const Selection& rSel) override
+ {
+ m_rSpinButton.SpinField::SetText(rText, rSel);
+ }
+
+ virtual void SetEntryTextColor(const Color* pColor) override
+ {
+ if (pColor)
+ m_rSpinButton.SetControlForeground(*pColor);
+ else
+ m_rSpinButton.SetControlForeground();
+ }
+
+ virtual SelectionOptions GetEntrySelectionOptions() const override
+ {
+ return m_rSpinButton.GetSettings().GetStyleSettings().GetSelectionOptions();
+ }
+
+ virtual void FieldModified() override
+ {
+ m_rSpinButton.SpinField::Modify();
+ }
+
+ virtual void UpdateCurrentValue(double dCurrentValue) override
+ {
+ Formatter::UpdateCurrentValue(dCurrentValue);
+ m_rSpinButton.SetUpperEnabled(!m_bHasMax || dCurrentValue < m_dMaxValue);
+ m_rSpinButton.SetLowerEnabled(!m_bHasMin || dCurrentValue > m_dMinValue);
+ }
+ };
+
+ class DoubleNumericFormatter : public FieldFormatter
+ {
+ private:
+ DoubleNumericField& m_rNumericSpinButton;
+ public:
+ DoubleNumericFormatter(DoubleNumericField& rNumericSpinButton)
+ : FieldFormatter(rNumericSpinButton)
+ , m_rNumericSpinButton(rNumericSpinButton)
+ {
+ }
+
+ virtual bool CheckText(const OUString& sText) const override
+ {
+ // We'd like to implement this using the NumberFormatter::IsNumberFormat, but unfortunately, this doesn't
+ // recognize fragments of numbers (like, for instance "1e", which happens during entering e.g. "1e10")
+ // Thus, the roundabout way via a regular expression
+ return m_rNumericSpinButton.GetNumberValidator().isValidNumericFragment(sText);
+ }
+
+ virtual void FormatChanged(FORMAT_CHANGE_TYPE nWhat) override
+ {
+ m_rNumericSpinButton.ResetConformanceTester();
+ FieldFormatter::FormatChanged(nWhat);
+ }
+ };
+
+ class DoubleCurrencyFormatter : public FieldFormatter
+ {
+ private:
+ DoubleCurrencyField& m_rCurrencySpinButton;
+ bool m_bChangingFormat;
+ public:
+ DoubleCurrencyFormatter(DoubleCurrencyField& rNumericSpinButton)
+ : FieldFormatter(rNumericSpinButton)
+ , m_rCurrencySpinButton(rNumericSpinButton)
+ , m_bChangingFormat(false)
+ {
+ }
+
+ virtual void FormatChanged(FORMAT_CHANGE_TYPE nWhat) override
+ {
+ if (m_bChangingFormat)
+ {
+ FieldFormatter::FormatChanged(nWhat);
+ return;
+ }
+
+ switch (nWhat)
+ {
+ case FORMAT_CHANGE_TYPE::FORMATTER:
+ case FORMAT_CHANGE_TYPE::PRECISION:
+ case FORMAT_CHANGE_TYPE::THOUSANDSSEP:
+ // the aspects which changed don't take our currency settings into account (in fact, they most probably
+ // destroyed them)
+ m_rCurrencySpinButton.UpdateCurrencyFormat();
+ break;
+ case FORMAT_CHANGE_TYPE::KEYONLY:
+ OSL_FAIL("DoubleCurrencyField::FormatChanged : somebody modified my key !");
+ // We always build our own format from the settings we get via special methods (setCurrencySymbol etc.).
+ // Nobody but ourself should modify the format key directly!
+ break;
+ default: break;
+ }
+
+ FieldFormatter::FormatChanged(nWhat);
+ }
+
+ void GuardSetFormat(const OUString& rString, LanguageType eLanguage)
+ {
+ // set this new basic format
+ m_bChangingFormat = true;
+ SetFormat(rString, eLanguage);
+ m_bChangingFormat = false;
+ }
+
+ };
+}
+
+DoubleNumericField::DoubleNumericField(vcl::Window* pParent, WinBits nStyle)
+ : FormattedField(pParent, nStyle)
+{
+ m_xOwnFormatter.reset(new DoubleNumericFormatter(*this));
+ m_pFormatter = m_xOwnFormatter.get();
+ ResetConformanceTester();
+}
+
+DoubleNumericField::~DoubleNumericField() = default;
+
+void DoubleNumericField::ResetConformanceTester()
+{
+ // the thousands and the decimal separator are language dependent
+ Formatter& rFormatter = GetFormatter();
+ const SvNumberformat* pFormatEntry = rFormatter.GetOrCreateFormatter()->GetEntry(rFormatter.GetFormatKey());
+
+ sal_Unicode cSeparatorThousand = ',';
+ sal_Unicode cSeparatorDecimal = '.';
+ if (pFormatEntry)
+ {
+ LocaleDataWrapper aLocaleInfo( LanguageTag( pFormatEntry->GetLanguage()) );
+
+ OUString sSeparator = aLocaleInfo.getNumThousandSep();
+ if (!sSeparator.isEmpty())
+ cSeparatorThousand = sSeparator[0];
+
+ sSeparator = aLocaleInfo.getNumDecimalSep();
+ if (!sSeparator.isEmpty())
+ cSeparatorDecimal = sSeparator[0];
+ }
+
+ m_pNumberValidator.reset(new validation::NumberValidator( cSeparatorThousand, cSeparatorDecimal ));
+}
+
+
+DoubleCurrencyField::DoubleCurrencyField(vcl::Window* pParent, WinBits nStyle)
+ :FormattedField(pParent, nStyle)
+{
+ m_xOwnFormatter.reset(new DoubleCurrencyFormatter(*this));
+ m_pFormatter = m_xOwnFormatter.get();
+
+ m_bPrependCurrSym = false;
+
+ // initialize with a system currency format
+ m_sCurrencySymbol = SvtSysLocale().GetLocaleData().getCurrSymbol();
+ UpdateCurrencyFormat();
+}
+
+void DoubleCurrencyField::setCurrencySymbol(const OUString& rSymbol)
+{
+ if (m_sCurrencySymbol == rSymbol)
+ return;
+
+ m_sCurrencySymbol = rSymbol;
+ UpdateCurrencyFormat();
+ m_pFormatter->FormatChanged(FORMAT_CHANGE_TYPE::CURRENCY_SYMBOL);
+}
+
+void DoubleCurrencyField::setPrependCurrSym(bool _bPrepend)
+{
+ if (m_bPrependCurrSym == _bPrepend)
+ return;
+
+ m_bPrependCurrSym = _bPrepend;
+ UpdateCurrencyFormat();
+ m_pFormatter->FormatChanged(FORMAT_CHANGE_TYPE::CURRSYM_POSITION);
+}
+
+void DoubleCurrencyField::UpdateCurrencyFormat()
+{
+ // the old settings
+ LanguageType eLanguage;
+ m_pFormatter->GetFormat(eLanguage);
+ bool bThSep = m_pFormatter->GetThousandsSep();
+ sal_uInt16 nDigits = m_pFormatter->GetDecimalDigits();
+
+ // build a new format string with the base class' and my own settings
+
+ /* Strangely with gcc 4.6.3 this needs a temporary LanguageTag, otherwise
+ * there's
+ * error: request for member 'getNumThousandSep' in 'aLocaleInfo', which is
+ * of non-class type 'LocaleDataWrapper(LanguageTag)' */
+ LocaleDataWrapper aLocaleInfo(( LanguageTag(eLanguage) ));
+
+ OUStringBuffer sNewFormat;
+ if (bThSep)
+ {
+ sNewFormat.append('#');
+ sNewFormat.append(aLocaleInfo.getNumThousandSep());
+ sNewFormat.append("##0");
+ }
+ else
+ sNewFormat.append('0');
+
+ if (nDigits)
+ {
+ sNewFormat.append(aLocaleInfo.getNumDecimalSep());
+ comphelper::string::padToLength(sNewFormat, sNewFormat.getLength() + nDigits, '0');
+ }
+
+ if (getPrependCurrSym())
+ {
+ OUString sSymbol = getCurrencySymbol();
+ sSymbol = comphelper::string::strip(sSymbol, ' ');
+
+ OUStringBuffer sTemp("[$");
+ sTemp.append(sSymbol);
+ sTemp.append("] ");
+ sTemp.append(sNewFormat);
+
+ // for negative values : $ -0.00, not -$ 0.00...
+ // (the real solution would be a possibility to choose a "positive currency format" and a "negative currency format"...
+ // But not now... (and hey, you could take a formatted field for this...))
+ // FS - 31.03.00 74642
+ sTemp.append(";[$");
+ sTemp.append(sSymbol);
+ sTemp.append("] -");
+ sTemp.append(sNewFormat);
+
+ sNewFormat = sTemp;
+ }
+ else
+ {
+ OUString sTemp = getCurrencySymbol();
+ sTemp = comphelper::string::strip(sTemp, ' ');
+
+ sNewFormat.append(" [$");
+ sNewFormat.append(sTemp);
+ sNewFormat.append(']');
+ }
+
+ // set this new basic format
+ static_cast<DoubleCurrencyFormatter*>(m_pFormatter)->GuardSetFormat(sNewFormat.makeStringAndClear(), eLanguage);
+}
+
+FormattedField::FormattedField(vcl::Window* pParent, WinBits nStyle)
+ : SpinField(pParent, nStyle, WindowType::FORMATTEDFIELD)
+ , m_pFormatter(nullptr)
+{
+}
+
+void FormattedField::dispose()
+{
+ m_pFormatter = nullptr;
+ m_xOwnFormatter.reset();
+ SpinField::dispose();
+}
+
+void FormattedField::SetText(const OUString& rStr)
+{
+ GetFormatter().SetFieldText(rStr, Selection(0, 0));
+}
+
+void FormattedField::SetText(const OUString& rStr, const Selection& rNewSelection)
+{
+ GetFormatter().SetFieldText(rStr, rNewSelection);
+ SetSelection(rNewSelection);
+}
+
+bool FormattedField::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "digits")
+ GetFormatter().SetDecimalDigits(rValue.toInt32());
+ else if (rKey == "wrap")
+ GetFormatter().SetWrapOnLimits(toBool(rValue));
+ else
+ return SpinField::set_property(rKey, rValue);
+ return true;
+}
+
+void FormattedField::Up()
+{
+ Formatter& rFormatter = GetFormatter();
+ auto nScale = weld::SpinButton::Power10(rFormatter.GetDecimalDigits());
+
+ sal_Int64 nValue = std::round(rFormatter.GetValue() * nScale);
+ sal_Int64 nSpinSize = std::round(rFormatter.GetSpinSize() * nScale);
+ assert(nSpinSize != 0);
+ sal_Int64 nRemainder = rFormatter.GetDisableRemainderFactor() || nSpinSize == 0 ? 0 : nValue % nSpinSize;
+ if (nValue >= 0)
+ nValue = (nRemainder == 0) ? nValue + nSpinSize : nValue + nSpinSize - nRemainder;
+ else
+ nValue = (nRemainder == 0) ? nValue + nSpinSize : nValue - nRemainder;
+
+ // setValue handles under- and overflows (min/max) automatically
+ rFormatter.SetValue(static_cast<double>(nValue) / nScale);
+ SetModifyFlag();
+ Modify();
+
+ SpinField::Up();
+}
+
+void FormattedField::Down()
+{
+ Formatter& rFormatter = GetFormatter();
+ auto nScale = weld::SpinButton::Power10(rFormatter.GetDecimalDigits());
+
+ sal_Int64 nValue = std::round(rFormatter.GetValue() * nScale);
+ sal_Int64 nSpinSize = std::round(rFormatter.GetSpinSize() * nScale);
+ assert(nSpinSize != 0);
+ sal_Int64 nRemainder = rFormatter.GetDisableRemainderFactor() || nSpinSize == 0 ? 0 : nValue % nSpinSize;
+ if (nValue >= 0)
+ nValue = (nRemainder == 0) ? nValue - nSpinSize : nValue - nRemainder;
+ else
+ nValue = (nRemainder == 0) ? nValue - nSpinSize : nValue - nSpinSize - nRemainder;
+
+ // setValue handles under- and overflows (min/max) automatically
+ rFormatter.SetValue(static_cast<double>(nValue) / nScale);
+ SetModifyFlag();
+ Modify();
+
+ SpinField::Down();
+}
+
+void FormattedField::First()
+{
+ Formatter& rFormatter = GetFormatter();
+ if (rFormatter.HasMinValue())
+ {
+ rFormatter.SetValue(rFormatter.GetMinValue());
+ SetModifyFlag();
+ Modify();
+ }
+
+ SpinField::First();
+}
+
+void FormattedField::Last()
+{
+ Formatter& rFormatter = GetFormatter();
+ if (rFormatter.HasMaxValue())
+ {
+ rFormatter.SetValue(rFormatter.GetMaxValue());
+ SetModifyFlag();
+ Modify();
+ }
+
+ SpinField::Last();
+}
+
+void FormattedField::Modify()
+{
+ GetFormatter().Modify();
+}
+
+bool FormattedField::PreNotify(NotifyEvent& rNEvt)
+{
+ if (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT)
+ GetFormatter().SetLastSelection(GetSelection());
+ return SpinField::PreNotify(rNEvt);
+}
+
+bool FormattedField::EventNotify(NotifyEvent& rNEvt)
+{
+ if ((rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !IsReadOnly())
+ {
+ const KeyEvent& rKEvt = *rNEvt.GetKeyEvent();
+ sal_uInt16 nMod = rKEvt.GetKeyCode().GetModifier();
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ {
+ Formatter& rFormatter = GetFormatter();
+ if (!nMod && rFormatter.GetOrCreateFormatter()->IsTextFormat(rFormatter.GetFormatKey()))
+ {
+ // the base class would translate this into calls to Up/Down/First/Last,
+ // but we don't want this if we are text-formatted
+ return true;
+ }
+ }
+ }
+ }
+
+ if ((rNEvt.GetType() == MouseNotifyEvent::COMMAND) && !IsReadOnly())
+ {
+ const CommandEvent* pCommand = rNEvt.GetCommandEvent();
+ if (pCommand->GetCommand() == CommandEventId::Wheel)
+ {
+ const CommandWheelData* pData = rNEvt.GetCommandEvent()->GetWheelData();
+ Formatter& rFormatter = GetFormatter();
+ if ((pData->GetMode() == CommandWheelMode::SCROLL) &&
+ rFormatter.GetOrCreateFormatter()->IsTextFormat(rFormatter.GetFormatKey()))
+ {
+ // same as above : prevent the base class from doing Up/Down-calls
+ // (normally I should put this test into the Up/Down methods itself, shouldn't I ?)
+ // FS - 71553 - 19.01.00
+ return true;
+ }
+ }
+ }
+
+ if (rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS && m_pFormatter)
+ m_pFormatter->EntryLostFocus();
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+Formatter& FormattedField::GetFormatter()
+{
+ if (!m_pFormatter)
+ {
+ m_xOwnFormatter.reset(new FieldFormatter(*this));
+ m_pFormatter = m_xOwnFormatter.get();
+ }
+ return *m_pFormatter;
+}
+
+void FormattedField::SetFormatter(Formatter* pFormatter)
+{
+ m_xOwnFormatter.reset();
+ m_pFormatter = pFormatter;
+}
+
+// currently used by online
+void FormattedField::SetValueFromString(const OUString& rStr)
+{
+ sal_Int32 nEnd;
+ rtl_math_ConversionStatus eStatus;
+ Formatter& rFormatter = GetFormatter();
+ double fValue = ::rtl::math::stringToDouble(rStr, '.', rFormatter.GetDecimalDigits(), &eStatus, &nEnd );
+
+ if (eStatus == rtl_math_ConversionStatus_Ok &&
+ nEnd == rStr.getLength())
+ {
+ rFormatter.SetValue(fValue);
+ SetModifyFlag();
+ Modify();
+
+ // Notify the value has changed
+ SpinField::Up();
+ }
+ else
+ {
+ SAL_WARN("vcl", "fail to convert the value: " << rStr);
+ }
+}
+
+void FormattedField::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ SpinField::DumpAsPropertyTree(rJsonWriter);
+ Formatter& rFormatter = GetFormatter();
+ rJsonWriter.put("min", rFormatter.GetMinValue());
+ rJsonWriter.put("max", rFormatter.GetMaxValue());
+ rJsonWriter.put("value", rFormatter.GetValue());
+ rJsonWriter.put("step", rFormatter.GetSpinSize());
+}
+
+FactoryFunction FormattedField::GetUITestFactory() const
+{
+ return FormattedFieldUIObject::create;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/hyperlabel.cxx b/vcl/source/control/hyperlabel.cxx
new file mode 100644
index 000000000..34f10750a
--- /dev/null
+++ b/vcl/source/control/hyperlabel.cxx
@@ -0,0 +1,176 @@
+/* -*- 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 <tools/color.hxx>
+#include <vcl/event.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <hyperlabel.hxx>
+
+namespace vcl
+{
+ HyperLabel::HyperLabel( vcl::Window* _pParent, WinBits _nWinStyle )
+ :FixedText( _pParent, _nWinStyle )
+ , ID(0)
+ , Index(0)
+ , bInteractive(false)
+ , m_bHyperMode(false)
+ {
+ implInit();
+ }
+
+ Size const & HyperLabel::CalcMinimumSize( tools::Long nMaxWidth )
+ {
+ m_aMinSize = FixedText::CalcMinimumSize( nMaxWidth );
+ // the MinimumSize is used to size the FocusRectangle
+ // and for the MouseMove method
+ m_aMinSize.AdjustHeight(2 );
+ m_aMinSize.AdjustWidth(1 );
+ return m_aMinSize;
+ }
+
+ void HyperLabel::implInit()
+ {
+ ToggleBackgroundColor( COL_TRANSPARENT );
+
+ WinBits nWinStyle = GetStyle();
+ nWinStyle |= WB_EXTRAOFFSET;
+ SetStyle( nWinStyle );
+
+ Show();
+ }
+
+ void HyperLabel::ToggleBackgroundColor( const Color& _rGBColor )
+ {
+ SetControlBackground( _rGBColor );
+ }
+
+ void HyperLabel::MouseMove( const MouseEvent& rMEvt )
+ {
+ vcl::Font aFont = GetControlFont( );
+
+ bool bHyperMode = false;
+ if (!rMEvt.IsLeaveWindow() && IsEnabled() && bInteractive)
+ {
+ Point aPoint = GetPointerPosPixel();
+ if (aPoint.X() < m_aMinSize.Width())
+ bHyperMode = true;
+ }
+
+ m_bHyperMode = bHyperMode;
+ if (bHyperMode)
+ {
+ aFont.SetUnderline(LINESTYLE_SINGLE);
+ SetPointer(PointerStyle::RefHand);
+ }
+ else
+ {
+ aFont.SetUnderline(LINESTYLE_NONE);
+ SetPointer(PointerStyle::Arrow);
+ }
+ SetControlFont(aFont);
+ }
+
+ void HyperLabel::MouseButtonDown( const MouseEvent& )
+ {
+ if ( m_bHyperMode && bInteractive )
+ {
+ maClickHdl.Call( this );
+ }
+ }
+
+ void HyperLabel::GetFocus()
+ {
+ if ( IsEnabled() && bInteractive )
+ {
+ Point aPoint(0,0);
+ tools::Rectangle rRect(aPoint, Size( m_aMinSize.Width(), GetSizePixel().Height() ) );
+ ShowFocus( rRect );
+ }
+ }
+
+ void HyperLabel::LoseFocus()
+ {
+ HideFocus();
+ }
+
+ HyperLabel::~HyperLabel( )
+ {
+ disposeOnce();
+ }
+
+ void HyperLabel::SetInteractive( bool _bInteractive )
+ {
+ bInteractive = ( _bInteractive && IsEnabled() );
+ }
+
+ sal_Int16 HyperLabel::GetID() const
+ {
+ return ID;
+ }
+
+ sal_Int32 HyperLabel::GetIndex() const
+ {
+ return Index;
+ }
+
+ void HyperLabel::SetID( sal_Int16 newID )
+ {
+ this->ID = newID;
+ }
+
+ void HyperLabel::SetIndex( sal_Int32 newIndex )
+ {
+ Index = newIndex;
+ }
+
+ void HyperLabel::SetLabel( const OUString& _rText )
+ {
+ SetText(_rText);
+ }
+
+ void HyperLabel::ApplySettings(vcl::RenderContext& rRenderContext)
+ {
+ FixedText::ApplySettings(rRenderContext);
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ if (GetControlBackground() == COL_TRANSPARENT)
+ rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
+ else
+ rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
+ }
+
+ void HyperLabel::DataChanged( const DataChangedEvent& rDCEvt )
+ {
+ FixedText::DataChanged( rDCEvt );
+
+ if ((( rDCEvt.GetType() == DataChangedEventType::SETTINGS ) ||
+ ( rDCEvt.GetType() == DataChangedEventType::DISPLAY )) &&
+ ( rDCEvt.GetFlags() & AllSettingsFlags::STYLE ))
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ if (GetControlBackground() != COL_TRANSPARENT)
+ SetControlBackground(rStyleSettings.GetHighlightColor());
+ Invalidate();
+ }
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/imgctrl.cxx b/vcl/source/control/imgctrl.cxx
new file mode 100644
index 000000000..414824b29
--- /dev/null
+++ b/vcl/source/control/imgctrl.cxx
@@ -0,0 +1,184 @@
+/* -*- 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 <vcl/toolkit/imgctrl.hxx>
+
+#include <com/sun/star/awt/ImageScaleMode.hpp>
+#include <osl/diagnose.h>
+
+namespace ImageScaleMode = css::awt::ImageScaleMode;
+
+ImageControl::ImageControl( vcl::Window* pParent, WinBits nStyle )
+ :FixedImage( pParent, nStyle )
+ ,mnScaleMode( ImageScaleMode::ANISOTROPIC )
+{
+}
+
+void ImageControl::SetScaleMode( const ::sal_Int16 _nMode )
+{
+ if ( _nMode != mnScaleMode )
+ {
+ mnScaleMode = _nMode;
+ Invalidate();
+ }
+}
+
+void ImageControl::Resize()
+{
+ Invalidate();
+}
+
+namespace
+{
+ Size lcl_calcPaintSize( const tools::Rectangle& _rPaintRect, const Size& _rBitmapSize )
+ {
+ const Size aPaintSize = _rPaintRect.GetSize();
+
+ const double nRatioX = 1.0 * aPaintSize.Width() / _rBitmapSize.Width();
+ const double nRatioY = 1.0 * aPaintSize.Height() / _rBitmapSize.Height();
+ const double nRatioMin = ::std::min( nRatioX, nRatioY );
+
+ return Size( tools::Long( _rBitmapSize.Width() * nRatioMin ), tools::Long( _rBitmapSize.Height() * nRatioMin ) );
+ }
+
+ Point lcl_centerWithin( const tools::Rectangle& _rArea, const Size& _rObjectSize )
+ {
+ Point aPos( _rArea.TopLeft() );
+ aPos.AdjustX(( _rArea.GetWidth() - _rObjectSize.Width() ) / 2 );
+ aPos.AdjustY(( _rArea.GetHeight() - _rObjectSize.Height() ) / 2 );
+ return aPos;
+ }
+}
+
+void ImageControl::ImplDraw(OutputDevice& rDev, const Point& rPos, const Size& rSize) const
+{
+ DrawImageFlags nStyle = DrawImageFlags::NONE;
+ if ( !IsEnabled() )
+ nStyle |= DrawImageFlags::Disable;
+
+ const Image& rImage( GetModeImage() );
+ const tools::Rectangle aDrawRect( rPos, rSize );
+ if (!rImage)
+ {
+ OUString sText( GetText() );
+ if ( sText.isEmpty() )
+ return;
+
+ WinBits nWinStyle = GetStyle();
+ DrawTextFlags nTextStyle = FixedText::ImplGetTextStyle( nWinStyle );
+ if ( !IsEnabled() )
+ nTextStyle |= DrawTextFlags::Disable;
+
+ rDev.DrawText( aDrawRect, sText, nTextStyle );
+ return;
+ }
+
+ const Size& rBitmapSize = rImage.GetSizePixel();
+
+ switch ( mnScaleMode )
+ {
+ case ImageScaleMode::NONE:
+ {
+ rDev.DrawImage(lcl_centerWithin( aDrawRect, rBitmapSize ), rImage, nStyle);
+ }
+ break;
+
+ case ImageScaleMode::ISOTROPIC:
+ {
+ const Size aPaintSize = lcl_calcPaintSize( aDrawRect, rBitmapSize );
+ rDev.DrawImage(lcl_centerWithin(aDrawRect, aPaintSize), aPaintSize, rImage, nStyle);
+ }
+ break;
+
+ case ImageScaleMode::ANISOTROPIC:
+ {
+ rDev.DrawImage(
+ aDrawRect.TopLeft(),
+ aDrawRect.GetSize(),
+ rImage, nStyle );
+ }
+ break;
+
+ default:
+ OSL_ENSURE( false, "ImageControl::ImplDraw: unhandled scale mode!" );
+ break;
+
+ } // switch ( mnScaleMode )
+}
+
+void ImageControl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
+{
+ ImplDraw(rRenderContext, Point(), GetOutputSizePixel());
+
+ if (!HasFocus())
+ return;
+
+ vcl::Window* pBorderWindow = GetWindow(GetWindowType::Border);
+
+ bool bFlat = (GetBorderStyle() == WindowBorderStyle::MONO);
+ tools::Rectangle aRect(Point(0,0), pBorderWindow->GetOutputSizePixel());
+ Color oldLineCol = pBorderWindow->GetOutDev()->GetLineColor();
+ Color oldFillCol = pBorderWindow->GetOutDev()->GetFillColor();
+ pBorderWindow->GetOutDev()->SetFillColor();
+ pBorderWindow->GetOutDev()->SetLineColor(bFlat ? COL_WHITE : COL_BLACK);
+ pBorderWindow->GetOutDev()->DrawRect(aRect);
+ aRect.AdjustLeft( 1 );
+ aRect.AdjustRight( -1 );
+ aRect.AdjustTop( 1 );
+ aRect.AdjustBottom( -1 );
+ pBorderWindow->GetOutDev()->SetLineColor(bFlat ? COL_BLACK : COL_WHITE);
+ pBorderWindow->GetOutDev()->DrawRect(aRect);
+ pBorderWindow->GetOutDev()->SetLineColor(oldLineCol);
+ pBorderWindow->GetOutDev()->SetFillColor(oldFillCol);
+
+}
+
+void ImageControl::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags )
+{
+ const Point aPos = pDev->LogicToPixel( rPos );
+ const Size aSize = GetSizePixel();
+ tools::Rectangle aRect( aPos, aSize );
+
+ pDev->Push();
+ pDev->SetMapMode();
+
+ // Border
+ if ( GetStyle() & WB_BORDER )
+ {
+ ImplDrawFrame( pDev, aRect );
+ }
+ pDev->IntersectClipRegion( aRect );
+ ImplDraw( *pDev, aRect.TopLeft(), aRect.GetSize() );
+
+ pDev->Pop();
+}
+
+void ImageControl::GetFocus()
+{
+ FixedImage::GetFocus();
+ GetWindow( GetWindowType::Border )->Invalidate();
+}
+
+void ImageControl::LoseFocus()
+{
+ FixedImage::GetFocus();
+ GetWindow( GetWindowType::Border )->Invalidate();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/imivctl.hxx b/vcl/source/control/imivctl.hxx
new file mode 100644
index 000000000..b242ef91d
--- /dev/null
+++ b/vcl/source/control/imivctl.hxx
@@ -0,0 +1,513 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SVTOOLS_SOURCE_CONTNR_IMIVCTL_HXX
+#define INCLUDED_SVTOOLS_SOURCE_CONTNR_IMIVCTL_HXX
+
+#include <sal/config.h>
+
+#include <o3tl/safeint.hxx>
+#include <vcl/toolkit/ivctrl.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/vclptr.hxx>
+#include <tools/debug.hxx>
+#include <vcl/svtaccessiblefactory.hxx>
+
+#include <limits.h>
+
+
+#include <memory>
+#include <map>
+
+class IcnCursor_Impl;
+class SvtIconChoiceCtrl;
+class SvxIconChoiceCtrlEntry;
+class IcnViewEdit_Impl;
+class IcnGridMap_Impl;
+
+
+// some defines
+
+#define PAINTFLAG_HOR_CENTERED 0x0001
+#define PAINTFLAG_VER_CENTERED 0x0002
+
+enum class IconChoiceFlags {
+ NONE = 0x0000,
+ AddMode = 0x0001,
+ SelectingRect = 0x0002,
+ DownCtrl = 0x0004,
+ DownDeselect = 0x0008,
+ EntryListPosValid = 0x0010,
+ ClearingSelection = 0x0020,
+ Arranging = 0x0040
+};
+namespace o3tl {
+ template<> struct typed_flags<IconChoiceFlags> : is_typed_flags<IconChoiceFlags, 0x007f> {};
+}
+
+// unit = pixels
+// distances from window borders
+#define LROFFS_WINBORDER 4
+#define TBOFFS_WINBORDER 4
+// for the bounding rectangle
+#define LROFFS_BOUND 2
+#define TBOFFS_BOUND 2
+// distance icon to text
+#define HOR_DIST_BMP_STRING 3
+#define VER_DIST_BMP_STRING 3
+// width offset of highlight rectangle for Text
+#define LROFFS_TEXT 2
+
+#define DEFAULT_MAX_VIRT_WIDTH 200
+#define DEFAULT_MAX_VIRT_HEIGHT 200
+
+#define VIEWMODE_MASK (WB_ICON | WB_SMALLICON | WB_DETAILS)
+
+
+enum class IcnViewFieldType
+{
+ Image,
+ Text
+};
+
+
+// Data about the focus of entries
+
+struct LocalFocus
+{
+ tools::Rectangle aRect;
+ Color aPenColor;
+};
+
+
+typedef sal_uLong GridId;
+
+#define GRID_NOT_FOUND (GridId(ULONG_MAX))
+
+// Implementation-class of IconChoiceCtrl
+
+
+typedef std::map<sal_uInt16, std::unique_ptr<SvxIconChoiceCtrlColumnInfo>> SvxIconChoiceCtrlColumnInfoMap;
+typedef std::vector<SvxIconChoiceCtrlEntry*> SvxIconChoiceCtrlEntryPtrVec;
+
+class SvxIconChoiceCtrl_Impl
+{
+ friend class IcnCursor_Impl;
+ friend class IcnGridMap_Impl;
+
+ std::vector< std::unique_ptr<SvxIconChoiceCtrlEntry> > maEntries;
+ VclPtr<ScrollBar> aVerSBar;
+ VclPtr<ScrollBar> aHorSBar;
+ VclPtr<ScrollBarBox> aScrBarBox;
+ tools::Rectangle aCurSelectionRect;
+ std::vector<tools::Rectangle> aSelectedRectList;
+ Idle aAutoArrangeIdle;
+ Idle aDocRectChangedIdle;
+ Idle aVisRectChangedIdle;
+ Idle aCallSelectHdlIdle;
+ Size aVirtOutputSize;
+ Size aImageSize;
+ Size aDefaultTextSize;
+ Size aOutputSize; // Pixel
+ VclPtr<SvtIconChoiceCtrl> pView;
+ std::unique_ptr<IcnCursor_Impl> pImpCursor;
+ std::unique_ptr<IcnGridMap_Impl> pGridMap;
+ tools::Long nMaxVirtWidth; // max. width aVirtOutputSize for ALIGN_TOP
+ tools::Long nMaxVirtHeight; // max. height aVirtOutputSize for ALIGN_LEFT
+ std::vector< SvxIconChoiceCtrlEntry* > maZOrderList;
+ std::unique_ptr<SvxIconChoiceCtrlColumnInfoMap> m_pColumns;
+ WinBits nWinBits;
+ tools::Long nMaxBoundHeight; // height of highest BoundRects
+ IconChoiceFlags nFlags;
+ DrawTextFlags nCurTextDrawFlags;
+ ImplSVEvent * nUserEventAdjustScrBars;
+ SvxIconChoiceCtrlEntry* pCurHighlightFrame;
+ bool bHighlightFramePressed;
+ SvxIconChoiceCtrlEntry* pHead = nullptr; // top left entry
+ SvxIconChoiceCtrlEntry* pCursor;
+ SvxIconChoiceCtrlEntry* pHdlEntry;
+ SvxIconChoiceCtrlEntry* pAnchor; // for selection
+ LocalFocus aFocus; // Data for focusrect
+ ::vcl::AccessibleFactoryAccess aAccFactory;
+
+ SvxIconChoiceCtrlTextMode eTextMode;
+ SelectionMode eSelectionMode;
+ sal_Int32 nSelectionCount;
+ SvxIconChoiceCtrlPositionMode ePositionMode;
+ bool bBoundRectsDirty;
+ bool bUpdateMode;
+
+ void ShowCursor( bool bShow );
+
+ void ImpArrange( bool bKeepPredecessors );
+ void AdjustVirtSize( const tools::Rectangle& );
+ void ResetVirtSize();
+ void CheckScrollBars();
+
+ DECL_LINK( ScrollUpDownHdl, ScrollBar*, void );
+ DECL_LINK( ScrollLeftRightHdl, ScrollBar*, void );
+ DECL_LINK( UserEventHdl, void*, void );
+ DECL_LINK( AutoArrangeHdl, Timer*, void );
+ DECL_LINK( DocRectChangedHdl, Timer*, void );
+ DECL_LINK( VisRectChangedHdl, Timer*, void );
+ DECL_LINK( CallSelectHdlHdl, Timer*, void );
+
+ void AdjustScrollBars();
+ void PositionScrollBars( tools::Long nRealWidth, tools::Long nRealHeight );
+ static tools::Long GetScrollBarPageSize( tools::Long nVisibleRange )
+ {
+ return ((nVisibleRange*75)/100);
+ }
+ tools::Long GetScrollBarLineSize() const
+ {
+ return nMaxBoundHeight / 2;
+ }
+ bool HandleScrollCommand( const CommandEvent& rCmd );
+ void ToDocPos( Point& rPosPixel )
+ {
+ rPosPixel -= pView->GetMapMode().GetOrigin();
+ }
+ void InitScrollBarBox();
+ void ToggleSelection( SvxIconChoiceCtrlEntry* );
+ void DeselectAllBut( SvxIconChoiceCtrlEntry const * );
+ void Center( SvxIconChoiceCtrlEntry* pEntry ) const;
+ void CallSelectHandler();
+ void SelectRect(
+ SvxIconChoiceCtrlEntry* pEntry1,
+ SvxIconChoiceCtrlEntry* pEntry2,
+ bool bAdd,
+ std::vector<tools::Rectangle>* pOtherRects
+ );
+
+ void SelectRange(
+ SvxIconChoiceCtrlEntry const * pStart,
+ SvxIconChoiceCtrlEntry const * pEnd,
+ bool bAdd
+ );
+
+ void AddSelectedRect( const tools::Rectangle& );
+ void AddSelectedRect(
+ SvxIconChoiceCtrlEntry* pEntry1,
+ SvxIconChoiceCtrlEntry* pEntry2
+ );
+
+ void ClearSelectedRectList();
+ tools::Rectangle CalcMaxTextRect( const SvxIconChoiceCtrlEntry* pEntry ) const;
+
+ void ClipAtVirtOutRect( tools::Rectangle& rRect ) const;
+ GridId GetPredecessorGrid( const Point& rDocPos) const;
+
+ void InitPredecessors();
+ void ClearPredecessors();
+
+ bool CheckVerScrollBar();
+ bool CheckHorScrollBar();
+ void CancelUserEvents();
+ void EntrySelected(
+ SvxIconChoiceCtrlEntry* pEntry,
+ bool bSelect
+ );
+ void RepaintSelectedEntries();
+ void SetListPositions();
+ void SetDefaultTextSize();
+ bool IsAutoArrange() const
+ {
+ return (ePositionMode == SvxIconChoiceCtrlPositionMode::AutoArrange);
+ }
+ void DocRectChanged() { aDocRectChangedIdle.Start(); }
+ void VisRectChanged() { aVisRectChangedIdle.Start(); }
+ void SetOrigin( const Point& );
+
+ void ShowFocus ( tools::Rectangle const & rRect );
+ void DrawFocusRect(vcl::RenderContext& rRenderContext);
+
+ bool IsMnemonicChar( sal_Unicode cChar, sal_uLong& rPos ) const;
+
+ // Copy assignment is forbidden and not implemented.
+ SvxIconChoiceCtrl_Impl (const SvxIconChoiceCtrl_Impl &) = delete;
+ SvxIconChoiceCtrl_Impl & operator= (const SvxIconChoiceCtrl_Impl &) = delete;
+
+public:
+
+ tools::Long nGridDX;
+ tools::Long nGridDY;
+ tools::Long nHorSBarHeight;
+ tools::Long nVerSBarWidth;
+
+ SvxIconChoiceCtrl_Impl( SvtIconChoiceCtrl* pView, WinBits nWinStyle );
+ ~SvxIconChoiceCtrl_Impl();
+
+ void SetSelectionMode(SelectionMode eMode)
+ {
+ eSelectionMode = eMode;
+ }
+
+ void Clear( bool bInCtor );
+ void SetStyle( WinBits nWinStyle );
+ WinBits GetStyle() const { return nWinBits; }
+ void InsertEntry( std::unique_ptr<SvxIconChoiceCtrlEntry>, size_t nPos );
+ void RemoveEntry( size_t nPos );
+ void FontModified();
+ void SelectAll();
+ void SelectEntry(
+ SvxIconChoiceCtrlEntry*,
+ bool bSelect,
+ bool bAddToSelection = false
+ );
+ void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect);
+ bool MouseButtonDown( const MouseEvent& );
+ bool MouseButtonUp( const MouseEvent& );
+ bool MouseMove( const MouseEvent&);
+ bool RequestHelp( const HelpEvent& rHEvt );
+ void SetCursor_Impl(
+ SvxIconChoiceCtrlEntry* pOldCursor,
+ SvxIconChoiceCtrlEntry* pNewCursor,
+ bool bMod1,
+ bool bShift
+ );
+ bool KeyInput( const KeyEvent& );
+ void Resize();
+ void GetFocus();
+ void LoseFocus();
+ void SetUpdateMode( bool bUpdate );
+ bool GetUpdateMode() const { return bUpdateMode; }
+ void PaintEntry(SvxIconChoiceCtrlEntry*, const Point&, vcl::RenderContext& rRenderContext);
+
+ void SetEntryPos(
+ SvxIconChoiceCtrlEntry* pEntry,
+ const Point& rPos
+ );
+
+ void InvalidateEntry( SvxIconChoiceCtrlEntry* );
+
+ void SetNoSelection();
+
+ SvxIconChoiceCtrlEntry* GetCurEntry() const { return pCursor; }
+ void SetCursor( SvxIconChoiceCtrlEntry* );
+
+ SvxIconChoiceCtrlEntry* GetEntry( const Point& rDocPos, bool bHit = false );
+
+ void MakeEntryVisible( SvxIconChoiceCtrlEntry* pEntry, bool bBound = true );
+
+ void Arrange(
+ bool bKeepPredecessors,
+ tools::Long nSetMaxVirtWidth,
+ tools::Long nSetMaxVirtHeight
+ );
+
+ tools::Rectangle CalcFocusRect( SvxIconChoiceCtrlEntry* );
+ tools::Rectangle CalcBmpRect( SvxIconChoiceCtrlEntry*, const Point* pPos = nullptr );
+ tools::Rectangle CalcTextRect(
+ SvxIconChoiceCtrlEntry*,
+ const Point* pPos = nullptr,
+ const OUString* pStr = nullptr
+ );
+
+ tools::Long CalcBoundingWidth() const;
+ tools::Long CalcBoundingHeight() const;
+ Size CalcBoundingSize() const;
+ void FindBoundingRect( SvxIconChoiceCtrlEntry* pEntry );
+ void SetBoundingRect_Impl(
+ SvxIconChoiceCtrlEntry* pEntry,
+ const Point& rPos,
+ const Size& rBoundingSize
+ );
+ // recalculates all invalid BoundRects
+ void RecalcAllBoundingRectsSmart();
+ const tools::Rectangle& GetEntryBoundRect( SvxIconChoiceCtrlEntry* );
+ void InvalidateBoundingRect( tools::Rectangle& rRect )
+ {
+ rRect.SetRight(LONG_MAX);
+ bBoundRectsDirty = true;
+ }
+ static bool IsBoundingRectValid( const tools::Rectangle& rRect ) { return ( rRect.Right() != LONG_MAX ); }
+
+ static void PaintEmphasis(const tools::Rectangle& rRect1, bool bSelected,
+ vcl::RenderContext& rRenderContext );
+
+ void PaintItem(const tools::Rectangle& rRect, IcnViewFieldType eItem, SvxIconChoiceCtrlEntry* pEntry,
+ sal_uInt16 nPaintFlags, vcl::RenderContext& rRenderContext);
+
+ // recalculates all BoundingRects if bMustRecalcBoundingRects == true
+ void CheckBoundingRects() { if (bBoundRectsDirty) RecalcAllBoundingRectsSmart(); }
+ void Command( const CommandEvent& rCEvt );
+ void ToTop( SvxIconChoiceCtrlEntry* );
+
+ sal_Int32 GetSelectionCount() const;
+ void SetGrid( const Size& );
+ Size GetMinGrid() const;
+ void Scroll( tools::Long nDeltaX, tools::Long nDeltaY );
+ const Size& GetItemSize( IcnViewFieldType ) const;
+
+ void HideDDIcon();
+
+ static bool IsOver(
+ std::vector<tools::Rectangle>* pSelectedRectList,
+ const tools::Rectangle& rEntryBoundRect
+ );
+
+ void SelectRect(
+ const tools::Rectangle&,
+ bool bAdd,
+ std::vector<tools::Rectangle>* pOtherRects
+ );
+
+ void MakeVisible(
+ const tools::Rectangle& rDocPos,
+ bool bInScrollBarEvent=false
+ );
+
+#ifdef DBG_UTIL
+ void SetEntryTextMode(
+ SvxIconChoiceCtrlTextMode,
+ SvxIconChoiceCtrlEntry* pEntry
+ );
+#endif
+ size_t GetEntryCount() const { return maEntries.size(); }
+ SvxIconChoiceCtrlEntry* GetEntry( size_t nPos )
+ {
+ return maEntries[ nPos ].get();
+ }
+ SvxIconChoiceCtrlEntry* GetEntry( size_t nPos ) const
+ {
+ return maEntries[ nPos ].get();
+ }
+ SvxIconChoiceCtrlEntry* GetFirstSelectedEntry() const;
+ sal_Int32 GetEntryListPos( SvxIconChoiceCtrlEntry const * ) const;
+ void InitSettings();
+ tools::Rectangle GetOutputRect() const;
+
+ void SetEntryPredecessor(SvxIconChoiceCtrlEntry* pEntry,SvxIconChoiceCtrlEntry* pPredecessor);
+ // only delivers valid results when in AutoArrange mode!
+ SvxIconChoiceCtrlEntry* FindEntryPredecessor( SvxIconChoiceCtrlEntry* pEntry, const Point& );
+
+ void SetPositionMode( SvxIconChoiceCtrlPositionMode );
+
+ void SetColumn( sal_uInt16 nIndex, const SvxIconChoiceCtrlColumnInfo& );
+ const SvxIconChoiceCtrlColumnInfo* GetColumn( sal_uInt16 nIndex ) const;
+
+ void SetEntryHighlightFrame(
+ SvxIconChoiceCtrlEntry* pEntry,
+ bool bKeepHighlightFlags
+ );
+ void DrawHighlightFrame(vcl::RenderContext& rRenderContext, const tools::Rectangle& rBmpRect);
+
+ void CallEventListeners( VclEventId nEvent, void* pData );
+
+ ::vcl::IAccessibleFactory& GetAccessibleFactory()
+ {
+ return aAccFactory.getFactory();
+ }
+};
+
+typedef std::map<sal_uInt16, SvxIconChoiceCtrlEntryPtrVec> IconChoiceMap;
+
+class IcnCursor_Impl
+{
+ SvxIconChoiceCtrl_Impl* pView;
+ std::unique_ptr<IconChoiceMap> xColumns;
+ std::unique_ptr<IconChoiceMap> xRows;
+ tools::Long nCols;
+ tools::Long nRows;
+ short nDeltaWidth;
+ short nDeltaHeight;
+ SvxIconChoiceCtrlEntry* pCurEntry;
+ void SetDeltas();
+ void ImplCreate();
+ void Create() { if( !xColumns ) ImplCreate(); }
+
+ sal_uInt16 GetSortListPos(
+ SvxIconChoiceCtrlEntryPtrVec& rList,
+ tools::Long nValue,
+ bool bVertical);
+ SvxIconChoiceCtrlEntry* SearchCol(
+ sal_uInt16 nCol,
+ sal_uInt16 nTop,
+ sal_uInt16 nBottom,
+ bool bDown,
+ bool bSimple
+ );
+
+ SvxIconChoiceCtrlEntry* SearchRow(
+ sal_uInt16 nRow,
+ sal_uInt16 nLeft,
+ sal_uInt16 nRight,
+ bool bRight,
+ bool bSimple
+ );
+
+public:
+ explicit IcnCursor_Impl( SvxIconChoiceCtrl_Impl* pOwner );
+ ~IcnCursor_Impl();
+ void Clear();
+
+ // for Cursortravelling etc.
+ SvxIconChoiceCtrlEntry* GoLeftRight( SvxIconChoiceCtrlEntry*, bool bRight );
+ SvxIconChoiceCtrlEntry* GoUpDown( SvxIconChoiceCtrlEntry*, bool bDown );
+ SvxIconChoiceCtrlEntry* GoPageUpDown( SvxIconChoiceCtrlEntry*, bool bDown );
+};
+
+class IcnGridMap_Impl
+{
+ tools::Rectangle _aLastOccupiedGrid;
+ SvxIconChoiceCtrl_Impl* _pView;
+ std::unique_ptr<bool[]> _pGridMap;
+ sal_uInt16 _nGridCols, _nGridRows;
+
+ void Expand();
+ void Create_Impl();
+ void Create() { if(!_pGridMap) Create_Impl(); }
+
+ void GetMinMapSize( sal_uInt16& rDX, sal_uInt16& rDY ) const;
+
+public:
+ explicit IcnGridMap_Impl(SvxIconChoiceCtrl_Impl* pView);
+ ~IcnGridMap_Impl();
+
+ void Clear();
+
+ GridId GetGrid( const Point& rDocPos );
+ GridId GetGrid( sal_uInt16 nGridX, sal_uInt16 nGridY );
+ GridId GetUnoccupiedGrid();
+
+ void OccupyGrids( const SvxIconChoiceCtrlEntry* );
+ void OccupyGrid( GridId nId )
+ {
+ DBG_ASSERT(!_pGridMap || nId<o3tl::make_unsigned(_nGridCols*_nGridRows),"OccupyGrid: Bad GridId");
+ if(_pGridMap && nId < o3tl::make_unsigned(_nGridCols *_nGridRows) )
+ _pGridMap[ nId ] = true;
+ }
+
+ tools::Rectangle GetGridRect( GridId );
+ void GetGridCoord( GridId, sal_uInt16& rGridX, sal_uInt16& rGridY );
+ static sal_uLong GetGridCount(
+ const Size& rSizePixel,
+ sal_uInt16 nGridWidth,
+ sal_uInt16 nGridHeight
+ );
+
+ void OutputSizeChanged();
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/imivctl1.cxx b/vcl/source/control/imivctl1.cxx
new file mode 100644
index 000000000..953f90f2a
--- /dev/null
+++ b/vcl/source/control/imivctl1.cxx
@@ -0,0 +1,2927 @@
+/* -*- 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 <limits.h>
+#include <osl/diagnose.h>
+#include <tools/debug.hxx>
+#include <vcl/wall.hxx>
+#include <vcl/help.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <tools/poly.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/i18nhelp.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/commandevent.hxx>
+
+#include <vcl/toolkit/ivctrl.hxx>
+#include "imivctl.hxx"
+
+#include <algorithm>
+#include <memory>
+#include <vcl/idle.hxx>
+
+constexpr auto DRAWTEXT_FLAGS_ICON =
+ DrawTextFlags::Center | DrawTextFlags::Top | DrawTextFlags::EndEllipsis |
+ DrawTextFlags::Clip | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak | DrawTextFlags::Mnemonic;
+
+#define DRAWTEXT_FLAGS_SMALLICON (DrawTextFlags::Left|DrawTextFlags::EndEllipsis|DrawTextFlags::Clip)
+
+#define EVENTID_SHOW_CURSOR (reinterpret_cast<void*>(1))
+#define EVENTID_ADJUST_SCROLLBARS (reinterpret_cast<void*>(2))
+
+SvxIconChoiceCtrl_Impl::SvxIconChoiceCtrl_Impl(
+ SvtIconChoiceCtrl* pCurView,
+ WinBits nWinStyle
+) :
+ aVerSBar( VclPtr<ScrollBar>::Create(pCurView, WB_DRAG | WB_VSCROLL) ),
+ aHorSBar( VclPtr<ScrollBar>::Create(pCurView, WB_DRAG | WB_HSCROLL) ),
+ aScrBarBox( VclPtr<ScrollBarBox>::Create(pCurView) ),
+ aAutoArrangeIdle( "svtools::SvxIconChoiceCtrl_Impl aAutoArrangeIdle" ),
+ aDocRectChangedIdle( "svtools::SvxIconChoiceCtrl_Impl aDocRectChangedIdle" ),
+ aVisRectChangedIdle( "svtools::SvxIconChoiceCtrl_Impl aVisRectChangedIdle" ),
+ aCallSelectHdlIdle( "svtools::SvxIconChoiceCtrl_Impl aCallSelectHdlIdle" ),
+ aImageSize( 32 * pCurView->GetDPIScaleFactor(), 32 * pCurView->GetDPIScaleFactor()),
+ pView(pCurView), nMaxVirtWidth(DEFAULT_MAX_VIRT_WIDTH), nMaxVirtHeight(DEFAULT_MAX_VIRT_HEIGHT),
+ nFlags(IconChoiceFlags::NONE), nUserEventAdjustScrBars(nullptr),
+ pCurHighlightFrame(nullptr), bHighlightFramePressed(false), pHead(nullptr), pCursor(nullptr),
+ pHdlEntry(nullptr),
+ pAnchor(nullptr), eTextMode(SvxIconChoiceCtrlTextMode::Short),
+ eSelectionMode(SelectionMode::Multiple), ePositionMode(SvxIconChoiceCtrlPositionMode::Free),
+ bUpdateMode(true)
+{
+ SetStyle( nWinStyle );
+ pImpCursor.reset( new IcnCursor_Impl( this ) );
+ pGridMap.reset( new IcnGridMap_Impl( this ) );
+
+ aVerSBar->SetScrollHdl( LINK( this, SvxIconChoiceCtrl_Impl, ScrollUpDownHdl ) );
+ aHorSBar->SetScrollHdl( LINK( this, SvxIconChoiceCtrl_Impl, ScrollLeftRightHdl ) );
+
+ nHorSBarHeight = aHorSBar->GetSizePixel().Height();
+ nVerSBarWidth = aVerSBar->GetSizePixel().Width();
+
+ aAutoArrangeIdle.SetPriority( TaskPriority::HIGH_IDLE );
+ aAutoArrangeIdle.SetInvokeHandler(LINK(this,SvxIconChoiceCtrl_Impl,AutoArrangeHdl));
+
+ aCallSelectHdlIdle.SetPriority( TaskPriority::LOWEST );
+ aCallSelectHdlIdle.SetInvokeHandler( LINK(this,SvxIconChoiceCtrl_Impl,CallSelectHdlHdl));
+
+ aDocRectChangedIdle.SetPriority( TaskPriority::HIGH_IDLE );
+ aDocRectChangedIdle.SetInvokeHandler(LINK(this,SvxIconChoiceCtrl_Impl,DocRectChangedHdl));
+
+ aVisRectChangedIdle.SetPriority( TaskPriority::HIGH_IDLE );
+ aVisRectChangedIdle.SetInvokeHandler(LINK(this,SvxIconChoiceCtrl_Impl,VisRectChangedHdl));
+
+ Clear( true );
+ Size gridSize(100,70);
+ if(pView->GetDPIScaleFactor() > 1)
+ {
+ gridSize.setHeight( gridSize.Height() * ( pView->GetDPIScaleFactor()) );
+ }
+ SetGrid(gridSize);
+}
+
+SvxIconChoiceCtrl_Impl::~SvxIconChoiceCtrl_Impl()
+{
+ Clear(false);
+ CancelUserEvents();
+ pImpCursor.reset();
+ pGridMap.reset();
+ ClearSelectedRectList();
+ m_pColumns.reset();
+ aVerSBar.disposeAndClear();
+ aHorSBar.disposeAndClear();
+ aScrBarBox.disposeAndClear();
+}
+
+void SvxIconChoiceCtrl_Impl::Clear( bool bInCtor )
+{
+ nSelectionCount = 0;
+ pCurHighlightFrame = nullptr;
+ CancelUserEvents();
+ ShowCursor( false );
+ bBoundRectsDirty = false;
+ nMaxBoundHeight = 0;
+
+ pCursor = nullptr;
+ if( !bInCtor )
+ {
+ pImpCursor->Clear();
+ pGridMap->Clear();
+ aVirtOutputSize.setWidth( 0 );
+ aVirtOutputSize.setHeight( 0 );
+ Size aSize( pView->GetOutputSizePixel() );
+ nMaxVirtWidth = aSize.Width() - nVerSBarWidth;
+ if( nMaxVirtWidth <= 0 )
+ nMaxVirtWidth = DEFAULT_MAX_VIRT_WIDTH;
+ nMaxVirtHeight = aSize.Height() - nHorSBarHeight;
+ if( nMaxVirtHeight <= 0 )
+ nMaxVirtHeight = DEFAULT_MAX_VIRT_HEIGHT;
+ maZOrderList.clear();
+ SetOrigin( Point() );
+ if( bUpdateMode )
+ pView->Invalidate(InvalidateFlags::NoChildren);
+ }
+ AdjustScrollBars();
+ maEntries.clear();
+ DocRectChanged();
+ VisRectChanged();
+}
+
+void SvxIconChoiceCtrl_Impl::SetStyle( WinBits nWinStyle )
+{
+ nWinBits = nWinStyle;
+ nCurTextDrawFlags = DRAWTEXT_FLAGS_ICON;
+ if( nWinBits & (WB_SMALLICON | WB_DETAILS) )
+ nCurTextDrawFlags = DRAWTEXT_FLAGS_SMALLICON;
+ if( nWinBits & WB_NOSELECTION )
+ eSelectionMode = SelectionMode::NONE;
+ if( !(nWinStyle & (WB_ALIGN_TOP | WB_ALIGN_LEFT)))
+ nWinBits |= WB_ALIGN_LEFT;
+ if( nWinStyle & WB_DETAILS )
+ {
+ if (!m_pColumns)
+ SetColumn( 0, SvxIconChoiceCtrlColumnInfo() );
+ }
+}
+
+IMPL_LINK( SvxIconChoiceCtrl_Impl, ScrollUpDownHdl, ScrollBar*, pScrollBar, void )
+{
+ // arrow up: delta=-1; arrow down: delta=+1
+ Scroll( 0, pScrollBar->GetDelta() );
+}
+
+IMPL_LINK( SvxIconChoiceCtrl_Impl, ScrollLeftRightHdl, ScrollBar*, pScrollBar, void )
+{
+ // arrow left: delta=-1; arrow right: delta=+1
+ Scroll( pScrollBar->GetDelta(), 0 );
+}
+
+void SvxIconChoiceCtrl_Impl::FontModified()
+{
+ SetDefaultTextSize();
+ ShowCursor( false );
+ ShowCursor( true );
+}
+
+void SvxIconChoiceCtrl_Impl::InsertEntry( std::unique_ptr<SvxIconChoiceCtrlEntry> pEntry1, size_t nPos)
+{
+ auto pEntry = pEntry1.get();
+
+ if ( nPos < maEntries.size() ) {
+ maEntries.insert( maEntries.begin() + nPos, std::move(pEntry1) );
+ } else {
+ maEntries.push_back( std::move(pEntry1) );
+ }
+
+ if( pHead )
+ pEntry->SetBacklink( pHead->pblink );
+
+ if( (nFlags & IconChoiceFlags::EntryListPosValid) && nPos >= maEntries.size() - 1 )
+ pEntry->nPos = maEntries.size() - 1;
+ else
+ nFlags &= ~IconChoiceFlags::EntryListPosValid;
+
+ maZOrderList.push_back( pEntry );
+ pImpCursor->Clear();
+
+ // If the UpdateMode is true, don't set all bounding rectangles to
+ // 'to be checked', but only the bounding rectangle of the new entry.
+ // Thus, don't call InvalidateBoundingRect!
+ pEntry->aRect.SetRight( LONG_MAX );
+ if( bUpdateMode )
+ {
+ FindBoundingRect( pEntry );
+ tools::Rectangle aOutputArea( GetOutputRect() );
+ pGridMap->OccupyGrids( pEntry );
+ if( !aOutputArea.Overlaps( pEntry->aRect ) )
+ return; // is invisible
+ pView->Invalidate( pEntry->aRect );
+ }
+ else
+ InvalidateBoundingRect( pEntry->aRect );
+}
+
+void SvxIconChoiceCtrl_Impl::RemoveEntry(size_t nPos)
+{
+ pImpCursor->Clear();
+ maEntries.erase(maEntries.begin() + nPos);
+ RecalcAllBoundingRectsSmart();
+}
+
+tools::Rectangle SvxIconChoiceCtrl_Impl::GetOutputRect() const
+{
+ Point aOrigin( pView->GetMapMode().GetOrigin() );
+ aOrigin *= -1;
+ return tools::Rectangle( aOrigin, aOutputSize );
+}
+
+void SvxIconChoiceCtrl_Impl::SetListPositions()
+{
+ if( nFlags & IconChoiceFlags::EntryListPosValid )
+ return;
+
+ size_t nCount = maEntries.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ maEntries[ nCur ]->nPos = nCur;
+ }
+ nFlags |= IconChoiceFlags::EntryListPosValid;
+}
+
+void SvxIconChoiceCtrl_Impl::SelectEntry( SvxIconChoiceCtrlEntry* pEntry, bool bSelect,
+ bool bAdd )
+{
+ if( eSelectionMode == SelectionMode::NONE )
+ return;
+
+ if( !bAdd )
+ {
+ if ( !( nFlags & IconChoiceFlags::ClearingSelection ) )
+ {
+ nFlags |= IconChoiceFlags::ClearingSelection;
+ DeselectAllBut( pEntry );
+ nFlags &= ~IconChoiceFlags::ClearingSelection;
+ }
+ }
+ if( pEntry->IsSelected() == bSelect )
+ return;
+
+ pHdlEntry = pEntry;
+ SvxIconViewFlags nEntryFlags = pEntry->GetFlags();
+ if( bSelect )
+ {
+ nEntryFlags |= SvxIconViewFlags::SELECTED;
+ pEntry->AssignFlags( nEntryFlags );
+ nSelectionCount++;
+ CallSelectHandler();
+ }
+ else
+ {
+ nEntryFlags &= ~SvxIconViewFlags::SELECTED;
+ pEntry->AssignFlags( nEntryFlags );
+ nSelectionCount--;
+ CallSelectHandler();
+ }
+ EntrySelected( pEntry, bSelect );
+}
+
+void SvxIconChoiceCtrl_Impl::EntrySelected(SvxIconChoiceCtrlEntry* pEntry, bool bSelect)
+{
+ // When using SingleSelection, make sure that the cursor is always placed
+ // over the (only) selected entry. (But only if a cursor exists.)
+ if (bSelect && pCursor &&
+ eSelectionMode == SelectionMode::Single &&
+ pEntry != pCursor)
+ {
+ SetCursor(pEntry);
+ }
+
+ // Not when dragging though, else the loop in SelectRect doesn't work
+ // correctly!
+ if (!(nFlags & IconChoiceFlags::SelectingRect))
+ ToTop(pEntry);
+ if (bUpdateMode)
+ {
+ if (pEntry == pCursor)
+ ShowCursor(false);
+ pView->Invalidate(CalcFocusRect(pEntry));
+ if (pEntry == pCursor)
+ ShowCursor(true);
+ }
+
+ // #i101012# emit vcl event LISTBOX_SELECT only in case that the given entry is selected.
+ if (bSelect)
+ {
+ CallEventListeners(VclEventId::ListboxSelect, pEntry);
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::ResetVirtSize()
+{
+ aVirtOutputSize.setWidth( 0 );
+ aVirtOutputSize.setHeight( 0 );
+ const size_t nCount = maEntries.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pCur = maEntries[ nCur ].get();
+ pCur->ClearFlags( SvxIconViewFlags::POS_MOVED );
+ if( pCur->IsPosLocked() )
+ {
+ // adapt (among others) VirtSize
+ if( !IsBoundingRectValid( pCur->aRect ) )
+ FindBoundingRect( pCur );
+ else
+ AdjustVirtSize( pCur->aRect );
+ }
+ else
+ InvalidateBoundingRect( pCur->aRect );
+ }
+
+ if( !(nWinBits & (WB_NOVSCROLL | WB_NOHSCROLL)) )
+ {
+ Size aRealOutputSize( pView->GetOutputSizePixel() );
+ if( aVirtOutputSize.Width() < aRealOutputSize.Width() ||
+ aVirtOutputSize.Height() < aRealOutputSize.Height() )
+ {
+ sal_uLong nGridCount = IcnGridMap_Impl::GetGridCount(
+ aRealOutputSize, static_cast<sal_uInt16>(nGridDX), static_cast<sal_uInt16>(nGridDY) );
+ if( nGridCount < nCount )
+ {
+ if( nWinBits & WB_ALIGN_TOP )
+ nMaxVirtWidth = aRealOutputSize.Width() - nVerSBarWidth;
+ else // WB_ALIGN_LEFT
+ nMaxVirtHeight = aRealOutputSize.Height() - nHorSBarHeight;
+ }
+ }
+ }
+
+ pImpCursor->Clear();
+ pGridMap->Clear();
+ VisRectChanged();
+}
+
+void SvxIconChoiceCtrl_Impl::AdjustVirtSize( const tools::Rectangle& rRect )
+{
+ tools::Long nHeightOffs = 0;
+ tools::Long nWidthOffs = 0;
+
+ if( aVirtOutputSize.Width() < (rRect.Right()+LROFFS_WINBORDER) )
+ nWidthOffs = (rRect.Right()+LROFFS_WINBORDER) - aVirtOutputSize.Width();
+
+ if( aVirtOutputSize.Height() < (rRect.Bottom()+TBOFFS_WINBORDER) )
+ nHeightOffs = (rRect.Bottom()+TBOFFS_WINBORDER) - aVirtOutputSize.Height();
+
+ if( !(nWidthOffs || nHeightOffs) )
+ return;
+
+ Range aRange;
+ aVirtOutputSize.AdjustWidth(nWidthOffs );
+ aRange.Max() = aVirtOutputSize.Width();
+ aHorSBar->SetRange( aRange );
+
+ aVirtOutputSize.AdjustHeight(nHeightOffs );
+ aRange.Max() = aVirtOutputSize.Height();
+ aVerSBar->SetRange( aRange );
+
+ pImpCursor->Clear();
+ pGridMap->OutputSizeChanged();
+ AdjustScrollBars();
+ DocRectChanged();
+}
+
+void SvxIconChoiceCtrl_Impl::InitPredecessors()
+{
+ DBG_ASSERT(!pHead,"SvxIconChoiceCtrl_Impl::InitPredecessors() >> Already initialized");
+ size_t nCount = maEntries.size();
+ if( nCount )
+ {
+ SvxIconChoiceCtrlEntry* pPrev = maEntries[ 0 ].get();
+ for( size_t nCur = 1; nCur <= nCount; nCur++ )
+ {
+ pPrev->ClearFlags( SvxIconViewFlags::POS_LOCKED | SvxIconViewFlags::POS_MOVED );
+
+ SvxIconChoiceCtrlEntry* pNext;
+ if( nCur == nCount )
+ pNext = maEntries[ 0 ].get();
+ else
+ pNext = maEntries[ nCur ].get();
+ pPrev->pflink = pNext;
+ pNext->pblink = pPrev;
+ pPrev = pNext;
+ }
+ pHead = maEntries[ 0 ].get();
+ }
+ else
+ pHead = nullptr;
+}
+
+void SvxIconChoiceCtrl_Impl::ClearPredecessors()
+{
+ if( pHead )
+ {
+ size_t nCount = maEntries.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pCur = maEntries[ nCur ].get();
+ pCur->pflink = nullptr;
+ pCur->pblink = nullptr;
+ }
+ pHead = nullptr;
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::Arrange( bool bKeepPredecessors, tools::Long nSetMaxVirtWidth, tools::Long nSetMaxVirtHeight )
+{
+ if ( nSetMaxVirtWidth != 0 )
+ nMaxVirtWidth = nSetMaxVirtWidth;
+ else
+ nMaxVirtWidth = aOutputSize.Width();
+
+ if ( nSetMaxVirtHeight != 0 )
+ nMaxVirtHeight = nSetMaxVirtHeight;
+ else
+ nMaxVirtHeight = aOutputSize.Height();
+
+ ImpArrange( bKeepPredecessors );
+}
+
+void SvxIconChoiceCtrl_Impl::ImpArrange( bool bKeepPredecessors )
+{
+ static const Point aEmptyPoint;
+
+ bool bOldUpdate = bUpdateMode;
+ tools::Rectangle aCurOutputArea( GetOutputRect() );
+ if( (nWinBits & WB_SMART_ARRANGE) && aCurOutputArea.TopLeft() != aEmptyPoint )
+ bUpdateMode = false;
+ aAutoArrangeIdle.Stop();
+ nFlags |= IconChoiceFlags::Arranging;
+ ShowCursor( false );
+ ResetVirtSize();
+ if( !bKeepPredecessors )
+ ClearPredecessors();
+ bBoundRectsDirty = false;
+ SetOrigin( Point() );
+ VisRectChanged();
+ RecalcAllBoundingRectsSmart();
+ // TODO: the invalidation in the detail view should be more intelligent
+ //if( !(nWinBits & WB_DETAILS ))
+ pView->Invalidate( InvalidateFlags::NoChildren );
+ nFlags &= ~IconChoiceFlags::Arranging;
+ if( (nWinBits & WB_SMART_ARRANGE) && aCurOutputArea.TopLeft() != aEmptyPoint )
+ {
+ MakeVisible( aCurOutputArea );
+ SetUpdateMode( bOldUpdate );
+ }
+ ShowCursor( true );
+}
+
+void SvxIconChoiceCtrl_Impl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+#if defined(OV_DRAWGRID)
+ Color aOldColor (rRenderContext.GetLineColor());
+ Color aCOL_BLACK);
+ rRenderContext.SetLineColor( aColor );
+ Point aOffs(rRenderContext.GetMapMode().GetOrigin());
+ Size aXSize(GetOutputSizePixel());
+ {
+ Point aStart(LROFFS_WINBORDER, 0);
+ Point aEnd(LROFFS_WINBORDER, aXSize.Height());
+ aStart -= aOffs;
+ aEnd -= aOffs;
+ rRenderContext.DrawLine(aStart, aEnd);
+ }
+ {
+ Point aStart(0, TBOFFS_WINBORDER);
+ Point aEnd(aXSize.Width(), TBOFFS_WINBORDER);
+ aStart -= aOffs;
+ aEnd -= aOffs;
+ rRenderContext.DrawLine(aStart, aEnd);
+ }
+
+ for (tools::Long nDX = nGridDX; nDX <= aXSize.Width(); nDX += nGridDX)
+ {
+ Point aStart( nDX+LROFFS_WINBORDER, 0 );
+ Point aEnd( nDX+LROFFS_WINBORDER, aXSize.Height());
+ aStart -= aOffs;
+ aEnd -= aOffs;
+ rRenderContext.DrawLine(aStart, aEnd);
+ }
+ for (tools::Long nDY = nGridDY; nDY <= aXSize.Height(); nDY += nGridDY)
+ {
+ Point aStart(0, nDY + TBOFFS_WINBORDER);
+ Point aEnd(aXSize.Width(), nDY + TBOFFS_WINBORDER);
+ aStart -= aOffs;
+ aEnd -= aOffs;
+ rRenderContext.DrawLine(aStart, aEnd);
+ }
+ rRenderContext.SetLineColor(aOldColor);
+#endif
+
+ if (!maEntries.size())
+ return;
+ if (!pCursor)
+ {
+ // set cursor to item with focus-flag
+ bool bfound = false;
+ for (sal_Int32 i = 0; i < pView->GetEntryCount() && !bfound; i++)
+ {
+ SvxIconChoiceCtrlEntry* pEntry = pView->GetEntry(i);
+ if (pEntry->IsFocused())
+ {
+ pCursor = pEntry;
+ bfound = true;
+ }
+ }
+
+ if (!bfound)
+ pCursor = maEntries[ 0 ].get();
+ }
+
+ size_t nCount = maZOrderList.size();
+ if (!nCount)
+ return;
+
+ rRenderContext.Push(vcl::PushFlags::CLIPREGION);
+ rRenderContext.SetClipRegion(vcl::Region(rRect));
+
+ std::vector< SvxIconChoiceCtrlEntry* > aNewZOrderList;
+ std::vector< SvxIconChoiceCtrlEntry* > aPaintedEntries;
+
+ size_t nPos = 0;
+ while(nCount)
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maZOrderList[nPos];
+ const tools::Rectangle& rBoundRect = GetEntryBoundRect(pEntry);
+ if (rRect.Overlaps(rBoundRect))
+ {
+ PaintEntry(pEntry, rBoundRect.TopLeft(), rRenderContext);
+ // set entries to Top if they are being repainted
+ aPaintedEntries.push_back(pEntry);
+ }
+ else
+ aNewZOrderList.push_back(pEntry);
+
+ nCount--;
+ nPos++;
+ }
+ maZOrderList = std::move( aNewZOrderList );
+ maZOrderList.insert(maZOrderList.end(), aPaintedEntries.begin(), aPaintedEntries.end());
+
+ rRenderContext.Pop();
+}
+
+void SvxIconChoiceCtrl_Impl::RepaintSelectedEntries()
+{
+ const size_t nCount = maZOrderList.size();
+ if (!nCount)
+ return;
+
+ tools::Rectangle aOutRect(GetOutputRect());
+ for (size_t nCur = 0; nCur < nCount; nCur++)
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maZOrderList[nCur];
+ if (pEntry->GetFlags() & SvxIconViewFlags::SELECTED)
+ {
+ const tools::Rectangle& rBoundRect = GetEntryBoundRect(pEntry);
+ if (aOutRect.Overlaps(rBoundRect))
+ pView->Invalidate(rBoundRect);
+ }
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::InitScrollBarBox()
+{
+ aScrBarBox->SetSizePixel( Size(nVerSBarWidth-1, nHorSBarHeight-1) );
+ Size aSize( pView->GetOutputSizePixel() );
+ aScrBarBox->SetPosPixel( Point(aSize.Width()-nVerSBarWidth+1, aSize.Height()-nHorSBarHeight+1));
+}
+
+bool SvxIconChoiceCtrl_Impl::MouseButtonDown( const MouseEvent& rMEvt)
+{
+ bool bHandled = true;
+ bHighlightFramePressed = false;
+ bool bGotFocus = (!pView->HasFocus() && !(nWinBits & WB_NOPOINTERFOCUS));
+ if( !(nWinBits & WB_NOPOINTERFOCUS) )
+ pView->GrabFocus();
+
+ Point aDocPos( rMEvt.GetPosPixel() );
+ if(aDocPos.X()>=aOutputSize.Width() || aDocPos.Y()>=aOutputSize.Height())
+ return false;
+ ToDocPos( aDocPos );
+ SvxIconChoiceCtrlEntry* pEntry = GetEntry( aDocPos, true );
+ if( pEntry )
+ MakeEntryVisible( pEntry, false );
+
+ if( rMEvt.IsShift() && eSelectionMode != SelectionMode::Single )
+ {
+ if( pEntry )
+ SetCursor_Impl( pCursor, pEntry, rMEvt.IsMod1(), rMEvt.IsShift() );
+ return true;
+ }
+
+ if( pAnchor && (rMEvt.IsShift() || rMEvt.IsMod1())) // keyboard selection?
+ {
+ DBG_ASSERT(eSelectionMode != SelectionMode::Single,"Invalid selection mode");
+ if( rMEvt.IsMod1() )
+ nFlags |= IconChoiceFlags::AddMode;
+
+ if( rMEvt.IsShift() )
+ {
+ tools::Rectangle aRect( GetEntryBoundRect( pAnchor ));
+ if( pEntry )
+ aRect.Union( GetEntryBoundRect( pEntry ) );
+ else
+ {
+ tools::Rectangle aTempRect( aDocPos, Size(1,1));
+ aRect.Union( aTempRect );
+ }
+ aCurSelectionRect = aRect;
+ SelectRect( aRect, bool(nFlags & IconChoiceFlags::AddMode), &aSelectedRectList );
+ }
+ else if( rMEvt.IsMod1() )
+ {
+ AddSelectedRect( aCurSelectionRect );
+ pAnchor = nullptr;
+ aCurSelectionRect.SetPos( aDocPos );
+ }
+
+ if( !pEntry && !(nWinBits & WB_NODRAGSELECTION))
+ pView->StartTracking( StartTrackingFlags::ScrollRepeat );
+ return true;
+ }
+ else
+ {
+ if( !pEntry )
+ {
+ if( eSelectionMode == SelectionMode::Multiple )
+ {
+ if( !rMEvt.IsMod1() ) // Ctrl
+ {
+ if( !bGotFocus )
+ {
+ SetNoSelection();
+ ClearSelectedRectList();
+ }
+ }
+ else
+ nFlags |= IconChoiceFlags::AddMode;
+ aCurSelectionRect.SetPos( aDocPos );
+ pView->StartTracking( StartTrackingFlags::ScrollRepeat );
+ }
+ else
+ bHandled = false;
+ return bHandled;
+ }
+ }
+ bool bSelected = pEntry->IsSelected();
+
+ if( rMEvt.GetClicks() == 2 )
+ {
+ DeselectAllBut( pEntry );
+ SelectEntry( pEntry, true, false );
+ pHdlEntry = pEntry;
+ pView->ClickIcon();
+ }
+ else
+ {
+ // Inplace-Editing ?
+ if( rMEvt.IsMod2() ) // Alt?
+ {
+ }
+ else if( eSelectionMode == SelectionMode::Single )
+ {
+ DeselectAllBut( pEntry );
+ SetCursor( pEntry );
+ }
+ else if( eSelectionMode == SelectionMode::NONE )
+ {
+ if( rMEvt.IsLeft() && (nWinBits & WB_HIGHLIGHTFRAME) )
+ {
+ pCurHighlightFrame = nullptr; // force repaint of frame
+ bHighlightFramePressed = true;
+ SetEntryHighlightFrame( pEntry, true );
+ }
+ }
+ else
+ {
+ if( !rMEvt.GetModifier() && rMEvt.IsLeft() )
+ {
+ if( !bSelected )
+ {
+ DeselectAllBut( pEntry );
+ SetCursor( pEntry );
+ SelectEntry( pEntry, true, false );
+ }
+ else
+ {
+ // deselect only in the Up, if the Move happened via D&D!
+ nFlags |= IconChoiceFlags::DownDeselect;
+ }
+ }
+ else if( rMEvt.IsMod1() )
+ nFlags |= IconChoiceFlags::DownCtrl;
+ }
+ }
+ return bHandled;
+}
+
+bool SvxIconChoiceCtrl_Impl::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ bool bHandled = false;
+ if( rMEvt.IsRight() && (nFlags & (IconChoiceFlags::DownCtrl | IconChoiceFlags::DownDeselect) ))
+ {
+ nFlags &= ~IconChoiceFlags(IconChoiceFlags::DownCtrl | IconChoiceFlags::DownDeselect);
+ bHandled = true;
+ }
+
+ Point aDocPos( rMEvt.GetPosPixel() );
+ ToDocPos( aDocPos );
+ SvxIconChoiceCtrlEntry* pDocEntry = GetEntry( aDocPos );
+ if( pDocEntry )
+ {
+ if( nFlags & IconChoiceFlags::DownCtrl )
+ {
+ // Ctrl & MultiSelection
+ ToggleSelection( pDocEntry );
+ SetCursor( pDocEntry );
+ bHandled = true;
+ }
+ else if( nFlags & IconChoiceFlags::DownDeselect )
+ {
+ DeselectAllBut( pDocEntry );
+ SetCursor( pDocEntry );
+ SelectEntry( pDocEntry, true, false );
+ bHandled = true;
+ }
+ }
+
+ nFlags &= ~IconChoiceFlags(IconChoiceFlags::DownCtrl | IconChoiceFlags::DownDeselect);
+
+ if((nWinBits & WB_HIGHLIGHTFRAME) && bHighlightFramePressed && pCurHighlightFrame)
+ {
+ bHandled = true;
+ SvxIconChoiceCtrlEntry* pEntry = pCurHighlightFrame;
+ pCurHighlightFrame = nullptr; // force repaint of frame
+ bHighlightFramePressed = false;
+ SetEntryHighlightFrame( pEntry, true );
+
+ pHdlEntry = pCurHighlightFrame;
+ pView->ClickIcon();
+
+ // set focus on Icon
+ SvxIconChoiceCtrlEntry* pOldCursor = pCursor;
+ SetCursor_Impl( pOldCursor, pHdlEntry, false, false );
+
+ pHdlEntry = nullptr;
+ }
+ return bHandled;
+}
+
+bool SvxIconChoiceCtrl_Impl::MouseMove( const MouseEvent& rMEvt )
+{
+ const Point aDocPos( pView->PixelToLogic(rMEvt.GetPosPixel()) );
+
+ if( pView->IsTracking() )
+ return false;
+ else if( nWinBits & WB_HIGHLIGHTFRAME )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = GetEntry( aDocPos, true );
+ SetEntryHighlightFrame( pEntry, false );
+ }
+ else
+ return false;
+ return true;
+}
+
+void SvxIconChoiceCtrl_Impl::SetCursor_Impl( SvxIconChoiceCtrlEntry* pOldCursor,
+ SvxIconChoiceCtrlEntry* pNewCursor, bool bMod1, bool bShift )
+{
+ if( !pNewCursor )
+ return;
+
+ SvxIconChoiceCtrlEntry* pFilterEntry = nullptr;
+ bool bDeselectAll = false;
+ if( eSelectionMode != SelectionMode::Single )
+ {
+ if( !bMod1 && !bShift )
+ bDeselectAll = true;
+ else if( bShift && !bMod1 && !pAnchor )
+ {
+ bDeselectAll = true;
+ pFilterEntry = pOldCursor;
+ }
+ }
+ if( bDeselectAll )
+ DeselectAllBut( pFilterEntry );
+ ShowCursor( false );
+ MakeEntryVisible( pNewCursor );
+ SetCursor( pNewCursor );
+ if( bMod1 && !bShift )
+ {
+ if( pAnchor )
+ {
+ AddSelectedRect( pAnchor, pOldCursor );
+ pAnchor = nullptr;
+ }
+ }
+ else if( bShift )
+ {
+ if( !pAnchor )
+ pAnchor = pOldCursor;
+ if ( nWinBits & WB_ALIGN_LEFT )
+ SelectRange( pAnchor, pNewCursor, bool(nFlags & IconChoiceFlags::AddMode) );
+ else
+ SelectRect(pAnchor,pNewCursor, bool(nFlags & IconChoiceFlags::AddMode), &aSelectedRectList);
+ }
+ else
+ {
+ SelectEntry( pCursor, true, false );
+ aCurSelectionRect = GetEntryBoundRect( pCursor );
+ CallEventListeners( VclEventId::ListboxSelect, pCursor );
+ }
+}
+
+bool SvxIconChoiceCtrl_Impl::KeyInput( const KeyEvent& rKEvt )
+{
+ bool bMod2 = rKEvt.GetKeyCode().IsMod2();
+ sal_Unicode cChar = rKEvt.GetCharCode();
+ sal_uLong nPos = sal_uLong(-1);
+ if ( bMod2 && cChar && IsMnemonicChar( cChar, nPos ) )
+ {
+ // shortcut is clicked
+ SvxIconChoiceCtrlEntry* pNewCursor = GetEntry( nPos );
+ SvxIconChoiceCtrlEntry* pOldCursor = pCursor;
+ if ( pNewCursor != pOldCursor )
+ SetCursor_Impl( pOldCursor, pNewCursor, false, false );
+ return true;
+ }
+
+ if ( bMod2 )
+ // no actions with <ALT>
+ return false;
+
+ bool bKeyUsed = true;
+ bool bMod1 = rKEvt.GetKeyCode().IsMod1();
+ bool bShift = rKEvt.GetKeyCode().IsShift();
+
+ if( eSelectionMode == SelectionMode::Single || eSelectionMode == SelectionMode::NONE)
+ {
+ bShift = false;
+ bMod1 = false;
+ }
+
+ if( bMod1 )
+ nFlags |= IconChoiceFlags::AddMode;
+
+ SvxIconChoiceCtrlEntry* pNewCursor;
+ SvxIconChoiceCtrlEntry* pOldCursor = pCursor;
+
+ sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
+ switch( nCode )
+ {
+ case KEY_UP:
+ case KEY_PAGEUP:
+ if( pCursor )
+ {
+ MakeEntryVisible( pCursor );
+ if( nCode == KEY_UP )
+ pNewCursor = pImpCursor->GoUpDown(pCursor,false);
+ else
+ pNewCursor = pImpCursor->GoPageUpDown(pCursor,false);
+ SetCursor_Impl( pOldCursor, pNewCursor, bMod1, bShift );
+ if( !pNewCursor )
+ {
+ tools::Rectangle aRect( GetEntryBoundRect( pCursor ) );
+ if( aRect.Top())
+ {
+ aRect.AdjustBottom( -(aRect.Top()) );
+ aRect.SetTop( 0 );
+ MakeVisible( aRect );
+ }
+ }
+ }
+ break;
+
+ case KEY_DOWN:
+ case KEY_PAGEDOWN:
+ if( pCursor )
+ {
+ if( nCode == KEY_DOWN )
+ pNewCursor=pImpCursor->GoUpDown( pCursor,true );
+ else
+ pNewCursor=pImpCursor->GoPageUpDown( pCursor,true );
+ SetCursor_Impl( pOldCursor, pNewCursor, bMod1, bShift );
+ }
+ break;
+
+ case KEY_RIGHT:
+ if( pCursor )
+ {
+ pNewCursor=pImpCursor->GoLeftRight(pCursor,true );
+ SetCursor_Impl( pOldCursor, pNewCursor, bMod1, bShift );
+ }
+ break;
+
+ case KEY_LEFT:
+ if( pCursor )
+ {
+ MakeEntryVisible( pCursor );
+ pNewCursor = pImpCursor->GoLeftRight(pCursor,false );
+ SetCursor_Impl( pOldCursor, pNewCursor, bMod1, bShift );
+ if( !pNewCursor )
+ {
+ tools::Rectangle aRect( GetEntryBoundRect(pCursor));
+ if( aRect.Left() )
+ {
+ aRect.AdjustRight( -(aRect.Left()) );
+ aRect.SetLeft( 0 );
+ MakeVisible( aRect );
+ }
+ }
+ }
+ break;
+
+ case KEY_F2:
+ if( bMod1 || bShift )
+ bKeyUsed = false;
+ break;
+
+ case KEY_F8:
+ if( rKEvt.GetKeyCode().IsShift() )
+ {
+ if( nFlags & IconChoiceFlags::AddMode )
+ nFlags &= ~IconChoiceFlags::AddMode;
+ else
+ nFlags |= IconChoiceFlags::AddMode;
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_SPACE:
+ if( pCursor && eSelectionMode != SelectionMode::Single )
+ {
+ if( !bMod1 )
+ {
+ //SelectAll( false );
+ SetNoSelection();
+ ClearSelectedRectList();
+
+ // click Icon with spacebar
+ SetEntryHighlightFrame( GetCurEntry(), true );
+ pView->ClickIcon();
+ pHdlEntry = pCurHighlightFrame;
+ pCurHighlightFrame=nullptr;
+ }
+ else
+ ToggleSelection( pCursor );
+ }
+ break;
+
+#ifdef DBG_UTIL
+ case KEY_F10:
+ if( rKEvt.GetKeyCode().IsShift() )
+ {
+ if( pCursor )
+ pView->SetEntryTextMode( SvxIconChoiceCtrlTextMode::Full, pCursor );
+ }
+ if( rKEvt.GetKeyCode().IsMod1() )
+ {
+ if( pCursor )
+ pView->SetEntryTextMode( SvxIconChoiceCtrlTextMode::Short, pCursor );
+ }
+ break;
+#endif
+
+ case KEY_ADD:
+ case KEY_DIVIDE :
+ case KEY_A:
+ if( bMod1 && (eSelectionMode != SelectionMode::Single))
+ SelectAll();
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_SUBTRACT:
+ case KEY_COMMA :
+ if( bMod1 )
+ SetNoSelection();
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_RETURN:
+ if( !bMod1 )
+ bKeyUsed = false;
+ break;
+
+ case KEY_END:
+ if( pCursor )
+ {
+ pNewCursor = maEntries.back().get();
+ SetCursor_Impl( pOldCursor, pNewCursor, bMod1, bShift );
+ }
+ break;
+
+ case KEY_HOME:
+ if( pCursor )
+ {
+ pNewCursor = maEntries[ 0 ].get();
+ SetCursor_Impl( pOldCursor, pNewCursor, bMod1, bShift );
+ }
+ break;
+
+ default:
+ bKeyUsed = false;
+
+ }
+ return bKeyUsed;
+}
+
+// recalculate TopLeft of scrollbars (but not their sizes!)
+void SvxIconChoiceCtrl_Impl::PositionScrollBars( tools::Long nRealWidth, tools::Long nRealHeight )
+{
+ // horizontal scrollbar
+ Point aPos( 0, nRealHeight );
+ aPos.AdjustY( -nHorSBarHeight );
+
+ if( aHorSBar->GetPosPixel() != aPos )
+ aHorSBar->SetPosPixel( aPos );
+
+ // vertical scrollbar
+ aPos.setX( nRealWidth ); aPos.setY( 0 );
+ aPos.AdjustX( -nVerSBarWidth );
+ aPos.AdjustX( 1 );
+ aPos.AdjustY( -1 );
+
+ if( aVerSBar->GetPosPixel() != aPos )
+ aVerSBar->SetPosPixel( aPos );
+}
+
+void SvxIconChoiceCtrl_Impl::AdjustScrollBars()
+{
+ tools::Long nVirtHeight = aVirtOutputSize.Height();
+ tools::Long nVirtWidth = aVirtOutputSize.Width();
+
+ Size aOSize( pView->Control::GetOutputSizePixel() );
+ tools::Long nRealHeight = aOSize.Height();
+ tools::Long nRealWidth = aOSize.Width();
+
+ PositionScrollBars( nRealWidth, nRealHeight );
+
+ const MapMode& rMapMode = pView->GetMapMode();
+ Point aOrigin( rMapMode.GetOrigin() );
+
+ tools::Long nVisibleWidth;
+ if( nRealWidth > nVirtWidth )
+ nVisibleWidth = nVirtWidth + aOrigin.X();
+ else
+ nVisibleWidth = nRealWidth;
+
+ tools::Long nVisibleHeight;
+ if( nRealHeight > nVirtHeight )
+ nVisibleHeight = nVirtHeight + aOrigin.Y();
+ else
+ nVisibleHeight = nRealHeight;
+
+ bool bVerSBar = ( nWinBits & WB_VSCROLL ) != 0;
+ bool bHorSBar = ( nWinBits & WB_HSCROLL ) != 0;
+ bool bNoVerSBar = ( nWinBits & WB_NOVSCROLL ) != 0;
+ bool bNoHorSBar = ( nWinBits & WB_NOHSCROLL ) != 0;
+
+ sal_uInt16 nResult = 0;
+ if( nVirtHeight )
+ {
+ // activate vertical scrollbar?
+ if( !bNoVerSBar && (bVerSBar || ( nVirtHeight > nVisibleHeight)) )
+ {
+ nResult = 0x0001;
+ nRealWidth -= nVerSBarWidth;
+
+ if( nRealWidth > nVirtWidth )
+ nVisibleWidth = nVirtWidth + aOrigin.X();
+ else
+ nVisibleWidth = nRealWidth;
+ }
+ // activate horizontal scrollbar?
+ if( !bNoHorSBar && (bHorSBar || (nVirtWidth > nVisibleWidth)) )
+ {
+ nResult |= 0x0002;
+ nRealHeight -= nHorSBarHeight;
+
+ if( nRealHeight > nVirtHeight )
+ nVisibleHeight = nVirtHeight + aOrigin.Y();
+ else
+ nVisibleHeight = nRealHeight;
+
+ // do we need a vertical scrollbar after all?
+ if( !(nResult & 0x0001) && // only if not already there
+ ( !bNoVerSBar && ((nVirtHeight > nVisibleHeight) || bVerSBar)) )
+ {
+ nResult = 3; // both turned on
+ nRealWidth -= nVerSBarWidth;
+
+ if( nRealWidth > nVirtWidth )
+ nVisibleWidth = nVirtWidth + aOrigin.X();
+ else
+ nVisibleWidth = nRealWidth;
+ }
+ }
+ }
+
+ // size vertical scrollbar
+ tools::Long nThumb = aVerSBar->GetThumbPos();
+ Size aSize( nVerSBarWidth, nRealHeight );
+ aSize.AdjustHeight(2 );
+ if( aSize != aVerSBar->GetSizePixel() )
+ aVerSBar->SetSizePixel( aSize );
+ aVerSBar->SetVisibleSize( nVisibleHeight );
+ aVerSBar->SetPageSize( GetScrollBarPageSize( nVisibleHeight ));
+
+ if( nResult & 0x0001 )
+ {
+ aVerSBar->SetThumbPos( nThumb );
+ aVerSBar->Show();
+ }
+ else
+ {
+ aVerSBar->SetThumbPos( 0 );
+ aVerSBar->Hide();
+ }
+
+ // size horizontal scrollbar
+ nThumb = aHorSBar->GetThumbPos();
+ aSize.setWidth( nRealWidth );
+ aSize.setHeight( nHorSBarHeight );
+ aSize.AdjustWidth( 1 );
+ if( nResult & 0x0001 ) // vertical scrollbar?
+ {
+ aSize.AdjustWidth( 1 );
+ nRealWidth++;
+ }
+ if( aSize != aHorSBar->GetSizePixel() )
+ aHorSBar->SetSizePixel( aSize );
+ aHorSBar->SetVisibleSize( nVisibleWidth );
+ aHorSBar->SetPageSize( GetScrollBarPageSize(nVisibleWidth ));
+ if( nResult & 0x0002 )
+ {
+ aHorSBar->SetThumbPos( nThumb );
+ aHorSBar->Show();
+ }
+ else
+ {
+ aHorSBar->SetThumbPos( 0 );
+ aHorSBar->Hide();
+ }
+
+ aOutputSize.setWidth( nRealWidth );
+ if( nResult & 0x0002 ) // horizontal scrollbar ?
+ nRealHeight++; // because lower border is clipped
+ aOutputSize.setHeight( nRealHeight );
+
+ if( (nResult & (0x0001|0x0002)) == (0x0001|0x0002) )
+ aScrBarBox->Show();
+ else
+ aScrBarBox->Hide();
+}
+
+void SvxIconChoiceCtrl_Impl::Resize()
+{
+ InitScrollBarBox();
+ aOutputSize = pView->GetOutputSizePixel();
+ pImpCursor->Clear();
+ pGridMap->OutputSizeChanged();
+
+ const Size& rSize = pView->Control::GetOutputSizePixel();
+ PositionScrollBars( rSize.Width(), rSize.Height() );
+ // The scrollbars are shown/hidden asynchronously, so derived classes can
+ // do an Arrange during Resize, without the scrollbars suddenly turning
+ // on and off again.
+ // If an event is already underway, we don't need to send a new one, at least
+ // as long as there is only one event type.
+ if ( ! nUserEventAdjustScrBars )
+ nUserEventAdjustScrBars =
+ Application::PostUserEvent( LINK( this, SvxIconChoiceCtrl_Impl, UserEventHdl),
+ EVENTID_ADJUST_SCROLLBARS);
+
+ VisRectChanged();
+}
+
+bool SvxIconChoiceCtrl_Impl::CheckHorScrollBar()
+{
+ if( maZOrderList.empty() || !aHorSBar->IsVisible() )
+ return false;
+ const MapMode& rMapMode = pView->GetMapMode();
+ Point aOrigin( rMapMode.GetOrigin() );
+ if(!( nWinBits & WB_HSCROLL) && !aOrigin.X() )
+ {
+ tools::Long nWidth = aOutputSize.Width();
+ const size_t nCount = maZOrderList.size();
+ tools::Long nMostRight = 0;
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maZOrderList[ nCur ];
+ tools::Long nRight = GetEntryBoundRect(pEntry).Right();
+ if( nRight > nWidth )
+ return false;
+ if( nRight > nMostRight )
+ nMostRight = nRight;
+ }
+ aHorSBar->Hide();
+ aOutputSize.AdjustHeight(nHorSBarHeight );
+ aVirtOutputSize.setWidth( nMostRight );
+ aHorSBar->SetThumbPos( 0 );
+ Range aRange;
+ aRange.Max() = nMostRight - 1;
+ aHorSBar->SetRange( aRange );
+ if( aVerSBar->IsVisible() )
+ {
+ Size aSize( aVerSBar->GetSizePixel());
+ aSize.AdjustHeight(nHorSBarHeight );
+ aVerSBar->SetSizePixel( aSize );
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SvxIconChoiceCtrl_Impl::CheckVerScrollBar()
+{
+ if( maZOrderList.empty() || !aVerSBar->IsVisible() )
+ return false;
+ const MapMode& rMapMode = pView->GetMapMode();
+ Point aOrigin( rMapMode.GetOrigin() );
+ if(!( nWinBits & WB_VSCROLL) && !aOrigin.Y() )
+ {
+ tools::Long nDeepest = 0;
+ tools::Long nHeight = aOutputSize.Height();
+ const size_t nCount = maZOrderList.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maZOrderList[ nCur ];
+ tools::Long nBottom = GetEntryBoundRect(pEntry).Bottom();
+ if( nBottom > nHeight )
+ return false;
+ if( nBottom > nDeepest )
+ nDeepest = nBottom;
+ }
+ aVerSBar->Hide();
+ aOutputSize.AdjustWidth(nVerSBarWidth );
+ aVirtOutputSize.setHeight( nDeepest );
+ aVerSBar->SetThumbPos( 0 );
+ Range aRange;
+ aRange.Max() = nDeepest - 1;
+ aVerSBar->SetRange( aRange );
+ if( aHorSBar->IsVisible() )
+ {
+ Size aSize( aHorSBar->GetSizePixel());
+ aSize.AdjustWidth(nVerSBarWidth );
+ aHorSBar->SetSizePixel( aSize );
+ }
+ return true;
+ }
+ return false;
+}
+
+
+// hides scrollbars if they're unnecessary
+void SvxIconChoiceCtrl_Impl::CheckScrollBars()
+{
+ CheckVerScrollBar();
+ if( CheckHorScrollBar() )
+ CheckVerScrollBar();
+ if( aVerSBar->IsVisible() && aHorSBar->IsVisible() )
+ aScrBarBox->Show();
+ else
+ aScrBarBox->Hide();
+}
+
+
+void SvxIconChoiceCtrl_Impl::GetFocus()
+{
+ RepaintSelectedEntries();
+ if( pCursor )
+ {
+ pCursor->SetFlags( SvxIconViewFlags::FOCUSED );
+ ShowCursor( true );
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::LoseFocus()
+{
+ if( pCursor )
+ pCursor->ClearFlags( SvxIconViewFlags::FOCUSED );
+ ShowCursor( false );
+
+// HideFocus ();
+// pView->Invalidate ( aFocus.aRect );
+
+ RepaintSelectedEntries();
+}
+
+void SvxIconChoiceCtrl_Impl::SetUpdateMode( bool bUpdate )
+{
+ if( bUpdate != bUpdateMode )
+ {
+ bUpdateMode = bUpdate;
+ if( bUpdate )
+ {
+ AdjustScrollBars();
+ pImpCursor->Clear();
+ pGridMap->Clear();
+ pView->Invalidate(InvalidateFlags::NoChildren);
+ }
+ }
+}
+
+// priorities of the emphasis: bSelected
+void SvxIconChoiceCtrl_Impl::PaintEmphasis(const tools::Rectangle& rTextRect, bool bSelected,
+ vcl::RenderContext& rRenderContext)
+{
+ Color aOldFillColor(rRenderContext.GetFillColor());
+
+ bool bSolidTextRect = false;
+
+ if (!bSelected)
+ {
+ const Color& rFillColor = rRenderContext.GetFont().GetFillColor();
+ rRenderContext.SetFillColor(rFillColor);
+ if (rFillColor != COL_TRANSPARENT)
+ bSolidTextRect = true;
+ }
+
+ // draw text rectangle
+ if (bSolidTextRect)
+ {
+ rRenderContext.DrawRect(rTextRect);
+ }
+
+ rRenderContext.SetFillColor(aOldFillColor);
+}
+
+
+void SvxIconChoiceCtrl_Impl::PaintItem(const tools::Rectangle& rRect,
+ IcnViewFieldType eItem, SvxIconChoiceCtrlEntry* pEntry, sal_uInt16 nPaintFlags,
+ vcl::RenderContext& rRenderContext )
+{
+ if (eItem == IcnViewFieldType::Text)
+ {
+ OUString aText = SvtIconChoiceCtrl::GetEntryText(pEntry);
+
+ rRenderContext.DrawText(rRect, aText, nCurTextDrawFlags);
+
+ if (pEntry->IsFocused())
+ {
+ tools::Rectangle aRect (CalcFocusRect(pEntry));
+ ShowFocus(aRect);
+ DrawFocusRect(rRenderContext);
+ }
+ }
+ else
+ {
+ Point aPos(rRect.TopLeft());
+ if (nPaintFlags & PAINTFLAG_HOR_CENTERED)
+ aPos.AdjustX((rRect.GetWidth() - aImageSize.Width()) / 2 );
+ if (nPaintFlags & PAINTFLAG_VER_CENTERED)
+ aPos.AdjustY((rRect.GetHeight() - aImageSize.Height()) / 2 );
+ SvtIconChoiceCtrl::DrawEntryImage(pEntry, aPos, rRenderContext);
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::PaintEntry(SvxIconChoiceCtrlEntry* pEntry, const Point& rPos, vcl::RenderContext& rRenderContext)
+{
+ bool bSelected = false;
+
+ if (eSelectionMode != SelectionMode::NONE)
+ bSelected = pEntry->IsSelected();
+
+ rRenderContext.Push(vcl::PushFlags::FONT | vcl::PushFlags::TEXTCOLOR);
+
+ OUString aEntryText(SvtIconChoiceCtrl::GetEntryText(pEntry));
+ tools::Rectangle aTextRect(CalcTextRect(pEntry, &rPos, &aEntryText));
+ tools::Rectangle aBmpRect(CalcBmpRect(pEntry, &rPos));
+
+ bool bShowSelection = (bSelected && (eSelectionMode != SelectionMode::NONE));
+
+ bool bActiveSelection = (0 != (nWinBits & WB_NOHIDESELECTION)) || pView->HasFocus();
+
+ if (bShowSelection)
+ {
+ const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
+ vcl::Font aNewFont(rRenderContext.GetFont());
+
+ // font fill colors that are attributed "hard" need corresponding "hard"
+ // attributed highlight colors
+ if ((nWinBits & WB_NOHIDESELECTION) || pView->HasFocus())
+ aNewFont.SetFillColor(rSettings.GetHighlightColor());
+ else
+ aNewFont.SetFillColor(rSettings.GetDeactiveColor());
+
+ Color aWinCol = rSettings.GetWindowTextColor();
+ if (!bActiveSelection && rSettings.GetFaceColor().IsBright() == aWinCol.IsBright())
+ aNewFont.SetColor(rSettings.GetWindowTextColor());
+ else
+ aNewFont.SetColor(rSettings.GetHighlightTextColor());
+
+ rRenderContext.SetFont(aNewFont);
+
+ rRenderContext.SetFillColor(rRenderContext.GetBackground().GetColor());
+ rRenderContext.DrawRect(CalcFocusRect(pEntry));
+ rRenderContext.SetFillColor();
+ }
+
+ bool bResetClipRegion = false;
+ if (!rRenderContext.IsClipRegion() && (aVerSBar->IsVisible() || aHorSBar->IsVisible()))
+ {
+ tools::Rectangle aOutputArea(GetOutputRect());
+ if (aOutputArea.Overlaps(aTextRect) || aOutputArea.Overlaps(aBmpRect))
+ {
+ rRenderContext.SetClipRegion(vcl::Region(aOutputArea));
+ bResetClipRegion = true;
+ }
+ }
+
+ bool bLargeIconMode = WB_ICON == ( nWinBits & VIEWMODE_MASK );
+ sal_uInt16 nBmpPaintFlags = PAINTFLAG_VER_CENTERED;
+ if (bLargeIconMode)
+ nBmpPaintFlags |= PAINTFLAG_HOR_CENTERED;
+ sal_uInt16 nTextPaintFlags = bLargeIconMode ? PAINTFLAG_HOR_CENTERED : PAINTFLAG_VER_CENTERED;
+
+ PaintEmphasis(aTextRect, bSelected, rRenderContext);
+
+ if ( bShowSelection )
+ vcl::RenderTools::DrawSelectionBackground(rRenderContext, *pView, CalcFocusRect(pEntry),
+ bActiveSelection ? 1 : 2, false, true, false);
+
+
+ PaintItem(aBmpRect, IcnViewFieldType::Image, pEntry, nBmpPaintFlags, rRenderContext);
+
+ PaintItem(aTextRect, IcnViewFieldType::Text, pEntry, nTextPaintFlags, rRenderContext);
+
+ // draw highlight frame
+ if (pEntry == pCurHighlightFrame)
+ DrawHighlightFrame(rRenderContext, CalcFocusRect(pEntry));
+
+ rRenderContext.Pop();
+ if (bResetClipRegion)
+ rRenderContext.SetClipRegion();
+}
+
+void SvxIconChoiceCtrl_Impl::SetEntryPos( SvxIconChoiceCtrlEntry* pEntry, const Point& rPos )
+{
+ ShowCursor( false );
+ tools::Rectangle aBoundRect( GetEntryBoundRect( pEntry ));
+ pView->Invalidate( aBoundRect );
+ ToTop( pEntry );
+ if( !IsAutoArrange() )
+ {
+ bool bAdjustVirtSize = false;
+ if( rPos != aBoundRect.TopLeft() )
+ {
+ Point aGridOffs(
+ pEntry->aGridRect.TopLeft() - pEntry->aRect.TopLeft() );
+ pImpCursor->Clear();
+ pGridMap->Clear();
+ aBoundRect.SetPos( rPos );
+ pEntry->aRect = aBoundRect;
+ pEntry->aGridRect.SetPos( rPos + aGridOffs );
+ bAdjustVirtSize = true;
+ }
+ if( bAdjustVirtSize )
+ AdjustVirtSize( pEntry->aRect );
+
+ pView->Invalidate( pEntry->aRect );
+ pGridMap->OccupyGrids( pEntry );
+ }
+ else
+ {
+ SvxIconChoiceCtrlEntry* pPrev = FindEntryPredecessor( pEntry, rPos );
+ SetEntryPredecessor( pEntry, pPrev );
+ aAutoArrangeIdle.Start();
+ }
+ ShowCursor( true );
+}
+
+void SvxIconChoiceCtrl_Impl::SetNoSelection()
+{
+ // block recursive calls via SelectEntry
+ if( !(nFlags & IconChoiceFlags::ClearingSelection ))
+ {
+ nFlags |= IconChoiceFlags::ClearingSelection;
+ DeselectAllBut( nullptr );
+ nFlags &= ~IconChoiceFlags::ClearingSelection;
+ }
+}
+
+SvxIconChoiceCtrlEntry* SvxIconChoiceCtrl_Impl::GetEntry( const Point& rDocPos, bool bHit )
+{
+ CheckBoundingRects();
+ // search through z-order list from the end
+ size_t nCount = maZOrderList.size();
+ while( nCount )
+ {
+ nCount--;
+ SvxIconChoiceCtrlEntry* pEntry = maZOrderList[ nCount ];
+ if( pEntry->aRect.Contains( rDocPos ) )
+ {
+ if( bHit )
+ {
+ tools::Rectangle aRect = CalcBmpRect( pEntry );
+ aRect.AdjustTop( -3 );
+ aRect.AdjustBottom(3 );
+ aRect.AdjustLeft( -3 );
+ aRect.AdjustRight(3 );
+ if( aRect.Contains( rDocPos ) )
+ return pEntry;
+ aRect = CalcTextRect( pEntry );
+ if( aRect.Contains( rDocPos ) )
+ return pEntry;
+ }
+ else
+ return pEntry;
+ }
+ }
+ return nullptr;
+}
+
+void SvxIconChoiceCtrl_Impl::MakeEntryVisible( SvxIconChoiceCtrlEntry* pEntry, bool bBound )
+{
+ if ( bBound )
+ {
+ const tools::Rectangle& rRect = GetEntryBoundRect( pEntry );
+ MakeVisible( rRect );
+ }
+ else
+ {
+ tools::Rectangle aRect = CalcBmpRect( pEntry );
+ aRect.Union( CalcTextRect( pEntry ) );
+ aRect.AdjustTop(TBOFFS_BOUND );
+ aRect.AdjustBottom(TBOFFS_BOUND );
+ aRect.AdjustLeft(LROFFS_BOUND );
+ aRect.AdjustRight(LROFFS_BOUND );
+ MakeVisible( aRect );
+ }
+}
+
+const tools::Rectangle& SvxIconChoiceCtrl_Impl::GetEntryBoundRect( SvxIconChoiceCtrlEntry* pEntry )
+{
+ if( !IsBoundingRectValid( pEntry->aRect ))
+ FindBoundingRect( pEntry );
+ return pEntry->aRect;
+}
+
+tools::Rectangle SvxIconChoiceCtrl_Impl::CalcBmpRect( SvxIconChoiceCtrlEntry* pEntry, const Point* pPos )
+{
+ tools::Rectangle aBound = GetEntryBoundRect( pEntry );
+ if( pPos )
+ aBound.SetPos( *pPos );
+ Point aPos( aBound.TopLeft() );
+
+ switch( nWinBits & VIEWMODE_MASK )
+ {
+ case WB_ICON:
+ {
+ aPos.AdjustX(( aBound.GetWidth() - aImageSize.Width() ) / 2 );
+ return tools::Rectangle( aPos, aImageSize );
+ }
+
+ case WB_SMALLICON:
+ case WB_DETAILS:
+ aPos.AdjustY(( aBound.GetHeight() - aImageSize.Height() ) / 2 );
+ //TODO: determine horizontal distance to bounding rectangle
+ return tools::Rectangle( aPos, aImageSize );
+
+ default:
+ OSL_FAIL("IconView: Viewmode not set");
+ return aBound;
+ }
+}
+
+tools::Rectangle SvxIconChoiceCtrl_Impl::CalcTextRect( SvxIconChoiceCtrlEntry* pEntry,
+ const Point* pEntryPos, const OUString* pStr )
+{
+ OUString aEntryText;
+ if( !pStr )
+ aEntryText = SvtIconChoiceCtrl::GetEntryText( pEntry );
+ else
+ aEntryText = *pStr;
+
+ const tools::Rectangle aMaxTextRect( CalcMaxTextRect( pEntry ) );
+ tools::Rectangle aBound( GetEntryBoundRect( pEntry ) );
+ if( pEntryPos )
+ aBound.SetPos( *pEntryPos );
+
+ tools::Rectangle aTextRect = pView->GetTextRect( aMaxTextRect, aEntryText, nCurTextDrawFlags );
+
+ Size aTextSize( aTextRect.GetSize() );
+
+ Point aPos( aBound.TopLeft() );
+ tools::Long nBoundWidth = aBound.GetWidth();
+ tools::Long nBoundHeight = aBound.GetHeight();
+
+ switch( nWinBits & VIEWMODE_MASK )
+ {
+ case WB_ICON:
+ aPos.AdjustY(aImageSize.Height() );
+ aPos.AdjustY(VER_DIST_BMP_STRING );
+ aPos.AdjustX((nBoundWidth - aTextSize.Width()) / 2 );
+ break;
+
+ case WB_SMALLICON:
+ case WB_DETAILS:
+ aPos.AdjustX(aImageSize.Width() );
+ aPos.AdjustX(HOR_DIST_BMP_STRING );
+ aPos.AdjustY((nBoundHeight - aTextSize.Height()) / 2 );
+ break;
+ }
+ return tools::Rectangle( aPos, aTextSize );
+}
+
+
+tools::Long SvxIconChoiceCtrl_Impl::CalcBoundingWidth() const
+{
+ tools::Long nStringWidth = GetItemSize( IcnViewFieldType::Text ).Width();
+ tools::Long nWidth = 0;
+
+ switch( nWinBits & VIEWMODE_MASK )
+ {
+ case WB_ICON:
+ nWidth = std::max( nStringWidth, aImageSize.Width() );
+ break;
+
+ case WB_SMALLICON:
+ case WB_DETAILS:
+ nWidth = aImageSize.Width();
+ nWidth += HOR_DIST_BMP_STRING;
+ nWidth += nStringWidth;
+ break;
+ }
+ return nWidth;
+}
+
+tools::Long SvxIconChoiceCtrl_Impl::CalcBoundingHeight() const
+{
+ tools::Long nStringHeight = GetItemSize(IcnViewFieldType::Text).Height();
+ tools::Long nHeight = 0;
+
+ switch( nWinBits & VIEWMODE_MASK )
+ {
+ case WB_ICON:
+ nHeight = aImageSize.Height();
+ nHeight += VER_DIST_BMP_STRING;
+ nHeight += nStringHeight;
+ break;
+
+ case WB_SMALLICON:
+ case WB_DETAILS:
+ nHeight = std::max( aImageSize.Height(), nStringHeight );
+ break;
+ }
+ if( nHeight > nMaxBoundHeight )
+ {
+ const_cast<SvxIconChoiceCtrl_Impl*>(this)->nMaxBoundHeight = nHeight;
+ const_cast<SvxIconChoiceCtrl_Impl*>(this)->aHorSBar->SetLineSize( GetScrollBarLineSize() );
+ const_cast<SvxIconChoiceCtrl_Impl*>(this)->aVerSBar->SetLineSize( GetScrollBarLineSize() );
+ }
+ return nHeight;
+}
+
+Size SvxIconChoiceCtrl_Impl::CalcBoundingSize() const
+{
+ return Size( CalcBoundingWidth(), CalcBoundingHeight() );
+}
+
+void SvxIconChoiceCtrl_Impl::RecalcAllBoundingRectsSmart()
+{
+ nMaxBoundHeight = 0;
+ maZOrderList.clear();
+ size_t nCur;
+ SvxIconChoiceCtrlEntry* pEntry;
+ const size_t nCount = maEntries.size();
+
+ if( !IsAutoArrange() || !pHead )
+ {
+ for( nCur = 0; nCur < nCount; nCur++ )
+ {
+ pEntry = maEntries[ nCur ].get();
+ if( IsBoundingRectValid( pEntry->aRect ))
+ {
+ Size aBoundSize( pEntry->aRect.GetSize() );
+ if( aBoundSize.Height() > nMaxBoundHeight )
+ nMaxBoundHeight = aBoundSize.Height();
+ }
+ else
+ FindBoundingRect( pEntry );
+ maZOrderList.push_back( pEntry );
+ }
+ }
+ else
+ {
+ nCur = 0;
+ pEntry = pHead;
+ while( nCur != nCount )
+ {
+ DBG_ASSERT(pEntry->pflink&&pEntry->pblink,"SvxIconChoiceCtrl_Impl::RecalcAllBoundingRect > Bad link(s)");
+ if( IsBoundingRectValid( pEntry->aRect ))
+ {
+ Size aBoundSize( pEntry->aRect.GetSize() );
+ if( aBoundSize.Height() > nMaxBoundHeight )
+ nMaxBoundHeight = aBoundSize.Height();
+ }
+ else
+ FindBoundingRect( pEntry );
+ maZOrderList.push_back( pEntry );
+ pEntry = pEntry->pflink;
+ nCur++;
+ }
+ }
+ AdjustScrollBars();
+}
+
+void SvxIconChoiceCtrl_Impl::FindBoundingRect( SvxIconChoiceCtrlEntry* pEntry )
+{
+ DBG_ASSERT(!pEntry->IsPosLocked(),"Locked entry pos in FindBoundingRect");
+ if( pEntry->IsPosLocked() && IsBoundingRectValid( pEntry->aRect) )
+ {
+ AdjustVirtSize( pEntry->aRect );
+ return;
+ }
+ Size aSize( CalcBoundingSize() );
+ Point aPos(pGridMap->GetGridRect(pGridMap->GetUnoccupiedGrid()).TopLeft());
+ SetBoundingRect_Impl( pEntry, aPos, aSize );
+}
+
+void SvxIconChoiceCtrl_Impl::SetBoundingRect_Impl( SvxIconChoiceCtrlEntry* pEntry, const Point& rPos,
+ const Size& /*rBoundingSize*/ )
+{
+ tools::Rectangle aGridRect( rPos, Size(nGridDX, nGridDY) );
+ pEntry->aGridRect = aGridRect;
+ Center( pEntry );
+ AdjustVirtSize( pEntry->aRect );
+ pGridMap->OccupyGrids( pEntry );
+}
+
+
+void SvxIconChoiceCtrl_Impl::SetCursor( SvxIconChoiceCtrlEntry* pEntry )
+{
+ if( pEntry == pCursor )
+ {
+ if( pCursor && eSelectionMode == SelectionMode::Single &&
+ !pCursor->IsSelected() )
+ SelectEntry( pCursor, true );
+ return;
+ }
+ ShowCursor( false );
+ SvxIconChoiceCtrlEntry* pOldCursor = pCursor;
+ pCursor = pEntry;
+ if( pOldCursor )
+ {
+ pOldCursor->ClearFlags( SvxIconViewFlags::FOCUSED );
+ if( eSelectionMode == SelectionMode::Single )
+ SelectEntry( pOldCursor, false ); // deselect old cursor
+ }
+ if( pCursor )
+ {
+ ToTop( pCursor );
+ pCursor->SetFlags( SvxIconViewFlags::FOCUSED );
+ if( eSelectionMode == SelectionMode::Single )
+ SelectEntry( pCursor, true );
+ ShowCursor( true );
+ }
+}
+
+
+void SvxIconChoiceCtrl_Impl::ShowCursor( bool bShow )
+{
+ if( !pCursor || !bShow || !pView->HasFocus() )
+ {
+ pView->HideFocus();
+ return;
+ }
+ tools::Rectangle aRect ( CalcFocusRect( pCursor ) );
+ /*pView->*/ShowFocus( aRect );
+}
+
+
+void SvxIconChoiceCtrl_Impl::HideDDIcon()
+{
+ pView->PaintImmediately();
+}
+
+bool SvxIconChoiceCtrl_Impl::HandleScrollCommand( const CommandEvent& rCmd )
+{
+ tools::Rectangle aDocRect( Point(), aVirtOutputSize );
+ tools::Rectangle aVisRect( GetOutputRect() );
+ if( aVisRect.Contains( aDocRect ))
+ return false;
+ Size aDocSize( aDocRect.GetSize() );
+ Size aVisSize( aVisRect.GetSize() );
+ bool bHor = aDocSize.Width() > aVisSize.Width();
+ bool bVer = aDocSize.Height() > aVisSize.Height();
+
+ tools::Long nScrollDX = 0, nScrollDY = 0;
+
+ switch( rCmd.GetCommand() )
+ {
+ case CommandEventId::StartAutoScroll:
+ {
+ pView->EndTracking();
+ StartAutoScrollFlags nScrollFlags = StartAutoScrollFlags::NONE;
+ if( bHor )
+ nScrollFlags |= StartAutoScrollFlags::Horz;
+ if( bVer )
+ nScrollFlags |= StartAutoScrollFlags::Vert;
+ if( nScrollFlags != StartAutoScrollFlags::NONE )
+ {
+ pView->StartAutoScroll( nScrollFlags );
+ return true;
+ }
+ }
+ break;
+
+ case CommandEventId::Wheel:
+ {
+ const CommandWheelData* pData = rCmd.GetWheelData();
+ if( pData && (CommandWheelMode::SCROLL == pData->GetMode()) && !pData->IsHorz() )
+ {
+ sal_uLong nScrollLines = pData->GetScrollLines();
+ if( nScrollLines == COMMAND_WHEEL_PAGESCROLL )
+ {
+ nScrollDY = GetScrollBarPageSize( aVisSize.Width() );
+ if( pData->GetDelta() < 0 )
+ nScrollDY *= -1;
+ }
+ else
+ {
+ nScrollDY = pData->GetNotchDelta() * static_cast<tools::Long>(nScrollLines);
+ nScrollDY *= GetScrollBarLineSize();
+ }
+ }
+ }
+ break;
+
+ case CommandEventId::AutoScroll:
+ {
+ const CommandScrollData* pData = rCmd.GetAutoScrollData();
+ if( pData )
+ {
+ nScrollDX = pData->GetDeltaX() * GetScrollBarLineSize();
+ nScrollDY = pData->GetDeltaY() * GetScrollBarLineSize();
+ }
+ }
+ break;
+
+ default: break;
+ }
+
+ if( nScrollDX || nScrollDY )
+ {
+ aVisRect.AdjustTop( -nScrollDY );
+ aVisRect.AdjustBottom( -nScrollDY );
+ aVisRect.AdjustLeft( -nScrollDX );
+ aVisRect.AdjustRight( -nScrollDX );
+ MakeVisible( aVisRect );
+ return true;
+ }
+ return false;
+}
+
+
+void SvxIconChoiceCtrl_Impl::Command( const CommandEvent& rCEvt )
+{
+ // scroll mouse event?
+ if( (rCEvt.GetCommand() == CommandEventId::Wheel) ||
+ (rCEvt.GetCommand() == CommandEventId::StartAutoScroll) ||
+ (rCEvt.GetCommand() == CommandEventId::AutoScroll) )
+ {
+ if( HandleScrollCommand( rCEvt ) )
+ return;
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::ToTop( SvxIconChoiceCtrlEntry* pEntry )
+{
+ if( maZOrderList.empty() || pEntry == maZOrderList.back())
+ return;
+
+ auto it = std::find(maZOrderList.begin(), maZOrderList.end(), pEntry);
+ if (it != maZOrderList.end())
+ {
+ maZOrderList.erase( it );
+ maZOrderList.push_back( pEntry );
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::ClipAtVirtOutRect( tools::Rectangle& rRect ) const
+{
+ if( rRect.Bottom() >= aVirtOutputSize.Height() )
+ rRect.SetBottom( aVirtOutputSize.Height() - 1 );
+ if( rRect.Right() >= aVirtOutputSize.Width() )
+ rRect.SetRight( aVirtOutputSize.Width() - 1 );
+ if( rRect.Top() < 0 )
+ rRect.SetTop( 0 );
+ if( rRect.Left() < 0 )
+ rRect.SetLeft( 0 );
+}
+
+// rRect: area of the document (in document coordinates) that we want to make
+// visible
+// bScrBar == true: rectangle was calculated because of a scrollbar event
+
+void SvxIconChoiceCtrl_Impl::MakeVisible( const tools::Rectangle& rRect, bool bScrBar )
+{
+ tools::Rectangle aVirtRect( rRect );
+ ClipAtVirtOutRect( aVirtRect );
+ Point aOrigin( pView->GetMapMode().GetOrigin() );
+ // convert to document coordinate
+ aOrigin *= -1;
+ tools::Rectangle aOutputArea( GetOutputRect() );
+ if( aOutputArea.Contains( aVirtRect ) )
+ return; // is already visible
+
+ tools::Long nDy;
+ if( aVirtRect.Top() < aOutputArea.Top() )
+ {
+ // scroll up (nDy < 0)
+ nDy = aVirtRect.Top() - aOutputArea.Top();
+ }
+ else if( aVirtRect.Bottom() > aOutputArea.Bottom() )
+ {
+ // scroll down (nDy > 0)
+ nDy = aVirtRect.Bottom() - aOutputArea.Bottom();
+ }
+ else
+ nDy = 0;
+
+ tools::Long nDx;
+ if( aVirtRect.Left() < aOutputArea.Left() )
+ {
+ // scroll to the left (nDx < 0)
+ nDx = aVirtRect.Left() - aOutputArea.Left();
+ }
+ else if( aVirtRect.Right() > aOutputArea.Right() )
+ {
+ // scroll to the right (nDx > 0)
+ nDx = aVirtRect.Right() - aOutputArea.Right();
+ }
+ else
+ nDx = 0;
+
+ aOrigin.AdjustX(nDx );
+ aOrigin.AdjustY(nDy );
+ aOutputArea.SetPos( aOrigin );
+ if( GetUpdateMode() )
+ {
+ HideDDIcon();
+ pView->PaintImmediately();
+ ShowCursor( false );
+ }
+
+ // invert origin for SV (so we can scroll/paint using document coordinates)
+ aOrigin *= -1;
+ SetOrigin( aOrigin );
+
+ bool bScrollable = pView->GetBackground().IsScrollable();
+
+ if( bScrollable && GetUpdateMode() )
+ {
+ // scroll in reverse direction!
+ pView->Control::Scroll( -nDx, -nDy, aOutputArea,
+ ScrollFlags::NoChildren | ScrollFlags::UseClipRegion | ScrollFlags::Clip );
+ }
+ else
+ pView->Invalidate(InvalidateFlags::NoChildren);
+
+ if( aHorSBar->IsVisible() || aVerSBar->IsVisible() )
+ {
+ if( !bScrBar )
+ {
+ aOrigin *= -1;
+ // correct thumbs
+ if(aHorSBar->IsVisible() && aHorSBar->GetThumbPos() != aOrigin.X())
+ aHorSBar->SetThumbPos( aOrigin.X() );
+ if(aVerSBar->IsVisible() && aVerSBar->GetThumbPos() != aOrigin.Y())
+ aVerSBar->SetThumbPos( aOrigin.Y() );
+ }
+ }
+
+ if( GetUpdateMode() )
+ ShowCursor( true );
+
+ // check if we still need scrollbars
+ CheckScrollBars();
+ if( bScrollable && GetUpdateMode() )
+ pView->PaintImmediately();
+
+ // If the requested area can not be made completely visible, the
+ // Vis-Rect-Changed handler is called in any case. This case may occur e.g.
+ // if only few pixels of the lower border are invisible, but a scrollbar has
+ // a larger line size.
+ VisRectChanged();
+}
+
+sal_Int32 SvxIconChoiceCtrl_Impl::GetSelectionCount() const
+{
+ if( (nWinBits & WB_HIGHLIGHTFRAME) && pCurHighlightFrame )
+ return 1;
+ return nSelectionCount;
+}
+
+void SvxIconChoiceCtrl_Impl::ToggleSelection( SvxIconChoiceCtrlEntry* pEntry )
+{
+ bool bSel;
+ bSel = !pEntry->IsSelected();
+ SelectEntry( pEntry, bSel, true );
+}
+
+void SvxIconChoiceCtrl_Impl::DeselectAllBut( SvxIconChoiceCtrlEntry const * pThisEntryNot )
+{
+ ClearSelectedRectList();
+
+ // TODO: work through z-order list, if necessary!
+
+ size_t nCount = maEntries.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maEntries[ nCur ].get();
+ if( pEntry != pThisEntryNot && pEntry->IsSelected() )
+ SelectEntry( pEntry, false, true );
+ }
+ pAnchor = nullptr;
+ nFlags &= ~IconChoiceFlags::AddMode;
+}
+
+Size SvxIconChoiceCtrl_Impl::GetMinGrid() const
+{
+ Size aMinSize( aImageSize );
+ aMinSize.AdjustWidth(2 * LROFFS_BOUND );
+ aMinSize.AdjustHeight(TBOFFS_BOUND ); // single offset is enough (FileDlg)
+ Size aTextSize( pView->GetTextWidth( "XXX" ), pView->GetTextHeight() );
+ if( nWinBits & WB_ICON )
+ {
+ aMinSize.AdjustHeight(VER_DIST_BMP_STRING );
+ aMinSize.AdjustHeight(aTextSize.Height() );
+ }
+ else
+ {
+ aMinSize.AdjustWidth(HOR_DIST_BMP_STRING );
+ aMinSize.AdjustWidth(aTextSize.Width() );
+ }
+ return aMinSize;
+}
+
+void SvxIconChoiceCtrl_Impl::SetGrid( const Size& rSize )
+{
+ Size aSize( rSize );
+ Size aMinSize( GetMinGrid() );
+ if( aSize.Width() < aMinSize.Width() )
+ aSize.setWidth( aMinSize.Width() );
+ if( aSize.Height() < aMinSize.Height() )
+ aSize.setHeight( aMinSize.Height() );
+
+ nGridDX = aSize.Width();
+ // HACK: Detail mode is not yet fully implemented, this workaround makes it
+ // fly with a single column
+ if( nWinBits & WB_DETAILS )
+ {
+ const SvxIconChoiceCtrlColumnInfo* pCol = GetColumn( 0 );
+ if( pCol )
+ const_cast<SvxIconChoiceCtrlColumnInfo*>(pCol)->SetWidth( nGridDX );
+ }
+ nGridDY = aSize.Height();
+ SetDefaultTextSize();
+}
+
+// Calculates the maximum size that the text rectangle may use within its
+// bounding rectangle. In WB_ICON mode with SvxIconChoiceCtrlTextMode::Full, Bottom is set to
+// LONG_MAX.
+
+tools::Rectangle SvxIconChoiceCtrl_Impl::CalcMaxTextRect( const SvxIconChoiceCtrlEntry* pEntry ) const
+{
+ tools::Rectangle aBoundRect;
+ // avoid infinite recursion: don't calculate the bounding rectangle here
+ if( IsBoundingRectValid( pEntry->aRect ) )
+ aBoundRect = pEntry->aRect;
+ else
+ aBoundRect = pEntry->aGridRect;
+
+ tools::Rectangle aBmpRect( const_cast<SvxIconChoiceCtrl_Impl*>(this)->CalcBmpRect(
+ const_cast<SvxIconChoiceCtrlEntry*>(pEntry) ) );
+ if( nWinBits & WB_ICON )
+ {
+ aBoundRect.SetTop( aBmpRect.Bottom() );
+ aBoundRect.AdjustTop(VER_DIST_BMP_STRING );
+ if( aBoundRect.Top() > aBoundRect.Bottom())
+ aBoundRect.SetTop( aBoundRect.Bottom() );
+ aBoundRect.AdjustLeft(LROFFS_BOUND );
+ aBoundRect.AdjustLeft( 1 );
+ aBoundRect.AdjustRight( -(LROFFS_BOUND) );
+ aBoundRect.AdjustRight( -1 );
+ if( aBoundRect.Left() > aBoundRect.Right())
+ aBoundRect.SetLeft( aBoundRect.Right() );
+ if( pEntry->GetTextMode() == SvxIconChoiceCtrlTextMode::Full )
+ aBoundRect.SetBottom( LONG_MAX );
+ }
+ else
+ {
+ aBoundRect.SetLeft( aBmpRect.Right() );
+ aBoundRect.AdjustLeft(HOR_DIST_BMP_STRING );
+ aBoundRect.AdjustRight( -(LROFFS_BOUND) );
+ if( aBoundRect.Left() > aBoundRect.Right() )
+ aBoundRect.SetLeft( aBoundRect.Right() );
+ tools::Long nHeight = aBoundRect.GetSize().Height();
+ nHeight = nHeight - aDefaultTextSize.Height();
+ nHeight /= 2;
+ aBoundRect.AdjustTop(nHeight );
+ aBoundRect.AdjustBottom( -nHeight );
+ }
+ return aBoundRect;
+}
+
+void SvxIconChoiceCtrl_Impl::SetDefaultTextSize()
+{
+ tools::Long nDY = nGridDY;
+ nDY -= aImageSize.Height();
+ nDY -= VER_DIST_BMP_STRING;
+ nDY -= 2 * TBOFFS_BOUND;
+ if (nDY <= 0)
+ nDY = 2;
+
+ tools::Long nDX = nGridDX;
+ nDX -= 2 * LROFFS_BOUND;
+ nDX -= 2;
+ if (nDX <= 0)
+ nDX = 2;
+
+ tools::Long nHeight = pView->GetTextHeight();
+ if (nDY < nHeight)
+ nDY = nHeight;
+ if(pView->GetDPIScaleFactor() > 1)
+ {
+ nDY*=2;
+ }
+ aDefaultTextSize = Size(nDX, nDY);
+}
+
+
+void SvxIconChoiceCtrl_Impl::Center( SvxIconChoiceCtrlEntry* pEntry ) const
+{
+ pEntry->aRect = pEntry->aGridRect;
+ Size aSize( CalcBoundingSize() );
+ if( nWinBits & WB_ICON )
+ {
+ // center horizontally
+ tools::Long nBorder = pEntry->aGridRect.GetWidth() - aSize.Width();
+ pEntry->aRect.AdjustLeft(nBorder / 2 );
+ pEntry->aRect.AdjustRight( -(nBorder / 2) );
+ }
+ // center vertically
+ pEntry->aRect.SetBottom( pEntry->aRect.Top() + aSize.Height() );
+}
+
+
+// The deltas are the offsets by which the view is moved on the document.
+// left, up: offsets < 0
+// right, down: offsets > 0
+void SvxIconChoiceCtrl_Impl::Scroll( tools::Long nDeltaX, tools::Long nDeltaY )
+{
+ const MapMode& rMapMode = pView->GetMapMode();
+ Point aOrigin( rMapMode.GetOrigin() );
+ // convert to document coordinate
+ aOrigin *= -1;
+ aOrigin.AdjustY(nDeltaY );
+ aOrigin.AdjustX(nDeltaX );
+ tools::Rectangle aRect( aOrigin, aOutputSize );
+ MakeVisible( aRect, true/*bScrollBar*/ );
+}
+
+
+const Size& SvxIconChoiceCtrl_Impl::GetItemSize( IcnViewFieldType eItem ) const
+{
+ if (eItem == IcnViewFieldType::Text)
+ return aDefaultTextSize;
+ return aImageSize; // IcnViewFieldType::Image
+}
+
+tools::Rectangle SvxIconChoiceCtrl_Impl::CalcFocusRect( SvxIconChoiceCtrlEntry* pEntry )
+{
+ tools::Rectangle aTextRect( CalcTextRect( pEntry ) );
+ tools::Rectangle aBoundRect( GetEntryBoundRect( pEntry ) );
+ return tools::Rectangle(
+ aBoundRect.Left(), aBoundRect.Top() - 1, aBoundRect.Right() - 1,
+ aTextRect.Bottom() + 1);
+}
+
+// the hot spot is the inner 50% of the rectangle
+static tools::Rectangle GetHotSpot( const tools::Rectangle& rRect )
+{
+ tools::Rectangle aResult( rRect );
+ aResult.Justify();
+ Size aSize( rRect.GetSize() );
+ tools::Long nDelta = aSize.Width() / 4;
+ aResult.AdjustLeft(nDelta );
+ aResult.AdjustRight( -nDelta );
+ nDelta = aSize.Height() / 4;
+ aResult.AdjustTop(nDelta );
+ aResult.AdjustBottom( -nDelta );
+ return aResult;
+}
+
+void SvxIconChoiceCtrl_Impl::SelectRect( SvxIconChoiceCtrlEntry* pEntry1, SvxIconChoiceCtrlEntry* pEntry2,
+ bool bAdd, std::vector<tools::Rectangle>* pOtherRects )
+{
+ DBG_ASSERT(pEntry1 && pEntry2,"SelectEntry: Invalid Entry-Ptr");
+ tools::Rectangle aRect( GetEntryBoundRect( pEntry1 ) );
+ aRect.Union( GetEntryBoundRect( pEntry2 ) );
+ SelectRect( aRect, bAdd, pOtherRects );
+}
+
+void SvxIconChoiceCtrl_Impl::SelectRect( const tools::Rectangle& rRect, bool bAdd,
+ std::vector<tools::Rectangle>* pOtherRects )
+{
+ aCurSelectionRect = rRect;
+ if( maZOrderList.empty() )
+ return;
+
+ // set flag, so ToTop won't be called in Select
+ bool bAlreadySelectingRect(nFlags & IconChoiceFlags::SelectingRect);
+ nFlags |= IconChoiceFlags::SelectingRect;
+
+ CheckBoundingRects();
+ pView->PaintImmediately();
+ const size_t nCount = maZOrderList.size();
+
+ tools::Rectangle aRect( rRect );
+ aRect.Justify();
+ bool bCalcOverlap = (bAdd && pOtherRects && !pOtherRects->empty());
+
+ bool bResetClipRegion = false;
+ if( !pView->GetOutDev()->IsClipRegion() )
+ {
+ bResetClipRegion = true;
+ pView->GetOutDev()->SetClipRegion(vcl::Region(GetOutputRect()));
+ }
+
+ for( size_t nPos = 0; nPos < nCount; nPos++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maZOrderList[ nPos ];
+
+ if( !IsBoundingRectValid( pEntry->aRect ))
+ FindBoundingRect( pEntry );
+ tools::Rectangle aBoundRect( GetHotSpot( pEntry->aRect ) );
+ bool bSelected = pEntry->IsSelected();
+
+ bool bOverlaps;
+ if( bCalcOverlap )
+ bOverlaps = IsOver( pOtherRects, aBoundRect );
+ else
+ bOverlaps = false;
+ bool bOver = aRect.Overlaps( aBoundRect );
+
+ if( bOver && !bOverlaps )
+ {
+ // is inside the new selection rectangle and outside of any old one
+ // => select
+ if( !bSelected )
+ SelectEntry( pEntry, true, true );
+ }
+ else if( !bAdd )
+ {
+ // is outside of the selection rectangle
+ // => deselect
+ if( bSelected )
+ SelectEntry( pEntry, false, true );
+ }
+ else if (bOverlaps)
+ {
+ // The entry is inside an old (=>span multiple rectangles with Ctrl)
+ // selection rectangle.
+
+ // There is still a bug here! The selection status of an entry in a
+ // previous rectangle has to be restored, if it was touched by the
+ // current selection rectangle but is not inside it any more.
+ // For simplicity's sake, let's assume that all entries in the old
+ // rectangles were correctly selected. It is wrong to just deselect
+ // the intersection.
+ // Possible solution: remember a snapshot of the selection before
+ // spanning the rectangle.
+ if( aBoundRect.Overlaps( rRect))
+ {
+ // deselect intersection between old rectangles and current rectangle
+ if( bSelected )
+ SelectEntry( pEntry, false, true );
+ }
+ else
+ {
+ // select entry of an old rectangle
+ if( !bSelected )
+ SelectEntry( pEntry, true, true );
+ }
+ }
+ else if( !bOver && bSelected )
+ {
+ // this entry is completely outside the rectangle => deselect it
+ SelectEntry( pEntry, false, true );
+ }
+ }
+
+ if( !bAlreadySelectingRect )
+ nFlags &= ~IconChoiceFlags::SelectingRect;
+
+ pView->PaintImmediately();
+ if( bResetClipRegion )
+ pView->GetOutDev()->SetClipRegion();
+}
+
+void SvxIconChoiceCtrl_Impl::SelectRange(
+ SvxIconChoiceCtrlEntry const * pStart,
+ SvxIconChoiceCtrlEntry const * pEnd,
+ bool bAdd )
+{
+ sal_uLong nFront = GetEntryListPos( pStart );
+ sal_uLong nBack = GetEntryListPos( pEnd );
+ sal_uLong nFirst = std::min( nFront, nBack );
+ sal_uLong nLast = std::max( nFront, nBack );
+ sal_uLong i;
+ SvxIconChoiceCtrlEntry* pEntry;
+
+ if ( ! bAdd )
+ {
+ // deselect everything before the first entry if not in
+ // adding mode
+ for ( i=0; i<nFirst; i++ )
+ {
+ pEntry = GetEntry( i );
+ if( pEntry->IsSelected() )
+ SelectEntry( pEntry, false, true );
+ }
+ }
+
+ // select everything between nFirst and nLast
+ for ( i=nFirst; i<=nLast; i++ )
+ {
+ pEntry = GetEntry( i );
+ if( ! pEntry->IsSelected() )
+ SelectEntry( pEntry, true, true );
+ }
+
+ if ( ! bAdd )
+ {
+ // deselect everything behind the last entry if not in
+ // adding mode
+ sal_uLong nEnd = GetEntryCount();
+ for ( ; i<nEnd; i++ )
+ {
+ pEntry = GetEntry( i );
+ if( pEntry->IsSelected() )
+ SelectEntry( pEntry, false, true );
+ }
+ }
+}
+
+bool SvxIconChoiceCtrl_Impl::IsOver( std::vector<tools::Rectangle>* pRectList, const tools::Rectangle& rBoundRect )
+{
+ const sal_uInt16 nCount = pRectList->size();
+ for( sal_uInt16 nCur = 0; nCur < nCount; nCur++ )
+ {
+ tools::Rectangle& rRect = (*pRectList)[ nCur ];
+ if( rBoundRect.Overlaps( rRect ))
+ return true;
+ }
+ return false;
+}
+
+void SvxIconChoiceCtrl_Impl::AddSelectedRect( SvxIconChoiceCtrlEntry* pEntry1,
+ SvxIconChoiceCtrlEntry* pEntry2 )
+{
+ DBG_ASSERT(pEntry1 && pEntry2,"SelectEntry: Invalid Entry-Ptr");
+ tools::Rectangle aRect( GetEntryBoundRect( pEntry1 ) );
+ aRect.Union( GetEntryBoundRect( pEntry2 ) );
+ AddSelectedRect( aRect );
+}
+
+void SvxIconChoiceCtrl_Impl::AddSelectedRect( const tools::Rectangle& rRect )
+{
+ tools::Rectangle newRect = rRect;
+ newRect.Justify();
+ aSelectedRectList.push_back( newRect );
+}
+
+void SvxIconChoiceCtrl_Impl::ClearSelectedRectList()
+{
+ aSelectedRectList.clear();
+}
+
+IMPL_LINK_NOARG(SvxIconChoiceCtrl_Impl, AutoArrangeHdl, Timer *, void)
+{
+ aAutoArrangeIdle.Stop();
+ Arrange( IsAutoArrange(), 0, 0 );
+}
+
+IMPL_LINK_NOARG(SvxIconChoiceCtrl_Impl, VisRectChangedHdl, Timer *, void)
+{
+ aVisRectChangedIdle.Stop();
+}
+
+IMPL_LINK_NOARG(SvxIconChoiceCtrl_Impl, DocRectChangedHdl, Timer *, void)
+{
+ aDocRectChangedIdle.Stop();
+}
+
+#ifdef DBG_UTIL
+void SvxIconChoiceCtrl_Impl::SetEntryTextMode( SvxIconChoiceCtrlTextMode eMode, SvxIconChoiceCtrlEntry* pEntry )
+{
+ if( !pEntry )
+ {
+ if( eTextMode != eMode )
+ {
+ eTextMode = eMode;
+ Arrange( true, 0, 0 );
+ }
+ }
+ else
+ {
+ if( pEntry->eTextMode != eMode )
+ {
+ pEntry->eTextMode = eMode;
+ InvalidateEntry( pEntry );
+ pView->Invalidate( GetEntryBoundRect( pEntry ) );
+ AdjustVirtSize( pEntry->aRect );
+ }
+ }
+}
+#endif
+
+// Draw my own focusrect, because the focusrect of the outputdevice has got the inverted color
+// of the background. But what will we see, if the backgroundcolor is gray ? - We will see
+// a gray focusrect on a gray background !!!
+
+void SvxIconChoiceCtrl_Impl::ShowFocus ( tools::Rectangle const & rRect )
+{
+ Color aBkgColor(pView->GetBackground().GetColor());
+ Color aPenColor;
+ sal_uInt16 nColor = ( aBkgColor.GetRed() + aBkgColor.GetGreen() + aBkgColor.GetBlue() ) / 3;
+ if (nColor > 128)
+ aPenColor = COL_BLACK;
+ else
+ aPenColor = COL_WHITE;
+
+ aFocus.aPenColor = aPenColor;
+ aFocus.aRect = rRect;
+}
+
+void SvxIconChoiceCtrl_Impl::DrawFocusRect(vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.SetLineColor(aFocus.aPenColor);
+ rRenderContext.SetFillColor();
+ tools::Polygon aPolygon (aFocus.aRect);
+
+ LineInfo aLineInfo(LineStyle::Dash);
+
+ aLineInfo.SetDashLen(1);
+ aLineInfo.SetDotLen(1);
+ aLineInfo.SetDistance(1);
+ aLineInfo.SetDotCount(1);
+
+ rRenderContext.DrawPolyLine(aPolygon, aLineInfo);
+}
+
+bool SvxIconChoiceCtrl_Impl::IsMnemonicChar( sal_Unicode cChar, sal_uLong& rPos ) const
+{
+ bool bRet = false;
+ const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
+ size_t nEntryCount = GetEntryCount();
+ for ( size_t i = 0; i < nEntryCount; ++i )
+ {
+ if ( rI18nHelper.MatchMnemonic( GetEntry( i )->GetText(), cChar ) )
+ {
+ bRet = true;
+ rPos = i;
+ break;
+ }
+ }
+
+ return bRet;
+}
+
+
+IMPL_LINK(SvxIconChoiceCtrl_Impl, UserEventHdl, void*, nId, void )
+{
+ if( nId == EVENTID_ADJUST_SCROLLBARS )
+ {
+ nUserEventAdjustScrBars = nullptr;
+ AdjustScrollBars();
+ }
+ else if( nId == EVENTID_SHOW_CURSOR )
+ {
+ ShowCursor( true );
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::CancelUserEvents()
+{
+ if( nUserEventAdjustScrBars )
+ {
+ Application::RemoveUserEvent( nUserEventAdjustScrBars );
+ nUserEventAdjustScrBars = nullptr;
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::InvalidateEntry( SvxIconChoiceCtrlEntry* pEntry )
+{
+ if( pEntry == pCursor )
+ ShowCursor( false );
+ pView->Invalidate( pEntry->aRect );
+ Center( pEntry );
+ pView->Invalidate( pEntry->aRect );
+ if( pEntry == pCursor )
+ ShowCursor( true );
+}
+
+SvxIconChoiceCtrlEntry* SvxIconChoiceCtrl_Impl::GetFirstSelectedEntry() const
+{
+ if( !GetSelectionCount() )
+ return nullptr;
+
+ if( (nWinBits & WB_HIGHLIGHTFRAME) && (eSelectionMode == SelectionMode::NONE) )
+ {
+ return pCurHighlightFrame;
+ }
+
+ size_t nCount = maEntries.size();
+ if( !pHead )
+ {
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maEntries[ nCur ].get();
+ if( pEntry->IsSelected() )
+ {
+ return pEntry;
+ }
+ }
+ }
+ else
+ {
+ SvxIconChoiceCtrlEntry* pEntry = pHead;
+ while( nCount-- )
+ {
+ if( pEntry->IsSelected() )
+ {
+ return pEntry;
+ }
+ pEntry = pEntry->pflink;
+ if( nCount && pEntry == pHead )
+ {
+ OSL_FAIL("SvxIconChoiceCtrl_Impl::GetFirstSelectedEntry > infinite loop!");
+ return nullptr;
+ }
+ }
+ }
+ return nullptr;
+}
+
+void SvxIconChoiceCtrl_Impl::SelectAll()
+{
+ size_t nCount = maEntries.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maEntries[ nCur ].get();
+ SelectEntry( pEntry, true/*bSelect*/, true );
+ }
+ nFlags &= ~IconChoiceFlags::AddMode;
+ pAnchor = nullptr;
+}
+
+
+
+
+sal_Int32 SvxIconChoiceCtrl_Impl::GetEntryListPos( SvxIconChoiceCtrlEntry const * pEntry ) const
+{
+ if( !(nFlags & IconChoiceFlags::EntryListPosValid ))
+ const_cast<SvxIconChoiceCtrl_Impl*>(this)->SetListPositions();
+ return pEntry->nPos;
+}
+
+void SvxIconChoiceCtrl_Impl::InitSettings()
+{
+ const StyleSettings& rStyleSettings = pView->GetSettings().GetStyleSettings();
+
+ // unit (from settings) is Point
+ vcl::Font aFont( rStyleSettings.GetFieldFont() );
+ aFont.SetColor( rStyleSettings.GetWindowTextColor() );
+ pView->SetPointFont( aFont );
+ SetDefaultTextSize();
+
+ pView->SetTextColor( rStyleSettings.GetFieldTextColor() );
+ pView->SetTextFillColor();
+
+ pView->SetBackground( rStyleSettings.GetFieldColor());
+
+ tools::Long nScrBarSize = rStyleSettings.GetScrollBarSize();
+ if( nScrBarSize == nHorSBarHeight && nScrBarSize == nVerSBarWidth )
+ return;
+
+ nHorSBarHeight = nScrBarSize;
+ Size aSize( aHorSBar->GetSizePixel() );
+ aSize.setHeight( nScrBarSize );
+ aHorSBar->Hide();
+ aHorSBar->SetSizePixel( aSize );
+
+ nVerSBarWidth = nScrBarSize;
+ aSize = aVerSBar->GetSizePixel();
+ aSize.setWidth( nScrBarSize );
+ aVerSBar->Hide();
+ aVerSBar->SetSizePixel( aSize );
+
+ Size aOSize( pView->Control::GetOutputSizePixel() );
+ PositionScrollBars( aOSize.Width(), aOSize.Height() );
+ AdjustScrollBars();
+}
+
+void SvxIconChoiceCtrl_Impl::SetPositionMode( SvxIconChoiceCtrlPositionMode eMode )
+{
+ if( eMode == ePositionMode )
+ return;
+
+ SvxIconChoiceCtrlPositionMode eOldMode = ePositionMode;
+ ePositionMode = eMode;
+ size_t nCount = maEntries.size();
+
+ if( eOldMode == SvxIconChoiceCtrlPositionMode::AutoArrange )
+ {
+ // when positioning moved entries "hard", there are problems with
+ // unwanted overlaps, as these entries aren't taken into account in
+ // Arrange.
+ if( maEntries.size() )
+ aAutoArrangeIdle.Start();
+ return;
+ }
+
+ if( ePositionMode == SvxIconChoiceCtrlPositionMode::AutoArrange )
+ {
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maEntries[ nCur ].get();
+ if( pEntry->GetFlags() & SvxIconViewFlags(SvxIconViewFlags::POS_LOCKED | SvxIconViewFlags::POS_MOVED))
+ SetEntryPos(pEntry, GetEntryBoundRect( pEntry ).TopLeft());
+ }
+
+ if( maEntries.size() )
+ aAutoArrangeIdle.Start();
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::SetEntryPredecessor( SvxIconChoiceCtrlEntry* pEntry,
+ SvxIconChoiceCtrlEntry* pPredecessor )
+{
+ if( !IsAutoArrange() )
+ return;
+
+ if( pEntry == pPredecessor )
+ return;
+
+ sal_uLong nPos1 = GetEntryListPos( pEntry );
+ if( !pHead )
+ {
+ if( pPredecessor )
+ {
+ sal_uLong nPos2 = GetEntryListPos( pPredecessor );
+ if( nPos1 == (nPos2 + 1) )
+ return; // is already the predecessor
+ }
+ else if( !nPos1 )
+ return;
+
+ InitPredecessors();
+ }
+
+ if( !pPredecessor && pHead == pEntry )
+ return; // is already the first one
+
+ bool bSetHead = false;
+ if( !pPredecessor )
+ {
+ bSetHead = true;
+ pPredecessor = pHead->pblink;
+ }
+ if( pEntry == pHead )
+ {
+ pHead = pHead->pflink;
+ bSetHead = false;
+ }
+ if( pEntry != pPredecessor )
+ {
+ pEntry->Unlink();
+ pEntry->SetBacklink( pPredecessor );
+ }
+ if( bSetHead )
+ pHead = pEntry;
+ aAutoArrangeIdle.Start();
+}
+
+SvxIconChoiceCtrlEntry* SvxIconChoiceCtrl_Impl::FindEntryPredecessor( SvxIconChoiceCtrlEntry* pEntry,
+ const Point& rPosTopLeft )
+{
+ Point aPos( rPosTopLeft ); //TopLeft
+ tools::Rectangle aCenterRect( CalcBmpRect( pEntry, &aPos ));
+ Point aNewPos( aCenterRect.Center() );
+ GridId nGrid = GetPredecessorGrid( aNewPos );
+ size_t nCount = maEntries.size();
+ if( nGrid == GRID_NOT_FOUND )
+ return nullptr;
+ if( nGrid >= nCount )
+ nGrid = nCount - 1;
+ if( !pHead )
+ return maEntries[ nGrid ].get();
+
+ SvxIconChoiceCtrlEntry* pCur = pHead; // Grid 0
+ // TODO: go through list from the end if nGrid > nCount/2
+ for( GridId nCur = 0; nCur < nGrid; nCur++ )
+ pCur = pCur->pflink;
+
+ return pCur;
+}
+
+GridId SvxIconChoiceCtrl_Impl::GetPredecessorGrid( const Point& rPos) const
+{
+ Point aPos( rPos );
+ aPos.AdjustX( -(LROFFS_WINBORDER) );
+ aPos.AdjustY( -(TBOFFS_WINBORDER) );
+ tools::Long nMaxCol = aVirtOutputSize.Width() / nGridDX;
+ if( nMaxCol )
+ nMaxCol--;
+ tools::Long nGridX = aPos.X() / nGridDX;
+ if( nGridX > nMaxCol )
+ nGridX = nMaxCol;
+ tools::Long nGridY = aPos.Y() / nGridDY;
+ tools::Long nGridsX = aOutputSize.Width() / nGridDX;
+ GridId nGrid = (nGridY * nGridsX) + nGridX;
+ tools::Long nMiddle = (nGridX * nGridDX) + (nGridDX / 2);
+ if( rPos.X() < nMiddle )
+ {
+ if( !nGrid )
+ nGrid = GRID_NOT_FOUND;
+ else
+ nGrid--;
+ }
+ return nGrid;
+}
+
+bool SvxIconChoiceCtrl_Impl::RequestHelp( const HelpEvent& rHEvt )
+{
+ if ( !(rHEvt.GetMode() & HelpEventMode::QUICK ) )
+ return false;
+
+ Point aPos( pView->ScreenToOutputPixel(rHEvt.GetMousePosPixel() ) );
+ aPos -= pView->GetMapMode().GetOrigin();
+ SvxIconChoiceCtrlEntry* pEntry = GetEntry( aPos, true );
+
+ if ( !pEntry )
+ return false;
+
+ OUString sQuickHelpText = pEntry->GetQuickHelpText();
+ OUString aEntryText( SvtIconChoiceCtrl::GetEntryText( pEntry ) );
+ tools::Rectangle aTextRect( CalcTextRect( pEntry, nullptr, &aEntryText ) );
+ if ( ( !aTextRect.Contains( aPos ) || aEntryText.isEmpty() ) && sQuickHelpText.isEmpty() )
+ return false;
+
+ tools::Rectangle aOptTextRect( aTextRect );
+ aOptTextRect.SetBottom( LONG_MAX );
+ DrawTextFlags nNewFlags = nCurTextDrawFlags;
+ nNewFlags &= ~DrawTextFlags( DrawTextFlags::Clip | DrawTextFlags::EndEllipsis );
+ aOptTextRect = pView->GetTextRect( aOptTextRect, aEntryText, nNewFlags );
+ if ( aOptTextRect != aTextRect || !sQuickHelpText.isEmpty() )
+ {
+ //aTextRect.Right() = aTextRect.Left() + aRealSize.Width() + 4;
+ Point aPt( aOptTextRect.TopLeft() );
+ aPt += pView->GetMapMode().GetOrigin();
+ aPt = pView->OutputToScreenPixel( aPt );
+ // subtract border of tooltip help
+ aPt.AdjustY( -1 );
+ aPt.AdjustX( -3 );
+ aOptTextRect.SetPos( aPt );
+ OUString sHelpText;
+ if ( !sQuickHelpText.isEmpty() )
+ sHelpText = sQuickHelpText;
+ else
+ sHelpText = aEntryText;
+ Help::ShowQuickHelp( static_cast<vcl::Window*>(pView), aOptTextRect, sHelpText, QuickHelpFlags::Left | QuickHelpFlags::VCenter );
+ }
+
+ return true;
+}
+
+void SvxIconChoiceCtrl_Impl::SetColumn( sal_uInt16 nIndex, const SvxIconChoiceCtrlColumnInfo& rInfo)
+{
+ if (!m_pColumns)
+ m_pColumns.reset(new SvxIconChoiceCtrlColumnInfoMap);
+
+ SvxIconChoiceCtrlColumnInfo* pInfo = new SvxIconChoiceCtrlColumnInfo( rInfo );
+ m_pColumns->insert(std::make_pair(nIndex, std::unique_ptr<SvxIconChoiceCtrlColumnInfo>(pInfo)));
+
+ // HACK: Detail mode is not yet fully implemented, this workaround makes it
+ // fly with a single column
+ if( !nIndex && (nWinBits & WB_DETAILS) )
+ nGridDX = pInfo->GetWidth();
+
+ if( GetUpdateMode() )
+ Arrange( IsAutoArrange(), 0, 0 );
+}
+
+const SvxIconChoiceCtrlColumnInfo* SvxIconChoiceCtrl_Impl::GetColumn( sal_uInt16 nIndex ) const
+{
+ if (!m_pColumns)
+ return nullptr;
+ auto const it = m_pColumns->find( nIndex );
+ if (it == m_pColumns->end())
+ return nullptr;
+ return it->second.get();
+}
+
+void SvxIconChoiceCtrl_Impl::DrawHighlightFrame(vcl::RenderContext& rRenderContext, const tools::Rectangle& rBmpRect)
+{
+ tools::Rectangle aBmpRect(rBmpRect);
+ tools::Long nBorder = 2;
+ if (aImageSize.Width() < 32)
+ nBorder = 1;
+ aBmpRect.AdjustRight(nBorder );
+ aBmpRect.AdjustLeft( -nBorder );
+ aBmpRect.AdjustBottom(nBorder );
+ aBmpRect.AdjustTop( -nBorder );
+
+ DecorationView aDecoView(&rRenderContext);
+ DrawHighlightFrameStyle nDecoFlags;
+ if (bHighlightFramePressed)
+ nDecoFlags = DrawHighlightFrameStyle::In;
+ else
+ nDecoFlags = DrawHighlightFrameStyle::Out;
+ aDecoView.DrawHighlightFrame(aBmpRect, nDecoFlags);
+}
+
+void SvxIconChoiceCtrl_Impl::SetEntryHighlightFrame( SvxIconChoiceCtrlEntry* pEntry,
+ bool bKeepHighlightFlags )
+{
+ if( pEntry == pCurHighlightFrame )
+ return;
+
+ if( !bKeepHighlightFlags )
+ bHighlightFramePressed = false;
+
+ if (pCurHighlightFrame)
+ {
+ tools::Rectangle aInvalidationRect(GetEntryBoundRect(pCurHighlightFrame));
+ aInvalidationRect.expand(5);
+ pCurHighlightFrame = nullptr;
+ pView->Invalidate(aInvalidationRect);
+ }
+
+ pCurHighlightFrame = pEntry;
+ if (pEntry)
+ {
+ tools::Rectangle aInvalidationRect(GetEntryBoundRect(pEntry));
+ aInvalidationRect.expand(5);
+ pView->Invalidate(aInvalidationRect);
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::CallSelectHandler()
+{
+ // When single-click mode is active, the selection handler should be called
+ // synchronously, as the selection is automatically taken away once the
+ // mouse cursor doesn't touch the object any more. Else, we might run into
+ // missing calls to Select if the object is selected from a mouse movement,
+ // because when starting the timer, the mouse cursor might have already left
+ // the object.
+ // In special cases (=>SfxFileDialog!), synchronous calls can be forced via
+ // WB_NOASYNCSELECTHDL.
+ if( nWinBits & (WB_NOASYNCSELECTHDL | WB_HIGHLIGHTFRAME) )
+ {
+ pHdlEntry = nullptr;
+ pView->ClickIcon();
+ //pView->Select();
+ }
+ else
+ aCallSelectHdlIdle.Start();
+}
+
+IMPL_LINK_NOARG(SvxIconChoiceCtrl_Impl, CallSelectHdlHdl, Timer *, void)
+{
+ pHdlEntry = nullptr;
+ pView->ClickIcon();
+ //pView->Select();
+}
+
+void SvxIconChoiceCtrl_Impl::SetOrigin( const Point& rPos )
+{
+ MapMode aMapMode( pView->GetMapMode() );
+ aMapMode.SetOrigin( rPos );
+ pView->SetMapMode( aMapMode );
+}
+
+void SvxIconChoiceCtrl_Impl::CallEventListeners( VclEventId nEvent, void* pData )
+{
+ pView->CallImplEventListeners( nEvent, pData );
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/imivctl2.cxx b/vcl/source/control/imivctl2.cxx
new file mode 100644
index 000000000..5b0d2d8ad
--- /dev/null
+++ b/vcl/source/control/imivctl2.cxx
@@ -0,0 +1,715 @@
+/* -*- 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 "imivctl.hxx"
+#include <sal/log.hxx>
+
+IcnCursor_Impl::IcnCursor_Impl( SvxIconChoiceCtrl_Impl* pOwner )
+{
+ pView = pOwner;
+ pCurEntry = nullptr;
+ nDeltaWidth = 0;
+ nDeltaHeight= 0;
+ nCols = 0;
+ nRows = 0;
+}
+
+IcnCursor_Impl::~IcnCursor_Impl()
+{
+}
+
+sal_uInt16 IcnCursor_Impl::GetSortListPos( SvxIconChoiceCtrlEntryPtrVec& rList, tools::Long nValue,
+ bool bVertical )
+{
+ sal_uInt16 nCount = rList.size();
+ if( !nCount )
+ return 0;
+
+ sal_uInt16 nCurPos = 0;
+ tools::Long nPrevValue = LONG_MIN;
+ while( nCount )
+ {
+ const tools::Rectangle& rRect = pView->GetEntryBoundRect( rList[nCurPos] );
+ tools::Long nCurValue;
+ if( bVertical )
+ nCurValue = rRect.Top();
+ else
+ nCurValue = rRect.Left();
+ if( nValue >= nPrevValue && nValue <= nCurValue )
+ return nCurPos;
+ nPrevValue = nCurValue;
+ nCount--;
+ nCurPos++;
+ }
+ return rList.size();
+}
+
+void IcnCursor_Impl::ImplCreate()
+{
+ pView->CheckBoundingRects();
+ DBG_ASSERT(xColumns==nullptr&&xRows==nullptr,"ImplCreate: Not cleared");
+
+ SetDeltas();
+
+ xColumns.reset(new IconChoiceMap);
+ xRows.reset(new IconChoiceMap);
+
+ size_t nCount = pView->maEntries.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = pView->maEntries[ nCur ].get();
+ // const Rectangle& rRect = pView->GetEntryBoundRect( pEntry );
+ tools::Rectangle rRect( pView->CalcBmpRect( pEntry ) );
+ short nY = static_cast<short>( ((rRect.Top()+rRect.Bottom())/2) / nDeltaHeight );
+ short nX = static_cast<short>( ((rRect.Left()+rRect.Right())/2) / nDeltaWidth );
+
+ // capture rounding errors
+ if( nY >= nRows )
+ nY = sal::static_int_cast< short >(nRows - 1);
+ if( nX >= nCols )
+ nX = sal::static_int_cast< short >(nCols - 1);
+
+ SvxIconChoiceCtrlEntryPtrVec& rColEntry = (*xColumns)[nX];
+ sal_uInt16 nIns = GetSortListPos( rColEntry, rRect.Top(), true );
+ rColEntry.insert( rColEntry.begin() + nIns, pEntry );
+
+ SvxIconChoiceCtrlEntryPtrVec& rRowEntry = (*xRows)[nY];
+ nIns = GetSortListPos( rRowEntry, rRect.Left(), false );
+ rRowEntry.insert( rRowEntry.begin() + nIns, pEntry );
+
+ pEntry->nX = nX;
+ pEntry->nY = nY;
+ }
+}
+
+
+void IcnCursor_Impl::Clear()
+{
+ if( xColumns )
+ {
+ xColumns.reset();
+ xRows.reset();
+ pCurEntry = nullptr;
+ nDeltaWidth = 0;
+ nDeltaHeight = 0;
+ }
+}
+
+SvxIconChoiceCtrlEntry* IcnCursor_Impl::SearchCol(sal_uInt16 nCol, sal_uInt16 nTop, sal_uInt16 nBottom,
+ bool bDown, bool bSimple )
+{
+ DBG_ASSERT(pCurEntry, "SearchCol: No reference entry");
+ IconChoiceMap::iterator mapIt = xColumns->find( nCol );
+ if ( mapIt == xColumns->end() )
+ return nullptr;
+ SvxIconChoiceCtrlEntryPtrVec const & rList = mapIt->second;
+ const sal_uInt16 nCount = rList.size();
+ if( !nCount )
+ return nullptr;
+
+ const tools::Rectangle& rRefRect = pView->GetEntryBoundRect(pCurEntry);
+
+ if( bSimple )
+ {
+ SvxIconChoiceCtrlEntryPtrVec::const_iterator it = std::find( rList.begin(), rList.end(), pCurEntry );
+
+ assert(it != rList.end()); //Entry not in Col-List
+ if (it == rList.end())
+ return nullptr;
+
+ if( bDown )
+ {
+ while( ++it != rList.end() )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = *it;
+ const tools::Rectangle& rRect = pView->GetEntryBoundRect( pEntry );
+ if( rRect.Top() > rRefRect.Top() )
+ return pEntry;
+ }
+ return nullptr;
+ }
+ else
+ {
+ SvxIconChoiceCtrlEntryPtrVec::const_reverse_iterator it2(it);
+ while (it2 != rList.rend())
+ {
+ SvxIconChoiceCtrlEntry* pEntry = *it2;
+ const tools::Rectangle& rRect = pView->GetEntryBoundRect( pEntry );
+ if( rRect.Top() < rRefRect.Top() )
+ return pEntry;
+ ++it2;
+ }
+ return nullptr;
+ }
+ }
+
+ if( nTop > nBottom )
+ std::swap(nTop, nBottom);
+
+ tools::Long nMinDistance = LONG_MAX;
+ SvxIconChoiceCtrlEntry* pResult = nullptr;
+ for( sal_uInt16 nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = rList[ nCur ];
+ if( pEntry != pCurEntry )
+ {
+ sal_uInt16 nY = pEntry->nY;
+ if( nY >= nTop && nY <= nBottom )
+ {
+ const tools::Rectangle& rRect = pView->GetEntryBoundRect( pEntry );
+ tools::Long nDistance = rRect.Top() - rRefRect.Top();
+ if( nDistance < 0 )
+ nDistance *= -1;
+ if( nDistance && nDistance < nMinDistance )
+ {
+ nMinDistance = nDistance;
+ pResult = pEntry;
+ }
+ }
+ }
+ }
+ return pResult;
+}
+
+SvxIconChoiceCtrlEntry* IcnCursor_Impl::SearchRow(sal_uInt16 nRow, sal_uInt16 nLeft, sal_uInt16 nRight,
+ bool bRight, bool bSimple )
+{
+ DBG_ASSERT(pCurEntry,"SearchRow: No reference entry");
+ IconChoiceMap::iterator mapIt = xRows->find( nRow );
+ if ( mapIt == xRows->end() )
+ return nullptr;
+ SvxIconChoiceCtrlEntryPtrVec const & rList = mapIt->second;
+ const sal_uInt16 nCount = rList.size();
+ if( !nCount )
+ return nullptr;
+
+ const tools::Rectangle& rRefRect = pView->GetEntryBoundRect(pCurEntry);
+
+ if( bSimple )
+ {
+ SvxIconChoiceCtrlEntryPtrVec::const_iterator it = std::find( rList.begin(), rList.end(), pCurEntry );
+
+ assert(it != rList.end()); //Entry not in Row-List
+ if (it == rList.end())
+ return nullptr;
+
+ if( bRight )
+ {
+ while( ++it != rList.end() )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = *it;
+ const tools::Rectangle& rRect = pView->GetEntryBoundRect( pEntry );
+ if( rRect.Left() > rRefRect.Left() )
+ return pEntry;
+ }
+ return nullptr;
+ }
+ else
+ {
+ SvxIconChoiceCtrlEntryPtrVec::const_reverse_iterator it2(it);
+ while (it2 != rList.rend())
+ {
+ SvxIconChoiceCtrlEntry* pEntry = *it2;
+ const tools::Rectangle& rRect = pView->GetEntryBoundRect( pEntry );
+ if( rRect.Left() < rRefRect.Left() )
+ return pEntry;
+ ++it2;
+ }
+ return nullptr;
+ }
+
+ }
+ if( nRight < nLeft )
+ std::swap(nRight, nLeft);
+
+ tools::Long nMinDistance = LONG_MAX;
+ SvxIconChoiceCtrlEntry* pResult = nullptr;
+ for( sal_uInt16 nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = rList[ nCur ];
+ if( pEntry != pCurEntry )
+ {
+ sal_uInt16 nX = pEntry->nX;
+ if( nX >= nLeft && nX <= nRight )
+ {
+ const tools::Rectangle& rRect = pView->GetEntryBoundRect( pEntry );
+ tools::Long nDistance = rRect.Left() - rRefRect.Left();
+ if( nDistance < 0 )
+ nDistance *= -1;
+ if( nDistance && nDistance < nMinDistance )
+ {
+ nMinDistance = nDistance;
+ pResult = pEntry;
+ }
+ }
+ }
+ }
+ return pResult;
+}
+
+
+/*
+ Searches, starting from the passed value, the next entry to the left/to the
+ right. Example for bRight = sal_True:
+
+ c
+ b c
+ a b c
+ S 1 1 1 ====> search direction
+ a b c
+ b c
+ c
+
+ S : starting position
+ 1 : first searched rectangle
+ a,b,c : 2nd, 3rd, 4th searched rectangle
+*/
+
+SvxIconChoiceCtrlEntry* IcnCursor_Impl::GoLeftRight( SvxIconChoiceCtrlEntry* pCtrlEntry, bool bRight )
+{
+ SvxIconChoiceCtrlEntry* pResult;
+ pCurEntry = pCtrlEntry;
+ Create();
+ sal_uInt16 nY = pCtrlEntry->nY;
+ sal_uInt16 nX = pCtrlEntry->nX;
+ DBG_ASSERT(nY< nRows,"GoLeftRight:Bad column");
+ DBG_ASSERT(nX< nCols,"GoLeftRight:Bad row");
+ // neighbor in same row?
+ if( bRight )
+ pResult = SearchRow(
+ nY, nX, sal::static_int_cast< sal_uInt16 >(nCols-1), true, true );
+ else
+ pResult = SearchRow( nY, 0, nX, false, true );
+ if( pResult )
+ return pResult;
+
+ tools::Long nCurCol = nX;
+
+ tools::Long nColOffs, nLastCol;
+ if( bRight )
+ {
+ nColOffs = 1;
+ nLastCol = nCols;
+ }
+ else
+ {
+ nColOffs = -1;
+ nLastCol = -1; // 0-1
+ }
+
+ sal_uInt16 nRowMin = nY;
+ sal_uInt16 nRowMax = nY;
+ do
+ {
+ SvxIconChoiceCtrlEntry* pEntry = SearchCol(static_cast<sal_uInt16>(nCurCol), nRowMin, nRowMax, true, false);
+ if( pEntry )
+ return pEntry;
+ if( nRowMin )
+ nRowMin--;
+ if( nRowMax < (nRows-1))
+ nRowMax++;
+ nCurCol += nColOffs;
+ } while( nCurCol != nLastCol );
+ return nullptr;
+}
+
+SvxIconChoiceCtrlEntry* IcnCursor_Impl::GoPageUpDown( SvxIconChoiceCtrlEntry* pStart, bool bDown)
+{
+ if( pView->IsAutoArrange() && !(pView->nWinBits & WB_ALIGN_TOP) )
+ {
+ const tools::Long nPos = static_cast<tools::Long>(pView->GetEntryListPos( pStart ));
+ tools::Long nEntriesInView = pView->aOutputSize.Height() / pView->nGridDY;
+ nEntriesInView *=
+ ((pView->aOutputSize.Width()+(pView->nGridDX/2)) / pView->nGridDX );
+ tools::Long nNewPos = nPos;
+ if( bDown )
+ {
+ nNewPos += nEntriesInView;
+ if( nNewPos >= static_cast<tools::Long>(pView->maEntries.size()) )
+ nNewPos = pView->maEntries.size() - 1;
+ }
+ else
+ {
+ nNewPos -= nEntriesInView;
+ if( nNewPos < 0 )
+ nNewPos = 0;
+ }
+ if( nPos != nNewPos )
+ return pView->maEntries[ static_cast<size_t>(nNewPos) ].get();
+ return nullptr;
+ }
+ tools::Long nOpt = pView->GetEntryBoundRect( pStart ).Top();
+ if( bDown )
+ {
+ nOpt += pView->aOutputSize.Height();
+ nOpt -= pView->nGridDY;
+ }
+ else
+ {
+ nOpt -= pView->aOutputSize.Height();
+ nOpt += pView->nGridDY;
+ }
+ if( nOpt < 0 )
+ nOpt = 0;
+
+ tools::Long nPrevErr = LONG_MAX;
+
+ SvxIconChoiceCtrlEntry* pPrev = pStart;
+ SvxIconChoiceCtrlEntry* pNext = GoUpDown( pStart, bDown );
+ while( pNext )
+ {
+ tools::Long nCur = pView->GetEntryBoundRect( pNext ).Top();
+ tools::Long nErr = nOpt - nCur;
+ if( nErr < 0 )
+ nErr *= -1;
+ if( nErr > nPrevErr )
+ return pPrev;
+ nPrevErr = nErr;
+ pPrev = pNext;
+ pNext = GoUpDown( pNext, bDown );
+ }
+ if( pPrev != pStart )
+ return pPrev;
+ return nullptr;
+}
+
+SvxIconChoiceCtrlEntry* IcnCursor_Impl::GoUpDown( SvxIconChoiceCtrlEntry* pCtrlEntry, bool bDown)
+{
+ if( pView->IsAutoArrange() && !(pView->nWinBits & WB_ALIGN_TOP) )
+ {
+ sal_uLong nPos = pView->GetEntryListPos( pCtrlEntry );
+ if( bDown && nPos < (pView->maEntries.size() - 1) )
+ return pView->maEntries[ nPos + 1 ].get();
+ else if( !bDown && nPos > 0 )
+ return pView->maEntries[ nPos - 1 ].get();
+ return nullptr;
+ }
+
+ SvxIconChoiceCtrlEntry* pResult;
+ pCurEntry = pCtrlEntry;
+ Create();
+ sal_uInt16 nY = pCtrlEntry->nY;
+ sal_uInt16 nX = pCtrlEntry->nX;
+ DBG_ASSERT(nY<nRows,"GoUpDown:Bad column");
+ DBG_ASSERT(nX<nCols,"GoUpDown:Bad row");
+
+ // neighbor in same column?
+ if( bDown )
+ pResult = SearchCol(
+ nX, nY, sal::static_int_cast< sal_uInt16 >(nRows-1), true, true );
+ else
+ pResult = SearchCol( nX, 0, nY, false, true );
+ if( pResult )
+ return pResult;
+
+ tools::Long nCurRow = nY;
+
+ tools::Long nRowOffs, nLastRow;
+ if( bDown )
+ {
+ nRowOffs = 1;
+ nLastRow = nRows;
+ }
+ else
+ {
+ nRowOffs = -1;
+ nLastRow = -1; // 0-1
+ }
+
+ sal_uInt16 nColMin = nX;
+ sal_uInt16 nColMax = nX;
+ do
+ {
+ SvxIconChoiceCtrlEntry* pEntry = SearchRow(static_cast<sal_uInt16>(nCurRow), nColMin, nColMax, true, false);
+ if( pEntry )
+ return pEntry;
+ if( nColMin )
+ nColMin--;
+ if( nColMax < (nCols-1))
+ nColMax++;
+ nCurRow += nRowOffs;
+ } while( nCurRow != nLastRow );
+ return nullptr;
+}
+
+void IcnCursor_Impl::SetDeltas()
+{
+ const Size& rSize = pView->aVirtOutputSize;
+ nCols = rSize.Width() / pView->nGridDX;
+ if( !nCols )
+ nCols = 1;
+ nRows = rSize.Height() / pView->nGridDY;
+ if( (nRows * pView->nGridDY) < rSize.Height() )
+ nRows++;
+ if( !nRows )
+ nRows = 1;
+
+ nDeltaWidth = static_cast<short>(rSize.Width() / nCols);
+ nDeltaHeight = static_cast<short>(rSize.Height() / nRows);
+ if( !nDeltaHeight )
+ {
+ nDeltaHeight = 1;
+ SAL_INFO("vcl", "SetDeltas:Bad height");
+ }
+ if( !nDeltaWidth )
+ {
+ nDeltaWidth = 1;
+ SAL_INFO("vcl", "SetDeltas:Bad width");
+ }
+}
+
+IcnGridMap_Impl::IcnGridMap_Impl(SvxIconChoiceCtrl_Impl* pView)
+ : _pView(pView), _nGridCols(0), _nGridRows(0)
+{
+}
+
+IcnGridMap_Impl::~IcnGridMap_Impl()
+{
+}
+
+void IcnGridMap_Impl::Expand()
+{
+ if( !_pGridMap )
+ Create_Impl();
+ else
+ {
+ sal_uInt16 nNewGridRows = _nGridRows;
+ sal_uInt16 nNewGridCols = _nGridCols;
+ if( _pView->nWinBits & WB_ALIGN_TOP )
+ nNewGridRows += 50;
+ else
+ nNewGridCols += 50;
+
+ size_t nNewCellCount = static_cast<size_t>(nNewGridRows) * nNewGridCols;
+ bool* pNewGridMap = new bool[nNewCellCount];
+ size_t nOldCellCount = static_cast<size_t>(_nGridRows) * _nGridCols;
+ memcpy(pNewGridMap, _pGridMap.get(), nOldCellCount * sizeof(bool));
+ memset(pNewGridMap + nOldCellCount, 0, (nNewCellCount-nOldCellCount) * sizeof(bool));
+ _pGridMap.reset( pNewGridMap );
+ _nGridRows = nNewGridRows;
+ _nGridCols = nNewGridCols;
+ }
+}
+
+void IcnGridMap_Impl::Create_Impl()
+{
+ DBG_ASSERT(!_pGridMap,"Unnecessary call to IcnGridMap_Impl::Create_Impl()");
+ if( _pGridMap )
+ return;
+ GetMinMapSize( _nGridCols, _nGridRows );
+ if( _pView->nWinBits & WB_ALIGN_TOP )
+ _nGridRows += 50; // avoid resize of gridmap too often
+ else
+ _nGridCols += 50;
+
+ size_t nCellCount = static_cast<size_t>(_nGridRows) * _nGridCols;
+ _pGridMap.reset( new bool[nCellCount] );
+ memset(_pGridMap.get(), 0, nCellCount * sizeof(bool));
+
+ const size_t nCount = _pView->maEntries.size();
+ for( size_t nCur=0; nCur < nCount; nCur++ )
+ OccupyGrids( _pView->maEntries[ nCur ].get() );
+}
+
+void IcnGridMap_Impl::GetMinMapSize( sal_uInt16& rDX, sal_uInt16& rDY ) const
+{
+ tools::Long nX, nY;
+ if( _pView->nWinBits & WB_ALIGN_TOP )
+ {
+ // The view grows in vertical direction. Its max. width is _pView->nMaxVirtWidth
+ nX = _pView->nMaxVirtWidth;
+ if( !nX )
+ nX = _pView->pView->GetOutputSizePixel().Width();
+ if( !(_pView->nFlags & IconChoiceFlags::Arranging) )
+ nX -= _pView->nVerSBarWidth;
+
+ nY = _pView->aVirtOutputSize.Height();
+ }
+ else
+ {
+ // The view grows in horizontal direction. Its max. height is _pView->nMaxVirtHeight
+ nY = _pView->nMaxVirtHeight;
+ if( !nY )
+ nY = _pView->pView->GetOutputSizePixel().Height();
+ if( !(_pView->nFlags & IconChoiceFlags::Arranging) )
+ nY -= _pView->nHorSBarHeight;
+ nX = _pView->aVirtOutputSize.Width();
+ }
+
+ if( !nX )
+ nX = DEFAULT_MAX_VIRT_WIDTH;
+ if( !nY )
+ nY = DEFAULT_MAX_VIRT_HEIGHT;
+
+ tools::Long nDX = nX / _pView->nGridDX;
+ tools::Long nDY = nY / _pView->nGridDY;
+
+ if( !nDX )
+ nDX++;
+ if( !nDY )
+ nDY++;
+
+ rDX = static_cast<sal_uInt16>(nDX);
+ rDY = static_cast<sal_uInt16>(nDY);
+}
+
+GridId IcnGridMap_Impl::GetGrid( sal_uInt16 nGridX, sal_uInt16 nGridY )
+{
+ Create();
+ if( _pView->nWinBits & WB_ALIGN_TOP )
+ return nGridX + ( static_cast<GridId>(nGridY) * _nGridCols );
+ else
+ return nGridY + ( static_cast<GridId>(nGridX) * _nGridRows );
+}
+
+GridId IcnGridMap_Impl::GetGrid( const Point& rDocPos )
+{
+ Create();
+
+ tools::Long nX = rDocPos.X();
+ tools::Long nY = rDocPos.Y();
+ nX -= LROFFS_WINBORDER;
+ nY -= TBOFFS_WINBORDER;
+ nX /= _pView->nGridDX;
+ nY /= _pView->nGridDY;
+ if( nX >= _nGridCols )
+ {
+ nX = _nGridCols - 1;
+ }
+ if( nY >= _nGridRows )
+ {
+ nY = _nGridRows - 1;
+ }
+ GridId nId = GetGrid( static_cast<sal_uInt16>(nX), static_cast<sal_uInt16>(nY) );
+ DBG_ASSERT(nId <o3tl::make_unsigned(_nGridCols*_nGridRows),"GetGrid failed");
+ return nId;
+}
+
+tools::Rectangle IcnGridMap_Impl::GetGridRect( GridId nId )
+{
+ Create();
+ sal_uInt16 nGridX, nGridY;
+ GetGridCoord( nId, nGridX, nGridY );
+ const tools::Long nLeft = nGridX * _pView->nGridDX+ LROFFS_WINBORDER;
+ const tools::Long nTop = nGridY * _pView->nGridDY + TBOFFS_WINBORDER;
+ return tools::Rectangle(
+ nLeft, nTop,
+ nLeft + _pView->nGridDX,
+ nTop + _pView->nGridDY );
+}
+
+GridId IcnGridMap_Impl::GetUnoccupiedGrid()
+{
+ Create();
+ sal_uLong nStart = 0;
+ bool bExpanded = false;
+
+ while( true )
+ {
+ const sal_uLong nCount = static_cast<sal_uInt16>(_nGridCols * _nGridRows);
+ for( sal_uLong nCur = nStart; nCur < nCount; nCur++ )
+ {
+ if( !_pGridMap[ nCur ] )
+ {
+ _pGridMap[ nCur ] = true;
+ return static_cast<GridId>(nCur);
+ }
+ }
+ DBG_ASSERT(!bExpanded,"ExpandGrid failed");
+ if( bExpanded )
+ return 0; // prevent never ending loop
+ bExpanded = true;
+ Expand();
+ nStart = nCount;
+ }
+}
+
+// An entry only means that there's a GridRect lying under its center. This
+// variant is much faster than allocating via the bounding rectangle but can
+// lead to small overlaps.
+void IcnGridMap_Impl::OccupyGrids( const SvxIconChoiceCtrlEntry* pEntry )
+{
+ if( !_pGridMap || !SvxIconChoiceCtrl_Impl::IsBoundingRectValid( pEntry->aRect ))
+ return;
+ OccupyGrid( GetGrid( pEntry->aRect.Center()) );
+}
+
+void IcnGridMap_Impl::Clear()
+{
+ if( _pGridMap )
+ {
+ _pGridMap.reset();
+ _nGridRows = 0;
+ _nGridCols = 0;
+ _aLastOccupiedGrid.SetEmpty();
+ }
+}
+
+sal_uLong IcnGridMap_Impl::GetGridCount( const Size& rSizePixel, sal_uInt16 nDX, sal_uInt16 nDY)
+{
+ tools::Long ndx = (rSizePixel.Width() - LROFFS_WINBORDER) / nDX;
+ if( ndx < 0 ) ndx *= -1;
+ tools::Long ndy = (rSizePixel.Height() - TBOFFS_WINBORDER) / nDY;
+ if( ndy < 0 ) ndy *= -1;
+ return static_cast<sal_uLong>(ndx * ndy);
+}
+
+void IcnGridMap_Impl::OutputSizeChanged()
+{
+ if( !_pGridMap )
+ return;
+
+ sal_uInt16 nCols, nRows;
+ GetMinMapSize( nCols, nRows );
+ if( _pView->nWinBits & WB_ALIGN_TOP )
+ {
+ if( nCols != _nGridCols )
+ Clear();
+ else if( nRows >= _nGridRows )
+ Expand();
+ }
+ else
+ {
+ if( nRows != _nGridRows )
+ Clear();
+ else if( nCols >= _nGridCols )
+ Expand();
+ }
+}
+
+// Independently of the view's alignment (TOP or LEFT), the gridmap
+// should contain the data in a continuous region, to make it possible
+// to copy the whole block if the gridmap needs to be expanded.
+void IcnGridMap_Impl::GetGridCoord( GridId nId, sal_uInt16& rGridX, sal_uInt16& rGridY )
+{
+ Create();
+ if( _pView->nWinBits & WB_ALIGN_TOP )
+ {
+ rGridX = static_cast<sal_uInt16>(nId % _nGridCols);
+ rGridY = static_cast<sal_uInt16>(nId / _nGridCols);
+ }
+ else
+ {
+ rGridX = static_cast<sal_uInt16>(nId / _nGridRows);
+ rGridY = static_cast<sal_uInt16>(nId % _nGridRows);
+ }
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/imp_listbox.cxx b/vcl/source/control/imp_listbox.cxx
new file mode 100644
index 000000000..fa01c647e
--- /dev/null
+++ b/vcl/source/control/imp_listbox.cxx
@@ -0,0 +1,3012 @@
+/* -*- 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 <memory>
+
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/event.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/i18nhelp.hxx>
+#include <vcl/naturalsort.hxx>
+
+#include <listbox.hxx>
+#include <svdata.hxx>
+#include <window.h>
+
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+#include <comphelper/string.hxx>
+#include <comphelper/processfactory.hxx>
+
+#include <limits>
+
+#define MULTILINE_ENTRY_DRAW_FLAGS ( DrawTextFlags::WordBreak | DrawTextFlags::MultiLine | DrawTextFlags::VCenter )
+
+using namespace ::com::sun::star;
+
+constexpr tools::Long gnBorder = 1;
+
+void ImplInitDropDownButton( PushButton* pButton )
+{
+ pButton->SetSymbol( SymbolType::SPIN_DOWN );
+
+ if ( pButton->IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)
+ && ! pButton->IsNativeControlSupported(ControlType::Listbox, ControlPart::ButtonDown) )
+ pButton->SetBackground();
+}
+
+ImplEntryList::ImplEntryList( vcl::Window* pWindow )
+{
+ mpWindow = pWindow;
+ mnLastSelected = LISTBOX_ENTRY_NOTFOUND;
+ mnSelectionAnchor = LISTBOX_ENTRY_NOTFOUND;
+ mnImages = 0;
+ mbCallSelectionChangedHdl = true;
+
+ mnMRUCount = 0;
+ mnMaxMRUCount = 0;
+}
+
+ImplEntryList::~ImplEntryList()
+{
+ Clear();
+}
+
+void ImplEntryList::Clear()
+{
+ mnImages = 0;
+ maEntries.clear();
+}
+
+void ImplEntryList::dispose()
+{
+ Clear();
+ mpWindow.clear();
+}
+
+void ImplEntryList::SelectEntry( sal_Int32 nPos, bool bSelect )
+{
+ if (0 <= nPos && o3tl::make_unsigned(nPos) < maEntries.size())
+ {
+ std::vector<std::unique_ptr<ImplEntryType> >::iterator iter = maEntries.begin()+nPos;
+
+ if ( ( (*iter)->mbIsSelected != bSelect ) &&
+ ( ( (*iter)->mnFlags & ListBoxEntryFlags::DisableSelection) == ListBoxEntryFlags::NONE ) )
+ {
+ (*iter)->mbIsSelected = bSelect;
+ if ( mbCallSelectionChangedHdl )
+ maSelectionChangedHdl.Call( nPos );
+ }
+ }
+}
+
+namespace
+{
+ comphelper::string::NaturalStringSorter& GetSorter()
+ {
+ static comphelper::string::NaturalStringSorter gSorter(
+ ::comphelper::getProcessComponentContext(),
+ Application::GetSettings().GetLanguageTag().getLocale());
+ return gSorter;
+ };
+}
+
+namespace vcl
+{
+ sal_Int32 NaturalSortCompare(const OUString &rA, const OUString &rB)
+ {
+ const comphelper::string::NaturalStringSorter &rSorter = GetSorter();
+ return rSorter.compare(rA, rB);
+ }
+}
+
+sal_Int32 ImplEntryList::InsertEntry( sal_Int32 nPos, ImplEntryType* pNewEntry, bool bSort )
+{
+ assert(nPos >= 0);
+ assert(maEntries.size() < LISTBOX_MAX_ENTRIES);
+
+ if ( !!pNewEntry->maImage )
+ mnImages++;
+
+ sal_Int32 insPos = 0;
+ const sal_Int32 nEntriesSize = static_cast<sal_Int32>(maEntries.size());
+
+ if ( !bSort || maEntries.empty())
+ {
+ if (0 <= nPos && nPos < nEntriesSize)
+ {
+ insPos = nPos;
+ maEntries.insert( maEntries.begin() + nPos, std::unique_ptr<ImplEntryType>(pNewEntry) );
+ }
+ else
+ {
+ insPos = nEntriesSize;
+ maEntries.push_back(std::unique_ptr<ImplEntryType>(pNewEntry));
+ }
+ }
+ else
+ {
+ const comphelper::string::NaturalStringSorter &rSorter = GetSorter();
+
+ const OUString& rStr = pNewEntry->maStr;
+
+ ImplEntryType* pTemp = GetEntry( nEntriesSize-1 );
+
+ try
+ {
+ sal_Int32 nComp = rSorter.compare(rStr, pTemp->maStr);
+
+ // fast insert for sorted data
+ if ( nComp >= 0 )
+ {
+ insPos = nEntriesSize;
+ maEntries.push_back(std::unique_ptr<ImplEntryType>(pNewEntry));
+ }
+ else
+ {
+ pTemp = GetEntry( mnMRUCount );
+
+ nComp = rSorter.compare(rStr, pTemp->maStr);
+ if ( nComp <= 0 )
+ {
+ insPos = 0;
+ maEntries.insert(maEntries.begin(), std::unique_ptr<ImplEntryType>(pNewEntry));
+ }
+ else
+ {
+ sal_uLong nLow = mnMRUCount;
+ sal_uLong nHigh = maEntries.size()-1;
+ sal_Int32 nMid;
+
+ // binary search
+ do
+ {
+ nMid = static_cast<sal_Int32>((nLow + nHigh) / 2);
+ pTemp = GetEntry( nMid );
+
+ nComp = rSorter.compare(rStr, pTemp->maStr);
+
+ if ( nComp < 0 )
+ nHigh = nMid-1;
+ else
+ {
+ if ( nComp > 0 )
+ nLow = nMid + 1;
+ else
+ break;
+ }
+ }
+ while ( nLow <= nHigh );
+
+ if ( nComp >= 0 )
+ nMid++;
+
+ insPos = nMid;
+ maEntries.insert(maEntries.begin()+nMid, std::unique_ptr<ImplEntryType>(pNewEntry));
+ }
+ }
+ }
+ catch (uno::RuntimeException& )
+ {
+ // XXX this is arguable, if the exception occurred because pNewEntry is
+ // garbage you wouldn't insert it. If the exception occurred because the
+ // Collator implementation is garbage then give the user a chance to see
+ // his stuff
+ insPos = 0;
+ maEntries.insert(maEntries.begin(), std::unique_ptr<ImplEntryType>(pNewEntry));
+ }
+
+ }
+
+ return insPos;
+}
+
+void ImplEntryList::RemoveEntry( sal_Int32 nPos )
+{
+ if (0 <= nPos && o3tl::make_unsigned(nPos) < maEntries.size())
+ {
+ std::vector<std::unique_ptr<ImplEntryType> >::iterator iter = maEntries.begin()+ nPos;
+
+ if ( !!(*iter)->maImage )
+ mnImages--;
+
+ maEntries.erase(iter);
+ }
+}
+
+sal_Int32 ImplEntryList::FindEntry( std::u16string_view rString, bool bSearchMRUArea ) const
+{
+ const sal_Int32 nEntries = static_cast<sal_Int32>(maEntries.size());
+ for ( sal_Int32 n = bSearchMRUArea ? 0 : GetMRUCount(); n < nEntries; n++ )
+ {
+ OUString aComp( vcl::I18nHelper::filterFormattingChars( maEntries[n]->maStr ) );
+ if ( aComp == rString )
+ return n;
+ }
+ return LISTBOX_ENTRY_NOTFOUND;
+}
+
+sal_Int32 ImplEntryList::FindMatchingEntry( const OUString& rStr, sal_Int32 nStart, bool bLazy ) const
+{
+ sal_Int32 nPos = LISTBOX_ENTRY_NOTFOUND;
+ sal_Int32 nEntryCount = GetEntryCount();
+
+ const vcl::I18nHelper& rI18nHelper = mpWindow->GetSettings().GetLocaleI18nHelper();
+ for ( sal_Int32 n = nStart; n < nEntryCount; )
+ {
+ ImplEntryType* pImplEntry = GetEntry( n );
+ bool bMatch;
+ if ( bLazy )
+ {
+ bMatch = rI18nHelper.MatchString( rStr, pImplEntry->maStr );
+ }
+ else
+ {
+ bMatch = pImplEntry->maStr.startsWith(rStr);
+ }
+ if ( bMatch )
+ {
+ nPos = n;
+ break;
+ }
+
+ n++;
+ }
+
+ return nPos;
+}
+
+tools::Long ImplEntryList::GetAddedHeight( sal_Int32 i_nEndIndex, sal_Int32 i_nBeginIndex ) const
+{
+ tools::Long nHeight = 0;
+ sal_Int32 nStart = std::min(i_nEndIndex, i_nBeginIndex);
+ sal_Int32 nStop = std::max(i_nEndIndex, i_nBeginIndex);
+ sal_Int32 nEntryCount = GetEntryCount();
+ if( 0 <= nStop && nStop != LISTBOX_ENTRY_NOTFOUND && nEntryCount != 0 )
+ {
+ // sanity check
+ if( nStop > nEntryCount-1 )
+ nStop = nEntryCount-1;
+ if (nStart < 0)
+ nStart = 0;
+ else if( nStart > nEntryCount-1 )
+ nStart = nEntryCount-1;
+
+ sal_Int32 nIndex = nStart;
+ while( nIndex != LISTBOX_ENTRY_NOTFOUND && nIndex < nStop )
+ {
+ tools::Long nPosHeight = GetEntryPtr( nIndex )->getHeightWithMargin();
+ if (nHeight > ::std::numeric_limits<tools::Long>::max() - nPosHeight)
+ {
+ SAL_WARN( "vcl", "ImplEntryList::GetAddedHeight: truncated");
+ break;
+ }
+ nHeight += nPosHeight;
+ nIndex++;
+ }
+ }
+ else
+ nHeight = 0;
+ return i_nEndIndex > i_nBeginIndex ? nHeight : -nHeight;
+}
+
+tools::Long ImplEntryList::GetEntryHeight( sal_Int32 nPos ) const
+{
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ return pImplEntry ? pImplEntry->getHeightWithMargin() : 0;
+}
+
+OUString ImplEntryList::GetEntryText( sal_Int32 nPos ) const
+{
+ OUString aEntryText;
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ if ( pImplEntry )
+ aEntryText = pImplEntry->maStr;
+ return aEntryText;
+}
+
+bool ImplEntryList::HasEntryImage( sal_Int32 nPos ) const
+{
+ bool bImage = false;
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ if ( pImplEntry )
+ bImage = !!pImplEntry->maImage;
+ return bImage;
+}
+
+Image ImplEntryList::GetEntryImage( sal_Int32 nPos ) const
+{
+ Image aImage;
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ if ( pImplEntry )
+ aImage = pImplEntry->maImage;
+ return aImage;
+}
+
+void ImplEntryList::SetEntryData( sal_Int32 nPos, void* pNewData )
+{
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ if ( pImplEntry )
+ pImplEntry->mpUserData = pNewData;
+}
+
+void* ImplEntryList::GetEntryData( sal_Int32 nPos ) const
+{
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ return pImplEntry ? pImplEntry->mpUserData : nullptr;
+}
+
+void ImplEntryList::SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags )
+{
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ if ( pImplEntry )
+ pImplEntry->mnFlags = nFlags;
+}
+
+sal_Int32 ImplEntryList::GetSelectedEntryCount() const
+{
+ sal_Int32 nSelCount = 0;
+ for ( sal_Int32 n = GetEntryCount(); n; )
+ {
+ ImplEntryType* pImplEntry = GetEntry( --n );
+ if ( pImplEntry->mbIsSelected )
+ nSelCount++;
+ }
+ return nSelCount;
+}
+
+OUString ImplEntryList::GetSelectedEntry( sal_Int32 nIndex ) const
+{
+ return GetEntryText( GetSelectedEntryPos( nIndex ) );
+}
+
+sal_Int32 ImplEntryList::GetSelectedEntryPos( sal_Int32 nIndex ) const
+{
+ sal_Int32 nSelEntryPos = LISTBOX_ENTRY_NOTFOUND;
+ sal_Int32 nSel = 0;
+ sal_Int32 nEntryCount = GetEntryCount();
+
+ for ( sal_Int32 n = 0; n < nEntryCount; n++ )
+ {
+ ImplEntryType* pImplEntry = GetEntry( n );
+ if ( pImplEntry->mbIsSelected )
+ {
+ if ( nSel == nIndex )
+ {
+ nSelEntryPos = n;
+ break;
+ }
+ nSel++;
+ }
+ }
+
+ return nSelEntryPos;
+}
+
+bool ImplEntryList::IsEntryPosSelected( sal_Int32 nIndex ) const
+{
+ ImplEntryType* pImplEntry = GetEntry( nIndex );
+ return pImplEntry && pImplEntry->mbIsSelected;
+}
+
+bool ImplEntryList::IsEntrySelectable( sal_Int32 nPos ) const
+{
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ return pImplEntry == nullptr || ((pImplEntry->mnFlags & ListBoxEntryFlags::DisableSelection) == ListBoxEntryFlags::NONE);
+}
+
+sal_Int32 ImplEntryList::FindFirstSelectable( sal_Int32 nPos, bool bForward /* = true */ ) const
+{
+ if( IsEntrySelectable( nPos ) )
+ return nPos;
+
+ if( bForward )
+ {
+ for( nPos = nPos + 1; nPos < GetEntryCount(); nPos++ )
+ {
+ if( IsEntrySelectable( nPos ) )
+ return nPos;
+ }
+ }
+ else
+ {
+ while( nPos )
+ {
+ nPos--;
+ if( IsEntrySelectable( nPos ) )
+ return nPos;
+ }
+ }
+
+ return LISTBOX_ENTRY_NOTFOUND;
+}
+
+ImplListBoxWindow::ImplListBoxWindow( vcl::Window* pParent, WinBits nWinStyle ) :
+ Control( pParent, 0 ),
+ maEntryList( this ),
+ maQuickSelectionEngine( *this )
+{
+
+ mnTop = 0;
+ mnLeft = 0;
+ mnSelectModifier = 0;
+ mnUserDrawEntry = LISTBOX_ENTRY_NOTFOUND;
+ mbTrack = false;
+ mbTravelSelect = false;
+ mbTrackingSelect = false;
+ mbSelectionChanged = false;
+ mbMouseMoveSelect = false;
+ mbMulti = false;
+ mbGrabFocus = false;
+ mbUserDrawEnabled = false;
+ mbInUserDraw = false;
+ mbReadOnly = false;
+ mbHasFocusRect = false;
+ mbRight = ( nWinStyle & WB_RIGHT );
+ mbCenter = ( nWinStyle & WB_CENTER );
+ mbSimpleMode = ( nWinStyle & WB_SIMPLEMODE );
+ mbSort = ( nWinStyle & WB_SORT );
+ mbIsDropdown = ( nWinStyle & WB_DROPDOWN );
+ mbEdgeBlending = false;
+
+ mnCurrentPos = LISTBOX_ENTRY_NOTFOUND;
+ mnTrackingSaveSelection = LISTBOX_ENTRY_NOTFOUND;
+
+ GetOutDev()->SetLineColor();
+ SetTextFillColor();
+ SetBackground( Wallpaper( GetSettings().GetStyleSettings().GetFieldColor() ) );
+
+ ApplySettings(*GetOutDev());
+ ImplCalcMetrics();
+}
+
+ImplListBoxWindow::~ImplListBoxWindow()
+{
+ disposeOnce();
+}
+
+void ImplListBoxWindow::dispose()
+{
+ maEntryList.dispose();
+ Control::dispose();
+}
+
+void ImplListBoxWindow::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ ApplyControlFont(rRenderContext, rStyleSettings.GetFieldFont());
+ ApplyControlForeground(rRenderContext, rStyleSettings.GetFieldTextColor());
+
+ if (IsControlBackground())
+ rRenderContext.SetBackground(GetControlBackground());
+ else
+ rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
+}
+
+void ImplListBoxWindow::ImplCalcMetrics()
+{
+ mnMaxWidth = 0;
+ mnMaxTxtWidth = 0;
+ mnMaxImgWidth = 0;
+ mnMaxImgTxtWidth= 0;
+ mnMaxImgHeight = 0;
+
+ mnTextHeight = static_cast<sal_uInt16>(GetTextHeight());
+ mnMaxTxtHeight = mnTextHeight + gnBorder;
+ mnMaxHeight = mnMaxTxtHeight;
+
+ if ( maUserItemSize.Height() > mnMaxHeight )
+ mnMaxHeight = static_cast<sal_uInt16>(maUserItemSize.Height());
+ if ( maUserItemSize.Width() > mnMaxWidth )
+ mnMaxWidth= static_cast<sal_uInt16>(maUserItemSize.Width());
+
+ for ( sal_Int32 n = maEntryList.GetEntryCount(); n; )
+ {
+ ImplEntryType* pEntry = maEntryList.GetMutableEntryPtr( --n );
+ ImplUpdateEntryMetrics( *pEntry );
+ }
+
+ if( mnCurrentPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ Size aSz( GetOutputSizePixel().Width(), maEntryList.GetEntryPtr( mnCurrentPos )->getHeightWithMargin() );
+ maFocusRect.SetSize( aSz );
+ }
+}
+
+void ImplListBoxWindow::Clear()
+{
+ maEntryList.Clear();
+
+ mnMaxHeight = mnMaxTxtHeight;
+ mnMaxWidth = 0;
+ mnMaxTxtWidth = 0;
+ mnMaxImgTxtWidth= 0;
+ mnMaxImgWidth = 0;
+ mnMaxImgHeight = 0;
+ mnTop = 0;
+ mnLeft = 0;
+ ImplClearLayoutData();
+
+ mnCurrentPos = LISTBOX_ENTRY_NOTFOUND;
+ maQuickSelectionEngine.Reset();
+
+ Invalidate();
+}
+
+void ImplListBoxWindow::SetUserItemSize( const Size& rSz )
+{
+ ImplClearLayoutData();
+ maUserItemSize = rSz;
+ ImplCalcMetrics();
+}
+
+namespace {
+
+struct ImplEntryMetrics
+{
+ bool bText;
+ bool bImage;
+ tools::Long nEntryWidth;
+ tools::Long nEntryHeight;
+ tools::Long nTextWidth;
+ tools::Long nImgWidth;
+ tools::Long nImgHeight;
+};
+
+}
+
+tools::Long ImplEntryType::getHeightWithMargin() const
+{
+ return mnHeight + ImplGetSVData()->maNWFData.mnListBoxEntryMargin;
+}
+
+SalLayoutGlyphs* ImplEntryType::GetTextGlyphs(const OutputDevice* pOutputDevice)
+{
+ if (maStrGlyphs.IsValid())
+ // Use pre-calculated result.
+ return &maStrGlyphs;
+
+ std::unique_ptr<SalLayout> pLayout = pOutputDevice->ImplLayout(
+ maStr, 0, maStr.getLength(), Point(0, 0), 0, {}, SalLayoutFlags::GlyphItemsOnly);
+ if (!pLayout)
+ return nullptr;
+
+ // Remember the calculation result.
+ maStrGlyphs = pLayout->GetGlyphs();
+
+ return &maStrGlyphs;
+}
+
+void ImplListBoxWindow::ImplUpdateEntryMetrics( ImplEntryType& rEntry )
+{
+ ImplEntryMetrics aMetrics;
+ aMetrics.bText = !rEntry.maStr.isEmpty();
+ aMetrics.bImage = !!rEntry.maImage;
+ aMetrics.nEntryWidth = 0;
+ aMetrics.nEntryHeight = 0;
+ aMetrics.nTextWidth = 0;
+ aMetrics.nImgWidth = 0;
+ aMetrics.nImgHeight = 0;
+
+ if ( aMetrics.bText )
+ {
+ if( rEntry.mnFlags & ListBoxEntryFlags::MultiLine )
+ {
+ // multiline case
+ Size aCurSize( PixelToLogic( GetSizePixel() ) );
+ // set the current size to a large number
+ // GetTextRect should shrink it to the actual size
+ aCurSize.setHeight( 0x7fffff );
+ tools::Rectangle aTextRect( Point( 0, 0 ), aCurSize );
+ aTextRect = GetTextRect( aTextRect, rEntry.maStr, DrawTextFlags::WordBreak | DrawTextFlags::MultiLine );
+ aMetrics.nTextWidth = aTextRect.GetWidth();
+ if( aMetrics.nTextWidth > mnMaxTxtWidth )
+ mnMaxTxtWidth = aMetrics.nTextWidth;
+ aMetrics.nEntryWidth = mnMaxTxtWidth;
+ aMetrics.nEntryHeight = aTextRect.GetHeight() + gnBorder;
+ }
+ else
+ {
+ // normal single line case
+ const SalLayoutGlyphs* pGlyphs = rEntry.GetTextGlyphs(GetOutDev());
+ aMetrics.nTextWidth
+ = static_cast<sal_uInt16>(GetTextWidth(rEntry.maStr, 0, -1, nullptr, pGlyphs));
+ if( aMetrics.nTextWidth > mnMaxTxtWidth )
+ mnMaxTxtWidth = aMetrics.nTextWidth;
+ aMetrics.nEntryWidth = mnMaxTxtWidth;
+ aMetrics.nEntryHeight = mnTextHeight + gnBorder;
+ }
+ }
+ if ( aMetrics.bImage )
+ {
+ Size aImgSz = rEntry.maImage.GetSizePixel();
+ aMetrics.nImgWidth = static_cast<sal_uInt16>(CalcZoom( aImgSz.Width() ));
+ aMetrics.nImgHeight = static_cast<sal_uInt16>(CalcZoom( aImgSz.Height() ));
+
+ if( aMetrics.nImgWidth > mnMaxImgWidth )
+ mnMaxImgWidth = aMetrics.nImgWidth;
+ if( aMetrics.nImgHeight > mnMaxImgHeight )
+ mnMaxImgHeight = aMetrics.nImgHeight;
+
+ mnMaxImgTxtWidth = std::max( mnMaxImgTxtWidth, aMetrics.nTextWidth );
+ aMetrics.nEntryHeight = std::max( aMetrics.nImgHeight, aMetrics.nEntryHeight );
+
+ }
+
+ bool bIsUserDrawEnabled = IsUserDrawEnabled();
+ if (bIsUserDrawEnabled || aMetrics.bImage)
+ {
+ aMetrics.nEntryWidth = std::max( aMetrics.nImgWidth, maUserItemSize.Width() );
+ if (!bIsUserDrawEnabled && aMetrics.bText)
+ aMetrics.nEntryWidth += aMetrics.nTextWidth + IMG_TXT_DISTANCE;
+ aMetrics.nEntryHeight = std::max( std::max( mnMaxImgHeight, maUserItemSize.Height() ) + 2,
+ aMetrics.nEntryHeight );
+ }
+
+ if (!aMetrics.bText && !aMetrics.bImage && !bIsUserDrawEnabled)
+ {
+ // entries which have no (aka an empty) text, and no image,
+ // and are not user-drawn, should be shown nonetheless
+ aMetrics.nEntryHeight = mnTextHeight + gnBorder;
+ }
+
+ if ( aMetrics.nEntryWidth > mnMaxWidth )
+ mnMaxWidth = aMetrics.nEntryWidth;
+ if ( aMetrics.nEntryHeight > mnMaxHeight )
+ mnMaxHeight = aMetrics.nEntryHeight;
+
+ rEntry.mnHeight = aMetrics.nEntryHeight;
+}
+
+void ImplListBoxWindow::ImplCallSelect()
+{
+ if ( !IsTravelSelect() && GetEntryList().GetMaxMRUCount() )
+ {
+ // Insert the selected entry as MRU, if not already first MRU
+ sal_Int32 nSelected = GetEntryList().GetSelectedEntryPos( 0 );
+ sal_Int32 nMRUCount = GetEntryList().GetMRUCount();
+ OUString aSelected = GetEntryList().GetEntryText( nSelected );
+ sal_Int32 nFirstMatchingEntryPos = GetEntryList().FindEntry( aSelected, true );
+ if ( nFirstMatchingEntryPos || !nMRUCount )
+ {
+ bool bSelectNewEntry = false;
+ if ( nFirstMatchingEntryPos < nMRUCount )
+ {
+ RemoveEntry( nFirstMatchingEntryPos );
+ nMRUCount--;
+ if ( nFirstMatchingEntryPos == nSelected )
+ bSelectNewEntry = true;
+ }
+ else if ( nMRUCount == GetEntryList().GetMaxMRUCount() )
+ {
+ RemoveEntry( nMRUCount - 1 );
+ nMRUCount--;
+ }
+
+ ImplClearLayoutData();
+
+ ImplEntryType* pNewEntry = new ImplEntryType( aSelected );
+ pNewEntry->mbIsSelected = bSelectNewEntry;
+ GetEntryList().InsertEntry( 0, pNewEntry, false );
+ ImplUpdateEntryMetrics( *pNewEntry );
+ GetEntryList().SetMRUCount( ++nMRUCount );
+ SetSeparatorPos( nMRUCount ? nMRUCount-1 : 0 );
+ maMRUChangedHdl.Call( nullptr );
+ }
+ }
+
+ maSelectHdl.Call( nullptr );
+ mbSelectionChanged = false;
+}
+
+sal_Int32 ImplListBoxWindow::InsertEntry(sal_Int32 nPos, ImplEntryType* pNewEntry, bool bSort)
+{
+ assert(nPos >= 0);
+ assert(maEntryList.GetEntryCount() < LISTBOX_MAX_ENTRIES);
+
+ ImplClearLayoutData();
+ sal_Int32 nNewPos = maEntryList.InsertEntry( nPos, pNewEntry, bSort );
+
+ if( GetStyle() & WB_WORDBREAK )
+ pNewEntry->mnFlags |= ListBoxEntryFlags::MultiLine;
+
+ ImplUpdateEntryMetrics( *pNewEntry );
+ return nNewPos;
+}
+
+sal_Int32 ImplListBoxWindow::InsertEntry( sal_Int32 nPos, ImplEntryType* pNewEntry )
+{
+ return InsertEntry(nPos, pNewEntry, mbSort);
+}
+
+void ImplListBoxWindow::RemoveEntry( sal_Int32 nPos )
+{
+ ImplClearLayoutData();
+ maEntryList.RemoveEntry( nPos );
+ if( mnCurrentPos >= maEntryList.GetEntryCount() )
+ mnCurrentPos = LISTBOX_ENTRY_NOTFOUND;
+ ImplCalcMetrics();
+}
+
+void ImplListBoxWindow::SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags )
+{
+ maEntryList.SetEntryFlags( nPos, nFlags );
+ ImplEntryType* pEntry = maEntryList.GetMutableEntryPtr( nPos );
+ if( pEntry )
+ ImplUpdateEntryMetrics( *pEntry );
+}
+
+void ImplListBoxWindow::ImplShowFocusRect()
+{
+ if ( mbHasFocusRect )
+ HideFocus();
+ ShowFocus( maFocusRect );
+ mbHasFocusRect = true;
+}
+
+void ImplListBoxWindow::ImplHideFocusRect()
+{
+ if ( mbHasFocusRect )
+ {
+ HideFocus();
+ mbHasFocusRect = false;
+ }
+}
+
+sal_Int32 ImplListBoxWindow::GetEntryPosForPoint( const Point& rPoint ) const
+{
+ tools::Long nY = gnBorder;
+
+ sal_Int32 nSelect = mnTop;
+ const ImplEntryType* pEntry = maEntryList.GetEntryPtr( nSelect );
+ while (pEntry)
+ {
+ tools::Long nEntryHeight = pEntry->getHeightWithMargin();
+ if (rPoint.Y() <= nEntryHeight + nY)
+ break;
+ nY += nEntryHeight;
+ pEntry = maEntryList.GetEntryPtr( ++nSelect );
+ }
+ if( pEntry == nullptr )
+ nSelect = LISTBOX_ENTRY_NOTFOUND;
+
+ return nSelect;
+}
+
+bool ImplListBoxWindow::IsVisible( sal_Int32 i_nEntry ) const
+{
+ bool bRet = false;
+
+ if( i_nEntry >= mnTop )
+ {
+ if( maEntryList.GetAddedHeight( i_nEntry, mnTop ) <
+ PixelToLogic( GetSizePixel() ).Height() )
+ {
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+tools::Long ImplListBoxWindow::GetEntryHeightWithMargin() const
+{
+ tools::Long nMargin = ImplGetSVData()->maNWFData.mnListBoxEntryMargin;
+ return mnMaxHeight + nMargin;
+}
+
+sal_Int32 ImplListBoxWindow::GetLastVisibleEntry() const
+{
+ sal_Int32 nPos = mnTop;
+ tools::Long nWindowHeight = GetSizePixel().Height();
+ sal_Int32 nCount = maEntryList.GetEntryCount();
+ tools::Long nDiff;
+ for( nDiff = 0; nDiff < nWindowHeight && nPos < nCount; nDiff = maEntryList.GetAddedHeight( nPos, mnTop ) )
+ nPos++;
+
+ if( nDiff > nWindowHeight && nPos > mnTop )
+ nPos--;
+
+ if( nPos >= nCount )
+ nPos = nCount-1;
+
+ return nPos;
+}
+
+void ImplListBoxWindow::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ mbMouseMoveSelect = false; // only till the first MouseButtonDown
+ maQuickSelectionEngine.Reset();
+
+ if ( !IsReadOnly() )
+ {
+ if( rMEvt.GetClicks() == 1 )
+ {
+ sal_Int32 nSelect = GetEntryPosForPoint( rMEvt.GetPosPixel() );
+ if( nSelect != LISTBOX_ENTRY_NOTFOUND )
+ {
+ if ( !mbMulti && GetEntryList().GetSelectedEntryCount() )
+ mnTrackingSaveSelection = GetEntryList().GetSelectedEntryPos( 0 );
+ else
+ mnTrackingSaveSelection = LISTBOX_ENTRY_NOTFOUND;
+
+ mnCurrentPos = nSelect;
+ mbTrackingSelect = true;
+ bool bCurPosChange = (mnCurrentPos != nSelect);
+ (void)SelectEntries( nSelect, LET_MBDOWN, rMEvt.IsShift(), rMEvt.IsMod1() ,bCurPosChange);
+ mbTrackingSelect = false;
+ if ( mbGrabFocus )
+ GrabFocus();
+
+ StartTracking( StartTrackingFlags::ScrollRepeat );
+ }
+ }
+ if( rMEvt.GetClicks() == 2 )
+ {
+ maDoubleClickHdl.Call( this );
+ }
+ }
+ else // if ( mbGrabFocus )
+ {
+ GrabFocus();
+ }
+}
+
+void ImplListBoxWindow::MouseMove( const MouseEvent& rMEvt )
+{
+ if (rMEvt.IsLeaveWindow() || mbMulti || !IsMouseMoveSelect() || !maEntryList.GetEntryCount())
+ return;
+
+ tools::Rectangle aRect( Point(), GetOutputSizePixel() );
+ if( !aRect.Contains( rMEvt.GetPosPixel() ) )
+ return;
+
+ if ( IsMouseMoveSelect() )
+ {
+ sal_Int32 nSelect = GetEntryPosForPoint( rMEvt.GetPosPixel() );
+ if( nSelect == LISTBOX_ENTRY_NOTFOUND )
+ nSelect = maEntryList.GetEntryCount() - 1;
+ nSelect = std::min( nSelect, GetLastVisibleEntry() );
+ nSelect = std::min( nSelect, static_cast<sal_Int32>( maEntryList.GetEntryCount() - 1 ) );
+ // Select only visible Entries with MouseMove, otherwise Tracking...
+ if ( IsVisible( nSelect ) &&
+ maEntryList.IsEntrySelectable( nSelect ) &&
+ ( ( nSelect != mnCurrentPos ) || !GetEntryList().GetSelectedEntryCount() || ( nSelect != GetEntryList().GetSelectedEntryPos( 0 ) ) ) )
+ {
+ mbTrackingSelect = true;
+ if ( SelectEntries( nSelect, LET_TRACKING ) )
+ {
+ // When list box selection change by mouse move, notify
+ // VclEventId::ListboxSelect vcl event.
+ maListItemSelectHdl.Call(nullptr);
+ }
+ mbTrackingSelect = false;
+ }
+ }
+
+ // if the DD button was pressed and someone moved into the ListBox
+ // with the mouse button pressed...
+ if ( rMEvt.IsLeft() && !rMEvt.IsSynthetic() )
+ {
+ if ( !mbMulti && GetEntryList().GetSelectedEntryCount() )
+ mnTrackingSaveSelection = GetEntryList().GetSelectedEntryPos( 0 );
+ else
+ mnTrackingSaveSelection = LISTBOX_ENTRY_NOTFOUND;
+
+ StartTracking( StartTrackingFlags::ScrollRepeat );
+ }
+}
+
+void ImplListBoxWindow::DeselectAll()
+{
+ while ( GetEntryList().GetSelectedEntryCount() )
+ {
+ sal_Int32 nS = GetEntryList().GetSelectedEntryPos( 0 );
+ SelectEntry( nS, false );
+ }
+}
+
+void ImplListBoxWindow::SelectEntry( sal_Int32 nPos, bool bSelect )
+{
+ if( (maEntryList.IsEntryPosSelected( nPos ) == bSelect) || !maEntryList.IsEntrySelectable( nPos ) )
+ return;
+
+ ImplHideFocusRect();
+ if( bSelect )
+ {
+ if( !mbMulti )
+ {
+ // deselect the selected entry
+ sal_Int32 nDeselect = GetEntryList().GetSelectedEntryPos( 0 );
+ if( nDeselect != LISTBOX_ENTRY_NOTFOUND )
+ {
+ //SelectEntryPos( nDeselect, false );
+ GetEntryList().SelectEntry( nDeselect, false );
+ if (IsUpdateMode() && IsReallyVisible())
+ Invalidate();
+ }
+ }
+ maEntryList.SelectEntry( nPos, true );
+ mnCurrentPos = nPos;
+ if ( ( nPos != LISTBOX_ENTRY_NOTFOUND ) && IsUpdateMode() )
+ {
+ Invalidate();
+ if ( !IsVisible( nPos ) )
+ {
+ ImplClearLayoutData();
+ sal_Int32 nVisibleEntries = GetLastVisibleEntry()-mnTop;
+ if ( !nVisibleEntries || !IsReallyVisible() || ( nPos < GetTopEntry() ) )
+ {
+ Resize();
+ ShowProminentEntry( nPos );
+ }
+ else
+ {
+ ShowProminentEntry( nPos );
+ }
+ }
+ }
+ }
+ else
+ {
+ maEntryList.SelectEntry( nPos, false );
+ Invalidate();
+ }
+ mbSelectionChanged = true;
+}
+
+bool ImplListBoxWindow::SelectEntries( sal_Int32 nSelect, LB_EVENT_TYPE eLET, bool bShift, bool bCtrl, bool bSelectPosChange /*=FALSE*/ )
+{
+ bool bSelectionChanged = false;
+
+ if( IsEnabled() && maEntryList.IsEntrySelectable( nSelect ) )
+ {
+ bool bFocusChanged = false;
+
+ // here (Single-ListBox) only one entry can be deselected
+ if( !mbMulti )
+ {
+ sal_Int32 nDeselect = maEntryList.GetSelectedEntryPos( 0 );
+ if( nSelect != nDeselect )
+ {
+ SelectEntry( nSelect, true );
+ maEntryList.SetLastSelected( nSelect );
+ bFocusChanged = true;
+ bSelectionChanged = true;
+ }
+ }
+ // MultiListBox without Modifier
+ else if( mbSimpleMode && !bCtrl && !bShift )
+ {
+ sal_Int32 nEntryCount = maEntryList.GetEntryCount();
+ for ( sal_Int32 nPos = 0; nPos < nEntryCount; nPos++ )
+ {
+ bool bSelect = nPos == nSelect;
+ if ( maEntryList.IsEntryPosSelected( nPos ) != bSelect )
+ {
+ SelectEntry( nPos, bSelect );
+ bFocusChanged = true;
+ bSelectionChanged = true;
+ }
+ }
+ maEntryList.SetLastSelected( nSelect );
+ maEntryList.SetSelectionAnchor( nSelect );
+ }
+ // MultiListBox only with CTRL/SHIFT or not in SimpleMode
+ else if( ( !mbSimpleMode /* && !bShift */ ) || ( mbSimpleMode && ( bCtrl || bShift ) ) )
+ {
+ // Space for selection change
+ if( !bShift && ( ( eLET == LET_KEYSPACE ) || ( eLET == LET_MBDOWN ) ) )
+ {
+ bool bSelect = !maEntryList.IsEntryPosSelected( nSelect );
+ SelectEntry( nSelect, bSelect );
+ maEntryList.SetLastSelected( nSelect );
+ maEntryList.SetSelectionAnchor( nSelect );
+ if ( !maEntryList.IsEntryPosSelected( nSelect ) )
+ maEntryList.SetSelectionAnchor( LISTBOX_ENTRY_NOTFOUND );
+ bFocusChanged = true;
+ bSelectionChanged = true;
+ }
+ else if( ( ( eLET == LET_TRACKING ) && ( nSelect != mnCurrentPos ) ) ||
+ ( bShift && ( ( eLET == LET_KEYMOVE ) || ( eLET == LET_MBDOWN ) ) ) )
+ {
+ mnCurrentPos = nSelect;
+ bFocusChanged = true;
+
+ sal_Int32 nAnchor = maEntryList.GetSelectionAnchor();
+ if( ( nAnchor == LISTBOX_ENTRY_NOTFOUND ) && maEntryList.GetSelectedEntryCount() )
+ {
+ nAnchor = maEntryList.GetSelectedEntryPos( maEntryList.GetSelectedEntryCount() - 1 );
+ }
+ if( nAnchor != LISTBOX_ENTRY_NOTFOUND )
+ {
+ // All entries from Anchor to nSelect have to be selected
+ sal_Int32 nStart = std::min( nSelect, nAnchor );
+ sal_Int32 nEnd = std::max( nSelect, nAnchor );
+ for ( sal_Int32 n = nStart; n <= nEnd; n++ )
+ {
+ if ( !maEntryList.IsEntryPosSelected( n ) )
+ {
+ SelectEntry( n, true );
+ bSelectionChanged = true;
+ }
+ }
+
+ // if appropriate some more has to be deselected...
+ sal_Int32 nLast = maEntryList.GetLastSelected();
+ if ( nLast != LISTBOX_ENTRY_NOTFOUND )
+ {
+ if ( ( nLast > nSelect ) && ( nLast > nAnchor ) )
+ {
+ for ( sal_Int32 n = nSelect+1; n <= nLast; n++ )
+ {
+ if ( maEntryList.IsEntryPosSelected( n ) )
+ {
+ SelectEntry( n, false );
+ bSelectionChanged = true;
+ }
+ }
+ }
+ else if ( ( nLast < nSelect ) && ( nLast < nAnchor ) )
+ {
+ for ( sal_Int32 n = nLast; n < nSelect; n++ )
+ {
+ if ( maEntryList.IsEntryPosSelected( n ) )
+ {
+ SelectEntry( n, false );
+ bSelectionChanged = true;
+ }
+ }
+ }
+ }
+ maEntryList.SetLastSelected( nSelect );
+ }
+ }
+ else if( eLET != LET_TRACKING )
+ {
+ ImplHideFocusRect();
+ Invalidate();
+ bFocusChanged = true;
+ }
+ }
+ else if( bShift )
+ {
+ bFocusChanged = true;
+ }
+
+ if( bSelectionChanged )
+ mbSelectionChanged = true;
+
+ if( bFocusChanged )
+ {
+ tools::Long nHeightDiff = maEntryList.GetAddedHeight( nSelect, mnTop );
+ maFocusRect.SetPos( Point( 0, nHeightDiff ) );
+ Size aSz( maFocusRect.GetWidth(),
+ maEntryList.GetEntryHeight( nSelect ) );
+ maFocusRect.SetSize( aSz );
+ if( HasFocus() )
+ ImplShowFocusRect();
+ if (bSelectPosChange)
+ {
+ maFocusHdl.Call(nSelect);
+ }
+ }
+ ImplClearLayoutData();
+ }
+ return bSelectionChanged;
+}
+
+void ImplListBoxWindow::Tracking( const TrackingEvent& rTEvt )
+{
+ tools::Rectangle aRect( Point(), GetOutputSizePixel() );
+ bool bInside = aRect.Contains( rTEvt.GetMouseEvent().GetPosPixel() );
+
+ if( rTEvt.IsTrackingCanceled() || rTEvt.IsTrackingEnded() ) // MouseButtonUp
+ {
+ if ( bInside && !rTEvt.IsTrackingCanceled() )
+ {
+ mnSelectModifier = rTEvt.GetMouseEvent().GetModifier();
+ ImplCallSelect();
+ }
+ else
+ {
+ maCancelHdl.Call( nullptr );
+ if ( !mbMulti )
+ {
+ mbTrackingSelect = true;
+ SelectEntry( mnTrackingSaveSelection, true );
+ mbTrackingSelect = false;
+ if ( mnTrackingSaveSelection != LISTBOX_ENTRY_NOTFOUND )
+ {
+ tools::Long nHeightDiff = maEntryList.GetAddedHeight( mnCurrentPos, mnTop );
+ maFocusRect.SetPos( Point( 0, nHeightDiff ) );
+ Size aSz( maFocusRect.GetWidth(),
+ maEntryList.GetEntryHeight( mnCurrentPos ) );
+ maFocusRect.SetSize( aSz );
+ ImplShowFocusRect();
+ }
+ }
+ }
+
+ mbTrack = false;
+ }
+ else
+ {
+ bool bTrackOrQuickClick = mbTrack;
+ if( !mbTrack )
+ {
+ if ( bInside )
+ {
+ mbTrack = true;
+ }
+
+ // this case only happens, if the mouse button is pressed very briefly
+ if( rTEvt.IsTrackingEnded() && mbTrack )
+ {
+ bTrackOrQuickClick = true;
+ mbTrack = false;
+ }
+ }
+
+ if( bTrackOrQuickClick )
+ {
+ MouseEvent aMEvt = rTEvt.GetMouseEvent();
+ Point aPt( aMEvt.GetPosPixel() );
+ bool bShift = aMEvt.IsShift();
+ bool bCtrl = aMEvt.IsMod1();
+
+ sal_Int32 nSelect = LISTBOX_ENTRY_NOTFOUND;
+ if( aPt.Y() < 0 )
+ {
+ if ( mnCurrentPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = mnCurrentPos ? ( mnCurrentPos - 1 ) : 0;
+ if( nSelect < mnTop )
+ SetTopEntry( mnTop-1 );
+ }
+ }
+ else if( aPt.Y() > GetOutputSizePixel().Height() )
+ {
+ if ( mnCurrentPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = std::min( static_cast<sal_Int32>(mnCurrentPos+1), static_cast<sal_Int32>(maEntryList.GetEntryCount()-1) );
+ if( nSelect >= GetLastVisibleEntry() )
+ SetTopEntry( mnTop+1 );
+ }
+ }
+ else
+ {
+ nSelect = static_cast<sal_Int32>( ( aPt.Y() + gnBorder ) / mnMaxHeight ) + mnTop;
+ nSelect = std::min( nSelect, GetLastVisibleEntry() );
+ nSelect = std::min( nSelect, static_cast<sal_Int32>( maEntryList.GetEntryCount() - 1 ) );
+ }
+
+ if ( bInside )
+ {
+ if ( ( nSelect != mnCurrentPos ) || !GetEntryList().GetSelectedEntryCount() )
+ {
+ mbTrackingSelect = true;
+ SelectEntries(nSelect, LET_TRACKING, bShift, bCtrl);
+ mbTrackingSelect = false;
+ }
+ }
+ else
+ {
+ if ( !mbMulti && GetEntryList().GetSelectedEntryCount() )
+ {
+ mbTrackingSelect = true;
+ SelectEntry( GetEntryList().GetSelectedEntryPos( 0 ), false );
+ mbTrackingSelect = false;
+ }
+ }
+ mnCurrentPos = nSelect;
+ if ( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
+ {
+ ImplHideFocusRect();
+ }
+ else
+ {
+ tools::Long nHeightDiff = maEntryList.GetAddedHeight( mnCurrentPos, mnTop );
+ maFocusRect.SetPos( Point( 0, nHeightDiff ) );
+ Size aSz( maFocusRect.GetWidth(), maEntryList.GetEntryHeight( mnCurrentPos ) );
+ maFocusRect.SetSize( aSz );
+ ImplShowFocusRect();
+ }
+ }
+ }
+}
+
+void ImplListBoxWindow::KeyInput( const KeyEvent& rKEvt )
+{
+ if( !ProcessKeyInput( rKEvt ) )
+ Control::KeyInput( rKEvt );
+}
+
+bool ImplListBoxWindow::ProcessKeyInput( const KeyEvent& rKEvt )
+{
+ // entry to be selected
+ sal_Int32 nSelect = LISTBOX_ENTRY_NOTFOUND;
+ LB_EVENT_TYPE eLET = LET_KEYMOVE;
+
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ bool bShift = aKeyCode.IsShift();
+ bool bCtrl = aKeyCode.IsMod1() || aKeyCode.IsMod3();
+ bool bMod2 = aKeyCode.IsMod2();
+ bool bDone = false;
+ bool bHandleKey = false;
+
+ switch( aKeyCode.GetCode() )
+ {
+ case KEY_UP:
+ {
+ if ( IsReadOnly() )
+ {
+ if ( GetTopEntry() )
+ SetTopEntry( GetTopEntry()-1 );
+ }
+ else if ( !bMod2 )
+ {
+ if( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = maEntryList.FindFirstSelectable( 0 );
+ }
+ else if ( mnCurrentPos )
+ {
+ // search first selectable above the current position
+ nSelect = maEntryList.FindFirstSelectable( mnCurrentPos - 1, false );
+ }
+
+ if( ( nSelect != LISTBOX_ENTRY_NOTFOUND ) && ( nSelect < mnTop ) )
+ SetTopEntry( mnTop-1 );
+
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_DOWN:
+ {
+ if ( IsReadOnly() )
+ {
+ SetTopEntry( GetTopEntry()+1 );
+ }
+ else if ( !bMod2 )
+ {
+ if( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = maEntryList.FindFirstSelectable( 0 );
+ }
+ else if ( (mnCurrentPos+1) < maEntryList.GetEntryCount() )
+ {
+ // search first selectable below the current position
+ nSelect = maEntryList.FindFirstSelectable( mnCurrentPos + 1 );
+ }
+
+ if( ( nSelect != LISTBOX_ENTRY_NOTFOUND ) && ( nSelect >= GetLastVisibleEntry() ) )
+ SetTopEntry( mnTop+1 );
+
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_PAGEUP:
+ {
+ if ( IsReadOnly() )
+ {
+ sal_Int32 nCurVis = GetLastVisibleEntry() - mnTop +1;
+ SetTopEntry( ( mnTop > nCurVis ) ?
+ (mnTop-nCurVis) : 0 );
+ }
+ else if ( !bCtrl && !bMod2 )
+ {
+ if( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = maEntryList.FindFirstSelectable( 0 );
+ }
+ else if ( mnCurrentPos )
+ {
+ if( mnCurrentPos == mnTop )
+ {
+ sal_Int32 nCurVis = GetLastVisibleEntry() - mnTop +1;
+ SetTopEntry( ( mnTop > nCurVis ) ? ( mnTop-nCurVis+1 ) : 0 );
+ }
+
+ // find first selectable starting from mnTop looking forward
+ nSelect = maEntryList.FindFirstSelectable( mnTop );
+ }
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_PAGEDOWN:
+ {
+ if ( IsReadOnly() )
+ {
+ SetTopEntry( GetLastVisibleEntry() );
+ }
+ else if ( !bCtrl && !bMod2 )
+ {
+ if( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = maEntryList.FindFirstSelectable( 0 );
+ }
+ else if ( (mnCurrentPos+1) < maEntryList.GetEntryCount() )
+ {
+ sal_Int32 nCount = maEntryList.GetEntryCount();
+ sal_Int32 nCurVis = GetLastVisibleEntry() - mnTop;
+ sal_Int32 nTmp = std::min( nCurVis, nCount );
+ nTmp += mnTop - 1;
+ if( mnCurrentPos == nTmp && mnCurrentPos != nCount - 1 )
+ {
+ tools::Long nTmp2 = std::min( static_cast<tools::Long>(nCount-nCurVis), static_cast<tools::Long>(static_cast<tools::Long>(mnTop)+static_cast<tools::Long>(nCurVis)-1) );
+ nTmp2 = std::max( tools::Long(0) , nTmp2 );
+ nTmp = static_cast<sal_Int32>(nTmp2+(nCurVis-1) );
+ SetTopEntry( static_cast<sal_Int32>(nTmp2) );
+ }
+ // find first selectable starting from nTmp looking backwards
+ nSelect = maEntryList.FindFirstSelectable( nTmp, false );
+ }
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_HOME:
+ {
+ if ( IsReadOnly() )
+ {
+ SetTopEntry( 0 );
+ }
+ else if ( !bCtrl && !bMod2 && mnCurrentPos )
+ {
+ nSelect = maEntryList.FindFirstSelectable( maEntryList.GetEntryCount() ? 0 : LISTBOX_ENTRY_NOTFOUND );
+ if( mnTop != 0 )
+ SetTopEntry( 0 );
+
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_END:
+ {
+ if ( IsReadOnly() )
+ {
+ SetTopEntry( 0xFFFF );
+ }
+ else if ( !bCtrl && !bMod2 )
+ {
+ if( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = maEntryList.FindFirstSelectable( 0 );
+ }
+ else if ( (mnCurrentPos+1) < maEntryList.GetEntryCount() )
+ {
+ sal_Int32 nCount = maEntryList.GetEntryCount();
+ nSelect = maEntryList.FindFirstSelectable( nCount - 1, false );
+ sal_Int32 nCurVis = GetLastVisibleEntry() - mnTop + 1;
+ if( nCount > nCurVis )
+ SetTopEntry( nCount - nCurVis );
+ }
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_LEFT:
+ {
+ if ( !bCtrl && !bMod2 )
+ {
+ ScrollHorz( -HORZ_SCROLL );
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_RIGHT:
+ {
+ if ( !bCtrl && !bMod2 )
+ {
+ ScrollHorz( HORZ_SCROLL );
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_RETURN:
+ {
+ if ( !bMod2 && !IsReadOnly() )
+ {
+ mnSelectModifier = rKEvt.GetKeyCode().GetModifier();
+ ImplCallSelect();
+ bDone = false; // do not catch RETURN
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_SPACE:
+ {
+ if ( !bMod2 && !IsReadOnly() )
+ {
+ if( mbMulti && ( !mbSimpleMode || ( mbSimpleMode && bCtrl && !bShift ) ) )
+ {
+ nSelect = mnCurrentPos;
+ eLET = LET_KEYSPACE;
+ }
+ bDone = true;
+ }
+ bHandleKey = true;
+ }
+ break;
+
+ case KEY_A:
+ {
+ if( bCtrl && mbMulti )
+ {
+ // paint only once
+ bool bUpdates = IsUpdateMode();
+ SetUpdateMode( false );
+
+ sal_Int32 nEntryCount = maEntryList.GetEntryCount();
+ for( sal_Int32 i = 0; i < nEntryCount; i++ )
+ SelectEntry( i, true );
+
+ // tdf#97066 - Update selected items
+ ImplCallSelect();
+
+ // restore update mode
+ SetUpdateMode( bUpdates );
+ Invalidate();
+
+ maQuickSelectionEngine.Reset();
+
+ bDone = true;
+ }
+ else
+ {
+ bHandleKey = true;
+ }
+ }
+ break;
+
+ default:
+ bHandleKey = true;
+ break;
+ }
+ if (bHandleKey && !IsReadOnly())
+ {
+ bDone = maQuickSelectionEngine.HandleKeyEvent( rKEvt );
+ }
+
+ if ( ( nSelect != LISTBOX_ENTRY_NOTFOUND )
+ && ( ( !maEntryList.IsEntryPosSelected( nSelect ) )
+ || ( eLET == LET_KEYSPACE )
+ )
+ )
+ {
+ SAL_WARN_IF( maEntryList.IsEntryPosSelected( nSelect ) && !mbMulti, "vcl", "ImplListBox: Selecting same Entry" );
+ sal_Int32 nCount = maEntryList.GetEntryCount();
+ if (nSelect >= nCount)
+ nSelect = nCount ? nCount-1 : LISTBOX_ENTRY_NOTFOUND;
+ bool bCurPosChange = (mnCurrentPos != nSelect);
+ mnCurrentPos = nSelect;
+ if(SelectEntries( nSelect, eLET, bShift, bCtrl, bCurPosChange))
+ {
+ // tdf#129043 Correctly deliver events when changing values with arrow keys in combobox
+ if (mbIsDropdown && IsReallyVisible())
+ mbTravelSelect = true;
+ mnSelectModifier = rKEvt.GetKeyCode().GetModifier();
+ ImplCallSelect();
+ mbTravelSelect = false;
+ }
+ }
+
+ return bDone;
+}
+
+namespace
+{
+ vcl::StringEntryIdentifier lcl_getEntry( const ImplEntryList& _rList, sal_Int32 _nPos, OUString& _out_entryText )
+ {
+ OSL_PRECOND( ( _nPos != LISTBOX_ENTRY_NOTFOUND ), "lcl_getEntry: invalid position!" );
+ sal_Int32 nEntryCount( _rList.GetEntryCount() );
+ if ( _nPos >= nEntryCount )
+ _nPos = 0;
+ _out_entryText = _rList.GetEntryText( _nPos );
+
+ // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
+ // => normalize
+ return reinterpret_cast< vcl::StringEntryIdentifier >( _nPos + 1 );
+ }
+
+ sal_Int32 lcl_getEntryPos( vcl::StringEntryIdentifier _entry )
+ {
+ // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
+ return static_cast< sal_Int32 >( reinterpret_cast< sal_Int64 >( _entry ) ) - 1;
+ }
+}
+
+vcl::StringEntryIdentifier ImplListBoxWindow::CurrentEntry( OUString& _out_entryText ) const
+{
+ return lcl_getEntry( GetEntryList(), ( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND ) ? 0 : mnCurrentPos, _out_entryText );
+}
+
+vcl::StringEntryIdentifier ImplListBoxWindow::NextEntry( vcl::StringEntryIdentifier _currentEntry, OUString& _out_entryText ) const
+{
+ sal_Int32 nNextPos = lcl_getEntryPos( _currentEntry ) + 1;
+ return lcl_getEntry( GetEntryList(), nNextPos, _out_entryText );
+}
+
+void ImplListBoxWindow::SelectEntry( vcl::StringEntryIdentifier _entry )
+{
+ sal_Int32 nSelect = lcl_getEntryPos( _entry );
+ if ( maEntryList.IsEntryPosSelected( nSelect ) )
+ {
+ // ignore that. This method is a callback from the QuickSelectionEngine, which means the user attempted
+ // to select the given entry by typing its starting letters. No need to act.
+ return;
+ }
+
+ // normalize
+ OSL_ENSURE( nSelect < maEntryList.GetEntryCount(), "ImplListBoxWindow::SelectEntry: how that?" );
+ sal_Int32 nCount = maEntryList.GetEntryCount();
+ if (nSelect >= nCount)
+ nSelect = nCount ? nCount-1 : LISTBOX_ENTRY_NOTFOUND;
+
+ // make visible
+ ShowProminentEntry( nSelect );
+
+ // actually select
+ mnCurrentPos = nSelect;
+ if ( SelectEntries( nSelect, LET_KEYMOVE ) )
+ {
+ mbTravelSelect = true;
+ mnSelectModifier = 0;
+ ImplCallSelect();
+ mbTravelSelect = false;
+ }
+}
+
+void ImplListBoxWindow::ImplPaint(vcl::RenderContext& rRenderContext, sal_Int32 nPos)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ const ImplEntryType* pEntry = maEntryList.GetEntryPtr( nPos );
+ if (!pEntry)
+ return;
+
+ tools::Long nWidth = GetOutputSizePixel().Width();
+ tools::Long nY = maEntryList.GetAddedHeight(nPos, mnTop);
+ tools::Rectangle aRect(Point(0, nY), Size(nWidth, pEntry->getHeightWithMargin()));
+
+ bool bSelected = maEntryList.IsEntryPosSelected(nPos);
+ if (bSelected)
+ {
+ rRenderContext.SetTextColor(!IsEnabled() ? rStyleSettings.GetDisableColor() : rStyleSettings.GetHighlightTextColor());
+ rRenderContext.SetFillColor(rStyleSettings.GetHighlightColor());
+ rRenderContext.SetLineColor();
+ rRenderContext.DrawRect(aRect);
+ }
+ else
+ {
+ ApplySettings(rRenderContext);
+ if (!IsEnabled())
+ rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
+ }
+ rRenderContext.SetTextFillColor();
+
+ if (IsUserDrawEnabled())
+ {
+ mbInUserDraw = true;
+ mnUserDrawEntry = nPos;
+ aRect.AdjustLeft( -mnLeft );
+ if (nPos < GetEntryList().GetMRUCount())
+ nPos = GetEntryList().FindEntry(GetEntryList().GetEntryText(nPos));
+ nPos = nPos - GetEntryList().GetMRUCount();
+
+ UserDrawEvent aUDEvt(&rRenderContext, aRect, nPos, bSelected);
+ maUserDrawHdl.Call( &aUDEvt );
+ mbInUserDraw = false;
+ }
+ else
+ {
+ DrawEntry(rRenderContext, nPos, true, true);
+ }
+}
+
+void ImplListBoxWindow::DrawEntry(vcl::RenderContext& rRenderContext, sal_Int32 nPos, bool bDrawImage, bool bDrawText)
+{
+ const ImplEntryType* pEntry = maEntryList.GetEntryPtr(nPos);
+ if (!pEntry)
+ return;
+
+ tools::Long nEntryHeight = pEntry->getHeightWithMargin();
+
+ // when changing this function don't forget to adjust ImplWin::DrawEntry()
+
+ if (mbInUserDraw)
+ nPos = mnUserDrawEntry; // real entry, not the matching entry from MRU
+
+ tools::Long nY = maEntryList.GetAddedHeight(nPos, mnTop);
+
+ if (bDrawImage && maEntryList.HasImages())
+ {
+ Image aImage = maEntryList.GetEntryImage(nPos);
+ if (!!aImage)
+ {
+ Size aImgSz = aImage.GetSizePixel();
+ Point aPtImg(gnBorder - mnLeft, nY + ((nEntryHeight - aImgSz.Height()) / 2));
+
+ if (!IsZoom())
+ {
+ rRenderContext.DrawImage(aPtImg, aImage);
+ }
+ else
+ {
+ aImgSz.setWidth( CalcZoom(aImgSz.Width()) );
+ aImgSz.setHeight( CalcZoom(aImgSz.Height()) );
+ rRenderContext.DrawImage(aPtImg, aImgSz, aImage);
+ }
+
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ const sal_uInt16 nEdgeBlendingPercent(GetEdgeBlending() ? rStyleSettings.GetEdgeBlending() : 0);
+
+ if (nEdgeBlendingPercent && aImgSz.Width() && aImgSz.Height())
+ {
+ const Color& rTopLeft(rStyleSettings.GetEdgeBlendingTopLeftColor());
+ const Color& rBottomRight(rStyleSettings.GetEdgeBlendingBottomRightColor());
+ const sal_uInt8 nAlpha((nEdgeBlendingPercent * 255) / 100);
+ const BitmapEx aBlendFrame(createBlendFrame(aImgSz, nAlpha, rTopLeft, rBottomRight));
+
+ if (!aBlendFrame.IsEmpty())
+ {
+ rRenderContext.DrawBitmapEx(aPtImg, aBlendFrame);
+ }
+ }
+ }
+ }
+
+ if (bDrawText)
+ {
+ OUString aStr(maEntryList.GetEntryText(nPos));
+ if (!aStr.isEmpty())
+ {
+ tools::Long nMaxWidth = std::max(mnMaxWidth, GetOutputSizePixel().Width() - 2 * gnBorder);
+ // a multiline entry should only be as wide as the window
+ if (pEntry->mnFlags & ListBoxEntryFlags::MultiLine)
+ nMaxWidth = GetOutputSizePixel().Width() - 2 * gnBorder;
+
+ tools::Rectangle aTextRect(Point(gnBorder - mnLeft, nY),
+ Size(nMaxWidth, nEntryHeight));
+
+ if (maEntryList.HasEntryImage(nPos) || IsUserDrawEnabled())
+ {
+ tools::Long nImageWidth = std::max(mnMaxImgWidth, maUserItemSize.Width());
+ aTextRect.AdjustLeft(nImageWidth + IMG_TXT_DISTANCE );
+ }
+
+ DrawTextFlags nDrawStyle = ImplGetTextStyle();
+ if (pEntry->mnFlags & ListBoxEntryFlags::MultiLine)
+ nDrawStyle |= MULTILINE_ENTRY_DRAW_FLAGS;
+ if (pEntry->mnFlags & ListBoxEntryFlags::DrawDisabled)
+ nDrawStyle |= DrawTextFlags::Disable;
+
+ rRenderContext.DrawText(aTextRect, aStr, nDrawStyle);
+ }
+ }
+
+ if ( !maSeparators.empty() && ( isSeparator(nPos) || isSeparator(nPos-1) ) )
+ {
+ Color aOldLineColor(rRenderContext.GetLineColor());
+ rRenderContext.SetLineColor((GetBackground() != COL_LIGHTGRAY) ? COL_LIGHTGRAY : COL_GRAY);
+ Point aStartPos(0, nY);
+ if (isSeparator(nPos))
+ aStartPos.AdjustY(pEntry->getHeightWithMargin() - 1 );
+ Point aEndPos(aStartPos);
+ aEndPos.setX( GetOutputSizePixel().Width() );
+ rRenderContext.DrawLine(aStartPos, aEndPos);
+ rRenderContext.SetLineColor(aOldLineColor);
+ }
+}
+
+void ImplListBoxWindow::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ const_cast<ImplListBoxWindow*>(this)->Invalidate(tools::Rectangle(Point(0, 0), GetOutDev()->GetOutputSize()));
+}
+
+void ImplListBoxWindow::ImplDoPaint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ sal_Int32 nCount = maEntryList.GetEntryCount();
+
+ bool bShowFocusRect = mbHasFocusRect;
+ if (mbHasFocusRect)
+ ImplHideFocusRect();
+
+ tools::Long nY = 0; // + gnBorder;
+ tools::Long nHeight = GetOutputSizePixel().Height();// - mnMaxHeight + gnBorder;
+
+ for (sal_Int32 i = mnTop; i < nCount && nY < nHeight + mnMaxHeight; i++)
+ {
+ const ImplEntryType* pEntry = maEntryList.GetEntryPtr(i);
+ tools::Long nEntryHeight = pEntry->getHeightWithMargin();
+ if (nY + nEntryHeight >= rRect.Top() &&
+ nY <= rRect.Bottom() + mnMaxHeight)
+ {
+ ImplPaint(rRenderContext, i);
+ }
+ nY += nEntryHeight;
+ }
+
+ tools::Long nHeightDiff = maEntryList.GetAddedHeight(mnCurrentPos, mnTop);
+ maFocusRect.SetPos(Point(0, nHeightDiff));
+ Size aSz(maFocusRect.GetWidth(), maEntryList.GetEntryHeight(mnCurrentPos));
+ maFocusRect.SetSize(aSz);
+ if (HasFocus() && bShowFocusRect)
+ ImplShowFocusRect();
+}
+
+void ImplListBoxWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (SupportsDoubleBuffering())
+ {
+ // This widget is explicitly double-buffered, so avoid partial paints.
+ tools::Rectangle aRect(Point(0, 0), GetOutputSizePixel());
+ ImplDoPaint(rRenderContext, aRect);
+ }
+ else
+ ImplDoPaint(rRenderContext, rRect);
+}
+
+sal_uInt16 ImplListBoxWindow::GetDisplayLineCount() const
+{
+ // FIXME: ListBoxEntryFlags::MultiLine
+
+ const sal_Int32 nCount = maEntryList.GetEntryCount()-mnTop;
+ tools::Long nHeight = GetOutputSizePixel().Height();// - mnMaxHeight + gnBorder;
+ sal_uInt16 nEntries = static_cast< sal_uInt16 >( ( nHeight + mnMaxHeight - 1 ) / mnMaxHeight );
+ if( nEntries > nCount )
+ nEntries = static_cast<sal_uInt16>(nCount);
+
+ return nEntries;
+}
+
+void ImplListBoxWindow::Resize()
+{
+ Control::Resize();
+
+ bool bShowFocusRect = mbHasFocusRect;
+ if ( bShowFocusRect )
+ ImplHideFocusRect();
+
+ if( mnCurrentPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ Size aSz( GetOutputSizePixel().Width(), maEntryList.GetEntryHeight( mnCurrentPos ) );
+ maFocusRect.SetSize( aSz );
+ }
+
+ if ( bShowFocusRect )
+ ImplShowFocusRect();
+
+ ImplClearLayoutData();
+}
+
+void ImplListBoxWindow::GetFocus()
+{
+ sal_Int32 nPos = mnCurrentPos;
+ if ( nPos == LISTBOX_ENTRY_NOTFOUND )
+ nPos = 0;
+ tools::Long nHeightDiff = maEntryList.GetAddedHeight( nPos, mnTop );
+ maFocusRect.SetPos( Point( 0, nHeightDiff ) );
+ Size aSz( maFocusRect.GetWidth(), maEntryList.GetEntryHeight( nPos ) );
+ maFocusRect.SetSize( aSz );
+ ImplShowFocusRect();
+ Control::GetFocus();
+}
+
+void ImplListBoxWindow::LoseFocus()
+{
+ ImplHideFocusRect();
+ Control::LoseFocus();
+}
+
+void ImplListBoxWindow::SetTopEntry( sal_Int32 nTop )
+{
+ if( maEntryList.GetEntryCount() == 0 )
+ return;
+
+ tools::Long nWHeight = PixelToLogic( GetSizePixel() ).Height();
+
+ sal_Int32 nLastEntry = maEntryList.GetEntryCount()-1;
+ if( nTop > nLastEntry )
+ nTop = nLastEntry;
+ const ImplEntryType* pLast = maEntryList.GetEntryPtr( nLastEntry );
+ while( nTop > 0 && maEntryList.GetAddedHeight( nLastEntry, nTop-1 ) + pLast->getHeightWithMargin() <= nWHeight )
+ nTop--;
+
+ if ( nTop == mnTop )
+ return;
+
+ ImplClearLayoutData();
+ tools::Long nDiff = maEntryList.GetAddedHeight( mnTop, nTop );
+ PaintImmediately();
+ ImplHideFocusRect();
+ mnTop = nTop;
+ Scroll( 0, nDiff );
+ PaintImmediately();
+ if( HasFocus() )
+ ImplShowFocusRect();
+ maScrollHdl.Call( this );
+}
+
+void ImplListBoxWindow::ShowProminentEntry( sal_Int32 nEntryPos )
+{
+ sal_Int32 nPos = nEntryPos;
+ auto nWHeight = PixelToLogic( GetSizePixel() ).Height();
+ while( nEntryPos > 0 && maEntryList.GetAddedHeight( nPos+1, nEntryPos ) < nWHeight/2 )
+ nEntryPos--;
+
+ SetTopEntry( nEntryPos );
+}
+
+void ImplListBoxWindow::SetLeftIndent( tools::Long n )
+{
+ ScrollHorz( n - mnLeft );
+}
+
+void ImplListBoxWindow::ScrollHorz( tools::Long n )
+{
+ tools::Long nDiff = 0;
+ if ( n > 0 )
+ {
+ tools::Long nWidth = GetOutputSizePixel().Width();
+ if( ( mnMaxWidth - mnLeft + n ) > nWidth )
+ nDiff = n;
+ }
+ else if ( n < 0 )
+ {
+ if( mnLeft )
+ {
+ tools::Long nAbs = -n;
+ nDiff = - std::min( mnLeft, nAbs );
+ }
+ }
+
+ if ( nDiff )
+ {
+ ImplClearLayoutData();
+ mnLeft = sal::static_int_cast<sal_uInt16>(mnLeft + nDiff);
+ PaintImmediately();
+ ImplHideFocusRect();
+ Scroll( -nDiff, 0 );
+ PaintImmediately();
+ if( HasFocus() )
+ ImplShowFocusRect();
+ maScrollHdl.Call( this );
+ }
+}
+
+void ImplListBoxWindow::SetSeparatorPos( sal_Int32 n )
+{
+ maSeparators.clear();
+
+ if ( n != LISTBOX_ENTRY_NOTFOUND )
+ {
+ maSeparators.insert( n );
+ }
+}
+
+sal_Int32 ImplListBoxWindow::GetSeparatorPos() const
+{
+ if (!maSeparators.empty())
+ return *(maSeparators.begin());
+ else
+ return LISTBOX_ENTRY_NOTFOUND;
+}
+
+bool ImplListBoxWindow::isSeparator( const sal_Int32 &n) const
+{
+ return maSeparators.find(n) != maSeparators.end();
+}
+
+Size ImplListBoxWindow::CalcSize(sal_Int32 nMaxLines) const
+{
+ // FIXME: ListBoxEntryFlags::MultiLine
+
+ Size aSz;
+ aSz.setHeight(nMaxLines * GetEntryHeightWithMargin());
+ aSz.setWidth( mnMaxWidth + 2*gnBorder );
+ return aSz;
+}
+
+tools::Rectangle ImplListBoxWindow::GetBoundingRectangle( sal_Int32 nItem ) const
+{
+ const ImplEntryType* pEntry = maEntryList.GetEntryPtr( nItem );
+ Size aSz( GetSizePixel().Width(), pEntry ? pEntry->getHeightWithMargin() : GetEntryHeightWithMargin() );
+ tools::Long nY = maEntryList.GetAddedHeight( nItem, GetTopEntry() ) + GetEntryList().GetMRUCount()*GetEntryHeightWithMargin();
+ tools::Rectangle aRect( Point( 0, nY ), aSz );
+ return aRect;
+}
+
+void ImplListBoxWindow::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( nType == StateChangedType::Zoom )
+ {
+ ApplySettings(*GetOutDev());
+ ImplCalcMetrics();
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::UpdateMode )
+ {
+ if ( IsUpdateMode() && IsReallyVisible() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlFont )
+ {
+ ApplySettings(*GetOutDev());
+ ImplCalcMetrics();
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ else if( nType == StateChangedType::Enable )
+ {
+ Invalidate();
+ }
+
+ ImplClearLayoutData();
+}
+
+void ImplListBoxWindow::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplClearLayoutData();
+ ApplySettings(*GetOutDev());
+ ImplCalcMetrics();
+ Invalidate();
+ }
+}
+
+DrawTextFlags ImplListBoxWindow::ImplGetTextStyle() const
+{
+ DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
+
+ if (maEntryList.HasImages())
+ nTextStyle |= DrawTextFlags::Left;
+ else if (mbCenter)
+ nTextStyle |= DrawTextFlags::Center;
+ else if (mbRight)
+ nTextStyle |= DrawTextFlags::Right;
+ else
+ nTextStyle |= DrawTextFlags::Left;
+
+ return nTextStyle;
+}
+
+ImplListBox::ImplListBox( vcl::Window* pParent, WinBits nWinStyle ) :
+ Control( pParent, nWinStyle ),
+ maLBWindow(VclPtr<ImplListBoxWindow>::Create( this, nWinStyle&(~WB_BORDER) ))
+{
+ // for native widget rendering we must be able to detect this window type
+ SetType( WindowType::LISTBOXWINDOW );
+
+ mpVScrollBar = VclPtr<ScrollBar>::Create( this, WB_VSCROLL | WB_DRAG );
+ mpHScrollBar = VclPtr<ScrollBar>::Create( this, WB_HSCROLL | WB_DRAG );
+ mpScrollBarBox = VclPtr<ScrollBarBox>::Create( this );
+
+ Link<ScrollBar*,void> aLink( LINK( this, ImplListBox, ScrollBarHdl ) );
+ mpVScrollBar->SetScrollHdl( aLink );
+ mpHScrollBar->SetScrollHdl( aLink );
+
+ mbVScroll = false;
+ mbHScroll = false;
+ mbAutoHScroll = ( nWinStyle & WB_AUTOHSCROLL );
+ mbEdgeBlending = false;
+
+ maLBWindow->SetScrollHdl( LINK( this, ImplListBox, LBWindowScrolled ) );
+ maLBWindow->SetMRUChangedHdl( LINK( this, ImplListBox, MRUChanged ) );
+ maLBWindow->SetEdgeBlending(GetEdgeBlending());
+ maLBWindow->Show();
+}
+
+ImplListBox::~ImplListBox()
+{
+ disposeOnce();
+}
+
+void ImplListBox::dispose()
+{
+ mpHScrollBar.disposeAndClear();
+ mpVScrollBar.disposeAndClear();
+ mpScrollBarBox.disposeAndClear();
+ maLBWindow.disposeAndClear();
+ Control::dispose();
+}
+
+void ImplListBox::Clear()
+{
+ maLBWindow->Clear();
+ if ( GetEntryList().GetMRUCount() )
+ {
+ maLBWindow->GetEntryList().SetMRUCount( 0 );
+ maLBWindow->SetSeparatorPos( LISTBOX_ENTRY_NOTFOUND );
+ }
+ mpVScrollBar->SetThumbPos( 0 );
+ mpHScrollBar->SetThumbPos( 0 );
+ CompatStateChanged( StateChangedType::Data );
+}
+
+sal_Int32 ImplListBox::InsertEntry( sal_Int32 nPos, const OUString& rStr )
+{
+ ImplEntryType* pNewEntry = new ImplEntryType( rStr );
+ sal_Int32 nNewPos = maLBWindow->InsertEntry( nPos, pNewEntry );
+ CompatStateChanged( StateChangedType::Data );
+ return nNewPos;
+}
+
+sal_Int32 ImplListBox::InsertEntry( sal_Int32 nPos, const OUString& rStr, const Image& rImage )
+{
+ ImplEntryType* pNewEntry = new ImplEntryType( rStr, rImage );
+ sal_Int32 nNewPos = maLBWindow->InsertEntry( nPos, pNewEntry );
+ CompatStateChanged( StateChangedType::Data );
+ return nNewPos;
+}
+
+void ImplListBox::RemoveEntry( sal_Int32 nPos )
+{
+ maLBWindow->RemoveEntry( nPos );
+ CompatStateChanged( StateChangedType::Data );
+}
+
+void ImplListBox::SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags )
+{
+ maLBWindow->SetEntryFlags( nPos, nFlags );
+}
+
+void ImplListBox::SelectEntry( sal_Int32 nPos, bool bSelect )
+{
+ maLBWindow->SelectEntry( nPos, bSelect );
+}
+
+void ImplListBox::SetNoSelection()
+{
+ maLBWindow->DeselectAll();
+}
+
+void ImplListBox::GetFocus()
+{
+ if (maLBWindow)
+ maLBWindow->GrabFocus();
+ else
+ Control::GetFocus();
+}
+
+void ImplListBox::Resize()
+{
+ Control::Resize();
+ ImplResizeControls();
+ ImplCheckScrollBars();
+}
+
+IMPL_LINK_NOARG(ImplListBox, MRUChanged, LinkParamNone*, void)
+{
+ CompatStateChanged( StateChangedType::Data );
+}
+
+IMPL_LINK_NOARG(ImplListBox, LBWindowScrolled, ImplListBoxWindow*, void)
+{
+ tools::Long nSet = GetTopEntry();
+ if( nSet > mpVScrollBar->GetRangeMax() )
+ mpVScrollBar->SetRangeMax( GetEntryList().GetEntryCount() );
+ mpVScrollBar->SetThumbPos( GetTopEntry() );
+
+ mpHScrollBar->SetThumbPos( GetLeftIndent() );
+
+ maScrollHdl.Call( this );
+}
+
+IMPL_LINK( ImplListBox, ScrollBarHdl, ScrollBar*, pSB, void )
+{
+ sal_uInt16 nPos = static_cast<sal_uInt16>(pSB->GetThumbPos());
+ if( pSB == mpVScrollBar )
+ SetTopEntry( nPos );
+ else if( pSB == mpHScrollBar )
+ SetLeftIndent( nPos );
+ if( GetParent() )
+ GetParent()->Invalidate( InvalidateFlags::Update );
+}
+
+void ImplListBox::ImplCheckScrollBars()
+{
+ bool bArrange = false;
+
+ Size aOutSz = GetOutputSizePixel();
+ sal_Int32 nEntries = GetEntryList().GetEntryCount();
+ sal_uInt16 nMaxVisEntries = static_cast<sal_uInt16>(aOutSz.Height() / GetEntryHeightWithMargin());
+
+ // vertical ScrollBar
+ if( nEntries > nMaxVisEntries )
+ {
+ if( !mbVScroll )
+ bArrange = true;
+ mbVScroll = true;
+
+ // check of the scrolled-out region
+ if( GetEntryList().GetSelectedEntryCount() == 1 &&
+ GetEntryList().GetSelectedEntryPos( 0 ) != LISTBOX_ENTRY_NOTFOUND )
+ ShowProminentEntry( GetEntryList().GetSelectedEntryPos( 0 ) );
+ else
+ SetTopEntry( GetTopEntry() ); // MaxTop is being checked...
+ }
+ else
+ {
+ if( mbVScroll )
+ bArrange = true;
+ mbVScroll = false;
+ SetTopEntry( 0 );
+ }
+
+ // horizontal ScrollBar
+ if( mbAutoHScroll )
+ {
+ tools::Long nWidth = static_cast<sal_uInt16>(aOutSz.Width());
+ if ( mbVScroll )
+ nWidth -= mpVScrollBar->GetSizePixel().Width();
+
+ tools::Long nMaxWidth = GetMaxEntryWidth();
+ if( nWidth < nMaxWidth )
+ {
+ if( !mbHScroll )
+ bArrange = true;
+ mbHScroll = true;
+
+ if ( !mbVScroll ) // maybe we do need one now
+ {
+ nMaxVisEntries = static_cast<sal_uInt16>( ( aOutSz.Height() - mpHScrollBar->GetSizePixel().Height() ) / GetEntryHeightWithMargin() );
+ if( nEntries > nMaxVisEntries )
+ {
+ bArrange = true;
+ mbVScroll = true;
+
+ // check of the scrolled-out region
+ if( GetEntryList().GetSelectedEntryCount() == 1 &&
+ GetEntryList().GetSelectedEntryPos( 0 ) != LISTBOX_ENTRY_NOTFOUND )
+ ShowProminentEntry( GetEntryList().GetSelectedEntryPos( 0 ) );
+ else
+ SetTopEntry( GetTopEntry() ); // MaxTop is being checked...
+ }
+ }
+
+ // check of the scrolled-out region
+ sal_uInt16 nMaxLI = static_cast<sal_uInt16>(nMaxWidth - nWidth);
+ if ( nMaxLI < GetLeftIndent() )
+ SetLeftIndent( nMaxLI );
+ }
+ else
+ {
+ if( mbHScroll )
+ bArrange = true;
+ mbHScroll = false;
+ SetLeftIndent( 0 );
+ }
+ }
+
+ if( bArrange )
+ ImplResizeControls();
+
+ ImplInitScrollBars();
+}
+
+void ImplListBox::ImplInitScrollBars()
+{
+ Size aOutSz = maLBWindow->GetOutputSizePixel();
+
+ if ( mbVScroll )
+ {
+ sal_Int32 nEntries = GetEntryList().GetEntryCount();
+ sal_uInt16 nVisEntries = static_cast<sal_uInt16>(aOutSz.Height() / GetEntryHeightWithMargin());
+ mpVScrollBar->SetRangeMax( nEntries );
+ mpVScrollBar->SetVisibleSize( nVisEntries );
+ mpVScrollBar->SetPageSize( nVisEntries - 1 );
+ }
+
+ if ( mbHScroll )
+ {
+ mpHScrollBar->SetRangeMax( GetMaxEntryWidth() + HORZ_SCROLL );
+ mpHScrollBar->SetVisibleSize( static_cast<sal_uInt16>(aOutSz.Width()) );
+ mpHScrollBar->SetLineSize( HORZ_SCROLL );
+ mpHScrollBar->SetPageSize( aOutSz.Width() - HORZ_SCROLL );
+ }
+}
+
+void ImplListBox::ImplResizeControls()
+{
+ // Here we only position the Controls; if the Scrollbars are to be
+ // visible is already determined in ImplCheckScrollBars
+
+ Size aOutSz = GetOutputSizePixel();
+ tools::Long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
+ nSBWidth = CalcZoom( nSBWidth );
+
+ Size aInnerSz( aOutSz );
+ if ( mbVScroll )
+ aInnerSz.AdjustWidth( -nSBWidth );
+ if ( mbHScroll )
+ aInnerSz.AdjustHeight( -nSBWidth );
+
+ Point aWinPos( 0, 0 );
+ maLBWindow->SetPosSizePixel( aWinPos, aInnerSz );
+
+ // ScrollBarBox
+ if( mbVScroll && mbHScroll )
+ {
+ Point aBoxPos( aInnerSz.Width(), aInnerSz.Height() );
+ mpScrollBarBox->SetPosSizePixel( aBoxPos, Size( nSBWidth, nSBWidth ) );
+ mpScrollBarBox->Show();
+ }
+ else
+ {
+ mpScrollBarBox->Hide();
+ }
+
+ // vertical ScrollBar
+ if( mbVScroll )
+ {
+ // Scrollbar on left or right side?
+ Point aVPos( aOutSz.Width() - nSBWidth, 0 );
+ mpVScrollBar->SetPosSizePixel( aVPos, Size( nSBWidth, aInnerSz.Height() ) );
+ mpVScrollBar->Show();
+ }
+ else
+ {
+ mpVScrollBar->Hide();
+ // #107254# Don't reset top entry after resize, but check for max top entry
+ SetTopEntry( GetTopEntry() );
+ }
+
+ // horizontal ScrollBar
+ if( mbHScroll )
+ {
+ Point aHPos( 0, aOutSz.Height() - nSBWidth );
+ mpHScrollBar->SetPosSizePixel( aHPos, Size( aInnerSz.Width(), nSBWidth ) );
+ mpHScrollBar->Show();
+ }
+ else
+ {
+ mpHScrollBar->Hide();
+ SetLeftIndent( 0 );
+ }
+}
+
+void ImplListBox::StateChanged( StateChangedType nType )
+{
+ if ( nType == StateChangedType::InitShow )
+ {
+ ImplCheckScrollBars();
+ }
+ else if ( ( nType == StateChangedType::UpdateMode ) || ( nType == StateChangedType::Data ) )
+ {
+ bool bUpdate = IsUpdateMode();
+ maLBWindow->SetUpdateMode( bUpdate );
+ if ( bUpdate && IsReallyVisible() )
+ ImplCheckScrollBars();
+ }
+ else if( nType == StateChangedType::Enable )
+ {
+ mpHScrollBar->Enable( IsEnabled() );
+ mpVScrollBar->Enable( IsEnabled() );
+ mpScrollBarBox->Enable( IsEnabled() );
+ maLBWindow->Enable( IsEnabled() );
+
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Zoom )
+ {
+ maLBWindow->SetZoom( GetZoom() );
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlFont )
+ {
+ maLBWindow->SetControlFont( GetControlFont() );
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ maLBWindow->SetControlForeground( GetControlForeground() );
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ maLBWindow->SetControlBackground( GetControlBackground() );
+ }
+ else if( nType == StateChangedType::Mirroring )
+ {
+ maLBWindow->EnableRTL( IsRTLEnabled() );
+ mpHScrollBar->EnableRTL( IsRTLEnabled() );
+ mpVScrollBar->EnableRTL( IsRTLEnabled() );
+ ImplResizeControls();
+ }
+
+ Control::StateChanged( nType );
+}
+
+bool ImplListBox::EventNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+ if ( rNEvt.GetType() == MouseNotifyEvent::COMMAND )
+ {
+ const CommandEvent& rCEvt = *rNEvt.GetCommandEvent();
+ if ( rCEvt.GetCommand() == CommandEventId::Wheel )
+ {
+ const CommandWheelData* pData = rCEvt.GetWheelData();
+ if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) )
+ {
+ bDone = HandleScrollCommand( rCEvt, mpHScrollBar, mpVScrollBar );
+ }
+ }
+ else if (rCEvt.GetCommand() == CommandEventId::Gesture)
+ {
+ bDone = HandleScrollCommand(rCEvt, mpHScrollBar, mpVScrollBar);
+ }
+ }
+
+ return bDone || Window::EventNotify( rNEvt );
+}
+
+const Wallpaper& ImplListBox::GetDisplayBackground() const
+{
+ return maLBWindow->GetDisplayBackground();
+}
+
+bool ImplListBox::HandleWheelAsCursorTravel(const CommandEvent& rCEvt, Control& rControl)
+{
+ bool bDone = false;
+ if ( rCEvt.GetCommand() == CommandEventId::Wheel )
+ {
+ const CommandWheelData* pData = rCEvt.GetWheelData();
+ if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) )
+ {
+ if (!rControl.HasChildPathFocus())
+ rControl.GrabFocus();
+ sal_uInt16 nKey = ( pData->GetDelta() < 0 ) ? KEY_DOWN : KEY_UP;
+ KeyEvent aKeyEvent( 0, vcl::KeyCode( nKey ) );
+ bDone = ProcessKeyInput( aKeyEvent );
+ }
+ }
+ return bDone;
+}
+
+void ImplListBox::SetMRUEntries( std::u16string_view rEntries, sal_Unicode cSep )
+{
+ bool bChanges = GetEntryList().GetMRUCount() != 0;
+
+ // Remove old MRU entries
+ for ( sal_Int32 n = GetEntryList().GetMRUCount();n; )
+ maLBWindow->RemoveEntry( --n );
+
+ sal_Int32 nMRUCount = 0;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OUString aEntry( o3tl::getToken(rEntries, 0, cSep, nIndex ) );
+ // Accept only existing entries
+ if ( GetEntryList().FindEntry( aEntry ) != LISTBOX_ENTRY_NOTFOUND )
+ {
+ ImplEntryType* pNewEntry = new ImplEntryType( aEntry );
+ maLBWindow->InsertEntry(nMRUCount++, pNewEntry, false);
+ bChanges = true;
+ }
+ }
+ while ( nIndex >= 0 );
+
+ if ( bChanges )
+ {
+ maLBWindow->GetEntryList().SetMRUCount( nMRUCount );
+ SetSeparatorPos( nMRUCount ? nMRUCount-1 : 0 );
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+OUString ImplListBox::GetMRUEntries( sal_Unicode cSep ) const
+{
+ OUStringBuffer aEntries;
+ for ( sal_Int32 n = 0; n < GetEntryList().GetMRUCount(); n++ )
+ {
+ aEntries.append(GetEntryList().GetEntryText( n ));
+ if( n < ( GetEntryList().GetMRUCount() - 1 ) )
+ aEntries.append(cSep);
+ }
+ return aEntries.makeStringAndClear();
+}
+
+void ImplListBox::SetEdgeBlending(bool bNew)
+{
+ if(mbEdgeBlending != bNew)
+ {
+ mbEdgeBlending = bNew;
+ maLBWindow->SetEdgeBlending(GetEdgeBlending());
+ }
+}
+
+ImplWin::ImplWin( vcl::Window* pParent, WinBits nWinStyle ) :
+ Control ( pParent, nWinStyle )
+{
+ if ( IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)
+ && ! IsNativeControlSupported(ControlType::Listbox, ControlPart::ButtonDown) )
+ SetBackground();
+ else
+ SetBackground( Wallpaper( GetSettings().GetStyleSettings().GetFieldColor() ) );
+
+ ImplGetWindowImpl()->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects;
+
+ mbEdgeBlending = false;
+ mnItemPos = LISTBOX_ENTRY_NOTFOUND;
+}
+
+void ImplWin::MouseButtonDown( const MouseEvent& )
+{
+ if( IsEnabled() )
+ {
+ maMBDownHdl.Call(this);
+ }
+}
+
+void ImplWin::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ ImplWin* pThis = const_cast<ImplWin*>(this);
+ pThis->ImplDraw(*pThis->GetOutDev(), true);
+}
+
+bool ImplWin::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && (pMouseEvt->IsEnterWindow() || pMouseEvt->IsLeaveWindow()) )
+ {
+ // trigger redraw as mouse over state has changed
+ if ( IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)
+ && ! IsNativeControlSupported(ControlType::Listbox, ControlPart::ButtonDown) )
+ {
+ GetParent()->GetWindow( GetWindowType::Border )->Invalidate( InvalidateFlags::NoErase );
+ }
+ }
+ }
+
+ return Control::PreNotify(rNEvt);
+}
+
+void ImplWin::ImplDraw(vcl::RenderContext& rRenderContext, bool bLayout)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ if (!bLayout)
+ {
+ bool bNativeOK = false;
+ bool bHasFocus = HasFocus();
+ bool bIsEnabled = IsEnabled();
+
+ ControlState nState = ControlState::ENABLED;
+ if (rRenderContext.IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)
+ && rRenderContext.IsNativeControlSupported(ControlType::Listbox, ControlPart::HasBackgroundTexture) )
+ {
+ // Repaint the (focused) area similarly to
+ // ImplSmallBorderWindowView::DrawWindow() in
+ // vcl/source/window/brdwin.cxx
+ vcl::Window *pWin = GetParent();
+
+ ImplControlValue aControlValue;
+ bIsEnabled &= pWin->IsEnabled();
+ if ( !bIsEnabled )
+ nState &= ~ControlState::ENABLED;
+ bHasFocus |= pWin->HasFocus();
+ if ( bHasFocus )
+ nState |= ControlState::FOCUSED;
+
+ // The listbox is painted over the entire control including the
+ // border, but ImplWin does not contain the border => correction
+ // needed.
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ pWin->GetBorder( nLeft, nTop, nRight, nBottom );
+ Point aPoint( -nLeft, -nTop );
+ tools::Rectangle aCtrlRegion( aPoint - GetPosPixel(), pWin->GetSizePixel() );
+
+ bool bMouseOver = false;
+ vcl::Window *pChild = pWin->GetWindow( GetWindowType::FirstChild );
+ while( pChild )
+ {
+ bMouseOver = pChild->IsMouseOver();
+ if (bMouseOver)
+ break;
+ pChild = pChild->GetWindow( GetWindowType::Next );
+ }
+ if( bMouseOver )
+ nState |= ControlState::ROLLOVER;
+
+ // if parent has no border, then nobody has drawn the background
+ // since no border window exists. so draw it here.
+ WinBits nParentStyle = pWin->GetStyle();
+ if( ! (nParentStyle & WB_BORDER) || (nParentStyle & WB_NOBORDER) )
+ {
+ tools::Rectangle aParentRect( Point( 0, 0 ), pWin->GetSizePixel() );
+ pWin->GetOutDev()->DrawNativeControl( ControlType::Listbox, ControlPart::Entire, aParentRect,
+ nState, aControlValue, OUString() );
+ }
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Listbox, ControlPart::Entire, aCtrlRegion,
+ nState, aControlValue, OUString());
+ }
+
+ if (bIsEnabled)
+ {
+ if (bHasFocus && !ImplGetSVData()->maNWFData.mbDDListBoxNoTextArea)
+ {
+ if ( !ImplGetSVData()->maNWFData.mbNoFocusRects )
+ {
+ rRenderContext.SetFillColor( rStyleSettings.GetHighlightColor() );
+ rRenderContext.SetTextColor( rStyleSettings.GetHighlightTextColor() );
+ }
+ else
+ {
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor();
+ rRenderContext.SetTextColor( rStyleSettings.GetFieldTextColor() );
+ }
+ rRenderContext.DrawRect( maFocusRect );
+ }
+ else
+ {
+ Color aColor;
+ if (IsControlForeground())
+ aColor = GetControlForeground();
+ else if (ImplGetSVData()->maNWFData.mbDDListBoxNoTextArea)
+ {
+ if( bNativeOK && (nState & ControlState::ROLLOVER) )
+ aColor = rStyleSettings.GetButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetButtonTextColor();
+ }
+ else
+ {
+ if( bNativeOK && (nState & ControlState::ROLLOVER) )
+ aColor = rStyleSettings.GetFieldRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetFieldTextColor();
+ }
+ rRenderContext.SetTextColor(aColor);
+ if (!bNativeOK)
+ rRenderContext.Erase(maFocusRect);
+ }
+ }
+ else // Disabled
+ {
+ rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
+ if (!bNativeOK)
+ rRenderContext.Erase(maFocusRect);
+ }
+ }
+
+ DrawEntry(rRenderContext, bLayout);
+}
+
+void ImplWin::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ ApplyControlFont(rRenderContext, rStyleSettings.GetFieldFont());
+ ApplyControlForeground(rRenderContext, rStyleSettings.GetFieldTextColor());
+
+ if (IsControlBackground())
+ rRenderContext.SetBackground(GetControlBackground());
+ else
+ rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
+}
+
+void ImplWin::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ ImplDraw(rRenderContext);
+}
+
+void ImplWin::DrawEntry(vcl::RenderContext& rRenderContext, bool bLayout)
+{
+ tools::Long nBorder = 1;
+ Size aOutSz(GetOutputSizePixel());
+
+ bool bImage = !!maImage;
+ if (bImage && !bLayout)
+ {
+ DrawImageFlags nStyle = DrawImageFlags::NONE;
+ Size aImgSz = maImage.GetSizePixel();
+ Point aPtImg( nBorder, ( ( aOutSz.Height() - aImgSz.Height() ) / 2 ) );
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+
+ // check for HC mode
+ Image *pImage = &maImage;
+
+ if ( !IsZoom() )
+ {
+ rRenderContext.DrawImage( aPtImg, *pImage, nStyle );
+ }
+ else
+ {
+ aImgSz.setWidth( CalcZoom( aImgSz.Width() ) );
+ aImgSz.setHeight( CalcZoom( aImgSz.Height() ) );
+ rRenderContext.DrawImage( aPtImg, aImgSz, *pImage, nStyle );
+ }
+
+ const sal_uInt16 nEdgeBlendingPercent(GetEdgeBlending() ? rStyleSettings.GetEdgeBlending() : 0);
+
+ if(nEdgeBlendingPercent)
+ {
+ const Color& rTopLeft(rStyleSettings.GetEdgeBlendingTopLeftColor());
+ const Color& rBottomRight(rStyleSettings.GetEdgeBlendingBottomRightColor());
+ const sal_uInt8 nAlpha((nEdgeBlendingPercent * 255) / 100);
+ const BitmapEx aBlendFrame(createBlendFrame(aImgSz, nAlpha, rTopLeft, rBottomRight));
+
+ if(!aBlendFrame.IsEmpty())
+ {
+ rRenderContext.DrawBitmapEx(aPtImg, aBlendFrame);
+ }
+ }
+ }
+
+ if( !maString.isEmpty() )
+ {
+ DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
+
+ if ( bImage && !bLayout )
+ nTextStyle |= DrawTextFlags::Left;
+ else if ( GetStyle() & WB_CENTER )
+ nTextStyle |= DrawTextFlags::Center;
+ else if ( GetStyle() & WB_RIGHT )
+ nTextStyle |= DrawTextFlags::Right;
+ else
+ nTextStyle |= DrawTextFlags::Left;
+
+ tools::Rectangle aTextRect( Point( nBorder, 0 ), Size( aOutSz.Width()-2*nBorder, aOutSz.Height() ) );
+
+ if ( bImage )
+ {
+ aTextRect.AdjustLeft(maImage.GetSizePixel().Width() + IMG_TXT_DISTANCE );
+ }
+
+ std::vector< tools::Rectangle >* pVector = bLayout ? &mxLayoutData->m_aUnicodeBoundRects : nullptr;
+ OUString* pDisplayText = bLayout ? &mxLayoutData->m_aDisplayText : nullptr;
+ rRenderContext.DrawText( aTextRect, maString, nTextStyle, pVector, pDisplayText );
+ }
+
+ if( HasFocus() && !bLayout )
+ ShowFocus( maFocusRect );
+}
+
+void ImplWin::Resize()
+{
+ Control::Resize();
+ maFocusRect.SetSize( GetOutputSizePixel() );
+ Invalidate();
+}
+
+void ImplWin::GetFocus()
+{
+ ShowFocus( maFocusRect );
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects &&
+ IsNativeWidgetEnabled() &&
+ IsNativeControlSupported( ControlType::Listbox, ControlPart::Entire ) )
+ {
+ vcl::Window* pWin = GetParent()->GetWindow( GetWindowType::Border );
+ if( ! pWin )
+ pWin = GetParent();
+ pWin->Invalidate();
+ }
+ else
+ Invalidate();
+ Control::GetFocus();
+}
+
+void ImplWin::LoseFocus()
+{
+ HideFocus();
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects &&
+ IsNativeWidgetEnabled() &&
+ IsNativeControlSupported( ControlType::Listbox, ControlPart::Entire ) )
+ {
+ vcl::Window* pWin = GetParent()->GetWindow( GetWindowType::Border );
+ if( ! pWin )
+ pWin = GetParent();
+ pWin->Invalidate();
+ }
+ else
+ Invalidate();
+ Control::LoseFocus();
+}
+
+void ImplWin::ShowFocus(const tools::Rectangle& rRect)
+{
+ if (IsNativeControlSupported(ControlType::Listbox, ControlPart::Focus))
+ {
+ ImplControlValue aControlValue;
+
+ vcl::Window *pWin = GetParent();
+ tools::Rectangle aParentRect(Point(0, 0), pWin->GetSizePixel());
+ pWin->GetOutDev()->DrawNativeControl(ControlType::Listbox, ControlPart::Focus, aParentRect,
+ ControlState::FOCUSED, aControlValue, OUString());
+ }
+ Control::ShowFocus(rRect);
+}
+
+ImplBtn::ImplBtn( vcl::Window* pParent, WinBits nWinStyle ) :
+ PushButton( pParent, nWinStyle )
+{
+}
+
+void ImplBtn::MouseButtonDown( const MouseEvent& )
+{
+ if( IsEnabled() )
+ maMBDownHdl.Call(this);
+}
+
+ImplListBoxFloatingWindow::ImplListBoxFloatingWindow( vcl::Window* pParent ) :
+ FloatingWindow( pParent, WB_BORDER | WB_SYSTEMWINDOW | WB_NOSHADOW ) // no drop shadow for list boxes
+{
+ // for native widget rendering we must be able to detect this window type
+ SetType( WindowType::LISTBOXWINDOW );
+
+ mpImplLB = nullptr;
+ mnDDLineCount = 0;
+ mbAutoWidth = false;
+
+ mnPopupModeStartSaveSelection = LISTBOX_ENTRY_NOTFOUND;
+
+ vcl::Window * pBorderWindow = ImplGetBorderWindow();
+ if( pBorderWindow )
+ {
+ SetAccessibleRole(accessibility::AccessibleRole::PANEL);
+ pBorderWindow->SetAccessibleRole(accessibility::AccessibleRole::WINDOW);
+ }
+ else
+ {
+ SetAccessibleRole(accessibility::AccessibleRole::WINDOW);
+ }
+
+}
+
+ImplListBoxFloatingWindow::~ImplListBoxFloatingWindow()
+{
+ disposeOnce();
+}
+
+void ImplListBoxFloatingWindow::dispose()
+{
+ mpImplLB.clear();
+ FloatingWindow::dispose();
+}
+
+
+bool ImplListBoxFloatingWindow::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if( !GetParent()->HasChildPathFocus( true ) )
+ EndPopupMode();
+ }
+
+ return FloatingWindow::PreNotify( rNEvt );
+}
+
+void ImplListBoxFloatingWindow::setPosSizePixel( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, PosSizeFlags nFlags )
+{
+ FloatingWindow::setPosSizePixel( nX, nY, nWidth, nHeight, nFlags );
+
+ // Fix #60890# ( MBA ): to be able to resize the Listbox even in its open state
+ // after a call to Resize(), we adjust its position if necessary
+ if ( IsReallyVisible() && ( nFlags & PosSizeFlags::Height ) )
+ {
+ Point aPos = GetParent()->GetPosPixel();
+ aPos = GetParent()->GetParent()->OutputToScreenPixel( aPos );
+
+ if ( nFlags & PosSizeFlags::X )
+ aPos.setX( nX );
+
+ if ( nFlags & PosSizeFlags::Y )
+ aPos.setY( nY );
+
+ sal_uInt16 nIndex;
+ SetPosPixel( ImplCalcPos( this, tools::Rectangle( aPos, GetParent()->GetSizePixel() ), FloatWinPopupFlags::Down, nIndex ) );
+ }
+
+// if( !IsReallyVisible() )
+ {
+ // The ImplListBox does not get a Resize() as not visible.
+ // But the windows must get a Resize(), so that the number of
+ // visible entries is correct for PgUp/PgDown.
+ // The number also cannot be calculated by List/Combobox, as for
+ // this the presence of the vertical Scrollbar has to be known.
+ mpImplLB->SetSizePixel( GetOutputSizePixel() );
+ static_cast<vcl::Window*>(mpImplLB)->Resize();
+ static_cast<vcl::Window*>(mpImplLB->GetMainWindow())->Resize();
+ }
+}
+
+void ImplListBoxFloatingWindow::Resize()
+{
+ mpImplLB->GetMainWindow()->ImplClearLayoutData();
+ FloatingWindow::Resize();
+}
+
+Size ImplListBoxFloatingWindow::CalcFloatSize() const
+{
+ Size aFloatSz( maPrefSz );
+
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ GetBorder( nLeft, nTop, nRight, nBottom );
+
+ sal_Int32 nLines = mpImplLB->GetEntryList().GetEntryCount();
+ if ( mnDDLineCount && ( nLines > mnDDLineCount ) )
+ nLines = mnDDLineCount;
+
+ Size aSz = mpImplLB->CalcSize( nLines );
+ tools::Long nMaxHeight = aSz.Height() + nTop + nBottom;
+
+ if ( mnDDLineCount )
+ aFloatSz.setHeight( nMaxHeight );
+
+ if( mbAutoWidth )
+ {
+ // AutoSize first only for width...
+
+ aFloatSz.setWidth( aSz.Width() + nLeft + nRight );
+ aFloatSz.AdjustWidth(nRight ); // adding some space looks better...
+
+ if ( ( aFloatSz.Height() < nMaxHeight ) || ( mnDDLineCount && ( mnDDLineCount < mpImplLB->GetEntryList().GetEntryCount() ) ) )
+ {
+ // then we also need the vertical Scrollbar
+ tools::Long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
+ aFloatSz.AdjustWidth(nSBWidth );
+ }
+
+ tools::Long nDesktopWidth = GetDesktopRectPixel().getWidth();
+ if (aFloatSz.Width() > nDesktopWidth)
+ // Don't exceed the desktop width.
+ aFloatSz.setWidth( nDesktopWidth );
+ }
+
+ if ( aFloatSz.Height() > nMaxHeight )
+ aFloatSz.setHeight( nMaxHeight );
+
+ // Minimal height, in case height is not set to Float height.
+ // The parent of FloatWin must be DropDown-Combo/Listbox.
+ Size aParentSz = GetParent()->GetSizePixel();
+ if( (!mnDDLineCount || !nLines) && ( aFloatSz.Height() < aParentSz.Height() ) )
+ aFloatSz.setHeight( aParentSz.Height() );
+
+ // do not get narrower than the parent...
+ if( aFloatSz.Width() < aParentSz.Width() )
+ aFloatSz.setWidth( aParentSz.Width() );
+
+ // align height to entries...
+ tools::Long nInnerHeight = aFloatSz.Height() - nTop - nBottom;
+ tools::Long nEntryHeight = mpImplLB->GetEntryHeightWithMargin();
+ if ( nInnerHeight % nEntryHeight )
+ {
+ nInnerHeight /= nEntryHeight;
+ nInnerHeight++;
+ nInnerHeight *= nEntryHeight;
+ aFloatSz.setHeight( nInnerHeight + nTop + nBottom );
+ }
+
+ if (aFloatSz.Width() < aSz.Width())
+ {
+ // The max width of list box entries exceeds the window width.
+ // Account for the scroll bar height.
+ tools::Long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
+ aFloatSz.AdjustHeight(nSBWidth );
+ }
+
+ return aFloatSz;
+}
+
+void ImplListBoxFloatingWindow::StartFloat( bool bStartTracking )
+{
+ if( IsInPopupMode() )
+ return;
+
+ Size aFloatSz = CalcFloatSize();
+
+ SetSizePixel( aFloatSz );
+ mpImplLB->SetSizePixel( GetOutputSizePixel() );
+
+ sal_Int32 nPos = mpImplLB->GetEntryList().GetSelectedEntryPos( 0 );
+ mnPopupModeStartSaveSelection = nPos;
+
+ Size aSz = GetParent()->GetSizePixel();
+ Point aPos = GetParent()->GetPosPixel();
+ aPos = GetParent()->GetParent()->OutputToScreenPixel( aPos );
+ // FIXME: this ugly hack is for Mac/Aqua
+ // should be replaced by a real mechanism to place the float rectangle
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects &&
+ GetParent()->IsNativeWidgetEnabled() )
+ {
+ const sal_Int32 nLeft = 4, nTop = 4, nRight = 4, nBottom = 4;
+ aPos.AdjustX(nLeft );
+ aPos.AdjustY(nTop );
+ aSz.AdjustWidth( -(nLeft + nRight) );
+ aSz.AdjustHeight( -(nTop + nBottom) );
+ }
+ tools::Rectangle aRect( aPos, aSz );
+
+ // check if the control's parent is un-mirrored which is the case for form controls in a mirrored UI
+ // where the document is unmirrored
+ // because StartPopupMode() expects a rectangle in mirrored coordinates we have to re-mirror
+ vcl::Window *pGrandparent = GetParent()->GetParent();
+ const OutputDevice *pGrandparentOutDev = pGrandparent->GetOutDev();
+
+ if( pGrandparent->GetOutDev()->ImplIsAntiparallel() )
+ pGrandparentOutDev->ReMirror( aRect );
+
+ // mouse-button right: close the List-Box-Float-win and don't stop the handling fdo#84795
+ StartPopupMode( aRect, FloatWinPopupFlags::Down | FloatWinPopupFlags::NoHorzPlacement | FloatWinPopupFlags::AllMouseButtonClose );
+
+ if( nPos != LISTBOX_ENTRY_NOTFOUND )
+ mpImplLB->ShowProminentEntry( nPos );
+
+ if( bStartTracking )
+ mpImplLB->GetMainWindow()->EnableMouseMoveSelect( true );
+
+ if ( mpImplLB->GetMainWindow()->IsGrabFocusAllowed() )
+ mpImplLB->GetMainWindow()->GrabFocus();
+
+ mpImplLB->GetMainWindow()->ImplClearLayoutData();
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/ivctrl.cxx b/vcl/source/control/ivctrl.cxx
new file mode 100644
index 000000000..54b64373c
--- /dev/null
+++ b/vcl/source/control/ivctrl.cxx
@@ -0,0 +1,634 @@
+/* -*- 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 <vcl/toolkit/ivctrl.hxx>
+#include "imivctl.hxx"
+#include <vcl/accessiblefactory.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <vcl/uitest/eventdescription.hxx>
+#include <verticaltabctrl.hxx>
+
+using namespace ::com::sun::star::accessibility;
+
+namespace
+{
+void collectUIInformation( const OUString& aID, const OUString& aPos)
+{
+ EventDescription aDescription;
+ aDescription.aID = aID;
+ aDescription.aParameters = {{ "POS" , aPos}};
+ aDescription.aAction = "SELECT";
+ aDescription.aKeyWord = "VerticalTab";
+ UITestLogger::getInstance().logEvent(aDescription);
+}
+}
+
+/*****************************************************************************
+|
+| class : SvxIconChoiceCtrlEntry
+|
+\*****************************************************************************/
+
+SvxIconChoiceCtrlEntry::SvxIconChoiceCtrlEntry( const OUString& rText,
+ const Image& rImage )
+ : aImage(rImage)
+ , aText(rText)
+ , nPos(0)
+ , pblink(nullptr)
+ , pflink(nullptr)
+ , eTextMode(SvxIconChoiceCtrlTextMode::Short)
+ , nX(0)
+ , nY(0)
+ , nFlags(SvxIconViewFlags::NONE)
+{
+}
+
+OUString SvxIconChoiceCtrlEntry::GetDisplayText() const
+{
+ return MnemonicGenerator::EraseAllMnemonicChars( aText );
+}
+
+
+SvxIconChoiceCtrlColumnInfo::SvxIconChoiceCtrlColumnInfo( const SvxIconChoiceCtrlColumnInfo& rInfo )
+{
+ nWidth = rInfo.nWidth;
+}
+
+/*****************************************************************************
+|
+| class : SvtIconChoiceCtrl
+|
+\*****************************************************************************/
+
+SvtIconChoiceCtrl::SvtIconChoiceCtrl( vcl::Window* pParent, WinBits nWinStyle ) :
+
+ // WB_CLIPCHILDREN on, as ScrollBars lie on the window!
+ Control( pParent, nWinStyle | WB_CLIPCHILDREN ),
+
+ _pImpl ( new SvxIconChoiceCtrl_Impl( this, nWinStyle ) )
+{
+ GetOutDev()->SetLineColor();
+ _pImpl->InitSettings();
+ _pImpl->SetPositionMode( SvxIconChoiceCtrlPositionMode::AutoArrange );
+}
+
+void SvtIconChoiceCtrl::SetSelectionMode(SelectionMode eMode)
+{
+ _pImpl->SetSelectionMode(eMode);
+}
+
+SvtIconChoiceCtrl::~SvtIconChoiceCtrl()
+{
+ disposeOnce();
+}
+
+void SvtIconChoiceCtrl::dispose()
+{
+ if (_pImpl)
+ {
+ _pImpl->CallEventListeners( VclEventId::ObjectDying, nullptr );
+ _pImpl.reset();
+ }
+ Control::dispose();
+}
+
+SvxIconChoiceCtrlEntry* SvtIconChoiceCtrl::InsertEntry( const OUString& rText, const Image& rImage )
+{
+ SvxIconChoiceCtrlEntry* pEntry = new SvxIconChoiceCtrlEntry( rText, rImage);
+
+ _pImpl->InsertEntry(std::unique_ptr<SvxIconChoiceCtrlEntry>(pEntry), _pImpl->GetEntryCount());
+
+ return pEntry;
+}
+
+void SvtIconChoiceCtrl::RemoveEntry(sal_Int32 nIndex)
+{
+ _pImpl->RemoveEntry(nIndex);
+}
+
+void SvtIconChoiceCtrl::DrawEntryImage( SvxIconChoiceCtrlEntry const * pEntry, const Point& rPos, OutputDevice& rDev )
+{
+ rDev.DrawImage( rPos, pEntry->GetImage() );
+}
+
+OUString SvtIconChoiceCtrl::GetEntryText( SvxIconChoiceCtrlEntry const * pEntry )
+{
+ return pEntry->GetText();
+}
+
+void SvtIconChoiceCtrl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ _pImpl->Paint(rRenderContext, rRect);
+}
+
+void SvtIconChoiceCtrl::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if( !_pImpl->MouseButtonDown( rMEvt ) )
+ Control::MouseButtonDown( rMEvt );
+}
+
+void SvtIconChoiceCtrl::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ if( !_pImpl->MouseButtonUp( rMEvt ) )
+ Control::MouseButtonUp( rMEvt );
+}
+
+void SvtIconChoiceCtrl::MouseMove( const MouseEvent& rMEvt )
+{
+ if( !_pImpl->MouseMove( rMEvt ) )
+ Control::MouseMove( rMEvt );
+}
+void SvtIconChoiceCtrl::ArrangeIcons()
+{
+ if ( GetStyle() & WB_ALIGN_TOP )
+ {
+ Size aFullSize;
+ tools::Rectangle aEntryRect;
+
+ for ( sal_Int32 i = 0; i < GetEntryCount(); i++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = GetEntry ( i );
+ aEntryRect = _pImpl->GetEntryBoundRect ( pEntry );
+
+ aFullSize.setWidth ( aFullSize.getWidth()+aEntryRect.GetWidth() );
+ }
+
+ _pImpl->Arrange ( false, aFullSize.getWidth(), 0 );
+ }
+ else if ( GetStyle() & WB_ALIGN_LEFT )
+ {
+ Size aFullSize;
+ tools::Rectangle aEntryRect;
+
+ for ( sal_Int32 i = 0; i < GetEntryCount(); i++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = GetEntry ( i );
+ aEntryRect = _pImpl->GetEntryBoundRect ( pEntry );
+
+ aFullSize.setHeight ( aFullSize.getHeight()+aEntryRect.GetHeight() );
+ }
+
+ _pImpl->Arrange ( false, 0, aFullSize.getHeight() );
+ }
+ else
+ {
+ _pImpl->Arrange(false, 0, 0);
+ }
+ _pImpl->Arrange( false, 0, 1000 );
+}
+void SvtIconChoiceCtrl::Resize()
+{
+ _pImpl->Resize();
+ Control::Resize();
+}
+
+void SvtIconChoiceCtrl::GetFocus()
+{
+ _pImpl->GetFocus();
+ Control::GetFocus();
+ SvxIconChoiceCtrlEntry* pSelectedEntry = GetSelectedEntry();
+ if ( pSelectedEntry )
+ _pImpl->CallEventListeners( VclEventId::ListboxSelect, pSelectedEntry );
+}
+
+void SvtIconChoiceCtrl::LoseFocus()
+{
+ if (_pImpl)
+ _pImpl->LoseFocus();
+ Control::LoseFocus();
+}
+
+void SvtIconChoiceCtrl::SetFont(const vcl::Font& rFont)
+{
+ if (rFont != GetFont())
+ {
+ Control::SetFont(rFont);
+ _pImpl->FontModified();
+ }
+}
+
+void SvtIconChoiceCtrl::SetPointFont(const vcl::Font& rFont)
+{
+ if (rFont != GetPointFont(*GetOutDev())) //FIXME
+ {
+ Control::SetPointFont(*GetOutDev(), rFont); //FIXME
+ _pImpl->FontModified();
+ }
+}
+
+WinBits SvtIconChoiceCtrl::GetStyle() const
+{
+ return _pImpl->GetStyle();
+}
+
+void SvtIconChoiceCtrl::Command(const CommandEvent& rCEvt)
+{
+ _pImpl->Command( rCEvt );
+ //pass at least alt press/release to parent impl
+ if (rCEvt.GetCommand() == CommandEventId::ModKeyChange)
+ Control::Command(rCEvt);
+}
+
+#ifdef DBG_UTIL
+void SvtIconChoiceCtrl::SetEntryTextMode( SvxIconChoiceCtrlTextMode eMode, SvxIconChoiceCtrlEntry* pEntry )
+{
+ _pImpl->SetEntryTextMode( eMode, pEntry );
+}
+#endif
+
+sal_Int32 SvtIconChoiceCtrl::GetEntryCount() const
+{
+ return _pImpl ? _pImpl->GetEntryCount() : 0;
+}
+
+SvxIconChoiceCtrlEntry* SvtIconChoiceCtrl::GetEntry( sal_Int32 nPos ) const
+{
+ return _pImpl ? _pImpl->GetEntry( nPos ) : nullptr;
+}
+
+SvxIconChoiceCtrlEntry* SvtIconChoiceCtrl::GetSelectedEntry() const
+{
+ return _pImpl ? _pImpl->GetFirstSelectedEntry() : nullptr;
+}
+
+void SvtIconChoiceCtrl::ClickIcon()
+{
+ GetSelectedEntry();
+ _aClickIconHdl.Call( this );
+}
+
+void SvtIconChoiceCtrl::KeyInput( const KeyEvent& rKEvt )
+{
+ bool bKeyUsed = DoKeyInput( rKEvt );
+ if ( !bKeyUsed )
+ {
+ Control::KeyInput( rKEvt );
+ }
+}
+bool SvtIconChoiceCtrl::DoKeyInput( const KeyEvent& rKEvt )
+{
+ return _pImpl->KeyInput( rKEvt );
+}
+sal_Int32 SvtIconChoiceCtrl::GetEntryListPos( SvxIconChoiceCtrlEntry const * pEntry ) const
+{
+ return _pImpl->GetEntryListPos( pEntry );
+}
+SvxIconChoiceCtrlEntry* SvtIconChoiceCtrl::GetCursor( ) const
+{
+ return _pImpl->GetCurEntry( );
+}
+void SvtIconChoiceCtrl::SetCursor( SvxIconChoiceCtrlEntry* pEntry )
+{
+ _pImpl->SetCursor( pEntry );
+}
+
+void SvtIconChoiceCtrl::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ if ( ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTS) ) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ _pImpl->InitSettings();
+ Invalidate(InvalidateFlags::NoChildren);
+ }
+ else
+ Control::DataChanged( rDCEvt );
+}
+
+void SvtIconChoiceCtrl::SetBackground( const Wallpaper& rPaper )
+{
+ if( rPaper == GetBackground() )
+ return;
+
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ // if it is the default (empty) wallpaper
+ if (rPaper.IsEmpty())
+ {
+ Control::SetBackground( rStyleSettings.GetFieldColor() );
+ }
+ else
+ {
+ Wallpaper aBackground( rPaper );
+ // HACK, as background might be transparent!
+ if( !aBackground.IsBitmap() )
+ aBackground.SetStyle( WallpaperStyle::Tile );
+
+ WallpaperStyle eStyle = aBackground.GetStyle();
+ Color aBack( aBackground.GetColor());
+ if( aBack == COL_TRANSPARENT &&
+ (!aBackground.IsBitmap() ||
+ aBackground.GetBitmap().IsAlpha() ||
+ (eStyle != WallpaperStyle::Tile && eStyle != WallpaperStyle::Scale)) )
+ {
+ aBackground.SetColor( rStyleSettings.GetFieldColor() );
+ }
+ if( aBackground.IsScrollable() )
+ {
+ tools::Rectangle aRect;
+ aRect.SetSize( Size(32765, 32765) );
+ aBackground.SetRect( aRect );
+ }
+ else
+ {
+ tools::Rectangle aRect( _pImpl->GetOutputRect() );
+ aBackground.SetRect( aRect );
+ }
+ Control::SetBackground( aBackground );
+ }
+
+ // If text colors are attributed "hard," don't use automatism to select
+ // a readable text color.
+ vcl::Font aFont( GetFont() );
+ aFont.SetColor( rStyleSettings.GetFieldTextColor() );
+ SetFont( aFont );
+
+ Invalidate(InvalidateFlags::NoChildren);
+}
+
+void SvtIconChoiceCtrl::RequestHelp( const HelpEvent& rHEvt )
+{
+ if ( !_pImpl->RequestHelp( rHEvt ) )
+ Control::RequestHelp( rHEvt );
+}
+
+tools::Rectangle SvtIconChoiceCtrl::GetBoundingBox( SvxIconChoiceCtrlEntry* pEntry ) const
+{
+ return _pImpl->GetEntryBoundRect( pEntry );
+}
+
+void SvtIconChoiceCtrl::FillLayoutData() const
+{
+ CreateLayoutData();
+ const_cast<SvtIconChoiceCtrl*>(this)->Invalidate();
+}
+
+tools::Rectangle SvtIconChoiceCtrl::GetEntryCharacterBounds( const sal_Int32 _nEntryPos, const sal_Int32 _nCharacterIndex ) const
+{
+ tools::Rectangle aRect;
+
+ Pair aEntryCharacterRange = GetLineStartEnd( _nEntryPos );
+ if ( aEntryCharacterRange.A() + _nCharacterIndex < aEntryCharacterRange.B() )
+ {
+ aRect = GetCharacterBounds( aEntryCharacterRange.A() + _nCharacterIndex );
+ }
+
+ return aRect;
+}
+
+void SvtIconChoiceCtrl::SetNoSelection()
+{
+ _pImpl->SetNoSelection();
+}
+
+void SvtIconChoiceCtrl::CallImplEventListeners(VclEventId nEvent, void* pData)
+{
+ CallEventListeners(nEvent, pData);
+}
+css::uno::Reference< XAccessible > SvtIconChoiceCtrl::CreateAccessible()
+{
+ vcl::Window* pParent = GetAccessibleParentWindow();
+ DBG_ASSERT( pParent, "SvTreeListBox::CreateAccessible - accessible parent not found" );
+
+ css::uno::Reference< XAccessible > xAccessible;
+ if ( pParent )
+ {
+ css::uno::Reference< XAccessible > xAccParent = pParent->GetAccessible();
+ if ( xAccParent.is() )
+ {
+ css::uno::Reference< css::awt::XWindowPeer > xHoldAlive(GetComponentInterface());
+ xAccessible = _pImpl->GetAccessibleFactory().createAccessibleIconChoiceCtrl( *this, xAccParent );
+ }
+ }
+ return xAccessible;
+}
+
+struct VerticalTabPageData
+{
+ OString sId;
+ SvxIconChoiceCtrlEntry* pEntry;
+ VclPtr<vcl::Window> xPage; ///< the TabPage itself
+};
+
+VerticalTabControl::VerticalTabControl(vcl::Window* pParent)
+ : VclHBox(pParent)
+ , m_xChooser(VclPtr<SvtIconChoiceCtrl>::Create(this, WB_3DLOOK | WB_ICON | WB_BORDER |
+ WB_NOCOLUMNHEADER | WB_HIGHLIGHTFRAME |
+ WB_NODRAGSELECTION | WB_TABSTOP | WB_CLIPCHILDREN |
+ WB_ALIGN_LEFT | WB_NOHSCROLL))
+ , m_xBox(VclPtr<VclVBox>::Create(this))
+{
+ SetStyle(GetStyle() | WB_DIALOGCONTROL);
+ SetType(WindowType::VERTICALTABCONTROL);
+ m_xChooser->SetSelectionMode(SelectionMode::Single);
+ m_xChooser->SetClickHdl(LINK(this, VerticalTabControl, ChosePageHdl_Impl));
+ m_xChooser->set_width_request(110);
+ m_xChooser->set_height_request(400);
+ m_xChooser->SetSizePixel(Size(110, 400));
+ m_xBox->set_vexpand(true);
+ m_xBox->set_hexpand(true);
+ m_xBox->set_expand(true);
+ m_xBox->Show();
+ m_xChooser->Show();
+}
+
+VerticalTabControl::~VerticalTabControl()
+{
+ disposeOnce();
+}
+
+void VerticalTabControl::dispose()
+{
+ m_xChooser.disposeAndClear();
+ m_xBox.disposeAndClear();
+ VclHBox::dispose();
+}
+
+IMPL_LINK_NOARG(VerticalTabControl, ChosePageHdl_Impl, SvtIconChoiceCtrl*, void)
+{
+ SvxIconChoiceCtrlEntry *pEntry = m_xChooser->GetSelectedEntry();
+ if (!pEntry)
+ pEntry = m_xChooser->GetCursor();
+
+ VerticalTabPageData* pData = GetPageData(pEntry);
+
+ if (pData->sId != m_sCurrentPageId)
+ SetCurPageId(pData->sId);
+}
+
+void VerticalTabControl::ActivatePage()
+{
+ m_aActivateHdl.Call( this );
+}
+
+bool VerticalTabControl::DeactivatePage()
+{
+ return !m_aDeactivateHdl.IsSet() || m_aDeactivateHdl.Call(this);
+}
+
+VerticalTabPageData* VerticalTabControl::GetPageData(const SvxIconChoiceCtrlEntry* pEntry) const
+{
+ VerticalTabPageData* pRet = nullptr;
+ for (auto & pData : maPageList)
+ {
+ if (pData->pEntry == pEntry)
+ {
+ pRet = pData.get();
+ break;
+ }
+ }
+ return pRet;
+}
+
+VerticalTabPageData* VerticalTabControl::GetPageData(std::string_view rId) const
+{
+ VerticalTabPageData* pRet = nullptr;
+ for (auto & pData : maPageList)
+ {
+ if (pData->sId == rId)
+ {
+ pRet = pData.get();
+ break;
+ }
+ }
+ return pRet;
+}
+
+void VerticalTabControl::SetCurPageId(const OString& rId)
+{
+ OString sOldPageId = GetCurPageId();
+ if (sOldPageId == rId)
+ return;
+
+ VerticalTabPageData* pOldData = GetPageData(sOldPageId);
+ if (pOldData && pOldData->xPage)
+ {
+ if (!DeactivatePage())
+ return;
+ pOldData->xPage->Hide();
+ }
+
+ m_sCurrentPageId = "";
+
+ VerticalTabPageData* pNewData = GetPageData(rId);
+ if (pNewData && pNewData->xPage)
+ {
+ m_sCurrentPageId = rId;
+ m_xChooser->SetCursor(pNewData->pEntry);
+
+ ActivatePage();
+ pNewData->xPage->Show();
+ }
+ collectUIInformation(get_id(),OStringToOUString(m_sCurrentPageId,RTL_TEXTENCODING_UTF8));
+}
+
+const OString & VerticalTabControl::GetPageId(sal_uInt16 nIndex) const
+{
+ return maPageList[nIndex]->sId;
+}
+
+void VerticalTabControl::InsertPage(const rtl::OString &rIdent, const rtl::OUString& rLabel, const Image& rImage,
+ const rtl::OUString& rTooltip, VclPtr<vcl::Window> xPage, int nPos)
+{
+ SvxIconChoiceCtrlEntry* pEntry = m_xChooser->InsertEntry(rLabel, rImage);
+ pEntry->SetQuickHelpText(rTooltip);
+ m_xChooser->ArrangeIcons();
+ VerticalTabPageData* pNew;
+ if (nPos == -1)
+ {
+ maPageList.emplace_back(new VerticalTabPageData);
+ pNew = maPageList.back().get();
+ }
+ else
+ {
+ maPageList.emplace(maPageList.begin() + nPos, new VerticalTabPageData);
+ pNew = maPageList[nPos].get();
+ }
+ pNew->sId = rIdent;
+ pNew->pEntry = pEntry;
+ pNew->xPage = xPage;
+ Size aOrigPrefSize(m_xBox->get_preferred_size());
+ Size aPagePrefSize(xPage->get_preferred_size());
+ m_xBox->set_width_request(std::max(aOrigPrefSize.Width(), aPagePrefSize.Width()));
+ m_xBox->set_height_request(std::max(aOrigPrefSize.Height(), aPagePrefSize.Height()));
+ pNew->xPage->Hide();
+}
+
+void VerticalTabControl::RemovePage(std::string_view rPageId)
+{
+ for (auto it = maPageList.begin(), end = maPageList.end(); it != end; ++it)
+ {
+ VerticalTabPageData* pData = it->get();
+ if (pData->sId == rPageId)
+ {
+ sal_Int32 nEntryListPos = m_xChooser->GetEntryListPos(pData->pEntry);
+ m_xChooser->RemoveEntry(nEntryListPos);
+ m_xChooser->ArrangeIcons();
+ maPageList.erase(it);
+ break;
+ }
+ }
+}
+
+sal_uInt16 VerticalTabControl::GetPagePos(std::string_view rPageId) const
+{
+ VerticalTabPageData* pData = GetPageData(rPageId);
+ if (!pData)
+ return TAB_PAGE_NOTFOUND;
+ return m_xChooser->GetEntryListPos(pData->pEntry);
+}
+
+VclPtr<vcl::Window> VerticalTabControl::GetPage(std::string_view rPageId) const
+{
+ VerticalTabPageData* pData = GetPageData(rPageId);
+ if (!pData)
+ return nullptr;
+ return pData->xPage;
+}
+
+OUString VerticalTabControl::GetPageText(std::string_view rPageId) const
+{
+ VerticalTabPageData* pData = GetPageData(rPageId);
+ if (!pData)
+ return OUString();
+ return pData->pEntry->GetText();
+}
+
+void VerticalTabControl::SetPageText(std::string_view rPageId, const OUString& rText)
+{
+ VerticalTabPageData* pData = GetPageData(rPageId);
+ if (!pData)
+ return;
+ pData->pEntry->SetText(rText);
+}
+
+FactoryFunction VerticalTabControl::GetUITestFactory() const
+{
+ return VerticalTabControlUIObject::create;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/listbox.cxx b/vcl/source/control/listbox.cxx
new file mode 100644
index 000000000..c8c8b0c8d
--- /dev/null
+++ b/vcl/source/control/listbox.cxx
@@ -0,0 +1,1427 @@
+/* -*- 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 <vcl/builder.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <sal/log.hxx>
+
+#include <svdata.hxx>
+#include <listbox.hxx>
+#include <dndeventdispatcher.hxx>
+#include <comphelper/lok.hxx>
+
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <boost/property_tree/ptree.hpp>
+#include <tools/json_writer.hxx>
+
+ListBox::ListBox(WindowType nType)
+ : Control(nType)
+ , mpImplLB(nullptr)
+{
+ ImplInitListBoxData();
+}
+
+ListBox::ListBox( vcl::Window* pParent, WinBits nStyle ) : Control( WindowType::LISTBOX )
+{
+ ImplInitListBoxData();
+ ImplInit( pParent, nStyle );
+}
+
+ListBox::~ListBox()
+{
+ disposeOnce();
+}
+
+void ListBox::dispose()
+{
+ CallEventListeners( VclEventId::ObjectDying );
+
+ mpImplLB.disposeAndClear();
+ mpFloatWin.disposeAndClear();
+ mpImplWin.disposeAndClear();
+ mpBtn.disposeAndClear();
+
+ Control::dispose();
+}
+
+void ListBox::ImplInitListBoxData()
+{
+ mpFloatWin = nullptr;
+ mpImplWin = nullptr;
+ mpBtn = nullptr;
+ mnDDHeight = 0;
+ mnLineCount = 0;
+ m_nMaxWidthChars = -1;
+ mbDDAutoSize = true;
+}
+
+void ListBox::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle( nStyle );
+ if ( !(nStyle & WB_NOBORDER) && ( nStyle & WB_DROPDOWN ) )
+ nStyle |= WB_BORDER;
+
+ Control::ImplInit( pParent, nStyle, nullptr );
+
+ css::uno::Reference< css::datatransfer::dnd::XDropTargetListener> xDrop = new DNDEventDispatcher(this);
+
+ if( nStyle & WB_DROPDOWN )
+ {
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ GetBorder( nLeft, nTop, nRight, nBottom );
+ mnDDHeight = static_cast<sal_uInt16>(GetTextHeight() + nTop + nBottom + 4);
+
+ if( IsNativeWidgetEnabled() &&
+ IsNativeControlSupported( ControlType::Listbox, ControlPart::Entire ) )
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), Size( 20, mnDDHeight ) );
+ tools::Rectangle aBoundingRgn( aCtrlRegion );
+ tools::Rectangle aContentRgn( aCtrlRegion );
+ if( GetNativeControlRegion( ControlType::Listbox, ControlPart::Entire, aCtrlRegion,
+ ControlState::ENABLED, aControlValue,
+ aBoundingRgn, aContentRgn ) )
+ {
+ sal_Int32 nHeight = aBoundingRgn.GetHeight();
+ if( nHeight > mnDDHeight )
+ mnDDHeight = static_cast<sal_uInt16>(nHeight);
+ }
+ }
+
+ mpFloatWin = VclPtr<ImplListBoxFloatingWindow>::Create( this );
+ if (!IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Focus))
+ mpFloatWin->RequestDoubleBuffering(true);
+ mpFloatWin->SetAutoWidth( true );
+ mpFloatWin->SetPopupModeEndHdl( LINK( this, ListBox, ImplPopupModeEndHdl ) );
+ mpFloatWin->GetDropTarget()->addDropTargetListener(xDrop);
+
+ mpImplWin = VclPtr<ImplWin>::Create( this, (nStyle & (WB_LEFT|WB_RIGHT|WB_CENTER))|WB_NOBORDER );
+ mpImplWin->SetMBDownHdl( LINK( this, ListBox, ImplClickBtnHdl ) );
+ mpImplWin->Show();
+ mpImplWin->GetDropTarget()->addDropTargetListener(xDrop);
+ mpImplWin->SetEdgeBlending(false);
+
+ mpBtn = VclPtr<ImplBtn>::Create( this, WB_NOLIGHTBORDER | WB_RECTSTYLE );
+ ImplInitDropDownButton( mpBtn );
+ mpBtn->SetMBDownHdl( LINK( this, ListBox, ImplClickBtnHdl ) );
+ mpBtn->Show();
+ mpBtn->GetDropTarget()->addDropTargetListener(xDrop);
+ }
+
+ vcl::Window* pLBParent = this;
+ if ( mpFloatWin )
+ pLBParent = mpFloatWin;
+ mpImplLB = VclPtr<ImplListBox>::Create( pLBParent, nStyle&(~WB_BORDER) );
+ mpImplLB->SetSelectHdl( LINK( this, ListBox, ImplSelectHdl ) );
+ mpImplLB->SetScrollHdl( LINK( this, ListBox, ImplScrollHdl ) );
+ mpImplLB->SetCancelHdl( LINK( this, ListBox, ImplCancelHdl ) );
+ mpImplLB->SetDoubleClickHdl( LINK( this, ListBox, ImplDoubleClickHdl ) );
+ mpImplLB->SetFocusHdl( LINK( this, ListBox, ImplFocusHdl ) );
+ mpImplLB->SetListItemSelectHdl( LINK( this, ListBox, ImplListItemSelectHdl ) );
+ mpImplLB->SetPosPixel( Point() );
+ mpImplLB->SetEdgeBlending(false);
+ mpImplLB->Show();
+
+ mpImplLB->GetDropTarget()->addDropTargetListener(xDrop);
+
+ if ( mpFloatWin )
+ {
+ mpFloatWin->SetImplListBox( mpImplLB );
+ mpImplLB->SetSelectionChangedHdl( LINK( this, ListBox, ImplSelectionChangedHdl ) );
+ }
+ else
+ mpImplLB->GetMainWindow()->AllowGrabFocus( true );
+
+ SetCompoundControl( true );
+}
+
+WinBits ListBox::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+IMPL_LINK_NOARG(ListBox, ImplSelectHdl, LinkParamNone*, void)
+{
+ bool bPopup = IsInDropDown();
+ if( IsDropDownBox() )
+ {
+ if( !mpImplLB->IsTravelSelect() )
+ {
+ mpFloatWin->EndPopupMode();
+ mpImplWin->GrabFocus();
+ }
+
+ mpImplWin->SetItemPos( GetSelectedEntryPos() );
+ mpImplWin->SetString( GetSelectedEntry() );
+ if( mpImplLB->GetEntryList().HasImages() )
+ {
+ Image aImage = mpImplLB->GetEntryList().GetEntryImage( GetSelectedEntryPos() );
+ mpImplWin->SetImage( aImage );
+ }
+ mpImplWin->Invalidate();
+ }
+
+ if ( ( !IsTravelSelect() || mpImplLB->IsSelectionChanged() ) || ( bPopup && !IsMultiSelectionEnabled() ) )
+ Select();
+}
+
+IMPL_LINK( ListBox, ImplFocusHdl, sal_Int32, nPos, void )
+{
+ CallEventListeners( VclEventId::ListboxFocus, reinterpret_cast<void*>(nPos) );
+}
+
+IMPL_LINK_NOARG( ListBox, ImplListItemSelectHdl, LinkParamNone*, void )
+{
+ CallEventListeners( VclEventId::DropdownSelect );
+}
+
+IMPL_LINK_NOARG(ListBox, ImplScrollHdl, ImplListBox*, void)
+{
+ CallEventListeners( VclEventId::ListboxScrolled );
+}
+
+IMPL_LINK_NOARG(ListBox, ImplCancelHdl, LinkParamNone*, void)
+{
+ if( IsInDropDown() )
+ mpFloatWin->EndPopupMode();
+}
+
+IMPL_LINK( ListBox, ImplSelectionChangedHdl, sal_Int32, nChanged, void )
+{
+ if ( mpImplLB->IsTrackingSelect() )
+ return;
+
+ const ImplEntryList& rEntryList = mpImplLB->GetEntryList();
+ if ( rEntryList.IsEntryPosSelected( nChanged ) )
+ {
+ // FIXME? This should've been turned into an ImplPaintEntry some time ago...
+ if ( nChanged < rEntryList.GetMRUCount() )
+ nChanged = rEntryList.FindEntry( rEntryList.GetEntryText( nChanged ) );
+ mpImplWin->SetItemPos( nChanged );
+ mpImplWin->SetString( rEntryList.GetEntryText( nChanged ) );
+ if( rEntryList.HasImages() )
+ {
+ Image aImage = rEntryList.GetEntryImage( nChanged );
+ mpImplWin->SetImage( aImage );
+ }
+ }
+ else
+ {
+ mpImplWin->SetItemPos( LISTBOX_ENTRY_NOTFOUND );
+ mpImplWin->SetString( OUString() );
+ Image aImage;
+ mpImplWin->SetImage( aImage );
+ }
+ mpImplWin->Invalidate();
+}
+
+IMPL_LINK_NOARG(ListBox, ImplDoubleClickHdl, ImplListBoxWindow*, void)
+{
+ DoubleClick();
+}
+
+IMPL_LINK_NOARG(ListBox, ImplClickBtnHdl, void*, void)
+{
+ if( mpFloatWin->IsInPopupMode() )
+ return;
+
+ CallEventListeners( VclEventId::DropdownPreOpen );
+ mpImplWin->GrabFocus();
+ mpBtn->SetPressed( true );
+ mpFloatWin->StartFloat( true );
+ CallEventListeners( VclEventId::DropdownOpen );
+
+ ImplClearLayoutData();
+ if( mpImplLB )
+ mpImplLB->GetMainWindow()->ImplClearLayoutData();
+ if( mpImplWin )
+ mpImplWin->ImplClearLayoutData();
+}
+
+IMPL_LINK_NOARG(ListBox, ImplPopupModeEndHdl, FloatingWindow*, void)
+{
+ if( mpFloatWin->IsPopupModeCanceled() )
+ {
+ if ( ( mpFloatWin->GetPopupModeStartSaveSelection() != LISTBOX_ENTRY_NOTFOUND )
+ && !IsEntryPosSelected( mpFloatWin->GetPopupModeStartSaveSelection() ) )
+ {
+ mpImplLB->SelectEntry( mpFloatWin->GetPopupModeStartSaveSelection(), true );
+ bool bTravelSelect = mpImplLB->IsTravelSelect();
+ mpImplLB->SetTravelSelect( true );
+
+ VclPtr<vcl::Window> xWindow = this;
+ Select();
+ if ( xWindow->isDisposed() )
+ return;
+
+ mpImplLB->SetTravelSelect( bTravelSelect );
+ }
+ }
+
+ ImplClearLayoutData();
+ if( mpImplLB )
+ mpImplLB->GetMainWindow()->ImplClearLayoutData();
+ if( mpImplWin )
+ mpImplWin->ImplClearLayoutData();
+
+ mpBtn->SetPressed( false );
+ CallEventListeners( VclEventId::DropdownClose );
+}
+
+void ListBox::ToggleDropDown()
+{
+ if( !IsDropDownBox() )
+ return;
+
+ if( mpFloatWin->IsInPopupMode() )
+ mpFloatWin->EndPopupMode();
+ else
+ {
+ CallEventListeners( VclEventId::DropdownPreOpen );
+ mpImplWin->GrabFocus();
+ mpBtn->SetPressed( true );
+ mpFloatWin->StartFloat( true );
+ CallEventListeners( VclEventId::DropdownOpen );
+ }
+}
+
+void ListBox::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.SetBackground();
+}
+
+void ListBox::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags )
+{
+ mpImplLB->GetMainWindow()->ApplySettings(*pDev);
+
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ vcl::Font aFont = mpImplLB->GetMainWindow()->GetDrawPixelFont( pDev );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ pDev->SetTextFillColor();
+
+ // Border/Background
+ pDev->SetLineColor();
+ pDev->SetFillColor();
+ bool bBorder = (GetStyle() & WB_BORDER);
+ bool bBackground = IsControlBackground();
+ if ( bBorder || bBackground )
+ {
+ tools::Rectangle aRect( aPos, aSize );
+ if ( bBorder )
+ {
+ ImplDrawFrame( pDev, aRect );
+ }
+ if ( bBackground )
+ {
+ pDev->SetFillColor( GetControlBackground() );
+ pDev->DrawRect( aRect );
+ }
+ }
+
+ // Content
+ if ( nFlags & SystemTextColorFlags::Mono )
+ {
+ pDev->SetTextColor( COL_BLACK );
+ }
+ else
+ {
+ if ( !IsEnabled() )
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ pDev->SetTextColor( rStyleSettings.GetDisableColor() );
+ }
+ else
+ {
+ pDev->SetTextColor( GetTextColor() );
+ }
+ }
+
+ const tools::Long nOnePixel = GetDrawPixel( pDev, 1 );
+ const tools::Long nOffX = 3*nOnePixel;
+ DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
+ tools::Rectangle aTextRect( aPos, aSize );
+
+ if ( GetStyle() & WB_CENTER )
+ nTextStyle |= DrawTextFlags::Center;
+ else if ( GetStyle() & WB_RIGHT )
+ nTextStyle |= DrawTextFlags::Right;
+ else
+ nTextStyle |= DrawTextFlags::Left;
+
+ aTextRect.AdjustLeft(nOffX );
+ aTextRect.AdjustRight( -nOffX );
+
+ if ( IsDropDownBox() )
+ {
+ OUString aText = GetSelectedEntry();
+ tools::Long nTextHeight = pDev->GetTextHeight();
+ tools::Long nTextWidth = pDev->GetTextWidth( aText );
+ tools::Long nOffY = (aSize.Height()-nTextHeight) / 2;
+
+ // Clipping?
+ if ( (nOffY < 0) ||
+ ((nOffY+nTextHeight) > aSize.Height()) ||
+ ((nOffX+nTextWidth) > aSize.Width()) )
+ {
+ tools::Rectangle aClip( aPos, aSize );
+ if ( nTextHeight > aSize.Height() )
+ aClip.AdjustBottom(nTextHeight-aSize.Height()+1 ); // So that HP Printers don't optimize this away
+ pDev->IntersectClipRegion( aClip );
+ }
+
+ pDev->DrawText( aTextRect, aText, nTextStyle );
+ }
+ else
+ {
+ tools::Long nTextHeight = pDev->GetTextHeight();
+ sal_uInt16 nLines = ( nTextHeight > 0 ) ? static_cast<sal_uInt16>(aSize.Height() / nTextHeight) : 1;
+ tools::Rectangle aClip( aPos, aSize );
+
+ pDev->IntersectClipRegion( aClip );
+
+ if ( !nLines )
+ nLines = 1;
+
+ for ( sal_uInt16 n = 0; n < nLines; n++ )
+ {
+ sal_Int32 nEntry = n+mpImplLB->GetTopEntry();
+ bool bSelected = mpImplLB->GetEntryList().IsEntryPosSelected( nEntry );
+ if ( bSelected )
+ {
+ pDev->SetFillColor( COL_BLACK );
+ pDev->DrawRect( tools::Rectangle( Point( aPos.X(), aPos.Y() + n*nTextHeight ),
+ Point( aPos.X() + aSize.Width(), aPos.Y() + (n+1)*nTextHeight + 2*nOnePixel ) ) );
+ pDev->SetFillColor();
+ pDev->SetTextColor( COL_WHITE );
+ }
+
+ aTextRect.SetTop( aPos.Y() + n*nTextHeight );
+ aTextRect.SetBottom( aTextRect.Top() + nTextHeight );
+
+ pDev->DrawText( aTextRect, mpImplLB->GetEntryList().GetEntryText( nEntry ), nTextStyle );
+
+ if ( bSelected )
+ pDev->SetTextColor( COL_BLACK );
+ }
+ }
+
+ pDev->Pop();
+}
+
+void ListBox::GetFocus()
+{
+ if ( mpImplLB )
+ {
+ if( IsDropDownBox() )
+ mpImplWin->GrabFocus();
+ else
+ mpImplLB->GrabFocus();
+ }
+
+ Control::GetFocus();
+}
+
+void ListBox::LoseFocus()
+{
+ if( IsDropDownBox() )
+ {
+ if (mpImplWin)
+ mpImplWin->HideFocus();
+ }
+ else
+ {
+ if (mpImplLB)
+ mpImplLB->HideFocus();
+ }
+
+ Control::LoseFocus();
+}
+
+void ListBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( !((rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))) )
+ return;
+
+ SetBackground(); // Due to a hack in Window::UpdateSettings the background must be reset
+ // otherwise it will overpaint NWF drawn listboxes
+ Resize();
+ mpImplLB->Resize(); // Is not called by ListBox::Resize() if the ImplLB does not change
+
+ if ( mpImplWin )
+ {
+ mpImplWin->GetOutDev()->SetSettings( GetSettings() ); // If not yet set...
+ mpImplWin->ApplySettings(*mpImplWin->GetOutDev());
+
+ mpBtn->GetOutDev()->SetSettings( GetSettings() );
+ ImplInitDropDownButton( mpBtn );
+ }
+
+ if ( IsDropDownBox() )
+ Invalidate();
+}
+
+void ListBox::EnableAutoSize( bool bAuto )
+{
+ mbDDAutoSize = bAuto;
+ if ( mpFloatWin )
+ {
+ if ( bAuto && !mpFloatWin->GetDropDownLineCount() )
+ {
+ // use GetListBoxMaximumLineCount here; before, was on fixed number of five
+ AdaptDropDownLineCountToMaximum();
+ }
+ else if ( !bAuto )
+ {
+ mpFloatWin->SetDropDownLineCount( 0 );
+ }
+ }
+}
+
+void ListBox::SetDropDownLineCount( sal_uInt16 nLines )
+{
+ mnLineCount = nLines;
+ if ( mpFloatWin )
+ mpFloatWin->SetDropDownLineCount( mnLineCount );
+}
+
+void ListBox::AdaptDropDownLineCountToMaximum()
+{
+ // Adapt to maximum allowed number.
+ // Limit for LOK as we can't render outside of the dialog canvas.
+ if (comphelper::LibreOfficeKit::isActive())
+ SetDropDownLineCount(11);
+ else
+ SetDropDownLineCount(GetSettings().GetStyleSettings().GetListBoxMaximumLineCount());
+}
+
+sal_uInt16 ListBox::GetDropDownLineCount() const
+{
+ if ( mpFloatWin )
+ return mpFloatWin->GetDropDownLineCount();
+ return mnLineCount;
+}
+
+void ListBox::setPosSizePixel( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, PosSizeFlags nFlags )
+{
+ if( IsDropDownBox() && ( nFlags & PosSizeFlags::Size ) )
+ {
+ Size aPrefSz = mpFloatWin->GetPrefSize();
+ if ( ( nFlags & PosSizeFlags::Height ) && ( nHeight >= 2*mnDDHeight ) )
+ aPrefSz.setHeight( nHeight-mnDDHeight );
+ if ( nFlags & PosSizeFlags::Width )
+ aPrefSz.setWidth( nWidth );
+ mpFloatWin->SetPrefSize( aPrefSz );
+
+ if (IsAutoSizeEnabled())
+ nHeight = mnDDHeight;
+ }
+
+ Control::setPosSizePixel( nX, nY, nWidth, nHeight, nFlags );
+}
+
+void ListBox::Resize()
+{
+ Size aOutSz = GetOutputSizePixel();
+ if( IsDropDownBox() )
+ {
+ // Initialize the dropdown button size with the standard scrollbar width
+ tools::Long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
+ tools::Long nBottom = aOutSz.Height();
+
+ // Note: in case of no border, pBorder will actually be this
+ vcl::Window *pBorder = GetWindow( GetWindowType::Border );
+ ImplControlValue aControlValue;
+ Point aPoint;
+ tools::Rectangle aContent, aBound;
+
+ // Use the full extent of the control
+ tools::Rectangle aArea( aPoint, pBorder->GetOutputSizePixel() );
+
+ if ( GetNativeControlRegion( ControlType::Listbox, ControlPart::ButtonDown,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ // Convert back from border space to local coordinates
+ aPoint = pBorder->ScreenToOutputPixel( OutputToScreenPixel( aPoint ) );
+ aContent.Move( -aPoint.X(), -aPoint.Y() );
+
+ // Use the themes drop down size for the button
+ aOutSz.setWidth( aContent.Left() );
+ mpBtn->setPosSizePixel( aContent.Left(), 0, aContent.GetWidth(), nBottom );
+
+ // Adjust the size of the edit field
+ if ( GetNativeControlRegion( ControlType::Listbox, ControlPart::SubEdit,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ // Convert back from border space to local coordinates
+ aContent.Move( -aPoint.X(), -aPoint.Y() );
+
+ // Use the themes drop down size
+ if( ! (GetStyle() & WB_BORDER) && ImplGetSVData()->maNWFData.mbNoFocusRects )
+ {
+ // No border but focus ring behavior -> we have a problem; the
+ // native rect relies on the border to draw the focus
+ // let's do the best we can and center vertically, so it doesn't look
+ // completely wrong.
+ Size aSz( GetOutputSizePixel() );
+ tools::Long nDiff = aContent.Top() - (aSz.Height() - aContent.GetHeight())/2;
+ aContent.AdjustTop( -nDiff );
+ aContent.AdjustBottom( -nDiff );
+ }
+ mpImplWin->SetPosSizePixel( aContent.TopLeft(), aContent.GetSize() );
+ }
+ else
+ mpImplWin->SetSizePixel( aOutSz );
+ }
+ else
+ {
+ nSBWidth = CalcZoom( nSBWidth );
+ mpImplWin->setPosSizePixel( 0, 0, aOutSz.Width() - nSBWidth, aOutSz.Height() );
+ mpBtn->setPosSizePixel( aOutSz.Width() - nSBWidth, 0, nSBWidth, aOutSz.Height() );
+ }
+ }
+ else
+ {
+ mpImplLB->SetSizePixel( aOutSz );
+ }
+
+ // Retain FloatingWindow size even when it's invisible, as we still process KEY_PGUP/DOWN ...
+ if ( mpFloatWin )
+ mpFloatWin->SetSizePixel( mpFloatWin->CalcFloatSize() );
+
+ Control::Resize();
+}
+
+void ListBox::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ const ImplListBoxWindow* rMainWin = mpImplLB->GetMainWindow();
+ if( mpFloatWin )
+ {
+ // Dropdown mode
+ AppendLayoutData( *mpImplWin );
+ mpImplWin->SetLayoutDataParent( this );
+ if( mpFloatWin->IsReallyVisible() )
+ {
+ AppendLayoutData( *rMainWin );
+ rMainWin->SetLayoutDataParent( this );
+ }
+ }
+ else
+ {
+ AppendLayoutData( *rMainWin );
+ rMainWin->SetLayoutDataParent( this );
+ }
+}
+
+tools::Long ListBox::GetIndexForPoint( const Point& rPoint, sal_Int32& rPos ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+
+ // Check whether rPoint fits at all
+ tools::Long nIndex = Control::GetIndexForPoint( rPoint );
+ if( nIndex != -1 )
+ {
+ // Point must be either in main list window
+ // or in impl window (dropdown case)
+ ImplListBoxWindow* rMain = mpImplLB->GetMainWindow();
+
+ // Convert coordinates to ImplListBoxWindow pixel coordinate space
+ Point aConvPoint = LogicToPixel( rPoint );
+ aConvPoint = OutputToAbsoluteScreenPixel( aConvPoint );
+ aConvPoint = rMain->AbsoluteScreenToOutputPixel( aConvPoint );
+ aConvPoint = rMain->PixelToLogic( aConvPoint );
+
+ // Try to find entry
+ sal_Int32 nEntry = rMain->GetEntryPosForPoint( aConvPoint );
+ if( nEntry == LISTBOX_ENTRY_NOTFOUND )
+ {
+ // Not found, maybe dropdown case
+ if( mpImplWin && mpImplWin->IsReallyVisible() )
+ {
+ // Convert to impl window pixel coordinates
+ aConvPoint = LogicToPixel( rPoint );
+ aConvPoint = OutputToAbsoluteScreenPixel( aConvPoint );
+ aConvPoint = mpImplWin->AbsoluteScreenToOutputPixel( aConvPoint );
+
+ // Check whether converted point is inside impl window
+ Size aImplWinSize = mpImplWin->GetOutputSizePixel();
+ if( aConvPoint.X() >= 0 && aConvPoint.Y() >= 0 && aConvPoint.X() < aImplWinSize.Width() && aConvPoint.Y() < aImplWinSize.Height() )
+ {
+ // Inside the impl window, the position is the current item pos
+ rPos = mpImplWin->GetItemPos();
+ }
+ else
+ nIndex = -1;
+ }
+ else
+ nIndex = -1;
+ }
+ else
+ rPos = nEntry;
+
+ SAL_WARN_IF( nIndex == -1, "vcl", "found index for point, but relative index failed" );
+ }
+
+ // Get line relative index
+ if( nIndex != -1 )
+ nIndex = ToRelativeLineIndex( nIndex );
+
+ return nIndex;
+}
+
+void ListBox::StateChanged( StateChangedType nType )
+{
+ if( nType == StateChangedType::ReadOnly )
+ {
+ if( mpImplWin )
+ mpImplWin->Enable( !IsReadOnly() );
+ if( mpBtn )
+ mpBtn->Enable( !IsReadOnly() );
+ }
+ else if( nType == StateChangedType::Enable )
+ {
+ mpImplLB->Enable( IsEnabled() );
+ if( mpImplWin )
+ {
+ mpImplWin->Enable( IsEnabled() );
+ if ( IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)
+ && ! IsNativeControlSupported(ControlType::Listbox, ControlPart::ButtonDown) )
+ {
+ GetWindow( GetWindowType::Border )->Invalidate( InvalidateFlags::NoErase );
+ }
+ else
+ mpImplWin->Invalidate();
+ }
+ if( mpBtn )
+ mpBtn->Enable( IsEnabled() );
+ }
+ else if( nType == StateChangedType::UpdateMode )
+ {
+ mpImplLB->SetUpdateMode( IsUpdateMode() );
+ }
+ else if ( nType == StateChangedType::Zoom )
+ {
+ mpImplLB->SetZoom( GetZoom() );
+ if ( mpImplWin )
+ {
+ mpImplWin->SetZoom( GetZoom() );
+ mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() );
+ mpImplWin->Invalidate();
+ }
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlFont )
+ {
+ mpImplLB->SetControlFont( GetControlFont() );
+ if ( mpImplWin )
+ {
+ mpImplWin->SetControlFont( GetControlFont() );
+ mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() );
+ mpImplWin->Invalidate();
+ }
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ mpImplLB->SetControlForeground( GetControlForeground() );
+ if ( mpImplWin )
+ {
+ mpImplWin->SetControlForeground( GetControlForeground() );
+ mpImplWin->SetTextColor( GetControlForeground() );
+ mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() );
+ mpImplWin->Invalidate();
+ }
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ mpImplLB->SetControlBackground( GetControlBackground() );
+ if ( mpImplWin )
+ {
+ if ( mpImplWin->IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire) )
+ {
+ // Transparent background
+ mpImplWin->SetBackground();
+ mpImplWin->SetControlBackground();
+ }
+ else
+ {
+ mpImplWin->SetBackground( mpImplLB->GetMainWindow()->GetControlBackground() );
+ mpImplWin->SetControlBackground( mpImplLB->GetMainWindow()->GetControlBackground() );
+ }
+ mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() );
+ mpImplWin->Invalidate();
+ }
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ mpImplLB->GetMainWindow()->EnableSort( ( GetStyle() & WB_SORT ) != 0 );
+ bool bSimpleMode = ( GetStyle() & WB_SIMPLEMODE ) != 0;
+ mpImplLB->SetMultiSelectionSimpleMode( bSimpleMode );
+ }
+ else if( nType == StateChangedType::Mirroring )
+ {
+ if( mpBtn )
+ {
+ mpBtn->EnableRTL( IsRTLEnabled() );
+ ImplInitDropDownButton( mpBtn );
+ }
+ mpImplLB->EnableRTL( IsRTLEnabled() );
+ if( mpImplWin )
+ mpImplWin->EnableRTL( IsRTLEnabled() );
+ Resize();
+ }
+
+ Control::StateChanged( nType );
+}
+
+bool ListBox::PreNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+ if ( mpImplLB )
+ {
+ if( ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) && ( rNEvt.GetWindow() == mpImplWin ) )
+ {
+ KeyEvent aKeyEvt = *rNEvt.GetKeyEvent();
+ switch( aKeyEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_DOWN:
+ {
+ if( mpFloatWin && !mpFloatWin->IsInPopupMode() &&
+ aKeyEvt.GetKeyCode().IsMod2() )
+ {
+ CallEventListeners( VclEventId::DropdownPreOpen );
+ mpBtn->SetPressed( true );
+ mpFloatWin->StartFloat( false );
+ CallEventListeners( VclEventId::DropdownOpen );
+ bDone = true;
+ }
+ else
+ {
+ bDone = mpImplLB->ProcessKeyInput( aKeyEvt );
+ }
+ }
+ break;
+ case KEY_UP:
+ {
+ if( mpFloatWin && mpFloatWin->IsInPopupMode() &&
+ aKeyEvt.GetKeyCode().IsMod2() )
+ {
+ mpFloatWin->EndPopupMode();
+ bDone = true;
+ }
+ else
+ {
+ bDone = mpImplLB->ProcessKeyInput( aKeyEvt );
+ }
+ }
+ break;
+ case KEY_RETURN:
+ {
+ if( IsInDropDown() )
+ {
+ mpImplLB->ProcessKeyInput( aKeyEvt );
+ bDone = true;
+ }
+ }
+ break;
+
+ default:
+ {
+ bDone = mpImplLB->ProcessKeyInput( aKeyEvt );
+ }
+ }
+ }
+ else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( IsInDropDown() && !HasChildPathFocus( true ) )
+ mpFloatWin->EndPopupMode();
+ }
+ else if ( (rNEvt.GetType() == MouseNotifyEvent::COMMAND) &&
+ (rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) &&
+ (rNEvt.GetWindow() == mpImplWin) )
+ {
+ MouseWheelBehaviour nWheelBehavior( GetSettings().GetMouseSettings().GetWheelBehavior() );
+ if ( ( nWheelBehavior == MouseWheelBehaviour::ALWAYS )
+ || ( ( nWheelBehavior == MouseWheelBehaviour::FocusOnly )
+ && HasChildPathFocus()
+ )
+ )
+ {
+ bDone = mpImplLB->HandleWheelAsCursorTravel(*rNEvt.GetCommandEvent(), *this);
+ }
+ else
+ {
+ bDone = false; // Don't consume this event, let the default handling take it (i.e. scroll the context)
+ }
+ }
+ }
+
+ return bDone || Control::PreNotify( rNEvt );
+}
+
+void ListBox::Select()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ListboxSelect, [this] () { maSelectHdl.Call(*this); } );
+}
+
+void ListBox::DoubleClick()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ListboxDoubleClick, {} );
+}
+
+void ListBox::Clear()
+{
+ if (!mpImplLB)
+ return;
+ mpImplLB->Clear();
+ if( IsDropDownBox() )
+ {
+ mpImplWin->SetItemPos( LISTBOX_ENTRY_NOTFOUND );
+ mpImplWin->SetString( OUString() );
+ Image aImage;
+ mpImplWin->SetImage( aImage );
+ mpImplWin->Invalidate();
+ }
+ CallEventListeners( VclEventId::ListboxItemRemoved, reinterpret_cast<void*>(-1) );
+}
+
+void ListBox::SetNoSelection()
+{
+ mpImplLB->SetNoSelection();
+ if( IsDropDownBox() )
+ {
+ mpImplWin->SetItemPos( LISTBOX_ENTRY_NOTFOUND );
+ mpImplWin->SetString( OUString() );
+ Image aImage;
+ mpImplWin->SetImage( aImage );
+ mpImplWin->Invalidate();
+ }
+}
+
+sal_Int32 ListBox::InsertEntry( const OUString& rStr, sal_Int32 nPos )
+{
+ sal_Int32 nRealPos = mpImplLB->InsertEntry( nPos + mpImplLB->GetEntryList().GetMRUCount(), rStr );
+ nRealPos = sal::static_int_cast<sal_Int32>(nRealPos - mpImplLB->GetEntryList().GetMRUCount());
+ CallEventListeners( VclEventId::ListboxItemAdded, reinterpret_cast<void*>(nRealPos) );
+ return nRealPos;
+}
+
+sal_Int32 ListBox::InsertEntry( const OUString& rStr, const Image& rImage, sal_Int32 nPos )
+{
+ sal_Int32 nRealPos = mpImplLB->InsertEntry( nPos + mpImplLB->GetEntryList().GetMRUCount(), rStr, rImage );
+ nRealPos = sal::static_int_cast<sal_Int32>(nRealPos - mpImplLB->GetEntryList().GetMRUCount());
+ CallEventListeners( VclEventId::ListboxItemAdded, reinterpret_cast<void*>(nRealPos) );
+ return nRealPos;
+}
+
+void ListBox::RemoveEntry( sal_Int32 nPos )
+{
+ mpImplLB->RemoveEntry( nPos + mpImplLB->GetEntryList().GetMRUCount() );
+ CallEventListeners( VclEventId::ListboxItemRemoved, reinterpret_cast<void*>(nPos) );
+}
+
+Image ListBox::GetEntryImage( sal_Int32 nPos ) const
+{
+ if ( mpImplLB && mpImplLB->GetEntryList().HasEntryImage( nPos ) )
+ return mpImplLB->GetEntryList().GetEntryImage( nPos );
+ return Image();
+}
+
+sal_Int32 ListBox::GetEntryPos( std::u16string_view rStr ) const
+{
+ if (!mpImplLB)
+ return LISTBOX_ENTRY_NOTFOUND;
+ sal_Int32 nPos = mpImplLB->GetEntryList().FindEntry( rStr );
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ nPos = nPos - mpImplLB->GetEntryList().GetMRUCount();
+ return nPos;
+}
+
+OUString ListBox::GetEntry( sal_Int32 nPos ) const
+{
+ if (!mpImplLB)
+ return OUString();
+ return mpImplLB->GetEntryList().GetEntryText( nPos + mpImplLB->GetEntryList().GetMRUCount() );
+}
+
+sal_Int32 ListBox::GetEntryCount() const
+{
+ if (!mpImplLB)
+ return 0;
+ return mpImplLB->GetEntryList().GetEntryCount() - mpImplLB->GetEntryList().GetMRUCount();
+}
+
+OUString ListBox::GetSelectedEntry(sal_Int32 nIndex) const
+{
+ return GetEntry( GetSelectedEntryPos( nIndex ) );
+}
+
+sal_Int32 ListBox::GetSelectedEntryCount() const
+{
+ if (!mpImplLB)
+ return 0;
+ return mpImplLB->GetEntryList().GetSelectedEntryCount();
+}
+
+sal_Int32 ListBox::GetSelectedEntryPos( sal_Int32 nIndex ) const
+{
+ if (!mpImplLB)
+ return LISTBOX_ENTRY_NOTFOUND;
+
+ sal_Int32 nPos = mpImplLB->GetEntryList().GetSelectedEntryPos( nIndex );
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ if ( nPos < mpImplLB->GetEntryList().GetMRUCount() )
+ nPos = mpImplLB->GetEntryList().FindEntry( mpImplLB->GetEntryList().GetEntryText( nPos ) );
+ nPos = nPos - mpImplLB->GetEntryList().GetMRUCount();
+ }
+ return nPos;
+}
+
+bool ListBox::IsEntryPosSelected( sal_Int32 nPos ) const
+{
+ return mpImplLB->GetEntryList().IsEntryPosSelected( nPos + mpImplLB->GetEntryList().GetMRUCount() );
+}
+
+void ListBox::SelectEntry( std::u16string_view rStr, bool bSelect )
+{
+ SelectEntryPos( GetEntryPos( rStr ), bSelect );
+}
+
+void ListBox::SelectEntryPos( sal_Int32 nPos, bool bSelect )
+{
+ if (!mpImplLB)
+ return;
+
+ if ( 0 <= nPos && nPos < mpImplLB->GetEntryList().GetEntryCount() )
+ {
+ sal_Int32 nCurrentPos = mpImplLB->GetCurrentPos();
+ mpImplLB->SelectEntry( nPos + mpImplLB->GetEntryList().GetMRUCount(), bSelect );
+ //Only when bSelect == true, send both Selection & Focus events
+ if (nCurrentPos != nPos && bSelect)
+ {
+ CallEventListeners( VclEventId::ListboxSelect, reinterpret_cast<void*>(nPos));
+ if (HasFocus())
+ CallEventListeners( VclEventId::ListboxFocus, reinterpret_cast<void*>(nPos));
+ }
+ }
+}
+
+void ListBox::SelectEntriesPos( const std::vector<sal_Int32>& rPositions, bool bSelect )
+{
+ if (!mpImplLB)
+ return;
+
+ bool bCallListeners = false;
+
+ const sal_Int32 nCurrentPos = mpImplLB->GetCurrentPos();
+ const auto nEntryCount = mpImplLB->GetEntryList().GetEntryCount();
+ const auto nMRUCount = mpImplLB->GetEntryList().GetMRUCount();
+
+ for (auto nPos : rPositions)
+ {
+ if (0 <= nPos && nPos < nEntryCount)
+ {
+ mpImplLB->SelectEntry(nPos + nMRUCount, bSelect);
+ if (nCurrentPos != nPos && bSelect)
+ bCallListeners = true;
+ }
+ }
+
+ //Only when bSelect == true, send both Selection & Focus events
+ if (bCallListeners)
+ {
+ CallEventListeners(VclEventId::ListboxSelect);
+ if (HasFocus())
+ CallEventListeners(VclEventId::ListboxFocus);
+ }
+}
+
+void ListBox::SetEntryData( sal_Int32 nPos, void* pNewData )
+{
+ mpImplLB->SetEntryData( nPos + mpImplLB->GetEntryList().GetMRUCount(), pNewData );
+}
+
+void* ListBox::GetEntryData( sal_Int32 nPos ) const
+{
+ return mpImplLB->GetEntryList().GetEntryData( nPos + mpImplLB->GetEntryList().GetMRUCount() );
+}
+
+void ListBox::SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags )
+{
+ mpImplLB->SetEntryFlags( nPos + mpImplLB->GetEntryList().GetMRUCount(), nFlags );
+}
+
+void ListBox::SetTopEntry( sal_Int32 nPos )
+{
+ mpImplLB->SetTopEntry( nPos + mpImplLB->GetEntryList().GetMRUCount() );
+}
+
+sal_Int32 ListBox::GetTopEntry() const
+{
+ sal_Int32 nPos = GetEntryCount() ? mpImplLB->GetTopEntry() : LISTBOX_ENTRY_NOTFOUND;
+ if ( nPos < mpImplLB->GetEntryList().GetMRUCount() )
+ nPos = 0;
+ return nPos;
+}
+
+bool ListBox::IsTravelSelect() const
+{
+ return mpImplLB->IsTravelSelect();
+}
+
+bool ListBox::IsInDropDown() const
+{
+ // when the dropdown is dismissed, first mbInPopupMode is set to false, and on the next event iteration then
+ // mbPopupMode is set to false
+ return mpFloatWin && mpFloatWin->IsInPopupMode() && mpFloatWin->ImplIsInPrivatePopupMode();
+}
+
+tools::Rectangle ListBox::GetBoundingRectangle( sal_Int32 nItem ) const
+{
+ tools::Rectangle aRect = mpImplLB->GetMainWindow()->GetBoundingRectangle( nItem );
+ tools::Rectangle aOffset = mpImplLB->GetMainWindow()->GetWindowExtentsRelative( static_cast<vcl::Window*>(const_cast<ListBox *>(this)) );
+ aRect.Move( aOffset.Left(), aOffset.Top() );
+ return aRect;
+}
+
+void ListBox::EnableMultiSelection( bool bMulti )
+{
+ mpImplLB->EnableMultiSelection( bMulti );
+
+ // WB_SIMPLEMODE:
+ // The MultiListBox behaves just like a normal ListBox
+ // MultiSelection is possible via corresponding additional keys
+ bool bSimpleMode = ( GetStyle() & WB_SIMPLEMODE ) != 0;
+ mpImplLB->SetMultiSelectionSimpleMode( bSimpleMode );
+
+ // In a MultiSelection, we can't see us travelling without focus
+ if ( mpFloatWin )
+ mpImplLB->GetMainWindow()->AllowGrabFocus( bMulti );
+}
+
+bool ListBox::IsMultiSelectionEnabled() const
+{
+ return mpImplLB->IsMultiSelectionEnabled();
+}
+
+Size ListBox::CalcMinimumSize() const
+{
+ Size aSz;
+
+ if (!mpImplLB)
+ return aSz;
+
+ aSz = CalcSubEditSize();
+
+ bool bAddScrollWidth = false;
+
+ if (IsDropDownBox())
+ {
+ aSz.AdjustHeight(4 ); // add a space between entry and border
+ aSz.AdjustWidth(4 ); // add a little breathing space
+ bAddScrollWidth = true;
+ }
+ else
+ bAddScrollWidth = (GetStyle() & WB_VSCROLL) == WB_VSCROLL;
+
+ if (bAddScrollWidth)
+ {
+ // Try native borders; scrollbar size may not be a good indicator
+ // See how large the edit area inside is to estimate what is needed for the dropdown
+ ImplControlValue aControlValue;
+ tools::Rectangle aContent, aBound;
+ Size aTestSize( 100, 20 );
+ tools::Rectangle aArea( Point(), aTestSize );
+ if( GetNativeControlRegion( ControlType::Listbox, ControlPart::SubEdit, aArea, ControlState::NONE,
+ aControlValue, aBound, aContent) )
+ {
+ // use the themes drop down size
+ aSz.AdjustWidth(aTestSize.Width() - aContent.GetWidth() );
+ }
+ else
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ }
+
+ aSz = CalcWindowSize( aSz );
+
+ if (IsDropDownBox()) // Check minimum height of dropdown box
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aRect( Point( 0, 0 ), aSz );
+ tools::Rectangle aContent, aBound;
+ if( GetNativeControlRegion( ControlType::Listbox, ControlPart::Entire, aRect, ControlState::NONE,
+ aControlValue, aBound, aContent) )
+ {
+ if( aBound.GetHeight() > aSz.Height() )
+ aSz.setHeight( aBound.GetHeight() );
+ }
+ }
+
+ return aSz;
+}
+
+Size ListBox::CalcSubEditSize() const
+{
+ Size aSz;
+
+ if (!mpImplLB)
+ return aSz;
+
+ if ( !IsDropDownBox() )
+ aSz = mpImplLB->CalcSize (mnLineCount ? mnLineCount : mpImplLB->GetEntryList().GetEntryCount());
+ else
+ {
+ aSz.setHeight( mpImplLB->GetEntryHeight() );
+ // Size to maximum entry width
+ aSz.setWidth( mpImplLB->GetMaxEntryWidth() );
+
+ if (m_nMaxWidthChars != -1)
+ {
+ tools::Long nMaxWidth = m_nMaxWidthChars * approximate_char_width();
+ aSz.setWidth( std::min(aSz.Width(), nMaxWidth) );
+ }
+
+ // Do not create ultrathin ListBoxes, it doesn't look good
+ if( aSz.Width() < GetSettings().GetStyleSettings().GetScrollBarSize() )
+ aSz.setWidth( GetSettings().GetStyleSettings().GetScrollBarSize() );
+ }
+
+ return aSz;
+}
+
+Size ListBox::GetOptimalSize() const
+{
+ return CalcMinimumSize();
+}
+
+Size ListBox::CalcAdjustedSize( const Size& rPrefSize ) const
+{
+ Size aSz = rPrefSize;
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ static_cast<vcl::Window*>(const_cast<ListBox *>(this))->GetBorder( nLeft, nTop, nRight, nBottom );
+ aSz.AdjustHeight( -(nTop+nBottom) );
+ if ( !IsDropDownBox() )
+ {
+ tools::Long nEntryHeight = CalcBlockSize( 1, 1 ).Height();
+ tools::Long nLines = aSz.Height() / nEntryHeight;
+ if ( nLines < 1 )
+ nLines = 1;
+ aSz.setHeight( nLines * nEntryHeight );
+ }
+ else
+ {
+ aSz.setHeight( mnDDHeight );
+ }
+ aSz.AdjustHeight(nTop+nBottom );
+
+ aSz = CalcWindowSize( aSz );
+ return aSz;
+}
+
+Size ListBox::CalcBlockSize( sal_uInt16 nColumns, sal_uInt16 nLines ) const
+{
+ // ScrollBars are shown if needed
+ Size aMinSz = CalcMinimumSize();
+ // aMinSz = ImplCalcOutSz( aMinSz );
+
+ Size aSz;
+
+ // Height
+ if ( nLines )
+ {
+ if ( !IsDropDownBox() )
+ aSz.setHeight( mpImplLB->CalcSize( nLines ).Height() );
+ else
+ aSz.setHeight( mnDDHeight );
+ }
+ else
+ aSz.setHeight( aMinSz.Height() );
+
+ // Width
+ if ( nColumns )
+ aSz.setWidth( nColumns * GetTextWidth( OUString('X') ) );
+ else
+ aSz.setWidth( aMinSz.Width() );
+
+ if ( IsDropDownBox() )
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
+
+ if ( !IsDropDownBox() )
+ {
+ if ( aSz.Width() < aMinSz.Width() )
+ aSz.AdjustHeight(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ if ( aSz.Height() < aMinSz.Height() )
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ }
+
+ aSz = CalcWindowSize( aSz );
+ return aSz;
+}
+
+void ListBox::GetMaxVisColumnsAndLines( sal_uInt16& rnCols, sal_uInt16& rnLines ) const
+{
+ float nCharWidth = approximate_char_width();
+ if ( !IsDropDownBox() )
+ {
+ Size aOutSz = mpImplLB->GetMainWindow()->GetOutputSizePixel();
+ rnCols = static_cast<sal_uInt16>(aOutSz.Width()/nCharWidth);
+ rnLines = static_cast<sal_uInt16>(aOutSz.Height()/mpImplLB->GetEntryHeightWithMargin());
+ }
+ else
+ {
+ Size aOutSz = mpImplWin->GetOutputSizePixel();
+ rnCols = static_cast<sal_uInt16>(aOutSz.Width()/nCharWidth);
+ rnLines = 1;
+ }
+}
+
+void ListBox::SetReadOnly( bool bReadOnly )
+{
+ if ( mpImplLB->IsReadOnly() != bReadOnly )
+ {
+ mpImplLB->SetReadOnly( bReadOnly );
+ CompatStateChanged( StateChangedType::ReadOnly );
+ }
+}
+
+bool ListBox::IsReadOnly() const
+{
+ return mpImplLB->IsReadOnly();
+}
+
+void ListBox::SetSeparatorPos( sal_Int32 n )
+{
+ mpImplLB->SetSeparatorPos( n );
+}
+
+sal_Int32 ListBox::GetSeparatorPos() const
+{
+ return mpImplLB->GetSeparatorPos();
+}
+
+void ListBox::AddSeparator( sal_Int32 n )
+{
+ mpImplLB->AddSeparator( n );
+}
+
+sal_uInt16 ListBox::GetDisplayLineCount() const
+{
+ return mpImplLB->GetDisplayLineCount();
+}
+
+tools::Rectangle ListBox::GetDropDownPosSizePixel() const
+{
+ return mpFloatWin ? mpFloatWin->GetWindowExtentsRelative(this) : tools::Rectangle();
+}
+
+const Wallpaper& ListBox::GetDisplayBackground() const
+{
+ // !!! Recursion does not occur because the ImplListBox is initialized by default
+ // to a non-transparent color in Window::ImplInitData
+ return mpImplLB->GetDisplayBackground();
+}
+
+void ListBox::setMaxWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != m_nMaxWidthChars)
+ {
+ m_nMaxWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+bool ListBox::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "active")
+ SelectEntryPos(rValue.toInt32());
+ else if (rKey == "max-width-chars")
+ setMaxWidthChars(rValue.toInt32());
+ else if (rKey == "can-focus")
+ {
+ // as far as I can see in Gtk, setting a ComboBox as can.focus means
+ // the focus gets stuck in it, so try here to behave like gtk does
+ // with the settings that work, i.e. can.focus of false doesn't
+ // set the hard WB_NOTABSTOP
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_TABSTOP|WB_NOTABSTOP);
+ if (toBool(rValue))
+ nBits |= WB_TABSTOP;
+ SetStyle(nBits);
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+FactoryFunction ListBox::GetUITestFactory() const
+{
+ return ListBoxUIObject::create;
+}
+
+void ListBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+
+ {
+ auto entriesNode = rJsonWriter.startArray("entries");
+ for (int i = 0; i < GetEntryCount(); ++i)
+ {
+ rJsonWriter.putSimpleValue(GetEntry(i));
+ }
+ }
+
+ rJsonWriter.put("selectedCount", GetSelectedEntryCount());
+
+ {
+ auto entriesNode = rJsonWriter.startArray("selectedEntries");
+ for (int i = 0; i < GetSelectedEntryCount(); ++i)
+ {
+ rJsonWriter.putSimpleValue(OUString::number(GetSelectedEntryPos(i)));
+ }
+ }
+}
+
+MultiListBox::MultiListBox( vcl::Window* pParent, WinBits nStyle ) :
+ ListBox( WindowType::MULTILISTBOX )
+{
+ ImplInit( pParent, nStyle );
+ EnableMultiSelection( true );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/longcurr.cxx b/vcl/source/control/longcurr.cxx
new file mode 100644
index 000000000..c2c6e4134
--- /dev/null
+++ b/vcl/source/control/longcurr.cxx
@@ -0,0 +1,429 @@
+/* -*- 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 <string_view>
+
+#include <comphelper/string.hxx>
+#include <tools/bigint.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/toolkit/longcurr.hxx>
+#include <vcl/weldutils.hxx>
+
+#include <unotools/localedatawrapper.hxx>
+
+using namespace ::comphelper;
+
+namespace
+{
+
+BigInt ImplPower10( sal_uInt16 n )
+{
+ sal_uInt16 i;
+ BigInt nValue = 1;
+
+ for ( i=0; i < n; i++ )
+ nValue *= 10;
+
+ return nValue;
+}
+
+OUString ImplGetCurr( const LocaleDataWrapper& rLocaleDataWrapper, const BigInt &rNumber, sal_uInt16 nDigits, std::u16string_view rCurrSymbol, bool bShowThousandSep )
+{
+ SAL_WARN_IF( nDigits >= 10, "vcl", "LongCurrency may only have 9 decimal places" );
+
+ if ( rNumber.IsZero() || static_cast<tools::Long>(rNumber) )
+ return rLocaleDataWrapper.getCurr( static_cast<tools::Long>(rNumber), nDigits, rCurrSymbol, bShowThousandSep );
+
+ BigInt aTmp( ImplPower10( nDigits ) );
+ BigInt aInteger( rNumber );
+ aInteger.Abs();
+ aInteger /= aTmp;
+ BigInt aFraction( rNumber );
+ aFraction.Abs();
+ aFraction %= aTmp;
+ if ( !aInteger.IsZero() )
+ {
+ aFraction += aTmp;
+ aTmp = 1000000000;
+ }
+ if ( rNumber.IsNeg() )
+ aFraction *= -1;
+
+ OUStringBuffer aTemplate(rLocaleDataWrapper.getCurr( static_cast<tools::Long>(aFraction), nDigits, rCurrSymbol, bShowThousandSep ));
+ while( !aInteger.IsZero() )
+ {
+ aFraction = aInteger;
+ aFraction %= aTmp;
+ aInteger /= aTmp;
+ if( !aInteger.IsZero() )
+ aFraction += aTmp;
+
+ OUString aFractionStr = rLocaleDataWrapper.getNum( static_cast<tools::Long>(aFraction), 0 );
+
+ sal_Int32 nSPos = aTemplate.indexOf( '1' );
+ if (nSPos == -1)
+ break;
+ if ( aFractionStr.getLength() == 1 )
+ aTemplate[ nSPos ] = aFractionStr[0];
+ else
+ {
+ aTemplate.remove( nSPos, 1 );
+ aTemplate.insert( nSPos, aFractionStr );
+ }
+ }
+
+ return aTemplate.makeStringAndClear();
+}
+
+bool ImplCurrencyGetValue( const OUString& rStr, BigInt& rValue,
+ sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper )
+{
+ OUString aStr = rStr;
+ OUStringBuffer aStr1;
+ OUStringBuffer aStr2;
+ sal_Int32 nDecPos;
+ bool bNegative = false;
+
+ // On empty string
+ if ( rStr.isEmpty() )
+ return false;
+
+ // Trim leading and trailing spaces
+ aStr = string::strip(aStr, ' ');
+
+ // Find decimal sign's position
+ nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSep() );
+ if (nDecPos < 0 && !rLocaleDataWrapper.getNumDecimalSepAlt().isEmpty())
+ nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSepAlt() );
+
+ if ( nDecPos != -1 )
+ {
+ aStr1 = aStr.subView( 0, nDecPos );
+ aStr2.append(aStr.subView(nDecPos+1));
+ }
+ else
+ aStr1 = aStr;
+
+ // Negative?
+ if ( (aStr[ 0 ] == '(') && (aStr[ aStr.getLength()-1 ] == ')') )
+ bNegative = true;
+ if ( !bNegative )
+ {
+ for (sal_Int32 i=0; i < aStr.getLength(); i++ )
+ {
+ if ( (aStr[ i ] >= '0') && (aStr[ i ] <= '9') )
+ break;
+ else if ( aStr[ i ] == '-' )
+ {
+ bNegative = true;
+ break;
+ }
+ }
+ }
+ if ( !bNegative && !aStr.isEmpty() )
+ {
+ sal_uInt16 nFormat = rLocaleDataWrapper.getCurrNegativeFormat();
+ if ( (nFormat == 3) || (nFormat == 6) ||
+ (nFormat == 7) || (nFormat == 10) )
+ {
+ for (sal_Int32 i = aStr.getLength()-1; i > 0; i++ )
+ {
+ if ( (aStr[ i ] >= '0') && (aStr[ i ] <= '9') )
+ break;
+ else if ( aStr[ i ] == '-' )
+ {
+ bNegative = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // delete unwanted characters
+ for (sal_Int32 i=0; i < aStr1.getLength(); )
+ {
+ if ( (aStr1[ i ] >= '0') && (aStr1[ i ] <= '9') )
+ i++;
+ else
+ aStr1.remove( i, 1 );
+ }
+ for (sal_Int32 i=0; i < aStr2.getLength(); )
+ {
+ if ((aStr2[i] >= '0') && (aStr2[i] <= '9'))
+ ++i;
+ else
+ aStr2.remove(i, 1);
+ }
+
+ if ( aStr1.isEmpty() && aStr2.isEmpty())
+ return false;
+
+ if ( aStr1.isEmpty() )
+ aStr1 = "0";
+ if ( bNegative )
+ aStr1.insert( 0, '-');
+
+ // Cut down decimal part and round while doing so
+ bool bRound = false;
+ if (aStr2.getLength() > nDecDigits)
+ {
+ if (aStr2[nDecDigits] >= '5')
+ bRound = true;
+ string::truncateToLength(aStr2, nDecDigits);
+ }
+ string::padToLength(aStr2, nDecDigits, '0');
+
+ aStr1.append(aStr2);
+ aStr = aStr1.makeStringAndClear();
+
+ // check range
+ BigInt nValue( aStr );
+ if ( bRound )
+ {
+ if ( !bNegative )
+ nValue+=1;
+ else
+ nValue-=1;
+ }
+
+ rValue = nValue;
+
+ return true;
+}
+
+} // namespace
+
+static bool ImplLongCurrencyGetValue( const OUString& rStr, BigInt& rValue,
+ sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper )
+{
+ return ImplCurrencyGetValue( rStr, rValue, nDecDigits, rLocaleDataWrapper );
+}
+
+namespace weld
+{
+ IMPL_LINK_NOARG(LongCurrencyFormatter, FormatOutputHdl, LinkParamNone*, bool)
+ {
+ const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
+ const OUString& rCurrencySymbol = !m_aCurrencySymbol.isEmpty() ? m_aCurrencySymbol : rLocaleDataWrapper.getCurrSymbol();
+ double fValue = GetValue() * weld::SpinButton::Power10(GetDecimalDigits());
+ OUString aText = ImplGetCurr(rLocaleDataWrapper, fValue, GetDecimalDigits(), rCurrencySymbol, m_bThousandSep);
+ ImplSetTextImpl(aText, nullptr);
+ return true;
+ }
+
+ IMPL_LINK(LongCurrencyFormatter, ParseInputHdl, sal_Int64*, result, TriState)
+ {
+ const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
+
+ BigInt value;
+ bool bRet = ImplLongCurrencyGetValue(GetEntryText(), value, GetDecimalDigits(), rLocaleDataWrapper);
+
+ if (bRet)
+ *result = double(value);
+
+ return bRet ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+}
+
+bool ImplLongCurrencyReformat( const OUString& rStr, BigInt const & nMin, BigInt const & nMax,
+ sal_uInt16 nDecDigits,
+ const LocaleDataWrapper& rLocaleDataWrapper, OUString& rOutStr,
+ LongCurrencyFormatter const & rFormatter )
+{
+ BigInt nValue;
+ if ( !ImplCurrencyGetValue( rStr, nValue, nDecDigits, rLocaleDataWrapper ) )
+ return true;
+ else
+ {
+ BigInt nTempVal = nValue;
+ if ( nTempVal > nMax )
+ nTempVal = nMax;
+ else if ( nTempVal < nMin )
+ nTempVal = nMin;
+
+ rOutStr = ImplGetCurr( rLocaleDataWrapper, nTempVal, nDecDigits, rFormatter.GetCurrencySymbol(), /*IsUseThousandSep*/true );
+ return true;
+ }
+}
+
+void LongCurrencyFormatter::ImpInit()
+{
+ mnLastValue = 0;
+ mnMin = 0;
+ mnMax = 0x7FFFFFFF;
+ mnMax *= 0x7FFFFFFF;
+ mnDecimalDigits = 0;
+ SetDecimalDigits( 0 );
+}
+
+LongCurrencyFormatter::LongCurrencyFormatter(Edit* pEdit)
+ : FormatterBase(pEdit)
+{
+ ImpInit();
+}
+
+LongCurrencyFormatter::~LongCurrencyFormatter()
+{
+}
+
+OUString const & LongCurrencyFormatter::GetCurrencySymbol() const
+{
+ return GetLocaleDataWrapper().getCurrSymbol();
+}
+
+void LongCurrencyFormatter::SetValue(const BigInt& rNewValue)
+{
+ SetUserValue(rNewValue);
+ SetEmptyFieldValueData( false );
+}
+
+void LongCurrencyFormatter::SetUserValue( BigInt nNewValue )
+{
+ if ( nNewValue > mnMax )
+ nNewValue = mnMax;
+ else if ( nNewValue < mnMin )
+ nNewValue = mnMin;
+ mnLastValue = nNewValue;
+
+ if ( !GetField() )
+ return;
+
+ OUString aStr = ImplGetCurr( GetLocaleDataWrapper(), nNewValue, GetDecimalDigits(), GetCurrencySymbol(), /*UseThousandSep*/true );
+ if ( GetField()->HasFocus() )
+ {
+ Selection aSelection = GetField()->GetSelection();
+ GetField()->SetText( aStr );
+ GetField()->SetSelection( aSelection );
+ }
+ else
+ GetField()->SetText( aStr );
+ MarkToBeReformatted( false );
+}
+
+BigInt LongCurrencyFormatter::GetValue() const
+{
+ if ( !GetField() )
+ return 0;
+
+ BigInt nTempValue;
+ if ( ImplLongCurrencyGetValue( GetField()->GetText(), nTempValue, GetDecimalDigits(), GetLocaleDataWrapper() ) )
+ {
+ if ( nTempValue > mnMax )
+ nTempValue = mnMax;
+ else if ( nTempValue < mnMin )
+ nTempValue = mnMin;
+ return nTempValue;
+ }
+ else
+ return mnLastValue;
+}
+
+void LongCurrencyFormatter::Reformat()
+{
+ if ( !GetField() )
+ return;
+
+ if ( GetField()->GetText().isEmpty() && ImplGetEmptyFieldValue() )
+ return;
+
+ OUString aStr;
+ bool bOK = ImplLongCurrencyReformat( GetField()->GetText(), mnMin, mnMax,
+ GetDecimalDigits(), GetLocaleDataWrapper(), aStr, *this );
+ if ( !bOK )
+ return;
+
+ if ( !aStr.isEmpty() )
+ {
+ GetField()->SetText( aStr );
+ MarkToBeReformatted( false );
+ ImplLongCurrencyGetValue( aStr, mnLastValue, GetDecimalDigits(), GetLocaleDataWrapper() );
+ }
+ else
+ SetValue( mnLastValue );
+}
+
+void LongCurrencyFormatter::ReformatAll()
+{
+ Reformat();
+}
+
+void LongCurrencyFormatter::SetDecimalDigits( sal_uInt16 nDigits )
+{
+ if ( nDigits > 9 )
+ nDigits = 9;
+
+ mnDecimalDigits = nDigits;
+ ReformatAll();
+}
+
+
+
+LongCurrencyBox::LongCurrencyBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox(pParent, nWinStyle)
+ , LongCurrencyFormatter(this)
+{
+ Reformat();
+}
+
+bool LongCurrencyBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ {
+ MarkToBeReformatted( false );
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() )
+ {
+ Reformat();
+ ComboBox::Modify();
+ }
+ }
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void LongCurrencyBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void LongCurrencyBox::ReformatAll()
+{
+ OUString aStr;
+ SetUpdateMode( false );
+ const sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; ++i )
+ {
+ ImplLongCurrencyReformat( GetEntry( i ), mnMin, mnMax,
+ GetDecimalDigits(), GetLocaleDataWrapper(),
+ aStr, *this );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ LongCurrencyFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/managedmenubutton.cxx b/vcl/source/control/managedmenubutton.cxx
new file mode 100644
index 000000000..bf3065e71
--- /dev/null
+++ b/vcl/source/control/managedmenubutton.cxx
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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/.
+ */
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+
+#include <managedmenubutton.hxx>
+#include <vcl/menu.hxx>
+
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <com/sun/star/frame/theDesktop.hpp>
+#include <com/sun/star/frame/thePopupMenuControllerFactory.hpp>
+
+ManagedMenuButton::ManagedMenuButton(vcl::Window* pParent, WinBits nStyle)
+ : MenuButton(pParent, nStyle)
+{
+ SetImageAlign(ImageAlign::Left);
+}
+
+ManagedMenuButton::~ManagedMenuButton()
+{
+ disposeOnce();
+}
+
+void ManagedMenuButton::dispose()
+{
+ css::uno::Reference<css::lang::XComponent> xComponent(m_xPopupController, css::uno::UNO_QUERY);
+ if (xComponent.is())
+ xComponent->dispose();
+
+ m_xPopupMenu.clear();
+ m_xPopupController.clear();
+ MenuButton::dispose();
+}
+
+void ManagedMenuButton::Activate()
+{
+ if (!GetPopupMenu())
+ SetPopupMenu(VclPtr<PopupMenu>::Create());
+
+ MenuButton::Activate();
+
+ if (m_xPopupController.is())
+ {
+ m_xPopupController->updatePopupMenu();
+ return;
+ }
+
+ if (!m_xPopupMenu.is())
+ m_xPopupMenu = GetPopupMenu()->CreateMenuInterface();
+
+ // FIXME: get the frame from the parent VclBuilder.
+ css::uno::Reference<css::uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
+ css::uno::Reference<css::frame::XDesktop2> xDesktop(css::frame::theDesktop::get(xContext));
+ css::uno::Reference<css::frame::XFrame> xFrame(xDesktop->getActiveFrame());
+ if (!xFrame.is())
+ return;
+
+ OUString aModuleName;
+ try
+ {
+ css::uno::Reference<css::frame::XModuleManager> xModuleManager(css::frame::ModuleManager::create(xContext));
+ aModuleName = xModuleManager->identify(xFrame);
+ }
+ catch( const css::uno::Exception& )
+ {}
+
+ css::uno::Sequence<css::uno::Any> aArgs {
+ css::uno::Any(comphelper::makePropertyValue("ModuleIdentifier", aModuleName)),
+ css::uno::Any(comphelper::makePropertyValue("Frame", css::uno::Any(xFrame))),
+ css::uno::Any(comphelper::makePropertyValue("InToolbar", css::uno::Any(true)))
+ };
+
+ const OUString aCommand(GetCommand());
+ if (!aCommand.isEmpty() && GetPopupMenu()->GetItemCount() == 0)
+ {
+ css::uno::Reference<css::frame::XUIControllerFactory> xPopupMenuControllerFactory =
+ css::frame::thePopupMenuControllerFactory::get(xContext);
+
+ if (xPopupMenuControllerFactory->hasController(aCommand, aModuleName))
+ m_xPopupController.set(xPopupMenuControllerFactory->createInstanceWithArgumentsAndContext(
+ aCommand, aArgs, xContext), css::uno::UNO_QUERY);
+ }
+
+ // No registered controller found, use one the can handle arbitrary menus (e.g. defined in .ui file).
+ if (!m_xPopupController.is())
+ m_xPopupController.set(xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ "com.sun.star.comp.framework.ResourceMenuController", aArgs, xContext), css::uno::UNO_QUERY);
+
+ if (m_xPopupController.is())
+ m_xPopupController->setPopupMenu(m_xPopupMenu);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/control/menubtn.cxx b/vcl/source/control/menubtn.cxx
new file mode 100644
index 000000000..cd9b20959
--- /dev/null
+++ b/vcl/source/control/menubtn.cxx
@@ -0,0 +1,290 @@
+/* -*- 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 <vcl/dockwin.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/toolkit/menubtn.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <vcl/uitest/eventdescription.hxx>
+#include <menutogglebutton.hxx>
+#include <tools/json_writer.hxx>
+
+namespace
+{
+void collectUIInformation( const OUString& aID, const OUString& aevent , const OUString& akey , const OUString& avalue)
+{
+ EventDescription aDescription;
+ aDescription.aID = aID;
+ aDescription.aParameters = {{ akey , avalue}};
+ aDescription.aAction = aevent;
+ aDescription.aParent = "MainWindow";
+ aDescription.aKeyWord = "MenuButton";
+ UITestLogger::getInstance().logEvent(aDescription);
+}
+}
+
+void MenuButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+
+ PushButton::ImplInit( pParent, nStyle );
+ EnableRTL( AllSettings::GetLayoutRTL() );
+}
+
+void MenuButton::ExecuteMenu()
+{
+ mbStartingMenu = true;
+
+ Activate();
+
+ if (!mpMenu && !mpFloatingWindow)
+ {
+ mbStartingMenu = false;
+ return;
+ }
+
+ Size aSize = GetSizePixel();
+ SetPressed( true );
+ EndSelection();
+ if (mpMenu)
+ {
+ Point aPos(0, 1);
+ tools::Rectangle aRect(aPos, aSize );
+ mpMenu->Execute(this, aRect, PopupMenuFlags::ExecuteDown);
+
+ if (isDisposed())
+ return;
+
+ mnCurItemId = mpMenu->GetCurItemId();
+ msCurItemIdent = mpMenu->GetCurItemIdent();
+ }
+ else
+ {
+ Point aPos(GetParent()->OutputToScreenPixel(GetPosPixel()));
+ tools::Rectangle aRect(aPos, aSize );
+ FloatWinPopupFlags nFlags = FloatWinPopupFlags::Down | FloatWinPopupFlags::GrabFocus;
+ if (mpFloatingWindow->GetType() == WindowType::FLOATINGWINDOW)
+ static_cast<FloatingWindow*>(mpFloatingWindow.get())->StartPopupMode(aRect, nFlags);
+ else
+ {
+ mpFloatingWindow->EnableDocking();
+ vcl::Window::GetDockingManager()->StartPopupMode(mpFloatingWindow, aRect, nFlags);
+ }
+ }
+
+ mbStartingMenu = false;
+
+ SetPressed(false);
+ OUString aID = get_id(); // tdf#136678 take a copy if we are destroyed by Select callback
+ if (mnCurItemId)
+ {
+ Select();
+ mnCurItemId = 0;
+ msCurItemIdent.clear();
+ }
+ collectUIInformation(aID,"OPENLIST","","");
+}
+
+void MenuButton::CancelMenu()
+{
+ if (!mpMenu && !mpFloatingWindow)
+ return;
+
+ if (mpMenu)
+ {
+ mpMenu->EndExecute();
+ }
+ else
+ {
+ if (mpFloatingWindow->GetType() == WindowType::FLOATINGWINDOW)
+ static_cast<FloatingWindow*>(mpFloatingWindow.get())->EndPopupMode();
+ else
+ vcl::Window::GetDockingManager()->EndPopupMode(mpFloatingWindow);
+ }
+ collectUIInformation(get_id(),"CLOSELIST","","");
+}
+
+bool MenuButton::InPopupMode() const
+{
+ if (mbStartingMenu)
+ return true;
+
+ if (!mpMenu && !mpFloatingWindow)
+ return false;
+
+ if (mpMenu)
+ return PopupMenu::GetActivePopupMenu() == mpMenu;
+ else
+ {
+ if (mpFloatingWindow->GetType() == WindowType::FLOATINGWINDOW)
+ return static_cast<const FloatingWindow*>(mpFloatingWindow.get())->IsInPopupMode();
+ else
+ return vcl::Window::GetDockingManager()->IsInPopupMode(mpFloatingWindow);
+ }
+}
+
+MenuButton::MenuButton( vcl::Window* pParent, WinBits nWinBits )
+ : PushButton(WindowType::MENUBUTTON)
+ , mnCurItemId(0)
+ , mbDelayMenu(false)
+ , mbStartingMenu(false)
+{
+ mnDDStyle = PushButtonDropdownStyle::MenuButton;
+ ImplInit(pParent, nWinBits);
+}
+
+MenuButton::~MenuButton()
+{
+ disposeOnce();
+}
+
+void MenuButton::dispose()
+{
+ mpMenuTimer.reset();
+ mpFloatingWindow.clear();
+ mpMenu.clear();
+ PushButton::dispose();
+}
+
+IMPL_LINK_NOARG(MenuButton, ImplMenuTimeoutHdl, Timer *, void)
+{
+ // See if Button Tracking is still active, as it could've been cancelled earlier
+ if ( IsTracking() )
+ {
+ if ( !(GetStyle() & WB_NOPOINTERFOCUS) )
+ GrabFocus();
+ ExecuteMenu();
+ }
+}
+
+void MenuButton::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ bool bExecute = true;
+ if (mbDelayMenu)
+ {
+ // If the separated dropdown symbol is not hit, delay the popup execution
+ if( rMEvt.GetPosPixel().X() <= ImplGetSeparatorX() )
+ {
+ if ( !mpMenuTimer )
+ {
+ mpMenuTimer.reset(new Timer("MenuTimer"));
+ mpMenuTimer->SetInvokeHandler( LINK( this, MenuButton, ImplMenuTimeoutHdl ) );
+ }
+
+ mpMenuTimer->SetTimeout( MouseSettings::GetActionDelay() );
+ mpMenuTimer->Start();
+
+ PushButton::MouseButtonDown( rMEvt );
+ bExecute = false;
+ }
+ }
+ if( bExecute )
+ {
+ if ( PushButton::ImplHitTestPushButton( this, rMEvt.GetPosPixel() ) )
+ {
+ if ( !(GetStyle() & WB_NOPOINTERFOCUS) )
+ GrabFocus();
+ ExecuteMenu();
+ }
+ }
+}
+
+void MenuButton::KeyInput( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+ sal_uInt16 nCode = aKeyCode.GetCode();
+ if ( (nCode == KEY_DOWN) && aKeyCode.IsMod2() )
+ ExecuteMenu();
+ else if ( !mbDelayMenu &&
+ !aKeyCode.GetModifier() &&
+ ((nCode == KEY_RETURN) || (nCode == KEY_SPACE)) )
+ ExecuteMenu();
+ else
+ PushButton::KeyInput( rKEvt );
+}
+
+void MenuButton::Activate()
+{
+ maActivateHdl.Call( this );
+}
+
+void MenuButton::Select()
+{
+ if (mnCurItemId)
+ collectUIInformation(get_id(),"OPENFROMLIST","POS",OUString::number(mnCurItemId));
+
+ maSelectHdl.Call( this );
+}
+
+void MenuButton::SetPopupMenu(PopupMenu* pNewMenu)
+{
+ if (pNewMenu == mpMenu)
+ return;
+
+ mpMenu = pNewMenu;
+}
+
+void MenuButton::SetPopover(Window* pWindow)
+{
+ if (pWindow == mpFloatingWindow)
+ return;
+
+ mpFloatingWindow = pWindow;
+}
+
+
+FactoryFunction MenuButton::GetUITestFactory() const
+{
+ return MenuButtonUIObject::create;
+}
+
+void MenuButton::SetCurItemId(){
+ mnCurItemId = mpMenu->GetCurItemId();
+ msCurItemIdent = mpMenu->GetCurItemIdent();
+}
+
+//class MenuToggleButton ----------------------------------------------------
+
+MenuToggleButton::MenuToggleButton( vcl::Window* pParent, WinBits nWinBits )
+ : MenuButton( pParent, nWinBits )
+{
+}
+
+MenuToggleButton::~MenuToggleButton()
+{
+ disposeOnce();
+}
+
+void MenuToggleButton::SetActive( bool bSel )
+{
+ mbIsActive = bSel;
+}
+
+bool MenuToggleButton::GetActive() const
+{
+ return mbIsActive;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/notebookbar.cxx b/vcl/source/control/notebookbar.cxx
new file mode 100644
index 000000000..24e619ff9
--- /dev/null
+++ b/vcl/source/control/notebookbar.cxx
@@ -0,0 +1,347 @@
+/* -*- 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/.
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <vcl/layout.hxx>
+#include <vcl/notebookbar/notebookbar.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/taskpanelist.hxx>
+#include <vcl/NotebookbarContextControl.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/processfactory.hxx>
+#include <rtl/bootstrap.hxx>
+#include <osl/file.hxx>
+#include <config_folders.h>
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/ui/ContextChangeEventMultiplexer.hpp>
+#include <comphelper/lok.hxx>
+
+static OUString getCustomizedUIRootDir()
+{
+ OUString sShareLayer("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE(
+ "bootstrap") ":UserInstallation}/user/config/soffice.cfg/");
+ rtl::Bootstrap::expandMacros(sShareLayer);
+ return sShareLayer;
+}
+
+static bool doesFileExist(std::u16string_view sUIDir, std::u16string_view sUIFile)
+{
+ OUString sUri = OUString::Concat(sUIDir) + sUIFile;
+ osl::File file(sUri);
+ return( file.open(0) == osl::FileBase::E_None );
+}
+
+/**
+ * split from the main class since it needs different ref-counting mana
+ */
+class NotebookBarContextChangeEventListener : public ::cppu::WeakImplHelper<css::ui::XContextChangeEventListener>
+{
+ VclPtr<NotebookBar> mpParent;
+public:
+ explicit NotebookBarContextChangeEventListener(NotebookBar *p) : mpParent(p) {}
+
+ // XContextChangeEventListener
+ virtual void SAL_CALL notifyContextChangeEvent(const css::ui::ContextChangeEventObject& rEvent) override;
+
+ virtual void SAL_CALL disposing(const ::css::lang::EventObject&) override;
+};
+
+NotebookBar::NotebookBar(Window* pParent, const OString& rID, const OUString& rUIXMLDescription,
+ const css::uno::Reference<css::frame::XFrame>& rFrame,
+ const NotebookBarAddonsItem& aNotebookBarAddonsItem)
+ : Control(pParent)
+ , m_pEventListener(new NotebookBarContextChangeEventListener(this))
+ , m_pViewShell(nullptr)
+ , m_bIsWelded(false)
+ , m_sUIXMLDescription(rUIXMLDescription)
+{
+ mxFrame = rFrame;
+
+ SetStyle(GetStyle() | WB_DIALOGCONTROL);
+ OUString sUIDir = AllSettings::GetUIRootDir();
+ bool doesCustomizedUIExist = doesFileExist(getCustomizedUIRootDir(), rUIXMLDescription);
+ if ( doesCustomizedUIExist )
+ sUIDir = getCustomizedUIRootDir();
+
+ bool bIsWelded = comphelper::LibreOfficeKit::isActive();
+ if (bIsWelded)
+ {
+ m_bIsWelded = true;
+ m_xVclContentArea = VclPtr<VclVBox>::Create(this);
+ m_xVclContentArea->Show();
+ // now access it using GetMainContainer and set dispose callback with SetDisposeCallback
+ }
+ else
+ {
+ m_pUIBuilder.reset(
+ new VclBuilder(this, sUIDir, rUIXMLDescription, rID, rFrame, true, &aNotebookBarAddonsItem));
+
+ // In the Notebookbar's .ui file must exist control handling context
+ // - implementing NotebookbarContextControl interface with id "ContextContainer"
+ // or "ContextContainerX" where X is a number >= 1
+ NotebookbarContextControl* pContextContainer = nullptr;
+ int i = 0;
+ do
+ {
+ OUString aName = "ContextContainer";
+ if (i)
+ aName += OUString::number(i);
+
+ pContextContainer = dynamic_cast<NotebookbarContextControl*>(m_pUIBuilder->get<Window>(OUStringToOString(aName, RTL_TEXTENCODING_UTF8)));
+ if (pContextContainer)
+ m_pContextContainers.push_back(pContextContainer);
+ i++;
+ }
+ while( pContextContainer != nullptr );
+ }
+
+ UpdateBackground();
+}
+
+void NotebookBar::SetDisposeCallback(const Link<const SfxViewShell*, void> rDisposeCallback, const SfxViewShell* pViewShell)
+{
+ m_rDisposeLink = rDisposeCallback;
+ m_pViewShell = pViewShell;
+}
+
+NotebookBar::~NotebookBar()
+{
+ disposeOnce();
+}
+
+void NotebookBar::dispose()
+{
+ m_pContextContainers.clear();
+ if (m_pSystemWindow && m_pSystemWindow->ImplIsInTaskPaneList(this))
+ m_pSystemWindow->GetTaskPaneList()->RemoveWindow(this);
+ m_pSystemWindow.clear();
+
+ if (m_rDisposeLink.IsSet())
+ m_rDisposeLink.Call(m_pViewShell);
+
+ if (m_bIsWelded)
+ m_xVclContentArea.disposeAndClear();
+ else
+ disposeBuilder();
+
+ assert(m_alisteningControllers.empty());
+ m_pEventListener.clear();
+
+ Control::dispose();
+}
+
+bool NotebookBar::PreNotify(NotifyEvent& rNEvt)
+{
+ // capture KeyEvents for taskpane cycling
+ if (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT)
+ {
+ if (m_pSystemWindow)
+ return m_pSystemWindow->PreNotify(rNEvt);
+ }
+ return Window::PreNotify( rNEvt );
+}
+
+Size NotebookBar::GetOptimalSize() const
+{
+ if (isLayoutEnabled(this))
+ return VclContainer::getLayoutRequisition(*GetWindow(GetWindowType::FirstChild));
+
+ return Control::GetOptimalSize();
+}
+
+void NotebookBar::setPosSizePixel(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, PosSizeFlags nFlags)
+{
+ bool bCanHandleSmallerWidth = false;
+ bool bCanHandleSmallerHeight = false;
+
+ bool bIsLayoutEnabled = isLayoutEnabled(this);
+ Window *pChild = GetWindow(GetWindowType::FirstChild);
+
+ if (bIsLayoutEnabled && pChild->GetType() == WindowType::SCROLLWINDOW)
+ {
+ WinBits nStyle = pChild->GetStyle();
+ if (nStyle & (WB_AUTOHSCROLL | WB_HSCROLL))
+ bCanHandleSmallerWidth = true;
+ if (nStyle & (WB_AUTOVSCROLL | WB_VSCROLL))
+ bCanHandleSmallerHeight = true;
+ }
+
+ Size aSize(GetOptimalSize());
+ if (!bCanHandleSmallerWidth)
+ nWidth = std::max(nWidth, aSize.Width());
+ if (!bCanHandleSmallerHeight)
+ nHeight = std::max(nHeight, aSize.Height());
+
+ Control::setPosSizePixel(nX, nY, nWidth, nHeight, nFlags);
+
+ if (bIsLayoutEnabled && (nFlags & PosSizeFlags::Size))
+ VclContainer::setLayoutAllocation(*pChild, Point(0, 0), Size(nWidth, nHeight));
+}
+
+void NotebookBar::Resize()
+{
+ if(m_pUIBuilder && m_pUIBuilder->get_widget_root())
+ {
+ vcl::Window* pWindow = m_pUIBuilder->get_widget_root()->GetChild(0);
+ if (pWindow)
+ {
+ Size aSize = pWindow->GetSizePixel();
+ aSize.setWidth( GetSizePixel().Width() );
+ pWindow->SetSizePixel(aSize);
+ }
+ }
+ if(m_bIsWelded)
+ {
+ vcl::Window* pChild = GetWindow(GetWindowType::FirstChild);
+ assert(pChild);
+ VclContainer::setLayoutAllocation(*pChild, Point(0, 0), GetSizePixel());
+ Control::Resize();
+ }
+ Control::Resize();
+}
+
+void NotebookBar::SetSystemWindow(SystemWindow* pSystemWindow)
+{
+ m_pSystemWindow = pSystemWindow;
+ if (!m_pSystemWindow->ImplIsInTaskPaneList(this))
+ m_pSystemWindow->GetTaskPaneList()->AddWindow(this);
+}
+
+void SAL_CALL NotebookBarContextChangeEventListener::notifyContextChangeEvent(const css::ui::ContextChangeEventObject& rEvent)
+{
+ if (mpParent)
+ {
+ for (NotebookbarContextControl* pControl : mpParent->m_pContextContainers)
+ pControl->SetContext(vcl::EnumContext::GetContextEnum(rEvent.ContextName));
+ }
+}
+
+void NotebookBar::ControlListenerForCurrentController(bool bListen)
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return;
+
+ auto xController = mxFrame->getController();
+ if(bListen)
+ {
+ // add listeners
+ if (m_alisteningControllers.count(xController) == 0)
+ {
+ auto xMultiplexer(css::ui::ContextChangeEventMultiplexer::get(
+ ::comphelper::getProcessComponentContext()));
+ xMultiplexer->addContextChangeEventListener(m_pEventListener, xController);
+ m_alisteningControllers.insert(xController);
+ }
+ }
+ else
+ {
+ // remove listeners
+ if (m_alisteningControllers.count(xController))
+ {
+ auto xMultiplexer(css::ui::ContextChangeEventMultiplexer::get(
+ ::comphelper::getProcessComponentContext()));
+ xMultiplexer->removeContextChangeEventListener(m_pEventListener, xController);
+ m_alisteningControllers.erase(xController);
+ }
+ }
+}
+
+void NotebookBar::StopListeningAllControllers()
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return;
+
+ auto xMultiplexer(
+ css::ui::ContextChangeEventMultiplexer::get(comphelper::getProcessComponentContext()));
+ xMultiplexer->removeAllContextChangeEventListeners(m_pEventListener);
+ m_alisteningControllers.clear();
+}
+
+void SAL_CALL NotebookBarContextChangeEventListener::disposing(const ::css::lang::EventObject&)
+{
+ mpParent.clear();
+}
+
+void NotebookBar::DataChanged(const DataChangedEvent& rDCEvt)
+{
+ UpdateBackground();
+ Control::DataChanged(rDCEvt);
+}
+
+void NotebookBar::StateChanged(const StateChangedType nStateChange )
+{
+ UpdateBackground();
+ Control::StateChanged(nStateChange);
+ Invalidate();
+}
+
+void NotebookBar::UpdateBackground()
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ const BitmapEx& aPersona = rStyleSettings.GetPersonaHeader();
+ Wallpaper aWallpaper(aPersona);
+ aWallpaper.SetStyle(WallpaperStyle::TopRight);
+ if (!aPersona.IsEmpty())
+ {
+ SetBackground(aWallpaper);
+ UpdatePersonaSettings();
+ GetOutDev()->SetSettings( PersonaSettings );
+ }
+ else
+ {
+ SetBackground(rStyleSettings.GetDialogColor());
+ UpdateDefaultSettings();
+ GetOutDev()->SetSettings( DefaultSettings );
+ }
+
+ Invalidate(tools::Rectangle(Point(0,0), GetSizePixel()));
+}
+
+void NotebookBar::UpdateDefaultSettings()
+{
+ AllSettings aAllSettings( GetSettings() );
+ StyleSettings aStyleSet( aAllSettings.GetStyleSettings() );
+
+ ::Color aTextColor = aStyleSet.GetFieldTextColor();
+ aStyleSet.SetDialogTextColor( aTextColor );
+ aStyleSet.SetButtonTextColor( aTextColor );
+ aStyleSet.SetRadioCheckTextColor( aTextColor );
+ aStyleSet.SetGroupTextColor( aTextColor );
+ aStyleSet.SetLabelTextColor( aTextColor );
+ aStyleSet.SetWindowTextColor( aTextColor );
+ aStyleSet.SetTabTextColor(aTextColor);
+ aStyleSet.SetToolTextColor(aTextColor);
+
+ aAllSettings.SetStyleSettings(aStyleSet);
+ DefaultSettings = aAllSettings;
+}
+
+void NotebookBar::UpdatePersonaSettings()
+{
+ AllSettings aAllSettings( GetSettings() );
+ StyleSettings aStyleSet( aAllSettings.GetStyleSettings() );
+
+ ::Color aTextColor = aStyleSet.GetPersonaMenuBarTextColor().value_or(COL_BLACK );
+ aStyleSet.SetDialogTextColor( aTextColor );
+ aStyleSet.SetButtonTextColor( aTextColor );
+ aStyleSet.SetRadioCheckTextColor( aTextColor );
+ aStyleSet.SetGroupTextColor( aTextColor );
+ aStyleSet.SetLabelTextColor( aTextColor );
+ aStyleSet.SetWindowTextColor( aTextColor );
+ aStyleSet.SetTabTextColor(aTextColor);
+ aStyleSet.SetToolTextColor(aTextColor);
+
+ aAllSettings.SetStyleSettings(aStyleSet);
+ PersonaSettings = aAllSettings;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/prgsbar.cxx b/vcl/source/control/prgsbar.cxx
new file mode 100644
index 000000000..6b7a7007c
--- /dev/null
+++ b/vcl/source/control/prgsbar.cxx
@@ -0,0 +1,232 @@
+/* -*- 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 <vcl/event.hxx>
+#include <vcl/status.hxx>
+#include <vcl/toolkit/prgsbar.hxx>
+#include <vcl/settings.hxx>
+#include <sal/log.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/idle.hxx>
+
+#define PROGRESSBAR_OFFSET 3
+#define PROGRESSBAR_WIN_OFFSET 2
+
+void ProgressBar::ImplInit()
+{
+ mnPrgsWidth = 0;
+ mnPrgsHeight = 0;
+ mnPercent = 0;
+ mnPercentCount = 0;
+ mbCalcNew = true;
+
+ ImplInitSettings( true, true, true );
+}
+
+static WinBits clearProgressBarBorder( vcl::Window const * pParent, WinBits nOrgStyle )
+{
+ WinBits nOutStyle = nOrgStyle;
+ if( pParent && (nOrgStyle & WB_BORDER) != 0 )
+ {
+ if( pParent->IsNativeControlSupported( ControlType::Progress, ControlPart::Entire ) )
+ nOutStyle &= WB_BORDER;
+ }
+ return nOutStyle;
+}
+
+Size ProgressBar::GetOptimalSize() const
+{
+ return Size(150, 20);
+}
+
+ProgressBar::ProgressBar( vcl::Window* pParent, WinBits nWinStyle ) :
+ Window( pParent, clearProgressBarBorder( pParent, nWinStyle ) )
+{
+ SetOutputSizePixel( GetOptimalSize() );
+ ImplInit();
+}
+
+void ProgressBar::ImplInitSettings( bool bFont,
+ bool bForeground, bool bBackground )
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+
+/* FIXME: !!! We do not support text output at the moment
+ if ( bFont )
+ ApplyControlFont(*this, rStyleSettings.GetAppFont());
+*/
+
+ if ( bBackground )
+ {
+ if( !IsControlBackground() &&
+ IsNativeControlSupported( ControlType::Progress, ControlPart::Entire ) )
+ {
+ if( GetStyle() & WB_BORDER )
+ SetBorderStyle( WindowBorderStyle::REMOVEBORDER );
+ EnableChildTransparentMode();
+ SetPaintTransparent( true );
+ SetBackground();
+ SetParentClipMode( ParentClipMode::NoClip );
+ }
+ else
+ {
+ Color aColor;
+ if ( IsControlBackground() )
+ aColor = GetControlBackground();
+ else
+ aColor = rStyleSettings.GetFaceColor();
+ SetBackground( aColor );
+ }
+ }
+
+ if ( !(bForeground || bFont) )
+ return;
+
+ Color aColor = rStyleSettings.GetHighlightColor();
+ if ( IsControlForeground() )
+ aColor = GetControlForeground();
+ if ( aColor.IsRGBEqual( GetBackground().GetColor() ) )
+ {
+ if ( aColor.GetLuminance() > 100 )
+ aColor.DecreaseLuminance( 64 );
+ else
+ aColor.IncreaseLuminance( 64 );
+ }
+ GetOutDev()->SetLineColor();
+ GetOutDev()->SetFillColor( aColor );
+/* FIXME: !!! We do not support text output at the moment
+ SetTextColor( aColor );
+ SetTextFillColor();
+*/
+}
+
+void ProgressBar::ImplDrawProgress(vcl::RenderContext& rRenderContext, sal_uInt16 nNewPerc)
+{
+ if (mbCalcNew)
+ {
+ mbCalcNew = false;
+
+ Size aSize(GetOutputSizePixel());
+ mnPrgsHeight = aSize.Height() - (PROGRESSBAR_WIN_OFFSET * 2);
+ mnPrgsWidth = (mnPrgsHeight * 2) / 3;
+ maPos.setY( PROGRESSBAR_WIN_OFFSET );
+ tools::Long nMaxWidth = aSize.Width() - (PROGRESSBAR_WIN_OFFSET * 2) + PROGRESSBAR_OFFSET;
+ sal_uInt16 nMaxCount = static_cast<sal_uInt16>(nMaxWidth / (mnPrgsWidth+PROGRESSBAR_OFFSET));
+ if (nMaxCount <= 1)
+ {
+ nMaxCount = 1;
+ }
+ else
+ {
+ while (((10000 / (10000 / nMaxCount)) * (mnPrgsWidth + PROGRESSBAR_OFFSET)) > nMaxWidth)
+ {
+ nMaxCount--;
+ }
+ }
+ mnPercentCount = 10000 / nMaxCount;
+ nMaxWidth = ((10000 / (10000 / nMaxCount)) * (mnPrgsWidth + PROGRESSBAR_OFFSET)) - PROGRESSBAR_OFFSET;
+ maPos.setX( (aSize.Width() - nMaxWidth) / 2 );
+ }
+
+ ::DrawProgress(this, rRenderContext, maPos, PROGRESSBAR_OFFSET, mnPrgsWidth, mnPrgsHeight,
+ /*nPercent1=*/0, nNewPerc * 100, mnPercentCount,
+ tools::Rectangle(Point(), GetSizePixel()));
+}
+
+void ProgressBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
+{
+ ImplDrawProgress(rRenderContext, mnPercent);
+}
+
+void ProgressBar::Resize()
+{
+ mbCalcNew = true;
+ if ( IsReallyVisible() )
+ Invalidate();
+}
+
+void ProgressBar::SetValue( sal_uInt16 nNewPercent )
+{
+ SAL_WARN_IF( nNewPercent > 100, "vcl", "StatusBar::SetProgressValue(): nPercent > 100" );
+
+ if ( nNewPercent < mnPercent )
+ {
+ mbCalcNew = true;
+ mnPercent = nNewPercent;
+ if ( IsReallyVisible() )
+ {
+ Invalidate();
+ PaintImmediately();
+ }
+ }
+ else if ( mnPercent != nNewPercent )
+ {
+ mnPercent = nNewPercent;
+ Invalidate();
+
+ // Make sure the progressbar is actually painted even if the caller is busy with its task,
+ // so the main loop would not be invoked.
+ Idle aIdle("ProgressBar::SetValue aIdle");
+ aIdle.SetPriority(TaskPriority::POST_PAINT);
+ aIdle.Start();
+ while (aIdle.IsActive() && !Application::IsQuit())
+ {
+ Application::Yield();
+ }
+ }
+}
+
+void ProgressBar::StateChanged( StateChangedType nType )
+{
+/* FIXME: !!! We do not support text output at the moment
+ if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( true, false, false );
+ Invalidate();
+ }
+ else
+*/
+ if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false, true, false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( false, false, true );
+ Invalidate();
+ }
+
+ Window::StateChanged( nType );
+}
+
+void ProgressBar::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ImplInitSettings( true, true, true );
+ Invalidate();
+ }
+
+ Window::DataChanged( rDCEvt );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/quickselectionengine.cxx b/vcl/source/control/quickselectionengine.cxx
new file mode 100644
index 000000000..777e00e0b
--- /dev/null
+++ b/vcl/source/control/quickselectionengine.cxx
@@ -0,0 +1,158 @@
+/* -*- 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 <vcl/quickselectionengine.hxx>
+#include <vcl/event.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/i18nhelp.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <sal/log.hxx>
+
+#include <optional>
+
+namespace vcl
+{
+
+ struct QuickSelectionEngine_Data
+ {
+ ISearchableStringList& rEntryList;
+ OUString sCurrentSearchString;
+ ::std::optional< sal_Unicode > aSingleSearchChar;
+ Timer aSearchTimeout;
+
+ explicit QuickSelectionEngine_Data( ISearchableStringList& _entryList )
+ :rEntryList( _entryList )
+ ,aSearchTimeout( "vcl::QuickSelectionEngine_Data aSearchTimeout" )
+ {
+ aSearchTimeout.SetTimeout( 2500 );
+ aSearchTimeout.SetInvokeHandler( LINK( this, QuickSelectionEngine_Data, SearchStringTimeout ) );
+ }
+
+ ~QuickSelectionEngine_Data()
+ {
+ aSearchTimeout.Stop();
+ }
+
+ DECL_LINK( SearchStringTimeout, Timer*, void );
+ };
+
+ namespace
+ {
+ void lcl_reset( QuickSelectionEngine_Data& _data )
+ {
+ _data.sCurrentSearchString.clear();
+ _data.aSingleSearchChar.reset();
+ _data.aSearchTimeout.Stop();
+ }
+ }
+
+ IMPL_LINK_NOARG( QuickSelectionEngine_Data, SearchStringTimeout, Timer*, void )
+ {
+ lcl_reset( *this );
+ }
+
+ static StringEntryIdentifier findMatchingEntry( const OUString& _searchString, QuickSelectionEngine_Data const & _engineData )
+ {
+ const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetLocaleI18nHelper();
+ // TODO: do we really need the Window's settings here? The original code used it ...
+
+ OUString sEntryText;
+ // get the "current + 1" entry
+ StringEntryIdentifier pSearchEntry = _engineData.rEntryList.CurrentEntry( sEntryText );
+ if ( pSearchEntry )
+ pSearchEntry = _engineData.rEntryList.NextEntry( pSearchEntry, sEntryText );
+ // loop 'til we find another matching entry
+ StringEntryIdentifier pStartedWith = pSearchEntry;
+ while ( pSearchEntry )
+ {
+ if ( rI18nHelper.MatchString( _searchString, sEntryText ) )
+ break;
+
+ pSearchEntry = _engineData.rEntryList.NextEntry( pSearchEntry, sEntryText );
+ if ( pSearchEntry == pStartedWith )
+ pSearchEntry = nullptr;
+ }
+
+ return pSearchEntry;
+ }
+
+ QuickSelectionEngine::QuickSelectionEngine( ISearchableStringList& _entryList )
+ :m_pData( new QuickSelectionEngine_Data( _entryList ) )
+ {
+ }
+
+ QuickSelectionEngine::~QuickSelectionEngine()
+ {
+ }
+
+ bool QuickSelectionEngine::HandleKeyEvent( const KeyEvent& _keyEvent )
+ {
+ sal_Unicode c = _keyEvent.GetCharCode();
+
+ if ( ( c >= 32 ) && ( c != 127 ) && !_keyEvent.GetKeyCode().IsMod2() )
+ {
+ m_pData->sCurrentSearchString += OUStringChar(c);
+ SAL_INFO( "vcl", "QuickSelectionEngine::HandleKeyEvent: searching for " << m_pData->sCurrentSearchString );
+
+ if ( m_pData->sCurrentSearchString.getLength() == 1 )
+ { // first character in the search -> remember
+ m_pData->aSingleSearchChar = c;
+ }
+ else if ( m_pData->sCurrentSearchString.getLength() > 1 )
+ {
+ if ( !!m_pData->aSingleSearchChar && ( *m_pData->aSingleSearchChar != c ) )
+ // we already have a "single char", but the current one is different -> reset
+ m_pData->aSingleSearchChar.reset();
+ }
+
+ OUString aSearchTemp( m_pData->sCurrentSearchString );
+
+ StringEntryIdentifier pMatchingEntry = findMatchingEntry( aSearchTemp, *m_pData );
+ SAL_INFO( "vcl", "QuickSelectionEngine::HandleKeyEvent: found " << pMatchingEntry );
+ if ( !pMatchingEntry && (aSearchTemp.getLength() > 1) && !!m_pData->aSingleSearchChar )
+ {
+ // if there's only one letter in the search string, use a different search mode
+ aSearchTemp = OUString(*m_pData->aSingleSearchChar);
+ pMatchingEntry = findMatchingEntry( aSearchTemp, *m_pData );
+ }
+
+ if ( pMatchingEntry )
+ {
+ m_pData->rEntryList.SelectEntry( pMatchingEntry );
+ m_pData->aSearchTimeout.Start();
+ }
+ else
+ {
+ lcl_reset( *m_pData );
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ void QuickSelectionEngine::Reset()
+ {
+ lcl_reset( *m_pData );
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/roadmap.cxx b/vcl/source/control/roadmap.cxx
new file mode 100644
index 000000000..7142c4dac
--- /dev/null
+++ b/vcl/source/control/roadmap.cxx
@@ -0,0 +1,845 @@
+/* -*- 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 <vector>
+#include <algorithm>
+#include <o3tl/safeint.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/roadmap.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/vclevent.hxx>
+#include <hyperlabel.hxx>
+#include <tools/color.hxx>
+#include <rtl/ustring.hxx>
+
+constexpr tools::Long LABELBASEMAPHEIGHT = 8;
+constexpr tools::Long ROADMAP_INDENT_X = 4;
+constexpr tools::Long ROADMAP_INDENT_Y = 27;
+constexpr tools::Long ROADMAP_ITEM_DISTANCE_Y = 6;
+
+namespace vcl
+{
+
+typedef std::vector< RoadmapItem* > HL_Vector;
+
+//= ColorChanger
+
+namespace {
+
+class IDLabel : public FixedText
+{
+public:
+ IDLabel( vcl::Window* _pParent, WinBits _nWinStyle );
+ virtual void DataChanged( const DataChangedEvent& rDCEvt ) override;
+ virtual void ApplySettings(vcl::RenderContext& rRenderContext) override;
+};
+
+}
+
+class RoadmapItem : public RoadmapTypes
+{
+private:
+ VclPtr<IDLabel> mpID;
+ VclPtr<HyperLabel> mpDescription;
+ const Size m_aItemPlayground;
+
+public:
+ RoadmapItem( ORoadmap& _rParent, const Size& _rItemPlayground );
+ ~RoadmapItem();
+
+ void SetID( sal_Int16 ID );
+ sal_Int16 GetID() const;
+
+ void SetIndex( ItemIndex Index );
+ ItemIndex GetIndex() const;
+
+ void Update( ItemIndex RMIndex, const OUString& _rText );
+
+ void SetPosition( RoadmapItem const * OldHyperLabel );
+
+ void ToggleBackgroundColor( const Color& _rGBColor );
+ void SetInteractive( bool _bInteractive );
+
+ void SetClickHdl( const Link<HyperLabel*,void>& rLink );
+ void Enable( bool bEnable );
+ bool IsEnabled() const;
+ void GrabFocus();
+
+ bool Contains( const vcl::Window* _pWindow ) const;
+
+private:
+ void ImplUpdateIndex( const ItemIndex _nIndex );
+ void ImplUpdatePosSize();
+};
+
+//= RoadmapImpl
+
+class RoadmapImpl : public RoadmapTypes
+{
+protected:
+ const ORoadmap& m_rAntiImpl;
+ Link<LinkParamNone*,void> m_aSelectHdl;
+ BitmapEx m_aPicture;
+ HL_Vector m_aRoadmapSteps;
+ ItemId m_iCurItemID;
+ bool m_bInteractive : 1;
+ bool m_bComplete : 1;
+ Size m_aItemSizePixel;
+public:
+ bool m_bPaintInitialized : 1;
+
+public:
+ explicit RoadmapImpl(const ORoadmap& rAntiImpl)
+ : m_rAntiImpl(rAntiImpl)
+ , m_iCurItemID(-1)
+ , m_bInteractive(true)
+ , m_bComplete(true)
+ , m_bPaintInitialized(false)
+ , InCompleteHyperLabel(nullptr)
+ {}
+
+ RoadmapItem* InCompleteHyperLabel;
+
+ HL_Vector& getHyperLabels()
+ {
+ return m_aRoadmapSteps;
+ }
+
+ void insertHyperLabel(ItemIndex Index, RoadmapItem* _rRoadmapStep)
+ {
+ m_aRoadmapSteps.insert(m_aRoadmapSteps.begin() + Index, _rRoadmapStep);
+ }
+
+ ItemIndex getItemCount() const
+ {
+ return m_aRoadmapSteps.size();
+ }
+
+ void setCurItemID(ItemId i)
+ {
+ m_iCurItemID = i;
+ }
+ ItemId getCurItemID() const
+ {
+ return m_iCurItemID;
+ }
+
+ void setInteractive(const bool _bInteractive)
+ {
+ m_bInteractive = _bInteractive;
+ }
+ bool isInteractive() const
+ {
+ return m_bInteractive;
+ }
+
+ void setComplete(const bool _bComplete)
+ {
+ m_bComplete = _bComplete;
+ }
+ bool isComplete() const
+ {
+ return m_bComplete;
+ }
+
+ void setPicture(const BitmapEx& _rPic)
+ {
+ m_aPicture = _rPic;
+ }
+ const BitmapEx& getPicture() const
+ {
+ return m_aPicture;
+ }
+
+ void setSelectHdl(const Link<LinkParamNone*,void>& _rHdl)
+ {
+ m_aSelectHdl = _rHdl;
+ }
+ const Link<LinkParamNone*,void>& getSelectHdl() const
+ {
+ return m_aSelectHdl;
+ }
+
+ void initItemSize();
+ const Size& getItemSize() const
+ {
+ return m_aItemSizePixel;
+ }
+
+ void removeHyperLabel(ItemIndex Index)
+ {
+ if ((Index > -1) && (Index < getItemCount()))
+ {
+ delete m_aRoadmapSteps[Index];
+ m_aRoadmapSteps.erase(m_aRoadmapSteps.begin() + Index);
+ }
+ }
+};
+
+void RoadmapImpl::initItemSize()
+{
+ Size aLabelSize( m_rAntiImpl.GetOutputSizePixel() );
+ aLabelSize.setHeight( m_rAntiImpl.LogicToPixel(Size(0, LABELBASEMAPHEIGHT), MapMode(MapUnit::MapAppFont)).Height() );
+ aLabelSize.AdjustWidth( -(m_rAntiImpl.LogicToPixel(Size(2 * ROADMAP_INDENT_X, 0), MapMode(MapUnit::MapAppFont)).Width()) );
+ m_aItemSizePixel = aLabelSize;
+}
+
+//= Roadmap
+
+ORoadmap::ORoadmap(vcl::Window* _pParent, WinBits _nWinStyle)
+ : Control(_pParent, _nWinStyle)
+ , m_pImpl(new RoadmapImpl(*this))
+{
+}
+
+void ORoadmap::implInit(vcl::RenderContext& rRenderContext)
+{
+ delete m_pImpl->InCompleteHyperLabel;
+ m_pImpl->InCompleteHyperLabel = nullptr;
+ m_pImpl->setCurItemID(-1);
+ m_pImpl->setComplete(true);
+ m_pImpl->m_bPaintInitialized = true;
+
+ // Roadmap control should be reachable as one unit with a Tab key
+ // the next Tab key should spring out of the control.
+ // To reach it the control itself should get focus and set it
+ // on entries. The entries themself should not be reachable with
+ // the Tab key directly. So each entry should have WB_NOTABSTOP.
+
+ // In other words the creator should create the control with the following
+ // flags:
+ // SetStyle( ( GetStyle() | WB_TABSTOP ) & ~WB_DIALOGCONTROL );
+
+// TODO: if somebody sets a new font from outside (OutputDevice::SetFont), we would have to react
+// on this with calculating a new bold font.
+// Unfortunately, the OutputDevice does not offer a notify mechanism for a changed font.
+// So settings the font from outside is simply a forbidden scenario at the moment
+ rRenderContext.EnableMapMode(false);
+}
+
+ORoadmap::~ORoadmap()
+{
+ disposeOnce();
+}
+
+void ORoadmap::dispose()
+{
+ HL_Vector aItemsCopy = m_pImpl->getHyperLabels();
+ m_pImpl->getHyperLabels().clear();
+ for (auto const& itemCopy : aItemsCopy)
+ {
+ delete itemCopy;
+ }
+ if ( ! m_pImpl->isComplete() )
+ delete m_pImpl->InCompleteHyperLabel;
+ m_pImpl.reset();
+ Control::dispose();
+}
+
+RoadmapTypes::ItemId ORoadmap::GetCurrentRoadmapItemID() const
+{
+ return m_pImpl->getCurItemID();
+}
+
+RoadmapItem* ORoadmap::GetPreviousHyperLabel(ItemIndex Index)
+{
+ RoadmapItem* pOldItem = nullptr;
+ if ( Index > 0 )
+ pOldItem = m_pImpl->getHyperLabels().at( Index - 1 );
+ return pOldItem;
+}
+
+RoadmapItem* ORoadmap::InsertHyperLabel(ItemIndex Index, const OUString& _sLabel, ItemId RMID, bool _bEnabled, bool _bIncomplete)
+{
+ if (m_pImpl->getItemCount() == 0)
+ m_pImpl->initItemSize();
+
+ RoadmapItem* pItem = nullptr;
+ RoadmapItem* pOldItem = GetPreviousHyperLabel( Index );
+
+ pItem = new RoadmapItem( *this, m_pImpl->getItemSize() );
+ if ( _bIncomplete )
+ {
+ pItem->SetInteractive( false );
+ }
+ else
+ {
+ pItem->SetInteractive( m_pImpl->isInteractive() );
+ m_pImpl->insertHyperLabel( Index, pItem );
+ }
+ pItem->SetPosition( pOldItem );
+ pItem->Update( Index, _sLabel );
+ pItem->SetClickHdl(LINK( this, ORoadmap, ImplClickHdl ) );
+ pItem->SetID( RMID );
+ pItem->SetIndex( Index );
+ if (!_bEnabled)
+ pItem->Enable( _bEnabled );
+ return pItem;
+}
+
+void ORoadmap::SetRoadmapBitmap(const BitmapEx& _rBmp)
+{
+ m_pImpl->setPicture( _rBmp );
+ Invalidate( );
+}
+
+void ORoadmap::SetRoadmapInteractive(bool _bInteractive)
+{
+ m_pImpl->setInteractive( _bInteractive );
+
+ const HL_Vector& rItems = m_pImpl->getHyperLabels();
+ for (auto const& item : rItems)
+ {
+ item->SetInteractive( _bInteractive );
+ }
+}
+
+bool ORoadmap::IsRoadmapInteractive() const
+{
+ return m_pImpl->isInteractive();
+}
+
+void ORoadmap::SetRoadmapComplete(bool _bComplete)
+{
+ bool bWasComplete = m_pImpl->isComplete();
+ m_pImpl->setComplete( _bComplete );
+ if (_bComplete)
+ {
+ if (m_pImpl->InCompleteHyperLabel != nullptr)
+ {
+ delete m_pImpl->InCompleteHyperLabel;
+ m_pImpl->InCompleteHyperLabel = nullptr;
+ }
+ }
+ else if (bWasComplete)
+ m_pImpl->InCompleteHyperLabel = InsertHyperLabel(m_pImpl->getItemCount(), "...", -1, true/*bEnabled*/, true/*bIncomplete*/ );
+}
+
+void ORoadmap::UpdatefollowingHyperLabels(ItemIndex _nIndex)
+{
+ const HL_Vector& rItems = m_pImpl->getHyperLabels();
+ if ( _nIndex < static_cast<ItemIndex>(rItems.size()) )
+ {
+ for ( HL_Vector::const_iterator i = rItems.begin() + _nIndex;
+ i != rItems.end();
+ ++i, ++_nIndex
+ )
+ {
+ RoadmapItem* pItem = *i;
+
+ pItem->SetIndex( _nIndex );
+ pItem->SetPosition( GetPreviousHyperLabel( _nIndex ) );
+ }
+
+ }
+ if ( ! m_pImpl->isComplete() )
+ {
+ RoadmapItem* pOldItem = GetPreviousHyperLabel( m_pImpl->getItemCount() );
+ m_pImpl->InCompleteHyperLabel->SetPosition( pOldItem );
+ m_pImpl->InCompleteHyperLabel->Update( m_pImpl->getItemCount(), "..." );
+ }
+}
+
+void ORoadmap::ReplaceRoadmapItem(ItemIndex Index, const OUString& roadmapItem, ItemId RMID, bool _bEnabled)
+{
+ RoadmapItem* pItem = GetByIndex( Index);
+ if ( pItem != nullptr )
+ {
+ pItem->Update( Index, roadmapItem );
+ pItem->SetID( RMID );
+ pItem->Enable( _bEnabled );
+ }
+}
+
+RoadmapTypes::ItemIndex ORoadmap::GetItemCount() const
+{
+ return m_pImpl->getItemCount();
+}
+
+RoadmapTypes::ItemId ORoadmap::GetItemID(ItemIndex _nIndex) const
+{
+ const RoadmapItem* pHyperLabel = GetByIndex( _nIndex );
+ if ( pHyperLabel )
+ return pHyperLabel->GetID();
+ return -1;
+}
+
+void ORoadmap::InsertRoadmapItem(ItemIndex Index, const OUString& RoadmapItem, ItemId _nUniqueId, bool _bEnabled)
+{
+ InsertHyperLabel( Index, RoadmapItem, _nUniqueId, _bEnabled, false/*bIncomplete*/ );
+ // TODO YPos is superfluous, if items are always appended
+ UpdatefollowingHyperLabels( Index + 1 );
+}
+
+void ORoadmap::DeleteRoadmapItem(ItemIndex Index)
+{
+ if ( m_pImpl->getItemCount() > 0 && ( Index > -1) && ( Index < m_pImpl->getItemCount() ) )
+ {
+ m_pImpl->removeHyperLabel( Index );
+ UpdatefollowingHyperLabels( Index );
+ }
+}
+
+bool ORoadmap::IsRoadmapComplete() const
+{
+ return m_pImpl->isComplete();
+}
+
+void ORoadmap::EnableRoadmapItem( ItemId _nItemId, bool _bEnable )
+{
+ RoadmapItem* pItem = GetByID( _nItemId );
+ if ( pItem != nullptr )
+ pItem->Enable( _bEnable );
+}
+
+void ORoadmap::ChangeRoadmapItemLabel( ItemId _nID, const OUString& _sLabel )
+{
+ RoadmapItem* pItem = GetByID( _nID );
+ if ( pItem == nullptr )
+ return;
+
+ pItem->Update( pItem->GetIndex(), _sLabel );
+
+ const HL_Vector& rItems = m_pImpl->getHyperLabels();
+ size_t nPos = 0;
+ for (auto const& item : rItems)
+ {
+ item->SetPosition( GetPreviousHyperLabel(nPos) );
+ ++nPos;
+ }
+}
+
+void ORoadmap::ChangeRoadmapItemID(ItemId _nID, ItemId NewID)
+{
+ RoadmapItem* pItem = GetByID( _nID );
+ if ( pItem != nullptr )
+ pItem->SetID( NewID );
+}
+
+RoadmapItem* ORoadmap::GetByID(ItemId _nID)
+{
+ ItemId nLocID = 0;
+ const HL_Vector& rItems = m_pImpl->getHyperLabels();
+ for (auto const& item : rItems)
+ {
+ nLocID = item->GetID();
+ if ( nLocID == _nID )
+ return item;
+ }
+ return nullptr;
+}
+
+const RoadmapItem* ORoadmap::GetByID(ItemId _nID) const
+{
+ return const_cast< ORoadmap* >( this )->GetByID( _nID );
+}
+
+RoadmapItem* ORoadmap::GetByIndex(ItemIndex _nItemIndex)
+{
+ const HL_Vector& rItems = m_pImpl->getHyperLabels();
+ if ( ( _nItemIndex > -1 ) && ( o3tl::make_unsigned(_nItemIndex) < rItems.size() ) )
+ {
+ return rItems.at( _nItemIndex );
+ }
+ return nullptr;
+}
+
+const RoadmapItem* ORoadmap::GetByIndex(ItemIndex _nItemIndex) const
+{
+ return const_cast< ORoadmap* >( this )->GetByIndex( _nItemIndex );
+}
+
+RoadmapTypes::ItemId ORoadmap::GetNextAvailableItemId(ItemIndex _nNewIndex)
+{
+ ItemIndex searchIndex = ++_nNewIndex;
+ while ( searchIndex < m_pImpl->getItemCount() )
+ {
+ RoadmapItem* pItem = GetByIndex( searchIndex );
+ if ( pItem->IsEnabled() )
+ return pItem->GetID( );
+
+ ++searchIndex;
+ }
+ return -1;
+}
+
+RoadmapTypes::ItemId ORoadmap::GetPreviousAvailableItemId(ItemIndex _nNewIndex)
+{
+ ItemIndex searchIndex = --_nNewIndex;
+ while ( searchIndex > -1 )
+ {
+ RoadmapItem* pItem = GetByIndex( searchIndex );
+ if ( pItem->IsEnabled() )
+ return pItem->GetID( );
+
+ searchIndex--;
+ }
+ return -1;
+}
+
+void ORoadmap::DeselectOldRoadmapItems()
+{
+ const HL_Vector& rItems = m_pImpl->getHyperLabels();
+ for (auto const& item : rItems)
+ {
+ item->ToggleBackgroundColor( COL_TRANSPARENT );
+ }
+}
+
+void ORoadmap::SetItemSelectHdl(const Link<LinkParamNone*,void>& _rHdl)
+{
+ m_pImpl->setSelectHdl(_rHdl);
+}
+
+Link<LinkParamNone*,void> const & ORoadmap::GetItemSelectHdl() const
+{
+ return m_pImpl->getSelectHdl();
+}
+
+void ORoadmap::Select()
+{
+ GetItemSelectHdl().Call( nullptr );
+ CallEventListeners( VclEventId::RoadmapItemSelected );
+}
+
+void ORoadmap::GetFocus()
+{
+ RoadmapItem* pCurHyperLabel = GetByID( GetCurrentRoadmapItemID() );
+ if ( pCurHyperLabel != nullptr )
+ pCurHyperLabel->GrabFocus();
+}
+
+bool ORoadmap::SelectRoadmapItemByID(ItemId _nNewID, bool bGrabFocus)
+{
+ DeselectOldRoadmapItems();
+ RoadmapItem* pItem = GetByID( _nNewID );
+ if ( pItem != nullptr )
+ {
+ if ( pItem->IsEnabled() )
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ pItem->ToggleBackgroundColor( rStyleSettings.GetHighlightColor() ); //HighlightColor
+
+ if (bGrabFocus)
+ pItem->GrabFocus();
+ m_pImpl->setCurItemID(_nNewID);
+
+ Select();
+ return true;
+ }
+ }
+ return false;
+}
+
+void ORoadmap::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& _rRect)
+{
+ if (!m_pImpl->m_bPaintInitialized)
+ implInit(rRenderContext);
+ Control::Paint(rRenderContext, _rRect);
+
+ // draw the bitmap
+ if (!m_pImpl->getPicture().IsEmpty())
+ {
+ Size aBitmapSize = m_pImpl->getPicture().GetSizePixel();
+ Size aMySize(GetOutputSizePixel());
+
+ Point aBitmapPos(aMySize.Width() - aBitmapSize.Width(), aMySize.Height() - aBitmapSize.Height());
+
+ // draw it
+ rRenderContext.DrawBitmapEx( aBitmapPos, m_pImpl->getPicture() );
+ }
+
+ // draw the headline
+ DrawHeadline(rRenderContext);
+}
+
+void ORoadmap::DrawHeadline(vcl::RenderContext& rRenderContext)
+{
+ Point aTextPos = LogicToPixel(Point(ROADMAP_INDENT_X, 8), MapMode(MapUnit::MapAppFont));
+
+ Size aOutputSize(GetOutputSizePixel());
+
+ // draw it
+ rRenderContext.DrawText(tools::Rectangle(aTextPos, aOutputSize), GetText(),
+ DrawTextFlags::Left | DrawTextFlags::Top | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak);
+ rRenderContext.DrawTextLine(aTextPos, aOutputSize.Width(), STRIKEOUT_NONE, LINESTYLE_SINGLE, LINESTYLE_NONE);
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ rRenderContext.SetLineColor(rStyleSettings.GetFieldTextColor());
+ rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
+}
+
+RoadmapItem* ORoadmap::GetByPointer(vcl::Window const * pWindow)
+{
+ const HL_Vector& rItems = m_pImpl->getHyperLabels();
+ for (auto const& item : rItems)
+ {
+ if ( item->Contains( pWindow ) )
+ return item;
+ }
+ return nullptr;
+}
+
+bool ORoadmap::PreNotify(NotifyEvent& _rNEvt)
+{
+ // capture KeyEvents for taskpane cycling
+ if ( _rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ vcl::Window* pWindow = _rNEvt.GetWindow();
+ RoadmapItem* pItem = GetByPointer( pWindow );
+ if ( pItem != nullptr )
+ {
+ sal_Int16 nKeyCode = _rNEvt.GetKeyEvent()->GetKeyCode().GetCode();
+ switch( nKeyCode )
+ {
+ case KEY_UP:
+ { // Note: Performance wise this is not optimal, because we search for an ID in the labels
+ // and afterwards we search again for a label with the appropriate ID ->
+ // unnecessarily we search twice!!!
+ ItemId nPrevItemID = GetPreviousAvailableItemId( pItem->GetIndex() );
+ if ( nPrevItemID != -1 )
+ return SelectRoadmapItemByID( nPrevItemID );
+ }
+ break;
+ case KEY_DOWN:
+ {
+ ItemId nNextItemID = GetNextAvailableItemId( pItem->GetIndex() );
+ if ( nNextItemID != -1 )
+ return SelectRoadmapItemByID( nNextItemID );
+ }
+ break;
+ case KEY_SPACE:
+ return SelectRoadmapItemByID( pItem->GetID() );
+ }
+ }
+ }
+ return Window::PreNotify( _rNEvt );
+}
+
+IMPL_LINK(ORoadmap, ImplClickHdl, HyperLabel*, CurHyperLabel, void)
+{
+ SelectRoadmapItemByID( CurHyperLabel->GetID() );
+}
+
+void ORoadmap::DataChanged(const DataChangedEvent& rDCEvt)
+{
+ if (!((( rDCEvt.GetType() == DataChangedEventType::SETTINGS ) ||
+ ( rDCEvt.GetType() == DataChangedEventType::DISPLAY )) &&
+ ( rDCEvt.GetFlags() & AllSettingsFlags::STYLE )))
+ return;
+
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ SetBackground( Wallpaper( rStyleSettings.GetFieldColor() ) );
+ Color aTextColor = rStyleSettings.GetFieldTextColor();
+ vcl::Font aFont = GetFont();
+ aFont.SetColor( aTextColor );
+ SetFont( aFont );
+ RoadmapTypes::ItemId curItemID = GetCurrentRoadmapItemID();
+ RoadmapItem* pLabelItem = GetByID( curItemID );
+ if (pLabelItem != nullptr)
+ {
+ pLabelItem->ToggleBackgroundColor(rStyleSettings.GetHighlightColor());
+ }
+ Invalidate();
+}
+
+void ORoadmap::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ Color aTextColor = rStyleSettings.GetFieldTextColor();
+ vcl::Font aFont = rRenderContext.GetFont();
+ aFont.SetColor(aTextColor);
+ aFont.SetWeight(WEIGHT_BOLD);
+ aFont.SetUnderline(LINESTYLE_SINGLE);
+ rRenderContext.SetFont(aFont);
+ rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
+}
+
+RoadmapItem::RoadmapItem(ORoadmap& _rParent, const Size& _rItemPlayground)
+ : m_aItemPlayground(_rItemPlayground)
+{
+ mpID = VclPtr<IDLabel>::Create( &_rParent, WB_WORDBREAK );
+ mpID->Show();
+ mpDescription = VclPtr<HyperLabel>::Create( &_rParent, WB_NOTABSTOP | WB_WORDBREAK );
+ mpDescription->Show();
+}
+
+RoadmapItem::~RoadmapItem()
+{
+ mpID.disposeAndClear();
+ mpDescription.disposeAndClear();
+}
+
+bool RoadmapItem::Contains(const vcl::Window* _pWindow) const
+{
+ return ( mpID == _pWindow ) || ( mpDescription == _pWindow );
+}
+
+void RoadmapItem::GrabFocus()
+{
+ if ( mpDescription )
+ mpDescription->GrabFocus();
+}
+
+void RoadmapItem::SetInteractive(bool _bInteractive)
+{
+ if ( mpDescription )
+ mpDescription->SetInteractive(_bInteractive);
+}
+
+void RoadmapItem::SetID(sal_Int16 ID)
+{
+ if ( mpDescription )
+ mpDescription->SetID(ID);
+}
+
+sal_Int16 RoadmapItem::GetID() const
+{
+ return mpDescription ? mpDescription->GetID() : sal_Int16(-1);
+}
+
+void RoadmapItem::ImplUpdateIndex(const ItemIndex _nIndex)
+{
+ mpDescription->SetIndex( _nIndex );
+
+ OUString aIDText = OUString::number( _nIndex + 1 ) + ".";
+ mpID->SetText( aIDText );
+
+ // update the geometry of both controls
+ ImplUpdatePosSize();
+}
+
+void RoadmapItem::SetIndex(ItemIndex Index)
+{
+ ImplUpdateIndex(Index);
+}
+
+RoadmapTypes::ItemIndex RoadmapItem::GetIndex() const
+{
+ return mpDescription ? mpDescription->GetIndex() : ItemIndex(-1);
+}
+
+void RoadmapItem::SetPosition(RoadmapItem const * _pOldItem)
+{
+ Point aIDPos;
+ if ( _pOldItem == nullptr )
+ {
+ aIDPos = mpID->LogicToPixel(Point(ROADMAP_INDENT_X, ROADMAP_INDENT_Y), MapMode(MapUnit::MapAppFont));
+ }
+ else
+ {
+ Size aOldSize = _pOldItem->mpDescription->GetSizePixel();
+
+ aIDPos = _pOldItem->mpID->GetPosPixel();
+ aIDPos.AdjustY(aOldSize.Height() );
+ aIDPos.AdjustY(mpID->GetParent()->LogicToPixel( Size( 0, ROADMAP_ITEM_DISTANCE_Y ) ).Height() );
+ }
+ mpID->SetPosPixel( aIDPos );
+
+ sal_Int32 nDescPos = aIDPos.X() + mpID->GetSizePixel().Width();
+ mpDescription->SetPosPixel( Point( nDescPos, aIDPos.Y() ) );
+}
+
+void RoadmapItem::Enable(bool _bEnable)
+{
+ mpID->Enable(_bEnable);
+ mpDescription->Enable(_bEnable);
+}
+
+bool RoadmapItem::IsEnabled() const
+{
+ return mpID->IsEnabled();
+}
+
+void RoadmapItem::ToggleBackgroundColor(const Color& _rGBColor)
+{
+ if (_rGBColor == COL_TRANSPARENT)
+ mpID->SetControlBackground();
+ else
+ mpID->SetControlBackground( mpID->GetSettings().GetStyleSettings().GetHighlightColor() );
+ mpDescription->ToggleBackgroundColor(_rGBColor);
+}
+
+void RoadmapItem::ImplUpdatePosSize()
+{
+ // calculate widths
+ tools::Long nIDWidth = mpID->GetTextWidth( mpID->GetText() );
+ tools::Long nMaxIDWidth = mpID->GetTextWidth( "100." );
+ nIDWidth = ::std::min( nIDWidth, nMaxIDWidth );
+
+ // check how many space the description would need
+ Size aDescriptionSize = mpDescription->CalcMinimumSize( m_aItemPlayground.Width() - nIDWidth );
+
+ // position and size both controls
+ Size aIDSize( nIDWidth, aDescriptionSize.Height() );
+ mpID->SetSizePixel( aIDSize );
+
+ Point aIDPos = mpID->GetPosPixel();
+ mpDescription->SetPosPixel( Point( aIDPos.X() + nIDWidth, aIDPos.Y() ) );
+ mpDescription->SetSizePixel( aDescriptionSize );
+}
+
+void RoadmapItem::Update(ItemIndex RMIndex, const OUString& _rText)
+{
+ // update description label
+ mpDescription->SetLabel( _rText );
+
+ // update the index in both controls, which triggers updating the geometry of both
+ ImplUpdateIndex( RMIndex );
+}
+
+void RoadmapItem::SetClickHdl(const Link<HyperLabel*,void>& rLink)
+{
+ if ( mpDescription )
+ mpDescription->SetClickHdl( rLink);
+}
+
+IDLabel::IDLabel(vcl::Window* _pParent, WinBits _nWinStyle)
+ : FixedText(_pParent, _nWinStyle)
+{
+}
+
+void IDLabel::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ FixedText::ApplySettings(rRenderContext);
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ if (GetControlBackground() == COL_TRANSPARENT)
+ rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
+ else
+ rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
+}
+
+void IDLabel::DataChanged(const DataChangedEvent& rDCEvt)
+{
+ FixedText::DataChanged( rDCEvt );
+
+ if ((( rDCEvt.GetType() == DataChangedEventType::SETTINGS ) ||
+ ( rDCEvt.GetType() == DataChangedEventType::DISPLAY )) &&
+ ( rDCEvt.GetFlags() & AllSettingsFlags::STYLE ))
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ if (GetControlBackground() != COL_TRANSPARENT)
+ SetControlBackground(rStyleSettings.GetHighlightColor());
+ Invalidate();
+ }
+}
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/roadmapwizard.cxx b/vcl/source/control/roadmapwizard.cxx
new file mode 100644
index 000000000..b1738d5be
--- /dev/null
+++ b/vcl/source/control/roadmapwizard.cxx
@@ -0,0 +1,798 @@
+/* -*- 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 <vcl/toolkit/roadmap.hxx>
+#include <tools/debug.hxx>
+#include <osl/diagnose.h>
+
+#include <strings.hrc>
+#include <svdata.hxx>
+#include <wizdlg.hxx>
+
+#include <vector>
+#include <map>
+#include <set>
+
+#include "wizimpldata.hxx"
+#include <uiobject-internal.hxx>
+
+namespace vcl
+{
+ using namespace RoadmapWizardTypes;
+
+ namespace
+ {
+ typedef ::std::set< WizardTypes::WizardState > StateSet;
+
+ typedef ::std::map<
+ PathId,
+ WizardPath
+ > Paths;
+
+ typedef ::std::map<
+ WizardTypes::WizardState,
+ ::std::pair<
+ OUString,
+ RoadmapPageFactory
+ >
+ > StateDescriptions;
+ }
+
+ struct RoadmapWizardImpl
+ {
+ ScopedVclPtr<ORoadmap> pRoadmap;
+ Paths aPaths;
+ PathId nActivePath;
+ StateDescriptions aStateDescriptors;
+ StateSet aDisabledStates;
+ bool bActivePathIsDefinite;
+
+ RoadmapWizardImpl()
+ :pRoadmap( nullptr )
+ ,nActivePath( -1 )
+ ,bActivePathIsDefinite( false )
+ {
+ }
+
+ /// returns the index of the current state in given path, or -1
+ static sal_Int32 getStateIndexInPath( WizardTypes::WizardState _nState, const WizardPath& _rPath );
+ /// returns the index of the current state in the path with the given id, or -1
+ sal_Int32 getStateIndexInPath( WizardTypes::WizardState _nState, PathId _nPathId );
+ /// returns the index of the first state in which the two given paths differ
+ static sal_Int32 getFirstDifferentIndex( const WizardPath& _rLHS, const WizardPath& _rRHS );
+ };
+
+
+ sal_Int32 RoadmapWizardImpl::getStateIndexInPath( WizardTypes::WizardState _nState, const WizardPath& _rPath )
+ {
+ sal_Int32 nStateIndexInPath = 0;
+ bool bFound = false;
+ for (auto const& path : _rPath)
+ {
+ if (path == _nState)
+ {
+ bFound = true;
+ break;
+ }
+ ++nStateIndexInPath;
+ }
+ if (!bFound)
+ nStateIndexInPath = -1;
+ return nStateIndexInPath;
+ }
+
+
+ sal_Int32 RoadmapWizardImpl::getStateIndexInPath( WizardTypes::WizardState _nState, PathId _nPathId )
+ {
+ sal_Int32 nStateIndexInPath = -1;
+ Paths::const_iterator aPathPos = aPaths.find( _nPathId );
+ if ( aPathPos != aPaths.end( ) )
+ nStateIndexInPath = getStateIndexInPath( _nState, aPathPos->second );
+ return nStateIndexInPath;
+ }
+
+
+ sal_Int32 RoadmapWizardImpl::getFirstDifferentIndex( const WizardPath& _rLHS, const WizardPath& _rRHS )
+ {
+ sal_Int32 nMinLength = ::std::min( _rLHS.size(), _rRHS.size() );
+ for ( sal_Int32 nCheck = 0; nCheck < nMinLength; ++nCheck )
+ {
+ if ( _rLHS[ nCheck ] != _rRHS[ nCheck ] )
+ return nCheck;
+ }
+ return nMinLength;
+ }
+
+ //= RoadmapWizard
+ RoadmapWizard::RoadmapWizard(vcl::Window* pParent, WinBits nStyle, InitFlag eFlag)
+ : Dialog(pParent, nStyle, eFlag)
+ , maWizardLayoutIdle("vcl RoadmapWizard maWizardLayoutIdle")
+ , m_pFinish(nullptr)
+ , m_pCancel(nullptr)
+ , m_pNextPage(nullptr)
+ , m_pPrevPage(nullptr)
+ , m_pHelp(nullptr)
+ , m_xWizardImpl(new WizardMachineImplData)
+ , m_xRoadmapImpl(new RoadmapWizardImpl)
+ {
+ mpFirstPage = nullptr;
+ mpFirstBtn = nullptr;
+ mpCurTabPage = nullptr;
+ mpPrevBtn = nullptr;
+ mpNextBtn = nullptr;
+ mpViewWindow = nullptr;
+ mnCurLevel = 0;
+ mbEmptyViewMargin = false;
+ mnLeftAlignCount = 0;
+
+ maWizardLayoutIdle.SetPriority(TaskPriority::RESIZE);
+ maWizardLayoutIdle.SetInvokeHandler( LINK( this, RoadmapWizard, ImplHandleWizardLayoutTimerHdl ) );
+
+ implConstruct(WizardButtonFlags::NEXT | WizardButtonFlags::PREVIOUS | WizardButtonFlags::FINISH | WizardButtonFlags::CANCEL | WizardButtonFlags::HELP);
+
+ SetLeftAlignedButtonCount( 1 );
+ mbEmptyViewMargin = true;
+
+ m_xRoadmapImpl->pRoadmap.disposeAndReset( VclPtr<ORoadmap>::Create( this, WB_TABSTOP ) );
+ m_xRoadmapImpl->pRoadmap->SetText( VclResId( STR_WIZDLG_ROADMAP_TITLE ) );
+ m_xRoadmapImpl->pRoadmap->SetPosPixel( Point( 0, 0 ) );
+ m_xRoadmapImpl->pRoadmap->SetItemSelectHdl( LINK( this, RoadmapWizard, OnRoadmapItemSelected ) );
+
+ Size aRoadmapSize = LogicToPixel(Size(85, 0), MapMode(MapUnit::MapAppFont));
+ aRoadmapSize.setHeight( GetSizePixel().Height() );
+ m_xRoadmapImpl->pRoadmap->SetSizePixel( aRoadmapSize );
+
+ mpViewWindow = m_xRoadmapImpl->pRoadmap;
+ m_xRoadmapImpl->pRoadmap->Show();
+ }
+
+ RoadmapWizardMachine::RoadmapWizardMachine(weld::Window* pParent)
+ : WizardMachine(pParent, WizardButtonFlags::NEXT | WizardButtonFlags::PREVIOUS | WizardButtonFlags::FINISH | WizardButtonFlags::CANCEL | WizardButtonFlags::HELP)
+ , m_pImpl( new RoadmapWizardImpl )
+ {
+ m_xAssistant->connect_jump_page(LINK(this, RoadmapWizardMachine, OnRoadmapItemSelected));
+ }
+
+ void RoadmapWizard::ShowRoadmap(bool bShow)
+ {
+ m_xRoadmapImpl->pRoadmap->Show(bShow);
+ CalcAndSetSize();
+ }
+
+ RoadmapWizard::~RoadmapWizard()
+ {
+ disposeOnce();
+ }
+
+ RoadmapWizardMachine::~RoadmapWizardMachine()
+ {
+ }
+
+ void RoadmapWizard::dispose()
+ {
+ m_xRoadmapImpl.reset();
+
+ m_pFinish.disposeAndClear();
+ m_pCancel.disposeAndClear();
+ m_pNextPage.disposeAndClear();
+ m_pPrevPage.disposeAndClear();
+ m_pHelp.disposeAndClear();
+
+ if (m_xWizardImpl)
+ {
+ for (WizardTypes::WizardState i = 0; i < m_xWizardImpl->nFirstUnknownPage; ++i)
+ {
+ TabPage *pPage = GetPage(i);
+ if (pPage)
+ pPage->disposeOnce();
+ }
+ m_xWizardImpl.reset();
+ }
+
+ maWizardLayoutIdle.Stop();
+
+ // Remove all buttons
+ while ( mpFirstBtn )
+ RemoveButton( mpFirstBtn->mpButton );
+
+ // Remove all pages
+ while ( mpFirstPage )
+ RemovePage( mpFirstPage->mpPage );
+
+ mpCurTabPage.clear();
+ mpPrevBtn.clear();
+ mpNextBtn.clear();
+ mpViewWindow.clear();
+ Dialog::dispose();
+ }
+
+ void RoadmapWizard::SetRoadmapHelpId( const OString& _rId )
+ {
+ m_xRoadmapImpl->pRoadmap->SetHelpId( _rId );
+ }
+
+ void RoadmapWizardMachine::SetRoadmapHelpId(const OString& rId)
+ {
+ m_xAssistant->set_page_side_help_id(rId);
+ }
+
+ void RoadmapWizardMachine::declarePath( PathId _nPathId, const WizardPath& _lWizardStates)
+ {
+ m_pImpl->aPaths.emplace( _nPathId, _lWizardStates );
+
+ if ( m_pImpl->aPaths.size() == 1 )
+ // the very first path -> activate it
+ activatePath( _nPathId );
+ else
+ implUpdateRoadmap( );
+ }
+
+ void RoadmapWizardMachine::activatePath( PathId _nPathId, bool _bDecideForIt )
+ {
+ if ( ( _nPathId == m_pImpl->nActivePath ) && ( _bDecideForIt == m_pImpl->bActivePathIsDefinite ) )
+ // nothing to do
+ return;
+
+ // does the given path exist?
+ Paths::const_iterator aNewPathPos = m_pImpl->aPaths.find( _nPathId );
+ DBG_ASSERT( aNewPathPos != m_pImpl->aPaths.end(), "RoadmapWizard::activate: there is no such path!" );
+ if ( aNewPathPos == m_pImpl->aPaths.end() )
+ return;
+
+ // determine the index of the current state in the current path
+ sal_Int32 nCurrentStatePathIndex = -1;
+ if ( m_pImpl->nActivePath != -1 )
+ nCurrentStatePathIndex = m_pImpl->getStateIndexInPath( getCurrentState(), m_pImpl->nActivePath );
+
+ DBG_ASSERT( static_cast<sal_Int32>(aNewPathPos->second.size()) > nCurrentStatePathIndex,
+ "RoadmapWizard::activate: you cannot activate a path which has less states than we've already advanced!" );
+ // If this asserts, this for instance means that we are already in state number, say, 5
+ // of our current path, and the caller tries to activate a path which has less than 5
+ // states
+ if ( static_cast<sal_Int32>(aNewPathPos->second.size()) <= nCurrentStatePathIndex )
+ return;
+
+ // assert that the current and the new path are equal, up to nCurrentStatePathIndex
+ Paths::const_iterator aActivePathPos = m_pImpl->aPaths.find( m_pImpl->nActivePath );
+ if ( aActivePathPos != m_pImpl->aPaths.end() )
+ {
+ if ( RoadmapWizardImpl::getFirstDifferentIndex( aActivePathPos->second, aNewPathPos->second ) <= nCurrentStatePathIndex )
+ {
+ OSL_FAIL( "RoadmapWizard::activate: you cannot activate a path which conflicts with the current one *before* the current state!" );
+ return;
+ }
+ }
+
+ m_pImpl->nActivePath = _nPathId;
+ m_pImpl->bActivePathIsDefinite = _bDecideForIt;
+
+ implUpdateRoadmap( );
+ }
+
+ void RoadmapWizard::implUpdateRoadmap( )
+ {
+ DBG_ASSERT( m_xRoadmapImpl->aPaths.find( m_xRoadmapImpl->nActivePath ) != m_xRoadmapImpl->aPaths.end(),
+ "RoadmapWizard::implUpdateRoadmap: there is no such path!" );
+ const WizardPath& rActivePath( m_xRoadmapImpl->aPaths[ m_xRoadmapImpl->nActivePath ] );
+
+ sal_Int32 nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( getCurrentState(), rActivePath );
+ if (nCurrentStatePathIndex < 0)
+ return;
+
+ // determine up to which index (in the new path) we have to display the items
+ RoadmapTypes::ItemIndex nUpperStepBoundary = static_cast<RoadmapTypes::ItemIndex>(rActivePath.size());
+ bool bIncompletePath = false;
+ if ( !m_xRoadmapImpl->bActivePathIsDefinite )
+ {
+ for (auto const& path : m_xRoadmapImpl->aPaths)
+ {
+ if ( path.first == m_xRoadmapImpl->nActivePath )
+ // it's the path we are just activating -> no need to check anything
+ continue;
+ // the index from which on both paths differ
+ sal_Int32 nDivergenceIndex = RoadmapWizardImpl::getFirstDifferentIndex( rActivePath, path.second );
+ if ( nDivergenceIndex <= nCurrentStatePathIndex )
+ // they differ in an index which we have already left behind us
+ // -> this is no conflict anymore
+ continue;
+
+ // the path conflicts with our new path -> don't activate the
+ // *complete* new path, but only up to the step which is unambiguous
+ nUpperStepBoundary = nDivergenceIndex;
+ bIncompletePath = true;
+ }
+ }
+
+ // now, we have to remove all items after nCurrentStatePathIndex, and insert the items from the active
+ // path, up to (excluding) nUpperStepBoundary
+ RoadmapTypes::ItemIndex nLoopUntil = ::std::max( nUpperStepBoundary, m_xRoadmapImpl->pRoadmap->GetItemCount() );
+ for ( RoadmapTypes::ItemIndex nItemIndex = nCurrentStatePathIndex; nItemIndex < nLoopUntil; ++nItemIndex )
+ {
+ bool bExistentItem = ( nItemIndex < m_xRoadmapImpl->pRoadmap->GetItemCount() );
+ bool bNeedItem = ( nItemIndex < nUpperStepBoundary );
+
+ bool bInsertItem = false;
+ if ( bExistentItem )
+ {
+ if ( !bNeedItem )
+ {
+ while ( nItemIndex < m_xRoadmapImpl->pRoadmap->GetItemCount() )
+ m_xRoadmapImpl->pRoadmap->DeleteRoadmapItem( nItemIndex );
+ break;
+ }
+ else
+ {
+ // there is an item with this index in the roadmap - does it match what is requested by
+ // the respective state in the active path?
+ RoadmapTypes::ItemId nPresentItemId = m_xRoadmapImpl->pRoadmap->GetItemID( nItemIndex );
+ WizardTypes::WizardState nRequiredState = rActivePath[ nItemIndex ];
+ if ( nPresentItemId != nRequiredState )
+ {
+ m_xRoadmapImpl->pRoadmap->DeleteRoadmapItem( nItemIndex );
+ bInsertItem = true;
+ }
+ }
+ }
+ else
+ {
+ DBG_ASSERT( bNeedItem, "RoadmapWizard::implUpdateRoadmap: ehm - none needed, none present - why did the loop not terminate?" );
+ bInsertItem = bNeedItem;
+ }
+
+ WizardTypes::WizardState nState( rActivePath[ nItemIndex ] );
+ if ( bInsertItem )
+ {
+ m_xRoadmapImpl->pRoadmap->InsertRoadmapItem(
+ nItemIndex,
+ getStateDisplayName( nState ),
+ nState,
+ true
+ );
+ }
+
+ const bool bEnable = m_xRoadmapImpl->aDisabledStates.find( nState ) == m_xRoadmapImpl->aDisabledStates.end();
+ m_xRoadmapImpl->pRoadmap->EnableRoadmapItem( m_xRoadmapImpl->pRoadmap->GetItemID( nItemIndex ), bEnable );
+ }
+
+ m_xRoadmapImpl->pRoadmap->SetRoadmapComplete( !bIncompletePath );
+ }
+
+ void RoadmapWizardMachine::implUpdateRoadmap( )
+ {
+
+ DBG_ASSERT( m_pImpl->aPaths.find( m_pImpl->nActivePath ) != m_pImpl->aPaths.end(),
+ "RoadmapWizard::implUpdateRoadmap: there is no such path!" );
+ const WizardPath& rActivePath( m_pImpl->aPaths[ m_pImpl->nActivePath ] );
+
+ sal_Int32 nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( getCurrentState(), rActivePath );
+ if (nCurrentStatePathIndex < 0)
+ return;
+
+ // determine up to which index (in the new path) we have to display the items
+ RoadmapTypes::ItemIndex nUpperStepBoundary = static_cast<RoadmapTypes::ItemIndex>(rActivePath.size());
+ if ( !m_pImpl->bActivePathIsDefinite )
+ {
+ for (auto const& path : m_pImpl->aPaths)
+ {
+ if ( path.first == m_pImpl->nActivePath )
+ // it's the path we are just activating -> no need to check anything
+ continue;
+ // the index from which on both paths differ
+ sal_Int32 nDivergenceIndex = RoadmapWizardImpl::getFirstDifferentIndex( rActivePath, path.second );
+ if ( nDivergenceIndex <= nCurrentStatePathIndex )
+ // they differ in an index which we have already left behind us
+ // -> this is no conflict anymore
+ continue;
+
+ // the path conflicts with our new path -> don't activate the
+ // *complete* new path, but only up to the step which is unambiguous
+ nUpperStepBoundary = nDivergenceIndex;
+ }
+ }
+
+ // can we advance from the current page?
+ bool bCurrentPageCanAdvance = true;
+ BuilderPage* pCurrentPage = GetPage( getCurrentState() );
+ if ( pCurrentPage )
+ {
+ const IWizardPageController* pController = getPageController( GetPage( getCurrentState() ) );
+ OSL_ENSURE( pController != nullptr, "RoadmapWizard::implUpdateRoadmap: no controller for the current page!" );
+ bCurrentPageCanAdvance = !pController || pController->canAdvance();
+ }
+
+ // now, we have to remove all items after nCurrentStatePathIndex, and insert the items from the active
+ // path, up to (excluding) nUpperStepBoundary
+ RoadmapTypes::ItemIndex nRoadmapItems = m_xAssistant->get_n_pages();
+ RoadmapTypes::ItemIndex nLoopUntil = ::std::max( nUpperStepBoundary, nRoadmapItems );
+ for ( RoadmapTypes::ItemIndex nItemIndex = nCurrentStatePathIndex; nItemIndex < nLoopUntil; ++nItemIndex )
+ {
+ bool bExistentItem = ( nItemIndex < nRoadmapItems );
+ bool bNeedItem = ( nItemIndex < nUpperStepBoundary );
+
+ bool bInsertItem = false;
+ if ( bExistentItem )
+ {
+ if ( !bNeedItem )
+ {
+ int nPages = nRoadmapItems;
+ for (int i = nPages - 1; i >= nItemIndex; --i)
+ {
+ m_xAssistant->set_page_title(m_xAssistant->get_page_ident(i), "");
+ --nRoadmapItems;
+ }
+ break;
+ }
+ else
+ {
+ // there is an item with this index in the roadmap - does it match what is requested by
+ // the respective state in the active path?
+ RoadmapTypes::ItemId nPresentItemId = m_xAssistant->get_page_ident(nItemIndex).toInt32();
+ WizardTypes::WizardState nRequiredState = rActivePath[ nItemIndex ];
+ if ( nPresentItemId != nRequiredState )
+ {
+ m_xAssistant->set_page_title(OString::number(nPresentItemId), "");
+ bInsertItem = true;
+ }
+ }
+ }
+ else
+ {
+ DBG_ASSERT( bNeedItem, "RoadmapWizard::implUpdateRoadmap: ehm - none needed, none present - why did the loop not terminate?" );
+ bInsertItem = bNeedItem;
+ }
+
+ WizardTypes::WizardState nState( rActivePath[ nItemIndex ] );
+
+ if ( bInsertItem )
+ {
+ GetOrCreatePage(nState);
+ }
+
+ OString sIdent(OString::number(nState));
+ m_xAssistant->set_page_index(sIdent, nItemIndex);
+ m_xAssistant->set_page_title(sIdent, getStateDisplayName(nState));
+
+ // if the item is *after* the current state, but the current page does not
+ // allow advancing, the disable the state. This relieves derived classes
+ // from disabling all future states just because the current state does not
+ // (yet) allow advancing.
+ const bool bUnconditionedDisable = !bCurrentPageCanAdvance && ( nItemIndex > nCurrentStatePathIndex );
+ const bool bEnable = !bUnconditionedDisable && ( m_pImpl->aDisabledStates.find( nState ) == m_pImpl->aDisabledStates.end() );
+ m_xAssistant->set_page_sensitive(sIdent, bEnable);
+ }
+ }
+
+ WizardTypes::WizardState RoadmapWizard::determineNextState( WizardTypes::WizardState _nCurrentState ) const
+ {
+ sal_Int32 nCurrentStatePathIndex = -1;
+
+ Paths::const_iterator aActivePathPos = m_xRoadmapImpl->aPaths.find( m_xRoadmapImpl->nActivePath );
+ if ( aActivePathPos != m_xRoadmapImpl->aPaths.end() )
+ nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( _nCurrentState, aActivePathPos->second );
+
+ DBG_ASSERT( nCurrentStatePathIndex != -1, "RoadmapWizard::determineNextState: ehm - how can we travel if there is no (valid) active path?" );
+ if ( nCurrentStatePathIndex == -1 )
+ return WZS_INVALID_STATE;
+
+ sal_Int32 nNextStateIndex = nCurrentStatePathIndex + 1;
+
+ while ( ( nNextStateIndex < static_cast<sal_Int32>(aActivePathPos->second.size()) )
+ && ( m_xRoadmapImpl->aDisabledStates.find( aActivePathPos->second[ nNextStateIndex ] ) != m_xRoadmapImpl->aDisabledStates.end() )
+ )
+ {
+ ++nNextStateIndex;
+ }
+
+ if ( nNextStateIndex >= static_cast<sal_Int32>(aActivePathPos->second.size()) )
+ // there is no next state in the current path (at least none which is enabled)
+ return WZS_INVALID_STATE;
+
+ return aActivePathPos->second[ nNextStateIndex ];
+ }
+
+ WizardTypes::WizardState RoadmapWizardMachine::determineNextState( WizardTypes::WizardState _nCurrentState ) const
+ {
+ sal_Int32 nCurrentStatePathIndex = -1;
+
+ Paths::const_iterator aActivePathPos = m_pImpl->aPaths.find( m_pImpl->nActivePath );
+ if ( aActivePathPos != m_pImpl->aPaths.end() )
+ nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( _nCurrentState, aActivePathPos->second );
+
+ DBG_ASSERT( nCurrentStatePathIndex != -1, "RoadmapWizard::determineNextState: ehm - how can we travel if there is no (valid) active path?" );
+ if ( nCurrentStatePathIndex == -1 )
+ return WZS_INVALID_STATE;
+
+ sal_Int32 nNextStateIndex = nCurrentStatePathIndex + 1;
+
+ while ( ( nNextStateIndex < static_cast<sal_Int32>(aActivePathPos->second.size()) )
+ && ( m_pImpl->aDisabledStates.find( aActivePathPos->second[ nNextStateIndex ] ) != m_pImpl->aDisabledStates.end() )
+ )
+ {
+ ++nNextStateIndex;
+ }
+
+ if ( nNextStateIndex >= static_cast<sal_Int32>(aActivePathPos->second.size()) )
+ // there is no next state in the current path (at least none which is enabled)
+ return WZS_INVALID_STATE;
+
+ return aActivePathPos->second[ nNextStateIndex ];
+ }
+
+ bool RoadmapWizardMachine::canAdvance() const
+ {
+ if ( !m_pImpl->bActivePathIsDefinite )
+ {
+ // check how many paths are still allowed
+ const WizardPath& rActivePath( m_pImpl->aPaths[ m_pImpl->nActivePath ] );
+
+ // if current path has only the base item, it is not possible to proceed without activating another path
+ if(rActivePath.size()<=1)
+ return false;
+
+ sal_Int32 nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( getCurrentState(), rActivePath );
+
+ size_t nPossiblePaths(0);
+ for (auto const& path : m_pImpl->aPaths)
+ {
+ // the index from which on both paths differ
+ sal_Int32 nDivergenceIndex = RoadmapWizardImpl::getFirstDifferentIndex( rActivePath, path.second );
+
+ if ( nDivergenceIndex > nCurrentStatePathIndex )
+ // this path is still a possible path
+ nPossiblePaths += 1;
+ }
+
+ // if we have more than one path which is still possible, then we assume
+ // to always have a next state. Though there might be scenarios where this
+ // is not true, but this is too sophisticated (means not really needed) right now.
+ if ( nPossiblePaths > 1 )
+ return true;
+ }
+
+ const WizardPath& rPath = m_pImpl->aPaths[ m_pImpl->nActivePath ];
+ return *rPath.rbegin() != getCurrentState();
+ }
+
+ void RoadmapWizardMachine::updateTravelUI()
+ {
+ WizardMachine::updateTravelUI();
+
+ // disable the "Previous" button if all states in our history are disabled
+ std::vector< WizardTypes::WizardState > aHistory;
+ getStateHistory( aHistory );
+ bool bHaveEnabledState = false;
+ for (auto const& state : aHistory)
+ {
+ if ( isStateEnabled(state) )
+ {
+ bHaveEnabledState = true;
+ break;
+ }
+ }
+
+ enableButtons( WizardButtonFlags::PREVIOUS, bHaveEnabledState );
+
+ implUpdateRoadmap();
+ }
+
+ IMPL_LINK_NOARG(RoadmapWizard, OnRoadmapItemSelected, LinkParamNone*, void)
+ {
+ RoadmapTypes::ItemId nCurItemId = m_xRoadmapImpl->pRoadmap->GetCurrentRoadmapItemID();
+ if ( nCurItemId == getCurrentState() )
+ // nothing to do
+ return;
+
+ if ( isTravelingSuspended() )
+ return;
+
+ RoadmapWizardTravelSuspension aTravelGuard( *this );
+
+ sal_Int32 nCurrentIndex = m_xRoadmapImpl->getStateIndexInPath( getCurrentState(), m_xRoadmapImpl->nActivePath );
+ sal_Int32 nNewIndex = m_xRoadmapImpl->getStateIndexInPath( nCurItemId, m_xRoadmapImpl->nActivePath );
+
+ DBG_ASSERT( ( nCurrentIndex != -1 ) && ( nNewIndex != -1 ),
+ "RoadmapWizard::OnRoadmapItemSelected: something's wrong here!" );
+ if ( ( nCurrentIndex == -1 ) || ( nNewIndex == -1 ) )
+ {
+ return;
+ }
+
+ bool bResult = true;
+ if ( nNewIndex > nCurrentIndex )
+ {
+ bResult = skipUntil( static_cast<WizardTypes::WizardState>(nCurItemId) );
+ WizardTypes::WizardState nTemp = static_cast<WizardTypes::WizardState>(nCurItemId);
+ while( nTemp )
+ {
+ if( m_xRoadmapImpl->aDisabledStates.find( --nTemp ) != m_xRoadmapImpl->aDisabledStates.end() )
+ removePageFromHistory( nTemp );
+ }
+ }
+ else
+ bResult = skipBackwardUntil( static_cast<WizardTypes::WizardState>(nCurItemId) );
+
+ if ( !bResult )
+ m_xRoadmapImpl->pRoadmap->SelectRoadmapItemByID( getCurrentState() );
+ }
+
+ IMPL_LINK(RoadmapWizardMachine, OnRoadmapItemSelected, const OString&, rCurItemId, bool)
+ {
+ int nCurItemId = rCurItemId.toInt32();
+
+ if ( nCurItemId == getCurrentState() )
+ // nothing to do
+ return false;
+
+ if ( isTravelingSuspended() )
+ return false;
+
+ WizardTravelSuspension aTravelGuard( *this );
+
+ sal_Int32 nCurrentIndex = m_pImpl->getStateIndexInPath( getCurrentState(), m_pImpl->nActivePath );
+ sal_Int32 nNewIndex = m_pImpl->getStateIndexInPath( nCurItemId, m_pImpl->nActivePath );
+
+ DBG_ASSERT( ( nCurrentIndex != -1 ) && ( nNewIndex != -1 ),
+ "RoadmapWizard::OnRoadmapItemSelected: something's wrong here!" );
+ if ( ( nCurrentIndex == -1 ) || ( nNewIndex == -1 ) )
+ {
+ return false;
+ }
+
+ bool bResult = true;
+ if ( nNewIndex > nCurrentIndex )
+ {
+ bResult = skipUntil( static_cast<WizardTypes::WizardState>(nCurItemId) );
+ WizardTypes::WizardState nTemp = static_cast<WizardTypes::WizardState>(nCurItemId);
+ while( nTemp )
+ {
+ if( m_pImpl->aDisabledStates.find( --nTemp ) != m_pImpl->aDisabledStates.end() )
+ removePageFromHistory( nTemp );
+ }
+ }
+ else
+ bResult = skipBackwardUntil( static_cast<WizardTypes::WizardState>(nCurItemId) );
+
+ return bResult;
+ }
+
+ void RoadmapWizard::enterState(WizardTypes::WizardState /*nState*/)
+ {
+ // synchronize the roadmap
+ implUpdateRoadmap( );
+ m_xRoadmapImpl->pRoadmap->SelectRoadmapItemByID( getCurrentState() );
+ }
+
+ void RoadmapWizardMachine::enterState( WizardTypes::WizardState _nState )
+ {
+ WizardMachine::enterState( _nState );
+
+ // synchronize the roadmap
+ implUpdateRoadmap();
+ }
+
+ OUString RoadmapWizard::getStateDisplayName( WizardTypes::WizardState _nState ) const
+ {
+ OUString sDisplayName;
+
+ StateDescriptions::const_iterator pos = m_xRoadmapImpl->aStateDescriptors.find( _nState );
+ OSL_ENSURE( pos != m_xRoadmapImpl->aStateDescriptors.end(),
+ "RoadmapWizard::getStateDisplayName: no default implementation available for this state!" );
+ if ( pos != m_xRoadmapImpl->aStateDescriptors.end() )
+ sDisplayName = pos->second.first;
+
+ return sDisplayName;
+ }
+
+ OUString RoadmapWizardMachine::getStateDisplayName( WizardTypes::WizardState _nState ) const
+ {
+ OUString sDisplayName;
+
+ StateDescriptions::const_iterator pos = m_pImpl->aStateDescriptors.find( _nState );
+ OSL_ENSURE( pos != m_pImpl->aStateDescriptors.end(),
+ "RoadmapWizard::getStateDisplayName: no default implementation available for this state!" );
+ if ( pos != m_pImpl->aStateDescriptors.end() )
+ sDisplayName = pos->second.first;
+
+ return sDisplayName;
+ }
+
+ VclPtr<TabPage> RoadmapWizard::createPage( WizardTypes::WizardState _nState )
+ {
+ VclPtr<TabPage> pPage;
+
+ StateDescriptions::const_iterator pos = m_xRoadmapImpl->aStateDescriptors.find( _nState );
+ OSL_ENSURE( pos != m_xRoadmapImpl->aStateDescriptors.end(),
+ "RoadmapWizard::createPage: no default implementation available for this state!" );
+ if ( pos != m_xRoadmapImpl->aStateDescriptors.end() )
+ {
+ RoadmapPageFactory pFactory = pos->second.second;
+ pPage = (*pFactory)( *this );
+ }
+
+ return pPage;
+ }
+
+ void RoadmapWizardMachine::enableState( WizardTypes::WizardState _nState, bool _bEnable )
+ {
+ // remember this (in case the state appears in the roadmap later on)
+ if ( _bEnable )
+ m_pImpl->aDisabledStates.erase( _nState );
+ else
+ {
+ m_pImpl->aDisabledStates.insert( _nState );
+ removePageFromHistory( _nState );
+ }
+
+ // if the state is currently in the roadmap, reflect it's new status
+ m_xAssistant->set_page_sensitive(OString::number(_nState), _bEnable);
+ }
+
+ bool RoadmapWizardMachine::knowsState( WizardTypes::WizardState i_nState ) const
+ {
+ for (auto const& path : m_pImpl->aPaths)
+ {
+ for (auto const& state : path.second)
+ {
+ if ( state == i_nState )
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool RoadmapWizardMachine::isStateEnabled( WizardTypes::WizardState _nState ) const
+ {
+ return m_pImpl->aDisabledStates.find( _nState ) == m_pImpl->aDisabledStates.end();
+ }
+
+ void RoadmapWizard::InsertRoadmapItem(int nItemIndex, const OUString& rText, int nItemId, bool bEnable)
+ {
+ m_xRoadmapImpl->pRoadmap->InsertRoadmapItem(nItemIndex, rText, nItemId, bEnable);
+ }
+
+ void RoadmapWizard::SelectRoadmapItemByID(int nItemId, bool bGrabFocus)
+ {
+ m_xRoadmapImpl->pRoadmap->SelectRoadmapItemByID(nItemId, bGrabFocus);
+ }
+
+ void RoadmapWizard::DeleteRoadmapItems()
+ {
+ while (m_xRoadmapImpl->pRoadmap->GetItemCount())
+ m_xRoadmapImpl->pRoadmap->DeleteRoadmapItem(0);
+ }
+
+ void RoadmapWizard::SetItemSelectHdl( const Link<LinkParamNone*,void>& _rHdl )
+ {
+ m_xRoadmapImpl->pRoadmap->SetItemSelectHdl(_rHdl);
+ }
+
+ int RoadmapWizard::GetCurrentRoadmapItemID() const
+ {
+ return m_xRoadmapImpl->pRoadmap->GetCurrentRoadmapItemID();
+ }
+
+ FactoryFunction RoadmapWizard::GetUITestFactory() const
+ {
+ return RoadmapWizardUIObject::create;
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/scrbar.cxx b/vcl/source/control/scrbar.cxx
new file mode 100644
index 000000000..844f80b02
--- /dev/null
+++ b/vcl/source/control/scrbar.cxx
@@ -0,0 +1,1467 @@
+/* -*- 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 <vcl/event.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/vclevent.hxx>
+
+#include <sal/log.hxx>
+
+/* #i77549#
+ HACK: for scrollbars in case of thumb rect, page up and page down rect we
+ abuse the HitTestNativeScrollbar interface. All theming engines but macOS
+ are actually able to draw the thumb according to our internal representation.
+ However macOS draws a little outside. The canonical way would be to enhance the
+ HitTestNativeScrollbar passing a ScrollbarValue additionally so all necessary
+ information is available in the call.
+ .
+ However since there is only this one small exception we will deviate a little and
+ instead pass the respective rect as control region to allow for a small correction.
+
+ So all places using HitTestNativeScrollbar on ControlPart::ThumbHorz, ControlPart::ThumbVert,
+ ControlPart::TrackHorzLeft, ControlPart::TrackHorzRight, ControlPart::TrackVertUpper, ControlPart::TrackVertLower
+ do not use the control rectangle as region but the actual part rectangle, making
+ only small deviations feasible.
+*/
+
+#include "thumbpos.hxx"
+
+#define SCRBAR_DRAW_BTN1 (sal_uInt16(0x0001))
+#define SCRBAR_DRAW_BTN2 (sal_uInt16(0x0002))
+#define SCRBAR_DRAW_PAGE1 (sal_uInt16(0x0004))
+#define SCRBAR_DRAW_PAGE2 (sal_uInt16(0x0008))
+#define SCRBAR_DRAW_THUMB (sal_uInt16(0x0010))
+#define SCRBAR_DRAW_BACKGROUND (sal_uInt16(0x0020))
+
+#define SCRBAR_STATE_BTN1_DOWN (sal_uInt16(0x0001))
+#define SCRBAR_STATE_BTN1_DISABLE (sal_uInt16(0x0002))
+#define SCRBAR_STATE_BTN2_DOWN (sal_uInt16(0x0004))
+#define SCRBAR_STATE_BTN2_DISABLE (sal_uInt16(0x0008))
+#define SCRBAR_STATE_PAGE1_DOWN (sal_uInt16(0x0010))
+#define SCRBAR_STATE_PAGE2_DOWN (sal_uInt16(0x0020))
+#define SCRBAR_STATE_THUMB_DOWN (sal_uInt16(0x0040))
+
+#define SCRBAR_VIEW_STYLE (WB_3DLOOK | WB_HORZ | WB_VERT)
+
+struct ImplScrollBarData
+{
+ AutoTimer maTimer { "vcl::ScrollBar mpData->maTimer" };
+ bool mbHide;
+};
+
+void ScrollBar::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ mpData = nullptr;
+ mnThumbPixRange = 0;
+ mnThumbPixPos = 0;
+ mnThumbPixSize = 0;
+ mnMinRange = 0;
+ mnMaxRange = 100;
+ mnThumbPos = 0;
+ mnVisibleSize = 0;
+ mnLineSize = 1;
+ mnPageSize = 1;
+ mnDelta = 0;
+ mnStateFlags = 0;
+ meScrollType = ScrollType::DontKnow;
+ mbCalcSize = true;
+ mbFullDrag = false;
+
+ ImplInitStyle( nStyle );
+ Control::ImplInit( pParent, nStyle, nullptr );
+
+ tools::Long nScrollSize = GetSettings().GetStyleSettings().GetScrollBarSize();
+ SetSizePixel( Size( nScrollSize, nScrollSize ) );
+}
+
+void ScrollBar::ImplInitStyle( WinBits nStyle )
+{
+ if ( nStyle & WB_DRAG )
+ mbFullDrag = true;
+ else
+ mbFullDrag = bool(GetSettings().GetStyleSettings().GetDragFullOptions() & DragFullOptions::Scroll);
+}
+
+ScrollBar::ScrollBar( vcl::Window* pParent, WinBits nStyle ) :
+ Control( WindowType::SCROLLBAR )
+{
+ ImplInit( pParent, nStyle );
+}
+
+ScrollBar::~ScrollBar()
+{
+ disposeOnce();
+}
+
+void ScrollBar::dispose()
+{
+ mpData.reset();
+ Control::dispose();
+}
+
+void ScrollBar::ImplUpdateRects( bool bUpdate )
+{
+ mnStateFlags &= ~SCRBAR_STATE_BTN1_DISABLE;
+ mnStateFlags &= ~SCRBAR_STATE_BTN2_DISABLE;
+
+ if ( mnThumbPixRange )
+ {
+ if ( GetStyle() & WB_HORZ )
+ {
+ maThumbRect.SetLeft( maTrackRect.Left()+mnThumbPixPos );
+ maThumbRect.SetRight( maThumbRect.Left()+mnThumbPixSize-1 );
+ if ( !mnThumbPixPos )
+ maPage1Rect.SetWidthEmpty();
+ else
+ maPage1Rect.SetRight( maThumbRect.Left()-1 );
+ if ( mnThumbPixPos >= (mnThumbPixRange-mnThumbPixSize) )
+ maPage2Rect.SetWidthEmpty();
+ else
+ {
+ maPage2Rect.SetLeft( maThumbRect.Right()+1 );
+ maPage2Rect.SetRight( maTrackRect.Right() );
+ }
+ }
+ else
+ {
+ maThumbRect.SetTop( maTrackRect.Top()+mnThumbPixPos );
+ maThumbRect.SetBottom( maThumbRect.Top()+mnThumbPixSize-1 );
+ if ( !mnThumbPixPos )
+ maPage1Rect.SetHeightEmpty();
+ else
+ maPage1Rect.SetBottom( maThumbRect.Top()-1 );
+ if ( mnThumbPixPos >= (mnThumbPixRange-mnThumbPixSize) )
+ maPage2Rect.SetHeightEmpty();
+ else
+ {
+ maPage2Rect.SetTop( maThumbRect.Bottom()+1 );
+ maPage2Rect.SetBottom( maTrackRect.Bottom() );
+ }
+ }
+ }
+ else
+ {
+ if ( GetStyle() & WB_HORZ )
+ {
+ const tools::Long nSpace = maTrackRect.Right() - maTrackRect.Left();
+ if ( nSpace > 0 )
+ {
+ maPage1Rect.SetLeft( maTrackRect.Left() );
+ maPage1Rect.SetRight( maTrackRect.Left() + (nSpace/2) );
+ maPage2Rect.SetLeft( maPage1Rect.Right() + 1 );
+ maPage2Rect.SetRight( maTrackRect.Right() );
+ }
+ }
+ else
+ {
+ const tools::Long nSpace = maTrackRect.Bottom() - maTrackRect.Top();
+ if ( nSpace > 0 )
+ {
+ maPage1Rect.SetTop( maTrackRect.Top() );
+ maPage1Rect.SetBottom( maTrackRect.Top() + (nSpace/2) );
+ maPage2Rect.SetTop( maPage1Rect.Bottom() + 1 );
+ maPage2Rect.SetBottom( maTrackRect.Bottom() );
+ }
+ }
+ }
+
+ if( !IsNativeControlSupported(ControlType::Scrollbar, ControlPart::Entire) )
+ {
+ // disable scrollbar buttons only in VCL's own 'theme'
+ // as it is uncommon on other platforms
+ if ( mnThumbPos == mnMinRange )
+ mnStateFlags |= SCRBAR_STATE_BTN1_DISABLE;
+ if ( mnThumbPos >= (mnMaxRange-mnVisibleSize) )
+ mnStateFlags |= SCRBAR_STATE_BTN2_DISABLE;
+ }
+
+ if ( bUpdate )
+ {
+ Invalidate();
+ }
+}
+
+tools::Long ScrollBar::ImplCalcThumbPos( tools::Long nPixPos ) const
+{
+ // Calculate position
+ tools::Long nCalcThumbPos;
+ nCalcThumbPos = ImplMulDiv( nPixPos, mnMaxRange-mnVisibleSize-mnMinRange,
+ mnThumbPixRange-mnThumbPixSize );
+ nCalcThumbPos += mnMinRange;
+ return nCalcThumbPos;
+}
+
+tools::Long ScrollBar::ImplCalcThumbPosPix( tools::Long nPos ) const
+{
+ tools::Long nCalcThumbPos;
+
+ // Calculate position
+ nCalcThumbPos = ImplMulDiv( nPos-mnMinRange, mnThumbPixRange-mnThumbPixSize,
+ mnMaxRange-mnVisibleSize-mnMinRange );
+
+ // At the start and end of the ScrollBar, we try to show the display correctly
+ if ( !nCalcThumbPos && (mnThumbPos > mnMinRange) )
+ nCalcThumbPos = 1;
+ if ( nCalcThumbPos &&
+ ((nCalcThumbPos+mnThumbPixSize) >= mnThumbPixRange) &&
+ (mnThumbPos < (mnMaxRange-mnVisibleSize)) )
+ nCalcThumbPos--;
+
+ return nCalcThumbPos;
+}
+
+void ScrollBar::ImplCalc( bool bUpdate )
+{
+ const Size aSize = GetOutputSizePixel();
+ const tools::Long nMinThumbSize = GetSettings().GetStyleSettings().GetMinThumbSize();
+
+ if ( mbCalcSize )
+ {
+ Size aOldSize = getCurrentCalcSize();
+
+ const tools::Rectangle aControlRegion( Point(0,0), aSize );
+ tools::Rectangle aBtn1Region, aBtn2Region, aTrackRegion, aBoundingRegion;
+
+ // reset rectangles to empty *and* (0,0) position
+ maThumbRect = tools::Rectangle();
+ maPage1Rect = tools::Rectangle();
+ maPage2Rect = tools::Rectangle();
+
+ if ( GetStyle() & WB_HORZ )
+ {
+ if ( GetNativeControlRegion( ControlType::Scrollbar, IsRTLEnabled()? ControlPart::ButtonRight: ControlPart::ButtonLeft,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aBtn1Region ) &&
+ GetNativeControlRegion( ControlType::Scrollbar, IsRTLEnabled()? ControlPart::ButtonLeft: ControlPart::ButtonRight,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aBtn2Region ) )
+ {
+ maBtn1Rect = aBtn1Region;
+ maBtn2Rect = aBtn2Region;
+ }
+ else
+ {
+ Size aBtnSize( aSize.Height(), aSize.Height() );
+ maBtn2Rect.SetTop( maBtn1Rect.Top() );
+ maBtn2Rect.SetLeft( aSize.Width()-aSize.Height() );
+ maBtn1Rect.SetSize( aBtnSize );
+ maBtn2Rect.SetSize( aBtnSize );
+ }
+
+ if ( GetNativeControlRegion( ControlType::Scrollbar, ControlPart::TrackHorzArea,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aTrackRegion ) )
+ maTrackRect = aTrackRegion;
+ else
+ maTrackRect = tools::Rectangle::Justify( maBtn1Rect.TopRight(), maBtn2Rect.BottomLeft() );
+
+ // Check if available space is big enough for thumb ( min thumb size = ScrBar width/height )
+ mnThumbPixRange = maTrackRect.Right() - maTrackRect.Left();
+ if( mnThumbPixRange > 0 )
+ {
+ maPage1Rect.SetLeft( maTrackRect.Left() );
+ maPage1Rect.SetBottom( maTrackRect.Bottom() );
+ maPage2Rect.SetBottom (maTrackRect.Bottom() );
+ maThumbRect.SetBottom( maTrackRect.Bottom() );
+ }
+ else
+ mnThumbPixRange = 0;
+ }
+ else // WB_VERT
+ {
+ if ( GetNativeControlRegion( ControlType::Scrollbar, ControlPart::ButtonUp,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aBtn1Region ) &&
+ GetNativeControlRegion( ControlType::Scrollbar, ControlPart::ButtonDown,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aBtn2Region ) )
+ {
+ maBtn1Rect = aBtn1Region;
+ maBtn2Rect = aBtn2Region;
+ }
+ else
+ {
+ const Size aBtnSize( aSize.Width(), aSize.Width() );
+ maBtn2Rect.SetLeft( maBtn1Rect.Left() );
+ maBtn2Rect.SetTop( aSize.Height()-aSize.Width() );
+ maBtn1Rect.SetSize( aBtnSize );
+ maBtn2Rect.SetSize( aBtnSize );
+ }
+
+ if ( GetNativeControlRegion( ControlType::Scrollbar, ControlPart::TrackVertArea,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aTrackRegion ) )
+ maTrackRect = aTrackRegion;
+ else
+ maTrackRect = tools::Rectangle::Justify( maBtn1Rect.BottomLeft()+Point(0,1), maBtn2Rect.TopRight() );
+
+ // Check if available space is big enough for thumb
+ mnThumbPixRange = maTrackRect.Bottom() - maTrackRect.Top();
+ if( mnThumbPixRange > 0 )
+ {
+ maPage1Rect.SetTop( maTrackRect.Top() );
+ maPage1Rect.SetRight( maTrackRect.Right() );
+ maPage2Rect.SetRight( maTrackRect.Right() );
+ maThumbRect.SetRight( maTrackRect.Right() );
+ }
+ else
+ mnThumbPixRange = 0;
+ }
+
+ mbCalcSize = false;
+
+ Size aNewSize = getCurrentCalcSize();
+ if (aOldSize != aNewSize)
+ {
+ queue_resize();
+ }
+ }
+
+ if ( mnThumbPixRange )
+ {
+ // Calculate values
+ if ( (mnVisibleSize >= (mnMaxRange-mnMinRange)) ||
+ ((mnMaxRange-mnMinRange) <= 0) )
+ {
+ mnThumbPos = mnMinRange;
+ mnThumbPixPos = 0;
+ mnThumbPixSize = mnThumbPixRange;
+ }
+ else
+ {
+ if ( mnVisibleSize )
+ mnThumbPixSize = ImplMulDiv( mnThumbPixRange, mnVisibleSize, mnMaxRange-mnMinRange );
+ else
+ {
+ if ( GetStyle() & WB_HORZ )
+ mnThumbPixSize = maThumbRect.GetWidth();
+ else
+ mnThumbPixSize = maThumbRect.GetHeight();
+ }
+ if ( mnThumbPixSize < nMinThumbSize )
+ mnThumbPixSize = nMinThumbSize;
+ if ( mnThumbPixSize > mnThumbPixRange )
+ mnThumbPixSize = mnThumbPixRange;
+ mnThumbPixPos = ImplCalcThumbPosPix( mnThumbPos );
+ }
+ }
+
+ // If we're ought to output again and we have been triggered
+ // a Paint event via an Action, we don't output directly,
+ // but invalidate everything
+ if ( bUpdate && HasPaintEvent() )
+ {
+ Invalidate();
+ bUpdate = false;
+ }
+ ImplUpdateRects( bUpdate );
+}
+
+void ScrollBar::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags )
+{
+ Point aPos = pDev->LogicToPixel( rPos );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ if ( !(nFlags & SystemTextColorFlags::Mono) )
+ {
+ // DecoView uses the FaceColor...
+ AllSettings aSettings = pDev->GetSettings();
+ StyleSettings aStyleSettings = aSettings.GetStyleSettings();
+ if ( IsControlBackground() )
+ aStyleSettings.SetFaceColor( GetControlBackground() );
+ else
+ aStyleSettings.SetFaceColor( GetSettings().GetStyleSettings().GetFaceColor() );
+
+ aSettings.SetStyleSettings( aStyleSettings );
+ pDev->SetSettings( aSettings );
+ }
+
+ // For printing:
+ // - calculate the size of the rects
+ // - because this is zero-based add the correct offset
+ // - print
+ // - force recalculate
+
+ if ( mbCalcSize )
+ ImplCalc( false );
+
+ maBtn1Rect+=aPos;
+ maBtn2Rect+=aPos;
+ maThumbRect+=aPos;
+ maTrackRect+=aPos;
+ maPage1Rect+=aPos;
+ maPage2Rect+=aPos;
+
+ ImplDraw(*pDev);
+ pDev->Pop();
+
+ mbCalcSize = true;
+}
+
+bool ScrollBar::ImplDrawNative(vcl::RenderContext& rRenderContext, sal_uInt16 nSystemTextColorFlags)
+{
+ ScrollbarValue scrValue;
+
+ bool bNativeOK = rRenderContext.IsNativeControlSupported(ControlType::Scrollbar, ControlPart::Entire);
+ if (!bNativeOK)
+ return false;
+
+ bool bHorz = (GetStyle() & WB_HORZ) != 0;
+
+ // Draw the entire background if the control supports it
+ if (rRenderContext.IsNativeControlSupported(ControlType::Scrollbar, bHorz ? ControlPart::DrawBackgroundHorz : ControlPart::DrawBackgroundVert))
+ {
+ ControlState nState = (IsEnabled() ? ControlState::ENABLED : ControlState::NONE)
+ | (HasFocus() ? ControlState::FOCUSED : ControlState::NONE);
+
+ scrValue.mnMin = mnMinRange;
+ scrValue.mnMax = mnMaxRange;
+ scrValue.mnCur = mnThumbPos;
+ scrValue.mnVisibleSize = mnVisibleSize;
+ scrValue.maThumbRect = maThumbRect;
+ scrValue.maButton1Rect = maBtn1Rect;
+ scrValue.maButton2Rect = maBtn2Rect;
+ scrValue.mnButton1State = ((mnStateFlags & SCRBAR_STATE_BTN1_DOWN) ? ControlState::PRESSED : ControlState::NONE) |
+ ((!(mnStateFlags & SCRBAR_STATE_BTN1_DISABLE)) ? ControlState::ENABLED : ControlState::NONE);
+ scrValue.mnButton2State = ((mnStateFlags & SCRBAR_STATE_BTN2_DOWN) ? ControlState::PRESSED : ControlState::NONE) |
+ ((!(mnStateFlags & SCRBAR_STATE_BTN2_DISABLE)) ? ControlState::ENABLED : ControlState::NONE);
+ scrValue.mnThumbState = nState | ((mnStateFlags & SCRBAR_STATE_THUMB_DOWN) ? ControlState::PRESSED : ControlState::NONE);
+
+ if (IsMouseOver())
+ {
+ tools::Rectangle* pRect = ImplFindPartRect(GetPointerPosPixel());
+ if (pRect)
+ {
+ if (pRect == &maThumbRect)
+ scrValue.mnThumbState |= ControlState::ROLLOVER;
+ else if (pRect == &maBtn1Rect)
+ scrValue.mnButton1State |= ControlState::ROLLOVER;
+ else if (pRect == &maBtn2Rect)
+ scrValue.mnButton2State |= ControlState::ROLLOVER;
+ }
+ }
+
+ tools::Rectangle aCtrlRegion;
+ aCtrlRegion.Union(maBtn1Rect);
+ aCtrlRegion.Union(maBtn2Rect);
+ aCtrlRegion.Union(maPage1Rect);
+ aCtrlRegion.Union(maPage2Rect);
+ aCtrlRegion.Union(maThumbRect);
+
+ tools::Rectangle aRequestedRegion(Point(0,0), GetOutputSizePixel());
+ // if the actual native control region is smaller then the region that
+ // we requested the control to draw in, then draw a background rectangle
+ // to avoid drawing artifacts in the uncovered region
+ if (aCtrlRegion.GetWidth() < aRequestedRegion.GetWidth() ||
+ aCtrlRegion.GetHeight() < aRequestedRegion.GetHeight())
+ {
+ Color aFaceColor = rRenderContext.GetSettings().GetStyleSettings().GetFaceColor();
+ rRenderContext.SetFillColor(aFaceColor);
+ rRenderContext.SetLineColor(aFaceColor);
+ rRenderContext.DrawRect(aRequestedRegion);
+ }
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, (bHorz ? ControlPart::DrawBackgroundHorz : ControlPart::DrawBackgroundVert),
+ aCtrlRegion, nState, scrValue, OUString());
+ }
+ else
+ {
+ if ((nSystemTextColorFlags & SCRBAR_DRAW_PAGE1) || (nSystemTextColorFlags & SCRBAR_DRAW_PAGE2))
+ {
+ ControlPart part1 = bHorz ? ControlPart::TrackHorzLeft : ControlPart::TrackVertUpper;
+ ControlPart part2 = bHorz ? ControlPart::TrackHorzRight : ControlPart::TrackVertLower;
+ tools::Rectangle aCtrlRegion1(maPage1Rect);
+ tools::Rectangle aCtrlRegion2(maPage2Rect);
+ ControlState nState1 = (IsEnabled() ? ControlState::ENABLED : ControlState::NONE)
+ | (HasFocus() ? ControlState::FOCUSED : ControlState::NONE);
+ ControlState nState2 = nState1;
+
+ nState1 |= ((mnStateFlags & SCRBAR_STATE_PAGE1_DOWN) ? ControlState::PRESSED : ControlState::NONE);
+ nState2 |= ((mnStateFlags & SCRBAR_STATE_PAGE2_DOWN) ? ControlState::PRESSED : ControlState::NONE);
+
+ if (IsMouseOver())
+ {
+ tools::Rectangle* pRect = ImplFindPartRect(GetPointerPosPixel());
+ if (pRect)
+ {
+ if (pRect == &maPage1Rect)
+ nState1 |= ControlState::ROLLOVER;
+ else if (pRect == &maPage2Rect)
+ nState2 |= ControlState::ROLLOVER;
+ }
+ }
+
+ if (nSystemTextColorFlags & SCRBAR_DRAW_PAGE1)
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, part1, aCtrlRegion1, nState1, scrValue, OUString());
+
+ if (nSystemTextColorFlags & SCRBAR_DRAW_PAGE2)
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, part2, aCtrlRegion2, nState2, scrValue, OUString());
+ }
+ if ((nSystemTextColorFlags & SCRBAR_DRAW_BTN1) || (nSystemTextColorFlags & SCRBAR_DRAW_BTN2))
+ {
+ ControlPart part1 = bHorz ? ControlPart::ButtonLeft : ControlPart::ButtonUp;
+ ControlPart part2 = bHorz ? ControlPart::ButtonRight : ControlPart::ButtonDown;
+ tools::Rectangle aCtrlRegion1(maBtn1Rect);
+ tools::Rectangle aCtrlRegion2(maBtn2Rect);
+ ControlState nState1 = HasFocus() ? ControlState::FOCUSED : ControlState::NONE;
+ ControlState nState2 = nState1;
+
+ if (!Window::IsEnabled() || !IsEnabled())
+ nState1 = (nState2 &= ~ControlState::ENABLED);
+ else
+ nState1 = (nState2 |= ControlState::ENABLED);
+
+ nState1 |= ((mnStateFlags & SCRBAR_STATE_BTN1_DOWN) ? ControlState::PRESSED : ControlState::NONE);
+ nState2 |= ((mnStateFlags & SCRBAR_STATE_BTN2_DOWN) ? ControlState::PRESSED : ControlState::NONE);
+
+ if (mnStateFlags & SCRBAR_STATE_BTN1_DISABLE)
+ nState1 &= ~ControlState::ENABLED;
+ if (mnStateFlags & SCRBAR_STATE_BTN2_DISABLE)
+ nState2 &= ~ControlState::ENABLED;
+
+ if (IsMouseOver())
+ {
+ tools::Rectangle* pRect = ImplFindPartRect(GetPointerPosPixel());
+ if (pRect)
+ {
+ if (pRect == &maBtn1Rect)
+ nState1 |= ControlState::ROLLOVER;
+ else if (pRect == &maBtn2Rect)
+ nState2 |= ControlState::ROLLOVER;
+ }
+ }
+
+ if (nSystemTextColorFlags & SCRBAR_DRAW_BTN1)
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, part1, aCtrlRegion1, nState1, scrValue, OUString());
+
+ if (nSystemTextColorFlags & SCRBAR_DRAW_BTN2)
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, part2, aCtrlRegion2, nState2, scrValue, OUString());
+ }
+ if ((nSystemTextColorFlags & SCRBAR_DRAW_THUMB) && !maThumbRect.IsEmpty())
+ {
+ ControlState nState = IsEnabled() ? ControlState::ENABLED : ControlState::NONE;
+ tools::Rectangle aCtrlRegion(maThumbRect);
+
+ if (mnStateFlags & SCRBAR_STATE_THUMB_DOWN)
+ nState |= ControlState::PRESSED;
+
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+
+ if (IsMouseOver())
+ {
+ tools::Rectangle* pRect = ImplFindPartRect(GetPointerPosPixel());
+ if (pRect && pRect == &maThumbRect)
+ nState |= ControlState::ROLLOVER;
+ }
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, (bHorz ? ControlPart::ThumbHorz : ControlPart::ThumbVert),
+ aCtrlRegion, nState, scrValue, OUString());
+ }
+ }
+ return bNativeOK;
+}
+
+void ScrollBar::ImplDraw(vcl::RenderContext& rRenderContext)
+{
+ DecorationView aDecoView(&rRenderContext);
+ tools::Rectangle aTempRect;
+ DrawButtonFlags nStyle;
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ SymbolType eSymbolType;
+ bool bEnabled = IsEnabled();
+
+ // Finish some open calculations (if any)
+ if (mbCalcSize)
+ ImplCalc(false);
+
+ //vcl::Window *pWin = NULL;
+ //if (rRenderContext.GetOutDevType() == OUTDEV_WINDOW)
+ // pWin = static_cast<vcl::Window*>(&rRenderContext);
+
+ // Draw the entire control if the native theme engine needs it
+ if (rRenderContext.IsNativeControlSupported(ControlType::Scrollbar, ControlPart::DrawBackgroundHorz))
+ {
+ ImplDrawNative(rRenderContext, SCRBAR_DRAW_BACKGROUND);
+ return;
+ }
+
+ if (!ImplDrawNative(rRenderContext, SCRBAR_DRAW_BTN1))
+ {
+ nStyle = DrawButtonFlags::NoLightBorder;
+ if (mnStateFlags & SCRBAR_STATE_BTN1_DOWN)
+ nStyle |= DrawButtonFlags::Pressed;
+ aTempRect = aDecoView.DrawButton( PixelToLogic(maBtn1Rect), nStyle );
+ ImplCalcSymbolRect( aTempRect );
+ DrawSymbolFlags nSymbolStyle = DrawSymbolFlags::NONE;
+ if ((mnStateFlags & SCRBAR_STATE_BTN1_DISABLE) || !bEnabled)
+ nSymbolStyle |= DrawSymbolFlags::Disable;
+ if (GetStyle() & WB_HORZ)
+ eSymbolType = SymbolType::SPIN_LEFT;
+ else
+ eSymbolType = SymbolType::SPIN_UP;
+ aDecoView.DrawSymbol(aTempRect, eSymbolType, rStyleSettings.GetButtonTextColor(), nSymbolStyle);
+ }
+
+ if (!ImplDrawNative(rRenderContext, SCRBAR_DRAW_BTN2))
+ {
+ nStyle = DrawButtonFlags::NoLightBorder;
+ if (mnStateFlags & SCRBAR_STATE_BTN2_DOWN)
+ nStyle |= DrawButtonFlags::Pressed;
+ aTempRect = aDecoView.DrawButton(PixelToLogic(maBtn2Rect), nStyle);
+ ImplCalcSymbolRect(aTempRect);
+ DrawSymbolFlags nSymbolStyle = DrawSymbolFlags::NONE;
+ if ((mnStateFlags & SCRBAR_STATE_BTN2_DISABLE) || !bEnabled)
+ nSymbolStyle |= DrawSymbolFlags::Disable;
+ if (GetStyle() & WB_HORZ)
+ eSymbolType = SymbolType::SPIN_RIGHT;
+ else
+ eSymbolType = SymbolType::SPIN_DOWN;
+ aDecoView.DrawSymbol(aTempRect, eSymbolType, rStyleSettings.GetButtonTextColor(), nSymbolStyle);
+ }
+
+ rRenderContext.SetLineColor();
+
+ if (!ImplDrawNative(rRenderContext, SCRBAR_DRAW_THUMB))
+ {
+ if (!maThumbRect.IsEmpty())
+ {
+ if (bEnabled)
+ {
+ nStyle = DrawButtonFlags::NoLightBorder;
+ aTempRect = aDecoView.DrawButton(PixelToLogic(maThumbRect), nStyle);
+ }
+ else
+ {
+ rRenderContext.SetFillColor(rStyleSettings.GetCheckedColor());
+ rRenderContext.DrawRect(PixelToLogic(maThumbRect));
+ }
+ }
+ }
+
+ if (!ImplDrawNative(rRenderContext, SCRBAR_DRAW_PAGE1))
+ {
+ if (mnStateFlags & SCRBAR_STATE_PAGE1_DOWN)
+ rRenderContext.SetFillColor(rStyleSettings.GetShadowColor());
+ else
+ rRenderContext.SetFillColor(rStyleSettings.GetCheckedColor());
+ rRenderContext.DrawRect(PixelToLogic(maPage1Rect));
+ }
+ if (!ImplDrawNative(rRenderContext, SCRBAR_DRAW_PAGE2))
+ {
+ if (mnStateFlags & SCRBAR_STATE_PAGE2_DOWN)
+ rRenderContext.SetFillColor(rStyleSettings.GetShadowColor());
+ else
+ rRenderContext.SetFillColor(rStyleSettings.GetCheckedColor());
+ rRenderContext.DrawRect(PixelToLogic(maPage2Rect));
+ }
+}
+
+tools::Long ScrollBar::ImplScroll( tools::Long nNewPos, bool bCallEndScroll )
+{
+ tools::Long nOldPos = mnThumbPos;
+ SetThumbPos( nNewPos );
+ tools::Long nDelta = mnThumbPos-nOldPos;
+ if ( nDelta )
+ {
+ mnDelta = nDelta;
+ Scroll();
+ if ( bCallEndScroll )
+ EndScroll();
+ mnDelta = 0;
+ }
+ return nDelta;
+}
+
+tools::Long ScrollBar::ImplDoAction( bool bCallEndScroll )
+{
+ tools::Long nDelta = 0;
+
+ switch ( meScrollType )
+ {
+ case ScrollType::LineUp:
+ nDelta = ImplScroll( mnThumbPos-mnLineSize, bCallEndScroll );
+ break;
+
+ case ScrollType::LineDown:
+ nDelta = ImplScroll( mnThumbPos+mnLineSize, bCallEndScroll );
+ break;
+
+ case ScrollType::PageUp:
+ nDelta = ImplScroll( mnThumbPos-mnPageSize, bCallEndScroll );
+ break;
+
+ case ScrollType::PageDown:
+ nDelta = ImplScroll( mnThumbPos+mnPageSize, bCallEndScroll );
+ break;
+ default:
+ ;
+ }
+
+ return nDelta;
+}
+
+void ScrollBar::ImplDoMouseAction( const Point& rMousePos, bool bCallAction )
+{
+ sal_uInt16 nOldStateFlags = mnStateFlags;
+ bool bAction = false;
+ bool bHorizontal = ( GetStyle() & WB_HORZ ) != 0;
+ bool bIsInside = false;
+
+ Point aPoint( 0, 0 );
+ tools::Rectangle aControlRegion( aPoint, GetOutputSizePixel() );
+
+ switch ( meScrollType )
+ {
+ case ScrollType::LineUp:
+ if ( GetOutDev()->HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonRight: ControlPart::ButtonLeft): ControlPart::ButtonUp,
+ aControlRegion, rMousePos, bIsInside )?
+ bIsInside:
+ maBtn1Rect.Contains( rMousePos ) )
+ {
+ bAction = bCallAction;
+ mnStateFlags |= SCRBAR_STATE_BTN1_DOWN;
+ }
+ else
+ mnStateFlags &= ~SCRBAR_STATE_BTN1_DOWN;
+ break;
+
+ case ScrollType::LineDown:
+ if ( GetOutDev()->HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonLeft: ControlPart::ButtonRight): ControlPart::ButtonDown,
+ aControlRegion, rMousePos, bIsInside )?
+ bIsInside:
+ maBtn2Rect.Contains( rMousePos ) )
+ {
+ bAction = bCallAction;
+ mnStateFlags |= SCRBAR_STATE_BTN2_DOWN;
+ }
+ else
+ mnStateFlags &= ~SCRBAR_STATE_BTN2_DOWN;
+ break;
+
+ case ScrollType::PageUp:
+ // HitTestNativeScrollbar, see remark at top of file
+ if ( GetOutDev()->HitTestNativeScrollbar( bHorizontal? ControlPart::TrackHorzLeft: ControlPart::TrackVertUpper,
+ maPage1Rect, rMousePos, bIsInside )?
+ bIsInside:
+ maPage1Rect.Contains( rMousePos ) )
+ {
+ bAction = bCallAction;
+ mnStateFlags |= SCRBAR_STATE_PAGE1_DOWN;
+ }
+ else
+ mnStateFlags &= ~SCRBAR_STATE_PAGE1_DOWN;
+ break;
+
+ case ScrollType::PageDown:
+ // HitTestNativeScrollbar, see remark at top of file
+ if ( GetOutDev()->HitTestNativeScrollbar( bHorizontal? ControlPart::TrackHorzRight: ControlPart::TrackVertLower,
+ maPage2Rect, rMousePos, bIsInside )?
+ bIsInside:
+ maPage2Rect.Contains( rMousePos ) )
+ {
+ bAction = bCallAction;
+ mnStateFlags |= SCRBAR_STATE_PAGE2_DOWN;
+ }
+ else
+ mnStateFlags &= ~SCRBAR_STATE_PAGE2_DOWN;
+ break;
+ default:
+ ;
+ }
+
+ if ( nOldStateFlags != mnStateFlags )
+ Invalidate();
+ if ( bAction )
+ ImplDoAction( false );
+}
+
+void ScrollBar::ImplDragThumb( const Point& rMousePos )
+{
+ tools::Long nMovePix;
+ if ( GetStyle() & WB_HORZ )
+ nMovePix = rMousePos.X()-(maThumbRect.Left()+mnMouseOff);
+ else
+ nMovePix = rMousePos.Y()-(maThumbRect.Top()+mnMouseOff);
+
+ // Move thumb if necessary
+ if ( !nMovePix )
+ return;
+
+ mnThumbPixPos += nMovePix;
+ if ( mnThumbPixPos < 0 )
+ mnThumbPixPos = 0;
+ if ( mnThumbPixPos > (mnThumbPixRange-mnThumbPixSize) )
+ mnThumbPixPos = mnThumbPixRange-mnThumbPixSize;
+ tools::Long nOldPos = mnThumbPos;
+ mnThumbPos = ImplCalcThumbPos( mnThumbPixPos );
+ ImplUpdateRects();
+ if ( !(mbFullDrag && (nOldPos != mnThumbPos)) )
+ return;
+
+ // When dragging in windows the repaint request gets starved so dragging
+ // the scrollbar feels slower than it actually is. Let's force an immediate
+ // repaint of the scrollbar.
+ if (SupportsDoubleBuffering())
+ {
+ Invalidate();
+ PaintImmediately();
+ }
+ else
+ ImplDraw(*GetOutDev());
+
+ mnDelta = mnThumbPos-nOldPos;
+ Scroll();
+ mnDelta = 0;
+}
+
+void ScrollBar::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ bool bPrimaryWarps = GetSettings().GetStyleSettings().GetPrimaryButtonWarpsSlider();
+ bool bWarp = bPrimaryWarps ? rMEvt.IsLeft() : rMEvt.IsMiddle();
+ bool bPrimaryWarping = bWarp && rMEvt.IsLeft();
+ bool bPage = bPrimaryWarps ? rMEvt.IsRight() : rMEvt.IsLeft();
+
+ if (!rMEvt.IsLeft() && !rMEvt.IsMiddle() && !rMEvt.IsRight())
+ return;
+
+ Point aPosPixel;
+ if (!IsMapModeEnabled() && GetMapMode().GetMapUnit() == MapUnit::MapTwip)
+ {
+ // rMEvt coordinates are in twips.
+ GetOutDev()->Push(vcl::PushFlags::MAPMODE);
+ EnableMapMode();
+ MapMode aMapMode = GetMapMode();
+ aMapMode.SetOrigin(Point(0, 0));
+ SetMapMode(aMapMode);
+ aPosPixel = LogicToPixel(rMEvt.GetPosPixel());
+ GetOutDev()->Pop();
+ }
+ const Point& rMousePos = (GetMapMode().GetMapUnit() != MapUnit::MapTwip ? rMEvt.GetPosPixel() : aPosPixel);
+ StartTrackingFlags nTrackFlags = StartTrackingFlags::NONE;
+ bool bHorizontal = ( GetStyle() & WB_HORZ ) != 0;
+ bool bIsInside = false;
+ bool bDragToMouse = false;
+
+ Point aPoint( 0, 0 );
+ tools::Rectangle aControlRegion( aPoint, GetOutputSizePixel() );
+
+ if ( GetOutDev()->HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonRight: ControlPart::ButtonLeft): ControlPart::ButtonUp,
+ aControlRegion, rMousePos, bIsInside )?
+ bIsInside:
+ maBtn1Rect.Contains( rMousePos ) )
+ {
+ if (rMEvt.IsLeft() && !(mnStateFlags & SCRBAR_STATE_BTN1_DISABLE) )
+ {
+ nTrackFlags = StartTrackingFlags::ButtonRepeat;
+ meScrollType = ScrollType::LineUp;
+ }
+ }
+ else if ( GetOutDev()->HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonLeft: ControlPart::ButtonRight): ControlPart::ButtonDown,
+ aControlRegion, rMousePos, bIsInside )?
+ bIsInside:
+ maBtn2Rect.Contains( rMousePos ) )
+ {
+ if (rMEvt.IsLeft() && !(mnStateFlags & SCRBAR_STATE_BTN2_DISABLE) )
+ {
+ nTrackFlags = StartTrackingFlags::ButtonRepeat;
+ meScrollType = ScrollType::LineDown;
+ }
+ }
+ else
+ {
+ bool bThumbHit = GetOutDev()->HitTestNativeScrollbar( bHorizontal? ControlPart::ThumbHorz : ControlPart::ThumbVert,
+ maThumbRect, rMousePos, bIsInside )
+ ? bIsInside : maThumbRect.Contains( rMousePos );
+
+ bool bThumbAction = bWarp || bPage;
+
+ bool bDragHandling = bWarp || (bThumbHit && bThumbAction);
+ if( bDragHandling )
+ {
+ if( mpData )
+ {
+ mpData->mbHide = true; // disable focus blinking
+ if (HasFocus())
+ {
+ mnStateFlags |= SCRBAR_DRAW_THUMB; // paint without focus
+ Invalidate();
+ }
+ }
+
+ if ( mnVisibleSize < mnMaxRange-mnMinRange )
+ {
+ nTrackFlags = StartTrackingFlags::NONE;
+ meScrollType = ScrollType::Drag;
+
+ // calculate mouse offset
+ if (bWarp && (!bThumbHit || !bPrimaryWarping))
+ {
+ bDragToMouse = true;
+ if ( GetStyle() & WB_HORZ )
+ mnMouseOff = maThumbRect.GetWidth()/2;
+ else
+ mnMouseOff = maThumbRect.GetHeight()/2;
+ }
+ else
+ {
+ if ( GetStyle() & WB_HORZ )
+ mnMouseOff = rMousePos.X()-maThumbRect.Left();
+ else
+ mnMouseOff = rMousePos.Y()-maThumbRect.Top();
+ }
+
+ mnStateFlags |= SCRBAR_STATE_THUMB_DOWN;
+ Invalidate();
+ }
+ }
+ else if(bPage && (!GetOutDev()->HitTestNativeScrollbar( bHorizontal? ControlPart::TrackHorzArea : ControlPart::TrackVertArea,
+ aControlRegion, rMousePos, bIsInside ) ||
+ bIsInside) )
+ {
+ nTrackFlags = StartTrackingFlags::ButtonRepeat;
+
+ // HitTestNativeScrollbar, see remark at top of file
+ if ( GetOutDev()->HitTestNativeScrollbar( bHorizontal? ControlPart::TrackHorzLeft : ControlPart::TrackVertUpper,
+ maPage1Rect, rMousePos, bIsInside )?
+ bIsInside:
+ maPage1Rect.Contains( rMousePos ) )
+ {
+ meScrollType = ScrollType::PageUp;
+ }
+ else
+ {
+ meScrollType = ScrollType::PageDown;
+ }
+ }
+ }
+
+ // Should we start Tracking?
+ if ( meScrollType == ScrollType::DontKnow )
+ return;
+
+ // store original position for cancel and EndScroll delta
+ mnStartPos = mnThumbPos;
+ // #92906# Call StartTracking() before ImplDoMouseAction(), otherwise
+ // MouseButtonUp() / EndTracking() may be called if somebody is spending
+ // a lot of time in the scroll handler
+ StartTracking( nTrackFlags );
+ ImplDoMouseAction( rMousePos );
+
+ if( bDragToMouse )
+ ImplDragThumb( rMousePos );
+
+}
+
+void ScrollBar::Tracking( const TrackingEvent& rTEvt )
+{
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ // Restore Button and PageRect status
+ sal_uInt16 nOldStateFlags = mnStateFlags;
+ mnStateFlags &= ~(SCRBAR_STATE_BTN1_DOWN | SCRBAR_STATE_BTN2_DOWN |
+ SCRBAR_STATE_PAGE1_DOWN | SCRBAR_STATE_PAGE2_DOWN |
+ SCRBAR_STATE_THUMB_DOWN);
+ if ( nOldStateFlags != mnStateFlags )
+ Invalidate();
+
+ // Restore the old ThumbPosition when canceled
+ if ( rTEvt.IsTrackingCanceled() )
+ {
+ tools::Long nOldPos = mnThumbPos;
+ SetThumbPos( mnStartPos );
+ mnDelta = mnThumbPos-nOldPos;
+ Scroll();
+ }
+
+ if ( meScrollType == ScrollType::Drag )
+ {
+ // On a SCROLLDRAG we recalculate the Thumb, so that it's back to a
+ // rounded ThumbPosition
+ ImplCalc();
+
+ if ( !mbFullDrag && (mnStartPos != mnThumbPos) )
+ {
+ mnDelta = mnThumbPos-mnStartPos;
+ Scroll();
+ mnDelta = 0;
+ }
+ }
+
+ mnDelta = mnThumbPos-mnStartPos;
+ EndScroll();
+ mnDelta = 0;
+ meScrollType = ScrollType::DontKnow;
+
+ if( mpData )
+ mpData->mbHide = false; // re-enable focus blinking
+ }
+ else
+ {
+ Point aPosPixel;
+ if (!IsMapModeEnabled() && GetMapMode().GetMapUnit() == MapUnit::MapTwip)
+ {
+ // rTEvt coordinates are in twips.
+ GetOutDev()->Push(vcl::PushFlags::MAPMODE);
+ EnableMapMode();
+ MapMode aMapMode = GetMapMode();
+ aMapMode.SetOrigin(Point(0, 0));
+ SetMapMode(aMapMode);
+ aPosPixel = LogicToPixel(rTEvt.GetMouseEvent().GetPosPixel());
+ GetOutDev()->Pop();
+ }
+ const Point rMousePos = (GetMapMode().GetMapUnit() != MapUnit::MapTwip ? rTEvt.GetMouseEvent().GetPosPixel() : aPosPixel);
+
+ // Dragging is treated in a special way
+ if ( meScrollType == ScrollType::Drag )
+ ImplDragThumb( rMousePos );
+ else
+ ImplDoMouseAction( rMousePos, rTEvt.IsTrackingRepeat() );
+
+ // If ScrollBar values are translated in a way that there's
+ // nothing left to track, we cancel here
+ if ( !IsVisible() || (mnVisibleSize >= (mnMaxRange-mnMinRange)) )
+ EndTracking();
+ }
+}
+
+void ScrollBar::KeyInput( const KeyEvent& rKEvt )
+{
+ if ( !rKEvt.GetKeyCode().GetModifier() )
+ {
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_HOME:
+ DoScroll( 0 );
+ break;
+
+ case KEY_END:
+ DoScroll( GetRangeMax() );
+ break;
+
+ case KEY_LEFT:
+ case KEY_UP:
+ DoScrollAction( ScrollType::LineUp );
+ break;
+
+ case KEY_RIGHT:
+ case KEY_DOWN:
+ DoScrollAction( ScrollType::LineDown );
+ break;
+
+ case KEY_PAGEUP:
+ DoScrollAction( ScrollType::PageUp );
+ break;
+
+ case KEY_PAGEDOWN:
+ DoScrollAction( ScrollType::PageDown );
+ break;
+
+ default:
+ Control::KeyInput( rKEvt );
+ break;
+ }
+ }
+ else
+ Control::KeyInput( rKEvt );
+}
+
+void ScrollBar::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.SetBackground();
+}
+
+void ScrollBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ ImplDraw(rRenderContext);
+}
+
+void ScrollBar::Move()
+{
+ Control::Move();
+ mbCalcSize = true;
+ if (IsReallyVisible())
+ ImplCalc(false);
+ Invalidate();
+}
+
+void ScrollBar::Resize()
+{
+ Control::Resize();
+ mbCalcSize = true;
+ if ( IsReallyVisible() )
+ ImplCalc( false );
+ Invalidate();
+}
+
+IMPL_LINK_NOARG(ScrollBar, ImplAutoTimerHdl, Timer *, void)
+{
+ if( mpData && mpData->mbHide )
+ return;
+ ImplInvert();
+}
+
+void ScrollBar::ImplInvert()
+{
+ tools::Rectangle aRect( maThumbRect );
+ if( aRect.getWidth() > 4 )
+ {
+ aRect.AdjustLeft(2 );
+ aRect.AdjustRight( -2 );
+ }
+ if( aRect.getHeight() > 4 )
+ {
+ aRect.AdjustTop(2 );
+ aRect.AdjustBottom( -2 );
+ }
+
+ GetOutDev()->Invert( aRect );
+}
+
+void ScrollBar::GetFocus()
+{
+ if( !mpData )
+ {
+ mpData.reset(new ImplScrollBarData);
+ mpData->maTimer.SetInvokeHandler( LINK( this, ScrollBar, ImplAutoTimerHdl ) );
+ mpData->mbHide = false;
+ }
+ ImplInvert(); // react immediately
+ mpData->maTimer.SetTimeout( GetSettings().GetStyleSettings().GetCursorBlinkTime() );
+ mpData->maTimer.Start();
+ Control::GetFocus();
+}
+
+void ScrollBar::LoseFocus()
+{
+ if( mpData )
+ mpData->maTimer.Stop();
+ Invalidate();
+
+ Control::LoseFocus();
+}
+
+void ScrollBar::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ ImplCalc( false );
+ else if ( nType == StateChangedType::Data )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ ImplCalc();
+ }
+ else if ( nType == StateChangedType::UpdateMode )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ ImplCalc( false );
+ Invalidate();
+ }
+ }
+ else if ( nType == StateChangedType::Enable )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ ImplInitStyle( GetStyle() );
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ if ( (GetPrevStyle() & SCRBAR_VIEW_STYLE) !=
+ (GetStyle() & SCRBAR_VIEW_STYLE) )
+ {
+ mbCalcSize = true;
+ ImplCalc( false );
+ Invalidate();
+ }
+ }
+ }
+}
+
+void ScrollBar::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ mbCalcSize = true;
+ ImplCalc( false );
+ Invalidate();
+ }
+}
+
+tools::Rectangle* ScrollBar::ImplFindPartRect( const Point& rPt )
+{
+ bool bHorizontal = ( GetStyle() & WB_HORZ ) != 0;
+ bool bIsInside = false;
+
+ Point aPoint( 0, 0 );
+ tools::Rectangle aControlRegion( aPoint, GetOutputSizePixel() );
+
+ if( GetOutDev()->HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonRight: ControlPart::ButtonLeft): ControlPart::ButtonUp,
+ aControlRegion, rPt, bIsInside )?
+ bIsInside:
+ maBtn1Rect.Contains( rPt ) )
+ return &maBtn1Rect;
+ else if( GetOutDev()->HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonLeft: ControlPart::ButtonRight): ControlPart::ButtonDown,
+ aControlRegion, rPt, bIsInside )?
+ bIsInside:
+ maBtn2Rect.Contains( rPt ) )
+ return &maBtn2Rect;
+ // HitTestNativeScrollbar, see remark at top of file
+ else if( GetOutDev()->HitTestNativeScrollbar( bHorizontal ? ControlPart::TrackHorzLeft : ControlPart::TrackVertUpper,
+ maPage1Rect, rPt, bIsInside)?
+ bIsInside:
+ maPage1Rect.Contains( rPt ) )
+ return &maPage1Rect;
+ // HitTestNativeScrollbar, see remark at top of file
+ else if( GetOutDev()->HitTestNativeScrollbar( bHorizontal ? ControlPart::TrackHorzRight : ControlPart::TrackVertLower,
+ maPage2Rect, rPt, bIsInside)?
+ bIsInside:
+ maPage2Rect.Contains( rPt ) )
+ return &maPage2Rect;
+ // HitTestNativeScrollbar, see remark at top of file
+ else if( GetOutDev()->HitTestNativeScrollbar( bHorizontal ? ControlPart::ThumbHorz : ControlPart::ThumbVert,
+ maThumbRect, rPt, bIsInside)?
+ bIsInside:
+ maThumbRect.Contains( rPt ) )
+ return &maThumbRect;
+ else
+ return nullptr;
+}
+
+bool ScrollBar::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
+ {
+ // Trigger a redraw if mouse over state has changed
+ if( IsNativeControlSupported(ControlType::Scrollbar, ControlPart::Entire) )
+ {
+ tools::Rectangle* pRect = ImplFindPartRect( GetPointerPosPixel() );
+ tools::Rectangle* pLastRect = ImplFindPartRect( GetLastPointerPosPixel() );
+ if( pRect != pLastRect || pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow() )
+ {
+ vcl::Region aRgn( GetOutDev()->GetActiveClipRegion() );
+ vcl::Region aClipRegion;
+
+ if ( pRect )
+ aClipRegion.Union( *pRect );
+ if ( pLastRect )
+ aClipRegion.Union( *pLastRect );
+
+ // Support for 3-button scroll bars
+ bool bHas3Buttons = IsNativeControlSupported( ControlType::Scrollbar, ControlPart::HasThreeButtons );
+ if ( bHas3Buttons && ( pRect == &maBtn1Rect || pLastRect == &maBtn1Rect ) )
+ {
+ aClipRegion.Union( maBtn2Rect );
+ }
+
+ GetOutDev()->SetClipRegion( aClipRegion );
+ Invalidate(aClipRegion.GetBoundRect());
+
+ GetOutDev()->SetClipRegion( aRgn );
+ }
+ }
+ }
+ }
+
+ return Control::PreNotify(rNEvt);
+}
+
+void ScrollBar::Scroll()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ScrollbarScroll, [this] () { maScrollHdl.Call(this); } );
+}
+
+void ScrollBar::EndScroll()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ScrollbarEndScroll, [this] () { maEndScrollHdl.Call(this); } );
+}
+
+tools::Long ScrollBar::DoScroll( tools::Long nNewPos )
+{
+ if ( meScrollType != ScrollType::DontKnow )
+ return 0;
+
+ SAL_INFO("vcl.scrollbar", "DoScroll(" << nNewPos << ")");
+ meScrollType = ScrollType::Drag;
+ tools::Long nDelta = ImplScroll( nNewPos, true );
+ meScrollType = ScrollType::DontKnow;
+ return nDelta;
+}
+
+tools::Long ScrollBar::DoScrollAction( ScrollType eScrollType )
+{
+ if ( (meScrollType != ScrollType::DontKnow) ||
+ (eScrollType == ScrollType::DontKnow) ||
+ (eScrollType == ScrollType::Drag) )
+ return 0;
+
+ meScrollType = eScrollType;
+ tools::Long nDelta = ImplDoAction( true );
+ meScrollType = ScrollType::DontKnow;
+ return nDelta;
+}
+
+void ScrollBar::SetRangeMin( tools::Long nNewRange )
+{
+ SetRange( Range( nNewRange, GetRangeMax() ) );
+}
+
+void ScrollBar::SetRangeMax( tools::Long nNewRange )
+{
+ SetRange( Range( GetRangeMin(), nNewRange ) );
+}
+
+void ScrollBar::SetRange( const Range& rRange )
+{
+ // Adapt Range
+ Range aRange = rRange;
+ aRange.Justify();
+ tools::Long nNewMinRange = aRange.Min();
+ tools::Long nNewMaxRange = aRange.Max();
+
+ // If Range differs, set a new one
+ if ( (mnMinRange == nNewMinRange) && (mnMaxRange == nNewMaxRange))
+ return;
+
+ mnMinRange = nNewMinRange;
+ mnMaxRange = nNewMaxRange;
+
+ // Adapt Thumb
+ if ( mnThumbPos > mnMaxRange-mnVisibleSize )
+ mnThumbPos = mnMaxRange-mnVisibleSize;
+ if ( mnThumbPos < mnMinRange )
+ mnThumbPos = mnMinRange;
+
+ CompatStateChanged( StateChangedType::Data );
+}
+
+void ScrollBar::SetThumbPos( tools::Long nNewThumbPos )
+{
+ if ( nNewThumbPos > mnMaxRange-mnVisibleSize )
+ nNewThumbPos = mnMaxRange-mnVisibleSize;
+ if ( nNewThumbPos < mnMinRange )
+ nNewThumbPos = mnMinRange;
+
+ if ( mnThumbPos != nNewThumbPos )
+ {
+ mnThumbPos = nNewThumbPos;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+void ScrollBar::SetVisibleSize( tools::Long nNewSize )
+{
+ if ( mnVisibleSize != nNewSize )
+ {
+ mnVisibleSize = nNewSize;
+
+ // Adapt Thumb
+ if ( mnThumbPos > mnMaxRange-mnVisibleSize )
+ mnThumbPos = mnMaxRange-mnVisibleSize;
+ if ( mnThumbPos < mnMinRange )
+ mnThumbPos = mnMinRange;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+Size ScrollBar::GetOptimalSize() const
+{
+ if (mbCalcSize)
+ const_cast<ScrollBar*>(this)->ImplCalc(false);
+
+ Size aRet = getCurrentCalcSize();
+
+ const tools::Long nMinThumbSize = GetSettings().GetStyleSettings().GetMinThumbSize();
+
+ if (GetStyle() & WB_HORZ)
+ {
+ aRet.setWidth( maBtn1Rect.GetWidth() + nMinThumbSize + maBtn2Rect.GetWidth() );
+ }
+ else
+ {
+ aRet.setHeight( maBtn1Rect.GetHeight() + nMinThumbSize + maBtn2Rect.GetHeight() );
+ }
+
+ return aRet;
+}
+
+Size ScrollBar::getCurrentCalcSize() const
+{
+ tools::Rectangle aCtrlRegion;
+ aCtrlRegion.Union(maBtn1Rect);
+ aCtrlRegion.Union(maBtn2Rect);
+ aCtrlRegion.Union(maPage1Rect);
+ aCtrlRegion.Union(maPage2Rect);
+ aCtrlRegion.Union(maThumbRect);
+ return aCtrlRegion.GetSize();
+}
+
+void ScrollBarBox::ImplInit(vcl::Window* pParent, WinBits nStyle)
+{
+ Window::ImplInit( pParent, nStyle, nullptr );
+
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ tools::Long nScrollSize = rStyleSettings.GetScrollBarSize();
+ SetSizePixel(Size(nScrollSize, nScrollSize));
+}
+
+ScrollBarBox::ScrollBarBox( vcl::Window* pParent, WinBits nStyle ) :
+ Window( WindowType::SCROLLBARBOX )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void ScrollBarBox::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ if (rRenderContext.IsBackground())
+ {
+ Color aColor = rRenderContext.GetSettings().GetStyleSettings().GetFaceColor();
+ ApplyControlBackground(rRenderContext, aColor);
+ }
+}
+
+void ScrollBarBox::StateChanged( StateChangedType nType )
+{
+ Window::StateChanged( nType );
+
+ if (nType == StateChangedType::ControlBackground)
+ {
+ Invalidate();
+ }
+}
+
+void ScrollBarBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Window::DataChanged( rDCEvt );
+
+ if ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))
+ {
+ Invalidate();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/slider.cxx b/vcl/source/control/slider.cxx
new file mode 100644
index 000000000..819b833e7
--- /dev/null
+++ b/vcl/source/control/slider.cxx
@@ -0,0 +1,905 @@
+/* -*- 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 <vcl/event.hxx>
+#include <vcl/decoview.hxx>
+#include <slider.hxx>
+#include <vcl/settings.hxx>
+
+#include "thumbpos.hxx"
+
+#define SLIDER_STATE_CHANNEL1_DOWN (sal_uInt16(0x0001))
+#define SLIDER_STATE_CHANNEL2_DOWN (sal_uInt16(0x0002))
+#define SLIDER_STATE_THUMB_DOWN (sal_uInt16(0x0004))
+
+#define SLIDER_THUMB_SIZE 9
+#define SLIDER_CHANNEL_SIZE 4
+#define SLIDER_CHANNEL_HALFSIZE 2
+
+#define SLIDER_HEIGHT 16
+
+#define SLIDER_VIEW_STYLE (WB_3DLOOK | WB_HORZ | WB_VERT)
+
+void Slider::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ mnThumbPixOffset = 0;
+ mnThumbPixRange = 0;
+ mnThumbPixPos = 0; // between mnThumbPixOffset and mnThumbPixOffset+mnThumbPixRange
+ mnThumbSize = SLIDER_THUMB_SIZE;
+ mnChannelPixRange = 0;
+ mnChannelPixTop = 0;
+ mnChannelPixBottom = 0;
+
+ mnMinRange = 0;
+ mnMaxRange = 100;
+ mnThumbPos = 0;
+ mnLineSize = 1;
+ mnPageSize = 1;
+ mnStateFlags = 0;
+ meScrollType = ScrollType::DontKnow;
+ mbCalcSize = true;
+
+ Control::ImplInit( pParent, nStyle, nullptr );
+
+ ImplInitSettings();
+ SetSizePixel( CalcWindowSizePixel() );
+}
+
+Slider::Slider( vcl::Window* pParent, WinBits nStyle ) :
+ Control(WindowType::SLIDER)
+{
+ ImplInit( pParent, nStyle );
+}
+
+Slider::~Slider()
+{
+ disposeOnce();
+}
+
+void Slider::ImplInitSettings()
+{
+ vcl::Window* pParent = GetParent();
+ if ( pParent->IsChildTransparentModeEnabled() && !IsControlBackground() )
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode( ParentClipMode::NoClip );
+ SetPaintTransparent( true );
+ SetBackground();
+ }
+ else
+ {
+ EnableChildTransparentMode( false );
+ SetParentClipMode();
+ SetPaintTransparent( false );
+
+ if ( IsControlBackground() )
+ SetBackground( GetControlBackground() );
+ else
+ SetBackground( pParent->GetBackground() );
+ }
+}
+
+void Slider::ImplUpdateRects( bool bUpdate )
+{
+ tools::Rectangle aOldThumbRect = maThumbRect;
+ bool bInvalidateAll = false;
+
+ if ( mnThumbPixRange )
+ {
+ if ( GetStyle() & WB_HORZ )
+ {
+ maThumbRect.SetLeft(mnThumbPixPos - (mnThumbSize / 2));
+ maThumbRect.SetRight(maThumbRect.Left() + mnThumbSize - 1);
+ if ( 0 < maThumbRect.Left() )
+ {
+ maChannel1Rect.SetLeft( 0 );
+ maChannel1Rect.SetRight( maThumbRect.Left()-1 );
+ maChannel1Rect.SetTop( mnChannelPixTop );
+ maChannel1Rect.SetBottom( mnChannelPixBottom );
+ }
+ else
+ maChannel1Rect.SetEmpty();
+ if ( mnChannelPixRange-1 > maThumbRect.Right() )
+ {
+ maChannel2Rect.SetLeft( maThumbRect.Right()+1 );
+ maChannel2Rect.SetRight( mnChannelPixRange-1 );
+ maChannel2Rect.SetTop( mnChannelPixTop );
+ maChannel2Rect.SetBottom( mnChannelPixBottom );
+ }
+ else
+ maChannel2Rect.SetEmpty();
+
+ const tools::Rectangle aControlRegion(tools::Rectangle(Point(), Size(mnThumbSize, 10)));
+ tools::Rectangle aThumbBounds, aThumbContent;
+ if ( GetNativeControlRegion( ControlType::Slider, ControlPart::ThumbHorz,
+ aControlRegion, ControlState::NONE, ImplControlValue(),
+ aThumbBounds, aThumbContent ) )
+ {
+ maThumbRect.SetLeft( mnThumbPixPos - aThumbBounds.GetWidth()/2 );
+ maThumbRect.SetRight( maThumbRect.Left() + aThumbBounds.GetWidth() - 1 );
+ bInvalidateAll = true;
+ }
+ }
+ else
+ {
+ maThumbRect.SetTop( mnThumbPixPos - (mnThumbSize / 2));
+ maThumbRect.SetBottom( maThumbRect.Top() + mnThumbSize - 1);
+ if ( 0 < maThumbRect.Top() )
+ {
+ maChannel1Rect.SetTop( 0 );
+ maChannel1Rect.SetBottom( maThumbRect.Top()-1 );
+ maChannel1Rect.SetLeft( mnChannelPixTop );
+ maChannel1Rect.SetRight( mnChannelPixBottom );
+ }
+ else
+ maChannel1Rect.SetEmpty();
+ if ( mnChannelPixRange-1 > maThumbRect.Bottom() )
+ {
+ maChannel2Rect.SetTop( maThumbRect.Bottom()+1 );
+ maChannel2Rect.SetBottom( mnChannelPixRange-1 );
+ maChannel2Rect.SetLeft( mnChannelPixTop );
+ maChannel2Rect.SetRight( mnChannelPixBottom );
+ }
+ else
+ maChannel2Rect.SetEmpty();
+
+ const tools::Rectangle aControlRegion(tools::Rectangle(Point(), Size(10, mnThumbSize)));
+ tools::Rectangle aThumbBounds, aThumbContent;
+ if ( GetNativeControlRegion( ControlType::Slider, ControlPart::ThumbVert,
+ aControlRegion, ControlState::NONE, ImplControlValue(),
+ aThumbBounds, aThumbContent ) )
+ {
+ maThumbRect.SetTop( mnThumbPixPos - aThumbBounds.GetHeight()/2 );
+ maThumbRect.SetBottom( maThumbRect.Top() + aThumbBounds.GetHeight() - 1 );
+ bInvalidateAll = true;
+ }
+ }
+ }
+ else
+ {
+ maChannel1Rect.SetEmpty();
+ maChannel2Rect.SetEmpty();
+ maThumbRect.SetEmpty();
+ }
+
+ if ( !bUpdate )
+ return;
+
+ if ( aOldThumbRect == maThumbRect )
+ return;
+
+ if( bInvalidateAll )
+ Invalidate(InvalidateFlags::NoChildren | InvalidateFlags::NoErase);
+ else
+ {
+ vcl::Region aInvalidRegion( aOldThumbRect );
+ aInvalidRegion.Union( maThumbRect );
+
+ if( !IsBackground() && GetParent() )
+ {
+ const Point aPos( GetPosPixel() );
+ aInvalidRegion.Move( aPos.X(), aPos.Y() );
+ GetParent()->Invalidate( aInvalidRegion, InvalidateFlags::Transparent | InvalidateFlags::Update );
+ }
+ else
+ Invalidate( aInvalidRegion );
+ }
+}
+
+tools::Long Slider::ImplCalcThumbPos( tools::Long nPixPos ) const
+{
+ // calculate position
+ tools::Long nCalcThumbPos;
+ nCalcThumbPos = ImplMulDiv( nPixPos-mnThumbPixOffset, mnMaxRange-mnMinRange, mnThumbPixRange-1 );
+ nCalcThumbPos += mnMinRange;
+ return nCalcThumbPos;
+}
+
+tools::Long Slider::ImplCalcThumbPosPix( tools::Long nPos ) const
+{
+ // calculate position
+ tools::Long nCalcThumbPos;
+ nCalcThumbPos = ImplMulDiv( nPos-mnMinRange, mnThumbPixRange-1, mnMaxRange-mnMinRange );
+ // at the beginning and end we try to display Slider correctly
+ if ( !nCalcThumbPos && (mnThumbPos > mnMinRange) )
+ nCalcThumbPos = 1;
+ if ( nCalcThumbPos &&
+ (nCalcThumbPos == mnThumbPixRange-1) &&
+ (mnThumbPos < mnMaxRange) )
+ nCalcThumbPos--;
+ return nCalcThumbPos+mnThumbPixOffset;
+}
+
+void Slider::ImplCalc( bool bUpdate )
+{
+ bool bInvalidateAll = false;
+
+ if (mbCalcSize)
+ {
+ if (GetStyle() & WB_HORZ)
+ {
+ const tools::Rectangle aControlRegion(tools::Rectangle(Point(), Size(SLIDER_THUMB_SIZE, 10)));
+ tools::Rectangle aThumbBounds, aThumbContent;
+ if (GetNativeControlRegion(ControlType::Slider, ControlPart::ThumbHorz,
+ aControlRegion, ControlState::NONE, ImplControlValue(),
+ aThumbBounds, aThumbContent))
+ {
+ mnThumbSize = aThumbBounds.GetWidth();
+ }
+ else
+ {
+ mnThumbSize = SLIDER_THUMB_SIZE;
+ }
+ }
+ else
+ {
+ const tools::Rectangle aControlRegion(tools::Rectangle(Point(), Size(10, SLIDER_THUMB_SIZE)));
+ tools::Rectangle aThumbBounds, aThumbContent;
+ if (GetNativeControlRegion( ControlType::Slider, ControlPart::ThumbVert,
+ aControlRegion, ControlState::NONE, ImplControlValue(),
+ aThumbBounds, aThumbContent))
+ {
+ mnThumbSize = aThumbBounds.GetHeight();
+ }
+ else
+ {
+ mnThumbSize = SLIDER_THUMB_SIZE;
+ }
+ }
+
+ tools::Long nOldChannelPixRange = mnChannelPixRange;
+ tools::Long nOldChannelPixTop = mnChannelPixTop;
+ tools::Long nOldChannelPixBottom = mnChannelPixBottom;
+ tools::Long nCalcWidth;
+ tools::Long nCalcHeight;
+
+ maChannel1Rect.SetEmpty();
+ maChannel2Rect.SetEmpty();
+ maThumbRect.SetEmpty();
+
+ Size aSize = GetOutputSizePixel();
+ if ( GetStyle() & WB_HORZ )
+ {
+ nCalcWidth = aSize.Width();
+ nCalcHeight = aSize.Height();
+ maThumbRect.SetTop( 0 );
+ maThumbRect.SetBottom( aSize.Height()-1 );
+ }
+ else
+ {
+ nCalcWidth = aSize.Height();
+ nCalcHeight = aSize.Width();
+ maThumbRect.SetLeft( 0 );
+ maThumbRect.SetRight( aSize.Width()-1 );
+ }
+
+ if (nCalcWidth >= mnThumbSize)
+ {
+ mnThumbPixOffset = mnThumbSize / 2;
+ mnThumbPixRange = nCalcWidth - mnThumbSize;
+ mnThumbPixPos = 0;
+ mnChannelPixRange = nCalcWidth;
+ mnChannelPixTop = (nCalcHeight/2)-SLIDER_CHANNEL_HALFSIZE;
+ mnChannelPixBottom = mnChannelPixTop+SLIDER_CHANNEL_SIZE-1;
+ }
+ else
+ {
+ mnThumbPixRange = 0;
+ mnChannelPixRange = 0;
+ }
+
+ if ( (nOldChannelPixRange != mnChannelPixRange) ||
+ (nOldChannelPixTop != mnChannelPixTop) ||
+ (nOldChannelPixBottom != mnChannelPixBottom) )
+ bInvalidateAll = true;
+
+ mbCalcSize = false;
+ }
+
+ if ( mnThumbPixRange )
+ mnThumbPixPos = ImplCalcThumbPosPix( mnThumbPos );
+
+ if ( bUpdate && bInvalidateAll )
+ {
+ Invalidate();
+ bUpdate = false;
+ }
+ ImplUpdateRects( bUpdate );
+}
+
+void Slider::ImplDraw(vcl::RenderContext& rRenderContext)
+{
+ // do missing calculations
+ if (mbCalcSize)
+ ImplCalc(false);
+
+ ControlPart nPart = (GetStyle() & WB_HORZ) ? ControlPart::TrackHorzArea : ControlPart::TrackVertArea;
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::Slider, nPart))
+ {
+ ControlState nState = (IsEnabled() ? ControlState::ENABLED : ControlState::NONE);
+ nState |= (HasFocus() ? ControlState::FOCUSED : ControlState::NONE);
+
+ SliderValue aSliderValue;
+ aSliderValue.mnMin = mnMinRange;
+ aSliderValue.mnMax = mnMaxRange;
+ aSliderValue.mnCur = mnThumbPos;
+ aSliderValue.maThumbRect = maThumbRect;
+
+ if (IsMouseOver())
+ {
+ if (maThumbRect.Contains(GetPointerPosPixel()))
+ aSliderValue.mnThumbState |= ControlState::ROLLOVER;
+ }
+
+ const tools::Rectangle aCtrlRegion(Point(0,0), GetOutputSizePixel());
+
+ if (rRenderContext.DrawNativeControl(ControlType::Slider, nPart, aCtrlRegion, nState, aSliderValue, OUString()))
+ return;
+ }
+
+ DecorationView aDecoView(&rRenderContext);
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ bool bEnabled = IsEnabled();
+
+ if (!maChannel1Rect.IsEmpty())
+ {
+ tools::Long nRectSize;
+ tools::Rectangle aRect = maChannel1Rect;
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ if (GetStyle() & WB_HORZ)
+ {
+ rRenderContext.DrawLine(aRect.TopLeft(), Point(aRect.Left(), aRect.Bottom() - 1));
+ rRenderContext.DrawLine(aRect.TopLeft(), aRect.TopRight());
+ }
+ else
+ {
+ rRenderContext.DrawLine(aRect.TopLeft(), Point(aRect.Right() - 1, aRect.Top()));
+ rRenderContext.DrawLine(aRect.TopLeft(), aRect.BottomLeft());
+ }
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ if (GetStyle() & WB_HORZ)
+ {
+ rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight());
+ nRectSize = aRect.GetWidth();
+ }
+ else
+ {
+ rRenderContext.DrawLine(aRect.TopRight(), aRect.BottomRight());
+ nRectSize = aRect.GetHeight();
+ }
+
+ if (nRectSize > 1)
+ {
+ aRect.AdjustLeft( 1 );
+ aRect.AdjustTop( 1 );
+ if (GetStyle() & WB_HORZ)
+ aRect.AdjustBottom( -1 );
+ else
+ aRect.AdjustRight( -1 );
+ rRenderContext.SetLineColor();
+ if (mnStateFlags & SLIDER_STATE_CHANNEL1_DOWN)
+ rRenderContext.SetFillColor(rStyleSettings.GetShadowColor());
+ else
+ rRenderContext.SetFillColor(rStyleSettings.GetCheckedColor());
+ rRenderContext.DrawRect(aRect);
+ }
+ }
+
+ if (!maChannel2Rect.IsEmpty())
+ {
+ tools::Long nRectSize;
+ tools::Rectangle aRect = maChannel2Rect;
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ if (GetStyle() & WB_HORZ)
+ {
+ rRenderContext.DrawLine(aRect.TopRight(), aRect.BottomRight());
+ rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight());
+ nRectSize = aRect.GetWidth();
+ }
+ else
+ {
+ rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight());
+ rRenderContext.DrawLine(aRect.TopRight(), aRect.BottomRight());
+ nRectSize = aRect.GetHeight();
+ }
+
+ if (nRectSize > 1)
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ if (GetStyle() & WB_HORZ)
+ rRenderContext.DrawLine(aRect.TopLeft(), Point(aRect.Right() - 1, aRect.Top()));
+ else
+ rRenderContext.DrawLine(aRect.TopLeft(), Point(aRect.Left(), aRect.Bottom() - 1));
+
+ aRect.AdjustRight( -1 );
+ aRect.AdjustBottom( -1 );
+ if (GetStyle() & WB_HORZ)
+ aRect.AdjustTop( 1 );
+ else
+ aRect.AdjustLeft( 1 );
+ rRenderContext.SetLineColor();
+ if (mnStateFlags & SLIDER_STATE_CHANNEL2_DOWN)
+ rRenderContext.SetFillColor(rStyleSettings.GetShadowColor());
+ else
+ rRenderContext.SetFillColor(rStyleSettings.GetCheckedColor());
+ rRenderContext.DrawRect(aRect);
+ }
+ }
+
+ if (maThumbRect.IsEmpty())
+ return;
+
+ if (bEnabled)
+ {
+ DrawButtonFlags nStyle = DrawButtonFlags::NONE;
+ if (mnStateFlags & SLIDER_STATE_THUMB_DOWN)
+ nStyle |= DrawButtonFlags::Pressed;
+ aDecoView.DrawButton(maThumbRect, nStyle);
+ }
+ else
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.SetFillColor(rStyleSettings.GetCheckedColor());
+ rRenderContext.DrawRect(maThumbRect);
+ }
+}
+
+bool Slider::ImplIsPageUp( const Point& rPos ) const
+{
+ Size aSize = GetOutputSizePixel();
+ tools::Rectangle aRect = maChannel1Rect;
+ if ( GetStyle() & WB_HORZ )
+ {
+ aRect.SetTop( 0 );
+ aRect.SetBottom( aSize.Height()-1 );
+ }
+ else
+ {
+ aRect.SetLeft( 0 );
+ aRect.SetRight( aSize.Width()-1 );
+ }
+ return aRect.Contains( rPos );
+}
+
+bool Slider::ImplIsPageDown( const Point& rPos ) const
+{
+ Size aSize = GetOutputSizePixel();
+ tools::Rectangle aRect = maChannel2Rect;
+ if ( GetStyle() & WB_HORZ )
+ {
+ aRect.SetTop( 0 );
+ aRect.SetBottom( aSize.Height()-1 );
+ }
+ else
+ {
+ aRect.SetLeft( 0 );
+ aRect.SetRight( aSize.Width()-1 );
+ }
+ return aRect.Contains( rPos );
+}
+
+tools::Long Slider::ImplSlide( tools::Long nNewPos )
+{
+ tools::Long nOldPos = mnThumbPos;
+ SetThumbPos( nNewPos );
+ tools::Long nDelta = mnThumbPos-nOldPos;
+ if ( nDelta )
+ {
+ Slide();
+ }
+ return nDelta;
+}
+
+tools::Long Slider::ImplDoAction()
+{
+ tools::Long nDelta = 0;
+
+ switch ( meScrollType )
+ {
+ case ScrollType::LineUp:
+ nDelta = ImplSlide( mnThumbPos-mnLineSize );
+ break;
+
+ case ScrollType::LineDown:
+ nDelta = ImplSlide( mnThumbPos+mnLineSize );
+ break;
+
+ case ScrollType::PageUp:
+ nDelta = ImplSlide( mnThumbPos-mnPageSize );
+ break;
+
+ case ScrollType::PageDown:
+ nDelta = ImplSlide( mnThumbPos+mnPageSize );
+ break;
+
+ default:
+ break;
+ }
+
+ return nDelta;
+}
+
+void Slider::ImplDoMouseAction( const Point& rMousePos, bool bCallAction )
+{
+ sal_uInt16 nOldStateFlags = mnStateFlags;
+ bool bAction = false;
+
+ switch ( meScrollType )
+ {
+ case ScrollType::PageUp:
+ if ( ImplIsPageUp( rMousePos ) )
+ {
+ bAction = bCallAction;
+ mnStateFlags |= SLIDER_STATE_CHANNEL1_DOWN;
+ }
+ else
+ mnStateFlags &= ~SLIDER_STATE_CHANNEL1_DOWN;
+ break;
+
+ case ScrollType::PageDown:
+ if ( ImplIsPageDown( rMousePos ) )
+ {
+ bAction = bCallAction;
+ mnStateFlags |= SLIDER_STATE_CHANNEL2_DOWN;
+ }
+ else
+ mnStateFlags &= ~SLIDER_STATE_CHANNEL2_DOWN;
+ break;
+ default:
+ break;
+ }
+
+ if ( bAction )
+ {
+ if ( ImplDoAction() )
+ {
+ Invalidate();
+ }
+ }
+ else if ( nOldStateFlags != mnStateFlags )
+ {
+ Invalidate();
+ }
+}
+
+void Slider::ImplDoSlide( tools::Long nNewPos )
+{
+ if ( meScrollType != ScrollType::DontKnow )
+ return;
+
+ meScrollType = ScrollType::Drag;
+ ImplSlide( nNewPos );
+ meScrollType = ScrollType::DontKnow;
+}
+
+void Slider::ImplDoSlideAction( ScrollType eScrollType )
+{
+ if ( (meScrollType != ScrollType::DontKnow) ||
+ (eScrollType == ScrollType::DontKnow) ||
+ (eScrollType == ScrollType::Drag) )
+ return;
+
+ meScrollType = eScrollType;
+ ImplDoAction();
+ meScrollType = ScrollType::DontKnow;
+}
+
+void Slider::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( !rMEvt.IsLeft() )
+ return;
+
+ const Point& rMousePos = rMEvt.GetPosPixel();
+ StartTrackingFlags nTrackFlags = StartTrackingFlags::NONE;
+
+ if ( maThumbRect.Contains( rMousePos ) )
+ {
+ meScrollType = ScrollType::Drag;
+
+ // calculate additional values
+ Point aCenterPos = maThumbRect.Center();
+ if ( GetStyle() & WB_HORZ )
+ mnMouseOff = rMousePos.X()-aCenterPos.X();
+ else
+ mnMouseOff = rMousePos.Y()-aCenterPos.Y();
+ }
+ else if ( ImplIsPageUp( rMousePos ) )
+ {
+ nTrackFlags = StartTrackingFlags::ButtonRepeat;
+ meScrollType = ScrollType::PageUp;
+ }
+ else if ( ImplIsPageDown( rMousePos ) )
+ {
+ nTrackFlags = StartTrackingFlags::ButtonRepeat;
+ meScrollType = ScrollType::PageDown;
+ }
+
+ // Shall we start Tracking?
+ if( meScrollType != ScrollType::DontKnow )
+ {
+ // store Start position for cancel and EndScroll delta
+ mnStartPos = mnThumbPos;
+ ImplDoMouseAction( rMousePos, /*bCallAction*/true );
+ PaintImmediately();
+
+ StartTracking( nTrackFlags );
+ }
+}
+
+void Slider::MouseButtonUp( const MouseEvent& )
+{
+}
+
+void Slider::Tracking( const TrackingEvent& rTEvt )
+{
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ // reset Button and PageRect state
+ sal_uInt16 nOldStateFlags = mnStateFlags;
+ mnStateFlags &= ~(SLIDER_STATE_CHANNEL1_DOWN | SLIDER_STATE_CHANNEL2_DOWN |
+ SLIDER_STATE_THUMB_DOWN);
+ if ( nOldStateFlags != mnStateFlags )
+ {
+ Invalidate(InvalidateFlags::NoChildren | InvalidateFlags::NoErase);
+ }
+
+ // on cancel, reset the previous Thumb position
+ if ( rTEvt.IsTrackingCanceled() )
+ {
+ SetThumbPos( mnStartPos );
+ Slide();
+ }
+
+ if ( meScrollType == ScrollType::Drag )
+ {
+ // after dragging, recalculate to a rounded Thumb position
+ ImplCalc();
+ PaintImmediately();
+ }
+
+ meScrollType = ScrollType::DontKnow;
+ }
+ else
+ {
+ const Point rMousePos = rTEvt.GetMouseEvent().GetPosPixel();
+
+ // special handling for dragging
+ if ( meScrollType == ScrollType::Drag )
+ {
+ tools::Long nMovePix;
+ Point aCenterPos = maThumbRect.Center();
+ if ( GetStyle() & WB_HORZ )
+ nMovePix = rMousePos.X()-(aCenterPos.X()+mnMouseOff);
+ else
+ nMovePix = rMousePos.Y()-(aCenterPos.Y()+mnMouseOff);
+ // only if the mouse moves in Scroll direction we have to act
+ if ( nMovePix )
+ {
+ mnThumbPixPos += nMovePix;
+ if ( mnThumbPixPos < mnThumbPixOffset )
+ mnThumbPixPos = mnThumbPixOffset;
+ if ( mnThumbPixPos > (mnThumbPixOffset+mnThumbPixRange-1) )
+ mnThumbPixPos = mnThumbPixOffset+mnThumbPixRange-1;
+ tools::Long nOldPos = mnThumbPos;
+ mnThumbPos = ImplCalcThumbPos( mnThumbPixPos );
+ if ( nOldPos != mnThumbPos )
+ {
+ ImplUpdateRects();
+ PaintImmediately();
+ if ( nOldPos != mnThumbPos )
+ {
+ Slide();
+ }
+ }
+ }
+ }
+ else
+ ImplDoMouseAction( rMousePos, rTEvt.IsTrackingRepeat() );
+
+ // end tracking if ScrollBar values indicate we are done
+ if ( !IsVisible() )
+ EndTracking();
+ }
+}
+
+void Slider::KeyInput( const KeyEvent& rKEvt )
+{
+ if ( !rKEvt.GetKeyCode().GetModifier() )
+ {
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_HOME:
+ ImplDoSlide( GetRangeMin() );
+ break;
+ case KEY_END:
+ ImplDoSlide( GetRangeMax() );
+ break;
+
+ case KEY_LEFT:
+ case KEY_UP:
+ ImplDoSlideAction( ScrollType::LineUp );
+ break;
+
+ case KEY_RIGHT:
+ case KEY_DOWN:
+ ImplDoSlideAction( ScrollType::LineDown );
+ break;
+
+ case KEY_PAGEUP:
+ ImplDoSlideAction( ScrollType::PageUp );
+ break;
+
+ case KEY_PAGEDOWN:
+ ImplDoSlideAction( ScrollType::PageDown );
+ break;
+
+ default:
+ Control::KeyInput( rKEvt );
+ break;
+ }
+ }
+ else
+ Control::KeyInput( rKEvt );
+}
+
+void Slider::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
+{
+ ImplDraw(rRenderContext);
+}
+
+void Slider::Resize()
+{
+ Control::Resize();
+ mbCalcSize = true;
+ if ( IsReallyVisible() )
+ ImplCalc( false );
+ Invalidate(InvalidateFlags::NoChildren | InvalidateFlags::NoErase);
+}
+
+void Slider::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ ImplCalc( false );
+ else if ( nType == StateChangedType::Data )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ ImplCalc();
+ }
+ else if ( nType == StateChangedType::UpdateMode )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ ImplCalc( false );
+ Invalidate();
+ }
+ }
+ else if ( nType == StateChangedType::Enable )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ Invalidate();
+ }
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ if ( (GetPrevStyle() & SLIDER_VIEW_STYLE) !=
+ (GetStyle() & SLIDER_VIEW_STYLE) )
+ {
+ mbCalcSize = true;
+ ImplCalc( false );
+ Invalidate();
+ }
+ }
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+}
+
+void Slider::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+}
+
+void Slider::Slide()
+{
+ maSlideHdl.Call( this );
+}
+
+void Slider::SetRangeMin(tools::Long nNewRange)
+{
+ SetRange(Range(nNewRange, GetRangeMax()));
+}
+
+void Slider::SetRangeMax(tools::Long nNewRange)
+{
+ SetRange(Range(GetRangeMin(), nNewRange));
+}
+
+void Slider::SetRange( const Range& rRange )
+{
+ // adjust Range
+ Range aRange = rRange;
+ aRange.Justify();
+ tools::Long nNewMinRange = aRange.Min();
+ tools::Long nNewMaxRange = aRange.Max();
+
+ // reset Range if different
+ if ( (mnMinRange != nNewMinRange) ||
+ (mnMaxRange != nNewMaxRange) )
+ {
+ mnMinRange = nNewMinRange;
+ mnMaxRange = nNewMaxRange;
+
+ // adjust Thumb
+ if ( mnThumbPos > mnMaxRange )
+ mnThumbPos = mnMaxRange;
+ if ( mnThumbPos < mnMinRange )
+ mnThumbPos = mnMinRange;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+void Slider::SetThumbPos( tools::Long nNewThumbPos )
+{
+ if ( nNewThumbPos < mnMinRange )
+ nNewThumbPos = mnMinRange;
+ if ( nNewThumbPos > mnMaxRange )
+ nNewThumbPos = mnMaxRange;
+
+ if ( mnThumbPos != nNewThumbPos )
+ {
+ mnThumbPos = nNewThumbPos;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+Size Slider::CalcWindowSizePixel() const
+{
+ tools::Long nWidth = mnMaxRange - mnMinRange + mnThumbSize + 1;
+ tools::Long nHeight = SLIDER_HEIGHT;
+ Size aSize;
+ if ( GetStyle() & WB_HORZ )
+ {
+ aSize.setWidth( nWidth );
+ aSize.setHeight( nHeight );
+ }
+ else
+ {
+ aSize.setHeight( nWidth );
+ aSize.setWidth( nHeight );
+ }
+ return aSize;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/spinbtn.cxx b/vcl/source/control/spinbtn.cxx
new file mode 100644
index 000000000..c800fdc7d
--- /dev/null
+++ b/vcl/source/control/spinbtn.cxx
@@ -0,0 +1,468 @@
+/* -*- 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 <vcl/event.hxx>
+#include <vcl/toolkit/spin.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/vclevent.hxx>
+
+#include <spin.hxx>
+
+void SpinButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ mbUpperIn = false;
+ mbLowerIn = false;
+ mbInitialUp = false;
+ mbInitialDown = false;
+
+ mnMinRange = 0;
+ mnMaxRange = 100;
+ mnValue = 0;
+ mnValueStep = 1;
+
+ maRepeatTimer.SetTimeout(MouseSettings::GetButtonStartRepeat());
+ maRepeatTimer.SetInvokeHandler(LINK(this, SpinButton, ImplTimeout));
+
+ mbRepeat = 0 != (nStyle & WB_REPEAT);
+
+ if (nStyle & WB_HSCROLL)
+ mbHorz = true;
+ else
+ mbHorz = false;
+
+ Control::ImplInit( pParent, nStyle, nullptr );
+}
+
+SpinButton::SpinButton( vcl::Window* pParent, WinBits nStyle )
+ : Control(WindowType::SPINBUTTON)
+ , maRepeatTimer("SpinButton maRepeatTimer")
+ , mbUpperIsFocused(false)
+{
+ ImplInit(pParent, nStyle);
+}
+
+IMPL_LINK(SpinButton, ImplTimeout, Timer*, pTimer, void)
+{
+ if (pTimer->GetTimeout() == static_cast<sal_uInt64>(MouseSettings::GetButtonStartRepeat()))
+ {
+ pTimer->SetTimeout( GetSettings().GetMouseSettings().GetButtonRepeat() );
+ pTimer->Start();
+ }
+ else
+ {
+ if (mbInitialUp)
+ Up();
+ else
+ Down();
+ }
+}
+
+void SpinButton::Up()
+{
+ if (ImplIsUpperEnabled())
+ {
+ mnValue += mnValueStep;
+ CompatStateChanged(StateChangedType::Data);
+
+ ImplMoveFocus(true);
+ }
+
+ ImplCallEventListenersAndHandler(VclEventId::SpinbuttonUp, nullptr );
+}
+
+void SpinButton::Down()
+{
+ if (ImplIsLowerEnabled())
+ {
+ mnValue -= mnValueStep;
+ CompatStateChanged(StateChangedType::Data);
+
+ ImplMoveFocus(false);
+ }
+
+ ImplCallEventListenersAndHandler(VclEventId::SpinbuttonDown, nullptr );
+}
+
+void SpinButton::Resize()
+{
+ Control::Resize();
+
+ Size aSize(GetOutputSizePixel());
+ tools::Rectangle aRect(Point(), aSize);
+ if (mbHorz)
+ {
+ maLowerRect = tools::Rectangle(0, 0, aSize.Width() / 2, aSize.Height() - 1);
+ maUpperRect = tools::Rectangle(maLowerRect.TopRight(), aRect.BottomRight());
+ }
+ else
+ {
+ maUpperRect = tools::Rectangle(0, 0, aSize.Width() - 1, aSize.Height() / 2);
+ maLowerRect = tools::Rectangle(maUpperRect.BottomLeft(), aRect.BottomRight());
+ }
+
+ ImplCalcFocusRect(ImplIsUpperEnabled() || !ImplIsLowerEnabled());
+
+ Invalidate();
+}
+
+void SpinButton::Draw(OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags)
+{
+ Point aPos = pDev->LogicToPixel(rPos);
+ Size aSize = GetSizePixel();
+
+ pDev->Push();
+ pDev->SetMapMode();
+ if ( !(nFlags & SystemTextColorFlags::Mono) )
+ {
+ // DecoView uses the FaceColor...
+ AllSettings aSettings = pDev->GetSettings();
+ StyleSettings aStyleSettings = aSettings.GetStyleSettings();
+ if ( IsControlBackground() )
+ aStyleSettings.SetFaceColor( GetControlBackground() );
+ else
+ aStyleSettings.SetFaceColor( GetSettings().GetStyleSettings().GetFaceColor() );
+
+ aSettings.SetStyleSettings( aStyleSettings );
+ pDev->SetSettings( aSettings );
+ }
+
+ tools::Rectangle aRect( Point( 0, 0 ), aSize );
+ tools::Rectangle aLowerRect, aUpperRect;
+ if ( mbHorz )
+ {
+ aLowerRect = tools::Rectangle( 0, 0, aSize.Width()/2, aSize.Height()-1 );
+ aUpperRect = tools::Rectangle( aLowerRect.TopRight(), aRect.BottomRight() );
+ }
+ else
+ {
+ aUpperRect = tools::Rectangle( 0, 0, aSize.Width()-1, aSize.Height()/2 );
+ aLowerRect = tools::Rectangle( aUpperRect.BottomLeft(), aRect.BottomRight() );
+ }
+
+ aUpperRect += aPos;
+ aLowerRect += aPos;
+
+ ImplDrawSpinButton(*pDev, this, aUpperRect, aLowerRect, false, false,
+ IsEnabled() && ImplIsUpperEnabled(),
+ IsEnabled() && ImplIsLowerEnabled(), mbHorz, true);
+ pDev->Pop();
+}
+
+void SpinButton::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
+{
+ HideFocus();
+
+ bool bEnable = IsEnabled();
+ ImplDrawSpinButton(rRenderContext, this, maUpperRect, maLowerRect, mbUpperIn, mbLowerIn,
+ bEnable && ImplIsUpperEnabled(),
+ bEnable && ImplIsLowerEnabled(), mbHorz, true);
+
+ if (HasFocus())
+ ShowFocus(maFocusRect);
+}
+
+void SpinButton::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( maUpperRect.Contains( rMEvt.GetPosPixel() ) && ( ImplIsUpperEnabled() ) )
+ {
+ mbUpperIn = true;
+ mbInitialUp = true;
+ Invalidate( maUpperRect );
+ }
+ else if ( maLowerRect.Contains( rMEvt.GetPosPixel() ) && ( ImplIsLowerEnabled() ) )
+ {
+ mbLowerIn = true;
+ mbInitialDown = true;
+ Invalidate( maLowerRect );
+ }
+
+ if ( mbUpperIn || mbLowerIn )
+ {
+ CaptureMouse();
+ if ( mbRepeat )
+ maRepeatTimer.Start();
+ }
+}
+
+void SpinButton::MouseButtonUp( const MouseEvent& )
+{
+ ReleaseMouse();
+ if ( mbRepeat )
+ {
+ maRepeatTimer.Stop();
+ maRepeatTimer.SetTimeout(MouseSettings::GetButtonStartRepeat() );
+ }
+
+ if ( mbUpperIn )
+ {
+ mbUpperIn = false;
+ Invalidate( maUpperRect );
+ Up();
+ }
+ else if ( mbLowerIn )
+ {
+ mbLowerIn = false;
+ Invalidate( maLowerRect );
+ Down();
+ }
+
+ mbInitialUp = mbInitialDown = false;
+}
+
+void SpinButton::MouseMove( const MouseEvent& rMEvt )
+{
+ if ( !rMEvt.IsLeft() || (!mbInitialUp && !mbInitialDown) )
+ return;
+
+ if ( !maUpperRect.Contains( rMEvt.GetPosPixel() ) &&
+ mbUpperIn && mbInitialUp )
+ {
+ mbUpperIn = false;
+ maRepeatTimer.Stop();
+ Invalidate( maUpperRect );
+ }
+ else if ( !maLowerRect.Contains( rMEvt.GetPosPixel() ) &&
+ mbLowerIn && mbInitialDown )
+ {
+ mbLowerIn = false;
+ maRepeatTimer.Stop();
+ Invalidate( maLowerRect );
+ }
+ else if ( maUpperRect.Contains( rMEvt.GetPosPixel() ) &&
+ !mbUpperIn && mbInitialUp )
+ {
+ mbUpperIn = true;
+ if ( mbRepeat )
+ maRepeatTimer.Start();
+ Invalidate( maUpperRect );
+ }
+ else if ( maLowerRect.Contains( rMEvt.GetPosPixel() ) &&
+ !mbLowerIn && mbInitialDown )
+ {
+ mbLowerIn = true;
+ if ( mbRepeat )
+ maRepeatTimer.Start();
+ Invalidate( maLowerRect );
+ }
+}
+
+void SpinButton::KeyInput( const KeyEvent& rKEvt )
+{
+ if ( !rKEvt.GetKeyCode().GetModifier() )
+ {
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ {
+ bool bUp = KEY_RIGHT == rKEvt.GetKeyCode().GetCode();
+ if ( mbHorz && !ImplMoveFocus( bUp ) )
+ bUp ? Up() : Down();
+ }
+ break;
+
+ case KEY_UP:
+ case KEY_DOWN:
+ {
+ bool bUp = KEY_UP == rKEvt.GetKeyCode().GetCode();
+ if ( !mbHorz && !ImplMoveFocus( KEY_UP == rKEvt.GetKeyCode().GetCode() ) )
+ bUp ? Up() : Down();
+ }
+ break;
+
+ case KEY_SPACE:
+ mbUpperIsFocused ? Up() : Down();
+ break;
+
+ default:
+ Control::KeyInput( rKEvt );
+ break;
+ }
+ }
+ else
+ Control::KeyInput( rKEvt );
+}
+
+void SpinButton::StateChanged( StateChangedType nType )
+{
+ switch ( nType )
+ {
+ case StateChangedType::Data:
+ case StateChangedType::Enable:
+ Invalidate();
+ break;
+
+ case StateChangedType::Style:
+ {
+ bool bNewRepeat = 0 != ( GetStyle() & WB_REPEAT );
+ if ( bNewRepeat != mbRepeat )
+ {
+ if ( maRepeatTimer.IsActive() )
+ {
+ maRepeatTimer.Stop();
+ maRepeatTimer.SetTimeout( MouseSettings::GetButtonStartRepeat() );
+ }
+ mbRepeat = bNewRepeat;
+ }
+
+ bool bNewHorz = 0 != ( GetStyle() & WB_HSCROLL );
+ if ( bNewHorz != mbHorz )
+ {
+ mbHorz = bNewHorz;
+ Resize();
+ }
+ }
+ break;
+ default:;
+ }
+
+ Control::StateChanged( nType );
+}
+
+void SpinButton::SetRangeMin( tools::Long nNewRange )
+{
+ SetRange( Range( nNewRange, GetRangeMax() ) );
+}
+
+void SpinButton::SetRangeMax( tools::Long nNewRange )
+{
+ SetRange( Range( GetRangeMin(), nNewRange ) );
+}
+
+void SpinButton::SetRange( const Range& rRange )
+{
+ // adjust rage
+ Range aRange = rRange;
+ aRange.Justify();
+ tools::Long nNewMinRange = aRange.Min();
+ tools::Long nNewMaxRange = aRange.Max();
+
+ // do something only if old and new range differ
+ if ( (mnMinRange == nNewMinRange) && (mnMaxRange == nNewMaxRange))
+ return;
+
+ mnMinRange = nNewMinRange;
+ mnMaxRange = nNewMaxRange;
+
+ // adjust value to new range, if necessary
+ if ( mnValue > mnMaxRange )
+ mnValue = mnMaxRange;
+ if ( mnValue < mnMinRange )
+ mnValue = mnMinRange;
+
+ CompatStateChanged( StateChangedType::Data );
+}
+
+void SpinButton::SetValue( tools::Long nValue )
+{
+ // adjust, if necessary
+ if ( nValue > mnMaxRange )
+ nValue = mnMaxRange;
+ if ( nValue < mnMinRange )
+ nValue = mnMinRange;
+
+ if ( mnValue != nValue )
+ {
+ mnValue = nValue;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+void SpinButton::GetFocus()
+{
+ ShowFocus( maFocusRect );
+ Control::GetFocus();
+}
+
+void SpinButton::LoseFocus()
+{
+ HideFocus();
+ Control::LoseFocus();
+}
+
+bool SpinButton::ImplMoveFocus( bool _bUpper )
+{
+ if ( _bUpper == mbUpperIsFocused )
+ return false;
+
+ HideFocus();
+ ImplCalcFocusRect( _bUpper );
+ if ( HasFocus() )
+ ShowFocus( maFocusRect );
+ return true;
+}
+
+void SpinButton::ImplCalcFocusRect( bool _bUpper )
+{
+ maFocusRect = _bUpper ? maUpperRect : maLowerRect;
+ // inflate by some pixels
+ maFocusRect.AdjustLeft(2 );
+ maFocusRect.AdjustTop(2 );
+ maFocusRect.AdjustRight( -2 );
+ maFocusRect.AdjustBottom( -2 );
+ mbUpperIsFocused = _bUpper;
+}
+
+tools::Rectangle* SpinButton::ImplFindPartRect( const Point& rPt )
+{
+ if( maUpperRect.Contains( rPt ) )
+ return &maUpperRect;
+ else if( maLowerRect.Contains( rPt ) )
+ return &maLowerRect;
+ else
+ return nullptr;
+}
+
+bool SpinButton::PreNotify( NotifyEvent& rNEvt )
+{
+ if (rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE)
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if (pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged())
+ {
+ // trigger redraw if mouse over state has changed
+ if (IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire) ||
+ IsNativeControlSupported(ControlType::Spinbox, ControlPart::AllButtons) )
+ {
+ tools::Rectangle* pRect = ImplFindPartRect( GetPointerPosPixel() );
+ tools::Rectangle* pLastRect = ImplFindPartRect( GetLastPointerPosPixel() );
+ if (pRect != pLastRect || (pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow()))
+ {
+ vcl::Region aRgn(GetOutDev()->GetActiveClipRegion());
+ if (pLastRect)
+ {
+ GetOutDev()->SetClipRegion(vcl::Region(*pLastRect));
+ Invalidate(*pLastRect);
+ GetOutDev()->SetClipRegion( aRgn );
+ }
+ if (pRect)
+ {
+ GetOutDev()->SetClipRegion(vcl::Region(*pRect));
+ Invalidate(*pRect);
+ GetOutDev()->SetClipRegion(aRgn);
+ }
+ }
+ }
+ }
+ }
+
+ return Control::PreNotify(rNEvt);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/spinfld.cxx b/vcl/source/control/spinfld.cxx
new file mode 100644
index 000000000..6bc1fac29
--- /dev/null
+++ b/vcl/source/control/spinfld.cxx
@@ -0,0 +1,1034 @@
+/* -*- 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 <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/toolkit/spinfld.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <sal/log.hxx>
+
+#include <spin.hxx>
+#include <svdata.hxx>
+
+namespace {
+
+void ImplGetSpinbuttonValue(vcl::Window* pWin,
+ const tools::Rectangle& rUpperRect, const tools::Rectangle& rLowerRect,
+ bool bUpperIn, bool bLowerIn, bool bUpperEnabled, bool bLowerEnabled,
+ bool bHorz, SpinbuttonValue& rValue )
+{
+ // convert spinbutton data to a SpinbuttonValue structure for native painting
+
+ rValue.maUpperRect = rUpperRect;
+ rValue.maLowerRect = rLowerRect;
+
+ Point aPointerPos = pWin->GetPointerPosPixel();
+
+ ControlState nState = ControlState::ENABLED;
+ if (bUpperIn)
+ nState |= ControlState::PRESSED;
+ if (!pWin->IsEnabled() || !bUpperEnabled)
+ nState &= ~ControlState::ENABLED;
+ if (pWin->HasFocus())
+ nState |= ControlState::FOCUSED;
+ if (pWin->IsMouseOver() && rUpperRect.Contains(aPointerPos))
+ nState |= ControlState::ROLLOVER;
+ rValue.mnUpperState = nState;
+
+ nState = ControlState::ENABLED;
+ if (bLowerIn)
+ nState |= ControlState::PRESSED;
+ if (!pWin->IsEnabled() || !bLowerEnabled)
+ nState &= ~ControlState::ENABLED;
+ if (pWin->HasFocus())
+ nState |= ControlState::FOCUSED;
+ // for overlapping spins: highlight only one
+ if (pWin->IsMouseOver() && rLowerRect.Contains(aPointerPos) && !rUpperRect.Contains(aPointerPos))
+ nState |= ControlState::ROLLOVER;
+ rValue.mnLowerState = nState;
+
+ rValue.mnUpperPart = bHorz ? ControlPart::ButtonLeft : ControlPart::ButtonUp;
+ rValue.mnLowerPart = bHorz ? ControlPart::ButtonRight : ControlPart::ButtonDown;
+}
+
+bool ImplDrawNativeSpinfield(vcl::RenderContext& rRenderContext, vcl::Window const * pWin, const SpinbuttonValue& rSpinbuttonValue)
+{
+ bool bNativeOK = false;
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire) &&
+ // there is just no useful native support for spinfields with dropdown
+ !(pWin->GetStyle() & WB_DROPDOWN))
+ {
+ if (rRenderContext.IsNativeControlSupported(ControlType::Spinbox, rSpinbuttonValue.mnUpperPart) &&
+ rRenderContext.IsNativeControlSupported(ControlType::Spinbox, rSpinbuttonValue.mnLowerPart))
+ {
+ // only paint the embedded spin buttons, all buttons are painted at once
+ tools::Rectangle aUpperAndLowerButtons( rSpinbuttonValue.maUpperRect.GetUnion( rSpinbuttonValue.maLowerRect ) );
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Spinbox, ControlPart::AllButtons, aUpperAndLowerButtons,
+ ControlState::ENABLED, rSpinbuttonValue, OUString());
+ }
+ else
+ {
+ // paint the spinbox as a whole, use borderwindow to have proper clipping
+ vcl::Window* pBorder = pWin->GetWindow(GetWindowType::Border);
+
+ // to not overwrite everything, set the button region as clipregion to the border window
+ tools::Rectangle aClipRect(rSpinbuttonValue.maLowerRect);
+ aClipRect.Union(rSpinbuttonValue.maUpperRect);
+
+ vcl::RenderContext* pContext = &rRenderContext;
+ vcl::Region oldRgn;
+ Point aPt;
+ Size aSize(pBorder->GetOutputSizePixel()); // the size of the border window, i.e., the whole control
+ tools::Rectangle aNatRgn(aPt, aSize);
+
+ if (!pWin->SupportsDoubleBuffering())
+ {
+ // convert from screen space to borderwin space
+ aClipRect.SetPos(pBorder->ScreenToOutputPixel(pWin->OutputToScreenPixel(aClipRect.TopLeft())));
+
+ oldRgn = pBorder->GetOutDev()->GetClipRegion();
+ pBorder->GetOutDev()->SetClipRegion(vcl::Region(aClipRect));
+
+ pContext = pBorder->GetOutDev();
+ }
+
+ tools::Rectangle aBound, aContent;
+ if (!ImplGetSVData()->maNWFData.mbCanDrawWidgetAnySize &&
+ pContext->GetNativeControlRegion(ControlType::Spinbox, ControlPart::Entire,
+ aNatRgn, ControlState::NONE, rSpinbuttonValue,
+ aBound, aContent))
+ {
+ aSize = aContent.GetSize();
+ }
+
+ tools::Rectangle aRgn(aPt, aSize);
+ if (pWin->SupportsDoubleBuffering())
+ {
+ // convert from borderwin space, to the pWin's space
+ aRgn.SetPos(pWin->ScreenToOutputPixel(pBorder->OutputToScreenPixel(aRgn.TopLeft())));
+ }
+
+ bNativeOK = pContext->DrawNativeControl(ControlType::Spinbox, ControlPart::Entire, aRgn,
+ ControlState::ENABLED, rSpinbuttonValue, OUString());
+
+ if (!pWin->SupportsDoubleBuffering())
+ pBorder->GetOutDev()->SetClipRegion(oldRgn);
+ }
+ }
+ return bNativeOK;
+}
+
+bool ImplDrawNativeSpinbuttons(vcl::RenderContext& rRenderContext, const SpinbuttonValue& rSpinbuttonValue)
+{
+ bool bNativeOK = false;
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::SpinButtons, ControlPart::Entire))
+ {
+ tools::Rectangle aArea = rSpinbuttonValue.maUpperRect.GetUnion(rSpinbuttonValue.maLowerRect);
+ // only paint the standalone spin buttons, all buttons are painted at once
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::SpinButtons, ControlPart::AllButtons, aArea,
+ ControlState::ENABLED, rSpinbuttonValue, OUString());
+ }
+ return bNativeOK;
+}
+
+}
+
+void ImplDrawSpinButton(vcl::RenderContext& rRenderContext, vcl::Window* pWindow,
+ const tools::Rectangle& rUpperRect, const tools::Rectangle& rLowerRect,
+ bool bUpperIn, bool bLowerIn, bool bUpperEnabled, bool bLowerEnabled,
+ bool bHorz, bool bMirrorHorz)
+{
+ bool bNativeOK = false;
+
+ if (pWindow)
+ {
+ // are we drawing standalone spin buttons or members of a spinfield ?
+ ControlType aControl = ControlType::SpinButtons;
+ switch (pWindow->GetType())
+ {
+ case WindowType::EDIT:
+ case WindowType::MULTILINEEDIT:
+ case WindowType::PATTERNFIELD:
+ case WindowType::METRICFIELD:
+ case WindowType::CURRENCYFIELD:
+ case WindowType::DATEFIELD:
+ case WindowType::TIMEFIELD:
+ case WindowType::SPINFIELD:
+ case WindowType::FORMATTEDFIELD:
+ aControl = ControlType::Spinbox;
+ break;
+ default:
+ aControl = ControlType::SpinButtons;
+ break;
+ }
+
+ SpinbuttonValue aValue;
+ ImplGetSpinbuttonValue(pWindow, rUpperRect, rLowerRect,
+ bUpperIn, bLowerIn, bUpperEnabled, bLowerEnabled,
+ bHorz, aValue);
+
+ if( aControl == ControlType::Spinbox )
+ bNativeOK = ImplDrawNativeSpinfield(rRenderContext, pWindow, aValue);
+ else if( aControl == ControlType::SpinButtons )
+ bNativeOK = ImplDrawNativeSpinbuttons(rRenderContext, aValue);
+ }
+
+ if (bNativeOK)
+ return;
+
+ ImplDrawUpDownButtons(rRenderContext,
+ rUpperRect, rLowerRect,
+ bUpperIn, bLowerIn, bUpperEnabled, bLowerEnabled,
+ bHorz, bMirrorHorz);
+}
+
+void ImplDrawUpDownButtons(vcl::RenderContext& rRenderContext,
+ const tools::Rectangle& rUpperRect, const tools::Rectangle& rLowerRect,
+ bool bUpperIn, bool bLowerIn, bool bUpperEnabled, bool bLowerEnabled,
+ bool bHorz, bool bMirrorHorz)
+{
+ DecorationView aDecoView(&rRenderContext);
+
+ SymbolType eType1, eType2;
+
+ if ( bHorz )
+ {
+ eType1 = bMirrorHorz ? SymbolType::SPIN_RIGHT : SymbolType::SPIN_LEFT;
+ eType2 = bMirrorHorz ? SymbolType::SPIN_LEFT : SymbolType::SPIN_RIGHT;
+ }
+ else
+ {
+ eType1 = SymbolType::SPIN_UP;
+ eType2 = SymbolType::SPIN_DOWN;
+ }
+
+ DrawButtonFlags nStyle = DrawButtonFlags::NoLeftLightBorder;
+ // draw upper/left Button
+ if (bUpperIn)
+ nStyle |= DrawButtonFlags::Pressed;
+
+ tools::Rectangle aUpRect = aDecoView.DrawButton(rUpperRect, nStyle);
+
+ nStyle = DrawButtonFlags::NoLeftLightBorder;
+ // draw lower/right Button
+ if (bLowerIn)
+ nStyle |= DrawButtonFlags::Pressed;
+
+ tools::Rectangle aLowRect = aDecoView.DrawButton(rLowerRect, nStyle);
+
+ // make use of additional default edge
+ aUpRect.AdjustLeft( -1 );
+ aUpRect.AdjustTop( -1 );
+ aUpRect.AdjustRight( 1 );
+ aUpRect.AdjustBottom( 1 );
+ aLowRect.AdjustLeft( -1 );
+ aLowRect.AdjustTop( -1 );
+ aLowRect.AdjustRight( 1 );
+ aLowRect.AdjustBottom( 1 );
+
+ // draw into the edge, so that something is visible if the rectangle is too small
+ if (aUpRect.GetHeight() < 4)
+ {
+ aUpRect.AdjustRight( 1 );
+ aUpRect.AdjustBottom( 1 );
+ aLowRect.AdjustRight( 1 );
+ aLowRect.AdjustBottom( 1 );
+ }
+
+ // calculate Symbol size
+ tools::Long nTempSize1 = aUpRect.GetWidth();
+ tools::Long nTempSize2 = aLowRect.GetWidth();
+ if (std::abs( nTempSize1-nTempSize2 ) == 1)
+ {
+ if (nTempSize1 > nTempSize2)
+ aUpRect.AdjustLeft( 1 );
+ else
+ aLowRect.AdjustLeft( 1 );
+ }
+ nTempSize1 = aUpRect.GetHeight();
+ nTempSize2 = aLowRect.GetHeight();
+ if (std::abs(nTempSize1 - nTempSize2) == 1)
+ {
+ if (nTempSize1 > nTempSize2)
+ aUpRect.AdjustTop( 1 );
+ else
+ aLowRect.AdjustTop( 1 );
+ }
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ DrawSymbolFlags nSymStyle = DrawSymbolFlags::NONE;
+ if (!bUpperEnabled)
+ nSymStyle |= DrawSymbolFlags::Disable;
+ aDecoView.DrawSymbol(aUpRect, eType1, rStyleSettings.GetButtonTextColor(), nSymStyle);
+
+ nSymStyle = DrawSymbolFlags::NONE;
+ if (!bLowerEnabled)
+ nSymStyle |= DrawSymbolFlags::Disable;
+ aDecoView.DrawSymbol(aLowRect, eType2, rStyleSettings.GetButtonTextColor(), nSymStyle);
+}
+
+void SpinField::ImplInitSpinFieldData()
+{
+ mpEdit.disposeAndClear();
+ mbSpin = false;
+ mbRepeat = false;
+ mbUpperIn = false;
+ mbLowerIn = false;
+ mbInitialUp = false;
+ mbInitialDown = false;
+ mbInDropDown = false;
+ mbUpperEnabled = true;
+ mbLowerEnabled = true;
+}
+
+void SpinField::ImplInit(vcl::Window* pParent, WinBits nWinStyle)
+{
+ Edit::ImplInit( pParent, nWinStyle );
+
+ if (!(nWinStyle & (WB_SPIN | WB_DROPDOWN)))
+ return;
+
+ mbSpin = true;
+
+ // Some themes want external spin buttons, therefore the main
+ // spinfield should not overdraw the border between its encapsulated
+ // edit field and the spin buttons
+ if ((nWinStyle & WB_SPIN) && ImplUseNativeBorder(*GetOutDev(), nWinStyle))
+ {
+ SetBackground();
+ mpEdit.set(VclPtr<Edit>::Create(this, WB_NOBORDER));
+ mpEdit->SetBackground();
+ }
+ else
+ mpEdit.set(VclPtr<Edit>::Create(this, WB_NOBORDER));
+
+ mpEdit->EnableRTL(false);
+ mpEdit->SetPosPixel(Point());
+ mpEdit->Show();
+
+ SetSubEdit(mpEdit);
+
+ maRepeatTimer.SetInvokeHandler(LINK( this, SpinField, ImplTimeout));
+ maRepeatTimer.SetTimeout(MouseSettings::GetButtonStartRepeat());
+ if (nWinStyle & WB_REPEAT)
+ mbRepeat = true;
+
+ SetCompoundControl(true);
+}
+
+SpinField::SpinField(vcl::Window* pParent, WinBits nWinStyle, WindowType nType) :
+ Edit(nType), maRepeatTimer("SpinField maRepeatTimer")
+{
+ ImplInitSpinFieldData();
+ ImplInit(pParent, nWinStyle);
+}
+
+SpinField::~SpinField()
+{
+ disposeOnce();
+}
+
+void SpinField::dispose()
+{
+ mpEdit.disposeAndClear();
+
+ Edit::dispose();
+}
+
+void SpinField::Up()
+{
+ ImplCallEventListenersAndHandler( VclEventId::SpinfieldUp, [this] () { maUpHdlLink.Call(*this); } );
+}
+
+void SpinField::Down()
+{
+ ImplCallEventListenersAndHandler( VclEventId::SpinfieldDown, [this] () { maDownHdlLink.Call(*this); } );
+}
+
+void SpinField::First()
+{
+ ImplCallEventListenersAndHandler(VclEventId::SpinfieldFirst, nullptr);
+}
+
+void SpinField::Last()
+{
+ ImplCallEventListenersAndHandler(VclEventId::SpinfieldLast, nullptr);
+}
+
+void SpinField::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if (!HasFocus() && (!mpEdit || !mpEdit->HasFocus()))
+ {
+ GrabFocus();
+ }
+
+ if (!IsReadOnly())
+ {
+ if (maUpperRect.Contains(rMEvt.GetPosPixel()))
+ {
+ mbUpperIn = true;
+ mbInitialUp = true;
+ Invalidate(maUpperRect);
+ }
+ else if (maLowerRect.Contains(rMEvt.GetPosPixel()))
+ {
+ mbLowerIn = true;
+ mbInitialDown = true;
+ Invalidate(maLowerRect);
+ }
+ else if (maDropDownRect.Contains(rMEvt.GetPosPixel()))
+ {
+ // put DropDownButton to the right
+ mbInDropDown = ShowDropDown( !mbInDropDown );
+ Invalidate(tools::Rectangle(Point(), GetOutputSizePixel()));
+ }
+
+ if (mbUpperIn || mbLowerIn)
+ {
+ CaptureMouse();
+ if (mbRepeat)
+ maRepeatTimer.Start();
+ return;
+ }
+ }
+
+ Edit::MouseButtonDown(rMEvt);
+}
+
+void SpinField::MouseButtonUp(const MouseEvent& rMEvt)
+{
+ ReleaseMouse();
+ mbInitialUp = mbInitialDown = false;
+ maRepeatTimer.Stop();
+ maRepeatTimer.SetTimeout(MouseSettings::GetButtonStartRepeat());
+
+ if (mbUpperIn)
+ {
+ mbUpperIn = false;
+ Invalidate(maUpperRect);
+ Up();
+ }
+ else if (mbLowerIn)
+ {
+ mbLowerIn = false;
+ Invalidate(maLowerRect);
+ Down();
+ }
+
+ Edit::MouseButtonUp(rMEvt);
+}
+
+void SpinField::MouseMove(const MouseEvent& rMEvt)
+{
+ if (rMEvt.IsLeft())
+ {
+ if (mbInitialUp)
+ {
+ bool bNewUpperIn = maUpperRect.Contains(rMEvt.GetPosPixel());
+ if (bNewUpperIn != mbUpperIn)
+ {
+ if (bNewUpperIn)
+ {
+ if (mbRepeat)
+ maRepeatTimer.Start();
+ }
+ else
+ maRepeatTimer.Stop();
+
+ mbUpperIn = bNewUpperIn;
+ Invalidate(maUpperRect);
+ }
+ }
+ else if (mbInitialDown)
+ {
+ bool bNewLowerIn = maLowerRect.Contains(rMEvt.GetPosPixel());
+ if (bNewLowerIn != mbLowerIn)
+ {
+ if (bNewLowerIn)
+ {
+ if (mbRepeat)
+ maRepeatTimer.Start();
+ }
+ else
+ maRepeatTimer.Stop();
+
+ mbLowerIn = bNewLowerIn;
+ Invalidate(maLowerRect);
+ }
+ }
+ }
+
+ Edit::MouseMove(rMEvt);
+}
+
+bool SpinField::EventNotify(NotifyEvent& rNEvt)
+{
+ bool bDone = false;
+ if (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT)
+ {
+ const KeyEvent& rKEvt = *rNEvt.GetKeyEvent();
+ if (!IsReadOnly())
+ {
+ sal_uInt16 nMod = rKEvt.GetKeyCode().GetModifier();
+ switch (rKEvt.GetKeyCode().GetCode())
+ {
+ case KEY_UP:
+ {
+ if (!nMod)
+ {
+ Up();
+ bDone = true;
+ }
+ }
+ break;
+ case KEY_DOWN:
+ {
+ if (!nMod)
+ {
+ Down();
+ bDone = true;
+ }
+ else if ((nMod == KEY_MOD2) && !mbInDropDown && (GetStyle() & WB_DROPDOWN))
+ {
+ mbInDropDown = ShowDropDown(true);
+ Invalidate(tools::Rectangle(Point(), GetOutputSizePixel()));
+ bDone = true;
+ }
+ }
+ break;
+ case KEY_PAGEUP:
+ {
+ if (!nMod)
+ {
+ Last();
+ bDone = true;
+ }
+ }
+ break;
+ case KEY_PAGEDOWN:
+ {
+ if (!nMod)
+ {
+ First();
+ bDone = true;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ if (rNEvt.GetType() == MouseNotifyEvent::COMMAND)
+ {
+ if ((rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) && !IsReadOnly())
+ {
+ MouseWheelBehaviour nWheelBehavior(GetSettings().GetMouseSettings().GetWheelBehavior());
+ if (nWheelBehavior == MouseWheelBehaviour::ALWAYS
+ || (nWheelBehavior == MouseWheelBehaviour::FocusOnly && HasChildPathFocus()))
+ {
+ const CommandWheelData* pData = rNEvt.GetCommandEvent()->GetWheelData();
+ if (pData->GetMode() == CommandWheelMode::SCROLL)
+ {
+ if (pData->GetDelta() < 0)
+ Down();
+ else
+ Up();
+ bDone = true;
+
+ if (!HasChildPathFocus())
+ GrabFocus();
+ }
+ }
+ else
+ bDone = false; // don't eat this event, let the default handling happen (i.e. scroll the context)
+ }
+ }
+
+ return bDone || Edit::EventNotify(rNEvt);
+}
+
+void SpinField::FillLayoutData() const
+{
+ if (mbSpin)
+ {
+ mxLayoutData.emplace();
+ AppendLayoutData(*GetSubEdit());
+ GetSubEdit()->SetLayoutDataParent(this);
+ }
+ else
+ Edit::FillLayoutData();
+}
+
+void SpinField::SetUpperEnabled(bool bEnabled)
+{
+ if (mbUpperEnabled == bEnabled)
+ return;
+
+ mbUpperEnabled = bEnabled;
+
+ if (mbSpin)
+ Invalidate(maUpperRect);
+}
+
+void SpinField::SetLowerEnabled(bool bEnabled)
+{
+ if (mbLowerEnabled == bEnabled)
+ return;
+
+ mbLowerEnabled = bEnabled;
+
+ if (mbSpin)
+ Invalidate(maLowerRect);
+}
+
+void SpinField::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (mbSpin)
+ {
+ bool bEnabled = IsEnabled();
+ bool bUpperEnabled = bEnabled && IsUpperEnabled();
+ bool bLowerEnabled = bEnabled && IsLowerEnabled();
+ ImplDrawSpinButton(rRenderContext, this, maUpperRect, maLowerRect,
+ mbUpperIn && bUpperEnabled, mbLowerIn && bLowerEnabled,
+ bUpperEnabled, bLowerEnabled);
+ }
+
+ if (GetStyle() & WB_DROPDOWN)
+ {
+ DecorationView aView(&rRenderContext);
+
+ DrawButtonFlags nStyle = DrawButtonFlags::NoLightBorder;
+ if (mbInDropDown)
+ nStyle |= DrawButtonFlags::Pressed;
+ tools::Rectangle aInnerRect = aView.DrawButton(maDropDownRect, nStyle);
+
+ DrawSymbolFlags nSymbolStyle = IsEnabled() ? DrawSymbolFlags::NONE : DrawSymbolFlags::Disable;
+ aView.DrawSymbol(aInnerRect, SymbolType::SPIN_DOWN, rRenderContext.GetSettings().GetStyleSettings().GetButtonTextColor(), nSymbolStyle);
+ }
+
+ Edit::Paint(rRenderContext, rRect);
+}
+
+void SpinField::ImplCalcButtonAreas(const OutputDevice* pDev, const Size& rOutSz, tools::Rectangle& rDDArea,
+ tools::Rectangle& rSpinUpArea, tools::Rectangle& rSpinDownArea)
+{
+ const StyleSettings& rStyleSettings = pDev->GetSettings().GetStyleSettings();
+
+ Size aSize = rOutSz;
+ Size aDropDownSize;
+
+ if (GetStyle() & WB_DROPDOWN)
+ {
+ tools::Long nW = rStyleSettings.GetScrollBarSize();
+ nW = GetDrawPixel( pDev, nW );
+ aDropDownSize = Size( CalcZoom( nW ), aSize.Height() );
+ aSize.AdjustWidth( -(aDropDownSize.Width()) );
+ rDDArea = tools::Rectangle( Point( aSize.Width(), 0 ), aDropDownSize );
+ rDDArea.AdjustTop( -1 );
+ }
+ else
+ rDDArea.SetEmpty();
+
+ // calculate sizes according to the height
+ if (GetStyle() & WB_SPIN)
+ {
+ tools::Long nBottom1 = aSize.Height()/2;
+ tools::Long nBottom2 = aSize.Height()-1;
+ tools::Long nTop2 = nBottom1;
+ if ( !(aSize.Height() & 0x01) )
+ nBottom1--;
+
+ bool bNativeRegionOK = false;
+ tools::Rectangle aContentUp, aContentDown;
+
+ if ((pDev->GetOutDevType() == OUTDEV_WINDOW) &&
+ // there is just no useful native support for spinfields with dropdown
+ ! (GetStyle() & WB_DROPDOWN) &&
+ IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire))
+ {
+ vcl::Window *pWin = pDev->GetOwnerWindow();
+ vcl::Window *pBorder = pWin->GetWindow( GetWindowType::Border );
+
+ // get the system's spin button size
+ ImplControlValue aControlValue;
+ tools::Rectangle aBound;
+ Point aPoint;
+
+ // use the full extent of the control
+ tools::Rectangle aArea( aPoint, pBorder->GetOutputSizePixel() );
+
+ bNativeRegionOK =
+ pWin->GetNativeControlRegion(ControlType::Spinbox, ControlPart::ButtonUp,
+ aArea, ControlState::NONE, aControlValue, aBound, aContentUp) &&
+ pWin->GetNativeControlRegion(ControlType::Spinbox, ControlPart::ButtonDown,
+ aArea, ControlState::NONE, aControlValue, aBound, aContentDown);
+
+ if (bNativeRegionOK)
+ {
+ // convert back from border space to local coordinates
+ aPoint = pBorder->ScreenToOutputPixel( pWin->OutputToScreenPixel( aPoint ) );
+ aContentUp.Move(-aPoint.X(), -aPoint.Y());
+ aContentDown.Move(-aPoint.X(), -aPoint.Y());
+ }
+ }
+
+ if (bNativeRegionOK)
+ {
+ rSpinUpArea = aContentUp;
+ rSpinDownArea = aContentDown;
+ }
+ else
+ {
+ aSize.AdjustWidth( -(CalcZoom( GetDrawPixel( pDev, rStyleSettings.GetSpinSize() ) )) );
+
+ rSpinUpArea = tools::Rectangle( aSize.Width(), 0, rOutSz.Width()-aDropDownSize.Width()-1, nBottom1 );
+ rSpinDownArea = tools::Rectangle( rSpinUpArea.Left(), nTop2, rSpinUpArea.Right(), nBottom2 );
+ }
+ }
+ else
+ {
+ rSpinUpArea.SetEmpty();
+ rSpinDownArea.SetEmpty();
+ }
+}
+
+void SpinField::Resize()
+{
+ if (!mbSpin)
+ return;
+
+ Control::Resize();
+ Size aSize = GetOutputSizePixel();
+ bool bSubEditPositioned = false;
+
+ if (GetStyle() & (WB_SPIN | WB_DROPDOWN))
+ {
+ ImplCalcButtonAreas( GetOutDev(), aSize, maDropDownRect, maUpperRect, maLowerRect );
+
+ ImplControlValue aControlValue;
+ Point aPoint;
+ tools::Rectangle aContent, aBound;
+
+ // use the full extent of the control
+ vcl::Window *pBorder = GetWindow( GetWindowType::Border );
+ tools::Rectangle aArea( aPoint, pBorder->GetOutputSizePixel() );
+
+ // adjust position and size of the edit field
+ if (GetNativeControlRegion(ControlType::Spinbox, ControlPart::SubEdit, aArea, ControlState::NONE,
+ aControlValue, aBound, aContent) &&
+ // there is just no useful native support for spinfields with dropdown
+ !(GetStyle() & WB_DROPDOWN))
+ {
+ // convert back from border space to local coordinates
+ aPoint = pBorder->ScreenToOutputPixel(OutputToScreenPixel(aPoint));
+ aContent.Move(-aPoint.X(), -aPoint.Y());
+
+ // use the themes drop down size
+ mpEdit->SetPosPixel( aContent.TopLeft() );
+ bSubEditPositioned = true;
+ aSize = aContent.GetSize();
+ }
+ else
+ {
+ if (maUpperRect.IsEmpty())
+ {
+ SAL_WARN_IF( maDropDownRect.IsEmpty(), "vcl", "SpinField::Resize: SPIN && DROPDOWN, but all empty rects?" );
+ aSize.setWidth( maDropDownRect.Left() );
+ }
+ else
+ aSize.setWidth( maUpperRect.Left() );
+ }
+ }
+
+ if (!bSubEditPositioned)
+ {
+ // this moves our sub edit if RTL gets switched
+ mpEdit->SetPosPixel(Point());
+ }
+ mpEdit->SetSizePixel(aSize);
+
+ if (GetStyle() & WB_SPIN)
+ Invalidate(tools::Rectangle(maUpperRect.TopLeft(), maLowerRect.BottomRight()));
+ if (GetStyle() & WB_DROPDOWN)
+ Invalidate(maDropDownRect);
+}
+
+void SpinField::StateChanged(StateChangedType nType)
+{
+ Edit::StateChanged(nType);
+
+ if (nType == StateChangedType::Enable)
+ {
+ if (mbSpin || (GetStyle() & WB_DROPDOWN))
+ {
+ mpEdit->Enable(IsEnabled());
+
+ if (mbSpin)
+ {
+ Invalidate(maLowerRect);
+ Invalidate(maUpperRect);
+ }
+ if (GetStyle() & WB_DROPDOWN)
+ Invalidate(maDropDownRect);
+ }
+ }
+ else if (nType == StateChangedType::Style)
+ {
+ if (GetStyle() & WB_REPEAT)
+ mbRepeat = true;
+ else
+ mbRepeat = false;
+ }
+ else if (nType == StateChangedType::Zoom)
+ {
+ Resize();
+ if (mpEdit)
+ mpEdit->SetZoom(GetZoom());
+ Invalidate();
+ }
+ else if (nType == StateChangedType::ControlFont)
+ {
+ if (mpEdit)
+ mpEdit->SetControlFont(GetControlFont());
+ Invalidate();
+ }
+ else if (nType == StateChangedType::ControlForeground)
+ {
+ if (mpEdit)
+ mpEdit->SetControlForeground(GetControlForeground());
+ Invalidate();
+ }
+ else if (nType == StateChangedType::ControlBackground)
+ {
+ if (mpEdit)
+ mpEdit->SetControlBackground(GetControlBackground());
+ Invalidate();
+ }
+ else if( nType == StateChangedType::Mirroring )
+ {
+ if (mpEdit)
+ mpEdit->CompatStateChanged(StateChangedType::Mirroring);
+ Resize();
+ }
+}
+
+void SpinField::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Edit::DataChanged(rDCEvt);
+
+ if ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))
+ {
+ Resize();
+ Invalidate();
+ }
+}
+
+tools::Rectangle* SpinField::ImplFindPartRect(const Point& rPt)
+{
+ if (maUpperRect.Contains(rPt))
+ return &maUpperRect;
+ else if (maLowerRect.Contains(rPt))
+ return &maLowerRect;
+ else
+ return nullptr;
+}
+
+bool SpinField::PreNotify(NotifyEvent& rNEvt)
+{
+ if (rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE)
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if (pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged())
+ {
+ // trigger redraw if mouse over state has changed
+ if( IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire) ||
+ IsNativeControlSupported(ControlType::Spinbox, ControlPart::AllButtons) )
+ {
+ tools::Rectangle* pRect = ImplFindPartRect( GetPointerPosPixel() );
+ tools::Rectangle* pLastRect = ImplFindPartRect( GetLastPointerPosPixel() );
+ if( pRect != pLastRect || (pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow()) )
+ {
+ // FIXME: this is currently only on macOS
+ // check for other platforms that need similar handling
+ if (ImplGetSVData()->maNWFData.mbNoFocusRects && IsNativeWidgetEnabled() &&
+ IsNativeControlSupported(ControlType::Editbox, ControlPart::Entire))
+ {
+ ImplInvalidateOutermostBorder(this);
+ }
+ else
+ {
+ // paint directly
+ vcl::Region aRgn( GetOutDev()->GetActiveClipRegion() );
+ if (pLastRect)
+ {
+ GetOutDev()->SetClipRegion(vcl::Region(*pLastRect));
+ Invalidate(*pLastRect);
+ GetOutDev()->SetClipRegion( aRgn );
+ }
+ if (pRect)
+ {
+ GetOutDev()->SetClipRegion(vcl::Region(*pRect));
+ Invalidate(*pRect);
+ GetOutDev()->SetClipRegion( aRgn );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return Edit::PreNotify(rNEvt);
+}
+
+void SpinField::EndDropDown()
+{
+ mbInDropDown = false;
+ Invalidate(tools::Rectangle(Point(), GetOutputSizePixel()));
+}
+
+bool SpinField::ShowDropDown( bool )
+{
+ return false;
+}
+
+Size SpinField::CalcMinimumSizeForText(const OUString &rString) const
+{
+ Size aSz = Edit::CalcMinimumSizeForText(rString);
+
+ if ( GetStyle() & WB_DROPDOWN )
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ if ( GetStyle() & WB_SPIN )
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aArea( Point(), Size(100, aSz.Height()));
+ tools::Rectangle aEntireBound, aEntireContent, aEditBound, aEditContent;
+ if (
+ GetNativeControlRegion(ControlType::Spinbox, ControlPart::Entire,
+ aArea, ControlState::NONE, aControlValue, aEntireBound, aEntireContent) &&
+ GetNativeControlRegion(ControlType::Spinbox, ControlPart::SubEdit,
+ aArea, ControlState::NONE, aControlValue, aEditBound, aEditContent)
+ )
+ {
+ aSz.AdjustWidth(aEntireContent.GetWidth() - aEditContent.GetWidth());
+ }
+ else
+ {
+ aSz.AdjustWidth(maUpperRect.GetWidth() );
+ }
+ }
+
+ return aSz;
+}
+
+Size SpinField::CalcMinimumSize() const
+{
+ return CalcMinimumSizeForText(GetText());
+}
+
+Size SpinField::GetOptimalSize() const
+{
+ return CalcMinimumSize();
+}
+
+Size SpinField::CalcSize(sal_Int32 nChars) const
+{
+ Size aSz = Edit::CalcSize( nChars );
+
+ if ( GetStyle() & WB_DROPDOWN )
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ if ( GetStyle() & WB_SPIN )
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetSpinSize() );
+
+ return aSz;
+}
+
+IMPL_LINK( SpinField, ImplTimeout, Timer*, pTimer, void )
+{
+ if ( pTimer->GetTimeout() == static_cast<sal_uInt64>(MouseSettings::GetButtonStartRepeat()) )
+ {
+ pTimer->SetTimeout( GetSettings().GetMouseSettings().GetButtonRepeat() );
+ pTimer->Start();
+ }
+ else
+ {
+ if ( mbInitialUp )
+ Up();
+ else
+ Down();
+ }
+}
+
+void SpinField::Draw(OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags)
+{
+ Edit::Draw(pDev, rPos, nFlags);
+
+ WinBits nFieldStyle = GetStyle();
+ if ( (nFlags & SystemTextColorFlags::NoControls ) || !( nFieldStyle & (WB_SPIN|WB_DROPDOWN) ) )
+ return;
+
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ AllSettings aOldSettings = pDev->GetSettings();
+
+ pDev->Push();
+ pDev->SetMapMode();
+
+ tools::Rectangle aDD, aUp, aDown;
+ ImplCalcButtonAreas(pDev, aSize, aDD, aUp, aDown);
+ aDD.Move(aPos.X(), aPos.Y());
+ aUp.Move(aPos.X(), aPos.Y());
+ aUp.AdjustTop( 1 );
+ aDown.Move(aPos.X(), aPos.Y());
+
+ Color aButtonTextColor;
+ if (nFlags & SystemTextColorFlags::Mono)
+ aButtonTextColor = COL_BLACK;
+ else
+ aButtonTextColor = GetSettings().GetStyleSettings().GetButtonTextColor();
+
+ if (GetStyle() & WB_DROPDOWN)
+ {
+ DecorationView aView( pDev );
+ tools::Rectangle aInnerRect = aView.DrawButton( aDD, DrawButtonFlags::NoLightBorder );
+ DrawSymbolFlags nSymbolStyle = IsEnabled() ? DrawSymbolFlags::NONE : DrawSymbolFlags::Disable;
+ aView.DrawSymbol(aInnerRect, SymbolType::SPIN_DOWN, aButtonTextColor, nSymbolStyle);
+ }
+
+ if (GetStyle() & WB_SPIN)
+ {
+ ImplDrawSpinButton(*pDev, this, aUp, aDown, false, false);
+ }
+
+ pDev->Pop();
+ pDev->SetSettings(aOldSettings);
+
+}
+
+FactoryFunction SpinField::GetUITestFactory() const
+{
+ return SpinFieldUIObject::create;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/tabctrl.cxx b/vcl/source/control/tabctrl.cxx
new file mode 100644
index 000000000..278fd59cd
--- /dev/null
+++ b/vcl/source/control/tabctrl.cxx
@@ -0,0 +1,2459 @@
+/* -*- 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 <sal/log.hxx>
+
+#include <vcl/notebookbar/notebookbar.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/help.hxx>
+#include <vcl/event.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/tabpage.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <bitmaps.hlst>
+#include <tools/json_writer.hxx>
+
+#include <svdata.hxx>
+#include <window.h>
+
+#include <deque>
+#include <unordered_map>
+#include <vector>
+
+class ImplTabItem final
+{
+ sal_uInt16 m_nId;
+
+public:
+ VclPtr<TabPage> mpTabPage;
+ OUString maText;
+ OUString maFormatText;
+ OUString maHelpText;
+ OUString maAccessibleName;
+ OUString maAccessibleDescription;
+ OString maTabName;
+ tools::Rectangle maRect;
+ sal_uInt16 mnLine;
+ bool mbFullVisible;
+ bool m_bEnabled; ///< the tab / page is selectable
+ bool m_bVisible; ///< the tab / page can be visible
+ Image maTabImage;
+
+ ImplTabItem(sal_uInt16 nId);
+
+ sal_uInt16 id() const { return m_nId; }
+};
+
+ImplTabItem::ImplTabItem(sal_uInt16 nId)
+ : m_nId(nId)
+ , mnLine(0)
+ , mbFullVisible(false)
+ , m_bEnabled(true)
+ , m_bVisible(true)
+{
+}
+
+struct ImplTabCtrlData
+{
+ std::unordered_map< int, int > maLayoutPageIdToLine;
+ std::unordered_map< int, int > maLayoutLineToPageId;
+ std::vector< ImplTabItem > maItemList;
+ VclPtr<ListBox> mpListBox;
+};
+
+// for the Tab positions
+#define TAB_PAGERECT 0xFFFF
+
+void TabControl::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ mbLayoutDirty = true;
+
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ if ( !(nStyle & WB_NODIALOGCONTROL) )
+ nStyle |= WB_DIALOGCONTROL;
+
+ Control::ImplInit( pParent, nStyle, nullptr );
+
+ mnLastWidth = 0;
+ mnLastHeight = 0;
+ mnActPageId = 0;
+ mnCurPageId = 0;
+ mbFormat = true;
+ mbShowTabs = true;
+ mbRestoreHelpId = false;
+ mbSmallInvalidate = false;
+ mpTabCtrlData.reset(new ImplTabCtrlData);
+ mpTabCtrlData->mpListBox = nullptr;
+
+ ImplInitSettings( true );
+
+ if( nStyle & WB_DROPDOWN )
+ {
+ mpTabCtrlData->mpListBox = VclPtr<ListBox>::Create( this, WB_DROPDOWN );
+ mpTabCtrlData->mpListBox->SetPosSizePixel( Point( 0, 0 ), Size( 200, 20 ) );
+ mpTabCtrlData->mpListBox->SetSelectHdl( LINK( this, TabControl, ImplListBoxSelectHdl ) );
+ mpTabCtrlData->mpListBox->Show();
+ }
+
+ // if the tabcontrol is drawn (ie filled) by a native widget, make sure all controls will have transparent background
+ // otherwise they will paint with a wrong background
+ if( IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire) )
+ EnableChildTransparentMode();
+
+ if (pParent && pParent->IsDialog())
+ pParent->AddChildEventListener( LINK( this, TabControl, ImplWindowEventListener ) );
+}
+
+const vcl::Font& TabControl::GetCanonicalFont( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetTabFont();
+}
+
+const Color& TabControl::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetTabTextColor();
+}
+
+void TabControl::ImplInitSettings( bool bBackground )
+{
+ Control::ImplInitSettings();
+
+ if ( !bBackground )
+ return;
+
+ vcl::Window* pParent = GetParent();
+ if ( !IsControlBackground() &&
+ (pParent->IsChildTransparentModeEnabled()
+ || IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire)
+ || IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire) ) )
+
+ {
+ // set transparent mode for NWF tabcontrols to have
+ // the background always cleared properly
+ EnableChildTransparentMode();
+ SetParentClipMode( ParentClipMode::NoClip );
+ SetPaintTransparent( true );
+ SetBackground();
+ ImplGetWindowImpl()->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects;
+ }
+ else
+ {
+ EnableChildTransparentMode( false );
+ SetParentClipMode();
+ SetPaintTransparent( false );
+
+ if ( IsControlBackground() )
+ SetBackground( GetControlBackground() );
+ else
+ SetBackground( pParent->GetBackground() );
+ }
+}
+
+void TabControl::ImplFreeLayoutData()
+{
+ if( HasLayoutData() )
+ {
+ ImplClearLayoutData();
+ mpTabCtrlData->maLayoutPageIdToLine.clear();
+ mpTabCtrlData->maLayoutLineToPageId.clear();
+ }
+}
+
+TabControl::TabControl( vcl::Window* pParent, WinBits nStyle ) :
+ Control( WindowType::TABCONTROL )
+{
+ ImplInit( pParent, nStyle );
+ SAL_INFO( "vcl", "*** TABCONTROL no notabs? " << (( GetStyle() & WB_NOBORDER ) ? "true" : "false") );
+}
+
+TabControl::~TabControl()
+{
+ disposeOnce();
+}
+
+void TabControl::dispose()
+{
+ Window *pParent = GetParent();
+ if (pParent && pParent->IsDialog())
+ GetParent()->RemoveChildEventListener( LINK( this, TabControl, ImplWindowEventListener ) );
+
+ ImplFreeLayoutData();
+
+ // delete TabCtrl data
+ if (mpTabCtrlData)
+ mpTabCtrlData->mpListBox.disposeAndClear();
+ mpTabCtrlData.reset();
+ Control::dispose();
+}
+
+ImplTabItem* TabControl::ImplGetItem( sal_uInt16 nId ) const
+{
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (item.id() == nId)
+ return &item;
+ }
+
+ return nullptr;
+}
+
+Size TabControl::ImplGetItemSize( ImplTabItem* pItem, tools::Long nMaxWidth )
+{
+ pItem->maFormatText = pItem->maText;
+ Size aSize( GetOutDev()->GetCtrlTextWidth( pItem->maFormatText ), GetTextHeight() );
+ Size aImageSize( 0, 0 );
+ if( !!pItem->maTabImage )
+ {
+ aImageSize = pItem->maTabImage.GetSizePixel();
+ if( !pItem->maFormatText.isEmpty() )
+ aImageSize.AdjustWidth(GetTextHeight()/4 );
+ }
+ aSize.AdjustWidth(aImageSize.Width() );
+ if( aImageSize.Height() > aSize.Height() )
+ aSize.setHeight( aImageSize.Height() );
+
+ aSize.AdjustWidth(TAB_TABOFFSET_X*2 );
+ aSize.AdjustHeight(TAB_TABOFFSET_Y*2 );
+
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), aSize );
+ tools::Rectangle aBoundingRgn, aContentRgn;
+ const TabitemValue aControlValue(tools::Rectangle(TAB_TABOFFSET_X, TAB_TABOFFSET_Y,
+ aSize.Width() - TAB_TABOFFSET_X * 2,
+ aSize.Height() - TAB_TABOFFSET_Y * 2));
+ if(GetNativeControlRegion( ControlType::TabItem, ControlPart::Entire, aCtrlRegion,
+ ControlState::ENABLED, aControlValue,
+ aBoundingRgn, aContentRgn ) )
+ {
+ return aContentRgn.GetSize();
+ }
+
+ // For languages with short names (e.g. Chinese), because the space is
+ // normally only one pixel per char
+ if ( pItem->maFormatText.getLength() < TAB_EXTRASPACE_X )
+ aSize.AdjustWidth(TAB_EXTRASPACE_X-pItem->maFormatText.getLength() );
+
+ // shorten Text if needed
+ if ( aSize.Width()+4 >= nMaxWidth )
+ {
+ OUString aAppendStr("...");
+ pItem->maFormatText += aAppendStr;
+ do
+ {
+ if (pItem->maFormatText.getLength() > aAppendStr.getLength())
+ pItem->maFormatText = pItem->maFormatText.replaceAt( pItem->maFormatText.getLength()-aAppendStr.getLength()-1, 1, u"" );
+ aSize.setWidth( GetOutDev()->GetCtrlTextWidth( pItem->maFormatText ) );
+ aSize.AdjustWidth(aImageSize.Width() );
+ aSize.AdjustWidth(TAB_TABOFFSET_X*2 );
+ }
+ while ( (aSize.Width()+4 >= nMaxWidth) && (pItem->maFormatText.getLength() > aAppendStr.getLength()) );
+ if ( aSize.Width()+4 >= nMaxWidth )
+ {
+ pItem->maFormatText = ".";
+ aSize.setWidth( 1 );
+ }
+ }
+
+ if( pItem->maFormatText.isEmpty() )
+ {
+ if( aSize.Height() < aImageSize.Height()+4 ) //leave space for focus rect
+ aSize.setHeight( aImageSize.Height()+4 );
+ }
+
+ return aSize;
+}
+
+// Feel free to move this to some more general place for reuse
+// http://en.wikipedia.org/wiki/Word_wrap#Minimum_raggedness
+// Mostly based on Alexey Frunze's nifty example at
+// http://stackoverflow.com/questions/9071205/balanced-word-wrap-minimum-raggedness-in-php
+namespace MinimumRaggednessWrap
+{
+ static std::deque<size_t> GetEndOfLineIndexes(const std::vector<sal_Int32>& rWidthsOf, sal_Int32 nLineWidth)
+ {
+ ++nLineWidth;
+
+ size_t nWidthsCount = rWidthsOf.size();
+ std::vector<sal_Int32> aCosts(nWidthsCount * nWidthsCount);
+
+ // cost function c(i, j) that computes the cost of a line consisting of
+ // the words Word[i] to Word[j]
+ for (size_t i = 0; i < nWidthsCount; ++i)
+ {
+ for (size_t j = 0; j < nWidthsCount; ++j)
+ {
+ if (j >= i)
+ {
+ sal_Int32 c = nLineWidth - (j - i);
+ for (size_t k = i; k <= j; ++k)
+ c -= rWidthsOf[k];
+ c = (c >= 0) ? c * c : SAL_MAX_INT32;
+ aCosts[j * nWidthsCount + i] = c;
+ }
+ else
+ {
+ aCosts[j * nWidthsCount + i] = SAL_MAX_INT32;
+ }
+ }
+ }
+
+ std::vector<sal_Int32> aFunction(nWidthsCount);
+ std::vector<sal_Int32> aWrapPoints(nWidthsCount);
+
+ // f(j) in aFunction[], collect wrap points in aWrapPoints[]
+ for (size_t j = 0; j < nWidthsCount; ++j)
+ {
+ aFunction[j] = aCosts[j * nWidthsCount];
+ if (aFunction[j] == SAL_MAX_INT32)
+ {
+ for (size_t k = 0; k < j; ++k)
+ {
+ sal_Int32 s;
+ if (aFunction[k] == SAL_MAX_INT32 || aCosts[j * nWidthsCount + k + 1] == SAL_MAX_INT32)
+ s = SAL_MAX_INT32;
+ else
+ s = aFunction[k] + aCosts[j * nWidthsCount + k + 1];
+ if (aFunction[j] > s)
+ {
+ aFunction[j] = s;
+ aWrapPoints[j] = k + 1;
+ }
+ }
+ }
+ }
+
+ std::deque<size_t> aSolution;
+
+ // no solution
+ if (aFunction[nWidthsCount - 1] == SAL_MAX_INT32)
+ return aSolution;
+
+ // optimal solution
+ size_t j = nWidthsCount - 1;
+ while (true)
+ {
+ aSolution.push_front(j);
+ if (!aWrapPoints[j])
+ break;
+ j = aWrapPoints[j] - 1;
+ }
+
+ return aSolution;
+ }
+};
+
+static void lcl_AdjustSingleLineTabs(tools::Long nMaxWidth, ImplTabCtrlData *pTabCtrlData)
+{
+ if (!ImplGetSVData()->maNWFData.mbCenteredTabs)
+ return;
+
+ int nRightSpace = nMaxWidth; // space left on the right by the tabs
+ for (auto const& item : pTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+ nRightSpace -= item.maRect.Right() - item.maRect.Left();
+ }
+ nRightSpace /= 2;
+
+ for (auto& item : pTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+ item.maRect.AdjustLeft(nRightSpace);
+ item.maRect.AdjustRight(nRightSpace);
+ }
+}
+
+bool TabControl::ImplPlaceTabs( tools::Long nWidth )
+{
+ if ( nWidth <= 0 )
+ return false;
+ if ( mpTabCtrlData->maItemList.empty() )
+ return false;
+
+ tools::Long nMaxWidth = nWidth;
+
+ const tools::Long nOffsetX = 2;
+ const tools::Long nOffsetY = 2;
+
+ //fdo#66435 throw Knuth/Tex minimum raggedness algorithm at the problem
+ //of ugly bare tabs on lines of their own
+
+ //collect widths
+ std::vector<sal_Int32> aWidths;
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+ aWidths.push_back(ImplGetItemSize(&item, nMaxWidth).Width());
+ }
+
+ //aBreakIndexes will contain the indexes of the last tab on each row
+ std::deque<size_t> aBreakIndexes(MinimumRaggednessWrap::GetEndOfLineIndexes(aWidths, nMaxWidth - nOffsetX - 2));
+
+ tools::Long nX = nOffsetX;
+ tools::Long nY = nOffsetY;
+
+ sal_uInt16 nLines = 0;
+ sal_uInt16 nCurLine = 0;
+
+ tools::Long nLineWidthAry[100];
+ sal_uInt16 nLinePosAry[101];
+ nLineWidthAry[0] = 0;
+ nLinePosAry[0] = 0;
+
+ size_t nIndex = 0;
+
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+
+ Size aSize = ImplGetItemSize( &item, nMaxWidth );
+
+ bool bNewLine = false;
+ if (!aBreakIndexes.empty() && nIndex > aBreakIndexes.front())
+ {
+ aBreakIndexes.pop_front();
+ bNewLine = true;
+ }
+
+ if ( bNewLine && (nWidth > 2+nOffsetX) )
+ {
+ if ( nLines == 99 )
+ break;
+
+ nX = nOffsetX;
+ nY += aSize.Height();
+ nLines++;
+ nLineWidthAry[nLines] = 0;
+ nLinePosAry[nLines] = nIndex;
+ }
+
+ tools::Rectangle aNewRect( Point( nX, nY ), aSize );
+ if ( mbSmallInvalidate && (item.maRect != aNewRect) )
+ mbSmallInvalidate = false;
+ item.maRect = aNewRect;
+ item.mnLine = nLines;
+ item.mbFullVisible = true;
+
+ nLineWidthAry[nLines] += aSize.Width();
+ nX += aSize.Width();
+
+ if (item.id() == mnCurPageId)
+ nCurLine = nLines;
+
+ ++nIndex;
+ }
+
+ if (nLines) // two or more lines
+ {
+ tools::Long nLineHeightAry[100];
+ tools::Long nIH = 0;
+ for (const auto& item : mpTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+ nIH = item.maRect.Bottom() - 1;
+ break;
+ }
+
+ for ( sal_uInt16 i = 0; i < nLines+1; i++ )
+ {
+ if ( i <= nCurLine )
+ nLineHeightAry[i] = nIH*(nLines-(nCurLine-i));
+ else
+ nLineHeightAry[i] = nIH*(i-nCurLine-1);
+ }
+
+ nLinePosAry[nLines+1] = static_cast<sal_uInt16>(mpTabCtrlData->maItemList.size());
+
+ tools::Long nDX = 0;
+ tools::Long nModDX = 0;
+ tools::Long nIDX = 0;
+
+ sal_uInt16 i = 0;
+ sal_uInt16 n = 0;
+
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+
+ if ( i == nLinePosAry[n] )
+ {
+ if ( n == nLines+1 )
+ break;
+
+ nIDX = 0;
+ if( nLinePosAry[n+1]-i > 0 )
+ {
+ nDX = ( nWidth - nOffsetX - nLineWidthAry[n] ) / ( nLinePosAry[n+1] - i );
+ nModDX = ( nWidth - nOffsetX - nLineWidthAry[n] ) % ( nLinePosAry[n+1] - i );
+ }
+ else
+ {
+ // FIXME: this is a case of tabctrl way too small
+ nDX = 0;
+ nModDX = 0;
+ }
+ n++;
+ }
+
+ item.maRect.AdjustLeft(nIDX );
+ item.maRect.AdjustRight(nIDX + nDX );
+ item.maRect.SetTop( nLineHeightAry[n-1] );
+ item.maRect.SetBottom(nLineHeightAry[n-1] + nIH - 1);
+ nIDX += nDX;
+
+ if ( nModDX )
+ {
+ nIDX++;
+ item.maRect.AdjustRight( 1 );
+ nModDX--;
+ }
+
+ i++;
+ }
+ }
+ else // only one line
+ lcl_AdjustSingleLineTabs(nMaxWidth, mpTabCtrlData.get());
+
+ return true;
+}
+
+tools::Rectangle TabControl::ImplGetTabRect( sal_uInt16 nItemPos, tools::Long nWidth, tools::Long nHeight )
+{
+ Size aWinSize = Control::GetOutputSizePixel();
+ if ( nWidth < 0 )
+ nWidth = aWinSize.Width();
+ if ( nHeight < 0 )
+ nHeight = aWinSize.Height();
+
+ if ( mpTabCtrlData->maItemList.empty() )
+ {
+ tools::Long nW = nWidth-TAB_OFFSET*2;
+ tools::Long nH = nHeight-TAB_OFFSET*2;
+ return (nW > 0 && nH > 0)
+ ? tools::Rectangle(Point(TAB_OFFSET, TAB_OFFSET), Size(nW, nH))
+ : tools::Rectangle();
+ }
+
+ if ( nItemPos == TAB_PAGERECT )
+ {
+ sal_uInt16 nLastPos;
+ if ( mnCurPageId )
+ nLastPos = GetPagePos( mnCurPageId );
+ else
+ nLastPos = 0;
+
+ tools::Rectangle aRect = ImplGetTabRect( nLastPos, nWidth, nHeight );
+ if (aRect.IsEmpty())
+ return aRect;
+
+ // with show-tabs of true (the usual) the page rect is from under the
+ // visible tab to the bottom of the TabControl, otherwise it extends
+ // from the top of the TabControl
+ tools::Long nTabBottom = mbShowTabs ? aRect.Bottom() : 0;
+
+ tools::Long nW = nWidth-TAB_OFFSET*2;
+ tools::Long nH = nHeight - nTabBottom - TAB_OFFSET*2;
+ return (nW > 0 && nH > 0)
+ ? tools::Rectangle( Point( TAB_OFFSET, nTabBottom + TAB_OFFSET ), Size( nW, nH ) )
+ : tools::Rectangle();
+ }
+
+ ImplTabItem* const pItem = (nItemPos < mpTabCtrlData->maItemList.size())
+ ? &mpTabCtrlData->maItemList[nItemPos] : nullptr;
+ return ImplGetTabRect(pItem, nWidth, nHeight);
+}
+
+tools::Rectangle TabControl::ImplGetTabRect(const ImplTabItem* pItem, tools::Long nWidth, tools::Long nHeight)
+{
+ if ((nWidth <= 1) || (nHeight <= 0) || !pItem || !pItem->m_bVisible)
+ return tools::Rectangle();
+
+ nWidth -= 1;
+
+ if ( mbFormat || (mnLastWidth != nWidth) || (mnLastHeight != nHeight) )
+ {
+ vcl::Font aFont( GetFont() );
+ aFont.SetTransparent( true );
+ SetFont( aFont );
+
+ bool bRet = ImplPlaceTabs( nWidth );
+ if ( !bRet )
+ return tools::Rectangle();
+
+ mnLastWidth = nWidth;
+ mnLastHeight = nHeight;
+ mbFormat = false;
+ }
+
+ return pItem->maRect;
+}
+
+void TabControl::ImplChangeTabPage( sal_uInt16 nId, sal_uInt16 nOldId )
+{
+ ImplFreeLayoutData();
+
+ ImplTabItem* pOldItem = ImplGetItem( nOldId );
+ ImplTabItem* pItem = ImplGetItem( nId );
+ TabPage* pOldPage = pOldItem ? pOldItem->mpTabPage.get() : nullptr;
+ TabPage* pPage = pItem ? pItem->mpTabPage.get() : nullptr;
+ vcl::Window* pCtrlParent = GetParent();
+
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ sal_uInt16 nPos = GetPagePos( nId );
+ tools::Rectangle aRect = ImplGetTabRect( nPos );
+
+ if ( !pOldItem || !pItem || (pOldItem->mnLine != pItem->mnLine) )
+ {
+ aRect.SetLeft( 0 );
+ aRect.SetTop( 0 );
+ aRect.SetRight( Control::GetOutputSizePixel().Width() );
+ }
+ else
+ {
+ aRect.AdjustLeft( -3 );
+ aRect.AdjustTop( -2 );
+ aRect.AdjustRight(3 );
+ Invalidate( aRect );
+ nPos = GetPagePos( nOldId );
+ aRect = ImplGetTabRect( nPos );
+ aRect.AdjustLeft( -3 );
+ aRect.AdjustTop( -2 );
+ aRect.AdjustRight(3 );
+ }
+ Invalidate( aRect );
+ }
+
+ if ( pOldPage == pPage )
+ return;
+
+ tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
+
+ if ( pOldPage )
+ {
+ if ( mbRestoreHelpId )
+ pCtrlParent->SetHelpId( OString() );
+ }
+
+ if ( pPage )
+ {
+ if ( GetStyle() & WB_NOBORDER )
+ {
+ tools::Rectangle aRectNoTab(Point(0, 0), GetSizePixel());
+ pPage->SetPosSizePixel( aRectNoTab.TopLeft(), aRectNoTab.GetSize() );
+ }
+ else
+ pPage->SetPosSizePixel( aRect.TopLeft(), aRect.GetSize() );
+
+ // activate page here so the controls can be switched
+ // also set the help id of the parent window to that of the tab page
+ if ( GetHelpId().isEmpty() )
+ {
+ mbRestoreHelpId = true;
+ pCtrlParent->SetHelpId( pPage->GetHelpId() );
+ }
+
+ pPage->Show();
+
+ if ( pOldPage && pOldPage->HasChildPathFocus() )
+ {
+ vcl::Window* pFirstChild = pPage->ImplGetDlgWindow( 0, GetDlgWindowType::First );
+ if ( pFirstChild )
+ pFirstChild->ImplControlFocus( GetFocusFlags::Init );
+ else
+ GrabFocus();
+ }
+ }
+
+ if ( pOldPage )
+ pOldPage->Hide();
+
+ // Invalidate the same region that will be send to NWF
+ // to always allow for bitmap caching
+ // see Window::DrawNativeControl()
+ if( IsNativeControlSupported( ControlType::TabPane, ControlPart::Entire ) )
+ {
+ aRect.AdjustLeft( -(TAB_OFFSET) );
+ aRect.AdjustTop( -(TAB_OFFSET) );
+ aRect.AdjustRight(TAB_OFFSET );
+ aRect.AdjustBottom(TAB_OFFSET );
+ }
+
+ Invalidate( aRect );
+}
+
+bool TabControl::ImplPosCurTabPage()
+{
+ // resize/position current TabPage
+ ImplTabItem* pItem = ImplGetItem( GetCurPageId() );
+ if ( pItem && pItem->mpTabPage )
+ {
+ if ( GetStyle() & WB_NOBORDER )
+ {
+ tools::Rectangle aRectNoTab(Point(0, 0), GetSizePixel());
+ pItem->mpTabPage->SetPosSizePixel( aRectNoTab.TopLeft(), aRectNoTab.GetSize() );
+ return true;
+ }
+ tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
+ pItem->mpTabPage->SetPosSizePixel( aRect.TopLeft(), aRect.GetSize() );
+ return true;
+ }
+
+ return false;
+}
+
+void TabControl::ImplActivateTabPage( bool bNext )
+{
+ sal_uInt16 nCurPos = GetPagePos( GetCurPageId() );
+
+ if ( bNext )
+ nCurPos = (nCurPos + 1) % GetPageCount();
+ else
+ {
+ if ( !nCurPos )
+ nCurPos = GetPageCount()-1;
+ else
+ nCurPos--;
+ }
+
+ SelectTabPage( GetPageId( nCurPos ) );
+}
+
+void TabControl::ImplShowFocus()
+{
+ if ( !GetPageCount() || mpTabCtrlData->mpListBox )
+ return;
+
+ sal_uInt16 nCurPos = GetPagePos( mnCurPageId );
+ tools::Rectangle aRect = ImplGetTabRect( nCurPos );
+ const ImplTabItem& rItem = mpTabCtrlData->maItemList[ nCurPos ];
+ Size aTabSize = aRect.GetSize();
+ Size aImageSize( 0, 0 );
+ tools::Long nTextHeight = GetTextHeight();
+ tools::Long nTextWidth = GetOutDev()->GetCtrlTextWidth( rItem.maFormatText );
+ sal_uInt16 nOff;
+
+ if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::Mono) )
+ nOff = 1;
+ else
+ nOff = 0;
+
+ if( !! rItem.maTabImage )
+ {
+ aImageSize = rItem.maTabImage.GetSizePixel();
+ if( !rItem.maFormatText.isEmpty() )
+ aImageSize.AdjustWidth(GetTextHeight()/4 );
+ }
+
+ if( !rItem.maFormatText.isEmpty() )
+ {
+ // show focus around text
+ aRect.SetLeft( aRect.Left()+aImageSize.Width()+((aTabSize.Width()-nTextWidth-aImageSize.Width())/2)-nOff-1-1 );
+ aRect.SetTop( aRect.Top()+((aTabSize.Height()-nTextHeight)/2)-1-1 );
+ aRect.SetRight( aRect.Left()+nTextWidth+2 );
+ aRect.SetBottom( aRect.Top()+nTextHeight+2 );
+ }
+ else
+ {
+ // show focus around image
+ tools::Long nXPos = aRect.Left()+((aTabSize.Width()-nTextWidth-aImageSize.Width())/2)-nOff-1;
+ tools::Long nYPos = aRect.Top();
+ if( aImageSize.Height() < aRect.GetHeight() )
+ nYPos += (aRect.GetHeight() - aImageSize.Height())/2;
+
+ aRect.SetLeft( nXPos - 2 );
+ aRect.SetTop( nYPos - 2 );
+ aRect.SetRight( aRect.Left() + aImageSize.Width() + 4 );
+ aRect.SetBottom( aRect.Top() + aImageSize.Height() + 4 );
+ }
+ ShowFocus( aRect );
+}
+
+void TabControl::ImplDrawItem(vcl::RenderContext& rRenderContext, ImplTabItem const * pItem, const tools::Rectangle& rCurRect,
+ bool bFirstInGroup, bool bLastInGroup )
+{
+ if (!pItem->m_bVisible || pItem->maRect.IsEmpty())
+ return;
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ tools::Rectangle aRect = pItem->maRect;
+ tools::Long nLeftBottom = aRect.Bottom();
+ tools::Long nRightBottom = aRect.Bottom();
+ bool bLeftBorder = true;
+ bool bRightBorder = true;
+ sal_uInt16 nOff;
+ bool bNativeOK = false;
+
+ sal_uInt16 nOff2 = 0;
+ sal_uInt16 nOff3 = 0;
+
+ if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
+ nOff = 1;
+ else
+ nOff = 0;
+
+ // if this is the active Page, we have to draw a little more
+ if (pItem->id() == mnCurPageId)
+ {
+ nOff2 = 2;
+ if (!ImplGetSVData()->maNWFData.mbNoActiveTabTextRaise)
+ nOff3 = 1;
+ }
+ else
+ {
+ Point aLeftTestPos = aRect.BottomLeft();
+ Point aRightTestPos = aRect.BottomRight();
+ if (aLeftTestPos.Y() == rCurRect.Bottom())
+ {
+ aLeftTestPos.AdjustX( -2 );
+ if (rCurRect.Contains(aLeftTestPos))
+ bLeftBorder = false;
+ aRightTestPos.AdjustX(2 );
+ if (rCurRect.Contains(aRightTestPos))
+ bRightBorder = false;
+ }
+ else
+ {
+ if (rCurRect.Contains(aLeftTestPos))
+ nLeftBottom -= 2;
+ if (rCurRect.Contains(aRightTestPos))
+ nRightBottom -= 2;
+ }
+ }
+
+ ControlState nState = ControlState::NONE;
+
+ if (pItem->id() == mnCurPageId)
+ {
+ nState |= ControlState::SELECTED;
+ // only the selected item can be focused
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+ }
+ if (IsEnabled())
+ nState |= ControlState::ENABLED;
+ if (IsMouseOver() && pItem->maRect.Contains(GetPointerPosPixel()))
+ {
+ nState |= ControlState::ROLLOVER;
+ for (auto const& item : mpTabCtrlData->maItemList)
+ if ((&item != pItem) && item.m_bVisible && item.maRect.Contains(GetPointerPosPixel()))
+ {
+ nState &= ~ControlState::ROLLOVER; // avoid multiple highlighted tabs
+ break;
+ }
+ assert(nState & ControlState::ROLLOVER);
+ }
+
+ bNativeOK = rRenderContext.IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire);
+ if ( bNativeOK )
+ {
+ TabitemValue tiValue(tools::Rectangle(pItem->maRect.Left() + TAB_TABOFFSET_X,
+ pItem->maRect.Top() + TAB_TABOFFSET_Y,
+ pItem->maRect.Right() - TAB_TABOFFSET_X,
+ pItem->maRect.Bottom() - TAB_TABOFFSET_Y));
+ if (pItem->maRect.Left() < 5)
+ tiValue.mnAlignment |= TabitemFlags::LeftAligned;
+ if (pItem->maRect.Right() > mnLastWidth - 5)
+ tiValue.mnAlignment |= TabitemFlags::RightAligned;
+ if (bFirstInGroup)
+ tiValue.mnAlignment |= TabitemFlags::FirstInGroup;
+ if (bLastInGroup)
+ tiValue.mnAlignment |= TabitemFlags::LastInGroup;
+
+ tools::Rectangle aCtrlRegion( pItem->maRect );
+ aCtrlRegion.AdjustBottom(TabPaneValue::m_nOverlap);
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::TabItem, ControlPart::Entire,
+ aCtrlRegion, nState, tiValue, OUString() );
+ }
+
+ if (!bNativeOK)
+ {
+ if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ rRenderContext.DrawPixel(Point(aRect.Left() + 1 - nOff2, aRect.Top() + 1 - nOff2)); // diagonally indented top-left pixel
+ if (bLeftBorder)
+ {
+ rRenderContext.DrawLine(Point(aRect.Left() - nOff2, aRect.Top() + 2 - nOff2),
+ Point(aRect.Left() - nOff2, nLeftBottom - 1));
+ }
+ rRenderContext.DrawLine(Point(aRect.Left() + 2 - nOff2, aRect.Top() - nOff2), // top line starting 2px from left border
+ Point(aRect.Right() + nOff2 - 3, aRect.Top() - nOff2)); // ending 3px from right border
+
+ if (bRightBorder)
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 2, aRect.Top() + 1 - nOff2),
+ Point(aRect.Right() + nOff2 - 2, nRightBottom - 1));
+
+ rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
+ rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 1, aRect.Top() + 3 - nOff2),
+ Point(aRect.Right() + nOff2 - 1, nRightBottom - 1));
+ }
+ }
+ else
+ {
+ rRenderContext.SetLineColor(COL_BLACK);
+ rRenderContext.DrawPixel(Point(aRect.Left() + 1 - nOff2, aRect.Top() + 1 - nOff2));
+ rRenderContext.DrawPixel(Point(aRect.Right() + nOff2 - 2, aRect.Top() + 1 - nOff2));
+ if (bLeftBorder)
+ {
+ rRenderContext.DrawLine(Point(aRect.Left() - nOff2, aRect.Top() + 2 - nOff2),
+ Point(aRect.Left() - nOff2, nLeftBottom - 1));
+ }
+ rRenderContext.DrawLine(Point(aRect.Left() + 2 - nOff2, aRect.Top() - nOff2),
+ Point(aRect.Right() - 3, aRect.Top() - nOff2));
+ if (bRightBorder)
+ {
+ rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 1, aRect.Top() + 2 - nOff2),
+ Point(aRect.Right() + nOff2 - 1, nRightBottom - 1));
+ }
+ }
+ }
+
+ // set font accordingly, current item is painted bold
+ // we set the font attributes always before drawing to be re-entrant (DrawNativeControl may trigger additional paints)
+ vcl::Font aFont(rRenderContext.GetFont());
+ aFont.SetTransparent(true);
+ rRenderContext.SetFont(aFont);
+
+ Size aTabSize = aRect.GetSize();
+ Size aImageSize(0, 0);
+ tools::Long nTextHeight = rRenderContext.GetTextHeight();
+ tools::Long nTextWidth = rRenderContext.GetCtrlTextWidth(pItem->maFormatText);
+ if (!!pItem->maTabImage)
+ {
+ aImageSize = pItem->maTabImage.GetSizePixel();
+ if (!pItem->maFormatText.isEmpty())
+ aImageSize.AdjustWidth(GetTextHeight() / 4 );
+ }
+ tools::Long nXPos = aRect.Left() + ((aTabSize.Width() - nTextWidth - aImageSize.Width()) / 2) - nOff - nOff3;
+ tools::Long nYPos = aRect.Top() + ((aTabSize.Height() - nTextHeight) / 2) - nOff3;
+ if (!pItem->maFormatText.isEmpty())
+ {
+ DrawTextFlags nStyle = DrawTextFlags::Mnemonic;
+ if (!pItem->m_bEnabled)
+ nStyle |= DrawTextFlags::Disable;
+
+ Color aColor(rStyleSettings.GetTabTextColor());
+ if (nState & ControlState::SELECTED)
+ aColor = rStyleSettings.GetTabHighlightTextColor();
+ else if (nState & ControlState::ROLLOVER)
+ aColor = rStyleSettings.GetTabRolloverTextColor();
+
+ Color aOldColor(rRenderContext.GetTextColor());
+ rRenderContext.SetTextColor(aColor);
+
+ const tools::Rectangle aOutRect(nXPos + aImageSize.Width(), nYPos,
+ nXPos + aImageSize.Width() + nTextWidth, nYPos + nTextHeight);
+ DrawControlText(rRenderContext, aOutRect, pItem->maFormatText, nStyle,
+ nullptr, nullptr);
+
+ rRenderContext.SetTextColor(aOldColor);
+ }
+
+ if (!!pItem->maTabImage)
+ {
+ Point aImgTL( nXPos, aRect.Top() );
+ if (aImageSize.Height() < aRect.GetHeight())
+ aImgTL.AdjustY((aRect.GetHeight() - aImageSize.Height()) / 2 );
+ rRenderContext.DrawImage(aImgTL, pItem->maTabImage, pItem->m_bEnabled ? DrawImageFlags::NONE : DrawImageFlags::Disable );
+ }
+}
+
+bool TabControl::ImplHandleKeyEvent( const KeyEvent& rKeyEvent )
+{
+ bool bRet = false;
+
+ if ( GetPageCount() > 1 )
+ {
+ vcl::KeyCode aKeyCode = rKeyEvent.GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+
+ if ( aKeyCode.IsMod1() )
+ {
+ if ( aKeyCode.IsShift() || (nKeyCode == KEY_PAGEUP) )
+ {
+ if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEUP) )
+ {
+ ImplActivateTabPage( false );
+ bRet = true;
+ }
+ }
+ else
+ {
+ if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEDOWN) )
+ {
+ ImplActivateTabPage( true );
+ bRet = true;
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+IMPL_LINK_NOARG(TabControl, ImplListBoxSelectHdl, ListBox&, void)
+{
+ SelectTabPage( GetPageId( mpTabCtrlData->mpListBox->GetSelectedEntryPos() ) );
+}
+
+IMPL_LINK( TabControl, ImplWindowEventListener, VclWindowEvent&, rEvent, void )
+{
+ if ( rEvent.GetId() == VclEventId::WindowKeyInput )
+ {
+ // Do not handle events from TabControl or its children, which is done in Notify(), where the events can be consumed.
+ if ( !IsWindowOrChild( rEvent.GetWindow() ) )
+ {
+ KeyEvent* pKeyEvent = static_cast< KeyEvent* >(rEvent.GetData());
+ ImplHandleKeyEvent( *pKeyEvent );
+ }
+ }
+}
+
+void TabControl::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if (mpTabCtrlData->mpListBox || !rMEvt.IsLeft())
+ return;
+
+ ImplTabItem *pItem = ImplGetItem(rMEvt.GetPosPixel());
+ if (pItem && pItem->m_bEnabled)
+ SelectTabPage(pItem->id());
+}
+
+void TabControl::KeyInput( const KeyEvent& rKEvt )
+{
+ if( mpTabCtrlData->mpListBox )
+ mpTabCtrlData->mpListBox->KeyInput( rKEvt );
+ else if ( GetPageCount() > 1 )
+ {
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+
+ if ( (nKeyCode == KEY_LEFT) || (nKeyCode == KEY_RIGHT) )
+ {
+ bool bNext = (nKeyCode == KEY_RIGHT);
+ ImplActivateTabPage( bNext );
+ }
+ }
+
+ Control::KeyInput( rKEvt );
+}
+
+static bool lcl_canPaint(const vcl::RenderContext& rRenderContext, const tools::Rectangle& rDrawRect,
+ const tools::Rectangle& rItemRect)
+{
+ vcl::Region aClipRgn(rRenderContext.GetActiveClipRegion());
+ aClipRgn.Intersect(rItemRect);
+ if (!rDrawRect.IsEmpty())
+ aClipRgn.Intersect(rDrawRect);
+ return !aClipRgn.IsEmpty();
+}
+
+void TabControl::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (GetStyle() & WB_NOBORDER)
+ return;
+
+ Control::Paint(rRenderContext, rRect);
+
+ HideFocus();
+
+ // reformat if needed
+ tools::Rectangle aRect = ImplGetTabRect(TAB_PAGERECT);
+
+ // find current item
+ ImplTabItem* pCurItem = nullptr;
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (item.id() == mnCurPageId)
+ {
+ pCurItem = &item;
+ break;
+ }
+ }
+
+ // Draw the TabPage border
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ tools::Rectangle aCurRect;
+ aRect.AdjustLeft( -(TAB_OFFSET) );
+ aRect.AdjustTop( -(TAB_OFFSET) );
+ aRect.AdjustRight(TAB_OFFSET );
+ aRect.AdjustBottom(TAB_OFFSET );
+
+ // if we have an invisible tabpage or no tabpage at all the tabpage rect should be
+ // increased to avoid round corners that might be drawn by a theme
+ // in this case we're only interested in the top border of the tabpage because the tabitems are used
+ // standalone (eg impress)
+ bool bNoTabPage = false;
+ TabPage* pCurPage = pCurItem ? pCurItem->mpTabPage.get() : nullptr;
+ if (!pCurPage || !pCurPage->IsVisible())
+ {
+ bNoTabPage = true;
+ aRect.AdjustLeft( -10 );
+ aRect.AdjustRight(10 );
+ }
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire))
+ {
+ const bool bPaneWithHeader = mbShowTabs && rRenderContext.IsNativeControlSupported(ControlType::TabPane, ControlPart::TabPaneWithHeader);
+ tools::Rectangle aHeaderRect(aRect.Left(), 0, aRect.Right(), aRect.Top());
+ if (bPaneWithHeader)
+ {
+ aRect.SetTop(0);
+ if (mpTabCtrlData->maItemList.size())
+ {
+ tools::Long nRight = 0;
+ for (const auto &item : mpTabCtrlData->maItemList)
+ if (item.m_bVisible)
+ nRight = item.maRect.Right();
+ assert(nRight);
+ aHeaderRect.SetRight(nRight);
+ }
+ }
+ const TabPaneValue aTabPaneValue(aHeaderRect, pCurItem ? pCurItem->maRect : tools::Rectangle());
+
+ ControlState nState = ControlState::ENABLED;
+ if (!IsEnabled())
+ nState &= ~ControlState::ENABLED;
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+
+ if (lcl_canPaint(rRenderContext, rRect, aRect))
+ rRenderContext.DrawNativeControl(ControlType::TabPane, ControlPart::Entire,
+ aRect, nState, aTabPaneValue, OUString());
+
+ if (!bPaneWithHeader && rRenderContext.IsNativeControlSupported(ControlType::TabHeader, ControlPart::Entire)
+ && lcl_canPaint(rRenderContext, rRect, aHeaderRect))
+ rRenderContext.DrawNativeControl(ControlType::TabHeader, ControlPart::Entire,
+ aHeaderRect, nState, aTabPaneValue, OUString());
+ }
+ else
+ {
+ tools::Long nTopOff = 1;
+ if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ else
+ rRenderContext.SetLineColor(COL_BLACK);
+ if (mbShowTabs && pCurItem && !pCurItem->maRect.IsEmpty())
+ {
+ aCurRect = pCurItem->maRect;
+ rRenderContext.DrawLine(aRect.TopLeft(), Point(aCurRect.Left() - 2, aRect.Top()));
+ if (aCurRect.Right() + 1 < aRect.Right())
+ {
+ rRenderContext.DrawLine(Point(aCurRect.Right(), aRect.Top()), aRect.TopRight());
+ }
+ else
+ {
+ nTopOff = 0;
+ }
+ }
+ else
+ rRenderContext.DrawLine(aRect.TopLeft(), aRect.TopRight());
+
+ rRenderContext.DrawLine(aRect.TopLeft(), aRect.BottomLeft());
+
+ if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
+ {
+ // if we have not tab page the bottom line of the tab page
+ // directly touches the tab items, so choose a color that fits seamlessly
+ if (bNoTabPage)
+ rRenderContext.SetLineColor(rStyleSettings.GetDialogColor());
+ else
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(Point(1, aRect.Bottom() - 1), Point(aRect.Right() - 1, aRect.Bottom() - 1));
+ rRenderContext.DrawLine(Point(aRect.Right() - 1, aRect.Top() + nTopOff), Point(aRect.Right() - 1, aRect.Bottom() - 1));
+ if (bNoTabPage)
+ rRenderContext.SetLineColor(rStyleSettings.GetDialogColor());
+ else
+ rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
+ rRenderContext.DrawLine(Point(0, aRect.Bottom()), Point(aRect.Right(), aRect.Bottom()));
+ rRenderContext.DrawLine(Point(aRect.Right(), aRect.Top() + nTopOff), Point(aRect.Right(), aRect.Bottom()));
+ }
+ else
+ {
+ rRenderContext.DrawLine(aRect.TopRight(), aRect.BottomRight());
+ rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight());
+ }
+ }
+
+ if (mbShowTabs && !mpTabCtrlData->maItemList.empty() && mpTabCtrlData->mpListBox == nullptr)
+ {
+ // Some native toolkits (GTK+) draw tabs right-to-left, with an
+ // overlap between adjacent tabs
+ bool bDrawTabsRTL = rRenderContext.IsNativeControlSupported(ControlType::TabItem, ControlPart::TabsDrawRtl);
+ ImplTabItem* pFirstTab = nullptr;
+ ImplTabItem* pLastTab = nullptr;
+ size_t idx;
+
+ // Even though there is a tab overlap with GTK+, the first tab is not
+ // overlapped on the left side. Other toolkits ignore this option.
+ if (bDrawTabsRTL)
+ {
+ pFirstTab = mpTabCtrlData->maItemList.data();
+ pLastTab = pFirstTab + mpTabCtrlData->maItemList.size() - 1;
+ idx = mpTabCtrlData->maItemList.size() - 1;
+ }
+ else
+ {
+ pLastTab = mpTabCtrlData->maItemList.data();
+ pFirstTab = pLastTab + mpTabCtrlData->maItemList.size() - 1;
+ idx = 0;
+ }
+
+ while (idx < mpTabCtrlData->maItemList.size())
+ {
+ ImplTabItem* pItem = &mpTabCtrlData->maItemList[idx];
+
+ if (pItem != pCurItem && pItem->m_bVisible && lcl_canPaint(rRenderContext, rRect, pItem->maRect))
+ ImplDrawItem(rRenderContext, pItem, aCurRect, pItem == pFirstTab, pItem == pLastTab);
+
+ if (bDrawTabsRTL)
+ idx--;
+ else
+ idx++;
+ }
+
+ if (pCurItem && lcl_canPaint(rRenderContext, rRect, pCurItem->maRect))
+ ImplDrawItem(rRenderContext, pCurItem, aCurRect, pCurItem == pFirstTab, pCurItem == pLastTab);
+ }
+
+ if (HasFocus())
+ ImplShowFocus();
+
+ mbSmallInvalidate = true;
+}
+
+void TabControl::setAllocation(const Size &rAllocation)
+{
+ ImplFreeLayoutData();
+
+ if ( !IsReallyShown() )
+ return;
+
+ if( mpTabCtrlData->mpListBox )
+ {
+ // get the listbox' preferred size
+ Size aTabCtrlSize( GetSizePixel() );
+ tools::Long nPrefWidth = mpTabCtrlData->mpListBox->get_preferred_size().Width();
+ if( nPrefWidth > aTabCtrlSize.Width() )
+ nPrefWidth = aTabCtrlSize.Width();
+ Size aNewSize( nPrefWidth, LogicToPixel( Size( 12, 12 ), MapMode( MapUnit::MapAppFont ) ).Height() );
+ Point aNewPos( (aTabCtrlSize.Width() - nPrefWidth) / 2, 0 );
+ mpTabCtrlData->mpListBox->SetPosSizePixel( aNewPos, aNewSize );
+ }
+
+ mbFormat = true;
+
+ // resize/position active TabPage
+ bool bTabPage = ImplPosCurTabPage();
+
+ // check what needs to be invalidated
+ Size aNewSize = rAllocation;
+ tools::Long nNewWidth = aNewSize.Width();
+ for (auto const& item : mpTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+ if (!item.mbFullVisible || (item.maRect.Right()-2 >= nNewWidth))
+ {
+ mbSmallInvalidate = false;
+ break;
+ }
+ }
+
+ if ( mbSmallInvalidate )
+ {
+ tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
+ aRect.AdjustLeft( -(TAB_OFFSET+TAB_BORDER_LEFT) );
+ aRect.AdjustTop( -(TAB_OFFSET+TAB_BORDER_TOP) );
+ aRect.AdjustRight(TAB_OFFSET+TAB_BORDER_RIGHT );
+ aRect.AdjustBottom(TAB_OFFSET+TAB_BORDER_BOTTOM );
+ if ( bTabPage )
+ Invalidate( aRect, InvalidateFlags::NoChildren );
+ else
+ Invalidate( aRect );
+
+ }
+ else
+ {
+ if ( bTabPage )
+ Invalidate( InvalidateFlags::NoChildren );
+ else
+ Invalidate();
+ }
+
+ mbLayoutDirty = false;
+}
+
+void TabControl::SetPosSizePixel(const Point& rNewPos, const Size& rNewSize)
+{
+ Window::SetPosSizePixel(rNewPos, rNewSize);
+ //if size changed, TabControl::Resize got called already
+ if (mbLayoutDirty)
+ setAllocation(rNewSize);
+}
+
+void TabControl::SetSizePixel(const Size& rNewSize)
+{
+ Window::SetSizePixel(rNewSize);
+ //if size changed, TabControl::Resize got called already
+ if (mbLayoutDirty)
+ setAllocation(rNewSize);
+}
+
+void TabControl::SetPosPixel(const Point& rPos)
+{
+ Window::SetPosPixel(rPos);
+ if (mbLayoutDirty)
+ setAllocation(GetOutputSizePixel());
+}
+
+void TabControl::Resize()
+{
+ setAllocation(Control::GetOutputSizePixel());
+}
+
+void TabControl::GetFocus()
+{
+ if( ! mpTabCtrlData->mpListBox )
+ {
+ if (mbShowTabs)
+ {
+ ImplShowFocus();
+ SetInputContext( InputContext( GetFont() ) );
+ }
+ else
+ {
+ // no tabs, focus first thing in current page
+ ImplTabItem* pItem = ImplGetItem(GetCurPageId());
+ if (pItem && pItem->mpTabPage)
+ {
+ vcl::Window* pFirstChild = pItem->mpTabPage->ImplGetDlgWindow(0, GetDlgWindowType::First);
+ if ( pFirstChild )
+ pFirstChild->ImplControlFocus(GetFocusFlags::Init);
+ }
+ }
+ }
+ else
+ {
+ if( mpTabCtrlData->mpListBox->IsReallyVisible() )
+ mpTabCtrlData->mpListBox->GrabFocus();
+ }
+
+ Control::GetFocus();
+}
+
+void TabControl::LoseFocus()
+{
+ if( mpTabCtrlData && ! mpTabCtrlData->mpListBox )
+ HideFocus();
+ Control::LoseFocus();
+}
+
+void TabControl::RequestHelp( const HelpEvent& rHEvt )
+{
+ sal_uInt16 nItemId = rHEvt.KeyboardActivated() ? mnCurPageId : GetPageId( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) );
+
+ if ( nItemId )
+ {
+ if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
+ {
+ OUString aStr = GetHelpText( nItemId );
+ if ( !aStr.isEmpty() )
+ {
+ tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
+ Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
+ aItemRect.SetLeft( aPt.X() );
+ aItemRect.SetTop( aPt.Y() );
+ aPt = OutputToScreenPixel( aItemRect.BottomRight() );
+ aItemRect.SetRight( aPt.X() );
+ aItemRect.SetBottom( aPt.Y() );
+ Help::ShowBalloon( this, aItemRect.Center(), aItemRect, aStr );
+ return;
+ }
+ }
+
+ // for Quick or Ballon Help, we show the text, if it is cut
+ if ( rHEvt.GetMode() & (HelpEventMode::QUICK | HelpEventMode::BALLOON) )
+ {
+ ImplTabItem* pItem = ImplGetItem( nItemId );
+ const OUString& rStr = pItem->maText;
+ if ( rStr != pItem->maFormatText )
+ {
+ tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
+ Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
+ aItemRect.SetLeft( aPt.X() );
+ aItemRect.SetTop( aPt.Y() );
+ aPt = OutputToScreenPixel( aItemRect.BottomRight() );
+ aItemRect.SetRight( aPt.X() );
+ aItemRect.SetBottom( aPt.Y() );
+ if ( !rStr.isEmpty() )
+ {
+ if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
+ Help::ShowBalloon( this, aItemRect.Center(), aItemRect, rStr );
+ else
+ Help::ShowQuickHelp( this, aItemRect, rStr );
+ return;
+ }
+ }
+ }
+
+ if ( rHEvt.GetMode() & HelpEventMode::QUICK )
+ {
+ ImplTabItem* pItem = ImplGetItem( nItemId );
+ const OUString& rHelpText = pItem->maHelpText;
+ if (!rHelpText.isEmpty())
+ {
+ tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
+ Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
+ aItemRect.SetLeft( aPt.X() );
+ aItemRect.SetTop( aPt.Y() );
+ aPt = OutputToScreenPixel( aItemRect.BottomRight() );
+ aItemRect.SetRight( aPt.X() );
+ aItemRect.SetBottom( aPt.Y() );
+ Help::ShowQuickHelp( this, aItemRect, rHelpText );
+ return;
+ }
+ }
+ }
+
+ Control::RequestHelp( rHEvt );
+}
+
+void TabControl::Command( const CommandEvent& rCEvt )
+{
+ if( (mpTabCtrlData->mpListBox == nullptr) && (rCEvt.GetCommand() == CommandEventId::ContextMenu) && (GetPageCount() > 1) )
+ {
+ Point aMenuPos;
+ bool bMenu;
+ if ( rCEvt.IsMouseEvent() )
+ {
+ aMenuPos = rCEvt.GetMousePosPixel();
+ bMenu = GetPageId( aMenuPos ) != 0;
+ }
+ else
+ {
+ aMenuPos = ImplGetTabRect( GetPagePos( mnCurPageId ) ).Center();
+ bMenu = true;
+ }
+
+ if ( bMenu )
+ {
+ ScopedVclPtrInstance<PopupMenu> aMenu;
+ for (auto const& item : mpTabCtrlData->maItemList)
+ {
+ aMenu->InsertItem(item.id(), item.maText, MenuItemBits::CHECKABLE | MenuItemBits::RADIOCHECK);
+ if (item.id() == mnCurPageId)
+ aMenu->CheckItem(item.id());
+ aMenu->SetHelpId(item.id(), OString());
+ }
+
+ sal_uInt16 nId = aMenu->Execute( this, aMenuPos );
+ if ( nId && (nId != mnCurPageId) )
+ SelectTabPage( nId );
+ return;
+ }
+ }
+
+ Control::Command( rCEvt );
+}
+
+void TabControl::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ {
+ ImplPosCurTabPage();
+ if( mpTabCtrlData->mpListBox )
+ Resize();
+ }
+ else if ( nType == StateChangedType::UpdateMode )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+void TabControl::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+ImplTabItem* TabControl::ImplGetItem(const Point& rPt) const
+{
+ ImplTabItem* pFoundItem = nullptr;
+ int nFound = 0;
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (item.m_bVisible && item.maRect.Contains(rPt))
+ {
+ nFound++;
+ pFoundItem = &item;
+ }
+ }
+
+ // assure that only one tab is highlighted at a time
+ assert(nFound <= 1);
+ return nFound == 1 ? pFoundItem : nullptr;
+}
+
+bool TabControl::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
+ {
+ // trigger redraw if mouse over state has changed
+ if( IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire) )
+ {
+ ImplTabItem *pItem = ImplGetItem(GetPointerPosPixel());
+ ImplTabItem *pLastItem = ImplGetItem(GetLastPointerPosPixel());
+ if ((pItem != pLastItem) || pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
+ {
+ vcl::Region aClipRgn;
+ if (pLastItem)
+ {
+ // allow for slightly bigger tabitems
+ // as used by gtk
+ // TODO: query for the correct sizes
+ tools::Rectangle aRect(pLastItem->maRect);
+ aRect.AdjustLeft( -2 );
+ aRect.AdjustRight(2 );
+ aRect.AdjustTop( -3 );
+ aClipRgn.Union( aRect );
+ }
+
+ if (pItem)
+ {
+ // allow for slightly bigger tabitems
+ // as used by gtk
+ // TODO: query for the correct sizes
+ tools::Rectangle aRect(pItem->maRect);
+ aRect.AdjustLeft( -2 );
+ aRect.AdjustRight(2 );
+ aRect.AdjustTop( -3 );
+ aClipRgn.Union( aRect );
+ }
+
+ if( !aClipRgn.IsEmpty() )
+ Invalidate( aClipRgn );
+ }
+ }
+ }
+ }
+
+ return Control::PreNotify(rNEvt);
+}
+
+bool TabControl::EventNotify( NotifyEvent& rNEvt )
+{
+ bool bRet = false;
+
+ if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ bRet = ImplHandleKeyEvent( *rNEvt.GetKeyEvent() );
+
+ return bRet || Control::EventNotify( rNEvt );
+}
+
+void TabControl::ActivatePage()
+{
+ maActivateHdl.Call( this );
+}
+
+bool TabControl::DeactivatePage()
+{
+ return !maDeactivateHdl.IsSet() || maDeactivateHdl.Call( this );
+}
+
+void TabControl::SetTabPageSizePixel( const Size& rSize )
+{
+ ImplFreeLayoutData();
+
+ Size aNewSize( rSize );
+ aNewSize.AdjustWidth(TAB_OFFSET*2 );
+ tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT,
+ aNewSize.Width(), aNewSize.Height() );
+ aNewSize.AdjustHeight(aRect.Top()+TAB_OFFSET );
+ Window::SetOutputSizePixel( aNewSize );
+}
+
+void TabControl::InsertPage( sal_uInt16 nPageId, const OUString& rText,
+ sal_uInt16 nPos )
+{
+ SAL_WARN_IF( !nPageId, "vcl", "TabControl::InsertPage(): PageId == 0" );
+ SAL_WARN_IF( GetPagePos( nPageId ) != TAB_PAGE_NOTFOUND, "vcl",
+ "TabControl::InsertPage(): PageId already exists" );
+
+ // insert new page item
+ ImplTabItem* pItem = nullptr;
+ if( nPos == TAB_APPEND || size_t(nPos) >= mpTabCtrlData->maItemList.size() )
+ {
+ mpTabCtrlData->maItemList.emplace_back(nPageId);
+ pItem = &mpTabCtrlData->maItemList.back();
+ if( mpTabCtrlData->mpListBox )
+ mpTabCtrlData->mpListBox->InsertEntry( rText );
+ }
+ else
+ {
+ std::vector< ImplTabItem >::iterator new_it =
+ mpTabCtrlData->maItemList.emplace(mpTabCtrlData->maItemList.begin() + nPos, nPageId);
+ pItem = &(*new_it);
+ if( mpTabCtrlData->mpListBox )
+ mpTabCtrlData->mpListBox->InsertEntry( rText, nPos);
+ }
+ if( mpTabCtrlData->mpListBox )
+ {
+ if( ! mnCurPageId )
+ mpTabCtrlData->mpListBox->SelectEntryPos( 0 );
+ mpTabCtrlData->mpListBox->SetDropDownLineCount( mpTabCtrlData->mpListBox->GetEntryCount() );
+ }
+
+ // set current page id
+ if ( !mnCurPageId )
+ mnCurPageId = nPageId;
+
+ // init new page item
+ pItem->maText = rText;
+ pItem->mbFullVisible = false;
+
+ mbFormat = true;
+ if ( IsUpdateMode() )
+ Invalidate();
+
+ ImplFreeLayoutData();
+ if( mpTabCtrlData->mpListBox ) // reposition/resize listbox
+ Resize();
+
+ CallEventListeners( VclEventId::TabpageInserted, reinterpret_cast<void*>(nPageId) );
+}
+
+void TabControl::RemovePage( sal_uInt16 nPageId )
+{
+ sal_uInt16 nPos = GetPagePos( nPageId );
+
+ // does the item exist ?
+ if ( nPos == TAB_PAGE_NOTFOUND )
+ return;
+
+ //remove page item
+ std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin() + nPos;
+ bool bIsCurrentPage = (it->id() == mnCurPageId);
+ mpTabCtrlData->maItemList.erase( it );
+ if( mpTabCtrlData->mpListBox )
+ {
+ mpTabCtrlData->mpListBox->RemoveEntry( nPos );
+ mpTabCtrlData->mpListBox->SetDropDownLineCount( mpTabCtrlData->mpListBox->GetEntryCount() );
+ }
+
+ // If current page is removed, then first page gets the current page
+ if ( bIsCurrentPage )
+ {
+ mnCurPageId = 0;
+
+ if( ! mpTabCtrlData->maItemList.empty() )
+ {
+ // don't do this by simply setting mnCurPageId to pFirstItem->id()
+ // this leaves a lot of stuff (such trivia as _showing_ the new current page) undone
+ // instead, call SetCurPageId
+ // without this, the next (outside) call to SetCurPageId with the id of the first page
+ // will result in doing nothing (as we assume that nothing changed, then), and the page
+ // will never be shown.
+ // 86875 - 05/11/2001 - frank.schoenheit@germany.sun.com
+
+ SetCurPageId(mpTabCtrlData->maItemList[0].id());
+ }
+ }
+
+ mbFormat = true;
+ if ( IsUpdateMode() )
+ Invalidate();
+
+ ImplFreeLayoutData();
+
+ CallEventListeners( VclEventId::TabpageRemoved, reinterpret_cast<void*>(nPageId) );
+}
+
+void TabControl::SetPageEnabled( sal_uInt16 i_nPageId, bool i_bEnable )
+{
+ ImplTabItem* pItem = ImplGetItem( i_nPageId );
+
+ if (!pItem || pItem->m_bEnabled == i_bEnable)
+ return;
+
+ pItem->m_bEnabled = i_bEnable;
+ if (!pItem->m_bVisible)
+ return;
+
+ mbFormat = true;
+ if( mpTabCtrlData->mpListBox )
+ mpTabCtrlData->mpListBox->SetEntryFlags( GetPagePos( i_nPageId ),
+ i_bEnable ? ListBoxEntryFlags::NONE : (ListBoxEntryFlags::DisableSelection | ListBoxEntryFlags::DrawDisabled) );
+
+ // SetCurPageId will change to a valid page
+ if (pItem->id() == mnCurPageId)
+ SetCurPageId( mnCurPageId );
+ else if ( IsUpdateMode() )
+ Invalidate();
+}
+
+void TabControl::SetPageVisible( sal_uInt16 nPageId, bool bVisible )
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ if (!pItem || pItem->m_bVisible == bVisible)
+ return;
+
+ pItem->m_bVisible = bVisible;
+ if (!bVisible)
+ {
+ if (pItem->mbFullVisible)
+ mbSmallInvalidate = false;
+ pItem->mbFullVisible = false;
+ pItem->maRect.SetEmpty();
+ }
+ mbFormat = true;
+
+ // SetCurPageId will change to a valid page
+ if (pItem->id() == mnCurPageId)
+ SetCurPageId(mnCurPageId);
+ else if (IsUpdateMode())
+ Invalidate();
+}
+
+sal_uInt16 TabControl::GetPageCount() const
+{
+ return static_cast<sal_uInt16>(mpTabCtrlData->maItemList.size());
+}
+
+sal_uInt16 TabControl::GetPageId( sal_uInt16 nPos ) const
+{
+ if( size_t(nPos) < mpTabCtrlData->maItemList.size() )
+ return mpTabCtrlData->maItemList[nPos].id();
+ return 0;
+}
+
+sal_uInt16 TabControl::GetPagePos( sal_uInt16 nPageId ) const
+{
+ sal_uInt16 nPos = 0;
+ for (auto const& item : mpTabCtrlData->maItemList)
+ {
+ if (item.id() == nPageId)
+ return nPos;
+ ++nPos;
+ }
+
+ return TAB_PAGE_NOTFOUND;
+}
+
+sal_uInt16 TabControl::GetPageId( const Point& rPos ) const
+{
+ Size winSize = Control::GetOutputSizePixel();
+ const auto &rList = mpTabCtrlData->maItemList;
+ const auto it = std::find_if(rList.begin(), rList.end(), [&rPos, &winSize, this](const auto &item) {
+ return const_cast<TabControl*>(this)->ImplGetTabRect(&item, winSize.Width(), winSize.Height()).Contains(rPos); });
+ return (it != rList.end()) ? it->id() : 0;
+}
+
+sal_uInt16 TabControl::GetPageId( const OString& rName ) const
+{
+ const auto &rList = mpTabCtrlData->maItemList;
+ const auto it = std::find_if(rList.begin(), rList.end(), [&rName](const auto &item) {
+ return item.maTabName == rName; });
+ return (it != rList.end()) ? it->id() : 0;
+}
+
+void TabControl::SetCurPageId( sal_uInt16 nPageId )
+{
+ sal_uInt16 nPos = GetPagePos( nPageId );
+ while (nPos != TAB_PAGE_NOTFOUND && !mpTabCtrlData->maItemList[nPos].m_bEnabled)
+ {
+ nPos++;
+ if( size_t(nPos) >= mpTabCtrlData->maItemList.size() )
+ nPos = 0;
+ if (mpTabCtrlData->maItemList[nPos].id() == nPageId)
+ break;
+ }
+
+ if( nPos == TAB_PAGE_NOTFOUND )
+ return;
+
+ nPageId = mpTabCtrlData->maItemList[nPos].id();
+ if ( nPageId == mnCurPageId )
+ {
+ if ( mnActPageId )
+ mnActPageId = nPageId;
+ return;
+ }
+
+ if ( mnActPageId )
+ mnActPageId = nPageId;
+ else
+ {
+ mbFormat = true;
+ sal_uInt16 nOldId = mnCurPageId;
+ mnCurPageId = nPageId;
+ ImplChangeTabPage( nPageId, nOldId );
+ }
+}
+
+sal_uInt16 TabControl::GetCurPageId() const
+{
+ if ( mnActPageId )
+ return mnActPageId;
+ else
+ return mnCurPageId;
+}
+
+void TabControl::SelectTabPage( sal_uInt16 nPageId )
+{
+ if ( !nPageId || (nPageId == mnCurPageId) )
+ return;
+
+ ImplFreeLayoutData();
+
+ CallEventListeners( VclEventId::TabpageDeactivate, reinterpret_cast<void*>(mnCurPageId) );
+ if ( DeactivatePage() )
+ {
+ mnActPageId = nPageId;
+ ActivatePage();
+ // Page could have been switched by the Activate handler
+ nPageId = mnActPageId;
+ mnActPageId = 0;
+ SetCurPageId( nPageId );
+ if( mpTabCtrlData->mpListBox )
+ mpTabCtrlData->mpListBox->SelectEntryPos( GetPagePos( nPageId ) );
+ CallEventListeners( VclEventId::TabpageActivate, reinterpret_cast<void*>(nPageId) );
+ }
+}
+
+void TabControl::SetTabPage( sal_uInt16 nPageId, TabPage* pTabPage )
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ if ( !pItem || (pItem->mpTabPage.get() == pTabPage) )
+ return;
+
+ if ( pTabPage )
+ {
+ if ( IsDefaultSize() )
+ SetTabPageSizePixel( pTabPage->GetSizePixel() );
+
+ // only set here, so that Resize does not reposition TabPage
+ pItem->mpTabPage = pTabPage;
+ queue_resize();
+
+ if (pItem->id() == mnCurPageId)
+ ImplChangeTabPage(pItem->id(), 0);
+ }
+ else
+ {
+ pItem->mpTabPage = nullptr;
+ queue_resize();
+ }
+}
+
+TabPage* TabControl::GetTabPage( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ if ( pItem )
+ return pItem->mpTabPage;
+ else
+ return nullptr;
+}
+
+void TabControl::SetPageText( sal_uInt16 nPageId, const OUString& rText )
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ if ( !pItem || pItem->maText == rText )
+ return;
+
+ pItem->maText = rText;
+ mbFormat = true;
+ if( mpTabCtrlData->mpListBox )
+ {
+ sal_uInt16 nPos = GetPagePos( nPageId );
+ mpTabCtrlData->mpListBox->RemoveEntry( nPos );
+ mpTabCtrlData->mpListBox->InsertEntry( rText, nPos );
+ }
+ if ( IsUpdateMode() )
+ Invalidate();
+ ImplFreeLayoutData();
+ CallEventListeners( VclEventId::TabpagePageTextChanged, reinterpret_cast<void*>(nPageId) );
+}
+
+OUString const & TabControl::GetPageText( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ assert( pItem );
+
+ return pItem->maText;
+}
+
+void TabControl::SetHelpText( sal_uInt16 nPageId, const OUString& rText )
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ assert( pItem );
+
+ pItem->maHelpText = rText;
+}
+
+const OUString& TabControl::GetHelpText( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ assert( pItem );
+ return pItem->maHelpText;
+}
+
+void TabControl::SetAccessibleName(sal_uInt16 nPageId, const OUString& rName)
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ assert( pItem );
+ pItem->maAccessibleName = rName;
+}
+
+OUString TabControl::GetAccessibleName( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ assert( pItem );
+ if (!pItem->maAccessibleName.isEmpty())
+ return pItem->maAccessibleName;
+ return OutputDevice::GetNonMnemonicString(pItem->maText);
+}
+
+void TabControl::SetAccessibleDescription(sal_uInt16 nPageId, const OUString& rDesc)
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ assert( pItem );
+ pItem->maAccessibleDescription = rDesc;
+}
+
+OUString TabControl::GetAccessibleDescription( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ assert( pItem );
+ if (!pItem->maAccessibleDescription.isEmpty())
+ return pItem->maAccessibleDescription;
+ return pItem->maHelpText;
+}
+
+void TabControl::SetPageName( sal_uInt16 nPageId, const OString& rName ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ if ( pItem )
+ pItem->maTabName = rName;
+}
+
+OString TabControl::GetPageName( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ if (pItem)
+ return pItem->maTabName;
+
+ return OString();
+}
+
+void TabControl::SetPageImage( sal_uInt16 i_nPageId, const Image& i_rImage )
+{
+ ImplTabItem* pItem = ImplGetItem( i_nPageId );
+
+ if ( pItem )
+ {
+ pItem->maTabImage = i_rImage;
+ mbFormat = true;
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+}
+
+tools::Rectangle TabControl::GetCharacterBounds( sal_uInt16 nPageId, tools::Long nIndex ) const
+{
+ tools::Rectangle aRet;
+
+ if( !HasLayoutData() || mpTabCtrlData->maLayoutPageIdToLine.empty() )
+ FillLayoutData();
+
+ if( HasLayoutData() )
+ {
+ std::unordered_map< int, int >::const_iterator it = mpTabCtrlData->maLayoutPageIdToLine.find( static_cast<int>(nPageId) );
+ if( it != mpTabCtrlData->maLayoutPageIdToLine.end() )
+ {
+ Pair aPair = mxLayoutData->GetLineStartEnd( it->second );
+ if( (aPair.B() - aPair.A()) >= nIndex )
+ aRet = mxLayoutData->GetCharacterBounds( aPair.A() + nIndex );
+ }
+ }
+
+ return aRet;
+}
+
+tools::Long TabControl::GetIndexForPoint( const Point& rPoint, sal_uInt16& rPageId ) const
+{
+ tools::Long nRet = -1;
+
+ if( !HasLayoutData() || mpTabCtrlData->maLayoutPageIdToLine.empty() )
+ FillLayoutData();
+
+ if( HasLayoutData() )
+ {
+ int nIndex = mxLayoutData->GetIndexForPoint( rPoint );
+ if( nIndex != -1 )
+ {
+ // what line (->pageid) is this index in ?
+ int nLines = mxLayoutData->GetLineCount();
+ int nLine = -1;
+ while( ++nLine < nLines )
+ {
+ Pair aPair = mxLayoutData->GetLineStartEnd( nLine );
+ if( aPair.A() <= nIndex && aPair.B() >= nIndex )
+ {
+ nRet = nIndex - aPair.A();
+ rPageId = static_cast<sal_uInt16>(mpTabCtrlData->maLayoutLineToPageId[ nLine ]);
+ break;
+ }
+ }
+ }
+ }
+
+ return nRet;
+}
+
+void TabControl::FillLayoutData() const
+{
+ mpTabCtrlData->maLayoutLineToPageId.clear();
+ mpTabCtrlData->maLayoutPageIdToLine.clear();
+ const_cast<TabControl*>(this)->Invalidate();
+}
+
+tools::Rectangle TabControl::GetTabBounds( sal_uInt16 nPageId ) const
+{
+ tools::Rectangle aRet;
+
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ if (pItem && pItem->m_bVisible)
+ aRet = pItem->maRect;
+
+ return aRet;
+}
+
+Size TabControl::ImplCalculateRequisition(sal_uInt16& nHeaderHeight) const
+{
+ Size aOptimalPageSize(0, 0);
+
+ sal_uInt16 nOrigPageId = GetCurPageId();
+ for (auto const& item : mpTabCtrlData->maItemList)
+ {
+ const TabPage *pPage = item.mpTabPage;
+ //it's a real nuisance if the page is not inserted yet :-(
+ //We need to force all tabs to exist to get overall optimal size for dialog
+ if (!pPage)
+ {
+ TabControl *pThis = const_cast<TabControl*>(this);
+ pThis->SetCurPageId(item.id());
+ pThis->ActivatePage();
+ pPage = item.mpTabPage;
+ }
+
+ if (!pPage)
+ continue;
+
+ Size aPageSize(VclContainer::getLayoutRequisition(*pPage));
+
+ if (aPageSize.Width() > aOptimalPageSize.Width())
+ aOptimalPageSize.setWidth( aPageSize.Width() );
+ if (aPageSize.Height() > aOptimalPageSize.Height())
+ aOptimalPageSize.setHeight( aPageSize.Height() );
+ }
+
+ //fdo#61940 If we were forced to activate pages in order to on-demand
+ //create them to get their optimal size, then switch back to the original
+ //page and re-activate it
+ if (nOrigPageId != GetCurPageId())
+ {
+ TabControl *pThis = const_cast<TabControl*>(this);
+ pThis->SetCurPageId(nOrigPageId);
+ pThis->ActivatePage();
+ }
+
+ tools::Long nTabLabelsBottom = 0, nTabLabelsRight = 0;
+ if (mbShowTabs)
+ {
+ for (sal_uInt16 nPos(0), sizeList(static_cast <sal_uInt16> (mpTabCtrlData->maItemList.size()));
+ nPos < sizeList; ++nPos)
+ {
+ TabControl* pThis = const_cast<TabControl*>(this);
+
+ tools::Rectangle aTabRect = pThis->ImplGetTabRect(nPos, aOptimalPageSize.Width(), LONG_MAX);
+ if (aTabRect.Bottom() > nTabLabelsBottom)
+ {
+ nTabLabelsBottom = aTabRect.Bottom();
+ nHeaderHeight = nTabLabelsBottom;
+ }
+ if (!aTabRect.IsEmpty() && aTabRect.Right() > nTabLabelsRight)
+ nTabLabelsRight = aTabRect.Right();
+ }
+ }
+
+ Size aOptimalSize(aOptimalPageSize);
+ aOptimalSize.AdjustHeight(nTabLabelsBottom );
+ aOptimalSize.setWidth( std::max(nTabLabelsRight, aOptimalSize.Width()) );
+
+ aOptimalSize.AdjustWidth(TAB_OFFSET * 2 );
+ aOptimalSize.AdjustHeight(TAB_OFFSET * 2 );
+
+ return aOptimalSize;
+}
+
+Size TabControl::calculateRequisition() const
+{
+ sal_uInt16 nHeaderHeight;
+ return ImplCalculateRequisition(nHeaderHeight);
+}
+
+Size TabControl::GetOptimalSize() const
+{
+ return calculateRequisition();
+}
+
+void TabControl::queue_resize(StateChangedType eReason)
+{
+ mbLayoutDirty = true;
+ Window::queue_resize(eReason);
+}
+
+std::vector<sal_uInt16> TabControl::GetPageIDs() const
+{
+ std::vector<sal_uInt16> aIDs;
+ for (auto const& item : mpTabCtrlData->maItemList)
+ {
+ aIDs.push_back(item.id());
+ }
+
+ return aIDs;
+}
+
+bool TabControl::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "show-tabs")
+ {
+ mbShowTabs = toBool(rValue);
+ queue_resize();
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+FactoryFunction TabControl::GetUITestFactory() const
+{
+ return TabControlUIObject::create;
+}
+
+void TabControl::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ rJsonWriter.put("id", get_id());
+ rJsonWriter.put("type", "tabcontrol");
+ rJsonWriter.put("selected", GetCurPageId());
+
+ {
+ auto childrenNode = rJsonWriter.startArray("children");
+ for (int i = 0; i < GetChildCount(); i++)
+ {
+ vcl::Window* pChild = GetChild(i);
+
+ if (pChild)
+ {
+ auto childNode = rJsonWriter.startStruct();
+ pChild->DumpAsPropertyTree(rJsonWriter);
+
+ if (!pChild->IsVisible())
+ rJsonWriter.put("hidden", "true");
+ }
+ }
+ }
+ {
+ auto tabsNode = rJsonWriter.startArray("tabs");
+ for(auto id : GetPageIDs())
+ {
+ auto tabNode = rJsonWriter.startStruct();
+ rJsonWriter.put("text", GetPageText(id));
+ rJsonWriter.put("id", id);
+ rJsonWriter.put("name", GetPageName(id));
+ }
+ }
+}
+
+sal_uInt16 NotebookbarTabControlBase::m_nHeaderHeight = 0;
+
+IMPL_LINK_NOARG(NotebookbarTabControlBase, OpenMenu, Button*, void)
+{
+ m_aIconClickHdl.Call(static_cast<NotebookBar*>(GetParent()->GetParent()));
+}
+
+NotebookbarTabControlBase::NotebookbarTabControlBase(vcl::Window* pParent)
+ : TabControl(pParent, WB_STDTABCONTROL)
+ , bLastContextWasSupported(true)
+ , eLastContext(vcl::EnumContext::Context::Any)
+{
+ m_pOpenMenu = VclPtr<PushButton>::Create( this , WB_CENTER | WB_VCENTER );
+ m_pOpenMenu->SetClickHdl(LINK(this, NotebookbarTabControlBase, OpenMenu));
+ m_pOpenMenu->SetModeImage(Image(StockImage::Yes, SV_RESID_BITMAP_NOTEBOOKBAR));
+ m_pOpenMenu->SetSizePixel(m_pOpenMenu->GetOptimalSize());
+ m_pOpenMenu->Show();
+}
+
+NotebookbarTabControlBase::~NotebookbarTabControlBase()
+{
+ disposeOnce();
+}
+
+void NotebookbarTabControlBase::SetContext( vcl::EnumContext::Context eContext )
+{
+ if (eLastContext == eContext)
+ return;
+
+ bool bHandled = false;
+
+ for (int nChild = 0; nChild < GetPageCount(); ++nChild)
+ {
+ sal_uInt16 nPageId = TabControl::GetPageId(nChild);
+ TabPage* pPage = GetTabPage(nPageId);
+
+ if (pPage)
+ {
+ SetPageVisible(nPageId, pPage->HasContext(eContext) || pPage->HasContext(vcl::EnumContext::Context::Any));
+
+ if (!bHandled && bLastContextWasSupported
+ && pPage->HasContext(vcl::EnumContext::Context::Default))
+ {
+ SetCurPageId(nPageId);
+ }
+
+ if (pPage->HasContext(eContext) && eContext != vcl::EnumContext::Context::Any)
+ {
+ SetCurPageId(nPageId);
+ bHandled = true;
+ bLastContextWasSupported = true;
+ }
+ }
+ }
+
+ if (!bHandled)
+ bLastContextWasSupported = false;
+ eLastContext = eContext;
+
+ // tdf#152908 Tabbed compact toolbar does not repaint itself when tabs getting removed
+ // For unknown reason this is needed by the tabbed compact toolbar for other than gtk
+ // vcl backends.
+ Resize();
+}
+
+void NotebookbarTabControlBase::dispose()
+{
+ m_pShortcuts.disposeAndClear();
+ m_pOpenMenu.disposeAndClear();
+ TabControl::dispose();
+}
+
+void NotebookbarTabControlBase::SetToolBox( ToolBox* pToolBox )
+{
+ m_pShortcuts.set( pToolBox );
+}
+
+void NotebookbarTabControlBase::SetIconClickHdl( Link<NotebookBar*, void> aHdl )
+{
+ m_aIconClickHdl = aHdl;
+}
+
+static bool lcl_isValidPage(const ImplTabItem& rItem, bool& bFound)
+{
+ if (rItem.m_bVisible && rItem.m_bEnabled)
+ bFound = true;
+ return bFound;
+}
+
+void NotebookbarTabControlBase::ImplActivateTabPage( bool bNext )
+{
+ const sal_uInt16 nOldPos = GetPagePos(GetCurPageId());
+ bool bFound = false;
+ sal_Int32 nCurPos = nOldPos;
+
+ if (bNext)
+ {
+ for (nCurPos++; nCurPos < GetPageCount(); nCurPos++)
+ if (lcl_isValidPage(mpTabCtrlData->maItemList[nCurPos], bFound))
+ break;
+ }
+ else
+ {
+ for (nCurPos--; nCurPos >= 0; nCurPos--)
+ if (lcl_isValidPage(mpTabCtrlData->maItemList[nCurPos], bFound))
+ break;
+ }
+
+ if (!bFound)
+ nCurPos = nOldPos;
+ SelectTabPage( TabControl::GetPageId( nCurPos ) );
+}
+
+sal_uInt16 NotebookbarTabControlBase::GetHeaderHeight()
+{
+ return m_nHeaderHeight;
+}
+
+bool NotebookbarTabControlBase::ImplPlaceTabs( tools::Long nWidth )
+{
+ if ( nWidth <= 0 )
+ return false;
+ if ( mpTabCtrlData->maItemList.empty() )
+ return false;
+ if (!m_pOpenMenu || m_pOpenMenu->isDisposed())
+ return false;
+
+ const tools::Long nHamburgerWidth = m_pOpenMenu->GetSizePixel().Width();
+ tools::Long nMaxWidth = nWidth - nHamburgerWidth;
+ tools::Long nShortcutsWidth = m_pShortcuts != nullptr ? m_pShortcuts->GetSizePixel().getWidth() + 1 : 0;
+ tools::Long nFullWidth = nShortcutsWidth;
+
+ const tools::Long nOffsetX = 2 + nShortcutsWidth;
+ const tools::Long nOffsetY = 2;
+
+ //fdo#66435 throw Knuth/Tex minimum raggedness algorithm at the problem
+ //of ugly bare tabs on lines of their own
+
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ tools::Long nTabWidth = 0;
+ if (item.m_bVisible)
+ {
+ nTabWidth = ImplGetItemSize(&item, nMaxWidth).getWidth();
+ if (!item.maText.isEmpty() && nTabWidth < 100)
+ nTabWidth = 100;
+ }
+ nFullWidth += nTabWidth;
+ }
+
+ tools::Long nX = nOffsetX;
+ tools::Long nY = nOffsetY;
+
+ tools::Long nLineWidthAry[100];
+ nLineWidthAry[0] = 0;
+
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+
+ Size aSize = ImplGetItemSize( &item, nMaxWidth );
+
+ // set minimum tab size
+ if( nFullWidth < nMaxWidth && !item.maText.isEmpty() && aSize.getWidth() < 100)
+ aSize.setWidth( 100 );
+
+ if( !item.maText.isEmpty() && aSize.getHeight() < 28 )
+ aSize.setHeight( 28 );
+
+ tools::Rectangle aNewRect( Point( nX, nY ), aSize );
+ if ( mbSmallInvalidate && (item.maRect != aNewRect) )
+ mbSmallInvalidate = false;
+
+ item.maRect = aNewRect;
+ item.mnLine = 0;
+ item.mbFullVisible = true;
+
+ nLineWidthAry[0] += aSize.Width();
+ nX += aSize.Width();
+ }
+
+ // we always have only one line of tabs
+ lcl_AdjustSingleLineTabs(nMaxWidth, mpTabCtrlData.get());
+
+ // position the shortcutbox
+ if (m_pShortcuts)
+ {
+ tools::Long nPosY = (m_nHeaderHeight - m_pShortcuts->GetSizePixel().getHeight()) / 2;
+ m_pShortcuts->SetPosPixel(Point(0, nPosY));
+ }
+
+ tools::Long nPosY = (m_nHeaderHeight - m_pOpenMenu->GetSizePixel().getHeight()) / 2;
+ // position the menu
+ m_pOpenMenu->SetPosPixel(Point(nWidth - nHamburgerWidth, nPosY));
+
+ return true;
+}
+
+Size NotebookbarTabControlBase::calculateRequisition() const
+{
+ return TabControl::ImplCalculateRequisition(m_nHeaderHeight);
+}
+
+Control* NotebookbarTabControlBase::GetOpenMenu()
+{
+ return m_pOpenMenu;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/throbber.cxx b/vcl/source/control/throbber.cxx
new file mode 100644
index 000000000..d7dc3e4a7
--- /dev/null
+++ b/vcl/source/control/throbber.cxx
@@ -0,0 +1,235 @@
+/* -*- 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 <vcl/toolkit/throbber.hxx>
+#include <vcl/svapp.hxx>
+
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/graphic/GraphicProvider.hpp>
+#include <com/sun/star/graphic/XGraphicProvider.hpp>
+#include <com/sun/star/awt/ImageScaleMode.hpp>
+
+#include <comphelper/namedvaluecollection.hxx>
+#include <comphelper/processfactory.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/urlobj.hxx>
+
+#include <limits>
+
+using ::com::sun::star::uno::Reference;
+using ::com::sun::star::graphic::XGraphic;
+using ::com::sun::star::graphic::XGraphicProvider;
+using ::com::sun::star::uno::Exception;
+namespace ImageScaleMode = ::com::sun::star::awt::ImageScaleMode;
+
+Throbber::Throbber( vcl::Window* i_parentWindow, WinBits i_style )
+ :ImageControl( i_parentWindow, i_style )
+ ,mbRepeat( true )
+ ,mnStepTime( 100 )
+ ,mnCurStep( 0 )
+ ,maWaitTimer("Throbber maWaitTimer")
+{
+ maWaitTimer.SetTimeout( mnStepTime );
+ maWaitTimer.SetInvokeHandler( LINK( this, Throbber, TimeOutHdl ) );
+
+ SetScaleMode( ImageScaleMode::NONE );
+ initImages();
+}
+
+Throbber::~Throbber()
+{
+ disposeOnce();
+}
+
+void Throbber::dispose()
+{
+ maWaitTimer.Stop();
+ ImageControl::dispose();
+}
+
+namespace
+{
+ ::std::vector< Image > lcl_loadImageSet( const Throbber::ImageSet i_imageSet )
+ {
+ ::std::vector< Image > aImages;
+
+ const Reference< css::uno::XComponentContext > aContext( ::comphelper::getProcessComponentContext() );
+ const Reference< XGraphicProvider > xGraphicProvider( css::graphic::GraphicProvider::create(aContext) );
+
+ ::std::vector< OUString > aImageURLs( Throbber::getDefaultImageURLs( i_imageSet ) );
+ aImages.reserve( aImageURLs.size() );
+
+ ::comphelper::NamedValueCollection aMediaProperties;
+ for ( const auto& rImageURL : aImageURLs )
+ {
+ Reference< XGraphic > xGraphic;
+ aMediaProperties.put( "URL", rImageURL );
+ xGraphic = xGraphicProvider->queryGraphic( aMediaProperties.getPropertyValues() );
+ aImages.emplace_back( xGraphic );
+ }
+
+ return aImages;
+ }
+}
+
+void Throbber::Resize()
+{
+ ImageControl::Resize();
+ initImages();
+}
+
+void Throbber::initImages()
+{
+ try
+ {
+ ::std::vector< ::std::vector< Image > > aImageSets
+ {
+ lcl_loadImageSet( ImageSet::N16px ),
+ lcl_loadImageSet( ImageSet::N32px ),
+ lcl_loadImageSet( ImageSet::N64px )
+ };
+
+ // find the best matching image set (size-wise)
+ const ::Size aWindowSizePixel = GetSizePixel();
+ size_t nPreferredSet = 0;
+ if ( aImageSets.size() > 1 )
+ {
+ tools::Long nMinimalDistance = ::std::numeric_limits< tools::Long >::max();
+ for ( ::std::vector< ::std::vector< Image > >::const_iterator check = aImageSets.begin();
+ check != aImageSets.end();
+ ++check
+ )
+ {
+ if ( check->empty() )
+ {
+ SAL_WARN( "vcl.control", "Throbber::initImages: illegal image!" );
+ continue;
+ }
+
+ const Size aImageSize = (*check)[0].GetSizePixel();
+
+ if ( ( aImageSize.Width() > aWindowSizePixel.Width() )
+ || ( aImageSize.Height() > aWindowSizePixel.Height() )
+ )
+ // do not use an image set which doesn't fit into the window
+ continue;
+
+ const sal_Int64 distance =
+ ( aWindowSizePixel.Width() - aImageSize.Width() ) * ( aWindowSizePixel.Width() - aImageSize.Width() )
+ + ( aWindowSizePixel.Height() - aImageSize.Height() ) * ( aWindowSizePixel.Height() - aImageSize.Height() );
+ if ( distance < nMinimalDistance )
+ {
+ nMinimalDistance = distance;
+ nPreferredSet = check - aImageSets.begin();
+ }
+ }
+ }
+
+ if ( nPreferredSet < aImageSets.size() )
+ setImageList( std::vector(aImageSets[nPreferredSet]) );
+ }
+ catch( const Exception& )
+ {
+ }
+}
+
+void Throbber::start()
+{
+ maWaitTimer.Start();
+}
+
+void Throbber::stop()
+{
+ maWaitTimer.Stop();
+}
+
+bool Throbber::isRunning() const
+{
+ return maWaitTimer.IsActive();
+}
+
+void Throbber::setImageList( ::std::vector< Image > && i_images )
+{
+ SAL_WARN_IF( i_images.size()>=SAL_MAX_INT32, "vcl.control", "Throbber::setImageList: too many images!" );
+
+ maImageList = std::move(i_images);
+
+ const Image aInitialImage( !maImageList.empty() ? maImageList[ 0 ] : Image() );
+ SetImage( aInitialImage );
+}
+
+::std::vector< OUString > Throbber::getDefaultImageURLs( const ImageSet i_imageSet )
+{
+ ::std::vector< OUString > aImageURLs;
+
+ char const* const pResolutions[] = { "16", "32", "64" };
+ size_t const nImageCounts[] = { 6, 12, 12 };
+
+ size_t index = 0;
+ switch ( i_imageSet )
+ {
+ case ImageSet::N16px: index = 0; break;
+ case ImageSet::N32px: index = 1; break;
+ case ImageSet::N64px: index = 2; break;
+ }
+
+ aImageURLs.reserve( nImageCounts[index] );
+ for ( size_t i=0; i<nImageCounts[index]; ++i )
+ {
+ OUStringBuffer aURL;
+ aURL.append( "private:graphicrepository/vcl/res/spinner-" );
+ aURL.appendAscii( pResolutions[index] );
+ aURL.append( "-" );
+ if ( i < 9 )
+ aURL.append( "0" );
+ aURL.append ( sal_Int32( i + 1 ) );
+ aURL.append( ".png" );
+
+ aImageURLs.push_back( aURL.makeStringAndClear() );
+ }
+
+ return aImageURLs;
+}
+
+IMPL_LINK_NOARG(Throbber, TimeOutHdl, Timer *, void)
+{
+ SolarMutexGuard aGuard;
+ if ( maImageList.empty() )
+ return;
+
+ if ( mnCurStep < static_cast<sal_Int32>(maImageList.size()-1) )
+ ++mnCurStep;
+ else
+ {
+ if ( mbRepeat )
+ {
+ // start over
+ mnCurStep = 0;
+ }
+ else
+ {
+ stop();
+ }
+ }
+
+ SetImage( maImageList[ mnCurStep ] );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/thumbpos.hxx b/vcl/source/control/thumbpos.hxx
new file mode 100644
index 000000000..e7048f35f
--- /dev/null
+++ b/vcl/source/control/thumbpos.hxx
@@ -0,0 +1,24 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_CONTROL_THUMBPOS_HXX
+#define INCLUDED_VCL_SOURCE_CONTROL_THUMBPOS_HXX
+
+inline tools::Long ImplMulDiv(tools::Long nNumber, tools::Long nNumerator, tools::Long nDenominator)
+{
+ if (!nDenominator)
+ return 0;
+ double n = (static_cast<double>(nNumber) * static_cast<double>(nNumerator))
+ / static_cast<double>(nDenominator);
+ return static_cast<tools::Long>(n);
+}
+
+#endif // INCLUDED_VCL_SOURCE_CONTROL_THUMBPOS_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/wizardmachine.cxx b/vcl/source/control/wizardmachine.cxx
new file mode 100644
index 000000000..e2ed41ff0
--- /dev/null
+++ b/vcl/source/control/wizardmachine.cxx
@@ -0,0 +1,1418 @@
+/* -*- 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 <comphelper/lok.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <vcl/event.hxx>
+#include <tools/debug.hxx>
+#include <tools/diagnose_ex.h>
+#include <strings.hrc>
+#include <svdata.hxx>
+#include <wizdlg.hxx>
+#include <stack>
+#include "wizimpldata.hxx"
+
+constexpr OStringLiteral HID_WIZARD_NEXT = "SVT_HID_WIZARD_NEXT";
+constexpr OStringLiteral HID_WIZARD_PREVIOUS = "SVT_HID_WIZARD_PREVIOUS";
+
+#define WIZARDDIALOG_BUTTON_OFFSET_Y 6
+#define WIZARDDIALOG_BUTTON_DLGOFFSET_X 6
+#define WIZARDDIALOG_VIEW_DLGOFFSET_X 6
+#define WIZARDDIALOG_VIEW_DLGOFFSET_Y 6
+
+namespace vcl
+{
+ //= WizardPageImplData
+ OWizardPage::OWizardPage(weld::Container* pPage, weld::DialogController* pController, const OUString& rUIXMLDescription, const OString& rID)
+ : BuilderPage(pPage, pController, rUIXMLDescription, rID)
+ {
+ }
+
+ OWizardPage::~OWizardPage()
+ {
+ }
+
+ void OWizardPage::initializePage()
+ {
+ }
+
+ void OWizardPage::Activate()
+ {
+ BuilderPage::Activate();
+ updateDialogTravelUI();
+ }
+
+ void OWizardPage::updateDialogTravelUI()
+ {
+ auto pWizardMachine = dynamic_cast<RoadmapWizardMachine*>(m_pDialogController);
+ if (pWizardMachine)
+ pWizardMachine->updateTravelUI();
+ }
+
+ bool OWizardPage::canAdvance() const
+ {
+ return true;
+ }
+
+ bool OWizardPage::commitPage( WizardTypes::CommitPageReason )
+ {
+ return true;
+ }
+
+ void RoadmapWizard::SetLeftAlignedButtonCount( sal_Int16 _nCount )
+ {
+ mnLeftAlignCount = _nCount;
+ }
+
+ void RoadmapWizard::ImplCalcSize( Size& rSize )
+ {
+ // calculate ButtonBar height and width
+ tools::Long nMaxHeight = 0;
+ tools::Long nBarWidth = WIZARDDIALOG_BUTTON_DLGOFFSET_X * 2 + LogicalCoordinateToPixel(6);
+ ImplWizButtonData* pBtnData = mpFirstBtn;
+ while (pBtnData)
+ {
+ auto nBtnHeight = pBtnData->mpButton->GetSizePixel().Height();
+ auto nBtnWidth = pBtnData->mpButton->GetSizePixel().Width();
+ if (pBtnData->mpButton->IsVisible())
+ {
+ nBarWidth += nBtnWidth;
+ nBarWidth += pBtnData->mnOffset;
+ }
+ if ( nBtnHeight > nMaxHeight )
+ nMaxHeight = nBtnHeight;
+ pBtnData = pBtnData->mpNext;
+ }
+ if ( nMaxHeight )
+ nMaxHeight += WIZARDDIALOG_BUTTON_OFFSET_Y*2;
+ rSize.AdjustHeight(nMaxHeight);
+
+ // add in the view window size
+ if ( mpViewWindow && mpViewWindow->IsVisible() )
+ {
+ Size aViewSize = mpViewWindow->GetSizePixel();
+ // align left
+ rSize.AdjustWidth(aViewSize.Width() );
+ }
+
+ if (nBarWidth > rSize.Width())
+ rSize.setWidth(nBarWidth);
+ }
+
+ void RoadmapWizard::queue_resize(StateChangedType /*eReason*/)
+ {
+ if (maWizardLayoutIdle.IsActive())
+ return;
+ if (IsInClose())
+ return;
+ maWizardLayoutIdle.Start();
+ }
+
+ IMPL_LINK_NOARG(RoadmapWizard, ImplHandleWizardLayoutTimerHdl, Timer*, void)
+ {
+ ImplPosCtrls();
+ ImplPosTabPage();
+ }
+
+ void RoadmapWizard::ImplPosCtrls()
+ {
+ Size aDlgSize = GetOutputSizePixel();
+ tools::Long nBtnWidth = 0;
+ tools::Long nMaxHeight = 0;
+ tools::Long nOffY = aDlgSize.Height();
+
+ ImplWizButtonData* pBtnData = mpFirstBtn;
+ int j = 0;
+ while ( pBtnData )
+ {
+ if (j >= mnLeftAlignCount)
+ {
+ Size aBtnSize = pBtnData->mpButton->GetSizePixel();
+ tools::Long nBtnHeight = aBtnSize.Height();
+ if ( nBtnHeight > nMaxHeight )
+ nMaxHeight = nBtnHeight;
+ nBtnWidth += aBtnSize.Width();
+ nBtnWidth += pBtnData->mnOffset;
+ }
+ pBtnData = pBtnData->mpNext;
+ j++;
+ }
+
+ if ( nMaxHeight )
+ {
+ tools::Long nOffX = aDlgSize.Width()-nBtnWidth-WIZARDDIALOG_BUTTON_DLGOFFSET_X;
+ tools::Long nOffLeftAlignX = LogicalCoordinateToPixel(6);
+ nOffY -= WIZARDDIALOG_BUTTON_OFFSET_Y+nMaxHeight;
+
+ pBtnData = mpFirstBtn;
+ int i = 0;
+ while ( pBtnData )
+ {
+ Size aBtnSize = pBtnData->mpButton->GetSizePixel();
+ if (i >= mnLeftAlignCount)
+ {
+ Point aPos( nOffX, nOffY+((nMaxHeight-aBtnSize.Height())/2) );
+ pBtnData->mpButton->SetPosPixel( aPos );
+ nOffX += aBtnSize.Width();
+ nOffX += pBtnData->mnOffset;
+ }
+ else
+ {
+ Point aPos( nOffLeftAlignX, nOffY+((nMaxHeight-aBtnSize.Height())/2) );
+ pBtnData->mpButton->SetPosPixel( aPos );
+ nOffLeftAlignX += aBtnSize.Width();
+ nOffLeftAlignX += pBtnData->mnOffset;
+ }
+
+ pBtnData = pBtnData->mpNext;
+ i++;
+ }
+
+ nOffY -= WIZARDDIALOG_BUTTON_OFFSET_Y;
+ }
+
+ if ( !(mpViewWindow && mpViewWindow->IsVisible()) )
+ return;
+
+ tools::Long nViewOffX = 0;
+ tools::Long nViewOffY = 0;
+ tools::Long nViewWidth = 0;
+ tools::Long nViewHeight = 0;
+ tools::Long nDlgHeight = nOffY;
+ PosSizeFlags nViewPosFlags = PosSizeFlags::Pos;
+ // align left
+ {
+ if ( mbEmptyViewMargin )
+ {
+ nViewOffX = 0;
+ nViewOffY = 0;
+ nViewHeight = nDlgHeight;
+ }
+ else
+ {
+ nViewOffX = WIZARDDIALOG_VIEW_DLGOFFSET_X;
+ nViewOffY = WIZARDDIALOG_VIEW_DLGOFFSET_Y;
+ nViewHeight = nDlgHeight-(WIZARDDIALOG_VIEW_DLGOFFSET_Y*2);
+ }
+ nViewPosFlags |= PosSizeFlags::Height;
+ }
+ mpViewWindow->setPosSizePixel( nViewOffX, nViewOffY,
+ nViewWidth, nViewHeight,
+ nViewPosFlags );
+ }
+
+ tools::Long RoadmapWizard::LogicalCoordinateToPixel(int iCoordinate) const
+ {
+ Size aLocSize = LogicToPixel(Size(iCoordinate, 0), MapMode(MapUnit::MapAppFont));
+ int iPixelCoordinate = aLocSize.Width();
+ return iPixelCoordinate;
+ }
+
+ void RoadmapWizard::ImplPosTabPage()
+ {
+ if ( !mpCurTabPage )
+ return;
+
+ if ( !IsInInitShow() )
+ {
+ // #100199# - On Unix initial size is equal to screen size, on Windows
+ // it's 0,0. One cannot calculate the size unless dialog is visible.
+ if ( !IsReallyVisible() )
+ return;
+ }
+
+ // calculate height of ButtonBar
+ tools::Long nMaxHeight = 0;
+ ImplWizButtonData* pBtnData = mpFirstBtn;
+ while ( pBtnData )
+ {
+ tools::Long nBtnHeight = pBtnData->mpButton->GetSizePixel().Height();
+ if ( nBtnHeight > nMaxHeight )
+ nMaxHeight = nBtnHeight;
+ pBtnData = pBtnData->mpNext;
+ }
+ if ( nMaxHeight )
+ nMaxHeight += WIZARDDIALOG_BUTTON_OFFSET_Y*2;
+
+ // position TabPage
+ Size aDlgSize = GetOutputSizePixel();
+ aDlgSize.AdjustHeight( -nMaxHeight );
+ tools::Long nOffX = 0;
+ tools::Long nOffY = 0;
+ if ( mpViewWindow && mpViewWindow->IsVisible() )
+ {
+ Size aViewSize = mpViewWindow->GetSizePixel();
+ // align left
+ tools::Long nViewOffset = mbEmptyViewMargin ? 0 : WIZARDDIALOG_VIEW_DLGOFFSET_X;
+ nOffX += aViewSize.Width() + nViewOffset;
+ aDlgSize.AdjustWidth( -nOffX );
+ }
+ Point aPos( nOffX, nOffY );
+ mpCurTabPage->SetPosSizePixel( aPos, aDlgSize );
+ }
+
+ void RoadmapWizard::ImplShowTabPage( TabPage* pTabPage )
+ {
+ if ( mpCurTabPage == pTabPage )
+ return;
+
+ TabPage* pOldTabPage = mpCurTabPage;
+
+ mpCurTabPage = pTabPage;
+ if ( pTabPage )
+ {
+ ImplPosTabPage();
+ pTabPage->Show();
+ }
+
+ if ( pOldTabPage )
+ pOldTabPage->Hide();
+ }
+
+ TabPage* RoadmapWizard::ImplGetPage( sal_uInt16 nLevel ) const
+ {
+ sal_uInt16 nTempLevel = 0;
+ ImplWizPageData* pPageData = mpFirstPage;
+ while ( pPageData )
+ {
+ if ( (nTempLevel == nLevel) || !pPageData->mpNext )
+ break;
+
+ nTempLevel++;
+ pPageData = pPageData->mpNext;
+ }
+
+ if ( pPageData )
+ return pPageData->mpPage;
+ return nullptr;
+ }
+
+ void RoadmapWizard::implConstruct( const WizardButtonFlags _nButtonFlags )
+ {
+ m_xWizardImpl->sTitleBase = GetText();
+
+ // create the buttons according to the wizard button flags
+ // the help button
+ if (_nButtonFlags & WizardButtonFlags::HELP)
+ {
+ m_pHelp= VclPtr<HelpButton>::Create(this, WB_TABSTOP);
+ m_pHelp->SetSizePixel(LogicToPixel(Size(50, 14), MapMode(MapUnit::MapAppFont)));
+ m_pHelp->Show();
+ AddButton( m_pHelp, WIZARDDIALOG_BUTTON_STDOFFSET_X);
+ }
+
+ // the previous button
+ if (_nButtonFlags & WizardButtonFlags::PREVIOUS)
+ {
+ m_pPrevPage = VclPtr<PushButton>::Create(this, WB_TABSTOP);
+ m_pPrevPage->SetHelpId( HID_WIZARD_PREVIOUS );
+ m_pPrevPage->SetSizePixel(LogicToPixel(Size(50, 14), MapMode(MapUnit::MapAppFont)));
+ m_pPrevPage->SetText(VclResId(STR_WIZDLG_PREVIOUS));
+ m_pPrevPage->Show();
+ m_pPrevPage->set_id("previous");
+
+ if (_nButtonFlags & WizardButtonFlags::NEXT)
+ AddButton( m_pPrevPage, ( WIZARDDIALOG_BUTTON_SMALLSTDOFFSET_X) ); // half x-offset to the next button
+ else
+ AddButton( m_pPrevPage, WIZARDDIALOG_BUTTON_STDOFFSET_X );
+ mpPrevBtn = m_pPrevPage;
+ m_pPrevPage->SetClickHdl( LINK( this, RoadmapWizard, OnPrevPage ) );
+ }
+
+ // the next button
+ if (_nButtonFlags & WizardButtonFlags::NEXT)
+ {
+ m_pNextPage = VclPtr<PushButton>::Create(this, WB_TABSTOP);
+ m_pNextPage->SetHelpId( HID_WIZARD_NEXT );
+ m_pNextPage->SetSizePixel(LogicToPixel(Size(50, 14), MapMode(MapUnit::MapAppFont)));
+ m_pNextPage->SetText(VclResId(STR_WIZDLG_NEXT));
+ m_pNextPage->Show();
+ m_pNextPage->set_id("next");
+
+ AddButton( m_pNextPage, WIZARDDIALOG_BUTTON_STDOFFSET_X );
+ mpNextBtn = m_pNextPage;
+ m_pNextPage->SetClickHdl( LINK( this, RoadmapWizard, OnNextPage ) );
+ }
+
+ // the finish button
+ if (_nButtonFlags & WizardButtonFlags::FINISH)
+ {
+ m_pFinish = VclPtr<OKButton>::Create(this, WB_TABSTOP);
+ m_pFinish->SetSizePixel(LogicToPixel(Size(50, 14), MapMode(MapUnit::MapAppFont)));
+ m_pFinish->SetText(VclResId(STR_WIZDLG_FINISH));
+ m_pFinish->Show();
+ m_pFinish->set_id("finish");
+
+ AddButton( m_pFinish, WIZARDDIALOG_BUTTON_STDOFFSET_X );
+ m_pFinish->SetClickHdl( LINK( this, RoadmapWizard, OnFinish ) );
+ }
+
+ // the cancel button
+ if (_nButtonFlags & WizardButtonFlags::CANCEL)
+ {
+ m_pCancel = VclPtr<CancelButton>::Create(this, WB_TABSTOP);
+ m_pCancel->SetSizePixel(LogicToPixel(Size(50, 14), MapMode(MapUnit::MapAppFont)));
+ m_pCancel->Show();
+
+ AddButton( m_pCancel, WIZARDDIALOG_BUTTON_STDOFFSET_X );
+ }
+ }
+
+ void RoadmapWizard::Resize()
+ {
+ if ( IsReallyShown() && !IsInInitShow() )
+ {
+ ImplPosCtrls();
+ ImplPosTabPage();
+ }
+
+ Dialog::Resize();
+ }
+
+ void RoadmapWizard::CalcAndSetSize()
+ {
+ Size aDlgSize = GetPageSizePixel();
+ if ( !aDlgSize.Width() || !aDlgSize.Height() )
+ {
+ ImplWizPageData* pPageData = mpFirstPage;
+ while ( pPageData )
+ {
+ if ( pPageData->mpPage )
+ {
+ Size aPageSize = pPageData->mpPage->GetSizePixel();
+ if ( aPageSize.Width() > aDlgSize.Width() )
+ aDlgSize.setWidth( aPageSize.Width() );
+ if ( aPageSize.Height() > aDlgSize.Height() )
+ aDlgSize.setHeight( aPageSize.Height() );
+ }
+
+ pPageData = pPageData->mpNext;
+ }
+ }
+ ImplCalcSize( aDlgSize );
+ SetMinOutputSizePixel( aDlgSize );
+ SetOutputSizePixel( aDlgSize );
+ }
+
+ void RoadmapWizard::StateChanged( StateChangedType nType )
+ {
+ if ( nType == StateChangedType::InitShow )
+ {
+ if ( IsDefaultSize() )
+ {
+ CalcAndSetSize();
+ }
+
+ ImplPosCtrls();
+ ImplPosTabPage();
+ ImplShowTabPage( ImplGetPage( mnCurLevel ) );
+ }
+
+ Dialog::StateChanged( nType );
+ }
+
+ bool RoadmapWizard::EventNotify( NotifyEvent& rNEvt )
+ {
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && mpPrevBtn && mpNextBtn )
+ {
+ const KeyEvent* pKEvt = rNEvt.GetKeyEvent();
+ vcl::KeyCode aKeyCode = pKEvt->GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+
+ if ( aKeyCode.IsMod1() )
+ {
+ if ( aKeyCode.IsShift() || (nKeyCode == KEY_PAGEUP) )
+ {
+ if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEUP) )
+ {
+ if ( mpPrevBtn->IsVisible() &&
+ mpPrevBtn->IsEnabled() && mpPrevBtn->IsInputEnabled() )
+ {
+ mpPrevBtn->SetPressed( true );
+ mpPrevBtn->SetPressed( false );
+ mpPrevBtn->Click();
+ }
+ return true;
+ }
+ }
+ else
+ {
+ if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEDOWN) )
+ {
+ if ( mpNextBtn->IsVisible() &&
+ mpNextBtn->IsEnabled() && mpNextBtn->IsInputEnabled() )
+ {
+ mpNextBtn->SetPressed( true );
+ mpNextBtn->SetPressed( false );
+ mpNextBtn->Click();
+ }
+ return true;
+ }
+ }
+ }
+ }
+
+ return Dialog::EventNotify( rNEvt );
+ }
+
+ void RoadmapWizard::GetOrCreatePage( const WizardTypes::WizardState i_nState )
+ {
+ if ( nullptr != GetPage( i_nState ) )
+ return;
+
+ VclPtr<TabPage> pNewPage = createPage( i_nState );
+ DBG_ASSERT( pNewPage, "RoadmapWizard::GetOrCreatePage: invalid new page (NULL)!" );
+
+ // fill up the page sequence of our base class (with dummies)
+ while ( m_xWizardImpl->nFirstUnknownPage < i_nState )
+ {
+ AddPage( nullptr );
+ ++m_xWizardImpl->nFirstUnknownPage;
+ }
+
+ if ( m_xWizardImpl->nFirstUnknownPage == i_nState )
+ {
+ // encountered this page number the first time
+ AddPage( pNewPage );
+ ++m_xWizardImpl->nFirstUnknownPage;
+ }
+ else
+ // already had this page - just change it
+ SetPage( i_nState, pNewPage );
+ }
+
+ void RoadmapWizard::ActivatePage()
+ {
+ WizardTypes::WizardState nCurrentLevel = GetCurLevel();
+ GetOrCreatePage( nCurrentLevel );
+
+ enterState( nCurrentLevel );
+ }
+
+ bool RoadmapWizard::ShowPage( sal_uInt16 nLevel )
+ {
+ mnCurLevel = nLevel;
+ ActivatePage();
+ ImplShowTabPage( ImplGetPage( mnCurLevel ) );
+ return true;
+ }
+
+ void RoadmapWizard::Finish( tools::Long nResult )
+ {
+ if ( IsInExecute() )
+ EndDialog( nResult );
+ else if ( GetStyle() & WB_CLOSEABLE )
+ Close();
+ }
+
+ void RoadmapWizard::AddPage( TabPage* pPage )
+ {
+ ImplWizPageData* pNewPageData = new ImplWizPageData;
+ pNewPageData->mpNext = nullptr;
+ pNewPageData->mpPage = pPage;
+
+ if ( !mpFirstPage )
+ mpFirstPage = pNewPageData;
+ else
+ {
+ ImplWizPageData* pPageData = mpFirstPage;
+ while ( pPageData->mpNext )
+ pPageData = pPageData->mpNext;
+ pPageData->mpNext = pNewPageData;
+ }
+ }
+
+ void RoadmapWizard::RemovePage( TabPage* pPage )
+ {
+ ImplWizPageData* pPrevPageData = nullptr;
+ ImplWizPageData* pPageData = mpFirstPage;
+ while ( pPageData )
+ {
+ if ( pPageData->mpPage == pPage )
+ {
+ if ( pPrevPageData )
+ pPrevPageData->mpNext = pPageData->mpNext;
+ else
+ mpFirstPage = pPageData->mpNext;
+ if ( pPage == mpCurTabPage )
+ mpCurTabPage = nullptr;
+ delete pPageData;
+ return;
+ }
+
+ pPrevPageData = pPageData;
+ pPageData = pPageData->mpNext;
+ }
+
+ OSL_FAIL( "RoadmapWizard::RemovePage() - Page not in list" );
+ }
+
+ void RoadmapWizard::SetPage( sal_uInt16 nLevel, TabPage* pPage )
+ {
+ sal_uInt16 nTempLevel = 0;
+ ImplWizPageData* pPageData = mpFirstPage;
+ while ( pPageData )
+ {
+ if ( (nTempLevel == nLevel) || !pPageData->mpNext )
+ break;
+
+ nTempLevel++;
+ pPageData = pPageData->mpNext;
+ }
+
+ if ( pPageData )
+ {
+ if ( pPageData->mpPage == mpCurTabPage )
+ mpCurTabPage = nullptr;
+ pPageData->mpPage = pPage;
+ }
+ }
+
+ TabPage* RoadmapWizard::GetPage( sal_uInt16 nLevel ) const
+ {
+ sal_uInt16 nTempLevel = 0;
+
+ for (ImplWizPageData* pPageData = mpFirstPage; pPageData;
+ pPageData = pPageData->mpNext)
+ {
+ if ( nTempLevel == nLevel )
+ return pPageData->mpPage;
+ nTempLevel++;
+ }
+
+ return nullptr;
+ }
+
+ void RoadmapWizard::AddButton( Button* pButton, tools::Long nOffset )
+ {
+ ImplWizButtonData* pNewBtnData = new ImplWizButtonData;
+ pNewBtnData->mpNext = nullptr;
+ pNewBtnData->mpButton = pButton;
+ pNewBtnData->mnOffset = nOffset;
+
+ if ( !mpFirstBtn )
+ mpFirstBtn = pNewBtnData;
+ else
+ {
+ ImplWizButtonData* pBtnData = mpFirstBtn;
+ while ( pBtnData->mpNext )
+ pBtnData = pBtnData->mpNext;
+ pBtnData->mpNext = pNewBtnData;
+ }
+ }
+
+ void RoadmapWizard::RemoveButton( Button* pButton )
+ {
+ ImplWizButtonData* pPrevBtnData = nullptr;
+ ImplWizButtonData* pBtnData = mpFirstBtn;
+ while ( pBtnData )
+ {
+ if ( pBtnData->mpButton == pButton )
+ {
+ if ( pPrevBtnData )
+ pPrevBtnData->mpNext = pBtnData->mpNext;
+ else
+ mpFirstBtn = pBtnData->mpNext;
+ delete pBtnData;
+ return;
+ }
+
+ pPrevBtnData = pBtnData;
+ pBtnData = pBtnData->mpNext;
+ }
+
+ OSL_FAIL( "RoadmapWizard::RemoveButton() - Button not in list" );
+ }
+
+ IMPL_LINK_NOARG(RoadmapWizard, OnFinish, Button*, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+ RoadmapWizardTravelSuspension aTravelGuard( *this );
+ Finish( RET_OK );
+ }
+
+ bool RoadmapWizard::skipBackwardUntil( WizardTypes::WizardState _nTargetState )
+ {
+ // don't travel directly on m_xWizardImpl->aStateHistory, in case something goes wrong
+ std::stack< WizardTypes::WizardState > aTravelVirtually = m_xWizardImpl->aStateHistory;
+ std::stack< WizardTypes::WizardState > aOldStateHistory = m_xWizardImpl->aStateHistory;
+
+ WizardTypes::WizardState nCurrentRollbackState = getCurrentState();
+ while ( nCurrentRollbackState != _nTargetState )
+ {
+ DBG_ASSERT( !aTravelVirtually.empty(), "RoadmapWizard::skipBackwardUntil: this target state does not exist in the history!" );
+ nCurrentRollbackState = aTravelVirtually.top();
+ aTravelVirtually.pop();
+ }
+ m_xWizardImpl->aStateHistory = aTravelVirtually;
+ if ( !ShowPage( _nTargetState ) )
+ {
+ m_xWizardImpl->aStateHistory = aOldStateHistory;
+ return false;
+ }
+ return true;
+ }
+
+ bool RoadmapWizard::skipUntil( WizardTypes::WizardState _nTargetState )
+ {
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+
+ // don't travel directly on m_xWizardImpl->aStateHistory, in case something goes wrong
+ std::stack< WizardTypes::WizardState > aTravelVirtually = m_xWizardImpl->aStateHistory;
+ std::stack< WizardTypes::WizardState > aOldStateHistory = m_xWizardImpl->aStateHistory;
+ while ( nCurrentState != _nTargetState )
+ {
+ WizardTypes::WizardState nNextState = determineNextState( nCurrentState );
+ if ( WZS_INVALID_STATE == nNextState )
+ {
+ OSL_FAIL( "RoadmapWizard::skipUntil: the given target state does not exist!" );
+ return false;
+ }
+
+ // remember the skipped state in the history
+ aTravelVirtually.push( nCurrentState );
+
+ // get the next state
+ nCurrentState = nNextState;
+ }
+ m_xWizardImpl->aStateHistory = aTravelVirtually;
+ // show the target page
+ if ( !ShowPage( nCurrentState ) )
+ {
+ // argh! prepareLeaveCurrentPage succeeded, determineNextState succeeded,
+ // but ShowPage doesn't? Somebody behaves very strange here...
+ OSL_FAIL( "RoadmapWizard::skipUntil: very unpolite..." );
+ m_xWizardImpl->aStateHistory = aOldStateHistory;
+ return false;
+ }
+ return true;
+ }
+
+ void RoadmapWizard::travelNext()
+ {
+ // determine the next state to travel to
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+ WizardTypes::WizardState nNextState = determineNextState(nCurrentState);
+ if (WZS_INVALID_STATE == nNextState)
+ return;
+
+ // the state history is used by the enterState method
+ // all fine
+ m_xWizardImpl->aStateHistory.push(nCurrentState);
+ if (!ShowPage(nNextState))
+ {
+ m_xWizardImpl->aStateHistory.pop();
+ }
+ }
+
+ void RoadmapWizard::travelPrevious()
+ {
+ DBG_ASSERT(!m_xWizardImpl->aStateHistory.empty(), "RoadmapWizard::travelPrevious: have no previous page!");
+
+ // the next state to switch to
+ WizardTypes::WizardState nPreviousState = m_xWizardImpl->aStateHistory.top();
+
+ // the state history is used by the enterState method
+ m_xWizardImpl->aStateHistory.pop();
+ // show this page
+ if (!ShowPage(nPreviousState))
+ {
+ m_xWizardImpl->aStateHistory.push(nPreviousState);
+ }
+
+ // all fine
+ }
+
+ void RoadmapWizard::removePageFromHistory( WizardTypes::WizardState nToRemove )
+ {
+
+ std::stack< WizardTypes::WizardState > aTemp;
+ while(!m_xWizardImpl->aStateHistory.empty())
+ {
+ WizardTypes::WizardState nPreviousState = m_xWizardImpl->aStateHistory.top();
+ m_xWizardImpl->aStateHistory.pop();
+ if(nPreviousState != nToRemove)
+ aTemp.push( nPreviousState );
+ else
+ break;
+ }
+ while(!aTemp.empty())
+ {
+ m_xWizardImpl->aStateHistory.push( aTemp.top() );
+ aTemp.pop();
+ }
+ }
+
+ IMPL_LINK_NOARG(RoadmapWizard, OnPrevPage, Button*, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+ RoadmapWizardTravelSuspension aTravelGuard( *this );
+ travelPrevious();
+ }
+
+ IMPL_LINK_NOARG(RoadmapWizard, OnNextPage, Button*, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+ RoadmapWizardTravelSuspension aTravelGuard( *this );
+ travelNext();
+ }
+
+ bool RoadmapWizard::isTravelingSuspended() const
+ {
+ return m_xWizardImpl->m_bTravelingSuspended;
+ }
+
+ void RoadmapWizard::suspendTraveling( AccessGuard )
+ {
+ DBG_ASSERT( !m_xWizardImpl->m_bTravelingSuspended, "RoadmapWizard::suspendTraveling: already suspended!" );
+ m_xWizardImpl->m_bTravelingSuspended = true;
+ }
+
+ void RoadmapWizard::resumeTraveling( AccessGuard )
+ {
+ DBG_ASSERT( m_xWizardImpl->m_bTravelingSuspended, "RoadmapWizard::resumeTraveling: nothing to resume!" );
+ m_xWizardImpl->m_bTravelingSuspended = false;
+ }
+
+ WizardMachine::WizardMachine(weld::Window* pParent, WizardButtonFlags nButtonFlags)
+ : AssistantController(pParent, "vcl/ui/wizard.ui", "Wizard")
+ , m_pCurTabPage(nullptr)
+ , m_nCurState(0)
+ , m_pFirstPage(nullptr)
+ , m_xFinish(m_xAssistant->weld_widget_for_response(RET_OK))
+ , m_xCancel(m_xAssistant->weld_widget_for_response(RET_CANCEL))
+ , m_xNextPage(m_xAssistant->weld_widget_for_response(RET_YES))
+ , m_xPrevPage(m_xAssistant->weld_widget_for_response(RET_NO))
+ , m_xHelp(m_xAssistant->weld_widget_for_response(RET_HELP))
+ , m_pImpl(new WizardMachineImplData)
+ {
+ implConstruct(nButtonFlags);
+ }
+
+ void WizardMachine::implConstruct(const WizardButtonFlags nButtonFlags)
+ {
+ m_pImpl->sTitleBase = m_xAssistant->get_title();
+
+ const bool bHideHelp = comphelper::LibreOfficeKit::isActive() &&
+ officecfg::Office::Common::Help::HelpRootURL::get().isEmpty();
+ // create the buttons according to the wizard button flags
+ // the help button
+ if (nButtonFlags & WizardButtonFlags::HELP && !bHideHelp)
+ m_xHelp->show();
+ else
+ m_xHelp->hide();
+
+ // the previous button
+ if (nButtonFlags & WizardButtonFlags::PREVIOUS)
+ {
+ m_xPrevPage->set_help_id( HID_WIZARD_PREVIOUS );
+ m_xPrevPage->show();
+
+ m_xPrevPage->connect_clicked( LINK( this, WizardMachine, OnPrevPage ) );
+ }
+ else
+ m_xPrevPage->hide();
+
+ // the next button
+ if (nButtonFlags & WizardButtonFlags::NEXT)
+ {
+ m_xNextPage->set_help_id( HID_WIZARD_NEXT );
+ m_xNextPage->show();
+
+ m_xNextPage->connect_clicked( LINK( this, WizardMachine, OnNextPage ) );
+ }
+ else
+ m_xNextPage->hide();
+
+ // the finish button
+ if (nButtonFlags & WizardButtonFlags::FINISH)
+ {
+ m_xFinish->show();
+
+ m_xFinish->connect_clicked( LINK( this, WizardMachine, OnFinish ) );
+ }
+ else
+ m_xFinish->hide();
+
+ // the cancel button
+ if (nButtonFlags & WizardButtonFlags::CANCEL)
+ {
+ m_xCancel->show();
+ m_xCancel->connect_clicked( LINK( this, WizardMachine, OnCancel ) );
+ }
+ else
+ m_xCancel->hide();
+ }
+
+ WizardMachine::~WizardMachine()
+ {
+ if (m_pImpl)
+ {
+ while (m_pFirstPage)
+ RemovePage(m_pFirstPage->mxPage.get());
+ m_pImpl.reset();
+ }
+ }
+
+ void WizardMachine::implUpdateTitle()
+ {
+ OUString sCompleteTitle(m_pImpl->sTitleBase);
+
+ // append the page title
+ BuilderPage* pCurrentPage = GetPage(getCurrentState());
+ if ( pCurrentPage && !pCurrentPage->GetPageTitle().isEmpty() )
+ {
+ sCompleteTitle += " - " + pCurrentPage->GetPageTitle();
+ }
+
+ m_xAssistant->set_title(sCompleteTitle);
+ }
+
+ void WizardMachine::setTitleBase(const OUString& _rTitleBase)
+ {
+ m_pImpl->sTitleBase = _rTitleBase;
+ implUpdateTitle();
+ }
+
+ BuilderPage* WizardMachine::GetOrCreatePage( const WizardTypes::WizardState i_nState )
+ {
+ if ( nullptr == GetPage( i_nState ) )
+ {
+ std::unique_ptr<BuilderPage> xNewPage = createPage( i_nState );
+ DBG_ASSERT( xNewPage, "WizardMachine::GetOrCreatePage: invalid new page (NULL)!" );
+
+ // fill up the page sequence of our base class (with dummies)
+ while ( m_pImpl->nFirstUnknownPage < i_nState )
+ {
+ AddPage( nullptr );
+ ++m_pImpl->nFirstUnknownPage;
+ }
+
+ if ( m_pImpl->nFirstUnknownPage == i_nState )
+ {
+ // encountered this page number the first time
+ AddPage(std::move(xNewPage));
+ ++m_pImpl->nFirstUnknownPage;
+ }
+ else
+ // already had this page - just change it
+ SetPage(i_nState, std::move(xNewPage));
+ }
+ return GetPage( i_nState );
+ }
+
+ void WizardMachine::ActivatePage()
+ {
+ WizardTypes::WizardState nCurrentLevel = m_nCurState;
+ GetOrCreatePage( nCurrentLevel );
+
+ enterState( nCurrentLevel );
+ }
+
+ bool WizardMachine::DeactivatePage()
+ {
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+ return leaveState(nCurrentState);
+ }
+
+ void WizardMachine::defaultButton(WizardButtonFlags _nWizardButtonFlags)
+ {
+ // the new default button
+ weld::Button* pNewDefButton = nullptr;
+ if (_nWizardButtonFlags & WizardButtonFlags::FINISH)
+ pNewDefButton = m_xFinish.get();
+ if (_nWizardButtonFlags & WizardButtonFlags::NEXT)
+ pNewDefButton = m_xNextPage.get();
+ if (_nWizardButtonFlags & WizardButtonFlags::PREVIOUS)
+ pNewDefButton = m_xPrevPage.get();
+ if (_nWizardButtonFlags & WizardButtonFlags::HELP)
+ pNewDefButton = m_xHelp.get();
+ if (_nWizardButtonFlags & WizardButtonFlags::CANCEL)
+ pNewDefButton = m_xCancel.get();
+
+ defaultButton(pNewDefButton);
+ }
+
+ void WizardMachine::defaultButton(weld::Button* _pNewDefButton)
+ {
+ // loop through all (direct and indirect) descendants which participate in our tabbing order, and
+ // reset the WB_DEFBUTTON for every window which is a button and set _pNewDefButton as the new
+ // WB_DEFBUTTON
+ m_xAssistant->change_default_widget(nullptr, _pNewDefButton);
+ }
+
+ void WizardMachine::enableButtons(WizardButtonFlags _nWizardButtonFlags, bool _bEnable)
+ {
+ if (_nWizardButtonFlags & WizardButtonFlags::FINISH)
+ m_xFinish->set_sensitive(_bEnable);
+ if (_nWizardButtonFlags & WizardButtonFlags::NEXT)
+ m_xNextPage->set_sensitive(_bEnable);
+ if (_nWizardButtonFlags & WizardButtonFlags::PREVIOUS)
+ m_xPrevPage->set_sensitive(_bEnable);
+ if (_nWizardButtonFlags & WizardButtonFlags::HELP)
+ m_xHelp->set_sensitive(_bEnable);
+ if (_nWizardButtonFlags & WizardButtonFlags::CANCEL)
+ m_xCancel->set_sensitive(_bEnable);
+ }
+
+ void WizardMachine::enterState(WizardTypes::WizardState _nState)
+ {
+ // tell the page
+ IWizardPageController* pController = getPageController( GetPage( _nState ) );
+ OSL_ENSURE( pController, "WizardMachine::enterState: no controller for the given page!" );
+ if ( pController )
+ pController->initializePage();
+
+ if ( isAutomaticNextButtonStateEnabled() )
+ enableButtons( WizardButtonFlags::NEXT, canAdvance() );
+
+ enableButtons( WizardButtonFlags::PREVIOUS, !m_pImpl->aStateHistory.empty() );
+
+ // set the new title - it depends on the current page (i.e. state)
+ implUpdateTitle();
+ }
+
+ bool WizardMachine::leaveState(WizardTypes::WizardState)
+ {
+ // no need to ask the page here.
+ // If we reach this point, we already gave the current page the chance to commit it's data,
+ // and it was allowed to commit it's data
+
+ return true;
+ }
+
+ bool WizardMachine::onFinish()
+ {
+ return Finish(RET_OK);
+ }
+
+ IMPL_LINK_NOARG(WizardMachine, OnFinish, weld::Button&, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+
+ // prevent WizardTravelSuspension from using this instance
+ // after will be destructed due to onFinish and async response call
+ {
+ WizardTravelSuspension aTravelGuard( *this );
+ if (!prepareLeaveCurrentState(WizardTypes::eFinish))
+ {
+ return;
+ }
+ }
+
+ onFinish();
+ }
+
+ IMPL_LINK_NOARG(WizardMachine, OnCancel, weld::Button&, void)
+ {
+ m_xAssistant->response(RET_CANCEL);
+ }
+
+ WizardTypes::WizardState WizardMachine::determineNextState(WizardTypes::WizardState _nCurrentState ) const
+ {
+ return _nCurrentState + 1;
+ }
+
+ bool WizardMachine::prepareLeaveCurrentState( WizardTypes::CommitPageReason _eReason )
+ {
+ IWizardPageController* pController = getPageController( GetPage( getCurrentState() ) );
+ ENSURE_OR_RETURN( pController != nullptr, "WizardMachine::prepareLeaveCurrentState: no controller for the current page!", true );
+ return pController->commitPage( _eReason );
+ }
+
+ bool WizardMachine::skipBackwardUntil(WizardTypes::WizardState _nTargetState)
+ {
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( WizardTypes::eTravelBackward ) )
+ return false;
+
+ // don't travel directly on m_pImpl->aStateHistory, in case something goes wrong
+ std::stack< WizardTypes::WizardState > aTravelVirtually = m_pImpl->aStateHistory;
+ std::stack< WizardTypes::WizardState > aOldStateHistory = m_pImpl->aStateHistory;
+
+ WizardTypes::WizardState nCurrentRollbackState = getCurrentState();
+ while ( nCurrentRollbackState != _nTargetState )
+ {
+ DBG_ASSERT( !aTravelVirtually.empty(), "WizardMachine::skipBackwardUntil: this target state does not exist in the history!" );
+ nCurrentRollbackState = aTravelVirtually.top();
+ aTravelVirtually.pop();
+ }
+ m_pImpl->aStateHistory = aTravelVirtually;
+ if ( !ShowPage( _nTargetState ) )
+ {
+ m_pImpl->aStateHistory = aOldStateHistory;
+ return false;
+ }
+ return true;
+ }
+
+ bool WizardMachine::skipUntil( WizardTypes::WizardState _nTargetState )
+ {
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( nCurrentState < _nTargetState ? WizardTypes::eTravelForward : WizardTypes::eTravelBackward ) )
+ return false;
+
+ // don't travel directly on m_pImpl->aStateHistory, in case something goes wrong
+ std::stack< WizardTypes::WizardState > aTravelVirtually = m_pImpl->aStateHistory;
+ std::stack< WizardTypes::WizardState > aOldStateHistory = m_pImpl->aStateHistory;
+ while ( nCurrentState != _nTargetState )
+ {
+ WizardTypes::WizardState nNextState = determineNextState( nCurrentState );
+ if ( WZS_INVALID_STATE == nNextState )
+ {
+ OSL_FAIL( "WizardMachine::skipUntil: the given target state does not exist!" );
+ return false;
+ }
+
+ // remember the skipped state in the history
+ aTravelVirtually.push( nCurrentState );
+
+ // get the next state
+ nCurrentState = nNextState;
+ }
+ m_pImpl->aStateHistory = aTravelVirtually;
+ // show the target page
+ if ( !ShowPage( nCurrentState ) )
+ {
+ // argh! prepareLeaveCurrentPage succeeded, determineNextState succeeded,
+ // but ShowPage doesn't? Somebody behaves very strange here...
+ OSL_FAIL( "WizardMachine::skipUntil: very unpolite..." );
+ m_pImpl->aStateHistory = aOldStateHistory;
+ return false;
+ }
+ return true;
+ }
+
+ void WizardMachine::skip()
+ {
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( WizardTypes::eTravelForward ) )
+ return;
+
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+ WizardTypes::WizardState nNextState = determineNextState(nCurrentState);
+
+ if (WZS_INVALID_STATE == nNextState)
+ return;
+
+ // remember the skipped state in the history
+ m_pImpl->aStateHistory.push(nCurrentState);
+
+ // get the next state
+ nCurrentState = nNextState;
+
+ // show the (n+1)th page
+ if (!ShowPage(nCurrentState))
+ {
+ // TODO: this leaves us in a state where we have no current page and an inconsistent state history.
+ // Perhaps we should rollback the skipping here...
+ OSL_FAIL("RoadmapWizard::skip: very unpolite...");
+ // if somebody does a skip and then does not allow to leave...
+ // (can't be a commit error, as we've already committed the current page. So if ShowPage fails here,
+ // somebody behaves really strange ...)
+ return;
+ }
+
+ // all fine
+ }
+
+ bool WizardMachine::travelNext()
+ {
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( WizardTypes::eTravelForward ) )
+ return false;
+
+ // determine the next state to travel to
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+ WizardTypes::WizardState nNextState = determineNextState(nCurrentState);
+ if (WZS_INVALID_STATE == nNextState)
+ return false;
+
+ // the state history is used by the enterState method
+ // all fine
+ m_pImpl->aStateHistory.push(nCurrentState);
+ if (!ShowPage(nNextState))
+ {
+ m_pImpl->aStateHistory.pop();
+ return false;
+ }
+
+ return true;
+ }
+
+ bool WizardMachine::ShowPage(WizardTypes::WizardState nState)
+ {
+ if (DeactivatePage())
+ {
+ BuilderPage* pOldTabPage = m_pCurTabPage;
+
+ m_nCurState = nState;
+ ActivatePage();
+
+ if (pOldTabPage)
+ pOldTabPage->Deactivate();
+
+ m_xAssistant->set_current_page(OString::number(nState));
+
+ m_pCurTabPage = GetPage(m_nCurState);
+ m_pCurTabPage->Activate();
+
+ return true;
+ }
+ return false;
+ }
+
+ bool WizardMachine::ShowNextPage()
+ {
+ return ShowPage(m_nCurState + 1);
+ }
+
+ bool WizardMachine::ShowPrevPage()
+ {
+ if (!m_nCurState)
+ return false;
+ return ShowPage(m_nCurState - 1);
+ }
+
+ bool WizardMachine::travelPrevious()
+ {
+ DBG_ASSERT(!m_pImpl->aStateHistory.empty(), "WizardMachine::travelPrevious: have no previous page!");
+
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( WizardTypes::eTravelBackward ) )
+ return false;
+
+ // the next state to switch to
+ WizardTypes::WizardState nPreviousState = m_pImpl->aStateHistory.top();
+
+ // the state history is used by the enterState method
+ m_pImpl->aStateHistory.pop();
+ // show this page
+ if (!ShowPage(nPreviousState))
+ {
+ m_pImpl->aStateHistory.push(nPreviousState);
+ return false;
+ }
+
+ // all fine
+ return true;
+ }
+
+
+ void WizardMachine::removePageFromHistory( WizardTypes::WizardState nToRemove )
+ {
+
+ std::stack< WizardTypes::WizardState > aTemp;
+ while(!m_pImpl->aStateHistory.empty())
+ {
+ WizardTypes::WizardState nPreviousState = m_pImpl->aStateHistory.top();
+ m_pImpl->aStateHistory.pop();
+ if(nPreviousState != nToRemove)
+ aTemp.push( nPreviousState );
+ else
+ break;
+ }
+ while(!aTemp.empty())
+ {
+ m_pImpl->aStateHistory.push( aTemp.top() );
+ aTemp.pop();
+ }
+ }
+
+
+ void WizardMachine::enableAutomaticNextButtonState()
+ {
+ m_pImpl->m_bAutoNextButtonState = true;
+ }
+
+
+ bool WizardMachine::isAutomaticNextButtonStateEnabled() const
+ {
+ return m_pImpl->m_bAutoNextButtonState;
+ }
+
+ IMPL_LINK_NOARG(WizardMachine, OnPrevPage, weld::Button&, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+ WizardTravelSuspension aTravelGuard( *this );
+ travelPrevious();
+ }
+
+ IMPL_LINK_NOARG(WizardMachine, OnNextPage, weld::Button&, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+ WizardTravelSuspension aTravelGuard( *this );
+ travelNext();
+ }
+
+ IWizardPageController* WizardMachine::getPageController(BuilderPage* pCurrentPage) const
+ {
+ IWizardPageController* pController = dynamic_cast<IWizardPageController*>(pCurrentPage);
+ return pController;
+ }
+
+ void WizardMachine::getStateHistory( std::vector< WizardTypes::WizardState >& _out_rHistory )
+ {
+ std::stack< WizardTypes::WizardState > aHistoryCopy( m_pImpl->aStateHistory );
+ while ( !aHistoryCopy.empty() )
+ {
+ _out_rHistory.push_back( aHistoryCopy.top() );
+ aHistoryCopy.pop();
+ }
+ }
+
+ bool WizardMachine::canAdvance() const
+ {
+ return WZS_INVALID_STATE != determineNextState( getCurrentState() );
+ }
+
+ void WizardMachine::updateTravelUI()
+ {
+ const IWizardPageController* pController = getPageController( GetPage( getCurrentState() ) );
+ OSL_ENSURE( pController != nullptr, "RoadmapWizard::updateTravelUI: no controller for the current page!" );
+
+ bool bCanAdvance =
+ ( !pController || pController->canAdvance() ) // the current page allows to advance
+ && canAdvance(); // the dialog as a whole allows to advance
+ enableButtons( WizardButtonFlags::NEXT, bCanAdvance );
+ }
+
+ bool WizardMachine::isTravelingSuspended() const
+ {
+ return m_pImpl->m_bTravelingSuspended;
+ }
+
+ void WizardMachine::suspendTraveling( AccessGuard )
+ {
+ DBG_ASSERT( !m_pImpl->m_bTravelingSuspended, "WizardMachine::suspendTraveling: already suspended!" );
+ m_pImpl->m_bTravelingSuspended = true;
+ }
+
+ void WizardMachine::resumeTraveling( AccessGuard )
+ {
+ if (!m_pImpl)
+ return;
+
+ DBG_ASSERT( m_pImpl->m_bTravelingSuspended, "WizardMachine::resumeTraveling: nothing to resume!" );
+ m_pImpl->m_bTravelingSuspended = false;
+ }
+
+ bool WizardMachine::Finish(short nResult)
+ {
+ if ( DeactivatePage() )
+ {
+ if (m_pCurTabPage)
+ m_pCurTabPage->Deactivate();
+
+ m_xAssistant->response(nResult);
+ return true;
+ }
+ else
+ return false;
+ }
+
+ void WizardMachine::AddPage(std::unique_ptr<BuilderPage> xPage)
+ {
+ WizPageData* pNewPageData = new WizPageData;
+ pNewPageData->mpNext = nullptr;
+ pNewPageData->mxPage = std::move(xPage);
+
+ if ( !m_pFirstPage )
+ m_pFirstPage = pNewPageData;
+ else
+ {
+ WizPageData* pPageData = m_pFirstPage;
+ while ( pPageData->mpNext )
+ pPageData = pPageData->mpNext;
+ pPageData->mpNext = pNewPageData;
+ }
+ }
+
+ void WizardMachine::RemovePage(const BuilderPage* pPage)
+ {
+ WizPageData* pPrevPageData = nullptr;
+ WizPageData* pPageData = m_pFirstPage;
+ while ( pPageData )
+ {
+ if (pPageData->mxPage.get() == pPage)
+ {
+ if (pPrevPageData)
+ pPrevPageData->mpNext = pPageData->mpNext;
+ else
+ m_pFirstPage = pPageData->mpNext;
+ if (pPage == m_pCurTabPage)
+ m_pCurTabPage = nullptr;
+ delete pPageData;
+ return;
+ }
+
+ pPrevPageData = pPageData;
+ pPageData = pPageData->mpNext;
+ }
+
+ OSL_FAIL( "WizardMachine::RemovePage() - Page not in list" );
+ }
+
+ void WizardMachine::SetPage(WizardTypes::WizardState nLevel, std::unique_ptr<BuilderPage> xPage)
+ {
+ sal_uInt16 nTempLevel = 0;
+ WizPageData* pPageData = m_pFirstPage;
+ while ( pPageData )
+ {
+ if ( (nTempLevel == nLevel) || !pPageData->mpNext )
+ break;
+
+ nTempLevel++;
+ pPageData = pPageData->mpNext;
+ }
+
+ if ( pPageData )
+ {
+ if (pPageData->mxPage.get() == m_pCurTabPage)
+ m_pCurTabPage = nullptr;
+ pPageData->mxPage = std::move(xPage);
+ }
+ }
+
+ BuilderPage* WizardMachine::GetPage(WizardTypes::WizardState nLevel) const
+ {
+ sal_uInt16 nTempLevel = 0;
+
+ for (WizPageData* pPageData = m_pFirstPage; pPageData;
+ pPageData = pPageData->mpNext)
+ {
+ if ( nTempLevel == nLevel )
+ return pPageData->mxPage.get();
+ nTempLevel++;
+ }
+
+ return nullptr;
+ }
+} // namespace svt
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/wizimpldata.hxx b/vcl/source/control/wizimpldata.hxx
new file mode 100644
index 000000000..f943c0473
--- /dev/null
+++ b/vcl/source/control/wizimpldata.hxx
@@ -0,0 +1,64 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_CONTROL_WIZIMPLDATA_HXX
+#define INCLUDED_VCL_SOURCE_CONTROL_WIZIMPLDATA_HXX
+
+#include <stack>
+
+struct WizPageData
+{
+ WizPageData* mpNext;
+ std::unique_ptr<BuilderPage> mxPage;
+};
+
+struct ImplWizButtonData
+{
+ ImplWizButtonData* mpNext;
+ VclPtr<Button> mpButton;
+ tools::Long mnOffset;
+};
+
+namespace vcl
+{
+ struct WizardMachineImplData
+ {
+ OUString sTitleBase; // the base for the title
+ std::stack<WizardTypes::WizardState> aStateHistory; // the history of all states (used for implementing "Back")
+
+ WizardTypes::WizardState nFirstUnknownPage;
+ // the WizardDialog does not allow non-linear transitions (e.g. it's
+ // not possible to add pages in a non-linear order), so we need some own maintenance data
+
+ bool m_bAutoNextButtonState;
+
+ bool m_bTravelingSuspended;
+
+ WizardMachineImplData()
+ :nFirstUnknownPage( 0 )
+ ,m_bAutoNextButtonState( false )
+ ,m_bTravelingSuspended( false )
+ {
+ }
+ };
+} // namespace svt
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textdat2.hxx b/vcl/source/edit/textdat2.hxx
new file mode 100644
index 000000000..c620f91ad
--- /dev/null
+++ b/vcl/source/edit/textdat2.hxx
@@ -0,0 +1,284 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_EDIT_TEXTDAT2_HXX
+#define INCLUDED_VCL_SOURCE_EDIT_TEXTDAT2_HXX
+
+#include <vcl/seleng.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/textdata.hxx>
+
+#include <cstddef>
+#include <limits>
+#include <vector>
+
+class TextNode;
+class TextView;
+
+#define PORTIONKIND_TEXT 0
+#define PORTIONKIND_TAB 1
+
+#define DELMODE_SIMPLE 0
+#define DELMODE_RESTOFWORD 1
+#define DELMODE_RESTOFCONTENT 2
+
+#define DEL_LEFT 1
+#define DEL_RIGHT 2
+#define TRAVEL_X_DONTKNOW 0xFFFF
+#define MAXCHARSINPARA 0x3FFF-CHARPOSGROW
+
+#define LINE_SEP 0x0A
+
+class TETextPortion
+{
+private:
+ tools::Long nWidth;
+ sal_Int32 nLen;
+ sal_uInt8 nKind;
+ bool bRightToLeft;
+
+public:
+ TETextPortion( sal_Int32 nL )
+ : nWidth {-1}
+ , nLen {nL}
+ , nKind {PORTIONKIND_TEXT}
+ , bRightToLeft {false}
+ {}
+
+ sal_Int32& GetLen() { return nLen; }
+ sal_Int32 GetLen() const { return nLen; }
+ tools::Long& GetWidth() { return nWidth; }
+ sal_uInt8& GetKind() { return nKind; }
+ void SetRightToLeft(bool b) { bRightToLeft = b; }
+ bool IsRightToLeft() const { return bRightToLeft; }
+};
+
+class TETextPortionList
+{
+private:
+ std::vector<TETextPortion> maPortions;
+
+public:
+ static constexpr auto npos = std::numeric_limits<std::size_t>::max();
+
+ TETextPortionList();
+ ~TETextPortionList();
+
+ TETextPortion& operator[]( std::size_t nPos );
+ std::vector<TETextPortion>::iterator begin();
+ std::vector<TETextPortion>::const_iterator begin() const;
+ std::vector<TETextPortion>::iterator end();
+ std::vector<TETextPortion>::const_iterator end() const;
+ bool empty() const;
+ std::size_t size() const;
+ std::vector<TETextPortion>::iterator erase( const std::vector<TETextPortion>::iterator& aIter );
+ std::vector<TETextPortion>::iterator insert( const std::vector<TETextPortion>::iterator& aIter,
+ const TETextPortion& rTP );
+ void push_back( const TETextPortion & aTP );
+
+ void Reset();
+ std::size_t FindPortion( sal_Int32 nCharPos, sal_Int32& rPortionStart, bool bPreferStartingPortion = false );
+ void DeleteFromPortion( std::size_t nDelFrom );
+};
+
+struct TEWritingDirectionInfo
+{
+ bool bLeftToRight;
+ sal_Int32 nStartPos;
+ sal_Int32 nEndPos;
+ TEWritingDirectionInfo( bool LeftToRight, sal_Int32 Start, sal_Int32 End )
+ : bLeftToRight {LeftToRight}
+ , nStartPos {Start}
+ , nEndPos {End}
+ {}
+};
+
+class TextLine
+{
+private:
+ sal_Int32 mnStart;
+ sal_Int32 mnEnd;
+ std::size_t mnStartPortion;
+ std::size_t mnEndPortion;
+
+ short mnStartX;
+
+ bool mbInvalid; // for clever formatting/output
+
+public:
+ TextLine()
+ : mnStart {0}
+ , mnEnd {0}
+ , mnStartPortion {0}
+ , mnEndPortion {0}
+ , mnStartX {0}
+ , mbInvalid {true}
+ {}
+
+ bool IsIn( sal_Int32 nIndex, bool bInclEnd ) const
+ { return nIndex >= mnStart && ( bInclEnd ? nIndex <= mnEnd : nIndex < mnEnd ); }
+
+ void SetStart( sal_Int32 n ) { mnStart = n; }
+ sal_Int32 GetStart() const { return mnStart; }
+
+ void SetEnd( sal_Int32 n ) { mnEnd = n; }
+ sal_Int32 GetEnd() const { return mnEnd; }
+
+ void SetStartPortion( std::size_t n ) { mnStartPortion = n; }
+ std::size_t GetStartPortion() const { return mnStartPortion; }
+
+ void SetEndPortion( std::size_t n ) { mnEndPortion = n; }
+ std::size_t GetEndPortion() const { return mnEndPortion; }
+
+ sal_Int32 GetLen() const { return mnEnd - mnStart; }
+
+ bool IsInvalid() const { return mbInvalid; }
+ bool IsValid() const { return !mbInvalid; }
+ void SetInvalid() { mbInvalid = true; }
+ void SetValid() { mbInvalid = false; }
+
+ short GetStartX() const { return mnStartX; }
+ void SetStartX( short n ) { mnStartX = n; }
+
+ inline bool operator == ( const TextLine& rLine ) const;
+};
+
+inline bool TextLine::operator == ( const TextLine& rLine ) const
+{
+ return mnStart == rLine.mnStart &&
+ mnEnd == rLine.mnEnd &&
+ mnStartPortion == rLine.mnStartPortion &&
+ mnEndPortion == rLine.mnEndPortion;
+}
+
+class TEParaPortion
+{
+private:
+ TextNode* mpNode;
+
+ std::vector<TextLine> maLines;
+ TETextPortionList maTextPortions;
+ std::vector<TEWritingDirectionInfo> maWritingDirectionInfos;
+
+ sal_Int32 mnInvalidPosStart;
+ sal_Int32 mnInvalidDiff;
+
+ bool mbInvalid;
+ bool mbSimple; // only type linearly
+
+public:
+ TEParaPortion( TextNode* pNode );
+ ~TEParaPortion();
+
+ TEParaPortion( const TEParaPortion& ) = delete;
+ void operator=( const TEParaPortion& ) = delete;
+
+ bool IsInvalid() const { return mbInvalid; }
+ bool IsSimpleInvalid() const { return mbSimple; }
+ void SetNotSimpleInvalid() { mbSimple = false; }
+ void SetValid() { mbInvalid = false; mbSimple = true;}
+
+ void MarkInvalid( sal_Int32 nStart, sal_Int32 nDiff );
+ void MarkSelectionInvalid( sal_Int32 nStart );
+
+ sal_Int32 GetInvalidPosStart() const { return mnInvalidPosStart; }
+ sal_Int32 GetInvalidDiff() const { return mnInvalidDiff; }
+
+ TextNode* GetNode() const { return mpNode; }
+ std::vector<TextLine>& GetLines() { return maLines; }
+ TETextPortionList& GetTextPortions() { return maTextPortions; }
+ std::vector<TEWritingDirectionInfo>& GetWritingDirectionInfos() { return maWritingDirectionInfos; }
+
+ std::vector<TextLine>::size_type GetLineNumber( sal_Int32 nIndex, bool bInclEnd );
+ void CorrectValuesBehindLastFormattedLine( sal_uInt16 nLastFormattedLine );
+};
+
+class TEParaPortions
+{
+private:
+ std::vector<std::unique_ptr<TEParaPortion>> mvData;
+
+public:
+ TEParaPortions() : mvData() {}
+ ~TEParaPortions();
+
+ sal_uInt32 Count() const { return static_cast<sal_uInt32>(mvData.size()); }
+ TEParaPortion* GetObject( sal_uInt32 nIndex ) { return mvData[nIndex].get(); }
+ void Insert( TEParaPortion* pObject, sal_uInt32 nPos ) { mvData.emplace( mvData.begin()+nPos, pObject ); }
+ void Remove( sal_uInt32 nPos ) { mvData.erase( mvData.begin()+nPos ); }
+};
+
+class TextSelFunctionSet: public FunctionSet
+{
+private:
+ TextView* mpView;
+
+public:
+ TextSelFunctionSet( TextView* pView );
+
+ virtual void BeginDrag() override;
+
+ virtual void CreateAnchor() override;
+
+ virtual void SetCursorAtPoint( const Point& rPointPixel, bool bDontSelectAtCursor = false ) override;
+
+ virtual bool IsSelectionAtPoint( const Point& rPointPixel ) override;
+ virtual void DeselectAll() override;
+
+ virtual void DeselectAtPoint( const Point& ) override;
+ virtual void DestroyAnchor() override;
+};
+
+class IdleFormatter : public Idle
+{
+private:
+ TextView* mpView;
+ sal_uInt16 mnRestarts;
+
+public:
+ IdleFormatter();
+ virtual ~IdleFormatter() override;
+
+ void DoIdleFormat( TextView* pV, sal_uInt16 nMaxRestarts );
+ void ForceTimeout();
+ TextView* GetView() { return mpView; }
+};
+
+struct TextDDInfo
+{
+ vcl::Cursor maCursor;
+ TextPaM maDropPos;
+
+ bool mbStarterOfDD;
+ bool mbVisCursor;
+
+ TextDDInfo()
+ : maCursor()
+ , maDropPos()
+ , mbStarterOfDD {false}
+ , mbVisCursor {false}
+ {
+ maCursor.SetStyle( CURSOR_SHADOW );
+ }
+};
+
+#endif // INCLUDED_VCL_SOURCE_EDIT_TEXTDAT2_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textdata.cxx b/vcl/source/edit/textdata.cxx
new file mode 100644
index 000000000..3cf859704
--- /dev/null
+++ b/vcl/source/edit/textdata.cxx
@@ -0,0 +1,344 @@
+/* -*- 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 <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <cstddef>
+
+#include <vcl/textdata.hxx>
+#include "textdat2.hxx"
+
+
+TextSelection::TextSelection()
+{
+}
+
+TextSelection::TextSelection( const TextPaM& rPaM ) :
+ maStartPaM( rPaM ), maEndPaM( rPaM )
+{
+}
+
+TextSelection::TextSelection( const TextPaM& rStart, const TextPaM& rEnd ) :
+ maStartPaM( rStart ), maEndPaM( rEnd )
+{
+}
+
+void TextSelection::Justify()
+{
+ if ( maEndPaM < maStartPaM )
+ {
+ TextPaM aTemp( maStartPaM );
+ maStartPaM = maEndPaM;
+ maEndPaM = aTemp;
+ }
+}
+
+TETextPortionList::TETextPortionList()
+{
+}
+
+TETextPortionList::~TETextPortionList()
+{
+ Reset();
+}
+
+TETextPortion& TETextPortionList::operator[]( std::size_t nPos )
+{
+ return maPortions[ nPos ];
+}
+
+std::vector<TETextPortion>::iterator TETextPortionList::begin()
+{
+ return maPortions.begin();
+}
+
+std::vector<TETextPortion>::const_iterator TETextPortionList::begin() const
+{
+ return maPortions.begin();
+}
+
+std::vector<TETextPortion>::iterator TETextPortionList::end()
+{
+ return maPortions.end();
+}
+
+std::vector<TETextPortion>::const_iterator TETextPortionList::end() const
+{
+ return maPortions.end();
+}
+
+bool TETextPortionList::empty() const
+{
+ return maPortions.empty();
+}
+
+std::size_t TETextPortionList::size() const
+{
+ return maPortions.size();
+}
+
+std::vector<TETextPortion>::iterator TETextPortionList::erase( const std::vector<TETextPortion>::iterator& aIter )
+{
+ return maPortions.erase( aIter );
+}
+
+std::vector<TETextPortion>::iterator TETextPortionList::insert( const std::vector<TETextPortion>::iterator& aIter,
+ const TETextPortion& rTP )
+{
+ return maPortions.insert( aIter, rTP );
+}
+
+void TETextPortionList::push_back( const TETextPortion& rTP )
+{
+ maPortions.push_back( rTP );
+}
+
+void TETextPortionList::Reset()
+{
+ maPortions.clear();
+}
+
+void TETextPortionList::DeleteFromPortion( std::size_t nDelFrom )
+{
+ SAL_WARN_IF( ( nDelFrom >= maPortions.size() ) && ( (nDelFrom != 0) || (!maPortions.empty()) ), "vcl", "DeleteFromPortion: Out of range" );
+ maPortions.erase( maPortions.begin() + nDelFrom, maPortions.end() );
+}
+
+std::size_t TETextPortionList::FindPortion( sal_Int32 nCharPos, sal_Int32& nPortionStart, bool bPreferStartingPortion )
+{
+ // find left portion at nCharPos at portion border
+ sal_Int32 nTmpPos = 0;
+ for ( std::size_t nPortion = 0; nPortion < maPortions.size(); nPortion++ )
+ {
+ TETextPortion& rPortion = maPortions[ nPortion ];
+ nTmpPos += rPortion.GetLen();
+ if ( nTmpPos >= nCharPos )
+ {
+ // take this one if we don't prefer the starting portion, or if it's the last one
+ if ( ( nTmpPos != nCharPos ) || !bPreferStartingPortion || ( nPortion == maPortions.size() - 1 ) )
+ {
+ nPortionStart = nTmpPos - rPortion.GetLen();
+ return nPortion;
+ }
+ }
+ }
+ OSL_FAIL( "FindPortion: Not found!" );
+ return ( maPortions.size() - 1 );
+}
+
+TEParaPortion::TEParaPortion( TextNode* pN )
+ : mpNode {pN}
+ , mnInvalidPosStart {0}
+ , mnInvalidDiff {0}
+ , mbInvalid {true}
+ , mbSimple {false}
+{
+}
+
+TEParaPortion::~TEParaPortion()
+{
+}
+
+void TEParaPortion::MarkInvalid( sal_Int32 nStart, sal_Int32 nDiff )
+{
+ if ( !mbInvalid )
+ {
+ mnInvalidPosStart = ( nDiff >= 0 ) ? nStart : ( nStart + nDiff );
+ mnInvalidDiff = nDiff;
+ }
+ else
+ {
+ // simple consecutive typing
+ if ( ( nDiff > 0 ) && ( mnInvalidDiff > 0 ) &&
+ ( ( mnInvalidPosStart+mnInvalidDiff ) == nStart ) )
+ {
+ mnInvalidDiff = mnInvalidDiff + nDiff;
+ }
+ // simple consecutive deleting
+ else if ( ( nDiff < 0 ) && ( mnInvalidDiff < 0 ) && ( mnInvalidPosStart == nStart ) )
+ {
+ mnInvalidPosStart = mnInvalidPosStart + nDiff;
+ mnInvalidDiff = mnInvalidDiff + nDiff;
+ }
+ else
+ {
+ SAL_WARN_IF( ( nDiff < 0 ) && ( (nStart+nDiff) < 0 ), "vcl", "MarkInvalid: Diff out of Range" );
+ mnInvalidPosStart = std::min( mnInvalidPosStart, nDiff < 0 ? nStart+nDiff : nDiff );
+ mnInvalidDiff = 0;
+ mbSimple = false;
+ }
+ }
+
+ maWritingDirectionInfos.clear();
+
+ mbInvalid = true;
+}
+
+void TEParaPortion::MarkSelectionInvalid( sal_Int32 nStart )
+{
+ if ( !mbInvalid )
+ {
+ mnInvalidPosStart = nStart;
+ }
+ else
+ {
+ mnInvalidPosStart = std::min( mnInvalidPosStart, nStart );
+ }
+
+ maWritingDirectionInfos.clear();
+
+ mnInvalidDiff = 0;
+ mbInvalid = true;
+ mbSimple = false;
+}
+
+std::vector<TextLine>::size_type TEParaPortion::GetLineNumber( sal_Int32 nChar, bool bInclEnd )
+{
+ for ( std::vector<TextLine>::size_type nLine = 0; nLine < maLines.size(); nLine++ )
+ {
+ TextLine& rLine = maLines[ nLine ];
+ if ( ( bInclEnd && ( rLine.GetEnd() >= nChar ) ) ||
+ ( rLine.GetEnd() > nChar ) )
+ {
+ return nLine;
+ }
+ }
+
+ // Then it should be at the end of the last line
+ OSL_ENSURE(nChar == maLines.back().GetEnd(), "wrong Index");
+ OSL_ENSURE(!bInclEnd, "Line not found: FindLine");
+ return ( maLines.size() - 1 );
+}
+
+void TEParaPortion::CorrectValuesBehindLastFormattedLine( sal_uInt16 nLastFormattedLine )
+{
+ sal_uInt16 nLines = maLines.size();
+ SAL_WARN_IF( !nLines, "vcl", "CorrectPortionNumbersFromLine: Empty portion?" );
+ if ( nLastFormattedLine >= ( nLines - 1 ) )
+ return;
+
+ const TextLine& rLastFormatted = maLines[ nLastFormattedLine ];
+ const TextLine& rUnformatted = maLines[ nLastFormattedLine+1 ];
+ std::ptrdiff_t nPortionDiff = rUnformatted.GetStartPortion() - rLastFormatted.GetEndPortion();
+ sal_Int32 nTextDiff = rUnformatted.GetStart() - rLastFormatted.GetEnd();
+ nTextDiff++; // LastFormatted.GetEnd() was inclusive => subtracted one too much!
+
+ // The first unformatted one has to start exactly one portion past the last
+ // formatted one.
+ // If a portion got split in the changed row, nLastEnd could be > nNextStart!
+ std::ptrdiff_t nPDiff = -( nPortionDiff-1 );
+ const sal_Int32 nTDiff = -( nTextDiff-1 );
+ if ( !(nPDiff || nTDiff) )
+ return;
+
+ for ( sal_uInt16 nL = nLastFormattedLine+1; nL < nLines; nL++ )
+ {
+ TextLine& rLine = maLines[ nL ];
+
+ rLine.SetStartPortion(rLine.GetStartPortion() + nPDiff);
+ rLine.SetEndPortion(rLine.GetEndPortion() + nPDiff);
+
+ rLine.SetStart(rLine.GetStart() + nTDiff);
+ rLine.SetEnd(rLine.GetEnd() + nTDiff);
+
+ rLine.SetValid();
+ }
+}
+
+TEParaPortions::~TEParaPortions()
+{
+}
+
+IdleFormatter::IdleFormatter()
+ : Idle("vcl::TextEngine mpIdleFormatter")
+ , mpView(nullptr)
+ , mnRestarts(0)
+{
+ SetPriority(TaskPriority::HIGH_IDLE);
+}
+
+IdleFormatter::~IdleFormatter()
+{
+ mpView = nullptr;
+}
+
+void IdleFormatter::DoIdleFormat( TextView* pV, sal_uInt16 nMaxRestarts )
+{
+ mpView = pV;
+
+ if ( IsActive() )
+ mnRestarts++;
+
+ if ( mnRestarts > nMaxRestarts )
+ {
+ mnRestarts = 0;
+ Invoke();
+ }
+ else
+ {
+ Start();
+ }
+}
+
+void IdleFormatter::ForceTimeout()
+{
+ if ( IsActive() )
+ {
+ Stop();
+ mnRestarts = 0;
+ Invoke();
+ }
+}
+
+TextHint::TextHint( SfxHintId Id ) : SfxHint( Id ), mnValue(0)
+{
+}
+
+TextHint::TextHint( SfxHintId Id, sal_Int32 nValue ) : SfxHint( Id ), mnValue(nValue)
+{
+}
+
+TEIMEInfos::TEIMEInfos( const TextPaM& rPos, const OUString& rOldTextAfterStartPos )
+ : aOldTextAfterStartPos(rOldTextAfterStartPos)
+ , aPos(rPos)
+ , nLen(0)
+ , bWasCursorOverwrite(false)
+{
+}
+
+TEIMEInfos::~TEIMEInfos()
+{
+}
+
+void TEIMEInfos::CopyAttribs(const ExtTextInputAttr* pA, sal_Int32 nL)
+{
+ nLen = nL;
+ pAttribs.reset( new ExtTextInputAttr[ nL ] );
+ memcpy( pAttribs.get(), pA, nL*sizeof(ExtTextInputAttr) );
+}
+
+void TEIMEInfos::DestroyAttribs()
+{
+ pAttribs.reset();
+ nLen = 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textdoc.cxx b/vcl/source/edit/textdoc.cxx
new file mode 100644
index 000000000..9e0c80b96
--- /dev/null
+++ b/vcl/source/edit/textdoc.cxx
@@ -0,0 +1,536 @@
+/* -*- 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 <memory>
+#include "textdoc.hxx"
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <rtl/ustrbuf.hxx>
+
+// compare function called by QuickSort
+static bool CompareStart( const std::unique_ptr<TextCharAttrib>& pFirst, const std::unique_ptr<TextCharAttrib>& pSecond )
+{
+ return pFirst->GetStart() < pSecond->GetStart();
+}
+
+TextCharAttrib::TextCharAttrib( const TextAttrib& rAttr, sal_Int32 nStart, sal_Int32 nEnd )
+ : mpAttr(rAttr.Clone())
+ , mnStart(nStart)
+ , mnEnd(nEnd)
+{
+}
+
+TextCharAttrib::TextCharAttrib( const TextCharAttrib& rTextCharAttrib )
+ : mpAttr(rTextCharAttrib.mpAttr->Clone())
+ , mnStart(rTextCharAttrib.mnStart)
+ , mnEnd(rTextCharAttrib.mnEnd)
+{
+}
+
+TextCharAttribList::TextCharAttribList()
+ : mbHasEmptyAttribs(false)
+{
+}
+
+TextCharAttribList::~TextCharAttribList()
+{
+ // PTRARR_DEL
+}
+
+void TextCharAttribList::Clear()
+{
+ maAttribs.clear();
+}
+
+void TextCharAttribList::InsertAttrib( std::unique_ptr<TextCharAttrib> pAttrib )
+{
+ if ( pAttrib->IsEmpty() )
+ mbHasEmptyAttribs = true;
+
+ const sal_Int32 nStart = pAttrib->GetStart(); // maybe better for Comp.Opt.
+ bool bInserted = false;
+ auto it = std::find_if(maAttribs.begin(), maAttribs.end(),
+ [nStart](std::unique_ptr<TextCharAttrib>& rAttrib) { return rAttrib->GetStart() > nStart; });
+ if (it != maAttribs.end())
+ {
+ maAttribs.insert( it, std::move(pAttrib) );
+ bInserted = true;
+ }
+ if ( !bInserted )
+ maAttribs.push_back( std::move(pAttrib) );
+}
+
+void TextCharAttribList::ResortAttribs()
+{
+ std::sort( maAttribs.begin(), maAttribs.end(), CompareStart );
+}
+
+TextCharAttrib* TextCharAttribList::FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos )
+{
+ for (std::vector<std::unique_ptr<TextCharAttrib> >::reverse_iterator it = maAttribs.rbegin(); it != maAttribs.rend(); ++it)
+ {
+ if ( (*it)->GetEnd() < nPos )
+ return nullptr;
+
+ if ( ( (*it)->Which() == nWhich ) && (*it)->IsIn(nPos) )
+ return it->get();
+ }
+ return nullptr;
+}
+
+bool TextCharAttribList::HasBoundingAttrib( sal_Int32 nBound )
+{
+ for (std::vector<std::unique_ptr<TextCharAttrib> >::reverse_iterator it = maAttribs.rbegin(); it != maAttribs.rend(); ++it)
+ {
+ if ( (*it)->GetEnd() < nBound )
+ return false;
+
+ if ( ( (*it)->GetStart() == nBound ) || ( (*it)->GetEnd() == nBound ) )
+ return true;
+ }
+ return false;
+}
+
+TextCharAttrib* TextCharAttribList::FindEmptyAttrib( sal_uInt16 nWhich, sal_Int32 nPos )
+{
+ if ( !mbHasEmptyAttribs )
+ return nullptr;
+
+ for (auto const& attrib : maAttribs)
+ {
+ if ( attrib->GetStart() > nPos )
+ return nullptr;
+
+ if ( ( attrib->GetStart() == nPos ) && ( attrib->GetEnd() == nPos ) && ( attrib->Which() == nWhich ) )
+ return attrib.get();
+ }
+ return nullptr;
+}
+
+void TextCharAttribList::DeleteEmptyAttribs()
+{
+ maAttribs.erase(
+ std::remove_if( maAttribs.begin(), maAttribs.end(),
+ [] (const std::unique_ptr<TextCharAttrib>& rAttrib) { return rAttrib->IsEmpty(); } ),
+ maAttribs.end() );
+ mbHasEmptyAttribs = false;
+}
+
+TextNode::TextNode( const OUString& rText ) :
+ maText( rText )
+{
+}
+
+void TextNode::ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNew )
+{
+ if ( !nNew )
+ return;
+
+ bool bResort = false;
+ sal_uInt16 nAttribs = maCharAttribs.Count();
+ for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
+ {
+ TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
+ if ( rAttrib.GetEnd() >= nIndex )
+ {
+ // move all attributes that are behind the cursor
+ if ( rAttrib.GetStart() > nIndex )
+ {
+ rAttrib.MoveForward( nNew );
+ }
+ // 0: expand empty attribute, if at cursor
+ else if ( rAttrib.IsEmpty() )
+ {
+ // Do not check the index; empty one may only be here.
+ // If checking later anyway, special case:
+ // Start == 0; AbsLen == 1, nNew = 1 => Expand due to new paragraph!
+ // Start <= nIndex, End >= nIndex => Start=End=nIndex!
+ rAttrib.Expand( nNew );
+ }
+ // 1: attribute starts before and reaches up to index
+ else if ( rAttrib.GetEnd() == nIndex ) // start must be before
+ {
+ // Only expand if no feature and not in Exclude list!
+ // Otherwise e.g. an UL would go until the new ULDB, thus expand both.
+ if ( !maCharAttribs.FindEmptyAttrib( rAttrib.Which(), nIndex ) )
+ {
+ rAttrib.Expand( nNew );
+ }
+ else
+ bResort = true;
+ }
+ // 2: attribute starts before and reaches past the index
+ else if ( ( rAttrib.GetStart() < nIndex ) && ( rAttrib.GetEnd() > nIndex ) )
+ {
+ rAttrib.Expand( nNew );
+ }
+ // 3: attribute starts at Index
+ else if ( rAttrib.GetStart() == nIndex )
+ {
+ if ( nIndex == 0 )
+ {
+ rAttrib.Expand( nNew );
+ }
+ else
+ rAttrib.MoveForward( nNew );
+ }
+ }
+
+ SAL_WARN_IF( rAttrib.GetStart() > rAttrib.GetEnd(), "vcl", "Expand: attribute twisted!" );
+ SAL_WARN_IF( ( rAttrib.GetEnd() > maText.getLength() ), "vcl", "Expand: attribute greater than paragraph!" );
+ SAL_WARN_IF( rAttrib.IsEmpty(), "vcl", "Empty attribute after ExpandAttribs?" );
+ }
+
+ if ( bResort )
+ maCharAttribs.ResortAttribs();
+}
+
+void TextNode::CollapseAttribs( sal_Int32 nIndex, sal_Int32 nDeleted )
+{
+ if ( !nDeleted )
+ return;
+
+ bool bResort = false;
+ const sal_Int32 nEndChanges = nIndex+nDeleted;
+
+ for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
+ {
+ TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
+ bool bDelAttr = false;
+ if ( rAttrib.GetEnd() >= nIndex )
+ {
+ // move all attributes that are behind the cursor
+ if ( rAttrib.GetStart() >= nEndChanges )
+ {
+ rAttrib.MoveBackward( nDeleted );
+ }
+ // 1. delete inner attributes
+ else if ( ( rAttrib.GetStart() >= nIndex ) && ( rAttrib.GetEnd() <= nEndChanges ) )
+ {
+ // special case: attribute covers the region exactly
+ // => keep as an empty attribute
+ if ( ( rAttrib.GetStart() == nIndex ) && ( rAttrib.GetEnd() == nEndChanges ) )
+ rAttrib.SetEnd(nIndex); // empty
+ else
+ bDelAttr = true;
+ }
+ // 2. attribute starts before, ends inside or after
+ else if ( ( rAttrib.GetStart() <= nIndex ) && ( rAttrib.GetEnd() > nIndex ) )
+ {
+ if ( rAttrib.GetEnd() <= nEndChanges ) // ends inside
+ rAttrib.SetEnd(nIndex);
+ else
+ rAttrib.Collaps( nDeleted ); // ends after
+ }
+ // 3. attribute starts inside, ends after
+ else if ( ( rAttrib.GetStart() >= nIndex ) && ( rAttrib.GetEnd() > nEndChanges ) )
+ {
+ // features are not allowed to expand!
+ rAttrib.SetStart(nEndChanges);
+ rAttrib.MoveBackward( nDeleted );
+ }
+ }
+
+ SAL_WARN_IF( rAttrib.GetStart() > rAttrib.GetEnd(), "vcl", "Collaps: attribute twisted!" );
+ SAL_WARN_IF( ( rAttrib.GetEnd() > maText.getLength()) && !bDelAttr, "vcl", "Collaps: attribute greater than paragraph!" );
+ if ( bDelAttr /* || rAttrib.IsEmpty() */ )
+ {
+ bResort = true;
+ maCharAttribs.RemoveAttrib( nAttr );
+ nAttr--;
+ }
+ else if ( rAttrib.IsEmpty() )
+ maCharAttribs.HasEmptyAttribs() = true;
+ }
+
+ if ( bResort )
+ maCharAttribs.ResortAttribs();
+}
+
+void TextNode::InsertText( sal_Int32 nPos, const OUString& rText )
+{
+ maText = maText.replaceAt( nPos, 0, rText );
+ ExpandAttribs( nPos, rText.getLength() );
+}
+
+void TextNode::InsertText( sal_Int32 nPos, sal_Unicode c )
+{
+ maText = maText.replaceAt( nPos, 0, rtl::OUStringChar(c) );
+ ExpandAttribs( nPos, 1 );
+}
+
+void TextNode::RemoveText( sal_Int32 nPos, sal_Int32 nChars )
+{
+ maText = maText.replaceAt( nPos, nChars, u"" );
+ CollapseAttribs( nPos, nChars );
+}
+
+std::unique_ptr<TextNode> TextNode::Split( sal_Int32 nPos )
+{
+ OUString aNewText;
+ if ( nPos < maText.getLength() )
+ {
+ aNewText = maText.copy( nPos );
+ maText = maText.copy(0, nPos);
+ }
+ std::unique_ptr<TextNode> pNew(new TextNode( aNewText ));
+
+ for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
+ {
+ TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
+ if ( rAttrib.GetEnd() < nPos )
+ {
+ // no change
+ ;
+ }
+ else if ( rAttrib.GetEnd() == nPos )
+ {
+ // must be copied as an empty attribute
+ // !FindAttrib only sensible if traversing backwards through the list!
+ if ( !pNew->maCharAttribs.FindAttrib( rAttrib.Which(), 0 ) )
+ {
+ std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
+ pNewAttrib->SetStart(0);
+ pNewAttrib->SetEnd(0);
+ pNew->maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
+ }
+ }
+ else if ( rAttrib.IsInside( nPos ) || ( !nPos && !rAttrib.GetStart() ) )
+ {
+ // If cutting at the very beginning, the attribute has to be
+ // copied and changed
+ std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
+ pNewAttrib->SetStart(0);
+ pNewAttrib->SetEnd(rAttrib.GetEnd()-nPos);
+ pNew->maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
+ // trim
+ rAttrib.SetEnd(nPos);
+ }
+ else
+ {
+ SAL_WARN_IF( rAttrib.GetStart() < nPos, "vcl", "Start < nPos!" );
+ SAL_WARN_IF( rAttrib.GetEnd() < nPos, "vcl", "End < nPos!" );
+ // move all into the new node (this)
+ pNew->maCharAttribs.InsertAttrib(maCharAttribs.RemoveAttrib(nAttr));
+ rAttrib.SetStart( rAttrib.GetStart() - nPos );
+ rAttrib.SetEnd( rAttrib.GetEnd() - nPos );
+ nAttr--;
+ }
+ }
+ return pNew;
+}
+
+void TextNode::Append( const TextNode& rNode )
+{
+ sal_Int32 nOldLen = maText.getLength();
+
+ maText += rNode.GetText();
+
+ const sal_uInt16 nAttribs = rNode.GetCharAttribs().Count();
+ for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
+ {
+ const TextCharAttrib& rAttrib = rNode.GetCharAttrib( nAttr );
+ bool bMelted = false;
+ if ( rAttrib.GetStart() == 0 )
+ {
+ // potentially merge attributes
+ sal_uInt16 nTmpAttribs = maCharAttribs.Count();
+ for ( sal_uInt16 nTmpAttr = 0; nTmpAttr < nTmpAttribs; nTmpAttr++ )
+ {
+ TextCharAttrib& rTmpAttrib = maCharAttribs.GetAttrib( nTmpAttr );
+
+ if ( rTmpAttrib.GetEnd() == nOldLen )
+ {
+ if ( ( rTmpAttrib.Which() == rAttrib.Which() ) &&
+ ( rTmpAttrib.GetAttr() == rAttrib.GetAttr() ) )
+ {
+ rTmpAttrib.SetEnd( rTmpAttrib.GetEnd() + rAttrib.GetLen() );
+ bMelted = true;
+ break; // there can be only one of this type at this position
+ }
+ }
+ }
+ }
+
+ if ( !bMelted )
+ {
+ std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
+ pNewAttrib->SetStart( pNewAttrib->GetStart() + nOldLen );
+ pNewAttrib->SetEnd( pNewAttrib->GetEnd() + nOldLen );
+ maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
+ }
+ }
+}
+
+TextDoc::TextDoc()
+ : mnLeftMargin(0)
+{
+};
+
+TextDoc::~TextDoc()
+{
+ DestroyTextNodes();
+}
+
+void TextDoc::Clear()
+{
+ DestroyTextNodes();
+}
+
+void TextDoc::DestroyTextNodes()
+{
+ maTextNodes.clear();
+}
+
+OUString TextDoc::GetText( const sal_Unicode* pSep ) const
+{
+ sal_uInt32 nNodes = static_cast<sal_uInt32>(maTextNodes.size());
+
+ OUStringBuffer aASCIIText;
+ const sal_uInt32 nLastNode = nNodes-1;
+ for ( sal_uInt32 nNode = 0; nNode < nNodes; ++nNode )
+ {
+ TextNode* pNode = maTextNodes[ nNode ].get();
+ aASCIIText.append(pNode->GetText());
+ if ( pSep && ( nNode != nLastNode ) )
+ aASCIIText.append(pSep);
+ }
+
+ return aASCIIText.makeStringAndClear();
+}
+
+OUString TextDoc::GetText( sal_uInt32 nPara ) const
+{
+ TextNode* pNode = ( nPara < maTextNodes.size() ) ? maTextNodes[ nPara ].get() : nullptr;
+ if ( pNode )
+ return pNode->GetText();
+
+ return OUString();
+}
+
+sal_Int32 TextDoc::GetTextLen( const sal_Unicode* pSep, const TextSelection* pSel ) const
+{
+ sal_Int32 nLen = 0;
+ sal_uInt32 nNodes = static_cast<sal_uInt32>(maTextNodes.size());
+ if ( nNodes )
+ {
+ sal_uInt32 nStartNode = 0;
+ sal_uInt32 nEndNode = nNodes-1;
+ if ( pSel )
+ {
+ nStartNode = pSel->GetStart().GetPara();
+ nEndNode = pSel->GetEnd().GetPara();
+ }
+
+ for ( sal_uInt32 nNode = nStartNode; nNode <= nEndNode; ++nNode )
+ {
+ TextNode* pNode = maTextNodes[ nNode ].get();
+
+ sal_Int32 nS = 0;
+ sal_Int32 nE = pNode->GetText().getLength();
+ if ( pSel && ( nNode == pSel->GetStart().GetPara() ) )
+ nS = pSel->GetStart().GetIndex();
+ if ( pSel && ( nNode == pSel->GetEnd().GetPara() ) )
+ nE = pSel->GetEnd().GetIndex();
+
+ nLen += ( nE - nS );
+ }
+
+ if ( pSep )
+ nLen += (nEndNode-nStartNode) * rtl_ustr_getLength(pSep);
+ }
+
+ return nLen;
+}
+
+TextPaM TextDoc::InsertText( const TextPaM& rPaM, sal_Unicode c )
+{
+ SAL_WARN_IF( c == 0x0A, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
+ SAL_WARN_IF( c == 0x0D, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
+
+ TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
+ pNode->InsertText( rPaM.GetIndex(), c );
+
+ TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+1 );
+ return aPaM;
+}
+
+TextPaM TextDoc::InsertText( const TextPaM& rPaM, const OUString& rStr )
+{
+ SAL_WARN_IF( rStr.indexOf( 0x0A ) != -1, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
+ SAL_WARN_IF( rStr.indexOf( 0x0D ) != -1, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
+
+ TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
+ pNode->InsertText( rPaM.GetIndex(), rStr );
+
+ TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+rStr.getLength() );
+ return aPaM;
+}
+
+TextPaM TextDoc::InsertParaBreak( const TextPaM& rPaM )
+{
+ TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
+ std::unique_ptr<TextNode> pNew = pNode->Split( rPaM.GetIndex() );
+
+ SAL_WARN_IF( maTextNodes.size()>=SAL_MAX_UINT32, "vcl", "InsertParaBreak: more than 4Gi paragraphs!" );
+ maTextNodes.insert( maTextNodes.begin() + rPaM.GetPara() + 1, std::move(pNew) );
+
+ TextPaM aPaM( rPaM.GetPara()+1, 0 );
+ return aPaM;
+}
+
+TextPaM TextDoc::ConnectParagraphs( TextNode* pLeft, const TextNode* pRight )
+{
+ sal_Int32 nPrevLen = pLeft->GetText().getLength();
+ pLeft->Append( *pRight );
+
+ // the paragraph on the right vanishes
+ maTextNodes.erase( std::find_if( maTextNodes.begin(), maTextNodes.end(),
+ [&] (std::unique_ptr<TextNode> const & p) { return p.get() == pRight; } ) );
+
+ sal_Int32 nLeft = ::std::find_if( maTextNodes.begin(), maTextNodes.end(),
+ [&] (std::unique_ptr<TextNode> const & p) { return p.get() == pLeft; } )
+ - maTextNodes.begin();
+ TextPaM aPaM( nLeft, nPrevLen );
+ return aPaM;
+}
+
+void TextDoc::RemoveChars( const TextPaM& rPaM, sal_Int32 nChars )
+{
+ TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
+ pNode->RemoveText( rPaM.GetIndex(), nChars );
+}
+
+bool TextDoc::IsValidPaM( const TextPaM& rPaM )
+{
+ if ( rPaM.GetPara() >= maTextNodes.size() )
+ {
+ OSL_FAIL( "PaM: Para out of range" );
+ return false;
+ }
+ TextNode * pNode = maTextNodes[ rPaM.GetPara() ].get();
+ if ( rPaM.GetIndex() > pNode->GetText().getLength() )
+ {
+ OSL_FAIL( "PaM: Index out of range" );
+ return false;
+ }
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textdoc.hxx b/vcl/source/edit/textdoc.hxx
new file mode 100644
index 000000000..11657463e
--- /dev/null
+++ b/vcl/source/edit/textdoc.hxx
@@ -0,0 +1,129 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_EDIT_TEXTDOC_HXX
+#define INCLUDED_VCL_SOURCE_EDIT_TEXTDOC_HXX
+
+#include <rtl/ustring.hxx>
+#include <vcl/textdata.hxx>
+#include <vcl/txtattr.hxx>
+#include <vector>
+#include <memory>
+
+class TextCharAttribList
+{
+private:
+ TextCharAttribList(const TextCharAttribList&) = delete;
+ TextCharAttribList& operator=(const TextCharAttribList&) = delete;
+
+ std::vector<std::unique_ptr<TextCharAttrib> > maAttribs;
+ bool mbHasEmptyAttribs;
+
+public:
+ TextCharAttribList();
+ ~TextCharAttribList();
+
+ void Clear();
+ sal_uInt16 Count() const { return maAttribs.size(); }
+
+ const TextCharAttrib& GetAttrib( sal_uInt16 n ) const { return *maAttribs[n]; }
+ TextCharAttrib& GetAttrib( sal_uInt16 n ) { return *maAttribs[n]; }
+ std::unique_ptr<TextCharAttrib> RemoveAttrib( sal_uInt16 n )
+ {
+ std::unique_ptr<TextCharAttrib> pReleased = std::move(maAttribs[n]);
+ maAttribs.erase( maAttribs.begin() + n );
+ return pReleased;
+ }
+
+ void InsertAttrib( std::unique_ptr<TextCharAttrib> pAttrib );
+
+ void DeleteEmptyAttribs();
+ void ResortAttribs();
+
+ bool& HasEmptyAttribs() { return mbHasEmptyAttribs; }
+
+ TextCharAttrib* FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos );
+ TextCharAttrib* FindEmptyAttrib( sal_uInt16 nWhich, sal_Int32 nPos );
+ bool HasBoundingAttrib( sal_Int32 nBound );
+};
+
+class TextNode
+{
+ OUString maText;
+ TextCharAttribList maCharAttribs;
+
+ void ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNewChars );
+ void CollapseAttribs( sal_Int32 nIndex, sal_Int32 nDelChars );
+
+public:
+ TextNode( const OUString& rText );
+
+ TextNode( const TextNode& ) = delete;
+ void operator=( const TextNode& ) = delete;
+
+ const OUString& GetText() const { return maText; }
+
+ const TextCharAttrib& GetCharAttrib(sal_uInt16 nPos) const { return maCharAttribs.GetAttrib(nPos); }
+ const TextCharAttribList& GetCharAttribs() const { return maCharAttribs; }
+ TextCharAttribList& GetCharAttribs() { return maCharAttribs; }
+
+ void InsertText( sal_Int32 nPos, const OUString& rText );
+ void InsertText( sal_Int32 nPos, sal_Unicode c );
+ void RemoveText( sal_Int32 nPos, sal_Int32 nChars );
+
+ std::unique_ptr<TextNode> Split( sal_Int32 nPos );
+ void Append( const TextNode& rNode );
+};
+
+class TextDoc
+{
+ std::vector<std::unique_ptr<TextNode>> maTextNodes;
+ sal_uInt16 mnLeftMargin;
+
+ void DestroyTextNodes();
+
+public:
+ TextDoc();
+ ~TextDoc();
+
+ void Clear();
+
+ std::vector<std::unique_ptr<TextNode>>& GetNodes() { return maTextNodes; }
+ const std::vector<std::unique_ptr<TextNode>>& GetNodes() const { return maTextNodes; }
+
+ void RemoveChars( const TextPaM& rPaM, sal_Int32 nChars );
+ TextPaM InsertText( const TextPaM& rPaM, sal_Unicode c );
+ TextPaM InsertText( const TextPaM& rPaM, const OUString& rStr );
+
+ TextPaM InsertParaBreak( const TextPaM& rPaM );
+ TextPaM ConnectParagraphs( TextNode* pLeft, const TextNode* pRight );
+
+ sal_Int32 GetTextLen( const sal_Unicode* pSep, const TextSelection* pSel = nullptr ) const;
+ OUString GetText( const sal_Unicode* pSep ) const;
+ OUString GetText( sal_uInt32 nPara ) const;
+
+ void SetLeftMargin( sal_uInt16 n ) { mnLeftMargin = n; }
+ sal_uInt16 GetLeftMargin() const { return mnLeftMargin; }
+
+ bool IsValidPaM( const TextPaM& rPaM );
+};
+
+#endif // INCLUDED_VCL_SOURCE_EDIT_TEXTDOC_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/texteng.cxx b/vcl/source/edit/texteng.cxx
new file mode 100644
index 000000000..6171e0417
--- /dev/null
+++ b/vcl/source/edit/texteng.cxx
@@ -0,0 +1,2895 @@
+/* -*- 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 <tools/stream.hxx>
+
+#include <vcl/texteng.hxx>
+#include <vcl/textview.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/inputctx.hxx>
+#include "textdoc.hxx"
+#include "textdat2.hxx"
+#include "textundo.hxx"
+#include "textund2.hxx"
+#include <svl/ctloptions.hxx>
+#include <vcl/window.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/virdev.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+
+#include <com/sun/star/i18n/WordType.hpp>
+
+#include <com/sun/star/i18n/InputSequenceChecker.hpp>
+#include <com/sun/star/i18n/InputSequenceCheckMode.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+
+#include <comphelper/processfactory.hxx>
+
+#include <unotools/localedatawrapper.hxx>
+#include <vcl/unohelp.hxx>
+
+#include <vcl/svapp.hxx>
+
+#include <unicode/ubidi.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdlib>
+#include <memory>
+#include <o3tl/sorted_vector.hxx>
+#include <string_view>
+#include <vector>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+TextEngine::TextEngine()
+ : mpActiveView {nullptr}
+ , maTextColor {COL_BLACK}
+ , mnMaxTextLen {0}
+ , mnMaxTextWidth {0}
+ , mnCharHeight {0}
+ , mnCurTextWidth {-1}
+ , mnCurTextHeight {0}
+ , mnDefTab {0}
+ , meAlign {TxtAlign::Left}
+ , mbIsFormatting {false}
+ , mbFormatted {false}
+ , mbUpdate {true}
+ , mbModified {false}
+ , mbUndoEnabled {false}
+ , mbIsInUndo {false}
+ , mbDowning {false}
+ , mbRightToLeft {false}
+ , mbHasMultiLineParas {false}
+{
+ mpViews.reset( new TextViews );
+
+ mpIdleFormatter.reset( new IdleFormatter );
+ mpIdleFormatter->SetInvokeHandler( LINK( this, TextEngine, IdleFormatHdl ) );
+
+ mpRefDev = VclPtr<VirtualDevice>::Create();
+
+ ImpInitLayoutMode( mpRefDev );
+
+ ImpInitDoc();
+
+ vcl::Font aFont(mpRefDev->GetFont().GetFamilyName(), Size(0, 0));
+ aFont.SetTransparent( false );
+ Color aFillColor( aFont.GetFillColor() );
+ aFillColor.SetAlpha( 255 );
+ aFont.SetFillColor( aFillColor );
+ SetFont( aFont );
+}
+
+TextEngine::~TextEngine()
+{
+ mbDowning = true;
+
+ mpIdleFormatter.reset();
+ mpDoc.reset();
+ mpTEParaPortions.reset();
+ mpViews.reset(); // only the list, not the Views
+ mpRefDev.disposeAndClear();
+ mpUndoManager.reset();
+ mpIMEInfos.reset();
+ mpLocaleDataWrapper.reset();
+}
+
+void TextEngine::InsertView( TextView* pTextView )
+{
+ mpViews->push_back( pTextView );
+ pTextView->SetSelection( TextSelection() );
+
+ if ( !GetActiveView() )
+ SetActiveView( pTextView );
+}
+
+void TextEngine::RemoveView( TextView* pTextView )
+{
+ TextViews::iterator it = std::find( mpViews->begin(), mpViews->end(), pTextView );
+ if( it != mpViews->end() )
+ {
+ pTextView->HideCursor();
+ mpViews->erase( it );
+ if ( pTextView == GetActiveView() )
+ SetActiveView( nullptr );
+ }
+}
+
+sal_uInt16 TextEngine::GetViewCount() const
+{
+ return mpViews->size();
+}
+
+TextView* TextEngine::GetView( sal_uInt16 nView ) const
+{
+ return (*mpViews)[ nView ];
+}
+
+
+void TextEngine::SetActiveView( TextView* pTextView )
+{
+ if ( pTextView != mpActiveView )
+ {
+ if ( mpActiveView )
+ mpActiveView->HideSelection();
+
+ mpActiveView = pTextView;
+
+ if ( mpActiveView )
+ mpActiveView->ShowSelection();
+ }
+}
+
+void TextEngine::SetFont( const vcl::Font& rFont )
+{
+ if ( rFont == maFont )
+ return;
+
+ maFont = rFont;
+ // #i40221# As the font's color now defaults to transparent (since i35764)
+ // we have to choose a useful textcolor in this case.
+ // Otherwise maTextColor and maFont.GetColor() are both transparent...
+ if( rFont.GetColor() == COL_TRANSPARENT )
+ maTextColor = COL_BLACK;
+ else
+ maTextColor = rFont.GetColor();
+
+ // Do not allow transparent fonts because of selection
+ // (otherwise delete the background in ImplPaint later differently)
+ maFont.SetTransparent( false );
+ // Tell VCL not to use the font color, use text color from OutputDevice
+ maFont.SetColor( COL_TRANSPARENT );
+ Color aFillColor( maFont.GetFillColor() );
+ aFillColor.SetAlpha( 255 );
+ maFont.SetFillColor( aFillColor );
+
+ maFont.SetAlignment( ALIGN_TOP );
+ mpRefDev->SetFont( maFont );
+ mnDefTab = mpRefDev->GetTextWidth(" ");
+ if ( !mnDefTab )
+ mnDefTab = mpRefDev->GetTextWidth("XXXX");
+ if ( !mnDefTab )
+ mnDefTab = 1;
+ mnCharHeight = mpRefDev->GetTextHeight();
+
+ FormatFullDoc();
+ UpdateViews();
+
+ for ( auto nView = mpViews->size(); nView; )
+ {
+ TextView* pView = (*mpViews)[ --nView ];
+ pView->GetWindow()->SetInputContext( InputContext( GetFont(), !pView->IsReadOnly() ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE ) );
+ }
+
+}
+
+void TextEngine::SetMaxTextLen( sal_Int32 nLen )
+{
+ mnMaxTextLen = nLen>=0 ? nLen : EDIT_NOLIMIT;
+}
+
+void TextEngine::SetMaxTextWidth( tools::Long nMaxWidth )
+{
+ if ( nMaxWidth>=0 && nMaxWidth != mnMaxTextWidth )
+ {
+ mnMaxTextWidth = nMaxWidth;
+ FormatFullDoc();
+ UpdateViews();
+ }
+}
+
+const sal_Unicode static_aLFText[] = { '\n', 0 };
+const sal_Unicode static_aCRText[] = { '\r', 0 };
+const sal_Unicode static_aCRLFText[] = { '\r', '\n', 0 };
+
+static const sal_Unicode* static_getLineEndText( LineEnd aLineEnd )
+{
+ const sal_Unicode* pRet = nullptr;
+
+ switch( aLineEnd )
+ {
+ case LINEEND_LF:
+ pRet = static_aLFText;
+ break;
+ case LINEEND_CR:
+ pRet = static_aCRText;
+ break;
+ case LINEEND_CRLF:
+ pRet = static_aCRLFText;
+ break;
+ }
+ return pRet;
+}
+
+void TextEngine::ReplaceText(const TextSelection& rSel, const OUString& rText)
+{
+ ImpInsertText( rSel, rText );
+}
+
+OUString TextEngine::GetText( LineEnd aSeparator ) const
+{
+ return mpDoc->GetText( static_getLineEndText( aSeparator ) );
+}
+
+OUString TextEngine::GetTextLines( LineEnd aSeparator ) const
+{
+ OUStringBuffer aText;
+ const sal_uInt32 nParas = mpTEParaPortions->Count();
+ const sal_Unicode* pSep = static_getLineEndText( aSeparator );
+ for ( sal_uInt32 nP = 0; nP < nParas; ++nP )
+ {
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nP );
+
+ const size_t nLines = pTEParaPortion->GetLines().size();
+ for ( size_t nL = 0; nL < nLines; ++nL )
+ {
+ TextLine& rLine = pTEParaPortion->GetLines()[nL];
+ aText.append( pTEParaPortion->GetNode()->GetText().subView(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()) );
+ if ( pSep && ( ( (nP+1) < nParas ) || ( (nL+1) < nLines ) ) )
+ aText.append(pSep);
+ }
+ }
+ return aText.makeStringAndClear();
+}
+
+OUString TextEngine::GetText( sal_uInt32 nPara ) const
+{
+ return mpDoc->GetText( nPara );
+}
+
+sal_Int32 TextEngine::GetTextLen() const
+{
+ return mpDoc->GetTextLen( static_getLineEndText( LINEEND_LF ) );
+}
+
+sal_Int32 TextEngine::GetTextLen( const TextSelection& rSel ) const
+{
+ TextSelection aSel( rSel );
+ aSel.Justify();
+ ValidateSelection( aSel );
+ return mpDoc->GetTextLen( static_getLineEndText( LINEEND_LF ), &aSel );
+}
+
+sal_Int32 TextEngine::GetTextLen( const sal_uInt32 nPara ) const
+{
+ return mpDoc->GetNodes()[ nPara ]->GetText().getLength();
+}
+
+void TextEngine::SetUpdateMode( bool bUpdate )
+{
+ if ( bUpdate != mbUpdate )
+ {
+ mbUpdate = bUpdate;
+ if ( mbUpdate )
+ {
+ FormatAndUpdate( GetActiveView() );
+ if ( GetActiveView() )
+ GetActiveView()->ShowCursor();
+ }
+ }
+}
+
+bool TextEngine::DoesKeyChangeText( const KeyEvent& rKeyEvent )
+{
+ bool bDoesChange = false;
+
+ KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction();
+ if ( eFunc != KeyFuncType::DONTKNOW )
+ {
+ switch ( eFunc )
+ {
+ case KeyFuncType::UNDO:
+ case KeyFuncType::REDO:
+ case KeyFuncType::CUT:
+ case KeyFuncType::PASTE:
+ bDoesChange = true;
+ break;
+ default:
+ // might get handled below
+ eFunc = KeyFuncType::DONTKNOW;
+ }
+ }
+ if ( eFunc == KeyFuncType::DONTKNOW )
+ {
+ switch ( rKeyEvent.GetKeyCode().GetCode() )
+ {
+ case KEY_DELETE:
+ case KEY_BACKSPACE:
+ if ( !rKeyEvent.GetKeyCode().IsMod2() )
+ bDoesChange = true;
+ break;
+ case KEY_RETURN:
+ case KEY_TAB:
+ if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() )
+ bDoesChange = true;
+ break;
+ default:
+ bDoesChange = TextEngine::IsSimpleCharInput( rKeyEvent );
+ }
+ }
+ return bDoesChange;
+}
+
+bool TextEngine::IsSimpleCharInput( const KeyEvent& rKeyEvent )
+{
+ return rKeyEvent.GetCharCode() >= 32 && rKeyEvent.GetCharCode() != 127 &&
+ KEY_MOD1 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT) && // (ssa) #i45714#:
+ KEY_MOD2 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT); // check for Ctrl and Alt separately
+}
+
+void TextEngine::ImpInitDoc()
+{
+ if ( mpDoc )
+ mpDoc->Clear();
+ else
+ mpDoc.reset( new TextDoc );
+
+ mpTEParaPortions.reset(new TEParaPortions);
+
+ std::unique_ptr<TextNode> pNode(new TextNode( OUString() ));
+ mpDoc->GetNodes().insert( mpDoc->GetNodes().begin(), std::move(pNode) );
+
+ TEParaPortion* pIniPortion = new TEParaPortion( mpDoc->GetNodes().begin()->get() );
+ mpTEParaPortions->Insert( pIniPortion, 0 );
+
+ mbFormatted = false;
+
+ ImpParagraphRemoved( TEXT_PARA_ALL );
+ ImpParagraphInserted( 0 );
+}
+
+OUString TextEngine::GetText( const TextSelection& rSel, LineEnd aSeparator ) const
+{
+ if ( !rSel.HasRange() )
+ return OUString();
+
+ TextSelection aSel( rSel );
+ aSel.Justify();
+
+ OUStringBuffer aText;
+ const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
+ const sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
+ const sal_Unicode* pSep = static_getLineEndText( aSeparator );
+ for ( sal_uInt32 nNode = aSel.GetStart().GetPara(); nNode <= nEndPara; ++nNode )
+ {
+ TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
+
+ sal_Int32 nStartPos = 0;
+ sal_Int32 nEndPos = pNode->GetText().getLength();
+ if ( nNode == nStartPara )
+ nStartPos = aSel.GetStart().GetIndex();
+ if ( nNode == nEndPara ) // may also be == nStart!
+ nEndPos = aSel.GetEnd().GetIndex();
+
+ aText.append(pNode->GetText().subView(nStartPos, nEndPos-nStartPos));
+ if ( nNode < nEndPara )
+ aText.append(pSep);
+ }
+ return aText.makeStringAndClear();
+}
+
+void TextEngine::ImpRemoveText()
+{
+ ImpInitDoc();
+
+ const TextSelection aEmptySel;
+ for (TextView* pView : *mpViews)
+ {
+ pView->ImpSetSelection( aEmptySel );
+ }
+ ResetUndo();
+}
+
+void TextEngine::SetText( const OUString& rText )
+{
+ ImpRemoveText();
+
+ const bool bUndoCurrentlyEnabled = IsUndoEnabled();
+ // the manually inserted text cannot be reversed by the user
+ EnableUndo( false );
+
+ const TextSelection aEmptySel;
+
+ TextPaM aPaM;
+ if ( !rText.isEmpty() )
+ aPaM = ImpInsertText( aEmptySel, rText );
+
+ for (TextView* pView : *mpViews)
+ {
+ pView->ImpSetSelection( aEmptySel );
+
+ // if no text, then no Format&Update => the text remains
+ if ( rText.isEmpty() && GetUpdateMode() )
+ pView->Invalidate();
+ }
+
+ if( rText.isEmpty() ) // otherwise needs invalidation later; !bFormatted is sufficient
+ mnCurTextHeight = 0;
+
+ FormatAndUpdate();
+
+ EnableUndo( bUndoCurrentlyEnabled );
+ SAL_WARN_IF( HasUndoManager() && GetUndoManager().GetUndoActionCount(), "vcl", "SetText: Undo!" );
+}
+
+void TextEngine::CursorMoved( sal_uInt32 nNode )
+{
+ // delete empty attribute; but only if paragraph is not empty!
+ TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
+ if ( pNode && pNode->GetCharAttribs().HasEmptyAttribs() && !pNode->GetText().isEmpty() )
+ pNode->GetCharAttribs().DeleteEmptyAttribs();
+}
+
+void TextEngine::ImpRemoveChars( const TextPaM& rPaM, sal_Int32 nChars )
+{
+ SAL_WARN_IF( !nChars, "vcl", "ImpRemoveChars: 0 Chars?!" );
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ // attributes have to be saved for UNDO before RemoveChars!
+ TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
+ OUString aStr( pNode->GetText().copy( rPaM.GetIndex(), nChars ) );
+
+ // check if attributes are being deleted or changed
+ const sal_Int32 nStart = rPaM.GetIndex();
+ const sal_Int32 nEnd = nStart + nChars;
+ for ( sal_uInt16 nAttr = pNode->GetCharAttribs().Count(); nAttr; )
+ {
+ TextCharAttrib& rAttr = pNode->GetCharAttribs().GetAttrib( --nAttr );
+ if ( ( rAttr.GetEnd() >= nStart ) && ( rAttr.GetStart() < nEnd ) )
+ {
+ break; // for
+ }
+ }
+ InsertUndo( std::make_unique<TextUndoRemoveChars>( this, rPaM, aStr ) );
+ }
+
+ mpDoc->RemoveChars( rPaM, nChars );
+ ImpCharsRemoved( rPaM.GetPara(), rPaM.GetIndex(), nChars );
+}
+
+TextPaM TextEngine::ImpConnectParagraphs( sal_uInt32 nLeft, sal_uInt32 nRight )
+{
+ SAL_WARN_IF( nLeft == nRight, "vcl", "ImpConnectParagraphs: connect the very same paragraph ?" );
+
+ TextNode* pLeft = mpDoc->GetNodes()[ nLeft ].get();
+ TextNode* pRight = mpDoc->GetNodes()[ nRight ].get();
+
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoConnectParas>( this, nLeft, pLeft->GetText().getLength() ) );
+
+ // first lookup Portions, as pRight is gone after ConnectParagraphs
+ TEParaPortion* pLeftPortion = mpTEParaPortions->GetObject( nLeft );
+ TEParaPortion* pRightPortion = mpTEParaPortions->GetObject( nRight );
+ SAL_WARN_IF( !pLeft || !pLeftPortion, "vcl", "ImpConnectParagraphs(1): Hidden Portion" );
+ SAL_WARN_IF( !pRight || !pRightPortion, "vcl", "ImpConnectParagraphs(2): Hidden Portion" );
+
+ TextPaM aPaM = mpDoc->ConnectParagraphs( pLeft, pRight );
+ ImpParagraphRemoved( nRight );
+
+ pLeftPortion->MarkSelectionInvalid( aPaM.GetIndex() );
+
+ mpTEParaPortions->Remove( nRight );
+ // the right Node is deleted by EditDoc::ConnectParagraphs()
+
+ return aPaM;
+}
+
+TextPaM TextEngine::ImpDeleteText( const TextSelection& rSel )
+{
+ if ( !rSel.HasRange() )
+ return rSel.GetStart();
+
+ TextSelection aSel( rSel );
+ aSel.Justify();
+ TextPaM aStartPaM( aSel.GetStart() );
+ TextPaM aEndPaM( aSel.GetEnd() );
+
+ CursorMoved( aStartPaM.GetPara() ); // so that newly-adjusted attributes vanish
+ CursorMoved( aEndPaM.GetPara() ); // so that newly-adjusted attributes vanish
+
+ SAL_WARN_IF( !mpDoc->IsValidPaM( aStartPaM ), "vcl", "ImpDeleteText(1): bad Index" );
+ SAL_WARN_IF( !mpDoc->IsValidPaM( aEndPaM ), "vcl", "ImpDeleteText(2): bad Index" );
+
+ const sal_uInt32 nStartNode = aStartPaM.GetPara();
+ sal_uInt32 nEndNode = aEndPaM.GetPara();
+
+ // remove all Nodes inbetween
+ for ( sal_uInt32 z = nStartNode+1; z < nEndNode; ++z )
+ {
+ // always nStartNode+1, because of Remove()!
+ ImpRemoveParagraph( nStartNode+1 );
+ }
+
+ if ( nStartNode != nEndNode )
+ {
+ // the remainder of StartNodes...
+ TextNode* pLeft = mpDoc->GetNodes()[ nStartNode ].get();
+ sal_Int32 nChars = pLeft->GetText().getLength() - aStartPaM.GetIndex();
+ if ( nChars )
+ {
+ ImpRemoveChars( aStartPaM, nChars );
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nStartNode );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(3): bad Index" );
+ pPortion->MarkSelectionInvalid( aStartPaM.GetIndex() );
+ }
+
+ // the beginning of EndNodes...
+ nEndNode = nStartNode+1; // the other paragraphs were deleted
+ nChars = aEndPaM.GetIndex();
+ if ( nChars )
+ {
+ aEndPaM.GetPara() = nEndNode;
+ aEndPaM.GetIndex() = 0;
+ ImpRemoveChars( aEndPaM, nChars );
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nEndNode );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(4): bad Index" );
+ pPortion->MarkSelectionInvalid( 0 );
+ }
+
+ // connect...
+ aStartPaM = ImpConnectParagraphs( nStartNode, nEndNode );
+ }
+ else
+ {
+ const sal_Int32 nChars = aEndPaM.GetIndex() - aStartPaM.GetIndex();
+ ImpRemoveChars( aStartPaM, nChars );
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nStartNode );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpDeleteText(5): bad Index" );
+ pPortion->MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() );
+ }
+
+// UpdateSelections();
+ TextModified();
+ return aStartPaM;
+}
+
+void TextEngine::ImpRemoveParagraph( sal_uInt32 nPara )
+{
+ std::unique_ptr<TextNode> pNode = std::move(mpDoc->GetNodes()[ nPara ]);
+
+ // the Node is handled by Undo and is deleted if appropriate
+ mpDoc->GetNodes().erase( mpDoc->GetNodes().begin() + nPara );
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoDelPara>( this, pNode.release(), nPara ) );
+
+ mpTEParaPortions->Remove( nPara );
+
+ ImpParagraphRemoved( nPara );
+}
+
+uno::Reference < i18n::XExtendedInputSequenceChecker > const & TextEngine::GetInputSequenceChecker()
+{
+ if ( !mxISC.is() )
+ {
+ mxISC = i18n::InputSequenceChecker::create( ::comphelper::getProcessComponentContext() );
+ }
+ return mxISC;
+}
+
+bool TextEngine::IsInputSequenceCheckingRequired( sal_Unicode c, const TextSelection& rCurSel ) const
+{
+ SvtCTLOptions aCTLOptions;
+
+ // get the index that really is first
+ const sal_Int32 nFirstPos = std::min(rCurSel.GetStart().GetIndex(), rCurSel.GetEnd().GetIndex());
+
+ bool bIsSequenceChecking =
+ aCTLOptions.IsCTLFontEnabled() &&
+ aCTLOptions.IsCTLSequenceChecking() &&
+ nFirstPos != 0; /* first char needs not to be checked */
+
+ if (bIsSequenceChecking)
+ {
+ uno::Reference< i18n::XBreakIterator > xBI = const_cast<TextEngine *>(this)->GetBreakIterator();
+ bIsSequenceChecking = xBI.is() && i18n::ScriptType::COMPLEX == xBI->getScriptType( OUString( c ), 0 );
+ }
+
+ return bIsSequenceChecking;
+}
+
+TextPaM TextEngine::ImpInsertText( const TextSelection& rCurSel, sal_Unicode c, bool bOverwrite )
+{
+ return ImpInsertText( c, rCurSel, bOverwrite );
+}
+
+TextPaM TextEngine::ImpInsertText( sal_Unicode c, const TextSelection& rCurSel, bool bOverwrite, bool bIsUserInput )
+{
+ SAL_WARN_IF( c == '\n', "vcl", "InsertText: NewLine!" );
+ SAL_WARN_IF( c == '\r', "vcl", "InsertText: NewLine!" );
+
+ TextPaM aPaM( rCurSel.GetStart() );
+ TextNode* pNode = mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+
+ bool bDoOverwrite = bOverwrite && ( aPaM.GetIndex() < pNode->GetText().getLength() );
+
+ bool bUndoAction = rCurSel.HasRange() || bDoOverwrite;
+
+ if ( bUndoAction )
+ UndoActionStart();
+
+ if ( rCurSel.HasRange() )
+ {
+ aPaM = ImpDeleteText( rCurSel );
+ }
+ else if ( bDoOverwrite )
+ {
+ // if selection, then don't overwrite a character
+ TextSelection aTmpSel( aPaM );
+ ++aTmpSel.GetEnd().GetIndex();
+ ImpDeleteText( aTmpSel );
+ }
+
+ if (bIsUserInput && IsInputSequenceCheckingRequired( c, rCurSel ))
+ {
+ uno::Reference < i18n::XExtendedInputSequenceChecker > xISC = GetInputSequenceChecker();
+ SvtCTLOptions aCTLOptions;
+
+ if (xISC.is())
+ {
+ sal_Int32 nTmpPos = aPaM.GetIndex();
+ sal_Int16 nCheckMode = aCTLOptions.IsCTLSequenceCheckingRestricted() ?
+ i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC;
+
+ // the text that needs to be checked is only the one
+ // before the current cursor position
+ OUString aOldText( mpDoc->GetText( aPaM.GetPara() ).copy(0, nTmpPos) );
+ if (aCTLOptions.IsCTLSequenceCheckingTypeAndReplace())
+ {
+ OUString aNewText( aOldText );
+ xISC->correctInputSequence( aNewText, nTmpPos - 1, c, nCheckMode );
+
+ // find position of first character that has changed
+ const sal_Int32 nOldLen = aOldText.getLength();
+ const sal_Int32 nNewLen = aNewText.getLength();
+ const sal_Unicode *pOldTxt = aOldText.getStr();
+ const sal_Unicode *pNewTxt = aNewText.getStr();
+ sal_Int32 nChgPos = 0;
+ while ( nChgPos < nOldLen && nChgPos < nNewLen &&
+ pOldTxt[nChgPos] == pNewTxt[nChgPos] )
+ ++nChgPos;
+
+ OUString aChgText( aNewText.copy( nChgPos ) );
+
+ // select text from first pos to be changed to current pos
+ TextSelection aSel( TextPaM( aPaM.GetPara(), nChgPos ), aPaM );
+
+ if (!aChgText.isEmpty())
+ // ImpInsertText implicitly handles undo...
+ return ImpInsertText( aSel, aChgText );
+ else
+ return aPaM;
+ }
+ else
+ {
+ // should the character be ignored (i.e. not get inserted) ?
+ if (!xISC->checkInputSequence( aOldText, nTmpPos - 1, c, nCheckMode ))
+ return aPaM; // nothing to be done -> no need for undo
+ }
+ }
+
+ // at this point now we will insert the character 'normally' some lines below...
+ }
+
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ std::unique_ptr<TextUndoInsertChars> pNewUndo(new TextUndoInsertChars( this, aPaM, OUString(c) ));
+ bool bTryMerge = !bDoOverwrite && ( c != ' ' );
+ InsertUndo( std::move(pNewUndo), bTryMerge );
+ }
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( aPaM.GetPara() );
+ pPortion->MarkInvalid( aPaM.GetIndex(), 1 );
+ if ( c == '\t' )
+ pPortion->SetNotSimpleInvalid();
+ aPaM = mpDoc->InsertText( aPaM, c );
+ ImpCharsInserted( aPaM.GetPara(), aPaM.GetIndex()-1, 1 );
+
+ TextModified();
+
+ if ( bUndoAction )
+ UndoActionEnd();
+
+ return aPaM;
+}
+
+TextPaM TextEngine::ImpInsertText( const TextSelection& rCurSel, const OUString& rStr )
+{
+ UndoActionStart();
+
+ TextPaM aPaM;
+
+ if ( rCurSel.HasRange() )
+ aPaM = ImpDeleteText( rCurSel );
+ else
+ aPaM = rCurSel.GetEnd();
+
+ OUString aText(convertLineEnd(rStr, LINEEND_LF));
+
+ sal_Int32 nStart = 0;
+ while ( nStart < aText.getLength() )
+ {
+ sal_Int32 nEnd = aText.indexOf( LINE_SEP, nStart );
+ if (nEnd == -1)
+ nEnd = aText.getLength(); // do not dereference!
+
+ // Start == End => empty line
+ if ( nEnd > nStart )
+ {
+ OUString aLine(aText.copy(nStart, nEnd-nStart));
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoInsertChars>( this, aPaM, aLine ) );
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( aPaM.GetPara() );
+ pPortion->MarkInvalid( aPaM.GetIndex(), aLine.getLength() );
+ if (aLine.indexOf( '\t' ) != -1)
+ pPortion->SetNotSimpleInvalid();
+
+ aPaM = mpDoc->InsertText( aPaM, aLine );
+ ImpCharsInserted( aPaM.GetPara(), aPaM.GetIndex()-aLine.getLength(), aLine.getLength() );
+
+ }
+ if ( nEnd < aText.getLength() )
+ aPaM = ImpInsertParaBreak( aPaM );
+
+ if ( nEnd == aText.getLength() ) // #108611# prevent overflow in "nStart = nEnd+1" calculation
+ break;
+
+ nStart = nEnd+1;
+ }
+
+ UndoActionEnd();
+
+ TextModified();
+ return aPaM;
+}
+
+TextPaM TextEngine::ImpInsertParaBreak( const TextSelection& rCurSel )
+{
+ TextPaM aPaM;
+ if ( rCurSel.HasRange() )
+ aPaM = ImpDeleteText( rCurSel );
+ else
+ aPaM = rCurSel.GetEnd();
+
+ return ImpInsertParaBreak( aPaM );
+}
+
+TextPaM TextEngine::ImpInsertParaBreak( const TextPaM& rPaM )
+{
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo( std::make_unique<TextUndoSplitPara>( this, rPaM.GetPara(), rPaM.GetIndex() ) );
+
+ TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
+ bool bFirstParaContentChanged = rPaM.GetIndex() < pNode->GetText().getLength();
+
+ TextPaM aPaM( mpDoc->InsertParaBreak( rPaM ) );
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
+ SAL_WARN_IF( !pPortion, "vcl", "ImpInsertParaBreak: Hidden Portion" );
+ pPortion->MarkInvalid( rPaM.GetIndex(), 0 );
+
+ TextNode* pNewNode = mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+ TEParaPortion* pNewPortion = new TEParaPortion( pNewNode );
+ mpTEParaPortions->Insert( pNewPortion, aPaM.GetPara() );
+ ImpParagraphInserted( aPaM.GetPara() );
+
+ CursorMoved( rPaM.GetPara() ); // if empty attribute created
+ TextModified();
+
+ if ( bFirstParaContentChanged )
+ Broadcast( TextHint( SfxHintId::TextParaContentChanged, rPaM.GetPara() ) );
+
+ return aPaM;
+}
+
+tools::Rectangle TextEngine::PaMtoEditCursor( const TextPaM& rPaM, bool bSpecial )
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "PaMtoEditCursor: GetUpdateMode()" );
+
+ tools::Rectangle aEditCursor;
+ tools::Long nY = 0;
+
+ if ( !mbHasMultiLineParas )
+ {
+ nY = rPaM.GetPara() * mnCharHeight;
+ }
+ else
+ {
+ for ( sal_uInt32 nPortion = 0; nPortion < rPaM.GetPara(); ++nPortion )
+ {
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject(nPortion);
+ nY += pPortion->GetLines().size() * mnCharHeight;
+ }
+ }
+
+ aEditCursor = GetEditCursor( rPaM, bSpecial );
+ aEditCursor.AdjustTop(nY );
+ aEditCursor.AdjustBottom(nY );
+ return aEditCursor;
+}
+
+tools::Rectangle TextEngine::GetEditCursor( const TextPaM& rPaM, bool bSpecial, bool bPreferPortionStart )
+{
+ if ( !IsFormatted() && !IsFormatting() )
+ FormatAndUpdate();
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
+ //TextNode* pNode = mpDoc->GetNodes().GetObject( rPaM.GetPara() );
+
+ /*
+ bSpecial: If behind the last character of a made up line, stay at the
+ end of the line, not at the start of the next line.
+ Purpose: - really END = > behind the last character
+ - to selection...
+
+ */
+
+ tools::Long nY = 0;
+ sal_Int32 nCurIndex = 0;
+ TextLine* pLine = nullptr;
+ for (TextLine & rTmpLine : pPortion->GetLines())
+ {
+ if ( ( rTmpLine.GetStart() == rPaM.GetIndex() ) || ( rTmpLine.IsIn( rPaM.GetIndex(), bSpecial ) ) )
+ {
+ pLine = &rTmpLine;
+ break;
+ }
+
+ nCurIndex = nCurIndex + rTmpLine.GetLen();
+ nY += mnCharHeight;
+ }
+ if ( !pLine )
+ {
+ // Cursor at end of paragraph
+ SAL_WARN_IF( rPaM.GetIndex() != nCurIndex, "vcl", "GetEditCursor: Bad Index!" );
+
+ pLine = & ( pPortion->GetLines().back() );
+ nY -= mnCharHeight;
+ }
+
+ tools::Rectangle aEditCursor;
+
+ aEditCursor.SetTop( nY );
+ nY += mnCharHeight;
+ aEditCursor.SetBottom( nY-1 );
+
+ // search within the line
+ tools::Long nX = ImpGetXPos( rPaM.GetPara(), pLine, rPaM.GetIndex(), bPreferPortionStart );
+ aEditCursor.SetLeft(nX);
+ aEditCursor.SetRight(nX);
+ return aEditCursor;
+}
+
+tools::Long TextEngine::ImpGetXPos( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart )
+{
+ SAL_WARN_IF( ( nIndex < pLine->GetStart() ) || ( nIndex > pLine->GetEnd() ) , "vcl", "ImpGetXPos: Bad parameters!" );
+
+ bool bDoPreferPortionStart = bPreferPortionStart;
+ // Assure that the portion belongs to this line
+ if ( nIndex == pLine->GetStart() )
+ bDoPreferPortionStart = true;
+ else if ( nIndex == pLine->GetEnd() )
+ bDoPreferPortionStart = false;
+
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
+
+ sal_Int32 nTextPortionStart = 0;
+ std::size_t nTextPortion = pParaPortion->GetTextPortions().FindPortion( nIndex, nTextPortionStart, bDoPreferPortionStart );
+
+ SAL_WARN_IF( ( nTextPortion < pLine->GetStartPortion() ) || ( nTextPortion > pLine->GetEndPortion() ), "vcl", "GetXPos: Portion not in current line!" );
+
+ TETextPortion& rPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
+
+ tools::Long nX = ImpGetPortionXOffset( nPara, pLine, nTextPortion );
+
+ tools::Long nPortionTextWidth = rPortion.GetWidth();
+
+ if ( nTextPortionStart != nIndex )
+ {
+ // Search within portion...
+ if ( nIndex == ( nTextPortionStart + rPortion.GetLen() ) )
+ {
+ // End of Portion
+ if ( ( rPortion.GetKind() == PORTIONKIND_TAB ) ||
+ ( !IsRightToLeft() && !rPortion.IsRightToLeft() ) ||
+ ( IsRightToLeft() && rPortion.IsRightToLeft() ) )
+ {
+ nX += nPortionTextWidth;
+ if ( ( rPortion.GetKind() == PORTIONKIND_TAB ) && ( (nTextPortion+1) < pParaPortion->GetTextPortions().size() ) )
+ {
+ TETextPortion& rNextPortion = pParaPortion->GetTextPortions()[ nTextPortion+1 ];
+ if (rNextPortion.GetKind() != PORTIONKIND_TAB && IsRightToLeft() != rNextPortion.IsRightToLeft())
+ {
+ // End of the tab portion, use start of next for cursor pos
+ SAL_WARN_IF( bPreferPortionStart, "vcl", "ImpGetXPos: How can we get here!" );
+ nX = ImpGetXPos( nPara, pLine, nIndex, true );
+ }
+
+ }
+ }
+ }
+ else if ( rPortion.GetKind() == PORTIONKIND_TEXT )
+ {
+ SAL_WARN_IF( nIndex == pLine->GetStart(), "vcl", "ImpGetXPos: Strange behavior" );
+
+ tools::Long nPosInPortion = CalcTextWidth( nPara, nTextPortionStart, nIndex-nTextPortionStart );
+
+ if (IsRightToLeft() == rPortion.IsRightToLeft())
+ {
+ nX += nPosInPortion;
+ }
+ else
+ {
+ nX += nPortionTextWidth - nPosInPortion;
+ }
+ }
+ }
+ else // if ( nIndex == pLine->GetStart() )
+ {
+ if (rPortion.GetKind() != PORTIONKIND_TAB && IsRightToLeft() != rPortion.IsRightToLeft())
+ {
+ nX += nPortionTextWidth;
+ }
+ }
+
+ return nX;
+}
+
+const TextAttrib* TextEngine::FindAttrib( const TextPaM& rPaM, sal_uInt16 nWhich ) const
+{
+ const TextAttrib* pAttr = nullptr;
+ const TextCharAttrib* pCharAttr = FindCharAttrib( rPaM, nWhich );
+ if ( pCharAttr )
+ pAttr = &pCharAttr->GetAttr();
+ return pAttr;
+}
+
+const TextCharAttrib* TextEngine::FindCharAttrib( const TextPaM& rPaM, sal_uInt16 nWhich ) const
+{
+ const TextCharAttrib* pAttr = nullptr;
+ TextNode* pNode = mpDoc->GetNodes()[ rPaM.GetPara() ].get();
+ if (pNode && (rPaM.GetIndex() <= pNode->GetText().getLength()))
+ pAttr = pNode->GetCharAttribs().FindAttrib( nWhich, rPaM.GetIndex() );
+ return pAttr;
+}
+
+TextPaM TextEngine::GetPaM( const Point& rDocPos )
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetPaM: GetUpdateMode()" );
+
+ tools::Long nY = 0;
+ for ( sal_uInt32 nPortion = 0; nPortion < mpTEParaPortions->Count(); ++nPortion )
+ {
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
+ tools::Long nTmpHeight = pPortion->GetLines().size() * mnCharHeight;
+ nY += nTmpHeight;
+ if ( nY > rDocPos.Y() )
+ {
+ nY -= nTmpHeight;
+ Point aPosInPara( rDocPos );
+ aPosInPara.AdjustY( -nY );
+
+ TextPaM aPaM( nPortion, 0 );
+ aPaM.GetIndex() = ImpFindIndex( nPortion, aPosInPara );
+ return aPaM;
+ }
+ }
+
+ // not found - go to last visible
+ const sal_uInt32 nLastNode = static_cast<sal_uInt32>(mpDoc->GetNodes().size() - 1);
+ TextNode* pLast = mpDoc->GetNodes()[ nLastNode ].get();
+ return TextPaM( nLastNode, pLast->GetText().getLength() );
+}
+
+sal_Int32 TextEngine::ImpFindIndex( sal_uInt32 nPortion, const Point& rPosInPara )
+{
+ SAL_WARN_IF( !IsFormatted(), "vcl", "GetPaM: Not formatted" );
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
+
+ sal_Int32 nCurIndex = 0;
+
+ tools::Long nY = 0;
+ TextLine* pLine = nullptr;
+ std::vector<TextLine>::size_type nLine;
+ for ( nLine = 0; nLine < pPortion->GetLines().size(); nLine++ )
+ {
+ TextLine& rmpLine = pPortion->GetLines()[ nLine ];
+ nY += mnCharHeight;
+ if ( nY > rPosInPara.Y() ) // that's it
+ {
+ pLine = &rmpLine;
+ break; // correct Y-Position not needed
+ }
+ }
+
+ assert(pLine && "ImpFindIndex: pLine ?");
+
+ nCurIndex = GetCharPos( nPortion, nLine, rPosInPara.X() );
+
+ if ( nCurIndex && ( nCurIndex == pLine->GetEnd() ) &&
+ ( pLine != &( pPortion->GetLines().back() ) ) )
+ {
+ uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
+ sal_Int32 nCount = 1;
+ nCurIndex = xBI->previousCharacters( pPortion->GetNode()->GetText(), nCurIndex, GetLocale(), i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount );
+ }
+ return nCurIndex;
+}
+
+sal_Int32 TextEngine::GetCharPos( sal_uInt32 nPortion, std::vector<TextLine>::size_type nLine, tools::Long nXPos )
+{
+
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPortion );
+ TextLine& rLine = pPortion->GetLines()[ nLine ];
+
+ sal_Int32 nCurIndex = rLine.GetStart();
+
+ tools::Long nTmpX = rLine.GetStartX();
+ if ( nXPos <= nTmpX )
+ return nCurIndex;
+
+ for ( std::size_t i = rLine.GetStartPortion(); i <= rLine.GetEndPortion(); i++ )
+ {
+ TETextPortion& rTextPortion = pPortion->GetTextPortions()[ i ];
+ nTmpX += rTextPortion.GetWidth();
+
+ if ( nTmpX > nXPos )
+ {
+ if( rTextPortion.GetLen() > 1 )
+ {
+ nTmpX -= rTextPortion.GetWidth(); // position before Portion
+ // TODO: Optimize: no GetTextBreak if fixed-width Font
+ vcl::Font aFont;
+ SeekCursor( nPortion, nCurIndex+1, aFont, nullptr );
+ mpRefDev->SetFont( aFont);
+ tools::Long nPosInPortion = nXPos-nTmpX;
+ if ( IsRightToLeft() != rTextPortion.IsRightToLeft() )
+ nPosInPortion = rTextPortion.GetWidth() - nPosInPortion;
+ nCurIndex = mpRefDev->GetTextBreak( pPortion->GetNode()->GetText(), nPosInPortion, nCurIndex );
+ // MT: GetTextBreak should assure that we are not within a CTL cell...
+ }
+ return nCurIndex;
+ }
+ nCurIndex += rTextPortion.GetLen();
+ }
+ return nCurIndex;
+}
+
+tools::Long TextEngine::GetTextHeight() const
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetTextHeight: GetUpdateMode()" );
+
+ if ( !IsFormatted() && !IsFormatting() )
+ const_cast<TextEngine*>(this)->FormatAndUpdate();
+
+ return mnCurTextHeight;
+}
+
+tools::Long TextEngine::GetTextHeight( sal_uInt32 nParagraph ) const
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "GetTextHeight: GetUpdateMode()" );
+
+ if ( !IsFormatted() && !IsFormatting() )
+ const_cast<TextEngine*>(this)->FormatAndUpdate();
+
+ return CalcParaHeight( nParagraph );
+}
+
+tools::Long TextEngine::CalcTextWidth( sal_uInt32 nPara )
+{
+ tools::Long nParaWidth = 0;
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
+ for ( auto nLine = pPortion->GetLines().size(); nLine; )
+ {
+ tools::Long nLineWidth = 0;
+ TextLine& rLine = pPortion->GetLines()[ --nLine ];
+ for ( std::size_t nTP = rLine.GetStartPortion(); nTP <= rLine.GetEndPortion(); nTP++ )
+ {
+ TETextPortion& rTextPortion = pPortion->GetTextPortions()[ nTP ];
+ nLineWidth += rTextPortion.GetWidth();
+ }
+ if ( nLineWidth > nParaWidth )
+ nParaWidth = nLineWidth;
+ }
+ return nParaWidth;
+}
+
+tools::Long TextEngine::CalcTextWidth()
+{
+ if ( !IsFormatted() && !IsFormatting() )
+ FormatAndUpdate();
+
+ if ( mnCurTextWidth < 0 )
+ {
+ mnCurTextWidth = 0;
+ for ( sal_uInt32 nPara = mpTEParaPortions->Count(); nPara; )
+ {
+ const tools::Long nParaWidth = CalcTextWidth( --nPara );
+ if ( nParaWidth > mnCurTextWidth )
+ mnCurTextWidth = nParaWidth;
+ }
+ }
+ return mnCurTextWidth+1;// wider by 1, as CreateLines breaks at >=
+}
+
+tools::Long TextEngine::CalcTextHeight() const
+{
+ SAL_WARN_IF( !GetUpdateMode(), "vcl", "CalcTextHeight: GetUpdateMode()" );
+
+ tools::Long nY = 0;
+ for ( auto nPortion = mpTEParaPortions->Count(); nPortion; )
+ nY += CalcParaHeight( --nPortion );
+ return nY;
+}
+
+tools::Long TextEngine::CalcTextWidth( sal_uInt32 nPara, sal_Int32 nPortionStart, sal_Int32 nLen )
+{
+#ifdef DBG_UTIL
+ // within the text there must not be a Portion change (attribute/tab)!
+ sal_Int32 nTabPos = mpDoc->GetNodes()[ nPara ]->GetText().indexOf( '\t', nPortionStart );
+ SAL_WARN_IF( nTabPos != -1 && nTabPos < (nPortionStart+nLen), "vcl", "CalcTextWidth: Tab!" );
+#endif
+
+ vcl::Font aFont;
+ SeekCursor( nPara, nPortionStart+1, aFont, nullptr );
+ mpRefDev->SetFont( aFont );
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ tools::Long nWidth = mpRefDev->GetTextWidth( pNode->GetText(), nPortionStart, nLen );
+ return nWidth;
+}
+
+void TextEngine::GetTextPortionRange(const TextPaM& rPaM, sal_Int32& nStart, sal_Int32& nEnd)
+{
+ nStart = 0;
+ nEnd = 0;
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( rPaM.GetPara() );
+ for ( std::size_t i = 0; i < pParaPortion->GetTextPortions().size(); ++i )
+ {
+ TETextPortion& rTextPortion = pParaPortion->GetTextPortions()[ i ];
+ if (nStart + rTextPortion.GetLen() > rPaM.GetIndex())
+ {
+ nEnd = nStart + rTextPortion.GetLen();
+ return;
+ }
+ else
+ {
+ nStart += rTextPortion.GetLen();
+ }
+ }
+}
+
+sal_uInt16 TextEngine::GetLineCount( sal_uInt32 nParagraph ) const
+{
+ SAL_WARN_IF( nParagraph >= mpTEParaPortions->Count(), "vcl", "GetLineCount: Out of range" );
+
+ TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
+ if ( pPPortion )
+ return pPPortion->GetLines().size();
+
+ return 0;
+}
+
+sal_Int32 TextEngine::GetLineLen( sal_uInt32 nParagraph, sal_uInt16 nLine ) const
+{
+ SAL_WARN_IF( nParagraph >= mpTEParaPortions->Count(), "vcl", "GetLineCount: Out of range" );
+
+ TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
+ if ( pPPortion && ( nLine < pPPortion->GetLines().size() ) )
+ {
+ return pPPortion->GetLines()[ nLine ].GetLen();
+ }
+
+ return 0;
+}
+
+tools::Long TextEngine::CalcParaHeight( sal_uInt32 nParagraph ) const
+{
+ tools::Long nHeight = 0;
+
+ TEParaPortion* pPPortion = mpTEParaPortions->GetObject( nParagraph );
+ SAL_WARN_IF( !pPPortion, "vcl", "GetParaHeight: paragraph not found" );
+ if ( pPPortion )
+ nHeight = pPPortion->GetLines().size() * mnCharHeight;
+
+ return nHeight;
+}
+
+Range TextEngine::GetInvalidYOffsets( sal_uInt32 nPortion )
+{
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPortion );
+ sal_uInt16 nLines = pTEParaPortion->GetLines().size();
+ sal_uInt16 nLastInvalid, nFirstInvalid = 0;
+ sal_uInt16 nLine;
+ for ( nLine = 0; nLine < nLines; nLine++ )
+ {
+ TextLine& rL = pTEParaPortion->GetLines()[ nLine ];
+ if ( rL.IsInvalid() )
+ {
+ nFirstInvalid = nLine;
+ break;
+ }
+ }
+
+ for ( nLastInvalid = nFirstInvalid; nLastInvalid < nLines; nLastInvalid++ )
+ {
+ TextLine& rL = pTEParaPortion->GetLines()[ nLine ];
+ if ( rL.IsValid() )
+ break;
+ }
+
+ if ( nLastInvalid >= nLines )
+ nLastInvalid = nLines-1;
+
+ return Range( nFirstInvalid*mnCharHeight, ((nLastInvalid+1)*mnCharHeight)-1 );
+}
+
+sal_uInt32 TextEngine::GetParagraphCount() const
+{
+ return static_cast<sal_uInt32>(mpDoc->GetNodes().size());
+}
+
+void TextEngine::EnableUndo( bool bEnable )
+{
+ // delete list when switching mode
+ if ( bEnable != IsUndoEnabled() )
+ ResetUndo();
+
+ mbUndoEnabled = bEnable;
+}
+
+SfxUndoManager& TextEngine::GetUndoManager()
+{
+ if ( !mpUndoManager )
+ mpUndoManager.reset( new TextUndoManager( this ) );
+ return *mpUndoManager;
+}
+
+void TextEngine::UndoActionStart( sal_uInt16 nId )
+{
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ GetUndoManager().EnterListAction( OUString(), OUString(), nId, ViewShellId(-1) );
+ }
+}
+
+void TextEngine::UndoActionEnd()
+{
+ if ( IsUndoEnabled() && !IsInUndo() )
+ GetUndoManager().LeaveListAction();
+}
+
+void TextEngine::InsertUndo( std::unique_ptr<TextUndo> pUndo, bool bTryMerge )
+{
+ SAL_WARN_IF( IsInUndo(), "vcl", "InsertUndo: in Undo mode!" );
+ GetUndoManager().AddUndoAction( std::move(pUndo), bTryMerge );
+}
+
+void TextEngine::ResetUndo()
+{
+ if ( mpUndoManager )
+ mpUndoManager->Clear();
+}
+
+void TextEngine::InsertContent( std::unique_ptr<TextNode> pNode, sal_uInt32 nPara )
+{
+ SAL_WARN_IF( !pNode, "vcl", "InsertContent: NULL-Pointer!" );
+ SAL_WARN_IF( !IsInUndo(), "vcl", "InsertContent: only in Undo()!" );
+ TEParaPortion* pNew = new TEParaPortion( pNode.get() );
+ mpTEParaPortions->Insert( pNew, nPara );
+ mpDoc->GetNodes().insert( mpDoc->GetNodes().begin() + nPara, std::move(pNode) );
+ ImpParagraphInserted( nPara );
+}
+
+TextPaM TextEngine::SplitContent( sal_uInt32 nNode, sal_Int32 nSepPos )
+{
+#ifdef DBG_UTIL
+ TextNode* pNode = mpDoc->GetNodes()[ nNode ].get();
+ SAL_WARN_IF( !pNode, "vcl", "SplitContent: Invalid Node!" );
+ SAL_WARN_IF( !IsInUndo(), "vcl", "SplitContent: only in Undo()!" );
+ SAL_WARN_IF( nSepPos > pNode->GetText().getLength(), "vcl", "SplitContent: Bad index" );
+#endif
+ TextPaM aPaM( nNode, nSepPos );
+ return ImpInsertParaBreak( aPaM );
+}
+
+TextPaM TextEngine::ConnectContents( sal_uInt32 nLeftNode )
+{
+ SAL_WARN_IF( !IsInUndo(), "vcl", "ConnectContent: only in Undo()!" );
+ return ImpConnectParagraphs( nLeftNode, nLeftNode+1 );
+}
+
+void TextEngine::SeekCursor( sal_uInt32 nPara, sal_Int32 nPos, vcl::Font& rFont, OutputDevice* pOutDev )
+{
+ rFont = maFont;
+ if ( pOutDev )
+ pOutDev->SetTextColor( maTextColor );
+
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ sal_uInt16 nAttribs = pNode->GetCharAttribs().Count();
+ for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
+ {
+ TextCharAttrib& rAttrib = pNode->GetCharAttribs().GetAttrib( nAttr );
+ if ( rAttrib.GetStart() > nPos )
+ break;
+
+ // When seeking don't use Attr that start there!
+ // Do not use empty attributes:
+ // - If just being setup and empty => no effect on Font
+ // - Characters that are setup in an empty paragraph become visible right away.
+ if ( ( ( rAttrib.GetStart() < nPos ) && ( rAttrib.GetEnd() >= nPos ) )
+ || pNode->GetText().isEmpty() )
+ {
+ if ( rAttrib.Which() != TEXTATTR_FONTCOLOR )
+ {
+ rAttrib.GetAttr().SetFont(rFont);
+ }
+ else
+ {
+ if ( pOutDev )
+ pOutDev->SetTextColor( static_cast<const TextAttribFontColor&>(rAttrib.GetAttr()).GetColor() );
+ }
+ }
+ }
+
+ if ( !(mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetPara() == nPara ) &&
+ ( nPos > mpIMEInfos->aPos.GetIndex() ) && ( nPos <= ( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ) )) )
+ return;
+
+ ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ];
+ if ( nAttr & ExtTextInputAttr::Underline )
+ rFont.SetUnderline( LINESTYLE_SINGLE );
+ else if ( nAttr & ExtTextInputAttr::DoubleUnderline )
+ rFont.SetUnderline( LINESTYLE_DOUBLE );
+ else if ( nAttr & ExtTextInputAttr::BoldUnderline )
+ rFont.SetUnderline( LINESTYLE_BOLD );
+ else if ( nAttr & ExtTextInputAttr::DottedUnderline )
+ rFont.SetUnderline( LINESTYLE_DOTTED );
+ else if ( nAttr & ExtTextInputAttr::DashDotUnderline )
+ rFont.SetUnderline( LINESTYLE_DOTTED );
+ if ( nAttr & ExtTextInputAttr::RedText )
+ rFont.SetColor( COL_RED );
+ else if ( nAttr & ExtTextInputAttr::HalfToneText )
+ rFont.SetColor( COL_LIGHTGRAY );
+ if ( nAttr & ExtTextInputAttr::Highlight )
+ {
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ rFont.SetColor( rStyleSettings.GetHighlightTextColor() );
+ rFont.SetFillColor( rStyleSettings.GetHighlightColor() );
+ rFont.SetTransparent( false );
+ }
+ else if ( nAttr & ExtTextInputAttr::GrayWaveline )
+ {
+ rFont.SetUnderline( LINESTYLE_WAVE );
+// if( pOut )
+// pOut->SetTextLineColor( COL_LIGHTGRAY );
+ }
+}
+
+void TextEngine::FormatAndUpdate( TextView* pCurView )
+{
+ if ( mbDowning )
+ return;
+
+ if ( IsInUndo() )
+ IdleFormatAndUpdate( pCurView );
+ else
+ {
+ FormatDoc();
+ UpdateViews( pCurView );
+ }
+}
+
+void TextEngine::IdleFormatAndUpdate( TextView* pCurView, sal_uInt16 nMaxTimerRestarts )
+{
+ mpIdleFormatter->DoIdleFormat( pCurView, nMaxTimerRestarts );
+}
+
+void TextEngine::TextModified()
+{
+ mbFormatted = false;
+ mbModified = true;
+}
+
+void TextEngine::UpdateViews( TextView* pCurView )
+{
+ if ( !GetUpdateMode() || IsFormatting() || maInvalidRect.IsEmpty() )
+ return;
+
+ SAL_WARN_IF( !IsFormatted(), "vcl", "UpdateViews: Doc not formatted!" );
+
+ for (TextView* pView : *mpViews)
+ {
+ pView->HideCursor();
+
+ tools::Rectangle aClipRect( maInvalidRect );
+ const Size aOutSz = pView->GetWindow()->GetOutputSizePixel();
+ const tools::Rectangle aVisArea( pView->GetStartDocPos(), aOutSz );
+ aClipRect.Intersection( aVisArea );
+ if ( !aClipRect.IsEmpty() )
+ {
+ // translate into window coordinates
+ Point aNewPos = pView->GetWindowPos( aClipRect.TopLeft() );
+ if ( IsRightToLeft() )
+ aNewPos.AdjustX( -(aOutSz.Width() - 1) );
+ aClipRect.SetPos( aNewPos );
+
+ pView->GetWindow()->Invalidate( aClipRect );
+ }
+ }
+
+ if ( pCurView )
+ {
+ pCurView->ShowCursor( pCurView->IsAutoScroll() );
+ }
+
+ maInvalidRect = tools::Rectangle();
+}
+
+IMPL_LINK_NOARG(TextEngine, IdleFormatHdl, Timer *, void)
+{
+ FormatAndUpdate( mpIdleFormatter->GetView() );
+}
+
+void TextEngine::CheckIdleFormatter()
+{
+ mpIdleFormatter->ForceTimeout();
+}
+
+void TextEngine::FormatFullDoc()
+{
+ for ( sal_uInt32 nPortion = 0; nPortion < mpTEParaPortions->Count(); ++nPortion )
+ {
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPortion );
+ pTEParaPortion->MarkSelectionInvalid( 0 );
+ }
+ mbFormatted = false;
+ FormatDoc();
+}
+
+void TextEngine::FormatDoc()
+{
+ if ( IsFormatted() || !GetUpdateMode() || IsFormatting() )
+ return;
+
+ mbIsFormatting = true;
+ mbHasMultiLineParas = false;
+
+ tools::Long nY = 0;
+ bool bGrow = false;
+
+ maInvalidRect = tools::Rectangle(); // clear
+ for ( sal_uInt32 nPara = 0; nPara < mpTEParaPortions->Count(); ++nPara )
+ {
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ if ( pTEParaPortion->IsInvalid() )
+ {
+ const tools::Long nOldParaWidth = mnCurTextWidth >= 0 ? CalcTextWidth( nPara ) : -1;
+
+ Broadcast( TextHint( SfxHintId::TextFormatPara, nPara ) );
+
+ if ( CreateLines( nPara ) )
+ bGrow = true;
+
+ // set InvalidRect only once
+ if ( maInvalidRect.IsEmpty() )
+ {
+ // otherwise remains Empty() for Paperwidth 0 (AutoPageSize)
+ const tools::Long nWidth = mnMaxTextWidth
+ ? mnMaxTextWidth
+ : std::numeric_limits<tools::Long>::max();
+ const Range aInvRange( GetInvalidYOffsets( nPara ) );
+ maInvalidRect = tools::Rectangle( Point( 0, nY+aInvRange.Min() ),
+ Size( nWidth, aInvRange.Len() ) );
+ }
+ else
+ {
+ maInvalidRect.SetBottom( nY + CalcParaHeight( nPara ) );
+ }
+
+ if ( mnCurTextWidth >= 0 )
+ {
+ const tools::Long nNewParaWidth = CalcTextWidth( nPara );
+ if ( nNewParaWidth >= mnCurTextWidth )
+ mnCurTextWidth = nNewParaWidth;
+ else if ( nOldParaWidth >= mnCurTextWidth )
+ mnCurTextWidth = -1;
+ }
+ }
+ else if ( bGrow )
+ {
+ maInvalidRect.SetBottom( nY + CalcParaHeight( nPara ) );
+ }
+ nY += CalcParaHeight( nPara );
+ if ( !mbHasMultiLineParas && pTEParaPortion->GetLines().size() > 1 )
+ mbHasMultiLineParas = true;
+ }
+
+ if ( !maInvalidRect.IsEmpty() )
+ {
+ const tools::Long nNewHeight = CalcTextHeight();
+ const tools::Long nDiff = nNewHeight - mnCurTextHeight;
+ if ( nNewHeight < mnCurTextHeight )
+ {
+ maInvalidRect.SetBottom( std::max( nNewHeight, mnCurTextHeight ) );
+ if ( maInvalidRect.IsEmpty() )
+ {
+ maInvalidRect.SetTop( 0 );
+ // Left and Right are not evaluated, but set because of IsEmpty
+ maInvalidRect.SetLeft( 0 );
+ maInvalidRect.SetRight( mnMaxTextWidth );
+ }
+ }
+
+ mnCurTextHeight = nNewHeight;
+ if ( nDiff )
+ {
+ mbFormatted = true;
+ Broadcast( TextHint( SfxHintId::TextHeightChanged ) );
+ }
+ }
+
+ mbIsFormatting = false;
+ mbFormatted = true;
+
+ Broadcast( TextHint( SfxHintId::TextFormatted ) );
+}
+
+void TextEngine::CreateAndInsertEmptyLine( sal_uInt32 nPara )
+{
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+
+ TextLine aTmpLine;
+ aTmpLine.SetStart( pNode->GetText().getLength() );
+ aTmpLine.SetEnd( aTmpLine.GetStart() );
+
+ if ( ImpGetAlign() == TxtAlign::Center )
+ aTmpLine.SetStartX( static_cast<short>(mnMaxTextWidth / 2) );
+ else if ( ImpGetAlign() == TxtAlign::Right )
+ aTmpLine.SetStartX( static_cast<short>(mnMaxTextWidth) );
+ else
+ aTmpLine.SetStartX( mpDoc->GetLeftMargin() );
+
+ bool bLineBreak = !pNode->GetText().isEmpty();
+
+ TETextPortion aDummyPortion( 0 );
+ aDummyPortion.GetWidth() = 0;
+ pTEParaPortion->GetTextPortions().push_back( aDummyPortion );
+
+ if ( bLineBreak )
+ {
+ // -2: The new one is already inserted.
+ const std::size_t nPos = pTEParaPortion->GetTextPortions().size() - 1;
+ aTmpLine.SetStartPortion( nPos );
+ aTmpLine.SetEndPortion( nPos );
+ }
+ pTEParaPortion->GetLines().push_back( aTmpLine );
+}
+
+void TextEngine::ImpBreakLine( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nPortionStart, tools::Long nRemainingWidth )
+{
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+
+ // Font still should be adjusted
+ sal_Int32 nMaxBreakPos = mpRefDev->GetTextBreak( pNode->GetText(), nRemainingWidth, nPortionStart );
+
+ SAL_WARN_IF( nMaxBreakPos >= pNode->GetText().getLength(), "vcl", "ImpBreakLine: Break?!" );
+
+ if ( nMaxBreakPos == -1 ) // GetTextBreak() != GetTextSize()
+ nMaxBreakPos = pNode->GetText().getLength() - 1;
+
+ uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
+ i18n::LineBreakHyphenationOptions aHyphOptions( nullptr, uno::Sequence< beans::PropertyValue >(), 1 );
+
+ i18n::LineBreakUserOptions aUserOptions;
+ aUserOptions.forbiddenBeginCharacters = ImpGetLocaleDataWrapper()->getForbiddenCharacters().beginLine;
+ aUserOptions.forbiddenEndCharacters = ImpGetLocaleDataWrapper()->getForbiddenCharacters().endLine;
+ aUserOptions.applyForbiddenRules = true;
+ aUserOptions.allowPunctuationOutsideMargin = false;
+ aUserOptions.allowHyphenateEnglish = false;
+
+ static const css::lang::Locale aDefLocale;
+ i18n::LineBreakResults aLBR = xBI->getLineBreak( pNode->GetText(), nMaxBreakPos, aDefLocale, pLine->GetStart(), aHyphOptions, aUserOptions );
+ sal_Int32 nBreakPos = aLBR.breakIndex;
+ if ( nBreakPos <= pLine->GetStart() )
+ {
+ nBreakPos = nMaxBreakPos;
+ if ( nBreakPos <= pLine->GetStart() )
+ nBreakPos = pLine->GetStart() + 1; // infinite loop otherwise!
+ }
+
+ // the damaged Portion is the End Portion
+ pLine->SetEnd( nBreakPos );
+ const std::size_t nEndPortion = SplitTextPortion( nPara, nBreakPos );
+
+ if ( nBreakPos >= pLine->GetStart() &&
+ nBreakPos < pNode->GetText().getLength() &&
+ pNode->GetText()[ nBreakPos ] == ' ' )
+ {
+ // generally suppress blanks at the end of line
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ TETextPortion& rTP = pTEParaPortion->GetTextPortions()[ nEndPortion ];
+ SAL_WARN_IF( nBreakPos <= pLine->GetStart(), "vcl", "ImpBreakLine: SplitTextPortion at beginning of line?" );
+ rTP.GetWidth() = CalcTextWidth( nPara, nBreakPos-rTP.GetLen(), rTP.GetLen()-1 );
+ }
+ pLine->SetEndPortion( nEndPortion );
+}
+
+std::size_t TextEngine::SplitTextPortion( sal_uInt32 nPara, sal_Int32 nPos )
+{
+
+ // the Portion at nPos is being split, unless there is already a switch at nPos
+ if ( nPos == 0 )
+ return 0;
+
+ std::size_t nSplitPortion;
+ sal_Int32 nTmpPos = 0;
+ TETextPortion* pTextPortion = nullptr;
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ const std::size_t nPortions = pTEParaPortion->GetTextPortions().size();
+ for ( nSplitPortion = 0; nSplitPortion < nPortions; nSplitPortion++ )
+ {
+ TETextPortion& rTP = pTEParaPortion->GetTextPortions()[nSplitPortion];
+ nTmpPos += rTP.GetLen();
+ if ( nTmpPos >= nPos )
+ {
+ if ( nTmpPos == nPos ) // nothing needs splitting
+ return nSplitPortion;
+ pTextPortion = &rTP;
+ break;
+ }
+ }
+
+ SAL_WARN_IF( !pTextPortion, "vcl", "SplitTextPortion: position outside of region!" );
+
+ const sal_Int32 nOverlapp = nTmpPos - nPos;
+ pTextPortion->GetLen() -= nOverlapp;
+ pTextPortion->GetWidth() = CalcTextWidth( nPara, nPos-pTextPortion->GetLen(), pTextPortion->GetLen() );
+ TETextPortion aNewPortion( nOverlapp );
+ pTEParaPortion->GetTextPortions().insert( pTEParaPortion->GetTextPortions().begin() + nSplitPortion + 1, aNewPortion );
+
+ return nSplitPortion;
+}
+
+void TextEngine::CreateTextPortions( sal_uInt32 nPara, sal_Int32 nStartPos )
+{
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ TextNode* pNode = pTEParaPortion->GetNode();
+ SAL_WARN_IF( pNode->GetText().isEmpty(), "vcl", "CreateTextPortions: should not be used for empty paragraphs!" );
+
+ o3tl::sorted_vector<sal_Int32> aPositions;
+ o3tl::sorted_vector<sal_Int32>::const_iterator aPositionsIt;
+ aPositions.insert(0);
+
+ const sal_uInt16 nAttribs = pNode->GetCharAttribs().Count();
+ for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
+ {
+ TextCharAttrib& rAttrib = pNode->GetCharAttribs().GetAttrib( nAttr );
+
+ aPositions.insert( rAttrib.GetStart() );
+ aPositions.insert( rAttrib.GetEnd() );
+ }
+ aPositions.insert( pNode->GetText().getLength() );
+
+ const std::vector<TEWritingDirectionInfo>& rWritingDirections = pTEParaPortion->GetWritingDirectionInfos();
+ for ( const auto& rWritingDirection : rWritingDirections )
+ aPositions.insert( rWritingDirection.nStartPos );
+
+ if ( mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetPara() == nPara ) )
+ {
+ ExtTextInputAttr nLastAttr = ExtTextInputAttr(0xffff);
+ for( sal_Int32 n = 0; n < mpIMEInfos->nLen; n++ )
+ {
+ if ( mpIMEInfos->pAttribs[n] != nLastAttr )
+ {
+ aPositions.insert( mpIMEInfos->aPos.GetIndex() + n );
+ nLastAttr = mpIMEInfos->pAttribs[n];
+ }
+ }
+ }
+
+ sal_Int32 nTabPos = pNode->GetText().indexOf( '\t' );
+ while ( nTabPos != -1 )
+ {
+ aPositions.insert( nTabPos );
+ aPositions.insert( nTabPos + 1 );
+ nTabPos = pNode->GetText().indexOf( '\t', nTabPos+1 );
+ }
+
+ // Delete starting with...
+ // Unfortunately, the number of TextPortions does not have to be
+ // equal to aPositions.Count(), because of linebreaks
+ sal_Int32 nPortionStart = 0;
+ std::size_t nInvPortion = 0;
+ std::size_t nP;
+ for ( nP = 0; nP < pTEParaPortion->GetTextPortions().size(); nP++ )
+ {
+ TETextPortion& rTmpPortion = pTEParaPortion->GetTextPortions()[nP];
+ nPortionStart += rTmpPortion.GetLen();
+ if ( nPortionStart >= nStartPos )
+ {
+ nPortionStart -= rTmpPortion.GetLen();
+ nInvPortion = nP;
+ break;
+ }
+ }
+ OSL_ENSURE(nP < pTEParaPortion->GetTextPortions().size()
+ || pTEParaPortion->GetTextPortions().empty(),
+ "CreateTextPortions: Nothing to delete!");
+ if ( nInvPortion && ( nPortionStart+pTEParaPortion->GetTextPortions()[nInvPortion].GetLen() > nStartPos ) )
+ {
+ // better one before...
+ // But only if it was within the Portion; otherwise it might be
+ // the only one in the previous line!
+ nInvPortion--;
+ nPortionStart -= pTEParaPortion->GetTextPortions()[nInvPortion].GetLen();
+ }
+ pTEParaPortion->GetTextPortions().DeleteFromPortion( nInvPortion );
+
+ // a Portion might have been created by a line break
+ aPositions.insert( nPortionStart );
+
+ aPositionsIt = aPositions.find( nPortionStart );
+ SAL_WARN_IF( aPositionsIt == aPositions.end(), "vcl", "CreateTextPortions: nPortionStart not found" );
+
+ if ( aPositionsIt != aPositions.end() )
+ {
+ o3tl::sorted_vector<sal_Int32>::const_iterator nextIt = aPositionsIt;
+ for ( ++nextIt; nextIt != aPositions.end(); ++aPositionsIt, ++nextIt )
+ {
+ TETextPortion aNew( *nextIt - *aPositionsIt );
+ pTEParaPortion->GetTextPortions().push_back( aNew );
+ }
+ }
+ OSL_ENSURE(pTEParaPortion->GetTextPortions().size(), "CreateTextPortions: No Portions?!");
+}
+
+void TextEngine::RecalcTextPortion( sal_uInt32 nPara, sal_Int32 nStartPos, sal_Int32 nNewChars )
+{
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ OSL_ENSURE(pTEParaPortion->GetTextPortions().size(), "RecalcTextPortion: no Portions!");
+ OSL_ENSURE(nNewChars, "RecalcTextPortion: Diff == 0");
+
+ TextNode* const pNode = pTEParaPortion->GetNode();
+ if ( nNewChars > 0 )
+ {
+ // If an Attribute is starting/ending at nStartPos, or there is a tab
+ // before nStartPos => a new Portion starts.
+ // Otherwise the Portion is extended at nStartPos.
+ // Or if at the very beginning ( StartPos 0 ) followed by a tab...
+ if ( ( pNode->GetCharAttribs().HasBoundingAttrib( nStartPos ) ) ||
+ ( nStartPos && ( pNode->GetText()[ nStartPos - 1 ] == '\t' ) ) ||
+ ( !nStartPos && ( nNewChars < pNode->GetText().getLength() ) && pNode->GetText()[ nNewChars ] == '\t' ) )
+ {
+ std::size_t nNewPortionPos = 0;
+ if ( nStartPos )
+ nNewPortionPos = SplitTextPortion( nPara, nStartPos ) + 1;
+
+ // Here could be an empty Portion if the paragraph was empty,
+ // or a new line was created by a hard line-break.
+ if ( ( nNewPortionPos < pTEParaPortion->GetTextPortions().size() ) &&
+ !pTEParaPortion->GetTextPortions()[nNewPortionPos].GetLen() )
+ {
+ // use the empty Portion
+ pTEParaPortion->GetTextPortions()[nNewPortionPos].GetLen() = nNewChars;
+ }
+ else
+ {
+ TETextPortion aNewPortion( nNewChars );
+ pTEParaPortion->GetTextPortions().insert( pTEParaPortion->GetTextPortions().begin() + nNewPortionPos, aNewPortion );
+ }
+ }
+ else
+ {
+ sal_Int32 nPortionStart {0};
+ const std::size_t nTP = pTEParaPortion->GetTextPortions().FindPortion( nStartPos, nPortionStart );
+ TETextPortion& rTP = pTEParaPortion->GetTextPortions()[ nTP ];
+ rTP.GetLen() += nNewChars;
+ rTP.GetWidth() = -1;
+ }
+ }
+ else
+ {
+ // Shrink or remove Portion
+ // Before calling this function, ensure that no Portions were in the deleted range!
+
+ // There must be no Portion reaching into or starting within,
+ // thus: nStartPos <= nPos <= nStartPos - nNewChars(neg.)
+ std::size_t nPortion = 0;
+ sal_Int32 nPos = 0;
+ const sal_Int32 nEnd = nStartPos-nNewChars;
+ const std::size_t nPortions = pTEParaPortion->GetTextPortions().size();
+ TETextPortion* pTP = nullptr;
+ for ( nPortion = 0; nPortion < nPortions; nPortion++ )
+ {
+ pTP = &pTEParaPortion->GetTextPortions()[ nPortion ];
+ if ( ( nPos+pTP->GetLen() ) > nStartPos )
+ {
+ SAL_WARN_IF( nPos > nStartPos, "vcl", "RecalcTextPortion: Bad Start!" );
+ SAL_WARN_IF( nPos+pTP->GetLen() < nEnd, "vcl", "RecalcTextPortion: Bad End!" );
+ break;
+ }
+ nPos += pTP->GetLen();
+ }
+ SAL_WARN_IF( !pTP, "vcl", "RecalcTextPortion: Portion not found!" );
+ if ( ( nPos == nStartPos ) && ( (nPos+pTP->GetLen()) == nEnd ) )
+ {
+ // remove Portion
+ pTEParaPortion->GetTextPortions().erase( pTEParaPortion->GetTextPortions().begin() + nPortion );
+ }
+ else
+ {
+ SAL_WARN_IF( pTP->GetLen() <= (-nNewChars), "vcl", "RecalcTextPortion: Portion too small to shrink!" );
+ pTP->GetLen() += nNewChars;
+ }
+ OSL_ENSURE( pTEParaPortion->GetTextPortions().size(),
+ "RecalcTextPortion: none are left!" );
+ }
+}
+
+void TextEngine::ImpPaint( OutputDevice* pOutDev, const Point& rStartPos, tools::Rectangle const* pPaintArea, TextSelection const* pSelection )
+{
+ if ( !GetUpdateMode() )
+ return;
+
+ if ( !IsFormatted() )
+ FormatDoc();
+
+ vcl::Window* const pOutWin = pOutDev->GetOwnerWindow();
+ const bool bTransparent = (pOutWin && pOutWin->IsPaintTransparent());
+
+ tools::Long nY = rStartPos.Y();
+
+ TextPaM const* pSelStart = nullptr;
+ TextPaM const* pSelEnd = nullptr;
+ if ( pSelection && pSelection->HasRange() )
+ {
+ const bool bInvers = pSelection->GetEnd() < pSelection->GetStart();
+ pSelStart = !bInvers ? &pSelection->GetStart() : &pSelection->GetEnd();
+ pSelEnd = bInvers ? &pSelection->GetStart() : &pSelection->GetEnd();
+ }
+
+ const StyleSettings& rStyleSettings = pOutDev->GetSettings().GetStyleSettings();
+
+ // for all paragraphs
+ for ( sal_uInt32 nPara = 0; nPara < mpTEParaPortions->Count(); ++nPara )
+ {
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
+ // in case while typing Idle-Formatting, asynchronous Paint
+ if ( pPortion->IsInvalid() )
+ return;
+
+ const tools::Long nParaHeight = CalcParaHeight( nPara );
+ if ( !pPaintArea || ( ( nY + nParaHeight ) > pPaintArea->Top() ) )
+ {
+ // for all lines of the paragraph
+ sal_Int32 nIndex = 0;
+ for ( auto & rLine : pPortion->GetLines() )
+ {
+ Point aTmpPos( rStartPos.X() + rLine.GetStartX(), nY );
+
+ if ( !pPaintArea || ( ( nY + mnCharHeight ) > pPaintArea->Top() ) )
+ {
+ // for all Portions of the line
+ nIndex = rLine.GetStart();
+ for ( std::size_t y = rLine.GetStartPortion(); y <= rLine.GetEndPortion(); y++ )
+ {
+ OSL_ENSURE(pPortion->GetTextPortions().size(),
+ "ImpPaint: Line without Textportion!");
+ TETextPortion& rTextPortion = pPortion->GetTextPortions()[ y ];
+
+ ImpInitLayoutMode( pOutDev /*, pTextPortion->IsRightToLeft() */);
+
+ const tools::Long nTxtWidth = rTextPortion.GetWidth();
+ aTmpPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nIndex, nIndex ) );
+
+ // only print if starting in the visible region
+ if ( ( aTmpPos.X() + nTxtWidth ) >= 0 )
+ {
+ switch ( rTextPortion.GetKind() )
+ {
+ case PORTIONKIND_TEXT:
+ {
+ vcl::Font aFont;
+ SeekCursor( nPara, nIndex+1, aFont, pOutDev );
+ if( bTransparent )
+ aFont.SetTransparent( true );
+ else if ( pSelection )
+ aFont.SetTransparent( false );
+ pOutDev->SetFont( aFont );
+
+ sal_Int32 nTmpIndex = nIndex;
+ sal_Int32 nEnd = nTmpIndex + rTextPortion.GetLen();
+ Point aPos = aTmpPos;
+
+ bool bDone = false;
+ if ( pSelStart )
+ {
+ // is a part of it in the selection?
+ const TextPaM aTextStart( nPara, nTmpIndex );
+ const TextPaM aTextEnd( nPara, nEnd );
+ if ( ( aTextStart < *pSelEnd ) && ( aTextEnd > *pSelStart ) )
+ {
+ // 1) vcl::Region before Selection
+ if ( aTextStart < *pSelStart )
+ {
+ const sal_Int32 nL = pSelStart->GetIndex() - nTmpIndex;
+ pOutDev->SetFont( aFont);
+ pOutDev->SetTextFillColor();
+ aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
+ pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nL );
+ nTmpIndex = nTmpIndex + nL;
+
+ }
+ // 2) vcl::Region with Selection
+ sal_Int32 nL = nEnd - nTmpIndex;
+ if ( aTextEnd > *pSelEnd )
+ nL = pSelEnd->GetIndex() - nTmpIndex;
+ if ( nL )
+ {
+ const Color aOldTextColor = pOutDev->GetTextColor();
+ pOutDev->SetTextColor( rStyleSettings.GetHighlightTextColor() );
+ pOutDev->SetTextFillColor( rStyleSettings.GetHighlightColor() );
+ aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
+ pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nL );
+ pOutDev->SetTextColor( aOldTextColor );
+ pOutDev->SetTextFillColor();
+ nTmpIndex = nTmpIndex + nL;
+ }
+
+ // 3) vcl::Region after Selection
+ if ( nTmpIndex < nEnd )
+ {
+ nL = nEnd-nTmpIndex;
+ pOutDev->SetTextFillColor();
+ aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nTmpIndex+nL ) );
+ pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nEnd-nTmpIndex );
+ }
+ bDone = true;
+ }
+ }
+ if ( !bDone )
+ {
+ pOutDev->SetTextFillColor();
+ aPos.setX( rStartPos.X() + ImpGetOutputOffset( nPara, &rLine, nTmpIndex, nEnd ) );
+ pOutDev->DrawText( aPos, pPortion->GetNode()->GetText(), nTmpIndex, nEnd-nTmpIndex );
+ }
+ }
+ break;
+ case PORTIONKIND_TAB:
+ // for HideSelection() only Range, pSelection = 0.
+ if ( pSelStart ) // also implies pSelEnd
+ {
+ const tools::Rectangle aTabArea( aTmpPos, Point( aTmpPos.X()+nTxtWidth, aTmpPos.Y()+mnCharHeight-1 ) );
+ // is the Tab in the Selection???
+ const TextPaM aTextStart(nPara, nIndex);
+ const TextPaM aTextEnd(nPara, nIndex + 1);
+ if ((aTextStart < *pSelEnd) && (aTextEnd > *pSelStart))
+ {
+ const Color aOldColor = pOutDev->GetFillColor();
+ pOutDev->SetFillColor(
+ rStyleSettings.GetHighlightColor());
+ pOutDev->DrawRect(aTabArea);
+ pOutDev->SetFillColor(aOldColor);
+ }
+ else
+ {
+ pOutDev->Erase( aTabArea );
+ }
+ }
+ break;
+ default:
+ OSL_FAIL( "ImpPaint: Unknown Portion-Type !" );
+ }
+ }
+
+ nIndex += rTextPortion.GetLen();
+ }
+ }
+
+ nY += mnCharHeight;
+
+ if ( pPaintArea && ( nY >= pPaintArea->Bottom() ) )
+ break; // no more visible actions
+ }
+ }
+ else
+ {
+ nY += nParaHeight;
+ }
+
+ if ( pPaintArea && ( nY > pPaintArea->Bottom() ) )
+ break; // no more visible actions
+ }
+}
+
+bool TextEngine::CreateLines( sal_uInt32 nPara )
+{
+ // bool: changing Height of Paragraph Yes/No - true/false
+
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ SAL_WARN_IF( !pTEParaPortion->IsInvalid(), "vcl", "CreateLines: Portion not invalid!" );
+
+ const auto nOldLineCount = pTEParaPortion->GetLines().size();
+
+ // fast special case for empty paragraphs
+ if ( pTEParaPortion->GetNode()->GetText().isEmpty() )
+ {
+ if ( !pTEParaPortion->GetTextPortions().empty() )
+ pTEParaPortion->GetTextPortions().Reset();
+ pTEParaPortion->GetLines().clear();
+ CreateAndInsertEmptyLine( nPara );
+ pTEParaPortion->SetValid();
+ return nOldLineCount != pTEParaPortion->GetLines().size();
+ }
+
+ // initialization
+ if ( pTEParaPortion->GetLines().empty() )
+ {
+ pTEParaPortion->GetLines().emplace_back( );
+ }
+
+ const sal_Int32 nInvalidDiff = pTEParaPortion->GetInvalidDiff();
+ const sal_Int32 nInvalidStart = pTEParaPortion->GetInvalidPosStart();
+ const sal_Int32 nInvalidEnd = nInvalidStart + std::abs( nInvalidDiff );
+ bool bQuickFormat = false;
+
+ if ( pTEParaPortion->GetWritingDirectionInfos().empty() )
+ ImpInitWritingDirections( nPara );
+
+ if ( pTEParaPortion->GetWritingDirectionInfos().size() == 1 && pTEParaPortion->IsSimpleInvalid() )
+ {
+ bQuickFormat = nInvalidDiff != 0;
+ if ( nInvalidDiff < 0 )
+ {
+ // check if deleting across Portion border
+ sal_Int32 nPos = 0;
+ for ( const auto & rTP : pTEParaPortion->GetTextPortions() )
+ {
+ // there must be no Start/End in the deleted region
+ nPos += rTP.GetLen();
+ if ( nPos > nInvalidStart && nPos < nInvalidEnd )
+ {
+ bQuickFormat = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( bQuickFormat )
+ RecalcTextPortion( nPara, nInvalidStart, nInvalidDiff );
+ else
+ CreateTextPortions( nPara, nInvalidStart );
+
+ // search for line with InvalidPos; start a line prior
+ // flag lines => do not remove!
+
+ sal_uInt16 nLine = pTEParaPortion->GetLines().size()-1;
+ for ( sal_uInt16 nL = 0; nL <= nLine; nL++ )
+ {
+ TextLine& rLine = pTEParaPortion->GetLines()[ nL ];
+ if ( rLine.GetEnd() > nInvalidStart )
+ {
+ nLine = nL;
+ break;
+ }
+ rLine.SetValid();
+ }
+ // start a line before...
+ // if typing at the end, the line before cannot change
+ if ( nLine && ( !pTEParaPortion->IsSimpleInvalid() || ( nInvalidEnd < pNode->GetText().getLength() ) || ( nInvalidDiff <= 0 ) ) )
+ nLine--;
+
+ TextLine* pLine = &( pTEParaPortion->GetLines()[ nLine ] );
+
+ // format all lines starting here
+ std::size_t nDelFromLine = TETextPortionList::npos;
+
+ sal_Int32 nIndex = pLine->GetStart();
+ TextLine aSaveLine( *pLine );
+
+ while ( nIndex < pNode->GetText().getLength() )
+ {
+ bool bEOL = false;
+ sal_Int32 nPortionStart = 0;
+ sal_Int32 nPortionEnd = 0;
+
+ sal_Int32 nTmpPos = nIndex;
+ std::size_t nTmpPortion = pLine->GetStartPortion();
+ tools::Long nTmpWidth = mpDoc->GetLeftMargin();
+ // do not subtract margin; it is included in TmpWidth
+ tools::Long nXWidth = std::max(
+ mnMaxTextWidth ? mnMaxTextWidth : std::numeric_limits<tools::Long>::max(), nTmpWidth);
+
+ // search for Portion that does not fit anymore into line
+ TETextPortion* pPortion = nullptr;
+ bool bBrokenLine = false;
+
+ while ( ( nTmpWidth <= nXWidth ) && !bEOL && ( nTmpPortion < pTEParaPortion->GetTextPortions().size() ) )
+ {
+ nPortionStart = nTmpPos;
+ pPortion = &pTEParaPortion->GetTextPortions()[ nTmpPortion ];
+ SAL_WARN_IF( !pPortion->GetLen(), "vcl", "CreateLines: Empty Portion!" );
+ if ( pNode->GetText()[ nTmpPos ] == '\t' )
+ {
+ tools::Long nCurPos = nTmpWidth-mpDoc->GetLeftMargin();
+ nTmpWidth = ((nCurPos/mnDefTab)+1)*mnDefTab+mpDoc->GetLeftMargin();
+ pPortion->GetWidth() = nTmpWidth - nCurPos - mpDoc->GetLeftMargin();
+ // infinite loop, if this is the first token of the line and nTmpWidth > aPaperSize.Width !!!
+ if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
+ {
+ // adjust Tab
+ pPortion->GetWidth() = nXWidth-1;
+ nTmpWidth = pPortion->GetWidth();
+ bEOL = true;
+ bBrokenLine = true;
+ }
+ pPortion->GetKind() = PORTIONKIND_TAB;
+ }
+ else
+ {
+
+ pPortion->GetWidth() = CalcTextWidth( nPara, nTmpPos, pPortion->GetLen() );
+ nTmpWidth += pPortion->GetWidth();
+
+ pPortion->SetRightToLeft( ImpGetRightToLeft( nPara, nTmpPos+1 ) );
+ pPortion->GetKind() = PORTIONKIND_TEXT;
+ }
+
+ nTmpPos += pPortion->GetLen();
+ nPortionEnd = nTmpPos;
+ nTmpPortion++;
+ }
+
+ // this was perhaps one Portion too far
+ bool bFixedEnd = false;
+ if ( nTmpWidth > nXWidth )
+ {
+ nPortionEnd = nTmpPos;
+ nTmpPos -= pPortion->GetLen();
+ nPortionStart = nTmpPos;
+ nTmpPortion--;
+ bEOL = false;
+
+ nTmpWidth -= pPortion->GetWidth();
+ if ( pPortion->GetKind() == PORTIONKIND_TAB )
+ {
+ bEOL = true;
+ bFixedEnd = true;
+ }
+ }
+ else
+ {
+ bEOL = true;
+ pLine->SetEnd( nPortionEnd );
+ OSL_ENSURE(pTEParaPortion->GetTextPortions().size(),
+ "CreateLines: No TextPortions?");
+ pLine->SetEndPortion( pTEParaPortion->GetTextPortions().size() - 1 );
+ }
+
+ if ( bFixedEnd )
+ {
+ pLine->SetEnd( nPortionStart );
+ pLine->SetEndPortion( nTmpPortion-1 );
+ }
+ else if ( bBrokenLine )
+ {
+ pLine->SetEnd( nPortionStart+1 );
+ pLine->SetEndPortion( nTmpPortion-1 );
+ }
+ else if ( !bEOL )
+ {
+ SAL_WARN_IF( (nPortionEnd-nPortionStart) != pPortion->GetLen(), "vcl", "CreateLines: There is a Portion after all?!" );
+ const tools::Long nRemainingWidth = mnMaxTextWidth - nTmpWidth;
+ ImpBreakLine( nPara, pLine, nPortionStart, nRemainingWidth );
+ }
+
+ if ( ( ImpGetAlign() == TxtAlign::Center ) || ( ImpGetAlign() == TxtAlign::Right ) )
+ {
+ // adjust
+ tools::Long nTextWidth = 0;
+ for ( std::size_t nTP = pLine->GetStartPortion(); nTP <= pLine->GetEndPortion(); nTP++ )
+ {
+ TETextPortion& rTextPortion = pTEParaPortion->GetTextPortions()[ nTP ];
+ nTextWidth += rTextPortion.GetWidth();
+ }
+ const tools::Long nSpace = mnMaxTextWidth - nTextWidth;
+ if ( nSpace > 0 )
+ {
+ if ( ImpGetAlign() == TxtAlign::Center )
+ pLine->SetStartX( static_cast<sal_uInt16>(nSpace / 2) );
+ else // TxtAlign::Right
+ pLine->SetStartX( static_cast<sal_uInt16>(nSpace) );
+ }
+ }
+ else
+ {
+ pLine->SetStartX( mpDoc->GetLeftMargin() );
+ }
+
+ // check if the line has to be printed again
+ pLine->SetInvalid();
+
+ if ( pTEParaPortion->IsSimpleInvalid() )
+ {
+ // Change due to simple TextChange...
+ // Do not abort formatting, as Portions might have to be split!
+ // Once it is ok to abort, then validate the following lines!
+ // But mark as valid, thus reduce printing...
+ if ( pLine->GetEnd() < nInvalidStart )
+ {
+ if ( *pLine == aSaveLine )
+ {
+ pLine->SetValid();
+ }
+ }
+ else
+ {
+ const sal_Int32 nStart = pLine->GetStart();
+ const sal_Int32 nEnd = pLine->GetEnd();
+
+ if ( nStart > nInvalidEnd )
+ {
+ if ( ( ( nStart-nInvalidDiff ) == aSaveLine.GetStart() ) &&
+ ( ( nEnd-nInvalidDiff ) == aSaveLine.GetEnd() ) )
+ {
+ pLine->SetValid();
+ if ( bQuickFormat )
+ {
+ pTEParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
+ break;
+ }
+ }
+ }
+ else if ( bQuickFormat && ( nEnd > nInvalidEnd) )
+ {
+ // If the invalid line ends such that the next line starts
+ // at the 'same' position as before (no change in line breaks),
+ // the text width does not have to be recalculated.
+ if ( nEnd == ( aSaveLine.GetEnd() + nInvalidDiff ) )
+ {
+ pTEParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
+ break;
+ }
+ }
+ }
+ }
+
+ nIndex = pLine->GetEnd(); // next line Start = previous line End
+ // because nEnd is past the last char!
+
+ const std::size_t nEndPortion = pLine->GetEndPortion();
+
+ // next line or new line
+ pLine = nullptr;
+ if ( nLine < pTEParaPortion->GetLines().size()-1 )
+ pLine = &( pTEParaPortion->GetLines()[ ++nLine ] );
+ if ( pLine && ( nIndex >= pNode->GetText().getLength() ) )
+ {
+ nDelFromLine = nLine;
+ break;
+ }
+ if ( !pLine )
+ {
+ if ( nIndex < pNode->GetText().getLength() )
+ {
+ ++nLine;
+ pTEParaPortion->GetLines().insert( pTEParaPortion->GetLines().begin() + nLine, TextLine() );
+ pLine = &pTEParaPortion->GetLines()[nLine];
+ }
+ else
+ {
+ break;
+ }
+ }
+ aSaveLine = *pLine;
+ pLine->SetStart( nIndex );
+ pLine->SetEnd( nIndex );
+ pLine->SetStartPortion( nEndPortion+1 );
+ pLine->SetEndPortion( nEndPortion+1 );
+
+ } // while ( Index < Len )
+
+ if (nDelFromLine != TETextPortionList::npos)
+ {
+ pTEParaPortion->GetLines().erase( pTEParaPortion->GetLines().begin() + nDelFromLine,
+ pTEParaPortion->GetLines().end() );
+ }
+
+ SAL_WARN_IF( pTEParaPortion->GetLines().empty(), "vcl", "CreateLines: No Line!" );
+
+ pTEParaPortion->SetValid();
+
+ return nOldLineCount != pTEParaPortion->GetLines().size();
+}
+
+OUString TextEngine::GetWord( const TextPaM& rCursorPos, TextPaM* pStartOfWord, TextPaM* pEndOfWord )
+{
+ OUString aWord;
+ if ( rCursorPos.GetPara() < mpDoc->GetNodes().size() )
+ {
+ TextSelection aSel( rCursorPos );
+ TextNode* pNode = mpDoc->GetNodes()[ rCursorPos.GetPara() ].get();
+ uno::Reference < i18n::XBreakIterator > xBI = GetBreakIterator();
+ i18n::Boundary aBoundary = xBI->getWordBoundary( pNode->GetText(), rCursorPos.GetIndex(), GetLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
+ // tdf#57879 - expand selection to the left to include connector punctuations and search for additional word boundaries
+ if (aBoundary.startPos > 0 && aBoundary.startPos < pNode->GetText().getLength() && u_charType(pNode->GetText()[aBoundary.startPos]) == U_CONNECTOR_PUNCTUATION)
+ {
+ aBoundary.startPos = xBI->getWordBoundary(pNode->GetText(), aBoundary.startPos - 1,
+ GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).startPos;
+ }
+ while (aBoundary.startPos > 0 && u_charType(pNode->GetText()[aBoundary.startPos - 1]) == U_CONNECTOR_PUNCTUATION)
+ {
+ aBoundary.startPos = std::min(aBoundary.startPos,
+ xBI->getWordBoundary( pNode->GetText(), aBoundary.startPos - 2,
+ GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).startPos);
+ }
+ // tdf#57879 - expand selection to the right to include connector punctuations and search for additional word boundaries
+ if (aBoundary.endPos > 0 && aBoundary.endPos < pNode->GetText().getLength() && u_charType(pNode->GetText()[aBoundary.endPos - 1]) == U_CONNECTOR_PUNCTUATION)
+ {
+ aBoundary.endPos = xBI->getWordBoundary(pNode->GetText(), aBoundary.endPos,
+ GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).endPos;
+ }
+ while (aBoundary.endPos < pNode->GetText().getLength() && u_charType(pNode->GetText()[aBoundary.endPos]) == U_CONNECTOR_PUNCTUATION)
+ {
+ aBoundary.endPos = xBI->getWordBoundary(pNode->GetText(), aBoundary.endPos + 1,
+ GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true).endPos;
+ }
+ aSel.GetStart().GetIndex() = aBoundary.startPos;
+ aSel.GetEnd().GetIndex() = aBoundary.endPos;
+ aWord = pNode->GetText().copy( aSel.GetStart().GetIndex(), aSel.GetEnd().GetIndex() - aSel.GetStart().GetIndex() );
+ if ( pStartOfWord )
+ *pStartOfWord = aSel.GetStart();
+ if (pEndOfWord)
+ *pEndOfWord = aSel.GetEnd();
+ }
+ return aWord;
+}
+
+bool TextEngine::Read( SvStream& rInput, const TextSelection* pSel )
+{
+ const bool bUpdate = GetUpdateMode();
+ SetUpdateMode( false );
+
+ UndoActionStart();
+ TextSelection aSel;
+ if ( pSel )
+ aSel = *pSel;
+ else
+ {
+ const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
+ TextNode* pNode = mpDoc->GetNodes()[ nParas - 1 ].get();
+ aSel = TextPaM( nParas-1 , pNode->GetText().getLength() );
+ }
+
+ if ( aSel.HasRange() )
+ aSel = ImpDeleteText( aSel );
+
+ OStringBuffer aLine;
+ bool bDone = rInput.ReadLine( aLine );
+ OUString aTmpStr(OStringToOUString(aLine, rInput.GetStreamCharSet()));
+ while ( bDone )
+ {
+ aSel = ImpInsertText( aSel, aTmpStr );
+ bDone = rInput.ReadLine( aLine );
+ aTmpStr = OStringToOUString(aLine, rInput.GetStreamCharSet());
+ if ( bDone )
+ aSel = ImpInsertParaBreak( aSel.GetEnd() );
+ }
+
+ UndoActionEnd();
+
+ const TextSelection aNewSel( aSel.GetEnd(), aSel.GetEnd() );
+
+ // so that FormatAndUpdate does not access the invalid selection
+ if ( GetActiveView() )
+ GetActiveView()->ImpSetSelection( aNewSel );
+
+ SetUpdateMode( bUpdate );
+ FormatAndUpdate( GetActiveView() );
+
+ return rInput.GetError() == ERRCODE_NONE;
+}
+
+void TextEngine::Write( SvStream& rOutput )
+{
+ TextSelection aSel;
+ const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
+ TextNode* pSelNode = mpDoc->GetNodes()[ nParas - 1 ].get();
+ aSel.GetStart() = TextPaM( 0, 0 );
+ aSel.GetEnd() = TextPaM( nParas-1, pSelNode->GetText().getLength() );
+
+ for ( sal_uInt32 nPara = aSel.GetStart().GetPara(); nPara <= aSel.GetEnd().GetPara(); ++nPara )
+ {
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+
+ const sal_Int32 nStartPos = nPara == aSel.GetStart().GetPara()
+ ? aSel.GetStart().GetIndex() : 0;
+ const sal_Int32 nEndPos = nPara == aSel.GetEnd().GetPara()
+ ? aSel.GetEnd().GetIndex() : pNode->GetText().getLength();
+
+ const OUString aText = pNode->GetText().copy( nStartPos, nEndPos-nStartPos );
+ rOutput.WriteLine(OUStringToOString(aText, rOutput.GetStreamCharSet()));
+ }
+}
+
+void TextEngine::RemoveAttribs( sal_uInt32 nPara )
+{
+ if ( nPara >= mpDoc->GetNodes().size() )
+ return;
+
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ if ( pNode->GetCharAttribs().Count() )
+ {
+ pNode->GetCharAttribs().Clear();
+
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+ pTEParaPortion->MarkSelectionInvalid( 0 );
+
+ mbFormatted = false;
+
+ IdleFormatAndUpdate( nullptr, 0xFFFF );
+ }
+}
+
+void TextEngine::SetAttrib( const TextAttrib& rAttr, sal_uInt32 nPara, sal_Int32 nStart, sal_Int32 nEnd )
+{
+
+ // For now do not check if Attributes overlap!
+ // This function is for TextEditors that want to _quickly_ generate the Syntax-Highlight
+
+ // As TextEngine is currently intended only for TextEditors, there is no Undo for Attributes!
+
+ if ( nPara >= mpDoc->GetNodes().size() )
+ return;
+
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ TEParaPortion* pTEParaPortion = mpTEParaPortions->GetObject( nPara );
+
+ const sal_Int32 nMax = pNode->GetText().getLength();
+ if ( nStart > nMax )
+ nStart = nMax;
+ if ( nEnd > nMax )
+ nEnd = nMax;
+
+ pNode->GetCharAttribs().InsertAttrib( std::make_unique<TextCharAttrib>( rAttr, nStart, nEnd ) );
+ pTEParaPortion->MarkSelectionInvalid( nStart );
+
+ mbFormatted = false;
+ IdleFormatAndUpdate( nullptr, 0xFFFF );
+}
+
+void TextEngine::SetTextAlign( TxtAlign eAlign )
+{
+ if ( eAlign != meAlign )
+ {
+ meAlign = eAlign;
+ FormatFullDoc();
+ UpdateViews();
+ }
+}
+
+void TextEngine::ValidateSelection( TextSelection& rSel ) const
+{
+ ValidatePaM( rSel.GetStart() );
+ ValidatePaM( rSel.GetEnd() );
+}
+
+void TextEngine::ValidatePaM( TextPaM& rPaM ) const
+{
+ const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
+ if ( rPaM.GetPara() >= nParas )
+ {
+ rPaM.GetPara() = nParas ? nParas-1 : 0;
+ rPaM.GetIndex() = TEXT_INDEX_ALL;
+ }
+
+ const sal_Int32 nMaxIndex = GetTextLen( rPaM.GetPara() );
+ if ( rPaM.GetIndex() > nMaxIndex )
+ rPaM.GetIndex() = nMaxIndex;
+}
+
+// adjust State & Selection
+
+void TextEngine::ImpParagraphInserted( sal_uInt32 nPara )
+{
+ // No adjustment needed for the active View;
+ // but for all passive Views the Selection needs adjusting.
+ if ( mpViews->size() > 1 )
+ {
+ for ( auto nView = mpViews->size(); nView; )
+ {
+ TextView* pView = (*mpViews)[ --nView ];
+ if ( pView != GetActiveView() )
+ {
+ for ( int n = 0; n <= 1; n++ )
+ {
+ TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
+ if ( rPaM.GetPara() >= nPara )
+ rPaM.GetPara()++;
+ }
+ }
+ }
+ }
+ Broadcast( TextHint( SfxHintId::TextParaInserted, nPara ) );
+}
+
+void TextEngine::ImpParagraphRemoved( sal_uInt32 nPara )
+{
+ if ( mpViews->size() > 1 )
+ {
+ for ( auto nView = mpViews->size(); nView; )
+ {
+ TextView* pView = (*mpViews)[ --nView ];
+ if ( pView != GetActiveView() )
+ {
+ const sal_uInt32 nParas = static_cast<sal_uInt32>(mpDoc->GetNodes().size());
+ for ( int n = 0; n <= 1; n++ )
+ {
+ TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
+ if ( rPaM.GetPara() > nPara )
+ rPaM.GetPara()--;
+ else if ( rPaM.GetPara() == nPara )
+ {
+ rPaM.GetIndex() = 0;
+ if ( rPaM.GetPara() >= nParas )
+ rPaM.GetPara()--;
+ }
+ }
+ }
+ }
+ }
+ Broadcast( TextHint( SfxHintId::TextParaRemoved, nPara ) );
+}
+
+void TextEngine::ImpCharsRemoved( sal_uInt32 nPara, sal_Int32 nPos, sal_Int32 nChars )
+{
+ if ( mpViews->size() > 1 )
+ {
+ for ( auto nView = mpViews->size(); nView; )
+ {
+ TextView* pView = (*mpViews)[ --nView ];
+ if ( pView != GetActiveView() )
+ {
+ const sal_Int32 nEnd = nPos + nChars;
+ for ( int n = 0; n <= 1; n++ )
+ {
+ TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
+ if ( rPaM.GetPara() == nPara )
+ {
+ if ( rPaM.GetIndex() > nEnd )
+ rPaM.GetIndex() = rPaM.GetIndex() - nChars;
+ else if ( rPaM.GetIndex() > nPos )
+ rPaM.GetIndex() = nPos;
+ }
+ }
+ }
+ }
+ }
+ Broadcast( TextHint( SfxHintId::TextParaContentChanged, nPara ) );
+}
+
+void TextEngine::ImpCharsInserted( sal_uInt32 nPara, sal_Int32 nPos, sal_Int32 nChars )
+{
+ if ( mpViews->size() > 1 )
+ {
+ for ( auto nView = mpViews->size(); nView; )
+ {
+ TextView* pView = (*mpViews)[ --nView ];
+ if ( pView != GetActiveView() )
+ {
+ for ( int n = 0; n <= 1; n++ )
+ {
+ TextPaM& rPaM = n ? pView->GetSelection().GetStart(): pView->GetSelection().GetEnd();
+ if ( rPaM.GetPara() == nPara )
+ {
+ if ( rPaM.GetIndex() >= nPos )
+ rPaM.GetIndex() += nChars;
+ }
+ }
+ }
+ }
+ }
+ Broadcast( TextHint( SfxHintId::TextParaContentChanged, nPara ) );
+}
+
+void TextEngine::Draw( OutputDevice* pDev, const Point& rPos )
+{
+ ImpPaint( pDev, rPos, nullptr );
+}
+
+void TextEngine::SetLeftMargin( sal_uInt16 n )
+{
+ mpDoc->SetLeftMargin( n );
+}
+
+uno::Reference< i18n::XBreakIterator > const & TextEngine::GetBreakIterator()
+{
+ if ( !mxBreakIterator.is() )
+ mxBreakIterator = vcl::unohelper::CreateBreakIterator();
+ SAL_WARN_IF( !mxBreakIterator.is(), "vcl", "BreakIterator: Failed to create!" );
+ return mxBreakIterator;
+}
+
+void TextEngine::SetLocale( const css::lang::Locale& rLocale )
+{
+ maLocale = rLocale;
+ mpLocaleDataWrapper.reset();
+}
+
+css::lang::Locale const & TextEngine::GetLocale()
+{
+ if ( maLocale.Language.isEmpty() )
+ {
+ maLocale = Application::GetSettings().GetUILanguageTag().getLocale(); // TODO: why UI locale?
+ }
+ return maLocale;
+}
+
+LocaleDataWrapper* TextEngine::ImpGetLocaleDataWrapper()
+{
+ if ( !mpLocaleDataWrapper )
+ mpLocaleDataWrapper.reset( new LocaleDataWrapper( LanguageTag( GetLocale()) ) );
+
+ return mpLocaleDataWrapper.get();
+}
+
+void TextEngine::SetRightToLeft( bool bR2L )
+{
+ if ( mbRightToLeft != bR2L )
+ {
+ mbRightToLeft = bR2L;
+ meAlign = bR2L ? TxtAlign::Right : TxtAlign::Left;
+ FormatFullDoc();
+ UpdateViews();
+ }
+}
+
+void TextEngine::ImpInitWritingDirections( sal_uInt32 nPara )
+{
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
+ std::vector<TEWritingDirectionInfo>& rInfos = pParaPortion->GetWritingDirectionInfos();
+ rInfos.clear();
+
+ if ( !pParaPortion->GetNode()->GetText().isEmpty() )
+ {
+ const UBiDiLevel nBidiLevel = IsRightToLeft() ? 1 /*RTL*/ : 0 /*LTR*/;
+ OUString aText( pParaPortion->GetNode()->GetText() );
+
+ // Bidi functions from icu 2.0
+
+ UErrorCode nError = U_ZERO_ERROR;
+ UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError );
+ nError = U_ZERO_ERROR;
+
+ ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aText.getStr()), aText.getLength(), nBidiLevel, nullptr, &nError );
+ nError = U_ZERO_ERROR;
+
+ tools::Long nCount = ubidi_countRuns( pBidi, &nError );
+
+ int32_t nStart = 0;
+ int32_t nEnd;
+ UBiDiLevel nCurrDir;
+
+ for ( tools::Long nIdx = 0; nIdx < nCount; ++nIdx )
+ {
+ ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir );
+ // bit 0 of nCurrDir indicates direction
+ rInfos.emplace_back( /*bLeftToRight*/ nCurrDir % 2 == 0, nStart, nEnd );
+ nStart = nEnd;
+ }
+
+ ubidi_close( pBidi );
+ }
+
+ // No infos mean no CTL and default dir is L2R...
+ if ( rInfos.empty() )
+ rInfos.emplace_back( 0, 0, pParaPortion->GetNode()->GetText().getLength() );
+
+}
+
+bool TextEngine::ImpGetRightToLeft( sal_uInt32 nPara, sal_Int32 nPos )
+{
+ bool bRightToLeft = false;
+
+ TextNode* pNode = mpDoc->GetNodes()[ nPara ].get();
+ if ( pNode && !pNode->GetText().isEmpty() )
+ {
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
+ if ( pParaPortion->GetWritingDirectionInfos().empty() )
+ ImpInitWritingDirections( nPara );
+
+ std::vector<TEWritingDirectionInfo>& rDirInfos = pParaPortion->GetWritingDirectionInfos();
+ for ( const auto& rWritingDirectionInfo : rDirInfos )
+ {
+ if ( rWritingDirectionInfo.nStartPos <= nPos && rWritingDirectionInfo.nEndPos >= nPos )
+ {
+ bRightToLeft = !rWritingDirectionInfo.bLeftToRight;
+ break;
+ }
+ }
+ }
+ return bRightToLeft;
+}
+
+tools::Long TextEngine::ImpGetPortionXOffset( sal_uInt32 nPara, TextLine const * pLine, std::size_t nTextPortion )
+{
+ tools::Long nX = pLine->GetStartX();
+
+ TEParaPortion* pParaPortion = mpTEParaPortions->GetObject( nPara );
+
+ for ( std::size_t i = pLine->GetStartPortion(); i < nTextPortion; i++ )
+ {
+ TETextPortion& rPortion = pParaPortion->GetTextPortions()[ i ];
+ nX += rPortion.GetWidth();
+ }
+
+ TETextPortion& rDestPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
+ if ( rDestPortion.GetKind() != PORTIONKIND_TAB )
+ {
+ if ( !IsRightToLeft() && rDestPortion.IsRightToLeft() )
+ {
+ // Portions behind must be added, visual before this portion
+ std::size_t nTmpPortion = nTextPortion+1;
+ while ( nTmpPortion <= pLine->GetEndPortion() )
+ {
+ TETextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PORTIONKIND_TAB ) )
+ nX += rNextTextPortion.GetWidth();
+ else
+ break;
+ nTmpPortion++;
+ }
+ // Portions before must be removed, visual behind this portion
+ nTmpPortion = nTextPortion;
+ while ( nTmpPortion > pLine->GetStartPortion() )
+ {
+ --nTmpPortion;
+ TETextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PORTIONKIND_TAB ) )
+ nX -= rPrevTextPortion.GetWidth();
+ else
+ break;
+ }
+ }
+ else if ( IsRightToLeft() && !rDestPortion.IsRightToLeft() )
+ {
+ // Portions behind must be removed, visual behind this portion
+ std::size_t nTmpPortion = nTextPortion+1;
+ while ( nTmpPortion <= pLine->GetEndPortion() )
+ {
+ TETextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( !rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PORTIONKIND_TAB ) )
+ nX += rNextTextPortion.GetWidth();
+ else
+ break;
+ nTmpPortion++;
+ }
+ // Portions before must be added, visual before this portion
+ nTmpPortion = nTextPortion;
+ while ( nTmpPortion > pLine->GetStartPortion() )
+ {
+ --nTmpPortion;
+ TETextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[ nTmpPortion ];
+ if ( !rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PORTIONKIND_TAB ) )
+ nX -= rPrevTextPortion.GetWidth();
+ else
+ break;
+ }
+ }
+ }
+
+ return nX;
+}
+
+void TextEngine::ImpInitLayoutMode( OutputDevice* pOutDev )
+{
+ vcl::text::ComplexTextLayoutFlags nLayoutMode = pOutDev->GetLayoutMode();
+
+ nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags(vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::BiDiStrong );
+
+ pOutDev->SetLayoutMode( nLayoutMode );
+}
+
+TxtAlign TextEngine::ImpGetAlign() const
+{
+ TxtAlign eAlign = meAlign;
+ if ( IsRightToLeft() )
+ {
+ if ( eAlign == TxtAlign::Left )
+ eAlign = TxtAlign::Right;
+ else if ( eAlign == TxtAlign::Right )
+ eAlign = TxtAlign::Left;
+ }
+ return eAlign;
+}
+
+tools::Long TextEngine::ImpGetOutputOffset( sal_uInt32 nPara, TextLine* pLine, sal_Int32 nIndex, sal_Int32 nIndex2 )
+{
+ TEParaPortion* pPortion = mpTEParaPortions->GetObject( nPara );
+
+ sal_Int32 nPortionStart {0};
+ const std::size_t nPortion = pPortion->GetTextPortions().FindPortion( nIndex, nPortionStart, true );
+
+ TETextPortion& rTextPortion = pPortion->GetTextPortions()[ nPortion ];
+
+ tools::Long nX;
+
+ if ( ( nIndex == nPortionStart ) && ( nIndex == nIndex2 ) )
+ {
+ // Output of full portion, so we need portion x offset.
+ // Use ImpGetPortionXOffset, because GetXPos may deliver left or right position from portion, depending on R2L, L2R
+ nX = ImpGetPortionXOffset( nPara, pLine, nPortion );
+ if ( IsRightToLeft() )
+ {
+ nX = -nX - rTextPortion.GetWidth();
+ }
+ }
+ else
+ {
+ nX = ImpGetXPos( nPara, pLine, nIndex, nIndex == nPortionStart );
+ if ( nIndex2 != nIndex )
+ {
+ const tools::Long nX2 = ImpGetXPos( nPara, pLine, nIndex2 );
+ if ( ( !IsRightToLeft() && ( nX2 < nX ) ) ||
+ ( IsRightToLeft() && ( nX2 > nX ) ) )
+ {
+ nX = nX2;
+ }
+ }
+ if ( IsRightToLeft() )
+ {
+ nX = -nX;
+ }
+ }
+
+ return nX;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textund2.hxx b/vcl/source/edit/textund2.hxx
new file mode 100644
index 000000000..729a130ad
--- /dev/null
+++ b/vcl/source/edit/textund2.hxx
@@ -0,0 +1,108 @@
+/* -*- 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 .
+ */
+#ifndef INCLUDED_VCL_SOURCE_EDIT_TEXTUND2_HXX
+#define INCLUDED_VCL_SOURCE_EDIT_TEXTUND2_HXX
+
+#include "textundo.hxx"
+#include <vcl/textdata.hxx>
+
+class TextUndoDelPara : public TextUndo
+{
+private:
+ bool mbDelObject;
+ sal_uInt32 mnPara;
+ TextNode* mpNode; // points at the valid not-destroyed object
+
+public:
+ TextUndoDelPara( TextEngine* pTextEngine, TextNode* pNode, sal_uInt32 nPara );
+ virtual ~TextUndoDelPara() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual OUString GetComment () const override;
+};
+
+class TextUndoConnectParas : public TextUndo
+{
+private:
+ sal_uInt32 mnPara;
+ sal_Int32 mnSepPos;
+
+public:
+ TextUndoConnectParas( TextEngine* pTextEngine, sal_uInt32 nPara, sal_Int32 nSepPos );
+ virtual ~TextUndoConnectParas() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual OUString GetComment () const override;
+};
+
+class TextUndoSplitPara : public TextUndo
+{
+private:
+ sal_uInt32 mnPara;
+ sal_Int32 mnSepPos;
+
+public:
+ TextUndoSplitPara( TextEngine* pTextEngine, sal_uInt32 nPara, sal_Int32 nSepPos );
+ virtual ~TextUndoSplitPara() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual OUString GetComment () const override;
+};
+
+class TextUndoInsertChars : public TextUndo
+{
+private:
+ TextPaM maTextPaM;
+ OUString maText;
+
+public:
+ TextUndoInsertChars( TextEngine* pTextEngine, const TextPaM& rTextPaM, const OUString& rStr );
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual bool Merge( SfxUndoAction *pNextAction ) override;
+
+ virtual OUString GetComment () const override;
+};
+
+class TextUndoRemoveChars : public TextUndo
+{
+private:
+ TextPaM maTextPaM;
+ OUString maText;
+
+public:
+ TextUndoRemoveChars( TextEngine* pTextEngine, const TextPaM& rTextPaM, const OUString& rStr );
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual OUString GetComment () const override;
+};
+
+#endif // INCLUDED_VCL_SOURCE_EDIT_TEXTUND2_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textundo.cxx b/vcl/source/edit/textundo.cxx
new file mode 100644
index 000000000..89dd0c50f
--- /dev/null
+++ b/vcl/source/edit/textundo.cxx
@@ -0,0 +1,329 @@
+/* -*- 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 "textundo.hxx"
+#include "textund2.hxx"
+#include <strings.hrc>
+
+#include <sal/log.hxx>
+#include <vcl/texteng.hxx>
+#include <vcl/textview.hxx>
+#include <vcl/textdata.hxx>
+#include "textdoc.hxx"
+#include "textdat2.hxx"
+#include <svdata.hxx>
+
+namespace
+{
+
+// Shorten() -- inserts ellipsis (...) in the middle of a long text
+void Shorten (OUString& rString)
+{
+ auto const nLen = rString.getLength();
+ if (nLen <= 48)
+ return;
+
+ // If possible, we don't break a word, hence first we look for a space.
+ // Space before the ellipsis:
+ auto iFirst = rString.lastIndexOf(' ', 32);
+ if (iFirst == -1 || iFirst < 16)
+ iFirst = 24; // not possible
+ // Space after the ellipsis:
+ auto iLast = rString.indexOf(' ', nLen - 16);
+ if (iLast == -1 || iLast > nLen - 4)
+ iLast = nLen - 8; // not possible
+ // finally:
+ rString =
+ OUString::Concat(rString.subView(0, iFirst + 1)) +
+ "..." +
+ rString.subView(iLast);
+}
+
+} // namespace
+
+TextUndoManager::TextUndoManager( TextEngine* p )
+{
+ mpTextEngine = p;
+}
+
+TextUndoManager::~TextUndoManager()
+{
+}
+
+bool TextUndoManager::Undo()
+{
+ if ( GetUndoActionCount() == 0 )
+ return false;
+
+ UndoRedoStart();
+
+ mpTextEngine->SetIsInUndo( true );
+ bool bDone = SfxUndoManager::Undo();
+ mpTextEngine->SetIsInUndo( false );
+
+ UndoRedoEnd();
+
+ return bDone;
+}
+
+bool TextUndoManager::Redo()
+{
+ if ( GetRedoActionCount() == 0 )
+ return false;
+
+ UndoRedoStart();
+
+ mpTextEngine->SetIsInUndo( true );
+ bool bDone = SfxUndoManager::Redo();
+ mpTextEngine->SetIsInUndo( false );
+
+ UndoRedoEnd();
+
+ return bDone;
+}
+
+void TextUndoManager::UndoRedoStart()
+{
+ SAL_WARN_IF( !GetView(), "vcl", "Undo/Redo: Active View?" );
+}
+
+void TextUndoManager::UndoRedoEnd()
+{
+ if ( GetView() )
+ {
+ TextSelection aNewSel( GetView()->GetSelection() );
+ aNewSel.GetStart() = aNewSel.GetEnd();
+ GetView()->ImpSetSelection( aNewSel );
+ }
+
+ mpTextEngine->FormatAndUpdate( GetView() );
+}
+
+TextUndo::TextUndo( TextEngine* p )
+{
+ mpTextEngine = p;
+}
+
+TextUndo::~TextUndo()
+{
+}
+
+OUString TextUndo::GetComment() const
+{
+ return OUString();
+}
+
+void TextUndo::SetSelection( const TextSelection& rSel )
+{
+ if ( GetView() )
+ GetView()->ImpSetSelection( rSel );
+}
+
+TextUndoDelPara::TextUndoDelPara( TextEngine* pTextEngine, TextNode* pNode, sal_uInt32 nPara )
+ : TextUndo( pTextEngine )
+ , mbDelObject( true)
+ , mnPara( nPara )
+ , mpNode( pNode )
+{
+}
+
+TextUndoDelPara::~TextUndoDelPara()
+{
+ if ( mbDelObject )
+ delete mpNode;
+}
+
+void TextUndoDelPara::Undo()
+{
+ GetTextEngine()->InsertContent( std::unique_ptr<TextNode>(mpNode), mnPara );
+ mbDelObject = false; // belongs again to the engine
+
+ if ( GetView() )
+ {
+ TextSelection aSel( TextPaM( mnPara, 0 ), TextPaM( mnPara, mpNode->GetText().getLength() ) );
+ SetSelection( aSel );
+ }
+}
+
+void TextUndoDelPara::Redo()
+{
+ auto & rDocNodes = GetDoc()->GetNodes();
+ // pNode is not valid anymore in case an Undo joined paragraphs
+ mpNode = rDocNodes[ mnPara ].get();
+
+ GetTEParaPortions()->Remove( mnPara );
+
+ // do not delete Node because of Undo!
+ auto it = ::std::find_if( rDocNodes.begin(), rDocNodes.end(),
+ [&] (std::unique_ptr<TextNode> const & p) { return p.get() == mpNode; } );
+ assert(it != rDocNodes.end());
+ it->release();
+ GetDoc()->GetNodes().erase( it );
+
+ GetTextEngine()->ImpParagraphRemoved( mnPara );
+
+ mbDelObject = true; // belongs again to the Undo
+
+ const sal_uInt32 nParas = static_cast<sal_uInt32>(GetDoc()->GetNodes().size());
+ const sal_uInt32 n = mnPara < nParas ? mnPara : nParas-1;
+ TextNode* pN = GetDoc()->GetNodes()[ n ].get();
+ TextPaM aPaM( n, pN->GetText().getLength() );
+ SetSelection( aPaM );
+}
+
+OUString TextUndoDelPara::GetComment () const
+{
+ return VclResId(STR_TEXTUNDO_DELPARA);
+}
+
+TextUndoConnectParas::TextUndoConnectParas( TextEngine* pTextEngine, sal_uInt32 nPara, sal_Int32 nPos )
+ : TextUndo( pTextEngine )
+ , mnPara( nPara )
+ , mnSepPos( nPos )
+{
+}
+
+TextUndoConnectParas::~TextUndoConnectParas()
+{
+}
+
+void TextUndoConnectParas::Undo()
+{
+ TextPaM aPaM = GetTextEngine()->SplitContent( mnPara, mnSepPos );
+ SetSelection( aPaM );
+}
+
+void TextUndoConnectParas::Redo()
+{
+ TextPaM aPaM = GetTextEngine()->ConnectContents( mnPara );
+ SetSelection( aPaM );
+}
+
+OUString TextUndoConnectParas::GetComment () const
+{
+ return VclResId(STR_TEXTUNDO_CONNECTPARAS);
+}
+
+TextUndoSplitPara::TextUndoSplitPara( TextEngine* pTextEngine, sal_uInt32 nPara, sal_Int32 nPos )
+ : TextUndo( pTextEngine )
+ , mnPara( nPara )
+ , mnSepPos ( nPos )
+{
+}
+
+TextUndoSplitPara::~TextUndoSplitPara()
+{
+}
+
+void TextUndoSplitPara::Undo()
+{
+ TextPaM aPaM = GetTextEngine()->ConnectContents( mnPara );
+ SetSelection( aPaM );
+}
+
+void TextUndoSplitPara::Redo()
+{
+ TextPaM aPaM = GetTextEngine()->SplitContent( mnPara, mnSepPos );
+ SetSelection( aPaM );
+}
+
+OUString TextUndoSplitPara::GetComment () const
+{
+ return VclResId(STR_TEXTUNDO_SPLITPARA);
+}
+
+TextUndoInsertChars::TextUndoInsertChars( TextEngine* pTextEngine, const TextPaM& rTextPaM, const OUString& rStr )
+ : TextUndo( pTextEngine ),
+ maTextPaM( rTextPaM ), maText( rStr )
+{
+}
+
+void TextUndoInsertChars::Undo()
+{
+ TextSelection aSel( maTextPaM, maTextPaM );
+ aSel.GetEnd().GetIndex() += maText.getLength();
+ TextPaM aPaM = GetTextEngine()->ImpDeleteText( aSel );
+ SetSelection( aPaM );
+}
+
+void TextUndoInsertChars::Redo()
+{
+ TextSelection aSel( maTextPaM, maTextPaM );
+ GetTextEngine()->ImpInsertText( aSel, maText );
+ TextPaM aNewPaM( maTextPaM );
+ aNewPaM.GetIndex() += maText.getLength();
+ SetSelection( TextSelection( aSel.GetStart(), aNewPaM ) );
+}
+
+bool TextUndoInsertChars::Merge( SfxUndoAction* pNextAction )
+{
+ TextUndoInsertChars* pNext = dynamic_cast<TextUndoInsertChars*>(pNextAction);
+ if ( !pNext )
+ return false;
+
+ if ( maTextPaM.GetPara() != pNext->maTextPaM.GetPara() )
+ return false;
+
+ if ( ( maTextPaM.GetIndex() + maText.getLength() ) == pNext->maTextPaM.GetIndex() )
+ {
+ maText += pNext->maText;
+ return true;
+ }
+ return false;
+}
+
+OUString TextUndoInsertChars::GetComment () const
+{
+ // multiple lines?
+ OUString sText(maText);
+ Shorten(sText);
+ return VclResId(STR_TEXTUNDO_INSERTCHARS).replaceAll("$1", sText);
+}
+
+TextUndoRemoveChars::TextUndoRemoveChars( TextEngine* pTextEngine, const TextPaM& rTextPaM, const OUString& rStr )
+ : TextUndo( pTextEngine ),
+ maTextPaM( rTextPaM ), maText( rStr )
+{
+}
+
+void TextUndoRemoveChars::Undo()
+{
+ TextSelection aSel( maTextPaM, maTextPaM );
+ GetTextEngine()->ImpInsertText( aSel, maText );
+ aSel.GetEnd().GetIndex() += maText.getLength();
+ SetSelection( aSel );
+}
+
+void TextUndoRemoveChars::Redo()
+{
+ TextSelection aSel( maTextPaM, maTextPaM );
+ aSel.GetEnd().GetIndex() += maText.getLength();
+ TextPaM aPaM = GetTextEngine()->ImpDeleteText( aSel );
+ SetSelection( aPaM );
+}
+
+OUString TextUndoRemoveChars::GetComment () const
+{
+ // multiple lines?
+ OUString sText(maText);
+ Shorten(sText);
+ return VclResId(STR_TEXTUNDO_REMOVECHARS).replaceAll("$1", sText);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textundo.hxx b/vcl/source/edit/textundo.hxx
new file mode 100644
index 000000000..0ec4a9aba
--- /dev/null
+++ b/vcl/source/edit/textundo.hxx
@@ -0,0 +1,80 @@
+/* -*- 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 .
+ */
+#ifndef INCLUDED_VCL_SOURCE_EDIT_TEXTUNDO_HXX
+#define INCLUDED_VCL_SOURCE_EDIT_TEXTUNDO_HXX
+
+#include <svl/undo.hxx>
+#include <vcl/texteng.hxx>
+
+class TextEngine;
+class TextView;
+class TextSelection;
+class TextDoc;
+class TEParaPortions;
+
+class TextUndoManager : public SfxUndoManager
+{
+ TextEngine* mpTextEngine;
+
+protected:
+
+ void UndoRedoStart();
+ void UndoRedoEnd();
+
+ TextView* GetView() const { return mpTextEngine->GetActiveView(); }
+
+public:
+ explicit TextUndoManager( TextEngine* pTextEngine );
+ virtual ~TextUndoManager() override;
+
+ using SfxUndoManager::Undo;
+ virtual bool Undo() override;
+ using SfxUndoManager::Redo;
+ virtual bool Redo() override;
+
+};
+
+class TextUndo : public SfxUndoAction
+{
+private:
+ TextEngine* mpTextEngine;
+
+protected:
+
+ TextView* GetView() const { return mpTextEngine->GetActiveView(); }
+ void SetSelection( const TextSelection& rSel );
+
+ TextDoc* GetDoc() const { return mpTextEngine->mpDoc.get(); }
+ TEParaPortions* GetTEParaPortions() const { return mpTextEngine->mpTEParaPortions.get(); }
+
+public:
+ explicit TextUndo( TextEngine* pTextEngine );
+ virtual ~TextUndo() override;
+
+ TextEngine* GetTextEngine() const { return mpTextEngine; }
+
+ virtual void Undo() override = 0;
+ virtual void Redo() override = 0;
+
+ virtual OUString GetComment() const override;
+};
+
+#endif // INCLUDED_VCL_SOURCE_EDIT_TEXTUNDO_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/textview.cxx b/vcl/source/edit/textview.cxx
new file mode 100644
index 000000000..ef83beab4
--- /dev/null
+++ b/vcl/source/edit/textview.cxx
@@ -0,0 +1,2237 @@
+/* -*- 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 <memory>
+#include <i18nutil/searchopt.hxx>
+#include <o3tl/deleter.hxx>
+#include <vcl/textview.hxx>
+#include <vcl/texteng.hxx>
+#include <vcl/settings.hxx>
+#include "textdoc.hxx"
+#include <vcl/textdata.hxx>
+#include <vcl/transfer.hxx>
+#include <vcl/xtextedt.hxx>
+#include "textdat2.hxx"
+#include <vcl/commandevent.hxx>
+#include <vcl/inputctx.hxx>
+
+#include <svl/undo.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/window.hxx>
+#include <vcl/svapp.hxx>
+#include <tools/stream.hxx>
+
+#include <sal/log.hxx>
+#include <sot/formats.hxx>
+
+#include <cppuhelper/weak.hxx>
+#include <cppuhelper/queryinterface.hxx>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/util/SearchFlags.hpp>
+
+#include <vcl/toolkit/edit.hxx>
+
+#include <sot/exchange.hxx>
+
+#include <algorithm>
+#include <cstddef>
+
+TETextDataObject::TETextDataObject( const OUString& rText ) : maText( rText )
+{
+}
+
+// css::uno::XInterface
+css::uno::Any TETextDataObject::queryInterface( const css::uno::Type & rType )
+{
+ css::uno::Any aRet = ::cppu::queryInterface( rType, static_cast< css::datatransfer::XTransferable* >(this) );
+ return (aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ));
+}
+
+// css::datatransfer::XTransferable
+css::uno::Any TETextDataObject::getTransferData( const css::datatransfer::DataFlavor& rFlavor )
+{
+ css::uno::Any aAny;
+
+ SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
+ if ( nT == SotClipboardFormatId::STRING )
+ {
+ aAny <<= maText;
+ }
+ else if ( nT == SotClipboardFormatId::HTML )
+ {
+ sal_uInt64 nLen = GetHTMLStream().TellEnd();
+ GetHTMLStream().Seek(0);
+
+ css::uno::Sequence< sal_Int8 > aSeq( nLen );
+ memcpy( aSeq.getArray(), GetHTMLStream().GetData(), nLen );
+ aAny <<= aSeq;
+ }
+ else
+ {
+ throw css::datatransfer::UnsupportedFlavorException();
+ }
+ return aAny;
+}
+
+css::uno::Sequence< css::datatransfer::DataFlavor > TETextDataObject::getTransferDataFlavors( )
+{
+ GetHTMLStream().Seek( STREAM_SEEK_TO_END );
+ bool bHTML = GetHTMLStream().Tell() > 0;
+ css::uno::Sequence< css::datatransfer::DataFlavor > aDataFlavors( bHTML ? 2 : 1 );
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aDataFlavors.getArray()[0] );
+ if ( bHTML )
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::HTML, aDataFlavors.getArray()[1] );
+ return aDataFlavors;
+}
+
+sal_Bool TETextDataObject::isDataFlavorSupported( const css::datatransfer::DataFlavor& rFlavor )
+{
+ SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
+ return ( nT == SotClipboardFormatId::STRING );
+}
+
+struct ImpTextView
+{
+ ExtTextEngine* mpTextEngine;
+
+ VclPtr<vcl::Window> mpWindow;
+ TextSelection maSelection;
+ Point maStartDocPos;
+
+ std::unique_ptr<vcl::Cursor, o3tl::default_delete<vcl::Cursor>> mpCursor;
+
+ std::unique_ptr<TextDDInfo, o3tl::default_delete<TextDDInfo>> mpDDInfo;
+
+ std::unique_ptr<SelectionEngine> mpSelEngine;
+ std::unique_ptr<TextSelFunctionSet> mpSelFuncSet;
+
+ css::uno::Reference< css::datatransfer::dnd::XDragSourceListener > mxDnDListener;
+
+ sal_uInt16 mnTravelXPos;
+
+ bool mbAutoScroll : 1;
+ bool mbInsertMode : 1;
+ bool mbReadOnly : 1;
+ bool mbPaintSelection : 1;
+ bool mbAutoIndent : 1;
+ bool mbCursorEnabled : 1;
+ bool mbClickedInSelection : 1;
+ bool mbCursorAtEndOfLine;
+};
+
+TextView::TextView( ExtTextEngine* pEng, vcl::Window* pWindow ) :
+ mpImpl(new ImpTextView)
+{
+ pWindow->EnableRTL( false );
+
+ mpImpl->mpWindow = pWindow;
+ mpImpl->mpTextEngine = pEng;
+
+ mpImpl->mbPaintSelection = true;
+ mpImpl->mbAutoScroll = true;
+ mpImpl->mbInsertMode = true;
+ mpImpl->mbReadOnly = false;
+ mpImpl->mbAutoIndent = false;
+ mpImpl->mbCursorEnabled = true;
+ mpImpl->mbClickedInSelection = false;
+// mbInSelection = false;
+
+ mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
+
+ mpImpl->mpSelFuncSet = std::make_unique<TextSelFunctionSet>( this );
+ mpImpl->mpSelEngine = std::make_unique<SelectionEngine>( mpImpl->mpWindow, mpImpl->mpSelFuncSet.get() );
+ mpImpl->mpSelEngine->SetSelectionMode( SelectionMode::Range );
+ mpImpl->mpSelEngine->EnableDrag( true );
+
+ mpImpl->mpCursor.reset(new vcl::Cursor);
+ mpImpl->mpCursor->Show();
+ pWindow->SetCursor( mpImpl->mpCursor.get() );
+ pWindow->SetInputContext( InputContext( pEng->GetFont(), InputContextFlags::Text|InputContextFlags::ExtText ) );
+
+ pWindow->GetOutDev()->SetLineColor();
+
+ if ( pWindow->GetDragGestureRecognizer().is() )
+ {
+ mpImpl->mxDnDListener = new vcl::unohelper::DragAndDropWrapper( this );
+
+ css::uno::Reference< css::datatransfer::dnd::XDragGestureListener> xDGL( mpImpl->mxDnDListener, css::uno::UNO_QUERY );
+ pWindow->GetDragGestureRecognizer()->addDragGestureListener( xDGL );
+ css::uno::Reference< css::datatransfer::dnd::XDropTargetListener> xDTL( xDGL, css::uno::UNO_QUERY );
+ pWindow->GetDropTarget()->addDropTargetListener( xDTL );
+ pWindow->GetDropTarget()->setActive( true );
+ pWindow->GetDropTarget()->setDefaultActions( css::datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE );
+ }
+}
+
+TextView::~TextView()
+{
+ mpImpl->mpSelEngine.reset();
+ mpImpl->mpSelFuncSet.reset();
+
+ if ( mpImpl->mpWindow->GetCursor() == mpImpl->mpCursor.get() )
+ mpImpl->mpWindow->SetCursor( nullptr );
+
+ mpImpl->mpCursor.reset();
+ mpImpl->mpDDInfo.reset();
+}
+
+void TextView::Invalidate()
+{
+ mpImpl->mpWindow->Invalidate();
+}
+
+void TextView::SetSelection( const TextSelection& rTextSel, bool bGotoCursor )
+{
+ // if someone left an empty attribute and then the Outliner manipulated the selection
+ if ( !mpImpl->maSelection.HasRange() )
+ mpImpl->mpTextEngine->CursorMoved( mpImpl->maSelection.GetStart().GetPara() );
+
+ // if the selection is manipulated after a KeyInput
+ mpImpl->mpTextEngine->CheckIdleFormatter();
+
+ HideSelection();
+ TextSelection aNewSel( rTextSel );
+ mpImpl->mpTextEngine->ValidateSelection( aNewSel );
+ ImpSetSelection( aNewSel );
+ ShowSelection();
+ ShowCursor( bGotoCursor );
+}
+
+void TextView::SetSelection( const TextSelection& rTextSel )
+{
+ SetSelection( rTextSel, mpImpl->mbAutoScroll );
+}
+
+const TextSelection& TextView::GetSelection() const
+{
+ return mpImpl->maSelection;
+}
+TextSelection& TextView::GetSelection()
+{
+ return mpImpl->maSelection;
+}
+
+void TextView::DeleteSelected()
+{
+// HideSelection();
+
+ mpImpl->mpTextEngine->UndoActionStart();
+ TextPaM aPaM = mpImpl->mpTextEngine->ImpDeleteText( mpImpl->maSelection );
+ mpImpl->mpTextEngine->UndoActionEnd();
+
+ ImpSetSelection( aPaM );
+ mpImpl->mpTextEngine->FormatAndUpdate( this );
+ ShowCursor();
+}
+
+void TextView::ImpPaint(vcl::RenderContext& rRenderContext, const Point& rStartPos, tools::Rectangle const* pPaintArea, TextSelection const* pSelection)
+{
+ if (!mpImpl->mbPaintSelection)
+ {
+ pSelection = nullptr;
+ }
+ else
+ {
+ // set correct background color;
+ // unfortunately we cannot detect if it has changed
+ vcl::Font aFont = mpImpl->mpTextEngine->GetFont();
+ Color aColor = rRenderContext.GetBackground().GetColor();
+ aColor.SetAlpha(255);
+ if (aColor != aFont.GetFillColor())
+ {
+ if (aFont.IsTransparent())
+ aColor = COL_TRANSPARENT;
+ aFont.SetFillColor(aColor);
+ mpImpl->mpTextEngine->maFont = aFont;
+ }
+ }
+
+ mpImpl->mpTextEngine->ImpPaint(&rRenderContext, rStartPos, pPaintArea, pSelection);
+}
+
+void TextView::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ ImpPaint(rRenderContext, rRect);
+}
+
+void TextView::ImpPaint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if ( !mpImpl->mpTextEngine->GetUpdateMode() || mpImpl->mpTextEngine->IsInUndo() )
+ return;
+
+ TextSelection *pDrawSelection = nullptr;
+ if (mpImpl->maSelection.HasRange())
+ pDrawSelection = &mpImpl->maSelection;
+
+ Point aStartPos = ImpGetOutputStartPos(mpImpl->maStartDocPos);
+ ImpPaint(rRenderContext, aStartPos, &rRect, pDrawSelection);
+}
+
+void TextView::ImpSetSelection( const TextSelection& rSelection )
+{
+ if (rSelection == mpImpl->maSelection)
+ return;
+
+ bool bCaret = false, bSelection = false;
+ const TextPaM &rEnd = rSelection.GetEnd();
+ const TextPaM &rOldEnd = mpImpl->maSelection.GetEnd();
+ bool bGap = rSelection.HasRange(), bOldGap = mpImpl->maSelection.HasRange();
+ if (rEnd != rOldEnd)
+ bCaret = true;
+ if (bGap || bOldGap)
+ bSelection = true;
+
+ mpImpl->maSelection = rSelection;
+
+ if (bSelection)
+ mpImpl->mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewSelectionChanged));
+
+ if (bCaret)
+ mpImpl->mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewCaretChanged));
+}
+
+void TextView::ShowSelection()
+{
+ ImpShowHideSelection();
+}
+
+void TextView::HideSelection()
+{
+ ImpShowHideSelection();
+}
+
+void TextView::ShowSelection( const TextSelection& rRange )
+{
+ ImpShowHideSelection( &rRange );
+}
+
+void TextView::ImpShowHideSelection(const TextSelection* pRange)
+{
+ const TextSelection* pRangeOrSelection = pRange ? pRange : &mpImpl->maSelection;
+
+ if ( !pRangeOrSelection->HasRange() )
+ return;
+
+ if( mpImpl->mpWindow->IsPaintTransparent() )
+ mpImpl->mpWindow->Invalidate();
+ else
+ {
+ TextSelection aRange( *pRangeOrSelection );
+ aRange.Justify();
+ bool bVisCursor = mpImpl->mpCursor->IsVisible();
+ mpImpl->mpCursor->Hide();
+ Invalidate();
+ if (bVisCursor)
+ mpImpl->mpCursor->Show();
+ }
+}
+
+bool TextView::KeyInput( const KeyEvent& rKeyEvent )
+{
+ bool bDone = true;
+ bool bModified = false;
+ bool bMoved = false;
+ bool bEndKey = false; // special CursorPosition
+ bool bAllowIdle = true;
+
+ // check mModified;
+ // the local bModified is not set e.g. by Cut/Paste, as here
+ // the update happens somewhere else
+ bool bWasModified = mpImpl->mpTextEngine->IsModified();
+ mpImpl->mpTextEngine->SetModified( false );
+
+ TextSelection aCurSel( mpImpl->maSelection );
+ TextSelection aOldSel( aCurSel );
+
+ sal_uInt16 nCode = rKeyEvent.GetKeyCode().GetCode();
+ KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction();
+ if ( eFunc != KeyFuncType::DONTKNOW )
+ {
+ switch ( eFunc )
+ {
+ case KeyFuncType::CUT:
+ {
+ if ( !mpImpl->mbReadOnly )
+ Cut();
+ }
+ break;
+ case KeyFuncType::COPY:
+ {
+ Copy();
+ }
+ break;
+ case KeyFuncType::PASTE:
+ {
+ if ( !mpImpl->mbReadOnly )
+ Paste();
+ }
+ break;
+ case KeyFuncType::UNDO:
+ {
+ if ( !mpImpl->mbReadOnly )
+ Undo();
+ }
+ break;
+ case KeyFuncType::REDO:
+ {
+ if ( !mpImpl->mbReadOnly )
+ Redo();
+ }
+ break;
+
+ default: // might get processed below
+ eFunc = KeyFuncType::DONTKNOW;
+ }
+ }
+ if ( eFunc == KeyFuncType::DONTKNOW )
+ {
+ switch ( nCode )
+ {
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ case KEY_HOME:
+ case KEY_END:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ case css::awt::Key::MOVE_WORD_FORWARD:
+ case css::awt::Key::SELECT_WORD_FORWARD:
+ case css::awt::Key::MOVE_WORD_BACKWARD:
+ case css::awt::Key::SELECT_WORD_BACKWARD:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
+ case css::awt::Key::MOVE_TO_END_OF_LINE:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
+ case css::awt::Key::SELECT_TO_END_OF_LINE:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
+ case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
+ case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
+ case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
+ case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
+ {
+ if ( ( !rKeyEvent.GetKeyCode().IsMod2() || ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) )
+ && !( rKeyEvent.GetKeyCode().IsMod1() && ( nCode == KEY_PAGEUP || nCode == KEY_PAGEDOWN ) ) )
+ {
+ aCurSel = ImpMoveCursor( rKeyEvent );
+ if ( aCurSel.HasRange() ) {
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
+ Copy( aSelection );
+ }
+ bMoved = true;
+ if ( nCode == KEY_END )
+ bEndKey = true;
+ }
+ else
+ bDone = false;
+ }
+ break;
+ case KEY_BACKSPACE:
+ case KEY_DELETE:
+ case css::awt::Key::DELETE_WORD_BACKWARD:
+ case css::awt::Key::DELETE_WORD_FORWARD:
+ case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
+ case css::awt::Key::DELETE_TO_END_OF_LINE:
+ {
+ if ( !mpImpl->mbReadOnly && !rKeyEvent.GetKeyCode().IsMod2() )
+ {
+ sal_uInt8 nDel = ( nCode == KEY_DELETE ) ? DEL_RIGHT : DEL_LEFT;
+ sal_uInt8 nMode = rKeyEvent.GetKeyCode().IsMod1() ? DELMODE_RESTOFWORD : DELMODE_SIMPLE;
+ if ( ( nMode == DELMODE_RESTOFWORD ) && rKeyEvent.GetKeyCode().IsShift() )
+ nMode = DELMODE_RESTOFCONTENT;
+
+ switch( nCode )
+ {
+ case css::awt::Key::DELETE_WORD_BACKWARD:
+ nDel = DEL_LEFT;
+ nMode = DELMODE_RESTOFWORD;
+ break;
+ case css::awt::Key::DELETE_WORD_FORWARD:
+ nDel = DEL_RIGHT;
+ nMode = DELMODE_RESTOFWORD;
+ break;
+ case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
+ nDel = DEL_LEFT;
+ nMode = DELMODE_RESTOFCONTENT;
+ break;
+ case css::awt::Key::DELETE_TO_END_OF_LINE:
+ nDel = DEL_RIGHT;
+ nMode = DELMODE_RESTOFCONTENT;
+ break;
+ default: break;
+ }
+
+ mpImpl->mpTextEngine->UndoActionStart();
+ aCurSel = ImpDelete( nDel, nMode );
+ mpImpl->mpTextEngine->UndoActionEnd();
+ bModified = true;
+ bAllowIdle = false;
+ }
+ else
+ bDone = false;
+ }
+ break;
+ case KEY_TAB:
+ {
+ if ( !mpImpl->mbReadOnly && !rKeyEvent.GetKeyCode().IsShift() &&
+ !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() &&
+ ImplCheckTextLen( OUString('x') ) )
+ {
+ aCurSel = mpImpl->mpTextEngine->ImpInsertText( aCurSel, '\t', !IsInsertMode() );
+ bModified = true;
+ }
+ else
+ bDone = false;
+ }
+ break;
+ case KEY_RETURN:
+ {
+ // do not swallow Shift-RETURN, as this would disable multi-line entries
+ // in dialogs & property editors
+ if ( !mpImpl->mbReadOnly && !rKeyEvent.GetKeyCode().IsMod1() &&
+ !rKeyEvent.GetKeyCode().IsMod2() && ImplCheckTextLen( OUString('x') ) )
+ {
+ mpImpl->mpTextEngine->UndoActionStart();
+ aCurSel = mpImpl->mpTextEngine->ImpInsertParaBreak( aCurSel );
+ if ( mpImpl->mbAutoIndent )
+ {
+ TextNode* pPrev = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aCurSel.GetEnd().GetPara() - 1 ].get();
+ sal_Int32 n = 0;
+ while ( ( n < pPrev->GetText().getLength() ) && (
+ ( pPrev->GetText()[ n ] == ' ' ) ||
+ ( pPrev->GetText()[ n ] == '\t' ) ) )
+ {
+ n++;
+ }
+ if ( n )
+ aCurSel = mpImpl->mpTextEngine->ImpInsertText( aCurSel, pPrev->GetText().copy( 0, n ) );
+ }
+ mpImpl->mpTextEngine->UndoActionEnd();
+ bModified = true;
+ }
+ else
+ bDone = false;
+ }
+ break;
+ case KEY_INSERT:
+ {
+ if ( !mpImpl->mbReadOnly )
+ SetInsertMode( !IsInsertMode() );
+ }
+ break;
+ default:
+ {
+ if ( TextEngine::IsSimpleCharInput( rKeyEvent ) )
+ {
+ sal_Unicode nCharCode = rKeyEvent.GetCharCode();
+ if ( !mpImpl->mbReadOnly && ImplCheckTextLen( OUString(nCharCode) ) ) // otherwise swallow the character anyway
+ {
+ aCurSel = mpImpl->mpTextEngine->ImpInsertText( nCharCode, aCurSel, !IsInsertMode(), true );
+ bModified = true;
+ }
+ }
+ else
+ bDone = false;
+ }
+ }
+ }
+
+ if ( aCurSel != aOldSel ) // Check if changed, maybe other method already changed mpImpl->maSelection, don't overwrite that!
+ ImpSetSelection( aCurSel );
+
+ if ( ( nCode != KEY_UP ) && ( nCode != KEY_DOWN ) )
+ mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
+
+ if ( bModified )
+ {
+ // Idle-Formatter only if AnyInput
+ if ( bAllowIdle && Application::AnyInput( VclInputFlags::KEYBOARD) )
+ mpImpl->mpTextEngine->IdleFormatAndUpdate( this );
+ else
+ mpImpl->mpTextEngine->FormatAndUpdate( this);
+ }
+ else if ( bMoved )
+ {
+ // selection is painted now in ImpMoveCursor
+ ImpShowCursor( mpImpl->mbAutoScroll, true, bEndKey );
+ }
+
+ if ( mpImpl->mpTextEngine->IsModified() )
+ mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ else if ( bWasModified )
+ mpImpl->mpTextEngine->SetModified( true );
+
+ return bDone;
+}
+
+void TextView::MouseButtonUp( const MouseEvent& rMouseEvent )
+{
+ mpImpl->mbClickedInSelection = false;
+ mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
+ mpImpl->mpSelEngine->SelMouseButtonUp( rMouseEvent );
+ if ( rMouseEvent.IsMiddle() && !IsReadOnly() &&
+ ( GetWindow()->GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) )
+ {
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
+ Paste( aSelection );
+ if ( mpImpl->mpTextEngine->IsModified() )
+ mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ else if ( rMouseEvent.IsLeft() && GetSelection().HasRange() )
+ {
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
+ Copy( aSelection );
+ }
+}
+
+void TextView::MouseButtonDown( const MouseEvent& rMouseEvent )
+{
+ mpImpl->mpTextEngine->CheckIdleFormatter(); // for fast typing and MouseButtonDown
+ mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
+ mpImpl->mbClickedInSelection = IsSelectionAtPoint( rMouseEvent.GetPosPixel() );
+
+ mpImpl->mpTextEngine->SetActiveView( this );
+
+ mpImpl->mpSelEngine->SelMouseButtonDown( rMouseEvent );
+
+ // mbu 20.01.2005 - SelMouseButtonDown() possibly triggers a 'selection changed'
+ // notification. The appropriate handler could change the current selection,
+ // which is the case in the MailMerge address block control. To enable select'n'drag
+ // we need to reevaluate the selection after the notification has been fired.
+ mpImpl->mbClickedInSelection = IsSelectionAtPoint( rMouseEvent.GetPosPixel() );
+
+ // special cases
+ if ( rMouseEvent.IsShift() || ( rMouseEvent.GetClicks() < 2 ))
+ return;
+
+ if ( rMouseEvent.IsMod2() )
+ {
+ HideSelection();
+ ImpSetSelection( mpImpl->maSelection.GetEnd() );
+ SetCursorAtPoint( rMouseEvent.GetPosPixel() ); // not set by SelectionEngine for MOD2
+ }
+
+ if ( rMouseEvent.GetClicks() == 2 )
+ {
+ // select word
+ if ( mpImpl->maSelection.GetEnd().GetIndex() < mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection.GetEnd().GetPara() ) )
+ {
+ HideSelection();
+ // tdf#57879 - expand selection to include connector punctuations
+ TextSelection aNewSel;
+ mpImpl->mpTextEngine->GetWord( mpImpl->maSelection.GetEnd(), &aNewSel.GetStart(), &aNewSel.GetEnd() );
+ ImpSetSelection( aNewSel );
+ ShowSelection();
+ ShowCursor();
+ }
+ }
+ else if ( rMouseEvent.GetClicks() == 3 )
+ {
+ // select paragraph
+ if ( mpImpl->maSelection.GetStart().GetIndex() || ( mpImpl->maSelection.GetEnd().GetIndex() < mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection.GetEnd().GetPara() ) ) )
+ {
+ HideSelection();
+ TextSelection aNewSel( mpImpl->maSelection );
+ aNewSel.GetStart().GetIndex() = 0;
+ aNewSel.GetEnd().GetIndex() = mpImpl->mpTextEngine->mpDoc->GetNodes()[ mpImpl->maSelection.GetEnd().GetPara() ]->GetText().getLength();
+ ImpSetSelection( aNewSel );
+ ShowSelection();
+ ShowCursor();
+ }
+ }
+}
+
+void TextView::MouseMove( const MouseEvent& rMouseEvent )
+{
+ mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
+ mpImpl->mpSelEngine->SelMouseMove( rMouseEvent );
+}
+
+void TextView::Command( const CommandEvent& rCEvt )
+{
+ mpImpl->mpTextEngine->CheckIdleFormatter(); // for fast typing and MouseButtonDown
+ mpImpl->mpTextEngine->SetActiveView( this );
+
+ if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
+ {
+ DeleteSelected();
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ GetSelection().GetEnd().GetPara() ].get();
+ mpImpl->mpTextEngine->mpIMEInfos = std::make_unique<TEIMEInfos>( GetSelection().GetEnd(), pNode->GetText().copy( GetSelection().GetEnd().GetIndex() ) );
+ mpImpl->mpTextEngine->mpIMEInfos->bWasCursorOverwrite = !IsInsertMode();
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
+ {
+ SAL_WARN_IF( !mpImpl->mpTextEngine->mpIMEInfos, "vcl", "CommandEventId::EndExtTextInput => No Start ?" );
+ if( mpImpl->mpTextEngine->mpIMEInfos )
+ {
+ TEParaPortion* pPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetPara() );
+ pPortion->MarkSelectionInvalid( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex() );
+
+ bool bInsertMode = !mpImpl->mpTextEngine->mpIMEInfos->bWasCursorOverwrite;
+
+ mpImpl->mpTextEngine->mpIMEInfos.reset();
+
+ mpImpl->mpTextEngine->TextModified();
+ mpImpl->mpTextEngine->FormatAndUpdate( this );
+
+ SetInsertMode( bInsertMode );
+
+ if ( mpImpl->mpTextEngine->IsModified() )
+ mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput )
+ {
+ SAL_WARN_IF( !mpImpl->mpTextEngine->mpIMEInfos, "vcl", "CommandEventId::ExtTextInput => No Start ?" );
+ if( mpImpl->mpTextEngine->mpIMEInfos )
+ {
+ const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData();
+
+ if ( !pData->IsOnlyCursorChanged() )
+ {
+ TextSelection aSelect( mpImpl->mpTextEngine->mpIMEInfos->aPos );
+ aSelect.GetEnd().GetIndex() += mpImpl->mpTextEngine->mpIMEInfos->nLen;
+ aSelect = mpImpl->mpTextEngine->ImpDeleteText( aSelect );
+ aSelect = mpImpl->mpTextEngine->ImpInsertText( aSelect, pData->GetText() );
+
+ if ( mpImpl->mpTextEngine->mpIMEInfos->bWasCursorOverwrite )
+ {
+ const sal_Int32 nOldIMETextLen = mpImpl->mpTextEngine->mpIMEInfos->nLen;
+ const sal_Int32 nNewIMETextLen = pData->GetText().getLength();
+
+ if ( ( nOldIMETextLen > nNewIMETextLen ) &&
+ ( nNewIMETextLen < mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
+ {
+ // restore old characters
+ sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen;
+ TextPaM aPaM( mpImpl->mpTextEngine->mpIMEInfos->aPos );
+ aPaM.GetIndex() += nNewIMETextLen;
+ mpImpl->mpTextEngine->ImpInsertText( aPaM, mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.copy( nNewIMETextLen, nRestore ) );
+ }
+ else if ( ( nOldIMETextLen < nNewIMETextLen ) &&
+ ( nOldIMETextLen < mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
+ {
+ // overwrite
+ const sal_Int32 nOverwrite = std::min( nNewIMETextLen, mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) - nOldIMETextLen;
+ SAL_WARN_IF( !nOverwrite || (nOverwrite >= 0xFF00), "vcl", "IME Overwrite?!" );
+ TextPaM aPaM( mpImpl->mpTextEngine->mpIMEInfos->aPos );
+ aPaM.GetIndex() += nNewIMETextLen;
+ TextSelection aSel( aPaM );
+ aSel.GetEnd().GetIndex() += nOverwrite;
+ mpImpl->mpTextEngine->ImpDeleteText( aSel );
+ }
+ }
+
+ if ( pData->GetTextAttr() )
+ {
+ mpImpl->mpTextEngine->mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() );
+ }
+ else
+ {
+ mpImpl->mpTextEngine->mpIMEInfos->DestroyAttribs();
+ }
+
+ TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetPara() );
+ pPPortion->MarkSelectionInvalid( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex() );
+ mpImpl->mpTextEngine->FormatAndUpdate( this );
+ }
+
+ TextSelection aNewSel = TextPaM( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetPara(), mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex()+pData->GetCursorPos() );
+ SetSelection( aNewSel );
+ SetInsertMode( !pData->IsCursorOverwrite() );
+
+ if ( pData->IsCursorVisible() )
+ ShowCursor();
+ else
+ HideCursor();
+ }
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
+ {
+ if ( mpImpl->mpTextEngine->mpIMEInfos && mpImpl->mpTextEngine->mpIMEInfos->nLen )
+ {
+ TextPaM aPaM( GetSelection().GetEnd() );
+ tools::Rectangle aR1 = mpImpl->mpTextEngine->PaMtoEditCursor( aPaM );
+
+ sal_Int32 nInputEnd = mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex() + mpImpl->mpTextEngine->mpIMEInfos->nLen;
+
+ if ( !mpImpl->mpTextEngine->IsFormatted() )
+ mpImpl->mpTextEngine->FormatDoc();
+
+ TEParaPortion* pParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
+ std::vector<TextLine>::size_type nLine = pParaPortion->GetLineNumber( aPaM.GetIndex(), true );
+ TextLine& rLine = pParaPortion->GetLines()[ nLine ];
+ if ( nInputEnd > rLine.GetEnd() )
+ nInputEnd = rLine.GetEnd();
+ tools::Rectangle aR2 = mpImpl->mpTextEngine->PaMtoEditCursor( TextPaM( aPaM.GetPara(), nInputEnd ) );
+
+ tools::Long nWidth = aR2.Left()-aR1.Right();
+ aR1.Move( -GetStartDocPos().X(), -GetStartDocPos().Y() );
+ GetWindow()->SetCursorRect( &aR1, nWidth );
+ }
+ else
+ {
+ GetWindow()->SetCursorRect();
+ }
+ }
+ else
+ {
+ mpImpl->mpSelEngine->Command( rCEvt );
+ }
+}
+
+void TextView::ShowCursor( bool bGotoCursor, bool bForceVisCursor )
+{
+ // this setting has more weight
+ if ( !mpImpl->mbAutoScroll )
+ bGotoCursor = false;
+ ImpShowCursor( bGotoCursor, bForceVisCursor, false );
+}
+
+void TextView::HideCursor()
+{
+ mpImpl->mpCursor->Hide();
+}
+
+void TextView::Scroll( tools::Long ndX, tools::Long ndY )
+{
+ SAL_WARN_IF( !mpImpl->mpTextEngine->IsFormatted(), "vcl", "Scroll: Not formatted!" );
+
+ if ( !ndX && !ndY )
+ return;
+
+ Point aNewStartPos( mpImpl->maStartDocPos );
+
+ // Vertical:
+ aNewStartPos.AdjustY( -ndY );
+ if ( aNewStartPos.Y() < 0 )
+ aNewStartPos.setY( 0 );
+
+ // Horizontal:
+ aNewStartPos.AdjustX( -ndX );
+ if ( aNewStartPos.X() < 0 )
+ aNewStartPos.setX( 0 );
+
+ tools::Long nDiffX = mpImpl->maStartDocPos.X() - aNewStartPos.X();
+ tools::Long nDiffY = mpImpl->maStartDocPos.Y() - aNewStartPos.Y();
+
+ if ( nDiffX || nDiffY )
+ {
+ bool bVisCursor = mpImpl->mpCursor->IsVisible();
+ mpImpl->mpCursor->Hide();
+ mpImpl->mpWindow->PaintImmediately();
+ mpImpl->maStartDocPos = aNewStartPos;
+
+ if ( mpImpl->mpTextEngine->IsRightToLeft() )
+ nDiffX = -nDiffX;
+ mpImpl->mpWindow->Scroll( nDiffX, nDiffY );
+ mpImpl->mpWindow->PaintImmediately();
+ mpImpl->mpCursor->SetPos( mpImpl->mpCursor->GetPos() + Point( nDiffX, nDiffY ) );
+ if ( bVisCursor && !mpImpl->mbReadOnly )
+ mpImpl->mpCursor->Show();
+ }
+
+ mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextViewScrolled ) );
+}
+
+void TextView::Undo()
+{
+ mpImpl->mpTextEngine->SetActiveView( this );
+ mpImpl->mpTextEngine->GetUndoManager().Undo();
+}
+
+void TextView::Redo()
+{
+ mpImpl->mpTextEngine->SetActiveView( this );
+ mpImpl->mpTextEngine->GetUndoManager().Redo();
+}
+
+void TextView::Cut()
+{
+ mpImpl->mpTextEngine->UndoActionStart();
+ Copy();
+ DeleteSelected();
+ mpImpl->mpTextEngine->UndoActionEnd();
+}
+
+void TextView::Copy( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard )
+{
+ if ( !rxClipboard.is() )
+ return;
+
+ rtl::Reference<TETextDataObject> pDataObj = new TETextDataObject( GetSelected() );
+
+ SolarMutexReleaser aReleaser;
+
+ try
+ {
+ rxClipboard->setContents( pDataObj, nullptr );
+
+ css::uno::Reference< css::datatransfer::clipboard::XFlushableClipboard > xFlushableClipboard( rxClipboard, css::uno::UNO_QUERY );
+ if( xFlushableClipboard.is() )
+ xFlushableClipboard->flushClipboard();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+void TextView::Copy()
+{
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetWindow()->GetClipboard());
+ Copy( aClipboard );
+}
+
+void TextView::Paste( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard )
+{
+ if ( !rxClipboard.is() )
+ return;
+
+ css::uno::Reference< css::datatransfer::XTransferable > xDataObj;
+
+ try
+ {
+ SolarMutexReleaser aReleaser;
+ xDataObj = rxClipboard->getContents();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ if ( !xDataObj.is() )
+ return;
+
+ css::datatransfer::DataFlavor aFlavor;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
+ if ( !xDataObj->isDataFlavorSupported( aFlavor ) )
+ return;
+
+ try
+ {
+ css::uno::Any aData = xDataObj->getTransferData( aFlavor );
+ OUString aText;
+ aData >>= aText;
+ bool bWasTruncated = false;
+ if( mpImpl->mpTextEngine->GetMaxTextLen() != 0 )
+ bWasTruncated = ImplTruncateNewText( aText );
+ InsertText( aText );
+ mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+
+ if( bWasTruncated )
+ Edit::ShowTruncationWarning(mpImpl->mpWindow->GetFrameWeld());
+ }
+ catch( const css::datatransfer::UnsupportedFlavorException& )
+ {
+ }
+}
+
+void TextView::Paste()
+{
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetWindow()->GetClipboard());
+ Paste( aClipboard );
+}
+
+OUString TextView::GetSelected() const
+{
+ return GetSelected( GetSystemLineEnd() );
+}
+
+OUString TextView::GetSelected( LineEnd aSeparator ) const
+{
+ return mpImpl->mpTextEngine->GetText( mpImpl->maSelection, aSeparator );
+}
+
+void TextView::SetInsertMode( bool bInsert )
+{
+ if ( mpImpl->mbInsertMode != bInsert )
+ {
+ mpImpl->mbInsertMode = bInsert;
+ ShowCursor( mpImpl->mbAutoScroll, false );
+ }
+}
+
+void TextView::SetReadOnly( bool bReadOnly )
+{
+ if ( mpImpl->mbReadOnly != bReadOnly )
+ {
+ mpImpl->mbReadOnly = bReadOnly;
+ if ( !mpImpl->mbReadOnly )
+ ShowCursor( mpImpl->mbAutoScroll, false );
+ else
+ HideCursor();
+
+ GetWindow()->SetInputContext( InputContext( mpImpl->mpTextEngine->GetFont(), bReadOnly ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE ) );
+ }
+}
+
+TextSelection const & TextView::ImpMoveCursor( const KeyEvent& rKeyEvent )
+{
+ // normally only needed for Up/Down; but who cares
+ mpImpl->mpTextEngine->CheckIdleFormatter();
+
+ TextPaM aPaM( mpImpl->maSelection.GetEnd() );
+ TextPaM aOldEnd( aPaM );
+
+ TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom;
+ if ( mpImpl->mpTextEngine->IsRightToLeft() )
+ eTextDirection = TextDirectionality::RightToLeft_TopToBottom;
+
+ KeyEvent aTranslatedKeyEvent = rKeyEvent.LogicalTextDirectionality( eTextDirection );
+
+ bool bCtrl = aTranslatedKeyEvent.GetKeyCode().IsMod1();
+ sal_uInt16 nCode = aTranslatedKeyEvent.GetKeyCode().GetCode();
+
+ bool bSelect = aTranslatedKeyEvent.GetKeyCode().IsShift();
+ switch ( nCode )
+ {
+ case KEY_UP: aPaM = CursorUp( aPaM );
+ break;
+ case KEY_DOWN: aPaM = CursorDown( aPaM );
+ break;
+ case KEY_HOME:
+ if (bCtrl)
+ {
+ aPaM = CursorStartOfDoc();
+ }
+ else
+ {
+ // tdf#145764 - move cursor to the beginning or the first non-space character in the same line
+ const TextPaM aFirstWordPaM = CursorFirstWord(aPaM);
+ aPaM = aPaM.GetIndex() == aFirstWordPaM.GetIndex() ? CursorStartOfLine(aPaM) : aFirstWordPaM;
+ }
+ break;
+ case KEY_END: aPaM = bCtrl ? CursorEndOfDoc() : CursorEndOfLine( aPaM );
+ break;
+ case KEY_PAGEUP: aPaM = bCtrl ? CursorStartOfDoc() : PageUp( aPaM );
+ break;
+ case KEY_PAGEDOWN: aPaM = bCtrl ? CursorEndOfDoc() : PageDown( aPaM );
+ break;
+ case KEY_LEFT: aPaM = bCtrl ? CursorWordLeft( aPaM ) : CursorLeft( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) : sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
+ break;
+ case KEY_RIGHT: aPaM = bCtrl ? CursorWordRight( aPaM ) : CursorRight( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) : sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
+ break;
+ case css::awt::Key::SELECT_WORD_FORWARD:
+ bSelect = true;
+ [[fallthrough]];
+ case css::awt::Key::MOVE_WORD_FORWARD:
+ aPaM = CursorWordRight( aPaM );
+ break;
+ case css::awt::Key::SELECT_WORD_BACKWARD:
+ bSelect = true;
+ [[fallthrough]];
+ case css::awt::Key::MOVE_WORD_BACKWARD:
+ aPaM = CursorWordLeft( aPaM );
+ break;
+ case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
+ bSelect = true;
+ [[fallthrough]];
+ case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
+ aPaM = CursorStartOfLine( aPaM );
+ break;
+ case css::awt::Key::SELECT_TO_END_OF_LINE:
+ bSelect = true;
+ [[fallthrough]];
+ case css::awt::Key::MOVE_TO_END_OF_LINE:
+ aPaM = CursorEndOfLine( aPaM );
+ break;
+ case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
+ bSelect = true;
+ [[fallthrough]];
+ case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
+ aPaM = CursorStartOfParagraph( aPaM );
+ break;
+ case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
+ bSelect = true;
+ [[fallthrough]];
+ case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
+ aPaM = CursorEndOfParagraph( aPaM );
+ break;
+ case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
+ bSelect = true;
+ [[fallthrough]];
+ case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
+ aPaM = CursorStartOfDoc();
+ break;
+ case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
+ bSelect = true;
+ [[fallthrough]];
+ case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
+ aPaM = CursorEndOfDoc();
+ break;
+ }
+
+ // might cause a CreateAnchor or Deselection all
+ mpImpl->mpSelEngine->CursorPosChanging( bSelect, aTranslatedKeyEvent.GetKeyCode().IsMod1() );
+
+ if ( aOldEnd != aPaM )
+ {
+ mpImpl->mpTextEngine->CursorMoved( aOldEnd.GetPara() );
+
+ TextSelection aNewSelection( mpImpl->maSelection );
+ aNewSelection.GetEnd() = aPaM;
+ if ( bSelect )
+ {
+ // extend the selection
+ ImpSetSelection( aNewSelection );
+ ShowSelection( TextSelection( aOldEnd, aPaM ) );
+ }
+ else
+ {
+ aNewSelection.GetStart() = aPaM;
+ ImpSetSelection( aNewSelection );
+ }
+ }
+
+ return mpImpl->maSelection;
+}
+
+void TextView::InsertText( const OUString& rStr )
+{
+ mpImpl->mpTextEngine->UndoActionStart();
+
+ TextSelection aNewSel = mpImpl->mpTextEngine->ImpInsertText( mpImpl->maSelection, rStr );
+
+ ImpSetSelection( aNewSel );
+
+ mpImpl->mpTextEngine->UndoActionEnd();
+
+ mpImpl->mpTextEngine->FormatAndUpdate( this );
+}
+
+TextPaM TextView::CursorLeft( const TextPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
+{
+ TextPaM aPaM( rPaM );
+
+ if ( aPaM.GetIndex() )
+ {
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+ css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
+ sal_Int32 nCount = 1;
+ aPaM.GetIndex() = xBI->previousCharacters( pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), nCharacterIteratorMode, nCount, nCount );
+ }
+ else if ( aPaM.GetPara() )
+ {
+ aPaM.GetPara()--;
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+ aPaM.GetIndex() = pNode->GetText().getLength();
+ }
+ return aPaM;
+}
+
+TextPaM TextView::CursorRight( const TextPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
+{
+ TextPaM aPaM( rPaM );
+
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+ if ( aPaM.GetIndex() < pNode->GetText().getLength() )
+ {
+ css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
+ sal_Int32 nCount = 1;
+ aPaM.GetIndex() = xBI->nextCharacters( pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), nCharacterIteratorMode, nCount, nCount );
+ }
+ else if ( aPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size()-1) )
+ {
+ aPaM.GetPara()++;
+ aPaM.GetIndex() = 0;
+ }
+
+ return aPaM;
+}
+
+TextPaM TextView::CursorFirstWord( const TextPaM& rPaM )
+{
+ TextPaM aPaM(rPaM);
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();
+
+ css::uno::Reference<css::i18n::XBreakIterator> xBI = mpImpl->mpTextEngine->GetBreakIterator();
+ aPaM.GetIndex() = xBI->beginOfSentence(pNode->GetText(), 0, mpImpl->mpTextEngine->GetLocale());
+
+ return aPaM;
+}
+
+TextPaM TextView::CursorWordLeft( const TextPaM& rPaM )
+{
+ TextPaM aPaM( rPaM );
+
+ if ( aPaM.GetIndex() )
+ {
+ // tdf#57879 - expand selection to the left to include connector punctuations
+ mpImpl->mpTextEngine->GetWord( rPaM, &aPaM );
+ if ( aPaM.GetIndex() >= rPaM.GetIndex() )
+ {
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+ css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
+ aPaM.GetIndex() = xBI->previousWord( pNode->GetText(), rPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES ).startPos;
+ if ( aPaM.GetIndex() > 0 )
+ mpImpl->mpTextEngine->GetWord( aPaM, &aPaM );
+ else
+ aPaM.GetIndex() = 0;
+ }
+ }
+ else if ( aPaM.GetPara() )
+ {
+ aPaM.GetPara()--;
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+ aPaM.GetIndex() = pNode->GetText().getLength();
+ }
+ return aPaM;
+}
+
+TextPaM TextView::CursorWordRight( const TextPaM& rPaM )
+{
+ TextPaM aPaM( rPaM );
+
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+ if ( aPaM.GetIndex() < pNode->GetText().getLength() )
+ {
+ css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
+ aPaM.GetIndex() = xBI->nextWord( pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES ).endPos;
+ mpImpl->mpTextEngine->GetWord( aPaM, nullptr, &aPaM );
+ }
+ else if ( aPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size()-1) )
+ {
+ aPaM.GetPara()++;
+ aPaM.GetIndex() = 0;
+ }
+
+ return aPaM;
+}
+
+TextPaM TextView::ImpDelete( sal_uInt8 nMode, sal_uInt8 nDelMode )
+{
+ if ( mpImpl->maSelection.HasRange() ) // only delete selection
+ return mpImpl->mpTextEngine->ImpDeleteText( mpImpl->maSelection );
+
+ TextPaM aStartPaM = mpImpl->maSelection.GetStart();
+ TextPaM aEndPaM = aStartPaM;
+ if ( nMode == DEL_LEFT )
+ {
+ if ( nDelMode == DELMODE_SIMPLE )
+ {
+ aEndPaM = CursorLeft( aEndPaM, sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) );
+ }
+ else if ( nDelMode == DELMODE_RESTOFWORD )
+ {
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
+ css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
+ css::i18n::Boundary aBoundary = xBI->getWordBoundary( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
+ if ( aBoundary.startPos == mpImpl->maSelection.GetEnd().GetIndex() )
+ aBoundary = xBI->previousWord( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES );
+ // #i63506# startPos is -1 when the paragraph starts with a tab
+ aEndPaM.GetIndex() = std::max<sal_Int32>(aBoundary.startPos, 0);
+ }
+ else // DELMODE_RESTOFCONTENT
+ {
+ if ( aEndPaM.GetIndex() != 0 )
+ aEndPaM.GetIndex() = 0;
+ else if ( aEndPaM.GetPara() )
+ {
+ // previous paragraph
+ aEndPaM.GetPara()--;
+ aEndPaM.GetIndex() = 0;
+ }
+ }
+ }
+ else
+ {
+ if ( nDelMode == DELMODE_SIMPLE )
+ {
+ aEndPaM = CursorRight( aEndPaM, sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
+ }
+ else if ( nDelMode == DELMODE_RESTOFWORD )
+ {
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
+ css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
+ css::i18n::Boundary aBoundary = xBI->nextWord( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES );
+ aEndPaM.GetIndex() = aBoundary.startPos;
+ }
+ else // DELMODE_RESTOFCONTENT
+ {
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
+ if ( aEndPaM.GetIndex() < pNode->GetText().getLength() )
+ aEndPaM.GetIndex() = pNode->GetText().getLength();
+ else if ( aEndPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size() - 1 ) )
+ {
+ // next paragraph
+ aEndPaM.GetPara()++;
+ TextNode* pNextNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
+ aEndPaM.GetIndex() = pNextNode->GetText().getLength();
+ }
+ }
+ }
+
+ return mpImpl->mpTextEngine->ImpDeleteText( TextSelection( aStartPaM, aEndPaM ) );
+}
+
+TextPaM TextView::CursorUp( const TextPaM& rPaM )
+{
+ TextPaM aPaM( rPaM );
+
+ tools::Long nX;
+ if ( mpImpl->mnTravelXPos == TRAVEL_X_DONTKNOW )
+ {
+ nX = mpImpl->mpTextEngine->GetEditCursor( rPaM, false ).Left();
+ mpImpl->mnTravelXPos = static_cast<sal_uInt16>(nX)+1;
+ }
+ else
+ nX = mpImpl->mnTravelXPos;
+
+ TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
+ std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false );
+ if ( nLine ) // same paragraph
+ {
+ aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( rPaM.GetPara(), nLine-1, nX );
+ // If we need to go to the end of a line that was wrapped automatically,
+ // the cursor ends up at the beginning of the 2nd line
+ // Problem: Last character of an automatically wrapped line = Cursor
+ TextLine& rLine = pPPortion->GetLines()[ nLine - 1 ];
+ if ( aPaM.GetIndex() && ( aPaM.GetIndex() == rLine.GetEnd() ) )
+ --aPaM.GetIndex();
+ }
+ else if ( rPaM.GetPara() ) // previous paragraph
+ {
+ aPaM.GetPara()--;
+ pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
+ std::vector<TextLine>::size_type nL = pPPortion->GetLines().size() - 1;
+ aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( aPaM.GetPara(), nL, nX+1 );
+ }
+
+ return aPaM;
+}
+
+TextPaM TextView::CursorDown( const TextPaM& rPaM )
+{
+ TextPaM aPaM( rPaM );
+
+ tools::Long nX;
+ if ( mpImpl->mnTravelXPos == TRAVEL_X_DONTKNOW )
+ {
+ nX = mpImpl->mpTextEngine->GetEditCursor( rPaM, false ).Left();
+ mpImpl->mnTravelXPos = static_cast<sal_uInt16>(nX)+1;
+ }
+ else
+ nX = mpImpl->mnTravelXPos;
+
+ TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
+ std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false );
+ if ( nLine < ( pPPortion->GetLines().size() - 1 ) )
+ {
+ aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( rPaM.GetPara(), nLine+1, nX );
+
+ // special case CursorUp
+ TextLine& rLine = pPPortion->GetLines()[ nLine + 1 ];
+ if ( ( aPaM.GetIndex() == rLine.GetEnd() ) && ( aPaM.GetIndex() > rLine.GetStart() ) && aPaM.GetIndex() < pPPortion->GetNode()->GetText().getLength() )
+ --aPaM.GetIndex();
+ }
+ else if ( rPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size() - 1 ) ) // next paragraph
+ {
+ aPaM.GetPara()++;
+ pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
+ aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( aPaM.GetPara(), 0, nX+1 );
+ TextLine& rLine = pPPortion->GetLines().front();
+ if ( ( aPaM.GetIndex() == rLine.GetEnd() ) && ( aPaM.GetIndex() > rLine.GetStart() ) && ( pPPortion->GetLines().size() > 1 ) )
+ --aPaM.GetIndex();
+ }
+
+ return aPaM;
+}
+
+TextPaM TextView::CursorStartOfLine( const TextPaM& rPaM )
+{
+ TextPaM aPaM( rPaM );
+
+ TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
+ std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
+ TextLine& rLine = pPPortion->GetLines()[ nLine ];
+ aPaM.GetIndex() = rLine.GetStart();
+
+ return aPaM;
+}
+
+TextPaM TextView::CursorEndOfLine( const TextPaM& rPaM )
+{
+ TextPaM aPaM( rPaM );
+
+ TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
+ std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
+ TextLine& rLine = pPPortion->GetLines()[ nLine ];
+ aPaM.GetIndex() = rLine.GetEnd();
+
+ if ( rLine.GetEnd() > rLine.GetStart() ) // empty line
+ {
+ sal_Unicode cLastChar = pPPortion->GetNode()->GetText()[ aPaM.GetIndex()-1 ];
+ if ( ( cLastChar == ' ' ) && ( aPaM.GetIndex() != pPPortion->GetNode()->GetText().getLength() ) )
+ {
+ // for a blank in an automatically-wrapped line it is better to stand before it,
+ // as the user will intend to stand behind the prior word.
+ // If there is a change, special case for Pos1 after End!
+ --aPaM.GetIndex();
+ }
+ }
+ return aPaM;
+}
+
+TextPaM TextView::CursorStartOfParagraph( const TextPaM& rPaM )
+{
+ TextPaM aPaM( rPaM );
+ aPaM.GetIndex() = 0;
+ return aPaM;
+}
+
+TextPaM TextView::CursorEndOfParagraph( const TextPaM& rPaM )
+{
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ rPaM.GetPara() ].get();
+ TextPaM aPaM( rPaM );
+ aPaM.GetIndex() = pNode->GetText().getLength();
+ return aPaM;
+}
+
+TextPaM TextView::CursorStartOfDoc()
+{
+ TextPaM aPaM( 0, 0 );
+ return aPaM;
+}
+
+TextPaM TextView::CursorEndOfDoc()
+{
+ const sal_uInt32 nNode = static_cast<sal_uInt32>(mpImpl->mpTextEngine->mpDoc->GetNodes().size() - 1);
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ nNode ].get();
+ TextPaM aPaM( nNode, pNode->GetText().getLength() );
+ return aPaM;
+}
+
+TextPaM TextView::PageUp( const TextPaM& rPaM )
+{
+ tools::Rectangle aRect = mpImpl->mpTextEngine->PaMtoEditCursor( rPaM );
+ Point aTopLeft = aRect.TopLeft();
+ aTopLeft.AdjustY( -(mpImpl->mpWindow->GetOutputSizePixel().Height() * 9/10) );
+ aTopLeft.AdjustX(1 );
+ if ( aTopLeft.Y() < 0 )
+ aTopLeft.setY( 0 );
+
+ TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aTopLeft );
+ return aPaM;
+}
+
+TextPaM TextView::PageDown( const TextPaM& rPaM )
+{
+ tools::Rectangle aRect = mpImpl->mpTextEngine->PaMtoEditCursor( rPaM );
+ Point aBottomRight = aRect.BottomRight();
+ aBottomRight.AdjustY(mpImpl->mpWindow->GetOutputSizePixel().Height() * 9/10 );
+ aBottomRight.AdjustX(1 );
+ tools::Long nHeight = mpImpl->mpTextEngine->GetTextHeight();
+ if ( aBottomRight.Y() > nHeight )
+ aBottomRight.setY( nHeight-1 );
+
+ TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aBottomRight );
+ return aPaM;
+}
+
+void TextView::ImpShowCursor( bool bGotoCursor, bool bForceVisCursor, bool bSpecial )
+{
+ if ( mpImpl->mpTextEngine->IsFormatting() )
+ return;
+ if ( !mpImpl->mpTextEngine->GetUpdateMode() )
+ return;
+ if ( mpImpl->mpTextEngine->IsInUndo() )
+ return;
+
+ mpImpl->mpTextEngine->CheckIdleFormatter();
+ if ( !mpImpl->mpTextEngine->IsFormatted() )
+ mpImpl->mpTextEngine->FormatAndUpdate( this );
+
+ TextPaM aPaM( mpImpl->maSelection.GetEnd() );
+ tools::Rectangle aEditCursor = mpImpl->mpTextEngine->PaMtoEditCursor( aPaM, bSpecial );
+
+ // Remember that we placed the cursor behind the last character of a line
+ mpImpl->mbCursorAtEndOfLine = false;
+ if( bSpecial )
+ {
+ TEParaPortion* pParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
+ mpImpl->mbCursorAtEndOfLine =
+ pParaPortion->GetLineNumber( aPaM.GetIndex(), true ) != pParaPortion->GetLineNumber( aPaM.GetIndex(), false );
+ }
+
+ if ( !IsInsertMode() && !mpImpl->maSelection.HasRange() )
+ {
+ TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
+ if ( !pNode->GetText().isEmpty() && ( aPaM.GetIndex() < pNode->GetText().getLength() ) )
+ {
+ // If we are behind a portion, and the next portion has other direction, we must change position...
+ aEditCursor.SetLeft( mpImpl->mpTextEngine->GetEditCursor( aPaM, false, true ).Left() );
+ aEditCursor.SetRight( aEditCursor.Left() );
+
+ TEParaPortion* pParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
+
+ sal_Int32 nTextPortionStart = 0;
+ std::size_t nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, true );
+ TETextPortion& rTextPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
+ if ( rTextPortion.GetKind() == PORTIONKIND_TAB )
+ {
+ aEditCursor.AdjustRight(rTextPortion.GetWidth() );
+ }
+ else
+ {
+ TextPaM aNext = CursorRight( TextPaM( aPaM.GetPara(), aPaM.GetIndex() ), sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
+ aEditCursor.SetRight( mpImpl->mpTextEngine->GetEditCursor( aNext, true ).Left() );
+ }
+ }
+ }
+
+ Size aOutSz = mpImpl->mpWindow->GetOutputSizePixel();
+ if ( aEditCursor.GetHeight() > aOutSz.Height() )
+ aEditCursor.SetBottom( aEditCursor.Top() + aOutSz.Height() - 1 );
+
+ aEditCursor.AdjustLeft( -1 );
+
+ if ( bGotoCursor
+ // #i81283# protect maStartDocPos against initialization problems
+ && aOutSz.Width() && aOutSz.Height()
+ )
+ {
+ tools::Long nVisStartY = mpImpl->maStartDocPos.Y();
+ tools::Long nVisEndY = mpImpl->maStartDocPos.Y() + aOutSz.Height();
+ tools::Long nVisStartX = mpImpl->maStartDocPos.X();
+ tools::Long nVisEndX = mpImpl->maStartDocPos.X() + aOutSz.Width();
+ tools::Long nMoreX = aOutSz.Width() / 4;
+
+ Point aNewStartPos( mpImpl->maStartDocPos );
+
+ if ( aEditCursor.Bottom() > nVisEndY )
+ {
+ aNewStartPos.AdjustY( aEditCursor.Bottom() - nVisEndY);
+ }
+ else if ( aEditCursor.Top() < nVisStartY )
+ {
+ aNewStartPos.AdjustY( -( nVisStartY - aEditCursor.Top() ) );
+ }
+
+ if ( aEditCursor.Right() >= nVisEndX )
+ {
+ aNewStartPos.AdjustX( aEditCursor.Right() - nVisEndX );
+
+ // do you want some more?
+ aNewStartPos.AdjustX(nMoreX );
+ }
+ else if ( aEditCursor.Left() <= nVisStartX )
+ {
+ aNewStartPos.AdjustX( -( nVisStartX - aEditCursor.Left() ) );
+
+ // do you want some more?
+ aNewStartPos.AdjustX( -nMoreX );
+ }
+
+ // X can be wrong for the 'some more' above:
+// sal_uLong nMaxTextWidth = mpImpl->mpTextEngine->GetMaxTextWidth();
+// if ( !nMaxTextWidth || ( nMaxTextWidth > 0x7FFFFFFF ) )
+// nMaxTextWidth = 0x7FFFFFFF;
+// long nMaxX = (long)nMaxTextWidth - aOutSz.Width();
+ tools::Long nMaxX = mpImpl->mpTextEngine->CalcTextWidth() - aOutSz.Width();
+ if ( nMaxX < 0 )
+ nMaxX = 0;
+
+ if ( aNewStartPos.X() < 0 )
+ aNewStartPos.setX( 0 );
+ else if ( aNewStartPos.X() > nMaxX )
+ aNewStartPos.setX( nMaxX );
+
+ // Y should not be further down than needed
+ tools::Long nYMax = mpImpl->mpTextEngine->GetTextHeight() - aOutSz.Height();
+ if ( nYMax < 0 )
+ nYMax = 0;
+ if ( aNewStartPos.Y() > nYMax )
+ aNewStartPos.setY( nYMax );
+
+ if ( aNewStartPos != mpImpl->maStartDocPos )
+ Scroll( -(aNewStartPos.X() - mpImpl->maStartDocPos.X()), -(aNewStartPos.Y() - mpImpl->maStartDocPos.Y()) );
+ }
+
+ if ( aEditCursor.Right() < aEditCursor.Left() )
+ {
+ tools::Long n = aEditCursor.Left();
+ aEditCursor.SetLeft( aEditCursor.Right() );
+ aEditCursor.SetRight( n );
+ }
+
+ Point aPoint( GetWindowPos( !mpImpl->mpTextEngine->IsRightToLeft() ? aEditCursor.TopLeft() : aEditCursor.TopRight() ) );
+ mpImpl->mpCursor->SetPos( aPoint );
+ mpImpl->mpCursor->SetSize( aEditCursor.GetSize() );
+ if ( bForceVisCursor && mpImpl->mbCursorEnabled )
+ mpImpl->mpCursor->Show();
+}
+
+void TextView::SetCursorAtPoint( const Point& rPosPixel )
+{
+ mpImpl->mpTextEngine->CheckIdleFormatter();
+
+ Point aDocPos = GetDocPos( rPosPixel );
+
+ TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aDocPos );
+
+ // aTmpNewSel: Diff between old and new; not the new selection
+ TextSelection aTmpNewSel( mpImpl->maSelection.GetEnd(), aPaM );
+ TextSelection aNewSel( mpImpl->maSelection );
+ aNewSel.GetEnd() = aPaM;
+
+ if ( !mpImpl->mpSelEngine->HasAnchor() )
+ {
+ if ( mpImpl->maSelection.GetStart() != aPaM )
+ mpImpl->mpTextEngine->CursorMoved( mpImpl->maSelection.GetStart().GetPara() );
+ aNewSel.GetStart() = aPaM;
+ ImpSetSelection( aNewSel );
+ }
+ else
+ {
+ ImpSetSelection( aNewSel );
+ ShowSelection( aTmpNewSel );
+ }
+
+ bool bForceCursor = !mpImpl->mpDDInfo; // && !mbInSelection
+ ImpShowCursor( mpImpl->mbAutoScroll, bForceCursor, false );
+}
+
+bool TextView::IsSelectionAtPoint( const Point& rPosPixel )
+{
+ Point aDocPos = GetDocPos( rPosPixel );
+ TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aDocPos );
+ // BeginDrag is only called, however, if IsSelectionAtPoint()
+ // Problem: IsSelectionAtPoint is not called by Command()
+ // if before MBDown returned false.
+ return IsInSelection( aPaM );
+}
+
+bool TextView::IsInSelection( const TextPaM& rPaM ) const
+{
+ TextSelection aSel = mpImpl->maSelection;
+ aSel.Justify();
+
+ const sal_uInt32 nStartNode = aSel.GetStart().GetPara();
+ const sal_uInt32 nEndNode = aSel.GetEnd().GetPara();
+ const sal_uInt32 nCurNode = rPaM.GetPara();
+
+ if ( ( nCurNode > nStartNode ) && ( nCurNode < nEndNode ) )
+ return true;
+
+ if ( nStartNode == nEndNode )
+ {
+ if ( nCurNode == nStartNode )
+ if ( ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) )
+ return true;
+ }
+ else if ( ( nCurNode == nStartNode ) && ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) )
+ return true;
+ else if ( ( nCurNode == nEndNode ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) )
+ return true;
+
+ return false;
+}
+
+void TextView::ImpHideDDCursor()
+{
+ if ( mpImpl->mpDDInfo && mpImpl->mpDDInfo->mbVisCursor )
+ {
+ mpImpl->mpDDInfo->maCursor.Hide();
+ mpImpl->mpDDInfo->mbVisCursor = false;
+ }
+}
+
+void TextView::ImpShowDDCursor()
+{
+ if ( !mpImpl->mpDDInfo->mbVisCursor )
+ {
+ tools::Rectangle aCursor = mpImpl->mpTextEngine->PaMtoEditCursor( mpImpl->mpDDInfo->maDropPos, true );
+ aCursor.AdjustRight( 1 );
+ aCursor.SetPos( GetWindowPos( aCursor.TopLeft() ) );
+
+ mpImpl->mpDDInfo->maCursor.SetWindow( mpImpl->mpWindow );
+ mpImpl->mpDDInfo->maCursor.SetPos( aCursor.TopLeft() );
+ mpImpl->mpDDInfo->maCursor.SetSize( aCursor.GetSize() );
+ mpImpl->mpDDInfo->maCursor.Show();
+ mpImpl->mpDDInfo->mbVisCursor = true;
+ }
+}
+
+void TextView::SetPaintSelection( bool bPaint )
+{
+ if ( bPaint != mpImpl->mbPaintSelection )
+ {
+ mpImpl->mbPaintSelection = bPaint;
+ ShowSelection( mpImpl->maSelection );
+ }
+}
+
+void TextView::Read( SvStream& rInput )
+{
+ mpImpl->mpTextEngine->Read( rInput, &mpImpl->maSelection );
+ ShowCursor();
+}
+
+bool TextView::ImplTruncateNewText( OUString& rNewText ) const
+{
+ bool bTruncated = false;
+
+ const sal_Int32 nMaxLen = mpImpl->mpTextEngine->GetMaxTextLen();
+ // 0 means unlimited
+ if( nMaxLen != 0 )
+ {
+ const sal_Int32 nCurLen = mpImpl->mpTextEngine->GetTextLen();
+
+ const sal_Int32 nNewLen = rNewText.getLength();
+ if ( nCurLen + nNewLen > nMaxLen )
+ {
+ // see how much text will be replaced
+ const sal_Int32 nSelLen = mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection );
+ if ( nCurLen + nNewLen - nSelLen > nMaxLen )
+ {
+ const sal_Int32 nTruncatedLen = nMaxLen - (nCurLen - nSelLen);
+ rNewText = rNewText.copy( 0, nTruncatedLen );
+ bTruncated = true;
+ }
+ }
+ }
+ return bTruncated;
+}
+
+bool TextView::ImplCheckTextLen( const OUString& rNewText ) const
+{
+ bool bOK = true;
+ if ( mpImpl->mpTextEngine->GetMaxTextLen() )
+ {
+ sal_Int32 n = mpImpl->mpTextEngine->GetTextLen() + rNewText.getLength();
+ if ( n > mpImpl->mpTextEngine->GetMaxTextLen() )
+ {
+ // calculate how much text is being deleted
+ n -= mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection );
+ if ( n > mpImpl->mpTextEngine->GetMaxTextLen() )
+ bOK = false;
+ }
+ }
+ return bOK;
+}
+
+void TextView::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& rDGE )
+{
+ if ( !mpImpl->mbClickedInSelection )
+ return;
+
+ SolarMutexGuard aVclGuard;
+
+ SAL_WARN_IF( !mpImpl->maSelection.HasRange(), "vcl", "TextView::dragGestureRecognized: mpImpl->mbClickedInSelection, but no selection?" );
+
+ mpImpl->mpDDInfo.reset(new TextDDInfo);
+ mpImpl->mpDDInfo->mbStarterOfDD = true;
+
+ rtl::Reference<TETextDataObject> pDataObj = new TETextDataObject( GetSelected() );
+
+ mpImpl->mpCursor->Hide();
+
+ sal_Int8 nActions = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+ if ( !IsReadOnly() )
+ nActions |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ rDGE.DragSource->startDrag( rDGE, nActions, 0 /*cursor*/, 0 /*image*/, pDataObj, mpImpl->mxDnDListener );
+}
+
+void TextView::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& )
+{
+ ImpHideDDCursor();
+ mpImpl->mpDDInfo.reset();
+}
+
+void TextView::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE )
+{
+ SolarMutexGuard aVclGuard;
+
+ if ( !mpImpl->mbReadOnly && mpImpl->mpDDInfo )
+ {
+ ImpHideDDCursor();
+
+ // Data for deleting after DROP_MOVE:
+ TextSelection aPrevSel( mpImpl->maSelection );
+ aPrevSel.Justify();
+ const sal_uInt32 nPrevParaCount = mpImpl->mpTextEngine->GetParagraphCount();
+ const sal_Int32 nPrevStartParaLen = mpImpl->mpTextEngine->GetTextLen( aPrevSel.GetStart().GetPara() );
+
+ bool bStarterOfDD = false;
+ for ( sal_uInt16 nView = mpImpl->mpTextEngine->GetViewCount(); nView && !bStarterOfDD; )
+ bStarterOfDD = mpImpl->mpTextEngine->GetView( --nView )->mpImpl->mpDDInfo && mpImpl->mpTextEngine->GetView( nView )->mpImpl->mpDDInfo->mbStarterOfDD;
+
+ HideSelection();
+ ImpSetSelection( mpImpl->mpDDInfo->maDropPos );
+
+ mpImpl->mpTextEngine->UndoActionStart();
+
+ OUString aText;
+ css::uno::Reference< css::datatransfer::XTransferable > xDataObj = rDTDE.Transferable;
+ if ( xDataObj.is() )
+ {
+ css::datatransfer::DataFlavor aFlavor;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
+ if ( xDataObj->isDataFlavorSupported( aFlavor ) )
+ {
+ css::uno::Any aData = xDataObj->getTransferData( aFlavor );
+ OUString aOUString;
+ aData >>= aOUString;
+ aText = convertLineEnd(aOUString, LINEEND_LF);
+ }
+ }
+
+ if ( !aText.isEmpty() && ( aText[ aText.getLength()-1 ] == LINE_SEP ) )
+ aText = aText.copy(0, aText.getLength()-1);
+
+ if ( ImplCheckTextLen( aText ) )
+ ImpSetSelection( mpImpl->mpTextEngine->ImpInsertText( mpImpl->mpDDInfo->maDropPos, aText ) );
+
+ if ( aPrevSel.HasRange() &&
+ (( rDTDE.DropAction & css::datatransfer::dnd::DNDConstants::ACTION_MOVE ) || !bStarterOfDD) )
+ {
+ // adjust selection if necessary
+ if ( ( mpImpl->mpDDInfo->maDropPos.GetPara() < aPrevSel.GetStart().GetPara() ) ||
+ ( ( mpImpl->mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara() )
+ && ( mpImpl->mpDDInfo->maDropPos.GetIndex() < aPrevSel.GetStart().GetIndex() ) ) )
+ {
+ const sal_uInt32 nNewParasBeforeSelection =
+ mpImpl->mpTextEngine->GetParagraphCount() - nPrevParaCount;
+
+ aPrevSel.GetStart().GetPara() += nNewParasBeforeSelection;
+ aPrevSel.GetEnd().GetPara() += nNewParasBeforeSelection;
+
+ if ( mpImpl->mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara() )
+ {
+ const sal_Int32 nNewChars =
+ mpImpl->mpTextEngine->GetTextLen( aPrevSel.GetStart().GetPara() ) - nPrevStartParaLen;
+
+ aPrevSel.GetStart().GetIndex() += nNewChars;
+ if ( aPrevSel.GetStart().GetPara() == aPrevSel.GetEnd().GetPara() )
+ aPrevSel.GetEnd().GetIndex() += nNewChars;
+ }
+ }
+ else
+ {
+ // adjust current selection
+ TextPaM aPaM = mpImpl->maSelection.GetStart();
+ aPaM.GetPara() -= ( aPrevSel.GetEnd().GetPara() - aPrevSel.GetStart().GetPara() );
+ if ( aPrevSel.GetEnd().GetPara() == mpImpl->mpDDInfo->maDropPos.GetPara() )
+ {
+ aPaM.GetIndex() -= aPrevSel.GetEnd().GetIndex();
+ if ( aPrevSel.GetStart().GetPara() == mpImpl->mpDDInfo->maDropPos.GetPara() )
+ aPaM.GetIndex() += aPrevSel.GetStart().GetIndex();
+ }
+ ImpSetSelection( aPaM );
+
+ }
+ mpImpl->mpTextEngine->ImpDeleteText( aPrevSel );
+ }
+
+ mpImpl->mpTextEngine->UndoActionEnd();
+
+ mpImpl->mpDDInfo.reset();
+
+ mpImpl->mpTextEngine->FormatAndUpdate( this );
+
+ mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ rDTDE.Context->dropComplete( false/*bChanges*/ );
+}
+
+void TextView::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& )
+{
+}
+
+void TextView::dragExit( const css::datatransfer::dnd::DropTargetEvent& )
+{
+ SolarMutexGuard aVclGuard;
+ ImpHideDDCursor();
+}
+
+void TextView::dragOver( const css::datatransfer::dnd::DropTargetDragEvent& rDTDE )
+{
+ SolarMutexGuard aVclGuard;
+
+ if (!mpImpl->mpDDInfo)
+ mpImpl->mpDDInfo.reset(new TextDDInfo);
+
+ TextPaM aPrevDropPos = mpImpl->mpDDInfo->maDropPos;
+ Point aMousePos( rDTDE.LocationX, rDTDE.LocationY );
+ Point aDocPos = GetDocPos( aMousePos );
+ mpImpl->mpDDInfo->maDropPos = mpImpl->mpTextEngine->GetPaM( aDocPos );
+
+ // Don't drop in selection or in read only engine
+ if ( IsReadOnly() || IsInSelection( mpImpl->mpDDInfo->maDropPos ))
+ {
+ ImpHideDDCursor();
+ rDTDE.Context->rejectDrag();
+ }
+ else
+ {
+ // delete old Cursor
+ if ( !mpImpl->mpDDInfo->mbVisCursor || ( aPrevDropPos != mpImpl->mpDDInfo->maDropPos ) )
+ {
+ ImpHideDDCursor();
+ ImpShowDDCursor();
+ }
+ rDTDE.Context->acceptDrag( rDTDE.DropAction );
+ }
+}
+
+Point TextView::ImpGetOutputStartPos( const Point& rStartDocPos ) const
+{
+ Point aStartPos( -rStartDocPos.X(), -rStartDocPos.Y() );
+ if ( mpImpl->mpTextEngine->IsRightToLeft() )
+ {
+ Size aSz = mpImpl->mpWindow->GetOutputSizePixel();
+ aStartPos.setX( rStartDocPos.X() + aSz.Width() - 1 ); // -1: Start is 0
+ }
+ return aStartPos;
+}
+
+Point TextView::GetDocPos( const Point& rWindowPos ) const
+{
+ // Window Position => Document Position
+
+ Point aPoint;
+
+ aPoint.setY( rWindowPos.Y() + mpImpl->maStartDocPos.Y() );
+
+ if ( !mpImpl->mpTextEngine->IsRightToLeft() )
+ {
+ aPoint.setX( rWindowPos.X() + mpImpl->maStartDocPos.X() );
+ }
+ else
+ {
+ Size aSz = mpImpl->mpWindow->GetOutputSizePixel();
+ aPoint.setX( ( aSz.Width() - 1 ) - rWindowPos.X() + mpImpl->maStartDocPos.X() );
+ }
+
+ return aPoint;
+}
+
+Point TextView::GetWindowPos( const Point& rDocPos ) const
+{
+ // Document Position => Window Position
+
+ Point aPoint;
+
+ aPoint.setY( rDocPos.Y() - mpImpl->maStartDocPos.Y() );
+
+ if ( !mpImpl->mpTextEngine->IsRightToLeft() )
+ {
+ aPoint.setX( rDocPos.X() - mpImpl->maStartDocPos.X() );
+ }
+ else
+ {
+ Size aSz = mpImpl->mpWindow->GetOutputSizePixel();
+ aPoint.setX( ( aSz.Width() - 1 ) - ( rDocPos.X() - mpImpl->maStartDocPos.X() ) );
+ }
+
+ return aPoint;
+}
+
+sal_Int32 TextView::GetLineNumberOfCursorInSelection() const
+{
+ // PROGRESS
+ sal_Int32 nLineNo = -1;
+ if( mpImpl->mbCursorEnabled )
+ {
+ TextPaM aPaM = GetSelection().GetEnd();
+ TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
+ nLineNo = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
+ //TODO: std::vector<TextLine>::size_type -> sal_Int32!
+ if( mpImpl->mbCursorAtEndOfLine )
+ --nLineNo;
+ }
+ return nLineNo;
+}
+
+// (+) class TextSelFunctionSet
+
+TextSelFunctionSet::TextSelFunctionSet( TextView* pView )
+{
+ mpView = pView;
+}
+
+void TextSelFunctionSet::BeginDrag()
+{
+}
+
+void TextSelFunctionSet::CreateAnchor()
+{
+// TextSelection aSel( mpView->GetSelection() );
+// aSel.GetStart() = aSel.GetEnd();
+// mpView->SetSelection( aSel );
+
+ // may not be followed by ShowCursor
+ mpView->HideSelection();
+ mpView->ImpSetSelection( mpView->mpImpl->maSelection.GetEnd() );
+}
+
+void TextSelFunctionSet::SetCursorAtPoint( const Point& rPointPixel, bool )
+{
+ mpView->SetCursorAtPoint( rPointPixel );
+}
+
+bool TextSelFunctionSet::IsSelectionAtPoint( const Point& rPointPixel )
+{
+ return mpView->IsSelectionAtPoint( rPointPixel );
+}
+
+void TextSelFunctionSet::DeselectAll()
+{
+ CreateAnchor();
+}
+
+void TextSelFunctionSet::DeselectAtPoint( const Point& )
+{
+ // only for multiple selection
+}
+
+void TextSelFunctionSet::DestroyAnchor()
+{
+ // only for multiple selection
+}
+TextEngine* TextView::GetTextEngine() const
+{ return mpImpl->mpTextEngine; }
+vcl::Window* TextView::GetWindow() const
+{ return mpImpl->mpWindow; }
+void TextView::EnableCursor( bool bEnable )
+{ mpImpl->mbCursorEnabled = bEnable; }
+bool TextView::IsCursorEnabled() const
+{ return mpImpl->mbCursorEnabled; }
+void TextView::SetStartDocPos( const Point& rPos )
+{ mpImpl->maStartDocPos = rPos; }
+const Point& TextView::GetStartDocPos() const
+{ return mpImpl->maStartDocPos; }
+void TextView::SetAutoIndentMode( bool bAutoIndent )
+{ mpImpl->mbAutoIndent = bAutoIndent; }
+bool TextView::IsReadOnly() const
+{ return mpImpl->mbReadOnly; }
+void TextView::SetAutoScroll( bool bAutoScroll )
+{ mpImpl->mbAutoScroll = bAutoScroll; }
+bool TextView::IsAutoScroll() const
+{ return mpImpl->mbAutoScroll; }
+bool TextView::HasSelection() const
+{ return mpImpl->maSelection.HasRange(); }
+bool TextView::IsInsertMode() const
+{ return mpImpl->mbInsertMode; }
+
+void TextView::MatchGroup()
+{
+ TextSelection aTmpSel( GetSelection() );
+ aTmpSel.Justify();
+ if ( ( aTmpSel.GetStart().GetPara() != aTmpSel.GetEnd().GetPara() ) ||
+ ( ( aTmpSel.GetEnd().GetIndex() - aTmpSel.GetStart().GetIndex() ) > 1 ) )
+ {
+ return;
+ }
+
+ TextSelection aMatchSel = static_cast<ExtTextEngine*>(GetTextEngine())->MatchGroup( aTmpSel.GetStart() );
+ if ( aMatchSel.HasRange() )
+ SetSelection( aMatchSel );
+}
+
+void TextView::CenterPaM( const TextPaM& rPaM )
+{
+ // Get textview size and the corresponding y-coordinates
+ Size aOutSz = mpImpl->mpWindow->GetOutputSizePixel();
+ tools::Long nVisStartY = mpImpl->maStartDocPos.Y();
+ tools::Long nVisEndY = mpImpl->maStartDocPos.Y() + aOutSz.Height();
+
+ // Retrieve the coordinates of the PaM
+ tools::Rectangle aRect = mpImpl->mpTextEngine->PaMtoEditCursor(rPaM);
+
+ // Recalculate the offset of the center y-coordinates and scroll
+ Scroll(0, (nVisStartY + nVisEndY) / 2 - aRect.TopLeft().getY());
+}
+
+bool TextView::Search( const i18nutil::SearchOptions& rSearchOptions, bool bForward )
+{
+ bool bFound = false;
+ TextSelection aSel( GetSelection() );
+ if ( static_cast<ExtTextEngine*>(GetTextEngine())->Search( aSel, rSearchOptions, bForward ) )
+ {
+ bFound = true;
+ // First add the beginning of the word to the selection,
+ // so that the whole word is in the visible region.
+ SetSelection( aSel.GetStart() );
+ ShowCursor( true, false );
+ }
+ else
+ {
+ aSel = GetSelection().GetEnd();
+ }
+
+ SetSelection( aSel );
+ // tdf#49482: Move the start of the selection to the center of the textview
+ if (bFound)
+ {
+ CenterPaM( aSel.GetStart() );
+ }
+ ShowCursor();
+
+ return bFound;
+}
+
+sal_uInt16 TextView::Replace( const i18nutil::SearchOptions& rSearchOptions, bool bAll, bool bForward )
+{
+ sal_uInt16 nFound = 0;
+
+ if ( !bAll )
+ {
+ if ( GetSelection().HasRange() )
+ {
+ InsertText( rSearchOptions.replaceString );
+ nFound = 1;
+ Search( rSearchOptions, bForward ); // right away to the next
+ }
+ else
+ {
+ if( Search( rSearchOptions, bForward ) )
+ nFound = 1;
+ }
+ }
+ else
+ {
+ // the writer replaces all, from beginning to end
+
+ ExtTextEngine* pTextEngine = static_cast<ExtTextEngine*>(GetTextEngine());
+
+ // HideSelection();
+ TextSelection aSel;
+
+ bool bSearchInSelection = (0 != (rSearchOptions.searchFlag & css::util::SearchFlags::REG_NOT_BEGINOFLINE) );
+ if ( bSearchInSelection )
+ {
+ aSel = GetSelection();
+ aSel.Justify();
+ }
+
+ TextSelection aSearchSel( aSel );
+
+ bool bFound = pTextEngine->Search( aSel, rSearchOptions );
+ if ( bFound )
+ pTextEngine->UndoActionStart();
+ while ( bFound )
+ {
+ nFound++;
+
+ TextPaM aNewStart = pTextEngine->ImpInsertText( aSel, rSearchOptions.replaceString );
+ // tdf#64690 - extend selection to include inserted text portions
+ if ( aSel.GetEnd().GetPara() == aSearchSel.GetEnd().GetPara() )
+ {
+ aSearchSel.GetEnd().GetIndex() += rSearchOptions.replaceString.getLength() - 1;
+ }
+ aSel = aSearchSel;
+ aSel.GetStart() = aNewStart;
+ bFound = pTextEngine->Search( aSel, rSearchOptions );
+ }
+ if ( nFound )
+ {
+ SetSelection( aSel.GetStart() );
+ pTextEngine->FormatAndUpdate( this );
+ pTextEngine->UndoActionEnd();
+ }
+ }
+ return nFound;
+}
+
+bool TextView::ImpIndentBlock( bool bRight )
+{
+ bool bDone = false;
+
+ TextSelection aSel = GetSelection();
+ aSel.Justify();
+
+ HideSelection();
+ GetTextEngine()->UndoActionStart();
+
+ const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
+ sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
+ if ( aSel.HasRange() && !aSel.GetEnd().GetIndex() )
+ {
+ nEndPara--; // do not indent
+ }
+
+ for ( sal_uInt32 nPara = nStartPara; nPara <= nEndPara; ++nPara )
+ {
+ if ( bRight )
+ {
+ // add tabs
+ GetTextEngine()->ImpInsertText( TextPaM( nPara, 0 ), '\t' );
+ bDone = true;
+ }
+ else
+ {
+ // remove Tabs/Blanks
+ OUString aText = GetTextEngine()->GetText( nPara );
+ if ( !aText.isEmpty() && (
+ ( aText[ 0 ] == '\t' ) ||
+ ( aText[ 0 ] == ' ' ) ) )
+ {
+ GetTextEngine()->ImpDeleteText( TextSelection( TextPaM( nPara, 0 ), TextPaM( nPara, 1 ) ) );
+ bDone = true;
+ }
+ }
+ }
+
+ GetTextEngine()->UndoActionEnd();
+
+ bool bRange = aSel.HasRange();
+ if ( bRight )
+ {
+ ++aSel.GetStart().GetIndex();
+ if ( bRange && ( aSel.GetEnd().GetPara() == nEndPara ) )
+ ++aSel.GetEnd().GetIndex();
+ }
+ else
+ {
+ if ( aSel.GetStart().GetIndex() )
+ --aSel.GetStart().GetIndex();
+ if ( bRange && aSel.GetEnd().GetIndex() )
+ --aSel.GetEnd().GetIndex();
+ }
+
+ ImpSetSelection( aSel );
+ GetTextEngine()->FormatAndUpdate( this );
+
+ return bDone;
+}
+
+bool TextView::IndentBlock()
+{
+ return ImpIndentBlock( true );
+}
+
+bool TextView::UnindentBlock()
+{
+ return ImpIndentBlock( false );
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/txtattr.cxx b/vcl/source/edit/txtattr.cxx
new file mode 100644
index 000000000..8e979c1e3
--- /dev/null
+++ b/vcl/source/edit/txtattr.cxx
@@ -0,0 +1,93 @@
+/* -*- 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 <vcl/txtattr.hxx>
+#include <vcl/font.hxx>
+
+TextAttrib::~TextAttrib()
+{
+}
+
+bool TextAttrib::operator==( const TextAttrib& rAttr ) const
+{
+ return mnWhich == rAttr.mnWhich;
+}
+
+TextAttribFontColor::TextAttribFontColor( const Color& rColor )
+ : TextAttrib( TEXTATTR_FONTCOLOR ), maColor( rColor )
+{
+}
+
+void TextAttribFontColor::SetFont( vcl::Font& rFont ) const
+{
+ rFont.SetColor( maColor );
+}
+
+std::unique_ptr<TextAttrib> TextAttribFontColor::Clone() const
+{
+ return std::unique_ptr<TextAttrib>(new TextAttribFontColor( *this ));
+}
+
+bool TextAttribFontColor::operator==( const TextAttrib& rAttr ) const
+{
+ return ( ( TextAttrib::operator==(rAttr ) ) &&
+ ( maColor == static_cast<const TextAttribFontColor&>(rAttr).maColor ) );
+}
+
+TextAttribFontWeight::TextAttribFontWeight( FontWeight eWeight )
+ : TextAttrib( TEXTATTR_FONTWEIGHT ), meWeight( eWeight )
+{
+}
+
+void TextAttribFontWeight::SetFont( vcl::Font& rFont ) const
+{
+ rFont.SetWeight( meWeight );
+}
+
+std::unique_ptr<TextAttrib> TextAttribFontWeight::Clone() const
+{
+ return std::unique_ptr<TextAttrib>(new TextAttribFontWeight( *this ));
+}
+
+bool TextAttribFontWeight::operator==( const TextAttrib& rAttr ) const
+{
+ return ( ( TextAttrib::operator==(rAttr ) ) &&
+ ( meWeight == static_cast<const TextAttribFontWeight&>(rAttr).meWeight ) );
+}
+
+TextAttribProtect::TextAttribProtect() :
+ TextAttrib( TEXTATTR_PROTECTED )
+{
+}
+
+void TextAttribProtect::SetFont( vcl::Font& ) const
+{
+}
+
+std::unique_ptr<TextAttrib> TextAttribProtect::Clone() const
+{
+ return std::unique_ptr<TextAttrib>(new TextAttribProtect());
+}
+
+bool TextAttribProtect::operator==( const TextAttrib& rAttr ) const
+{
+ return TextAttrib::operator==(rAttr );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/vclmedit.cxx b/vcl/source/edit/vclmedit.cxx
new file mode 100644
index 000000000..03ed860bd
--- /dev/null
+++ b/vcl/source/edit/vclmedit.cxx
@@ -0,0 +1,1494 @@
+/* -*- 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 <memory>
+#include <i18nlangtag/languagetag.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/event.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/specialchars.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/toolkit/vclmedit.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/xtextedt.hxx>
+#include <vcl/textview.hxx>
+#include <vcl/ptrstyle.hxx>
+
+#include <svl/undo.hxx>
+#include <svl/lstner.hxx>
+#include <vcl/uitest/uiobject.hxx>
+
+#include <vcl/scrbar.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/weld.hxx>
+#include <osl/diagnose.h>
+#include <tools/json_writer.hxx>
+
+class ImpVclMEdit : public SfxListener
+{
+private:
+ VclPtr<VclMultiLineEdit> pVclMultiLineEdit;
+
+ VclPtr<TextWindow> mpTextWindow;
+ VclPtr<ScrollBar> mpHScrollBar;
+ VclPtr<ScrollBar> mpVScrollBar;
+ VclPtr<ScrollBarBox> mpScrollBox;
+
+ tools::Long mnTextWidth;
+ mutable Selection maSelection;
+
+protected:
+ virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override;
+ void ImpUpdateScrollBarVis( WinBits nWinStyle );
+ void ImpInitScrollBars();
+ void ImpSetScrollBarRanges();
+ void ImpSetHScrollBarThumbPos();
+ DECL_LINK( ScrollHdl, ScrollBar*, void );
+
+public:
+ ImpVclMEdit( VclMultiLineEdit* pVclMultiLineEdit, WinBits nWinStyle );
+ virtual ~ImpVclMEdit() override;
+
+ void SetModified( bool bMod );
+
+ void SetReadOnly( bool bRdOnly );
+ bool IsReadOnly() const;
+
+ void SetMaxTextLen(sal_Int32 nLen);
+ sal_Int32 GetMaxTextLen() const;
+
+ void SetMaxTextWidth(tools::Long nMaxWidth);
+
+ void InsertText( const OUString& rStr );
+ OUString GetSelected() const;
+ OUString GetSelected( LineEnd aSeparator ) const;
+
+ void SetSelection( const Selection& rSelection );
+ const Selection& GetSelection() const;
+
+ void Cut();
+ void Copy();
+ void Paste();
+
+ void SetText( const OUString& rStr );
+ OUString GetText() const;
+ OUString GetText( LineEnd aSeparator ) const;
+ OUString GetTextLines( LineEnd aSeparator ) const;
+
+ void Resize();
+ void GetFocus();
+
+ bool HandleCommand( const CommandEvent& rCEvt );
+
+ void Enable( bool bEnable );
+
+ Size CalcMinimumSize() const;
+ Size CalcBlockSize( sal_uInt16 nColumns, sal_uInt16 nLines ) const;
+ void GetMaxVisColumnsAndLines( sal_uInt16& rnCols, sal_uInt16& rnLines ) const;
+
+ void SetAlign( WinBits nWinStyle );
+
+ void InitFromStyle( WinBits nWinStyle );
+
+ TextWindow* GetTextWindow() { return mpTextWindow; }
+ ScrollBar& GetHScrollBar() { return *mpHScrollBar; }
+ ScrollBar& GetVScrollBar() { return *mpVScrollBar; }
+};
+
+ImpVclMEdit::ImpVclMEdit( VclMultiLineEdit* pEdt, WinBits nWinStyle )
+ : pVclMultiLineEdit(pEdt)
+ , mpTextWindow(VclPtr<TextWindow>::Create(pEdt))
+ , mpHScrollBar(VclPtr<ScrollBar>::Create(pVclMultiLineEdit, WB_HSCROLL|WB_DRAG))
+ , mpVScrollBar(VclPtr<ScrollBar>::Create(pVclMultiLineEdit, WB_VSCROLL|WB_DRAG))
+ , mpScrollBox(VclPtr<ScrollBarBox>::Create(pVclMultiLineEdit, WB_SIZEABLE))
+ , mnTextWidth(0)
+{
+ mpVScrollBar->SetScrollHdl( LINK( this, ImpVclMEdit, ScrollHdl ) );
+ mpHScrollBar->SetScrollHdl( LINK( this, ImpVclMEdit, ScrollHdl ) );
+ mpTextWindow->Show();
+ InitFromStyle( nWinStyle );
+ StartListening( *mpTextWindow->GetTextEngine() );
+}
+
+void ImpVclMEdit::ImpUpdateScrollBarVis( WinBits nWinStyle )
+{
+ const bool bHaveVScroll = mpVScrollBar->IsVisible();
+ const bool bHaveHScroll = mpHScrollBar->IsVisible();
+ const bool bHaveScrollBox = mpScrollBox->IsVisible();
+
+ bool bNeedVScroll = ( nWinStyle & WB_VSCROLL ) == WB_VSCROLL;
+ const bool bNeedHScroll = ( nWinStyle & WB_HSCROLL ) == WB_HSCROLL;
+
+ const bool bAutoVScroll = ( nWinStyle & WB_AUTOVSCROLL ) == WB_AUTOVSCROLL;
+ if ( !bNeedVScroll && bAutoVScroll )
+ {
+ TextEngine& rEngine( *mpTextWindow->GetTextEngine() );
+ tools::Long nOverallTextHeight(0);
+ for ( sal_uInt32 i=0; i<rEngine.GetParagraphCount(); ++i )
+ nOverallTextHeight += rEngine.GetTextHeight( i );
+ if ( nOverallTextHeight > mpTextWindow->GetOutputSizePixel().Height() )
+ bNeedVScroll = true;
+ }
+
+ const bool bNeedScrollBox = bNeedVScroll && bNeedHScroll;
+
+ bool bScrollbarsChanged = false;
+ if ( bHaveVScroll != bNeedVScroll )
+ {
+ mpVScrollBar->Show(bNeedVScroll);
+ bScrollbarsChanged = true;
+ }
+
+ if ( bHaveHScroll != bNeedHScroll )
+ {
+ mpHScrollBar->Show(bNeedHScroll);
+ bScrollbarsChanged = true;
+ }
+
+ if ( bHaveScrollBox != bNeedScrollBox )
+ {
+ mpScrollBox->Show(bNeedScrollBox);
+ }
+
+ if ( bScrollbarsChanged )
+ {
+ ImpInitScrollBars();
+ Resize();
+ }
+}
+
+void ImpVclMEdit::InitFromStyle( WinBits nWinStyle )
+{
+ ImpUpdateScrollBarVis( nWinStyle );
+ SetAlign( nWinStyle );
+
+ if ( nWinStyle & WB_NOHIDESELECTION )
+ mpTextWindow->SetAutoFocusHide( false );
+ else
+ mpTextWindow->SetAutoFocusHide( true );
+
+ if ( nWinStyle & WB_READONLY )
+ mpTextWindow->GetTextView()->SetReadOnly( true );
+ else
+ mpTextWindow->GetTextView()->SetReadOnly( false );
+
+ if ( nWinStyle & WB_IGNORETAB )
+ {
+ mpTextWindow->SetIgnoreTab( true );
+ }
+ else
+ {
+ mpTextWindow->SetIgnoreTab( false );
+ // #103667# VclMultiLineEdit has the flag, but focusable window also needs this flag
+ WinBits nStyle = mpTextWindow->GetStyle();
+ nStyle |= WB_NODIALOGCONTROL;
+ mpTextWindow->SetStyle( nStyle );
+ }
+}
+
+ImpVclMEdit::~ImpVclMEdit()
+{
+ EndListening( *mpTextWindow->GetTextEngine() );
+ mpTextWindow.disposeAndClear();
+ mpHScrollBar.disposeAndClear();
+ mpVScrollBar.disposeAndClear();
+ mpScrollBox.disposeAndClear();
+ pVclMultiLineEdit.disposeAndClear();
+}
+
+void ImpVclMEdit::ImpSetScrollBarRanges()
+{
+ const tools::Long nTextHeight = mpTextWindow->GetTextEngine()->GetTextHeight();
+ mpVScrollBar->SetRange( Range( 0, nTextHeight-1 ) );
+
+ mpHScrollBar->SetRange( Range( 0, mnTextWidth-1 ) );
+}
+
+void ImpVclMEdit::ImpInitScrollBars()
+{
+ static const sal_Unicode sampleChar = { 'x' };
+
+ ImpSetScrollBarRanges();
+
+ Size aCharBox;
+ aCharBox.setWidth( mpTextWindow->GetTextWidth( OUString(sampleChar) ) );
+ aCharBox.setHeight( mpTextWindow->GetTextHeight() );
+ Size aOutSz = mpTextWindow->GetOutputSizePixel();
+
+ mpHScrollBar->SetVisibleSize( aOutSz.Width() );
+ mpHScrollBar->SetPageSize( aOutSz.Width() * 8 / 10 );
+ mpHScrollBar->SetLineSize( aCharBox.Width()*10 );
+ ImpSetHScrollBarThumbPos();
+
+ mpVScrollBar->SetVisibleSize( aOutSz.Height() );
+ mpVScrollBar->SetPageSize( aOutSz.Height() * 8 / 10 );
+ mpVScrollBar->SetLineSize( aCharBox.Height() );
+ mpVScrollBar->SetThumbPos( mpTextWindow->GetTextView()->GetStartDocPos().Y() );
+}
+
+void ImpVclMEdit::ImpSetHScrollBarThumbPos()
+{
+ tools::Long nX = mpTextWindow->GetTextView()->GetStartDocPos().X();
+ if ( !mpTextWindow->GetTextEngine()->IsRightToLeft() )
+ mpHScrollBar->SetThumbPos( nX );
+ else
+ mpHScrollBar->SetThumbPos( mnTextWidth - mpHScrollBar->GetVisibleSize() - nX );
+
+}
+
+IMPL_LINK( ImpVclMEdit, ScrollHdl, ScrollBar*, pCurScrollBar, void )
+{
+ tools::Long nDiffX = 0, nDiffY = 0;
+
+ if ( pCurScrollBar == mpVScrollBar )
+ nDiffY = mpTextWindow->GetTextView()->GetStartDocPos().Y() - pCurScrollBar->GetThumbPos();
+ else if ( pCurScrollBar == mpHScrollBar )
+ nDiffX = mpTextWindow->GetTextView()->GetStartDocPos().X() - pCurScrollBar->GetThumbPos();
+
+ mpTextWindow->GetTextView()->Scroll( nDiffX, nDiffY );
+ // mpTextWindow->GetTextView()->ShowCursor( false, true );
+}
+
+void ImpVclMEdit::SetAlign( WinBits nWinStyle )
+{
+ bool bRTL = AllSettings::GetLayoutRTL();
+ mpTextWindow->GetTextEngine()->SetRightToLeft( bRTL );
+
+ if ( nWinStyle & WB_CENTER )
+ mpTextWindow->GetTextEngine()->SetTextAlign( TxtAlign::Center );
+ else if ( nWinStyle & WB_RIGHT )
+ mpTextWindow->GetTextEngine()->SetTextAlign( !bRTL ? TxtAlign::Right : TxtAlign::Left );
+ else if ( nWinStyle & WB_LEFT )
+ mpTextWindow->GetTextEngine()->SetTextAlign( !bRTL ? TxtAlign::Left : TxtAlign::Right );
+}
+
+void ImpVclMEdit::SetModified( bool bMod )
+{
+ mpTextWindow->GetTextEngine()->SetModified( bMod );
+}
+
+void ImpVclMEdit::SetReadOnly( bool bRdOnly )
+{
+ mpTextWindow->GetTextView()->SetReadOnly( bRdOnly );
+ // TODO: Adjust color?
+}
+
+bool ImpVclMEdit::IsReadOnly() const
+{
+ return mpTextWindow->GetTextView()->IsReadOnly();
+}
+
+void ImpVclMEdit::SetMaxTextLen(sal_Int32 nLen)
+{
+ mpTextWindow->GetTextEngine()->SetMaxTextLen(nLen);
+}
+
+sal_Int32 ImpVclMEdit::GetMaxTextLen() const
+{
+ return mpTextWindow->GetTextEngine()->GetMaxTextLen();
+}
+
+void ImpVclMEdit::InsertText( const OUString& rStr )
+{
+ mpTextWindow->GetTextView()->InsertText( rStr );
+}
+
+OUString ImpVclMEdit::GetSelected() const
+{
+ return mpTextWindow->GetTextView()->GetSelected();
+}
+
+OUString ImpVclMEdit::GetSelected( LineEnd aSeparator ) const
+{
+ return mpTextWindow->GetTextView()->GetSelected( aSeparator );
+}
+
+void ImpVclMEdit::SetMaxTextWidth(tools::Long nMaxWidth)
+{
+ mpTextWindow->GetTextEngine()->SetMaxTextWidth(nMaxWidth);
+}
+
+void ImpVclMEdit::Resize()
+{
+ int nIteration = 1;
+ do
+ {
+ WinBits nWinStyle( pVclMultiLineEdit->GetStyle() );
+ if ( ( nWinStyle & WB_AUTOVSCROLL ) == WB_AUTOVSCROLL )
+ ImpUpdateScrollBarVis( nWinStyle );
+
+ Size aSz = pVclMultiLineEdit->GetOutputSizePixel();
+ Size aEditSize = aSz;
+ tools::Long nSBWidth = pVclMultiLineEdit->GetSettings().GetStyleSettings().GetScrollBarSize();
+ nSBWidth = pVclMultiLineEdit->CalcZoom( nSBWidth );
+
+ if (mpHScrollBar->IsVisible())
+ aSz.AdjustHeight( -(nSBWidth+1) );
+ if (mpVScrollBar->IsVisible())
+ aSz.AdjustWidth( -(nSBWidth+1) );
+
+ if (!mpHScrollBar->IsVisible())
+ mpTextWindow->GetTextEngine()->SetMaxTextWidth( aSz.Width() );
+ else
+ mpHScrollBar->setPosSizePixel( 0, aEditSize.Height()-nSBWidth, aSz.Width(), nSBWidth );
+
+ Point aTextWindowPos;
+ if (mpVScrollBar->IsVisible())
+ {
+ if( AllSettings::GetLayoutRTL() )
+ {
+ mpVScrollBar->setPosSizePixel( 0, 0, nSBWidth, aSz.Height() );
+ aTextWindowPos.AdjustX(nSBWidth );
+ }
+ else
+ mpVScrollBar->setPosSizePixel( aEditSize.Width()-nSBWidth, 0, nSBWidth, aSz.Height() );
+ }
+
+ if (mpScrollBox->IsVisible())
+ mpScrollBox->setPosSizePixel( aSz.Width(), aSz.Height(), nSBWidth, nSBWidth );
+
+ Size aTextWindowSize( aSz );
+ if ( aTextWindowSize.Width() < 0 )
+ aTextWindowSize.setWidth( 0 );
+ if ( aTextWindowSize.Height() < 0 )
+ aTextWindowSize.setHeight( 0 );
+
+ Size aOldTextWindowSize( mpTextWindow->GetSizePixel() );
+ mpTextWindow->SetPosSizePixel( aTextWindowPos, aTextWindowSize );
+ if ( aOldTextWindowSize == aTextWindowSize )
+ break;
+
+ // Changing the text window size might effectively have changed the need for
+ // scrollbars, so do another iteration.
+ ++nIteration;
+ OSL_ENSURE( nIteration < 3, "ImpVclMEdit::Resize: isn't this expected to terminate with the second iteration?" );
+
+ } while ( nIteration <= 3 ); // artificial break after four iterations
+
+ ImpInitScrollBars();
+}
+
+void ImpVclMEdit::GetFocus()
+{
+ mpTextWindow->GrabFocus();
+}
+
+void ImpVclMEdit::Cut()
+{
+ if ( !mpTextWindow->GetTextView()->IsReadOnly() )
+ mpTextWindow->GetTextView()->Cut();
+}
+
+void ImpVclMEdit::Copy()
+{
+ mpTextWindow->GetTextView()->Copy();
+}
+
+void ImpVclMEdit::Paste()
+{
+ if ( !mpTextWindow->GetTextView()->IsReadOnly() )
+ mpTextWindow->GetTextView()->Paste();
+}
+
+void ImpVclMEdit::SetText( const OUString& rStr )
+{
+ bool bWasModified = mpTextWindow->GetTextEngine()->IsModified();
+ mpTextWindow->GetTextEngine()->SetText( rStr );
+ if ( !bWasModified )
+ mpTextWindow->GetTextEngine()->SetModified( false );
+
+ mpTextWindow->GetTextView()->SetSelection( TextSelection() );
+
+ WinBits nWinStyle( pVclMultiLineEdit->GetStyle() );
+ if ( ( nWinStyle & WB_AUTOVSCROLL ) == WB_AUTOVSCROLL )
+ ImpUpdateScrollBarVis( nWinStyle );
+}
+
+OUString ImpVclMEdit::GetText() const
+{
+ return mpTextWindow->GetTextEngine()->GetText();
+}
+
+OUString ImpVclMEdit::GetText( LineEnd aSeparator ) const
+{
+ return mpTextWindow->GetTextEngine()->GetText( aSeparator );
+}
+
+OUString ImpVclMEdit::GetTextLines( LineEnd aSeparator ) const
+{
+ return mpTextWindow->GetTextEngine()->GetTextLines( aSeparator );
+}
+
+void ImpVclMEdit::Notify( SfxBroadcaster&, const SfxHint& rHint )
+{
+ const TextHint* pTextHint = dynamic_cast<const TextHint*>(&rHint);
+ if ( !pTextHint )
+ return;
+
+ switch (pTextHint->GetId())
+ {
+ case SfxHintId::TextViewScrolled:
+ if (mpHScrollBar->IsVisible())
+ ImpSetHScrollBarThumbPos();
+ if (mpVScrollBar->IsVisible())
+ mpVScrollBar->SetThumbPos( mpTextWindow->GetTextView()->GetStartDocPos().Y() );
+ break;
+
+ case SfxHintId::TextHeightChanged:
+ if ( mpTextWindow->GetTextView()->GetStartDocPos().Y() )
+ {
+ tools::Long nOutHeight = mpTextWindow->GetOutputSizePixel().Height();
+ tools::Long nTextHeight = mpTextWindow->GetTextEngine()->GetTextHeight();
+ if ( nTextHeight < nOutHeight )
+ mpTextWindow->GetTextView()->Scroll( 0, mpTextWindow->GetTextView()->GetStartDocPos().Y() );
+ }
+ ImpSetScrollBarRanges();
+ break;
+
+ case SfxHintId::TextFormatted:
+ if (mpHScrollBar->IsVisible())
+ {
+ const tools::Long nWidth = mpTextWindow->GetTextEngine()->CalcTextWidth();
+ if ( nWidth != mnTextWidth )
+ {
+ mnTextWidth = nWidth;
+ mpHScrollBar->SetRange( Range( 0, mnTextWidth-1 ) );
+ ImpSetHScrollBarThumbPos();
+ }
+ }
+ break;
+
+ case SfxHintId::TextModified:
+ ImpUpdateScrollBarVis(pVclMultiLineEdit->GetStyle());
+ pVclMultiLineEdit->Modify();
+ break;
+
+ case SfxHintId::TextViewSelectionChanged:
+ pVclMultiLineEdit->SelectionChanged();
+ break;
+
+ case SfxHintId::TextViewCaretChanged:
+ pVclMultiLineEdit->CaretChanged();
+ break;
+
+ default: break;
+ }
+}
+
+void ImpVclMEdit::SetSelection( const Selection& rSelection )
+{
+ OUString aText = mpTextWindow->GetTextEngine()->GetText();
+
+ Selection aNewSelection( rSelection );
+ if ( aNewSelection.Min() < 0 )
+ aNewSelection.Min() = 0;
+ else if ( aNewSelection.Min() > aText.getLength() )
+ aNewSelection.Min() = aText.getLength();
+ if ( aNewSelection.Max() < 0 )
+ aNewSelection.Max() = 0;
+ else if ( aNewSelection.Max() > aText.getLength() )
+ aNewSelection.Max() = aText.getLength();
+
+ tools::Long nEnd = std::max( aNewSelection.Min(), aNewSelection.Max() );
+ TextSelection aTextSel;
+ sal_uInt32 nPara = 0;
+ sal_Int32 nChar = 0;
+ tools::Long x = 0;
+ while ( x <= nEnd )
+ {
+ if ( x == aNewSelection.Min() )
+ aTextSel.GetStart() = TextPaM( nPara, nChar );
+ if ( x == aNewSelection.Max() )
+ aTextSel.GetEnd() = TextPaM( nPara, nChar );
+
+ if ( ( x < aText.getLength() ) && ( aText[ x ] == '\n' ) )
+ {
+ nPara++;
+ nChar = 0;
+ }
+ else
+ nChar++;
+ x++;
+ }
+ mpTextWindow->GetTextView()->SetSelection( aTextSel );
+}
+
+const Selection& ImpVclMEdit::GetSelection() const
+{
+ maSelection = Selection();
+ TextSelection aTextSel( mpTextWindow->GetTextView()->GetSelection() );
+ aTextSel.Justify();
+ // flatten selection => every line-break a character
+
+ ExtTextEngine* pExtTextEngine = mpTextWindow->GetTextEngine();
+ // paragraphs before
+ for ( sal_uInt32 n = 0; n < aTextSel.GetStart().GetPara(); ++n )
+ {
+ maSelection.Min() += pExtTextEngine->GetTextLen( n );
+ maSelection.Min()++;
+ }
+
+ // first paragraph with selection
+ maSelection.Max() = maSelection.Min();
+ maSelection.Min() += aTextSel.GetStart().GetIndex();
+
+ for ( sal_uInt32 n = aTextSel.GetStart().GetPara(); n < aTextSel.GetEnd().GetPara(); ++n )
+ {
+ maSelection.Max() += pExtTextEngine->GetTextLen( n );
+ maSelection.Max()++;
+ }
+
+ maSelection.Max() += aTextSel.GetEnd().GetIndex();
+
+ return maSelection;
+}
+
+Size ImpVclMEdit::CalcMinimumSize() const
+{
+ Size aSz( mpTextWindow->GetTextEngine()->CalcTextWidth(),
+ mpTextWindow->GetTextEngine()->GetTextHeight() );
+
+ if (mpHScrollBar->IsVisible())
+ aSz.AdjustHeight(mpHScrollBar->GetSizePixel().Height() );
+ if (mpVScrollBar->IsVisible())
+ aSz.AdjustWidth(mpVScrollBar->GetSizePixel().Width() );
+
+ return aSz;
+}
+
+Size ImpVclMEdit::CalcBlockSize( sal_uInt16 nColumns, sal_uInt16 nLines ) const
+{
+ static const sal_Unicode sampleChar = 'X';
+
+ Size aSz;
+ Size aCharSz;
+ aCharSz.setWidth( mpTextWindow->GetTextWidth( OUString(sampleChar) ) );
+ aCharSz.setHeight( mpTextWindow->GetTextHeight() );
+
+ if ( nLines )
+ aSz.setHeight( nLines*aCharSz.Height() );
+ else
+ aSz.setHeight( mpTextWindow->GetTextEngine()->GetTextHeight() );
+
+ if ( nColumns )
+ aSz.setWidth( nColumns*aCharSz.Width() );
+ else
+ aSz.setWidth( mpTextWindow->GetTextEngine()->CalcTextWidth() );
+
+ if (mpHScrollBar->IsVisible())
+ aSz.AdjustHeight(mpHScrollBar->GetSizePixel().Height() );
+ if (mpVScrollBar->IsVisible())
+ aSz.AdjustWidth(mpVScrollBar->GetSizePixel().Width() );
+
+ return aSz;
+}
+
+void ImpVclMEdit::GetMaxVisColumnsAndLines( sal_uInt16& rnCols, sal_uInt16& rnLines ) const
+{
+ static const sal_Unicode sampleChar = { 'x' };
+ Size aOutSz = mpTextWindow->GetOutputSizePixel();
+ Size aCharSz( mpTextWindow->GetTextWidth( OUString(sampleChar) ), mpTextWindow->GetTextHeight() );
+ rnCols = static_cast<sal_uInt16>(aOutSz.Width()/aCharSz.Width());
+ rnLines = static_cast<sal_uInt16>(aOutSz.Height()/aCharSz.Height());
+}
+
+void ImpVclMEdit::Enable( bool bEnable )
+{
+ mpTextWindow->Enable( bEnable );
+ if (mpHScrollBar->IsVisible())
+ mpHScrollBar->Enable( bEnable );
+ if (mpVScrollBar->IsVisible())
+ mpVScrollBar->Enable( bEnable );
+}
+
+bool ImpVclMEdit::HandleCommand( const CommandEvent& rCEvt )
+{
+ bool bDone = false;
+ CommandEventId nCommand = rCEvt.GetCommand();
+ if (nCommand == CommandEventId::Wheel ||
+ nCommand == CommandEventId::StartAutoScroll ||
+ nCommand == CommandEventId::AutoScroll ||
+ nCommand == CommandEventId::Gesture)
+ {
+ ScrollBar* pHScrollBar = mpHScrollBar->IsVisible() ? mpHScrollBar.get() : nullptr;
+ ScrollBar* pVScrollBar = mpVScrollBar->IsVisible() ? mpVScrollBar.get() : nullptr;
+ mpTextWindow->HandleScrollCommand(rCEvt, pHScrollBar, pVScrollBar);
+ bDone = true;
+ }
+ return bDone;
+}
+
+TextWindow::TextWindow(Edit* pParent)
+ : Window(pParent)
+ , mxParent(pParent)
+{
+ mbInMBDown = false;
+ mbFocusSelectionHide = false;
+ mbIgnoreTab = false;
+ mbActivePopup = false;
+ mbSelectOnTab = true;
+
+ SetPointer( PointerStyle::Text );
+
+ mpExtTextEngine.reset(new ExtTextEngine);
+ mpExtTextEngine->SetMaxTextLen(EDIT_NOLIMIT);
+ if( pParent->GetStyle() & WB_BORDER )
+ mpExtTextEngine->SetLeftMargin( 2 );
+ mpExtTextEngine->SetLocale( GetSettings().GetLanguageTag().getLocale() );
+ mpExtTextView.reset(new TextView( mpExtTextEngine.get(), this ));
+ mpExtTextEngine->InsertView( mpExtTextView.get() );
+ mpExtTextEngine->EnableUndo( true );
+ mpExtTextView->ShowCursor();
+
+ Color aBackgroundColor = GetSettings().GetStyleSettings().GetWorkspaceColor();
+ SetBackground( aBackgroundColor );
+ pParent->SetBackground( aBackgroundColor );
+}
+
+TextWindow::~TextWindow()
+{
+ disposeOnce();
+}
+
+void TextWindow::dispose()
+{
+ mxParent.clear();
+ mpExtTextView.reset();
+ mpExtTextEngine.reset();
+ Window::dispose();
+}
+
+void TextWindow::MouseMove( const MouseEvent& rMEvt )
+{
+ mpExtTextView->MouseMove( rMEvt );
+ Window::MouseMove( rMEvt );
+}
+
+void TextWindow::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ mbInMBDown = true; // so that GetFocus does not select everything
+ mpExtTextView->MouseButtonDown( rMEvt );
+ GrabFocus();
+ mbInMBDown = false;
+}
+
+void TextWindow::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ mpExtTextView->MouseButtonUp( rMEvt );
+}
+
+void TextWindow::KeyInput( const KeyEvent& rKEvent )
+{
+ bool bDone = false;
+ sal_uInt16 nCode = rKEvent.GetKeyCode().GetCode();
+ if ( nCode == css::awt::Key::SELECT_ALL ||
+ ( (nCode == KEY_A) && rKEvent.GetKeyCode().IsMod1() && !rKEvent.GetKeyCode().IsMod2() )
+ )
+ {
+ mpExtTextView->SetSelection( TextSelection( TextPaM( 0, 0 ), TextPaM( TEXT_PARA_ALL, TEXT_INDEX_ALL ) ) );
+ bDone = true;
+ }
+ else if ( (nCode == KEY_S) && rKEvent.GetKeyCode().IsShift() && rKEvent.GetKeyCode().IsMod1() )
+ {
+ if ( vcl::GetGetSpecialCharsFunction() )
+ {
+ // to maintain the selection
+ mbActivePopup = true;
+ OUString aChars = vcl::GetGetSpecialCharsFunction()(GetFrameWeld(), GetFont());
+ if (!aChars.isEmpty())
+ {
+ mpExtTextView->InsertText( aChars );
+ mpExtTextView->GetTextEngine()->SetModified( true );
+ }
+ mbActivePopup = false;
+ bDone = true;
+ }
+ }
+ else if ( nCode == KEY_TAB )
+ {
+ if ( !mbIgnoreTab || rKEvent.GetKeyCode().IsMod1() )
+ bDone = mpExtTextView->KeyInput( rKEvent );
+ }
+ else
+ {
+ bDone = mpExtTextView->KeyInput( rKEvent );
+ }
+
+ if ( !bDone )
+ Window::KeyInput( rKEvent );
+}
+
+void TextWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ mpExtTextView->Paint(rRenderContext, rRect);
+}
+
+void TextWindow::Resize()
+{
+}
+
+void TextWindow::Command( const CommandEvent& rCEvt )
+{
+ if ( rCEvt.GetCommand() == CommandEventId::ContextMenu )
+ {
+ VclPtr<PopupMenu> pPopup = mxParent->CreatePopupMenu();
+ bool bEnableCut = true;
+ bool bEnableCopy = true;
+ bool bEnableDelete = true;
+ bool bEnablePaste = true;
+ bool bEnableSpecialChar = true;
+ bool bEnableUndo = true;
+
+ if ( !mpExtTextView->HasSelection() )
+ {
+ bEnableCut = false;
+ bEnableCopy = false;
+ bEnableDelete = false;
+ }
+ if ( mpExtTextView->IsReadOnly() )
+ {
+ bEnableCut = false;
+ bEnablePaste = false;
+ bEnableDelete = false;
+ bEnableSpecialChar = false;
+ }
+ if ( !mpExtTextView->GetTextEngine()->HasUndoManager() || !mpExtTextView->GetTextEngine()->GetUndoManager().GetUndoActionCount() )
+ {
+ bEnableUndo = false;
+ }
+ pPopup->EnableItem(pPopup->GetItemId("cut"), bEnableCut);
+ pPopup->EnableItem(pPopup->GetItemId("copy"), bEnableCopy);
+ pPopup->EnableItem(pPopup->GetItemId("delete"), bEnableDelete);
+ pPopup->EnableItem(pPopup->GetItemId("paste"), bEnablePaste);
+ pPopup->EnableItem(pPopup->GetItemId("specialchar"), bEnableSpecialChar);
+ pPopup->EnableItem(pPopup->GetItemId("undo"), bEnableUndo);
+ pPopup->ShowItem(pPopup->GetItemId("specialchar"), !vcl::GetGetSpecialCharsFunction());
+
+ mbActivePopup = true;
+ Point aPos = rCEvt.GetMousePosPixel();
+ if ( !rCEvt.IsMouseEvent() )
+ {
+ // Sometime do show Menu centered in the selection !!!
+ Size aSize = GetOutputSizePixel();
+ aPos = Point( aSize.Width()/2, aSize.Height()/2 );
+ }
+ sal_uInt16 n = pPopup->Execute( this, aPos );
+ OString sCommand = pPopup->GetItemIdent(n);
+ if (sCommand == "undo")
+ {
+ mpExtTextView->Undo();
+ mpExtTextEngine->SetModified( true );
+ mpExtTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ else if (sCommand == "cut")
+ {
+ mpExtTextView->Cut();
+ mpExtTextEngine->SetModified( true );
+ mpExtTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ else if (sCommand == "copy")
+ {
+ mpExtTextView->Copy();
+ }
+ else if (sCommand == "paste")
+ {
+ mpExtTextView->Paste();
+ mpExtTextEngine->SetModified( true );
+ mpExtTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ else if (sCommand == "delete")
+ {
+ mpExtTextView->DeleteSelected();
+ mpExtTextEngine->SetModified( true );
+ mpExtTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ else if (sCommand == "selectall")
+ {
+ mpExtTextView->SetSelection( TextSelection( TextPaM( 0, 0 ), TextPaM( TEXT_PARA_ALL, TEXT_INDEX_ALL ) ) );
+ }
+ else if (sCommand == "specialchar")
+ {
+ OUString aChars = vcl::GetGetSpecialCharsFunction()(GetFrameWeld(), GetFont());
+ if (!aChars.isEmpty())
+ {
+ mpExtTextView->InsertText( aChars );
+ mpExtTextEngine->SetModified( true );
+ mpExtTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
+ }
+ }
+ pPopup.clear();
+ mbActivePopup = false;
+ }
+ else
+ {
+ mpExtTextView->Command( rCEvt );
+ }
+ Window::Command( rCEvt );
+}
+
+void TextWindow::GetFocus()
+{
+ Window::GetFocus();
+ if ( mbActivePopup )
+ return;
+
+ bool bGotoCursor = !mpExtTextView->IsReadOnly();
+ if ( mbFocusSelectionHide && IsReallyVisible() && mbSelectOnTab && !mbInMBDown )
+ {
+ // select everything, but do not scroll
+ bool bAutoScroll = mpExtTextView->IsAutoScroll();
+ mpExtTextView->SetAutoScroll( false );
+ mpExtTextView->SetSelection( TextSelection( TextPaM( 0, 0 ), TextPaM( TEXT_PARA_ALL, TEXT_INDEX_ALL ) ) );
+ mpExtTextView->SetAutoScroll( bAutoScroll );
+ bGotoCursor = false;
+ }
+ mpExtTextView->SetPaintSelection( true );
+ mpExtTextView->ShowCursor( bGotoCursor );
+}
+
+void TextWindow::LoseFocus()
+{
+ Window::LoseFocus();
+
+ if ( mbFocusSelectionHide && !mbActivePopup && mpExtTextView )
+ mpExtTextView->SetPaintSelection( false );
+}
+
+VclMultiLineEdit::VclMultiLineEdit( vcl::Window* pParent, WinBits nWinStyle )
+ : Edit( pParent, nWinStyle )
+{
+ SetType( WindowType::MULTILINEEDIT );
+ pImpVclMEdit.reset(new ImpVclMEdit( this, nWinStyle ));
+ ImplInitSettings( true );
+
+ SetCompoundControl( true );
+ SetStyle( ImplInitStyle( nWinStyle ) );
+}
+
+VclMultiLineEdit::~VclMultiLineEdit()
+{
+ disposeOnce();
+}
+
+void VclMultiLineEdit::dispose()
+{
+ pImpVclMEdit.reset();
+ Edit::dispose();
+}
+
+WinBits VclMultiLineEdit::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+
+ if ( !(nStyle & WB_IGNORETAB ))
+ nStyle |= WB_NODIALOGCONTROL;
+
+ return nStyle;
+}
+
+void VclMultiLineEdit::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ // The Font has to be adjusted, as the TextEngine does not take care of
+ // TextColor/Background
+
+ Color aTextColor = rStyleSettings.GetFieldTextColor();
+ if (IsControlForeground())
+ aTextColor = GetControlForeground();
+
+ if (!IsEnabled())
+ aTextColor = rStyleSettings.GetDisableColor();
+
+ vcl::Font aFont = rStyleSettings.GetFieldFont();
+ aFont.SetTransparent(IsPaintTransparent());
+ ApplyControlFont(rRenderContext, aFont);
+
+ vcl::Font theFont = rRenderContext.GetFont();
+ theFont.SetColor(aTextColor);
+ if (IsPaintTransparent())
+ theFont.SetFillColor(COL_TRANSPARENT);
+ else
+ theFont.SetFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
+
+ pImpVclMEdit->GetTextWindow()->SetFont(theFont);
+ // FIXME: next call causes infinite invalidation loop, rethink how to properly fix this situation
+ // pImpVclMEdit->GetTextWindow()->GetTextEngine()->SetFont(theFont);
+ pImpVclMEdit->GetTextWindow()->SetTextColor(aTextColor);
+
+ if (IsPaintTransparent())
+ {
+ pImpVclMEdit->GetTextWindow()->SetPaintTransparent(true);
+ pImpVclMEdit->GetTextWindow()->SetBackground();
+ pImpVclMEdit->GetTextWindow()->SetControlBackground();
+ rRenderContext.SetBackground();
+ SetControlBackground();
+ }
+ else
+ {
+ if (IsControlBackground())
+ pImpVclMEdit->GetTextWindow()->SetBackground(GetControlBackground());
+ else
+ pImpVclMEdit->GetTextWindow()->SetBackground(rStyleSettings.GetFieldColor());
+ // also adjust for VclMultiLineEdit as the TextComponent might hide Scrollbars
+ rRenderContext.SetBackground(pImpVclMEdit->GetTextWindow()->GetBackground());
+ }
+}
+
+void VclMultiLineEdit::ImplInitSettings(bool bBackground)
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+
+ // The Font has to be adjusted, as the TextEngine does not take care of
+ // TextColor/Background
+
+ Color aTextColor = rStyleSettings.GetFieldTextColor();
+ if (IsControlForeground())
+ aTextColor = GetControlForeground();
+ if (!IsEnabled())
+ aTextColor = rStyleSettings.GetDisableColor();
+
+ vcl::Font aFont = rStyleSettings.GetFieldFont();
+ aFont.SetTransparent(IsPaintTransparent());
+ ApplyControlFont(*GetOutDev(), aFont);
+
+ vcl::Font TheFont = GetFont();
+ TheFont.SetColor(aTextColor);
+ if (IsPaintTransparent())
+ TheFont.SetFillColor(COL_TRANSPARENT);
+ else
+ TheFont.SetFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
+ pImpVclMEdit->GetTextWindow()->SetFont(TheFont);
+ pImpVclMEdit->GetTextWindow()->GetTextEngine()->SetFont(TheFont);
+ pImpVclMEdit->GetTextWindow()->SetTextColor(aTextColor);
+
+ if (!bBackground)
+ return;
+
+ if (IsPaintTransparent())
+ {
+ pImpVclMEdit->GetTextWindow()->SetPaintTransparent(true);
+ pImpVclMEdit->GetTextWindow()->SetBackground();
+ pImpVclMEdit->GetTextWindow()->SetControlBackground();
+ SetBackground();
+ SetControlBackground();
+ }
+ else
+ {
+ if (IsControlBackground())
+ pImpVclMEdit->GetTextWindow()->SetBackground(GetControlBackground());
+ else
+ pImpVclMEdit->GetTextWindow()->SetBackground(rStyleSettings.GetFieldColor());
+ // also adjust for VclMultiLineEdit as the TextComponent might hide Scrollbars
+ SetBackground(pImpVclMEdit->GetTextWindow()->GetBackground());
+ }
+}
+
+void VclMultiLineEdit::Modify()
+{
+ aModifyHdlLink.Call( *this );
+
+ CallEventListeners( VclEventId::EditModify );
+}
+
+void VclMultiLineEdit::SelectionChanged()
+{
+ CallEventListeners(VclEventId::EditSelectionChanged);
+}
+
+void VclMultiLineEdit::CaretChanged()
+{
+ CallEventListeners(VclEventId::EditCaretChanged);
+}
+
+void VclMultiLineEdit::SetModifyFlag()
+{
+ pImpVclMEdit->SetModified( true );
+}
+
+void VclMultiLineEdit::SetReadOnly( bool bReadOnly )
+{
+ pImpVclMEdit->SetReadOnly( bReadOnly );
+ Edit::SetReadOnly( bReadOnly );
+
+ // #94921# ReadOnly can be overwritten in InitFromStyle() when WB not set.
+ WinBits nStyle = GetStyle();
+ if ( bReadOnly )
+ nStyle |= WB_READONLY;
+ else
+ nStyle &= ~WB_READONLY;
+ SetStyle( nStyle );
+}
+
+bool VclMultiLineEdit::IsReadOnly() const
+{
+ if (!pImpVclMEdit) // might be called from within the dtor, when pImpVclMEdit == NULL is a valid state
+ return true;
+
+ return pImpVclMEdit->IsReadOnly();
+}
+
+void VclMultiLineEdit::SetMaxTextLen(sal_Int32 nMaxLen)
+{
+ pImpVclMEdit->SetMaxTextLen(nMaxLen);
+}
+
+void VclMultiLineEdit::SetMaxTextWidth(tools::Long nMaxWidth)
+{
+ pImpVclMEdit->SetMaxTextWidth(nMaxWidth );
+}
+
+sal_Int32 VclMultiLineEdit::GetMaxTextLen() const
+{
+ return pImpVclMEdit->GetMaxTextLen();
+}
+
+void VclMultiLineEdit::ReplaceSelected( const OUString& rStr )
+{
+ pImpVclMEdit->InsertText( rStr );
+}
+
+void VclMultiLineEdit::DeleteSelected()
+{
+ pImpVclMEdit->InsertText( OUString() );
+}
+
+OUString VclMultiLineEdit::GetSelected() const
+{
+ return pImpVclMEdit->GetSelected();
+}
+
+OUString VclMultiLineEdit::GetSelected( LineEnd aSeparator ) const
+{
+ return pImpVclMEdit->GetSelected( aSeparator );
+}
+
+void VclMultiLineEdit::Cut()
+{
+ pImpVclMEdit->Cut();
+}
+
+void VclMultiLineEdit::Copy()
+{
+ pImpVclMEdit->Copy();
+}
+
+void VclMultiLineEdit::Paste()
+{
+ pImpVclMEdit->Paste();
+}
+
+void VclMultiLineEdit::SetText( const OUString& rStr )
+{
+ pImpVclMEdit->SetText( rStr );
+}
+
+OUString VclMultiLineEdit::GetText() const
+{
+ return pImpVclMEdit ? pImpVclMEdit->GetText() : OUString();
+}
+
+OUString VclMultiLineEdit::GetText( LineEnd aSeparator ) const
+{
+ return pImpVclMEdit ? pImpVclMEdit->GetText( aSeparator ) : OUString();
+}
+
+OUString VclMultiLineEdit::GetTextLines( LineEnd aSeparator ) const
+{
+ return pImpVclMEdit ? pImpVclMEdit->GetTextLines( aSeparator ) : OUString();
+}
+
+void VclMultiLineEdit::Resize()
+{
+ pImpVclMEdit->Resize();
+}
+
+void VclMultiLineEdit::GetFocus()
+{
+ if ( !pImpVclMEdit ) // might be called from within the dtor, when pImpVclMEdit == NULL is a valid state
+ return;
+
+ pImpVclMEdit->GetFocus();
+}
+
+void VclMultiLineEdit::SetSelection( const Selection& rSelection )
+{
+ pImpVclMEdit->SetSelection( rSelection );
+}
+
+const Selection& VclMultiLineEdit::GetSelection() const
+{
+ return pImpVclMEdit->GetSelection();
+}
+
+Size VclMultiLineEdit::CalcMinimumSize() const
+{
+ Size aSz = pImpVclMEdit->CalcMinimumSize();
+
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ static_cast<vcl::Window*>(const_cast<VclMultiLineEdit *>(this))->GetBorder( nLeft, nTop, nRight, nBottom );
+ aSz.AdjustWidth(nLeft+nRight );
+ aSz.AdjustHeight(nTop+nBottom );
+
+ return aSz;
+}
+
+Size VclMultiLineEdit::CalcAdjustedSize( const Size& rPrefSize ) const
+{
+ Size aSz = rPrefSize;
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ static_cast<vcl::Window*>(const_cast<VclMultiLineEdit *>(this))->GetBorder( nLeft, nTop, nRight, nBottom );
+
+ // center vertically for whole lines
+
+ tools::Long nHeight = aSz.Height() - nTop - nBottom;
+ tools::Long nLineHeight = pImpVclMEdit->CalcBlockSize( 1, 1 ).Height();
+ tools::Long nLines = nHeight / nLineHeight;
+ if ( nLines < 1 )
+ nLines = 1;
+
+ aSz.setHeight( nLines * nLineHeight );
+ aSz.AdjustHeight(nTop+nBottom );
+
+ return aSz;
+}
+
+Size VclMultiLineEdit::CalcBlockSize( sal_uInt16 nColumns, sal_uInt16 nLines ) const
+{
+ Size aSz = pImpVclMEdit->CalcBlockSize( nColumns, nLines );
+
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ static_cast<vcl::Window*>(const_cast<VclMultiLineEdit *>(this))->GetBorder( nLeft, nTop, nRight, nBottom );
+ aSz.AdjustWidth(nLeft+nRight );
+ aSz.AdjustHeight(nTop+nBottom );
+ return aSz;
+}
+
+void VclMultiLineEdit::GetMaxVisColumnsAndLines( sal_uInt16& rnCols, sal_uInt16& rnLines ) const
+{
+ pImpVclMEdit->GetMaxVisColumnsAndLines( rnCols, rnLines );
+}
+
+void VclMultiLineEdit::StateChanged( StateChangedType nType )
+{
+ if( nType == StateChangedType::Enable )
+ {
+ pImpVclMEdit->Enable( IsEnabled() );
+ ImplInitSettings( false );
+ }
+ else if( nType == StateChangedType::ReadOnly )
+ {
+ pImpVclMEdit->SetReadOnly( IsReadOnly() );
+ }
+ else if ( nType == StateChangedType::Zoom )
+ {
+ pImpVclMEdit->GetTextWindow()->SetZoom( GetZoom() );
+ ImplInitSettings( false );
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlFont )
+ {
+ ImplInitSettings( false );
+ Resize();
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ pImpVclMEdit->InitFromStyle( GetStyle() );
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ }
+ else if ( nType == StateChangedType::InitShow )
+ {
+ if( IsPaintTransparent() )
+ {
+ pImpVclMEdit->GetTextWindow()->SetPaintTransparent( true );
+ pImpVclMEdit->GetTextWindow()->SetBackground();
+ pImpVclMEdit->GetTextWindow()->SetControlBackground();
+ SetBackground();
+ SetControlBackground();
+ }
+ }
+
+ Control::StateChanged( nType );
+}
+
+void VclMultiLineEdit::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ImplInitSettings( true );
+ Resize();
+ Invalidate();
+ }
+ else
+ Control::DataChanged( rDCEvt );
+}
+
+void VclMultiLineEdit::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags )
+{
+ ImplInitSettings(true);
+
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+
+ vcl::Font aFont = pImpVclMEdit->GetTextWindow()->GetDrawPixelFont(pDev);
+ aFont.SetTransparent( true );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ pDev->SetTextFillColor();
+
+ // Border/Background
+ pDev->SetLineColor();
+ pDev->SetFillColor();
+ bool bBorder = (GetStyle() & WB_BORDER);
+ bool bBackground = IsControlBackground();
+ if ( bBorder || bBackground )
+ {
+ tools::Rectangle aRect( aPos, aSize );
+ if ( bBorder )
+ {
+ DecorationView aDecoView( pDev );
+ aRect = aDecoView.DrawFrame( aRect, DrawFrameStyle::DoubleIn );
+ }
+ if ( bBackground )
+ {
+ pDev->SetFillColor( GetControlBackground() );
+ pDev->DrawRect( aRect );
+ }
+ }
+
+ pDev->SetSystemTextColor(nFlags, IsEnabled());
+
+ OUString aText = GetText();
+ Size aTextSz( pDev->GetTextWidth( aText ), pDev->GetTextHeight() );
+ sal_uLong nLines = static_cast<sal_uLong>(aSize.Height() / aTextSz.Height());
+ if ( !nLines )
+ nLines = 1;
+ aTextSz.setHeight( nLines*aTextSz.Height() );
+ tools::Long nOnePixel = GetDrawPixel( pDev, 1 );
+ tools::Long nOffX = 3*nOnePixel;
+ tools::Long nOffY = 2*nOnePixel;
+
+ // Clipping?
+ if ( ( nOffY < 0 ) || ( (nOffY+aTextSz.Height()) > aSize.Height() ) || ( (nOffX+aTextSz.Width()) > aSize.Width() ) )
+ {
+ tools::Rectangle aClip( aPos, aSize );
+ if ( aTextSz.Height() > aSize.Height() )
+ aClip.AdjustBottom(aTextSz.Height() - aSize.Height() + 1 ); // so that HP-printer does not 'optimize-away'
+ pDev->IntersectClipRegion( aClip );
+ }
+
+ ExtTextEngine aTE;
+ aTE.SetText( GetText() );
+ aTE.SetMaxTextWidth( aSize.Width() );
+ aTE.SetFont( aFont );
+ aTE.SetTextAlign( pImpVclMEdit->GetTextWindow()->GetTextEngine()->GetTextAlign() );
+ aTE.Draw( pDev, Point( aPos.X() + nOffX, aPos.Y() + nOffY ) );
+
+ pDev->Pop();
+}
+
+bool VclMultiLineEdit::EventNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+ if( rNEvt.GetType() == MouseNotifyEvent::COMMAND )
+ {
+ bDone = pImpVclMEdit->HandleCommand( *rNEvt.GetCommandEvent() );
+ }
+ return bDone || Edit::EventNotify( rNEvt );
+}
+
+bool VclMultiLineEdit::PreNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+
+ if( ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) && ( !GetTextView()->IsCursorEnabled() ) )
+ {
+ const KeyEvent& rKEvent = *rNEvt.GetKeyEvent();
+ if ( !rKEvent.GetKeyCode().IsShift() && ( rKEvent.GetKeyCode().GetGroup() == KEYGROUP_CURSOR ) )
+ {
+ bDone = true;
+ TextSelection aSel = pImpVclMEdit->GetTextWindow()->GetTextView()->GetSelection();
+ if ( aSel.HasRange() )
+ {
+ aSel.GetStart() = aSel.GetEnd();
+ pImpVclMEdit->GetTextWindow()->GetTextView()->SetSelection( aSel );
+ }
+ else
+ {
+ switch ( rKEvent.GetKeyCode().GetCode() )
+ {
+ case KEY_UP:
+ {
+ if ( pImpVclMEdit->GetVScrollBar().IsVisible() )
+ pImpVclMEdit->GetVScrollBar().DoScrollAction( ScrollType::LineUp );
+ }
+ break;
+ case KEY_DOWN:
+ {
+ if ( pImpVclMEdit->GetVScrollBar().IsVisible() )
+ pImpVclMEdit->GetVScrollBar().DoScrollAction( ScrollType::LineDown );
+ }
+ break;
+ case KEY_PAGEUP :
+ {
+ if ( pImpVclMEdit->GetVScrollBar().IsVisible() )
+ pImpVclMEdit->GetVScrollBar().DoScrollAction( ScrollType::PageUp );
+ }
+ break;
+ case KEY_PAGEDOWN:
+ {
+ if ( pImpVclMEdit->GetVScrollBar().IsVisible() )
+ pImpVclMEdit->GetVScrollBar().DoScrollAction( ScrollType::PageDown );
+ }
+ break;
+ case KEY_LEFT:
+ {
+ if ( pImpVclMEdit->GetHScrollBar().IsVisible() )
+ pImpVclMEdit->GetHScrollBar().DoScrollAction( ScrollType::LineUp );
+ }
+ break;
+ case KEY_RIGHT:
+ {
+ if ( pImpVclMEdit->GetHScrollBar().IsVisible() )
+ pImpVclMEdit->GetHScrollBar().DoScrollAction( ScrollType::LineDown );
+ }
+ break;
+ case KEY_HOME:
+ {
+ if ( rKEvent.GetKeyCode().IsMod1() )
+ pImpVclMEdit->GetTextWindow()->GetTextView()->
+ SetSelection( TextSelection( TextPaM( 0, 0 ) ) );
+ }
+ break;
+ case KEY_END:
+ {
+ if ( rKEvent.GetKeyCode().IsMod1() )
+ pImpVclMEdit->GetTextWindow()->GetTextView()->
+ SetSelection( TextSelection( TextPaM( TEXT_PARA_ALL, TEXT_INDEX_ALL ) ) );
+ }
+ break;
+ default:
+ {
+ bDone = false;
+ }
+ }
+ }
+ }
+ }
+
+ return bDone || Edit::PreNotify( rNEvt );
+}
+
+// Internals for derived classes, e.g. TextComponent
+
+ExtTextEngine* VclMultiLineEdit::GetTextEngine() const
+{
+ return pImpVclMEdit->GetTextWindow()->GetTextEngine();
+}
+
+TextView* VclMultiLineEdit::GetTextView() const
+{
+ return pImpVclMEdit->GetTextWindow()->GetTextView();
+}
+
+ScrollBar& VclMultiLineEdit::GetVScrollBar() const
+{
+ return pImpVclMEdit->GetVScrollBar();
+}
+
+void VclMultiLineEdit::EnableFocusSelectionHide( bool bHide )
+{
+ pImpVclMEdit->GetTextWindow()->SetAutoFocusHide( bHide );
+}
+
+void VclMultiLineEdit::DisableSelectionOnFocus()
+{
+ pImpVclMEdit->GetTextWindow()->DisableSelectionOnFocus();
+}
+
+void VclMultiLineEdit::EnableCursor( bool bEnable )
+{
+ GetTextView()->EnableCursor( bEnable );
+}
+
+bool VclMultiLineEdit::CanUp() const
+{
+ TextView* pTextView = GetTextView();
+ const TextSelection& rTextSelection = pTextView->GetSelection();
+ TextPaM aPaM(rTextSelection.GetEnd());
+ return aPaM != pTextView->CursorUp(aPaM);
+}
+
+bool VclMultiLineEdit::CanDown() const
+{
+ TextView* pTextView = GetTextView();
+ const TextSelection& rTextSelection = pTextView->GetSelection();
+ TextPaM aPaM(rTextSelection.GetEnd());
+ return aPaM != pTextView->CursorDown(aPaM);
+}
+
+TextWindow* VclMultiLineEdit::GetTextWindow()
+{
+ return pImpVclMEdit->GetTextWindow();
+}
+
+FactoryFunction VclMultiLineEdit::GetUITestFactory() const
+{
+ return MultiLineEditUIObject::create;
+}
+
+bool VclMultiLineEdit::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "cursor-visible")
+ EnableCursor(toBool(rValue));
+ else if (rKey == "accepts-tab")
+ pImpVclMEdit->GetTextWindow()->SetIgnoreTab(!toBool(rValue));
+ else
+ return Edit::set_property(rKey, rValue);
+ return true;
+}
+
+void VclMultiLineEdit::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Edit::DumpAsPropertyTree(rJsonWriter);
+
+ rJsonWriter.put("cursor", pImpVclMEdit->GetTextWindow()->GetTextView()->IsCursorEnabled());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/edit/xtextedt.cxx b/vcl/source/edit/xtextedt.cxx
new file mode 100644
index 000000000..106ad69bf
--- /dev/null
+++ b/vcl/source/edit/xtextedt.cxx
@@ -0,0 +1,227 @@
+/* -*- 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 <i18nutil/searchopt.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <vcl/textdata.hxx>
+#include <vcl/xtextedt.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <unotools/textsearch.hxx>
+#include <com/sun/star/util/SearchFlags.hpp>
+
+using namespace ::com::sun::star;
+
+const std::wstring gaGroupChars = L"(){}[]";
+
+ExtTextEngine::ExtTextEngine()
+{
+}
+
+ExtTextEngine::~ExtTextEngine()
+{
+}
+
+TextSelection ExtTextEngine::MatchGroup( const TextPaM& rCursor ) const
+{
+ TextSelection aSel( rCursor );
+ const sal_Int32 nPos = rCursor.GetIndex();
+ sal_uInt32 nPara = rCursor.GetPara();
+ const sal_uInt32 nParas = GetParagraphCount();
+ if ( ( nPara < nParas ) && ( nPos < GetTextLen( nPara ) ) )
+ {
+ size_t nMatchIndex = gaGroupChars.find( GetText( rCursor.GetPara() )[ nPos ] );
+ if ( nMatchIndex != std::wstring::npos )
+ {
+ if ( ( nMatchIndex % 2 ) == 0 )
+ {
+ // search forwards
+ sal_Unicode nSC = gaGroupChars[ nMatchIndex ];
+ sal_Unicode nEC = gaGroupChars[ nMatchIndex+1 ];
+
+ sal_Int32 nCur = nPos+1;
+ sal_uInt16 nLevel = 1;
+ while ( nLevel && ( nPara < nParas ) )
+ {
+ OUString aStr = GetText( nPara );
+ while ( nCur < aStr.getLength() )
+ {
+ if ( aStr[nCur] == nSC )
+ nLevel++;
+ else if ( aStr[nCur] == nEC )
+ {
+ nLevel--;
+ if ( !nLevel )
+ break; // while nCur...
+ }
+ nCur++;
+ }
+
+ if ( nLevel )
+ {
+ nPara++;
+ nCur = 0;
+ }
+ }
+ if ( nLevel == 0 ) // found
+ {
+ aSel.GetStart() = rCursor;
+ aSel.GetEnd() = TextPaM( nPara, nCur+1 );
+ }
+ }
+ else
+ {
+ // search backwards
+ sal_Unicode nEC = gaGroupChars[ nMatchIndex ];
+ sal_Unicode nSC = gaGroupChars[ nMatchIndex-1 ];
+
+ sal_Int32 nCur = rCursor.GetIndex()-1;
+ sal_uInt16 nLevel = 1;
+ while ( nLevel )
+ {
+ if ( GetTextLen( nPara ) )
+ {
+ OUString aStr = GetText( nPara );
+ while ( nCur )
+ {
+ if ( aStr[nCur] == nSC )
+ {
+ nLevel--;
+ if ( !nLevel )
+ break; // while nCur...
+ }
+ else if ( aStr[nCur] == nEC )
+ nLevel++;
+
+ nCur--;
+ }
+ }
+
+ if ( nLevel )
+ {
+ if ( nPara )
+ {
+ nPara--;
+ nCur = GetTextLen( nPara )-1; // no matter if negative, as if Len()
+ }
+ else
+ break;
+ }
+ }
+
+ if ( nLevel == 0 ) // found
+ {
+ aSel.GetStart() = rCursor;
+ ++aSel.GetStart().GetIndex(); // behind the char
+ aSel.GetEnd() = TextPaM( nPara, nCur );
+ }
+ }
+ }
+ }
+ return aSel;
+}
+
+bool ExtTextEngine::Search( TextSelection& rSel, const i18nutil::SearchOptions& rSearchOptions, bool bForward ) const
+{
+ TextSelection aSel( rSel );
+ aSel.Justify();
+
+ bool bSearchInSelection = (0 != (rSearchOptions.searchFlag & util::SearchFlags::REG_NOT_BEGINOFLINE) );
+
+ TextPaM aStartPaM( aSel.GetEnd() );
+ if ( aSel.HasRange() && ( ( bSearchInSelection && bForward ) || ( !bSearchInSelection && !bForward ) ) )
+ {
+ aStartPaM = aSel.GetStart();
+ }
+
+ bool bFound = false;
+ sal_uInt32 nEndNode;
+
+ if ( bSearchInSelection )
+ nEndNode = bForward ? aSel.GetEnd().GetPara() : aSel.GetStart().GetPara();
+ else
+ nEndNode = bForward ? (GetParagraphCount()-1) : 0;
+
+ const sal_uInt32 nStartNode = aStartPaM.GetPara();
+
+ i18nutil::SearchOptions aOptions( rSearchOptions );
+ aOptions.Locale = Application::GetSettings().GetLanguageTag().getLocale();
+ utl::TextSearch aSearcher( utl::TextSearch::UpgradeToSearchOptions2(aOptions) );
+
+ // iterate over the paragraphs
+ for ( sal_uInt32 nNode = nStartNode;
+ bForward ? ( nNode <= nEndNode) : ( nNode >= nEndNode );
+ bForward ? nNode++ : nNode-- )
+ {
+ OUString aText = GetText( nNode );
+ sal_Int32 nStartPos = 0;
+ sal_Int32 nEndPos = aText.getLength();
+ if ( nNode == nStartNode )
+ {
+ if ( bForward )
+ nStartPos = aStartPaM.GetIndex();
+ else
+ nEndPos = aStartPaM.GetIndex();
+ }
+ if ( ( nNode == nEndNode ) && bSearchInSelection )
+ {
+ if ( bForward )
+ nEndPos = aSel.GetEnd().GetIndex();
+ else
+ nStartPos = aSel.GetStart().GetIndex();
+ }
+
+ if ( bForward )
+ bFound = aSearcher.SearchForward( aText, &nStartPos, &nEndPos );
+ else
+ bFound = aSearcher.SearchBackward( aText, &nEndPos, &nStartPos );
+
+ if ( bFound )
+ {
+ rSel.GetStart().GetPara() = nNode;
+ rSel.GetStart().GetIndex() = nStartPos;
+ rSel.GetEnd().GetPara() = nNode;
+ rSel.GetEnd().GetIndex() = nEndPos;
+ // Select over the paragraph?
+ // FIXME This should be max long...
+ if( nEndPos == -1)
+ {
+ if ( (rSel.GetEnd().GetPara()+1) < GetParagraphCount() )
+ {
+ rSel.GetEnd().GetPara()++;
+ rSel.GetEnd().GetIndex() = 0;
+ }
+ else
+ {
+ rSel.GetEnd().GetIndex() = nStartPos;
+ bFound = false;
+ }
+ }
+
+ break;
+ }
+
+ if ( !bForward && !nNode ) // if searching backwards, if nEndNode == 0:
+ break;
+ }
+
+ return bFound;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/FilterConfigCache.cxx b/vcl/source/filter/FilterConfigCache.cxx
new file mode 100644
index 000000000..9f3668ccc
--- /dev/null
+++ b/vcl/source/filter/FilterConfigCache.cxx
@@ -0,0 +1,489 @@
+/* -*- 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 "FilterConfigCache.hxx"
+
+#include <o3tl/safeint.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <unotools/configmgr.hxx>
+#include <tools/svlibrary.h>
+#include <com/sun/star/uno/Any.h>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <com/sun/star/uno/Exception.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+
+using namespace ::com::sun::star::lang ; // XMultiServiceFactory
+using namespace ::com::sun::star::container ; // XNameAccess
+using namespace ::com::sun::star::uno ; // Reference
+using namespace ::com::sun::star::beans ; // PropertyValue
+using namespace ::com::sun::star::configuration ;
+
+const char* FilterConfigCache::FilterConfigCacheEntry::InternalPixelFilterNameList[] =
+{
+ IMP_BMP, IMP_GIF, IMP_PNG, IMP_JPEG, IMP_TIFF, IMP_WEBP,
+ IMP_XBM, IMP_XPM, IMP_TGA, IMP_PICT, IMP_MET, IMP_RAS,
+ IMP_PCX, IMP_MOV, IMP_PSD, IMP_PCD, IMP_PBM, IMP_DXF,
+ EXP_BMP, EXP_GIF, EXP_PNG, EXP_JPEG, EXP_TIFF, EXP_WEBP,
+ nullptr
+};
+
+void FilterConfigCache::FilterConfigCacheEntry::CreateFilterName( const OUString& rUserDataEntry )
+{
+ bIsPixelFormat = false;
+ sFilterName = rUserDataEntry;
+ const char** pPtr;
+ for ( pPtr = InternalPixelFilterNameList; *pPtr; pPtr++ )
+ {
+ if ( sFilterName.equalsIgnoreAsciiCaseAscii( *pPtr ) )
+ {
+ bIsPixelFormat = true;
+ }
+ }
+}
+
+OUString FilterConfigCache::FilterConfigCacheEntry::GetShortName()
+{
+ OUString aShortName;
+ if ( !lExtensionList.empty() )
+ {
+ aShortName = lExtensionList[ 0 ];
+ if ( aShortName.startsWith( "*." ) )
+ aShortName = aShortName.replaceAt( 0, 2, u"" );
+ }
+ return aShortName;
+}
+
+/** helper to open the configuration root of the underlying
+ config package
+
+ @param sPackage
+ specify, which config package should be opened.
+ Must be one of "types" or "filters"
+
+ @return A valid object if open was successful. The access on opened
+ data will be readonly. It returns NULL in case open failed.
+
+ @throws It let pass RuntimeExceptions only.
+ */
+static Reference< XInterface > openConfig(const char* sPackage)
+{
+ Reference< XComponentContext > xContext(
+ comphelper::getProcessComponentContext() );
+ Reference< XInterface > xCfg;
+ try
+ {
+ // get access to config API (not to file!)
+ Reference< XMultiServiceFactory > xConfigProvider = theDefaultProvider::get( xContext );
+
+ PropertyValue aParam ;
+
+ // define cfg path for open
+ aParam.Name = "nodepath";
+ if (rtl_str_compareIgnoreAsciiCase(sPackage, "types") == 0)
+ aParam.Value <<= OUString( "/org.openoffice.TypeDetection.Types/Types" );
+ if (rtl_str_compareIgnoreAsciiCase(sPackage, "filters") == 0)
+ aParam.Value <<= OUString( "/org.openoffice.TypeDetection.GraphicFilter/Filters" );
+ Sequence< Any > lParams{ Any(aParam) };
+
+ // get access to file
+ xCfg = xConfigProvider->createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess", lParams);
+ }
+ catch(const RuntimeException&)
+ { throw; }
+ catch(const Exception&)
+ { xCfg.clear(); }
+
+ return xCfg;
+}
+
+void FilterConfigCache::ImplInit()
+{
+ OUString const STYPE ( "Type" );
+ OUString const SUINAME ( "UIName" );
+ OUString const SFLAGS ( "Flags" );
+ OUString const SMEDIATYPE ( "MediaType" );
+ OUString const SEXTENSIONS ( "Extensions" );
+ OUString const SFORMATNAME ( "FormatName" );
+ OUString const SREALFILTERNAME ( "RealFilterName" );
+
+ // get access to config
+ Reference< XNameAccess > xTypeAccess ( openConfig("types" ), UNO_QUERY );
+ Reference< XNameAccess > xFilterAccess( openConfig("filters"), UNO_QUERY );
+
+ if ( !(xTypeAccess.is() && xFilterAccess.is()) )
+ return;
+
+ const Sequence< OUString > lAllFilter = xFilterAccess->getElementNames();
+
+ for ( const OUString& sInternalFilterName : lAllFilter )
+ {
+ Reference< XPropertySet > xFilterSet;
+ xFilterAccess->getByName( sInternalFilterName ) >>= xFilterSet;
+ if (!xFilterSet.is())
+ continue;
+
+ FilterConfigCacheEntry aEntry;
+
+ aEntry.sInternalFilterName = sInternalFilterName;
+ xFilterSet->getPropertyValue(STYPE) >>= aEntry.sType;
+ xFilterSet->getPropertyValue(SUINAME) >>= aEntry.sUIName;
+ xFilterSet->getPropertyValue(SREALFILTERNAME) >>= aEntry.sFilterType;
+ Sequence< OUString > lFlags;
+ xFilterSet->getPropertyValue(SFLAGS) >>= lFlags;
+ if (lFlags.getLength()!=1 || lFlags[0].isEmpty())
+ continue;
+ if (lFlags[0].equalsIgnoreAsciiCase("import"))
+ aEntry.nFlags = 1;
+ else if (lFlags[0].equalsIgnoreAsciiCase("export"))
+ aEntry.nFlags = 2;
+ else
+ aEntry.nFlags = 0;
+
+ OUString sFormatName;
+ xFilterSet->getPropertyValue(SFORMATNAME) >>= sFormatName;
+ aEntry.CreateFilterName( sFormatName );
+
+ Reference< XPropertySet > xTypeSet;
+ xTypeAccess->getByName( aEntry.sType ) >>= xTypeSet;
+ if (!xTypeSet.is())
+ continue;
+
+ xTypeSet->getPropertyValue(SMEDIATYPE) >>= aEntry.sMediaType;
+ css::uno::Sequence<OUString> tmp;
+ if (xTypeSet->getPropertyValue(SEXTENSIONS) >>= tmp)
+ aEntry.lExtensionList = comphelper::sequenceToContainer<std::vector<OUString>>(tmp);
+
+ // The first extension will be used
+ // to generate our internal FilterType ( BMP, WMF ... )
+ OUString aExtension( aEntry.GetShortName() );
+ if (aExtension.isEmpty())
+ continue;
+
+ if ( aEntry.nFlags & 1 )
+ aImport.push_back( aEntry );
+ if ( aEntry.nFlags & 2 )
+ aExport.push_back( aEntry );
+
+ // bFilterEntryCreated!?
+ if (!( aEntry.nFlags & 3 ))
+ continue; //? Entry was already inserted ... but following code will be suppressed?!
+ }
+};
+
+const char* FilterConfigCache::InternalFilterListForSvxLight[] =
+{
+ "bmp","1","SVBMP",
+ "bmp","2","SVBMP",
+ "dxf","1","SVDXF",
+ "eps","1","SVIEPS",
+ "eps","2","SVEEPS",
+ "gif","1","SVIGIF",
+ "gif","2","SVEGIF",
+ "jpg","1","SVIJPEG",
+ "jpg","2","SVEJPEG",
+ "mov","1","SVMOV",
+ "mov","2","SVMOV",
+ "met","1","SVMET",
+ "png","1","SVIPNG",
+ "png","2","SVEPNG",
+ "pct","1","SVPICT",
+ "pcd","1","SVPCD",
+ "psd","1","SVPSD",
+ "pcx","1","SVPCX",
+ "pbm","1","SVPBM",
+ "pgm","1","SVPBM",
+ "ppm","1","SVPBM",
+ "ras","1","SVRAS",
+ "svm","1","SVMETAFILE",
+ "svm","2","SVMETAFILE",
+ "tga","1","SVTGA",
+ "tif","1","SVTIFF",
+ "tif","2","SVTIFF",
+ "emf","1","SVEMF",
+ "emf","2","SVEMF",
+ "wmf","1","SVWMF",
+ "wmf","2","SVWMF",
+ "xbm","1","SVIXBM",
+ "xpm","1","SVIXPM",
+ "svg","1","SVISVG",
+ "svg","2","SVESVG",
+ "webp","1","SVIWEBP",
+ "webp","2","SVEWEBP",
+ nullptr
+};
+
+void FilterConfigCache::ImplInitSmart()
+{
+ const char** pPtr;
+ for ( pPtr = InternalFilterListForSvxLight; *pPtr; pPtr++ )
+ {
+ FilterConfigCacheEntry aEntry;
+
+ OUString sExtension( OUString::createFromAscii( *pPtr++ ) );
+
+ aEntry.lExtensionList.push_back(sExtension);
+
+ aEntry.sType = sExtension;
+ aEntry.sUIName = sExtension;
+
+ OString sFlags( *pPtr++ );
+ aEntry.nFlags = sFlags.toInt32();
+
+ OUString sUserData( OUString::createFromAscii( *pPtr ) );
+ aEntry.CreateFilterName( sUserData );
+
+ if ( aEntry.nFlags & 1 )
+ aImport.push_back( aEntry );
+ if ( aEntry.nFlags & 2 )
+ aExport.push_back( aEntry );
+ }
+}
+
+FilterConfigCache::FilterConfigCache(bool bConfig)
+{
+ if (bConfig)
+ bConfig = !utl::ConfigManager::IsFuzzing();
+ if (bConfig)
+ ImplInit();
+ else
+ ImplInitSmart();
+}
+
+FilterConfigCache::~FilterConfigCache()
+{
+}
+
+OUString FilterConfigCache::GetImportFilterName( sal_uInt16 nFormat )
+{
+ if( nFormat < aImport.size() )
+ return aImport[ nFormat ].sFilterName;
+ return OUString();
+}
+
+sal_uInt16 FilterConfigCache::GetImportFormatNumber( std::u16string_view rFormatName )
+{
+ sal_uInt16 nPos = 0;
+ for (auto const& elem : aImport)
+ {
+ if ( elem.sUIName.equalsIgnoreAsciiCase( rFormatName ) )
+ return nPos;
+ ++nPos;
+ }
+ return GRFILTER_FORMAT_NOTFOUND;
+}
+
+/// get the index of the filter that matches this extension
+sal_uInt16 FilterConfigCache::GetImportFormatNumberForExtension( std::u16string_view rExt )
+{
+ sal_uInt16 nPos = 0;
+ for (auto const& elem : aImport)
+ {
+ for ( OUString const & s : elem.lExtensionList )
+ {
+ if ( s.equalsIgnoreAsciiCase( rExt ) )
+ return nPos;
+ }
+ ++nPos;
+ }
+ return GRFILTER_FORMAT_NOTFOUND;
+}
+
+sal_uInt16 FilterConfigCache::GetImportFormatNumberForShortName( std::u16string_view rShortName )
+{
+ sal_uInt16 nPos = 0;
+ for (auto & elem : aImport)
+ {
+ if ( elem.GetShortName().equalsIgnoreAsciiCase( rShortName ) )
+ return nPos;
+ ++nPos;
+ }
+ return GRFILTER_FORMAT_NOTFOUND;
+}
+
+sal_uInt16 FilterConfigCache::GetImportFormatNumberForTypeName( std::u16string_view rType )
+{
+ sal_uInt16 nPos = 0;
+ for (auto const& elem : aImport)
+ {
+ if ( elem.sType.equalsIgnoreAsciiCase( rType ) )
+ return nPos;
+ ++nPos;
+ }
+ return GRFILTER_FORMAT_NOTFOUND;
+}
+
+OUString FilterConfigCache::GetImportFormatName( sal_uInt16 nFormat )
+{
+ if( nFormat < aImport.size() )
+ return aImport[ nFormat ].sUIName;
+ return OUString();
+}
+
+OUString FilterConfigCache::GetImportFormatMediaType( sal_uInt16 nFormat )
+{
+ if( nFormat < aImport.size() )
+ return aImport[ nFormat ].sMediaType;
+ return OUString();
+}
+
+OUString FilterConfigCache::GetImportFormatShortName( sal_uInt16 nFormat )
+{
+ if( nFormat < aImport.size() )
+ return aImport[ nFormat ].GetShortName();
+ return OUString();
+}
+
+OUString FilterConfigCache::GetImportFormatExtension( sal_uInt16 nFormat, sal_Int32 nEntry )
+{
+ if ( (nFormat < aImport.size()) && (o3tl::make_unsigned(nEntry) < aImport[ nFormat ].lExtensionList.size()) )
+ return aImport[ nFormat ].lExtensionList[ nEntry ];
+ return OUString();
+}
+
+OUString FilterConfigCache::GetImportFilterType( sal_uInt16 nFormat )
+{
+ if( nFormat < aImport.size() )
+ return aImport[ nFormat ].sType;
+ return OUString();
+}
+
+OUString FilterConfigCache::GetImportFilterTypeName( sal_uInt16 nFormat )
+{
+ if( nFormat < aImport.size() )
+ return aImport[ nFormat ].sFilterType;
+ return OUString();
+}
+
+OUString FilterConfigCache::GetImportWildcard(sal_uInt16 nFormat, sal_Int32 nEntry)
+{
+ OUString aWildcard( GetImportFormatExtension( nFormat, nEntry ) );
+ if ( !aWildcard.isEmpty() )
+ aWildcard = aWildcard.replaceAt( 0, 0, u"*." );
+ return aWildcard;
+}
+
+OUString FilterConfigCache::GetExportFilterName( sal_uInt16 nFormat )
+{
+ if( nFormat < aExport.size() )
+ return aExport[ nFormat ].sFilterName;
+ return OUString();
+}
+
+sal_uInt16 FilterConfigCache::GetExportFormatNumber(std::u16string_view rFormatName)
+{
+ sal_uInt16 nPos = 0;
+ for (auto const& elem : aExport)
+ {
+ if ( elem.sUIName.equalsIgnoreAsciiCase( rFormatName ) )
+ return nPos;
+ ++nPos;
+ }
+ return GRFILTER_FORMAT_NOTFOUND;
+}
+
+sal_uInt16 FilterConfigCache::GetExportFormatNumberForMediaType( std::u16string_view rMediaType )
+{
+ sal_uInt16 nPos = 0;
+ for (auto const& elem : aExport)
+ {
+ if ( elem.sMediaType.equalsIgnoreAsciiCase( rMediaType ) )
+ return nPos;
+ ++nPos;
+ }
+ return GRFILTER_FORMAT_NOTFOUND;
+}
+
+sal_uInt16 FilterConfigCache::GetExportFormatNumberForShortName( std::u16string_view rShortName )
+{
+ sal_uInt16 nPos = 0;
+ for (auto & elem : aExport)
+ {
+ if ( elem.GetShortName().equalsIgnoreAsciiCase( rShortName ) )
+ return nPos;
+ ++nPos;
+ }
+ return GRFILTER_FORMAT_NOTFOUND;
+}
+
+sal_uInt16 FilterConfigCache::GetExportFormatNumberForTypeName( std::u16string_view rType )
+{
+ sal_uInt16 nPos = 0;
+ for (auto const& elem : aExport)
+ {
+ if ( elem.sType.equalsIgnoreAsciiCase( rType ) )
+ return nPos;
+ ++nPos;
+ }
+ return GRFILTER_FORMAT_NOTFOUND;
+}
+
+OUString FilterConfigCache::GetExportFormatName( sal_uInt16 nFormat )
+{
+ if( nFormat < aExport.size() )
+ return aExport[ nFormat ].sUIName;
+ return OUString();
+}
+
+OUString FilterConfigCache::GetExportFormatMediaType( sal_uInt16 nFormat )
+{
+ if( nFormat < aExport.size() )
+ return aExport[ nFormat ].sMediaType;
+ return OUString();
+}
+
+OUString FilterConfigCache::GetExportFormatShortName( sal_uInt16 nFormat )
+{
+ if( nFormat < aExport.size() )
+ return aExport[ nFormat ].GetShortName();
+ return OUString();
+}
+
+OUString FilterConfigCache::GetExportFormatExtension( sal_uInt16 nFormat, sal_Int32 nEntry )
+{
+ if ( (nFormat < aExport.size()) && (o3tl::make_unsigned(nEntry) < aExport[ nFormat ].lExtensionList.size()) )
+ return aExport[ nFormat ].lExtensionList[ nEntry ];
+ return OUString();
+}
+
+OUString FilterConfigCache::GetExportInternalFilterName( sal_uInt16 nFormat )
+{
+ if( nFormat < aExport.size() )
+ return aExport[ nFormat ].sInternalFilterName;
+ return OUString();
+}
+
+OUString FilterConfigCache::GetExportWildcard( sal_uInt16 nFormat, sal_Int32 nEntry )
+{
+ OUString aWildcard( GetExportFormatExtension( nFormat, nEntry ) );
+ if ( !aWildcard.isEmpty() )
+ aWildcard = aWildcard.replaceAt( 0, 0, u"*." );
+ return aWildcard;
+}
+
+bool FilterConfigCache::IsExportPixelFormat( sal_uInt16 nFormat )
+{
+ return (nFormat < aExport.size()) && aExport[ nFormat ].bIsPixelFormat;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/FilterConfigCache.hxx b/vcl/source/filter/FilterConfigCache.hxx
new file mode 100644
index 000000000..f398a58ff
--- /dev/null
+++ b/vcl/source/filter/FilterConfigCache.hxx
@@ -0,0 +1,100 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_FILTERCONFIGCACHE_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_FILTERCONFIGCACHE_HXX
+
+#include <rtl/ustring.hxx>
+#include <vector>
+
+/** Cache to keep list of graphic filters + the filters themselves. */
+class FilterConfigCache
+{
+ struct FilterConfigCacheEntry
+ {
+ OUString sInternalFilterName;
+ OUString sType;
+ std::vector< OUString > lExtensionList;
+ OUString sUIName;
+
+ OUString sMediaType;
+ OUString sFilterType;
+
+ sal_Int32 nFlags;
+
+ // user data
+ OUString sFilterName;
+ bool bIsPixelFormat : 1;
+
+ void CreateFilterName( const OUString& rUserDataEntry );
+ OUString GetShortName( );
+
+ static const char* InternalPixelFilterNameList[];
+ };
+
+
+ std::vector< FilterConfigCacheEntry > aImport;
+ std::vector< FilterConfigCacheEntry > aExport;
+
+ static const char* InternalFilterListForSvxLight[];
+
+ void ImplInit();
+ void ImplInitSmart();
+
+public:
+
+ sal_uInt16 GetImportFormatCount() const
+ { return sal::static_int_cast< sal_uInt16 >(aImport.size()); };
+ sal_uInt16 GetImportFormatNumber( std::u16string_view rFormatName );
+ sal_uInt16 GetImportFormatNumberForShortName( std::u16string_view rShortName );
+ sal_uInt16 GetImportFormatNumberForTypeName( std::u16string_view rType );
+ sal_uInt16 GetImportFormatNumberForExtension( std::u16string_view rExt );
+ OUString GetImportFilterName( sal_uInt16 nFormat );
+ OUString GetImportFormatName( sal_uInt16 nFormat );
+ OUString GetImportFormatExtension( sal_uInt16 nFormat, sal_Int32 nEntry = 0);
+ OUString GetImportFormatMediaType( sal_uInt16 nFormat );
+ OUString GetImportFormatShortName( sal_uInt16 nFormat );
+ OUString GetImportWildcard( sal_uInt16 nFormat, sal_Int32 nEntry );
+ OUString GetImportFilterType( sal_uInt16 nFormat );
+ OUString GetImportFilterTypeName( sal_uInt16 nFormat );
+
+
+ sal_uInt16 GetExportFormatCount() const
+ { return sal::static_int_cast< sal_uInt16 >(aExport.size()); };
+ sal_uInt16 GetExportFormatNumber( std::u16string_view rFormatName );
+ sal_uInt16 GetExportFormatNumberForMediaType( std::u16string_view rMediaType );
+ sal_uInt16 GetExportFormatNumberForShortName( std::u16string_view rShortName );
+ sal_uInt16 GetExportFormatNumberForTypeName( std::u16string_view rType );
+ OUString GetExportFilterName( sal_uInt16 nFormat );
+ OUString GetExportFormatName( sal_uInt16 nFormat );
+ OUString GetExportFormatExtension( sal_uInt16 nFormat, sal_Int32 nEntry = 0 );
+ OUString GetExportFormatMediaType( sal_uInt16 nFormat );
+ OUString GetExportFormatShortName( sal_uInt16 nFormat );
+ OUString GetExportWildcard( sal_uInt16 nFormat, sal_Int32 nEntry );
+ OUString GetExportInternalFilterName( sal_uInt16 nFormat );
+
+ bool IsExportPixelFormat( sal_uInt16 nFormat );
+
+ explicit FilterConfigCache( bool bUseConfig );
+ ~FilterConfigCache();
+};
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_FILTERCONFIGCACHE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/FilterConfigItem.cxx b/vcl/source/filter/FilterConfigItem.cxx
new file mode 100644
index 000000000..962d970bf
--- /dev/null
+++ b/vcl/source/filter/FilterConfigItem.cxx
@@ -0,0 +1,389 @@
+/* -*- 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 <vcl/FilterConfigItem.hxx>
+
+#include <unotools/configmgr.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <osl/diagnose.h>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/util/XChangesBatch.hpp>
+#include <com/sun/star/beans/XPropertySetInfo.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+
+using namespace ::com::sun::star::lang ; // XMultiServiceFactory
+using namespace ::com::sun::star::beans ; // PropertyValue
+using namespace ::com::sun::star::uno ; // Reference
+using namespace ::com::sun::star::util ; // XChangesBatch
+using namespace ::com::sun::star::awt ; // Size
+using namespace ::com::sun::star::container ;
+using namespace ::com::sun::star::configuration;
+using namespace ::com::sun::star::task ; // XStatusIndicator
+
+static bool ImpIsTreeAvailable( Reference< XMultiServiceFactory > const & rXCfgProv, const OUString& rTree )
+{
+ bool bAvailable = !rTree.isEmpty();
+ if ( bAvailable )
+ {
+ sal_Int32 nIdx{0};
+ if ( rTree[0] == '/' )
+ ++nIdx;
+
+ // creation arguments: nodepath
+ PropertyValue aPathArgument = comphelper::makePropertyValue("nodepath",
+ rTree.getToken(0, '/', nIdx));
+ Sequence< Any > aArguments{ Any(aPathArgument) };
+
+ Reference< XInterface > xReadAccess;
+ try
+ {
+ xReadAccess = rXCfgProv->createInstanceWithArguments(
+ "com.sun.star.configuration.ConfigurationAccess",
+ aArguments );
+ }
+ catch (const css::uno::Exception&)
+ {
+ bAvailable = false;
+ }
+ if ( xReadAccess.is() )
+ {
+ const sal_Int32 nEnd {rTree.getLength()};
+ while (bAvailable && nIdx>=0 && nIdx<nEnd)
+ {
+ Reference< XHierarchicalNameAccess > xHierarchicalNameAccess
+ ( xReadAccess, UNO_QUERY );
+
+ if ( !xHierarchicalNameAccess.is() )
+ bAvailable = false;
+ else
+ {
+ const OUString aNode( rTree.getToken(0, '/', nIdx) );
+ if ( !xHierarchicalNameAccess->hasByHierarchicalName( aNode ) )
+ bAvailable = false;
+ else
+ {
+ Any a( xHierarchicalNameAccess->getByHierarchicalName( aNode ) );
+ bAvailable = (a >>= xReadAccess);
+ }
+ }
+ }
+ }
+ }
+ return bAvailable;
+}
+
+void FilterConfigItem::ImpInitTree( std::u16string_view rSubTree )
+{
+ bModified = false;
+
+ Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() );
+
+ Reference< XMultiServiceFactory > xCfgProv = theDefaultProvider::get( xContext );
+
+ OUString sTree = OUString::Concat("/org.openoffice.") + rSubTree;
+ if ( !ImpIsTreeAvailable(xCfgProv, sTree) )
+ return;
+
+ // creation arguments: nodepath
+ PropertyValue aPathArgument;
+ aPathArgument.Name = "nodepath";
+ aPathArgument.Value <<= sTree;
+
+ Sequence< Any > aArguments{ Any(aPathArgument) };
+
+ try
+ {
+ xUpdatableView = xCfgProv->createInstanceWithArguments(
+ "com.sun.star.configuration.ConfigurationUpdateAccess",
+ aArguments );
+ if ( xUpdatableView.is() )
+ xPropSet.set( xUpdatableView, UNO_QUERY );
+ }
+ catch ( css::uno::Exception& )
+ {
+ OSL_FAIL( "FilterConfigItem::FilterConfigItem - Could not access configuration Key" );
+ }
+}
+
+FilterConfigItem::FilterConfigItem( std::u16string_view rSubTree )
+{
+ ImpInitTree( rSubTree );
+}
+
+FilterConfigItem::FilterConfigItem( css::uno::Sequence< css::beans::PropertyValue > const * pFilterData )
+ : bModified(false)
+{
+ if ( pFilterData )
+ aFilterData = *pFilterData;
+}
+
+FilterConfigItem::FilterConfigItem( std::u16string_view rSubTree,
+ css::uno::Sequence< css::beans::PropertyValue > const * pFilterData )
+{
+ ImpInitTree( rSubTree );
+
+ if ( pFilterData )
+ aFilterData = *pFilterData;
+};
+
+FilterConfigItem::~FilterConfigItem()
+{
+ WriteModifiedConfig();
+}
+
+void FilterConfigItem::WriteModifiedConfig()
+{
+ if ( !xUpdatableView.is() )
+ return;
+
+ if ( !(xPropSet.is() && bModified) )
+ return;
+
+ Reference< XChangesBatch > xUpdateControl( xUpdatableView, UNO_QUERY );
+ if ( xUpdateControl.is() )
+ {
+ try
+ {
+ xUpdateControl->commitChanges();
+ bModified = false;
+ }
+ catch ( css::uno::Exception& )
+ {
+ OSL_FAIL( "FilterConfigItem::FilterConfigItem - Could not update configuration data" );
+ }
+ }
+}
+
+bool FilterConfigItem::ImplGetPropertyValue( Any& rAny, const Reference< XPropertySet >& rXPropSet, const OUString& rString )
+{
+ bool bRetValue = true;
+
+ if ( rXPropSet.is() )
+ {
+ bRetValue = false;
+ try
+ {
+ Reference< XPropertySetInfo >
+ aXPropSetInfo( rXPropSet->getPropertySetInfo() );
+ if ( aXPropSetInfo.is() )
+ bRetValue = aXPropSetInfo->hasPropertyByName( rString );
+ }
+ catch( css::uno::Exception& )
+ {
+ }
+ if ( bRetValue )
+ {
+ try
+ {
+ rAny = rXPropSet->getPropertyValue( rString );
+ if ( !rAny.hasValue() )
+ bRetValue = false;
+ }
+ catch( css::uno::Exception& )
+ {
+ bRetValue = false;
+ }
+ }
+ }
+ else
+ bRetValue = false;
+ return bRetValue;
+}
+
+// if property is available it returns a pointer,
+// otherwise the result is null
+const PropertyValue* FilterConfigItem::GetPropertyValue( const Sequence< PropertyValue >& rPropSeq, const OUString& rName )
+{
+ auto pProp = std::find_if(rPropSeq.begin(), rPropSeq.end(),
+ [&rName](const PropertyValue& rProp) { return rProp.Name == rName; });
+ if (pProp != rPropSeq.end())
+ return pProp;
+ return nullptr;
+}
+
+/* if PropertySequence already includes a PropertyValue using the same name, the
+ corresponding PropertyValue is replaced, otherwise the given PropertyValue
+ will be appended */
+
+bool FilterConfigItem::WritePropertyValue( Sequence< PropertyValue >& rPropSeq, const PropertyValue& rPropValue )
+{
+ bool bRet = false;
+ if ( !rPropValue.Name.isEmpty() )
+ {
+ auto pProp = std::find_if(std::cbegin(rPropSeq), std::cend(rPropSeq),
+ [&rPropValue](const PropertyValue& rProp) { return rProp.Name == rPropValue.Name; });
+ sal_Int32 i = std::distance(std::cbegin(rPropSeq), pProp);
+ sal_Int32 nCount = rPropSeq.getLength();
+ if ( i == nCount )
+ rPropSeq.realloc( ++nCount );
+
+ rPropSeq.getArray()[ i ] = rPropValue;
+
+ bRet = true;
+ }
+ return bRet;
+}
+
+bool FilterConfigItem::ReadBool( const OUString& rKey, bool bDefault )
+{
+ Any aAny;
+ bool bRetValue = bDefault;
+ const PropertyValue* pPropVal = GetPropertyValue( aFilterData, rKey );
+ if ( pPropVal )
+ {
+ pPropVal->Value >>= bRetValue;
+ }
+ else if ( ImplGetPropertyValue( aAny, xPropSet, rKey ) )
+ {
+ aAny >>= bRetValue;
+ }
+ PropertyValue aBool;
+ aBool.Name = rKey;
+ aBool.Value <<= bRetValue;
+ WritePropertyValue( aFilterData, aBool );
+ return bRetValue;
+}
+
+sal_Int32 FilterConfigItem::ReadInt32( const OUString& rKey, sal_Int32 nDefault )
+{
+ Any aAny;
+ sal_Int32 nRetValue = nDefault;
+ const PropertyValue* pPropVal = GetPropertyValue( aFilterData, rKey );
+ if ( pPropVal )
+ {
+ pPropVal->Value >>= nRetValue;
+ }
+ else if ( ImplGetPropertyValue( aAny, xPropSet, rKey ) )
+ {
+ aAny >>= nRetValue;
+ }
+ PropertyValue aInt32;
+ aInt32.Name = rKey;
+ aInt32.Value <<= nRetValue;
+ WritePropertyValue( aFilterData, aInt32 );
+ return nRetValue;
+}
+
+OUString FilterConfigItem::ReadString( const OUString& rKey, const OUString& rDefault )
+{
+ Any aAny;
+ OUString aRetValue( rDefault );
+ const PropertyValue* pPropVal = GetPropertyValue( aFilterData, rKey );
+ if ( pPropVal )
+ {
+ pPropVal->Value >>= aRetValue;
+ }
+ else if ( ImplGetPropertyValue( aAny, xPropSet, rKey ) )
+ {
+ aAny >>= aRetValue;
+ }
+ PropertyValue aString;
+ aString.Name = rKey;
+ aString.Value <<= aRetValue;
+ WritePropertyValue( aFilterData, aString );
+ return aRetValue;
+}
+
+void FilterConfigItem::WriteBool( const OUString& rKey, bool bNewValue )
+{
+ PropertyValue aBool;
+ aBool.Name = rKey;
+ aBool.Value <<= bNewValue;
+ WritePropertyValue( aFilterData, aBool );
+
+ if ( !xPropSet.is() )
+ return;
+
+ Any aAny;
+ if ( !ImplGetPropertyValue( aAny, xPropSet, rKey ) )
+ return;
+
+ bool bOldValue(true);
+ if ( !(aAny >>= bOldValue) )
+ return;
+
+ if ( bOldValue != bNewValue )
+ {
+ try
+ {
+ xPropSet->setPropertyValue( rKey, Any(bNewValue) );
+ bModified = true;
+ }
+ catch ( css::uno::Exception& )
+ {
+ OSL_FAIL( "FilterConfigItem::WriteBool - could not set PropertyValue" );
+ }
+ }
+}
+
+void FilterConfigItem::WriteInt32( const OUString& rKey, sal_Int32 nNewValue )
+{
+ PropertyValue aInt32;
+ aInt32.Name = rKey;
+ aInt32.Value <<= nNewValue;
+ WritePropertyValue( aFilterData, aInt32 );
+
+ if ( !xPropSet.is() )
+ return;
+
+ Any aAny;
+
+ if ( !ImplGetPropertyValue( aAny, xPropSet, rKey ) )
+ return;
+
+ sal_Int32 nOldValue = 0;
+ if ( !(aAny >>= nOldValue) )
+ return;
+
+ if ( nOldValue != nNewValue )
+ {
+ try
+ {
+ xPropSet->setPropertyValue( rKey, Any(nNewValue) );
+ bModified = true;
+ }
+ catch ( css::uno::Exception& )
+ {
+ OSL_FAIL( "FilterConfigItem::WriteInt32 - could not set PropertyValue" );
+ }
+ }
+}
+
+
+Reference< XStatusIndicator > FilterConfigItem::GetStatusIndicator() const
+{
+ Reference< XStatusIndicator > xStatusIndicator;
+
+ auto pPropVal = std::find_if(aFilterData.begin(), aFilterData.end(),
+ [](const css::beans::PropertyValue& rPropVal) {
+ return rPropVal.Name == "StatusIndicator"; });
+ if (pPropVal != aFilterData.end())
+ {
+ pPropVal->Value >>= xStatusIndicator;
+ }
+ return xStatusIndicator;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/GraphicFormatDetector.cxx b/vcl/source/filter/GraphicFormatDetector.cxx
new file mode 100644
index 000000000..6ef3d3015
--- /dev/null
+++ b/vcl/source/filter/GraphicFormatDetector.cxx
@@ -0,0 +1,859 @@
+/* -*- 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 <algorithm>
+
+#include <graphic/GraphicFormatDetector.hxx>
+#include <graphic/DetectorTools.hxx>
+#include <tools/solar.h>
+#include <tools/zcodec.hxx>
+
+constexpr sal_uInt32 SVG_CHECK_SIZE = 2048;
+constexpr sal_uInt32 WMF_EMF_CHECK_SIZE = 44;
+
+namespace vcl
+{
+bool peekGraphicFormat(SvStream& rStream, OUString& rFormatExtension, bool bTest)
+{
+ vcl::GraphicFormatDetector aDetector(rStream, rFormatExtension);
+ if (!aDetector.detect())
+ return false;
+
+ // The following variable is used when bTest == true. It remains false
+ // if the format (rFormatExtension) has not yet been set.
+ bool bSomethingTested = false;
+
+ // Now the different formats are checked. The order *does* matter. e.g. a MET file
+ // could also go through the BMP test, however, a BMP file can hardly go through the MET test.
+ // So MET should be tested prior to BMP. However, theoretically a BMP file could conceivably
+ // go through the MET test. These problems are of course not only in MET and BMP.
+ // Therefore, in the case of a format check (bTest == true) we only test *exactly* this
+ // format. Everything else could have fatal consequences, for example if the user says it is
+ // a BMP file (and it is a BMP) file, and the file would go through the MET test ...
+
+ if (!bTest || rFormatExtension.startsWith("MET"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkMET())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("BMP"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkBMP())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("WMF") || rFormatExtension.startsWith("EMF")
+ || rFormatExtension.startsWith("WMZ") || rFormatExtension.startsWith("EMZ"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkWMForEMF())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PCX"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPCX())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("TIF"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkTIF())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("GIF"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkGIF())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PNG"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPNG())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("JPG"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkJPG())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("SVM"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkSVM())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PCD"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPCD())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PSD"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPSD())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("EPS"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkEPS())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("DXF"))
+ {
+ if (aDetector.checkDXF())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PCT"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPCT())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PBM") || rFormatExtension.startsWith("PGM")
+ || rFormatExtension.startsWith("PPM"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkPBMorPGMorPPM())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("RAS"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkRAS())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest)
+ {
+ bSomethingTested = true;
+ if (aDetector.checkXPM())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+ else if (rFormatExtension.startsWith("XPM"))
+ {
+ return true;
+ }
+
+ if (!bTest)
+ {
+ if (aDetector.checkXBM())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+ else if (rFormatExtension.startsWith("XBM"))
+ {
+ return true;
+ }
+
+ if (!bTest)
+ {
+ if (aDetector.checkSVG())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+ else if (rFormatExtension.startsWith("SVG"))
+ {
+ return true;
+ }
+
+ if (!bTest || rFormatExtension.startsWith("TGA"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkTGA())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("MOV"))
+ {
+ if (aDetector.checkMOV())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("PDF"))
+ {
+ if (aDetector.checkPDF())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ if (!bTest || rFormatExtension.startsWith("WEBP"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkWEBP())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
+ return bTest && !bSomethingTested;
+}
+
+namespace
+{
+bool isPCT(SvStream& rStream, sal_uLong nStreamPos, sal_uLong nStreamLen)
+{
+ sal_uInt8 sBuf[3];
+ // store number format
+ SvStreamEndian oldNumberFormat = rStream.GetEndian();
+ sal_uInt32 nOffset; // in MS documents the pict format is used without the first 512 bytes
+ for (nOffset = 0; (nOffset <= 512) && ((nStreamPos + nOffset + 14) <= nStreamLen);
+ nOffset += 512)
+ {
+ short y1, x1, y2, x2;
+ bool bdBoxOk = true;
+
+ rStream.Seek(nStreamPos + nOffset);
+ // size of the pict in version 1 pict ( 2bytes) : ignored
+ rStream.SeekRel(2);
+ // bounding box (bytes 2 -> 9)
+ rStream.SetEndian(SvStreamEndian::BIG);
+ rStream.ReadInt16(y1).ReadInt16(x1).ReadInt16(y2).ReadInt16(x2);
+ rStream.SetEndian(oldNumberFormat); // reset format
+
+ // read version op
+ rStream.ReadBytes(sBuf, 3);
+
+ if (!rStream.good())
+ break;
+
+ if (x1 > x2 || y1 > y2 || // bad bdbox
+ (x1 == x2 && y1 == y2) || // 1 pixel picture
+ x2 - x1 > 2048 || y2 - y1 > 2048) // picture abnormally big
+ bdBoxOk = false;
+
+ // see http://developer.apple.com/legacy/mac/library/documentation/mac/pdf/Imaging_With_QuickDraw/Appendix_A.pdf
+ // normal version 2 - page A23 and A24
+ if (sBuf[0] == 0x00 && sBuf[1] == 0x11 && sBuf[2] == 0x02)
+ return true;
+ // normal version 1 - page A25
+ else if (sBuf[0] == 0x11 && sBuf[1] == 0x01 && bdBoxOk)
+ return true;
+ }
+ return false;
+}
+
+} // end anonymous namespace
+
+GraphicFormatDetector::GraphicFormatDetector(SvStream& rStream, OUString const& rFormatExtension)
+ : mrStream(rStream)
+ , maExtension(rFormatExtension)
+ , mnFirstLong(0)
+ , mnSecondLong(0)
+ , mnStreamPosition(0)
+ , mnStreamLength(0)
+{
+}
+
+bool GraphicFormatDetector::detect()
+{
+ maFirstBytes.clear();
+ maFirstBytes.resize(256, 0);
+
+ mnFirstLong = 0;
+ mnSecondLong = 0;
+
+ mnStreamPosition = mrStream.Tell();
+ mnStreamLength = mrStream.remainingSize();
+
+ if (!mnStreamLength)
+ {
+ SvLockBytes* pLockBytes = mrStream.GetLockBytes();
+ if (pLockBytes)
+ pLockBytes->SetSynchronMode();
+ mnStreamLength = mrStream.remainingSize();
+ }
+
+ if (mnStreamLength == 0)
+ {
+ return false; // this prevents at least a STL assertion
+ }
+ else if (mnStreamLength >= maFirstBytes.size())
+ {
+ // load first 256 bytes into a buffer
+ sal_uInt64 nRead = mrStream.ReadBytes(maFirstBytes.data(), maFirstBytes.size());
+ if (nRead < maFirstBytes.size())
+ mnStreamLength = nRead;
+ }
+ else
+ {
+ mnStreamLength = mrStream.ReadBytes(maFirstBytes.data(), mnStreamLength);
+ }
+
+ if (mrStream.GetError())
+ return false;
+
+ for (int i = 0; i < 4; ++i)
+ {
+ mnFirstLong = (mnFirstLong << 8) | sal_uInt32(maFirstBytes[i]);
+ mnSecondLong = (mnSecondLong << 8) | sal_uInt32(maFirstBytes[i + 4]);
+ }
+ return true;
+}
+
+bool GraphicFormatDetector::checkMET()
+{
+ if (maFirstBytes[2] != 0xd3)
+ return false;
+ mrStream.SetEndian(SvStreamEndian::BIG);
+ mrStream.Seek(mnStreamPosition);
+ sal_uInt16 nFieldSize;
+ sal_uInt8 nMagic;
+
+ mrStream.ReadUInt16(nFieldSize).ReadUChar(nMagic);
+ for (int i = 0; i < 3; i++)
+ {
+ if (nFieldSize < 6)
+ return false;
+ if (mnStreamLength < mrStream.Tell() + nFieldSize)
+ return false;
+ mrStream.SeekRel(nFieldSize - 3);
+ mrStream.ReadUInt16(nFieldSize).ReadUChar(nMagic);
+ if (nMagic != 0xd3)
+ return false;
+ }
+ mrStream.SetEndian(SvStreamEndian::LITTLE);
+
+ if (mrStream.GetError())
+ return false;
+
+ msDetectedFormat = "MET";
+ return true;
+}
+
+bool GraphicFormatDetector::checkBMP()
+{
+ sal_uInt8 nOffset;
+
+ // We're possibly also able to read an OS/2 bitmap array
+ // ('BA'), therefore we must adjust the offset to discover the
+ // first bitmap in the array
+ if (maFirstBytes[0] == 0x42 && maFirstBytes[1] == 0x41)
+ nOffset = 14;
+ else
+ nOffset = 0;
+
+ // Now we initially test on 'BM'
+ if (maFirstBytes[0 + nOffset] == 0x42 && maFirstBytes[1 + nOffset] == 0x4d)
+ {
+ // OS/2 can set the Reserved flags to a value other than 0
+ // (which they really should not do...);
+ // In this case we test the size of the BmpInfoHeaders
+ if ((maFirstBytes[6 + nOffset] == 0x00 && maFirstBytes[7 + nOffset] == 0x00
+ && maFirstBytes[8 + nOffset] == 0x00 && maFirstBytes[9 + nOffset] == 0x00)
+ || maFirstBytes[14 + nOffset] == 0x28 || maFirstBytes[14 + nOffset] == 0x0c)
+ {
+ msDetectedFormat = "BMP";
+ return true;
+ }
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkWMForEMF()
+{
+ sal_uInt64 nCheckSize = std::min<sal_uInt64>(mnStreamLength, 256);
+ sal_uInt8 sExtendedOrDecompressedFirstBytes[WMF_EMF_CHECK_SIZE];
+ sal_uInt64 nDecompressedSize = nCheckSize;
+ // check if it is gzipped -> wmz/emz
+ sal_uInt8* pCheckArray = checkAndUncompressBuffer(sExtendedOrDecompressedFirstBytes,
+ WMF_EMF_CHECK_SIZE, nDecompressedSize);
+ if (mnFirstLong == 0xd7cdc69a || mnFirstLong == 0x01000900)
+ {
+ msDetectedFormat = "WMF";
+ return true;
+ }
+ else if (mnFirstLong == 0x01000000 && pCheckArray[40] == 0x20 && pCheckArray[41] == 0x45
+ && pCheckArray[42] == 0x4d && pCheckArray[43] == 0x46)
+ {
+ msDetectedFormat = "EMF";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPCX()
+{
+ if (maFirstBytes[0] != 0x0a)
+ return false;
+
+ sal_uInt8 nVersion = maFirstBytes[1];
+ sal_uInt8 nEncoding = maFirstBytes[2];
+ if ((nVersion == 0 || nVersion == 2 || nVersion == 3 || nVersion == 5) && nEncoding <= 1)
+ {
+ msDetectedFormat = "PCX";
+ return true;
+ }
+
+ return false;
+}
+
+bool GraphicFormatDetector::checkTIF()
+{
+ if (mnFirstLong == 0x49492a00 || mnFirstLong == 0x4d4d002a)
+ {
+ msDetectedFormat = "TIF";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkGIF()
+{
+ if (mnFirstLong == 0x47494638 && (maFirstBytes[4] == 0x37 || maFirstBytes[4] == 0x39)
+ && maFirstBytes[5] == 0x61)
+ {
+ msDetectedFormat = "GIF";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPNG()
+{
+ if (mnFirstLong == 0x89504e47 && mnSecondLong == 0x0d0a1a0a)
+ {
+ msDetectedFormat = "PNG";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkJPG()
+{
+ if ((mnFirstLong == 0xffd8ffe0 && maFirstBytes[6] == 0x4a && maFirstBytes[7] == 0x46
+ && maFirstBytes[8] == 0x49 && maFirstBytes[9] == 0x46)
+ || (mnFirstLong == 0xffd8fffe) || (0xffd8ff00 == (mnFirstLong & 0xffffff00)))
+ {
+ msDetectedFormat = "JPG";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkSVM()
+{
+ if (mnFirstLong == 0x53564744 && maFirstBytes[4] == 0x49)
+ {
+ msDetectedFormat = "SVM";
+ return true;
+ }
+ else if (maFirstBytes[0] == 0x56 && maFirstBytes[1] == 0x43 && maFirstBytes[2] == 0x4C
+ && maFirstBytes[3] == 0x4D && maFirstBytes[4] == 0x54 && maFirstBytes[5] == 0x46)
+ {
+ msDetectedFormat = "SVM";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPCD()
+{
+ if (mnStreamLength < 2055)
+ return false;
+ char sBuffer[8];
+ mrStream.Seek(mnStreamPosition + 2048);
+ sBuffer[mrStream.ReadBytes(sBuffer, 7)] = 0;
+
+ if (strncmp(sBuffer, "PCD_IPI", 7) == 0)
+ {
+ msDetectedFormat = "PCD";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPSD()
+{
+ if ((mnFirstLong == 0x38425053) && ((mnSecondLong >> 16) == 1))
+ {
+ msDetectedFormat = "PSD";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkEPS()
+{
+ const char* pFirstBytesAsCharArray = reinterpret_cast<char*>(maFirstBytes.data());
+
+ if (mnFirstLong == 0xC5D0D3C6)
+ {
+ msDetectedFormat = "EPS";
+ return true;
+ }
+ else if (checkArrayForMatchingStrings(pFirstBytesAsCharArray, 30, { "%!PS-Adobe", " EPS" }))
+ {
+ msDetectedFormat = "EPS";
+ return true;
+ }
+
+ return false;
+}
+
+bool GraphicFormatDetector::checkDXF()
+{
+ if (strncmp(reinterpret_cast<char*>(maFirstBytes.data()), "AutoCAD Binary DXF", 18) == 0)
+ {
+ msDetectedFormat = "DXF";
+ return true;
+ }
+
+ // ASCII DXF File Format
+ int i = 0;
+ while (i < 256 && maFirstBytes[i] <= 32)
+ {
+ ++i;
+ }
+
+ if (i < 256 && maFirstBytes[i] == '0')
+ {
+ ++i;
+
+ // only now do we have sufficient data to make a judgement
+ // based on a '0' + 'SECTION' == DXF argument
+
+ while (i < 256 && maFirstBytes[i] <= 32)
+ {
+ ++i;
+ }
+
+ if (i + 7 < 256
+ && (strncmp(reinterpret_cast<char*>(maFirstBytes.data() + i), "SECTION", 7) == 0))
+ {
+ msDetectedFormat = "DXF";
+ return true;
+ }
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPCT()
+{
+ if (isPCT(mrStream, mnStreamPosition, mnStreamLength))
+ {
+ msDetectedFormat = "PCT";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPBMorPGMorPPM()
+{
+ if (maFirstBytes[0] == 'P')
+ {
+ switch (maFirstBytes[1])
+ {
+ case '1':
+ case '4':
+ msDetectedFormat = "PBM";
+ return true;
+
+ case '2':
+ case '5':
+ msDetectedFormat = "PGM";
+ return true;
+
+ case '3':
+ case '6':
+ msDetectedFormat = "PPM";
+ return true;
+ }
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkRAS()
+{
+ if (mnFirstLong == 0x59a66a95)
+ {
+ msDetectedFormat = "RAS";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkXPM()
+{
+ const char* pFirstBytesAsCharArray = reinterpret_cast<char*>(maFirstBytes.data());
+ if (matchArrayWithString(pFirstBytesAsCharArray, 256, "/* XPM */"))
+ {
+ msDetectedFormat = "XPM";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkXBM()
+{
+ sal_uInt64 nSize = std::min<sal_uInt64>(mnStreamLength, 2048);
+ std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nSize]);
+
+ mrStream.Seek(mnStreamPosition);
+ nSize = mrStream.ReadBytes(pBuffer.get(), nSize);
+
+ const char* pBufferAsCharArray = reinterpret_cast<char*>(pBuffer.get());
+
+ if (checkArrayForMatchingStrings(pBufferAsCharArray, nSize, { "#define", "_width" }))
+ {
+ msDetectedFormat = "XBM";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkSVG()
+{
+ sal_uInt64 nCheckSize = std::min<sal_uInt64>(mnStreamLength, 256);
+ sal_uInt8 sExtendedOrDecompressedFirstBytes[SVG_CHECK_SIZE];
+ sal_uInt64 nDecompressedSize = nCheckSize;
+ // check if it is gzipped -> svgz
+ sal_uInt8* pCheckArray = checkAndUncompressBuffer(sExtendedOrDecompressedFirstBytes,
+ SVG_CHECK_SIZE, nDecompressedSize);
+ nCheckSize = std::min<sal_uInt64>(nDecompressedSize, 256);
+ bool bIsSvg(false);
+ bool bIsGZip = (nDecompressedSize > 0);
+ const char* pCheckArrayAsCharArray = reinterpret_cast<char*>(pCheckArray);
+ // check for XML
+ // #119176# SVG files which have no xml header at all have shown up this is optional
+ // check for "xml" then "version" then "DOCTYPE" and "svg" tags
+ if (checkArrayForMatchingStrings(pCheckArrayAsCharArray, nCheckSize,
+ { "<?xml", "version", "DOCTYPE", "svg" }))
+ {
+ bIsSvg = true;
+ }
+
+ // check for svg element in 1st 256 bytes
+ // search for '<svg'
+ if (!bIsSvg && checkArrayForMatchingStrings(pCheckArrayAsCharArray, nCheckSize, { "<svg" }))
+ {
+ bIsSvg = true;
+ }
+
+ // extended search for svg element
+ if (!bIsSvg)
+ {
+ // it's a xml, look for '<svg' in full file. Should not happen too
+ // often since the tests above will handle most cases, but can happen
+ // with Svg files containing big comment headers or Svg as the host
+ // language
+
+ pCheckArrayAsCharArray = reinterpret_cast<char*>(sExtendedOrDecompressedFirstBytes);
+
+ if (bIsGZip)
+ {
+ nCheckSize = std::min<sal_uInt64>(nDecompressedSize, 2048);
+ }
+ else
+ {
+ nCheckSize = std::min<sal_uInt64>(mnStreamLength, 2048);
+ mrStream.Seek(mnStreamPosition);
+ nCheckSize = mrStream.ReadBytes(sExtendedOrDecompressedFirstBytes, nCheckSize);
+ }
+
+ // search for '<svg'
+ if (checkArrayForMatchingStrings(pCheckArrayAsCharArray, nCheckSize, { "<svg" }))
+ {
+ bIsSvg = true;
+ }
+ }
+
+ if (bIsSvg)
+ {
+ msDetectedFormat = "SVG";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkTGA()
+{
+ // Check TGA ver.2 footer bytes
+ if (mnStreamLength > 18)
+ {
+ char sFooterBytes[18];
+
+ mrStream.Seek(STREAM_SEEK_TO_END);
+ mrStream.SeekRel(-18);
+ if (mrStream.ReadBytes(sFooterBytes, 18) == 18
+ && memcmp(sFooterBytes, "TRUEVISION-XFILE.", SAL_N_ELEMENTS(sFooterBytes)) == 0)
+ {
+ msDetectedFormat = "TGA";
+ return true;
+ }
+ }
+
+ // Fallback to file extension check
+ if (maExtension.startsWith("TGA"))
+ {
+ msDetectedFormat = "TGA";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkMOV()
+{
+ if ((maFirstBytes[4] == 'f' && maFirstBytes[5] == 't' && maFirstBytes[6] == 'y'
+ && maFirstBytes[7] == 'p' && maFirstBytes[8] == 'q' && maFirstBytes[9] == 't')
+ || (maFirstBytes[4] == 'm' && maFirstBytes[5] == 'o' && maFirstBytes[6] == 'o'
+ && maFirstBytes[7] == 'v' && maFirstBytes[11] == 'l' && maFirstBytes[12] == 'm'))
+ {
+ msDetectedFormat = "MOV";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkPDF()
+{
+ if (maFirstBytes[0] == '%' && maFirstBytes[1] == 'P' && maFirstBytes[2] == 'D'
+ && maFirstBytes[3] == 'F' && maFirstBytes[4] == '-')
+ {
+ msDetectedFormat = "PDF";
+ return true;
+ }
+ return false;
+}
+
+bool GraphicFormatDetector::checkWEBP()
+{
+ if (maFirstBytes[0] == 'R' && maFirstBytes[1] == 'I' && maFirstBytes[2] == 'F'
+ && maFirstBytes[3] == 'F' && maFirstBytes[8] == 'W' && maFirstBytes[9] == 'E'
+ && maFirstBytes[10] == 'B' && maFirstBytes[11] == 'P')
+ {
+ msDetectedFormat = "WEBP";
+ return true;
+ }
+ return false;
+}
+
+sal_uInt8* GraphicFormatDetector::checkAndUncompressBuffer(sal_uInt8* aUncompressedBuffer,
+ sal_uInt32 nSize, sal_uInt64& nRetSize)
+{
+ if (ZCodec::IsZCompressed(mrStream))
+ {
+ ZCodec aCodec;
+ mrStream.Seek(mnStreamPosition);
+ aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/ true);
+ auto nDecompressedOut = aCodec.Read(mrStream, aUncompressedBuffer, nSize);
+ // ZCodec::Decompress returns -1 on failure
+ nRetSize = nDecompressedOut < 0 ? 0 : nDecompressedOut;
+ aCodec.EndCompression();
+ // Recalculate first/second long
+ for (int i = 0; i < 4; ++i)
+ {
+ mnFirstLong = (mnFirstLong << 8) | sal_uInt32(aUncompressedBuffer[i]);
+ mnSecondLong = (mnSecondLong << 8) | sal_uInt32(aUncompressedBuffer[i + 4]);
+ }
+ return aUncompressedBuffer;
+ }
+ nRetSize = 0;
+ return maFirstBytes.data();
+}
+
+} // vcl namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/GraphicNativeMetadata.cxx b/vcl/source/filter/GraphicNativeMetadata.cxx
new file mode 100644
index 000000000..5b11fcc78
--- /dev/null
+++ b/vcl/source/filter/GraphicNativeMetadata.cxx
@@ -0,0 +1,59 @@
+/* -*- 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 <vcl/GraphicNativeMetadata.hxx>
+#include <vcl/gfxlink.hxx>
+#include "jpeg/Exif.hxx"
+#include <memory>
+
+GraphicNativeMetadata::GraphicNativeMetadata()
+ : mRotation(0)
+{
+}
+
+bool GraphicNativeMetadata::read(Graphic const& rGraphic)
+{
+ GfxLink aLink = rGraphic.GetGfxLink();
+ if (aLink.GetType() != GfxLinkType::NativeJpg)
+ return false;
+
+ sal_uInt32 aDataSize = aLink.GetDataSize();
+ if (!aDataSize)
+ return false;
+
+ std::unique_ptr<sal_uInt8[]> aBuffer(new sal_uInt8[aDataSize]);
+
+ memcpy(aBuffer.get(), aLink.GetData(), aDataSize);
+ SvMemoryStream aMemoryStream(aBuffer.get(), aDataSize, StreamMode::READ);
+
+ read(aMemoryStream);
+
+ return true;
+}
+
+bool GraphicNativeMetadata::read(SvStream& rStream)
+{
+ Exif aExif;
+ aExif.read(rStream);
+ mRotation = aExif.getRotation();
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/GraphicNativeTransform.cxx b/vcl/source/filter/GraphicNativeTransform.cxx
new file mode 100644
index 000000000..792dd6a93
--- /dev/null
+++ b/vcl/source/filter/GraphicNativeTransform.cxx
@@ -0,0 +1,164 @@
+/* -*- 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 <vcl/GraphicNativeTransform.hxx>
+
+#include <vcl/gfxlink.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <tools/stream.hxx>
+#include <comphelper/propertyvalue.hxx>
+
+#include "jpeg/Exif.hxx"
+#include "jpeg/JpegTransform.hxx"
+
+GraphicNativeTransform::GraphicNativeTransform(Graphic& rGraphic)
+ : mrGraphic(rGraphic)
+{
+}
+
+void GraphicNativeTransform::rotate(Degree10 aInputRotation)
+{
+ // Rotation can be between 0 and 3600
+ Degree10 aRotation = aInputRotation % 3600_deg10;
+
+ if (aRotation == 0_deg10)
+ {
+ return; // No rotation is needed
+ }
+ else if (aRotation != 900_deg10 && aRotation != 1800_deg10 && aRotation != 2700_deg10)
+ {
+ return;
+ }
+
+ GfxLink aLink = mrGraphic.GetGfxLink();
+ if (aLink.GetType() == GfxLinkType::NativeJpg)
+ {
+ rotateJPEG(aRotation);
+ }
+ else if (aLink.GetType() == GfxLinkType::NativePng)
+ {
+ rotateGeneric(aRotation, u"png");
+ }
+ else if (aLink.GetType() == GfxLinkType::NativeGif)
+ {
+ rotateGeneric(aRotation, u"gif");
+ }
+ else if (aLink.GetType() == GfxLinkType::NONE)
+ {
+ rotateBitmapOnly(aRotation);
+ }
+}
+
+bool GraphicNativeTransform::rotateBitmapOnly(Degree10 aRotation)
+{
+ if (mrGraphic.IsAnimated())
+ {
+ return false;
+ }
+
+ BitmapEx aBitmap = mrGraphic.GetBitmapEx();
+ aBitmap.Rotate(aRotation, COL_BLACK);
+ mrGraphic = aBitmap;
+
+ return true;
+}
+
+bool GraphicNativeTransform::rotateGeneric(Degree10 aRotation, std::u16string_view aType)
+{
+ // Can't rotate animations yet
+ if (mrGraphic.IsAnimated())
+ {
+ return false;
+ }
+
+ SvMemoryStream aStream;
+
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+
+ css::uno::Sequence<css::beans::PropertyValue> aFilterData{
+ comphelper::makePropertyValue("Interlaced", sal_Int32(0)),
+ comphelper::makePropertyValue("Compression", sal_Int32(9)),
+ comphelper::makePropertyValue("Quality", sal_Int32(90))
+ };
+
+ sal_uInt16 nFilterFormat = rFilter.GetExportFormatNumberForShortName(aType);
+
+ BitmapEx aBitmap = mrGraphic.GetBitmapEx();
+ aBitmap.Rotate(aRotation, COL_BLACK);
+ rFilter.ExportGraphic(aBitmap, u"none", aStream, nFilterFormat, &aFilterData);
+
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ Graphic aGraphic;
+ rFilter.ImportGraphic(aGraphic, u"import", aStream);
+
+ mrGraphic = aGraphic;
+ return true;
+}
+
+void GraphicNativeTransform::rotateJPEG(Degree10 aRotation)
+{
+ BitmapEx aBitmap = mrGraphic.GetBitmapEx();
+
+ if (aBitmap.GetSizePixel().Width() % 16 != 0 || aBitmap.GetSizePixel().Height() % 16 != 0)
+ {
+ rotateGeneric(aRotation, u"png");
+ }
+ else
+ {
+ GfxLink aLink = mrGraphic.GetGfxLink();
+
+ SvMemoryStream aSourceStream;
+ aSourceStream.WriteBytes(aLink.GetData(), aLink.GetDataSize());
+ aSourceStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ exif::Orientation aOrientation = exif::TOP_LEFT;
+
+ Exif exif;
+ if (exif.read(aSourceStream))
+ {
+ aOrientation = exif.getOrientation();
+ }
+
+ SvMemoryStream aTargetStream;
+ JpegTransform transform(aSourceStream, aTargetStream);
+ transform.setRotate(aRotation);
+ transform.perform();
+
+ aTargetStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ // Reset orientation in exif if needed
+ if (exif.hasExif() && aOrientation != exif::TOP_LEFT)
+ {
+ exif.setOrientation(exif::TOP_LEFT);
+ exif.write(aTargetStream);
+ }
+
+ aTargetStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ Graphic aGraphic;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.ImportGraphic(aGraphic, u"import", aTargetStream);
+ mrGraphic = aGraphic;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/bmp/BmpReader.cxx b/vcl/source/filter/bmp/BmpReader.cxx
new file mode 100644
index 000000000..3d2b6a463
--- /dev/null
+++ b/vcl/source/filter/bmp/BmpReader.cxx
@@ -0,0 +1,30 @@
+/* -*- 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 <filter/BmpReader.hxx>
+#include <vcl/TypeSerializer.hxx>
+
+bool BmpReader(SvStream& rStream, Graphic& rGraphic)
+{
+ TypeSerializer aSerializer(rStream);
+ aSerializer.readGraphic(rGraphic);
+ return !rStream.GetError();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/bmp/BmpWriter.cxx b/vcl/source/filter/bmp/BmpWriter.cxx
new file mode 100644
index 000000000..a5fcedec2
--- /dev/null
+++ b/vcl/source/filter/bmp/BmpWriter.cxx
@@ -0,0 +1,40 @@
+/* -*- 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 <filter/BmpWriter.hxx>
+#include <vcl/dibtools.hxx>
+
+bool BmpWriter(SvStream& rStream, const Graphic& rGraphic, FilterConfigItem* pFilterConfigItem)
+{
+ BitmapEx aBitmap = rGraphic.GetBitmapEx();
+ sal_Int32 nColor = pFilterConfigItem->ReadInt32("Color", 0);
+
+ auto nColorRes = static_cast<BmpConversion>(nColor);
+ if (nColorRes != BmpConversion::NNONE && nColorRes <= BmpConversion::N24Bit)
+ {
+ if (!aBitmap.Convert(nColorRes))
+ aBitmap = rGraphic.GetBitmapEx();
+ }
+ bool bRleCoding = pFilterConfigItem->ReadBool("RLE_Coding", true);
+ WriteDIB(aBitmap, rStream, bRleCoding);
+
+ return rStream.good();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/egif/egif.cxx b/vcl/source/filter/egif/egif.cxx
new file mode 100644
index 000000000..699b7f05d
--- /dev/null
+++ b/vcl/source/filter/egif/egif.cxx
@@ -0,0 +1,549 @@
+/* -*- 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 <tools/stream.hxx>
+#include <tools/debug.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+#include "giflzwc.hxx"
+#include <memory>
+#include <filter/GifWriter.hxx>
+
+namespace {
+
+class GIFWriter
+{
+ Bitmap aAccBmp;
+ SvStream& m_rGIF;
+ BitmapReadAccess* m_pAcc;
+ sal_uInt32 nMinPercent;
+ sal_uInt32 nMaxPercent;
+ sal_uInt32 nLastPercent;
+ tools::Long nActX;
+ tools::Long nActY;
+ sal_Int32 nInterlaced;
+ bool bStatus;
+ bool bTransparent;
+
+ void MayCallback(sal_uInt32 nPercent);
+ void WriteSignature( bool bGIF89a );
+ void WriteGlobalHeader( const Size& rSize );
+ void WriteLoopExtension( const Animation& rAnimation );
+ void WriteLogSizeExtension( const Size& rSize100 );
+ void WriteImageExtension( tools::Long nTimer, Disposal eDisposal );
+ void WriteLocalHeader();
+ void WritePalette();
+ void WriteAccess();
+ void WriteTerminator();
+
+ bool CreateAccess( const BitmapEx& rBmpEx );
+ void DestroyAccess();
+
+ void WriteAnimation( const Animation& rAnimation );
+ void WriteBitmapEx( const BitmapEx& rBmpEx, const Point& rPoint, bool bExtended,
+ tools::Long nTimer = 0, Disposal eDisposal = Disposal::Not );
+
+ css::uno::Reference< css::task::XStatusIndicator > xStatusIndicator;
+
+public:
+
+ explicit GIFWriter(SvStream &rStream);
+
+ bool WriteGIF( const Graphic& rGraphic, FilterConfigItem* pConfigItem );
+};
+
+}
+
+GIFWriter::GIFWriter(SvStream &rStream)
+ : m_rGIF(rStream)
+ , m_pAcc(nullptr)
+ , nMinPercent(0)
+ , nMaxPercent(0)
+ , nLastPercent(0)
+ , nActX(0)
+ , nActY(0)
+ , nInterlaced(0)
+ , bStatus(false)
+ , bTransparent(false)
+{
+}
+
+
+bool GIFWriter::WriteGIF(const Graphic& rGraphic, FilterConfigItem* pFilterConfigItem)
+{
+ if ( pFilterConfigItem )
+ {
+ xStatusIndicator = pFilterConfigItem->GetStatusIndicator();
+ if ( xStatusIndicator.is() )
+ {
+ xStatusIndicator->start( OUString(), 100 );
+ }
+ }
+
+ Size aSize100;
+ const MapMode aMap( rGraphic.GetPrefMapMode() );
+ bool bLogSize = ( aMap.GetMapUnit() != MapUnit::MapPixel );
+
+ if( bLogSize )
+ aSize100 = OutputDevice::LogicToLogic(rGraphic.GetPrefSize(), aMap, MapMode(MapUnit::Map100thMM));
+
+ bStatus = true;
+ nLastPercent = 0;
+ nInterlaced = 0;
+ m_pAcc = nullptr;
+
+ if ( pFilterConfigItem )
+ nInterlaced = pFilterConfigItem->ReadInt32( "Interlaced", 0 );
+
+ m_rGIF.SetEndian( SvStreamEndian::LITTLE );
+
+ if( rGraphic.IsAnimated() )
+ {
+ const Animation& rAnimation = rGraphic.GetAnimation();
+
+ WriteSignature( true );
+
+ if ( bStatus )
+ {
+ WriteGlobalHeader( rAnimation.GetDisplaySizePixel() );
+
+ if( bStatus )
+ {
+ WriteLoopExtension( rAnimation );
+
+ if( bStatus )
+ WriteAnimation( rAnimation );
+ }
+ }
+ }
+ else
+ {
+ const bool bGrafTrans = rGraphic.IsTransparent();
+
+ BitmapEx aBmpEx = rGraphic.GetBitmapEx();
+
+ nMinPercent = 0;
+ nMaxPercent = 100;
+
+ WriteSignature( bGrafTrans || bLogSize );
+
+ if( bStatus )
+ {
+ WriteGlobalHeader( aBmpEx.GetSizePixel() );
+
+ if( bStatus )
+ WriteBitmapEx( aBmpEx, Point(), bGrafTrans );
+ }
+ }
+
+ if( bStatus )
+ {
+ if( bLogSize )
+ WriteLogSizeExtension( aSize100 );
+
+ WriteTerminator();
+ }
+
+ if ( xStatusIndicator.is() )
+ xStatusIndicator->end();
+
+ return bStatus;
+}
+
+
+void GIFWriter::WriteBitmapEx( const BitmapEx& rBmpEx, const Point& rPoint,
+ bool bExtended, tools::Long nTimer, Disposal eDisposal )
+{
+ if( !CreateAccess( rBmpEx ) )
+ return;
+
+ nActX = rPoint.X();
+ nActY = rPoint.Y();
+
+ if( bExtended )
+ WriteImageExtension( nTimer, eDisposal );
+
+ if( bStatus )
+ {
+ WriteLocalHeader();
+
+ if( bStatus )
+ {
+ WritePalette();
+
+ if( bStatus )
+ WriteAccess();
+ }
+ }
+
+ DestroyAccess();
+}
+
+
+void GIFWriter::WriteAnimation( const Animation& rAnimation )
+{
+ const sal_uInt16 nCount = rAnimation.Count();
+
+ if( !nCount )
+ return;
+
+ const double fStep = 100. / nCount;
+
+ nMinPercent = 0;
+ nMaxPercent = static_cast<sal_uInt32>(fStep);
+
+ for( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ const AnimationBitmap& rAnimationBitmap = rAnimation.Get( i );
+
+ WriteBitmapEx(rAnimationBitmap.maBitmapEx, rAnimationBitmap.maPositionPixel, true,
+ rAnimationBitmap.mnWait, rAnimationBitmap.meDisposal );
+ nMinPercent = nMaxPercent;
+ nMaxPercent = static_cast<sal_uInt32>(nMaxPercent + fStep);
+ }
+}
+
+
+void GIFWriter::MayCallback(sal_uInt32 nPercent)
+{
+ if ( xStatusIndicator.is() )
+ {
+ if( nPercent >= nLastPercent + 3 )
+ {
+ nLastPercent = nPercent;
+ if ( nPercent <= 100 )
+ xStatusIndicator->setValue( nPercent );
+ }
+ }
+}
+
+
+bool GIFWriter::CreateAccess( const BitmapEx& rBmpEx )
+{
+ if( bStatus )
+ {
+ Bitmap aMask( rBmpEx.GetAlpha() );
+
+ aAccBmp = rBmpEx.GetBitmap();
+ bTransparent = false;
+
+ if( !aMask.IsEmpty() )
+ {
+ if( aAccBmp.Convert( BmpConversion::N8BitTrans ) )
+ {
+ aMask.Convert( BmpConversion::N1BitThreshold );
+ aAccBmp.Replace( aMask, BMP_COL_TRANS );
+ bTransparent = true;
+ }
+ else
+ aAccBmp.Convert( BmpConversion::N8BitColors );
+ }
+ else
+ aAccBmp.Convert( BmpConversion::N8BitColors );
+
+ m_pAcc = aAccBmp.AcquireReadAccess();
+
+ if( !m_pAcc )
+ bStatus = false;
+ }
+
+ return bStatus;
+}
+
+
+void GIFWriter::DestroyAccess()
+{
+ Bitmap::ReleaseAccess( m_pAcc );
+ m_pAcc = nullptr;
+}
+
+
+void GIFWriter::WriteSignature( bool bGIF89a )
+{
+ if( bStatus )
+ {
+ m_rGIF.WriteBytes(bGIF89a ? "GIF89a" : "GIF87a" , 6);
+
+ if( m_rGIF.GetError() )
+ bStatus = false;
+ }
+}
+
+
+void GIFWriter::WriteGlobalHeader( const Size& rSize )
+{
+ if( !bStatus )
+ return;
+
+ // 256 colors
+ const sal_uInt16 nWidth = static_cast<sal_uInt16>(rSize.Width());
+ const sal_uInt16 nHeight = static_cast<sal_uInt16>(rSize.Height());
+ const sal_uInt8 cFlags = 128 | ( 7 << 4 );
+
+ // write values
+ m_rGIF.WriteUInt16( nWidth );
+ m_rGIF.WriteUInt16( nHeight );
+ m_rGIF.WriteUChar( cFlags );
+ m_rGIF.WriteUChar( 0x00 );
+ m_rGIF.WriteUChar( 0x00 );
+
+ // write dummy palette with two entries (black/white);
+ // we do this only because of a bug in Photoshop, since those can't
+ // read pictures without a global color palette
+ m_rGIF.WriteUInt16( 0 );
+ m_rGIF.WriteUInt16( 255 );
+ m_rGIF.WriteUInt16( 65535 );
+
+ if( m_rGIF.GetError() )
+ bStatus = false;
+}
+
+
+void GIFWriter::WriteLoopExtension( const Animation& rAnimation )
+{
+ DBG_ASSERT( rAnimation.Count() > 0, "Animation has no bitmaps!" );
+
+ sal_uInt16 nLoopCount = static_cast<sal_uInt16>(rAnimation.GetLoopCount());
+
+ // if only one run should take place
+ // the LoopExtension won't be written
+ // The default in this case is a single run
+ if( nLoopCount == 1 )
+ return;
+
+ // Netscape interprets the LoopCount
+ // as the sole number of _repetitions_
+ if( nLoopCount )
+ nLoopCount--;
+
+ const sal_uInt8 cLoByte = static_cast<sal_uInt8>(nLoopCount);
+ const sal_uInt8 cHiByte = static_cast<sal_uInt8>( nLoopCount >> 8 );
+
+ m_rGIF.WriteUChar( 0x21 );
+ m_rGIF.WriteUChar( 0xff );
+ m_rGIF.WriteUChar( 0x0b );
+ m_rGIF.WriteBytes( "NETSCAPE2.0", 11 );
+ m_rGIF.WriteUChar( 0x03 );
+ m_rGIF.WriteUChar( 0x01 );
+ m_rGIF.WriteUChar( cLoByte );
+ m_rGIF.WriteUChar( cHiByte );
+ m_rGIF.WriteUChar( 0x00 );
+}
+
+
+void GIFWriter::WriteLogSizeExtension( const Size& rSize100 )
+{
+ // writer PrefSize in 100th-mm as ApplicationExtension
+ if( rSize100.Width() && rSize100.Height() )
+ {
+ m_rGIF.WriteUChar( 0x21 );
+ m_rGIF.WriteUChar( 0xff );
+ m_rGIF.WriteUChar( 0x0b );
+ m_rGIF.WriteBytes( "STARDIV 5.0", 11 );
+ m_rGIF.WriteUChar( 0x09 );
+ m_rGIF.WriteUChar( 0x01 );
+ m_rGIF.WriteUInt32( rSize100.Width() );
+ m_rGIF.WriteUInt32( rSize100.Height() );
+ m_rGIF.WriteUChar( 0x00 );
+ }
+}
+
+
+void GIFWriter::WriteImageExtension( tools::Long nTimer, Disposal eDisposal )
+{
+ if( !bStatus )
+ return;
+
+ const sal_uInt16 nDelay = static_cast<sal_uInt16>(nTimer);
+ sal_uInt8 cFlags = 0;
+
+ // set Transparency-Flag
+ if( bTransparent )
+ cFlags |= 1;
+
+ // set Disposal-value
+ if( eDisposal == Disposal::Back )
+ cFlags |= ( 2 << 2 );
+ else if( eDisposal == Disposal::Previous )
+ cFlags |= ( 3 << 2 );
+
+ m_rGIF.WriteUChar( 0x21 );
+ m_rGIF.WriteUChar( 0xf9 );
+ m_rGIF.WriteUChar( 0x04 );
+ m_rGIF.WriteUChar( cFlags );
+ m_rGIF.WriteUInt16( nDelay );
+ m_rGIF.WriteUChar( m_pAcc->GetBestPaletteIndex( BMP_COL_TRANS ) );
+ m_rGIF.WriteUChar( 0x00 );
+
+ if( m_rGIF.GetError() )
+ bStatus = false;
+}
+
+
+void GIFWriter::WriteLocalHeader()
+{
+ if( !bStatus )
+ return;
+
+ const sal_uInt16 nPosX = static_cast<sal_uInt16>(nActX);
+ const sal_uInt16 nPosY = static_cast<sal_uInt16>(nActY);
+ const sal_uInt16 nWidth = static_cast<sal_uInt16>(m_pAcc->Width());
+ const sal_uInt16 nHeight = static_cast<sal_uInt16>(m_pAcc->Height());
+ sal_uInt8 cFlags = static_cast<sal_uInt8>( m_pAcc->GetBitCount() - 1 );
+
+ // set Interlaced-Flag
+ if( nInterlaced )
+ cFlags |= 0x40;
+
+ // set Flag for the local color palette
+ cFlags |= 0x80;
+
+ m_rGIF.WriteUChar( 0x2c );
+ m_rGIF.WriteUInt16( nPosX );
+ m_rGIF.WriteUInt16( nPosY );
+ m_rGIF.WriteUInt16( nWidth );
+ m_rGIF.WriteUInt16( nHeight );
+ m_rGIF.WriteUChar( cFlags );
+
+ if( m_rGIF.GetError() )
+ bStatus = false;
+}
+
+
+void GIFWriter::WritePalette()
+{
+ if( !(bStatus && m_pAcc->HasPalette()) )
+ return;
+
+ const sal_uInt16 nCount = m_pAcc->GetPaletteEntryCount();
+ const sal_uInt16 nMaxCount = ( 1 << m_pAcc->GetBitCount() );
+
+ for ( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ const BitmapColor& rColor = m_pAcc->GetPaletteColor( i );
+
+ m_rGIF.WriteUChar( rColor.GetRed() );
+ m_rGIF.WriteUChar( rColor.GetGreen() );
+ m_rGIF.WriteUChar( rColor.GetBlue() );
+ }
+
+ // fill up the rest with 0
+ if( nCount < nMaxCount )
+ m_rGIF.SeekRel( ( nMaxCount - nCount ) * 3 );
+
+ if( m_rGIF.GetError() )
+ bStatus = false;
+}
+
+
+void GIFWriter::WriteAccess()
+{
+ GIFLZWCompressor aCompressor;
+ const tools::Long nWidth = m_pAcc->Width();
+ const tools::Long nHeight = m_pAcc->Height();
+ std::unique_ptr<sal_uInt8[]> pBuffer;
+ bool bNative = m_pAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal;
+
+ if( !bNative )
+ pBuffer.reset(new sal_uInt8[ nWidth ]);
+
+ if( !(bStatus && ( 8 == m_pAcc->GetBitCount() ) && m_pAcc->HasPalette()) )
+ return;
+
+ aCompressor.StartCompression( m_rGIF, m_pAcc->GetBitCount() );
+
+ tools::Long nY, nT;
+
+ for( tools::Long i = 0; i < nHeight; ++i )
+ {
+ if( nInterlaced )
+ {
+ nY = i << 3;
+
+ if( nY >= nHeight )
+ {
+ nT = i - ( ( nHeight + 7 ) >> 3 );
+ nY= ( nT << 3 ) + 4;
+
+ if( nY >= nHeight )
+ {
+ nT -= ( nHeight + 3 ) >> 3;
+ nY = ( nT << 2 ) + 2;
+
+ if ( nY >= nHeight )
+ {
+ nT -= ( ( nHeight + 1 ) >> 2 );
+ nY = ( nT << 1 ) + 1;
+ }
+ }
+ }
+ }
+ else
+ nY = i;
+
+ if( bNative )
+ aCompressor.Compress( m_pAcc->GetScanline( nY ), nWidth );
+ else
+ {
+ Scanline pScanline = m_pAcc->GetScanline( nY );
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ pBuffer[ nX ] = m_pAcc->GetIndexFromData( pScanline, nX );
+
+ aCompressor.Compress( pBuffer.get(), nWidth );
+ }
+
+ if ( m_rGIF.GetError() )
+ bStatus = false;
+
+ MayCallback( nMinPercent + ( nMaxPercent - nMinPercent ) * i / nHeight );
+
+ if( !bStatus )
+ break;
+ }
+
+ aCompressor.EndCompression();
+
+ if ( m_rGIF.GetError() )
+ bStatus = false;
+}
+
+
+void GIFWriter::WriteTerminator()
+{
+ if( bStatus )
+ {
+ m_rGIF.WriteUChar( 0x3b );
+
+ if( m_rGIF.GetError() )
+ bStatus = false;
+ }
+}
+
+
+bool ExportGifGraphic(SvStream& rStream, const Graphic& rGraphic, FilterConfigItem* pConfigItem)
+{
+ GIFWriter aWriter(rStream);
+ return aWriter.WriteGIF(rGraphic, pConfigItem);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/egif/giflzwc.cxx b/vcl/source/filter/egif/giflzwc.cxx
new file mode 100644
index 000000000..41c65d2da
--- /dev/null
+++ b/vcl/source/filter/egif/giflzwc.cxx
@@ -0,0 +1,225 @@
+/* -*- 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 <tools/stream.hxx>
+#include "giflzwc.hxx"
+#include <array>
+
+
+class GIFImageDataOutputStream
+{
+private:
+
+ void FlushBlockBuf();
+ inline void FlushBitsBufsFullBytes();
+
+ SvStream& rStream;
+ std::array<sal_uInt8, 255>
+ pBlockBuf;
+ sal_uInt8 nBlockBufSize;
+ sal_uInt32 nBitsBuf;
+ sal_uInt16 nBitsBufSize;
+
+public:
+
+ GIFImageDataOutputStream( SvStream & rGIF, sal_uInt8 nLZWDataSize );
+ ~GIFImageDataOutputStream();
+
+ inline void WriteBits( sal_uInt16 nCode, sal_uInt16 nCodeLen );
+};
+
+
+inline void GIFImageDataOutputStream::FlushBitsBufsFullBytes()
+{
+ while (nBitsBufSize>=8)
+ {
+ if( nBlockBufSize==255 )
+ FlushBlockBuf();
+
+ pBlockBuf[nBlockBufSize++] = static_cast<sal_uInt8>(nBitsBuf);
+ nBitsBuf >>= 8;
+ nBitsBufSize -= 8;
+ }
+}
+
+
+inline void GIFImageDataOutputStream::WriteBits( sal_uInt16 nCode, sal_uInt16 nCodeLen )
+{
+ if( nBitsBufSize+nCodeLen>32 )
+ FlushBitsBufsFullBytes();
+
+ nBitsBuf |= static_cast<sal_uInt32>(nCode) << nBitsBufSize;
+ nBitsBufSize = nBitsBufSize + nCodeLen;
+}
+
+
+GIFImageDataOutputStream::GIFImageDataOutputStream( SvStream & rGIF, sal_uInt8 nLZWDataSize ) :
+ rStream(rGIF), nBlockBufSize(0), nBitsBuf(0), nBitsBufSize(0)
+{
+ rStream.WriteUChar( nLZWDataSize );
+}
+
+
+GIFImageDataOutputStream::~GIFImageDataOutputStream()
+{
+ WriteBits(0,7);
+ FlushBitsBufsFullBytes();
+ FlushBlockBuf();
+ rStream.WriteUChar( 0 );
+}
+
+
+void GIFImageDataOutputStream::FlushBlockBuf()
+{
+ if( nBlockBufSize )
+ {
+ rStream.WriteUChar( nBlockBufSize );
+ rStream.WriteBytes(pBlockBuf.data(), nBlockBufSize);
+ nBlockBufSize = 0;
+ }
+}
+
+
+struct GIFLZWCTreeNode
+{
+
+ GIFLZWCTreeNode* pBrother; // next node which has the same father
+ GIFLZWCTreeNode* pFirstChild; // first
+ sal_uInt16 nCode; // the code for the string of pixel values which comes about
+ sal_uInt16 nValue; // the pixel value
+};
+
+
+GIFLZWCompressor::GIFLZWCompressor()
+ : pPrefix(nullptr), nDataSize(0), nClearCode(0),
+ nEOICode(0), nTableSize(0), nCodeSize(0)
+{
+}
+
+
+GIFLZWCompressor::~GIFLZWCompressor()
+{
+ if (pIDOS!=nullptr) EndCompression();
+}
+
+
+void GIFLZWCompressor::StartCompression( SvStream& rGIF, sal_uInt16 nPixelSize )
+{
+ if( pIDOS )
+ return;
+
+ sal_uInt16 i;
+
+ nDataSize = nPixelSize;
+
+ if( nDataSize < 2 )
+ nDataSize=2;
+
+ nClearCode=1<<nDataSize;
+ nEOICode=nClearCode+1;
+ nTableSize=nEOICode+1;
+ nCodeSize=nDataSize+1;
+
+ pIDOS.reset(new GIFImageDataOutputStream(rGIF,static_cast<sal_uInt8>(nDataSize)));
+ pTable.reset(new GIFLZWCTreeNode[4096]);
+
+ for (i=0; i<4096; i++)
+ {
+ pTable[i].pBrother = pTable[i].pFirstChild = nullptr;
+ pTable[i].nCode = i;
+ pTable[i].nValue = static_cast<sal_uInt8>( i );
+ }
+
+ pPrefix = nullptr;
+ pIDOS->WriteBits( nClearCode,nCodeSize );
+}
+
+void GIFLZWCompressor::Compress(sal_uInt8* pSrc, sal_uInt32 nSize)
+{
+ if( !pIDOS )
+ return;
+
+ GIFLZWCTreeNode* p;
+ sal_uInt16 i;
+ sal_uInt8 nV;
+
+ if( !pPrefix && nSize )
+ {
+ pPrefix=&pTable[*pSrc++];
+ nSize--;
+ }
+
+ while( nSize )
+ {
+ nSize--;
+ nV=*pSrc++;
+ for( p=pPrefix->pFirstChild; p!=nullptr; p=p->pBrother )
+ {
+ if (p->nValue==nV)
+ break;
+ }
+
+ if( p)
+ pPrefix=p;
+ else
+ {
+ pIDOS->WriteBits(pPrefix->nCode,nCodeSize);
+
+ if (nTableSize==4096)
+ {
+ pIDOS->WriteBits(nClearCode,nCodeSize);
+
+ for (i=0; i<nClearCode; i++)
+ pTable[i].pFirstChild=nullptr;
+
+ nCodeSize=nDataSize+1;
+ nTableSize=nEOICode+1;
+ }
+ else
+ {
+ if(nTableSize==static_cast<sal_uInt16>(1<<nCodeSize))
+ nCodeSize++;
+
+ p=&pTable[nTableSize++];
+ p->pBrother=pPrefix->pFirstChild;
+ pPrefix->pFirstChild=p;
+ p->nValue=nV;
+ p->pFirstChild=nullptr;
+ }
+
+ pPrefix=&pTable[nV];
+ }
+ }
+}
+
+void GIFLZWCompressor::EndCompression()
+{
+ if( pIDOS )
+ {
+ if( pPrefix )
+ pIDOS->WriteBits(pPrefix->nCode,nCodeSize);
+
+ pIDOS->WriteBits( nEOICode,nCodeSize );
+ pTable.reset();
+ pIDOS.reset();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/egif/giflzwc.hxx b/vcl/source/filter/egif/giflzwc.hxx
new file mode 100644
index 000000000..057710c85
--- /dev/null
+++ b/vcl/source/filter/egif/giflzwc.hxx
@@ -0,0 +1,55 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_FILTER_SOURCE_GRAPHICFILTER_EGIF_GIFLZWC_HXX
+#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_EGIF_GIFLZWC_HXX
+
+#include <vcl/mapmod.hxx>
+
+
+class GIFImageDataOutputStream;
+struct GIFLZWCTreeNode;
+
+
+class GIFLZWCompressor
+{
+private:
+
+ std::unique_ptr<GIFImageDataOutputStream> pIDOS;
+ std::unique_ptr<GIFLZWCTreeNode[]> pTable;
+ GIFLZWCTreeNode* pPrefix;
+ sal_uInt16 nDataSize;
+ sal_uInt16 nClearCode;
+ sal_uInt16 nEOICode;
+ sal_uInt16 nTableSize;
+ sal_uInt16 nCodeSize;
+
+public:
+
+ GIFLZWCompressor();
+ ~GIFLZWCompressor();
+
+ void StartCompression( SvStream& rGIF, sal_uInt16 nPixelSize );
+ void Compress(sal_uInt8* pSrc, sal_uInt32 nSize);
+ void EndCompression();
+};
+
+#endif // INCLUDED_FILTER_SOURCE_GRAPHICFILTER_EGIF_GIFLZWC_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/eps/eps.cxx b/vcl/source/filter/eps/eps.cxx
new file mode 100644
index 000000000..bb25bdccb
--- /dev/null
+++ b/vcl/source/filter/eps/eps.cxx
@@ -0,0 +1,2667 @@
+/* -*- 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 <filter/EpsWriter.hxx>
+#include <tools/stream.hxx>
+#include <tools/poly.hxx>
+#include <tools/fract.hxx>
+#include <tools/helpers.hxx>
+#include <unotools/resmgr.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/region.hxx>
+#include <vcl/font.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/gradient.hxx>
+#include <unotools/configmgr.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <vcl/graphictools.hxx>
+#include <vcl/weld.hxx>
+#include <strings.hrc>
+#include <osl/diagnose.h>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+#include <officecfg/Office/Common.hxx>
+
+#include <cstdlib>
+#include <memory>
+
+using namespace ::com::sun::star::uno;
+
+#define POSTSCRIPT_BOUNDINGSEARCH 0x1000 // we only try to get the BoundingBox
+ // in the first 4096 bytes
+
+#define EPS_PREVIEW_TIFF 1
+#define EPS_PREVIEW_EPSI 2
+
+#define PS_LINESIZE 70 // maximum number of characters a line in the output
+
+// -----------------------------field-types------------------------------
+
+namespace {
+
+struct StackMember
+{
+ struct StackMember * pSucc;
+ Color aGlobalCol;
+ bool bLineCol;
+ Color aLineCol;
+ bool bFillCol;
+ Color aFillCol;
+ Color aTextCol;
+ bool bTextFillCol;
+ Color aTextFillCol;
+ Color aBackgroundCol;
+ vcl::Font aFont;
+ TextAlign eTextAlign;
+
+ double fLineWidth;
+ double fMiterLimit;
+ SvtGraphicStroke::CapType eLineCap;
+ SvtGraphicStroke::JoinType eJoinType;
+ SvtGraphicStroke::DashArray aDashArray;
+};
+
+struct PSLZWCTreeNode
+{
+
+ PSLZWCTreeNode* pBrother; // next node who has the same father
+ PSLZWCTreeNode* pFirstChild; // first son
+ sal_uInt16 nCode; // The code for the string of pixel values, which arises if... <missing comment>
+ sal_uInt16 nValue; // the pixel value
+};
+
+enum NMode {PS_NONE = 0x00, PS_SPACE = 0x01, PS_RET = 0x02, PS_WRAP = 0x04}; // formatting mode: action which is inserted behind the output
+inline NMode operator|(NMode a, NMode b)
+{
+ return static_cast<NMode>(static_cast<sal_uInt8>(a) | static_cast<sal_uInt8>(b));
+}
+
+class PSWriter
+{
+private:
+ bool mbStatus;
+ bool mbLevelWarning; // if there any embedded eps file which was not exported
+ sal_uInt32 mnLatestPush; // offset to streamposition, where last push was done
+
+ tools::Long mnLevel; // dialog options
+ bool mbGrayScale;
+ bool mbCompression;
+ sal_Int32 mnPreview;
+ sal_Int32 mnTextMode;
+
+ SvStream* mpPS;
+ const GDIMetaFile* pMTF;
+ std::unique_ptr<GDIMetaFile>
+ pAMTF; // only created if Graphics is not a Metafile
+ ScopedVclPtrInstance<VirtualDevice>
+ pVDev;
+
+ double nBoundingX2; // this represents the bounding box
+ double nBoundingY2;
+
+ StackMember* pGDIStack;
+ sal_uInt32 mnCursorPos; // current cursor position in output
+ Color aColor; // current color which is used for output
+ bool bLineColor;
+ Color aLineColor; // current GDIMetafile color settings
+ bool bFillColor;
+ Color aFillColor;
+ Color aTextColor;
+ bool bTextFillColor;
+ Color aTextFillColor;
+ Color aBackgroundColor;
+ TextAlign eTextAlign;
+
+ double fLineWidth;
+ double fMiterLimit;
+ SvtGraphicStroke::CapType eLineCap;
+ SvtGraphicStroke::JoinType eJoinType;
+ SvtGraphicStroke::DashArray aDashArray;
+
+ vcl::Font maFont;
+ vcl::Font maLastFont;
+
+ std::unique_ptr<PSLZWCTreeNode[]> pTable; // LZW compression data
+ PSLZWCTreeNode* pPrefix; // the compression is as same as the TIFF compression
+ sal_uInt16 nDataSize;
+ sal_uInt16 nClearCode;
+ sal_uInt16 nEOICode;
+ sal_uInt16 nTableSize;
+ sal_uInt16 nCodeSize;
+ sal_uInt32 nOffset;
+ sal_uInt32 dwShift;
+
+ css::uno::Reference< css::task::XStatusIndicator > xStatusIndicator;
+
+ void ImplWriteProlog( const Graphic* pPreviewEPSI );
+ void ImplWriteEpilog();
+ void ImplWriteActions( const GDIMetaFile& rMtf, VirtualDevice& rVDev );
+
+ // this method makes LF's, space inserting and word wrapping as used in all nMode
+ // parameters
+ inline void ImplExecMode( NMode nMode );
+
+ // writes char[] + LF to stream
+ inline void ImplWriteLine( const char*, NMode nMode = PS_RET );
+
+ // writes ( nNumb / 10^nCount ) in ASCII format to stream
+ void ImplWriteF( sal_Int32 nNumb, sal_uInt8 nCount = 3, NMode nMode = PS_SPACE );
+
+ // writes a double in ASCII format to stream
+ void ImplWriteDouble( double );
+
+ // writes a long in ASCII format to stream
+ void ImplWriteLong( sal_Int32 nNumb, NMode nMode = PS_SPACE );
+
+ // writes a byte in ASCII format to stream
+ void ImplWriteByte( sal_uInt8 nNumb, NMode nMode = PS_SPACE );
+
+ // writes a byte in ASCII (hex) format to stream
+ void ImplWriteHexByte( sal_uInt8 nNumb, NMode nMode = PS_WRAP );
+
+ // writes nNumb as number from 0.000 till 1.000 in ASCII format to stream
+ void ImplWriteB1( sal_uInt8 nNumb );
+
+ inline void ImplWritePoint( const Point& );
+ void ImplMoveTo( const Point& );
+ void ImplLineTo( const Point&, NMode nMode = PS_SPACE );
+ void ImplCurveTo( const Point& rP1, const Point& rP2, const Point& rP3, NMode nMode );
+ void ImplTranslate( const double& fX, const double& fY );
+ void ImplScale( const double& fX, const double& fY );
+
+ void ImplAddPath( const tools::Polygon & rPolygon );
+ void ImplWriteLineInfo( double fLineWidth, double fMiterLimit, SvtGraphicStroke::CapType eLineCap,
+ SvtGraphicStroke::JoinType eJoinType, SvtGraphicStroke::DashArray && rDashArray );
+ void ImplWriteLineInfo( const LineInfo& rLineInfo );
+ void ImplRect( const tools::Rectangle & rRectangle );
+ void ImplRectFill ( const tools::Rectangle & rRectangle );
+ void ImplWriteGradient( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, VirtualDevice& rVDev );
+ void ImplIntersect( const tools::PolyPolygon& rPolyPoly );
+ void ImplPolyPoly( const tools::PolyPolygon & rPolyPolygon, bool bTextOutline = false );
+ void ImplPolyLine( const tools::Polygon & rPolygon );
+
+ void ImplSetClipRegion( vcl::Region const & rRegion );
+ void ImplBmp( Bitmap const *, Bitmap const *, const Point &, double nWidth, double nHeight );
+ void ImplText( const OUString& rUniString, const Point& rPos, o3tl::span<const sal_Int32> pDXArry, sal_Int32 nWidth, VirtualDevice const & rVDev );
+ void ImplSetAttrForText( const Point & rPoint );
+ void ImplWriteCharacter( char );
+ void ImplWriteString( const OString&, VirtualDevice const & rVDev, o3tl::span<const sal_Int32> pDXArry, bool bStretch );
+ void ImplDefineFont( const char*, const char* );
+
+ void ImplClosePathDraw();
+ void ImplPathDraw();
+
+ inline void ImplWriteLineColor( NMode nMode );
+ inline void ImplWriteFillColor( NMode nMode );
+ inline void ImplWriteTextColor( NMode nMode );
+ void ImplWriteColor( NMode nMode );
+
+ static double ImplGetScaling( const MapMode& );
+ void ImplGetMapMode( const MapMode& );
+ static bool ImplGetBoundingBox( double* nNumb, sal_uInt8* pSource, sal_uInt32 nSize );
+ static sal_uInt8* ImplSearchEntry( sal_uInt8* pSource, sal_uInt8 const * pDest, sal_uInt32 nComp, sal_uInt32 nSize );
+ // LZW methods
+ void StartCompression();
+ void Compress( sal_uInt8 nSrc );
+ void EndCompression();
+ inline void WriteBits( sal_uInt16 nCode, sal_uInt16 nCodeLen );
+
+public:
+ bool WritePS( const Graphic& rGraphic, SvStream& rTargetStream, FilterConfigItem* );
+ PSWriter();
+};
+
+}
+
+//========================== methods from PSWriter ==========================
+
+
+PSWriter::PSWriter()
+ : mbStatus(false)
+ , mbLevelWarning(false)
+ , mnLatestPush(0)
+ , mnLevel(0)
+ , mbGrayScale(false)
+ , mbCompression(false)
+ , mnPreview(0)
+ , mnTextMode(0)
+ , mpPS(nullptr)
+ , pMTF(nullptr)
+ , nBoundingX2(0)
+ , nBoundingY2(0)
+ , pGDIStack(nullptr)
+ , mnCursorPos(0)
+ , bLineColor(false)
+ , bFillColor(false)
+ , bTextFillColor(false)
+ , eTextAlign()
+ , fLineWidth(0)
+ , fMiterLimit(0)
+ , eLineCap()
+ , eJoinType()
+ , pPrefix(nullptr)
+ , nDataSize(0)
+ , nClearCode(0)
+ , nEOICode(0)
+ , nTableSize(0)
+ , nCodeSize(0)
+ , nOffset(0)
+ , dwShift(0)
+{
+}
+
+bool PSWriter::WritePS( const Graphic& rGraphic, SvStream& rTargetStream, FilterConfigItem* pFilterConfigItem )
+{
+ sal_uInt32 nStreamPosition = 0, nPSPosition = 0; // -Wall warning, unset, check
+
+ mbStatus = true;
+ mnPreview = 0;
+ mbLevelWarning = false;
+ mnLatestPush = 0xEFFFFFFE;
+
+ if ( pFilterConfigItem )
+ {
+ xStatusIndicator = pFilterConfigItem->GetStatusIndicator();
+ if ( xStatusIndicator.is() )
+ {
+ xStatusIndicator->start( OUString(), 100 );
+ }
+ }
+
+ mpPS = &rTargetStream;
+ mpPS->SetEndian( SvStreamEndian::LITTLE );
+
+ // default values for the dialog options
+ mnLevel = 2;
+ mbGrayScale = false;
+#ifdef UNX // don't compress by default on unix as ghostscript is unable to read LZW compressed eps
+ mbCompression = false;
+#else
+ mbCompression = true;
+#endif
+ mnTextMode = 0; // default0 : export glyph outlines
+
+ // try to get the dialog selection
+ if ( pFilterConfigItem )
+ {
+#ifdef UNX // don't put binary tiff preview ahead of postscript code by default on unix as ghostscript is unable to read it
+ mnPreview = pFilterConfigItem->ReadInt32( "Preview", 0 );
+#else
+ mnPreview = pFilterConfigItem->ReadInt32( "Preview", 1 );
+#endif
+ mnLevel = pFilterConfigItem->ReadInt32( "Version", 2 );
+ if ( mnLevel != 1 )
+ mnLevel = 2;
+ mbGrayScale = pFilterConfigItem->ReadInt32( "ColorFormat", 1 ) == 2;
+#ifdef UNX // don't compress by default on unix as ghostscript is unable to read LZW compressed eps
+ mbCompression = pFilterConfigItem->ReadInt32( "CompressionMode", 0 ) != 0;
+#else
+ mbCompression = pFilterConfigItem->ReadInt32( "CompressionMode", 1 ) == 1;
+#endif
+ mnTextMode = pFilterConfigItem->ReadInt32( "TextMode", 0 );
+ if ( mnTextMode > 2 )
+ mnTextMode = 0;
+ }
+
+ // compression is not available for Level 1
+ if ( mnLevel == 1 )
+ {
+ mbGrayScale = true;
+ mbCompression = false;
+ }
+
+ if ( mnPreview & EPS_PREVIEW_TIFF )
+ {
+ rTargetStream.WriteUInt32( 0xC6D3D0C5 );
+ nStreamPosition = rTargetStream.Tell();
+ rTargetStream.WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 )
+ .WriteUInt32( nStreamPosition + 26 ).WriteUInt32( 0 ).WriteUInt16( 0xffff );
+
+ ErrCode nErrCode;
+ if ( mbGrayScale )
+ {
+ BitmapEx aTempBitmapEx( rGraphic.GetBitmapEx() );
+ aTempBitmapEx.Convert( BmpConversion::N8BitGreys );
+ nErrCode = GraphicConverter::Export( rTargetStream, aTempBitmapEx, ConvertDataFormat::TIF ) ;
+ }
+ else
+ nErrCode = GraphicConverter::Export( rTargetStream, rGraphic, ConvertDataFormat::TIF ) ;
+
+ if ( nErrCode == ERRCODE_NONE )
+ {
+ nPSPosition = rTargetStream.TellEnd();
+ rTargetStream.Seek( nStreamPosition + 20 );
+ rTargetStream.WriteUInt32( nPSPosition - 30 ); // size of tiff gfx
+ rTargetStream.Seek( nPSPosition );
+ }
+ else
+ {
+ mnPreview &=~ EPS_PREVIEW_TIFF;
+ rTargetStream.Seek( nStreamPosition - 4 );
+ }
+ }
+
+ // global default value setting
+ StackMember* pGS;
+
+ if (rGraphic.GetType() == GraphicType::GdiMetafile)
+ pMTF = &rGraphic.GetGDIMetaFile();
+ else if (rGraphic.GetGDIMetaFile().GetActionSize())
+ {
+ pAMTF.reset( new GDIMetaFile( rGraphic.GetGDIMetaFile() ) );
+ pMTF = pAMTF.get();
+ }
+ else
+ {
+ BitmapEx aBmp( rGraphic.GetBitmapEx() );
+ pAMTF.reset( new GDIMetaFile );
+ ScopedVclPtrInstance< VirtualDevice > pTmpVDev;
+ pAMTF->Record( pTmpVDev );
+ pTmpVDev->DrawBitmapEx( Point(), aBmp );
+ pAMTF->Stop();
+ pAMTF->SetPrefSize( aBmp.GetSizePixel() );
+ pMTF = pAMTF.get();
+ }
+ pVDev->SetMapMode( pMTF->GetPrefMapMode() );
+ nBoundingX2 = pMTF->GetPrefSize().Width();
+ nBoundingY2 = pMTF->GetPrefSize().Height();
+
+ pGDIStack = nullptr;
+ aColor = COL_TRANSPARENT;
+ bLineColor = true;
+ aLineColor = COL_BLACK;
+ bFillColor = true;
+ aFillColor = COL_WHITE;
+ bTextFillColor = true;
+ aTextFillColor = COL_BLACK;
+ fLineWidth = 1;
+ fMiterLimit = 15; // use same limit as most graphic systems and basegfx
+ eLineCap = SvtGraphicStroke::capButt;
+ eJoinType = SvtGraphicStroke::joinMiter;
+ aBackgroundColor = COL_WHITE;
+ eTextAlign = ALIGN_BASELINE;
+
+ if( pMTF->GetActionSize() )
+ {
+ ImplWriteProlog( ( mnPreview & EPS_PREVIEW_EPSI ) ? &rGraphic : nullptr );
+ mnCursorPos = 0;
+ ImplWriteActions( *pMTF, *pVDev );
+ ImplWriteEpilog();
+ if ( mnPreview & EPS_PREVIEW_TIFF )
+ {
+ sal_uInt32 nPosition = rTargetStream.Tell();
+ rTargetStream.Seek( nStreamPosition );
+ rTargetStream.WriteUInt32( nPSPosition );
+ rTargetStream.WriteUInt32( nPosition - nPSPosition );
+ rTargetStream.Seek( nPosition );
+ }
+ while( pGDIStack )
+ {
+ pGS=pGDIStack;
+ pGDIStack=pGS->pSucc;
+ delete pGS;
+ }
+ }
+ else
+ mbStatus = false;
+
+ if ( mbStatus && mbLevelWarning && pFilterConfigItem )
+ {
+ std::locale loc = Translate::Create("flt");
+ std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Info, VclButtonsType::Ok,
+ Translate::get(KEY_VERSION_CHECK, loc)));
+ xInfoBox->run();
+ }
+
+ if ( xStatusIndicator.is() )
+ xStatusIndicator->end();
+
+ return mbStatus;
+}
+
+void PSWriter::ImplWriteProlog( const Graphic* pPreview )
+{
+ ImplWriteLine( "%!PS-Adobe-3.0 EPSF-3.0 " );
+ mpPS->WriteCharPtr( "%%BoundingBox: " ); // BoundingBox
+ ImplWriteLong( 0 );
+ ImplWriteLong( 0 );
+ Size aSizePoint = OutputDevice::LogicToLogic( pMTF->GetPrefSize(),
+ pMTF->GetPrefMapMode(), MapMode(MapUnit::MapPoint));
+ ImplWriteLong( aSizePoint.Width() );
+ ImplWriteLong( aSizePoint.Height() ,PS_RET );
+ ImplWriteLine( "%%Pages: 0" );
+ OUString aCreator;
+ OUString aCreatorOverride = officecfg::Office::Common::Save::Document::GeneratorOverride::get();
+ if( !aCreatorOverride.isEmpty())
+ aCreator = aCreatorOverride;
+ else
+ aCreator = "%%Creator: " + utl::ConfigManager::getProductName() + " " +
+ utl::ConfigManager::getProductVersion();
+ ImplWriteLine( OUStringToOString( aCreator, RTL_TEXTENCODING_UTF8 ).getStr() );
+ ImplWriteLine( "%%Title: none" );
+ ImplWriteLine( "%%CreationDate: none" );
+
+// defaults
+
+ mpPS->WriteCharPtr( "%%LanguageLevel: " ); // Language level
+ ImplWriteLong( mnLevel, PS_RET );
+ if ( !mbGrayScale && mnLevel == 1 )
+ ImplWriteLine( "%%Extensions: CMYK" ); // CMYK extension is to set in color mode in level 1
+ ImplWriteLine( "%%EndComments" );
+ if ( pPreview && aSizePoint.Width() && aSizePoint.Height() )
+ {
+ Size aSizeBitmap( ( aSizePoint.Width() + 7 ) & ~7, aSizePoint.Height() );
+ Bitmap aTmpBitmap( pPreview->GetBitmapEx().GetBitmap() );
+ aTmpBitmap.Scale( aSizeBitmap, BmpScaleFlag::BestQuality );
+ aTmpBitmap.Convert( BmpConversion::N1BitThreshold );
+ BitmapReadAccess* pAcc = aTmpBitmap.AcquireReadAccess();
+ if ( pAcc )
+ {
+ mpPS->WriteCharPtr( "%%BeginPreview: " ); // BoundingBox
+ ImplWriteLong( aSizeBitmap.Width() );
+ ImplWriteLong( aSizeBitmap.Height() );
+ mpPS->WriteCharPtr( "1 " );
+ sal_Int32 nLines = aSizeBitmap.Width() / 312;
+ if ( ( nLines * 312 ) != aSizeBitmap.Width() )
+ nLines++;
+ nLines *= aSizeBitmap.Height();
+ ImplWriteLong( nLines );
+ sal_Int32 nCount2, nCount = 4;
+ const BitmapColor aBlack( pAcc->GetBestMatchingColor( COL_BLACK ) );
+ for ( tools::Long nY = 0; nY < aSizeBitmap.Height(); nY++ )
+ {
+ nCount2 = 0;
+ char nVal = 0;
+ Scanline pScanline = pAcc->GetScanline( nY );
+ for ( tools::Long nX = 0; nX < aSizeBitmap.Width(); nX++ )
+ {
+ if ( !nCount2 )
+ {
+ ImplExecMode( PS_RET );
+ mpPS->WriteCharPtr( "%" );
+ nCount2 = 312;
+ }
+ nVal <<= 1;
+ if ( pAcc->GetPixelFromData( pScanline, nX ) == aBlack )
+ nVal |= 1;
+ if ( ! ( --nCount ) )
+ {
+ if ( nVal > 9 )
+ nVal += 'A' - 10;
+ else
+ nVal += '0';
+ mpPS->WriteChar( nVal );
+ nVal = 0;
+ nCount += 4;
+ }
+ nCount2--;
+ }
+ }
+ Bitmap::ReleaseAccess( pAcc );
+ ImplExecMode( PS_RET );
+ ImplWriteLine( "%%EndPreview" );
+ }
+ }
+ ImplWriteLine( "%%BeginProlog" );
+ ImplWriteLine( "%%BeginResource: procset SDRes-Prolog 1.0 0" );
+
+// BEGIN EPSF
+ ImplWriteLine( "/b4_inc_state save def\n/dict_count countdictstack def\n/op_count count 1 sub def\nuserdict begin" );
+ ImplWriteLine( "0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin 10 setmiterlimit[] 0 setdash newpath" );
+ ImplWriteLine( "/languagelevel where {pop languagelevel 1 ne {false setstrokeadjust false setoverprint} if} if" );
+
+ ImplWriteLine( "/bdef {bind def} bind def" ); // the new operator bdef is created
+ if ( mbGrayScale )
+ ImplWriteLine( "/c {setgray} bdef" );
+ else
+ ImplWriteLine( "/c {setrgbcolor} bdef" );
+ ImplWriteLine( "/l {neg lineto} bdef" );
+ ImplWriteLine( "/rl {neg rlineto} bdef" );
+ ImplWriteLine( "/lc {setlinecap} bdef" );
+ ImplWriteLine( "/lj {setlinejoin} bdef" );
+ ImplWriteLine( "/lw {setlinewidth} bdef" );
+ ImplWriteLine( "/ml {setmiterlimit} bdef" );
+ ImplWriteLine( "/ld {setdash} bdef" );
+ ImplWriteLine( "/m {neg moveto} bdef" );
+ ImplWriteLine( "/ct {6 2 roll neg 6 2 roll neg 6 2 roll neg curveto} bdef" );
+ ImplWriteLine( "/r {rotate} bdef" );
+ ImplWriteLine( "/t {neg translate} bdef" );
+ ImplWriteLine( "/s {scale} bdef" );
+ ImplWriteLine( "/sw {show} bdef" );
+ ImplWriteLine( "/gs {gsave} bdef" );
+ ImplWriteLine( "/gr {grestore} bdef" );
+
+ ImplWriteLine( "/f {findfont dup length dict begin" ); // Setfont
+ ImplWriteLine( "{1 index /FID ne {def} {pop pop} ifelse} forall /Encoding ISOLatin1Encoding def" );
+ ImplWriteLine( "currentdict end /NFont exch definefont pop /NFont findfont} bdef" );
+
+ ImplWriteLine( "/p {closepath} bdef" );
+ ImplWriteLine( "/sf {scalefont setfont} bdef" );
+
+ ImplWriteLine( "/ef {eofill}bdef" ); // close path and fill
+ ImplWriteLine( "/pc {closepath stroke}bdef" ); // close path and draw
+ ImplWriteLine( "/ps {stroke}bdef" ); // draw current path
+ ImplWriteLine( "/pum {matrix currentmatrix}bdef" ); // pushes the current matrix
+ ImplWriteLine( "/pom {setmatrix}bdef" ); // pops the matrix
+ ImplWriteLine( "/bs {/aString exch def /nXOfs exch def /nWidth exch def currentpoint nXOfs 0 rmoveto pum nWidth aString stringwidth pop div 1 scale aString show pom moveto} bdef" );
+ ImplWriteLine( "%%EndResource" );
+ ImplWriteLine( "%%EndProlog" );
+ ImplWriteLine( "%%BeginSetup" );
+ ImplWriteLine( "%%EndSetup" );
+ ImplWriteLine( "%%Page: 1 1" );
+ ImplWriteLine( "%%BeginPageSetup" );
+ ImplWriteLine( "%%EndPageSetup" );
+ ImplWriteLine( "pum" );
+ ImplScale( static_cast<double>(aSizePoint.Width()) / static_cast<double>(pMTF->GetPrefSize().Width()), static_cast<double>(aSizePoint.Height()) / static_cast<double>(pMTF->GetPrefSize().Height()) );
+ ImplWriteDouble( 0 );
+ ImplWriteDouble( -pMTF->GetPrefSize().Height() );
+ ImplWriteLine( "t" );
+ ImplWriteLine( "/tm matrix currentmatrix def" );
+}
+
+void PSWriter::ImplWriteEpilog()
+{
+ ImplTranslate( 0, nBoundingY2 );
+ ImplWriteLine( "pom" );
+ ImplWriteLine( "count op_count sub {pop} repeat countdictstack dict_count sub {end} repeat b4_inc_state restore" );
+
+ ImplWriteLine( "%%PageTrailer" );
+ ImplWriteLine( "%%Trailer" );
+
+ ImplWriteLine( "%%EOF" );
+}
+
+void PSWriter::ImplWriteActions( const GDIMetaFile& rMtf, VirtualDevice& rVDev )
+{
+ tools::PolyPolygon aFillPath;
+
+ for( size_t nCurAction = 0, nCount = rMtf.GetActionSize(); nCurAction < nCount; nCurAction++ )
+ {
+ MetaAction* pMA = rMtf.GetAction( nCurAction );
+
+ switch( pMA->GetType() )
+ {
+ case MetaActionType::NONE :
+ break;
+
+ case MetaActionType::PIXEL :
+ {
+ Color aOldLineColor( aLineColor );
+ aLineColor = static_cast<const MetaPixelAction*>(pMA)->GetColor();
+ ImplWriteLineColor( PS_SPACE );
+ ImplMoveTo( static_cast<const MetaPixelAction*>(pMA)->GetPoint() );
+ ImplLineTo( static_cast<const MetaPixelAction*>(pMA)->GetPoint() );
+ ImplPathDraw();
+ aLineColor = aOldLineColor;
+ }
+ break;
+
+ case MetaActionType::POINT :
+ {
+ ImplWriteLineColor( PS_SPACE );
+ ImplMoveTo( static_cast<const MetaPointAction*>(pMA)->GetPoint() );
+ ImplLineTo( static_cast<const MetaPointAction*>(pMA)->GetPoint() );
+ ImplPathDraw();
+ }
+ break;
+
+ case MetaActionType::LINE :
+ {
+ const LineInfo& rLineInfo = static_cast<const MetaLineAction*>(pMA)->GetLineInfo();
+ ImplWriteLineInfo( rLineInfo );
+ if ( bLineColor )
+ {
+ ImplWriteLineColor( PS_SPACE );
+ ImplMoveTo( static_cast<const MetaLineAction*>(pMA)->GetStartPoint() );
+ ImplLineTo( static_cast<const MetaLineAction*>(pMA )->GetEndPoint() );
+ ImplPathDraw();
+ }
+ }
+ break;
+
+ case MetaActionType::RECT :
+ {
+ ImplRect( static_cast<const MetaRectAction*>(pMA)->GetRect() );
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT :
+ ImplRect( static_cast<const MetaRoundRectAction*>(pMA)->GetRect() );
+ break;
+
+ case MetaActionType::ELLIPSE :
+ {
+ tools::Rectangle aRect = static_cast<const MetaEllipseAction*>(pMA)->GetRect();
+ Point aCenter = aRect.Center();
+ tools::Polygon aPoly( aCenter, aRect.GetWidth() / 2, aRect.GetHeight() / 2 );
+ tools::PolyPolygon aPolyPoly( aPoly );
+ ImplPolyPoly( aPolyPoly );
+ }
+ break;
+
+ case MetaActionType::ARC :
+ {
+ tools::Polygon aPoly( static_cast<const MetaArcAction*>(pMA)->GetRect(), static_cast<const MetaArcAction*>(pMA)->GetStartPoint(),
+ static_cast<const MetaArcAction*>(pMA)->GetEndPoint(), PolyStyle::Arc );
+ tools::PolyPolygon aPolyPoly( aPoly );
+ ImplPolyPoly( aPolyPoly );
+ }
+ break;
+
+ case MetaActionType::PIE :
+ {
+ tools::Polygon aPoly( static_cast<const MetaPieAction*>(pMA)->GetRect(), static_cast<const MetaPieAction*>(pMA)->GetStartPoint(),
+ static_cast<const MetaPieAction*>(pMA)->GetEndPoint(), PolyStyle::Pie );
+ tools::PolyPolygon aPolyPoly( aPoly );
+ ImplPolyPoly( aPolyPoly );
+ }
+ break;
+
+ case MetaActionType::CHORD :
+ {
+ tools::Polygon aPoly( static_cast<const MetaChordAction*>(pMA)->GetRect(), static_cast<const MetaChordAction*>(pMA)->GetStartPoint(),
+ static_cast<const MetaChordAction*>(pMA)->GetEndPoint(), PolyStyle::Chord );
+ tools::PolyPolygon aPolyPoly( aPoly );
+ ImplPolyPoly( aPolyPoly );
+ }
+ break;
+
+ case MetaActionType::POLYLINE :
+ {
+ tools::Polygon aPoly( static_cast<const MetaPolyLineAction*>(pMA)->GetPolygon() );
+ const LineInfo& rLineInfo = static_cast<const MetaPolyLineAction*>(pMA)->GetLineInfo();
+ ImplWriteLineInfo( rLineInfo );
+
+ if(basegfx::B2DLineJoin::NONE == rLineInfo.GetLineJoin()
+ && rLineInfo.GetWidth() > 1)
+ {
+ // emulate B2DLineJoin::NONE by creating single edges
+ const sal_uInt16 nPoints(aPoly.GetSize());
+ const bool bCurve(aPoly.HasFlags());
+
+ for(sal_uInt16 a(0); a + 1 < nPoints; a++)
+ {
+ if(bCurve
+ && PolyFlags::Normal != aPoly.GetFlags(a + 1)
+ && a + 2 < nPoints
+ && PolyFlags::Normal != aPoly.GetFlags(a + 2)
+ && a + 3 < nPoints)
+ {
+ const tools::Polygon aSnippet(4,
+ aPoly.GetConstPointAry() + a,
+ aPoly.GetConstFlagAry() + a);
+ ImplPolyLine(aSnippet);
+ a += 2;
+ }
+ else
+ {
+ const tools::Polygon aSnippet(2,
+ aPoly.GetConstPointAry() + a);
+ ImplPolyLine(aSnippet);
+ }
+ }
+ }
+ else
+ {
+ ImplPolyLine( aPoly );
+ }
+ }
+ break;
+
+ case MetaActionType::POLYGON :
+ {
+ tools::PolyPolygon aPolyPoly( static_cast<const MetaPolygonAction*>(pMA)->GetPolygon() );
+ ImplPolyPoly( aPolyPoly );
+ }
+ break;
+
+ case MetaActionType::POLYPOLYGON :
+ {
+ ImplPolyPoly( static_cast<const MetaPolyPolygonAction*>(pMA)->GetPolyPolygon() );
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction * pA = static_cast<const MetaTextAction*>(pMA);
+
+ OUString aUniStr = pA->GetText().copy( pA->GetIndex(), pA->GetLen() );
+ Point aPoint( pA->GetPoint() );
+
+ ImplText( aUniStr, aPoint, {}, 0, rVDev );
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ OSL_FAIL( "Unsupported action: TextRect...Action!" );
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT :
+ {
+ const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pMA);
+ OUString aUniStr = pA->GetText().copy( pA->GetIndex(), pA->GetLen() );
+ Point aPoint( pA->GetPoint() );
+
+ ImplText( aUniStr, aPoint, {}, pA->GetWidth(), rVDev );
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pMA);
+ OUString aUniStr = pA->GetText().copy( pA->GetIndex(), pA->GetLen() );
+ Point aPoint( pA->GetPoint() );
+
+ ImplText( aUniStr, aPoint, pA->GetDXArray(), 0, rVDev );
+ }
+ break;
+
+ case MetaActionType::BMP :
+ {
+ Bitmap aBitmap = static_cast<const MetaBmpAction*>(pMA)->GetBitmap();
+ if ( mbGrayScale )
+ aBitmap.Convert( BmpConversion::N8BitGreys );
+ Point aPoint = static_cast<const MetaBmpAction*>(pMA)->GetPoint();
+ Size aSize( rVDev.PixelToLogic( aBitmap.GetSizePixel() ) );
+ ImplBmp( &aBitmap, nullptr, aPoint, aSize.Width(), aSize.Height() );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE :
+ {
+ Bitmap aBitmap = static_cast<const MetaBmpScaleAction*>(pMA)->GetBitmap();
+ if ( mbGrayScale )
+ aBitmap.Convert( BmpConversion::N8BitGreys );
+ Point aPoint = static_cast<const MetaBmpScaleAction*>(pMA)->GetPoint();
+ Size aSize = static_cast<const MetaBmpScaleAction*>(pMA)->GetSize();
+ ImplBmp( &aBitmap, nullptr, aPoint, aSize.Width(), aSize.Height() );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART :
+ {
+ Bitmap aBitmap( static_cast<const MetaBmpScalePartAction*>(pMA)->GetBitmap() );
+ aBitmap.Crop( tools::Rectangle( static_cast<const MetaBmpScalePartAction*>(pMA)->GetSrcPoint(),
+ static_cast<const MetaBmpScalePartAction*>(pMA)->GetSrcSize() ) );
+ if ( mbGrayScale )
+ aBitmap.Convert( BmpConversion::N8BitGreys );
+ Point aPoint = static_cast<const MetaBmpScalePartAction*>(pMA)->GetDestPoint();
+ Size aSize = static_cast<const MetaBmpScalePartAction*>(pMA)->GetDestSize();
+ ImplBmp( &aBitmap, nullptr, aPoint, aSize.Width(), aSize.Height() );
+ }
+ break;
+
+ case MetaActionType::BMPEX :
+ {
+ BitmapEx aBitmapEx( static_cast<MetaBmpExAction*>(pMA)->GetBitmapEx() );
+ Bitmap aBitmap( aBitmapEx.GetBitmap() );
+ if ( mbGrayScale )
+ aBitmap.Convert( BmpConversion::N8BitGreys );
+ Bitmap aMask( aBitmapEx.GetAlpha() );
+ Point aPoint( static_cast<const MetaBmpExAction*>(pMA)->GetPoint() );
+ Size aSize( rVDev.PixelToLogic( aBitmap.GetSizePixel() ) );
+ ImplBmp( &aBitmap, &aMask, aPoint, aSize.Width(), aSize.Height() );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE :
+ {
+ BitmapEx aBitmapEx( static_cast<MetaBmpExScaleAction*>(pMA)->GetBitmapEx() );
+ Bitmap aBitmap( aBitmapEx.GetBitmap() );
+ if ( mbGrayScale )
+ aBitmap.Convert( BmpConversion::N8BitGreys );
+ Bitmap aMask( aBitmapEx.GetAlpha() );
+ Point aPoint = static_cast<const MetaBmpExScaleAction*>(pMA)->GetPoint();
+ Size aSize( static_cast<const MetaBmpExScaleAction*>(pMA)->GetSize() );
+ ImplBmp( &aBitmap, &aMask, aPoint, aSize.Width(), aSize.Height() );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART :
+ {
+ BitmapEx aBitmapEx( static_cast<const MetaBmpExScalePartAction*>(pMA)->GetBitmapEx() );
+ aBitmapEx.Crop( tools::Rectangle( static_cast<const MetaBmpExScalePartAction*>(pMA)->GetSrcPoint(),
+ static_cast<const MetaBmpExScalePartAction*>(pMA)->GetSrcSize() ) );
+ Bitmap aBitmap( aBitmapEx.GetBitmap() );
+ if ( mbGrayScale )
+ aBitmap.Convert( BmpConversion::N8BitGreys );
+ Bitmap aMask( aBitmapEx.GetAlpha() );
+ Point aPoint = static_cast<const MetaBmpExScalePartAction*>(pMA)->GetDestPoint();
+ Size aSize = static_cast<const MetaBmpExScalePartAction*>(pMA)->GetDestSize();
+ ImplBmp( &aBitmap, &aMask, aPoint, aSize.Width(), aSize.Height() );
+ }
+ break;
+
+ // Unsupported Actions
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ case MetaActionType::MASKSCALEPART:
+ {
+ OSL_FAIL( "Unsupported action: MetaMask...Action!" );
+ }
+ break;
+
+ case MetaActionType::GRADIENT :
+ {
+ tools::PolyPolygon aPolyPoly( static_cast<const MetaGradientAction*>(pMA)->GetRect() );
+ ImplWriteGradient( aPolyPoly, static_cast<const MetaGradientAction*>(pMA)->GetGradient(), rVDev );
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX :
+ {
+ tools::PolyPolygon aPolyPoly( static_cast<const MetaGradientExAction*>(pMA)->GetPolyPolygon() );
+ ImplWriteGradient( aPolyPoly, static_cast<const MetaGradientExAction*>(pMA)->GetGradient(), rVDev );
+ }
+ break;
+
+ case MetaActionType::HATCH :
+ {
+ ScopedVclPtrInstance< VirtualDevice > l_pVirDev;
+ GDIMetaFile aTmpMtf;
+
+ l_pVirDev->SetMapMode( rVDev.GetMapMode() );
+ l_pVirDev->AddHatchActions( static_cast<const MetaHatchAction*>(pMA)->GetPolyPolygon(),
+ static_cast<const MetaHatchAction*>(pMA)->GetHatch(), aTmpMtf );
+ ImplWriteActions( aTmpMtf, rVDev );
+ }
+ break;
+
+ case MetaActionType::WALLPAPER :
+ {
+ const MetaWallpaperAction* pA = static_cast<const MetaWallpaperAction*>(pMA);
+ tools::Rectangle aRect = pA->GetRect();
+ const Wallpaper& aWallpaper = pA->GetWallpaper();
+
+ if ( aWallpaper.IsBitmap() )
+ {
+ BitmapEx aBitmapEx = aWallpaper.GetBitmap();
+ Bitmap aBitmap( aBitmapEx.GetBitmap() );
+ if ( aBitmapEx.IsAlpha() )
+ {
+ if ( aWallpaper.IsGradient() )
+ {
+
+ // gradient action
+
+ }
+ Bitmap aMask( aBitmapEx.GetAlpha() );
+ ImplBmp( &aBitmap, &aMask, Point( aRect.Left(), aRect.Top() ), aRect.GetWidth(), aRect.GetHeight() );
+ }
+ else
+ ImplBmp( &aBitmap, nullptr, Point( aRect.Left(), aRect.Top() ), aRect.GetWidth(), aRect.GetHeight() );
+
+ // wallpaper Style
+
+ }
+ else if ( aWallpaper.IsGradient() )
+ {
+
+ // gradient action
+
+ }
+ else
+ {
+ aColor = aWallpaper.GetColor();
+ ImplRectFill( aRect );
+ }
+ }
+ break;
+
+ case MetaActionType::ISECTRECTCLIPREGION:
+ {
+ const MetaISectRectClipRegionAction* pA = static_cast<const MetaISectRectClipRegionAction*>(pMA);
+ vcl::Region aRegion( pA->GetRect() );
+ ImplSetClipRegion( aRegion );
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ {
+ const MetaClipRegionAction* pA = static_cast<const MetaClipRegionAction*>(pMA);
+ const vcl::Region& aRegion( pA->GetRegion() );
+ ImplSetClipRegion( aRegion );
+ }
+ break;
+
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ {
+ const MetaISectRegionClipRegionAction* pA = static_cast<const MetaISectRegionClipRegionAction*>(pMA);
+ const vcl::Region& aRegion( pA->GetRegion() );
+ ImplSetClipRegion( aRegion );
+ }
+ break;
+
+ case MetaActionType::MOVECLIPREGION:
+ {
+ // TODO: Implement!
+ }
+ break;
+
+ case MetaActionType::LINECOLOR :
+ {
+ if ( static_cast<const MetaLineColorAction*>(pMA)->IsSetting() )
+ {
+ bLineColor = true;
+ aLineColor = static_cast<const MetaLineColorAction*>(pMA)->GetColor();
+ }
+ else
+ bLineColor = false;
+ }
+ break;
+
+ case MetaActionType::FILLCOLOR :
+ {
+ if ( static_cast<const MetaFillColorAction*>(pMA)->IsSetting() )
+ {
+ bFillColor = true;
+ aFillColor = static_cast<const MetaFillColorAction*>(pMA)->GetColor();
+ }
+ else
+ bFillColor = false;
+ }
+ break;
+
+ case MetaActionType::TEXTCOLOR :
+ {
+ aTextColor = static_cast<const MetaTextColorAction*>(pMA)->GetColor();
+ }
+ break;
+
+ case MetaActionType::TEXTFILLCOLOR :
+ {
+ if ( static_cast<const MetaTextFillColorAction*>(pMA)->IsSetting() )
+ {
+ bTextFillColor = true;
+ aTextFillColor = static_cast<const MetaTextFillColorAction*>(pMA)->GetColor();
+ }
+ else
+ bTextFillColor = false;
+ }
+ break;
+
+ case MetaActionType::TEXTALIGN :
+ {
+ eTextAlign = static_cast<const MetaTextAlignAction*>(pMA)->GetTextAlign();
+ }
+ break;
+
+ case MetaActionType::MAPMODE :
+ {
+ pMA->Execute( &rVDev );
+ ImplGetMapMode( rVDev.GetMapMode() );
+ }
+ break;
+
+ case MetaActionType::FONT :
+ {
+ maFont = static_cast<const MetaFontAction*>(pMA)->GetFont();
+ rVDev.SetFont( maFont );
+ }
+ break;
+
+ case MetaActionType::PUSH :
+ {
+ rVDev.Push(static_cast<const MetaPushAction*>(pMA)->GetFlags() );
+ StackMember* pGS = new StackMember;
+ pGS->pSucc = pGDIStack;
+ pGDIStack = pGS;
+ pGS->aDashArray = aDashArray;
+ pGS->eJoinType = eJoinType;
+ pGS->eLineCap = eLineCap;
+ pGS->fLineWidth = fLineWidth;
+ pGS->fMiterLimit = fMiterLimit;
+ pGS->eTextAlign = eTextAlign;
+ pGS->aGlobalCol = aColor;
+ pGS->bLineCol = bLineColor;
+ pGS->aLineCol = aLineColor;
+ pGS->bFillCol = bFillColor;
+ pGS->aFillCol = aFillColor;
+ pGS->aTextCol = aTextColor;
+ pGS->bTextFillCol = bTextFillColor;
+ pGS->aTextFillCol = aTextFillColor;
+ pGS->aBackgroundCol = aBackgroundColor;
+ pGS->aFont = maFont;
+ mnLatestPush = mpPS->Tell();
+ ImplWriteLine( "gs" );
+ }
+ break;
+
+ case MetaActionType::POP :
+ {
+ rVDev.Pop();
+ if( pGDIStack )
+ {
+ StackMember* pGS = pGDIStack;
+ pGDIStack = pGS->pSucc;
+ aDashArray = pGS->aDashArray;
+ eJoinType = pGS->eJoinType;
+ eLineCap = pGS->eLineCap;
+ fLineWidth = pGS->fLineWidth;
+ fMiterLimit = pGS->fMiterLimit;
+ eTextAlign = pGS->eTextAlign;
+ aColor = pGS->aGlobalCol;
+ bLineColor = pGS->bLineCol;
+ aLineColor = pGS->aLineCol;
+ bFillColor = pGS->bFillCol;
+ aFillColor = pGS->aFillCol;
+ aTextColor = pGS->aTextCol;
+ bTextFillColor = pGS->bTextFillCol;
+ aTextFillColor = pGS->aTextFillCol;
+ aBackgroundColor = pGS->aBackgroundCol;
+ maFont = pGS->aFont;
+ maLastFont = vcl::Font(); // set maLastFont != maFont -> so that
+ delete pGS;
+ sal_uInt32 nCurrentPos = mpPS->Tell();
+ if ( nCurrentPos - 3 == mnLatestPush )
+ {
+ mpPS->Seek( mnLatestPush );
+ ImplWriteLine( " " );
+ mpPS->Seek( mnLatestPush );
+ }
+ else
+ ImplWriteLine( "gr" );
+ }
+ }
+ break;
+
+ case MetaActionType::EPS :
+ {
+ GfxLink aGfxLink = static_cast<const MetaEPSAction*>(pMA)->GetLink();
+ const GDIMetaFile aSubstitute( static_cast<const MetaEPSAction*>(pMA)->GetSubstitute() );
+
+ bool bLevelConflict = false;
+ sal_uInt8* pSource = const_cast<sal_uInt8*>(aGfxLink.GetData());
+ sal_uInt32 nSize = aGfxLink.GetDataSize();
+ sal_uInt32 nParseThis = POSTSCRIPT_BOUNDINGSEARCH;
+ if ( nSize < 64 ) // assuming eps is larger than 64 bytes
+ pSource = nullptr;
+ if ( nParseThis > nSize )
+ nParseThis = nSize;
+
+ if ( pSource && ( mnLevel == 1 ) )
+ {
+ sal_uInt8* pFound = ImplSearchEntry( pSource, reinterpret_cast<sal_uInt8 const *>("%%LanguageLevel:"), nParseThis - 10, 16 );
+ if ( pFound )
+ {
+ sal_uInt8 k, i = 10;
+ pFound += 16;
+ while ( --i )
+ {
+ k = *pFound++;
+ if ( ( k > '0' ) && ( k <= '9' ) )
+ {
+ if ( k != '1' )
+ {
+ bLevelConflict = true;
+ mbLevelWarning = true;
+ }
+ break;
+ }
+ }
+ }
+ }
+ if ( !bLevelConflict )
+ {
+ double nBoundingBox[4];
+ if ( pSource && ImplGetBoundingBox( nBoundingBox, pSource, nParseThis ) )
+ {
+ Point aPoint = static_cast<const MetaEPSAction*>(pMA)->GetPoint();
+ Size aSize = static_cast<const MetaEPSAction*>(pMA)->GetSize();
+
+ MapMode aMapMode( aSubstitute.GetPrefMapMode() );
+ Size aOutSize( OutputDevice::LogicToLogic( aSize, rVDev.GetMapMode(), aMapMode ) );
+ Point aOrigin( OutputDevice::LogicToLogic( aPoint, rVDev.GetMapMode(), aMapMode ) );
+ aOrigin.AdjustY(aOutSize.Height() );
+ aMapMode.SetOrigin( aOrigin );
+ aMapMode.SetScaleX( Fraction(aOutSize.Width() / ( nBoundingBox[ 2 ] - nBoundingBox[ 0 ] )) );
+ aMapMode.SetScaleY( Fraction(aOutSize.Height() / ( nBoundingBox[ 3 ] - nBoundingBox[ 1 ] )) );
+ ImplWriteLine( "gs" );
+ ImplGetMapMode( aMapMode );
+ ImplWriteLine( "%%BeginDocument:" );
+ mpPS->WriteBytes(pSource, aGfxLink.GetDataSize());
+ ImplWriteLine( "%%EndDocument\ngr" );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::Transparent:
+ {
+ // TODO: implement!
+ }
+ break;
+
+ case MetaActionType::RASTEROP:
+ {
+ pMA->Execute( &rVDev );
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pMA);
+
+ GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() );
+ Point aSrcPt( aTmpMtf.GetPrefMapMode().GetOrigin() );
+ const Size aSrcSize( aTmpMtf.GetPrefSize() );
+ const Point aDestPt( pA->GetPoint() );
+ const Size aDestSize( pA->GetSize() );
+ const double fScaleX = aSrcSize.Width() ? static_cast<double>(aDestSize.Width()) / aSrcSize.Width() : 1.0;
+ const double fScaleY = aSrcSize.Height() ? static_cast<double>(aDestSize.Height()) / aSrcSize.Height() : 1.0;
+ tools::Long nMoveX, nMoveY;
+
+ if( fScaleX != 1.0 || fScaleY != 1.0 )
+ {
+ aTmpMtf.Scale( fScaleX, fScaleY );
+ aSrcPt.setX( FRound( aSrcPt.X() * fScaleX ) );
+ aSrcPt.setY( FRound( aSrcPt.Y() * fScaleY ) );
+ }
+
+ nMoveX = aDestPt.X() - aSrcPt.X();
+ nMoveY = aDestPt.Y() - aSrcPt.Y();
+
+ if( nMoveX || nMoveY )
+ aTmpMtf.Move( nMoveX, nMoveY );
+
+ ImplWriteActions( aTmpMtf, rVDev );
+ }
+ break;
+
+ case MetaActionType::COMMENT:
+ {
+ const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pMA);
+ if ( pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN") )
+ {
+ const MetaGradientExAction* pGradAction = nullptr;
+ while( ++nCurAction < nCount )
+ {
+ MetaAction* pAction = rMtf.GetAction( nCurAction );
+ if( pAction->GetType() == MetaActionType::GRADIENTEX )
+ pGradAction = static_cast<const MetaGradientExAction*>(pAction);
+ else if( ( pAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END") ) )
+ {
+ break;
+ }
+ }
+
+ if( pGradAction )
+ ImplWriteGradient( pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), rVDev );
+ }
+ else if ( pA->GetComment() == "XPATHFILL_SEQ_END" )
+ {
+ if ( aFillPath.Count() )
+ {
+ aFillPath = tools::PolyPolygon();
+ ImplWriteLine( "gr" );
+ }
+ }
+ else
+ {
+ const sal_uInt8* pData = pA->GetData();
+ if ( pData )
+ {
+ SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pData), pA->GetDataSize(), StreamMode::READ );
+ bool bSkipSequence = false;
+ OString sSeqEnd;
+
+ if( pA->GetComment() == "XPATHSTROKE_SEQ_BEGIN" )
+ {
+ sSeqEnd = "XPATHSTROKE_SEQ_END";
+ SvtGraphicStroke aStroke;
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+
+ tools::Polygon aPath;
+ aStroke.getPath( aPath );
+
+ tools::PolyPolygon aStartArrow;
+ tools::PolyPolygon aEndArrow;
+ double fStrokeWidth( aStroke.getStrokeWidth() );
+ SvtGraphicStroke::JoinType eJT( aStroke.getJoinType() );
+ SvtGraphicStroke::DashArray l_aDashArray;
+
+ aStroke.getStartArrow( aStartArrow );
+ aStroke.getEndArrow( aEndArrow );
+ aStroke.getDashArray( l_aDashArray );
+
+ bSkipSequence = true;
+ if ( l_aDashArray.size() > 11 ) // ps dasharray limit is 11
+ bSkipSequence = false;
+ if ( aStartArrow.Count() || aEndArrow.Count() )
+ bSkipSequence = false;
+ if ( static_cast<sal_uInt32>(eJT) > 2 )
+ bSkipSequence = false;
+ if ( !l_aDashArray.empty() && ( fStrokeWidth != 0.0 ) )
+ bSkipSequence = false;
+ if ( bSkipSequence )
+ {
+ ImplWriteLineInfo( fStrokeWidth, aStroke.getMiterLimit(),
+ aStroke.getCapType(), eJT, std::move(l_aDashArray) );
+ ImplPolyLine( aPath );
+ }
+ }
+ else if (pA->GetComment() == "XPATHFILL_SEQ_BEGIN")
+ {
+ sSeqEnd = "XPATHFILL_SEQ_END";
+ SvtGraphicFill aFill;
+ ReadSvtGraphicFill( aMemStm, aFill );
+ switch( aFill.getFillType() )
+ {
+ case SvtGraphicFill::fillSolid :
+ {
+ bSkipSequence = true;
+ tools::PolyPolygon aPolyPoly;
+ aFill.getPath( aPolyPoly );
+ sal_uInt16 i, nPolyCount = aPolyPoly.Count();
+ if ( nPolyCount )
+ {
+ aFillColor = aFill.getFillColor();
+ ImplWriteFillColor( PS_SPACE );
+ for ( i = 0; i < nPolyCount; )
+ {
+ ImplAddPath( aPolyPoly.GetObject( i ) );
+ if ( ++i < nPolyCount )
+ {
+ mpPS->WriteCharPtr( "p" );
+ mnCursorPos += 2;
+ ImplExecMode( PS_RET );
+ }
+ }
+ mpPS->WriteCharPtr( "p ef" );
+ mnCursorPos += 4;
+ ImplExecMode( PS_RET );
+ }
+ }
+ break;
+
+ case SvtGraphicFill::fillTexture :
+ {
+ aFill.getPath( aFillPath );
+
+ /* normally an object filling is consisting of three MetaActions:
+ MetaBitmapAction using RasterOp xor,
+ MetaPolyPolygonAction using RasterOp rop_0
+ MetaBitmapAction using RasterOp xor
+
+ Because RasterOps cannot been used in Postscript, we have to
+ replace these actions. The MetaComment "XPATHFILL_SEQ_BEGIN" is
+ providing the clippath of the object. The following loop is
+ trying to find the bitmap that is matching the clippath, so that
+ only one bitmap is exported, otherwise if the bitmap is not
+ locatable, all metaactions are played normally.
+ */
+ sal_uInt32 nCommentStartAction = nCurAction;
+ sal_uInt32 nBitmapCount = 0;
+ sal_uInt32 nBitmapAction = 0;
+
+ bool bOk = true;
+ while( bOk && ( ++nCurAction < nCount ) )
+ {
+ MetaAction* pAction = rMtf.GetAction( nCurAction );
+ switch( pAction->GetType() )
+ {
+ case MetaActionType::BMPSCALE :
+ case MetaActionType::BMPSCALEPART :
+ case MetaActionType::BMPEXSCALE :
+ case MetaActionType::BMPEXSCALEPART :
+ {
+ nBitmapCount++;
+ nBitmapAction = nCurAction;
+ }
+ break;
+ case MetaActionType::COMMENT :
+ {
+ if (static_cast<const MetaCommentAction*>(pAction)->GetComment() == "XPATHFILL_SEQ_END")
+ bOk = false;
+ }
+ break;
+ default: break;
+ }
+ }
+ if( nBitmapCount == 2 )
+ {
+ ImplWriteLine( "gs" );
+ ImplIntersect( aFillPath );
+ GDIMetaFile aTempMtf;
+ aTempMtf.AddAction( rMtf.GetAction( nBitmapAction )->Clone() );
+ ImplWriteActions( aTempMtf, rVDev );
+ ImplWriteLine( "gr" );
+ aFillPath = tools::PolyPolygon();
+ }
+ else
+ nCurAction = nCommentStartAction + 1;
+ }
+ break;
+
+ case SvtGraphicFill::fillGradient :
+ aFill.getPath( aFillPath );
+ break;
+
+ case SvtGraphicFill::fillHatch :
+ break;
+ }
+ if ( aFillPath.Count() )
+ {
+ ImplWriteLine( "gs" );
+ ImplIntersect( aFillPath );
+ }
+ }
+ if ( bSkipSequence )
+ {
+ while( ++nCurAction < nCount )
+ {
+ pMA = rMtf.GetAction( nCurAction );
+ if ( pMA->GetType() == MetaActionType::COMMENT )
+ {
+ OString sComment( static_cast<MetaCommentAction*>(pMA)->GetComment() );
+ if ( sComment == sSeqEnd )
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+ default: break;
+ }
+ }
+}
+
+inline void PSWriter::ImplWritePoint( const Point& rPoint )
+{
+ ImplWriteDouble( rPoint.X() );
+ ImplWriteDouble( rPoint.Y() );
+}
+
+void PSWriter::ImplMoveTo( const Point& rPoint )
+{
+ ImplWritePoint( rPoint );
+ ImplWriteByte( 'm' );
+ ImplExecMode( PS_SPACE );
+}
+
+void PSWriter::ImplLineTo( const Point& rPoint, NMode nMode )
+{
+ ImplWritePoint( rPoint );
+ ImplWriteByte( 'l' );
+ ImplExecMode( nMode );
+}
+
+void PSWriter::ImplCurveTo( const Point& rP1, const Point& rP2, const Point& rP3, NMode nMode )
+{
+ ImplWritePoint( rP1 );
+ ImplWritePoint( rP2 );
+ ImplWritePoint( rP3 );
+ mpPS->WriteCharPtr( "ct " );
+ ImplExecMode( nMode );
+}
+
+void PSWriter::ImplTranslate( const double& fX, const double& fY )
+{
+ ImplWriteDouble( fX );
+ ImplWriteDouble( fY );
+ ImplWriteByte( 't' );
+ ImplExecMode( PS_RET );
+}
+
+void PSWriter::ImplScale( const double& fX, const double& fY )
+{
+ ImplWriteDouble( fX );
+ ImplWriteDouble( fY );
+ ImplWriteByte( 's' );
+ ImplExecMode( PS_RET );
+}
+
+void PSWriter::ImplRect( const tools::Rectangle & rRect )
+{
+ if ( bFillColor )
+ ImplRectFill( rRect );
+ if ( bLineColor )
+ {
+ double nWidth = rRect.GetWidth();
+ double nHeight = rRect.GetHeight();
+
+ ImplWriteLineColor( PS_SPACE );
+ ImplMoveTo( rRect.TopLeft() );
+ ImplWriteDouble( nWidth );
+ mpPS->WriteCharPtr( "0 rl 0 " );
+ ImplWriteDouble( nHeight );
+ mpPS->WriteCharPtr( "rl " );
+ ImplWriteDouble( nWidth );
+ mpPS->WriteCharPtr( "neg 0 rl " );
+ ImplClosePathDraw();
+ }
+ mpPS->WriteUChar( 10 );
+ mnCursorPos = 0;
+}
+
+void PSWriter::ImplRectFill( const tools::Rectangle & rRect )
+{
+ double nWidth = rRect.GetWidth();
+ double nHeight = rRect.GetHeight();
+
+ ImplWriteFillColor( PS_SPACE );
+ ImplMoveTo( rRect.TopLeft() );
+ ImplWriteDouble( nWidth );
+ mpPS->WriteCharPtr( "0 rl 0 " );
+ ImplWriteDouble( nHeight );
+ mpPS->WriteCharPtr( "rl " );
+ ImplWriteDouble( nWidth );
+ mpPS->WriteCharPtr( "neg 0 rl ef " );
+ mpPS->WriteCharPtr( "p ef" );
+ mnCursorPos += 2;
+ ImplExecMode( PS_RET );
+}
+
+void PSWriter::ImplAddPath( const tools::Polygon & rPolygon )
+{
+ sal_uInt16 nPointCount = rPolygon.GetSize();
+ if ( nPointCount <= 1 )
+ return;
+
+ sal_uInt16 i = 1;
+ ImplMoveTo( rPolygon.GetPoint( 0 ) );
+ while ( i < nPointCount )
+ {
+ if ( ( rPolygon.GetFlags( i ) == PolyFlags::Control )
+ && ( ( i + 2 ) < nPointCount )
+ && ( rPolygon.GetFlags( i + 1 ) == PolyFlags::Control )
+ && ( rPolygon.GetFlags( i + 2 ) != PolyFlags::Control ) )
+ {
+ ImplCurveTo( rPolygon[ i ], rPolygon[ i + 1 ], rPolygon[ i + 2 ], PS_WRAP );
+ i += 3;
+ }
+ else
+ ImplLineTo( rPolygon.GetPoint( i++ ), PS_SPACE | PS_WRAP );
+ }
+}
+
+void PSWriter::ImplIntersect( const tools::PolyPolygon& rPolyPoly )
+{
+ sal_uInt16 i, nPolyCount = rPolyPoly.Count();
+ for ( i = 0; i < nPolyCount; )
+ {
+ ImplAddPath( rPolyPoly.GetObject( i ) );
+ if ( ++i < nPolyCount )
+ {
+ mpPS->WriteCharPtr( "p" );
+ mnCursorPos += 2;
+ ImplExecMode( PS_RET );
+ }
+ }
+ ImplWriteLine( "eoclip newpath" );
+}
+
+void PSWriter::ImplWriteGradient( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, VirtualDevice& rVDev )
+{
+ ScopedVclPtrInstance< VirtualDevice > l_pVDev;
+ GDIMetaFile aTmpMtf;
+ l_pVDev->SetMapMode( rVDev.GetMapMode() );
+ Gradient aGradient(rGradient);
+ aGradient.AddGradientActions( rPolyPoly.GetBoundRect(), aTmpMtf );
+ ImplWriteActions( aTmpMtf, rVDev );
+}
+
+void PSWriter::ImplPolyPoly( const tools::PolyPolygon & rPolyPoly, bool bTextOutline )
+{
+ sal_uInt16 i, nPolyCount = rPolyPoly.Count();
+ if ( !nPolyCount )
+ return;
+
+ if ( bFillColor || bTextOutline )
+ {
+ if ( bTextOutline )
+ ImplWriteTextColor( PS_SPACE );
+ else
+ ImplWriteFillColor( PS_SPACE );
+ for ( i = 0; i < nPolyCount; )
+ {
+ ImplAddPath( rPolyPoly.GetObject( i ) );
+ if ( ++i < nPolyCount )
+ {
+ mpPS->WriteCharPtr( "p" );
+ mnCursorPos += 2;
+ ImplExecMode( PS_RET );
+ }
+ }
+ mpPS->WriteCharPtr( "p ef" );
+ mnCursorPos += 4;
+ ImplExecMode( PS_RET );
+ }
+ if ( bLineColor )
+ {
+ ImplWriteLineColor( PS_SPACE );
+ for ( i = 0; i < nPolyCount; i++ )
+ ImplAddPath( rPolyPoly.GetObject( i ) );
+ ImplClosePathDraw();
+ }
+}
+
+void PSWriter::ImplPolyLine( const tools::Polygon & rPoly )
+{
+ if ( !bLineColor )
+ return;
+
+ ImplWriteLineColor( PS_SPACE );
+ sal_uInt16 i, nPointCount = rPoly.GetSize();
+ if ( !nPointCount )
+ return;
+
+ if ( nPointCount > 1 )
+ {
+ ImplMoveTo( rPoly.GetPoint( 0 ) );
+ i = 1;
+ while ( i < nPointCount )
+ {
+ if ( ( rPoly.GetFlags( i ) == PolyFlags::Control )
+ && ( ( i + 2 ) < nPointCount )
+ && ( rPoly.GetFlags( i + 1 ) == PolyFlags::Control )
+ && ( rPoly.GetFlags( i + 2 ) != PolyFlags::Control ) )
+ {
+ ImplCurveTo( rPoly[ i ], rPoly[ i + 1 ], rPoly[ i + 2 ], PS_WRAP );
+ i += 3;
+ }
+ else
+ ImplLineTo( rPoly.GetPoint( i++ ), PS_SPACE | PS_WRAP );
+ }
+ }
+
+ // #104645# explicitly close path if polygon is closed
+ if( rPoly[ 0 ] == rPoly[ nPointCount-1 ] )
+ ImplClosePathDraw();
+ else
+ ImplPathDraw();
+}
+
+void PSWriter::ImplSetClipRegion( vcl::Region const & rClipRegion )
+{
+ if ( rClipRegion.IsEmpty() )
+ return;
+
+ RectangleVector aRectangles;
+ rClipRegion.GetRegionRectangles(aRectangles);
+
+ for (auto const& rectangle : aRectangles)
+ {
+ double nX1(rectangle.Left());
+ double nY1(rectangle.Top());
+ double nX2(rectangle.Right());
+ double nY2(rectangle.Bottom());
+
+ ImplWriteDouble( nX1 );
+ ImplWriteDouble( nY1 );
+ ImplWriteByte( 'm' );
+ ImplWriteDouble( nX2 );
+ ImplWriteDouble( nY1 );
+ ImplWriteByte( 'l' );
+ ImplWriteDouble( nX2 );
+ ImplWriteDouble( nY2 );
+ ImplWriteByte( 'l' );
+ ImplWriteDouble( nX1 );
+ ImplWriteDouble( nY2 );
+ ImplWriteByte( 'l' );
+ ImplWriteDouble( nX1 );
+ ImplWriteDouble( nY1 );
+ ImplWriteByte( 'l', PS_SPACE | PS_WRAP );
+ }
+
+ ImplWriteLine( "eoclip newpath" );
+}
+
+// possible gfx formats:
+//
+// level 1: grayscale 8 bit
+// color 24 bit
+//
+// level 2: grayscale 8 bit
+// color 1(pal), 4(pal), 8(pal), 24 Bit
+//
+
+void PSWriter::ImplBmp( Bitmap const * pBitmap, Bitmap const * pMaskBitmap, const Point & rPoint, double nXWidth, double nYHeightOrg )
+{
+ if ( !pBitmap )
+ return;
+
+ sal_Int32 nHeightOrg = pBitmap->GetSizePixel().Height();
+ sal_Int32 nHeightLeft = nHeightOrg;
+ tools::Long nWidth = pBitmap->GetSizePixel().Width();
+ Point aSourcePos( rPoint );
+
+ while ( nHeightLeft )
+ {
+ Bitmap aTileBitmap( *pBitmap );
+ tools::Long nHeight = nHeightLeft;
+ double nYHeight = nYHeightOrg;
+
+ bool bDoTrans = false;
+
+ tools::Rectangle aRect;
+ vcl::Region aRegion;
+
+ if ( pMaskBitmap )
+ {
+ bDoTrans = true;
+ while (true)
+ {
+ if ( mnLevel == 1 && nHeight > 10 )
+ nHeight = 8;
+ aRect = tools::Rectangle( Point( 0, nHeightOrg - nHeightLeft ), Size( nWidth, nHeight ) );
+ aRegion = pMaskBitmap->CreateRegion( COL_BLACK, aRect );
+
+ if( mnLevel == 1 )
+ {
+ RectangleVector aRectangleVector;
+ aRegion.GetRegionRectangles(aRectangleVector);
+
+ if ( aRectangleVector.size() * 5 > 1000 )
+ {
+ nHeight >>= 1;
+ if ( nHeight < 2 )
+ return;
+ continue;
+ }
+ }
+ break;
+ }
+ }
+ if ( nHeight != nHeightOrg )
+ {
+ nYHeight = nYHeightOrg * nHeight / nHeightOrg;
+ aTileBitmap.Crop( tools::Rectangle( Point( 0, nHeightOrg - nHeightLeft ), Size( nWidth, nHeight ) ) );
+ }
+ if ( bDoTrans )
+ {
+ ImplWriteLine( "gs\npum" );
+ ImplTranslate( aSourcePos.X(), aSourcePos.Y() );
+ ImplScale( nXWidth / nWidth, nYHeight / nHeight );
+
+ RectangleVector aRectangles;
+ aRegion.GetRegionRectangles(aRectangles);
+ const tools::Long nMoveVertical(nHeightLeft - nHeightOrg);
+
+ for (auto & rectangle : aRectangles)
+ {
+ rectangle.Move(0, nMoveVertical);
+
+ ImplWriteLong( rectangle.Left() );
+ ImplWriteLong( rectangle.Top() );
+ ImplWriteByte( 'm' );
+ ImplWriteLong( rectangle.Right() + 1 );
+ ImplWriteLong( rectangle.Top() );
+ ImplWriteByte( 'l' );
+ ImplWriteLong( rectangle.Right() + 1 );
+ ImplWriteLong( rectangle.Bottom() + 1 );
+ ImplWriteByte( 'l' );
+ ImplWriteLong( rectangle.Left() );
+ ImplWriteLong( rectangle.Bottom() + 1 );
+ ImplWriteByte( 'l' );
+ ImplWriteByte( 'p', PS_SPACE | PS_WRAP );
+ }
+
+ ImplWriteLine( "eoclip newpath" );
+ ImplWriteLine( "pom" );
+ }
+ BitmapReadAccess* pAcc = aTileBitmap.AcquireReadAccess();
+
+ if (!bDoTrans )
+ ImplWriteLine( "pum" );
+
+ ImplTranslate( aSourcePos.X(), aSourcePos.Y() + nYHeight );
+ ImplScale( nXWidth, nYHeight );
+ if ( mnLevel == 1 ) // level 1 is always grayscale !!!
+ {
+ ImplWriteLong( nWidth );
+ ImplWriteLong( nHeight );
+ mpPS->WriteCharPtr( "8 [" );
+ ImplWriteLong( nWidth );
+ mpPS->WriteCharPtr( "0 0 " );
+ ImplWriteLong( -nHeight );
+ ImplWriteLong( 0 );
+ ImplWriteLong( nHeight );
+ ImplWriteLine( "]" );
+ mpPS->WriteCharPtr( "{currentfile " );
+ ImplWriteLong( nWidth );
+ ImplWriteLine( "string readhexstring pop}" );
+ ImplWriteLine( "image" );
+ for ( tools::Long y = 0; y < nHeight; y++ )
+ {
+ Scanline pScanlineRead = pAcc->GetScanline( y );
+ for ( tools::Long x = 0; x < nWidth; x++ )
+ {
+ ImplWriteHexByte( pAcc->GetIndexFromData( pScanlineRead, x ) );
+ }
+ }
+ mpPS->WriteUChar( 10 );
+ }
+ else // Level 2
+ {
+ if ( mbGrayScale )
+ {
+ ImplWriteLine( "/DeviceGray setcolorspace" );
+ ImplWriteLine( "<<" );
+ ImplWriteLine( "/ImageType 1" );
+ mpPS->WriteCharPtr( "/Width " );
+ ImplWriteLong( nWidth, PS_RET );
+ mpPS->WriteCharPtr( "/Height " );
+ ImplWriteLong( nHeight, PS_RET );
+ ImplWriteLine( "/BitsPerComponent 8" );
+ ImplWriteLine( "/Decode[0 1]" );
+ mpPS->WriteCharPtr( "/ImageMatrix[" );
+ ImplWriteLong( nWidth );
+ mpPS->WriteCharPtr( "0 0 " );
+ ImplWriteLong( -nHeight );
+ ImplWriteLong( 0 );
+ ImplWriteLong( nHeight, PS_NONE );
+ ImplWriteByte( ']', PS_RET );
+ ImplWriteLine( "/DataSource currentfile" );
+ ImplWriteLine( "/ASCIIHexDecode filter" );
+ if ( mbCompression )
+ ImplWriteLine( "/LZWDecode filter" );
+ ImplWriteLine( ">>" );
+ ImplWriteLine( "image" );
+ if ( mbCompression )
+ {
+ StartCompression();
+ for ( tools::Long y = 0; y < nHeight; y++ )
+ {
+ Scanline pScanlineRead = pAcc->GetScanline( y );
+ for ( tools::Long x = 0; x < nWidth; x++ )
+ {
+ Compress( pAcc->GetIndexFromData( pScanlineRead, x ) );
+ }
+ }
+ EndCompression();
+ }
+ else
+ {
+ for ( tools::Long y = 0; y < nHeight; y++ )
+ {
+ Scanline pScanlineRead = pAcc->GetScanline( y );
+ for ( tools::Long x = 0; x < nWidth; x++ )
+ {
+ ImplWriteHexByte( pAcc->GetIndexFromData( pScanlineRead, x ) );
+ }
+ }
+ }
+ }
+ else
+ {
+ // have we to write a palette ?
+
+ if ( pAcc->HasPalette() )
+ {
+ ImplWriteLine( "[/Indexed /DeviceRGB " );
+ ImplWriteLong( pAcc->GetPaletteEntryCount() - 1, PS_RET );
+ ImplWriteByte( '<', PS_NONE );
+ for ( sal_uInt16 i = 0; i < pAcc->GetPaletteEntryCount(); i++ )
+ {
+ BitmapColor aBitmapColor = pAcc->GetPaletteColor( i );
+ ImplWriteHexByte( aBitmapColor.GetRed(), PS_NONE );
+ ImplWriteHexByte( aBitmapColor.GetGreen(), PS_NONE );
+ ImplWriteHexByte( aBitmapColor.GetBlue(), PS_SPACE | PS_WRAP );
+ }
+ ImplWriteByte( '>', PS_RET );
+
+ ImplWriteLine( "] setcolorspace" );
+ ImplWriteLine( "<<" );
+ ImplWriteLine( "/ImageType 1" );
+ mpPS->WriteCharPtr( "/Width " );
+ ImplWriteLong( nWidth, PS_RET );
+ mpPS->WriteCharPtr( "/Height " );
+ ImplWriteLong( nHeight, PS_RET );
+ ImplWriteLine( "/BitsPerComponent 8" );
+ ImplWriteLine( "/Decode[0 255]" );
+ mpPS->WriteCharPtr( "/ImageMatrix[" );
+ ImplWriteLong( nWidth );
+ mpPS->WriteCharPtr( "0 0 " );
+ ImplWriteLong( -nHeight );
+ ImplWriteLong( 0);
+ ImplWriteLong( nHeight, PS_NONE );
+ ImplWriteByte( ']', PS_RET );
+ ImplWriteLine( "/DataSource currentfile" );
+ ImplWriteLine( "/ASCIIHexDecode filter" );
+ if ( mbCompression )
+ ImplWriteLine( "/LZWDecode filter" );
+ ImplWriteLine( ">>" );
+ ImplWriteLine( "image" );
+ if ( mbCompression )
+ {
+ StartCompression();
+ for ( tools::Long y = 0; y < nHeight; y++ )
+ {
+ Scanline pScanlineRead = pAcc->GetScanline( y );
+ for ( tools::Long x = 0; x < nWidth; x++ )
+ {
+ Compress( pAcc->GetIndexFromData( pScanlineRead, x ) );
+ }
+ }
+ EndCompression();
+ }
+ else
+ {
+ for ( tools::Long y = 0; y < nHeight; y++ )
+ {
+ Scanline pScanlineRead = pAcc->GetScanline( y );
+ for ( tools::Long x = 0; x < nWidth; x++ )
+ {
+ ImplWriteHexByte( pAcc->GetIndexFromData( pScanlineRead, x ) );
+ }
+ }
+ }
+ }
+ else // 24 bit color
+ {
+ ImplWriteLine( "/DeviceRGB setcolorspace" );
+ ImplWriteLine( "<<" );
+ ImplWriteLine( "/ImageType 1" );
+ mpPS->WriteCharPtr( "/Width " );
+ ImplWriteLong( nWidth, PS_RET );
+ mpPS->WriteCharPtr( "/Height " );
+ ImplWriteLong( nHeight, PS_RET );
+ ImplWriteLine( "/BitsPerComponent 8" );
+ ImplWriteLine( "/Decode[0 1 0 1 0 1]" );
+ mpPS->WriteCharPtr( "/ImageMatrix[" );
+ ImplWriteLong( nWidth );
+ mpPS->WriteCharPtr( "0 0 " );
+ ImplWriteLong( -nHeight );
+ ImplWriteLong( 0 );
+ ImplWriteLong( nHeight, PS_NONE );
+ ImplWriteByte( ']', PS_RET );
+ ImplWriteLine( "/DataSource currentfile" );
+ ImplWriteLine( "/ASCIIHexDecode filter" );
+ if ( mbCompression )
+ ImplWriteLine( "/LZWDecode filter" );
+ ImplWriteLine( ">>" );
+ ImplWriteLine( "image" );
+ if ( mbCompression )
+ {
+ StartCompression();
+ for ( tools::Long y = 0; y < nHeight; y++ )
+ {
+ Scanline pScanlineRead = pAcc->GetScanline( y );
+ for ( tools::Long x = 0; x < nWidth; x++ )
+ {
+ const BitmapColor aBitmapColor( pAcc->GetPixelFromData( pScanlineRead, x ) );
+ Compress( aBitmapColor.GetRed() );
+ Compress( aBitmapColor.GetGreen() );
+ Compress( aBitmapColor.GetBlue() );
+ }
+ }
+ EndCompression();
+ }
+ else
+ {
+ for ( tools::Long y = 0; y < nHeight; y++ )
+ {
+ Scanline pScanline = pAcc->GetScanline( y );
+ for ( tools::Long x = 0; x < nWidth; x++ )
+ {
+ const BitmapColor aBitmapColor( pAcc->GetPixelFromData( pScanline, x ) );
+ ImplWriteHexByte( aBitmapColor.GetRed() );
+ ImplWriteHexByte( aBitmapColor.GetGreen() );
+ ImplWriteHexByte( aBitmapColor.GetBlue() );
+ }
+ }
+ }
+ }
+ }
+ ImplWriteLine( ">" ); // in Level 2 the dictionary needs to be closed (eod)
+ }
+ if ( bDoTrans )
+ ImplWriteLine( "gr" );
+ else
+ ImplWriteLine( "pom" );
+
+ Bitmap::ReleaseAccess( pAcc );
+ nHeightLeft -= nHeight;
+ if ( nHeightLeft )
+ {
+ nHeightLeft++;
+ aSourcePos.setY( static_cast<tools::Long>( rPoint.Y() + ( nYHeightOrg * ( nHeightOrg - nHeightLeft ) ) / nHeightOrg ) );
+ }
+ }
+}
+
+void PSWriter::ImplWriteCharacter( char nChar )
+{
+ switch( nChar )
+ {
+ case '(' :
+ case ')' :
+ case '\\' :
+ ImplWriteByte( sal_uInt8('\\'), PS_NONE );
+ }
+ ImplWriteByte( static_cast<sal_uInt8>(nChar), PS_NONE );
+}
+
+void PSWriter::ImplWriteString( const OString& rString, VirtualDevice const & rVDev, o3tl::span<const sal_Int32> pDXArry, bool bStretch )
+{
+ sal_Int32 nLen = rString.getLength();
+ if ( !nLen )
+ return;
+
+ if ( !pDXArry.empty() )
+ {
+ double nx = 0;
+
+ for (sal_Int32 i = 0; i < nLen; ++i)
+ {
+ if ( i > 0 )
+ nx = pDXArry[ i - 1 ];
+ ImplWriteDouble( bStretch ? nx : rVDev.GetTextWidth( OUString(rString[i]) ) );
+ ImplWriteDouble( nx );
+ ImplWriteLine( "(", PS_NONE );
+ ImplWriteCharacter( rString[i] );
+ ImplWriteLine( ") bs" );
+ }
+ }
+ else
+ {
+ ImplWriteByte( '(', PS_NONE );
+ for (sal_Int32 i = 0; i < nLen; ++i)
+ ImplWriteCharacter( rString[i] );
+ ImplWriteLine( ") sw" );
+ }
+}
+
+void PSWriter::ImplText( const OUString& rUniString, const Point& rPos, o3tl::span<const sal_Int32> pDXArry, sal_Int32 nWidth, VirtualDevice const & rVDev )
+{
+ if ( rUniString.isEmpty() )
+ return;
+ if ( mnTextMode == 0 ) // using glyph outlines
+ {
+ vcl::Font aNotRotatedFont( maFont );
+ aNotRotatedFont.SetOrientation( 0_deg10 );
+
+ ScopedVclPtrInstance< VirtualDevice > pVirDev(DeviceFormat::DEFAULT);
+ pVirDev->SetMapMode( rVDev.GetMapMode() );
+ pVirDev->SetFont( aNotRotatedFont );
+ pVirDev->SetTextAlign( eTextAlign );
+
+ Degree10 nRotation = maFont.GetOrientation();
+ tools::Polygon aPolyDummy( 1 );
+
+ Point aPos( rPos );
+ if ( nRotation )
+ {
+ aPolyDummy.SetPoint( aPos, 0 );
+ aPolyDummy.Rotate( rPos, nRotation );
+ aPos = aPolyDummy.GetPoint( 0 );
+ }
+ bool bOldLineColor = bLineColor;
+ bLineColor = false;
+ std::vector<tools::PolyPolygon> aPolyPolyVec;
+ if ( pVirDev->GetTextOutlines( aPolyPolyVec, rUniString, 0, 0, -1, nWidth, pDXArry ) )
+ {
+ // always adjust text position to match baseline alignment
+ ImplWriteLine( "pum" );
+ ImplWriteDouble( aPos.X() );
+ ImplWriteDouble( aPos.Y() );
+ ImplWriteLine( "t" );
+ if ( nRotation )
+ {
+ ImplWriteF( nRotation.get(), 1 );
+ mpPS->WriteCharPtr( "r " );
+ }
+ for (auto const& elem : aPolyPolyVec)
+ ImplPolyPoly( elem, true );
+ ImplWriteLine( "pom" );
+ }
+ bLineColor = bOldLineColor;
+ }
+ else if ( ( mnTextMode == 1 ) || ( mnTextMode == 2 ) ) // normal text output
+ {
+ if ( mnTextMode == 2 ) // forcing output one complete text packet, by
+ pDXArry = {}; // ignoring the kerning array
+ ImplSetAttrForText( rPos );
+ OString aStr(OUStringToOString(rUniString,
+ maFont.GetCharSet()));
+ ImplWriteString( aStr, rVDev, pDXArry, nWidth != 0 );
+ if ( maFont.GetOrientation() )
+ ImplWriteLine( "gr" );
+ }
+}
+
+void PSWriter::ImplSetAttrForText( const Point& rPoint )
+{
+ Point aPoint( rPoint );
+
+ Degree10 nRotation = maFont.GetOrientation();
+ ImplWriteTextColor(PS_RET);
+
+ Size aSize = maFont.GetFontSize();
+
+ if ( maLastFont != maFont )
+ {
+ if ( maFont.GetPitch() == PITCH_FIXED ) // a little bit font selection
+ ImplDefineFont( "Courier", "Oblique" );
+ else if ( maFont.GetCharSet() == RTL_TEXTENCODING_SYMBOL )
+ ImplWriteLine( "/Symbol findfont" );
+ else if ( maFont.GetFamilyType() == FAMILY_SWISS )
+ ImplDefineFont( "Helvetica", "Oblique" );
+ else
+ ImplDefineFont( "Times", "Italic" );
+
+ maLastFont = maFont;
+ aSize = maFont.GetFontSize();
+ ImplWriteDouble( aSize.Height() );
+ mpPS->WriteCharPtr( "sf " );
+ }
+ if ( eTextAlign != ALIGN_BASELINE )
+ { // PostScript does not know about FontAlignment
+ if ( eTextAlign == ALIGN_TOP ) // -> so I assume that
+ aPoint.AdjustY( aSize.Height() * 4 / 5 ); // the area under the baseline
+ else if ( eTextAlign == ALIGN_BOTTOM ) // is about 20% of the font size
+ aPoint.AdjustY( -( aSize.Height() / 5 ) );
+ }
+ ImplMoveTo( aPoint );
+ if ( nRotation )
+ {
+ mpPS->WriteCharPtr( "gs " );
+ ImplWriteF( nRotation.get(), 1 );
+ mpPS->WriteCharPtr( "r " );
+ }
+}
+
+void PSWriter::ImplDefineFont( const char* pOriginalName, const char* pItalic )
+{
+ mpPS->WriteUChar( '/' ); //convert the font pOriginalName using ISOLatin1Encoding
+ mpPS->WriteCharPtr( pOriginalName );
+ switch ( maFont.GetWeight() )
+ {
+ case WEIGHT_SEMIBOLD :
+ case WEIGHT_BOLD :
+ case WEIGHT_ULTRABOLD :
+ case WEIGHT_BLACK :
+ mpPS->WriteCharPtr( "-Bold" );
+ if ( maFont.GetItalic() != ITALIC_NONE )
+ mpPS->WriteCharPtr( pItalic );
+ break;
+ default:
+ if ( maFont.GetItalic() != ITALIC_NONE )
+ mpPS->WriteCharPtr( pItalic );
+ break;
+ }
+ ImplWriteLine( " f" );
+}
+
+void PSWriter::ImplClosePathDraw()
+{
+ mpPS->WriteCharPtr( "pc" );
+ mnCursorPos += 2;
+ ImplExecMode( PS_RET );
+}
+
+void PSWriter::ImplPathDraw()
+{
+ mpPS->WriteCharPtr( "ps" );
+ mnCursorPos += 2;
+ ImplExecMode( PS_RET );
+}
+
+
+inline void PSWriter::ImplWriteLineColor( NMode nMode )
+{
+ if ( aColor != aLineColor )
+ {
+ aColor = aLineColor;
+ ImplWriteColor( nMode );
+ }
+}
+
+inline void PSWriter::ImplWriteFillColor( NMode nMode )
+{
+ if ( aColor != aFillColor )
+ {
+ aColor = aFillColor;
+ ImplWriteColor( nMode );
+ }
+}
+
+inline void PSWriter::ImplWriteTextColor( NMode nMode )
+{
+ if ( aColor != aTextColor )
+ {
+ aColor = aTextColor;
+ ImplWriteColor( nMode );
+ }
+}
+
+void PSWriter::ImplWriteColor( NMode nMode )
+{
+ if ( mbGrayScale )
+ {
+ // writes the Color (grayscale) as a Number from 0.000 up to 1.000
+
+ ImplWriteF( 1000 * ( aColor.GetRed() * 77 + aColor.GetGreen() * 151 +
+ aColor.GetBlue() * 28 + 1 ) / 65536, 3, nMode );
+ }
+ else
+ {
+ ImplWriteB1 ( aColor.GetRed() );
+ ImplWriteB1 ( aColor.GetGreen() );
+ ImplWriteB1 ( aColor.GetBlue() );
+ }
+ mpPS->WriteCharPtr( "c" ); // ( c is defined as setrgbcolor or setgray )
+ ImplExecMode( nMode );
+}
+
+void PSWriter::ImplGetMapMode( const MapMode& rMapMode )
+{
+ ImplWriteLine( "tm setmatrix" );
+ double fMul = ImplGetScaling(rMapMode);
+ double fScaleX = static_cast<double>(rMapMode.GetScaleX()) * fMul;
+ double fScaleY = static_cast<double>(rMapMode.GetScaleY()) * fMul;
+ ImplTranslate( rMapMode.GetOrigin().X() * fScaleX, rMapMode.GetOrigin().Y() * fScaleY );
+ ImplScale( fScaleX, fScaleY );
+}
+
+inline void PSWriter::ImplExecMode( NMode nMode )
+{
+ if ( nMode & PS_WRAP )
+ {
+ if ( mnCursorPos >= PS_LINESIZE )
+ {
+ mnCursorPos = 0;
+ mpPS->WriteUChar( 0xa );
+ return;
+ }
+ }
+ if ( nMode & PS_SPACE )
+ {
+ mpPS->WriteUChar( 32 );
+ mnCursorPos++;
+ }
+ if ( nMode & PS_RET )
+ {
+ mpPS->WriteUChar( 0xa );
+ mnCursorPos = 0;
+ }
+}
+
+inline void PSWriter::ImplWriteLine( const char* pString, NMode nMode )
+{
+ sal_uInt32 i = 0;
+ while ( pString[ i ] )
+ {
+ mpPS->WriteUChar( pString[ i++ ] );
+ }
+ mnCursorPos += i;
+ ImplExecMode( nMode );
+}
+
+double PSWriter::ImplGetScaling( const MapMode& rMapMode )
+{
+ double nMul;
+ switch (rMapMode.GetMapUnit())
+ {
+ case MapUnit::MapPixel :
+ case MapUnit::MapSysFont :
+ case MapUnit::MapAppFont :
+
+ case MapUnit::Map100thMM :
+ nMul = 1;
+ break;
+ case MapUnit::Map10thMM :
+ nMul = 10;
+ break;
+ case MapUnit::MapMM :
+ nMul = 100;
+ break;
+ case MapUnit::MapCM :
+ nMul = 1000;
+ break;
+ case MapUnit::Map1000thInch :
+ nMul = 2.54;
+ break;
+ case MapUnit::Map100thInch :
+ nMul = 25.4;
+ break;
+ case MapUnit::Map10thInch :
+ nMul = 254;
+ break;
+ case MapUnit::MapInch :
+ nMul = 2540;
+ break;
+ case MapUnit::MapTwip :
+ nMul = 1.76388889;
+ break;
+ case MapUnit::MapPoint :
+ nMul = 35.27777778;
+ break;
+ default:
+ nMul = 1.0;
+ break;
+ }
+ return nMul;
+}
+
+
+void PSWriter::ImplWriteLineInfo( double fLWidth, double fMLimit,
+ SvtGraphicStroke::CapType eLCap,
+ SvtGraphicStroke::JoinType eJoin,
+ SvtGraphicStroke::DashArray && rLDash )
+{
+ if ( fLineWidth != fLWidth )
+ {
+ fLineWidth = fLWidth;
+ ImplWriteDouble( fLineWidth );
+ ImplWriteLine( "lw", PS_SPACE );
+ }
+ if ( eLineCap != eLCap )
+ {
+ eLineCap = eLCap;
+ ImplWriteLong( static_cast<sal_Int32>(eLineCap) );
+ ImplWriteLine( "lc", PS_SPACE );
+ }
+ if ( eJoinType != eJoin )
+ {
+ eJoinType = eJoin;
+ ImplWriteLong( static_cast<sal_Int32>(eJoinType) );
+ ImplWriteLine( "lj", PS_SPACE );
+ }
+ if ( eJoinType == SvtGraphicStroke::joinMiter )
+ {
+ if ( fMiterLimit != fMLimit )
+ {
+ fMiterLimit = fMLimit;
+ ImplWriteDouble( fMiterLimit );
+ ImplWriteLine( "ml", PS_SPACE );
+ }
+ }
+ if ( aDashArray != rLDash )
+ {
+ aDashArray = std::move(rLDash);
+ sal_uInt32 j, i = aDashArray.size();
+ ImplWriteLine( "[", PS_SPACE );
+ for ( j = 0; j < i; j++ )
+ ImplWriteDouble( aDashArray[ j ] );
+ ImplWriteLine( "] 0 ld" );
+ }
+}
+
+void PSWriter::ImplWriteLineInfo( const LineInfo& rLineInfo )
+{
+ std::vector< double > l_aDashArray;
+ if ( rLineInfo.GetStyle() == LineStyle::Dash )
+ l_aDashArray = rLineInfo.GetDotDashArray();
+ const double fLWidth(( ( rLineInfo.GetWidth() + 1 ) + ( rLineInfo.GetWidth() + 1 ) ) * 0.5);
+ SvtGraphicStroke::JoinType aJoinType(SvtGraphicStroke::joinMiter);
+ SvtGraphicStroke::CapType aCapType(SvtGraphicStroke::capButt);
+
+ switch(rLineInfo.GetLineJoin())
+ {
+ case basegfx::B2DLineJoin::NONE:
+ // do NOT use SvtGraphicStroke::joinNone here
+ // since it will be written as numerical value directly
+ // and is NOT a valid EPS value
+ break;
+ case basegfx::B2DLineJoin::Miter:
+ aJoinType = SvtGraphicStroke::joinMiter;
+ break;
+ case basegfx::B2DLineJoin::Bevel:
+ aJoinType = SvtGraphicStroke::joinBevel;
+ break;
+ case basegfx::B2DLineJoin::Round:
+ aJoinType = SvtGraphicStroke::joinRound;
+ break;
+ }
+ switch(rLineInfo.GetLineCap())
+ {
+ default: /* css::drawing::LineCap_BUTT */
+ {
+ aCapType = SvtGraphicStroke::capButt;
+ break;
+ }
+ case css::drawing::LineCap_ROUND:
+ {
+ aCapType = SvtGraphicStroke::capRound;
+ break;
+ }
+ case css::drawing::LineCap_SQUARE:
+ {
+ aCapType = SvtGraphicStroke::capSquare;
+ break;
+ }
+ }
+
+ ImplWriteLineInfo( fLWidth, fMiterLimit, aCapType, aJoinType, std::move(l_aDashArray) );
+}
+
+void PSWriter::ImplWriteLong(sal_Int32 nNumber, NMode nMode)
+{
+ const OString aNumber(OString::number(nNumber));
+ mnCursorPos += aNumber.getLength();
+ mpPS->WriteOString( aNumber );
+ ImplExecMode(nMode);
+}
+
+void PSWriter::ImplWriteDouble( double fNumber )
+{
+ sal_Int32 nPTemp = static_cast<sal_Int32>(fNumber);
+ sal_Int32 nATemp = std::abs( static_cast<sal_Int32>( ( fNumber - nPTemp ) * 100000 ) );
+
+ if ( !nPTemp && nATemp && ( fNumber < 0.0 ) )
+ mpPS->WriteChar( '-' );
+
+ const OString aNumber1(OString::number(nPTemp));
+ mpPS->WriteOString( aNumber1 );
+ mnCursorPos += aNumber1.getLength();
+
+ if ( nATemp )
+ {
+ int zCount = 0;
+ mpPS->WriteUChar( '.' );
+ mnCursorPos++;
+ const OString aNumber2(OString::number(nATemp));
+
+ sal_Int16 n, nLen = aNumber2.getLength();
+ if ( nLen < 8 )
+ {
+ mnCursorPos += 6 - nLen;
+ for ( n = 0; n < ( 5 - nLen ); n++ )
+ {
+ mpPS->WriteUChar( '0' );
+ }
+ }
+ mnCursorPos += nLen;
+ for ( n = 0; n < nLen; n++ )
+ {
+ mpPS->WriteChar( aNumber2[n] );
+ zCount--;
+ if ( aNumber2[n] != '0' )
+ zCount = 0;
+ }
+ if ( zCount )
+ mpPS->SeekRel( zCount );
+ }
+ ImplExecMode( PS_SPACE );
+}
+
+/// Writes the number to stream: nNumber / ( 10^nCount )
+void PSWriter::ImplWriteF( sal_Int32 nNumber, sal_uInt8 nCount, NMode nMode )
+{
+ if ( nNumber < 0 )
+ {
+ mpPS->WriteUChar( '-' );
+ nNumber = -nNumber;
+ mnCursorPos++;
+ }
+ const OString aScaleFactor(OString::number(nNumber));
+ sal_uInt32 nLen = aScaleFactor.getLength();
+ sal_Int32 const nStSize = (nCount + 1) - nLen;
+ static_assert(sizeof(nStSize) == sizeof((nCount + 1) - nLen)); // tdf#134667
+ if ( nStSize >= 1 )
+ {
+ mpPS->WriteUChar( '0' );
+ mnCursorPos++;
+ }
+ if ( nStSize >= 2 )
+ {
+ mpPS->WriteUChar( '.' );
+ for (sal_Int32 i = 1; i < nStSize; ++i)
+ {
+ mpPS->WriteUChar( '0' );
+ mnCursorPos++;
+ }
+ }
+ mnCursorPos += nLen;
+ for( sal_uInt32 n = 0; n < nLen; n++ )
+ {
+ if ( n == nLen - nCount )
+ {
+ mpPS->WriteUChar( '.' );
+ mnCursorPos++;
+ }
+ mpPS->WriteChar( aScaleFactor[n] );
+ }
+ ImplExecMode( nMode );
+}
+
+void PSWriter::ImplWriteByte( sal_uInt8 nNumb, NMode nMode )
+{
+ mpPS->WriteUChar( nNumb );
+ mnCursorPos++;
+ ImplExecMode( nMode );
+}
+
+void PSWriter::ImplWriteHexByte( sal_uInt8 nNumb, NMode nMode )
+{
+ if ( ( nNumb >> 4 ) > 9 )
+ mpPS->WriteUChar( ( nNumb >> 4 ) + 'A' - 10 );
+ else
+ mpPS->WriteUChar( ( nNumb >> 4 ) + '0' );
+
+ if ( ( nNumb & 0xf ) > 9 )
+ mpPS->WriteUChar( ( nNumb & 0xf ) + 'A' - 10 );
+ else
+ mpPS->WriteUChar( ( nNumb & 0xf ) + '0' );
+ mnCursorPos += 2;
+ ImplExecMode( nMode );
+}
+
+// writes the sal_uInt8 nNumb as a Number from 0.000 up to 1.000
+
+void PSWriter::ImplWriteB1( sal_uInt8 nNumb )
+{
+ ImplWriteF( 1000 * ( nNumb + 1 ) / 256 );
+}
+
+inline void PSWriter::WriteBits( sal_uInt16 nCode, sal_uInt16 nCodeLen )
+{
+ dwShift |= ( nCode << ( nOffset - nCodeLen ) );
+ nOffset -= nCodeLen;
+ while ( nOffset < 24 )
+ {
+ ImplWriteHexByte( static_cast<sal_uInt8>( dwShift >> 24 ) );
+ dwShift <<= 8;
+ nOffset += 8;
+ }
+ if ( nCode == 257 && nOffset != 32 )
+ ImplWriteHexByte( static_cast<sal_uInt8>( dwShift >> 24 ) );
+}
+
+void PSWriter::StartCompression()
+{
+ sal_uInt16 i;
+ nDataSize = 8;
+
+ nClearCode = 1 << nDataSize;
+ nEOICode = nClearCode + 1;
+ nTableSize = nEOICode + 1;
+ nCodeSize = nDataSize + 1;
+
+ nOffset = 32; // number of free unused in dwShift
+ dwShift = 0;
+
+ pTable.reset(new PSLZWCTreeNode[ 4096 ]);
+
+ for ( i = 0; i < 4096; i++ )
+ {
+ pTable[ i ].pBrother = pTable[ i ].pFirstChild = nullptr;
+ pTable[ i ].nCode = i;
+ pTable[ i ].nValue = static_cast<sal_uInt8>( i );
+ }
+ pPrefix = nullptr;
+ WriteBits( nClearCode, nCodeSize );
+}
+
+void PSWriter::Compress( sal_uInt8 nCompThis )
+{
+ PSLZWCTreeNode* p;
+ sal_uInt16 i;
+ sal_uInt8 nV;
+
+ if( !pPrefix )
+ {
+ pPrefix = pTable.get() + nCompThis;
+ }
+ else
+ {
+ nV = nCompThis;
+ for( p = pPrefix->pFirstChild; p != nullptr; p = p->pBrother )
+ {
+ if ( p->nValue == nV )
+ break;
+ }
+
+ if( p )
+ pPrefix = p;
+ else
+ {
+ WriteBits( pPrefix->nCode, nCodeSize );
+
+ if ( nTableSize == 409 )
+ {
+ WriteBits( nClearCode, nCodeSize );
+
+ for ( i = 0; i < nClearCode; i++ )
+ pTable[ i ].pFirstChild = nullptr;
+
+ nCodeSize = nDataSize + 1;
+ nTableSize = nEOICode + 1;
+ }
+ else
+ {
+ if( nTableSize == static_cast<sal_uInt16>( ( 1 << nCodeSize ) - 1 ) )
+ nCodeSize++;
+
+ p = pTable.get() + ( nTableSize++ );
+ p->pBrother = pPrefix->pFirstChild;
+ pPrefix->pFirstChild = p;
+ p->nValue = nV;
+ p->pFirstChild = nullptr;
+ }
+
+ pPrefix = pTable.get() + nV;
+ }
+ }
+}
+
+void PSWriter::EndCompression()
+{
+ if( pPrefix )
+ WriteBits( pPrefix->nCode, nCodeSize );
+
+ WriteBits( nEOICode, nCodeSize );
+ pTable.reset();
+}
+
+sal_uInt8* PSWriter::ImplSearchEntry( sal_uInt8* pSource, sal_uInt8 const * pDest, sal_uInt32 nComp, sal_uInt32 nSize )
+{
+ while ( nComp-- >= nSize )
+ {
+ sal_uInt64 i;
+ for ( i = 0; i < nSize; i++ )
+ {
+ if ( ( pSource[i]&~0x20 ) != ( pDest[i]&~0x20 ) )
+ break;
+ }
+ if ( i == nSize )
+ return pSource;
+ pSource++;
+ }
+ return nullptr;
+}
+
+bool PSWriter::ImplGetBoundingBox( double* nNumb, sal_uInt8* pSource, sal_uInt32 nSize )
+{
+ bool bRetValue = false;
+ sal_uInt32 nBytesRead;
+
+ if ( nSize < 256 ) // we assume that the file is greater than 256 bytes
+ return false;
+
+ if ( nSize < POSTSCRIPT_BOUNDINGSEARCH )
+ nBytesRead = nSize;
+ else
+ nBytesRead = POSTSCRIPT_BOUNDINGSEARCH;
+
+ sal_uInt8* pDest = ImplSearchEntry( pSource, reinterpret_cast<sal_uInt8 const *>("%%BoundingBox:"), nBytesRead, 14 );
+ if ( pDest )
+ {
+ int nSecurityCount = 100; // only 100 bytes following the bounding box will be checked
+ nNumb[0] = nNumb[1] = nNumb[2] = nNumb[3] = 0;
+ pDest += 14;
+ for ( int i = 0; ( i < 4 ) && nSecurityCount; i++ )
+ {
+ int nDivision = 1;
+ bool bDivision = false;
+ bool bNegative = false;
+ bool bValid = true;
+
+ while ( ( --nSecurityCount ) && ( ( *pDest == ' ' ) || ( *pDest == 0x9 ) ) )
+ pDest++;
+ sal_uInt8 nByte = *pDest;
+ while ( nSecurityCount && ( nByte != ' ' ) && ( nByte != 0x9 ) && ( nByte != 0xd ) && ( nByte != 0xa ) )
+ {
+ switch ( nByte )
+ {
+ case '.' :
+ if ( bDivision )
+ bValid = false;
+ else
+ bDivision = true;
+ break;
+ case '-' :
+ bNegative = true;
+ break;
+ default :
+ if ( ( nByte < '0' ) || ( nByte > '9' ) )
+ nSecurityCount = 1; // error parsing the bounding box values
+ else if ( bValid )
+ {
+ if ( bDivision )
+ nDivision*=10;
+ nNumb[i] *= 10;
+ nNumb[i] += nByte - '0';
+ }
+ break;
+ }
+ nSecurityCount--;
+ nByte = *(++pDest);
+ }
+ if ( bNegative )
+ nNumb[i] = -nNumb[i];
+ if ( bDivision && ( nDivision != 1 ) )
+ nNumb[i] /= nDivision;
+ }
+ if ( nSecurityCount)
+ bRetValue = true;
+ }
+ return bRetValue;
+}
+
+//================== GraphicExport - the exported function ===================
+
+bool ExportEpsGraphic(SvStream & rStream, const Graphic & rGraphic, FilterConfigItem* pFilterConfigItem)
+{
+ PSWriter aPSWriter;
+ return aPSWriter.WritePS(rGraphic, rStream, pFilterConfigItem);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/etiff/etiff.cxx b/vcl/source/filter/etiff/etiff.cxx
new file mode 100644
index 000000000..b34accab8
--- /dev/null
+++ b/vcl/source/filter/etiff/etiff.cxx
@@ -0,0 +1,586 @@
+/* -*- 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 <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+#include <filter/TiffWriter.hxx>
+
+#define NewSubfileType 254
+#define ImageWidth 256
+#define ImageLength 257
+#define BitsPerSample 258
+#define Compression 259
+#define PhotometricInterpretation 262
+#define StripOffsets 273
+#define SamplesPerPixel 277
+#define RowsPerStrip 278
+#define StripByteCounts 279
+#define XResolution 282
+#define YResolution 283
+#define PlanarConfiguration 284
+#define ResolutionUnit 296
+#define ColorMap 320
+
+namespace {
+
+struct TIFFLZWCTreeNode
+{
+
+ TIFFLZWCTreeNode* pBrother; // next node with the same father
+ TIFFLZWCTreeNode* pFirstChild; // first son
+ sal_uInt16 nCode; // The code for the string of pixel values, which arises if... <missing comment>
+ sal_uInt16 nValue; // pixel value
+};
+
+
+class TIFFWriter
+{
+private:
+
+ SvStream& m_rOStm;
+ sal_uInt32 mnStreamOfs;
+
+ bool mbStatus;
+ BitmapReadAccess* mpAcc;
+
+ sal_uInt32 mnWidth, mnHeight, mnColors;
+ sal_uInt32 mnCurAllPictHeight;
+ sal_uInt32 mnSumOfAllPictHeight;
+ sal_uInt32 mnBitsPerPixel;
+ sal_uInt32 mnLastPercent;
+
+ sal_uInt32 mnLatestIfdPos;
+ sal_uInt16 mnTagCount; // number of tags already written
+ sal_uInt32 mnCurrentTagCountPos; // offset to the position where the current
+ // tag count is to insert
+
+ sal_uInt32 mnXResPos; // if != 0 this DWORDs stores the
+ sal_uInt32 mnYResPos; // actual streamposition of the
+ sal_uInt32 mnPalPos; // Tag Entry
+ sal_uInt32 mnBitmapPos;
+ sal_uInt32 mnStripByteCountPos;
+
+ std::unique_ptr<TIFFLZWCTreeNode[]> pTable;
+ TIFFLZWCTreeNode* pPrefix;
+ sal_uInt16 nDataSize;
+ sal_uInt16 nClearCode;
+ sal_uInt16 nEOICode;
+ sal_uInt16 nTableSize;
+ sal_uInt16 nCodeSize;
+ sal_uInt32 nOffset;
+ sal_uInt32 dwShift;
+
+ css::uno::Reference< css::task::XStatusIndicator > xStatusIndicator;
+
+ void ImplCallback( sal_uInt32 nPercent );
+ bool ImplWriteHeader( bool bMultiPage );
+ void ImplWritePalette();
+ void ImplWriteBody();
+ void ImplWriteTag( sal_uInt16 TagID, sal_uInt16 DataType, sal_uInt32 NumberOfItems, sal_uInt32 Value);
+ void ImplWriteResolution( sal_uInt64 nStreamPos, sal_uInt32 nResolutionUnit );
+ void StartCompression();
+ void Compress( sal_uInt8 nSrc );
+ void EndCompression();
+ inline void WriteBits( sal_uInt16 nCode, sal_uInt16 nCodeLen );
+
+public:
+
+ explicit TIFFWriter(SvStream &rStream);
+
+ bool WriteTIFF( const Graphic& rGraphic, FilterConfigItem const * pFilterConfigItem );
+};
+
+}
+
+TIFFWriter::TIFFWriter(SvStream &rStream)
+ : m_rOStm(rStream)
+ , mnStreamOfs(0)
+ , mbStatus(true)
+ , mpAcc(nullptr)
+ , mnWidth(0)
+ , mnHeight(0)
+ , mnColors(0)
+ , mnCurAllPictHeight(0)
+ , mnSumOfAllPictHeight(0)
+ , mnBitsPerPixel(0)
+ , mnLastPercent(0)
+ , mnLatestIfdPos(0)
+ , mnTagCount(0)
+ , mnCurrentTagCountPos(0)
+ , mnXResPos(0)
+ , mnYResPos(0)
+ , mnPalPos(0)
+ , mnBitmapPos(0)
+ , mnStripByteCountPos(0)
+ , pPrefix(nullptr)
+ , nDataSize(0)
+ , nClearCode(0)
+ , nEOICode(0)
+ , nTableSize(0)
+ , nCodeSize(0)
+ , nOffset(0)
+ , dwShift(0)
+{
+}
+
+
+bool TIFFWriter::WriteTIFF( const Graphic& rGraphic, FilterConfigItem const * pFilterConfigItem)
+{
+ if ( pFilterConfigItem )
+ {
+ xStatusIndicator = pFilterConfigItem->GetStatusIndicator();
+ if ( xStatusIndicator.is() )
+ {
+ xStatusIndicator->start( OUString(), 100 );
+ }
+ }
+
+ const SvStreamEndian nOldFormat = m_rOStm.GetEndian();
+ mnStreamOfs = m_rOStm.Tell();
+
+ // we will use the BIG Endian Mode
+ // TIFF header
+ m_rOStm.SetEndian( SvStreamEndian::BIG );
+ m_rOStm.WriteUInt32( 0x4d4d002a ); // TIFF identifier
+ mnLatestIfdPos = m_rOStm.Tell();
+ m_rOStm.WriteUInt32( 0 );
+
+ if( mbStatus )
+ {
+ Animation aAnimation = rGraphic.IsAnimated() ? rGraphic.GetAnimation() : Animation();
+ if (!rGraphic.IsAnimated())
+ aAnimation.Insert(AnimationBitmap(rGraphic.GetBitmapEx(), Point(), Size()));
+
+ for (size_t i = 0; i < aAnimation.Count(); ++i)
+ mnSumOfAllPictHeight += aAnimation.Get(i).maBitmapEx.GetSizePixel().Height();
+
+ for (size_t i = 0; mbStatus && i < aAnimation.Count(); ++i)
+ {
+ mnPalPos = 0;
+ const AnimationBitmap& rAnimationBitmap = aAnimation.Get( i );
+ Bitmap aBmp = rAnimationBitmap.maBitmapEx.GetBitmap();
+ mpAcc = aBmp.AcquireReadAccess();
+ if ( mpAcc )
+ {
+ mnBitsPerPixel = vcl::pixelFormatBitCount(aBmp.getPixelFormat());
+
+ // export code below only handles four discrete cases
+ mnBitsPerPixel =
+ mnBitsPerPixel <= 1 ? 1 : mnBitsPerPixel <= 4 ? 4 : mnBitsPerPixel <= 8 ? 8 : 24;
+
+ if ( ImplWriteHeader( aAnimation.Count() > 0 ) )
+ {
+ Size aDestMapSize( 300, 300 );
+ const MapMode& aMapMode( aBmp.GetPrefMapMode() );
+ if ( aMapMode.GetMapUnit() != MapUnit::MapPixel )
+ {
+ const Size aPrefSize( rGraphic.GetPrefSize() );
+ aDestMapSize = OutputDevice::LogicToLogic(aPrefSize, aMapMode, MapMode(MapUnit::MapInch));
+ }
+ ImplWriteResolution( mnXResPos, aDestMapSize.Width() );
+ ImplWriteResolution( mnYResPos, aDestMapSize.Height() );
+ if ( mnPalPos )
+ ImplWritePalette();
+ ImplWriteBody();
+ }
+ sal_uInt32 nCurPos = m_rOStm.Tell();
+ m_rOStm.Seek( mnCurrentTagCountPos );
+ m_rOStm.WriteUInt16( mnTagCount );
+ m_rOStm.Seek( nCurPos );
+
+ Bitmap::ReleaseAccess( mpAcc );
+ }
+ else
+ mbStatus = false;
+ }
+ }
+ m_rOStm.SetEndian( nOldFormat );
+
+ if ( xStatusIndicator.is() )
+ xStatusIndicator->end();
+
+ return mbStatus;
+}
+
+
+void TIFFWriter::ImplCallback( sal_uInt32 nPercent )
+{
+ if ( xStatusIndicator.is() )
+ {
+ if( nPercent >= mnLastPercent + 3 )
+ {
+ mnLastPercent = nPercent;
+ if ( nPercent <= 100 )
+ xStatusIndicator->setValue( nPercent );
+ }
+ }
+}
+
+
+bool TIFFWriter::ImplWriteHeader( bool bMultiPage )
+{
+ mnTagCount = 0;
+ mnWidth = mpAcc->Width();
+ mnHeight = mpAcc->Height();
+
+ if ( mnWidth && mnHeight && mnBitsPerPixel && mbStatus )
+ {
+ sal_uInt32 nCurrentPos = m_rOStm.Tell();
+ m_rOStm.Seek( mnLatestIfdPos );
+ m_rOStm.WriteUInt32( nCurrentPos - mnStreamOfs ); // offset to the IFD
+ m_rOStm.Seek( nCurrentPos );
+
+ // (OFS8) TIFF image file directory (IFD)
+ mnCurrentTagCountPos = m_rOStm.Tell();
+ m_rOStm.WriteUInt16( 0 ); // the number of tangents to insert later
+
+ sal_uInt32 nSubFileFlags = 0;
+ if ( bMultiPage )
+ nSubFileFlags |= 2;
+ ImplWriteTag( NewSubfileType, 4, 1, nSubFileFlags );
+ ImplWriteTag( ImageWidth, 4, 1, mnWidth );
+ ImplWriteTag( ImageLength, 4, 1, mnHeight);
+ ImplWriteTag( BitsPerSample, 3, 1, ( mnBitsPerPixel == 24 ) ? 8 : mnBitsPerPixel );
+ ImplWriteTag( Compression, 3, 1, 5 );
+ sal_uInt8 nTemp;
+ switch ( mnBitsPerPixel )
+ {
+ case 1 :
+ nTemp = 1;
+ break;
+ case 4 :
+ case 8 :
+ nTemp = 3;
+ break;
+ case 24:
+ nTemp = 2;
+ break;
+ default:
+ nTemp = 0; // -Wall set a default...
+ break;
+ }
+ ImplWriteTag( PhotometricInterpretation, 3, 1, nTemp );
+ mnBitmapPos = m_rOStm.Tell();
+ ImplWriteTag( StripOffsets, 4, 1, 0 );
+ ImplWriteTag( SamplesPerPixel, 3, 1, ( mnBitsPerPixel == 24 ) ? 3 : 1 );
+ ImplWriteTag( RowsPerStrip, 4, 1, mnHeight );
+ mnStripByteCountPos = m_rOStm.Tell();
+ ImplWriteTag( StripByteCounts, 4, 1, ( ( mnWidth * mnBitsPerPixel * mnHeight ) + 7 ) >> 3 );
+ mnXResPos = m_rOStm.Tell();
+ ImplWriteTag( XResolution, 5, 1, 0 );
+ mnYResPos = m_rOStm.Tell();
+ ImplWriteTag( YResolution, 5, 1, 0 );
+ if ( mnBitsPerPixel != 1 )
+ ImplWriteTag( PlanarConfiguration, 3, 1, 1 ); // ( RGB ORDER )
+ ImplWriteTag( ResolutionUnit, 3, 1, 2); // Resolution Unit is Inch
+ if ( ( mnBitsPerPixel == 4 ) || ( mnBitsPerPixel == 8 ) )
+ {
+ mnColors = mpAcc->GetPaletteEntryCount();
+ mnPalPos = m_rOStm.Tell();
+ ImplWriteTag( ColorMap, 3, 3 * mnColors, 0 );
+ }
+
+ // and last we write zero to close the num dir entries list
+ mnLatestIfdPos = m_rOStm.Tell();
+ m_rOStm.WriteUInt32( 0 ); // there are no more IFD
+ }
+ else
+ mbStatus = false;
+
+ return mbStatus;
+}
+
+
+void TIFFWriter::ImplWritePalette()
+{
+ sal_uInt64 nCurrentPos = m_rOStm.Tell();
+ m_rOStm.Seek( mnPalPos + 8 ); // the palette tag entry needs the offset
+ m_rOStm.WriteUInt32( nCurrentPos - mnStreamOfs ); // to the palette colors
+ m_rOStm.Seek( nCurrentPos );
+
+ for ( sal_uInt32 i = 0; i < mnColors; i++ )
+ {
+ const BitmapColor& rColor = mpAcc->GetPaletteColor( i );
+ m_rOStm.WriteUInt16( rColor.GetRed() << 8 );
+ }
+ for ( sal_uInt32 i = 0; i < mnColors; i++ )
+ {
+ const BitmapColor& rColor = mpAcc->GetPaletteColor( i );
+ m_rOStm.WriteUInt16( rColor.GetGreen() << 8 );
+ }
+ for ( sal_uInt32 i = 0; i < mnColors; i++ )
+ {
+ const BitmapColor& rColor = mpAcc->GetPaletteColor( i );
+ m_rOStm.WriteUInt16( rColor.GetBlue() << 8 );
+ }
+}
+
+
+void TIFFWriter::ImplWriteBody()
+{
+ sal_uInt8 nTemp = 0;
+ sal_uInt8 nShift;
+ sal_uInt32 j, x, y;
+
+ sal_uInt64 nGfxBegin = m_rOStm.Tell();
+ m_rOStm.Seek( mnBitmapPos + 8 ); // the strip offset tag entry needs the offset
+ m_rOStm.WriteUInt32( nGfxBegin - mnStreamOfs ); // to the bitmap data
+ m_rOStm.Seek( nGfxBegin );
+
+ StartCompression();
+
+ switch( mnBitsPerPixel )
+ {
+ case 24 :
+ {
+ for ( y = 0; y < mnHeight; y++, mnCurAllPictHeight++ )
+ {
+ ImplCallback( 100 * mnCurAllPictHeight / mnSumOfAllPictHeight );
+ Scanline pScanline = mpAcc->GetScanline( y );
+ for ( x = 0; x < mnWidth; x++ )
+ {
+ const BitmapColor& rColor = mpAcc->GetPixelFromData( pScanline, x );
+ Compress( rColor.GetRed() );
+ Compress( rColor.GetGreen() );
+ Compress( rColor.GetBlue() );
+ }
+ }
+ }
+ break;
+
+ case 8 :
+ {
+ for ( y = 0; y < mnHeight; y++, mnCurAllPictHeight++ )
+ {
+ ImplCallback( 100 * mnCurAllPictHeight / mnSumOfAllPictHeight );
+ Scanline pScanline = mpAcc->GetScanline( y );
+ for ( x = 0; x < mnWidth; x++ )
+ {
+ Compress( mpAcc->GetIndexFromData( pScanline, x ) );
+ }
+ }
+ }
+ break;
+
+ case 4 :
+ {
+ for ( nShift = 0, y = 0; y < mnHeight; y++, mnCurAllPictHeight++ )
+ {
+ ImplCallback( 100 * mnCurAllPictHeight / mnSumOfAllPictHeight );
+ Scanline pScanline = mpAcc->GetScanline( y );
+ for ( x = 0; x < mnWidth; x++, nShift++ )
+ {
+ if (!( nShift & 1 ))
+ nTemp = ( mpAcc->GetIndexFromData( pScanline, x ) << 4 );
+ else
+ Compress( static_cast<sal_uInt8>( nTemp | ( mpAcc->GetIndexFromData( pScanline, x ) & 0xf ) ) );
+ }
+ if ( nShift & 1 )
+ Compress( nTemp );
+ }
+ }
+ break;
+
+ case 1 :
+ {
+ j = 1;
+ for ( y = 0; y < mnHeight; y++, mnCurAllPictHeight++ )
+ {
+ ImplCallback( 100 * mnCurAllPictHeight / mnSumOfAllPictHeight );
+ Scanline pScanline = mpAcc->GetScanline( y );
+ for ( x = 0; x < mnWidth; x++)
+ {
+ j <<= 1;
+ j |= ( ( ~mpAcc->GetIndexFromData( pScanline, x ) ) & 1 );
+ if ( j & 0x100 )
+ {
+ Compress( static_cast<sal_uInt8>(j) );
+ j = 1;
+ }
+ }
+ if ( j != 1 )
+ {
+ Compress( static_cast<sal_uInt8>(j << ( ( ( x & 7) ^ 7 ) + 1 ) ) );
+ j = 1;
+ }
+ }
+ }
+ break;
+
+ default:
+ {
+ mbStatus = false;
+ }
+ break;
+ }
+
+ EndCompression();
+
+ if ( mnStripByteCountPos && mbStatus )
+ {
+ sal_uInt64 nGfxEnd = m_rOStm.Tell();
+ m_rOStm.Seek( mnStripByteCountPos + 8 );
+ m_rOStm.WriteUInt32( nGfxEnd - nGfxBegin ); // mnStripByteCountPos needs the size of the compression data
+ m_rOStm.Seek( nGfxEnd );
+ }
+}
+
+
+void TIFFWriter::ImplWriteResolution( sal_uInt64 nStreamPos, sal_uInt32 nResolutionUnit )
+{
+ sal_uInt64 nCurrentPos = m_rOStm.Tell();
+ m_rOStm.Seek( nStreamPos + 8 );
+ m_rOStm.WriteUInt32( nCurrentPos - mnStreamOfs );
+ m_rOStm.Seek( nCurrentPos );
+ m_rOStm.WriteUInt32( 1 );
+ m_rOStm.WriteUInt32( nResolutionUnit );
+}
+
+
+void TIFFWriter::ImplWriteTag( sal_uInt16 nTagID, sal_uInt16 nDataType, sal_uInt32 nNumberOfItems, sal_uInt32 nValue)
+{
+ mnTagCount++;
+
+ m_rOStm.WriteUInt16( nTagID );
+ m_rOStm.WriteUInt16( nDataType );
+ m_rOStm.WriteUInt32( nNumberOfItems );
+ if ( nDataType == 3 )
+ nValue <<=16; // in Big Endian Mode WORDS needed to be shifted to a DWORD
+ m_rOStm.WriteUInt32( nValue );
+}
+
+
+inline void TIFFWriter::WriteBits( sal_uInt16 nCode, sal_uInt16 nCodeLen )
+{
+ dwShift |= ( nCode << ( nOffset - nCodeLen ) );
+ nOffset -= nCodeLen;
+ while ( nOffset < 24 )
+ {
+ m_rOStm.WriteUChar( dwShift >> 24 );
+ dwShift <<= 8;
+ nOffset += 8;
+ }
+ if ( nCode == 257 && nOffset != 32 )
+ {
+ m_rOStm.WriteUChar( dwShift >> 24 );
+ }
+}
+
+
+void TIFFWriter::StartCompression()
+{
+ sal_uInt16 i;
+ nDataSize = 8;
+
+ nClearCode = 1 << nDataSize;
+ nEOICode = nClearCode + 1;
+ nTableSize = nEOICode + 1;
+ nCodeSize = nDataSize + 1;
+
+ nOffset = 32; // number of free bits in dwShift
+ dwShift = 0;
+
+ pTable.reset(new TIFFLZWCTreeNode[ 4096 ]);
+
+ for ( i = 0; i < 4096; i++)
+ {
+ pTable[ i ].pBrother = pTable[ i ].pFirstChild = nullptr;
+ pTable[ i ].nCode = i;
+ pTable[ i ].nValue = static_cast<sal_uInt8>( i );
+ }
+
+ pPrefix = nullptr;
+ WriteBits( nClearCode, nCodeSize );
+}
+
+
+void TIFFWriter::Compress( sal_uInt8 nCompThis )
+{
+ TIFFLZWCTreeNode* p;
+ sal_uInt16 i;
+ sal_uInt8 nV;
+
+ if( !pPrefix )
+ {
+ pPrefix = &pTable[nCompThis];
+ }
+ else
+ {
+ nV = nCompThis;
+ for( p = pPrefix->pFirstChild; p != nullptr; p = p->pBrother )
+ {
+ if ( p->nValue == nV )
+ break;
+ }
+
+ if( p )
+ pPrefix = p;
+ else
+ {
+ WriteBits( pPrefix->nCode, nCodeSize );
+
+ if ( nTableSize == 409 )
+ {
+ WriteBits( nClearCode, nCodeSize );
+
+ for ( i = 0; i < nClearCode; i++ )
+ pTable[ i ].pFirstChild = nullptr;
+
+ nCodeSize = nDataSize + 1;
+ nTableSize = nEOICode + 1;
+ }
+ else
+ {
+ if( nTableSize == static_cast<sal_uInt16>( ( 1 << nCodeSize ) - 1 ) )
+ nCodeSize++;
+
+ p = &pTable[ nTableSize++ ];
+ p->pBrother = pPrefix->pFirstChild;
+ pPrefix->pFirstChild = p;
+ p->nValue = nV;
+ p->pFirstChild = nullptr;
+ }
+
+ pPrefix = &pTable[nV];
+ }
+ }
+}
+
+
+void TIFFWriter::EndCompression()
+{
+ if( pPrefix )
+ WriteBits( pPrefix->nCode, nCodeSize );
+
+ WriteBits( nEOICode, nCodeSize );
+ pTable.reset();
+}
+
+bool ExportTiffGraphicImport(SvStream & rStream, const Graphic & rGraphic, const FilterConfigItem* pFilterConfigItem)
+{
+ TIFFWriter aWriter(rStream);
+ return aWriter.WriteTIFF( rGraphic, pFilterConfigItem );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/graphicfilter.cxx b/vcl/source/filter/graphicfilter.cxx
new file mode 100644
index 000000000..d208c7d81
--- /dev/null
+++ b/vcl/source/filter/graphicfilter.cxx
@@ -0,0 +1,2056 @@
+/* -*- 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 <config_folders.h>
+
+#include <sal/log.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/threadpool.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <tools/fract.hxx>
+#include <unotools/configmgr.hxx>
+#include <tools/stream.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/zcodec.hxx>
+#include <fltcall.hxx>
+#include <vcl/salctype.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include <vcl/filter/SvmWriter.hxx>
+#include <vcl/pngwrite.hxx>
+#include <vcl/vectorgraphicdata.hxx>
+#include <vcl/virdev.hxx>
+#include <impgraph.hxx>
+#include <vcl/svapp.hxx>
+#include <osl/file.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <vcl/wmf.hxx>
+#include "igif/gifread.hxx"
+#include <vcl/pdfread.hxx>
+#include "jpeg/jpeg.hxx"
+#include "png/png.hxx"
+#include "ixbm/xbmread.hxx"
+#include <filter/XpmReader.hxx>
+#include <filter/TiffReader.hxx>
+#include <filter/TiffWriter.hxx>
+#include <filter/TgaReader.hxx>
+#include <filter/PictReader.hxx>
+#include <filter/MetReader.hxx>
+#include <filter/RasReader.hxx>
+#include <filter/PcxReader.hxx>
+#include <filter/EpsReader.hxx>
+#include <filter/EpsWriter.hxx>
+#include <filter/PsdReader.hxx>
+#include <filter/PcdReader.hxx>
+#include <filter/PbmReader.hxx>
+#include <filter/DxfReader.hxx>
+#include <filter/GifWriter.hxx>
+#include <filter/BmpReader.hxx>
+#include <filter/BmpWriter.hxx>
+#include <filter/WebpReader.hxx>
+#include <filter/WebpWriter.hxx>
+#include <osl/module.hxx>
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/uno/XInterface.hpp>
+#include <com/sun/star/io/XActiveDataSource.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/svg/XSVGWriter.hpp>
+#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
+#include <com/sun/star/xml/sax/Writer.hpp>
+#include <unotools/ucbstreamhelper.hxx>
+#include <rtl/bootstrap.hxx>
+#include <tools/svlibrary.h>
+#include <comphelper/string.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <vector>
+#include <memory>
+#include <mutex>
+#include <string_view>
+#include <o3tl/string_view.hxx>
+#include <vcl/TypeSerializer.hxx>
+
+#include "FilterConfigCache.hxx"
+#include "graphicfilter_internal.hxx"
+
+#include <graphic/GraphicFormatDetector.hxx>
+#include <graphic/GraphicReader.hxx>
+
+// Support for GfxLinkType::NativeWebp is so far disabled,
+// as enabling it would write .webp images e.g. to .odt documents,
+// making those images unreadable for older readers. So for now
+// disable the support so that .webp images will be written out as .png,
+// and somewhen later enable the support unconditionally.
+static bool supportNativeWebp()
+{
+ const char* const testname = getenv("LO_TESTNAME");
+ if(testname == nullptr)
+ return false;
+ // Enable support only for those unittests that test it.
+ if( std::string_view("_anonymous_namespace___GraphicTest__testUnloadedGraphicLoading_") == testname
+ || std::string_view("VclFiltersTest__testExportImport_") == testname
+ || o3tl::starts_with(std::string_view(testname), "WebpFilterTest__"))
+ return true;
+ return false;
+}
+
+static std::vector< GraphicFilter* > gaFilterHdlList;
+
+static std::mutex& getListMutex()
+{
+ static std::mutex s_aListProtection;
+ return s_aListProtection;
+}
+
+namespace {
+
+class ImpFilterOutputStream : public ::cppu::WeakImplHelper< css::io::XOutputStream >
+{
+ SvStream& mrStm;
+
+ virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& rData ) override
+ { mrStm.WriteBytes(rData.getConstArray(), rData.getLength()); }
+ virtual void SAL_CALL flush() override
+ { mrStm.FlushBuffer(); }
+ virtual void SAL_CALL closeOutput() override {}
+
+public:
+
+ explicit ImpFilterOutputStream( SvStream& rStm ) : mrStm( rStm ) {}
+};
+
+}
+
+// Helper functions
+
+sal_uInt8* ImplSearchEntry( sal_uInt8* pSource, sal_uInt8 const * pDest, sal_uLong nComp, sal_uLong nSize )
+{
+ while ( nComp-- >= nSize )
+ {
+ sal_uLong i;
+ for ( i = 0; i < nSize; i++ )
+ {
+ if ( ( pSource[i]&~0x20 ) != ( pDest[i]&~0x20 ) )
+ break;
+ }
+ if ( i == nSize )
+ return pSource;
+ pSource++;
+ }
+ return nullptr;
+}
+
+static OUString ImpGetExtension( std::u16string_view rPath )
+{
+ OUString aExt;
+ INetURLObject aURL( rPath );
+ aExt = aURL.GetFileExtension().toAsciiUpperCase();
+ return aExt;
+}
+
+bool isPCT(SvStream& rStream, sal_uLong nStreamPos, sal_uLong nStreamLen)
+{
+ sal_uInt8 sBuf[3];
+ // store number format
+ SvStreamEndian oldNumberFormat = rStream.GetEndian();
+ sal_uInt32 nOffset; // in MS documents the pict format is used without the first 512 bytes
+ for ( nOffset = 0; ( nOffset <= 512 ) && ( ( nStreamPos + nOffset + 14 ) <= nStreamLen ); nOffset += 512 )
+ {
+ short y1,x1,y2,x2;
+ bool bdBoxOk = true;
+
+ rStream.Seek( nStreamPos + nOffset);
+ // size of the pict in version 1 pict ( 2bytes) : ignored
+ rStream.SeekRel(2);
+ // bounding box (bytes 2 -> 9)
+ rStream.SetEndian(SvStreamEndian::BIG);
+ rStream.ReadInt16( y1 ).ReadInt16( x1 ).ReadInt16( y2 ).ReadInt16( x2 );
+ rStream.SetEndian(oldNumberFormat); // reset format
+
+ if (x1 > x2 || y1 > y2 || // bad bdbox
+ (x1 == x2 && y1 == y2) || // 1 pixel picture
+ x2-x1 > 2048 || y2-y1 > 2048 ) // picture abnormally big
+ bdBoxOk = false;
+
+ // read version op
+ rStream.ReadBytes(sBuf, 3);
+ // see http://developer.apple.com/legacy/mac/library/documentation/mac/pdf/Imaging_With_QuickDraw/Appendix_A.pdf
+ // normal version 2 - page A23 and A24
+ if ( sBuf[ 0 ] == 0x00 && sBuf[ 1 ] == 0x11 && sBuf[ 2 ] == 0x02)
+ return true;
+ // normal version 1 - page A25
+ else if (sBuf[ 0 ] == 0x11 && sBuf[ 1 ] == 0x01 && bdBoxOk)
+ return true;
+ }
+ return false;
+}
+
+ErrCode GraphicFilter::ImpTestOrFindFormat( std::u16string_view rPath, SvStream& rStream, sal_uInt16& rFormat )
+{
+ // determine or check the filter/format by reading into it
+ if( rFormat == GRFILTER_FORMAT_DONTKNOW )
+ {
+ OUString aFormatExt;
+ if (vcl::peekGraphicFormat(rStream, aFormatExt, false))
+ {
+ rFormat = pConfig->GetImportFormatNumberForExtension( aFormatExt );
+ if( rFormat != GRFILTER_FORMAT_DONTKNOW )
+ return ERRCODE_NONE;
+ }
+ // determine filter by file extension
+ if( !rPath.empty() )
+ {
+ OUString aExt( ImpGetExtension( rPath ) );
+ rFormat = pConfig->GetImportFormatNumberForExtension( aExt );
+ if( rFormat != GRFILTER_FORMAT_DONTKNOW )
+ return ERRCODE_NONE;
+ }
+ return ERRCODE_GRFILTER_FORMATERROR;
+ }
+ else
+ {
+ OUString aTmpStr( pConfig->GetImportFormatExtension( rFormat ) );
+ aTmpStr = aTmpStr.toAsciiUpperCase();
+ if (!vcl::peekGraphicFormat(rStream, aTmpStr, true))
+ return ERRCODE_GRFILTER_FORMATERROR;
+ if ( pConfig->GetImportFormatExtension( rFormat ).equalsIgnoreAsciiCase( "pcd" ) )
+ {
+ sal_Int32 nBase = 2; // default Base0
+ if ( pConfig->GetImportFilterType( rFormat ).equalsIgnoreAsciiCase( "pcd_Photo_CD_Base4" ) )
+ nBase = 1;
+ else if ( pConfig->GetImportFilterType( rFormat ).equalsIgnoreAsciiCase( "pcd_Photo_CD_Base16" ) )
+ nBase = 0;
+ FilterConfigItem aFilterConfigItem( u"Office.Common/Filter/Graphic/Import/PCD" );
+ aFilterConfigItem.WriteInt32( "Resolution", nBase );
+ }
+ }
+
+ return ERRCODE_NONE;
+}
+
+static Graphic ImpGetScaledGraphic( const Graphic& rGraphic, FilterConfigItem& rConfigItem )
+{
+ Graphic aGraphic;
+
+ sal_Int32 nLogicalWidth = rConfigItem.ReadInt32( "LogicalWidth", 0 );
+ sal_Int32 nLogicalHeight = rConfigItem.ReadInt32( "LogicalHeight", 0 );
+
+ if ( rGraphic.GetType() != GraphicType::NONE )
+ {
+ sal_Int32 nMode = rConfigItem.ReadInt32( "ExportMode", -1 );
+
+ if ( nMode == -1 ) // the property is not there, this is possible, if the graphic filter
+ { // is called via UnoGraphicExporter and not from a graphic export Dialog
+ nMode = 0; // then we are defaulting this mode to 0
+ if ( nLogicalWidth || nLogicalHeight )
+ nMode = 2;
+ }
+
+ Size aOriginalSize;
+ Size aPrefSize( rGraphic.GetPrefSize() );
+ MapMode aPrefMapMode( rGraphic.GetPrefMapMode() );
+ if (aPrefMapMode.GetMapUnit() == MapUnit::MapPixel)
+ aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aPrefSize, MapMode(MapUnit::Map100thMM));
+ else
+ aOriginalSize = OutputDevice::LogicToLogic(aPrefSize, aPrefMapMode, MapMode(MapUnit::Map100thMM));
+ if ( !nLogicalWidth )
+ nLogicalWidth = aOriginalSize.Width();
+ if ( !nLogicalHeight )
+ nLogicalHeight = aOriginalSize.Height();
+ if( rGraphic.GetType() == GraphicType::Bitmap )
+ {
+
+ // Resolution is set
+ if( nMode == 1 )
+ {
+ BitmapEx aBitmap( rGraphic.GetBitmapEx() );
+ MapMode aMap( MapUnit::Map100thInch );
+
+ sal_Int32 nDPI = rConfigItem.ReadInt32( "Resolution", 75 );
+ Fraction aFrac( 1, std::clamp( nDPI, sal_Int32(75), sal_Int32(600) ) );
+
+ aMap.SetScaleX( aFrac );
+ aMap.SetScaleY( aFrac );
+
+ Size aOldSize = aBitmap.GetSizePixel();
+ aGraphic = rGraphic;
+ aGraphic.SetPrefMapMode( aMap );
+ aGraphic.SetPrefSize( Size( aOldSize.Width() * 100,
+ aOldSize.Height() * 100 ) );
+ }
+ // Size is set
+ else if( nMode == 2 )
+ {
+ aGraphic = rGraphic;
+ aGraphic.SetPrefMapMode( MapMode( MapUnit::Map100thMM ) );
+ aGraphic.SetPrefSize( Size( nLogicalWidth, nLogicalHeight ) );
+ }
+ else
+ aGraphic = rGraphic;
+
+ sal_Int32 nColors = rConfigItem.ReadInt32( "Color", 0 );
+ if ( nColors ) // graphic conversion necessary ?
+ {
+ BitmapEx aBmpEx( aGraphic.GetBitmapEx() );
+ aBmpEx.Convert( static_cast<BmpConversion>(nColors) ); // the entries in the xml section have the same meaning as
+ aGraphic = aBmpEx; // they have in the BmpConversion enum, so it should be
+ } // allowed to cast them
+ }
+ else
+ {
+ if( ( nMode == 1 ) || ( nMode == 2 ) )
+ {
+ GDIMetaFile aMtf( rGraphic.GetGDIMetaFile() );
+ Size aNewSize( OutputDevice::LogicToLogic(Size(nLogicalWidth, nLogicalHeight), MapMode(MapUnit::Map100thMM), aMtf.GetPrefMapMode()) );
+
+ if( aNewSize.Width() && aNewSize.Height() )
+ {
+ const Size aPreferredSize( aMtf.GetPrefSize() );
+ aMtf.Scale( Fraction( aNewSize.Width(), aPreferredSize.Width() ),
+ Fraction( aNewSize.Height(), aPreferredSize.Height() ) );
+ }
+ aGraphic = Graphic( aMtf );
+ }
+ else
+ aGraphic = rGraphic;
+ }
+
+ }
+ else
+ aGraphic = rGraphic;
+
+ return aGraphic;
+}
+
+GraphicFilter::GraphicFilter( bool bConfig )
+ : bUseConfig(bConfig)
+{
+ ImplInit();
+}
+
+GraphicFilter::~GraphicFilter()
+{
+ {
+ std::scoped_lock aGuard( getListMutex() );
+ auto it = std::find(gaFilterHdlList.begin(), gaFilterHdlList.end(), this);
+ if( it != gaFilterHdlList.end() )
+ gaFilterHdlList.erase( it );
+
+ if( gaFilterHdlList.empty() )
+ delete pConfig;
+ }
+
+ mxErrorEx.reset();
+}
+
+void GraphicFilter::ImplInit()
+{
+ {
+ std::scoped_lock aGuard( getListMutex() );
+
+ if ( gaFilterHdlList.empty() )
+ pConfig = new FilterConfigCache( bUseConfig );
+ else
+ pConfig = gaFilterHdlList.front()->pConfig;
+
+ gaFilterHdlList.push_back( this );
+ }
+
+ if( bUseConfig )
+ {
+ OUString url("$BRAND_BASE_DIR/" LIBO_LIB_FOLDER);
+ rtl::Bootstrap::expandMacros(url); //TODO: detect failure
+ osl::FileBase::getSystemPathFromFileURL(url, aFilterPath);
+ }
+
+ mxErrorEx = ERRCODE_NONE;
+}
+
+ErrCode GraphicFilter::ImplSetError( ErrCode nError, const SvStream* pStm )
+{
+ mxErrorEx = pStm ? pStm->GetError() : ERRCODE_NONE;
+ return nError;
+}
+
+sal_uInt16 GraphicFilter::GetImportFormatCount() const
+{
+ return pConfig->GetImportFormatCount();
+}
+
+sal_uInt16 GraphicFilter::GetImportFormatNumber( std::u16string_view rFormatName )
+{
+ return pConfig->GetImportFormatNumber( rFormatName );
+}
+
+sal_uInt16 GraphicFilter::GetImportFormatNumberForShortName( std::u16string_view rShortName )
+{
+ return pConfig->GetImportFormatNumberForShortName( rShortName );
+}
+
+sal_uInt16 GraphicFilter::GetImportFormatNumberForTypeName( std::u16string_view rType )
+{
+ return pConfig->GetImportFormatNumberForTypeName( rType );
+}
+
+OUString GraphicFilter::GetImportFormatName( sal_uInt16 nFormat )
+{
+ return pConfig->GetImportFormatName( nFormat );
+}
+
+OUString GraphicFilter::GetImportFormatTypeName( sal_uInt16 nFormat )
+{
+ return pConfig->GetImportFilterTypeName( nFormat );
+}
+
+#ifdef _WIN32
+OUString GraphicFilter::GetImportFormatMediaType( sal_uInt16 nFormat )
+{
+ return pConfig->GetImportFormatMediaType( nFormat );
+}
+#endif
+
+OUString GraphicFilter::GetImportFormatShortName( sal_uInt16 nFormat )
+{
+ return pConfig->GetImportFormatShortName( nFormat );
+}
+
+OUString GraphicFilter::GetImportWildcard( sal_uInt16 nFormat, sal_Int32 nEntry )
+{
+ return pConfig->GetImportWildcard( nFormat, nEntry );
+}
+
+sal_uInt16 GraphicFilter::GetExportFormatCount() const
+{
+ return pConfig->GetExportFormatCount();
+}
+
+sal_uInt16 GraphicFilter::GetExportFormatNumber( std::u16string_view rFormatName )
+{
+ return pConfig->GetExportFormatNumber( rFormatName );
+}
+
+sal_uInt16 GraphicFilter::GetExportFormatNumberForMediaType( std::u16string_view rMediaType )
+{
+ return pConfig->GetExportFormatNumberForMediaType( rMediaType );
+}
+
+sal_uInt16 GraphicFilter::GetExportFormatNumberForShortName( std::u16string_view rShortName )
+{
+ return pConfig->GetExportFormatNumberForShortName( rShortName );
+}
+
+OUString GraphicFilter::GetExportInternalFilterName( sal_uInt16 nFormat )
+{
+ return pConfig->GetExportInternalFilterName( nFormat );
+}
+
+sal_uInt16 GraphicFilter::GetExportFormatNumberForTypeName( std::u16string_view rType )
+{
+ return pConfig->GetExportFormatNumberForTypeName( rType );
+}
+
+OUString GraphicFilter::GetExportFormatName( sal_uInt16 nFormat )
+{
+ return pConfig->GetExportFormatName( nFormat );
+}
+
+OUString GraphicFilter::GetExportFormatMediaType( sal_uInt16 nFormat )
+{
+ return pConfig->GetExportFormatMediaType( nFormat );
+}
+
+OUString GraphicFilter::GetExportFormatShortName( sal_uInt16 nFormat )
+{
+ return pConfig->GetExportFormatShortName( nFormat );
+}
+
+OUString GraphicFilter::GetExportWildcard( sal_uInt16 nFormat )
+{
+ return pConfig->GetExportWildcard( nFormat, 0 );
+}
+
+bool GraphicFilter::IsExportPixelFormat( sal_uInt16 nFormat )
+{
+ return pConfig->IsExportPixelFormat( nFormat );
+}
+
+ErrCode GraphicFilter::CanImportGraphic( const INetURLObject& rPath,
+ sal_uInt16 nFormat, sal_uInt16* pDeterminedFormat )
+{
+ ErrCode nRetValue = ERRCODE_GRFILTER_FORMATERROR;
+ SAL_WARN_IF( rPath.GetProtocol() == INetProtocol::NotValid, "vcl.filter", "GraphicFilter::CanImportGraphic() : ProtType == INetProtocol::NotValid" );
+
+ OUString aMainUrl( rPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ std::unique_ptr<SvStream> xStream(::utl::UcbStreamHelper::CreateStream( aMainUrl, StreamMode::READ | StreamMode::SHARE_DENYNONE ));
+ if (xStream)
+ {
+ nRetValue = CanImportGraphic( aMainUrl, *xStream, nFormat, pDeterminedFormat );
+ }
+ return nRetValue;
+}
+
+ErrCode GraphicFilter::CanImportGraphic( std::u16string_view rMainUrl, SvStream& rIStream,
+ sal_uInt16 nFormat, sal_uInt16* pDeterminedFormat )
+{
+ sal_uInt64 nStreamPos = rIStream.Tell();
+ ErrCode nRes = ImpTestOrFindFormat( rMainUrl, rIStream, nFormat );
+
+ rIStream.Seek(nStreamPos);
+
+ if( nRes==ERRCODE_NONE && pDeterminedFormat!=nullptr )
+ *pDeterminedFormat = nFormat;
+
+ return ImplSetError( nRes, &rIStream );
+}
+
+//SJ: TODO, we need to create a GraphicImporter component
+ErrCode GraphicFilter::ImportGraphic( Graphic& rGraphic, const INetURLObject& rPath,
+ sal_uInt16 nFormat, sal_uInt16 * pDeterminedFormat, GraphicFilterImportFlags nImportFlags )
+{
+ ErrCode nRetValue = ERRCODE_GRFILTER_FORMATERROR;
+ SAL_WARN_IF( rPath.GetProtocol() == INetProtocol::NotValid, "vcl.filter", "GraphicFilter::ImportGraphic() : ProtType == INetProtocol::NotValid" );
+
+ OUString aMainUrl( rPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ std::unique_ptr<SvStream> xStream(::utl::UcbStreamHelper::CreateStream( aMainUrl, StreamMode::READ | StreamMode::SHARE_DENYNONE ));
+ if (xStream)
+ {
+ nRetValue = ImportGraphic( rGraphic, aMainUrl, *xStream, nFormat, pDeterminedFormat, nImportFlags );
+ }
+ return nRetValue;
+}
+
+namespace {
+
+/// Contains a stream and other associated data to import pixels into a
+/// Graphic.
+struct GraphicImportContext
+{
+ /// Pixel data is read from this stream.
+ std::unique_ptr<SvStream> m_pStream;
+ /// The Graphic the import filter gets.
+ std::shared_ptr<Graphic> m_pGraphic;
+ /// Write pixel data using this access.
+ std::unique_ptr<BitmapScopedWriteAccess> m_pAccess;
+ std::unique_ptr<AlphaScopedWriteAccess> m_pAlphaAccess;
+ // Need to have an AlphaMask instance to keep its lifetime.
+ AlphaMask mAlphaMask;
+ /// Signals if import finished correctly.
+ ErrCode m_nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ /// Original graphic format.
+ GfxLinkType m_eLinkType = GfxLinkType::NONE;
+ /// Position of the stream before reading the data.
+ sal_uInt64 m_nStreamBegin = 0;
+ /// Flags for the import filter.
+ GraphicFilterImportFlags m_nImportFlags = GraphicFilterImportFlags::NONE;
+};
+
+/// Graphic import worker that gets executed on a thread.
+class GraphicImportTask : public comphelper::ThreadTask
+{
+ GraphicImportContext& m_rContext;
+public:
+ GraphicImportTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, GraphicImportContext& rContext);
+ void doWork() override;
+ /// Shared code between threaded and non-threaded version.
+ static void doImport(GraphicImportContext& rContext);
+};
+
+}
+
+GraphicImportTask::GraphicImportTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, GraphicImportContext& rContext)
+ : comphelper::ThreadTask(pTag),
+ m_rContext(rContext)
+{
+}
+
+void GraphicImportTask::doWork()
+{
+ GraphicImportTask::doImport(m_rContext);
+}
+
+void GraphicImportTask::doImport(GraphicImportContext& rContext)
+{
+ if(rContext.m_eLinkType == GfxLinkType::NativeJpg)
+ {
+ if (!ImportJPEG(*rContext.m_pStream, *rContext.m_pGraphic, rContext.m_nImportFlags | GraphicFilterImportFlags::UseExistingBitmap, rContext.m_pAccess.get()))
+ rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ else if(rContext.m_eLinkType == GfxLinkType::NativePng)
+ {
+ if (!vcl::ImportPNG(*rContext.m_pStream, *rContext.m_pGraphic,
+ rContext.m_nImportFlags | GraphicFilterImportFlags::UseExistingBitmap,
+ rContext.m_pAccess.get(), rContext.m_pAlphaAccess.get()))
+ {
+ rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ }
+}
+
+void GraphicFilter::ImportGraphics(std::vector< std::shared_ptr<Graphic> >& rGraphics, std::vector< std::unique_ptr<SvStream> > vStreams)
+{
+ static bool bThreads = !getenv("VCL_NO_THREAD_IMPORT");
+ std::vector<GraphicImportContext> aContexts;
+ aContexts.reserve(vStreams.size());
+ comphelper::ThreadPool& rSharedPool = comphelper::ThreadPool::getSharedOptimalPool();
+ std::shared_ptr<comphelper::ThreadTaskTag> pTag = comphelper::ThreadPool::createThreadTaskTag();
+
+ for (auto& pStream : vStreams)
+ {
+ aContexts.emplace_back();
+ GraphicImportContext& rContext = aContexts.back();
+
+ if (pStream)
+ {
+ rContext.m_pStream = std::move(pStream);
+ rContext.m_pGraphic = std::make_shared<Graphic>();
+ rContext.m_nStatus = ERRCODE_NONE;
+
+ // Detect the format.
+ ResetLastError();
+ rContext.m_nStreamBegin = rContext.m_pStream->Tell();
+ sal_uInt16 nFormat = GRFILTER_FORMAT_DONTKNOW;
+ rContext.m_nStatus = ImpTestOrFindFormat(u"", *rContext.m_pStream, nFormat);
+ rContext.m_pStream->Seek(rContext.m_nStreamBegin);
+
+ // Import the graphic.
+ if (rContext.m_nStatus == ERRCODE_NONE && !rContext.m_pStream->GetError())
+ {
+ OUString aFilterName = pConfig->GetImportFilterName(nFormat);
+
+ if (aFilterName.equalsIgnoreAsciiCase(IMP_JPEG))
+ {
+ rContext.m_eLinkType = GfxLinkType::NativeJpg;
+ rContext.m_nImportFlags = GraphicFilterImportFlags::SetLogsizeForJpeg;
+
+ if (ImportJPEG( *rContext.m_pStream, *rContext.m_pGraphic, rContext.m_nImportFlags | GraphicFilterImportFlags::OnlyCreateBitmap, nullptr))
+ {
+ Bitmap& rBitmap = const_cast<Bitmap&>(rContext.m_pGraphic->GetBitmapExRef().GetBitmap());
+ rContext.m_pAccess = std::make_unique<BitmapScopedWriteAccess>(rBitmap);
+ rContext.m_pStream->Seek(rContext.m_nStreamBegin);
+ if (bThreads)
+ rSharedPool.pushTask(std::make_unique<GraphicImportTask>(pTag, rContext));
+ else
+ GraphicImportTask::doImport(rContext);
+ }
+ else
+ rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_PNG))
+ {
+ rContext.m_eLinkType = GfxLinkType::NativePng;
+
+ if (vcl::ImportPNG( *rContext.m_pStream, *rContext.m_pGraphic, rContext.m_nImportFlags | GraphicFilterImportFlags::OnlyCreateBitmap, nullptr, nullptr))
+ {
+ const BitmapEx& rBitmapEx = rContext.m_pGraphic->GetBitmapExRef();
+ Bitmap& rBitmap = const_cast<Bitmap&>(rBitmapEx.GetBitmap());
+ rContext.m_pAccess = std::make_unique<BitmapScopedWriteAccess>(rBitmap);
+ if(rBitmapEx.IsAlpha())
+ {
+ // The separate alpha bitmap causes a number of complications. Not only
+ // we need to have an extra bitmap access for it, but we also need
+ // to keep an AlphaMask instance in the context. This is because
+ // BitmapEx internally keeps Bitmap and not AlphaMask (because the Bitmap
+ // may be also a mask, not alpha). So BitmapEx::GetAlpha() returns
+ // a temporary, and direct access to the Bitmap wouldn't work
+ // with AlphaScopedBitmapAccess. *sigh*
+ rContext.mAlphaMask = rBitmapEx.GetAlpha();
+ rContext.m_pAlphaAccess = std::make_unique<AlphaScopedWriteAccess>(rContext.mAlphaMask);
+ }
+ rContext.m_pStream->Seek(rContext.m_nStreamBegin);
+ if (bThreads)
+ rSharedPool.pushTask(std::make_unique<GraphicImportTask>(pTag, rContext));
+ else
+ GraphicImportTask::doImport(rContext);
+ }
+ else
+ rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ else
+ rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ }
+ }
+
+ rSharedPool.waitUntilDone(pTag);
+
+ // Process data after import.
+ for (auto& rContext : aContexts)
+ {
+ if(rContext.m_pAlphaAccess) // Need to move the AlphaMask back to the BitmapEx.
+ *rContext.m_pGraphic = BitmapEx( rContext.m_pGraphic->GetBitmapExRef().GetBitmap(), rContext.mAlphaMask );
+ rContext.m_pAccess.reset();
+ rContext.m_pAlphaAccess.reset();
+
+ if (rContext.m_nStatus == ERRCODE_NONE && (rContext.m_eLinkType != GfxLinkType::NONE) && !rContext.m_pGraphic->GetReaderContext())
+ {
+ std::unique_ptr<sal_uInt8[]> pGraphicContent;
+
+ const sal_uInt64 nStreamEnd = rContext.m_pStream->Tell();
+ sal_Int32 nGraphicContentSize = nStreamEnd - rContext.m_nStreamBegin;
+
+ if (nGraphicContentSize > 0)
+ {
+ try
+ {
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+ }
+ catch (const std::bad_alloc&)
+ {
+ rContext.m_nStatus = ERRCODE_GRFILTER_TOOBIG;
+ }
+
+ if (rContext.m_nStatus == ERRCODE_NONE)
+ {
+ rContext.m_pStream->Seek(rContext.m_nStreamBegin);
+ rContext.m_pStream->ReadBytes(pGraphicContent.get(), nGraphicContentSize);
+ }
+ }
+
+ if (rContext.m_nStatus == ERRCODE_NONE)
+ rContext.m_pGraphic->SetGfxLink(std::make_shared<GfxLink>(std::move(pGraphicContent), nGraphicContentSize, rContext.m_eLinkType));
+ }
+
+ if (rContext.m_nStatus != ERRCODE_NONE)
+ rContext.m_pGraphic = nullptr;
+
+ rGraphics.push_back(rContext.m_pGraphic);
+ }
+}
+
+void GraphicFilter::MakeGraphicsAvailableThreaded(std::vector<Graphic*>& graphics)
+{
+ // Graphic::makeAvailable() is not thread-safe. Only the jpeg and png loaders are, so here
+ // we process only jpeg and png images that also have their stream data, load new Graphic's
+ // from them and then update the passed objects using them.
+ std::vector< Graphic* > toLoad;
+ for(auto graphic : graphics)
+ {
+ // Need to use GetSharedGfxLink, to access the pointer without copying.
+ if(!graphic->isAvailable() && graphic->IsGfxLink()
+ && graphic->GetSharedGfxLink()->GetDataSize() != 0
+ && (graphic->GetSharedGfxLink()->GetType() == GfxLinkType::NativeJpg
+ || graphic->GetSharedGfxLink()->GetType() == GfxLinkType::NativePng))
+ {
+ // Graphic objects share internal ImpGraphic, do not process any of those twice.
+ const auto predicate = [graphic](Graphic* item) { return item->ImplGetImpGraphic() == graphic->ImplGetImpGraphic(); };
+ if( std::find_if(toLoad.begin(), toLoad.end(), predicate ) == toLoad.end())
+ toLoad.push_back( graphic );
+ }
+ }
+ if( toLoad.empty())
+ return;
+ std::vector< std::unique_ptr<SvStream>> streams;
+ for( auto graphic : toLoad )
+ {
+ streams.push_back( std::make_unique<SvMemoryStream>( const_cast<sal_uInt8*>(graphic->GetSharedGfxLink()->GetData()),
+ graphic->GetSharedGfxLink()->GetDataSize(), StreamMode::READ | StreamMode::WRITE));
+ }
+ std::vector< std::shared_ptr<Graphic>> loadedGraphics;
+ ImportGraphics(loadedGraphics, std::move(streams));
+ assert(loadedGraphics.size() == toLoad.size());
+ for( size_t i = 0; i < toLoad.size(); ++i )
+ {
+ if(loadedGraphics[ i ] != nullptr)
+ toLoad[ i ]->ImplGetImpGraphic()->updateFromLoadedGraphic(loadedGraphics[ i ]->ImplGetImpGraphic());
+ }
+}
+
+Graphic GraphicFilter::ImportUnloadedGraphic(SvStream& rIStream, sal_uInt64 sizeLimit,
+ const Size* pSizeHint)
+{
+ Graphic aGraphic;
+ sal_uInt16 nFormat = GRFILTER_FORMAT_DONTKNOW;
+ GfxLinkType eLinkType = GfxLinkType::NONE;
+
+ ResetLastError();
+
+ const sal_uInt64 nStreamBegin = rIStream.Tell();
+
+ rIStream.Seek(nStreamBegin);
+
+ ErrCode nStatus = ImpTestOrFindFormat(u"", rIStream, nFormat);
+
+ rIStream.Seek(nStreamBegin);
+ sal_uInt32 nStreamLength(rIStream.remainingSize());
+ if (sizeLimit && sizeLimit < nStreamLength)
+ nStreamLength = sizeLimit;
+
+ OUString aFilterName = pConfig->GetImportFilterName(nFormat);
+
+ std::unique_ptr<sal_uInt8[]> pGraphicContent;
+ sal_Int32 nGraphicContentSize = 0;
+
+ // read graphic
+ {
+ if (aFilterName.equalsIgnoreAsciiCase(IMP_GIF))
+ {
+ eLinkType = GfxLinkType::NativeGif;
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_PNG))
+ {
+ // check if this PNG contains a GIF chunk!
+ pGraphicContent = vcl::PngImageReader::getMicrosoftGifChunk(rIStream, &nGraphicContentSize);
+ if( pGraphicContent )
+ eLinkType = GfxLinkType::NativeGif;
+ else
+ eLinkType = GfxLinkType::NativePng;
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_JPEG))
+ {
+ eLinkType = GfxLinkType::NativeJpg;
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_SVG))
+ {
+ bool bOkay(false);
+
+ if (nStreamLength > 0)
+ {
+ std::vector<sal_uInt8> aTwoBytes(2);
+ rIStream.ReadBytes(aTwoBytes.data(), 2);
+ rIStream.Seek(nStreamBegin);
+
+ if (aTwoBytes[0] == 0x1F && aTwoBytes[1] == 0x8B)
+ {
+ SvMemoryStream aMemStream;
+ ZCodec aCodec;
+ tools::Long nMemoryLength;
+
+ aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true);
+ nMemoryLength = aCodec.Decompress(rIStream, aMemStream);
+ aCodec.EndCompression();
+
+ if (!rIStream.GetError() && nMemoryLength >= 0)
+ {
+ nGraphicContentSize = nMemoryLength;
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+
+ aMemStream.Seek(STREAM_SEEK_TO_BEGIN);
+ aMemStream.ReadBytes(pGraphicContent.get(), nGraphicContentSize);
+
+ bOkay = true;
+ }
+ }
+ else
+ {
+ nGraphicContentSize = nStreamLength;
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+ rIStream.ReadBytes(pGraphicContent.get(), nStreamLength);
+
+ bOkay = true;
+ }
+ }
+
+ if (bOkay)
+ {
+ eLinkType = GfxLinkType::NativeSvg;
+ }
+ else
+ {
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_BMP))
+ {
+ eLinkType = GfxLinkType::NativeBmp;
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_MOV))
+ {
+ eLinkType = GfxLinkType::NativeMov;
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_WMF) ||
+ aFilterName.equalsIgnoreAsciiCase(IMP_EMF))
+ {
+ nGraphicContentSize = nStreamLength;
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+ rIStream.Seek(nStreamBegin);
+ if (ZCodec::IsZCompressed(rIStream))
+ {
+ ZCodec aCodec;
+ SvMemoryStream aMemStream;
+ tools::Long nMemoryLength;
+ aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true);
+ nMemoryLength = aCodec.Decompress(rIStream, aMemStream);
+ aCodec.EndCompression();
+
+ if (!rIStream.GetError() && nMemoryLength >= 0)
+ {
+ nGraphicContentSize = nMemoryLength;
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+
+ aMemStream.Seek(STREAM_SEEK_TO_BEGIN);
+ aMemStream.ReadBytes(pGraphicContent.get(), nGraphicContentSize);
+ }
+ }
+ else
+ {
+ rIStream.ReadBytes(pGraphicContent.get(), nStreamLength);
+ }
+ if (!rIStream.GetError())
+ {
+ eLinkType = GfxLinkType::NativeWmf;
+ }
+ else
+ {
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ }
+ else if (aFilterName == IMP_PDF)
+ {
+ eLinkType = GfxLinkType::NativePdf;
+ }
+ else if (aFilterName == IMP_TIFF)
+ {
+ eLinkType = GfxLinkType::NativeTif;
+ }
+ else if (aFilterName == IMP_PICT)
+ {
+ eLinkType = GfxLinkType::NativePct;
+ }
+ else if (aFilterName == IMP_MET)
+ {
+ eLinkType = GfxLinkType::NativeMet;
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_WEBP))
+ {
+ if(supportNativeWebp())
+ eLinkType = GfxLinkType::NativeWebp;
+ else
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ else
+ {
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ }
+
+ if (nStatus == ERRCODE_NONE && eLinkType != GfxLinkType::NONE)
+ {
+ if (!pGraphicContent)
+ {
+ nGraphicContentSize = nStreamLength;
+
+ if (nGraphicContentSize > 0)
+ {
+ try
+ {
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+ }
+ catch (const std::bad_alloc&)
+ {
+ nStatus = ERRCODE_GRFILTER_TOOBIG;
+ }
+
+ if (nStatus == ERRCODE_NONE)
+ {
+ rIStream.Seek(nStreamBegin);
+ nGraphicContentSize = rIStream.ReadBytes(pGraphicContent.get(), nGraphicContentSize);
+ }
+ }
+ }
+
+ if( nStatus == ERRCODE_NONE )
+ {
+ bool bAnimated = false;
+ Size aLogicSize;
+ if (eLinkType == GfxLinkType::NativeGif)
+ {
+ SvMemoryStream aMemoryStream(pGraphicContent.get(), nGraphicContentSize, StreamMode::READ);
+ bAnimated = IsGIFAnimated(aMemoryStream, aLogicSize);
+ if (!pSizeHint && aLogicSize.getWidth() && aLogicSize.getHeight())
+ {
+ pSizeHint = &aLogicSize;
+ }
+ }
+ aGraphic.SetGfxLink(std::make_shared<GfxLink>(std::move(pGraphicContent), nGraphicContentSize, eLinkType));
+ aGraphic.ImplGetImpGraphic()->setPrepared(bAnimated, pSizeHint);
+ }
+ }
+
+ // Set error code or try to set native buffer
+ if (nStatus != ERRCODE_NONE)
+ ImplSetError(nStatus, &rIStream);
+ if (nStatus != ERRCODE_NONE || eLinkType == GfxLinkType::NONE)
+ rIStream.Seek(nStreamBegin);
+
+ return aGraphic;
+}
+
+ErrCode GraphicFilter::readGIF(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType)
+{
+ if (ImportGIF(rStream, rGraphic))
+ {
+ rLinkType = GfxLinkType::NativeGif;
+ return ERRCODE_NONE;
+ }
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readPNG(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType, std::unique_ptr<sal_uInt8[]> & rpGraphicContent,
+ sal_Int32& rGraphicContentSize)
+{
+ ErrCode aReturnCode = ERRCODE_NONE;
+
+ // check if this PNG contains a GIF chunk!
+ rpGraphicContent = vcl::PngImageReader::getMicrosoftGifChunk(rStream, &rGraphicContentSize);
+ if( rpGraphicContent )
+ {
+ SvMemoryStream aIStrm(rpGraphicContent.get(), rGraphicContentSize, StreamMode::READ);
+ ImportGIF(aIStrm, rGraphic);
+ rLinkType = GfxLinkType::NativeGif;
+ return aReturnCode;
+ }
+
+ // PNG has no GIF chunk
+ vcl::PngImageReader aPNGReader(rStream);
+ BitmapEx aBitmapEx(aPNGReader.read());
+ if (!aBitmapEx.IsEmpty())
+ {
+ rGraphic = aBitmapEx;
+ rLinkType = GfxLinkType::NativePng;
+ }
+ else
+ aReturnCode = ERRCODE_GRFILTER_FILTERERROR;
+
+ return aReturnCode;
+}
+
+ErrCode GraphicFilter::readJPEG(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType, GraphicFilterImportFlags nImportFlags)
+{
+ ErrCode aReturnCode = ERRCODE_NONE;
+
+ // set LOGSIZE flag always, if not explicitly disabled
+ // (see #90508 and #106763)
+ if (!(nImportFlags & GraphicFilterImportFlags::DontSetLogsizeForJpeg))
+ {
+ nImportFlags |= GraphicFilterImportFlags::SetLogsizeForJpeg;
+ }
+
+ sal_uInt64 nPosition = rStream.Tell();
+ if (!ImportJPEG(rStream, rGraphic, nImportFlags | GraphicFilterImportFlags::OnlyCreateBitmap, nullptr))
+ aReturnCode = ERRCODE_GRFILTER_FILTERERROR;
+ else
+ {
+ Bitmap& rBitmap = const_cast<Bitmap&>(rGraphic.GetBitmapExRef().GetBitmap());
+ BitmapScopedWriteAccess pWriteAccess(rBitmap);
+ rStream.Seek(nPosition);
+ if (!ImportJPEG(rStream, rGraphic, nImportFlags | GraphicFilterImportFlags::UseExistingBitmap, &pWriteAccess))
+ aReturnCode = ERRCODE_GRFILTER_FILTERERROR;
+ else
+ rLinkType = GfxLinkType::NativeJpg;
+ }
+
+ return aReturnCode;
+}
+
+ErrCode GraphicFilter::readSVG(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType, std::unique_ptr<sal_uInt8[]> & rpGraphicContent,
+ sal_Int32& rGraphicContentSize)
+{
+ ErrCode aReturnCode = ERRCODE_NONE;
+
+ const sal_uInt64 nStreamPosition(rStream.Tell());
+ const sal_uInt64 nStreamLength(rStream.remainingSize());
+
+ bool bOkay(false);
+
+ if (nStreamLength > 0)
+ {
+ std::vector<sal_uInt8> aTwoBytes(2);
+ rStream.ReadBytes(aTwoBytes.data(), 2);
+ rStream.Seek(nStreamPosition);
+
+ if (aTwoBytes[0] == 0x1F && aTwoBytes[1] == 0x8B)
+ {
+ SvMemoryStream aMemStream;
+ ZCodec aCodec;
+ tools::Long nMemoryLength;
+
+ aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true);
+ nMemoryLength = aCodec.Decompress(rStream, aMemStream);
+ aCodec.EndCompression();
+
+ if (!rStream.GetError() && nMemoryLength >= 0)
+ {
+ VectorGraphicDataArray aNewData(nMemoryLength);
+ aMemStream.Seek(STREAM_SEEK_TO_BEGIN);
+ aMemStream.ReadBytes(aNewData.getArray(), nMemoryLength);
+
+ // Make a uncompressed copy for GfxLink
+ rGraphicContentSize = nMemoryLength;
+ rpGraphicContent.reset(new sal_uInt8[rGraphicContentSize]);
+ std::copy(std::cbegin(aNewData), std::cend(aNewData), rpGraphicContent.get());
+
+ if (!aMemStream.GetError())
+ {
+ BinaryDataContainer aDataContainer(reinterpret_cast<const sal_uInt8*>(aNewData.getConstArray()), aNewData.getLength());
+ auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aDataContainer, VectorGraphicDataType::Svg);
+ rGraphic = Graphic(aVectorGraphicDataPtr);
+ bOkay = true;
+ }
+ }
+ }
+ else
+ {
+ VectorGraphicDataArray aNewData(nStreamLength);
+ rStream.ReadBytes(aNewData.getArray(), nStreamLength);
+
+ if (!rStream.GetError())
+ {
+ BinaryDataContainer aDataContainer(reinterpret_cast<const sal_uInt8*>(aNewData.getConstArray()), aNewData.getLength());
+ auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aDataContainer, VectorGraphicDataType::Svg);
+ rGraphic = Graphic(aVectorGraphicDataPtr);
+ bOkay = true;
+ }
+ }
+ }
+
+ if (bOkay)
+ {
+ rLinkType = GfxLinkType::NativeSvg;
+ }
+ else
+ {
+ aReturnCode = ERRCODE_GRFILTER_FILTERERROR;
+ }
+
+ return aReturnCode;
+}
+
+ErrCode GraphicFilter::readXBM(SvStream & rStream, Graphic & rGraphic)
+{
+ if (ImportXBM(rStream, rGraphic))
+ return ERRCODE_NONE;
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readXPM(SvStream & rStream, Graphic & rGraphic)
+{
+ if (ImportXPM(rStream, rGraphic))
+ return ERRCODE_NONE;
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readWMF_EMF(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType, VectorGraphicDataType eType)
+{
+ // use new UNO API service, do not directly import but create a
+ // Graphic that contains the original data and decomposes to
+ // primitives on demand
+ sal_uInt32 nStreamLength(rStream.remainingSize());
+ SvStream* aNewStream = &rStream;
+ ErrCode aReturnCode = ERRCODE_GRFILTER_FILTERERROR;
+ SvMemoryStream aMemStream;
+ if (ZCodec::IsZCompressed(rStream))
+ {
+ ZCodec aCodec;
+ aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true);
+ auto nDecompressLength = aCodec.Decompress(rStream, aMemStream);
+ aCodec.EndCompression();
+ aMemStream.Seek(STREAM_SEEK_TO_BEGIN);
+ if (nDecompressLength >= 0)
+ {
+ nStreamLength = nDecompressLength;
+ aNewStream = &aMemStream;
+ }
+ }
+ VectorGraphicDataArray aNewData(nStreamLength);
+ aNewStream->ReadBytes(aNewData.getArray(), nStreamLength);
+ if (!aNewStream->GetError())
+ {
+ const VectorGraphicDataType aDataType(eType);
+ BinaryDataContainer aDataContainer(reinterpret_cast<const sal_uInt8*>(aNewData.getConstArray()), aNewData.getLength());
+
+ auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aDataContainer, aDataType);
+
+ rGraphic = Graphic(aVectorGraphicDataPtr);
+ rLinkType = GfxLinkType::NativeWmf;
+ aReturnCode = ERRCODE_NONE;
+ }
+
+ return aReturnCode;
+}
+
+ErrCode GraphicFilter::readWMF(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType)
+{
+ return readWMF_EMF(rStream, rGraphic, rLinkType,VectorGraphicDataType::Wmf);
+}
+
+ErrCode GraphicFilter::readEMF(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType)
+{
+ return readWMF_EMF(rStream, rGraphic, rLinkType, VectorGraphicDataType::Emf);
+}
+
+ErrCode GraphicFilter::readPDF(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType)
+{
+ if (vcl::ImportPDF(rStream, rGraphic))
+ {
+ rLinkType = GfxLinkType::NativePdf;
+ return ERRCODE_NONE;
+ }
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readTIFF(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType)
+{
+ if (ImportTiffGraphicImport(rStream, rGraphic))
+ {
+ rLinkType = GfxLinkType::NativeTif;
+ return ERRCODE_NONE;
+ }
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readWithTypeSerializer(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType, std::u16string_view aFilterName)
+{
+ ErrCode aReturnCode = ERRCODE_GRFILTER_FILTERERROR;
+
+ // SV internal filters for import bitmaps and MetaFiles
+ TypeSerializer aSerializer(rStream);
+ aSerializer.readGraphic(rGraphic);
+
+ if (!rStream.GetError())
+ {
+ if (o3tl::equalsIgnoreAsciiCase(aFilterName, u"" IMP_MOV))
+ {
+ rGraphic.SetDefaultType();
+ rStream.Seek(STREAM_SEEK_TO_END);
+ rLinkType = GfxLinkType::NativeMov;
+ }
+ aReturnCode = ERRCODE_NONE;
+ }
+ return aReturnCode;
+}
+
+ErrCode GraphicFilter::readBMP(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType)
+{
+ if (BmpReader(rStream, rGraphic))
+ {
+ rLinkType = GfxLinkType::NativeBmp;
+ return ERRCODE_NONE;
+ }
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readTGA(SvStream & rStream, Graphic & rGraphic)
+{
+ if (ImportTgaGraphic(rStream, rGraphic))
+ return ERRCODE_NONE;
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readPICT(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType)
+{
+ if (ImportPictGraphic(rStream, rGraphic))
+ {
+ rLinkType = GfxLinkType::NativePct;
+ return ERRCODE_NONE;
+ }
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readMET(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType)
+{
+ if (ImportMetGraphic(rStream, rGraphic))
+ {
+ rLinkType = GfxLinkType::NativeMet;
+ return ERRCODE_NONE;
+ }
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readRAS(SvStream & rStream, Graphic & rGraphic)
+{
+ if (ImportRasGraphic(rStream, rGraphic))
+ return ERRCODE_NONE;
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readPCX(SvStream & rStream, Graphic & rGraphic)
+{
+ if (ImportPcxGraphic(rStream, rGraphic))
+ return ERRCODE_NONE;
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readEPS(SvStream & rStream, Graphic & rGraphic)
+{
+ if (ImportEpsGraphic(rStream, rGraphic))
+ return ERRCODE_NONE;
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readPSD(SvStream & rStream, Graphic & rGraphic)
+{
+ if (ImportPsdGraphic(rStream, rGraphic))
+ return ERRCODE_NONE;
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readPCD(SvStream & rStream, Graphic & rGraphic)
+{
+ std::unique_ptr<FilterConfigItem> pFilterConfigItem;
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ OUString aFilterConfigPath( "Office.Common/Filter/Graphic/Import/PCD" );
+ pFilterConfigItem = std::make_unique<FilterConfigItem>(aFilterConfigPath);
+ }
+
+ if (ImportPcdGraphic(rStream, rGraphic, pFilterConfigItem.get()))
+ return ERRCODE_NONE;
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readPBM(SvStream & rStream, Graphic & rGraphic)
+{
+ if (ImportPbmGraphic(rStream, rGraphic))
+ return ERRCODE_NONE;
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readDXF(SvStream & rStream, Graphic & rGraphic)
+{
+ if (ImportDxfGraphic(rStream, rGraphic))
+ return ERRCODE_NONE;
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::readWEBP(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType)
+{
+ if (ImportWebpGraphic(rStream, rGraphic))
+ {
+ if(supportNativeWebp())
+ rLinkType = GfxLinkType::NativeWebp;
+ return ERRCODE_NONE;
+ }
+ else
+ return ERRCODE_GRFILTER_FILTERERROR;
+}
+
+ErrCode GraphicFilter::ImportGraphic(Graphic& rGraphic, std::u16string_view rPath, SvStream& rIStream,
+ sal_uInt16 nFormat, sal_uInt16* pDeterminedFormat, GraphicFilterImportFlags nImportFlags)
+{
+ OUString aFilterName;
+ sal_uInt64 nStreamBegin;
+ ErrCode nStatus;
+ GfxLinkType eLinkType = GfxLinkType::NONE;
+ const bool bLinkSet = rGraphic.IsGfxLink();
+
+ std::unique_ptr<sal_uInt8[]> pGraphicContent;
+ sal_Int32 nGraphicContentSize = 0;
+
+ ResetLastError();
+
+ std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext();
+ bool bDummyContext = rGraphic.IsDummyContext();
+ if( !pContext || bDummyContext )
+ {
+ if( bDummyContext )
+ {
+ rGraphic.SetDummyContext( false );
+ nStreamBegin = 0;
+ }
+ else
+ nStreamBegin = rIStream.Tell();
+
+ nStatus = ImpTestOrFindFormat( rPath, rIStream, nFormat );
+ // if pending, return ERRCODE_NONE in order to request more bytes
+ if( rIStream.GetError() == ERRCODE_IO_PENDING )
+ {
+ rGraphic.SetDummyContext(true);
+ rIStream.ResetError();
+ rIStream.Seek( nStreamBegin );
+ return ImplSetError( ERRCODE_NONE );
+ }
+
+ rIStream.Seek( nStreamBegin );
+
+ if( ( nStatus != ERRCODE_NONE ) || rIStream.GetError() )
+ return ImplSetError( ( nStatus != ERRCODE_NONE ) ? nStatus : ERRCODE_GRFILTER_OPENERROR, &rIStream );
+
+ if( pDeterminedFormat )
+ *pDeterminedFormat = nFormat;
+
+ aFilterName = pConfig->GetImportFilterName( nFormat );
+ }
+ else
+ {
+ aFilterName = pContext->GetUpperFilterName();
+
+ nStreamBegin = 0;
+ nStatus = ERRCODE_NONE;
+ }
+
+ // read graphic
+ {
+ if (aFilterName.equalsIgnoreAsciiCase(IMP_GIF))
+ {
+ nStatus = readGIF(rIStream, rGraphic, eLinkType);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_PNG))
+ {
+ nStatus = readPNG(rIStream, rGraphic, eLinkType, pGraphicContent, nGraphicContentSize);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_JPEG))
+ {
+ nStatus = readJPEG(rIStream, rGraphic, eLinkType, nImportFlags);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_SVG))
+ {
+ nStatus = readSVG(rIStream, rGraphic, eLinkType, pGraphicContent, nGraphicContentSize);
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( IMP_XBM ) )
+ {
+ nStatus = readXBM(rIStream, rGraphic);
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( IMP_XPM ) )
+ {
+ nStatus = readXPM(rIStream, rGraphic);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_BMP))
+ {
+ nStatus = readBMP(rIStream, rGraphic, eLinkType);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_SVMETAFILE))
+ {
+ nStatus = readWithTypeSerializer(rIStream, rGraphic, eLinkType, aFilterName);
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase(IMP_MOV))
+ {
+ nStatus = readWithTypeSerializer(rIStream, rGraphic, eLinkType, aFilterName);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_WMF))
+ {
+ nStatus = readWMF(rIStream, rGraphic, eLinkType);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_EMF))
+ {
+ nStatus = readEMF(rIStream, rGraphic, eLinkType);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_PDF))
+ {
+ nStatus = readPDF(rIStream, rGraphic, eLinkType);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_TIFF) )
+ {
+ nStatus = readTIFF(rIStream, rGraphic, eLinkType);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_TGA) )
+ {
+ nStatus = readTGA(rIStream, rGraphic);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_PICT))
+ {
+ nStatus = readPICT(rIStream, rGraphic, eLinkType);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_MET))
+ {
+ nStatus = readMET(rIStream, rGraphic, eLinkType);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_RAS))
+ {
+ nStatus = readRAS(rIStream, rGraphic);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_PCX))
+ {
+ nStatus = readPCX(rIStream, rGraphic);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_EPS))
+ {
+ nStatus = readEPS(rIStream, rGraphic);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_PSD))
+ {
+ nStatus = readPSD(rIStream, rGraphic);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_PCD))
+ {
+ nStatus = readPCD(rIStream, rGraphic);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_PBM))
+ {
+ nStatus = readPBM(rIStream, rGraphic);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_DXF))
+ {
+ nStatus = readDXF(rIStream, rGraphic);
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_WEBP))
+ {
+ nStatus = readWEBP(rIStream, rGraphic, eLinkType);
+ }
+ else
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+
+ if( nStatus == ERRCODE_NONE && ( eLinkType != GfxLinkType::NONE ) && !rGraphic.GetReaderContext() && !bLinkSet )
+ {
+ if (!pGraphicContent)
+ {
+ const sal_uInt64 nStreamEnd = rIStream.Tell();
+ nGraphicContentSize = nStreamEnd - nStreamBegin;
+
+ if (nGraphicContentSize > 0)
+ {
+ try
+ {
+ pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]);
+ }
+ catch (const std::bad_alloc&)
+ {
+ nStatus = ERRCODE_GRFILTER_TOOBIG;
+ }
+
+ if( nStatus == ERRCODE_NONE )
+ {
+ rIStream.Seek(nStreamBegin);
+ rIStream.ReadBytes(pGraphicContent.get(), nGraphicContentSize);
+ }
+ }
+ }
+ if( nStatus == ERRCODE_NONE )
+ {
+ rGraphic.SetGfxLink(std::make_shared<GfxLink>(std::move(pGraphicContent), nGraphicContentSize, eLinkType));
+ }
+ }
+
+ // Set error code or try to set native buffer
+ if( nStatus != ERRCODE_NONE )
+ {
+ ImplSetError( nStatus, &rIStream );
+ rIStream.Seek( nStreamBegin );
+ rGraphic.Clear();
+ }
+
+ return nStatus;
+}
+
+ErrCode GraphicFilter::ExportGraphic( const Graphic& rGraphic, const INetURLObject& rPath,
+ sal_uInt16 nFormat, const css::uno::Sequence< css::beans::PropertyValue >* pFilterData )
+{
+ SAL_INFO( "vcl.filter", "GraphicFilter::ExportGraphic() (thb)" );
+ ErrCode nRetValue = ERRCODE_GRFILTER_FORMATERROR;
+ SAL_WARN_IF( rPath.GetProtocol() == INetProtocol::NotValid, "vcl.filter", "GraphicFilter::ExportGraphic() : ProtType == INetProtocol::NotValid" );
+
+ OUString aMainUrl(rPath.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ bool bAlreadyExists = utl::UCBContentHelper::IsDocument(aMainUrl);
+
+ std::unique_ptr<SvStream> xStream(::utl::UcbStreamHelper::CreateStream( aMainUrl, StreamMode::WRITE | StreamMode::TRUNC ));
+ if (xStream)
+ {
+ nRetValue = ExportGraphic( rGraphic, aMainUrl, *xStream, nFormat, pFilterData );
+ xStream.reset();
+
+ if( ( ERRCODE_NONE != nRetValue ) && !bAlreadyExists )
+ utl::UCBContentHelper::Kill(aMainUrl);
+ }
+ return nRetValue;
+}
+
+ErrCode GraphicFilter::ExportGraphic( const Graphic& rGraphic, std::u16string_view rPath,
+ SvStream& rOStm, sal_uInt16 nFormat, const css::uno::Sequence< css::beans::PropertyValue >* pFilterData )
+{
+ SAL_INFO( "vcl.filter", "GraphicFilter::ExportGraphic() (thb)" );
+ sal_uInt16 nFormatCount = GetExportFormatCount();
+
+ ResetLastError();
+
+ if( nFormat == GRFILTER_FORMAT_DONTKNOW )
+ {
+ INetURLObject aURL( rPath );
+ OUString aExt( aURL.GetFileExtension().toAsciiUpperCase() );
+
+ for( sal_uInt16 i = 0; i < nFormatCount; i++ )
+ {
+ if ( pConfig->GetExportFormatExtension( i ).equalsIgnoreAsciiCase( aExt ) )
+ {
+ nFormat=i;
+ break;
+ }
+ }
+ }
+ if( nFormat >= nFormatCount )
+ return ImplSetError( ERRCODE_GRFILTER_FORMATERROR );
+
+ FilterConfigItem aConfigItem( pFilterData );
+ OUString aFilterName( pConfig->GetExportFilterName( nFormat ) );
+ ErrCode nStatus = ERRCODE_NONE;
+ GraphicType eType;
+ Graphic aGraphic = ImpGetScaledGraphic( rGraphic, aConfigItem );
+ eType = aGraphic.GetType();
+
+ if( pConfig->IsExportPixelFormat( nFormat ) )
+ {
+ if( eType != GraphicType::Bitmap )
+ {
+ Size aSizePixel;
+ sal_uLong nBitsPerPixel,nNeededMem,nMaxMem;
+ ScopedVclPtrInstance< VirtualDevice > aVirDev;
+
+ nMaxMem = 1024;
+ nMaxMem *= 1024; // In Bytes
+
+ // Calculate how big the image would normally be:
+ aSizePixel=aVirDev->LogicToPixel(aGraphic.GetPrefSize(),aGraphic.GetPrefMapMode());
+
+ // Calculate how much memory the image will take up
+ nBitsPerPixel=aVirDev->GetBitCount();
+ nNeededMem=(static_cast<sal_uLong>(aSizePixel.Width())*static_cast<sal_uLong>(aSizePixel.Height())*nBitsPerPixel+7)/8;
+
+ // is the image larger than available memory?
+ if (nMaxMem<nNeededMem)
+ {
+ double fFak=sqrt(static_cast<double>(nMaxMem)/static_cast<double>(nNeededMem));
+ aSizePixel.setWidth(static_cast<sal_uLong>(static_cast<double>(aSizePixel.Width())*fFak) );
+ aSizePixel.setHeight(static_cast<sal_uLong>(static_cast<double>(aSizePixel.Height())*fFak) );
+ }
+
+ aVirDev->SetMapMode(MapMode(MapUnit::MapPixel));
+ aVirDev->SetOutputSizePixel(aSizePixel);
+ Graphic aGraphic2=aGraphic;
+ aGraphic2.Draw(*aVirDev, Point(0, 0), aSizePixel); // this changes the MapMode
+ aVirDev->SetMapMode(MapMode(MapUnit::MapPixel));
+ aGraphic=Graphic(aVirDev->GetBitmapEx(Point(0,0),aSizePixel));
+ }
+ }
+ if( rOStm.GetError() )
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ if( ERRCODE_NONE == nStatus )
+ {
+ if( aFilterName.equalsIgnoreAsciiCase( EXP_BMP ) )
+ {
+ if (!BmpWriter(rOStm, aGraphic, &aConfigItem))
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+ if (rOStm.GetError())
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(EXP_TIFF))
+ {
+ if (!ExportTiffGraphicImport(rOStm, aGraphic, &aConfigItem))
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+
+ if( rOStm.GetError() )
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(EXP_GIF))
+ {
+ if (!ExportGifGraphic(rOStm, aGraphic, &aConfigItem))
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+
+ if( rOStm.GetError() )
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( EXP_SVMETAFILE ) )
+ {
+ sal_Int32 nVersion = aConfigItem.ReadInt32( "Version", 0 ) ;
+ if ( nVersion )
+ rOStm.SetVersion( nVersion );
+
+ // #i119735# just use GetGDIMetaFile, it will create a buffered version of contained bitmap now automatically
+ GDIMetaFile aMTF(aGraphic.GetGDIMetaFile());
+
+ SvmWriter aWriter( rOStm );
+ aWriter.Write( aMTF );
+
+ if( rOStm.GetError() )
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else if ( aFilterName.equalsIgnoreAsciiCase( EXP_WMF ) )
+ {
+ bool bDone(false);
+
+ // do we have a native Vector Graphic Data RenderGraphic, whose data can be written directly?
+ auto const & rVectorGraphicDataPtr(rGraphic.getVectorGraphicData());
+
+ bool bIsEMF = rGraphic.GetGfxLink().IsEMF();
+
+ // VectorGraphicDataType::Wmf means WMF or EMF, allow direct write in the WMF case
+ // only.
+ if (rVectorGraphicDataPtr
+ && rVectorGraphicDataPtr->getType() == VectorGraphicDataType::Wmf
+ && !rVectorGraphicDataPtr->getBinaryDataContainer().isEmpty()
+ && !bIsEMF)
+ {
+ auto & aDataContainer = rVectorGraphicDataPtr->getBinaryDataContainer();
+ rOStm.WriteBytes(aDataContainer.getData(), aDataContainer.getSize());
+
+ if (rOStm.GetError())
+ {
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else
+ {
+ bDone = true;
+ }
+ }
+
+ if (!bDone)
+ {
+ // #i119735# just use GetGDIMetaFile, it will create a buffered version of contained bitmap now automatically
+ if (!ConvertGraphicToWMF(aGraphic, rOStm, &aConfigItem))
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+
+ if (rOStm.GetError())
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ }
+ else if ( aFilterName.equalsIgnoreAsciiCase( EXP_EMF ) )
+ {
+ bool bDone(false);
+
+ // do we have a native Vector Graphic Data RenderGraphic, whose data can be written directly?
+ auto const & rVectorGraphicDataPtr(rGraphic.getVectorGraphicData());
+
+ if (rVectorGraphicDataPtr
+ && rVectorGraphicDataPtr->getType() == VectorGraphicDataType::Emf
+ && !rVectorGraphicDataPtr->getBinaryDataContainer().isEmpty())
+ {
+ auto & aDataContainer = rVectorGraphicDataPtr->getBinaryDataContainer();
+ rOStm.WriteBytes(aDataContainer.getData(), aDataContainer.getSize());
+
+ if (rOStm.GetError())
+ {
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else
+ {
+ bDone = true;
+ }
+ }
+
+ if (!bDone)
+ {
+ // #i119735# just use GetGDIMetaFile, it will create a buffered version of contained bitmap now automatically
+ if (!ConvertGDIMetaFileToEMF(aGraphic.GetGDIMetaFile(), rOStm))
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+
+ if (rOStm.GetError())
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( EXP_JPEG ) )
+ {
+ bool bExportedGrayJPEG = false;
+ if( !ExportJPEG( rOStm, aGraphic, pFilterData, &bExportedGrayJPEG ) )
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+
+ if( rOStm.GetError() )
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(EXP_EPS))
+ {
+ if (!ExportEpsGraphic(rOStm, aGraphic, &aConfigItem))
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+
+ if (rOStm.GetError())
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else if ( aFilterName.equalsIgnoreAsciiCase( EXP_PNG ) )
+ {
+ vcl::PNGWriter aPNGWriter( aGraphic.GetBitmapEx(), pFilterData );
+ if ( pFilterData )
+ {
+ for ( const auto& rPropVal : *pFilterData )
+ {
+ if ( rPropVal.Name == "AdditionalChunks" )
+ {
+ css::uno::Sequence< css::beans::PropertyValue > aAdditionalChunkSequence;
+ if ( rPropVal.Value >>= aAdditionalChunkSequence )
+ {
+ for ( const auto& rAdditionalChunk : std::as_const(aAdditionalChunkSequence) )
+ {
+ if ( rAdditionalChunk.Name.getLength() == 4 )
+ {
+ sal_uInt32 nChunkType = 0;
+ for ( sal_Int32 k = 0; k < 4; k++ )
+ {
+ nChunkType <<= 8;
+ nChunkType |= static_cast<sal_uInt8>(rAdditionalChunk.Name[ k ]);
+ }
+ css::uno::Sequence< sal_Int8 > aByteSeq;
+ if ( rAdditionalChunk.Value >>= aByteSeq )
+ {
+ std::vector< vcl::PNGWriter::ChunkData >& rChunkData = aPNGWriter.GetChunks();
+ if ( !rChunkData.empty() )
+ {
+ sal_uInt32 nChunkLen = aByteSeq.getLength();
+
+ vcl::PNGWriter::ChunkData aChunkData;
+ aChunkData.nType = nChunkType;
+ if ( nChunkLen )
+ {
+ aChunkData.aData.resize( nChunkLen );
+ memcpy( aChunkData.aData.data(), aByteSeq.getConstArray(), nChunkLen );
+ }
+ std::vector< vcl::PNGWriter::ChunkData >::iterator aIter = rChunkData.end() - 1;
+ rChunkData.insert( aIter, aChunkData );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ aPNGWriter.Write( rOStm );
+
+ if( rOStm.GetError() )
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else if( aFilterName.equalsIgnoreAsciiCase( EXP_SVG ) )
+ {
+ bool bDone(false);
+
+ // do we have a native Vector Graphic Data RenderGraphic, whose data can be written directly?
+ auto const & rVectorGraphicDataPtr(rGraphic.getVectorGraphicData());
+
+ if (rVectorGraphicDataPtr
+ && rVectorGraphicDataPtr->getType() == VectorGraphicDataType::Svg
+ && !rVectorGraphicDataPtr->getBinaryDataContainer().isEmpty())
+ {
+ auto & aDataContainer = rVectorGraphicDataPtr->getBinaryDataContainer();
+ rOStm.WriteBytes(aDataContainer.getData(), aDataContainer.getSize());
+
+ if( rOStm.GetError() )
+ {
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else
+ {
+ bDone = true;
+ }
+ }
+
+ if( !bDone )
+ {
+ // do the normal GDIMetaFile export instead
+ try
+ {
+ css::uno::Reference< css::uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
+
+ css::uno::Reference< css::xml::sax::XDocumentHandler > xSaxWriter(
+ css::xml::sax::Writer::create( xContext ), css::uno::UNO_QUERY_THROW);
+ css::uno::Sequence< css::uno::Any > aArguments{ css::uno::Any(
+ aConfigItem.GetFilterData()) };
+ css::uno::Reference< css::svg::XSVGWriter > xSVGWriter(
+ xContext->getServiceManager()->createInstanceWithArgumentsAndContext( "com.sun.star.svg.SVGWriter", aArguments, xContext),
+ css::uno::UNO_QUERY );
+ if( xSaxWriter.is() && xSVGWriter.is() )
+ {
+ css::uno::Reference< css::io::XActiveDataSource > xActiveDataSource(
+ xSaxWriter, css::uno::UNO_QUERY );
+
+ if( xActiveDataSource.is() )
+ {
+ const css::uno::Reference< css::uno::XInterface > xStmIf(
+ static_cast< ::cppu::OWeakObject* >( new ImpFilterOutputStream( rOStm ) ) );
+
+ SvMemoryStream aMemStm( 65535, 65535 );
+
+ // #i119735# just use GetGDIMetaFile, it will create a buffered version of contained bitmap now automatically
+ SvmWriter aWriter( aMemStm );
+ aWriter.Write( aGraphic.GetGDIMetaFile() );
+
+ xActiveDataSource->setOutputStream( css::uno::Reference< css::io::XOutputStream >(
+ xStmIf, css::uno::UNO_QUERY ) );
+ css::uno::Sequence< sal_Int8 > aMtfSeq( static_cast<sal_Int8 const *>(aMemStm.GetData()), aMemStm.Tell() );
+ xSVGWriter->write( xSaxWriter, aMtfSeq );
+ }
+ }
+ }
+ catch(const css::uno::Exception&)
+ {
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ }
+ }
+ else if (aFilterName.equalsIgnoreAsciiCase(EXP_WEBP))
+ {
+ if (!ExportWebpGraphic(rOStm, aGraphic, &aConfigItem))
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+
+ if( rOStm.GetError() )
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
+ else
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
+ if( nStatus != ERRCODE_NONE )
+ {
+ ImplSetError( nStatus, &rOStm );
+ }
+ return nStatus;
+}
+
+
+void GraphicFilter::ResetLastError()
+{
+ mxErrorEx = ERRCODE_NONE;
+}
+
+Link<ConvertData&,bool> GraphicFilter::GetFilterCallback() const
+{
+ Link<ConvertData&,bool> aLink( LINK( const_cast<GraphicFilter*>(this), GraphicFilter, FilterCallback ) );
+ return aLink;
+}
+
+IMPL_LINK( GraphicFilter, FilterCallback, ConvertData&, rData, bool )
+{
+ bool bRet = false;
+
+ sal_uInt16 nFormat = GRFILTER_FORMAT_DONTKNOW;
+ OUString aShortName;
+ css::uno::Sequence< css::beans::PropertyValue > aFilterData;
+ switch( rData.mnFormat )
+ {
+ case ConvertDataFormat::BMP: aShortName = BMP_SHORTNAME; break;
+ case ConvertDataFormat::GIF: aShortName = GIF_SHORTNAME; break;
+ case ConvertDataFormat::JPG: aShortName = JPG_SHORTNAME; break;
+ case ConvertDataFormat::MET: aShortName = MET_SHORTNAME; break;
+ case ConvertDataFormat::PCT: aShortName = PCT_SHORTNAME; break;
+ case ConvertDataFormat::PNG: aShortName = PNG_SHORTNAME; break;
+ case ConvertDataFormat::SVM: aShortName = SVM_SHORTNAME; break;
+ case ConvertDataFormat::TIF: aShortName = TIF_SHORTNAME; break;
+ case ConvertDataFormat::WMF: aShortName = WMF_SHORTNAME; break;
+ case ConvertDataFormat::EMF: aShortName = EMF_SHORTNAME; break;
+ case ConvertDataFormat::SVG: aShortName = SVG_SHORTNAME; break;
+ case ConvertDataFormat::WEBP: aShortName = WEBP_SHORTNAME; break;
+
+ default:
+ break;
+ }
+ if( GraphicType::NONE == rData.maGraphic.GetType() || rData.maGraphic.GetReaderContext() ) // Import
+ {
+ // Import
+ nFormat = GetImportFormatNumberForShortName( aShortName );
+ bRet = ImportGraphic( rData.maGraphic, u"", rData.mrStm, nFormat ) == ERRCODE_NONE;
+ }
+ else if( !aShortName.isEmpty() )
+ {
+ // Export
+#if defined(IOS) || defined(ANDROID)
+ if (aShortName == PNG_SHORTNAME)
+ {
+ aFilterData.realloc(aFilterData.getLength() + 1);
+ auto pFilterData = aFilterData.getArray();
+ pFilterData[aFilterData.getLength() - 1].Name = "Compression";
+ // We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed.
+ pFilterData[aFilterData.getLength() - 1].Value <<= static_cast<sal_Int32>(1);
+ }
+#endif
+ nFormat = GetExportFormatNumberForShortName( aShortName );
+ bRet = ExportGraphic( rData.maGraphic, u"", rData.mrStm, nFormat, &aFilterData ) == ERRCODE_NONE;
+ }
+
+ return bRet;
+}
+
+namespace
+{
+ class StandardGraphicFilter
+ {
+ public:
+ StandardGraphicFilter()
+ {
+ m_aFilter.GetImportFormatCount();
+ }
+ GraphicFilter m_aFilter;
+ };
+}
+
+GraphicFilter& GraphicFilter::GetGraphicFilter()
+{
+ static StandardGraphicFilter gStandardFilter;
+ return gStandardFilter.m_aFilter;
+}
+
+ErrCode GraphicFilter::LoadGraphic( const OUString &rPath, const OUString &rFilterName,
+ Graphic& rGraphic, GraphicFilter* pFilter,
+ sal_uInt16* pDeterminedFormat )
+{
+ if ( !pFilter )
+ pFilter = &GetGraphicFilter();
+
+ const sal_uInt16 nFilter = !rFilterName.isEmpty() && pFilter->GetImportFormatCount()
+ ? pFilter->GetImportFormatNumber( rFilterName )
+ : GRFILTER_FORMAT_DONTKNOW;
+
+ INetURLObject aURL( rPath );
+ if ( aURL.HasError() )
+ {
+ aURL.SetSmartProtocol( INetProtocol::File );
+ aURL.SetSmartURL( rPath );
+ }
+
+ std::unique_ptr<SvStream> pStream;
+ if ( INetProtocol::File != aURL.GetProtocol() )
+ pStream = ::utl::UcbStreamHelper::CreateStream( rPath, StreamMode::READ );
+
+ ErrCode nRes = ERRCODE_NONE;
+ if ( !pStream )
+ nRes = pFilter->ImportGraphic( rGraphic, aURL, nFilter, pDeterminedFormat );
+ else
+ nRes = pFilter->ImportGraphic( rGraphic, rPath, *pStream, nFilter, pDeterminedFormat );
+
+#ifdef DBG_UTIL
+ OUString aReturnString;
+
+ if (nRes == ERRCODE_GRFILTER_OPENERROR)
+ aReturnString="open error";
+ else if (nRes == ERRCODE_GRFILTER_IOERROR)
+ aReturnString="IO error";
+ else if (nRes == ERRCODE_GRFILTER_FORMATERROR)
+ aReturnString="format error";
+ else if (nRes == ERRCODE_GRFILTER_VERSIONERROR)
+ aReturnString="version error";
+ else if (nRes == ERRCODE_GRFILTER_FILTERERROR)
+ aReturnString="filter error";
+ else if (nRes == ERRCODE_GRFILTER_TOOBIG)
+ aReturnString="graphic is too big";
+
+ SAL_INFO_IF( nRes, "vcl.filter", "Problem importing graphic " << rPath << ". Reason: " << aReturnString );
+#endif
+
+ return nRes;
+}
+
+ErrCode GraphicFilter::compressAsPNG(const Graphic& rGraphic, SvStream& rOutputStream)
+{
+ css::uno::Sequence< css::beans::PropertyValue > aFilterData{ comphelper::makePropertyValue(
+ "Compression", sal_uInt32(9)) };
+
+ sal_uInt16 nFilterFormat = GetExportFormatNumberForShortName(u"PNG");
+ return ExportGraphic(rGraphic, u"", rOutputStream, nFilterFormat, &aFilterData);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/graphicfilter2.cxx b/vcl/source/filter/graphicfilter2.cxx
new file mode 100644
index 000000000..c02e9d557
--- /dev/null
+++ b/vcl/source/filter/graphicfilter2.cxx
@@ -0,0 +1,1229 @@
+/* -*- 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 <string.h>
+#include <tools/stream.hxx>
+#include <tools/fract.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/zcodec.hxx>
+#include <vcl/TypeSerializer.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <filter/WebpReader.hxx>
+#include "graphicfilter_internal.hxx"
+
+#define DATA_SIZE 640
+constexpr sal_uInt32 EMF_CHECK_SIZE = 44;
+constexpr sal_uInt32 EMR_HEADER = 0x00000001;
+constexpr sal_uInt32 ENHMETA_SIGNATURE = 0x464d4520;
+
+GraphicDescriptor::GraphicDescriptor( const INetURLObject& rPath ) :
+ pFileStm( ::utl::UcbStreamHelper::CreateStream( rPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ), StreamMode::READ ).release() ),
+ aPathExt( rPath.GetFileExtension().toAsciiLowerCase() ),
+ bOwnStream( true )
+{
+ ImpConstruct();
+}
+
+GraphicDescriptor::GraphicDescriptor( SvStream& rInStream, const OUString* pPath) :
+ pFileStm ( &rInStream ),
+ bOwnStream ( false )
+{
+ ImpConstruct();
+
+ if ( pPath )
+ {
+ INetURLObject aURL( *pPath );
+ aPathExt = aURL.GetFileExtension().toAsciiLowerCase();
+ }
+}
+
+GraphicDescriptor::~GraphicDescriptor()
+{
+ if ( bOwnStream )
+ delete pFileStm;
+}
+
+bool GraphicDescriptor::Detect( bool bExtendedInfo )
+{
+ bool bRet = false;
+ if ( pFileStm && !pFileStm->GetError() )
+ {
+ SvStream& rStm = *pFileStm;
+ SvStreamEndian nOldFormat = rStm.GetEndian();
+
+ if ( ImpDetectGIF( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectJPG( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectBMP( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectPNG( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectTIF( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectPCX( rStm ) ) bRet = true;
+ else if ( ImpDetectDXF( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectMET( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectSVM( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectWMF( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectEMF( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectSVG( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectPCT( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectXBM( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectXPM( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectPBM( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectPGM( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectPPM( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectRAS( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectTGA( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectPSD( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectEPS( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectPCD( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectWEBP( rStm, bExtendedInfo ) ) bRet = true;
+
+ rStm.SetEndian( nOldFormat );
+ }
+ return bRet;
+}
+
+void GraphicDescriptor::ImpConstruct()
+{
+ nFormat = GraphicFileFormat::NOT;
+ nBitsPerPixel = 0;
+ nPlanes = 0;
+ mnNumberOfImageComponents = 0;
+ bIsTransparent = false;
+ bIsAlpha = false;
+}
+
+bool GraphicDescriptor::ImpDetectBMP( SvStream& rStm, bool bExtendedInfo )
+{
+ sal_uInt16 nTemp16 = 0;
+ bool bRet = false;
+ sal_Int32 nStmPos = rStm.Tell();
+
+ rStm.SetEndian( SvStreamEndian::LITTLE );
+ rStm.ReadUInt16( nTemp16 );
+
+ // OS/2-BitmapArray
+ if ( nTemp16 == 0x4142 )
+ {
+ rStm.SeekRel( 0x0c );
+ rStm.ReadUInt16( nTemp16 );
+ }
+
+ // Bitmap
+ if ( nTemp16 == 0x4d42 )
+ {
+ nFormat = GraphicFileFormat::BMP;
+ bRet = true;
+
+ if ( bExtendedInfo )
+ {
+ sal_uInt32 nTemp32;
+ sal_uInt32 nCompression;
+
+ // up to first info
+ rStm.SeekRel( 0x10 );
+
+ // Pixel width
+ rStm.ReadUInt32( nTemp32 );
+ aPixSize.setWidth( nTemp32 );
+
+ // Pixel height
+ rStm.ReadUInt32( nTemp32 );
+ aPixSize.setHeight( nTemp32 );
+
+ // Planes
+ rStm.ReadUInt16( nTemp16 );
+ nPlanes = nTemp16;
+
+ // BitCount
+ rStm.ReadUInt16( nTemp16 );
+ nBitsPerPixel = nTemp16;
+
+ // Compression
+ rStm.ReadUInt32( nTemp32 );
+ nCompression = nTemp32;
+
+ // logical width
+ rStm.SeekRel( 4 );
+ rStm.ReadUInt32( nTemp32 );
+ sal_uInt32 nXPelsPerMeter = 0;
+ if ( nTemp32 )
+ {
+ aLogSize.setWidth( ( aPixSize.Width() * 100000 ) / nTemp32 );
+ nXPelsPerMeter = nTemp32;
+ }
+
+ // logical height
+ rStm.ReadUInt32( nTemp32 );
+ sal_uInt32 nYPelsPerMeter = 0;
+ if ( nTemp32 )
+ {
+ aLogSize.setHeight( ( aPixSize.Height() * 100000 ) / nTemp32 );
+ nYPelsPerMeter = nTemp32;
+ }
+
+ // further validation, check for rational values
+ if ( ( nBitsPerPixel > 24 ) || ( nCompression > 3 ) )
+ {
+ nFormat = GraphicFileFormat::NOT;
+ bRet = false;
+ }
+
+ if (bRet && nXPelsPerMeter && nYPelsPerMeter)
+ {
+ maPreferredMapMode
+ = MapMode(MapUnit::MapMM, Point(), Fraction(1000, nXPelsPerMeter),
+ Fraction(1000, nYPelsPerMeter));
+
+ maPreferredLogSize = Size(aPixSize.getWidth(), aPixSize.getHeight());
+ }
+ }
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectGIF( SvStream& rStm, bool bExtendedInfo )
+{
+ sal_uInt32 n32 = 0;
+ bool bRet = false;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::LITTLE );
+ rStm.ReadUInt32( n32 );
+
+ if ( n32 == 0x38464947 )
+ {
+ sal_uInt16 n16 = 0;
+ rStm.ReadUInt16( n16 );
+ if ( ( n16 == 0x6137 ) || ( n16 == 0x6139 ) )
+ {
+ nFormat = GraphicFileFormat::GIF;
+ bRet = true;
+
+ if ( bExtendedInfo )
+ {
+ sal_uInt16 nTemp16 = 0;
+ sal_uInt8 cByte = 0;
+
+ // Pixel width
+ rStm.ReadUInt16( nTemp16 );
+ aPixSize.setWidth( nTemp16 );
+
+ // Pixel height
+ rStm.ReadUInt16( nTemp16 );
+ aPixSize.setHeight( nTemp16 );
+
+ // Bits/Pixel
+ rStm.ReadUChar( cByte );
+ nBitsPerPixel = ( ( cByte & 112 ) >> 4 ) + 1;
+ }
+ }
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+// returns the next jpeg marker, a return value of 0 represents an error
+static sal_uInt8 ImpDetectJPG_GetNextMarker( SvStream& rStm )
+{
+ sal_uInt8 nByte;
+ do
+ {
+ do
+ {
+ rStm.ReadUChar( nByte );
+ if (!rStm.good()) // as 0 is not allowed as marker,
+ return 0; // we can use it as errorcode
+ }
+ while ( nByte != 0xff );
+ do
+ {
+ rStm.ReadUChar( nByte );
+ if (!rStm.good())
+ return 0;
+ }
+ while( nByte == 0xff );
+ }
+ while( nByte == 0 ); // 0xff00 represents 0xff and not a marker,
+ // the marker detection has to be restarted.
+ return nByte;
+}
+
+bool GraphicDescriptor::ImpDetectJPG( SvStream& rStm, bool bExtendedInfo )
+{
+ sal_uInt32 nTemp32 = 0;
+ bool bRet = false;
+
+ sal_Int32 nStmPos = rStm.Tell();
+
+ rStm.SetEndian( SvStreamEndian::BIG );
+ rStm.ReadUInt32( nTemp32 );
+
+ // compare upper 24 bits
+ if( 0xffd8ff00 == ( nTemp32 & 0xffffff00 ) )
+ {
+ nFormat = GraphicFileFormat::JPG;
+ bRet = true;
+
+ if ( bExtendedInfo )
+ {
+ rStm.SeekRel( -2 );
+
+ ErrCode nError( rStm.GetError() );
+
+ bool bScanFailure = false;
+ bool bScanFinished = false;
+ MapMode aMap;
+
+ while (!bScanFailure && !bScanFinished && rStm.good())
+ {
+ sal_uInt8 nMarker = ImpDetectJPG_GetNextMarker( rStm );
+ switch( nMarker )
+ {
+ // fixed size marker, not having a two byte length parameter
+ case 0xd0 : // RST0
+ case 0xd1 :
+ case 0xd2 :
+ case 0xd3 :
+ case 0xd4 :
+ case 0xd5 :
+ case 0xd6 :
+ case 0xd7 : // RST7
+ case 0x01 : // TEM
+ break;
+
+ case 0xd8 : // SOI (has already been checked, there should not be a second one)
+ case 0x00 : // marker is invalid, we should stop now
+ bScanFailure = true;
+ break;
+
+ case 0xd9 : // EOI
+ bScanFinished = true;
+ break;
+
+ // per default we assume marker segments containing a length parameter
+ default :
+ {
+ sal_uInt16 nLength = 0;
+ rStm.ReadUInt16( nLength );
+
+ if ( nLength < 2 )
+ bScanFailure = true;
+ else
+ {
+ sal_uInt32 nNextMarkerPos = rStm.Tell() + nLength - 2;
+ switch( nMarker )
+ {
+ case 0xe0 : // APP0 Marker
+ {
+ if ( nLength == 16 )
+ {
+ sal_Int32 nIdentifier = 0;
+ rStm.ReadInt32( nIdentifier );
+ if ( nIdentifier == 0x4a464946 ) // JFIF Identifier
+ {
+ sal_uInt8 nStringTerminator = 0;
+ sal_uInt8 nMajorRevision = 0;
+ sal_uInt8 nMinorRevision = 0;
+ sal_uInt8 nUnits = 0;
+ sal_uInt16 nHorizontalResolution = 0;
+ sal_uInt16 nVerticalResolution = 0;
+ sal_uInt8 nHorzThumbnailPixelCount = 0;
+ sal_uInt8 nVertThumbnailPixelCount = 0;
+
+ rStm.ReadUChar( nStringTerminator )
+ .ReadUChar( nMajorRevision )
+ .ReadUChar( nMinorRevision )
+ .ReadUChar( nUnits )
+ .ReadUInt16( nHorizontalResolution )
+ .ReadUInt16( nVerticalResolution )
+ .ReadUChar( nHorzThumbnailPixelCount )
+ .ReadUChar( nVertThumbnailPixelCount );
+
+ // setting the logical size
+ if ( nUnits && nHorizontalResolution && nVerticalResolution )
+ {
+ aMap.SetMapUnit( nUnits == 1 ? MapUnit::MapInch : MapUnit::MapCM );
+ aMap.SetScaleX( Fraction( 1, nHorizontalResolution ) );
+ aMap.SetScaleY( Fraction( 1, nVerticalResolution ) );
+ aLogSize = OutputDevice::LogicToLogic( aPixSize, aMap, MapMode( MapUnit::Map100thMM ) );
+ }
+ }
+ }
+ }
+ break;
+
+ // Start of Frame Markers
+ case 0xc0 : // SOF0
+ case 0xc1 : // SOF1
+ case 0xc2 : // SOF2
+ case 0xc3 : // SOF3
+ case 0xc5 : // SOF5
+ case 0xc6 : // SOF6
+ case 0xc7 : // SOF7
+ case 0xc9 : // SOF9
+ case 0xca : // SOF10
+ case 0xcb : // SOF11
+ case 0xcd : // SOF13
+ case 0xce : // SOF14
+ case 0xcf : // SOF15
+ {
+ sal_uInt8 nSamplePrecision = 0;
+ sal_uInt16 nNumberOfLines = 0;
+ sal_uInt16 nSamplesPerLine = 0;
+ sal_uInt8 nNumberOfImageComponents = 0;
+ sal_uInt8 nComponentsIdentifier = 0;
+ sal_uInt8 nSamplingFactor = 0;
+ sal_uInt8 nQuantizationTableDestinationSelector = 0;
+ rStm.ReadUChar( nSamplePrecision )
+ .ReadUInt16( nNumberOfLines )
+ .ReadUInt16( nSamplesPerLine )
+ .ReadUChar( nNumberOfImageComponents )
+ .ReadUChar( nComponentsIdentifier )
+ .ReadUChar( nSamplingFactor )
+ .ReadUChar( nQuantizationTableDestinationSelector );
+ mnNumberOfImageComponents = nNumberOfImageComponents;
+
+ // nSamplingFactor (lower nibble: vertical,
+ // upper nibble: horizontal) is unused
+
+ aPixSize.setHeight( nNumberOfLines );
+ aPixSize.setWidth( nSamplesPerLine );
+ nBitsPerPixel = ( nNumberOfImageComponents == 3 ? 24 : nNumberOfImageComponents == 1 ? 8 : 0 );
+ nPlanes = 1;
+
+ if (aMap.GetMapUnit() != MapUnit::MapPixel)
+ // We already know the DPI, but the
+ // pixel size arrived later, so do the
+ // conversion again.
+ aLogSize = OutputDevice::LogicToLogic(
+ aPixSize, aMap, MapMode(MapUnit::Map100thMM));
+
+ bScanFinished = true;
+ }
+ break;
+ }
+ rStm.Seek( nNextMarkerPos );
+ }
+ }
+ break;
+ }
+ }
+ rStm.SetError( nError );
+ }
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPCD( SvStream& rStm, bool )
+{
+ bool bRet = false;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::LITTLE );
+
+ sal_uInt32 nTemp32 = 0;
+ sal_uInt16 nTemp16 = 0;
+ sal_uInt8 cByte = 0;
+
+ rStm.SeekRel( 2048 );
+ rStm.ReadUInt32( nTemp32 );
+ rStm.ReadUInt16( nTemp16 );
+ rStm.ReadUChar( cByte );
+
+ if ( ( nTemp32 == 0x5f444350 ) &&
+ ( nTemp16 == 0x5049 ) &&
+ ( cByte == 0x49 ) )
+ {
+ nFormat = GraphicFileFormat::PCD;
+ bRet = true;
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPCX( SvStream& rStm )
+{
+ // ! Because 0x0a can be interpreted as LF too ...
+ // we can't be sure that this special sign represent a PCX file only.
+ // Every Ascii file is possible here :-(
+ // We must detect the whole header.
+
+ bool bRet = false;
+ sal_uInt8 cByte = 0;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::LITTLE );
+ rStm.ReadUChar( cByte );
+
+ if ( cByte == 0x0a )
+ {
+ nFormat = GraphicFileFormat::PCX;
+
+ rStm.SeekRel( 1 );
+
+ // compression
+ rStm.ReadUChar( cByte );
+
+ bRet = (cByte==0 || cByte ==1);
+ if (bRet)
+ {
+ sal_uInt16 nTemp16;
+ sal_uInt16 nXmin;
+ sal_uInt16 nXmax;
+ sal_uInt16 nYmin;
+ sal_uInt16 nYmax;
+ sal_uInt16 nDPIx;
+ sal_uInt16 nDPIy;
+
+ // Bits/Pixel
+ rStm.ReadUChar( cByte );
+ nBitsPerPixel = cByte;
+
+ // image dimensions
+ rStm.ReadUInt16( nTemp16 );
+ nXmin = nTemp16;
+ rStm.ReadUInt16( nTemp16 );
+ nYmin = nTemp16;
+ rStm.ReadUInt16( nTemp16 );
+ nXmax = nTemp16;
+ rStm.ReadUInt16( nTemp16 );
+ nYmax = nTemp16;
+
+ aPixSize.setWidth( nXmax - nXmin + 1 );
+ aPixSize.setHeight( nYmax - nYmin + 1 );
+
+ // resolution
+ rStm.ReadUInt16( nTemp16 );
+ nDPIx = nTemp16;
+ rStm.ReadUInt16( nTemp16 );
+ nDPIy = nTemp16;
+
+ // set logical size
+ MapMode aMap( MapUnit::MapInch, Point(),
+ Fraction( 1, nDPIx ), Fraction( 1, nDPIy ) );
+ aLogSize = OutputDevice::LogicToLogic( aPixSize, aMap,
+ MapMode( MapUnit::Map100thMM ) );
+
+ // number of color planes
+ cByte = 5; // Illegal value in case of EOF.
+ rStm.SeekRel( 49 );
+ rStm.ReadUChar( cByte );
+ nPlanes = cByte;
+
+ bRet = (nPlanes<=4);
+ }
+ }
+
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPNG( SvStream& rStm, bool bExtendedInfo )
+{
+ sal_uInt32 nTemp32 = 0;
+ bool bRet = false;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::BIG );
+ rStm.ReadUInt32( nTemp32 );
+
+ if ( nTemp32 == 0x89504e47 )
+ {
+ rStm.ReadUInt32( nTemp32 );
+ if ( nTemp32 == 0x0d0a1a0a )
+ {
+ nFormat = GraphicFileFormat::PNG;
+ bRet = true;
+
+ if ( bExtendedInfo )
+ {
+ do {
+ sal_uInt8 cByte = 0;
+
+ // IHDR-Chunk
+ rStm.SeekRel( 8 );
+
+ // width
+ rStm.ReadUInt32( nTemp32 );
+ if (!rStm.good())
+ break;
+ aPixSize.setWidth( nTemp32 );
+
+ // height
+ rStm.ReadUInt32( nTemp32 );
+ if (!rStm.good())
+ break;
+ aPixSize.setHeight( nTemp32 );
+
+ // Bits/Pixel
+ rStm.ReadUChar( cByte );
+ if (!rStm.good())
+ break;
+ nBitsPerPixel = cByte;
+
+ // Colour type - check whether it supports alpha values
+ sal_uInt8 cColType = 0;
+ rStm.ReadUChar( cColType );
+ if (!rStm.good())
+ break;
+ bIsAlpha = bIsTransparent = ( cColType == 4 || cColType == 6 );
+
+ // Planes always 1;
+ // compression always
+ nPlanes = 1;
+
+ sal_uInt32 nLen32 = 0;
+ nTemp32 = 0;
+
+ rStm.SeekRel( 7 );
+
+ // read up to the start of the image
+ rStm.ReadUInt32( nLen32 );
+ rStm.ReadUInt32( nTemp32 );
+ while (rStm.good() && nTemp32 != 0x49444154)
+ {
+ if ( nTemp32 == 0x70485973 ) // physical pixel dimensions
+ {
+ sal_uLong nXRes;
+ sal_uLong nYRes;
+
+ // horizontal resolution
+ nTemp32 = 0;
+ rStm.ReadUInt32( nTemp32 );
+ nXRes = nTemp32;
+
+ // vertical resolution
+ nTemp32 = 0;
+ rStm.ReadUInt32( nTemp32 );
+ nYRes = nTemp32;
+
+ // unit
+ cByte = 0;
+ rStm.ReadUChar( cByte );
+
+ if ( cByte )
+ {
+ if ( nXRes )
+ aLogSize.setWidth( (aPixSize.Width() * 100000) / nXRes );
+
+ if ( nYRes )
+ aLogSize.setHeight( (aPixSize.Height() * 100000) / nYRes );
+ }
+
+ nLen32 -= 9;
+ }
+ else if ( nTemp32 == 0x74524e53 ) // transparency
+ {
+ bIsTransparent = true;
+ bIsAlpha = ( cColType != 0 && cColType != 2 );
+ }
+
+ // skip forward to next chunk
+ rStm.SeekRel( 4 + nLen32 );
+ rStm.ReadUInt32( nLen32 );
+ rStm.ReadUInt32( nTemp32 );
+ }
+ } while (false);
+ }
+ }
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectTIF( SvStream& rStm, bool bExtendedInfo )
+{
+ bool bRet = false;
+ sal_uInt8 cByte1 = 0;
+ sal_uInt8 cByte2 = 1;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.ReadUChar( cByte1 );
+ rStm.ReadUChar( cByte2 );
+ if ( cByte1 == cByte2 )
+ {
+ bool bDetectOk = false;
+
+ if ( cByte1 == 0x49 )
+ {
+ rStm.SetEndian( SvStreamEndian::LITTLE );
+ bDetectOk = true;
+ }
+ else if ( cByte1 == 0x4d )
+ {
+ rStm.SetEndian( SvStreamEndian::BIG );
+ bDetectOk = true;
+ }
+
+ if ( bDetectOk )
+ {
+ sal_uInt16 nTemp16 = 0;
+
+ rStm.ReadUInt16( nTemp16 );
+ if ( nTemp16 == 0x2a )
+ {
+ nFormat = GraphicFileFormat::TIF;
+ bRet = true;
+
+ if ( bExtendedInfo )
+ {
+ sal_uLong nCount;
+ sal_uLong nMax = DATA_SIZE - 48;
+ sal_uInt32 nTemp32 = 0;
+
+ // Offset of the first IFD
+ rStm.ReadUInt32( nTemp32 );
+ nCount = nTemp32 + 2;
+ rStm.SeekRel( nCount - 0x08 );
+
+ if ( nCount < nMax )
+ {
+ bool bOk = false;
+
+ // read tags till we find Tag256 ( Width )
+ // do not read more bytes than DATA_SIZE
+ rStm.ReadUInt16( nTemp16 );
+ while ( nTemp16 != 256 )
+ {
+ bOk = nCount < nMax;
+ if ( !bOk )
+ {
+ break;
+ }
+ rStm.SeekRel( 10 );
+ rStm.ReadUInt16( nTemp16 );
+ nCount += 12;
+ }
+
+ if ( bOk )
+ {
+ // width
+ rStm.ReadUInt16( nTemp16 );
+ rStm.SeekRel( 4 );
+ if ( nTemp16 == 3 )
+ {
+ rStm.ReadUInt16( nTemp16 );
+ aPixSize.setWidth( nTemp16 );
+ rStm.SeekRel( 2 );
+ }
+ else
+ {
+ rStm.ReadUInt32( nTemp32 );
+ aPixSize.setWidth( nTemp32 );
+ }
+
+ // height
+ rStm.SeekRel( 2 );
+ rStm.ReadUInt16( nTemp16 );
+ rStm.SeekRel( 4 );
+ if ( nTemp16 == 3 )
+ {
+ rStm.ReadUInt16( nTemp16 );
+ aPixSize.setHeight( nTemp16 );
+ rStm.SeekRel( 2 );
+ }
+ else
+ {
+ rStm.ReadUInt32( nTemp32 );
+ aPixSize.setHeight( nTemp32 );
+ }
+
+ // Bits/Pixel
+ rStm.ReadUInt16( nTemp16 );
+ if ( nTemp16 == 258 )
+ {
+ rStm.SeekRel( 6 );
+ rStm.ReadUInt16( nTemp16 );
+ nBitsPerPixel = nTemp16;
+ rStm.SeekRel( 2 );
+ }
+ else
+ rStm.SeekRel( -2 );
+
+ // compression
+ rStm.ReadUInt16( nTemp16 );
+ if ( nTemp16 == 259 )
+ {
+ rStm.SeekRel( 6 );
+ rStm.ReadUInt16( nTemp16 ); // compression
+ rStm.SeekRel( 2 );
+ }
+ else
+ rStm.SeekRel( -2 );
+ }
+ }
+ }
+ }
+ }
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectXBM( SvStream&, bool )
+{
+ bool bRet = aPathExt.startsWith( "xbm" );
+ if (bRet)
+ nFormat = GraphicFileFormat::XBM;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectXPM( SvStream&, bool )
+{
+ bool bRet = aPathExt.startsWith( "xpm" );
+ if (bRet)
+ nFormat = GraphicFileFormat::XPM;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPBM( SvStream& rStm, bool )
+{
+ bool bRet = false;
+
+ // check file extension first, as this trumps the 2 ID bytes
+ if ( aPathExt.startsWith( "pbm" ) )
+ bRet = true;
+ else
+ {
+ sal_Int32 nStmPos = rStm.Tell();
+ sal_uInt8 nFirst = 0, nSecond = 0;
+ rStm.ReadUChar( nFirst ).ReadUChar( nSecond );
+ if ( nFirst == 'P' && ( ( nSecond == '1' ) || ( nSecond == '4' ) ) )
+ bRet = true;
+ rStm.Seek( nStmPos );
+ }
+
+ if ( bRet )
+ nFormat = GraphicFileFormat::PBM;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPGM( SvStream& rStm, bool )
+{
+ bool bRet = false;
+
+ if ( aPathExt.startsWith( "pgm" ) )
+ bRet = true;
+ else
+ {
+ sal_uInt8 nFirst = 0, nSecond = 0;
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.ReadUChar( nFirst ).ReadUChar( nSecond );
+ if ( nFirst == 'P' && ( ( nSecond == '2' ) || ( nSecond == '5' ) ) )
+ bRet = true;
+ rStm.Seek( nStmPos );
+ }
+
+ if ( bRet )
+ nFormat = GraphicFileFormat::PGM;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPPM( SvStream& rStm, bool )
+{
+ bool bRet = false;
+
+ if ( aPathExt.startsWith( "ppm" ) )
+ bRet = true;
+ else
+ {
+ sal_uInt8 nFirst = 0, nSecond = 0;
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.ReadUChar( nFirst ).ReadUChar( nSecond );
+ if ( nFirst == 'P' && ( ( nSecond == '3' ) || ( nSecond == '6' ) ) )
+ bRet = true;
+ rStm.Seek( nStmPos );
+ }
+
+ if ( bRet )
+ nFormat = GraphicFileFormat::PPM;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectRAS( SvStream& rStm, bool )
+{
+ sal_uInt32 nMagicNumber = 0;
+ bool bRet = false;
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::BIG );
+ rStm.ReadUInt32( nMagicNumber );
+ if ( nMagicNumber == 0x59a66a95 )
+ {
+ nFormat = GraphicFileFormat::RAS;
+ bRet = true;
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectTGA( SvStream&, bool )
+{
+ bool bRet = aPathExt.startsWith( "tga" );
+ if (bRet)
+ nFormat = GraphicFileFormat::TGA;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPSD( SvStream& rStm, bool bExtendedInfo )
+{
+ bool bRet = false;
+
+ sal_uInt32 nMagicNumber = 0;
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::BIG );
+ rStm.ReadUInt32( nMagicNumber );
+ if ( nMagicNumber == 0x38425053 )
+ {
+ sal_uInt16 nVersion = 0;
+ rStm.ReadUInt16( nVersion );
+ if ( nVersion == 1 )
+ {
+ bRet = true;
+ if ( bExtendedInfo )
+ {
+ sal_uInt16 nChannels = 0;
+ sal_uInt32 nRows = 0;
+ sal_uInt32 nColumns = 0;
+ sal_uInt16 nDepth = 0;
+ sal_uInt16 nMode = 0;
+ rStm.SeekRel( 6 ); // Pad
+ rStm.ReadUInt16( nChannels ).ReadUInt32( nRows ).ReadUInt32( nColumns ).ReadUInt16( nDepth ).ReadUInt16( nMode );
+ if ( ( nDepth == 1 ) || ( nDepth == 8 ) || ( nDepth == 16 ) )
+ {
+ nBitsPerPixel = ( nDepth == 16 ) ? 8 : nDepth;
+ switch ( nChannels )
+ {
+ case 4 :
+ case 3 :
+ nBitsPerPixel = 24;
+ [[fallthrough]];
+ case 2 :
+ case 1 :
+ aPixSize.setWidth( nColumns );
+ aPixSize.setHeight( nRows );
+ break;
+ default:
+ bRet = false;
+ }
+ }
+ else
+ bRet = false;
+ }
+ }
+ }
+
+ if ( bRet )
+ nFormat = GraphicFileFormat::PSD;
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectEPS( SvStream& rStm, bool )
+{
+ // check the EPS preview and the file extension
+ sal_uInt32 nFirstLong = 0;
+ sal_uInt8 nFirstBytes[20] = {};
+ bool bRet = false;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::BIG );
+ rStm.ReadUInt32( nFirstLong );
+ rStm.SeekRel( -4 );
+ rStm.ReadBytes( &nFirstBytes, 20 );
+
+ if ( ( nFirstLong == 0xC5D0D3C6 ) || aPathExt.startsWith( "eps" ) ||
+ ( ImplSearchEntry( nFirstBytes, reinterpret_cast<sal_uInt8 const *>("%!PS-Adobe"), 10, 10 )
+ && ImplSearchEntry( &nFirstBytes[15], reinterpret_cast<sal_uInt8 const *>("EPS"), 3, 3 ) ) )
+ {
+ nFormat = GraphicFileFormat::EPS;
+ bRet = true;
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectDXF( SvStream&, bool )
+{
+ bool bRet = aPathExt.startsWith( "dxf" );
+ if (bRet)
+ nFormat = GraphicFileFormat::DXF;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectMET( SvStream&, bool )
+{
+ bool bRet = aPathExt.startsWith( "met" );
+ if (bRet)
+ nFormat = GraphicFileFormat::MET;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectPCT( SvStream& rStm, bool )
+{
+ bool bRet = aPathExt.startsWith( "pct" );
+ if (bRet)
+ nFormat = GraphicFileFormat::PCT;
+ else
+ {
+ sal_uInt64 const nStreamPos = rStm.Tell();
+ sal_uInt64 const nStreamLen = rStm.remainingSize();
+ if (isPCT(rStm, nStreamPos, nStreamLen))
+ {
+ bRet = true;
+ nFormat = GraphicFileFormat::PCT;
+ }
+ rStm.Seek(nStreamPos);
+ }
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectSVM( SvStream& rStm, bool bExtendedInfo )
+{
+ sal_uInt32 n32 = 0;
+ bool bRet = false;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::LITTLE );
+ rStm.ReadUInt32( n32 );
+ if ( n32 == 0x44475653 )
+ {
+ sal_uInt8 cByte = 0;
+ rStm.ReadUChar( cByte );
+ if ( cByte == 0x49 )
+ {
+ nFormat = GraphicFileFormat::SVM;
+ bRet = true;
+
+ if ( bExtendedInfo )
+ {
+ sal_uInt32 nTemp32;
+ sal_uInt16 nTemp16;
+
+ rStm.SeekRel( 0x04 );
+
+ // width
+ nTemp32 = 0;
+ rStm.ReadUInt32( nTemp32 );
+ aLogSize.setWidth( nTemp32 );
+
+ // height
+ nTemp32 = 0;
+ rStm.ReadUInt32( nTemp32 );
+ aLogSize.setHeight( nTemp32 );
+
+ // read MapUnit and determine PrefSize
+ nTemp16 = 0;
+ rStm.ReadUInt16( nTemp16 );
+ aLogSize = OutputDevice::LogicToLogic( aLogSize,
+ MapMode( static_cast<MapUnit>(nTemp16) ),
+ MapMode( MapUnit::Map100thMM ) );
+ }
+ }
+ }
+ else
+ {
+ rStm.SeekRel( -4 );
+ n32 = 0;
+ rStm.ReadUInt32( n32 );
+
+ if( n32 == 0x4D4C4356 )
+ {
+ sal_uInt16 nTmp16 = 0;
+
+ rStm.ReadUInt16( nTmp16 );
+
+ if( nTmp16 == 0x4654 )
+ {
+ nFormat = GraphicFileFormat::SVM;
+ bRet = true;
+
+ if( bExtendedInfo )
+ {
+ MapMode aMapMode;
+ rStm.SeekRel( 0x06 );
+ TypeSerializer aSerializer(rStm);
+ aSerializer.readMapMode(aMapMode);
+ aSerializer.readSize(aLogSize);
+ aLogSize = OutputDevice::LogicToLogic( aLogSize, aMapMode, MapMode( MapUnit::Map100thMM ) );
+ }
+ }
+ }
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectWMF( SvStream&, bool )
+{
+ bool bRet = aPathExt.startsWith( "wmf" ) || aPathExt.startsWith( "wmz" );
+ if (bRet)
+ nFormat = GraphicFileFormat::WMF;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectEMF(SvStream& rStm, bool bExtendedInfo)
+{
+ SvStream* aNewStream = &rStm;
+ SvMemoryStream aMemStream;
+ sal_uInt8 aUncompressedBuffer[EMF_CHECK_SIZE];
+ if (ZCodec::IsZCompressed(rStm))
+ {
+ ZCodec aCodec;
+ aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/ true);
+ auto nDecompressLength = aCodec.Read(rStm, aUncompressedBuffer, EMF_CHECK_SIZE);
+ aCodec.EndCompression();
+ if (nDecompressLength != EMF_CHECK_SIZE)
+ return false;
+ aMemStream.SetBuffer(aUncompressedBuffer, EMF_CHECK_SIZE, EMF_CHECK_SIZE);
+ aNewStream = &aMemStream;
+ }
+
+ sal_uInt32 nRecordType = 0;
+ bool bRet = false;
+ sal_Int32 nStmPos = aNewStream->Tell();
+ aNewStream->SetEndian(SvStreamEndian::LITTLE);
+ aNewStream->ReadUInt32(nRecordType);
+ if (nRecordType == EMR_HEADER)
+ {
+ sal_Int32 nBoundLeft = 0, nBoundTop = 0, nBoundRight = 0, nBoundBottom = 0;
+ sal_Int32 nFrameLeft = 0, nFrameTop = 0, nFrameRight = 0, nFrameBottom = 0;
+ sal_uInt32 nSignature = 0;
+
+ aNewStream->SeekRel(4);
+ aNewStream->ReadInt32(nBoundLeft);
+ aNewStream->ReadInt32(nBoundTop);
+ aNewStream->ReadInt32(nBoundRight);
+ aNewStream->ReadInt32(nBoundBottom);
+ aNewStream->ReadInt32(nFrameLeft);
+ aNewStream->ReadInt32(nFrameTop);
+ aNewStream->ReadInt32(nFrameRight);
+ aNewStream->ReadInt32(nFrameBottom);
+ aNewStream->ReadUInt32(nSignature);
+
+ if (nSignature == ENHMETA_SIGNATURE)
+ {
+ nFormat = GraphicFileFormat::EMF;
+ bRet = true;
+
+ if (bExtendedInfo)
+ {
+ // size in pixels
+ aPixSize.setWidth(nBoundRight - nBoundLeft + 1);
+ aPixSize.setHeight(nBoundBottom - nBoundTop + 1);
+
+ // size in 0.01mm units
+ aLogSize.setWidth(nFrameRight - nFrameLeft + 1);
+ aLogSize.setHeight(nFrameBottom - nFrameTop + 1);
+ }
+ }
+ }
+
+ rStm.Seek(nStmPos);
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectSVG( SvStream& /*rStm*/, bool /*bExtendedInfo*/ )
+{
+ bool bRet = aPathExt.startsWith( "svg" );
+ if (bRet)
+ nFormat = GraphicFileFormat::SVG;
+
+ return bRet;
+}
+
+bool GraphicDescriptor::ImpDetectWEBP( SvStream& rStm, bool bExtendedInfo )
+{
+ sal_uInt32 nTemp32 = 0;
+ bool bRet = false;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::BIG );
+ rStm.ReadUInt32( nTemp32 );
+
+ if ( nTemp32 == 0x52494646 )
+ {
+ rStm.ReadUInt32( nTemp32 ); // skip
+ rStm.ReadUInt32( nTemp32 );
+ if ( nTemp32 == 0x57454250 )
+ {
+ nFormat = GraphicFileFormat::WEBP;
+ bRet = true;
+
+ if ( bExtendedInfo )
+ {
+ rStm.Seek(nStmPos);
+ ReadWebpInfo(rStm, aPixSize, nBitsPerPixel, bIsAlpha );
+ bIsTransparent = bIsAlpha;
+ }
+ }
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
+OUString GraphicDescriptor::GetImportFormatShortName( GraphicFileFormat nFormat )
+{
+ const char *pKeyName = nullptr;
+
+ switch( nFormat )
+ {
+ case GraphicFileFormat::BMP : pKeyName = "bmp"; break;
+ case GraphicFileFormat::GIF : pKeyName = "gif"; break;
+ case GraphicFileFormat::JPG : pKeyName = "jpg"; break;
+ case GraphicFileFormat::PCD : pKeyName = "pcd"; break;
+ case GraphicFileFormat::PCX : pKeyName = "pcx"; break;
+ case GraphicFileFormat::PNG : pKeyName = "png"; break;
+ case GraphicFileFormat::XBM : pKeyName = "xbm"; break;
+ case GraphicFileFormat::XPM : pKeyName = "xpm"; break;
+ case GraphicFileFormat::PBM : pKeyName = "pbm"; break;
+ case GraphicFileFormat::PGM : pKeyName = "pgm"; break;
+ case GraphicFileFormat::PPM : pKeyName = "ppm"; break;
+ case GraphicFileFormat::RAS : pKeyName = "ras"; break;
+ case GraphicFileFormat::TGA : pKeyName = "tga"; break;
+ case GraphicFileFormat::PSD : pKeyName = "psd"; break;
+ case GraphicFileFormat::EPS : pKeyName = "eps"; break;
+ case GraphicFileFormat::TIF : pKeyName = "tif"; break;
+ case GraphicFileFormat::DXF : pKeyName = "dxf"; break;
+ case GraphicFileFormat::MET : pKeyName = "met"; break;
+ case GraphicFileFormat::PCT : pKeyName = "pct"; break;
+ case GraphicFileFormat::SVM : pKeyName = "svm"; break;
+ case GraphicFileFormat::WMF : pKeyName = "wmf"; break;
+ case GraphicFileFormat::EMF : pKeyName = "emf"; break;
+ case GraphicFileFormat::SVG : pKeyName = "svg"; break;
+ case GraphicFileFormat::WEBP : pKeyName = "webp"; break;
+ default: assert(false);
+ }
+
+ return OUString::createFromAscii(pKeyName);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/graphicfilter_internal.hxx b/vcl/source/filter/graphicfilter_internal.hxx
new file mode 100644
index 000000000..87f328fce
--- /dev/null
+++ b/vcl/source/filter/graphicfilter_internal.hxx
@@ -0,0 +1,32 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_GRAPHICFILTER_INTERNAL_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_GRAPHICFILTER_INTERNAL_HXX
+
+#include <tools/solar.h>
+#include <tools/stream.hxx>
+
+sal_uInt8* ImplSearchEntry(sal_uInt8*, sal_uInt8 const*, sal_uLong, sal_uLong);
+
+extern bool isPCT(SvStream& rStream, sal_uLong nStreamPos, sal_uLong nStreamLen);
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_GRAPHICFILTER_INTERNAL_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/idxf/dxf2mtf.cxx b/vcl/source/filter/idxf/dxf2mtf.cxx
new file mode 100644
index 000000000..2b26abffd
--- /dev/null
+++ b/vcl/source/filter/idxf/dxf2mtf.cxx
@@ -0,0 +1,902 @@
+/* -*- 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 <unotools/configmgr.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include <tools/poly.hxx>
+#include "dxf2mtf.hxx"
+
+#include <math.h>
+
+
+sal_uInt64 DXF2GDIMetaFile::CountEntities(const DXFEntities & rEntities)
+{
+ const DXFBasicEntity * pBE;
+ sal_uInt64 nRes;
+
+ nRes=0;
+ for (pBE=rEntities.pFirst; pBE!=nullptr; pBE=pBE->pSucc) nRes++;
+ return nRes;
+}
+
+Color DXF2GDIMetaFile::ConvertColor(sal_uInt8 nColor) const
+{
+ return Color(
+ pDXF->aPalette.GetRed( nColor ),
+ pDXF->aPalette.GetGreen( nColor ),
+ pDXF->aPalette.GetBlue( nColor ) );
+}
+
+tools::Long DXF2GDIMetaFile::GetEntityColor(const DXFBasicEntity & rE) const
+{
+ tools::Long nColor;
+
+ nColor=rE.nColor;
+ if (nColor==256) {
+ if (rE.m_sLayer.getLength() < 2) {
+ nColor=nParentLayerColor;
+ } else {
+ const DXFLayer * pLayer=pDXF->aTables.SearchLayer(rE.m_sLayer);
+ if (pLayer!=nullptr) nColor=pLayer->nColor;
+ else nColor=nParentLayerColor;
+ }
+ }
+ else if (nColor==0) nColor=nBlockColor;
+ return nColor;
+}
+
+DXFLineInfo DXF2GDIMetaFile::LTypeToDXFLineInfo(std::string_view rLineType) const
+{
+ const DXFLType * pLT;
+ DXFLineInfo aDXFLineInfo;
+
+ pLT = pDXF->aTables.SearchLType(rLineType);
+ if (pLT==nullptr || pLT->nDashCount == 0) {
+ aDXFLineInfo.eStyle = LineStyle::Solid;
+ }
+ else {
+ aDXFLineInfo.eStyle = LineStyle::Dash;
+ for (tools::Long i=0; i < (pLT->nDashCount); i++) {
+ const double x = pLT->fDash[i] * pDXF->getGlobalLineTypeScale();
+ if ( x >= 0.0 ) {
+ if ( aDXFLineInfo.nDotCount == 0 ) {
+ aDXFLineInfo.nDotCount ++;
+ aDXFLineInfo.fDotLen = x;
+ }
+ else if ( aDXFLineInfo.fDotLen == x ) {
+ aDXFLineInfo.nDotCount ++;
+ }
+ else if ( aDXFLineInfo.nDashCount == 0 ) {
+ aDXFLineInfo.nDashCount ++;
+ aDXFLineInfo.fDashLen = x;
+ }
+ else if ( aDXFLineInfo.fDashLen == x ) {
+ aDXFLineInfo.nDashCount ++;
+ }
+ else {
+ // It is impossible to be converted.
+ }
+ }
+ else {
+ if ( aDXFLineInfo.fDistance == 0 ) {
+ aDXFLineInfo.fDistance = -1 * x;
+ }
+ else {
+ // It is impossible to be converted.
+ }
+ }
+
+ }
+ }
+
+ return aDXFLineInfo;
+}
+
+DXFLineInfo DXF2GDIMetaFile::GetEntityDXFLineInfo(const DXFBasicEntity & rE)
+{
+ DXFLineInfo aDXFLineInfo;
+
+ aDXFLineInfo.eStyle = LineStyle::Solid;
+ aDXFLineInfo.nDashCount = 0;
+ aDXFLineInfo.fDashLen = 0;
+ aDXFLineInfo.nDotCount = 0;
+ aDXFLineInfo.fDotLen = 0;
+ aDXFLineInfo.fDistance = 0;
+
+ if (rE.m_sLineType == "BYLAYER") {
+ if (rE.m_sLayer.getLength() < 2) {
+ aDXFLineInfo=aParentLayerDXFLineInfo;
+ } else {
+ const DXFLayer * pLayer=pDXF->aTables.SearchLayer(rE.m_sLayer);
+ if (pLayer!=nullptr) {
+ aDXFLineInfo = LTypeToDXFLineInfo(pLayer->m_sLineType);
+ }
+ else aDXFLineInfo=aParentLayerDXFLineInfo;
+ }
+ }
+ else if (rE.m_sLineType == "BYBLOCK") {
+ aDXFLineInfo=aBlockDXFLineInfo;
+ }
+ else {
+ aDXFLineInfo = LTypeToDXFLineInfo(rE.m_sLineType);
+ }
+ return aDXFLineInfo;
+}
+
+
+bool DXF2GDIMetaFile::SetLineAttribute(const DXFBasicEntity & rE)
+{
+ tools::Long nColor;
+ Color aColor;
+
+ nColor=GetEntityColor(rE);
+ if (nColor<0) return false;
+ aColor=ConvertColor(static_cast<sal_uInt8>(nColor));
+
+ if (aActLineColor!=aColor) {
+ aActLineColor = aColor;
+ pVirDev->SetLineColor( aActLineColor );
+ }
+
+ if (aActFillColor!=COL_TRANSPARENT) {
+ aActFillColor = COL_TRANSPARENT;
+ pVirDev->SetFillColor(aActFillColor);
+ }
+ return true;
+}
+
+
+bool DXF2GDIMetaFile::SetAreaAttribute(const DXFBasicEntity & rE)
+{
+ tools::Long nColor;
+ Color aColor;
+
+ nColor=GetEntityColor(rE);
+ if (nColor<0) return false;
+ aColor=ConvertColor(static_cast<sal_uInt8>(nColor));
+
+ if (aActLineColor!=aColor) {
+ aActLineColor = aColor;
+ pVirDev->SetLineColor( aActLineColor );
+ }
+
+ if ( aActFillColor == COL_TRANSPARENT || aActFillColor != aColor) {
+ aActFillColor = aColor;
+ pVirDev->SetFillColor( aActFillColor );
+ }
+ return true;
+}
+
+
+bool DXF2GDIMetaFile::SetFontAttribute(const DXFBasicEntity & rE, short nAngle, sal_uInt16 nHeight)
+{
+ tools::Long nColor;
+ Color aColor;
+ vcl::Font aFont;
+
+ nAngle=-nAngle;
+ while (nAngle>=3600) nAngle-=3600;
+ while (nAngle<0) nAngle+=3600;
+
+ nColor=GetEntityColor(rE);
+ if (nColor<0) return false;
+ aColor=ConvertColor(static_cast<sal_uInt8>(nColor));
+
+ aFont.SetColor(aColor);
+ aFont.SetTransparent(true);
+ aFont.SetFamily(FAMILY_SWISS);
+ aFont.SetFontSize(Size(0,nHeight));
+ aFont.SetAlignment(ALIGN_BASELINE);
+ aFont.SetOrientation(Degree10(nAngle));
+ if (aActFont!=aFont) {
+ aActFont=aFont;
+ pVirDev->SetFont(aActFont);
+ }
+
+ return true;
+}
+
+
+void DXF2GDIMetaFile::DrawLineEntity(const DXFLineEntity & rE, const DXFTransform & rTransform)
+{
+ if (!SetLineAttribute(rE))
+ return;
+
+ Point aP0,aP1;
+ rTransform.Transform(rE.aP0,aP0);
+ rTransform.Transform(rE.aP1,aP1);
+
+ DXFLineInfo aDXFLineInfo=GetEntityDXFLineInfo(rE);
+ LineInfo aLineInfo;
+ aLineInfo = rTransform.Transform(aDXFLineInfo);
+
+ pVirDev->DrawLine(aP0,aP1,aLineInfo);
+ if (rE.fThickness!=0) {
+ Point aP2,aP3;
+ rTransform.Transform(rE.aP0+DXFVector(0,0,rE.fThickness),aP2);
+ rTransform.Transform(rE.aP1+DXFVector(0,0,rE.fThickness),aP3);
+ DrawLine(aP2,aP3);
+ DrawLine(aP0,aP2);
+ DrawLine(aP1,aP3);
+ }
+}
+
+
+void DXF2GDIMetaFile::DrawPointEntity(const DXFPointEntity & rE, const DXFTransform & rTransform)
+{
+
+ if (SetLineAttribute(rE)) {
+ Point aP0;
+ rTransform.Transform(rE.aP0,aP0);
+ if (rE.fThickness==0) pVirDev->DrawPixel(aP0);
+ else {
+ Point aP1;
+ rTransform.Transform(rE.aP0+DXFVector(0,0,rE.fThickness),aP1);
+ DrawLine(aP0,aP1);
+ }
+ }
+}
+
+
+void DXF2GDIMetaFile::DrawCircleEntity(const DXFCircleEntity & rE, const DXFTransform & rTransform)
+{
+ double frx,fry;
+ sal_uInt16 nPoints,i;
+ DXFVector aC;
+
+ if (!SetLineAttribute(rE)) return;
+ rTransform.Transform(rE.aP0,aC);
+ if (rE.fThickness==0 && rTransform.TransCircleToEllipse(rE.fRadius,frx,fry)) {
+ pVirDev->DrawEllipse(
+ tools::Rectangle(static_cast<tools::Long>(aC.fx-frx+0.5),static_cast<tools::Long>(aC.fy-fry+0.5),
+ static_cast<tools::Long>(aC.fx+frx+0.5),static_cast<tools::Long>(aC.fy+fry+0.5)));
+ }
+ else {
+ double fAng;
+ nPoints=OptPointsPerCircle;
+ tools::Polygon aPoly(nPoints);
+ for (i=0; i<nPoints; i++) {
+ fAng=2*M_PI/static_cast<double>(nPoints-1)*static_cast<double>(i);
+ rTransform.Transform(
+ rE.aP0+DXFVector(rE.fRadius*cos(fAng),rE.fRadius*sin(fAng),0),
+ aPoly[i]
+ );
+ }
+ pVirDev->DrawPolyLine(aPoly);
+ if (rE.fThickness!=0) {
+ tools::Polygon aPoly2(nPoints);
+ for (i=0; i<nPoints; i++) {
+ fAng=2*M_PI/static_cast<double>(nPoints-1)*static_cast<double>(i);
+ rTransform.Transform(
+ rE.aP0+DXFVector(rE.fRadius*cos(fAng),rE.fRadius*sin(fAng),rE.fThickness),
+ aPoly2[i]
+ );
+
+ }
+ pVirDev->DrawPolyLine(aPoly2);
+ for (i=0; i<nPoints-1; i++) DrawLine(aPoly[i],aPoly2[i]);
+ }
+ }
+}
+
+void DXF2GDIMetaFile::DrawLine(const Point& rA, const Point& rB)
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return;
+ GDIMetaFile* pMetaFile = pVirDev->GetConnectMetaFile();
+ assert(pMetaFile);
+ //use AddAction instead of OutputDevice::DrawLine so that we can explicitly share
+ //the aDefaultLineInfo between the MetaLineActions to reduce memory use
+ pMetaFile->AddAction(new MetaLineAction(rA, rB, aDefaultLineInfo));
+}
+
+void DXF2GDIMetaFile::DrawArcEntity(const DXFArcEntity & rE, const DXFTransform & rTransform)
+{
+ double frx,fry;
+ sal_uInt16 nPoints,i;
+ DXFVector aC;
+
+ if (!SetLineAttribute(rE)) return;
+ double fA1=rE.fStart;
+ double fdA=rE.fEnd-fA1;
+ fdA = fmod(fdA, 360.0);
+ if (fdA<=0) fdA+=360.0;
+ rTransform.Transform(rE.aP0,aC);
+ if (rE.fThickness==0 && fdA>5.0 && rTransform.TransCircleToEllipse(rE.fRadius,frx,fry)) {
+ DXFVector aVS(cos(basegfx::deg2rad(fA1)),sin(basegfx::deg2rad(fA1)),0.0);
+ aVS*=rE.fRadius;
+ aVS+=rE.aP0;
+ DXFVector aVE(cos(basegfx::deg2rad(fA1+fdA)),sin(basegfx::deg2rad(fA1+fdA)),0.0);
+ aVE*=rE.fRadius;
+ aVE+=rE.aP0;
+ Point aPS,aPE;
+ if (rTransform.Mirror()) {
+ rTransform.Transform(aVS,aPS);
+ rTransform.Transform(aVE,aPE);
+ }
+ else {
+ rTransform.Transform(aVS,aPE);
+ rTransform.Transform(aVE,aPS);
+ }
+ pVirDev->DrawArc(
+ tools::Rectangle(static_cast<tools::Long>(aC.fx-frx+0.5),static_cast<tools::Long>(aC.fy-fry+0.5),
+ static_cast<tools::Long>(aC.fx+frx+0.5),static_cast<tools::Long>(aC.fy+fry+0.5)),
+ aPS,aPE
+ );
+ }
+ else {
+ double fAng;
+ nPoints=static_cast<sal_uInt16>(fdA/360.0*static_cast<double>(OptPointsPerCircle)+0.5);
+ if (nPoints<2) nPoints=2;
+ tools::Polygon aPoly(nPoints);
+ for (i=0; i<nPoints; i++) {
+ fAng=basegfx::deg2rad( fA1 + fdA/static_cast<double>(nPoints-1)*static_cast<double>(i) );
+ rTransform.Transform(
+ rE.aP0+DXFVector(rE.fRadius*cos(fAng),rE.fRadius*sin(fAng),0),
+ aPoly[i]
+ );
+ }
+ pVirDev->DrawPolyLine(aPoly);
+ if (rE.fThickness!=0) {
+ tools::Polygon aPoly2(nPoints);
+ for (i=0; i<nPoints; i++) {
+ fAng=basegfx::deg2rad( fA1 + fdA/static_cast<double>(nPoints-1)*static_cast<double>(i) );
+ rTransform.Transform(
+ rE.aP0+DXFVector(rE.fRadius*cos(fAng),rE.fRadius*sin(fAng),rE.fThickness),
+ aPoly2[i]
+ );
+ }
+ pVirDev->DrawPolyLine(aPoly2);
+ for (i=0; i<nPoints; i++)
+ DrawLine(aPoly[i], aPoly2[i]);
+ }
+ }
+}
+
+void DXF2GDIMetaFile::DrawTraceEntity(const DXFTraceEntity & rE, const DXFTransform & rTransform)
+{
+ if (!SetLineAttribute(rE))
+ return;
+
+ tools::Polygon aPoly(4);
+ rTransform.Transform(rE.aP0,aPoly[0]);
+ rTransform.Transform(rE.aP1,aPoly[1]);
+ rTransform.Transform(rE.aP3,aPoly[2]);
+ rTransform.Transform(rE.aP2,aPoly[3]);
+ pVirDev->DrawPolygon(aPoly);
+ if (rE.fThickness!=0) {
+ sal_uInt16 i;
+ tools::Polygon aPoly2(4);
+ DXFVector aVAdd(0,0,rE.fThickness);
+ rTransform.Transform(rE.aP0+aVAdd,aPoly2[0]);
+ rTransform.Transform(rE.aP1+aVAdd,aPoly2[1]);
+ rTransform.Transform(rE.aP3+aVAdd,aPoly2[2]);
+ rTransform.Transform(rE.aP2+aVAdd,aPoly2[3]);
+ pVirDev->DrawPolygon(aPoly2);
+ for (i=0; i<4; i++) DrawLine(aPoly[i],aPoly2[i]);
+ }
+}
+
+
+void DXF2GDIMetaFile::DrawSolidEntity(const DXFSolidEntity & rE, const DXFTransform & rTransform)
+{
+ if (!SetAreaAttribute(rE))
+ return;
+
+ sal_uInt16 nN;
+ if (rE.aP2==rE.aP3) nN=3; else nN=4;
+ tools::Polygon aPoly(nN);
+ rTransform.Transform(rE.aP0,aPoly[0]);
+ rTransform.Transform(rE.aP1,aPoly[1]);
+ rTransform.Transform(rE.aP3,aPoly[2]);
+ if (nN>3) rTransform.Transform(rE.aP2,aPoly[3]);
+ pVirDev->DrawPolygon(aPoly);
+ if (rE.fThickness==0) return;
+
+ tools::Polygon aPoly2(nN);
+ DXFVector aVAdd(0,0,rE.fThickness);
+ rTransform.Transform(rE.aP0+aVAdd,aPoly2[0]);
+ rTransform.Transform(rE.aP1+aVAdd,aPoly2[1]);
+ rTransform.Transform(rE.aP3+aVAdd,aPoly2[2]);
+ if (nN>3) rTransform.Transform(rE.aP2+aVAdd,aPoly2[3]);
+ pVirDev->DrawPolygon(aPoly2);
+ if (SetLineAttribute(rE)) {
+ sal_uInt16 i;
+ for (i=0; i<nN; i++) DrawLine(aPoly[i],aPoly2[i]);
+ }
+}
+
+
+void DXF2GDIMetaFile::DrawTextEntity(const DXFTextEntity & rE, const DXFTransform & rTransform)
+{
+ DXFVector aV;
+ double fA;
+ sal_uInt16 nHeight;
+ short nAng;
+ DXFTransform aT( DXFTransform(rE.fXScale,rE.fHeight,1.0,rE.fRotAngle,rE.aP0), rTransform );
+ aT.TransDir(DXFVector(0,1,0),aV);
+ nHeight=static_cast<sal_uInt16>(aV.Abs()+0.5);
+ fA=aT.CalcRotAngle();
+ nAng=static_cast<short>(fA*10.0+0.5);
+ aT.TransDir(DXFVector(1,0,0),aV);
+ if ( SetFontAttribute( rE,nAng, nHeight ) )
+ {
+ OUString const aUString(pDXF->ToOUString(rE.m_sText));
+ Point aPt;
+ aT.Transform( DXFVector( 0, 0, 0 ), aPt );
+ pVirDev->DrawText( aPt, aUString );
+ }
+}
+
+
+void DXF2GDIMetaFile::DrawInsertEntity(const DXFInsertEntity & rE, const DXFTransform & rTransform)
+{
+ const DXFBlock * pB;
+ pB=pDXF->aBlocks.Search(rE.m_sName);
+ if (pB==nullptr)
+ return;
+
+ DXFTransform aDXFTransform1(1.0,1.0,1.0,DXFVector(0.0,0.0,0.0)-pB->aBasePoint);
+ DXFTransform aDXFTransform2(rE.fXScale,rE.fYScale,rE.fZScale,rE.fRotAngle,rE.aP0);
+ DXFTransform aT(
+ DXFTransform( aDXFTransform1, aDXFTransform2 ),
+ rTransform
+ );
+ tools::Long nSavedBlockColor, nSavedParentLayerColor;
+ DXFLineInfo aSavedBlockDXFLineInfo, aSavedParentLayerDXFLineInfo;
+ nSavedBlockColor=nBlockColor;
+ nSavedParentLayerColor=nParentLayerColor;
+ aSavedBlockDXFLineInfo=aBlockDXFLineInfo;
+ aSavedParentLayerDXFLineInfo=aParentLayerDXFLineInfo;
+ nBlockColor=GetEntityColor(rE);
+ aBlockDXFLineInfo=GetEntityDXFLineInfo(rE);
+ if (rE.m_sLayer.getLength() > 1) {
+ DXFLayer * pLayer=pDXF->aTables.SearchLayer(rE.m_sLayer);
+ if (pLayer!=nullptr) {
+ nParentLayerColor=pLayer->nColor;
+ aParentLayerDXFLineInfo = LTypeToDXFLineInfo(pLayer->m_sLineType);
+ }
+ }
+ DrawEntities(*pB,aT);
+ aBlockDXFLineInfo=aSavedBlockDXFLineInfo;
+ aParentLayerDXFLineInfo=aSavedParentLayerDXFLineInfo;
+ nBlockColor=nSavedBlockColor;
+ nParentLayerColor=nSavedParentLayerColor;
+}
+
+
+void DXF2GDIMetaFile::DrawAttribEntity(const DXFAttribEntity & rE, const DXFTransform & rTransform)
+{
+ if ((rE.nAttrFlags&1)!=0)
+ return;
+
+ DXFVector aV;
+ double fA;
+ sal_uInt16 nHeight;
+ short nAng;
+ DXFTransform aT( DXFTransform( rE.fXScale, rE.fHeight, 1.0, rE.fRotAngle, rE.aP0 ), rTransform );
+ aT.TransDir(DXFVector(0,1,0),aV);
+ nHeight=static_cast<sal_uInt16>(aV.Abs()+0.5);
+ fA=aT.CalcRotAngle();
+ nAng=static_cast<short>(fA*10.0+0.5);
+ aT.TransDir(DXFVector(1,0,0),aV);
+ if (SetFontAttribute(rE,nAng,nHeight))
+ {
+ OUString const aUString(pDXF->ToOUString(rE.m_sText));
+ Point aPt;
+ aT.Transform( DXFVector( 0, 0, 0 ), aPt );
+ pVirDev->DrawText( aPt, aUString );
+ }
+}
+
+
+void DXF2GDIMetaFile::DrawPolyLineEntity(const DXFPolyLineEntity & rE, const DXFTransform & rTransform)
+{
+ sal_uInt16 i,nPolySize;
+ const DXFBasicEntity * pBE;
+
+ nPolySize=0;
+ pBE=rE.pSucc;
+ while (pBE!=nullptr && pBE->eType==DXF_VERTEX) {
+ nPolySize++;
+ pBE=pBE->pSucc;
+ }
+ if (nPolySize<2)
+ return;
+ tools::Polygon aPoly(nPolySize);
+ pBE=rE.pSucc;
+ for (i=0; i<nPolySize; i++) {
+ rTransform.Transform(static_cast<const DXFVertexEntity*>(pBE)->aP0,aPoly[i]);
+ pBE=pBE->pSucc;
+ }
+
+ if (!SetLineAttribute(rE))
+ return;
+
+ if ((rE.nFlags&1)!=0) pVirDev->DrawPolygon(aPoly);
+ else pVirDev->DrawPolyLine(aPoly);
+ if (rE.fThickness==0)
+ return;
+
+ tools::Polygon aPoly2(nPolySize);
+ pBE=rE.pSucc;
+ for (i=0; i<nPolySize; i++) {
+ rTransform.Transform(
+ (static_cast<const DXFVertexEntity*>(pBE)->aP0)+DXFVector(0,0,rE.fThickness),
+ aPoly2[i]
+ );
+ pBE=pBE->pSucc;
+ }
+ if ((rE.nFlags&1)!=0) pVirDev->DrawPolygon(aPoly2);
+ else pVirDev->DrawPolyLine(aPoly2);
+ for (i=0; i<nPolySize; i++) DrawLine(aPoly[i],aPoly2[i]);
+}
+
+void DXF2GDIMetaFile::DrawLWPolyLineEntity(const DXFLWPolyLineEntity & rE, const DXFTransform & rTransform )
+{
+ sal_Int32 nPolySize = rE.aP.size();
+ if (!nPolySize)
+ return;
+
+ tools::Polygon aPoly( static_cast<sal_uInt16>(nPolySize));
+ for (sal_Int32 i = 0; i < nPolySize; ++i)
+ {
+ rTransform.Transform( rE.aP[ static_cast<sal_uInt16>(i) ], aPoly[ static_cast<sal_uInt16>(i) ] );
+ }
+ if ( SetLineAttribute( rE ) )
+ {
+ if ( ( rE.nFlags & 1 ) != 0 )
+ pVirDev->DrawPolygon( aPoly );
+ else
+ pVirDev->DrawPolyLine( aPoly );
+ }
+}
+
+void DXF2GDIMetaFile::DrawHatchEntity(const DXFHatchEntity & rE, const DXFTransform & rTransform )
+{
+ if (rE.aBoundaryPathData.empty())
+ return;
+
+ SetAreaAttribute( rE );
+ tools::PolyPolygon aPolyPoly;
+ for (const DXFBoundaryPathData& rPathData : rE.aBoundaryPathData)
+ {
+ std::vector< Point > aPtAry;
+ if ( rPathData.bIsPolyLine )
+ {
+ for (const auto& a : rPathData.aP)
+ {
+ Point aPt;
+ rTransform.Transform(a, aPt);
+ aPtAry.push_back( aPt );
+ }
+ }
+ else
+ {
+ for ( auto& rEdge : rPathData.aEdges )
+ {
+ const DXFEdgeType* pEdge = rEdge.get();
+ switch( pEdge->nEdgeType )
+ {
+ case 1 :
+ {
+ Point aPt;
+ rTransform.Transform( static_cast<const DXFEdgeTypeLine*>(pEdge)->aStartPoint, aPt );
+ aPtAry.push_back( aPt );
+ rTransform.Transform( static_cast<const DXFEdgeTypeLine*>(pEdge)->aEndPoint, aPt );
+ aPtAry.push_back( aPt );
+ }
+ break;
+ case 2 :
+ case 3 :
+ case 4 :
+ break;
+ }
+ }
+ }
+ sal_uInt16 i, nSize = static_cast<sal_uInt16>(aPtAry.size());
+ if ( nSize )
+ {
+ tools::Polygon aPoly( nSize );
+ for ( i = 0; i < nSize; i++ )
+ aPoly[ i ] = aPtAry[ i ];
+ aPolyPoly.Insert( aPoly );
+ }
+ }
+ if ( aPolyPoly.Count() )
+ pVirDev->DrawPolyPolygon( aPolyPoly );
+}
+
+void DXF2GDIMetaFile::Draw3DFaceEntity(const DXF3DFaceEntity & rE, const DXFTransform & rTransform)
+{
+ sal_uInt16 nN,i;
+ if (!SetLineAttribute(rE))
+ return;
+
+ if (rE.aP2==rE.aP3) nN=3; else nN=4;
+ tools::Polygon aPoly(nN);
+ rTransform.Transform(rE.aP0,aPoly[0]);
+ rTransform.Transform(rE.aP1,aPoly[1]);
+ rTransform.Transform(rE.aP2,aPoly[2]);
+ if (nN>3) rTransform.Transform(rE.aP3,aPoly[3]);
+ if ((rE.nIEFlags&0x0f)==0) pVirDev->DrawPolygon(aPoly);
+ else {
+ for (i=0; i<nN; i++) {
+ if ( (rE.nIEFlags & (static_cast<tools::Long>(1)<<i)) == 0 ) {
+ DrawLine(aPoly[i],aPoly[(i+1)%nN]);
+ }
+ }
+ }
+}
+
+void DXF2GDIMetaFile::DrawDimensionEntity(const DXFDimensionEntity & rE, const DXFTransform & rTransform)
+{
+ const DXFBlock * pB;
+ pB=pDXF->aBlocks.Search(rE.m_sPseudoBlock);
+ if (pB==nullptr)
+ return;
+
+ DXFTransform aT(
+ DXFTransform(1.0,1.0,1.0,DXFVector(0.0,0.0,0.0)-pB->aBasePoint),
+ rTransform
+ );
+ tools::Long nSavedBlockColor, nSavedParentLayerColor;
+ DXFLineInfo aSavedBlockDXFLineInfo, aSavedParentLayerDXFLineInfo;
+ nSavedBlockColor=nBlockColor;
+ nSavedParentLayerColor=nParentLayerColor;
+ aSavedBlockDXFLineInfo=aBlockDXFLineInfo;
+ aSavedParentLayerDXFLineInfo=aParentLayerDXFLineInfo;
+ nBlockColor=GetEntityColor(rE);
+ aBlockDXFLineInfo=GetEntityDXFLineInfo(rE);
+ if (rE.m_sLayer.getLength() > 1) {
+ DXFLayer * pLayer=pDXF->aTables.SearchLayer(rE.m_sLayer);
+ if (pLayer!=nullptr) {
+ nParentLayerColor=pLayer->nColor;
+ aParentLayerDXFLineInfo = LTypeToDXFLineInfo(pLayer->m_sLineType);
+ }
+ }
+ DrawEntities(*pB,aT);
+ aBlockDXFLineInfo=aSavedBlockDXFLineInfo;
+ aParentLayerDXFLineInfo=aSavedParentLayerDXFLineInfo;
+ nBlockColor=nSavedBlockColor;
+ nParentLayerColor=nSavedParentLayerColor;
+}
+
+
+void DXF2GDIMetaFile::DrawEntities(const DXFEntities & rEntities,
+ const DXFTransform & rTransform)
+{
+ if (rEntities.mbBeingDrawn)
+ return;
+ rEntities.mbBeingDrawn = true;
+
+ DXFTransform aET;
+ const DXFTransform * pT;
+
+ const DXFBasicEntity * pE=rEntities.pFirst;
+
+ while (pE!=nullptr && bStatus) {
+ if (pE->nSpace==0) {
+ if (pE->aExtrusion.fz==1.0) {
+ pT=&rTransform;
+ }
+ else {
+ aET=DXFTransform(DXFTransform(pE->aExtrusion),rTransform);
+ pT=&aET;
+ }
+ switch (pE->eType) {
+ case DXF_LINE:
+ DrawLineEntity(static_cast<const DXFLineEntity&>(*pE),*pT);
+ break;
+ case DXF_POINT:
+ DrawPointEntity(static_cast<const DXFPointEntity&>(*pE),*pT);
+ break;
+ case DXF_CIRCLE:
+ DrawCircleEntity(static_cast<const DXFCircleEntity&>(*pE),*pT);
+ break;
+ case DXF_ARC:
+ DrawArcEntity(static_cast<const DXFArcEntity&>(*pE),*pT);
+ break;
+ case DXF_TRACE:
+ DrawTraceEntity(static_cast<const DXFTraceEntity&>(*pE),*pT);
+ break;
+ case DXF_SOLID:
+ DrawSolidEntity(static_cast<const DXFSolidEntity&>(*pE),*pT);
+ break;
+ case DXF_TEXT:
+ DrawTextEntity(static_cast<const DXFTextEntity&>(*pE),*pT);
+ break;
+ case DXF_INSERT:
+ DrawInsertEntity(static_cast<const DXFInsertEntity&>(*pE),*pT);
+ break;
+ case DXF_ATTRIB:
+ DrawAttribEntity(static_cast<const DXFAttribEntity&>(*pE),*pT);
+ break;
+ case DXF_POLYLINE:
+ DrawPolyLineEntity(static_cast<const DXFPolyLineEntity&>(*pE),*pT);
+ break;
+ case DXF_LWPOLYLINE :
+ DrawLWPolyLineEntity(static_cast<const DXFLWPolyLineEntity&>(*pE), *pT);
+ break;
+ case DXF_HATCH :
+ DrawHatchEntity(static_cast<const DXFHatchEntity&>(*pE), *pT);
+ break;
+ case DXF_3DFACE:
+ Draw3DFaceEntity(static_cast<const DXF3DFaceEntity&>(*pE),*pT);
+ break;
+ case DXF_DIMENSION:
+ DrawDimensionEntity(static_cast<const DXFDimensionEntity&>(*pE),*pT);
+ break;
+ default:
+ break; // four other values not handled -Wall
+ }
+ }
+ pE=pE->pSucc;
+ }
+
+ rEntities.mbBeingDrawn = false;
+}
+
+
+DXF2GDIMetaFile::DXF2GDIMetaFile()
+ : pVirDev(nullptr)
+ , pDXF(nullptr)
+ , bStatus(false)
+ , OptPointsPerCircle(0)
+ , nMinPercent(0)
+ , nMaxPercent(0)
+ , nLastPercent(0)
+ , nMainEntitiesCount(0)
+ , nBlockColor(0)
+ , nParentLayerColor(0)
+{
+}
+
+
+DXF2GDIMetaFile::~DXF2GDIMetaFile()
+{
+}
+
+
+bool DXF2GDIMetaFile::Convert(const DXFRepresentation & rDXF, GDIMetaFile & rMTF, sal_uInt16 nminpercent, sal_uInt16 nmaxpercent)
+{
+ double fWidth,fHeight,fScale(0.0);
+ DXFTransform aTransform;
+ Size aPrefSize;
+ const DXFLayer * pLayer;
+ const DXFVPort * pVPort;
+
+ pVirDev = VclPtr<VirtualDevice>::Create();
+ pDXF = &rDXF;
+ bStatus = true;
+
+ OptPointsPerCircle=50;
+
+ nMinPercent=nminpercent;
+ nMaxPercent=nmaxpercent;
+ nLastPercent=nMinPercent;
+ nMainEntitiesCount=CountEntities(pDXF->aEntities);
+
+ nBlockColor=7;
+ aBlockDXFLineInfo.eStyle = LineStyle::Solid;
+ aBlockDXFLineInfo.nDashCount = 0;
+ aBlockDXFLineInfo.fDashLen = 0;
+ aBlockDXFLineInfo.nDotCount = 0;
+ aBlockDXFLineInfo.fDotLen = 0;
+ aBlockDXFLineInfo.fDistance = 0;
+
+ pLayer=pDXF->aTables.SearchLayer("0");
+ if (pLayer!=nullptr) {
+ nParentLayerColor=pLayer->nColor & 0xff;
+ aParentLayerDXFLineInfo = LTypeToDXFLineInfo(pLayer->m_sLineType);
+ }
+ else {
+ nParentLayerColor=7;
+ aParentLayerDXFLineInfo.eStyle = LineStyle::Solid;
+ aParentLayerDXFLineInfo.nDashCount = 0;
+ aParentLayerDXFLineInfo.fDashLen = 0;
+ aParentLayerDXFLineInfo.nDotCount = 0;
+ aParentLayerDXFLineInfo.fDotLen = 0;
+ aParentLayerDXFLineInfo.fDistance = 0;
+ }
+
+ pVirDev->EnableOutput(false);
+ if (!utl::ConfigManager::IsFuzzing()) // for fuzzing don't bother recording the drawing
+ rMTF.Record(pVirDev);
+
+ aActLineColor = pVirDev->GetLineColor();
+ aActFillColor = pVirDev->GetFillColor();
+ aActFont = pVirDev->GetFont();
+
+ pVPort=pDXF->aTables.SearchVPort("*ACTIVE");
+ if (pVPort!=nullptr) {
+ if (pVPort->aDirection.fx==0 && pVPort->aDirection.fy==0)
+ pVPort=nullptr;
+ }
+
+ if (pVPort==nullptr) {
+ if (pDXF->aBoundingBox.bEmpty)
+ bStatus=false;
+ else {
+ fWidth=pDXF->aBoundingBox.fMaxX-pDXF->aBoundingBox.fMinX;
+ fHeight=pDXF->aBoundingBox.fMaxY-pDXF->aBoundingBox.fMinY;
+ if (fWidth<=0 || fHeight<=0) {
+ bStatus=false;
+ }
+ else {
+ if (fWidth>fHeight)
+ fScale=10000.0/fWidth;
+ else
+ fScale=10000.0/fHeight;
+ aTransform=DXFTransform(fScale,-fScale,fScale,
+ DXFVector(-pDXF->aBoundingBox.fMinX*fScale,
+ pDXF->aBoundingBox.fMaxY*fScale,
+ -pDXF->aBoundingBox.fMinZ*fScale));
+ }
+ aPrefSize.setWidth(static_cast<tools::Long>(fWidth*fScale+1.5) );
+ aPrefSize.setHeight(static_cast<tools::Long>(fHeight*fScale+1.5) );
+ }
+ }
+ else {
+ fHeight=pVPort->fHeight;
+ fWidth=fHeight*pVPort->fAspectRatio;
+ if (fWidth<=0 || fHeight<=0) {
+ bStatus=false;
+ } else {
+ if (fWidth>fHeight)
+ fScale=10000.0/fWidth;
+ else
+ fScale=10000.0/fHeight;
+ aTransform=DXFTransform(
+ DXFTransform(pVPort->aDirection,pVPort->aTarget),
+ DXFTransform(
+ DXFTransform(1.0,-1.0,1.0,DXFVector(fWidth/2-pVPort->fCenterX,fHeight/2+pVPort->fCenterY,0)),
+ DXFTransform(fScale,fScale,fScale,DXFVector(0,0,0))
+ )
+ );
+ }
+ aPrefSize.setWidth(static_cast<tools::Long>(fWidth*fScale+1.5) );
+ aPrefSize.setHeight(static_cast<tools::Long>(fHeight*fScale+1.5) );
+ }
+
+ if (bStatus)
+ DrawEntities(pDXF->aEntities,aTransform);
+
+ rMTF.Stop();
+
+ if ( bStatus )
+ {
+ rMTF.SetPrefSize( aPrefSize );
+ // simply set map mode to 1/100-mm (1/10-mm) if the graphic
+ // does not get not too small (<0.5cm)
+ if( ( aPrefSize.Width() < 500 ) && ( aPrefSize.Height() < 500 ) )
+ rMTF.SetPrefMapMode( MapMode( MapUnit::Map10thMM ) );
+ else
+ rMTF.SetPrefMapMode( MapMode( MapUnit::Map100thMM ) );
+ }
+
+ pVirDev.disposeAndClear();
+ return bStatus;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/idxf/dxf2mtf.hxx b/vcl/source/filter/idxf/dxf2mtf.hxx
new file mode 100644
index 000000000..d8a935b51
--- /dev/null
+++ b/vcl/source/filter/idxf/dxf2mtf.hxx
@@ -0,0 +1,119 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXF2MTF_HXX
+#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXF2MTF_HXX
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include "dxfreprd.hxx"
+#include <vcl/font.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/vclptr.hxx>
+#include <vcl/virdev.hxx>
+
+class DXF2GDIMetaFile {
+private:
+
+ VclPtr<VirtualDevice> pVirDev;
+ const DXFRepresentation * pDXF;
+ bool bStatus;
+
+ sal_uInt16 OptPointsPerCircle;
+
+ sal_uInt16 nMinPercent;
+ sal_uInt16 nMaxPercent;
+ sal_uInt16 nLastPercent;
+ sal_uInt16 nMainEntitiesCount;
+
+ tools::Long nBlockColor;
+ DXFLineInfo aBlockDXFLineInfo;
+ tools::Long nParentLayerColor;
+ DXFLineInfo aParentLayerDXFLineInfo;
+ Color aActLineColor;
+ Color aActFillColor;
+ vcl::Font aActFont;
+ const LineInfo aDefaultLineInfo; // to share between lines to reduce memory
+
+ static sal_uInt64 CountEntities(const DXFEntities & rEntities);
+
+ Color ConvertColor(sal_uInt8 nColor) const;
+
+ tools::Long GetEntityColor(const DXFBasicEntity & rE) const;
+
+ DXFLineInfo LTypeToDXFLineInfo(std::string_view rLineType) const;
+
+ DXFLineInfo GetEntityDXFLineInfo(const DXFBasicEntity & rE);
+
+ bool SetLineAttribute(const DXFBasicEntity & rE);
+
+ bool SetAreaAttribute(const DXFBasicEntity & rE);
+
+ bool SetFontAttribute(const DXFBasicEntity & rE, short nAngle,
+ sal_uInt16 nHeight);
+
+ void DrawLineEntity(const DXFLineEntity & rE, const DXFTransform & rTransform);
+
+ void DrawPointEntity(const DXFPointEntity & rE, const DXFTransform & rTransform);
+
+ void DrawCircleEntity(const DXFCircleEntity & rE, const DXFTransform & rTransform);
+
+ void DrawArcEntity(const DXFArcEntity & rE, const DXFTransform & rTransform);
+
+ void DrawTraceEntity(const DXFTraceEntity & rE, const DXFTransform & rTransform);
+
+ void DrawSolidEntity(const DXFSolidEntity & rE, const DXFTransform & rTransform);
+
+ void DrawTextEntity(const DXFTextEntity & rE, const DXFTransform & rTransform);
+
+ void DrawInsertEntity(const DXFInsertEntity & rE, const DXFTransform & rTransform);
+
+ void DrawAttribEntity(const DXFAttribEntity & rE, const DXFTransform & rTransform);
+
+ void DrawPolyLineEntity(const DXFPolyLineEntity & rE, const DXFTransform & rTransform);
+
+ void Draw3DFaceEntity(const DXF3DFaceEntity & rE, const DXFTransform & rTransform);
+
+ void DrawDimensionEntity(const DXFDimensionEntity & rE, const DXFTransform & rTransform);
+
+ void DrawLWPolyLineEntity( const DXFLWPolyLineEntity & rE, const DXFTransform & rTransform );
+
+ void DrawHatchEntity( const DXFHatchEntity & rE, const DXFTransform & rTransform );
+
+ void DrawEntities(const DXFEntities & rEntities,
+ const DXFTransform & rTransform);
+
+ void DrawLine(const Point& rA, const Point& rB);
+
+public:
+
+ DXF2GDIMetaFile();
+ ~DXF2GDIMetaFile();
+
+ bool Convert( const DXFRepresentation & rDXF, GDIMetaFile & rMTF, sal_uInt16 nMinPercent, sal_uInt16 nMaxPercent);
+
+};
+
+
+#endif
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/idxf/dxfblkrd.cxx b/vcl/source/filter/idxf/dxfblkrd.cxx
new file mode 100644
index 000000000..b5a96b93e
--- /dev/null
+++ b/vcl/source/filter/idxf/dxfblkrd.cxx
@@ -0,0 +1,125 @@
+/* -*- 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 "dxfblkrd.hxx"
+
+
+//---------------- DXFBlock --------------------------------------------------
+
+
+DXFBlock::DXFBlock()
+ : pSucc(nullptr)
+ , nFlags(0)
+{
+}
+
+
+DXFBlock::~DXFBlock()
+{
+}
+
+
+void DXFBlock::Read(DXFGroupReader & rDGR)
+{
+ m_sName = "";
+ m_sAlsoName = "";
+ aBasePoint.fx=0.0;
+ aBasePoint.fy=0.0;
+ aBasePoint.fz=0.0;
+ nFlags=0;
+ m_sXRef = "";
+
+ while (rDGR.Read()!=0)
+ {
+ switch (rDGR.GetG())
+ {
+ case 2: m_sName = rDGR.GetS(); break;
+ case 3: m_sAlsoName = rDGR.GetS(); break;
+ case 70: nFlags=rDGR.GetI(); break;
+ case 10: aBasePoint.fx=rDGR.GetF(); break;
+ case 20: aBasePoint.fy=rDGR.GetF(); break;
+ case 30: aBasePoint.fz=rDGR.GetF(); break;
+ case 1: m_sXRef = rDGR.GetS(); break;
+ }
+ }
+ DXFEntities::Read(rDGR);
+}
+
+
+//---------------- DXFBlocks -------------------------------------------------
+
+
+DXFBlocks::DXFBlocks()
+{
+ pFirst=nullptr;
+}
+
+
+DXFBlocks::~DXFBlocks()
+{
+ Clear();
+}
+
+
+void DXFBlocks::Read(DXFGroupReader & rDGR)
+{
+ DXFBlock * pB, * * ppSucc;
+
+ ppSucc=&pFirst;
+ while (*ppSucc!=nullptr) ppSucc=&((*ppSucc)->pSucc);
+
+ for (;;) {
+ while (rDGR.GetG()!=0) rDGR.Read();
+ if (rDGR.GetS() == "ENDSEC" ||
+ rDGR.GetS() == "EOF") break;
+ if (rDGR.GetS() == "BLOCK") {
+ pB=new DXFBlock;
+ pB->Read(rDGR);
+ *ppSucc=pB;
+ ppSucc=&(pB->pSucc);
+ }
+ else rDGR.Read();
+ }
+}
+
+
+DXFBlock * DXFBlocks::Search(std::string_view rName) const
+{
+ DXFBlock * pB;
+ for (pB=pFirst; pB!=nullptr; pB=pB->pSucc) {
+ if (rName == pB->m_sName) break;
+ }
+ return pB;
+}
+
+
+void DXFBlocks::Clear()
+{
+ DXFBlock * ptmp;
+
+ while (pFirst!=nullptr) {
+ ptmp=pFirst;
+ pFirst=ptmp->pSucc;
+ delete ptmp;
+ }
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/idxf/dxfblkrd.hxx b/vcl/source/filter/idxf/dxfblkrd.hxx
new file mode 100644
index 000000000..ca0a0e68a
--- /dev/null
+++ b/vcl/source/filter/idxf/dxfblkrd.hxx
@@ -0,0 +1,83 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFBLKRD_HXX
+#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFBLKRD_HXX
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include "dxfentrd.hxx"
+
+
+//---------------- A Block (= Set of Entities) --------------------------
+
+
+class DXFBlock : public DXFEntities {
+
+public:
+
+ DXFBlock * pSucc;
+ // pointer to the next block in the list DXFBlocks::pFirst
+
+ // properties of blocks; commented with group codes:
+ OString m_sName; // 2
+ OString m_sAlsoName; // 3
+ tools::Long nFlags; // 70
+ DXFVector aBasePoint; // 10,20,30
+ OString m_sXRef; // 1
+
+ DXFBlock();
+ ~DXFBlock();
+
+ void Read(DXFGroupReader & rDGR);
+ // reads the block (including entities) from a dxf file
+ // by rGDR until an ENDBLK, ENDSEC or EOF.
+};
+
+
+//---------------- A set of blocks -----------------------------------
+
+
+class DXFBlocks {
+
+ DXFBlock * pFirst;
+ // list of blocks, READ ONLY!
+
+public:
+
+ DXFBlocks();
+ ~DXFBlocks();
+
+ void Read(DXFGroupReader & rDGR);
+ // reads all block per rDGR until an ENDSEC or EOF.
+
+ DXFBlock * Search(std::string_view rName) const;
+ // looks for a block with the name, return NULL if not successful
+
+ void Clear();
+ // deletes all blocks
+
+};
+
+#endif
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/idxf/dxfentrd.cxx b/vcl/source/filter/idxf/dxfentrd.cxx
new file mode 100644
index 000000000..b4915c657
--- /dev/null
+++ b/vcl/source/filter/idxf/dxfentrd.cxx
@@ -0,0 +1,848 @@
+/* -*- 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 <o3tl/safeint.hxx>
+
+#include "dxfentrd.hxx"
+
+//--------------------------DXFBasicEntity--------------------------------------
+
+DXFBasicEntity::DXFBasicEntity(DXFEntityType eThisType)
+ : m_sLayer("0")
+ , m_sLineType("BYLAYER")
+{
+ eType=eThisType;
+ pSucc=nullptr;
+ fThickness=0;
+ nColor=256;
+ nSpace=0;
+ aExtrusion.fx=0.0;
+ aExtrusion.fy=0.0;
+ aExtrusion.fz=1.0;
+}
+
+void DXFBasicEntity::Read(DXFGroupReader & rDGR)
+{
+ while (rDGR.Read()!=0) EvaluateGroup(rDGR);
+}
+
+void DXFBasicEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG())
+ {
+ case 8: m_sLayer = rDGR.GetS(); break;
+ case 6: m_sLineType = rDGR.GetS(); break;
+ case 39: fThickness=rDGR.GetF(); break;
+ case 62: nColor=rDGR.GetI(); break;
+ case 67: nSpace=rDGR.GetI(); break;
+ case 210: aExtrusion.fx=rDGR.GetF(); break;
+ case 220: aExtrusion.fy=rDGR.GetF(); break;
+ case 230: aExtrusion.fz=rDGR.GetF(); break;
+ }
+}
+
+DXFBasicEntity::~DXFBasicEntity()
+{
+}
+
+//--------------------------DXFLineEntity---------------------------------------
+
+DXFLineEntity::DXFLineEntity() : DXFBasicEntity(DXF_LINE)
+{
+}
+
+void DXFLineEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG()) {
+ case 10: aP0.fx=rDGR.GetF(); break;
+ case 20: aP0.fy=rDGR.GetF(); break;
+ case 30: aP0.fz=rDGR.GetF(); break;
+ case 11: aP1.fx=rDGR.GetF(); break;
+ case 21: aP1.fy=rDGR.GetF(); break;
+ case 31: aP1.fz=rDGR.GetF(); break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+//--------------------------DXFPointEntity--------------------------------------
+
+DXFPointEntity::DXFPointEntity() : DXFBasicEntity(DXF_POINT)
+{
+}
+
+void DXFPointEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG()) {
+ case 10: aP0.fx=rDGR.GetF(); break;
+ case 20: aP0.fy=rDGR.GetF(); break;
+ case 30: aP0.fz=rDGR.GetF(); break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+//--------------------------DXFCircleEntity-------------------------------------
+
+DXFCircleEntity::DXFCircleEntity() : DXFBasicEntity(DXF_CIRCLE)
+{
+ fRadius=1.0;
+}
+
+void DXFCircleEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG()) {
+ case 10: aP0.fx=rDGR.GetF(); break;
+ case 20: aP0.fy=rDGR.GetF(); break;
+ case 30: aP0.fz=rDGR.GetF(); break;
+ case 40: fRadius=rDGR.GetF(); break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+//--------------------------DXFArcEntity----------------------------------------
+
+DXFArcEntity::DXFArcEntity() : DXFBasicEntity(DXF_ARC)
+{
+ fRadius=1.0;
+ fStart=0;
+ fEnd=360.0;
+}
+
+void DXFArcEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG()) {
+ case 10: aP0.fx=rDGR.GetF(); break;
+ case 20: aP0.fy=rDGR.GetF(); break;
+ case 30: aP0.fz=rDGR.GetF(); break;
+ case 40: fRadius=rDGR.GetF(); break;
+ case 50: fStart=rDGR.GetF(); break;
+ case 51: fEnd=rDGR.GetF(); break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+//--------------------------DXFTraceEntity--------------------------------------
+
+DXFTraceEntity::DXFTraceEntity() : DXFBasicEntity(DXF_TRACE)
+{
+}
+
+void DXFTraceEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG()) {
+ case 10: aP0.fx=rDGR.GetF(); break;
+ case 20: aP0.fy=rDGR.GetF(); break;
+ case 30: aP0.fz=rDGR.GetF(); break;
+ case 11: aP1.fx=rDGR.GetF(); break;
+ case 21: aP1.fy=rDGR.GetF(); break;
+ case 31: aP1.fz=rDGR.GetF(); break;
+ case 12: aP2.fx=rDGR.GetF(); break;
+ case 22: aP2.fy=rDGR.GetF(); break;
+ case 32: aP2.fz=rDGR.GetF(); break;
+ case 13: aP3.fx=rDGR.GetF(); break;
+ case 23: aP3.fy=rDGR.GetF(); break;
+ case 33: aP3.fz=rDGR.GetF(); break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+//--------------------------DXFSolidEntity--------------------------------------
+
+DXFSolidEntity::DXFSolidEntity() : DXFBasicEntity(DXF_SOLID)
+{
+}
+
+void DXFSolidEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG()) {
+ case 10: aP0.fx=rDGR.GetF(); break;
+ case 20: aP0.fy=rDGR.GetF(); break;
+ case 30: aP0.fz=rDGR.GetF(); break;
+ case 11: aP1.fx=rDGR.GetF(); break;
+ case 21: aP1.fy=rDGR.GetF(); break;
+ case 31: aP1.fz=rDGR.GetF(); break;
+ case 12: aP2.fx=rDGR.GetF(); break;
+ case 22: aP2.fy=rDGR.GetF(); break;
+ case 32: aP2.fz=rDGR.GetF(); break;
+ case 13: aP3.fx=rDGR.GetF(); break;
+ case 23: aP3.fy=rDGR.GetF(); break;
+ case 33: aP3.fz=rDGR.GetF(); break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+//--------------------------DXFTextEntity---------------------------------------
+
+DXFTextEntity::DXFTextEntity()
+ : DXFBasicEntity(DXF_TEXT)
+ , m_sStyle("STANDARD")
+{
+ fHeight=1.0;
+ fRotAngle=0.0;
+ fXScale=1.0;
+ fOblAngle=0.0;
+ nGenFlags=0;
+ nHorzJust=0;
+ nVertJust=0;
+}
+
+void DXFTextEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG()) {
+ case 10: aP0.fx=rDGR.GetF(); break;
+ case 20: aP0.fy=rDGR.GetF(); break;
+ case 30: aP0.fz=rDGR.GetF(); break;
+ case 40: fHeight=rDGR.GetF(); break;
+ case 1: m_sText = rDGR.GetS(); break;
+ case 50: fRotAngle=rDGR.GetF(); break;
+ case 41: fXScale=rDGR.GetF(); break;
+ case 42: fOblAngle=rDGR.GetF(); break;
+ case 7: m_sStyle = rDGR.GetS(); break;
+ case 71: nGenFlags=rDGR.GetI(); break;
+ case 72: nHorzJust=rDGR.GetI(); break;
+ case 73: nVertJust=rDGR.GetI(); break;
+ case 11: aAlign.fx=rDGR.GetF(); break;
+ case 21: aAlign.fy=rDGR.GetF(); break;
+ case 31: aAlign.fz=rDGR.GetF(); break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+//--------------------------DXFShapeEntity--------------------------------------
+
+DXFShapeEntity::DXFShapeEntity() : DXFBasicEntity(DXF_SHAPE)
+{
+ fSize=1.0;
+ fRotAngle=0;
+ fXScale=1.0;
+ fOblAngle=0;
+}
+
+void DXFShapeEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG()) {
+ case 10: aP0.fx=rDGR.GetF(); break;
+ case 20: aP0.fy=rDGR.GetF(); break;
+ case 30: aP0.fz=rDGR.GetF(); break;
+ case 40: fSize=rDGR.GetF(); break;
+ case 2: m_sName = rDGR.GetS(); break;
+ case 50: fRotAngle=rDGR.GetF(); break;
+ case 41: fXScale=rDGR.GetF(); break;
+ case 51: fOblAngle=rDGR.GetF(); break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+//--------------------------DXFInsertEntity-------------------------------------
+
+DXFInsertEntity::DXFInsertEntity() : DXFBasicEntity(DXF_INSERT)
+{
+ nAttrFlag=0;
+ fXScale=1.0;
+ fYScale=1.0;
+ fZScale=1.0;
+ fRotAngle=0.0;
+ nColCount=1;
+ nRowCount=1;
+ fColSpace=0.0;
+ fRowSpace=0.0;
+}
+
+void DXFInsertEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG()) {
+ case 66: nAttrFlag=rDGR.GetI(); break;
+ case 2: m_sName = rDGR.GetS(); break;
+ case 10: aP0.fx=rDGR.GetF(); break;
+ case 20: aP0.fy=rDGR.GetF(); break;
+ case 30: aP0.fz=rDGR.GetF(); break;
+ case 41: fXScale=rDGR.GetF(); break;
+ case 42: fYScale=rDGR.GetF(); break;
+ case 43: fZScale=rDGR.GetF(); break;
+ case 50: fRotAngle=rDGR.GetF(); break;
+ case 70: nColCount=rDGR.GetI(); break;
+ case 71: nRowCount=rDGR.GetI(); break;
+ case 44: fColSpace=rDGR.GetF(); break;
+ case 45: fRowSpace=rDGR.GetF(); break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+//--------------------------DXFAttDefEntity-------------------------------------
+
+DXFAttDefEntity::DXFAttDefEntity()
+ : DXFBasicEntity(DXF_ATTDEF)
+ , m_sStyle("STANDARD")
+{
+ fHeight=1.0;
+ nAttrFlags=0;
+ nFieldLen=0;
+ fRotAngle=0.0;
+ fXScale=1.0;
+ fOblAngle=0.0;
+ nGenFlags=0;
+ nHorzJust=0;
+ nVertJust=0;
+}
+
+void DXFAttDefEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG()) {
+ case 10: aP0.fx=rDGR.GetF(); break;
+ case 20: aP0.fy=rDGR.GetF(); break;
+ case 30: aP0.fz=rDGR.GetF(); break;
+ case 40: fHeight=rDGR.GetF(); break;
+ case 1: m_sDefVal = rDGR.GetS(); break;
+ case 3: m_sPrompt = rDGR.GetS(); break;
+ case 2: m_sTagStr = rDGR.GetS(); break;
+ case 70: nAttrFlags=rDGR.GetI(); break;
+ case 73: nFieldLen=rDGR.GetI(); break;
+ case 50: fRotAngle=rDGR.GetF(); break;
+ case 41: fXScale=rDGR.GetF(); break;
+ case 51: fOblAngle=rDGR.GetF(); break;
+ case 7: m_sStyle = rDGR.GetS(); break;
+ case 71: nGenFlags=rDGR.GetI(); break;
+ case 72: nHorzJust=rDGR.GetI(); break;
+ case 74: nVertJust=rDGR.GetI(); break;
+ case 11: aAlign.fx=rDGR.GetF(); break;
+ case 21: aAlign.fy=rDGR.GetF(); break;
+ case 31: aAlign.fz=rDGR.GetF(); break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+//--------------------------DXFAttribEntity-------------------------------------
+
+DXFAttribEntity::DXFAttribEntity()
+ : DXFBasicEntity(DXF_ATTRIB)
+ , m_sStyle("STANDARD")
+{
+ fHeight=1.0;
+ nAttrFlags=0;
+ nFieldLen=0;
+ fRotAngle=0.0;
+ fXScale=1.0;
+ fOblAngle=0.0;
+ nGenFlags=0;
+ nHorzJust=0;
+ nVertJust=0;
+}
+
+void DXFAttribEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG()) {
+ case 10: aP0.fx=rDGR.GetF(); break;
+ case 20: aP0.fy=rDGR.GetF(); break;
+ case 30: aP0.fz=rDGR.GetF(); break;
+ case 40: fHeight=rDGR.GetF(); break;
+ case 1: m_sText = rDGR.GetS(); break;
+ case 2: m_sTagStr = rDGR.GetS(); break;
+ case 70: nAttrFlags=rDGR.GetI(); break;
+ case 73: nFieldLen=rDGR.GetI(); break;
+ case 50: fRotAngle=rDGR.GetF(); break;
+ case 41: fXScale=rDGR.GetF(); break;
+ case 51: fOblAngle=rDGR.GetF(); break;
+ case 7: m_sStyle = rDGR.GetS(); break;
+ case 71: nGenFlags=rDGR.GetI(); break;
+ case 72: nHorzJust=rDGR.GetI(); break;
+ case 74: nVertJust=rDGR.GetI(); break;
+ case 11: aAlign.fx=rDGR.GetF(); break;
+ case 21: aAlign.fy=rDGR.GetF(); break;
+ case 31: aAlign.fz=rDGR.GetF(); break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+//--------------------------DXFPolyLine-----------------------------------------
+
+DXFPolyLineEntity::DXFPolyLineEntity() : DXFBasicEntity(DXF_POLYLINE)
+{
+ nFlags=0;
+ fSWidth=0.0;
+ fEWidth=0.0;
+ nMeshMCount=0;
+ nMeshNCount=0;
+ nMDensity=0;
+ nNDensity=0;
+ nCSSType=0;
+}
+
+void DXFPolyLineEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG()) {
+ case 70: nFlags=rDGR.GetI(); break;
+ case 40: fSWidth=rDGR.GetF(); break;
+ case 41: fEWidth=rDGR.GetF(); break;
+ case 71: nMeshMCount=rDGR.GetI(); break;
+ case 72: nMeshNCount=rDGR.GetI(); break;
+ case 73: nMDensity=rDGR.GetI(); break;
+ case 74: nNDensity=rDGR.GetI(); break;
+ case 75: nCSSType=rDGR.GetI(); break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+//--------------------------DXFLWPolyLine---------------------------------------
+
+DXFLWPolyLineEntity::DXFLWPolyLineEntity() :
+ DXFBasicEntity( DXF_LWPOLYLINE ),
+ nIndex( 0 ),
+ nCount( 0 ),
+ nFlags( 0 ),
+ fConstantWidth( 0.0 ),
+ fStartWidth( 0.0 ),
+ fEndWidth( 0.0 )
+{
+}
+
+void DXFLWPolyLineEntity::EvaluateGroup( DXFGroupReader & rDGR )
+{
+ switch ( rDGR.GetG() )
+ {
+ case 90 :
+ {
+ nCount = rDGR.GetI();
+ // limit alloc to max reasonable size based on remaining data in stream
+ if (nCount > 0 && o3tl::make_unsigned(nCount) <= rDGR.remainingSize())
+ aP.reserve(nCount);
+ else
+ nCount = 0;
+ }
+ break;
+ case 70: nFlags = rDGR.GetI(); break;
+ case 43: fConstantWidth = rDGR.GetF(); break;
+ case 40: fStartWidth = rDGR.GetF(); break;
+ case 41: fEndWidth = rDGR.GetF(); break;
+ case 10:
+ {
+ if (nIndex < nCount)
+ {
+ aP.resize(nIndex+1);
+ aP[nIndex].fx = rDGR.GetF();
+ }
+ }
+ break;
+ case 20:
+ {
+ if (nIndex < nCount)
+ {
+ aP.resize(nIndex+1);
+ aP[nIndex].fy = rDGR.GetF();
+ ++nIndex;
+ }
+ }
+ break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+//--------------------------DXFHatchEntity-------------------------------------
+
+DXFEdgeTypeLine::DXFEdgeTypeLine() :
+ DXFEdgeType( 1 )
+{
+
+}
+
+bool DXFEdgeTypeLine::EvaluateGroup( DXFGroupReader & rDGR )
+{
+ bool bExecutingGroupCode = true;
+ switch ( rDGR.GetG() )
+ {
+ case 10 : aStartPoint.fx = rDGR.GetF(); break;
+ case 20 : aStartPoint.fy = rDGR.GetF(); break;
+ case 11 : aEndPoint.fx = rDGR.GetF(); break;
+ case 21 : aEndPoint.fy = rDGR.GetF(); break;
+ default : bExecutingGroupCode = false; break;
+ }
+ return bExecutingGroupCode;
+}
+
+DXFEdgeTypeCircularArc::DXFEdgeTypeCircularArc() :
+ DXFEdgeType( 2 ),
+ fRadius( 0.0 ),
+ fStartAngle( 0.0 ),
+ fEndAngle( 0.0 ),
+ nIsCounterClockwiseFlag( 0 )
+{
+}
+
+bool DXFEdgeTypeCircularArc::EvaluateGroup( DXFGroupReader & rDGR )
+{
+ bool bExecutingGroupCode = true;
+ switch ( rDGR.GetG() )
+ {
+ case 10 : aCenter.fx = rDGR.GetF(); break;
+ case 20 : aCenter.fy = rDGR.GetF(); break;
+ case 40 : fRadius = rDGR.GetF(); break;
+ case 50 : fStartAngle = rDGR.GetF(); break;
+ case 51 : fEndAngle = rDGR.GetF(); break;
+ case 73 : nIsCounterClockwiseFlag = rDGR.GetI(); break;
+ default : bExecutingGroupCode = false; break;
+ }
+ return bExecutingGroupCode;
+}
+
+DXFEdgeTypeEllipticalArc::DXFEdgeTypeEllipticalArc() :
+ DXFEdgeType( 3 ),
+ fLength( 0.0 ),
+ fStartAngle( 0.0 ),
+ fEndAngle( 0.0 ),
+ nIsCounterClockwiseFlag( 0 )
+{
+}
+
+bool DXFEdgeTypeEllipticalArc::EvaluateGroup( DXFGroupReader & rDGR )
+{
+ bool bExecutingGroupCode = true;
+ switch( rDGR.GetG() )
+ {
+ case 10 : aCenter.fx = rDGR.GetF(); break;
+ case 20 : aCenter.fy = rDGR.GetF(); break;
+ case 11 : aEndPoint.fx = rDGR.GetF(); break;
+ case 21 : aEndPoint.fy = rDGR.GetF(); break;
+ case 40 : fLength = rDGR.GetF(); break;
+ case 50 : fStartAngle = rDGR.GetF(); break;
+ case 51 : fEndAngle = rDGR.GetF(); break;
+ case 73 : nIsCounterClockwiseFlag = rDGR.GetI(); break;
+ default : bExecutingGroupCode = false; break;
+ }
+ return bExecutingGroupCode;
+}
+
+DXFEdgeTypeSpline::DXFEdgeTypeSpline() :
+ DXFEdgeType( 4 ),
+ nDegree( 0 ),
+ nRational( 0 ),
+ nPeriodic( 0 ),
+ nKnotCount( 0 ),
+ nControlCount( 0 )
+{
+}
+
+bool DXFEdgeTypeSpline::EvaluateGroup( DXFGroupReader & rDGR )
+{
+ bool bExecutingGroupCode = true;
+ switch ( rDGR.GetG() )
+ {
+ case 94 : nDegree = rDGR.GetI(); break;
+ case 73 : nRational = rDGR.GetI(); break;
+ case 74 : nPeriodic = rDGR.GetI(); break;
+ case 95 : nKnotCount = rDGR.GetI(); break;
+ case 96 : nControlCount = rDGR.GetI(); break;
+ default : bExecutingGroupCode = false; break;
+ }
+ return bExecutingGroupCode;
+}
+
+DXFBoundaryPathData::DXFBoundaryPathData() :
+ nPointCount( 0 ),
+ nFlags( 0 ),
+ nHasBulgeFlag( 0 ),
+ nIsClosedFlag( 0 ),
+ fBulge( 0.0 ),
+ nSourceBoundaryObjects( 0 ),
+ nEdgeCount( 0 ),
+ bIsPolyLine( true ),
+ nPointIndex( 0 )
+{
+}
+
+bool DXFBoundaryPathData::EvaluateGroup( DXFGroupReader & rDGR )
+{
+ bool bExecutingGroupCode = true;
+ if ( bIsPolyLine )
+ {
+ switch( rDGR.GetG() )
+ {
+ case 92 :
+ {
+ nFlags = rDGR.GetI();
+ if ( ( nFlags & 2 ) == 0 )
+ bIsPolyLine = false;
+ }
+ break;
+ case 93 :
+ {
+ nPointCount = rDGR.GetI();
+ // limit alloc to max reasonable size based on remaining data in stream
+ if (nPointCount > 0 && o3tl::make_unsigned(nPointCount) <= rDGR.remainingSize())
+ aP.reserve(nPointCount);
+ else
+ nPointCount = 0;
+ }
+ break;
+ case 72 : nHasBulgeFlag = rDGR.GetI(); break;
+ case 73 : nIsClosedFlag = rDGR.GetI(); break;
+ case 97 : nSourceBoundaryObjects = rDGR.GetI(); break;
+ case 42 : fBulge = rDGR.GetF(); break;
+ case 10:
+ {
+ if (nPointIndex < nPointCount)
+ {
+ aP.resize(nPointIndex+1);
+ aP[nPointIndex].fx = rDGR.GetF();
+ }
+ }
+ break;
+ case 20:
+ {
+ if (nPointIndex < nPointCount)
+ {
+ aP.resize(nPointIndex+1);
+ aP[nPointIndex].fy = rDGR.GetF();
+ ++nPointIndex;
+ }
+ }
+ break;
+
+ default : bExecutingGroupCode = false; break;
+ }
+ }
+ else
+ {
+ if ( rDGR.GetG() == 93 )
+ nEdgeCount = rDGR.GetI();
+ else if ( rDGR.GetG() == 72 )
+ {
+ sal_Int32 nEdgeType = rDGR.GetI();
+ switch( nEdgeType )
+ {
+ case 1 : aEdges.emplace_back( new DXFEdgeTypeLine() ); break;
+ case 2 : aEdges.emplace_back( new DXFEdgeTypeCircularArc() ); break;
+ case 3 : aEdges.emplace_back( new DXFEdgeTypeEllipticalArc() ); break;
+ case 4 : aEdges.emplace_back( new DXFEdgeTypeSpline() ); break;
+ }
+ }
+ else if ( !aEdges.empty() )
+ aEdges.back()->EvaluateGroup( rDGR );
+ else
+ bExecutingGroupCode = false;
+ }
+ return bExecutingGroupCode;
+}
+
+DXFHatchEntity::DXFHatchEntity() :
+ DXFBasicEntity( DXF_HATCH ),
+ bIsInBoundaryPathContext( false ),
+ nCurrentBoundaryPathIndex( -1 ),
+ nFlags( 0 ),
+ nAssociativityFlag( 0 ),
+ nMaxBoundaryPathCount( 0 ),
+ nHatchStyle( 0 ),
+ nHatchPatternType( 0 ),
+ fHatchPatternAngle( 0.0 ),
+ fHatchPatternScale( 1.0 ),
+ nHatchDoubleFlag( 0 ),
+ nHatchPatternDefinitionLines( 0 ),
+ fPixelSize( 1.0 ),
+ nNumberOfSeedPoints( 0 )
+{
+}
+
+void DXFHatchEntity::EvaluateGroup( DXFGroupReader & rDGR )
+{
+ switch ( rDGR.GetG() )
+ {
+// case 10 : aElevationPoint.fx = rDGR.GetF(); break;
+// case 20 : aElevationPoint.fy = rDGR.GetF(); break;
+// case 30 : aElevationPoint.fz = rDGR.GetF(); break;
+ case 70 : nFlags = rDGR.GetI(); break;
+ case 71 : nAssociativityFlag = rDGR.GetI(); break;
+ case 91 :
+ {
+ bIsInBoundaryPathContext = true;
+ nMaxBoundaryPathCount = rDGR.GetI();
+ // limit alloc to max reasonable size based on remaining data in stream
+ if (nMaxBoundaryPathCount > 0 && o3tl::make_unsigned(nMaxBoundaryPathCount) <= rDGR.remainingSize())
+ aBoundaryPathData.reserve(nMaxBoundaryPathCount);
+ else
+ nMaxBoundaryPathCount = 0;
+ }
+ break;
+ case 75 :
+ {
+ nHatchStyle = rDGR.GetI();
+ bIsInBoundaryPathContext = false;
+ }
+ break;
+ case 76 : nHatchPatternType = rDGR.GetI(); break;
+ case 52 : fHatchPatternAngle = rDGR.GetF(); break;
+ case 41 : fHatchPatternScale = rDGR.GetF(); break;
+ case 77 : nHatchDoubleFlag = rDGR.GetI(); break;
+ case 78 : nHatchPatternDefinitionLines = rDGR.GetI(); break;
+ case 47 : fPixelSize = rDGR.GetF(); break;
+ case 98 : nNumberOfSeedPoints = rDGR.GetI(); break;
+
+ case 92:
+ nCurrentBoundaryPathIndex++;
+ [[fallthrough]];
+ default:
+ {
+ bool bExecutingGroupCode = false;
+ if ( bIsInBoundaryPathContext )
+ {
+ if (nCurrentBoundaryPathIndex >= 0 && nCurrentBoundaryPathIndex < nMaxBoundaryPathCount)
+ {
+ aBoundaryPathData.resize(nCurrentBoundaryPathIndex + 1);
+ bExecutingGroupCode = aBoundaryPathData.back().EvaluateGroup(rDGR);
+ }
+ }
+ if ( !bExecutingGroupCode )
+ DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+ break;
+ }
+}
+
+//--------------------------DXFVertexEntity-------------------------------------
+
+DXFVertexEntity::DXFVertexEntity() : DXFBasicEntity(DXF_VERTEX)
+{
+ fSWidth=-1.0;
+ fEWidth=-1.0;
+ fBulge=0.0;
+ nFlags=0;
+ fCFTDir=0.0;
+
+}
+
+void DXFVertexEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG()) {
+ case 10: aP0.fx=rDGR.GetF(); break;
+ case 20: aP0.fy=rDGR.GetF(); break;
+ case 30: aP0.fz=rDGR.GetF(); break;
+ case 40: fSWidth=rDGR.GetF(); break;
+ case 41: fEWidth=rDGR.GetF(); break;
+ case 42: fBulge=rDGR.GetF(); break;
+ case 70: nFlags=rDGR.GetI(); break;
+ case 50: fCFTDir=rDGR.GetF(); break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+//--------------------------DXFSeqEndEntity-------------------------------------
+
+DXFSeqEndEntity::DXFSeqEndEntity() : DXFBasicEntity(DXF_SEQEND)
+{
+}
+
+//--------------------------DXF3DFace-------------------------------------------
+
+DXF3DFaceEntity::DXF3DFaceEntity() : DXFBasicEntity(DXF_3DFACE)
+{
+ nIEFlags=0;
+}
+
+void DXF3DFaceEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG()) {
+ case 10: aP0.fx=rDGR.GetF(); break;
+ case 20: aP0.fy=rDGR.GetF(); break;
+ case 30: aP0.fz=rDGR.GetF(); break;
+ case 11: aP1.fx=rDGR.GetF(); break;
+ case 21: aP1.fy=rDGR.GetF(); break;
+ case 31: aP1.fz=rDGR.GetF(); break;
+ case 12: aP2.fx=rDGR.GetF(); break;
+ case 22: aP2.fy=rDGR.GetF(); break;
+ case 32: aP2.fz=rDGR.GetF(); break;
+ case 13: aP3.fx=rDGR.GetF(); break;
+ case 23: aP3.fy=rDGR.GetF(); break;
+ case 33: aP3.fz=rDGR.GetF(); break;
+ case 70: nIEFlags=rDGR.GetI(); break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+
+//--------------------------DXFDimensionEntity----------------------------------
+
+DXFDimensionEntity::DXFDimensionEntity() : DXFBasicEntity(DXF_DIMENSION)
+{
+}
+
+void DXFDimensionEntity::EvaluateGroup(DXFGroupReader & rDGR)
+{
+ switch (rDGR.GetG()) {
+ case 2: m_sPseudoBlock = rDGR.GetS(); break;
+ default: DXFBasicEntity::EvaluateGroup(rDGR);
+ }
+}
+
+//---------------------------- DXFEntities --------------------------------------
+
+void DXFEntities::Read(DXFGroupReader & rDGR)
+{
+ DXFBasicEntity * pE, * * ppSucc;
+
+ ppSucc=&pFirst;
+ while (*ppSucc!=nullptr) ppSucc=&((*ppSucc)->pSucc);
+
+ while (rDGR.GetG()!=0) rDGR.Read();
+
+ while (rDGR.GetS()!="ENDBLK" &&
+ rDGR.GetS()!="ENDSEC" &&
+ rDGR.GetS()!="EOF" )
+ {
+
+ if (rDGR.GetS() == "LINE" ) pE=new DXFLineEntity;
+ else if (rDGR.GetS() == "POINT" ) pE=new DXFPointEntity;
+ else if (rDGR.GetS() == "CIRCLE" ) pE=new DXFCircleEntity;
+ else if (rDGR.GetS() == "ARC" ) pE=new DXFArcEntity;
+ else if (rDGR.GetS() == "TRACE" ) pE=new DXFTraceEntity;
+ else if (rDGR.GetS() == "SOLID" ) pE=new DXFSolidEntity;
+ else if (rDGR.GetS() == "TEXT" ) pE=new DXFTextEntity;
+ else if (rDGR.GetS() == "SHAPE" ) pE=new DXFShapeEntity;
+ else if (rDGR.GetS() == "INSERT" ) pE=new DXFInsertEntity;
+ else if (rDGR.GetS() == "ATTDEF" ) pE=new DXFAttDefEntity;
+ else if (rDGR.GetS() == "ATTRIB" ) pE=new DXFAttribEntity;
+ else if (rDGR.GetS() == "POLYLINE" ) pE=new DXFPolyLineEntity;
+ else if (rDGR.GetS() == "LWPOLYLINE") pE=new DXFLWPolyLineEntity;
+ else if (rDGR.GetS() == "VERTEX" ) pE=new DXFVertexEntity;
+ else if (rDGR.GetS() == "SEQEND" ) pE=new DXFSeqEndEntity;
+ else if (rDGR.GetS() == "3DFACE" ) pE=new DXF3DFaceEntity;
+ else if (rDGR.GetS() == "DIMENSION" ) pE=new DXFDimensionEntity;
+ else if (rDGR.GetS() == "HATCH" ) pE=new DXFHatchEntity;
+ else
+ {
+ do {
+ rDGR.Read();
+ } while (rDGR.GetG()!=0);
+ continue;
+ }
+ *ppSucc=pE;
+ ppSucc=&(pE->pSucc);
+ pE->Read(rDGR);
+ }
+}
+
+void DXFEntities::Clear()
+{
+ DXFBasicEntity * ptmp;
+
+ while (pFirst!=nullptr) {
+ ptmp=pFirst;
+ pFirst=ptmp->pSucc;
+ delete ptmp;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/idxf/dxfentrd.hxx b/vcl/source/filter/idxf/dxfentrd.hxx
new file mode 100644
index 000000000..85dbf7dd0
--- /dev/null
+++ b/vcl/source/filter/idxf/dxfentrd.hxx
@@ -0,0 +1,538 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFENTRD_HXX
+#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFENTRD_HXX
+
+#include "dxfgrprd.hxx"
+#include "dxfvec.hxx"
+#include <tools/long.hxx>
+
+#include <memory>
+#include <vector>
+
+enum DXFEntityType {
+ DXF_LINE,
+ DXF_POINT,
+ DXF_CIRCLE,
+ DXF_ARC,
+ DXF_TRACE,
+ DXF_SOLID,
+ DXF_TEXT,
+ DXF_SHAPE,
+ DXF_INSERT,
+ DXF_ATTDEF,
+ DXF_ATTRIB,
+ DXF_POLYLINE,
+ DXF_VERTEX,
+ DXF_SEQEND,
+ DXF_3DFACE,
+ DXF_DIMENSION,
+ DXF_LWPOLYLINE,
+ DXF_HATCH
+};
+
+// base class of an entity
+
+class DXFBasicEntity {
+
+public:
+
+ DXFBasicEntity * pSucc;
+ // pointer to next entity (in the list of DXFEntities.pFirst)
+
+ DXFEntityType eType;
+ // entity kind (line or circle or what)
+
+ // properties that all entities have, each
+ // commented with group codes:
+ OString m_sLayer; // 8
+ OString m_sLineType; // 6
+ double fThickness; // 39
+ tools::Long nColor; // 62
+ tools::Long nSpace; // 67
+ DXFVector aExtrusion; // 210,220,230
+
+protected:
+
+ DXFBasicEntity(DXFEntityType eThisType);
+ // always initialize the constructors of entities with default values
+
+public:
+
+ virtual ~DXFBasicEntity();
+ void Read(DXFGroupReader & rDGR);
+ // Reads a parameter till the next 0-group
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR);
+ // This method will be called by Read() for every parameter (respectively
+ // for every group).
+ // As far as the group code of the entity is known, the corresponding
+ // parameter is fetched.
+
+};
+
+
+// the different kinds of entities
+
+class DXFLineEntity : public DXFBasicEntity {
+
+public:
+
+ DXFVector aP0; // 10,20,30
+ DXFVector aP1; // 11,21,31
+
+ DXFLineEntity();
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR) override;
+};
+
+class DXFPointEntity : public DXFBasicEntity {
+
+public:
+
+ DXFVector aP0; // 10,20,30
+
+ DXFPointEntity();
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR) override;
+};
+
+class DXFCircleEntity : public DXFBasicEntity {
+
+public:
+
+ DXFVector aP0; // 10,20,30
+ double fRadius; // 40
+
+ DXFCircleEntity();
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR) override;
+};
+
+class DXFArcEntity : public DXFBasicEntity {
+
+public:
+
+ DXFVector aP0; // 10,20,30
+ double fRadius; // 40
+ double fStart; // 50
+ double fEnd; // 51
+
+ DXFArcEntity();
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR) override;
+};
+
+class DXFTraceEntity : public DXFBasicEntity {
+
+public:
+
+ DXFVector aP0; // 10,20,30
+ DXFVector aP1; // 11,21,31
+ DXFVector aP2; // 12,22,32
+ DXFVector aP3; // 13,23,33
+
+ DXFTraceEntity();
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR) override;
+};
+
+class DXFSolidEntity : public DXFBasicEntity {
+
+public:
+
+ DXFVector aP0; // 10,20,30
+ DXFVector aP1; // 11,21,31
+ DXFVector aP2; // 12,22,32
+ DXFVector aP3; // 13,23,33
+
+ DXFSolidEntity();
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR) override;
+};
+
+class DXFTextEntity : public DXFBasicEntity {
+
+public:
+
+ DXFVector aP0; // 10,20,30
+ double fHeight; // 40
+ OString m_sText; // 1
+ double fRotAngle; // 50
+ double fXScale; // 41
+ double fOblAngle; // 42
+ OString m_sStyle; // 7
+ tools::Long nGenFlags; // 71
+ tools::Long nHorzJust; // 72
+ tools::Long nVertJust; // 73
+ DXFVector aAlign; // 11,21,31
+
+ DXFTextEntity();
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR) override;
+};
+
+class DXFShapeEntity : public DXFBasicEntity {
+
+ DXFVector aP0; // 10,20,30
+ double fSize; // 40
+ OString m_sName; // 2
+ double fRotAngle; // 50
+ double fXScale; // 41
+ double fOblAngle; // 51
+
+public:
+
+ DXFShapeEntity();
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR) override;
+};
+
+class DXFInsertEntity : public DXFBasicEntity {
+
+public:
+
+ tools::Long nAttrFlag; // 66
+ OString m_sName; // 2
+ DXFVector aP0; // 10,20,30
+ double fXScale; // 41
+ double fYScale; // 42
+ double fZScale; // 43
+ double fRotAngle; // 50
+ tools::Long nColCount; // 70
+ tools::Long nRowCount; // 71
+ double fColSpace; // 44
+ double fRowSpace; // 45
+
+ DXFInsertEntity();
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR) override;
+};
+
+class DXFAttDefEntity : public DXFBasicEntity {
+
+ DXFVector aP0; // 10,20,30
+ double fHeight; // 40
+ OString m_sDefVal; // 1
+ OString m_sPrompt; // 3
+ OString m_sTagStr; // 2
+ tools::Long nAttrFlags; // 70
+ tools::Long nFieldLen; // 73
+ double fRotAngle; // 50
+ double fXScale; // 41
+ double fOblAngle; // 51
+ OString m_sStyle; // 7
+ tools::Long nGenFlags; // 71
+ tools::Long nHorzJust; // 72
+ tools::Long nVertJust; // 74
+ DXFVector aAlign; // 11,21,31
+
+public:
+
+ DXFAttDefEntity();
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR) override;
+};
+
+class DXFAttribEntity : public DXFBasicEntity {
+
+public:
+
+ DXFVector aP0; // 10,20,30
+ double fHeight; // 40
+ OString m_sText; // 1
+ OString m_sTagStr; // 2
+ tools::Long nAttrFlags; // 70
+ tools::Long nFieldLen; // 73
+ double fRotAngle; // 50
+ double fXScale; // 41
+ double fOblAngle; // 51
+ OString m_sStyle; // 7
+ tools::Long nGenFlags; // 71
+ tools::Long nHorzJust; // 72
+ tools::Long nVertJust; // 74
+ DXFVector aAlign; // 11,21,31
+
+ DXFAttribEntity();
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR) override;
+};
+
+class DXFPolyLineEntity : public DXFBasicEntity {
+
+public:
+
+ tools::Long nFlags; // 70
+ double fSWidth; // 40
+ double fEWidth; // 41
+ tools::Long nMeshMCount; // 71
+ tools::Long nMeshNCount; // 72
+ tools::Long nMDensity; // 73
+ tools::Long nNDensity; // 74
+ tools::Long nCSSType; // 75
+
+ DXFPolyLineEntity();
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR) override;
+};
+
+class DXFLWPolyLineEntity : public DXFBasicEntity
+{
+ sal_Int32 nIndex;
+ sal_Int32 nCount; // 90
+
+ public:
+
+ sal_Int32 nFlags; // 70 1 = closed, 128 = plinegen
+ double fConstantWidth; // 43 (optional - default: 0, not used if fStartWidth and/or fEndWidth is used)
+ double fStartWidth; // 40
+ double fEndWidth; // 41
+
+ std::vector<DXFVector> aP;
+
+ DXFLWPolyLineEntity();
+
+ protected:
+
+ virtual void EvaluateGroup( DXFGroupReader & rDGR ) override;
+
+};
+
+struct DXFEdgeType
+{
+ sal_Int32 nEdgeType;
+
+ virtual ~DXFEdgeType(){};
+ virtual bool EvaluateGroup( DXFGroupReader & /*rDGR*/ ){ return true; };
+
+ protected:
+
+ DXFEdgeType( sal_Int32 EdgeType ):nEdgeType(EdgeType){};
+};
+
+struct DXFEdgeTypeLine : public DXFEdgeType
+{
+ DXFVector aStartPoint; // 10,20
+ DXFVector aEndPoint; // 11,21
+ DXFEdgeTypeLine();
+ virtual bool EvaluateGroup( DXFGroupReader & rDGR ) override;
+};
+
+struct DXFEdgeTypeCircularArc : public DXFEdgeType
+{
+ DXFVector aCenter; // 10,20
+ double fRadius; // 40
+ double fStartAngle; // 50
+ double fEndAngle; // 51
+ sal_Int32 nIsCounterClockwiseFlag; // 73
+ DXFEdgeTypeCircularArc();
+ virtual bool EvaluateGroup( DXFGroupReader & rDGR ) override;
+};
+
+struct DXFEdgeTypeEllipticalArc : public DXFEdgeType
+{
+ DXFVector aCenter; // 10,20
+ DXFVector aEndPoint; // 11,21
+ double fLength; // 40
+ double fStartAngle; // 50
+ double fEndAngle; // 51
+ sal_Int32 nIsCounterClockwiseFlag; // 73
+
+ DXFEdgeTypeEllipticalArc();
+ virtual bool EvaluateGroup( DXFGroupReader & rDGR ) override;
+};
+
+struct DXFEdgeTypeSpline : public DXFEdgeType
+{
+ sal_Int32 nDegree; // 94
+ sal_Int32 nRational; // 73
+ sal_Int32 nPeriodic; // 74
+ sal_Int32 nKnotCount; // 75
+ sal_Int32 nControlCount; // 76
+
+ DXFEdgeTypeSpline();
+ virtual bool EvaluateGroup( DXFGroupReader & rDGR ) override;
+};
+
+struct DXFBoundaryPathData
+{
+private:
+ sal_Int32 nPointCount; // 93
+public:
+ sal_Int32 nFlags; // 92
+ sal_Int32 nHasBulgeFlag; // 72
+ sal_Int32 nIsClosedFlag; // 73
+ double fBulge; // 42
+ sal_Int32 nSourceBoundaryObjects; // 97
+ sal_Int32 nEdgeCount; // 93
+
+ bool bIsPolyLine;
+ sal_Int32 nPointIndex;
+
+ std::vector<DXFVector> aP;
+ std::vector<std::unique_ptr<DXFEdgeType>> aEdges;
+
+ DXFBoundaryPathData();
+
+ bool EvaluateGroup( DXFGroupReader & rDGR );
+};
+
+class DXFHatchEntity : public DXFBasicEntity
+{
+ bool bIsInBoundaryPathContext;
+ sal_Int32 nCurrentBoundaryPathIndex;
+
+ public:
+
+ sal_Int32 nFlags; // 70 (solid fill = 1, pattern fill = 0)
+ sal_Int32 nAssociativityFlag; // 71 (associative = 1, non-associative = 0)
+ sal_Int32 nMaxBoundaryPathCount; // 91
+ sal_Int32 nHatchStyle; // 75 (odd parity = 0, outmost area = 1, entire area = 2 )
+ sal_Int32 nHatchPatternType; // 76 (user defined = 0, predefined = 1, custom = 2)
+ double fHatchPatternAngle; // 52 (pattern fill only)
+ double fHatchPatternScale; // 41 (pattern fill only:scale or spacing)
+ sal_Int32 nHatchDoubleFlag; // 77 (pattern fill only:double = 1, not double = 0)
+ sal_Int32 nHatchPatternDefinitionLines; // 78
+ double fPixelSize; // 47
+ sal_Int32 nNumberOfSeedPoints; // 98
+
+ std::vector<DXFBoundaryPathData> aBoundaryPathData;
+
+ DXFHatchEntity();
+
+ protected:
+
+ virtual void EvaluateGroup( DXFGroupReader & rDGR ) override;
+};
+
+class DXFVertexEntity : public DXFBasicEntity {
+
+public:
+
+ DXFVector aP0; // 10,20,30
+ double fSWidth; // 40 (if <0.0, then one has DXFPolyLine::fSWidth)
+ double fEWidth; // 41 (if <0.0, then one has DXFPolyLine::fEWidth)
+ double fBulge; // 42
+ tools::Long nFlags; // 70
+ double fCFTDir; // 50
+
+ DXFVertexEntity();
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR) override;
+};
+
+class DXFSeqEndEntity : public DXFBasicEntity {
+
+public:
+
+ DXFSeqEndEntity();
+};
+
+class DXF3DFaceEntity : public DXFBasicEntity {
+
+public:
+
+ DXFVector aP0; // 10,20,30
+ DXFVector aP1; // 11,21,31
+ DXFVector aP2; // 12,22,32
+ DXFVector aP3; // 13,23,33
+ tools::Long nIEFlags; // 70
+
+ DXF3DFaceEntity();
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR) override;
+};
+
+class DXFDimensionEntity : public DXFBasicEntity {
+
+public:
+
+ OString m_sPseudoBlock; // 2
+
+ DXFDimensionEntity();
+
+protected:
+
+ virtual void EvaluateGroup(DXFGroupReader & rDGR) override;
+};
+
+
+// read and represent the set of entities
+class DXFEntities {
+
+public:
+
+ DXFEntities()
+ : pFirst(nullptr)
+ , mbBeingDrawn(false)
+ {
+ }
+
+ ~DXFEntities()
+ {
+ Clear();
+ }
+
+ DXFBasicEntity * pFirst; // list of entities, READ ONLY!
+ mutable bool mbBeingDrawn; // guard for loop in entity parsing
+
+ void Read(DXFGroupReader & rDGR);
+ // read entities by rGDR of a DXF file until a
+ // ENDBLK, ENDSEC or EOF (of group 0).
+ // (all unknown thing will be skipped)
+
+ void Clear();
+ // deletes all entities
+};
+
+#endif
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/idxf/dxfgrprd.cxx b/vcl/source/filter/idxf/dxfgrprd.cxx
new file mode 100644
index 000000000..48cdb3660
--- /dev/null
+++ b/vcl/source/filter/idxf/dxfgrprd.cxx
@@ -0,0 +1,213 @@
+/* -*- 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 <stdlib.h>
+#include <rtl/strbuf.hxx>
+#include <tools/stream.hxx>
+#include <o3tl/string_view.hxx>
+#include "dxfgrprd.hxx"
+
+// we use an own ReadLine function, because Stream::ReadLine stops if
+// a 0-sign occurs; this function converts 0-signs to blanks and reads
+// a complete line until a cr/lf is found
+
+static OString DXFReadLine(SvStream& rIStm)
+{
+ char buf[256 + 1];
+ bool bEnd = false;
+ sal_uInt64 nOldFilePos = rIStm.Tell();
+ char c = 0;
+
+ OStringBuffer aBuf(512);
+
+ while( !bEnd && !rIStm.GetError() ) // !!! do not check for EOF
+ // !!! because we read blockwise
+ {
+ sal_uInt16 nLen = static_cast<sal_uInt16>(rIStm.ReadBytes(buf, sizeof(buf)-1));
+ if( !nLen )
+ {
+ if( aBuf.isEmpty() )
+ return OString();
+ else
+ break;
+ }
+
+ for( sal_uInt16 n = 0; n < nLen ; n++ )
+ {
+ c = buf[n];
+ if( c != '\n' && c != '\r' )
+ {
+ if( !c )
+ c = ' ';
+ aBuf.append(c);
+ }
+ else
+ {
+ bEnd = true;
+ break;
+ }
+ }
+ }
+
+ if( !bEnd && !rIStm.GetError() && !aBuf.isEmpty() )
+ bEnd = true;
+
+ nOldFilePos += aBuf.getLength();
+ if( rIStm.Tell() > nOldFilePos )
+ nOldFilePos++;
+ rIStm.Seek( nOldFilePos ); // seek because of BlockRead above!
+
+ if( bEnd && (c=='\r' || c=='\n')) // special treatment of DOS files
+ {
+ char cTemp(0);
+ rIStm.ReadBytes(&cTemp, 1);
+ if( cTemp == c || (cTemp != '\n' && cTemp != '\r') )
+ rIStm.Seek( nOldFilePos );
+ }
+
+ return aBuf.makeStringAndClear();
+}
+
+static void DXFSkipLine(SvStream& rIStm)
+{
+ while (rIStm.good())
+ {
+ char buf[256 + 1];
+ sal_uInt16 nLen = static_cast<sal_uInt16>(rIStm.ReadBytes(buf, sizeof(buf) - 1));
+ for (sal_uInt16 n = 0; n < nLen; n++)
+ {
+ char c = buf[n];
+ if ((c == '\n') || (c == '\r'))
+ {
+ rIStm.SeekRel(n-nLen+1); // return stream to next to current position
+ char c1 = 0;
+ rIStm.ReadBytes(&c1, 1);
+ if (c1 == c || (c1 != '\n' && c1!= '\r'))
+ rIStm.SeekRel(-1);
+ return;
+ }
+ }
+ }
+}
+
+DXFGroupReader::DXFGroupReader(SvStream & rIStream)
+ : rIS(rIStream)
+ , bStatus(true)
+ , nLastG(0)
+ , I(0)
+{
+ rIS.Seek(0);
+}
+
+sal_uInt16 DXFGroupReader::Read()
+{
+ sal_uInt16 nG = 0;
+ if ( bStatus )
+ {
+ nG = static_cast<sal_uInt16>(ReadI());
+ if ( bStatus )
+ {
+ if (nG< 10) ReadS();
+ else if (nG< 60) F = ReadF();
+ else if (nG< 80) I = ReadI();
+ else if (nG< 90) DXFSkipLine(rIS);
+ else if (nG< 99) I = ReadI();
+ else if (nG==100) ReadS();
+ else if (nG==102) ReadS();
+ else if (nG==105) DXFSkipLine(rIS);
+ else if (nG< 140) DXFSkipLine(rIS);
+ else if (nG< 148) F = ReadF();
+ else if (nG< 170) DXFSkipLine(rIS);
+ else if (nG< 176) I = ReadI();
+ else if (nG< 180) DXFSkipLine(rIS); // ReadI();
+ else if (nG< 210) DXFSkipLine(rIS);
+ else if (nG< 240) F = ReadF();
+ else if (nG<=369) DXFSkipLine(rIS);
+ else if (nG< 999) DXFSkipLine(rIS);
+ else if (nG<1010) ReadS();
+ else if (nG<1060) F = ReadF();
+ else if (nG<1072) I = ReadI();
+ else bStatus = false;
+ }
+ }
+ if ( !bStatus )
+ {
+ nG = 0;
+ S = "EOF";
+ }
+ nLastG = nG;
+ return nG;
+}
+
+tools::Long DXFGroupReader::ReadI()
+{
+ OString s = DXFReadLine(rIS);
+ char *p=s.pData->buffer;
+ const char *end = s.pData->buffer + s.pData->length;
+
+ while((p != end) && (*p==0x20)) p++;
+
+ if ((p == end) || ((*p<'0' || *p>'9') && *p!='-')) {
+ bStatus=false;
+ return 0;
+ }
+
+ OStringBuffer aNumber;
+ if (*p == '-') {
+ aNumber.append(*p++);
+ }
+
+ while ((p != end) && *p >= '0' && *p <= '9') {
+ aNumber.append(*p++);
+ }
+
+ while ((p != end) && (*p==0x20)) p++;
+ if (p != end) {
+ bStatus=false;
+ return 0;
+ }
+
+ return o3tl::toInt32(aNumber);
+}
+
+double DXFGroupReader::ReadF()
+{
+ OString s = DXFReadLine(rIS);
+ char *p = s.pData->buffer;
+ const char *end = s.pData->buffer + s.pData->length;
+
+ while((p != end) && (*p==0x20)) p++;
+ if ((p == end) || ((*p<'0' || *p>'9') && *p!='.' && *p!='-')) {
+ bStatus=false;
+ return 0.0;
+ }
+ return atof(p);
+}
+
+void DXFGroupReader::ReadS()
+{
+ S = DXFReadLine(rIS);
+}
+
+sal_uInt64 DXFGroupReader::remainingSize() const
+{
+ return rIS.remainingSize();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/idxf/dxfgrprd.hxx b/vcl/source/filter/idxf/dxfgrprd.hxx
new file mode 100644
index 000000000..4d20ae2bf
--- /dev/null
+++ b/vcl/source/filter/idxf/dxfgrprd.hxx
@@ -0,0 +1,115 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFGRPRD_HXX
+#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFGRPRD_HXX
+
+#include <rtl/string.hxx>
+#include <sal/types.h>
+#include <tools/long.hxx>
+
+class SvStream;
+
+class DXFGroupReader
+{
+public:
+ explicit DXFGroupReader( SvStream & rIStream );
+
+ bool GetStatus() const;
+
+ void SetError();
+
+ sal_uInt16 Read();
+ // Reads next group and returns the group code.
+ // In case of an error GetStatus() returns sal_False, group code will be set
+ // to 0 and SetS(0,"EOF") will be executed.
+ bool Read(sal_uInt16 nExpectedG) { return Read() == nExpectedG; }
+
+ sal_uInt16 GetG() const;
+ // Return the last group code (the one the last Read() did return).
+
+ tools::Long GetI() const;
+ // Returns the integer value of the group which was read earlier with Read().
+ // This read must have returned a group code for datatype Integer.
+ // If not 0 is returned
+
+ double GetF() const;
+ // Returns the floating point value of the group which was read earlier with Read().
+ // This read must have returned a group code for datatype Floatingpoint.
+ // If not 0 is returned
+
+ const OString& GetS() const;
+ // Returns the string of the group which was read earlier with Read().
+ // This read must have returned a group code for datatype String.
+ // If not NULL is returned
+
+ sal_uInt64 remainingSize() const;
+private:
+
+ tools::Long ReadI();
+ double ReadF();
+ void ReadS();
+
+ SvStream & rIS;
+ bool bStatus;
+ sal_uInt16 nLastG;
+
+ OString S;
+ union {
+ double F;
+ tools::Long I;
+ };
+};
+
+
+inline bool DXFGroupReader::GetStatus() const
+{
+ return bStatus;
+}
+
+
+inline void DXFGroupReader::SetError()
+{
+ bStatus=false;
+}
+
+inline sal_uInt16 DXFGroupReader::GetG() const
+{
+ return nLastG;
+}
+
+inline tools::Long DXFGroupReader::GetI() const
+{
+ return I;
+}
+
+inline double DXFGroupReader::GetF() const
+{
+ return F;
+}
+
+inline const OString& DXFGroupReader::GetS() const
+{
+ return S;
+}
+
+#endif
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/idxf/dxfreprd.cxx b/vcl/source/filter/idxf/dxfreprd.cxx
new file mode 100644
index 000000000..7124e296e
--- /dev/null
+++ b/vcl/source/filter/idxf/dxfreprd.cxx
@@ -0,0 +1,480 @@
+/* -*- 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 <string_view>
+
+#include "dxfreprd.hxx"
+#include <osl/nlsupport.h>
+#include <unotools/defaultencoding.hxx>
+#include <unotools/wincodepage.hxx>
+
+//------------------DXFBoundingBox--------------------------------------------
+
+
+void DXFBoundingBox::Union(const DXFVector & rVector)
+{
+ if (bEmpty) {
+ fMinX=rVector.fx;
+ fMinY=rVector.fy;
+ fMinZ=rVector.fz;
+ fMaxX=rVector.fx;
+ fMaxY=rVector.fy;
+ fMaxZ=rVector.fz;
+ bEmpty=false;
+ }
+ else {
+ if (fMinX>rVector.fx) fMinX=rVector.fx;
+ if (fMinY>rVector.fy) fMinY=rVector.fy;
+ if (fMinZ>rVector.fz) fMinZ=rVector.fz;
+ if (fMaxX<rVector.fx) fMaxX=rVector.fx;
+ if (fMaxY<rVector.fy) fMaxY=rVector.fy;
+ if (fMaxZ<rVector.fz) fMaxZ=rVector.fz;
+ }
+}
+
+
+//------------------DXFPalette------------------------------------------------
+
+
+DXFPalette::DXFPalette()
+{
+ short i,j,nHue,nNSat,nVal,nC[3],nmax,nmed,nmin;
+ sal_uInt8 nV;
+
+ // colors 0 - 9 (normal colors)
+ SetColor(0, 0x00, 0x00, 0x00); // actually never being used
+ SetColor(1, 0xff, 0x00, 0x00);
+ SetColor(2, 0xff, 0xff, 0x00);
+ SetColor(3, 0x00, 0xff, 0x00);
+ SetColor(4, 0x00, 0xff, 0xff);
+ SetColor(5, 0x00, 0x00, 0xff);
+ SetColor(6, 0xff, 0x00, 0xff);
+ SetColor(7, 0x0f, 0x0f, 0x0f); // actually white???
+ SetColor(8, 0x80, 0x80, 0x80);
+ SetColor(9, 0xc0, 0xc0, 0xc0);
+
+ // colors 10 - 249
+ // (Universal-Palette: 24 hues * 5 lightnesses * 2 saturations )
+ i=10;
+ for (nHue=0; nHue<24; nHue++) {
+ for (nVal=5; nVal>=1; nVal--) {
+ for (nNSat=0; nNSat<2; nNSat++) {
+ nmax=((nHue+3)>>3)%3;
+ j=nHue-(nmax<<3); if (j>4) j=j-24;
+ if (j>=0) {
+ nmed=(nmax+1)%3;
+ nmin=(nmax+2)%3;
+ }
+ else {
+ nmed=(nmax+2)%3;
+ nmin=(nmax+1)%3;
+ j=-j;
+ }
+ nC[nmin]=0;
+ nC[nmed]=255*j/4;
+ nC[nmax]=255;
+ if (nNSat!=0) {
+ for (j=0; j<3; j++) nC[j]=(nC[j]>>1)+128;
+ }
+ for (j=0; j<3; j++) nC[j]=nC[j]*nVal/5;
+ SetColor(static_cast<sal_uInt8>(i++),static_cast<sal_uInt8>(nC[0]),static_cast<sal_uInt8>(nC[1]),static_cast<sal_uInt8>(nC[2]));
+ }
+ }
+ }
+
+ // Farben 250 - 255 (shades of gray)
+ for (i=0; i<6; i++) {
+ nV=static_cast<sal_uInt8>(i*38+65);
+ SetColor(static_cast<sal_uInt8>(250+i),nV,nV,nV);
+ }
+}
+
+
+void DXFPalette::SetColor(sal_uInt8 nIndex, sal_uInt8 nRed, sal_uInt8 nGreen, sal_uInt8 nBlue)
+{
+ pRed[nIndex]=nRed;
+ pGreen[nIndex]=nGreen;
+ pBlue[nIndex]=nBlue;
+}
+
+
+//------------------DXFRepresentation-----------------------------------------
+
+
+DXFRepresentation::DXFRepresentation()
+ : mEnc(RTL_TEXTENCODING_DONTKNOW)
+ , mbInCalc(false)
+{
+ setGlobalLineTypeScale(1.0);
+}
+
+DXFRepresentation::~DXFRepresentation()
+{
+}
+
+rtl_TextEncoding DXFRepresentation::getTextEncoding() const
+{
+ return (isTextEncodingSet()) ?
+ mEnc :
+ osl_getTextEncodingFromLocale(nullptr); // Use default encoding if none specified
+}
+
+bool DXFRepresentation::Read( SvStream & rIStream )
+{
+ bool bRes;
+
+ aTables.Clear();
+ aBlocks.Clear();
+ aEntities.Clear();
+
+ DXFGroupReader DGR( rIStream );
+
+ DGR.Read();
+ while (DGR.GetG()!=0 || (DGR.GetS() != "EOF")) {
+ if (DGR.GetG()==0 && DGR.GetS() == "SECTION") {
+ if (DGR.Read()!=2) {
+ DGR.SetError();
+ break;
+ }
+ if (DGR.GetS() == "HEADER") ReadHeader(DGR);
+ else if (DGR.GetS() == "TABLES") aTables.Read(DGR);
+ else if (DGR.GetS() == "BLOCKS") aBlocks.Read(DGR);
+ else if (DGR.GetS() == "ENTITIES") aEntities.Read(DGR);
+ else DGR.Read();
+ }
+ else DGR.Read();
+ }
+
+ bRes=DGR.GetStatus();
+
+ if (bRes && aBoundingBox.bEmpty)
+ CalcBoundingBox(aEntities,aBoundingBox);
+
+ return bRes;
+}
+
+void DXFRepresentation::ReadHeader(DXFGroupReader & rDGR)
+{
+ while (rDGR.GetG()!=0 || (rDGR.GetS() != "EOF" && rDGR.GetS() != "ENDSEC") )
+ {
+ if (rDGR.GetG()==9) {
+ if (rDGR.GetS() == "$EXTMIN" ||
+ rDGR.GetS() == "$EXTMAX")
+ {
+ DXFVector aVector;
+ while (rDGR.Read()!=9 && rDGR.GetG()!=0) {
+ switch (rDGR.GetG()) {
+ case 10: aVector.fx = rDGR.GetF(); break;
+ case 20: aVector.fy = rDGR.GetF(); break;
+ case 30: aVector.fz = rDGR.GetF(); break;
+ }
+ }
+ aBoundingBox.Union(aVector);
+ }
+ else if (rDGR.GetS() == "$ACADVER")
+ {
+ if (!rDGR.Read(1))
+ continue;
+ // Versions of AutoCAD up to Release 12 (inclusive, AC1009)
+ // were DOS software and used OEM encoding for storing strings.
+ // Release 13 (AC1012) had both DOS and Windows variants.
+ // Its Windows variant, and later releases used ANSI encodings for
+ // strings (up to version 2006, which was the last one to do so).
+ // Later versions (2007+, AC1021+) use UTF-8 for that.
+ // Other (non-Autodesk) implementations may have used different
+ // encodings for storing to corresponding formats, but there's
+ // no way to know that.
+ // See http://autodesk.blogs.com/between_the_lines/autocad-release-history.html
+ if ((rDGR.GetS() <= std::string_view("AC1009")) || (rDGR.GetS() == "AC2.22") || (rDGR.GetS() == "AC2.21") || (rDGR.GetS() == "AC2.10") ||
+ (rDGR.GetS() == "AC1.50") || (rDGR.GetS() == "AC1.40") || (rDGR.GetS() == "AC1.2") || (rDGR.GetS() == "MC0.0"))
+ {
+ // Set OEM encoding for old DOS formats
+ // only if the encoding is not set yet
+ // e.g. by previous $DWGCODEPAGE
+ if (!isTextEncodingSet())
+ setTextEncoding(utl_getWinTextEncodingFromLangStr(
+ utl_getLocaleForGlobalDefaultEncoding(), true));
+ }
+ else if (rDGR.GetS() >= std::string_view("AC1021"))
+ setTextEncoding(RTL_TEXTENCODING_UTF8);
+ else
+ {
+ // Set ANSI encoding for old Windows formats
+ // only if the encoding is not set yet
+ // e.g. by previous $DWGCODEPAGE
+ if (!isTextEncodingSet())
+ setTextEncoding(utl_getWinTextEncodingFromLangStr(
+ utl_getLocaleForGlobalDefaultEncoding()));
+ }
+ }
+ else if (rDGR.GetS() == "$DWGCODEPAGE")
+ {
+ if (!rDGR.Read(3))
+ continue;
+
+ // If we already use UTF8, then don't update encoding anymore
+ if (mEnc == RTL_TEXTENCODING_UTF8)
+ continue;
+ // FIXME: we really need a whole table of
+ // $DWGCODEPAGE to encodings mappings
+ else if ( (rDGR.GetS().equalsIgnoreAsciiCase("ANSI_932")) ||
+ (rDGR.GetS().equalsIgnoreAsciiCase("DOS932")) )
+ {
+ setTextEncoding(RTL_TEXTENCODING_MS_932);
+ }
+ else if (rDGR.GetS().equalsIgnoreAsciiCase("ANSI_936"))
+ {
+ setTextEncoding(RTL_TEXTENCODING_MS_936);
+ }
+ else if (rDGR.GetS().equalsIgnoreAsciiCase("ANSI_949"))
+ {
+ setTextEncoding(RTL_TEXTENCODING_MS_949);
+ }
+ else if (rDGR.GetS().equalsIgnoreAsciiCase("ANSI_950"))
+ {
+ setTextEncoding(RTL_TEXTENCODING_MS_950);
+ }
+ else if (rDGR.GetS().equalsIgnoreAsciiCase("ANSI_1251"))
+ {
+ setTextEncoding(RTL_TEXTENCODING_MS_1251);
+ }
+ }
+ else if (rDGR.GetS() == "$LTSCALE")
+ {
+ if (!rDGR.Read(40))
+ continue;
+ setGlobalLineTypeScale(getGlobalLineTypeScale() * rDGR.GetF());
+ }
+ else rDGR.Read();
+ }
+ else rDGR.Read();
+ }
+}
+
+void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities,
+ DXFBoundingBox & rBox)
+{
+ if (mbInCalc)
+ return;
+ mbInCalc = true;
+
+ DXFBasicEntity * pBE=rEntities.pFirst;
+ while (pBE!=nullptr) {
+ switch (pBE->eType) {
+ case DXF_LINE: {
+ const DXFLineEntity * pE = static_cast<const DXFLineEntity*>(pBE);
+ rBox.Union(pE->aP0);
+ rBox.Union(pE->aP1);
+ break;
+ }
+ case DXF_POINT: {
+ const DXFPointEntity * pE = static_cast<const DXFPointEntity*>(pBE);
+ rBox.Union(pE->aP0);
+ break;
+ }
+ case DXF_CIRCLE: {
+ const DXFCircleEntity * pE = static_cast<const DXFCircleEntity*>(pBE);
+ DXFVector aP;
+ aP=pE->aP0;
+ aP.fx-=pE->fRadius;
+ aP.fy-=pE->fRadius;
+ rBox.Union(aP);
+ aP=pE->aP0;
+ aP.fx+=pE->fRadius;
+ aP.fy+=pE->fRadius;
+ rBox.Union(aP);
+ break;
+ }
+ case DXF_ARC: {
+ const DXFArcEntity * pE = static_cast<const DXFArcEntity*>(pBE);
+ DXFVector aP;
+ aP=pE->aP0;
+ aP.fx-=pE->fRadius;
+ aP.fy-=pE->fRadius;
+ rBox.Union(aP);
+ aP=pE->aP0;
+ aP.fx+=pE->fRadius;
+ aP.fy+=pE->fRadius;
+ rBox.Union(aP);
+ break;
+ }
+ case DXF_TRACE: {
+ const DXFTraceEntity * pE = static_cast<const DXFTraceEntity*>(pBE);
+ rBox.Union(pE->aP0);
+ rBox.Union(pE->aP1);
+ rBox.Union(pE->aP2);
+ rBox.Union(pE->aP3);
+ break;
+ }
+ case DXF_SOLID: {
+ const DXFSolidEntity * pE = static_cast<const DXFSolidEntity*>(pBE);
+ rBox.Union(pE->aP0);
+ rBox.Union(pE->aP1);
+ rBox.Union(pE->aP2);
+ rBox.Union(pE->aP3);
+ break;
+ }
+ case DXF_TEXT: {
+ //const DXFTextEntity * pE = (DXFTextEntity*)pBE;
+ //???
+ break;
+ }
+ case DXF_SHAPE: {
+ //const DXFShapeEntity * pE = (DXFShapeEntity*)pBE;
+ //???
+ break;
+ }
+ case DXF_INSERT: {
+ const DXFInsertEntity * pE = static_cast<const DXFInsertEntity*>(pBE);
+ DXFBlock * pB;
+ DXFBoundingBox aBox;
+ DXFVector aP;
+ pB=aBlocks.Search(pE->m_sName);
+ if (pB==nullptr) break;
+ CalcBoundingBox(*pB,aBox);
+ if (aBox.bEmpty) break;
+ aP.fx=(aBox.fMinX-pB->aBasePoint.fx)*pE->fXScale+pE->aP0.fx;
+ aP.fy=(aBox.fMinY-pB->aBasePoint.fy)*pE->fYScale+pE->aP0.fy;
+ aP.fz=(aBox.fMinZ-pB->aBasePoint.fz)*pE->fZScale+pE->aP0.fz;
+ rBox.Union(aP);
+ aP.fx=(aBox.fMaxX-pB->aBasePoint.fx)*pE->fXScale+pE->aP0.fx;
+ aP.fy=(aBox.fMaxY-pB->aBasePoint.fy)*pE->fYScale+pE->aP0.fy;
+ aP.fz=(aBox.fMaxZ-pB->aBasePoint.fz)*pE->fZScale+pE->aP0.fz;
+ rBox.Union(aP);
+ break;
+ }
+ case DXF_ATTDEF: {
+ //const DXFAttDefEntity * pE = (DXFAttDefEntity*)pBE;
+ //???
+ break;
+ }
+ case DXF_ATTRIB: {
+ //const DXFAttribEntity * pE = (DXFAttribEntity*)pBE;
+ //???
+ break;
+ }
+ case DXF_VERTEX: {
+ const DXFVertexEntity * pE = static_cast<const DXFVertexEntity*>(pBE);
+ rBox.Union(pE->aP0);
+ break;
+ }
+ case DXF_3DFACE: {
+ const DXF3DFaceEntity * pE = static_cast<const DXF3DFaceEntity*>(pBE);
+ rBox.Union(pE->aP0);
+ rBox.Union(pE->aP1);
+ rBox.Union(pE->aP2);
+ rBox.Union(pE->aP3);
+ break;
+ }
+ case DXF_DIMENSION: {
+ const DXFDimensionEntity * pE = static_cast<const DXFDimensionEntity*>(pBE);
+ DXFBlock * pB;
+ DXFBoundingBox aBox;
+ DXFVector aP;
+ pB = aBlocks.Search(pE->m_sPseudoBlock);
+ if (pB==nullptr) break;
+ CalcBoundingBox(*pB,aBox);
+ if (aBox.bEmpty) break;
+ aP.fx=aBox.fMinX-pB->aBasePoint.fx;
+ aP.fy=aBox.fMinY-pB->aBasePoint.fy;
+ aP.fz=aBox.fMinZ-pB->aBasePoint.fz;
+ rBox.Union(aP);
+ aP.fx=aBox.fMaxX-pB->aBasePoint.fx;
+ aP.fy=aBox.fMaxY-pB->aBasePoint.fy;
+ aP.fz=aBox.fMaxZ-pB->aBasePoint.fz;
+ rBox.Union(aP);
+ break;
+ }
+ case DXF_POLYLINE: {
+ //const DXFAttribEntity * pE = (DXFAttribEntity*)pBE;
+ //???
+ break;
+ }
+ case DXF_SEQEND: {
+ //const DXFAttribEntity * pE = (DXFAttribEntity*)pBE;
+ //???
+ break;
+ }
+ case DXF_HATCH :
+ break;
+ case DXF_LWPOLYLINE :
+ break;
+ }
+ pBE=pBE->pSucc;
+ }
+ mbInCalc = false;
+}
+
+namespace {
+ bool lcl_isDec(sal_Unicode ch)
+ {
+ return ch >= L'0' && ch <= L'9';
+ }
+ bool lcl_isHex(sal_Unicode ch)
+ {
+ return lcl_isDec(ch) || (ch >= L'A' && ch <= L'F') || (ch >= L'a' && ch <= L'f');
+ }
+}
+
+OUString DXFRepresentation::ToOUString(std::string_view s) const
+{
+ OUString result = OStringToOUString(s, getTextEncoding(),
+ RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR
+ | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
+ | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR);
+ result = result.replaceAll("%%o", "") // Overscore - simply remove
+ .replaceAll("%%u", "") // Underscore - simply remove
+ .replaceAll("%%d", u"\u00B0") // Degrees symbol (°)
+ .replaceAll("%%p", u"\u00B1") // Tolerance symbol (±)
+ .replaceAll("%%c", u"\u2205") // Diameter symbol
+ .replaceAll("%%%", "%"); // Percent symbol
+
+ sal_Int32 pos = result.indexOf("%%"); // %%nnn, where nnn - 3-digit decimal ASCII code
+ while (pos != -1 && pos <= result.getLength() - 5) {
+ OUString asciiNum = result.copy(pos + 2, 3);
+ if (lcl_isDec(asciiNum[0]) &&
+ lcl_isDec(asciiNum[1]) &&
+ lcl_isDec(asciiNum[2]))
+ {
+ char ch = static_cast<char>(asciiNum.toUInt32());
+ OUString codePt(&ch, 1, mEnc);
+ result = result.replaceAll(result.subView(pos, 5), codePt, pos);
+ }
+ pos = result.indexOf("%%", pos + 1);
+ }
+
+ pos = result.indexOf("\\U+"); // \U+XXXX, where XXXX - 4-digit hex unicode
+ while (pos != -1 && pos <= result.getLength() - 7) {
+ OUString codePtNum = result.copy(pos + 3, 4);
+ if (lcl_isHex(codePtNum[0]) &&
+ lcl_isHex(codePtNum[1]) &&
+ lcl_isHex(codePtNum[2]) &&
+ lcl_isHex(codePtNum[3]))
+ {
+ OUString codePt(static_cast<sal_Unicode>(codePtNum.toUInt32(16)));
+ result = result.replaceAll(result.subView(pos, 7), codePt, pos);
+ }
+ pos = result.indexOf("\\U+", pos + 1);
+ }
+ return result;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/idxf/dxfreprd.hxx b/vcl/source/filter/idxf/dxfreprd.hxx
new file mode 100644
index 000000000..e9c6bc653
--- /dev/null
+++ b/vcl/source/filter/idxf/dxfreprd.hxx
@@ -0,0 +1,129 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFREPRD_HXX
+#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFREPRD_HXX
+
+#include "dxfblkrd.hxx"
+#include "dxftblrd.hxx"
+#include <array>
+#include <string_view>
+
+//--------------------Other stuff---------------------------------------------
+
+
+//-------------------A 3D-Min/Max-Box-----------------------------------------
+
+class DXFBoundingBox {
+public:
+ bool bEmpty;
+ double fMinX;
+ double fMinY;
+ double fMinZ;
+ double fMaxX;
+ double fMaxY;
+ double fMaxZ;
+
+ DXFBoundingBox():bEmpty(true), fMinX(0.0), fMinY(0.0), fMinZ(0.0), fMaxX(0.0), fMaxY(0.0), fMaxZ(0.0) {}
+ void Union(const DXFVector & rVector);
+};
+
+
+//-------------------The (constant) palette for DXF-------------------------
+
+class DXFPalette {
+
+public:
+
+ DXFPalette();
+
+ sal_uInt8 GetRed(sal_uInt8 nIndex) const;
+ sal_uInt8 GetGreen(sal_uInt8 nIndex) const;
+ sal_uInt8 GetBlue(sal_uInt8 nIndex) const;
+
+private:
+ std::array<sal_uInt8, 256> pRed;
+ std::array<sal_uInt8, 256> pGreen;
+ std::array<sal_uInt8, 256> pBlue;
+ void SetColor(sal_uInt8 nIndex, sal_uInt8 nRed, sal_uInt8 nGreen, sal_uInt8 nBlue);
+};
+
+
+//-----------------read and represent DXF file--------------------------------
+
+
+class DXFRepresentation {
+
+public:
+
+ DXFPalette aPalette;
+ // The always equal DXF color palette
+
+ DXFBoundingBox aBoundingBox;
+ // is equal to the AutoCAD variables EXTMIN, EXTMAX if those exist
+ // within the DXF file. Otherwise the BoundingBox gets calculated (in Read())
+
+ DXFTables aTables;
+ // the tables of the DXF file
+
+ DXFBlocks aBlocks;
+ // the blocks of the DXF file
+
+ DXFEntities aEntities;
+ // the entities (from the Entities-Section) of the DXF file
+
+ rtl_TextEncoding mEnc; // $DWGCODEPAGE
+
+ double mfGlobalLineTypeScale; // $LTSCALE
+
+ bool mbInCalc; // guard for self-recursive bounding box calc
+
+ DXFRepresentation();
+ ~DXFRepresentation();
+
+ rtl_TextEncoding getTextEncoding() const;
+ void setTextEncoding(rtl_TextEncoding aEnc) { mEnc = aEnc; }
+ OUString ToOUString(std::string_view s) const;
+
+ double getGlobalLineTypeScale() const { return mfGlobalLineTypeScale; }
+ void setGlobalLineTypeScale(double fGlobalLineTypeScale) { mfGlobalLineTypeScale = fGlobalLineTypeScale; }
+
+ bool Read( SvStream & rIStream );
+ // Reads complete DXF file.
+
+private:
+ void ReadHeader(DXFGroupReader & rDGR);
+ void CalcBoundingBox(const DXFEntities & rEntities,
+ DXFBoundingBox & rBox);
+
+ bool isTextEncodingSet() const { return mEnc != RTL_TEXTENCODING_DONTKNOW; }
+};
+
+
+//-------------------inlines--------------------------------------------------
+
+
+inline sal_uInt8 DXFPalette::GetRed(sal_uInt8 nIndex) const { return pRed[nIndex]; }
+inline sal_uInt8 DXFPalette::GetGreen(sal_uInt8 nIndex) const { return pGreen[nIndex]; }
+inline sal_uInt8 DXFPalette::GetBlue(sal_uInt8 nIndex) const { return pBlue[nIndex]; }
+
+#endif
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/idxf/dxftblrd.cxx b/vcl/source/filter/idxf/dxftblrd.cxx
new file mode 100644
index 000000000..7e20e6c69
--- /dev/null
+++ b/vcl/source/filter/idxf/dxftblrd.cxx
@@ -0,0 +1,381 @@
+/* -*- 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 "dxftblrd.hxx"
+
+//----------------------------------DXFLType-----------------------------------
+
+DXFLType::DXFLType()
+ : pSucc(nullptr)
+ , nFlags(0)
+ , nDashCount(0)
+ , fPatternLength(0.0)
+ , fDash{0.0}
+{
+}
+
+void DXFLType::Read(DXFGroupReader & rDGR)
+{
+ tools::Long nDashIndex=-1;
+
+ while (rDGR.Read()!=0)
+ {
+ switch (rDGR.GetG())
+ {
+ case 2:
+ m_sName = rDGR.GetS();
+ break;
+ case 3:
+ m_sDescription = rDGR.GetS();
+ break;
+ case 70:
+ nFlags=rDGR.GetI();
+ break;
+ case 73:
+ if (nDashIndex!=-1)
+ {
+ rDGR.SetError();
+ return;
+ }
+ nDashCount=rDGR.GetI();
+ if (nDashCount>DXF_MAX_DASH_COUNT)
+ {
+ nDashCount=DXF_MAX_DASH_COUNT;
+ }
+ nDashIndex=0;
+ break;
+ case 40: fPatternLength=rDGR.GetF(); break;
+ case 49:
+ if (nDashCount==-1)
+ {
+ rDGR.SetError();
+ return;
+ }
+ if (nDashIndex < nDashCount)
+ {
+ if (nDashIndex < 0)
+ {
+ rDGR.SetError();
+ return;
+ }
+ fDash[nDashIndex++] = rDGR.GetF();
+ }
+ break;
+ }
+ }
+}
+
+//----------------------------------DXFLayer-----------------------------------
+
+DXFLayer::DXFLayer()
+{
+ pSucc=nullptr;
+ nFlags=0;
+ nColor=-1;
+}
+
+void DXFLayer::Read(DXFGroupReader & rDGR)
+{
+ while (rDGR.Read()!=0)
+ {
+ switch(rDGR.GetG())
+ {
+ case 2:
+ m_sName = rDGR.GetS();
+ break;
+ case 6:
+ m_sLineType = rDGR.GetS();
+ break;
+ case 70:
+ nFlags=rDGR.GetI();
+ break;
+ case 62:
+ nColor=rDGR.GetI();
+ break;
+ }
+ }
+}
+
+//----------------------------------DXFStyle-----------------------------------
+
+DXFStyle::DXFStyle()
+{
+ pSucc=nullptr;
+ nFlags=0;
+ fHeight=0.0;
+ fWidthFak=1.0;
+ fOblAngle=0.0;
+ nTextGenFlags=0;
+ fLastHeightUsed=0.0;
+}
+
+void DXFStyle::Read(DXFGroupReader & rDGR)
+{
+ while (rDGR.Read()!=0)
+ {
+ switch(rDGR.GetG())
+ {
+ case 2:
+ m_sName = rDGR.GetS();
+ break;
+ case 3:
+ m_sPrimFontFile = rDGR.GetS();
+ break;
+ case 4:
+ m_sBigFontFile = rDGR.GetS();
+ break;
+ case 70:
+ nFlags=rDGR.GetI();
+ break;
+ case 40:
+ fHeight=rDGR.GetF();
+ break;
+ case 41:
+ fWidthFak=rDGR.GetF();
+ break;
+ case 42:
+ fLastHeightUsed=rDGR.GetF();
+ break;
+ case 50:
+ fOblAngle=rDGR.GetF();
+ break;
+ case 71:
+ nTextGenFlags=rDGR.GetI();
+ break;
+ }
+ }
+}
+
+//----------------------------------DXFVPort-----------------------------------
+
+DXFVPort::DXFVPort()
+ : pSucc(nullptr)
+ , nFlags(0)
+ , fMinX(0.0)
+ , fMinY(0.0)
+ , fMaxX(0.0)
+ , fMaxY(0.0)
+ , fCenterX(0.0)
+ , fCenterY(0.0)
+ , fSnapBaseX(0.0)
+ , fSnapBaseY(0.0)
+ , fSnapSpacingX(0.0)
+ , fSnapSpacingY(0.0)
+ , fGridX(0.0)
+ , fGridY(0.0)
+ , aDirection(DXFVector(0.0, 0.0, 1.0))
+ , fHeight(0.0)
+ , fAspectRatio(0.0)
+ , fLensLength(0.0)
+ , fFrontClipPlane(0.0)
+ , fBackClipPlane(0.0)
+ , fTwistAngle(0.0)
+ , nStatus(0)
+ , nID(0)
+ , nMode(0)
+ , nCircleZoomPercent(0)
+ , nFastZoom(0)
+ , nUCSICON(0)
+ , nSnap(0)
+ , nGrid(0)
+ , nSnapStyle(0)
+ , nSnapIsopair(0)
+{
+}
+
+void DXFVPort::Read(DXFGroupReader & rDGR)
+{
+ while (rDGR.Read()!=0)
+ {
+ switch(rDGR.GetG())
+ {
+ case 2:
+ m_sName = rDGR.GetS();
+ break;
+ case 10: fMinX=rDGR.GetF(); break;
+ case 11: fMaxX=rDGR.GetF(); break;
+ case 12: fCenterX=rDGR.GetF(); break;
+ case 13: fSnapBaseX=rDGR.GetF(); break;
+ case 14: fSnapSpacingX=rDGR.GetF(); break;
+ case 15: fGridX=rDGR.GetF(); break;
+ case 16: aDirection.fx=rDGR.GetF(); break;
+ case 17: aTarget.fx=rDGR.GetF(); break;
+ case 20: fMinY=rDGR.GetF(); break;
+ case 21: fMaxY=rDGR.GetF(); break;
+ case 22: fCenterY=rDGR.GetF(); break;
+ case 23: fSnapBaseY=rDGR.GetF(); break;
+ case 24: fSnapSpacingY=rDGR.GetF(); break;
+ case 25: fGridY=rDGR.GetF(); break;
+ case 26: aDirection.fy=rDGR.GetF(); break;
+ case 27: aTarget.fy=rDGR.GetF(); break;
+ case 36: aDirection.fz=rDGR.GetF(); break;
+ case 37: aTarget.fz=rDGR.GetF(); break;
+ case 40: fHeight=rDGR.GetF(); break;
+ case 41: fAspectRatio=rDGR.GetF(); break;
+ case 42: fLensLength=rDGR.GetF(); break;
+ case 43: fFrontClipPlane=rDGR.GetF(); break;
+ case 44: fBackClipPlane=rDGR.GetF(); break;
+ case 51: fTwistAngle=rDGR.GetF(); break;
+ case 68: nStatus=rDGR.GetI(); break;
+ case 69: nID=rDGR.GetI(); break;
+ case 70: nFlags=rDGR.GetI(); break;
+ case 71: nMode=rDGR.GetI(); break;
+ case 72: nCircleZoomPercent=rDGR.GetI(); break;
+ case 73: nFastZoom=rDGR.GetI(); break;
+ case 74: nUCSICON=rDGR.GetI(); break;
+ case 75: nSnap=rDGR.GetI(); break;
+ case 76: nGrid=rDGR.GetI(); break;
+ case 77: nSnapStyle=rDGR.GetI(); break;
+ case 78: nSnapIsopair=rDGR.GetI(); break;
+ }
+ }
+}
+
+//----------------------------------DXFTables----------------------------------
+
+
+DXFTables::DXFTables()
+{
+ pLTypes=nullptr;
+ pLayers=nullptr;
+ pStyles=nullptr;
+ pVPorts=nullptr;
+}
+
+
+DXFTables::~DXFTables()
+{
+ Clear();
+}
+
+
+void DXFTables::Read(DXFGroupReader & rDGR)
+{
+ DXFLType * * ppLT, * pLT;
+ DXFLayer * * ppLa, * pLa;
+ DXFStyle * * ppSt, * pSt;
+ DXFVPort * * ppVP, * pVP;
+
+ ppLT=&pLTypes;
+ while(*ppLT!=nullptr) ppLT=&((*ppLT)->pSucc);
+
+ ppLa=&pLayers;
+ while(*ppLa!=nullptr) ppLa=&((*ppLa)->pSucc);
+
+ ppSt=&pStyles;
+ while(*ppSt!=nullptr) ppSt=&((*ppSt)->pSucc);
+
+ ppVP=&pVPorts;
+ while(*ppVP!=nullptr) ppVP=&((*ppVP)->pSucc);
+
+ for (;;) {
+ while (rDGR.GetG()!=0) rDGR.Read();
+ if (rDGR.GetS() == "EOF" ||
+ rDGR.GetS() == "ENDSEC") break;
+ else if (rDGR.GetS() == "LTYPE") {
+ pLT=new DXFLType;
+ pLT->Read(rDGR);
+ *ppLT=pLT;
+ ppLT=&(pLT->pSucc);
+ }
+ else if (rDGR.GetS() == "LAYER") {
+ pLa=new DXFLayer;
+ pLa->Read(rDGR);
+ *ppLa=pLa;
+ ppLa=&(pLa->pSucc);
+ }
+ else if (rDGR.GetS() == "STYLE") {
+ pSt=new DXFStyle;
+ pSt->Read(rDGR);
+ *ppSt=pSt;
+ ppSt=&(pSt->pSucc);
+ }
+ else if (rDGR.GetS() == "VPORT") {
+ pVP=new DXFVPort;
+ pVP->Read(rDGR);
+ *ppVP=pVP;
+ ppVP=&(pVP->pSucc);
+ }
+ else rDGR.Read();
+ }
+}
+
+
+void DXFTables::Clear()
+{
+ DXFLType * pLT;
+ DXFLayer * pLa;
+ DXFStyle * pSt;
+ DXFVPort * pVP;
+
+ while (pStyles!=nullptr) {
+ pSt=pStyles;
+ pStyles=pSt->pSucc;
+ delete pSt;
+ }
+ while (pLayers!=nullptr) {
+ pLa=pLayers;
+ pLayers=pLa->pSucc;
+ delete pLa;
+ }
+ while (pLTypes!=nullptr) {
+ pLT=pLTypes;
+ pLTypes=pLT->pSucc;
+ delete pLT;
+ }
+ while (pVPorts!=nullptr) {
+ pVP=pVPorts;
+ pVPorts=pVP->pSucc;
+ delete pVP;
+ }
+}
+
+
+DXFLType * DXFTables::SearchLType(std::string_view rName) const
+{
+ DXFLType * p;
+ for (p=pLTypes; p!=nullptr; p=p->pSucc) {
+ if (rName == p->m_sName) break;
+ }
+ return p;
+}
+
+
+DXFLayer * DXFTables::SearchLayer(std::string_view rName) const
+{
+ DXFLayer * p;
+ for (p=pLayers; p!=nullptr; p=p->pSucc) {
+ if (rName == p->m_sName) break;
+ }
+ return p;
+}
+
+
+DXFVPort * DXFTables::SearchVPort(std::string_view rName) const
+{
+ DXFVPort * p;
+ for (p=pVPorts; p!=nullptr; p=p->pSucc) {
+ if (rName == p->m_sName) break;
+ }
+ return p;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/idxf/dxftblrd.hxx b/vcl/source/filter/idxf/dxftblrd.hxx
new file mode 100644
index 000000000..f60c0461e
--- /dev/null
+++ b/vcl/source/filter/idxf/dxftblrd.hxx
@@ -0,0 +1,175 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFTBLRD_HXX
+#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFTBLRD_HXX
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include "dxfgrprd.hxx"
+#include "dxfvec.hxx"
+
+
+//------------------- Line Type ----------------------------------------------
+
+
+#define DXF_MAX_DASH_COUNT 32
+
+class DXFLType {
+
+public:
+
+ DXFLType * pSucc;
+
+ OString m_sName; // 2
+ tools::Long nFlags; // 70
+ OString m_sDescription; // 3
+ tools::Long nDashCount; // 73
+ double fPatternLength; // 40
+ double fDash[DXF_MAX_DASH_COUNT]; // 49,49,...
+
+ DXFLType();
+ void Read(DXFGroupReader & rDGR);
+};
+
+
+//------------------ Layer ---------------------------------------------------
+
+
+class DXFLayer {
+
+public:
+
+ DXFLayer * pSucc;
+
+ OString m_sName; // 2
+ tools::Long nFlags; // 70
+ tools::Long nColor; // 62
+ OString m_sLineType; // 6
+
+ DXFLayer();
+ void Read(DXFGroupReader & rDGR);
+};
+
+
+//------------------ Style ---------------------------------------------------
+
+
+class DXFStyle {
+
+public:
+
+ DXFStyle * pSucc;
+
+ OString m_sName; // 2
+ tools::Long nFlags; // 70
+ double fHeight; // 40
+ double fWidthFak; // 41
+ double fOblAngle; // 50
+ tools::Long nTextGenFlags; // 71
+ double fLastHeightUsed; // 42
+ OString m_sPrimFontFile; // 3
+ OString m_sBigFontFile; // 4
+
+ DXFStyle();
+ void Read(DXFGroupReader & rDGR);
+};
+
+
+//------------------ VPort ---------------------------------------------------
+
+
+class DXFVPort {
+
+public:
+
+ DXFVPort * pSucc;
+
+ OString m_sName; // 2
+ tools::Long nFlags; // 70
+ double fMinX; // 10
+ double fMinY; // 20
+ double fMaxX; // 11
+ double fMaxY; // 21
+ double fCenterX; // 12
+ double fCenterY; // 22
+ double fSnapBaseX; // 13
+ double fSnapBaseY; // 23
+ double fSnapSpacingX; // 14
+ double fSnapSpacingY; // 24
+ double fGridX; // 15
+ double fGridY; // 25
+ DXFVector aDirection; // 16,26,36
+ DXFVector aTarget; // 17,27,37
+ double fHeight; // 40
+ double fAspectRatio; // 41
+ double fLensLength; // 42
+ double fFrontClipPlane; // 43
+ double fBackClipPlane; // 44
+ double fTwistAngle; // 51
+ tools::Long nStatus; // 68
+ tools::Long nID; // 69
+ tools::Long nMode; // 71
+ tools::Long nCircleZoomPercent; // 72
+ tools::Long nFastZoom; // 73
+ tools::Long nUCSICON; // 74
+ tools::Long nSnap; // 75
+ tools::Long nGrid; // 76
+ tools::Long nSnapStyle; // 77
+ tools::Long nSnapIsopair; // 78
+
+ DXFVPort();
+ void Read(DXFGroupReader & rDGR);
+};
+
+
+//------------------ Tables --------------------------------------------------
+
+
+class DXFTables {
+
+ DXFLType * pLTypes; // list of line types
+ DXFLayer * pLayers; // list of layers
+ DXFStyle * pStyles; // list of styles
+ DXFVPort * pVPorts; // list of viewports
+
+public:
+
+ DXFTables();
+ ~DXFTables();
+
+ void Read(DXFGroupReader & rDGR);
+ // Reads the table until an ENDSEC or EOF
+ // (Unknown things/tables will be skipped)
+
+ void Clear();
+
+ // look for table entries:
+ DXFLType * SearchLType(std::string_view rName) const;
+ DXFLayer * SearchLayer(std::string_view rName) const;
+ DXFVPort * SearchVPort(std::string_view rName) const;
+
+};
+
+#endif
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/idxf/dxfvec.cxx b/vcl/source/filter/idxf/dxfvec.cxx
new file mode 100644
index 000000000..dc5f39835
--- /dev/null
+++ b/vcl/source/filter/idxf/dxfvec.cxx
@@ -0,0 +1,232 @@
+/* -*- 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 <math.h>
+#include "dxfvec.hxx"
+#include <tools/gen.hxx>
+
+
+//---------------------------- DXFVector ---------------------------------------
+
+
+double DXFVector::Abs() const
+{
+ return sqrt(SProd(*this));
+}
+
+
+DXFVector DXFVector::Unit() const
+{
+ double flen;
+
+ flen=Abs();
+ if (flen!=0) return (*this)*(1.0/flen);
+ else return DXFVector(1.0,0.0,0.0);
+}
+
+
+//---------------------------- DXFTransform ------------------------------------
+
+
+DXFTransform::DXFTransform() :
+ aMX(1.0, 0.0, 0.0),
+ aMY(0.0, 1.0, 0.0),
+ aMZ(0.0, 0.0, 1.0),
+ aMP(0.0, 0.0, 0.0)
+{
+}
+
+
+DXFTransform::DXFTransform(double fScaleX, double fScaleY, double fScaleZ,
+ const DXFVector & rShift) :
+ aMX(fScaleX, 0.0, 0.0),
+ aMY(0.0, fScaleY, 0.0),
+ aMZ(0.0, 0.0, fScaleZ),
+ aMP(rShift)
+{
+}
+
+
+DXFTransform::DXFTransform(double fScaleX, double fScaleY, double fScaleZ,
+ double fRotAngle,
+ const DXFVector & rShift) :
+ aMX(0.0, 0.0, 0.0),
+ aMY(0.0, 0.0, 0.0),
+ aMZ(0.0, 0.0, fScaleZ),
+ aMP(rShift)
+{
+ aMX.fx=cos(basegfx::deg2rad(fRotAngle));
+ aMX.fy=sin(basegfx::deg2rad(fRotAngle));
+ aMY.fx=-aMX.fy;
+ aMY.fy=aMX.fx;
+ aMX*=fScaleX;
+ aMY*=fScaleY;
+}
+
+
+DXFTransform::DXFTransform(const DXFVector & rExtrusion) :
+ aMP(0.0, 0.0, 0.0)
+{
+ // 'Arbitrary Axis Algorithm' (cf. DXF documentation by Autodesk)
+ if ( fabs(rExtrusion.fx) < 1.0/64.0 && fabs(rExtrusion.fy) < 1.0/64.0) {
+ aMX = DXFVector(0.0, 1.0, 0.0) * rExtrusion;
+ }
+ else {
+ aMX = DXFVector(0.0, 0.0, 1.0) * rExtrusion;
+ }
+ aMX=aMX.Unit();
+ aMY=(rExtrusion*aMX).Unit();
+ aMZ=rExtrusion.Unit();
+}
+
+
+DXFTransform::DXFTransform(const DXFVector & rViewDir, const DXFVector & rViewTarget)
+{
+ DXFVector aV;
+
+ aV=rViewDir.Unit();
+ aMX.fz=aV.fx;
+ aMY.fz=aV.fy;
+ aMZ.fz=aV.fz;
+
+ aMZ.fx=0;
+ if (aV.fx==0) aMY.fx=0; else aMY.fx=sqrt(1/(1+aV.fy*aV.fy/(aV.fx*aV.fx)));
+ aMX.fx=sqrt(1-aMY.fx*aMY.fx);
+ if (aV.fx*aV.fy*aMY.fx>0) aMX.fx=-aMX.fx;
+
+ aV=aV*DXFVector(aMX.fx,aMY.fx,aMZ.fx);
+ aMX.fy=aV.fx;
+ aMY.fy=aV.fy;
+ aMZ.fy=aV.fz;
+
+ if (aMZ.fy<0) {
+ aMX.fy=-aMX.fy;
+ aMY.fy=-aMY.fy;
+ aMZ.fy=-aMZ.fy;
+ aMX.fx=-aMX.fx;
+ aMY.fx=-aMY.fx;
+ }
+
+ aV=DXFVector(0,0,0)-rViewTarget;
+ aMP.fx = aV.fx * aMX.fx + aV.fy * aMY.fx + aV.fz * aMZ.fx;
+ aMP.fy = aV.fx * aMX.fy + aV.fy * aMY.fy + aV.fz * aMZ.fy;
+ aMP.fz = aV.fx * aMX.fz + aV.fy * aMY.fz + aV.fz * aMZ.fz;
+}
+
+
+DXFTransform::DXFTransform(const DXFTransform & rT1, const DXFTransform & rT2)
+{
+ rT2.TransDir(rT1.aMX,aMX);
+ rT2.TransDir(rT1.aMY,aMY);
+ rT2.TransDir(rT1.aMZ,aMZ);
+ rT2.Transform(rT1.aMP,aMP);
+}
+
+
+void DXFTransform::Transform(const DXFVector & rSrc, DXFVector & rTgt) const
+{
+ rTgt.fx = rSrc.fx * aMX.fx + rSrc.fy * aMY.fx + rSrc.fz * aMZ.fx + aMP.fx;
+ rTgt.fy = rSrc.fx * aMX.fy + rSrc.fy * aMY.fy + rSrc.fz * aMZ.fy + aMP.fy;
+ rTgt.fz = rSrc.fx * aMX.fz + rSrc.fy * aMY.fz + rSrc.fz * aMZ.fz + aMP.fz;
+}
+
+
+void DXFTransform::Transform(const DXFVector & rSrc, Point & rTgt) const
+{
+ rTgt.setX(static_cast<tools::Long>( rSrc.fx * aMX.fx + rSrc.fy * aMY.fx + rSrc.fz * aMZ.fx + aMP.fx + 0.5 ) );
+ rTgt.setY(static_cast<tools::Long>( rSrc.fx * aMX.fy + rSrc.fy * aMY.fy + rSrc.fz * aMZ.fy + aMP.fy + 0.5 ) );
+}
+
+
+void DXFTransform::TransDir(const DXFVector & rSrc, DXFVector & rTgt) const
+{
+ rTgt.fx = rSrc.fx * aMX.fx + rSrc.fy * aMY.fx + rSrc.fz * aMZ.fx;
+ rTgt.fy = rSrc.fx * aMX.fy + rSrc.fy * aMY.fy + rSrc.fz * aMZ.fy;
+ rTgt.fz = rSrc.fx * aMX.fz + rSrc.fy * aMY.fz + rSrc.fz * aMZ.fz;
+}
+
+
+bool DXFTransform::TransCircleToEllipse(double fRadius, double & rEx, double & rEy) const
+{
+ double fMXAbs=aMX.Abs();
+ double fMYAbs=aMY.Abs();
+ double fNearNull=(fMXAbs+fMYAbs)*0.001;
+
+ if (fabs(aMX.fy)<=fNearNull && fabs(aMX.fz)<=fNearNull &&
+ fabs(aMY.fx)<=fNearNull && fabs(aMY.fz)<=fNearNull)
+ {
+ rEx=fabs(aMX.fx*fRadius);
+ rEy=fabs(aMY.fy*fRadius);
+ return true;
+ }
+ else if (fabs(aMX.fx)<=fNearNull && fabs(aMX.fz)<=fNearNull &&
+ fabs(aMY.fy)<=fNearNull && fabs(aMY.fz)<=fNearNull)
+ {
+ rEx=fabs(aMY.fx*fRadius);
+ rEy=fabs(aMX.fy*fRadius);
+ return true;
+ }
+ else if (fabs(fMXAbs-fMYAbs)<=fNearNull &&
+ fabs(aMX.fz)<=fNearNull && fabs(aMY.fz)<=fNearNull)
+ {
+ rEx=rEy=fabs(((fMXAbs+fMYAbs)/2)*fRadius);
+ return true;
+ }
+ else return false;
+}
+
+LineInfo DXFTransform::Transform(const DXFLineInfo& aDXFLineInfo) const
+{
+ double fex,fey,scale;
+
+ fex=sqrt(aMX.fx*aMX.fx + aMX.fy*aMX.fy);
+ fey=sqrt(aMY.fx*aMY.fx + aMY.fy*aMY.fy);
+ scale = (fex+fey)/2.0;
+
+ LineInfo aLineInfo;
+
+ aLineInfo.SetStyle( aDXFLineInfo.eStyle );
+ aLineInfo.SetWidth( 0 );
+ aLineInfo.SetDashCount( static_cast< sal_uInt16 >( aDXFLineInfo.nDashCount ) );
+ aLineInfo.SetDashLen( aDXFLineInfo.fDashLen * scale );
+ aLineInfo.SetDotCount( static_cast< sal_uInt16 >( aDXFLineInfo.nDotCount ) );
+ aLineInfo.SetDotLen( aDXFLineInfo.fDotLen * scale );
+ aLineInfo.SetDistance( aDXFLineInfo.fDistance * scale );
+
+ if ( aLineInfo.GetDashCount() > 0 && aLineInfo.GetDashLen() == 0 )
+ aLineInfo.SetDashLen(1);
+
+ if ( aLineInfo.GetDotCount() > 0 && aLineInfo.GetDotLen() == 0 )
+ aLineInfo.SetDotLen(1);
+
+ return aLineInfo;
+}
+
+double DXFTransform::CalcRotAngle() const
+{
+ return basegfx::rad2deg(atan2(aMX.fy,aMX.fx));
+}
+
+bool DXFTransform::Mirror() const
+{
+ return aMZ.SProd(aMX*aMY)<0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/idxf/dxfvec.hxx b/vcl/source/filter/idxf/dxfvec.hxx
new file mode 100644
index 000000000..59b6babc2
--- /dev/null
+++ b/vcl/source/filter/idxf/dxfvec.hxx
@@ -0,0 +1,218 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFVEC_HXX
+#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFVEC_HXX
+
+#include <sal/types.h>
+#include <vcl/lineinfo.hxx>
+
+class Point;
+
+class DXFLineInfo {
+public:
+ LineStyle eStyle;
+ sal_Int32 nDashCount;
+ double fDashLen;
+ sal_Int32 nDotCount;
+ double fDotLen;
+ double fDistance;
+
+ DXFLineInfo() :
+ eStyle(LineStyle::Solid),
+ nDashCount(0),
+ fDashLen(0),
+ nDotCount(0),
+ fDotLen(0),
+ fDistance(0) {}
+};
+
+
+//---------------------------- DXFVector ---------------------------------------
+
+// common 3D vector with doubles
+
+class DXFVector {
+
+public:
+
+ double fx,fy,fz; // public ! - why not?
+
+ inline DXFVector(double fX=0.0, double fY=0.0, double fZ=0.0);
+
+ // summation/subtraktion:
+ DXFVector & operator += (const DXFVector & rV);
+ DXFVector operator + (const DXFVector & rV) const;
+ DXFVector operator - (const DXFVector & rV) const;
+
+ // vector product
+ DXFVector operator * (const DXFVector & rV) const;
+
+ // scalar product:
+ double SProd(const DXFVector & rV) const;
+
+ // multiplication with scalar:
+ DXFVector & operator *= (double fs);
+ DXFVector operator * (double fs) const;
+
+ // length:
+ double Abs() const;
+
+ // vector with same direction and a length of 1:
+ DXFVector Unit() const;
+
+ // equivalence or net:
+ bool operator == (const DXFVector & rV) const;
+};
+
+
+//---------------------------- DXFTransform ------------------------------------
+
+// a transformation matrice specialized for our problem
+
+class DXFTransform {
+
+public:
+
+ DXFTransform();
+ // destination coordinate = source coordinate
+
+ DXFTransform(double fScaleX, double fScaleY, double fScaleZ,
+ const DXFVector & rShift);
+ // dest coordinate = translate(scale(source coordinate))
+
+ DXFTransform(double fScaleX, double fScaleY, double fScaleZ,
+ double fRotAngle,
+ const DXFVector & rShift);
+ // dest coordinate = translate(rotate(scale(source coordinate)))
+ // rotation around z-axis, fRotAngle in degrees.
+
+ DXFTransform(const DXFVector & rExtrusion);
+ // Transformation "ECS->WCS" via "Entity Extrusion Direction"
+ // ant the "Arbitrary Axis Algorithm"
+ // (See DXF-Docu from AutoDesk)
+
+ DXFTransform(const DXFVector & rViewDir, const DXFVector & rViewTarget);
+ // Transformation object space->picture space on the basis of direction
+ // destination point of a viewport
+ // (See DXF-Docu from AutoDesk: VPORT)
+
+ DXFTransform(const DXFTransform & rT1, const DXFTransform & rT2);
+ // destination coordinate = rT2(rT1(source coordinate))
+
+
+ void Transform(const DXFVector & rSrc, DXFVector & rTgt) const;
+ // Transformation from DXFVector to DXFVector
+
+ void Transform(const DXFVector & rSrc, Point & rTgt) const;
+ // Transformation from DXFVector to SvPoint
+
+ void TransDir(const DXFVector & rSrc, DXFVector & rTgt) const;
+ // Transformation of a relative vector (so no translation)
+
+ bool TransCircleToEllipse(double fRadius, double & rEx, double & rEy) const;
+ // Attempt to transform a circle (in xy plane) so that it results
+ // in an aligned ellipse. If the does not work because an ellipse of
+ // arbitrary position would be created, sal_False is returned.
+ // (The center point will not be transformed, use Transform(..))
+
+ double CalcRotAngle() const;
+ // Calculates the rotation angle around z-axis (in degrees)
+
+ bool Mirror() const;
+ // Returns sal_True, if the matrice represents a left-handed coordinate system
+
+ LineInfo Transform(const DXFLineInfo& aDXFLineInfo) const;
+ // Transform to LineInfo
+
+private:
+ DXFVector aMX;
+ DXFVector aMY;
+ DXFVector aMZ;
+ DXFVector aMP;
+};
+
+
+//------------------------------- inlines --------------------------------------
+
+
+inline DXFVector::DXFVector(double fX, double fY, double fZ)
+{
+ fx=fX; fy=fY; fz=fZ;
+}
+
+
+inline DXFVector & DXFVector::operator += (const DXFVector & rV)
+{
+ fx+=rV.fx; fy+=rV.fy; fz+=rV.fz;
+ return *this;
+}
+
+
+inline DXFVector DXFVector::operator + (const DXFVector & rV) const
+{
+ return DXFVector(fx+rV.fx, fy+rV.fy, fz+rV.fz);
+}
+
+
+inline DXFVector DXFVector::operator - (const DXFVector & rV) const
+{
+ return DXFVector(fx-rV.fx, fy-rV.fy, fz-rV.fz);
+}
+
+
+inline DXFVector DXFVector::operator * (const DXFVector & rV) const
+{
+ return DXFVector(
+ fy * rV.fz - fz * rV.fy,
+ fz * rV.fx - fx * rV.fz,
+ fx * rV.fy - fy * rV.fx
+ );
+}
+
+
+inline double DXFVector::SProd(const DXFVector & rV) const
+{
+ return fx*rV.fx + fy*rV.fy + fz*rV.fz;
+}
+
+
+inline DXFVector & DXFVector::operator *= (double fs)
+{
+ fx*=fs; fy*=fs; fz*=fs;
+ return *this;
+}
+
+
+inline DXFVector DXFVector::operator * (double fs) const
+{
+ return DXFVector(fx*fs,fy*fs,fz*fs);
+}
+
+
+inline bool DXFVector::operator == (const DXFVector & rV) const
+{
+ if (fx==rV.fx && fy==rV.fy && fz==rV.fz) return true;
+ else return false;
+}
+
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/idxf/idxf.cxx b/vcl/source/filter/idxf/idxf.cxx
new file mode 100644
index 000000000..26d42b10c
--- /dev/null
+++ b/vcl/source/filter/idxf/idxf.cxx
@@ -0,0 +1,43 @@
+/* -*- 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 <filter/DxfReader.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/graph.hxx>
+#include "dxf2mtf.hxx"
+
+//================== GraphicImport - the exported function ================
+
+bool ImportDxfGraphic(SvStream & rStream, Graphic & rGraphic)
+{
+ DXFRepresentation aDXF;
+ DXF2GDIMetaFile aConverter;
+ GDIMetaFile aMTF;
+
+ if ( !aDXF.Read( rStream ) )
+ return false;
+ if ( !aConverter.Convert( aDXF, aMTF, 60, 100 ) )
+ return false;
+ rGraphic = Graphic(aMTF);
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ieps/ieps.cxx b/vcl/source/filter/ieps/ieps.cxx
new file mode 100644
index 000000000..9c223d6cd
--- /dev/null
+++ b/vcl/source/filter/ieps/ieps.cxx
@@ -0,0 +1,825 @@
+/* -*- 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 <filter/EpsReader.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/tempfile.hxx>
+#include <osl/process.h>
+#include <osl/file.hxx>
+#include <osl/thread.h>
+#include <rtl/byteseq.hxx>
+#include <sal/log.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <o3tl/safeint.hxx>
+#include <memory>
+#include <string_view>
+
+class FilterConfigItem;
+
+/*************************************************************************
+|*
+|* ImpSearchEntry()
+|*
+|* Description Checks if there is a string(pDest) of length nSize
+|* inside the memory area pSource which is nComp bytes long.
+|* Check is NON-CASE-SENSITIVE. The return value is the
+|* address where the string is found or NULL
+|*
+*************************************************************************/
+
+static sal_uInt8* ImplSearchEntry( sal_uInt8* pSource, sal_uInt8 const * pDest, size_t nComp, size_t nSize )
+{
+ while ( nComp-- >= nSize )
+ {
+ size_t i;
+ for ( i = 0; i < nSize; i++ )
+ {
+ if ( ( pSource[i]&~0x20 ) != ( pDest[i]&~0x20 ) )
+ break;
+ }
+ if ( i == nSize )
+ return pSource;
+ pSource++;
+ }
+ return nullptr;
+}
+
+
+// SecurityCount is the buffersize of the buffer in which we will parse for a number
+static tools::Long ImplGetNumber(sal_uInt8* &rBuf, sal_uInt32& nSecurityCount)
+{
+ bool bValid = true;
+ bool bNegative = false;
+ tools::Long nRetValue = 0;
+ while (nSecurityCount && (*rBuf == ' ' || *rBuf == 0x9))
+ {
+ ++rBuf;
+ --nSecurityCount;
+ }
+ while ( nSecurityCount && ( *rBuf != ' ' ) && ( *rBuf != 0x9 ) && ( *rBuf != 0xd ) && ( *rBuf != 0xa ) )
+ {
+ switch ( *rBuf )
+ {
+ case '.' :
+ // we'll only use the integer format
+ bValid = false;
+ break;
+ case '-' :
+ bNegative = true;
+ break;
+ default :
+ if ( ( *rBuf < '0' ) || ( *rBuf > '9' ) )
+ nSecurityCount = 1; // error parsing the bounding box values
+ else if ( bValid )
+ {
+ const bool bFail = o3tl::checked_multiply<tools::Long>(nRetValue, 10, nRetValue) ||
+ o3tl::checked_add<tools::Long>(nRetValue, *rBuf - '0', nRetValue);
+ if (bFail)
+ return 0;
+ }
+ break;
+ }
+ nSecurityCount--;
+ ++rBuf;
+ }
+ if ( bNegative )
+ nRetValue = -nRetValue;
+ return nRetValue;
+}
+
+
+static int ImplGetLen( sal_uInt8* pBuf, int nMax )
+{
+ int nLen = 0;
+ while( nLen != nMax )
+ {
+ sal_uInt8 nDat = *pBuf++;
+ if ( nDat == 0x0a || nDat == 0x25 )
+ break;
+ nLen++;
+ }
+ return nLen;
+}
+
+static void MakeAsMeta(Graphic &rGraphic)
+{
+ ScopedVclPtrInstance< VirtualDevice > pVDev;
+ GDIMetaFile aMtf;
+ Size aSize = rGraphic.GetPrefSize();
+
+ if( !aSize.Width() || !aSize.Height() )
+ aSize = Application::GetDefaultDevice()->PixelToLogic(
+ rGraphic.GetSizePixel(), MapMode(MapUnit::Map100thMM));
+ else
+ aSize = OutputDevice::LogicToLogic( aSize,
+ rGraphic.GetPrefMapMode(), MapMode(MapUnit::Map100thMM));
+
+ pVDev->EnableOutput( false );
+ aMtf.Record( pVDev );
+ pVDev->DrawBitmapEx( Point(), aSize, rGraphic.GetBitmapEx() );
+ aMtf.Stop();
+ aMtf.WindStart();
+ aMtf.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ aMtf.SetPrefSize( aSize );
+ rGraphic = aMtf;
+}
+
+static oslProcessError runProcessWithPathSearch(const OUString &rProgName,
+ rtl_uString* pArgs[], sal_uInt32 nArgs, oslProcess *pProcess,
+ oslFileHandle *pIn, oslFileHandle *pOut, oslFileHandle *pErr)
+{
+ oslProcessError result = osl_Process_E_None;
+ oslSecurity pSecurity = osl_getCurrentSecurity();
+#ifdef _WIN32
+ /*
+ * ooo#72096
+ * On Window the underlying SearchPath searches in order of...
+ * The directory from which the application loaded.
+ * The current directory.
+ * The Windows system directory.
+ * The Windows directory.
+ * The directories that are listed in the PATH environment variable.
+ *
+ * Because one of our programs is called "convert" and there is a convert
+ * in the windows system directory, we want to explicitly search the PATH
+ * to avoid picking up on that one if ImageMagick's convert precedes it in
+ * PATH.
+ *
+ */
+ OUString url;
+ OUString path(o3tl::toU(_wgetenv(L"PATH")));
+
+ oslFileError err = osl_searchFileURL(rProgName.pData, path.pData, &url.pData);
+ if (err != osl_File_E_None)
+ result = osl_Process_E_NotFound;
+ else
+ result = osl_executeProcess_WithRedirectedIO(url.pData,
+ pArgs, nArgs, osl_Process_HIDDEN,
+ pSecurity, nullptr, nullptr, 0, pProcess, pIn, pOut, pErr);
+#else
+ result = osl_executeProcess_WithRedirectedIO(rProgName.pData,
+ pArgs, nArgs, osl_Process_SEARCHPATH | osl_Process_HIDDEN,
+ pSecurity, nullptr, nullptr, 0, pProcess, pIn, pOut, pErr);
+#endif
+ osl_freeSecurityHandle( pSecurity );
+ return result;
+}
+
+#if defined(_WIN32)
+# define EXESUFFIX ".exe"
+#else
+# define EXESUFFIX ""
+#endif
+
+static bool RenderAsEMF(const sal_uInt8* pBuf, sal_uInt32 nBytesRead, Graphic &rGraphic)
+{
+ utl::TempFile aTempOutput;
+ utl::TempFile aTempInput;
+ aTempOutput.EnableKillingFile();
+ aTempInput.EnableKillingFile();
+ OUString output;
+ osl::FileBase::getSystemPathFromFileURL(aTempOutput.GetURL(), output);
+ OUString input;
+ osl::FileBase::getSystemPathFromFileURL(aTempInput.GetURL(), input);
+
+ SvStream* pInputStream = aTempInput.GetStream(StreamMode::WRITE);
+ sal_uInt64 nCount = pInputStream->WriteBytes(pBuf, nBytesRead);
+ aTempInput.CloseStream();
+
+ //fdo#64161 pstoedit under non-windows uses libEMF to output the EMF, but
+ //libEMF cannot calculate the bounding box of text, so the overall bounding
+ //box is not increased to include that of any text in the eps
+ //
+ //-drawbb will force pstoedit to draw a pair of pixels with the bg color to
+ //the topleft and bottom right of the bounding box as pstoedit sees it,
+ //which libEMF will then extend its bounding box to fit
+ //
+ //-usebbfrominput forces pstoedit to take the original ps bounding box
+ //as the bounding box as it sees it, instead of calculating its own
+ //which also doesn't work for this example
+ //
+ //Under Linux, positioning of letters within pstoedit is very approximate.
+ //Using the -nfw option delegates the positioning to the reader, and we
+ //will do a proper job. The option is ignored on Windows.
+ OUString arg1("-usebbfrominput"); //-usebbfrominput use the original ps bounding box
+ OUString arg2("-f");
+ OUString arg3("emf:-OO -drawbb -nfw"); //-drawbb mark out the bounding box extent with bg pixels
+ //-nfw delegate letter placement to us
+ rtl_uString *args[] =
+ {
+ arg1.pData, arg2.pData, arg3.pData, input.pData, output.pData
+ };
+ oslProcess aProcess;
+ oslFileHandle pIn = nullptr;
+ oslFileHandle pOut = nullptr;
+ oslFileHandle pErr = nullptr;
+ oslProcessError eErr = runProcessWithPathSearch(
+ "pstoedit" EXESUFFIX,
+ args, SAL_N_ELEMENTS(args),
+ &aProcess, &pIn, &pOut, &pErr);
+
+ if (eErr!=osl_Process_E_None)
+ return false;
+
+ bool bRet = false;
+ if (pIn) osl_closeFile(pIn);
+ osl_joinProcess(aProcess);
+ osl_freeProcessHandle(aProcess);
+ bool bEMFSupported=true;
+ if (pOut)
+ {
+ rtl::ByteSequence seq;
+ if (osl_File_E_None == osl_readLine(pOut, reinterpret_cast<sal_Sequence **>(&seq)))
+ {
+ OString line( reinterpret_cast<const char *>(seq.getConstArray()), seq.getLength() );
+ if (line.startsWith("Unsupported output format"))
+ bEMFSupported=false;
+ }
+ osl_closeFile(pOut);
+ }
+ if (pErr) osl_closeFile(pErr);
+ if (nCount == nBytesRead && bEMFSupported)
+ {
+ SvFileStream aFile(output, StreamMode::READ);
+ if (GraphicConverter::Import(aFile, rGraphic, ConvertDataFormat::EMF) == ERRCODE_NONE)
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+namespace {
+
+struct WriteData
+{
+ oslFileHandle m_pFile;
+ const sal_uInt8 *m_pBuf;
+ sal_uInt32 m_nBytesToWrite;
+};
+
+}
+
+extern "C" {
+
+static void WriteFileInThread(void *wData)
+{
+ sal_uInt64 nCount;
+ WriteData *wdata = static_cast<WriteData *>(wData);
+ osl_writeFile(wdata->m_pFile, wdata->m_pBuf, wdata->m_nBytesToWrite, &nCount);
+ // The number of bytes written does not matter.
+ // The helper process may close its input stream before reading it all.
+ // (e.g. at "showpage" in EPS)
+
+ // File must be closed here.
+ // Otherwise, the helper process may wait for the next input,
+ // then its stdout is not closed and osl_readFile() blocks.
+ if (wdata->m_pFile) osl_closeFile(wdata->m_pFile);
+}
+
+}
+
+static bool RenderAsBMPThroughHelper(const sal_uInt8* pBuf, sal_uInt32 nBytesRead,
+ Graphic& rGraphic,
+ std::initializer_list<std::u16string_view> aProgNames,
+ rtl_uString* pArgs[], size_t nArgs)
+{
+ oslProcess aProcess = nullptr;
+ oslFileHandle pIn = nullptr;
+ oslFileHandle pOut = nullptr;
+ oslFileHandle pErr = nullptr;
+ oslProcessError eErr = osl_Process_E_Unknown;
+ for (const auto& rProgName : aProgNames)
+ {
+ eErr = runProcessWithPathSearch(OUString(rProgName), pArgs, nArgs, &aProcess, &pIn, &pOut, &pErr);
+ if (eErr == osl_Process_E_None)
+ break;
+ }
+ if (eErr!=osl_Process_E_None)
+ return false;
+
+ WriteData Data;
+ Data.m_pFile = pIn;
+ Data.m_pBuf = pBuf;
+ Data.m_nBytesToWrite = nBytesRead;
+ oslThread hThread = osl_createThread(WriteFileInThread, &Data);
+
+ bool bRet = false;
+ sal_uInt64 nCount;
+ {
+ SvMemoryStream aMemStm;
+ sal_uInt8 aBuf[32000];
+ oslFileError eFileErr = osl_readFile(pOut, aBuf, 32000, &nCount);
+ while (eFileErr == osl_File_E_None && nCount)
+ {
+ aMemStm.WriteBytes(aBuf, sal::static_int_cast<std::size_t>(nCount));
+ eFileErr = osl_readFile(pOut, aBuf, 32000, &nCount);
+ }
+
+ aMemStm.Seek(0);
+ if (
+ aMemStm.GetEndOfData() &&
+ GraphicConverter::Import(aMemStm, rGraphic, ConvertDataFormat::BMP) == ERRCODE_NONE
+ )
+ {
+ MakeAsMeta(rGraphic);
+ bRet = true;
+ }
+ }
+ if (pOut) osl_closeFile(pOut);
+ if (pErr) osl_closeFile(pErr);
+ osl_joinProcess(aProcess);
+ osl_freeProcessHandle(aProcess);
+ osl_joinWithThread(hThread);
+ osl_destroyThread(hThread);
+ return bRet;
+}
+
+static bool RenderAsBMPThroughConvert(const sal_uInt8* pBuf, sal_uInt32 nBytesRead,
+ Graphic &rGraphic)
+{
+ // density in pixel/inch
+ OUString arg1("-density");
+ // since the preview is also used for PDF-Export & printing on non-PS-printers,
+ // use some better quality - 300x300 should allow some resizing as well
+ OUString arg2("300x300");
+ // read eps from STDIN
+ OUString arg3("eps:-");
+ // write bmp to STDOUT
+ OUString arg4("bmp:-");
+ rtl_uString *args[] =
+ {
+ arg1.pData, arg2.pData, arg3.pData, arg4.pData
+ };
+ return RenderAsBMPThroughHelper(pBuf, nBytesRead, rGraphic,
+ { u"convert" EXESUFFIX },
+ args,
+ SAL_N_ELEMENTS(args));
+}
+
+static bool RenderAsBMPThroughGS(const sal_uInt8* pBuf, sal_uInt32 nBytesRead,
+ Graphic &rGraphic)
+{
+ OUString arg1("-q");
+ OUString arg2("-dBATCH");
+ OUString arg3("-dNOPAUSE");
+ OUString arg4("-dPARANOIDSAFER");
+ OUString arg5("-dEPSCrop");
+ OUString arg6("-dTextAlphaBits=4");
+ OUString arg7("-dGraphicsAlphaBits=4");
+ OUString arg8("-r300x300");
+ OUString arg9("-sDEVICE=bmp16m");
+ OUString arg10("-sOutputFile=-");
+ OUString arg11("-");
+ rtl_uString *args[] =
+ {
+ arg1.pData, arg2.pData, arg3.pData, arg4.pData, arg5.pData,
+ arg6.pData, arg7.pData, arg8.pData, arg9.pData, arg10.pData,
+ arg11.pData
+ };
+ return RenderAsBMPThroughHelper(pBuf, nBytesRead, rGraphic,
+#ifdef _WIN32
+ // Try both 32-bit and 64-bit ghostscript executable name
+ {
+ u"gswin32c" EXESUFFIX,
+ u"gswin64c" EXESUFFIX,
+ },
+#else
+ { u"gs" EXESUFFIX },
+#endif
+ args,
+ SAL_N_ELEMENTS(args));
+}
+
+static bool RenderAsBMP(const sal_uInt8* pBuf, sal_uInt32 nBytesRead, Graphic &rGraphic)
+{
+ if (RenderAsBMPThroughGS(pBuf, nBytesRead, rGraphic))
+ return true;
+ else
+ return RenderAsBMPThroughConvert(pBuf, nBytesRead, rGraphic);
+}
+
+// this method adds a replacement action containing the original wmf or tiff replacement,
+// so the original eps can be written when storing to ODF.
+static void CreateMtfReplacementAction( GDIMetaFile& rMtf, SvStream& rStrm, sal_uInt32 nOrigPos, sal_uInt32 nPSSize,
+ sal_uInt32 nPosWMF, sal_uInt32 nSizeWMF, sal_uInt32 nPosTIFF, sal_uInt32 nSizeTIFF )
+{
+ OString aComment("EPSReplacementGraphic");
+ if ( nSizeWMF || nSizeTIFF )
+ {
+ std::vector<sal_uInt8> aWMFBuf;
+ if (nSizeWMF && checkSeek(rStrm, nOrigPos + nPosWMF) && rStrm.remainingSize() >= nSizeWMF)
+ {
+ aWMFBuf.resize(nSizeWMF);
+ aWMFBuf.resize(rStrm.ReadBytes(aWMFBuf.data(), nSizeWMF));
+ }
+ nSizeWMF = aWMFBuf.size();
+
+ std::vector<sal_uInt8> aTIFFBuf;
+ if (nSizeTIFF && checkSeek(rStrm, nOrigPos + nPosTIFF) && rStrm.remainingSize() >= nSizeTIFF)
+ {
+ aTIFFBuf.resize(nSizeTIFF);
+ aTIFFBuf.resize(rStrm.ReadBytes(aTIFFBuf.data(), nSizeTIFF));
+ }
+ nSizeTIFF = aTIFFBuf.size();
+
+ SvMemoryStream aReplacement( nSizeWMF + nSizeTIFF + 28 );
+ sal_uInt32 const nMagic = 0xc6d3d0c5;
+ sal_uInt32 nPPos = 28 + nSizeWMF + nSizeTIFF;
+ sal_uInt32 nWPos = nSizeWMF ? 28 : 0;
+ sal_uInt32 nTPos = nSizeTIFF ? 28 + nSizeWMF : 0;
+
+ aReplacement.WriteUInt32( nMagic ).WriteUInt32( nPPos ).WriteUInt32( nPSSize )
+ .WriteUInt32( nWPos ).WriteUInt32( nSizeWMF )
+ .WriteUInt32( nTPos ).WriteUInt32( nSizeTIFF );
+
+ aReplacement.WriteBytes(aWMFBuf.data(), nSizeWMF);
+ aReplacement.WriteBytes(aTIFFBuf.data(), nSizeTIFF);
+ rMtf.AddAction( static_cast<MetaAction*>( new MetaCommentAction( aComment, 0, static_cast<const sal_uInt8*>(aReplacement.GetData()), aReplacement.Tell() ) ) );
+ }
+ else
+ rMtf.AddAction( static_cast<MetaAction*>( new MetaCommentAction( aComment, 0, nullptr, 0 ) ) );
+}
+
+//there is no preview -> make a red box
+static void MakePreview(sal_uInt8* pBuf, sal_uInt32 nBytesRead,
+ tools::Long nWidth, tools::Long nHeight, Graphic &rGraphic)
+{
+ GDIMetaFile aMtf;
+ ScopedVclPtrInstance< VirtualDevice > pVDev;
+ vcl::Font aFont;
+
+ pVDev->EnableOutput( false );
+ aMtf.Record( pVDev );
+ pVDev->SetLineColor( COL_RED );
+ pVDev->SetFillColor();
+
+ aFont.SetColor( COL_LIGHTRED );
+
+ pVDev->Push( vcl::PushFlags::FONT );
+ pVDev->SetFont( aFont );
+
+ tools::Rectangle aRect( Point( 1, 1 ), Size( nWidth - 2, nHeight - 2 ) );
+ pVDev->DrawRect( aRect );
+
+ OUString aString;
+ int nLen;
+ sal_uInt8* pDest = ImplSearchEntry( pBuf, reinterpret_cast<sal_uInt8 const *>("%%Title:"), nBytesRead - 32, 8 );
+ sal_uInt32 nRemainingBytes = pDest ? (nBytesRead - (pDest - pBuf)) : 0;
+ if (nRemainingBytes >= 8)
+ {
+ pDest += 8;
+ nRemainingBytes -= 8;
+ if (nRemainingBytes && *pDest == ' ')
+ {
+ ++pDest;
+ --nRemainingBytes;
+ }
+ nLen = ImplGetLen(pDest, std::min<sal_uInt32>(nRemainingBytes, 32));
+ if (o3tl::make_unsigned(nLen) < nRemainingBytes)
+ {
+ sal_uInt8 aOldValue(pDest[ nLen ]); pDest[ nLen ] = 0;
+ if ( strcmp( reinterpret_cast<char*>(pDest), "none" ) != 0 )
+ {
+ const char* pStr = reinterpret_cast<char*>(pDest);
+ aString += " Title:" + OUString(pStr, strlen(pStr), RTL_TEXTENCODING_ASCII_US) + "\n";
+ }
+ pDest[ nLen ] = aOldValue;
+ }
+ }
+ pDest = ImplSearchEntry( pBuf, reinterpret_cast<sal_uInt8 const *>("%%Creator:"), nBytesRead - 32, 10 );
+ nRemainingBytes = pDest ? (nBytesRead - (pDest - pBuf)) : 0;
+ if (nRemainingBytes >= 10)
+ {
+ pDest += 10;
+ nRemainingBytes -= 10;
+ if (nRemainingBytes && *pDest == ' ')
+ {
+ ++pDest;
+ --nRemainingBytes;
+ }
+ nLen = ImplGetLen(pDest, std::min<sal_uInt32>(nRemainingBytes, 32));
+ if (o3tl::make_unsigned(nLen) < nRemainingBytes)
+ {
+ sal_uInt8 aOldValue(pDest[nLen]); pDest[nLen] = 0;
+ const char* pStr = reinterpret_cast<char*>(pDest);
+ aString += " Creator:" + OUString(pStr, strlen(pStr), RTL_TEXTENCODING_ASCII_US) + "\n";
+ pDest[nLen] = aOldValue;
+ }
+ }
+ pDest = ImplSearchEntry( pBuf, reinterpret_cast<sal_uInt8 const *>("%%CreationDate:"), nBytesRead - 32, 15 );
+ nRemainingBytes = pDest ? (nBytesRead - (pDest - pBuf)) : 0;
+ if (nRemainingBytes >= 15)
+ {
+ pDest += 15;
+ nRemainingBytes -= 15;
+ if (nRemainingBytes && *pDest == ' ')
+ {
+ ++pDest;
+ --nRemainingBytes;
+ }
+ nLen = ImplGetLen(pDest, std::min<sal_uInt32>(nRemainingBytes, 32));
+ if (o3tl::make_unsigned(nLen) < nRemainingBytes)
+ {
+ sal_uInt8 aOldValue(pDest[ nLen ]); pDest[ nLen ] = 0;
+ if ( strcmp( reinterpret_cast<char*>(pDest), "none" ) != 0 )
+ {
+ aString += " CreationDate:" + OUString::createFromAscii( reinterpret_cast<char*>(pDest) ) + "\n";
+ const char* pStr = reinterpret_cast<char*>(pDest);
+ aString += " CreationDate:" + OUString(pStr, strlen(pStr), RTL_TEXTENCODING_ASCII_US) + "\n";
+ }
+ pDest[ nLen ] = aOldValue;
+ }
+ }
+ pDest = ImplSearchEntry( pBuf, reinterpret_cast<sal_uInt8 const *>("%%LanguageLevel:"), nBytesRead - 4, 16 );
+ nRemainingBytes = pDest ? (nBytesRead - (pDest - pBuf)) : 0;
+ if (nRemainingBytes >= 16)
+ {
+ pDest += 16;
+ nRemainingBytes -= 16;
+ sal_uInt32 nCount = std::min<sal_uInt32>(nRemainingBytes, 4U);
+ sal_uInt32 nNumber = ImplGetNumber(pDest, nCount);
+ if (nCount && nNumber < 10)
+ {
+ aString += " LanguageLevel:" + OUString::number( nNumber );
+ }
+ }
+ pVDev->DrawText( aRect, aString, DrawTextFlags::Clip | DrawTextFlags::MultiLine );
+ pVDev->Pop();
+ aMtf.Stop();
+ aMtf.WindStart();
+ aMtf.SetPrefMapMode(MapMode(MapUnit::MapPoint));
+ aMtf.SetPrefSize( Size( nWidth, nHeight ) );
+ rGraphic = aMtf;
+}
+
+//================== GraphicImport - the exported function ================
+
+
+bool ImportEpsGraphic( SvStream & rStream, Graphic & rGraphic)
+{
+ if ( rStream.GetError() )
+ return false;
+
+ Graphic aGraphic;
+ bool bRetValue = false;
+ bool bHasPreview = false;
+ sal_uInt32 nSignature = 0, nPSStreamPos, nPSSize = 0;
+ sal_uInt32 nSizeWMF = 0;
+ sal_uInt32 nPosWMF = 0;
+ sal_uInt32 nSizeTIFF = 0;
+ sal_uInt32 nPosTIFF = 0;
+
+ auto nOrigPos = nPSStreamPos = rStream.Tell();
+ SvStreamEndian nOldFormat = rStream.GetEndian();
+
+ rStream.SetEndian( SvStreamEndian::LITTLE );
+ rStream.ReadUInt32( nSignature );
+ if ( nSignature == 0xc6d3d0c5 )
+ {
+ rStream.ReadUInt32( nPSStreamPos ).ReadUInt32( nPSSize ).ReadUInt32( nPosWMF ).ReadUInt32( nSizeWMF );
+
+ // first we try to get the metafile grafix
+
+ if ( nSizeWMF )
+ {
+ if (nPosWMF && checkSeek(rStream, nOrigPos + nPosWMF))
+ {
+ if (GraphicConverter::Import(rStream, aGraphic, ConvertDataFormat::WMF) == ERRCODE_NONE)
+ bHasPreview = bRetValue = true;
+ }
+ }
+ else
+ {
+ rStream.ReadUInt32( nPosTIFF ).ReadUInt32( nSizeTIFF );
+
+ // else we have to get the tiff grafix
+
+ if (nPosTIFF && nSizeTIFF && checkSeek(rStream, nOrigPos + nPosTIFF))
+ {
+ if ( GraphicConverter::Import( rStream, aGraphic, ConvertDataFormat::TIF ) == ERRCODE_NONE )
+ {
+ MakeAsMeta(aGraphic);
+ rStream.Seek( nOrigPos + nPosTIFF );
+ bHasPreview = bRetValue = true;
+ }
+ }
+ }
+ }
+ else
+ {
+ nPSStreamPos = nOrigPos; // no preview available _>so we must get the size manually
+ nPSSize = rStream.Seek( STREAM_SEEK_TO_END ) - nOrigPos;
+ }
+
+ std::vector<sal_uInt8> aHeader(22, 0);
+ rStream.Seek( nPSStreamPos );
+ rStream.ReadBytes(aHeader.data(), 22); // check PostScript header
+ sal_uInt8* pHeader = aHeader.data();
+ bool bOk = ImplSearchEntry(pHeader, reinterpret_cast<sal_uInt8 const *>("%!PS-Adobe"), 10, 10) &&
+ ImplSearchEntry(pHeader + 15, reinterpret_cast<sal_uInt8 const *>("EPS"), 3, 3);
+ if (bOk)
+ {
+ rStream.Seek(nPSStreamPos);
+ bOk = rStream.remainingSize() >= nPSSize;
+ SAL_WARN_IF(!bOk, "filter.eps", "eps claims to be: " << nPSSize << " in size, but only " << rStream.remainingSize() << " remains");
+ }
+ if (bOk)
+ {
+ std::unique_ptr<sal_uInt8[]> pBuf( new sal_uInt8[ nPSSize ] );
+
+ sal_uInt32 nBufStartPos = rStream.Tell();
+ sal_uInt32 nBytesRead = rStream.ReadBytes(pBuf.get(), nPSSize);
+ if ( nBytesRead == nPSSize )
+ {
+ sal_uInt32 nSecurityCount = 32;
+ // if there is no tiff/wmf preview, we will parse for a preview in
+ // the eps prolog
+ if (!bHasPreview && nBytesRead >= nSecurityCount)
+ {
+ sal_uInt8* pDest = ImplSearchEntry( pBuf.get(), reinterpret_cast<sal_uInt8 const *>("%%BeginPreview:"), nBytesRead - nSecurityCount, 15 );
+ sal_uInt32 nRemainingBytes = pDest ? (nBytesRead - (pDest - pBuf.get())) : 0;
+ if (nRemainingBytes >= 15)
+ {
+ pDest += 15;
+ nSecurityCount = nRemainingBytes - 15;
+ tools::Long nWidth = ImplGetNumber(pDest, nSecurityCount);
+ tools::Long nHeight = ImplGetNumber(pDest, nSecurityCount);
+ tools::Long nBitDepth = ImplGetNumber(pDest, nSecurityCount);
+ tools::Long nScanLines = ImplGetNumber(pDest, nSecurityCount);
+ pDest = ImplSearchEntry(pDest, reinterpret_cast<sal_uInt8 const *>("%"), nSecurityCount, 1); // go to the first Scanline
+ bOk = pDest && nWidth > 0 && nHeight > 0 && ( ( nBitDepth == 1 ) || ( nBitDepth == 8 ) ) && nScanLines;
+ if (bOk)
+ {
+ tools::Long nResult;
+ bOk = !o3tl::checked_multiply(nWidth, nHeight, nResult) && nResult <= SAL_MAX_INT32/2/3;
+ }
+ if (bOk)
+ {
+ rStream.Seek( nBufStartPos + ( pDest - pBuf.get() ) );
+
+ vcl::bitmap::RawBitmap aBitmap( Size( nWidth, nHeight ), 24 );
+ {
+ bool bIsValid = true;
+ sal_uInt8 nDat = 0;
+ char nByte;
+ for (tools::Long y = 0; bIsValid && y < nHeight; ++y)
+ {
+ int nBitsLeft = 0;
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ if ( --nBitsLeft < 0 )
+ {
+ while ( bIsValid && ( nBitsLeft != 7 ) )
+ {
+ rStream.ReadChar(nByte);
+ bIsValid = rStream.good();
+ if (!bIsValid)
+ break;
+ switch (nByte)
+ {
+ case 0x0a :
+ if ( --nScanLines < 0 )
+ bIsValid = false;
+ break;
+ case 0x09 :
+ case 0x0d :
+ case 0x20 :
+ case 0x25 :
+ break;
+ default:
+ {
+ if ( nByte >= '0' )
+ {
+ if ( nByte > '9' )
+ {
+ nByte &=~0x20; // case none sensitive for hexadecimal values
+ nByte -= ( 'A' - 10 );
+ if ( nByte > 15 )
+ bIsValid = false;
+ }
+ else
+ nByte -= '0';
+ nBitsLeft += 4;
+ nDat <<= 4;
+ nDat |= ( nByte ^ 0xf ); // in epsi a zero bit represents white color
+ }
+ else
+ bIsValid = false;
+ }
+ break;
+ }
+ }
+ }
+ if (!bIsValid)
+ break;
+ if ( nBitDepth == 1 )
+ aBitmap.SetPixel( y, x, Color(ColorTransparency, static_cast<sal_uInt8>(nDat >> nBitsLeft) & 1) );
+ else
+ {
+ aBitmap.SetPixel( y, x, nDat ? COL_WHITE : COL_BLACK ); // nBitDepth == 8
+ nBitsLeft = 0;
+ }
+ }
+ }
+ if (bIsValid)
+ {
+ ScopedVclPtrInstance<VirtualDevice> pVDev;
+ GDIMetaFile aMtf;
+ Size aSize( nWidth, nHeight );
+ pVDev->EnableOutput( false );
+ aMtf.Record( pVDev );
+ aSize = OutputDevice::LogicToLogic(aSize, MapMode(), MapMode(MapUnit::Map100thMM));
+ pVDev->DrawBitmapEx( Point(), aSize, vcl::bitmap::CreateFromData(std::move(aBitmap)) );
+ aMtf.Stop();
+ aMtf.WindStart();
+ aMtf.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ aMtf.SetPrefSize( aSize );
+ aGraphic = aMtf;
+ bHasPreview = bRetValue = true;
+ }
+ }
+ }
+ }
+ }
+
+ sal_uInt8* pDest = ImplSearchEntry( pBuf.get(), reinterpret_cast<sal_uInt8 const *>("%%BoundingBox:"), nBytesRead, 14 );
+ sal_uInt32 nRemainingBytes = pDest ? (nBytesRead - (pDest - pBuf.get())) : 0;
+ if (nRemainingBytes >= 14)
+ {
+ pDest += 14;
+ nSecurityCount = std::min<sal_uInt32>(nRemainingBytes - 14, 100);
+ tools::Long nNumb[4];
+ nNumb[0] = nNumb[1] = nNumb[2] = nNumb[3] = 0;
+ for ( int i = 0; ( i < 4 ) && nSecurityCount; i++ )
+ {
+ nNumb[ i ] = ImplGetNumber(pDest, nSecurityCount);
+ }
+ bool bFail = nSecurityCount == 0;
+ tools::Long nWidth(0), nHeight(0);
+ if (!bFail)
+ bFail = o3tl::checked_sub(nNumb[2], nNumb[0], nWidth) || o3tl::checked_add(nWidth, tools::Long(1), nWidth);
+ if (!bFail)
+ bFail = o3tl::checked_sub(nNumb[3], nNumb[1], nHeight) || o3tl::checked_add(nHeight, tools::Long(1), nHeight);
+ if (!bFail && nWidth > 0 && nHeight > 0)
+ {
+ GDIMetaFile aMtf;
+
+ // if there is no preview -> try with gs to make one
+ if (!bHasPreview && !utl::ConfigManager::IsFuzzing())
+ {
+ bHasPreview = RenderAsEMF(pBuf.get(), nBytesRead, aGraphic);
+ if (!bHasPreview)
+ bHasPreview = RenderAsBMP(pBuf.get(), nBytesRead, aGraphic);
+ }
+
+ // if there is no preview -> make a red box
+ if( !bHasPreview )
+ {
+ MakePreview(pBuf.get(), nBytesRead, nWidth, nHeight,
+ aGraphic);
+ }
+
+ GfxLink aGfxLink( std::move(pBuf), nPSSize, GfxLinkType::EpsBuffer ) ;
+ aMtf.AddAction( static_cast<MetaAction*>( new MetaEPSAction( Point(), Size( nWidth, nHeight ),
+ aGfxLink, aGraphic.GetGDIMetaFile() ) ) );
+ CreateMtfReplacementAction( aMtf, rStream, nOrigPos, nPSSize, nPosWMF, nSizeWMF, nPosTIFF, nSizeTIFF );
+ aMtf.WindStart();
+ aMtf.SetPrefMapMode(MapMode(MapUnit::MapPoint));
+ aMtf.SetPrefSize( Size( nWidth, nHeight ) );
+ rGraphic = aMtf;
+ bRetValue = true;
+ }
+ }
+ }
+ }
+
+ rStream.SetEndian(nOldFormat);
+ rStream.Seek( nOrigPos );
+ return bRetValue;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/igif/decode.cxx b/vcl/source/filter/igif/decode.cxx
new file mode 100644
index 000000000..b062593a9
--- /dev/null
+++ b/vcl/source/filter/igif/decode.cxx
@@ -0,0 +1,214 @@
+/* -*- 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 "decode.hxx"
+
+#include <cstdlib>
+#include <cstring>
+
+struct GIFLZWTableEntry
+{
+ GIFLZWTableEntry* pPrev;
+ GIFLZWTableEntry* pFirst;
+ sal_uInt8 nData;
+};
+
+GIFLZWDecompressor::GIFLZWDecompressor(sal_uInt8 cDataSize)
+ : pTable(new GIFLZWTableEntry[4098])
+ , pOutBufData(pOutBuf.data() + 4096)
+ , pBlockBuf(nullptr)
+ , nInputBitsBuf(0)
+ , bEOIFound(false)
+ , nDataSize(cDataSize)
+ , nBlockBufSize(0)
+ , nBlockBufPos(0)
+ , nClearCode(1 << nDataSize)
+ , nEOICode(nClearCode + 1)
+ , nTableSize(nEOICode + 1)
+ , nCodeSize(nDataSize + 1)
+ , nOldCode(0xffff)
+ , nOutBufDataLen(0)
+ , nInputBitsBufSize(0)
+{
+ for (sal_uInt16 i = 0; i < nTableSize; ++i)
+ {
+ pTable[i].pPrev = nullptr;
+ pTable[i].pFirst = &pTable[i];
+ pTable[i].nData = static_cast<sal_uInt8>(i);
+ }
+
+ memset(pTable.get() + nTableSize, 0, sizeof(GIFLZWTableEntry) * (4098 - nTableSize));
+}
+
+GIFLZWDecompressor::~GIFLZWDecompressor()
+{
+}
+
+Scanline GIFLZWDecompressor::DecompressBlock( sal_uInt8* pSrc, sal_uInt8 cBufSize,
+ sal_uLong& rCount, bool& rEOI )
+{
+ sal_uLong nTargetSize = 4096;
+ sal_uLong nCount = 0;
+ sal_uInt8* pTarget = static_cast<sal_uInt8*>(std::malloc( nTargetSize ));
+ sal_uInt8* pTmpTarget = pTarget;
+
+ nBlockBufSize = cBufSize;
+ nBlockBufPos = 0;
+ pBlockBuf = pSrc;
+
+ while (pTarget && ProcessOneCode())
+ {
+ nCount += nOutBufDataLen;
+
+ if( nCount > nTargetSize )
+ {
+ sal_uLong nNewSize = nTargetSize << 1;
+ sal_uLong nOffset = pTmpTarget - pTarget;
+ if (auto p = static_cast<sal_uInt8*>(std::realloc(pTarget, nNewSize)))
+ pTarget = p;
+ else
+ {
+ free(pTarget);
+ pTarget = nullptr;
+ break;
+ }
+
+ nTargetSize = nNewSize;
+ pTmpTarget = pTarget + nOffset;
+ }
+
+ memcpy( pTmpTarget, pOutBufData, nOutBufDataLen );
+ pTmpTarget += nOutBufDataLen;
+ pOutBufData += nOutBufDataLen;
+ nOutBufDataLen = 0;
+
+ if ( bEOIFound )
+ break;
+ }
+
+ rCount = nCount;
+ rEOI = bEOIFound;
+
+ return pTarget;
+}
+
+bool GIFLZWDecompressor::AddToTable( sal_uInt16 nPrevCode, sal_uInt16 nCodeFirstData )
+{
+ if( nTableSize < 4096 )
+ {
+ GIFLZWTableEntry* pE = pTable.get() + nTableSize;
+ pE->pPrev = pTable.get() + nPrevCode;
+ pE->pFirst = pE->pPrev->pFirst;
+ GIFLZWTableEntry *pEntry = pTable[nCodeFirstData].pFirst;
+ if (!pEntry)
+ return false;
+ pE->nData = pEntry->nData;
+ nTableSize++;
+
+ if ( ( nTableSize == static_cast<sal_uInt16>(1 << nCodeSize) ) && ( nTableSize < 4096 ) )
+ nCodeSize++;
+ }
+ return true;
+}
+
+bool GIFLZWDecompressor::ProcessOneCode()
+{
+ bool bRet = false;
+ bool bEndOfBlock = false;
+
+ while( nInputBitsBufSize < nCodeSize )
+ {
+ if( nBlockBufPos >= nBlockBufSize )
+ {
+ bEndOfBlock = true;
+ break;
+ }
+
+ nInputBitsBuf |= static_cast<sal_uLong>(pBlockBuf[ nBlockBufPos++ ]) << nInputBitsBufSize;
+ nInputBitsBufSize += 8;
+ }
+
+ if ( !bEndOfBlock )
+ {
+ // fetch code from input buffer
+ sal_uInt16 nCode = sal::static_int_cast< sal_uInt16 >(
+ static_cast<sal_uInt16>(nInputBitsBuf) & ( ~( 0xffff << nCodeSize ) ));
+ nInputBitsBuf >>= nCodeSize;
+ nInputBitsBufSize = nInputBitsBufSize - nCodeSize;
+
+ if ( nCode < nClearCode )
+ {
+ bool bOk = true;
+ if ( nOldCode != 0xffff )
+ bOk = AddToTable(nOldCode, nCode);
+ if (!bOk)
+ return false;
+ }
+ else if ( ( nCode > nEOICode ) && ( nCode <= nTableSize ) )
+ {
+ if ( nOldCode != 0xffff )
+ {
+ bool bOk;
+ if ( nCode == nTableSize )
+ bOk = AddToTable( nOldCode, nOldCode );
+ else
+ bOk = AddToTable( nOldCode, nCode );
+ if (!bOk)
+ return false;
+ }
+ }
+ else
+ {
+ if ( nCode == nClearCode )
+ {
+ nTableSize = nEOICode + 1;
+ nCodeSize = nDataSize + 1;
+ nOldCode = 0xffff;
+ nOutBufDataLen = 0;
+ }
+ else
+ bEOIFound = true;
+
+ return true;
+ }
+
+ nOldCode = nCode;
+
+ if (nCode >= 4096)
+ return false;
+
+ // write character(/-sequence) of code nCode in the output buffer:
+ GIFLZWTableEntry* pE = pTable.get() + nCode;
+ do
+ {
+ if (pOutBufData == pOutBuf.data()) //can't go back past start
+ return false;
+ nOutBufDataLen++;
+ *(--pOutBufData) = pE->nData;
+ pE = pE->pPrev;
+ }
+ while( pE );
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/igif/decode.hxx b/vcl/source/filter/igif/decode.hxx
new file mode 100644
index 000000000..6e16730a3
--- /dev/null
+++ b/vcl/source/filter/igif/decode.hxx
@@ -0,0 +1,66 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_IGIF_DECODE_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_IGIF_DECODE_HXX
+
+#include <tools/solar.h>
+#include <vcl/Scanline.hxx>
+#include <array>
+#include <memory>
+
+struct GIFLZWTableEntry;
+
+class GIFLZWDecompressor
+{
+ std::unique_ptr<GIFLZWTableEntry[]>
+ pTable;
+ std::array<sal_uInt8, 4096>
+ pOutBuf;
+ sal_uInt8* pOutBufData;
+ sal_uInt8* pBlockBuf;
+ sal_uLong nInputBitsBuf;
+ bool bEOIFound;
+ sal_uInt8 nDataSize;
+ sal_uInt8 nBlockBufSize;
+ sal_uInt8 nBlockBufPos;
+ sal_uInt16 nClearCode;
+ sal_uInt16 nEOICode;
+ sal_uInt16 nTableSize;
+ sal_uInt16 nCodeSize;
+ sal_uInt16 nOldCode;
+ sal_uInt16 nOutBufDataLen;
+ sal_uInt16 nInputBitsBufSize;
+
+ bool AddToTable(sal_uInt16 nPrevCode, sal_uInt16 nCodeFirstData);
+ bool ProcessOneCode();
+
+ GIFLZWDecompressor(const GIFLZWDecompressor&) = delete;
+ GIFLZWDecompressor& operator=(const GIFLZWDecompressor&) = delete;
+public:
+
+ explicit GIFLZWDecompressor( sal_uInt8 cDataSize );
+ ~GIFLZWDecompressor();
+
+ Scanline DecompressBlock( sal_uInt8* pSrc, sal_uInt8 cBufSize, sal_uLong& rCount, bool& rEOI );
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/igif/gifread.cxx b/vcl/source/filter/igif/gifread.cxx
new file mode 100644
index 000000000..fa1270e6a
--- /dev/null
+++ b/vcl/source/filter/igif/gifread.cxx
@@ -0,0 +1,1002 @@
+/* -*- 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/log.hxx>
+#include <tools/stream.hxx>
+#include "decode.hxx"
+#include "gifread.hxx"
+#include <memory>
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <graphic/GraphicReader.hxx>
+
+#define NO_PENDING( rStm ) ( ( rStm ).GetError() != ERRCODE_IO_PENDING )
+
+namespace {
+
+enum GIFAction
+{
+ GLOBAL_HEADER_READING,
+ MARKER_READING,
+ EXTENSION_READING,
+ LOCAL_HEADER_READING,
+ FIRST_BLOCK_READING,
+ NEXT_BLOCK_READING,
+ ABORT_READING,
+ END_READING
+};
+
+enum ReadState
+{
+ GIFREAD_OK,
+ GIFREAD_ERROR,
+ GIFREAD_NEED_MORE
+};
+
+}
+
+class GIFLZWDecompressor;
+
+class SvStream;
+
+namespace {
+
+class GIFReader : public GraphicReader
+{
+ Animation aAnimation;
+ sal_uInt64 nAnimationByteSize;
+ sal_uInt64 nAnimationMinFileData;
+ Bitmap aBmp8;
+ Bitmap aBmp1;
+ BitmapPalette aGPalette;
+ BitmapPalette aLPalette;
+ SvStream& rIStm;
+ std::vector<sal_uInt8> aSrcBuf;
+ std::unique_ptr<GIFLZWDecompressor> pDecomp;
+ BitmapScopedWriteAccess pAcc8;
+ BitmapScopedWriteAccess pAcc1;
+ tools::Long nYAcc;
+ tools::Long nLastPos;
+ sal_uInt64 nMaxStreamData;
+ sal_uInt32 nLogWidth100;
+ sal_uInt32 nLogHeight100;
+ sal_uInt16 nTimer;
+ sal_uInt16 nGlobalWidth; // maximum imagewidth from header
+ sal_uInt16 nGlobalHeight; // maximum imageheight from header
+ sal_uInt16 nImageWidth; // maximum screenwidth from header
+ sal_uInt16 nImageHeight; // maximum screenheight from header
+ sal_uInt16 nImagePosX;
+ sal_uInt16 nImagePosY;
+ sal_uInt16 nImageX; // maximum screenwidth from header
+ sal_uInt16 nImageY; // maximum screenheight from header
+ sal_uInt16 nLastImageY;
+ sal_uInt16 nLastInterCount;
+ sal_uInt16 nLoops;
+ GIFAction eActAction;
+ bool bStatus;
+ bool bGCTransparent; // is the image transparent, if yes:
+ bool bInterlaced;
+ bool bOverreadBlock;
+ bool bImGraphicReady;
+ bool bGlobalPalette;
+ sal_uInt8 nBackgroundColor; // backgroundcolour
+ sal_uInt8 nGCTransparentIndex; // pixels of this index are transparent
+ sal_uInt8 nGCDisposalMethod; // 'Disposal Method' (see GIF docs)
+ sal_uInt8 cTransIndex1;
+ sal_uInt8 cNonTransIndex1;
+
+ void ReadPaletteEntries( BitmapPalette* pPal, sal_uLong nCount );
+ void ClearImageExtensions();
+ void CreateBitmaps( tools::Long nWidth, tools::Long nHeight, BitmapPalette* pPal, bool bWatchForBackgroundColor );
+ bool ReadGlobalHeader();
+ bool ReadExtension();
+ bool ReadLocalHeader();
+ sal_uLong ReadNextBlock();
+ void FillImages( const sal_uInt8* pBytes, sal_uLong nCount );
+ void CreateNewBitmaps();
+ bool ProcessGIF();
+
+public:
+
+ ReadState ReadGIF( Graphic& rGraphic );
+ bool ReadIsAnimated();
+ void GetLogicSize(Size& rLogicSize);
+ Graphic GetIntermediateGraphic();
+
+ explicit GIFReader( SvStream& rStm );
+};
+
+}
+
+GIFReader::GIFReader( SvStream& rStm )
+ : nAnimationByteSize(0)
+ , nAnimationMinFileData(0)
+ , aGPalette ( 256 )
+ , aLPalette ( 256 )
+ , rIStm ( rStm )
+ , nYAcc ( 0 )
+ , nLastPos ( rStm.Tell() )
+ , nMaxStreamData( rStm.remainingSize() )
+ , nLogWidth100 ( 0 )
+ , nLogHeight100 ( 0 )
+ , nGlobalWidth ( 0 )
+ , nGlobalHeight ( 0 )
+ , nImageWidth ( 0 )
+ , nImageHeight ( 0 )
+ , nImagePosX ( 0 )
+ , nImagePosY ( 0 )
+ , nImageX ( 0 )
+ , nImageY ( 0 )
+ , nLastImageY ( 0 )
+ , nLastInterCount ( 0 )
+ , nLoops ( 1 )
+ , eActAction ( GLOBAL_HEADER_READING )
+ , bStatus ( false )
+ , bGCTransparent ( false )
+ , bInterlaced ( false)
+ , bOverreadBlock ( false )
+ , bImGraphicReady ( false )
+ , bGlobalPalette ( false )
+ , nBackgroundColor ( 0 )
+ , nGCTransparentIndex ( 0 )
+ , cTransIndex1 ( 0 )
+ , cNonTransIndex1 ( 0 )
+{
+ maUpperName = "SVIGIF";
+ aSrcBuf.resize(256); // Memory buffer for ReadNextBlock
+ ClearImageExtensions();
+}
+
+void GIFReader::ClearImageExtensions()
+{
+ nGCDisposalMethod = 0;
+ bGCTransparent = false;
+ nTimer = 0;
+}
+
+void GIFReader::CreateBitmaps(tools::Long nWidth, tools::Long nHeight, BitmapPalette* pPal,
+ bool bWatchForBackgroundColor)
+{
+ const Size aSize(nWidth, nHeight);
+
+ sal_uInt64 nCombinedPixSize = nWidth * nHeight;
+ if (bGCTransparent)
+ nCombinedPixSize += (nCombinedPixSize/8);
+
+ // "Overall data compression asymptotically approaches 3839 × 8 / 12 = 2559 1/3"
+ // so assume compression of 1:2560 is possible
+ // (http://cloudinary.com/blog/a_one_color_image_is_worth_two_thousand_words suggests
+ // 1:1472.88 [184.11 x 8] is more realistic)
+
+ sal_uInt64 nMinFileData = nWidth * nHeight / 2560;
+
+ nMinFileData += nAnimationMinFileData;
+ nCombinedPixSize += nAnimationByteSize;
+
+ if (nMaxStreamData < nMinFileData)
+ {
+ //there is nowhere near enough data in this stream to fill the claimed dimensions
+ SAL_WARN("vcl.filter", "in gif frame index " << aAnimation.Count() << " gif claims dimensions " << nWidth << " x " << nHeight <<
+ " but filesize of " << nMaxStreamData << " is surely insufficiently large to fill all frame images");
+ bStatus = false;
+ return;
+ }
+
+ // Don't bother allocating a bitmap of a size that would fail on a
+ // 32-bit system. We have at least one unit tests that is expected
+ // to fail (loading a 65535*65535 size GIF
+ // svtools/qa/cppunit/data/gif/fail/CVE-2008-5937-1.gif), but
+ // which doesn't fail on 64-bit macOS at least. Why the loading
+ // fails on 64-bit Linux, no idea.
+ if (nCombinedPixSize >= SAL_MAX_INT32/3*2)
+ {
+ bStatus = false;
+ return;
+ }
+
+ if (!aSize.Width() || !aSize.Height())
+ {
+ bStatus = false;
+ return;
+ }
+
+ if (bGCTransparent)
+ {
+ const Color aWhite(COL_WHITE);
+
+ aBmp1 = Bitmap(aSize, vcl::PixelFormat::N1_BPP);
+
+ if (!aAnimation.Count())
+ aBmp1.Erase(aWhite);
+
+ pAcc1 = BitmapScopedWriteAccess(aBmp1);
+
+ if (pAcc1)
+ {
+ cTransIndex1 = static_cast<sal_uInt8>(pAcc1->GetBestPaletteIndex(aWhite));
+ cNonTransIndex1 = cTransIndex1 ? 0 : 1;
+ }
+ else
+ {
+ bStatus = false;
+ }
+ }
+
+ if (bStatus)
+ {
+ aBmp8 = Bitmap(aSize, vcl::PixelFormat::N8_BPP, pPal);
+
+ if (!aBmp8.IsEmpty() && bWatchForBackgroundColor && aAnimation.Count())
+ aBmp8.Erase((*pPal)[nBackgroundColor]);
+ else
+ aBmp8.Erase(COL_WHITE);
+
+ pAcc8 = BitmapScopedWriteAccess(aBmp8);
+ bStatus = bool(pAcc8);
+ }
+}
+
+bool GIFReader::ReadGlobalHeader()
+{
+ char pBuf[ 7 ];
+ bool bRet = false;
+
+ auto nRead = rIStm.ReadBytes(pBuf, 6);
+ if (nRead == 6 && NO_PENDING(rIStm))
+ {
+ pBuf[ 6 ] = 0;
+ if( !strcmp( pBuf, "GIF87a" ) || !strcmp( pBuf, "GIF89a" ) )
+ {
+ nRead = rIStm.ReadBytes(pBuf, 7);
+ if (nRead == 7 && NO_PENDING(rIStm))
+ {
+ sal_uInt8 nAspect;
+ sal_uInt8 nRF;
+ SvMemoryStream aMemStm;
+
+ aMemStm.SetBuffer( pBuf, 7, 7 );
+ aMemStm.ReadUInt16( nGlobalWidth );
+ aMemStm.ReadUInt16( nGlobalHeight );
+ aMemStm.ReadUChar( nRF );
+ aMemStm.ReadUChar( nBackgroundColor );
+ aMemStm.ReadUChar( nAspect );
+
+ bGlobalPalette = ( nRF & 0x80 );
+
+ if( bGlobalPalette )
+ ReadPaletteEntries( &aGPalette, sal_uLong(1) << ( ( nRF & 7 ) + 1 ) );
+ else
+ nBackgroundColor = 0;
+
+ if( NO_PENDING( rIStm ) )
+ bRet = true;
+ }
+ }
+ else
+ bStatus = false;
+ }
+
+ return bRet;
+}
+
+void GIFReader::ReadPaletteEntries( BitmapPalette* pPal, sal_uLong nCount )
+{
+ sal_uLong nLen = 3 * nCount;
+ const sal_uInt64 nMaxPossible = rIStm.remainingSize();
+ if (nLen > nMaxPossible)
+ nLen = nMaxPossible;
+ std::unique_ptr<sal_uInt8[]> pBuf(new sal_uInt8[ nLen ]);
+ std::size_t nRead = rIStm.ReadBytes(pBuf.get(), nLen);
+ nCount = nRead/3UL;
+ if( !(NO_PENDING( rIStm )) )
+ return;
+
+ sal_uInt8* pTmp = pBuf.get();
+
+ for (sal_uLong i = 0; i < nCount; ++i)
+ {
+ BitmapColor& rColor = (*pPal)[i];
+
+ rColor.SetRed( *pTmp++ );
+ rColor.SetGreen( *pTmp++ );
+ rColor.SetBlue( *pTmp++ );
+ }
+
+ // if possible accommodate some standard colours
+ if( nCount < 256 )
+ {
+ (*pPal)[ 255UL ] = COL_WHITE;
+
+ if( nCount < 255 )
+ (*pPal)[ 254UL ] = COL_BLACK;
+ }
+}
+
+bool GIFReader::ReadExtension()
+{
+ bool bRet = false;
+
+ // Extension-Label
+ sal_uInt8 cFunction(0);
+ rIStm.ReadUChar( cFunction );
+ if( NO_PENDING( rIStm ) )
+ {
+ bool bOverreadDataBlocks = false;
+ sal_uInt8 cSize(0);
+ // Block length
+ rIStm.ReadUChar( cSize );
+ switch( cFunction )
+ {
+ // 'Graphic Control Extension'
+ case 0xf9 :
+ {
+ sal_uInt8 cFlags(0);
+ rIStm.ReadUChar(cFlags);
+ rIStm.ReadUInt16(nTimer);
+ rIStm.ReadUChar(nGCTransparentIndex);
+ sal_uInt8 cByte(0);
+ rIStm.ReadUChar(cByte);
+
+ if ( NO_PENDING( rIStm ) )
+ {
+ nGCDisposalMethod = ( cFlags >> 2) & 7;
+ bGCTransparent = ( cFlags & 1 );
+ bStatus = ( cSize == 4 ) && ( cByte == 0 );
+ bRet = true;
+ }
+ }
+ break;
+
+ // Application extension
+ case 0xff :
+ {
+ if ( NO_PENDING( rIStm ) )
+ {
+ // by default overread this extension
+ bOverreadDataBlocks = true;
+
+ // Appl. extension has length 11
+ if ( cSize == 0x0b )
+ {
+ OString aAppId = read_uInt8s_ToOString(rIStm, 8);
+ OString aAppCode = read_uInt8s_ToOString(rIStm, 3);
+ rIStm.ReadUChar( cSize );
+
+ // NetScape-Extension
+ if( aAppId == "NETSCAPE" && aAppCode == "2.0" && cSize == 3 )
+ {
+ sal_uInt8 cByte(0);
+ rIStm.ReadUChar( cByte );
+
+ // Loop-Extension
+ if ( cByte == 0x01 )
+ {
+ rIStm.ReadUChar( cByte );
+ nLoops = cByte;
+ rIStm.ReadUChar( cByte );
+ nLoops |= ( static_cast<sal_uInt16>(cByte) << 8 );
+ rIStm.ReadUChar( cByte );
+
+ bStatus = ( cByte == 0 );
+ bRet = NO_PENDING( rIStm );
+ bOverreadDataBlocks = false;
+
+ // Netscape interprets the loop count
+ // as pure number of _repeats_;
+ // here it is the total number of loops
+ if( nLoops )
+ nLoops++;
+ }
+ else
+ rIStm.SeekRel( -1 );
+ }
+ else if ( aAppId == "STARDIV " && aAppCode == "5.0" && cSize == 9 )
+ {
+ sal_uInt8 cByte(0);
+ rIStm.ReadUChar( cByte );
+
+ // Loop extension
+ if ( cByte == 0x01 )
+ {
+ rIStm.ReadUInt32( nLogWidth100 ).ReadUInt32( nLogHeight100 );
+ rIStm.ReadUChar( cByte );
+ bStatus = ( cByte == 0 );
+ bRet = NO_PENDING( rIStm );
+ bOverreadDataBlocks = false;
+ }
+ else
+ rIStm.SeekRel( -1 );
+ }
+
+ }
+ }
+ }
+ break;
+
+ // overread everything else
+ default:
+ bOverreadDataBlocks = true;
+ break;
+ }
+
+ // overread sub-blocks
+ if ( bOverreadDataBlocks )
+ {
+ bRet = true;
+ while( cSize && bStatus && !rIStm.eof() )
+ {
+ sal_uInt16 nCount = static_cast<sal_uInt16>(cSize) + 1;
+ const sal_uInt64 nMaxPossible = rIStm.remainingSize();
+ if (nCount > nMaxPossible)
+ nCount = nMaxPossible;
+
+ if (nCount)
+ rIStm.SeekRel( nCount - 1 ); // Skip subblock data
+
+ bRet = false;
+ std::size_t nRead = rIStm.ReadBytes(&cSize, 1);
+ if (NO_PENDING(rIStm) && nRead == 1)
+ {
+ bRet = true;
+ }
+ else
+ cSize = 0;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+bool GIFReader::ReadLocalHeader()
+{
+ sal_uInt8 pBuf[ 9 ];
+ bool bRet = false;
+
+ std::size_t nRead = rIStm.ReadBytes(pBuf, 9);
+ if (NO_PENDING(rIStm) && nRead == 9)
+ {
+ SvMemoryStream aMemStm;
+ BitmapPalette* pPal;
+
+ aMemStm.SetBuffer( pBuf, 9, 9 );
+ aMemStm.ReadUInt16( nImagePosX );
+ aMemStm.ReadUInt16( nImagePosY );
+ aMemStm.ReadUInt16( nImageWidth );
+ aMemStm.ReadUInt16( nImageHeight );
+ sal_uInt8 nFlags(0);
+ aMemStm.ReadUChar(nFlags);
+
+ // if interlaced, first define startvalue
+ bInterlaced = ( ( nFlags & 0x40 ) == 0x40 );
+ nLastInterCount = 7;
+ nLastImageY = 0;
+
+ if( nFlags & 0x80 )
+ {
+ pPal = &aLPalette;
+ ReadPaletteEntries( pPal, sal_uLong(1) << ( (nFlags & 7 ) + 1 ) );
+ }
+ else
+ pPal = &aGPalette;
+
+ // if we could read everything, we will create the local image;
+ // if the global colour table is valid for the image, we will
+ // consider the BackGroundColorIndex.
+ if( NO_PENDING( rIStm ) )
+ {
+ CreateBitmaps( nImageWidth, nImageHeight, pPal, bGlobalPalette && ( pPal == &aGPalette ) );
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+sal_uLong GIFReader::ReadNextBlock()
+{
+ sal_uLong nRet = 0;
+ sal_uInt8 cBlockSize;
+
+ rIStm.ReadUChar( cBlockSize );
+
+ if ( rIStm.eof() )
+ nRet = 4;
+ else if ( NO_PENDING( rIStm ) )
+ {
+ if ( cBlockSize == 0 )
+ nRet = 2;
+ else
+ {
+ rIStm.ReadBytes( aSrcBuf.data(), cBlockSize );
+
+ if( NO_PENDING( rIStm ) )
+ {
+ if( bOverreadBlock )
+ nRet = 3;
+ else
+ {
+ bool bEOI;
+ sal_uLong nRead;
+ sal_uInt8* pTarget = pDecomp->DecompressBlock( aSrcBuf.data(), cBlockSize, nRead, bEOI );
+
+ nRet = ( bEOI ? 3 : 1 );
+
+ if( nRead && !bOverreadBlock )
+ FillImages( pTarget, nRead );
+
+ std::free( pTarget );
+ }
+ }
+ }
+ }
+
+ return nRet;
+}
+
+void GIFReader::FillImages( const sal_uInt8* pBytes, sal_uLong nCount )
+{
+ for( sal_uLong i = 0; i < nCount; i++ )
+ {
+ if( nImageX >= nImageWidth )
+ {
+ if( bInterlaced )
+ {
+ tools::Long nT1;
+
+ // lines will be copied if interlaced
+ if( nLastInterCount )
+ {
+ tools::Long nMinY = std::min( static_cast<tools::Long>(nLastImageY) + 1, static_cast<tools::Long>(nImageHeight) - 1 );
+ tools::Long nMaxY = std::min( static_cast<tools::Long>(nLastImageY) + nLastInterCount, static_cast<tools::Long>(nImageHeight) - 1 );
+
+ // copy last line read, if lines do not coincide
+ // ( happens at the end of the image )
+ if( ( nMinY > nLastImageY ) && ( nLastImageY < ( nImageHeight - 1 ) ) )
+ {
+ sal_uInt8* pScanline8 = pAcc8->GetScanline( nYAcc );
+ sal_uInt32 nSize8 = pAcc8->GetScanlineSize();
+ sal_uInt8* pScanline1 = nullptr;
+ sal_uInt32 nSize1 = 0;
+
+ if( bGCTransparent )
+ {
+ pScanline1 = pAcc1->GetScanline( nYAcc );
+ nSize1 = pAcc1->GetScanlineSize();
+ }
+
+ for( tools::Long j = nMinY; j <= nMaxY; j++ )
+ {
+ memcpy( pAcc8->GetScanline( j ), pScanline8, nSize8 );
+
+ if( bGCTransparent )
+ memcpy( pAcc1->GetScanline( j ), pScanline1, nSize1 );
+ }
+ }
+ }
+
+ nT1 = ( ++nImageY ) << 3;
+ nLastInterCount = 7;
+
+ if( nT1 >= nImageHeight )
+ {
+ tools::Long nT2 = nImageY - ( ( nImageHeight + 7 ) >> 3 );
+ nT1 = ( nT2 << 3 ) + 4;
+ nLastInterCount = 3;
+
+ if( nT1 >= nImageHeight )
+ {
+ nT2 -= ( nImageHeight + 3 ) >> 3;
+ nT1 = ( nT2 << 2 ) + 2;
+ nLastInterCount = 1;
+
+ if( nT1 >= nImageHeight )
+ {
+ nT2 -= ( nImageHeight + 1 ) >> 2;
+ nT1 = ( nT2 << 1 ) + 1;
+ nLastInterCount = 0;
+ }
+ }
+ }
+
+ nLastImageY = static_cast<sal_uInt16>(nT1);
+ nYAcc = nT1;
+ }
+ else
+ {
+ nLastImageY = ++nImageY;
+ nYAcc = nImageY;
+ }
+
+ // line starts from the beginning
+ nImageX = 0;
+ }
+
+ if( nImageY < nImageHeight )
+ {
+ const sal_uInt8 cTmp = pBytes[ i ];
+
+ if( bGCTransparent )
+ {
+ if( cTmp == nGCTransparentIndex )
+ pAcc1->SetPixelIndex( nYAcc, nImageX++, cTransIndex1 );
+ else
+ {
+ pAcc8->SetPixelIndex( nYAcc, nImageX, cTmp );
+ pAcc1->SetPixelIndex( nYAcc, nImageX++, cNonTransIndex1 );
+ }
+ }
+ else
+ pAcc8->SetPixelIndex( nYAcc, nImageX++, cTmp );
+ }
+ else
+ {
+ bOverreadBlock = true;
+ break;
+ }
+ }
+}
+
+void GIFReader::CreateNewBitmaps()
+{
+ AnimationBitmap aAnimationBitmap;
+
+ pAcc8.reset();
+
+ if( bGCTransparent )
+ {
+ pAcc1.reset();
+ aAnimationBitmap.maBitmapEx = BitmapEx( aBmp8, aBmp1 );
+ }
+ else
+ aAnimationBitmap.maBitmapEx = BitmapEx( aBmp8 );
+
+ aAnimationBitmap.maPositionPixel = Point( nImagePosX, nImagePosY );
+ aAnimationBitmap.maSizePixel = Size( nImageWidth, nImageHeight );
+ aAnimationBitmap.mnWait = ( nTimer != 65535 ) ? nTimer : ANIMATION_TIMEOUT_ON_CLICK;
+ aAnimationBitmap.mbUserInput = false;
+
+ // tdf#104121 . Internet Explorer, Firefox, Chrome and Safari all set a minimum default playback speed.
+ // IE10 Consumer Preview sets default of 100ms for rates less that 20ms. We do the same
+ if (aAnimationBitmap.mnWait < 2) // 20ms, specified in 100's of a second
+ aAnimationBitmap.mnWait = 10;
+
+ if( nGCDisposalMethod == 2 )
+ aAnimationBitmap.meDisposal = Disposal::Back;
+ else if( nGCDisposalMethod == 3 )
+ aAnimationBitmap.meDisposal = Disposal::Previous;
+ else
+ aAnimationBitmap.meDisposal = Disposal::Not;
+
+ nAnimationByteSize += aAnimationBitmap.maBitmapEx.GetSizeBytes();
+ nAnimationMinFileData += static_cast<sal_uInt64>(nImageWidth) * nImageHeight / 2560;
+ aAnimation.Insert(aAnimationBitmap);
+
+ if( aAnimation.Count() == 1 )
+ {
+ aAnimation.SetDisplaySizePixel( Size( nGlobalWidth, nGlobalHeight ) );
+ aAnimation.SetLoopCount( nLoops );
+ }
+}
+
+Graphic GIFReader::GetIntermediateGraphic()
+{
+ Graphic aImGraphic;
+
+ // only create intermediate graphic, if data is available
+ // but graphic still not completely read
+ if ( bImGraphicReady && !aAnimation.Count() )
+ {
+ pAcc8.reset();
+
+ if ( bGCTransparent )
+ {
+ pAcc1.reset();
+ aImGraphic = BitmapEx( aBmp8, aBmp1 );
+
+ pAcc1 = BitmapScopedWriteAccess(aBmp1);
+ bStatus = bStatus && pAcc1;
+ }
+ else
+ aImGraphic = BitmapEx(aBmp8);
+
+ pAcc8 = BitmapScopedWriteAccess(aBmp8);
+ bStatus = bStatus && pAcc8;
+ }
+
+ return aImGraphic;
+}
+
+bool GIFReader::ProcessGIF()
+{
+ bool bRead = false;
+ bool bEnd = false;
+
+ if ( !bStatus )
+ eActAction = ABORT_READING;
+
+ // set stream to right position
+ rIStm.Seek( nLastPos );
+
+ switch( eActAction )
+ {
+ // read next marker
+ case MARKER_READING:
+ {
+ sal_uInt8 cByte;
+
+ rIStm.ReadUChar( cByte );
+
+ if( rIStm.eof() )
+ eActAction = END_READING;
+ else if( NO_PENDING( rIStm ) )
+ {
+ bRead = true;
+
+ if( cByte == '!' )
+ eActAction = EXTENSION_READING;
+ else if( cByte == ',' )
+ eActAction = LOCAL_HEADER_READING;
+ else if( cByte == ';' )
+ eActAction = END_READING;
+ else
+ eActAction = ABORT_READING;
+ }
+ }
+ break;
+
+ // read ScreenDescriptor
+ case GLOBAL_HEADER_READING:
+ {
+ bRead = ReadGlobalHeader();
+ if( bRead )
+ {
+ ClearImageExtensions();
+ eActAction = MARKER_READING;
+ }
+ }
+ break;
+
+ // read extension
+ case EXTENSION_READING:
+ {
+ bRead = ReadExtension();
+ if( bRead )
+ eActAction = MARKER_READING;
+ }
+ break;
+
+ // read Image-Descriptor
+ case LOCAL_HEADER_READING:
+ {
+ bRead = ReadLocalHeader();
+ if( bRead )
+ {
+ nYAcc = nImageX = nImageY = 0;
+ eActAction = FIRST_BLOCK_READING;
+ }
+ }
+ break;
+
+ // read first data block
+ case FIRST_BLOCK_READING:
+ {
+ sal_uInt8 cDataSize;
+
+ rIStm.ReadUChar( cDataSize );
+
+ if( rIStm.eof() )
+ eActAction = ABORT_READING;
+ else if( cDataSize > 12 )
+ bStatus = false;
+ else if( NO_PENDING( rIStm ) )
+ {
+ bRead = true;
+ pDecomp = std::make_unique<GIFLZWDecompressor>( cDataSize );
+ eActAction = NEXT_BLOCK_READING;
+ bOverreadBlock = false;
+ }
+ else
+ eActAction = FIRST_BLOCK_READING;
+ }
+ break;
+
+ // read next data block
+ case NEXT_BLOCK_READING:
+ {
+ sal_uInt16 nLastX = nImageX;
+ sal_uInt16 nLastY = nImageY;
+ sal_uLong nRet = ReadNextBlock();
+
+ // Return: 0:Pending / 1:OK; / 2:OK and last block: / 3:EOI / 4:HardAbort
+ if( nRet )
+ {
+ bRead = true;
+
+ if ( nRet == 1 )
+ {
+ bImGraphicReady = true;
+ eActAction = NEXT_BLOCK_READING;
+ bOverreadBlock = false;
+ }
+ else
+ {
+ if( nRet == 2 )
+ {
+ pDecomp.reset();
+ CreateNewBitmaps();
+ eActAction = MARKER_READING;
+ ClearImageExtensions();
+ }
+ else if( nRet == 3 )
+ {
+ eActAction = NEXT_BLOCK_READING;
+ bOverreadBlock = true;
+ }
+ else
+ {
+ pDecomp.reset();
+ CreateNewBitmaps();
+ eActAction = ABORT_READING;
+ ClearImageExtensions();
+ }
+ }
+ }
+ else
+ {
+ nImageX = nLastX;
+ nImageY = nLastY;
+ }
+ }
+ break;
+
+ // an error occurred
+ case ABORT_READING:
+ {
+ bEnd = true;
+ eActAction = END_READING;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // set stream to right position,
+ // if data could be read put it at the old
+ // position otherwise at the actual one
+ if( bRead || bEnd )
+ nLastPos = rIStm.Tell();
+
+ return bRead;
+}
+
+bool GIFReader::ReadIsAnimated()
+{
+ ReadState eReadState;
+
+ bStatus = true;
+
+ while( ProcessGIF() && ( eActAction != END_READING ) ) {}
+
+ if( !bStatus )
+ eReadState = GIFREAD_ERROR;
+ else if( eActAction == END_READING )
+ eReadState = GIFREAD_OK;
+ else
+ {
+ if ( rIStm.GetError() == ERRCODE_IO_PENDING )
+ rIStm.ResetError();
+
+ eReadState = GIFREAD_NEED_MORE;
+ }
+
+ if (eReadState == GIFREAD_OK)
+ return aAnimation.Count() > 1;
+ return false;
+}
+
+void GIFReader::GetLogicSize(Size& rLogicSize)
+{
+ rLogicSize.setWidth(nLogWidth100);
+ rLogicSize.setHeight(nLogHeight100);
+}
+
+ReadState GIFReader::ReadGIF( Graphic& rGraphic )
+{
+ ReadState eReadState;
+
+ bStatus = true;
+
+ while( ProcessGIF() && ( eActAction != END_READING ) ) {}
+
+ if( !bStatus )
+ eReadState = GIFREAD_ERROR;
+ else if( eActAction == END_READING )
+ eReadState = GIFREAD_OK;
+ else
+ {
+ if ( rIStm.GetError() == ERRCODE_IO_PENDING )
+ rIStm.ResetError();
+
+ eReadState = GIFREAD_NEED_MORE;
+ }
+
+ if( aAnimation.Count() == 1 )
+ {
+ rGraphic = aAnimation.Get(0).maBitmapEx;
+
+ if( nLogWidth100 && nLogHeight100 )
+ {
+ rGraphic.SetPrefSize( Size( nLogWidth100, nLogHeight100 ) );
+ rGraphic.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ }
+ }
+ else
+ rGraphic = aAnimation;
+
+ return eReadState;
+}
+
+bool IsGIFAnimated(SvStream & rStm, Size& rLogicSize)
+{
+ GIFReader aReader(rStm);
+
+ SvStreamEndian nOldFormat = rStm.GetEndian();
+ rStm.SetEndian(SvStreamEndian::LITTLE);
+ bool bResult = aReader.ReadIsAnimated();
+ aReader.GetLogicSize(rLogicSize);
+ rStm.SetEndian(nOldFormat);
+
+ return bResult;
+}
+
+VCL_DLLPUBLIC bool ImportGIF( SvStream & rStm, Graphic& rGraphic )
+{
+ std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext();
+ rGraphic.SetReaderContext(nullptr);
+ GIFReader* pGIFReader = dynamic_cast<GIFReader*>( pContext.get() );
+ if (!pGIFReader)
+ {
+ pContext = std::make_shared<GIFReader>( rStm );
+ pGIFReader = static_cast<GIFReader*>( pContext.get() );
+ }
+
+ SvStreamEndian nOldFormat = rStm.GetEndian();
+ rStm.SetEndian( SvStreamEndian::LITTLE );
+
+ bool bRet = true;
+
+ ReadState eReadState = pGIFReader->ReadGIF(rGraphic);
+
+ if (eReadState == GIFREAD_ERROR)
+ {
+ bRet = false;
+ }
+ else if (eReadState == GIFREAD_NEED_MORE)
+ {
+ rGraphic = pGIFReader->GetIntermediateGraphic();
+ rGraphic.SetReaderContext(pContext);
+ }
+
+ rStm.SetEndian(nOldFormat);
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/igif/gifread.hxx b/vcl/source/filter/igif/gifread.hxx
new file mode 100644
index 000000000..de9a506f2
--- /dev/null
+++ b/vcl/source/filter/igif/gifread.hxx
@@ -0,0 +1,30 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_IGIF_GIFREAD_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_IGIF_GIFREAD_HXX
+
+#include <vcl/graph.hxx>
+
+VCL_DLLPUBLIC bool ImportGIF(SvStream& rStream, Graphic& rGraphic);
+bool IsGIFAnimated(SvStream& rStream, Size& rLogicSize);
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_IGIF_GIFREAD_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/imet/ios2met.cxx b/vcl/source/filter/imet/ios2met.cxx
new file mode 100644
index 000000000..fe856c1d7
--- /dev/null
+++ b/vcl/source/filter/imet/ios2met.cxx
@@ -0,0 +1,2883 @@
+/* -*- 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 <osl/thread.h>
+#include <o3tl/safeint.hxx>
+#include <tools/poly.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <sal/log.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/gdimtf.hxx>
+#include <filter/MetReader.hxx>
+#include <basegfx/numeric/ftools.hxx>
+
+#include <cmath>
+#include <memory>
+
+class FilterConfigItem;
+
+namespace {
+
+enum PenStyle { PEN_NULL, PEN_SOLID, PEN_DOT, PEN_DASH, PEN_DASHDOT };
+
+}
+
+// -----------------------------Field Types-------------------------------
+
+#define BegDocumnMagic 0xA8A8 /* Begin Document */
+#define EndDocumnMagic 0xA8A9 /* End Document */
+
+#define BegResGrpMagic 0xC6A8 /* Begin Resource Group */
+#define EndResGrpMagic 0xC6A9 /* End Resource Group */
+
+#define BegColAtrMagic 0x77A8 /* Begin Color Attribute Table */
+#define EndColAtrMagic 0x77A9 /* End Color Attribute Table */
+#define BlkColAtrMagic 0x77B0 /* Color Attribute Table */
+#define MapColAtrMagic 0x77AB /* Map Color Attribute Table */
+
+#define BegImgObjMagic 0xFBA8 /* Begin Image Object */
+#define EndImgObjMagic 0xFBA9 /* End Image Object */
+#define DscImgObjMagic 0xFBA6 /* Image Data Descriptor */
+#define DatImgObjMagic 0xFBEE /* Image Picture Data */
+
+#define BegObEnv1Magic 0xC7A8 /* Begin Object Environment Group */
+#define EndObEnv1Magic 0xC7A9 /* End Object Environment Group */
+
+#define BegGrfObjMagic 0xBBA8 /* Begin Graphics Object */
+#define EndGrfObjMagic 0xBBA9 /* End Graphics Object */
+#define DscGrfObjMagic 0xBBA6 /* Graphics Data Descriptor */
+#define DatGrfObjMagic 0xBBEE /* Graphics Data */
+
+#define MapCodFntMagic 0x8AAB /* Map Coded Font */
+#define MapDatResMagic 0xC3AB /* Map Data Resource */
+
+// -----------------------------Order Types-------------------------------
+
+#define GOrdGivArc 0xC6 /* 1 Arc at given position */
+#define GOrdCurArc 0x86 /* 1 Arc at current position */
+#define GOrdGivBzr 0xE5 /* 1 Beziercurve at given position */
+#define GOrdCurBzr 0xA5 /* 1 Beziercurve at current position */
+#define GOrdGivBox 0xC0 /* 1 Box at given position */
+#define GOrdCurBox 0x80 /* 1 Box at current position */
+#define GOrdGivFil 0xC5 /* 1 Fillet at given position */
+#define GOrdCurFil 0x85 /* 1 Fillet at current position */
+#define GOrdGivCrc 0xC7 /* 1 Full arc (circle) at given position */
+#define GOrdCurCrc 0x87 /* 1 Full arc (circle) at current position */
+#define GOrdGivLin 0xC1 /* 1 Line at given position */
+#define GOrdCurLin 0x81 /* 1 Line at current position */
+#define GOrdGivMrk 0xC2 /* 1 Marker at given position */
+#define GOrdCurMrk 0x82 /* 1 Marker at current position */
+#define GOrdGivArP 0xE3 /* 1 Partial arc at given position */
+#define GOrdCurArP 0xA3 /* 1 Partial arc at current position */
+#define GOrdGivRLn 0xE1 /* 1 Relative line at given position */
+#define GOrdCurRLn 0xA1 /* 1 Relative line at current position */
+#define GOrdGivSFl 0xE4 /* 1 Sharp fillet at given position */
+#define GOrdCurSFl 0xA4 /* 1 Sharp fillet at current position */
+
+#define GOrdGivStM 0xF1 /* 1 Character string move at given position */
+#define GOrdCurStM 0xB1 /* 1 Character string move at current position */
+#define GOrdGivStr 0xC3 /* 1 Character string at given position */
+#define GOrdCurStr 0x83 /* 1 Character string at current position */
+#define GOrdGivStx 0xFEF0 /* 2 Character string extended at given position */
+#define GOrdCurStx 0xFEB0 /* 2 Character string extended at current position */
+
+#define GOrdGivImg 0xD1 /* 1 Begin Image at given position */
+#define GOrdCurImg 0x91 /* 1 Begin Image at current position */
+#define GOrdImgDat 0x92 /* 1 Image data */
+#define GOrdEndImg 0x93 /* 1 End Image */
+#define GOrdBegAra 0x68 /* 0 1 Begin area */
+#define GOrdEndAra 0x60 /* 1 End area */
+#define GOrdBegElm 0xD2 /* 1 Begin element */
+#define GOrdEndElm 0x49 /* 0 1 End element */
+
+#define GOrdBegPth 0xD0 /* 1 Begin path */
+#define GOrdEndPth 0x7F /* 0 1 End path */
+#define GOrdFilPth 0xD7 /* 1 Fill path */
+#define GOrdModPth 0xD8 /* 1 Modify path */
+#define GOrdOutPth 0xD4 /* 1 Outline path */
+#define GOrdSClPth 0xB4 /* 1 Set clip path */
+
+#define GOrdNopNop 0x00 /* 0 0 No operation */
+#define GOrdRemark 0x01 /* 1 Comment */
+#define GOrdSegLab 0xD3 /* 1 Label */
+#define GOrdBitBlt 0xD6 /* 1 Bitblt */
+#define GOrdCalSeg 0x07 /* 1 Call Segment */
+#define GOrdSSgBnd 0x32 /* 1 Set segment boundary */
+#define GOrdSegChr 0x04 /* 1 Segment characteristics */
+#define GOrdCloFig 0x7D /* 0 1 Close Figure */
+#define GOrdEndSym 0xFF /* 0 0 End of symbol definition */
+#define GOrdEndPlg 0x3E /* 0 1 End prolog */
+#define GOrdEscape 0xD5 /* 1 Escape */
+#define GOrdExtEsc 0xFED5 /* 2 Extended Escape */
+#define GOrdPolygn 0xF3 /* 2 Polygons */
+
+#define GOrdStkPop 0x3F /* 0 1 Pop */
+
+#define GOrdSIvAtr 0x14 /* 1 Set individual attribute */
+#define GOrdPIvAtr 0x54 /* 1 Push and set individual attribute */
+#define GOrdSColor 0x0A /* 0 1 Set color */
+#define GOrdPColor 0x4A /* 0 1 Push and set color */
+#define GOrdSIxCol 0xA6 /* 1 Set indexed color */
+#define GOrdPIxCol 0xE6 /* 1 Push and set indexed color */
+#define GOrdSXtCol 0x26 /* 1 Set extended color */
+#define GOrdPXtCol 0x66 /* 1 Push and set extended color */
+#define GOrdSBgCol 0x25 /* 1 Set background color */
+#define GOrdPBgCol 0x65 /* 1 Push and set background color */
+#define GOrdSBxCol 0xA7 /* 1 Set background indexed color */
+#define GOrdPBxCol 0xE7 /* 1 Push and set background indexed color */
+#define GOrdSMixMd 0x0C /* 0 1 Set mix */
+#define GOrdPMixMd 0x4C /* 0 1 Push and set mix */
+#define GOrdSBgMix 0x0D /* 0 1 Set background mix */
+#define GOrdPBgMix 0x4D /* 0 1 Push and set background mix */
+
+#define GOrdSPtSet 0x08 /* 0 1 Set pattern set */
+#define GOrdPPtSet 0x48 /* 0 1 Push and set pattern set */
+#define GOrdSPtSym 0x28 /* 0 1 Set pattern symbol */
+#define GOrdPPtSym 0x09 /* 0 1 Push and set pattern symbol */
+#define GOrdSPtRef 0xA0 /* 1 Set model pattern reference */
+#define GOrdPPtRef 0xE0 /* 1 Push and set pattern reference point */
+
+#define GOrdSLnEnd 0x1A /* 0 1 Set line end */
+#define GOrdPLnEnd 0x5A /* 0 1 Push and set line end */
+#define GOrdSLnJoi 0x1B /* 0 1 Set line join */
+#define GOrdPLnJoi 0x5B /* 0 1 Push and set line join */
+#define GOrdSLnTyp 0x18 /* 0 1 Set line type */
+#define GOrdPLnTyp 0x58 /* 0 1 Push and set line type */
+#define GOrdSLnWdt 0x19 /* 0 1 Set line width */
+#define GOrdPLnWdt 0x59 /* 0 1 Push and set line width */
+#define GOrdSFrLWd 0x11 /* 1 Set fractional line width */
+#define GOrdPFrLWd 0x51 /* 1 Push and set fractional line width */
+#define GOrdSStLWd 0x15 /* 1 Set stroke line width */
+#define GOrdPStLWd 0x55 /* 1 Push and set stroke line width */
+
+#define GOrdSChDir 0x3A /* 0 1 Set character direction */
+#define GOrdPChDir 0x7A /* 0 1 Push and set character direction */
+#define GOrdSChPrc 0x39 /* 0 1 Set character precision */
+#define GOrdPChPrc 0x79 /* 0 1 Push and set character precision */
+#define GOrdSChSet 0x38 /* 0 1 Set character set */
+#define GOrdPChSet 0x78 /* 0 1 Push and set character set */
+#define GOrdSChAng 0x34 /* 1 Set character angle */
+#define GOrdPChAng 0x74 /* 1 Push and set character angle */
+#define GOrdSChBrx 0x05 /* 1 Set character break extra */
+#define GOrdPChBrx 0x45 /* 1 Push and set character break extra */
+#define GOrdSChCel 0x33 /* 1 Set character cell */
+#define GOrdPChCel 0x03 /* 1 Push and set character cell */
+#define GOrdSChXtr 0x17 /* 1 Set character extra */
+#define GOrdPChXtr 0x57 /* 1 Push and set character extra */
+#define GOrdSChShr 0x35 /* 1 Set character shear */
+#define GOrdPChShr 0x75 /* 1 Push and set character shear */
+#define GOrdSTxAlg 0x36 /* 0 2 Set text allingment */
+#define GOrdPTxAlg 0x76 /* 0 2 Push and set text allingment */
+
+#define GOrdSMkPrc 0x3B /* 0 1 Set marker precision */
+#define GOrdPMkPrc 0x7B /* 0 1 Push and set marker precision */
+#define GOrdSMkSet 0x3C /* 0 1 Set marker set */
+#define GOrdPMkSet 0x7C /* 0 1 Push and set marker set */
+#define GOrdSMkSym 0x29 /* 0 1 Set marker symbol */
+#define GOrdPMkSym 0x69 /* 0 1 Push and set marker symbol */
+#define GOrdSMkCel 0x37 /* 1 Set marker cell */
+#define GOrdPMkCel 0x77 /* 1 Push and set marker cell */
+
+#define GOrdSArcPa 0x22 /* 1 Set arc parameters */
+#define GOrdPArcPa 0x62 /* 1 Push and set arc parameters */
+
+#define GOrdSCrPos 0x21 /* 1 Set current position */
+#define GOrdPCrPos 0x61 /* 1 Push and set current position */
+
+#define GOrdSMdTrn 0x24 /* 1 Set model transform */
+#define GOrdPMdTrn 0x64 /* 1 Push and set model transform */
+#define GOrdSPkIdn 0x43 /* 1 Set pick identifier */
+#define GOrdPPkIdn 0x23 /* 1 Push and set pick identifier */
+#define GOrdSVwTrn 0x31 /* 1 Set viewing transform */
+#define GOrdSVwWin 0x27 /* 1 Set viewing window */
+#define GOrdPVwWin 0x67 /* 1 Push and set viewing window */
+
+//============================ OS2METReader ==================================
+
+namespace {
+
+struct OSPalette {
+ OSPalette * pSucc;
+ sal_uInt32 * p0RGB; // May be NULL!
+ size_t nSize;
+};
+
+struct OSArea {
+ OSArea * pSucc;
+ sal_uInt8 nFlags;
+ tools::PolyPolygon aPPoly;
+ bool bClosed;
+ Color aCol;
+ Color aBgCol;
+ RasterOp eMix;
+ RasterOp eBgMix;
+ bool bFill;
+
+ OSArea()
+ : pSucc(nullptr)
+ , nFlags(0)
+ , bClosed(false)
+ , eMix(RasterOp::OverPaint)
+ , eBgMix(RasterOp::OverPaint)
+ , bFill(false)
+ {
+ }
+};
+
+struct OSPath
+{
+ OSPath* pSucc;
+ sal_uInt32 nID;
+ tools::PolyPolygon aPPoly;
+ bool bClosed;
+ bool bStroke;
+
+ OSPath()
+ : pSucc(nullptr)
+ , nID(0)
+ , bClosed(false)
+ , bStroke(false)
+ {
+ }
+};
+
+struct OSFont {
+ OSFont * pSucc;
+ sal_uInt32 nID;
+ vcl::Font aFont;
+
+ OSFont()
+ : pSucc(nullptr)
+ , nID(0)
+ {
+ }
+};
+
+struct OSBitmap {
+ OSBitmap * pSucc;
+ sal_uInt32 nID;
+ BitmapEx aBitmapEx;
+
+ // required during reading of the bitmap:
+ SvStream * pBMP; // pointer to temporary Windows-BMP file or NULL
+ sal_uInt32 nWidth, nHeight;
+ sal_uInt16 nBitsPerPixel;
+ sal_uInt32 nMapPos;
+};
+
+struct OSAttr
+{
+ OSAttr * pSucc;
+ sal_uInt16 nPushOrder;
+ sal_uInt8 nIvAttrA, nIvAttrP; // special variables for the Order "GOrdPIvAtr"
+
+ Color aLinCol;
+ Color aLinBgCol;
+ RasterOp eLinMix;
+ RasterOp eLinBgMix;
+ Color aChrCol;
+ Color aChrBgCol;
+ RasterOp eChrMix;
+ RasterOp eChrBgMix;
+ Color aMrkCol;
+ Color aMrkBgCol;
+ RasterOp eMrkMix;
+ RasterOp eMrkBgMix;
+ Color aPatCol;
+ Color aPatBgCol;
+ RasterOp ePatMix;
+ RasterOp ePatBgMix;
+ Color aImgCol;
+ Color aImgBgCol;
+ RasterOp eImgMix;
+ RasterOp eImgBgMix;
+ sal_Int32 nArcP, nArcQ, nArcR, nArcS;
+ Degree10 nChrAng;
+ sal_Int32 nChrCellHeight;
+ sal_uInt32 nChrSet;
+ Point aCurPos;
+ PenStyle eLinStyle;
+ sal_uInt16 nLinWidth;
+ Size aMrkCellSize;
+ sal_uInt8 nMrkPrec;
+ sal_uInt8 nMrkSet;
+ sal_uInt8 nMrkSymbol;
+ bool bFill;
+ sal_uInt16 nStrLinWidth;
+
+ OSAttr()
+ : pSucc(nullptr)
+ , nPushOrder(0)
+ , nIvAttrA(0)
+ , nIvAttrP(0)
+ , eLinMix(RasterOp::OverPaint)
+ , eLinBgMix(RasterOp::OverPaint)
+ , eChrMix(RasterOp::OverPaint)
+ , eChrBgMix(RasterOp::OverPaint)
+ , eMrkMix(RasterOp::OverPaint)
+ , eMrkBgMix(RasterOp::OverPaint)
+ , ePatMix(RasterOp::OverPaint)
+ , ePatBgMix(RasterOp::OverPaint)
+ , eImgMix(RasterOp::OverPaint)
+ , eImgBgMix(RasterOp::OverPaint)
+ , nArcP(0)
+ , nArcQ(0)
+ , nArcR(0)
+ , nArcS(0)
+ , nChrAng(0)
+ , nChrCellHeight(0)
+ , nChrSet(0)
+ , eLinStyle(PEN_NULL)
+ , nLinWidth(0)
+ , nMrkPrec(0)
+ , nMrkSet(0)
+ , nMrkSymbol(0)
+ , bFill(false)
+ , nStrLinWidth(0)
+ {
+ }
+};
+
+class OS2METReader {
+
+private:
+
+ int ErrorCode;
+
+ SvStream * pOS2MET; // the OS2MET file to be read
+ VclPtr<VirtualDevice> pVirDev; // here the drawing methods are being called
+ // While doing this a recording in the GDIMetaFile
+ // will take place.
+ tools::Rectangle aBoundingRect; // bounding rectangle as stored in the file
+ tools::Rectangle aCalcBndRect; // bounding rectangle calculated on our own
+ MapMode aGlobMapMode; // resolution of the picture
+ bool bCoord32;
+
+ OSPalette * pPaletteStack;
+
+ LineInfo aLineInfo;
+
+ OSArea * pAreaStack; // Areas that are being worked on
+
+ OSPath * pPathStack; // Paths that are being worked on
+ OSPath * pPathList; // finished Paths
+
+ OSFont * pFontList;
+
+ OSBitmap * pBitmapList;
+
+ OSAttr aDefAttr;
+ OSAttr aAttr;
+ OSAttr * pAttrStack;
+
+ std::unique_ptr<SvMemoryStream> xOrdFile;
+
+ void AddPointsToPath(const tools::Polygon & rPoly);
+ void AddPointsToArea(const tools::Polygon & rPoly);
+ void CloseFigure();
+ void PushAttr(sal_uInt16 nPushOrder);
+ void PopAttr();
+
+ void ChangeBrush( const Color& rPatColor, bool bFill );
+ void SetPen( const Color& rColor, sal_uInt16 nStrLinWidth = 0, PenStyle ePenStyle = PEN_SOLID );
+ void SetRasterOp(RasterOp eROP);
+
+ void SetPalette0RGB(sal_uInt16 nIndex, sal_uInt32 nCol);
+ sal_uInt32 GetPalette0RGB(sal_uInt32 nIndex) const;
+ // gets color from palette, or, if it doesn't exist,
+ // interprets nIndex as immediate RGB value.
+ Color GetPaletteColor(sal_uInt32 nIndex) const;
+
+
+ bool IsLineInfo() const;
+ void DrawPolyLine( const tools::Polygon& rPolygon );
+ void DrawPolygon( const tools::Polygon& rPolygon );
+ void DrawPolyPolygon( const tools::PolyPolygon& rPolygon );
+ sal_uInt16 ReadBigEndianWord();
+ sal_uInt32 ReadBigEndian3BytesLong();
+ sal_uInt32 ReadLittleEndian3BytesLong();
+ sal_Int32 ReadCoord(bool b32);
+ Point ReadPoint( const bool bAdjustBoundRect = true );
+ static RasterOp OS2MixToRasterOp(sal_uInt8 nMix);
+ void ReadLine(bool bGivenPos, sal_uInt16 nOrderLen);
+ void ReadRelLine(bool bGivenPos, sal_uInt16 nOrderLen);
+ void ReadBox(bool bGivenPos);
+ void ReadBitBlt();
+ void ReadChrStr(bool bGivenPos, bool bMove, bool bExtra, sal_uInt16 nOrderLen);
+ void ReadArc(bool bGivenPos);
+ void ReadFullArc(bool bGivenPos, sal_uInt16 nOrderSize);
+ void ReadPartialArc(bool bGivenPos, sal_uInt16 nOrderSize);
+ void ReadPolygons();
+ void ReadBezier(bool bGivenPos, sal_uInt16 nOrderLen);
+ void ReadFillet(bool bGivenPos, sal_uInt16 nOrderLen);
+ void ReadFilletSharp(bool bGivenPos, sal_uInt16 nOrderLen);
+ void ReadMarker(bool bGivenPos, sal_uInt16 nOrderLen);
+ void ReadOrder(sal_uInt16 nOrderID, sal_uInt16 nOrderLen);
+ void ReadDsc(sal_uInt16 nDscID);
+ void ReadImageData(sal_uInt16 nDataID, sal_uInt16 nDataLen);
+ void ReadFont(sal_uInt16 nFieldSize);
+ void ReadField(sal_uInt16 nFieldType, sal_uInt16 nFieldSize);
+
+public:
+
+ OS2METReader();
+ ~OS2METReader();
+
+ void ReadOS2MET( SvStream & rStreamOS2MET, GDIMetaFile & rGDIMetaFile );
+ // Reads from the stream an OS2MET file and fills up the GDIMetaFile
+
+};
+
+}
+
+//=================== Methods of OS2METReader ==============================
+
+OS2METReader::OS2METReader()
+ : ErrorCode(0)
+ , pOS2MET(nullptr)
+ , pVirDev(VclPtr<VirtualDevice>::Create())
+ , bCoord32(false)
+ , pPaletteStack(nullptr)
+ , pAreaStack(nullptr)
+ , pPathStack(nullptr)
+ , pPathList(nullptr)
+ , pFontList(nullptr)
+ , pBitmapList(nullptr)
+ , pAttrStack(nullptr)
+{
+ pVirDev->EnableOutput(false);
+}
+
+OS2METReader::~OS2METReader()
+{
+ pVirDev.disposeAndClear();
+
+ while (pAreaStack!=nullptr) {
+ OSArea * p=pAreaStack;
+ pAreaStack=p->pSucc;
+ delete p;
+ }
+
+ while (pPathStack!=nullptr) {
+ OSPath * p=pPathStack;
+ pPathStack=p->pSucc;
+ delete p;
+ }
+
+ while (pPathList!=nullptr) {
+ OSPath * p=pPathList;
+ pPathList=p->pSucc;
+ delete p;
+ }
+
+ while (pFontList!=nullptr) {
+ OSFont * p=pFontList;
+ pFontList=p->pSucc;
+ delete p;
+ }
+
+ while (pBitmapList!=nullptr) {
+ OSBitmap * p=pBitmapList;
+ pBitmapList=p->pSucc;
+ delete p->pBMP;
+ delete p;
+ }
+
+ while (pAttrStack!=nullptr) {
+ OSAttr * p=pAttrStack;
+ pAttrStack=p->pSucc;
+ delete p;
+ }
+
+ while (pPaletteStack!=nullptr) {
+ OSPalette * p=pPaletteStack;
+ pPaletteStack=p->pSucc;
+ delete[] p->p0RGB;
+ delete p;
+ }
+}
+
+bool OS2METReader::IsLineInfo() const
+{
+ return ( ! ( aLineInfo.IsDefault() || ( aLineInfo.GetStyle() == LineStyle::NONE ) || ( pVirDev->GetLineColor() == COL_TRANSPARENT ) ) );
+}
+
+void OS2METReader::DrawPolyLine( const tools::Polygon& rPolygon )
+{
+ if ( aLineInfo.GetStyle() == LineStyle::Dash || ( aLineInfo.GetWidth() > 1 ) )
+ pVirDev->DrawPolyLine( rPolygon, aLineInfo );
+ else
+ pVirDev->DrawPolyLine( rPolygon );
+}
+
+void OS2METReader::DrawPolygon( const tools::Polygon& rPolygon )
+{
+ if ( IsLineInfo() )
+ {
+ pVirDev->Push( vcl::PushFlags::LINECOLOR );
+ pVirDev->SetLineColor( COL_TRANSPARENT );
+ pVirDev->DrawPolygon( rPolygon );
+ pVirDev->Pop();
+ pVirDev->DrawPolyLine( rPolygon, aLineInfo );
+ }
+ else
+ pVirDev->DrawPolygon( rPolygon );
+}
+
+void OS2METReader::DrawPolyPolygon( const tools::PolyPolygon& rPolyPolygon )
+{
+ if ( IsLineInfo() )
+ {
+ pVirDev->Push( vcl::PushFlags::LINECOLOR );
+ pVirDev->SetLineColor( COL_TRANSPARENT );
+ pVirDev->DrawPolyPolygon( rPolyPolygon );
+ pVirDev->Pop();
+ for ( sal_uInt16 i = 0; i < rPolyPolygon.Count(); i++ )
+ pVirDev->DrawPolyLine( rPolyPolygon.GetObject( i ), aLineInfo );
+ }
+ else
+ pVirDev->DrawPolyPolygon( rPolyPolygon );
+}
+
+void OS2METReader::AddPointsToArea(const tools::Polygon & rPoly)
+{
+ sal_uInt16 nOldSize, nNewSize,i;
+
+ if (pAreaStack==nullptr || rPoly.GetSize()==0) return;
+ tools::PolyPolygon * pPP=&(pAreaStack->aPPoly);
+ if (pPP->Count()==0 || pAreaStack->bClosed) pPP->Insert(rPoly);
+ else {
+ tools::Polygon aLastPoly(pPP->GetObject(pPP->Count()-1));
+ nOldSize=aLastPoly.GetSize();
+ if (nOldSize && aLastPoly.GetPoint(nOldSize-1)==rPoly.GetPoint(0)) nOldSize--;
+ nNewSize=nOldSize+rPoly.GetSize();
+ aLastPoly.SetSize(nNewSize);
+ for (i=nOldSize; i<nNewSize; i++) {
+ aLastPoly.SetPoint(rPoly.GetPoint(i-nOldSize),i);
+ }
+ pPP->Replace(aLastPoly,pPP->Count()-1);
+ }
+ pAreaStack->bClosed=false;
+}
+
+void OS2METReader::AddPointsToPath(const tools::Polygon & rPoly)
+{
+ sal_uInt16 nOldSize, nNewSize,i;
+
+ if (pPathStack==nullptr || rPoly.GetSize()==0) return;
+ tools::PolyPolygon * pPP=&(pPathStack->aPPoly);
+ if (pPP->Count()==0 /*|| pPathStack->bClosed==sal_True*/) pPP->Insert(rPoly);
+ else {
+ tools::Polygon aLastPoly(pPP->GetObject(pPP->Count()-1));
+ nOldSize=aLastPoly.GetSize();
+ if (nOldSize && aLastPoly.GetPoint(nOldSize-1)!=rPoly.GetPoint(0)) pPP->Insert(rPoly);
+ else {
+ nOldSize--;
+ nNewSize=nOldSize+rPoly.GetSize();
+ aLastPoly.SetSize(nNewSize);
+ for (i=nOldSize; i<nNewSize; i++) {
+ aLastPoly.SetPoint(rPoly.GetPoint(i-nOldSize),i);
+ }
+ pPP->Replace(aLastPoly,pPP->Count()-1);
+ }
+ }
+ pPathStack->bClosed=false;
+}
+
+void OS2METReader::CloseFigure()
+{
+ if (pAreaStack!=nullptr) pAreaStack->bClosed=true;
+ else if (pPathStack!=nullptr) pPathStack->bClosed=true;
+}
+
+void OS2METReader::PushAttr(sal_uInt16 nPushOrder)
+{
+ OSAttr * p;
+ p=new OSAttr;
+ *p=aAttr;
+ p->pSucc=pAttrStack; pAttrStack=p;
+ p->nPushOrder=nPushOrder;
+}
+
+void OS2METReader::PopAttr()
+{
+ OSAttr * p=pAttrStack;
+
+ if (p==nullptr) return;
+ switch (p->nPushOrder) {
+
+ case GOrdPIvAtr:
+ switch (p->nIvAttrA) {
+ case 1: switch (p->nIvAttrP) {
+ case 1: aAttr.aLinCol=p->aLinCol; break;
+ case 2: aAttr.aChrCol=p->aChrCol; break;
+ case 3: aAttr.aMrkCol=p->aMrkCol; break;
+ case 4: aAttr.aPatCol=p->aPatCol; break;
+ case 5: aAttr.aImgCol=p->aImgCol; break;
+ } break;
+ case 2: switch (p->nIvAttrP) {
+ case 1: aAttr.aLinBgCol=p->aLinBgCol; break;
+ case 2: aAttr.aChrBgCol=p->aChrBgCol; break;
+ case 3: aAttr.aMrkBgCol=p->aMrkBgCol; break;
+ case 4: aAttr.aPatBgCol=p->aPatBgCol; break;
+ case 5: aAttr.aImgBgCol=p->aImgBgCol; break;
+ } break;
+ case 3: switch (p->nIvAttrP) {
+ case 1: aAttr.eLinMix=p->eLinMix; break;
+ case 2: aAttr.eChrMix=p->eChrMix; break;
+ case 3: aAttr.eMrkMix=p->eMrkMix; break;
+ case 4: aAttr.ePatMix=p->ePatMix; break;
+ case 5: aAttr.eImgMix=p->eImgMix; break;
+ } break;
+ case 4: switch (p->nIvAttrP) {
+ case 1: aAttr.eLinBgMix=p->eLinBgMix; break;
+ case 2: aAttr.eChrBgMix=p->eChrBgMix; break;
+ case 3: aAttr.eMrkBgMix=p->eMrkBgMix; break;
+ case 4: aAttr.ePatBgMix=p->ePatBgMix; break;
+ case 5: aAttr.eImgBgMix=p->eImgBgMix; break;
+ } break;
+ }
+ break;
+
+ case GOrdPLnTyp: aAttr.eLinStyle=p->eLinStyle; break;
+
+ case GOrdPLnWdt: aAttr.nLinWidth=p->nLinWidth; break;
+
+ case GOrdPStLWd: aAttr.nStrLinWidth=p->nStrLinWidth; break;
+
+ case GOrdPChSet: aAttr.nChrSet=p->nChrSet; break;
+
+ case GOrdPChAng: aAttr.nChrAng=p->nChrAng; break;
+
+ case GOrdPMixMd:
+ aAttr.eLinMix=p->eLinMix;
+ aAttr.eChrMix=p->eChrMix;
+ aAttr.eMrkMix=p->eMrkMix;
+ aAttr.ePatMix=p->ePatMix;
+ aAttr.eImgMix=p->eImgMix;
+ break;
+
+ case GOrdPBgMix:
+ aAttr.eLinBgMix=p->eLinBgMix;
+ aAttr.eChrBgMix=p->eChrBgMix;
+ aAttr.eMrkBgMix=p->eMrkBgMix;
+ aAttr.ePatBgMix=p->ePatBgMix;
+ aAttr.eImgBgMix=p->eImgBgMix;
+ break;
+
+ case GOrdPPtSym: aAttr.bFill = p->bFill; break;
+
+ case GOrdPColor:
+ case GOrdPIxCol:
+ case GOrdPXtCol:
+ aAttr.aLinCol=p->aLinCol;
+ aAttr.aChrCol=p->aChrCol;
+ aAttr.aMrkCol=p->aMrkCol;
+ aAttr.aPatCol=p->aPatCol;
+ aAttr.aImgCol=p->aImgCol;
+ break;
+
+ case GOrdPBgCol:
+ case GOrdPBxCol:
+ aAttr.aLinBgCol=p->aLinBgCol;
+ aAttr.aChrBgCol=p->aChrBgCol;
+ aAttr.aMrkBgCol=p->aMrkBgCol;
+ aAttr.aPatBgCol=p->aPatBgCol;
+ aAttr.aImgBgCol=p->aImgBgCol;
+ break;
+
+ case GOrdPMkPrc: aAttr.nMrkPrec=aDefAttr.nMrkPrec; break;
+
+ case GOrdPMkSet: aAttr.nMrkSet=aDefAttr.nMrkSet; break;
+
+ case GOrdPMkSym: aAttr.nMrkSymbol=aDefAttr.nMrkSymbol; break;
+
+ case GOrdPMkCel: aAttr.aMrkCellSize=aDefAttr.aMrkCellSize; break;
+
+ case GOrdPArcPa:
+ aAttr.nArcP=p->nArcP; aAttr.nArcQ=p->nArcQ;
+ aAttr.nArcR=p->nArcR; aAttr.nArcS=p->nArcS;
+ break;
+
+ case GOrdPCrPos:
+ aAttr.aCurPos=p->aCurPos;
+ break;
+ }
+ pAttrStack=p->pSucc;
+ delete p;
+}
+
+void OS2METReader::ChangeBrush(const Color& rPatColor, bool bFill )
+{
+ Color aColor;
+
+ if( bFill )
+ aColor = rPatColor;
+ else
+ aColor = COL_TRANSPARENT;
+
+ if( pVirDev->GetFillColor() != aColor )
+ pVirDev->SetFillColor( aColor );
+}
+
+void OS2METReader::SetPen( const Color& rColor, sal_uInt16 nLineWidth, PenStyle ePenStyle )
+{
+ LineStyle eLineStyle( LineStyle::Solid );
+
+ if ( pVirDev->GetLineColor() != rColor )
+ pVirDev->SetLineColor( rColor );
+ aLineInfo.SetWidth( nLineWidth );
+
+ sal_uInt16 nDotCount = 0;
+ sal_uInt16 nDashCount = 0;
+ switch ( ePenStyle )
+ {
+ case PEN_NULL :
+ eLineStyle = LineStyle::NONE;
+ break;
+ case PEN_DASHDOT :
+ nDashCount++;
+ [[fallthrough]];
+ case PEN_DOT :
+ nDotCount++;
+ nDashCount--;
+ [[fallthrough]];
+ case PEN_DASH :
+ nDashCount++;
+ aLineInfo.SetDotCount( nDotCount );
+ aLineInfo.SetDashCount( nDashCount );
+ aLineInfo.SetDistance( nLineWidth );
+ aLineInfo.SetDotLen( nLineWidth );
+ aLineInfo.SetDashLen( nLineWidth << 2 );
+ eLineStyle = LineStyle::Dash;
+ break;
+ case PEN_SOLID:
+ break; // -Wall not handled...
+ }
+ aLineInfo.SetStyle( eLineStyle );
+}
+
+void OS2METReader::SetRasterOp(RasterOp eROP)
+{
+ if (pVirDev->GetRasterOp()!=eROP) pVirDev->SetRasterOp(eROP);
+}
+
+void OS2METReader::SetPalette0RGB(sal_uInt16 nIndex, sal_uInt32 nCol)
+{
+ if (pPaletteStack==nullptr) {
+ pPaletteStack=new OSPalette;
+ pPaletteStack->pSucc=nullptr;
+ pPaletteStack->p0RGB=nullptr;
+ pPaletteStack->nSize=0;
+ }
+ if (pPaletteStack->p0RGB==nullptr || nIndex>=pPaletteStack->nSize) {
+ sal_uInt32 * pOld0RGB=pPaletteStack->p0RGB;
+ size_t nOldSize = pPaletteStack->nSize;
+ if (pOld0RGB==nullptr) nOldSize=0;
+ pPaletteStack->nSize=2*(nIndex+1);
+ if (pPaletteStack->nSize<256) pPaletteStack->nSize=256;
+ pPaletteStack->p0RGB = new sal_uInt32[pPaletteStack->nSize];
+ for (size_t i=0; i < pPaletteStack->nSize; ++i)
+ {
+ if (i<nOldSize) pPaletteStack->p0RGB[i]=pOld0RGB[i];
+ else if (i==0) pPaletteStack->p0RGB[i]=0x00ffffff;
+ else pPaletteStack->p0RGB[i]=0;
+ }
+ delete[] pOld0RGB;
+ }
+ pPaletteStack->p0RGB[nIndex]=nCol;
+}
+
+sal_uInt32 OS2METReader::GetPalette0RGB(sal_uInt32 nIndex) const
+{
+ if (pPaletteStack!=nullptr && pPaletteStack->p0RGB!=nullptr &&
+ pPaletteStack->nSize>nIndex) nIndex=pPaletteStack->p0RGB[nIndex];
+ return nIndex;
+}
+
+Color OS2METReader::GetPaletteColor(sal_uInt32 nIndex) const
+{
+ nIndex=GetPalette0RGB(nIndex);
+ return Color(sal::static_int_cast< sal_uInt8 >((nIndex>>16)&0xff),
+ sal::static_int_cast< sal_uInt8 >((nIndex>>8)&0xff),
+ sal::static_int_cast< sal_uInt8 >(nIndex&0xff));
+}
+
+sal_uInt16 OS2METReader::ReadBigEndianWord()
+{
+ sal_uInt8 nLo(0), nHi(0);
+ pOS2MET->ReadUChar( nHi ).ReadUChar( nLo );
+ return (static_cast<sal_uInt16>(nHi)<<8)|(static_cast<sal_uInt16>(nLo)&0x00ff);
+}
+
+sal_uInt32 OS2METReader::ReadBigEndian3BytesLong()
+{
+ sal_uInt8 nHi(0);
+ pOS2MET->ReadUChar( nHi );
+ sal_uInt16 nLo = ReadBigEndianWord();
+ return ((static_cast<sal_uInt32>(nHi)<<16)&0x00ff0000)|static_cast<sal_uInt32>(nLo);
+}
+
+sal_uInt32 OS2METReader::ReadLittleEndian3BytesLong()
+{
+ sal_uInt8 nHi(0), nMed(0), nLo(0);
+
+ pOS2MET->ReadUChar( nLo ).ReadUChar( nMed ).ReadUChar( nHi );
+ return ((static_cast<sal_uInt32>(nHi)&0xff)<<16)|((static_cast<sal_uInt32>(nMed)&0xff)<<8)|(static_cast<sal_uInt32>(nLo)&0xff);
+}
+
+sal_Int32 OS2METReader::ReadCoord(bool b32)
+{
+ sal_Int32 l(0);
+
+ if (b32) pOS2MET->ReadInt32( l );
+ else { short s(0); pOS2MET->ReadInt16( s ); l = static_cast<sal_Int32>(s); }
+ return l;
+}
+
+Point OS2METReader::ReadPoint( const bool bAdjustBoundRect )
+{
+ sal_Int32 x = ReadCoord(bCoord32);
+ sal_Int32 y = ReadCoord(bCoord32);
+ x=x-aBoundingRect.Left();
+ y=aBoundingRect.Bottom()-y;
+
+ if (bAdjustBoundRect)
+ {
+ if (x == SAL_MAX_INT32 || y == SAL_MAX_INT32)
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ else
+ aCalcBndRect.Union(tools::Rectangle(x, y, x + 1, y + 1));
+ }
+
+ return Point(x,y);
+}
+
+RasterOp OS2METReader::OS2MixToRasterOp(sal_uInt8 nMix)
+{
+ switch (nMix) {
+ case 0x0c: return RasterOp::Invert;
+ case 0x04: return RasterOp::Xor;
+ case 0x0b: return RasterOp::Xor;
+ default: return RasterOp::OverPaint;
+ }
+}
+
+void OS2METReader::ReadLine(bool bGivenPos, sal_uInt16 nOrderLen)
+{
+ sal_uInt16 i,nPolySize;
+
+ if (bCoord32) nPolySize=nOrderLen/8; else nPolySize=nOrderLen/4;
+ if (!bGivenPos) nPolySize++;
+ if (nPolySize==0) return;
+ tools::Polygon aPolygon(nPolySize);
+ for (i=0; i<nPolySize; i++) {
+ if (i==0 && !bGivenPos) aPolygon.SetPoint(aAttr.aCurPos,i);
+ else aPolygon.SetPoint(ReadPoint(),i);
+ }
+ aAttr.aCurPos=aPolygon.GetPoint(nPolySize-1);
+ if (pAreaStack!=nullptr) AddPointsToArea(aPolygon);
+ else if (pPathStack!=nullptr) AddPointsToPath(aPolygon);
+ else
+ {
+ SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
+ SetRasterOp(aAttr.eLinMix);
+ DrawPolyLine( aPolygon );
+ }
+}
+
+void OS2METReader::ReadRelLine(bool bGivenPos, sal_uInt16 nOrderLen)
+{
+ sal_uInt16 i,nPolySize;
+ Point aP0;
+
+ if (bGivenPos) {
+ aP0=ReadPoint();
+ if (bCoord32) nOrderLen-=8; else nOrderLen-=4;
+ }
+ else aP0=aAttr.aCurPos;
+ if (nOrderLen > pOS2MET->remainingSize())
+ throw css::uno::Exception("attempt to read past end of input", nullptr);
+ nPolySize=nOrderLen/2;
+ if (nPolySize==0) return;
+ tools::Polygon aPolygon(nPolySize);
+ for (i=0; i<nPolySize; i++) {
+ sal_Int8 nsignedbyte;
+ pOS2MET->ReadSChar( nsignedbyte ); aP0.AdjustX(static_cast<sal_Int32>(nsignedbyte));
+ pOS2MET->ReadSChar( nsignedbyte ); aP0.AdjustY(-static_cast<sal_Int32>(nsignedbyte));
+ aCalcBndRect.Union(tools::Rectangle(aP0,Size(1,1)));
+ aPolygon.SetPoint(aP0,i);
+ }
+ aAttr.aCurPos=aPolygon.GetPoint(nPolySize-1);
+ if (pAreaStack!=nullptr) AddPointsToArea(aPolygon);
+ else if (pPathStack!=nullptr) AddPointsToPath(aPolygon);
+ else
+ {
+ SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
+ SetRasterOp(aAttr.eLinMix);
+ DrawPolyLine( aPolygon );
+ }
+}
+
+void OS2METReader::ReadBox(bool bGivenPos)
+{
+ sal_uInt8 nFlags;
+ Point P0;
+
+ pOS2MET->ReadUChar( nFlags );
+ pOS2MET->SeekRel(1);
+
+ if ( bGivenPos )
+ P0 = ReadPoint();
+ else
+ P0 = aAttr.aCurPos;
+
+ aAttr.aCurPos = ReadPoint();
+ sal_Int32 nHRound = ReadCoord(bCoord32);
+ sal_Int32 nVRound = ReadCoord(bCoord32);
+
+ if (!pOS2MET->good())
+ {
+ SAL_WARN("filter.os2met", "OS2METReader::ReadBox: short read");
+ return;
+ }
+
+ tools::Rectangle aBoxRect( P0, aAttr.aCurPos );
+
+ if ( pAreaStack )
+ AddPointsToArea( tools::Polygon( aBoxRect ) );
+ else if ( pPathStack )
+ AddPointsToPath( tools::Polygon( aBoxRect ) );
+ else
+ {
+ if ( nFlags & 0x20 )
+ SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
+ else
+ SetPen( COL_TRANSPARENT );
+
+ if ( nFlags & 0x40 )
+ {
+ ChangeBrush(aAttr.aPatCol, aAttr.bFill);
+ SetRasterOp(aAttr.ePatMix);
+ }
+ else
+ {
+ ChangeBrush( COL_TRANSPARENT, false );
+ SetRasterOp(aAttr.eLinMix);
+ }
+
+ if ( IsLineInfo() )
+ {
+ tools::Polygon aPolygon( aBoxRect, nHRound, nVRound );
+ if ( nFlags & 0x40 )
+ {
+ pVirDev->Push( vcl::PushFlags::LINECOLOR );
+ pVirDev->SetLineColor( COL_TRANSPARENT );
+ pVirDev->DrawRect( aBoxRect, nHRound, nVRound );
+ pVirDev->Pop();
+ }
+ pVirDev->DrawPolyLine( aPolygon, aLineInfo );
+ }
+ else
+ pVirDev->DrawRect( aBoxRect, nHRound, nVRound );
+ }
+}
+
+void OS2METReader::ReadBitBlt()
+{
+ pOS2MET->SeekRel(4);
+ sal_uInt32 nID(0);
+ pOS2MET->ReadUInt32( nID );
+ pOS2MET->SeekRel(4);
+ Point aP1 = ReadPoint();
+ Point aP2 = ReadPoint();
+ if (aP1.X() > aP2.X()) { auto nt=aP1.X(); aP1.setX(aP2.X() ); aP2.setX(nt ); }
+ if (aP1.Y() > aP2.Y()) { auto nt=aP1.Y(); aP1.setY(aP2.Y() ); aP2.setY(nt ); }
+ Size aSize(aP2.X() - aP1.X(), aP2.Y() - aP1.Y());
+
+ OSBitmap* pB = pBitmapList;
+ while (pB!=nullptr && pB->nID!=nID) pB=pB->pSucc;
+ if (pB!=nullptr) {
+ SetRasterOp(aAttr.ePatMix);
+ pVirDev->DrawBitmapEx(aP1,aSize,pB->aBitmapEx);
+ }
+}
+
+void OS2METReader::ReadChrStr(bool bGivenPos, bool bMove, bool bExtra, sal_uInt16 nOrderLen)
+{
+ Point aP0;
+ sal_uInt16 nLen;
+ OSFont * pF;
+ vcl::Font aFont;
+ Size aSize;
+
+ pF = pFontList;
+ while (pF!=nullptr && pF->nID!=aAttr.nChrSet) pF=pF->pSucc;
+ if (pF!=nullptr)
+ aFont = pF->aFont;
+ aFont.SetColor(aAttr.aChrCol);
+ aFont.SetFontSize(Size(0,aAttr.nChrCellHeight));
+ if ( aAttr.nChrAng )
+ aFont.SetOrientation(aAttr.nChrAng);
+
+ if (bGivenPos)
+ aP0 = ReadPoint();
+ else
+ aP0 = aAttr.aCurPos;
+ if (bExtra)
+ {
+ pOS2MET->SeekRel(2);
+ ReadPoint( false );
+ ReadPoint( false );
+ pOS2MET->ReadUInt16( nLen );
+ }
+ else
+ {
+ if ( !bGivenPos )
+ nLen = nOrderLen;
+ else if ( bCoord32 )
+ nLen = nOrderLen-8;
+ else
+ nLen = nOrderLen-4;
+ }
+ if (!pOS2MET->good() || nLen > pOS2MET->remainingSize())
+ throw css::uno::Exception("attempt to read past end of input", nullptr);
+ std::unique_ptr<char[]> pChr(new char[nLen+1]);
+ for (sal_uInt16 i=0; i<nLen; i++)
+ pOS2MET->ReadChar( pChr[i] );
+ pChr[nLen] = 0;
+ OUString aStr( pChr.get(), strlen(pChr.get()), osl_getThreadTextEncoding() );
+ SetRasterOp(aAttr.eChrMix);
+ if (pVirDev->GetFont()!=aFont)
+ pVirDev->SetFont(aFont);
+ pVirDev->DrawText(aP0,aStr);
+
+ aSize = Size( pVirDev->GetTextWidth(aStr), pVirDev->GetTextHeight() );
+ if ( !aAttr.nChrAng )
+ {
+ aCalcBndRect.Union(tools::Rectangle( Point(aP0.X(),aP0.Y()-aSize.Height()),
+ Size(aSize.Width(),aSize.Height()*2)));
+ if (bMove)
+ aAttr.aCurPos = Point( aP0.X() + aSize.Width(), aP0.Y());
+ }
+ else
+ {
+ tools::Polygon aDummyPoly(4);
+
+ aDummyPoly.SetPoint( Point( aP0.X(), aP0.Y() ), 0); // TOP LEFT
+ aDummyPoly.SetPoint( Point( aP0.X(), aP0.Y() - aSize.Height() ), 1); // BOTTOM LEFT
+ aDummyPoly.SetPoint( Point( aP0.X() + aSize.Width(), aP0.Y() ), 2); // TOP RIGHT
+ aDummyPoly.SetPoint( Point( aP0.X() + aSize.Width(), aP0.Y() - aSize.Height() ), 3);// BOTTOM RIGHT
+ aDummyPoly.Rotate( aP0, aAttr.nChrAng );
+ if ( bMove )
+ aAttr.aCurPos = aDummyPoly.GetPoint( 0 );
+ aCalcBndRect.Union( tools::Rectangle( aDummyPoly.GetPoint( 0 ), aDummyPoly.GetPoint( 3 ) ) );
+ aCalcBndRect.Union( tools::Rectangle( aDummyPoly.GetPoint( 1 ), aDummyPoly.GetPoint( 2 ) ) );
+ }
+}
+
+void OS2METReader::ReadArc(bool bGivenPos)
+{
+ Point aP1, aP2, aP3;
+ double x1,y1,x2,y2,x3,y3,p,q,cx,cy,ncx,ncy,r,rx,ry,w1,w3;
+ if (bGivenPos) aP1=ReadPoint(); else aP1=aAttr.aCurPos;
+ aP2=ReadPoint(); aP3=ReadPoint();
+ aAttr.aCurPos=aP3;
+ SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
+ SetRasterOp(aAttr.eLinMix);
+ // Ok, given are 3 point of the ellipse, and the relation
+ // of width and height (as p to q):
+ x1=aP1.X(); y1=aP1.Y();
+ x2=aP2.X(); y2=aP2.Y();
+ x3=aP3.X(); y3=aP3.Y();
+ p=aAttr.nArcP;q=aAttr.nArcQ;
+ // Calculation of the center point cx, cy of the ellipse:
+ ncy=2*p*p*((y3-y1)*(x2-x1)-(y1-y2)*(x1-x3));
+ ncx=2*q*q*(x2-x1);
+ if ( (ncx<0.001 && ncx>-0.001) || (ncy<0.001 && ncy>-0.001) ) {
+ // Calculation impossible, points are all on the same straight line
+ pVirDev->DrawLine(aP1,aP2);
+ pVirDev->DrawLine(aP2,aP3);
+ return;
+ }
+ cy=( q*q*((x3*x3-x1*x1)*(x2-x1)+(x2*x2-x1*x1)*(x1-x3)) +
+ p*p*((y3*y3-y1*y1)*(x2-x1)+(y2*y2-y1*y1)*(x1-x3)) ) / ncy;
+ cx=( q*q*(x2*x2-x1*x1)+p*p*(y2*y2-y1*y1)+cy*2*p*p*(y1-y2) ) / ncx;
+ // now we still need the radius in x and y direction:
+ r=sqrt(q*q*(x1-cx)*(x1-cx)+p*p*(y1-cy)*(y1-cy));
+ rx=r/q; ry=r/p;
+ // We now have to find out how the starting and the end point
+ // have to be chosen so that point no. 2 lies inside the drawn arc:
+ w1=fmod((atan2(x1-cx,y1-cy)-atan2(x2-cx,y2-cy)),6.28318530718); if (w1<0) w1+=6.28318530718;
+ w3=fmod((atan2(x3-cx,y3-cy)-atan2(x2-cx,y2-cy)),6.28318530718); if (w3<0) w3+=6.28318530718;
+ if (w3<w1) {
+ pVirDev->DrawArc(tools::Rectangle(static_cast<sal_Int32>(cx-rx),static_cast<sal_Int32>(cy-ry),
+ static_cast<sal_Int32>(cx+rx),static_cast<sal_Int32>(cy+ry)),aP1,aP3);
+ }
+ else {
+ pVirDev->DrawArc(tools::Rectangle(static_cast<sal_Int32>(cx-rx),static_cast<sal_Int32>(cy-ry),
+ static_cast<sal_Int32>(cx+rx),static_cast<sal_Int32>(cy+ry)),aP3,aP1);
+ }
+}
+
+void OS2METReader::ReadFullArc(bool bGivenPos, sal_uInt16 nOrderSize)
+{
+ Point aCenter;
+ tools::Rectangle aRect;
+
+ if (bGivenPos) {
+ aCenter=ReadPoint();
+ if (bCoord32) nOrderSize-=8; else nOrderSize-=4;
+ }
+ else aCenter=aAttr.aCurPos;
+
+ sal_Int32 nP = aAttr.nArcP;
+ sal_Int32 nQ = aAttr.nArcQ;
+ if (nP < 0)
+ nP = o3tl::saturating_toggle_sign(nP);
+ if (nQ < 0)
+ nQ = o3tl::saturating_toggle_sign(nQ);
+ sal_uInt32 nMul(0);
+ if (nOrderSize>=4)
+ pOS2MET->ReadUInt32( nMul );
+ else {
+ sal_uInt16 nMulS(0);
+ pOS2MET->ReadUInt16( nMulS );
+ nMul=static_cast<sal_uInt32>(nMulS)<<8;
+ }
+ if (nMul!=0x00010000) {
+ nP=(nP*nMul)>>16;
+ nQ=(nQ*nMul)>>16;
+ }
+
+ aRect=tools::Rectangle(aCenter.X()-nP,aCenter.Y()-nQ,
+ aCenter.X()+nP,aCenter.Y()+nQ);
+ aCalcBndRect.Union(aRect);
+
+ if (pAreaStack!=nullptr) {
+ ChangeBrush(aAttr.aPatCol, aAttr.bFill);
+ SetRasterOp(aAttr.ePatMix);
+ if ((pAreaStack->nFlags&0x40)!=0)
+ SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
+ else
+ SetPen( COL_TRANSPARENT, 0, PEN_NULL );
+ }
+ else
+ {
+ SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
+ ChangeBrush(COL_TRANSPARENT, false);
+ SetRasterOp(aAttr.eLinMix);
+ }
+ pVirDev->DrawEllipse(aRect);
+}
+
+void OS2METReader::ReadPartialArc(bool bGivenPos, sal_uInt16 nOrderSize)
+{
+ Point aP0, aCenter,aPStart,aPEnd;
+ tools::Rectangle aRect;
+
+ if (bGivenPos) {
+ aP0=ReadPoint();
+ if (bCoord32) nOrderSize-=8; else nOrderSize-=4;
+ }
+ else aP0=aAttr.aCurPos;
+ aCenter=ReadPoint();
+
+ sal_Int32 nP = aAttr.nArcP;
+ sal_Int32 nQ = aAttr.nArcQ;
+ if (nP < 0)
+ nP = o3tl::saturating_toggle_sign(nP);
+ if (nQ < 0)
+ nQ = o3tl::saturating_toggle_sign(nQ);
+ sal_uInt32 nMul(0);
+ if (nOrderSize>=12)
+ pOS2MET->ReadUInt32( nMul );
+ else {
+ sal_uInt16 nMulS(0);
+ pOS2MET->ReadUInt16( nMulS );
+ nMul=static_cast<sal_uInt32>(nMulS)<<8;
+ }
+ if (nMul!=0x00010000) {
+ nP=(nP*nMul)>>16;
+ nQ=(nQ*nMul)>>16;
+ }
+
+ sal_Int32 nStart(0), nSweep(0);
+ pOS2MET->ReadInt32( nStart ).ReadInt32( nSweep );
+ double fStart = basegfx::deg2rad<65536>(static_cast<double>(nStart));
+ double fEnd = fStart+ basegfx::deg2rad<65536>(static_cast<double>(nSweep));
+ aPStart=Point(aCenter.X()+static_cast<sal_Int32>( cos(fStart)*nP),
+ aCenter.Y()+static_cast<sal_Int32>(-sin(fStart)*nQ));
+ aPEnd= Point(aCenter.X()+static_cast<sal_Int32>( cos(fEnd)*nP),
+ aCenter.Y()+static_cast<sal_Int32>(-sin(fEnd)*nQ));
+
+ aRect=tools::Rectangle(aCenter.X()-nP,aCenter.Y()-nQ,
+ aCenter.X()+nP,aCenter.Y()+nQ);
+ aCalcBndRect.Union(aRect);
+
+ SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
+ SetRasterOp(aAttr.eLinMix);
+
+ pVirDev->DrawLine(aP0,aPStart);
+ pVirDev->DrawArc(aRect,aPStart,aPEnd);
+ aAttr.aCurPos=aPEnd;
+}
+
+void OS2METReader::ReadPolygons()
+{
+ tools::PolyPolygon aPolyPoly;
+ tools::Polygon aPoly;
+ Point aPoint;
+
+ sal_uInt8 nFlags(0);
+ sal_uInt32 nNumPolys(0);
+ pOS2MET->ReadUChar(nFlags).ReadUInt32(nNumPolys);
+
+ if (!pOS2MET->good() || nNumPolys > SAL_MAX_UINT16)
+ {
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ ErrorCode=11;
+ return;
+ }
+
+ for (sal_uInt32 i=0; i<nNumPolys; ++i)
+ {
+ sal_uInt32 nNumPoints(0);
+ pOS2MET->ReadUInt32(nNumPoints);
+ sal_uInt32 nLimit = SAL_MAX_UINT16;
+ if (i==0) --nLimit;
+ if (!pOS2MET->good() || nNumPoints > nLimit)
+ {
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ ErrorCode=11;
+ return;
+ }
+ if (i==0) ++nNumPoints;
+ aPoly.SetSize(static_cast<short>(nNumPoints));
+ for (sal_uInt32 j=0; j<nNumPoints; ++j)
+ {
+ if (i==0 && j==0) aPoint=aAttr.aCurPos;
+ else aPoint=ReadPoint();
+ aPoly.SetPoint(aPoint,static_cast<short>(j));
+ if (i==nNumPolys-1 && j==nNumPoints-1) aAttr.aCurPos=aPoint;
+ }
+ aPolyPoly.Insert(aPoly);
+ }
+
+ ChangeBrush(aAttr.aPatCol, aAttr.bFill);
+ SetRasterOp(aAttr.ePatMix);
+ if ((nFlags&0x01)!=0)
+ SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
+ else
+ SetPen( COL_TRANSPARENT, 0, PEN_NULL );
+ DrawPolyPolygon( aPolyPoly );
+}
+
+void OS2METReader::ReadBezier(bool bGivenPos, sal_uInt16 nOrderLen)
+{
+ sal_uInt16 i, nNumPoints = nOrderLen / ( bCoord32 ? 8 : 4 );
+
+ if( !bGivenPos )
+ nNumPoints++;
+
+ if( !nNumPoints )
+ return;
+
+ tools::Polygon aPolygon( nNumPoints );
+
+ for( i=0; i < nNumPoints; i++ )
+ {
+ if( i==0 && !bGivenPos)
+ aPolygon.SetPoint( aAttr.aCurPos, i );
+ else
+ aPolygon.SetPoint( ReadPoint(), i );
+ }
+
+ if( !( nNumPoints % 4 ) )
+ {
+ // create bezier polygon
+ const sal_uInt16 nSegPoints = 25;
+ const sal_uInt16 nSegments = aPolygon.GetSize() >> 2;
+ tools::Polygon aBezPoly( nSegments * nSegPoints );
+
+ sal_uInt16 nSeg, nBezPos, nStartPos;
+ for( nSeg = 0, nBezPos = 0, nStartPos = 0; nSeg < nSegments; nSeg++, nStartPos += 4 )
+ {
+ const tools::Polygon aSegPoly( aPolygon[ nStartPos ], aPolygon[ nStartPos + 1 ],
+ aPolygon[ nStartPos + 3 ], aPolygon[ nStartPos + 2 ],
+ nSegPoints );
+
+ for( sal_uInt16 nSegPos = 0; nSegPos < nSegPoints; )
+ aBezPoly[ nBezPos++ ] = aSegPoly[ nSegPos++ ];
+ }
+
+ nNumPoints = nBezPos;
+
+ if( nNumPoints != aBezPoly.GetSize() )
+ aBezPoly.SetSize( nNumPoints );
+
+ aPolygon = aBezPoly;
+ }
+
+ aAttr.aCurPos = aPolygon[ nNumPoints - 1 ];
+
+ if (pAreaStack!=nullptr)
+ AddPointsToArea(aPolygon);
+ else if (pPathStack!=nullptr)
+ AddPointsToPath(aPolygon);
+ else
+ {
+ SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
+ SetRasterOp(aAttr.eLinMix);
+ DrawPolyLine( aPolygon );
+ }
+}
+
+void OS2METReader::ReadFillet(bool bGivenPos, sal_uInt16 nOrderLen)
+{
+ sal_uInt16 i,nNumPoints;
+
+ if (bCoord32) nNumPoints=nOrderLen/8; else nNumPoints=nOrderLen/4;
+ if (!bGivenPos) nNumPoints++;
+ if (nNumPoints==0) return;
+ tools::Polygon aPolygon(nNumPoints);
+ for (i=0; i<nNumPoints; i++) {
+ if (i==0 && !bGivenPos) aPolygon.SetPoint(aAttr.aCurPos,i);
+ else aPolygon.SetPoint(ReadPoint(),i);
+ }
+ aAttr.aCurPos=aPolygon.GetPoint(nNumPoints-1);
+ if (pAreaStack!=nullptr) AddPointsToArea(aPolygon);
+ else if (pPathStack!=nullptr) AddPointsToPath(aPolygon);
+ else {
+ SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
+ SetRasterOp(aAttr.eLinMix);
+ DrawPolyLine( aPolygon );
+ }
+}
+
+void OS2METReader::ReadFilletSharp(bool bGivenPos, sal_uInt16 nOrderLen)
+{
+ if (bGivenPos) {
+ aAttr.aCurPos=ReadPoint();
+ if (bCoord32) nOrderLen-=8; else nOrderLen-=4;
+ }
+
+ sal_uInt16 nNumPoints;
+ if (bCoord32) nNumPoints=1+nOrderLen/10;
+ else nNumPoints=1+nOrderLen/6;
+
+ tools::Polygon aPolygon(nNumPoints);
+ aPolygon.SetPoint(aAttr.aCurPos, 0);
+ for (sal_uInt16 i = 1; i <nNumPoints; ++i)
+ aPolygon.SetPoint(ReadPoint(), i);
+
+ if (!pOS2MET->good())
+ {
+ SAL_WARN("filter.os2met", "OS2METReader::ReadFilletSharp: short read");
+ return;
+ }
+
+ aAttr.aCurPos=aPolygon.GetPoint(nNumPoints-1);
+ if (pAreaStack!=nullptr) AddPointsToArea(aPolygon);
+ else if (pPathStack!=nullptr) AddPointsToPath(aPolygon);
+ else
+ {
+ SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
+ SetRasterOp(aAttr.eLinMix);
+ DrawPolyLine( aPolygon );
+ }
+}
+
+void OS2METReader::ReadMarker(bool bGivenPos, sal_uInt16 nOrderLen)
+{
+ sal_uInt16 i,nNumPoints;
+
+ SetPen( aAttr.aMrkCol );
+ SetRasterOp(aAttr.eMrkMix);
+ if (aAttr.nMrkSymbol>=5 && aAttr.nMrkSymbol<=9)
+ {
+ ChangeBrush(aAttr.aMrkCol, true);
+ }
+ else
+ {
+ ChangeBrush(COL_TRANSPARENT, false);
+ }
+ if (bCoord32) nNumPoints=nOrderLen/8; else nNumPoints=nOrderLen/4;
+ if (!bGivenPos) nNumPoints++;
+ for (i=0; i<nNumPoints; i++) {
+ if (i!=0 || bGivenPos) aAttr.aCurPos=ReadPoint();
+ const auto x = aAttr.aCurPos.X();
+ const auto y = aAttr.aCurPos.Y();
+ aCalcBndRect.Union(tools::Rectangle(x-5,y-5,x+5,y+5));
+ switch (aAttr.nMrkSymbol) {
+ case 2: // PLUS
+ pVirDev->DrawLine(Point(x-4,y),Point(x+4,y));
+ pVirDev->DrawLine(Point(x,y-4),Point(x,y+4));
+ break;
+ case 3: // DIAMOND
+ case 7: { // SOLIDDIAMOND
+ tools::Polygon aPoly(4);
+ aPoly.SetPoint(Point(x,y+4),0);
+ aPoly.SetPoint(Point(x+4,y),1);
+ aPoly.SetPoint(Point(x,y-4),2);
+ aPoly.SetPoint(Point(x-4,y),3);
+ pVirDev->DrawPolygon(aPoly);
+ break;
+ }
+ case 4: // SQUARE
+ case 8: { // SOLIDSUARE
+ tools::Polygon aPoly(4);
+ aPoly.SetPoint(Point(x+4,y+4),0);
+ aPoly.SetPoint(Point(x+4,y-4),1);
+ aPoly.SetPoint(Point(x-4,y-4),2);
+ aPoly.SetPoint(Point(x-4,y+4),3);
+ pVirDev->DrawPolygon(aPoly);
+ break;
+ }
+ case 5: { // SIXPOINTSTAR
+ tools::Polygon aPoly(12);
+ aPoly.SetPoint(Point(x ,y-4),0);
+ aPoly.SetPoint(Point(x+2,y-2),1);
+ aPoly.SetPoint(Point(x+4,y-2),2);
+ aPoly.SetPoint(Point(x+2,y ),3);
+ aPoly.SetPoint(Point(x+4,y+2),4);
+ aPoly.SetPoint(Point(x+2,y+2),5);
+ aPoly.SetPoint(Point(x ,y+4),6);
+ aPoly.SetPoint(Point(x-2,y+2),7);
+ aPoly.SetPoint(Point(x-4,y+2),8);
+ aPoly.SetPoint(Point(x-2,y ),9);
+ aPoly.SetPoint(Point(x-4,y-2),10);
+ aPoly.SetPoint(Point(x-2,y-2),11);
+ pVirDev->DrawPolygon(aPoly);
+ break;
+ }
+ case 6: { // EIGHTPOINTSTAR
+ tools::Polygon aPoly(16);
+ aPoly.SetPoint(Point(x ,y-4),0);
+ aPoly.SetPoint(Point(x+1,y-2),1);
+ aPoly.SetPoint(Point(x+3,y-3),2);
+ aPoly.SetPoint(Point(x+2,y-1),3);
+ aPoly.SetPoint(Point(x+4,y ),4);
+ aPoly.SetPoint(Point(x+2,y+1),5);
+ aPoly.SetPoint(Point(x+3,y+3),6);
+ aPoly.SetPoint(Point(x+1,y+2),7);
+ aPoly.SetPoint(Point(x ,y+4),8);
+ aPoly.SetPoint(Point(x-1,y+2),9);
+ aPoly.SetPoint(Point(x-3,y+3),10);
+ aPoly.SetPoint(Point(x-2,y+1),11);
+ aPoly.SetPoint(Point(x-4,y ),12);
+ aPoly.SetPoint(Point(x-2,y-1),13);
+ aPoly.SetPoint(Point(x-3,y-3),14);
+ aPoly.SetPoint(Point(x-1,y-2),15);
+ pVirDev->DrawPolygon(aPoly);
+ break;
+ }
+ case 9: // DOT
+ pVirDev->DrawEllipse(tools::Rectangle(x-1,y-1,x+1,y+1));
+ break;
+ case 10: // SMALLCIRCLE
+ pVirDev->DrawEllipse(tools::Rectangle(x-2,y-2,x+2,y+2));
+ break;
+ case 64: // BLANK
+ break;
+ default: // (=1) CROSS
+ pVirDev->DrawLine(Point(x-4,y-4),Point(x+4,y+4));
+ pVirDev->DrawLine(Point(x-4,y+4),Point(x+4,y-4));
+ break;
+ }
+ }
+}
+
+void OS2METReader::ReadOrder(sal_uInt16 nOrderID, sal_uInt16 nOrderLen)
+{
+ switch (nOrderID) {
+
+ case GOrdGivArc: ReadArc(true); break;
+ case GOrdCurArc: ReadArc(false); break;
+
+ case GOrdGivBzr: ReadBezier(true,nOrderLen); break;
+ case GOrdCurBzr: ReadBezier(false,nOrderLen); break;
+
+ case GOrdGivBox: ReadBox(true); break;
+ case GOrdCurBox: ReadBox(false); break;
+
+ case GOrdGivFil: ReadFillet(true,nOrderLen); break;
+ case GOrdCurFil: ReadFillet(false,nOrderLen); break;
+
+ case GOrdGivCrc: ReadFullArc(true,nOrderLen); break;
+ case GOrdCurCrc: ReadFullArc(false,nOrderLen); break;
+
+ case GOrdGivLin: ReadLine(true, nOrderLen); break;
+ case GOrdCurLin: ReadLine(false, nOrderLen); break;
+
+ case GOrdGivMrk: ReadMarker(true, nOrderLen); break;
+ case GOrdCurMrk: ReadMarker(false, nOrderLen); break;
+
+ case GOrdGivArP: ReadPartialArc(true,nOrderLen); break;
+ case GOrdCurArP: ReadPartialArc(false,nOrderLen); break;
+
+ case GOrdGivRLn: ReadRelLine(true,nOrderLen); break;
+ case GOrdCurRLn: ReadRelLine(false,nOrderLen); break;
+
+ case GOrdGivSFl: ReadFilletSharp(true,nOrderLen); break;
+ case GOrdCurSFl: ReadFilletSharp(false,nOrderLen); break;
+
+ case GOrdGivStM: ReadChrStr(true , true , false, nOrderLen); break;
+ case GOrdCurStM: ReadChrStr(false, true , false, nOrderLen); break;
+ case GOrdGivStr: ReadChrStr(true , false, false, nOrderLen); break;
+ case GOrdCurStr: ReadChrStr(false, false, false, nOrderLen); break;
+ case GOrdGivStx: ReadChrStr(true , false, true , nOrderLen); break;
+ case GOrdCurStx: ReadChrStr(false, false, true , nOrderLen); break;
+
+ case GOrdGivImg: SAL_INFO("filter.os2met","GOrdGivImg");
+ break;
+ case GOrdCurImg: SAL_INFO("filter.os2met","GOrdCurImg");
+ break;
+ case GOrdImgDat: SAL_INFO("filter.os2met","GOrdImgDat");
+ break;
+ case GOrdEndImg: SAL_INFO("filter.os2met","GOrdEndImg");
+ break;
+
+ case GOrdBegAra: {
+ OSArea * p=new OSArea;
+ p->bClosed=false;
+ p->pSucc=pAreaStack; pAreaStack=p;
+ pOS2MET->ReadUChar( p->nFlags );
+ p->aCol=aAttr.aPatCol;
+ p->aBgCol=aAttr.aPatBgCol;
+ p->eMix=aAttr.ePatMix;
+ p->eBgMix=aAttr.ePatBgMix;
+ p->bFill=aAttr.bFill;
+ break;
+ }
+ case GOrdEndAra:
+ {
+ OSArea * p=pAreaStack;
+ if ( p )
+ {
+ pAreaStack = p->pSucc;
+ if ( pPathStack )
+ {
+ for ( sal_uInt16 i=0; i<p->aPPoly.Count(); i++ )
+ {
+ AddPointsToPath( p->aPPoly.GetObject( i ) );
+ CloseFigure();
+ }
+ }
+ else
+ {
+ if ( ( p->nFlags & 0x40 ) == 0 )
+ SetPen( COL_TRANSPARENT, 0, PEN_NULL );
+ else
+ SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
+
+ ChangeBrush(p->aCol, p->bFill);
+ SetRasterOp(p->eMix);
+ DrawPolyPolygon( p->aPPoly );
+ }
+ delete p;
+ }
+ }
+ break;
+
+ case GOrdBegElm: SAL_INFO("filter.os2met","GOrdBegElm");
+ break;
+ case GOrdEndElm: SAL_INFO("filter.os2met","GOrdEndElm");
+ break;
+
+ case GOrdBegPth: {
+ OSPath * p=new OSPath;
+ p->pSucc=pPathStack; pPathStack=p;
+ pOS2MET->SeekRel(2);
+ pOS2MET->ReadUInt32( p->nID );
+ p->bClosed=false;
+ p->bStroke=false;
+ break;
+ }
+ case GOrdEndPth: {
+ OSPath * p, * pprev, * psucc;
+ if (pPathStack==nullptr) break;
+ p=pPathList; pprev=nullptr;
+ while (p!=nullptr) {
+ psucc=p->pSucc;
+ if (p->nID==pPathStack->nID) {
+ if (pprev==nullptr) pPathList=psucc; else pprev->pSucc=psucc;
+ delete p;
+ }
+ else pprev=p;
+ p=psucc;
+ }
+ p=pPathStack;
+ pPathStack=p->pSucc;
+ p->pSucc=pPathList; pPathList=p;
+ break;
+ }
+ case GOrdFilPth:
+ {
+ sal_uInt32 nID(0);
+ sal_uInt16 nDummy(0);
+ OSPath* p = pPathList;
+
+ pOS2MET->ReadUInt16( nDummy )
+ .ReadUInt32( nID );
+
+ if ( ! ( nDummy & 0x20 ) ) // #30933# i do not know the exact meaning of this bit,
+ { // but if set it seems to be better not to fill this path
+ while( p && p->nID != nID )
+ p = p->pSucc;
+
+ if( p )
+ {
+ if( p->bStroke )
+ {
+ SetPen( aAttr.aPatCol, aAttr.nStrLinWidth );
+ ChangeBrush(COL_TRANSPARENT, false);
+ SetRasterOp( aAttr.ePatMix );
+ if ( IsLineInfo() )
+ {
+ for ( sal_uInt16 i = 0; i < p->aPPoly.Count(); i++ )
+ pVirDev->DrawPolyLine( p->aPPoly.GetObject( i ), aLineInfo );
+ }
+ else
+ pVirDev->DrawPolyPolygon( p->aPPoly );
+ }
+ else
+ {
+ SetPen( COL_TRANSPARENT, 0, PEN_NULL );
+ ChangeBrush( aAttr.aPatCol, aAttr.bFill );
+ SetRasterOp( aAttr.ePatMix );
+ pVirDev->DrawPolyPolygon( p->aPPoly );
+ }
+ }
+ }
+ }
+ break;
+
+ case GOrdModPth:
+ {
+ OSPath* p = pPathList;
+
+ while( p && p->nID != 1 )
+ p = p->pSucc;
+
+ if( p )
+ p->bStroke = true;
+ }
+ break;
+
+ case GOrdOutPth:
+ {
+ sal_uInt32 nID;
+ sal_uInt16 i,nC;
+ OSPath* p=pPathList;
+ pOS2MET->SeekRel(2);
+ pOS2MET->ReadUInt32( nID );
+ while (p && pOS2MET->good() && p->nID != nID)
+ p = p->pSucc;
+
+ if (p)
+ {
+ SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
+ SetRasterOp(aAttr.eLinMix);
+ ChangeBrush(COL_TRANSPARENT, false);
+ nC=p->aPPoly.Count();
+ for (i=0; i<nC; i++)
+ {
+ if (i+1<nC || p->bClosed)
+ DrawPolygon( p->aPPoly.GetObject( i ) );
+ else
+ DrawPolyLine( p->aPPoly.GetObject( i ) );
+ }
+ }
+ break;
+ }
+ case GOrdSClPth: {
+ SAL_INFO("filter.os2met","GOrdSClPth");
+ sal_uInt32 nID(0);
+ OSPath * p=pPathList;
+ pOS2MET->SeekRel(2);
+ pOS2MET->ReadUInt32( nID );
+ if (nID==0) p=nullptr;
+ while (p!=nullptr && p->nID!=nID) p=p->pSucc;
+ if (p!=nullptr) pVirDev->SetClipRegion(vcl::Region(p->aPPoly));
+ else pVirDev->SetClipRegion();
+ break;
+ }
+ case GOrdNopNop:
+ break;
+ case GOrdRemark: SAL_INFO("filter.os2met","GOrdRemark");
+ break;
+ case GOrdSegLab: SAL_INFO("filter.os2met","GOrdSegLab");
+ break;
+
+ case GOrdBitBlt: ReadBitBlt(); break;
+
+ case GOrdCalSeg: SAL_INFO("filter.os2met","GOrdCalSeg");
+ break;
+ case GOrdSSgBnd: SAL_INFO("filter.os2met","GOrdSSgBnd");
+ break;
+ case GOrdSegChr: SAL_INFO("filter.os2met","GOrdSegChr");
+ break;
+ case GOrdCloFig:
+ CloseFigure();
+ break;
+ case GOrdEndSym: SAL_INFO("filter.os2met","GOrdEndSym");
+ break;
+ case GOrdEndPlg: SAL_INFO("filter.os2met","GOrdEndPlg");
+ break;
+ case GOrdEscape: SAL_INFO("filter.os2met","GOrdEscape");
+ break;
+ case GOrdExtEsc: SAL_INFO("filter.os2met","GOrdExtEsc");
+ break;
+
+ case GOrdPolygn: ReadPolygons(); break;
+
+ case GOrdStkPop: PopAttr(); break;
+
+ case GOrdPIvAtr: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSIvAtr: {
+ sal_uInt8 nA(0), nP(0), nFlags(0);
+ Color aCol;
+ RasterOp eROP;
+ pOS2MET->ReadUChar( nA ).ReadUChar( nP ).ReadUChar( nFlags );
+ if (nOrderID==GOrdPIvAtr) {
+ pAttrStack->nIvAttrA=nA;
+ pAttrStack->nIvAttrP=nP;
+ }
+ if (nA<=2) {
+ if ((nFlags&0x80)!=0) {
+ if (nA==1) switch (nP) {
+ case 1: aAttr.aLinCol=aDefAttr.aLinCol; break;
+ case 2: aAttr.aChrCol=aDefAttr.aChrCol; break;
+ case 3: aAttr.aMrkCol=aDefAttr.aMrkCol; break;
+ case 4: aAttr.aPatCol=aDefAttr.aPatCol; break;
+ case 5: aAttr.aImgCol=aDefAttr.aImgCol; break;
+ }
+ else switch (nP) {
+ case 1: aAttr.aLinBgCol=aDefAttr.aLinBgCol; break;
+ case 2: aAttr.aChrBgCol=aDefAttr.aChrBgCol; break;
+ case 3: aAttr.aMrkBgCol=aDefAttr.aMrkBgCol; break;
+ case 4: aAttr.aPatBgCol=aDefAttr.aPatBgCol; break;
+ case 5: aAttr.aImgBgCol=aDefAttr.aImgBgCol; break;
+ }
+ }
+ else {
+ const auto nVal = ReadLittleEndian3BytesLong();
+ if ((nFlags&0x40)!=0 && nVal==1) aCol=COL_BLACK;
+ else if ((nFlags&0x40)!=0 && nVal==2) aCol=COL_WHITE;
+ else if ((nFlags&0x40)!=0 && nVal==4) aCol=COL_WHITE;
+ else if ((nFlags&0x40)!=0 && nVal==5) aCol=COL_BLACK;
+ else aCol=GetPaletteColor(nVal);
+ if (nA==1) switch (nP) {
+ case 1: aAttr.aLinCol=aCol; break;
+ case 2: aAttr.aChrCol=aCol; break;
+ case 3: aAttr.aMrkCol=aCol; break;
+ case 4: aAttr.aPatCol=aCol; break;
+ case 5: aAttr.aImgCol=aCol; break;
+ }
+ else switch (nP) {
+ case 1: aAttr.aLinBgCol=aCol; break;
+ case 2: aAttr.aChrBgCol=aCol; break;
+ case 3: aAttr.aMrkBgCol=aCol; break;
+ case 4: aAttr.aPatBgCol=aCol; break;
+ case 5: aAttr.aImgBgCol=aCol; break;
+ }
+ }
+ }
+ else {
+ sal_uInt8 nMix(0);
+ pOS2MET->ReadUChar( nMix );
+ if (nMix==0) {
+ switch (nP) {
+ case 1: aAttr.eLinBgMix=aDefAttr.eLinBgMix; break;
+ case 2: aAttr.eChrBgMix=aDefAttr.eChrBgMix; break;
+ case 3: aAttr.eMrkBgMix=aDefAttr.eMrkBgMix; break;
+ case 4: aAttr.ePatBgMix=aDefAttr.ePatBgMix; break;
+ case 5: aAttr.eImgBgMix=aDefAttr.eImgBgMix; break;
+ }
+ }
+ else {
+ eROP=OS2MixToRasterOp(nMix);
+ switch (nP) {
+ case 1: aAttr.eLinBgMix=eROP; break;
+ case 2: aAttr.eChrBgMix=eROP; break;
+ case 3: aAttr.eMrkBgMix=eROP; break;
+ case 4: aAttr.ePatBgMix=eROP; break;
+ case 5: aAttr.eImgBgMix=eROP; break;
+ }
+ }
+ }
+ break;
+ }
+ case GOrdPIxCol: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSIxCol: {
+ sal_uInt8 nFlags(0);
+ pOS2MET->ReadUChar( nFlags );
+ if ((nFlags&0x80)!=0) {
+ aAttr.aLinCol=aDefAttr.aLinCol;
+ aAttr.aChrCol=aDefAttr.aChrCol;
+ aAttr.aMrkCol=aDefAttr.aMrkCol;
+ aAttr.aPatCol=aDefAttr.aPatCol;
+ aAttr.aImgCol=aDefAttr.aImgCol;
+ }
+ else {
+ Color aCol;
+ const auto nVal = ReadLittleEndian3BytesLong();
+ if ((nFlags&0x40)!=0 && nVal==1) aCol=COL_BLACK;
+ else if ((nFlags&0x40)!=0 && nVal==2) aCol=COL_WHITE;
+ else if ((nFlags&0x40)!=0 && nVal==4) aCol=COL_WHITE;
+ else if ((nFlags&0x40)!=0 && nVal==5) aCol=COL_BLACK;
+ else aCol=GetPaletteColor(nVal);
+ aAttr.aLinCol = aAttr.aChrCol = aAttr.aMrkCol = aAttr.aPatCol =
+ aAttr.aImgCol = aCol;
+ }
+ break;
+ }
+
+ case GOrdPColor:
+ case GOrdPXtCol: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSColor:
+ case GOrdSXtCol: {
+ sal_uInt16 nVal(0);
+ if (nOrderID==GOrdPColor || nOrderID==GOrdSColor) {
+ sal_uInt8 nbyte(0);
+ pOS2MET->ReadUChar( nbyte ); nVal=static_cast<sal_uInt16>(nbyte)|0xff00;
+ }
+ else pOS2MET->ReadUInt16( nVal );
+ if (nVal==0x0000 || nVal==0xff00) {
+ aAttr.aLinCol=aDefAttr.aLinCol;
+ aAttr.aChrCol=aDefAttr.aChrCol;
+ aAttr.aMrkCol=aDefAttr.aMrkCol;
+ aAttr.aPatCol=aDefAttr.aPatCol;
+ aAttr.aImgCol=aDefAttr.aImgCol;
+ }
+ else {
+ Color aCol;
+ if (nVal==0x0007) aCol=COL_WHITE;
+ else if (nVal==0x0008) aCol=COL_BLACK;
+ else if (nVal==0xff08) aCol=GetPaletteColor(1);
+ else aCol=GetPaletteColor(static_cast<sal_uInt32>(nVal) & 0x000000ff);
+ aAttr.aLinCol = aAttr.aChrCol = aAttr.aMrkCol = aAttr.aPatCol =
+ aAttr.aImgCol = aCol;
+ }
+ break;
+ }
+
+ case GOrdPBgCol: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSBgCol: {
+ sal_uInt16 nVal(0);
+ pOS2MET->ReadUInt16( nVal );
+ if (nVal==0x0000 || nVal==0xff00) {
+ aAttr.aLinBgCol=aDefAttr.aLinBgCol;
+ aAttr.aChrBgCol=aDefAttr.aChrBgCol;
+ aAttr.aMrkBgCol=aDefAttr.aMrkBgCol;
+ aAttr.aPatBgCol=aDefAttr.aPatBgCol;
+ aAttr.aImgBgCol=aDefAttr.aImgBgCol;
+ }
+ else {
+ Color aCol;
+ if (nVal==0x0007) aCol=COL_WHITE;
+ else if (nVal==0x0008) aCol=COL_BLACK;
+ else if (nVal==0xff08) aCol=GetPaletteColor(0);
+ else aCol=GetPaletteColor(static_cast<sal_uInt32>(nVal) & 0x000000ff);
+ aAttr.aLinBgCol = aAttr.aChrBgCol = aAttr.aMrkBgCol =
+ aAttr.aPatBgCol = aAttr.aImgBgCol = aCol;
+ }
+ break;
+ }
+ case GOrdPBxCol: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSBxCol: {
+ sal_uInt8 nFlags(0);
+ pOS2MET->ReadUChar( nFlags );
+ if ((nFlags&0x80)!=0) {
+ aAttr.aLinBgCol=aDefAttr.aLinBgCol;
+ aAttr.aChrBgCol=aDefAttr.aChrBgCol;
+ aAttr.aMrkBgCol=aDefAttr.aMrkBgCol;
+ aAttr.aPatBgCol=aDefAttr.aPatBgCol;
+ aAttr.aImgBgCol=aDefAttr.aImgBgCol;
+ }
+ else {
+ Color aCol;
+ const auto nVal = ReadLittleEndian3BytesLong();
+ if ((nFlags&0x40)!=0 && nVal==1) aCol=COL_BLACK;
+ else if ((nFlags&0x40)!=0 && nVal==2) aCol=COL_WHITE;
+ else if ((nFlags&0x40)!=0 && nVal==4) aCol=COL_WHITE;
+ else if ((nFlags&0x40)!=0 && nVal==5) aCol=COL_BLACK;
+ else aCol=GetPaletteColor(nVal);
+ aAttr.aLinBgCol = aAttr.aChrBgCol = aAttr.aMrkBgCol =
+ aAttr.aPatBgCol = aAttr.aImgBgCol = aCol;
+ }
+ break;
+ }
+
+ case GOrdPMixMd: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSMixMd: {
+ sal_uInt8 nMix(0);
+ pOS2MET->ReadUChar( nMix );
+ if (nMix==0) {
+ aAttr.eLinMix=aDefAttr.eLinMix;
+ aAttr.eChrMix=aDefAttr.eChrMix;
+ aAttr.eMrkMix=aDefAttr.eMrkMix;
+ aAttr.ePatMix=aDefAttr.ePatMix;
+ aAttr.eImgMix=aDefAttr.eImgMix;
+ }
+ else {
+ aAttr.eLinMix = aAttr.eChrMix = aAttr.eMrkMix =
+ aAttr.ePatMix = aAttr.eImgMix = OS2MixToRasterOp(nMix);
+ }
+ break;
+ }
+ case GOrdPBgMix: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSBgMix: {
+ sal_uInt8 nMix(0);
+ pOS2MET->ReadUChar( nMix );
+ if (nMix==0) {
+ aAttr.eLinBgMix=aDefAttr.eLinBgMix;
+ aAttr.eChrBgMix=aDefAttr.eChrBgMix;
+ aAttr.eMrkBgMix=aDefAttr.eMrkBgMix;
+ aAttr.ePatBgMix=aDefAttr.ePatBgMix;
+ aAttr.eImgBgMix=aDefAttr.eImgBgMix;
+ }
+ else {
+ aAttr.eLinBgMix = aAttr.eChrBgMix = aAttr.eMrkBgMix =
+ aAttr.ePatBgMix = aAttr.eImgBgMix = OS2MixToRasterOp(nMix);
+ }
+ break;
+ }
+ case GOrdPPtSet: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSPtSet: SAL_INFO("filter.os2met","GOrdSPtSet");
+ break;
+
+ case GOrdPPtSym: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSPtSym: {
+ sal_uInt8 nPatt(0);
+ pOS2MET->ReadUChar( nPatt );
+ aAttr.bFill = ( nPatt != 0x0f );
+ break;
+ }
+
+ case GOrdPPtRef: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSPtRef: SAL_INFO("filter.os2met","GOrdSPtRef");
+ break;
+
+ case GOrdPLnEnd: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSLnEnd:
+ break;
+
+ case GOrdPLnJoi: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSLnJoi:
+ break;
+
+ case GOrdPLnTyp: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSLnTyp: {
+ sal_uInt8 nType(0);
+ pOS2MET->ReadUChar( nType );
+ switch (nType) {
+ case 0: aAttr.eLinStyle=aDefAttr.eLinStyle; break;
+ case 1: case 4: aAttr.eLinStyle=PEN_DOT; break;
+ case 2: case 5: aAttr.eLinStyle=PEN_DASH; break;
+ case 3: case 6: aAttr.eLinStyle=PEN_DASHDOT; break;
+ case 8: aAttr.eLinStyle=PEN_NULL; break;
+ default: aAttr.eLinStyle=PEN_SOLID;
+ }
+ break;
+ }
+ case GOrdPLnWdt: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSLnWdt: {
+ sal_uInt8 nbyte(0);
+ pOS2MET->ReadUChar( nbyte );
+ if (nbyte==0) aAttr.nLinWidth=aDefAttr.nLinWidth;
+ else aAttr.nLinWidth=static_cast<sal_uInt16>(nbyte)-1;
+ break;
+ }
+ case GOrdPFrLWd: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSFrLWd:
+ break;
+
+ case GOrdPStLWd: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSStLWd :
+ {
+ sal_uInt8 nFlags(0);
+
+ pOS2MET->ReadUChar( nFlags );
+ if ( nFlags & 0x80 )
+ aAttr.nStrLinWidth = aDefAttr.nStrLinWidth;
+ else
+ {
+ pOS2MET->SeekRel( 1 );
+ sal_Int32 nWd = ReadCoord( bCoord32 );
+ if (nWd < 0)
+ nWd = o3tl::saturating_toggle_sign(nWd);
+ aAttr.nStrLinWidth = static_cast<sal_uInt16>(nWd);
+ }
+ break;
+ }
+ case GOrdPChDir: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSChDir:
+ break;
+
+ case GOrdPChPrc: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSChPrc:
+ break;
+
+ case GOrdPChSet: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSChSet: {
+ sal_uInt8 nbyte(0);
+ pOS2MET->ReadUChar( nbyte );
+ aAttr.nChrSet=static_cast<sal_uInt32>(nbyte)&0xff;
+ break;
+ }
+ case GOrdPChAng: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSChAng: {
+ sal_Int32 nX = ReadCoord(bCoord32);
+ sal_Int32 nY = ReadCoord(bCoord32);
+ if (nX>=0 && nY==0) aAttr.nChrAng=0_deg10;
+ else {
+ aAttr.nChrAng = Degree10(static_cast<short>(basegfx::rad2deg<10>(atan2(static_cast<double>(nY),static_cast<double>(nX)))));
+ while (aAttr.nChrAng < 0_deg10) aAttr.nChrAng += 3600_deg10;
+ aAttr.nChrAng %= 3600_deg10;
+ }
+ break;
+ }
+ case GOrdPChBrx: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSChBrx:
+ break;
+
+ case GOrdPChCel: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSChCel: {
+ sal_uInt16 nLen=nOrderLen;
+ (void) ReadCoord(bCoord32); // Width, unused
+ auto nHeight = ReadCoord(bCoord32);
+ if (nHeight < 0 || nHeight > SAL_MAX_INT16)
+ {
+ SAL_WARN("filter.os2met", "ignoring out of sane range font height: " << nHeight);
+ aAttr.nChrCellHeight = aDefAttr.nChrCellHeight;
+ }
+ else
+ aAttr.nChrCellHeight = nHeight;
+ if (bCoord32) nLen-=8; else nLen-=4;
+ if (nLen>=4) {
+ pOS2MET->SeekRel(4); nLen-=4;
+ }
+ if (nLen>=2) {
+ sal_uInt8 nbyte(0);
+ pOS2MET->ReadUChar( nbyte );
+ if ((nbyte&0x80)==0 && aAttr.nChrCellHeight == 0)
+ aAttr.nChrCellHeight = aDefAttr.nChrCellHeight;
+ }
+ break;
+ }
+ case GOrdPChXtr: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSChXtr:
+ break;
+
+ case GOrdPChShr: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSChShr:
+ break;
+
+ case GOrdPTxAlg: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSTxAlg: SAL_INFO("filter.os2met","GOrdSTxAlg");
+ break;
+
+ case GOrdPMkPrc: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSMkPrc: {
+ sal_uInt8 nbyte(0);
+ pOS2MET->ReadUChar( nbyte );
+ if (nbyte==0) aAttr.nMrkPrec=aDefAttr.nMrkPrec;
+ else aAttr.nMrkPrec=nbyte;
+ break;
+ }
+
+ case GOrdPMkSet: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSMkSet: {
+ sal_uInt8 nbyte(0);
+ pOS2MET->ReadUChar( nbyte );
+ if (nbyte==0) aAttr.nMrkSet=aDefAttr.nMrkSet;
+ else aAttr.nMrkSet=nbyte;
+ break;
+ }
+
+ case GOrdPMkSym: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSMkSym: {
+ sal_uInt8 nbyte(0);
+ pOS2MET->ReadUChar( nbyte );
+ if (nbyte==0) aAttr.nMrkSymbol=aDefAttr.nMrkSymbol;
+ else aAttr.nMrkSymbol=nbyte;
+ break;
+ }
+
+ case GOrdPMkCel: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSMkCel: {
+ sal_uInt16 nLen=nOrderLen;
+ aAttr.aMrkCellSize.setWidth(ReadCoord(bCoord32) );
+ aAttr.aMrkCellSize.setHeight(ReadCoord(bCoord32) );
+ if (bCoord32) nLen-=8; else nLen-=4;
+ if (nLen>=2) {
+ sal_uInt8 nbyte(0);
+ pOS2MET->ReadUChar( nbyte );
+ if ((nbyte&0x80)==0 && aAttr.aMrkCellSize==Size(0,0))
+ aAttr.aMrkCellSize=aDefAttr.aMrkCellSize;
+ }
+ break;
+ }
+
+ case GOrdPArcPa: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSArcPa:
+ aAttr.nArcP=ReadCoord(bCoord32);
+ aAttr.nArcQ=ReadCoord(bCoord32);
+ aAttr.nArcR=ReadCoord(bCoord32);
+ aAttr.nArcS=ReadCoord(bCoord32);
+ break;
+
+ case GOrdPCrPos: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSCrPos:
+ aAttr.aCurPos=ReadPoint();
+ break;
+
+ case GOrdPMdTrn: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSMdTrn: SAL_INFO("filter.os2met","GOrdSMdTrn");
+ break;
+
+ case GOrdPPkIdn: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSPkIdn: SAL_INFO("filter.os2met","GOrdSPkIdn");
+ break;
+
+ case GOrdSVwTrn: SAL_INFO("filter.os2met","GOrdSVwTrn");
+ break;
+
+ case GOrdPVwWin: PushAttr(nOrderID);
+ [[fallthrough]];
+ case GOrdSVwWin: SAL_INFO("filter.os2met","GOrdSVwWin");
+ break;
+ default: SAL_INFO("filter.os2met","Unknown order: " << nOrderID);
+ }
+}
+
+void OS2METReader::ReadDsc(sal_uInt16 nDscID)
+{
+ switch (nDscID) {
+ case 0x00f7: { // 'Specify GVM Subset'
+ sal_uInt8 nbyte(0);
+ pOS2MET->SeekRel(6);
+ pOS2MET->ReadUChar( nbyte );
+ if (nbyte==0x05) bCoord32=true;
+ else if (nbyte==0x04) bCoord32=false;
+ else {
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ ErrorCode=1;
+ }
+ break;
+ }
+ case 0x00f6:
+ {
+ // 'Set Picture Descriptor'
+ bool b32;
+ sal_uInt8 nbyte(0), nUnitType(0);
+
+ pOS2MET->SeekRel(2);
+ pOS2MET->ReadUChar( nbyte );
+
+ if (nbyte==0x05)
+ b32=true;
+ else if(nbyte==0x04)
+ b32=false;
+ else
+ {
+ b32 = false; // -Wall added the case.
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ ErrorCode=2;
+ }
+
+ pOS2MET->ReadUChar( nUnitType );
+
+ sal_Int32 xr = ReadCoord(b32);
+ sal_Int32 yr = ReadCoord(b32);
+
+ ReadCoord(b32);
+
+ if (nUnitType==0x00 && xr>0 && yr>0)
+ aGlobMapMode=MapMode(MapUnit::MapInch,Point(0,0),Fraction(10,xr),Fraction(10,yr));
+ else if (nUnitType==0x01 && xr>0 && yr>0)
+ aGlobMapMode=MapMode(MapUnit::MapCM,Point(0,0),Fraction(10,xr),Fraction(10,yr));
+ else
+ aGlobMapMode=MapMode();
+
+ sal_Int32 x1 = ReadCoord(b32);
+ sal_Int32 x2 = ReadCoord(b32);
+ sal_Int32 y1 = ReadCoord(b32);
+ sal_Int32 y2 = ReadCoord(b32);
+
+ if (x1>x2)
+ {
+ const auto nt = x1;
+ x1=x2;
+ x2=nt;
+ }
+
+ if (y1>y2)
+ {
+ const auto nt = y1;
+ y1=y2;
+ y2=nt;
+ }
+
+ aBoundingRect.SetLeft( x1 );
+ aBoundingRect.SetRight( x2 );
+ aBoundingRect.SetTop( y1 );
+ aBoundingRect.SetBottom( y2 );
+
+ // no output beside this bounding rect
+ pVirDev->IntersectClipRegion( tools::Rectangle( Point(), aBoundingRect.GetSize() ) );
+
+ break;
+ }
+ case 0x0021: // 'Set Current Defaults'
+ break;
+ }
+}
+
+void OS2METReader::ReadImageData(sal_uInt16 nDataID, sal_uInt16 nDataLen)
+{
+ OSBitmap * p=pBitmapList; if (p==nullptr) return;
+
+ switch (nDataID) {
+
+ case 0x0070: // Begin Segment
+ break;
+
+ case 0x0091: // Begin Image Content
+ break;
+
+ case 0x0094: // Image Size
+ pOS2MET->SeekRel(5);
+ p->nHeight=ReadBigEndianWord();
+ p->nWidth=ReadBigEndianWord();
+ break;
+
+ case 0x0095: // Image Encoding
+ break;
+
+ case 0x0096: { // Image IDE-Size
+ sal_uInt8 nbyte(0);
+ pOS2MET->ReadUChar( nbyte ); p->nBitsPerPixel=nbyte;
+ break;
+ }
+
+ case 0x0097: // Image LUT-ID
+ break;
+
+ case 0x009b: // IDE Structure
+ break;
+
+ case 0xfe92: { // Image Data
+ // At the latest we now need the temporary BMP file and
+ // inside this file we need the header and the palette.
+ if (p->pBMP==nullptr) {
+ p->pBMP=new SvMemoryStream();
+ p->pBMP->SetEndian(SvStreamEndian::LITTLE);
+ if (p->nWidth==0 || p->nHeight==0 || p->nBitsPerPixel==0) {
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ ErrorCode=3;
+ return;
+ }
+ // write (Windows-)BITMAPINFOHEADER:
+ p->pBMP->WriteUInt32( 40 ).WriteUInt32( p->nWidth ).WriteUInt32( p->nHeight );
+ p->pBMP->WriteUInt16( 1 ).WriteUInt16( p->nBitsPerPixel );
+ p->pBMP->WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 );
+ p->pBMP->WriteUInt32( 0 ).WriteUInt32( 0 );
+ // write color table:
+ if (p->nBitsPerPixel<=8) {
+ sal_uInt16 i, nColTabSize=1<<(p->nBitsPerPixel);
+ for (i=0; i<nColTabSize; i++) p->pBMP->WriteUInt32( GetPalette0RGB(i) );
+ }
+ }
+ // OK, now the map data is being pushed. Unfortunately OS2 and BMP
+ // do have a different RGB ordering when using 24-bit
+ std::unique_ptr<sal_uInt8[]> pBuf(new sal_uInt8[nDataLen]);
+ pOS2MET->ReadBytes(pBuf.get(), nDataLen);
+ sal_uInt32 nBytesPerLineToSwap = (p->nBitsPerPixel == 24) ?
+ ((p->nWidth * 3 + 3) & 0xfffffffc) : 0;
+ if (nBytesPerLineToSwap) {
+ sal_uInt32 nAlign = p->nMapPos - (p->nMapPos % nBytesPerLineToSwap);
+ sal_uInt32 i=0;
+ while (nAlign+i+2<p->nMapPos+nDataLen) {
+ if (nAlign+i>=p->nMapPos) {
+ sal_uInt32 j = nAlign + i - p->nMapPos;
+ std::swap(pBuf[j], pBuf[j+2]);
+ }
+ i+=3;
+ if (i + 2 >= nBytesPerLineToSwap) {
+ nAlign += nBytesPerLineToSwap;
+ i=0;
+ }
+ }
+ }
+ p->pBMP->WriteBytes(pBuf.get(), nDataLen);
+ p->nMapPos+=nDataLen;
+ break;
+ }
+ case 0x0093: // End Image Content
+ break;
+
+ case 0x0071: // End Segment
+ break;
+ }
+}
+
+void OS2METReader::ReadFont(sal_uInt16 nFieldSize)
+{
+ OSFont * pF=new OSFont;
+ pF->pSucc=pFontList; pFontList=pF;
+ pF->nID=0;
+ pF->aFont.SetTransparent(true);
+ pF->aFont.SetAlignment(ALIGN_BASELINE);
+
+ auto nPos=pOS2MET->Tell();
+ auto nMaxPos = nPos + nFieldSize;
+ pOS2MET->SeekRel(2); nPos+=2;
+ while (nPos<nMaxPos && pOS2MET->good()) {
+ sal_uInt8 nByte(0);
+ pOS2MET->ReadUChar(nByte);
+ sal_uInt16 nLen = static_cast<sal_uInt16>(nByte) & 0x00ff;
+ if (nLen == 0)
+ {
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ ErrorCode=4;
+ }
+ sal_uInt8 nTripType(0);
+ pOS2MET->ReadUChar( nTripType );
+ switch (nTripType) {
+ case 0x02:
+ {
+ sal_uInt8 nTripType2(0);
+ pOS2MET->ReadUChar( nTripType2 );
+ switch (nTripType2) {
+ case 0x84: // Font name
+ break;
+ case 0x08: { // Font Typeface
+ char str[33];
+ pOS2MET->SeekRel(1);
+ str[pOS2MET->ReadBytes(str, 32)] = 0;
+ OUString aStr( str, strlen(str), osl_getThreadTextEncoding() );
+ if ( aStr.equalsIgnoreAsciiCase( "Helv" ) )
+ aStr = "Helvetica";
+ pF->aFont.SetFamilyName( aStr );
+ break;
+ }
+ }
+ break;
+ }
+ case 0x24: // Icid
+ {
+ sal_uInt8 nTripType2(0);
+ pOS2MET->ReadUChar( nTripType2 );
+ switch (nTripType2) {
+ case 0x05: //Icid
+ pOS2MET->ReadUChar( nByte );
+ pF->nID=static_cast<sal_uInt32>(nByte)&0xff;
+ break;
+ }
+ break;
+ }
+ case 0x20: // Font Binary GCID
+ break;
+ case 0x1f: { // Font Attributes
+ FontWeight eWeight;
+ sal_uInt8 nbyte(0);
+ pOS2MET->ReadUChar( nbyte );
+ switch (nbyte) {
+ case 1: eWeight=WEIGHT_THIN; break;
+ case 2: eWeight=WEIGHT_ULTRALIGHT; break;
+ case 3: eWeight=WEIGHT_LIGHT; break;
+ case 4: eWeight=WEIGHT_SEMILIGHT; break;
+ case 5: eWeight=WEIGHT_NORMAL; break;
+ case 6: eWeight=WEIGHT_SEMIBOLD; break;
+ case 7: eWeight=WEIGHT_BOLD; break;
+ case 8: eWeight=WEIGHT_ULTRABOLD; break;
+ case 9: eWeight=WEIGHT_BLACK; break;
+ default: eWeight=WEIGHT_DONTKNOW;
+ }
+ pF->aFont.SetWeight(eWeight);
+ break;
+ }
+ }
+ nPos+=nLen;
+ pOS2MET->Seek(nPos);
+ }
+}
+
+void OS2METReader::ReadField(sal_uInt16 nFieldType, sal_uInt16 nFieldSize)
+{
+ switch (nFieldType) {
+ case BegDocumnMagic:
+ break;
+ case EndDocumnMagic:
+ break;
+ case BegResGrpMagic:
+ break;
+ case EndResGrpMagic:
+ break;
+ case BegColAtrMagic:
+ break;
+ case EndColAtrMagic:
+ break;
+ case BlkColAtrMagic: {
+ sal_uInt8 nbyte;
+ sal_uInt16 nStartIndex, nEndIndex, i, nElemLen, nBytesPerCol;
+
+ auto nPos = pOS2MET->Tell();
+ auto nMaxPos = nPos + nFieldSize;
+ pOS2MET->SeekRel(3); nPos+=3;
+ while (nPos<nMaxPos && pOS2MET->GetError()==ERRCODE_NONE) {
+ pOS2MET->ReadUChar( nbyte ); nElemLen=static_cast<sal_uInt16>(nbyte) & 0x00ff;
+ if (nElemLen>11) {
+ pOS2MET->SeekRel(4);
+ nStartIndex=ReadBigEndianWord();
+ pOS2MET->SeekRel(3);
+ pOS2MET->ReadUChar( nbyte );
+ nBytesPerCol=static_cast<sal_uInt16>(nbyte) & 0x00ff;
+ if (nBytesPerCol == 0)
+ {
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ ErrorCode=4;
+ break;
+ }
+ nEndIndex=nStartIndex+(nElemLen-11)/nBytesPerCol;
+ for (i=nStartIndex; i<nEndIndex; i++) {
+ if (nBytesPerCol > 3) pOS2MET->SeekRel(nBytesPerCol-3);
+ auto nCol = ReadBigEndian3BytesLong();
+ SetPalette0RGB(i, nCol);
+ }
+ }
+ else if (nElemLen<10) {
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ ErrorCode=4;
+ }
+ nPos += nElemLen;
+ pOS2MET->Seek(nPos);
+ }
+ break;
+ }
+ case MapColAtrMagic:
+ break;
+ case BegImgObjMagic: {
+ // create new bitmap by now: (will be filled later)
+ OSBitmap * pB=new OSBitmap;
+ pB->pSucc=pBitmapList; pBitmapList=pB;
+ pB->pBMP=nullptr; pB->nWidth=0; pB->nHeight=0; pB->nBitsPerPixel=0;
+ pB->nMapPos=0;
+ // determine ID of the bitmap:
+ pB->nID=0;
+ for (sal_uInt8 i = 0; i < 4; ++i) {
+ sal_uInt8 nbyte(0),nbyte2(0);
+ pOS2MET->ReadUChar(nbyte).ReadUChar(nbyte2);
+ nbyte -= 0x30;
+ nbyte2 -= 0x30;
+ nbyte = (nbyte << 4) | nbyte2;
+ pB->nID=(pB->nID>>8)|(static_cast<sal_uInt32>(nbyte)<<24);
+ }
+ // put new palette on the palette stack: (will be filled later)
+ OSPalette * pP=new OSPalette;
+ pP->pSucc=pPaletteStack; pPaletteStack=pP;
+ pP->p0RGB=nullptr; pP->nSize=0;
+ break;
+ }
+ case EndImgObjMagic: {
+ // read temporary Windows BMP file:
+ if (pBitmapList==nullptr || pBitmapList->pBMP==nullptr ||
+ pBitmapList->pBMP->GetError()!=ERRCODE_NONE) {
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ ErrorCode=5;
+ return;
+ }
+ pBitmapList->pBMP->Seek(0);
+
+ ReadDIBBitmapEx(pBitmapList->aBitmapEx, *(pBitmapList->pBMP), false);
+
+ if (pBitmapList->pBMP->GetError()!=ERRCODE_NONE) {
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ ErrorCode=6;
+ }
+ delete pBitmapList->pBMP; pBitmapList->pBMP=nullptr;
+ // kill palette from stack:
+ OSPalette * pP=pPaletteStack;
+ if (pP!=nullptr) {
+ pPaletteStack=pP->pSucc;
+ delete[] pP->p0RGB;
+ delete pP;
+ }
+ break;
+ }
+ case DscImgObjMagic:
+ break;
+ case DatImgObjMagic: {
+ sal_uInt16 nDataID, nDataLen;
+ sal_uInt8 nbyte;
+
+ auto nPos = pOS2MET->Tell();
+ auto nMaxPos = nPos + nFieldSize;
+ while (nPos<nMaxPos && pOS2MET->GetError()==ERRCODE_NONE) {
+ pOS2MET->ReadUChar( nbyte ); nDataID=static_cast<sal_uInt16>(nbyte)&0x00ff;
+ if (nDataID==0x00fe) {
+ pOS2MET->ReadUChar( nbyte );
+ nDataID=(nDataID<<8)|(static_cast<sal_uInt16>(nbyte)&0x00ff);
+ nDataLen=ReadBigEndianWord();
+ nPos+=4;
+ }
+ else {
+ pOS2MET->ReadUChar( nbyte ); nDataLen=static_cast<sal_uInt16>(nbyte)&0x00ff;
+ nPos+=2;
+ }
+ ReadImageData(nDataID, nDataLen);
+ nPos += nDataLen;
+ pOS2MET->Seek(nPos);
+ }
+ break;
+ }
+
+ case BegObEnv1Magic:
+ break;
+ case EndObEnv1Magic:
+ break;
+ case BegGrfObjMagic:
+ break;
+ case EndGrfObjMagic: {
+ if (!xOrdFile)
+ break;
+
+ auto nMaxPos = xOrdFile->Tell();
+ if (!nMaxPos)
+ break;
+
+ // In xOrdFile all "DatGrfObj" fields were collected so that the
+ // therein contained "Orders" are continuous and not segmented by fields.
+ // To read them from the memory stream without having any trouble,
+ // we use a little trick:
+
+ SvStream *pSave = pOS2MET;
+ pOS2MET=xOrdFile.get(); //(!)
+ pOS2MET->Seek(0);
+
+ // in a sane world this block is just: pOS2MET->SetStreamSize(nMaxPos);
+ if (nMaxPos)
+ {
+#ifndef NDEBUG
+ const sal_uInt8 nLastByte = static_cast<const sal_uInt8*>(xOrdFile->GetData())[nMaxPos-1];
+#endif
+ pOS2MET->SetStreamSize(nMaxPos); // shrink stream to written portion
+ assert(pOS2MET->remainingSize() == nMaxPos || pOS2MET->remainingSize() == nMaxPos - 1);
+ SAL_WARN_IF(pOS2MET->remainingSize() == nMaxPos, "filter.os2met", "this SetStreamSize workaround is no longer needed");
+ // The shrink case of SvMemoryStream::ReAllocateMemory, i.e. nEndOfData = nNewSize - 1, looks buggy to me, workaround
+ // it by using Seek to move the nEndOfData to the sane position
+ if (pOS2MET->remainingSize() < nMaxPos)
+ {
+ pOS2MET->Seek(nMaxPos);
+ pOS2MET->Seek(0);
+ }
+
+ assert(nLastByte == static_cast<const sal_uInt8*>(xOrdFile->GetData())[nMaxPos-1]);
+ }
+
+ assert(pOS2MET->remainingSize() == nMaxPos);
+
+ // disable stream growing past its current size
+ xOrdFile->SetResizeOffset(0);
+
+
+ // "Segment header":
+ sal_uInt8 nbyte(0);
+ pOS2MET->ReadUChar( nbyte );
+ if (nbyte==0x70) { // header exists
+ pOS2MET->SeekRel(15); // but we don't need it
+ }
+ else pOS2MET->SeekRel(-1); // no header, go back one byte
+
+ // loop through Order:
+ while (pOS2MET->Tell()<nMaxPos && pOS2MET->GetError()==ERRCODE_NONE) {
+ pOS2MET->ReadUChar( nbyte );
+ sal_uInt16 nOrderID = static_cast<sal_uInt16>(nbyte) & 0x00ff;
+ if (nOrderID==0x00fe) {
+ pOS2MET->ReadUChar( nbyte );
+ nOrderID=(nOrderID << 8) | (static_cast<sal_uInt16>(nbyte) & 0x00ff);
+ }
+ sal_uInt16 nOrderLen;
+ if (nOrderID>0x00ff || nOrderID==GOrdPolygn) {
+ // ooo: As written in OS2 documentation, the order length should now
+ // be written as big endian word. (Quote: "Highorder byte precedes loworder byte").
+ // In reality there are files in which the length is stored as little endian word
+ // (at least for nOrderID==GOrdPolygn)
+ // So we throw a coin or what else can we do?
+ pOS2MET->ReadUChar( nbyte ); nOrderLen=static_cast<sal_uInt16>(nbyte)&0x00ff;
+ pOS2MET->ReadUChar( nbyte ); if (nbyte!=0) nOrderLen=nOrderLen<<8|(static_cast<sal_uInt16>(nbyte)&0x00ff);
+ }
+ else if (nOrderID==GOrdSTxAlg || nOrderID==GOrdPTxAlg) nOrderLen=2;
+ else if ((nOrderID&0xff88)==0x0008) nOrderLen=1;
+ else if (nOrderID==0x0000 || nOrderID==0x00ff) nOrderLen=0;
+ else { pOS2MET->ReadUChar( nbyte ); nOrderLen=static_cast<sal_uInt16>(nbyte) & 0x00ff; }
+ auto nPos=pOS2MET->Tell();
+ ReadOrder(nOrderID, nOrderLen);
+ if (nPos+nOrderLen < pOS2MET->Tell()) {
+ SAL_INFO("filter.os2met","Order is shorter than expected. OrderID: " << nOrderID << " Position: " << nPos);
+ }
+ else if (nPos+nOrderLen != pOS2MET->Tell()) {
+ SAL_INFO("filter.os2met","Order was not read completely. OrderID: " << nOrderID << " Position: " << nPos);
+ }
+ pOS2MET->Seek(nPos+nOrderLen);
+ }
+
+ pOS2MET=pSave;
+ if (xOrdFile->GetError()) {
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ ErrorCode=10;
+ }
+ xOrdFile.reset();
+ break;
+ }
+ case DscGrfObjMagic: {
+ sal_uInt16 nDscID, nDscLen;
+ sal_uInt8 nbyte;
+
+ auto nMaxPos = pOS2MET->Tell() + nFieldSize;
+ while (pOS2MET->Tell()<nMaxPos && pOS2MET->GetError()==ERRCODE_NONE) {
+ pOS2MET->ReadUChar( nbyte ); nDscID =static_cast<sal_uInt16>(nbyte) & 0x00ff;
+ pOS2MET->ReadUChar( nbyte ); nDscLen=static_cast<sal_uInt16>(nbyte) & 0x00ff;
+ auto nPos = pOS2MET->Tell();
+ ReadDsc(nDscID);
+ pOS2MET->Seek(nPos+nDscLen);
+ }
+ break;
+ }
+ case DatGrfObjMagic: {
+ if (!xOrdFile) {
+ xOrdFile.reset(new SvMemoryStream);
+ xOrdFile->SetEndian(SvStreamEndian::LITTLE);
+ }
+ std::unique_ptr<sal_uInt8[]> pBuf(new sal_uInt8[nFieldSize]);
+ pOS2MET->ReadBytes(pBuf.get(), nFieldSize);
+ xOrdFile->WriteBytes(pBuf.get(), nFieldSize);
+ break;
+ }
+ case MapCodFntMagic:
+ ReadFont(nFieldSize);
+ break;
+
+ case MapDatResMagic:
+ break;
+ }
+}
+
+void OS2METReader::ReadOS2MET( SvStream & rStreamOS2MET, GDIMetaFile & rGDIMetaFile )
+{
+ ErrorCode=0;
+
+ pOS2MET = &rStreamOS2MET;
+ auto nOrigPos = pOS2MET->Tell();
+ SvStreamEndian nOrigNumberFormat = pOS2MET->GetEndian();
+
+ bCoord32 = true;
+ pPaletteStack=nullptr;
+ pAreaStack=nullptr;
+ pPathStack=nullptr;
+ pPathList=nullptr;
+ pFontList=nullptr;
+ pBitmapList=nullptr;
+ pAttrStack=nullptr;
+
+ aDefAttr.aLinCol =COL_BLACK;
+ aDefAttr.aLinBgCol =COL_WHITE;
+ aDefAttr.eLinMix =RasterOp::OverPaint;
+ aDefAttr.eLinBgMix =RasterOp::OverPaint;
+ aDefAttr.aChrCol =COL_BLACK;
+ aDefAttr.aChrBgCol =COL_WHITE;
+ aDefAttr.eChrMix =RasterOp::OverPaint;
+ aDefAttr.eChrBgMix =RasterOp::OverPaint;
+ aDefAttr.aMrkCol =COL_BLACK;
+ aDefAttr.aMrkBgCol =COL_WHITE;
+ aDefAttr.eMrkMix =RasterOp::OverPaint;
+ aDefAttr.eMrkBgMix =RasterOp::OverPaint;
+ aDefAttr.aPatCol =COL_BLACK;
+ aDefAttr.aPatBgCol =COL_WHITE;
+ aDefAttr.ePatMix =RasterOp::OverPaint;
+ aDefAttr.ePatBgMix =RasterOp::OverPaint;
+ aDefAttr.aImgCol =COL_BLACK;
+ aDefAttr.aImgBgCol =COL_WHITE;
+ aDefAttr.eImgMix =RasterOp::OverPaint;
+ aDefAttr.eImgBgMix =RasterOp::OverPaint;
+ aDefAttr.nArcP =1;
+ aDefAttr.nArcQ =1;
+ aDefAttr.nArcR =0;
+ aDefAttr.nArcS =0;
+ aDefAttr.nChrAng =0_deg10;
+ aDefAttr.nChrCellHeight = 12;
+ aDefAttr.nChrSet =0;
+ aDefAttr.aCurPos =Point(0,0);
+ aDefAttr.eLinStyle =PEN_SOLID;
+ aDefAttr.nLinWidth =0;
+ aDefAttr.aMrkCellSize=Size(10,10);
+ aDefAttr.nMrkPrec =0x01;
+ aDefAttr.nMrkSet =0xff;
+ aDefAttr.nMrkSymbol =0x01;
+ aDefAttr.bFill =true;
+ aDefAttr.nStrLinWidth=0;
+
+ aAttr=aDefAttr;
+
+ xOrdFile.reset();
+
+ rGDIMetaFile.Record(pVirDev);
+
+ pOS2MET->SetEndian(SvStreamEndian::LITTLE);
+
+ sal_uInt64 nPos = pOS2MET->Tell();
+
+ for (;;) {
+
+ sal_uInt16 nFieldSize = ReadBigEndianWord();
+ sal_uInt8 nMagicByte(0);
+ pOS2MET->ReadUChar( nMagicByte );
+ if (nMagicByte!=0xd3) {
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ ErrorCode=7;
+ break;
+ }
+
+ sal_uInt16 nFieldType(0);
+ pOS2MET->ReadUInt16(nFieldType);
+
+ pOS2MET->SeekRel(3);
+
+ if (pOS2MET->GetError())
+ break;
+
+ if (nFieldType==EndDocumnMagic)
+ break;
+
+ if (pOS2MET->eof() || nFieldSize < 8)
+ {
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ ErrorCode=8;
+ break;
+ }
+
+ nPos+=8; nFieldSize-=8;
+
+ if (nFieldSize > pOS2MET->remainingSize())
+ {
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ ErrorCode=8;
+ break;
+ }
+
+ ReadField(nFieldType, nFieldSize);
+ nPos += nFieldSize;
+
+ if (pOS2MET->Tell() > nPos)
+ {
+ pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ ErrorCode=9;
+ break;
+ }
+ pOS2MET->Seek(nPos);
+ }
+
+ rGDIMetaFile.Stop();
+
+ rGDIMetaFile.SetPrefMapMode( aGlobMapMode );
+
+ if( aBoundingRect.GetWidth() && aBoundingRect.GetHeight() )
+ rGDIMetaFile.SetPrefSize( aBoundingRect.GetSize() );
+ else
+ {
+ if( aCalcBndRect.Left() || aCalcBndRect.Top() )
+ rGDIMetaFile.Move( -aCalcBndRect.Left(), -aCalcBndRect.Top() );
+
+ rGDIMetaFile.SetPrefSize( aCalcBndRect.GetSize() );
+ }
+
+ pOS2MET->SetEndian(nOrigNumberFormat);
+
+ if (pOS2MET->GetError()) {
+ SAL_INFO("filter.os2met","Error code: " << ErrorCode);
+ pOS2MET->Seek(nOrigPos);
+ }
+}
+
+//================== GraphicImport - the exported function ================
+
+bool ImportMetGraphic(SvStream & rStream, Graphic & rGraphic)
+{
+ OS2METReader aOS2METReader;
+ GDIMetaFile aMTF;
+ bool bRet = false;
+
+ try
+ {
+ aOS2METReader.ReadOS2MET( rStream, aMTF );
+
+ if ( !rStream.GetError() )
+ {
+ rGraphic=Graphic( aMTF );
+ bRet = true;
+ }
+ }
+ catch (const css::uno::Exception&)
+ {
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ipbm/ipbm.cxx b/vcl/source/filter/ipbm/ipbm.cxx
new file mode 100644
index 000000000..37736a48f
--- /dev/null
+++ b/vcl/source/filter/ipbm/ipbm.cxx
@@ -0,0 +1,541 @@
+/* -*- 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 <o3tl/safeint.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <tools/stream.hxx>
+#include <filter/PbmReader.hxx>
+
+//============================ PBMReader ==================================
+
+namespace {
+
+class PBMReader {
+
+private:
+
+ SvStream& mrPBM; // the PBM file to read
+
+ bool mbStatus;
+ bool mbRemark; // sal_False if the stream is in a comment
+ bool mbRaw; // RAW/ASCII MODE
+ sal_uInt8 mnMode; // 0->PBM, 1->PGM, 2->PPM
+ std::unique_ptr<vcl::bitmap::RawBitmap> mpRawBmp;
+ std::vector<Color> mvPalette;
+ sal_Int32 mnWidth, mnHeight; // dimensions in pixel
+ sal_uInt16 mnCol;
+ sal_uInt64 mnMaxVal; // max value in the <missing comment>
+ bool ImplReadBody();
+ bool ImplReadHeader();
+
+public:
+ explicit PBMReader(SvStream & rPBM);
+ bool ReadPBM(Graphic & rGraphic );
+};
+
+}
+
+//=================== Methods of PBMReader ==============================
+
+PBMReader::PBMReader(SvStream & rPBM)
+ : mrPBM(rPBM)
+ , mbStatus(true)
+ , mbRemark(false)
+ , mbRaw(true)
+ , mnMode(0)
+ , mnWidth(0)
+ , mnHeight(0)
+ , mnCol(0)
+ , mnMaxVal(0)
+{
+}
+
+bool PBMReader::ReadPBM(Graphic & rGraphic )
+{
+ if ( mrPBM.GetError() )
+ return false;
+
+ mrPBM.SetEndian( SvStreamEndian::LITTLE );
+
+ // read header:
+
+ mbStatus = ImplReadHeader();
+ if ( !mbStatus )
+ return false;
+
+ if ( ( mnMaxVal == 0 ) || ( mnWidth <= 0 ) || ( mnHeight <= 0 ) )
+ return false;
+
+ sal_uInt32 nPixelsRequired;
+ if (o3tl::checked_multiply<sal_uInt32>(mnWidth, mnHeight, nPixelsRequired))
+ return false;
+ const auto nRemainingSize = mrPBM.remainingSize();
+
+ // 0->PBM, 1->PGM, 2->PPM
+ switch ( mnMode )
+ {
+ case 0:
+ {
+ if (nRemainingSize < nPixelsRequired / 8)
+ return false;
+
+ mpRawBmp.reset( new vcl::bitmap::RawBitmap( Size( mnWidth, mnHeight ), 24 ) );
+ mvPalette.resize( 2 );
+ mvPalette[0] = Color( 0xff, 0xff, 0xff );
+ mvPalette[1] = Color( 0x00, 0x00, 0x00 );
+ break;
+ }
+ case 1 :
+ if (nRemainingSize < nPixelsRequired)
+ return false;
+
+ mpRawBmp.reset( new vcl::bitmap::RawBitmap( Size( mnWidth, mnHeight ), 24 ) );
+ mnCol = static_cast<sal_uInt16>(mnMaxVal) + 1;
+ if ( mnCol > 256 )
+ mnCol = 256;
+
+ mvPalette.resize( 256 );
+ for ( sal_uInt16 i = 0; i < mnCol; i++ )
+ {
+ sal_uInt16 nCount = 255 * i / mnCol;
+ mvPalette[i] = Color( static_cast<sal_uInt8>(nCount), static_cast<sal_uInt8>(nCount), static_cast<sal_uInt8>(nCount) );
+ }
+ break;
+ case 2 :
+ if (nRemainingSize / 3 < nPixelsRequired)
+ return false;
+
+ mpRawBmp.reset( new vcl::bitmap::RawBitmap( Size( mnWidth, mnHeight ), 24 ) );
+ break;
+ }
+
+ // read bitmap data
+ mbStatus = ImplReadBody();
+
+ if ( mbStatus )
+ rGraphic = vcl::bitmap::CreateFromData(std::move(*mpRawBmp));
+
+ return mbStatus;
+}
+
+bool PBMReader::ImplReadHeader()
+{
+ sal_uInt8 nID[ 2 ];
+ sal_uInt8 nDat;
+ sal_uInt8 nMax, nCount = 0;
+ bool bFinished = false;
+
+ mrPBM.ReadUChar( nID[ 0 ] ).ReadUChar( nID[ 1 ] );
+ if (!mrPBM.good() || nID[0] != 'P')
+ return false;
+ mnMaxVal = mnWidth = mnHeight = 0;
+ switch ( nID[ 1 ] )
+ {
+ case '1' :
+ mbRaw = false;
+ [[fallthrough]];
+ case '4' :
+ mnMode = 0;
+ nMax = 2; // number of parameters in Header
+ mnMaxVal = 1;
+ break;
+ case '2' :
+ mbRaw = false;
+ [[fallthrough]];
+ case '5' :
+ mnMode = 1;
+ nMax = 3;
+ break;
+ case '3' :
+ mbRaw = false;
+ [[fallthrough]];
+ case '6' :
+ mnMode = 2;
+ nMax = 3;
+ break;
+ default:
+ return false;
+ }
+ while ( !bFinished )
+ {
+ mrPBM.ReadUChar( nDat );
+
+ if (!mrPBM.good())
+ return false;
+
+ if ( nDat == '#' )
+ {
+ mbRemark = true;
+ continue;
+ }
+ else if ( ( nDat == 0x0d ) || ( nDat == 0x0a ) )
+ {
+ mbRemark = false;
+ nDat = 0x20;
+ }
+ if ( mbRemark )
+ continue;
+
+ if ( ( nDat == 0x20 ) || ( nDat == 0x09 ) )
+ {
+ if ( ( nCount == 0 ) && mnWidth )
+ nCount++;
+ else if ( ( nCount == 1 ) && mnHeight )
+ {
+ if ( ++nCount == nMax )
+ bFinished = true;
+ }
+ else if ( ( nCount == 2 ) && mnMaxVal )
+ {
+ bFinished = true;
+ }
+ continue;
+ }
+ if ( ( nDat >= '0' ) && ( nDat <= '9' ) )
+ {
+ nDat -= '0';
+ if ( nCount == 0 )
+ {
+ if (mnWidth > SAL_MAX_INT32 / 10)
+ {
+ return false;
+ }
+ mnWidth *= 10;
+ if (nDat > SAL_MAX_INT32 - mnWidth)
+ {
+ return false;
+ }
+ mnWidth += nDat;
+ }
+ else if ( nCount == 1 )
+ {
+ if (mnHeight > SAL_MAX_INT32 / 10)
+ {
+ return false;
+ }
+ mnHeight *= 10;
+ if (nDat > SAL_MAX_INT32 - mnHeight)
+ {
+ return false;
+ }
+ mnHeight += nDat;
+ }
+ else if ( nCount == 2 )
+ {
+ if (mnMaxVal > std::numeric_limits<sal_uInt64>::max() / 10)
+ {
+ return false;
+ }
+ mnMaxVal *= 10;
+ if (nDat > std::numeric_limits<sal_uInt64>::max() - mnMaxVal)
+ {
+ return false;
+ }
+ mnMaxVal += nDat;
+ }
+ }
+ else
+ return false;
+ }
+ return mbStatus;
+}
+
+bool PBMReader::ImplReadBody()
+{
+ sal_uInt8 nDat = 0, nCount;
+ sal_uInt64 nGrey, nRGB[3];
+ sal_Int32 nWidth = 0;
+ sal_Int32 nHeight = 0;
+
+ if ( mbRaw )
+ {
+ signed char nShift = 0;
+ switch ( mnMode )
+ {
+
+ // PBM
+ case 0 :
+ while ( nHeight != mnHeight )
+ {
+ if (!mrPBM.good())
+ return false;
+
+ if ( --nShift < 0 )
+ {
+ mrPBM.ReadUChar( nDat );
+ nShift = 7;
+ }
+ mpRawBmp->SetPixel( nHeight, nWidth, mvPalette[(nDat >> nShift) & 0x01] );
+ if ( ++nWidth == mnWidth )
+ {
+ nShift = 0;
+ nWidth = 0;
+ nHeight++;
+ }
+ }
+ break;
+
+ // PGM
+ case 1 :
+ while ( nHeight != mnHeight )
+ {
+ if (!mrPBM.good())
+ return false;
+
+ mrPBM.ReadUChar( nDat );
+ mpRawBmp->SetPixel( nHeight, nWidth++, mvPalette[nDat]);
+
+ if ( nWidth == mnWidth )
+ {
+ nWidth = 0;
+ nHeight++;
+ }
+ }
+ break;
+
+ // PPM
+ case 2 :
+ while ( nHeight != mnHeight )
+ {
+ if (!mrPBM.good())
+ return false;
+
+ sal_uInt8 nR, nG, nB;
+ sal_uInt8 nRed, nGreen, nBlue;
+ mrPBM.ReadUChar( nR ).ReadUChar( nG ).ReadUChar( nB );
+ nRed = 255 * nR / mnMaxVal;
+ nGreen = 255 * nG / mnMaxVal;
+ nBlue = 255 * nB / mnMaxVal;
+ mpRawBmp->SetPixel( nHeight, nWidth++, Color( nRed, nGreen, nBlue ) );
+ if ( nWidth == mnWidth )
+ {
+ nWidth = 0;
+ nHeight++;
+ }
+ }
+ break;
+ }
+ }
+ else
+ {
+ bool bPara = false;
+ bool bFinished = false;
+
+ switch ( mnMode )
+ {
+ // PBM
+ case 0 :
+ while ( !bFinished )
+ {
+ if (!mrPBM.good())
+ return false;
+
+ mrPBM.ReadUChar( nDat );
+
+ if ( nDat == '#' )
+ {
+ mbRemark = true;
+ continue;
+ }
+ else if ( ( nDat == 0x0d ) || ( nDat == 0x0a ) )
+ {
+ mbRemark = false;
+ continue;
+ }
+ if ( mbRemark || nDat == 0x20 || nDat == 0x09 )
+ continue;
+
+ if ( nDat == '0' || nDat == '1' )
+ {
+ mpRawBmp->SetPixel( nHeight, nWidth, mvPalette[static_cast<sal_uInt8>(nDat - '0')] );
+ nWidth++;
+ if ( nWidth == mnWidth )
+ {
+ nWidth = 0;
+ if ( ++nHeight == mnHeight )
+ bFinished = true;
+ }
+ }
+ else
+ return false;
+ }
+ break;
+
+ // PGM
+ case 1 :
+
+ bPara = false;
+ nCount = 0;
+ nGrey = 0;
+
+ while ( !bFinished )
+ {
+ if ( nCount )
+ {
+ nCount--;
+ if ( nGrey <= mnMaxVal )
+ nGrey = 255 * nGrey / mnMaxVal;
+ mpRawBmp->SetPixel( nHeight, nWidth++, mvPalette[static_cast<sal_uInt8>(nGrey)] );
+ nGrey = 0;
+ if ( nWidth == mnWidth )
+ {
+ nWidth = 0;
+ if ( ++nHeight == mnHeight )
+ bFinished = true;
+ }
+ continue;
+ }
+
+ if (!mrPBM.good())
+ return false;
+
+ mrPBM.ReadUChar( nDat );
+
+ if ( nDat == '#' )
+ {
+ mbRemark = true;
+ if ( bPara )
+ {
+ bPara = false;
+ nCount++;
+ }
+ continue;
+ }
+ else if ( ( nDat == 0x0d ) || ( nDat == 0x0a ) )
+ {
+ mbRemark = false;
+ if ( bPara )
+ {
+ bPara = false;
+ nCount++;
+ }
+ continue;
+ }
+
+ if ( nDat == 0x20 || nDat == 0x09 )
+ {
+ if ( bPara )
+ {
+ bPara = false;
+ nCount++;
+ }
+ continue;
+ }
+ if ( nDat >= '0' && nDat <= '9' )
+ {
+ bPara = true;
+ nGrey *= 10;
+ nGrey += nDat-'0';
+ continue;
+ }
+ else
+ return false;
+ }
+ break;
+
+
+ // PPM
+ case 2 :
+
+ bPara = false;
+ nCount = 0;
+ nRGB[ 0 ] = nRGB[ 1 ] = nRGB[ 2 ] = 0;
+
+ while ( !bFinished )
+ {
+ if ( nCount == 3 )
+ {
+ nCount = 0;
+ mpRawBmp->SetPixel( nHeight, nWidth++, Color( static_cast< sal_uInt8 >( ( nRGB[ 0 ] * 255 ) / mnMaxVal ),
+ static_cast< sal_uInt8 >( ( nRGB[ 1 ] * 255 ) / mnMaxVal ),
+ static_cast< sal_uInt8 >( ( nRGB[ 2 ] * 255 ) / mnMaxVal ) ) );
+ nRGB[ 0 ] = nRGB[ 1 ] = nRGB[ 2 ] = 0;
+ if ( nWidth == mnWidth )
+ {
+ nWidth = 0;
+ if ( ++nHeight == mnHeight )
+ bFinished = true;
+ }
+ continue;
+ }
+
+ if (!mrPBM.good())
+ return false;
+
+ mrPBM.ReadUChar( nDat );
+
+ if ( nDat == '#' )
+ {
+ mbRemark = true;
+ if ( bPara )
+ {
+ bPara = false;
+ nCount++;
+ }
+ continue;
+ }
+ else if ( ( nDat == 0x0d ) || ( nDat == 0x0a ) )
+ {
+ mbRemark = false;
+ if ( bPara )
+ {
+ bPara = false;
+ nCount++;
+ }
+ continue;
+ }
+
+ if ( nDat == 0x20 || nDat == 0x09 )
+ {
+ if ( bPara )
+ {
+ bPara = false;
+ nCount++;
+ }
+ continue;
+ }
+ if ( nDat >= '0' && nDat <= '9' )
+ {
+ bPara = true;
+ nRGB[ nCount ] *= 10;
+ nRGB[ nCount ] += nDat-'0';
+ continue;
+ }
+ else
+ return false;
+ }
+ break;
+ }
+ }
+ return mbStatus;
+}
+
+//================== GraphicImport - the exported function ================
+
+bool ImportPbmGraphic( SvStream & rStream, Graphic & rGraphic)
+{
+ PBMReader aPBMReader(rStream);
+
+ return aPBMReader.ReadPBM(rGraphic );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ipcd/ipcd.cxx b/vcl/source/filter/ipcd/ipcd.cxx
new file mode 100644
index 000000000..220ac6111
--- /dev/null
+++ b/vcl/source/filter/ipcd/ipcd.cxx
@@ -0,0 +1,352 @@
+/* -*- 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 <string_view>
+
+#include <vcl/graph.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <tools/stream.hxx>
+#include <filter/PcdReader.hxx>
+
+//============================ PCDReader ==================================
+
+namespace {
+
+// these resolutions are contained in a PCD file:
+enum PCDResolution {
+ PCDRES_BASE16, // 192 x 128
+ PCDRES_BASE4, // 384 x 256
+ PCDRES_BASE, // 768 x 512
+ // the following ones are compressed
+ // and CANNOT be read by us
+ PCDRES_4BASE, // 1536 x 1024
+ PCDRES_16BASE // 3072 x 3072
+};
+
+class PCDReader {
+
+private:
+
+ bool bStatus;
+
+ SvStream &m_rPCD;
+ std::unique_ptr<vcl::bitmap::RawBitmap> mpBitmap;
+
+ sal_uInt8 nOrientation; // orientation of the picture within the PCD file:
+ // 0 - spire point up
+ // 1 - spire points to the right
+ // 2 - spire points down
+ // 3 - spire points to the left
+
+ PCDResolution eResolution; // which resolution we want
+
+ sal_uInt32 nWidth; // width of the PCD picture
+ sal_uInt32 nHeight; // height of the PCD picture
+ sal_uInt32 nImagePos; // position of the picture within the PCD file
+
+ // temporary lLue-Green-Red-Bitmap
+ sal_uInt32 nBMPWidth;
+ sal_uInt32 nBMPHeight;
+
+ void CheckPCDImagePacFile();
+ // checks whether it's a Photo-CD file with 'Image Pac'
+
+ void ReadOrientation();
+ // reads the orientation and sets nOrientation
+
+ void ReadImage();
+
+public:
+
+ explicit PCDReader(SvStream &rStream)
+ : bStatus(false)
+ , m_rPCD(rStream)
+ , nOrientation(0)
+ , eResolution(PCDRES_BASE16)
+ , nWidth(0)
+ , nHeight(0)
+ , nImagePos(0)
+ , nBMPWidth(0)
+ , nBMPHeight(0)
+ {
+ }
+
+ bool ReadPCD( Graphic & rGraphic, FilterConfigItem* pConfigItem );
+};
+
+}
+
+//=================== Methods of PCDReader ==============================
+
+bool PCDReader::ReadPCD( Graphic & rGraphic, FilterConfigItem* pConfigItem )
+{
+ bStatus = true;
+
+ // is it a PCD file with a picture? ( sets bStatus == sal_False, if that's not the case):
+ CheckPCDImagePacFile();
+
+ // read orientation of the picture:
+ ReadOrientation();
+
+ // which resolution do we want?:
+ eResolution = PCDRES_BASE;
+ if ( pConfigItem )
+ {
+ sal_Int32 nResolution = pConfigItem->ReadInt32( "Resolution", 2 );
+ if ( nResolution == 1 )
+ eResolution = PCDRES_BASE4;
+ else if ( nResolution == 0 )
+ eResolution = PCDRES_BASE16;
+ }
+ // determine size and position (position within the PCD file) of the picture:
+ switch (eResolution)
+ {
+ case PCDRES_BASE16 :
+ nWidth = 192;
+ nHeight = 128;
+ nImagePos = 8192;
+ break;
+
+ case PCDRES_BASE4 :
+ nWidth = 384;
+ nHeight = 256;
+ nImagePos = 47104;
+ break;
+
+ case PCDRES_BASE :
+ nWidth = 768;
+ nHeight = 512;
+ nImagePos = 196608;
+ break;
+
+ default:
+ bStatus = false;
+ }
+ if ( bStatus )
+ {
+ if ( ( nOrientation & 0x01 ) == 0 )
+ {
+ nBMPWidth = nWidth;
+ nBMPHeight = nHeight;
+ }
+ else
+ {
+ nBMPWidth = nHeight;
+ nBMPHeight = nWidth;
+ }
+ mpBitmap.reset(new vcl::bitmap::RawBitmap( Size( nBMPWidth, nBMPHeight ), 24 ));
+
+ ReadImage();
+
+ rGraphic = vcl::bitmap::CreateFromData(std::move(*mpBitmap));
+ }
+ return bStatus;
+}
+
+void PCDReader::CheckPCDImagePacFile()
+{
+ char Buf[ 8 ];
+
+ m_rPCD.Seek( 2048 );
+ m_rPCD.ReadBytes(Buf, 7);
+ Buf[ 7 ] = 0;
+ if (!m_rPCD.good() || Buf != std::string_view("PCD_IPI"))
+ bStatus = false;
+}
+
+void PCDReader::ReadOrientation()
+{
+ if ( !bStatus )
+ return;
+ m_rPCD.Seek( 194635 );
+ m_rPCD.ReadUChar( nOrientation );
+ nOrientation &= 0x03;
+}
+
+void PCDReader::ReadImage()
+{
+ tools::Long nL,nCb,nCr;
+
+ if ( !bStatus )
+ return;
+
+ sal_uInt32 nW2 = nWidth>>1;
+ sal_uInt32 nH2 = nHeight>>1;
+
+ // luminance for each pixel of the 1st row of the current pair of rows
+ std::vector<sal_uInt8> aL0(nWidth);
+ // luminance for each pixel of the 2nd row of the current pair of rows
+ std::vector<sal_uInt8> aL1(nWidth);
+ // blue chrominance for each 2x2 pixel of the current pair of rows
+ std::vector<sal_uInt8> aCb(nW2 + 1);
+ // red chrominance for each 2x2 pixel of the current pair of rows
+ std::vector<sal_uInt8> aCr(nW2 + 1);
+ // like above, but for the next pair of rows
+ std::vector<sal_uInt8> aL0N(nWidth);
+ std::vector<sal_uInt8> aL1N(nWidth);
+ std::vector<sal_uInt8> aCbN(nW2 + 1);
+ std::vector<sal_uInt8> aCrN(nW2 + 1);
+
+ sal_uInt8* pL0 = aL0.data();
+ sal_uInt8* pL1 = aL1.data();
+ sal_uInt8* pCb = aCb.data();
+ sal_uInt8* pCr = aCr.data();
+ sal_uInt8* pL0N = aL0N.data();
+ sal_uInt8* pL1N = aL1N.data();
+ sal_uInt8* pCbN = aCbN.data();
+ sal_uInt8* pCrN = aCrN.data();
+
+ m_rPCD.Seek( nImagePos );
+
+ // next pair of rows := first pair of rows:
+ if (m_rPCD.ReadBytes(pL0N, nWidth) != nWidth ||
+ m_rPCD.ReadBytes(pL1N, nWidth) != nWidth ||
+ m_rPCD.ReadBytes(pCbN, nW2) != nW2 ||
+ m_rPCD.ReadBytes(pCrN, nW2) != nW2)
+ {
+ bStatus = false;
+ return;
+ }
+ pCbN[ nW2 ] = pCbN[ nW2 - 1 ];
+ pCrN[ nW2 ] = pCrN[ nW2 - 1 ];
+
+ for (sal_uInt32 nYPair = 0; nYPair < nH2; ++nYPair)
+ {
+ sal_uInt8 * pt;
+ // current pair of rows := next pair of rows:
+ pt=pL0; pL0=pL0N; pL0N=pt;
+ pt=pL1; pL1=pL1N; pL1N=pt;
+ pt=pCb; pCb=pCbN; pCbN=pt;
+ pt=pCr; pCr=pCrN; pCrN=pt;
+
+ // get the next pair of rows:
+ if ( nYPair < nH2 - 1 )
+ {
+ m_rPCD.ReadBytes( pL0N, nWidth );
+ m_rPCD.ReadBytes( pL1N, nWidth );
+ m_rPCD.ReadBytes( pCbN, nW2 );
+ m_rPCD.ReadBytes( pCrN, nW2 );
+ pCbN[nW2]=pCbN[ nW2 - 1 ];
+ pCrN[nW2]=pCrN[ nW2 - 1 ];
+ }
+ else
+ {
+ for (sal_uInt32 nXPair = 0; nXPair < nW2; ++nXPair)
+ {
+ pCbN[ nXPair ] = pCb[ nXPair ];
+ pCrN[ nXPair ] = pCr[ nXPair ];
+ }
+ }
+
+ // loop through both rows of the pair of rows:
+ for (sal_uInt32 ndy = 0; ndy < 2; ++ndy)
+ {
+ sal_uInt32 ny = ( nYPair << 1 ) + ndy;
+
+ // loop through X:
+ for (sal_uInt32 nx = 0; nx < nWidth; ++nx)
+ {
+ // get/calculate nL,nCb,nCr for the pixel nx,ny:
+ sal_uInt32 nXPair = nx >> 1;
+ if ( ndy == 0 )
+ {
+ nL = static_cast<tools::Long>(pL0[ nx ]);
+ if (( nx & 1 ) == 0 )
+ {
+ nCb = static_cast<tools::Long>(pCb[ nXPair ]);
+ nCr = static_cast<tools::Long>(pCr[ nXPair ]);
+ }
+ else
+ {
+ nCb = ( static_cast<tools::Long>(pCb[ nXPair ]) + static_cast<tools::Long>(pCb[ nXPair + 1 ]) ) >> 1;
+ nCr = ( static_cast<tools::Long>(pCr[ nXPair ]) + static_cast<tools::Long>(pCr[ nXPair + 1 ]) ) >> 1;
+ }
+ }
+ else {
+ nL = pL1[ nx ];
+ if ( ( nx & 1 ) == 0 )
+ {
+ nCb = ( static_cast<tools::Long>(pCb[ nXPair ]) + static_cast<tools::Long>(pCbN[ nXPair ]) ) >> 1;
+ nCr = ( static_cast<tools::Long>(pCr[ nXPair ]) + static_cast<tools::Long>(pCrN[ nXPair ]) ) >> 1;
+ }
+ else
+ {
+ nCb = ( static_cast<tools::Long>(pCb[ nXPair ]) + static_cast<tools::Long>(pCb[ nXPair + 1 ]) +
+ static_cast<tools::Long>(pCbN[ nXPair ]) + static_cast<tools::Long>(pCbN[ nXPair + 1 ]) ) >> 2;
+ nCr = ( static_cast<tools::Long>(pCr[ nXPair ]) + static_cast<tools::Long>(pCr[ nXPair + 1]) +
+ static_cast<tools::Long>(pCrN[ nXPair ]) + static_cast<tools::Long>(pCrN[ nXPair + 1 ]) ) >> 2;
+ }
+ }
+ // conversion of nL,nCb,nCr in nRed,nGreen,nBlue:
+ nL *= 89024;
+ nCb -= 156;
+ nCr -= 137;
+ tools::Long nRed = ( nL + nCr * 119374 + 0x8000 ) >> 16;
+ if ( nRed < 0 )
+ nRed = 0;
+ if ( nRed > 255)
+ nRed = 255;
+ tools::Long nGreen = ( nL - nCb * 28198 - nCr * 60761 + 0x8000 ) >> 16;
+ if ( nGreen < 0 )
+ nGreen = 0;
+ if ( nGreen > 255 )
+ nGreen = 255;
+ tools::Long nBlue = ( nL + nCb * 145352 + 0x8000 ) >> 16;
+ if ( nBlue < 0 )
+ nBlue = 0;
+ if ( nBlue > 255 )
+ nBlue = 255;
+
+ // register color value in pBMPMap:
+ if ( nOrientation < 2 )
+ {
+ if ( nOrientation == 0 )
+ mpBitmap->SetPixel( ny, nx, Color( static_cast<sal_uInt8>(nRed), static_cast<sal_uInt8>(nGreen), static_cast<sal_uInt8>(nBlue) ) );
+ else
+ mpBitmap->SetPixel( nWidth - 1 - nx, ny, Color( static_cast<sal_uInt8>(nRed), static_cast<sal_uInt8>(nGreen), static_cast<sal_uInt8>(nBlue) ) );
+ }
+ else
+ {
+ if ( nOrientation == 2 )
+ mpBitmap->SetPixel( nHeight - 1 - ny, ( nWidth - 1 - nx ), Color( static_cast<sal_uInt8>(nRed), static_cast<sal_uInt8>(nGreen), static_cast<sal_uInt8>(nBlue) ) );
+ else
+ mpBitmap->SetPixel( nx, ( nHeight - 1 - ny ), Color( static_cast<sal_uInt8>(nRed), static_cast<sal_uInt8>(nGreen), static_cast<sal_uInt8>(nBlue) ) );
+ }
+ }
+ }
+
+ if ( m_rPCD.GetError() )
+ bStatus = false;
+ if ( !bStatus )
+ break;
+ }
+}
+
+//================== GraphicImport - the exported Function ================
+
+bool ImportPcdGraphic(SvStream & rStream, Graphic & rGraphic, FilterConfigItem* pConfigItem)
+{
+ PCDReader aPCDReader(rStream);
+ return aPCDReader.ReadPCD(rGraphic, pConfigItem);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ipcx/ipcx.cxx b/vcl/source/filter/ipcx/ipcx.cxx
new file mode 100644
index 000000000..b1162d5ec
--- /dev/null
+++ b/vcl/source/filter/ipcx/ipcx.cxx
@@ -0,0 +1,411 @@
+/* -*- 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 <memory>
+#include <vcl/graph.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <tools/stream.hxx>
+#include <filter/PcxReader.hxx>
+
+class FilterConfigItem;
+
+//============================ PCXReader ==================================
+
+namespace {
+
+class PCXReader {
+
+private:
+
+ SvStream& m_rPCX; // the PCX file to read
+
+ std::unique_ptr<vcl::bitmap::RawBitmap> mpBitmap;
+ std::vector<Color> mvPalette;
+ sal_uInt8 nVersion; // PCX-Version
+ sal_uInt8 nEncoding; // compression type
+ sal_uInt16 nBitsPerPlanePix; // bits per plane per pixel
+ sal_uInt16 nPlanes; // no of planes
+ sal_uInt16 nBytesPerPlaneLin; // bytes per plane line
+
+ sal_uInt32 nWidth, nHeight; // dimension in pixel
+ sal_uInt16 nResX, nResY; // resolution in pixel per inch or 0,0
+ sal_uInt16 nDestBitsPerPixel; // bits per pixel in destination bitmap 1,4,8 or 24
+ std::unique_ptr<sal_uInt8[]>
+ pPalette;
+ bool bStatus; // from now on do not read status from stream ( SJ )
+
+
+ void ImplReadBody();
+ void ImplReadPalette( unsigned int nCol );
+ void ImplReadHeader();
+
+public:
+ explicit PCXReader(SvStream &rStream);
+ bool ReadPCX(Graphic & rGraphic );
+ // Reads a PCX file from the stream and fills the GDIMetaFile
+};
+
+}
+
+//=================== methods of PCXReader ==============================
+
+PCXReader::PCXReader(SvStream &rStream)
+ : m_rPCX(rStream)
+ , nVersion(0)
+ , nEncoding(0)
+ , nBitsPerPlanePix(0)
+ , nPlanes(0)
+ , nBytesPerPlaneLin(0)
+ , nWidth(0)
+ , nHeight(0)
+ , nResX(0)
+ , nResY(0)
+ , nDestBitsPerPixel(0)
+ , pPalette(new sal_uInt8[ 768 ])
+ , bStatus(false)
+{
+}
+
+bool PCXReader::ReadPCX(Graphic & rGraphic)
+{
+ if ( m_rPCX.GetError() )
+ return false;
+
+ m_rPCX.SetEndian(SvStreamEndian::LITTLE);
+
+ // read header:
+
+ bStatus = true;
+
+ ImplReadHeader();
+
+ // sanity check there is enough data before trying allocation
+ if (bStatus && nBytesPerPlaneLin > m_rPCX.remainingSize() / nPlanes)
+ {
+ bStatus = false;
+ }
+
+ if (bStatus)
+ {
+ sal_uInt32 nResult;
+ bStatus = !o3tl::checked_multiply(nWidth, nHeight, nResult) && nResult <= SAL_MAX_INT32/2/3;
+ }
+
+ // Write BMP header and conditionally (maybe invalid for now) color palette:
+ if (bStatus)
+ {
+ mpBitmap.reset( new vcl::bitmap::RawBitmap( Size( nWidth, nHeight ), 24 ) );
+
+ if ( nDestBitsPerPixel <= 8 )
+ {
+ sal_uInt16 nColors = 1 << nDestBitsPerPixel;
+ sal_uInt8* pPal = pPalette.get();
+ mvPalette.resize( nColors );
+ for ( sal_uInt16 i = 0; i < nColors; i++, pPal += 3 )
+ {
+ mvPalette[i] = Color( pPal[ 0 ], pPal[ 1 ], pPal[ 2 ] );
+ }
+ }
+
+ // read bitmap data
+ ImplReadBody();
+
+ // If an extended color palette exists at the end of the file, then read it and
+ // and write again in palette:
+ if ( nDestBitsPerPixel == 8 && bStatus )
+ {
+ sal_uInt8* pPal = pPalette.get();
+ m_rPCX.SeekRel(1);
+ ImplReadPalette(256);
+ mvPalette.resize( 256 );
+ for ( sal_uInt16 i = 0; i < 256; i++, pPal += 3 )
+ {
+ mvPalette[i] = Color( pPal[ 0 ], pPal[ 1 ], pPal[ 2 ] );
+ }
+ }
+
+ if ( bStatus )
+ {
+ rGraphic = vcl::bitmap::CreateFromData(std::move(*mpBitmap));
+ return true;
+ }
+ }
+ return false;
+}
+
+void PCXReader::ImplReadHeader()
+{
+ sal_uInt8 nbyte(0);
+ m_rPCX.ReadUChar( nbyte ).ReadUChar( nVersion ).ReadUChar( nEncoding );
+ if ( nbyte!=0x0a || (nVersion != 0 && nVersion != 2 && nVersion != 3 && nVersion != 5) || nEncoding > 1 )
+ {
+ bStatus = false;
+ return;
+ }
+
+ nbyte = 0;
+ m_rPCX.ReadUChar( nbyte ); nBitsPerPlanePix = static_cast<sal_uInt16>(nbyte);
+ sal_uInt16 nMinX(0),nMinY(0),nMaxX(0),nMaxY(0);
+ m_rPCX.ReadUInt16( nMinX ).ReadUInt16( nMinY ).ReadUInt16( nMaxX ).ReadUInt16( nMaxY );
+
+ if ((nMinX > nMaxX) || (nMinY > nMaxY))
+ {
+ bStatus = false;
+ return;
+ }
+
+ nWidth = nMaxX-nMinX+1;
+ nHeight = nMaxY-nMinY+1;
+
+ m_rPCX.ReadUInt16( nResX );
+ m_rPCX.ReadUInt16( nResY );
+ if ( nResX >= nWidth || nResY >= nHeight || ( nResX != nResY ) )
+ nResX = nResY = 0;
+
+ ImplReadPalette( 16 );
+
+ m_rPCX.SeekRel( 1 );
+ nbyte = 0;
+ m_rPCX.ReadUChar( nbyte ); nPlanes = static_cast<sal_uInt16>(nbyte);
+ sal_uInt16 nushort(0);
+ m_rPCX.ReadUInt16( nushort ); nBytesPerPlaneLin = nushort;
+ sal_uInt16 nPaletteInfo;
+ m_rPCX.ReadUInt16( nPaletteInfo );
+
+ m_rPCX.SeekRel( 58 );
+
+ nDestBitsPerPixel = nBitsPerPlanePix * nPlanes;
+ if (nDestBitsPerPixel == 2 || nDestBitsPerPixel == 3) nDestBitsPerPixel = 4;
+
+ if ( ( nDestBitsPerPixel != 1 && nDestBitsPerPixel != 4 && nDestBitsPerPixel != 8 && nDestBitsPerPixel != 24 )
+ || nPlanes > 4 || nBytesPerPlaneLin < ( ( nWidth * nBitsPerPlanePix+7 ) >> 3 ) )
+ {
+ bStatus = false;
+ return;
+ }
+
+ // If the bitmap has only 2 colors, the palette is most often invalid and it is always(?)
+ // a black and white image:
+ if ( nPlanes == 1 && nBitsPerPlanePix == 1 )
+ {
+ pPalette[ 0 ] = pPalette[ 1 ] = pPalette[ 2 ] = 0x00;
+ pPalette[ 3 ] = pPalette[ 4 ] = pPalette[ 5 ] = 0xff;
+ }
+}
+
+void PCXReader::ImplReadBody()
+{
+ std::unique_ptr<sal_uInt8[]> pPlane[ 4 ];
+ sal_uInt8 * pDest;
+ sal_uInt32 i, ny, nLastPercent = 0, nPercent;
+ sal_uInt16 nCount, nx;
+ sal_uInt8 nDat = 0, nCol = 0;
+
+ for (sal_uInt16 np = 0; np < nPlanes; ++np)
+ pPlane[np].reset(new sal_uInt8[nBytesPerPlaneLin]());
+
+ nCount = 0;
+ for ( ny = 0; ny < nHeight; ny++ )
+ {
+ if (!m_rPCX.good())
+ {
+ bStatus = false;
+ break;
+ }
+ nPercent = ny * 60 / nHeight + 10;
+ if ( ny == 0 || nLastPercent + 4 <= nPercent )
+ {
+ nLastPercent = nPercent;
+ }
+ for (sal_uInt16 np = 0; np < nPlanes; ++np)
+ {
+ if ( nEncoding == 0)
+ m_rPCX.ReadBytes( static_cast<void *>(pPlane[ np ].get()), nBytesPerPlaneLin );
+ else
+ {
+ pDest = pPlane[ np ].get();
+ nx = nBytesPerPlaneLin;
+ while ( nCount > 0 && nx > 0)
+ {
+ *(pDest++) = nDat;
+ nx--;
+ nCount--;
+ }
+ while (nx > 0 && m_rPCX.good())
+ {
+ m_rPCX.ReadUChar( nDat );
+ if ( ( nDat & 0xc0 ) == 0xc0 )
+ {
+ nCount =static_cast<sal_uInt64>(nDat) & 0x003f;
+ m_rPCX.ReadUChar( nDat );
+ if ( nCount < nx )
+ {
+ nx -= nCount;
+ while ( nCount > 0)
+ {
+ *(pDest++) = nDat;
+ nCount--;
+ }
+ }
+ else
+ {
+ nCount -= nx;
+ do
+ {
+ *(pDest++) = nDat;
+ nx--;
+ }
+ while ( nx > 0 );
+ break;
+ }
+ }
+ else
+ {
+ *(pDest++) = nDat;
+ nx--;
+ }
+ }
+ }
+ }
+ sal_uInt8 *pSource1 = pPlane[ 0 ].get();
+ sal_uInt8 *pSource2 = pPlane[ 1 ].get();
+ sal_uInt8 *pSource3 = pPlane[ 2 ].get();
+ sal_uInt8 *pSource4 = pPlane[ 3 ].get();
+ switch ( nBitsPerPlanePix + ( nPlanes << 8 ) )
+ {
+ // 2 colors
+ case 0x101 :
+ for ( i = 0; i < nWidth; i++ )
+ {
+ sal_uInt32 nShift = ( i & 7 ) ^ 7;
+ if ( nShift == 0 )
+ mpBitmap->SetPixel( ny, i, mvPalette[*(pSource1++) & 1] );
+ else
+ mpBitmap->SetPixel( ny, i, mvPalette[(*pSource1 >> nShift ) & 1] );
+ }
+ break;
+ // 4 colors
+ case 0x102 :
+ for ( i = 0; i < nWidth; i++ )
+ {
+ switch( i & 3 )
+ {
+ case 0 :
+ nCol = *pSource1 >> 6;
+ break;
+ case 1 :
+ nCol = ( *pSource1 >> 4 ) & 0x03 ;
+ break;
+ case 2 :
+ nCol = ( *pSource1 >> 2 ) & 0x03;
+ break;
+ case 3 :
+ nCol = ( *pSource1++ ) & 0x03;
+ break;
+ }
+ mpBitmap->SetPixel( ny, i, mvPalette[nCol] );
+ }
+ break;
+ // 256 colors
+ case 0x108 :
+ for ( i = 0; i < nWidth; i++ )
+ {
+ mpBitmap->SetPixel( ny, i, mvPalette[*pSource1++] );
+ }
+ break;
+ // 8 colors
+ case 0x301 :
+ for ( i = 0; i < nWidth; i++ )
+ {
+ sal_uInt32 nShift = ( i & 7 ) ^ 7;
+ if ( nShift == 0 )
+ {
+ nCol = ( *pSource1++ & 1) + ( ( *pSource2++ << 1 ) & 2 ) + ( ( *pSource3++ << 2 ) & 4 );
+ mpBitmap->SetPixel( ny, i, mvPalette[nCol] );
+ }
+ else
+ {
+ nCol = sal::static_int_cast< sal_uInt8 >(
+ ( ( *pSource1 >> nShift ) & 1) + ( ( ( *pSource2 >> nShift ) << 1 ) & 2 ) +
+ ( ( ( *pSource3 >> nShift ) << 2 ) & 4 ));
+ mpBitmap->SetPixel( ny, i, mvPalette[nCol] );
+ }
+ }
+ break;
+ // 16 colors
+ case 0x401 :
+ for ( i = 0; i < nWidth; i++ )
+ {
+ sal_uInt32 nShift = ( i & 7 ) ^ 7;
+ if ( nShift == 0 )
+ {
+ nCol = ( *pSource1++ & 1) + ( ( *pSource2++ << 1 ) & 2 ) + ( ( *pSource3++ << 2 ) & 4 ) +
+ ( ( *pSource4++ << 3 ) & 8 );
+ mpBitmap->SetPixel( ny, i, mvPalette[nCol] );
+ }
+ else
+ {
+ nCol = sal::static_int_cast< sal_uInt8 >(
+ ( ( *pSource1 >> nShift ) & 1) + ( ( ( *pSource2 >> nShift ) << 1 ) & 2 ) +
+ ( ( ( *pSource3 >> nShift ) << 2 ) & 4 ) + ( ( ( *pSource4 >> nShift ) << 3 ) & 8 ));
+ mpBitmap->SetPixel( ny, i, mvPalette[nCol] );
+ }
+ }
+ break;
+ // 16m colors
+ case 0x308 :
+ for ( i = 0; i < nWidth; i++ )
+ {
+ mpBitmap->SetPixel( ny, i, Color( *pSource1++, *pSource2++, *pSource3++ ) );
+
+ }
+ break;
+ default :
+ bStatus = false;
+ break;
+ }
+ }
+}
+
+void PCXReader::ImplReadPalette( unsigned int nCol )
+{
+ sal_uInt8 r, g, b;
+ sal_uInt8* pPtr = pPalette.get();
+ for ( unsigned int i = 0; i < nCol; i++ )
+ {
+ m_rPCX.ReadUChar( r ).ReadUChar( g ).ReadUChar( b );
+ *pPtr++ = r;
+ *pPtr++ = g;
+ *pPtr++ = b;
+ }
+}
+
+//================== GraphicImport - the exported function ================
+
+bool ImportPcxGraphic(SvStream & rStream, Graphic & rGraphic)
+{
+ PCXReader aPCXReader(rStream);
+ bool bRetValue = aPCXReader.ReadPCX(rGraphic);
+ if ( !bRetValue )
+ rStream.SetError( SVSTREAM_FILEFORMAT_ERROR );
+ return bRetValue;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ipdf/pdfcompat.cxx b/vcl/source/filter/ipdf/pdfcompat.cxx
new file mode 100644
index 000000000..62413e585
--- /dev/null
+++ b/vcl/source/filter/ipdf/pdfcompat.cxx
@@ -0,0 +1,114 @@
+/* -*- 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/.
+ */
+
+#include <pdf/pdfcompat.hxx>
+
+#include <o3tl/string_view.hxx>
+#include <vcl/filter/PDFiumLibrary.hxx>
+#include <sal/log.hxx>
+
+namespace vcl::pdf
+{
+/// Decide if PDF data is old enough to be compatible.
+bool isCompatible(SvStream& rInStream, sal_uInt64 nPos, sal_uInt64 nSize)
+{
+ if (nSize < 8)
+ return false;
+
+ // %PDF-x.y
+ sal_uInt8 aFirstBytes[8];
+ rInStream.Seek(nPos);
+ sal_uLong nRead = rInStream.ReadBytes(aFirstBytes, 8);
+ if (nRead < 8)
+ return false;
+
+ if (aFirstBytes[0] != '%' || aFirstBytes[1] != 'P' || aFirstBytes[2] != 'D'
+ || aFirstBytes[3] != 'F' || aFirstBytes[4] != '-')
+ return false;
+
+ sal_Int32 nMajor = o3tl::toInt32(std::string_view(reinterpret_cast<char*>(&aFirstBytes[5]), 1));
+ sal_Int32 nMinor = o3tl::toInt32(std::string_view(reinterpret_cast<char*>(&aFirstBytes[7]), 1));
+ return !(nMajor > 1 || (nMajor == 1 && nMinor > 6));
+}
+
+/// Converts to highest supported format version (1.6).
+/// Usually used to deal with missing referenced objects in source
+/// pdf stream.
+bool convertToHighestSupported(SvStream& rInStream, SvStream& rOutStream)
+{
+ sal_uInt64 nPos = STREAM_SEEK_TO_BEGIN;
+ sal_uInt64 nSize = STREAM_SEEK_TO_END;
+ rInStream.Seek(nPos);
+ // Convert to PDF-1.6.
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+ if (!pPdfium)
+ return false;
+
+ // Read input into a buffer.
+ SvMemoryStream aInBuffer;
+ aInBuffer.WriteStream(rInStream, nSize);
+
+ SvMemoryStream aSaved;
+ {
+ // Load the buffer using pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
+ = pPdfium->openDocument(aInBuffer.GetData(), aInBuffer.GetSize(), OString());
+ if (!pPdfDocument)
+ return false;
+
+ // 16 means PDF-1.6.
+ if (!pPdfDocument->saveWithVersion(aSaved, 16))
+ return false;
+ }
+
+ aSaved.Seek(STREAM_SEEK_TO_BEGIN);
+ rOutStream.WriteStream(aSaved);
+
+ return rOutStream.good();
+}
+
+/// Takes care of transparently downgrading the version of the PDF stream in
+/// case it's too new for our PDF export.
+bool getCompatibleStream(SvStream& rInStream, SvStream& rOutStream)
+{
+ sal_uInt64 nPos = STREAM_SEEK_TO_BEGIN;
+ sal_uInt64 nSize = STREAM_SEEK_TO_END;
+ bool bCompatible = isCompatible(rInStream, nPos, nSize);
+ rInStream.Seek(nPos);
+ if (bCompatible)
+ // Not converting.
+ rOutStream.WriteStream(rInStream, nSize);
+ else
+ convertToHighestSupported(rInStream, rOutStream);
+
+ return rOutStream.good();
+}
+
+BinaryDataContainer createBinaryDataContainer(SvStream& rStream)
+{
+ // Save the original PDF stream for later use.
+ SvMemoryStream aMemoryStream;
+ if (!getCompatibleStream(rStream, aMemoryStream))
+ return {};
+
+ const sal_uInt32 nStreamLength = aMemoryStream.TellEnd();
+
+ auto aPdfData = std::make_unique<std::vector<sal_uInt8>>(nStreamLength);
+
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ aMemoryStream.ReadBytes(aPdfData->data(), aPdfData->size());
+ if (aMemoryStream.GetError())
+ return {};
+
+ return { std::move(aPdfData) };
+}
+
+} // end vcl::filter::ipdf namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx
new file mode 100644
index 000000000..a93083ce8
--- /dev/null
+++ b/vcl/source/filter/ipdf/pdfdocument.cxx
@@ -0,0 +1,3325 @@
+/* -*- 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/.
+ */
+
+#include <vcl/filter/pdfdocument.hxx>
+#include <pdf/pdfcompat.hxx>
+#include <config_features.h>
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/security/XCertificate.hpp>
+
+#include <comphelper/scopeguard.hxx>
+#include <comphelper/string.hxx>
+#include <o3tl/string_view.hxx>
+#include <rtl/character.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/string.hxx>
+#include <sal/log.hxx>
+#include <sal/types.h>
+#include <svl/cryptosign.hxx>
+#include <tools/zcodec.hxx>
+#include <vcl/pdfwriter.hxx>
+#include <o3tl/safeint.hxx>
+
+#include <pdf/objectcopier.hxx>
+
+using namespace com::sun::star;
+
+namespace vcl::filter
+{
+XRefEntry::XRefEntry() = default;
+
+PDFDocument::PDFDocument() = default;
+
+PDFDocument::~PDFDocument() = default;
+
+bool PDFDocument::RemoveSignature(size_t nPosition)
+{
+ std::vector<PDFObjectElement*> aSignatures = GetSignatureWidgets();
+ if (nPosition >= aSignatures.size())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::RemoveSignature: invalid nPosition");
+ return false;
+ }
+
+ if (aSignatures.size() != m_aEOFs.size() - 1)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::RemoveSignature: no 1:1 mapping between signatures "
+ "and incremental updates");
+ return false;
+ }
+
+ // The EOF offset is the end of the original file, without the signature at
+ // nPosition.
+ m_aEditBuffer.Seek(m_aEOFs[nPosition]);
+ // Drop all bytes after the current position.
+ m_aEditBuffer.SetStreamSize(m_aEditBuffer.Tell() + 1);
+
+ return m_aEditBuffer.good();
+}
+
+sal_Int32 PDFDocument::createObject()
+{
+ sal_Int32 nObject = m_aXRef.size();
+ m_aXRef[nObject] = XRefEntry();
+ return nObject;
+}
+
+bool PDFDocument::updateObject(sal_Int32 nObject)
+{
+ if (o3tl::make_unsigned(nObject) >= m_aXRef.size())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::updateObject: invalid nObject");
+ return false;
+ }
+
+ XRefEntry aEntry;
+ aEntry.SetOffset(m_aEditBuffer.Tell());
+ aEntry.SetDirty(true);
+ m_aXRef[nObject] = aEntry;
+ return true;
+}
+
+bool PDFDocument::writeBuffer(const void* pBuffer, sal_uInt64 nBytes)
+{
+ std::size_t nWritten = m_aEditBuffer.WriteBytes(pBuffer, nBytes);
+ return nWritten == nBytes;
+}
+
+void PDFDocument::SetSignatureLine(std::vector<sal_Int8>&& rSignatureLine)
+{
+ m_aSignatureLine = std::move(rSignatureLine);
+}
+
+void PDFDocument::SetSignaturePage(size_t nPage) { m_nSignaturePage = nPage; }
+
+sal_uInt32 PDFDocument::GetNextSignature()
+{
+ sal_uInt32 nRet = 0;
+ for (const auto& pSignature : GetSignatureWidgets())
+ {
+ auto pT = dynamic_cast<PDFLiteralStringElement*>(pSignature->Lookup("T"));
+ if (!pT)
+ continue;
+
+ const OString& rValue = pT->GetValue();
+ const OString aPrefix = "Signature";
+ if (!rValue.startsWith(aPrefix))
+ continue;
+
+ nRet = std::max(nRet, o3tl::toUInt32(rValue.subView(aPrefix.getLength())));
+ }
+
+ return nRet + 1;
+}
+
+sal_Int32 PDFDocument::WriteSignatureObject(const OUString& rDescription, bool bAdES,
+ sal_uInt64& rLastByteRangeOffset,
+ sal_Int64& rContentOffset)
+{
+ // Write signature object.
+ sal_Int32 nSignatureId = m_aXRef.size();
+ XRefEntry aSignatureEntry;
+ aSignatureEntry.SetOffset(m_aEditBuffer.Tell());
+ aSignatureEntry.SetDirty(true);
+ m_aXRef[nSignatureId] = aSignatureEntry;
+ OStringBuffer aSigBuffer;
+ aSigBuffer.append(nSignatureId);
+ aSigBuffer.append(" 0 obj\n");
+ aSigBuffer.append("<</Contents <");
+ rContentOffset = aSignatureEntry.GetOffset() + aSigBuffer.getLength();
+ // Reserve space for the PKCS#7 object.
+ OStringBuffer aContentFiller(MAX_SIGNATURE_CONTENT_LENGTH);
+ comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0');
+ aSigBuffer.append(aContentFiller);
+ aSigBuffer.append(">\n/Type/Sig/SubFilter");
+ if (bAdES)
+ aSigBuffer.append("/ETSI.CAdES.detached");
+ else
+ aSigBuffer.append("/adbe.pkcs7.detached");
+
+ // Time of signing.
+ aSigBuffer.append(" /M (");
+ aSigBuffer.append(vcl::PDFWriter::GetDateTime());
+ aSigBuffer.append(")");
+
+ // Byte range: we can write offset1-length1 and offset2 right now, will
+ // write length2 later.
+ aSigBuffer.append(" /ByteRange [ 0 ");
+ // -1 and +1 is the leading "<" and the trailing ">" around the hex string.
+ aSigBuffer.append(rContentOffset - 1);
+ aSigBuffer.append(" ");
+ aSigBuffer.append(rContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
+ aSigBuffer.append(" ");
+ rLastByteRangeOffset = aSignatureEntry.GetOffset() + aSigBuffer.getLength();
+ // We don't know how many bytes we need for the last ByteRange value, this
+ // should be enough.
+ OStringBuffer aByteRangeFiller;
+ comphelper::string::padToLength(aByteRangeFiller, 100, ' ');
+ aSigBuffer.append(aByteRangeFiller);
+ // Finish the Sig obj.
+ aSigBuffer.append(" /Filter/Adobe.PPKMS");
+
+ if (!rDescription.isEmpty())
+ {
+ aSigBuffer.append("/Reason<");
+ vcl::PDFWriter::AppendUnicodeTextString(rDescription, aSigBuffer);
+ aSigBuffer.append(">");
+ }
+
+ aSigBuffer.append(" >>\nendobj\n\n");
+ m_aEditBuffer.WriteOString(aSigBuffer);
+
+ return nSignatureId;
+}
+
+sal_Int32 PDFDocument::WriteAppearanceObject(tools::Rectangle& rSignatureRectangle)
+{
+ PDFDocument aPDFDocument;
+ filter::PDFObjectElement* pPage = nullptr;
+ std::vector<filter::PDFObjectElement*> aContentStreams;
+
+ if (!m_aSignatureLine.empty())
+ {
+ // Parse the PDF data of signature line: we can set the signature rectangle to non-empty
+ // based on it.
+ SvMemoryStream aPDFStream;
+ aPDFStream.WriteBytes(m_aSignatureLine.data(), m_aSignatureLine.size());
+ aPDFStream.Seek(0);
+ if (!aPDFDocument.Read(aPDFStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::WriteAppearanceObject: failed to read the PDF document");
+ return -1;
+ }
+
+ std::vector<filter::PDFObjectElement*> aPages = aPDFDocument.GetPages();
+ if (aPages.empty())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::WriteAppearanceObject: no pages");
+ return -1;
+ }
+
+ pPage = aPages[0];
+ if (!pPage)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::WriteAppearanceObject: no page");
+ return -1;
+ }
+
+ // Calculate the bounding box.
+ PDFElement* pMediaBox = pPage->Lookup("MediaBox");
+ auto pMediaBoxArray = dynamic_cast<PDFArrayElement*>(pMediaBox);
+ if (!pMediaBoxArray || pMediaBoxArray->GetElements().size() < 4)
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::WriteAppearanceObject: MediaBox is not an array of 4");
+ return -1;
+ }
+ const std::vector<PDFElement*>& rMediaBoxElements = pMediaBoxArray->GetElements();
+ auto pWidth = dynamic_cast<PDFNumberElement*>(rMediaBoxElements[2]);
+ if (!pWidth)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::WriteAppearanceObject: MediaBox has no width");
+ return -1;
+ }
+ rSignatureRectangle.setWidth(pWidth->GetValue());
+ auto pHeight = dynamic_cast<PDFNumberElement*>(rMediaBoxElements[3]);
+ if (!pHeight)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::WriteAppearanceObject: MediaBox has no height");
+ return -1;
+ }
+ rSignatureRectangle.setHeight(pHeight->GetValue());
+
+ if (PDFObjectElement* pContentStream = pPage->LookupObject("Contents"))
+ {
+ aContentStreams.push_back(pContentStream);
+ }
+
+ if (aContentStreams.empty())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::WriteAppearanceObject: no content stream");
+ return -1;
+ }
+ }
+ m_aSignatureLine.clear();
+
+ // Write appearance object: allocate an ID.
+ sal_Int32 nAppearanceId = m_aXRef.size();
+ m_aXRef[nAppearanceId] = XRefEntry();
+
+ // Write the object content.
+ SvMemoryStream aEditBuffer;
+ aEditBuffer.WriteUInt32AsString(nAppearanceId);
+ aEditBuffer.WriteCharPtr(" 0 obj\n");
+ aEditBuffer.WriteCharPtr("<</Type/XObject\n/Subtype/Form\n");
+
+ PDFObjectCopier aCopier(*this);
+ if (!aContentStreams.empty())
+ {
+ assert(pPage && "aContentStreams is only filled if there was a pPage");
+ OStringBuffer aBuffer;
+ aCopier.copyPageResources(pPage, aBuffer);
+ aEditBuffer.WriteOString(aBuffer);
+ }
+
+ aEditBuffer.WriteCharPtr("/BBox[0 0 ");
+ aEditBuffer.WriteOString(OString::number(rSignatureRectangle.getWidth()));
+ aEditBuffer.WriteCharPtr(" ");
+ aEditBuffer.WriteOString(OString::number(rSignatureRectangle.getHeight()));
+ aEditBuffer.WriteCharPtr("]\n/Length ");
+
+ // Add the object to the doc-level edit buffer and update the offset.
+ SvMemoryStream aStream;
+ bool bCompressed = false;
+ sal_Int32 nLength = 0;
+ if (!aContentStreams.empty())
+ {
+ nLength = PDFObjectCopier::copyPageStreams(aContentStreams, aStream, bCompressed);
+ }
+ aEditBuffer.WriteOString(OString::number(nLength));
+ if (bCompressed)
+ {
+ aEditBuffer.WriteOString(" /Filter/FlateDecode");
+ }
+
+ aEditBuffer.WriteCharPtr("\n>>\n");
+
+ aEditBuffer.WriteCharPtr("stream\n");
+
+ // Copy the original page streams to the form XObject stream.
+ aStream.Seek(0);
+ aEditBuffer.WriteStream(aStream);
+
+ aEditBuffer.WriteCharPtr("\nendstream\nendobj\n\n");
+
+ aEditBuffer.Seek(0);
+ XRefEntry aAppearanceEntry;
+ aAppearanceEntry.SetOffset(m_aEditBuffer.Tell());
+ aAppearanceEntry.SetDirty(true);
+ m_aXRef[nAppearanceId] = aAppearanceEntry;
+ m_aEditBuffer.WriteStream(aEditBuffer);
+
+ return nAppearanceId;
+}
+
+sal_Int32 PDFDocument::WriteAnnotObject(PDFObjectElement const& rFirstPage, sal_Int32 nSignatureId,
+ sal_Int32 nAppearanceId,
+ const tools::Rectangle& rSignatureRectangle)
+{
+ // Decide what identifier to use for the new signature.
+ sal_uInt32 nNextSignature = GetNextSignature();
+
+ // Write the Annot object, references nSignatureId and nAppearanceId.
+ sal_Int32 nAnnotId = m_aXRef.size();
+ XRefEntry aAnnotEntry;
+ aAnnotEntry.SetOffset(m_aEditBuffer.Tell());
+ aAnnotEntry.SetDirty(true);
+ m_aXRef[nAnnotId] = aAnnotEntry;
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n");
+ m_aEditBuffer.WriteCharPtr("<</Type/Annot/Subtype/Widget/F 132\n");
+ m_aEditBuffer.WriteCharPtr("/Rect[0 0 ");
+ m_aEditBuffer.WriteOString(OString::number(rSignatureRectangle.getWidth()));
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteOString(OString::number(rSignatureRectangle.getHeight()));
+ m_aEditBuffer.WriteCharPtr("]\n");
+ m_aEditBuffer.WriteCharPtr("/FT/Sig\n");
+ m_aEditBuffer.WriteCharPtr("/P ");
+ m_aEditBuffer.WriteUInt32AsString(rFirstPage.GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" 0 R\n");
+ m_aEditBuffer.WriteCharPtr("/T(Signature");
+ m_aEditBuffer.WriteUInt32AsString(nNextSignature);
+ m_aEditBuffer.WriteCharPtr(")\n");
+ m_aEditBuffer.WriteCharPtr("/V ");
+ m_aEditBuffer.WriteUInt32AsString(nSignatureId);
+ m_aEditBuffer.WriteCharPtr(" 0 R\n");
+ m_aEditBuffer.WriteCharPtr("/DV ");
+ m_aEditBuffer.WriteUInt32AsString(nSignatureId);
+ m_aEditBuffer.WriteCharPtr(" 0 R\n");
+ m_aEditBuffer.WriteCharPtr("/AP<<\n/N ");
+ m_aEditBuffer.WriteUInt32AsString(nAppearanceId);
+ m_aEditBuffer.WriteCharPtr(" 0 R\n>>\n");
+ m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n");
+
+ return nAnnotId;
+}
+
+bool PDFDocument::WritePageObject(PDFObjectElement& rFirstPage, sal_Int32 nAnnotId)
+{
+ PDFElement* pAnnots = rFirstPage.Lookup("Annots");
+ auto pAnnotsReference = dynamic_cast<PDFReferenceElement*>(pAnnots);
+ if (pAnnotsReference)
+ {
+ // Write the updated Annots key of the Page object.
+ PDFObjectElement* pAnnotsObject = pAnnotsReference->LookupObject();
+ if (!pAnnotsObject)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: invalid Annots reference");
+ return false;
+ }
+
+ sal_uInt32 nAnnotsId = pAnnotsObject->GetObjectValue();
+ m_aXRef[nAnnotsId].SetType(XRefEntryType::NOT_COMPRESSED);
+ m_aXRef[nAnnotsId].SetOffset(m_aEditBuffer.Tell());
+ m_aXRef[nAnnotsId].SetDirty(true);
+ m_aEditBuffer.WriteUInt32AsString(nAnnotsId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n[");
+
+ // Write existing references.
+ PDFArrayElement* pArray = pAnnotsObject->GetArray();
+ if (!pArray)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: Page Annots is a reference to a non-array");
+ return false;
+ }
+
+ for (size_t i = 0; i < pArray->GetElements().size(); ++i)
+ {
+ auto pReference = dynamic_cast<PDFReferenceElement*>(pArray->GetElements()[i]);
+ if (!pReference)
+ continue;
+
+ if (i)
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(pReference->GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" 0 R");
+ }
+ // Write our reference.
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R");
+
+ m_aEditBuffer.WriteCharPtr("]\nendobj\n\n");
+ }
+ else
+ {
+ // Write the updated first page object, references nAnnotId.
+ sal_uInt32 nFirstPageId = rFirstPage.GetObjectValue();
+ if (nFirstPageId >= m_aXRef.size())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: invalid first page obj id");
+ return false;
+ }
+ m_aXRef[nFirstPageId].SetOffset(m_aEditBuffer.Tell());
+ m_aXRef[nFirstPageId].SetDirty(true);
+ m_aEditBuffer.WriteUInt32AsString(nFirstPageId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n");
+ m_aEditBuffer.WriteCharPtr("<<");
+ auto pAnnotsArray = dynamic_cast<PDFArrayElement*>(pAnnots);
+ if (!pAnnotsArray)
+ {
+ // No Annots key, just write the key with a single reference.
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + rFirstPage.GetDictionaryOffset(),
+ rFirstPage.GetDictionaryLength());
+ m_aEditBuffer.WriteCharPtr("/Annots[");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R]");
+ }
+ else
+ {
+ // Annots key is already there, insert our reference at the end.
+ PDFDictionaryElement* pDictionary = rFirstPage.GetDictionary();
+
+ // Offset right before the end of the Annots array.
+ sal_uInt64 nAnnotsEndOffset = pDictionary->GetKeyOffset("Annots")
+ + pDictionary->GetKeyValueLength("Annots") - 1;
+ // Length of beginning of the dictionary -> Annots end.
+ sal_uInt64 nAnnotsBeforeEndLength = nAnnotsEndOffset - rFirstPage.GetDictionaryOffset();
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + rFirstPage.GetDictionaryOffset(),
+ nAnnotsBeforeEndLength);
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R");
+ // Length of Annots end -> end of the dictionary.
+ sal_uInt64 nAnnotsAfterEndLength = rFirstPage.GetDictionaryOffset()
+ + rFirstPage.GetDictionaryLength()
+ - nAnnotsEndOffset;
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + nAnnotsEndOffset,
+ nAnnotsAfterEndLength);
+ }
+ m_aEditBuffer.WriteCharPtr(">>");
+ m_aEditBuffer.WriteCharPtr("\nendobj\n\n");
+ }
+
+ return true;
+}
+
+bool PDFDocument::WriteCatalogObject(sal_Int32 nAnnotId, PDFReferenceElement*& pRoot)
+{
+ if (m_pXRefStream)
+ pRoot = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Root"));
+ else
+ {
+ if (!m_pTrailer)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: found no trailer");
+ return false;
+ }
+ pRoot = dynamic_cast<PDFReferenceElement*>(m_pTrailer->Lookup("Root"));
+ }
+ if (!pRoot)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: trailer has no root reference");
+ return false;
+ }
+ PDFObjectElement* pCatalog = pRoot->LookupObject();
+ if (!pCatalog)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: invalid catalog reference");
+ return false;
+ }
+ sal_uInt32 nCatalogId = pCatalog->GetObjectValue();
+ if (nCatalogId >= m_aXRef.size())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: invalid catalog obj id");
+ return false;
+ }
+ PDFElement* pAcroForm = pCatalog->Lookup("AcroForm");
+ auto pAcroFormReference = dynamic_cast<PDFReferenceElement*>(pAcroForm);
+ if (pAcroFormReference)
+ {
+ // Write the updated AcroForm key of the Catalog object.
+ PDFObjectElement* pAcroFormObject = pAcroFormReference->LookupObject();
+ if (!pAcroFormObject)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: invalid AcroForm reference");
+ return false;
+ }
+
+ sal_uInt32 nAcroFormId = pAcroFormObject->GetObjectValue();
+ m_aXRef[nAcroFormId].SetType(XRefEntryType::NOT_COMPRESSED);
+ m_aXRef[nAcroFormId].SetOffset(m_aEditBuffer.Tell());
+ m_aXRef[nAcroFormId].SetDirty(true);
+ m_aEditBuffer.WriteUInt32AsString(nAcroFormId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n");
+
+ // If this is nullptr, then the AcroForm object is not in an object stream.
+ SvMemoryStream* pStreamBuffer = pAcroFormObject->GetStreamBuffer();
+
+ if (!pAcroFormObject->Lookup("Fields"))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Sign: AcroForm object without required Fields key");
+ return false;
+ }
+
+ PDFDictionaryElement* pAcroFormDictionary = pAcroFormObject->GetDictionary();
+ if (!pAcroFormDictionary)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: AcroForm object has no dictionary");
+ return false;
+ }
+
+ // Offset right before the end of the Fields array.
+ sal_uInt64 nFieldsEndOffset = pAcroFormDictionary->GetKeyOffset("Fields")
+ + pAcroFormDictionary->GetKeyValueLength("Fields")
+ - strlen("]");
+
+ // Length of beginning of the object dictionary -> Fields end.
+ sal_uInt64 nFieldsBeforeEndLength = nFieldsEndOffset;
+ if (pStreamBuffer)
+ m_aEditBuffer.WriteBytes(pStreamBuffer->GetData(), nFieldsBeforeEndLength);
+ else
+ {
+ nFieldsBeforeEndLength -= pAcroFormObject->GetDictionaryOffset();
+ m_aEditBuffer.WriteCharPtr("<<");
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + pAcroFormObject->GetDictionaryOffset(),
+ nFieldsBeforeEndLength);
+ }
+
+ // Append our reference at the end of the Fields array.
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R");
+
+ // Length of Fields end -> end of the object dictionary.
+ if (pStreamBuffer)
+ {
+ sal_uInt64 nFieldsAfterEndLength = pStreamBuffer->GetSize() - nFieldsEndOffset;
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(pStreamBuffer->GetData())
+ + nFieldsEndOffset,
+ nFieldsAfterEndLength);
+ }
+ else
+ {
+ sal_uInt64 nFieldsAfterEndLength = pAcroFormObject->GetDictionaryOffset()
+ + pAcroFormObject->GetDictionaryLength()
+ - nFieldsEndOffset;
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + nFieldsEndOffset,
+ nFieldsAfterEndLength);
+ m_aEditBuffer.WriteCharPtr(">>");
+ }
+
+ m_aEditBuffer.WriteCharPtr("\nendobj\n\n");
+ }
+ else
+ {
+ // Write the updated Catalog object, references nAnnotId.
+ auto pAcroFormDictionary = dynamic_cast<PDFDictionaryElement*>(pAcroForm);
+ m_aXRef[nCatalogId].SetOffset(m_aEditBuffer.Tell());
+ m_aXRef[nCatalogId].SetDirty(true);
+ m_aEditBuffer.WriteUInt32AsString(nCatalogId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n");
+ m_aEditBuffer.WriteCharPtr("<<");
+ if (!pAcroFormDictionary)
+ {
+ // No AcroForm key, assume no signatures.
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + pCatalog->GetDictionaryOffset(),
+ pCatalog->GetDictionaryLength());
+ m_aEditBuffer.WriteCharPtr("/AcroForm<</Fields[\n");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R\n]/SigFlags 3>>\n");
+ }
+ else
+ {
+ // AcroForm key is already there, insert our reference at the Fields end.
+ auto it = pAcroFormDictionary->GetItems().find("Fields");
+ if (it == pAcroFormDictionary->GetItems().end())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: AcroForm without required Fields key");
+ return false;
+ }
+
+ auto pFields = dynamic_cast<PDFArrayElement*>(it->second);
+ if (!pFields)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: AcroForm Fields is not an array");
+ return false;
+ }
+
+ // Offset right before the end of the Fields array.
+ sal_uInt64 nFieldsEndOffset = pAcroFormDictionary->GetKeyOffset("Fields")
+ + pAcroFormDictionary->GetKeyValueLength("Fields") - 1;
+ // Length of beginning of the Catalog dictionary -> Fields end.
+ sal_uInt64 nFieldsBeforeEndLength = nFieldsEndOffset - pCatalog->GetDictionaryOffset();
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + pCatalog->GetDictionaryOffset(),
+ nFieldsBeforeEndLength);
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R");
+ // Length of Fields end -> end of the Catalog dictionary.
+ sal_uInt64 nFieldsAfterEndLength = pCatalog->GetDictionaryOffset()
+ + pCatalog->GetDictionaryLength() - nFieldsEndOffset;
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData())
+ + nFieldsEndOffset,
+ nFieldsAfterEndLength);
+ }
+ m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n");
+ }
+
+ return true;
+}
+
+void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement const* pRoot)
+{
+ if (m_pXRefStream)
+ {
+ // Write the xref stream.
+ // This is a bit meta: the xref stream stores its own offset.
+ sal_Int32 nXRefStreamId = m_aXRef.size();
+ XRefEntry aXRefStreamEntry;
+ aXRefStreamEntry.SetOffset(nXRefOffset);
+ aXRefStreamEntry.SetDirty(true);
+ m_aXRef[nXRefStreamId] = aXRefStreamEntry;
+
+ // Write stream data.
+ SvMemoryStream aXRefStream;
+ const size_t nOffsetLen = 3;
+ // 3 additional bytes: predictor, the first and the third field.
+ const size_t nLineLength = nOffsetLen + 3;
+ // This is the line as it appears before tweaking according to the predictor.
+ std::vector<unsigned char> aOrigLine(nLineLength);
+ // This is the previous line.
+ std::vector<unsigned char> aPrevLine(nLineLength);
+ // This is the line as written to the stream.
+ std::vector<unsigned char> aFilteredLine(nLineLength);
+ for (const auto& rXRef : m_aXRef)
+ {
+ const XRefEntry& rEntry = rXRef.second;
+
+ if (!rEntry.GetDirty())
+ continue;
+
+ // Predictor.
+ size_t nPos = 0;
+ // PNG prediction: up (on all rows).
+ aOrigLine[nPos++] = 2;
+
+ // First field.
+ unsigned char nType = 0;
+ switch (rEntry.GetType())
+ {
+ case XRefEntryType::FREE:
+ nType = 0;
+ break;
+ case XRefEntryType::NOT_COMPRESSED:
+ nType = 1;
+ break;
+ case XRefEntryType::COMPRESSED:
+ nType = 2;
+ break;
+ }
+ aOrigLine[nPos++] = nType;
+
+ // Second field.
+ for (size_t i = 0; i < nOffsetLen; ++i)
+ {
+ size_t nByte = nOffsetLen - i - 1;
+ // Fields requiring more than one byte are stored with the
+ // high-order byte first.
+ unsigned char nCh = (rEntry.GetOffset() & (0xff << (nByte * 8))) >> (nByte * 8);
+ aOrigLine[nPos++] = nCh;
+ }
+
+ // Third field.
+ aOrigLine[nPos++] = 0;
+
+ // Now apply the predictor.
+ aFilteredLine[0] = aOrigLine[0];
+ for (size_t i = 1; i < nLineLength; ++i)
+ {
+ // Count the delta vs the previous line.
+ aFilteredLine[i] = aOrigLine[i] - aPrevLine[i];
+ // Remember the new reference.
+ aPrevLine[i] = aOrigLine[i];
+ }
+
+ aXRefStream.WriteBytes(aFilteredLine.data(), aFilteredLine.size());
+ }
+
+ m_aEditBuffer.WriteUInt32AsString(nXRefStreamId);
+ m_aEditBuffer.WriteCharPtr(
+ " 0 obj\n<</DecodeParms<</Columns 5/Predictor 12>>/Filter/FlateDecode");
+
+ // ID.
+ auto pID = dynamic_cast<PDFArrayElement*>(m_pXRefStream->Lookup("ID"));
+ if (pID)
+ {
+ const std::vector<PDFElement*>& rElements = pID->GetElements();
+ m_aEditBuffer.WriteCharPtr("/ID [ <");
+ for (size_t i = 0; i < rElements.size(); ++i)
+ {
+ auto pIDString = dynamic_cast<PDFHexStringElement*>(rElements[i]);
+ if (!pIDString)
+ continue;
+
+ m_aEditBuffer.WriteOString(pIDString->GetValue());
+ if ((i + 1) < rElements.size())
+ m_aEditBuffer.WriteCharPtr("> <");
+ }
+ m_aEditBuffer.WriteCharPtr("> ] ");
+ }
+
+ // Index.
+ m_aEditBuffer.WriteCharPtr("/Index [ ");
+ for (const auto& rXRef : m_aXRef)
+ {
+ if (!rXRef.second.GetDirty())
+ continue;
+
+ m_aEditBuffer.WriteUInt32AsString(rXRef.first);
+ m_aEditBuffer.WriteCharPtr(" 1 ");
+ }
+ m_aEditBuffer.WriteCharPtr("] ");
+
+ // Info.
+ auto pInfo = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Info"));
+ if (pInfo)
+ {
+ m_aEditBuffer.WriteCharPtr("/Info ");
+ m_aEditBuffer.WriteUInt32AsString(pInfo->GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(pInfo->GetGenerationValue());
+ m_aEditBuffer.WriteCharPtr(" R ");
+ }
+
+ // Length.
+ m_aEditBuffer.WriteCharPtr("/Length ");
+ {
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ aXRefStream.Seek(0);
+ SvMemoryStream aStream;
+ aZCodec.Compress(aXRefStream, aStream);
+ aZCodec.EndCompression();
+ aXRefStream.Seek(0);
+ aXRefStream.SetStreamSize(0);
+ aStream.Seek(0);
+ aXRefStream.WriteStream(aStream);
+ }
+ m_aEditBuffer.WriteUInt32AsString(aXRefStream.GetSize());
+
+ if (!m_aStartXRefs.empty())
+ {
+ // Write location of the previous cross-reference section.
+ m_aEditBuffer.WriteCharPtr("/Prev ");
+ m_aEditBuffer.WriteUInt32AsString(m_aStartXRefs.back());
+ }
+
+ // Root.
+ m_aEditBuffer.WriteCharPtr("/Root ");
+ m_aEditBuffer.WriteUInt32AsString(pRoot->GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(pRoot->GetGenerationValue());
+ m_aEditBuffer.WriteCharPtr(" R ");
+
+ // Size.
+ m_aEditBuffer.WriteCharPtr("/Size ");
+ m_aEditBuffer.WriteUInt32AsString(m_aXRef.size());
+
+ m_aEditBuffer.WriteCharPtr("/Type/XRef/W[1 3 1]>>\nstream\n");
+ aXRefStream.Seek(0);
+ m_aEditBuffer.WriteStream(aXRefStream);
+ m_aEditBuffer.WriteCharPtr("\nendstream\nendobj\n\n");
+ }
+ else
+ {
+ // Write the xref table.
+ m_aEditBuffer.WriteCharPtr("xref\n");
+ for (const auto& rXRef : m_aXRef)
+ {
+ size_t nObject = rXRef.first;
+ size_t nOffset = rXRef.second.GetOffset();
+ if (!rXRef.second.GetDirty())
+ continue;
+
+ m_aEditBuffer.WriteUInt32AsString(nObject);
+ m_aEditBuffer.WriteCharPtr(" 1\n");
+ OStringBuffer aBuffer;
+ aBuffer.append(static_cast<sal_Int32>(nOffset));
+ while (aBuffer.getLength() < 10)
+ aBuffer.insert(0, "0");
+ if (nObject == 0)
+ aBuffer.append(" 65535 f \n");
+ else
+ aBuffer.append(" 00000 n \n");
+ m_aEditBuffer.WriteOString(aBuffer);
+ }
+
+ // Write the trailer.
+ m_aEditBuffer.WriteCharPtr("trailer\n<</Size ");
+ m_aEditBuffer.WriteUInt32AsString(m_aXRef.size());
+ m_aEditBuffer.WriteCharPtr("/Root ");
+ m_aEditBuffer.WriteUInt32AsString(pRoot->GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(pRoot->GetGenerationValue());
+ m_aEditBuffer.WriteCharPtr(" R\n");
+ auto pInfo = dynamic_cast<PDFReferenceElement*>(m_pTrailer->Lookup("Info"));
+ if (pInfo)
+ {
+ m_aEditBuffer.WriteCharPtr("/Info ");
+ m_aEditBuffer.WriteUInt32AsString(pInfo->GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(pInfo->GetGenerationValue());
+ m_aEditBuffer.WriteCharPtr(" R\n");
+ }
+ auto pID = dynamic_cast<PDFArrayElement*>(m_pTrailer->Lookup("ID"));
+ if (pID)
+ {
+ const std::vector<PDFElement*>& rElements = pID->GetElements();
+ m_aEditBuffer.WriteCharPtr("/ID [ <");
+ for (size_t i = 0; i < rElements.size(); ++i)
+ {
+ auto pIDString = dynamic_cast<PDFHexStringElement*>(rElements[i]);
+ if (!pIDString)
+ continue;
+
+ m_aEditBuffer.WriteOString(pIDString->GetValue());
+ if ((i + 1) < rElements.size())
+ m_aEditBuffer.WriteCharPtr(">\n<");
+ }
+ m_aEditBuffer.WriteCharPtr("> ]\n");
+ }
+
+ if (!m_aStartXRefs.empty())
+ {
+ // Write location of the previous cross-reference section.
+ m_aEditBuffer.WriteCharPtr("/Prev ");
+ m_aEditBuffer.WriteUInt32AsString(m_aStartXRefs.back());
+ }
+
+ m_aEditBuffer.WriteCharPtr(">>\n");
+ }
+}
+
+bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificate,
+ const OUString& rDescription, bool bAdES)
+{
+ m_aEditBuffer.Seek(STREAM_SEEK_TO_END);
+ m_aEditBuffer.WriteCharPtr("\n");
+
+ sal_uInt64 nSignatureLastByteRangeOffset = 0;
+ sal_Int64 nSignatureContentOffset = 0;
+ sal_Int32 nSignatureId = WriteSignatureObject(
+ rDescription, bAdES, nSignatureLastByteRangeOffset, nSignatureContentOffset);
+
+ tools::Rectangle aSignatureRectangle;
+ sal_Int32 nAppearanceId = WriteAppearanceObject(aSignatureRectangle);
+
+ std::vector<PDFObjectElement*> aPages = GetPages();
+ if (aPages.empty())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: found no pages");
+ return false;
+ }
+
+ size_t nPage = 0;
+ if (m_nSignaturePage < aPages.size())
+ {
+ nPage = m_nSignaturePage;
+ }
+ if (!aPages[nPage])
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: failed to find page #" << nPage);
+ return false;
+ }
+
+ PDFObjectElement& rPage = *aPages[nPage];
+ sal_Int32 nAnnotId = WriteAnnotObject(rPage, nSignatureId, nAppearanceId, aSignatureRectangle);
+
+ if (!WritePageObject(rPage, nAnnotId))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: failed to write the updated Page object");
+ return false;
+ }
+
+ PDFReferenceElement* pRoot = nullptr;
+ if (!WriteCatalogObject(nAnnotId, pRoot))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: failed to write the updated Catalog object");
+ return false;
+ }
+
+ sal_uInt64 nXRefOffset = m_aEditBuffer.Tell();
+ WriteXRef(nXRefOffset, pRoot);
+
+ // Write startxref.
+ m_aEditBuffer.WriteCharPtr("startxref\n");
+ m_aEditBuffer.WriteUInt32AsString(nXRefOffset);
+ m_aEditBuffer.WriteCharPtr("\n%%EOF\n");
+
+ // Finalize the signature, now that we know the total file size.
+ // Calculate the length of the last byte range.
+ sal_uInt64 nFileEnd = m_aEditBuffer.Tell();
+ sal_Int64 nLastByteRangeLength
+ = nFileEnd - (nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
+ // Write the length to the buffer.
+ m_aEditBuffer.Seek(nSignatureLastByteRangeOffset);
+ OString aByteRangeBuffer = OString::number(nLastByteRangeLength) + " ]";
+ m_aEditBuffer.WriteOString(aByteRangeBuffer);
+
+ // Create the PKCS#7 object.
+ css::uno::Sequence<sal_Int8> aDerEncoded = xCertificate->getEncoded();
+ if (!aDerEncoded.hasElements())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: empty certificate");
+ return false;
+ }
+
+ m_aEditBuffer.Seek(0);
+ sal_uInt64 nBufferSize1 = nSignatureContentOffset - 1;
+ std::unique_ptr<char[]> aBuffer1(new char[nBufferSize1]);
+ m_aEditBuffer.ReadBytes(aBuffer1.get(), nBufferSize1);
+
+ m_aEditBuffer.Seek(nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
+ sal_uInt64 nBufferSize2 = nLastByteRangeLength;
+ std::unique_ptr<char[]> aBuffer2(new char[nBufferSize2]);
+ m_aEditBuffer.ReadBytes(aBuffer2.get(), nBufferSize2);
+
+ OStringBuffer aCMSHexBuffer;
+ svl::crypto::Signing aSigning(xCertificate);
+ aSigning.AddDataRange(aBuffer1.get(), nBufferSize1);
+ aSigning.AddDataRange(aBuffer2.get(), nBufferSize2);
+ if (!aSigning.Sign(aCMSHexBuffer))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Sign: PDFWriter::Sign() failed");
+ return false;
+ }
+
+ assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
+
+ m_aEditBuffer.Seek(nSignatureContentOffset);
+ m_aEditBuffer.WriteOString(aCMSHexBuffer);
+
+ return true;
+}
+
+bool PDFDocument::Write(SvStream& rStream)
+{
+ m_aEditBuffer.Seek(0);
+ rStream.WriteStream(m_aEditBuffer);
+ return rStream.good();
+}
+
+bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode,
+ std::vector<std::unique_ptr<PDFElement>>& rElements,
+ PDFObjectElement* pObjectElement)
+{
+ // Last seen object token.
+ PDFObjectElement* pObject = pObjectElement;
+ PDFNameElement* pObjectKey = nullptr;
+ PDFObjectElement* pObjectStream = nullptr;
+ bool bInXRef = false;
+ // The next number will be an xref offset.
+ bool bInStartXRef = false;
+ // Dictionary depth, so we know when we're outside any dictionaries.
+ int nDepth = 0;
+ // Last seen array token that's outside any dictionaries.
+ PDFArrayElement* pArray = nullptr;
+ // If we're inside an obj/endobj pair.
+ bool bInObject = false;
+
+ while (true)
+ {
+ char ch;
+ rStream.ReadChar(ch);
+ if (rStream.eof())
+ break;
+
+ switch (ch)
+ {
+ case '%':
+ {
+ auto pComment = new PDFCommentElement(*this);
+ rElements.push_back(std::unique_ptr<PDFElement>(pComment));
+ rStream.SeekRel(-1);
+ if (!rElements.back()->Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Tokenize: PDFCommentElement::Read() failed");
+ return false;
+ }
+ if (eMode == TokenizeMode::EOF_TOKEN && !m_aEOFs.empty()
+ && m_aEOFs.back() == rStream.Tell())
+ {
+ // Found EOF and partial parsing requested, we're done.
+ return true;
+ }
+ break;
+ }
+ case '<':
+ {
+ // Dictionary or hex string.
+ rStream.ReadChar(ch);
+ rStream.SeekRel(-2);
+ if (ch == '<')
+ {
+ rElements.push_back(std::unique_ptr<PDFElement>(new PDFDictionaryElement()));
+ ++nDepth;
+ }
+ else
+ rElements.push_back(std::unique_ptr<PDFElement>(new PDFHexStringElement));
+ if (!rElements.back()->Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Tokenize: PDFDictionaryElement::Read() failed");
+ return false;
+ }
+ break;
+ }
+ case '>':
+ {
+ rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndDictionaryElement()));
+ --nDepth;
+ rStream.SeekRel(-1);
+ if (!rElements.back()->Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Tokenize: PDFEndDictionaryElement::Read() failed");
+ return false;
+ }
+ break;
+ }
+ case '[':
+ {
+ auto pArr = new PDFArrayElement(pObject);
+ rElements.push_back(std::unique_ptr<PDFElement>(pArr));
+ if (nDepth == 0)
+ {
+ // The array is attached directly, inform the object.
+ pArray = pArr;
+ if (pObject)
+ {
+ pObject->SetArray(pArray);
+ pObject->SetArrayOffset(rStream.Tell());
+ }
+ }
+ ++nDepth;
+ rStream.SeekRel(-1);
+ if (!rElements.back()->Read(rStream))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Tokenize: PDFArrayElement::Read() failed");
+ return false;
+ }
+ break;
+ }
+ case ']':
+ {
+ rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndArrayElement()));
+ --nDepth;
+ rStream.SeekRel(-1);
+ if (nDepth == 0)
+ {
+ if (pObject)
+ {
+ pObject->SetArrayLength(rStream.Tell() - pObject->GetArrayOffset());
+ }
+ }
+ if (!rElements.back()->Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Tokenize: PDFEndArrayElement::Read() failed");
+ return false;
+ }
+ break;
+ }
+ case '/':
+ {
+ auto pNameElement = new PDFNameElement();
+ rElements.push_back(std::unique_ptr<PDFElement>(pNameElement));
+ rStream.SeekRel(-1);
+ if (!pNameElement->Read(rStream))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Tokenize: PDFNameElement::Read() failed");
+ return false;
+ }
+
+ if (pObject && pObjectKey && pObjectKey->GetValue() == "Type"
+ && pNameElement->GetValue() == "ObjStm")
+ pObjectStream = pObject;
+ else
+ pObjectKey = pNameElement;
+ break;
+ }
+ case '(':
+ {
+ rElements.push_back(std::unique_ptr<PDFElement>(new PDFLiteralStringElement));
+ rStream.SeekRel(-1);
+ if (!rElements.back()->Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Tokenize: PDFLiteralStringElement::Read() failed");
+ return false;
+ }
+ break;
+ }
+ default:
+ {
+ if (rtl::isAsciiDigit(static_cast<unsigned char>(ch)) || ch == '-' || ch == '+'
+ || ch == '.')
+ {
+ // Numbering object: an integer or a real.
+ auto pNumberElement = new PDFNumberElement();
+ rElements.push_back(std::unique_ptr<PDFElement>(pNumberElement));
+ rStream.SeekRel(-1);
+ if (!pNumberElement->Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Tokenize: PDFNumberElement::Read() failed");
+ return false;
+ }
+ if (bInStartXRef)
+ {
+ bInStartXRef = false;
+ m_aStartXRefs.push_back(pNumberElement->GetValue());
+
+ auto it = m_aOffsetObjects.find(pNumberElement->GetValue());
+ if (it != m_aOffsetObjects.end())
+ m_pXRefStream = it->second;
+ }
+ else if (bInObject && !nDepth && pObject)
+ // Number element inside an object, but outside a
+ // dictionary / array: remember it.
+ pObject->SetNumberElement(pNumberElement);
+ }
+ else if (rtl::isAsciiAlpha(static_cast<unsigned char>(ch)))
+ {
+ // Possible keyword, like "obj".
+ rStream.SeekRel(-1);
+ OString aKeyword = ReadKeyword(rStream);
+
+ bool bObj = aKeyword == "obj";
+ if (bObj || aKeyword == "R")
+ {
+ size_t nElements = rElements.size();
+ if (nElements < 2)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Tokenize: expected at least two "
+ "tokens before 'obj' or 'R' keyword");
+ return false;
+ }
+
+ auto pObjectNumber
+ = dynamic_cast<PDFNumberElement*>(rElements[nElements - 2].get());
+ auto pGenerationNumber
+ = dynamic_cast<PDFNumberElement*>(rElements[nElements - 1].get());
+ if (!pObjectNumber || !pGenerationNumber)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Tokenize: missing object or "
+ "generation number before 'obj' or 'R' keyword");
+ return false;
+ }
+
+ if (bObj)
+ {
+ pObject = new PDFObjectElement(*this, pObjectNumber->GetValue(),
+ pGenerationNumber->GetValue());
+ rElements.push_back(std::unique_ptr<PDFElement>(pObject));
+ m_aOffsetObjects[pObjectNumber->GetLocation()] = pObject;
+ m_aIDObjects[pObjectNumber->GetValue()] = pObject;
+ bInObject = true;
+ }
+ else
+ {
+ auto pReference = new PDFReferenceElement(*this, *pObjectNumber,
+ *pGenerationNumber);
+ rElements.push_back(std::unique_ptr<PDFElement>(pReference));
+ if (bInObject && nDepth > 0 && pObject)
+ // Inform the object about a new in-dictionary reference.
+ pObject->AddDictionaryReference(pReference);
+ }
+ if (!rElements.back()->Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Tokenize: PDFElement::Read() failed");
+ return false;
+ }
+ }
+ else if (aKeyword == "stream")
+ {
+ // Look up the length of the stream from the parent object's dictionary.
+ size_t nLength = 0;
+ for (size_t nElement = 0; nElement < rElements.size(); ++nElement)
+ {
+ // Iterate in reverse order.
+ size_t nIndex = rElements.size() - nElement - 1;
+ PDFElement* pElement = rElements[nIndex].get();
+ auto pObj = dynamic_cast<PDFObjectElement*>(pElement);
+ if (!pObj)
+ continue;
+
+ PDFElement* pLookup = pObj->Lookup("Length");
+ auto pReference = dynamic_cast<PDFReferenceElement*>(pLookup);
+ if (pReference)
+ {
+ // Length is provided as a reference.
+ nLength = pReference->LookupNumber(rStream);
+ break;
+ }
+
+ auto pNumber = dynamic_cast<PDFNumberElement*>(pLookup);
+ if (pNumber)
+ {
+ // Length is provided directly.
+ nLength = pNumber->GetValue();
+ break;
+ }
+
+ SAL_WARN(
+ "vcl.filter",
+ "PDFDocument::Tokenize: found no Length key for stream keyword");
+ return false;
+ }
+
+ PDFDocument::SkipLineBreaks(rStream);
+ auto pStreamElement = new PDFStreamElement(nLength);
+ if (pObject)
+ pObject->SetStream(pStreamElement);
+ rElements.push_back(std::unique_ptr<PDFElement>(pStreamElement));
+ if (!rElements.back()->Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Tokenize: PDFStreamElement::Read() failed");
+ return false;
+ }
+ }
+ else if (aKeyword == "endstream")
+ {
+ rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndStreamElement));
+ if (!rElements.back()->Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Tokenize: PDFEndStreamElement::Read() failed");
+ return false;
+ }
+ }
+ else if (aKeyword == "endobj")
+ {
+ rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndObjectElement));
+ if (!rElements.back()->Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Tokenize: PDFEndObjectElement::Read() failed");
+ return false;
+ }
+ if (eMode == TokenizeMode::END_OF_OBJECT)
+ {
+ // Found endobj and only object parsing was requested, we're done.
+ return true;
+ }
+
+ if (pObjectStream)
+ {
+ // We're at the end of an object stream, parse the stored objects.
+ pObjectStream->ParseStoredObjects();
+ pObjectStream = nullptr;
+ pObjectKey = nullptr;
+ }
+ bInObject = false;
+ }
+ else if (aKeyword == "true" || aKeyword == "false")
+ rElements.push_back(std::unique_ptr<PDFElement>(
+ new PDFBooleanElement(aKeyword.toBoolean())));
+ else if (aKeyword == "null")
+ rElements.push_back(std::unique_ptr<PDFElement>(new PDFNullElement));
+ else if (aKeyword == "xref")
+ // Allow 'f' and 'n' keywords.
+ bInXRef = true;
+ else if (bInXRef && (aKeyword == "f" || aKeyword == "n"))
+ {
+ }
+ else if (aKeyword == "trailer")
+ {
+ auto pTrailer = new PDFTrailerElement(*this);
+
+ // Make it possible to find this trailer later by offset.
+ pTrailer->Read(rStream);
+ m_aOffsetTrailers[pTrailer->GetLocation()] = pTrailer;
+
+ // When reading till the first EOF token only, remember
+ // just the first trailer token.
+ if (eMode != TokenizeMode::EOF_TOKEN || !m_pTrailer)
+ m_pTrailer = pTrailer;
+ rElements.push_back(std::unique_ptr<PDFElement>(pTrailer));
+ }
+ else if (aKeyword == "startxref")
+ {
+ bInStartXRef = true;
+ }
+ else
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Tokenize: unexpected '"
+ << aKeyword << "' keyword at byte position "
+ << rStream.Tell());
+ return false;
+ }
+ }
+ else
+ {
+ auto uChar = static_cast<unsigned char>(ch);
+ // Be more lenient and allow unexpected null char
+ if (!rtl::isAsciiWhiteSpace(uChar) && uChar != 0)
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::Tokenize: unexpected character with code "
+ << sal_Int32(ch) << " at byte position " << rStream.Tell());
+ return false;
+ }
+ SAL_WARN_IF(uChar == 0, "vcl.filter",
+ "PDFDocument::Tokenize: unexpected null character at "
+ << rStream.Tell() << " - ignoring");
+ }
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+void PDFDocument::SetIDObject(size_t nID, PDFObjectElement* pObject)
+{
+ m_aIDObjects[nID] = pObject;
+}
+
+bool PDFDocument::ReadWithPossibleFixup(SvStream& rStream)
+{
+ if (Read(rStream))
+ return true;
+
+ // Read failed, try a roundtrip through pdfium and then retry.
+ rStream.Seek(0);
+ SvMemoryStream aStandardizedStream;
+ vcl::pdf::convertToHighestSupported(rStream, aStandardizedStream);
+ return Read(aStandardizedStream);
+}
+
+bool PDFDocument::Read(SvStream& rStream)
+{
+ // Check file magic.
+ std::vector<sal_Int8> aHeader(5);
+ rStream.Seek(0);
+ rStream.ReadBytes(aHeader.data(), aHeader.size());
+ if (aHeader[0] != '%' || aHeader[1] != 'P' || aHeader[2] != 'D' || aHeader[3] != 'F'
+ || aHeader[4] != '-')
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Read: header mismatch");
+ return false;
+ }
+
+ // Allow later editing of the contents in-memory.
+ rStream.Seek(0);
+ m_aEditBuffer.WriteStream(rStream);
+
+ // Look up the offset of the xref table.
+ size_t nStartXRef = FindStartXRef(rStream);
+ SAL_INFO("vcl.filter", "PDFDocument::Read: nStartXRef is " << nStartXRef);
+ if (nStartXRef == 0)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Read: found no xref start offset");
+ return false;
+ }
+ while (true)
+ {
+ rStream.Seek(nStartXRef);
+ OString aKeyword = ReadKeyword(rStream);
+ if (aKeyword.isEmpty())
+ ReadXRefStream(rStream);
+
+ else
+ {
+ if (aKeyword != "xref")
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Read: xref is not the first keyword");
+ return false;
+ }
+ ReadXRef(rStream);
+ if (!Tokenize(rStream, TokenizeMode::EOF_TOKEN, m_aElements, nullptr))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::Read: failed to tokenizer trailer after xref");
+ return false;
+ }
+ }
+
+ PDFNumberElement* pPrev = nullptr;
+ if (m_pTrailer)
+ {
+ pPrev = dynamic_cast<PDFNumberElement*>(m_pTrailer->Lookup("Prev"));
+
+ // Remember the offset of this trailer in the correct order. It's
+ // possible that newer trailers don't have a larger offset.
+ m_aTrailerOffsets.push_back(m_pTrailer->GetLocation());
+ }
+ else if (m_pXRefStream)
+ pPrev = dynamic_cast<PDFNumberElement*>(m_pXRefStream->Lookup("Prev"));
+ if (pPrev)
+ nStartXRef = pPrev->GetValue();
+
+ // Reset state, except the edit buffer.
+ m_aElements.clear();
+ m_aOffsetObjects.clear();
+ m_aIDObjects.clear();
+ m_aStartXRefs.clear();
+ m_aEOFs.clear();
+ m_pTrailer = nullptr;
+ m_pXRefStream = nullptr;
+ if (!pPrev)
+ break;
+ }
+
+ // Then we can tokenize the stream.
+ rStream.Seek(0);
+ return Tokenize(rStream, TokenizeMode::END_OF_STREAM, m_aElements, nullptr);
+}
+
+OString PDFDocument::ReadKeyword(SvStream& rStream)
+{
+ OStringBuffer aBuf;
+ char ch;
+ rStream.ReadChar(ch);
+ if (rStream.eof())
+ return {};
+ while (rtl::isAsciiAlpha(static_cast<unsigned char>(ch)))
+ {
+ aBuf.append(ch);
+ rStream.ReadChar(ch);
+ if (rStream.eof())
+ return aBuf.toString();
+ }
+ rStream.SeekRel(-1);
+ return aBuf.toString();
+}
+
+size_t PDFDocument::FindStartXRef(SvStream& rStream)
+{
+ // Find the "startxref" token, somewhere near the end of the document.
+ std::vector<char> aBuf(1024);
+ rStream.Seek(STREAM_SEEK_TO_END);
+ if (rStream.Tell() > aBuf.size())
+ rStream.SeekRel(static_cast<sal_Int64>(-1) * aBuf.size());
+ else
+ // The document is really short, then just read it from the start.
+ rStream.Seek(0);
+ size_t nBeforePeek = rStream.Tell();
+ size_t nSize = rStream.ReadBytes(aBuf.data(), aBuf.size());
+ rStream.Seek(nBeforePeek);
+ if (nSize != aBuf.size())
+ aBuf.resize(nSize);
+ OString aPrefix("startxref");
+ // Find the last startxref at the end of the document.
+ auto itLastValid = aBuf.end();
+ auto it = aBuf.begin();
+ while (true)
+ {
+ it = std::search(it, aBuf.end(), aPrefix.getStr(), aPrefix.getStr() + aPrefix.getLength());
+ if (it == aBuf.end())
+ break;
+
+ itLastValid = it;
+ ++it;
+ }
+ if (itLastValid == aBuf.end())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::FindStartXRef: found no startxref");
+ return 0;
+ }
+
+ rStream.SeekRel(itLastValid - aBuf.begin() + aPrefix.getLength());
+ if (rStream.eof())
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::FindStartXRef: unexpected end of stream after startxref");
+ return 0;
+ }
+
+ PDFDocument::SkipWhitespace(rStream);
+ PDFNumberElement aNumber;
+ if (!aNumber.Read(rStream))
+ return 0;
+ return aNumber.GetValue();
+}
+
+void PDFDocument::ReadXRefStream(SvStream& rStream)
+{
+ // Look up the stream length in the object dictionary.
+ if (!Tokenize(rStream, TokenizeMode::END_OF_OBJECT, m_aElements, nullptr))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: failed to read object");
+ return;
+ }
+
+ if (m_aElements.empty())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: no tokens found");
+ return;
+ }
+
+ PDFObjectElement* pObject = nullptr;
+ for (const auto& pElement : m_aElements)
+ {
+ if (auto pObj = dynamic_cast<PDFObjectElement*>(pElement.get()))
+ {
+ pObject = pObj;
+ break;
+ }
+ }
+ if (!pObject)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: no object token found");
+ return;
+ }
+
+ // So that the Prev key can be looked up later.
+ m_pXRefStream = pObject;
+
+ PDFElement* pLookup = pObject->Lookup("Length");
+ auto pNumber = dynamic_cast<PDFNumberElement*>(pLookup);
+ if (!pNumber)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: stream length is not provided");
+ return;
+ }
+ sal_uInt64 nLength = pNumber->GetValue();
+
+ // Look up the stream offset.
+ PDFStreamElement* pStream = nullptr;
+ for (const auto& pElement : m_aElements)
+ {
+ if (auto pS = dynamic_cast<PDFStreamElement*>(pElement.get()))
+ {
+ pStream = pS;
+ break;
+ }
+ }
+ if (!pStream)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: no stream token found");
+ return;
+ }
+
+ // Read and decompress it.
+ rStream.Seek(pStream->GetOffset());
+ std::vector<char> aBuf(nLength);
+ rStream.ReadBytes(aBuf.data(), aBuf.size());
+
+ auto pFilter = dynamic_cast<PDFNameElement*>(pObject->Lookup("Filter"));
+ if (!pFilter)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: no Filter found");
+ return;
+ }
+
+ if (pFilter->GetValue() != "FlateDecode")
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::ReadXRefStream: unexpected filter: " << pFilter->GetValue());
+ return;
+ }
+
+ int nColumns = 1;
+ int nPredictor = 1;
+ if (auto pDecodeParams = dynamic_cast<PDFDictionaryElement*>(pObject->Lookup("DecodeParms")))
+ {
+ const std::map<OString, PDFElement*>& rItems = pDecodeParams->GetItems();
+ auto it = rItems.find("Columns");
+ if (it != rItems.end())
+ if (auto pColumns = dynamic_cast<PDFNumberElement*>(it->second))
+ nColumns = pColumns->GetValue();
+ it = rItems.find("Predictor");
+ if (it != rItems.end())
+ if (auto pPredictor = dynamic_cast<PDFNumberElement*>(it->second))
+ nPredictor = pPredictor->GetValue();
+ }
+
+ SvMemoryStream aSource(aBuf.data(), aBuf.size(), StreamMode::READ);
+ SvMemoryStream aStream;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ aZCodec.Decompress(aSource, aStream);
+ if (!aZCodec.EndCompression())
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: decompression failed");
+ return;
+ }
+
+ // Look up the first and the last entry we need to read.
+ auto pIndex = dynamic_cast<PDFArrayElement*>(pObject->Lookup("Index"));
+ std::vector<size_t> aFirstObjects;
+ std::vector<size_t> aNumberOfObjects;
+ if (!pIndex)
+ {
+ auto pSize = dynamic_cast<PDFNumberElement*>(pObject->Lookup("Size"));
+ if (pSize)
+ {
+ aFirstObjects.push_back(0);
+ aNumberOfObjects.push_back(pSize->GetValue());
+ }
+ else
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: Index and Size not found");
+ return;
+ }
+ }
+ else
+ {
+ const std::vector<PDFElement*>& rIndexElements = pIndex->GetElements();
+ size_t nFirstObject = 0;
+ for (size_t i = 0; i < rIndexElements.size(); ++i)
+ {
+ if (i % 2 == 0)
+ {
+ auto pFirstObject = dynamic_cast<PDFNumberElement*>(rIndexElements[i]);
+ if (!pFirstObject)
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::ReadXRefStream: Index has no first object");
+ return;
+ }
+ nFirstObject = pFirstObject->GetValue();
+ continue;
+ }
+
+ auto pNumberOfObjects = dynamic_cast<PDFNumberElement*>(rIndexElements[i]);
+ if (!pNumberOfObjects)
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::ReadXRefStream: Index has no number of objects");
+ return;
+ }
+ aFirstObjects.push_back(nFirstObject);
+ aNumberOfObjects.push_back(pNumberOfObjects->GetValue());
+ }
+ }
+
+ // Look up the format of a single entry.
+ const int nWSize = 3;
+ auto pW = dynamic_cast<PDFArrayElement*>(pObject->Lookup("W"));
+ if (!pW || pW->GetElements().size() < nWSize)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: W not found or has < 3 elements");
+ return;
+ }
+ int aW[nWSize];
+ // First character is the (kind of) repeated predictor.
+ int nLineLength = 1;
+ for (size_t i = 0; i < nWSize; ++i)
+ {
+ auto pI = dynamic_cast<PDFNumberElement*>(pW->GetElements()[i]);
+ if (!pI)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: W contains non-number");
+ return;
+ }
+ aW[i] = pI->GetValue();
+ nLineLength += aW[i];
+ }
+
+ if (nPredictor > 1 && nLineLength - 1 != nColumns)
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDocument::ReadXRefStream: /DecodeParms/Columns is inconsistent with /W");
+ return;
+ }
+
+ aStream.Seek(0);
+ for (size_t nSubSection = 0; nSubSection < aFirstObjects.size(); ++nSubSection)
+ {
+ size_t nFirstObject = aFirstObjects[nSubSection];
+ size_t nNumberOfObjects = aNumberOfObjects[nSubSection];
+
+ // This is the line as read from the stream.
+ std::vector<unsigned char> aOrigLine(nLineLength);
+ // This is the line as it appears after tweaking according to nPredictor.
+ std::vector<unsigned char> aFilteredLine(nLineLength);
+ for (size_t nEntry = 0; nEntry < nNumberOfObjects; ++nEntry)
+ {
+ size_t nIndex = nFirstObject + nEntry;
+
+ aStream.ReadBytes(aOrigLine.data(), aOrigLine.size());
+ if (nPredictor > 1 && aOrigLine[0] + 10 != nPredictor)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: in-stream predictor is "
+ "inconsistent with /DecodeParms/Predictor for object #"
+ << nIndex);
+ return;
+ }
+
+ for (int i = 0; i < nLineLength; ++i)
+ {
+ switch (nPredictor)
+ {
+ case 1:
+ // No prediction.
+ break;
+ case 12:
+ // PNG prediction: up (on all rows).
+ aFilteredLine[i] = aFilteredLine[i] + aOrigLine[i];
+ break;
+ default:
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: unexpected predictor: "
+ << nPredictor);
+ return;
+ }
+ }
+
+ // First character is already handled above.
+ int nPos = 1;
+ size_t nType = 0;
+ // Start of the current field in the stream data.
+ int nOffset = nPos;
+ for (; nPos < nOffset + aW[0]; ++nPos)
+ {
+ unsigned char nCh = aFilteredLine[nPos];
+ nType = (nType << 8) + nCh;
+ }
+
+ // Start of the object in the file stream.
+ size_t nStreamOffset = 0;
+ nOffset = nPos;
+ for (; nPos < nOffset + aW[1]; ++nPos)
+ {
+ unsigned char nCh = aFilteredLine[nPos];
+ nStreamOffset = (nStreamOffset << 8) + nCh;
+ }
+
+ // Generation number of the object.
+ size_t nGenerationNumber = 0;
+ nOffset = nPos;
+ for (; nPos < nOffset + aW[2]; ++nPos)
+ {
+ unsigned char nCh = aFilteredLine[nPos];
+ nGenerationNumber = (nGenerationNumber << 8) + nCh;
+ }
+
+ // Ignore invalid nType.
+ if (nType <= 2)
+ {
+ if (m_aXRef.find(nIndex) == m_aXRef.end())
+ {
+ XRefEntry aEntry;
+ switch (nType)
+ {
+ case 0:
+ aEntry.SetType(XRefEntryType::FREE);
+ break;
+ case 1:
+ aEntry.SetType(XRefEntryType::NOT_COMPRESSED);
+ break;
+ case 2:
+ aEntry.SetType(XRefEntryType::COMPRESSED);
+ break;
+ }
+ aEntry.SetOffset(nStreamOffset);
+ m_aXRef[nIndex] = aEntry;
+ }
+ }
+ }
+ }
+}
+
+void PDFDocument::ReadXRef(SvStream& rStream)
+{
+ PDFDocument::SkipWhitespace(rStream);
+
+ while (true)
+ {
+ PDFNumberElement aFirstObject;
+ if (!aFirstObject.Read(rStream))
+ {
+ // Next token is not a number, it'll be the trailer.
+ return;
+ }
+
+ if (aFirstObject.GetValue() < 0)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: expected first object number >= 0");
+ return;
+ }
+
+ PDFDocument::SkipWhitespace(rStream);
+ PDFNumberElement aNumberOfEntries;
+ if (!aNumberOfEntries.Read(rStream))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: failed to read number of entries");
+ return;
+ }
+
+ if (aNumberOfEntries.GetValue() < 0)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: expected zero or more entries");
+ return;
+ }
+
+ size_t nSize = aNumberOfEntries.GetValue();
+ for (size_t nEntry = 0; nEntry < nSize; ++nEntry)
+ {
+ size_t nIndex = aFirstObject.GetValue() + nEntry;
+ PDFDocument::SkipWhitespace(rStream);
+ PDFNumberElement aOffset;
+ if (!aOffset.Read(rStream))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: failed to read offset");
+ return;
+ }
+
+ PDFDocument::SkipWhitespace(rStream);
+ PDFNumberElement aGenerationNumber;
+ if (!aGenerationNumber.Read(rStream))
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: failed to read generation number");
+ return;
+ }
+
+ PDFDocument::SkipWhitespace(rStream);
+ OString aKeyword = ReadKeyword(rStream);
+ if (aKeyword != "f" && aKeyword != "n")
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: unexpected keyword");
+ return;
+ }
+ // xrefs are read in reverse order, so never update an existing
+ // offset with an older one.
+ if (m_aXRef.find(nIndex) == m_aXRef.end())
+ {
+ XRefEntry aEntry;
+ aEntry.SetOffset(aOffset.GetValue());
+ // Initially only the first entry is dirty.
+ if (nIndex == 0)
+ aEntry.SetDirty(true);
+ m_aXRef[nIndex] = aEntry;
+ }
+ PDFDocument::SkipWhitespace(rStream);
+ }
+ }
+}
+
+void PDFDocument::SkipWhitespace(SvStream& rStream)
+{
+ char ch = 0;
+
+ while (true)
+ {
+ rStream.ReadChar(ch);
+ if (rStream.eof())
+ break;
+
+ if (!rtl::isAsciiWhiteSpace(static_cast<unsigned char>(ch)))
+ {
+ rStream.SeekRel(-1);
+ return;
+ }
+ }
+}
+
+void PDFDocument::SkipLineBreaks(SvStream& rStream)
+{
+ char ch = 0;
+
+ while (true)
+ {
+ rStream.ReadChar(ch);
+ if (rStream.eof())
+ break;
+
+ if (ch != '\n' && ch != '\r')
+ {
+ rStream.SeekRel(-1);
+ return;
+ }
+ }
+}
+
+size_t PDFDocument::GetObjectOffset(size_t nIndex) const
+{
+ auto it = m_aXRef.find(nIndex);
+ if (it == m_aXRef.end() || it->second.GetType() == XRefEntryType::COMPRESSED)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::GetObjectOffset: wanted to look up index #"
+ << nIndex << ", but failed");
+ return 0;
+ }
+
+ return it->second.GetOffset();
+}
+
+const std::vector<std::unique_ptr<PDFElement>>& PDFDocument::GetElements() const
+{
+ return m_aElements;
+}
+
+/// Visits the page tree recursively, looking for page objects.
+static void visitPages(PDFObjectElement* pPages, std::vector<PDFObjectElement*>& rRet)
+{
+ auto pKids = dynamic_cast<PDFArrayElement*>(pPages->Lookup("Kids"));
+ if (!pKids)
+ {
+ SAL_WARN("vcl.filter", "visitPages: pages has no kids");
+ return;
+ }
+
+ pPages->setVisiting(true);
+
+ for (const auto& pKid : pKids->GetElements())
+ {
+ auto pReference = dynamic_cast<PDFReferenceElement*>(pKid);
+ if (!pReference)
+ continue;
+
+ PDFObjectElement* pKidObject = pReference->LookupObject();
+ if (!pKidObject)
+ continue;
+
+ // detect if visiting reenters itself
+ if (pKidObject->alreadyVisiting())
+ {
+ SAL_WARN("vcl.filter", "visitPages: loop in hierarchy");
+ continue;
+ }
+
+ auto pName = dynamic_cast<PDFNameElement*>(pKidObject->Lookup("Type"));
+ if (pName && pName->GetValue() == "Pages")
+ // Pages inside pages: recurse.
+ visitPages(pKidObject, rRet);
+ else
+ // Found an actual page.
+ rRet.push_back(pKidObject);
+ }
+
+ pPages->setVisiting(false);
+}
+
+PDFObjectElement* PDFDocument::GetCatalog()
+{
+ PDFReferenceElement* pRoot = nullptr;
+
+ PDFTrailerElement* pTrailer = nullptr;
+ if (!m_aTrailerOffsets.empty())
+ {
+ // Get access to the latest trailer, and work with the keys of that
+ // one.
+ auto it = m_aOffsetTrailers.find(m_aTrailerOffsets[0]);
+ if (it != m_aOffsetTrailers.end())
+ pTrailer = it->second;
+ }
+
+ if (pTrailer)
+ pRoot = dynamic_cast<PDFReferenceElement*>(pTrailer->Lookup("Root"));
+ else if (m_pXRefStream)
+ pRoot = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Root"));
+
+ if (!pRoot)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::GetCatalog: trailer has no Root key");
+ return nullptr;
+ }
+
+ return pRoot->LookupObject();
+}
+
+std::vector<PDFObjectElement*> PDFDocument::GetPages()
+{
+ std::vector<PDFObjectElement*> aRet;
+
+ PDFObjectElement* pCatalog = GetCatalog();
+ if (!pCatalog)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::GetPages: trailer has no catalog");
+ return aRet;
+ }
+
+ PDFObjectElement* pPages = pCatalog->LookupObject("Pages");
+ if (!pPages)
+ {
+ SAL_WARN("vcl.filter", "PDFDocument::GetPages: catalog (obj " << pCatalog->GetObjectValue()
+ << ") has no pages");
+ return aRet;
+ }
+
+ visitPages(pPages, aRet);
+
+ return aRet;
+}
+
+void PDFDocument::PushBackEOF(size_t nOffset) { m_aEOFs.push_back(nOffset); }
+
+std::vector<PDFObjectElement*> PDFDocument::GetSignatureWidgets()
+{
+ std::vector<PDFObjectElement*> aRet;
+
+ std::vector<PDFObjectElement*> aPages = GetPages();
+
+ for (const auto& pPage : aPages)
+ {
+ if (!pPage)
+ continue;
+
+ PDFElement* pAnnotsElement = pPage->Lookup("Annots");
+ auto pAnnots = dynamic_cast<PDFArrayElement*>(pAnnotsElement);
+ if (!pAnnots)
+ {
+ // Annots is not an array, see if it's a reference to an object
+ // with a direct array.
+ auto pAnnotsRef = dynamic_cast<PDFReferenceElement*>(pAnnotsElement);
+ if (pAnnotsRef)
+ {
+ if (PDFObjectElement* pAnnotsObject = pAnnotsRef->LookupObject())
+ {
+ pAnnots = pAnnotsObject->GetArray();
+ }
+ }
+ }
+
+ if (!pAnnots)
+ continue;
+
+ for (const auto& pAnnot : pAnnots->GetElements())
+ {
+ auto pReference = dynamic_cast<PDFReferenceElement*>(pAnnot);
+ if (!pReference)
+ continue;
+
+ PDFObjectElement* pAnnotObject = pReference->LookupObject();
+ if (!pAnnotObject)
+ continue;
+
+ auto pFT = dynamic_cast<PDFNameElement*>(pAnnotObject->Lookup("FT"));
+ if (!pFT || pFT->GetValue() != "Sig")
+ continue;
+
+ aRet.push_back(pAnnotObject);
+ }
+ }
+
+ return aRet;
+}
+
+std::vector<unsigned char> PDFDocument::DecodeHexString(PDFHexStringElement const* pElement)
+{
+ return svl::crypto::DecodeHexString(pElement->GetValue());
+}
+
+OUString PDFDocument::DecodeHexStringUTF16BE(PDFHexStringElement const& rElement)
+{
+ std::vector<unsigned char> const encoded(DecodeHexString(&rElement));
+ // Text strings can be PDF-DocEncoding or UTF-16BE with mandatory BOM;
+ // only the latter supported is here
+ if (encoded.size() < 2 || encoded[0] != 0xFE || encoded[1] != 0xFF || (encoded.size() & 1) != 0)
+ {
+ return {};
+ }
+ OUStringBuffer buf(encoded.size() - 2);
+ for (size_t i = 2; i < encoded.size(); i += 2)
+ {
+ buf.append(sal_Unicode((static_cast<sal_uInt16>(encoded[i]) << 8) | encoded[i + 1]));
+ }
+ return buf.makeStringAndClear();
+}
+
+PDFCommentElement::PDFCommentElement(PDFDocument& rDoc)
+ : m_rDoc(rDoc)
+{
+}
+
+bool PDFCommentElement::Read(SvStream& rStream)
+{
+ // Read from (including) the % char till (excluding) the end of the line/stream.
+ OStringBuffer aBuf;
+ char ch;
+ rStream.ReadChar(ch);
+ while (true)
+ {
+ if (ch == '\n' || ch == '\r' || rStream.eof())
+ {
+ m_aComment = aBuf.makeStringAndClear();
+
+ if (m_aComment.startsWith("%%EOF"))
+ {
+ sal_uInt64 nPos = rStream.Tell();
+ if (ch == '\r')
+ {
+ rStream.ReadChar(ch);
+ rStream.SeekRel(-1);
+ // If the comment ends with a \r\n, count the \n as well to match Adobe Acrobat
+ // behavior.
+ if (ch == '\n')
+ {
+ nPos += 1;
+ }
+ }
+ m_rDoc.PushBackEOF(nPos);
+ }
+
+ SAL_INFO("vcl.filter", "PDFCommentElement::Read: m_aComment is '" << m_aComment << "'");
+ return true;
+ }
+ aBuf.append(ch);
+ rStream.ReadChar(ch);
+ }
+
+ return false;
+}
+
+PDFNumberElement::PDFNumberElement() = default;
+
+bool PDFNumberElement::Read(SvStream& rStream)
+{
+ OStringBuffer aBuf;
+ m_nOffset = rStream.Tell();
+ char ch;
+ rStream.ReadChar(ch);
+ if (rStream.eof())
+ {
+ return false;
+ }
+ if (!rtl::isAsciiDigit(static_cast<unsigned char>(ch)) && ch != '-' && ch != '+' && ch != '.')
+ {
+ rStream.SeekRel(-1);
+ return false;
+ }
+ while (!rStream.eof())
+ {
+ if (!rtl::isAsciiDigit(static_cast<unsigned char>(ch)) && ch != '-' && ch != '+'
+ && ch != '.')
+ {
+ rStream.SeekRel(-1);
+ m_nLength = rStream.Tell() - m_nOffset;
+ m_fValue = o3tl::toDouble(aBuf);
+ aBuf.setLength(0);
+ SAL_INFO("vcl.filter", "PDFNumberElement::Read: m_fValue is '" << m_fValue << "'");
+ return true;
+ }
+ aBuf.append(ch);
+ rStream.ReadChar(ch);
+ }
+
+ return false;
+}
+
+sal_uInt64 PDFNumberElement::GetLocation() const { return m_nOffset; }
+
+sal_uInt64 PDFNumberElement::GetLength() const { return m_nLength; }
+
+bool PDFBooleanElement::Read(SvStream& /*rStream*/) { return true; }
+
+bool PDFNullElement::Read(SvStream& /*rStream*/) { return true; }
+
+bool PDFHexStringElement::Read(SvStream& rStream)
+{
+ char ch;
+ rStream.ReadChar(ch);
+ if (ch != '<')
+ {
+ SAL_INFO("vcl.filter", "PDFHexStringElement::Read: expected '<' as first character");
+ return false;
+ }
+ rStream.ReadChar(ch);
+
+ OStringBuffer aBuf;
+ while (!rStream.eof())
+ {
+ if (ch == '>')
+ {
+ m_aValue = aBuf.makeStringAndClear();
+ SAL_INFO("vcl.filter",
+ "PDFHexStringElement::Read: m_aValue length is " << m_aValue.getLength());
+ return true;
+ }
+ aBuf.append(ch);
+ rStream.ReadChar(ch);
+ }
+
+ return false;
+}
+
+const OString& PDFHexStringElement::GetValue() const { return m_aValue; }
+
+bool PDFLiteralStringElement::Read(SvStream& rStream)
+{
+ char nPrevCh = 0;
+ char ch = 0;
+ rStream.ReadChar(ch);
+ if (ch != '(')
+ {
+ SAL_INFO("vcl.filter", "PDFHexStringElement::Read: expected '(' as first character");
+ return false;
+ }
+ nPrevCh = ch;
+ rStream.ReadChar(ch);
+
+ // Start with 1 nesting level as we read a '(' above already.
+ int nDepth = 1;
+ OStringBuffer aBuf;
+ while (!rStream.eof())
+ {
+ if (ch == '(' && nPrevCh != '\\')
+ ++nDepth;
+
+ if (ch == ')' && nPrevCh != '\\')
+ --nDepth;
+
+ if (nDepth == 0)
+ {
+ // ')' of the outermost '(' is reached.
+ m_aValue = aBuf.makeStringAndClear();
+ SAL_INFO("vcl.filter",
+ "PDFLiteralStringElement::Read: m_aValue is '" << m_aValue << "'");
+ return true;
+ }
+ aBuf.append(ch);
+ nPrevCh = ch;
+ rStream.ReadChar(ch);
+ }
+
+ return false;
+}
+
+const OString& PDFLiteralStringElement::GetValue() const { return m_aValue; }
+
+PDFTrailerElement::PDFTrailerElement(PDFDocument& rDoc)
+ : m_rDoc(rDoc)
+ , m_pDictionaryElement(nullptr)
+{
+}
+
+bool PDFTrailerElement::Read(SvStream& rStream)
+{
+ m_nOffset = rStream.Tell();
+ return true;
+}
+
+PDFElement* PDFTrailerElement::Lookup(const OString& rDictionaryKey)
+{
+ if (!m_pDictionaryElement)
+ {
+ PDFObjectParser aParser(m_rDoc.GetElements());
+ aParser.parse(this);
+ }
+ if (!m_pDictionaryElement)
+ return nullptr;
+ return m_pDictionaryElement->LookupElement(rDictionaryKey);
+}
+
+sal_uInt64 PDFTrailerElement::GetLocation() const { return m_nOffset; }
+
+double PDFNumberElement::GetValue() const { return m_fValue; }
+
+PDFObjectElement::PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue)
+ : m_rDoc(rDoc)
+ , m_fObjectValue(fObjectValue)
+ , m_fGenerationValue(fGenerationValue)
+ , m_pNumberElement(nullptr)
+ , m_nDictionaryOffset(0)
+ , m_nDictionaryLength(0)
+ , m_pDictionaryElement(nullptr)
+ , m_nArrayOffset(0)
+ , m_nArrayLength(0)
+ , m_pArrayElement(nullptr)
+ , m_pStreamElement(nullptr)
+ , m_bParsed(false)
+{
+}
+
+bool PDFObjectElement::Read(SvStream& /*rStream*/)
+{
+ SAL_INFO("vcl.filter",
+ "PDFObjectElement::Read: " << m_fObjectValue << " " << m_fGenerationValue << " obj");
+ return true;
+}
+
+PDFDictionaryElement::PDFDictionaryElement() = default;
+
+PDFElement* PDFDictionaryElement::Lookup(const std::map<OString, PDFElement*>& rDictionary,
+ const OString& rKey)
+{
+ auto it = rDictionary.find(rKey);
+ if (it == rDictionary.end())
+ return nullptr;
+
+ return it->second;
+}
+
+PDFObjectElement* PDFDictionaryElement::LookupObject(const OString& rDictionaryKey)
+{
+ auto pKey = dynamic_cast<PDFReferenceElement*>(
+ PDFDictionaryElement::Lookup(m_aItems, rDictionaryKey));
+ if (!pKey)
+ {
+ SAL_WARN("vcl.filter",
+ "PDFDictionaryElement::LookupObject: no such key with reference value: "
+ << rDictionaryKey);
+ return nullptr;
+ }
+
+ return pKey->LookupObject();
+}
+
+PDFElement* PDFDictionaryElement::LookupElement(const OString& rDictionaryKey)
+{
+ return PDFDictionaryElement::Lookup(m_aItems, rDictionaryKey);
+}
+
+void PDFObjectElement::parseIfNecessary()
+{
+ if (m_bParsed)
+ return;
+
+ if (!m_aElements.empty())
+ {
+ // This is a stored object in an object stream.
+ PDFObjectParser aParser(m_aElements);
+ aParser.parse(this);
+ }
+ else
+ {
+ // Normal object: elements are stored as members of the document itself.
+ PDFObjectParser aParser(m_rDoc.GetElements());
+ aParser.parse(this);
+ }
+ m_bParsed = true;
+}
+
+PDFElement* PDFObjectElement::Lookup(const OString& rDictionaryKey)
+{
+ parseIfNecessary();
+ if (!m_pDictionaryElement)
+ return nullptr;
+ return PDFDictionaryElement::Lookup(GetDictionaryItems(), rDictionaryKey);
+}
+
+PDFObjectElement* PDFObjectElement::LookupObject(const OString& rDictionaryKey)
+{
+ auto pKey = dynamic_cast<PDFReferenceElement*>(Lookup(rDictionaryKey));
+ if (!pKey)
+ {
+ SAL_WARN("vcl.filter", "PDFObjectElement::LookupObject: no such key with reference value: "
+ << rDictionaryKey);
+ return nullptr;
+ }
+
+ return pKey->LookupObject();
+}
+
+double PDFObjectElement::GetObjectValue() const { return m_fObjectValue; }
+
+void PDFObjectElement::SetDictionaryOffset(sal_uInt64 nDictionaryOffset)
+{
+ m_nDictionaryOffset = nDictionaryOffset;
+}
+
+sal_uInt64 PDFObjectElement::GetDictionaryOffset()
+{
+ parseIfNecessary();
+ return m_nDictionaryOffset;
+}
+
+void PDFObjectElement::SetArrayOffset(sal_uInt64 nArrayOffset) { m_nArrayOffset = nArrayOffset; }
+
+sal_uInt64 PDFObjectElement::GetArrayOffset() const { return m_nArrayOffset; }
+
+void PDFDictionaryElement::SetKeyOffset(const OString& rKey, sal_uInt64 nOffset)
+{
+ m_aDictionaryKeyOffset[rKey] = nOffset;
+}
+
+void PDFDictionaryElement::SetKeyValueLength(const OString& rKey, sal_uInt64 nLength)
+{
+ m_aDictionaryKeyValueLength[rKey] = nLength;
+}
+
+sal_uInt64 PDFDictionaryElement::GetKeyOffset(const OString& rKey) const
+{
+ auto it = m_aDictionaryKeyOffset.find(rKey);
+ if (it == m_aDictionaryKeyOffset.end())
+ return 0;
+
+ return it->second;
+}
+
+sal_uInt64 PDFDictionaryElement::GetKeyValueLength(const OString& rKey) const
+{
+ auto it = m_aDictionaryKeyValueLength.find(rKey);
+ if (it == m_aDictionaryKeyValueLength.end())
+ return 0;
+
+ return it->second;
+}
+
+const std::map<OString, PDFElement*>& PDFDictionaryElement::GetItems() const { return m_aItems; }
+
+void PDFObjectElement::SetDictionaryLength(sal_uInt64 nDictionaryLength)
+{
+ m_nDictionaryLength = nDictionaryLength;
+}
+
+sal_uInt64 PDFObjectElement::GetDictionaryLength()
+{
+ parseIfNecessary();
+ return m_nDictionaryLength;
+}
+
+void PDFObjectElement::SetArrayLength(sal_uInt64 nArrayLength) { m_nArrayLength = nArrayLength; }
+
+sal_uInt64 PDFObjectElement::GetArrayLength() const { return m_nArrayLength; }
+
+PDFDictionaryElement* PDFObjectElement::GetDictionary()
+{
+ parseIfNecessary();
+ return m_pDictionaryElement;
+}
+
+void PDFObjectElement::SetDictionary(PDFDictionaryElement* pDictionaryElement)
+{
+ m_pDictionaryElement = pDictionaryElement;
+}
+
+void PDFObjectElement::SetNumberElement(PDFNumberElement* pNumberElement)
+{
+ m_pNumberElement = pNumberElement;
+}
+
+PDFNumberElement* PDFObjectElement::GetNumberElement() const { return m_pNumberElement; }
+
+const std::vector<PDFReferenceElement*>& PDFObjectElement::GetDictionaryReferences() const
+{
+ return m_aDictionaryReferences;
+}
+
+void PDFObjectElement::AddDictionaryReference(PDFReferenceElement* pReference)
+{
+ m_aDictionaryReferences.push_back(pReference);
+}
+
+const std::map<OString, PDFElement*>& PDFObjectElement::GetDictionaryItems()
+{
+ parseIfNecessary();
+ return m_pDictionaryElement->GetItems();
+}
+
+void PDFObjectElement::SetArray(PDFArrayElement* pArrayElement) { m_pArrayElement = pArrayElement; }
+
+void PDFObjectElement::SetStream(PDFStreamElement* pStreamElement)
+{
+ m_pStreamElement = pStreamElement;
+}
+
+PDFStreamElement* PDFObjectElement::GetStream() const { return m_pStreamElement; }
+
+PDFArrayElement* PDFObjectElement::GetArray()
+{
+ parseIfNecessary();
+ return m_pArrayElement;
+}
+
+void PDFObjectElement::ParseStoredObjects()
+{
+ if (!m_pStreamElement)
+ {
+ SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: no stream");
+ return;
+ }
+
+ auto pType = dynamic_cast<PDFNameElement*>(Lookup("Type"));
+ if (!pType || pType->GetValue() != "ObjStm")
+ {
+ if (!pType)
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: missing unexpected type");
+ else
+ SAL_WARN("vcl.filter",
+ "PDFDocument::ReadXRefStream: unexpected type: " << pType->GetValue());
+ return;
+ }
+
+ auto pFilter = dynamic_cast<PDFNameElement*>(Lookup("Filter"));
+ if (!pFilter || pFilter->GetValue() != "FlateDecode")
+ {
+ if (!pFilter)
+ SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: missing filter");
+ else
+ SAL_WARN("vcl.filter",
+ "PDFDocument::ReadXRefStream: unexpected filter: " << pFilter->GetValue());
+ return;
+ }
+
+ auto pFirst = dynamic_cast<PDFNumberElement*>(Lookup("First"));
+ if (!pFirst)
+ {
+ SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: no First");
+ return;
+ }
+
+ auto pN = dynamic_cast<PDFNumberElement*>(Lookup("N"));
+ if (!pN)
+ {
+ SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: no N");
+ return;
+ }
+ size_t nN = pN->GetValue();
+
+ auto pLength = dynamic_cast<PDFNumberElement*>(Lookup("Length"));
+ if (!pLength)
+ {
+ SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: no length");
+ return;
+ }
+ size_t nLength = pLength->GetValue();
+
+ // Read and decompress it.
+ SvMemoryStream& rEditBuffer = m_rDoc.GetEditBuffer();
+ rEditBuffer.Seek(m_pStreamElement->GetOffset());
+ std::vector<char> aBuf(nLength);
+ rEditBuffer.ReadBytes(aBuf.data(), aBuf.size());
+ SvMemoryStream aSource(aBuf.data(), aBuf.size(), StreamMode::READ);
+ SvMemoryStream aStream;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ aZCodec.Decompress(aSource, aStream);
+ if (!aZCodec.EndCompression())
+ {
+ SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: decompression failed");
+ return;
+ }
+
+ nLength = aStream.TellEnd();
+ aStream.Seek(0);
+ std::vector<size_t> aObjNums;
+ std::vector<size_t> aOffsets;
+ std::vector<size_t> aLengths;
+ // First iterate over and find out the lengths.
+ for (size_t nObject = 0; nObject < nN; ++nObject)
+ {
+ PDFNumberElement aObjNum;
+ if (!aObjNum.Read(aStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFObjectElement::ParseStoredObjects: failed to read object number");
+ return;
+ }
+ aObjNums.push_back(aObjNum.GetValue());
+
+ PDFDocument::SkipWhitespace(aStream);
+
+ PDFNumberElement aByteOffset;
+ if (!aByteOffset.Read(aStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFObjectElement::ParseStoredObjects: failed to read byte offset");
+ return;
+ }
+ aOffsets.push_back(pFirst->GetValue() + aByteOffset.GetValue());
+
+ if (aOffsets.size() > 1)
+ aLengths.push_back(aOffsets.back() - aOffsets[aOffsets.size() - 2]);
+ if (nObject + 1 == nN)
+ aLengths.push_back(nLength - aOffsets.back());
+
+ PDFDocument::SkipWhitespace(aStream);
+ }
+
+ // Now create streams with the proper length and tokenize the data.
+ for (size_t nObject = 0; nObject < nN; ++nObject)
+ {
+ size_t nObjNum = aObjNums[nObject];
+ size_t nOffset = aOffsets[nObject];
+ size_t nLen = aLengths[nObject];
+
+ aStream.Seek(nOffset);
+ m_aStoredElements.push_back(std::make_unique<PDFObjectElement>(m_rDoc, nObjNum, 0));
+ PDFObjectElement* pStored = m_aStoredElements.back().get();
+
+ aBuf.clear();
+ aBuf.resize(nLen);
+ aStream.ReadBytes(aBuf.data(), aBuf.size());
+ SvMemoryStream aStoredStream(aBuf.data(), aBuf.size(), StreamMode::READ);
+
+ m_rDoc.Tokenize(aStoredStream, TokenizeMode::STORED_OBJECT, pStored->GetStoredElements(),
+ pStored);
+ // This is how references know the object is stored inside this object stream.
+ m_rDoc.SetIDObject(nObjNum, pStored);
+
+ // Store the stream of the object in the object stream for later use.
+ std::unique_ptr<SvMemoryStream> pStreamBuffer(new SvMemoryStream());
+ aStoredStream.Seek(0);
+ pStreamBuffer->WriteStream(aStoredStream);
+ pStored->SetStreamBuffer(pStreamBuffer);
+ }
+}
+
+std::vector<std::unique_ptr<PDFElement>>& PDFObjectElement::GetStoredElements()
+{
+ return m_aElements;
+}
+
+SvMemoryStream* PDFObjectElement::GetStreamBuffer() const { return m_pStreamBuffer.get(); }
+
+void PDFObjectElement::SetStreamBuffer(std::unique_ptr<SvMemoryStream>& pStreamBuffer)
+{
+ m_pStreamBuffer = std::move(pStreamBuffer);
+}
+
+PDFDocument& PDFObjectElement::GetDocument() { return m_rDoc; }
+
+PDFReferenceElement::PDFReferenceElement(PDFDocument& rDoc, PDFNumberElement& rObject,
+ PDFNumberElement const& rGeneration)
+ : m_rDoc(rDoc)
+ , m_fObjectValue(rObject.GetValue())
+ , m_fGenerationValue(rGeneration.GetValue())
+ , m_rObject(rObject)
+{
+}
+
+PDFNumberElement& PDFReferenceElement::GetObjectElement() const { return m_rObject; }
+
+bool PDFReferenceElement::Read(SvStream& rStream)
+{
+ SAL_INFO("vcl.filter",
+ "PDFReferenceElement::Read: " << m_fObjectValue << " " << m_fGenerationValue << " R");
+ m_nOffset = rStream.Tell();
+ return true;
+}
+
+sal_uInt64 PDFReferenceElement::GetOffset() const { return m_nOffset; }
+
+double PDFReferenceElement::LookupNumber(SvStream& rStream) const
+{
+ size_t nOffset = m_rDoc.GetObjectOffset(m_fObjectValue);
+ if (nOffset == 0)
+ {
+ SAL_WARN("vcl.filter", "PDFReferenceElement::LookupNumber: found no offset for object #"
+ << m_fObjectValue);
+ return 0;
+ }
+
+ sal_uInt64 nOrigPos = rStream.Tell();
+ comphelper::ScopeGuard g([&]() { rStream.Seek(nOrigPos); });
+
+ rStream.Seek(nOffset);
+ {
+ PDFDocument::SkipWhitespace(rStream);
+ PDFNumberElement aNumber;
+ bool bRet = aNumber.Read(rStream);
+ if (!bRet || aNumber.GetValue() != m_fObjectValue)
+ {
+ SAL_WARN("vcl.filter",
+ "PDFReferenceElement::LookupNumber: offset points to not matching object");
+ return 0;
+ }
+ }
+
+ {
+ PDFDocument::SkipWhitespace(rStream);
+ PDFNumberElement aNumber;
+ bool bRet = aNumber.Read(rStream);
+ if (!bRet || aNumber.GetValue() != m_fGenerationValue)
+ {
+ SAL_WARN("vcl.filter",
+ "PDFReferenceElement::LookupNumber: offset points to not matching generation");
+ return 0;
+ }
+ }
+
+ {
+ PDFDocument::SkipWhitespace(rStream);
+ OString aKeyword = PDFDocument::ReadKeyword(rStream);
+ if (aKeyword != "obj")
+ {
+ SAL_WARN("vcl.filter",
+ "PDFReferenceElement::LookupNumber: offset doesn't point to an obj keyword");
+ return 0;
+ }
+ }
+
+ PDFDocument::SkipWhitespace(rStream);
+ PDFNumberElement aNumber;
+ if (!aNumber.Read(rStream))
+ {
+ SAL_WARN("vcl.filter",
+ "PDFReferenceElement::LookupNumber: failed to read referenced number");
+ return 0;
+ }
+
+ return aNumber.GetValue();
+}
+
+PDFObjectElement* PDFReferenceElement::LookupObject()
+{
+ return m_rDoc.LookupObject(m_fObjectValue);
+}
+
+PDFObjectElement* PDFDocument::LookupObject(size_t nObjectNumber)
+{
+ auto itIDObjects = m_aIDObjects.find(nObjectNumber);
+
+ if (itIDObjects != m_aIDObjects.end())
+ return itIDObjects->second;
+
+ SAL_WARN("vcl.filter", "PDFDocument::LookupObject: can't find obj " << nObjectNumber);
+ return nullptr;
+}
+
+SvMemoryStream& PDFDocument::GetEditBuffer() { return m_aEditBuffer; }
+
+int PDFReferenceElement::GetObjectValue() const { return m_fObjectValue; }
+
+int PDFReferenceElement::GetGenerationValue() const { return m_fGenerationValue; }
+
+bool PDFDictionaryElement::Read(SvStream& rStream)
+{
+ char ch;
+ rStream.ReadChar(ch);
+ if (ch != '<')
+ {
+ SAL_WARN("vcl.filter", "PDFDictionaryElement::Read: unexpected character: " << ch);
+ return false;
+ }
+
+ if (rStream.eof())
+ {
+ SAL_WARN("vcl.filter", "PDFDictionaryElement::Read: unexpected end of file");
+ return false;
+ }
+
+ rStream.ReadChar(ch);
+ if (ch != '<')
+ {
+ SAL_WARN("vcl.filter", "PDFDictionaryElement::Read: unexpected character: " << ch);
+ return false;
+ }
+
+ m_nLocation = rStream.Tell();
+
+ SAL_INFO("vcl.filter", "PDFDictionaryElement::Read: '<<'");
+
+ return true;
+}
+
+PDFEndDictionaryElement::PDFEndDictionaryElement() = default;
+
+sal_uInt64 PDFEndDictionaryElement::GetLocation() const { return m_nLocation; }
+
+bool PDFEndDictionaryElement::Read(SvStream& rStream)
+{
+ m_nLocation = rStream.Tell();
+ char ch;
+ rStream.ReadChar(ch);
+ if (ch != '>')
+ {
+ SAL_WARN("vcl.filter", "PDFEndDictionaryElement::Read: unexpected character: " << ch);
+ return false;
+ }
+
+ if (rStream.eof())
+ {
+ SAL_WARN("vcl.filter", "PDFEndDictionaryElement::Read: unexpected end of file");
+ return false;
+ }
+
+ rStream.ReadChar(ch);
+ if (ch != '>')
+ {
+ SAL_WARN("vcl.filter", "PDFEndDictionaryElement::Read: unexpected character: " << ch);
+ return false;
+ }
+
+ SAL_INFO("vcl.filter", "PDFEndDictionaryElement::Read: '>>'");
+
+ return true;
+}
+
+PDFNameElement::PDFNameElement() = default;
+
+bool PDFNameElement::Read(SvStream& rStream)
+{
+ char ch;
+ rStream.ReadChar(ch);
+ if (ch != '/')
+ {
+ SAL_WARN("vcl.filter", "PDFNameElement::Read: unexpected character: " << ch);
+ return false;
+ }
+ m_nLocation = rStream.Tell();
+
+ if (rStream.eof())
+ {
+ SAL_WARN("vcl.filter", "PDFNameElement::Read: unexpected end of file");
+ return false;
+ }
+
+ // Read till the first white-space.
+ OStringBuffer aBuf;
+ rStream.ReadChar(ch);
+ while (!rStream.eof())
+ {
+ if (rtl::isAsciiWhiteSpace(static_cast<unsigned char>(ch)) || ch == '/' || ch == '['
+ || ch == ']' || ch == '<' || ch == '>' || ch == '(')
+ {
+ rStream.SeekRel(-1);
+ m_aValue = aBuf.makeStringAndClear();
+ SAL_INFO("vcl.filter", "PDFNameElement::Read: m_aValue is '" << m_aValue << "'");
+ return true;
+ }
+ aBuf.append(ch);
+ rStream.ReadChar(ch);
+ }
+
+ return false;
+}
+
+const OString& PDFNameElement::GetValue() const { return m_aValue; }
+
+sal_uInt64 PDFNameElement::GetLocation() const { return m_nLocation; }
+
+PDFStreamElement::PDFStreamElement(size_t nLength)
+ : m_nLength(nLength)
+ , m_nOffset(0)
+{
+}
+
+bool PDFStreamElement::Read(SvStream& rStream)
+{
+ SAL_INFO("vcl.filter", "PDFStreamElement::Read: length is " << m_nLength);
+ m_nOffset = rStream.Tell();
+ std::vector<unsigned char> aBytes(m_nLength);
+ rStream.ReadBytes(aBytes.data(), aBytes.size());
+ m_aMemory.WriteBytes(aBytes.data(), aBytes.size());
+
+ return rStream.good();
+}
+
+SvMemoryStream& PDFStreamElement::GetMemory() { return m_aMemory; }
+
+sal_uInt64 PDFStreamElement::GetOffset() const { return m_nOffset; }
+
+bool PDFEndStreamElement::Read(SvStream& /*rStream*/) { return true; }
+
+bool PDFEndObjectElement::Read(SvStream& /*rStream*/) { return true; }
+
+PDFArrayElement::PDFArrayElement(PDFObjectElement* pObject)
+ : m_pObject(pObject)
+{
+}
+
+bool PDFArrayElement::Read(SvStream& rStream)
+{
+ char ch;
+ rStream.ReadChar(ch);
+ if (ch != '[')
+ {
+ SAL_WARN("vcl.filter", "PDFArrayElement::Read: unexpected character: " << ch);
+ return false;
+ }
+
+ SAL_INFO("vcl.filter", "PDFArrayElement::Read: '['");
+
+ return true;
+}
+
+void PDFArrayElement::PushBack(PDFElement* pElement)
+{
+ if (m_pObject)
+ SAL_INFO("vcl.filter",
+ "PDFArrayElement::PushBack: object is " << m_pObject->GetObjectValue());
+ m_aElements.push_back(pElement);
+}
+
+const std::vector<PDFElement*>& PDFArrayElement::GetElements() const { return m_aElements; }
+
+PDFEndArrayElement::PDFEndArrayElement() = default;
+
+bool PDFEndArrayElement::Read(SvStream& rStream)
+{
+ m_nOffset = rStream.Tell();
+ char ch;
+ rStream.ReadChar(ch);
+ if (ch != ']')
+ {
+ SAL_WARN("vcl.filter", "PDFEndArrayElement::Read: unexpected character: " << ch);
+ return false;
+ }
+
+ SAL_INFO("vcl.filter", "PDFEndArrayElement::Read: ']'");
+
+ return true;
+}
+
+sal_uInt64 PDFEndArrayElement::GetOffset() const { return m_nOffset; }
+
+// PDFObjectParser
+
+size_t PDFObjectParser::parse(PDFElement* pParsingElement, size_t nStartIndex, int nCurrentDepth)
+{
+ // The index of last parsed element
+ size_t nReturnIndex = 0;
+
+ pParsingElement->setParsing(true);
+
+ comphelper::ScopeGuard aGuard([pParsingElement]() { pParsingElement->setParsing(false); });
+
+ // Current object, if root is an object, else nullptr
+ auto pParsingObject = dynamic_cast<PDFObjectElement*>(pParsingElement);
+ auto pParsingTrailer = dynamic_cast<PDFTrailerElement*>(pParsingElement);
+
+ // Current dictionary, if root is an dictionary, else nullptr
+ auto pParsingDictionary = dynamic_cast<PDFDictionaryElement*>(pParsingElement);
+
+ // Current parsing array, if root is an array, else nullptr
+ auto pParsingArray = dynamic_cast<PDFArrayElement*>(pParsingElement);
+
+ // Find out where the dictionary for this object starts.
+ size_t nIndex = nStartIndex;
+ for (size_t i = nStartIndex; i < mrElements.size(); ++i)
+ {
+ if (mrElements[i].get() == pParsingElement)
+ {
+ nIndex = i;
+ break;
+ }
+ }
+
+ OString aName;
+ sal_uInt64 nNameOffset = 0;
+ std::vector<PDFNumberElement*> aNumbers;
+
+ sal_uInt64 nDictionaryOffset = 0;
+
+ // Current depth; 1 is current
+ int nDepth = 0;
+
+ for (size_t i = nIndex; i < mrElements.size(); ++i)
+ {
+ auto* pCurrentElement = mrElements[i].get();
+
+ // Dictionary tokens can be nested, track enter/leave.
+ if (auto pCurrentDictionary = dynamic_cast<PDFDictionaryElement*>(pCurrentElement))
+ {
+ // Handle previously stored number
+ if (!aNumbers.empty())
+ {
+ if (pParsingDictionary)
+ {
+ PDFNumberElement* pNumber = aNumbers.back();
+ sal_uInt64 nLength
+ = pNumber->GetLocation() + pNumber->GetLength() - nNameOffset;
+
+ pParsingDictionary->insert(aName, pNumber);
+ pParsingDictionary->SetKeyOffset(aName, nNameOffset);
+ pParsingDictionary->SetKeyValueLength(aName, nLength);
+ }
+ else if (pParsingArray)
+ {
+ for (auto& pNumber : aNumbers)
+ pParsingArray->PushBack(pNumber);
+ }
+ else
+ {
+ SAL_INFO("vcl.filter", "neither Dictionary nor Array available");
+ }
+ aName.clear();
+ aNumbers.clear();
+ }
+
+ nDepth++;
+
+ if (nDepth == 1) // pParsingDictionary is the current one
+ {
+ // First dictionary start, track start offset.
+ nDictionaryOffset = pCurrentDictionary->GetLocation();
+
+ if (pParsingObject)
+ {
+ // Then the toplevel dictionary of the object.
+ pParsingObject->SetDictionary(pCurrentDictionary);
+ pParsingObject->SetDictionaryOffset(nDictionaryOffset);
+ pParsingDictionary = pCurrentDictionary;
+ }
+ else if (pParsingTrailer)
+ {
+ pParsingTrailer->SetDictionary(pCurrentDictionary);
+ pParsingDictionary = pCurrentDictionary;
+ }
+ }
+ else if (!pCurrentDictionary->alreadyParsing())
+ {
+ if (pParsingArray)
+ {
+ pParsingArray->PushBack(pCurrentDictionary);
+ }
+ else if (pParsingDictionary)
+ {
+ // Dictionary toplevel value.
+ pParsingDictionary->insert(aName, pCurrentDictionary);
+ }
+ else
+ {
+ SAL_INFO("vcl.filter", "neither Dictionary nor Array available");
+ }
+ // Nested dictionary.
+ const size_t nNextElementIndex = parse(pCurrentDictionary, i, nCurrentDepth + 1);
+ i = std::max(i, nNextElementIndex - 1);
+ }
+ }
+ else if (auto pCurrentEndDictionary
+ = dynamic_cast<PDFEndDictionaryElement*>(pCurrentElement))
+ {
+ // Handle previously stored number
+ if (!aNumbers.empty())
+ {
+ if (pParsingDictionary)
+ {
+ PDFNumberElement* pNumber = aNumbers.back();
+ sal_uInt64 nLength
+ = pNumber->GetLocation() + pNumber->GetLength() - nNameOffset;
+
+ pParsingDictionary->insert(aName, pNumber);
+ pParsingDictionary->SetKeyOffset(aName, nNameOffset);
+ pParsingDictionary->SetKeyValueLength(aName, nLength);
+ }
+ else if (pParsingArray)
+ {
+ for (auto& pNumber : aNumbers)
+ pParsingArray->PushBack(pNumber);
+ }
+ else
+ {
+ SAL_INFO("vcl.filter", "neither Dictionary nor Array available");
+ }
+ aName.clear();
+ aNumbers.clear();
+ }
+
+ if (pParsingDictionary)
+ {
+ pParsingDictionary->SetKeyOffset(aName, nNameOffset);
+ sal_uInt64 nLength = pCurrentEndDictionary->GetLocation() - nNameOffset + 2;
+ pParsingDictionary->SetKeyValueLength(aName, nLength);
+ aName.clear();
+ }
+
+ if (nDepth == 1) // did the parsing ended
+ {
+ // Last dictionary end, track length and stop parsing.
+ if (pParsingObject)
+ {
+ sal_uInt64 nDictionaryLength
+ = pCurrentEndDictionary->GetLocation() - nDictionaryOffset;
+ pParsingObject->SetDictionaryLength(nDictionaryLength);
+ }
+ nReturnIndex = i;
+ break;
+ }
+
+ nDepth--;
+ }
+ else if (auto pCurrentArray = dynamic_cast<PDFArrayElement*>(pCurrentElement))
+ {
+ // Handle previously stored number
+ if (!aNumbers.empty())
+ {
+ if (pParsingDictionary)
+ {
+ PDFNumberElement* pNumber = aNumbers.back();
+
+ sal_uInt64 nLength
+ = pNumber->GetLocation() + pNumber->GetLength() - nNameOffset;
+ pParsingDictionary->insert(aName, pNumber);
+ pParsingDictionary->SetKeyOffset(aName, nNameOffset);
+ pParsingDictionary->SetKeyValueLength(aName, nLength);
+ }
+ else if (pParsingArray)
+ {
+ for (auto& pNumber : aNumbers)
+ pParsingArray->PushBack(pNumber);
+ }
+ else
+ {
+ SAL_INFO("vcl.filter", "neither Dictionary nor Array available");
+ }
+ aName.clear();
+ aNumbers.clear();
+ }
+
+ nDepth++;
+ if (nDepth == 1) // pParsingDictionary is the current one
+ {
+ if (pParsingObject)
+ {
+ pParsingObject->SetArray(pCurrentArray);
+ pParsingArray = pCurrentArray;
+ }
+ }
+ else if (!pCurrentArray->alreadyParsing())
+ {
+ if (pParsingArray)
+ {
+ // Array is toplevel
+ pParsingArray->PushBack(pCurrentArray);
+ }
+ else if (pParsingDictionary)
+ {
+ // Dictionary toplevel value.
+ pParsingDictionary->insert(aName, pCurrentArray);
+ }
+
+ const size_t nNextElementIndex = parse(pCurrentArray, i, nCurrentDepth + 1);
+
+ // ensure we go forwards and not endlessly loop
+ i = std::max(i, nNextElementIndex - 1);
+ }
+ }
+ else if (auto pCurrentEndArray = dynamic_cast<PDFEndArrayElement*>(pCurrentElement))
+ {
+ // Handle previously stored number
+ if (!aNumbers.empty())
+ {
+ if (pParsingDictionary)
+ {
+ PDFNumberElement* pNumber = aNumbers.back();
+
+ sal_uInt64 nLength
+ = pNumber->GetLocation() + pNumber->GetLength() - nNameOffset;
+ pParsingDictionary->insert(aName, pNumber);
+ pParsingDictionary->SetKeyOffset(aName, nNameOffset);
+ pParsingDictionary->SetKeyValueLength(aName, nLength);
+ }
+ else if (pParsingArray)
+ {
+ for (auto& pNumber : aNumbers)
+ pParsingArray->PushBack(pNumber);
+ }
+ else
+ {
+ SAL_INFO("vcl.filter", "neither Dictionary nor Array available");
+ }
+ aName.clear();
+ aNumbers.clear();
+ }
+
+ if (nDepth == 1) // did the pParsing ended
+ {
+ // Last array end, track length and stop parsing.
+ nReturnIndex = i;
+ break;
+ }
+
+ if (pParsingDictionary)
+ {
+ pParsingDictionary->SetKeyOffset(aName, nNameOffset);
+ // Include the ending ']' in the length of the key - (array)value pair length.
+ sal_uInt64 nLength = pCurrentEndArray->GetOffset() - nNameOffset + 1;
+ pParsingDictionary->SetKeyValueLength(aName, nLength);
+ aName.clear();
+ }
+ nDepth--;
+ }
+ else if (auto pCurrentName = dynamic_cast<PDFNameElement*>(pCurrentElement))
+ {
+ // Handle previously stored number
+ if (!aNumbers.empty())
+ {
+ if (pParsingDictionary)
+ {
+ PDFNumberElement* pNumber = aNumbers.back();
+
+ sal_uInt64 nLength
+ = pNumber->GetLocation() + pNumber->GetLength() - nNameOffset;
+ pParsingDictionary->insert(aName, pNumber);
+ pParsingDictionary->SetKeyOffset(aName, nNameOffset);
+ pParsingDictionary->SetKeyValueLength(aName, nLength);
+ }
+ else if (pParsingArray)
+ {
+ for (auto& pNumber : aNumbers)
+ pParsingArray->PushBack(pNumber);
+ }
+ aName.clear();
+ aNumbers.clear();
+ }
+
+ // Now handle name
+ if (pParsingArray)
+ {
+ // if we are in an array, just push the name to array
+ pParsingArray->PushBack(pCurrentName);
+ }
+ else if (pParsingDictionary)
+ {
+ // if we are in a dictionary, we need to store the name as a possible key
+ if (aName.isEmpty())
+ {
+ aName = pCurrentName->GetValue();
+ nNameOffset = pCurrentName->GetLocation();
+ }
+ else
+ {
+ sal_uInt64 nKeyLength
+ = pCurrentName->GetLocation() + pCurrentName->GetLength() - nNameOffset;
+ pParsingDictionary->insert(aName, pCurrentName);
+ pParsingDictionary->SetKeyOffset(aName, nNameOffset);
+ pParsingDictionary->SetKeyValueLength(aName, nKeyLength);
+ aName.clear();
+ }
+ }
+ }
+ else if (auto pReference = dynamic_cast<PDFReferenceElement*>(pCurrentElement))
+ {
+ if (pParsingArray)
+ {
+ pParsingArray->PushBack(pReference);
+ }
+ else if (pParsingDictionary)
+ {
+ sal_uInt64 nLength = pReference->GetOffset() - nNameOffset;
+ pParsingDictionary->insert(aName, pReference);
+ pParsingDictionary->SetKeyOffset(aName, nNameOffset);
+ pParsingDictionary->SetKeyValueLength(aName, nLength);
+ aName.clear();
+ }
+ else
+ {
+ SAL_INFO("vcl.filter", "neither Dictionary nor Array available");
+ }
+ aNumbers.clear();
+ }
+ else if (auto pLiteralString = dynamic_cast<PDFLiteralStringElement*>(pCurrentElement))
+ {
+ if (pParsingArray)
+ {
+ pParsingArray->PushBack(pLiteralString);
+ }
+ else if (pParsingDictionary)
+ {
+ pParsingDictionary->insert(aName, pLiteralString);
+ pParsingDictionary->SetKeyOffset(aName, nNameOffset);
+ aName.clear();
+ }
+ else
+ {
+ SAL_INFO("vcl.filter", "neither Dictionary nor Array available");
+ }
+ }
+ else if (auto pBoolean = dynamic_cast<PDFBooleanElement*>(pCurrentElement))
+ {
+ if (pParsingArray)
+ {
+ pParsingArray->PushBack(pBoolean);
+ }
+ else if (pParsingDictionary)
+ {
+ pParsingDictionary->insert(aName, pBoolean);
+ pParsingDictionary->SetKeyOffset(aName, nNameOffset);
+ aName.clear();
+ }
+ else
+ {
+ SAL_INFO("vcl.filter", "neither Dictionary nor Array available");
+ }
+ }
+ else if (auto pHexString = dynamic_cast<PDFHexStringElement*>(pCurrentElement))
+ {
+ if (pParsingArray)
+ {
+ pParsingArray->PushBack(pHexString);
+ }
+ else if (pParsingDictionary)
+ {
+ pParsingDictionary->insert(aName, pHexString);
+ pParsingDictionary->SetKeyOffset(aName, nNameOffset);
+ aName.clear();
+ }
+ }
+ else if (auto pNumberElement = dynamic_cast<PDFNumberElement*>(pCurrentElement))
+ {
+ // Just remember this, so that in case it's not a reference parameter,
+ // we can handle it later.
+ aNumbers.push_back(pNumberElement);
+ }
+ else if (dynamic_cast<PDFEndObjectElement*>(pCurrentElement))
+ {
+ // parsing of the object is finished
+ break;
+ }
+ else if (dynamic_cast<PDFObjectElement*>(pCurrentElement)
+ || dynamic_cast<PDFTrailerElement*>(pCurrentElement))
+ {
+ continue;
+ }
+ else
+ {
+ SAL_INFO("vcl.filter", "Unhandled element while parsing.");
+ }
+ }
+
+ return nReturnIndex;
+}
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ipdf/pdfread.cxx b/vcl/source/filter/ipdf/pdfread.cxx
new file mode 100644
index 000000000..c6bc4fd5b
--- /dev/null
+++ b/vcl/source/filter/ipdf/pdfread.cxx
@@ -0,0 +1,399 @@
+/* -*- 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/.
+ */
+
+#include <vcl/pdfread.hxx>
+#include <pdf/pdfcompat.hxx>
+
+#include <pdf/PdfConfig.hxx>
+#include <vcl/graph.hxx>
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <unotools/datetime.hxx>
+
+#include <vcl/filter/PDFiumLibrary.hxx>
+#include <sal/log.hxx>
+#include <o3tl/string_view.hxx>
+
+using namespace com::sun::star;
+
+namespace vcl
+{
+size_t RenderPDFBitmaps(const void* pBuffer, int nSize, std::vector<BitmapEx>& rBitmaps,
+ const size_t nFirstPage, int nPages, const basegfx::B2DTuple* pSizeHint)
+{
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+ if (!pPdfium)
+ {
+ return 0;
+ }
+
+ // Load the buffer using pdfium.
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
+ = pPdfium->openDocument(pBuffer, nSize, OString());
+ if (!pPdfDocument)
+ return 0;
+
+ static const double fResolutionDPI = vcl::pdf::getDefaultPdfResolutionDpi();
+
+ const int nPageCount = pPdfDocument->getPageCount();
+ if (nPages <= 0)
+ nPages = nPageCount;
+ const size_t nLastPage = std::min<int>(nPageCount, nFirstPage + nPages) - 1;
+ for (size_t nPageIndex = nFirstPage; nPageIndex <= nLastPage; ++nPageIndex)
+ {
+ // Render next page.
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(nPageIndex);
+ if (!pPdfPage)
+ break;
+
+ // Calculate the bitmap size in points.
+ double nPageWidthPoints = pPdfPage->getWidth();
+ double nPageHeightPoints = pPdfPage->getHeight();
+ if (pSizeHint && pSizeHint->getX() && pSizeHint->getY())
+ {
+ // Have a size hint, prefer that over the logic size from the PDF.
+ nPageWidthPoints
+ = o3tl::convert(pSizeHint->getX(), o3tl::Length::mm100, o3tl::Length::pt);
+ nPageHeightPoints
+ = o3tl::convert(pSizeHint->getY(), o3tl::Length::mm100, o3tl::Length::pt);
+ }
+
+ // Returned unit is points, convert that to pixel.
+
+ const size_t nPageWidth
+ = std::round(vcl::pdf::pointToPixel(nPageWidthPoints, fResolutionDPI)
+ * PDF_INSERT_MAGIC_SCALE_FACTOR);
+ const size_t nPageHeight
+ = std::round(vcl::pdf::pointToPixel(nPageHeightPoints, fResolutionDPI)
+ * PDF_INSERT_MAGIC_SCALE_FACTOR);
+ std::unique_ptr<vcl::pdf::PDFiumBitmap> pPdfBitmap
+ = pPdfium->createBitmap(nPageWidth, nPageHeight, /*nAlpha=*/1);
+ if (!pPdfBitmap)
+ break;
+
+ bool bTransparent = pPdfPage->hasTransparency();
+ if (pSizeHint)
+ {
+ // This is the PDF-in-EMF case: force transparency, even in case pdfium would tell us
+ // the PDF is not transparent.
+ bTransparent = true;
+ }
+ const sal_uInt32 nColor = bTransparent ? 0x00000000 : 0xFFFFFFFF;
+ pPdfBitmap->fillRect(0, 0, nPageWidth, nPageHeight, nColor);
+ pPdfBitmap->renderPageBitmap(pPdfDocument.get(), pPdfPage.get(), /*nStartX=*/0,
+ /*nStartY=*/0, nPageWidth, nPageHeight);
+
+ // Save the buffer as a bitmap.
+ Bitmap aBitmap(Size(nPageWidth, nPageHeight), vcl::PixelFormat::N24_BPP);
+ AlphaMask aMask(Size(nPageWidth, nPageHeight));
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ AlphaScopedWriteAccess pMaskAccess(aMask);
+ ConstScanline pPdfBuffer = pPdfBitmap->getBuffer();
+ const int nStride = pPdfBitmap->getStride();
+ std::vector<sal_uInt8> aScanlineAlpha(nPageWidth);
+ for (size_t nRow = 0; nRow < nPageHeight; ++nRow)
+ {
+ ConstScanline pPdfLine = pPdfBuffer + (nStride * nRow);
+ // pdfium byte order is BGRA.
+ pWriteAccess->CopyScanline(nRow, pPdfLine, ScanlineFormat::N32BitTcBgra, nStride);
+ for (size_t nCol = 0; nCol < nPageWidth; ++nCol)
+ {
+ // Invert alpha (source is alpha, target is opacity).
+ aScanlineAlpha[nCol] = ~pPdfLine[3];
+ pPdfLine += 4;
+ }
+ pMaskAccess->CopyScanline(nRow, aScanlineAlpha.data(), ScanlineFormat::N8BitPal,
+ nPageWidth);
+ }
+ }
+
+ if (bTransparent)
+ {
+ rBitmaps.emplace_back(aBitmap, aMask);
+ }
+ else
+ {
+ rBitmaps.emplace_back(std::move(aBitmap));
+ }
+ }
+
+ return rBitmaps.size();
+}
+
+bool importPdfVectorGraphicData(SvStream& rStream,
+ std::shared_ptr<VectorGraphicData>& rVectorGraphicData)
+{
+ BinaryDataContainer aDataContainer = vcl::pdf::createBinaryDataContainer(rStream);
+ if (aDataContainer.isEmpty())
+ {
+ SAL_WARN("vcl.filter", "ImportPDF: empty PDF data array");
+ return false;
+ }
+
+ rVectorGraphicData
+ = std::make_shared<VectorGraphicData>(aDataContainer, VectorGraphicDataType::Pdf);
+
+ return true;
+}
+
+bool ImportPDF(SvStream& rStream, Graphic& rGraphic)
+{
+ std::shared_ptr<VectorGraphicData> pVectorGraphicData;
+ if (!importPdfVectorGraphicData(rStream, pVectorGraphicData))
+ return false;
+ rGraphic = Graphic(pVectorGraphicData);
+ return true;
+}
+
+namespace
+{
+basegfx::B2DPoint convertFromPDFInternalToHMM(basegfx::B2DSize const& rInputPoint,
+ basegfx::B2DSize const& rPageSize)
+{
+ double x = convertPointToMm100(rInputPoint.getX());
+ double y = convertPointToMm100(rPageSize.getY() - rInputPoint.getY());
+ return { x, y };
+}
+
+std::vector<PDFGraphicAnnotation>
+findAnnotations(const std::unique_ptr<vcl::pdf::PDFiumPage>& pPage, basegfx::B2DSize aPageSize)
+{
+ std::vector<PDFGraphicAnnotation> aPDFGraphicAnnotations;
+ if (!pPage)
+ {
+ return aPDFGraphicAnnotations;
+ }
+
+ for (int nAnnotation = 0; nAnnotation < pPage->getAnnotationCount(); nAnnotation++)
+ {
+ auto pAnnotation = pPage->getAnnotation(nAnnotation);
+ if (pAnnotation)
+ {
+ auto eSubtype = pAnnotation->getSubType();
+
+ if (eSubtype == vcl::pdf::PDFAnnotationSubType::Text
+ || eSubtype == vcl::pdf::PDFAnnotationSubType::Polygon
+ || eSubtype == vcl::pdf::PDFAnnotationSubType::Circle
+ || eSubtype == vcl::pdf::PDFAnnotationSubType::Square
+ || eSubtype == vcl::pdf::PDFAnnotationSubType::Ink
+ || eSubtype == vcl::pdf::PDFAnnotationSubType::Highlight
+ || eSubtype == vcl::pdf::PDFAnnotationSubType::Line)
+ {
+ OUString sAuthor = pAnnotation->getString(vcl::pdf::constDictionaryKeyTitle);
+ OUString sText = pAnnotation->getString(vcl::pdf::constDictionaryKeyContents);
+
+ basegfx::B2DRectangle rRectangle = pAnnotation->getRectangle();
+ basegfx::B2DRectangle rRectangleHMM(
+ convertPointToMm100(rRectangle.getMinX()),
+ convertPointToMm100(aPageSize.getY() - rRectangle.getMinY()),
+ convertPointToMm100(rRectangle.getMaxX()),
+ convertPointToMm100(aPageSize.getY() - rRectangle.getMaxY()));
+
+ OUString sDateTimeString
+ = pAnnotation->getString(vcl::pdf::constDictionaryKeyModificationDate);
+ OUString sISO8601String = vcl::pdf::convertPdfDateToISO8601(sDateTimeString);
+
+ css::util::DateTime aDateTime;
+ if (!sISO8601String.isEmpty())
+ {
+ utl::ISO8601parseDateTime(sISO8601String, aDateTime);
+ }
+
+ Color aColor = pAnnotation->getColor();
+
+ aPDFGraphicAnnotations.emplace_back();
+
+ auto& rPDFGraphicAnnotation = aPDFGraphicAnnotations.back();
+ rPDFGraphicAnnotation.maRectangle = rRectangleHMM;
+ rPDFGraphicAnnotation.maAuthor = sAuthor;
+ rPDFGraphicAnnotation.maText = sText;
+ rPDFGraphicAnnotation.maDateTime = aDateTime;
+ rPDFGraphicAnnotation.meSubType = eSubtype;
+ rPDFGraphicAnnotation.maColor = aColor;
+
+ if (eSubtype == vcl::pdf::PDFAnnotationSubType::Polygon)
+ {
+ auto const& rVertices = pAnnotation->getVertices();
+ if (!rVertices.empty())
+ {
+ auto pMarker = std::make_shared<vcl::pdf::PDFAnnotationMarkerPolygon>();
+ rPDFGraphicAnnotation.mpMarker = pMarker;
+ for (auto const& rVertex : rVertices)
+ {
+ auto aPoint = convertFromPDFInternalToHMM(rVertex, aPageSize);
+ pMarker->maPolygon.append(aPoint);
+ }
+ pMarker->maPolygon.setClosed(true);
+ pMarker->mnWidth = convertPointToMm100(pAnnotation->getBorderWidth());
+ if (pAnnotation->hasKey(vcl::pdf::constDictionaryKeyInteriorColor))
+ pMarker->maFillColor = pAnnotation->getInteriorColor();
+ }
+ }
+ else if (eSubtype == vcl::pdf::PDFAnnotationSubType::Square)
+ {
+ auto pMarker = std::make_shared<vcl::pdf::PDFAnnotationMarkerSquare>();
+ rPDFGraphicAnnotation.mpMarker = pMarker;
+ pMarker->mnWidth = convertPointToMm100(pAnnotation->getBorderWidth());
+ if (pAnnotation->hasKey(vcl::pdf::constDictionaryKeyInteriorColor))
+ pMarker->maFillColor = pAnnotation->getInteriorColor();
+ }
+ else if (eSubtype == vcl::pdf::PDFAnnotationSubType::Circle)
+ {
+ auto pMarker = std::make_shared<vcl::pdf::PDFAnnotationMarkerCircle>();
+ rPDFGraphicAnnotation.mpMarker = pMarker;
+ pMarker->mnWidth = convertPointToMm100(pAnnotation->getBorderWidth());
+ if (pAnnotation->hasKey(vcl::pdf::constDictionaryKeyInteriorColor))
+ pMarker->maFillColor = pAnnotation->getInteriorColor();
+ }
+ else if (eSubtype == vcl::pdf::PDFAnnotationSubType::Ink)
+ {
+ auto const& rStrokesList = pAnnotation->getInkStrokes();
+ if (!rStrokesList.empty())
+ {
+ auto pMarker = std::make_shared<vcl::pdf::PDFAnnotationMarkerInk>();
+ rPDFGraphicAnnotation.mpMarker = pMarker;
+ for (auto const& rStrokes : rStrokesList)
+ {
+ basegfx::B2DPolygon aPolygon;
+ for (auto const& rVertex : rStrokes)
+ {
+ auto aPoint = convertFromPDFInternalToHMM(rVertex, aPageSize);
+ aPolygon.append(aPoint);
+ }
+ pMarker->maStrokes.push_back(aPolygon);
+ }
+ float fWidth = pAnnotation->getBorderWidth();
+ pMarker->mnWidth = convertPointToMm100(fWidth);
+ if (pAnnotation->hasKey(vcl::pdf::constDictionaryKeyInteriorColor))
+ pMarker->maFillColor = pAnnotation->getInteriorColor();
+ }
+ }
+ else if (eSubtype == vcl::pdf::PDFAnnotationSubType::Highlight)
+ {
+ size_t nCount = pAnnotation->getAttachmentPointsCount();
+ if (nCount > 0)
+ {
+ auto pMarker = std::make_shared<vcl::pdf::PDFAnnotationMarkerHighlight>(
+ vcl::pdf::PDFTextMarkerType::Highlight);
+ rPDFGraphicAnnotation.mpMarker = pMarker;
+ for (size_t i = 0; i < nCount; ++i)
+ {
+ auto aAttachmentPoints = pAnnotation->getAttachmentPoints(i);
+ if (!aAttachmentPoints.empty())
+ {
+ basegfx::B2DPolygon aPolygon;
+ aPolygon.setClosed(true);
+
+ auto aPoint1
+ = convertFromPDFInternalToHMM(aAttachmentPoints[0], aPageSize);
+ aPolygon.append(aPoint1);
+ auto aPoint2
+ = convertFromPDFInternalToHMM(aAttachmentPoints[1], aPageSize);
+ aPolygon.append(aPoint2);
+ auto aPoint3
+ = convertFromPDFInternalToHMM(aAttachmentPoints[3], aPageSize);
+ aPolygon.append(aPoint3);
+ auto aPoint4
+ = convertFromPDFInternalToHMM(aAttachmentPoints[2], aPageSize);
+ aPolygon.append(aPoint4);
+
+ pMarker->maQuads.push_back(aPolygon);
+ }
+ }
+ }
+ }
+ else if (eSubtype == vcl::pdf::PDFAnnotationSubType::Line)
+ {
+ auto const& rLineGeometry = pAnnotation->getLineGeometry();
+ if (!rLineGeometry.empty())
+ {
+ auto pMarker = std::make_shared<vcl::pdf::PDFAnnotationMarkerLine>();
+ rPDFGraphicAnnotation.mpMarker = pMarker;
+
+ auto aPoint1 = convertFromPDFInternalToHMM(rLineGeometry[0], aPageSize);
+ pMarker->maLineStart = aPoint1;
+
+ auto aPoint2 = convertFromPDFInternalToHMM(rLineGeometry[1], aPageSize);
+ pMarker->maLineEnd = aPoint2;
+
+ float fWidth = pAnnotation->getBorderWidth();
+ pMarker->mnWidth = convertPointToMm100(fWidth);
+ }
+ }
+ }
+ }
+ }
+ return aPDFGraphicAnnotations;
+}
+
+} // end anonymous namespace
+
+size_t ImportPDFUnloaded(const OUString& rURL, std::vector<PDFGraphicResult>& rGraphics)
+{
+ std::unique_ptr<SvStream> xStream(
+ ::utl::UcbStreamHelper::CreateStream(rURL, StreamMode::READ | StreamMode::SHARE_DENYNONE));
+
+ // Save the original PDF stream for later use.
+ BinaryDataContainer aDataContainer = vcl::pdf::createBinaryDataContainer(*xStream);
+ if (aDataContainer.isEmpty())
+ return 0;
+
+ // Prepare the link with the PDF stream.
+ auto pGfxLink = std::make_shared<GfxLink>(aDataContainer, GfxLinkType::NativePdf);
+
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+ if (!pPdfium)
+ {
+ return 0;
+ }
+
+ // Load the buffer using pdfium.
+ auto pPdfDocument
+ = pPdfium->openDocument(pGfxLink->GetData(), pGfxLink->GetDataSize(), OString());
+
+ if (!pPdfDocument)
+ return 0;
+
+ const int nPageCount = pPdfDocument->getPageCount();
+ if (nPageCount <= 0)
+ return 0;
+
+ for (int nPageIndex = 0; nPageIndex < nPageCount; ++nPageIndex)
+ {
+ basegfx::B2DSize aPageSize = pPdfDocument->getPageSize(nPageIndex);
+ if (aPageSize.getX() <= 0.0 || aPageSize.getY() <= 0.0)
+ continue;
+
+ // Returned unit is points, convert that to twip
+ // 1 pt = 20 twips
+ constexpr double pointToTwipconversionRatio = 20;
+
+ tools::Long nPageWidth = convertTwipToMm100(aPageSize.getX() * pointToTwipconversionRatio);
+ tools::Long nPageHeight = convertTwipToMm100(aPageSize.getY() * pointToTwipconversionRatio);
+
+ // Create the Graphic with the VectorGraphicDataPtr and link the original PDF stream.
+ // We swap out this Graphic as soon as possible, and a later swap in
+ // actually renders the correct Bitmap on demand.
+ Graphic aGraphic(pGfxLink, nPageIndex);
+
+ auto pPage = pPdfDocument->openPage(nPageIndex);
+
+ std::vector<PDFGraphicAnnotation> aPDFGraphicAnnotations
+ = findAnnotations(pPage, aPageSize);
+
+ rGraphics.emplace_back(std::move(aGraphic), Size(nPageWidth, nPageHeight),
+ aPDFGraphicAnnotations);
+ }
+
+ return rGraphics.size();
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ipict/ipict.cxx b/vcl/source/filter/ipict/ipict.cxx
new file mode 100644
index 000000000..0f8fd5ba0
--- /dev/null
+++ b/vcl/source/filter/ipict/ipict.cxx
@@ -0,0 +1,2041 @@
+/* -*- 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 <filter/PictReader.hxx>
+#include <string.h>
+#include <osl/thread.h>
+#include <sal/log.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/gdimtf.hxx>
+#include <tools/poly.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <vcl/virdev.hxx>
+#include <math.h>
+#include "shape.hxx"
+#include <memory>
+
+#include <vcl/FilterConfigItem.hxx>
+ // complete FilterConfigItem for GraphicImport under -fsanitize=function
+
+namespace PictReaderInternal {
+ namespace {
+
+ //! utilitary class to store a pattern, ...
+ class Pattern {
+ public:
+ //! constructor
+ Pattern() : penStyle(PEN_SOLID),
+ brushStyle(BRUSH_SOLID),
+ nBitCount(64),
+ isColor(false),
+ isRead(false)
+ {}
+
+ //! reads black/white pattern from SvStream
+ sal_uInt8 read(SvStream &stream);
+ //! sets the color
+ void setColor(Color col) { isColor = true; color = col; }
+ /** returns a color which can be "used" to replace the pattern,
+ * created from ForeColor and BackColor, ...
+ *
+ * note: maybe, we must also use some mode PatCopy, ... to define the color
+ */
+ Color getColor(Color bkColor, Color fgColor) const {
+ if (isColor) return color;
+ // we create a gray pattern from nBitCount
+ double alpha = nBitCount / 64.0;
+ return Color(sal_uInt8(alpha*fgColor.GetRed()+(1.0-alpha)*bkColor.GetRed()),
+ sal_uInt8(alpha*fgColor.GetGreen()+(1.0-alpha)*bkColor.GetGreen()),
+ sal_uInt8(alpha*fgColor.GetBlue()+(1.0-alpha)*bkColor.GetBlue()));
+ }
+
+ //! returns true if this is the default pattern
+ bool isDefault() const { return !isRead; }
+
+ enum PenStyle { PEN_NULL, PEN_SOLID, PEN_DOT, PEN_DASH, PEN_DASHDOT };
+ enum BrushStyle { BRUSH_SOLID, BRUSH_HORZ, BRUSH_VERT,
+ BRUSH_CROSS, BRUSH_DIAGCROSS, BRUSH_UPDIAG, BRUSH_DOWNDIAG,
+ BRUSH_25, BRUSH_50, BRUSH_75 };
+ // Data
+ enum PenStyle penStyle;
+ enum BrushStyle brushStyle;
+ short nBitCount;
+
+ bool isColor; // true if it is a color pattern
+ Color color;
+
+ protected:
+ // flag to know if the pattern came from reading the picture, or if it is the default pattern
+ bool isRead;
+ };
+
+ }
+
+ sal_uInt8 Pattern::read(SvStream &stream) {
+ unsigned char nbyte[8] = {0};
+ isColor = false;
+
+ // count the no of bits in pattern which are set to 1:
+ nBitCount=0;
+ for (unsigned char & ny : nbyte) {
+ stream.ReadChar( reinterpret_cast<char&>(ny) );
+ for (short nx=0; nx<8; nx++) {
+ if ( (ny & (1<<nx)) != 0 ) nBitCount++;
+ }
+ }
+
+ // store pattern in 2 long words:
+ sal_uInt32 nHiBytes = (((((static_cast<sal_uInt32>(nbyte[0])<<8)|
+ static_cast<sal_uInt32>(nbyte[1]))<<8)|
+ static_cast<sal_uInt32>(nbyte[2]))<<8)|
+ static_cast<sal_uInt32>(nbyte[3]);
+ sal_uInt32 nLoBytes = (((((static_cast<sal_uInt32>(nbyte[4])<<8)|
+ static_cast<sal_uInt32>(nbyte[5]))<<8)|
+ static_cast<sal_uInt32>(nbyte[6]))<<8)|
+ static_cast<sal_uInt32>(nbyte[7]);
+
+ // create a PenStyle:
+ if (nBitCount<=0) penStyle=PEN_NULL;
+ else if (nBitCount<=16) penStyle=PEN_DOT;
+ else if (nBitCount<=32) penStyle=PEN_DASHDOT;
+ else if (nBitCount<=48) penStyle=PEN_DASH;
+ else penStyle=PEN_SOLID;
+
+ // create a BrushStyle:
+ if (nHiBytes==0xffffffff && nLoBytes==0xffffffff) brushStyle=BRUSH_SOLID;
+ else if (nHiBytes==0xff000000 && nLoBytes==0x00000000) brushStyle=BRUSH_HORZ;
+ else if (nHiBytes==0x80808080 && nLoBytes==0x80808080) brushStyle=BRUSH_VERT;
+ else if (nHiBytes==0xff808080 && nLoBytes==0x80808080) brushStyle=BRUSH_CROSS;
+ else if (nHiBytes==0x01824428 && nLoBytes==0x10284482) brushStyle=BRUSH_DIAGCROSS;
+ else if (nHiBytes==0x80402010 && nLoBytes==0x08040201) brushStyle=BRUSH_UPDIAG;
+ else if (nHiBytes==0x01020408 && nLoBytes==0x10204080) brushStyle=BRUSH_DOWNDIAG;
+ else if (nBitCount<=24) brushStyle=BRUSH_25;
+ else if (nBitCount<=40) brushStyle=BRUSH_50;
+ else if (nBitCount<=56) brushStyle=BRUSH_75;
+ else brushStyle=BRUSH_SOLID;
+
+ isRead = true;
+
+ return 8;
+ }
+}
+
+//============================ PictReader ==================================
+
+namespace {
+
+enum class PictDrawingMethod {
+ FRAME, PAINT, ERASE, INVERT, FILL,
+ TEXT, UNDEFINED
+};
+
+class PictReader {
+ typedef class PictReaderInternal::Pattern Pattern;
+private:
+
+ SvStream * pPict; // The Pict file to read.
+ VclPtr<VirtualDevice> pVirDev; // Here the drawing method will be called.
+ // A recording into the GDIMetaFile will take place.
+
+ sal_uInt64 nOrigPos; // Initial position in pPict.
+ bool IsVersion2; // If it is a version 2 Pictfile.
+ tools::Rectangle aBoundingRect; // Min/Max-Rectangle for the whole drawing.
+
+ Point aPenPosition;
+ Point aTextPosition;
+ Color aActForeColor;
+ Color aActBackColor;
+ Pattern eActPenPattern;
+ Pattern eActFillPattern;
+ Pattern eActBackPattern;
+ Size nActPenSize;
+ // Note: Postscript mode is stored by setting eActRop to RasterOp::N1
+ RasterOp eActROP;
+ PictDrawingMethod eActMethod;
+ Size aActOvalSize;
+ vcl::Font aActFont;
+
+ Fraction aHRes;
+ Fraction aVRes;
+
+ Point ReadPoint();
+
+ Point ReadDeltaH(Point aBase);
+ Point ReadDeltaV(Point aBase);
+
+ Point ReadUnsignedDeltaH(Point aBase);
+ Point ReadUnsignedDeltaV(Point aBase);
+
+ Size ReadSize();
+
+ Color ReadColor();
+
+ Color ReadRGBColor();
+
+ void ReadRectangle(tools::Rectangle & rRect);
+
+ sal_uInt64 ReadPolygon(tools::Polygon & rPoly);
+
+ sal_uInt64 ReadPixPattern(Pattern &pattern);
+
+ tools::Rectangle aLastRect;
+ sal_uInt8 ReadAndDrawRect(PictDrawingMethod eMethod);
+ sal_uInt8 ReadAndDrawSameRect(PictDrawingMethod eMethod);
+
+ tools::Rectangle aLastRoundRect;
+ sal_uInt8 ReadAndDrawRoundRect(PictDrawingMethod eMethod);
+ sal_uInt8 ReadAndDrawSameRoundRect(PictDrawingMethod eMethod);
+
+ tools::Rectangle aLastOval;
+ sal_uInt8 ReadAndDrawOval(PictDrawingMethod eMethod);
+ sal_uInt8 ReadAndDrawSameOval(PictDrawingMethod eMethod);
+
+ tools::Polygon aLastPolygon;
+ sal_uInt64 ReadAndDrawPolygon(PictDrawingMethod eMethod);
+ sal_uInt8 ReadAndDrawSamePolygon(PictDrawingMethod eMethod);
+
+ tools::Rectangle aLastArcRect;
+ sal_uInt8 ReadAndDrawArc(PictDrawingMethod eMethod);
+ sal_uInt8 ReadAndDrawSameArc(PictDrawingMethod eMethod);
+
+ sal_uInt64 ReadAndDrawRgn(PictDrawingMethod eMethod);
+ sal_uInt8 ReadAndDrawSameRgn(PictDrawingMethod eMethod);
+
+ // returns true if there's no need to print the shape/text/frame
+ bool IsInvisible( PictDrawingMethod eMethod ) const {
+ if ( eActROP == RasterOp::N1 ) return true;
+ if ( eMethod == PictDrawingMethod::FRAME && nActPenSize.IsEmpty() ) return true;
+ return false;
+ }
+
+ void DrawingMethod(PictDrawingMethod eMethod);
+
+ sal_uInt64 ReadAndDrawText();
+
+ sal_uInt64 ReadPixMapEtc(BitmapEx & rBitmap, bool bBaseAddr, bool bColorTable,
+ tools::Rectangle * pSrcRect, tools::Rectangle * pDestRect,
+ bool bMode, bool bMaskRgn);
+
+ void ReadHeader();
+ // Reads the header of the Pict file, set IsVersion and aBoundingRect
+
+ sal_uInt64 ReadData(sal_uInt16 nOpcode);
+ // Reads the date of anOopcode and executes the operation.
+ // The number of data bytes belonging to the opcode will be returned
+ // in any case.
+
+ void SetLineColor( const Color& rColor );
+ void SetFillColor( const Color& rColor );
+
+ // OSNOLA: returns the text encoding which must be used for system id
+ static rtl_TextEncoding GetTextEncoding (sal_uInt16 fId = 0xFFFF);
+
+public:
+
+ PictReader()
+ : pPict(nullptr)
+ , pVirDev(nullptr)
+ , nOrigPos(0)
+ , IsVersion2(false)
+ , eActROP(RasterOp::OverPaint)
+ , eActMethod(PictDrawingMethod::UNDEFINED)
+ {
+ aActFont.SetCharSet(GetTextEncoding());
+ }
+
+ void ReadPict( SvStream & rStreamPict, GDIMetaFile & rGDIMetaFile );
+ // reads a pict file from the stream and fills the GDIMetaFile
+
+};
+
+}
+
+static void SetByte(sal_uInt16& nx, sal_uInt16 ny, vcl::bitmap::RawBitmap& rBitmap, sal_uInt16 nPixelSize, sal_uInt8 nDat, sal_uInt16 nWidth, std::vector<Color> const & rvPalette)
+{
+ switch (nPixelSize)
+ {
+ case 1:
+ rBitmap.SetPixel(ny, nx++, rvPalette[(nDat >> 7) & 1]);
+ if ( nx == nWidth ) break;
+ rBitmap.SetPixel(ny, nx++, rvPalette[(nDat >> 6) & 1]);
+ if ( nx == nWidth ) break;
+ rBitmap.SetPixel(ny, nx++, rvPalette[(nDat >> 5) & 1]);
+ if ( nx == nWidth ) break;
+ rBitmap.SetPixel(ny, nx++, rvPalette[(nDat >> 4) & 1]);
+ if ( nx == nWidth ) break;
+ rBitmap.SetPixel(ny, nx++, rvPalette[(nDat >> 3) & 1]);
+ if ( nx == nWidth ) break;
+ rBitmap.SetPixel(ny, nx++, rvPalette[(nDat >> 2) & 1]);
+ if ( nx == nWidth ) break;
+ rBitmap.SetPixel(ny, nx++, rvPalette[(nDat >> 1) & 1]);
+ if ( nx == nWidth ) break;
+ rBitmap.SetPixel(ny, nx++, rvPalette[nDat & 1]);
+ break;
+ case 2:
+ rBitmap.SetPixel(ny, nx++, rvPalette[nDat >> 6]);
+ if ( nx == nWidth ) break;
+ rBitmap.SetPixel(ny, nx++, rvPalette[(nDat>>4)&3]);
+ if ( nx == nWidth ) break;
+ rBitmap.SetPixel(ny, nx++, rvPalette[(nDat>>2)&3]);
+ if ( nx == nWidth ) break;
+ rBitmap.SetPixel(ny, nx++, rvPalette[nDat & 3]);
+ break;
+ case 4:
+ rBitmap.SetPixel(ny, nx++, rvPalette[nDat >> 4]);
+ if ( nx == nWidth ) break;
+ rBitmap.SetPixel(ny, nx++, rvPalette[nDat & 0x0f]);
+ break;
+ case 8:
+ rBitmap.SetPixel(ny, nx++, rvPalette[nDat]);
+ break;
+ }
+}
+
+//=================== methods of PictReader ==============================
+rtl_TextEncoding PictReader::GetTextEncoding (sal_uInt16 fId) {
+ static rtl_TextEncoding enc = []()
+ {
+ rtl_TextEncoding def = osl_getThreadTextEncoding();
+ // we keep osl_getThreadTextEncoding only if it is a mac encoding
+ switch(def) {
+ case RTL_TEXTENCODING_APPLE_ROMAN:
+ case RTL_TEXTENCODING_APPLE_ARABIC:
+ case RTL_TEXTENCODING_APPLE_CENTEURO:
+ case RTL_TEXTENCODING_APPLE_CROATIAN:
+ case RTL_TEXTENCODING_APPLE_CYRILLIC:
+ case RTL_TEXTENCODING_APPLE_DEVANAGARI:
+ case RTL_TEXTENCODING_APPLE_FARSI:
+ case RTL_TEXTENCODING_APPLE_GREEK:
+ case RTL_TEXTENCODING_APPLE_GUJARATI:
+ case RTL_TEXTENCODING_APPLE_GURMUKHI:
+ case RTL_TEXTENCODING_APPLE_HEBREW:
+ case RTL_TEXTENCODING_APPLE_ICELAND:
+ case RTL_TEXTENCODING_APPLE_ROMANIAN:
+ case RTL_TEXTENCODING_APPLE_THAI:
+ case RTL_TEXTENCODING_APPLE_TURKISH:
+ case RTL_TEXTENCODING_APPLE_UKRAINIAN:
+ case RTL_TEXTENCODING_APPLE_CHINSIMP:
+ case RTL_TEXTENCODING_APPLE_CHINTRAD:
+ case RTL_TEXTENCODING_APPLE_JAPANESE:
+ case RTL_TEXTENCODING_APPLE_KOREAN:
+ return def; break;
+ default:
+ break;
+ }
+ return RTL_TEXTENCODING_APPLE_ROMAN;
+ }();
+ if (fId == 13) return RTL_TEXTENCODING_ADOBE_DINGBATS; // CHECKME
+ if (fId == 23) return RTL_TEXTENCODING_ADOBE_SYMBOL;
+ return enc;
+}
+
+void PictReader::SetLineColor( const Color& rColor )
+{
+ pVirDev->SetLineColor( rColor );
+}
+
+void PictReader::SetFillColor( const Color& rColor )
+{
+ pVirDev->SetFillColor( rColor );
+}
+
+Point PictReader::ReadPoint()
+{
+ short nx(0), ny(0);
+
+ pPict->ReadInt16( ny ).ReadInt16( nx );
+
+ Point aPoint(nx - aBoundingRect.Left(), ny - aBoundingRect.Top());
+
+ SAL_INFO("filter.pict", "ReadPoint: " << aPoint);
+ return aPoint;
+}
+
+Point PictReader::ReadDeltaH(Point aBase)
+{
+ signed char ndh(0);
+
+ pPict->ReadChar( reinterpret_cast<char&>(ndh) );
+
+ return Point( aBase.X() + static_cast<tools::Long>(ndh), aBase.Y() );
+}
+
+Point PictReader::ReadDeltaV(Point aBase)
+{
+ signed char ndv(0);
+
+ pPict->ReadChar( reinterpret_cast<char&>(ndv) );
+
+ return Point( aBase.X(), aBase.Y() + static_cast<tools::Long>(ndv) );
+}
+
+Point PictReader::ReadUnsignedDeltaH(Point aBase)
+{
+ sal_uInt8 ndh(0);
+
+ pPict->ReadUChar( ndh );
+
+ return Point( aBase.X() + static_cast<tools::Long>(ndh), aBase.Y() );
+}
+
+Point PictReader::ReadUnsignedDeltaV(Point aBase)
+{
+ sal_uInt8 ndv(0);
+
+ pPict->ReadUChar( ndv );
+
+ return Point( aBase.X(), aBase.Y() + static_cast<tools::Long>(ndv) );
+}
+
+Size PictReader::ReadSize()
+{
+ short nx(0), ny(0);
+
+ pPict->ReadInt16( ny ).ReadInt16( nx );
+
+ return Size(nx, ny);
+}
+
+Color PictReader::ReadColor()
+{
+ Color aCol;
+
+ sal_uInt32 nCol(0);
+ pPict->ReadUInt32( nCol );
+ switch (nCol)
+ {
+ case 33: aCol=COL_BLACK; break;
+ case 30: aCol=COL_WHITE; break;
+ case 205: aCol=COL_LIGHTRED; break;
+ case 341: aCol=COL_LIGHTGREEN; break;
+ case 409: aCol=COL_LIGHTBLUE; break;
+ case 273: aCol=COL_LIGHTCYAN; break;
+ case 137: aCol=COL_LIGHTMAGENTA; break;
+ case 69: aCol=COL_YELLOW; break;
+ default: aCol=COL_LIGHTGRAY;
+ }
+ return aCol;
+}
+
+Color PictReader::ReadRGBColor()
+{
+ sal_uInt16 nR(0), nG(0), nB(0);
+
+ pPict->ReadUInt16( nR ).ReadUInt16( nG ).ReadUInt16( nB );
+ return Color( static_cast<sal_uInt8>( nR >> 8 ), static_cast<sal_uInt8>( nG >> 8 ), static_cast<sal_uInt8>( nB >> 8 ) );
+}
+
+void PictReader::ReadRectangle(tools::Rectangle & rRect)
+{
+ Point aTopLeft = ReadPoint();
+ Point aBottomRight = ReadPoint();
+ if (!pPict->good() || aTopLeft.X() > aBottomRight.X() || aTopLeft.Y() > aBottomRight.Y())
+ {
+ SAL_WARN("filter.pict", "broken rectangle");
+ pPict->SetError( SVSTREAM_FILEFORMAT_ERROR );
+ rRect = tools::Rectangle();
+ return;
+ }
+ rRect=tools::Rectangle(aTopLeft,aBottomRight);
+
+ SAL_INFO("filter.pict", "ReadRectangle: " << rRect);
+}
+
+sal_uInt64 PictReader::ReadPolygon(tools::Polygon & rPoly)
+{
+ sal_uInt16 nSize(0);
+ pPict->ReadUInt16(nSize);
+ pPict->SeekRel(8);
+ sal_uInt64 nDataSize = static_cast<sal_uInt64>(nSize);
+ nSize=(nSize-10)/4;
+ const size_t nMaxPossiblePoints = pPict->remainingSize() / 2 * sizeof(sal_uInt16);
+ if (nSize > nMaxPossiblePoints)
+ {
+ SAL_WARN("filter.pict", "pict record claims to have: " << nSize << " points, but only " << nMaxPossiblePoints << " possible, clamping");
+ nSize = nMaxPossiblePoints;
+ }
+ rPoly.SetSize(nSize);
+ for (sal_uInt16 i = 0; i < nSize; ++i)
+ {
+ rPoly.SetPoint(ReadPoint(), i);
+ if (!pPict->good())
+ {
+ rPoly.SetSize(i);
+ break;
+ }
+ }
+ return nDataSize;
+}
+
+sal_uInt64 PictReader::ReadPixPattern(PictReader::Pattern &pattern)
+{
+ // Don't know if this is correct because no picture which contains PixPatterns found.
+ // Here again the attempt to calculate the size of the date to create simple StarView-Styles
+ // from them. Luckily a PixPattern always contains a normal pattern.
+
+ sal_uInt64 nDataSize;
+
+ sal_uInt16 nPatType(0);
+ pPict->ReadUInt16(nPatType);
+ if (nPatType==1) {
+ pattern.read(*pPict);
+ BitmapEx aBMP;
+ nDataSize=ReadPixMapEtc(aBMP,false,true,nullptr,nullptr,false,false);
+ // CHANGEME: use average pixmap colors to update the pattern, ...
+ if (nDataSize!=0xffffffff) nDataSize+=10;
+ }
+ else if (nPatType==2) {
+ pattern.read(*pPict);
+ // RGBColor
+ sal_uInt16 nR, nG, nB;
+ pPict->ReadUInt16( nR ).ReadUInt16( nG ).ReadUInt16( nB );
+ Color col(static_cast<sal_uInt8>( nR >> 8 ), static_cast<sal_uInt8>( nG >> 8 ), static_cast<sal_uInt8>( nB >> 8 ) );
+ pattern.setColor(col);
+ nDataSize=16;
+ }
+ else nDataSize=0xffffffff;
+
+ return nDataSize;
+}
+
+sal_uInt8 PictReader::ReadAndDrawRect(PictDrawingMethod eMethod)
+{
+ ReadRectangle(aLastRect);
+ ReadAndDrawSameRect(eMethod);
+ return 8;
+}
+
+sal_uInt8 PictReader::ReadAndDrawSameRect(PictDrawingMethod eMethod)
+{
+ if (IsInvisible(eMethod)) return 0;
+ DrawingMethod(eMethod);
+ PictReaderShape::drawRectangle( pVirDev, eMethod == PictDrawingMethod::FRAME, aLastRect, nActPenSize );
+ return 0;
+}
+
+sal_uInt8 PictReader::ReadAndDrawRoundRect(PictDrawingMethod eMethod)
+{
+ ReadRectangle(aLastRoundRect);
+ ReadAndDrawSameRoundRect(eMethod);
+ return 8;
+}
+
+sal_uInt8 PictReader::ReadAndDrawSameRoundRect(PictDrawingMethod eMethod)
+{
+ if (IsInvisible(eMethod)) return 0;
+ DrawingMethod(eMethod);
+ PictReaderShape::drawRoundRectangle( pVirDev, eMethod == PictDrawingMethod::FRAME, aLastRoundRect, aActOvalSize, nActPenSize );
+ return 0;
+}
+
+sal_uInt8 PictReader::ReadAndDrawOval(PictDrawingMethod eMethod)
+{
+ ReadRectangle(aLastOval);
+ ReadAndDrawSameOval(eMethod);
+ return 8;
+}
+
+sal_uInt8 PictReader::ReadAndDrawSameOval(PictDrawingMethod eMethod)
+{
+ if (IsInvisible(eMethod)) return 0;
+ DrawingMethod(eMethod);
+ PictReaderShape::drawEllipse( pVirDev, eMethod == PictDrawingMethod::FRAME, aLastOval, nActPenSize );
+ return 0;
+}
+
+sal_uInt64 PictReader::ReadAndDrawPolygon(PictDrawingMethod eMethod)
+{
+ sal_uInt64 nDataSize;
+ nDataSize=ReadPolygon(aLastPolygon);
+ ReadAndDrawSamePolygon(eMethod);
+ return nDataSize;
+}
+
+sal_uInt8 PictReader::ReadAndDrawSamePolygon(PictDrawingMethod eMethod)
+{
+ if (IsInvisible(eMethod)) return 0;
+ DrawingMethod(eMethod);
+ PictReaderShape::drawPolygon( pVirDev, eMethod == PictDrawingMethod::FRAME, aLastPolygon, nActPenSize );
+ return 0;
+}
+
+
+sal_uInt8 PictReader::ReadAndDrawArc(PictDrawingMethod eMethod)
+{
+ ReadRectangle(aLastArcRect);
+ ReadAndDrawSameArc(eMethod);
+ return 12;
+}
+
+sal_uInt8 PictReader::ReadAndDrawSameArc(PictDrawingMethod eMethod)
+{
+ short nstartAngle, narcAngle;
+
+ pPict->ReadInt16( nstartAngle ).ReadInt16( narcAngle );
+ if (!pPict->good() || IsInvisible(eMethod)) return 4;
+ DrawingMethod(eMethod);
+
+ if (narcAngle<0) {
+ nstartAngle = nstartAngle + narcAngle;
+ narcAngle=-narcAngle;
+ }
+ const double pi = 2 * acos(0.0);
+ double fAng1 = static_cast<double>(nstartAngle) * pi / 180.0;
+ double fAng2 = static_cast<double>(nstartAngle + narcAngle) * pi / 180.0;
+ PictReaderShape::drawArc( pVirDev, eMethod == PictDrawingMethod::FRAME, aLastArcRect, fAng1, fAng2, nActPenSize );
+ return 4;
+}
+
+sal_uInt64 PictReader::ReadAndDrawRgn(PictDrawingMethod eMethod)
+{
+ sal_uInt16 nSize(0);
+ pPict->ReadUInt16( nSize );
+
+ // read the DATA
+ //
+ // a region data is a mask and is probably coded as
+ // - the first 8 bytes: bdbox ( which can be read by ReadRectangle )
+ // - then a list of line modifiers: y_i, a_0, b_0, a_1, b_1, ..., a_{n_i}, b_{n_i}, 0x7fff
+ // - 0x7fff
+ // where y_i is the increasing sequences of line coordinates
+ // and on each line: a0 < b0 < a1 < b1 < ... < a_{n_i} < b_{n_i}
+
+ // it can be probably decoded as :
+ // M=an empty mask: ie. (0, 0, ... ) with (left_box-right_box+1) zeroes
+ // then for each line (y_i):
+ // - takes M and inverts all values in [a_0,b_0-1], in [a_1,b_1-1] ...
+ // - sets M = new y_i line mask
+ ReadAndDrawSameRgn(eMethod);
+ return static_cast<sal_uInt64>(nSize);
+}
+
+sal_uInt8 PictReader::ReadAndDrawSameRgn(PictDrawingMethod eMethod)
+{
+ if (IsInvisible(eMethod)) return 0;
+ DrawingMethod(eMethod);
+ // DISPLAY: ...???...
+ return 0;
+}
+
+void PictReader::DrawingMethod(PictDrawingMethod eMethod)
+{
+ if( eActMethod==eMethod ) return;
+ switch (eMethod) {
+ case PictDrawingMethod::FRAME:
+ if (eActPenPattern.isDefault())
+ SetLineColor( aActForeColor );
+ else
+ SetLineColor(eActPenPattern.getColor(aActBackColor, aActForeColor));
+ SetFillColor( COL_TRANSPARENT );
+ pVirDev->SetRasterOp(eActROP);
+ break;
+ case PictDrawingMethod::PAINT:
+ SetLineColor( COL_TRANSPARENT );
+ if (eActPenPattern.isDefault())
+ SetFillColor( aActForeColor );
+ else
+ SetFillColor(eActPenPattern.getColor(aActBackColor, aActForeColor));
+ pVirDev->SetRasterOp(eActROP);
+ break;
+ case PictDrawingMethod::ERASE:
+ SetLineColor( COL_TRANSPARENT );
+ if (eActBackPattern.isDefault())
+ SetFillColor( aActBackColor );// Osnola: previously aActForeColor
+ else // checkMe
+ SetFillColor(eActBackPattern.getColor(COL_BLACK, aActBackColor));
+ pVirDev->SetRasterOp(RasterOp::OverPaint);
+ break;
+ case PictDrawingMethod::INVERT: // checkme
+ SetLineColor( COL_TRANSPARENT);
+ SetFillColor( COL_BLACK );
+ pVirDev->SetRasterOp(RasterOp::Invert);
+ break;
+ case PictDrawingMethod::FILL:
+ SetLineColor( COL_TRANSPARENT );
+ if (eActFillPattern.isDefault())
+ SetFillColor( aActForeColor );
+ else
+ SetFillColor(eActFillPattern.getColor(aActBackColor, aActForeColor));
+ pVirDev->SetRasterOp(RasterOp::OverPaint);
+ break;
+ case PictDrawingMethod::TEXT:
+ aActFont.SetColor(aActForeColor);
+ aActFont.SetFillColor(aActBackColor);
+ aActFont.SetTransparent(true);
+ pVirDev->SetFont(aActFont);
+ pVirDev->SetRasterOp(RasterOp::OverPaint);
+ break;
+ default:
+ break; // -Wall undefined not handled...
+ }
+ eActMethod=eMethod;
+}
+
+sal_uInt64 PictReader::ReadAndDrawText()
+{
+ char sText[256];
+
+ char nByteLen(0);
+ pPict->ReadChar(nByteLen);
+ sal_uInt32 nLen = static_cast<sal_uInt32>(nByteLen)&0x000000ff;
+ sal_uInt32 nDataLen = nLen + 1;
+ nLen = pPict->ReadBytes(&sText, nLen);
+
+ if (IsInvisible( PictDrawingMethod::TEXT )) return nDataLen;
+ DrawingMethod( PictDrawingMethod::TEXT );
+
+ // remove annoying control characters:
+ while ( nLen > 0 && static_cast<unsigned char>(sText[ nLen - 1 ]) < 32 )
+ nLen--;
+ sText[ nLen ] = 0;
+ OUString aString( sText, strlen(sText), aActFont.GetCharSet());
+ pVirDev->DrawText( Point( aTextPosition.X(), aTextPosition.Y() ), aString );
+ return nDataLen;
+}
+
+sal_uInt64 PictReader::ReadPixMapEtc( BitmapEx &rBitmap, bool bBaseAddr, bool bColorTable, tools::Rectangle* pSrcRect,
+ tools::Rectangle* pDestRect, bool bMode, bool bMaskRgn )
+{
+ std::unique_ptr<vcl::bitmap::RawBitmap> pBitmap;
+ sal_uInt16 nPackType(0), nPixelSize(0), nCmpCount(0), nCmpSize(0);
+ sal_uInt8 nDat(0), nRed(0), nGreen(0), nBlue(0);
+
+ // The calculation of nDataSize is considering the size of the whole data.
+ size_t nDataSize = 0;
+
+ // conditionally skip BaseAddr
+ if ( bBaseAddr )
+ {
+ pPict->SeekRel( 4 );
+ nDataSize += 4;
+ }
+
+ // Read PixMap or Bitmap structure;
+ sal_uInt16 nRowBytes(0), nBndX(0), nBndY(0), nWidth(0), nHeight(0);
+ pPict->ReadUInt16(nRowBytes).ReadUInt16(nBndY).ReadUInt16(nBndX).ReadUInt16(nHeight).ReadUInt16(nWidth);
+ if (nBndY > nHeight)
+ return 0xffffffff;
+ nHeight = nHeight - nBndY;
+ if (nHeight == 0)
+ return 0xffffffff;
+ if (nBndX > nWidth)
+ return 0xffffffff;
+ nWidth = nWidth - nBndX;
+ if (nWidth == 0)
+ return 0xffffffff;
+
+ std::vector<Color> aPalette;
+ const bool bNotMonoChrome = (nRowBytes & 0x8000) != 0;
+ if (bNotMonoChrome)
+ { // it is a PixMap
+ nRowBytes &= 0x3fff;
+ sal_uInt16 nVersion;
+ sal_uInt32 nPackSize;
+ sal_uInt16 nPixelType;
+ sal_uInt32 nPlaneBytes;
+ sal_uInt32 nHRes, nVRes;
+ pPict->ReadUInt16( nVersion ).ReadUInt16( nPackType ).ReadUInt32( nPackSize ).ReadUInt32( nHRes ).ReadUInt32( nVRes ).ReadUInt16( nPixelType ).ReadUInt16( nPixelSize ).ReadUInt16( nCmpCount ).ReadUInt16( nCmpSize ).ReadUInt32( nPlaneBytes );
+
+ pPict->SeekRel( 8 );
+ nDataSize += 46;
+
+ if ( bColorTable )
+ {
+ pPict->SeekRel( 6 );
+ sal_uInt16 nColTabSize(0);
+ pPict->ReadUInt16(nColTabSize);
+
+ if (nColTabSize > 255)
+ return 0xffffffff;
+
+ ++nColTabSize;
+
+ aPalette.resize(nColTabSize);
+
+ for (size_t i = 0; i < nColTabSize; ++i)
+ {
+ pPict->SeekRel(2);
+ sal_uInt8 nDummy;
+ pPict->ReadUChar( nRed ).ReadUChar( nDummy ).ReadUChar( nGreen ).ReadUChar( nDummy ).ReadUChar( nBlue ).ReadUChar( nDummy );
+ aPalette[i] = Color(nRed, nGreen, nBlue);
+ }
+
+ nDataSize += 8 + nColTabSize * 8;
+ }
+ }
+ else
+ {
+ nRowBytes &= 0x3fff;
+ nPixelSize = nCmpCount = nCmpSize = 1;
+ nDataSize += 10;
+ aPalette.resize(2);
+ aPalette[0] = Color(0xff, 0xff, 0xff);
+ aPalette[1] = Color(0, 0, 0);
+ }
+
+ // conditionally read source rectangle:
+ if ( pSrcRect != nullptr)
+ {
+ sal_uInt16 nTop, nLeft, nBottom, nRight;
+ pPict->ReadUInt16( nTop ).ReadUInt16( nLeft ).ReadUInt16( nBottom ).ReadUInt16( nRight );
+ *pSrcRect = tools::Rectangle(nLeft, nTop, nRight, nBottom);
+ nDataSize += 8;
+ }
+
+ // conditionally read destination rectangle:
+ if ( pDestRect != nullptr )
+ {
+ Point aTL = ReadPoint();
+ Point aBR = ReadPoint();
+ *pDestRect = tools::Rectangle( aTL, aBR );
+ nDataSize += 8;
+ }
+
+ // conditionally read mode (or skip it):
+ if ( bMode )
+ {
+ pPict->SeekRel(2);
+ nDataSize += 2;
+ }
+
+ // conditionally read region (or skip it):
+ if ( bMaskRgn )
+ {
+ sal_uInt16 nSize(0);
+ pPict->ReadUInt16( nSize );
+ pPict->SeekRel( nSize - 2 );
+ nDataSize += nSize;
+ }
+
+ // read and write Bitmap bits:
+ if ( nPixelSize == 1 || nPixelSize == 2 || nPixelSize == 4 || nPixelSize == 8 )
+ {
+ sal_uInt16 nSrcBPL, nDestBPL;
+ size_t nCount;
+
+ if ( nPixelSize == 1 ) nSrcBPL = ( nWidth + 7 ) >> 3;
+ else if ( nPixelSize == 2 ) nSrcBPL = ( nWidth + 3 ) >> 2;
+ else if ( nPixelSize == 4 ) nSrcBPL = ( nWidth + 1 ) >> 1;
+ else nSrcBPL = nWidth;
+ nDestBPL = ( nSrcBPL + 3 ) & 0xfffc;
+ if (!nRowBytes || nRowBytes < nSrcBPL || nRowBytes > nDestBPL)
+ return 0xffffffff;
+
+ if (nRowBytes < 8 || nPackType == 1)
+ {
+ if (nHeight > pPict->remainingSize() / (sizeof(sal_uInt8) * nRowBytes))
+ return 0xffffffff;
+ }
+ else
+ {
+ size_t nByteCountSize = nRowBytes > 250 ? sizeof(sal_uInt16) : sizeof(sal_uInt8);
+ if (nHeight > pPict->remainingSize() / nByteCountSize)
+ return 0xffffffff;
+ }
+
+ pBitmap.reset(new vcl::bitmap::RawBitmap( Size(nWidth, nHeight), 24 ));
+
+ for (sal_uInt16 ny = 0; ny < nHeight; ++ny)
+ {
+ sal_uInt16 nx = 0;
+ if ( nRowBytes < 8 || nPackType == 1 )
+ {
+ for (size_t i = 0; i < nRowBytes; ++i)
+ {
+ pPict->ReadUChar( nDat );
+ if ( nx < nWidth )
+ SetByte(nx, ny, *pBitmap, nPixelSize, nDat, nWidth, aPalette);
+ }
+ nDataSize += nRowBytes;
+ }
+ else
+ {
+ sal_uInt16 nByteCount(0);
+ if ( nRowBytes > 250 )
+ {
+ pPict->ReadUInt16( nByteCount );
+ nDataSize += 2 + static_cast<sal_uInt32>(nByteCount);
+ }
+ else
+ {
+ sal_uInt8 nByteCountAsByte(0);
+ pPict->ReadUChar( nByteCountAsByte );
+ nByteCount = static_cast<sal_uInt16>(nByteCountAsByte) & 0x00ff;
+ nDataSize += 1 + nByteCount;
+ }
+
+ while (pPict->good() && nByteCount)
+ {
+ sal_uInt8 nFlagCounterByte(0);
+ pPict->ReadUChar(nFlagCounterByte);
+ if ( ( nFlagCounterByte & 0x80 ) == 0 )
+ {
+ nCount = static_cast<sal_uInt16>(nFlagCounterByte) + 1;
+ for (size_t i = 0; i < nCount; ++i)
+ {
+ pPict->ReadUChar( nDat );
+ if ( nx < nWidth )
+ SetByte(nx, ny, *pBitmap, nPixelSize, nDat, nWidth, aPalette);
+ }
+ nByteCount -= 1 + nCount;
+ }
+ else
+ {
+ nCount = static_cast<sal_uInt16>( 1 - sal_Int16( static_cast<sal_uInt16>(nFlagCounterByte) | 0xff00 ) );
+ pPict->ReadUChar( nDat );
+ for (size_t i = 0; i < nCount; ++i)
+ {
+ if ( nx < nWidth )
+ SetByte(nx, ny, *pBitmap, nPixelSize, nDat, nWidth, aPalette);
+ }
+ nByteCount -= 2;
+ }
+ }
+ }
+ }
+ }
+ else if ( nPixelSize == 16 )
+ {
+ sal_uInt8 nByteCountAsByte, nFlagCounterByte;
+ sal_uInt16 nByteCount, nCount, nD;
+ sal_uInt64 nSrcBitsPos;
+
+ if (nWidth > nRowBytes / 2)
+ return 0xffffffff;
+
+ if (nRowBytes < 8 || nPackType == 1)
+ {
+ if (nHeight > pPict->remainingSize() / (sizeof(sal_uInt8) * nRowBytes))
+ return 0xffffffff;
+ }
+ else
+ {
+ size_t nByteCountSize = nRowBytes > 250 ? sizeof(sal_uInt16) : sizeof(sal_uInt8);
+ if (nHeight > pPict->remainingSize() / nByteCountSize)
+ return 0xffffffff;
+ }
+
+ pBitmap.reset(new vcl::bitmap::RawBitmap( Size(nWidth, nHeight), 24 ));
+
+ for (sal_uInt16 ny = 0; ny < nHeight; ++ny)
+ {
+ sal_uInt16 nx = 0;
+ if ( nRowBytes < 8 || nPackType == 1 )
+ {
+ for (size_t i = 0; i < nWidth; ++i)
+ {
+ pPict->ReadUInt16( nD );
+ nRed = static_cast<sal_uInt8>( nD >> 7 );
+ nGreen = static_cast<sal_uInt8>( nD >> 2 );
+ nBlue = static_cast<sal_uInt8>( nD << 3 );
+ pBitmap->SetPixel(ny, nx++, Color(nRed, nGreen, nBlue));
+ }
+ nDataSize += static_cast<sal_uInt32>(nWidth) * 2;
+ }
+ else
+ {
+ nSrcBitsPos = pPict->Tell();
+ if ( nRowBytes > 250 )
+ {
+ pPict->ReadUInt16( nByteCount );
+ nByteCount += 2;
+ }
+ else
+ {
+ pPict->ReadUChar( nByteCountAsByte );
+ nByteCount = static_cast<sal_uInt16>(nByteCountAsByte) & 0x00ff;
+ nByteCount++;
+ }
+ while ( nx != nWidth )
+ {
+ pPict->ReadUChar( nFlagCounterByte );
+ if ( (nFlagCounterByte & 0x80) == 0)
+ {
+ nCount=static_cast<sal_uInt16>(nFlagCounterByte)+1;
+ if ( nCount + nx > nWidth)
+ nCount = nWidth - nx;
+ if (pPict->remainingSize() < sizeof(sal_uInt16) * nCount)
+ return 0xffffffff;
+ /* SJ: the RLE decoding seems not to be correct here,
+ I don't want to change this until I have a bugdoc for
+ this case. Have a look at 32bit, there I changed the
+ encoding, so that it is used a straight forward array
+ */
+ for (size_t i = 0; i < nCount; ++i)
+ {
+ pPict->ReadUInt16( nD );
+ nRed = static_cast<sal_uInt8>( nD >> 7 );
+ nGreen = static_cast<sal_uInt8>( nD >> 2 );
+ nBlue = static_cast<sal_uInt8>( nD << 3 );
+ pBitmap->SetPixel(ny, nx++, Color(nRed, nGreen, nBlue));
+ }
+ }
+ else
+ {
+ if (pPict->remainingSize() < sizeof(sal_uInt16))
+ return 0xffffffff;
+ nCount=(1-sal_Int16(static_cast<sal_uInt16>(nFlagCounterByte)|0xff00));
+ if ( nCount + nx > nWidth )
+ nCount = nWidth - nx;
+ pPict->ReadUInt16( nD );
+ nRed = static_cast<sal_uInt8>( nD >> 7 );
+ nGreen = static_cast<sal_uInt8>( nD >> 2 );
+ nBlue = static_cast<sal_uInt8>( nD << 3 );
+ for (size_t i = 0; i < nCount; ++i)
+ {
+ pBitmap->SetPixel(ny, nx++, Color(nRed, nGreen, nBlue));
+ }
+ }
+ }
+ nDataSize += nByteCount;
+ pPict->Seek(nSrcBitsPos+nByteCount);
+ }
+ }
+ }
+ else if ( nPixelSize == 32 )
+ {
+ sal_uInt16 nByteCount;
+ size_t nCount;
+ sal_uInt64 nSrcBitsPos;
+ if ( nRowBytes != 4*nWidth )
+ return 0xffffffff;
+
+ if ( nRowBytes < 8 || nPackType == 1 )
+ {
+ const size_t nMaxPixels = pPict->remainingSize() / 4;
+ const size_t nMaxRows = nMaxPixels / nWidth;
+ if (nHeight > nMaxRows)
+ return 0xffffffff;
+ const size_t nMaxCols = nMaxPixels / nHeight;
+ if (nWidth > nMaxCols)
+ return 0xffffffff;
+
+ pBitmap.reset(new vcl::bitmap::RawBitmap( Size(nWidth, nHeight), 24 ));
+
+ for (sal_uInt16 ny = 0; ny < nHeight; ++ny)
+ {
+ for (sal_uInt16 nx = 0; nx < nWidth; ++nx)
+ {
+ sal_uInt8 nDummy;
+ pPict->ReadUChar( nDummy ).ReadUChar( nRed ).ReadUChar( nGreen ).ReadUChar( nBlue );
+ pBitmap->SetPixel(ny, nx, Color(nRed, nGreen, nBlue));
+ }
+ nDataSize += static_cast<sal_uInt32>(nWidth) * 4;
+ }
+ }
+ else if ( nPackType == 2 )
+ {
+ const size_t nMaxPixels = pPict->remainingSize() / 3;
+ const size_t nMaxRows = nMaxPixels / nWidth;
+ if (nHeight > nMaxRows)
+ return 0xffffffff;
+ const size_t nMaxCols = nMaxPixels / nHeight;
+ if (nWidth > nMaxCols)
+ return 0xffffffff;
+
+ pBitmap.reset(new vcl::bitmap::RawBitmap( Size(nWidth, nHeight), 24 ));
+
+ for (sal_uInt16 ny = 0; ny < nHeight; ++ny)
+ {
+ for (sal_uInt16 nx = 0; nx < nWidth; ++nx)
+ {
+ pPict->ReadUChar( nRed ).ReadUChar( nGreen ).ReadUChar( nBlue );
+ pBitmap->SetPixel(ny, nx, Color(nRed, nGreen, nBlue));
+ }
+ nDataSize += static_cast<sal_uInt32>(nWidth) * 3;
+ }
+ }
+ else
+ {
+ sal_uInt8 nByteCountAsByte;
+ sal_uInt8 nFlagCounterByte;
+ if ( ( nCmpCount == 3 ) || ( nCmpCount == 4 ) )
+ {
+ size_t nByteCountSize = nRowBytes > 250 ? sizeof(sal_uInt16) : sizeof(sal_uInt8);
+ if (nHeight > pPict->remainingSize() / nByteCountSize)
+ return 0xffffffff;
+
+ pBitmap.reset(new vcl::bitmap::RawBitmap( Size(nWidth, nHeight), 24 ));
+
+ // cid#1458434 to sanitize Untrusted loop bound
+ nWidth = pBitmap->Width();
+
+ size_t nByteWidth = static_cast<size_t>(nWidth) * nCmpCount;
+ std::vector<sal_uInt8> aScanline(nByteWidth);
+ for (sal_uInt16 ny = 0; ny < nHeight; ++ny)
+ {
+ nSrcBitsPos = pPict->Tell();
+ if ( nRowBytes > 250 )
+ {
+ pPict->ReadUInt16( nByteCount );
+ nByteCount += 2;
+ }
+ else
+ {
+ pPict->ReadUChar( nByteCountAsByte );
+ nByteCount = nByteCountAsByte;
+ nByteCount++;
+ }
+ size_t i = 0;
+ while (i < aScanline.size())
+ {
+ pPict->ReadUChar( nFlagCounterByte );
+ if ( ( nFlagCounterByte & 0x80 ) == 0)
+ {
+ nCount = static_cast<sal_uInt16>(nFlagCounterByte) + 1;
+ if ((i + nCount) > aScanline.size())
+ nCount = aScanline.size() - i;
+ if (pPict->remainingSize() < nCount)
+ return 0xffffffff;
+ while( nCount-- )
+ {
+ pPict->ReadUChar( nDat );
+ aScanline[ i++ ] = nDat;
+ }
+ }
+ else
+ {
+ if (pPict->remainingSize() < 1)
+ return 0xffffffff;
+ nCount = ( 1 - sal_Int16( static_cast<sal_uInt16>(nFlagCounterByte) | 0xff00 ) );
+ if (( i + nCount) > aScanline.size())
+ nCount = aScanline.size() - i;
+ pPict->ReadUChar( nDat );
+ while( nCount-- )
+ aScanline[ i++ ] = nDat;
+ }
+ }
+ sal_uInt8* pTmp = aScanline.data();
+ if ( nCmpCount == 4 )
+ pTmp += nWidth;
+ for (sal_uInt16 nx = 0; nx < nWidth; pTmp++)
+ pBitmap->SetPixel(ny, nx++, Color(*pTmp, pTmp[ nWidth ], pTmp[ 2 * nWidth ]));
+ nDataSize += nByteCount;
+ pPict->Seek( nSrcBitsPos + nByteCount );
+ }
+ }
+ }
+ }
+ else
+ return 0xffffffff;
+ rBitmap = vcl::bitmap::CreateFromData(std::move(*pBitmap));
+ return nDataSize;
+}
+
+void PictReader::ReadHeader()
+{
+ short y1,x1,y2,x2;
+
+ char sBuf[ 2 ];
+ // previous code considers pPict->Tell() as the normal starting position,
+ // nStartPos can be != 0 f.e. a pict embedded in a microsoft word document
+ sal_uInt64 nStartPos = pPict->Tell();
+ // Standard:
+ // a picture file begins by 512 bytes (reserved to the application) followed by the picture data
+ // while clipboard, pictures stored in a document often contain only the picture data.
+
+ // Special cases:
+ // - some Pict v.1 use 0x00 0x11 0x01 ( instead of 0x11 0x01) to store the version op
+ // (we consider here this as another standard for Pict. v.1 )
+ // - some files seem to contain extra garbage data at the beginning
+ // - some picture data seem to contain extra NOP opcode(0x00) between the bounding box and the version opcode
+
+ // This code looks hard to find a picture header, ie. it looks at positions
+ // - nStartPos+0, nStartPos+512 with potential extra NOP codes between bdbox and version (at most 9 extra NOP)
+ // - 512..1024 with more strict bdbox checking and no extra NOP codes
+
+ // Notes:
+ // - if the header can begin at nStartPos+0 and at nStartPos+512, we try to choose the more
+ // <<probable>> ( using the variable confidence)
+ // - svtools/source/filter.vcl/filter/{filter.cxx,filter2.cxx} only check for standard Pict,
+ // this may cause future problems
+ int st;
+ sal_uInt32 nOffset;
+ int confidence[2] = { 0, 0};
+ for ( st = 0; st < 3 + 513; st++ )
+ {
+ int actualConfid = 20; // the actual confidence
+ pPict->ResetError();
+ if (st < 2) nOffset = nStartPos+st*512;
+ else if (st == 2) {
+ // choose nStartPos+0 or nStartPos+512 even if there are a little dubious
+ int actPos = -1, actConf=0;
+ if (confidence[0] > 0) { actPos = 0; actConf = confidence[0]; }
+ if (confidence[1] > 0 && confidence[1] >= actConf) actPos = 1;
+ if (actPos < 0) continue;
+ nOffset = nStartPos+actPos*512;
+ }
+ else {
+ nOffset = nStartPos+509+st;
+ // a small test to check if versionOp code exists after the bdbox ( with no extra NOP codes)
+ pPict->Seek(nOffset+10);
+ pPict->ReadBytes(sBuf, 2);
+ if (!pPict->good()) break;
+ if (sBuf[0] == 0x11 || (sBuf[0] == 0x00 && sBuf[1] == 0x11)) ; // maybe ok
+ else continue;
+ }
+ pPict->Seek(nOffset);
+
+ // 2 bytes to store size ( version 1 ) ignored
+ pPict->SeekRel( 2 );
+ pPict->ReadInt16( y1 ).ReadInt16( x1 ).ReadInt16( y2 ).ReadInt16( x2 ); // frame rectangle of the picture
+ if (x1 > x2 || y1 > y2) continue; // bad bdbox
+ if (x1 < -2048 || x2 > 2048 || y1 < -2048 || y2 > 2048 || // origin|dest is very small|large
+ (x1 == x2 && y1 == y2) ) // 1 pixel pict is dubious
+ actualConfid-=3;
+ else if (x2 < x1+8 || y2 < y1+8) // a little dubious
+ actualConfid-=1;
+ if (st >= 3 && actualConfid != 20) continue;
+ aBoundingRect=tools::Rectangle( x1,y1, x2, y2 );
+
+ if (!pPict->good()) continue;
+ // read version
+ pPict->ReadBytes(sBuf, 2);
+ // version 1 file
+ if ( sBuf[ 0 ] == 0x11 && sBuf[ 1 ] == 0x01 ) {
+ // pict v1 must be rare and we do only few tests
+ if (st < 2) { confidence[st] = --actualConfid; continue; }
+ IsVersion2 = false; return;
+ }
+ if (sBuf[0] != 0x00) continue; // unrecoverable error
+ int numZero = 0;
+ do
+ {
+ numZero++;
+ pPict->SeekRel(-1);
+ pPict->ReadBytes(sBuf, 2);
+ }
+ while ( sBuf[0] == 0x00 && numZero < 10);
+ actualConfid -= (numZero-1); // extra nop are dubious
+ if (!pPict->good()) continue;
+ if (sBuf[0] != 0x11) continue; // not a version opcode
+ // abnormal version 1 file
+ if (sBuf[1] == 0x01 ) {
+ // pict v1 must be rare and we do only few tests
+ if (st < 2) { confidence[st] = --actualConfid; continue; }
+ IsVersion2 = false; return;
+ }
+ if (sBuf[1] != 0x02 ) continue; // not a version 2 file
+
+ IsVersion2=true;
+ short nExtVer, nReserved;
+ // 3 Bytes ignored : end of version arg 0x02FF (ie: 0xFF), HeaderOp : 0x0C00
+ pPict->SeekRel( 3 );
+ pPict->ReadInt16( nExtVer ).ReadInt16( nReserved );
+ if (!pPict->good()) continue;
+
+ if ( nExtVer == -2 ) // extended version 2 picture
+ {
+ sal_Int32 nHResFixed, nVResFixed;
+ pPict->ReadInt32( nHResFixed ).ReadInt32( nVResFixed );
+ pPict->ReadInt16( y1 ).ReadInt16( x1 ).ReadInt16( y2 ).ReadInt16( x2 ); // reading the optimal bounding rect
+ if (x1 > x2 || y1 > y2) continue; // bad bdbox
+ if (st < 2 && actualConfid != 20) { confidence[st] = actualConfid; continue; }
+
+ double fHRes = nHResFixed;
+ fHRes /= 65536;
+ double fVRes = nVResFixed;
+ fVRes /= 65536;
+ aHRes /= fHRes;
+ aVRes /= fVRes;
+ aBoundingRect=tools::Rectangle( x1,y1, x2, y2 );
+ pPict->SeekRel( 4 ); // 4 bytes reserved
+ return;
+ }
+ else if (nExtVer == -1 ) { // basic version 2 picture
+ if (st < 2 && actualConfid != 20) { confidence[st] = actualConfid; continue; }
+ pPict->SeekRel( 16); // bdbox(4 fixed number)
+ pPict->SeekRel(4); // 4 bytes reserved
+ return;
+ }
+ }
+ pPict->SetError(SVSTREAM_FILEFORMAT_ERROR);
+}
+
+#if OSL_DEBUG_LEVEL > 0
+static const char* operationName(sal_uInt16 nOpcode)
+{
+ // add here whatever makes the debugging easier for you, otherwise you'll
+ // see only the operation's opcode
+ switch (nOpcode)
+ {
+ case 0x0001: return "Clip";
+ case 0x0003: return "TxFont";
+ case 0x0004: return "TxFace";
+ case 0x0008: return "PnMode";
+ case 0x0009: return "PnPat";
+ case 0x000d: return "TxSize";
+ case 0x001a: return "RGBFgCol";
+ case 0x001d: return "HiliteColor";
+ case 0x0020: return "Line";
+ case 0x0022: return "ShortLine";
+ case 0x0028: return "LongText";
+ case 0x0029: return "DHText";
+ case 0x002a: return "DVText";
+ case 0x002c: return "fontName";
+ case 0x002e: return "glyphState";
+ case 0x0031: return "paintRect";
+ case 0x0038: return "frameSameRect";
+ case 0x0070: return "framePoly";
+ case 0x0071: return "paintPoly";
+ case 0x00a1: return "LongComment";
+ default: return "?";
+ }
+}
+#endif
+
+sal_uInt64 PictReader::ReadData(sal_uInt16 nOpcode)
+{
+ Point aPoint;
+ sal_uInt64 nDataSize=0;
+ PictDrawingMethod shapeDMethod = PictDrawingMethod::UNDEFINED;
+ switch (nOpcode & 7) {
+ case 0: shapeDMethod = PictDrawingMethod::FRAME; break;
+ case 1: shapeDMethod = PictDrawingMethod::PAINT; break;
+ case 2: shapeDMethod = PictDrawingMethod::ERASE; break;
+ case 3: shapeDMethod = PictDrawingMethod::INVERT; break;
+ case 4: shapeDMethod = PictDrawingMethod::FILL; break;
+ default: break;
+ }
+
+#if OSL_DEBUG_LEVEL > 0
+ SAL_INFO("filter.pict", "Operation: 0x" << OUString::number(nOpcode, 16) << " [" << operationName(nOpcode) << "]");
+#endif
+
+ switch(nOpcode) {
+
+ case 0x0000: // NOP
+ nDataSize=0;
+ break;
+
+ case 0x0001: { // Clip
+ sal_uInt16 nUSHORT(0);
+ tools::Rectangle aRect;
+ pPict->ReadUInt16( nUSHORT );
+ nDataSize=nUSHORT;
+ ReadRectangle(aRect);
+ // checkme: do we really want to extend the rectangle here ?
+ // I do that because the clipping is often used to clean a region,
+ // before drawing some text and also to draw this text.
+ // So using a too small region can lead to clip the end of the text ;
+ // but this can be discussable...
+ aRect.setWidth(aRect.getWidth()+1);
+ aRect.setHeight(aRect.getHeight()+1);
+ pVirDev->SetClipRegion( vcl::Region( aRect ) );
+ break;
+ }
+ case 0x0002: // BkPat
+ nDataSize = eActBackPattern.read(*pPict);
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ break;
+
+ case 0x0003: // TxFont
+ {
+ sal_uInt16 nUSHORT(0);
+ pPict->ReadUInt16( nUSHORT );
+ if (nUSHORT <= 1) aActFont.SetFamily(FAMILY_SWISS);
+ else if (nUSHORT <= 12) aActFont.SetFamily(FAMILY_DECORATIVE);
+ else if (nUSHORT <= 20) aActFont.SetFamily(FAMILY_ROMAN);
+ else if (nUSHORT == 21) aActFont.SetFamily(FAMILY_SWISS);
+ else if (nUSHORT == 22) aActFont.SetFamily(FAMILY_MODERN);
+ else if (nUSHORT <= 1023) aActFont.SetFamily(FAMILY_SWISS);
+ else aActFont.SetFamily(FAMILY_ROMAN);
+ aActFont.SetCharSet(GetTextEncoding(nUSHORT));
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ nDataSize=2;
+ break;
+ }
+ case 0x0004: { // TxFace
+ char nFace(0);
+ pPict->ReadChar( nFace );
+ if ( (nFace & 0x01)!=0 ) aActFont.SetWeight(WEIGHT_BOLD);
+ else aActFont.SetWeight(WEIGHT_NORMAL);
+ if ( (nFace & 0x02)!=0 ) aActFont.SetItalic(ITALIC_NORMAL);
+ else aActFont.SetItalic(ITALIC_NONE);
+ if ( (nFace & 0x04)!=0 ) aActFont.SetUnderline(LINESTYLE_SINGLE);
+ else aActFont.SetUnderline(LINESTYLE_NONE);
+ if ( (nFace & 0x08)!=0 ) aActFont.SetOutline(true);
+ else aActFont.SetOutline(false);
+ if ( (nFace & 0x10)!=0 ) aActFont.SetShadow(true);
+ else aActFont.SetShadow(false);
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ nDataSize=1;
+ break;
+ }
+ case 0x0005: // TxMode
+ nDataSize=2;
+ break;
+
+ case 0x0006: // SpExtra
+ nDataSize=4;
+ break;
+
+ case 0x0007: { // PnSize
+ nActPenSize=ReadSize();
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ nDataSize=4;
+ break;
+ }
+ case 0x0008: // PnMode
+ {
+ sal_uInt16 nUSHORT(0);
+ pPict->ReadUInt16( nUSHORT );
+ // internal code for postscript command (Quickdraw Reference Drawing B-30,B-34)
+ if (nUSHORT==23) eActROP = RasterOp::N1;
+ else {
+ switch (nUSHORT & 0x0007) {
+ case 0: eActROP=RasterOp::OverPaint; break; // Copy
+ case 1: eActROP=RasterOp::OverPaint; break; // Or
+ case 2: eActROP=RasterOp::Xor; break; // Xor
+ case 3: eActROP=RasterOp::OverPaint; break; // Bic
+ case 4: eActROP=RasterOp::Invert; break; // notCopy
+ case 5: eActROP=RasterOp::OverPaint; break; // notOr
+ case 6: eActROP=RasterOp::Xor; break; // notXor
+ case 7: eActROP=RasterOp::OverPaint; break; // notBic
+ }
+ }
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ nDataSize=2;
+ break;
+ }
+ case 0x0009: // PnPat
+ nDataSize=eActPenPattern.read(*pPict);
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ break;
+
+ case 0x000a: // FillPat
+ nDataSize=eActFillPattern.read(*pPict);
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ break;
+
+ case 0x000b: // OvSize
+ aActOvalSize=ReadSize();
+ nDataSize=4;
+ break;
+
+ case 0x000c: // Origin
+ nDataSize=4;
+ break;
+
+ case 0x000d: // TxSize
+ {
+ sal_uInt16 nUSHORT(0);
+ pPict->ReadUInt16( nUSHORT );
+ aActFont.SetFontSize( Size( 0, static_cast<tools::Long>(nUSHORT) ) );
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ nDataSize=2;
+ }
+ break;
+
+ case 0x000e: // FgColor
+ aActForeColor=ReadColor();
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ nDataSize=4;
+ break;
+
+ case 0x000f: // BkColor
+ aActBackColor=ReadColor();
+ nDataSize=4;
+ break;
+
+ case 0x0010: // TxRatio
+ nDataSize=8;
+ break;
+
+ case 0x0011: // VersionOp
+ nDataSize=1;
+ break;
+
+ case 0x0012: // BkPixPat
+ nDataSize=ReadPixPattern(eActBackPattern);
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ break;
+
+ case 0x0013: // PnPixPat
+ nDataSize=ReadPixPattern(eActPenPattern);
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ break;
+
+ case 0x0014: // FillPixPat
+ nDataSize=ReadPixPattern(eActFillPattern);
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ break;
+
+ case 0x0015: // PnLocHFrac
+ nDataSize=2;
+ break;
+
+ case 0x0016: // ChExtra
+ nDataSize=2;
+ break;
+
+ case 0x0017: // Reserved (0 Bytes)
+ case 0x0018: // Reserved (0 Bytes)
+ case 0x0019: // Reserved (0 Bytes)
+ nDataSize=0;
+ break;
+
+ case 0x001a: // RGBFgCol
+ aActForeColor=ReadRGBColor();
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ nDataSize=6;
+ break;
+
+ case 0x001b: // RGBBkCol
+ aActBackColor=ReadRGBColor();
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ nDataSize=6;
+ break;
+
+ case 0x001c: // HiliteMode
+ nDataSize=0;
+ break;
+
+ case 0x001d: // HiliteColor
+ nDataSize=6;
+ break;
+
+ case 0x001e: // DefHilite
+ nDataSize=0;
+ break;
+
+ case 0x001f: // OpColor
+ nDataSize=6;
+ break;
+
+ case 0x0020: // Line
+ aPoint=ReadPoint(); aPenPosition=ReadPoint();
+ nDataSize=8;
+
+ if (!pPict->good())
+ break;
+
+ if (IsInvisible( PictDrawingMethod::FRAME )) break;
+ DrawingMethod( PictDrawingMethod::FRAME );
+ PictReaderShape::drawLine(pVirDev, aPoint,aPenPosition, nActPenSize);
+ break;
+
+ case 0x0021: // LineFrom
+ aPoint=aPenPosition; aPenPosition=ReadPoint();
+ nDataSize=4;
+
+ if (!pPict->good())
+ break;
+
+ if (IsInvisible( PictDrawingMethod::FRAME )) break;
+ DrawingMethod( PictDrawingMethod::FRAME );
+ PictReaderShape::drawLine(pVirDev, aPoint,aPenPosition, nActPenSize);
+ break;
+
+ case 0x0022: // ShortLine
+ aPoint=ReadPoint();
+ aPenPosition=ReadDeltaH(aPoint);
+ aPenPosition=ReadDeltaV(aPenPosition);
+ nDataSize=6;
+
+ if (!pPict->good())
+ break;
+
+ if ( IsInvisible(PictDrawingMethod::FRAME) ) break;
+ DrawingMethod( PictDrawingMethod::FRAME );
+ PictReaderShape::drawLine(pVirDev, aPoint,aPenPosition, nActPenSize);
+ break;
+
+ case 0x0023: // ShortLineFrom
+ aPoint=aPenPosition;
+ aPenPosition=ReadDeltaH(aPoint);
+ aPenPosition=ReadDeltaV(aPenPosition);
+ nDataSize=2;
+
+ if (!pPict->good())
+ break;
+
+ if (IsInvisible( PictDrawingMethod::FRAME )) break;
+ DrawingMethod( PictDrawingMethod::FRAME );
+ PictReaderShape::drawLine(pVirDev, aPoint,aPenPosition, nActPenSize);
+ break;
+
+ case 0x0024: // Reserved (n Bytes)
+ case 0x0025: // Reserved (n Bytes)
+ case 0x0026: // Reserved (n Bytes)
+ case 0x0027: // Reserved (n Bytes)
+ {
+ sal_uInt16 nUSHORT(0);
+ pPict->ReadUInt16( nUSHORT );
+ nDataSize=2+nUSHORT;
+ break;
+ }
+ case 0x0028: // LongText
+ aTextPosition=ReadPoint();
+ nDataSize=4+ReadAndDrawText();
+ break;
+
+ case 0x0029: // DHText
+ aTextPosition=ReadUnsignedDeltaH(aTextPosition);
+ nDataSize=1+ReadAndDrawText();
+ break;
+
+ case 0x002a: // DVText
+ aTextPosition=ReadUnsignedDeltaV(aTextPosition);
+ nDataSize=1+ReadAndDrawText();
+ break;
+
+ case 0x002b: // DHDVText
+ aTextPosition=ReadUnsignedDeltaH(aTextPosition);
+ aTextPosition=ReadUnsignedDeltaV(aTextPosition);
+ nDataSize=2+ReadAndDrawText();
+ break;
+
+ case 0x002c: { // fontName
+ sal_uInt16 nUSHORT(0);
+ pPict->ReadUInt16( nUSHORT ); nDataSize=nUSHORT+2;
+ pPict->ReadUInt16( nUSHORT );
+ if (nUSHORT <= 1) aActFont.SetFamily(FAMILY_SWISS);
+ else if (nUSHORT <= 12) aActFont.SetFamily(FAMILY_DECORATIVE);
+ else if (nUSHORT <= 20) aActFont.SetFamily(FAMILY_ROMAN);
+ else if (nUSHORT == 21) aActFont.SetFamily(FAMILY_SWISS);
+ else if (nUSHORT == 22) aActFont.SetFamily(FAMILY_MODERN);
+ else if (nUSHORT <= 1023) aActFont.SetFamily(FAMILY_SWISS);
+ else aActFont.SetFamily(FAMILY_ROMAN);
+ aActFont.SetCharSet(GetTextEncoding(nUSHORT));
+ char nByteLen(0);
+ pPict->ReadChar( nByteLen );
+ sal_uInt16 nLen = static_cast<sal_uInt16>(nByteLen)&0x00ff;
+ char sFName[ 256 ];
+ sFName[pPict->ReadBytes(sFName, nLen)] = 0;
+ OUString aString( sFName, strlen(sFName), osl_getThreadTextEncoding() );
+ aActFont.SetFamilyName( aString );
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ break;
+ }
+ case 0x002d: // lineJustify
+ nDataSize=10;
+ break;
+
+ case 0x002e: // glyphState
+ {
+ sal_uInt16 nUSHORT(0);
+ pPict->ReadUInt16( nUSHORT );
+ nDataSize=2+nUSHORT;
+ break;
+ }
+ case 0x002f: // Reserved (n Bytes)
+ {
+ sal_uInt16 nUSHORT(0);
+ pPict->ReadUInt16( nUSHORT );
+ nDataSize=2+nUSHORT;
+ break;
+ }
+ case 0x0030: // frameRect
+ case 0x0031: // paintRect
+ case 0x0032: // eraseRect
+ case 0x0033: // invertRect
+ case 0x0034: // fillRect
+ nDataSize=ReadAndDrawRect(shapeDMethod);
+ break;
+
+ case 0x0035: // Reserved (8 Bytes)
+ case 0x0036: // Reserved (8 Bytes)
+ case 0x0037: // Reserved (8 Bytes)
+ nDataSize=8;
+ break;
+
+ case 0x0038: // frameSameRect
+ case 0x0039: // paintSameRect
+ case 0x003a: // eraseSameRect
+ case 0x003b: // invertSameRect
+ case 0x003c: // fillSameRect
+ nDataSize=ReadAndDrawSameRect(shapeDMethod);
+ break;
+
+ case 0x003d: // Reserved (0 Bytes)
+ case 0x003e: // Reserved (0 Bytes)
+ case 0x003f: // Reserved (0 Bytes)
+ nDataSize=0;
+ break;
+
+ case 0x0040: // frameRRect
+ case 0x0041: // paintRRect
+ case 0x0042: // eraseRRect
+ case 0x0043: // invertRRect
+ case 0x0044: // fillRRect
+ nDataSize=ReadAndDrawRoundRect(shapeDMethod);
+ break;
+
+ case 0x0045: // Reserved (8 Bytes)
+ case 0x0046: // Reserved (8 Bytes)
+ case 0x0047: // Reserved (8 Bytes)
+ nDataSize=8;
+ break;
+
+ case 0x0048: // frameSameRRect
+ case 0x0049: // paintSameRRect
+ case 0x004a: // eraseSameRRect
+ case 0x004b: // invertSameRRect
+ case 0x004c: // fillSameRRect
+ nDataSize=ReadAndDrawSameRoundRect(shapeDMethod);
+ break;
+
+ case 0x004d: // Reserved (0 Bytes)
+ case 0x004e: // Reserved (0 Bytes)
+ case 0x004f: // Reserved (0 Bytes)
+ nDataSize=0;
+ break;
+
+ case 0x0050: // frameOval
+ case 0x0051: // paintOval
+ case 0x0052: // eraseOval
+ case 0x0053: // invertOval
+ case 0x0054: // fillOval
+ nDataSize=ReadAndDrawOval(shapeDMethod);
+ break;
+
+ case 0x0055: // Reserved (8 Bytes)
+ case 0x0056: // Reserved (8 Bytes)
+ case 0x0057: // Reserved (8 Bytes)
+ nDataSize=8;
+ break;
+
+ case 0x0058: // frameSameOval
+ case 0x0059: // paintSameOval
+ case 0x005a: // eraseSameOval
+ case 0x005b: // invertSameOval
+ case 0x005c: // fillSameOval
+ nDataSize=ReadAndDrawSameOval(shapeDMethod);
+ break;
+
+ case 0x005d: // Reserved (0 Bytes)
+ case 0x005e: // Reserved (0 Bytes)
+ case 0x005f: // Reserved (0 Bytes)
+ nDataSize=0;
+ break;
+
+ case 0x0060: // frameArc
+ case 0x0061: // paintArc
+ case 0x0062: // eraseArc
+ case 0x0063: // invertArc
+ case 0x0064: // fillArc
+ nDataSize=ReadAndDrawArc(shapeDMethod);
+ break;
+
+ case 0x0065: // Reserved (12 Bytes)
+ case 0x0066: // Reserved (12 Bytes)
+ case 0x0067: // Reserved (12 Bytes)
+ nDataSize=12;
+ break;
+
+ case 0x0068: // frameSameArc
+ case 0x0069: // paintSameArc
+ case 0x006a: // eraseSameArc
+ case 0x006b: // invertSameArc
+ case 0x006c: // fillSameArc
+ nDataSize=ReadAndDrawSameArc(shapeDMethod);
+ break;
+
+ case 0x006d: // Reserved (4 Bytes)
+ case 0x006e: // Reserved (4 Bytes)
+ case 0x006f: // Reserved (4 Bytes)
+ nDataSize=4;
+ break;
+
+ case 0x0070: // framePoly
+ case 0x0071: // paintPoly
+ case 0x0072: // erasePoly
+ case 0x0073: // invertPoly
+ case 0x0074: // fillPoly
+ nDataSize=ReadAndDrawPolygon(shapeDMethod);
+ break;
+
+ case 0x0075: // Reserved (Polygon-Size)
+ case 0x0076: // Reserved (Polygon-Size)
+ case 0x0077: // Reserved (Polygon-Size)
+ {
+ sal_uInt16 nUSHORT(0);
+ pPict->ReadUInt16( nUSHORT ); nDataSize=nUSHORT;
+ break;
+ }
+ case 0x0078: // frameSamePoly
+ case 0x0079: // paintSamePoly
+ case 0x007a: // eraseSamePoly
+ case 0x007b: // invertSamePoly
+ case 0x007c: // fillSamePoly
+ nDataSize=ReadAndDrawSamePolygon(shapeDMethod);
+ break;
+
+ case 0x007d: // Reserved (0 Bytes)
+ case 0x007e: // Reserved (0 Bytes)
+ case 0x007f: // Reserved (0 Bytes)
+ nDataSize=0;
+ break;
+
+ case 0x0080: // frameRgn
+ case 0x0081: // paintRgn
+ case 0x0082: // eraseRgn
+ case 0x0083: // invertRgn
+ case 0x0084: // fillRgn
+ nDataSize=ReadAndDrawRgn(shapeDMethod);
+ break;
+
+ case 0x0085: // Reserved (Region-Size)
+ case 0x0086: // Reserved (Region-Size)
+ case 0x0087: // Reserved (Region-Size)
+ {
+ sal_uInt16 nUSHORT(0);
+ pPict->ReadUInt16( nUSHORT ); nDataSize=nUSHORT;
+ break;
+ }
+ case 0x0088: // frameSameRgn
+ case 0x0089: // paintSameRgn
+ case 0x008a: // eraseSameRgn
+ case 0x008b: // invertSameRgn
+ case 0x008c: // fillSameRgn
+ nDataSize=ReadAndDrawSameRgn(shapeDMethod);
+ break;
+
+ case 0x008d: // Reserved (0 Bytes)
+ case 0x008e: // Reserved (0 Bytes)
+ case 0x008f: // Reserved (0 Bytes)
+ nDataSize=0;
+ break;
+
+ case 0x0090: { // BitsRect
+ BitmapEx aBmp;
+ tools::Rectangle aSrcRect, aDestRect;
+ nDataSize=ReadPixMapEtc(aBmp, false, true, &aSrcRect, &aDestRect, true, false);
+ DrawingMethod( PictDrawingMethod::PAINT );
+ pVirDev->DrawBitmapEx(aDestRect.TopLeft(),aDestRect.GetSize(),aBmp);
+ break;
+ }
+ case 0x0091: { // BitsRgn
+ BitmapEx aBmp;
+ tools::Rectangle aSrcRect, aDestRect;
+ nDataSize=ReadPixMapEtc(aBmp, false, true, &aSrcRect, &aDestRect, true, true);
+ DrawingMethod( PictDrawingMethod::PAINT );
+ pVirDev->DrawBitmapEx(aDestRect.TopLeft(),aDestRect.GetSize(),aBmp);
+ break;
+ }
+ case 0x0092: // Reserved (n Bytes)
+ case 0x0093: // Reserved (n Bytes)
+ case 0x0094: // Reserved (n Bytes)
+ case 0x0095: // Reserved (n Bytes)
+ case 0x0096: // Reserved (n Bytes)
+ case 0x0097: // Reserved (n Bytes)
+ {
+ sal_uInt16 nUSHORT(0);
+ pPict->ReadUInt16( nUSHORT ); nDataSize=2+nUSHORT;
+ break;
+ }
+ case 0x0098: { // PackBitsRect
+ BitmapEx aBmp;
+ tools::Rectangle aSrcRect, aDestRect;
+ nDataSize=ReadPixMapEtc(aBmp, false, true, &aSrcRect, &aDestRect, true, false);
+ DrawingMethod( PictDrawingMethod::PAINT );
+ pVirDev->DrawBitmapEx(aDestRect.TopLeft(),aDestRect.GetSize(),aBmp);
+ break;
+ }
+ case 0x0099: { // PackBitsRgn
+ BitmapEx aBmp;
+ tools::Rectangle aSrcRect, aDestRect;
+ nDataSize=ReadPixMapEtc(aBmp, false, true, &aSrcRect, &aDestRect, true, true);
+ DrawingMethod( PictDrawingMethod::PAINT );
+ pVirDev->DrawBitmapEx(aDestRect.TopLeft(),aDestRect.GetSize(),aBmp);
+ break;
+ }
+ case 0x009a: { // DirectBitsRect
+ BitmapEx aBmp;
+ tools::Rectangle aSrcRect, aDestRect;
+ nDataSize=ReadPixMapEtc(aBmp, true, false, &aSrcRect, &aDestRect, true, false);
+ DrawingMethod( PictDrawingMethod::PAINT );
+ pVirDev->DrawBitmapEx(aDestRect.TopLeft(),aDestRect.GetSize(),aBmp);
+ break;
+ }
+ case 0x009b: { // DirectBitsRgn
+ BitmapEx aBmp;
+ tools::Rectangle aSrcRect, aDestRect;
+ nDataSize=ReadPixMapEtc(aBmp, true, false, &aSrcRect, &aDestRect, true, true);
+ DrawingMethod( PictDrawingMethod::PAINT );
+ pVirDev->DrawBitmapEx(aDestRect.TopLeft(),aDestRect.GetSize(),aBmp);
+ break;
+ }
+ case 0x009c: // Reserved (n Bytes)
+ case 0x009d: // Reserved (n Bytes)
+ case 0x009e: // Reserved (n Bytes)
+ case 0x009f: // Reserved (n Bytes)
+ {
+ sal_uInt16 nUSHORT(0);
+ pPict->ReadUInt16( nUSHORT ); nDataSize=2+nUSHORT;
+ break;
+ }
+ case 0x00a0: // ShortComment
+ nDataSize=2;
+ break;
+
+ case 0x00a1: // LongComment
+ {
+ sal_uInt16 nUSHORT(0);
+ pPict->SeekRel(2); pPict->ReadUInt16( nUSHORT ); nDataSize=4+nUSHORT;
+ break;
+ }
+ default: // 0x00a2 bis 0xffff (most times reserved)
+ sal_uInt16 nUSHORT(0);
+ if (nOpcode<=0x00af) { pPict->ReadUInt16( nUSHORT ); nDataSize=2+nUSHORT; }
+ else if (nOpcode<=0x00cf) { nDataSize=0; }
+ else if (nOpcode<=0x00fe) { sal_uInt32 nTemp(0); pPict->ReadUInt32(nTemp) ; nDataSize = nTemp; nDataSize+=4; }
+ // Osnola: checkme: in the Quickdraw Ref examples ( for pict v2)
+ // 0x00ff(EndOfPict) is also not followed by any data...
+ else if (nOpcode==0x00ff) { nDataSize=IsVersion2 ? 2 : 0; } // OpEndPic
+ else if (nOpcode<=0x01ff) { nDataSize=2; }
+ else if (nOpcode<=0x0bfe) { nDataSize=4; }
+ else if (nOpcode<=0x0bff) { nDataSize=22; }
+ else if (nOpcode==0x0c00) { nDataSize=24; } // HeaderOp
+ else if (nOpcode<=0x7eff) { nDataSize=24; }
+ else if (nOpcode<=0x7fff) { nDataSize=254; }
+ else if (nOpcode<=0x80ff) { nDataSize=0; }
+ else { sal_uInt32 nTemp(0); pPict->ReadUInt32(nTemp) ; nDataSize = nTemp; nDataSize+=4; }
+ }
+
+ if (nDataSize==0xffffffff) {
+ pPict->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ return 0;
+ }
+ return nDataSize;
+}
+
+void PictReader::ReadPict( SvStream & rStreamPict, GDIMetaFile & rGDIMetaFile )
+{
+ try {
+ sal_uInt16 nOpcode;
+ sal_uInt8 nOneByteOpcode;
+ sal_uInt64 nSize;
+
+ pPict = &rStreamPict;
+ nOrigPos = pPict->Tell();
+ SvStreamEndian nOrigNumberFormat = pPict->GetEndian();
+
+ aActForeColor = COL_BLACK;
+ aActBackColor = COL_WHITE;
+ nActPenSize = Size(1,1);
+ eActROP = RasterOp::OverPaint;
+ eActMethod = PictDrawingMethod::UNDEFINED;
+ aActOvalSize = Size(1,1);
+
+ aActFont.SetCharSet( GetTextEncoding());
+ aActFont.SetFamily(FAMILY_SWISS);
+ aActFont.SetFontSize(Size(0,12));
+ aActFont.SetAlignment(ALIGN_BASELINE);
+
+ aHRes = aVRes = Fraction( 1, 1 );
+
+ pVirDev = VclPtr<VirtualDevice>::Create();
+ pVirDev->EnableOutput(false);
+ rGDIMetaFile.Record(pVirDev);
+
+ pPict->SetEndian(SvStreamEndian::BIG);
+
+ ReadHeader();
+
+ aPenPosition=Point(-aBoundingRect.Left(),-aBoundingRect.Top());
+ aTextPosition=aPenPosition;
+
+ sal_uInt64 nPos=pPict->Tell();
+
+ for (;;) {
+
+ if (IsVersion2 )
+ pPict->ReadUInt16( nOpcode );
+ else
+ {
+ pPict->ReadUChar( nOneByteOpcode );
+ nOpcode=static_cast<sal_uInt16>(nOneByteOpcode);
+ }
+
+ if (pPict->GetError())
+ break;
+
+ if (pPict->eof())
+ {
+ pPict->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ break;
+ }
+
+ if (nOpcode==0x00ff)
+ break;
+
+ nSize=ReadData(nOpcode);
+
+ if ( IsVersion2 )
+ {
+ if ( nSize & 1 )
+ nSize++;
+
+ nPos+=2+nSize;
+ }
+ else
+ nPos+=1+nSize;
+
+ if (!checkSeek(*pPict, nPos))
+ {
+ pPict->SetError(SVSTREAM_FILEFORMAT_ERROR);
+ break;
+ }
+ }
+
+ pVirDev->SetClipRegion();
+ rGDIMetaFile.Stop();
+ pVirDev.disposeAndClear();
+
+ rGDIMetaFile.SetPrefMapMode( MapMode( MapUnit::MapInch, Point(), aHRes, aVRes ) );
+ rGDIMetaFile.SetPrefSize( aBoundingRect.GetSize() );
+
+ pPict->SetEndian(nOrigNumberFormat);
+
+ if (pPict->GetError()) pPict->Seek(nOrigPos);
+ } catch (...)
+ {
+ rStreamPict.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ }
+}
+
+void ReadPictFile(SvStream &rStreamPict, GDIMetaFile& rGDIMetaFile)
+{
+ PictReader aPictReader;
+ aPictReader.ReadPict(rStreamPict, rGDIMetaFile);
+}
+
+//================== GraphicImport - the exported function ================
+
+bool ImportPictGraphic( SvStream& rIStm, Graphic & rGraphic)
+{
+ GDIMetaFile aMTF;
+ bool bRet = false;
+
+ ReadPictFile(rIStm, aMTF);
+
+ if ( !rIStm.GetError() )
+ {
+ rGraphic = Graphic( aMTF );
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ipict/shape.cxx b/vcl/source/filter/ipict/shape.cxx
new file mode 100644
index 000000000..88a62cfd2
--- /dev/null
+++ b/vcl/source/filter/ipict/shape.cxx
@@ -0,0 +1,259 @@
+/* -*- 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 .
+ */
+
+/** Osnola:
+IMPORTANT NOTE: some Quickdraw lines/frames can not be "quickly" drawn exactly:
+for instance, when PenSize=(1,1), the line from (0,0) to (8,0)
+corresponds to the rectangle (0,0)(0,1)(9,1)(9,0), which can only be drawn
+ by drawing a rectangle. Drawing a non horizontal/vertical will imply to draw
+a polygon, ...
+Similarly, drawing the frame of a rectangle (0,0)(0,1)(9,1)(9,0) when PenSize=(1,1),
+will imply to draw a rectangle (0.5,0.5)(0.5,8.5)(8.5,8.5)(8.5,0.5) with linewidth=1...
+
+Here, we choose:
+- for horizontal/vertical lines and line with length less than five to draw the real line,
+- in the other case, we keep the same shape (even if this means some "bad" coordinates)
+*/
+
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include "shape.hxx"
+
+namespace PictReaderShapePrivate {
+ /** returns an inside rectangle knowing the penSize in order to obtain the ``correct'' position
+ when we draw a frame in wide length*/
+ static tools::Rectangle contractRectangle(bool drawFrame, tools::Rectangle const &rect, Size const &pSize) {
+ if (!drawFrame) return rect;
+ tools::Long penSize=(pSize.Width()+pSize.Height())/2;
+ if (2*penSize > rect.Right()-rect.Left()) penSize = (rect.Right()-rect.Left()+1)/2;
+ if (2*penSize > rect.Bottom()-rect.Top()) penSize = (rect.Bottom()-rect.Top()+1)/2;
+ tools::Long const X[2] = { rect.Left()+penSize/2, rect.Right()-(penSize+1)/2 };
+ tools::Long const Y[2] = { rect.Top()+penSize/2, rect.Bottom()-(penSize+1)/2 };
+ return tools::Rectangle(Point(X[0],Y[0]), Point(X[1], Y[1]));
+ }
+}
+
+namespace PictReaderShape {
+ //--------- draws a horizontal/vertical/small line (by creating a "rectangle/polygon") ---------
+ static bool drawLineHQ(VirtualDevice *dev, Point const &orig, Point const &dest, Size const &pSize) {
+ tools::Long dir[2] = { dest.X()-orig.X(), dest.Y()-orig.Y() };
+ bool vertic = dir[0] == 0;
+ bool horiz = dir[1] == 0;
+ if (!horiz && !vertic && dir[0]*dir[0]+dir[1]*dir[1] > 25) return false;
+
+ using namespace basegfx;
+ B2DPolygon poly;
+ if (horiz || vertic) {
+ tools::Long X[2]={ orig.X(), dest.X() }, Y[2] = { orig.Y(), dest.Y() };
+ if (horiz) {
+ if (X[0] < X[1]) X[1]+=pSize.Width();
+ else X[0]+=pSize.Width();
+ Y[1] += pSize.Height();
+ }
+ else {
+ if (Y[0] < Y[1]) Y[1]+=pSize.Height();
+ else Y[0]+=pSize.Height();
+ X[1] += pSize.Width();
+ }
+ poly.append(B2DPoint(X[0], Y[0])); poly.append(B2DPoint(X[1], Y[0]));
+ poly.append(B2DPoint(X[1], Y[1])); poly.append(B2DPoint(X[0], Y[1]));
+ poly.append(B2DPoint(X[0], Y[0]));
+ }
+ else {
+ tools::Long origPt[4][2] = { { orig.X(), orig.Y() }, { orig.X()+pSize.Width(), orig.Y() },
+ { orig.X()+pSize.Width(), orig.Y()+pSize.Height() },
+ { orig.X(), orig.Y()+pSize.Height() }};
+ int origAvoid = dir[0] > 0 ? (dir[1] > 0 ? 2 : 1) : (dir[1] > 0 ? 3 : 0);
+ tools::Long destPt[4][2] = { { dest.X(), dest.Y() }, { dest.X()+pSize.Width(), dest.Y() },
+ { dest.X()+pSize.Width(), dest.Y()+pSize.Height() },
+ { dest.X(), dest.Y()+pSize.Height() }};
+ for (int w = origAvoid+1; w < origAvoid+4; w++) {
+ int wh = w%4;
+ poly.append(B2DPoint(origPt[wh][0], origPt[wh][1]));
+ }
+ for (int w = origAvoid+3; w < origAvoid+6; w++) {
+ int wh = w%4;
+ poly.append(B2DPoint(destPt[wh][0], destPt[wh][1]));
+ }
+ int wh = (origAvoid+1)%4;
+ poly.append(B2DPoint(origPt[wh][0], origPt[wh][1]));
+ }
+
+ // HACK: here we use the line coloring when drawing the shape
+ // must be changed if other parameter are changed to draw
+ // a line/fill shape
+ Color oldFColor = dev->GetFillColor(), oldLColor = dev->GetLineColor();
+ dev->SetFillColor(oldLColor); dev->SetLineColor(COL_TRANSPARENT);
+ dev->DrawPolygon(poly);
+ dev->SetLineColor(oldLColor); dev->SetFillColor(oldFColor);
+ return true;
+ }
+
+
+ //-------------------- draws a line --------------------
+
+ void drawLine(VirtualDevice *dev, Point const &orig, Point const &dest, Size const &pSize) {
+ if (drawLineHQ(dev,orig,dest,pSize)) return;
+
+ tools::Long penSize=(pSize.Width()+pSize.Height())/2;
+ tools::Long decal[2] = { pSize.Width()/2, pSize.Height()/2};
+
+ using namespace basegfx;
+ B2DPolygon poly;
+ poly.append(B2DPoint(double(orig.X()+decal[0]), double(orig.Y()+decal[1])));
+ poly.append(B2DPoint(double(dest.X()+decal[0]), double(dest.Y()+decal[1])));
+ dev->DrawPolyLine(poly, double(penSize), basegfx::B2DLineJoin::NONE);
+ }
+
+ //-------------------- draws a rectangle --------------------
+ /* Note(checkme): contradictally with the QuickDraw's reference 3-23, it seems better to consider
+ that the frame/content of a rectangle appears inside the given rectangle. Does a conversion
+ appear between the pascal functions and the data stored in the file ? */
+ void drawRectangle(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &orig, Size const &pSize) {
+ int penSize=(pSize.Width()+pSize.Height())/2;
+ tools::Rectangle rect = PictReaderShapePrivate::contractRectangle(drawFrame, orig, pSize);
+ tools::Long const X[2] = { rect.Left(), rect.Right() };
+ tools::Long const Y[2] = { rect.Top(), rect.Bottom() };
+
+ using namespace basegfx;
+ B2DPolygon poly;
+ poly.append(B2DPoint(X[0], Y[0])); poly.append(B2DPoint(X[1], Y[0]));
+ poly.append(B2DPoint(X[1], Y[1])); poly.append(B2DPoint(X[0], Y[1]));
+ poly.append(B2DPoint(X[0], Y[0]));
+
+ if (drawFrame)
+ dev->DrawPolyLine(poly, double(penSize), basegfx::B2DLineJoin::NONE);
+ else
+ dev->DrawPolygon(poly);
+ }
+
+ //-------------------- draws an ellipse --------------------
+ void drawEllipse(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &orig, Size const &pSize) {
+ int penSize=(pSize.Width()+pSize.Height())/2;
+ tools::Rectangle oval = PictReaderShapePrivate::contractRectangle(drawFrame, orig, pSize);
+ using namespace basegfx;
+ tools::Long const X[2] = { oval.Left(), oval.Right() };
+ tools::Long const Y[2] = { oval.Top(), oval.Bottom() };
+ B2DPoint center(0.5*(X[1]+X[0]), 0.5*(Y[1]+Y[0]));
+ B2DPolygon poly = basegfx::utils::createPolygonFromEllipse(center, 0.5*(X[1]-X[0]), 0.5*(Y[1]-Y[0]));
+ if (drawFrame)
+ dev->DrawPolyLine(poly, double(penSize), basegfx::B2DLineJoin::NONE);
+ else
+ dev->DrawPolygon(poly);
+ }
+
+ //-------------------- draws an arc/pie --------------------
+ void drawArc(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &orig, const double& angle1, const double& angle2, Size const &pSize) {
+ int penSize=(pSize.Width()+pSize.Height())/2;
+ tools::Rectangle arc = PictReaderShapePrivate::contractRectangle(drawFrame, orig, pSize);
+ using namespace basegfx;
+
+ // pict angle are CW with 0 at twelve o'clock (with Y-axis inverted)...
+ double angl1 = angle1-M_PI_2;
+ double angl2 = angle2-M_PI_2;
+ tools::Long const X[2] = { arc.Left(), arc.Right() };
+ tools::Long const Y[2] = { arc.Top(), arc.Bottom() };
+ B2DPoint center(0.5*(X[1]+X[0]), 0.5*(Y[1]+Y[0]));
+
+ // We must have angl1 between 0 and 2PI
+ while (angl1 < 0.0) { angl1 += 2 * M_PI; angl2 += 2 * M_PI; }
+ while (angl1 >= 2 * M_PI) { angl1 -= 2 * M_PI; angl2 -= 2 * M_PI; }
+
+ // if this happen, we want a complete circle
+ // so we set angl2 slightly less than angl1
+ if (angl2 >= angl1 + 2 * M_PI) angl2 = angl1-0.001;
+
+ // We must have angl2 between 0 and 2PI
+ while (angl2 < 0.0) angl2 += 2 * M_PI;
+ while (angl2 >= 2 * M_PI) angl2 -= 2 * M_PI;
+
+ B2DPolygon poly = basegfx::utils::createPolygonFromEllipseSegment(center, 0.5*(X[1]-X[0]), 0.5*(Y[1]-Y[0]), angl1, angl2);
+ if (drawFrame)
+ dev->DrawPolyLine(poly, double(penSize), basegfx::B2DLineJoin::NONE);
+ else {
+ // adds circle's center
+ poly.append(center);
+ dev->DrawPolygon(poly);
+ }
+ }
+ //-------------------- draws a rectangle with round corner --------------------
+ void drawRoundRectangle(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &orig, Size const &ovalSize, Size const &pSize) {
+ int penSize=(pSize.Width()+pSize.Height())/2;
+ tools::Rectangle oval = PictReaderShapePrivate::contractRectangle(drawFrame, orig, pSize);
+ int ovalW=ovalSize.Width(), ovalH=ovalSize.Height();
+ using namespace basegfx;
+ tools::Long const X[2] = { oval.Left(), oval.Right() };
+ tools::Long const Y[2] = { oval.Top(), oval.Bottom() };
+ tools::Long width = X[1] - X[0];
+ tools::Long height = Y[1] - Y[0];
+ if (ovalW > width) ovalW = static_cast< int >( width );
+ if (ovalH > height) ovalH = static_cast< int >( height );
+
+ B2DRectangle rect(B2DPoint(X[0],Y[0]), B2DPoint(X[1],Y[1]));
+ B2DPolygon poly = basegfx::utils::createPolygonFromRect(rect, (width != 0.0) ? ovalW/width : 0.0, (height != 0.0) ? ovalH/height : 0.0);
+
+ if (drawFrame)
+ dev->DrawPolyLine(poly, double(penSize), basegfx::B2DLineJoin::NONE);
+ else
+ dev->DrawPolygon(poly);
+ }
+
+ //-------------------- draws a polygon --------------------
+void drawPolygon(VirtualDevice *dev, bool drawFrame, tools::Polygon const &orig, Size const &pSize) {
+ int penSize=(pSize.Width()+pSize.Height())/2;
+ tools::Long decalTL[2] = {0, 0}, decalBR[2] = { pSize.Width(), pSize.Height()};
+ if (drawFrame) {
+ decalTL[0] += penSize/2; decalTL[1] += penSize/2;
+ decalBR[0] -= (penSize+1)/2; decalBR[1] -= (penSize+1)/2;
+ }
+ // Quickdraw Drawing Reference 3-82: the pen size is only used for frame
+ else decalBR[0] = decalBR[1] = 0;
+
+
+ int numPt = orig.GetSize();
+ if (numPt <= 1) return;
+
+ // we compute a barycenter of the point to define the extended direction of each point
+ double bary[2] = { 0.0, 0.0 };
+ for (int i = 0; i < numPt; i++) {
+ Point const &pt = orig.GetPoint(i);
+ bary[0] += double(pt.X()); bary[1] += double(pt.Y());
+ }
+ bary[0]/=double(numPt); bary[1]/=double(numPt);
+
+ using namespace basegfx;
+ B2DPolygon poly;
+ poly.reserve(numPt);
+ // Note: a polygon can be open, so we must not close it when we draw the frame
+ for (int i = 0; i < numPt; i++) {
+ Point const &pt = orig.GetPoint(i);
+ double x = (double(pt.X()) < bary[0]) ? pt.X()+decalTL[0] : pt.X()+decalBR[0];
+ double y = (double(pt.Y()) < bary[1]) ? pt.Y()+decalTL[1] : pt.Y()+decalBR[1];
+ poly.append(B2DPoint(x, y));
+ }
+ if (drawFrame)
+ dev->DrawPolyLine(poly, double(penSize), basegfx::B2DLineJoin::NONE);
+ else
+ dev->DrawPolygon(poly);
+ }
+
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ipict/shape.hxx b/vcl/source/filter/ipict/shape.hxx
new file mode 100644
index 000000000..bccd39e63
--- /dev/null
+++ b/vcl/source/filter/ipict/shape.hxx
@@ -0,0 +1,57 @@
+/* -*- 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 .
+ */
+#ifndef INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IPICT_SHAPE_HXX
+#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IPICT_SHAPE_HXX
+
+#include <vcl/virdev.hxx>
+
+namespace PictReaderShape {
+ /** draws a line from orig to dest knowing penSize
+
+ Attention: in order to draw horizontal/vertical/small lines, this function can instead draw a rectangle or
+ a polygon. In this case, we retrieve the line information from VirtualDev ( GetLineColor )
+ and we use them as fill information ( SetFillColor ). We restore after the VirtualDev state.
+
+ This implies also that this function must be modified if we use real pattern to draw these primitives.
+ */
+ void drawLine(VirtualDevice *dev, Point const &orig, Point const &dest, Size const &pSize);
+
+ /** draws a rectangle knowing penSize */
+ void drawRectangle(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &rect, Size const &pSize);
+
+ /** draws a polygon knowing penSize */
+void drawPolygon(VirtualDevice *dev, bool drawFrame, tools::Polygon const &rect, Size const &pSize);
+
+ /** draws an ellipse knowing penSize */
+ void drawEllipse(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &orig, Size const &pSize);
+
+ /** draws a rounded rectangle knowing penSize
+ \note ovalSize is two time the size of the corner
+ */
+ void drawRoundRectangle(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &orig, Size const &ovalS, Size const &pSize);
+
+ /** draws an arc in a b2dpolygon knowing penSize
+ \note - it supposes that angl1 < angl2
+ */
+ void drawArc(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &orig, const double& angle1, const double& angle2, Size const &pSize);
+}
+
+#endif // INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IPICT_SHAPE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ipsd/ipsd.cxx b/vcl/source/filter/ipsd/ipsd.cxx
new file mode 100644
index 000000000..4c1277027
--- /dev/null
+++ b/vcl/source/filter/ipsd/ipsd.cxx
@@ -0,0 +1,776 @@
+/* -*- 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 <vcl/graph.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <vcl/outdev.hxx>
+#include <sal/log.hxx>
+#include <tools/fract.hxx>
+#include <tools/helpers.hxx>
+#include <tools/stream.hxx>
+#include <memory>
+#include <filter/PsdReader.hxx>
+
+
+class FilterConfigItem;
+
+//============================ PSDReader ==================================
+
+#define PSD_BITMAP 0
+#define PSD_GRAYSCALE 1
+#define PSD_INDEXED 2
+#define PSD_RGB 3
+#define PSD_CMYK 4
+#define PSD_MULTICHANNEL 7
+#define PSD_DUOTONE 8
+#define PSD_LAB 9
+
+namespace {
+
+struct PSDFileHeader
+{
+ sal_uInt32 nSignature;
+ sal_uInt16 nVersion;
+ sal_uInt32 nPad1;
+ sal_uInt16 nPad2;
+ sal_uInt16 nChannels;
+ sal_uInt32 nRows;
+ sal_uInt32 nColumns;
+ sal_uInt16 nDepth;
+ sal_uInt16 nMode;
+};
+
+class PSDReader {
+
+private:
+
+ SvStream& m_rPSD; // the PSD file to be read in
+ std::unique_ptr<PSDFileHeader>
+ mpFileHeader;
+
+ sal_uInt32 mnXResFixed;
+ sal_uInt32 mnYResFixed;
+
+ bool mbStatus;
+ bool mbTransparent;
+
+ std::unique_ptr<vcl::bitmap::RawBitmap> mpBitmap;
+ std::vector<Color> mvPalette;
+ sal_uInt16 mnDestBitDepth;
+ bool mbCompression; // RLE decoding
+ std::unique_ptr<sal_uInt8[]>
+ mpPalette;
+
+ bool ImplReadBody();
+ bool ImplReadHeader();
+
+public:
+ explicit PSDReader(SvStream &rStream);
+ bool ReadPSD(Graphic & rGraphic);
+};
+
+}
+
+//=================== Methods of PSDReader ==============================
+
+PSDReader::PSDReader(SvStream &rStream)
+ : m_rPSD(rStream)
+ , mnXResFixed(0)
+ , mnYResFixed(0)
+ , mbStatus(true)
+ , mbTransparent(false)
+ , mnDestBitDepth(0)
+ , mbCompression(false)
+{
+}
+
+bool PSDReader::ReadPSD(Graphic & rGraphic )
+{
+ if (m_rPSD.GetError())
+ return false;
+
+ m_rPSD.SetEndian( SvStreamEndian::BIG );
+
+ // read header:
+
+ if ( !ImplReadHeader() )
+ return false;
+
+ if (mbStatus)
+ {
+ sal_uInt32 nResult;
+ if (o3tl::checked_multiply(mpFileHeader->nColumns, mpFileHeader->nRows, nResult) || nResult > SAL_MAX_INT32/2/3)
+ return false;
+ }
+
+ Size aBitmapSize( mpFileHeader->nColumns, mpFileHeader->nRows );
+ mpBitmap.reset( new vcl::bitmap::RawBitmap( aBitmapSize, mbTransparent ? 32 : 24 ) );
+ if ( mpPalette && mbStatus )
+ {
+ mvPalette.resize( 256 );
+ for ( sal_uInt16 i = 0; i < 256; i++ )
+ {
+ mvPalette[i] = Color( mpPalette[ i ], mpPalette[ i + 256 ], mpPalette[ i + 512 ] );
+ }
+ }
+
+ if ((mnDestBitDepth == 1 || mnDestBitDepth == 8) && mvPalette.empty())
+ {
+ SAL_WARN("vcl", "no palette, but bit depth is " << mnDestBitDepth);
+ mbStatus = false;
+ return mbStatus;
+ }
+
+ // read bitmap data
+ if ( mbStatus && ImplReadBody() )
+ {
+ rGraphic = Graphic( vcl::bitmap::CreateFromData( std::move(*mpBitmap) ) );
+
+ if ( mnXResFixed && mnYResFixed )
+ {
+ Fraction aFractX( 1, mnXResFixed >> 16 );
+ Fraction aFractY( 1, mnYResFixed >> 16 );
+ MapMode aMapMode( MapUnit::MapInch, Point(), aFractX, aFractY );
+ Size aPrefSize = OutputDevice::LogicToLogic(aBitmapSize, aMapMode, MapMode(MapUnit::Map100thMM));
+ rGraphic.SetPrefSize( aPrefSize );
+ rGraphic.SetPrefMapMode( MapMode( MapUnit::Map100thMM ) );
+ }
+ }
+ else
+ mbStatus = false;
+ return mbStatus;
+}
+
+
+bool PSDReader::ImplReadHeader()
+{
+ mpFileHeader.reset( new PSDFileHeader );
+
+ m_rPSD.ReadUInt32( mpFileHeader->nSignature ).ReadUInt16( mpFileHeader->nVersion ).ReadUInt32( mpFileHeader->nPad1 ). ReadUInt16( mpFileHeader->nPad2 ).ReadUInt16( mpFileHeader->nChannels ).ReadUInt32( mpFileHeader->nRows ). ReadUInt32( mpFileHeader->nColumns ).ReadUInt16( mpFileHeader->nDepth ).ReadUInt16( mpFileHeader->nMode );
+
+ if (!m_rPSD.good())
+ return false;
+
+ if ( ( mpFileHeader->nSignature != 0x38425053 ) || ( mpFileHeader->nVersion != 1 ) )
+ return false;
+
+ if ( mpFileHeader->nRows == 0 || mpFileHeader->nColumns == 0 )
+ return false;
+
+ if ( ( mpFileHeader->nRows > 30000 ) || ( mpFileHeader->nColumns > 30000 ) )
+ return false;
+
+ sal_uInt16 nDepth = mpFileHeader->nDepth;
+ if (!( ( nDepth == 1 ) || ( nDepth == 8 ) || ( nDepth == 16 ) ) )
+ return false;
+
+ mnDestBitDepth = ( nDepth == 16 ) ? 8 : nDepth;
+
+ sal_uInt32 nColorLength(0);
+ m_rPSD.ReadUInt32( nColorLength );
+ if ( mpFileHeader->nMode == PSD_CMYK )
+ {
+ switch ( mpFileHeader->nChannels )
+ {
+ case 5 :
+ mbTransparent = true;
+ [[fallthrough]];
+ case 4 :
+ mnDestBitDepth = 24;
+ break;
+ default :
+ return false;
+ }
+ }
+ else switch ( mpFileHeader->nChannels )
+ {
+ case 2 :
+ mbTransparent = true;
+ break;
+ case 1 :
+ break;
+ case 4 :
+ mbTransparent = true;
+ [[fallthrough]];
+ case 3 :
+ mnDestBitDepth = 24;
+ break;
+ default:
+ return false;
+ }
+
+ switch ( mpFileHeader->nMode )
+ {
+ case PSD_BITMAP :
+ {
+ if ( nColorLength || ( nDepth != 1 ) )
+ return false;
+ }
+ break;
+
+ case PSD_INDEXED :
+ {
+ if ( nColorLength != 768 ) // we need the color map
+ return false;
+ mpPalette.reset( new sal_uInt8[ 768 ] );
+ m_rPSD.ReadBytes(mpPalette.get(), 768);
+ }
+ break;
+
+ case PSD_DUOTONE : // we'll handle the duotone color like a normal grayscale picture
+ m_rPSD.SeekRel( nColorLength );
+ nColorLength = 0;
+ [[fallthrough]];
+ case PSD_GRAYSCALE :
+ {
+ if ( nColorLength )
+ return false;
+ mpPalette.reset( new sal_uInt8[ 768 ] );
+ for ( sal_uInt16 i = 0; i < 256; i++ )
+ {
+ mpPalette[ i ] = mpPalette[ i + 256 ] = mpPalette[ i + 512 ] = static_cast<sal_uInt8>(i);
+ }
+ }
+ break;
+
+ case PSD_CMYK :
+ case PSD_RGB :
+ case PSD_MULTICHANNEL :
+ case PSD_LAB :
+ {
+ if ( nColorLength ) // color table is not supported by the other graphic modes
+ return false;
+ }
+ break;
+
+ default:
+ return false;
+ }
+ sal_uInt32 nResourceLength(0);
+ m_rPSD.ReadUInt32(nResourceLength);
+ if (nResourceLength > m_rPSD.remainingSize())
+ return false;
+ sal_uInt32 nLayerPos = m_rPSD.Tell() + nResourceLength;
+
+ // this is a loop over the resource entries to get the resolution info
+ while( m_rPSD.Tell() < nLayerPos )
+ {
+ sal_uInt32 nType(0);
+ sal_uInt16 nUniqueID(0);
+ sal_uInt8 n8(0);
+ m_rPSD.ReadUInt32(nType).ReadUInt16(nUniqueID).ReadUChar(n8);
+ if (nType != 0x3842494d)
+ break;
+ sal_uInt32 nPStringLen = n8;
+ if ( ! ( nPStringLen & 1 ) )
+ nPStringLen++;
+ m_rPSD.SeekRel( nPStringLen ); // skipping the pstring
+ sal_uInt32 nResEntryLen(0);
+ m_rPSD.ReadUInt32( nResEntryLen );
+ if ( nResEntryLen & 1 )
+ nResEntryLen++; // the resource entries are padded
+ sal_uInt32 nCurrentPos = m_rPSD.Tell();
+ if (nCurrentPos > nLayerPos || nResEntryLen > (nLayerPos - nCurrentPos)) // check if size
+ break; // is possible
+ switch( nUniqueID )
+ {
+ case 0x3ed : // UID for the resolution info
+ {
+ sal_Int16 nUnit;
+
+ m_rPSD.ReadUInt32( mnXResFixed ).ReadInt16( nUnit ).ReadInt16( nUnit )
+ .ReadUInt32( mnYResFixed ).ReadInt16( nUnit ).ReadInt16( nUnit );
+ }
+ break;
+ }
+ m_rPSD.Seek( nCurrentPos + nResEntryLen ); // set the stream to the next
+ } // resource entry
+ m_rPSD.Seek( nLayerPos );
+ sal_uInt32 nLayerMaskLength(0);
+ m_rPSD.ReadUInt32( nLayerMaskLength );
+ m_rPSD.SeekRel( nLayerMaskLength );
+
+ sal_uInt16 nCompression(0);
+ m_rPSD.ReadUInt16(nCompression);
+ if ( nCompression == 0 )
+ {
+ mbCompression = false;
+ }
+ else if ( nCompression == 1 )
+ {
+ m_rPSD.SeekRel( ( mpFileHeader->nRows * mpFileHeader->nChannels ) << 1 );
+ mbCompression = true;
+ }
+ else
+ return false;
+
+ return true;
+}
+
+namespace
+{
+ const Color& SanitizePaletteIndex(std::vector<Color> const & rvPalette, sal_uInt8 nIndex)
+ {
+ if (nIndex >= rvPalette.size())
+ {
+ auto nSanitizedIndex = nIndex % rvPalette.size();
+ SAL_WARN_IF(nIndex != nSanitizedIndex, "filter.psd", "invalid colormap index: "
+ << static_cast<unsigned int>(nIndex) << ", colormap len is: "
+ << rvPalette.size());
+ nIndex = nSanitizedIndex;
+ }
+ return rvPalette[nIndex];
+ }
+}
+
+bool PSDReader::ImplReadBody()
+{
+ sal_uInt32 nX, nY;
+ signed char nRunCount = 0;
+ sal_uInt8 nDat = 0, nDummy, nRed, nGreen, nBlue;
+ BitmapColor aBitmapColor;
+ nX = nY = 0;
+
+ switch ( mnDestBitDepth )
+ {
+ case 1 :
+ {
+ signed char nBitCount = -1;
+ while (nY < mpFileHeader->nRows && m_rPSD.good())
+ {
+ if ( nBitCount == -1 )
+ {
+ if ( mbCompression ) // else nRunCount = 0 -> so we use only single raw packets
+ {
+ char nTmp(0);
+ m_rPSD.ReadChar(nTmp);
+ nRunCount = nTmp;
+ }
+ }
+ if ( nRunCount & 0x80 ) // a run length packet
+ {
+ const sal_uInt16 nCount = -nRunCount + 1;
+ for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i)
+ {
+ if ( nBitCount == -1 ) // bits left in nDat?
+ {
+ m_rPSD.ReadUChar( nDat );
+ nDat ^= 0xff;
+ nBitCount = 7;
+ }
+ mpBitmap->SetPixel(nY, nX, SanitizePaletteIndex(mvPalette, nDat >> nBitCount--));
+ if ( ++nX == mpFileHeader->nColumns )
+ {
+ nX = 0;
+ nY++;
+ nBitCount = -1;
+ if ( nY == mpFileHeader->nRows )
+ break;
+ }
+ }
+ }
+ else // a raw packet
+ {
+ const sal_uInt16 nCount = (nRunCount & 0x7f) + 1;
+ for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i)
+ {
+ if ( nBitCount == -1 ) // bits left in nDat ?
+ {
+ m_rPSD.ReadUChar( nDat );
+ nDat ^= 0xff;
+ nBitCount = 7;
+ }
+ mpBitmap->SetPixel(nY, nX, SanitizePaletteIndex(mvPalette, nDat >> nBitCount--));
+ if ( ++nX == mpFileHeader->nColumns )
+ {
+ nX = 0;
+ nY++;
+ nBitCount = -1;
+ if ( nY == mpFileHeader->nRows )
+ break;
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case 8 :
+ {
+ while (nY < mpFileHeader->nRows && m_rPSD.good())
+ {
+ if ( mbCompression ) // else nRunCount = 0 -> so we use only single raw packets
+ {
+ char nTmp(0);
+ m_rPSD.ReadChar(nTmp);
+ nRunCount = nTmp;
+ }
+
+ if ( nRunCount & 0x80 ) // a run length packet
+ {
+ m_rPSD.ReadUChar( nDat );
+ if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped
+ m_rPSD.ReadUChar( nDummy );
+ const sal_uInt16 nCount = -nRunCount + 1;
+ for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i)
+ {
+ mpBitmap->SetPixel(nY, nX, SanitizePaletteIndex(mvPalette, nDat));
+ if ( ++nX == mpFileHeader->nColumns )
+ {
+ nX = 0;
+ nY++;
+ if ( nY == mpFileHeader->nRows )
+ break;
+ }
+ }
+ }
+ else // a raw packet
+ {
+ const sal_uInt16 nCount = (nRunCount & 0x7f) + 1;
+ for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i)
+ {
+ m_rPSD.ReadUChar( nDat );
+ if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped
+ m_rPSD.ReadUChar( nDummy );
+ mpBitmap->SetPixel(nY, nX, SanitizePaletteIndex(mvPalette, nDat));
+ if ( ++nX == mpFileHeader->nColumns )
+ {
+ nX = 0;
+ nY++;
+ if ( nY == mpFileHeader->nRows )
+ break;
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case 24 :
+ {
+
+ // the psd format is in plain order (RRRR GGGG BBBB) so we have to set each pixel three times
+ // maybe the format is CCCC MMMM YYYY KKKK
+
+ while (nY < mpFileHeader->nRows && m_rPSD.good())
+ {
+ if ( mbCompression ) // else nRunCount = 0 -> so we use only single raw packets
+ {
+ char nTmp(0);
+ m_rPSD.ReadChar(nTmp);
+ nRunCount = nTmp;
+ }
+
+ if ( nRunCount & 0x80 ) // a run length packet
+ {
+ m_rPSD.ReadUChar( nRed );
+ if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped
+ m_rPSD.ReadUChar( nDummy );
+ const sal_uInt16 nCount = -nRunCount + 1;
+ for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i)
+ {
+ mpBitmap->SetPixel( nY, nX, Color( nRed, sal_uInt8(0), sal_uInt8(0) ) );
+ if ( ++nX == mpFileHeader->nColumns )
+ {
+ nX = 0;
+ nY++;
+ if ( nY == mpFileHeader->nRows )
+ break;
+ }
+ }
+ }
+ else // a raw packet
+ {
+ const sal_uInt16 nCount = (nRunCount & 0x7f) + 1;
+ for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i)
+ {
+ m_rPSD.ReadUChar( nRed );
+ if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped
+ m_rPSD.ReadUChar( nDummy );
+ mpBitmap->SetPixel( nY, nX, Color( nRed, sal_uInt8(0), sal_uInt8(0) ) );
+ if ( ++nX == mpFileHeader->nColumns )
+ {
+ nX = 0;
+ nY++;
+ if ( nY == mpFileHeader->nRows )
+ break;
+ }
+ }
+ }
+ }
+ nY = 0;
+ while (nY < mpFileHeader->nRows && m_rPSD.good())
+ {
+ if ( mbCompression )
+ {
+ char nTmp(0);
+ m_rPSD.ReadChar(nTmp);
+ nRunCount = nTmp;
+ }
+
+ if ( nRunCount & 0x80 ) // a run length packet
+ {
+ m_rPSD.ReadUChar( nGreen );
+ if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped
+ m_rPSD.ReadUChar( nDummy );
+ const sal_uInt16 nCount = -nRunCount + 1;
+ for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i)
+ {
+ aBitmapColor = mpBitmap->GetPixel( nY, nX );
+ mpBitmap->SetPixel( nY, nX, Color( aBitmapColor.GetRed(), nGreen, aBitmapColor.GetBlue() ) );
+ if ( ++nX == mpFileHeader->nColumns )
+ {
+ nX = 0;
+ nY++;
+ if ( nY == mpFileHeader->nRows )
+ break;
+ }
+ }
+ }
+ else // a raw packet
+ {
+ const sal_uInt16 nCount = (nRunCount & 0x7f) + 1;
+ for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i)
+ {
+ m_rPSD.ReadUChar( nGreen );
+ if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped
+ m_rPSD.ReadUChar( nDummy );
+ aBitmapColor = mpBitmap->GetPixel( nY, nX );
+ mpBitmap->SetPixel( nY, nX, Color( aBitmapColor.GetRed(), nGreen, aBitmapColor.GetBlue() ) );
+ if ( ++nX == mpFileHeader->nColumns )
+ {
+ nX = 0;
+ nY++;
+ if ( nY == mpFileHeader->nRows )
+ break;
+ }
+ }
+ }
+ }
+ nY = 0;
+ while (nY < mpFileHeader->nRows && m_rPSD.good())
+ {
+ if ( mbCompression )
+ {
+ char nTmp(0);
+ m_rPSD.ReadChar(nTmp);
+ nRunCount = nTmp;
+ }
+
+ if ( nRunCount & 0x80 ) // a run length packet
+ {
+ m_rPSD.ReadUChar( nBlue );
+ if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped
+ m_rPSD.ReadUChar( nDummy );
+ const sal_uInt16 nCount = -nRunCount + 1;
+ for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i)
+ {
+ aBitmapColor = mpBitmap->GetPixel( nY, nX );
+ mpBitmap->SetPixel( nY, nX, Color( aBitmapColor.GetRed(), aBitmapColor.GetGreen(), nBlue ) );
+ if ( ++nX == mpFileHeader->nColumns )
+ {
+ nX = 0;
+ nY++;
+ if ( nY == mpFileHeader->nRows )
+ break;
+ }
+ }
+ }
+ else // a raw packet
+ {
+ const sal_uInt16 nCount = (nRunCount & 0x7f) + 1;
+ for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i)
+ {
+ m_rPSD.ReadUChar( nBlue );
+ if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped
+ m_rPSD.ReadUChar( nDummy );
+ aBitmapColor = mpBitmap->GetPixel( nY, nX );
+ mpBitmap->SetPixel( nY, nX, Color( aBitmapColor.GetRed(), aBitmapColor.GetGreen(), nBlue ) );
+ if ( ++nX == mpFileHeader->nColumns )
+ {
+ nX = 0;
+ nY++;
+ if ( nY == mpFileHeader->nRows )
+ break;
+ }
+ }
+ }
+ }
+ if (mpFileHeader->nMode == PSD_CMYK && m_rPSD.good())
+ {
+ sal_uInt32 nBlack, nBlackMax = 0;
+ std::vector<sal_uInt8> aBlack(mpFileHeader->nRows * mpFileHeader->nColumns, 0);
+ nY = 0;
+ while (nY < mpFileHeader->nRows && m_rPSD.good())
+ {
+ if ( mbCompression ) // else nRunCount = 0 -> so we use only single raw packets
+ {
+ char nTmp(0);
+ m_rPSD.ReadChar(nTmp);
+ nRunCount = nTmp;
+ }
+
+ if ( nRunCount & 0x80 ) // a run length packet
+ {
+ m_rPSD.ReadUChar( nDat );
+
+ if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped
+ m_rPSD.ReadUChar( nDummy );
+
+ for ( sal_uInt16 i = 0; i < ( -nRunCount + 1 ); i++ )
+ {
+ nBlack = mpBitmap->GetPixel( nY, nX ).GetRed() + nDat;
+ if ( nBlack > nBlackMax )
+ nBlackMax = nBlack;
+ nBlack = mpBitmap->GetPixel( nY, nX ).GetGreen() + nDat;
+ if ( nBlack > nBlackMax )
+ nBlackMax = nBlack;
+ nBlack = mpBitmap->GetPixel( nY, nX ).GetBlue() + nDat;
+ if ( nBlack > nBlackMax )
+ nBlackMax = nBlack;
+ aBlack[ nX + nY * mpFileHeader->nColumns ] = nDat ^ 0xff;
+ if ( ++nX == mpFileHeader->nColumns )
+ {
+ nX = 0;
+ nY++;
+ if ( nY == mpFileHeader->nRows )
+ break;
+ }
+ }
+ }
+ else // a raw packet
+ {
+ for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ )
+ {
+ m_rPSD.ReadUChar( nDat );
+
+ if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped
+ m_rPSD.ReadUChar( nDummy );
+ nBlack = mpBitmap->GetPixel( nY, nX ).GetRed() + nDat;
+ if ( nBlack > nBlackMax )
+ nBlackMax = nBlack;
+ nBlack = mpBitmap->GetPixel( nY, nX ).GetGreen() + nDat;
+ if ( nBlack > nBlackMax )
+ nBlackMax = nBlack;
+ nBlack = mpBitmap->GetPixel( nY, nX ).GetBlue() + nDat;
+ if ( nBlack > nBlackMax )
+ nBlackMax = nBlack;
+ aBlack[ nX + nY * mpFileHeader->nColumns ] = nDat ^ 0xff;
+ if ( ++nX == mpFileHeader->nColumns )
+ {
+ nX = 0;
+ nY++;
+ if ( nY == mpFileHeader->nRows )
+ break;
+ }
+ }
+ }
+ }
+
+ for ( nY = 0; nY < mpFileHeader->nRows; nY++ )
+ {
+ for ( nX = 0; nX < mpFileHeader->nColumns; nX++ )
+ {
+ sal_Int32 nDAT = aBlack[ nX + nY * mpFileHeader->nColumns ] * ( nBlackMax - 256 ) / 0x1ff;
+
+ aBitmapColor = mpBitmap->GetPixel( nY, nX );
+ sal_uInt8 cR = static_cast<sal_uInt8>(MinMax( aBitmapColor.GetRed() - nDAT, 0, 255L ));
+ sal_uInt8 cG = static_cast<sal_uInt8>(MinMax( aBitmapColor.GetGreen() - nDAT, 0, 255L ));
+ sal_uInt8 cB = static_cast<sal_uInt8>(MinMax( aBitmapColor.GetBlue() - nDAT, 0, 255L ));
+ mpBitmap->SetPixel( nY, nX, Color( cR, cG, cB ) );
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ if (mbTransparent && m_rPSD.good())
+ {
+ // the psd is 24 or 8 bit grafix + alphachannel
+
+ nY = nX = 0;
+ while ( nY < mpFileHeader->nRows )
+ {
+ if ( mbCompression ) // else nRunCount = 0 -> so we use only single raw packets
+ {
+ char nTmp(0);
+ m_rPSD.ReadChar(nTmp);
+ nRunCount = nTmp;
+ }
+
+ if ( nRunCount & 0x80 ) // a run length packet
+ {
+ m_rPSD.ReadUChar( nDat );
+ if ( nDat )
+ nDat = 0;
+ else
+ nDat = 1;
+ if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped
+ m_rPSD.ReadUChar( nDummy );
+ for ( sal_uInt16 i = 0; i < ( -nRunCount + 1 ); i++ )
+ {
+ mpBitmap->SetAlpha(nY, nX, nDat ? 0 : 255);
+ if ( ++nX == mpFileHeader->nColumns )
+ {
+ nX = 0;
+ nY++;
+ if ( nY == mpFileHeader->nRows )
+ break;
+ }
+ }
+ }
+ else // a raw packet
+ {
+ for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ )
+ {
+ m_rPSD.ReadUChar( nDat );
+ if ( nDat )
+ nDat = 0;
+ else
+ nDat = 1;
+ if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped
+ m_rPSD.ReadUChar( nDummy );
+ mpBitmap->SetAlpha(nY, nX, nDat ? 0 : 255);
+ if ( ++nX == mpFileHeader->nColumns )
+ {
+ nX = 0;
+ nY++;
+ if ( nY == mpFileHeader->nRows )
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return m_rPSD.good();
+}
+
+//================== GraphicImport - the exported function ================
+
+bool ImportPsdGraphic(SvStream& rStream, Graphic& rGraphic)
+{
+ PSDReader aPSDReader(rStream);
+ return aPSDReader.ReadPSD(rGraphic);
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/iras/iras.cxx b/vcl/source/filter/iras/iras.cxx
new file mode 100644
index 000000000..49cfe2bef
--- /dev/null
+++ b/vcl/source/filter/iras/iras.cxx
@@ -0,0 +1,414 @@
+/* -*- 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 <vcl/graph.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <filter/RasReader.hxx>
+
+class FilterConfigItem;
+
+#define RAS_TYPE_OLD 0x00000000 // supported formats by this filter
+#define RAS_TYPE_STANDARD 0x00000001
+#define RAS_TYPE_BYTE_ENCODED 0x00000002
+#define RAS_TYPE_RGB_FORMAT 0x00000003
+
+#define RAS_COLOR_NO_MAP 0x00000000
+#define RAS_COLOR_RGB_MAP 0x00000001
+#define RAS_COLOR_RAW_MAP 0x00000002
+
+#define SUNRASTER_MAGICNUMBER 0x59a66a95
+
+//============================ RASReader ==================================
+
+namespace {
+
+class RASReader {
+
+private:
+
+ SvStream& m_rRAS; // the RAS file to be read in
+
+ bool mbStatus;
+ sal_Int32 mnWidth, mnHeight; // image dimensions in pixels
+ sal_uInt16 mnDstBitsPerPix;
+ sal_uInt16 mnDstColors;
+ sal_Int32 mnDepth, mnImageDatSize, mnType;
+ sal_Int32 mnColorMapType, mnColorMapSize;
+ sal_uInt8 mnRepCount, mnRepVal; // RLE Decoding
+
+ bool ImplReadBody(vcl::bitmap::RawBitmap&, std::vector<Color> const & rvPalette);
+ bool ImplReadHeader();
+ sal_uInt8 ImplGetByte();
+
+public:
+ explicit RASReader(SvStream &rRAS);
+ bool ReadRAS(Graphic & rGraphic);
+};
+
+}
+
+//=================== Methods of RASReader ==============================
+
+RASReader::RASReader(SvStream &rRAS)
+ : m_rRAS(rRAS)
+ , mbStatus(true)
+ , mnWidth(0)
+ , mnHeight(0)
+ , mnDstBitsPerPix(0)
+ , mnDstColors(0)
+ , mnDepth(0)
+ , mnImageDatSize(0)
+ , mnType(0)
+ , mnColorMapType(0)
+ , mnColorMapSize(0)
+ , mnRepCount(0)
+ , mnRepVal(0)
+{
+}
+
+bool RASReader::ReadRAS(Graphic & rGraphic)
+{
+ sal_uInt32 nMagicNumber;
+
+ if ( m_rRAS.GetError() )
+ return false;
+
+ m_rRAS.SetEndian( SvStreamEndian::BIG );
+ m_rRAS.ReadUInt32( nMagicNumber );
+ if (!m_rRAS.good() || nMagicNumber != SUNRASTER_MAGICNUMBER)
+ return false;
+
+ // Kopf einlesen:
+
+ mbStatus = ImplReadHeader();
+ if ( !mbStatus )
+ return false;
+
+ std::vector<Color> aPalette;
+ bool bOk = true;
+
+ if ( mnDstBitsPerPix <= 8 ) // pallets pictures
+ {
+ bool bPalette(false);
+
+ if ( mnColorMapType == RAS_COLOR_RAW_MAP ) // RAW color map is skipped
+ {
+ sal_uInt64 nCurPos = m_rRAS.Tell();
+ bOk = checkSeek(m_rRAS, nCurPos + mnColorMapSize);
+ }
+ else if ( mnColorMapType == RAS_COLOR_RGB_MAP ) // we can read out the RGB
+ {
+ mnDstColors = static_cast<sal_uInt16>( mnColorMapSize / 3 );
+
+ if ( ( 1 << mnDstBitsPerPix ) < mnDstColors )
+ return false;
+
+ if ( ( mnDstColors >= 2 ) && ( ( mnColorMapSize % 3 ) == 0 ) )
+ {
+ aPalette.resize(mnDstColors);
+ sal_uInt16 i;
+ sal_uInt8 nRed[256], nGreen[256], nBlue[256];
+ for ( i = 0; i < mnDstColors; i++ ) m_rRAS.ReadUChar( nRed[ i ] );
+ for ( i = 0; i < mnDstColors; i++ ) m_rRAS.ReadUChar( nGreen[ i ] );
+ for ( i = 0; i < mnDstColors; i++ ) m_rRAS.ReadUChar( nBlue[ i ] );
+ for ( i = 0; i < mnDstColors; i++ )
+ {
+ aPalette[i] = Color(nRed[ i ], nGreen[ i ], nBlue[ i ]);
+ }
+ bPalette = true;
+ }
+ else
+ return false;
+
+ }
+ else if ( mnColorMapType != RAS_COLOR_NO_MAP ) // everything else is not standard
+ return false;
+
+ if (!bPalette)
+ {
+ mnDstColors = 1 << mnDstBitsPerPix;
+ aPalette.resize(mnDstColors);
+ for ( sal_uInt16 i = 0; i < mnDstColors; i++ )
+ {
+ sal_uInt8 nCount = 255 - ( 255 * i / ( mnDstColors - 1 ) );
+ aPalette[i] = Color(nCount, nCount, nCount);
+ }
+ }
+ }
+ else
+ {
+ if ( mnColorMapType != RAS_COLOR_NO_MAP ) // when graphic has more than 256 colors and a color map we skip
+ { // the colormap
+ sal_uInt64 nCurPos = m_rRAS.Tell();
+ bOk = checkSeek(m_rRAS, nCurPos + mnColorMapSize);
+ }
+ }
+
+ if (!bOk)
+ return false;
+
+ //The RLE packets are typically three bytes in size:
+ //The first byte is a Flag Value indicating the type of RLE packet.
+ //The second byte is the Run Count.
+ //The third byte is the Run Value.
+ //
+ //for the sake of simplicity we'll assume that RAS_TYPE_BYTE_ENCODED can
+ //describe data 255 times larger than the data stored
+ size_t nMaxCompression = mnType != RAS_TYPE_BYTE_ENCODED ? 1 : 255;
+ sal_Int32 nBitSize;
+ if (o3tl::checked_multiply<sal_Int32>(mnWidth, mnHeight, nBitSize) || o3tl::checked_multiply<sal_Int32>(nBitSize, mnDepth, nBitSize))
+ return false;
+ if (m_rRAS.remainingSize() * nMaxCompression < static_cast<sal_uInt32>(nBitSize) / 8)
+ return false;
+
+ vcl::bitmap::RawBitmap aBmp(Size(mnWidth, mnHeight), 24);
+
+ // read in the bitmap data
+ mbStatus = ImplReadBody(aBmp, aPalette);
+
+ if ( mbStatus )
+ rGraphic = vcl::bitmap::CreateFromData(std::move(aBmp));
+
+ return mbStatus;
+}
+
+bool RASReader::ImplReadHeader()
+{
+ m_rRAS.ReadInt32(mnWidth).ReadInt32(mnHeight).ReadInt32(mnDepth).ReadInt32(mnImageDatSize).ReadInt32(mnType).ReadInt32(mnColorMapType).ReadInt32(mnColorMapSize);
+
+ if (!m_rRAS.good() || mnWidth <= 0 || mnHeight <= 0 || mnImageDatSize <= 0)
+ mbStatus = false;
+
+ switch ( mnDepth )
+ {
+ case 24 :
+ case 8 :
+ case 1 :
+ mnDstBitsPerPix = static_cast<sal_uInt16>(mnDepth);
+ break;
+ case 32 :
+ mnDstBitsPerPix = 24;
+ break;
+
+ default :
+ mbStatus = false;
+ }
+
+ switch ( mnType )
+ {
+ case RAS_TYPE_OLD :
+ case RAS_TYPE_STANDARD :
+ case RAS_TYPE_RGB_FORMAT :
+ case RAS_TYPE_BYTE_ENCODED : // this type will be supported later
+ break;
+
+ default:
+ mbStatus = false;
+ }
+ return mbStatus;
+}
+
+namespace
+{
+ const Color& SanitizePaletteIndex(std::vector<Color> const & rvPalette, sal_uInt8 nIndex)
+ {
+ if (nIndex >= rvPalette.size())
+ {
+ auto nSanitizedIndex = nIndex % rvPalette.size();
+ SAL_WARN_IF(nIndex != nSanitizedIndex, "filter.ras", "invalid colormap index: "
+ << static_cast<unsigned int>(nIndex) << ", colormap len is: "
+ << rvPalette.size());
+ nIndex = nSanitizedIndex;
+ }
+ return rvPalette[nIndex];
+ }
+}
+
+bool RASReader::ImplReadBody(vcl::bitmap::RawBitmap& rBitmap, std::vector<Color> const & rvPalette)
+{
+ sal_Int32 x, y;
+ sal_uInt8 nRed, nGreen, nBlue;
+ switch ( mnDstBitsPerPix )
+ {
+ case 1 :
+ {
+ sal_uInt8 nDat = 0;
+ for (y = 0; y < mnHeight && mbStatus; ++y)
+ {
+ for (x = 0; x < mnWidth && mbStatus; ++x)
+ {
+ if (!(x & 7))
+ {
+ nDat = ImplGetByte();
+ if (!m_rRAS.good())
+ mbStatus = false;
+ }
+ rBitmap.SetPixel(y, x, SanitizePaletteIndex(rvPalette,
+ sal::static_int_cast< sal_uInt8 >(
+ nDat >> ( ( x & 7 ) ^ 7 ))));
+ }
+ if (!( ( x - 1 ) & 0x8 ) )
+ {
+ ImplGetByte(); // WORD ALIGNMENT ???
+ if (!m_rRAS.good())
+ mbStatus = false;
+ }
+ }
+ break;
+ }
+
+ case 8 :
+ for (y = 0; y < mnHeight && mbStatus; ++y)
+ {
+ for (x = 0; x < mnWidth && mbStatus; ++x)
+ {
+ sal_uInt8 nDat = ImplGetByte();
+ rBitmap.SetPixel(y, x, SanitizePaletteIndex(rvPalette, nDat));
+ if (!m_rRAS.good())
+ mbStatus = false;
+ }
+ if ( x & 1 )
+ {
+ ImplGetByte(); // WORD ALIGNMENT ???
+ if (!m_rRAS.good())
+ mbStatus = false;
+ }
+ }
+ break;
+
+ case 24 :
+ switch ( mnDepth )
+ {
+
+ case 24 :
+ for (y = 0; y < mnHeight && mbStatus; ++y)
+ {
+ for (x = 0; x < mnWidth && mbStatus; ++x)
+ {
+ if ( mnType == RAS_TYPE_RGB_FORMAT )
+ {
+ nRed = ImplGetByte();
+ nGreen = ImplGetByte();
+ nBlue = ImplGetByte();
+ }
+ else
+ {
+ nBlue = ImplGetByte();
+ nGreen = ImplGetByte();
+ nRed = ImplGetByte();
+ }
+ rBitmap.SetPixel(y, x, Color(nRed, nGreen, nBlue));
+ if (!m_rRAS.good())
+ mbStatus = false;
+ }
+ if ( x & 1 )
+ {
+ ImplGetByte(); // WORD ALIGNMENT ???
+ if (!m_rRAS.good())
+ mbStatus = false;
+ }
+ }
+ break;
+
+ case 32 :
+ for (y = 0; y < mnHeight && mbStatus; ++y)
+ {
+ for (x = 0; x < mnWidth && mbStatus; ++x)
+ {
+ ImplGetByte(); // pad byte > nil
+ if ( mnType == RAS_TYPE_RGB_FORMAT )
+ {
+ nRed = ImplGetByte();
+ nGreen = ImplGetByte();
+ nBlue = ImplGetByte();
+ }
+ else
+ {
+ nBlue = ImplGetByte();
+ nGreen = ImplGetByte();
+ nRed = ImplGetByte();
+ }
+ rBitmap.SetPixel(y, x, Color(nRed, nGreen, nBlue));
+ if (!m_rRAS.good())
+ mbStatus = false;
+ }
+ }
+ break;
+ }
+ break;
+
+ default:
+ mbStatus = false;
+ break;
+ }
+ return mbStatus;
+}
+
+sal_uInt8 RASReader::ImplGetByte()
+{
+ sal_uInt8 nRetVal(0);
+ if ( mnType != RAS_TYPE_BYTE_ENCODED )
+ {
+ m_rRAS.ReadUChar( nRetVal );
+ return nRetVal;
+ }
+ else
+ {
+ if ( mnRepCount )
+ {
+ mnRepCount--;
+ return mnRepVal;
+ }
+ else
+ {
+ m_rRAS.ReadUChar( nRetVal );
+ if ( nRetVal != 0x80 )
+ return nRetVal;
+ m_rRAS.ReadUChar( nRetVal );
+ if ( nRetVal == 0 )
+ return 0x80;
+ mnRepCount = nRetVal ;
+ m_rRAS.ReadUChar( mnRepVal );
+ return mnRepVal;
+ }
+ }
+}
+
+//================== GraphicImport - the exported function ================
+
+bool ImportRasGraphic( SvStream & rStream, Graphic & rGraphic)
+{
+ bool bRet = false;
+
+ try
+ {
+ RASReader aRASReader(rStream);
+ bRet = aRASReader.ReadRAS(rGraphic );
+ }
+ catch (...)
+ {
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/itga/itga.cxx b/vcl/source/filter/itga/itga.cxx
new file mode 100644
index 000000000..6b3b3037f
--- /dev/null
+++ b/vcl/source/filter/itga/itga.cxx
@@ -0,0 +1,790 @@
+/* -*- 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 <vcl/graph.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <tools/stream.hxx>
+#include <memory>
+#include <filter/TgaReader.hxx>
+
+class FilterConfigItem;
+
+//============================ TGAReader ==================================
+
+namespace {
+
+struct TGAFileHeader
+{
+ sal_uInt8 nImageIDLength;
+ sal_uInt8 nColorMapType;
+ sal_uInt8 nImageType;
+ sal_uInt16 nColorMapFirstEntryIndex;
+ sal_uInt16 nColorMapLength;
+ sal_uInt8 nColorMapEntrySize;
+ sal_uInt16 nColorMapXOrigin;
+ sal_uInt16 nColorMapYOrigin;
+ sal_uInt16 nImageWidth;
+ sal_uInt16 nImageHeight;
+ sal_uInt8 nPixelDepth;
+ sal_uInt8 nImageDescriptor;
+};
+
+#define SizeOfTGAFileFooter 26
+
+struct TGAFileFooter
+{
+ sal_uInt32 nExtensionFileOffset;
+ sal_uInt32 nDeveloperDirectoryOffset;
+ sal_uInt32 nSignature[4];
+ sal_uInt8 nPadByte;
+ sal_uInt8 nStringTerminator;
+};
+
+#define SizeOfTGAExtension 495
+
+struct TGAExtension
+{
+ sal_uInt16 nExtensionSize;
+ char sAuthorName[41];
+ char sAuthorComment[324];
+ char sDateTimeStamp[12];
+ char sJobNameID[41];
+ char sSoftwareID[41];
+ sal_uInt16 nSoftwareVersionNumber;
+ sal_uInt8 nSoftwareVersionLetter;
+ sal_uInt32 nKeyColor;
+ sal_uInt16 nPixelAspectRatioNumerator;
+ sal_uInt16 nPixelAspectRatioDeNumerator;
+ sal_uInt16 nGammaValueNumerator;
+ sal_uInt16 nGammaValueDeNumerator;
+ sal_uInt32 nColorCorrectionOffset;
+ sal_uInt32 nPostageStampOffset;
+ sal_uInt32 nScanLineOffset;
+ sal_uInt8 nAttributesType;
+};
+
+class TGAReader {
+
+private:
+
+ SvStream& m_rTGA;
+
+ std::unique_ptr<vcl::bitmap::RawBitmap> mpBitmap;
+ std::vector<Color> mvPalette;
+ std::unique_ptr<TGAFileHeader>
+ mpFileHeader;
+ std::unique_ptr<TGAFileFooter>
+ mpFileFooter;
+ std::unique_ptr<TGAExtension>
+ mpExtension;
+ std::unique_ptr<sal_uInt32[]>
+ mpColorMap;
+
+ bool mbStatus;
+
+ sal_uInt8 mnTGAVersion; // Enhanced TGA is defined as Version 2.0
+ sal_uInt16 mnDestBitDepth;
+ bool mbIndexing; // sal_True if source contains indexing color values
+ bool mbEncoding; // sal_True if source is compressed
+
+ bool ImplReadHeader();
+ bool ImplReadPalette();
+ bool ImplReadBody();
+
+public:
+ explicit TGAReader(SvStream &rTGA);
+ bool ReadTGA(Graphic &rGraphic);
+};
+
+}
+
+//=================== Methods of TGAReader ==============================
+
+TGAReader::TGAReader(SvStream &rTGA)
+ : m_rTGA(rTGA)
+ , mbStatus(true)
+ , mnTGAVersion(1)
+ , mnDestBitDepth(8)
+ , mbIndexing(false)
+ , mbEncoding(false)
+{
+}
+
+bool TGAReader::ReadTGA(Graphic & rGraphic)
+{
+ if ( m_rTGA.GetError() )
+ return false;
+
+ m_rTGA.SetEndian( SvStreamEndian::LITTLE );
+
+ // Kopf einlesen:
+
+ if ( !m_rTGA.GetError() )
+ {
+ mbStatus = ImplReadHeader();
+ if (mbStatus)
+ mbStatus = mpFileHeader->nImageWidth && mpFileHeader->nImageHeight;
+ if (mbStatus)
+ {
+ sal_Size nSize = mpFileHeader->nImageWidth;
+ nSize *= mpFileHeader->nImageHeight;
+ if (nSize > SAL_MAX_INT32/2/3)
+ return false;
+
+ mpBitmap.reset( new vcl::bitmap::RawBitmap( Size( mpFileHeader->nImageWidth, mpFileHeader->nImageHeight ), 24 ) );
+ if ( mbIndexing )
+ mbStatus = ImplReadPalette();
+ if ( mbStatus )
+ mbStatus = ImplReadBody();
+
+ if ( mbStatus )
+ rGraphic = vcl::bitmap::CreateFromData(std::move(*mpBitmap));
+ }
+ }
+ return mbStatus;
+}
+
+
+bool TGAReader::ImplReadHeader()
+{
+ mpFileHeader.reset( new TGAFileHeader );
+
+ m_rTGA.ReadUChar( mpFileHeader->nImageIDLength ).ReadUChar( mpFileHeader->nColorMapType ).ReadUChar( mpFileHeader->nImageType ). ReadUInt16( mpFileHeader->nColorMapFirstEntryIndex ).ReadUInt16( mpFileHeader->nColorMapLength ).ReadUChar( mpFileHeader->nColorMapEntrySize ). ReadUInt16( mpFileHeader->nColorMapXOrigin ).ReadUInt16( mpFileHeader->nColorMapYOrigin ).ReadUInt16( mpFileHeader->nImageWidth ). ReadUInt16( mpFileHeader->nImageHeight ).ReadUChar( mpFileHeader->nPixelDepth ).ReadUChar( mpFileHeader->nImageDescriptor );
+
+ if ( !m_rTGA.good())
+ return false;
+
+ if ( mpFileHeader->nColorMapType > 1 )
+ return false;
+ if ( mpFileHeader->nColorMapType == 1 )
+ mbIndexing = true;
+
+ // first we want to get the version
+ mpFileFooter.reset( new TGAFileFooter ); // read the TGA-File-Footer to determine whether
+ // we got an old TGA format or the new one
+
+ sal_uInt64 nCurStreamPos = m_rTGA.Tell();
+ m_rTGA.Seek( STREAM_SEEK_TO_END );
+ sal_uInt64 nTemp = m_rTGA.Tell();
+ m_rTGA.Seek( nTemp - SizeOfTGAFileFooter );
+
+ m_rTGA.ReadUInt32( mpFileFooter->nExtensionFileOffset ).ReadUInt32( mpFileFooter->nDeveloperDirectoryOffset ). ReadUInt32( mpFileFooter->nSignature[0] ).ReadUInt32( mpFileFooter->nSignature[1] ).ReadUInt32( mpFileFooter->nSignature[2] ). ReadUInt32( mpFileFooter->nSignature[3] ).ReadUChar( mpFileFooter->nPadByte ).ReadUChar( mpFileFooter->nStringTerminator );
+
+
+ if ( !m_rTGA.good())
+ return false;
+
+ // check for sal_True, VISI, ON-X, FILE in the signatures
+ if ( mpFileFooter->nSignature[ 0 ] == (('T'<<24)|('R'<<16)|('U'<<8)|'E') &&
+ mpFileFooter->nSignature[ 1 ] == (('V'<<24)|('I'<<16)|('S'<<8)|'I') &&
+ mpFileFooter->nSignature[ 2 ] == (('O'<<24)|('N'<<16)|('-'<<8)|'X') &&
+ mpFileFooter->nSignature[ 3 ] == (('F'<<24)|('I'<<16)|('L'<<8)|'E') )
+ {
+ mpExtension.reset( new TGAExtension );
+
+ m_rTGA.Seek( mpFileFooter->nExtensionFileOffset );
+ m_rTGA.ReadUInt16( mpExtension->nExtensionSize );
+ if ( !m_rTGA.good())
+ return false;
+ if ( mpExtension->nExtensionSize >= SizeOfTGAExtension )
+ {
+ mnTGAVersion = 2;
+
+ m_rTGA.ReadBytes(mpExtension->sAuthorName, 41);
+ m_rTGA.ReadBytes(mpExtension->sAuthorComment, 324);
+ m_rTGA.ReadBytes(mpExtension->sDateTimeStamp, 12);
+ m_rTGA.ReadBytes(mpExtension->sJobNameID, 12);
+ m_rTGA.ReadChar( mpExtension->sJobNameID[ 0 ] ).ReadChar( mpExtension->sJobNameID[ 1 ] ).ReadChar( mpExtension->sJobNameID[ 2 ] );
+ m_rTGA.ReadBytes(mpExtension->sSoftwareID, 41);
+ m_rTGA.ReadUInt16( mpExtension->nSoftwareVersionNumber ).ReadUChar( mpExtension->nSoftwareVersionLetter )
+ .ReadUInt32( mpExtension->nKeyColor ).ReadUInt16( mpExtension->nPixelAspectRatioNumerator )
+ .ReadUInt16( mpExtension->nPixelAspectRatioDeNumerator ).ReadUInt16( mpExtension->nGammaValueNumerator )
+ .ReadUInt16( mpExtension->nGammaValueDeNumerator ).ReadUInt32( mpExtension->nColorCorrectionOffset )
+ .ReadUInt32( mpExtension->nPostageStampOffset ).ReadUInt32( mpExtension->nScanLineOffset )
+ .ReadUChar( mpExtension->nAttributesType );
+
+ if ( !m_rTGA.good())
+ return false;
+ }
+ }
+ m_rTGA.Seek( nCurStreamPos );
+
+ // using the TGA file specification this was the correct form but adobe photoshop sets nImageDescriptor
+ // equal to nPixelDepth
+ // mnDestBitDepth = mpFileHeader->nPixelDepth - ( mpFileHeader->nImageDescriptor & 0xf );
+ mnDestBitDepth = mpFileHeader->nPixelDepth;
+
+ if ( mnDestBitDepth == 8 ) // this is a patch for grayscale pictures not including a palette
+ mbIndexing = true;
+
+ if ( mnDestBitDepth > 32 ) // maybe the pixeldepth is invalid
+ return false;
+ else if ( mnDestBitDepth > 8 )
+ mnDestBitDepth = 24;
+ else if ( mnDestBitDepth > 4 )
+ mnDestBitDepth = 8;
+ else if ( mnDestBitDepth > 2 )
+ mnDestBitDepth = 4;
+
+ if ( !mbIndexing && ( mnDestBitDepth < 15 ) )
+ return false;
+
+ switch ( mpFileHeader->nImageType )
+ {
+ case 9 : // encoding for colortype 9, 10, 11
+ case 10 :
+ case 11 :
+ mbEncoding = true;
+ break;
+ }
+
+ if ( mpFileHeader->nImageIDLength ) // skip the Image ID
+ m_rTGA.SeekRel( mpFileHeader->nImageIDLength );
+
+ return mbStatus;
+}
+
+
+bool TGAReader::ImplReadBody()
+{
+
+ sal_uInt16 nXCount, nYCount, nRGB16;
+ sal_uInt8 nRed, nGreen, nBlue, nRunCount, nDummy, nDepth;
+
+ // this four variables match the image direction
+ tools::Long nY, nYAdd, nX, nXAdd, nXStart;
+
+ nX = nXStart = nY = 0;
+ nXCount = nYCount = 0;
+ nYAdd = nXAdd = 1;
+
+ if ( mpFileHeader->nImageDescriptor & 0x10 )
+ {
+ nX = nXStart = mpFileHeader->nImageWidth - 1;
+ nXAdd -= 2;
+ }
+
+ if ( !(mpFileHeader->nImageDescriptor & 0x20 ) )
+ {
+ nY = mpFileHeader->nImageHeight - 1;
+ nYAdd -=2;
+ }
+
+ nDepth = mpFileHeader->nPixelDepth;
+
+ if ( mbEncoding )
+ {
+ if ( mbIndexing )
+ {
+ switch( nDepth )
+ {
+ // 16 bit encoding + indexing
+ case 16 :
+ while ( nYCount < mpFileHeader->nImageHeight )
+ {
+ m_rTGA.ReadUChar( nRunCount );
+ if ( !m_rTGA.good())
+ return false;
+ if ( nRunCount & 0x80 ) // a run length packet
+ {
+ m_rTGA.ReadUInt16( nRGB16 );
+ if (!m_rTGA.good())
+ return false;
+ if ( nRGB16 >= mpFileHeader->nColorMapLength )
+ return false;
+ nRed = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] >> 16 );
+ nGreen = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] >> 8 );
+ nBlue = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] );
+ for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ )
+ {
+ mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) );
+ nX += nXAdd;
+ nXCount++;
+ if ( nXCount == mpFileHeader->nImageWidth )
+ {
+ nX = nXStart;
+ nXCount = 0;
+ nY += nYAdd;
+ nYCount++;
+
+ if( nYCount >= mpFileHeader->nImageHeight )
+ break;
+ }
+ }
+ }
+ else // a raw packet
+ {
+ for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ )
+ {
+ m_rTGA.ReadUInt16( nRGB16 );
+ if ( !m_rTGA.good())
+ return false;
+ if ( nRGB16 >= mpFileHeader->nColorMapLength )
+ return false;
+ nRed = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] >> 16 );
+ nGreen = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] >> 8 );
+ nBlue = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] );
+ if ( !m_rTGA.good())
+ return false;
+ mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) );
+ nX += nXAdd;
+ nXCount++;
+ if ( nXCount == mpFileHeader->nImageWidth )
+ {
+ nX = nXStart;
+ nXCount = 0;
+ nY += nYAdd;
+ nYCount++;
+
+ if( nYCount >= mpFileHeader->nImageHeight )
+ break;
+ }
+ }
+ }
+ }
+ break;
+
+ // 8 bit encoding + indexing
+ case 8 :
+ while ( nYCount < mpFileHeader->nImageHeight )
+ {
+ m_rTGA.ReadUChar( nRunCount );
+ if ( !m_rTGA.good())
+ return false;
+ if ( nRunCount & 0x80 ) // a run length packet
+ {
+ m_rTGA.ReadUChar( nDummy );
+ if ( !m_rTGA.good())
+ return false;
+ if ( nDummy >= mpFileHeader->nColorMapLength )
+ return false;
+ for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ )
+ {
+ mpBitmap->SetPixel( nY, nX, mvPalette[nDummy] );
+ nX += nXAdd;
+ nXCount++;
+ if ( nXCount == mpFileHeader->nImageWidth )
+ {
+ nX = nXStart;
+ nXCount = 0;
+ nY += nYAdd;
+ nYCount++;
+
+ if( nYCount >= mpFileHeader->nImageHeight )
+ break;
+ }
+ }
+ }
+ else // a raw packet
+ {
+ for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ )
+ {
+ m_rTGA.ReadUChar( nDummy );
+ if ( !m_rTGA.good())
+ return false;
+ if ( nDummy >= mpFileHeader->nColorMapLength )
+ return false;
+ mpBitmap->SetPixel( nY, nX, mvPalette[nDummy] );
+ nX += nXAdd;
+ nXCount++;
+ if ( nXCount == mpFileHeader->nImageWidth )
+ {
+ nX = nXStart;
+ nXCount = 0;
+ nY += nYAdd;
+ nYCount++;
+
+ if( nYCount >= mpFileHeader->nImageHeight )
+ break;
+ }
+ }
+ }
+ }
+ break;
+ default:
+ return false;
+ }
+ }
+ else
+ {
+ switch( nDepth )
+ {
+ // 32 bit transparent true color encoding
+ case 32 :
+ {
+ while ( nYCount < mpFileHeader->nImageHeight )
+ {
+ m_rTGA.ReadUChar( nRunCount );
+ if ( !m_rTGA.good())
+ return false;
+ if ( nRunCount & 0x80 ) // a run length packet
+ {
+ m_rTGA.ReadUChar( nBlue ).ReadUChar( nGreen ).ReadUChar( nRed ).ReadUChar( nDummy );
+ if ( !m_rTGA.good())
+ return false;
+ for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ )
+ {
+ mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) );
+ nX += nXAdd;
+ nXCount++;
+ if ( nXCount == mpFileHeader->nImageWidth )
+ {
+ nX = nXStart;
+ nXCount = 0;
+ nY += nYAdd;
+ nYCount++;
+
+ if( nYCount >= mpFileHeader->nImageHeight )
+ break;
+ }
+ }
+ }
+ else // a raw packet
+ {
+ for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ )
+ {
+ m_rTGA.ReadUChar( nBlue ).ReadUChar( nGreen ).ReadUChar( nRed ).ReadUChar( nDummy );
+ if ( !m_rTGA.good())
+ return false;
+ mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) );
+ nX += nXAdd;
+ nXCount++;
+ if ( nXCount == mpFileHeader->nImageWidth )
+ {
+ nX = nXStart;
+ nXCount = 0;
+ nY += nYAdd;
+ nYCount++;
+
+ if( nYCount >= mpFileHeader->nImageHeight )
+ break;
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ // 24 bit true color encoding
+ case 24 :
+ while ( nYCount < mpFileHeader->nImageHeight )
+ {
+ m_rTGA.ReadUChar( nRunCount );
+ if ( !m_rTGA.good())
+ return false;
+ if ( nRunCount & 0x80 ) // a run length packet
+ {
+ m_rTGA.ReadUChar( nBlue ).ReadUChar( nGreen ).ReadUChar( nRed );
+ if ( !m_rTGA.good())
+ return false;
+ for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ )
+ {
+ mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) );
+ nX += nXAdd;
+ nXCount++;
+ if ( nXCount == mpFileHeader->nImageWidth )
+ {
+ nX = nXStart;
+ nXCount = 0;
+ nY += nYAdd;
+ nYCount++;
+
+ if( nYCount >= mpFileHeader->nImageHeight )
+ break;
+ }
+ }
+ }
+ else // a raw packet
+ {
+ for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ )
+ {
+ m_rTGA.ReadUChar( nBlue ).ReadUChar( nGreen ).ReadUChar( nRed );
+ if ( !m_rTGA.good())
+ return false;
+ mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) );
+ nX += nXAdd;
+ nXCount++;
+ if ( nXCount == mpFileHeader->nImageWidth )
+ {
+ nX = nXStart;
+ nXCount = 0;
+ nY += nYAdd;
+ nYCount++;
+
+ if( nYCount >= mpFileHeader->nImageHeight )
+ break;
+ }
+ }
+ }
+ }
+ break;
+
+ // 16 bit true color encoding
+ case 16 :
+ while ( nYCount < mpFileHeader->nImageHeight )
+ {
+ m_rTGA.ReadUChar( nRunCount );
+ if ( !m_rTGA.good())
+ return false;
+ if ( nRunCount & 0x80 ) // a run length packet
+ {
+ m_rTGA.ReadUInt16( nRGB16 );
+ if ( !m_rTGA.good())
+ return false;
+ nRed = static_cast<sal_uInt8>( nRGB16 >> 7 ) & 0xf8;
+ nGreen = static_cast<sal_uInt8>( nRGB16 >> 2 ) & 0xf8;
+ nBlue = static_cast<sal_uInt8>( nRGB16 << 3 ) & 0xf8;
+ for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ )
+ {
+ mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) );
+ nX += nXAdd;
+ nXCount++;
+ if ( nXCount == mpFileHeader->nImageWidth )
+ {
+ nX = nXStart;
+ nXCount = 0;
+ nY += nYAdd;
+ nYCount++;
+
+ if( nYCount >= mpFileHeader->nImageHeight )
+ break;
+ }
+ }
+ }
+ else // a raw packet
+ {
+ for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ )
+ {
+ m_rTGA.ReadUInt16( nRGB16 );
+ if ( !m_rTGA.good())
+ return false;
+ nRed = static_cast<sal_uInt8>( nRGB16 >> 7 ) & 0xf8;
+ nGreen = static_cast<sal_uInt8>( nRGB16 >> 2 ) & 0xf8;
+ nBlue = static_cast<sal_uInt8>( nRGB16 << 3 ) & 0xf8;
+ mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) );
+ nX += nXAdd;
+ nXCount++;
+ if ( nXCount == mpFileHeader->nImageWidth )
+ {
+ nX = nXStart;
+ nXCount = 0;
+ nY += nYAdd;
+ nYCount++;
+
+ if( nYCount >= mpFileHeader->nImageHeight )
+ break;
+ }
+ }
+ }
+ }
+ break;
+
+ default:
+ return false;
+ }
+ }
+ }
+ else
+ {
+ for ( nYCount = 0; nYCount < mpFileHeader->nImageHeight; nYCount++, nY += nYAdd )
+ {
+ nX = nXStart;
+ nXCount = 0;
+
+ if ( mbIndexing )
+ {
+ switch( nDepth )
+ {
+ // 16 bit indexing
+ case 16 :
+ for (;nXCount < mpFileHeader->nImageWidth; nXCount++, nX += nXAdd )
+ {
+ m_rTGA.ReadUInt16( nRGB16 );
+ if ( !m_rTGA.good())
+ return false;
+ if ( nRGB16 >= mpFileHeader->nColorMapLength )
+ return false;
+ nRed = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] >> 16 );
+ nGreen = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] >> 8 );
+ nBlue = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] );
+ mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) );
+ }
+ break;
+
+ // 8 bit indexing
+ case 8 :
+ for (;nXCount < mpFileHeader->nImageWidth; nXCount++, nX += nXAdd )
+ {
+ m_rTGA.ReadUChar( nDummy );
+ if ( !m_rTGA.good())
+ return false;
+ if ( nDummy >= mpFileHeader->nColorMapLength )
+ return false;
+ mpBitmap->SetPixel( nY, nX, Color(ColorTransparency, nDummy) );
+ }
+ break;
+ default:
+ return false;
+ }
+ }
+ else
+ {
+ switch( nDepth )
+ {
+ // 32 bit true color
+ case 32 :
+ {
+ for (;nXCount < mpFileHeader->nImageWidth; nXCount++, nX += nXAdd )
+ {
+ m_rTGA.ReadUChar( nBlue ).ReadUChar( nGreen ).ReadUChar( nRed ).ReadUChar( nDummy );
+ if ( !m_rTGA.good())
+ return false;
+ mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) );
+ }
+ }
+ break;
+
+ // 24 bit true color
+ case 24 :
+ for (;nXCount < mpFileHeader->nImageWidth; nXCount++, nX += nXAdd )
+ {
+ m_rTGA.ReadUChar( nBlue ).ReadUChar( nGreen ).ReadUChar( nRed );
+ if ( !m_rTGA.good())
+ return false;
+ mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) );
+ }
+ break;
+
+ // 16 bit true color
+ case 16 :
+ for (;nXCount < mpFileHeader->nImageWidth; nXCount++, nX += nXAdd )
+ {
+ m_rTGA.ReadUInt16( nRGB16 );
+ if ( !m_rTGA.good())
+ return false;
+ nRed = static_cast<sal_uInt8>( nRGB16 >> 7 ) & 0xf8;
+ nGreen = static_cast<sal_uInt8>( nRGB16 >> 2 ) & 0xf8;
+ nBlue = static_cast<sal_uInt8>( nRGB16 << 3 ) & 0xf8;
+ mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) );
+ }
+ break;
+ default:
+ return false;
+ }
+ }
+ }
+ }
+ return mbStatus;
+}
+
+
+bool TGAReader::ImplReadPalette()
+{
+ if ( mbIndexing ) // read the colormap
+ {
+ sal_uInt16 nColors = mpFileHeader->nColorMapLength;
+
+ if ( !nColors ) // colors == 0 ? -> we will build a grayscale palette
+ {
+ if ( mpFileHeader->nPixelDepth != 8 )
+ return false;
+ nColors = 256;
+ mpFileHeader->nColorMapLength = 256;
+ mpFileHeader->nColorMapEntrySize = 0x3f; // patch for the following switch routine
+ }
+ mpColorMap.reset( new sal_uInt32[ nColors ] ); // we will always index dwords
+
+ switch( mpFileHeader->nColorMapEntrySize )
+ {
+ case 0x3f :
+ {
+ for (sal_uInt32 i = 0; i < nColors; ++i)
+ {
+ mpColorMap[ i ] = ( i << 16 ) + ( i << 8 ) + i;
+ }
+ }
+ break;
+
+ case 32 :
+ for (sal_uInt16 i = 0; i < nColors; i++)
+ {
+ m_rTGA.ReadUInt32(mpColorMap[i]);
+ }
+ break;
+
+ case 24 :
+ {
+ for ( sal_uInt16 i = 0; i < nColors; i++ )
+ {
+ sal_uInt8 nBlue;
+ sal_uInt8 nGreen;
+ sal_uInt8 nRed;
+ m_rTGA.ReadUChar(nBlue).ReadUChar(nGreen).ReadUChar(nRed);
+ mpColorMap[i] = (nRed << 16) | (nGreen << 8) | nBlue;
+ }
+ }
+ break;
+
+ case 15 :
+ case 16 :
+ {
+ for ( sal_uInt16 i = 0; i < nColors; i++ )
+ {
+ sal_uInt16 nTemp;
+ m_rTGA.ReadUInt16( nTemp );
+ if ( !m_rTGA.good() )
+ return false;
+ mpColorMap[ i ] = ( ( nTemp & 0x7c00 ) << 9 ) + ( ( nTemp & 0x01e0 ) << 6 ) +
+ ( ( nTemp & 0x1f ) << 3 );
+ }
+ }
+ break;
+
+ default :
+ return false;
+ }
+ if ( mnDestBitDepth <= 8 )
+ {
+ sal_uInt16 nDestColors = ( 1 << mnDestBitDepth );
+ if ( nColors > nDestColors )
+ return false;
+
+ mvPalette.resize( nColors );
+ for ( sal_uInt16 i = 0; i < nColors; i++ )
+ {
+ mvPalette[i] = Color( static_cast<sal_uInt8>( mpColorMap[ i ] >> 16 ),
+ static_cast<sal_uInt8>( mpColorMap[ i ] >> 8 ), static_cast<sal_uInt8>(mpColorMap[ i ] ) );
+ }
+ }
+ }
+
+ return mbStatus;
+}
+
+//================== GraphicImport - the exported function ================
+
+bool ImportTgaGraphic(SvStream & rStream, Graphic & rGraphic)
+{
+ TGAReader aTGAReader(rStream);
+
+ return aTGAReader.ReadTGA(rGraphic);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/itiff/itiff.cxx b/vcl/source/filter/itiff/itiff.cxx
new file mode 100644
index 000000000..e6d243b57
--- /dev/null
+++ b/vcl/source/filter/itiff/itiff.cxx
@@ -0,0 +1,294 @@
+/* -*- 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 <sal/log.hxx>
+
+#include <comphelper/scopeguard.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <vcl/animate/Animation.hxx>
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <unotools/configmgr.hxx>
+
+#include <tiffio.h>
+
+#include <filter/TiffReader.hxx>
+
+namespace
+{
+ struct Context
+ {
+ SvStream& rStream;
+ tsize_t nSize;
+ int nShortReads;
+ Context(SvStream& rInStream, tsize_t nInSize)
+ : rStream(rInStream)
+ , nSize(nInSize)
+ , nShortReads(0)
+ {
+ }
+ };
+}
+
+static tsize_t tiff_read(thandle_t handle, tdata_t buf, tsize_t size)
+{
+ Context* pContext = static_cast<Context*>(handle);
+ tsize_t nRead = pContext->rStream.ReadBytes(buf, size);
+ // tdf#149417 allow one short read, which is similar to what
+ // we do for jpeg since tdf#138950
+ if (nRead < size && !pContext->nShortReads)
+ {
+ memset(static_cast<char*>(buf) + nRead, 0, size - nRead);
+ ++pContext->nShortReads;
+ return size;
+ }
+ return nRead;
+}
+
+static tsize_t tiff_write(thandle_t, tdata_t, tsize_t)
+{
+ return -1;
+}
+
+static toff_t tiff_seek(thandle_t handle, toff_t offset, int whence)
+{
+ Context* pContext = static_cast<Context*>(handle);
+
+ switch (whence)
+ {
+ case SEEK_SET:
+ pContext->rStream.Seek(offset);
+ break;
+ case SEEK_CUR:
+ pContext->rStream.SeekRel(offset);
+ break;
+ case SEEK_END:
+ pContext->rStream.Seek(STREAM_SEEK_TO_END);
+ pContext->rStream.SeekRel(offset);
+ break;
+ default:
+ assert(false && "unknown seek type");
+ break;
+ }
+
+ return pContext->rStream.Tell();
+}
+
+static int tiff_close(thandle_t)
+{
+ return 0;
+}
+
+static toff_t tiff_size(thandle_t handle)
+{
+ Context* pContext = static_cast<Context*>(handle);
+ return pContext->nSize;
+}
+
+bool ImportTiffGraphicImport(SvStream& rTIFF, Graphic& rGraphic)
+{
+ auto origErrorHandler = TIFFSetErrorHandler(nullptr);
+ auto origWarningHandler = TIFFSetWarningHandler(nullptr);
+ comphelper::ScopeGuard restoreDefaultHandlers([&]() {
+ TIFFSetErrorHandler(origErrorHandler);
+ TIFFSetWarningHandler(origWarningHandler);
+ });
+
+ Context aContext(rTIFF, rTIFF.remainingSize());
+ TIFF* tif = TIFFClientOpen("libtiff-svstream", "r", &aContext,
+ tiff_read, tiff_write,
+ tiff_seek, tiff_close,
+ tiff_size, nullptr, nullptr);
+
+ if (!tif)
+ return false;
+
+ const auto nOrigPos = rTIFF.Tell();
+
+ Animation aAnimation;
+
+ do
+ {
+ uint32_t w, h;
+
+ if (TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w) != 1)
+ {
+ SAL_WARN("filter.tiff", "missing width");
+ break;
+ }
+
+ if (TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h) != 1)
+ {
+ SAL_WARN("filter.tiff", "missing height");
+ break;
+ }
+
+ if (w > SAL_MAX_INT32 / 32 || h > SAL_MAX_INT32 / 32)
+ {
+ SAL_WARN("filter.tiff", "image too large");
+ break;
+ }
+
+ if (utl::ConfigManager::IsFuzzing())
+ {
+ const uint64_t MAX_SIZE = 500000000;
+ if (TIFFTileSize64(tif) > MAX_SIZE)
+ {
+ SAL_WARN("filter.tiff", "skipping large tiffs");
+ break;
+ }
+ }
+
+ uint32_t nPixelsRequired;
+ constexpr size_t nMaxPixelsAllowed = SAL_MAX_INT32/4;
+ // two buffers currently required, so limit further
+ bool bOk = !o3tl::checked_multiply(w, h, nPixelsRequired) && nPixelsRequired <= nMaxPixelsAllowed / 2;
+ if (!bOk)
+ {
+ SAL_WARN("filter.tiff", "skipping oversized tiff image " << w << " x " << h);
+ break;
+ }
+
+ std::vector<uint32_t> raster(nPixelsRequired);
+ if (TIFFReadRGBAImageOriented(tif, w, h, raster.data(), ORIENTATION_TOPLEFT, 1))
+ {
+ Bitmap bitmap(Size(w, h), vcl::PixelFormat::N24_BPP);
+ BitmapScopedWriteAccess access(bitmap);
+ if (!access)
+ {
+ SAL_WARN("filter.tiff", "cannot create image " << w << " x " << h);
+ break;
+ }
+
+ AlphaMask bitmapAlpha(Size(w, h));
+ AlphaScopedWriteAccess accessAlpha(bitmapAlpha);
+ if (!accessAlpha)
+ {
+ SAL_WARN("filter.tiff", "cannot create alpha " << w << " x " << h);
+ break;
+ }
+
+ /*
+ ORIENTATION_TOPLEFT = 1
+ ORIENTATION_TOPRIGHT = 2
+ ORIENTATION_BOTRIGHT = 3
+ ORIENTATION_BOTLEFT = 4
+ ORIENTATION_LEFTTOP = 5
+ ORIENTATION_RIGHTTOP = 6
+ ORIENTATION_RIGHTBOT = 7
+ ORIENTATION_LEFTBOT = 8
+ */
+ uint16_t nOrientation;
+ if (TIFFGetField(tif, TIFFTAG_ORIENTATION, &nOrientation) != 1)
+ nOrientation = 0;
+
+ for (uint32_t y = 0; y < h; ++y)
+ {
+ const uint32_t* src = raster.data() + w * y;
+ for (uint32_t x = 0; x < w; ++x)
+ {
+ sal_uInt8 r = TIFFGetR(*src);
+ sal_uInt8 g = TIFFGetG(*src);
+ sal_uInt8 b = TIFFGetB(*src);
+ sal_uInt8 a = TIFFGetA(*src);
+
+ uint32_t dest;
+ switch (nOrientation)
+ {
+ case ORIENTATION_LEFTBOT:
+ dest = w - 1 - x;
+ break;
+ default:
+ dest = x;
+ break;
+ }
+
+ access->SetPixel(y, dest, Color(r, g, b));
+ accessAlpha->SetPixelIndex(y, dest, 255 - a);
+ ++src;
+ }
+ }
+
+ raster.clear();
+
+ access.reset();
+ accessAlpha.reset();
+
+ BitmapEx aBitmapEx(bitmap, bitmapAlpha);
+
+ switch (nOrientation)
+ {
+ case ORIENTATION_LEFTBOT:
+ aBitmapEx.Rotate(2700_deg10, COL_BLACK);
+ break;
+ default:
+ break;
+ }
+
+ MapMode aMapMode;
+ uint16_t ResolutionUnit = RESUNIT_NONE;
+ if (TIFFGetField(tif, TIFFTAG_RESOLUTIONUNIT, &ResolutionUnit) == 1 && ResolutionUnit != RESUNIT_NONE)
+ {
+ float xres = 0, yres = 0;
+
+ if (TIFFGetField(tif, TIFFTAG_XRESOLUTION, &xres) == 1 &&
+ TIFFGetField(tif, TIFFTAG_YRESOLUTION, &yres) == 1 &&
+ xres != 0 && yres != 0)
+ {
+ if (ResolutionUnit == RESUNIT_INCH)
+ aMapMode = MapMode(MapUnit::MapInch, Point(0,0), Fraction(1/xres), Fraction(1/yres));
+ else if (ResolutionUnit == RESUNIT_CENTIMETER)
+ aMapMode = MapMode(MapUnit::MapCM, Point(0,0), Fraction(1/xres), Fraction(1/yres));
+ }
+ }
+ aBitmapEx.SetPrefMapMode(aMapMode);
+ aBitmapEx.SetPrefSize(Size(w, h));
+
+ AnimationBitmap aAnimationBitmap(aBitmapEx, Point(0, 0), aBitmapEx.GetSizePixel(),
+ ANIMATION_TIMEOUT_ON_CLICK, Disposal::Back);
+ aAnimation.Insert(aAnimationBitmap);
+ }
+ else
+ break;
+ } while (TIFFReadDirectory(tif));
+
+ TIFFClose(tif);
+
+ const auto nImages = aAnimation.Count();
+ if (nImages)
+ {
+ if (nImages == 1)
+ rGraphic = aAnimation.GetBitmapEx();
+ else
+ rGraphic = aAnimation;
+
+ // seek to end of TIFF if succeeded
+ rTIFF.Seek(STREAM_SEEK_TO_END);
+
+ return true;
+ }
+
+ rTIFF.Seek(nOrigPos);
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ixbm/xbmread.cxx b/vcl/source/filter/ixbm/xbmread.cxx
new file mode 100644
index 000000000..9368f9f9a
--- /dev/null
+++ b/vcl/source/filter/ixbm/xbmread.cxx
@@ -0,0 +1,396 @@
+/* -*- 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 <memory>
+#include <sal/config.h>
+#include <tools/stream.hxx>
+
+#include <rtl/character.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <graphic/GraphicReader.hxx>
+
+#include "xbmread.hxx"
+
+namespace {
+
+enum XBMFormat
+{
+ XBM10,
+ XBM11
+};
+
+enum ReadState
+{
+ XBMREAD_OK,
+ XBMREAD_ERROR,
+ XBMREAD_NEED_MORE
+};
+
+class XBMReader : public GraphicReader
+{
+ SvStream& rIStm;
+ Bitmap aBmp1;
+ BitmapScopedWriteAccess pAcc1;
+ std::unique_ptr<short[]>
+ pHexTable;
+ BitmapColor aWhite;
+ BitmapColor aBlack;
+ tools::Long nLastPos;
+ tools::Long nWidth;
+ tools::Long nHeight;
+ bool bStatus;
+
+ void InitTable();
+ OString FindTokenLine( SvStream* pInStm, const char* pTok1, const char* pTok2 );
+ int ParseDefine( const char* pDefine );
+ void ParseData( SvStream* pInStm, const OString& aLastLine, XBMFormat eFormat );
+
+public:
+
+ explicit XBMReader( SvStream& rStm );
+
+ ReadState ReadXBM( Graphic& rGraphic );
+};
+
+}
+
+XBMReader::XBMReader( SvStream& rStm ) :
+ rIStm ( rStm ),
+ nLastPos ( rStm.Tell() ),
+ nWidth ( 0 ),
+ nHeight ( 0 ),
+ bStatus ( true )
+{
+ pHexTable.reset( new short[ 256 ] );
+ maUpperName = "SVIXBM";
+ InitTable();
+}
+
+void XBMReader::InitTable()
+{
+ memset( pHexTable.get(), 0, sizeof( short ) * 256 );
+
+ pHexTable[int('0')] = 0;
+ pHexTable[int('1')] = 1;
+ pHexTable[int('2')] = 2;
+ pHexTable[int('3')] = 3;
+ pHexTable[int('4')] = 4;
+ pHexTable[int('5')] = 5;
+ pHexTable[int('6')] = 6;
+ pHexTable[int('7')] = 7;
+ pHexTable[int('8')] = 8;
+ pHexTable[int('9')] = 9;
+ pHexTable[int('A')] = 10;
+ pHexTable[int('B')] = 11;
+ pHexTable[int('C')] = 12;
+ pHexTable[int('D')] = 13;
+ pHexTable[int('E')] = 14;
+ pHexTable[int('F')] = 15;
+ pHexTable[int('X')] = 0;
+ pHexTable[int('a')] = 10;
+ pHexTable[int('b')] = 11;
+ pHexTable[int('c')] = 12;
+ pHexTable[int('d')] = 13;
+ pHexTable[int('e')] = 14;
+ pHexTable[int('f')] = 15;
+ pHexTable[int('x')] = 0;
+ pHexTable[int(' ')] = -1;
+ pHexTable[int(',')] = -1;
+ pHexTable[int('}')] = -1;
+ pHexTable[int('\n')] = -1;
+ pHexTable[int('\t')] = -1;
+ pHexTable[int('\0')] = -1;
+}
+
+OString XBMReader::FindTokenLine( SvStream* pInStm, const char* pTok1,
+ const char* pTok2 )
+{
+ OString aRet;
+ sal_Int32 nPos1, nPos2;
+
+ bStatus = false;
+
+ do
+ {
+ if( !pInStm->ReadLine( aRet ) )
+ break;
+
+ if( pTok1 )
+ {
+ if( ( nPos1 = aRet.indexOf( pTok1 ) ) != -1 )
+ {
+ bStatus = true;
+
+ if( pTok2 )
+ {
+ bStatus = false;
+
+ nPos2 = aRet.indexOf( pTok2 );
+ if( ( nPos2 != -1 ) && ( nPos2 > nPos1 ) )
+ {
+ bStatus = true;
+ }
+ }
+ }
+ }
+ }
+ while( !bStatus );
+
+ return aRet;
+}
+
+int XBMReader::ParseDefine( const char* pDefine )
+{
+ sal_Int32 nRet = 0;
+ const char* pTmp = pDefine;
+ unsigned char cTmp;
+
+ // move to end
+ pTmp += ( strlen( pDefine ) - 1 );
+ cTmp = *pTmp--;
+
+ // search last digit
+ while (pHexTable[ cTmp ] == -1 && pTmp >= pDefine)
+ cTmp = *pTmp--;
+
+ // move before number
+ while (pHexTable[ cTmp ] != -1 && pTmp >= pDefine)
+ cTmp = *pTmp--;
+
+ // move to start of number
+ pTmp += 2;
+
+ // read Hex
+ if( ( pTmp[0] == '0' ) && ( ( pTmp[1] == 'X' ) || ( pTmp[1] == 'x' ) ) )
+ {
+ pTmp += 2;
+ nRet = OString(pTmp, strlen(pTmp)).toInt32(16);
+ }
+ else // read decimal
+ {
+ nRet = OString(pTmp, strlen(pTmp)).toInt32();
+ }
+
+ return nRet;
+}
+
+void XBMReader::ParseData( SvStream* pInStm, const OString& aLastLine, XBMFormat eFormat )
+{
+ OString aLine;
+ tools::Long nRow = 0;
+ tools::Long nCol = 0;
+ tools::Long nBits = ( eFormat == XBM10 ) ? 16 : 8;
+ tools::Long nBit;
+ sal_uInt16 nValue;
+ sal_uInt16 nDigits;
+ bool bFirstLine = true;
+
+ while( nRow < nHeight )
+ {
+ if( bFirstLine )
+ {
+ sal_Int32 nPos;
+
+ // delete opening curly bracket
+ aLine = aLastLine;
+ nPos = aLine.indexOf('{');
+ if( nPos != -1 )
+ aLine = aLine.copy(nPos + 1);
+
+ bFirstLine = false;
+ }
+ else if( !pInStm->ReadLine( aLine ) )
+ break;
+
+ if (!aLine.isEmpty())
+ {
+ sal_Int32 nIndex = 0;
+ const sal_Int32 nLen {aLine.getLength()};
+ while (nRow<nHeight && nIndex<nLen)
+ {
+ bool bProcessed = false;
+
+ nBit = nDigits = nValue = 0;
+
+ while (nIndex<nLen)
+ {
+ const unsigned char cChar = aLine[nIndex];
+
+ ++nIndex;
+ if (cChar==',') // sequence completed, ',' already skipped for next loop
+ break;
+
+ const short nTable = pHexTable[ cChar ];
+
+ if( rtl::isAsciiHexDigit( cChar ) || !nTable )
+ {
+ nValue = ( nValue << 4 ) + nTable;
+ nDigits++;
+ bProcessed = true;
+ }
+ else if( ( nTable < 0 ) && nDigits )
+ {
+ bProcessed = true;
+ break;
+ }
+ }
+
+ if( bProcessed )
+ {
+ Scanline pScanline = pAcc1->GetScanline(nRow);
+ while( ( nCol < nWidth ) && ( nBit < nBits ) )
+ pAcc1->SetPixelOnData(pScanline, nCol++, ( nValue & ( 1 << nBit++ ) ) ? aBlack : aWhite);
+
+ if( nCol == nWidth )
+ {
+ nCol = 0;
+ nRow++;
+ }
+ }
+ }
+ }
+ }
+}
+
+ReadState XBMReader::ReadXBM( Graphic& rGraphic )
+{
+ ReadState eReadState;
+ sal_uInt8 cDummy;
+
+ // check if we can read ALL
+ rIStm.Seek( STREAM_SEEK_TO_END );
+ rIStm.ReadUChar( cDummy );
+
+ // if we cannot read all
+ // we return and wait for new data
+ if ( rIStm.GetError() != ERRCODE_IO_PENDING )
+ {
+ rIStm.Seek( nLastPos );
+ bStatus = false;
+ OString aLine = FindTokenLine( &rIStm, "#define", "_width" );
+
+ if ( bStatus )
+ {
+ int nValue;
+ if ( ( nValue = ParseDefine( aLine.getStr() ) ) > 0 )
+ {
+ nWidth = nValue;
+ aLine = FindTokenLine( &rIStm, "#define", "_height" );
+
+ // if height was not received, we search again
+ // from start of the file
+ if ( !bStatus )
+ {
+ rIStm.Seek( nLastPos );
+ aLine = FindTokenLine( &rIStm, "#define", "_height" );
+ }
+ }
+ else
+ bStatus = false;
+
+ if ( bStatus )
+ {
+ if ( ( nValue = ParseDefine( aLine.getStr() ) ) > 0 )
+ {
+ nHeight = nValue;
+ aLine = FindTokenLine( &rIStm, "static", "_bits" );
+
+ if ( bStatus )
+ {
+ XBMFormat eFormat = XBM10;
+
+ if (aLine.indexOf("short") != -1)
+ eFormat = XBM10;
+ else if (aLine.indexOf("char") != -1)
+ eFormat = XBM11;
+ else
+ bStatus = false;
+
+ //xbms are a minimum of one character per 8 pixels, so if the file isn't
+ //even that long, it's not all there
+ if (rIStm.remainingSize() < (static_cast<sal_uInt64>(nWidth) * nHeight) / 8)
+ bStatus = false;
+
+ if ( bStatus && nWidth && nHeight )
+ {
+ aBmp1 = Bitmap(Size(nWidth, nHeight), vcl::PixelFormat::N1_BPP);
+ pAcc1 = BitmapScopedWriteAccess(aBmp1);
+
+ if( pAcc1 )
+ {
+ aWhite = pAcc1->GetBestMatchingColor( COL_WHITE );
+ aBlack = pAcc1->GetBestMatchingColor( COL_BLACK );
+ ParseData( &rIStm, aLine, eFormat );
+ }
+ else
+ bStatus = false;
+ }
+ }
+ }
+ }
+ }
+
+ if (bStatus && pAcc1)
+ {
+ Bitmap aBlackBmp(Size(pAcc1->Width(), pAcc1->Height()), vcl::PixelFormat::N1_BPP);
+
+ pAcc1.reset();
+ aBlackBmp.Erase( COL_BLACK );
+ rGraphic = BitmapEx( aBlackBmp, aBmp1 );
+ eReadState = XBMREAD_OK;
+ }
+ else
+ eReadState = XBMREAD_ERROR;
+ }
+ else
+ {
+ rIStm.ResetError();
+ eReadState = XBMREAD_NEED_MORE;
+ }
+
+ return eReadState;
+}
+
+VCL_DLLPUBLIC bool ImportXBM( SvStream& rStm, Graphic& rGraphic )
+{
+ std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext();
+ rGraphic.SetReaderContext(nullptr);
+ XBMReader* pXBMReader = dynamic_cast<XBMReader*>( pContext.get() );
+ if (!pXBMReader)
+ {
+ pContext = std::make_shared<XBMReader>( rStm );
+ pXBMReader = static_cast<XBMReader*>( pContext.get() );
+ }
+
+ bool bRet = true;
+
+ ReadState eReadState = pXBMReader->ReadXBM( rGraphic );
+
+ if( eReadState == XBMREAD_ERROR )
+ {
+ bRet = false;
+ }
+ else if( eReadState == XBMREAD_NEED_MORE )
+ rGraphic.SetReaderContext( pContext );
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ixbm/xbmread.hxx b/vcl/source/filter/ixbm/xbmread.hxx
new file mode 100644
index 000000000..bee3eadc9
--- /dev/null
+++ b/vcl/source/filter/ixbm/xbmread.hxx
@@ -0,0 +1,29 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_IXBM_XBMREAD_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_IXBM_XBMREAD_HXX
+
+#include <vcl/graph.hxx>
+
+VCL_DLLPUBLIC bool ImportXBM(SvStream& rStream, Graphic& rGraphic);
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_IXBM_XBMREAD_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ixpm/rgbtable.hxx b/vcl/source/filter/ixpm/rgbtable.hxx
new file mode 100644
index 000000000..1ded061e7
--- /dev/null
+++ b/vcl/source/filter/ixpm/rgbtable.hxx
@@ -0,0 +1,696 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_IXPM_RGBTABLE_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_IXPM_RGBTABLE_HXX
+
+#include <sal/types.h>
+
+struct XPMRGBTab
+{
+ const char* name;
+ sal_uInt8 red;
+ sal_uInt8 green;
+ sal_uInt8 blue;
+};
+
+const XPMRGBTab pRGBTable[] = {
+{ "white", 255, 255, 255 },
+{ "black", 0, 0, 0 },
+{ "snow", 255, 250, 250 },
+{ "GhostWhite", 248, 248, 255 },
+{ "WhiteSmoke", 245, 245, 245 },
+{ "gainsboro", 220, 220, 220 },
+{ "FloralWhite", 255, 250, 240 },
+{ "OldLace", 253, 245, 230 },
+{ "linen", 250, 240, 230 },
+{ "AntiqueWhite", 250, 235, 215 },
+{ "PapayaWhip", 255, 239, 213 },
+{ "BlanchedAlmond", 255, 235, 205 },
+{ "bisque", 255, 228, 196 },
+{ "PeachPuff", 255, 218, 185 },
+{ "NavajoWhite", 255, 222, 173 },
+{ "moccasin", 255, 228, 181 },
+{ "cornsilk", 255, 248, 220 },
+{ "ivory", 255, 255, 240 },
+{ "LemonChiffon", 255, 250, 205 },
+{ "seashell", 255, 245, 238 },
+{ "honeydew", 240, 255, 240 },
+{ "MintCream", 245, 255, 250 },
+{ "azure", 240, 255, 255 },
+{ "AliceBlue", 240, 248, 255 },
+{ "lavender", 230, 230, 250 },
+{ "LavenderBlush", 255, 240, 245 },
+{ "MistyRose", 255, 228, 225 },
+{ "DarkSlateGray", 47, 79, 79 },
+{ "DarkSlateGrey", 47, 79, 79 },
+{ "DimGray", 105, 105, 105 },
+{ "DimGrey", 105, 105, 105 },
+{ "SlateGray", 112, 128, 144 },
+{ "SlateGrey", 112, 128, 144 },
+{ "LightSlateGray", 119, 136, 153 },
+{ "LightSlateGrey", 119, 136, 153 },
+{ "gray", 190, 190, 190 },
+{ "grey", 190, 190, 190 },
+{ "LightGrey", 211, 211, 211 },
+{ "LightGray", 211, 211, 211 },
+{ "MidnightBlue", 25, 25, 112 },
+{ "navy", 0, 0, 128 },
+{ "NavyBlue", 0, 0, 128 },
+{ "CornflowerBlue", 100, 149, 237 },
+{ "DarkSlateBlue", 72, 61, 139 },
+{ "SlateBlue", 106, 90, 205 },
+{ "MediumSlateBlue", 123, 104, 238 },
+{ "LightSlateBlue", 132, 112, 255 },
+{ "MediumBlue", 0, 0, 205 },
+{ "RoyalBlue", 65, 105, 225 },
+{ "blue", 0, 0, 255 },
+{ "DodgerBlue", 30, 144, 255 },
+{ "DeepSkyBlue", 0, 191, 255 },
+{ "SkyBlue", 135, 206, 235 },
+{ "LightSkyBlue", 135, 206, 250 },
+{ "SteelBlue", 70, 130, 180 },
+{ "LightSteelBlue", 176, 196, 222 },
+{ "LightBlue", 173, 216, 230 },
+{ "PowderBlue", 176, 224, 230 },
+{ "PaleTurquoise", 175, 238, 238 },
+{ "DarkTurquoise", 0, 206, 209 },
+{ "MediumTurquoise", 72, 209, 204 },
+{ "turquoise", 64, 224, 208 },
+{ "cyan", 0, 255, 255 },
+{ "LightCyan", 224, 255, 255 },
+{ "CadetBlue", 95, 158, 160 },
+{ "MediumAquamarine", 102, 205, 170 },
+{ "aquamarine", 127, 255, 212 },
+{ "DarkGreen", 0, 100, 0 },
+{ "DarkOliveGreen", 85, 107, 47 },
+{ "DarkSeaGreen", 143, 188, 143 },
+{ "SeaGreen", 46, 139, 87 },
+{ "MediumSeaGreen", 60, 179, 113 },
+{ "LightSeaGreen", 32, 178, 170 },
+{ "PaleGreen", 152, 251, 152 },
+{ "SpringGreen", 0, 255, 127 },
+{ "LawnGreen", 124, 252, 0 },
+{ "green", 0, 255, 0 },
+{ "chartreuse", 127, 255, 0 },
+{ "MediumSpringGreen", 0, 250, 154 },
+{ "GreenYellow", 173, 255 , 47 },
+{ "LimeGreen", 50, 205, 50 },
+{ "YellowGreen", 154, 205, 50 },
+{ "ForestGreen", 34, 139, 34 },
+{ "OliveDrab", 107, 142, 35 },
+{ "DarkKhaki", 189, 183, 107 },
+{ "khaki", 240, 230, 140 },
+{ "PaleGoldenrod", 238, 232, 170 },
+{ "LightGoldenrodYellow", 250, 250, 210 },
+{ "LightYellow", 255, 255, 224 },
+{ "yellow", 255, 255, 0 },
+{ "gold", 255, 215, 0 },
+{ "LightGoldenrod", 238, 221, 130 },
+{ "goldenrod", 218, 165, 32 },
+{ "DarkGoldenrod", 184, 134, 11 },
+{ "RosyBrown", 188, 143, 143 },
+{ "IndianRed", 205, 92, 92 },
+{ "SaddleBrown", 139, 69, 19 },
+{ "sienna", 160, 82, 45 },
+{ "peru", 205, 133, 63 },
+{ "burlywood", 222, 184, 135 },
+{ "beige", 245, 245, 220 },
+{ "wheat", 245, 222, 179 },
+{ "SandyBrown", 244, 164, 96 },
+{ "tan", 210, 180, 140 },
+{ "chocolate", 210, 105, 30 },
+{ "firebrick", 178, 34, 34 },
+{ "brown", 165, 42, 42 },
+{ "DarkSalmon", 233, 150, 122 },
+{ "salmon", 250, 128, 114 },
+{ "LightSalmon", 255, 160, 122 },
+{ "orange", 255, 165, 0 },
+{ "DarkOrange", 255, 140, 0 },
+{ "coral", 255, 127, 80 },
+{ "LightCoral", 240, 128, 128 },
+{ "tomato", 255, 99, 71 },
+{ "OrangeRed", 255, 69, 0 },
+{ "red", 255, 0, 0 },
+{ "HotPink", 255, 105, 180 },
+{ "DeepPink", 255, 20, 147 },
+{ "pink", 255, 192, 203 },
+{ "LightPink", 255, 182, 193 },
+{ "PaleVioletRed", 219, 112, 147 },
+{ "maroon", 176, 48, 96 },
+{ "MediumVioletRed", 199, 21, 133 },
+{ "VioletRed", 208, 32, 144 },
+{ "magenta", 255, 0, 255 },
+{ "violet", 238, 130, 238 },
+{ "plum", 221, 160, 221 },
+{ "orchid", 218, 112, 214 },
+{ "MediumOrchid", 186, 85, 211 },
+{ "DarkOrchid", 153, 50, 204 },
+{ "DarkViolet", 148, 0, 211 },
+{ "BlueViolet", 138, 43, 226 },
+{ "purple", 160, 32, 240 },
+{ "MediumPurple", 147, 112, 219 },
+{ "thistle", 216, 191, 216 },
+{ "snow1", 255, 250, 250 },
+{ "snow2", 238, 233, 233 },
+{ "snow3", 205, 201, 201 },
+{ "snow4", 139, 137, 137 },
+{ "seashell1", 255, 245, 238 },
+{ "seashell2", 238, 229, 222 },
+{ "seashell3", 205, 197, 191 },
+{ "seashell4", 139, 134, 130 },
+{ "AntiqueWhite1", 255, 239, 219 },
+{ "AntiqueWhite2", 238, 223, 204 },
+{ "AntiqueWhite3", 205, 192, 176 },
+{ "AntiqueWhite4", 139, 131, 120 },
+{ "bisque1", 255, 228, 196 },
+{ "bisque2", 238, 213, 183 },
+{ "bisque3", 205, 183, 158 },
+{ "bisque4", 139, 125, 107 },
+{ "PeachPuff1", 255, 218, 185 },
+{ "PeachPuff2", 238, 203, 173 },
+{ "PeachPuff3", 205, 175, 149 },
+{ "PeachPuff4", 139, 119, 101 },
+{ "NavajoWhite1", 255, 222, 173 },
+{ "NavajoWhite2", 238, 207, 161 },
+{ "NavajoWhite3", 205, 179, 139 },
+{ "NavajoWhite4", 139, 121, 94 },
+{ "LemonChiffon1", 255, 250, 205 },
+{ "LemonChiffon2", 238, 233, 191 },
+{ "LemonChiffon3", 205, 201, 165 },
+{ "LemonChiffon4", 139, 137, 112 },
+{ "cornsilk1", 255, 248, 220 },
+{ "cornsilk2", 238, 232, 205 },
+{ "cornsilk3", 205, 200, 177 },
+{ "cornsilk4", 139, 136, 120 },
+{ "ivory1", 255, 255, 240 },
+{ "ivory2", 238, 238, 224 },
+{ "ivory3", 205, 205, 193 },
+{ "ivory4", 139, 139, 131 },
+{ "honeydew1", 240, 255, 240 },
+{ "honeydew2", 224, 238, 224 },
+{ "honeydew3", 193, 205, 193 },
+{ "honeydew4", 131, 139, 131 },
+{ "LavenderBlush1", 255, 240, 245 },
+{ "LavenderBlush2", 238, 224, 229 },
+{ "LavenderBlush3", 205, 193, 197 },
+{ "LavenderBlush4", 139, 131, 134 },
+{ "MistyRose1", 255, 228, 225 },
+{ "MistyRose2", 238, 213, 210 },
+{ "MistyRose3", 205, 183, 181 },
+{ "MistyRose4", 139, 125, 123 },
+{ "azure1", 240, 255, 255 },
+{ "azure2", 224, 238, 238 },
+{ "azure3", 193, 205, 205 },
+{ "azure4", 131, 139, 139 },
+{ "SlateBlue1", 131, 111, 255 },
+{ "SlateBlue2", 122, 103, 238 },
+{ "SlateBlue3", 105, 89, 205 },
+{ "SlateBlue4", 71, 60, 139 },
+{ "RoyalBlue1", 72, 118, 255 },
+{ "RoyalBlue2", 67, 110, 238 },
+{ "RoyalBlue3", 58, 95, 205 },
+{ "RoyalBlue4", 39, 64, 139 },
+{ "blue1", 0, 0, 255 },
+{ "blue2", 0, 0, 238 },
+{ "blue3", 0, 0, 205 },
+{ "blue4", 0, 0, 139 },
+{ "DodgerBlue1", 30, 144, 255 },
+{ "DodgerBlue2", 28, 134, 238 },
+{ "DodgerBlue3", 24, 116, 205 },
+{ "DodgerBlue4", 16, 78, 139 },
+{ "SteelBlue1", 99, 184, 255 },
+{ "SteelBlue2", 92, 172, 238 },
+{ "SteelBlue3", 79, 148, 205 },
+{ "SteelBlue4", 54, 100, 139 },
+{ "DeepSkyBlue1", 0, 191, 255 },
+{ "DeepSkyBlue2", 0, 178, 238 },
+{ "DeepSkyBlue3", 0, 154, 205 },
+{ "DeepSkyBlue4", 0, 104, 139 },
+{ "SkyBlue1", 135, 206, 255 },
+{ "SkyBlue2", 126, 192, 238 },
+{ "SkyBlue3", 108, 166, 205 },
+{ "SkyBlue4", 74, 112, 139 },
+{ "LightSkyBlue1", 176, 226, 255 },
+{ "LightSkyBlue2", 164, 211, 238 },
+{ "LightSkyBlue3", 141, 182, 205 },
+{ "LightSkyBlue4", 96, 123, 139 },
+{ "SlateGray1", 198, 226, 255 },
+{ "SlateGray2", 185, 211, 238 },
+{ "SlateGray3", 159, 182, 205 },
+{ "SlateGray4", 108, 123, 139 },
+{ "LightSteelBlue1", 202, 225, 255 },
+{ "LightSteelBlue2", 188, 210, 238 },
+{ "LightSteelBlue3", 162, 181, 205 },
+{ "LightSteelBlue4", 110, 123, 139 },
+{ "LightBlue1", 191, 239, 255 },
+{ "LightBlue2", 178, 223, 238 },
+{ "LightBlue3", 154, 192, 205 },
+{ "LightBlue4", 104, 131, 139 },
+{ "LightCyan1", 224, 255, 255 },
+{ "LightCyan2", 209, 238, 238 },
+{ "LightCyan3", 180, 205, 205 },
+{ "LightCyan4", 122, 139, 139 },
+{ "PaleTurquoise1", 187, 255, 255 },
+{ "PaleTurquoise2", 174, 238, 238 },
+{ "PaleTurquoise3", 150, 205, 205 },
+{ "PaleTurquoise4", 102, 139, 139 },
+{ "CadetBlue1", 152, 245, 255 },
+{ "CadetBlue2", 142, 229, 238 },
+{ "CadetBlue3", 122, 197, 205 },
+{ "CadetBlue4", 83, 134, 139 },
+{ "turquoise1", 0, 245, 255 },
+{ "turquoise2", 0, 229, 238 },
+{ "turquoise3", 0, 197, 205 },
+{ "turquoise4", 0, 134, 139 },
+{ "cyan1", 0, 255, 255 },
+{ "cyan2", 0, 238, 238 },
+{ "cyan3", 0, 205, 205 },
+{ "cyan4", 0, 139, 139 },
+{ "DarkSlateGray1", 151, 255, 255 },
+{ "DarkSlateGray2", 141, 238, 238 },
+{ "DarkSlateGray3", 121, 205, 205 },
+{ "DarkSlateGray4", 82, 139, 139 },
+{ "aquamarine1", 127, 255, 212 },
+{ "aquamarine2", 118, 238, 198 },
+{ "aquamarine3", 102, 205, 170 },
+{ "aquamarine4", 69, 139, 116 },
+{ "DarkSeaGreen1", 193, 255, 193 },
+{ "DarkSeaGreen2", 180, 238, 180 },
+{ "DarkSeaGreen3", 155, 205, 155 },
+{ "DarkSeaGreen4", 105, 139, 105 },
+{ "SeaGreen1", 84, 255, 159 },
+{ "SeaGreen2", 78, 238, 148 },
+{ "SeaGreen3", 67, 205, 128 },
+{ "SeaGreen4", 46, 139, 87 },
+{ "PaleGreen1", 154, 255, 154 },
+{ "PaleGreen2", 144, 238, 144 },
+{ "PaleGreen3", 124, 205, 124 },
+{ "PaleGreen4", 84, 139, 84 },
+{ "SpringGreen1", 0, 255, 127 },
+{ "SpringGreen2", 0, 238, 118 },
+{ "SpringGreen3", 0, 205, 102 },
+{ "SpringGreen4", 0, 139, 69 },
+{ "green1", 0, 255, 0 },
+{ "green2", 0, 238, 0 },
+{ "green3", 0, 205, 0 },
+{ "green4", 0, 139, 0 },
+{ "chartreuse1", 127, 255, 0 },
+{ "chartreuse2", 118, 238, 0 },
+{ "chartreuse3", 102, 205, 0 },
+{ "chartreuse4", 69, 139, 0 },
+{ "OliveDrab1", 192, 255, 62 },
+{ "OliveDrab2", 179, 238, 58 },
+{ "OliveDrab3", 154, 205, 50 },
+{ "OliveDrab4", 105, 139, 34 },
+{ "DarkOliveGreen1", 202, 255, 112 },
+{ "DarkOliveGreen2", 188, 238, 104 },
+{ "DarkOliveGreen3", 162, 205, 90 },
+{ "DarkOliveGreen4", 110, 139, 61 },
+{ "khaki1", 255, 246, 143 },
+{ "khaki2", 238, 230, 133 },
+{ "khaki3", 205, 198, 115 },
+{ "khaki4", 139, 134, 78 },
+{ "LightGoldenrod1", 255, 236, 139 },
+{ "LightGoldenrod2", 238, 220, 130 },
+{ "LightGoldenrod3", 205, 190, 112 },
+{ "LightGoldenrod4", 139, 129, 76 },
+{ "LightYellow1", 255, 255, 224 },
+{ "LightYellow2", 238, 238, 209 },
+{ "LightYellow3", 205, 205, 180 },
+{ "LightYellow4", 139, 139, 122 },
+{ "yellow1", 255, 255, 0 },
+{ "yellow2", 238, 238, 0 },
+{ "yellow3", 205, 205, 0 },
+{ "yellow4", 139, 139, 0 },
+{ "gold1", 255, 215, 0 },
+{ "gold2", 238, 201, 0 },
+{ "gold3", 205, 173, 0 },
+{ "gold4", 139, 117, 0 },
+{ "goldenrod1", 255, 193, 37 },
+{ "goldenrod2", 238, 180, 34 },
+{ "goldenrod3", 205, 155, 29 },
+{ "goldenrod4", 139, 105, 20 },
+{ "DarkGoldenrod1", 255, 185, 15 },
+{ "DarkGoldenrod2", 238, 173, 14 },
+{ "DarkGoldenrod3", 205, 149, 12 },
+{ "DarkGoldenrod4", 139, 101, 8 },
+{ "RosyBrown1", 255, 193, 193 },
+{ "RosyBrown2", 238, 180, 180 },
+{ "RosyBrown3", 205, 155, 155 },
+{ "RosyBrown4", 139, 105, 105 },
+{ "IndianRed1", 255, 106, 106 },
+{ "IndianRed2", 238, 99, 99 },
+{ "IndianRed3", 205, 85, 85 },
+{ "IndianRed4", 139, 58, 58 },
+{ "sienna1", 255, 130, 71 },
+{ "sienna2", 238, 121, 66 },
+{ "sienna3", 205, 104, 57 },
+{ "sienna4", 139, 71, 38 },
+{ "burlywood1", 255, 211, 155 },
+{ "burlywood2", 238, 197, 145 },
+{ "burlywood3", 205, 170, 125 },
+{ "burlywood4", 139, 115, 85 },
+{ "wheat1", 255, 231, 186 },
+{ "wheat2", 238, 216, 174 },
+{ "wheat3", 205, 186, 150 },
+{ "wheat4", 139, 126, 102 },
+{ "tan1", 255, 165, 79 },
+{ "tan2", 238, 154, 73 },
+{ "tan3", 205, 133, 63 },
+{ "tan4", 139 , 90, 43 },
+{ "chocolate1", 255, 127, 36 },
+{ "chocolate2", 238, 118, 33 },
+{ "chocolate3", 205, 102, 29 },
+{ "chocolate4", 139, 69, 19 },
+{ "firebrick1", 255, 48, 48 },
+{ "firebrick2", 238, 44, 44 },
+{ "firebrick3", 205, 38, 38 },
+{ "firebrick4", 139, 26, 26 },
+{ "brown1", 255, 64, 64 },
+{ "brown2", 238, 59, 59 },
+{ "brown3", 205, 51, 51 },
+{ "brown4", 139, 35, 35 },
+{ "salmon1", 255, 140, 105 },
+{ "salmon2", 238, 130, 98 },
+{ "salmon3", 205, 112, 84 },
+{ "salmon4", 139, 76, 57 },
+{ "LightSalmon1", 255, 160, 122 },
+{ "LightSalmon2", 238, 149, 114 },
+{ "LightSalmon3", 205, 129, 98 },
+{ "LightSalmon4", 139, 87, 66 },
+{ "orange1", 255, 165, 0 },
+{ "orange2", 238, 154, 0 },
+{ "orange3", 205, 133, 0 },
+{ "orange4", 139 , 90, 0 },
+{ "DarkOrange1", 255, 127, 0 },
+{ "DarkOrange2", 238, 118, 0 },
+{ "DarkOrange3", 205, 102, 0 },
+{ "DarkOrange4", 139 , 69, 0 },
+{ "coral1", 255, 114, 86 },
+{ "coral2", 238, 106, 80 },
+{ "coral3", 205, 91, 69 },
+{ "coral4", 139, 62, 47 },
+{ "tomato1", 255, 99, 71 },
+{ "tomato2", 238, 92, 66 },
+{ "tomato3", 205, 79, 57 },
+{ "tomato4", 139, 54, 38 },
+{ "OrangeRed1", 255, 69, 0 },
+{ "OrangeRed2", 238, 64, 0 },
+{ "OrangeRed3", 205, 55, 0 },
+{ "OrangeRed4", 139, 37, 0 },
+{ "red1", 255, 0, 0 },
+{ "red2", 238, 0, 0 },
+{ "red3", 205, 0, 0 },
+{ "red4", 139, 0, 0 },
+{ "DeepPink1", 255, 20, 147 },
+{ "DeepPink2", 238, 18, 137 },
+{ "DeepPink3", 205, 16, 118 },
+{ "DeepPink4", 139, 10, 80 },
+{ "HotPink1", 255, 110, 180 },
+{ "HotPink2", 238, 106, 167 },
+{ "HotPink3", 205, 96, 144 },
+{ "HotPink4", 139, 58, 98 },
+{ "pink1", 255, 181, 197 },
+{ "pink2", 238, 169, 184 },
+{ "pink3", 205, 145, 158 },
+{ "pink4", 139, 99, 108 },
+{ "LightPink1", 255, 174, 185 },
+{ "LightPink2", 238, 162, 173 },
+{ "LightPink3", 205, 140, 149 },
+{ "LightPink4", 139, 95, 101 },
+{ "PaleVioletRed1", 255, 130, 171 },
+{ "PaleVioletRed2", 238, 121, 159 },
+{ "PaleVioletRed3", 205, 104, 137 },
+{ "PaleVioletRed4", 139, 71, 93 },
+{ "maroon1", 255, 52, 179 },
+{ "maroon2", 238, 48, 167 },
+{ "maroon3", 205, 41, 144 },
+{ "maroon4", 139, 28, 98 },
+{ "VioletRed1", 255, 62, 150 },
+{ "VioletRed2", 238, 58, 140 },
+{ "VioletRed3", 205, 50, 120 },
+{ "VioletRed4", 139, 34, 82 },
+{ "magenta1", 255, 0, 255 },
+{ "magenta2", 238, 0, 238 },
+{ "magenta3", 205, 0, 205 },
+{ "magenta4", 139, 0, 139 },
+{ "orchid1", 255, 131, 250 },
+{ "orchid2", 238, 122, 233 },
+{ "orchid3", 205, 105, 201 },
+{ "orchid4", 139, 71, 137 },
+{ "plum1", 255, 187, 255 },
+{ "plum2", 238, 174, 238 },
+{ "plum3", 205, 150, 205 },
+{ "plum4", 139, 102, 139 },
+{ "MediumOrchid1", 224, 102, 255 },
+{ "MediumOrchid2", 209, 95, 238 },
+{ "MediumOrchid3", 180, 82, 205 },
+{ "MediumOrchid4", 122, 55, 139 },
+{ "DarkOrchid1", 191, 62, 255 },
+{ "DarkOrchid2", 178, 58, 238 },
+{ "DarkOrchid3", 154, 50, 205 },
+{ "DarkOrchid4", 104, 34, 139 },
+{ "purple1", 155, 48, 255 },
+{ "purple2", 145, 44, 238 },
+{ "purple3", 125, 38, 205 },
+{ "purple4", 85, 26, 139 },
+{ "MediumPurple1", 171, 130, 255 },
+{ "MediumPurple2", 159, 121, 238 },
+{ "MediumPurple3", 137, 104, 205 },
+{ "MediumPurple4", 93, 71, 139 },
+{ "thistle1", 255, 225, 255 },
+{ "thistle2", 238, 210, 238 },
+{ "thistle3", 205, 181, 205 },
+{ "thistle4", 139, 123, 139 },
+{ "gray0", 0, 0, 0 },
+{ "grey0", 0, 0, 0 },
+{ "gray1", 3, 3, 3 },
+{ "grey1", 3, 3, 3 },
+{ "gray2", 5, 5, 5 },
+{ "grey2", 5, 5, 5 },
+{ "gray3", 8, 8, 8 },
+{ "grey3", 8, 8, 8 },
+{ "gray4", 10, 10, 10 },
+{ "grey4", 10, 10, 10 },
+{ "gray5", 13, 13, 13 },
+{ "grey5", 13, 13, 13 },
+{ "gray6", 15, 15, 15 },
+{ "grey6", 15, 15, 15 },
+{ "gray7", 18, 18, 18 },
+{ "grey7", 18, 18, 18 },
+{ "gray8", 20, 20, 20 },
+{ "grey8", 20, 20, 20 },
+{ "gray9", 23, 23, 23 },
+{ "grey9", 23, 23, 23 },
+{ "gray10", 26, 26, 26 },
+{ "grey10", 26, 26, 26 },
+{ "gray11", 28, 28, 28 },
+{ "grey11", 28, 28, 28 },
+{ "gray12", 31, 31, 31 },
+{ "grey12", 31, 31, 31 },
+{ "gray13", 33, 33, 33 },
+{ "grey13", 33, 33, 33 },
+{ "gray14", 36, 36, 36 },
+{ "grey14", 36, 36, 36 },
+{ "gray15", 38, 38, 38 },
+{ "grey15", 38, 38, 38 },
+{ "gray16", 41, 41, 41 },
+{ "grey16", 41, 41, 41 },
+{ "gray17", 43, 43, 43 },
+{ "grey17", 43, 43, 43 },
+{ "gray18", 46, 46, 46 },
+{ "grey18", 46, 46, 46 },
+{ "gray19", 48, 48, 48 },
+{ "grey19", 48, 48, 48 },
+{ "gray20", 51, 51, 51 },
+{ "grey20", 51, 51, 51 },
+{ "gray21", 54, 54, 54 },
+{ "grey21", 54, 54, 54 },
+{ "gray22", 56, 56, 56 },
+{ "grey22", 56, 56, 56 },
+{ "gray23", 59, 59, 59 },
+{ "grey23", 59, 59, 59 },
+{ "gray24", 61, 61, 61 },
+{ "grey24", 61, 61, 61 },
+{ "gray25", 64, 64, 64 },
+{ "grey25", 64, 64, 64 },
+{ "gray26", 66, 66, 66 },
+{ "grey26", 66, 66, 66 },
+{ "gray27", 69, 69, 69 },
+{ "grey27", 69, 69, 69 },
+{ "gray28", 71, 71, 71 },
+{ "grey28", 71, 71, 71 },
+{ "gray29", 74, 74, 74 },
+{ "grey29", 74, 74, 74 },
+{ "gray30", 77, 77, 77 },
+{ "grey30", 77, 77, 77 },
+{ "gray31", 79, 79, 79 },
+{ "grey31", 79, 79, 79 },
+{ "gray32", 82, 82, 82 },
+{ "grey32", 82, 82, 82 },
+{ "gray33", 84, 84, 84 },
+{ "grey33", 84, 84, 84 },
+{ "gray34", 87, 87, 87 },
+{ "grey34", 87, 87, 87 },
+{ "gray35", 89, 89, 89 },
+{ "grey35", 89, 89, 89 },
+{ "gray36", 92, 92, 92 },
+{ "grey36", 92, 92, 92 },
+{ "gray37", 94, 94, 94 },
+{ "grey37", 94, 94, 94 },
+{ "gray38", 97, 97, 97 },
+{ "grey38", 97, 97, 97 },
+{ "gray39", 99, 99, 99 },
+{ "grey39", 99, 99, 99 },
+{ "gray40", 102, 102, 102 },
+{ "grey40", 102, 102, 102 },
+{ "gray41", 105, 105, 105 },
+{ "grey41", 105, 105, 105 },
+{ "gray42", 107, 107, 107 },
+{ "grey42", 107, 107, 107 },
+{ "gray43", 110, 110, 110 },
+{ "grey43", 110, 110, 110 },
+{ "gray44", 112, 112, 112 },
+{ "grey44", 112, 112, 112 },
+{ "gray45", 115, 115, 115 },
+{ "grey45", 115, 115, 115 },
+{ "gray46", 117, 117, 117 },
+{ "grey46", 117, 117, 117 },
+{ "gray47", 120, 120, 120 },
+{ "grey47", 120, 120, 120 },
+{ "gray48", 122, 122, 122 },
+{ "grey48", 122, 122, 122 },
+{ "gray49", 125, 125, 125 },
+{ "grey49", 125, 125, 125 },
+{ "gray50", 127, 127, 127 },
+{ "grey50", 127, 127, 127 },
+{ "gray51", 130, 130, 130 },
+{ "grey51", 130, 130, 130 },
+{ "gray52", 133, 133, 133 },
+{ "grey52", 133, 133, 133 },
+{ "gray53", 135, 135, 135 },
+{ "grey53", 135, 135, 135 },
+{ "gray54", 138, 138, 138 },
+{ "grey54", 138, 138, 138 },
+{ "gray55", 140, 140, 140 },
+{ "grey55", 140, 140, 140 },
+{ "gray56", 143, 143, 143 },
+{ "grey56", 143, 143, 143 },
+{ "gray57", 145, 145, 145 },
+{ "grey57", 145, 145, 145 },
+{ "gray58", 148, 148, 148 },
+{ "grey58", 148, 148, 148 },
+{ "gray59", 150, 150, 150 },
+{ "grey59", 150, 150, 150 },
+{ "gray60", 153, 153, 153 },
+{ "grey60", 153, 153, 153 },
+{ "gray61", 156, 156, 156 },
+{ "grey61", 156, 156, 156 },
+{ "gray62", 158, 158, 158 },
+{ "grey62", 158, 158, 158 },
+{ "gray63", 161, 161, 161 },
+{ "grey63", 161, 161, 161 },
+{ "gray64", 163, 163, 163 },
+{ "grey64", 163, 163, 163 },
+{ "gray65", 166, 166, 166 },
+{ "grey65", 166, 166, 166 },
+{ "gray66", 168, 168, 168 },
+{ "grey66", 168, 168, 168 },
+{ "gray67", 171, 171, 171 },
+{ "grey67", 171, 171, 171 },
+{ "gray68", 173, 173, 173 },
+{ "grey68", 173, 173, 173 },
+{ "gray69", 176, 176, 176 },
+{ "grey69", 176, 176, 176 },
+{ "gray70", 179, 179, 179 },
+{ "grey70", 179, 179, 179 },
+{ "gray71", 181, 181, 181 },
+{ "grey71", 181, 181, 181 },
+{ "gray72", 184, 184, 184 },
+{ "grey72", 184, 184, 184 },
+{ "gray73", 186, 186, 186 },
+{ "grey73", 186, 186, 186 },
+{ "gray74", 189, 189, 189 },
+{ "grey74", 189, 189, 189 },
+{ "gray75", 191, 191, 191 },
+{ "grey75", 191, 191, 191 },
+{ "gray76", 194, 194, 194 },
+{ "grey76", 194, 194, 194 },
+{ "gray77", 196, 196, 196 },
+{ "grey77", 196, 196, 196 },
+{ "gray78", 199, 199, 199 },
+{ "grey78", 199, 199, 199 },
+{ "gray79", 201, 201, 201 },
+{ "grey79", 201, 201, 201 },
+{ "gray80", 204, 204, 204 },
+{ "grey80", 204, 204, 204 },
+{ "gray81", 207, 207, 207 },
+{ "grey81", 207, 207, 207 },
+{ "gray82", 209, 209, 209 },
+{ "grey82", 209, 209, 209 },
+{ "gray83", 212, 212, 212 },
+{ "grey83", 212, 212, 212 },
+{ "gray84", 214, 214, 214 },
+{ "grey84", 214, 214, 214 },
+{ "gray85", 217, 217, 217 },
+{ "grey85", 217, 217, 217 },
+{ "gray86", 219, 219, 219 },
+{ "grey86", 219, 219, 219 },
+{ "gray87", 222, 222, 222 },
+{ "grey87", 222, 222, 222 },
+{ "gray88", 224, 224, 224 },
+{ "grey88", 224, 224, 224 },
+{ "gray89", 227, 227, 227 },
+{ "grey89", 227, 227, 227 },
+{ "gray90", 229, 229, 229 },
+{ "grey90", 229, 229, 229 },
+{ "gray91", 232, 232, 232 },
+{ "grey91", 232, 232, 232 },
+{ "gray92", 235, 235, 235 },
+{ "grey92", 235, 235, 235 },
+{ "gray93", 237, 237, 237 },
+{ "grey93", 237, 237, 237 },
+{ "gray94", 240, 240, 240 },
+{ "grey94", 240, 240, 240 },
+{ "gray95", 242, 242, 242 },
+{ "grey95", 242, 242, 242 },
+{ "gray96", 245, 245, 245 },
+{ "grey96", 245, 245, 245 },
+{ "gray97", 247, 247, 247 },
+{ "grey97", 247, 247, 247 },
+{ "gray98", 250, 250, 250 },
+{ "grey98", 250, 250, 250 },
+{ "gray99", 252, 252, 252 },
+{ "grey99", 252, 252, 252 },
+{ "gray100", 255, 255, 255 },
+{ "grey100", 255, 255, 255 },
+{ "DarkGrey", 169, 169, 169 },
+{ "DarkGray", 169, 169, 169 },
+{ "DarkBlue", 0, 0, 139 },
+{ "DarkCyan", 0, 139, 139 },
+{ "DarkMagenta", 139, 0, 139 },
+{ "DarkRed", 139, 0, 0 },
+{ "LightGreen", 144, 238, 144 },
+{ nullptr, 0 , 0, 0}
+};
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_IXPM_RGBTABLE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/ixpm/xpmread.cxx b/vcl/source/filter/ixpm/xpmread.cxx
new file mode 100644
index 000000000..2a979d2df
--- /dev/null
+++ b/vcl/source/filter/ixpm/xpmread.cxx
@@ -0,0 +1,697 @@
+/* -*- 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 <filter/XpmReader.hxx>
+
+#include <vcl/graph.hxx>
+#include <tools/stream.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <graphic/GraphicReader.hxx>
+
+#include "rgbtable.hxx"
+
+#include <cstring>
+#include <array>
+#include <map>
+
+#define XPMTEMPBUFSIZE 0x00008000
+#define XPMSTRINGBUF 0x00008000
+
+#define XPMIDENTIFIER 0x00000001 // mnIdentifier includes one of the six phases
+#define XPMDEFINITION 0x00000002 // the XPM format consists of
+#define XPMVALUES 0x00000003
+#define XPMCOLORS 0x00000004
+#define XPMPIXELS 0x00000005
+#define XPMEXTENSIONS 0x00000006
+#define XPMENDEXT 0x00000007
+
+#define XPMREMARK 0x00000001 // defines used by mnStatus
+#define XPMDOUBLE 0x00000002
+#define XPMSTRING 0x00000004
+#define XPMFINISHED 0x00000008
+
+namespace {
+
+enum ReadState
+{
+ XPMREAD_OK,
+ XPMREAD_ERROR,
+ XPMREAD_NEED_MORE
+};
+
+}
+
+class BitmapWriteAccess;
+class Graphic;
+
+namespace {
+
+class XPMReader : public GraphicReader
+{
+private:
+
+ SvStream& mrIStm;
+ Bitmap maBmp;
+ BitmapScopedWriteAccess mpAcc;
+ Bitmap maMaskBmp;
+ BitmapScopedWriteAccess mpMaskAcc;
+ tools::Long mnLastPos;
+
+ sal_uLong mnWidth;
+ sal_uLong mnHeight;
+ sal_uLong mnColors;
+ sal_uInt32 mnCpp; // characters per pix
+ bool mbTransparent;
+ bool mbStatus;
+ sal_uLong mnStatus;
+ sal_uLong mnIdentifier;
+ sal_uInt8 mcThisByte;
+ sal_uInt8 mcLastByte;
+ sal_uLong mnTempAvail;
+ sal_uInt8* mpTempBuf;
+ sal_uInt8* mpTempPtr;
+ // each key is ( mnCpp )Byte(s)-> ASCII entry assigned to the colour
+ // each colordata is
+ // 1 Byte -> 0xFF if colour is transparent
+ // 3 Bytes -> RGB value of the colour
+ typedef std::array<sal_uInt8,4> colordata;
+ typedef std::map<OString, colordata> colormap;
+ colormap maColMap;
+ sal_uLong mnStringSize;
+ sal_uInt8* mpStringBuf;
+ sal_uLong mnParaSize;
+ sal_uInt8* mpPara;
+
+ bool ImplGetString();
+ bool ImplGetColor();
+ bool ImplGetScanLine( sal_uLong );
+ bool ImplGetColSub(colordata &rDest);
+ bool ImplGetColKey( sal_uInt8 );
+ void ImplGetRGBHex(colordata &rDest, sal_uLong);
+ bool ImplGetPara( sal_uLong numb );
+ static bool ImplCompare(sal_uInt8 const *, sal_uInt8 const *, sal_uLong);
+ sal_uLong ImplGetULONG( sal_uLong nPara );
+
+public:
+ explicit XPMReader( SvStream& rStm );
+
+ ReadState ReadXPM( Graphic& rGraphic );
+};
+
+}
+
+XPMReader::XPMReader(SvStream& rStm)
+ : mrIStm(rStm)
+ , mnLastPos(rStm.Tell())
+ , mnWidth(0)
+ , mnHeight(0)
+ , mnColors(0)
+ , mnCpp(0)
+ , mbTransparent(false)
+ , mbStatus(true)
+ , mnStatus( 0 )
+ , mnIdentifier(XPMIDENTIFIER)
+ , mcThisByte(0)
+ , mcLastByte(0)
+ , mnTempAvail(0)
+ , mpTempBuf(nullptr)
+ , mpTempPtr(nullptr)
+ , mnStringSize(0)
+ , mpStringBuf(nullptr)
+ , mnParaSize(0)
+ , mpPara(nullptr)
+{
+}
+
+ReadState XPMReader::ReadXPM( Graphic& rGraphic )
+{
+ ReadState eReadState;
+ sal_uInt8 cDummy;
+
+ // check if we can real ALL
+ mrIStm.Seek( STREAM_SEEK_TO_END );
+ mrIStm.ReadUChar( cDummy );
+
+ // if we could not read all
+ // return and wait for new data
+ if ( mrIStm.GetError() != ERRCODE_IO_PENDING )
+ {
+ mrIStm.Seek( mnLastPos );
+ mbStatus = true;
+
+ if ( mbStatus )
+ {
+ mpStringBuf = new sal_uInt8 [ XPMSTRINGBUF ];
+ mpTempBuf = new sal_uInt8 [ XPMTEMPBUFSIZE ];
+
+ mbStatus = ImplGetString();
+ if ( mbStatus )
+ {
+ mnIdentifier = XPMVALUES; // fetch Bitmap information
+ mnWidth = ImplGetULONG( 0 );
+ mnHeight = ImplGetULONG( 1 );
+ mnColors = ImplGetULONG( 2 );
+ mnCpp = ImplGetULONG( 3 );
+ }
+ if ( mnColors > ( SAL_MAX_UINT32 / ( 4 + mnCpp ) ) )
+ mbStatus = false;
+ if ( ( mnWidth * mnCpp ) >= XPMSTRINGBUF )
+ mbStatus = false;
+ //xpms are a minimum of one character (one byte) per pixel, so if the file isn't
+ //even that long, it's not all there
+ if (mrIStm.remainingSize() + mnTempAvail < static_cast<sal_uInt64>(mnWidth) * mnHeight)
+ mbStatus = false;
+ if ( mbStatus && mnWidth && mnHeight && mnColors && mnCpp )
+ {
+ mnIdentifier = XPMCOLORS;
+
+ for (sal_uLong i = 0; i < mnColors; ++i)
+ {
+ if (!ImplGetColor())
+ {
+ mbStatus = false;
+ break;
+ }
+ }
+
+ if ( mbStatus )
+ {
+ // create a 24bit graphic when more as 256 colours present
+ auto ePixelFormat = vcl::PixelFormat::INVALID;
+ if ( mnColors > 256 )
+ ePixelFormat = vcl::PixelFormat::N24_BPP;
+ else if ( mnColors > 2 )
+ ePixelFormat = vcl::PixelFormat::N8_BPP;
+ else
+ ePixelFormat = vcl::PixelFormat::N1_BPP;
+
+ maBmp = Bitmap(Size(mnWidth, mnHeight), ePixelFormat);
+ mpAcc = BitmapScopedWriteAccess(maBmp);
+
+ // mbTransparent is TRUE if at least one colour is transparent
+ if ( mbTransparent )
+ {
+ maMaskBmp = Bitmap(Size(mnWidth, mnHeight), vcl::PixelFormat::N1_BPP);
+ mpMaskAcc = BitmapScopedWriteAccess(maMaskBmp);
+ if ( !mpMaskAcc )
+ mbStatus = false;
+ }
+ if( mpAcc && mbStatus )
+ {
+ if (mnColors <= 256) // palette is only needed by using less than 257
+ { // colors
+ sal_uInt8 i = 0;
+ for (auto& elem : maColMap)
+ {
+ mpAcc->SetPaletteColor(i, Color(elem.second[1], elem.second[2], elem.second[3]));
+ //reuse map entry, overwrite color with palette index
+ elem.second[1] = i;
+ i++;
+ }
+ }
+
+ // now we get the bitmap data
+ mnIdentifier = XPMPIXELS;
+ for (sal_uLong i = 0; i < mnHeight; ++i)
+ {
+ if ( !ImplGetScanLine( i ) )
+ {
+ mbStatus = false;
+ break;
+ }
+ }
+ mnIdentifier = XPMEXTENSIONS;
+ }
+ }
+ }
+
+ delete[] mpStringBuf;
+ delete[] mpTempBuf;
+
+ }
+ if( mbStatus )
+ {
+ mpAcc.reset();
+ if ( mpMaskAcc )
+ {
+ mpMaskAcc.reset();
+ rGraphic = Graphic( BitmapEx( maBmp, maMaskBmp ) );
+ }
+ else
+ {
+ rGraphic = BitmapEx(maBmp);
+ }
+ eReadState = XPMREAD_OK;
+ }
+ else
+ {
+ mpMaskAcc.reset();
+ mpAcc.reset();
+
+ eReadState = XPMREAD_ERROR;
+ }
+ }
+ else
+ {
+ mrIStm.ResetError();
+ eReadState = XPMREAD_NEED_MORE;
+ }
+ return eReadState;
+}
+
+// ImplGetColor returns various colour values,
+// returns TRUE if various colours could be assigned
+bool XPMReader::ImplGetColor()
+{
+ sal_uInt8* pString = mpStringBuf;
+ if (!ImplGetString())
+ return false;
+
+ if (mnStringSize < mnCpp)
+ return false;
+
+ OString aKey(reinterpret_cast<char*>(pString), mnCpp);
+ colordata aValue{0};
+ bool bStatus = ImplGetColSub(aValue);
+ if (bStatus)
+ {
+ maColMap[aKey] = aValue;
+ }
+ return bStatus;
+}
+
+// ImpGetScanLine reads the string mpBufSize and writes the pixel in the
+// Bitmap. Parameter nY is the horizontal position.
+bool XPMReader::ImplGetScanLine( sal_uLong nY )
+{
+ bool bStatus = ImplGetString();
+ sal_uInt8* pString = mpStringBuf;
+ BitmapColor aWhite;
+ BitmapColor aBlack;
+
+ if ( bStatus )
+ {
+ if ( mpMaskAcc )
+ {
+ aWhite = mpMaskAcc->GetBestMatchingColor( COL_WHITE );
+ aBlack = mpMaskAcc->GetBestMatchingColor( COL_BLACK );
+ }
+ if ( mnStringSize != ( mnWidth * mnCpp ))
+ bStatus = false;
+ else
+ {
+ Scanline pScanline = mpAcc->GetScanline(nY);
+ Scanline pMaskScanline = mpMaskAcc ? mpMaskAcc->GetScanline(nY) : nullptr;
+ for (sal_uLong i = 0; i < mnWidth; ++i)
+ {
+ OString aKey(reinterpret_cast<char*>(pString), mnCpp);
+ auto it = maColMap.find(aKey);
+ if (it != maColMap.end())
+ {
+ if (mnColors > 256)
+ mpAcc->SetPixelOnData(pScanline, i, Color(it->second[1], it->second[2], it->second[3]));
+ else
+ mpAcc->SetPixelOnData(pScanline, i, BitmapColor(it->second[1]));
+ if (pMaskScanline)
+ mpMaskAcc->SetPixelOnData(pMaskScanline, i, it->second[0] ? aWhite : aBlack);
+ }
+ pString += mnCpp;
+ }
+ }
+ }
+ return bStatus;
+}
+
+// tries to determine a colour value from mpStringBuf
+// if a colour was found the RGB value is written a pDest[1]..pDest[2]
+// pDest[0] contains 0xFF if the colour is transparent otherwise 0
+
+bool XPMReader::ImplGetColSub(colordata &rDest)
+{
+ unsigned char cTransparent[] = "None";
+
+ bool bColStatus = false;
+
+ if ( ImplGetColKey( 'c' ) || ImplGetColKey( 'm' ) || ImplGetColKey( 'g' ) )
+ {
+ // hexentry for RGB or HSV color ?
+ if (*mpPara == '#')
+ {
+ rDest[0] = 0;
+ bColStatus = true;
+ switch ( mnParaSize )
+ {
+ case 25 :
+ ImplGetRGBHex(rDest, 6);
+ break;
+ case 13 :
+ ImplGetRGBHex(rDest, 2);
+ break;
+ case 7 :
+ ImplGetRGBHex(rDest, 0);
+ break;
+ default:
+ bColStatus = false;
+ break;
+ }
+ }
+ // maybe pixel is transparent
+ else if ( ImplCompare( &cTransparent[0], mpPara, 4 ))
+ {
+ rDest[0] = 0xff;
+ bColStatus = true;
+ mbTransparent = true;
+ }
+ // last we will try to get the colorname
+ else if ( mnParaSize > 2 ) // name must enlarge the minimum size
+ {
+ sal_uLong i = 0;
+ while ( true )
+ {
+ if ( pRGBTable[ i ].name == nullptr )
+ break;
+ if ( std::strlen(pRGBTable[i].name) > mnParaSize &&
+ pRGBTable[ i ].name[ mnParaSize ] == 0 )
+ {
+ if ( ImplCompare ( reinterpret_cast<unsigned char const *>(pRGBTable[ i ].name),
+ mpPara, mnParaSize ) )
+ {
+ bColStatus = true;
+ rDest[0] = 0;
+ rDest[1] = pRGBTable[i].red;
+ rDest[2] = pRGBTable[i].green;
+ rDest[3] = pRGBTable[i].blue;
+ break;
+ }
+ }
+ i++;
+ }
+ }
+ }
+ return bColStatus;
+}
+
+// ImplGetColKey searches string mpStringBuf for a parameter 'nKey'
+// and returns a boolean. (if TRUE mpPara and mnParaSize will be set)
+
+bool XPMReader::ImplGetColKey( sal_uInt8 nKey )
+{
+ sal_uInt8 nTemp, nPrev = ' ';
+
+ if (mnStringSize < mnCpp + 1)
+ return false;
+
+ mpPara = mpStringBuf + mnCpp + 1;
+ mnParaSize = 0;
+
+ while ( *mpPara != 0 )
+ {
+ if ( *mpPara == nKey )
+ {
+ nTemp = *( mpPara + 1 );
+ if ( nTemp == ' ' || nTemp == 0x09 )
+ {
+ if ( nPrev == ' ' || nPrev == 0x09 )
+ break;
+ }
+ }
+ nPrev = *mpPara;
+ mpPara++;
+ }
+ if ( *mpPara )
+ {
+ mpPara++;
+ while ( (*mpPara == ' ') || (*mpPara == 0x09) )
+ {
+ mpPara++;
+ }
+ if ( *mpPara != 0 )
+ {
+ while ( *(mpPara+mnParaSize) != ' ' && *(mpPara+mnParaSize) != 0x09 &&
+ *(mpPara+mnParaSize) != 0 )
+ {
+ mnParaSize++;
+ }
+ }
+ }
+ return mnParaSize != 0;
+}
+
+// ImplGetRGBHex translates the ASCII-Hexadecimalvalue belonging to mpPara
+// in a RGB value and writes this to rDest
+// below formats should be contained in mpPara:
+// if nAdd = 0 : '#12ab12' -> RGB = 0x12, 0xab, 0x12
+// 2 : '#1234abcd1234' " " " "
+// 6 : '#12345678abcdefab12345678' " " " "
+
+void XPMReader::ImplGetRGBHex(colordata &rDest, sal_uLong nAdd)
+{
+ sal_uInt8* pPtr = mpPara+1;
+
+ for (sal_uLong i = 1; i < 4; ++i)
+ {
+ sal_uInt8 nHex = (*pPtr++) - '0';
+ if ( nHex > 9 )
+ nHex = ((nHex - 'A' + '0') & 7) + 10;
+
+ sal_uInt8 nTemp = (*pPtr++) - '0';
+ if ( nTemp > 9 )
+ nTemp = ((nTemp - 'A' + '0') & 7) + 10;
+ nHex = ( nHex << 4 ) + nTemp;
+
+ pPtr += nAdd;
+ rDest[i] = nHex;
+ }
+}
+
+// ImplGetUlong returns the value of a up to 6-digit long ASCII-decimal number.
+
+sal_uLong XPMReader::ImplGetULONG( sal_uLong nPara )
+{
+ if ( ImplGetPara ( nPara ) )
+ {
+ sal_uLong nRetValue = 0;
+ sal_uInt8* pPtr = mpPara;
+
+ if ( ( mnParaSize > 6 ) || ( mnParaSize == 0 ) ) return 0;
+ for ( sal_uLong i = 0; i < mnParaSize; i++ )
+ {
+ sal_uInt8 j = (*pPtr++) - 48;
+ if ( j > 9 ) return 0; // ascii is invalid
+ nRetValue*=10;
+ nRetValue+=j;
+ }
+ return nRetValue;
+ }
+ else return 0;
+}
+
+bool XPMReader::ImplCompare(sal_uInt8 const * pSource, sal_uInt8 const * pDest, sal_uLong nSize)
+{
+ for (sal_uLong i = 0; i < nSize; ++i)
+ {
+ if ( ( pSource[i]&~0x20 ) != ( pDest[i]&~0x20 ) )
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+// ImplGetPara tries to retrieve nNumb (0...x) parameters from mpStringBuf.
+// Parameters are separated by spaces or tabs.
+// If a parameter was found then the return value is TRUE and mpPara + mnParaSize
+// are set.
+
+bool XPMReader::ImplGetPara ( sal_uLong nNumb )
+{
+ sal_uInt8 nByte;
+ sal_uLong nSize = 0;
+ sal_uInt8* pPtr = mpStringBuf;
+ sal_uLong nCount = 0;
+
+ if ( ( *pPtr != ' ' ) && ( *pPtr != 0x09 ) )
+ {
+ mpPara = pPtr;
+ mnParaSize = 0;
+ nCount = 0;
+ }
+ else
+ {
+ mpPara = nullptr;
+ nCount = 0xffffffff;
+ }
+
+ while ( nSize < mnStringSize )
+ {
+ nByte = *pPtr;
+
+ if ( mpPara )
+ {
+ if ( ( nByte == ' ' ) || ( nByte == 0x09 ) )
+ {
+ if ( nCount == nNumb )
+ break;
+ else
+ mpPara = nullptr;
+ }
+ else
+ mnParaSize++;
+ }
+ else
+ {
+ if ( ( nByte != ' ' ) && ( nByte != 0x09 ) )
+ {
+ mpPara = pPtr;
+ mnParaSize = 1;
+ nCount++;
+ }
+ }
+ nSize++;
+ pPtr++;
+ }
+ return ( ( nCount == nNumb ) && mpPara );
+}
+
+// The next string is read and stored in mpStringBuf (terminated with 0);
+// mnStringSize contains the size of the string read.
+// Comments like '//' and '/*...*/' are skipped.
+
+bool XPMReader::ImplGetString()
+{
+ sal_uInt8 const sID[] = "/* XPM */";
+ sal_uInt8* pString = mpStringBuf;
+
+ mnStringSize = 0;
+ mpStringBuf[0] = 0;
+
+ while( mbStatus && ( mnStatus != XPMFINISHED ) )
+ {
+ if ( mnTempAvail == 0 )
+ {
+ mnTempAvail = mrIStm.ReadBytes( mpTempBuf, XPMTEMPBUFSIZE );
+ if ( mnTempAvail == 0 )
+ break;
+
+ mpTempPtr = mpTempBuf;
+
+ if ( mnIdentifier == XPMIDENTIFIER )
+ {
+ if ( mnTempAvail <= 50 )
+ {
+ mbStatus = false; // file is too short to be a correct XPM format
+ break;
+ }
+ for ( int i = 0; i < 9; i++ ) // searching for "/* XPM */"
+ if ( *mpTempPtr++ != sID[i] )
+ {
+ mbStatus = false;
+ break;
+ }
+ mnTempAvail-=9;
+ mnIdentifier++;
+ }
+ }
+ mcLastByte = mcThisByte;
+ mcThisByte = *mpTempPtr++;
+ mnTempAvail--;
+
+ if ( mnStatus & XPMDOUBLE )
+ {
+ if ( mcThisByte == 0x0a )
+ mnStatus &=~XPMDOUBLE;
+ continue;
+ }
+ if ( mnStatus & XPMREMARK )
+ {
+ if ( ( mcThisByte == '/' ) && ( mcLastByte == '*' ) )
+ mnStatus &=~XPMREMARK;
+ continue;
+ }
+ if ( mnStatus & XPMSTRING ) // characters in string
+ {
+ if ( mcThisByte == '"' )
+ {
+ mnStatus &=~XPMSTRING; // end of parameter by eol
+ break;
+ }
+ if ( mnStringSize >= ( XPMSTRINGBUF - 1 ) )
+ {
+ mbStatus = false;
+ break;
+ }
+ *pString++ = mcThisByte;
+ pString[0] = 0;
+ mnStringSize++;
+ continue;
+ }
+ else
+ { // characters beside string
+ switch ( mcThisByte )
+ {
+ case '*' :
+ if ( mcLastByte == '/' ) mnStatus |= XPMREMARK;
+ break;
+ case '/' :
+ if ( mcLastByte == '/' ) mnStatus |= XPMDOUBLE;
+ break;
+ case '"' : mnStatus |= XPMSTRING;
+ break;
+ case '{' :
+ if ( mnIdentifier == XPMDEFINITION )
+ mnIdentifier++;
+ break;
+ case '}' :
+ if ( mnIdentifier == XPMENDEXT )
+ mnStatus = XPMFINISHED;
+ break;
+ }
+ }
+ }
+ return mbStatus;
+}
+
+
+VCL_DLLPUBLIC bool ImportXPM( SvStream& rStm, Graphic& rGraphic )
+{
+ std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext();
+ rGraphic.SetReaderContext(nullptr);
+ XPMReader* pXPMReader = dynamic_cast<XPMReader*>( pContext.get() );
+ if (!pXPMReader)
+ {
+ pContext = std::make_shared<XPMReader>( rStm );
+ pXPMReader = static_cast<XPMReader*>( pContext.get() );
+ }
+
+ bool bRet = true;
+
+ ReadState eReadState = pXPMReader->ReadXPM( rGraphic );
+
+ if( eReadState == XPMREAD_ERROR )
+ {
+ bRet = false;
+ }
+ else if( eReadState == XPMREAD_NEED_MORE )
+ rGraphic.SetReaderContext( pContext );
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/Exif.cxx b/vcl/source/filter/jpeg/Exif.cxx
new file mode 100644
index 000000000..53b55f69a
--- /dev/null
+++ b/vcl/source/filter/jpeg/Exif.cxx
@@ -0,0 +1,288 @@
+/* -*- 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 "Exif.hxx"
+#include <memory>
+#include <osl/endian.h>
+#include <tools/stream.hxx>
+
+Exif::Exif() :
+ maOrientation(exif::TOP_LEFT),
+ mbExifPresent(false)
+{}
+
+void Exif::setOrientation(exif::Orientation aOrientation) {
+ maOrientation = aOrientation;
+}
+
+exif::Orientation Exif::convertToOrientation(sal_Int32 value)
+{
+ switch(value) {
+ case 1: return exif::TOP_LEFT;
+ case 2: return exif::TOP_RIGHT;
+ case 3: return exif::BOTTOM_RIGHT;
+ case 4: return exif::BOTTOM_LEFT;
+ case 5: return exif::LEFT_TOP;
+ case 6: return exif::RIGHT_TOP;
+ case 7: return exif::RIGHT_BOTTOM;
+ case 8: return exif::LEFT_BOTTOM;
+ }
+ return exif::TOP_LEFT;
+}
+
+Degree10 Exif::getRotation() const
+{
+ switch(maOrientation) {
+ case exif::TOP_LEFT:
+ return 0_deg10;
+ case exif::BOTTOM_RIGHT:
+ return 1800_deg10;
+ case exif::RIGHT_TOP:
+ return 2700_deg10;
+ case exif::LEFT_BOTTOM:
+ return 900_deg10;
+ default:
+ break;
+ }
+ return 0_deg10;
+}
+
+bool Exif::read(SvStream& rStream)
+{
+ sal_Int32 nStreamPosition = rStream.Tell();
+ bool result = processJpeg(rStream, false);
+ rStream.Seek( nStreamPosition );
+
+ return result;
+}
+
+void Exif::write(SvStream& rStream)
+{
+ sal_Int32 nStreamPosition = rStream.Tell();
+ processJpeg(rStream, true);
+ rStream.Seek( nStreamPosition );
+}
+
+bool Exif::processJpeg(SvStream& rStream, bool bSetValue)
+{
+ sal_uInt16 aMagic16;
+ sal_uInt16 aLength;
+
+ sal_uInt32 aSize = rStream.TellEnd();
+ rStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ rStream.SetEndian( SvStreamEndian::BIG );
+ rStream.ReadUInt16( aMagic16 );
+
+ // Compare JPEG magic bytes
+ if( 0xFFD8 != aMagic16 )
+ {
+ return false;
+ }
+
+ sal_uInt32 aPreviousPosition = STREAM_SEEK_TO_BEGIN;
+
+ while(true)
+ {
+ sal_uInt8 aMarker = 0xD9;
+ sal_Int32 aCount;
+
+ for (aCount = 0; aCount < 7; aCount++)
+ {
+ rStream.ReadUChar( aMarker );
+ if (aMarker != 0xFF)
+ {
+ break;
+ }
+ if (aCount >= 6)
+ {
+ return false;
+ }
+ }
+
+ rStream.ReadUInt16( aLength );
+
+ if (aLength < 8 || aLength > rStream.remainingSize())
+ {
+ return false;
+ }
+
+ if (aMarker == 0xE1)
+ {
+ return processExif(rStream, aLength, bSetValue);
+ }
+ else if (aMarker == 0xD9)
+ {
+ return false;
+ }
+ else
+ {
+ sal_uInt32 aCurrentPosition = rStream.SeekRel(aLength-1);
+ if (aCurrentPosition == aPreviousPosition || aCurrentPosition > aSize)
+ {
+ return false;
+ }
+ aPreviousPosition = aCurrentPosition;
+ }
+ }
+ return false;
+}
+
+namespace {
+
+sal_uInt16 read16(sal_uInt8 const (& data)[2], bool littleEndian) {
+ if (littleEndian) {
+ return data[0] | (sal_uInt16(data[1]) << 8);
+ } else {
+ return data[1] | (sal_uInt16(data[0]) << 8);
+ }
+}
+
+void write16(sal_uInt16 value, sal_uInt8 (& data)[2], bool littleEndian) {
+ if (littleEndian) {
+ data[0] = value & 0xFF;
+ data[1] = value >> 8;
+ } else {
+ data[1] = value & 0xFF;
+ data[0] = value >> 8;
+ }
+}
+
+void write32(sal_uInt32 value, sal_uInt8 (& data)[4], bool littleEndian) {
+ if (littleEndian) {
+ data[0] = value & 0xFF;
+ data[1] = (value >> 8) & 0xFF;
+ data[2] = (value >> 16) & 0xFF;
+ data[3] = value >> 24;
+ } else {
+ data[3] = value & 0xFF;
+ data[2] = (value >> 8) & 0xFF;
+ data[1] = (value >> 16) & 0xFF;
+ data[0] = value >> 24;
+ }
+}
+
+}
+
+void Exif::processIFD(sal_uInt8* pExifData, sal_uInt16 aLength, sal_uInt16 aOffset, sal_uInt16 aNumberOfTags, bool bSetValue, bool littleEndian)
+{
+ ExifIFD* ifd = nullptr;
+
+ while (aOffset <= aLength - 12 && aNumberOfTags > 0)
+ {
+ ifd = reinterpret_cast<ExifIFD*>(&pExifData[aOffset]);
+ sal_uInt16 tag = read16(ifd->tag, littleEndian);
+
+ if (tag == ORIENTATION)
+ {
+ if(bSetValue)
+ {
+ write16(3, ifd->type, littleEndian);
+ write32(1, ifd->count, littleEndian);
+ write16(
+ maOrientation, reinterpret_cast<sal_uInt8 (&)[2]>(ifd->offset), littleEndian);
+ }
+ else
+ {
+ sal_uInt16 nIfdOffset = read16(
+ reinterpret_cast<sal_uInt8 (&)[2]>(ifd->offset), littleEndian);
+ maOrientation = convertToOrientation(nIfdOffset);
+ }
+ }
+
+ aNumberOfTags--;
+ aOffset += 12;
+ }
+}
+
+bool Exif::processExif(SvStream& rStream, sal_uInt16 aSectionLength, bool bSetValue)
+{
+ sal_uInt32 aMagic32;
+ sal_uInt16 aMagic16;
+
+ rStream.ReadUInt32( aMagic32 );
+ rStream.ReadUInt16( aMagic16 );
+
+ // Compare EXIF magic bytes
+ if( 0x45786966 != aMagic32 || 0x0000 != aMagic16)
+ {
+ return false;
+ }
+
+ sal_uInt16 aLength = aSectionLength - 6; // Length = Section - Header
+
+ std::unique_ptr<sal_uInt8[]> aExifData(new sal_uInt8[aLength]);
+ sal_uInt32 aExifDataBeginPosition = rStream.Tell();
+
+ rStream.ReadBytes(aExifData.get(), aLength);
+
+ // Exif detected
+ mbExifPresent = true;
+
+ TiffHeader* aTiffHeader = reinterpret_cast<TiffHeader*>(&aExifData[0]);
+
+ bool bIntel = aTiffHeader->byteOrder == 0x4949; //little-endian
+ bool bMotorola = aTiffHeader->byteOrder == 0x4D4D; //big-endian
+
+ if (!bIntel && !bMotorola)
+ {
+ return false;
+ }
+
+ bool bSwap = false;
+
+#ifdef OSL_BIGENDIAN
+ if (bIntel)
+ bSwap = true;
+#else
+ if (bMotorola)
+ bSwap = true;
+#endif
+
+ if (bSwap)
+ {
+ aTiffHeader->tagAlign = OSL_SWAPWORD(aTiffHeader->tagAlign);
+ aTiffHeader->offset = OSL_SWAPDWORD(aTiffHeader->offset);
+ }
+
+ if (aTiffHeader->tagAlign != 0x002A) // TIFF tag
+ {
+ return false;
+ }
+
+ sal_uInt16 aOffset = aTiffHeader->offset;
+
+ sal_uInt16 aNumberOfTags = aExifData[aOffset];
+ if (bSwap)
+ {
+ aNumberOfTags = ((aExifData[aOffset] << 8) | aExifData[aOffset+1]);
+ }
+
+ processIFD(aExifData.get(), aLength, aOffset+2, aNumberOfTags, bSetValue, bIntel);
+
+ if (bSetValue)
+ {
+ rStream.Seek(aExifDataBeginPosition);
+ rStream.WriteBytes(aExifData.get(), aLength);
+ }
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/Exif.hxx b/vcl/source/filter/jpeg/Exif.hxx
new file mode 100644
index 000000000..287b7ae43
--- /dev/null
+++ b/vcl/source/filter/jpeg/Exif.hxx
@@ -0,0 +1,83 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <tools/stream.hxx>
+#include <tools/degree.hxx>
+
+namespace exif {
+
+enum Orientation {
+ TOP_LEFT = 1,
+ TOP_RIGHT = 2,
+ BOTTOM_RIGHT = 3,
+ BOTTOM_LEFT = 4,
+ LEFT_TOP = 5,
+ RIGHT_TOP = 6,
+ RIGHT_BOTTOM = 7,
+ LEFT_BOTTOM = 8
+};
+};
+
+enum Tag {
+ ORIENTATION = 0x0112
+};
+
+class Exif final
+{
+private:
+ exif::Orientation maOrientation;
+ bool mbExifPresent;
+
+ bool processJpeg(SvStream& rStream, bool bSetValue);
+ bool processExif(SvStream& rStream, sal_uInt16 aLength, bool bSetValue);
+ void processIFD(sal_uInt8* pExifData, sal_uInt16 aLength, sal_uInt16 aOffset, sal_uInt16 aNumberOfTags, bool bSetValue, bool bLittleEndian);
+
+ struct ExifIFD {
+ sal_uInt8 tag[2];
+ sal_uInt8 type[2];
+ sal_uInt8 count[4];
+ sal_uInt8 offset[4];
+ };
+
+ struct TiffHeader {
+ sal_uInt16 byteOrder;
+ sal_uInt16 tagAlign;
+ sal_uInt32 offset;
+ };
+
+ static exif::Orientation convertToOrientation(sal_Int32 value);
+
+public:
+ Exif();
+
+ bool hasExif() const { return mbExifPresent;}
+
+ exif::Orientation getOrientation() const { return maOrientation;}
+ Degree10 getRotation() const;
+
+ void setOrientation(exif::Orientation orientation);
+
+ bool read(SvStream& rStream);
+ void write(SvStream& rStream);
+
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegReader.cxx b/vcl/source/filter/jpeg/JpegReader.cxx
new file mode 100644
index 000000000..c68ba88d7
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegReader.cxx
@@ -0,0 +1,331 @@
+/* -*- 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 "jpeg.h"
+#include <jpeglib.h>
+#include <jerror.h>
+
+#include "JpegReader.hxx"
+#include <vcl/graphicfilter.hxx>
+#include <vcl/outdev.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <memory>
+
+#define BUFFER_SIZE 4096
+
+extern "C" {
+
+/*
+ * Initialize source --- called by jpeg_read_header
+ * before any data is actually read.
+ */
+static void init_source (j_decompress_ptr cinfo)
+{
+ SourceManagerStruct * source = reinterpret_cast<SourceManagerStruct *>(cinfo->src);
+
+ /* We reset the empty-input-file flag for each image,
+ * but we don't clear the input buffer.
+ * This is correct behavior for reading a series of images from one source.
+ */
+ source->start_of_file = TRUE;
+ source->no_data_available_failures = 0;
+}
+
+}
+
+static tools::Long StreamRead( SvStream* pStream, void* pBuffer, tools::Long nBufferSize )
+{
+ tools::Long nRead = 0;
+
+ if( pStream->GetError() != ERRCODE_IO_PENDING )
+ {
+ sal_uInt64 nInitialPosition = pStream->Tell();
+
+ nRead = static_cast<tools::Long>(pStream->ReadBytes(pBuffer, nBufferSize));
+
+ if( pStream->GetError() == ERRCODE_IO_PENDING )
+ {
+ // in order to search from the old position
+ // we temporarily reset the error
+ pStream->ResetError();
+ pStream->Seek( nInitialPosition );
+ pStream->SetError( ERRCODE_IO_PENDING );
+ }
+ }
+
+ return nRead;
+}
+
+extern "C" {
+
+static boolean fill_input_buffer (j_decompress_ptr cinfo)
+{
+ SourceManagerStruct * source = reinterpret_cast<SourceManagerStruct *>(cinfo->src);
+ size_t nbytes;
+
+ nbytes = StreamRead(source->stream, source->buffer, BUFFER_SIZE);
+
+ if (!nbytes)
+ {
+ source->no_data_available_failures++;
+ if (source->start_of_file) /* Treat empty input file as fatal error */
+ {
+ ERREXIT(cinfo, JERR_INPUT_EMPTY);
+ }
+ WARNMS(cinfo, JWRN_JPEG_EOF);
+ /* Insert a fake EOI marker */
+ source->buffer[0] = JOCTET(0xFF);
+ source->buffer[1] = JOCTET(JPEG_EOI);
+ nbytes = 2;
+ }
+
+ source->pub.next_input_byte = source->buffer;
+ source->pub.bytes_in_buffer = nbytes;
+ source->start_of_file = FALSE;
+
+ return TRUE;
+}
+
+static void skip_input_data (j_decompress_ptr cinfo, long numberOfBytes)
+{
+ SourceManagerStruct * source = reinterpret_cast<SourceManagerStruct *>(cinfo->src);
+
+ /* Just a dumb implementation for now. Could use fseek() except
+ * it doesn't work on pipes. Not clear that being smart is worth
+ * any trouble anyway --- large skips are infrequent.
+ */
+ if (numberOfBytes <= 0)
+ return;
+
+ while (numberOfBytes > static_cast<tools::Long>(source->pub.bytes_in_buffer))
+ {
+ numberOfBytes -= static_cast<tools::Long>(source->pub.bytes_in_buffer);
+ (void) fill_input_buffer(cinfo);
+
+ /* note we assume that fill_input_buffer will never return false,
+ * so suspension need not be handled.
+ */
+ }
+ source->pub.next_input_byte += static_cast<size_t>(numberOfBytes);
+ source->pub.bytes_in_buffer -= static_cast<size_t>(numberOfBytes);
+}
+
+static void term_source (j_decompress_ptr)
+{
+ /* no work necessary here */
+}
+
+}
+
+void jpeg_svstream_src (j_decompress_ptr cinfo, void* input)
+{
+ SourceManagerStruct * source;
+ SvStream* stream = static_cast<SvStream*>(input);
+
+ /* The source object and input buffer are made permanent so that a series
+ * of JPEG images can be read from the same file by calling jpeg_stdio_src
+ * only before the first one. (If we discarded the buffer at the end of
+ * one image, we'd likely lose the start of the next one.)
+ * This makes it unsafe to use this manager and a different source
+ * manager serially with the same JPEG object. Caveat programmer.
+ */
+
+ if (cinfo->src == nullptr)
+ { /* first time for this JPEG object? */
+ cinfo->src = static_cast<jpeg_source_mgr *>(
+ (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, sizeof(SourceManagerStruct)));
+ source = reinterpret_cast<SourceManagerStruct *>(cinfo->src);
+ source->buffer = static_cast<JOCTET *>(
+ (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, BUFFER_SIZE * sizeof(JOCTET)));
+ }
+
+ source = reinterpret_cast<SourceManagerStruct*>(cinfo->src);
+ source->pub.init_source = init_source;
+ source->pub.fill_input_buffer = fill_input_buffer;
+ source->pub.skip_input_data = skip_input_data;
+ source->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
+ source->pub.term_source = term_source;
+ source->stream = stream;
+ source->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
+ source->pub.next_input_byte = nullptr; /* until buffer loaded */
+}
+
+JPEGReader::JPEGReader( SvStream& rStream, GraphicFilterImportFlags nImportFlags ) :
+ mrStream ( rStream ),
+ mnLastPos ( rStream.Tell() ),
+ mnLastLines ( 0 ),
+ mbSetLogSize ( nImportFlags & GraphicFilterImportFlags::SetLogsizeForJpeg )
+{
+ maUpperName = "SVIJPEG";
+
+ if (!(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap))
+ {
+ mpBitmap.reset(new Bitmap());
+ mpIncompleteAlpha.reset(new Bitmap());
+ }
+}
+
+JPEGReader::~JPEGReader()
+{
+}
+
+bool JPEGReader::CreateBitmap(JPEGCreateBitmapParam const & rParam)
+{
+ if (rParam.nWidth > SAL_MAX_INT32 / 8 || rParam.nHeight > SAL_MAX_INT32 / 8)
+ return false; // avoid overflows later
+
+ if (rParam.nWidth == 0 || rParam.nHeight == 0)
+ return false;
+
+ Size aSize(rParam.nWidth, rParam.nHeight);
+ bool bGray = rParam.bGray;
+
+ mpBitmap.reset(new Bitmap());
+
+ sal_uInt64 nSize = aSize.Width() * aSize.Height();
+
+ if (nSize > SAL_MAX_INT32 / (bGray?1:3))
+ return false;
+
+ if( bGray )
+ {
+ BitmapPalette aGrayPal( 256 );
+
+ for( sal_uInt16 n = 0; n < 256; n++ )
+ {
+ const sal_uInt8 cGray = static_cast<sal_uInt8>(n);
+ aGrayPal[ n ] = BitmapColor( cGray, cGray, cGray );
+ }
+
+ mpBitmap.reset(new Bitmap(aSize, vcl::PixelFormat::N8_BPP, &aGrayPal));
+ }
+ else
+ {
+ mpBitmap.reset(new Bitmap(aSize, vcl::PixelFormat::N24_BPP));
+ }
+
+ if (mbSetLogSize)
+ {
+ unsigned long nUnit = rParam.density_unit;
+
+ if (((1 == nUnit) || (2 == nUnit)) && rParam.X_density && rParam.Y_density )
+ {
+ Fraction aFractX( 1, rParam.X_density );
+ Fraction aFractY( 1, rParam.Y_density );
+ MapMode aMapMode( nUnit == 1 ? MapUnit::MapInch : MapUnit::MapCM, Point(), aFractX, aFractY );
+ Size aPrefSize = OutputDevice::LogicToLogic(aSize, aMapMode, MapMode(MapUnit::Map100thMM));
+
+ mpBitmap->SetPrefSize(aPrefSize);
+ mpBitmap->SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ }
+ }
+
+ return true;
+}
+
+Graphic JPEGReader::CreateIntermediateGraphic(tools::Long nLines)
+{
+ Graphic aGraphic;
+ const Size aSizePixel(mpBitmap->GetSizePixel());
+
+ if (!mnLastLines)
+ {
+ mpIncompleteAlpha.reset(new Bitmap(aSizePixel, vcl::PixelFormat::N1_BPP));
+ mpIncompleteAlpha->Erase(COL_WHITE);
+ }
+
+ if (nLines && (nLines < aSizePixel.Height()))
+ {
+ const tools::Long nNewLines = nLines - mnLastLines;
+
+ if (nNewLines > 0)
+ {
+ {
+ BitmapScopedWriteAccess pAccess(*mpIncompleteAlpha);
+ pAccess->SetFillColor(COL_BLACK);
+ pAccess->FillRect(tools::Rectangle(Point(0, mnLastLines), Size(pAccess->Width(), nNewLines)));
+ }
+
+ aGraphic = BitmapEx(*mpBitmap, *mpIncompleteAlpha);
+ }
+ else
+ {
+ aGraphic = BitmapEx(*mpBitmap);
+ }
+ }
+ else
+ {
+ aGraphic = BitmapEx(*mpBitmap);
+ }
+
+ mnLastLines = nLines;
+
+ return aGraphic;
+}
+
+ReadState JPEGReader::Read( Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess )
+{
+ ReadState eReadState;
+ bool bRet = false;
+
+ // seek back to the original position
+ mrStream.Seek( mnLastPos );
+
+ // read the (partial) image
+ tools::Long nLines;
+ ReadJPEG( this, &mrStream, &nLines, nImportFlags, ppAccess );
+
+ auto bUseExistingBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap);
+ if (bUseExistingBitmap || !mpBitmap->IsEmpty())
+ {
+ if( mrStream.GetError() == ERRCODE_IO_PENDING )
+ {
+ rGraphic = CreateIntermediateGraphic(nLines);
+ }
+ else
+ {
+ if (!bUseExistingBitmap)
+ rGraphic = BitmapEx(*mpBitmap);
+ }
+
+ bRet = true;
+ }
+ else if( mrStream.GetError() == ERRCODE_IO_PENDING )
+ {
+ bRet = true;
+ }
+
+ // Set status ( Pending has priority )
+ if (mrStream.GetError() == ERRCODE_IO_PENDING)
+ {
+ eReadState = JPEGREAD_NEED_MORE;
+ mrStream.ResetError();
+ }
+ else
+ {
+ eReadState = bRet ? JPEGREAD_OK : JPEGREAD_ERROR;
+ }
+
+ return eReadState;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegReader.hxx b/vcl/source/filter/jpeg/JpegReader.hxx
new file mode 100644
index 000000000..f9a2eb292
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegReader.hxx
@@ -0,0 +1,74 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGREADER_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGREADER_HXX
+
+#include <vcl/graph.hxx>
+#include <vcl/bitmap.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <graphic/GraphicReader.hxx>
+
+enum class GraphicFilterImportFlags;
+
+enum ReadState
+{
+ JPEGREAD_OK,
+ JPEGREAD_ERROR,
+ JPEGREAD_NEED_MORE
+};
+
+struct JPEGCreateBitmapParam
+{
+ tools::ULong nWidth;
+ tools::ULong nHeight;
+ tools::ULong density_unit;
+ tools::ULong X_density;
+ tools::ULong Y_density;
+
+ bool bGray;
+};
+
+class JPEGReader : public GraphicReader
+{
+ SvStream& mrStream;
+ std::unique_ptr<Bitmap> mpBitmap;
+ std::unique_ptr<Bitmap> mpIncompleteAlpha;
+
+ tools::Long mnLastPos;
+ tools::Long mnLastLines;
+ bool mbSetLogSize;
+
+ Graphic CreateIntermediateGraphic(tools::Long nLines);
+
+public:
+ JPEGReader( SvStream& rStream, GraphicFilterImportFlags nImportFlags );
+ virtual ~JPEGReader() override;
+
+ ReadState Read(Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess);
+
+ bool CreateBitmap(JPEGCreateBitmapParam const & param);
+
+ Bitmap& GetBitmap() { return *mpBitmap; }
+};
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGREADER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegTransform.cxx b/vcl/source/filter/jpeg/JpegTransform.cxx
new file mode 100644
index 000000000..16c0c060b
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegTransform.cxx
@@ -0,0 +1,42 @@
+/* -*- 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 "jpeg.h"
+
+#include "JpegTransform.hxx"
+
+JpegTransform::JpegTransform(SvStream& rInputStream, SvStream& rOutputStream) :
+ maRotate ( 0 ),
+ mrInputStream ( rInputStream ),
+ mrOutputStream ( rOutputStream )
+{}
+
+void JpegTransform::perform()
+{
+ Transform( &mrInputStream, &mrOutputStream, maRotate );
+}
+
+void JpegTransform::setRotate(Degree10 aRotate)
+{
+ maRotate = aRotate;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegTransform.hxx b/vcl/source/filter/jpeg/JpegTransform.hxx
new file mode 100644
index 000000000..2b5c6dfd6
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegTransform.hxx
@@ -0,0 +1,40 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGTRANSFORM_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGTRANSFORM_HXX
+
+#include <tools/stream.hxx>
+
+class JpegTransform final
+{
+ Degree10 maRotate;
+ SvStream& mrInputStream;
+ SvStream& mrOutputStream;
+
+public:
+ JpegTransform(SvStream& rInputStream, SvStream& rOutputStream);
+
+ void setRotate(Degree10 aRotate);
+ void perform();
+};
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGTRANSFORM_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegWriter.cxx b/vcl/source/filter/jpeg/JpegWriter.cxx
new file mode 100644
index 000000000..026ab887b
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegWriter.cxx
@@ -0,0 +1,265 @@
+/* -*- 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 <sal/log.hxx>
+
+#include "jpeg.h"
+#include <jpeglib.h>
+#include <jerror.h>
+
+#include "JpegWriter.hxx"
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <tools/helpers.hxx>
+#include <tools/stream.hxx>
+
+#define BUFFER_SIZE 4096
+
+namespace {
+
+struct DestinationManagerStruct
+{
+ jpeg_destination_mgr pub; /* public fields */
+ SvStream* stream; /* target stream */
+ JOCTET * buffer; /* start of buffer */
+};
+
+}
+
+extern "C" {
+
+static void init_destination (j_compress_ptr cinfo)
+{
+ DestinationManagerStruct * destination = reinterpret_cast<DestinationManagerStruct *>(cinfo->dest);
+
+ /* Allocate the output buffer -- it will be released when done with image */
+ destination->buffer = static_cast<JOCTET *>(
+ (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, BUFFER_SIZE * sizeof(JOCTET)));
+
+ destination->pub.next_output_byte = destination->buffer;
+ destination->pub.free_in_buffer = BUFFER_SIZE;
+}
+
+static boolean empty_output_buffer (j_compress_ptr cinfo)
+{
+ DestinationManagerStruct * destination = reinterpret_cast<DestinationManagerStruct *>(cinfo->dest);
+
+ if (destination->stream->WriteBytes(destination->buffer, BUFFER_SIZE) != BUFFER_SIZE)
+ {
+ ERREXIT(cinfo, JERR_FILE_WRITE);
+ }
+
+ destination->pub.next_output_byte = destination->buffer;
+ destination->pub.free_in_buffer = BUFFER_SIZE;
+
+ return TRUE;
+}
+
+static void term_destination (j_compress_ptr cinfo)
+{
+ DestinationManagerStruct * destination = reinterpret_cast<DestinationManagerStruct *>(cinfo->dest);
+ size_t datacount = BUFFER_SIZE - destination->pub.free_in_buffer;
+
+ /* Write any data remaining in the buffer */
+ if (datacount > 0)
+ {
+ if (destination->stream->WriteBytes(destination->buffer, datacount) != datacount)
+ {
+ ERREXIT(cinfo, JERR_FILE_WRITE);
+ }
+ }
+}
+
+}
+
+void jpeg_svstream_dest (j_compress_ptr cinfo, void* output)
+{
+ SvStream* stream = static_cast<SvStream*>(output);
+ DestinationManagerStruct * destination;
+
+ /* The destination object is made permanent so that multiple JPEG images
+ * can be written to the same file without re-executing jpeg_svstream_dest.
+ * This makes it dangerous to use this manager and a different destination
+ * manager serially with the same JPEG object, because their private object
+ * sizes may be different. Caveat programmer.
+ */
+ if (cinfo->dest == nullptr)
+ { /* first time for this JPEG object? */
+ cinfo->dest = static_cast<jpeg_destination_mgr*>(
+ (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, sizeof(DestinationManagerStruct)));
+ }
+
+ destination = reinterpret_cast<DestinationManagerStruct *>(cinfo->dest);
+ destination->pub.init_destination = init_destination;
+ destination->pub.empty_output_buffer = empty_output_buffer;
+ destination->pub.term_destination = term_destination;
+ destination->stream = stream;
+}
+
+JPEGWriter::JPEGWriter( SvStream& rStream, const css::uno::Sequence< css::beans::PropertyValue >* pFilterData, bool* pExportWasGrey ) :
+ mrStream ( rStream ),
+ mpBuffer ( nullptr ),
+ mbNative ( false ),
+ mpExpWasGrey ( pExportWasGrey )
+{
+ FilterConfigItem aConfigItem( pFilterData );
+ mbGreys = aConfigItem.ReadInt32( "ColorMode", 0 ) != 0;
+ mnQuality = aConfigItem.ReadInt32( "Quality", 75 );
+ maChromaSubsampling = aConfigItem.ReadInt32( "ChromaSubsamplingMode", 0 );
+
+ if ( pFilterData )
+ {
+ for( const auto& rValue : *pFilterData )
+ {
+ if ( rValue.Name == "StatusIndicator" )
+ {
+ rValue.Value >>= mxStatusIndicator;
+ }
+ }
+ }
+}
+
+void* JPEGWriter::GetScanline( tools::Long nY )
+{
+ void* pScanline = nullptr;
+
+ if( mpReadAccess )
+ {
+ if( mbNative )
+ {
+ pScanline = mpReadAccess->GetScanline( nY );
+ }
+ else if( mpBuffer )
+ {
+ BitmapColor aColor;
+ tools::Long nWidth = mpReadAccess->Width();
+ sal_uInt8* pTmp = mpBuffer;
+
+ if( mpReadAccess->HasPalette() )
+ {
+ Scanline pScanlineRead = mpReadAccess->GetScanline( nY );
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ aColor = mpReadAccess->GetPaletteColor( mpReadAccess->GetIndexFromData( pScanlineRead, nX ) );
+ *pTmp++ = aColor.GetRed();
+ if ( !mbGreys )
+ {
+ *pTmp++ = aColor.GetGreen();
+ *pTmp++ = aColor.GetBlue();
+ }
+ }
+ }
+ else
+ {
+ Scanline pScanlineRead = mpReadAccess->GetScanline( nY );
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ aColor = mpReadAccess->GetPixelFromData( pScanlineRead, nX );
+ *pTmp++ = aColor.GetRed();
+ if ( !mbGreys )
+ {
+ *pTmp++ = aColor.GetGreen();
+ *pTmp++ = aColor.GetBlue();
+ }
+ }
+ }
+
+ pScanline = mpBuffer;
+ }
+ }
+
+ return pScanline;
+}
+
+bool JPEGWriter::Write( const Graphic& rGraphic )
+{
+ bool bRet = false;
+
+ if ( mxStatusIndicator.is() )
+ {
+ mxStatusIndicator->start( OUString(), 100 );
+ }
+
+ // This slightly weird logic is here to match the behaviour in ImpGraphic::ImplGetBitmap
+ // and is necessary to match pre-existing behaviour. We should probably pass down the expected
+ // background color for alpha from the higher layers.
+ Bitmap aGraphicBmp;
+ if (rGraphic.GetType() == GraphicType::Bitmap)
+ aGraphicBmp = rGraphic.GetBitmapEx().GetBitmap(COL_WHITE);
+ else
+ aGraphicBmp = rGraphic.GetBitmapEx().GetBitmap();
+
+ if ( mbGreys )
+ {
+ if ( !aGraphicBmp.Convert( BmpConversion::N8BitGreys ) )
+ aGraphicBmp = rGraphic.GetBitmapEx().GetBitmap();
+ }
+
+ mpReadAccess = Bitmap::ScopedReadAccess(aGraphicBmp);
+ if( mpReadAccess )
+ {
+ if ( !mbGreys ) // bitmap was not explicitly converted into greyscale,
+ { // check if source is greyscale only
+ bool bIsGrey = true;
+
+ tools::Long nWidth = mpReadAccess->Width();
+ for ( tools::Long nY = 0; bIsGrey && ( nY < mpReadAccess->Height() ); nY++ )
+ {
+ BitmapColor aColor;
+ Scanline pScanlineRead = mpReadAccess->GetScanline( nY );
+ for( tools::Long nX = 0; bIsGrey && ( nX < nWidth ); nX++ )
+ {
+ aColor = mpReadAccess->HasPalette() ? mpReadAccess->GetPaletteColor( mpReadAccess->GetIndexFromData( pScanlineRead, nX ) )
+ : mpReadAccess->GetPixelFromData( pScanlineRead, nX );
+ bIsGrey = ( aColor.GetRed() == aColor.GetGreen() ) && ( aColor.GetRed() == aColor.GetBlue() );
+ }
+ }
+ if ( bIsGrey )
+ mbGreys = true;
+ }
+ if( mpExpWasGrey )
+ *mpExpWasGrey = mbGreys;
+
+ if ( mbGreys )
+ mbNative = ( mpReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal && aGraphicBmp.HasGreyPalette8Bit());
+ else
+ mbNative = ( mpReadAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb );
+
+ if( !mbNative )
+ mpBuffer = new sal_uInt8[ AlignedWidth4Bytes( mbGreys ? mpReadAccess->Width() * 8L : mpReadAccess->Width() * 24L ) ];
+
+ SAL_INFO("vcl", "\nJPEG Export - DPI X: " << rGraphic.GetPPI().getX() << "\nJPEG Export - DPI Y: " << rGraphic.GetPPI().getY());
+
+ bRet = WriteJPEG( this, &mrStream, mpReadAccess->Width(),
+ mpReadAccess->Height(), rGraphic.GetPPI(), mbGreys,
+ mnQuality, maChromaSubsampling, mxStatusIndicator );
+
+ delete[] mpBuffer;
+ mpBuffer = nullptr;
+
+ mpReadAccess.reset();
+ }
+ if ( mxStatusIndicator.is() )
+ mxStatusIndicator->end();
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegWriter.hxx b/vcl/source/filter/jpeg/JpegWriter.hxx
new file mode 100644
index 000000000..76666cfa0
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegWriter.hxx
@@ -0,0 +1,57 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGWRITER_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGWRITER_HXX
+
+#include <vcl/bitmap.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/graph.hxx>
+
+#include <com/sun/star/uno/Sequence.h>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+
+class JPEGWriter final
+{
+ SvStream& mrStream;
+ Bitmap::ScopedReadAccess mpReadAccess;
+ sal_uInt8* mpBuffer;
+ bool mbNative;
+ bool mbGreys;
+ sal_Int32 mnQuality;
+ sal_Int32 maChromaSubsampling;
+
+ bool* mpExpWasGrey;
+
+ css::uno::Reference< css::task::XStatusIndicator > mxStatusIndicator;
+
+public:
+ JPEGWriter( SvStream& rStream,
+ const css::uno::Sequence< css::beans::PropertyValue >* pFilterData,
+ bool* pExportWasGrey );
+
+ void* GetScanline( tools::Long nY );
+ bool Write( const Graphic& rGraphic );
+
+};
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGWRITER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/jinclude.h b/vcl/source/filter/jpeg/jinclude.h
new file mode 100644
index 000000000..b863b11c4
--- /dev/null
+++ b/vcl/source/filter/jpeg/jinclude.h
@@ -0,0 +1,79 @@
+/*
+ * jinclude.h
+ *
+ * Copyright (C) 1991-1994, Thomas G. Lane.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file exists to provide a single place to fix any problems with
+ * including the wrong system include files. (Common problems are taken
+ * care of by the standard jconfig symbols, but on really weird systems
+ * you may have to edit this file.)
+ *
+ * NOTE: this file is NOT intended to be included by applications using the
+ * JPEG library. Most applications need only include jpeglib.h.
+ */
+
+/* Include auto-config file to find out which system include files we need. */
+
+#include <jconfig.h> /* auto configuration options */
+#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */
+
+/*
+ * We need the NULL macro and size_t typedef.
+ * On an ANSI-conforming system it is sufficient to include <stddef.h>.
+ * Otherwise, we get them from <stdlib.h> or <stdio.h>; we may have to
+ * pull in <sys/types.h> as well.
+ * Note that the core JPEG library does not require <stdio.h>;
+ * only the default error handler and data source/destination modules do.
+ * But we must pull it in because of the references to FILE in jpeglib.h.
+ * You can remove those references if you want to compile without <stdio.h>.
+ */
+
+#ifdef HAVE_STDDEF_H
+#include <stddef.h>
+#endif
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+
+#ifdef NEED_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#include <stdio.h>
+
+/*
+ * We need memory copying and zeroing functions, plus strncpy().
+ * ANSI and System V implementations declare these in <string.h>.
+ * BSD doesn't have the mem() functions, but it does have bcopy()/bzero().
+ * Some systems may declare memset and memcpy in <memory.h>.
+ *
+ * NOTE: we assume the size parameters to these functions are of type size_t.
+ * Change the casts in these macros if not!
+ */
+
+#ifdef NEED_BSD_STRINGS
+
+#include <strings.h>
+#define MEMZERO(target, size) bzero((void*)(target), (size_t)(size))
+#define MEMCOPY(dest, src, size) bcopy((const void*)(src), (void*)(dest), (size_t)(size))
+
+#else /* not BSD, assume ANSI/SysV string lib */
+
+#include <string.h>
+#define MEMZERO(target, size) memset((void*)(target), 0, (size_t)(size))
+#define MEMCOPY(dest, src, size) memcpy((void*)(dest), (const void*)(src), (size_t)(size))
+
+#endif
+
+/*
+ * In ANSI C, and indeed any rational implementation, size_t is also the
+ * type returned by sizeof(). However, it seems there are some irrational
+ * implementations out there, in which sizeof() returns an int even though
+ * size_t is defined as long or unsigned long. To ensure consistent results
+ * we always use this SIZEOF() macro in place of using sizeof() directly.
+ */
+
+#define SIZEOF(object) ((size_t)sizeof(object))
diff --git a/vcl/source/filter/jpeg/jpeg.cxx b/vcl/source/filter/jpeg/jpeg.cxx
new file mode 100644
index 000000000..e7158b858
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpeg.cxx
@@ -0,0 +1,62 @@
+/* -*- 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 "JpegReader.hxx"
+#include "JpegWriter.hxx"
+#include "jpeg.hxx"
+
+#include <vcl/graphicfilter.hxx>
+
+VCL_DLLPUBLIC bool ImportJPEG( SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess )
+{
+ bool bReturn = true;
+
+ std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext();
+ rGraphic.SetReaderContext(nullptr);
+ JPEGReader* pJPEGReader = dynamic_cast<JPEGReader*>( pContext.get() );
+ if (!pJPEGReader)
+ {
+ pContext = std::make_shared<JPEGReader>( rInputStream, nImportFlags );
+ pJPEGReader = static_cast<JPEGReader*>( pContext.get() );
+ }
+
+ ReadState eReadState = pJPEGReader->Read( rGraphic, nImportFlags, ppAccess );
+
+ if( eReadState == JPEGREAD_ERROR )
+ {
+ bReturn = false;
+ }
+ else if( eReadState == JPEGREAD_NEED_MORE )
+ {
+ rGraphic.SetReaderContext( pContext );
+ }
+
+ return bReturn;
+}
+
+bool ExportJPEG(SvStream& rOutputStream, const Graphic& rGraphic,
+ const css::uno::Sequence<css::beans::PropertyValue>* pFilterData,
+ bool* pExportWasGrey)
+{
+ JPEGWriter aJPEGWriter( rOutputStream, pFilterData, pExportWasGrey );
+ bool bReturn = aJPEGWriter.Write( rGraphic );
+ return bReturn;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/jpeg.h b/vcl/source/filter/jpeg/jpeg.h
new file mode 100644
index 000000000..a7ddcffa6
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpeg.h
@@ -0,0 +1,67 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_H
+#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_H
+
+#include <sal/config.h>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <basegfx/vector/b2dsize.hxx>
+#include <bitmap/BitmapWriteAccess.hxx>
+
+#include <jpeglib.h>
+
+namespace com::sun::star::task {
+ class XStatusIndicator;
+}
+class JPEGReader;
+class JPEGWriter;
+class Size;
+class SvStream;
+enum class GraphicFilterImportFlags;
+
+void jpeg_svstream_src (j_decompress_ptr cinfo, void* infile);
+
+void jpeg_svstream_dest (j_compress_ptr cinfo, void* outfile);
+
+bool WriteJPEG( JPEGWriter* pJPEGWriter, void* pOutputStream,
+ tools::Long nWidth, tools::Long nHeight, basegfx::B2DSize const & aPPI, bool bGreyScale,
+ tools::Long nQualityPercent, tools::Long aChromaSubsampling,
+ css::uno::Reference<css::task::XStatusIndicator> const & status);
+
+void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream, tools::Long* pLines,
+ GraphicFilterImportFlags nImportFlags,
+ BitmapScopedWriteAccess* ppAccess );
+
+void Transform(void* pInputStream, void* pOutputStream, Degree10 nAngle);
+
+/* Expanded data source object for stdio input */
+
+struct SourceManagerStruct {
+ jpeg_source_mgr pub; /* public fields */
+ SvStream* stream; /* source stream */
+ JOCTET* buffer; /* start of buffer */
+ boolean start_of_file; /* have we gotten any data yet? */
+ int no_data_available_failures;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/jpeg.hxx b/vcl/source/filter/jpeg/jpeg.hxx
new file mode 100644
index 000000000..96c9280c2
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpeg.hxx
@@ -0,0 +1,39 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_HXX
+
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+#include <com/sun/star/uno/Sequence.h>
+
+VCL_DLLPUBLIC bool ImportJPEG( SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess );
+
+bool ExportJPEG(SvStream& rOutputStream,
+ const Graphic& rGraphic,
+ const css::uno::Sequence< css::beans::PropertyValue >* pFilterData,
+ bool* pExportWasGrey);
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/jpegc.cxx b/vcl/source/filter/jpeg/jpegc.cxx
new file mode 100644
index 000000000..8807927a8
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpegc.cxx
@@ -0,0 +1,520 @@
+/* -*- 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 <sal/log.hxx>
+#include <o3tl/float_int_conversion.hxx>
+#include <o3tl/safeint.hxx>
+
+#include <stdio.h>
+#include <setjmp.h>
+#include <jpeglib.h>
+
+#include <com/sun/star/task/XStatusIndicator.hpp>
+
+extern "C" {
+#include "transupp.h"
+}
+
+#include "jpeg.h"
+#include "JpegReader.hxx"
+#include "JpegWriter.hxx"
+#include <memory>
+#include <unotools/configmgr.hxx>
+#include <vcl/graphicfilter.hxx>
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning (disable: 4324) /* disable to __declspec(align()) aligned warning */
+#endif
+
+namespace {
+
+struct ErrorManagerStruct
+{
+ jpeg_error_mgr pub;
+ jmp_buf setjmp_buffer;
+};
+
+}
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+extern "C" {
+
+static void errorExit (j_common_ptr cinfo)
+{
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message) (cinfo, buffer);
+ SAL_WARN("vcl.filter", "fatal failure reading JPEG: " << buffer);
+ ErrorManagerStruct * error = reinterpret_cast<ErrorManagerStruct *>(cinfo->err);
+ longjmp(error->setjmp_buffer, 1);
+}
+
+static void outputMessage (j_common_ptr cinfo)
+{
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message) (cinfo, buffer);
+ SAL_WARN("vcl.filter", "failure reading JPEG: " << buffer);
+}
+
+}
+
+static int GetWarningLimit()
+{
+ return utl::ConfigManager::IsFuzzing() ? 5 : 1000;
+}
+
+extern "C" {
+
+static void emitMessage (j_common_ptr cinfo, int msg_level)
+{
+ if (msg_level < 0)
+ {
+ // ofz#3002 try to retain some degree of recoverability up to some
+ // reasonable limit (initially using ImageMagick's current limit of
+ // 1000), then bail.
+ // https://libjpeg-turbo.org/pmwiki/uploads/About/TwoIssueswiththeJPEGStandard.pdf
+ if (++cinfo->err->num_warnings > GetWarningLimit())
+ cinfo->err->error_exit(cinfo);
+ else
+ cinfo->err->output_message(cinfo);
+ }
+ else if (cinfo->err->trace_level >= msg_level)
+ cinfo->err->output_message(cinfo);
+}
+
+}
+
+namespace {
+
+class JpegDecompressOwner
+{
+public:
+ void set(jpeg_decompress_struct *cinfo)
+ {
+ m_cinfo = cinfo;
+ }
+ ~JpegDecompressOwner()
+ {
+ if (m_cinfo != nullptr)
+ {
+ jpeg_destroy_decompress(m_cinfo);
+ }
+ }
+private:
+ jpeg_decompress_struct *m_cinfo = nullptr;
+};
+
+class JpegCompressOwner
+{
+public:
+ void set(jpeg_compress_struct *cinfo)
+ {
+ m_cinfo = cinfo;
+ }
+ ~JpegCompressOwner()
+ {
+ if (m_cinfo != nullptr)
+ {
+ jpeg_destroy_compress(m_cinfo);
+ }
+ }
+private:
+ jpeg_compress_struct *m_cinfo = nullptr;
+};
+
+struct JpegStuff
+{
+ jpeg_decompress_struct cinfo;
+ ErrorManagerStruct jerr;
+ JpegDecompressOwner aOwner;
+ std::unique_ptr<BitmapScopedWriteAccess> pScopedAccess;
+ std::vector<sal_uInt8> pScanLineBuffer;
+ std::vector<sal_uInt8> pCYMKBuffer;
+};
+
+}
+
+static void ReadJPEG(JpegStuff& rContext, JPEGReader* pJPEGReader, void* pInputStream, tools::Long* pLines,
+ GraphicFilterImportFlags nImportFlags,
+ BitmapScopedWriteAccess* ppAccess)
+{
+ if (setjmp(rContext.jerr.setjmp_buffer))
+ {
+ return;
+ }
+
+ rContext.cinfo.err = jpeg_std_error(&rContext.jerr.pub);
+ rContext.jerr.pub.error_exit = errorExit;
+ rContext.jerr.pub.output_message = outputMessage;
+ rContext.jerr.pub.emit_message = emitMessage;
+
+ jpeg_create_decompress(&rContext.cinfo);
+ rContext.aOwner.set(&rContext.cinfo);
+ jpeg_svstream_src(&rContext.cinfo, pInputStream);
+ SourceManagerStruct *source = reinterpret_cast<SourceManagerStruct*>(rContext.cinfo.src);
+ jpeg_read_header(&rContext.cinfo, TRUE);
+
+ rContext.cinfo.scale_num = 1;
+ rContext.cinfo.scale_denom = 1;
+ rContext.cinfo.output_gamma = 1.0;
+ rContext.cinfo.raw_data_out = FALSE;
+ rContext.cinfo.quantize_colors = FALSE;
+
+ jpeg_calc_output_dimensions(&rContext.cinfo);
+
+ tools::Long nWidth = rContext.cinfo.output_width;
+ tools::Long nHeight = rContext.cinfo.output_height;
+
+ tools::Long nResult = 0;
+ if (utl::ConfigManager::IsFuzzing() && (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000))
+ return;
+
+ bool bGray = (rContext.cinfo.output_components == 1);
+
+ JPEGCreateBitmapParam aCreateBitmapParam;
+
+ aCreateBitmapParam.nWidth = nWidth;
+ aCreateBitmapParam.nHeight = nHeight;
+
+ aCreateBitmapParam.density_unit = rContext.cinfo.density_unit;
+ aCreateBitmapParam.X_density = rContext.cinfo.X_density;
+ aCreateBitmapParam.Y_density = rContext.cinfo.Y_density;
+ aCreateBitmapParam.bGray = bGray;
+
+ const auto bOnlyCreateBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::OnlyCreateBitmap);
+ const auto bUseExistingBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap);
+ bool bBitmapCreated = bUseExistingBitmap;
+ if (!bBitmapCreated)
+ bBitmapCreated = pJPEGReader->CreateBitmap(aCreateBitmapParam);
+
+ if (bBitmapCreated && !bOnlyCreateBitmap)
+ {
+ if (nImportFlags & GraphicFilterImportFlags::UseExistingBitmap)
+ // ppAccess must be set if this flag is used.
+ assert(ppAccess);
+ else
+ rContext.pScopedAccess.reset(new BitmapScopedWriteAccess(pJPEGReader->GetBitmap()));
+
+ BitmapScopedWriteAccess& pAccess = bUseExistingBitmap ? *ppAccess : *rContext.pScopedAccess;
+
+ if (pAccess)
+ {
+ int nPixelSize = 3;
+ J_COLOR_SPACE best_out_color_space = JCS_RGB;
+ ScanlineFormat eScanlineFormat = ScanlineFormat::N24BitTcRgb;
+ ScanlineFormat eFinalFormat = pAccess->GetScanlineFormat();
+
+ if (bGray)
+ {
+ best_out_color_space = JCS_GRAYSCALE;
+ eScanlineFormat = ScanlineFormat::N8BitPal;
+ nPixelSize = 1;
+ }
+#if defined(JCS_EXTENSIONS)
+ else if (eFinalFormat == ScanlineFormat::N24BitTcBgr)
+ {
+ best_out_color_space = JCS_EXT_BGR;
+ eScanlineFormat = eFinalFormat;
+ nPixelSize = 3;
+ }
+ else if (eFinalFormat == ScanlineFormat::N32BitTcBgra)
+ {
+ best_out_color_space = JCS_EXT_BGRA;
+ eScanlineFormat = eFinalFormat;
+ nPixelSize = 4;
+ }
+ else if (eFinalFormat == ScanlineFormat::N32BitTcRgba)
+ {
+ best_out_color_space = JCS_EXT_RGBA;
+ eScanlineFormat = eFinalFormat;
+ nPixelSize = 4;
+ }
+ else if (eFinalFormat == ScanlineFormat::N32BitTcArgb)
+ {
+ best_out_color_space = JCS_EXT_ARGB;
+ eScanlineFormat = eFinalFormat;
+ nPixelSize = 4;
+ }
+#endif
+ if (rContext.cinfo.jpeg_color_space == JCS_YCCK)
+ rContext.cinfo.out_color_space = JCS_CMYK;
+
+ if (rContext.cinfo.out_color_space != JCS_CMYK)
+ rContext.cinfo.out_color_space = best_out_color_space;
+
+ jpeg_start_decompress(&rContext.cinfo);
+
+ JSAMPLE* aRangeLimit = rContext.cinfo.sample_range_limit;
+
+ rContext.pScanLineBuffer.resize(nWidth * nPixelSize);
+
+ if (rContext.cinfo.out_color_space == JCS_CMYK)
+ {
+ rContext.pCYMKBuffer.resize(nWidth * 4);
+ }
+
+ // tdf#138950 allow up to one short read (no_data_available_failures <= 1) to not trigger cancelling import
+ for (*pLines = 0; *pLines < nHeight && source->no_data_available_failures <= 1; (*pLines)++)
+ {
+ size_t yIndex = *pLines;
+
+ sal_uInt8* p = (rContext.cinfo.out_color_space == JCS_CMYK) ? rContext.pCYMKBuffer.data() : rContext.pScanLineBuffer.data();
+ jpeg_read_scanlines(&rContext.cinfo, reinterpret_cast<JSAMPARRAY>(&p), 1);
+
+ if (rContext.cinfo.out_color_space == JCS_CMYK)
+ {
+ // convert CMYK to RGB
+ Scanline pScanline = pAccess->GetScanline(yIndex);
+ for (tools::Long cmyk = 0, x = 0; cmyk < nWidth * 4; cmyk += 4, ++x)
+ {
+ int color_C = 255 - rContext.pCYMKBuffer[cmyk + 0];
+ int color_M = 255 - rContext.pCYMKBuffer[cmyk + 1];
+ int color_Y = 255 - rContext.pCYMKBuffer[cmyk + 2];
+ int color_K = 255 - rContext.pCYMKBuffer[cmyk + 3];
+
+ sal_uInt8 cRed = aRangeLimit[255L - (color_C + color_K)];
+ sal_uInt8 cGreen = aRangeLimit[255L - (color_M + color_K)];
+ sal_uInt8 cBlue = aRangeLimit[255L - (color_Y + color_K)];
+
+ pAccess->SetPixelOnData(pScanline, x, BitmapColor(cRed, cGreen, cBlue));
+ }
+ }
+ else
+ {
+ pAccess->CopyScanline(yIndex, rContext.pScanLineBuffer.data(), eScanlineFormat, rContext.pScanLineBuffer.size());
+ }
+
+ /* PENDING ??? */
+ if (rContext.cinfo.err->msg_code == 113)
+ break;
+ }
+
+ rContext.pScanLineBuffer.clear();
+ rContext.pCYMKBuffer.clear();
+ }
+ rContext.pScopedAccess.reset();
+ }
+
+ if (bBitmapCreated && !bOnlyCreateBitmap)
+ {
+ jpeg_finish_decompress(&rContext.cinfo);
+ }
+ else
+ {
+ jpeg_abort_decompress(&rContext.cinfo);
+ }
+}
+
+void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream, tools::Long* pLines,
+ GraphicFilterImportFlags nImportFlags,
+ BitmapScopedWriteAccess* ppAccess )
+{
+ JpegStuff aContext;
+ ReadJPEG(aContext, pJPEGReader, pInputStream, pLines, nImportFlags, ppAccess);
+}
+
+bool WriteJPEG( JPEGWriter* pJPEGWriter, void* pOutputStream,
+ tools::Long nWidth, tools::Long nHeight, basegfx::B2DSize const & rPPI, bool bGreys,
+ tools::Long nQualityPercent, tools::Long aChromaSubsampling,
+ css::uno::Reference<css::task::XStatusIndicator> const & status )
+{
+ jpeg_compress_struct cinfo;
+ ErrorManagerStruct jerr;
+ void* pScanline;
+ tools::Long nY;
+
+ JpegCompressOwner aOwner;
+
+ if ( setjmp( jerr.setjmp_buffer ) )
+ {
+ return false;
+ }
+
+ cinfo.err = jpeg_std_error( &jerr.pub );
+ jerr.pub.error_exit = errorExit;
+ jerr.pub.output_message = outputMessage;
+
+ jpeg_create_compress( &cinfo );
+ aOwner.set(&cinfo);
+ jpeg_svstream_dest( &cinfo, pOutputStream );
+
+ cinfo.image_width = static_cast<JDIMENSION>(nWidth);
+ cinfo.image_height = static_cast<JDIMENSION>(nHeight);
+ if ( bGreys )
+ {
+ cinfo.input_components = 1;
+ cinfo.in_color_space = JCS_GRAYSCALE;
+ }
+ else
+ {
+ cinfo.input_components = 3;
+ cinfo.in_color_space = JCS_RGB;
+ }
+
+ jpeg_set_defaults( &cinfo );
+ jpeg_set_quality( &cinfo, static_cast<int>(nQualityPercent), FALSE );
+
+ if (o3tl::convertsToAtMost(rPPI.getX(), 65535) && o3tl::convertsToAtMost(rPPI.getY(), 65535))
+ {
+ cinfo.density_unit = 1;
+ cinfo.X_density = rPPI.getX();
+ cinfo.Y_density = rPPI.getY();
+ }
+ else
+ {
+ SAL_WARN("vcl.filter", "ignoring too large PPI " << rPPI);
+ }
+
+ if ( ( nWidth > 128 ) || ( nHeight > 128 ) )
+ jpeg_simple_progression( &cinfo );
+
+ if (aChromaSubsampling == 1) // YUV 4:4:4
+ {
+ cinfo.comp_info[0].h_samp_factor = 1;
+ cinfo.comp_info[0].v_samp_factor = 1;
+ }
+ else if (aChromaSubsampling == 2) // YUV 4:2:2
+ {
+ cinfo.comp_info[0].h_samp_factor = 2;
+ cinfo.comp_info[0].v_samp_factor = 1;
+ }
+ else if (aChromaSubsampling == 3) // YUV 4:2:0
+ {
+ cinfo.comp_info[0].h_samp_factor = 2;
+ cinfo.comp_info[0].v_samp_factor = 2;
+ }
+
+ jpeg_start_compress( &cinfo, TRUE );
+
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ pScanline = pJPEGWriter->GetScanline( nY );
+
+ if( pScanline )
+ {
+ jpeg_write_scanlines( &cinfo, reinterpret_cast<JSAMPARRAY>(&pScanline), 1 );
+ }
+
+ if( status.is() )
+ {
+ status->setValue( nY * 100L / nHeight );
+ }
+ }
+
+ jpeg_finish_compress(&cinfo);
+
+ return true;
+}
+
+void Transform(void* pInputStream, void* pOutputStream, Degree10 nAngle)
+{
+ jpeg_transform_info aTransformOption;
+ JCOPY_OPTION aCopyOption = JCOPYOPT_ALL;
+
+ jpeg_decompress_struct aSourceInfo;
+ jpeg_compress_struct aDestinationInfo;
+ ErrorManagerStruct aSourceError;
+ ErrorManagerStruct aDestinationError;
+
+ jvirt_barray_ptr* aSourceCoefArrays = nullptr;
+ jvirt_barray_ptr* aDestinationCoefArrays = nullptr;
+
+ aTransformOption.force_grayscale = FALSE;
+ aTransformOption.trim = FALSE;
+ aTransformOption.perfect = FALSE;
+ aTransformOption.crop = FALSE;
+
+ // Angle to transform option
+ // 90 Clockwise = 270 Counterclockwise
+ switch (nAngle.get())
+ {
+ case 2700:
+ aTransformOption.transform = JXFORM_ROT_90;
+ break;
+ case 1800:
+ aTransformOption.transform = JXFORM_ROT_180;
+ break;
+ case 900:
+ aTransformOption.transform = JXFORM_ROT_270;
+ break;
+ default:
+ aTransformOption.transform = JXFORM_NONE;
+ }
+
+ // Decompression
+ aSourceInfo.err = jpeg_std_error(&aSourceError.pub);
+ aSourceInfo.err->error_exit = errorExit;
+ aSourceInfo.err->output_message = outputMessage;
+
+ // Compression
+ aDestinationInfo.err = jpeg_std_error(&aDestinationError.pub);
+ aDestinationInfo.err->error_exit = errorExit;
+ aDestinationInfo.err->output_message = outputMessage;
+
+ aDestinationInfo.optimize_coding = TRUE;
+
+ JpegDecompressOwner aDecompressOwner;
+ JpegCompressOwner aCompressOwner;
+
+ if (setjmp(aSourceError.setjmp_buffer))
+ {
+ jpeg_destroy_decompress(&aSourceInfo);
+ jpeg_destroy_compress(&aDestinationInfo);
+ return;
+ }
+ if (setjmp(aDestinationError.setjmp_buffer))
+ {
+ jpeg_destroy_decompress(&aSourceInfo);
+ jpeg_destroy_compress(&aDestinationInfo);
+ return;
+ }
+
+ jpeg_create_decompress(&aSourceInfo);
+ aDecompressOwner.set(&aSourceInfo);
+ jpeg_create_compress(&aDestinationInfo);
+ aCompressOwner.set(&aDestinationInfo);
+
+ jpeg_svstream_src (&aSourceInfo, pInputStream);
+
+ jcopy_markers_setup(&aSourceInfo, aCopyOption);
+ jpeg_read_header(&aSourceInfo, TRUE);
+ jtransform_request_workspace(&aSourceInfo, &aTransformOption);
+
+ aSourceCoefArrays = jpeg_read_coefficients(&aSourceInfo);
+ jpeg_copy_critical_parameters(&aSourceInfo, &aDestinationInfo);
+
+ aDestinationCoefArrays = jtransform_adjust_parameters(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
+ jpeg_svstream_dest (&aDestinationInfo, pOutputStream);
+
+ // Compute optimal Huffman coding tables instead of precomputed tables
+ aDestinationInfo.optimize_coding = TRUE;
+ jpeg_write_coefficients(&aDestinationInfo, aDestinationCoefArrays);
+ jcopy_markers_execute(&aSourceInfo, &aDestinationInfo, aCopyOption);
+ jtransform_execute_transformation(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
+
+ jpeg_finish_compress(&aDestinationInfo);
+
+ jpeg_finish_decompress(&aSourceInfo);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/jpegcomp.h b/vcl/source/filter/jpeg/jpegcomp.h
new file mode 100644
index 000000000..9b3e36775
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpegcomp.h
@@ -0,0 +1,18 @@
+/*
+ * jpegcomp.h
+ *
+ * Copyright (C) 2010, D. R. Commander
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * JPEG compatibility macros
+ * These declarations are considered internal to the JPEG library; most
+ * applications using the library shouldn't need to include this file.
+ */
+
+#if JPEG_LIB_VERSION >= 70
+#define min_DCT_h_scaled_size_ min_DCT_h_scaled_size
+#define min_DCT_v_scaled_size_ min_DCT_v_scaled_size
+#else
+#define min_DCT_h_scaled_size_ min_DCT_scaled_size
+#define min_DCT_v_scaled_size_ min_DCT_scaled_size
+#endif
diff --git a/vcl/source/filter/jpeg/transupp.c b/vcl/source/filter/jpeg/transupp.c
new file mode 100644
index 000000000..d26cb9510
--- /dev/null
+++ b/vcl/source/filter/jpeg/transupp.c
@@ -0,0 +1,1570 @@
+/*
+ * transupp.c
+ *
+ * This file was part of the Independent JPEG Group's software:
+ * Copyright (C) 1997-2011, Thomas G. Lane, Guido Vollbeding.
+ * Modifications:
+ * Copyright (C) 2010, D. R. Commander.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file contains image transformation routines and other utility code
+ * used by the jpegtran sample application. These are NOT part of the core
+ * JPEG library. But we keep these routines separate from jpegtran.c to
+ * ease the task of maintaining jpegtran-like programs that have other user
+ * interfaces.
+ */
+
+#include <sal/config.h>
+
+#include "jinclude.h"
+#include <jerror.h>
+#include <jpeglib.h>
+#include "transupp.h" /* My own external interface */
+#include "jpegcomp.h"
+
+/* Definition of jdiv_round_up is copied here from jutils.c in jpeg-8c.tar.gz,
+ just as the rest of this file appears to be copied here from transupp.c in
+ jpeg-8c.tar.gz: */
+static long
+jdiv_round_up (long a, long b)
+/* Compute a/b rounded up to next integer, ie, ceil(a/b) */
+/* Assumes a >= 0, b > 0 */
+{
+ return (a + b - 1) / b;
+}
+
+#if JPEG_LIB_VERSION >= 70
+#define dstinfo_min_DCT_h_scaled_size dstinfo->min_DCT_h_scaled_size
+#define dstinfo_min_DCT_v_scaled_size dstinfo->min_DCT_v_scaled_size
+#else
+#define dstinfo_min_DCT_h_scaled_size DCTSIZE
+#define dstinfo_min_DCT_v_scaled_size DCTSIZE
+#endif
+
+
+#if TRANSFORMS_SUPPORTED
+
+/*
+ * Lossless image transformation routines. These routines work on DCT
+ * coefficient arrays and thus do not require any lossy decompression
+ * or recompression of the image.
+ * Thanks to Guido Vollbeding for the initial design and code of this feature,
+ * and to Ben Jackson for introducing the cropping feature.
+ *
+ * Horizontal flipping is done in-place, using a single top-to-bottom
+ * pass through the virtual source array. It will thus be much the
+ * fastest option for images larger than main memory.
+ *
+ * The other routines require a set of destination virtual arrays, so they
+ * need twice as much memory as jpegtran normally does. The destination
+ * arrays are always written in normal scan order (top to bottom) because
+ * the virtual array manager expects this. The source arrays will be scanned
+ * in the corresponding order, which means multiple passes through the source
+ * arrays for most of the transforms. That could result in much thrashing
+ * if the image is larger than main memory.
+ *
+ * If cropping or trimming is involved, the destination arrays may be smaller
+ * than the source arrays. Note it is not possible to do horizontal flip
+ * in-place when a nonzero Y crop offset is specified, since we'd have to move
+ * data from one block row to another but the virtual array manager doesn't
+ * guarantee we can touch more than one row at a time. So in that case,
+ * we have to use a separate destination array.
+ *
+ * Some notes about the operating environment of the individual transform
+ * routines:
+ * 1. Both the source and destination virtual arrays are allocated from the
+ * source JPEG object, and therefore should be manipulated by calling the
+ * source's memory manager.
+ * 2. The destination's component count should be used. It may be smaller
+ * than the source's when forcing to grayscale.
+ * 3. Likewise the destination's sampling factors should be used. When
+ * forcing to grayscale the destination's sampling factors will be all 1,
+ * and we may as well take that as the effective iMCU size.
+ * 4. When "trim" is in effect, the destination's dimensions will be the
+ * trimmed values but the source's will be untrimmed.
+ * 5. When "crop" is in effect, the destination's dimensions will be the
+ * cropped values but the source's will be uncropped. Each transform
+ * routine is responsible for picking up source data starting at the
+ * correct X and Y offset for the crop region. (The X and Y offsets
+ * passed to the transform routines are measured in iMCU blocks of the
+ * destination.)
+ * 6. All the routines assume that the source and destination buffers are
+ * padded out to a full iMCU boundary. This is true, although for the
+ * source buffer it is an undocumented property of jdcoefct.c.
+ */
+
+static void lcl_jcopy_block_row (JBLOCKROW input_row, JBLOCKROW output_row, JDIMENSION num_blocks)
+/* Copy a row of coefficient blocks from one place to another. */
+{
+#ifdef FMEMCOPY
+ FMEMCOPY(output_row, input_row, num_blocks * (DCTSIZE2 * SIZEOF(JCOEF)));
+#else
+ JCOEFPTR inptr, outptr;
+ long count;
+
+ inptr = (JCOEFPTR) input_row;
+ outptr = (JCOEFPTR) output_row;
+ for (count = (long) num_blocks * DCTSIZE2; count > 0; count--) {
+ *outptr++ = *inptr++;
+ }
+#endif
+}
+
+LOCAL(void)
+do_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Crop. This is only used when no rotate/flip is requested with the crop. */
+{
+ JDIMENSION dst_blk_y, x_crop_blocks, y_crop_blocks;
+ int ci, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ jpeg_component_info *compptr;
+
+ /* We simply have to copy the right amount of data (the destination's
+ * image size) starting at the given X and Y offsets in the source.
+ */
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_y + y_crop_blocks,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ lcl_jcopy_block_row(src_buffer[offset_y] + x_crop_blocks,
+ dst_buffer[offset_y],
+ compptr->width_in_blocks);
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_flip_h_no_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays)
+/* Horizontal flip; done in-place, so no separate dest array is required.
+ * NB: this only works when y_crop_offset is zero.
+ */
+{
+ JDIMENSION MCU_cols, comp_width, blk_x, blk_y, x_crop_blocks;
+ int ci, k, offset_y;
+ JBLOCKARRAY buffer;
+ JCOEFPTR ptr1, ptr2;
+ JCOEF temp1, temp2;
+ jpeg_component_info *compptr;
+
+ /* Horizontal mirroring of DCT blocks is accomplished by swapping
+ * pairs of blocks in-place. Within a DCT block, we perform horizontal
+ * mirroring by changing the signs of odd-numbered columns.
+ * Partial iMCUs at the right edge are left untouched.
+ */
+ MCU_cols = srcinfo->output_width /
+ (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ for (blk_y = 0; blk_y < compptr->height_in_blocks;
+ blk_y += compptr->v_samp_factor) {
+ buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ /* Do the mirroring */
+ for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) {
+ ptr1 = buffer[offset_y][blk_x];
+ ptr2 = buffer[offset_y][comp_width - blk_x - 1];
+ /* this unrolled loop doesn't need to know which row it's on... */
+ for (k = 0; k < DCTSIZE2; k += 2) {
+ temp1 = *ptr1; /* swap even column */
+ temp2 = *ptr2;
+ *ptr1++ = temp2;
+ *ptr2++ = temp1;
+ temp1 = *ptr1; /* swap odd column with sign change */
+ temp2 = *ptr2;
+ *ptr1++ = -temp2;
+ *ptr2++ = -temp1;
+ }
+ }
+ if (x_crop_blocks > 0) {
+ /* Now left-justify the portion of the data to be kept.
+ * We can't use a single lcl_jcopy_block_row() call because that routine
+ * depends on memcpy(), whose behavior is unspecified for overlapping
+ * source and destination areas. Sigh.
+ */
+ for (blk_x = 0; blk_x < compptr->width_in_blocks; blk_x++) {
+ lcl_jcopy_block_row(buffer[offset_y] + blk_x + x_crop_blocks,
+ buffer[offset_y] + blk_x,
+ (JDIMENSION) 1);
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Horizontal flip in general cropping case */
+{
+ JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, k, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JBLOCKROW src_row_ptr, dst_row_ptr;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Here we must output into a separate array because we can't touch
+ * different rows of a single virtual array simultaneously. Otherwise,
+ * this is essentially the same as the routine above.
+ */
+ MCU_cols = srcinfo->output_width /
+ (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_y + y_crop_blocks,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ dst_row_ptr = dst_buffer[offset_y];
+ src_row_ptr = src_buffer[offset_y];
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Do the mirrorable blocks */
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1];
+ /* this unrolled loop doesn't need to know which row it's on... */
+ for (k = 0; k < DCTSIZE2; k += 2) {
+ *dst_ptr++ = *src_ptr++; /* copy even column */
+ *dst_ptr++ = - *src_ptr++; /* copy odd column with sign change */
+ }
+ } else {
+ /* Copy last partial block(s) verbatim */
+ lcl_jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks,
+ dst_row_ptr + dst_blk_x,
+ (JDIMENSION) 1);
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Vertical flip */
+{
+ JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JBLOCKROW src_row_ptr, dst_row_ptr;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* We output into a separate array because we can't touch different
+ * rows of the source virtual array simultaneously. Otherwise, this
+ * is a pretty straightforward analog of horizontal flip.
+ * Within a DCT block, vertical mirroring is done by changing the signs
+ * of odd-numbered rows.
+ * Partial iMCUs at the bottom edge are copied verbatim.
+ */
+ MCU_rows = srcinfo->output_height /
+ (dstinfo->max_v_samp_factor * dstinfo_min_DCT_v_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ /* Row is within the mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_height - y_crop_blocks - dst_blk_y -
+ (JDIMENSION) compptr->v_samp_factor,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ } else {
+ /* Bottom-edge blocks will be copied verbatim. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_y + y_crop_blocks,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ }
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ /* Row is within the mirrorable area. */
+ dst_row_ptr = dst_buffer[offset_y];
+ src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
+ src_row_ptr += x_crop_blocks;
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[dst_blk_x];
+ for (i = 0; i < DCTSIZE; i += 2) {
+ /* copy even row */
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = *src_ptr++;
+ /* copy odd row with sign change */
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = - *src_ptr++;
+ }
+ }
+ } else {
+ /* Just copy row verbatim. */
+ lcl_jcopy_block_row(src_buffer[offset_y] + x_crop_blocks,
+ dst_buffer[offset_y],
+ compptr->width_in_blocks);
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Transpose source into destination */
+{
+ JDIMENSION dst_blk_x, dst_blk_y, x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Transposing pixels within a block just requires transposing the
+ * DCT coefficients.
+ * Partial iMCUs at the edges require no special treatment; we simply
+ * process all the available DCT blocks for every component.
+ */
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_x + x_crop_blocks,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ src_ptr = src_buffer[offset_x][dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* 90 degree rotation is equivalent to
+ * 1. Transposing the image;
+ * 2. Horizontal mirroring.
+ * These two steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Because of the horizontal mirror step, we can't process partial iMCUs
+ * at the (output) right edge properly. They just get transposed and
+ * not mirrored.
+ */
+ MCU_cols = srcinfo->output_height /
+ (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_width - x_crop_blocks - dst_blk_x -
+ (JDIMENSION) compptr->h_samp_factor,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ } else {
+ /* Edge blocks are transposed but not mirrored. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_x + x_crop_blocks,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ }
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1]
+ [dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ i++;
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ } else {
+ /* Edge blocks are transposed but not mirrored. */
+ src_ptr = src_buffer[offset_x]
+ [dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* 270 degree rotation is equivalent to
+ * 1. Horizontal mirroring;
+ * 2. Transposing the image.
+ * These two steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Because of the horizontal mirror step, we can't process partial iMCUs
+ * at the (output) bottom edge properly. They just get transposed and
+ * not mirrored.
+ */
+ MCU_rows = srcinfo->output_width /
+ (dstinfo->max_v_samp_factor * dstinfo_min_DCT_v_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_x + x_crop_blocks,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ /* Block is within the mirrorable area. */
+ src_ptr = src_buffer[offset_x]
+ [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ }
+ } else {
+ /* Edge blocks are transposed but not mirrored. */
+ src_ptr = src_buffer[offset_x]
+ [dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* 180 degree rotation is equivalent to
+ * 1. Vertical mirroring;
+ * 2. Horizontal mirroring.
+ * These two steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JBLOCKROW src_row_ptr, dst_row_ptr;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ MCU_cols = srcinfo->output_width /
+ (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size);
+ MCU_rows = srcinfo->output_height /
+ (dstinfo->max_v_samp_factor * dstinfo_min_DCT_v_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ /* Row is within the vertically mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_height - y_crop_blocks - dst_blk_y -
+ (JDIMENSION) compptr->v_samp_factor,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ } else {
+ /* Bottom-edge rows are only mirrored horizontally. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_y + y_crop_blocks,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ }
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ dst_row_ptr = dst_buffer[offset_y];
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ /* Row is within the mirrorable area. */
+ src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Process the blocks that can be mirrored both ways. */
+ src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1];
+ for (i = 0; i < DCTSIZE; i += 2) {
+ /* For even row, negate every odd column. */
+ for (j = 0; j < DCTSIZE; j += 2) {
+ *dst_ptr++ = *src_ptr++;
+ *dst_ptr++ = - *src_ptr++;
+ }
+ /* For odd row, negate every even column. */
+ for (j = 0; j < DCTSIZE; j += 2) {
+ *dst_ptr++ = - *src_ptr++;
+ *dst_ptr++ = *src_ptr++;
+ }
+ }
+ } else {
+ /* Any remaining right-edge blocks are only mirrored vertically. */
+ src_ptr = src_row_ptr[x_crop_blocks + dst_blk_x];
+ for (i = 0; i < DCTSIZE; i += 2) {
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = *src_ptr++;
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = - *src_ptr++;
+ }
+ }
+ }
+ } else {
+ /* Remaining rows are just mirrored horizontally. */
+ src_row_ptr = src_buffer[offset_y];
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Process the blocks that can be mirrored. */
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1];
+ for (i = 0; i < DCTSIZE2; i += 2) {
+ *dst_ptr++ = *src_ptr++;
+ *dst_ptr++ = - *src_ptr++;
+ }
+ } else {
+ /* Any remaining right-edge blocks are only copied. */
+ lcl_jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks,
+ dst_row_ptr + dst_blk_x,
+ (JDIMENSION) 1);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Transverse transpose is equivalent to
+ * 1. 180 degree rotation;
+ * 2. Transposition;
+ * or
+ * 1. Horizontal mirroring;
+ * 2. Transposition;
+ * 3. Horizontal mirroring.
+ * These steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ MCU_cols = srcinfo->output_height /
+ (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size);
+ MCU_rows = srcinfo->output_width /
+ (dstinfo->max_v_samp_factor * dstinfo_min_DCT_v_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_width - x_crop_blocks - dst_blk_x -
+ (JDIMENSION) compptr->h_samp_factor,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ } else {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_x + x_crop_blocks,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ }
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1]
+ [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ i++;
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ } else {
+ /* Right-edge blocks are mirrored in y only */
+ src_ptr = src_buffer[offset_x]
+ [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ } else {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Bottom-edge blocks are mirrored in x only */
+ src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1]
+ [dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ i++;
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ } else {
+ /* At lower right corner, just transpose, no mirroring */
+ src_ptr = src_buffer[offset_x]
+ [dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+/* Trim off any partial iMCUs on the indicated destination edge */
+
+LOCAL(void)
+trim_right_edge (jpeg_transform_info *info, JDIMENSION full_width)
+{
+ JDIMENSION MCU_cols;
+
+ MCU_cols = info->output_width / info->iMCU_sample_width;
+ if (MCU_cols > 0 && info->x_crop_offset + MCU_cols ==
+ full_width / info->iMCU_sample_width)
+ info->output_width = MCU_cols * info->iMCU_sample_width;
+}
+
+LOCAL(void)
+trim_bottom_edge (jpeg_transform_info *info, JDIMENSION full_height)
+{
+ JDIMENSION MCU_rows;
+
+ MCU_rows = info->output_height / info->iMCU_sample_height;
+ if (MCU_rows > 0 && info->y_crop_offset + MCU_rows ==
+ full_height / info->iMCU_sample_height)
+ info->output_height = MCU_rows * info->iMCU_sample_height;
+}
+
+
+/* Request any required workspace.
+ *
+ * This routine figures out the size that the output image will be
+ * (which implies that all the transform parameters must be set before
+ * it is called).
+ *
+ * We allocate the workspace virtual arrays from the source decompression
+ * object, so that all the arrays (both the original data and the workspace)
+ * will be taken into account while making memory management decisions.
+ * Hence, this routine must be called after jpeg_read_header (which reads
+ * the image dimensions) and before jpeg_read_coefficients (which realizes
+ * the source's virtual arrays).
+ *
+ * This function returns FALSE right away if -perfect is given
+ * and transformation is not perfect. Otherwise returns TRUE.
+ */
+
+GLOBAL(boolean)
+jtransform_request_workspace (j_decompress_ptr srcinfo,
+ jpeg_transform_info *info)
+{
+ jvirt_barray_ptr *coef_arrays;
+ boolean need_workspace, transpose_it;
+ jpeg_component_info *compptr;
+ JDIMENSION xoffset, yoffset;
+ JDIMENSION width_in_iMCUs, height_in_iMCUs;
+ JDIMENSION width_in_blocks, height_in_blocks;
+ int ci, h_samp_factor, v_samp_factor;
+
+ /* Determine number of components in output image */
+ if (info->force_grayscale &&
+ srcinfo->jpeg_color_space == JCS_YCbCr &&
+ srcinfo->num_components == 3)
+ /* We'll only process the first component */
+ info->num_components = 1;
+ else
+ /* Process all the components */
+ info->num_components = srcinfo->num_components;
+
+ /* Compute output image dimensions and related values. */
+#if JPEG_LIB_VERSION >= 80
+ jpeg_core_output_dimensions(srcinfo);
+#else
+ srcinfo->output_width = srcinfo->image_width;
+ srcinfo->output_height = srcinfo->image_height;
+#endif
+
+ /* Return right away if -perfect is given and transformation is not perfect.
+ */
+ if (info->perfect) {
+ if (info->num_components == 1) {
+ if (!jtransform_perfect_transform(srcinfo->output_width,
+ srcinfo->output_height,
+ srcinfo->min_DCT_h_scaled_size_,
+ srcinfo->min_DCT_v_scaled_size_,
+ info->transform))
+ return FALSE;
+ } else {
+ if (!jtransform_perfect_transform(srcinfo->output_width,
+ srcinfo->output_height,
+ srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size_,
+ srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size_,
+ info->transform))
+ return FALSE;
+ }
+ }
+
+ /* If there is only one output component, force the iMCU size to be 1;
+ * else use the source iMCU size. (This allows us to do the right thing
+ * when reducing color to grayscale, and also provides a handy way of
+ * cleaning up "funny" grayscale images whose sampling factors are not 1x1.)
+ */
+ switch (info->transform) {
+ case JXFORM_TRANSPOSE:
+ case JXFORM_TRANSVERSE:
+ case JXFORM_ROT_90:
+ case JXFORM_ROT_270:
+ info->output_width = srcinfo->output_height;
+ info->output_height = srcinfo->output_width;
+ if (info->num_components == 1) {
+ info->iMCU_sample_width = srcinfo->min_DCT_v_scaled_size_;
+ info->iMCU_sample_height = srcinfo->min_DCT_h_scaled_size_;
+ } else {
+ info->iMCU_sample_width =
+ srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size_;
+ info->iMCU_sample_height =
+ srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size_;
+ }
+ break;
+ default:
+ info->output_width = srcinfo->output_width;
+ info->output_height = srcinfo->output_height;
+ if (info->num_components == 1) {
+ info->iMCU_sample_width = srcinfo->min_DCT_h_scaled_size_;
+ info->iMCU_sample_height = srcinfo->min_DCT_v_scaled_size_;
+ } else {
+ info->iMCU_sample_width =
+ srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size_;
+ info->iMCU_sample_height =
+ srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size_;
+ }
+ break;
+ }
+
+ /* If cropping has been requested, compute the crop area's position and
+ * dimensions, ensuring that its upper left corner falls at an iMCU boundary.
+ */
+ if (info->crop) {
+ /* Insert default values for unset crop parameters */
+ if (info->crop_xoffset_set == JCROP_UNSET)
+ info->crop_xoffset = 0; /* default to +0 */
+ if (info->crop_yoffset_set == JCROP_UNSET)
+ info->crop_yoffset = 0; /* default to +0 */
+ if (info->crop_xoffset >= info->output_width ||
+ info->crop_yoffset >= info->output_height)
+ ERREXIT(srcinfo, JERR_CONVERSION_NOTIMPL);
+ if (info->crop_width_set == JCROP_UNSET)
+ info->crop_width = info->output_width - info->crop_xoffset;
+ if (info->crop_height_set == JCROP_UNSET)
+ info->crop_height = info->output_height - info->crop_yoffset;
+ /* Ensure parameters are valid */
+ if (info->crop_width <= 0 || info->crop_width > info->output_width ||
+ info->crop_height <= 0 || info->crop_height > info->output_height ||
+ info->crop_xoffset > info->output_width - info->crop_width ||
+ info->crop_yoffset > info->output_height - info->crop_height)
+ ERREXIT(srcinfo, JERR_CONVERSION_NOTIMPL);
+ /* Convert negative crop offsets into regular offsets */
+ if (info->crop_xoffset_set == JCROP_NEG)
+ xoffset = info->output_width - info->crop_width - info->crop_xoffset;
+ else
+ xoffset = info->crop_xoffset;
+ if (info->crop_yoffset_set == JCROP_NEG)
+ yoffset = info->output_height - info->crop_height - info->crop_yoffset;
+ else
+ yoffset = info->crop_yoffset;
+ /* Now adjust so that upper left corner falls at an iMCU boundary */
+ if (info->crop_width_set == JCROP_FORCE)
+ info->output_width = info->crop_width;
+ else
+ info->output_width =
+ info->crop_width + (xoffset % info->iMCU_sample_width);
+ if (info->crop_height_set == JCROP_FORCE)
+ info->output_height = info->crop_height;
+ else
+ info->output_height =
+ info->crop_height + (yoffset % info->iMCU_sample_height);
+ /* Save x/y offsets measured in iMCUs */
+ info->x_crop_offset = xoffset / info->iMCU_sample_width;
+ info->y_crop_offset = yoffset / info->iMCU_sample_height;
+ } else {
+ info->x_crop_offset = 0;
+ info->y_crop_offset = 0;
+ }
+
+ /* Figure out whether we need workspace arrays,
+ * and if so whether they are transposed relative to the source.
+ */
+ need_workspace = FALSE;
+ transpose_it = FALSE;
+ switch (info->transform) {
+ case JXFORM_NONE:
+ if (info->x_crop_offset != 0 || info->y_crop_offset != 0)
+ need_workspace = TRUE;
+ /* No workspace needed if neither cropping nor transforming */
+ break;
+ case JXFORM_FLIP_H:
+ if (info->trim)
+ trim_right_edge(info, srcinfo->output_width);
+ if (info->y_crop_offset != 0 || info->slow_hflip)
+ need_workspace = TRUE;
+ /* do_flip_h_no_crop doesn't need a workspace array */
+ break;
+ case JXFORM_FLIP_V:
+ if (info->trim)
+ trim_bottom_edge(info, srcinfo->output_height);
+ /* Need workspace arrays having same dimensions as source image. */
+ need_workspace = TRUE;
+ break;
+ case JXFORM_TRANSPOSE:
+ /* transpose does NOT have to trim anything */
+ /* Need workspace arrays having transposed dimensions. */
+ need_workspace = TRUE;
+ transpose_it = TRUE;
+ break;
+ case JXFORM_TRANSVERSE:
+ if (info->trim) {
+ trim_right_edge(info, srcinfo->output_height);
+ trim_bottom_edge(info, srcinfo->output_width);
+ }
+ /* Need workspace arrays having transposed dimensions. */
+ need_workspace = TRUE;
+ transpose_it = TRUE;
+ break;
+ case JXFORM_ROT_90:
+ if (info->trim)
+ trim_right_edge(info, srcinfo->output_height);
+ /* Need workspace arrays having transposed dimensions. */
+ need_workspace = TRUE;
+ transpose_it = TRUE;
+ break;
+ case JXFORM_ROT_180:
+ if (info->trim) {
+ trim_right_edge(info, srcinfo->output_width);
+ trim_bottom_edge(info, srcinfo->output_height);
+ }
+ /* Need workspace arrays having same dimensions as source image. */
+ need_workspace = TRUE;
+ break;
+ case JXFORM_ROT_270:
+ if (info->trim)
+ trim_bottom_edge(info, srcinfo->output_width);
+ /* Need workspace arrays having transposed dimensions. */
+ need_workspace = TRUE;
+ transpose_it = TRUE;
+ break;
+ }
+
+ /* Allocate workspace if needed.
+ * Note that we allocate arrays padded out to the next iMCU boundary,
+ * so that transform routines need not worry about missing edge blocks.
+ */
+ if (need_workspace) {
+ coef_arrays = (jvirt_barray_ptr *)
+ (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE,
+ SIZEOF(jvirt_barray_ptr) * info->num_components);
+ width_in_iMCUs = (JDIMENSION)
+ jdiv_round_up((long) info->output_width,
+ (long) info->iMCU_sample_width);
+ height_in_iMCUs = (JDIMENSION)
+ jdiv_round_up((long) info->output_height,
+ (long) info->iMCU_sample_height);
+ for (ci = 0; ci < info->num_components; ci++) {
+ compptr = srcinfo->comp_info + ci;
+ if (info->num_components == 1) {
+ /* we're going to force samp factors to 1x1 in this case */
+ h_samp_factor = v_samp_factor = 1;
+ } else if (transpose_it) {
+ h_samp_factor = compptr->v_samp_factor;
+ v_samp_factor = compptr->h_samp_factor;
+ } else {
+ h_samp_factor = compptr->h_samp_factor;
+ v_samp_factor = compptr->v_samp_factor;
+ }
+ width_in_blocks = width_in_iMCUs * h_samp_factor;
+ height_in_blocks = height_in_iMCUs * v_samp_factor;
+ coef_arrays[ci] = (*srcinfo->mem->request_virt_barray)
+ ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE,
+ width_in_blocks, height_in_blocks, (JDIMENSION) v_samp_factor);
+ }
+ info->workspace_coef_arrays = coef_arrays;
+ } else
+ info->workspace_coef_arrays = NULL;
+
+ return TRUE;
+}
+
+
+/* Transpose destination image parameters */
+
+LOCAL(void)
+transpose_critical_parameters (j_compress_ptr dstinfo)
+{
+ int tblno, i, j, ci, itemp;
+ jpeg_component_info *compptr;
+ JQUANT_TBL *qtblptr;
+ JDIMENSION jtemp;
+ UINT16 qtemp;
+
+ /* Transpose image dimensions */
+ jtemp = dstinfo->image_width;
+ dstinfo->image_width = dstinfo->image_height;
+ dstinfo->image_height = jtemp;
+#if JPEG_LIB_VERSION >= 70
+ itemp = dstinfo->min_DCT_h_scaled_size;
+ dstinfo->min_DCT_h_scaled_size = dstinfo->min_DCT_v_scaled_size;
+ dstinfo->min_DCT_v_scaled_size = itemp;
+#endif
+
+ /* Transpose sampling factors */
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ itemp = compptr->h_samp_factor;
+ compptr->h_samp_factor = compptr->v_samp_factor;
+ compptr->v_samp_factor = itemp;
+ }
+
+ /* Transpose quantization tables */
+ for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) {
+ qtblptr = dstinfo->quant_tbl_ptrs[tblno];
+ if (qtblptr != NULL) {
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < i; j++) {
+ qtemp = qtblptr->quantval[i*DCTSIZE+j];
+ qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i];
+ qtblptr->quantval[j*DCTSIZE+i] = qtemp;
+ }
+ }
+ }
+ }
+}
+
+
+/* Adjust Exif image parameters.
+ *
+ * We try to adjust the Tags ExifImageWidth and ExifImageHeight if possible.
+ */
+
+#if JPEG_LIB_VERSION >= 70
+LOCAL(void)
+adjust_exif_parameters (JOCTET FAR * data, unsigned int length,
+ JDIMENSION new_width, JDIMENSION new_height)
+{
+ boolean is_motorola; /* Flag for byte order */
+ unsigned int number_of_tags, tagnum;
+ unsigned int firstoffset, offset;
+ JDIMENSION new_value;
+
+ if (length < 12) return; /* Length of an IFD entry */
+
+ /* Discover byte order */
+ if (GETJOCTET(data[0]) == 0x49 && GETJOCTET(data[1]) == 0x49)
+ is_motorola = FALSE;
+ else if (GETJOCTET(data[0]) == 0x4D && GETJOCTET(data[1]) == 0x4D)
+ is_motorola = TRUE;
+ else
+ return;
+
+ /* Check Tag Mark */
+ if (is_motorola) {
+ if (GETJOCTET(data[2]) != 0) return;
+ if (GETJOCTET(data[3]) != 0x2A) return;
+ } else {
+ if (GETJOCTET(data[3]) != 0) return;
+ if (GETJOCTET(data[2]) != 0x2A) return;
+ }
+
+ /* Get first IFD offset (offset to IFD0) */
+ if (is_motorola) {
+ if (GETJOCTET(data[4]) != 0) return;
+ if (GETJOCTET(data[5]) != 0) return;
+ firstoffset = GETJOCTET(data[6]);
+ firstoffset <<= 8;
+ firstoffset += GETJOCTET(data[7]);
+ } else {
+ if (GETJOCTET(data[7]) != 0) return;
+ if (GETJOCTET(data[6]) != 0) return;
+ firstoffset = GETJOCTET(data[5]);
+ firstoffset <<= 8;
+ firstoffset += GETJOCTET(data[4]);
+ }
+ if (firstoffset > length - 2) return; /* check end of data segment */
+
+ /* Get the number of directory entries contained in this IFD */
+ if (is_motorola) {
+ number_of_tags = GETJOCTET(data[firstoffset]);
+ number_of_tags <<= 8;
+ number_of_tags += GETJOCTET(data[firstoffset+1]);
+ } else {
+ number_of_tags = GETJOCTET(data[firstoffset+1]);
+ number_of_tags <<= 8;
+ number_of_tags += GETJOCTET(data[firstoffset]);
+ }
+ if (number_of_tags == 0) return;
+ firstoffset += 2;
+
+ /* Search for ExifSubIFD offset Tag in IFD0 */
+ for (;;) {
+ if (firstoffset > length - 12) return; /* check end of data segment */
+ /* Get Tag number */
+ if (is_motorola) {
+ tagnum = GETJOCTET(data[firstoffset]);
+ tagnum <<= 8;
+ tagnum += GETJOCTET(data[firstoffset+1]);
+ } else {
+ tagnum = GETJOCTET(data[firstoffset+1]);
+ tagnum <<= 8;
+ tagnum += GETJOCTET(data[firstoffset]);
+ }
+ if (tagnum == 0x8769) break; /* found ExifSubIFD offset Tag */
+ if (--number_of_tags == 0) return;
+ firstoffset += 12;
+ }
+
+ /* Get the ExifSubIFD offset */
+ if (is_motorola) {
+ if (GETJOCTET(data[firstoffset+8]) != 0) return;
+ if (GETJOCTET(data[firstoffset+9]) != 0) return;
+ offset = GETJOCTET(data[firstoffset+10]);
+ offset <<= 8;
+ offset += GETJOCTET(data[firstoffset+11]);
+ } else {
+ if (GETJOCTET(data[firstoffset+11]) != 0) return;
+ if (GETJOCTET(data[firstoffset+10]) != 0) return;
+ offset = GETJOCTET(data[firstoffset+9]);
+ offset <<= 8;
+ offset += GETJOCTET(data[firstoffset+8]);
+ }
+ if (offset > length - 2) return; /* check end of data segment */
+
+ /* Get the number of directory entries contained in this SubIFD */
+ if (is_motorola) {
+ number_of_tags = GETJOCTET(data[offset]);
+ number_of_tags <<= 8;
+ number_of_tags += GETJOCTET(data[offset+1]);
+ } else {
+ number_of_tags = GETJOCTET(data[offset+1]);
+ number_of_tags <<= 8;
+ number_of_tags += GETJOCTET(data[offset]);
+ }
+ if (number_of_tags < 2) return;
+ offset += 2;
+
+ /* Search for ExifImageWidth and ExifImageHeight Tags in this SubIFD */
+ do {
+ if (offset > length - 12) return; /* check end of data segment */
+ /* Get Tag number */
+ if (is_motorola) {
+ tagnum = GETJOCTET(data[offset]);
+ tagnum <<= 8;
+ tagnum += GETJOCTET(data[offset+1]);
+ } else {
+ tagnum = GETJOCTET(data[offset+1]);
+ tagnum <<= 8;
+ tagnum += GETJOCTET(data[offset]);
+ }
+ if (tagnum == 0xA002 || tagnum == 0xA003) {
+ if (tagnum == 0xA002)
+ new_value = new_width; /* ExifImageWidth Tag */
+ else
+ new_value = new_height; /* ExifImageHeight Tag */
+ if (is_motorola) {
+ data[offset+2] = 0; /* Format = unsigned long (4 octets) */
+ data[offset+3] = 4;
+ data[offset+4] = 0; /* Number Of Components = 1 */
+ data[offset+5] = 0;
+ data[offset+6] = 0;
+ data[offset+7] = 1;
+ data[offset+8] = 0;
+ data[offset+9] = 0;
+ data[offset+10] = (JOCTET)((new_value >> 8) & 0xFF);
+ data[offset+11] = (JOCTET)(new_value & 0xFF);
+ } else {
+ data[offset+2] = 4; /* Format = unsigned long (4 octets) */
+ data[offset+3] = 0;
+ data[offset+4] = 1; /* Number Of Components = 1 */
+ data[offset+5] = 0;
+ data[offset+6] = 0;
+ data[offset+7] = 0;
+ data[offset+8] = (JOCTET)(new_value & 0xFF);
+ data[offset+9] = (JOCTET)((new_value >> 8) & 0xFF);
+ data[offset+10] = 0;
+ data[offset+11] = 0;
+ }
+ }
+ offset += 12;
+ } while (--number_of_tags);
+}
+#endif
+
+
+/* Adjust output image parameters as needed.
+ *
+ * This must be called after jpeg_copy_critical_parameters()
+ * and before jpeg_write_coefficients().
+ *
+ * The return value is the set of virtual coefficient arrays to be written
+ * (either the ones allocated by jtransform_request_workspace, or the
+ * original source data arrays). The caller will need to pass this value
+ * to jpeg_write_coefficients().
+ */
+
+GLOBAL(jvirt_barray_ptr *)
+jtransform_adjust_parameters (j_decompress_ptr srcinfo,
+ j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info)
+{
+ /* If force-to-grayscale is requested, adjust destination parameters */
+ if (info->force_grayscale) {
+ /* First, ensure we have YCbCr or grayscale data, and that the source's
+ * Y channel is full resolution. (No reasonable person would make Y
+ * be less than full resolution, so actually copying with that case
+ * isn't worth extra code space. But we check it to avoid crashing.)
+ */
+ if (((dstinfo->jpeg_color_space == JCS_YCbCr &&
+ dstinfo->num_components == 3) ||
+ (dstinfo->jpeg_color_space == JCS_GRAYSCALE &&
+ dstinfo->num_components == 1)) &&
+ srcinfo->comp_info[0].h_samp_factor == srcinfo->max_h_samp_factor &&
+ srcinfo->comp_info[0].v_samp_factor == srcinfo->max_v_samp_factor) {
+ /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed
+ * properly. Among other things, it sets the target h_samp_factor &
+ * v_samp_factor to 1, which typically won't match the source.
+ * We have to preserve the source's quantization table number, however.
+ */
+ int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no;
+ jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE);
+ dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no;
+ } else {
+ /* Sorry, can't do it */
+ ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL);
+ }
+ } else if (info->num_components == 1) {
+ /* For a single-component source, we force the destination sampling factors
+ * to 1x1, with or without force_grayscale. This is useful because some
+ * decoders choke on grayscale images with other sampling factors.
+ */
+ dstinfo->comp_info[0].h_samp_factor = 1;
+ dstinfo->comp_info[0].v_samp_factor = 1;
+ }
+
+ /* Correct the destination's image dimensions as necessary
+ * for rotate/flip, resize, and crop operations.
+ */
+#if JPEG_LIB_VERSION >= 70
+ dstinfo->jpeg_width = info->output_width;
+ dstinfo->jpeg_height = info->output_height;
+#endif
+
+ /* Transpose destination image parameters */
+ switch (info->transform) {
+ case JXFORM_TRANSPOSE:
+ case JXFORM_TRANSVERSE:
+ case JXFORM_ROT_90:
+ case JXFORM_ROT_270:
+#if JPEG_LIB_VERSION < 70
+ dstinfo->image_width = info->output_height;
+ dstinfo->image_height = info->output_width;
+#endif
+ transpose_critical_parameters(dstinfo);
+ break;
+ default:
+#if JPEG_LIB_VERSION < 70
+ dstinfo->image_width = info->output_width;
+ dstinfo->image_height = info->output_height;
+#endif
+ break;
+ }
+
+ /* Adjust Exif properties */
+ if (srcinfo->marker_list != NULL &&
+ srcinfo->marker_list->marker == JPEG_APP0+1 &&
+ srcinfo->marker_list->data_length >= 6 &&
+ GETJOCTET(srcinfo->marker_list->data[0]) == 0x45 &&
+ GETJOCTET(srcinfo->marker_list->data[1]) == 0x78 &&
+ GETJOCTET(srcinfo->marker_list->data[2]) == 0x69 &&
+ GETJOCTET(srcinfo->marker_list->data[3]) == 0x66 &&
+ GETJOCTET(srcinfo->marker_list->data[4]) == 0 &&
+ GETJOCTET(srcinfo->marker_list->data[5]) == 0) {
+ /* Suppress output of JFIF marker */
+ dstinfo->write_JFIF_header = FALSE;
+#if JPEG_LIB_VERSION >= 70
+ /* Adjust Exif image parameters */
+ if (dstinfo->jpeg_width != srcinfo->image_width ||
+ dstinfo->jpeg_height != srcinfo->image_height)
+ /* Align data segment to start of TIFF structure for parsing */
+ adjust_exif_parameters(srcinfo->marker_list->data + 6,
+ srcinfo->marker_list->data_length - 6,
+ dstinfo->jpeg_width, dstinfo->jpeg_height);
+#endif
+ }
+
+ /* Return the appropriate output data set */
+ if (info->workspace_coef_arrays != NULL)
+ return info->workspace_coef_arrays;
+ return src_coef_arrays;
+}
+
+
+/* Execute the actual transformation, if any.
+ *
+ * This must be called *after* jpeg_write_coefficients, because it depends
+ * on jpeg_write_coefficients to have computed subsidiary values such as
+ * the per-component width and height fields in the destination object.
+ *
+ * Note that some transformations will modify the source data arrays!
+ */
+
+GLOBAL(void)
+jtransform_execute_transform (j_decompress_ptr srcinfo,
+ j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info)
+{
+ jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays;
+
+ /* Note: conditions tested here should match those in switch statement
+ * in jtransform_request_workspace()
+ */
+ switch (info->transform) {
+ case JXFORM_NONE:
+ if (info->x_crop_offset != 0 || info->y_crop_offset != 0)
+ do_crop(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_FLIP_H:
+ if (info->y_crop_offset != 0 || info->slow_hflip)
+ do_flip_h(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ else
+ do_flip_h_no_crop(srcinfo, dstinfo, info->x_crop_offset,
+ src_coef_arrays);
+ break;
+ case JXFORM_FLIP_V:
+ do_flip_v(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_TRANSPOSE:
+ do_transpose(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_TRANSVERSE:
+ do_transverse(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_90:
+ do_rot_90(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_180:
+ do_rot_180(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_270:
+ do_rot_270(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ }
+}
+
+/* jtransform_perfect_transform
+ *
+ * Determine whether lossless transformation is perfectly
+ * possible for a specified image and transformation.
+ *
+ * Inputs:
+ * image_width, image_height: source image dimensions.
+ * MCU_width, MCU_height: pixel dimensions of MCU.
+ * transform: transformation identifier.
+ * Parameter sources from initialized jpeg_struct
+ * (after reading source header):
+ * image_width = cinfo.image_width
+ * image_height = cinfo.image_height
+ * MCU_width = cinfo.max_h_samp_factor * cinfo.block_size
+ * MCU_height = cinfo.max_v_samp_factor * cinfo.block_size
+ * Result:
+ * TRUE = perfect transformation possible
+ * FALSE = perfect transformation not possible
+ * (may use custom action then)
+ */
+
+GLOBAL(boolean)
+jtransform_perfect_transform(JDIMENSION image_width, JDIMENSION image_height,
+ int MCU_width, int MCU_height,
+ JXFORM_CODE transform)
+{
+ boolean result = TRUE; /* initialize TRUE */
+
+ switch (transform) {
+ case JXFORM_FLIP_H:
+ case JXFORM_ROT_270:
+ if (image_width % (JDIMENSION) MCU_width)
+ result = FALSE;
+ break;
+ case JXFORM_FLIP_V:
+ case JXFORM_ROT_90:
+ if (image_height % (JDIMENSION) MCU_height)
+ result = FALSE;
+ break;
+ case JXFORM_TRANSVERSE:
+ case JXFORM_ROT_180:
+ if (image_width % (JDIMENSION) MCU_width)
+ result = FALSE;
+ if (image_height % (JDIMENSION) MCU_height)
+ result = FALSE;
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+#endif /* TRANSFORMS_SUPPORTED */
+
+
+/* Setup decompression object to save desired markers in memory.
+ * This must be called before jpeg_read_header() to have the desired effect.
+ */
+
+GLOBAL(void)
+jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option)
+{
+#ifdef SAVE_MARKERS_SUPPORTED
+ int m;
+
+ /* Save comments except under NONE option */
+ if (option != JCOPYOPT_NONE) {
+ jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF);
+ }
+ /* Save all types of APPn markers iff ALL option */
+ if (option == JCOPYOPT_ALL) {
+ for (m = 0; m < 16; m++)
+ jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF);
+ }
+#else
+ (void) srcinfo; (void) option;
+#endif /* SAVE_MARKERS_SUPPORTED */
+}
+
+/* Copy markers saved in the given source object to the destination object.
+ * This should be called just after jpeg_start_compress() or
+ * jpeg_write_coefficients().
+ * Note that those routines will have written the SOI, and also the
+ * JFIF APP0 or Adobe APP14 markers if selected.
+ */
+
+GLOBAL(void)
+jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JCOPY_OPTION option)
+{
+ jpeg_saved_marker_ptr marker;
+
+ /* In the current implementation, we don't actually need to examine the
+ * option flag here; we just copy everything that got saved.
+ * But to avoid confusion, we do not output JFIF and Adobe APP14 markers
+ * if the encoder library already wrote one.
+ */
+ (void)option;
+
+ for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) {
+ if (dstinfo->write_JFIF_header &&
+ marker->marker == JPEG_APP0 &&
+ marker->data_length >= 5 &&
+ GETJOCTET(marker->data[0]) == 0x4A &&
+ GETJOCTET(marker->data[1]) == 0x46 &&
+ GETJOCTET(marker->data[2]) == 0x49 &&
+ GETJOCTET(marker->data[3]) == 0x46 &&
+ GETJOCTET(marker->data[4]) == 0)
+ continue; /* reject duplicate JFIF */
+ if (dstinfo->write_Adobe_marker &&
+ marker->marker == JPEG_APP0+14 &&
+ marker->data_length >= 5 &&
+ GETJOCTET(marker->data[0]) == 0x41 &&
+ GETJOCTET(marker->data[1]) == 0x64 &&
+ GETJOCTET(marker->data[2]) == 0x6F &&
+ GETJOCTET(marker->data[3]) == 0x62 &&
+ GETJOCTET(marker->data[4]) == 0x65)
+ continue; /* reject duplicate Adobe */
+#ifdef NEED_FAR_POINTERS
+ /* We could use jpeg_write_marker if the data weren't FAR... */
+ {
+ unsigned int i;
+ jpeg_write_m_header(dstinfo, marker->marker, marker->data_length);
+ for (i = 0; i < marker->data_length; i++)
+ jpeg_write_m_byte(dstinfo, marker->data[i]);
+ }
+#else
+ jpeg_write_marker(dstinfo, marker->marker,
+ marker->data, marker->data_length);
+#endif
+ }
+}
diff --git a/vcl/source/filter/jpeg/transupp.h b/vcl/source/filter/jpeg/transupp.h
new file mode 100644
index 000000000..a5d403335
--- /dev/null
+++ b/vcl/source/filter/jpeg/transupp.h
@@ -0,0 +1,212 @@
+/*
+ * transupp.h
+ *
+ * Copyright (C) 1997-2011, Thomas G. Lane, Guido Vollbeding.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file contains declarations for image transformation routines and
+ * other utility code used by the jpegtran sample application. These are
+ * NOT part of the core JPEG library. But we keep these routines separate
+ * from jpegtran.c to ease the task of maintaining jpegtran-like programs
+ * that have other user interfaces.
+ *
+ * NOTE: all the routines declared here have very specific requirements
+ * about when they are to be executed during the reading and writing of the
+ * source and destination files. See the comments in transupp.c, or see
+ * jpegtran.c for an example of correct usage.
+ */
+
+/* If you happen not to want the image transform support, disable it here */
+#ifndef TRANSFORMS_SUPPORTED
+#define TRANSFORMS_SUPPORTED 1 /* 0 disables transform code */
+#endif
+
+/*
+ * Although rotating and flipping data expressed as DCT coefficients is not
+ * hard, there is an asymmetry in the JPEG format specification for images
+ * whose dimensions aren't multiples of the iMCU size. The right and bottom
+ * image edges are padded out to the next iMCU boundary with junk data; but
+ * no padding is possible at the top and left edges. If we were to flip
+ * the whole image including the pad data, then pad garbage would become
+ * visible at the top and/or left, and real pixels would disappear into the
+ * pad margins --- perhaps permanently, since encoders & decoders may not
+ * bother to preserve DCT blocks that appear to be completely outside the
+ * nominal image area. So, we have to exclude any partial iMCUs from the
+ * basic transformation.
+ *
+ * Transpose is the only transformation that can handle partial iMCUs at the
+ * right and bottom edges completely cleanly. flip_h can flip partial iMCUs
+ * at the bottom, but leaves any partial iMCUs at the right edge untouched.
+ * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched.
+ * The other transforms are defined as combinations of these basic transforms
+ * and process edge blocks in a way that preserves the equivalence.
+ *
+ * The "trim" option causes untransformable partial iMCUs to be dropped;
+ * this is not strictly lossless, but it usually gives the best-looking
+ * result for odd-size images. Note that when this option is active,
+ * the expected mathematical equivalences between the transforms may not hold.
+ * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim
+ * followed by -rot 180 -trim trims both edges.)
+ *
+ * We also offer a lossless-crop option, which discards data outside a given
+ * image region but losslessly preserves what is inside. Like the rotate and
+ * flip transforms, lossless crop is restricted by the JPEG format: the upper
+ * left corner of the selected region must fall on an iMCU boundary. If this
+ * does not hold for the given crop parameters, we silently move the upper left
+ * corner up and/or left to make it so, simultaneously increasing the region
+ * dimensions to keep the lower right crop corner unchanged. (Thus, the
+ * output image covers at least the requested region, but may cover more.)
+ * The adjustment of the region dimensions may be optionally disabled.
+ *
+ * We also provide a lossless-resize option, which is kind of a lossless-crop
+ * operation in the DCT coefficient block domain - it discards higher-order
+ * coefficients and losslessly preserves lower-order coefficients of a
+ * sub-block.
+ *
+ * Rotate/flip transform, resize, and crop can be requested together in a
+ * single invocation. The crop is applied last --- that is, the crop region
+ * is specified in terms of the destination image after transform/resize.
+ *
+ * We also offer a "force to grayscale" option, which simply discards the
+ * chrominance channels of a YCbCr image. This is lossless in the sense that
+ * the luminance channel is preserved exactly. It's not the same kind of
+ * thing as the rotate/flip transformations, but it's convenient to handle it
+ * as part of this package, mainly because the transformation routines have to
+ * be aware of the option to know how many components to work on.
+ */
+
+/* Short forms of external names for systems with brain-damaged linkers. */
+
+#ifdef NEED_SHORT_EXTERNAL_NAMES
+#define jtransform_parse_crop_spec jTrParCrop
+#define jtransform_request_workspace jTrRequest
+#define jtransform_adjust_parameters jTrAdjust
+#define jtransform_execute_transform jTrExec
+#define jtransform_perfect_transform jTrPerfect
+#define jcopy_markers_setup jCMrkSetup
+#define jcopy_markers_execute jCMrkExec
+#endif /* NEED_SHORT_EXTERNAL_NAMES */
+
+/*
+ * Codes for supported types of image transformations.
+ */
+
+typedef enum {
+ JXFORM_NONE, /* no transformation */
+ JXFORM_FLIP_H, /* horizontal flip */
+ JXFORM_FLIP_V, /* vertical flip */
+ JXFORM_TRANSPOSE, /* transpose across UL-to-LR axis */
+ JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */
+ JXFORM_ROT_90, /* 90-degree clockwise rotation */
+ JXFORM_ROT_180, /* 180-degree rotation */
+ JXFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) */
+} JXFORM_CODE;
+
+/*
+ * Codes for crop parameters, which can individually be unspecified,
+ * positive or negative for xoffset or yoffset,
+ * positive or forced for width or height.
+ */
+
+typedef enum {
+ JCROP_UNSET,
+ JCROP_POS,
+ JCROP_NEG,
+ JCROP_FORCE
+} JCROP_CODE;
+
+/*
+ * Transform parameters struct.
+ * NB: application must not change any elements of this struct after
+ * calling jtransform_request_workspace.
+ */
+
+typedef struct {
+ /* Options: set by caller */
+ JXFORM_CODE transform; /* image transform operator */
+ boolean perfect; /* if TRUE, fail if partial MCUs are requested */
+ boolean trim; /* if TRUE, trim partial MCUs as needed */
+ boolean force_grayscale; /* if TRUE, convert color image to grayscale */
+ boolean crop; /* if TRUE, crop source image */
+ boolean slow_hflip; /* For best performance, the JXFORM_FLIP_H transform
+ normally modifies the source coefficients in place.
+ Setting this to TRUE will instead use a slower,
+ double-buffered algorithm, which leaves the source
+ coefficients intact (necessary if other transformed
+ images must be generated from the same set of
+ coefficients. */
+
+ /* Crop parameters: application need not set these unless crop is TRUE.
+ * These can be filled in by jtransform_parse_crop_spec().
+ */
+ JDIMENSION crop_width; /* Width of selected region */
+ JCROP_CODE crop_width_set; /* (forced disables adjustment) */
+ JDIMENSION crop_height; /* Height of selected region */
+ JCROP_CODE crop_height_set; /* (forced disables adjustment) */
+ JDIMENSION crop_xoffset; /* X offset of selected region */
+ JCROP_CODE crop_xoffset_set; /* (negative measures from right edge) */
+ JDIMENSION crop_yoffset; /* Y offset of selected region */
+ JCROP_CODE crop_yoffset_set; /* (negative measures from bottom edge) */
+
+ /* Internal workspace: caller should not touch these */
+ int num_components; /* # of components in workspace */
+ jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */
+ JDIMENSION output_width; /* cropped destination dimensions */
+ JDIMENSION output_height;
+ JDIMENSION x_crop_offset; /* destination crop offsets measured in iMCUs */
+ JDIMENSION y_crop_offset;
+ int iMCU_sample_width; /* destination iMCU size */
+ int iMCU_sample_height;
+} jpeg_transform_info;
+
+#if TRANSFORMS_SUPPORTED
+
+/* Request any required workspace */
+EXTERN(boolean) jtransform_request_workspace
+ (j_decompress_ptr srcinfo, jpeg_transform_info *info);
+/* Adjust output image parameters */
+EXTERN(jvirt_barray_ptr *) jtransform_adjust_parameters
+ (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info);
+/* Execute the actual transformation, if any */
+EXTERN(void) jtransform_execute_transform
+ (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info);
+/* Determine whether lossless transformation is perfectly
+ * possible for a specified image and transformation.
+ */
+EXTERN(boolean) jtransform_perfect_transform
+ (JDIMENSION image_width, JDIMENSION image_height,
+ int MCU_width, int MCU_height,
+ JXFORM_CODE transform);
+
+/* jtransform_execute_transform used to be called
+ * jtransform_execute_transformation, but some compilers complain about
+ * routine names that long. This macro is here to avoid breaking any
+ * old source code that uses the original name...
+ */
+#define jtransform_execute_transformation jtransform_execute_transform
+
+#endif /* TRANSFORMS_SUPPORTED */
+
+/*
+ * Support for copying optional markers from source to destination file.
+ */
+
+typedef enum {
+ JCOPYOPT_NONE, /* copy no optional markers */
+ JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */
+ JCOPYOPT_ALL /* copy all optional markers */
+} JCOPY_OPTION;
+
+
+/* Setup decompression object to save desired markers in memory */
+EXTERN(void) jcopy_markers_setup
+ (j_decompress_ptr srcinfo, JCOPY_OPTION option);
+/* Copy markers saved in the given source object to the destination object */
+EXTERN(void) jcopy_markers_execute
+ (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JCOPY_OPTION option);
diff --git a/vcl/source/filter/png/PngImageReader.cxx b/vcl/source/filter/png/PngImageReader.cxx
new file mode 100644
index 000000000..6a3053a72
--- /dev/null
+++ b/vcl/source/filter/png/PngImageReader.cxx
@@ -0,0 +1,532 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/filter/PngImageReader.hxx>
+#include <png.h>
+#include <rtl/crc.h>
+#include <tools/stream.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/alpha.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <unotools/configmgr.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+#include "png.hxx"
+
+namespace
+{
+void lclReadStream(png_structp pPng, png_bytep pOutBytes, png_size_t nBytesToRead)
+{
+ png_voidp pIO = png_get_io_ptr(pPng);
+
+ if (pIO == nullptr)
+ return;
+
+ SvStream* pStream = static_cast<SvStream*>(pIO);
+
+ sal_Size nBytesRead = pStream->ReadBytes(pOutBytes, nBytesToRead);
+
+ if (nBytesRead != nBytesToRead)
+ {
+ if (!nBytesRead)
+ png_error(pPng, "Error reading");
+ else
+ {
+ // Make sure to not reuse old data (could cause infinite loop).
+ memset(pOutBytes + nBytesRead, 0, nBytesToRead - nBytesRead);
+ png_warning(pPng, "Short read");
+ }
+ }
+}
+
+constexpr int PNG_SIGNATURE_SIZE = 8;
+
+bool isPng(SvStream& rStream)
+{
+ // Check signature bytes
+ sal_uInt8 aHeader[PNG_SIGNATURE_SIZE];
+ if (rStream.ReadBytes(aHeader, PNG_SIGNATURE_SIZE) != PNG_SIGNATURE_SIZE)
+ return false;
+ return png_sig_cmp(aHeader, 0, PNG_SIGNATURE_SIZE) == 0;
+}
+
+struct PngDestructor
+{
+ ~PngDestructor() { png_destroy_read_struct(&pPng, &pInfo, nullptr); }
+ png_structp pPng;
+ png_infop pInfo;
+};
+
+#if defined __GNUC__ && __GNUC__ == 8 && !defined __clang__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wclobbered"
+#endif
+bool reader(SvStream& rStream, BitmapEx& rBitmapEx,
+ GraphicFilterImportFlags nImportFlags = GraphicFilterImportFlags::NONE,
+ BitmapScopedWriteAccess* pAccess = nullptr,
+ AlphaScopedWriteAccess* pAlphaAccess = nullptr)
+{
+ if (!isPng(rStream))
+ return false;
+
+ png_structp pPng = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (!pPng)
+ return false;
+
+ png_infop pInfo = png_create_info_struct(pPng);
+ if (!pInfo)
+ {
+ png_destroy_read_struct(&pPng, nullptr, nullptr);
+ return false;
+ }
+
+ PngDestructor pngDestructor = { pPng, pInfo };
+
+ // All variables holding resources need to be declared here in order to be
+ // properly cleaned up in case of an error, otherwise libpng's longjmp()
+ // jumps over the destructor calls.
+ Bitmap aBitmap;
+ AlphaMask aBitmapAlpha;
+ Size prefSize;
+ BitmapScopedWriteAccess pWriteAccessInstance;
+ AlphaScopedWriteAccess pWriteAccessAlphaInstance;
+ const bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ const bool bSupportsBitmap32 = bFuzzing || ImplGetSVData()->mpDefInst->supportsBitmap32();
+ const bool bOnlyCreateBitmap
+ = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::OnlyCreateBitmap);
+ const bool bUseExistingBitmap
+ = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap);
+
+ if (setjmp(png_jmpbuf(pPng)))
+ {
+ if (!bUseExistingBitmap)
+ {
+ // Set the bitmap if it contains something, even on failure. This allows
+ // reading images that are only partially broken.
+ pWriteAccessInstance.reset();
+ pWriteAccessAlphaInstance.reset();
+ if (!aBitmap.IsEmpty() && !aBitmapAlpha.IsEmpty())
+ rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
+ else if (!aBitmap.IsEmpty())
+ rBitmapEx = BitmapEx(aBitmap);
+ if (!rBitmapEx.IsEmpty() && !prefSize.IsEmpty())
+ {
+ rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ rBitmapEx.SetPrefSize(prefSize);
+ }
+ }
+ return false;
+ }
+
+ png_set_option(pPng, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON);
+
+ png_set_read_fn(pPng, &rStream, lclReadStream);
+
+ if (!bFuzzing)
+ png_set_crc_action(pPng, PNG_CRC_ERROR_QUIT, PNG_CRC_WARN_DISCARD);
+ else
+ png_set_crc_action(pPng, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
+
+ png_set_sig_bytes(pPng, PNG_SIGNATURE_SIZE);
+
+ png_read_info(pPng, pInfo);
+
+ png_uint_32 width = 0;
+ png_uint_32 height = 0;
+ int bitDepth = 0;
+ int colorType = -1;
+ int interlace = -1;
+
+ png_uint_32 returnValue = png_get_IHDR(pPng, pInfo, &width, &height, &bitDepth, &colorType,
+ &interlace, nullptr, nullptr);
+
+ if (returnValue != 1)
+ return false;
+
+ if (colorType == PNG_COLOR_TYPE_PALETTE)
+ png_set_palette_to_rgb(pPng);
+
+ if (colorType == PNG_COLOR_TYPE_GRAY)
+ png_set_expand_gray_1_2_4_to_8(pPng);
+
+ if (png_get_valid(pPng, pInfo, PNG_INFO_tRNS))
+ png_set_tRNS_to_alpha(pPng);
+
+ if (bitDepth == 16)
+ png_set_scale_16(pPng);
+
+ if (bitDepth < 8)
+ png_set_packing(pPng);
+
+ // Convert gray+alpha to RGBA, keep gray as gray.
+ if (colorType == PNG_COLOR_TYPE_GRAY_ALPHA
+ || (colorType == PNG_COLOR_TYPE_GRAY && png_get_valid(pPng, pInfo, PNG_INFO_tRNS)))
+ {
+ png_set_gray_to_rgb(pPng);
+ }
+
+ // Sets the filler byte - if RGB it converts to RGBA
+ // png_set_filler(pPng, 0xFF, PNG_FILLER_AFTER);
+
+ int nNumberOfPasses = png_set_interlace_handling(pPng);
+
+ png_read_update_info(pPng, pInfo);
+ returnValue = png_get_IHDR(pPng, pInfo, &width, &height, &bitDepth, &colorType, nullptr,
+ nullptr, nullptr);
+
+ if (returnValue != 1)
+ return false;
+
+ if (bitDepth != 8
+ || (colorType != PNG_COLOR_TYPE_RGB && colorType != PNG_COLOR_TYPE_RGB_ALPHA
+ && colorType != PNG_COLOR_TYPE_GRAY))
+ {
+ return false;
+ }
+
+ png_uint_32 res_x = 0;
+ png_uint_32 res_y = 0;
+ int unit_type = 0;
+ if (png_get_pHYs(pPng, pInfo, &res_x, &res_y, &unit_type) != 0
+ && unit_type == PNG_RESOLUTION_METER && res_x && res_y)
+ {
+ // convert into MapUnit::Map100thMM
+ prefSize = Size(static_cast<sal_Int32>((100000.0 * width) / res_x),
+ static_cast<sal_Int32>((100000.0 * height) / res_y));
+ }
+
+ if (!bUseExistingBitmap)
+ {
+ switch (colorType)
+ {
+ case PNG_COLOR_TYPE_RGB:
+ aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP);
+ break;
+ case PNG_COLOR_TYPE_RGBA:
+ if (bSupportsBitmap32)
+ aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N32_BPP);
+ else
+ {
+ aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP);
+ aBitmapAlpha = AlphaMask(Size(width, height), nullptr);
+ }
+ break;
+ case PNG_COLOR_TYPE_GRAY:
+ aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N8_BPP,
+ &Bitmap::GetGreyPalette(256));
+ break;
+ default:
+ abort();
+ }
+
+ if (bOnlyCreateBitmap)
+ {
+ if (!aBitmapAlpha.IsEmpty())
+ rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
+ else
+ rBitmapEx = BitmapEx(aBitmap);
+ if (!prefSize.IsEmpty())
+ {
+ rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ rBitmapEx.SetPrefSize(prefSize);
+ }
+ return true;
+ }
+
+ pWriteAccessInstance = BitmapScopedWriteAccess(aBitmap);
+ if (!pWriteAccessInstance)
+ return false;
+ if (!aBitmapAlpha.IsEmpty())
+ {
+ pWriteAccessAlphaInstance = AlphaScopedWriteAccess(aBitmapAlpha);
+ if (!pWriteAccessAlphaInstance)
+ return false;
+ }
+ }
+ BitmapScopedWriteAccess& pWriteAccess = pAccess ? *pAccess : pWriteAccessInstance;
+ AlphaScopedWriteAccess& pWriteAccessAlpha
+ = pAlphaAccess ? *pAlphaAccess : pWriteAccessAlphaInstance;
+
+ if (colorType == PNG_COLOR_TYPE_RGB)
+ {
+ ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat();
+ if (eFormat == ScanlineFormat::N24BitTcBgr)
+ png_set_bgr(pPng);
+
+ for (int pass = 0; pass < nNumberOfPasses; pass++)
+ {
+ for (png_uint_32 y = 0; y < height; y++)
+ {
+ Scanline pScanline = pWriteAccess->GetScanline(y);
+ png_read_row(pPng, pScanline, nullptr);
+ }
+ }
+ }
+ else if (colorType == PNG_COLOR_TYPE_RGB_ALPHA)
+ {
+ size_t aRowSizeBytes = png_get_rowbytes(pPng, pInfo);
+
+ if (bSupportsBitmap32)
+ {
+ ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat();
+ if (eFormat == ScanlineFormat::N32BitTcAbgr || eFormat == ScanlineFormat::N32BitTcBgra)
+ png_set_bgr(pPng);
+
+ for (int pass = 0; pass < nNumberOfPasses; pass++)
+ {
+ for (png_uint_32 y = 0; y < height; y++)
+ {
+ Scanline pScanline = pWriteAccess->GetScanline(y);
+ png_read_row(pPng, pScanline, nullptr);
+ }
+ }
+#if !ENABLE_WASM_STRIP_PREMULTIPLY
+ const vcl::bitmap::lookup_table& premultiply = vcl::bitmap::get_premultiply_table();
+#endif
+ if (eFormat == ScanlineFormat::N32BitTcAbgr || eFormat == ScanlineFormat::N32BitTcArgb)
+ { // alpha first and premultiply
+ for (png_uint_32 y = 0; y < height; y++)
+ {
+ Scanline pScanline = pWriteAccess->GetScanline(y);
+ for (size_t i = 0; i < aRowSizeBytes; i += 4)
+ {
+ const sal_uInt8 alpha = pScanline[i + 3];
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ pScanline[i + 3] = vcl::bitmap::premultiply(alpha, pScanline[i + 2]);
+ pScanline[i + 2] = vcl::bitmap::premultiply(alpha, pScanline[i + 1]);
+ pScanline[i + 1] = vcl::bitmap::premultiply(alpha, pScanline[i]);
+#else
+ pScanline[i + 3] = premultiply[alpha][pScanline[i + 2]];
+ pScanline[i + 2] = premultiply[alpha][pScanline[i + 1]];
+ pScanline[i + 1] = premultiply[alpha][pScanline[i]];
+#endif
+ pScanline[i] = alpha;
+ }
+ }
+ }
+ else
+ { // keep alpha last, only premultiply
+ for (png_uint_32 y = 0; y < height; y++)
+ {
+ Scanline pScanline = pWriteAccess->GetScanline(y);
+ for (size_t i = 0; i < aRowSizeBytes; i += 4)
+ {
+ const sal_uInt8 alpha = pScanline[i + 3];
+#if ENABLE_WASM_STRIP_PREMULTIPLY
+ pScanline[i] = vcl::bitmap::premultiply(alpha, pScanline[i]);
+ pScanline[i + 1] = vcl::bitmap::premultiply(alpha, pScanline[i + 1]);
+ pScanline[i + 2] = vcl::bitmap::premultiply(alpha, pScanline[i + 2]);
+#else
+ pScanline[i] = premultiply[alpha][pScanline[i]];
+ pScanline[i + 1] = premultiply[alpha][pScanline[i + 1]];
+ pScanline[i + 2] = premultiply[alpha][pScanline[i + 2]];
+#endif
+ }
+ }
+ }
+ }
+ else
+ {
+ ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat();
+ if (eFormat == ScanlineFormat::N24BitTcBgr)
+ png_set_bgr(pPng);
+
+ if (nNumberOfPasses == 1)
+ {
+ // optimise the common case, where we can use a buffer of only a single row
+ std::vector<png_byte> aRow(aRowSizeBytes, 0);
+ for (png_uint_32 y = 0; y < height; y++)
+ {
+ Scanline pScanline = pWriteAccess->GetScanline(y);
+ Scanline pScanAlpha = pWriteAccessAlpha->GetScanline(y);
+ png_bytep pRow = aRow.data();
+ png_read_row(pPng, pRow, nullptr);
+ size_t iAlpha = 0;
+ size_t iColor = 0;
+ for (size_t i = 0; i < aRowSizeBytes; i += 4)
+ {
+ pScanline[iColor++] = pRow[i + 0];
+ pScanline[iColor++] = pRow[i + 1];
+ pScanline[iColor++] = pRow[i + 2];
+ pScanAlpha[iAlpha++] = 0xFF - pRow[i + 3];
+ }
+ }
+ }
+ else
+ {
+ std::vector<std::vector<png_byte>> aRows(height);
+ for (auto& rRow : aRows)
+ rRow.resize(aRowSizeBytes, 0);
+ for (int pass = 0; pass < nNumberOfPasses; pass++)
+ {
+ for (png_uint_32 y = 0; y < height; y++)
+ {
+ Scanline pScanline = pWriteAccess->GetScanline(y);
+ Scanline pScanAlpha = pWriteAccessAlpha->GetScanline(y);
+ png_bytep pRow = aRows[y].data();
+ png_read_row(pPng, pRow, nullptr);
+ size_t iAlpha = 0;
+ size_t iColor = 0;
+ for (size_t i = 0; i < aRowSizeBytes; i += 4)
+ {
+ pScanline[iColor++] = pRow[i + 0];
+ pScanline[iColor++] = pRow[i + 1];
+ pScanline[iColor++] = pRow[i + 2];
+ pScanAlpha[iAlpha++] = 0xFF - pRow[i + 3];
+ }
+ }
+ }
+ }
+ }
+ }
+ else if (colorType == PNG_COLOR_TYPE_GRAY)
+ {
+ for (int pass = 0; pass < nNumberOfPasses; pass++)
+ {
+ for (png_uint_32 y = 0; y < height; y++)
+ {
+ Scanline pScanline = pWriteAccess->GetScanline(y);
+ png_read_row(pPng, pScanline, nullptr);
+ }
+ }
+ }
+
+ png_read_end(pPng, pInfo);
+
+ if (!bUseExistingBitmap)
+ {
+ pWriteAccess.reset();
+ pWriteAccessAlpha.reset();
+ if (!aBitmapAlpha.IsEmpty())
+ rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
+ else
+ rBitmapEx = BitmapEx(aBitmap);
+ if (!prefSize.IsEmpty())
+ {
+ rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ rBitmapEx.SetPrefSize(prefSize);
+ }
+ }
+
+ return true;
+}
+
+std::unique_ptr<sal_uInt8[]> getMsGifChunk(SvStream& rStream, sal_Int32* chunkSize)
+{
+ if (chunkSize)
+ *chunkSize = 0;
+ if (!isPng(rStream))
+ return nullptr;
+ // It's easier to read manually the contents and find the chunk than
+ // try to get it using libpng.
+ // https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_format
+ // Each chunk is: 4 bytes length, 4 bytes type, <length> bytes, 4 bytes crc
+ bool ignoreCrc = utl::ConfigManager::IsFuzzing();
+ for (;;)
+ {
+ sal_uInt32 length(0), type(0), crc(0);
+ rStream.ReadUInt32(length);
+ rStream.ReadUInt32(type);
+ if (!rStream.good())
+ return nullptr;
+ constexpr sal_uInt32 PNGCHUNK_msOG = 0x6d734f47; // Microsoft Office Animated GIF
+ constexpr sal_uInt64 MSGifHeaderSize = 11; // "MSOFFICE9.0"
+ if (type == PNGCHUNK_msOG && length > MSGifHeaderSize)
+ {
+ // calculate chunktype CRC (swap it back to original byte order)
+ sal_uInt32 typeForCrc = type;
+#if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN)
+ typeForCrc = OSL_SWAPDWORD(typeForCrc);
+#endif
+ sal_uInt32 computedCrc = rtl_crc32(0, &typeForCrc, 4);
+ const sal_uInt64 pos = rStream.Tell();
+ if (pos + length >= rStream.TellEnd())
+ return nullptr; // broken PNG
+
+ char msHeader[MSGifHeaderSize];
+ if (rStream.ReadBytes(msHeader, MSGifHeaderSize) != MSGifHeaderSize)
+ return nullptr;
+ computedCrc = rtl_crc32(computedCrc, msHeader, MSGifHeaderSize);
+ length -= MSGifHeaderSize;
+
+ std::unique_ptr<sal_uInt8[]> chunk(new sal_uInt8[length]);
+ if (rStream.ReadBytes(chunk.get(), length) != length)
+ return nullptr;
+ computedCrc = rtl_crc32(computedCrc, chunk.get(), length);
+ rStream.ReadUInt32(crc);
+ if (!ignoreCrc && crc != computedCrc)
+ continue; // invalid chunk, ignore
+ if (chunkSize)
+ *chunkSize = length;
+ return chunk;
+ }
+ if (rStream.remainingSize() < length)
+ return nullptr;
+ rStream.SeekRel(length);
+ rStream.ReadUInt32(crc);
+ constexpr sal_uInt32 PNGCHUNK_IEND = 0x49454e44;
+ if (type == PNGCHUNK_IEND)
+ return nullptr;
+ }
+}
+#if defined __GNUC__ && __GNUC__ == 8 && !defined __clang__
+#pragma GCC diagnostic pop
+#endif
+
+} // anonymous namespace
+
+namespace vcl
+{
+PngImageReader::PngImageReader(SvStream& rStream)
+ : mrStream(rStream)
+{
+}
+
+bool PngImageReader::read(BitmapEx& rBitmapEx) { return reader(mrStream, rBitmapEx); }
+
+BitmapEx PngImageReader::read()
+{
+ BitmapEx bitmap;
+ read(bitmap);
+ return bitmap;
+}
+
+std::unique_ptr<sal_uInt8[]> PngImageReader::getMicrosoftGifChunk(SvStream& rStream,
+ sal_Int32* chunkSize)
+{
+ sal_uInt64 originalPosition = rStream.Tell();
+ SvStreamEndian originalEndian = rStream.GetEndian();
+ rStream.SetEndian(SvStreamEndian::BIG);
+ std::unique_ptr<sal_uInt8[]> chunk = getMsGifChunk(rStream, chunkSize);
+ rStream.SetEndian(originalEndian);
+ rStream.Seek(originalPosition);
+ return chunk;
+}
+
+bool ImportPNG(SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags,
+ BitmapScopedWriteAccess* pAccess, AlphaScopedWriteAccess* pAlphaAccess)
+{
+ // Creating empty bitmaps should be practically a no-op, and thus thread-safe.
+ BitmapEx bitmap;
+ if (reader(rInputStream, bitmap, nImportFlags, pAccess, pAlphaAccess))
+ {
+ if (!(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap))
+ rGraphic = bitmap;
+ return true;
+ }
+ return false;
+}
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/png/png.hxx b/vcl/source/filter/png/png.hxx
new file mode 100644
index 000000000..b3a1b7b17
--- /dev/null
+++ b/vcl/source/filter/png/png.hxx
@@ -0,0 +1,33 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+namespace vcl
+{
+bool ImportPNG(SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags,
+ BitmapScopedWriteAccess* pAccess, AlphaScopedWriteAccess* pAlphaAccess);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/png/pngwrite.cxx b/vcl/source/filter/png/pngwrite.cxx
new file mode 100644
index 000000000..865fe38eb
--- /dev/null
+++ b/vcl/source/filter/png/pngwrite.cxx
@@ -0,0 +1,660 @@
+/* -*- 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 <vcl/pngwrite.hxx>
+#include <vcl/bitmapex.hxx>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+
+#include <limits>
+#include <rtl/crc.h>
+#include <tools/solar.h>
+#include <tools/zcodec.hxx>
+#include <tools/stream.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/alpha.hxx>
+#include <osl/endian.h>
+#include <memory>
+#include <vcl/BitmapTools.hxx>
+
+#define PNG_DEF_COMPRESSION 6
+
+#define PNGCHUNK_IHDR 0x49484452
+#define PNGCHUNK_PLTE 0x504c5445
+#define PNGCHUNK_IDAT 0x49444154
+#define PNGCHUNK_IEND 0x49454e44
+#define PNGCHUNK_pHYs 0x70485973
+
+namespace vcl
+{
+class PNGWriterImpl
+{
+public:
+ PNGWriterImpl(const BitmapEx& BmpEx,
+ const css::uno::Sequence<css::beans::PropertyValue>* pFilterData);
+
+ bool Write(SvStream& rOutStream);
+
+ std::vector<vcl::PNGWriter::ChunkData>& GetChunks() { return maChunkSeq; }
+
+private:
+ std::vector<vcl::PNGWriter::ChunkData> maChunkSeq;
+
+ sal_Int32 mnCompLevel;
+ sal_Int32 mnInterlaced;
+ sal_uInt32 mnMaxChunkSize;
+ bool mbStatus;
+
+ Bitmap::ScopedReadAccess mpAccess;
+ BitmapReadAccess* mpMaskAccess;
+ ZCodec mpZCodec;
+
+ std::unique_ptr<sal_uInt8[]>
+ mpDeflateInBuf; // as big as the size of a scanline + alphachannel + 1
+ std::unique_ptr<sal_uInt8[]> mpPreviousScan; // as big as mpDeflateInBuf
+ std::unique_ptr<sal_uInt8[]> mpCurrentScan;
+ sal_uLong mnDeflateInSize;
+
+ sal_uLong mnWidth;
+ sal_uLong mnHeight;
+ sal_uInt8 mnBitsPerPixel;
+ sal_uInt8 mnFilterType; // 0 or 4;
+ sal_uLong mnBBP; // bytes per pixel ( needed for filtering )
+ bool mbTrueAlpha;
+
+ void ImplWritepHYs(const BitmapEx& rBitmapEx);
+ void ImplWriteIDAT();
+ sal_uLong ImplGetFilter(sal_uLong nY, sal_uLong nXStart = 0, sal_uLong nXAdd = 1);
+ void ImplClearFirstScanline();
+ bool ImplWriteHeader();
+ void ImplWritePalette();
+ void ImplOpenChunk(sal_uLong nChunkType);
+ void ImplWriteChunk(sal_uInt8 nNumb);
+ void ImplWriteChunk(sal_uInt32 nNumb);
+ void ImplWriteChunk(unsigned char const* pSource, sal_uInt32 nDatSize);
+};
+
+PNGWriterImpl::PNGWriterImpl(const BitmapEx& rBitmapEx,
+ const css::uno::Sequence<css::beans::PropertyValue>* pFilterData)
+ : mnCompLevel(PNG_DEF_COMPRESSION)
+ , mnInterlaced(0)
+ , mnMaxChunkSize(0)
+ , mbStatus(true)
+ , mpMaskAccess(nullptr)
+ , mnDeflateInSize(0)
+ , mnWidth(0)
+ , mnHeight(0)
+ , mnBitsPerPixel(0)
+ , mnFilterType(0)
+ , mnBBP(0)
+ , mbTrueAlpha(false)
+{
+ if (rBitmapEx.IsEmpty())
+ return;
+
+ BitmapEx aBitmapEx;
+
+ if (rBitmapEx.GetBitmap().getPixelFormat() == vcl::PixelFormat::N32_BPP)
+ {
+ if (!vcl::bitmap::convertBitmap32To24Plus8(rBitmapEx, aBitmapEx))
+ return;
+ }
+ else
+ {
+ aBitmapEx = rBitmapEx;
+ }
+
+ Bitmap aBmp(aBitmapEx.GetBitmap());
+
+ mnMaxChunkSize = std::numeric_limits<sal_uInt32>::max();
+ bool bTranslucent = true;
+
+ if (pFilterData)
+ {
+ for (const auto& rPropVal : *pFilterData)
+ {
+ if (rPropVal.Name == "Compression")
+ rPropVal.Value >>= mnCompLevel;
+ else if (rPropVal.Name == "Interlaced")
+ rPropVal.Value >>= mnInterlaced;
+ else if (rPropVal.Name == "Translucent")
+ {
+ tools::Long nTmp = 0;
+ rPropVal.Value >>= nTmp;
+ if (!nTmp)
+ bTranslucent = false;
+ }
+ else if (rPropVal.Name == "MaxChunkSize")
+ {
+ sal_Int32 nVal = 0;
+ if (rPropVal.Value >>= nVal)
+ mnMaxChunkSize = static_cast<sal_uInt32>(nVal);
+ }
+ }
+ }
+ mnBitsPerPixel = sal_uInt8(vcl::pixelFormatBitCount(aBmp.getPixelFormat()));
+
+ if (aBitmapEx.IsAlpha() && bTranslucent)
+ {
+ if (mnBitsPerPixel <= 8)
+ {
+ aBmp.Convert(BmpConversion::N24Bit);
+ mnBitsPerPixel = 24;
+ }
+
+ mpAccess = Bitmap::ScopedReadAccess(aBmp); // true RGB with alphachannel
+ if (mpAccess)
+ {
+ mbTrueAlpha = true;
+ AlphaMask aMask(aBitmapEx.GetAlpha());
+ mpMaskAccess = aMask.AcquireReadAccess();
+ if (mpMaskAccess)
+ {
+ if (ImplWriteHeader())
+ {
+ ImplWritepHYs(aBitmapEx);
+ ImplWriteIDAT();
+ }
+ aMask.ReleaseAccess(mpMaskAccess);
+ mpMaskAccess = nullptr;
+ }
+ else
+ {
+ mbStatus = false;
+ }
+ mpAccess.reset();
+ }
+ else
+ {
+ mbStatus = false;
+ }
+ }
+ else
+ {
+ mpAccess = Bitmap::ScopedReadAccess(aBmp); // palette + RGB without alphachannel
+ if (mpAccess)
+ {
+ if (ImplWriteHeader())
+ {
+ ImplWritepHYs(aBitmapEx);
+ if (mpAccess->HasPalette())
+ ImplWritePalette();
+
+ ImplWriteIDAT();
+ }
+ mpAccess.reset();
+ }
+ else
+ {
+ mbStatus = false;
+ }
+ }
+
+ if (mbStatus)
+ {
+ ImplOpenChunk(PNGCHUNK_IEND); // create an IEND chunk
+ }
+}
+
+bool PNGWriterImpl::Write(SvStream& rOStm)
+{
+ /* png signature is always an array of 8 bytes */
+ SvStreamEndian nOldMode = rOStm.GetEndian();
+ rOStm.SetEndian(SvStreamEndian::BIG);
+ rOStm.WriteUInt32(0x89504e47);
+ rOStm.WriteUInt32(0x0d0a1a0a);
+
+ for (auto const& chunk : maChunkSeq)
+ {
+ sal_uInt32 nType = chunk.nType;
+#if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN)
+ nType = OSL_SWAPDWORD(nType);
+#endif
+ sal_uInt32 nCRC = rtl_crc32(0, &nType, 4);
+ sal_uInt32 nDataSize = chunk.aData.size();
+ if (nDataSize)
+ nCRC = rtl_crc32(nCRC, chunk.aData.data(), nDataSize);
+ rOStm.WriteUInt32(nDataSize);
+ rOStm.WriteUInt32(chunk.nType);
+ if (nDataSize)
+ rOStm.WriteBytes(chunk.aData.data(), nDataSize);
+ rOStm.WriteUInt32(nCRC);
+ }
+ rOStm.SetEndian(nOldMode);
+ return mbStatus;
+}
+
+bool PNGWriterImpl::ImplWriteHeader()
+{
+ ImplOpenChunk(PNGCHUNK_IHDR);
+ mnWidth = mpAccess->Width();
+ ImplWriteChunk(sal_uInt32(mnWidth));
+ mnHeight = mpAccess->Height();
+ ImplWriteChunk(sal_uInt32(mnHeight));
+
+ if (mnWidth && mnHeight && mnBitsPerPixel && mbStatus)
+ {
+ sal_uInt8 nBitDepth = mnBitsPerPixel;
+ if (mnBitsPerPixel <= 8)
+ mnFilterType = 0;
+ else
+ mnFilterType = 4;
+
+ sal_uInt8 nColorType = 2; // colortype:
+
+ // bit 0 -> palette is used
+ if (mpAccess->HasPalette()) // bit 1 -> color is used
+ nColorType |= 1; // bit 2 -> alpha channel is used
+ else
+ nBitDepth /= 3;
+
+ if (mpMaskAccess)
+ nColorType |= 4;
+
+ ImplWriteChunk(nBitDepth);
+ ImplWriteChunk(nColorType); // colortype
+ ImplWriteChunk(static_cast<sal_uInt8>(0)); // compression type
+ ImplWriteChunk(static_cast<sal_uInt8>(0)); // filter type - is not supported in this version
+ ImplWriteChunk(static_cast<sal_uInt8>(mnInterlaced)); // interlace type
+ }
+ else
+ {
+ mbStatus = false;
+ }
+ return mbStatus;
+}
+
+void PNGWriterImpl::ImplWritePalette()
+{
+ const sal_uLong nCount = mpAccess->GetPaletteEntryCount();
+ std::unique_ptr<sal_uInt8[]> pTempBuf(new sal_uInt8[nCount * 3]);
+ sal_uInt8* pTmp = pTempBuf.get();
+
+ ImplOpenChunk(PNGCHUNK_PLTE);
+
+ for (sal_uLong i = 0; i < nCount; i++)
+ {
+ const BitmapColor& rColor = mpAccess->GetPaletteColor(i);
+ *pTmp++ = rColor.GetRed();
+ *pTmp++ = rColor.GetGreen();
+ *pTmp++ = rColor.GetBlue();
+ }
+ ImplWriteChunk(pTempBuf.get(), nCount * 3);
+}
+
+void PNGWriterImpl::ImplWritepHYs(const BitmapEx& rBmpEx)
+{
+ if (rBmpEx.GetPrefMapMode().GetMapUnit() != MapUnit::Map100thMM)
+ return;
+
+ Size aPrefSize(rBmpEx.GetPrefSize());
+
+ if (aPrefSize.Width() && aPrefSize.Height() && mnWidth && mnHeight)
+ {
+ ImplOpenChunk(PNGCHUNK_pHYs);
+ sal_uInt32 nPrefSizeX = static_cast<sal_uInt32>(
+ 100000.0 / (static_cast<double>(aPrefSize.Width()) / mnWidth) + 0.5);
+ sal_uInt32 nPrefSizeY = static_cast<sal_uInt32>(
+ 100000.0 / (static_cast<double>(aPrefSize.Height()) / mnHeight) + 0.5);
+ ImplWriteChunk(nPrefSizeX);
+ ImplWriteChunk(nPrefSizeY);
+ ImplWriteChunk(sal_uInt8(1)); // nMapUnit
+ }
+}
+
+void PNGWriterImpl::ImplWriteIDAT()
+{
+ mnDeflateInSize = mnBitsPerPixel;
+
+ if (mpMaskAccess)
+ mnDeflateInSize += 8;
+
+ mnBBP = (mnDeflateInSize + 7) >> 3;
+
+ mnDeflateInSize = mnBBP * mnWidth + 1;
+
+ mpDeflateInBuf.reset(new sal_uInt8[mnDeflateInSize]);
+
+ if (mnFilterType) // using filter type 4 we need memory for the scanline 3 times
+ {
+ mpPreviousScan.reset(new sal_uInt8[mnDeflateInSize]);
+ mpCurrentScan.reset(new sal_uInt8[mnDeflateInSize]);
+ ImplClearFirstScanline();
+ }
+ mpZCodec.BeginCompression(mnCompLevel);
+ SvMemoryStream aOStm;
+ if (mnInterlaced == 0)
+ {
+ for (sal_uLong nY = 0; nY < mnHeight; nY++)
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY));
+ }
+ }
+ else
+ {
+ // interlace mode
+ sal_uLong nY;
+ for (nY = 0; nY < mnHeight; nY += 8) // pass 1
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 0, 8));
+ }
+ ImplClearFirstScanline();
+
+ for (nY = 0; nY < mnHeight; nY += 8) // pass 2
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 4, 8));
+ }
+ ImplClearFirstScanline();
+
+ if (mnHeight >= 5) // pass 3
+ {
+ for (nY = 4; nY < mnHeight; nY += 8)
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 0, 4));
+ }
+ ImplClearFirstScanline();
+ }
+
+ for (nY = 0; nY < mnHeight; nY += 4) // pass 4
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 2, 4));
+ }
+ ImplClearFirstScanline();
+
+ if (mnHeight >= 3) // pass 5
+ {
+ for (nY = 2; nY < mnHeight; nY += 4)
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 0, 2));
+ }
+ ImplClearFirstScanline();
+ }
+
+ for (nY = 0; nY < mnHeight; nY += 2) // pass 6
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 1, 2));
+ }
+ ImplClearFirstScanline();
+
+ if (mnHeight >= 2) // pass 7
+ {
+ for (nY = 1; nY < mnHeight; nY += 2)
+ {
+ mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY));
+ }
+ }
+ }
+ mpZCodec.EndCompression();
+
+ if (mnFilterType) // using filter type 4 we need memory for the scanline 3 times
+ {
+ mpCurrentScan.reset();
+ mpPreviousScan.reset();
+ }
+ mpDeflateInBuf.reset();
+
+ sal_uInt32 nIDATSize = aOStm.Tell();
+ sal_uInt32 nBytes, nBytesToWrite = nIDATSize;
+ while (nBytesToWrite)
+ {
+ nBytes = nBytesToWrite <= mnMaxChunkSize ? nBytesToWrite : mnMaxChunkSize;
+ ImplOpenChunk(PNGCHUNK_IDAT);
+ ImplWriteChunk(
+ const_cast<unsigned char*>(static_cast<unsigned char const*>(aOStm.GetData()))
+ + (nIDATSize - nBytesToWrite),
+ nBytes);
+ nBytesToWrite -= nBytes;
+ }
+}
+
+// ImplGetFilter writes the complete Scanline (nY) - in interlace mode the parameter nXStart and nXAdd
+// appends to the currently used pass
+// the complete size of scanline will be returned - in interlace mode zero is possible!
+
+sal_uLong PNGWriterImpl::ImplGetFilter(sal_uLong nY, sal_uLong nXStart, sal_uLong nXAdd)
+{
+ sal_uInt8* pDest;
+
+ if (mnFilterType)
+ pDest = mpCurrentScan.get();
+ else
+ pDest = mpDeflateInBuf.get();
+
+ if (nXStart < mnWidth)
+ {
+ *pDest++ = mnFilterType; // in this version the filter type is either 0 or 4
+
+ if (mpAccess
+ ->HasPalette()) // alphachannel is not allowed by pictures including palette entries
+ {
+ switch (mnBitsPerPixel)
+ {
+ case 1:
+ {
+ Scanline pScanline = mpAccess->GetScanline(nY);
+ sal_uLong nX, nXIndex;
+ for (nX = nXStart, nXIndex = 0; nX < mnWidth; nX += nXAdd, nXIndex++)
+ {
+ sal_uLong nShift = (nXIndex & 7) ^ 7;
+ if (nShift == 7)
+ *pDest = mpAccess->GetIndexFromData(pScanline, nX) << nShift;
+ else if (nShift == 0)
+ *pDest++ |= mpAccess->GetIndexFromData(pScanline, nX) << nShift;
+ else
+ *pDest |= mpAccess->GetIndexFromData(pScanline, nX) << nShift;
+ }
+ if ((nXIndex & 7) != 0)
+ pDest++; // byte is not completely used, so the bufferpointer is to correct
+ }
+ break;
+
+ case 4:
+ {
+ Scanline pScanline = mpAccess->GetScanline(nY);
+ sal_uLong nX, nXIndex;
+ for (nX = nXStart, nXIndex = 0; nX < mnWidth; nX += nXAdd, nXIndex++)
+ {
+ if (nXIndex & 1)
+ *pDest++ |= mpAccess->GetIndexFromData(pScanline, nX);
+ else
+ *pDest = mpAccess->GetIndexFromData(pScanline, nX) << 4;
+ }
+ if (nXIndex & 1)
+ pDest++;
+ }
+ break;
+
+ case 8:
+ {
+ Scanline pScanline = mpAccess->GetScanline(nY);
+ for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd)
+ {
+ *pDest++ = mpAccess->GetIndexFromData(pScanline, nX);
+ }
+ }
+ break;
+
+ default:
+ mbStatus = false;
+ break;
+ }
+ }
+ else
+ {
+ if (mpMaskAccess) // mpMaskAccess != NULL -> alphachannel is to create
+ {
+ if (mbTrueAlpha)
+ {
+ Scanline pScanline = mpAccess->GetScanline(nY);
+ Scanline pScanlineMask = mpMaskAccess->GetScanline(nY);
+ for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd)
+ {
+ const BitmapColor& rColor = mpAccess->GetPixelFromData(pScanline, nX);
+ *pDest++ = rColor.GetRed();
+ *pDest++ = rColor.GetGreen();
+ *pDest++ = rColor.GetBlue();
+ *pDest++ = 255 - mpMaskAccess->GetIndexFromData(pScanlineMask, nX);
+ }
+ }
+ else
+ {
+ const BitmapColor aTrans(mpMaskAccess->GetBestMatchingColor(COL_WHITE));
+ Scanline pScanline = mpAccess->GetScanline(nY);
+ Scanline pScanlineMask = mpMaskAccess->GetScanline(nY);
+
+ for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd)
+ {
+ const BitmapColor& rColor = mpAccess->GetPixelFromData(pScanline, nX);
+ *pDest++ = rColor.GetRed();
+ *pDest++ = rColor.GetGreen();
+ *pDest++ = rColor.GetBlue();
+
+ if (mpMaskAccess->GetPixelFromData(pScanlineMask, nX) == aTrans)
+ *pDest++ = 0;
+ else
+ *pDest++ = 0xff;
+ }
+ }
+ }
+ else
+ {
+ Scanline pScanline = mpAccess->GetScanline(nY);
+ for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd)
+ {
+ const BitmapColor& rColor = mpAccess->GetPixelFromData(pScanline, nX);
+ *pDest++ = rColor.GetRed();
+ *pDest++ = rColor.GetGreen();
+ *pDest++ = rColor.GetBlue();
+ }
+ }
+ }
+ }
+ // filter type4 ( PAETH ) will be used only for 24bit graphics
+ if (mnFilterType)
+ {
+ mnDeflateInSize = pDest - mpCurrentScan.get();
+ pDest = mpDeflateInBuf.get();
+ *pDest++ = 4; // filter type
+
+ sal_uInt8* p1 = mpCurrentScan.get() + 1; // Current Pixel
+ sal_uInt8* p2 = p1 - mnBBP; // left pixel
+ sal_uInt8* p3 = mpPreviousScan.get(); // upper pixel
+ sal_uInt8* p4 = p3 - mnBBP; // upperleft Pixel;
+
+ while (pDest < mpDeflateInBuf.get() + mnDeflateInSize)
+ {
+ sal_uLong nb = *p3++;
+ sal_uLong na, nc;
+ if (p2 >= mpCurrentScan.get() + 1)
+ {
+ na = *p2;
+ nc = *p4;
+ }
+ else
+ {
+ na = nc = 0;
+ }
+
+ tools::Long np = na + nb - nc;
+ tools::Long npa = np - na;
+ tools::Long npb = np - nb;
+ tools::Long npc = np - nc;
+
+ if (npa < 0)
+ npa = -npa;
+ if (npb < 0)
+ npb = -npb;
+ if (npc < 0)
+ npc = -npc;
+
+ if (npa <= npb && npa <= npc)
+ *pDest++ = *p1++ - static_cast<sal_uInt8>(na);
+ else if (npb <= npc)
+ *pDest++ = *p1++ - static_cast<sal_uInt8>(nb);
+ else
+ *pDest++ = *p1++ - static_cast<sal_uInt8>(nc);
+
+ p4++;
+ p2++;
+ }
+ for (tools::Long i = 0; i < static_cast<tools::Long>(mnDeflateInSize - 1); i++)
+ {
+ mpPreviousScan[i] = mpCurrentScan[i + 1];
+ }
+ }
+ else
+ {
+ mnDeflateInSize = pDest - mpDeflateInBuf.get();
+ }
+ return mnDeflateInSize;
+}
+
+void PNGWriterImpl::ImplClearFirstScanline()
+{
+ if (mnFilterType)
+ memset(mpPreviousScan.get(), 0, mnDeflateInSize);
+}
+
+void PNGWriterImpl::ImplOpenChunk(sal_uLong nChunkType)
+{
+ maChunkSeq.emplace_back();
+ maChunkSeq.back().nType = nChunkType;
+}
+
+void PNGWriterImpl::ImplWriteChunk(sal_uInt8 nSource)
+{
+ maChunkSeq.back().aData.push_back(nSource);
+}
+
+void PNGWriterImpl::ImplWriteChunk(sal_uInt32 nSource)
+{
+ vcl::PNGWriter::ChunkData& rChunkData = maChunkSeq.back();
+ rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource >> 24));
+ rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource >> 16));
+ rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource >> 8));
+ rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource));
+}
+
+void PNGWriterImpl::ImplWriteChunk(unsigned char const* pSource, sal_uInt32 nDatSize)
+{
+ if (nDatSize)
+ {
+ vcl::PNGWriter::ChunkData& rChunkData = maChunkSeq.back();
+ sal_uInt32 nSize = rChunkData.aData.size();
+ rChunkData.aData.resize(nSize + nDatSize);
+ memcpy(&rChunkData.aData[nSize], pSource, nDatSize);
+ }
+}
+
+PNGWriter::PNGWriter(const BitmapEx& rBmpEx,
+ const css::uno::Sequence<css::beans::PropertyValue>* pFilterData)
+ : mpImpl(new vcl::PNGWriterImpl(rBmpEx, pFilterData))
+{
+}
+
+PNGWriter::~PNGWriter() {}
+
+bool PNGWriter::Write(SvStream& rStream) { return mpImpl->Write(rStream); }
+
+std::vector<vcl::PNGWriter::ChunkData>& PNGWriter::GetChunks() { return mpImpl->GetChunks(); }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/svm/SvmConverter.cxx b/vcl/source/filter/svm/SvmConverter.cxx
new file mode 100644
index 000000000..2e7ed8f35
--- /dev/null
+++ b/vcl/source/filter/svm/SvmConverter.cxx
@@ -0,0 +1,1290 @@
+/* -*- 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/log.hxx>
+#include <osl/diagnose.h>
+#include <osl/thread.h>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <o3tl/safeint.hxx>
+
+#include <vcl/TypeSerializer.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/filter/SvmReader.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include "SvmConverter.hxx"
+
+#include <boost/rational.hpp>
+#include <algorithm>
+#include <memory>
+#include <stack>
+#include <string.h>
+
+// Inlines
+static void ImplReadRect( SvStream& rIStm, tools::Rectangle& rRect )
+{
+ Point aTL;
+ Point aBR;
+
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(aTL);
+ aSerializer.readPoint(aBR);
+
+ rRect = tools::Rectangle( aTL, aBR );
+}
+
+static bool ImplReadPoly(SvStream& rIStm, tools::Polygon& rPoly)
+{
+ TypeSerializer aSerializer(rIStm);
+
+ sal_Int32 nSize32(0);
+ rIStm.ReadInt32(nSize32);
+ sal_uInt16 nSize = nSize32;
+
+ const size_t nMaxPossiblePoints = rIStm.remainingSize() / 2 * sizeof(sal_Int32);
+ if (nSize > nMaxPossiblePoints)
+ {
+ SAL_WARN("vcl.gdi", "svm record claims to have: " << nSize << " points, but only " << nMaxPossiblePoints << " possible");
+ return false;
+ }
+
+ rPoly = tools::Polygon(nSize);
+
+ for (sal_uInt16 i = 0; i < nSize && rIStm.good(); ++i)
+ {
+ aSerializer.readPoint(rPoly[i]);
+ }
+ return rIStm.good();
+}
+
+static bool ImplReadPolyPoly(SvStream& rIStm, tools::PolyPolygon& rPolyPoly)
+{
+ bool bSuccess = true;
+
+ tools::Polygon aPoly;
+ sal_Int32 nPolyCount32(0);
+ rIStm.ReadInt32(nPolyCount32);
+ sal_uInt16 nPolyCount = static_cast<sal_uInt16>(nPolyCount32);
+
+ for (sal_uInt16 i = 0; i < nPolyCount && rIStm.good(); ++i)
+ {
+ if (!ImplReadPoly(rIStm, aPoly))
+ {
+ bSuccess = false;
+ break;
+ }
+ rPolyPoly.Insert(aPoly);
+ }
+
+ return bSuccess && rIStm.good();
+}
+
+static void ImplReadColor( SvStream& rIStm, Color& rColor )
+{
+ sal_Int16 nVal(0);
+
+ rIStm.ReadInt16( nVal ); rColor.SetRed( sal::static_int_cast<sal_uInt8>(static_cast<sal_uInt16>(nVal) >> 8) );
+ rIStm.ReadInt16( nVal ); rColor.SetGreen( sal::static_int_cast<sal_uInt8>(static_cast<sal_uInt16>(nVal) >> 8) );
+ rIStm.ReadInt16( nVal ); rColor.SetBlue( sal::static_int_cast<sal_uInt8>(static_cast<sal_uInt16>(nVal) >> 8) );
+}
+
+static bool ImplReadMapMode(SvStream& rIStm, MapMode& rMapMode)
+{
+ sal_Int16 nUnit(0);
+ rIStm.ReadInt16(nUnit);
+
+ Point aOrg;
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(aOrg);
+
+ sal_Int32 nXNum(0), nXDenom(0), nYNum(0), nYDenom(0);
+ rIStm.ReadInt32(nXNum).ReadInt32(nXDenom).ReadInt32(nYNum).ReadInt32(nYDenom);
+
+ if (!rIStm.good() || nXDenom <= 0 || nYDenom <= 0 || nXNum <= 0 || nYNum <= 0)
+ {
+ SAL_WARN("vcl.gdi", "Parsing error: invalid mapmode fraction");
+ return false;
+ }
+
+ if (nUnit < sal_Int16(MapUnit::Map100thMM) || nUnit > sal_Int16(MapUnit::LAST))
+ {
+ SAL_WARN("vcl.gdi", "Parsing error: invalid mapmode");
+ return false;
+ }
+
+ rMapMode = MapMode(static_cast<MapUnit>(nUnit), aOrg, Fraction(nXNum, nXDenom), Fraction(nYNum, nYDenom));
+
+ return true;
+}
+
+static void ImplReadUnicodeComment( sal_uInt32 nStrmPos, SvStream& rIStm, OUString& rString )
+{
+ sal_uInt64 nOld = rIStm.Tell();
+ if ( nStrmPos )
+ {
+ sal_uInt16 nType;
+ sal_uInt32 nActionSize;
+ std::size_t nStringLen;
+
+ rIStm.Seek( nStrmPos );
+ rIStm .ReadUInt16( nType )
+ .ReadUInt32( nActionSize );
+
+ nStringLen = (nActionSize - 4) >> 1;
+
+ if ( nStringLen && ( nType == GDI_UNICODE_COMMENT ) )
+ rString = read_uInt16s_ToOUString(rIStm, nStringLen);
+ }
+ rIStm.Seek( nOld );
+}
+
+static void ImplSkipActions(SvStream& rIStm, sal_uLong nSkipCount)
+{
+ sal_Int32 nActionSize;
+ sal_Int16 nType;
+ for (sal_uLong i = 0; i < nSkipCount; ++i)
+ {
+ rIStm.ReadInt16(nType).ReadInt32(nActionSize);
+ if (!rIStm.good() || nActionSize < 4)
+ break;
+ rIStm.SeekRel(nActionSize - 4);
+ }
+}
+
+static void ImplReadExtendedPolyPolygonAction(SvStream& rIStm, tools::PolyPolygon& rPolyPoly)
+{
+ TypeSerializer aSerializer(rIStm);
+
+ rPolyPoly.Clear();
+ sal_uInt16 nPolygonCount(0);
+ rIStm.ReadUInt16( nPolygonCount );
+
+ if (!nPolygonCount)
+ return;
+
+ const size_t nMinRecordSize = sizeof(sal_uInt16);
+ const size_t nMaxRecords = rIStm.remainingSize() / nMinRecordSize;
+ if (nPolygonCount > nMaxRecords)
+ {
+ SAL_WARN("vcl.gdi", "Parsing error: " << nMaxRecords <<
+ " max possible entries, but " << nPolygonCount << " claimed, truncating");
+ nPolygonCount = nMaxRecords;
+ }
+
+ for(sal_uInt16 a(0); a < nPolygonCount; a++)
+ {
+ sal_uInt16 nPointCount(0);
+ rIStm.ReadUInt16(nPointCount);
+
+ const size_t nMinPolygonSize = sizeof(sal_Int32) * 2;
+ const size_t nMaxPolygons = rIStm.remainingSize() / nMinPolygonSize;
+ if (nPointCount > nMaxPolygons)
+ {
+ SAL_WARN("vcl.gdi", "Parsing error: " << nMaxPolygons <<
+ " max possible entries, but " << nPointCount << " claimed, truncating");
+ nPointCount = nMaxPolygons;
+ }
+
+ tools::Polygon aCandidate(nPointCount);
+
+ if (nPointCount)
+ {
+ for(sal_uInt16 b(0); b < nPointCount; b++)
+ {
+ aSerializer.readPoint(aCandidate[b]);
+ }
+
+ sal_uInt8 bHasFlags(int(false));
+ rIStm.ReadUChar( bHasFlags );
+
+ if(bHasFlags)
+ {
+ sal_uInt8 aPolyFlags(0);
+
+ for(sal_uInt16 c(0); c < nPointCount; c++)
+ {
+ rIStm.ReadUChar( aPolyFlags );
+ aCandidate.SetFlags(c, static_cast<PolyFlags>(aPolyFlags));
+ }
+ }
+ }
+
+ rPolyPoly.Insert(aCandidate);
+ }
+}
+
+SVMConverter::SVMConverter( SvStream& rStm, GDIMetaFile& rMtf )
+{
+ if( !rStm.GetError() )
+ {
+ ImplConvertFromSVM1( rStm, rMtf );
+ }
+}
+
+namespace
+{
+ sal_Int32 SkipActions(sal_Int32 i, sal_Int32 nFollowingActionCount, sal_Int32 nActions)
+ {
+ sal_Int32 remainingActions = nActions - i;
+ if (nFollowingActionCount < 0)
+ nFollowingActionCount = remainingActions;
+ return std::min(remainingActions, nFollowingActionCount);
+ }
+
+ void ClampRange(const OUString& rStr, sal_Int32& rIndex, sal_Int32& rLength,
+ std::vector<sal_Int32>* pDXAry = nullptr)
+ {
+ const sal_Int32 nStrLength = rStr.getLength();
+
+ if (rIndex < 0 || rIndex > nStrLength)
+ {
+ SAL_WARN("vcl.gdi", "inconsistent offset");
+ rIndex = nStrLength;
+ }
+
+ if (rLength < 0 || rLength > nStrLength - rIndex)
+ {
+ SAL_WARN("vcl.gdi", "inconsistent len");
+ rLength = nStrLength - rIndex;
+ }
+
+ if (pDXAry && pDXAry->size() > o3tl::make_unsigned(rLength))
+ pDXAry->resize(rLength);
+ }
+}
+
+#define LF_FACESIZE 32
+
+void static lcl_error( SvStream& rIStm, const SvStreamEndian& nOldFormat, sal_uInt64 nPos)
+{
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ rIStm.SetEndian(nOldFormat);
+ rIStm.Seek(nPos);
+ return;
+}
+void SVMConverter::ImplConvertFromSVM1( SvStream& rIStm, GDIMetaFile& rMtf )
+{
+ const sal_uInt64 nPos = rIStm.Tell();
+ const SvStreamEndian nOldFormat = rIStm.GetEndian();
+
+ rIStm.SetEndian( SvStreamEndian::LITTLE );
+
+ char aCode[ 5 ];
+ Size aPrefSz;
+
+ // read header
+ rIStm.ReadBytes(aCode, sizeof(aCode)); // Identifier
+ sal_Int16 nSize(0);
+ rIStm.ReadInt16( nSize ); // Size
+ sal_Int16 nVersion(0);
+ rIStm.ReadInt16( nVersion ); // Version
+ sal_Int32 nTmp32(0);
+ rIStm.ReadInt32( nTmp32 );
+ if (nTmp32 < 0)
+ {
+ SAL_WARN("vcl.gdi", "svm: value for width should be positive");
+ lcl_error(rIStm, nOldFormat, nPos);
+ return;
+ }
+ aPrefSz.setWidth( nTmp32 ); // PrefSize.Width()
+ rIStm.ReadInt32( nTmp32 );
+ if (nTmp32 < 0)
+ {
+ SAL_WARN("vcl.gdi", "svm: value for height should be positive");
+ lcl_error(rIStm, nOldFormat, nPos);
+ return;
+ }
+ aPrefSz.setHeight( nTmp32 ); // PrefSize.Height()
+
+ // check header-magic and version
+ if( rIStm.GetError()
+ || ( nVersion != 200 )
+ || ( memcmp( aCode, "SVGDI", sizeof( aCode ) ) != 0 ) )
+ {
+ SAL_WARN("vcl.gdi", "svm: wrong check for header-magic and version");
+ lcl_error(rIStm, nOldFormat, nPos);
+ return;
+ }
+
+ LineInfo aLineInfo( LineStyle::NONE, 0 );
+ std::stack<LineInfo, std::vector<LineInfo>> aLIStack;
+ ScopedVclPtrInstance< VirtualDevice > aFontVDev;
+ rtl_TextEncoding eActualCharSet = osl_getThreadTextEncoding();
+ bool bFatLine = false;
+
+ tools::Polygon aActionPoly;
+ tools::Rectangle aRect;
+ Point aPt, aPt1;
+ Size aSz;
+ Color aActionColor;
+
+ sal_uInt32 nUnicodeCommentStreamPos = 0;
+ sal_Int32 nUnicodeCommentActionNumber = 0;
+
+ rMtf.SetPrefSize(aPrefSz);
+
+ MapMode aMapMode;
+ if (ImplReadMapMode(rIStm, aMapMode)) // MapMode
+ rMtf.SetPrefMapMode(aMapMode);
+
+ sal_Int32 nActions(0);
+ rIStm.ReadInt32(nActions); // Action count
+ if (nActions < 0)
+ {
+ SAL_WARN("vcl.gdi", "svm claims negative action count (" << nActions << ")");
+ nActions = 0;
+ }
+
+ const size_t nMinActionSize = sizeof(sal_uInt16) + sizeof(sal_Int32);
+ const size_t nMaxPossibleActions = rIStm.remainingSize() / nMinActionSize;
+ if (o3tl::make_unsigned(nActions) > nMaxPossibleActions)
+ {
+ SAL_WARN("vcl.gdi", "svm claims more actions (" << nActions << ") than stream could provide, truncating");
+ nActions = nMaxPossibleActions;
+ }
+
+ size_t nLastPolygonAction(0);
+
+ TypeSerializer aSerializer(rIStm);
+
+ for (sal_Int32 i = 0; i < nActions && rIStm.good(); ++i)
+ {
+ sal_Int16 nType(0);
+ rIStm.ReadInt16(nType);
+ sal_Int32 nActBegin = rIStm.Tell();
+ sal_Int32 nActionSize(0);
+ rIStm.ReadInt32(nActionSize);
+
+ SAL_WARN_IF( ( nType > 33 ) && ( nType < 1024 ), "vcl.gdi", "Unknown GDIMetaAction while converting!" );
+
+ switch( nType )
+ {
+ case GDI_PIXEL_ACTION:
+ {
+ aSerializer.readPoint(aPt);
+ ImplReadColor( rIStm, aActionColor );
+ rMtf.AddAction( new MetaPixelAction( aPt, aActionColor ) );
+ }
+ break;
+
+ case GDI_POINT_ACTION:
+ {
+ aSerializer.readPoint(aPt);
+ rMtf.AddAction( new MetaPointAction( aPt ) );
+ }
+ break;
+
+ case GDI_LINE_ACTION:
+ {
+ aSerializer.readPoint(aPt);
+ aSerializer.readPoint(aPt1);
+ rMtf.AddAction( new MetaLineAction( aPt, aPt1, aLineInfo ) );
+ }
+ break;
+
+ case GDI_LINEJOIN_ACTION :
+ {
+ sal_Int16 nLineJoin(0);
+ rIStm.ReadInt16( nLineJoin );
+ aLineInfo.SetLineJoin(static_cast<basegfx::B2DLineJoin>(nLineJoin));
+ }
+ break;
+
+ case GDI_LINECAP_ACTION :
+ {
+ sal_Int16 nLineCap(0);
+ rIStm.ReadInt16( nLineCap );
+ aLineInfo.SetLineCap(static_cast<css::drawing::LineCap>(nLineCap));
+ }
+ break;
+
+ case GDI_LINEDASHDOT_ACTION :
+ {
+ sal_Int16 a(0);
+ sal_Int32 b(0);
+
+ rIStm.ReadInt16( a ); aLineInfo.SetDashCount(a);
+ rIStm.ReadInt32( b ); aLineInfo.SetDashLen(b);
+ rIStm.ReadInt16( a ); aLineInfo.SetDotCount(a);
+ rIStm.ReadInt32( b ); aLineInfo.SetDotLen(b);
+ rIStm.ReadInt32( b ); aLineInfo.SetDistance(b);
+
+ if(((aLineInfo.GetDashCount() && aLineInfo.GetDashLen())
+ || (aLineInfo.GetDotCount() && aLineInfo.GetDotLen()))
+ && aLineInfo.GetDistance())
+ {
+ aLineInfo.SetStyle(LineStyle::Dash);
+ }
+ }
+ break;
+
+ case GDI_EXTENDEDPOLYGON_ACTION :
+ {
+ // read the tools::PolyPolygon in every case
+ tools::PolyPolygon aInputPolyPolygon;
+ ImplReadExtendedPolyPolygonAction(rIStm, aInputPolyPolygon);
+
+ // now check if it can be set somewhere
+ if(nLastPolygonAction < rMtf.GetActionSize())
+ {
+ MetaPolyLineAction* pPolyLineAction = dynamic_cast< MetaPolyLineAction* >(rMtf.GetAction(nLastPolygonAction));
+
+ if(pPolyLineAction)
+ {
+ // replace MetaPolyLineAction when we have a single polygon. Do not rely on the
+ // same point count; the originally written GDI_POLYLINE_ACTION may have been
+ // Subdivided for better quality for older usages
+ if(1 == aInputPolyPolygon.Count())
+ {
+ rMtf.ReplaceAction(
+ new MetaPolyLineAction(
+ aInputPolyPolygon.GetObject(0),
+ pPolyLineAction->GetLineInfo()),
+ nLastPolygonAction);
+ }
+ }
+ else
+ {
+ MetaPolyPolygonAction* pPolyPolygonAction = dynamic_cast< MetaPolyPolygonAction* >(rMtf.GetAction(nLastPolygonAction));
+
+ if(pPolyPolygonAction)
+ {
+ // replace MetaPolyPolygonAction when we have a curved polygon. Do rely on the
+ // same sub-polygon count
+ if(pPolyPolygonAction->GetPolyPolygon().Count() == aInputPolyPolygon.Count())
+ {
+ rMtf.ReplaceAction(
+ new MetaPolyPolygonAction(
+ aInputPolyPolygon),
+ nLastPolygonAction);
+ }
+ }
+ else
+ {
+ MetaPolygonAction* pPolygonAction = dynamic_cast< MetaPolygonAction* >(rMtf.GetAction(nLastPolygonAction));
+
+ if(pPolygonAction)
+ {
+ // replace MetaPolygonAction
+ if(1 == aInputPolyPolygon.Count())
+ {
+ rMtf.ReplaceAction(
+ new MetaPolygonAction(
+ aInputPolyPolygon.GetObject(0)),
+ nLastPolygonAction);
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case GDI_RECT_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+ sal_Int32 nTmp(0), nTmp1(0);
+ rIStm.ReadInt32( nTmp ).ReadInt32( nTmp1 );
+
+ if( nTmp || nTmp1 )
+ rMtf.AddAction( new MetaRoundRectAction( aRect, nTmp, nTmp1 ) );
+ else
+ {
+ rMtf.AddAction( new MetaRectAction( aRect ) );
+
+ if( bFatLine )
+ rMtf.AddAction( new MetaPolyLineAction( aRect, aLineInfo ) );
+ }
+ }
+ break;
+
+ case GDI_ELLIPSE_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+
+ if( bFatLine )
+ {
+ const tools::Polygon aPoly( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 );
+
+ rMtf.AddAction( new MetaPushAction( vcl::PushFlags::LINECOLOR ) );
+ rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) );
+ rMtf.AddAction( new MetaPolygonAction( aPoly ) );
+ rMtf.AddAction( new MetaPopAction() );
+ rMtf.AddAction( new MetaPolyLineAction( aPoly, aLineInfo ) );
+ }
+ else
+ rMtf.AddAction( new MetaEllipseAction( aRect ) );
+ }
+ break;
+
+ case GDI_ARC_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+ aSerializer.readPoint(aPt);
+ aSerializer.readPoint(aPt1);
+
+ if( bFatLine )
+ {
+ const tools::Polygon aPoly( aRect, aPt, aPt1, PolyStyle::Arc );
+
+ rMtf.AddAction( new MetaPushAction( vcl::PushFlags::LINECOLOR ) );
+ rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) );
+ rMtf.AddAction( new MetaPolygonAction( aPoly ) );
+ rMtf.AddAction( new MetaPopAction() );
+ rMtf.AddAction( new MetaPolyLineAction( aPoly, aLineInfo ) );
+ }
+ else
+ rMtf.AddAction( new MetaArcAction( aRect, aPt, aPt1 ) );
+ }
+ break;
+
+ case GDI_PIE_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+ aSerializer.readPoint(aPt);
+ aSerializer.readPoint(aPt1);
+
+ if( bFatLine )
+ {
+ const tools::Polygon aPoly( aRect, aPt, aPt1, PolyStyle::Pie );
+
+ rMtf.AddAction( new MetaPushAction( vcl::PushFlags::LINECOLOR ) );
+ rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) );
+ rMtf.AddAction( new MetaPolygonAction( aPoly ) );
+ rMtf.AddAction( new MetaPopAction() );
+ rMtf.AddAction( new MetaPolyLineAction( aPoly, aLineInfo ) );
+ }
+ else
+ rMtf.AddAction( new MetaPieAction( aRect, aPt, aPt1 ) );
+ }
+ break;
+
+ case GDI_INVERTRECT_ACTION:
+ case GDI_HIGHLIGHTRECT_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+ rMtf.AddAction( new MetaPushAction( vcl::PushFlags::RASTEROP ) );
+ rMtf.AddAction( new MetaRasterOpAction( RasterOp::Invert ) );
+ rMtf.AddAction( new MetaRectAction( aRect ) );
+ rMtf.AddAction( new MetaPopAction() );
+ }
+ break;
+
+ case GDI_POLYLINE_ACTION:
+ {
+ if (ImplReadPoly(rIStm, aActionPoly))
+ {
+ nLastPolygonAction = rMtf.GetActionSize();
+
+ if( bFatLine )
+ rMtf.AddAction( new MetaPolyLineAction( aActionPoly, aLineInfo ) );
+ else
+ rMtf.AddAction( new MetaPolyLineAction( aActionPoly ) );
+ }
+ }
+ break;
+
+ case GDI_POLYGON_ACTION:
+ {
+ if (ImplReadPoly(rIStm, aActionPoly))
+ {
+ if( bFatLine )
+ {
+ rMtf.AddAction( new MetaPushAction( vcl::PushFlags::LINECOLOR ) );
+ rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) );
+ rMtf.AddAction( new MetaPolygonAction( aActionPoly ) );
+ rMtf.AddAction( new MetaPopAction() );
+ rMtf.AddAction( new MetaPolyLineAction( aActionPoly, aLineInfo ) );
+ }
+ else
+ {
+ nLastPolygonAction = rMtf.GetActionSize();
+ rMtf.AddAction( new MetaPolygonAction( aActionPoly ) );
+ }
+ }
+ }
+ break;
+
+ case GDI_POLYPOLYGON_ACTION:
+ {
+ tools::PolyPolygon aPolyPoly;
+
+ if (ImplReadPolyPoly(rIStm, aPolyPoly))
+ {
+ if( bFatLine )
+ {
+ rMtf.AddAction( new MetaPushAction( vcl::PushFlags::LINECOLOR ) );
+ rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) );
+ rMtf.AddAction( new MetaPolyPolygonAction( aPolyPoly ) );
+ rMtf.AddAction( new MetaPopAction() );
+
+ for( sal_uInt16 nPoly = 0, nCount = aPolyPoly.Count(); nPoly < nCount; nPoly++ )
+ rMtf.AddAction( new MetaPolyLineAction( aPolyPoly[ nPoly ], aLineInfo ) );
+ }
+ else
+ {
+ nLastPolygonAction = rMtf.GetActionSize();
+ rMtf.AddAction( new MetaPolyPolygonAction( aPolyPoly ) );
+ }
+ }
+ }
+ break;
+
+ case GDI_FONT_ACTION:
+ {
+ vcl::Font aFont;
+ char aName[LF_FACESIZE+1];
+
+ ImplReadColor( rIStm, aActionColor ); aFont.SetColor( aActionColor );
+ ImplReadColor( rIStm, aActionColor ); aFont.SetFillColor( aActionColor );
+ size_t nRet = rIStm.ReadBytes(aName, LF_FACESIZE);
+ aName[nRet] = 0;
+ aFont.SetFamilyName( OUString( aName, strlen(aName), rIStm.GetStreamCharSet() ) );
+
+ sal_Int32 nWidth(0), nHeight(0);
+ rIStm.ReadInt32(nWidth).ReadInt32(nHeight);
+ sal_Int16 nCharOrient(0), nLineOrient(0);
+ rIStm.ReadInt16(nCharOrient).ReadInt16(nLineOrient);
+ sal_Int16 nCharSet(0), nFamily(0), nPitch(0), nAlign(0), nWeight(0), nUnderline(0), nStrikeout(0);
+ rIStm.ReadInt16(nCharSet).ReadInt16(nFamily).ReadInt16(nPitch).ReadInt16(nAlign).ReadInt16(nWeight).ReadInt16(nUnderline).ReadInt16(nStrikeout);
+ bool bItalic(false), bOutline(false), bShadow(false), bTransparent(false);
+ rIStm.ReadCharAsBool(bItalic).ReadCharAsBool(bOutline).ReadCharAsBool(bShadow).ReadCharAsBool(bTransparent);
+
+ aFont.SetFontSize( Size( nWidth, nHeight ) );
+ aFont.SetCharSet( static_cast<rtl_TextEncoding>(nCharSet) );
+ aFont.SetFamily( static_cast<FontFamily>(nFamily & SAL_MAX_ENUM) );
+ aFont.SetPitch( static_cast<FontPitch>(nPitch & SAL_MAX_ENUM) );
+ aFont.SetAlignment( static_cast<TextAlign>(nAlign & SAL_MAX_ENUM) );
+ aFont.SetWeight( ( nWeight == 1 ) ? WEIGHT_LIGHT : ( nWeight == 2 ) ? WEIGHT_NORMAL :
+ ( nWeight == 3 ) ? WEIGHT_BOLD : WEIGHT_DONTKNOW );
+ aFont.SetUnderline( static_cast<FontLineStyle>(nUnderline & SAL_MAX_ENUM) );
+ aFont.SetStrikeout( static_cast<FontStrikeout>(nStrikeout & SAL_MAX_ENUM) );
+ aFont.SetItalic( bItalic ? ITALIC_NORMAL : ITALIC_NONE );
+ aFont.SetOutline( bOutline );
+ aFont.SetShadow( bShadow );
+ aFont.SetOrientation( Degree10(nLineOrient) );
+ aFont.SetTransparent( bTransparent );
+
+ eActualCharSet = aFont.GetCharSet();
+ if ( eActualCharSet == RTL_TEXTENCODING_DONTKNOW )
+ eActualCharSet = osl_getThreadTextEncoding();
+
+ rMtf.AddAction( new MetaFontAction( aFont ) );
+ rMtf.AddAction( new MetaTextAlignAction( aFont.GetAlignment() ) );
+ rMtf.AddAction( new MetaTextColorAction( aFont.GetColor() ) );
+ rMtf.AddAction( new MetaTextFillColorAction( aFont.GetFillColor(), !aFont.IsTransparent() ) );
+
+ // #106172# Track font relevant data in shadow VDev
+ aFontVDev->SetFont( aFont );
+ }
+ break;
+
+ case GDI_TEXT_ACTION:
+ {
+ sal_Int32 nIndex(0), nLen(0), nTmp(0);
+ aSerializer.readPoint(aPt);
+ rIStm.ReadInt32( nIndex ).ReadInt32( nLen ).ReadInt32( nTmp );
+ if (nTmp > 0)
+ {
+ OString aByteStr = read_uInt8s_ToOString(rIStm, nTmp);
+ sal_uInt8 nTerminator = 0;
+ rIStm.ReadUChar( nTerminator );
+ SAL_WARN_IF( nTerminator != 0, "vcl.gdi", "expected string to be NULL terminated" );
+
+ OUString aStr(OStringToOUString(aByteStr, eActualCharSet));
+ if ( nUnicodeCommentActionNumber == i )
+ ImplReadUnicodeComment( nUnicodeCommentStreamPos, rIStm, aStr );
+ ClampRange(aStr, nIndex, nLen);
+ rMtf.AddAction( new MetaTextAction( aPt, aStr, nIndex, nLen ) );
+ }
+
+ if (nActionSize < 24)
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ else
+ rIStm.Seek(nActBegin + nActionSize);
+ }
+ break;
+
+ case GDI_TEXTARRAY_ACTION:
+ {
+ sal_Int32 nIndex(0), nLen(0), nAryLen(0), nTmp(0);
+ aSerializer.readPoint(aPt);
+ rIStm.ReadInt32( nIndex ).ReadInt32( nLen ).ReadInt32( nTmp ).ReadInt32( nAryLen );
+ if (nTmp > 0)
+ {
+ OString aByteStr = read_uInt8s_ToOString(rIStm, nTmp);
+ sal_uInt8 nTerminator = 0;
+ rIStm.ReadUChar( nTerminator );
+ SAL_WARN_IF( nTerminator != 0, "vcl.gdi", "expected string to be NULL terminated" );
+
+ OUString aStr(OStringToOUString(aByteStr, eActualCharSet));
+
+ std::vector<sal_Int32> aDXAry;
+ if (nAryLen > 0)
+ {
+ const size_t nMinRecordSize = sizeof(sal_Int32);
+ const size_t nMaxRecords = rIStm.remainingSize() / nMinRecordSize;
+ if (o3tl::make_unsigned(nAryLen) > nMaxRecords)
+ {
+ SAL_WARN("vcl.gdi", "Parsing error: " << nMaxRecords <<
+ " max possible entries, but " << nAryLen << " claimed, truncating");
+ nAryLen = nMaxRecords;
+ }
+
+ sal_Int32 nStrLen( aStr.getLength() );
+
+ sal_Int32 nDXAryLen = std::max(nAryLen, nStrLen);
+
+ if (nDXAryLen < nLen)
+ {
+ //MetaTextArrayAction ctor expects pDXAry to be >= nLen if set, so if this can't
+ //be achieved, don't read it, it's utterly broken.
+ SAL_WARN("vcl.gdi", "dxary too short, discarding completely");
+ rIStm.SeekRel(sizeof(sal_Int32) * nDXAryLen);
+ nLen = 0;
+ nIndex = 0;
+ }
+ else
+ {
+ aDXAry.resize(nDXAryLen);
+
+ for (sal_Int32 j = 0; j < nAryLen; ++j)
+ {
+ rIStm.ReadInt32( nTmp );
+ aDXAry[ j ] = nTmp;
+ }
+
+ // #106172# Add last DX array elem, if missing
+ if( nAryLen != nStrLen )
+ {
+ if (nAryLen+1 == nStrLen && nIndex >= 0)
+ {
+ std::vector<sal_Int32> aTmpAry;
+
+ aFontVDev->GetTextArray( aStr, &aTmpAry, nIndex, nLen );
+
+ if (aTmpAry.size() < o3tl::make_unsigned(nStrLen))
+ SAL_WARN("vcl.gdi", "TextArray too short to recover missing element");
+ else
+ {
+ // now, the difference between the
+ // last and the second last DX array
+ // is the advancement for the last
+ // glyph. Thus, to complete our meta
+ // action's DX array, just add that
+ // difference to last elem and store
+ // in very last.
+ if( nStrLen > 1 )
+ aDXAry[ nStrLen-1 ] = aDXAry[ nStrLen-2 ] + aTmpAry[ nStrLen-1 ] - aTmpAry[ nStrLen-2 ];
+ else
+ aDXAry[ nStrLen-1 ] = aTmpAry[ nStrLen-1 ]; // len=1: 0th position taken to be 0
+ }
+ }
+#ifdef DBG_UTIL
+ else
+ OSL_FAIL("More than one DX array element missing on SVM import");
+#endif
+ }
+ }
+ }
+ if ( nUnicodeCommentActionNumber == i )
+ ImplReadUnicodeComment( nUnicodeCommentStreamPos, rIStm, aStr );
+ ClampRange(aStr, nIndex, nLen, &aDXAry);
+ rMtf.AddAction( new MetaTextArrayAction( aPt, aStr, aDXAry, nIndex, nLen ) );
+ }
+
+ if (nActionSize < 24)
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ else
+ rIStm.Seek(nActBegin + nActionSize);
+ }
+ break;
+
+ case GDI_STRETCHTEXT_ACTION:
+ {
+ sal_Int32 nIndex(0), nLen(0), nWidth(0), nTmp(0);
+
+ aSerializer.readPoint(aPt);
+ rIStm.ReadInt32( nIndex ).ReadInt32( nLen ).ReadInt32( nTmp ).ReadInt32( nWidth );
+ if (nTmp > 0)
+ {
+ OString aByteStr = read_uInt8s_ToOString(rIStm, nTmp);
+ sal_uInt8 nTerminator = 0;
+ rIStm.ReadUChar( nTerminator );
+ SAL_WARN_IF( nTerminator != 0, "vcl.gdi", "expected string to be NULL terminated" );
+
+ OUString aStr(OStringToOUString(aByteStr, eActualCharSet));
+ if ( nUnicodeCommentActionNumber == i )
+ ImplReadUnicodeComment( nUnicodeCommentStreamPos, rIStm, aStr );
+ ClampRange(aStr, nIndex, nLen);
+ rMtf.AddAction( new MetaStretchTextAction( aPt, nWidth, aStr, nIndex, nLen ) );
+ }
+
+ if (nActionSize < 28)
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ else
+ rIStm.Seek(nActBegin + nActionSize);
+ }
+ break;
+
+ case GDI_BITMAP_ACTION:
+ {
+ Bitmap aBmp;
+
+ aSerializer.readPoint(aPt);
+ ReadDIB(aBmp, rIStm, true);
+ rMtf.AddAction( new MetaBmpAction( aPt, aBmp ) );
+ }
+ break;
+
+ case GDI_BITMAPSCALE_ACTION:
+ {
+ Bitmap aBmp;
+
+ aSerializer.readPoint(aPt);
+ aSerializer.readSize(aSz);
+ ReadDIB(aBmp, rIStm, true);
+ rMtf.AddAction( new MetaBmpScaleAction( aPt, aSz, aBmp ) );
+ }
+ break;
+
+ case GDI_BITMAPSCALEPART_ACTION:
+ {
+ Bitmap aBmp;
+ Size aSz2;
+
+ aSerializer.readPoint(aPt);
+ aSerializer.readSize(aSz);
+ aSerializer.readPoint(aPt1);
+ aSerializer.readSize(aSz2);
+ ReadDIB(aBmp, rIStm, true);
+ rMtf.AddAction( new MetaBmpScalePartAction( aPt, aSz, aPt1, aSz2, aBmp ) );
+ }
+ break;
+
+ case GDI_PEN_ACTION:
+ {
+ ImplReadColor( rIStm, aActionColor );
+
+ sal_Int32 nPenWidth(0);
+ sal_Int16 nPenStyle(0);
+ rIStm.ReadInt32( nPenWidth ).ReadInt16( nPenStyle );
+
+ aLineInfo.SetStyle( nPenStyle ? LineStyle::Solid : LineStyle::NONE );
+ aLineInfo.SetWidth( nPenWidth );
+ bFatLine = nPenStyle && !aLineInfo.IsDefault();
+
+ rMtf.AddAction( new MetaLineColorAction( aActionColor, nPenStyle != 0 ) );
+ }
+ break;
+
+ case GDI_FILLBRUSH_ACTION:
+ {
+ ImplReadColor( rIStm, aActionColor );
+ rIStm.SeekRel( 6 );
+ sal_Int16 nBrushStyle(0);
+ rIStm.ReadInt16( nBrushStyle );
+ rMtf.AddAction( new MetaFillColorAction( aActionColor, nBrushStyle != 0 ) );
+ rIStm.SeekRel( 2 );
+ }
+ break;
+
+ case GDI_MAPMODE_ACTION:
+ {
+ if (ImplReadMapMode(rIStm, aMapMode))
+ {
+ rMtf.AddAction(new MetaMapModeAction(aMapMode));
+
+ // #106172# Track font relevant data in shadow VDev
+ aFontVDev->SetMapMode(aMapMode);
+ };
+ }
+ break;
+
+ case GDI_CLIPREGION_ACTION:
+ {
+ vcl::Region aRegion;
+ bool bClip = false;
+
+ sal_Int16 nRegType(0);
+ sal_Int16 bIntersect(0);
+ rIStm.ReadInt16( nRegType ).ReadInt16( bIntersect );
+ ImplReadRect( rIStm, aRect );
+
+ switch( nRegType )
+ {
+ case 0:
+ break;
+
+ case 1:
+ {
+ tools::Rectangle aRegRect;
+
+ ImplReadRect( rIStm, aRegRect );
+ aRegion = vcl::Region( aRegRect );
+ bClip = true;
+ }
+ break;
+
+ case 2:
+ {
+ if (ImplReadPoly(rIStm, aActionPoly))
+ {
+ aRegion = vcl::Region( aActionPoly );
+ bClip = true;
+ }
+ }
+ break;
+
+ case 3:
+ {
+ bool bSuccess = true;
+ tools::PolyPolygon aPolyPoly;
+ sal_Int32 nPolyCount32(0);
+ rIStm.ReadInt32(nPolyCount32);
+ sal_uInt16 nPolyCount(nPolyCount32);
+
+ for (sal_uInt16 j = 0; j < nPolyCount && rIStm.good(); ++j)
+ {
+ if (!ImplReadPoly(rIStm, aActionPoly))
+ {
+ bSuccess = false;
+ break;
+ }
+ aPolyPoly.Insert(aActionPoly);
+ }
+
+ if (bSuccess)
+ {
+ aRegion = vcl::Region( aPolyPoly );
+ bClip = true;
+ }
+ }
+ break;
+ }
+
+ if( bIntersect )
+ aRegion.Intersect( aRect );
+
+ rMtf.AddAction( new MetaClipRegionAction( aRegion, bClip ) );
+ }
+ break;
+
+ case GDI_MOVECLIPREGION_ACTION:
+ {
+ sal_Int32 nTmp(0), nTmp1(0);
+ rIStm.ReadInt32( nTmp ).ReadInt32( nTmp1 );
+ rMtf.AddAction( new MetaMoveClipRegionAction( nTmp, nTmp1 ) );
+ }
+ break;
+
+ case GDI_ISECTCLIPREGION_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+ rMtf.AddAction( new MetaISectRectClipRegionAction( aRect ) );
+ }
+ break;
+
+ case GDI_RASTEROP_ACTION:
+ {
+ RasterOp eRasterOp;
+
+ sal_Int16 nRasterOp(0);
+ rIStm.ReadInt16( nRasterOp );
+
+ switch( nRasterOp )
+ {
+ case 1:
+ eRasterOp = RasterOp::Invert;
+ break;
+
+ case 4:
+ case 5:
+ eRasterOp = RasterOp::Xor;
+ break;
+
+ default:
+ eRasterOp = RasterOp::OverPaint;
+ break;
+ }
+
+ rMtf.AddAction( new MetaRasterOpAction( eRasterOp ) );
+ }
+ break;
+
+ case GDI_PUSH_ACTION:
+ {
+ aLIStack.push(aLineInfo);
+ rMtf.AddAction( new MetaPushAction( vcl::PushFlags::ALL ) );
+
+ // #106172# Track font relevant data in shadow VDev
+ aFontVDev->Push();
+ }
+ break;
+
+ case GDI_POP_ACTION:
+ {
+
+ std::optional<LineInfo> xLineInfo;
+ if (!aLIStack.empty())
+ {
+ xLineInfo = std::move(aLIStack.top());
+ aLIStack.pop();
+ }
+
+ // restore line info
+ if (xLineInfo)
+ {
+ aLineInfo = *xLineInfo;
+ xLineInfo.reset();
+ bFatLine = ( LineStyle::NONE != aLineInfo.GetStyle() ) && !aLineInfo.IsDefault();
+ }
+
+ rMtf.AddAction( new MetaPopAction() );
+
+ // #106172# Track font relevant data in shadow VDev
+ aFontVDev->Pop();
+ }
+ break;
+
+ case GDI_GRADIENT_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+
+ sal_Int16 nStyle(0);
+ rIStm.ReadInt16( nStyle );
+
+ Color aStartCol, aEndCol;
+ ImplReadColor( rIStm, aStartCol );
+ ImplReadColor( rIStm, aEndCol );
+
+ sal_Int16 nAngle(0), nBorder(0), nOfsX(0), nOfsY(0), nIntensityStart(0), nIntensityEnd(0);
+ rIStm.ReadInt16( nAngle ).ReadInt16( nBorder ).ReadInt16( nOfsX ).ReadInt16( nOfsY ).ReadInt16( nIntensityStart ).ReadInt16( nIntensityEnd );
+
+ Gradient aGrad( static_cast<GradientStyle>(nStyle), aStartCol, aEndCol );
+
+ aGrad.SetAngle( Degree10(nAngle) );
+ aGrad.SetBorder( nBorder );
+ aGrad.SetOfsX( nOfsX );
+ aGrad.SetOfsY( nOfsY );
+ aGrad.SetStartIntensity( nIntensityStart );
+ aGrad.SetEndIntensity( nIntensityEnd );
+ rMtf.AddAction( new MetaGradientAction( aRect, aGrad ) );
+ }
+ break;
+
+ case GDI_TRANSPARENT_COMMENT:
+ {
+ tools::PolyPolygon aPolyPoly;
+ sal_Int32 nFollowingActionCount(0);
+ sal_Int16 nTrans(0);
+
+ ReadPolyPolygon( rIStm, aPolyPoly );
+ rIStm.ReadInt16( nTrans ).ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaTransparentAction( aPolyPoly, nTrans ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_FLOATTRANSPARENT_COMMENT:
+ {
+ GDIMetaFile aMtf;
+ Point aPos;
+ Size aSize;
+ Gradient aGradient;
+ sal_Int32 nFollowingActionCount(0);
+
+ SvmReader aReader( rIStm );
+ aReader.Read( aMtf );
+ aSerializer.readPoint(aPos);
+ aSerializer.readSize(aSize);
+ aSerializer.readGradient(aGradient);
+ rIStm.ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaFloatTransparentAction( aMtf, aPos, aSize, aGradient ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_HATCH_COMMENT:
+ {
+ tools::PolyPolygon aPolyPoly;
+ Hatch aHatch;
+ sal_Int32 nFollowingActionCount(0);
+
+ ReadPolyPolygon( rIStm, aPolyPoly );
+ ReadHatch( rIStm, aHatch );
+ rIStm.ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaHatchAction( aPolyPoly, aHatch ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_REFPOINT_COMMENT:
+ {
+ Point aRefPoint;
+ bool bSet(false);
+ sal_Int32 nFollowingActionCount(0);
+
+ aSerializer.readPoint(aRefPoint);
+ rIStm.ReadCharAsBool( bSet ).ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaRefPointAction( aRefPoint, bSet ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+
+ // #106172# Track font relevant data in shadow VDev
+ if( bSet )
+ aFontVDev->SetRefPoint( aRefPoint );
+ else
+ aFontVDev->SetRefPoint();
+ }
+ break;
+
+ case GDI_TEXTLINECOLOR_COMMENT:
+ {
+ Color aColor;
+ bool bSet(false);
+ sal_Int32 nFollowingActionCount(0);
+
+ aSerializer.readColor(aColor);
+ rIStm.ReadCharAsBool( bSet ).ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaTextLineColorAction( aColor, bSet ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_TEXTLINE_COMMENT:
+ {
+ Point aStartPt;
+ sal_Int32 nWidth(0);
+ sal_uInt32 nStrikeout(0);
+ sal_uInt32 nUnderline(0);
+ sal_Int32 nFollowingActionCount(0);
+
+ aSerializer.readPoint(aStartPt);
+ rIStm.ReadInt32(nWidth ).ReadUInt32(nStrikeout).ReadUInt32(nUnderline).ReadInt32(nFollowingActionCount);
+ ImplSkipActions(rIStm, nFollowingActionCount);
+ rMtf.AddAction( new MetaTextLineAction( aStartPt, nWidth,
+ static_cast<FontStrikeout>(nStrikeout & SAL_MAX_ENUM),
+ static_cast<FontLineStyle>(nUnderline & SAL_MAX_ENUM),
+ LINESTYLE_NONE ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_GRADIENTEX_COMMENT:
+ {
+ tools::PolyPolygon aPolyPoly;
+ Gradient aGradient;
+ sal_Int32 nFollowingActionCount(0);
+
+ ReadPolyPolygon( rIStm, aPolyPoly );
+ aSerializer.readGradient(aGradient);
+ rIStm.ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaGradientExAction( aPolyPoly, aGradient ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_COMMENT_COMMENT:
+ {
+ std::vector<sal_uInt8> aData;
+
+ OString aComment = read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm);
+ sal_Int32 nValue(0);
+ sal_uInt32 nDataSize(0);
+ rIStm.ReadInt32(nValue).ReadUInt32(nDataSize);
+
+ if (nDataSize)
+ {
+ const size_t nMaxPossibleData = rIStm.remainingSize();
+ if (nDataSize > nMaxPossibleActions)
+ {
+ SAL_WARN("vcl.gdi", "svm record claims to have: " << nDataSize << " data, but only " << nMaxPossibleData << " possible");
+ nDataSize = nMaxPossibleActions;
+ }
+ aData.resize(nDataSize);
+ nDataSize = rIStm.ReadBytes(aData.data(), nDataSize);
+ }
+
+ sal_Int32 nFollowingActionCount(0);
+ rIStm.ReadInt32(nFollowingActionCount);
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction(new MetaCommentAction(aComment, nValue, aData.data(), nDataSize));
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_UNICODE_COMMENT:
+ {
+ nUnicodeCommentActionNumber = i + 1;
+ nUnicodeCommentStreamPos = rIStm.Tell() - 6;
+ if (nActionSize < 4)
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ else
+ rIStm.SeekRel(nActionSize - 4);
+ }
+ break;
+
+ default:
+ if (nActionSize < 4)
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ else
+ rIStm.SeekRel(nActionSize - 4);
+ break;
+ }
+ }
+
+ rIStm.SetEndian( nOldFormat );
+}
+
+bool TestImportSVM(SvStream& rStream)
+{
+ GDIMetaFile aGDIMetaFile;
+ SvmReader aReader(rStream);
+ aReader.Read(aGDIMetaFile);
+ ScopedVclPtrInstance<VirtualDevice> aVDev;
+ aVDev->SetTextRenderModeForResolutionIndependentLayout(true);
+ try
+ {
+ aGDIMetaFile.Play(*aVDev);
+ }
+ catch (const boost::bad_rational&)
+ {
+ return false;
+ }
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/svm/SvmConverter.hxx b/vcl/source/filter/svm/SvmConverter.hxx
new file mode 100644
index 000000000..23185dc04
--- /dev/null
+++ b/vcl/source/filter/svm/SvmConverter.hxx
@@ -0,0 +1,92 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_INC_SVMCONVERTER_HXX
+#define INCLUDED_VCL_INC_SVMCONVERTER_HXX
+
+#include <vcl/dllapi.h>
+#include <vcl/gdimtf.hxx>
+
+#define GDI_PIXEL_ACTION 1
+#define GDI_POINT_ACTION 2
+#define GDI_LINE_ACTION 3
+#define GDI_RECT_ACTION 4
+#define GDI_ELLIPSE_ACTION 5
+#define GDI_ARC_ACTION 6
+#define GDI_PIE_ACTION 7
+#define GDI_INVERTRECT_ACTION 8
+#define GDI_HIGHLIGHTRECT_ACTION 9
+#define GDI_POLYLINE_ACTION 10
+#define GDI_POLYGON_ACTION 11
+#define GDI_POLYPOLYGON_ACTION 12
+#define GDI_TEXT_ACTION 13
+#define GDI_TEXTARRAY_ACTION 14
+#define GDI_STRETCHTEXT_ACTION 15
+#define GDI_BITMAP_ACTION 17
+#define GDI_BITMAPSCALE_ACTION 18
+#define GDI_PEN_ACTION 19
+#define GDI_FONT_ACTION 20
+#define GDI_FILLBRUSH_ACTION 22
+#define GDI_MAPMODE_ACTION 23
+#define GDI_CLIPREGION_ACTION 24
+#define GDI_RASTEROP_ACTION 25
+#define GDI_PUSH_ACTION 26
+#define GDI_POP_ACTION 27
+#define GDI_MOVECLIPREGION_ACTION 28
+#define GDI_ISECTCLIPREGION_ACTION 29
+#define GDI_BITMAPSCALEPART_ACTION 32
+#define GDI_GRADIENT_ACTION 33
+
+#define GDI_TRANSPARENT_COMMENT 1024
+#define GDI_HATCH_COMMENT 1025
+#define GDI_REFPOINT_COMMENT 1026
+#define GDI_TEXTLINECOLOR_COMMENT 1027
+#define GDI_TEXTLINE_COMMENT 1028
+#define GDI_FLOATTRANSPARENT_COMMENT 1029
+#define GDI_GRADIENTEX_COMMENT 1030
+#define GDI_COMMENT_COMMENT 1031
+#define GDI_UNICODE_COMMENT 1032
+
+#define GDI_LINEJOIN_ACTION 1033
+#define GDI_EXTENDEDPOLYGON_ACTION 1034
+#define GDI_LINEDASHDOT_ACTION 1035
+
+#define GDI_LINECAP_ACTION 1036
+
+/**
+ * Converts old SVGDI aka SVM1 format data to current VCLMTF aka SVM2 format metafile data.
+ */
+class SVMConverter
+{
+private:
+ static void ImplConvertFromSVM1( SvStream& rIStm, GDIMetaFile& rMtf );
+
+public:
+ SVMConverter( SvStream& rIStm, GDIMetaFile& rMtf );
+
+private:
+ SVMConverter( const SVMConverter& ) = delete;
+ SVMConverter& operator=( const SVMConverter& ) = delete;
+};
+
+extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportSVM(SvStream& rStream);
+
+#endif // INCLUDED_VCL_INC_SVMCONVERTER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/svm/SvmReader.cxx b/vcl/source/filter/svm/SvmReader.cxx
new file mode 100644
index 000000000..f02451ea3
--- /dev/null
+++ b/vcl/source/filter/svm/SvmReader.cxx
@@ -0,0 +1,1438 @@
+/* -*- 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/log.hxx>
+#include <osl/thread.h>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+
+#include <vcl/filter/SvmReader.hxx>
+#include <vcl/TypeSerializer.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+
+#include "SvmConverter.hxx"
+
+namespace
+{
+class DepthGuard
+{
+private:
+ ImplMetaReadData& m_rData;
+ rtl_TextEncoding m_eOrigCharSet;
+
+public:
+ DepthGuard(ImplMetaReadData& rData, SvStream const& rIStm)
+ : m_rData(rData)
+ , m_eOrigCharSet(m_rData.meActualCharSet)
+ {
+ ++m_rData.mnParseDepth;
+ m_rData.meActualCharSet = rIStm.GetStreamCharSet();
+ }
+ bool TooDeep() const { return m_rData.mnParseDepth > 1024; }
+ ~DepthGuard()
+ {
+ --m_rData.mnParseDepth;
+ m_rData.meActualCharSet = m_eOrigCharSet;
+ }
+};
+}
+
+SvmReader::SvmReader(SvStream& rIStm)
+ : mrStream(rIStm)
+{
+}
+
+SvStream& SvmReader::Read(GDIMetaFile& rMetaFile, ImplMetaReadData* pData)
+{
+ if (mrStream.GetError())
+ {
+ SAL_WARN("vcl.gdi", "Stream error: " << mrStream.GetError());
+ return mrStream;
+ }
+
+ sal_uInt64 nStmPos = mrStream.Tell();
+ SvStreamEndian nOldFormat = mrStream.GetEndian();
+
+ mrStream.SetEndian(SvStreamEndian::LITTLE);
+
+ try
+ {
+ char aId[7];
+ aId[0] = 0;
+ aId[6] = 0;
+ mrStream.ReadBytes(aId, 6);
+ if (mrStream.good() && !strcmp(aId, "VCLMTF"))
+ {
+ // new format
+ sal_uInt32 nStmCompressMode = 0;
+ sal_uInt32 nCount = 0;
+ std::unique_ptr<VersionCompatRead> pCompat(new VersionCompatRead(mrStream));
+
+ mrStream.ReadUInt32(nStmCompressMode);
+ TypeSerializer aSerializer(mrStream);
+ MapMode aMapMode;
+ aSerializer.readMapMode(aMapMode);
+ rMetaFile.SetPrefMapMode(aMapMode);
+ Size aSize;
+ aSerializer.readSize(aSize);
+ rMetaFile.SetPrefSize(aSize);
+ mrStream.ReadUInt32(nCount);
+
+ pCompat.reset(); // destructor writes stuff into the header
+
+ std::unique_ptr<ImplMetaReadData> xReadData;
+ if (!pData)
+ {
+ xReadData.reset(new ImplMetaReadData);
+ pData = xReadData.get();
+ }
+ DepthGuard aDepthGuard(*pData, mrStream);
+
+ if (aDepthGuard.TooDeep())
+ throw std::runtime_error("too much recursion");
+
+ for (sal_uInt32 nAction = 0; (nAction < nCount) && !mrStream.eof(); nAction++)
+ {
+ rtl::Reference<MetaAction> pAction = MetaActionHandler(pData);
+ if (pAction)
+ {
+ if (pAction->GetType() == MetaActionType::COMMENT)
+ {
+ MetaCommentAction* pCommentAct
+ = static_cast<MetaCommentAction*>(pAction.get());
+
+ if (pCommentAct->GetComment() == "EMF_PLUS")
+ rMetaFile.UseCanvas(true);
+ }
+ rMetaFile.AddAction(pAction);
+ }
+ }
+ }
+ else
+ {
+ mrStream.Seek(nStmPos);
+ SVMConverter(mrStream, rMetaFile);
+ }
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl", "GDIMetaFile exception during load");
+ mrStream.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ };
+
+ // check for errors
+ if (mrStream.GetError())
+ {
+ rMetaFile.Clear();
+ mrStream.Seek(nStmPos);
+ }
+
+ mrStream.SetEndian(nOldFormat);
+ return mrStream;
+}
+
+rtl::Reference<MetaAction> SvmReader::MetaActionHandler(ImplMetaReadData* pData)
+{
+ rtl::Reference<MetaAction> pAction;
+ sal_uInt16 nTmp = 0;
+ mrStream.ReadUInt16(nTmp);
+ MetaActionType nType = static_cast<MetaActionType>(nTmp);
+
+ switch (nType)
+ {
+ case MetaActionType::NONE:
+ return DefaultHandler();
+ case MetaActionType::PIXEL:
+ return PixelHandler();
+ case MetaActionType::POINT:
+ return PointHandler();
+ case MetaActionType::LINE:
+ return LineHandler();
+ case MetaActionType::RECT:
+ return RectHandler();
+ case MetaActionType::ROUNDRECT:
+ return RoundRectHandler();
+ case MetaActionType::ELLIPSE:
+ return EllipseHandler();
+ case MetaActionType::ARC:
+ return ArcHandler();
+ case MetaActionType::PIE:
+ return PieHandler();
+ case MetaActionType::CHORD:
+ return ChordHandler();
+ case MetaActionType::POLYLINE:
+ return PolyLineHandler();
+ case MetaActionType::POLYGON:
+ return PolygonHandler();
+ case MetaActionType::POLYPOLYGON:
+ return PolyPolygonHandler();
+ case MetaActionType::TEXT:
+ return TextHandler(pData);
+ case MetaActionType::TEXTARRAY:
+ return TextArrayHandler(pData);
+ case MetaActionType::STRETCHTEXT:
+ return StretchTextHandler(pData);
+ case MetaActionType::TEXTRECT:
+ return TextRectHandler(pData);
+ case MetaActionType::TEXTLINE:
+ return TextLineHandler();
+ case MetaActionType::BMP:
+ return BmpHandler();
+ case MetaActionType::BMPSCALE:
+ return BmpScaleHandler();
+ case MetaActionType::BMPSCALEPART:
+ return BmpScalePartHandler();
+ case MetaActionType::BMPEX:
+ return BmpExHandler();
+ case MetaActionType::BMPEXSCALE:
+ return BmpExScaleHandler();
+ case MetaActionType::BMPEXSCALEPART:
+ return BmpExScalePartHandler();
+ case MetaActionType::MASK:
+ return MaskHandler();
+ case MetaActionType::MASKSCALE:
+ return MaskScaleHandler();
+ case MetaActionType::MASKSCALEPART:
+ return MaskScalePartHandler();
+ case MetaActionType::GRADIENT:
+ return GradientHandler();
+ case MetaActionType::GRADIENTEX:
+ return GradientExHandler();
+ case MetaActionType::HATCH:
+ return HatchHandler();
+ case MetaActionType::WALLPAPER:
+ return WallpaperHandler();
+ case MetaActionType::CLIPREGION:
+ return ClipRegionHandler();
+ case MetaActionType::ISECTRECTCLIPREGION:
+ return ISectRectClipRegionHandler();
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ return ISectRegionClipRegionHandler();
+ case MetaActionType::MOVECLIPREGION:
+ return MoveClipRegionHandler();
+ case MetaActionType::LINECOLOR:
+ return LineColorHandler();
+ case MetaActionType::FILLCOLOR:
+ return FillColorHandler();
+ case MetaActionType::TEXTCOLOR:
+ return TextColorHandler();
+ case MetaActionType::TEXTFILLCOLOR:
+ return TextFillColorHandler();
+ case MetaActionType::TEXTLINECOLOR:
+ return TextLineColorHandler();
+ case MetaActionType::OVERLINECOLOR:
+ return OverlineColorHandler();
+ case MetaActionType::TEXTALIGN:
+ return TextAlignHandler();
+ case MetaActionType::MAPMODE:
+ return MapModeHandler();
+ case MetaActionType::FONT:
+ return FontHandler(pData);
+ case MetaActionType::PUSH:
+ return PushHandler();
+ case MetaActionType::POP:
+ return PopHandler();
+ case MetaActionType::RASTEROP:
+ return RasterOpHandler();
+ case MetaActionType::Transparent:
+ return TransparentHandler();
+ case MetaActionType::FLOATTRANSPARENT:
+ return FloatTransparentHandler(pData);
+ case MetaActionType::EPS:
+ return EPSHandler();
+ case MetaActionType::REFPOINT:
+ return RefPointHandler();
+ case MetaActionType::COMMENT:
+ return CommentHandler();
+ case MetaActionType::LAYOUTMODE:
+ return LayoutModeHandler();
+ case MetaActionType::TEXTLANGUAGE:
+ return TextLanguageHandler();
+
+ default:
+ {
+ VersionCompatRead aCompat(mrStream);
+ }
+ break;
+ }
+
+ return pAction;
+}
+
+void SvmReader::ReadColor(Color& rColor)
+{
+ sal_uInt32 nTmp(0);
+ mrStream.ReadUInt32(nTmp);
+ rColor = ::Color(ColorTransparency, nTmp);
+}
+
+rtl::Reference<MetaAction> SvmReader::LineColorHandler()
+{
+ rtl::Reference<MetaLineColorAction> pAction(new MetaLineColorAction);
+
+ VersionCompatRead aCompat(mrStream);
+ Color aColor;
+ ReadColor(aColor);
+ bool aBool(false);
+ mrStream.ReadCharAsBool(aBool);
+
+ pAction->SetSetting(aBool);
+ pAction->SetColor(aColor);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::FillColorHandler()
+{
+ rtl::Reference<MetaFillColorAction> pAction(new MetaFillColorAction);
+
+ VersionCompatRead aCompat(mrStream);
+
+ Color aColor;
+ ReadColor(aColor);
+ bool aBool(false);
+ mrStream.ReadCharAsBool(aBool);
+
+ pAction->SetColor(aColor);
+ pAction->SetSetting(aBool);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::RectHandler()
+{
+ rtl::Reference<MetaRectAction> pAction(new MetaRectAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+
+ tools::Rectangle aRectangle;
+ aSerializer.readRectangle(aRectangle);
+ pAction->SetRect(aRectangle);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::PointHandler()
+{
+ rtl::Reference<MetaPointAction> pAction(new MetaPointAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+ pAction->SetPoint(aPoint);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::PixelHandler()
+{
+ rtl::Reference<MetaPixelAction> pAction(new MetaPixelAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+ Color aColor;
+ ReadColor(aColor);
+
+ pAction->SetPoint(aPoint);
+ pAction->SetColor(aColor);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::LineHandler()
+{
+ rtl::Reference<MetaLineAction> pAction(new MetaLineAction);
+
+ VersionCompatRead aCompat(mrStream);
+
+ // Version 1
+ TypeSerializer aSerializer(mrStream);
+ Point aPoint;
+ Point aEndPoint;
+ aSerializer.readPoint(aPoint);
+ aSerializer.readPoint(aEndPoint);
+
+ pAction->SetStartPoint(aPoint);
+ pAction->SetEndPoint(aEndPoint);
+
+ // Version 2
+ if (aCompat.GetVersion() >= 2)
+ {
+ LineInfo aLineInfo;
+ ReadLineInfo(mrStream, aLineInfo);
+ pAction->SetLineInfo(aLineInfo);
+ }
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::RoundRectHandler()
+{
+ rtl::Reference<MetaRoundRectAction> pAction(new MetaRoundRectAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+
+ tools::Rectangle aRectangle;
+ aSerializer.readRectangle(aRectangle);
+ sal_uInt32 HorzRound(0);
+ sal_uInt32 VertRound(0);
+ mrStream.ReadUInt32(HorzRound).ReadUInt32(VertRound);
+
+ pAction->SetRect(aRectangle);
+ pAction->SetHorzRound(HorzRound);
+ pAction->SetVertRound(VertRound);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::EllipseHandler()
+{
+ rtl::Reference<MetaEllipseAction> pAction(new MetaEllipseAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+
+ tools::Rectangle aRectangle;
+ aSerializer.readRectangle(aRectangle);
+
+ pAction->SetRect(aRectangle);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::ArcHandler()
+{
+ rtl::Reference<MetaArcAction> pAction(new MetaArcAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+
+ tools::Rectangle aRectangle;
+ aSerializer.readRectangle(aRectangle);
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+ Point aEndPoint;
+ aSerializer.readPoint(aEndPoint);
+
+ pAction->SetRect(aRectangle);
+ pAction->SetStartPoint(aPoint);
+ pAction->SetEndPoint(aEndPoint);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::PieHandler()
+{
+ rtl::Reference<MetaPieAction> pAction(new MetaPieAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+
+ tools::Rectangle aRectangle;
+ aSerializer.readRectangle(aRectangle);
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+ Point aEndPoint;
+ aSerializer.readPoint(aEndPoint);
+
+ pAction->SetRect(aRectangle);
+ pAction->SetStartPoint(aPoint);
+ pAction->SetEndPoint(aEndPoint);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::ChordHandler()
+{
+ rtl::Reference<MetaChordAction> pAction(new MetaChordAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+
+ tools::Rectangle aRectangle;
+ aSerializer.readRectangle(aRectangle);
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+ Point aEndPoint;
+ aSerializer.readPoint(aEndPoint);
+
+ pAction->SetRect(aRectangle);
+ pAction->SetStartPoint(aPoint);
+ pAction->SetEndPoint(aEndPoint);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::PolyLineHandler()
+{
+ rtl::Reference<MetaPolyLineAction> pAction(new MetaPolyLineAction);
+
+ VersionCompatRead aCompat(mrStream);
+
+ // Version 1
+ tools::Polygon aPolygon;
+ ReadPolygon(mrStream, aPolygon);
+
+ // Version 2
+ if (aCompat.GetVersion() >= 2)
+ {
+ LineInfo aLineInfo;
+ ReadLineInfo(mrStream, aLineInfo);
+ pAction->SetLineInfo(aLineInfo);
+ }
+ if (aCompat.GetVersion() >= 3)
+ {
+ sal_uInt8 bHasPolyFlags(0);
+ mrStream.ReadUChar(bHasPolyFlags);
+ if (bHasPolyFlags)
+ aPolygon.Read(mrStream);
+ }
+ pAction->SetPolygon(aPolygon);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::PolygonHandler()
+{
+ rtl::Reference<MetaPolygonAction> pAction(new MetaPolygonAction);
+
+ VersionCompatRead aCompat(mrStream);
+
+ tools::Polygon aPolygon;
+ ReadPolygon(mrStream, aPolygon); // Version 1
+
+ if (aCompat.GetVersion() >= 2) // Version 2
+ {
+ sal_uInt8 bHasPolyFlags(0);
+ mrStream.ReadUChar(bHasPolyFlags);
+ if (bHasPolyFlags)
+ aPolygon.Read(mrStream);
+ }
+
+ pAction->SetPolygon(aPolygon);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::PolyPolygonHandler()
+{
+ rtl::Reference<MetaPolyPolygonAction> pAction(new MetaPolyPolygonAction);
+
+ VersionCompatRead aCompat(mrStream);
+ tools::PolyPolygon aPolyPolygon;
+ ReadPolyPolygon(mrStream, aPolyPolygon); // Version 1
+
+ if (aCompat.GetVersion() < 2) // Version 2
+ {
+ pAction->SetPolyPolygon(aPolyPolygon);
+ return pAction;
+ }
+
+ sal_uInt16 nNumberOfComplexPolygons(0);
+ mrStream.ReadUInt16(nNumberOfComplexPolygons);
+ const size_t nMinRecordSize = sizeof(sal_uInt16);
+ const size_t nMaxRecords = mrStream.remainingSize() / nMinRecordSize;
+ if (nNumberOfComplexPolygons > nMaxRecords)
+ {
+ SAL_WARN("vcl.gdi", "Parsing error: " << nMaxRecords << " max possible entries, but "
+ << nNumberOfComplexPolygons
+ << " claimed, truncating");
+ nNumberOfComplexPolygons = nMaxRecords;
+ }
+ for (sal_uInt16 i = 0; i < nNumberOfComplexPolygons; ++i)
+ {
+ sal_uInt16 nIndex(0);
+ mrStream.ReadUInt16(nIndex);
+ tools::Polygon aPoly;
+ aPoly.Read(mrStream);
+ if (nIndex >= aPolyPolygon.Count())
+ {
+ SAL_WARN("vcl.gdi", "svm contains polygon index " << nIndex
+ << " outside possible range "
+ << aPolyPolygon.Count());
+ continue;
+ }
+ aPolyPolygon.Replace(aPoly, nIndex);
+ }
+
+ pAction->SetPolyPolygon(aPolyPolygon);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::TextHandler(const ImplMetaReadData* pData)
+{
+ rtl::Reference<MetaTextAction> pAction(new MetaTextAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+ OUString aStr = mrStream.ReadUniOrByteString(pData->meActualCharSet);
+ sal_uInt16 nTmpIndex(0);
+ mrStream.ReadUInt16(nTmpIndex);
+ sal_uInt16 nTmpLen(0);
+ mrStream.ReadUInt16(nTmpLen);
+
+ pAction->SetPoint(aPoint);
+
+ if (aCompat.GetVersion() >= 2) // Version 2
+ aStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(mrStream);
+
+ if (nTmpIndex > aStr.getLength())
+ {
+ SAL_WARN("vcl.gdi", "inconsistent offset");
+ nTmpIndex = aStr.getLength();
+ }
+
+ if (nTmpLen > aStr.getLength() - nTmpIndex)
+ {
+ SAL_WARN("vcl.gdi", "inconsistent len");
+ nTmpLen = aStr.getLength() - nTmpIndex;
+ }
+
+ pAction->SetIndex(nTmpIndex);
+ pAction->SetLen(nTmpLen);
+
+ pAction->SetText(aStr);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::TextArrayHandler(const ImplMetaReadData* pData)
+{
+ rtl::Reference<MetaTextArrayAction> pAction(new MetaTextArrayAction);
+
+ std::vector<sal_Int32> aArray;
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+ pAction->SetPoint(aPoint);
+
+ OUString aStr = mrStream.ReadUniOrByteString(pData->meActualCharSet);
+ pAction->SetText(aStr);
+
+ sal_uInt16 nTmpIndex(0);
+ mrStream.ReadUInt16(nTmpIndex);
+
+ sal_uInt16 nTmpLen(0);
+ mrStream.ReadUInt16(nTmpLen);
+
+ sal_Int32 nAryLen(0);
+ mrStream.ReadInt32(nAryLen);
+
+ if (nTmpLen > aStr.getLength() - nTmpIndex)
+ {
+ SAL_WARN("vcl.gdi", "inconsistent offset and len");
+ pAction->SetIndex(0);
+ pAction->SetLen(aStr.getLength());
+ return pAction;
+ }
+
+ pAction->SetIndex(nTmpIndex);
+ pAction->SetLen(nTmpLen);
+
+ if (nAryLen)
+ {
+ // #i9762#, #106172# Ensure that DX array is at least mnLen entries long
+ if (nTmpLen >= nAryLen)
+ {
+ try
+ {
+ aArray.resize(nTmpLen);
+ sal_Int32 i;
+ sal_Int32 val(0);
+ for (i = 0; i < nAryLen; i++)
+ {
+ mrStream.ReadInt32(val);
+ aArray[i] = val;
+ }
+ // #106172# setup remainder
+ for (; i < nTmpLen; i++)
+ aArray[i] = 0;
+ }
+ catch (std::bad_alloc&)
+ {
+ }
+ }
+ else
+ {
+ return pAction;
+ }
+ }
+
+ if (aCompat.GetVersion() >= 2) // Version 2
+ {
+ aStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(mrStream);
+ pAction->SetText(aStr);
+
+ if (nTmpLen > aStr.getLength() - nTmpIndex)
+ {
+ SAL_WARN("vcl.gdi", "inconsistent offset and len");
+ pAction->SetIndex(0);
+ pAction->SetLen(aStr.getLength());
+ aArray.clear();
+ }
+ }
+
+ if (!aArray.empty())
+ pAction->SetDXArray(std::move(aArray));
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::StretchTextHandler(const ImplMetaReadData* pData)
+{
+ rtl::Reference<MetaStretchTextAction> pAction(new MetaStretchTextAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+ OUString aStr = mrStream.ReadUniOrByteString(pData->meActualCharSet);
+ sal_uInt32 nTmpWidth(0);
+ mrStream.ReadUInt32(nTmpWidth);
+ sal_uInt16 nTmpIndex(0);
+ mrStream.ReadUInt16(nTmpIndex);
+ sal_uInt16 nTmpLen(0);
+ mrStream.ReadUInt16(nTmpLen);
+
+ pAction->SetPoint(aPoint);
+ pAction->SetWidth(nTmpWidth);
+
+ if (aCompat.GetVersion() >= 2) // Version 2
+ aStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(mrStream);
+
+ if (nTmpIndex > aStr.getLength())
+ {
+ SAL_WARN("vcl.gdi", "inconsistent offset");
+ nTmpIndex = aStr.getLength();
+ }
+
+ if (nTmpLen > aStr.getLength() - nTmpIndex)
+ {
+ SAL_WARN("vcl.gdi", "inconsistent len");
+ nTmpLen = aStr.getLength() - nTmpIndex;
+ }
+
+ pAction->SetIndex(nTmpIndex);
+ pAction->SetLen(nTmpLen);
+
+ pAction->SetText(aStr);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::TextRectHandler(const ImplMetaReadData* pData)
+{
+ rtl::Reference<MetaTextRectAction> pAction(new MetaTextRectAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+
+ tools::Rectangle aRect;
+ aSerializer.readRectangle(aRect);
+ OUString aStr = mrStream.ReadUniOrByteString(pData->meActualCharSet);
+ sal_uInt16 nTmp(0);
+ mrStream.ReadUInt16(nTmp);
+
+ pAction->SetRect(aRect);
+ pAction->SetStyle(static_cast<DrawTextFlags>(nTmp));
+
+ if (aCompat.GetVersion() >= 2) // Version 2
+ aStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(mrStream);
+
+ pAction->SetText(aStr);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::TextLineHandler()
+{
+ rtl::Reference<MetaTextLineAction> pAction(new MetaTextLineAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+
+ Point aPos;
+ aSerializer.readPoint(aPos);
+ sal_Int32 nTempWidth(0);
+ mrStream.ReadInt32(nTempWidth);
+
+ pAction->SetStartPoint(aPos);
+ pAction->SetWidth(nTempWidth);
+
+ sal_uInt32 nTempStrikeout(0);
+ mrStream.ReadUInt32(nTempStrikeout);
+ sal_uInt32 nTempUnderline(0);
+ mrStream.ReadUInt32(nTempUnderline);
+
+ pAction->SetStrikeout(static_cast<FontStrikeout>(nTempStrikeout & SAL_MAX_ENUM));
+ pAction->SetUnderline(static_cast<FontLineStyle>(nTempUnderline & SAL_MAX_ENUM));
+
+ if (aCompat.GetVersion() >= 2)
+ {
+ sal_uInt32 nTempOverline(0);
+ mrStream.ReadUInt32(nTempOverline);
+ pAction->SetOverline(static_cast<FontLineStyle>(nTempOverline & SAL_MAX_ENUM));
+ }
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::BmpHandler()
+{
+ rtl::Reference<MetaBmpAction> pAction(new MetaBmpAction);
+
+ VersionCompatRead aCompat(mrStream);
+ Bitmap aBmp;
+ ReadDIB(aBmp, mrStream, true);
+ TypeSerializer aSerializer(mrStream);
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+
+ pAction->SetBitmap(aBmp);
+ pAction->SetPoint(aPoint);
+
+ return pAction;
+}
+
+namespace
+{
+void sanitizeNegativeSizeDimensions(Size& rSize)
+{
+ if (rSize.Width() < 0)
+ {
+ SAL_WARN("vcl.gdi", "sanitizeNegativeSizeDimensions: negative width");
+ rSize.setWidth(0);
+ }
+
+ if (rSize.Height() < 0)
+ {
+ SAL_WARN("vcl.gdi", "sanitizeNegativeSizeDimensions: negative height");
+ rSize.setHeight(0);
+ }
+}
+}
+
+rtl::Reference<MetaAction> SvmReader::BmpScaleHandler()
+{
+ rtl::Reference<MetaBmpScaleAction> pAction(new MetaBmpScaleAction);
+
+ VersionCompatRead aCompat(mrStream);
+ Bitmap aBmp;
+ ReadDIB(aBmp, mrStream, true);
+ TypeSerializer aSerializer(mrStream);
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+
+ Size aSz;
+ aSerializer.readSize(aSz);
+ sanitizeNegativeSizeDimensions(aSz);
+
+ pAction->SetBitmap(aBmp);
+ pAction->SetPoint(aPoint);
+ pAction->SetSize(aSz);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::BmpScalePartHandler()
+{
+ rtl::Reference<MetaBmpScalePartAction> pAction(new MetaBmpScalePartAction);
+
+ VersionCompatRead aCompat(mrStream);
+ Bitmap aBmp;
+ ReadDIB(aBmp, mrStream, true);
+ TypeSerializer aSerializer(mrStream);
+ Point aDestPoint;
+ aSerializer.readPoint(aDestPoint);
+ Size aDestSize;
+ aSerializer.readSize(aDestSize);
+ Point aSrcPoint;
+ aSerializer.readPoint(aSrcPoint);
+ Size aSrcSize;
+ aSerializer.readSize(aSrcSize);
+
+ pAction->SetBitmap(aBmp);
+ pAction->SetDestPoint(aDestPoint);
+ pAction->SetDestSize(aDestSize);
+ pAction->SetSrcPoint(aSrcPoint);
+ pAction->SetSrcSize(aSrcSize);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::BmpExHandler()
+{
+ rtl::Reference<MetaBmpExAction> pAction(new MetaBmpExAction);
+
+ VersionCompatRead aCompat(mrStream);
+ BitmapEx aBmpEx;
+ ReadDIBBitmapEx(aBmpEx, mrStream);
+ TypeSerializer aSerializer(mrStream);
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+
+ pAction->SetPoint(aPoint);
+ pAction->SetBitmapEx(aBmpEx);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::BmpExScaleHandler()
+{
+ rtl::Reference<MetaBmpExScaleAction> pAction(new MetaBmpExScaleAction);
+
+ VersionCompatRead aCompat(mrStream);
+ BitmapEx aBmpEx;
+ ReadDIBBitmapEx(aBmpEx, mrStream);
+ TypeSerializer aSerializer(mrStream);
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+
+ Size aSize;
+ aSerializer.readSize(aSize);
+ sanitizeNegativeSizeDimensions(aSize);
+
+ pAction->SetBitmapEx(aBmpEx);
+ pAction->SetPoint(aPoint);
+ pAction->SetSize(aSize);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::BmpExScalePartHandler()
+{
+ rtl::Reference<MetaBmpExScalePartAction> pAction(new MetaBmpExScalePartAction);
+
+ VersionCompatRead aCompat(mrStream);
+ BitmapEx aBmpEx;
+ ReadDIBBitmapEx(aBmpEx, mrStream);
+ TypeSerializer aSerializer(mrStream);
+ Point aDstPoint;
+ aSerializer.readPoint(aDstPoint);
+ Size aDstSize;
+ aSerializer.readSize(aDstSize);
+ Point aSrcPoint;
+ aSerializer.readPoint(aSrcPoint);
+ Size aSrcSize;
+ aSerializer.readSize(aSrcSize);
+
+ pAction->SetBitmapEx(aBmpEx);
+ pAction->SetDestPoint(aDstPoint);
+ pAction->SetDestSize(aDstSize);
+ pAction->SetSrcPoint(aSrcPoint);
+ pAction->SetSrcSize(aSrcSize);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::MaskHandler()
+{
+ rtl::Reference<MetaMaskAction> pAction(new MetaMaskAction);
+
+ VersionCompatRead aCompat(mrStream);
+ Bitmap aBmp;
+ ReadDIB(aBmp, mrStream, true);
+ TypeSerializer aSerializer(mrStream);
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+
+ pAction->SetBitmap(aBmp);
+ pAction->SetPoint(aPoint);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::MaskScaleHandler()
+{
+ rtl::Reference<MetaMaskScaleAction> pAction(new MetaMaskScaleAction);
+
+ VersionCompatRead aCompat(mrStream);
+ Bitmap aBmp;
+ ReadDIB(aBmp, mrStream, true);
+ TypeSerializer aSerializer(mrStream);
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+ Size aSize;
+ aSerializer.readSize(aSize);
+
+ pAction->SetBitmap(aBmp);
+ pAction->SetPoint(aPoint);
+ pAction->SetSize(aSize);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::MaskScalePartHandler()
+{
+ rtl::Reference<MetaMaskScalePartAction> pAction(new MetaMaskScalePartAction);
+
+ VersionCompatRead aCompat(mrStream);
+ Bitmap aBmp;
+ ReadDIB(aBmp, mrStream, true);
+ Color aColor;
+ ReadColor(aColor);
+ TypeSerializer aSerializer(mrStream);
+ Point aDstPt;
+ aSerializer.readPoint(aDstPt);
+ Size aDstSz;
+ aSerializer.readSize(aDstSz);
+ Point aSrcPt;
+ aSerializer.readPoint(aSrcPt);
+ Size aSrcSz;
+ aSerializer.readSize(aSrcSz);
+
+ pAction->SetBitmap(aBmp);
+ pAction->SetColor(aColor);
+ pAction->SetDestPoint(aDstPt);
+ pAction->SetDestSize(aDstSz);
+ pAction->SetSrcPoint(aSrcPt);
+ pAction->SetSrcSize(aSrcSz);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::GradientHandler()
+{
+ rtl::Reference<MetaGradientAction> pAction(new MetaGradientAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+
+ tools::Rectangle aRect;
+ aSerializer.readRectangle(aRect);
+ Gradient aGradient;
+ aSerializer.readGradient(aGradient);
+
+ pAction->SetRect(aRect);
+ pAction->SetGradient(aGradient);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::GradientExHandler()
+{
+ rtl::Reference<MetaGradientExAction> pAction(new MetaGradientExAction);
+
+ VersionCompatRead aCompat(mrStream);
+ tools::PolyPolygon aPolyPoly;
+ ReadPolyPolygon(mrStream, aPolyPoly);
+ TypeSerializer aSerializer(mrStream);
+ Gradient aGradient;
+ aSerializer.readGradient(aGradient);
+
+ pAction->SetGradient(aGradient);
+ pAction->SetPolyPolygon(aPolyPoly);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::HatchHandler()
+{
+ rtl::Reference<MetaHatchAction> pAction(new MetaHatchAction);
+
+ VersionCompatRead aCompat(mrStream);
+ tools::PolyPolygon aPolyPoly;
+ ReadPolyPolygon(mrStream, aPolyPoly);
+ Hatch aHatch;
+ ReadHatch(mrStream, aHatch);
+
+ pAction->SetPolyPolygon(aPolyPoly);
+ pAction->SetHatch(aHatch);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::WallpaperHandler()
+{
+ rtl::Reference<MetaWallpaperAction> pAction(new MetaWallpaperAction);
+
+ VersionCompatRead aCompat(mrStream);
+ Wallpaper aWallpaper;
+ ReadWallpaper(mrStream, aWallpaper);
+
+ pAction->SetWallpaper(aWallpaper);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::ClipRegionHandler()
+{
+ rtl::Reference<MetaClipRegionAction> pAction(new MetaClipRegionAction);
+
+ VersionCompatRead aCompat(mrStream);
+ vcl::Region aRegion;
+ ReadRegion(mrStream, aRegion);
+ bool aClip(false);
+ mrStream.ReadCharAsBool(aClip);
+
+ pAction->SetRegion(aRegion);
+ pAction->SetClipping(aClip);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::ISectRectClipRegionHandler()
+{
+ rtl::Reference<MetaISectRectClipRegionAction> pAction(new MetaISectRectClipRegionAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+ tools::Rectangle aRect;
+ aSerializer.readRectangle(aRect);
+
+ pAction->SetRect(aRect);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::ISectRegionClipRegionHandler()
+{
+ rtl::Reference<MetaISectRegionClipRegionAction> pAction(new MetaISectRegionClipRegionAction);
+
+ VersionCompatRead aCompat(mrStream);
+ vcl::Region aRegion;
+ ReadRegion(mrStream, aRegion);
+ pAction->SetRegion(aRegion);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::MoveClipRegionHandler()
+{
+ rtl::Reference<MetaMoveClipRegionAction> pAction(new MetaMoveClipRegionAction);
+
+ VersionCompatRead aCompat(mrStream);
+ sal_Int32 nTmpHM(0), nTmpVM(0);
+ mrStream.ReadInt32(nTmpHM).ReadInt32(nTmpVM);
+
+ pAction->SetHorzMove(nTmpHM);
+ pAction->SetVertMove(nTmpVM);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::TextColorHandler()
+{
+ rtl::Reference<MetaTextColorAction> pAction(new MetaTextColorAction);
+
+ VersionCompatRead aCompat(mrStream);
+ Color aColor;
+ ReadColor(aColor);
+
+ pAction->SetColor(aColor);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::TextFillColorHandler()
+{
+ rtl::Reference<MetaTextFillColorAction> pAction(new MetaTextFillColorAction);
+
+ VersionCompatRead aCompat(mrStream);
+ Color aColor;
+ ReadColor(aColor);
+ bool bSet(false);
+ mrStream.ReadCharAsBool(bSet);
+
+ pAction->SetColor(aColor);
+ pAction->SetSetting(bSet);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::TextLineColorHandler()
+{
+ rtl::Reference<MetaTextLineColorAction> pAction(new MetaTextLineColorAction);
+
+ VersionCompatRead aCompat(mrStream);
+ Color aColor;
+ ReadColor(aColor);
+ bool bSet(false);
+ mrStream.ReadCharAsBool(bSet);
+
+ pAction->SetColor(aColor);
+ pAction->SetSetting(bSet);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::OverlineColorHandler()
+{
+ rtl::Reference<MetaOverlineColorAction> pAction(new MetaOverlineColorAction);
+
+ VersionCompatRead aCompat(mrStream);
+ Color aColor;
+ ReadColor(aColor);
+ bool bSet(false);
+ mrStream.ReadCharAsBool(bSet);
+
+ pAction->SetColor(aColor);
+ pAction->SetSetting(bSet);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::TextAlignHandler()
+{
+ rtl::Reference<MetaTextAlignAction> pAction(new MetaTextAlignAction);
+
+ VersionCompatRead aCompat(mrStream);
+ sal_uInt16 nTmp16(0);
+ mrStream.ReadUInt16(nTmp16);
+
+ pAction->SetTextAlign(static_cast<TextAlign>(nTmp16));
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::MapModeHandler()
+{
+ rtl::Reference<MetaMapModeAction> pAction(new MetaMapModeAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+ MapMode aMapMode;
+ aSerializer.readMapMode(aMapMode);
+
+ pAction->SetMapMode(aMapMode);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::FontHandler(ImplMetaReadData* pData)
+{
+ rtl::Reference<MetaFontAction> pAction(new MetaFontAction);
+
+ VersionCompatRead aCompat(mrStream);
+ vcl::Font aFont;
+ ReadFont(mrStream, aFont);
+ pData->meActualCharSet = aFont.GetCharSet();
+ if (pData->meActualCharSet == RTL_TEXTENCODING_DONTKNOW)
+ pData->meActualCharSet = osl_getThreadTextEncoding();
+
+ pAction->SetFont(aFont);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::PushHandler()
+{
+ rtl::Reference<MetaPushAction> pAction(new MetaPushAction);
+
+ VersionCompatRead aCompat(mrStream);
+ sal_uInt16 nTmp(0);
+ mrStream.ReadUInt16(nTmp);
+
+ pAction->SetPushFlags(static_cast<vcl::PushFlags>(nTmp));
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::PopHandler()
+{
+ rtl::Reference<MetaPopAction> pAction(new MetaPopAction);
+
+ VersionCompatRead aCompat(mrStream);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::RasterOpHandler()
+{
+ rtl::Reference<MetaRasterOpAction> pAction(new MetaRasterOpAction);
+
+ sal_uInt16 nTmp16(0);
+
+ VersionCompatRead aCompat(mrStream);
+ mrStream.ReadUInt16(nTmp16);
+
+ pAction->SetRasterOp(static_cast<RasterOp>(nTmp16));
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::TransparentHandler()
+{
+ rtl::Reference<MetaTransparentAction> pAction(new MetaTransparentAction);
+
+ VersionCompatRead aCompat(mrStream);
+ tools::PolyPolygon aPolyPoly;
+ ReadPolyPolygon(mrStream, aPolyPoly);
+ sal_uInt16 nTransPercent(0);
+ mrStream.ReadUInt16(nTransPercent);
+
+ pAction->SetPolyPolygon(aPolyPoly);
+ pAction->SetTransparence(nTransPercent);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::FloatTransparentHandler(ImplMetaReadData* pData)
+{
+ rtl::Reference<MetaFloatTransparentAction> pAction(new MetaFloatTransparentAction);
+
+ VersionCompatRead aCompat(mrStream);
+ GDIMetaFile aMtf;
+ SvmReader aReader(mrStream);
+ aReader.Read(aMtf, pData);
+ TypeSerializer aSerializer(mrStream);
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+
+ Size aSize;
+ aSerializer.readSize(aSize);
+ sanitizeNegativeSizeDimensions(aSize);
+
+ Gradient aGradient;
+ aSerializer.readGradient(aGradient);
+
+ pAction->SetGDIMetaFile(aMtf);
+ pAction->SetPoint(aPoint);
+ pAction->SetSize(aSize);
+ pAction->SetGradient(aGradient);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::EPSHandler()
+{
+ rtl::Reference<MetaEPSAction> pAction(new MetaEPSAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+ GfxLink aGfxLink;
+ aSerializer.readGfxLink(aGfxLink);
+ Point aPoint;
+ aSerializer.readPoint(aPoint);
+ Size aSize;
+ aSerializer.readSize(aSize);
+ GDIMetaFile aSubst;
+ Read(aSubst);
+
+ pAction->SetLink(aGfxLink);
+ pAction->SetPoint(aPoint);
+ pAction->SetSize(aSize);
+ pAction->SetSubstitute(aSubst);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::RefPointHandler()
+{
+ rtl::Reference<MetaRefPointAction> pAction(new MetaRefPointAction);
+
+ VersionCompatRead aCompat(mrStream);
+ TypeSerializer aSerializer(mrStream);
+
+ Point aRefPoint;
+ aSerializer.readPoint(aRefPoint);
+ bool bSet(false);
+ mrStream.ReadCharAsBool(bSet);
+
+ pAction->SetRefPoint(aRefPoint);
+ pAction->SetSetting(bSet);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::CommentHandler()
+{
+ rtl::Reference<MetaCommentAction> pAction(new MetaCommentAction);
+
+ VersionCompatRead aCompat(mrStream);
+ OString aComment;
+ aComment = read_uInt16_lenPrefixed_uInt8s_ToOString(mrStream);
+ sal_Int32 nValue(0);
+ sal_uInt32 nDataSize(0);
+ mrStream.ReadInt32(nValue).ReadUInt32(nDataSize);
+
+ if (nDataSize > mrStream.remainingSize())
+ {
+ SAL_WARN("vcl.gdi", "Parsing error: " << mrStream.remainingSize() << " available data, but "
+ << nDataSize << " claimed, truncating");
+ nDataSize = mrStream.remainingSize();
+ }
+
+ SAL_INFO("vcl.gdi", "MetaCommentAction::Read " << aComment);
+
+ std::unique_ptr<sal_uInt8[]> pData;
+ pData.reset();
+
+ if (nDataSize)
+ {
+ pData.reset(new sal_uInt8[nDataSize]);
+ mrStream.ReadBytes(pData.get(), nDataSize);
+ }
+
+ pAction->SetComment(aComment);
+ pAction->SetDataSize(nDataSize);
+ pAction->SetValue(nValue);
+ pAction->SetData(pData.get(), nDataSize);
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::LayoutModeHandler()
+{
+ rtl::Reference<MetaLayoutModeAction> pAction(new MetaLayoutModeAction);
+
+ VersionCompatRead aCompat(mrStream);
+ sal_uInt32 tmp(0);
+ mrStream.ReadUInt32(tmp);
+
+ pAction->SetLayoutMode(static_cast<vcl::text::ComplexTextLayoutFlags>(tmp));
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::TextLanguageHandler()
+{
+ rtl::Reference<MetaTextLanguageAction> pAction(new MetaTextLanguageAction);
+
+ VersionCompatRead aCompat(mrStream);
+ sal_uInt16 nTmp = 0;
+ mrStream.ReadUInt16(nTmp);
+
+ pAction->SetTextLanguage(static_cast<LanguageType>(nTmp));
+
+ return pAction;
+}
+
+rtl::Reference<MetaAction> SvmReader::DefaultHandler()
+{
+ return rtl::Reference<MetaAction>(new MetaAction);
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/svm/SvmWriter.cxx b/vcl/source/filter/svm/SvmWriter.cxx
new file mode 100644
index 000000000..118db1325
--- /dev/null
+++ b/vcl/source/filter/svm/SvmWriter.cxx
@@ -0,0 +1,1421 @@
+/* -*- 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 <vcl/filter/SvmWriter.hxx>
+#include <vcl/TypeSerializer.hxx>
+#include <vcl/dibtools.hxx>
+
+#include <tools/vcompat.hxx>
+
+#include <osl/thread.h>
+
+SvmWriter::SvmWriter(SvStream& rIStm)
+ : mrStream(rIStm)
+{
+}
+
+void SvmWriter::WriteColor(::Color aColor)
+{
+ mrStream.WriteUInt32(static_cast<sal_uInt32>(aColor));
+}
+
+SvStream& SvmWriter::Write(const GDIMetaFile& rMetaFile)
+{
+ const SvStreamCompressFlags nStmCompressMode = mrStream.GetCompressMode();
+ SvStreamEndian nOldFormat = mrStream.GetEndian();
+
+ mrStream.SetEndian(SvStreamEndian::LITTLE);
+ mrStream.WriteBytes("VCLMTF", 6);
+
+ {
+ VersionCompatWrite aCompat(mrStream, 1);
+
+ mrStream.WriteUInt32(static_cast<sal_uInt32>(nStmCompressMode));
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writeMapMode(rMetaFile.GetPrefMapMode());
+ aSerializer.writeSize(rMetaFile.GetPrefSize());
+ mrStream.WriteUInt32(rMetaFile.GetActionSize());
+ } // VersionCompatWrite dtor writes stuff into the header
+
+ ImplMetaWriteData aWriteData;
+
+ aWriteData.meActualCharSet = mrStream.GetStreamCharSet();
+
+ MetaAction* pAct = const_cast<GDIMetaFile&>(rMetaFile).FirstAction();
+ while (pAct)
+ {
+ MetaActionHandler(pAct, &aWriteData);
+ pAct = const_cast<GDIMetaFile&>(rMetaFile).NextAction();
+ }
+
+ mrStream.SetEndian(nOldFormat);
+
+ return mrStream;
+}
+
+BitmapChecksum SvmWriter::GetChecksum(const GDIMetaFile& rMetaFile)
+{
+ SvMemoryStream aMemStm(65535, 65535);
+ ImplMetaWriteData aWriteData;
+ SVBT16 aBT16;
+ SVBT32 aBT32;
+ BitmapChecksumOctetArray aBCOA;
+ BitmapChecksum nCrc = 0;
+
+ aWriteData.meActualCharSet = aMemStm.GetStreamCharSet();
+
+ for (size_t i = 0, nObjCount = rMetaFile.GetActionSize(); i < nObjCount; i++)
+ {
+ MetaAction* pAction = rMetaFile.GetAction(i);
+
+ switch (pAction->GetType())
+ {
+ case MetaActionType::BMP:
+ {
+ MetaBmpAction* pAct = static_cast<MetaBmpAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = vcl_get_checksum(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA);
+ nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ Int32ToSVBT32(pAct->GetPoint().X(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().Y(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = vcl_get_checksum(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA);
+ nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ Int32ToSVBT32(pAct->GetPoint().X(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().Y(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSize().Width(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSize().Height(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = vcl_get_checksum(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA);
+ nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ Int32ToSVBT32(pAct->GetDestPoint().X(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestPoint().Y(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestSize().Width(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestSize().Height(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcPoint().X(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcPoint().Y(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcSize().Width(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcSize().Height(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ MetaBmpExAction* pAct = static_cast<MetaBmpExAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = vcl_get_checksum(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmapEx().GetChecksum(), aBCOA);
+ nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ Int32ToSVBT32(pAct->GetPoint().X(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().Y(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = vcl_get_checksum(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmapEx().GetChecksum(), aBCOA);
+ nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ Int32ToSVBT32(pAct->GetPoint().X(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().Y(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSize().Width(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSize().Height(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = vcl_get_checksum(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmapEx().GetChecksum(), aBCOA);
+ nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ Int32ToSVBT32(pAct->GetDestPoint().X(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestPoint().Y(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestSize().Width(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestSize().Height(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcPoint().X(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcPoint().Y(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcSize().Width(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcSize().Height(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::MASK:
+ {
+ MetaMaskAction* pAct = static_cast<MetaMaskAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = vcl_get_checksum(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA);
+ nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ UInt32ToSVBT32(sal_uInt32(pAct->GetColor()), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().X(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().Y(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::MASKSCALE:
+ {
+ MetaMaskScaleAction* pAct = static_cast<MetaMaskScaleAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = vcl_get_checksum(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA);
+ nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ UInt32ToSVBT32(sal_uInt32(pAct->GetColor()), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().X(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetPoint().Y(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSize().Width(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSize().Height(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ {
+ MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction);
+
+ ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16);
+ nCrc = vcl_get_checksum(nCrc, aBT16, 2);
+
+ BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA);
+ nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
+
+ UInt32ToSVBT32(sal_uInt32(pAct->GetColor()), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestPoint().X(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestPoint().Y(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestSize().Width(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetDestSize().Height(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcPoint().X(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcPoint().Y(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcSize().Width(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+
+ Int32ToSVBT32(pAct->GetSrcSize().Height(), aBT32);
+ nCrc = vcl_get_checksum(nCrc, aBT32, 4);
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction);
+ nCrc = vcl_get_checksum(nCrc, pAct->GetLink().GetData(),
+ pAct->GetLink().GetDataSize());
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ {
+ MetaClipRegionAction& rAct = static_cast<MetaClipRegionAction&>(*pAction);
+ const vcl::Region& rRegion = rAct.GetRegion();
+
+ if (rRegion.HasPolyPolygonOrB2DPolyPolygon())
+ {
+ // It has shown that this is a possible bottleneck for checksum calculation.
+ // In worst case a very expensive RegionHandle representation gets created.
+ // In this case it's cheaper to use the PolyPolygon
+ const basegfx::B2DPolyPolygon aPolyPolygon(rRegion.GetAsB2DPolyPolygon());
+ SVBT64 aSVBT64;
+
+ for (auto const& rPolygon : aPolyPolygon)
+ {
+ const sal_uInt32 nPointCount(rPolygon.count());
+ const bool bControl(rPolygon.areControlPointsUsed());
+
+ for (sal_uInt32 b(0); b < nPointCount; b++)
+ {
+ const basegfx::B2DPoint aPoint(rPolygon.getB2DPoint(b));
+
+ DoubleToSVBT64(aPoint.getX(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+ DoubleToSVBT64(aPoint.getY(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+
+ if (bControl)
+ {
+ if (rPolygon.isPrevControlPointUsed(b))
+ {
+ const basegfx::B2DPoint aCtrl(rPolygon.getPrevControlPoint(b));
+
+ DoubleToSVBT64(aCtrl.getX(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+ DoubleToSVBT64(aCtrl.getY(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+ }
+
+ if (rPolygon.isNextControlPointUsed(b))
+ {
+ const basegfx::B2DPoint aCtrl(rPolygon.getNextControlPoint(b));
+
+ DoubleToSVBT64(aCtrl.getX(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+ DoubleToSVBT64(aCtrl.getY(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+ }
+ }
+ }
+ }
+
+ sal_uInt8 tmp = static_cast<sal_uInt8>(rAct.IsClipping());
+ nCrc = vcl_get_checksum(nCrc, &tmp, 1);
+ }
+ else
+ {
+ SvmWriter aWriter(aMemStm);
+ aWriter.MetaActionHandler(pAction, &aWriteData);
+ nCrc = vcl_get_checksum(nCrc, aMemStm.GetData(), aMemStm.Tell());
+ aMemStm.Seek(0);
+ }
+ }
+ break;
+
+ default:
+ {
+ SvmWriter aWriter(aMemStm);
+ aWriter.MetaActionHandler(pAction, &aWriteData);
+ nCrc = vcl_get_checksum(nCrc, aMemStm.GetData(), aMemStm.Tell());
+ aMemStm.Seek(0);
+ }
+ break;
+ }
+ }
+
+ return nCrc;
+}
+
+void SvmWriter::MetaActionHandler(MetaAction* pAction, ImplMetaWriteData* pData)
+{
+ MetaActionType nType = pAction->GetType();
+
+ switch (nType)
+ {
+ case MetaActionType::NONE:
+ {
+ ActionHandler(pAction);
+ }
+ break;
+
+ case MetaActionType::PIXEL:
+ {
+ auto* pMetaAction = static_cast<MetaPixelAction*>(pAction);
+ PixelHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::POINT:
+ {
+ auto pMetaAction = static_cast<MetaPointAction*>(pAction);
+ PointHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ auto* pMetaAction = static_cast<MetaLineAction*>(pAction);
+ LineHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ auto* pMetaAction = static_cast<MetaRectAction*>(pAction);
+ RectHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ auto* pMetaAction = static_cast<MetaRoundRectAction*>(pAction);
+ RoundRectHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ auto* pMetaAction = static_cast<MetaEllipseAction*>(pAction);
+ EllipseHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::ARC:
+ {
+ auto* pMetaAction = static_cast<MetaArcAction*>(pAction);
+ ArcHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::PIE:
+ {
+ auto* pMetaAction = static_cast<MetaPieAction*>(pAction);
+ PieHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::CHORD:
+ {
+ auto* pMetaAction = static_cast<MetaChordAction*>(pAction);
+ ChordHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ auto* pMetaAction = static_cast<MetaPolyLineAction*>(pAction);
+ PolyLineHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::POLYGON:
+ {
+ auto* pMetaAction = static_cast<MetaPolygonAction*>(pAction);
+ PolygonHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ {
+ auto* pMetaAction = static_cast<MetaPolyPolygonAction*>(pAction);
+ PolyPolygonHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ auto* pMetaAction = static_cast<MetaTextAction*>(pAction);
+ TextHandler(pMetaAction, pData);
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ auto* pMetaAction = static_cast<MetaTextArrayAction*>(pAction);
+ TextArrayHandler(pMetaAction, pData);
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ auto* pMetaAction = static_cast<MetaStretchTextAction*>(pAction);
+ StretchTextHandler(pMetaAction, pData);
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ auto* pMetaAction = static_cast<MetaTextRectAction*>(pAction);
+ TextRectHandler(pMetaAction, pData);
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ {
+ auto* pMetaAction = static_cast<MetaTextLineAction*>(pAction);
+ TextLineHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::BMP:
+ {
+ auto* pMetaAction = static_cast<MetaBmpAction*>(pAction);
+ BmpHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ auto* pMetaAction = static_cast<MetaBmpScaleAction*>(pAction);
+ BmpScaleHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ auto* pMetaAction = static_cast<MetaBmpScalePartAction*>(pAction);
+ BmpScalePartHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ auto* pMetaAction = static_cast<MetaBmpExAction*>(pAction);
+ BmpExHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ auto* pMetaAction = static_cast<MetaBmpExScaleAction*>(pAction);
+ BmpExScaleHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ auto* pMetaAction = static_cast<MetaBmpExScalePartAction*>(pAction);
+ BmpExScalePartHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::MASK:
+ {
+ auto* pMetaAction = static_cast<MetaMaskAction*>(pAction);
+ MaskHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::MASKSCALE:
+ {
+ auto* pMetaAction = static_cast<MetaMaskScaleAction*>(pAction);
+ MaskScaleHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ {
+ auto* pMetaAction = static_cast<MetaMaskScalePartAction*>(pAction);
+ MaskScalePartHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ auto* pMetaAction = static_cast<MetaGradientAction*>(pAction);
+ GradientHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ {
+ auto* pMetaAction = static_cast<MetaGradientExAction*>(pAction);
+ GradientExHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ auto* pMetaAction = static_cast<MetaHatchAction*>(pAction);
+ HatchHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::WALLPAPER:
+ {
+ auto* pMetaAction = static_cast<MetaWallpaperAction*>(pAction);
+ WallpaperHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ {
+ auto* pMetaAction = static_cast<MetaClipRegionAction*>(pAction);
+ ClipRegionHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::ISECTRECTCLIPREGION:
+ {
+ auto* pMetaAction = static_cast<MetaISectRectClipRegionAction*>(pAction);
+ ISectRectClipRegionHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ {
+ auto* pMetaAction = static_cast<MetaISectRegionClipRegionAction*>(pAction);
+ ISectRegionClipRegionHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::MOVECLIPREGION:
+ {
+ auto* pMetaAction = static_cast<MetaMoveClipRegionAction*>(pAction);
+ MoveClipRegionHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::LINECOLOR:
+ {
+ auto* pMetaAction = static_cast<MetaLineColorAction*>(pAction);
+ LineColorHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::FILLCOLOR:
+ {
+ auto* pMetaAction = static_cast<MetaFillColorAction*>(pAction);
+ FillColorHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::TEXTCOLOR:
+ {
+ auto* pMetaAction = static_cast<MetaTextColorAction*>(pAction);
+ TextColorHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::TEXTFILLCOLOR:
+ {
+ auto* pMetaAction = static_cast<MetaTextFillColorAction*>(pAction);
+ TextFillColorHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::TEXTLINECOLOR:
+ {
+ auto* pMetaAction = static_cast<MetaTextLineColorAction*>(pAction);
+ TextLineColorHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::OVERLINECOLOR:
+ {
+ auto* pMetaAction = static_cast<MetaOverlineColorAction*>(pAction);
+ OverlineColorHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::TEXTALIGN:
+ {
+ auto* pMetaAction = static_cast<MetaTextAlignAction*>(pAction);
+ TextAlignHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::MAPMODE:
+ {
+ auto* pMetaAction = static_cast<MetaMapModeAction*>(pAction);
+ MapModeHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::FONT:
+ {
+ auto* pMetaAction = static_cast<MetaFontAction*>(pAction);
+ FontHandler(pMetaAction, pData);
+ }
+ break;
+
+ case MetaActionType::PUSH:
+ {
+ auto* pMetaAction = static_cast<MetaPushAction*>(pAction);
+ PushHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::POP:
+ {
+ auto* pMetaAction = static_cast<MetaPopAction*>(pAction);
+ PopHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::RASTEROP:
+ {
+ auto* pMetaAction = static_cast<MetaRasterOpAction*>(pAction);
+ RasterOpHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::Transparent:
+ {
+ auto* pMetaAction = static_cast<MetaTransparentAction*>(pAction);
+ TransparentHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ auto* pMetaAction = static_cast<MetaFloatTransparentAction*>(pAction);
+ FloatTransparentHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ auto* pMetaAction = static_cast<MetaEPSAction*>(pAction);
+ EPSHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::REFPOINT:
+ {
+ auto* pMetaAction = static_cast<MetaRefPointAction*>(pAction);
+ RefPointHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::COMMENT:
+ {
+ auto* pMetaAction = static_cast<MetaCommentAction*>(pAction);
+ CommentHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::LAYOUTMODE:
+ {
+ auto* pMetaAction = static_cast<MetaLayoutModeAction*>(pAction);
+ LayoutModeHandler(pMetaAction);
+ }
+ break;
+
+ case MetaActionType::TEXTLANGUAGE:
+ {
+ auto* pMetaAction = static_cast<MetaTextLanguageAction*>(pAction);
+ TextLanguageHandler(pMetaAction);
+ }
+ break;
+ }
+}
+
+void SvmWriter::ActionHandler(const MetaAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+}
+
+void SvmWriter::PixelHandler(const MetaPixelAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetPoint());
+ WriteColor(pAction->GetColor());
+}
+
+void SvmWriter::PointHandler(const MetaPointAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetPoint());
+}
+
+void SvmWriter::LineHandler(const MetaLineAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+
+ VersionCompatWrite aCompat(mrStream, 2);
+
+ // Version 1
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetStartPoint());
+ aSerializer.writePoint(pAction->GetEndPoint());
+ // Version 2
+ WriteLineInfo(mrStream, pAction->GetLineInfo());
+}
+
+void SvmWriter::RectHandler(const MetaRectAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+
+ VersionCompatWrite aCompat(mrStream, 1);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writeRectangle(pAction->GetRect());
+}
+
+void SvmWriter::RoundRectHandler(const MetaRoundRectAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+
+ VersionCompatWrite aCompat(mrStream, 1);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writeRectangle(pAction->GetRect());
+ mrStream.WriteUInt32(pAction->GetHorzRound()).WriteUInt32(pAction->GetVertRound());
+}
+
+void SvmWriter::EllipseHandler(const MetaEllipseAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+
+ VersionCompatWrite aCompat(mrStream, 1);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writeRectangle(pAction->GetRect());
+}
+
+void SvmWriter::ArcHandler(const MetaArcAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+
+ VersionCompatWrite aCompat(mrStream, 1);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writeRectangle(pAction->GetRect());
+ aSerializer.writePoint(pAction->GetStartPoint());
+ aSerializer.writePoint(pAction->GetEndPoint());
+}
+
+void SvmWriter::PieHandler(const MetaPieAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+
+ VersionCompatWrite aCompat(mrStream, 1);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writeRectangle(pAction->GetRect());
+ aSerializer.writePoint(pAction->GetStartPoint());
+ aSerializer.writePoint(pAction->GetEndPoint());
+}
+
+void SvmWriter::ChordHandler(const MetaChordAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+
+ VersionCompatWrite aCompat(mrStream, 1);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writeRectangle(pAction->GetRect());
+ aSerializer.writePoint(pAction->GetStartPoint());
+ aSerializer.writePoint(pAction->GetEndPoint());
+}
+
+void SvmWriter::PolyLineHandler(const MetaPolyLineAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+
+ VersionCompatWrite aCompat(mrStream, 3);
+
+ tools::Polygon aSimplePoly;
+ pAction->GetPolygon().AdaptiveSubdivide(aSimplePoly);
+
+ WritePolygon(mrStream, aSimplePoly); // Version 1
+ WriteLineInfo(mrStream, pAction->GetLineInfo()); // Version 2
+
+ bool bHasPolyFlags = pAction->GetPolygon().HasFlags(); // Version 3
+ mrStream.WriteBool(bHasPolyFlags);
+ if (bHasPolyFlags)
+ pAction->GetPolygon().Write(mrStream);
+}
+
+void SvmWriter::PolygonHandler(const MetaPolygonAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+
+ VersionCompatWrite aCompat(mrStream, 2);
+
+ tools::Polygon aSimplePoly; // Version 1
+ pAction->GetPolygon().AdaptiveSubdivide(aSimplePoly);
+ WritePolygon(mrStream, aSimplePoly);
+
+ bool bHasPolyFlags = pAction->GetPolygon().HasFlags(); // Version 2
+ mrStream.WriteBool(bHasPolyFlags);
+ if (bHasPolyFlags)
+ pAction->GetPolygon().Write(mrStream);
+}
+
+void SvmWriter::PolyPolygonHandler(const MetaPolyPolygonAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+
+ VersionCompatWrite aCompat(mrStream, 2);
+
+ sal_uInt16 nNumberOfComplexPolygons = 0;
+ sal_uInt16 i, nPolyCount = pAction->GetPolyPolygon().Count();
+
+ tools::Polygon aSimplePoly; // Version 1
+ mrStream.WriteUInt16(nPolyCount);
+ for (i = 0; i < nPolyCount; i++)
+ {
+ const tools::Polygon& rPoly = pAction->GetPolyPolygon().GetObject(i);
+ if (rPoly.HasFlags())
+ nNumberOfComplexPolygons++;
+ rPoly.AdaptiveSubdivide(aSimplePoly);
+ WritePolygon(mrStream, aSimplePoly);
+ }
+
+ mrStream.WriteUInt16(nNumberOfComplexPolygons); // Version 2
+ for (i = 0; nNumberOfComplexPolygons && (i < nPolyCount); i++)
+ {
+ const tools::Polygon& rPoly = pAction->GetPolyPolygon().GetObject(i);
+ if (rPoly.HasFlags())
+ {
+ mrStream.WriteUInt16(i);
+ rPoly.Write(mrStream);
+
+ nNumberOfComplexPolygons--;
+ }
+ }
+}
+
+void SvmWriter::TextHandler(const MetaTextAction* pAction, const ImplMetaWriteData* pData)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+
+ VersionCompatWrite aCompat(mrStream, 2);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetPoint());
+ mrStream.WriteUniOrByteString(pAction->GetText(), pData->meActualCharSet);
+ mrStream.WriteUInt16(pAction->GetIndex());
+ mrStream.WriteUInt16(pAction->GetLen());
+
+ write_uInt16_lenPrefixed_uInt16s_FromOUString(mrStream, pAction->GetText()); // version 2
+}
+
+void SvmWriter::TextArrayHandler(const MetaTextArrayAction* pAction, const ImplMetaWriteData* pData)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+
+ const std::vector<sal_Int32>& rDXArray = pAction->GetDXArray();
+
+ const sal_Int32 nAryLen = !rDXArray.empty() ? pAction->GetLen() : 0;
+
+ VersionCompatWrite aCompat(mrStream, 2);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetPoint());
+ mrStream.WriteUniOrByteString(pAction->GetText(), pData->meActualCharSet);
+ mrStream.WriteUInt16(pAction->GetIndex());
+ mrStream.WriteUInt16(pAction->GetLen());
+ mrStream.WriteInt32(nAryLen);
+
+ for (sal_Int32 i = 0; i < nAryLen; ++i)
+ mrStream.WriteInt32(rDXArray[i]);
+
+ write_uInt16_lenPrefixed_uInt16s_FromOUString(mrStream, pAction->GetText()); // version 2
+}
+
+void SvmWriter::StretchTextHandler(const MetaStretchTextAction* pAction,
+ const ImplMetaWriteData* pData)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+
+ VersionCompatWrite aCompat(mrStream, 2);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetPoint());
+ mrStream.WriteUniOrByteString(pAction->GetText(), pData->meActualCharSet);
+ mrStream.WriteUInt32(pAction->GetWidth());
+ mrStream.WriteUInt16(pAction->GetIndex());
+ mrStream.WriteUInt16(pAction->GetLen());
+
+ write_uInt16_lenPrefixed_uInt16s_FromOUString(mrStream, pAction->GetText()); // version 2
+}
+
+void SvmWriter::TextRectHandler(const MetaTextRectAction* pAction, const ImplMetaWriteData* pData)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+
+ VersionCompatWrite aCompat(mrStream, 2);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writeRectangle(pAction->GetRect());
+ mrStream.WriteUniOrByteString(pAction->GetText(), pData->meActualCharSet);
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetStyle()));
+
+ write_uInt16_lenPrefixed_uInt16s_FromOUString(mrStream, pAction->GetText()); // version 2
+}
+
+void SvmWriter::TextLineHandler(const MetaTextLineAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+
+ VersionCompatWrite aCompat(mrStream, 2);
+
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetStartPoint());
+
+ mrStream.WriteInt32(pAction->GetWidth());
+ mrStream.WriteUInt32(pAction->GetStrikeout());
+ mrStream.WriteUInt32(pAction->GetUnderline());
+ // new in version 2
+ mrStream.WriteUInt32(pAction->GetOverline());
+}
+
+void SvmWriter::BmpHandler(const MetaBmpAction* pAction)
+{
+ if (!pAction->GetBitmap().IsEmpty())
+ {
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteDIB(pAction->GetBitmap(), mrStream, false, true);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetPoint());
+ }
+}
+
+void SvmWriter::BmpScaleHandler(const MetaBmpScaleAction* pAction)
+{
+ if (!pAction->GetBitmap().IsEmpty())
+ {
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteDIB(pAction->GetBitmap(), mrStream, false, true);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetPoint());
+ aSerializer.writeSize(pAction->GetSize());
+ }
+}
+
+void SvmWriter::BmpScalePartHandler(const MetaBmpScalePartAction* pAction)
+{
+ if (!pAction->GetBitmap().IsEmpty())
+ {
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteDIB(pAction->GetBitmap(), mrStream, false, true);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetDestPoint());
+ aSerializer.writeSize(pAction->GetDestSize());
+ aSerializer.writePoint(pAction->GetSrcPoint());
+ aSerializer.writeSize(pAction->GetSrcSize());
+ }
+}
+
+void SvmWriter::BmpExHandler(const MetaBmpExAction* pAction)
+{
+ if (!pAction->GetBitmapEx().GetBitmap().IsEmpty())
+ {
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteDIBBitmapEx(pAction->GetBitmapEx(), mrStream);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetPoint());
+ }
+}
+
+void SvmWriter::BmpExScaleHandler(const MetaBmpExScaleAction* pAction)
+{
+ if (!pAction->GetBitmapEx().GetBitmap().IsEmpty())
+ {
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteDIBBitmapEx(pAction->GetBitmapEx(), mrStream);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetPoint());
+ aSerializer.writeSize(pAction->GetSize());
+ }
+}
+
+void SvmWriter::BmpExScalePartHandler(const MetaBmpExScalePartAction* pAction)
+{
+ if (!pAction->GetBitmapEx().GetBitmap().IsEmpty())
+ {
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteDIBBitmapEx(pAction->GetBitmapEx(), mrStream);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetDestPoint());
+ aSerializer.writeSize(pAction->GetDestSize());
+ aSerializer.writePoint(pAction->GetSrcPoint());
+ aSerializer.writeSize(pAction->GetSrcSize());
+ }
+}
+
+void SvmWriter::MaskHandler(const MetaMaskAction* pAction)
+{
+ if (!pAction->GetBitmap().IsEmpty())
+ {
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteDIB(pAction->GetBitmap(), mrStream, false, true);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetPoint());
+ }
+}
+
+void SvmWriter::MaskScaleHandler(const MetaMaskScaleAction* pAction)
+{
+ if (!pAction->GetBitmap().IsEmpty())
+ {
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteDIB(pAction->GetBitmap(), mrStream, false, true);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetPoint());
+ aSerializer.writeSize(pAction->GetSize());
+ }
+}
+
+void SvmWriter::MaskScalePartHandler(const MetaMaskScalePartAction* pAction)
+{
+ if (!pAction->GetBitmap().IsEmpty())
+ {
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteDIB(pAction->GetBitmap(), mrStream, false, true);
+ WriteColor(pAction->GetColor());
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetDestPoint());
+ aSerializer.writeSize(pAction->GetDestSize());
+ aSerializer.writePoint(pAction->GetSrcPoint());
+ aSerializer.writeSize(pAction->GetSrcSize());
+ }
+}
+
+void SvmWriter::GradientHandler(const MetaGradientAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writeRectangle(pAction->GetRect());
+ aSerializer.writeGradient(pAction->GetGradient());
+}
+
+void SvmWriter::GradientExHandler(const MetaGradientExAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+
+ // #i105373# see comment at MetaTransparentAction::Write
+ tools::PolyPolygon aNoCurvePolyPolygon;
+ pAction->GetPolyPolygon().AdaptiveSubdivide(aNoCurvePolyPolygon);
+
+ WritePolyPolygon(mrStream, aNoCurvePolyPolygon);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writeGradient(pAction->GetGradient());
+}
+
+void SvmWriter::HatchHandler(const MetaHatchAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+
+ // #i105373# see comment at MetaTransparentAction::Write
+ tools::PolyPolygon aNoCurvePolyPolygon;
+ pAction->GetPolyPolygon().AdaptiveSubdivide(aNoCurvePolyPolygon);
+
+ WritePolyPolygon(mrStream, aNoCurvePolyPolygon);
+ WriteHatch(mrStream, pAction->GetHatch());
+}
+
+void SvmWriter::WallpaperHandler(const MetaWallpaperAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+
+ WriteWallpaper(mrStream, pAction->GetWallpaper());
+}
+
+void SvmWriter::ClipRegionHandler(const MetaClipRegionAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteRegion(mrStream, pAction->GetRegion());
+ mrStream.WriteBool(pAction->IsClipping());
+}
+
+void SvmWriter::ISectRectClipRegionHandler(const MetaISectRectClipRegionAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writeRectangle(pAction->GetRect());
+}
+
+void SvmWriter::ISectRegionClipRegionHandler(const MetaISectRegionClipRegionAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteRegion(mrStream, pAction->GetRegion());
+}
+
+void SvmWriter::MoveClipRegionHandler(const MetaMoveClipRegionAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ mrStream.WriteInt32(pAction->GetHorzMove()).WriteInt32(pAction->GetVertMove());
+}
+
+void SvmWriter::LineColorHandler(const MetaLineColorAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteColor(pAction->GetColor());
+ mrStream.WriteBool(pAction->IsSetting());
+}
+
+void SvmWriter::FillColorHandler(const MetaFillColorAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteColor(pAction->GetColor());
+ mrStream.WriteBool(pAction->IsSetting());
+}
+
+void SvmWriter::TextColorHandler(const MetaTextColorAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteColor(pAction->GetColor());
+}
+
+void SvmWriter::TextFillColorHandler(const MetaTextFillColorAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteColor(pAction->GetColor());
+ mrStream.WriteBool(pAction->IsSetting());
+}
+
+void SvmWriter::TextLineColorHandler(const MetaTextLineColorAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteColor(pAction->GetColor());
+ mrStream.WriteBool(pAction->IsSetting());
+}
+
+void SvmWriter::OverlineColorHandler(const MetaOverlineColorAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteColor(pAction->GetColor());
+ mrStream.WriteBool(pAction->IsSetting());
+}
+
+void SvmWriter::TextAlignHandler(const MetaTextAlignAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetTextAlign()));
+}
+
+void SvmWriter::MapModeHandler(const MetaMapModeAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writeMapMode(pAction->GetMapMode());
+}
+
+void SvmWriter::FontHandler(const MetaFontAction* pAction, ImplMetaWriteData* pData)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ WriteFont(mrStream, pAction->GetFont());
+ pData->meActualCharSet = pAction->GetFont().GetCharSet();
+ if (pData->meActualCharSet == RTL_TEXTENCODING_DONTKNOW)
+ pData->meActualCharSet = osl_getThreadTextEncoding();
+}
+
+void SvmWriter::PushHandler(const MetaPushAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetFlags()));
+}
+
+void SvmWriter::PopHandler(const MetaPopAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+}
+
+void SvmWriter::RasterOpHandler(const MetaRasterOpAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetRasterOp()));
+}
+
+void SvmWriter::TransparentHandler(const MetaTransparentAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+
+ // #i105373# The tools::PolyPolygon in this action may be a curve; this
+ // was ignored until now what is an error. To make older office
+ // versions work with MetaFiles, i opt for applying AdaptiveSubdivide
+ // to the PolyPolygon.
+ // The alternative would be to really write the curve information
+ // like in MetaPolyPolygonAction::Write (where someone extended it
+ // correctly, but not here :-( ).
+ // The golden solution would be to combine both, but i think it's
+ // not necessary; a good subdivision will be sufficient.
+ tools::PolyPolygon aNoCurvePolyPolygon;
+ pAction->GetPolyPolygon().AdaptiveSubdivide(aNoCurvePolyPolygon);
+
+ WritePolyPolygon(mrStream, aNoCurvePolyPolygon);
+ mrStream.WriteUInt16(pAction->GetTransparence());
+}
+
+void SvmWriter::FloatTransparentHandler(const MetaFloatTransparentAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+
+ SvmWriter aWriter(mrStream);
+ GDIMetaFile aMtf = pAction->GetGDIMetaFile();
+ aWriter.Write(aMtf);
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetPoint());
+ aSerializer.writeSize(pAction->GetSize());
+ aSerializer.writeGradient(pAction->GetGradient());
+}
+
+void SvmWriter::EPSHandler(const MetaEPSAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writeGfxLink(pAction->GetLink());
+ aSerializer.writePoint(pAction->GetPoint());
+ aSerializer.writeSize(pAction->GetSize());
+
+ SvmWriter aWriter(mrStream);
+ GDIMetaFile aMtf = pAction->GetSubstitute();
+ aWriter.Write(aMtf);
+}
+
+void SvmWriter::RefPointHandler(const MetaRefPointAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+
+ TypeSerializer aSerializer(mrStream);
+ aSerializer.writePoint(pAction->GetRefPoint());
+ mrStream.WriteBool(pAction->IsSetting());
+}
+
+void SvmWriter::CommentHandler(const MetaCommentAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ write_uInt16_lenPrefixed_uInt8s_FromOString(mrStream, pAction->GetComment());
+ mrStream.WriteInt32(pAction->GetValue()).WriteUInt32(pAction->GetDataSize());
+
+ if (pAction->GetDataSize())
+ mrStream.WriteBytes(pAction->GetData(), pAction->GetDataSize());
+}
+
+void SvmWriter::LayoutModeHandler(const MetaLayoutModeAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ mrStream.WriteUInt32(static_cast<sal_uInt32>(pAction->GetLayoutMode()));
+}
+
+void SvmWriter::TextLanguageHandler(const MetaTextLanguageAction* pAction)
+{
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
+ VersionCompatWrite aCompat(mrStream, 1);
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetTextLanguage()));
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/webp/reader.cxx b/vcl/source/filter/webp/reader.cxx
new file mode 100644
index 000000000..3c0b1399c
--- /dev/null
+++ b/vcl/source/filter/webp/reader.cxx
@@ -0,0 +1,318 @@
+/* -*- 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 <memory>
+#include <vcl/graph.hxx>
+#include <tools/stream.hxx>
+#include <filter/WebpReader.hxx>
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <salinst.hxx>
+#include <sal/log.hxx>
+#include <unotools/configmgr.hxx>
+#include <svdata.hxx>
+#include <comphelper/scopeguard.hxx>
+
+#include <webp/decode.h>
+
+static bool readWebpInfo(SvStream& stream, std::vector<uint8_t>& data,
+ WebPBitstreamFeatures& features)
+{
+ for (;;)
+ {
+ // Read 4096 (more) bytes.
+ size_t lastSize = data.size();
+ data.resize(data.size() + 4096);
+ sal_Size nBytesRead = stream.ReadBytes(data.data() + lastSize, 4096);
+ if (nBytesRead <= 0)
+ return false;
+ data.resize(lastSize + nBytesRead);
+ int status = WebPGetFeatures(data.data(), data.size(), &features);
+ if (status == VP8_STATUS_OK)
+ break;
+ if (status == VP8_STATUS_NOT_ENOUGH_DATA)
+ continue; // Try again with 4096 more bytes read.
+ return false;
+ }
+ return true;
+}
+
+static bool readWebp(SvStream& stream, Graphic& graphic)
+{
+ WebPDecoderConfig config;
+ if (!WebPInitDecoderConfig(&config))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPInitDecoderConfig() failed");
+ return false;
+ }
+ comphelper::ScopeGuard freeBuffer([&config]() { WebPFreeDecBuffer(&config.output); });
+ std::vector<uint8_t> data;
+ if (!readWebpInfo(stream, data, config.input))
+ return false;
+ // Here various parts of 'config' can be altered if wanted.
+ const int& width = config.input.width;
+ const int& height = config.input.height;
+ const int& has_alpha = config.input.has_alpha;
+
+ if (width > SAL_MAX_INT32 / 8 || height > SAL_MAX_INT32 / 8)
+ return false; // avoid overflows later
+
+ const bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ const bool bSupportsBitmap32 = bFuzzing || ImplGetSVData()->mpDefInst->supportsBitmap32();
+
+ Bitmap bitmap;
+ AlphaMask bitmapAlpha;
+ if (bSupportsBitmap32 && has_alpha)
+ {
+ bitmap = Bitmap(Size(width, height), vcl::PixelFormat::N32_BPP);
+ }
+ else
+ {
+ bitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP);
+ if (has_alpha)
+ bitmapAlpha = AlphaMask(Size(width, height));
+ }
+
+ BitmapScopedWriteAccess access(bitmap);
+ if (!access)
+ return false;
+ // If data cannot be read directly into the bitmap, read data first to this buffer and then convert.
+ std::vector<uint8_t> tmpRgbaData;
+ enum class PixelMode
+ {
+ DirectRead, // read data directly to the bitmap
+ Split, // read to tmp buffer and split to rgb and alpha
+ SetPixel // read to tmp buffer and use setPixel()
+ };
+ PixelMode pixelMode = PixelMode::SetPixel;
+
+ config.output.width = width;
+ config.output.height = height;
+ config.output.is_external_memory = 1;
+ if (bSupportsBitmap32 && has_alpha)
+ {
+ switch (RemoveScanline(access->GetScanlineFormat()))
+ {
+ // Our bitmap32 code expects premultiplied.
+ case ScanlineFormat::N32BitTcRgba:
+ config.output.colorspace = MODE_rgbA;
+ pixelMode = PixelMode::DirectRead;
+ break;
+ case ScanlineFormat::N32BitTcBgra:
+ config.output.colorspace = MODE_bgrA;
+ pixelMode = PixelMode::DirectRead;
+ break;
+ case ScanlineFormat::N32BitTcArgb:
+ config.output.colorspace = MODE_Argb;
+ pixelMode = PixelMode::DirectRead;
+ break;
+ default:
+ config.output.colorspace = MODE_RGBA;
+ pixelMode = PixelMode::SetPixel;
+ break;
+ }
+ }
+ else
+ {
+ if (has_alpha)
+ {
+ switch (RemoveScanline(access->GetScanlineFormat()))
+ {
+ case ScanlineFormat::N24BitTcRgb:
+ config.output.colorspace = MODE_RGBA;
+ pixelMode = PixelMode::Split;
+ break;
+ case ScanlineFormat::N24BitTcBgr:
+ config.output.colorspace = MODE_BGRA;
+ pixelMode = PixelMode::Split;
+ break;
+ default:
+ config.output.colorspace = MODE_RGBA;
+ pixelMode = PixelMode::SetPixel;
+ break;
+ }
+ }
+ else
+ {
+ switch (RemoveScanline(access->GetScanlineFormat()))
+ {
+ case ScanlineFormat::N24BitTcRgb:
+ config.output.colorspace = MODE_RGB;
+ pixelMode = PixelMode::DirectRead;
+ break;
+ case ScanlineFormat::N24BitTcBgr:
+ config.output.colorspace = MODE_BGR;
+ pixelMode = PixelMode::DirectRead;
+ break;
+ default:
+ config.output.colorspace = MODE_RGBA;
+ pixelMode = PixelMode::SetPixel;
+ break;
+ }
+ }
+ }
+ if (pixelMode == PixelMode::DirectRead)
+ {
+ config.output.u.RGBA.rgba = access->GetBuffer();
+ config.output.u.RGBA.stride = access->GetScanlineSize();
+ config.output.u.RGBA.size = access->GetScanlineSize() * access->Height();
+ }
+ else
+ {
+ tmpRgbaData.resize(width * height * 4);
+ config.output.u.RGBA.rgba = tmpRgbaData.data();
+ config.output.u.RGBA.stride = width * 4;
+ config.output.u.RGBA.size = tmpRgbaData.size();
+ }
+
+ std::unique_ptr<WebPIDecoder, decltype(&WebPIDelete)> decoder(WebPIDecode(nullptr, 0, &config),
+ WebPIDelete);
+
+ bool success = true;
+ for (;;)
+ {
+ // During first iteration, use data read while reading the header.
+ int status = WebPIAppend(decoder.get(), data.data(), data.size());
+ if (status == VP8_STATUS_OK)
+ break;
+ if (status != VP8_STATUS_SUSPENDED)
+ {
+ // An error, still try to return at least a partially read bitmap,
+ // even if returning an error flag.
+ success = false;
+ break;
+ }
+ // If more data is needed, reading 4096 bytes more and repeat.
+ data.resize(4096);
+ sal_Size nBytesRead = stream.ReadBytes(data.data(), 4096);
+ if (nBytesRead <= 0)
+ {
+ // Truncated file, again try to return at least something.
+ success = false;
+ break;
+ }
+ data.resize(nBytesRead);
+ }
+
+ switch (pixelMode)
+ {
+ case PixelMode::DirectRead:
+ {
+ // Adjust for IsBottomUp() if necessary.
+ if (access->IsBottomUp())
+ {
+ std::vector<char> tmp;
+ const sal_uInt32 lineSize = access->GetScanlineSize();
+ tmp.resize(lineSize);
+ for (tools::Long y = 0; y < access->Height() / 2; ++y)
+ {
+ tools::Long otherY = access->Height() - 1 - y;
+ memcpy(tmp.data(), access->GetScanline(y), lineSize);
+ memcpy(access->GetScanline(y), access->GetScanline(otherY), lineSize);
+ memcpy(access->GetScanline(otherY), tmp.data(), lineSize);
+ }
+ }
+ break;
+ }
+ case PixelMode::Split:
+ {
+ // Split to normal and alpha bitmaps.
+ AlphaScopedWriteAccess accessAlpha(bitmapAlpha);
+ for (tools::Long y = 0; y < access->Height(); ++y)
+ {
+ const unsigned char* src = tmpRgbaData.data() + width * 4 * y;
+ unsigned char* dstB = access->GetScanline(y);
+ unsigned char* dstA = accessAlpha->GetScanline(y);
+ for (tools::Long x = 0; x < access->Width(); ++x)
+ {
+ memcpy(dstB, src, 3);
+ *dstA = 255 - *(src + 3);
+ src += 4;
+ dstB += 3;
+ dstA += 1;
+ }
+ }
+ break;
+ }
+ case PixelMode::SetPixel:
+ {
+ for (tools::Long y = 0; y < access->Height(); ++y)
+ {
+ const unsigned char* src = tmpRgbaData.data() + width * 4 * y;
+ for (tools::Long x = 0; x < access->Width(); ++x)
+ {
+ sal_uInt8 r = src[0];
+ sal_uInt8 g = src[1];
+ sal_uInt8 b = src[2];
+ sal_uInt8 a = src[3];
+ access->SetPixel(y, x, Color(ColorAlpha, a, r, g, b));
+ src += 4;
+ }
+ }
+ if (!bitmapAlpha.IsEmpty())
+ {
+ AlphaScopedWriteAccess accessAlpha(bitmapAlpha);
+ for (tools::Long y = 0; y < accessAlpha->Height(); ++y)
+ {
+ const unsigned char* src = tmpRgbaData.data() + width * 4 * y;
+ for (tools::Long x = 0; x < accessAlpha->Width(); ++x)
+ {
+ sal_uInt8 a = src[3];
+ accessAlpha->SetPixelIndex(y, x, 255 - a);
+ src += 4;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ access.reset(); // Flush BitmapScopedWriteAccess.
+ if (bSupportsBitmap32 && has_alpha)
+ graphic = BitmapEx(bitmap);
+ else
+ {
+ if (has_alpha)
+ graphic = BitmapEx(bitmap, bitmapAlpha);
+ else
+ graphic = BitmapEx(bitmap);
+ }
+ return success;
+}
+
+bool ImportWebpGraphic(SvStream& rStream, Graphic& rGraphic)
+{
+ bool bRetValue = readWebp(rStream, rGraphic);
+ if (!bRetValue)
+ rStream.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ return bRetValue;
+}
+
+bool ReadWebpInfo(SvStream& stream, Size& pixelSize, sal_uInt16& bitsPerPixel, bool& hasAlpha)
+{
+ std::vector<uint8_t> data;
+ WebPBitstreamFeatures features;
+ if (!readWebpInfo(stream, data, features))
+ return false;
+ pixelSize = Size(features.width, features.height);
+ bitsPerPixel = features.has_alpha ? 32 : 24;
+ hasAlpha = features.has_alpha;
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/webp/writer.cxx b/vcl/source/filter/webp/writer.cxx
new file mode 100644
index 000000000..f29dad007
--- /dev/null
+++ b/vcl/source/filter/webp/writer.cxx
@@ -0,0 +1,205 @@
+/* -*- 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 <vcl/graph.hxx>
+#include <tools/stream.hxx>
+#include <filter/WebpWriter.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <sal/log.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <webp/encode.h>
+
+static int writerFunction(const uint8_t* data, size_t size, const WebPPicture* picture)
+{
+ SvStream* stream = static_cast<SvStream*>(picture->custom_ptr);
+ return stream->WriteBytes(data, size) == size ? 1 : 0;
+}
+
+static WebPPreset presetToValue(std::u16string_view preset)
+{
+ if (o3tl::equalsIgnoreAsciiCase(preset, u"picture"))
+ return WEBP_PRESET_PICTURE;
+ if (o3tl::equalsIgnoreAsciiCase(preset, u"photo"))
+ return WEBP_PRESET_PHOTO;
+ if (o3tl::equalsIgnoreAsciiCase(preset, u"drawing"))
+ return WEBP_PRESET_DRAWING;
+ if (o3tl::equalsIgnoreAsciiCase(preset, u"icon"))
+ return WEBP_PRESET_ICON;
+ if (o3tl::equalsIgnoreAsciiCase(preset, u"text"))
+ return WEBP_PRESET_TEXT;
+ return WEBP_PRESET_DEFAULT;
+}
+
+static bool writeWebp(SvStream& rStream, const BitmapEx& bitmapEx, bool lossless,
+ std::u16string_view preset, int quality)
+{
+ WebPConfig config;
+ if (!WebPConfigInit(&config))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPConfigInit() failed");
+ return false;
+ }
+ if (lossless)
+ {
+ if (!WebPConfigLosslessPreset(&config, 6))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPConfigLosslessPreset() failed");
+ return false;
+ }
+ }
+ else
+ {
+ if (!WebPConfigPreset(&config, presetToValue(preset), quality))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPConfigPreset() failed");
+ return false;
+ }
+ }
+ // Here various parts of 'config' can be altered if wanted.
+ assert(WebPValidateConfig(&config));
+
+ const int width = bitmapEx.GetSizePixel().Width();
+ const int height = bitmapEx.GetSizePixel().Height();
+
+ WebPPicture picture;
+ if (!WebPPictureInit(&picture))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPPictureInit() failed");
+ return false;
+ }
+ picture.width = width;
+ picture.height = height;
+ picture.use_argb = lossless ? 1 : 0; // libwebp recommends argb only for lossless
+ comphelper::ScopeGuard freePicture([&picture]() { WebPPictureFree(&picture); });
+
+ // Apparently libwebp needs the entire image data at once in WebPPicture,
+ // so allocate it and copy there.
+ Bitmap bitmap(bitmapEx.GetBitmap());
+ AlphaMask bitmapAlpha;
+ if (bitmapEx.IsAlpha())
+ bitmapAlpha = bitmapEx.GetAlpha();
+ Bitmap::ScopedReadAccess access(bitmap);
+ AlphaMask::ScopedReadAccess accessAlpha(bitmapAlpha);
+ bool dataDone = false;
+ if (!access->IsBottomUp() && bitmapAlpha.IsEmpty())
+ {
+ // Try to directly copy the bitmap data.
+ switch (access->GetScanlineFormat())
+ {
+ case ScanlineFormat::N24BitTcRgb:
+ if (!WebPPictureImportRGB(&picture, access->GetBuffer(), access->GetScanlineSize()))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPPictureImportRGB() failed");
+ return false;
+ }
+ dataDone = true;
+ break;
+ case ScanlineFormat::N24BitTcBgr:
+ if (!WebPPictureImportBGR(&picture, access->GetBuffer(), access->GetScanlineSize()))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPPictureImportBGR() failed");
+ return false;
+ }
+ dataDone = true;
+ break;
+ // Our argb formats are premultiplied, so can't read directly using libwebp functions.
+ default:
+ break;
+ }
+ }
+ if (!dataDone)
+ {
+ // It would be simpler to convert the bitmap to 32bpp, but our 32bpp support is broken
+ // (it's unspecified whether it's premultiplied or not, for example). So handle this manually.
+ // Special handling for some common cases, generic otherwise.
+ if (!WebPPictureAlloc(&picture))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPPictureAlloc() failed");
+ return false;
+ }
+ std::vector<uint8_t> data;
+ const int bpp = 4;
+ data.resize(width * height * bpp);
+ if (!bitmapAlpha.IsEmpty())
+ {
+ for (tools::Long y = 0; y < access->Height(); ++y)
+ {
+ unsigned char* dst = data.data() + width * bpp * y;
+ const sal_uInt8* srcB = access->GetScanline(y);
+ const sal_uInt8* srcA = accessAlpha->GetScanline(y);
+ for (tools::Long x = 0; x < access->Width(); ++x)
+ {
+ BitmapColor color = access->GetPixelFromData(srcB, x);
+ BitmapColor transparency = accessAlpha->GetPixelFromData(srcA, x);
+ color.SetAlpha(255 - transparency.GetIndex());
+ dst[0] = color.GetRed();
+ dst[1] = color.GetGreen();
+ dst[2] = color.GetBlue();
+ dst[3] = color.GetAlpha();
+ dst += bpp;
+ }
+ }
+ }
+ else
+ {
+ for (tools::Long y = 0; y < access->Height(); ++y)
+ {
+ unsigned char* dst = data.data() + width * bpp * y;
+ const sal_uInt8* src = access->GetScanline(y);
+ for (tools::Long x = 0; x < access->Width(); ++x)
+ {
+ Color color = access->GetPixelFromData(src, x);
+ dst[0] = color.GetRed();
+ dst[1] = color.GetGreen();
+ dst[2] = color.GetBlue();
+ dst[3] = color.GetAlpha();
+ dst += bpp;
+ }
+ }
+ }
+ // And now import from the temporary data. Use the import function rather
+ // than writing the data directly to avoid the need to write the data
+ // in the exact format WebPPicture wants (YUV, etc.).
+ if (!WebPPictureImportRGBA(&picture, data.data(), width * bpp))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPPictureImportRGBA() failed");
+ return false;
+ }
+ }
+
+ picture.writer = writerFunction;
+ picture.custom_ptr = &rStream;
+ return WebPEncode(&config, &picture);
+}
+
+bool ExportWebpGraphic(SvStream& rStream, const Graphic& rGraphic,
+ FilterConfigItem* pFilterConfigItem)
+{
+ BitmapEx bitmap = rGraphic.GetBitmapEx();
+ // If lossless, neither presets nor quality matter.
+ bool lossless = pFilterConfigItem->ReadBool("Lossless", true);
+ // Preset is WebPPreset values.
+ const OUString preset = pFilterConfigItem->ReadString("Preset", "");
+ int quality = pFilterConfigItem->ReadInt32("Quality", 75);
+ return writeWebp(rStream, bitmap, lossless, preset, quality);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/wmf/emfwr.cxx b/vcl/source/filter/wmf/emfwr.cxx
new file mode 100644
index 000000000..b2782847b
--- /dev/null
+++ b/vcl/source/filter/wmf/emfwr.cxx
@@ -0,0 +1,1511 @@
+/* -*- 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 <sal/log.hxx>
+
+#include <algorithm>
+
+#include "emfwr.hxx"
+#include <tools/helpers.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/metaact.hxx>
+#include <memory>
+
+#define WIN_EMR_POLYGON 3
+#define WIN_EMR_POLYLINE 4
+#define WIN_EMR_POLYBEZIERTO 5
+#define WIN_EMR_POLYLINETO 6
+#define WIN_EMR_POLYPOLYGON 8
+#define WIN_EMR_SETWINDOWEXTEX 9
+#define WIN_EMR_SETWINDOWORGEX 10
+#define WIN_EMR_SETVIEWPORTEXTEX 11
+#define WIN_EMR_SETVIEWPORTORGEX 12
+#define WIN_EMR_EOF 14
+#define WIN_EMR_SETPIXELV 15
+#define WIN_EMR_SETMAPMODE 17
+#define WIN_EMR_SETBKMODE 18
+#define WIN_EMR_SETROP2 20
+#define WIN_EMR_SETTEXTALIGN 22
+#define WIN_EMR_SETTEXTCOLOR 24
+#define WIN_EMR_MOVETOEX 27
+#define WIN_EMR_INTERSECTCLIPRECT 30
+#define WIN_EMR_SAVEDC 33
+#define WIN_EMR_RESTOREDC 34
+#define WIN_EMR_SELECTOBJECT 37
+#define WIN_EMR_CREATEPEN 38
+#define WIN_EMR_CREATEBRUSHINDIRECT 39
+#define WIN_EMR_DELETEOBJECT 40
+#define WIN_EMR_ELLIPSE 42
+#define WIN_EMR_RECTANGLE 43
+#define WIN_EMR_ROUNDRECT 44
+#define WIN_EMR_LINETO 54
+#define WIN_EMR_BEGINPATH 59
+#define WIN_EMR_ENDPATH 60
+#define WIN_EMR_CLOSEFIGURE 61
+#define WIN_EMR_FILLPATH 62
+#define WIN_EMR_STROKEPATH 64
+
+#define WIN_EMR_GDICOMMENT 70
+#define WIN_EMR_STRETCHDIBITS 81
+#define WIN_EMR_EXTCREATEFONTINDIRECTW 82
+#define WIN_EMR_EXTTEXTOUTW 84
+
+#define WIN_SRCCOPY 0x00CC0020L
+#define WIN_SRCPAINT 0x00EE0086L
+#define WIN_SRCAND 0x008800C6L
+#define WIN_SRCINVERT 0x00660046L
+#define WIN_EMR_COMMENT_EMFPLUS 0x2B464D45L
+
+#define HANDLE_INVALID 0xffffffff
+#define MAXHANDLES 65000
+
+#define LINE_SELECT 0x00000001
+#define FILL_SELECT 0x00000002
+#define TEXT_SELECT 0x00000004
+
+/* Text Alignment Options */
+#define TA_RIGHT 2
+
+#define TA_TOP 0
+#define TA_BOTTOM 8
+#define TA_BASELINE 24
+#define TA_RTLREADING 256
+
+#define MM_ANISOTROPIC 8
+
+enum class EmfPlusRecordType
+{
+ Header = 0x4001,
+ EndOfFile = 0x4002,
+ GetDC = 0x4004,
+ FillPolygon = 0x400C,
+ SetAntiAliasMode = 0x401E,
+ SetInterpolationMode = 0x4021,
+ SetPixelOffsetMode = 0x4022,
+ SetCompositingQuality = 0x4024
+};
+
+void EMFWriter::ImplBeginCommentRecord( sal_Int32 nCommentType )
+{
+ ImplBeginRecord( WIN_EMR_GDICOMMENT );
+ m_rStm.SeekRel( 4 );
+ m_rStm.WriteInt32( nCommentType );
+}
+
+void EMFWriter::ImplEndCommentRecord()
+{
+ if( mbRecordOpen )
+ {
+ sal_Int32 nActPos = m_rStm.Tell();
+ m_rStm.Seek( mnRecordPos + 8 );
+ m_rStm.WriteUInt32( nActPos - mnRecordPos - 0xc );
+ m_rStm.Seek( nActPos );
+ }
+ ImplEndRecord();
+}
+
+void EMFWriter::ImplBeginPlusRecord( EmfPlusRecordType nType, sal_uInt16 nFlags )
+{
+ SAL_WARN_IF( mbRecordPlusOpen, "vcl", "Another EMF+ record is already opened!" );
+
+ if( !mbRecordPlusOpen )
+ {
+ mbRecordPlusOpen = true;
+ mnRecordPlusPos = m_rStm.Tell();
+
+ m_rStm.WriteUInt16( static_cast<sal_uInt16>(nType) ).WriteUInt16( nFlags );
+ m_rStm.SeekRel( 8 );
+ }
+}
+
+void EMFWriter::ImplEndPlusRecord()
+{
+ SAL_WARN_IF( !mbRecordPlusOpen, "vcl", "EMF+ Record was not opened!" );
+
+ if( mbRecordPlusOpen )
+ {
+ sal_Int32 nActPos = m_rStm.Tell();
+ sal_Int32 nSize = nActPos - mnRecordPlusPos;
+ m_rStm.Seek( mnRecordPlusPos + 4 );
+ m_rStm.WriteUInt32( nSize ) // Size
+ .WriteUInt32( nSize - 0xc ); // Data Size
+ m_rStm.Seek( nActPos );
+ mbRecordPlusOpen = false;
+ }
+}
+
+void EMFWriter::ImplPlusRecord( EmfPlusRecordType nType, sal_uInt16 nFlags )
+{
+ ImplBeginPlusRecord( nType, nFlags );
+ ImplEndPlusRecord();
+}
+
+void EMFWriter::WriteEMFPlusHeader( const Size &rMtfSizePix, const Size &rMtfSizeLog )
+{
+ ImplBeginCommentRecord( WIN_EMR_COMMENT_EMFPLUS );
+
+ sal_Int32 nDPIX = rMtfSizePix.Width()*25;
+ sal_Int32 nDivX = rMtfSizeLog.Width()/100;
+ if (nDivX)
+ nDPIX /= nDivX; // DPI X
+
+ sal_Int32 nDPIY = rMtfSizePix.Height()*25;
+ sal_Int32 nDivY = rMtfSizeLog.Height()/100;
+ if (nDivY)
+ nDPIY /= nDivY; // DPI Y
+
+ m_rStm.WriteInt16( sal_Int16(EmfPlusRecordType::Header) );
+ m_rStm.WriteInt16( 0x01 ) // Flags - Dual Mode // TODO: Check this
+ .WriteInt32( 0x1C ) // Size
+ .WriteInt32( 0x10 ) // Data Size
+ .WriteInt32( 0xdbc01002 ) // (lower 12bits) 1-> v1 2-> v1.1 // TODO: Check this
+ .WriteInt32( 0x01 ) // Video display
+ .WriteInt32( nDPIX )
+ .WriteInt32( nDPIY );
+ ImplEndCommentRecord();
+
+ // Write more properties
+ ImplBeginCommentRecord( WIN_EMR_COMMENT_EMFPLUS );
+ ImplPlusRecord( EmfPlusRecordType::SetPixelOffsetMode, 0x0 );
+ ImplPlusRecord( EmfPlusRecordType::SetAntiAliasMode, 0x09 ); // TODO: Check actual values for AntiAlias
+ ImplPlusRecord( EmfPlusRecordType::SetCompositingQuality, 0x0100 ); // Default Quality
+ ImplPlusRecord( EmfPlusRecordType::SetInterpolationMode, 0x00 ); // Default
+ ImplPlusRecord( EmfPlusRecordType::GetDC, 0x00 );
+ ImplEndCommentRecord();
+}
+
+void EMFWriter::ImplWritePlusEOF()
+{
+ ImplBeginCommentRecord( WIN_EMR_COMMENT_EMFPLUS );
+ ImplPlusRecord( EmfPlusRecordType::EndOfFile, 0x0 );
+ ImplEndCommentRecord();
+}
+
+void EMFWriter::ImplWritePlusColor( const Color& rColor, sal_uInt32 nTrans )
+{
+ sal_uInt32 nAlpha = ((100-nTrans)*0xFF)/100;
+ sal_uInt32 nCol = rColor.GetBlue();
+
+ nCol |= static_cast<sal_uInt32>(rColor.GetGreen()) << 8;
+ nCol |= static_cast<sal_uInt32>(rColor.GetRed()) << 16;
+ nCol |= ( nAlpha << 24 );
+ m_rStm.WriteUInt32( nCol );
+}
+
+void EMFWriter::ImplWritePlusPoint( const Point& rPoint )
+{
+ // Convert to pixels
+ const Point aPoint(maVDev->LogicToPixel( rPoint, maDestMapMode ));
+ m_rStm.WriteUInt16( aPoint.X() ).WriteUInt16( aPoint.Y() );
+}
+
+void EMFWriter::ImplWritePlusFillPolygonRecord( const tools::Polygon& rPoly, sal_uInt32 nTrans )
+{
+ ImplBeginCommentRecord( WIN_EMR_COMMENT_EMFPLUS );
+ if( rPoly.GetSize() )
+ {
+ ImplBeginPlusRecord( EmfPlusRecordType::FillPolygon, 0xC000 ); // Sets the color as well
+ ImplWritePlusColor( maVDev->GetFillColor(), nTrans );
+ m_rStm.WriteUInt32( rPoly.GetSize() );
+ for( sal_uInt16 i = 0; i < rPoly.GetSize(); i++ )
+ ImplWritePlusPoint( rPoly[ i ] );
+ ImplEndPlusRecord();
+ }
+ ImplEndCommentRecord();
+}
+
+bool EMFWriter::WriteEMF(const GDIMetaFile& rMtf)
+{
+ const sal_uInt64 nHeaderPos = m_rStm.Tell();
+
+ maVDev->EnableOutput( false );
+ maVDev->SetMapMode( rMtf.GetPrefMapMode() );
+ // don't work with pixel as destination map mode -> higher resolution preferable
+ maDestMapMode.SetMapUnit( MapUnit::Map100thMM );
+ mHandlesUsed = std::vector<bool>(MAXHANDLES, false);
+ mnHandleCount = mnRecordCount = mnRecordPos = mnRecordPlusPos = 0;
+ mbRecordOpen = mbRecordPlusOpen = false;
+ mbLineChanged = mbFillChanged = mbTextChanged = false;
+ mnLineHandle = mnFillHandle = mnTextHandle = HANDLE_INVALID;
+ mnHorTextAlign = 0;
+
+ const Size aMtfSizePix( maVDev->LogicToPixel( rMtf.GetPrefSize(), rMtf.GetPrefMapMode() ) );
+ const Size aMtfSizeLog( OutputDevice::LogicToLogic(rMtf.GetPrefSize(), rMtf.GetPrefMapMode(), MapMode(MapUnit::Map100thMM)) );
+
+ // seek over header
+ // use [MS-EMF 2.2.11] HeaderExtension2 Object, otherwise resulting EMF cannot be converted with GetWinMetaFileBits()
+ m_rStm.SeekRel( 108 );
+
+ // Write EMF+ Header
+ WriteEMFPlusHeader( aMtfSizePix, aMtfSizeLog );
+
+ // write initial values
+
+ // set 100th mm map mode in EMF
+ ImplBeginRecord( WIN_EMR_SETMAPMODE );
+ m_rStm.WriteInt32( MM_ANISOTROPIC );
+ ImplEndRecord();
+
+ ImplBeginRecord( WIN_EMR_SETVIEWPORTEXTEX );
+ m_rStm.WriteInt32( maVDev->GetDPIX() ).WriteInt32( maVDev->GetDPIY() );
+ ImplEndRecord();
+
+ ImplBeginRecord( WIN_EMR_SETWINDOWEXTEX );
+ m_rStm.WriteInt32( 2540 ).WriteInt32( 2540 );
+ ImplEndRecord();
+
+ ImplBeginRecord( WIN_EMR_SETVIEWPORTORGEX );
+ m_rStm.WriteInt32( 0 ).WriteInt32( 0 );
+ ImplEndRecord();
+
+ ImplBeginRecord( WIN_EMR_SETWINDOWORGEX );
+ m_rStm.WriteInt32( 0 ).WriteInt32( 0 );
+ ImplEndRecord();
+
+ ImplWriteRasterOp( RasterOp::OverPaint );
+
+ ImplBeginRecord( WIN_EMR_SETBKMODE );
+ m_rStm.WriteUInt32( 1 ); // TRANSPARENT
+ ImplEndRecord();
+
+ // write emf data
+ ImplWrite( rMtf );
+
+ ImplWritePlusEOF();
+
+ ImplBeginRecord( WIN_EMR_EOF );
+ m_rStm.WriteUInt32( 0 ) // nPalEntries
+ .WriteUInt32( 0x10 ) // offPalEntries
+ .WriteUInt32( 0x14 ); // nSizeLast
+ ImplEndRecord();
+
+ // write header
+ const sal_uInt64 nEndPos = m_rStm.Tell(); m_rStm.Seek( nHeaderPos );
+
+ m_rStm.WriteUInt32( 0x00000001 ).WriteUInt32( 108 ) //use [MS-EMF 2.2.11] HeaderExtension2 Object
+ .WriteInt32( 0 ).WriteInt32( 0 ).WriteInt32( aMtfSizePix.Width() - 1 ).WriteInt32( aMtfSizePix.Height() - 1 )
+ .WriteInt32( 0 ).WriteInt32( 0 ).WriteInt32( aMtfSizeLog.Width() - 1 ).WriteInt32( aMtfSizeLog.Height() - 1 )
+ .WriteUInt32( 0x464d4520 ).WriteUInt32( 0x10000 ).WriteUInt32( nEndPos - nHeaderPos )
+ .WriteUInt32( mnRecordCount ).WriteUInt16( mnHandleCount + 1 ).WriteUInt16( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 )
+ .WriteInt32( aMtfSizePix.Width() ).WriteInt32( aMtfSizePix.Height() )
+ .WriteInt32( aMtfSizeLog.Width() / 100 ).WriteInt32( aMtfSizeLog.Height() / 100 )
+ .WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 )
+ .WriteInt32( aMtfSizeLog.Width() * 10 ).WriteInt32( aMtfSizeLog.Height() * 10 ); //use [MS-EMF 2.2.11] HeaderExtension2 Object
+
+ m_rStm.Seek( nEndPos );
+ mHandlesUsed.clear();
+
+ return( m_rStm.GetError() == ERRCODE_NONE );
+}
+
+sal_uLong EMFWriter::ImplAcquireHandle()
+{
+ sal_uLong nHandle = HANDLE_INVALID;
+
+ for( sal_uLong i = 0; i < mHandlesUsed.size() && ( HANDLE_INVALID == nHandle ); i++ )
+ {
+ if( !mHandlesUsed[ i ] )
+ {
+ mHandlesUsed[ i ] = true;
+
+ if( ( nHandle = i ) == mnHandleCount )
+ mnHandleCount++;
+ }
+ }
+
+ SAL_WARN_IF( nHandle == HANDLE_INVALID, "vcl", "No more handles available" );
+ return( nHandle != HANDLE_INVALID ? nHandle + 1 : HANDLE_INVALID );
+}
+
+void EMFWriter::ImplReleaseHandle( sal_uLong nHandle )
+{
+ SAL_WARN_IF( !nHandle || ( nHandle >= mHandlesUsed.size() ), "vcl", "Handle out of range" );
+ mHandlesUsed[ nHandle - 1 ] = false;
+}
+
+void EMFWriter::ImplBeginRecord( sal_uInt32 nType )
+{
+ SAL_WARN_IF( mbRecordOpen, "vcl", "Another record is already opened!" );
+
+ if( !mbRecordOpen )
+ {
+ mbRecordOpen = true;
+ mnRecordPos = m_rStm.Tell();
+
+ m_rStm.WriteUInt32( nType );
+ m_rStm.SeekRel( 4 );
+ }
+}
+
+void EMFWriter::ImplEndRecord()
+{
+ SAL_WARN_IF( !mbRecordOpen, "vcl", "Record was not opened!" );
+
+ if( !mbRecordOpen )
+ return;
+
+ sal_Int32 nFillBytes, nActPos = m_rStm.Tell();
+ m_rStm.Seek( mnRecordPos + 4 );
+ nFillBytes = nActPos - mnRecordPos;
+ nFillBytes += 3; // each record has to be dword aligned
+ nFillBytes ^= 3;
+ nFillBytes &= 3;
+ m_rStm.WriteUInt32( ( nActPos - mnRecordPos ) + nFillBytes );
+ m_rStm.Seek( nActPos );
+ while( nFillBytes-- )
+ m_rStm.WriteUChar( 0 );
+ mnRecordCount++;
+ mbRecordOpen = false;
+
+}
+
+bool EMFWriter::ImplPrepareHandleSelect( sal_uInt32& rHandle, sal_uLong nSelectType )
+{
+ if( rHandle != HANDLE_INVALID )
+ {
+ sal_uInt32 nStockObject = 0x80000000;
+
+ if( LINE_SELECT == nSelectType )
+ nStockObject |= 0x00000007;
+ else if( FILL_SELECT == nSelectType )
+ nStockObject |= 0x00000001;
+ else if( TEXT_SELECT == nSelectType )
+ nStockObject |= 0x0000000a;
+
+ // select stock object first
+ ImplBeginRecord( WIN_EMR_SELECTOBJECT );
+ m_rStm.WriteUInt32( nStockObject );
+ ImplEndRecord();
+
+ // destroy handle of created object
+ ImplBeginRecord( WIN_EMR_DELETEOBJECT );
+ m_rStm.WriteUInt32( rHandle );
+ ImplEndRecord();
+
+ // mark handle as free
+ ImplReleaseHandle( rHandle );
+ }
+
+ rHandle = ImplAcquireHandle();
+
+ return( HANDLE_INVALID != rHandle );
+}
+
+void EMFWriter::ImplCheckLineAttr()
+{
+ if( !(mbLineChanged && ImplPrepareHandleSelect( mnLineHandle, LINE_SELECT )) )
+ return;
+
+ sal_uInt32 nStyle = maVDev->IsLineColor() ? 0 : 5;
+
+ ImplBeginRecord( WIN_EMR_CREATEPEN );
+ m_rStm.WriteUInt32( mnLineHandle ).WriteUInt32( nStyle ).WriteUInt32( 0/*nWidth*/ ).WriteUInt32( 0/*nHeight*/ );
+ ImplWriteColor( maVDev->GetLineColor() );
+ ImplEndRecord();
+
+ ImplBeginRecord( WIN_EMR_SELECTOBJECT );
+ m_rStm.WriteUInt32( mnLineHandle );
+ ImplEndRecord();
+}
+
+void EMFWriter::ImplCheckFillAttr()
+{
+ if( !(mbFillChanged && ImplPrepareHandleSelect( mnFillHandle, FILL_SELECT )) )
+ return;
+
+ sal_uInt32 nStyle = maVDev->IsFillColor() ? 0 : 1;
+
+ ImplBeginRecord( WIN_EMR_CREATEBRUSHINDIRECT );
+ m_rStm.WriteUInt32( mnFillHandle ).WriteUInt32( nStyle );
+ ImplWriteColor( maVDev->GetFillColor() );
+ m_rStm.WriteUInt32( 0/*nPatternStyle*/ );
+ ImplEndRecord();
+
+ ImplBeginRecord( WIN_EMR_SELECTOBJECT );
+ m_rStm.WriteUInt32( mnFillHandle );
+ ImplEndRecord();
+}
+
+void EMFWriter::ImplCheckTextAttr()
+{
+ if( !(mbTextChanged && ImplPrepareHandleSelect( mnTextHandle, TEXT_SELECT )) )
+ return;
+
+ const vcl::Font& rFont = maVDev->GetFont();
+ const OUString& aFontName( rFont.GetFamilyName() );
+ sal_Int32 nWeight;
+ sal_uInt16 i;
+ sal_uInt8 nPitchAndFamily;
+
+ // tdf#127471 adapt nFontWidth from NormedFontScaling to
+ // Windows-like notation if used for text scaling
+ const tools::Long nFontHeight(rFont.GetFontSize().Height());
+ tools::Long nFontWidth(rFont.GetFontSize().Width());
+
+#ifndef _WIN32
+ const bool bFontScaledHorizontally(nFontWidth != 0 && nFontWidth != nFontHeight);
+
+ if(bFontScaledHorizontally)
+ {
+ // tdf#127471 nFontWidth is the non-Windows NormedFontScaling, need to convert to
+ // Windows-like notation with pre-multiplied AvgFontWidth since EMF/WMF are Windows
+ // specific formats.
+ const tools::Long nAverageFontWidth(rFont.GetOrCalculateAverageFontWidth());
+
+ if(nFontHeight > 0)
+ {
+ const double fScaleFactor(static_cast<double>(nAverageFontWidth) / static_cast<double>(nFontHeight));
+ nFontWidth = static_cast<tools::Long>(static_cast<double>(nFontWidth) * fScaleFactor);
+ }
+ }
+#endif
+
+ ImplBeginRecord( WIN_EMR_EXTCREATEFONTINDIRECTW );
+ m_rStm.WriteUInt32( mnTextHandle );
+ ImplWriteExtent( -nFontHeight );
+ ImplWriteExtent( nFontWidth );
+ m_rStm.WriteInt32( rFont.GetOrientation().get() ).WriteInt32( rFont.GetOrientation().get() );
+
+ switch( rFont.GetWeight() )
+ {
+ case WEIGHT_THIN: nWeight = 100; break;
+ case WEIGHT_ULTRALIGHT: nWeight = 200; break;
+ case WEIGHT_LIGHT: nWeight = 300; break;
+ case WEIGHT_SEMILIGHT: nWeight = 300; break;
+ case WEIGHT_NORMAL: nWeight = 400; break;
+ case WEIGHT_MEDIUM: nWeight = 500; break;
+ case WEIGHT_SEMIBOLD: nWeight = 600; break;
+ case WEIGHT_BOLD: nWeight = 700; break;
+ case WEIGHT_ULTRABOLD: nWeight = 800; break;
+ case WEIGHT_BLACK: nWeight = 900; break;
+ default: nWeight = 0; break;
+ }
+
+ m_rStm.WriteInt32( nWeight );
+ m_rStm.WriteUChar( ( ITALIC_NONE == rFont.GetItalic() ) ? 0 : 1 );
+ m_rStm.WriteUChar( ( LINESTYLE_NONE == rFont.GetUnderline() ) ? 0 : 1 );
+ m_rStm.WriteUChar( ( STRIKEOUT_NONE == rFont.GetStrikeout() ) ? 0 : 1 );
+ m_rStm.WriteUChar( ( RTL_TEXTENCODING_SYMBOL == rFont.GetCharSet() ) ? 2 : 0 );
+ m_rStm.WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 );
+
+ switch( rFont.GetPitch() )
+ {
+ case PITCH_FIXED: nPitchAndFamily = 0x01; break;
+ case PITCH_VARIABLE: nPitchAndFamily = 0x02; break;
+ default: nPitchAndFamily = 0x00; break;
+ }
+
+ switch( rFont.GetFamilyType() )
+ {
+ case FAMILY_DECORATIVE: nPitchAndFamily |= 0x50; break;
+ case FAMILY_MODERN: nPitchAndFamily |= 0x30; break;
+ case FAMILY_ROMAN: nPitchAndFamily |= 0x10; break;
+ case FAMILY_SCRIPT: nPitchAndFamily |= 0x40; break;
+ case FAMILY_SWISS: nPitchAndFamily |= 0x20; break;
+ default: break;
+ }
+
+ m_rStm.WriteUChar( nPitchAndFamily );
+
+ for( i = 0; i < 32; i++ )
+ m_rStm.WriteUInt16( ( i < aFontName.getLength() ) ? aFontName[ i ] : 0 );
+
+ // dummy elfFullName
+ for( i = 0; i < 64; i++ )
+ m_rStm.WriteUInt16( 0 );
+
+ // dummy elfStyle
+ for( i = 0; i < 32; i++ )
+ m_rStm.WriteUInt16( 0 );
+
+ // dummy elfVersion, elfStyleSize, elfMatch, elfReserved
+ m_rStm.WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ) ;
+
+ // dummy elfVendorId
+ m_rStm.WriteUInt32( 0 );
+
+ // dummy elfCulture
+ m_rStm.WriteUInt32( 0 );
+
+ // dummy elfPanose
+ m_rStm.WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 );
+
+ // fill record to get a record size divideable by 4
+ m_rStm.WriteUInt16( 0 );
+
+ ImplEndRecord();
+
+ // TextAlign
+ sal_uInt32 nTextAlign;
+
+ switch( rFont.GetAlignment() )
+ {
+ case ALIGN_TOP: nTextAlign = TA_TOP; break;
+ case ALIGN_BOTTOM: nTextAlign = TA_BOTTOM; break;
+ default: nTextAlign = TA_BASELINE; break;
+ }
+ nTextAlign |= mnHorTextAlign;
+
+ ImplBeginRecord( WIN_EMR_SETTEXTALIGN );
+ m_rStm.WriteUInt32( nTextAlign );
+ ImplEndRecord();
+
+ // Text color
+ ImplBeginRecord( WIN_EMR_SETTEXTCOLOR );
+ ImplWriteColor( maVDev->GetTextColor() );
+ ImplEndRecord();
+
+ ImplBeginRecord( WIN_EMR_SELECTOBJECT );
+ m_rStm.WriteUInt32( mnTextHandle );
+ ImplEndRecord();
+
+}
+
+void EMFWriter::ImplWriteColor( const Color& rColor )
+{
+ sal_uInt32 nCol = rColor.GetRed();
+
+ nCol |= static_cast<sal_uInt32>(rColor.GetGreen()) << 8;
+ nCol |= static_cast<sal_uInt32>(rColor.GetBlue()) << 16;
+
+ m_rStm.WriteUInt32( nCol );
+}
+
+void EMFWriter::ImplWriteRasterOp( RasterOp eRop )
+{
+ sal_uInt32 nROP2;
+
+ switch( eRop )
+ {
+ case RasterOp::Invert: nROP2 = 6; break;
+ case RasterOp::Xor: nROP2 = 7; break;
+ default: nROP2 = 13;break;
+ }
+
+ ImplBeginRecord( WIN_EMR_SETROP2 );
+ m_rStm.WriteUInt32( nROP2 );
+ ImplEndRecord();
+}
+
+void EMFWriter::ImplWriteExtent( tools::Long nExtent )
+{
+ nExtent = OutputDevice::LogicToLogic( Size( nExtent, 0 ), maVDev->GetMapMode(), maDestMapMode ).Width();
+ m_rStm.WriteInt32( nExtent );
+}
+
+void EMFWriter::ImplWritePoint( const Point& rPoint )
+{
+ const Point aPoint( OutputDevice::LogicToLogic( rPoint, maVDev->GetMapMode(), maDestMapMode ));
+ m_rStm.WriteInt32( aPoint.X() ).WriteInt32( aPoint.Y() );
+}
+
+void EMFWriter::ImplWriteSize( const Size& rSize)
+{
+ const Size aSize( OutputDevice::LogicToLogic( rSize, maVDev->GetMapMode(), maDestMapMode ));
+ m_rStm.WriteInt32( aSize.Width() ).WriteInt32( aSize.Height() );
+}
+
+void EMFWriter::ImplWriteRect( const tools::Rectangle& rRect )
+{
+ const tools::Rectangle aRect( OutputDevice::LogicToLogic ( rRect, maVDev->GetMapMode(), maDestMapMode ));
+ auto right = aRect.IsWidthEmpty() ? aRect.Left() : aRect.Right();
+ auto bottom = aRect.IsHeightEmpty() ? aRect.Top() : aRect.Bottom();
+ m_rStm
+ .WriteInt32( aRect.Left() )
+ .WriteInt32( aRect.Top() )
+ .WriteInt32( right )
+ .WriteInt32( bottom );
+}
+
+void EMFWriter::ImplWritePolygonRecord( const tools::Polygon& rPoly, bool bClose )
+{
+ if( !rPoly.GetSize() )
+ return;
+
+ if( rPoly.HasFlags() )
+ ImplWritePath( rPoly, bClose );
+ else
+ {
+ if( bClose )
+ ImplCheckFillAttr();
+
+ ImplCheckLineAttr();
+
+ ImplBeginRecord( bClose ? WIN_EMR_POLYGON : WIN_EMR_POLYLINE );
+ ImplWriteRect( rPoly.GetBoundRect() );
+ m_rStm.WriteUInt32( rPoly.GetSize() );
+
+ for( sal_uInt16 i = 0; i < rPoly.GetSize(); i++ )
+ ImplWritePoint( rPoly[ i ] );
+
+ ImplEndRecord();
+ }
+}
+
+void EMFWriter::ImplWritePolyPolygonRecord( const tools::PolyPolygon& rPolyPoly )
+{
+ sal_uInt16 n, i, nPolyCount = rPolyPoly.Count();
+
+ if( !nPolyCount )
+ return;
+
+ if( 1 == nPolyCount )
+ ImplWritePolygonRecord( rPolyPoly[ 0 ], true );
+ else
+ {
+ bool bHasFlags = false;
+ sal_uInt32 nTotalPoints = 0;
+
+ for( i = 0; i < nPolyCount; i++ )
+ {
+ nTotalPoints += rPolyPoly[ i ].GetSize();
+ if ( rPolyPoly[ i ].HasFlags() )
+ bHasFlags = true;
+ }
+ if( nTotalPoints )
+ {
+ if ( bHasFlags )
+ ImplWritePath( rPolyPoly, true );
+ else
+ {
+ ImplCheckFillAttr();
+ ImplCheckLineAttr();
+
+ ImplBeginRecord( WIN_EMR_POLYPOLYGON );
+ ImplWriteRect( rPolyPoly.GetBoundRect() );
+ m_rStm.WriteUInt32( nPolyCount ).WriteUInt32( nTotalPoints );
+
+ for( i = 0; i < nPolyCount; i++ )
+ m_rStm.WriteUInt32( rPolyPoly[ i ].GetSize() );
+
+ for( i = 0; i < nPolyCount; i++ )
+ {
+ const tools::Polygon& rPoly = rPolyPoly[ i ];
+
+ for( n = 0; n < rPoly.GetSize(); n++ )
+ ImplWritePoint( rPoly[ n ] );
+ }
+ ImplEndRecord();
+ }
+ }
+ }
+}
+
+void EMFWriter::ImplWritePath( const tools::PolyPolygon& rPolyPoly, bool bClosed )
+{
+ if ( bClosed )
+ ImplCheckFillAttr();
+ ImplCheckLineAttr();
+
+ ImplBeginRecord( WIN_EMR_BEGINPATH );
+ ImplEndRecord();
+
+ sal_uInt16 i, n, o, nPolyCount = rPolyPoly.Count();
+ for ( i = 0; i < nPolyCount; i++ )
+ {
+ n = 0;
+ const tools::Polygon& rPoly = rPolyPoly[ i ];
+ while ( n < rPoly.GetSize() )
+ {
+ if( n == 0 )
+ {
+ ImplBeginRecord( WIN_EMR_MOVETOEX );
+ ImplWritePoint( rPoly[ 0 ] );
+ ImplEndRecord();
+ n++;
+ continue;
+ }
+
+ sal_uInt16 nBezPoints = 0;
+
+ while ( ( ( nBezPoints + n + 2 ) < rPoly.GetSize() ) && ( rPoly.GetFlags( nBezPoints + n ) == PolyFlags::Control ) )
+ nBezPoints += 3;
+
+ if ( nBezPoints )
+ {
+ ImplBeginRecord( WIN_EMR_POLYBEZIERTO );
+ tools::Polygon aNewPoly( nBezPoints + 1 );
+ aNewPoly[ 0 ] = rPoly[ n - 1 ];
+ for ( o = 0; o < nBezPoints; o++ )
+ aNewPoly[ o + 1 ] = rPoly[ n + o ];
+ ImplWriteRect( aNewPoly.GetBoundRect() );
+ m_rStm.WriteUInt32( nBezPoints );
+ for( o = 1; o < aNewPoly.GetSize(); o++ )
+ ImplWritePoint( aNewPoly[ o ] );
+ ImplEndRecord();
+ n = n + nBezPoints;
+ }
+ else
+ {
+ sal_uInt16 nPoints = 1;
+ while( ( nPoints + n ) < rPoly.GetSize() && ( rPoly.GetFlags( nPoints + n ) != PolyFlags::Control ) )
+ nPoints++;
+
+ if ( nPoints > 1 )
+ {
+ ImplBeginRecord( WIN_EMR_POLYLINETO );
+ tools::Polygon aNewPoly( nPoints + 1 );
+ aNewPoly[ 0 ] = rPoly[ n - 1];
+ for ( o = 1; o <= nPoints; o++ )
+ aNewPoly[ o ] = rPoly[ n - 1 + o ];
+ ImplWriteRect( aNewPoly.GetBoundRect() );
+ m_rStm.WriteUInt32( nPoints );
+ for( o = 1; o < aNewPoly.GetSize(); o++ )
+ ImplWritePoint( aNewPoly[ o ] );
+ ImplEndRecord();
+ }
+ else
+ {
+ ImplBeginRecord( WIN_EMR_LINETO );
+ ImplWritePoint( rPoly[ n ] );
+ ImplEndRecord();
+ }
+ n = n + nPoints;
+ }
+ if ( bClosed && ( n == rPoly.GetSize() ) )
+ {
+ ImplBeginRecord( WIN_EMR_CLOSEFIGURE );
+ ImplEndRecord();
+ }
+ }
+ }
+ ImplBeginRecord( WIN_EMR_ENDPATH );
+ ImplEndRecord();
+ ImplBeginRecord( bClosed ? WIN_EMR_FILLPATH : WIN_EMR_STROKEPATH );
+ ImplWriteRect( rPolyPoly.GetBoundRect() );
+ ImplEndRecord();
+}
+
+void EMFWriter::ImplWriteBmpRecord( const Bitmap& rBmp, const Point& rPt,
+ const Size& rSz, sal_uInt32 nROP )
+{
+ if( rBmp.IsEmpty() )
+ return;
+
+ SvMemoryStream aMemStm( 65535, 65535 );
+ const Size aBmpSizePixel( rBmp.GetSizePixel() );
+
+ ImplBeginRecord( WIN_EMR_STRETCHDIBITS );
+ ImplWriteRect( tools::Rectangle( rPt, rSz ) );
+ ImplWritePoint( rPt );
+ m_rStm.WriteInt32( 0 ).WriteInt32( 0 ).WriteInt32( aBmpSizePixel.Width() ).WriteInt32( aBmpSizePixel.Height() );
+
+ // write offset positions and sizes later
+ const sal_uInt64 nOffPos = m_rStm.Tell();
+ m_rStm.SeekRel( 16 );
+
+ m_rStm.WriteUInt32( 0 ).WriteInt32( ( RasterOp::Xor == maVDev->GetRasterOp() && WIN_SRCCOPY == nROP ) ? WIN_SRCINVERT : nROP );
+ ImplWriteSize( rSz );
+
+ WriteDIB(rBmp, aMemStm, true, false);
+
+ sal_uInt32 nDIBSize = aMemStm.Tell(), nHeaderSize, nCompression, nColsUsed, nPalCount, nImageSize;
+ sal_uInt16 nBitCount;
+
+ // get DIB parameters
+ aMemStm.Seek( 0 );
+ aMemStm.ReadUInt32( nHeaderSize );
+ aMemStm.SeekRel( 10 );
+ aMemStm.ReadUInt16( nBitCount ).ReadUInt32( nCompression ).ReadUInt32( nImageSize );
+ aMemStm.SeekRel( 8 );
+ aMemStm.ReadUInt32( nColsUsed );
+
+ if (nBitCount <= 8)
+ {
+ if (nColsUsed)
+ nPalCount = nColsUsed;
+ else
+ nPalCount = 1 << static_cast<sal_uInt32>(nBitCount);
+ }
+ else
+ {
+ if (nCompression == BITFIELDS)
+ nPalCount = 3;
+ else
+ nPalCount = 0;
+ }
+
+ sal_uInt32 nPalSize = nPalCount * 4;
+
+ m_rStm.WriteBytes( aMemStm.GetData(), nDIBSize );
+
+ const sal_uInt64 nEndPos = m_rStm.Tell();
+ m_rStm.Seek( nOffPos );
+ m_rStm.WriteUInt32( 80 ).WriteUInt32( nHeaderSize + nPalSize );
+ m_rStm.WriteUInt32( 80 + nHeaderSize + nPalSize ).WriteUInt32( nImageSize );
+ m_rStm.Seek( nEndPos );
+
+ ImplEndRecord();
+
+}
+
+void EMFWriter::ImplWriteTextRecord( const Point& rPos, const OUString& rText, o3tl::span<const sal_Int32> pDXArray, sal_uInt32 nWidth )
+{
+ sal_Int32 nLen = rText.getLength(), i;
+
+ if( !nLen )
+ return;
+
+ sal_uInt32 nNormWidth;
+ std::vector<sal_Int32> aOwnArray;
+ o3tl::span<const sal_Int32> pDX;
+
+ // get text sizes
+ if( !pDXArray.empty() )
+ {
+ nNormWidth = maVDev->GetTextWidth( rText );
+ pDX = pDXArray;
+ }
+ else
+ {
+ nNormWidth = maVDev->GetTextArray( rText, &aOwnArray );
+ pDX = aOwnArray;
+ }
+
+ if( nLen > 1 )
+ {
+ nNormWidth = pDX[ nLen - 2 ] + maVDev->GetTextWidth( OUString(rText[ nLen - 1 ]) );
+
+ if( nWidth && nNormWidth && ( nWidth != nNormWidth ) )
+ {
+ if (!pDXArray.empty())
+ {
+ aOwnArray.insert(aOwnArray.begin(), pDXArray.begin(), pDXArray.end());
+ pDX = aOwnArray;
+ }
+ const double fFactor = static_cast<double>(nWidth) / nNormWidth;
+
+ for( i = 0; i < ( nLen - 1 ); i++ )
+ aOwnArray[ i ] = FRound( aOwnArray[ i ] * fFactor );
+ }
+ }
+
+ // write text record
+ ImplBeginRecord( WIN_EMR_EXTTEXTOUTW );
+
+ ImplWriteRect( tools::Rectangle( rPos, Size( nNormWidth, maVDev->GetTextHeight() ) ) );
+ m_rStm.WriteUInt32( 1 );
+ m_rStm.WriteInt32( 0 ).WriteInt32( 0 );
+ ImplWritePoint( rPos );
+ m_rStm.WriteUInt32( nLen ).WriteUInt32( 76 ).WriteUInt32( 2 );
+ m_rStm.WriteInt32( 0 ).WriteInt32( 0 ).WriteInt32( 0 ).WriteInt32( 0 );
+ m_rStm.WriteUInt32( 76 + ( nLen << 1 ) + ( (nLen & 1 ) ? 2 : 0 ) );
+
+ // write text
+ for( i = 0; i < nLen; i++ )
+ m_rStm.WriteUInt16( rText[ i ] );
+
+ // padding word
+ if( nLen & 1 )
+ m_rStm.WriteUInt16( 0 );
+
+ // write DX array
+ ImplWriteExtent( pDX[ 0 ] );
+
+ if( nLen > 1 )
+ {
+ for( i = 1; i < ( nLen - 1 ); i++ )
+ ImplWriteExtent( pDX[ i ] - pDX[ i - 1 ] );
+
+ ImplWriteExtent( pDX[ nLen - 2 ] / ( nLen - 1 ) );
+ }
+
+ ImplEndRecord();
+
+}
+
+void EMFWriter::Impl_handleLineInfoPolyPolygons(const LineInfo& rInfo, const basegfx::B2DPolygon& rLinePolygon)
+{
+ if(!rLinePolygon.count())
+ return;
+
+ basegfx::B2DPolyPolygon aLinePolyPolygon(rLinePolygon);
+ basegfx::B2DPolyPolygon aFillPolyPolygon;
+
+ rInfo.applyToB2DPolyPolygon(aLinePolyPolygon, aFillPolyPolygon);
+
+ if(aLinePolyPolygon.count())
+ {
+ for(auto const& rB2DPolygon : std::as_const(aLinePolyPolygon))
+ {
+ ImplWritePolygonRecord( tools::Polygon(rB2DPolygon), false );
+ }
+ }
+
+ if(!aFillPolyPolygon.count())
+ return;
+
+ const Color aOldLineColor(maVDev->GetLineColor());
+ const Color aOldFillColor(maVDev->GetFillColor());
+
+ maVDev->SetLineColor();
+ maVDev->SetFillColor(aOldLineColor);
+
+ for(auto const& rB2DPolygon : std::as_const(aFillPolyPolygon))
+ {
+ ImplWritePolyPolygonRecord(tools::PolyPolygon( tools::Polygon(rB2DPolygon) ));
+ }
+
+ maVDev->SetLineColor(aOldLineColor);
+ maVDev->SetFillColor(aOldFillColor);
+}
+
+void EMFWriter::ImplWrite( const GDIMetaFile& rMtf )
+{
+ for( size_t j = 0, nActionCount = rMtf.GetActionSize(); j < nActionCount; j++ )
+ {
+ const MetaAction* pAction = rMtf.GetAction( j );
+ const MetaActionType nType = pAction->GetType();
+
+ switch( nType )
+ {
+ case MetaActionType::PIXEL:
+ {
+ const MetaPixelAction* pA = static_cast<const MetaPixelAction*>(pAction);
+
+ ImplCheckLineAttr();
+ ImplBeginRecord( WIN_EMR_SETPIXELV );
+ ImplWritePoint( pA->GetPoint() );
+ ImplWriteColor( pA->GetColor() );
+ ImplEndRecord();
+ }
+ break;
+
+ case MetaActionType::POINT:
+ {
+ if( maVDev->IsLineColor() )
+ {
+ const MetaPointAction* pA = static_cast<const MetaPointAction*>(pAction);
+
+ ImplCheckLineAttr();
+ ImplBeginRecord( WIN_EMR_SETPIXELV );
+ ImplWritePoint( pA->GetPoint() );
+ ImplWriteColor( maVDev->GetLineColor() );
+ ImplEndRecord();
+ }
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ if( maVDev->IsLineColor() )
+ {
+ const MetaLineAction* pA = static_cast<const MetaLineAction*>(pAction);
+
+ if(pA->GetLineInfo().IsDefault())
+ {
+ ImplCheckLineAttr();
+
+ ImplBeginRecord( WIN_EMR_MOVETOEX );
+ ImplWritePoint( pA->GetStartPoint() );
+ ImplEndRecord();
+
+ ImplBeginRecord( WIN_EMR_LINETO );
+ ImplWritePoint( pA->GetEndPoint() );
+ ImplEndRecord();
+
+ ImplBeginRecord( WIN_EMR_SETPIXELV );
+ ImplWritePoint( pA->GetEndPoint() );
+ ImplWriteColor( maVDev->GetLineColor() );
+ ImplEndRecord();
+ }
+ else
+ {
+ // LineInfo used; handle Dash/Dot and fat lines
+ basegfx::B2DPolygon aPolygon;
+ aPolygon.append(basegfx::B2DPoint(pA->GetStartPoint().X(), pA->GetStartPoint().Y()));
+ aPolygon.append(basegfx::B2DPoint(pA->GetEndPoint().X(), pA->GetEndPoint().Y()));
+ Impl_handleLineInfoPolyPolygons(pA->GetLineInfo(), aPolygon);
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ if( maVDev->IsLineColor() || maVDev->IsFillColor() )
+ {
+ const MetaRectAction* pA = static_cast<const MetaRectAction*>(pAction);
+
+ ImplCheckFillAttr();
+ ImplCheckLineAttr();
+
+ ImplBeginRecord( WIN_EMR_RECTANGLE );
+ ImplWriteRect( pA->GetRect() );
+ ImplEndRecord();
+ }
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ if( maVDev->IsLineColor() || maVDev->IsFillColor() )
+ {
+ const MetaRoundRectAction* pA = static_cast<const MetaRoundRectAction*>(pAction);
+
+ ImplCheckFillAttr();
+ ImplCheckLineAttr();
+
+ ImplBeginRecord( WIN_EMR_ROUNDRECT );
+ ImplWriteRect( pA->GetRect() );
+ ImplWriteSize( Size( pA->GetHorzRound(), pA->GetVertRound() ) );
+ ImplEndRecord();
+ }
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ if( maVDev->IsLineColor() || maVDev->IsFillColor() )
+ {
+ const MetaEllipseAction* pA = static_cast<const MetaEllipseAction*>(pAction);
+
+ ImplCheckFillAttr();
+ ImplCheckLineAttr();
+
+ ImplBeginRecord( WIN_EMR_ELLIPSE );
+ ImplWriteRect( pA->GetRect() );
+ ImplEndRecord();
+ }
+ }
+ break;
+
+ case MetaActionType::ARC:
+ case MetaActionType::PIE:
+ case MetaActionType::CHORD:
+ case MetaActionType::POLYGON:
+ {
+ if( maVDev->IsLineColor() || maVDev->IsFillColor() )
+ {
+ tools::Polygon aPoly;
+
+ switch( nType )
+ {
+ case MetaActionType::ARC:
+ {
+ const MetaArcAction* pA = static_cast<const MetaArcAction*>(pAction);
+ aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Arc );
+ }
+ break;
+
+ case MetaActionType::PIE:
+ {
+ const MetaPieAction* pA = static_cast<const MetaPieAction*>(pAction);
+ aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Pie );
+ }
+ break;
+
+ case MetaActionType::CHORD:
+ {
+ const MetaChordAction* pA = static_cast<const MetaChordAction*>(pAction);
+ aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Chord );
+ }
+ break;
+
+ case MetaActionType::POLYGON:
+ aPoly = static_cast<const MetaPolygonAction*>(pAction)->GetPolygon();
+ break;
+ default: break;
+ }
+
+ ImplWritePolygonRecord( aPoly, nType != MetaActionType::ARC );
+ }
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ if( maVDev->IsLineColor() )
+ {
+ const MetaPolyLineAction* pA = static_cast<const MetaPolyLineAction*>(pAction);
+ const tools::Polygon& rPoly = pA->GetPolygon();
+
+ if( rPoly.GetSize() )
+ {
+ if(pA->GetLineInfo().IsDefault())
+ {
+ ImplWritePolygonRecord( rPoly, false );
+ }
+ else
+ {
+ // LineInfo used; handle Dash/Dot and fat lines
+ Impl_handleLineInfoPolyPolygons(pA->GetLineInfo(), rPoly.getB2DPolygon());
+ }
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ {
+ if( maVDev->IsLineColor() || maVDev->IsFillColor() )
+ ImplWritePolyPolygonRecord( static_cast<const MetaPolyPolygonAction*>(pAction)->GetPolyPolygon() );
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pAction);
+ GDIMetaFile aTmpMtf;
+
+ Gradient aGradient = pA->GetGradient();
+ aGradient.AddGradientActions( pA->GetRect(), aTmpMtf );
+ ImplWrite( aTmpMtf );
+ }
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ const MetaHatchAction* pA = static_cast<const MetaHatchAction*>(pAction);
+ GDIMetaFile aTmpMtf;
+
+ maVDev->AddHatchActions( pA->GetPolyPolygon(), pA->GetHatch(), aTmpMtf );
+ ImplWrite( aTmpMtf );
+ }
+ break;
+
+ case MetaActionType::Transparent:
+ {
+ const tools::PolyPolygon& rPolyPoly = static_cast<const MetaTransparentAction*>(pAction)->GetPolyPolygon();
+ if( rPolyPoly.Count() )
+ ImplWritePlusFillPolygonRecord( rPolyPoly[0], static_cast<const MetaTransparentAction*>(pAction)->GetTransparence() );
+ ImplCheckFillAttr();
+ ImplCheckLineAttr();
+ ImplWritePolyPolygonRecord( rPolyPoly );
+
+ ImplBeginCommentRecord( WIN_EMR_COMMENT_EMFPLUS );
+ ImplPlusRecord( EmfPlusRecordType::GetDC, 0x00 );
+ ImplEndCommentRecord();
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pAction);
+
+ GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() );
+ Point aSrcPt( aTmpMtf.GetPrefMapMode().GetOrigin() );
+ const Size aSrcSize( aTmpMtf.GetPrefSize() );
+ const Point aDestPt( pA->GetPoint() );
+ const Size aDestSize( pA->GetSize() );
+ const double fScaleX = aSrcSize.Width() ? static_cast<double>(aDestSize.Width()) / aSrcSize.Width() : 1.0;
+ const double fScaleY = aSrcSize.Height() ? static_cast<double>(aDestSize.Height()) / aSrcSize.Height() : 1.0;
+ tools::Long nMoveX, nMoveY;
+
+ if( fScaleX != 1.0 || fScaleY != 1.0 )
+ {
+ aTmpMtf.Scale( fScaleX, fScaleY );
+ aSrcPt.setX( FRound( aSrcPt.X() * fScaleX ) );
+ aSrcPt.setY( FRound( aSrcPt.Y() * fScaleY ) );
+ }
+
+ nMoveX = aDestPt.X() - aSrcPt.X();
+ nMoveY = aDestPt.Y() - aSrcPt.Y();
+
+ if( nMoveX || nMoveY )
+ aTmpMtf.Move( nMoveX, nMoveY );
+
+ ImplCheckFillAttr();
+ ImplCheckLineAttr();
+ ImplCheckTextAttr();
+ ImplWrite( aTmpMtf );
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pAction);
+ const GDIMetaFile& aSubstitute( pA->GetSubstitute() );
+
+ for( size_t i = 0, nCount = aSubstitute.GetActionSize(); i < nCount; i++ )
+ {
+ const MetaAction* pSubstAct = aSubstitute.GetAction( i );
+ if( pSubstAct->GetType() == MetaActionType::BMPSCALE )
+ {
+ maVDev->Push();
+ ImplBeginRecord( WIN_EMR_SAVEDC );
+ ImplEndRecord();
+
+ MapMode aMapMode( aSubstitute.GetPrefMapMode() );
+ Size aOutSize( OutputDevice::LogicToLogic( pA->GetSize(), maVDev->GetMapMode(), aMapMode ) );
+ aMapMode.SetScaleX( Fraction( aOutSize.Width(), aSubstitute.GetPrefSize().Width() ) );
+ aMapMode.SetScaleY( Fraction( aOutSize.Height(), aSubstitute.GetPrefSize().Height() ) );
+ aMapMode.SetOrigin( OutputDevice::LogicToLogic( pA->GetPoint(), maVDev->GetMapMode(), aMapMode ) );
+ maVDev->SetMapMode( aMapMode );
+ ImplWrite( aSubstitute );
+
+ maVDev->Pop();
+ ImplBeginRecord( WIN_EMR_RESTOREDC );
+ m_rStm.WriteInt32( -1 );
+ ImplEndRecord();
+ break;
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::BMP:
+ {
+ const MetaBmpAction* pA = static_cast<const MetaBmpAction *>(pAction);
+ ImplWriteBmpRecord( pA->GetBitmap(), pA->GetPoint(), maVDev->PixelToLogic( pA->GetBitmap().GetSizePixel() ), WIN_SRCCOPY );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
+ ImplWriteBmpRecord( pA->GetBitmap(), pA->GetPoint(), pA->GetSize(), WIN_SRCCOPY );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pAction);
+ Bitmap aTmp( pA->GetBitmap() );
+
+ if( aTmp.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) ) )
+ ImplWriteBmpRecord( aTmp, pA->GetDestPoint(), pA->GetDestSize(), WIN_SRCCOPY );
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ const MetaBmpExAction* pA = static_cast<const MetaBmpExAction *>(pAction);
+ Bitmap aBmp( pA->GetBitmapEx().GetBitmap() );
+ Bitmap aMsk( pA->GetBitmapEx().GetAlpha() );
+
+ if( !aMsk.IsEmpty() )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ aMsk.Invert();
+ ImplWriteBmpRecord( aMsk, pA->GetPoint(), maVDev->PixelToLogic( aMsk.GetSizePixel() ), WIN_SRCPAINT );
+ ImplWriteBmpRecord( aBmp, pA->GetPoint(), maVDev->PixelToLogic( aBmp.GetSizePixel() ), WIN_SRCAND );
+ }
+ else
+ ImplWriteBmpRecord( aBmp, pA->GetPoint(), aBmp.GetSizePixel(), WIN_SRCCOPY );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
+ Bitmap aBmp( pA->GetBitmapEx().GetBitmap() );
+ Bitmap aMsk( pA->GetBitmapEx().GetAlpha() );
+
+ if( !aMsk.IsEmpty() )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ aMsk.Invert();
+ ImplWriteBmpRecord( aMsk, pA->GetPoint(), pA->GetSize(), WIN_SRCPAINT );
+ ImplWriteBmpRecord( aBmp, pA->GetPoint(), pA->GetSize(), WIN_SRCAND );
+ }
+ else
+ ImplWriteBmpRecord( aBmp, pA->GetPoint(), pA->GetSize(), WIN_SRCCOPY );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction);
+ BitmapEx aBmpEx( pA->GetBitmapEx() );
+ aBmpEx.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) );
+ Bitmap aBmp( aBmpEx.GetBitmap() );
+ Bitmap aMsk( aBmpEx.GetAlpha() );
+
+ if( !aMsk.IsEmpty() )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ aMsk.Invert();
+ ImplWriteBmpRecord( aMsk, pA->GetDestPoint(), pA->GetDestSize(), WIN_SRCPAINT );
+ ImplWriteBmpRecord( aBmp, pA->GetDestPoint(), pA->GetDestSize(), WIN_SRCAND );
+ }
+ else
+ ImplWriteBmpRecord( aBmp, pA->GetDestPoint(), pA->GetDestSize(), WIN_SRCCOPY );
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction);
+ const OUString aText = pA->GetText().copy( pA->GetIndex(), std::min(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) );
+
+ ImplCheckTextAttr();
+ ImplWriteTextRecord( pA->GetPoint(), aText, {}, 0 );
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction);
+ const OUString& aText( pA->GetText() );
+
+ ImplCheckTextAttr();
+ ImplWriteTextRecord( pA->GetRect().TopLeft(), aText, {}, 0 );
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction);
+ const OUString aText = pA->GetText().copy( pA->GetIndex(), std::min(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) );
+
+ ImplCheckTextAttr();
+ ImplWriteTextRecord( pA->GetPoint(), aText, pA->GetDXArray(), 0 );
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction);
+ const OUString aText = pA->GetText().copy( pA->GetIndex(), std::min(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) );
+
+ ImplCheckTextAttr();
+ ImplWriteTextRecord( pA->GetPoint(), aText, {}, pA->GetWidth() );
+ }
+ break;
+
+ case MetaActionType::LINECOLOR:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( maVDev );
+ mbLineChanged = true;
+ }
+ break;
+
+ case MetaActionType::FILLCOLOR:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( maVDev );
+ mbFillChanged = true;
+ }
+ break;
+
+ case MetaActionType::TEXTCOLOR:
+ case MetaActionType::TEXTLINECOLOR:
+ case MetaActionType::TEXTFILLCOLOR:
+ case MetaActionType::TEXTALIGN:
+ case MetaActionType::FONT:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( maVDev );
+ mbTextChanged = true;
+ }
+ break;
+
+ case MetaActionType::ISECTRECTCLIPREGION:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( maVDev );
+
+ ImplBeginRecord( WIN_EMR_INTERSECTCLIPRECT );
+ ImplWriteRect( static_cast<const MetaISectRectClipRegionAction*>(pAction)->GetRect() );
+ ImplEndRecord();
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ case MetaActionType::MOVECLIPREGION:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( maVDev );
+ }
+ break;
+
+ case MetaActionType::REFPOINT:
+ case MetaActionType::MAPMODE:
+ const_cast<MetaAction*>(pAction)->Execute( maVDev );
+ break;
+
+ case MetaActionType::PUSH:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( maVDev );
+
+ ImplBeginRecord( WIN_EMR_SAVEDC );
+ ImplEndRecord();
+ }
+ break;
+
+ case MetaActionType::POP:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( maVDev );
+
+ ImplBeginRecord( WIN_EMR_RESTOREDC );
+ m_rStm.WriteInt32( -1 );
+ ImplEndRecord();
+
+ ImplWriteRasterOp( maVDev->GetRasterOp() );
+ mbLineChanged = mbFillChanged = mbTextChanged = true;
+ }
+ break;
+
+ case MetaActionType::RASTEROP:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( maVDev );
+ ImplWriteRasterOp( static_cast<const MetaRasterOpAction*>(pAction)->GetRasterOp() );
+ }
+ break;
+
+ case MetaActionType::LAYOUTMODE:
+ {
+ vcl::text::ComplexTextLayoutFlags nLayoutMode = static_cast<const MetaLayoutModeAction*>(pAction)->GetLayoutMode();
+ mnHorTextAlign = 0;
+ if ((nLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl) != vcl::text::ComplexTextLayoutFlags::Default)
+ {
+ mnHorTextAlign = TA_RIGHT | TA_RTLREADING;
+ }
+ if ((nLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginRight) != vcl::text::ComplexTextLayoutFlags::Default)
+ mnHorTextAlign |= TA_RIGHT;
+ else if ((nLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginLeft) != vcl::text::ComplexTextLayoutFlags::Default)
+ mnHorTextAlign &= ~TA_RIGHT;
+ break;
+ }
+
+ case MetaActionType::COMMENT:
+ {
+ MetaCommentAction const*const pCommentAction(
+ static_cast<MetaCommentAction const*>(pAction));
+ if (pCommentAction->GetComment() == "EMF_PLUS")
+ {
+ ImplBeginCommentRecord(WIN_EMR_COMMENT_EMFPLUS);
+ m_rStm.WriteBytes(pCommentAction->GetData(),
+ pCommentAction->GetDataSize());
+ ImplEndCommentRecord();
+ }
+ }
+ break;
+
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ case MetaActionType::MASKSCALEPART:
+ case MetaActionType::WALLPAPER:
+ case MetaActionType::TEXTLINE:
+ case MetaActionType::GRADIENTEX:
+ // Explicitly ignored cases
+ break;
+
+ default:
+ // TODO: Implement more cases as necessary. Let's not bother with a warning.
+ break;
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/wmf/emfwr.hxx b/vcl/source/filter/wmf/emfwr.hxx
new file mode 100644
index 000000000..502ae5d28
--- /dev/null
+++ b/vcl/source/filter/wmf/emfwr.hxx
@@ -0,0 +1,114 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_WMF_EMFWR_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_WMF_EMFWR_HXX
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/virdev.hxx>
+
+class LineInfo;
+namespace basegfx { class B2DPolygon; }
+enum class EmfPlusRecordType;
+
+class EMFWriter
+{
+private:
+
+ ScopedVclPtr<VirtualDevice> maVDev;
+ MapMode maDestMapMode;
+ SvStream& m_rStm;
+ std::vector<bool> mHandlesUsed;
+ sal_uLong mnHandleCount;
+ sal_uLong mnRecordCount;
+ sal_uLong mnRecordPos;
+ sal_uLong mnRecordPlusPos;
+ bool mbRecordOpen;
+ bool mbRecordPlusOpen;
+ bool mbLineChanged;
+ sal_uInt32 mnLineHandle;
+ bool mbFillChanged;
+ sal_uInt32 mnFillHandle;
+ bool mbTextChanged;
+ sal_uInt32 mnTextHandle;
+ sal_uInt32 mnHorTextAlign;
+
+ void ImplBeginRecord( sal_uInt32 nType );
+ void ImplEndRecord();
+ void ImplBeginPlusRecord( EmfPlusRecordType nType, sal_uInt16 nFlags );
+ void ImplEndPlusRecord();
+ void ImplPlusRecord( EmfPlusRecordType nType, sal_uInt16 nFlags );
+ void ImplBeginCommentRecord( sal_Int32 nCommentType );
+ void ImplEndCommentRecord();
+
+ sal_uLong ImplAcquireHandle();
+ void ImplReleaseHandle( sal_uLong nHandle );
+
+ bool ImplPrepareHandleSelect( sal_uInt32& rHandle, sal_uLong nSelectType );
+ void ImplCheckLineAttr();
+ void ImplCheckFillAttr();
+ void ImplCheckTextAttr();
+
+ void ImplWriteColor( const Color& rColor );
+ void ImplWriteRasterOp( RasterOp eRop );
+ void ImplWriteExtent( tools::Long nExtent );
+ void ImplWritePoint( const Point& rPoint );
+ void ImplWriteSize( const Size& rSize);
+ void ImplWriteRect( const tools::Rectangle& rRect );
+ void ImplWritePath( const tools::PolyPolygon& rPolyPoly, bool bClose );
+ void ImplWritePolygonRecord( const tools::Polygon& rPoly, bool bClose );
+ void ImplWritePolyPolygonRecord( const tools::PolyPolygon& rPolyPoly );
+ void ImplWriteBmpRecord( const Bitmap& rBmp, const Point& rPt, const Size& rSz, sal_uInt32 nROP );
+ void ImplWriteTextRecord( const Point& rPos, const OUString& rText, o3tl::span<const sal_Int32> pDXArray, sal_uInt32 nWidth );
+
+ void Impl_handleLineInfoPolyPolygons(const LineInfo& rInfo, const basegfx::B2DPolygon& rLinePolygon);
+ void ImplWrite( const GDIMetaFile& rMtf );
+ void WriteEMFPlusHeader( const Size &rMtfSizePix, const Size &rMtfSizeLog );
+ void ImplWritePlusEOF();
+ void ImplWritePlusFillPolygonRecord( const tools::Polygon& rPoly, sal_uInt32 nTrans );
+ void ImplWritePlusColor( const Color& rColor, sal_uInt32 nTrans );
+ void ImplWritePlusPoint( const Point& rPoint );
+
+public:
+
+ explicit EMFWriter(SvStream &rStream)
+ : maVDev( VclPtr<VirtualDevice>::Create() )
+ , m_rStm(rStream)
+ , mnHandleCount(0)
+ , mnRecordCount(0)
+ , mnRecordPos(0)
+ , mnRecordPlusPos(0)
+ , mbRecordOpen(false)
+ , mbRecordPlusOpen(false)
+ , mbLineChanged(false)
+ , mnLineHandle(0)
+ , mbFillChanged(false)
+ , mnFillHandle(0)
+ , mbTextChanged(false)
+ , mnTextHandle(0)
+ , mnHorTextAlign(0)
+ {
+ }
+
+ bool WriteEMF(const GDIMetaFile& rMtf);
+};
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_WMF_EMFWR_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/wmf/wmf.cxx b/vcl/source/filter/wmf/wmf.cxx
new file mode 100644
index 000000000..bf91502b4
--- /dev/null
+++ b/vcl/source/filter/wmf/wmf.cxx
@@ -0,0 +1,134 @@
+/* -*- 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 "emfwr.hxx"
+#include "wmfwr.hxx"
+#include <vcl/wmf.hxx>
+#include <vcl/gdimetafiletools.hxx>
+#include <vcl/graph.hxx>
+
+using namespace com::sun::star;
+
+bool ReadWindowMetafile( SvStream& rStream, GDIMetaFile& rMTF )
+{
+ // tdf#111484 Use new method to import Metafile. Take current StreamPos
+ // into account (used by SwWW8ImplReader::ReadGrafFile and by
+ // SwWw6ReadMetaStream, so do *not* ignore. OTOH XclImpDrawing::ReadWmf
+ // is nice enough to copy to an own MemStream to avoid that indirect
+ // parameter passing...)
+ const sal_uInt32 nStreamStart(rStream.Tell());
+ const sal_uInt32 nStreamEnd(rStream.TellEnd());
+
+ if (nStreamStart >= nStreamEnd)
+ {
+ return false;
+ }
+
+ // Read binary data to mem array
+ const sal_uInt32 nStreamLength(nStreamEnd - nStreamStart);
+ auto rData = std::make_unique<std::vector<sal_uInt8>>(nStreamLength);
+ rStream.ReadBytes(rData->data(), rData->size());
+ BinaryDataContainer aDataContainer(std::move(rData));
+ rStream.Seek(nStreamStart);
+
+ if (rStream.good())
+ {
+ // Throw into VectorGraphicData to get the import. Do not care
+ // too much for type, this will be checked there. Also no path
+ // needed, it is a temporary object
+ auto aVectorGraphicDataPtr =
+ std::make_shared<VectorGraphicData>(aDataContainer, VectorGraphicDataType::Emf);
+
+ // create a Graphic and grep Metafile from it
+ const Graphic aGraphic(aVectorGraphicDataPtr);
+
+ // get the Metafile from it, done
+ rMTF = aGraphic.GetGDIMetaFile();
+ return true;
+ }
+
+ return rStream.good();
+}
+
+bool ConvertGDIMetaFileToWMF( const GDIMetaFile & rMTF, SvStream & rTargetStream,
+ FilterConfigItem const * pConfigItem, bool bPlaceable)
+{
+ WMFWriter aWMFWriter;
+ GDIMetaFile aGdiMetaFile(rMTF);
+
+ if(usesClipActions(aGdiMetaFile))
+ {
+ // #i121267# It is necessary to prepare the metafile since the export does *not* support
+ // clip regions. This tooling method clips the geometry content of the metafile internally
+ // against its own clip regions, so that the export is safe to ignore clip regions
+ clipMetafileContentAgainstOwnRegions(aGdiMetaFile);
+ }
+
+ bool bRet = aWMFWriter.WriteWMF(aGdiMetaFile, rTargetStream, pConfigItem, bPlaceable);
+ return bRet;
+}
+
+bool ConvertGraphicToWMF(const Graphic& rGraphic, SvStream& rTargetStream,
+ FilterConfigItem const* pConfigItem, bool bPlaceable)
+{
+ GfxLink aLink = rGraphic.GetGfxLink();
+ if (aLink.GetType() == GfxLinkType::NativeWmf && aLink.GetData() && aLink.GetDataSize())
+ {
+ if(!aLink.IsEMF()) // If WMF, just write directly.
+ return rTargetStream.WriteBytes(aLink.GetData(), aLink.GetDataSize()) == aLink.GetDataSize();
+
+ // This may be an EMF+ file. In EmfReader::ReadEnhWMF() we normally drop non-EMF commands
+ // when reading EMF+, so converting that to WMF is better done by re-parsing with EMF+ disabled.
+ auto & rDataContainer = aLink.getDataContainer();
+ auto aVectorGraphicData
+ = std::make_shared<VectorGraphicData>(rDataContainer, VectorGraphicDataType::Emf);
+ aVectorGraphicData->setEnableEMFPlus(false);
+ Graphic aGraphic(aVectorGraphicData);
+ bool bRet = ConvertGDIMetaFileToWMF(aGraphic.GetGDIMetaFile(), rTargetStream, pConfigItem,
+ bPlaceable);
+ return bRet;
+ }
+
+ bool bRet = ConvertGDIMetaFileToWMF(rGraphic.GetGDIMetaFile(), rTargetStream, pConfigItem,
+ bPlaceable);
+ return bRet;
+}
+
+bool ConvertGDIMetaFileToEMF(const GDIMetaFile & rMTF, SvStream & rTargetStream)
+{
+ EMFWriter aEMFWriter(rTargetStream);
+ GDIMetaFile aGdiMetaFile(rMTF);
+
+ if(usesClipActions(aGdiMetaFile))
+ {
+ // #i121267# It is necessary to prepare the metafile since the export does *not* support
+ // clip regions. This tooling method clips the geometry content of the metafile internally
+ // against its own clip regions, so that the export is safe to ignore clip regions
+ clipMetafileContentAgainstOwnRegions(aGdiMetaFile);
+ }
+
+ return aEMFWriter.WriteEMF(aGdiMetaFile);
+}
+
+bool WriteWindowMetafileBits( SvStream& rStream, const GDIMetaFile& rMTF )
+{
+ return WMFWriter().WriteWMF( rMTF, rStream, nullptr, false );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/wmf/wmfexternal.cxx b/vcl/source/filter/wmf/wmfexternal.cxx
new file mode 100644
index 000000000..c5616beac
--- /dev/null
+++ b/vcl/source/filter/wmf/wmfexternal.cxx
@@ -0,0 +1,74 @@
+/* -*- 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 <comphelper/propertyvalue.hxx>
+#include <vcl/wmfexternal.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+
+// formally known as WMF_EXTERNALHEADER
+WmfExternal::WmfExternal()
+ : xExt(0)
+ , yExt(0)
+ , mapMode(0)
+{
+}
+
+css::uno::Sequence<css::beans::PropertyValue> WmfExternal::getSequence() const
+{
+ if (0 != xExt || 0 != yExt || 0 != mapMode)
+ {
+ return { comphelper::makePropertyValue("Width", static_cast<sal_Int16>(xExt)),
+ comphelper::makePropertyValue("Height", static_cast<sal_Int16>(yExt)),
+ comphelper::makePropertyValue("MapMode", static_cast<sal_Int16>(mapMode)) };
+ }
+
+ return {};
+}
+
+bool WmfExternal::setSequence(const css::uno::Sequence<css::beans::PropertyValue>& rSequence)
+{
+ bool bRetval(false);
+
+ for (const auto& rPropVal : rSequence)
+ {
+ const OUString aName(rPropVal.Name);
+
+ if (aName == "Width")
+ {
+ rPropVal.Value >>= xExt;
+ bRetval = true;
+ }
+ else if (aName == "Height")
+ {
+ rPropVal.Value >>= yExt;
+ bRetval = true;
+ }
+ else if (aName == "MapMode")
+ {
+ rPropVal.Value >>= mapMode;
+ bRetval = true;
+ }
+ }
+
+ return bRetval;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/wmf/wmfwr.cxx b/vcl/source/filter/wmf/wmfwr.cxx
new file mode 100644
index 000000000..22230ba46
--- /dev/null
+++ b/vcl/source/filter/wmf/wmfwr.cxx
@@ -0,0 +1,1904 @@
+/* -*- 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 <osl/diagnose.h>
+
+#include <algorithm>
+
+#include "wmfwr.hxx"
+#include "emfwr.hxx"
+#include <rtl/crc.h>
+#include <rtl/tencinfo.h>
+#include <tools/bigint.hxx>
+#include <tools/helpers.hxx>
+#include <tools/tenccvt.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <memory>
+#include <vcl/fontcharmap.hxx>
+#include <comphelper/sequenceashashmap.hxx>
+
+// MS Windows defines
+
+#define W_META_SETBKMODE 0x0102
+#define W_META_SETROP2 0x0104
+#define W_META_SETSTRETCHBLTMODE 0x0107
+#define W_META_SETTEXTCOLOR 0x0209
+#define W_META_SETWINDOWORG 0x020B
+#define W_META_SETWINDOWEXT 0x020C
+#define W_META_LINETO 0x0213
+#define W_META_MOVETO 0x0214
+#define W_META_INTERSECTCLIPRECT 0x0416
+#define W_META_ARC 0x0817
+#define W_META_ELLIPSE 0x0418
+#define W_META_PIE 0x081A
+#define W_META_RECTANGLE 0x041B
+#define W_META_ROUNDRECT 0x061C
+#define W_META_SAVEDC 0x001E
+#define W_META_SETPIXEL 0x041F
+#define W_META_TEXTOUT 0x0521
+#define W_META_POLYGON 0x0324
+#define W_META_POLYLINE 0x0325
+#define W_META_ESCAPE 0x0626
+#define W_META_RESTOREDC 0x0127
+#define W_META_SELECTOBJECT 0x012D
+#define W_META_SETTEXTALIGN 0x012E
+#define W_META_CHORD 0x0830
+#define W_META_EXTTEXTOUT 0x0a32
+#define W_META_POLYPOLYGON 0x0538
+#define W_META_STRETCHDIB 0x0f43
+#define W_META_DELETEOBJECT 0x01f0
+#define W_META_CREATEPENINDIRECT 0x02FA
+#define W_META_CREATEFONTINDIRECT 0x02FB
+#define W_META_CREATEBRUSHINDIRECT 0x02FC
+
+#define W_TRANSPARENT 1
+#define W_OPAQUE 2
+
+#define W_R2_NOT 6
+#define W_R2_XORPEN 7
+#define W_R2_COPYPEN 13
+
+#define W_TA_NOUPDATECP 0x0000
+#define W_TA_LEFT 0x0000
+#define W_TA_RIGHT 0x0002
+#define W_TA_TOP 0x0000
+#define W_TA_BOTTOM 0x0008
+#define W_TA_BASELINE 0x0018
+#define W_TA_RTLREADING 0x0100
+
+#define W_SRCCOPY 0x00CC0020L
+#define W_SRCPAINT 0x00EE0086L
+#define W_SRCAND 0x008800C6L
+#define W_SRCINVERT 0x00660046L
+#define W_DSTINVERT 0x00550009L
+
+#define W_PS_SOLID 0
+#define W_PS_DASH 1
+#define W_PS_DOT 2
+#define W_PS_DASHDOT 3
+#define W_PS_DASHDOTDOT 4
+#define W_PS_NULL 5
+
+#define W_LF_FACESIZE 32
+
+#define W_ANSI_CHARSET 0
+
+#define W_DEFAULT_PITCH 0x00
+#define W_FIXED_PITCH 0x01
+#define W_VARIABLE_PITCH 0x02
+
+#define W_FF_DONTCARE 0x00
+#define W_FF_ROMAN 0x10
+#define W_FF_SWISS 0x20
+#define W_FF_MODERN 0x30
+#define W_FF_SCRIPT 0x40
+#define W_FF_DECORATIVE 0x50
+
+#define W_FW_DONTCARE 0
+#define W_FW_THIN 100
+#define W_FW_LIGHT 300
+#define W_FW_NORMAL 400
+#define W_FW_MEDIUM 500
+#define W_FW_SEMIBOLD 600
+#define W_FW_BOLD 700
+#define W_FW_ULTRALIGHT 200
+#define W_FW_ULTRABOLD 800
+#define W_FW_BLACK 900
+
+#define W_BS_SOLID 0
+#define W_BS_HOLLOW 1
+
+#define W_MFCOMMENT 15
+
+#define PRIVATE_ESCAPE_UNICODE 2
+
+WMFWriter::WMFWriter()
+ : bStatus(false)
+ , nLastPercent(0)
+ , pWMF(nullptr)
+ , pVirDev(nullptr)
+ , nMetafileHeaderPos(0)
+ , nMaxRecordSize(0)
+ , nActRecordPos(0)
+ , eSrcRasterOp(RasterOp::OverPaint)
+ , eSrcTextAlign(ALIGN_BASELINE)
+ , pAttrStack(nullptr)
+ , eSrcHorTextAlign(W_TA_LEFT)
+ , eDstROP2(RasterOp::OverPaint)
+ , eDstTextAlign(ALIGN_BASELINE)
+ , eDstHorTextAlign(W_TA_LEFT)
+ , bHandleAllocated{}
+ , nDstPenHandle(0)
+ , nDstFontHandle(0)
+ , nDstBrushHandle(0)
+ , nNumberOfActions(0)
+ , nNumberOfBitmaps(0)
+ , nWrittenActions(0)
+ , nWrittenBitmaps(0)
+ , nActBitmapPercent(0)
+ , bEmbedEMF(false)
+{
+}
+
+void WMFWriter::MayCallback()
+{
+ if ( !xStatusIndicator.is() )
+ return;
+
+ sal_uLong nPercent;
+
+ // we simply assume that 16386 actions match to a bitmap
+ // (normally a metafile either contains only actions or some bitmaps and
+ // almost no actions. In which case the ratio is less important)
+
+ nPercent=((nWrittenBitmaps<<14)+(nActBitmapPercent<<14)/100+nWrittenActions)
+ *100
+ /((nNumberOfBitmaps<<14)+nNumberOfActions);
+
+ if ( nPercent >= nLastPercent + 3 )
+ {
+ nLastPercent = nPercent;
+ if( nPercent <= 100 )
+ xStatusIndicator->setValue( nPercent );
+ }
+}
+
+void WMFWriter::CountActionsAndBitmaps( const GDIMetaFile & rMTF )
+{
+ size_t nAction, nActionCount;
+
+ nActionCount = rMTF.GetActionSize();
+
+ for ( nAction=0; nAction < nActionCount; nAction++ )
+ {
+ MetaAction* pMA = rMTF.GetAction( nAction );
+
+ switch( pMA->GetType() )
+ {
+ case MetaActionType::BMP:
+ case MetaActionType::BMPSCALE:
+ case MetaActionType::BMPSCALEPART:
+ case MetaActionType::BMPEX:
+ case MetaActionType::BMPEXSCALE:
+ case MetaActionType::BMPEXSCALEPART:
+ nNumberOfBitmaps++;
+ break;
+ default: break;
+ }
+ nNumberOfActions++;
+ }
+}
+
+void WMFWriter::WritePointXY(const Point & rPoint)
+{
+ Point aPt( OutputDevice::LogicToLogic(rPoint,aSrcMapMode,aTargetMapMode) );
+ pWMF->WriteInt16( aPt.X() ).WriteInt16( aPt.Y() );
+}
+
+void WMFWriter::WritePointYX(const Point & rPoint)
+{
+ Point aPt( OutputDevice::LogicToLogic(rPoint,aSrcMapMode,aTargetMapMode) );
+ pWMF->WriteInt16( aPt.Y() ).WriteInt16( aPt.X() );
+}
+
+sal_Int32 WMFWriter::ScaleWidth( sal_Int32 nDX )
+{
+ Size aSz( OutputDevice::LogicToLogic(Size(nDX,0),aSrcMapMode,aTargetMapMode) );
+ return aSz.Width();
+}
+
+void WMFWriter::WriteSize(const Size & rSize)
+{
+ Size aSz( OutputDevice::LogicToLogic(rSize,aSrcMapMode,aTargetMapMode) );
+ pWMF->WriteInt16( aSz.Width() ).WriteInt16( aSz.Height() );
+}
+
+void WMFWriter::WriteHeightWidth(const Size & rSize)
+{
+ Size aSz( OutputDevice::LogicToLogic(rSize,aSrcMapMode,aTargetMapMode) );
+ pWMF->WriteInt16( aSz.Height() ).WriteInt16( aSz.Width() );
+}
+
+void WMFWriter::WriteRectangle(const tools::Rectangle & rRect)
+{
+ WritePointYX(Point(rRect.Right()+1,rRect.Bottom()+1));
+ WritePointYX(rRect.TopLeft());
+}
+
+void WMFWriter::WriteColor(const Color & rColor)
+{
+ pWMF->WriteUChar( rColor.GetRed() ).WriteUChar( rColor.GetGreen() ).WriteUChar( rColor.GetBlue() ).WriteUChar( 0 );
+}
+
+void WMFWriter::WriteRecordHeader(sal_uInt32 nSizeWords, sal_uInt16 nType)
+{
+ nActRecordPos=pWMF->Tell();
+ if (nSizeWords>nMaxRecordSize) nMaxRecordSize=nSizeWords;
+ pWMF->WriteUInt32( nSizeWords ).WriteUInt16( nType );
+}
+
+void WMFWriter::UpdateRecordHeader()
+{
+ sal_uLong nPos;
+ sal_uInt32 nSize;
+
+ nPos=pWMF->Tell(); nSize=nPos-nActRecordPos;
+ if ((nSize & 1)!=0) {
+ pWMF->WriteUChar( 0 );
+ nPos++; nSize++;
+ }
+ nSize/=2;
+ if (nSize>nMaxRecordSize) nMaxRecordSize=nSize;
+ pWMF->Seek(nActRecordPos);
+ pWMF->WriteUInt32( nSize );
+ pWMF->Seek(nPos);
+}
+
+void WMFWriter::WMFRecord_Arc(const tools::Rectangle & rRect, const Point & rStartPt, const Point & rEndPt)
+{
+ WriteRecordHeader(0x0000000b,W_META_ARC);
+ WritePointYX(rEndPt);
+ WritePointYX(rStartPt);
+ WriteRectangle(rRect);
+}
+
+void WMFWriter::WMFRecord_Chord(const tools::Rectangle & rRect, const Point & rStartPt, const Point & rEndPt)
+{
+ WriteRecordHeader(0x0000000b,W_META_CHORD);
+ WritePointYX(rEndPt);
+ WritePointYX(rStartPt);
+ WriteRectangle(rRect);
+}
+
+void WMFWriter::WMFRecord_CreateBrushIndirect(const Color& rColor)
+{
+ WriteRecordHeader(0x00000007,W_META_CREATEBRUSHINDIRECT);
+
+ if( rColor==COL_TRANSPARENT )
+ pWMF->WriteUInt16( W_BS_HOLLOW );
+ else
+ pWMF->WriteUInt16( W_BS_SOLID );
+
+ WriteColor( rColor );
+ pWMF->WriteUInt16( 0 );
+}
+
+void WMFWriter::WMFRecord_CreateFontIndirect(const vcl::Font & rFont)
+{
+ sal_uInt16 nWeight,i;
+ sal_uInt8 nPitchFamily;
+
+ WriteRecordHeader(0x00000000,W_META_CREATEFONTINDIRECT);
+ WriteHeightWidth(Size(rFont.GetFontSize().Width(),-rFont.GetFontSize().Height()));
+ pWMF->WriteInt16( rFont.GetOrientation().get() ).WriteInt16( rFont.GetOrientation().get() );
+
+ switch (rFont.GetWeight()) {
+ case WEIGHT_THIN: nWeight=W_FW_THIN; break;
+ case WEIGHT_ULTRALIGHT: nWeight=W_FW_ULTRALIGHT; break;
+ case WEIGHT_LIGHT: nWeight=W_FW_LIGHT; break;
+ case WEIGHT_SEMILIGHT: nWeight=W_FW_LIGHT; break;
+ case WEIGHT_NORMAL: nWeight=W_FW_NORMAL; break;
+ case WEIGHT_MEDIUM: nWeight=W_FW_MEDIUM; break;
+ case WEIGHT_SEMIBOLD: nWeight=W_FW_SEMIBOLD; break;
+ case WEIGHT_BOLD: nWeight=W_FW_BOLD; break;
+ case WEIGHT_ULTRABOLD: nWeight=W_FW_ULTRABOLD; break;
+ case WEIGHT_BLACK: nWeight=W_FW_BLACK; break;
+ default: nWeight=W_FW_DONTCARE;
+ }
+ pWMF->WriteUInt16( nWeight );
+
+ if (rFont.GetItalic()==ITALIC_NONE) pWMF->WriteUChar( 0 ); else pWMF->WriteUChar( 1 );
+ if (rFont.GetUnderline()==LINESTYLE_NONE) pWMF->WriteUChar( 0 ); else pWMF->WriteUChar( 1 );
+ if (rFont.GetStrikeout()==STRIKEOUT_NONE) pWMF->WriteUChar( 0 ); else pWMF->WriteUChar( 1 );
+
+ rtl_TextEncoding eFontNameEncoding = rFont.GetCharSet();
+ sal_uInt8 nCharSet = rtl_getBestWindowsCharsetFromTextEncoding( eFontNameEncoding );
+ if ( eFontNameEncoding == RTL_TEXTENCODING_SYMBOL )
+ eFontNameEncoding = RTL_TEXTENCODING_MS_1252;
+ if ( nCharSet == 1 )
+ nCharSet = W_ANSI_CHARSET;
+ pWMF->WriteUChar( nCharSet );
+
+ pWMF->WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 );
+
+ switch (rFont.GetPitch()) {
+ case PITCH_FIXED: nPitchFamily=W_FIXED_PITCH; break;
+ case PITCH_VARIABLE: nPitchFamily=W_VARIABLE_PITCH; break;
+ default: nPitchFamily=W_DEFAULT_PITCH;
+ }
+ switch (rFont.GetFamilyType()) {
+ case FAMILY_DECORATIVE: nPitchFamily|=W_FF_DECORATIVE; break;
+ case FAMILY_MODERN: nPitchFamily|=W_FF_MODERN; break;
+ case FAMILY_ROMAN: nPitchFamily|=W_FF_ROMAN; break;
+ case FAMILY_SCRIPT: nPitchFamily|=W_FF_SCRIPT; break;
+ case FAMILY_SWISS: nPitchFamily|=W_FF_SWISS; break;
+ default: nPitchFamily|=W_FF_DONTCARE;
+ }
+ pWMF->WriteUChar( nPitchFamily );
+
+ OString aFontName(OUStringToOString(rFont.GetFamilyName(), eFontNameEncoding));
+ for ( i = 0; i < W_LF_FACESIZE; i++ )
+ {
+ char nChar = ( i < aFontName.getLength() ) ? aFontName[i] : 0;
+ pWMF->WriteChar( nChar );
+ }
+ UpdateRecordHeader();
+}
+
+void WMFWriter::WMFRecord_CreatePenIndirect(const Color& rColor, const LineInfo& rLineInfo )
+{
+ WriteRecordHeader(0x00000008,W_META_CREATEPENINDIRECT);
+ sal_uInt16 nStyle = rColor == COL_TRANSPARENT ? W_PS_NULL : W_PS_SOLID;
+ switch( rLineInfo.GetStyle() )
+ {
+ case LineStyle::Dash :
+ {
+ if ( rLineInfo.GetDotCount() )
+ {
+ if ( !rLineInfo.GetDashCount() )
+ nStyle = W_PS_DOT;
+ else
+ {
+ if ( rLineInfo.GetDotCount() == 1 )
+ nStyle = W_PS_DASHDOT;
+ else
+ nStyle = W_PS_DASHDOTDOT;
+ }
+ }
+ else
+ nStyle = W_PS_DASH;
+ }
+ break;
+ case LineStyle::NONE :
+ nStyle = W_PS_NULL;
+ break;
+ default:
+ break;
+ }
+ pWMF->WriteUInt16( nStyle );
+
+ WriteSize( Size( rLineInfo.GetWidth(), 0 ) );
+ WriteColor( rColor );
+}
+
+void WMFWriter::WMFRecord_DeleteObject(sal_uInt16 nObjectHandle)
+{
+ WriteRecordHeader(0x00000004,W_META_DELETEOBJECT);
+ pWMF->WriteUInt16( nObjectHandle );
+}
+
+void WMFWriter::WMFRecord_Ellipse(const tools::Rectangle & rRect)
+{
+ WriteRecordHeader(0x00000007,W_META_ELLIPSE);
+ WriteRectangle(rRect);
+}
+
+void WMFWriter::WMFRecord_Escape( sal_uInt32 nEsc, sal_uInt32 nLen, const sal_Int8* pData )
+{
+#ifdef OSL_BIGENDIAN
+ sal_uInt32 nTmp = OSL_SWAPDWORD( nEsc );
+ sal_uInt32 nCheckSum = rtl_crc32( 0, &nTmp, 4 );
+#else
+ sal_uInt32 nCheckSum = rtl_crc32( 0, &nEsc, 4 );
+#endif
+ if ( nLen )
+ nCheckSum = rtl_crc32( nCheckSum, pData, nLen );
+
+ WriteRecordHeader( 3 + 9 + ( ( nLen + 1 ) >> 1 ), W_META_ESCAPE );
+ pWMF->WriteUInt16( W_MFCOMMENT )
+ .WriteUInt16( nLen + 14 ) // we will always have a fourteen byte escape header:
+ .WriteUInt16( 0x4f4f ) // OO
+ .WriteUInt32( 0xa2c2a ) // evil magic number
+ .WriteUInt32( nCheckSum ) // crc32 checksum about nEsc & pData
+ .WriteUInt32( nEsc ); // escape number
+ pWMF->WriteBytes( pData, nLen );
+ if ( nLen & 1 )
+ pWMF->WriteUChar( 0 ); // pad byte
+}
+
+/* if return value is true, then a complete unicode string and also a polygon replacement has been written,
+ so there is no more action necessary
+*/
+bool WMFWriter::WMFRecord_Escape_Unicode( const Point& rPoint, const OUString& rUniStr, o3tl::span<const sal_Int32> pDXAry )
+{
+ bool bEscapeUsed = false;
+
+ sal_uInt32 i, nStringLen = rUniStr.getLength();
+ if ( nStringLen )
+ {
+ // first we will check if a comment is necessary
+ if ( aSrcFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL ) // symbol is always byte character, so there is no unicode loss
+ {
+ const sal_Unicode* pBuf = rUniStr.getStr();
+ const rtl_TextEncoding aTextEncodingOrg = aSrcFont.GetCharSet();
+ OString aByteStr(OUStringToOString(rUniStr, aTextEncodingOrg));
+ OUString aUniStr2(OStringToOUString(aByteStr, aTextEncodingOrg));
+ const sal_Unicode* pConversion = aUniStr2.getStr(); // this is the unicode array after bytestring <-> unistring conversion
+ for ( i = 0; i < nStringLen; i++ )
+ {
+ if ( *pBuf++ != *pConversion++ )
+ break;
+ }
+
+ if ( i != nStringLen ) // after conversion the characters are not original,
+ { // try again, with determining a better charset from unicode char
+ pBuf = rUniStr.getStr();
+ const sal_Unicode* pCheckChar = pBuf;
+ rtl_TextEncoding aTextEncoding = getBestMSEncodingByChar(*pCheckChar); // try the first character
+ if (aTextEncoding == RTL_TEXTENCODING_DONTKNOW) {
+ aTextEncoding = aTextEncodingOrg;
+ }
+ for ( i = 1; i < nStringLen; i++)
+ {
+ if (aTextEncoding != aTextEncodingOrg) // found something
+ break;
+ pCheckChar++;
+ aTextEncoding = getBestMSEncodingByChar(*pCheckChar); // try the next character
+ if (aTextEncoding == RTL_TEXTENCODING_DONTKNOW) {
+ aTextEncoding = aTextEncodingOrg;
+ }
+ }
+
+ aByteStr = OUStringToOString(rUniStr, aTextEncoding);
+ aUniStr2 = OStringToOUString(aByteStr, aTextEncoding);
+ pConversion = aUniStr2.getStr(); // this is the unicode array after bytestring <-> unistring conversion
+ for ( i = 0; i < nStringLen; i++ )
+ {
+ if ( *pBuf++ != *pConversion++ )
+ break;
+ }
+ if (i == nStringLen)
+ {
+ aSrcFont.SetCharSet (aTextEncoding);
+ SetAllAttr();
+ }
+ }
+
+ if ( ( i != nStringLen ) || IsStarSymbol( aSrcFont.GetFamilyName() ) ) // after conversion the characters are not original, so we
+ { // will store the unicode string and a polypoly replacement
+ Color aOldFillColor( aSrcFillColor );
+ Color aOldLineColor( aSrcLineColor );
+ aSrcLineInfo = LineInfo();
+ aSrcFillColor = aSrcTextColor;
+ aSrcLineColor = COL_TRANSPARENT;
+ SetLineAndFillAttr();
+ pVirDev->SetFont( aSrcFont );
+ std::vector<tools::PolyPolygon> aPolyPolyVec;
+ if ( pVirDev->GetTextOutlines( aPolyPolyVec, rUniStr ) )
+ {
+ sal_uInt32 nDXCount = !pDXAry.empty() ? nStringLen : 0;
+ sal_uInt32 nSkipActions = aPolyPolyVec.size();
+ sal_Int32 nStrmLen = 8 +
+ + sizeof( nStringLen ) + ( nStringLen * 2 )
+ + sizeof( nDXCount ) + ( nDXCount * 4 )
+ + sizeof( nSkipActions );
+
+ SvMemoryStream aMemoryStream( nStrmLen );
+ Point aPt( OutputDevice::LogicToLogic( rPoint, aSrcMapMode, aTargetMapMode ) );
+ aMemoryStream.WriteInt32( aPt.X() )
+ .WriteInt32( aPt.Y() )
+ .WriteUInt32( nStringLen );
+ for ( i = 0; i < nStringLen; i++ )
+ aMemoryStream.WriteUInt16( rUniStr[ i ] );
+ aMemoryStream.WriteUInt32( nDXCount );
+ for ( i = 0; i < nDXCount; i++ )
+ aMemoryStream.WriteInt32( pDXAry[ i ] );
+ aMemoryStream.WriteUInt32( nSkipActions );
+ WMFRecord_Escape( PRIVATE_ESCAPE_UNICODE, nStrmLen, static_cast<const sal_Int8*>(aMemoryStream.GetData()) );
+
+ for ( const auto& rPolyPoly : aPolyPolyVec )
+ {
+ tools::PolyPolygon aPolyPoly( rPolyPoly );
+ aPolyPoly.Move( rPoint.X(), rPoint.Y() );
+ WMFRecord_PolyPolygon( aPolyPoly );
+ }
+ aSrcFillColor = aOldFillColor;
+ aSrcLineColor = aOldLineColor;
+ bEscapeUsed = true;
+ }
+ }
+ }
+ }
+ return bEscapeUsed;
+}
+
+void WMFWriter::WMFRecord_ExtTextOut( const Point& rPoint,
+ const OUString& rString,
+ o3tl::span<const sal_Int32> pDXAry )
+{
+ sal_Int32 nOriginalTextLen = rString.getLength();
+
+ if ( (nOriginalTextLen <= 1) || pDXAry.empty() )
+ {
+ WMFRecord_TextOut(rPoint, rString);
+ return;
+ }
+ rtl_TextEncoding eChrSet = aSrcFont.GetCharSet();
+ OString aByteString(OUStringToOString(rString, eChrSet));
+ TrueExtTextOut(rPoint, rString, aByteString, pDXAry);
+}
+
+void WMFWriter::TrueExtTextOut( const Point& rPoint, const OUString& rString,
+ const OString& rByteString, o3tl::span<const sal_Int32> pDXAry )
+{
+ WriteRecordHeader( 0, W_META_EXTTEXTOUT );
+ WritePointYX( rPoint );
+ sal_uInt16 nNewTextLen = static_cast<sal_uInt16>(rByteString.getLength());
+ pWMF->WriteUInt16( nNewTextLen ).WriteUInt16( 0 );
+ write_uInt8s_FromOString(*pWMF, rByteString, nNewTextLen);
+ if ( nNewTextLen & 1 )
+ pWMF->WriteUChar( 0 );
+
+ sal_Int32 nOriginalTextLen = rString.getLength();
+ std::unique_ptr<sal_Int16[]> pConvertedDXAry(new sal_Int16[ nOriginalTextLen ]);
+ sal_Int32 j = 0;
+ pConvertedDXAry[ j++ ] = static_cast<sal_Int16>(ScaleWidth( pDXAry[ 0 ] ));
+ for (sal_Int32 i = 1; i < ( nOriginalTextLen - 1 ); ++i)
+ pConvertedDXAry[ j++ ] = static_cast<sal_Int16>(ScaleWidth( pDXAry[ i ] - pDXAry[ i - 1 ] ));
+ pConvertedDXAry[ j ] = static_cast<sal_Int16>(ScaleWidth( pDXAry[ nOriginalTextLen - 2 ] / ( nOriginalTextLen - 1 ) ));
+
+ for (sal_Int32 i = 0; i < nOriginalTextLen; ++i)
+ {
+ sal_Int16 nDx = pConvertedDXAry[ i ];
+ pWMF->WriteInt16( nDx );
+ if ( nOriginalTextLen < nNewTextLen )
+ {
+ sal_Unicode nUniChar = rString[i];
+ OString aTemp(&nUniChar, 1, aSrcFont.GetCharSet());
+ j = aTemp.getLength();
+ while ( --j > 0 )
+ pWMF->WriteUInt16( 0 );
+ }
+ }
+ pConvertedDXAry.reset();
+ UpdateRecordHeader();
+}
+
+void WMFWriter::WMFRecord_LineTo(const Point & rPoint)
+{
+ WriteRecordHeader(0x00000005,W_META_LINETO);
+ WritePointYX(rPoint);
+}
+
+void WMFWriter::WMFRecord_MoveTo(const Point & rPoint)
+{
+ WriteRecordHeader(0x00000005,W_META_MOVETO);
+ WritePointYX(rPoint);
+}
+
+void WMFWriter::WMFRecord_Pie(const tools::Rectangle & rRect, const Point & rStartPt, const Point & rEndPt)
+{
+ WriteRecordHeader(0x0000000b,W_META_PIE);
+ WritePointYX(rEndPt);
+ WritePointYX(rStartPt);
+ WriteRectangle(rRect);
+}
+
+void WMFWriter::WMFRecord_Polygon(const tools::Polygon & rPoly)
+{
+ tools::Polygon aSimplePoly;
+ if ( rPoly.HasFlags() )
+ rPoly.AdaptiveSubdivide( aSimplePoly );
+ else
+ aSimplePoly = rPoly;
+ const sal_uInt16 nSize = aSimplePoly.GetSize();
+ WriteRecordHeader(static_cast<sal_uInt32>(nSize)*2+4,W_META_POLYGON);
+ pWMF->WriteUInt16( nSize );
+ for (sal_uInt16 i=0; i<nSize; ++i)
+ WritePointXY(aSimplePoly.GetPoint(i));
+}
+
+void WMFWriter::WMFRecord_PolyLine(const tools::Polygon & rPoly)
+{
+ tools::Polygon aSimplePoly;
+ if ( rPoly.HasFlags() )
+ rPoly.AdaptiveSubdivide( aSimplePoly );
+ else
+ aSimplePoly = rPoly;
+ const sal_uInt16 nSize = aSimplePoly.GetSize();
+ WriteRecordHeader(static_cast<sal_uInt32>(nSize)*2+4,W_META_POLYLINE);
+ pWMF->WriteUInt16( nSize );
+ for (sal_uInt16 i=0; i<nSize; ++i)
+ WritePointXY(aSimplePoly.GetPoint(i));
+}
+
+void WMFWriter::WMFRecord_PolyPolygon(const tools::PolyPolygon & rPolyPoly)
+{
+ const tools::Polygon * pPoly;
+ sal_uInt16 nCount,nSize,i,j;
+
+ nCount=rPolyPoly.Count();
+ tools::PolyPolygon aSimplePolyPoly( rPolyPoly );
+ for ( i = 0; i < nCount; i++ )
+ {
+ if ( aSimplePolyPoly[ i ].HasFlags() )
+ {
+ tools::Polygon aSimplePoly;
+ aSimplePolyPoly[ i ].AdaptiveSubdivide( aSimplePoly );
+ aSimplePolyPoly[ i ] = aSimplePoly;
+ }
+ }
+ WriteRecordHeader(0,W_META_POLYPOLYGON);
+ pWMF->WriteUInt16( nCount );
+ for (i=0; i<nCount; i++) pWMF->WriteUInt16( aSimplePolyPoly.GetObject(i).GetSize() );
+ for (i=0; i<nCount; i++) {
+ pPoly=&(aSimplePolyPoly.GetObject(i));
+ nSize=pPoly->GetSize();
+ for (j=0; j<nSize; j++) WritePointXY(pPoly->GetPoint(j));
+ }
+ UpdateRecordHeader();
+}
+
+void WMFWriter::WMFRecord_Rectangle(const tools::Rectangle & rRect)
+{
+ WriteRecordHeader( 0x00000007,W_META_RECTANGLE );
+ WriteRectangle( rRect );
+}
+
+void WMFWriter::WMFRecord_RestoreDC()
+{
+ WriteRecordHeader(0x00000004,W_META_RESTOREDC);
+ pWMF->WriteInt16( -1 );
+}
+
+void WMFWriter::WMFRecord_RoundRect(const tools::Rectangle & rRect, tools::Long nHorzRound, tools::Long nVertRound)
+{
+ WriteRecordHeader(0x00000009,W_META_ROUNDRECT);
+ WriteHeightWidth(Size(nHorzRound,nVertRound));
+ WriteRectangle(rRect);
+}
+
+void WMFWriter::WMFRecord_SaveDC()
+{
+ WriteRecordHeader(0x00000003,W_META_SAVEDC);
+}
+
+void WMFWriter::WMFRecord_SelectObject(sal_uInt16 nObjectHandle)
+{
+ WriteRecordHeader(0x00000004,W_META_SELECTOBJECT);
+ pWMF->WriteUInt16( nObjectHandle );
+}
+
+void WMFWriter::WMFRecord_SetBkMode(bool bTransparent)
+{
+ WriteRecordHeader(0x00000004,W_META_SETBKMODE);
+ if (bTransparent) pWMF->WriteUInt16( W_TRANSPARENT );
+ else pWMF->WriteUInt16( W_OPAQUE );
+}
+
+void WMFWriter::WMFRecord_SetStretchBltMode()
+{
+ WriteRecordHeader( 0x00000004, W_META_SETSTRETCHBLTMODE );
+ pWMF->WriteUInt16( 3 ); // STRETCH_DELETESCANS
+}
+
+void WMFWriter::WMFRecord_SetPixel(const Point & rPoint, const Color & rColor)
+{
+ WriteRecordHeader(0x00000007,W_META_SETPIXEL);
+ WriteColor(rColor);
+ WritePointYX(rPoint);
+}
+
+void WMFWriter::WMFRecord_SetROP2(RasterOp eROP)
+{
+ sal_uInt16 nROP2;
+
+ switch (eROP) {
+ case RasterOp::Invert: nROP2=W_R2_NOT; break;
+ case RasterOp::Xor: nROP2=W_R2_XORPEN; break;
+ default: nROP2=W_R2_COPYPEN;
+ }
+ WriteRecordHeader(0x00000004,W_META_SETROP2);
+ pWMF->WriteUInt16( nROP2 );
+}
+
+void WMFWriter::WMFRecord_SetTextAlign(TextAlign eFontAlign, sal_uInt16 eHorTextAlign)
+{
+ sal_uInt16 nAlign;
+
+ switch (eFontAlign) {
+ case ALIGN_TOP: nAlign=W_TA_TOP; break;
+ case ALIGN_BOTTOM: nAlign=W_TA_BOTTOM; break;
+ default: nAlign=W_TA_BASELINE;
+ }
+ nAlign|=eHorTextAlign;
+ nAlign|=W_TA_NOUPDATECP;
+
+ WriteRecordHeader(0x00000004,W_META_SETTEXTALIGN);
+ pWMF->WriteUInt16( nAlign );
+}
+
+void WMFWriter::WMFRecord_SetTextColor(const Color & rColor)
+{
+ WriteRecordHeader(0x00000005,W_META_SETTEXTCOLOR);
+ WriteColor(rColor);
+}
+
+void WMFWriter::WMFRecord_SetWindowExt(const Size & rSize)
+{
+ WriteRecordHeader(0x00000005,W_META_SETWINDOWEXT);
+ WriteHeightWidth(rSize);
+}
+
+void WMFWriter::WMFRecord_SetWindowOrg(const Point & rPoint)
+{
+ WriteRecordHeader(0x00000005,W_META_SETWINDOWORG);
+ WritePointYX(rPoint);
+}
+
+void WMFWriter::WMFRecord_StretchDIB( const Point & rPoint, const Size & rSize,
+ const Bitmap & rBitmap, sal_uInt32 nROP )
+{
+ sal_uLong nPosAnf,nPosEnd;
+
+ nActBitmapPercent=50;
+ MayCallback();
+
+ WriteRecordHeader(0x00000000,W_META_STRETCHDIB);
+
+ // The sequence in the metafile should be:
+ // some parameters (length 22), then the bitmap without FILEHEADER.
+ // As *pWMF << rBitmap generates a FILEHEADER of size 14,
+ // we first write the bitmap at the right position
+ // and overwrite later the FILEHEADER with the parameters.
+ nPosAnf=pWMF->Tell(); // remember position, where parameters should be stored
+ pWMF->WriteInt32( 0 ).WriteInt32( 0 ); // replenish 8 bytes (these 8 bytes +
+ // 14 bytes superfluous FILEHEADER
+ // = 22 bytes parameter)
+
+ // write bitmap
+ WriteDIB(rBitmap, *pWMF, false, true);
+
+ // write the parameters:
+ nPosEnd=pWMF->Tell();
+ pWMF->Seek(nPosAnf);
+
+ // determine raster-op, if nothing was passed
+ if( !nROP )
+ {
+ switch( eSrcRasterOp )
+ {
+ case RasterOp::Invert: nROP = W_DSTINVERT; break;
+ case RasterOp::Xor: nROP = W_SRCINVERT; break;
+ default: nROP = W_SRCCOPY;
+ }
+ }
+
+ pWMF->WriteUInt32( nROP ).
+ WriteInt16( 0 ).
+ WriteInt16( rBitmap.GetSizePixel().Height() ).
+ WriteInt16( rBitmap.GetSizePixel().Width() ).
+ WriteInt16( 0 ).
+ WriteInt16( 0 );
+
+ WriteHeightWidth(rSize);
+ WritePointYX(rPoint);
+ pWMF->Seek(nPosEnd);
+
+ UpdateRecordHeader();
+
+ nWrittenBitmaps++;
+ nActBitmapPercent=0;
+}
+
+void WMFWriter::WMFRecord_TextOut(const Point & rPoint, std::u16string_view rStr)
+{
+ rtl_TextEncoding eChrSet = aSrcFont.GetCharSet();
+ OString aString(OUStringToOString(rStr, eChrSet));
+ TrueTextOut(rPoint, aString);
+}
+
+void WMFWriter::TrueTextOut(const Point & rPoint, const OString& rString)
+{
+ WriteRecordHeader(0,W_META_TEXTOUT);
+
+ write_uInt16_lenPrefixed_uInt8s_FromOString(*pWMF, rString);
+ sal_Int32 nLen = rString.getLength();
+ if ((nLen&1)!=0) pWMF->WriteUChar( 0 );
+ WritePointYX(rPoint);
+ UpdateRecordHeader();
+}
+
+void WMFWriter::WMFRecord_IntersectClipRect( const tools::Rectangle& rRect )
+{
+ WriteRecordHeader( 0x00000007, W_META_INTERSECTCLIPRECT );
+ WriteRectangle(rRect);
+}
+
+sal_uInt16 WMFWriter::AllocHandle()
+{
+ sal_uInt16 i;
+
+ for (i=0; i<MAXOBJECTHANDLES; i++) {
+ if (!bHandleAllocated[i]) {
+ bHandleAllocated[i]=true;
+ return i;
+ }
+ }
+ bStatus=false;
+ return 0xffff;
+}
+
+void WMFWriter::FreeHandle(sal_uInt16 nObjectHandle)
+{
+ if (nObjectHandle<MAXOBJECTHANDLES) bHandleAllocated[nObjectHandle]=false;
+}
+
+void WMFWriter::CreateSelectDeletePen( const Color& rColor, const LineInfo& rLineInfo )
+{
+ sal_uInt16 nOldHandle;
+
+ nOldHandle=nDstPenHandle;
+ nDstPenHandle=AllocHandle();
+ WMFRecord_CreatePenIndirect( rColor, rLineInfo );
+ WMFRecord_SelectObject(nDstPenHandle);
+ if (nOldHandle<MAXOBJECTHANDLES) {
+ WMFRecord_DeleteObject(nOldHandle);
+ FreeHandle(nOldHandle);
+ }
+}
+
+void WMFWriter::CreateSelectDeleteFont(const vcl::Font & rFont)
+{
+ sal_uInt16 nOldHandle;
+
+ nOldHandle=nDstFontHandle;
+ nDstFontHandle=AllocHandle();
+ WMFRecord_CreateFontIndirect(rFont);
+ WMFRecord_SelectObject(nDstFontHandle);
+ if (nOldHandle<MAXOBJECTHANDLES) {
+ WMFRecord_DeleteObject(nOldHandle);
+ FreeHandle(nOldHandle);
+ }
+}
+
+void WMFWriter::CreateSelectDeleteBrush(const Color& rColor)
+{
+ sal_uInt16 nOldHandle;
+
+ nOldHandle=nDstBrushHandle;
+ nDstBrushHandle=AllocHandle();
+ WMFRecord_CreateBrushIndirect(rColor);
+ WMFRecord_SelectObject(nDstBrushHandle);
+ if (nOldHandle<MAXOBJECTHANDLES) {
+ WMFRecord_DeleteObject(nOldHandle);
+ FreeHandle(nOldHandle);
+ }
+}
+
+void WMFWriter::SetLineAndFillAttr()
+{
+ if ( eDstROP2 != eSrcRasterOp )
+ {
+ eDstROP2=eSrcRasterOp;
+ WMFRecord_SetROP2(eDstROP2);
+ }
+ if ( ( aDstLineColor != aSrcLineColor ) || ( aDstLineInfo != aSrcLineInfo ) )
+ {
+ aDstLineColor = aSrcLineColor;
+ aDstLineInfo = aSrcLineInfo;
+ CreateSelectDeletePen( aDstLineColor, aDstLineInfo );
+ }
+ if ( aDstFillColor != aSrcFillColor )
+ {
+ aDstFillColor = aSrcFillColor;
+ CreateSelectDeleteBrush( aDstFillColor );
+ }
+}
+
+void WMFWriter::SetAllAttr()
+{
+ SetLineAndFillAttr();
+ if ( aDstTextColor != aSrcTextColor )
+ {
+ aDstTextColor = aSrcTextColor;
+ WMFRecord_SetTextColor(aDstTextColor);
+ }
+ if ( eDstTextAlign != eSrcTextAlign || eDstHorTextAlign != eSrcHorTextAlign )
+ {
+ eDstTextAlign = eSrcTextAlign;
+ eDstHorTextAlign = eSrcHorTextAlign;
+ WMFRecord_SetTextAlign( eDstTextAlign, eDstHorTextAlign );
+ }
+ if ( aDstFont == aSrcFont )
+ return;
+
+ pVirDev->SetFont(aSrcFont);
+ if ( aDstFont.GetFamilyName() != aSrcFont.GetFamilyName() )
+ {
+ FontCharMapRef xFontCharMap;
+ if ( pVirDev->GetFontCharMap( xFontCharMap ) )
+ {
+ if ( ( xFontCharMap->GetFirstChar() & 0xff00 ) == 0xf000 )
+ aSrcFont.SetCharSet( RTL_TEXTENCODING_SYMBOL );
+ else if ( aSrcFont.GetCharSet() == RTL_TEXTENCODING_SYMBOL )
+ aSrcFont.SetCharSet( RTL_TEXTENCODING_MS_1252 );
+ }
+ }
+
+ aDstFont = aSrcFont;
+ CreateSelectDeleteFont(aDstFont);
+}
+
+void WMFWriter::HandleLineInfoPolyPolygons(const LineInfo& rInfo, const basegfx::B2DPolygon& rLinePolygon)
+{
+ if(!rLinePolygon.count())
+ return;
+
+ basegfx::B2DPolyPolygon aLinePolyPolygon(rLinePolygon);
+ basegfx::B2DPolyPolygon aFillPolyPolygon;
+
+ rInfo.applyToB2DPolyPolygon(aLinePolyPolygon, aFillPolyPolygon);
+
+ if(aLinePolyPolygon.count())
+ {
+ aSrcLineInfo = rInfo;
+ SetLineAndFillAttr();
+
+ for(auto const& rB2DPolygon : std::as_const(aLinePolyPolygon))
+ {
+ WMFRecord_PolyLine( tools::Polygon(rB2DPolygon) );
+ }
+ }
+
+ if(!aFillPolyPolygon.count())
+ return;
+
+ const Color aOldLineColor(aSrcLineColor);
+ const Color aOldFillColor(aSrcFillColor);
+
+ aSrcLineColor = COL_TRANSPARENT;
+ aSrcFillColor = aOldLineColor;
+ SetLineAndFillAttr();
+
+ for(auto const& rB2DPolygon : std::as_const(aFillPolyPolygon))
+ {
+ WMFRecord_Polygon( tools::Polygon(rB2DPolygon) );
+ }
+
+ aSrcLineColor = aOldLineColor;
+ aSrcFillColor = aOldFillColor;
+ SetLineAndFillAttr();
+}
+
+void WMFWriter::WriteRecords( const GDIMetaFile & rMTF )
+{
+ if( !bStatus )
+ return;
+
+ size_t nACount = rMTF.GetActionSize();
+
+ WMFRecord_SetStretchBltMode();
+
+ for( size_t nA = 0; nA < nACount; nA++ )
+ {
+ MetaAction* pMA = rMTF.GetAction( nA );
+
+ switch( pMA->GetType() )
+ {
+ case MetaActionType::PIXEL:
+ {
+ const MetaPixelAction* pA = static_cast<const MetaPixelAction *>(pMA);
+ aSrcLineInfo = LineInfo();
+ SetLineAndFillAttr();
+ WMFRecord_SetPixel( pA->GetPoint(), pA->GetColor() );
+ }
+ break;
+
+ case MetaActionType::POINT:
+ {
+ const MetaPointAction* pA = static_cast<const MetaPointAction*>(pMA);
+ const Point& rPt = pA->GetPoint();
+ aSrcLineInfo = LineInfo();
+ SetLineAndFillAttr();
+ WMFRecord_MoveTo( rPt);
+ WMFRecord_LineTo( rPt );
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ const MetaLineAction* pA = static_cast<const MetaLineAction *>(pMA);
+ if(pA->GetLineInfo().IsDefault())
+ {
+ aSrcLineInfo = pA->GetLineInfo();
+ SetLineAndFillAttr();
+ WMFRecord_MoveTo( pA->GetStartPoint() );
+ WMFRecord_LineTo( pA->GetEndPoint() );
+ }
+ else
+ {
+ // LineInfo used; handle Dash/Dot and fat lines
+ basegfx::B2DPolygon aPolygon;
+ aPolygon.append(basegfx::B2DPoint(pA->GetStartPoint().X(), pA->GetStartPoint().Y()));
+ aPolygon.append(basegfx::B2DPoint(pA->GetEndPoint().X(), pA->GetEndPoint().Y()));
+ HandleLineInfoPolyPolygons(pA->GetLineInfo(), aPolygon);
+ }
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ const MetaRectAction* pA = static_cast<const MetaRectAction*>(pMA);
+ aSrcLineInfo = LineInfo();
+ SetLineAndFillAttr();
+ WMFRecord_Rectangle( pA->GetRect() );
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ const MetaRoundRectAction* pA = static_cast<const MetaRoundRectAction*>(pMA);
+ aSrcLineInfo = LineInfo();
+ SetLineAndFillAttr();
+ WMFRecord_RoundRect( pA->GetRect(), pA->GetHorzRound(), pA->GetVertRound() );
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ const MetaEllipseAction* pA = static_cast<const MetaEllipseAction*>(pMA);
+ aSrcLineInfo = LineInfo();
+ SetLineAndFillAttr();
+ WMFRecord_Ellipse( pA->GetRect() );
+ }
+ break;
+
+ case MetaActionType::ARC:
+ {
+ const MetaArcAction* pA = static_cast<const MetaArcAction*>(pMA);
+ aSrcLineInfo = LineInfo();
+ SetLineAndFillAttr();
+ WMFRecord_Arc( pA->GetRect(),pA->GetStartPoint(),pA->GetEndPoint() );
+ }
+ break;
+
+ case MetaActionType::PIE:
+ {
+ const MetaPieAction* pA = static_cast<const MetaPieAction*>(pMA);
+ aSrcLineInfo = LineInfo();
+ SetLineAndFillAttr();
+ WMFRecord_Pie( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() );
+ }
+ break;
+
+ case MetaActionType::CHORD:
+ {
+ const MetaChordAction* pA = static_cast<const MetaChordAction*>(pMA);
+ aSrcLineInfo = LineInfo();
+ SetLineAndFillAttr();
+ WMFRecord_Chord( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() );
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ const MetaPolyLineAction* pA = static_cast<const MetaPolyLineAction*>(pMA);
+ const tools::Polygon& rPoly = pA->GetPolygon();
+
+ if( rPoly.GetSize() )
+ {
+ if(pA->GetLineInfo().IsDefault())
+ {
+ aSrcLineInfo = pA->GetLineInfo();
+ SetLineAndFillAttr();
+ WMFRecord_PolyLine( rPoly );
+ }
+ else
+ {
+ // LineInfo used; handle Dash/Dot and fat lines
+ HandleLineInfoPolyPolygons(pA->GetLineInfo(), rPoly.getB2DPolygon());
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::POLYGON:
+ {
+ const MetaPolygonAction* pA = static_cast<const MetaPolygonAction*>(pMA);
+ aSrcLineInfo = LineInfo();
+ SetLineAndFillAttr();
+ WMFRecord_Polygon( pA->GetPolygon() );
+ }
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ {
+ const MetaPolyPolygonAction* pA = static_cast<const MetaPolyPolygonAction*>(pMA);
+ aSrcLineInfo = LineInfo();
+ SetLineAndFillAttr();
+ WMFRecord_PolyPolygon( pA->GetPolyPolygon() );
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ const MetaTextRectAction * pA = static_cast<const MetaTextRectAction*>(pMA);
+ OUString aTemp( pA->GetText() );
+ aSrcLineInfo = LineInfo();
+ SetAllAttr();
+
+ Point aPos( pA->GetRect().TopLeft() );
+ if ( !WMFRecord_Escape_Unicode( aPos, aTemp, {} ) )
+ WMFRecord_TextOut( aPos, aTemp );
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction * pA = static_cast<const MetaTextAction*>(pMA);
+ OUString aTemp = pA->GetText().copy( pA->GetIndex(), std::min<sal_Int32>(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) );
+ aSrcLineInfo = LineInfo();
+ SetAllAttr();
+ if ( !WMFRecord_Escape_Unicode( pA->GetPoint(), aTemp, {} ) )
+ WMFRecord_TextOut( pA->GetPoint(), aTemp );
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pMA);
+
+ OUString aTemp = pA->GetText().copy( pA->GetIndex(), std::min<sal_Int32>(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) );
+ aSrcLineInfo = LineInfo();
+ SetAllAttr();
+ if ( !WMFRecord_Escape_Unicode( pA->GetPoint(), aTemp, pA->GetDXArray() ) )
+ WMFRecord_ExtTextOut( pA->GetPoint(), aTemp, pA->GetDXArray() );
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction *>(pMA);
+ OUString aTemp = pA->GetText().copy( pA->GetIndex(), std::min<sal_Int32>(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) );
+
+ pVirDev->SetFont( aSrcFont );
+ const sal_Int32 nLen = aTemp.getLength();
+ std::vector<sal_Int32> aDXAry;
+ const sal_Int32 nNormSize = pVirDev->GetTextArray( aTemp, nLen ? &aDXAry : nullptr );
+ if (nLen && nNormSize == 0)
+ {
+ OSL_FAIL("Impossible div by 0 action: MetaStretchTextAction!");
+ }
+ else
+ {
+ for ( sal_Int32 i = 0; i < ( nLen - 1 ); i++ )
+ aDXAry[ i ] = aDXAry[ i ] * static_cast<sal_Int32>(pA->GetWidth()) / nNormSize;
+ if ( ( nLen <= 1 ) || ( static_cast<sal_Int32>(pA->GetWidth()) == nNormSize ) )
+ aDXAry.clear();
+ aSrcLineInfo = LineInfo();
+ SetAllAttr();
+ if ( !WMFRecord_Escape_Unicode( pA->GetPoint(), aTemp, aDXAry ) )
+ WMFRecord_ExtTextOut( pA->GetPoint(), aTemp, aDXAry );
+ }
+ }
+ break;
+
+ case MetaActionType::BMP:
+ {
+ const MetaBmpAction* pA = static_cast<const MetaBmpAction *>(pMA);
+ WMFRecord_StretchDIB( pA->GetPoint(), pA->GetBitmap().GetSizePixel(), pA->GetBitmap() );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pMA);
+ WMFRecord_StretchDIB( pA->GetPoint(), pA->GetSize(), pA->GetBitmap() );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pMA);
+ Bitmap aTmp( pA->GetBitmap() );
+
+ if( aTmp.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) ) )
+ WMFRecord_StretchDIB( pA->GetDestPoint(), pA->GetDestSize(), aTmp );
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ const MetaBmpExAction* pA = static_cast<const MetaBmpExAction *>(pMA);
+ Bitmap aBmp( pA->GetBitmapEx().GetBitmap() );
+ Bitmap aMsk( pA->GetBitmapEx().GetAlpha() );
+
+ if( !aMsk.IsEmpty() )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ aMsk.Invert();
+ WMFRecord_StretchDIB( pA->GetPoint(), aMsk.GetSizePixel(), aBmp, W_SRCPAINT );
+ WMFRecord_StretchDIB( pA->GetPoint(), aBmp.GetSizePixel(), aBmp, W_SRCAND );
+ }
+ else
+ WMFRecord_StretchDIB( pA->GetPoint(), aBmp.GetSizePixel(), aBmp );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pMA);
+ Bitmap aBmp( pA->GetBitmapEx().GetBitmap() );
+ Bitmap aMsk( pA->GetBitmapEx().GetAlpha() );
+
+ if( !aMsk.IsEmpty() )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ aMsk.Invert();
+ WMFRecord_StretchDIB( pA->GetPoint(), pA->GetSize(), aMsk, W_SRCPAINT );
+ WMFRecord_StretchDIB( pA->GetPoint(), pA->GetSize(), aBmp, W_SRCAND );
+ }
+ else
+ WMFRecord_StretchDIB( pA->GetPoint(), pA->GetSize(), aBmp );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pMA);
+ BitmapEx aBmpEx( pA->GetBitmapEx() );
+ aBmpEx.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) );
+ Bitmap aBmp( aBmpEx.GetBitmap() );
+ Bitmap aMsk( aBmpEx.GetAlpha() );
+
+ if( !aMsk.IsEmpty() )
+ {
+ aBmp.Replace( aMsk, COL_WHITE );
+ aMsk.Invert();
+ WMFRecord_StretchDIB( pA->GetDestPoint(), pA->GetDestSize(), aMsk, W_SRCPAINT );
+ WMFRecord_StretchDIB( pA->GetDestPoint(), pA->GetDestSize(), aBmp, W_SRCAND );
+ }
+ else
+ WMFRecord_StretchDIB( pA->GetDestPoint(), pA->GetDestSize(), aBmp );
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pMA);
+ GDIMetaFile aTmpMtf;
+
+ Gradient aGradient = pA->GetGradient();
+ aGradient.AddGradientActions( pA->GetRect(), aTmpMtf );
+ WriteRecords( aTmpMtf );
+ }
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ const MetaHatchAction* pA = static_cast<const MetaHatchAction*>(pMA);
+ GDIMetaFile aTmpMtf;
+
+ pVirDev->AddHatchActions( pA->GetPolyPolygon(), pA->GetHatch(), aTmpMtf );
+ WriteRecords( aTmpMtf );
+ }
+ break;
+
+ case MetaActionType::WALLPAPER:
+ {
+ const MetaWallpaperAction* pA = static_cast<const MetaWallpaperAction*>(pMA);
+ const Color& rColor = pA->GetWallpaper().GetColor();
+ const Color aOldLineColor( aSrcLineColor );
+ const Color aOldFillColor( aSrcFillColor );
+
+ aSrcLineColor = rColor;
+ aSrcFillColor = rColor;
+ aSrcLineInfo = LineInfo();
+ SetLineAndFillAttr();
+ WMFRecord_Rectangle( pA->GetRect() );
+ aSrcLineColor = aOldLineColor;
+ aSrcFillColor = aOldFillColor;
+ }
+ break;
+
+ case MetaActionType::ISECTRECTCLIPREGION:
+ {
+ const MetaISectRectClipRegionAction* pA = static_cast<const MetaISectRectClipRegionAction*>(pMA);
+ WMFRecord_IntersectClipRect( pA->GetRect() );
+ }
+ break;
+
+ case MetaActionType::LINECOLOR:
+ {
+ const MetaLineColorAction* pA = static_cast<const MetaLineColorAction*>(pMA);
+
+ if( pA->IsSetting() )
+ aSrcLineColor = pA->GetColor();
+ else
+ aSrcLineColor = COL_TRANSPARENT;
+ }
+ break;
+
+ case MetaActionType::FILLCOLOR:
+ {
+ const MetaFillColorAction* pA = static_cast<const MetaFillColorAction*>(pMA);
+
+ if( pA->IsSetting() )
+ aSrcFillColor = pA->GetColor();
+ else
+ aSrcFillColor = COL_TRANSPARENT;
+ }
+ break;
+
+ case MetaActionType::TEXTCOLOR:
+ {
+ const MetaTextColorAction* pA = static_cast<const MetaTextColorAction*>(pMA);
+ aSrcTextColor = pA->GetColor();
+ }
+ break;
+
+ case MetaActionType::TEXTFILLCOLOR:
+ {
+ const MetaTextFillColorAction* pA = static_cast<const MetaTextFillColorAction*>(pMA);
+ if( pA->IsSetting() )
+ aSrcFont.SetFillColor( pA->GetColor() );
+ else
+ aSrcFont.SetFillColor( COL_TRANSPARENT );
+ }
+ break;
+
+ case MetaActionType::TEXTALIGN:
+ {
+ const MetaTextAlignAction* pA = static_cast<const MetaTextAlignAction*>(pMA);
+ eSrcTextAlign = pA->GetTextAlign();
+ }
+ break;
+
+ case MetaActionType::MAPMODE:
+ {
+ const MetaMapModeAction* pA = static_cast<const MetaMapModeAction*>(pMA);
+
+ if (aSrcMapMode!=pA->GetMapMode())
+ {
+ if( pA->GetMapMode().GetMapUnit() == MapUnit::MapRelative )
+ {
+ const MapMode& aMM = pA->GetMapMode();
+ Fraction aScaleX = aMM.GetScaleX();
+ Fraction aScaleY = aMM.GetScaleY();
+
+ Point aOrigin = aSrcMapMode.GetOrigin();
+ BigInt aX( aOrigin.X() );
+ aX *= BigInt( aScaleX.GetDenominator() );
+ if( aOrigin.X() >= 0 )
+ if( aScaleX.GetNumerator() >= 0 )
+ aX += BigInt( aScaleX.GetNumerator()/2 );
+ else
+ aX -= BigInt( (aScaleX.GetNumerator()+1)/2 );
+ else
+ if( aScaleX.GetNumerator() >= 0 )
+ aX -= BigInt( (aScaleX.GetNumerator()-1)/2 );
+ else
+ aX += BigInt( aScaleX.GetNumerator()/2 );
+ aX /= BigInt( aScaleX.GetNumerator() );
+ aOrigin.setX( static_cast<tools::Long>(aX) + aMM.GetOrigin().X() );
+ BigInt aY( aOrigin.Y() );
+ aY *= BigInt( aScaleY.GetDenominator() );
+ if( aOrigin.Y() >= 0 )
+ if( aScaleY.GetNumerator() >= 0 )
+ aY += BigInt( aScaleY.GetNumerator()/2 );
+ else
+ aY -= BigInt( (aScaleY.GetNumerator()+1)/2 );
+ else
+ if( aScaleY.GetNumerator() >= 0 )
+ aY -= BigInt( (aScaleY.GetNumerator()-1)/2 );
+ else
+ aY += BigInt( aScaleY.GetNumerator()/2 );
+ aY /= BigInt( aScaleY.GetNumerator() );
+ aOrigin.setY( static_cast<tools::Long>(aY) + aMM.GetOrigin().Y() );
+ aSrcMapMode.SetOrigin( aOrigin );
+
+ aScaleX *= aSrcMapMode.GetScaleX();
+ aScaleY *= aSrcMapMode.GetScaleY();
+ aSrcMapMode.SetScaleX( aScaleX );
+ aSrcMapMode.SetScaleY( aScaleY );
+ }
+ else
+ aSrcMapMode=pA->GetMapMode();
+ }
+ }
+ break;
+
+ case MetaActionType::FONT:
+ {
+ const MetaFontAction* pA = static_cast<const MetaFontAction*>(pMA);
+ aSrcFont = pA->GetFont();
+
+ if ( (aSrcFont.GetCharSet() == RTL_TEXTENCODING_DONTKNOW)
+ || (aSrcFont.GetCharSet() == RTL_TEXTENCODING_UNICODE) )
+ {
+ aSrcFont.SetCharSet( RTL_TEXTENCODING_MS_1252 );
+ }
+ eSrcTextAlign = aSrcFont.GetAlignment();
+ aSrcTextColor = aSrcFont.GetColor();
+ aSrcFont.SetAlignment( ALIGN_BASELINE );
+ aSrcFont.SetColor( COL_WHITE );
+ }
+ break;
+
+ case MetaActionType::PUSH:
+ {
+ const MetaPushAction* pA = static_cast<const MetaPushAction*>(pMA);
+
+ WMFWriterAttrStackMember* pAt = new WMFWriterAttrStackMember;
+ pAt->nFlags = pA->GetFlags();
+ pAt->aClipRegion = aSrcClipRegion;
+ pAt->aLineColor=aSrcLineColor;
+ pAt->aFillColor=aSrcFillColor;
+ pAt->eRasterOp=eSrcRasterOp;
+ pAt->aFont=aSrcFont;
+ pAt->eTextAlign=eSrcTextAlign;
+ pAt->aTextColor=aSrcTextColor;
+ pAt->aMapMode=aSrcMapMode;
+ pAt->aLineInfo=aDstLineInfo;
+ pAt->pSucc=pAttrStack;
+ pAttrStack=pAt;
+
+ SetAllAttr(); // update ( now all source attributes are equal to the destination attributes )
+ WMFRecord_SaveDC();
+
+ }
+ break;
+
+ case MetaActionType::POP:
+ {
+ WMFWriterAttrStackMember * pAt=pAttrStack;
+
+ if( pAt )
+ {
+ aDstLineInfo = pAt->aLineInfo;
+ aDstLineColor = pAt->aLineColor;
+ if ( pAt->nFlags & vcl::PushFlags::LINECOLOR )
+ aSrcLineColor = pAt->aLineColor;
+ aDstFillColor = pAt->aFillColor;
+ if ( pAt->nFlags & vcl::PushFlags::FILLCOLOR )
+ aSrcFillColor = pAt->aFillColor;
+ eDstROP2 = pAt->eRasterOp;
+ if ( pAt->nFlags & vcl::PushFlags::RASTEROP )
+ eSrcRasterOp = pAt->eRasterOp;
+ aDstFont = pAt->aFont;
+ if ( pAt->nFlags & vcl::PushFlags::FONT )
+ aSrcFont = pAt->aFont;
+ eDstTextAlign = pAt->eTextAlign;
+ if ( pAt->nFlags & ( vcl::PushFlags::FONT | vcl::PushFlags::TEXTALIGN ) )
+ eSrcTextAlign = pAt->eTextAlign;
+ aDstTextColor = pAt->aTextColor;
+ if ( pAt->nFlags & ( vcl::PushFlags::FONT | vcl::PushFlags::TEXTCOLOR ) )
+ aSrcTextColor = pAt->aTextColor;
+ if ( pAt->nFlags & vcl::PushFlags::MAPMODE )
+ aSrcMapMode = pAt->aMapMode;
+ aDstClipRegion = pAt->aClipRegion;
+ if ( pAt->nFlags & vcl::PushFlags::CLIPREGION )
+ aSrcClipRegion = pAt->aClipRegion;
+
+ WMFRecord_RestoreDC();
+ pAttrStack = pAt->pSucc;
+ delete pAt;
+ }
+ }
+ break;
+
+ case MetaActionType::EPS :
+ {
+ const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pMA);
+ const GDIMetaFile& aGDIMetaFile( pA->GetSubstitute() );
+
+ size_t nCount = aGDIMetaFile.GetActionSize();
+ for ( size_t i = 0; i < nCount; i++ )
+ {
+ const MetaAction* pMetaAct = aGDIMetaFile.GetAction( i );
+ if ( pMetaAct->GetType() == MetaActionType::BMPSCALE )
+ {
+ const MetaBmpScaleAction* pBmpScaleAction = static_cast<const MetaBmpScaleAction*>(pMetaAct);
+ WMFRecord_StretchDIB( pA->GetPoint(), pA->GetSize(), pBmpScaleAction->GetBitmap() );
+ break;
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::RASTEROP:
+ {
+ const MetaRasterOpAction* pA = static_cast<const MetaRasterOpAction*>(pMA);
+ eSrcRasterOp=pA->GetRasterOp();
+ }
+ break;
+
+ case MetaActionType::Transparent:
+ {
+ aSrcLineInfo = LineInfo();
+ SetLineAndFillAttr();
+ WMFRecord_PolyPolygon( static_cast<const MetaTransparentAction*>(pMA)->GetPolyPolygon() );
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pMA);
+
+ GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() );
+ Point aSrcPt( aTmpMtf.GetPrefMapMode().GetOrigin() );
+ const Size aSrcSize( aTmpMtf.GetPrefSize() );
+ const Point aDestPt( pA->GetPoint() );
+ const Size aDestSize( pA->GetSize() );
+ const double fScaleX = aSrcSize.Width() ? static_cast<double>(aDestSize.Width()) / aSrcSize.Width() : 1.0;
+ const double fScaleY = aSrcSize.Height() ? static_cast<double>(aDestSize.Height()) / aSrcSize.Height() : 1.0;
+ tools::Long nMoveX, nMoveY;
+
+ aSrcLineInfo = LineInfo();
+ SetAllAttr();
+
+ if( fScaleX != 1.0 || fScaleY != 1.0 )
+ {
+ aTmpMtf.Scale( fScaleX, fScaleY );
+ aSrcPt.setX( FRound( aSrcPt.X() * fScaleX ) );
+ aSrcPt.setY( FRound( aSrcPt.Y() * fScaleY ) );
+ }
+
+ nMoveX = aDestPt.X() - aSrcPt.X();
+ nMoveY = aDestPt.Y() - aSrcPt.Y();
+
+ if( nMoveX || nMoveY )
+ aTmpMtf.Move( nMoveX, nMoveY );
+
+ WriteRecords( aTmpMtf );
+ }
+ break;
+
+ case MetaActionType::LAYOUTMODE:
+ {
+ vcl::text::ComplexTextLayoutFlags nLayoutMode = static_cast<const MetaLayoutModeAction*>(pMA)->GetLayoutMode();
+ eSrcHorTextAlign = 0; // TA_LEFT
+ if ((nLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl) != vcl::text::ComplexTextLayoutFlags::Default)
+ {
+ eSrcHorTextAlign = W_TA_RIGHT | W_TA_RTLREADING;
+ }
+ if ((nLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginRight) != vcl::text::ComplexTextLayoutFlags::Default)
+ eSrcHorTextAlign |= W_TA_RIGHT;
+ else if ((nLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginLeft) != vcl::text::ComplexTextLayoutFlags::Default)
+ eSrcHorTextAlign &= ~W_TA_RIGHT;
+ break;
+ }
+
+ case MetaActionType::CLIPREGION:
+ case MetaActionType::TEXTLANGUAGE:
+ case MetaActionType::COMMENT:
+ // Explicitly ignored cases
+ break;
+
+ default:
+ // TODO: Implement more cases as necessary. Let's not bother with a warning.
+ break;
+ }
+
+ nWrittenActions++;
+ MayCallback();
+
+ if (pWMF->GetError())
+ bStatus=false;
+
+ if(!bStatus)
+ break;
+ }
+}
+
+void WMFWriter::WriteHeader( bool bPlaceable )
+{
+ if( bPlaceable )
+ {
+ sal_uInt16 nCheckSum, nValue;
+ Size aSize( OutputDevice::LogicToLogic(Size(1,1),MapMode(MapUnit::MapInch), aTargetMapMode) );
+ sal_uInt16 nUnitsPerInch = static_cast<sal_uInt16>( ( aSize.Width() + aSize.Height() ) >> 1 );
+
+ nCheckSum=0;
+ nValue=0xcdd7; nCheckSum^=nValue; pWMF->WriteUInt16( nValue );
+ nValue=0x9ac6; nCheckSum^=nValue; pWMF->WriteUInt16( nValue );
+ nValue=0x0000; nCheckSum^=nValue; pWMF->WriteUInt16( nValue );
+ nValue=0x0000; nCheckSum^=nValue; pWMF->WriteUInt16( nValue );
+ nValue=0x0000; nCheckSum^=nValue; pWMF->WriteUInt16( nValue );
+ nValue=static_cast<sal_uInt16>(aTargetSize.Width()); nCheckSum^=nValue; pWMF->WriteUInt16( nValue );
+ nValue=static_cast<sal_uInt16>(aTargetSize.Height()); nCheckSum^=nValue; pWMF->WriteUInt16( nValue );
+ nValue=nUnitsPerInch; nCheckSum^=nValue; pWMF->WriteUInt16( nValue );
+ nValue=0x0000; nCheckSum^=nValue; pWMF->WriteUInt16( nValue );
+ nValue=0x0000; nCheckSum^=nValue; pWMF->WriteUInt16( nValue );
+ pWMF->WriteUInt16( nCheckSum );
+ }
+
+ nMetafileHeaderPos=pWMF->Tell();
+ pWMF->WriteUInt16( 0x0001 ) // type: file
+ .WriteUInt16( 0x0009 ) // header length in words
+ .WriteUInt16( 0x0300 ) // Version as BCD number
+ .WriteUInt32( 0x00000000 ) // file length (without 1st header), is later corrected by UpdateHeader()
+ .WriteUInt16( MAXOBJECTHANDLES ) // maximum number of simultaneous objects
+ .WriteUInt32( 0x00000000 ) // maximum record length, is later corrected by UpdateHeader()
+ .WriteUInt16( 0x0000 ); // reserved
+}
+
+void WMFWriter::UpdateHeader()
+{
+ sal_uLong nPos;
+ sal_uInt32 nFileSize;
+
+ nPos=pWMF->Tell(); // endposition = total size of file
+ nFileSize=nPos-nMetafileHeaderPos; // subtract size of 1st header
+ if ((nFileSize&1)!=0) { // if needed round to words
+ pWMF->WriteUChar( 0 );
+ nPos++;
+ nFileSize++;
+ }
+ nFileSize>>=1; // convert to number of words
+ pWMF->Seek(nMetafileHeaderPos+6); // to filesize entry in second header
+ pWMF->WriteUInt32( nFileSize ); // rectify file size
+ pWMF->SeekRel(2); // to max-record-length-entry in second header
+ pWMF->WriteUInt32( nMaxRecordSize ); // and rectify
+ pWMF->Seek(nPos);
+}
+
+bool WMFWriter::WriteWMF( const GDIMetaFile& rMTF, SvStream& rTargetStream,
+ FilterConfigItem const * pFConfigItem, bool bPlaceable )
+{
+ WMFWriterAttrStackMember * pAt;
+
+ bEmbedEMF = true;
+ bStatus=true;
+ pVirDev = VclPtr<VirtualDevice>::Create();
+
+ if (pFConfigItem)
+ {
+ xStatusIndicator = pFConfigItem->GetStatusIndicator();
+ if ( xStatusIndicator.is() )
+ {
+ xStatusIndicator->start( OUString(), 100 );
+ }
+
+ comphelper::SequenceAsHashMap aMap(pFConfigItem->GetFilterData());
+ auto it = aMap.find("EmbedEMF");
+ if (it != aMap.end())
+ {
+ it->second >>= bEmbedEMF;
+ }
+ }
+ nLastPercent=0;
+
+ pWMF=&rTargetStream;
+ pWMF->SetEndian(SvStreamEndian::LITTLE);
+
+ nMaxRecordSize=0;
+
+ aSrcMapMode=rMTF.GetPrefMapMode();
+
+ if( bPlaceable )
+ {
+ aTargetMapMode = aSrcMapMode;
+ aTargetSize = rMTF.GetPrefSize();
+ sal_uInt16 nTargetDivisor = CalcSaveTargetMapMode(aTargetMapMode, aTargetSize);
+ aTargetSize.setWidth( aTargetSize.Width() / nTargetDivisor );
+ aTargetSize.setHeight( aTargetSize.Height() / nTargetDivisor );
+ }
+ else
+ {
+ aTargetMapMode = MapMode( MapUnit::MapInch );
+
+ const tools::Long nUnit = pVirDev->LogicToPixel( Size( 1, 1 ), aTargetMapMode ).Width();
+ const Fraction aFrac( 1, nUnit );
+
+ aTargetMapMode.SetScaleX( aFrac );
+ aTargetMapMode.SetScaleY( aFrac );
+ aTargetSize = OutputDevice::LogicToLogic( rMTF.GetPrefSize(), aSrcMapMode, aTargetMapMode );
+ }
+
+ pVirDev->SetMapMode( aTargetMapMode );
+
+ pAttrStack=nullptr;
+
+ for (bool & rn : bHandleAllocated)
+ rn=false;
+
+ nDstPenHandle=0xffff;
+ nDstFontHandle=0xffff;
+ nDstBrushHandle=0xffff;
+
+ nNumberOfActions=0;
+ nNumberOfBitmaps=0;
+ nWrittenActions=0;
+ nWrittenBitmaps=0;
+ nActBitmapPercent=0;
+
+ CountActionsAndBitmaps(rMTF);
+
+ WriteHeader(bPlaceable);
+ if( bEmbedEMF )
+ WriteEmbeddedEMF( rMTF );
+ WMFRecord_SetWindowOrg(Point(0,0));
+ WMFRecord_SetWindowExt(rMTF.GetPrefSize());
+ WMFRecord_SetBkMode( true );
+
+ eDstROP2 = eSrcRasterOp = RasterOp::OverPaint;
+ WMFRecord_SetROP2(eDstROP2);
+
+ aDstLineInfo = LineInfo();
+ aDstLineColor = aSrcLineColor = COL_BLACK;
+ CreateSelectDeletePen( aDstLineColor, aDstLineInfo );
+
+ aDstFillColor = aSrcFillColor = COL_WHITE;
+ CreateSelectDeleteBrush( aDstFillColor );
+
+ aDstClipRegion = aSrcClipRegion = vcl::Region();
+
+ vcl::Font aFont;
+ aFont.SetCharSet( GetExtendedTextEncoding( RTL_TEXTENCODING_MS_1252 ) );
+ aFont.SetColor( COL_WHITE );
+ aFont.SetAlignment( ALIGN_BASELINE );
+ aDstFont = aSrcFont = aFont;
+ CreateSelectDeleteFont(aDstFont);
+
+ eDstTextAlign = eSrcTextAlign = ALIGN_BASELINE;
+ eDstHorTextAlign = eSrcHorTextAlign = W_TA_LEFT;
+ WMFRecord_SetTextAlign( eDstTextAlign, eDstHorTextAlign );
+
+ aDstTextColor = aSrcTextColor = COL_WHITE;
+ WMFRecord_SetTextColor(aDstTextColor);
+
+ // Write records
+ WriteRecords(rMTF);
+
+ WriteRecordHeader(0x00000003,0x0000); // end of file
+ UpdateHeader();
+
+ while(pAttrStack)
+ {
+ pAt=pAttrStack;
+ pAttrStack=pAt->pSucc;
+ delete pAt;
+ }
+
+ pVirDev.disposeAndClear();
+
+ if ( xStatusIndicator.is() )
+ xStatusIndicator->end();
+
+ return bStatus;
+}
+
+sal_uInt16 WMFWriter::CalcSaveTargetMapMode(MapMode& rMapMode,
+ const Size& rPrefSize)
+{
+ Fraction aDivFrac(2, 1);
+ sal_uInt16 nDivisor = 1;
+
+ Size aSize = OutputDevice::LogicToLogic( rPrefSize, aSrcMapMode, rMapMode );
+
+ while( nDivisor <= 64 && (aSize.Width() > 32767 || aSize.Height() > 32767) )
+ {
+ Fraction aFrac = rMapMode.GetScaleX();
+
+ aFrac *= aDivFrac;
+ rMapMode.SetScaleX(aFrac);
+ aFrac = rMapMode.GetScaleY();
+ aFrac *= aDivFrac;
+ rMapMode.SetScaleY(aFrac);
+ nDivisor <<= 1;
+ aSize = OutputDevice::LogicToLogic( rPrefSize, aSrcMapMode, rMapMode );
+ }
+
+ return nDivisor;
+}
+
+void WMFWriter::WriteEmbeddedEMF( const GDIMetaFile& rMTF )
+{
+ SvMemoryStream aStream;
+ EMFWriter aEMFWriter(aStream);
+
+ if( !aEMFWriter.WriteEMF( rMTF ) )
+ return;
+
+ sal_uInt64 const nTotalSize = aStream.Tell();
+ if( nTotalSize > SAL_MAX_UINT32 )
+ return;
+ aStream.Seek( 0 );
+ sal_uInt32 nRemainingSize = static_cast< sal_uInt32 >( nTotalSize );
+ sal_uInt32 nRecCounts = ( (nTotalSize - 1) / 0x2000 ) + 1;
+ sal_uInt16 nCheckSum = 0, nWord;
+
+ sal_uInt32 nPos = 0;
+
+ while( nPos + 1 < nTotalSize )
+ {
+ aStream.ReadUInt16( nWord );
+ nCheckSum ^= nWord;
+ nPos += 2;
+ }
+
+ nCheckSum = static_cast< sal_uInt16 >( nCheckSum * -1 );
+
+ aStream.Seek( 0 );
+ while( nRemainingSize > 0 )
+ {
+ sal_uInt32 nCurSize;
+ if( nRemainingSize > 0x2000 )
+ {
+ nCurSize = 0x2000;
+ nRemainingSize -= 0x2000;
+ }
+ else
+ {
+ nCurSize = nRemainingSize;
+ nRemainingSize = 0;
+ }
+ WriteEMFRecord( aStream,
+ nCurSize,
+ nRemainingSize,
+ nTotalSize,
+ nRecCounts,
+ nCheckSum );
+ nCheckSum = 0;
+ }
+
+}
+
+void WMFWriter::WriteEMFRecord( SvMemoryStream& rStream, sal_uInt32 nCurSize, sal_uInt32 nRemainingSize,
+ sal_uInt32 nTotalSize, sal_uInt32 nRecCounts, sal_uInt16 nCheckSum )
+{
+ // according to http://msdn.microsoft.com/en-us/library/dd366152%28PROT.13%29.aspx
+ WriteRecordHeader( 0, W_META_ESCAPE );
+ pWMF->WriteUInt16( W_MFCOMMENT ) // same as META_ESCAPE_ENHANCED_METAFILE
+ .WriteUInt16( nCurSize + 34 ) // we will always have a 34 byte escape header:
+ .WriteUInt32( 0x43464D57 ) // WMFC
+ .WriteUInt32( 0x00000001 ) // Comment type
+ .WriteUInt32( 0x00010000 ) // version
+ .WriteUInt16( nCheckSum ) // check sum
+ .WriteUInt32( 0 ) // flags = 0
+ .WriteUInt32( nRecCounts ) // total number of records
+ .WriteUInt32( nCurSize ) // size of this record's data
+ .WriteUInt32( nRemainingSize ) // remaining size of data in following records, missing in MSDN documentation
+ .WriteUInt32( nTotalSize ); // total size of EMF stream
+
+ pWMF->WriteBytes(static_cast<const char*>(rStream.GetData()) + rStream.Tell(), nCurSize);
+ rStream.SeekRel( nCurSize );
+ UpdateRecordHeader();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/wmf/wmfwr.hxx b/vcl/source/filter/wmf/wmfwr.hxx
new file mode 100644
index 000000000..7b0ce679e
--- /dev/null
+++ b/vcl/source/filter/wmf/wmfwr.hxx
@@ -0,0 +1,205 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_WMF_WMFWR_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_WMF_WMFWR_HXX
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+#include <tools/stream.hxx>
+
+#define MAXOBJECTHANDLES 16
+
+struct WMFWriterAttrStackMember
+{
+ struct WMFWriterAttrStackMember * pSucc;
+ Color aLineColor;
+ Color aFillColor;
+ Color aTextColor;
+ LineInfo aLineInfo;
+ TextAlign eTextAlign;
+ RasterOp eRasterOp;
+ vcl::Font aFont;
+ MapMode aMapMode;
+ vcl::Region aClipRegion;
+ vcl::PushFlags nFlags;
+};
+
+class StarSymbolToMSMultiFont;
+class LineInfo;
+namespace basegfx { class B2DPolygon; }
+
+class WMFWriter
+{
+private:
+
+ bool bStatus;
+
+ sal_uLong nLastPercent; // with which number pCallback was called last time.
+
+ css::uno::Reference< css::task::XStatusIndicator > xStatusIndicator;
+
+ SvStream* pWMF;
+ VclPtr<VirtualDevice> pVirDev;
+ MapMode aTargetMapMode;
+ Size aTargetSize;
+
+ sal_uLong nMetafileHeaderPos;
+ sal_uInt32 nMaxRecordSize; // in words
+ sal_uLong nActRecordPos;
+
+ // actual attribute in source metafile:
+ Color aSrcLineColor;
+ Color aSrcFillColor;
+ Color aSrcTextColor;
+ LineInfo aSrcLineInfo;
+ RasterOp eSrcRasterOp;
+ TextAlign eSrcTextAlign;
+ vcl::Font aSrcFont;
+ MapMode aSrcMapMode;
+ vcl::Region aSrcClipRegion;
+ WMFWriterAttrStackMember * pAttrStack;
+
+ sal_uInt16 eSrcHorTextAlign;
+
+ // actual attribute in destination metafile:
+ Color aDstLineColor;
+ Color aDstFillColor;
+ Color aDstTextColor;
+ LineInfo aDstLineInfo;
+ RasterOp eDstROP2;
+ TextAlign eDstTextAlign;
+ vcl::Font aDstFont;
+
+ sal_uInt16 eDstHorTextAlign;
+
+ vcl::Region aDstClipRegion; // ???: not taken into account at the moment
+ bool bHandleAllocated[MAXOBJECTHANDLES]; // which handles have been assigned
+ sal_uInt16 nDstPenHandle,nDstFontHandle,nDstBrushHandle; // which handles are owned by
+ // Selected-Objects
+ // 0xFFFF = none:
+
+ // to prevent we have to compare all attributes at each operation:
+
+ sal_uLong nNumberOfActions; // number of actions in the GDIMetafile
+ sal_uLong nNumberOfBitmaps; // number of bitmaps
+ sal_uLong nWrittenActions; // number of processed actions while writing the directory
+ sal_uLong nWrittenBitmaps; // number of bitmaps written
+ sal_uLong nActBitmapPercent; // percentage of next bitmap written.
+
+ bool bEmbedEMF; // optionally embed EMF data into WMF
+
+ void MayCallback();
+ // this function calculates percentage using the above 5 parameters
+ // and triggers a callback if needed. Puts bStatus to FALSE if the
+ // users wants to abort.
+
+ void CountActionsAndBitmaps(const GDIMetaFile & rMTF);
+ // Counts bitmaps and actions (nNumberOfActions and nNumberOfBitmaps should
+ // be initialised to 0 at start, as this method is recursive)
+
+ void WritePointXY(const Point & rPoint);
+ void WritePointYX(const Point & rPoint);
+ sal_Int32 ScaleWidth( sal_Int32 nDX );
+ void WriteSize(const Size & rSize);
+ void WriteHeightWidth(const Size & rSize);
+ void WriteRectangle(const tools::Rectangle & rRect);
+ void WriteColor(const Color & rColor);
+
+ void WriteRecordHeader(sal_uInt32 nSizeWords, sal_uInt16 nType);
+ // nSizeWords is the size of the all records in number of words.
+ // If nSizeWords is unknown, then use 0 (see UpdateRecordHeader())
+
+ void UpdateRecordHeader();
+ // returns the size of the record after writing the parameters, if
+ // nSizeWords was unknown upon calling WriteRecordHeader(..)
+ // if needed it inserts a BYTE 0 to make number of bytes even
+
+ void WMFRecord_Arc(const tools::Rectangle& rRect, const Point& rStartPt, const Point& rEndPt);
+ void WMFRecord_Chord(const tools::Rectangle& rRect, const Point& rStartPt, const Point& rEndPt);
+ void WMFRecord_CreateBrushIndirect(const Color& rColor);
+ void WMFRecord_CreateFontIndirect(const vcl::Font& rFont);
+ void WMFRecord_CreatePenIndirect(const Color& rColor, const LineInfo& rLineInfo );
+ void WMFRecord_DeleteObject(sal_uInt16 nObjectHandle);
+ void WMFRecord_Ellipse(const tools::Rectangle& rRect);
+ void WMFRecord_Escape( sal_uInt32 nEsc, sal_uInt32 nLen, const sal_Int8* pData );
+ bool WMFRecord_Escape_Unicode( const Point& rPoint, const OUString& rStr, o3tl::span<const sal_Int32> pDXAry );
+ void WMFRecord_ExtTextOut(const Point& rPoint, const OUString& rString, o3tl::span<const sal_Int32> pDXAry);
+
+ void TrueExtTextOut(const Point& rPoint, const OUString& rString,
+ const OString& rByteString, o3tl::span<const sal_Int32> pDXAry);
+ void TrueTextOut(const Point& rPoint, const OString& rString);
+ void WMFRecord_LineTo(const Point & rPoint);
+ void WMFRecord_MoveTo(const Point & rPoint);
+ void WMFRecord_Pie(const tools::Rectangle & rRect, const Point & rStartPt, const Point & rEndPt);
+ void WMFRecord_Polygon(const tools::Polygon & rPoly);
+ void WMFRecord_PolyLine(const tools::Polygon & rPoly);
+ void WMFRecord_PolyPolygon(const tools::PolyPolygon & rPolyPoly);
+ void WMFRecord_Rectangle(const tools::Rectangle & rRect);
+ void WMFRecord_RestoreDC();
+ void WMFRecord_RoundRect(const tools::Rectangle & rRect, tools::Long nHorzRound, tools::Long nVertRound);
+ void WMFRecord_SaveDC();
+ void WMFRecord_SelectObject(sal_uInt16 nObjectHandle);
+ void WMFRecord_SetBkMode(bool bTransparent);
+ void WMFRecord_SetStretchBltMode();
+ void WMFRecord_SetPixel(const Point & rPoint, const Color & rColor);
+ void WMFRecord_SetROP2(RasterOp eROP);
+ void WMFRecord_SetTextAlign(TextAlign eFontAlign, sal_uInt16 eHorTextAlign);
+ void WMFRecord_SetTextColor(const Color & rColor);
+ void WMFRecord_SetWindowExt(const Size & rSize);
+ void WMFRecord_SetWindowOrg(const Point & rPoint);
+ void WMFRecord_StretchDIB(const Point & rPoint, const Size & rSize, const Bitmap & rBitmap, sal_uInt32 nROP = 0 );
+ void WMFRecord_TextOut(const Point & rPoint, std::u16string_view rString);
+ void WMFRecord_IntersectClipRect( const tools::Rectangle& rRect);
+
+ sal_uInt16 AllocHandle();
+ void FreeHandle(sal_uInt16 nObjectHandle);
+ void CreateSelectDeletePen( const Color& rColor, const LineInfo& rLineInfo );
+ void CreateSelectDeleteFont(const vcl::Font & rFont);
+ void CreateSelectDeleteBrush(const Color& rColor);
+
+ void SetLineAndFillAttr();
+ void SetAllAttr();
+
+ void HandleLineInfoPolyPolygons(const LineInfo& rInfo, const basegfx::B2DPolygon& rLinePolygon);
+ void WriteRecords(const GDIMetaFile & rMTF);
+
+ void WriteHeader(bool bPlaceable);
+ void UpdateHeader();
+
+ void WriteEmbeddedEMF( const GDIMetaFile& rMTF );
+ void WriteEMFRecord( SvMemoryStream& rStream, sal_uInt32 nCurSize,
+ sal_uInt32 nRemainingSize,
+ sal_uInt32 nTotalSize,
+ sal_uInt32 nRecCounts,
+ sal_uInt16 nCheckSum );
+
+ sal_uInt16 CalcSaveTargetMapMode(MapMode& rMapMode, const Size& rPrefSize);
+
+public:
+ WMFWriter();
+ bool WriteWMF(const GDIMetaFile & rMTF, SvStream & rTargetStream, FilterConfigItem const * pFilterConfigItem, bool bPlaceable);
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/DirectFontSubstitution.cxx b/vcl/source/font/DirectFontSubstitution.cxx
new file mode 100644
index 000000000..e494fb7ce
--- /dev/null
+++ b/vcl/source/font/DirectFontSubstitution.cxx
@@ -0,0 +1,71 @@
+/* -*- 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 <unotools/fontdefs.hxx>
+
+#include <font/DirectFontSubstitution.hxx>
+
+#include <svdata.hxx>
+
+#include <string>
+#include <vector>
+
+namespace vcl::font
+{
+void DirectFontSubstitution::AddFontSubstitute(const OUString& rFontName,
+ const OUString& rSubstFontName,
+ AddFontSubstituteFlags nFlags)
+{
+ maFontSubstList.emplace_back(rFontName, rSubstFontName, nFlags);
+}
+
+void DirectFontSubstitution::RemoveFontsSubstitute() { maFontSubstList.clear(); }
+
+bool DirectFontSubstitution::FindFontSubstitute(OUString& rSubstName,
+ std::u16string_view rSearchName) const
+{
+ // TODO: get rid of O(N) searches
+ std::vector<FontSubstEntry>::const_iterator it = std::find_if(
+ maFontSubstList.begin(), maFontSubstList.end(), [&](const FontSubstEntry& s) {
+ return (s.mnFlags & AddFontSubstituteFlags::ALWAYS) && (s.maSearchName == rSearchName);
+ });
+ if (it != maFontSubstList.end())
+ {
+ rSubstName = it->maSearchReplaceName;
+ return true;
+ }
+ return false;
+}
+
+void ImplFontSubstitute(OUString& rFontName)
+{
+ // must be canonicalised
+ assert(GetEnglishSearchFontName(rFontName) == rFontName);
+ OUString aSubstFontName;
+ // apply user-configurable font replacement (eg, from the list in Tools->Options)
+ const DirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst;
+ if (pSubst && pSubst->FindFontSubstitute(aSubstFontName, rFontName))
+ {
+ rFontName = aSubstFontName;
+ return;
+ }
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/font/Feature.cxx b/vcl/source/font/Feature.cxx
new file mode 100644
index 000000000..ba4b09a5d
--- /dev/null
+++ b/vcl/source/font/Feature.cxx
@@ -0,0 +1,162 @@
+/* -*- 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/.
+ *
+ */
+
+#include <utility>
+#include <vcl/font/Feature.hxx>
+#include <svdata.hxx>
+
+#include <hb.h>
+
+namespace vcl::font
+{
+OUString featureCodeAsString(uint32_t nFeature)
+{
+ std::vector<char> aString(5, 0);
+ aString[0] = char(nFeature >> 24 & 0xff);
+ aString[1] = char(nFeature >> 16 & 0xff);
+ aString[2] = char(nFeature >> 8 & 0xff);
+ aString[3] = char(nFeature >> 0 & 0xff);
+
+ return OStringToOUString(aString.data(), RTL_TEXTENCODING_ASCII_US);
+}
+
+// Feature
+Feature::Feature()
+ : m_aID({ 0, 0, 0 })
+ , m_eType(FeatureType::OpenType)
+{
+}
+
+Feature::Feature(FeatureID const& rID, FeatureType eType)
+ : m_aID(rID)
+ , m_eType(eType)
+{
+}
+
+// FeatureSetting
+FeatureSetting::FeatureSetting(OString feature)
+ : m_nTag(0)
+ , m_nValue(0)
+ , m_nStart(0)
+ , m_nEnd(0)
+{
+ hb_feature_t aFeat;
+ if (hb_feature_from_string(feature.getStr(), feature.getLength(), &aFeat))
+ {
+ m_nTag = aFeat.tag;
+ m_nValue = aFeat.value;
+ m_nStart = aFeat.start;
+ m_nEnd = aFeat.end;
+ }
+}
+
+// FeatureParameter
+
+FeatureParameter::FeatureParameter(uint32_t nCode, OUString aDescription)
+ : m_nCode(nCode)
+ , m_sDescription(std::move(aDescription))
+{
+}
+
+FeatureParameter::FeatureParameter(uint32_t nCode, TranslateId pDescriptionID)
+ : m_nCode(nCode)
+ , m_pDescriptionID(pDescriptionID)
+{
+}
+
+OUString FeatureParameter::getDescription() const
+{
+ OUString aReturnString;
+
+ if (m_pDescriptionID)
+ aReturnString = VclResId(m_pDescriptionID);
+ else if (!m_sDescription.isEmpty())
+ aReturnString = m_sDescription;
+
+ return aReturnString;
+}
+
+uint32_t FeatureParameter::getCode() const { return m_nCode; }
+
+// FeatureDefinition
+
+FeatureDefinition::FeatureDefinition()
+ : m_nCode(0)
+ , m_nDefault(0)
+ , m_eType(FeatureParameterType::BOOL)
+{
+}
+
+FeatureDefinition::FeatureDefinition(uint32_t nCode, OUString const& rDescription,
+ FeatureParameterType eType,
+ std::vector<FeatureParameter>&& rEnumParameters,
+ uint32_t nDefault)
+ : m_sDescription(rDescription)
+ , m_nCode(nCode)
+ , m_nDefault(nDefault)
+ , m_eType(eType)
+ , m_aEnumParameters(std::move(rEnumParameters))
+{
+}
+
+FeatureDefinition::FeatureDefinition(uint32_t nCode, TranslateId pDescriptionID,
+ OUString const& rNumericPart)
+ : m_pDescriptionID(pDescriptionID)
+ , m_sNumericPart(rNumericPart)
+ , m_nCode(nCode)
+ , m_nDefault(0)
+ , m_eType(FeatureParameterType::BOOL)
+{
+}
+
+FeatureDefinition::FeatureDefinition(uint32_t nCode, TranslateId pDescriptionID,
+ std::vector<FeatureParameter> aEnumParameters)
+ : m_pDescriptionID(pDescriptionID)
+ , m_nCode(nCode)
+ , m_nDefault(0)
+ , m_eType(FeatureParameterType::ENUM)
+ , m_aEnumParameters(std::move(aEnumParameters))
+{
+}
+
+const std::vector<FeatureParameter>& FeatureDefinition::getEnumParameters() const
+{
+ return m_aEnumParameters;
+}
+
+OUString FeatureDefinition::getDescription() const
+{
+ if (m_pDescriptionID)
+ {
+ OUString sTranslatedDescription = VclResId(m_pDescriptionID);
+ if (!m_sNumericPart.isEmpty())
+ return sTranslatedDescription.replaceFirst("%1", m_sNumericPart);
+ return sTranslatedDescription;
+ }
+ else if (!m_sDescription.isEmpty())
+ {
+ return m_sDescription;
+ }
+ else
+ {
+ return vcl::font::featureCodeAsString(m_nCode);
+ }
+}
+
+uint32_t FeatureDefinition::getCode() const { return m_nCode; }
+
+FeatureParameterType FeatureDefinition::getType() const { return m_eType; }
+
+FeatureDefinition::operator bool() const { return m_nCode != 0; }
+
+uint32_t FeatureDefinition::getDefault() const { return m_nDefault; }
+} // end vcl::font namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/FeatureCollector.cxx b/vcl/source/font/FeatureCollector.cxx
new file mode 100644
index 000000000..6cb1b14c5
--- /dev/null
+++ b/vcl/source/font/FeatureCollector.cxx
@@ -0,0 +1,158 @@
+/* -*- 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/.
+ */
+
+#include <font/FeatureCollector.hxx>
+#include <font/OpenTypeFeatureDefinitionList.hxx>
+
+#include <hb-ot.h>
+#include <hb-graphite2.h>
+
+namespace vcl::font
+{
+bool FeatureCollector::collectGraphite()
+{
+ gr_face* grFace = hb_graphite2_face_get_gr_face(m_pHbFace);
+
+ if (grFace == nullptr)
+ return false;
+
+ gr_uint16 nUILanguage = gr_uint16(m_eLanguageType);
+
+ gr_uint16 nNumberOfFeatures = gr_face_n_fref(grFace);
+ gr_feature_val* pfeatureValues
+ = gr_face_featureval_for_lang(grFace, 0); // shame we don't know which lang
+
+ for (gr_uint16 i = 0; i < nNumberOfFeatures; ++i)
+ {
+ const gr_feature_ref* pFeatureRef = gr_face_fref(grFace, i);
+ gr_uint32 nFeatureCode = gr_fref_id(pFeatureRef);
+
+ if (nFeatureCode == 0) // illegal feature code - skip
+ continue;
+
+ gr_uint16 nValue = gr_fref_feature_value(pFeatureRef, pfeatureValues);
+ gr_uint32 nLabelLength = 0;
+ void* pLabel = gr_fref_label(pFeatureRef, &nUILanguage, gr_utf8, &nLabelLength);
+ OUString sLabel(OUString::createFromAscii(static_cast<char*>(pLabel)));
+ gr_label_destroy(pLabel);
+
+ std::vector<vcl::font::FeatureParameter> aParameters;
+ gr_uint16 nNumberOfValues = gr_fref_n_values(pFeatureRef);
+
+ if (nNumberOfValues > 0)
+ {
+ for (gr_uint16 j = 0; j < nNumberOfValues; ++j)
+ {
+ gr_uint32 nValueLabelLength = 0;
+ void* pValueLabel = gr_fref_value_label(pFeatureRef, j, &nUILanguage, gr_utf8,
+ &nValueLabelLength);
+ OUString sValueLabel(OUString::createFromAscii(static_cast<char*>(pValueLabel)));
+ gr_uint16 nParamValue = gr_fref_value(pFeatureRef, j);
+ aParameters.emplace_back(sal_uInt32(nParamValue), sValueLabel);
+ gr_label_destroy(pValueLabel);
+ }
+
+ auto eFeatureParameterType = vcl::font::FeatureParameterType::ENUM;
+
+ // Check if the parameters are boolean
+ if (aParameters.size() == 2
+ && (aParameters[0].getDescription() == "True"
+ || aParameters[0].getDescription() == "False"))
+ {
+ eFeatureParameterType = vcl::font::FeatureParameterType::BOOL;
+ aParameters.clear();
+ }
+
+ m_rFontFeatures.emplace_back(
+ FeatureID{ nFeatureCode, HB_OT_TAG_DEFAULT_SCRIPT, HB_OT_TAG_DEFAULT_LANGUAGE },
+ vcl::font::FeatureType::Graphite);
+ vcl::font::Feature& rFeature = m_rFontFeatures.back();
+ rFeature.m_aDefinition
+ = vcl::font::FeatureDefinition(nFeatureCode, sLabel, eFeatureParameterType,
+ std::move(aParameters), sal_uInt32(nValue));
+ }
+ }
+ gr_featureval_destroy(pfeatureValues);
+ return true;
+}
+
+void FeatureCollector::collectForLanguage(hb_tag_t aTableTag, sal_uInt32 nScript,
+ hb_tag_t aScriptTag, sal_uInt32 nLanguage,
+ hb_tag_t aLanguageTag)
+{
+ unsigned int nFeatureCount = hb_ot_layout_language_get_feature_tags(
+ m_pHbFace, aTableTag, nScript, nLanguage, 0, nullptr, nullptr);
+ std::vector<hb_tag_t> aFeatureTags(nFeatureCount);
+ hb_ot_layout_language_get_feature_tags(m_pHbFace, aTableTag, nScript, nLanguage, 0,
+ &nFeatureCount, aFeatureTags.data());
+ aFeatureTags.resize(nFeatureCount);
+
+ for (hb_tag_t aFeatureTag : aFeatureTags)
+ {
+ if (OpenTypeFeatureDefinitionList().isRequired(aFeatureTag))
+ continue;
+
+ m_rFontFeatures.emplace_back();
+ vcl::font::Feature& rFeature = m_rFontFeatures.back();
+ rFeature.m_aID = { aFeatureTag, aScriptTag, aLanguageTag };
+
+ FeatureDefinition aDefinition = OpenTypeFeatureDefinitionList().getDefinition(aFeatureTag);
+ if (aDefinition)
+ {
+ rFeature.m_aDefinition = aDefinition;
+ }
+ }
+}
+
+void FeatureCollector::collectForScript(hb_tag_t aTableTag, sal_uInt32 nScript, hb_tag_t aScriptTag)
+{
+ collectForLanguage(aTableTag, nScript, aScriptTag, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
+ HB_OT_TAG_DEFAULT_LANGUAGE);
+
+ unsigned int nLanguageCount
+ = hb_ot_layout_script_get_language_tags(m_pHbFace, aTableTag, nScript, 0, nullptr, nullptr);
+ std::vector<hb_tag_t> aLanguageTags(nLanguageCount);
+ hb_ot_layout_script_get_language_tags(m_pHbFace, aTableTag, nScript, 0, &nLanguageCount,
+ aLanguageTags.data());
+ aLanguageTags.resize(nLanguageCount);
+ for (sal_uInt32 nLanguage = 0; nLanguage < sal_uInt32(nLanguageCount); ++nLanguage)
+ collectForLanguage(aTableTag, nScript, aScriptTag, nLanguage, aLanguageTags[nLanguage]);
+}
+
+void FeatureCollector::collectForTable(hb_tag_t aTableTag)
+{
+ unsigned int nScriptCount
+ = hb_ot_layout_table_get_script_tags(m_pHbFace, aTableTag, 0, nullptr, nullptr);
+ std::vector<hb_tag_t> aScriptTags(nScriptCount);
+ hb_ot_layout_table_get_script_tags(m_pHbFace, aTableTag, 0, &nScriptCount, aScriptTags.data());
+ aScriptTags.resize(nScriptCount);
+
+ for (sal_uInt32 nScript = 0; nScript < sal_uInt32(nScriptCount); ++nScript)
+ collectForScript(aTableTag, nScript, aScriptTags[nScript]);
+}
+
+bool FeatureCollector::collect()
+{
+ gr_face* grFace = hb_graphite2_face_get_gr_face(m_pHbFace);
+
+ if (grFace)
+ {
+ return collectGraphite();
+ }
+ else
+ {
+ collectForTable(HB_OT_TAG_GSUB); // substitution
+ collectForTable(HB_OT_TAG_GPOS); // positioning
+ return true;
+ }
+}
+
+} // end namespace vcl::font
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/FeatureParser.cxx b/vcl/source/font/FeatureParser.cxx
new file mode 100644
index 000000000..5182cea7a
--- /dev/null
+++ b/vcl/source/font/FeatureParser.cxx
@@ -0,0 +1,70 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/font/FeatureParser.hxx>
+#include <vcl/font/Feature.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace vcl::font
+{
+OUString trimFontNameFeatures(OUString const& rFontName)
+{
+ const sal_Int32 nPrefixIdx{ rFontName.indexOf(vcl::font::FeaturePrefix) };
+
+ if (nPrefixIdx < 0)
+ return rFontName;
+
+ return rFontName.copy(0, nPrefixIdx);
+}
+
+FeatureParser::FeatureParser(std::u16string_view rFontName)
+{
+ size_t nPrefixIdx{ rFontName.find(vcl::font::FeaturePrefix) };
+
+ if (nPrefixIdx == std::u16string_view::npos)
+ return;
+
+ std::u16string_view sName(rFontName.substr(++nPrefixIdx));
+ sal_Int32 nIndex = 0;
+ do
+ {
+ std::u16string_view sToken = o3tl::getToken(sName, 0, vcl::font::FeatureSeparator, nIndex);
+
+ sal_Int32 nInnerIdx{ 0 };
+ std::u16string_view sID = o3tl::getToken(sToken, 0, '=', nInnerIdx);
+
+ if (sID == u"lang")
+ {
+ m_sLanguage = o3tl::getToken(sToken, 0, '=', nInnerIdx);
+ }
+ else
+ {
+ OString sFeature = OUStringToOString(sToken, RTL_TEXTENCODING_ASCII_US);
+ FeatureSetting aFeature(sFeature);
+ if (aFeature.m_nTag != 0)
+ m_aFeatures.push_back(aFeature);
+ }
+ } while (nIndex >= 0);
+}
+
+std::unordered_map<uint32_t, uint32_t> FeatureParser::getFeaturesMap() const
+{
+ std::unordered_map<uint32_t, uint32_t> aResultMap;
+ for (auto const& rFeat : m_aFeatures)
+ {
+ if (rFeat.m_nValue != 0)
+ aResultMap.emplace(rFeat.m_nTag, rFeat.m_nValue);
+ }
+ return aResultMap;
+}
+
+} // end vcl::font namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/FontSelectPattern.cxx b/vcl/source/font/FontSelectPattern.cxx
new file mode 100644
index 000000000..23e7673c8
--- /dev/null
+++ b/vcl/source/font/FontSelectPattern.cxx
@@ -0,0 +1,155 @@
+/* -*- 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 <o3tl/safeint.hxx>
+#include <tools/gen.hxx>
+
+#include <vcl/font.hxx>
+
+#include <font/FontSelectPattern.hxx>
+#include <font/PhysicalFontFace.hxx>
+
+namespace vcl::font
+{
+
+// These mustn't conflict with font name lists which use ; and ,
+const char FontSelectPattern::FEAT_PREFIX = ':';
+const char FontSelectPattern::FEAT_SEPARATOR = '&';
+
+FontSelectPattern::FontSelectPattern( const vcl::Font& rFont,
+ const OUString& rSearchName, const Size& rSize, float fExactHeight, bool bNonAntialias)
+ : maSearchName( rSearchName )
+ , mnWidth( rSize.Width() )
+ , mnHeight( rSize.Height() )
+ , mfExactHeight( fExactHeight)
+ , mnOrientation( rFont.GetOrientation() )
+ , meLanguage( rFont.GetLanguage() )
+ , mbVertical( rFont.IsVertical() )
+ , mbNonAntialiased(bNonAntialias)
+ , mbEmbolden( false )
+{
+ maTargetName = GetFamilyName();
+
+ rFont.GetFontAttributes( *this );
+
+ // normalize orientation between 0 and 3600
+ if( mnOrientation < 0_deg10 || mnOrientation >= 3600_deg10 )
+ {
+ if( mnOrientation >= 0_deg10 )
+ mnOrientation %= 3600_deg10;
+ else
+ mnOrientation = 3600_deg10 - (-mnOrientation % 3600_deg10);
+ }
+
+ // normalize width and height
+ if( mnHeight < 0 )
+ mnHeight = o3tl::saturating_toggle_sign(mnHeight);
+ if( mnWidth < 0 )
+ mnWidth = o3tl::saturating_toggle_sign(mnWidth);
+}
+
+
+// NOTE: this ctor is still used on Windows. Do not remove.
+#ifdef _WIN32
+FontSelectPattern::FontSelectPattern( const PhysicalFontFace& rFontData,
+ const Size& rSize, float fExactHeight, int nOrientation, bool bVertical )
+ : FontAttributes( rFontData )
+ , mnWidth( rSize.Width() )
+ , mnHeight( rSize.Height() )
+ , mfExactHeight( fExactHeight )
+ , mnOrientation( nOrientation )
+ , meLanguage( 0 )
+ , mbVertical( bVertical )
+ , mbNonAntialiased( false )
+ , mbEmbolden( false )
+{
+ maTargetName = maSearchName = GetFamilyName();
+ // NOTE: no normalization for width/height/orientation
+}
+
+#endif
+
+size_t FontSelectPattern::hashCode() const
+{
+ // TODO: does it pay off to improve this hash function?
+ size_t nHash;
+ // check for features and generate a unique hash if necessary
+ if (maTargetName.indexOf(FontSelectPattern::FEAT_PREFIX)
+ != -1)
+ {
+ nHash = maTargetName.hashCode();
+ }
+ else
+ {
+ nHash = maSearchName.hashCode();
+ }
+ nHash += 11U * mnHeight;
+ nHash += 19 * GetWeight();
+ nHash += 29 * GetItalic();
+ nHash += 37 * mnOrientation.get();
+ nHash += 41 * static_cast<sal_uInt16>(meLanguage);
+ if( mbVertical )
+ nHash += 53;
+ return nHash;
+}
+
+bool FontSelectPattern::operator==(const FontSelectPattern& rOther) const
+{
+ if (!CompareDeviceIndependentFontAttributes(rOther))
+ return false;
+
+ if (maTargetName != rOther.maTargetName)
+ return false;
+
+ if (maSearchName != rOther.maSearchName)
+ return false;
+
+ if (mnWidth != rOther.mnWidth)
+ return false;
+
+ if (mnHeight != rOther.mnHeight)
+ return false;
+
+ if (mfExactHeight != rOther.mfExactHeight)
+ return false;
+
+ if (mnOrientation != rOther.mnOrientation)
+ return false;
+
+ if (meLanguage != rOther.meLanguage)
+ return false;
+
+ if (mbVertical != rOther.mbVertical)
+ return false;
+
+ if (mbNonAntialiased != rOther.mbNonAntialiased)
+ return false;
+
+ if (mbEmbolden != rOther.mbEmbolden)
+ return false;
+
+ if (maItalicMatrix != rOther.maItalicMatrix)
+ return false;
+
+ return true;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/OpenTypeFeatureDefinitionList.cxx b/vcl/source/font/OpenTypeFeatureDefinitionList.cxx
new file mode 100644
index 000000000..f49837cce
--- /dev/null
+++ b/vcl/source/font/OpenTypeFeatureDefinitionList.cxx
@@ -0,0 +1,200 @@
+/* -*- 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/.
+ *
+ */
+
+#include <font/OpenTypeFeatureDefinitionList.hxx>
+#include <font/OpenTypeFeatureStrings.hrc>
+
+#include <rtl/character.hxx>
+
+#include <algorithm>
+
+namespace vcl::font
+{
+OpenTypeFeatureDefinitionListPrivate& OpenTypeFeatureDefinitionList()
+{
+ static OpenTypeFeatureDefinitionListPrivate SINGLETON;
+ return SINGLETON;
+};
+
+OpenTypeFeatureDefinitionListPrivate::OpenTypeFeatureDefinitionListPrivate() { init(); }
+
+void OpenTypeFeatureDefinitionListPrivate::init()
+{
+ m_aFeatureDefinition.assign({
+ { featureCode("aalt"), STR_FONT_FEATURE_ID_AALT },
+ { featureCode("afrc"), STR_FONT_FEATURE_ID_AFRC },
+ { featureCode("alig"), STR_FONT_FEATURE_ID_ALIG },
+ { featureCode("c2pc"), STR_FONT_FEATURE_ID_C2PC },
+ { featureCode("c2sc"), STR_FONT_FEATURE_ID_C2SC },
+ { featureCode("calt"), STR_FONT_FEATURE_ID_CALT },
+ { featureCode("case"), STR_FONT_FEATURE_ID_CASE },
+ { featureCode("clig"), STR_FONT_FEATURE_ID_CLIG },
+ { featureCode("cpct"), STR_FONT_FEATURE_ID_CPCT },
+ { featureCode("cpsp"), STR_FONT_FEATURE_ID_CPSP },
+ { featureCode("cswh"), STR_FONT_FEATURE_ID_CSWH },
+ { featureCode("dcap"), STR_FONT_FEATURE_ID_DCAP },
+ { featureCode("dlig"), STR_FONT_FEATURE_ID_DLIG },
+ { featureCode("dnom"), STR_FONT_FEATURE_ID_DNOM },
+ { featureCode("dpng"), STR_FONT_FEATURE_ID_DPNG },
+ { featureCode("expt"), STR_FONT_FEATURE_ID_EXPT },
+ { featureCode("falt"), STR_FONT_FEATURE_ID_FALT },
+ { featureCode("frac"), STR_FONT_FEATURE_ID_FRAC },
+ { featureCode("fwid"), STR_FONT_FEATURE_ID_FWID },
+ { featureCode("halt"), STR_FONT_FEATURE_ID_HALT },
+ { featureCode("hist"), STR_FONT_FEATURE_ID_HIST },
+ { featureCode("hkna"), STR_FONT_FEATURE_ID_HKNA },
+ { featureCode("hlig"), STR_FONT_FEATURE_ID_HLIG },
+ { featureCode("hngl"), STR_FONT_FEATURE_ID_HNGL },
+ { featureCode("hojo"), STR_FONT_FEATURE_ID_HOJO },
+ { featureCode("hwid"), STR_FONT_FEATURE_ID_HWID },
+ { featureCode("ital"), STR_FONT_FEATURE_ID_ITAL },
+ { featureCode("jalt"), STR_FONT_FEATURE_ID_JALT },
+ { featureCode("jp78"), STR_FONT_FEATURE_ID_JP78 },
+ { featureCode("jp83"), STR_FONT_FEATURE_ID_JP83 },
+ { featureCode("jp90"), STR_FONT_FEATURE_ID_JP90 },
+ { featureCode("jp04"), STR_FONT_FEATURE_ID_JP04 },
+ { featureCode("kern"), STR_FONT_FEATURE_ID_KERN },
+ { featureCode("lfbd"), STR_FONT_FEATURE_ID_LFBD },
+ { featureCode("liga"), STR_FONT_FEATURE_ID_LIGA },
+ { featureCode("lnum"), STR_FONT_FEATURE_ID_LNUM },
+ { featureCode("mgrk"), STR_FONT_FEATURE_ID_MGRK },
+ { featureCode("nalt"), STR_FONT_FEATURE_ID_NALT },
+ { featureCode("nlck"), STR_FONT_FEATURE_ID_NLCK },
+ { featureCode("numr"), STR_FONT_FEATURE_ID_NUMR },
+ { featureCode("onum"), STR_FONT_FEATURE_ID_ONUM },
+ { featureCode("opbd"), STR_FONT_FEATURE_ID_OPBD },
+ { featureCode("ordn"), STR_FONT_FEATURE_ID_ORDN },
+ { featureCode("ornm"), STR_FONT_FEATURE_ID_ORNM },
+ { featureCode("palt"), STR_FONT_FEATURE_ID_PALT },
+ { featureCode("pcap"), STR_FONT_FEATURE_ID_PCAP },
+ { featureCode("pkna"), STR_FONT_FEATURE_ID_PKNA },
+ { featureCode("pnum"), STR_FONT_FEATURE_ID_PNUM },
+ { featureCode("pwid"), STR_FONT_FEATURE_ID_PWID },
+ { featureCode("qwid"), STR_FONT_FEATURE_ID_QWID },
+ { featureCode("rtbd"), STR_FONT_FEATURE_ID_RTBD },
+ { featureCode("ruby"), STR_FONT_FEATURE_ID_RUBY },
+ { featureCode("salt"), STR_FONT_FEATURE_ID_SALT },
+ { featureCode("sinf"), STR_FONT_FEATURE_ID_SINF },
+ { featureCode("smcp"), STR_FONT_FEATURE_ID_SMCP },
+ { featureCode("smpl"), STR_FONT_FEATURE_ID_SMPL },
+ { featureCode("subs"), STR_FONT_FEATURE_ID_SUBS },
+ { featureCode("sups"), STR_FONT_FEATURE_ID_SUPS },
+ { featureCode("swsh"), STR_FONT_FEATURE_ID_SWSH },
+ { featureCode("titl"), STR_FONT_FEATURE_ID_TITL },
+ { featureCode("tnam"), STR_FONT_FEATURE_ID_TNAM },
+ { featureCode("tnum"), STR_FONT_FEATURE_ID_TNUM },
+ { featureCode("trad"), STR_FONT_FEATURE_ID_TRAD },
+ { featureCode("twid"), STR_FONT_FEATURE_ID_TWID },
+ { featureCode("unic"), STR_FONT_FEATURE_ID_UNIC },
+ { featureCode("valt"), STR_FONT_FEATURE_ID_VALT },
+ { featureCode("vhal"), STR_FONT_FEATURE_ID_VHAL },
+ { featureCode("vkna"), STR_FONT_FEATURE_ID_VKNA },
+ { featureCode("vkrn"), STR_FONT_FEATURE_ID_VKRN },
+ { featureCode("vpal"), STR_FONT_FEATURE_ID_VPAL },
+ { featureCode("vrt2"), STR_FONT_FEATURE_ID_VRT2 },
+ { featureCode("vrtr"), STR_FONT_FEATURE_ID_VRTR },
+ { featureCode("zero"), STR_FONT_FEATURE_ID_ZERO },
+ });
+
+ for (size_t i = 0; i < m_aFeatureDefinition.size(); ++i)
+ {
+ m_aCodeToIndex.emplace(m_aFeatureDefinition[i].getCode(), i);
+ }
+
+ m_aRequiredFeatures.assign({
+ featureCode("abvf"), featureCode("abvm"), featureCode("abvs"), featureCode("akhn"),
+ featureCode("blwf"), featureCode("blwm"), featureCode("blws"), featureCode("ccmp"),
+ featureCode("cfar"), featureCode("cjct"), featureCode("curs"), featureCode("dist"),
+ featureCode("dtls"), featureCode("fin2"), featureCode("fin3"), featureCode("fina"),
+ featureCode("flac"), featureCode("half"), featureCode("haln"), featureCode("init"),
+ featureCode("isol"), featureCode("ljmo"), featureCode("locl"), featureCode("ltra"),
+ featureCode("ltrm"), featureCode("mark"), featureCode("med2"), featureCode("medi"),
+ featureCode("mkmk"), featureCode("mset"), featureCode("nukt"), featureCode("pref"),
+ featureCode("pres"), featureCode("pstf"), featureCode("psts"), featureCode("rand"),
+ featureCode("rclt"), featureCode("rkrf"), featureCode("rlig"), featureCode("rphf"),
+ featureCode("rtla"), featureCode("rtlm"), featureCode("rvrn"), featureCode("size"),
+ featureCode("ssty"), featureCode("stch"), featureCode("tjmo"), featureCode("vatu"),
+ featureCode("vert"), featureCode("vjmo"),
+ });
+}
+
+namespace
+{
+bool isCharacterVariantCode(sal_uInt32 nFeatureCode)
+{
+ return ((sal_uInt32(nFeatureCode) >> 24) & 0xFF) == 'c'
+ && ((sal_uInt32(nFeatureCode) >> 16) & 0xFF) == 'v';
+}
+
+bool isStylisticSetCode(sal_uInt32 nFeatureCode)
+{
+ return ((sal_uInt32(nFeatureCode) >> 24) & 0xFF) == 's'
+ && ((sal_uInt32(nFeatureCode) >> 16) & 0xFF) == 's';
+}
+
+OUString getNumericLowerPart(sal_uInt32 nFeatureCode)
+{
+ char cChar1((sal_uInt32(nFeatureCode) >> 8) & 0xFF);
+ char cChar2((sal_uInt32(nFeatureCode) >> 0) & 0xFF);
+
+ if (rtl::isAsciiDigit(static_cast<unsigned char>(cChar1))
+ && rtl::isAsciiDigit(static_cast<unsigned char>(cChar2)))
+ {
+ return OUStringChar(cChar1) + OUStringChar(cChar2);
+ }
+ return OUString();
+}
+
+} // end anonymous namespace
+
+bool OpenTypeFeatureDefinitionListPrivate::isSpecialFeatureCode(sal_uInt32 nFeatureCode)
+{
+ return isCharacterVariantCode(nFeatureCode) || isStylisticSetCode(nFeatureCode);
+}
+
+FeatureDefinition
+OpenTypeFeatureDefinitionListPrivate::handleSpecialFeatureCode(sal_uInt32 nFeatureCode)
+{
+ FeatureDefinition aFeatureDefinition;
+ OUString sNumericPart = getNumericLowerPart(nFeatureCode);
+ if (!sNumericPart.isEmpty())
+ {
+ if (isCharacterVariantCode(nFeatureCode))
+ aFeatureDefinition = { nFeatureCode, STR_FONT_FEATURE_ID_CVXX, sNumericPart };
+ else if (isStylisticSetCode(nFeatureCode))
+ aFeatureDefinition = { nFeatureCode, STR_FONT_FEATURE_ID_SSXX, sNumericPart };
+ }
+ return aFeatureDefinition;
+}
+
+FeatureDefinition OpenTypeFeatureDefinitionListPrivate::getDefinition(sal_uInt32 nFeatureCode)
+{
+ if (isSpecialFeatureCode(nFeatureCode))
+ {
+ return handleSpecialFeatureCode(nFeatureCode);
+ }
+
+ if (m_aCodeToIndex.find(nFeatureCode) != m_aCodeToIndex.end())
+ {
+ size_t nIndex = m_aCodeToIndex.at(nFeatureCode);
+ return m_aFeatureDefinition[nIndex];
+ }
+ return FeatureDefinition();
+}
+
+bool OpenTypeFeatureDefinitionListPrivate::isRequired(sal_uInt32 nFeatureCode)
+{
+ return std::find(m_aRequiredFeatures.begin(), m_aRequiredFeatures.end(), nFeatureCode)
+ != m_aRequiredFeatures.end();
+}
+
+} // end vcl::font namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/PhysicalFontCollection.cxx b/vcl/source/font/PhysicalFontCollection.cxx
new file mode 100644
index 000000000..3abf7db3d
--- /dev/null
+++ b/vcl/source/font/PhysicalFontCollection.cxx
@@ -0,0 +1,1279 @@
+/* -*- 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 <memory>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/fontdefs.hxx>
+#include <o3tl/sorted_vector.hxx>
+
+#include <font/PhysicalFontFaceCollection.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <font/fontsubstitution.hxx>
+
+static ImplFontAttrs lcl_IsCJKFont( const OUString& rFontName )
+{
+ // Test, if Fontname includes CJK characters --> In this case we
+ // mention that it is a CJK font
+ for(int i = 0; i < rFontName.getLength(); i++)
+ {
+ const sal_Unicode ch = rFontName[i];
+ // japanese
+ if ( ((ch >= 0x3040) && (ch <= 0x30FF)) ||
+ ((ch >= 0x3190) && (ch <= 0x319F)) )
+ return ImplFontAttrs::CJK|ImplFontAttrs::CJK_JP;
+
+ // korean
+ if ( ((ch >= 0xAC00) && (ch <= 0xD7AF)) ||
+ ((ch >= 0xA960) && (ch <= 0xA97F)) ||
+ ((ch >= 0xD7B0) && (ch <= 0xD7FF)) ||
+ ((ch >= 0x3130) && (ch <= 0x318F)) ||
+ ((ch >= 0x1100) && (ch <= 0x11FF)) )
+ return ImplFontAttrs::CJK|ImplFontAttrs::CJK_KR;
+
+ // chinese
+ if ( (ch >= 0x3400) && (ch <= 0x9FFF) )
+ return ImplFontAttrs::CJK|ImplFontAttrs::CJK_TC|ImplFontAttrs::CJK_SC;
+
+ // cjk
+ if ( ((ch >= 0x3000) && (ch <= 0xD7AF)) ||
+ ((ch >= 0xFF00) && (ch <= 0xFFEE)) )
+ return ImplFontAttrs::CJK;
+
+ }
+
+ return ImplFontAttrs::None;
+}
+
+namespace vcl::font
+{
+
+PhysicalFontCollection::PhysicalFontCollection()
+ : mbMatchData( false )
+ , mpPreMatchHook( nullptr )
+ , mpFallbackHook( nullptr )
+ , mnFallbackCount( -1 )
+{}
+
+PhysicalFontCollection::~PhysicalFontCollection()
+{
+ Clear();
+}
+
+void PhysicalFontCollection::SetPreMatchHook(PreMatchFontSubstitution* pHook)
+{
+ mpPreMatchHook = pHook;
+}
+
+void PhysicalFontCollection::SetFallbackHook(GlyphFallbackFontSubstitution* pHook)
+{
+ mpFallbackHook = pHook;
+}
+
+void PhysicalFontCollection::Clear()
+{
+ // remove fallback lists
+ mpFallbackList.reset();
+ mnFallbackCount = -1;
+
+ // clear all entries in the device font list
+ maPhysicalFontFamilies.clear();
+
+ // match data must be recalculated too
+ mbMatchData = false;
+}
+
+void PhysicalFontCollection::ImplInitGenericGlyphFallback() const
+{
+ // normalized family names of fonts suited for glyph fallback
+ // if a font is available related fonts can be ignored
+ // TODO: implement dynamic lists
+ static const char* aGlyphFallbackList[] = {
+ // empty strings separate the names of unrelated fonts
+ "eudc", "",
+ "arialunicodems", "cyberbit", "code2000", "",
+ "andalesansui", "",
+ "starsymbol", "opensymbol", "",
+ "msmincho", "fzmingti", "fzheiti", "ipamincho", "sazanamimincho", "kochimincho", "",
+ "sunbatang", "sundotum", "baekmukdotum", "gulim", "batang", "dotum", "",
+ "hgmincholightj", "msunglightsc", "msunglighttc", "hymyeongjolightk", "",
+ "tahoma", "dejavusans", "timesnewroman", "liberationsans", "",
+ "shree", "mangal", "",
+ "raavi", "shruti", "tunga", "",
+ "latha", "gautami", "kartika", "vrinda", "",
+ "shayyalmt", "naskmt", "scheherazade", "",
+ "david", "nachlieli", "lucidagrande", "",
+ "norasi", "angsanaupc", "",
+ "khmerossystem", "",
+ "muktinarrow", "",
+ "phetsarathot", "",
+ "padauk", "pinlonmyanmar", "",
+ "iskoolapota", "lklug", "",
+ nullptr
+ };
+
+ bool bHasEudc = false;
+ int nMaxLevel = 0;
+ int nBestQuality = 0;
+ std::unique_ptr<std::array<PhysicalFontFamily*,MAX_GLYPHFALLBACK>> pFallbackList;
+
+ for( const char** ppNames = &aGlyphFallbackList[0];; ++ppNames )
+ {
+ // advance to next sub-list when end-of-sublist marker
+ if( !**ppNames ) // #i46456# check for empty string, i.e., deref string itself not only ptr to it
+ {
+ if( nBestQuality > 0 )
+ if( ++nMaxLevel >= MAX_GLYPHFALLBACK )
+ break;
+
+ if( !ppNames[1] )
+ break;
+
+ nBestQuality = 0;
+ continue;
+ }
+
+ // test if the glyph fallback candidate font is available and scalable
+ OUString aTokenName( *ppNames, strlen(*ppNames), RTL_TEXTENCODING_UTF8 );
+ PhysicalFontFamily* pFallbackFont = FindFontFamily( aTokenName );
+
+ if( !pFallbackFont )
+ continue;
+
+ // keep the best font of the glyph fallback sub-list
+ if( nBestQuality < pFallbackFont->GetMinQuality() )
+ {
+ nBestQuality = pFallbackFont->GetMinQuality();
+ // store available glyph fallback fonts
+ if( !pFallbackList )
+ pFallbackList.reset(new std::array<PhysicalFontFamily*,MAX_GLYPHFALLBACK>);
+
+ (*pFallbackList)[ nMaxLevel ] = pFallbackFont;
+ if( !bHasEudc && !nMaxLevel )
+ bHasEudc = !strncmp( *ppNames, "eudc", 5 );
+ }
+ }
+
+ mnFallbackCount = nMaxLevel;
+ mpFallbackList = std::move(pFallbackList);
+}
+
+PhysicalFontFamily* PhysicalFontCollection::GetGlyphFallbackFont(FontSelectPattern& rFontSelData,
+ LogicalFontInstance* pFontInstance,
+ OUString& rMissingCodes,
+ int nFallbackLevel) const
+{
+ PhysicalFontFamily* pFallbackData = nullptr;
+
+ // find a matching font candidate for platform specific glyph fallback
+ if( mpFallbackHook )
+ {
+ // check cache for the first matching entry
+ // to avoid calling the expensive fallback hook (#i83491#)
+ sal_UCS4 cChar = 0;
+ bool bCached = true;
+ sal_Int32 nStrIndex = 0;
+ while( nStrIndex < rMissingCodes.getLength() )
+ {
+ cChar = rMissingCodes.iterateCodePoints( &nStrIndex );
+ bCached = pFontInstance->GetFallbackForUnicode(cChar, rFontSelData.GetWeight(),
+ &rFontSelData.maSearchName,
+ &rFontSelData.mbEmbolden,
+ &rFontSelData.maItalicMatrix);
+
+ // ignore entries which don't have a fallback
+ if( !bCached || !rFontSelData.maSearchName.isEmpty() )
+ break;
+ }
+
+ if( bCached )
+ {
+ // there is a matching fallback in the cache
+ // so update rMissingCodes with codepoints not yet resolved by this fallback
+ int nRemainingLength = 0;
+ std::unique_ptr<sal_UCS4[]> const pRemainingCodes(new sal_UCS4[rMissingCodes.getLength()]);
+ OUString aFontName;
+ bool bEmbolden;
+ ItalicMatrix aMatrix;
+
+ while( nStrIndex < rMissingCodes.getLength() )
+ {
+ cChar = rMissingCodes.iterateCodePoints( &nStrIndex );
+ bCached = pFontInstance->GetFallbackForUnicode(cChar, rFontSelData.GetWeight(),
+ &aFontName, &bEmbolden, &aMatrix);
+ if (!bCached || rFontSelData.maSearchName != aFontName ||
+ rFontSelData.mbEmbolden != bEmbolden ||
+ rFontSelData.maItalicMatrix != aMatrix)
+ {
+ pRemainingCodes[ nRemainingLength++ ] = cChar;
+ }
+ }
+ rMissingCodes = OUString( pRemainingCodes.get(), nRemainingLength );
+ }
+ else
+ {
+ OUString aOldMissingCodes = rMissingCodes;
+
+ // call the hook to query the best matching glyph fallback font
+ if (mpFallbackHook->FindFontSubstitute(rFontSelData, pFontInstance, rMissingCodes))
+ // apply outdev3.cxx specific fontname normalization
+ rFontSelData.maSearchName = GetEnglishSearchFontName( rFontSelData.maSearchName );
+ else
+ rFontSelData.maSearchName.clear();
+
+ // Cache the result even if there was no match
+ // See tdf#32665 and tdf#147283 for an example where FreeSerif that has glyphs that exist
+ // in the bold font, but not in the bold+italic version where fontconfig suggest the bold
+ // font + applying a matrix to fake the missing italic.
+ for(;;)
+ {
+ if (!pFontInstance->GetFallbackForUnicode(cChar, rFontSelData.GetWeight(),
+ &rFontSelData.maSearchName,
+ &rFontSelData.mbEmbolden,
+ &rFontSelData.maItalicMatrix))
+ {
+ pFontInstance->AddFallbackForUnicode(cChar, rFontSelData.GetWeight(),
+ rFontSelData.maSearchName,
+ rFontSelData.mbEmbolden,
+ rFontSelData.maItalicMatrix);
+ }
+ if( nStrIndex >= aOldMissingCodes.getLength() )
+ break;
+ cChar = aOldMissingCodes.iterateCodePoints( &nStrIndex );
+ }
+ if( !rFontSelData.maSearchName.isEmpty() )
+ {
+ // remove cache entries that were still not resolved
+ for( nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); )
+ {
+ cChar = rMissingCodes.iterateCodePoints( &nStrIndex );
+ pFontInstance->IgnoreFallbackForUnicode( cChar, rFontSelData.GetWeight(), rFontSelData.maSearchName );
+ }
+ }
+ }
+
+ // find the matching device font
+ if( !rFontSelData.maSearchName.isEmpty() )
+ pFallbackData = FindFontFamily( rFontSelData.maSearchName );
+ }
+
+ // else find a matching font candidate for generic glyph fallback
+ if( !pFallbackData )
+ {
+ // initialize font candidates for generic glyph fallback if needed
+ if( mnFallbackCount < 0 )
+ ImplInitGenericGlyphFallback();
+
+ // TODO: adjust nFallbackLevel by number of levels resolved by the fallback hook
+ if( nFallbackLevel < mnFallbackCount )
+ pFallbackData = (*mpFallbackList)[ nFallbackLevel ];
+ }
+
+ return pFallbackData;
+}
+
+void PhysicalFontCollection::Add(PhysicalFontFace* pNewData)
+{
+ OUString aSearchName = GetEnglishSearchFontName( pNewData->GetFamilyName() );
+
+ PhysicalFontFamily* pFoundData = FindOrCreateFontFamily(aSearchName);
+
+ pFoundData->AddFontFace( pNewData );
+}
+
+// find the font from the normalized font family name
+PhysicalFontFamily* PhysicalFontCollection::ImplFindFontFamilyBySearchName(const OUString& rSearchName) const
+{
+ // must be called with a normalized name.
+ assert( GetEnglishSearchFontName( rSearchName ) == rSearchName );
+
+ PhysicalFontFamilies::const_iterator it = maPhysicalFontFamilies.find( rSearchName );
+ if( it == maPhysicalFontFamilies.end() )
+ return nullptr;
+
+ PhysicalFontFamily* pFoundData = (*it).second.get();
+ return pFoundData;
+}
+
+PhysicalFontFamily* PhysicalFontCollection::FindFontFamily(std::u16string_view rFontName) const
+{
+ return ImplFindFontFamilyBySearchName( GetEnglishSearchFontName( rFontName ) );
+}
+
+PhysicalFontFamily *PhysicalFontCollection::FindOrCreateFontFamily(OUString const& rFamilyName)
+{
+ PhysicalFontFamilies::const_iterator it = maPhysicalFontFamilies.find( rFamilyName );
+ PhysicalFontFamily* pFoundData = nullptr;
+
+ if( it != maPhysicalFontFamilies.end() )
+ pFoundData = (*it).second.get();
+
+ if( !pFoundData )
+ {
+ pFoundData = new PhysicalFontFamily(rFamilyName);
+ maPhysicalFontFamilies[ rFamilyName ].reset(pFoundData);
+ }
+
+ return pFoundData;
+}
+
+PhysicalFontFamily* PhysicalFontCollection::FindFontFamilyByTokenNames(std::u16string_view rTokenStr) const
+{
+ PhysicalFontFamily* pFoundData = nullptr;
+
+ // use normalized font name tokens to find the font
+ for( sal_Int32 nTokenPos = 0; nTokenPos != -1; )
+ {
+ std::u16string_view aFamilyName = GetNextFontToken( rTokenStr, nTokenPos );
+ if( aFamilyName.empty() )
+ continue;
+
+ pFoundData = FindFontFamily( aFamilyName );
+
+ if( pFoundData )
+ break;
+ }
+
+ return pFoundData;
+}
+
+PhysicalFontFamily* PhysicalFontCollection::ImplFindFontFamilyBySubstFontAttr(utl::FontNameAttr const& rFontAttr) const
+{
+ PhysicalFontFamily* pFoundData = nullptr;
+
+ // use the font substitutions suggested by the FontNameAttr to find the font
+ for (auto const& substitution : rFontAttr.Substitutions)
+ {
+ pFoundData = FindFontFamily(substitution);
+ if( pFoundData )
+ return pFoundData;
+ }
+
+ // use known attributes from the configuration to find a matching substitute
+ const ImplFontAttrs nSearchType = rFontAttr.Type;
+ if( nSearchType != ImplFontAttrs::None )
+ {
+ const FontWeight eSearchWeight = rFontAttr.Weight;
+ const FontWidth eSearchWidth = rFontAttr.Width;
+ const FontItalic eSearchSlant = ITALIC_DONTKNOW;
+
+ pFoundData = FindFontFamilyByAttributes( nSearchType,
+ eSearchWeight, eSearchWidth, eSearchSlant, "" );
+
+ if( pFoundData )
+ return pFoundData;
+ }
+
+ return nullptr;
+}
+
+void PhysicalFontCollection::ImplInitMatchData() const
+{
+ // short circuit if already done
+ if( mbMatchData )
+ return;
+ mbMatchData = true;
+
+ if (utl::ConfigManager::IsFuzzing())
+ return;
+
+ // calculate MatchData for all entries
+ const utl::FontSubstConfiguration& rFontSubst = utl::FontSubstConfiguration::get();
+
+ for (auto const& family : maPhysicalFontFamilies)
+ {
+ const OUString& rSearchName = family.first;
+ PhysicalFontFamily* pEntry = family.second.get();
+
+ pEntry->InitMatchData( rFontSubst, rSearchName );
+ }
+}
+
+PhysicalFontFamily* PhysicalFontCollection::FindFontFamilyByAttributes(ImplFontAttrs nSearchType,
+ FontWeight eSearchWeight,
+ FontWidth eSearchWidth,
+ FontItalic eSearchItalic,
+ OUString const& rSearchFamilyName ) const
+{
+ if( (eSearchItalic != ITALIC_NONE) && (eSearchItalic != ITALIC_DONTKNOW) )
+ nSearchType |= ImplFontAttrs::Italic;
+
+ // don't bother to match attributes if the attributes aren't worth matching
+ if( nSearchType == ImplFontAttrs::None
+ && ((eSearchWeight == WEIGHT_DONTKNOW) || (eSearchWeight == WEIGHT_NORMAL))
+ && ((eSearchWidth == WIDTH_DONTKNOW) || (eSearchWidth == WIDTH_NORMAL)) )
+ return nullptr;
+
+ ImplInitMatchData();
+ PhysicalFontFamily* pFoundData = nullptr;
+
+ tools::Long nBestMatch = 40000;
+ ImplFontAttrs nBestType = ImplFontAttrs::None;
+
+ for (auto const& family : maPhysicalFontFamilies)
+ {
+ PhysicalFontFamily* pData = family.second.get();
+
+ // Get all information about the matching font
+ ImplFontAttrs nMatchType = pData->GetMatchType();
+ FontWeight eMatchWeight= pData->GetMatchWeight();
+ FontWidth eMatchWidth = pData->GetMatchWidth();
+
+ // Calculate Match Value
+ // 1000000000
+ // 100000000
+ // 10000000 CJK, CTL, None-Latin, Symbol
+ // 1000000 FamilyName, Script, Fixed, -Special, -Decorative,
+ // Titling, Capitals, Outline, Shadow
+ // 100000 Match FamilyName, Serif, SansSerif, Italic,
+ // Width, Weight
+ // 10000 Scalable, Standard, Default,
+ // full, Normal, Knownfont,
+ // Otherstyle, +Special, +Decorative,
+ // 1000 Typewriter, Rounded, Gothic, Schollbook
+ // 100
+ tools::Long nTestMatch = 0;
+
+ // test CJK script attributes
+ if ( nSearchType & ImplFontAttrs::CJK )
+ {
+ // if the matching font doesn't support any CJK languages, then
+ // it is not appropriate
+ if ( !(nMatchType & ImplFontAttrs::CJK_AllLang) )
+ {
+ nTestMatch -= 10000000;
+ }
+ else
+ {
+ // Matching language
+ if ( (nSearchType & ImplFontAttrs::CJK_AllLang)
+ && (nMatchType & ImplFontAttrs::CJK_AllLang) )
+ nTestMatch += 10000000*3;
+ if ( nMatchType & ImplFontAttrs::CJK )
+ nTestMatch += 10000000*2;
+ if ( nMatchType & ImplFontAttrs::Full )
+ nTestMatch += 10000000;
+ }
+ }
+ else if ( nMatchType & ImplFontAttrs::CJK )
+ {
+ nTestMatch -= 10000000;
+ }
+
+ // test CTL script attributes
+ if( nSearchType & ImplFontAttrs::CTL )
+ {
+ if( nMatchType & ImplFontAttrs::CTL )
+ nTestMatch += 10000000*2;
+ if( nMatchType & ImplFontAttrs::Full )
+ nTestMatch += 10000000;
+ }
+ else if ( nMatchType & ImplFontAttrs::CTL )
+ {
+ nTestMatch -= 10000000;
+ }
+
+ // test LATIN script attributes
+ if( nSearchType & ImplFontAttrs::NoneLatin )
+ {
+ if( nMatchType & ImplFontAttrs::NoneLatin )
+ nTestMatch += 10000000*2;
+ if( nMatchType & ImplFontAttrs::Full )
+ nTestMatch += 10000000;
+ }
+
+ // test SYMBOL attributes
+ if ( nSearchType & ImplFontAttrs::Symbol )
+ {
+ const OUString& rSearchName = family.first;
+ // prefer some special known symbol fonts
+ if ( rSearchName == "starsymbol" )
+ {
+ nTestMatch += 10000000*6+(10000*3);
+ }
+ else if ( rSearchName == "opensymbol" )
+ {
+ nTestMatch += 10000000*6;
+ }
+ else if ( rSearchName == "starbats" ||
+ rSearchName == "wingdings" ||
+ rSearchName == "monotypesorts" ||
+ rSearchName == "dingbats" ||
+ rSearchName == "zapfdingbats" )
+ {
+ nTestMatch += 10000000*5;
+ }
+ else if (pData->GetTypeFaces() & FontTypeFaces::Symbol)
+ {
+ nTestMatch += 10000000*4;
+ }
+ else
+ {
+ if( nMatchType & ImplFontAttrs::Symbol )
+ nTestMatch += 10000000*2;
+ if( nMatchType & ImplFontAttrs::Full )
+ nTestMatch += 10000000;
+ }
+ }
+ else if ((pData->GetTypeFaces() & (FontTypeFaces::Symbol | FontTypeFaces::NoneSymbol)) == FontTypeFaces::Symbol)
+ {
+ nTestMatch -= 10000000;
+ }
+ else if ( nMatchType & ImplFontAttrs::Symbol )
+ {
+ nTestMatch -= 10000;
+ }
+
+ // match stripped family name
+ if( !rSearchFamilyName.isEmpty() && (rSearchFamilyName == pData->GetMatchFamilyName()) )
+ {
+ nTestMatch += 1000000*3;
+ }
+
+ // match ALLSCRIPT? attribute
+ if( nSearchType & ImplFontAttrs::AllScript )
+ {
+ if( nMatchType & ImplFontAttrs::AllScript )
+ {
+ nTestMatch += 1000000*2;
+ }
+ if( nSearchType & ImplFontAttrs::AllSubscript )
+ {
+ if( ImplFontAttrs::None == ((nSearchType ^ nMatchType) & ImplFontAttrs::AllSubscript) )
+ nTestMatch += 1000000*2;
+ if( ImplFontAttrs::None != ((nSearchType ^ nMatchType) & ImplFontAttrs::BrushScript) )
+ nTestMatch -= 1000000;
+ }
+ }
+ else if( nMatchType & ImplFontAttrs::AllScript )
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // test MONOSPACE+TYPEWRITER attributes
+ if( nSearchType & ImplFontAttrs::Fixed )
+ {
+ if( nMatchType & ImplFontAttrs::Fixed )
+ nTestMatch += 1000000*2;
+ // a typewriter attribute is even better
+ if( ImplFontAttrs::None == ((nSearchType ^ nMatchType) & ImplFontAttrs::Typewriter) )
+ nTestMatch += 10000*2;
+ }
+ else if( nMatchType & ImplFontAttrs::Fixed )
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // test SPECIAL attribute
+ if( nSearchType & ImplFontAttrs::Special )
+ {
+ if( nMatchType & ImplFontAttrs::Special )
+ {
+ nTestMatch += 10000;
+ }
+ else if( !(nSearchType & ImplFontAttrs::AllSerifStyle) )
+ {
+ if( nMatchType & ImplFontAttrs::Serif )
+ {
+ nTestMatch += 1000*2;
+ }
+ else if( nMatchType & ImplFontAttrs::SansSerif )
+ {
+ nTestMatch += 1000;
+ }
+ }
+ }
+ else if( (nMatchType & ImplFontAttrs::Special) && !(nSearchType & ImplFontAttrs::Symbol) )
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // test DECORATIVE attribute
+ if( nSearchType & ImplFontAttrs::Decorative )
+ {
+ if( nMatchType & ImplFontAttrs::Decorative )
+ {
+ nTestMatch += 10000;
+ }
+ else if( !(nSearchType & ImplFontAttrs::AllSerifStyle) )
+ {
+ if( nMatchType & ImplFontAttrs::Serif )
+ nTestMatch += 1000*2;
+ else if ( nMatchType & ImplFontAttrs::SansSerif )
+ nTestMatch += 1000;
+ }
+ }
+ else if( nMatchType & ImplFontAttrs::Decorative )
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // test TITLE+CAPITALS attributes
+ if( nSearchType & (ImplFontAttrs::Titling | ImplFontAttrs::Capitals) )
+ {
+ if( nMatchType & (ImplFontAttrs::Titling | ImplFontAttrs::Capitals) )
+ {
+ nTestMatch += 1000000*2;
+ }
+ if( ImplFontAttrs::None == ((nSearchType^nMatchType) & ImplFontAttrs(ImplFontAttrs::Titling | ImplFontAttrs::Capitals)))
+ {
+ nTestMatch += 1000000;
+ }
+ else if( (nMatchType & (ImplFontAttrs::Titling | ImplFontAttrs::Capitals)) &&
+ (nMatchType & (ImplFontAttrs::Standard | ImplFontAttrs::Default)) )
+ {
+ nTestMatch += 1000000;
+ }
+ }
+ else if( nMatchType & (ImplFontAttrs::Titling | ImplFontAttrs::Capitals) )
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // test OUTLINE+SHADOW attributes
+ if( nSearchType & (ImplFontAttrs::Outline | ImplFontAttrs::Shadow) )
+ {
+ if( nMatchType & (ImplFontAttrs::Outline | ImplFontAttrs::Shadow) )
+ {
+ nTestMatch += 1000000*2;
+ }
+ if( ImplFontAttrs::None == ((nSearchType ^ nMatchType) & ImplFontAttrs(ImplFontAttrs::Outline | ImplFontAttrs::Shadow)) )
+ {
+ nTestMatch += 1000000;
+ }
+ else if( (nMatchType & (ImplFontAttrs::Outline | ImplFontAttrs::Shadow)) &&
+ (nMatchType & (ImplFontAttrs::Standard | ImplFontAttrs::Default)) )
+ {
+ nTestMatch += 1000000;
+ }
+ }
+ else if ( nMatchType & (ImplFontAttrs::Outline | ImplFontAttrs::Shadow) )
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // test font name substrings
+ // TODO: calculate name matching score using e.g. Levenstein distance
+ if( (rSearchFamilyName.getLength() >= 4) &&
+ (pData->GetMatchFamilyName().getLength() >= 4) &&
+ ((rSearchFamilyName.indexOf( pData->GetMatchFamilyName() ) != -1) ||
+ (pData->GetMatchFamilyName().indexOf( rSearchFamilyName ) != -1)) )
+ {
+ nTestMatch += 5000;
+ }
+ // test SERIF attribute
+ if( nSearchType & ImplFontAttrs::Serif )
+ {
+ if( nMatchType & ImplFontAttrs::Serif )
+ nTestMatch += 1000000*2;
+ else if( nMatchType & ImplFontAttrs::SansSerif )
+ nTestMatch -= 1000000;
+ }
+
+ // test SANSERIF attribute
+ if( nSearchType & ImplFontAttrs::SansSerif )
+ {
+ if( nMatchType & ImplFontAttrs::SansSerif )
+ nTestMatch += 1000000;
+ else if ( nMatchType & ImplFontAttrs::Serif )
+ nTestMatch -= 1000000;
+ }
+
+ // test ITALIC attribute
+ if( nSearchType & ImplFontAttrs::Italic )
+ {
+ if (pData->GetTypeFaces() & FontTypeFaces::Italic)
+ nTestMatch += 1000000*3;
+ if( nMatchType & ImplFontAttrs::Italic )
+ nTestMatch += 1000000;
+ }
+ else if (!(nSearchType & ImplFontAttrs::AllScript)
+ && ((nMatchType & ImplFontAttrs::Italic)
+ || !(pData->GetTypeFaces() & FontTypeFaces::NoneItalic)))
+ {
+ nTestMatch -= 1000000*2;
+ }
+
+ // test WIDTH attribute
+ if( (eSearchWidth != WIDTH_DONTKNOW) && (eSearchWidth != WIDTH_NORMAL) )
+ {
+ if( eSearchWidth < WIDTH_NORMAL )
+ {
+ if( eSearchWidth == eMatchWidth )
+ nTestMatch += 1000000*3;
+ else if( (eMatchWidth < WIDTH_NORMAL) && (eMatchWidth != WIDTH_DONTKNOW) )
+ nTestMatch += 1000000;
+ }
+ else
+ {
+ if( eSearchWidth == eMatchWidth )
+ nTestMatch += 1000000*3;
+ else if( eMatchWidth > WIDTH_NORMAL )
+ nTestMatch += 1000000;
+ }
+ }
+ else if( (eMatchWidth != WIDTH_DONTKNOW) && (eMatchWidth != WIDTH_NORMAL) )
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // test WEIGHT attribute
+ if( (eSearchWeight != WEIGHT_DONTKNOW) &&
+ (eSearchWeight != WEIGHT_NORMAL) &&
+ (eSearchWeight != WEIGHT_MEDIUM) )
+ {
+ if( eSearchWeight < WEIGHT_NORMAL )
+ {
+ if (pData->GetTypeFaces() & FontTypeFaces::Light)
+ nTestMatch += 1000000;
+ if( (eMatchWeight < WEIGHT_NORMAL) && (eMatchWeight != WEIGHT_DONTKNOW) )
+ nTestMatch += 1000000;
+ }
+ else
+ {
+ if (pData->GetTypeFaces() & FontTypeFaces::Bold)
+ nTestMatch += 1000000;
+ if( eMatchWeight > WEIGHT_BOLD )
+ nTestMatch += 1000000;
+ }
+ }
+ else if (((eMatchWeight != WEIGHT_DONTKNOW)
+ && (eMatchWeight != WEIGHT_NORMAL)
+ && (eMatchWeight != WEIGHT_MEDIUM))
+ || !(pData->GetTypeFaces() & FontTypeFaces::Normal))
+ {
+ nTestMatch -= 1000000;
+ }
+
+ // prefer scalable fonts
+ if (pData->GetTypeFaces() & FontTypeFaces::Scalable)
+ nTestMatch += 10000*4;
+ else
+ nTestMatch -= 10000*4;
+
+ // test STANDARD+DEFAULT+FULL+NORMAL attributes
+ if( nMatchType & ImplFontAttrs::Standard )
+ nTestMatch += 10000*2;
+ if( nMatchType & ImplFontAttrs::Default )
+ nTestMatch += 10000;
+ if( nMatchType & ImplFontAttrs::Full )
+ nTestMatch += 10000;
+ if( nMatchType & ImplFontAttrs::Normal )
+ nTestMatch += 10000;
+
+ // test OTHERSTYLE attribute
+ if( ((nSearchType ^ nMatchType) & ImplFontAttrs::OtherStyle) != ImplFontAttrs::None )
+ {
+ nTestMatch -= 10000;
+ }
+
+ // test ROUNDED attribute
+ if( ImplFontAttrs::None == ((nSearchType ^ nMatchType) & ImplFontAttrs::Rounded) )
+ nTestMatch += 1000;
+
+ // test TYPEWRITER attribute
+ if( ImplFontAttrs::None == ((nSearchType ^ nMatchType) & ImplFontAttrs::Typewriter) )
+ nTestMatch += 1000;
+
+ // test GOTHIC attribute
+ if( nSearchType & ImplFontAttrs::Gothic )
+ {
+ if( nMatchType & ImplFontAttrs::Gothic )
+ nTestMatch += 1000*3;
+ if( nMatchType & ImplFontAttrs::SansSerif )
+ nTestMatch += 1000*2;
+ }
+
+ // test SCHOOLBOOK attribute
+ if( nSearchType & ImplFontAttrs::Schoolbook )
+ {
+ if( nMatchType & ImplFontAttrs::Schoolbook )
+ nTestMatch += 1000*3;
+ if( nMatchType & ImplFontAttrs::Serif )
+ nTestMatch += 1000*2;
+ }
+
+ // compare with best matching font yet
+ if ( nTestMatch > nBestMatch )
+ {
+ pFoundData = pData;
+ nBestMatch = nTestMatch;
+ nBestType = nMatchType;
+ }
+ else if( nTestMatch == nBestMatch )
+ {
+ // some fonts are more suitable defaults
+ if( nMatchType & ImplFontAttrs::Default )
+ {
+ pFoundData = pData;
+ nBestType = nMatchType;
+ }
+ else if( (nMatchType & ImplFontAttrs::Standard) &&
+ !(nBestType & ImplFontAttrs::Default) )
+ {
+ pFoundData = pData;
+ nBestType = nMatchType;
+ }
+ }
+ }
+
+ return pFoundData;
+}
+
+PhysicalFontFamily* PhysicalFontCollection::ImplFindFontFamilyOfDefaultFont() const
+{
+ // try to find one of the default fonts of the
+ // UNICODE, SANSSERIF, SERIF or FIXED default font lists
+ PhysicalFontFamily* pFoundData = nullptr;
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ const utl::DefaultFontConfiguration& rDefaults = utl::DefaultFontConfiguration::get();
+ LanguageTag aLanguageTag("en");
+ OUString aFontname = rDefaults.getDefaultFont( aLanguageTag, DefaultFontType::SANS_UNICODE );
+ pFoundData = FindFontFamilyByTokenNames( aFontname );
+
+ if( pFoundData )
+ return pFoundData;
+
+ aFontname = rDefaults.getDefaultFont( aLanguageTag, DefaultFontType::SANS );
+ pFoundData = FindFontFamilyByTokenNames( aFontname );
+ if( pFoundData )
+ return pFoundData;
+
+ aFontname = rDefaults.getDefaultFont( aLanguageTag, DefaultFontType::SERIF );
+ pFoundData = FindFontFamilyByTokenNames( aFontname );
+ if( pFoundData )
+ return pFoundData;
+
+ aFontname = rDefaults.getDefaultFont( aLanguageTag, DefaultFontType::FIXED );
+ pFoundData = FindFontFamilyByTokenNames( aFontname );
+ if( pFoundData )
+ return pFoundData;
+ }
+
+ // now try to find a reasonable non-symbol font
+
+ ImplInitMatchData();
+
+ for (auto const& family : maPhysicalFontFamilies)
+ {
+ PhysicalFontFamily* pData = family.second.get();
+ if( pData->GetMatchType() & ImplFontAttrs::Symbol )
+ continue;
+
+ pFoundData = pData;
+ if( pData->GetMatchType() & (ImplFontAttrs::Default|ImplFontAttrs::Standard) )
+ break;
+ }
+ if( pFoundData )
+ return pFoundData;
+
+ // finding any font is better than finding no font at all
+ auto it = maPhysicalFontFamilies.begin();
+ if( it != maPhysicalFontFamilies.end() )
+ pFoundData = (*it).second.get();
+
+ return pFoundData;
+}
+
+std::shared_ptr<PhysicalFontCollection> PhysicalFontCollection::Clone() const
+{
+ auto xClonedCollection = std::make_shared<PhysicalFontCollection>();
+ xClonedCollection->mpPreMatchHook = mpPreMatchHook;
+ xClonedCollection->mpFallbackHook = mpFallbackHook;
+
+ // TODO: clone the config-font attributes too?
+ xClonedCollection->mbMatchData = false;
+
+ for (auto const& family : maPhysicalFontFamilies)
+ {
+ const PhysicalFontFamily* pFontFace = family.second.get();
+ pFontFace->UpdateCloneFontList(*xClonedCollection);
+ }
+
+ return xClonedCollection;
+}
+
+std::unique_ptr<PhysicalFontFaceCollection> PhysicalFontCollection::GetFontFaceCollection() const
+{
+ std::unique_ptr<PhysicalFontFaceCollection> pDeviceFontList(new PhysicalFontFaceCollection);
+
+ for (auto const& family : maPhysicalFontFamilies)
+ {
+ const PhysicalFontFamily* pFontFamily = family.second.get();
+ pFontFamily->UpdateDevFontList( *pDeviceFontList );
+ }
+
+ return pDeviceFontList;
+}
+
+// These are the metric-compatible replacement fonts that are bundled with
+// LibreOffice, we prefer them over generic substitutions that might be
+// provided by the system.
+const std::vector<std::pair<OUString, OUString>> aMetricCompatibleMap =
+{
+ { "Times New Roman", "Liberation Serif" },
+ { "Arial", "Liberation Sans" },
+ { "Arial Narrow", "Liberation Sans Narrow" },
+ { "Courier New", "Liberation Mono" },
+ { "Cambria", "Caladea" },
+ { "Calibri", "Carlito" },
+};
+
+static bool FindMetricCompatibleFont(FontSelectPattern& rFontSelData)
+{
+ for (const auto& aSub : aMetricCompatibleMap)
+ {
+ if (rFontSelData.maSearchName == GetEnglishSearchFontName(aSub.first))
+ {
+ rFontSelData.maSearchName = aSub.second;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+PhysicalFontFamily* PhysicalFontCollection::FindFontFamily(FontSelectPattern& rFSD) const
+{
+ // give up if no fonts are available
+ if( !Count() )
+ return nullptr;
+
+ static bool noFontLookup = getenv("SAL_NO_FONT_LOOKUP") != nullptr;
+ if (noFontLookup)
+ {
+ // Hard code the use of Liberation Sans and skip font search.
+ sal_Int32 nIndex = 0;
+ rFSD.maTargetName = GetNextFontToken(rFSD.GetFamilyName(), nIndex);
+ rFSD.maSearchName = "liberationsans";
+ PhysicalFontFamily* pFont = ImplFindFontFamilyBySearchName(rFSD.maSearchName);
+ assert(pFont);
+ return pFont;
+ }
+
+ bool bMultiToken = false;
+ sal_Int32 nTokenPos = 0;
+ OUString& aSearchName = rFSD.maSearchName; // TODO: get rid of reference
+ for(;;)
+ {
+ rFSD.maTargetName = GetNextFontToken( rFSD.GetFamilyName(), nTokenPos );
+ aSearchName = rFSD.maTargetName;
+
+ // Until features are properly supported, they are appended to the
+ // font name, so we need to strip them off so the font is found.
+ sal_Int32 nFeat = aSearchName.indexOf(FontSelectPattern::FEAT_PREFIX);
+ OUString aOrigName = rFSD.maTargetName;
+ OUString aBaseFontName = aSearchName.copy( 0, (nFeat != -1) ? nFeat : aSearchName.getLength() );
+
+ if (nFeat != -1)
+ {
+ aSearchName = aBaseFontName;
+ rFSD.maTargetName = aBaseFontName;
+ }
+
+ aSearchName = GetEnglishSearchFontName( aSearchName );
+ ImplFontSubstitute(aSearchName);
+ // #114999# special emboldening for Ricoh fonts
+ // TODO: smarter check for special cases by using PreMatch infrastructure?
+ if( (rFSD.GetWeight() > WEIGHT_MEDIUM) &&
+ aSearchName.startsWithIgnoreAsciiCase( "hg" ) )
+ {
+ OUString aBoldName;
+ if( aSearchName.startsWithIgnoreAsciiCase( "hggothicb" ) )
+ aBoldName = "hggothice";
+ else if( aSearchName.startsWithIgnoreAsciiCase( "hgpgothicb" ) )
+ aBoldName = "hgpgothice";
+ else if( aSearchName.startsWithIgnoreAsciiCase( "hgminchol" ) )
+ aBoldName = "hgminchob";
+ else if( aSearchName.startsWithIgnoreAsciiCase( "hgpminchol" ) )
+ aBoldName = "hgpminchob";
+ else if( aSearchName.equalsIgnoreAsciiCase( "hgminchob" ) )
+ aBoldName = "hgminchoe";
+ else if( aSearchName.equalsIgnoreAsciiCase( "hgpminchob" ) )
+ aBoldName = "hgpminchoe";
+
+ if( !aBoldName.isEmpty() && ImplFindFontFamilyBySearchName( aBoldName ) )
+ {
+ // the other font is available => use it
+ aSearchName = aBoldName;
+ // prevent synthetic emboldening of bold version
+ rFSD.SetWeight(WEIGHT_DONTKNOW);
+ }
+ }
+
+ // restore the features to make the font selection data unique
+ rFSD.maTargetName = aOrigName;
+
+ // check if the current font name token or its substitute is valid
+ PhysicalFontFamily* pFoundData = ImplFindFontFamilyBySearchName(aSearchName);
+ if( pFoundData )
+ return pFoundData;
+
+ // some systems provide special customization
+ // e.g. they suggest "serif" as UI-font, but this name cannot be used directly
+ // because the system wants to map it to another font first, e.g. "Helvetica"
+
+ // use the target name to search in the prematch hook
+ rFSD.maTargetName = aBaseFontName;
+
+ // Related: fdo#49271 RTF files often contain weird-ass
+ // Win 3.1/Win95 style fontnames which attempt to put the
+ // charset encoding into the filename
+ // http://www.webcenter.ru/~kazarn/eng/fonts_ttf.htm
+ OUString sStrippedName = StripScriptFromName(rFSD.maTargetName);
+ if (sStrippedName != rFSD.maTargetName)
+ {
+ rFSD.maTargetName = sStrippedName;
+ aSearchName = GetEnglishSearchFontName(rFSD.maTargetName);
+ pFoundData = ImplFindFontFamilyBySearchName(aSearchName);
+ if( pFoundData )
+ return pFoundData;
+ }
+
+ if (FindMetricCompatibleFont(rFSD) ||
+ (mpPreMatchHook && mpPreMatchHook->FindFontSubstitute(rFSD)))
+ {
+ aSearchName = GetEnglishSearchFontName(aSearchName);
+ }
+
+ // the prematch hook uses the target name to search, but we now need
+ // to restore the features to make the font selection data unique
+ rFSD.maTargetName = aOrigName;
+
+ pFoundData = ImplFindFontFamilyBySearchName( aSearchName );
+ if( pFoundData )
+ return pFoundData;
+
+ // break after last font name token was checked unsuccessfully
+ if( nTokenPos == -1)
+ break;
+ bMultiToken = true;
+ }
+
+ // if the first font was not available find the next available font in
+ // the semicolon separated list of font names. A font is also considered
+ // available when there is a matching entry in the Tools->Options->Fonts
+ // dialog with neither ALWAYS nor SCREENONLY flags set and the substitution
+ // font is available
+ for( nTokenPos = 0; nTokenPos != -1; )
+ {
+ if( bMultiToken )
+ {
+ rFSD.maTargetName = GetNextFontToken( rFSD.GetFamilyName(), nTokenPos );
+ aSearchName = GetEnglishSearchFontName( rFSD.maTargetName );
+ }
+ else
+ nTokenPos = -1;
+ if (FindMetricCompatibleFont(rFSD) ||
+ (mpPreMatchHook && mpPreMatchHook->FindFontSubstitute(rFSD)))
+ {
+ aSearchName = GetEnglishSearchFontName( aSearchName );
+ }
+ ImplFontSubstitute(aSearchName);
+ PhysicalFontFamily* pFoundData = ImplFindFontFamilyBySearchName(aSearchName);
+ if( pFoundData )
+ return pFoundData;
+ }
+
+ // if no font with a directly matching name is available use the
+ // first font name token and get its attributes to find a replacement
+ if ( bMultiToken )
+ {
+ nTokenPos = 0;
+ rFSD.maTargetName = GetNextFontToken( rFSD.GetFamilyName(), nTokenPos );
+ aSearchName = GetEnglishSearchFontName( rFSD.maTargetName );
+ }
+
+ OUString aSearchShortName;
+ OUString aSearchFamilyName;
+ FontWeight eSearchWeight = rFSD.GetWeight();
+ FontWidth eSearchWidth = rFSD.GetWidthType();
+ ImplFontAttrs nSearchType = ImplFontAttrs::None;
+ utl::FontSubstConfiguration::getMapName( aSearchName, aSearchShortName, aSearchFamilyName,
+ eSearchWeight, eSearchWidth, nSearchType );
+
+ // note: the search name was already translated to english (if possible)
+ // use the font's shortened name if needed
+ if ( aSearchShortName != aSearchName )
+ {
+ PhysicalFontFamily* pFoundData = ImplFindFontFamilyBySearchName(aSearchShortName);
+ if( pFoundData )
+ {
+#ifdef UNX
+ /* #96738# don't use mincho as a replacement for "MS Mincho" on X11: Mincho is
+ a korean bitmap font that is not suitable here. Use the font replacement table,
+ that automatically leads to the desired "HG Mincho Light J". Same story for
+ MS Gothic, there are thai and korean "Gothic" fonts, so we even prefer Andale */
+ if ((aSearchName != "msmincho") && (aSearchName != "msgothic"))
+ // TODO: add heuristic to only throw out the fake ms* fonts
+#endif
+ {
+ return pFoundData;
+ }
+ }
+ }
+
+ // use font fallback
+ const utl::FontNameAttr* pFontAttr = nullptr;
+ if (!aSearchName.isEmpty() && !utl::ConfigManager::IsFuzzing())
+ {
+ // get fallback info using FontSubstConfiguration and
+ // the target name, it's shortened name and family name in that order
+ const utl::FontSubstConfiguration& rFontSubst = utl::FontSubstConfiguration::get();
+ pFontAttr = rFontSubst.getSubstInfo( aSearchName );
+ if ( !pFontAttr && (aSearchShortName != aSearchName) )
+ pFontAttr = rFontSubst.getSubstInfo( aSearchShortName );
+ if ( !pFontAttr && (aSearchFamilyName != aSearchShortName) )
+ pFontAttr = rFontSubst.getSubstInfo( aSearchFamilyName );
+
+ // try the font substitutions suggested by the fallback info
+ if( pFontAttr )
+ {
+ PhysicalFontFamily* pFoundData = ImplFindFontFamilyBySubstFontAttr(*pFontAttr);
+ if( pFoundData )
+ return pFoundData;
+ }
+ }
+
+ // if a target symbol font is not available use a default symbol font
+ if( rFSD.IsSymbolFont() )
+ {
+ LanguageTag aDefaultLanguageTag("en");
+ if (utl::ConfigManager::IsFuzzing())
+ aSearchName = "OpenSymbol";
+ else
+ aSearchName = utl::DefaultFontConfiguration::get().getDefaultFont( aDefaultLanguageTag, DefaultFontType::SYMBOL );
+ PhysicalFontFamily* pFoundData = FindFontFamilyByTokenNames(aSearchName);
+ if( pFoundData )
+ return pFoundData;
+ }
+
+ // now try the other font name tokens
+ while( nTokenPos != -1 )
+ {
+ rFSD.maTargetName = GetNextFontToken( rFSD.GetFamilyName(), nTokenPos );
+ if( rFSD.maTargetName.isEmpty() )
+ continue;
+
+ aSearchName = GetEnglishSearchFontName( rFSD.maTargetName );
+
+ OUString aTempShortName;
+ OUString aTempFamilyName;
+ ImplFontAttrs nTempType = ImplFontAttrs::None;
+ FontWeight eTempWeight = rFSD.GetWeight();
+ FontWidth eTempWidth = WIDTH_DONTKNOW;
+ utl::FontSubstConfiguration::getMapName( aSearchName, aTempShortName, aTempFamilyName,
+ eTempWeight, eTempWidth, nTempType );
+
+ // use a shortened token name if available
+ if( aTempShortName != aSearchName )
+ {
+ PhysicalFontFamily* pFoundData = ImplFindFontFamilyBySearchName(aTempShortName);
+ if( pFoundData )
+ return pFoundData;
+ }
+
+ const utl::FontNameAttr* pTempFontAttr = nullptr;
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ // use a font name from font fallback list to determine font attributes
+ // get fallback info using FontSubstConfiguration and
+ // the target name, it's shortened name and family name in that order
+ const utl::FontSubstConfiguration& rFontSubst = utl::FontSubstConfiguration::get();
+ pTempFontAttr = rFontSubst.getSubstInfo( aSearchName );
+
+ if ( !pTempFontAttr && (aTempShortName != aSearchName) )
+ pTempFontAttr = rFontSubst.getSubstInfo( aTempShortName );
+
+ if ( !pTempFontAttr && (aTempFamilyName != aTempShortName) )
+ pTempFontAttr = rFontSubst.getSubstInfo( aTempFamilyName );
+ }
+
+ // try the font substitutions suggested by the fallback info
+ if( pTempFontAttr )
+ {
+ PhysicalFontFamily* pFoundData = ImplFindFontFamilyBySubstFontAttr(*pTempFontAttr);
+ if( pFoundData )
+ return pFoundData;
+ if( !pFontAttr )
+ pFontAttr = pTempFontAttr;
+ }
+ }
+
+ // if still needed use the font request's attributes to find a good match
+ if (MsLangId::isSimplifiedChinese(rFSD.meLanguage))
+ nSearchType |= ImplFontAttrs::CJK | ImplFontAttrs::CJK_SC;
+ else if (MsLangId::isTraditionalChinese(rFSD.meLanguage))
+ nSearchType |= ImplFontAttrs::CJK | ImplFontAttrs::CJK_TC;
+ else if (MsLangId::isKorean(rFSD.meLanguage))
+ nSearchType |= ImplFontAttrs::CJK | ImplFontAttrs::CJK_KR;
+ else if (rFSD.meLanguage == LANGUAGE_JAPANESE)
+ nSearchType |= ImplFontAttrs::CJK | ImplFontAttrs::CJK_JP;
+ else
+ {
+ nSearchType |= lcl_IsCJKFont( rFSD.GetFamilyName() );
+ if( rFSD.IsSymbolFont() )
+ nSearchType |= ImplFontAttrs::Symbol;
+ }
+
+ PhysicalFontFamily::CalcType(nSearchType, eSearchWeight, eSearchWidth, rFSD.GetFamilyType(), pFontAttr);
+ PhysicalFontFamily* pFoundData = FindFontFamilyByAttributes(nSearchType,
+ eSearchWeight, eSearchWidth, rFSD.GetItalic(), aSearchFamilyName);
+
+ if( pFoundData )
+ {
+ // overwrite font selection attributes using info from the typeface flags
+ if ((eSearchWeight >= WEIGHT_BOLD)
+ && (eSearchWeight > rFSD.GetWeight())
+ && (pFoundData->GetTypeFaces() & FontTypeFaces::Bold))
+ {
+ rFSD.SetWeight( eSearchWeight );
+ }
+ else if ((eSearchWeight < WEIGHT_NORMAL)
+ && (eSearchWeight < rFSD.GetWeight())
+ && (eSearchWeight != WEIGHT_DONTKNOW)
+ && (pFoundData->GetTypeFaces() & FontTypeFaces::Light))
+ {
+ rFSD.SetWeight( eSearchWeight );
+ }
+
+ if ((nSearchType & ImplFontAttrs::Italic)
+ && ((rFSD.GetItalic() == ITALIC_DONTKNOW)
+ || (rFSD.GetItalic() == ITALIC_NONE))
+ && (pFoundData->GetTypeFaces() & FontTypeFaces::Italic))
+ {
+ rFSD.SetItalic( ITALIC_NORMAL );
+ }
+ }
+ else
+ {
+ // if still needed fall back to default fonts
+ pFoundData = ImplFindFontFamilyOfDefaultFont();
+ }
+
+ return pFoundData;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/font/PhysicalFontFace.cxx b/vcl/source/font/PhysicalFontFace.cxx
new file mode 100644
index 000000000..aef9054fd
--- /dev/null
+++ b/vcl/source/font/PhysicalFontFace.cxx
@@ -0,0 +1,206 @@
+/* -*- 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 <sal/types.h>
+#include <tools/fontenum.hxx>
+#include <unotools/fontdefs.hxx>
+
+#include <fontattributes.hxx>
+
+#include <font/FontSelectPattern.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <string_view>
+
+namespace vcl::font
+{
+
+PhysicalFontFace::PhysicalFontFace( const FontAttributes& rDFA )
+ : FontAttributes( rDFA )
+{
+ // StarSymbol is a unicode font, but it still deserves the symbol flag
+ if( !IsSymbolFont() )
+ if ( IsStarSymbol( GetFamilyName() ) )
+ SetSymbolFlag( true );
+}
+
+sal_Int32 PhysicalFontFace::CompareIgnoreSize( const PhysicalFontFace& rOther ) const
+{
+ // compare their width, weight, italic, style name and family name
+ if( GetWidthType() < rOther.GetWidthType() )
+ return -1;
+ else if( GetWidthType() > rOther.GetWidthType() )
+ return 1;
+
+ if( GetWeight() < rOther.GetWeight() )
+ return -1;
+ else if( GetWeight() > rOther.GetWeight() )
+ return 1;
+
+ if( GetItalic() < rOther.GetItalic() )
+ return -1;
+ else if( GetItalic() > rOther.GetItalic() )
+ return 1;
+
+ sal_Int32 nRet = GetFamilyName().compareTo( rOther.GetFamilyName() );
+
+ if (nRet == 0)
+ {
+ nRet = GetStyleName().compareTo( rOther.GetStyleName() );
+ }
+
+ return nRet;
+}
+
+static int FamilyNameMatchValue(FontSelectPattern const& rFSP, std::u16string_view sFontFamily)
+{
+ const OUString& rFontName = rFSP.maTargetName;
+
+ if (rFontName.equalsIgnoreAsciiCase(sFontFamily))
+ return 240000;
+
+ return 0;
+}
+
+static int StyleNameMatchValue(FontMatchStatus const& rStatus, std::u16string_view rStyle)
+{
+ if (rStatus.mpTargetStyleName && o3tl::equalsIgnoreAsciiCase(rStyle, *rStatus.mpTargetStyleName))
+ return 120000;
+
+ return 0;
+}
+
+static int PitchMatchValue(FontSelectPattern const& rFSP, FontPitch ePitch)
+{
+ if ((rFSP.GetPitch() != PITCH_DONTKNOW) && (rFSP.GetPitch() == ePitch))
+ return 20000;
+
+ return 0;
+}
+
+static int PreferNormalFontWidthMatchValue(FontWidth eWidthType)
+{
+ // TODO: change when the upper layers can tell their width preference
+ if (eWidthType == WIDTH_NORMAL)
+ return 400;
+ else if ((eWidthType == WIDTH_SEMI_EXPANDED) || (eWidthType == WIDTH_SEMI_CONDENSED))
+ return 300;
+
+ return 0;
+}
+
+static int WeightMatchValue(FontSelectPattern const& rFSP, FontWeight eWeight)
+{
+ int nMatch = 0;
+
+ if (rFSP.GetWeight() != WEIGHT_DONTKNOW)
+ {
+ // if not bold or requiring emboldening prefer light fonts to bold fonts
+ FontWeight ePatternWeight = rFSP.mbEmbolden ? WEIGHT_NORMAL : rFSP.GetWeight();
+
+ int nReqWeight = static_cast<int>(ePatternWeight);
+ if (ePatternWeight > WEIGHT_MEDIUM)
+ nReqWeight += 100;
+
+ int nGivenWeight = static_cast<int>(eWeight);
+ if (eWeight > WEIGHT_MEDIUM)
+ nGivenWeight += 100;
+
+ int nWeightDiff = nReqWeight - nGivenWeight;
+
+ if (nWeightDiff == 0)
+ nMatch += 1000;
+ else if (nWeightDiff == +1 || nWeightDiff == -1)
+ nMatch += 700;
+ else if (nWeightDiff < +50 && nWeightDiff > -50)
+ nMatch += 200;
+ }
+ else
+ {
+ // prefer NORMAL font weight
+ // TODO: change when the upper layers can tell their weight preference
+ if (eWeight == WEIGHT_NORMAL)
+ nMatch += 450;
+ else if (eWeight == WEIGHT_MEDIUM)
+ nMatch += 350;
+ else if ((eWeight == WEIGHT_SEMILIGHT) || (eWeight == WEIGHT_SEMIBOLD))
+ nMatch += 200;
+ else if (eWeight == WEIGHT_LIGHT)
+ nMatch += 150;
+ }
+
+ return nMatch;
+}
+
+static int ItalicMatchValue(FontSelectPattern const& rFSP, FontItalic eItalic)
+{
+ // if requiring custom matrix to fake italic, prefer upright font
+ FontItalic ePatternItalic
+ = rFSP.maItalicMatrix != ItalicMatrix() ? ITALIC_NONE : rFSP.GetItalic();
+
+ if (ePatternItalic == ITALIC_NONE)
+ {
+ if (eItalic == ITALIC_NONE)
+ return 900;
+ }
+ else
+ {
+ if (ePatternItalic == eItalic)
+ return 900;
+ else if (eItalic != ITALIC_NONE)
+ return 600;
+ }
+
+ return 0;
+}
+
+bool PhysicalFontFace::IsBetterMatch( const FontSelectPattern& rFSP, FontMatchStatus& rStatus ) const
+{
+ int nMatch = FamilyNameMatchValue(rFSP, GetFamilyName());
+ nMatch += StyleNameMatchValue(rStatus, GetStyleName());
+ nMatch += PitchMatchValue(rFSP, GetPitch());
+ nMatch += PreferNormalFontWidthMatchValue(GetWidthType());
+ nMatch += WeightMatchValue(rFSP, GetWeight());
+ nMatch += ItalicMatchValue(rFSP, GetItalic());
+
+ if (rFSP.mnOrientation != 0_deg10)
+ nMatch += 80;
+ else if (rFSP.mnWidth != 0)
+ nMatch += 25;
+ else
+ nMatch += 5;
+
+ if( rStatus.mnFaceMatch > nMatch )
+ {
+ return false;
+ }
+ else if( rStatus.mnFaceMatch < nMatch )
+ {
+ rStatus.mnFaceMatch = nMatch;
+ return true;
+ }
+
+ return true;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/font/PhysicalFontFamily.cxx b/vcl/source/font/PhysicalFontFamily.cxx
new file mode 100644
index 000000000..b402cd618
--- /dev/null
+++ b/vcl/source/font/PhysicalFontFamily.cxx
@@ -0,0 +1,269 @@
+/* -*- 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 <rtl/ustring.hxx>
+#include <unotools/fontdefs.hxx>
+
+#include <font/PhysicalFontFaceCollection.hxx>
+#include <font/PhysicalFontCollection.hxx>
+
+namespace vcl::font
+{
+
+void PhysicalFontFamily::CalcType( ImplFontAttrs& rType, FontWeight& rWeight, FontWidth& rWidth,
+ FontFamily eFamily, const utl::FontNameAttr* pFontAttr )
+{
+ if ( eFamily != FAMILY_DONTKNOW )
+ {
+ if ( eFamily == FAMILY_SWISS )
+ rType |= ImplFontAttrs::SansSerif;
+ else if ( eFamily == FAMILY_ROMAN )
+ rType |= ImplFontAttrs::Serif;
+ else if ( eFamily == FAMILY_SCRIPT )
+ rType |= ImplFontAttrs::Script;
+ else if ( eFamily == FAMILY_MODERN )
+ rType |= ImplFontAttrs::Fixed;
+ else if ( eFamily == FAMILY_DECORATIVE )
+ rType |= ImplFontAttrs::Decorative;
+ }
+
+ if ( pFontAttr )
+ {
+ rType |= pFontAttr->Type;
+
+ if ( ((rWeight == WEIGHT_DONTKNOW) || (rWeight == WEIGHT_NORMAL)) &&
+ (pFontAttr->Weight != WEIGHT_DONTKNOW) )
+ rWeight = pFontAttr->Weight;
+ if ( ((rWidth == WIDTH_DONTKNOW) || (rWidth == WIDTH_NORMAL)) &&
+ (pFontAttr->Width != WIDTH_DONTKNOW) )
+ rWidth = pFontAttr->Width;
+ }
+}
+
+static ImplFontAttrs lcl_IsCJKFont( const OUString& rFontName )
+{
+ // Test, if Fontname includes CJK characters --> In this case we
+ // mention that it is a CJK font
+ for(int i = 0; i < rFontName.getLength(); i++)
+ {
+ const sal_Unicode ch = rFontName[i];
+ // japanese
+ if ( ((ch >= 0x3040) && (ch <= 0x30FF)) ||
+ ((ch >= 0x3190) && (ch <= 0x319F)) )
+ return ImplFontAttrs::CJK|ImplFontAttrs::CJK_JP;
+
+ // korean
+ if ( ((ch >= 0xAC00) && (ch <= 0xD7AF)) ||
+ ((ch >= 0xA960) && (ch <= 0xA97F)) ||
+ ((ch >= 0xD7B0) && (ch <= 0xD7FF)) ||
+ ((ch >= 0x3130) && (ch <= 0x318F)) ||
+ ((ch >= 0x1100) && (ch <= 0x11FF)) )
+ return ImplFontAttrs::CJK|ImplFontAttrs::CJK_KR;
+
+ // chinese
+ if ( (ch >= 0x3400) && (ch <= 0x9FFF) )
+ return ImplFontAttrs::CJK|ImplFontAttrs::CJK_TC|ImplFontAttrs::CJK_SC;
+
+ // cjk
+ if ( ((ch >= 0x3000) && (ch <= 0xD7AF)) ||
+ ((ch >= 0xFF00) && (ch <= 0xFFEE)) )
+ return ImplFontAttrs::CJK;
+
+ }
+
+ return ImplFontAttrs::None;
+}
+
+PhysicalFontFamily::PhysicalFontFamily( const OUString& rSearchName )
+: maSearchName( rSearchName ),
+ mnTypeFaces( FontTypeFaces::NONE ),
+ meFamily( FAMILY_DONTKNOW ),
+ mePitch( PITCH_DONTKNOW ),
+ mnMinQuality( -1 ),
+ mnMatchType( ImplFontAttrs::None ),
+ meMatchWeight( WEIGHT_DONTKNOW ),
+ meMatchWidth( WIDTH_DONTKNOW )
+{}
+
+PhysicalFontFamily::~PhysicalFontFamily()
+{
+}
+
+void PhysicalFontFamily::AddFontFace( PhysicalFontFace* pNewFontFace )
+{
+ if( maFontFaces.empty() )
+ {
+ maFamilyName = pNewFontFace->GetFamilyName();
+ maMapNames = pNewFontFace->GetMapNames();
+ meFamily = pNewFontFace->GetFamilyType();
+ mePitch = pNewFontFace->GetPitch();
+ mnMinQuality = pNewFontFace->GetQuality();
+ }
+ else
+ {
+ if( meFamily == FAMILY_DONTKNOW )
+ meFamily = pNewFontFace->GetFamilyType();
+ if( mePitch == PITCH_DONTKNOW )
+ mePitch = pNewFontFace->GetPitch();
+ if( mnMinQuality > pNewFontFace->GetQuality() )
+ mnMinQuality = pNewFontFace->GetQuality();
+ }
+
+ // set attributes for attribute based font matching
+ mnTypeFaces |= FontTypeFaces::Scalable;
+
+ if( pNewFontFace->IsSymbolFont() )
+ mnTypeFaces |= FontTypeFaces::Symbol;
+ else
+ mnTypeFaces |= FontTypeFaces::NoneSymbol;
+
+ if( pNewFontFace->GetWeight() != WEIGHT_DONTKNOW )
+ {
+ if( pNewFontFace->GetWeight() >= WEIGHT_SEMIBOLD )
+ mnTypeFaces |= FontTypeFaces::Bold;
+ else if( pNewFontFace->GetWeight() <= WEIGHT_SEMILIGHT )
+ mnTypeFaces |= FontTypeFaces::Light;
+ else
+ mnTypeFaces |= FontTypeFaces::Normal;
+ }
+
+ if( pNewFontFace->GetItalic() == ITALIC_NONE )
+ mnTypeFaces |= FontTypeFaces::NoneItalic;
+ else if( (pNewFontFace->GetItalic() == ITALIC_NORMAL)
+ || (pNewFontFace->GetItalic() == ITALIC_OBLIQUE) )
+ mnTypeFaces |= FontTypeFaces::Italic;
+
+ // reassign name (sharing saves memory)
+ if( pNewFontFace->GetFamilyName() == GetFamilyName() )
+ pNewFontFace->SetFamilyName( GetFamilyName() );
+
+ // add the new physical font face, replacing existing font face if necessary
+ // TODO: get rid of linear search?
+ auto it(maFontFaces.begin());
+ for (; it != maFontFaces.end(); ++it)
+ {
+ PhysicalFontFace* pFoundFontFace = it->get();
+ sal_Int32 eComp = pNewFontFace->CompareIgnoreSize( *pFoundFontFace );
+ if( eComp > 0 )
+ continue;
+ if( eComp < 0 )
+ break;
+
+ // ignore duplicate if its quality is worse
+ if( pNewFontFace->GetQuality() < pFoundFontFace->GetQuality() )
+ return;
+
+ // keep the device font if its quality is good enough
+ if( pNewFontFace->GetQuality() == pFoundFontFace->GetQuality() )
+ return;
+
+ // replace existing font face with a better one
+ *it = pNewFontFace; // insert at sort position
+ return;
+ }
+
+ maFontFaces.emplace(it, pNewFontFace); // insert at sort position
+}
+
+// get font attributes using the normalized font family name
+void PhysicalFontFamily::InitMatchData( const utl::FontSubstConfiguration& rFontSubst,
+ const OUString& rSearchName )
+{
+ OUString aShortName;
+ OUString aMatchFamilyName(maMatchFamilyName);
+ // get font attributes from the decorated font name
+ utl::FontSubstConfiguration::getMapName( rSearchName, aShortName, aMatchFamilyName,
+ meMatchWeight, meMatchWidth, mnMatchType );
+ maMatchFamilyName = aMatchFamilyName;
+ const utl::FontNameAttr* pFontAttr = rFontSubst.getSubstInfo( rSearchName );
+ // eventually use the stripped name
+ if( !pFontAttr )
+ if( aShortName != rSearchName )
+ pFontAttr = rFontSubst.getSubstInfo( aShortName );
+ CalcType( mnMatchType, meMatchWeight, meMatchWidth, meFamily, pFontAttr );
+ mnMatchType |= lcl_IsCJKFont( maFamilyName );
+}
+
+PhysicalFontFace* PhysicalFontFamily::FindBestFontFace( const vcl::font::FontSelectPattern& rFSD ) const
+{
+ if( maFontFaces.empty() )
+ return nullptr;
+ if( maFontFaces.size() == 1)
+ return maFontFaces[0].get();
+
+ // FontName+StyleName should map to FamilyName+StyleName
+ const OUString& rSearchName = rFSD.maTargetName;
+ OUString aTargetStyleName;
+ const OUString* pTargetStyleName = nullptr;
+ if((rSearchName.getLength() > maSearchName.getLength())
+ && rSearchName.startsWith( maSearchName ) )
+ {
+ aTargetStyleName = rSearchName.copy(maSearchName.getLength() + 1);
+ pTargetStyleName = &aTargetStyleName;
+ }
+
+ // TODO: linear search improve!
+ PhysicalFontFace* pBestFontFace = maFontFaces[0].get();
+ FontMatchStatus aFontMatchStatus = {0, pTargetStyleName};
+ for (auto const& font : maFontFaces)
+ {
+ PhysicalFontFace* pFoundFontFace = font.get();
+ if( pFoundFontFace->IsBetterMatch( rFSD, aFontMatchStatus ) )
+ pBestFontFace = pFoundFontFace;
+ }
+
+ return pBestFontFace;
+}
+
+// update device font list with unique font faces, with uniqueness
+// meaning different font attributes, but not different fonts sizes
+void PhysicalFontFamily::UpdateDevFontList( vcl::font::PhysicalFontFaceCollection& rDevFontList ) const
+{
+ PhysicalFontFace* pPrevFace = nullptr;
+ for (auto const& font : maFontFaces)
+ {
+ PhysicalFontFace* pFoundFontFace = font.get();
+ if( !pPrevFace || pFoundFontFace->CompareIgnoreSize( *pPrevFace ) )
+ rDevFontList.Add( pFoundFontFace );
+ pPrevFace = pFoundFontFace;
+ }
+}
+
+void PhysicalFontFamily::UpdateCloneFontList(vcl::font::PhysicalFontCollection& rFontCollection) const
+{
+ OUString aFamilyName = GetEnglishSearchFontName( GetFamilyName() );
+ PhysicalFontFamily* pFamily(nullptr);
+
+ for (auto const& font : maFontFaces)
+ {
+ PhysicalFontFace *pFoundFontFace = font.get();
+
+ if (!pFamily)
+ { // tdf#98989 lazy create as family without faces won't work
+ pFamily = rFontCollection.FindOrCreateFontFamily(aFamilyName);
+ }
+ assert(pFamily);
+ pFamily->AddFontFace( pFoundFontFace );
+ }
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/font.cxx b/vcl/source/font/font.cxx
new file mode 100644
index 000000000..2b7f22ad0
--- /dev/null
+++ b/vcl/source/font/font.cxx
@@ -0,0 +1,1155 @@
+/* -*- 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 <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/gen.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/fontcfg.hxx>
+#include <unotools/fontdefs.hxx>
+#include <o3tl/hash_combine.hxx>
+
+#include <vcl/font.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <impfont.hxx>
+#include <fontattributes.hxx>
+#include <fontsubset.hxx>
+#include <sft.hxx>
+
+#include <algorithm>
+#include <string_view>
+
+#include <vcl/TypeSerializer.hxx>
+
+#ifdef _WIN32
+#include <vcl/metric.hxx>
+#endif
+
+using namespace vcl;
+
+namespace
+{
+ Font::ImplType& GetGlobalDefault()
+ {
+ static Font::ImplType gDefault;
+ return gDefault;
+ }
+}
+
+Font::Font() : mpImplFont(GetGlobalDefault())
+{
+}
+
+Font::Font( const vcl::Font& rFont ) : mpImplFont( rFont.mpImplFont )
+{
+}
+
+Font::Font( vcl::Font&& rFont ) noexcept : mpImplFont( std::move(rFont.mpImplFont) )
+{
+}
+
+Font::Font( const OUString& rFamilyName, const Size& rSize )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->maFamilyName != rFamilyName
+ || const_cast<const ImplType&>(mpImplFont)->maAverageFontSize != rSize)
+ {
+ mpImplFont->SetFamilyName( rFamilyName );
+ mpImplFont->SetFontSize( rSize );
+ }
+}
+
+Font::Font( const OUString& rFamilyName, const OUString& rStyleName, const Size& rSize )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->maFamilyName != rFamilyName
+ || const_cast<const ImplType&>(mpImplFont)->maStyleName != rStyleName
+ || const_cast<const ImplType&>(mpImplFont)->maAverageFontSize != rSize)
+ {
+ mpImplFont->SetFamilyName( rFamilyName );
+ mpImplFont->SetStyleName( rStyleName );
+ mpImplFont->SetFontSize( rSize );
+ }
+}
+
+Font::Font( FontFamily eFamily, const Size& rSize )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->meFamily != eFamily
+ || const_cast<const ImplType&>(mpImplFont)->maAverageFontSize != rSize)
+ {
+ mpImplFont->SetFamilyType( eFamily );
+ mpImplFont->SetFontSize( rSize );
+ }
+}
+
+Font::~Font()
+{
+}
+
+void Font::SetColor( const Color& rColor )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->maColor != rColor)
+ {
+ mpImplFont->maColor = rColor;
+ }
+}
+
+void Font::SetFillColor( const Color& rColor )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->maFillColor != rColor)
+ {
+ mpImplFont->maFillColor = rColor;
+ if ( rColor.IsTransparent() )
+ mpImplFont->mbTransparent = true;
+ }
+}
+
+void Font::SetTransparent( bool bTransparent )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->mbTransparent != bTransparent)
+ mpImplFont->mbTransparent = bTransparent;
+}
+
+void Font::SetAlignment( TextAlign eAlign )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->meAlign != eAlign)
+ mpImplFont->SetAlignment(eAlign);
+}
+
+void Font::SetFamilyName( const OUString& rFamilyName )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->maFamilyName != rFamilyName)
+ mpImplFont->SetFamilyName( rFamilyName );
+}
+
+void Font::SetStyleName( const OUString& rStyleName )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->maStyleName != rStyleName)
+ mpImplFont->maStyleName = rStyleName;
+}
+
+void Font::SetFontSize( const Size& rSize )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->GetFontSize() != rSize)
+ mpImplFont->SetFontSize( rSize );
+}
+
+void Font::SetFamily( FontFamily eFamily )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->GetFamilyTypeNoAsk() != eFamily)
+ mpImplFont->SetFamilyType( eFamily );
+}
+
+void Font::SetCharSet( rtl_TextEncoding eCharSet )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->GetCharSet() != eCharSet)
+ {
+ mpImplFont->SetCharSet( eCharSet );
+
+ if ( eCharSet == RTL_TEXTENCODING_SYMBOL )
+ mpImplFont->SetSymbolFlag( true );
+ else
+ mpImplFont->SetSymbolFlag( false );
+ }
+}
+
+bool Font::IsSymbolFont() const
+{
+ return mpImplFont->IsSymbolFont();
+}
+
+void Font::SetSymbolFlag( bool bSymbol )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->mbSymbolFlag != bSymbol)
+ {
+ mpImplFont->SetSymbolFlag( bSymbol );
+
+ if ( IsSymbolFont() )
+ {
+ mpImplFont->SetCharSet( RTL_TEXTENCODING_SYMBOL );
+ }
+ else
+ {
+ if ( std::as_const(mpImplFont)->GetCharSet() == RTL_TEXTENCODING_SYMBOL )
+ mpImplFont->SetCharSet( RTL_TEXTENCODING_DONTKNOW );
+ }
+ }
+}
+
+void Font::SetLanguageTag( const LanguageTag& rLanguageTag )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->maLanguageTag != rLanguageTag)
+ mpImplFont->maLanguageTag = rLanguageTag;
+}
+
+void Font::SetCJKContextLanguageTag( const LanguageTag& rLanguageTag )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->maCJKLanguageTag != rLanguageTag)
+ mpImplFont->maCJKLanguageTag = rLanguageTag;
+}
+
+void Font::SetLanguage( LanguageType eLanguage )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->maLanguageTag.getLanguageType(false) != eLanguage)
+ mpImplFont->maLanguageTag.reset( eLanguage);
+}
+
+void Font::SetCJKContextLanguage( LanguageType eLanguage )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->maCJKLanguageTag.getLanguageType(false) != eLanguage)
+ mpImplFont->maCJKLanguageTag.reset( eLanguage);
+}
+
+void Font::SetPitch( FontPitch ePitch )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->GetPitchNoAsk() != ePitch)
+ mpImplFont->SetPitch( ePitch );
+}
+
+void Font::SetOrientation( Degree10 nOrientation )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->mnOrientation != nOrientation)
+ mpImplFont->mnOrientation = nOrientation;
+}
+
+void Font::SetVertical( bool bVertical )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->mbVertical != bVertical)
+ mpImplFont->mbVertical = bVertical;
+}
+
+void Font::SetKerning( FontKerning eKerning )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->meKerning != eKerning)
+ mpImplFont->meKerning = eKerning;
+}
+
+bool Font::IsKerning() const
+{
+ return mpImplFont->meKerning != FontKerning::NONE;
+}
+
+void Font::SetWeight( FontWeight eWeight )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->GetWeightNoAsk() != eWeight)
+ mpImplFont->SetWeight( eWeight );
+}
+
+void Font::SetWidthType( FontWidth eWidth )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->GetWidthTypeNoAsk() != eWidth)
+ mpImplFont->SetWidthType( eWidth );
+}
+
+void Font::SetItalic( FontItalic eItalic )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->GetItalicNoAsk() != eItalic)
+ mpImplFont->SetItalic( eItalic );
+}
+
+void Font::SetOutline( bool bOutline )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->mbOutline != bOutline)
+ mpImplFont->mbOutline = bOutline;
+}
+
+void Font::SetShadow( bool bShadow )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->mbShadow != bShadow)
+ mpImplFont->mbShadow = bShadow;
+}
+
+void Font::SetUnderline( FontLineStyle eUnderline )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->meUnderline != eUnderline)
+ mpImplFont->meUnderline = eUnderline;
+}
+
+void Font::SetOverline( FontLineStyle eOverline )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->meOverline != eOverline)
+ mpImplFont->meOverline = eOverline;
+}
+
+void Font::SetStrikeout( FontStrikeout eStrikeout )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->meStrikeout != eStrikeout)
+ mpImplFont->meStrikeout = eStrikeout;
+}
+
+void Font::SetRelief( FontRelief eRelief )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->meRelief != eRelief)
+ mpImplFont->meRelief = eRelief;
+}
+
+void Font::SetEmphasisMark( FontEmphasisMark eEmphasisMark )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->meEmphasisMark != eEmphasisMark )
+ mpImplFont->meEmphasisMark = eEmphasisMark;
+}
+
+void Font::SetWordLineMode( bool bWordLine )
+{
+ if (const_cast<const ImplType&>(mpImplFont)->mbWordLine != bWordLine)
+ mpImplFont->mbWordLine = bWordLine;
+}
+
+Font& Font::operator=( const vcl::Font& rFont )
+{
+ mpImplFont = rFont.mpImplFont;
+ return *this;
+}
+
+Font& Font::operator=( vcl::Font&& rFont ) noexcept
+{
+ mpImplFont = std::move(rFont.mpImplFont);
+ return *this;
+}
+
+bool Font::operator==( const vcl::Font& rFont ) const
+{
+ return mpImplFont == rFont.mpImplFont;
+}
+
+bool Font::EqualIgnoreColor( const vcl::Font& rFont ) const
+{
+ return mpImplFont->EqualIgnoreColor( *rFont.mpImplFont );
+}
+
+size_t Font::GetHashValue() const
+{
+ return mpImplFont->GetHashValue();
+}
+
+size_t Font::GetHashValueIgnoreColor() const
+{
+ return mpImplFont->GetHashValueIgnoreColor();
+}
+
+void Font::Merge( const vcl::Font& rFont )
+{
+ if ( !rFont.GetFamilyName().isEmpty() )
+ {
+ SetFamilyName( rFont.GetFamilyName() );
+ SetStyleName( rFont.GetStyleName() );
+ SetCharSet( GetCharSet() );
+ SetLanguageTag( rFont.GetLanguageTag() );
+ SetCJKContextLanguageTag( rFont.GetCJKContextLanguageTag() );
+ // don't use access methods here, might lead to AskConfig(), if DONTKNOW
+ SetFamily( rFont.mpImplFont->GetFamilyTypeNoAsk() );
+ SetPitch( rFont.mpImplFont->GetPitchNoAsk() );
+ }
+
+ // don't use access methods here, might lead to AskConfig(), if DONTKNOW
+ if ( rFont.mpImplFont->GetWeightNoAsk() != WEIGHT_DONTKNOW )
+ SetWeight( rFont.GetWeight() );
+ if ( rFont.mpImplFont->GetItalicNoAsk() != ITALIC_DONTKNOW )
+ SetItalic( rFont.GetItalic() );
+ if ( rFont.mpImplFont->GetWidthTypeNoAsk() != WIDTH_DONTKNOW )
+ SetWidthType( rFont.GetWidthType() );
+
+ if ( rFont.GetFontSize().Height() )
+ SetFontSize( rFont.GetFontSize() );
+ if ( rFont.GetUnderline() != LINESTYLE_DONTKNOW )
+ {
+ SetUnderline( rFont.GetUnderline() );
+ SetWordLineMode( rFont.IsWordLineMode() );
+ }
+ if ( rFont.GetOverline() != LINESTYLE_DONTKNOW )
+ {
+ SetOverline( rFont.GetOverline() );
+ SetWordLineMode( rFont.IsWordLineMode() );
+ }
+ if ( rFont.GetStrikeout() != STRIKEOUT_DONTKNOW )
+ {
+ SetStrikeout( rFont.GetStrikeout() );
+ SetWordLineMode( rFont.IsWordLineMode() );
+ }
+
+ // Defaults?
+ SetOrientation( rFont.GetOrientation() );
+ SetVertical( rFont.IsVertical() );
+ SetEmphasisMark( rFont.GetEmphasisMark() );
+ SetKerning( rFont.IsKerning() ? FontKerning::FontSpecific : FontKerning::NONE );
+ SetOutline( rFont.IsOutline() );
+ SetShadow( rFont.IsShadow() );
+ SetRelief( rFont.GetRelief() );
+}
+
+void Font::GetFontAttributes( FontAttributes& rAttrs ) const
+{
+ rAttrs.SetFamilyName( mpImplFont->GetFamilyName() );
+ rAttrs.SetStyleName( mpImplFont->maStyleName );
+ rAttrs.SetFamilyType( mpImplFont->GetFamilyTypeNoAsk() );
+ rAttrs.SetPitch( mpImplFont->GetPitchNoAsk() );
+ rAttrs.SetItalic( mpImplFont->GetItalicNoAsk() );
+ rAttrs.SetWeight( mpImplFont->GetWeightNoAsk() );
+ rAttrs.SetWidthType( WIDTH_DONTKNOW );
+ rAttrs.SetSymbolFlag( mpImplFont->GetCharSet() == RTL_TEXTENCODING_SYMBOL );
+}
+
+// tdf#127471 for corrections on EMF/WMF we need the AvgFontWidth in Windows-specific notation
+tools::Long Font::GetOrCalculateAverageFontWidth() const
+{
+ if(0 == mpImplFont->GetCalculatedAverageFontWidth())
+ {
+ // VirtualDevice is not thread safe
+ SolarMutexGuard aGuard;
+
+ // create unscaled copy of font (this), a VirtualDevice and set it there
+ vcl::Font aUnscaledFont(*this);
+ ScopedVclPtr<VirtualDevice> pTempVirtualDevice(VclPtr<VirtualDevice>::Create());
+ aUnscaledFont.SetAverageFontWidth(0);
+ pTempVirtualDevice->SetFont(aUnscaledFont);
+
+#ifdef _WIN32
+ // on Windows systems use FontMetric to get/create AverageFontWidth from system
+ const FontMetric aMetric(pTempVirtualDevice->GetFontMetric());
+ const_cast<Font*>(this)->mpImplFont->SetCalculatedAverageFontWidth(aMetric.GetAverageFontWidth());
+#else
+ // On non-Windows systems we need to calculate AvgFontWidth
+ // as close as possible (discussion see documentation in task),
+ // so calculate it. For discussion of method used, see task
+ // buffer measure string creation, will always use the same
+ static constexpr OUStringLiteral aMeasureString
+ = u"\u0020\u0021\u0022\u0023\u0024\u0025\u0026\u0027"
+ "\u0028\u0029\u002A\u002B\u002C\u002D\u002E\u002F"
+ "\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037"
+ "\u0038\u0039\u003A\u003B\u003C\u003D\u003E\u003F"
+ "\u0040\u0041\u0042\u0043\u0044\u0045\u0046\u0047"
+ "\u0048\u0049\u004A\u004B\u004C\u004D\u004E\u004F"
+ "\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057"
+ "\u0058\u0059\u005A\u005B\u005C\u005D\u005E\u005F"
+ "\u0060\u0061\u0062\u0063\u0064\u0065\u0066\u0067"
+ "\u0068\u0069\u006A\u006B\u006C\u006D\u006E\u006F"
+ "\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077"
+ "\u0078\u0079\u007A\u007B\u007C\u007D\u007E";
+
+ const double fAverageFontWidth(
+ pTempVirtualDevice->GetTextWidth(aMeasureString) /
+ static_cast<double>(aMeasureString.getLength()));
+ const_cast<Font*>(this)->mpImplFont->SetCalculatedAverageFontWidth(basegfx::fround(fAverageFontWidth));
+#endif
+ }
+
+ return mpImplFont->GetCalculatedAverageFontWidth();
+}
+
+SvStream& ReadImplFont( SvStream& rIStm, ImplFont& rImplFont, tools::Long& rnNormedFontScaling )
+{
+ VersionCompatRead aCompat( rIStm );
+ sal_uInt16 nTmp16(0);
+ sal_Int16 nTmps16(0);
+ bool bTmp(false);
+ sal_uInt8 nTmp8(0);
+
+ rImplFont.SetFamilyName( rIStm.ReadUniOrByteString(rIStm.GetStreamCharSet()) );
+ rImplFont.maStyleName = rIStm.ReadUniOrByteString(rIStm.GetStreamCharSet());
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readSize(rImplFont.maAverageFontSize);
+
+ static const bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ if (bFuzzing)
+ {
+ if (rImplFont.maAverageFontSize.Width() > 8192)
+ {
+ SAL_WARN("vcl.gdi", "suspicious average width of: " << rImplFont.maAverageFontSize.Width());
+ rImplFont.maAverageFontSize.setWidth(8192);
+ }
+ }
+
+ rIStm.ReadUInt16( nTmp16 ); rImplFont.SetCharSet( static_cast<rtl_TextEncoding>(nTmp16) );
+ rIStm.ReadUInt16( nTmp16 ); rImplFont.SetFamilyType( static_cast<FontFamily>(nTmp16) );
+ rIStm.ReadUInt16( nTmp16 ); rImplFont.SetPitch( static_cast<FontPitch>(nTmp16) );
+ rIStm.ReadUInt16( nTmp16 ); rImplFont.SetWeight( static_cast<FontWeight>(nTmp16) );
+ rIStm.ReadUInt16( nTmp16 ); rImplFont.meUnderline = static_cast<FontLineStyle>(nTmp16);
+ rIStm.ReadUInt16( nTmp16 ); rImplFont.meStrikeout = static_cast<FontStrikeout>(nTmp16);
+ rIStm.ReadUInt16( nTmp16 ); rImplFont.SetItalic( static_cast<FontItalic>(nTmp16) );
+ rIStm.ReadUInt16( nTmp16 ); rImplFont.maLanguageTag.reset( LanguageType(nTmp16) );
+ rIStm.ReadUInt16( nTmp16 ); rImplFont.meWidthType = static_cast<FontWidth>(nTmp16);
+
+ rIStm.ReadInt16( nTmps16 ); rImplFont.mnOrientation = Degree10(nTmps16);
+
+ rIStm.ReadCharAsBool( bTmp ); rImplFont.mbWordLine = bTmp;
+ rIStm.ReadCharAsBool( bTmp ); rImplFont.mbOutline = bTmp;
+ rIStm.ReadCharAsBool( bTmp ); rImplFont.mbShadow = bTmp;
+ rIStm.ReadUChar( nTmp8 ); rImplFont.meKerning = static_cast<FontKerning>(nTmp8);
+
+ if( aCompat.GetVersion() >= 2 )
+ {
+ rIStm.ReadUChar( nTmp8 ); rImplFont.meRelief = static_cast<FontRelief>(nTmp8);
+ rIStm.ReadUInt16( nTmp16 ); rImplFont.maCJKLanguageTag.reset( LanguageType(nTmp16) );
+ rIStm.ReadCharAsBool( bTmp ); rImplFont.mbVertical = bTmp;
+ rIStm.ReadUInt16( nTmp16 ); rImplFont.meEmphasisMark = static_cast<FontEmphasisMark>(nTmp16);
+ }
+
+ if( aCompat.GetVersion() >= 3 )
+ {
+ rIStm.ReadUInt16( nTmp16 ); rImplFont.meOverline = static_cast<FontLineStyle>(nTmp16);
+ }
+
+ // tdf#127471 read NormedFontScaling
+ if( aCompat.GetVersion() >= 4 )
+ {
+ sal_Int32 nNormedFontScaling(0);
+ rIStm.ReadInt32(nNormedFontScaling);
+ rnNormedFontScaling = nNormedFontScaling;
+ }
+
+ // Relief
+ // CJKContextLanguage
+
+ return rIStm;
+}
+
+SvStream& WriteImplFont( SvStream& rOStm, const ImplFont& rImplFont, tools::Long nNormedFontScaling )
+{
+ // tdf#127471 increase to version 4
+ VersionCompatWrite aCompat( rOStm, 4 );
+
+ TypeSerializer aSerializer(rOStm);
+ rOStm.WriteUniOrByteString( rImplFont.GetFamilyName(), rOStm.GetStreamCharSet() );
+ rOStm.WriteUniOrByteString( rImplFont.GetStyleName(), rOStm.GetStreamCharSet() );
+ aSerializer.writeSize(rImplFont.maAverageFontSize);
+
+ rOStm.WriteUInt16( GetStoreCharSet( rImplFont.GetCharSet() ) );
+ rOStm.WriteUInt16( rImplFont.GetFamilyTypeNoAsk() );
+ rOStm.WriteUInt16( rImplFont.GetPitchNoAsk() );
+ rOStm.WriteUInt16( rImplFont.GetWeightNoAsk() );
+ rOStm.WriteUInt16( rImplFont.meUnderline );
+ rOStm.WriteUInt16( rImplFont.meStrikeout );
+ rOStm.WriteUInt16( rImplFont.GetItalicNoAsk() );
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rImplFont.maLanguageTag.getLanguageType( false)) );
+ rOStm.WriteUInt16( rImplFont.GetWidthTypeNoAsk() );
+
+ rOStm.WriteInt16( rImplFont.mnOrientation.get() );
+
+ rOStm.WriteBool( rImplFont.mbWordLine );
+ rOStm.WriteBool( rImplFont.mbOutline );
+ rOStm.WriteBool( rImplFont.mbShadow );
+ rOStm.WriteUChar( static_cast<sal_uInt8>(rImplFont.meKerning) );
+
+ // new in version 2
+ rOStm.WriteUChar( static_cast<unsigned char>(rImplFont.meRelief) );
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rImplFont.maCJKLanguageTag.getLanguageType( false)) );
+ rOStm.WriteBool( rImplFont.mbVertical );
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rImplFont.meEmphasisMark) );
+
+ // new in version 3
+ rOStm.WriteUInt16( rImplFont.meOverline );
+
+ // new in version 4, NormedFontScaling
+ rOStm.WriteInt32(nNormedFontScaling);
+
+ return rOStm;
+}
+
+SvStream& ReadFont( SvStream& rIStm, vcl::Font& rFont )
+{
+ // tdf#127471 try to read NormedFontScaling
+ tools::Long nNormedFontScaling(0);
+ SvStream& rRetval(ReadImplFont( rIStm, *rFont.mpImplFont, nNormedFontScaling ));
+
+ if (nNormedFontScaling > 0)
+ {
+#ifdef _WIN32
+ // we run on windows and a NormedFontScaling was written
+ if(rFont.GetFontSize().getWidth() == nNormedFontScaling)
+ {
+ // the writing producer was running on a non-windows system, adapt to needed windows
+ // system-specific pre-multiply
+ const tools::Long nHeight(std::max<tools::Long>(rFont.GetFontSize().getHeight(), 0));
+ sal_uInt32 nScaledWidth(0);
+
+ if(nHeight > 0)
+ {
+ vcl::Font aUnscaledFont(rFont);
+ aUnscaledFont.SetAverageFontWidth(0);
+ const FontMetric aUnscaledFontMetric(Application::GetDefaultDevice()->GetFontMetric(aUnscaledFont));
+
+ if (nHeight > 0)
+ {
+ const double fScaleFactor(static_cast<double>(nNormedFontScaling) / static_cast<double>(nHeight));
+ nScaledWidth = basegfx::fround(static_cast<double>(aUnscaledFontMetric.GetAverageFontWidth()) * fScaleFactor);
+ }
+ }
+
+ rFont.SetAverageFontWidth(nScaledWidth);
+ }
+ else
+ {
+ // the writing producer was on a windows system, correct pre-multiplied value
+ // is already set, nothing to do. Ignore 2nd value. Here a check
+ // could be done if adapting the 2nd, NormedFontScaling value would be similar to
+ // the set value for plausibility reasons
+ }
+#else
+ // we do not run on windows and a NormedFontScaling was written
+ if(rFont.GetFontSize().getWidth() == nNormedFontScaling)
+ {
+ // the writing producer was not on a windows system, correct value
+ // already set, nothing to do
+ }
+ else
+ {
+ // the writing producer was on a windows system, correct FontScaling.
+ // The correct non-pre-multiplied value is the 2nd one, use it
+ rFont.SetAverageFontWidth(nNormedFontScaling);
+ }
+#endif
+ }
+
+ return rRetval;
+}
+
+SvStream& WriteFont( SvStream& rOStm, const vcl::Font& rFont )
+{
+ // tdf#127471 prepare NormedFontScaling for additional export
+ tools::Long nNormedFontScaling(rFont.GetFontSize().getWidth());
+
+ // FontScaling usage at vcl-Font is detected by checking that FontWidth != 0
+ if (nNormedFontScaling > 0)
+ {
+ const tools::Long nHeight(std::max<tools::Long>(rFont.GetFontSize().getHeight(), 0));
+
+ // check for negative height
+ if(0 == nHeight)
+ {
+ nNormedFontScaling = 0;
+ }
+ else
+ {
+#ifdef _WIN32
+ // for WIN32 the value is pre-multiplied with AverageFontWidth
+ // which makes it system-dependent. Turn that back to have the
+ // normed non-windows form of it for export as 2nd value
+ vcl::Font aUnscaledFont(rFont);
+ aUnscaledFont.SetAverageFontWidth(0);
+ const FontMetric aUnscaledFontMetric(
+ Application::GetDefaultDevice()->GetFontMetric(aUnscaledFont));
+
+ if (aUnscaledFontMetric.GetAverageFontWidth() > 0)
+ {
+ const double fScaleFactor(
+ static_cast<double>(nNormedFontScaling)
+ / static_cast<double>(aUnscaledFontMetric.GetAverageFontWidth()));
+ nNormedFontScaling = static_cast<tools::Long>(fScaleFactor * nHeight);
+ }
+#endif
+ }
+ }
+
+ return WriteImplFont( rOStm, *rFont.mpImplFont, nNormedFontScaling );
+}
+
+namespace
+{
+ bool identifyTrueTypeFont( const void* i_pBuffer, sal_uInt32 i_nSize, Font& o_rResult )
+ {
+ bool bResult = false;
+ TrueTypeFont* pTTF = nullptr;
+ if( OpenTTFontBuffer( i_pBuffer, i_nSize, 0, &pTTF ) == SFErrCodes::Ok )
+ {
+ TTGlobalFontInfo aInfo;
+ GetTTGlobalFontInfo( pTTF, &aInfo );
+ // most importantly: the family name
+ if( aInfo.ufamily )
+ o_rResult.SetFamilyName( OUString(aInfo.ufamily) );
+ else if( aInfo.family )
+ o_rResult.SetFamilyName( OStringToOUString( aInfo.family, RTL_TEXTENCODING_ASCII_US ) );
+ // set weight
+ if( aInfo.weight )
+ {
+ if( aInfo.weight < FW_EXTRALIGHT )
+ o_rResult.SetWeight( WEIGHT_THIN );
+ else if( aInfo.weight < FW_LIGHT )
+ o_rResult.SetWeight( WEIGHT_ULTRALIGHT );
+ else if( aInfo.weight < FW_NORMAL )
+ o_rResult.SetWeight( WEIGHT_LIGHT );
+ else if( aInfo.weight < FW_MEDIUM )
+ o_rResult.SetWeight( WEIGHT_NORMAL );
+ else if( aInfo.weight < FW_SEMIBOLD )
+ o_rResult.SetWeight( WEIGHT_MEDIUM );
+ else if( aInfo.weight < FW_BOLD )
+ o_rResult.SetWeight( WEIGHT_SEMIBOLD );
+ else if( aInfo.weight < FW_EXTRABOLD )
+ o_rResult.SetWeight( WEIGHT_BOLD );
+ else if( aInfo.weight < FW_BLACK )
+ o_rResult.SetWeight( WEIGHT_ULTRABOLD );
+ else
+ o_rResult.SetWeight( WEIGHT_BLACK );
+ }
+ else
+ o_rResult.SetWeight( (aInfo.macStyle & 1) ? WEIGHT_BOLD : WEIGHT_NORMAL );
+ // set width
+ if( aInfo.width )
+ {
+ if( aInfo.width == FWIDTH_ULTRA_CONDENSED )
+ o_rResult.SetAverageFontWidth( WIDTH_ULTRA_CONDENSED );
+ else if( aInfo.width == FWIDTH_EXTRA_CONDENSED )
+ o_rResult.SetAverageFontWidth( WIDTH_EXTRA_CONDENSED );
+ else if( aInfo.width == FWIDTH_CONDENSED )
+ o_rResult.SetAverageFontWidth( WIDTH_CONDENSED );
+ else if( aInfo.width == FWIDTH_SEMI_CONDENSED )
+ o_rResult.SetAverageFontWidth( WIDTH_SEMI_CONDENSED );
+ else if( aInfo.width == FWIDTH_NORMAL )
+ o_rResult.SetAverageFontWidth( WIDTH_NORMAL );
+ else if( aInfo.width == FWIDTH_SEMI_EXPANDED )
+ o_rResult.SetAverageFontWidth( WIDTH_SEMI_EXPANDED );
+ else if( aInfo.width == FWIDTH_EXPANDED )
+ o_rResult.SetAverageFontWidth( WIDTH_EXPANDED );
+ else if( aInfo.width == FWIDTH_EXTRA_EXPANDED )
+ o_rResult.SetAverageFontWidth( WIDTH_EXTRA_EXPANDED );
+ else if( aInfo.width >= FWIDTH_ULTRA_EXPANDED )
+ o_rResult.SetAverageFontWidth( WIDTH_ULTRA_EXPANDED );
+ }
+ // set italic
+ o_rResult.SetItalic( (aInfo.italicAngle != 0) ? ITALIC_NORMAL : ITALIC_NONE );
+
+ // set pitch
+ o_rResult.SetPitch( (aInfo.pitch == 0) ? PITCH_VARIABLE : PITCH_FIXED );
+
+ // set style name
+ if( aInfo.usubfamily )
+ o_rResult.SetStyleName( OUString( aInfo.usubfamily ) );
+ else if( aInfo.subfamily )
+ o_rResult.SetStyleName( OUString::createFromAscii( aInfo.subfamily ) );
+
+ // cleanup
+ CloseTTFont( pTTF );
+ // success
+ bResult = true;
+ }
+ return bResult;
+ }
+
+ struct WeightSearchEntry
+ {
+ const char* string;
+ int string_len;
+ FontWeight weight;
+
+ bool operator<( const WeightSearchEntry& rRight ) const
+ {
+ return rtl_str_compareIgnoreAsciiCase_WithLength( string, string_len, rRight.string, rRight.string_len ) < 0;
+ }
+ }
+ const weight_table[] =
+ {
+ { "black", 5, WEIGHT_BLACK },
+ { "bold", 4, WEIGHT_BOLD },
+ { "book", 4, WEIGHT_LIGHT },
+ { "demi", 4, WEIGHT_SEMIBOLD },
+ { "heavy", 5, WEIGHT_BLACK },
+ { "light", 5, WEIGHT_LIGHT },
+ { "medium", 6, WEIGHT_MEDIUM },
+ { "regular", 7, WEIGHT_NORMAL },
+ { "super", 5, WEIGHT_ULTRABOLD },
+ { "thin", 4, WEIGHT_THIN }
+ };
+
+ bool identifyType1Font( const char* i_pBuffer, sal_uInt32 i_nSize, Font& o_rResult )
+ {
+ // might be a type1, find eexec
+ const char* pStream = i_pBuffer;
+ const char* const pExec = "eexec";
+ const char* pExecPos = std::search( pStream, pStream+i_nSize, pExec, pExec+5 );
+ if( pExecPos != pStream+i_nSize)
+ {
+ // find /FamilyName entry
+ static const char* const pFam = "/FamilyName";
+ const char* pFamPos = std::search( pStream, pExecPos, pFam, pFam+11 );
+ if( pFamPos != pExecPos )
+ {
+ // extract the string value behind /FamilyName
+ const char* pOpen = pFamPos+11;
+ while( pOpen < pExecPos && *pOpen != '(' )
+ pOpen++;
+ const char* pClose = pOpen;
+ while( pClose < pExecPos && *pClose != ')' )
+ pClose++;
+ if( pClose - pOpen > 1 )
+ {
+ o_rResult.SetFamilyName( OStringToOUString( std::string_view( pOpen+1, pClose-pOpen-1 ), RTL_TEXTENCODING_ASCII_US ) );
+ }
+ }
+
+ // parse /ItalicAngle
+ static const char* const pItalic = "/ItalicAngle";
+ const char* pItalicPos = std::search( pStream, pExecPos, pItalic, pItalic+12 );
+ if( pItalicPos != pExecPos )
+ {
+ const char* pItalicEnd = pItalicPos + 12;
+ auto nItalic = rtl_str_toInt64_WithLength(pItalicEnd, 10, pExecPos - pItalicEnd);
+ o_rResult.SetItalic( (nItalic != 0) ? ITALIC_NORMAL : ITALIC_NONE );
+ }
+
+ // parse /Weight
+ static const char* const pWeight = "/Weight";
+ const char* pWeightPos = std::search( pStream, pExecPos, pWeight, pWeight+7 );
+ if( pWeightPos != pExecPos )
+ {
+ // extract the string value behind /Weight
+ const char* pOpen = pWeightPos+7;
+ while( pOpen < pExecPos && *pOpen != '(' )
+ pOpen++;
+ const char* pClose = pOpen;
+ while( pClose < pExecPos && *pClose != ')' )
+ pClose++;
+ if( pClose - pOpen > 1 )
+ {
+ WeightSearchEntry aEnt;
+ aEnt.string = pOpen+1;
+ aEnt.string_len = (pClose-pOpen)-1;
+ aEnt.weight = WEIGHT_NORMAL;
+ WeightSearchEntry const * pFound = std::lower_bound( std::begin(weight_table), std::end(weight_table), aEnt );
+ if( pFound != std::end(weight_table) &&
+ rtl_str_compareIgnoreAsciiCase_WithLength( pFound->string, pFound->string_len, aEnt.string, aEnt.string_len) == 0 )
+ o_rResult.SetWeight( pFound->weight );
+ }
+ }
+
+ // parse isFixedPitch
+ static const char* const pFixed = "/isFixedPitch";
+ const char* pFixedPos = std::search( pStream, pExecPos, pFixed, pFixed+13 );
+ if( pFixedPos != pExecPos )
+ {
+ // skip whitespace
+ while( pFixedPos < pExecPos-4 &&
+ ( *pFixedPos == ' ' ||
+ *pFixedPos == '\t' ||
+ *pFixedPos == '\r' ||
+ *pFixedPos == '\n' ) )
+ {
+ pFixedPos++;
+ }
+ // find "true" value
+ if( rtl_str_compareIgnoreAsciiCase_WithLength( pFixedPos, 4, "true", 4 ) == 0 )
+ o_rResult.SetPitch( PITCH_FIXED );
+ else
+ o_rResult.SetPitch( PITCH_VARIABLE );
+ }
+ }
+ return false;
+ }
+}
+
+Font Font::identifyFont( const void* i_pBuffer, sal_uInt32 i_nSize )
+{
+ Font aResult;
+ if( ! identifyTrueTypeFont( i_pBuffer, i_nSize, aResult ) )
+ {
+ const char* pStream = static_cast<const char*>(i_pBuffer);
+ if( pStream && i_nSize > 100 &&
+ *pStream == '%' && pStream[1] == '!' )
+ {
+ identifyType1Font( pStream, i_nSize, aResult );
+ }
+ }
+
+ return aResult;
+}
+
+// The inlines from the font.hxx header are now instantiated for pImpl-ification
+const Color& Font::GetColor() const { return mpImplFont->maColor; }
+const Color& Font::GetFillColor() const { return mpImplFont->maFillColor; }
+bool Font::IsTransparent() const { return mpImplFont->mbTransparent; }
+
+TextAlign Font::GetAlignment() const { return mpImplFont->GetAlignment(); }
+
+const OUString& Font::GetFamilyName() const { return mpImplFont->GetFamilyName(); }
+const OUString& Font::GetStyleName() const { return mpImplFont->maStyleName; }
+
+const Size& Font::GetFontSize() const { return mpImplFont->GetFontSize(); }
+void Font::SetFontHeight( tools::Long nHeight ) { SetFontSize( Size( std::as_const(mpImplFont)->GetFontSize().Width(), nHeight ) ); }
+tools::Long Font::GetFontHeight() const { return mpImplFont->GetFontSize().Height(); }
+void Font::SetAverageFontWidth( tools::Long nWidth ) { SetFontSize( Size( nWidth, std::as_const(mpImplFont)->GetFontSize().Height() ) ); }
+tools::Long Font::GetAverageFontWidth() const { return mpImplFont->GetFontSize().Width(); }
+
+rtl_TextEncoding Font::GetCharSet() const { return mpImplFont->GetCharSet(); }
+
+const LanguageTag& Font::GetLanguageTag() const { return mpImplFont->maLanguageTag; }
+const LanguageTag& Font::GetCJKContextLanguageTag() const { return mpImplFont->maCJKLanguageTag; }
+LanguageType Font::GetLanguage() const { return mpImplFont->maLanguageTag.getLanguageType( false); }
+LanguageType Font::GetCJKContextLanguage() const { return mpImplFont->maCJKLanguageTag.getLanguageType( false); }
+
+Degree10 Font::GetOrientation() const { return mpImplFont->mnOrientation; }
+bool Font::IsVertical() const { return mpImplFont->mbVertical; }
+FontKerning Font::GetKerning() const { return mpImplFont->meKerning; }
+
+FontPitch Font::GetPitch() { return mpImplFont->GetPitch(); }
+FontWeight Font::GetWeight() { return mpImplFont->GetWeight(); }
+FontWidth Font::GetWidthType() { return mpImplFont->GetWidthType(); }
+FontItalic Font::GetItalic() { return mpImplFont->GetItalic(); }
+FontFamily Font::GetFamilyType() { return mpImplFont->GetFamilyType(); }
+
+FontPitch Font::GetPitch() const { return mpImplFont->GetPitchNoAsk(); }
+FontWeight Font::GetWeight() const { return mpImplFont->GetWeightNoAsk(); }
+FontWidth Font::GetWidthType() const { return mpImplFont->GetWidthTypeNoAsk(); }
+FontItalic Font::GetItalic() const { return mpImplFont->GetItalicNoAsk(); }
+FontFamily Font::GetFamilyType() const { return mpImplFont->GetFamilyTypeNoAsk(); }
+
+int Font::GetQuality() const { return mpImplFont->GetQuality(); }
+void Font::SetQuality( int nQuality ) { mpImplFont->SetQuality( nQuality ); }
+void Font::IncreaseQualityBy( int nQualityAmount ) { mpImplFont->IncreaseQualityBy( nQualityAmount ); }
+void Font::DecreaseQualityBy( int nQualityAmount ) { mpImplFont->DecreaseQualityBy( nQualityAmount ); }
+
+bool Font::IsOutline() const { return mpImplFont->mbOutline; }
+bool Font::IsShadow() const { return mpImplFont->mbShadow; }
+FontRelief Font::GetRelief() const { return mpImplFont->meRelief; }
+FontLineStyle Font::GetUnderline() const { return mpImplFont->meUnderline; }
+FontLineStyle Font::GetOverline() const { return mpImplFont->meOverline; }
+FontStrikeout Font::GetStrikeout() const { return mpImplFont->meStrikeout; }
+FontEmphasisMark Font::GetEmphasisMark() const { return mpImplFont->meEmphasisMark; }
+bool Font::IsWordLineMode() const { return mpImplFont->mbWordLine; }
+bool Font::IsSameInstance( const vcl::Font& rFont ) const { return (mpImplFont == rFont.mpImplFont); }
+
+
+ImplFont::ImplFont() :
+ meWeight( WEIGHT_DONTKNOW ),
+ meFamily( FAMILY_DONTKNOW ),
+ mePitch( PITCH_DONTKNOW ),
+ meWidthType( WIDTH_DONTKNOW ),
+ meItalic( ITALIC_NONE ),
+ meAlign( ALIGN_TOP ),
+ meUnderline( LINESTYLE_NONE ),
+ meOverline( LINESTYLE_NONE ),
+ meStrikeout( STRIKEOUT_NONE ),
+ meRelief( FontRelief::NONE ),
+ meEmphasisMark( FontEmphasisMark::NONE ),
+ meKerning( FontKerning::FontSpecific ),
+ meCharSet( RTL_TEXTENCODING_DONTKNOW ),
+ maLanguageTag( LANGUAGE_DONTKNOW ),
+ maCJKLanguageTag( LANGUAGE_DONTKNOW ),
+ mbSymbolFlag( false ),
+ mbOutline( false ),
+ mbConfigLookup( false ),
+ mbShadow( false ),
+ mbVertical( false ),
+ mbTransparent( true ),
+ maColor( COL_TRANSPARENT ),
+ maFillColor( COL_TRANSPARENT ),
+ mbWordLine( false ),
+ mnOrientation( 0 ),
+ mnQuality( 0 ),
+ mnCalculatedAverageFontWidth( 0 )
+{}
+
+ImplFont::ImplFont( const ImplFont& rImplFont ) :
+ maFamilyName( rImplFont.maFamilyName ),
+ maStyleName( rImplFont.maStyleName ),
+ meWeight( rImplFont.meWeight ),
+ meFamily( rImplFont.meFamily ),
+ mePitch( rImplFont.mePitch ),
+ meWidthType( rImplFont.meWidthType ),
+ meItalic( rImplFont.meItalic ),
+ meAlign( rImplFont.meAlign ),
+ meUnderline( rImplFont.meUnderline ),
+ meOverline( rImplFont.meOverline ),
+ meStrikeout( rImplFont.meStrikeout ),
+ meRelief( rImplFont.meRelief ),
+ meEmphasisMark( rImplFont.meEmphasisMark ),
+ meKerning( rImplFont.meKerning ),
+ maAverageFontSize( rImplFont.maAverageFontSize ),
+ meCharSet( rImplFont.meCharSet ),
+ maLanguageTag( rImplFont.maLanguageTag ),
+ maCJKLanguageTag( rImplFont.maCJKLanguageTag ),
+ mbSymbolFlag( rImplFont.mbSymbolFlag ),
+ mbOutline( rImplFont.mbOutline ),
+ mbConfigLookup( rImplFont.mbConfigLookup ),
+ mbShadow( rImplFont.mbShadow ),
+ mbVertical( rImplFont.mbVertical ),
+ mbTransparent( rImplFont.mbTransparent ),
+ maColor( rImplFont.maColor ),
+ maFillColor( rImplFont.maFillColor ),
+ mbWordLine( rImplFont.mbWordLine ),
+ mnOrientation( rImplFont.mnOrientation ),
+ mnQuality( rImplFont.mnQuality ),
+ mnCalculatedAverageFontWidth( rImplFont.mnCalculatedAverageFontWidth )
+{}
+
+bool ImplFont::operator==( const ImplFont& rOther ) const
+{
+ if(!EqualIgnoreColor( rOther ))
+ return false;
+
+ if( (maColor != rOther.maColor)
+ || (maFillColor != rOther.maFillColor) )
+ return false;
+
+ return true;
+}
+
+bool ImplFont::EqualIgnoreColor( const ImplFont& rOther ) const
+{
+ // equality tests split up for easier debugging
+ if( (meWeight != rOther.meWeight)
+ || (meItalic != rOther.meItalic)
+ || (meFamily != rOther.meFamily)
+ || (mePitch != rOther.mePitch) )
+ return false;
+
+ if( (meCharSet != rOther.meCharSet)
+ || (maLanguageTag != rOther.maLanguageTag)
+ || (maCJKLanguageTag != rOther.maCJKLanguageTag)
+ || (meAlign != rOther.meAlign) )
+ return false;
+
+ if( (maAverageFontSize != rOther.maAverageFontSize)
+ || (mnOrientation != rOther.mnOrientation)
+ || (mbVertical != rOther.mbVertical) )
+ return false;
+
+ if( (maFamilyName != rOther.maFamilyName)
+ || (maStyleName != rOther.maStyleName) )
+ return false;
+
+ if( (meUnderline != rOther.meUnderline)
+ || (meOverline != rOther.meOverline)
+ || (meStrikeout != rOther.meStrikeout)
+ || (meRelief != rOther.meRelief)
+ || (meEmphasisMark != rOther.meEmphasisMark)
+ || (mbWordLine != rOther.mbWordLine)
+ || (mbOutline != rOther.mbOutline)
+ || (mbShadow != rOther.mbShadow)
+ || (meKerning != rOther.meKerning)
+ || (mbTransparent != rOther.mbTransparent) )
+ return false;
+
+ return true;
+}
+
+size_t ImplFont::GetHashValue() const
+{
+ size_t hash = GetHashValueIgnoreColor();
+ o3tl::hash_combine( hash, static_cast<sal_uInt32>( maColor ));
+ o3tl::hash_combine( hash, static_cast<sal_uInt32>( maFillColor ));
+ return hash;
+}
+
+size_t ImplFont::GetHashValueIgnoreColor() const
+{
+ size_t hash = 0;
+
+ o3tl::hash_combine( hash, meWeight );
+ o3tl::hash_combine( hash, meItalic );
+ o3tl::hash_combine( hash, meFamily );
+ o3tl::hash_combine( hash, mePitch );
+
+ o3tl::hash_combine( hash, meCharSet );
+ o3tl::hash_combine( hash, maLanguageTag.getLanguageType( false ).get());
+ o3tl::hash_combine( hash, maCJKLanguageTag.getLanguageType( false ).get());
+ o3tl::hash_combine( hash, meAlign );
+
+ o3tl::hash_combine( hash, maAverageFontSize.GetHashValue());
+ o3tl::hash_combine( hash, mnOrientation.get());
+ o3tl::hash_combine( hash, mbVertical );
+
+ o3tl::hash_combine( hash, maFamilyName );
+ o3tl::hash_combine( hash, maStyleName );
+
+ o3tl::hash_combine( hash, meUnderline );
+ o3tl::hash_combine( hash, meOverline );
+ o3tl::hash_combine( hash, meStrikeout );
+ o3tl::hash_combine( hash, meRelief );
+ o3tl::hash_combine( hash, meEmphasisMark );
+ o3tl::hash_combine( hash, mbWordLine );
+ o3tl::hash_combine( hash, mbOutline );
+ o3tl::hash_combine( hash, mbShadow );
+ o3tl::hash_combine( hash, meKerning );
+ o3tl::hash_combine( hash, mbTransparent );
+
+ return hash;
+}
+
+void ImplFont::AskConfig()
+{
+ if( mbConfigLookup )
+ return;
+
+ mbConfigLookup = true;
+
+ // prepare the FontSubst configuration lookup
+ const utl::FontSubstConfiguration& rFontSubst = utl::FontSubstConfiguration::get();
+
+ OUString aShortName;
+ OUString aFamilyName;
+ ImplFontAttrs nType = ImplFontAttrs::None;
+ FontWeight eWeight = WEIGHT_DONTKNOW;
+ FontWidth eWidthType = WIDTH_DONTKNOW;
+ OUString aMapName = GetEnglishSearchFontName( maFamilyName );
+
+ utl::FontSubstConfiguration::getMapName( aMapName,
+ aShortName, aFamilyName, eWeight, eWidthType, nType );
+
+ // lookup the font name in the configuration
+ const utl::FontNameAttr* pFontAttr = rFontSubst.getSubstInfo( aMapName );
+
+ // if the direct lookup failed try again with an alias name
+ if ( !pFontAttr && (aShortName != aMapName) )
+ pFontAttr = rFontSubst.getSubstInfo( aShortName );
+
+ if( pFontAttr )
+ {
+ // the font was found in the configuration
+ if( meFamily == FAMILY_DONTKNOW )
+ {
+ if ( pFontAttr->Type & ImplFontAttrs::Serif )
+ meFamily = FAMILY_ROMAN;
+ else if ( pFontAttr->Type & ImplFontAttrs::SansSerif )
+ meFamily = FAMILY_SWISS;
+ else if ( pFontAttr->Type & ImplFontAttrs::Typewriter )
+ meFamily = FAMILY_MODERN;
+ else if ( pFontAttr->Type & ImplFontAttrs::Italic )
+ meFamily = FAMILY_SCRIPT;
+ else if ( pFontAttr->Type & ImplFontAttrs::Decorative )
+ meFamily = FAMILY_DECORATIVE;
+ }
+
+ if( mePitch == PITCH_DONTKNOW )
+ {
+ if ( pFontAttr->Type & ImplFontAttrs::Fixed )
+ mePitch = PITCH_FIXED;
+ }
+ }
+
+ // if some attributes are still unknown then use the FontSubst magic
+ if( meFamily == FAMILY_DONTKNOW )
+ {
+ if( nType & ImplFontAttrs::Serif )
+ meFamily = FAMILY_ROMAN;
+ else if( nType & ImplFontAttrs::SansSerif )
+ meFamily = FAMILY_SWISS;
+ else if( nType & ImplFontAttrs::Typewriter )
+ meFamily = FAMILY_MODERN;
+ else if( nType & ImplFontAttrs::Italic )
+ meFamily = FAMILY_SCRIPT;
+ else if( nType & ImplFontAttrs::Decorative )
+ meFamily = FAMILY_DECORATIVE;
+ }
+
+ if( GetWeight() == WEIGHT_DONTKNOW )
+ SetWeight( eWeight );
+ if( meWidthType == WIDTH_DONTKNOW )
+ meWidthType = eWidthType;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/fontattributes.cxx b/vcl/source/font/fontattributes.cxx
new file mode 100644
index 000000000..20e8e1b65
--- /dev/null
+++ b/vcl/source/font/fontattributes.cxx
@@ -0,0 +1,62 @@
+/* -*- 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 <fontattributes.hxx>
+
+FontAttributes::FontAttributes()
+: meWeight( WEIGHT_DONTKNOW ),
+ meFamily( FAMILY_DONTKNOW ),
+ mePitch( PITCH_DONTKNOW ),
+ meWidthType ( WIDTH_DONTKNOW ),
+ meItalic ( ITALIC_NONE ),
+ meCharSet( RTL_TEXTENCODING_DONTKNOW ),
+ mbSymbolFlag( false ),
+ mnQuality( 0 )
+{}
+
+bool FontAttributes::CompareDeviceIndependentFontAttributes(const FontAttributes& rOther) const
+{
+ if (maFamilyName != rOther.maFamilyName)
+ return false;
+
+ if (maStyleName != rOther.maStyleName)
+ return false;
+
+ if (meWeight != rOther.meWeight)
+ return false;
+
+ if (meItalic != rOther.meItalic)
+ return false;
+
+ if (meFamily != rOther.meFamily)
+ return false;
+
+ if (mePitch != rOther.mePitch)
+ return false;
+
+ if (meWidthType != rOther.meWidthType)
+ return false;
+
+ if (mbSymbolFlag != rOther.mbSymbolFlag)
+ return false;
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/fontcache.cxx b/vcl/source/font/fontcache.cxx
new file mode 100644
index 000000000..9a87d02bc
--- /dev/null
+++ b/vcl/source/font/fontcache.cxx
@@ -0,0 +1,279 @@
+/* -*- 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 <sal/log.hxx>
+
+#include <font/PhysicalFontCollection.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <font/PhysicalFontFamily.hxx>
+#include <fontinstance.hxx>
+#include <impfontcache.hxx>
+
+using namespace vcl::font;
+
+size_t ImplFontCache::IFSD_Hash::operator()( const vcl::font::FontSelectPattern& rFSD ) const
+{
+ return rFSD.hashCode();
+}
+
+bool ImplFontCache::IFSD_Equal::operator()(const vcl::font::FontSelectPattern& rA, const vcl::font::FontSelectPattern& rB) const
+{
+ // check normalized font family name
+ if( rA.maSearchName != rB.maSearchName )
+ return false;
+
+ // check font transformation
+ if( (rA.mnHeight != rB.mnHeight)
+ || (rA.mnWidth != rB.mnWidth)
+ || (rA.mnOrientation != rB.mnOrientation) )
+ return false;
+
+ // check mapping relevant attributes
+ if( (rA.mbVertical != rB.mbVertical)
+ || (rA.meLanguage != rB.meLanguage) )
+ return false;
+
+ // check font face attributes
+ if( (rA.GetWeight() != rB.GetWeight())
+ || (rA.GetItalic() != rB.GetItalic())
+// || (rA.meFamily != rB.meFamily) // TODO: remove this mostly obsolete member
+ || (rA.GetPitch() != rB.GetPitch()) )
+ return false;
+
+ // check style name
+ if( rA.GetStyleName() != rB.GetStyleName() )
+ return false;
+
+ // Symbol fonts may recode from one type to another So they are only
+ // safely equivalent for equal targets
+ if (rA.IsSymbolFont() || rB.IsSymbolFont())
+ {
+ if (rA.maTargetName != rB.maTargetName)
+ return false;
+ }
+
+ // check for features
+ if ((rA.maTargetName.indexOf(vcl::font::FontSelectPattern::FEAT_PREFIX)
+ != -1 ||
+ rB.maTargetName.indexOf(vcl::font::FontSelectPattern::FEAT_PREFIX)
+ != -1) && rA.maTargetName != rB.maTargetName)
+ return false;
+
+ if (rA.mbEmbolden != rB.mbEmbolden)
+ return false;
+
+ if (rA.maItalicMatrix != rB.maItalicMatrix)
+ return false;
+
+ return true;
+}
+
+ImplFontCache::ImplFontCache()
+ : mpLastHitCacheEntry( nullptr )
+ , maFontInstanceList(std::numeric_limits<size_t>::max()) // "unlimited", i.e. no cleanup
+ // The cache limit is set by the rough number of characters needed to read your average Asian newspaper.
+ , m_aBoundRectCache(3000)
+{}
+
+ImplFontCache::~ImplFontCache()
+{
+ for (const auto & rLFI : maFontInstanceList)
+ {
+ rLFI.second->mpFontCache = nullptr;
+ }
+}
+
+rtl::Reference<LogicalFontInstance> ImplFontCache::GetFontInstance( PhysicalFontCollection const * pFontList,
+ const vcl::Font& rFont, const Size& rSize, float fExactHeight, bool bNonAntialias )
+{
+ // initialize internal font request object
+ vcl::font::FontSelectPattern aFontSelData(rFont, rFont.GetFamilyName(), rSize, fExactHeight, bNonAntialias);
+ return GetFontInstance( pFontList, aFontSelData );
+}
+
+rtl::Reference<LogicalFontInstance> ImplFontCache::GetFontInstance( PhysicalFontCollection const * pFontList,
+ vcl::font::FontSelectPattern& aFontSelData )
+{
+ rtl::Reference<LogicalFontInstance> pFontInstance;
+ PhysicalFontFamily* pFontFamily = nullptr;
+
+ // check if a directly matching logical font instance is already cached,
+ // the most recently used font usually has a hit rate of >50%
+ if (mpLastHitCacheEntry && IFSD_Equal()(aFontSelData, mpLastHitCacheEntry->GetFontSelectPattern()))
+ pFontInstance = mpLastHitCacheEntry;
+ else
+ {
+ FontInstanceList::const_iterator it = maFontInstanceList.find( aFontSelData );
+ if( it != maFontInstanceList.end() )
+ pFontInstance = (*it).second;
+ }
+
+ if( !pFontInstance ) // no direct cache hit
+ {
+ // find the best matching logical font family and update font selector accordingly
+ pFontFamily = pFontList->FindFontFamily( aFontSelData );
+ SAL_WARN_IF( (pFontFamily == nullptr), "vcl", "ImplFontCache::Get() No logical font found!" );
+ if( pFontFamily )
+ {
+ aFontSelData.maSearchName = pFontFamily->GetSearchName();
+
+ // check if an indirectly matching logical font instance is already cached
+ FontInstanceList::const_iterator it = maFontInstanceList.find( aFontSelData );
+ if( it != maFontInstanceList.end() )
+ pFontInstance = (*it).second;
+ }
+ }
+
+ if( !pFontInstance && pFontFamily) // still no cache hit => create a new font instance
+ {
+ vcl::font::PhysicalFontFace* pFontData = pFontFamily->FindBestFontFace(aFontSelData);
+
+ // create a new logical font instance from this physical font face
+ pFontInstance = pFontData->CreateFontInstance( aFontSelData );
+ pFontInstance->mpFontCache = this;
+
+ // if we're substituting from or to a symbol font we may need a symbol
+ // conversion table
+ if( pFontData->IsSymbolFont() || aFontSelData.IsSymbolFont() )
+ {
+ if( aFontSelData.maTargetName != aFontSelData.maSearchName )
+ pFontInstance->mpConversion = ConvertChar::GetRecodeData( aFontSelData.maTargetName, aFontSelData.maSearchName );
+ }
+
+#ifdef MACOSX
+ //It might be better to dig out the font version of the target font
+ //to see if it's a modern re-coded apple symbol font in case that
+ //font shows up on a different platform
+ if (!pFontInstance->mpConversion &&
+ aFontSelData.maTargetName.equalsIgnoreAsciiCase("symbol") &&
+ aFontSelData.maSearchName.equalsIgnoreAsciiCase("symbol"))
+ {
+ pFontInstance->mpConversion = ConvertChar::GetRecodeData( u"Symbol", u"AppleSymbol" );
+ }
+#endif
+
+ static const size_t FONTCACHE_MAX = getenv("LO_TESTNAME") ? 1 : 50;
+
+ if (maFontInstanceList.size() >= FONTCACHE_MAX)
+ {
+ struct limit_exception : public std::exception {};
+ try
+ {
+ maFontInstanceList.remove_if([this] (FontInstanceList::key_value_pair_t const& rFontPair)
+ {
+ if (maFontInstanceList.size() < FONTCACHE_MAX)
+ throw limit_exception();
+ LogicalFontInstance* pFontEntry = rFontPair.second.get();
+ if (pFontEntry->m_nCount > 1)
+ return false;
+ m_aBoundRectCache.remove_if([&pFontEntry] (GlyphBoundRectCache::key_value_pair_t const& rGlyphPair)
+ { return rGlyphPair.first.m_pFont == pFontEntry; });
+ if (mpLastHitCacheEntry == pFontEntry)
+ mpLastHitCacheEntry = nullptr;
+ return true;
+ });
+ }
+ catch (limit_exception&) {}
+ }
+
+ assert(pFontInstance);
+ // add the new entry to the cache
+ maFontInstanceList.insert({aFontSelData, pFontInstance.get()});
+ }
+
+ mpLastHitCacheEntry = pFontInstance.get();
+ return pFontInstance;
+}
+
+rtl::Reference<LogicalFontInstance> ImplFontCache::GetGlyphFallbackFont( PhysicalFontCollection const * pFontCollection,
+ vcl::font::FontSelectPattern& rFontSelData, LogicalFontInstance* pFontInstance, int nFallbackLevel, OUString& rMissingCodes )
+{
+ // get a candidate font for glyph fallback
+ // unless the previously selected font got a device specific substitution
+ // e.g. PsPrint Arial->Helvetica for udiaeresis when Helvetica doesn't support it
+ if( nFallbackLevel >= 1)
+ {
+ PhysicalFontFamily* pFallbackData = nullptr;
+
+ //fdo#33898 If someone has EUDC installed then they really want that to
+ //be used as the first-choice glyph fallback seeing as it's filled with
+ //private area codes with don't make any sense in any other font so
+ //prioritize it here if it's available. Ideally we would remove from
+ //rMissingCodes all the glyphs which it is able to resolve as an
+ //optimization, but that's tricky to achieve cross-platform without
+ //sufficient heavy-weight code that's likely to undo the value of the
+ //optimization
+ if (nFallbackLevel == 1)
+ pFallbackData = pFontCollection->FindFontFamily(u"EUDC");
+ if (!pFallbackData)
+ pFallbackData = pFontCollection->GetGlyphFallbackFont(rFontSelData, pFontInstance, rMissingCodes, nFallbackLevel-1);
+ // escape when there are no font candidates
+ if( !pFallbackData )
+ return nullptr;
+ // override the font name
+ rFontSelData.SetFamilyName( pFallbackData->GetFamilyName() );
+ // clear the cached normalized name
+ rFontSelData.maSearchName.clear();
+ }
+
+ rtl::Reference<LogicalFontInstance> pFallbackFont = GetFontInstance( pFontCollection, rFontSelData );
+ return pFallbackFont;
+}
+
+void ImplFontCache::Invalidate()
+{
+ // #112304# make sure the font cache is really clean
+ mpLastHitCacheEntry = nullptr;
+ for (auto const & pair : maFontInstanceList)
+ pair.second->mpFontCache = nullptr;
+ maFontInstanceList.clear();
+ m_aBoundRectCache.clear();
+}
+
+bool ImplFontCache::GetCachedGlyphBoundRect(const LogicalFontInstance *pFont, sal_GlyphId nID, tools::Rectangle &rRect)
+{
+ if (!pFont->GetFontCache())
+ return false;
+ assert(pFont->GetFontCache() == this);
+ if (pFont->GetFontCache() != this)
+ return false;
+
+ auto it = m_aBoundRectCache.find({pFont, nID});
+ if (it != m_aBoundRectCache.end())
+ {
+ rRect = it->second;
+ return true;
+ }
+ return false;
+}
+
+void ImplFontCache::CacheGlyphBoundRect(const LogicalFontInstance *pFont, sal_GlyphId nID, tools::Rectangle &rRect)
+{
+ if (!pFont->GetFontCache())
+ return;
+ assert(pFont->GetFontCache() == this);
+ if (pFont->GetFontCache() != this)
+ return;
+
+ m_aBoundRectCache.insert({{pFont, nID}, rRect});
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/fontcharmap.cxx b/vcl/source/font/fontcharmap.cxx
new file mode 100644
index 000000000..7ca3e56a2
--- /dev/null
+++ b/vcl/source/font/fontcharmap.cxx
@@ -0,0 +1,639 @@
+/*
+ * 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 <vcl/fontcharmap.hxx>
+#include <impfontcharmap.hxx>
+#include <rtl/textcvt.h>
+#include <rtl/textenc.h>
+#include <sal/log.hxx>
+
+#include <algorithm>
+#include <vector>
+#include <o3tl/sorted_vector.hxx>
+
+CmapResult::CmapResult( bool bSymbolic,
+ const sal_UCS4* pRangeCodes, int nRangeCount )
+: mpRangeCodes( pRangeCodes)
+, mpStartGlyphs( nullptr)
+, mpGlyphIds( nullptr)
+, mnRangeCount( nRangeCount)
+, mbSymbolic( bSymbolic)
+, mbRecoded( false)
+{}
+
+static ImplFontCharMapRef g_pDefaultImplFontCharMap;
+const sal_UCS4 aDefaultUnicodeRanges[] = {0x0020,0xD800, 0xE000,0xFFF0};
+const sal_UCS4 aDefaultSymbolRanges[] = {0x0020,0x0100, 0xF020,0xF100};
+
+ImplFontCharMap::~ImplFontCharMap()
+{
+ if( !isDefaultMap() )
+ {
+ delete[] mpRangeCodes;
+ delete[] mpStartGlyphs;
+ delete[] mpGlyphIds;
+ }
+}
+
+ImplFontCharMap::ImplFontCharMap( const CmapResult& rCR )
+: mpRangeCodes( rCR.mpRangeCodes )
+, mpStartGlyphs( rCR.mpStartGlyphs )
+, mpGlyphIds( rCR.mpGlyphIds )
+, mnRangeCount( rCR.mnRangeCount )
+, mnCharCount( 0 )
+ , m_bSymbolic(rCR.mbSymbolic)
+{
+ const sal_UCS4* pRangePtr = mpRangeCodes;
+ for( int i = mnRangeCount; --i >= 0; pRangePtr += 2 )
+ {
+ sal_UCS4 cFirst = pRangePtr[0];
+ sal_UCS4 cLast = pRangePtr[1];
+ mnCharCount += cLast - cFirst;
+ }
+}
+
+ImplFontCharMapRef const & ImplFontCharMap::getDefaultMap( bool bSymbols )
+{
+ const sal_UCS4* pRangeCodes = aDefaultUnicodeRanges;
+ int nCodesCount = std::size(aDefaultUnicodeRanges);
+ if( bSymbols )
+ {
+ pRangeCodes = aDefaultSymbolRanges;
+ nCodesCount = std::size(aDefaultSymbolRanges);
+ }
+
+ CmapResult aDefaultCR( bSymbols, pRangeCodes, nCodesCount/2 );
+ g_pDefaultImplFontCharMap = ImplFontCharMapRef(new ImplFontCharMap(aDefaultCR));
+
+ return g_pDefaultImplFontCharMap;
+}
+
+bool ImplFontCharMap::isDefaultMap() const
+{
+ const bool bIsDefault = (mpRangeCodes == aDefaultUnicodeRanges) || (mpRangeCodes == aDefaultSymbolRanges);
+ return bIsDefault;
+}
+
+static unsigned GetUInt( const unsigned char* p ) { return((p[0]<<24)+(p[1]<<16)+(p[2]<<8)+p[3]);}
+static unsigned GetUShort( const unsigned char* p ){ return((p[0]<<8) | p[1]);}
+static int GetSShort( const unsigned char* p ){ return static_cast<sal_Int16>((p[0]<<8)|p[1]);}
+
+// TODO: move CMAP parsing directly into the ImplFontCharMap class
+bool ParseCMAP( const unsigned char* pCmap, int nLength, CmapResult& rResult )
+{
+ rResult.mpRangeCodes = nullptr;
+ rResult.mpStartGlyphs= nullptr;
+ rResult.mpGlyphIds = nullptr;
+ rResult.mnRangeCount = 0;
+ rResult.mbRecoded = false;
+ rResult.mbSymbolic = false;
+
+ // parse the table header and check for validity
+ if( !pCmap || (nLength < 24) )
+ return false;
+
+ if( GetUShort( pCmap ) != 0x0000 ) // simple check for CMAP corruption
+ return false;
+
+ int nSubTables = GetUShort( pCmap + 2 );
+ if( (nSubTables <= 0) || (nSubTables > (nLength - 24) / 8) )
+ return false;
+
+ const unsigned char* pEndValidArea = pCmap + nLength;
+
+ // find the most interesting subtable in the CMAP
+ rtl_TextEncoding eRecodeFrom = RTL_TEXTENCODING_UNICODE;
+ int nOffset = 0;
+ int nFormat = -1;
+ int nBestVal = 0;
+ for( const unsigned char* p = pCmap + 4; --nSubTables >= 0; p += 8 )
+ {
+ int nPlatform = GetUShort( p );
+ int nEncoding = GetUShort( p+2 );
+ int nPlatformEncoding = (nPlatform << 8) + nEncoding;
+
+ int nValue;
+ rtl_TextEncoding eTmpEncoding = RTL_TEXTENCODING_UNICODE;
+ switch( nPlatformEncoding )
+ {
+ case 0x000: nValue = 20; break; // Unicode 1.0
+ case 0x001: nValue = 21; break; // Unicode 1.1
+ case 0x002: nValue = 22; break; // iso10646_1993
+ case 0x003: nValue = 23; break; // UCS-2
+ case 0x004: nValue = 24; break; // UCS-4
+ case 0x100: nValue = 22; break; // Mac Unicode<2.0
+ case 0x103: nValue = 23; break; // Mac Unicode>2.0
+ case 0x300: nValue = 5; rResult.mbSymbolic = true; break; // Win Symbol
+ case 0x301: nValue = 28; break; // Win UCS-2
+ case 0x30A: nValue = 29; break; // Win-UCS-4
+ case 0x302: nValue = 11; eTmpEncoding = RTL_TEXTENCODING_SHIFT_JIS; break;
+ case 0x303: nValue = 12; eTmpEncoding = RTL_TEXTENCODING_GB_18030; break;
+ case 0x304: nValue = 11; eTmpEncoding = RTL_TEXTENCODING_BIG5; break;
+ case 0x305: nValue = 11; eTmpEncoding = RTL_TEXTENCODING_MS_949; break;
+ case 0x306: nValue = 11; eTmpEncoding = RTL_TEXTENCODING_MS_1361; break;
+ default: nValue = 0; break;
+ }
+
+ if( nValue <= 0 ) // ignore unknown encodings
+ continue;
+
+ int nTmpOffset = GetUInt( p+4 );
+
+ if (nTmpOffset > nLength - 2 || nTmpOffset < 0)
+ continue;
+
+ int nTmpFormat = GetUShort( pCmap + nTmpOffset );
+ if( nTmpFormat == 12 ) // 32bit code -> glyph map format
+ nValue += 3;
+ else if( nTmpFormat != 4 ) // 16bit code -> glyph map format
+ continue; // ignore other formats
+
+ if( nBestVal < nValue )
+ {
+ nBestVal = nValue;
+ nOffset = nTmpOffset;
+ nFormat = nTmpFormat;
+ eRecodeFrom = eTmpEncoding;
+ }
+ }
+
+ // parse the best CMAP subtable
+ int nRangeCount = 0;
+ sal_UCS4* pCodePairs = nullptr;
+ int* pStartGlyphs = nullptr;
+
+ std::vector<sal_uInt16> aGlyphIdArray;
+ aGlyphIdArray.reserve( 0x1000 );
+ aGlyphIdArray.push_back( 0 );
+
+ // format 4, the most common 16bit char mapping table
+ if( (nFormat == 4) && ((nOffset+16) < nLength) )
+ {
+ int nSegCountX2 = GetUShort( pCmap + nOffset + 6 );
+ nRangeCount = nSegCountX2/2 - 1;
+ if (nRangeCount < 0)
+ {
+ SAL_WARN("vcl.gdi", "negative RangeCount");
+ nRangeCount = 0;
+ }
+
+ const unsigned char* pLimitBase = pCmap + nOffset + 14;
+ const unsigned char* pBeginBase = pLimitBase + nSegCountX2 + 2;
+ const unsigned char* pDeltaBase = pBeginBase + nSegCountX2;
+ const unsigned char* pOffsetBase = pDeltaBase + nSegCountX2;
+
+ const int nOffsetBaseStart = pOffsetBase - pCmap;
+ const int nRemainingLen = nLength - nOffsetBaseStart;
+ const int nMaxPossibleRangeOffsets = nRemainingLen / 2;
+ if (nRangeCount > nMaxPossibleRangeOffsets)
+ {
+ SAL_WARN("vcl.gdi", "more range offsets requested then space available");
+ nRangeCount = std::max(0, nMaxPossibleRangeOffsets);
+ }
+
+ pCodePairs = new sal_UCS4[ nRangeCount * 2 ];
+ pStartGlyphs = new int[ nRangeCount ];
+
+ sal_UCS4* pCP = pCodePairs;
+ for( int i = 0; i < nRangeCount; ++i )
+ {
+ const sal_UCS4 cMinChar = GetUShort( pBeginBase + 2*i );
+ const sal_UCS4 cMaxChar = GetUShort( pLimitBase + 2*i );
+ const int nGlyphDelta = GetSShort( pDeltaBase + 2*i );
+ const int nRangeOffset = GetUShort( pOffsetBase + 2*i );
+ if( cMinChar > cMaxChar ) { // no sane font should trigger this
+ SAL_WARN("vcl.gdi", "Min char should never be more than the max char!");
+ break;
+ }
+ if( cMaxChar == 0xFFFF ) {
+ SAL_WARN("vcl.gdi", "Format 4 char should not be 0xFFFF");
+ break;
+ }
+ if( !nRangeOffset ) {
+ // glyphid can be calculated directly
+ pStartGlyphs[i] = (cMinChar + nGlyphDelta) & 0xFFFF;
+ } else {
+ // update the glyphid-array with the glyphs in this range
+ pStartGlyphs[i] = -static_cast<int>(aGlyphIdArray.size());
+ const unsigned char* pGlyphIdPtr = pOffsetBase + 2*i + nRangeOffset;
+ const size_t nRemainingSize = pEndValidArea >= pGlyphIdPtr ? pEndValidArea - pGlyphIdPtr : 0;
+ const size_t nMaxPossibleRecords = nRemainingSize/2;
+ if (nMaxPossibleRecords == 0) { // no sane font should trigger this
+ SAL_WARN("vcl.gdi", "More indexes claimed that space available in font!");
+ break;
+ }
+ const size_t nMaxLegalChar = cMinChar + nMaxPossibleRecords-1;
+ if (cMaxChar > nMaxLegalChar) { // no sane font should trigger this
+ SAL_WARN("vcl.gdi", "More indexes claimed that space available in font!");
+ break;
+ }
+ for( sal_UCS4 c = cMinChar; c <= cMaxChar; ++c, pGlyphIdPtr+=2 ) {
+ const int nGlyphIndex = GetUShort( pGlyphIdPtr ) + nGlyphDelta;
+ aGlyphIdArray.push_back( static_cast<sal_uInt16>(nGlyphIndex) );
+ }
+ }
+ *(pCP++) = cMinChar;
+ *(pCP++) = cMaxChar + 1;
+ }
+ nRangeCount = (pCP - pCodePairs) / 2;
+ }
+ // format 12, the most common 32bit char mapping table
+ else if( (nFormat == 12) && ((nOffset+16) < nLength) )
+ {
+ nRangeCount = GetUInt( pCmap + nOffset + 12 );
+ if (nRangeCount < 0)
+ {
+ SAL_WARN("vcl.gdi", "negative RangeCount");
+ nRangeCount = 0;
+ }
+
+ const int nGroupOffset = nOffset + 16;
+ const int nRemainingLen = nLength - nGroupOffset;
+ const int nMaxPossiblePairs = nRemainingLen / 12;
+ if (nRangeCount > nMaxPossiblePairs)
+ {
+ SAL_WARN("vcl.gdi", "more code pairs requested then space available");
+ nRangeCount = std::max(0, nMaxPossiblePairs);
+ }
+
+ pCodePairs = new sal_UCS4[ nRangeCount * 2 ];
+ pStartGlyphs = new int[ nRangeCount ];
+
+ const unsigned char* pGroup = pCmap + nGroupOffset;
+ sal_UCS4* pCP = pCodePairs;
+ for( int i = 0; i < nRangeCount; ++i )
+ {
+ sal_UCS4 cMinChar = GetUInt( pGroup + 0 );
+ sal_UCS4 cMaxChar = GetUInt( pGroup + 4 );
+ int nGlyphId = GetUInt( pGroup + 8 );
+ pGroup += 12;
+
+ if( cMinChar > cMaxChar ) { // no sane font should trigger this
+ SAL_WARN("vcl.gdi", "Min char should never be more than the max char!");
+ break;
+ }
+
+ *(pCP++) = cMinChar;
+ *(pCP++) = cMaxChar + 1;
+ pStartGlyphs[i] = nGlyphId;
+ }
+ nRangeCount = (pCP - pCodePairs) / 2;
+ }
+
+ // check if any subtable resulted in something usable
+ if( nRangeCount <= 0 )
+ {
+ delete[] pCodePairs;
+ delete[] pStartGlyphs;
+
+ // even when no CMAP is available we know it for symbol fonts
+ if( rResult.mbSymbolic )
+ {
+ pCodePairs = new sal_UCS4[4];
+ pCodePairs[0] = 0x0020; // aliased symbols
+ pCodePairs[1] = 0x0100;
+ pCodePairs[2] = 0xF020; // original symbols
+ pCodePairs[3] = 0xF100;
+ rResult.mpRangeCodes = pCodePairs;
+ rResult.mnRangeCount = 2;
+ return true;
+ }
+
+ return false;
+ }
+
+ // recode the code ranges to their unicode encoded ranges if needed
+ rtl_TextToUnicodeConverter aConverter = nullptr;
+ rtl_UnicodeToTextContext aCvtContext = nullptr;
+
+ rResult.mbRecoded = ( eRecodeFrom != RTL_TEXTENCODING_UNICODE );
+ if( rResult.mbRecoded )
+ {
+ aConverter = rtl_createTextToUnicodeConverter( eRecodeFrom );
+ aCvtContext = rtl_createTextToUnicodeContext( aConverter );
+ }
+
+ if( aConverter && aCvtContext )
+ {
+ // determine the set of supported code points from encoded ranges
+ o3tl::sorted_vector<sal_UCS4> aSupportedCodePoints;
+ aSupportedCodePoints.reserve(256);
+
+ static const int NINSIZE = 64;
+ static const int NOUTSIZE = 64;
+ std::vector<char> cCharsInp;
+ cCharsInp.reserve(NINSIZE);
+ sal_Unicode cCharsOut[ NOUTSIZE ];
+ sal_UCS4* pCP = pCodePairs;
+ for( int i = 0; i < nRangeCount; ++i )
+ {
+ sal_UCS4 cMin = *(pCP++);
+ sal_UCS4 cEnd = *(pCP++);
+ // ofz#25868 the conversion only makes sense with
+ // input codepoints in 0..SAL_MAX_UINT16 range
+ while (cMin < cEnd && cMin <= SAL_MAX_UINT16)
+ {
+ for (int j = 0; (cMin < cEnd) && (j < NINSIZE); ++cMin, ++j)
+ {
+ if( cMin >= 0x0100 )
+ cCharsInp.push_back(static_cast<char>(cMin >> 8));
+ if( (cMin >= 0x0100) || (cMin < 0x00A0) )
+ cCharsInp.push_back(static_cast<char>(cMin));
+ }
+
+ sal_uInt32 nCvtInfo;
+ sal_Size nSrcCvtBytes;
+ int nOutLen = rtl_convertTextToUnicode(
+ aConverter, aCvtContext,
+ cCharsInp.data(), cCharsInp.size(), cCharsOut, NOUTSIZE,
+ RTL_TEXTTOUNICODE_FLAGS_INVALID_IGNORE
+ | RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_IGNORE,
+ &nCvtInfo, &nSrcCvtBytes );
+
+ cCharsInp.clear();
+
+ for (int j = 0; j < nOutLen; ++j)
+ aSupportedCodePoints.insert( cCharsOut[j] );
+ }
+ }
+
+ rtl_destroyTextToUnicodeConverter( aCvtContext );
+ rtl_destroyTextToUnicodeConverter( aConverter );
+
+ // convert the set of supported code points to ranges
+ std::vector<sal_UCS4> aSupportedRanges;
+
+ for (auto const& supportedPoint : aSupportedCodePoints)
+ {
+ if( aSupportedRanges.empty()
+ || (aSupportedRanges.back() != supportedPoint) )
+ {
+ // add new range beginning with current unicode
+ aSupportedRanges.push_back(supportedPoint);
+ aSupportedRanges.push_back( 0 );
+ }
+
+ // extend existing range to include current unicode
+ aSupportedRanges.back() = supportedPoint + 1;
+ }
+
+ // glyph mapping for non-unicode fonts not implemented
+ delete[] pStartGlyphs;
+ pStartGlyphs = nullptr;
+ aGlyphIdArray.clear();
+
+ // make a pCodePairs array using the vector from above
+ delete[] pCodePairs;
+ nRangeCount = aSupportedRanges.size() / 2;
+ if( nRangeCount <= 0 )
+ return false;
+ pCodePairs = new sal_UCS4[ nRangeCount * 2 ];
+ pCP = pCodePairs;
+ for (auto const& supportedRange : aSupportedRanges)
+ *(pCP++) = supportedRange;
+ }
+
+ // prepare the glyphid-array if needed
+ // TODO: merge ranges if they are close enough?
+ sal_uInt16* pGlyphIds = nullptr;
+ if( !aGlyphIdArray.empty())
+ {
+ pGlyphIds = new sal_uInt16[ aGlyphIdArray.size() ];
+ sal_uInt16* pOut = pGlyphIds;
+ for (auto const& glyphId : aGlyphIdArray)
+ *(pOut++) = glyphId;
+ }
+
+ // update the result struct
+ rResult.mpRangeCodes = pCodePairs;
+ rResult.mpStartGlyphs = pStartGlyphs;
+ rResult.mnRangeCount = nRangeCount;
+ rResult.mpGlyphIds = pGlyphIds;
+ return true;
+}
+
+FontCharMap::FontCharMap()
+ : mpImplFontCharMap( ImplFontCharMap::getDefaultMap() )
+{
+}
+
+FontCharMap::FontCharMap( ImplFontCharMapRef const & pIFCMap )
+ : mpImplFontCharMap( pIFCMap )
+{
+}
+
+FontCharMap::FontCharMap( const CmapResult& rCR )
+ : mpImplFontCharMap(new ImplFontCharMap(rCR))
+{
+}
+
+FontCharMap::~FontCharMap()
+{
+ mpImplFontCharMap = nullptr;
+}
+
+FontCharMapRef FontCharMap::GetDefaultMap( bool bSymbol )
+{
+ FontCharMapRef xFontCharMap( new FontCharMap( ImplFontCharMap::getDefaultMap( bSymbol ) ) );
+ return xFontCharMap;
+}
+
+bool FontCharMap::IsDefaultMap() const
+{
+ return mpImplFontCharMap->isDefaultMap();
+}
+
+bool FontCharMap::isSymbolic() const { return mpImplFontCharMap->m_bSymbolic; }
+
+int FontCharMap::GetCharCount() const
+{
+ return mpImplFontCharMap->mnCharCount;
+}
+
+int FontCharMap::CountCharsInRange( sal_UCS4 cMin, sal_UCS4 cMax ) const
+{
+ int nCount = 0;
+
+ // find and adjust range and char count for cMin
+ int nRangeMin = findRangeIndex( cMin );
+ if( nRangeMin & 1 )
+ ++nRangeMin;
+ else if( cMin > mpImplFontCharMap->mpRangeCodes[ nRangeMin ] )
+ nCount -= cMin - mpImplFontCharMap->mpRangeCodes[ nRangeMin ];
+
+ // find and adjust range and char count for cMax
+ int nRangeMax = findRangeIndex( cMax );
+ if( nRangeMax & 1 )
+ --nRangeMax;
+ else
+ nCount -= mpImplFontCharMap->mpRangeCodes[ nRangeMax+1 ] - cMax - 1;
+
+ // count chars in complete ranges between cMin and cMax
+ for( int i = nRangeMin; i <= nRangeMax; i+=2 )
+ nCount += mpImplFontCharMap->mpRangeCodes[i+1] - mpImplFontCharMap->mpRangeCodes[i];
+
+ return nCount;
+}
+
+bool FontCharMap::HasChar( sal_UCS4 cChar ) const
+{
+ bool bHasChar = false;
+
+ if( mpImplFontCharMap->mpStartGlyphs == nullptr ) { // only the char-ranges are known
+ const int nRange = findRangeIndex( cChar );
+ if( nRange==0 && cChar < mpImplFontCharMap->mpRangeCodes[0] )
+ return false;
+ bHasChar = ((nRange & 1) == 0); // inside a range
+ } else { // glyph mapping is available
+ const int nGlyphIndex = GetGlyphIndex( cChar );
+ bHasChar = (nGlyphIndex != 0); // not the notdef-glyph
+ }
+
+ return bHasChar;
+}
+
+sal_UCS4 FontCharMap::GetFirstChar() const
+{
+ return mpImplFontCharMap->mpRangeCodes[0];
+}
+
+sal_UCS4 FontCharMap::GetLastChar() const
+{
+ return (mpImplFontCharMap->mpRangeCodes[ 2*mpImplFontCharMap->mnRangeCount-1 ] - 1);
+}
+
+sal_UCS4 FontCharMap::GetNextChar( sal_UCS4 cChar ) const
+{
+ if( cChar < GetFirstChar() )
+ return GetFirstChar();
+ if( cChar >= GetLastChar() )
+ return GetLastChar();
+
+ int nRange = findRangeIndex( cChar + 1 );
+ if( nRange & 1 ) // outside of range?
+ return mpImplFontCharMap->mpRangeCodes[ nRange + 1 ]; // => first in next range
+ return (cChar + 1);
+}
+
+sal_UCS4 FontCharMap::GetPrevChar( sal_UCS4 cChar ) const
+{
+ if( cChar <= GetFirstChar() )
+ return GetFirstChar();
+ if( cChar > GetLastChar() )
+ return GetLastChar();
+
+ int nRange = findRangeIndex( cChar - 1 );
+ if( nRange & 1 ) // outside a range?
+ return (mpImplFontCharMap->mpRangeCodes[ nRange ] - 1); // => last in prev range
+ return (cChar - 1);
+}
+
+int FontCharMap::GetIndexFromChar( sal_UCS4 cChar ) const
+{
+ // TODO: improve linear walk?
+ int nCharIndex = 0;
+ const sal_UCS4* pRange = &mpImplFontCharMap->mpRangeCodes[0];
+ for( int i = 0; i < mpImplFontCharMap->mnRangeCount; ++i )
+ {
+ sal_UCS4 cFirst = *(pRange++);
+ sal_UCS4 cLast = *(pRange++);
+ if( cChar >= cLast )
+ nCharIndex += cLast - cFirst;
+ else if( cChar >= cFirst )
+ return nCharIndex + (cChar - cFirst);
+ else
+ break;
+ }
+
+ return -1;
+}
+
+sal_UCS4 FontCharMap::GetCharFromIndex( int nIndex ) const
+{
+ // TODO: improve linear walk?
+ const sal_UCS4* pRange = &mpImplFontCharMap->mpRangeCodes[0];
+ for( int i = 0; i < mpImplFontCharMap->mnRangeCount; ++i )
+ {
+ sal_UCS4 cFirst = *(pRange++);
+ sal_UCS4 cLast = *(pRange++);
+ nIndex -= cLast - cFirst;
+ if( nIndex < 0 )
+ return (cLast + nIndex);
+ }
+
+ // we can only get here with an out-of-bounds charindex
+ return mpImplFontCharMap->mpRangeCodes[0];
+}
+
+int FontCharMap::findRangeIndex( sal_UCS4 cChar ) const
+{
+ int nLower = 0;
+ int nMid = mpImplFontCharMap->mnRangeCount;
+ int nUpper = 2 * mpImplFontCharMap->mnRangeCount - 1;
+ while( nLower < nUpper )
+ {
+ if( cChar >= mpImplFontCharMap->mpRangeCodes[ nMid ] )
+ nLower = nMid;
+ else
+ nUpper = nMid - 1;
+ nMid = (nLower + nUpper + 1) / 2;
+ }
+
+ return nMid;
+}
+
+int FontCharMap::GetGlyphIndex( sal_UCS4 cChar ) const
+{
+ // return -1 if the object doesn't know the glyph ids
+ if( !mpImplFontCharMap->mpStartGlyphs )
+ return -1;
+
+ // return 0 if the unicode doesn't have a matching glyph
+ int nRange = findRangeIndex( cChar );
+ // check that we are inside any range
+ if( (nRange == 0) && (cChar < mpImplFontCharMap->mpRangeCodes[0]) ) {
+ // symbol aliasing gives symbol fonts a second chance
+ const bool bSymbolic = cChar <= 0xFF && (mpImplFontCharMap->mpRangeCodes[0]>=0xF000) &&
+ (mpImplFontCharMap->mpRangeCodes[1]<=0xF0FF);
+ if( !bSymbolic )
+ return 0;
+ // check for symbol aliasing (U+F0xx -> U+00xx)
+ cChar |= 0xF000;
+ nRange = findRangeIndex( cChar );
+ if( (nRange == 0) && (cChar < mpImplFontCharMap->mpRangeCodes[0]) ) {
+ return 0;
+ }
+ }
+ // check that we are inside a range
+ if( (nRange & 1) != 0 )
+ return 0;
+
+ // get glyph index directly or indirectly
+ int nGlyphIndex = cChar - mpImplFontCharMap->mpRangeCodes[ nRange ];
+ const int nStartIndex = mpImplFontCharMap->mpStartGlyphs[ nRange/2 ];
+ if( nStartIndex >= 0 ) {
+ // the glyph index can be calculated
+ nGlyphIndex += nStartIndex;
+ } else {
+ // the glyphid array has the glyph index
+ nGlyphIndex = mpImplFontCharMap->mpGlyphIds[ nGlyphIndex - nStartIndex];
+ }
+
+ return nGlyphIndex;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/fontinstance.cxx b/vcl/source/font/fontinstance.cxx
new file mode 100644
index 000000000..0907d657a
--- /dev/null
+++ b/vcl/source/font/fontinstance.cxx
@@ -0,0 +1,192 @@
+/* -*- 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 <hb-ot.h>
+#include <hb-graphite2.h>
+
+#include <font/PhysicalFontFace.hxx>
+#include <fontinstance.hxx>
+#include <impfontcache.hxx>
+
+LogicalFontInstance::LogicalFontInstance(const vcl::font::PhysicalFontFace& rFontFace, const vcl::font::FontSelectPattern& rFontSelData )
+ : mxFontMetric( new ImplFontMetricData( rFontSelData ))
+ , mpConversion( nullptr )
+ , mnLineHeight( 0 )
+ , mnOwnOrientation( 0 )
+ , mnOrientation( 0 )
+ , mbInit( false )
+ , mpFontCache( nullptr )
+ , m_aFontSelData(rFontSelData)
+ , m_pHbFont(nullptr)
+ , m_nAveWidthFactor(1.0f)
+ , m_pFontFace(&const_cast<vcl::font::PhysicalFontFace&>(rFontFace))
+{
+}
+
+LogicalFontInstance::~LogicalFontInstance()
+{
+ maUnicodeFallbackList.clear();
+ mpFontCache = nullptr;
+ mxFontMetric = nullptr;
+
+ if (m_pHbFont)
+ hb_font_destroy(m_pHbFont);
+}
+
+hb_font_t* LogicalFontInstance::InitHbFont(hb_face_t* pHbFace)
+{
+ assert(pHbFace);
+ hb_font_t* pHbFont = hb_font_create(pHbFace);
+ unsigned int nUPEM = hb_face_get_upem(pHbFace);
+ hb_font_set_scale(pHbFont, nUPEM, nUPEM);
+ hb_ot_font_set_funcs(pHbFont);
+ // hb_font_t keeps a reference to hb_face_t, so destroy this one.
+ hb_face_destroy(pHbFace);
+ return pHbFont;
+}
+
+int LogicalFontInstance::GetKashidaWidth() const
+{
+ hb_font_t* pHbFont = const_cast<LogicalFontInstance*>(this)->GetHbFont();
+ hb_position_t nWidth = 0;
+ hb_codepoint_t nIndex = 0;
+
+ if (hb_font_get_glyph(pHbFont, 0x0640, 0, &nIndex))
+ {
+ double nXScale = 0;
+ GetScale(&nXScale, nullptr);
+ nWidth = hb_font_get_glyph_h_advance(pHbFont, nIndex) * nXScale;
+ }
+
+ return nWidth;
+}
+
+void LogicalFontInstance::GetScale(double* nXScale, double* nYScale) const
+{
+ hb_face_t* pHbFace = hb_font_get_face(const_cast<LogicalFontInstance*>(this)->GetHbFont());
+ unsigned int nUPEM = hb_face_get_upem(pHbFace);
+
+ double nHeight(m_aFontSelData.mnHeight);
+
+ // On Windows, mnWidth is relative to average char width not font height,
+ // and we need to keep it that way for GDI to correctly scale the glyphs.
+ // Here we compensate for this so that HarfBuzz gives us the correct glyph
+ // positions.
+ double nWidth(m_aFontSelData.mnWidth ? m_aFontSelData.mnWidth * m_nAveWidthFactor : nHeight);
+
+ if (nYScale)
+ *nYScale = nHeight / nUPEM;
+
+ if (nXScale)
+ *nXScale = nWidth / nUPEM;
+}
+
+void LogicalFontInstance::AddFallbackForUnicode(sal_UCS4 cChar, FontWeight eWeight, const OUString& rFontName,
+ bool bEmbolden, const ItalicMatrix& rMatrix)
+{
+ MapEntry& rEntry = maUnicodeFallbackList[ std::pair< sal_UCS4, FontWeight >(cChar,eWeight) ];
+ rEntry.sFontName = rFontName;
+ rEntry.bEmbolden = bEmbolden;
+ rEntry.aItalicMatrix = rMatrix;
+}
+
+bool LogicalFontInstance::GetFallbackForUnicode(sal_UCS4 cChar, FontWeight eWeight,
+ OUString* pFontName, bool* pEmbolden, ItalicMatrix* pMatrix) const
+{
+ UnicodeFallbackList::const_iterator it = maUnicodeFallbackList.find( std::pair< sal_UCS4, FontWeight >(cChar,eWeight) );
+ if( it == maUnicodeFallbackList.end() )
+ return false;
+
+ const MapEntry& rEntry = (*it).second;
+ *pFontName = rEntry.sFontName;
+ *pEmbolden = rEntry.bEmbolden;
+ *pMatrix = rEntry.aItalicMatrix;
+ return true;
+}
+
+void LogicalFontInstance::IgnoreFallbackForUnicode( sal_UCS4 cChar, FontWeight eWeight, std::u16string_view rFontName )
+{
+ UnicodeFallbackList::iterator it = maUnicodeFallbackList.find( std::pair< sal_UCS4,FontWeight >(cChar,eWeight) );
+ if( it == maUnicodeFallbackList.end() )
+ return;
+ const MapEntry& rEntry = (*it).second;
+ if (rEntry.sFontName == rFontName)
+ maUnicodeFallbackList.erase( it );
+}
+
+bool LogicalFontInstance::GetGlyphBoundRect(sal_GlyphId nID, tools::Rectangle &rRect, bool bVertical) const
+{
+ if (mpFontCache && mpFontCache->GetCachedGlyphBoundRect(this, nID, rRect))
+ return true;
+
+ bool res = ImplGetGlyphBoundRect(nID, rRect, bVertical);
+ if (mpFontCache && res)
+ mpFontCache->CacheGlyphBoundRect(this, nID, rRect);
+ return res;
+}
+
+bool LogicalFontInstance::IsGraphiteFont()
+{
+ if (!m_xbIsGraphiteFont)
+ {
+ m_xbIsGraphiteFont = hb_graphite2_face_get_gr_face(hb_font_get_face(GetHbFont())) != nullptr;
+ }
+ return *m_xbIsGraphiteFont;
+}
+
+bool LogicalFontInstance::NeedOffsetCorrection(sal_Int32 nYOffset)
+{
+ if (!m_xeFontFamilyEnum)
+ {
+ char familyname[10];
+ unsigned int familyname_size = 10;
+
+ m_xeFontFamilyEnum = FontFamilyEnum::Unclassified;
+
+ if (hb_ot_name_get_utf8 (hb_font_get_face(GetHbFont()),
+ HB_OT_NAME_ID_FONT_FAMILY , HB_LANGUAGE_INVALID, &familyname_size, familyname) == 8)
+ {
+ // DFKai-SB (ukai.ttf) is a built-in font under traditional Chinese
+ // Windows. It has wrong extent values in glyf table. The problem results
+ // in wrong positioning of glyphs in vertical writing.
+ // Check https://github.com/harfbuzz/harfbuzz/issues/3521 for reference.
+ if (!strncmp("DFKai-SB", familyname, 8))
+ m_xeFontFamilyEnum = FontFamilyEnum::DFKaiSB;
+ }
+ }
+
+ bool bRet = true;
+
+ switch (*m_xeFontFamilyEnum)
+ {
+ case FontFamilyEnum::DFKaiSB:
+ // -839: optimization for one third of ukai.ttf
+ if (nYOffset == -839)
+ bRet = false;
+ break;
+ default:
+ bRet = false;
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/font/fontmetric.cxx b/vcl/source/font/fontmetric.cxx
new file mode 100644
index 000000000..428af2e46
--- /dev/null
+++ b/vcl/source/font/fontmetric.cxx
@@ -0,0 +1,468 @@
+/* -*- 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 <i18nlangtag/mslangid.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <unotools/configmgr.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/outdev.hxx>
+#include <sal/log.hxx>
+
+#include <font/FontSelectPattern.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <fontinstance.hxx>
+#include <impfontmetricdata.hxx>
+#include <sft.hxx>
+
+#include <com/sun/star/uno/Sequence.hxx>
+#include <comphelper/sequence.hxx>
+#include <hb-ot.h>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::rtl;
+using namespace ::utl;
+
+FontMetric::FontMetric()
+: mnAscent( 0 ),
+ mnDescent( 0 ),
+ mnIntLeading( 0 ),
+ mnExtLeading( 0 ),
+ mnLineHeight( 0 ),
+ mnSlant( 0 ),
+ mnBulletOffset( 0 ),
+ mnHangingBaseline( 0 ),
+ mbFullstopCentered( false )
+{}
+
+FontMetric::FontMetric( const FontMetric& rFontMetric ) = default;
+
+FontMetric::FontMetric(vcl::font::PhysicalFontFace const& rFace)
+ : FontMetric()
+{
+ SetFamilyName(rFace.GetFamilyName());
+ SetStyleName(rFace.GetStyleName());
+ SetCharSet(rFace.GetCharSet());
+ SetFamily(rFace.GetFamilyType());
+ SetPitch(rFace.GetPitch());
+ SetWeight(rFace.GetWeight());
+ SetItalic(rFace.GetItalic());
+ SetAlignment(TextAlign::ALIGN_TOP);
+ SetWidthType(rFace.GetWidthType());
+ SetQuality(rFace.GetQuality() );
+}
+
+FontMetric::~FontMetric()
+{
+}
+
+FontMetric& FontMetric::operator=(const FontMetric& rFontMetric) = default;
+
+FontMetric& FontMetric::operator=(FontMetric&& rFontMetric) = default;
+
+bool FontMetric::EqualNoBase( const FontMetric& r ) const
+{
+ if (mbFullstopCentered != r.mbFullstopCentered)
+ return false;
+ if( mnAscent != r.mnAscent )
+ return false;
+ if( mnDescent != r.mnDescent )
+ return false;
+ if( mnIntLeading != r.mnIntLeading )
+ return false;
+ if( mnExtLeading != r.mnExtLeading )
+ return false;
+ if( mnSlant != r.mnSlant )
+ return false;
+
+ return true;
+}
+
+bool FontMetric::operator==( const FontMetric& r ) const
+{
+ if( Font::operator!=(r) )
+ return false;
+ return EqualNoBase(r);
+}
+
+bool FontMetric::EqualIgnoreColor( const FontMetric& r ) const
+{
+ if( !Font::EqualIgnoreColor(r) )
+ return false;
+ return EqualNoBase(r);
+}
+
+size_t FontMetric::GetHashValueNoBase() const
+{
+ size_t hash = 0;
+ o3tl::hash_combine( hash, mbFullstopCentered );
+ o3tl::hash_combine( hash, mnAscent );
+ o3tl::hash_combine( hash, mnDescent );
+ o3tl::hash_combine( hash, mnIntLeading );
+ o3tl::hash_combine( hash, mnExtLeading );
+ o3tl::hash_combine( hash, mnSlant );
+ return hash;
+}
+
+size_t FontMetric::GetHashValueIgnoreColor() const
+{
+ size_t hash = GetHashValueNoBase();
+ o3tl::hash_combine( hash, Font::GetHashValueIgnoreColor());
+ return hash;
+}
+
+ImplFontMetricData::ImplFontMetricData( const vcl::font::FontSelectPattern& rFontSelData )
+ : FontAttributes( rFontSelData )
+ , mnHeight ( rFontSelData.mnHeight )
+ , mnWidth ( rFontSelData.mnWidth )
+ , mnOrientation( static_cast<short>(rFontSelData.mnOrientation) )
+ , mnAscent( 0 )
+ , mnDescent( 0 )
+ , mnIntLeading( 0 )
+ , mnExtLeading( 0 )
+ , mnSlant( 0 )
+ , mnMinKashida( 0 )
+ , mnHangingBaseline( 0 )
+ , mbFullstopCentered( false )
+ , mnBulletOffset( 0 )
+ , mnUnderlineSize( 0 )
+ , mnUnderlineOffset( 0 )
+ , mnBUnderlineSize( 0 )
+ , mnBUnderlineOffset( 0 )
+ , mnDUnderlineSize( 0 )
+ , mnDUnderlineOffset1( 0 )
+ , mnDUnderlineOffset2( 0 )
+ , mnWUnderlineSize( 0 )
+ , mnWUnderlineOffset( 0 )
+ , mnAboveUnderlineSize( 0 )
+ , mnAboveUnderlineOffset( 0 )
+ , mnAboveBUnderlineSize( 0 )
+ , mnAboveBUnderlineOffset( 0 )
+ , mnAboveDUnderlineSize( 0 )
+ , mnAboveDUnderlineOffset1( 0 )
+ , mnAboveDUnderlineOffset2( 0 )
+ , mnAboveWUnderlineSize( 0 )
+ , mnAboveWUnderlineOffset( 0 )
+ , mnStrikeoutSize( 0 )
+ , mnStrikeoutOffset( 0 )
+ , mnBStrikeoutSize( 0 )
+ , mnBStrikeoutOffset( 0 )
+ , mnDStrikeoutSize( 0 )
+ , mnDStrikeoutOffset1( 0 )
+ , mnDStrikeoutOffset2( 0 )
+{
+ // initialize the used font name
+ sal_Int32 nTokenPos = 0;
+ SetFamilyName( OUString(GetNextFontToken( rFontSelData.GetFamilyName(), nTokenPos )) );
+ SetStyleName( rFontSelData.GetStyleName() );
+}
+
+void ImplFontMetricData::ImplInitTextLineSize( const OutputDevice* pDev )
+{
+ tools::Long nDescent = mnDescent;
+ if ( nDescent <= 0 )
+ {
+ nDescent = mnAscent / 10;
+ if ( !nDescent )
+ nDescent = 1;
+ }
+
+ // #i55341# for some fonts it is not a good idea to calculate
+ // their text line metrics from the real font descent
+ // => work around this problem just for these fonts
+ if( 3*nDescent > mnAscent )
+ nDescent = mnAscent / 3;
+
+ tools::Long nLineHeight = ((nDescent*25)+50) / 100;
+ if ( !nLineHeight )
+ nLineHeight = 1;
+ tools::Long nLineHeight2 = nLineHeight / 2;
+ if ( !nLineHeight2 )
+ nLineHeight2 = 1;
+
+ tools::Long nBLineHeight = ((nDescent*50)+50) / 100;
+ if ( nBLineHeight == nLineHeight )
+ nBLineHeight++;
+ tools::Long nBLineHeight2 = nBLineHeight/2;
+ if ( !nBLineHeight2 )
+ nBLineHeight2 = 1;
+
+ tools::Long n2LineHeight = ((nDescent*16)+50) / 100;
+ if ( !n2LineHeight )
+ n2LineHeight = 1;
+ tools::Long n2LineDY = n2LineHeight;
+ /* #117909#
+ * add some pixels to minimum double line distance on higher resolution devices
+ */
+ tools::Long nMin2LineDY = 1 + pDev->GetDPIY()/150;
+ if ( n2LineDY < nMin2LineDY )
+ n2LineDY = nMin2LineDY;
+ tools::Long n2LineDY2 = n2LineDY/2;
+ if ( !n2LineDY2 )
+ n2LineDY2 = 1;
+
+ const vcl::Font& rFont ( pDev->GetFont() );
+ bool bCJKVertical = MsLangId::isCJK(rFont.GetLanguage()) && rFont.IsVertical();
+ tools::Long nUnderlineOffset = bCJKVertical ? mnDescent : (mnDescent/2 + 1);
+ tools::Long nStrikeoutOffset = rFont.IsVertical() ? -((mnAscent - mnDescent) / 2) : -((mnAscent - mnIntLeading) / 3);
+
+ mnUnderlineSize = nLineHeight;
+ mnUnderlineOffset = nUnderlineOffset - nLineHeight2;
+
+ mnBUnderlineSize = nBLineHeight;
+ mnBUnderlineOffset = nUnderlineOffset - nBLineHeight2;
+
+ mnDUnderlineSize = n2LineHeight;
+ mnDUnderlineOffset1 = nUnderlineOffset - n2LineDY2 - n2LineHeight;
+ mnDUnderlineOffset2 = mnDUnderlineOffset1 + n2LineDY + n2LineHeight;
+
+ tools::Long nWCalcSize = mnDescent;
+ if ( nWCalcSize < 6 )
+ {
+ if ( (nWCalcSize == 1) || (nWCalcSize == 2) )
+ mnWUnderlineSize = nWCalcSize;
+ else
+ mnWUnderlineSize = 3;
+ }
+ else
+ mnWUnderlineSize = ((nWCalcSize*50)+50) / 100;
+
+
+ // Don't assume that wavelines are never placed below the descent, because for most fonts the waveline
+ // is drawn into the text
+ mnWUnderlineOffset = nUnderlineOffset;
+
+ mnStrikeoutSize = nLineHeight;
+ mnStrikeoutOffset = nStrikeoutOffset - nLineHeight2;
+
+ mnBStrikeoutSize = nBLineHeight;
+ mnBStrikeoutOffset = nStrikeoutOffset - nBLineHeight2;
+
+ mnDStrikeoutSize = n2LineHeight;
+ mnDStrikeoutOffset1 = nStrikeoutOffset - n2LineDY2 - n2LineHeight;
+ mnDStrikeoutOffset2 = mnDStrikeoutOffset1 + n2LineDY + n2LineHeight;
+
+ mnBulletOffset = ( pDev->GetTextWidth( OUString( u' ' ) ) - pDev->GetTextWidth( OUString( u'\x00b7' ) ) ) >> 1 ;
+
+}
+
+
+void ImplFontMetricData::ImplInitAboveTextLineSize()
+{
+ tools::Long nIntLeading = mnIntLeading;
+ // TODO: assess usage of nLeading below (changed in extleading CWS)
+ // if no leading is available, we assume 15% of the ascent
+ if ( nIntLeading <= 0 )
+ {
+ nIntLeading = mnAscent*15/100;
+ if ( !nIntLeading )
+ nIntLeading = 1;
+ }
+
+ tools::Long nLineHeight = ((nIntLeading*25)+50) / 100;
+ if ( !nLineHeight )
+ nLineHeight = 1;
+
+ tools::Long nBLineHeight = ((nIntLeading*50)+50) / 100;
+ if ( nBLineHeight == nLineHeight )
+ nBLineHeight++;
+
+ tools::Long n2LineHeight = ((nIntLeading*16)+50) / 100;
+ if ( !n2LineHeight )
+ n2LineHeight = 1;
+
+ tools::Long nCeiling = -mnAscent;
+
+ mnAboveUnderlineSize = nLineHeight;
+ mnAboveUnderlineOffset = nCeiling + (nIntLeading - nLineHeight + 1) / 2;
+
+ mnAboveBUnderlineSize = nBLineHeight;
+ mnAboveBUnderlineOffset = nCeiling + (nIntLeading - nBLineHeight + 1) / 2;
+
+ mnAboveDUnderlineSize = n2LineHeight;
+ mnAboveDUnderlineOffset1 = nCeiling + (nIntLeading - 3*n2LineHeight + 1) / 2;
+ mnAboveDUnderlineOffset2 = nCeiling + (nIntLeading + n2LineHeight + 1) / 2;
+
+ tools::Long nWCalcSize = nIntLeading;
+ if ( nWCalcSize < 6 )
+ {
+ if ( (nWCalcSize == 1) || (nWCalcSize == 2) )
+ mnAboveWUnderlineSize = nWCalcSize;
+ else
+ mnAboveWUnderlineSize = 3;
+ }
+ else
+ mnAboveWUnderlineSize = ((nWCalcSize*50)+50) / 100;
+
+ mnAboveWUnderlineOffset = nCeiling + (nIntLeading + 1) / 2;
+}
+
+void ImplFontMetricData::ImplInitFlags( const OutputDevice* pDev )
+{
+ const vcl::Font& rFont ( pDev->GetFont() );
+ bool bCentered = true;
+ if (MsLangId::isCJK(rFont.GetLanguage()))
+ {
+ tools::Rectangle aRect;
+ pDev->GetTextBoundRect( aRect, u"\x3001" ); // Fullwidth fullstop
+ const auto nH = rFont.GetFontSize().Height();
+ const auto nB = aRect.Left();
+ // Use 18.75% as a threshold to define a centered fullwidth fullstop.
+ // In general, nB/nH < 5% for most Japanese fonts.
+ bCentered = nB > (((nH >> 1)+nH)>>3);
+ }
+ SetFullstopCenteredFlag( bCentered );
+}
+
+bool ImplFontMetricData::ShouldUseWinMetrics(const vcl::TTGlobalFontInfo& rInfo) const
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return false;
+
+ OUString aFontIdentifier(
+ GetFamilyName() + ","
+ + OUString::number(rInfo.ascender) + "," + OUString::number(rInfo.descender) + ","
+ + OUString::number(rInfo.typoAscender) + "," + OUString::number(rInfo.typoDescender) + ","
+ + OUString::number(rInfo.winAscent) + "," + OUString::number(rInfo.winDescent));
+
+ css::uno::Sequence<OUString> rWinMetricFontList(
+ officecfg::Office::Common::Misc::FontsUseWinMetrics::get());
+ if (comphelper::findValue(rWinMetricFontList, aFontIdentifier) != -1)
+ {
+ SAL_INFO("vcl.gdi.fontmetric", "Using win metrics for: " << aFontIdentifier);
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Calculate line spacing:
+ *
+ * - hhea metrics should be used, since hhea is a mandatory font table and
+ * should always be present.
+ * - But if OS/2 is present, it should be used since it is mandatory in
+ * Windows.
+ * OS/2 has Typo and Win metrics, but the later was meant to control
+ * text clipping not line spacing and can be ridiculously large.
+ * Unfortunately many Windows application incorrectly use the Win metrics
+ * (thanks to GDI’s TEXTMETRIC) and old fonts might be designed with this
+ * in mind, so OpenType introduced a flag for fonts to indicate that they
+ * really want to use Typo metrics. So for best backward compatibility:
+ * - Use Win metrics if available.
+ * - Unless USE_TYPO_METRICS flag is set, in which case use Typo metrics.
+*/
+void ImplFontMetricData::ImplCalcLineSpacing(LogicalFontInstance *pFontInstance)
+{
+ mnAscent = mnDescent = mnExtLeading = mnIntLeading = 0;
+
+ hb_font_t* pHbFont = pFontInstance->GetHbFont();
+ hb_face_t* pHbFace = hb_font_get_face(pHbFont);
+
+ hb_blob_t* pHhea = hb_face_reference_table(pHbFace, HB_TAG('h', 'h', 'e', 'a'));
+ hb_blob_t* pOS2 = hb_face_reference_table(pHbFace, HB_TAG('O', 'S', '/', '2'));
+
+ vcl::TTGlobalFontInfo rInfo = {};
+ GetTTFontMetrics(reinterpret_cast<const uint8_t*>(hb_blob_get_data(pHhea, nullptr)), hb_blob_get_length(pHhea),
+ reinterpret_cast<const uint8_t*>(hb_blob_get_data(pOS2, nullptr)), hb_blob_get_length(pOS2),
+ &rInfo);
+
+ hb_blob_destroy(pHhea);
+ hb_blob_destroy(pOS2);
+
+ double nUPEM = hb_face_get_upem(pHbFace);
+ double fScale = mnHeight / nUPEM;
+ double fAscent = 0, fDescent = 0, fExtLeading = 0;
+
+ // Try hhea table first.
+ // tdf#107605: Some fonts have weird values here, so check that ascender is
+ // +ve and descender is -ve as they normally should.
+ if (rInfo.ascender >= 0 && rInfo.descender <= 0)
+ {
+ fAscent = rInfo.ascender * fScale;
+ fDescent = -rInfo.descender * fScale;
+ fExtLeading = rInfo.linegap * fScale;
+ }
+
+ // But if OS/2 is present, prefer it.
+ if (rInfo.winAscent || rInfo.winDescent ||
+ rInfo.typoAscender || rInfo.typoDescender)
+ {
+ if (ShouldUseWinMetrics(rInfo) || (fAscent == 0.0 && fDescent == 0.0))
+ {
+ fAscent = rInfo.winAscent * fScale;
+ fDescent = rInfo.winDescent * fScale;
+ fExtLeading = 0;
+ }
+
+ const uint16_t kUseTypoMetricsMask = 1 << 7;
+ if (rInfo.fsSelection & kUseTypoMetricsMask &&
+ rInfo.typoAscender >= 0 && rInfo.typoDescender <= 0)
+ {
+ fAscent = rInfo.typoAscender * fScale;
+ fDescent = -rInfo.typoDescender * fScale;
+ fExtLeading = rInfo.typoLineGap * fScale;
+ }
+ }
+
+ mnAscent = round(fAscent);
+ mnDescent = round(fDescent);
+ mnExtLeading = round(fExtLeading);
+
+ if (mnAscent || mnDescent)
+ mnIntLeading = mnAscent + mnDescent - mnHeight;
+
+ SAL_INFO("vcl.gdi.fontmetric", GetFamilyName()
+ << ": fsSelection: " << rInfo.fsSelection
+ << ", typoAscender: " << rInfo.typoAscender
+ << ", typoDescender: " << rInfo.typoDescender
+ << ", typoLineGap: " << rInfo.typoLineGap
+ << ", winAscent: " << rInfo.winAscent
+ << ", winDescent: " << rInfo.winDescent
+ << ", ascender: " << rInfo.ascender
+ << ", descender: " << rInfo.descender
+ << ", linegap: " << rInfo.linegap
+ );
+}
+
+void ImplFontMetricData::ImplInitBaselines(LogicalFontInstance *pFontInstance)
+{
+ hb_font_t* pHbFont = pFontInstance->GetHbFont();
+ hb_face_t* pHbFace = hb_font_get_face(pHbFont);
+ double nUPEM = hb_face_get_upem(pHbFace);
+ double fScale = mnHeight / nUPEM;
+ hb_position_t nBaseline = 0;
+
+ if (hb_ot_layout_get_baseline(pHbFont,
+ HB_OT_LAYOUT_BASELINE_TAG_HANGING,
+ HB_DIRECTION_INVALID,
+ HB_SCRIPT_UNKNOWN,
+ HB_TAG_NONE,
+ &nBaseline))
+ {
+ mnHangingBaseline = nBaseline * fScale;
+ }
+ else
+ {
+ mnHangingBaseline = 0;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/cff.cxx b/vcl/source/fontsubset/cff.cxx
new file mode 100644
index 000000000..ef756e2fa
--- /dev/null
+++ b/vcl/source/fontsubset/cff.cxx
@@ -0,0 +1,2240 @@
+/* -*- 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 <cstdio>
+#include <cstring>
+#include <vector>
+#include <assert.h>
+
+#include <fontsubset.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <strhelper.hxx>
+#include <sal/log.hxx>
+
+typedef sal_uInt8 U8;
+typedef sal_uInt16 U16;
+typedef sal_Int64 S64;
+
+typedef double RealType;
+typedef RealType ValType;
+
+static const char* pStringIds[] = {
+/*0*/ ".notdef", "space", "exclam", "quotedbl",
+ "numbersign", "dollar", "percent", "ampersand",
+ "quoteright", "parenleft", "parenright", "asterisk",
+ "plus", "comma", "hyphen", "period",
+/*16*/ "slash", "zero", "one", "two",
+ "three", "four", "five", "six",
+ "seven", "eight", "nine", "colon",
+ "semicolon", "less", "equal", "greater",
+/*32*/ "question", "at", "A", "B",
+ "C", "D", "E", "F",
+ "G", "H", "I", "J",
+ "K", "L", "M", "N",
+/*48*/ "O", "P", "Q", "R",
+ "S", "T", "U", "V",
+ "W", "X", "Y", "Z",
+ "bracketleft", "backslash", "bracketright", "asciicircum",
+/*64*/ "underscore", "quoteleft", "a", "b",
+ "c", "d", "e", "f",
+ "g", "h", "i", "j",
+ "k", "l", "m", "n",
+/*80*/ "o", "p", "q", "r",
+ "s", "t", "u", "v",
+ "w", "x", "y", "z",
+ "braceleft", "bar", "braceright", "asciitilde",
+/*96*/ "exclamdown", "cent", "sterlin", "fraction",
+ "yen", "florin", "section", "currency",
+ "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft",
+ "guilsinglright", "fi", "fl", "endash",
+/*112*/ "dagger", "daggerdbl", "periodcentered", "paragraph",
+ "bullet", "quotesinglbase", "quotedblbase", "quotedblright",
+ "guillemotright", "ellipsis", "perthousand", "questiondown",
+ "grave", "acute", "circumflex", "tilde",
+/*128*/ "macron", "breve", "dotaccent", "dieresis",
+ "ring", "cedilla", "hungarumlaut", "ogonek",
+ "caron", "emdash", "AE", "ordfeminine",
+ "Lslash", "Oslash", "OE", "ordmasculine",
+/*144*/ "ae", "dotlessi", "lslash", "oslash",
+ "oe", "germandbls", "onesuperior", "logicalnot",
+ "mu", "trademark", "Eth", "onehalf",
+ "plusminus", "Thorn", "onequarter", "divide",
+/*160*/ "brokenbar", "degree", "thorn", "threequarters",
+ "twosuperior", "registered", "minus", "eth",
+ "multiply", "threesuperior", "copyright", "Aacute",
+ "Acircumflex", "Adieresis", "Agrave", "Aring",
+/*176*/ "Atilde", "Ccedilla", "Eacute", "Ecircumflex",
+ "Edieresis", "Egrave", "Iacute", "Icircumflex",
+ "Idieresis", "Igrave", "Ntilde", "Oacute",
+ "Ocircumflex", "Odieresis", "Ograve", "Otilde",
+/*192*/ "Scaron", "Uacute", "Ucircumflex", "Udieresis",
+ "Ugrave", "Yacute", "Ydieresis", "Zcaron",
+ "aacute", "acircumflex", "adieresis", "agrave",
+ "aring", "atilde", "ccedilla", "eacute",
+/*208*/ "ecircumflex", "edieresis", "egrave", "iacute",
+ "icircumflex", "idieresis", "igrave", "ntilde",
+ "oacute", "ocircumflex", "odieresis", "ograve",
+ "otilde", "scaron", "uacute", "ucircumflex",
+/*224*/ "udieresis", "ugrave", "yacute", "ydieresis",
+ "zcaron", "exclamsmall", "Hungarumlautsmall","dollaroldstyle",
+ "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior",
+ "parenrightsuperior","twodotenleader", "onedotenleader", "zerooldstyle",
+/*240*/ "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle",
+ "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle",
+ "nineoldstile", "commasuperior", "threequartersemdash","periodsuperior",
+ "questionsmall", "asuperior", "bsuperior", "centsuperior",
+/*256*/ "dsuperior", "esuperior", "isuperior", "lsuperior",
+ "msuperior", "nsuperior", "osuperior", "rsuperior",
+ "ssuperior", "tsuperior", "ff", "ffi",
+ "ffl", "parenleftinferior","parenrightinferior","Circumflexsmall",
+/*272*/ "hyphensuperior","Gravesmall", "Asmall", "Bsmall",
+ "Csmall", "Dsmall", "Esmall", "Fsmall",
+ "Gsmall", "Hsmall", "Ismall", "Jsmall",
+ "Ksmall", "Lsmall", "Msmall", "Nsmall",
+/*288*/ "Osmall", "Psmall", "Qsmall", "Rsmall",
+ "Ssmall", "Tsmall", "Usmall", "Vsmall",
+ "Wsmall", "Xsmall", "Ysmall", "Zsmall",
+ "colonmonetary", "onefitted", "rupia", "Tildesmall",
+/*304*/ "exclamdownsmall","centoldstyle", "Lslashsmall", "Scaronsmall",
+ "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall",
+ "Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior",
+ "Ogoneksmall", "Ringsmall", "Cedillasmall", "questiondownsmall",
+/*320*/ "oneeight", "threeeights", "fiveeights", "seveneights",
+ "onethird", "twothirds", "zerosuperior", "foursuperior",
+ "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior",
+ "ninesuperior", "zeroinferior", "oneinferior", "twoinferior",
+/*336*/ "threeinferior","fourinferior", "fiveinferior", "sixinferior",
+ "seveninferior", "eightinferior", "nineinferior", "centinferior",
+ "dollarinferior", "periodinferior", "commainferior", "Agravesmall",
+ "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall",
+/*352*/ "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall",
+ "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall",
+ "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall",
+ "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall",
+/*368*/ "Otildesmall", "Odieressissmall", "OEsmall", "Oslashsmall",
+ "Ugravesmall", "Uacutesmall", "Ucircumflexsmall", "Udieresissmall",
+ "Yacutesmall", "Thornsmall", "Ydieresissmall", "001.000",
+ "001.001", "001.002", "001.003", "Black",
+/*384*/ "Bold", "Book", "Light", "Medium",
+ "Regular", "Roman", "Semibold"
+};
+
+// TOP DICT keywords (also covers PRIV DICT keywords)
+static const char* pDictOps[] = {
+ "sVersion", "sNotice", "sFullName", "sFamilyName",
+ "sWeight", "aFontBBox", "dBlueValues", "dOtherBlues",
+ "dFamilyBlues", "dFamilyOtherBlues", "nStdHW", "nStdVW",
+ "xESC", "nUniqueID", "aXUID", "nCharset",
+ "nEncoding", "nCharStrings", "PPrivate", "nSubrs",
+ "nDefaultWidthX", "nNominalWidthX", nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr,
+ "shortint", "longint", "BCD", nullptr
+};
+
+// TOP DICT escapes (also covers PRIV DICT escapes)
+static const char* pDictEscs[] = {
+ "sCopyright", "bIsFixedPitch", "nItalicAngle", "nUnderlinePosition",
+ "nUnderlineThickness", "nPaintType", "tCharstringType", "aFontMatrix",
+ "nStrokeWidth", "nBlueScale", "nBlueShift", "nBlueFuzz",
+ "dStemSnapH", "dStemSnapV", "bForceBold", nullptr,
+ nullptr, "nLanguageGroup", "nExpansionFactor", "nInitialRandomSeed",
+ "nSyntheticBase", "sPostScript", "sBaseFontName", "dBaseFontBlend",
+ nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, "rROS", "nCIDFontVersion",
+ "nCIDFontRevision", "nCIDFontType", "nCIDCount", "nUIDBase",
+ "nFDArray", "nFDSelect", "sFontName"
+};
+
+static const char* pStandardEncoding[] = {
+ ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
+ ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
+ ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
+ ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
+ ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
+ ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
+ ".notdef", ".notdef", "space", "exclam", "quotedbl",
+ "numbersign", "dollar", "percent", "ampersand",
+ "quoteright", "parenleft", "parenright", "asterisk", "plus",
+ "comma", "hyphen", "period", "slash", "zero", "one", "two",
+ "three", "four", "five", "six", "seven", "eight", "nine",
+ "colon", "semicolon", "less", "equal", "greater",
+ "question", "at", "A", "B", "C", "D", "E", "F", "G", "H",
+ "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
+ "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash",
+ "bracketright", "asciicircum", "underscore", "quoteleft",
+ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l",
+ "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x",
+ "y", "z", "braceleft", "bar", "braceright", "asciitilde",
+ ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
+ ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
+ ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
+ ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
+ ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
+ ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
+ ".notdef", ".notdef", ".notdef", ".notdef", "exclamdown",
+ "cent", "sterling", "fraction", "yen", "florin", "section",
+ "currency", "quotesingle", "quotedblleft", "guillemotleft",
+ "guilsinglleft", "guilsinglright", "fi", "fl", ".notdef",
+ "endash", "dagger", "daggerdbl", "periodcentered",
+ ".notdef", "paragraph", "bullet", "quotesinglbase",
+ "quotedblbase", "quotedblright", "guillemotright",
+ "ellipsis", "perthousand", ".notdef", "questiondown",
+ ".notdef", "grave", "acute", "circumflex", "tilde",
+ "macron", "breve", "dotaccent", "dieresis", ".notdef",
+ "ring", "cedilla", ".notdef", "hungarumlaut", "ogonek",
+ "caron", "emdash", ".notdef", ".notdef", ".notdef",
+ ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
+ ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
+ ".notdef", ".notdef", ".notdef", "AE", ".notdef",
+ "ordfeminine", ".notdef", ".notdef", ".notdef", ".notdef",
+ "Lslash", "Oslash", "OE", "ordmasculine", ".notdef",
+ ".notdef", ".notdef", ".notdef", ".notdef", "ae", ".notdef",
+ ".notdef", ".notdef", "dotlessi", ".notdef", ".notdef",
+ "lslash", "oslash", "oe", "germandbls", ".notdef",
+ ".notdef", ".notdef", ".notdef"
+};
+
+namespace {
+
+namespace TYPE1OP
+{
+ enum OPS
+ {
+ HSTEM=1, VSTEM=3, VMOVETO=4, RLINETO=5,
+ HLINETO=6, VLINETO=7, RCURVETO=8, CLOSEPATH=9,
+ CALLSUBR=10, RETURN=11, T1ESC=12, HSBW=13,
+ ENDCHAR=14, RMOVETO=21, HMOVETO=22, VHCURVETO=30,
+ HVCURVETO=31
+ };
+
+ enum ESCS
+ {
+ DOTSECTION=0, VSTEM3=1, HSTEM3=2, SEAC=6,
+ SBW=7, ABS=9, ADD=10, SUB=11,
+ DIV=12, CALLOTHERSUBR=16, POP=17, SETCURRENTPOINT=33
+ };
+}
+
+namespace TYPE2OP
+{
+ enum OPS
+ {
+ HSTEM=1, VSTEM=3, VMOVETO=4, RLINETO=5,
+ HLINETO=6, VLINETO=7, RCURVETO=8, CALLSUBR=10,
+ RETURN=11, T2ESC=12, ENDCHAR=14, HSTEMHM=18,
+ HINTMASK=19, CNTRMASK=20, RMOVETO=21, HMOVETO=22,
+ VSTEMHM=23, RCURVELINE=24, RLINECURVE=25, VVCURVETO=26,
+ HHCURVETO=27, SHORTINT=28, CALLGSUBR=29, VHCURVETO=30,
+ HVCURVETO=31
+ };
+
+ enum ESCS
+ {
+ AND=3, OR=4, NOT=5, ABS=9,
+ ADD=10, SUB=11, DIV=12, NEG=14,
+ EQ=15, DROP=18, PUT=20, GET=21,
+ IFELSE=22, RANDOM=23, MUL=24, SQRT=26,
+ DUP=27, EXCH=28, INDEX=29, ROLL=30,
+ HFLEX=34, FLEX=35, HFLEX1=36, FLEX1=37
+ };
+}
+
+struct CffGlobal
+{
+ explicit CffGlobal();
+
+ int mnNameIdxBase;
+ int mnStringIdxBase;
+ bool mbCIDFont;
+ int mnCharStrBase;
+ int mnCharStrCount;
+ int mnCharsetBase;
+ int mnGlobalSubrBase;
+ int mnGlobalSubrCount;
+ int mnGlobalSubrBias;
+ int mnFDSelectBase;
+ int mnFontDictBase;
+ int mnFDAryCount;
+
+ std::vector<ValType> maFontBBox;
+ std::vector<ValType> maFontMatrix;
+
+ int mnFontNameSID;
+ int mnFullNameSID;
+};
+
+struct CffLocal
+{
+ explicit CffLocal();
+
+ int mnPrivDictBase;
+ int mnPrivDictSize;
+ int mnLocalSubrOffs;
+ int mnLocalSubrBase;
+ int mnLocalSubrBias;
+
+ ValType maNominalWidth;
+ ValType maDefaultWidth;
+
+ // ATM hinting related values
+ ValType maStemStdHW;
+ ValType maStemStdVW;
+ std::vector<ValType> maStemSnapH;
+ std::vector<ValType> maStemSnapV;
+ std::vector<ValType> maBlueValues;
+ std::vector<ValType> maOtherBlues;
+ std::vector<ValType> maFamilyBlues;
+ std::vector<ValType> maFamilyOtherBlues;
+ RealType mfBlueScale;
+ RealType mfBlueShift;
+ RealType mfBlueFuzz;
+ RealType mfExpFactor;
+ int mnLangGroup;
+ bool mbForceBold;
+};
+
+const int MAX_T1OPS_SIZE = 81920; // TODO: use dynamic value
+
+struct CharString
+{
+ int nLen;
+ U8 aOps[MAX_T1OPS_SIZE];
+ int nCffGlyphId;
+ ValType aCharWidth;
+};
+
+
+class CffSubsetterContext
+: private CffGlobal
+{
+public:
+ static const int NMAXSTACK = 48; // see CFF.appendixB
+ static const int NMAXHINTS = 2*96; // see CFF.appendixB
+ static const int NMAXTRANS = 32; // see CFF.appendixB
+
+ explicit CffSubsetterContext( const U8* pBasePtr, int nBaseLen);
+
+ bool initialCffRead();
+ void emitAsType1( class Type1Emitter&,
+ const sal_GlyphId* pGlyphIds, const U8* pEncoding,
+ sal_Int32* pGlyphWidths, int nGlyphCount, FontSubsetInfo& );
+
+private:
+ void convertCharStrings(const sal_GlyphId* pGlyphIds, int nGlyphCount,
+ std::vector<CharString>& rCharStrings);
+ int convert2Type1Ops( CffLocal*, const U8* pType2Ops, int nType2Len, U8* pType1Ops);
+ void convertOneTypeOp();
+ void convertOneTypeEsc();
+ void callType2Subr( bool bGlobal, int nSubrNumber);
+ sal_Int32 getReadOfs() const { return static_cast<sal_Int32>(mpReadPtr - mpBasePtr);}
+
+ const U8* mpBasePtr;
+ const U8* mpBaseEnd;
+
+ const U8* mpReadPtr;
+ const U8* mpReadEnd;
+
+ U8* mpWritePtr;
+ bool mbNeedClose;
+ bool mbIgnoreHints;
+ sal_Int32 mnCntrMask;
+
+ int seekIndexData( int nIndexBase, int nDataIndex);
+ void seekIndexEnd( int nIndexBase);
+
+ CffLocal maCffLocal[256];
+ CffLocal* mpCffLocal;
+
+ void readDictOp();
+ RealType readRealVal();
+ const char* getString( int nStringID);
+ int getFDSelect( int nGlyphIndex) const;
+ int getGlyphSID( int nGlyphIndex) const;
+ const char* getGlyphName( int nGlyphIndex);
+ bool getBaseAccent(ValType aBase, ValType aAccent, int* nBase, int* nAccent);
+
+ void read2push();
+ void writeType1Val( ValType);
+ void writeTypeOp( int nTypeOp);
+ void writeTypeEsc( int nTypeOp);
+ void writeCurveTo( int nStackPos, int nIX1, int nIY1, int nIX2, int nIY2, int nIX3, int nIY3);
+ void pop2MultiWrite( int nArgsPerTypo, int nTypeOp, int nTypeXor=0);
+ void popAll2Write( int nTypeOp);
+
+public: // TODO: is public really needed?
+ // accessing the value stack
+ // TODO: add more checks
+ void push( ValType nVal) { mnValStack[ mnStackIdx++] = nVal;}
+ ValType popVal() { return ((mnStackIdx>0) ? mnValStack[ --mnStackIdx] : 0);}
+ ValType getVal( int nIndex) const { return mnValStack[ nIndex];}
+ int popInt();
+ int size() const { return mnStackIdx;}
+ void clear() { mnStackIdx = 0;}
+
+ // accessing the charstring hints
+ void addHints( bool bVerticalHints);
+
+ // accessing other charstring specifics
+ void updateWidth( bool bUseFirstVal);
+
+private:
+ // typeop execution context
+ int mnStackIdx;
+ ValType mnValStack[ NMAXSTACK+4];
+ ValType mnTransVals[ NMAXTRANS];
+
+ int mnHintSize;
+ int mnHorzHintSize;
+ ValType mnHintStack[ NMAXHINTS];
+
+ ValType maCharWidth;
+
+ bool mbDoSeac;
+ std::vector<sal_GlyphId> maExtraGlyphIds;
+};
+
+}
+
+CffSubsetterContext::CffSubsetterContext( const U8* pBasePtr, int nBaseLen)
+ : mpBasePtr( pBasePtr)
+ , mpBaseEnd( pBasePtr+nBaseLen)
+ , mpReadPtr(nullptr)
+ , mpReadEnd(nullptr)
+ , mpWritePtr(nullptr)
+ , mbNeedClose(false)
+ , mbIgnoreHints(false)
+ , mnCntrMask(0)
+ , mnStackIdx(0)
+ , mnValStack{}
+ , mnTransVals{}
+ , mnHintSize(0)
+ , mnHorzHintSize(0)
+ , mnHintStack{}
+ , maCharWidth(-1)
+ , mbDoSeac(true)
+{
+// setCharStringType( 1);
+ // TODO: new CffLocal[ mnFDAryCount];
+ mpCffLocal = &maCffLocal[0];
+}
+
+inline int CffSubsetterContext::popInt()
+{
+ const ValType aVal = popVal();
+ const int nInt = static_cast<int>(aVal);
+ assert( nInt == aVal);
+ return nInt;
+}
+
+inline void CffSubsetterContext::updateWidth( bool bUseFirstVal)
+{
+ // the first value is not a hint but the charwidth
+ if( maCharWidth>0 )
+ return;
+
+ if( bUseFirstVal) {
+ maCharWidth = mpCffLocal->maNominalWidth + mnValStack[0];
+ // remove bottom stack entry
+ --mnStackIdx;
+ for( int i = 0; i < mnStackIdx; ++i)
+ mnValStack[ i] = mnValStack[ i+1];
+ } else {
+ maCharWidth = mpCffLocal->maDefaultWidth;
+ }
+}
+
+void CffSubsetterContext::addHints( bool bVerticalHints)
+{
+ // the first charstring value may a charwidth instead of a charwidth
+ updateWidth( (mnStackIdx & 1) != 0);
+ // return early (e.g. no implicit hints for hintmask)
+ if( !mnStackIdx)
+ return;
+
+ // copy the remaining values to the hint arrays
+ // assert( (mnStackIdx & 1) == 0); // depends on called subrs
+ if( mnStackIdx & 1) --mnStackIdx;//#######
+ // TODO: if( !bSubr) assert( mnStackIdx >= 2);
+
+ assert( (mnHintSize + mnStackIdx) <= 2*NMAXHINTS);
+
+ ValType nHintOfs = 0;
+ for( int i = 0; i < mnStackIdx; ++i) {
+ nHintOfs += mnValStack[ i ];
+ mnHintStack[ mnHintSize++] = nHintOfs;
+ }
+
+ if( !bVerticalHints)
+ mnHorzHintSize = mnHintSize;
+
+ // clear all values from the stack
+ mnStackIdx = 0;
+}
+
+void CffSubsetterContext::readDictOp()
+{
+ const U8 c = *mpReadPtr;
+ if( c <= 21 ) {
+ int nOpId = *(mpReadPtr++);
+ const char* pCmdName = nullptr;
+ if( nOpId != 12)
+ pCmdName = pDictOps[nOpId];
+ else {
+ const U8 nExtId = *(mpReadPtr++);
+ if (nExtId < 39)
+ pCmdName = pDictEscs[nExtId];
+ nOpId = 900 + nExtId;
+ }
+
+ if (!pCmdName) // skip reserved operators
+ return;
+
+ //TODO: if( nStackIdx > 0)
+ int nInt = 0;
+ switch( *pCmdName) {
+ default: SAL_WARN("vcl.fonts", "unsupported DictOp.type='" << *pCmdName << "'."); break;
+ case 'b': // bool
+ nInt = popInt();
+ switch( nOpId) {
+ case 915: mpCffLocal->mbForceBold = nInt; break; // "ForceBold"
+ default: break; // TODO: handle more boolean dictops?
+ }
+ break;
+ case 'n': { // dict-op number
+ ValType nVal = popVal();
+ nInt = static_cast<int>(nVal);
+ switch( nOpId) {
+ case 10: mpCffLocal->maStemStdHW = nVal; break; // "StdHW"
+ case 11: mpCffLocal->maStemStdVW = nVal; break; // "StdVW"
+ case 15: mnCharsetBase = nInt; break; // "charset"
+ case 16: break; // "nEncoding"
+ case 17: mnCharStrBase = nInt; break; // "nCharStrings"
+ case 19: mpCffLocal->mnLocalSubrOffs = nInt; break;// "nSubrs"
+ case 20: mpCffLocal->maDefaultWidth = nVal; break; // "defaultWidthX"
+ case 21: mpCffLocal->maNominalWidth = nVal; break; // "nominalWidthX"
+ case 909: mpCffLocal->mfBlueScale = nVal; break; // "BlueScale"
+ case 910: mpCffLocal->mfBlueShift = nVal; break; // "BlueShift"
+ case 911: mpCffLocal->mfBlueFuzz = nVal; break; // "BlueFuzz"
+ case 912: mpCffLocal->mfExpFactor = nVal; break; // "ExpansionFactor"
+ case 917: mpCffLocal->mnLangGroup = nInt; break; // "LanguageGroup"
+ case 936: mnFontDictBase = nInt; break; // "nFDArray"
+ case 937: mnFDSelectBase = nInt; break; // "nFDSelect"
+ default: break; // TODO: handle more numeric dictops?
+ }
+ } break;
+ case 'a': { // array
+ switch( nOpId) {
+ case 5: maFontBBox.clear(); break; // "FontBBox"
+ case 907: maFontMatrix.clear(); break; // "FontMatrix"
+ default: break; // TODO: reset other arrays?
+ }
+ for( int i = 0; i < size(); ++i ) {
+ ValType nVal = getVal(i);
+ switch( nOpId) {
+ case 5: maFontBBox.push_back( nVal); break; // "FontBBox"
+ case 907: maFontMatrix.push_back( nVal); break; // "FontMatrix"
+ default: break; // TODO: handle more array dictops?
+ }
+ }
+ clear();
+ } break;
+ case 'd': { // delta array
+ ValType nVal = 0;
+ for( int i = 0; i < size(); ++i ) {
+ nVal += getVal(i);
+ switch( nOpId) {
+ case 6: mpCffLocal->maBlueValues.push_back( nVal); break; // "BlueValues"
+ case 7: mpCffLocal->maOtherBlues.push_back( nVal); break; // "OtherBlues"
+ case 8: mpCffLocal->maFamilyBlues.push_back( nVal); break; // "FamilyBlues"
+ case 9: mpCffLocal->maFamilyOtherBlues.push_back( nVal); break;// "FamilyOtherBlues"
+ case 912: mpCffLocal->maStemSnapH.push_back( nVal); break; // "StemSnapH"
+ case 913: mpCffLocal->maStemSnapV.push_back( nVal); break; // "StemSnapV"
+ default: break; // TODO: handle more delta-array dictops?
+ }
+ }
+ clear();
+ } break;
+ case 's': // stringid (SID)
+ nInt = popInt();
+ switch( nOpId ) {
+ case 2: mnFullNameSID = nInt; break; // "FullName"
+ case 3: break; // "FamilyName"
+ case 938: mnFontNameSID = nInt; break; // "FontName"
+ default: break; // TODO: handle more string dictops?
+ }
+ break;
+ case 'P': // private dict
+ mpCffLocal->mnPrivDictBase = popInt();
+ mpCffLocal->mnPrivDictSize = popInt();
+ break;
+ case 'r': { // ROS operands
+ popInt(); // TODO: use sid1
+ popInt(); // TODO: use sid2
+ popVal();
+ mbCIDFont = true;
+ } break;
+ case 't': // CharstringType
+ popInt();
+ break;
+ }
+ } else if( (c >= 32) || (c == 28) ) {
+// --mpReadPtr;
+ read2push();
+ } else if( c == 29 ) { // longint
+ ++mpReadPtr; // skip 29
+ sal_Int32 nS32 = mpReadPtr[0] << 24;
+ nS32 += mpReadPtr[1] << 16;
+ nS32 += mpReadPtr[2] << 8;
+ nS32 += mpReadPtr[3] << 0;
+ mpReadPtr += 4;
+ ValType nVal = static_cast<ValType>(nS32);
+ push( nVal );
+ } else if( c == 30) { // real number
+ ++mpReadPtr; // skip 30
+ const RealType fReal = readRealVal();
+ // push value onto stack
+ ValType nVal = fReal;
+ push( nVal);
+ }
+}
+
+void CffSubsetterContext::read2push()
+{
+ ValType aVal = 0;
+
+ const U8*& p = mpReadPtr;
+ const U8 c = *p;
+ if( c == 28 ) {
+ sal_Int16 nS16 = (p[1] << 8) + p[2];
+ aVal = nS16;
+ p += 3;
+ } else if( c <= 246 ) { // -107..+107
+ aVal = static_cast<ValType>(p[0] - 139);
+ p += 1;
+ } else if( c <= 250 ) { // +108..+1131
+ aVal = static_cast<ValType>(((p[0] << 8) + p[1]) - 63124);
+ p += 2;
+ } else if( c <= 254 ) { // -108..-1131
+ aVal = static_cast<ValType>(64148 - ((p[0] << 8) + p[1]));
+ p += 2;
+ } else /*if( c == 255)*/ { // Fixed16.16
+ int nS32 = (p[1] << 24) + (p[2] << 16) + (p[3] << 8) + p[4];
+ if( (sizeof(nS32) != 2) && (nS32 & (1U<<31)))
+ nS32 |= (~0U) << 31; // assuming 2s complement
+ aVal = static_cast<ValType>(nS32 * (1.0 / 0x10000));
+ p += 5;
+ }
+
+ push( aVal);
+}
+
+void CffSubsetterContext::writeType1Val( ValType aVal)
+{
+ U8* pOut = mpWritePtr;
+
+ // tdf#126242
+ // Type2 has 16.16 fixed numbers, but Type1 does not. To represent values
+ // with fractions we multiply it by a factor then use “div” operator to
+ // divide it back and keep the fractions.
+ // Code Adapted from:
+ // https://github.com/fontforge/fontforge/blob/f152f12e567ea5bd737a2907c318ae26cfaabd08/fontforge/splinesave.c#L378
+ int nDiv = 0;
+ aVal = rint(aVal * 1024) / 1024;
+ if (aVal != floor(aVal))
+ {
+ if (aVal == rint(aVal * 64) / 64)
+ nDiv = 64;
+ else
+ nDiv = 1024;
+ aVal *= nDiv;
+ }
+
+ int nInt = static_cast<int>(rint(aVal));
+ if (nDiv && floor(nInt) / nDiv == floor(nInt / nDiv))
+ {
+ nInt = rint(nInt / nDiv);
+ nDiv = 0;
+ }
+
+ if( (nInt >= -107) && (nInt <= +107)) {
+ *(pOut++) = static_cast<U8>(nInt + 139); // -107..+107
+ } else if( (nInt >= -1131) && (nInt <= +1131)) {
+ if( nInt >= 0)
+ nInt += 63124; // +108..+1131
+ else
+ nInt = 64148 - nInt; // -108..-1131
+ *(pOut++) = static_cast<U8>(nInt >> 8);
+ *(pOut++) = static_cast<U8>(nInt);
+ } else {
+ // numtype==255 means int32 for Type1, but 16.16 for Type2 charstrings!!!
+ *(pOut++) = 255;
+ *(pOut++) = static_cast<U8>(nInt >> 24);
+ *(pOut++) = static_cast<U8>(nInt >> 16);
+ *(pOut++) = static_cast<U8>(nInt >> 8);
+ *(pOut++) = static_cast<U8>(nInt);
+ }
+
+ mpWritePtr = pOut;
+
+ if (nDiv)
+ {
+ writeType1Val(nDiv);
+ writeTypeEsc(TYPE1OP::DIV);
+ }
+}
+
+inline void CffSubsetterContext::writeTypeOp( int nTypeOp)
+{
+ *(mpWritePtr++) = static_cast<U8>(nTypeOp);
+}
+
+inline void CffSubsetterContext::writeTypeEsc( int nTypeEsc)
+{
+ *(mpWritePtr++) = TYPE1OP::T1ESC;
+ *(mpWritePtr++) = static_cast<U8>(nTypeEsc);
+}
+
+void CffSubsetterContext::pop2MultiWrite( int nArgsPerTypo, int nTypeOp, int nTypeXor)
+{
+ for( int i = 0; i < mnStackIdx;) {
+ for( int j = 0; j < nArgsPerTypo; ++j) {
+ const ValType aVal = mnValStack[i+j];
+ writeType1Val( aVal);
+ }
+ i += nArgsPerTypo;
+ writeTypeOp( nTypeOp);
+ nTypeOp ^= nTypeXor; // for toggling vlineto/hlineto
+ }
+ clear();
+}
+
+void CffSubsetterContext::popAll2Write( int nTypeOp)
+{
+ // pop in reverse order, then write
+ for( int i = 0; i < mnStackIdx; ++i) {
+ const ValType aVal = mnValStack[i];
+ writeType1Val( aVal);
+ }
+ clear();
+ writeTypeOp( nTypeOp);
+}
+
+void CffSubsetterContext::writeCurveTo( int nStackPos,
+ int nIX1, int nIY1, int nIX2, int nIY2, int nIX3, int nIY3)
+{
+ // get the values from the stack
+ const ValType nDX1 = nIX1 ? mnValStack[ nStackPos+nIX1 ] : 0;
+ const ValType nDY1 = nIY1 ? mnValStack[ nStackPos+nIY1 ] : 0;
+ const ValType nDX2 = nIX2 ? mnValStack[ nStackPos+nIX2 ] : 0;
+ const ValType nDY2 = nIY2 ? mnValStack[ nStackPos+nIY2 ] : 0;
+ const ValType nDX3 = nIX3 ? mnValStack[ nStackPos+nIX3 ] : 0;
+ const ValType nDY3 = nIY3 ? mnValStack[ nStackPos+nIY3 ] : 0;
+
+ // emit the curveto operator and operands
+ // TODO: determine the most efficient curveto operator
+ // TODO: depending on type1op or type2op target
+ writeType1Val( nDX1 );
+ writeType1Val( nDY1 );
+ writeType1Val( nDX2 );
+ writeType1Val( nDY2 );
+ writeType1Val( nDX3 );
+ writeType1Val( nDY3 );
+ writeTypeOp( TYPE1OP::RCURVETO );
+}
+
+void CffSubsetterContext::convertOneTypeOp()
+{
+ const int nType2Op = *(mpReadPtr++);
+
+ int i, nInt; // prevent WAE for declarations inside switch cases
+ // convert each T2op
+ switch( nType2Op) {
+ case TYPE2OP::T2ESC:
+ convertOneTypeEsc();
+ break;
+ case TYPE2OP::HSTEM:
+ case TYPE2OP::VSTEM:
+ addHints( nType2Op == TYPE2OP::VSTEM );
+ for( i = 0; i < mnHintSize; i+=2 ) {
+ writeType1Val( mnHintStack[i]);
+ writeType1Val( mnHintStack[i+1] - mnHintStack[i]);
+ writeTypeOp( nType2Op );
+ }
+ break;
+ case TYPE2OP::HSTEMHM:
+ case TYPE2OP::VSTEMHM:
+ addHints( nType2Op == TYPE2OP::VSTEMHM);
+ break;
+ case TYPE2OP::CNTRMASK:
+ // TODO: replace cntrmask with vstem3/hstem3
+ addHints( true);
+ {
+ U8 nMaskBit = 0;
+ U8 nMaskByte = 0;
+ for( i = 0; i < mnHintSize; i+=2, nMaskBit>>=1) {
+ if( !nMaskBit) {
+ nMaskByte = *(mpReadPtr++);
+ nMaskBit = 0x80;
+ }
+ if( !(nMaskByte & nMaskBit))
+ continue;
+ if( i >= 8*int(sizeof(mnCntrMask)))
+ mbIgnoreHints = true;
+ if( mbIgnoreHints)
+ continue;
+ mnCntrMask |= (1U << i);
+ }
+ }
+ break;
+ case TYPE2OP::HINTMASK:
+ addHints( true);
+ {
+ sal_Int32 nHintMask = 0;
+ int nCntrBits[2] = {0,0};
+ U8 nMaskBit = 0;
+ U8 nMaskByte = 0;
+ int const MASK_BITS = 8*sizeof(nHintMask);
+ for( i = 0; i < mnHintSize; i+=2, nMaskBit>>=1) {
+ if( !nMaskBit) {
+ nMaskByte = *(mpReadPtr++);
+ nMaskBit = 0x80;
+ }
+ if( !(nMaskByte & nMaskBit))
+ continue;
+ if( i >= MASK_BITS)
+ mbIgnoreHints = true;
+ if( mbIgnoreHints)
+ continue;
+ nHintMask |= (1U << i);
+ nCntrBits[ i < mnHorzHintSize] += (mnCntrMask >> i) & 1;
+ }
+
+ mbIgnoreHints |= (nCntrBits[0] && (nCntrBits[0] != 3));
+ mbIgnoreHints |= (nCntrBits[1] && (nCntrBits[1] != 3));
+ if( mbIgnoreHints)
+ break;
+
+ for( i = 0; i < mnHintSize; i+=2) {
+ if(i >= MASK_BITS || !(nHintMask & (1U << i)))
+ continue;
+ writeType1Val( mnHintStack[i]);
+ writeType1Val( mnHintStack[i+1] - mnHintStack[i]);
+ const bool bHorz = (i < mnHorzHintSize);
+ if( !nCntrBits[ bHorz])
+ writeTypeOp( bHorz ? TYPE1OP::HSTEM : TYPE1OP::VSTEM);
+ else if( !--nCntrBits[ bHorz])
+ writeTypeEsc( bHorz ? TYPE1OP::HSTEM3 : TYPE1OP::VSTEM3);
+ }
+ }
+ break;
+ case TYPE2OP::CALLSUBR:
+ case TYPE2OP::CALLGSUBR:
+ {
+ nInt = popInt();
+ const bool bGlobal = (nType2Op == TYPE2OP::CALLGSUBR);
+ callType2Subr( bGlobal, nInt);
+ }
+ break;
+ case TYPE2OP::RETURN:
+ // TODO: check that we are in a subroutine
+ return;
+ case TYPE2OP::VMOVETO:
+ case TYPE2OP::HMOVETO:
+ if( mbNeedClose)
+ writeTypeOp( TYPE1OP::CLOSEPATH);
+ else
+ updateWidth( size() > 1);
+ mbNeedClose = true;
+ pop2MultiWrite( 1, nType2Op);
+ break;
+ case TYPE2OP::VLINETO:
+ case TYPE2OP::HLINETO:
+ pop2MultiWrite( 1, nType2Op,
+ TYPE1OP::VLINETO ^ TYPE1OP::HLINETO);
+ break;
+ case TYPE2OP::RMOVETO:
+ // TODO: convert rmoveto to vlineto/hlineto if possible
+ if( mbNeedClose)
+ writeTypeOp( TYPE1OP::CLOSEPATH);
+ else
+ updateWidth( size() > 2);
+ mbNeedClose = true;
+ pop2MultiWrite( 2, nType2Op);
+ break;
+ case TYPE2OP::RLINETO:
+ // TODO: convert rlineto to vlineto/hlineto if possible
+ pop2MultiWrite( 2, nType2Op);
+ break;
+ case TYPE2OP::RCURVETO:
+ // TODO: convert rcurveto to vh/hv/hh/vv-curveto if possible
+ pop2MultiWrite( 6, nType2Op);
+ break;
+ case TYPE2OP::RCURVELINE:
+ i = 0;
+ while( (i += 6) <= mnStackIdx)
+ writeCurveTo( i, -6, -5, -4, -3, -2, -1 );
+ i -= 6;
+ while( (i += 2) <= mnStackIdx) {
+ writeType1Val( mnValStack[i-2]);
+ writeType1Val( mnValStack[i-1]);
+ writeTypeOp( TYPE2OP::RLINETO);
+ }
+ clear();
+ break;
+ case TYPE2OP::RLINECURVE:
+ i = 0;
+ while( (i += 2) <= mnStackIdx-6) {
+ writeType1Val( mnValStack[i-2]);
+ writeType1Val( mnValStack[i-1]);
+ writeTypeOp( TYPE2OP::RLINETO);
+ }
+ i -= 2;
+ while( (i += 6) <= mnStackIdx)
+ writeCurveTo( i, -6, -5, -4, -3, -2, -1 );
+ clear();
+ break;
+ case TYPE2OP::VHCURVETO:
+ case TYPE2OP::HVCURVETO:
+ {
+ bool bVert = (nType2Op == TYPE2OP::VHCURVETO);
+ i = 0;
+ nInt = 0;
+ if( mnStackIdx & 1 )
+ nInt = static_cast<int>(mnValStack[ --mnStackIdx ]);
+ while( (i += 4) <= mnStackIdx) {
+ // TODO: use writeCurveTo()
+ if( bVert ) writeType1Val( 0 );
+ writeType1Val( mnValStack[i-4] );
+ if( !bVert ) writeType1Val( 0);
+ writeType1Val( mnValStack[i-3] );
+ writeType1Val( mnValStack[i-2] );
+ if( !bVert ) writeType1Val( static_cast<ValType>((i==mnStackIdx) ? nInt : 0) );
+ writeType1Val( mnValStack[i-1] );
+ if( bVert ) writeType1Val( static_cast<ValType>((i==mnStackIdx) ? nInt : 0) );
+ bVert = !bVert;
+ writeTypeOp( TYPE2OP::RCURVETO);
+ }
+ }
+ clear();
+ break;
+ case TYPE2OP::HHCURVETO:
+ i = (mnStackIdx & 1);
+ while( (i += 4) <= mnStackIdx) {
+ if( i != 5)
+ writeCurveTo( i, -4, 0, -3, -2, -1, 0);
+ else
+ writeCurveTo( i, -4, -5, -3, -2, -1, 0);
+ }
+ clear();
+ break;
+ case TYPE2OP::VVCURVETO:
+ i = (mnStackIdx & 1);
+ while( (i += 4) <= mnStackIdx) {
+ if( i != 5)
+ writeCurveTo( i, 0, -4, -3, -2, 0, -1);
+ else
+ writeCurveTo( i, -5, -4, -3, -2, 0, -1);
+ }
+ clear();
+ break;
+ case TYPE2OP::ENDCHAR:
+ if (size() >= 4 && mbDoSeac)
+ {
+ // Deprecated seac-like use of endchar (Adobe Technical Note #5177,
+ // Appendix C).
+ auto achar = popVal();
+ auto bchar = popVal();
+ auto ady = popVal();
+ auto adx = popVal();
+ int nBase, nAccent;
+ if (getBaseAccent(bchar, achar, &nBase, &nAccent))
+ {
+ maExtraGlyphIds.push_back(nBase);
+ maExtraGlyphIds.push_back(nAccent);
+ writeType1Val(0); // TODO accent sb
+ writeType1Val(adx);
+ writeType1Val(ady);
+ writeType1Val(bchar);
+ writeType1Val(achar);
+ writeTypeEsc(TYPE1OP::SEAC);
+ }
+ }
+ if( mbNeedClose)
+ writeTypeOp( TYPE1OP::CLOSEPATH);
+ else
+ updateWidth( size() >= 1);
+ // mbNeedClose = true;
+ writeTypeOp( TYPE1OP::ENDCHAR);
+ break;
+ default:
+ if( ((nType2Op >= 32) && (nType2Op <= 255)) || (nType2Op == 28)) {
+ --mpReadPtr;
+ read2push();
+ } else {
+ popAll2Write( nType2Op);
+ assert(false && "TODO?");
+ }
+ break;
+ }
+}
+
+void CffSubsetterContext::convertOneTypeEsc()
+{
+ const int nType2Esc = *(mpReadPtr++);
+ ValType* pTop = &mnValStack[ mnStackIdx-1];
+ // convert each T2op
+ switch( nType2Esc) {
+ case TYPE2OP::AND:
+ assert( mnStackIdx >= 2 );
+ pTop[0] = static_cast<ValType>(static_cast<int>(pTop[0]) & static_cast<int>(pTop[-1]));
+ --mnStackIdx;
+ break;
+ case TYPE2OP::OR:
+ assert( mnStackIdx >= 2 );
+ pTop[0] = static_cast<ValType>(static_cast<int>(pTop[0]) | static_cast<int>(pTop[-1]));
+ --mnStackIdx;
+ break;
+ case TYPE2OP::NOT:
+ assert( mnStackIdx >= 1 );
+ pTop[0] = ValType(pTop[0] == 0);
+ break;
+ case TYPE2OP::ABS:
+ assert( mnStackIdx >= 1 );
+ if( pTop[0] >= 0)
+ break;
+ [[fallthrough]];
+ case TYPE2OP::NEG:
+ assert( mnStackIdx >= 1 );
+ pTop[0] = -pTop[0];
+ break;
+ case TYPE2OP::ADD:
+ assert( mnStackIdx >= 2 );
+ pTop[0] += pTop[-1];
+ --mnStackIdx;
+ break;
+ case TYPE2OP::SUB:
+ assert( mnStackIdx >= 2 );
+ pTop[0] -= pTop[-1];
+ --mnStackIdx;
+ break;
+ case TYPE2OP::MUL:
+ assert( mnStackIdx >= 2 );
+ if( pTop[-1])
+ pTop[0] *= pTop[-1];
+ --mnStackIdx;
+ break;
+ case TYPE2OP::DIV:
+ assert( mnStackIdx >= 2 );
+ if( pTop[-1])
+ pTop[0] /= pTop[-1];
+ --mnStackIdx;
+ break;
+ case TYPE2OP::EQ:
+ assert( mnStackIdx >= 2 );
+ pTop[0] = ValType(pTop[0] == pTop[-1]);
+ --mnStackIdx;
+ break;
+ case TYPE2OP::DROP:
+ assert( mnStackIdx >= 1 );
+ --mnStackIdx;
+ break;
+ case TYPE2OP::PUT: {
+ assert( mnStackIdx >= 2 );
+ const int nIdx = static_cast<int>(pTop[0]);
+ assert( nIdx >= 0 );
+ assert( nIdx < NMAXTRANS );
+ mnTransVals[ nIdx] = pTop[-1];
+ mnStackIdx -= 2;
+ break;
+ }
+ case TYPE2OP::GET: {
+ assert( mnStackIdx >= 1 );
+ const int nIdx = static_cast<int>(pTop[0]);
+ assert( nIdx >= 0 );
+ assert( nIdx < NMAXTRANS );
+ pTop[0] = mnTransVals[ nIdx ];
+ break;
+ }
+ case TYPE2OP::IFELSE: {
+ assert( mnStackIdx >= 4 );
+ if( pTop[-1] > pTop[0] )
+ pTop[-3] = pTop[-2];
+ mnStackIdx -= 3;
+ break;
+ }
+ case TYPE2OP::RANDOM:
+ pTop[+1] = 1234; // TODO
+ ++mnStackIdx;
+ break;
+ case TYPE2OP::SQRT:
+ // TODO: implement
+ break;
+ case TYPE2OP::DUP:
+ assert( mnStackIdx >= 1 );
+ pTop[+1] = pTop[0];
+ ++mnStackIdx;
+ break;
+ case TYPE2OP::EXCH: {
+ assert( mnStackIdx >= 2 );
+ const ValType nVal = pTop[0];
+ pTop[0] = pTop[-1];
+ pTop[-1] = nVal;
+ break;
+ }
+ case TYPE2OP::INDEX: {
+ assert( mnStackIdx >= 1 );
+ const int nVal = static_cast<int>(pTop[0]);
+ assert( nVal >= 0 );
+ assert( nVal < mnStackIdx-1 );
+ pTop[0] = pTop[-1-nVal];
+ break;
+ }
+ case TYPE2OP::ROLL: {
+ assert( mnStackIdx >= 1 );
+ const int nNum = static_cast<int>(pTop[0]);
+ assert( nNum >= 0);
+ assert( nNum < mnStackIdx-2 );
+ (void)nNum; // TODO: implement
+ // TODO: implement: const int nOfs = static_cast<int>(pTop[-1]);
+ mnStackIdx -= 2;
+ break;
+ }
+ case TYPE2OP::HFLEX1: {
+ assert( mnStackIdx == 9);
+
+ writeCurveTo( mnStackIdx, -9, -8, -7, -6, -5, 0);
+ writeCurveTo( mnStackIdx, -4, 0, -3, -2, -1, 0);
+ // TODO: emulate hflex1 using othersubr call
+
+ mnStackIdx -= 9;
+ }
+ break;
+ case TYPE2OP::HFLEX: {
+ assert( mnStackIdx == 7);
+ ValType* pX = &mnValStack[ mnStackIdx];
+
+ pX[+1] = -pX[-5]; // temp: +dy5==-dy2
+ writeCurveTo( mnStackIdx, -7, 0, -6, -5, -4, 0);
+ writeCurveTo( mnStackIdx, -3, 0, -2, +1, -1, 0);
+ // TODO: emulate hflex using othersubr call
+
+ mnStackIdx -= 7;
+ }
+ break;
+ case TYPE2OP::FLEX: {
+ assert( mnStackIdx == 13 );
+ writeCurveTo( mnStackIdx, -13, -12, -11, -10, -9, -8 );
+ writeCurveTo( mnStackIdx, -7, -6, -5, -4, -3, -2 );
+ // ignoring ValType nFlexDepth = mnValStack[ mnStackIdx-1 ];
+ mnStackIdx -= 13;
+ }
+ break;
+ case TYPE2OP::FLEX1: {
+ assert( mnStackIdx == 11 );
+ // write the first part of the flex1-hinted curve
+ writeCurveTo( mnStackIdx, -11, -10, -9, -8, -7, -6 );
+
+ // determine if nD6 is horizontal or vertical
+ const int i = mnStackIdx;
+ ValType nDeltaX = mnValStack[i-11] + mnValStack[i-9] + mnValStack[i-7] + mnValStack[i-5] + mnValStack[i-3];
+ if( nDeltaX < 0 ) nDeltaX = -nDeltaX;
+ ValType nDeltaY = mnValStack[i-10] + mnValStack[i-8] + mnValStack[i-6] + mnValStack[i-4] + mnValStack[i-2];
+ if( nDeltaY < 0 ) nDeltaY = -nDeltaY;
+ const bool bVertD6 = (nDeltaY > nDeltaX);
+
+ // write the second part of the flex1-hinted curve
+ if( !bVertD6 )
+ writeCurveTo( mnStackIdx, -5, -4, -3, -2, -1, 0);
+ else
+ writeCurveTo( mnStackIdx, -5, -4, -3, -2, 0, -1);
+ mnStackIdx -= 11;
+ }
+ break;
+ default:
+ SAL_WARN("vcl.fonts", "unhandled type2esc " << nType2Esc);
+ assert( false);
+ break;
+ }
+}
+
+void CffSubsetterContext::callType2Subr( bool bGlobal, int nSubrNumber)
+{
+ const U8* const pOldReadPtr = mpReadPtr;
+ const U8* const pOldReadEnd = mpReadEnd;
+
+ if( bGlobal ) {
+ nSubrNumber += mnGlobalSubrBias;
+ seekIndexData( mnGlobalSubrBase, nSubrNumber);
+ } else {
+ nSubrNumber += mpCffLocal->mnLocalSubrBias;
+ seekIndexData( mpCffLocal->mnLocalSubrBase, nSubrNumber);
+ }
+
+ while( mpReadPtr < mpReadEnd)
+ convertOneTypeOp();
+
+ mpReadPtr = pOldReadPtr;
+ mpReadEnd = pOldReadEnd;
+}
+
+int CffSubsetterContext::convert2Type1Ops( CffLocal* pCffLocal, const U8* const pT2Ops, int nT2Len, U8* const pT1Ops)
+{
+ mpCffLocal = pCffLocal;
+
+ // prepare the charstring conversion
+ mpWritePtr = pT1Ops;
+ U8 aType1Ops[ MAX_T1OPS_SIZE];
+ if( !pT1Ops)
+ mpWritePtr = aType1Ops;
+ *const_cast<U8**>(&pT1Ops) = mpWritePtr;
+
+ // prepend random seed for T1crypt
+ *(mpWritePtr++) = 0x48;
+ *(mpWritePtr++) = 0x44;
+ *(mpWritePtr++) = 0x55;
+ *(mpWritePtr++) = ' ';
+
+ // convert the Type2 charstring to Type1
+ mpReadPtr = pT2Ops;
+ mpReadEnd = pT2Ops + nT2Len;
+ // prepend "hsbw" or "sbw"
+ // TODO: only emit hsbw when charwidth is known
+ writeType1Val(0); // TODO: aSubsetterContext.getLeftSideBearing();
+ U8* pCharWidthPtr=mpWritePtr; // need to overwrite that later
+ // pad out 5 bytes for the char width with default val 1000 (to be
+ // filled with the actual value below)
+ *(mpWritePtr++) = 255;
+ *(mpWritePtr++) = static_cast<U8>(0);
+ *(mpWritePtr++) = static_cast<U8>(0);
+ *(mpWritePtr++) = static_cast<U8>(250);
+ *(mpWritePtr++) = static_cast<U8>(124);
+ writeTypeOp(TYPE1OP::HSBW);
+ mbNeedClose = false;
+ mbIgnoreHints = false;
+ mnHintSize=mnHorzHintSize=mnStackIdx=0; maCharWidth=-1;//#######
+ mnCntrMask = 0;
+ while( mpReadPtr < mpReadEnd)
+ convertOneTypeOp();
+ if( maCharWidth != -1 )
+ {
+ // overwrite earlier charWidth value, which we only now have
+ // parsed out of mpReadPtr buffer (by way of
+ // convertOneTypeOp()s above)
+ const int nInt = static_cast<int>(maCharWidth);
+ *(pCharWidthPtr++) = 255;
+ *(pCharWidthPtr++) = static_cast<U8>(nInt >> 24);
+ *(pCharWidthPtr++) = static_cast<U8>(nInt >> 16);
+ *(pCharWidthPtr++) = static_cast<U8>(nInt >> 8);
+ *(pCharWidthPtr++) = static_cast<U8>(nInt);
+ }
+
+ const int nType1Len = mpWritePtr - pT1Ops;
+
+ // encrypt the Type1 charstring
+ unsigned nRDCryptR = 4330; // TODO: mnRDCryptSeed;
+ for( U8* p = pT1Ops; p < mpWritePtr; ++p) {
+ *p ^= (nRDCryptR >> 8);
+ nRDCryptR = (*p + nRDCryptR) * 52845 + 22719;
+ }
+
+ return nType1Len;
+}
+
+RealType CffSubsetterContext::readRealVal()
+{
+ // TODO: more thorough number validity test
+ bool bComma = false;
+ int nExpVal = 0;
+ int nExpSign = 0;
+ S64 nNumber = 0;
+ RealType fReal = +1.0;
+ for(;;){
+ const U8 c = *(mpReadPtr++); // read nibbles
+ // parse high nibble
+ const U8 nH = c >> 4U;
+ if( nH <= 9) {
+ nNumber = nNumber * 10 + nH;
+ --nExpVal;
+ } else if( nH == 10) { // comma
+ nExpVal = 0;
+ bComma = true;
+ } else if( nH == 11) { // +exp
+ fReal *= nNumber;
+ nExpSign = +1;
+ nNumber = 0;
+ } else if( nH == 12) { // -exp
+ fReal *= nNumber;
+ nExpSign = -1;
+ nNumber = 0;
+ } else if( nH == 13) { // reserved
+ // TODO: ignore or error?
+ } else if( nH == 14) // minus
+ fReal = -fReal;
+ else if( nH == 15) // end
+ break;
+ // parse low nibble
+ const U8 nL = c & 0x0F;
+ if( nL <= 9) {
+ nNumber = nNumber * 10 + nL;
+ --nExpVal;
+ } else if( nL == 10) { // comma
+ nExpVal = 0;
+ bComma = true;
+ } else if( nL == 11) { // +exp
+ fReal *= nNumber;
+ nNumber = 0;
+ nExpSign = +1;
+ } else if( nL == 12) { // -exp
+ fReal *= nNumber;
+ nNumber = 0;
+ nExpSign = -1;
+ } else if( nL == 13) { // reserved
+ // TODO: ignore or error?
+ } else if( nL == 14) // minus
+ fReal = -fReal;
+ else if( nL == 15) // end
+ break;
+ }
+
+ // merge exponents
+ if( !bComma)
+ nExpVal = 0;
+ if( !nExpSign) { fReal *= nNumber;}
+ else if( nExpSign > 0) { nExpVal += static_cast<int>(nNumber);}
+ else if( nExpSign < 0) { nExpVal -= static_cast<int>(nNumber);}
+
+ // apply exponents
+ if( !nExpVal) { /*nothing to apply*/}
+ else if( nExpVal > 0) { while( --nExpVal >= 0) fReal *= 10.0;}
+ else if( nExpVal < 0) { while( ++nExpVal <= 0) fReal /= 10.0;}
+ return fReal;
+}
+
+// prepare to access an element inside a CFF/CID index table
+int CffSubsetterContext::seekIndexData( int nIndexBase, int nDataIndex)
+{
+ assert( (nIndexBase > 0) && (mpBasePtr + nIndexBase + 3 <= mpBaseEnd));
+ if( nDataIndex < 0)
+ return -1;
+ mpReadPtr = mpBasePtr + nIndexBase;
+ const int nDataCount = (mpReadPtr[0]<<8) + mpReadPtr[1];
+ if( nDataIndex >= nDataCount)
+ return -1;
+ const int nDataOfsSz = mpReadPtr[2];
+ mpReadPtr += 3 + (nDataOfsSz * nDataIndex);
+ int nOfs1 = 0;
+ switch( nDataOfsSz) {
+ default: SAL_WARN("vcl.fonts", "\tINVALID nDataOfsSz=" << nDataOfsSz); return -1;
+ case 1: nOfs1 = mpReadPtr[0]; break;
+ case 2: nOfs1 = (mpReadPtr[0]<<8) + mpReadPtr[1]; break;
+ case 3: nOfs1 = (mpReadPtr[0]<<16) + (mpReadPtr[1]<<8) + mpReadPtr[2]; break;
+ case 4: nOfs1 = (mpReadPtr[0]<<24) + (mpReadPtr[1]<<16) + (mpReadPtr[2]<<8) + mpReadPtr[3]; break;
+ }
+ mpReadPtr += nDataOfsSz;
+
+ int nOfs2 = 0;
+ switch( nDataOfsSz) {
+ case 1: nOfs2 = mpReadPtr[0]; break;
+ case 2: nOfs2 = (mpReadPtr[0]<<8) + mpReadPtr[1]; break;
+ case 3: nOfs2 = (mpReadPtr[0]<<16) + (mpReadPtr[1]<<8) + mpReadPtr[2]; break;
+ case 4: nOfs2 = (mpReadPtr[0]<<24) + (mpReadPtr[1]<<16) + (mpReadPtr[2]<<8) + mpReadPtr[3]; break;
+ }
+
+ mpReadPtr = mpBasePtr + (nIndexBase + 2) + nDataOfsSz * (nDataCount + 1) + nOfs1;
+ mpReadEnd = mpReadPtr + (nOfs2 - nOfs1);
+ assert( nOfs1 >= 0);
+ assert( nOfs2 >= nOfs1);
+ assert( mpReadPtr <= mpBaseEnd);
+ assert( mpReadEnd <= mpBaseEnd);
+ return (nOfs2 - nOfs1);
+}
+
+// skip over a CFF/CID index table
+void CffSubsetterContext::seekIndexEnd( int nIndexBase)
+{
+ assert( (nIndexBase > 0) && (mpBasePtr + nIndexBase + 3 <= mpBaseEnd));
+ mpReadPtr = mpBasePtr + nIndexBase;
+ const int nDataCount = (mpReadPtr[0]<<8) + mpReadPtr[1];
+ const int nDataOfsSz = mpReadPtr[2];
+ mpReadPtr += 3 + nDataOfsSz * nDataCount;
+ assert( mpReadPtr <= mpBaseEnd);
+ int nEndOfs = 0;
+ switch( nDataOfsSz) {
+ default: SAL_WARN("vcl.fonts", "\tINVALID nDataOfsSz=" << nDataOfsSz); return;
+ case 1: nEndOfs = mpReadPtr[0]; break;
+ case 2: nEndOfs = (mpReadPtr[0]<<8) + mpReadPtr[1]; break;
+ case 3: nEndOfs = (mpReadPtr[0]<<16) + (mpReadPtr[1]<<8) + mpReadPtr[2];break;
+ case 4: nEndOfs = (mpReadPtr[0]<<24) + (mpReadPtr[1]<<16) + (mpReadPtr[2]<<8) + mpReadPtr[3]; break;
+ }
+ mpReadPtr += nDataOfsSz;
+ mpReadPtr += nEndOfs - 1;
+ mpReadEnd = mpBaseEnd;
+ assert( nEndOfs >= 0);
+ assert( mpReadEnd <= mpBaseEnd);
+}
+
+// initialize FONTDICT specific values
+CffLocal::CffLocal()
+: mnPrivDictBase( 0)
+, mnPrivDictSize( 0)
+, mnLocalSubrOffs( 0)
+, mnLocalSubrBase( 0)
+, mnLocalSubrBias( 0)
+, maNominalWidth( 0)
+, maDefaultWidth( 0)
+, maStemStdHW( 0)
+, maStemStdVW( 0)
+, mfBlueScale( 0.0)
+, mfBlueShift( 0.0)
+, mfBlueFuzz( 0.0)
+, mfExpFactor( 0.0)
+, mnLangGroup( 0)
+, mbForceBold( false)
+{
+}
+
+CffGlobal::CffGlobal()
+: mnNameIdxBase( 0)
+, mnStringIdxBase( 0)
+, mbCIDFont( false)
+, mnCharStrBase( 0)
+, mnCharStrCount( 0)
+, mnCharsetBase( 0)
+, mnGlobalSubrBase( 0)
+, mnGlobalSubrCount( 0)
+, mnGlobalSubrBias( 0)
+, mnFDSelectBase( 0)
+, mnFontDictBase( 0)
+, mnFDAryCount( 1)
+, mnFontNameSID( 0)
+, mnFullNameSID( 0)
+{
+}
+
+bool CffSubsetterContext::initialCffRead()
+{
+ // get the CFFHeader
+ mpReadPtr = mpBasePtr;
+ const U8 nVerMajor = *(mpReadPtr++);
+ const U8 nVerMinor = *(mpReadPtr++);
+ const U8 nHeaderSize = *(mpReadPtr++);
+ const U8 nOffsetSize = *(mpReadPtr++);
+ // TODO: is the version number useful for anything else?
+ assert( (nVerMajor == 1) && (nVerMinor == 0));
+ (void)(nVerMajor + nVerMinor + nOffsetSize); // avoid compiler warnings
+
+ // prepare access to the NameIndex
+ mnNameIdxBase = nHeaderSize;
+ mpReadPtr = mpBasePtr + nHeaderSize;
+ seekIndexEnd( mnNameIdxBase);
+
+ // get the TopDict index
+ const sal_Int32 nTopDictBase = getReadOfs();
+ const int nTopDictCount = (mpReadPtr[0]<<8) + mpReadPtr[1];
+ if( nTopDictCount) {
+ for( int i = 0; i < nTopDictCount; ++i) {
+ seekIndexData( nTopDictBase, i);
+ while( mpReadPtr < mpReadEnd)
+ readDictOp();
+ assert( mpReadPtr == mpReadEnd);
+ }
+ }
+
+ // prepare access to the String index
+ mnStringIdxBase = getReadOfs();
+ seekIndexEnd( mnStringIdxBase);
+
+ // prepare access to the GlobalSubr index
+ mnGlobalSubrBase = getReadOfs();
+ mnGlobalSubrCount = (mpReadPtr[0]<<8) + mpReadPtr[1];
+ mnGlobalSubrBias = (mnGlobalSubrCount<1240)?107:(mnGlobalSubrCount<33900)?1131:32768;
+ // skip past the last GlobalSubr entry
+// seekIndexEnd( mnGlobalSubrBase);
+
+ // get/skip the Encodings (we got mnEncodingBase from TOPDICT)
+// seekEncodingsEnd( mnEncodingBase);
+ // get/skip the Charsets (we got mnCharsetBase from TOPDICT)
+// seekCharsetsEnd( mnCharStrBase);
+ // get/skip FDSelect (CID only) data
+
+ // prepare access to the CharStrings index (we got the base from TOPDICT)
+ mpReadPtr = mpBasePtr + mnCharStrBase;
+ mnCharStrCount = (mpReadPtr[0]<<8) + mpReadPtr[1];
+// seekIndexEnd( mnCharStrBase);
+
+ // read the FDArray index (CID only)
+ if( mbCIDFont) {
+// assert( mnFontDictBase == tellRel());
+ mpReadPtr = mpBasePtr + mnFontDictBase;
+ mnFDAryCount = (mpReadPtr[0]<<8) + mpReadPtr[1];
+ if (o3tl::make_unsigned(mnFDAryCount) >= SAL_N_ELEMENTS(maCffLocal))
+ {
+ SAL_INFO("vcl.fonts", "CffSubsetterContext: too many CFF in font");
+ return false;
+ }
+
+ // read FDArray details to get access to the PRIVDICTs
+ for( int i = 0; i < mnFDAryCount; ++i) {
+ mpCffLocal = &maCffLocal[i];
+ seekIndexData( mnFontDictBase, i);
+ while( mpReadPtr < mpReadEnd)
+ readDictOp();
+ assert( mpReadPtr == mpReadEnd);
+ }
+ }
+
+ for( int i = 0; i < mnFDAryCount; ++i) {
+ mpCffLocal = &maCffLocal[i];
+
+ // get the PrivateDict index
+ // (we got mnPrivDictSize and mnPrivDictBase from TOPDICT or FDArray)
+ if( mpCffLocal->mnPrivDictSize != 0) {
+ assert( mpCffLocal->mnPrivDictSize > 0);
+ // get the PrivDict data
+ mpReadPtr = mpBasePtr + mpCffLocal->mnPrivDictBase;
+ mpReadEnd = mpReadPtr + mpCffLocal->mnPrivDictSize;
+ assert( mpReadEnd <= mpBaseEnd);
+ // read PrivDict details
+ while( mpReadPtr < mpReadEnd)
+ readDictOp();
+ }
+
+ // prepare access to the LocalSubrs (we got mnLocalSubrOffs from PRIVDICT)
+ if( mpCffLocal->mnLocalSubrOffs) {
+ // read LocalSubrs summary
+ mpCffLocal->mnLocalSubrBase = mpCffLocal->mnPrivDictBase + mpCffLocal->mnLocalSubrOffs;
+ mpReadPtr = mpBasePtr + mpCffLocal->mnLocalSubrBase;
+ const int nSubrCount = (mpReadPtr[0] << 8) + mpReadPtr[1];
+ mpCffLocal->mnLocalSubrBias = (nSubrCount<1240)?107:(nSubrCount<33900)?1131:32768;
+// seekIndexEnd( mpCffLocal->mnLocalSubrBase);
+ }
+ }
+
+ // ignore the Notices info
+
+ return true;
+}
+
+// get a cstring from a StringID
+const char* CffSubsetterContext::getString( int nStringID)
+{
+ // get a standard string if possible
+ const static int nStdStrings = SAL_N_ELEMENTS(pStringIds);
+ if( (nStringID >= 0) && (nStringID < nStdStrings))
+ return pStringIds[ nStringID];
+
+ // else get the string from the StringIndex table
+ const U8* pReadPtr = mpReadPtr;
+ const U8* pReadEnd = mpReadEnd;
+ nStringID -= nStdStrings;
+ int nLen = seekIndexData( mnStringIdxBase, nStringID);
+ // assert( nLen >= 0);
+ // TODO: just return the undecorated name
+ // TODO: get rid of static char buffer
+ static char aNameBuf[ 2560];
+ if( nLen < 0) {
+ sprintf( aNameBuf, "name[%d].notfound!", nStringID);
+ } else {
+ const int nMaxLen = sizeof(aNameBuf) - 1;
+ if( nLen >= nMaxLen)
+ nLen = nMaxLen;
+ for( int i = 0; i < nLen; ++i)
+ aNameBuf[i] = *(mpReadPtr++);
+ aNameBuf[ nLen] = '\0';
+ }
+ mpReadPtr = pReadPtr;
+ mpReadEnd = pReadEnd;
+ return aNameBuf;
+}
+
+// access a CID's FDSelect table
+int CffSubsetterContext::getFDSelect( int nGlyphIndex) const
+{
+ assert( nGlyphIndex >= 0);
+ assert( nGlyphIndex < mnCharStrCount);
+ if( !mbCIDFont)
+ return 0;
+
+ const U8* pReadPtr = mpBasePtr + mnFDSelectBase;
+ const U8 nFDSelFormat = *(pReadPtr++);
+ switch( nFDSelFormat) {
+ case 0: { // FDSELECT format 0
+ pReadPtr += nGlyphIndex;
+ const U8 nFDIdx = *(pReadPtr++);
+ return nFDIdx;
+ } //break;
+ case 3: { // FDSELECT format 3
+ const U16 nRangeCount = (pReadPtr[0]<<8) + pReadPtr[1];
+ assert( nRangeCount > 0);
+ assert( nRangeCount <= mnCharStrCount);
+ U16 nPrev = (pReadPtr[2]<<8) + pReadPtr[3];
+ assert( nPrev == 0);
+ (void)nPrev;
+ pReadPtr += 4;
+ // TODO? binary search
+ for( int i = 0; i < nRangeCount; ++i) {
+ const U8 nFDIdx = pReadPtr[0];
+ const U16 nNext = (pReadPtr[1]<<8) + pReadPtr[2];
+ assert( nPrev < nNext);
+ if( nGlyphIndex < nNext)
+ return nFDIdx;
+ pReadPtr += 3;
+ nPrev = nNext;
+ }
+ } break;
+ default: // invalid FDselect format
+ SAL_WARN("vcl.fonts", "invalid CFF.FdselType=" << nFDSelFormat);
+ break;
+ }
+
+ assert( false);
+ return -1;
+}
+
+int CffSubsetterContext::getGlyphSID( int nGlyphIndex) const
+{
+ if( nGlyphIndex == 0)
+ return 0; // ".notdef"
+ assert( nGlyphIndex >= 0);
+ assert( nGlyphIndex < mnCharStrCount);
+ if( (nGlyphIndex < 0) || (nGlyphIndex >= mnCharStrCount))
+ return -1;
+
+ // get the SID/CID from the Charset table
+ const U8* pReadPtr = mpBasePtr + mnCharsetBase;
+ const U8 nCSetFormat = *(pReadPtr++);
+ int nGlyphsToSkip = nGlyphIndex - 1;
+ switch( nCSetFormat) {
+ case 0: // charset format 0
+ pReadPtr += 2 * nGlyphsToSkip;
+ nGlyphsToSkip = 0;
+ break;
+ case 1: // charset format 1
+ while( nGlyphsToSkip >= 0) {
+ const int nLeft = pReadPtr[2];
+ if( nGlyphsToSkip <= nLeft)
+ break;
+ nGlyphsToSkip -= nLeft + 1;
+ pReadPtr += 3;
+ }
+ break;
+ case 2: // charset format 2
+ while( nGlyphsToSkip >= 0) {
+ const int nLeft = (pReadPtr[2]<<8) + pReadPtr[3];
+ if( nGlyphsToSkip <= nLeft)
+ break;
+ nGlyphsToSkip -= nLeft + 1;
+ pReadPtr += 4;
+ }
+ break;
+ default:
+ SAL_WARN("vcl.fonts", "ILLEGAL CFF-Charset format " << nCSetFormat);
+ return -2;
+ }
+
+ int nSID = (pReadPtr[0]<<8) + pReadPtr[1];
+ nSID += nGlyphsToSkip;
+ // NOTE: for CID-fonts the resulting SID is interpreted as CID
+ return nSID;
+}
+
+// NOTE: the result becomes invalid with the next call to this method
+const char* CffSubsetterContext::getGlyphName( int nGlyphIndex)
+{
+ // the first glyph is always the .notdef glyph
+ const char* pGlyphName = ".notdef";
+ if( nGlyphIndex == 0)
+ return pGlyphName;
+
+ // prepare a result buffer
+ // TODO: get rid of static buffer
+ static char aDefaultGlyphName[64];
+ pGlyphName = aDefaultGlyphName;
+
+ // get the glyph specific name
+ const int nSID = getGlyphSID( nGlyphIndex);
+ if( nSID < 0) // default glyph name
+ sprintf( aDefaultGlyphName, "gly%03d", nGlyphIndex);
+ else if( mbCIDFont) // default glyph name in CIDs
+ sprintf( aDefaultGlyphName, "cid%03d", nSID);
+ else { // glyph name from string table
+ const char* pSidName = getString( nSID);
+ // check validity of glyph name
+ if( pSidName) {
+ const char* p = pSidName;
+ while( (*p >= '0') && (*p <= 'z')) ++p;
+ if( (p >= pSidName+1) && (*p == '\0'))
+ pGlyphName = pSidName;
+ }
+ // if needed invent a fallback name
+ if( pGlyphName != pSidName)
+ sprintf( aDefaultGlyphName, "bad%03d", nSID);
+ }
+
+ return pGlyphName;
+}
+
+bool CffSubsetterContext::getBaseAccent(ValType aBase, ValType aAccent, int* nBase, int* nAccent)
+{
+ bool bBase = false, bAccent = false;
+ for (int i = 0; i < mnCharStrCount; i++)
+ {
+ const char* pGlyphName = getGlyphName(i);
+ if (pGlyphName == pStandardEncoding[int(aBase)])
+ {
+ *nBase = i;
+ bBase = true;
+ }
+ if (pGlyphName == pStandardEncoding[int(aAccent)])
+ {
+ *nAccent = i;
+ bAccent = true;
+ }
+ if (bBase && bAccent)
+ return true;
+ }
+ return false;
+}
+
+namespace {
+
+class Type1Emitter
+{
+public:
+ explicit Type1Emitter( FILE* pOutFile, bool bPfbSubset);
+ ~Type1Emitter();
+ void setSubsetName( const char* );
+
+ size_t emitRawData( const char* pData, size_t nLength) const;
+ void emitAllRaw();
+ void emitAllHex();
+ void emitAllCrypted();
+ int tellPos() const;
+ void updateLen( int nTellPos, size_t nLength);
+ void emitValVector( const char* pLineHead, const char* pLineTail, const std::vector<ValType>&);
+private:
+ FILE* mpFileOut;
+ char maBuffer[MAX_T1OPS_SIZE]; // TODO: dynamic allocation
+ unsigned mnEECryptR;
+public:
+ char* mpPtr;
+
+ char maSubsetName[256];
+ bool mbPfbSubset;
+ int mnHexLineCol;
+};
+
+}
+
+Type1Emitter::Type1Emitter( FILE* pOutFile, bool bPfbSubset)
+: mpFileOut( pOutFile)
+, maBuffer{}
+, mnEECryptR( 55665) // default eexec seed, TODO: mnEECryptSeed
+, mpPtr( maBuffer)
+, mbPfbSubset( bPfbSubset)
+, mnHexLineCol( 0)
+{
+ maSubsetName[0] = '\0';
+}
+
+Type1Emitter::~Type1Emitter()
+{
+ if( !mpFileOut)
+ return;
+ mpFileOut = nullptr;
+}
+
+void Type1Emitter::setSubsetName( const char* pSubsetName)
+{
+ maSubsetName[0] = '\0';
+ if( pSubsetName)
+ strncpy( maSubsetName, pSubsetName, sizeof(maSubsetName) - 1);
+ maSubsetName[sizeof(maSubsetName)-1] = '\0';
+}
+
+int Type1Emitter::tellPos() const
+{
+ int nTellPos = ftell( mpFileOut);
+ return nTellPos;
+}
+
+void Type1Emitter::updateLen( int nTellPos, size_t nLength)
+{
+ // update PFB segment header length
+ U8 cData[4];
+ cData[0] = static_cast<U8>(nLength >> 0);
+ cData[1] = static_cast<U8>(nLength >> 8);
+ cData[2] = static_cast<U8>(nLength >> 16);
+ cData[3] = static_cast<U8>(nLength >> 24);
+ const tools::Long nCurrPos = ftell(mpFileOut);
+ if (nCurrPos < 0)
+ return;
+ if (fseek( mpFileOut, nTellPos, SEEK_SET) != 0)
+ return;
+ fwrite(cData, 1, sizeof(cData), mpFileOut);
+ (void)fseek(mpFileOut, nCurrPos, SEEK_SET);
+}
+
+inline size_t Type1Emitter::emitRawData(const char* pData, size_t nLength) const
+{
+ return fwrite( pData, 1, nLength, mpFileOut);
+}
+
+inline void Type1Emitter::emitAllRaw()
+{
+ // writeout raw data
+ assert( (mpPtr - maBuffer) < int(sizeof(maBuffer)));
+ emitRawData( maBuffer, mpPtr - maBuffer);
+ // reset the raw buffer
+ mpPtr = maBuffer;
+}
+
+inline void Type1Emitter::emitAllHex()
+{
+ assert( (mpPtr - maBuffer) < int(sizeof(maBuffer)));
+ for( const char* p = maBuffer; p < mpPtr;) {
+ // convert binary chunk to hex
+ char aHexBuf[0x4000];
+ char* pOut = aHexBuf;
+ while( (p < mpPtr) && (pOut < aHexBuf+sizeof(aHexBuf)-4)) {
+ // convert each byte to hex
+ char cNibble = (static_cast<unsigned char>(*p) >> 4) & 0x0F;
+ cNibble += (cNibble < 10) ? '0' : 'A'-10;
+ *(pOut++) = cNibble;
+ cNibble = *(p++) & 0x0F;
+ cNibble += (cNibble < 10) ? '0' : 'A'-10;
+ *(pOut++) = cNibble;
+ // limit the line length
+ if( (++mnHexLineCol & 0x3F) == 0)
+ *(pOut++) = '\n';
+ }
+ // writeout hex-converted chunk
+ emitRawData( aHexBuf, pOut-aHexBuf);
+ }
+ // reset the raw buffer
+ mpPtr = maBuffer;
+}
+
+void Type1Emitter::emitAllCrypted()
+{
+ // apply t1crypt
+ for( char* p = maBuffer; p < mpPtr; ++p) {
+ *p ^= (mnEECryptR >> 8);
+ mnEECryptR = (*reinterpret_cast<U8*>(p) + mnEECryptR) * 52845 + 22719;
+ }
+
+ // emit the t1crypt result
+ if( mbPfbSubset)
+ emitAllRaw();
+ else
+ emitAllHex();
+}
+
+// #i110387# quick-and-dirty double->ascii conversion
+// needed because sprintf/ecvt/etc. alone are too localized (LC_NUMERIC)
+// also strip off trailing zeros in fraction while we are at it
+static int dbl2str( char* pOut, double fVal)
+{
+ const int nLen = psp::getValueOfDouble( pOut, fVal, 6);
+ return nLen;
+}
+
+void Type1Emitter::emitValVector( const char* pLineHead, const char* pLineTail,
+ const std::vector<ValType>& rVector)
+{
+ // ignore empty vectors
+ if( rVector.empty())
+ return;
+
+ // emit the line head
+ mpPtr += sprintf( mpPtr, "%s", pLineHead);
+ // emit the vector values
+ std::vector<ValType>::value_type aVal = 0;
+ for( std::vector<ValType>::const_iterator it = rVector.begin();;) {
+ aVal = *it;
+ if( ++it == rVector.end() )
+ break;
+ mpPtr += dbl2str( mpPtr, aVal);
+ *(mpPtr++) = ' ';
+ }
+ // emit the last value
+ mpPtr += dbl2str( mpPtr, aVal);
+ // emit the line tail
+ mpPtr += sprintf( mpPtr, "%s", pLineTail);
+}
+
+void CffSubsetterContext::convertCharStrings(const sal_GlyphId* pGlyphIds, int nGlyphCount,
+ std::vector<CharString>& rCharStrings)
+{
+ // If we are doing extra glyphs used for seac operator, check for already
+ // converted glyphs.
+ bool bCheckDuplicates = !rCharStrings.empty();
+ rCharStrings.reserve(rCharStrings.size() + nGlyphCount);
+ for (int i = 0; i < nGlyphCount; ++i)
+ {
+ const int nCffGlyphId = pGlyphIds[i];
+ assert((nCffGlyphId >= 0) && (nCffGlyphId < mnCharStrCount));
+
+ if (!bCheckDuplicates)
+ {
+ const auto& it
+ = std::find_if(rCharStrings.begin(), rCharStrings.end(),
+ [&](const CharString& c) { return c.nCffGlyphId == nCffGlyphId; });
+ if (it != rCharStrings.end())
+ continue;
+ }
+
+ // get privdict context matching to the glyph
+ const int nFDSelect = getFDSelect(nCffGlyphId);
+ if (nFDSelect < 0)
+ continue;
+ mpCffLocal = &maCffLocal[nFDSelect];
+
+ // convert the Type2op charstring to its Type1op counterpart
+ const int nT2Len = seekIndexData(mnCharStrBase, nCffGlyphId);
+ assert(nT2Len > 0);
+
+ CharString aCharString;
+ const int nT1Len = convert2Type1Ops(mpCffLocal, mpReadPtr, nT2Len, aCharString.aOps);
+ aCharString.nLen = nT1Len;
+ aCharString.nCffGlyphId = nCffGlyphId;
+ aCharString.aCharWidth = maCharWidth;
+
+ rCharStrings.push_back(aCharString);
+ }
+}
+
+void CffSubsetterContext::emitAsType1( Type1Emitter& rEmitter,
+ const sal_GlyphId* pReqGlyphIds, const U8* pReqEncoding,
+ sal_Int32* pGlyphWidths, int nGlyphCount, FontSubsetInfo& rFSInfo)
+{
+ // prepare some fontdirectory details
+ static const int nUniqueIdBase = 4100000; // using private-interchange UniqueIds
+ static int nUniqueId = nUniqueIdBase;
+ ++nUniqueId;
+
+ char* pFontName = rEmitter.maSubsetName;
+ if( !*pFontName ) {
+ if( mnFontNameSID) {
+ // get the fontname directly if available
+ strncpy( pFontName, getString( mnFontNameSID), sizeof(rEmitter.maSubsetName) - 1);
+ pFontName[sizeof(rEmitter.maSubsetName) - 1] = 0;
+ } else if( mnFullNameSID) {
+ // approximate fontname as fullname-whitespace
+ const char* pI = getString( mnFullNameSID);
+ char* pO = pFontName;
+ const char* pLimit = pFontName + sizeof(rEmitter.maSubsetName) - 1;
+ while( pO < pLimit) {
+ const char c = *(pI++);
+ if( c != ' ')
+ *(pO++) = c;
+ if( !c)
+ break;
+ }
+ *pO = '\0';
+ } else {
+ // fallback name of last resort
+ strncpy( pFontName, "DummyName", sizeof(rEmitter.maSubsetName));
+ }
+ }
+ const char* pFullName = pFontName;
+ const char* pFamilyName = pFontName;
+
+ char*& pOut = rEmitter.mpPtr; // convenience reference, TODO: cleanup
+
+ // create a PFB+Type1 header
+ if( rEmitter.mbPfbSubset ) {
+ static const char aPfbHeader[] = "\x80\x01\x00\x00\x00\x00";
+ rEmitter.emitRawData( aPfbHeader, sizeof(aPfbHeader)-1);
+ }
+
+ pOut += sprintf( pOut, "%%!FontType1-1.0: %s 001.003\n", rEmitter.maSubsetName);
+ // emit TOPDICT
+ pOut += sprintf( pOut,
+ "11 dict begin\n" // TODO: dynamic entry count for TOPDICT
+ "/FontType 1 def\n"
+ "/PaintType 0 def\n");
+ pOut += sprintf( pOut, "/FontName /%s def\n", rEmitter.maSubsetName);
+ pOut += sprintf( pOut, "/UniqueID %d def\n", nUniqueId);
+ // emit FontMatrix
+ if( maFontMatrix.size() == 6)
+ rEmitter.emitValVector( "/FontMatrix [", "]readonly def\n", maFontMatrix);
+ else // emit default FontMatrix if needed
+ pOut += sprintf( pOut, "/FontMatrix [0.001 0 0 0.001 0 0]readonly def\n");
+ // emit FontBBox
+ auto aFontBBox = maFontBBox;
+ if (aFontBBox.size() != 4)
+ aFontBBox = { 0, 0, 999, 999 }; // emit default FontBBox if needed
+ rEmitter.emitValVector( "/FontBBox {", "}readonly def\n", aFontBBox);
+ // emit FONTINFO into TOPDICT
+ pOut += sprintf( pOut,
+ "/FontInfo 2 dict dup begin\n" // TODO: check fontinfo entry count
+ " /FullName (%s) readonly def\n"
+ " /FamilyName (%s) readonly def\n"
+ "end readonly def\n",
+ pFullName, pFamilyName);
+
+ pOut += sprintf( pOut,
+ "/Encoding 256 array\n"
+ "0 1 255 {1 index exch /.notdef put} for\n");
+ for( int i = 1; (i < nGlyphCount) && (i < 256); ++i) {
+ const char* pGlyphName = getGlyphName( pReqGlyphIds[i]);
+ pOut += sprintf( pOut, "dup %d /%s put\n", pReqEncoding[i], pGlyphName);
+ }
+ pOut += sprintf( pOut, "readonly def\n");
+ pOut += sprintf( pOut,
+ // TODO: more topdict entries
+ "currentdict end\n"
+ "currentfile eexec\n");
+
+ // emit PFB header
+ rEmitter.emitAllRaw();
+ if( rEmitter.mbPfbSubset) {
+ // update PFB header segment
+ const int nPfbHeaderLen = rEmitter.tellPos() - 6;
+ rEmitter.updateLen( 2, nPfbHeaderLen);
+
+ // prepare start of eexec segment
+ rEmitter.emitRawData( "\x80\x02\x00\x00\x00\x00", 6); // segment start
+ }
+ const int nEExecSegTell = rEmitter.tellPos();
+
+ // which always starts with a privdict
+ // count the privdict entries
+ int nPrivEntryCount = 9;
+ // emit blue hints only if non-default values
+ nPrivEntryCount += int(!mpCffLocal->maOtherBlues.empty());
+ nPrivEntryCount += int(!mpCffLocal->maFamilyBlues.empty());
+ nPrivEntryCount += int(!mpCffLocal->maFamilyOtherBlues.empty());
+ nPrivEntryCount += int(mpCffLocal->mfBlueScale != 0.0);
+ nPrivEntryCount += int(mpCffLocal->mfBlueShift != 0.0);
+ nPrivEntryCount += int(mpCffLocal->mfBlueFuzz != 0.0);
+ // emit stem hints only if non-default values
+ nPrivEntryCount += int(mpCffLocal->maStemStdHW != 0);
+ nPrivEntryCount += int(mpCffLocal->maStemStdVW != 0);
+ nPrivEntryCount += int(!mpCffLocal->maStemSnapH.empty());
+ nPrivEntryCount += int(!mpCffLocal->maStemSnapV.empty());
+ // emit other hints only if non-default values
+ nPrivEntryCount += int(mpCffLocal->mfExpFactor != 0.0);
+ nPrivEntryCount += int(mpCffLocal->mnLangGroup != 0);
+ nPrivEntryCount += int(mpCffLocal->mnLangGroup == 1);
+ nPrivEntryCount += int(mpCffLocal->mbForceBold);
+ // emit the privdict header
+ pOut += sprintf( pOut,
+ "\110\104\125 "
+ "dup\n/Private %d dict dup begin\n"
+ "/RD{string currentfile exch readstring pop}executeonly def\n"
+ "/ND{noaccess def}executeonly def\n"
+ "/NP{noaccess put}executeonly def\n"
+ "/MinFeature{16 16}ND\n"
+ "/password 5839 def\n", // TODO: mnRDCryptSeed?
+ nPrivEntryCount);
+
+ // emit blue hint related privdict entries
+ if( !mpCffLocal->maBlueValues.empty())
+ rEmitter.emitValVector( "/BlueValues [", "]ND\n", mpCffLocal->maBlueValues);
+ else
+ pOut += sprintf( pOut, "/BlueValues []ND\n"); // default to empty BlueValues
+ rEmitter.emitValVector( "/OtherBlues [", "]ND\n", mpCffLocal->maOtherBlues);
+ rEmitter.emitValVector( "/FamilyBlues [", "]ND\n", mpCffLocal->maFamilyBlues);
+ rEmitter.emitValVector( "/FamilyOtherBlues [", "]ND\n", mpCffLocal->maFamilyOtherBlues);
+
+ if( mpCffLocal->mfBlueScale) {
+ pOut += sprintf( pOut, "/BlueScale ");
+ pOut += dbl2str( pOut, mpCffLocal->mfBlueScale);
+ pOut += sprintf( pOut, " def\n");
+ }
+ if( mpCffLocal->mfBlueShift) { // default BlueShift==7
+ pOut += sprintf( pOut, "/BlueShift ");
+ pOut += dbl2str( pOut, mpCffLocal->mfBlueShift);
+ pOut += sprintf( pOut, " def\n");
+ }
+ if( mpCffLocal->mfBlueFuzz) { // default BlueFuzz==1
+ pOut += sprintf( pOut, "/BlueFuzz ");
+ pOut += dbl2str( pOut, mpCffLocal->mfBlueFuzz);
+ pOut += sprintf( pOut, " def\n");
+ }
+
+ // emit stem hint related privdict entries
+ if( mpCffLocal->maStemStdHW) {
+ pOut += sprintf( pOut, "/StdHW [");
+ pOut += dbl2str( pOut, mpCffLocal->maStemStdHW);
+ pOut += sprintf( pOut, "] def\n");
+ }
+ if( mpCffLocal->maStemStdVW) {
+ pOut += sprintf( pOut, "/StdVW [");
+ pOut += dbl2str( pOut, mpCffLocal->maStemStdVW);
+ pOut += sprintf( pOut, "] def\n");
+ }
+ rEmitter.emitValVector( "/StemSnapH [", "]ND\n", mpCffLocal->maStemSnapH);
+ rEmitter.emitValVector( "/StemSnapV [", "]ND\n", mpCffLocal->maStemSnapV);
+
+ // emit other hints
+ if( mpCffLocal->mbForceBold)
+ pOut += sprintf( pOut, "/ForceBold true def\n");
+ if( mpCffLocal->mnLangGroup != 0)
+ pOut += sprintf( pOut, "/LanguageGroup %d def\n", mpCffLocal->mnLangGroup);
+ if( mpCffLocal->mnLangGroup == 1) // compatibility with ancient printers
+ pOut += sprintf( pOut, "/RndStemUp false def\n");
+ if( mpCffLocal->mfExpFactor) {
+ pOut += sprintf( pOut, "/ExpansionFactor ");
+ pOut += dbl2str( pOut, mpCffLocal->mfExpFactor);
+ pOut += sprintf( pOut, " def\n");
+ }
+
+ // emit remaining privdict entries
+ pOut += sprintf( pOut, "/UniqueID %d def\n", nUniqueId);
+ // TODO?: more privdict entries?
+
+ static const char aOtherSubrs[] =
+ "/OtherSubrs\n"
+ "% Dummy code for faking flex hints\n"
+ "[ {} {} {} {systemdict /internaldict known not {pop 3}\n"
+ "{1183615869 systemdict /internaldict get exec\n"
+ "dup /startlock known\n"
+ "{/startlock get exec}\n"
+ "{dup /strtlck known\n"
+ "{/strtlck get exec}\n"
+ "{pop 3}\nifelse}\nifelse}\nifelse\n} executeonly\n"
+ "] ND\n";
+ memcpy( pOut, aOtherSubrs, sizeof(aOtherSubrs)-1);
+ pOut += sizeof(aOtherSubrs)-1;
+
+ // emit used GlobalSubr charstrings
+ // these are the just the default subrs
+ // TODO: do we need them as the flex hints are resolved differently?
+ static const char aSubrs[] =
+ "/Subrs 5 array\n"
+ "dup 0 15 RD \x5F\x3D\x6B\xAC\x3C\xBD\x74\x3D\x3E\x17\xA0\x86\x58\x08\x85 NP\n"
+ "dup 1 9 RD \x5F\x3D\x6B\xD8\xA6\xB5\x68\xB6\xA2 NP\n"
+ "dup 2 9 RD \x5F\x3D\x6B\xAC\x39\x46\xB9\x43\xF9 NP\n"
+ "dup 3 5 RD \x5F\x3D\x6B\xAC\xB9 NP\n"
+ "dup 4 12 RD \x5F\x3D\x6B\xAC\x3E\x5D\x48\x54\x62\x76\x39\x03 NP\n"
+ "ND\n";
+ memcpy( pOut, aSubrs, sizeof(aSubrs)-1);
+ pOut += sizeof(aSubrs)-1;
+
+ // TODO: emit more GlobalSubr charstrings?
+ // TODO: emit used LocalSubr charstrings?
+
+ // emit the CharStrings for the requested glyphs
+ std::vector<CharString> aCharStrings;
+ mbDoSeac = true;
+ convertCharStrings(pReqGlyphIds, nGlyphCount, aCharStrings);
+
+ // The previous convertCharStrings might collect extra glyphs used in seac
+ // operator, convert them as well
+ if (!maExtraGlyphIds.empty())
+ {
+ mbDoSeac = false;
+ convertCharStrings(maExtraGlyphIds.data(), maExtraGlyphIds.size(), aCharStrings);
+ }
+ pOut += sprintf( pOut,
+ "2 index /CharStrings %zu dict dup begin\n", aCharStrings.size());
+ rEmitter.emitAllCrypted();
+ for (size_t i = 0; i < aCharStrings.size(); i++)
+ {
+ const auto& rCharString = aCharStrings[i];
+ // get the glyph name
+ const char* pGlyphName = getGlyphName(rCharString.nCffGlyphId);
+ // emit the encrypted Type1op charstring
+ pOut += sprintf( pOut, "/%s %d RD ", pGlyphName, rCharString.nLen);
+ memcpy( pOut, rCharString.aOps, rCharString.nLen);
+ pOut += rCharString.nLen;
+ pOut += sprintf( pOut, " ND\n");
+ rEmitter.emitAllCrypted();
+ // provide individual glyphwidths if requested
+ if( pGlyphWidths ) {
+ ValType aCharWidth = rCharString.aCharWidth;
+ if( maFontMatrix.size() >= 4)
+ aCharWidth *= 1000.0F * maFontMatrix[0];
+ pGlyphWidths[i] = static_cast<sal_Int32>(aCharWidth);
+ }
+ }
+ pOut += sprintf( pOut, "end end\nreadonly put\nput\n");
+ pOut += sprintf( pOut, "dup/FontName get exch definefont pop\n");
+ pOut += sprintf( pOut, "mark currentfile closefile\n");
+ rEmitter.emitAllCrypted();
+
+ // mark stop of eexec encryption
+ if( rEmitter.mbPfbSubset) {
+ const int nEExecLen = rEmitter.tellPos() - nEExecSegTell;
+ rEmitter.updateLen( nEExecSegTell-4, nEExecLen);
+ }
+
+ // create PFB footer
+ static const char aPfxFooter[] = "\x80\x01\x14\x02\x00\x00\n" // TODO: check segment len
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "0000000000000000000000000000000000000000000000000000000000000000\n"
+ "cleartomark\n"
+ "\x80\x03";
+ if( rEmitter.mbPfbSubset)
+ rEmitter.emitRawData( aPfxFooter, sizeof(aPfxFooter)-1);
+ else
+ rEmitter.emitRawData( aPfxFooter+6, sizeof(aPfxFooter)-9);
+
+ // provide details to the subset requesters, TODO: move into own method?
+ // note: Top and Bottom are flipped between Type1 and VCL
+ // note: the rest of VCL expects the details below to be scaled like for an emUnits==1000 font
+ ValType fXFactor = 1.0;
+ ValType fYFactor = 1.0;
+ if( maFontMatrix.size() >= 4) {
+ fXFactor = 1000.0F * maFontMatrix[0];
+ fYFactor = 1000.0F * maFontMatrix[3];
+ }
+ rFSInfo.m_aFontBBox = { Point(static_cast<sal_Int32>(aFontBBox[0] * fXFactor),
+ static_cast<sal_Int32>(aFontBBox[1] * fYFactor)),
+ Point(static_cast<sal_Int32>(aFontBBox[2] * fXFactor),
+ static_cast<sal_Int32>(aFontBBox[3] * fYFactor)) };
+ // PDF-Spec says the values below mean the ink bounds!
+ // TODO: use better approximations for these ink bounds
+ rFSInfo.m_nAscent = +rFSInfo.m_aFontBBox.Bottom(); // for capital letters
+ rFSInfo.m_nDescent = -rFSInfo.m_aFontBBox.Top(); // for all letters
+ rFSInfo.m_nCapHeight = rFSInfo.m_nAscent; // for top-flat capital letters
+
+ rFSInfo.m_nFontType = rEmitter.mbPfbSubset ? FontType::TYPE1_PFB : FontType::TYPE1_PFA;
+ rFSInfo.m_aPSName = OUString( rEmitter.maSubsetName, strlen(rEmitter.maSubsetName), RTL_TEXTENCODING_UTF8 );
+}
+
+bool FontSubsetInfo::CreateFontSubsetFromCff( sal_Int32* pOutGlyphWidths )
+{
+ CffSubsetterContext aCff( mpInFontBytes, mnInByteLength);
+ bool bRC = aCff.initialCffRead();
+ if (!bRC)
+ return bRC;
+
+ // emit Type1 subset from the CFF input
+ // TODO: also support CFF->CFF subsetting (when PDF-export and PS-printing need it)
+ const bool bPfbSubset(mnReqFontTypeMask & FontType::TYPE1_PFB);
+ Type1Emitter aType1Emitter( mpOutFile, bPfbSubset);
+ aType1Emitter.setSubsetName( mpReqFontName);
+ aCff.emitAsType1( aType1Emitter,
+ mpReqGlyphIds, mpReqEncodedIds,
+ pOutGlyphWidths, mnReqGlyphCount, *this);
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/fontsubset.cxx b/vcl/source/fontsubset/fontsubset.cxx
new file mode 100644
index 000000000..bd663d8bc
--- /dev/null
+++ b/vcl/source/fontsubset/fontsubset.cxx
@@ -0,0 +1,163 @@
+/* -*- 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 <osl/diagnose.h>
+#include <sal/log.hxx>
+
+#include <fontsubset.hxx>
+#include <sft.hxx>
+
+FontSubsetInfo::FontSubsetInfo()
+ : m_nAscent( 0)
+ , m_nDescent( 0)
+ , m_nCapHeight( 0)
+ , m_nFontType( FontType::NO_FONT)
+ , mpInFontBytes( nullptr)
+ , mnInByteLength( 0)
+ , meInFontType( FontType::NO_FONT)
+ , mpSftTTFont( nullptr)
+ , mnReqFontTypeMask( FontType::NO_FONT )
+ , mpOutFile(nullptr)
+ , mpReqFontName(nullptr)
+ , mpReqGlyphIds(nullptr)
+ , mpReqEncodedIds(nullptr)
+ , mnReqGlyphCount(0)
+{
+}
+
+FontSubsetInfo::~FontSubsetInfo()
+{
+}
+
+// prepare subsetting for fonts where the input font file is mapped
+void FontSubsetInfo::LoadFont(
+ FontType eInFontType,
+ const unsigned char* pInFontBytes, int nInByteLength)
+{
+ SAL_WARN_IF( (mpSftTTFont != nullptr), "vcl", "Subset from SFT and from mapped font-file requested");
+ meInFontType = eInFontType;
+ mpInFontBytes = pInFontBytes;
+ mnInByteLength = nInByteLength;
+}
+
+// prepare subsetting for fonts that are known to the SFT-parser
+void FontSubsetInfo::LoadFont( vcl::TrueTypeFont* pSftTTFont )
+{
+ SAL_WARN_IF( (mpInFontBytes != nullptr), "vcl", "Subset from SFT and from mapped font-file requested");
+ mpSftTTFont = pSftTTFont;
+ meInFontType = FontType::ANY_SFNT;
+}
+
+bool FontSubsetInfo::CreateFontSubset(
+ FontType nReqFontTypeMask,
+ FILE* pOutFile, const char* pReqFontName,
+ const sal_GlyphId* pReqGlyphIds, const sal_uInt8* pReqEncodedIds, int nReqGlyphCount,
+ sal_Int32* pOutGlyphWidths)
+{
+ // prepare request details needed by all underlying subsetters
+ mnReqFontTypeMask = nReqFontTypeMask;
+ mpOutFile = pOutFile;
+ mpReqFontName = pReqFontName;
+ mpReqGlyphIds = pReqGlyphIds;
+ mpReqEncodedIds = pReqEncodedIds;
+ mnReqGlyphCount = nReqGlyphCount;
+
+ // TODO: move the glyphid/encid/notdef reshuffling from the callers to here
+
+ // dispatch to underlying subsetters
+ bool bOK = false;
+
+ // TODO: better match available input-type to possible subset-types
+ switch( meInFontType) {
+ case FontType::SFNT_TTF:
+ case FontType::SFNT_CFF:
+ case FontType::ANY_SFNT:
+ bOK = CreateFontSubsetFromSfnt( pOutGlyphWidths);
+ break;
+ case FontType::CFF_FONT:
+ bOK = CreateFontSubsetFromCff( pOutGlyphWidths);
+ break;
+ case FontType::TYPE1_PFA:
+ case FontType::TYPE1_PFB:
+ case FontType::ANY_TYPE1:
+ bOK = CreateFontSubsetFromType1( pOutGlyphWidths);
+ break;
+ case FontType::NO_FONT:
+ default:
+ OSL_FAIL( "unhandled type in CreateFontSubset()");
+ break;
+ }
+
+ return bOK;
+}
+
+// TODO: move function to sft.cxx to replace dummy implementation
+bool FontSubsetInfo::CreateFontSubsetFromSfnt( sal_Int32* pOutGlyphWidths )
+{
+ // handle SFNT_CFF fonts
+ sal_uInt32 nCffLength = 0;
+ const sal_uInt8* pCffBytes = mpSftTTFont->table(vcl::O_CFF, nCffLength);
+ if (pCffBytes)
+ {
+ LoadFont( FontType::CFF_FONT, pCffBytes, nCffLength);
+ const bool bOK = CreateFontSubsetFromCff( pOutGlyphWidths);
+ return bOK;
+ }
+
+ // handle SFNT_TTF fonts
+ // by forwarding the subset request to AG's sft subsetter
+#if 1 // TODO: remove conversion tp 16bit glyphids when sft-subsetter has been updated
+ std::vector<sal_uInt16> aShortGlyphIds;
+ aShortGlyphIds.reserve(mnReqGlyphCount);
+ for (int i = 0; i < mnReqGlyphCount; ++i)
+ aShortGlyphIds.push_back(static_cast<sal_uInt16>(mpReqGlyphIds[i]));
+ // remove const_cast when sft-subsetter is const-correct
+ sal_uInt8* pEncArray = const_cast<sal_uInt8*>( mpReqEncodedIds );
+#endif
+ vcl::SFErrCodes nSFTErr = vcl::SFErrCodes::BadArg;
+ if( mnReqFontTypeMask & FontType::TYPE42_FONT )
+ {
+ nSFTErr = CreateT42FromTTGlyphs( mpSftTTFont, mpOutFile, mpReqFontName,
+ aShortGlyphIds.data(), pEncArray, mnReqGlyphCount );
+ }
+ else if( mnReqFontTypeMask & FontType::TYPE3_FONT )
+ {
+ nSFTErr = CreateT3FromTTGlyphs( mpSftTTFont, mpOutFile, mpReqFontName,
+ aShortGlyphIds.data(), pEncArray, mnReqGlyphCount,
+ 0 /* 0 = horizontal, 1 = vertical */ );
+ }
+ else if( mnReqFontTypeMask & FontType::SFNT_TTF )
+ {
+ // TODO: use CreateTTFromTTGlyphs()
+ // TODO: move functionality from callers here
+ }
+
+ return (nSFTErr != vcl::SFErrCodes::Ok);
+}
+
+// TODO: replace dummy implementation
+bool FontSubsetInfo::CreateFontSubsetFromType1( const sal_Int32* /*pOutGlyphWidths*/)
+{
+ SAL_WARN("vcl.fonts",
+ "CreateFontSubsetFromType1: replace dummy implementation.");
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/list.cxx b/vcl/source/fontsubset/list.cxx
new file mode 100644
index 000000000..aca585678
--- /dev/null
+++ b/vcl/source/fontsubset/list.cxx
@@ -0,0 +1,229 @@
+/* -*- 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 .
+ */
+
+/*[]---------------------------------------------------[]*/
+/*| |*/
+/*| list.c - bidirectional list class |*/
+/*| |*/
+/*| |*/
+/*| Author: Alexander Gelfenbain |*/
+/*[]---------------------------------------------------[]*/
+
+#include <assert.h>
+#include <cstdlib>
+
+#include "list.h"
+
+namespace {
+
+/*- private data types */
+struct lnode {
+ struct lnode *next;
+ struct lnode *prev;
+
+ void *value;
+
+};
+
+}
+
+struct list_ {
+ lnode *head, *tail, *cptr;
+ size_t aCount;
+ list_destructor eDtor;
+};
+
+/*- private methods */
+
+static lnode *newNode(void *el)
+{
+ lnode *ptr = static_cast<lnode *>(std::malloc(sizeof(lnode)));
+ assert(ptr != nullptr);
+
+ ptr->value = el;
+
+ return ptr;
+}
+
+static lnode *appendPrim(list pThis, void *el)
+{
+ lnode *ptr = newNode(el);
+ lnode **flink, *blink;
+
+ if (pThis->tail != nullptr) {
+ flink = &(pThis->tail->next);
+ blink = pThis->tail;
+ } else {
+ flink = &pThis->head;
+ blink = nullptr;
+ pThis->cptr = ptr; /*- list was empty - set current to this element */
+ }
+
+ *flink = ptr;
+ pThis->tail = ptr;
+
+ ptr->prev = blink;
+ ptr->next = nullptr;
+
+ pThis->aCount++;
+ return ptr;
+}
+
+/*- public methods */
+list listNewEmpty() /*- default ctor */
+{
+ list pThis = static_cast<list>(std::malloc(sizeof(struct list_)));
+ assert(pThis != nullptr);
+
+ pThis->aCount = 0;
+ pThis->eDtor = nullptr;
+ pThis->head = pThis->tail = pThis->cptr = nullptr;
+
+ return pThis;
+}
+
+void listDispose(list pThis) /*- dtor */
+{
+ assert(pThis != nullptr);
+ listClear(pThis);
+ std::free(pThis);
+}
+
+void listSetElementDtor(list pThis, list_destructor f)
+{
+ assert(pThis != nullptr);
+ pThis->eDtor = f;
+}
+
+/* calling this function on an empty list is a run-time error */
+void *listCurrent(list pThis)
+{
+ assert(pThis != nullptr);
+ assert(pThis->cptr != nullptr);
+ return pThis->cptr->value;
+}
+
+int listCount(list pThis)
+{
+ assert(pThis != nullptr);
+ return pThis->aCount;
+}
+
+int listIsEmpty(list pThis)
+{
+ assert(pThis != nullptr);
+ return pThis->aCount == 0;
+}
+
+int listNext(list pThis)
+{
+ return listSkipForward(pThis, 1);
+}
+
+int listSkipForward(list pThis, int n)
+{
+ int m = 0;
+ assert(pThis != nullptr);
+
+ if (pThis->cptr == nullptr) return 0;
+
+ while (n != 0) {
+ if (pThis->cptr->next == nullptr) break;
+ pThis->cptr = pThis->cptr->next;
+ n--;
+ m++;
+ }
+ return m;
+}
+
+int listToFirst(list pThis)
+{
+ assert(pThis != nullptr);
+
+ if (pThis->cptr != pThis->head) {
+ pThis->cptr = pThis->head;
+ return 1;
+ }
+ return 0;
+}
+
+int listToLast(list pThis)
+{
+ assert(pThis != nullptr);
+
+ if (pThis->cptr != pThis->tail) {
+ pThis->cptr = pThis->tail;
+ return 1;
+ }
+ return 0;
+}
+
+list listAppend(list pThis, void *el)
+{
+ assert(pThis != nullptr);
+
+ appendPrim(pThis, el);
+ return pThis;
+}
+
+list listRemove(list pThis)
+{
+ lnode *ptr = nullptr;
+ if (pThis->cptr == nullptr) return pThis;
+
+ if (pThis->cptr->next != nullptr) {
+ ptr = pThis->cptr->next;
+ pThis->cptr->next->prev = pThis->cptr->prev;
+ } else {
+ pThis->tail = pThis->cptr->prev;
+ }
+
+ if (pThis->cptr->prev != nullptr) {
+ if (ptr == nullptr) ptr = pThis->cptr->prev;
+ pThis->cptr->prev->next = pThis->cptr->next;
+ } else {
+ pThis->head = pThis->cptr->next;
+ }
+
+ if (pThis->eDtor) pThis->eDtor(pThis->cptr->value); /* call the dtor callback */
+
+ std::free(pThis->cptr);
+ pThis->aCount--;
+ pThis->cptr = ptr;
+ return pThis;
+}
+
+list listClear(list pThis)
+{
+ lnode *node = pThis->head, *ptr;
+
+ while (node) {
+ ptr = node->next;
+ if (pThis->eDtor) pThis->eDtor(node->value); /* call the dtor callback */
+ std::free(node);
+ pThis->aCount--;
+ node = ptr;
+ }
+
+ pThis->head = pThis->tail = pThis->cptr = nullptr;
+ assert(pThis->aCount == 0);
+ return pThis;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/list.h b/vcl/source/fontsubset/list.h
new file mode 100644
index 000000000..18fda151a
--- /dev/null
+++ b/vcl/source/fontsubset/list.h
@@ -0,0 +1,89 @@
+/* -*- 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 .
+ */
+
+/*[]---------------------------------------------------[]*/
+/*| |*/
+/*| Implementation of the list data type |*/
+/*| |*/
+/*| |*/
+/*| Author: Alexander Gelfenbain |*/
+/*[]---------------------------------------------------[]*/
+
+#ifndef INCLUDED_VCL_INC_LIST_H
+#define INCLUDED_VCL_INC_LIST_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/*
+ * List of void * pointers
+ */
+
+ typedef struct list_ *list;
+ typedef void (*list_destructor)(void *);
+
+/*- constructors and a destructor */
+ list listNewEmpty(void);
+#ifdef TEST
+ list listNewCopy(list);
+#endif
+ void listDispose(list);
+ void listSetElementDtor(list, list_destructor); /*- this function will be executed when the element is removed via listRemove() or listClear() */
+
+/*- queries */
+ void * listCurrent(list);
+ int listCount(list);
+ int listIsEmpty(list);
+#ifdef TEST
+ int listAtFirst(list);
+ int listAtLast(list);
+ int listPosition(list); /* Expensive! */
+#endif
+
+/*- positioning functions */
+/*- return the number of elements by which the current position in the list changes */
+ int listNext(list);
+ int listSkipForward(list, int n);
+ int listToFirst(list);
+ int listToLast(list);
+
+/*- adding and removing elements */
+ list listAppend(list, void *);
+#ifdef TEST
+ list listPrepend(list, void *);
+ list listInsertAfter(list, void *);
+ list listInsertBefore(list, void *);
+#endif
+ list listRemove(list); /* removes the current element */
+ list listClear(list); /* removes all elements */
+
+#ifdef TEST
+/*- forall */
+ void listForAll(list, void (*f)(void *));
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // INCLUDED_VCL_INC_LIST_H
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/sft.cxx b/vcl/source/fontsubset/sft.cxx
new file mode 100644
index 000000000..93ad3e98d
--- /dev/null
+++ b/vcl/source/fontsubset/sft.cxx
@@ -0,0 +1,2468 @@
+/* -*- 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 .
+ */
+
+/*
+ * Sun Font Tools
+ *
+ * Author: Alexander Gelfenbain
+ *
+ */
+
+#include <assert.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#ifdef UNX
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+#include <sft.hxx>
+#include <impfontcharmap.hxx>
+#ifdef SYSTEM_LIBFIXMATH
+#include <libfixmath/fix16.hpp>
+#else
+#include <tools/fix16.hxx>
+#endif
+#include "ttcr.hxx"
+#include "xlat.hxx"
+#include <rtl/crc.h>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/endian.h>
+#include <fontsubset.hxx>
+#include <algorithm>
+
+namespace vcl
+{
+
+/*- module identification */
+
+const char * const modname = "SunTypeTools-TT";
+const char * const modver = "1.0";
+const char * const modextra = "gelf";
+
+/*- private functions, constants and data types */
+
+namespace {
+
+enum PathSegmentType {
+ PS_NOOP = 0,
+ PS_MOVETO = 1,
+ PS_LINETO = 2,
+ PS_CURVETO = 3,
+ PS_CLOSEPATH = 4
+};
+
+struct PSPathElement
+{
+ PathSegmentType type;
+ int x1, y1;
+ int x2, y2;
+ int x3, y3;
+
+ explicit PSPathElement( PathSegmentType i_eType ) : type( i_eType ),
+ x1( 0 ), y1( 0 ),
+ x2( 0 ), y2( 0 ),
+ x3( 0 ), y3( 0 )
+ {
+ }
+};
+
+/*- In horizontal writing mode right sidebearing is calculated using this formula
+ *- rsb = aw - (lsb + xMax - xMin) -*/
+struct TTGlyphMetrics {
+ sal_Int16 xMin;
+ sal_Int16 yMin;
+ sal_Int16 xMax;
+ sal_Int16 yMax;
+ sal_uInt16 aw; /*- Advance Width (horizontal writing mode) */
+ sal_Int16 lsb; /*- Left sidebearing (horizontal writing mode) */
+ sal_uInt16 ah; /*- advance height (vertical writing mode) */
+};
+
+#define HFORMAT_LINELEN 64
+
+struct HexFmt {
+ FILE *o;
+ char buffer[HFORMAT_LINELEN];
+ size_t bufpos;
+ int total;
+};
+
+struct GlyphOffsets {
+ sal_uInt32 nGlyphs; /* number of glyphs in the font + 1 */
+ sal_uInt32 *offs; /* array of nGlyphs offsets */
+};
+
+}
+
+static void *smalloc(size_t size)
+{
+ void *res = malloc(size);
+ assert(res != nullptr);
+ return res;
+}
+
+static void *scalloc(size_t n, size_t size)
+{
+ void *res = calloc(n, size);
+ assert(res != nullptr);
+ return res;
+}
+
+/*- Data access methods for data stored in big-endian format */
+static sal_Int16 GetInt16(const sal_uInt8 *ptr, size_t offset)
+{
+ sal_Int16 t;
+ assert(ptr != nullptr);
+
+ t = (ptr+offset)[0] << 8 | (ptr+offset)[1];
+
+ return t;
+}
+
+static sal_uInt16 GetUInt16(const sal_uInt8 *ptr, size_t offset)
+{
+ sal_uInt16 t;
+ assert(ptr != nullptr);
+
+ t = (ptr+offset)[0] << 8 | (ptr+offset)[1];
+
+ return t;
+}
+
+static sal_Int32 GetInt32(const sal_uInt8 *ptr, size_t offset)
+{
+ sal_Int32 t;
+ assert(ptr != nullptr);
+
+ t = (ptr+offset)[0] << 24 | (ptr+offset)[1] << 16 |
+ (ptr+offset)[2] << 8 | (ptr+offset)[3];
+
+ return t;
+}
+
+static sal_uInt32 GetUInt32(const sal_uInt8 *ptr, size_t offset)
+{
+ sal_uInt32 t;
+ assert(ptr != nullptr);
+
+ t = (ptr+offset)[0] << 24 | (ptr+offset)[1] << 16 |
+ (ptr+offset)[2] << 8 | (ptr+offset)[3];
+
+ return t;
+}
+
+static F16Dot16 fixedMul(F16Dot16 a, F16Dot16 b)
+{
+ return fix16_mul(a, b);
+}
+
+static F16Dot16 fixedDiv(F16Dot16 a, F16Dot16 b)
+{
+ return fix16_div(a, b);
+}
+
+/*- returns a * b / c -*/
+/* XXX provide a real implementation that preserves accuracy */
+static F16Dot16 fixedMulDiv(F16Dot16 a, F16Dot16 b, F16Dot16 c)
+{
+ F16Dot16 res = fixedMul(a, b);
+ return fixedDiv(res, c);
+}
+
+/*- Translate units from TT to PS (standard 1/1000) -*/
+static int XUnits(int unitsPerEm, int n)
+{
+ return (n * 1000) / unitsPerEm;
+}
+
+static char toHex(sal_uInt8 nIndex)
+{
+ /* Hex Formatter functions */
+ static const char HexChars[] = "0123456789ABCDEF";
+ assert(nIndex < SAL_N_ELEMENTS(HexChars));
+ return HexChars[nIndex];
+}
+
+static HexFmt *HexFmtNew(FILE *outf)
+{
+ HexFmt* res = static_cast<HexFmt*>(smalloc(sizeof(HexFmt)));
+ res->bufpos = res->total = 0;
+ res->o = outf;
+ return res;
+}
+
+static bool HexFmtFlush(HexFmt *_this)
+{
+ bool bRet = true;
+ if (_this->bufpos) {
+ size_t nWritten = fwrite(_this->buffer, 1, _this->bufpos, _this->o);
+ bRet = nWritten == _this->bufpos;
+ _this->bufpos = 0;
+ }
+ return bRet;
+}
+
+static void HexFmtOpenString(HexFmt *_this)
+{
+ fputs("<\n", _this->o);
+}
+
+static void HexFmtCloseString(HexFmt *_this)
+{
+ HexFmtFlush(_this);
+ fputs("00\n>\n", _this->o);
+}
+
+static void HexFmtDispose(HexFmt *_this)
+{
+ HexFmtFlush(_this);
+ free(_this);
+}
+
+static void HexFmtBlockWrite(HexFmt *_this, const void *ptr, sal_uInt32 size)
+{
+ if (_this->total + size > 65534) {
+ HexFmtFlush(_this);
+ HexFmtCloseString(_this);
+ _this->total = 0;
+ HexFmtOpenString(_this);
+ }
+
+ for (sal_uInt32 i = 0; i < size; ++i) {
+ sal_uInt8 Ch = static_cast<sal_uInt8 const *>(ptr)[i];
+ _this->buffer[_this->bufpos++] = toHex(Ch >> 4);
+ _this->buffer[_this->bufpos++] = toHex(Ch & 0xF);
+ if (_this->bufpos == HFORMAT_LINELEN) {
+ HexFmtFlush(_this);
+ fputc('\n', _this->o);
+ }
+
+ }
+ _this->total += size;
+}
+
+/* Outline Extraction functions */
+
+/* fills the aw and lsb entries of the TTGlyphMetrics structure from hmtx table -*/
+static void GetMetrics(AbstractTrueTypeFont const *ttf, sal_uInt32 glyphID, TTGlyphMetrics *metrics)
+{
+ sal_uInt32 nSize;
+ const sal_uInt8* table = ttf->table(O_hmtx, nSize);
+
+ metrics->aw = metrics->lsb = metrics->ah = 0;
+ if (!table || !ttf->horzMetricCount())
+ return;
+
+ if (glyphID < ttf->horzMetricCount())
+ {
+ metrics->aw = GetUInt16(table, 4 * glyphID);
+ metrics->lsb = GetInt16(table, 4 * glyphID + 2);
+ }
+ else
+ {
+ metrics->aw = GetUInt16(table, 4 * (ttf->horzMetricCount() - 1));
+ metrics->lsb = GetInt16(table + ttf->horzMetricCount() * 4, (glyphID - ttf->horzMetricCount()) * 2);
+ }
+
+ table = ttf->table(O_vmtx, nSize);
+ if (!table || !ttf->vertMetricCount())
+ return;
+
+ if (glyphID < ttf->vertMetricCount())
+ metrics->ah = GetUInt16(table, 4 * glyphID);
+ else
+ metrics->ah = GetUInt16(table, 4 * (ttf->vertMetricCount() - 1));
+}
+
+static int GetTTGlyphOutline(AbstractTrueTypeFont *, sal_uInt32 , ControlPoint **, TTGlyphMetrics *, std::vector< sal_uInt32 >* );
+
+/* returns the number of control points, allocates the pointArray */
+static int GetSimpleTTOutline(AbstractTrueTypeFont const *ttf, sal_uInt32 glyphID, ControlPoint **pointArray, TTGlyphMetrics *metrics)
+{
+ sal_uInt32 nTableSize;
+ const sal_uInt8* table = ttf->table(O_glyf, nTableSize);
+ sal_uInt8 n;
+ int i, j, z;
+
+ *pointArray = nullptr;
+
+ if (glyphID >= ttf->glyphCount())
+ return 0;
+
+ sal_uInt32 nGlyphOffset = ttf->glyphOffset(glyphID);
+ if (nGlyphOffset > nTableSize)
+ return 0;
+
+ const sal_uInt8* ptr = table + nGlyphOffset;
+ const sal_uInt32 nMaxGlyphSize = nTableSize - nGlyphOffset;
+ constexpr sal_uInt32 nContourOffset = 10;
+ if (nMaxGlyphSize < nContourOffset)
+ return 0;
+
+ const sal_Int16 numberOfContours = GetInt16(ptr, GLYF_numberOfContours_offset);
+ if( numberOfContours <= 0 ) /*- glyph is not simple */
+ return 0;
+
+ const sal_Int32 nMaxContours = (nMaxGlyphSize - nContourOffset)/2;
+ if (numberOfContours > nMaxContours)
+ return 0;
+
+ if (metrics) { /*- GetCompoundTTOutline() calls this function with NULL metrics -*/
+ metrics->xMin = GetInt16(ptr, GLYF_xMin_offset);
+ metrics->yMin = GetInt16(ptr, GLYF_yMin_offset);
+ metrics->xMax = GetInt16(ptr, GLYF_xMax_offset);
+ metrics->yMax = GetInt16(ptr, GLYF_yMax_offset);
+ GetMetrics(ttf, glyphID, metrics);
+ }
+
+ /* determine the last point and be extra safe about it. But probably this code is not needed */
+ sal_uInt16 lastPoint=0;
+ for (i=0; i<numberOfContours; i++)
+ {
+ const sal_uInt16 t = GetUInt16(ptr, nContourOffset + i * 2);
+ if (t > lastPoint)
+ lastPoint = t;
+ }
+
+ sal_uInt32 nInstLenOffset = nContourOffset + numberOfContours * 2;
+ if (nInstLenOffset + 2 > nMaxGlyphSize)
+ return 0;
+ sal_uInt16 instLen = GetUInt16(ptr, nInstLenOffset);
+
+ sal_uInt32 nOffset = nContourOffset + 2 * numberOfContours + 2 + instLen;
+ if (nOffset > nMaxGlyphSize)
+ return 0;
+ const sal_uInt8* p = ptr + nOffset;
+
+ sal_uInt32 nBytesRemaining = nMaxGlyphSize - nOffset;
+ const sal_uInt32 palen = lastPoint+1;
+
+ //at a minimum its one byte per entry
+ if (palen > nBytesRemaining || lastPoint > nBytesRemaining-1)
+ {
+ SAL_WARN("vcl.fonts", "Font " << OUString::createFromAscii(ttf->fileName()) <<
+ "claimed a palen of "
+ << palen << " but max bytes remaining is " << nBytesRemaining);
+ return 0;
+ }
+
+ ControlPoint* pa = static_cast<ControlPoint*>(calloc(palen, sizeof(ControlPoint)));
+
+ i = 0;
+ while (i <= lastPoint) {
+ if (!nBytesRemaining)
+ {
+ SAL_WARN("vcl.fonts", "short read");
+ break;
+ }
+ sal_uInt8 flag = *p++;
+ --nBytesRemaining;
+ pa[i++].flags = static_cast<sal_uInt32>(flag);
+ if (flag & 8) { /*- repeat flag */
+ if (!nBytesRemaining)
+ {
+ SAL_WARN("vcl.fonts", "short read");
+ break;
+ }
+ n = *p++;
+ --nBytesRemaining;
+ // coverity[tainted_data : FALSE] - i > lastPoint extra checks the n loop bound
+ for (j=0; j<n; j++) {
+ if (i > lastPoint) { /*- if the font is really broken */
+ free(pa);
+ return 0;
+ }
+ pa[i++].flags = flag;
+ }
+ }
+ }
+
+ /*- Process the X coordinate */
+ z = 0;
+ for (i = 0; i <= lastPoint; i++) {
+ if (pa[i].flags & 0x02) {
+ if (!nBytesRemaining)
+ {
+ SAL_WARN("vcl.fonts", "short read");
+ break;
+ }
+ if (pa[i].flags & 0x10) {
+ z += static_cast<int>(*p++);
+ } else {
+ z -= static_cast<int>(*p++);
+ }
+ --nBytesRemaining;
+ } else if ( !(pa[i].flags & 0x10)) {
+ if (nBytesRemaining < 2)
+ {
+ SAL_WARN("vcl.fonts", "short read");
+ break;
+ }
+ z += GetInt16(p, 0);
+ p += 2;
+ nBytesRemaining -= 2;
+ }
+ pa[i].x = static_cast<sal_Int16>(z);
+ }
+
+ /*- Process the Y coordinate */
+ z = 0;
+ for (i = 0; i <= lastPoint; i++) {
+ if (pa[i].flags & 0x04) {
+ if (!nBytesRemaining)
+ {
+ SAL_WARN("vcl.fonts", "short read");
+ break;
+ }
+ if (pa[i].flags & 0x20) {
+ z += *p++;
+ } else {
+ z -= *p++;
+ }
+ --nBytesRemaining;
+ } else if ( !(pa[i].flags & 0x20)) {
+ if (nBytesRemaining < 2)
+ {
+ SAL_WARN("vcl.fonts", "short read");
+ break;
+ }
+ z += GetInt16(p, 0);
+ p += 2;
+ nBytesRemaining -= 2;
+ }
+ pa[i].y = static_cast<sal_Int16>(z);
+ }
+
+ for (i=0; i<numberOfContours; i++) {
+ sal_uInt16 offset = GetUInt16(ptr, 10 + i * 2);
+ SAL_WARN_IF(offset >= palen, "vcl.fonts", "Font " << OUString::createFromAscii(ttf->fileName()) <<
+ " contour " << i << " claimed an illegal offset of "
+ << offset << " but max offset is " << palen-1);
+ if (offset >= palen)
+ continue;
+ pa[offset].flags |= 0x00008000; /*- set the end contour flag */
+ }
+
+ *pointArray = pa;
+ return lastPoint + 1;
+}
+
+static F16Dot16 fromF2Dot14(sal_Int16 n)
+{
+ // Avoid undefined shift of negative values prior to C++2a:
+ return sal_uInt32(n) << 2;
+}
+
+static int GetCompoundTTOutline(AbstractTrueTypeFont *ttf, sal_uInt32 glyphID, ControlPoint **pointArray, TTGlyphMetrics *metrics, std::vector< sal_uInt32 >& glyphlist)
+{
+ sal_uInt16 flags, index;
+ sal_Int16 e, f;
+ sal_uInt32 nTableSize;
+ const sal_uInt8* table = ttf->table(O_glyf, nTableSize);
+ std::vector<ControlPoint> myPoints;
+ ControlPoint *nextComponent, *pa;
+ int i, np;
+ F16Dot16 a = 0x10000, b = 0, c = 0, d = 0x10000, m, n, abs1, abs2, abs3;
+
+ *pointArray = nullptr;
+
+ if (glyphID >= ttf->glyphCount())
+ return 0;
+
+ sal_uInt32 nGlyphOffset = ttf->glyphOffset(glyphID);
+ if (nGlyphOffset > nTableSize)
+ return 0;
+
+ const sal_uInt8* ptr = table + nGlyphOffset;
+ sal_uInt32 nAvailableBytes = nTableSize - nGlyphOffset;
+
+ if (GLYF_numberOfContours_offset + 2 > nAvailableBytes)
+ return 0;
+
+ if (GetInt16(ptr, GLYF_numberOfContours_offset) != -1) /* number of contours - glyph is not compound */
+ return 0;
+
+ if (metrics) {
+ metrics->xMin = GetInt16(ptr, GLYF_xMin_offset);
+ metrics->yMin = GetInt16(ptr, GLYF_yMin_offset);
+ metrics->xMax = GetInt16(ptr, GLYF_xMax_offset);
+ metrics->yMax = GetInt16(ptr, GLYF_yMax_offset);
+ GetMetrics(ttf, glyphID, metrics);
+ }
+
+ if (nAvailableBytes < 10)
+ {
+ SAL_WARN("vcl.fonts", "short read");
+ return 0;
+ }
+
+ ptr += 10;
+ nAvailableBytes -= 10;
+
+ do {
+
+ if (nAvailableBytes < 4)
+ {
+ SAL_WARN("vcl.fonts", "short read");
+ return 0;
+ }
+ flags = GetUInt16(ptr, 0);
+ /* printf("flags: 0x%X\n", flags); */
+ index = GetUInt16(ptr, 2);
+ ptr += 4;
+ nAvailableBytes -= 4;
+
+ if( std::find( glyphlist.begin(), glyphlist.end(), index ) != glyphlist.end() )
+ {
+ SAL_WARN("vcl.fonts", "Endless loop found in a compound glyph.");
+
+#if OSL_DEBUG_LEVEL > 1
+ std::ostringstream oss;
+ oss << index << " -> [";
+ for( const auto& rGlyph : glyphlist )
+ {
+ oss << (int) rGlyph << " ";
+ }
+ oss << "]";
+ SAL_INFO("vcl.fonts", oss.str());
+ /**/
+#endif
+ return 0;
+ }
+
+ glyphlist.push_back( index );
+
+ np = GetTTGlyphOutline(ttf, index, &nextComponent, nullptr, &glyphlist);
+
+ if( ! glyphlist.empty() )
+ glyphlist.pop_back();
+
+ if (np == 0)
+ {
+ /* XXX that probably indicates a corrupted font */
+ SAL_WARN("vcl.fonts", "An empty compound!");
+ /* assert(!"An empty compound"); */
+ return 0;
+ }
+
+ if ((flags & USE_MY_METRICS) && metrics)
+ GetMetrics(ttf, index, metrics);
+
+ if (flags & ARG_1_AND_2_ARE_WORDS) {
+ if (nAvailableBytes < 4)
+ {
+ SAL_WARN("vcl.fonts", "short read");
+ free(nextComponent);
+ return 0;
+ }
+ e = GetInt16(ptr, 0);
+ f = GetInt16(ptr, 2);
+ /* printf("ARG_1_AND_2_ARE_WORDS: %d %d\n", e & 0xFFFF, f & 0xFFFF); */
+ ptr += 4;
+ nAvailableBytes -= 4;
+ } else {
+ if (nAvailableBytes < 2)
+ {
+ SAL_WARN("vcl.fonts", "short read");
+ free(nextComponent);
+ return 0;
+ }
+ if (flags & ARGS_ARE_XY_VALUES) { /* args are signed */
+ e = static_cast<sal_Int8>(*ptr++);
+ f = static_cast<sal_Int8>(*ptr++);
+ /* printf("ARGS_ARE_XY_VALUES: %d %d\n", e & 0xFF, f & 0xFF); */
+ } else { /* args are unsigned */
+ /* printf("!ARGS_ARE_XY_VALUES\n"); */
+ e = *ptr++;
+ f = *ptr++;
+ }
+ nAvailableBytes -= 2;
+ }
+
+ a = d = 0x10000;
+ b = c = 0;
+
+ if (flags & WE_HAVE_A_SCALE) {
+ if (nAvailableBytes < 2)
+ {
+ SAL_WARN("vcl.fonts", "short read");
+ free(nextComponent);
+ return 0;
+ }
+ a = fromF2Dot14(GetInt16(ptr, 0));
+ d = a;
+ ptr += 2;
+ nAvailableBytes -= 2;
+ } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
+ if (nAvailableBytes < 4)
+ {
+ SAL_WARN("vcl.fonts", "short read");
+ free(nextComponent);
+ return 0;
+ }
+ a = fromF2Dot14(GetInt16(ptr, 0));
+ d = fromF2Dot14(GetInt16(ptr, 2));
+ ptr += 4;
+ nAvailableBytes -= 4;
+ } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
+ if (nAvailableBytes < 8)
+ {
+ SAL_WARN("vcl.fonts", "short read");
+ free(nextComponent);
+ return 0;
+ }
+ a = fromF2Dot14(GetInt16(ptr, 0));
+ b = fromF2Dot14(GetInt16(ptr, 2));
+ c = fromF2Dot14(GetInt16(ptr, 4));
+ d = fromF2Dot14(GetInt16(ptr, 6));
+ ptr += 8;
+ nAvailableBytes -= 8;
+ }
+
+ abs1 = (a < 0) ? -a : a;
+ abs2 = (b < 0) ? -b : b;
+ m = std::max(abs1, abs2);
+ abs3 = abs1 - abs2;
+ if (abs3 < 0) abs3 = -abs3;
+ if (abs3 <= 33) m *= 2;
+
+ abs1 = (c < 0) ? -c : c;
+ abs2 = (d < 0) ? -d : d;
+ n = std::max(abs1, abs2);
+ abs3 = abs1 - abs2;
+ if (abs3 < 0) abs3 = -abs3;
+ if (abs3 <= 33) n *= 2;
+
+ SAL_WARN_IF(np && (!m || !n), "vcl.fonts", "Parsing error in " << OUString::createFromAscii(ttf->fileName()) <<
+ ": divide by zero");
+
+ if (m != 0 && n != 0) {
+ for (i=0; i<np; i++) {
+ F16Dot16 t;
+ ControlPoint cp;
+ cp.flags = nextComponent[i].flags;
+ const sal_uInt16 x = nextComponent[i].x;
+ const sal_uInt16 y = nextComponent[i].y;
+ t = o3tl::saturating_add(o3tl::saturating_add(fixedMulDiv(a, x << 16, m), fixedMulDiv(c, y << 16, m)), sal_Int32(sal_uInt16(e) << 16));
+ cp.x = static_cast<sal_Int16>(fixedMul(t, m) >> 16);
+ t = o3tl::saturating_add(o3tl::saturating_add(fixedMulDiv(b, x << 16, n), fixedMulDiv(d, y << 16, n)), sal_Int32(sal_uInt16(f) << 16));
+ cp.y = static_cast<sal_Int16>(fixedMul(t, n) >> 16);
+
+ myPoints.push_back( cp );
+ }
+ }
+
+ free(nextComponent);
+
+ } while (flags & MORE_COMPONENTS);
+
+ // #i123417# some fonts like IFAOGrec have no outline points in some compound glyphs
+ // so this unlikely but possible scenario should be handled gracefully
+ if( myPoints.empty() )
+ return 0;
+
+ np = myPoints.size();
+ if (np > 0)
+ {
+ pa = static_cast<ControlPoint*>(calloc(np, sizeof(ControlPoint)));
+ assert(pa != nullptr);
+
+ memcpy(pa, myPoints.data(), np * sizeof(ControlPoint));
+
+ *pointArray = pa;
+ }
+ return np;
+}
+
+/* NOTE: GetTTGlyphOutline() returns -1 if the glyphID is incorrect,
+ * but Get{Simple|Compound}GlyphOutline returns 0 in such a case.
+ *
+ * NOTE: glyphlist is the stack of glyphs traversed while constructing
+ * a composite glyph. This is a safeguard against endless recursion
+ * in corrupted fonts.
+ */
+static int GetTTGlyphOutline(AbstractTrueTypeFont *ttf, sal_uInt32 glyphID, ControlPoint **pointArray, TTGlyphMetrics *metrics, std::vector< sal_uInt32 >* glyphlist)
+{
+ sal_uInt32 glyflength;
+ const sal_uInt8 *table = ttf->table(O_glyf, glyflength);
+ sal_Int16 numberOfContours;
+ int res;
+ *pointArray = nullptr;
+
+ if (metrics)
+ memset(metrics, 0, sizeof(TTGlyphMetrics));
+
+ if (glyphID >= ttf->glyphCount())
+ return -1;
+
+ sal_uInt32 nNextOffset = ttf->glyphOffset(glyphID + 1);
+ if (nNextOffset > glyflength)
+ return -1;
+
+ sal_uInt32 nOffset = ttf->glyphOffset(glyphID);
+ if (nOffset > nNextOffset)
+ return -1;
+
+ int length = nNextOffset - nOffset;
+ if (length == 0) { /*- empty glyphs still have hmtx and vmtx metrics values */
+ if (metrics) GetMetrics(ttf, glyphID, metrics);
+ return 0;
+ }
+
+ const sal_uInt8* ptr = table + nOffset;
+ const sal_uInt32 nMaxGlyphSize = glyflength - nOffset;
+
+ if (nMaxGlyphSize < 2)
+ return -1;
+
+ numberOfContours = GetInt16(ptr, 0);
+
+ if (numberOfContours >= 0)
+ {
+ res=GetSimpleTTOutline(ttf, glyphID, pointArray, metrics);
+ }
+ else
+ {
+ std::vector< sal_uInt32 > aPrivList { glyphID };
+ res = GetCompoundTTOutline(ttf, glyphID, pointArray, metrics, glyphlist ? *glyphlist : aPrivList );
+ }
+
+ return res;
+}
+
+/*- returns the number of items in the path -*/
+
+static int BSplineToPSPath(ControlPoint const *srcA, int srcCount, PSPathElement **path)
+{
+ std::vector< PSPathElement > aPathList;
+ int nPathCount = 0;
+ PSPathElement p( PS_NOOP );
+
+ int x0 = 0, y0 = 0, x1 = 0, y1 = 0, x2, y2, curx, cury;
+ bool lastOff = false; /*- last point was off-contour */
+ int scflag = 1; /*- start contour flag */
+ bool ecflag = false; /*- end contour flag */
+ int cp = 0; /*- current point */
+ int StartContour = 0, EndContour = 1;
+
+ *path = nullptr;
+
+ /* if (srcCount > 0) for(;;) */
+ while (srcCount > 0) { /*- srcCount does not get changed inside the loop. */
+ if (scflag) {
+ int l = cp;
+ StartContour = cp;
+ while (!(srcA[l].flags & 0x8000)) l++;
+ EndContour = l;
+ if (StartContour == EndContour) {
+ if (cp + 1 < srcCount) {
+ cp++;
+ continue;
+ } else {
+ break;
+ }
+ }
+ p = PSPathElement(PS_MOVETO);
+ if (!(srcA[cp].flags & 1)) {
+ if (!(srcA[EndContour].flags & 1)) {
+ p.x1 = x0 = (srcA[cp].x + srcA[EndContour].x + 1) / 2;
+ p.y1 = y0 = (srcA[cp].y + srcA[EndContour].y + 1) / 2;
+ } else {
+ p.x1 = x0 = srcA[EndContour].x;
+ p.y1 = y0 = srcA[EndContour].y;
+ }
+ } else {
+ p.x1 = x0 = srcA[cp].x;
+ p.y1 = y0 = srcA[cp].y;
+ cp++;
+ }
+ aPathList.push_back( p );
+ lastOff = false;
+ scflag = 0;
+ }
+
+ curx = srcA[cp].x;
+ cury = srcA[cp].y;
+
+ if (srcA[cp].flags & 1)
+ {
+ if (lastOff)
+ {
+ p = PSPathElement(PS_CURVETO);
+ p.x1 = x0 + (2 * (x1 - x0) + 1) / 3;
+ p.y1 = y0 + (2 * (y1 - y0) + 1) / 3;
+ p.x2 = x1 + (curx - x1 + 1) / 3;
+ p.y2 = y1 + (cury - y1 + 1) / 3;
+ p.x3 = curx;
+ p.y3 = cury;
+ aPathList.push_back( p );
+ }
+ else
+ {
+ if (x0 != curx || y0 != cury)
+ { /* eliminate empty lines */
+ p = PSPathElement(PS_LINETO);
+ p.x1 = curx;
+ p.y1 = cury;
+ aPathList.push_back( p );
+ }
+ }
+ x0 = curx; y0 = cury; lastOff = false;
+ }
+ else
+ {
+ if (lastOff)
+ {
+ x2 = (x1 + curx + 1) / 2;
+ y2 = (y1 + cury + 1) / 2;
+ p = PSPathElement(PS_CURVETO);
+ p.x1 = x0 + (2 * (x1 - x0) + 1) / 3;
+ p.y1 = y0 + (2 * (y1 - y0) + 1) / 3;
+ p.x2 = x1 + (x2 - x1 + 1) / 3;
+ p.y2 = y1 + (y2 - y1 + 1) / 3;
+ p.x3 = x2;
+ p.y3 = y2;
+ aPathList.push_back( p );
+ x0 = x2; y0 = y2;
+ x1 = curx; y1 = cury;
+ } else {
+ x1 = curx; y1 = cury;
+ }
+ lastOff = true;
+ }
+
+ if (ecflag) {
+ aPathList.emplace_back(PS_CLOSEPATH );
+ scflag = 1;
+ ecflag = false;
+ cp = EndContour + 1;
+ if (cp >= srcCount) break;
+ continue;
+ }
+
+ if (cp == EndContour) {
+ cp = StartContour;
+ ecflag = true;
+ } else {
+ cp++;
+ }
+ }
+
+ if( (nPathCount = static_cast<int>(aPathList.size())) > 0)
+ {
+ *path = static_cast<PSPathElement*>(calloc(nPathCount, sizeof(PSPathElement)));
+ assert(*path != nullptr);
+ memcpy( *path, aPathList.data(), nPathCount * sizeof(PSPathElement) );
+ }
+
+ return nPathCount;
+}
+
+/*- Extracts a string from the name table and allocates memory for it -*/
+
+static char *nameExtract( const sal_uInt8* name, int nTableSize, int n, int dbFlag, sal_Unicode** ucs2result )
+{
+ char *res;
+ const sal_uInt8* ptr = name + GetUInt16(name, 4) + GetUInt16(name + 6, 12 * n + 10);
+ int len = GetUInt16(name+6, 12 * n + 8);
+
+ // sanity check
+ const sal_uInt8* end_table = name+nTableSize;
+ const int available_space = ptr > end_table ? 0 : (end_table - ptr);
+ if( (len <= 0) || len > available_space)
+ {
+ if( ucs2result )
+ *ucs2result = nullptr;
+ return nullptr;
+ }
+
+ if( ucs2result )
+ *ucs2result = nullptr;
+ if (dbFlag) {
+ res = static_cast<char*>(malloc(1 + len/2));
+ assert(res != nullptr);
+ for (int i = 0; i < len/2; i++)
+ res[i] = *(ptr + i * 2 + 1);
+ res[len/2] = 0;
+ if( ucs2result )
+ {
+ *ucs2result = static_cast<sal_Unicode*>(malloc( len+2 ));
+ for (int i = 0; i < len/2; i++ )
+ (*ucs2result)[i] = GetUInt16( ptr, 2*i );
+ (*ucs2result)[len/2] = 0;
+ }
+ } else {
+ res = static_cast<char*>(malloc(1 + len));
+ assert(res != nullptr);
+ memcpy(res, ptr, len);
+ res[len] = 0;
+ }
+
+ return res;
+}
+
+static int findname( const sal_uInt8 *name, sal_uInt16 n, sal_uInt16 platformID,
+ sal_uInt16 encodingID, sal_uInt16 languageID, sal_uInt16 nameID )
+{
+ if (n == 0) return -1;
+
+ int l = 0, r = n-1;
+ sal_uInt32 t1, t2;
+ sal_uInt32 m1, m2;
+
+ m1 = (platformID << 16) | encodingID;
+ m2 = (languageID << 16) | nameID;
+
+ do {
+ const int i = (l + r) >> 1;
+ t1 = GetUInt32(name + 6, i * 12 + 0);
+ t2 = GetUInt32(name + 6, i * 12 + 4);
+
+ if (! ((m1 < t1) || ((m1 == t1) && (m2 < t2)))) l = i + 1;
+ if (! ((m1 > t1) || ((m1 == t1) && (m2 > t2)))) r = i - 1;
+ } while (l <= r);
+
+ if (l - r == 2) {
+ return l - 1;
+ }
+
+ return -1;
+}
+
+/* XXX marlett.ttf uses (3, 0, 1033) instead of (3, 1, 1033) and does not have any Apple tables.
+ * Fix: if (3, 1, 1033) is not found - need to check for (3, 0, 1033)
+ *
+ * /d/fonts/ttzh_tw/Big5/Hanyi/ma6b5p uses (1, 0, 19) for English strings, instead of (1, 0, 0)
+ * and does not have (3, 1, 1033)
+ * Fix: if (1, 0, 0) and (3, 1, 1033) are not found need to look for (1, 0, *) - that will
+ * require a change in algorithm
+ *
+ * /d/fonts/fdltest/Korean/h2drrm has unsorted names and an unknown (to me) Mac LanguageID,
+ * but (1, 0, 1042) strings usable
+ * Fix: change algorithm, and use (1, 0, *) if both standard Mac and MS strings are not found
+ */
+
+static void GetNames(TrueTypeFont *t)
+{
+ sal_uInt32 nTableSize;
+ const sal_uInt8* table = t->table(O_name, nTableSize);
+
+ if (nTableSize < 6)
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.fonts", "O_name table too small.");
+#endif
+ return;
+ }
+
+ sal_uInt16 n = GetUInt16(table, 2);
+
+ /* simple sanity check for name table entry count */
+ const size_t nMinRecordSize = 12;
+ const size_t nSpaceAvailable = nTableSize - 6;
+ const size_t nMaxRecords = nSpaceAvailable/nMinRecordSize;
+ if (n >= nMaxRecords)
+ n = 0;
+
+ int i, r;
+ bool bPSNameOK = true;
+
+ /* PostScript name: preferred Microsoft */
+ t->psname = nullptr;
+ if ((r = findname(table, n, 3, 1, 0x0409, 6)) != -1)
+ t->psname = nameExtract(table, nTableSize, r, 1, nullptr);
+ if ( ! t->psname && (r = findname(table, n, 1, 0, 0, 6)) != -1)
+ t->psname = nameExtract(table, nTableSize, r, 0, nullptr);
+ if ( ! t->psname && (r = findname(table, n, 3, 0, 0x0409, 6)) != -1)
+ {
+ // some symbol fonts like Marlett have a 3,0 name!
+ t->psname = nameExtract(table, nTableSize, r, 1, nullptr);
+ }
+ // for embedded font in Ghostscript PDFs
+ if ( ! t->psname && (r = findname(table, n, 2, 2, 0, 6)) != -1)
+ {
+ t->psname = nameExtract(table, nTableSize, r, 0, nullptr);
+ }
+ if ( ! t->psname )
+ {
+ if (!t->fileName().empty())
+ {
+ const char* pReverse = t->fileName().data() + t->fileName().length();
+ /* take only last token of filename */
+ while (pReverse != t->fileName() && *pReverse != '/') pReverse--;
+ if(*pReverse == '/') pReverse++;
+ t->psname = strdup(pReverse);
+ assert(t->psname != nullptr);
+ for (i=strlen(t->psname) - 1; i > 0; i--)
+ {
+ /*- Remove the suffix -*/
+ if (t->psname[i] == '.' ) {
+ t->psname[i] = 0;
+ break;
+ }
+ }
+ }
+ else
+ t->psname = strdup( "Unknown" );
+ }
+
+ /* Font family and subfamily names: preferred Apple */
+ t->family = nullptr;
+ if ((r = findname(table, n, 0, 0, 0, 1)) != -1)
+ t->family = nameExtract(table, nTableSize, r, 1, &t->ufamily);
+ if ( ! t->family && (r = findname(table, n, 3, 1, 0x0409, 1)) != -1)
+ t->family = nameExtract(table, nTableSize, r, 1, &t->ufamily);
+ if ( ! t->family && (r = findname(table, n, 1, 0, 0, 1)) != -1)
+ t->family = nameExtract(table, nTableSize, r, 0, nullptr);
+ if ( ! t->family && (r = findname(table, n, 3, 1, 0x0411, 1)) != -1)
+ t->family = nameExtract(table, nTableSize, r, 1, &t->ufamily);
+ if ( ! t->family && (r = findname(table, n, 3, 0, 0x0409, 1)) != -1)
+ t->family = nameExtract(table, nTableSize, r, 1, &t->ufamily);
+ if ( ! t->family )
+ {
+ t->family = strdup(t->psname);
+ assert(t->family != nullptr);
+ }
+
+ t->subfamily = nullptr;
+ t->usubfamily = nullptr;
+ if ((r = findname(table, n, 1, 0, 0, 2)) != -1)
+ t->subfamily = nameExtract(table, nTableSize, r, 0, &t->usubfamily);
+ if ( ! t->subfamily && (r = findname(table, n, 3, 1, 0x0409, 2)) != -1)
+ t->subfamily = nameExtract(table, nTableSize, r, 1, &t->usubfamily);
+ if ( ! t->subfamily )
+ {
+ t->subfamily = strdup("");
+ }
+
+ /* #i60349# sanity check psname
+ * psname practically has to be 7bit ASCII and should not contain spaces
+ * there is a class of broken fonts which do not fulfill that at all, so let's try
+ * if the family name is 7bit ASCII and take it instead if so
+ */
+ /* check psname */
+ for( i = 0; t->psname[i] != 0 && bPSNameOK; i++ )
+ if( t->psname[ i ] < 33 || (t->psname[ i ] & 0x80) )
+ bPSNameOK = false;
+ if( bPSNameOK )
+ return;
+
+ /* check if family is a suitable replacement */
+ if( !(t->ufamily && t->family) )
+ return;
+
+ bool bReplace = true;
+
+ for( i = 0; t->ufamily[ i ] != 0 && bReplace; i++ )
+ if( t->ufamily[ i ] < 33 || t->ufamily[ i ] > 127 )
+ bReplace = false;
+ if( bReplace )
+ {
+ free( t->psname );
+ t->psname = strdup( t->family );
+ }
+}
+
+/*- Public functions */
+
+int CountTTCFonts(const char* fname)
+{
+ int nFonts = 0;
+ sal_uInt8 buffer[12];
+ FILE* fd = fopen(fname, "rb");
+ if( fd ) {
+ if (fread(buffer, 1, 12, fd) == 12) {
+ if(GetUInt32(buffer, 0) == T_ttcf )
+ nFonts = GetUInt32(buffer, 8);
+ }
+ fclose(fd);
+ }
+ return nFonts;
+}
+
+#if !defined(_WIN32)
+SFErrCodes OpenTTFontFile(const char* fname, sal_uInt32 facenum, TrueTypeFont** ttf,
+ const FontCharMapRef xCharMap)
+{
+ SFErrCodes ret;
+ int fd = -1;
+ struct stat st;
+
+ if (!fname || !*fname) return SFErrCodes::BadFile;
+
+ *ttf = new TrueTypeFont(fname, xCharMap);
+ if( ! *ttf )
+ return SFErrCodes::Memory;
+
+ if( (*ttf)->fileName().empty() )
+ {
+ ret = SFErrCodes::Memory;
+ goto cleanup;
+ }
+
+ fd = open(fname, O_RDONLY);
+
+ if (fd == -1) {
+ ret = SFErrCodes::BadFile;
+ goto cleanup;
+ }
+
+ if (fstat(fd, &st) == -1) {
+ ret = SFErrCodes::FileIo;
+ goto cleanup;
+ }
+
+ (*ttf)->fsize = st.st_size;
+
+ /* On Mac OS, most likely will happen if a Mac user renames a font file
+ * to be .ttf when it's really a Mac resource-based font.
+ * Size will be 0, but fonts smaller than 4 bytes would be broken anyway.
+ */
+ if ((*ttf)->fsize == 0) {
+ ret = SFErrCodes::BadFile;
+ goto cleanup;
+ }
+
+ if (((*ttf)->ptr = static_cast<sal_uInt8 *>(mmap(nullptr, (*ttf)->fsize, PROT_READ, MAP_SHARED, fd, 0))) == MAP_FAILED) {
+ ret = SFErrCodes::Memory;
+ goto cleanup;
+ }
+
+ ret = (*ttf)->open(facenum);
+
+cleanup:
+ if (fd != -1) close(fd);
+ if (ret != SFErrCodes::Ok)
+ {
+ delete *ttf;
+ *ttf = nullptr;
+ }
+ return ret;
+}
+#endif
+
+SFErrCodes OpenTTFontBuffer(const void* pBuffer, sal_uInt32 nLen, sal_uInt32 facenum, TrueTypeFont** ttf,
+ const FontCharMapRef xCharMap)
+{
+ *ttf = new TrueTypeFont(nullptr, xCharMap);
+ if( *ttf == nullptr )
+ return SFErrCodes::Memory;
+
+ (*ttf)->fsize = nLen;
+ (*ttf)->ptr = const_cast<sal_uInt8 *>(static_cast<sal_uInt8 const *>(pBuffer));
+
+ SFErrCodes ret = (*ttf)->open(facenum);
+ if (ret != SFErrCodes::Ok)
+ {
+ delete *ttf;
+ *ttf = nullptr;
+ }
+ return ret;
+}
+
+namespace {
+
+bool withinBounds(sal_uInt32 tdoffset, sal_uInt32 moreoffset, sal_uInt32 len, sal_uInt32 available)
+{
+ sal_uInt32 result;
+ if (o3tl::checked_add(tdoffset, moreoffset, result))
+ return false;
+ if (o3tl::checked_add(result, len, result))
+ return false;
+ return result <= available;
+}
+}
+
+AbstractTrueTypeFont::AbstractTrueTypeFont(const char* pFileName, const FontCharMapRef xCharMap)
+ : m_nGlyphs(0xFFFFFFFF)
+ , m_nHorzMetrics(0)
+ , m_nVertMetrics(0)
+ , m_nUnitsPerEm(0)
+ , m_xCharMap(xCharMap)
+{
+ if (pFileName)
+ m_sFileName = pFileName;
+}
+
+AbstractTrueTypeFont::~AbstractTrueTypeFont()
+{
+}
+
+TrueTypeFont::TrueTypeFont(const char* pFileName, const FontCharMapRef xCharMap)
+ : AbstractTrueTypeFont(pFileName, xCharMap)
+ , fsize(-1)
+ , ptr(nullptr)
+ , psname(nullptr)
+ , family(nullptr)
+ , ufamily(nullptr)
+ , subfamily(nullptr)
+ , usubfamily(nullptr)
+ , ntables(0)
+{
+}
+
+TrueTypeFont::~TrueTypeFont()
+{
+#if !defined(_WIN32)
+ if (!fileName().empty())
+ munmap(ptr, fsize);
+#endif
+ free(psname);
+ free(family);
+ free(ufamily);
+ free(subfamily);
+ free(usubfamily);
+}
+
+void CloseTTFont(TrueTypeFont* ttf) { delete ttf; }
+
+sal_uInt32 AbstractTrueTypeFont::glyphOffset(sal_uInt32 glyphID) const
+{
+ if (m_aGlyphOffsets.empty()) // the O_CFF and Bitmap cases
+ return 0;
+ return m_aGlyphOffsets[glyphID];
+}
+
+SFErrCodes AbstractTrueTypeFont::indexGlyphData()
+{
+ if (!(hasTable(O_maxp) && hasTable(O_head) && hasTable(O_name) && hasTable(O_cmap)))
+ return SFErrCodes::TtFormat;
+
+ sal_uInt32 table_size;
+ const sal_uInt8* table = this->table(O_maxp, table_size);
+ m_nGlyphs = table_size >= 6 ? GetUInt16(table, 4) : 0;
+
+ table = this->table(O_head, table_size);
+ if (table_size < HEAD_Length)
+ return SFErrCodes::TtFormat;
+
+ m_nUnitsPerEm = GetUInt16(table, HEAD_unitsPerEm_offset);
+ int indexfmt = GetInt16(table, HEAD_indexToLocFormat_offset);
+
+ if (((indexfmt != 0) && (indexfmt != 1)) || (m_nUnitsPerEm <= 0))
+ return SFErrCodes::TtFormat;
+
+ if (hasTable(O_glyf) && (table = this->table(O_loca, table_size))) /* TTF or TTF-OpenType */
+ {
+ int k = (table_size / (indexfmt ? 4 : 2)) - 1;
+ if (k < static_cast<int>(m_nGlyphs)) /* Hack for broken Chinese fonts */
+ m_nGlyphs = k;
+
+ m_aGlyphOffsets.clear();
+ m_aGlyphOffsets.reserve(m_nGlyphs + 1);
+ for (int i = 0; i <= static_cast<int>(m_nGlyphs); ++i)
+ m_aGlyphOffsets.push_back(indexfmt ? GetUInt32(table, i << 2) : static_cast<sal_uInt32>(GetUInt16(table, i << 1)) << 1);
+ }
+ else if (this->table(O_CFF, table_size)) /* PS-OpenType */
+ {
+ int k = (table_size / 2) - 1; /* set a limit here, presumably much lower than the table size, but establishes some sort of physical bound */
+ if (k < static_cast<int>(m_nGlyphs))
+ m_nGlyphs = k;
+
+ m_aGlyphOffsets.clear();
+ /* TODO: implement to get subsetting */
+ }
+ else {
+ // Bitmap font, accept for now.
+ m_aGlyphOffsets.clear();
+ /* TODO: implement to get subsetting */
+ }
+
+ table = this->table(O_hhea, table_size);
+ m_nHorzMetrics = (table && table_size >= 36) ? GetUInt16(table, 34) : 0;
+
+ table = this->table(O_vhea, table_size);
+ m_nVertMetrics = (table && table_size >= 36) ? GetUInt16(table, 34) : 0;
+
+ if (!m_xCharMap.is())
+ {
+ CmapResult aCmapResult;
+ table = this->table(O_cmap, table_size);
+ if (!ParseCMAP(table, table_size, aCmapResult))
+ return SFErrCodes::TtFormat;
+ m_xCharMap = new FontCharMap(aCmapResult);
+ }
+
+ return SFErrCodes::Ok;
+}
+
+SFErrCodes TrueTypeFont::open(sal_uInt32 facenum)
+{
+ if (fsize < 4)
+ return SFErrCodes::TtFormat;
+
+ int i;
+ sal_uInt32 length, tag;
+ sal_uInt32 tdoffset = 0; /* offset to TableDirectory in a TTC file. For TTF files is 0 */
+
+ sal_uInt32 TTCTag = GetInt32(ptr, 0);
+
+ if ((TTCTag == 0x00010000) || (TTCTag == T_true)) {
+ tdoffset = 0;
+ } else if (TTCTag == T_otto) { /* PS-OpenType font */
+ tdoffset = 0;
+ } else if (TTCTag == T_ttcf) { /* TrueType collection */
+ if (!withinBounds(12, 4 * facenum, sizeof(sal_uInt32), fsize))
+ return SFErrCodes::FontNo;
+ sal_uInt32 Version = GetUInt32(ptr, 4);
+ if (Version != 0x00010000 && Version != 0x00020000) {
+ return SFErrCodes::TtFormat;
+ }
+ if (facenum >= GetUInt32(ptr, 8))
+ return SFErrCodes::FontNo;
+ tdoffset = GetUInt32(ptr, 12 + 4 * facenum);
+ } else {
+ return SFErrCodes::TtFormat;
+ }
+
+ if (withinBounds(tdoffset, 0, 4 + sizeof(sal_uInt16), fsize))
+ ntables = GetUInt16(ptr + tdoffset, 4);
+
+ if (ntables >= 128 || ntables == 0)
+ return SFErrCodes::TtFormat;
+
+ /* parse the tables */
+ for (i = 0; i < static_cast<int>(ntables); i++)
+ {
+ int nIndex;
+ const sal_uInt32 nStart = tdoffset + 12;
+ const sal_uInt32 nOffset = 16 * i;
+ if (withinBounds(nStart, nOffset, sizeof(sal_uInt32), fsize))
+ tag = GetUInt32(ptr + nStart, nOffset);
+ else
+ tag = static_cast<sal_uInt32>(-1);
+ switch( tag ) {
+ case T_maxp: nIndex = O_maxp; break;
+ case T_glyf: nIndex = O_glyf; break;
+ case T_head: nIndex = O_head; break;
+ case T_loca: nIndex = O_loca; break;
+ case T_name: nIndex = O_name; break;
+ case T_hhea: nIndex = O_hhea; break;
+ case T_hmtx: nIndex = O_hmtx; break;
+ case T_cmap: nIndex = O_cmap; break;
+ case T_vhea: nIndex = O_vhea; break;
+ case T_vmtx: nIndex = O_vmtx; break;
+ case T_OS2 : nIndex = O_OS2; break;
+ case T_post: nIndex = O_post; break;
+ case T_cvt : nIndex = O_cvt; break;
+ case T_prep: nIndex = O_prep; break;
+ case T_fpgm: nIndex = O_fpgm; break;
+ case T_gsub: nIndex = O_gsub; break;
+ case T_CFF: nIndex = O_CFF; break;
+ default: nIndex = -1; break;
+ }
+
+ if ((nIndex >= 0) && withinBounds(nStart, nOffset, 12 + sizeof(sal_uInt32), fsize))
+ {
+ sal_uInt32 nTableOffset = GetUInt32(ptr + nStart, nOffset + 8);
+ length = GetUInt32(ptr + nStart, nOffset + 12);
+ m_aTableList[nIndex].pData = ptr + nTableOffset;
+ m_aTableList[nIndex].nSize = length;
+ }
+ }
+
+ /* Fixup offsets when only a TTC extract was provided */
+ if (facenum == sal_uInt32(~0))
+ {
+ sal_uInt8* pHead = const_cast<sal_uInt8*>(m_aTableList[O_head].pData);
+ if (!pHead)
+ return SFErrCodes::TtFormat;
+
+ /* limit Head candidate to TTC extract's limits */
+ if (pHead > ptr + (fsize - 54))
+ pHead = ptr + (fsize - 54);
+
+ /* TODO: find better method than searching head table's magic */
+ sal_uInt8* p = nullptr;
+ for (p = pHead + 12; p > ptr; --p)
+ {
+ if( p[0]==0x5F && p[1]==0x0F && p[2]==0x3C && p[3]==0xF5 ) {
+ int nDelta = (pHead + 12) - p;
+ if( nDelta )
+ for( int j = 0; j < NUM_TAGS; ++j )
+ if (hasTable(j))
+ m_aTableList[j].pData -= nDelta;
+ break;
+ }
+ }
+ if (p <= ptr)
+ return SFErrCodes::TtFormat;
+ }
+
+ /* Check the table offsets after TTC correction */
+ for (i=0; i<NUM_TAGS; i++) {
+ /* sanity check: table must lay completely within the file
+ * at this point one could check the checksum of all contained
+ * tables, but this would be quite time intensive.
+ * Try to fix tables, so we can cope with minor problems.
+ */
+
+ if (m_aTableList[i].pData < ptr)
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN_IF(m_aTableList[i].pData, "vcl.fonts", "font file " << fileName()
+ << " has bad table offset "
+ << (sal_uInt8*)m_aTableList[i].pData - ptr
+ << "d (tagnum=" << i << ").");
+#endif
+ m_aTableList[i].nSize = 0;
+ m_aTableList[i].pData = nullptr;
+ }
+ else if (const_cast<sal_uInt8*>(m_aTableList[i].pData) + m_aTableList[i].nSize > ptr + fsize)
+ {
+ sal_PtrDiff nMaxLen = (ptr + fsize) - m_aTableList[i].pData;
+ if( nMaxLen < 0 )
+ nMaxLen = 0;
+ m_aTableList[i].nSize = nMaxLen;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.fonts", "font file " << fileName()
+ << " has too big table (tagnum=" << i << ").");
+#endif
+ }
+ }
+
+ /* At this point TrueTypeFont is constructed, now need to verify the font format
+ and read the basic font properties */
+
+ SFErrCodes ret = indexGlyphData();
+ if (ret != SFErrCodes::Ok)
+ return ret;
+
+ GetNames(this);
+
+ return SFErrCodes::Ok;
+}
+
+int GetTTGlyphPoints(AbstractTrueTypeFont *ttf, sal_uInt32 glyphID, ControlPoint **pointArray)
+{
+ return GetTTGlyphOutline(ttf, glyphID, pointArray, nullptr, nullptr);
+}
+
+int GetTTGlyphComponents(AbstractTrueTypeFont *ttf, sal_uInt32 glyphID, std::vector< sal_uInt32 >& glyphlist)
+{
+ int n = 1;
+
+ if (glyphID >= ttf->glyphCount())
+ return 0;
+
+ sal_uInt32 glyflength;
+ const sal_uInt8* glyf = ttf->table(O_glyf, glyflength);
+
+ sal_uInt32 nNextOffset = ttf->glyphOffset(glyphID + 1);
+ if (nNextOffset > glyflength)
+ return 0;
+
+ sal_uInt32 nOffset = ttf->glyphOffset(glyphID);
+ if (nOffset > nNextOffset)
+ return 0;
+
+ const sal_uInt8* ptr = glyf + nOffset;
+ const sal_uInt8* nptr = glyf + nNextOffset;
+ if (nptr < ptr)
+ return 0;
+
+ if (std::find(glyphlist.begin(), glyphlist.end(), glyphID) != glyphlist.end())
+ {
+ SAL_WARN("vcl.fonts", "Endless loop found in a compound glyph.");
+ return 0;
+ }
+
+ glyphlist.push_back( glyphID );
+
+ // Empty glyph.
+ if (nptr == ptr)
+ return n;
+
+ sal_uInt32 nRemainingData = glyflength - nOffset;
+
+ if (nRemainingData >= 10 && GetInt16(ptr, 0) == -1) {
+ sal_uInt16 flags, index;
+ ptr += 10;
+ nRemainingData -= 10;
+ do {
+ if (nRemainingData < 4)
+ {
+ SAL_WARN("vcl.fonts", "short read");
+ break;
+ }
+ flags = GetUInt16(ptr, 0);
+ index = GetUInt16(ptr, 2);
+
+ ptr += 4;
+ nRemainingData -= 4;
+ n += GetTTGlyphComponents(ttf, index, glyphlist);
+
+ sal_uInt32 nAdvance;
+ if (flags & ARG_1_AND_2_ARE_WORDS) {
+ nAdvance = 4;
+ } else {
+ nAdvance = 2;
+ }
+
+ if (flags & WE_HAVE_A_SCALE) {
+ nAdvance += 2;
+ } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
+ nAdvance += 4;
+ } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
+ nAdvance += 8;
+ }
+ if (nRemainingData < nAdvance)
+ {
+ SAL_WARN("vcl.fonts", "short read");
+ break;
+ }
+ ptr += nAdvance;
+ nRemainingData -= nAdvance;
+ } while (flags & MORE_COMPONENTS);
+ }
+
+ return n;
+}
+
+SFErrCodes CreateT3FromTTGlyphs(TrueTypeFont *ttf, FILE *outf, const char *fname,
+ sal_uInt16 const *glyphArray, sal_uInt8 *encoding, int nGlyphs,
+ int wmode)
+{
+ ControlPoint *pa;
+ PSPathElement *path;
+ int i, j, n;
+ sal_uInt32 nSize;
+ const sal_uInt8* table = ttf->table(O_head, nSize);
+ TTGlyphMetrics metrics;
+ int UPEm = ttf->unitsPerEm();
+
+ const char * const h01 = "%%!PS-AdobeFont-%d.%d-%d.%d\n";
+ const char * const h02 = "%% Creator: %s %s %s\n";
+ const char * const h09 = "%% Original font name: %s\n";
+
+ const char * const h10 =
+ "30 dict begin\n"
+ "/PaintType 0 def\n"
+ "/FontType 3 def\n"
+ "/StrokeWidth 0 def\n";
+
+ const char * const h11 = "/FontName (%s) cvn def\n";
+
+ /*
+ const char *h12 = "%/UniqueID %d def\n";
+ */
+ const char * const h13 = "/FontMatrix [.001 0 0 .001 0 0] def\n";
+ const char * const h14 = "/FontBBox [%d %d %d %d] def\n";
+
+ const char * const h15=
+ "/Encoding 256 array def\n"
+ " 0 1 255 {Encoding exch /.notdef put} for\n";
+
+ const char * const h16 = " Encoding %d /glyph%d put\n";
+ const char * const h17 = "/XUID [103 0 0 16#%08" SAL_PRIXUINT32 " %d 16#%08" SAL_PRIXUINT32 " 16#%08" SAL_PRIXUINT32 "] def\n";
+
+ const char * const h30 = "/CharProcs %d dict def\n";
+ const char * const h31 = " CharProcs begin\n";
+ const char * const h32 = " /.notdef {} def\n";
+ const char * const h33 = " /glyph%d {\n";
+ const char * const h34 = " } bind def\n";
+ const char * const h35 = " end\n";
+
+ const char * const h40 =
+ "/BuildGlyph {\n"
+ " exch /CharProcs get exch\n"
+ " 2 copy known not\n"
+ " {pop /.notdef} if\n"
+ " get exec\n"
+ "} bind def\n"
+ "/BuildChar {\n"
+ " 1 index /Encoding get exch get\n"
+ " 1 index /BuildGlyph get exec\n"
+ "} bind def\n"
+ "currentdict end\n";
+
+ const char * const h41 = "(%s) cvn exch definefont pop\n";
+
+ if ((nGlyphs <= 0) || (nGlyphs > 256)) return SFErrCodes::GlyphNum;
+ if (!glyphArray) return SFErrCodes::BadArg;
+ if (!fname) fname = ttf->psname;
+
+ fprintf(outf, h01, GetInt16(table, 0), GetUInt16(table, 2), GetInt16(table, 4), GetUInt16(table, 6));
+ fprintf(outf, h02, modname, modver, modextra);
+ fprintf(outf, h09, ttf->psname);
+
+ fprintf(outf, "%s", h10);
+ fprintf(outf, h11, fname);
+/* fprintf(outf, h12, 4000000); */
+
+ /* XUID generation:
+ * 103 0 0 C1 C2 C3 C4
+ * C1 - CRC-32 of the entire source TrueType font
+ * C2 - number of glyphs in the subset
+ * C3 - CRC-32 of the glyph array
+ * C4 - CRC-32 of the encoding array
+ *
+ * All CRC-32 numbers are presented as hexadecimal numbers
+ */
+
+ fprintf(outf, h17, rtl_crc32(0, ttf->ptr, ttf->fsize), nGlyphs, rtl_crc32(0, glyphArray, nGlyphs * 2), rtl_crc32(0, encoding, nGlyphs));
+ fprintf(outf, "%s", h13);
+ fprintf(outf, h14, XUnits(UPEm, GetInt16(table, 36)), XUnits(UPEm, GetInt16(table, 38)), XUnits(UPEm, GetInt16(table, 40)), XUnits(UPEm, GetInt16(table, 42)));
+ fprintf(outf, "%s", h15);
+
+ for (i = 0; i < nGlyphs; i++) {
+ fprintf(outf, h16, encoding[i], i);
+ }
+
+ fprintf(outf, h30, nGlyphs+1);
+ fprintf(outf, "%s", h31);
+ fprintf(outf, "%s", h32);
+
+ for (i = 0; i < nGlyphs; i++) {
+ fprintf(outf, h33, i);
+ int r = GetTTGlyphOutline(ttf, glyphArray[i] < ttf->glyphCount() ? glyphArray[i] : 0, &pa, &metrics, nullptr);
+
+ if (r > 0) {
+ n = BSplineToPSPath(pa, r, &path);
+ } else {
+ n = 0; /* glyph might have zero contours but valid metrics ??? */
+ path = nullptr;
+ if (r < 0) { /* glyph is not present in the font - pa array was not allocated, so no need to free it */
+ continue;
+ }
+ }
+ fprintf(outf, "\t%d %d %d %d %d %d setcachedevice\n",
+ wmode == 0 ? XUnits(UPEm, metrics.aw) : 0,
+ wmode == 0 ? 0 : -XUnits(UPEm, metrics.ah),
+ XUnits(UPEm, metrics.xMin),
+ XUnits(UPEm, metrics.yMin),
+ XUnits(UPEm, metrics.xMax),
+ XUnits(UPEm, metrics.yMax));
+
+ for (j = 0; j < n; j++)
+ {
+ switch (path[j].type)
+ {
+ case PS_MOVETO:
+ fprintf(outf, "\t%d %d moveto\n", XUnits(UPEm, path[j].x1), XUnits(UPEm, path[j].y1));
+ break;
+
+ case PS_LINETO:
+ fprintf(outf, "\t%d %d lineto\n", XUnits(UPEm, path[j].x1), XUnits(UPEm, path[j].y1));
+ break;
+
+ case PS_CURVETO:
+ fprintf(outf, "\t%d %d %d %d %d %d curveto\n", XUnits(UPEm, path[j].x1), XUnits(UPEm, path[j].y1), XUnits(UPEm, path[j].x2), XUnits(UPEm, path[j].y2), XUnits(UPEm, path[j].x3), XUnits(UPEm, path[j].y3));
+ break;
+
+ case PS_CLOSEPATH:
+ fprintf(outf, "\tclosepath\n");
+ break;
+ case PS_NOOP:
+ break;
+ }
+ }
+ if (n > 0) fprintf(outf, "\tfill\n"); /* if glyph is not a whitespace character */
+
+ fprintf(outf, "%s", h34);
+
+ free(pa);
+ free(path);
+ }
+ fprintf(outf, "%s", h35);
+
+ fprintf(outf, "%s", h40);
+ fprintf(outf, h41, fname);
+
+ return SFErrCodes::Ok;
+}
+
+SFErrCodes CreateTTFromTTGlyphs(AbstractTrueTypeFont *ttf,
+ const char *fname,
+ sal_uInt16 const *glyphArray,
+ sal_uInt8 const *encoding,
+ int nGlyphs)
+{
+ TrueTypeCreator *ttcr;
+ TrueTypeTable *head=nullptr, *hhea=nullptr, *maxp=nullptr, *cvt=nullptr, *prep=nullptr, *glyf=nullptr, *fpgm=nullptr, *cmap=nullptr, *name=nullptr, *post = nullptr, *os2 = nullptr;
+ int i;
+ SFErrCodes res;
+
+ TrueTypeCreatorNewEmpty(T_true, &ttcr);
+
+ /** name **/
+
+ NameRecord *names = nullptr;
+ int n = GetTTNameRecords(ttf, &names);
+ name = TrueTypeTableNew_name(n, names);
+ DisposeNameRecords(names, n);
+
+ /** maxp **/
+ sal_uInt32 nTableSize;
+ const sal_uInt8* p = ttf->table(O_maxp, nTableSize);
+ maxp = TrueTypeTableNew_maxp(p, nTableSize);
+
+ /** hhea **/
+ p = ttf->table(O_hhea, nTableSize);
+ if (p && nTableSize >= HHEA_caretSlopeRun_offset + 2)
+ hhea = TrueTypeTableNew_hhea(GetInt16(p, HHEA_ascender_offset), GetInt16(p, HHEA_descender_offset), GetInt16(p, HHEA_lineGap_offset), GetInt16(p, HHEA_caretSlopeRise_offset), GetInt16(p, HHEA_caretSlopeRun_offset));
+ else
+ hhea = TrueTypeTableNew_hhea(0, 0, 0, 0, 0);
+
+ /** head **/
+
+ p = ttf->table(O_head, nTableSize);
+ assert(p != nullptr);
+ head = TrueTypeTableNew_head(GetInt32(p, HEAD_fontRevision_offset),
+ GetUInt16(p, HEAD_flags_offset),
+ GetUInt16(p, HEAD_unitsPerEm_offset),
+ p+HEAD_created_offset,
+ GetUInt16(p, HEAD_macStyle_offset),
+ GetUInt16(p, HEAD_lowestRecPPEM_offset),
+ GetInt16(p, HEAD_fontDirectionHint_offset));
+
+ /** glyf **/
+
+ glyf = TrueTypeTableNew_glyf();
+ sal_uInt32* gID = static_cast<sal_uInt32*>(scalloc(nGlyphs, sizeof(sal_uInt32)));
+
+ for (i = 0; i < nGlyphs; i++) {
+ gID[i] = glyfAdd(glyf, GetTTRawGlyphData(ttf, glyphArray[i]), ttf);
+ }
+
+ /** cmap **/
+ cmap = TrueTypeTableNew_cmap();
+
+ for (i=0; i < nGlyphs; i++) {
+ cmapAdd(cmap, 0x010000, encoding[i], gID[i]);
+ }
+
+ /** cvt **/
+ if ((p = ttf->table(O_cvt, nTableSize)) != nullptr)
+ cvt = TrueTypeTableNew(T_cvt, nTableSize, p);
+
+ /** prep **/
+ if ((p = ttf->table(O_prep, nTableSize)) != nullptr)
+ prep = TrueTypeTableNew(T_prep, nTableSize, p);
+
+ /** fpgm **/
+ if ((p = ttf->table(O_fpgm, nTableSize)) != nullptr)
+ fpgm = TrueTypeTableNew(T_fpgm, nTableSize, p);
+
+ /** post **/
+ if ((p = ttf->table(O_post, nTableSize)) != nullptr)
+ {
+ sal_Int32 nItalic = (POST_italicAngle_offset + 4 < nTableSize) ?
+ GetInt32(p, POST_italicAngle_offset) : 0;
+ sal_Int16 nPosition = (POST_underlinePosition_offset + 2 < nTableSize) ?
+ GetInt16(p, POST_underlinePosition_offset) : 0;
+ sal_Int16 nThickness = (POST_underlineThickness_offset + 2 < nTableSize) ?
+ GetInt16(p, POST_underlineThickness_offset) : 0;
+ sal_uInt32 nFixedPitch = (POST_isFixedPitch_offset + 4 < nTableSize) ?
+ GetUInt32(p, POST_isFixedPitch_offset) : 0;
+
+ post = TrueTypeTableNew_post(0x00030000,
+ nItalic, nPosition,
+ nThickness, nFixedPitch);
+ }
+ else
+ post = TrueTypeTableNew_post(0x00030000, 0, 0, 0, 0);
+
+ AddTable(ttcr, name); AddTable(ttcr, maxp); AddTable(ttcr, hhea);
+ AddTable(ttcr, head); AddTable(ttcr, glyf); AddTable(ttcr, cmap);
+ AddTable(ttcr, cvt ); AddTable(ttcr, prep); AddTable(ttcr, fpgm);
+ AddTable(ttcr, post); AddTable(ttcr, os2);
+
+ res = StreamToFile(ttcr, fname);
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN_IF(res != SFErrCodes::Ok, "vcl.fonts", "StreamToFile: error code: "
+ << (int) res << ".");
+#endif
+
+ TrueTypeCreatorDispose(ttcr);
+ free(gID);
+
+ return res;
+}
+
+static GlyphOffsets *GlyphOffsetsNew(sal_uInt8 *sfntP, sal_uInt32 sfntLen)
+{
+ GlyphOffsets* res = static_cast<GlyphOffsets*>(smalloc(sizeof(GlyphOffsets)));
+ sal_uInt8 *loca = nullptr;
+ sal_uInt16 numTables = GetUInt16(sfntP, 4);
+ sal_uInt32 locaLen = 0;
+ sal_Int16 indexToLocFormat = 0;
+
+ sal_uInt32 nMaxPossibleTables = sfntLen / (3*sizeof(sal_uInt32)); /*the three GetUInt32 calls*/
+ if (numTables > nMaxPossibleTables)
+ {
+ SAL_WARN( "vcl.fonts", "GlyphOffsetsNew claimed to have "
+ << numTables << " tables, but that's impossibly large");
+ numTables = nMaxPossibleTables;
+ }
+
+ for (sal_uInt16 i = 0; i < numTables; i++) {
+ sal_uInt32 nLargestFixedOffsetPos = 12 + 16 * i + 12;
+ sal_uInt32 nMinSize = nLargestFixedOffsetPos + sizeof(sal_uInt32);
+ if (nMinSize > sfntLen)
+ {
+ SAL_WARN( "vcl.fonts", "GlyphOffsetsNew claimed to have "
+ << numTables << " tables, but only space for " << i);
+ break;
+ }
+
+ sal_uInt32 tag = GetUInt32(sfntP, 12 + 16 * i);
+ sal_uInt32 off = GetUInt32(sfntP, 12 + 16 * i + 8);
+ sal_uInt32 len = GetUInt32(sfntP, nLargestFixedOffsetPos);
+
+ if (tag == T_loca) {
+ loca = sfntP + off;
+ locaLen = len;
+ } else if (tag == T_head) {
+ indexToLocFormat = GetInt16(sfntP + off, 50);
+ }
+ }
+
+ res->nGlyphs = locaLen / ((indexToLocFormat == 1) ? 4 : 2);
+ assert(res->nGlyphs != 0);
+ res->offs = static_cast<sal_uInt32*>(scalloc(res->nGlyphs, sizeof(sal_uInt32)));
+
+ for (sal_uInt32 i = 0; i < res->nGlyphs; i++) {
+ if (indexToLocFormat == 1) {
+ res->offs[i] = GetUInt32(loca, i * 4);
+ } else {
+ res->offs[i] = GetUInt16(loca, i * 2) << 1;
+ }
+ }
+ return res;
+}
+
+static void GlyphOffsetsDispose(GlyphOffsets *_this)
+{
+ if (_this) {
+ free(_this->offs);
+ free(_this);
+ }
+}
+
+static void DumpSfnts(FILE *outf, sal_uInt8 *sfntP, sal_uInt32 sfntLen)
+{
+ if (sfntLen < 12)
+ {
+ SAL_WARN( "vcl.fonts", "DumpSfnts sfntLen is too short: "
+ << sfntLen << " legal min is: " << 12);
+ return;
+ }
+
+ const sal_uInt32 nSpaceForTables = sfntLen - 12;
+ const sal_uInt32 nTableSize = 16;
+ const sal_uInt32 nMaxPossibleTables = nSpaceForTables/nTableSize;
+
+ HexFmt *h = HexFmtNew(outf);
+ sal_uInt16 i, numTables = GetUInt16(sfntP, 4);
+ GlyphOffsets *go = GlyphOffsetsNew(sfntP, sfntLen);
+ sal_uInt8 const pad[] = {0,0,0,0}; /* zeroes */
+
+ if (numTables > nMaxPossibleTables)
+ {
+ SAL_WARN( "vcl.fonts", "DumpSfnts claimed to have "
+ << numTables << " tables, but only space for " << nMaxPossibleTables);
+ numTables = nMaxPossibleTables;
+ }
+
+ assert(numTables <= 9); /* Type42 has 9 required tables */
+
+ sal_uInt32* offs = static_cast<sal_uInt32*>(scalloc(numTables, sizeof(sal_uInt32)));
+
+ fputs("/sfnts [", outf);
+ HexFmtOpenString(h);
+ HexFmtBlockWrite(h, sfntP, 12); /* stream out the Offset Table */
+ HexFmtBlockWrite(h, sfntP+12, 16 * numTables); /* stream out the Table Directory */
+
+ for (i=0; i<numTables; i++)
+ {
+ sal_uInt32 nLargestFixedOffsetPos = 12 + 16 * i + 12;
+ sal_uInt32 nMinSize = nLargestFixedOffsetPos + sizeof(sal_uInt32);
+ if (nMinSize > sfntLen)
+ {
+ SAL_WARN( "vcl.fonts", "DumpSfnts claimed to have "
+ << numTables << " tables, but only space for " << i);
+ break;
+ }
+
+ sal_uInt32 tag = GetUInt32(sfntP, 12 + 16 * i);
+ sal_uInt32 off = GetUInt32(sfntP, 12 + 16 * i + 8);
+ if (off > sfntLen)
+ {
+ SAL_WARN( "vcl.fonts", "DumpSfnts claims offset of "
+ << off << " but max possible is " << sfntLen);
+ break;
+ }
+ sal_uInt8 *pRecordStart = sfntP + off;
+ sal_uInt32 len = GetUInt32(sfntP, nLargestFixedOffsetPos);
+ sal_uInt32 nMaxLenPossible = sfntLen - off;
+ if (len > nMaxLenPossible)
+ {
+ SAL_WARN( "vcl.fonts", "DumpSfnts claims len of "
+ << len << " but only space for " << nMaxLenPossible);
+ break;
+ }
+
+ if (tag != T_glyf)
+ {
+ HexFmtBlockWrite(h, pRecordStart, len);
+ }
+ else
+ {
+ sal_uInt8 *glyf = pRecordStart;
+ sal_uInt8 *eof = pRecordStart + nMaxLenPossible;
+ for (sal_uInt32 j = 0; j < go->nGlyphs - 1; j++)
+ {
+ sal_uInt32 nStartOffset = go->offs[j];
+ sal_uInt8 *pSubRecordStart = glyf + nStartOffset;
+ if (pSubRecordStart > eof)
+ {
+ SAL_WARN( "vcl.fonts", "DumpSfnts start glyf claims offset of "
+ << pSubRecordStart - sfntP << " but max possible is " << eof - sfntP);
+ break;
+ }
+
+ sal_uInt32 nEndOffset = go->offs[j + 1];
+ sal_uInt8 *pSubRecordEnd = glyf + nEndOffset;
+ if (pSubRecordEnd > eof)
+ {
+ SAL_WARN( "vcl.fonts", "DumpSfnts end glyf offset of "
+ << pSubRecordEnd - sfntP << " but max possible is " << eof - sfntP);
+ break;
+ }
+
+ sal_uInt32 l = pSubRecordEnd - pSubRecordStart;
+ HexFmtBlockWrite(h, pSubRecordStart, l);
+ }
+ }
+ HexFmtBlockWrite(h, pad, (4 - (len & 3)) & 3);
+ }
+ HexFmtCloseString(h);
+ fputs("] def\n", outf);
+ GlyphOffsetsDispose(go);
+ HexFmtDispose(h);
+ free(offs);
+}
+
+SFErrCodes CreateT42FromTTGlyphs(TrueTypeFont *ttf,
+ FILE *outf,
+ const char *psname,
+ sal_uInt16 const *glyphArray,
+ sal_uInt8 *encoding,
+ int nGlyphs)
+{
+ TrueTypeCreator *ttcr;
+ TrueTypeTable *head=nullptr, *hhea=nullptr, *maxp=nullptr, *cvt=nullptr, *prep=nullptr, *glyf=nullptr, *fpgm=nullptr;
+ int i;
+ SFErrCodes res;
+
+ sal_uInt16 ver;
+ sal_Int32 rev;
+
+ sal_uInt8 *sfntP;
+ sal_uInt32 sfntLen;
+ int UPEm = ttf->unitsPerEm();
+
+ if (nGlyphs >= 256) return SFErrCodes::GlyphNum;
+
+ assert(psname != nullptr);
+
+ TrueTypeCreatorNewEmpty(T_true, &ttcr);
+
+ /* head */
+ sal_uInt32 nTableSize;
+ const sal_uInt8* p = ttf->table(O_head, nTableSize);
+ const sal_uInt8* headP = p;
+ assert(p != nullptr);
+ head = TrueTypeTableNew_head(GetInt32(p, HEAD_fontRevision_offset), GetUInt16(p, HEAD_flags_offset), GetUInt16(p, HEAD_unitsPerEm_offset), p+HEAD_created_offset, GetUInt16(p, HEAD_macStyle_offset), GetUInt16(p, HEAD_lowestRecPPEM_offset), GetInt16(p, HEAD_fontDirectionHint_offset));
+ ver = GetUInt16(p, HEAD_majorVersion_offset);
+ rev = GetInt32(p, HEAD_fontRevision_offset);
+
+ /** hhea **/
+ p = ttf->table(O_hhea, nTableSize);
+ if (p)
+ hhea = TrueTypeTableNew_hhea(GetInt16(p, HHEA_ascender_offset), GetInt16(p, HHEA_descender_offset), GetInt16(p, HHEA_lineGap_offset), GetInt16(p, HHEA_caretSlopeRise_offset), GetInt16(p, HHEA_caretSlopeRun_offset));
+ else
+ hhea = TrueTypeTableNew_hhea(0, 0, 0, 0, 0);
+
+ /** maxp **/
+ p = ttf->table(O_maxp, nTableSize);
+ maxp = TrueTypeTableNew_maxp(p, nTableSize);
+
+ /** cvt **/
+ if ((p = ttf->table(O_cvt, nTableSize)) != nullptr)
+ cvt = TrueTypeTableNew(T_cvt, nTableSize, p);
+
+ /** prep **/
+ if ((p = ttf->table(O_prep, nTableSize)) != nullptr)
+ prep = TrueTypeTableNew(T_prep, nTableSize, p);
+
+ /** fpgm **/
+ if ((p = ttf->table(O_fpgm, nTableSize)) != nullptr)
+ fpgm = TrueTypeTableNew(T_fpgm, nTableSize, p);
+
+ /** glyf **/
+ glyf = TrueTypeTableNew_glyf();
+ sal_uInt16* gID = static_cast<sal_uInt16*>(scalloc(nGlyphs, sizeof(sal_uInt32)));
+
+ for (i = 0; i < nGlyphs; i++) {
+ gID[i] = static_cast<sal_uInt16>(glyfAdd(glyf, GetTTRawGlyphData(ttf, glyphArray[i]), ttf));
+ }
+
+ AddTable(ttcr, head); AddTable(ttcr, hhea); AddTable(ttcr, maxp); AddTable(ttcr, cvt);
+ AddTable(ttcr, prep); AddTable(ttcr, glyf); AddTable(ttcr, fpgm);
+
+ if ((res = StreamToMemory(ttcr, &sfntP, &sfntLen)) != SFErrCodes::Ok) {
+ TrueTypeCreatorDispose(ttcr);
+ free(gID);
+ return res;
+ }
+
+ fprintf(outf, "%%!PS-TrueTypeFont-%d.%d-%d.%d\n", static_cast<int>(ver), static_cast<int>(ver & 0xFF), static_cast<int>(rev>>16), static_cast<int>(rev & 0xFFFF));
+ fprintf(outf, "%%%%Creator: %s %s %s\n", modname, modver, modextra);
+ fprintf(outf, "%%- Font subset generated from a source font file: '%s'\n", ttf->fileName().data());
+ fprintf(outf, "%%- Original font name: %s\n", ttf->psname);
+ fprintf(outf, "%%- Original font family: %s\n", ttf->family);
+ fprintf(outf, "%%- Original font sub-family: %s\n", ttf->subfamily);
+ fprintf(outf, "11 dict begin\n");
+ fprintf(outf, "/FontName (%s) cvn def\n", psname);
+ fprintf(outf, "/PaintType 0 def\n");
+ fprintf(outf, "/FontMatrix [1 0 0 1 0 0] def\n");
+ fprintf(outf, "/FontBBox [%d %d %d %d] def\n", XUnits(UPEm, GetInt16(headP, HEAD_xMin_offset)), XUnits(UPEm, GetInt16(headP, HEAD_yMin_offset)), XUnits(UPEm, GetInt16(headP, HEAD_xMax_offset)), XUnits(UPEm, GetInt16(headP, HEAD_yMax_offset)));
+ fprintf(outf, "/FontType 42 def\n");
+ fprintf(outf, "/Encoding 256 array def\n");
+ fprintf(outf, " 0 1 255 {Encoding exch /.notdef put} for\n");
+
+ for (i = 1; i<nGlyphs; i++) {
+ fprintf(outf, "Encoding %d /glyph%u put\n", encoding[i], gID[i]);
+ }
+ fprintf(outf, "/XUID [103 0 1 16#%08X %u 16#%08X 16#%08X] def\n", static_cast<unsigned int>(rtl_crc32(0, ttf->ptr, ttf->fsize)), static_cast<unsigned int>(nGlyphs), static_cast<unsigned int>(rtl_crc32(0, glyphArray, nGlyphs * 2)), static_cast<unsigned int>(rtl_crc32(0, encoding, nGlyphs)));
+
+ DumpSfnts(outf, sfntP, sfntLen);
+
+ /* dump charstrings */
+ fprintf(outf, "/CharStrings %d dict dup begin\n", nGlyphs);
+ fprintf(outf, "/.notdef 0 def\n");
+ for (i = 1; i < static_cast<int>(glyfCount(glyf)); i++) {
+ fprintf(outf,"/glyph%d %d def\n", i, i);
+ }
+ fprintf(outf, "end readonly def\n");
+
+ fprintf(outf, "FontName currentdict end definefont pop\n");
+ TrueTypeCreatorDispose(ttcr);
+ free(gID);
+ free(sfntP);
+ return SFErrCodes::Ok;
+}
+
+std::unique_ptr<sal_uInt16[]> GetTTSimpleGlyphMetrics(AbstractTrueTypeFont const *ttf, const sal_uInt16 *glyphArray, int nGlyphs, bool vertical)
+{
+ const sal_uInt8* pTable;
+ sal_uInt32 n;
+ sal_uInt32 nTableSize;
+
+ if (!vertical)
+ {
+ n = ttf->horzMetricCount();
+ pTable = ttf->table(O_hmtx, nTableSize);
+ }
+ else
+ {
+ n = ttf->vertMetricCount();
+ pTable = ttf->table(O_vmtx, nTableSize);
+ }
+
+ if (!nGlyphs || !glyphArray) return nullptr; /* invalid parameters */
+ if (!n || !pTable) return nullptr; /* the font does not contain the requested metrics */
+
+ std::unique_ptr<sal_uInt16[]> res(new sal_uInt16[nGlyphs]);
+
+ const int UPEm = ttf->unitsPerEm();
+ for( int i = 0; i < nGlyphs; ++i) {
+ sal_uInt32 nAdvOffset;
+ sal_uInt16 glyphID = glyphArray[i];
+
+ if (glyphID < n) {
+ nAdvOffset = 4 * glyphID;
+ } else {
+ nAdvOffset = 4 * (n - 1);
+ }
+
+ if (nAdvOffset >= nTableSize || UPEm == 0)
+ res[i] = 0; /* better than a crash for buggy fonts */
+ else
+ res[i] = static_cast<sal_uInt16>(
+ XUnits( UPEm, GetUInt16( pTable, nAdvOffset) ) );
+ }
+
+ return res;
+}
+
+// TODO, clean up table parsing and re-use it elsewhere in this file.
+void GetTTFontMetrics(const uint8_t *pHhea, size_t nHhea,
+ const uint8_t *pOs2, size_t nOs2,
+ TTGlobalFontInfo *info)
+{
+ /* There are 3 different versions of OS/2 table: original (68 bytes long),
+ * Microsoft old (78 bytes long) and Microsoft new (86 bytes long,)
+ * Apple's documentation recommends looking at the table length.
+ */
+ if (nOs2 >= OS2_V0_length)
+ {
+ info->fsSelection = GetUInt16(pOs2, OS2_fsSelection_offset);
+ info->typoAscender = GetInt16(pOs2, OS2_typoAscender_offset);
+ info->typoDescender = GetInt16(pOs2, OS2_typoDescender_offset);
+ info->typoLineGap = GetInt16(pOs2, OS2_typoLineGap_offset);
+ info->winAscent = GetUInt16(pOs2, OS2_winAscent_offset);
+ info->winDescent = GetUInt16(pOs2, OS2_winDescent_offset);
+ }
+
+ if (nHhea >= HHEA_lineGap_offset + 2) {
+ info->ascender = GetInt16(pHhea, HHEA_ascender_offset);
+ info->descender = GetInt16(pHhea, HHEA_descender_offset);
+ info->linegap = GetInt16(pHhea, HHEA_lineGap_offset);
+ }
+}
+
+bool GetTTGlobalFontHeadInfo(const AbstractTrueTypeFont *ttf, int& xMin, int& yMin, int& xMax, int& yMax, sal_uInt16& macStyle)
+{
+ sal_uInt32 table_size;
+ const sal_uInt8* table = ttf->table(O_head, table_size);
+ if (table_size < 46)
+ return false;
+
+ const int UPEm = ttf->unitsPerEm();
+ if (UPEm == 0)
+ return false;
+ xMin = XUnits(UPEm, GetInt16(table, HEAD_xMin_offset));
+ yMin = XUnits(UPEm, GetInt16(table, HEAD_yMin_offset));
+ xMax = XUnits(UPEm, GetInt16(table, HEAD_xMax_offset));
+ yMax = XUnits(UPEm, GetInt16(table, HEAD_yMax_offset));
+ macStyle = GetUInt16(table, HEAD_macStyle_offset);
+ return true;
+}
+
+void GetTTGlobalFontInfo(TrueTypeFont *ttf, TTGlobalFontInfo *info)
+{
+ int UPEm = ttf->unitsPerEm();
+
+ memset(info, 0, sizeof(TTGlobalFontInfo));
+
+ info->family = ttf->family;
+ info->ufamily = ttf->ufamily;
+ info->subfamily = ttf->subfamily;
+ info->usubfamily = ttf->usubfamily;
+ info->psname = ttf->psname;
+ info->symbolEncoded = ttf->GetCharMap()->isSymbolic();
+
+ sal_uInt32 table_size;
+ const sal_uInt8* table = ttf->table(O_OS2, table_size);
+ if (table_size >= 42)
+ {
+ info->weight = GetUInt16(table, OS2_usWeightClass_offset);
+ info->width = GetUInt16(table, OS2_usWidthClass_offset);
+
+ if (table_size >= OS2_V0_length && UPEm != 0) {
+ info->typoAscender = XUnits(UPEm,GetInt16(table, OS2_typoAscender_offset));
+ info->typoDescender = XUnits(UPEm, GetInt16(table, OS2_typoDescender_offset));
+ info->typoLineGap = XUnits(UPEm, GetInt16(table, OS2_typoLineGap_offset));
+ info->winAscent = XUnits(UPEm, GetUInt16(table, OS2_winAscent_offset));
+ info->winDescent = XUnits(UPEm, GetUInt16(table, OS2_winDescent_offset));
+ /* sanity check; some fonts treat winDescent as signed
+ * violating the standard */
+ if( info->winDescent > 5*UPEm )
+ info->winDescent = XUnits(UPEm, GetInt16(table, OS2_winDescent_offset));
+ }
+ memcpy(info->panose, table + OS2_panose_offset, OS2_panoseNbBytes_offset);
+ info->typeFlags = GetUInt16( table, OS2_fsType_offset );
+ }
+
+ table = ttf->table(O_post, table_size);
+ if (table_size >= 12 + sizeof(sal_uInt32))
+ {
+ info->pitch = GetUInt32(table, POST_isFixedPitch_offset);
+ info->italicAngle = GetInt32(table, POST_italicAngle_offset);
+ }
+
+ GetTTGlobalFontHeadInfo(ttf, info->xMin, info->yMin, info->xMax, info->yMax, info->macStyle);
+
+ table = ttf->table(O_hhea, table_size);
+ if (table_size >= 10 && UPEm != 0)
+ {
+ info->ascender = XUnits(UPEm, GetInt16(table, HHEA_ascender_offset));
+ info->descender = XUnits(UPEm, GetInt16(table, HHEA_descender_offset));
+ info->linegap = XUnits(UPEm, GetInt16(table, HHEA_lineGap_offset));
+ }
+}
+
+GlyphData *GetTTRawGlyphData(AbstractTrueTypeFont *ttf, sal_uInt32 glyphID)
+{
+ if (glyphID >= ttf->glyphCount())
+ return nullptr;
+
+ sal_uInt32 hmtxlength;
+ const sal_uInt8* hmtx = ttf->table(O_hmtx, hmtxlength);
+
+ if (!hmtxlength)
+ return nullptr;
+
+ sal_uInt32 glyflength;
+ const sal_uInt8* glyf = ttf->table(O_glyf, glyflength);
+ int n;
+
+ /* #127161# check the glyph offsets */
+ sal_uInt32 nNextOffset = ttf->glyphOffset(glyphID + 1);
+ if (nNextOffset > glyflength)
+ return nullptr;
+
+ sal_uInt32 nOffset = ttf->glyphOffset(glyphID);
+ if (nOffset > nNextOffset)
+ return nullptr;
+
+ sal_uInt32 length = nNextOffset - nOffset;
+
+ GlyphData* d = static_cast<GlyphData*>(malloc(sizeof(GlyphData))); assert(d != nullptr);
+
+ if (length > 0) {
+ const sal_uInt8* srcptr = glyf + ttf->glyphOffset(glyphID);
+ const size_t nChunkLen = ((length + 1) & ~1);
+ d->ptr = static_cast<sal_uInt8*>(malloc(nChunkLen)); assert(d->ptr != nullptr);
+ memcpy(d->ptr, srcptr, length);
+ memset(d->ptr + length, 0, nChunkLen - length);
+ d->compflag = (GetInt16( srcptr, 0 ) < 0);
+ } else {
+ d->ptr = nullptr;
+ d->compflag = false;
+ }
+
+ d->glyphID = glyphID;
+ d->nbytes = static_cast<sal_uInt16>((length + 1) & ~1);
+
+ /* now calculate npoints and ncontours */
+ ControlPoint *cp;
+ n = GetTTGlyphPoints(ttf, glyphID, &cp);
+ if (n > 0)
+ {
+ int m = 0;
+ for (int i = 0; i < n; i++)
+ {
+ if (cp[i].flags & 0x8000)
+ m++;
+ }
+ d->npoints = static_cast<sal_uInt16>(n);
+ d->ncontours = static_cast<sal_uInt16>(m);
+ free(cp);
+ } else {
+ d->npoints = 0;
+ d->ncontours = 0;
+ }
+
+ /* get advance width and left sidebearing */
+ sal_uInt32 nAwOffset;
+ sal_uInt32 nLsboffset;
+ if (glyphID < ttf->horzMetricCount()) {
+ nAwOffset = 4 * glyphID;
+ nLsboffset = 4 * glyphID + 2;
+ } else {
+ nAwOffset = 4 * (ttf->horzMetricCount() - 1);
+ nLsboffset = (ttf->horzMetricCount() * 4) + ((glyphID - ttf->horzMetricCount()) * 2);
+ }
+
+ if (nAwOffset + 2 <= hmtxlength)
+ d->aw = GetUInt16(hmtx, nAwOffset);
+ else
+ {
+ SAL_WARN("vcl.fonts", "hmtx offset " << nAwOffset << " not available");
+ d->aw = 0;
+ }
+ if (nLsboffset + 2 <= hmtxlength)
+ d->lsb = GetInt16(hmtx, nLsboffset);
+ else
+ {
+ SAL_WARN("vcl.fonts", "hmtx offset " << nLsboffset << " not available");
+ d->lsb = 0;
+ }
+
+ return d;
+}
+
+int GetTTNameRecords(AbstractTrueTypeFont const *ttf, NameRecord **nr)
+{
+ sal_uInt32 nTableSize;
+ const sal_uInt8* table = ttf->table(O_name, nTableSize);
+
+ if (nTableSize < 6)
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.fonts", "O_name table too small.");
+#endif
+ return 0;
+ }
+
+ sal_uInt16 n = GetUInt16(table, 2);
+ sal_uInt32 nStrBase = GetUInt16(table, 4);
+ int i;
+
+ *nr = nullptr;
+ if (n == 0) return 0;
+
+ const sal_uInt32 remaining_table_size = nTableSize-6;
+ const sal_uInt32 nMinRecordSize = 12;
+ const sal_uInt32 nMaxRecords = remaining_table_size / nMinRecordSize;
+ if (n > nMaxRecords)
+ {
+ SAL_WARN("vcl.fonts", "Parsing error in " << OUString::createFromAscii(ttf->fileName()) <<
+ ": " << nMaxRecords << " max possible entries, but " <<
+ n << " claimed, truncating");
+ n = nMaxRecords;
+ }
+
+ NameRecord* rec = static_cast<NameRecord*>(calloc(n, sizeof(NameRecord)));
+ assert(rec);
+
+ for (i = 0; i < n; i++) {
+ sal_uInt32 nLargestFixedOffsetPos = 6 + 10 + 12 * i;
+ sal_uInt32 nMinSize = nLargestFixedOffsetPos + sizeof(sal_uInt16);
+ if (nMinSize > nTableSize)
+ {
+ SAL_WARN( "vcl.fonts", "Font " << OUString::createFromAscii(ttf->fileName()) << " claimed to have "
+ << n << " name records, but only space for " << i);
+ n = i;
+ break;
+ }
+
+ rec[i].platformID = GetUInt16(table, 6 + 0 + 12 * i);
+ rec[i].encodingID = GetUInt16(table, 6 + 2 + 12 * i);
+ rec[i].languageID = LanguageType(GetUInt16(table, 6 + 4 + 12 * i));
+ rec[i].nameID = GetUInt16(table, 6 + 6 + 12 * i);
+ rec[i].slen = GetUInt16(table, 6 + 8 + 12 * i);
+ sal_uInt32 nStrOffset = GetUInt16(table, nLargestFixedOffsetPos);
+ if (rec[i].slen) {
+ if (nStrBase + nStrOffset + rec[i].slen >= nTableSize)
+ {
+ rec[i].sptr = nullptr;
+ rec[i].slen = 0;
+ continue;
+ }
+
+ const sal_uInt32 rec_string = nStrBase + nStrOffset;
+ const size_t available_space = rec_string > nTableSize ? 0 : (nTableSize - rec_string);
+ if (rec[i].slen <= available_space)
+ {
+ rec[i].sptr = static_cast<sal_uInt8 *>(malloc(rec[i].slen)); assert(rec[i].sptr != nullptr);
+ memcpy(rec[i].sptr, table + rec_string, rec[i].slen);
+ }
+ else
+ {
+ rec[i].sptr = nullptr;
+ rec[i].slen = 0;
+ }
+ } else {
+ rec[i].sptr = nullptr;
+ }
+ // some fonts have 3.0 names => fix them to 3.1
+ if( (rec[i].platformID == 3) && (rec[i].encodingID == 0) )
+ rec[i].encodingID = 1;
+ }
+
+ *nr = rec;
+ return n;
+}
+
+void DisposeNameRecords(NameRecord* nr, int n)
+{
+ int i;
+ for (i = 0; i < n; i++) {
+ if (nr[i].sptr) free(nr[i].sptr);
+ }
+ free(nr);
+}
+
+template<size_t N> static void
+append(std::bitset<N> & rSet, size_t const nOffset, sal_uInt32 const nValue)
+{
+ for (size_t i = 0; i < 32; ++i)
+ {
+ rSet.set(nOffset + i, (nValue & (1U << i)) != 0);
+ }
+}
+
+bool getTTCoverage(
+ std::optional<std::bitset<UnicodeCoverage::MAX_UC_ENUM>> &rUnicodeRange,
+ std::optional<std::bitset<CodePageCoverage::MAX_CP_ENUM>> &rCodePageRange,
+ const unsigned char* pTable, size_t nLength)
+{
+ bool bRet = false;
+ // parse OS/2 header
+ if (nLength >= OS2_Legacy_length)
+ {
+ rUnicodeRange = std::bitset<UnicodeCoverage::MAX_UC_ENUM>();
+ append(*rUnicodeRange, 0, GetUInt32(pTable, OS2_ulUnicodeRange1_offset));
+ append(*rUnicodeRange, 32, GetUInt32(pTable, OS2_ulUnicodeRange2_offset));
+ append(*rUnicodeRange, 64, GetUInt32(pTable, OS2_ulUnicodeRange3_offset));
+ append(*rUnicodeRange, 96, GetUInt32(pTable, OS2_ulUnicodeRange4_offset));
+ bRet = true;
+ if (nLength >= OS2_V1_length)
+ {
+ rCodePageRange = std::bitset<CodePageCoverage::MAX_CP_ENUM>();
+ append(*rCodePageRange, 0, GetUInt32(pTable, OS2_ulCodePageRange1_offset));
+ append(*rCodePageRange, 32, GetUInt32(pTable, OS2_ulCodePageRange2_offset));
+ }
+ }
+ return bRet;
+}
+
+} // namespace vcl
+
+int TestFontSubset(const void* data, sal_uInt32 size)
+{
+ int nResult = -1;
+ vcl::TrueTypeFont* pTTF = nullptr;
+ if (OpenTTFontBuffer(data, size, 0, &pTTF) == vcl::SFErrCodes::Ok)
+ {
+ vcl::TTGlobalFontInfo aInfo;
+ GetTTGlobalFontInfo(pTTF, &aInfo);
+
+ sal_uInt16 aGlyphIds[ 256 ] = {};
+ sal_uInt8 aEncoding[ 256 ] = {};
+
+ for (sal_uInt16 c = 32; c < 256; ++c)
+ {
+ aEncoding[c] = c;
+ aGlyphIds[c] = c - 31;
+ }
+
+ CreateTTFromTTGlyphs(pTTF, nullptr, aGlyphIds, aEncoding, 256);
+
+
+ // cleanup
+ CloseTTFont( pTTF );
+ // success
+ nResult = 0;
+ }
+
+ return nResult;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/ttcr.cxx b/vcl/source/fontsubset/ttcr.cxx
new file mode 100644
index 000000000..40729ffd1
--- /dev/null
+++ b/vcl/source/fontsubset/ttcr.cxx
@@ -0,0 +1,1526 @@
+/* -*- 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 .
+ */
+
+/*
+ * TrueTypeCreator method implementation
+ */
+
+#include <iomanip>
+#include <assert.h>
+
+#include <sal/log.hxx>
+
+#include "ttcr.hxx"
+#include "list.h"
+#include <string.h>
+
+namespace vcl
+{
+
+/*
+ * Private Data Types
+ */
+
+ struct TrueTypeCreator {
+ sal_uInt32 tag; /**< TrueType file tag */
+ list tables; /**< List of table tags and pointers */
+ };
+
+namespace {
+
+struct TableEntry {
+ sal_uInt32 tag;
+ sal_uInt32 length;
+ sal_uInt8 *data;
+};
+
+}
+
+/*- Data access macros for data stored in big-endian or little-endian format */
+static sal_Int16 GetInt16( const sal_uInt8* ptr, sal_uInt32 offset)
+{
+ assert(ptr != nullptr);
+ sal_Int16 t = (ptr+offset)[0] << 8 | (ptr+offset)[1];
+ return t;
+}
+
+static sal_uInt16 GetUInt16( const sal_uInt8* ptr, sal_uInt32 offset)
+{
+ assert(ptr != nullptr);
+ sal_uInt16 t = (ptr+offset)[0] << 8 | (ptr+offset)[1];
+ return t;
+}
+
+static void PutInt16(sal_Int16 val, sal_uInt8 *ptr, sal_uInt32 offset)
+{
+ assert(ptr != nullptr);
+
+ ptr[offset] = static_cast<sal_uInt8>((val >> 8) & 0xFF);
+ ptr[offset+1] = static_cast<sal_uInt8>(val & 0xFF);
+}
+
+static void PutUInt16(sal_uInt16 val, sal_uInt8 *ptr, sal_uInt32 offset)
+{
+ assert(ptr != nullptr);
+
+ ptr[offset] = static_cast<sal_uInt8>((val >> 8) & 0xFF);
+ ptr[offset+1] = static_cast<sal_uInt8>(val & 0xFF);
+}
+
+static void PutUInt32(sal_uInt32 val, sal_uInt8 *ptr, sal_uInt32 offset)
+{
+ assert(ptr != nullptr);
+
+ ptr[offset] = static_cast<sal_uInt8>((val >> 24) & 0xFF);
+ ptr[offset+1] = static_cast<sal_uInt8>((val >> 16) & 0xFF);
+ ptr[offset+2] = static_cast<sal_uInt8>((val >> 8) & 0xFF);
+ ptr[offset+3] = static_cast<sal_uInt8>(val & 0xFF);
+}
+
+static int TableEntryCompareF(const void *l, const void *r)
+{
+ sal_uInt32 const ltag(static_cast<TableEntry const*>(l)->tag);
+ sal_uInt32 const rtag(static_cast<TableEntry const*>(r)->tag);
+ return (ltag == rtag) ? 0 : (ltag < rtag) ? -1 : 1;
+}
+
+static int NameRecordCompareF(const void *l, const void *r)
+{
+ NameRecord const *ll = static_cast<NameRecord const *>(l);
+ NameRecord const *rr = static_cast<NameRecord const *>(r);
+
+ if (ll->platformID != rr->platformID) {
+ return (ll->platformID < rr->platformID) ? -1 : 1;
+ } else if (ll->encodingID != rr->encodingID) {
+ return (ll->encodingID < rr->encodingID) ? -1 : 1;
+ } else if (ll->languageID != rr->languageID) {
+ return (ll->languageID < rr->languageID) ? -1 : 1;
+ } else if (ll->nameID != rr->nameID) {
+ return (ll->nameID < rr->nameID) ? -1 : 1;
+ }
+ return 0;
+}
+
+static sal_uInt32 CheckSum(sal_uInt32 *ptr, sal_uInt32 length)
+{
+ sal_uInt32 sum = 0;
+ sal_uInt32 *endptr = ptr + ((length + 3) & sal_uInt32(~3)) / 4;
+
+ while (ptr < endptr) sum += *ptr++;
+
+ return sum;
+}
+
+static void *smalloc(sal_uInt32 size)
+{
+ void *res = malloc(size);
+ assert(res != nullptr);
+ return res;
+}
+
+static void *scalloc(sal_uInt32 n, sal_uInt32 size)
+{
+ void *res = calloc(n, size);
+ assert(res != nullptr);
+ return res;
+}
+
+/*
+ * Public functions
+ */
+
+void TrueTypeCreatorNewEmpty(sal_uInt32 tag, TrueTypeCreator **_this)
+{
+ TrueTypeCreator* ptr = static_cast<TrueTypeCreator*>(smalloc(sizeof(TrueTypeCreator)));
+
+ ptr->tables = listNewEmpty();
+ listSetElementDtor(ptr->tables, TrueTypeTableDispose);
+
+ ptr->tag = tag;
+
+ *_this = ptr;
+}
+
+void AddTable(TrueTypeCreator *_this, TrueTypeTable *table)
+{
+ if (table != nullptr) {
+ listAppend(_this->tables, table);
+ }
+}
+
+void RemoveTable(TrueTypeCreator *_this, sal_uInt32 tag)
+{
+ if (!listCount(_this->tables))
+ return;
+
+ listToFirst(_this->tables);
+ int done = 0;
+ do {
+ if (static_cast<TrueTypeTable *>(listCurrent(_this->tables))->tag == tag)
+ {
+ listRemove(_this->tables);
+ }
+ else
+ {
+ if (listNext(_this->tables))
+ {
+ done = 1;
+ }
+ }
+ } while (!done);
+}
+
+static void ProcessTables(TrueTypeCreator *);
+
+SFErrCodes StreamToMemory(TrueTypeCreator *_this, sal_uInt8 **ptr, sal_uInt32 *length)
+{
+ sal_uInt16 searchRange=1, entrySelector=0, rangeShift;
+ sal_uInt32 s, offset, checkSumAdjustment = 0;
+ sal_uInt32 *p;
+ sal_uInt8 *head = nullptr; /* saved pointer to the head table data for checkSumAdjustment calculation */
+
+ if (listIsEmpty(_this->tables)) return SFErrCodes::TtFormat;
+
+ ProcessTables(_this);
+
+ /* ProcessTables() adds 'loca' and 'hmtx' */
+
+ sal_uInt16 numTables = listCount(_this->tables);
+
+ TableEntry* te = static_cast<TableEntry*>(scalloc(numTables, sizeof(TableEntry)));
+ TableEntry* e = te;
+
+ listToFirst(_this->tables);
+ do {
+ GetRawData(static_cast<TrueTypeTable *>(listCurrent(_this->tables)), &e->data, &e->length, &e->tag);
+ ++e;
+ } while (listNext(_this->tables));
+
+ qsort(te, numTables, sizeof(TableEntry), TableEntryCompareF);
+
+ do {
+ searchRange *= 2;
+ entrySelector++;
+ } while (searchRange <= numTables);
+
+ searchRange *= 8;
+ entrySelector--;
+ rangeShift = numTables * 16 - searchRange;
+
+ s = offset = 12 + 16 * numTables;
+
+ for (int i = 0; i < numTables; ++i) {
+ s += (te[i].length + 3) & sal_uInt32(~3);
+ /* if ((te[i].length & 3) != 0) s += (4 - (te[i].length & 3)) & 3; */
+ }
+
+ sal_uInt8* ttf = static_cast<sal_uInt8*>(smalloc(s));
+
+ /* Offset Table */
+ PutUInt32(_this->tag, ttf, 0);
+ PutUInt16(numTables, ttf, 4);
+ PutUInt16(searchRange, ttf, 6);
+ PutUInt16(entrySelector, ttf, 8);
+ PutUInt16(rangeShift, ttf, 10);
+
+ /* Table Directory */
+ for (int i = 0; i < numTables; ++i) {
+ PutUInt32(te[i].tag, ttf + 12, 16 * i);
+ PutUInt32(CheckSum(reinterpret_cast<sal_uInt32 *>(te[i].data), te[i].length), ttf + 12, 16 * i + 4);
+ PutUInt32(offset, ttf + 12, 16 * i + 8);
+ PutUInt32(te[i].length, ttf + 12, 16 * i + 12);
+
+ if (te[i].tag == T_head) {
+ head = ttf + offset;
+ }
+
+ memcpy(ttf+offset, te[i].data, (te[i].length + 3) & sal_uInt32(~3) );
+ offset += (te[i].length + 3) & sal_uInt32(~3);
+ /* if ((te[i].length & 3) != 0) offset += (4 - (te[i].length & 3)) & 3; */
+ }
+
+ free(te);
+
+ p = reinterpret_cast<sal_uInt32 *>(ttf);
+ for (int i = 0; i < static_cast<int>(s) / 4; ++i) checkSumAdjustment += p[i];
+ PutUInt32(0xB1B0AFBA - checkSumAdjustment, head, 8);
+
+ *ptr = ttf;
+ *length = s;
+
+ return SFErrCodes::Ok;
+}
+
+SFErrCodes StreamToFile(TrueTypeCreator *_this, const char* fname)
+{
+ sal_uInt8 *ptr;
+ sal_uInt32 length;
+ SFErrCodes r;
+
+ if ((r = StreamToMemory(_this, &ptr, &length)) != SFErrCodes::Ok) return r;
+ r = SFErrCodes::BadFile;
+ if (fname)
+ {
+ FILE* fd = fopen(fname, "wb");
+ if (fd)
+ {
+ if (fwrite(ptr, 1, length, fd) != length) {
+ r = SFErrCodes::FileIo;
+ } else {
+ r = SFErrCodes::Ok;
+ }
+ fclose(fd);
+ }
+ }
+ free(ptr);
+ return r;
+}
+
+/*
+ * TrueTypeTable private methods
+ */
+
+/* Table data points to
+ * --------------------------------------------
+ * generic tdata_generic struct
+ * 'head' HEAD_Length bytes of memory
+ * 'hhea' HHEA_Length bytes of memory
+ * 'loca' tdata_loca struct
+ * 'maxp' MAXP_Version1Length bytes of memory
+ * 'glyf' list of GlyphData structs (defined in sft.h)
+ * 'name' list of NameRecord structs (defined in sft.h)
+ * 'post' tdata_post struct
+ *
+ */
+
+#define CMAP_SUBTABLE_INIT 10
+#define CMAP_SUBTABLE_INCR 10
+#define CMAP_PAIR_INIT 500
+#define CMAP_PAIR_INCR 500
+
+namespace {
+
+struct CmapSubTable {
+ sal_uInt32 id; /* subtable ID (platform/encoding ID) */
+ sal_uInt32 n; /* number of used translation pairs */
+ sal_uInt32 m; /* number of allocated translation pairs */
+ sal_uInt32 *xc; /* character array */
+ sal_uInt32 *xg; /* glyph array */
+};
+
+struct table_cmap {
+ sal_uInt32 n; /* number of used CMAP sub-tables */
+ sal_uInt32 m; /* number of allocated CMAP sub-tables */
+ CmapSubTable *s; /* sorted array of sub-tables */
+};
+
+struct tdata_generic {
+ sal_uInt32 tag;
+ sal_uInt32 nbytes;
+ sal_uInt8 *ptr;
+};
+
+struct tdata_loca {
+ sal_uInt32 nbytes; /* number of bytes in loca table */
+ sal_uInt8 *ptr; /* pointer to the data */
+};
+
+struct tdata_post {
+ sal_uInt32 format;
+ sal_uInt32 italicAngle;
+ sal_Int16 underlinePosition;
+ sal_Int16 underlineThickness;
+ sal_uInt32 isFixedPitch;
+ void *ptr; /* format-specific pointer */
+};
+
+}
+
+/* allocate memory for a TT table */
+static sal_uInt8 *ttmalloc(sal_uInt32 nbytes)
+{
+ sal_uInt32 n;
+
+ n = (nbytes + 3) & sal_uInt32(~3);
+ sal_uInt8* res = static_cast<sal_uInt8*>(calloc(n, 1));
+ assert(res != nullptr);
+
+ return res;
+}
+
+static void FreeGlyphData(void *ptr)
+{
+ GlyphData *p = static_cast<GlyphData *>(ptr);
+ if (p->ptr) free(p->ptr);
+ free(p);
+}
+
+static void TrueTypeTableDispose_generic(TrueTypeTable *_this)
+{
+ if (_this) {
+ if (_this->data) {
+ tdata_generic *pdata = static_cast<tdata_generic *>(_this->data);
+ if (pdata->nbytes) free(pdata->ptr);
+ free(_this->data);
+ }
+ free(_this);
+ }
+}
+
+static void TrueTypeTableDispose_head(TrueTypeTable *_this)
+{
+ if (_this) {
+ if (_this->data) free(_this->data);
+ free(_this);
+ }
+}
+
+static void TrueTypeTableDispose_hhea(TrueTypeTable *_this)
+{
+ if (_this) {
+ if (_this->data) free(_this->data);
+ free(_this);
+ }
+}
+
+static void TrueTypeTableDispose_loca(TrueTypeTable *_this)
+{
+ if (_this) {
+ if (_this->data) {
+ tdata_loca *p = static_cast<tdata_loca *>(_this->data);
+ if (p->ptr) free(p->ptr);
+ free(_this->data);
+ }
+ free(_this);
+ }
+}
+
+static void TrueTypeTableDispose_maxp(TrueTypeTable *_this)
+{
+ if (_this) {
+ if (_this->data) free(_this->data);
+ free(_this);
+ }
+}
+
+static void TrueTypeTableDispose_glyf(TrueTypeTable *_this)
+{
+ if (_this) {
+ if (_this->data) listDispose(static_cast<list>(_this->data));
+ free(_this);
+ }
+}
+
+static void TrueTypeTableDispose_cmap(TrueTypeTable *_this)
+{
+ if (!_this) return;
+
+ table_cmap *t = static_cast<table_cmap *>(_this->data);
+ if (t) {
+ CmapSubTable *s = t->s;
+ if (s) {
+ for (sal_uInt32 i = 0; i < t->m; i++) {
+ if (s[i].xc) free(s[i].xc);
+ if (s[i].xg) free(s[i].xg);
+ }
+ free(s);
+ }
+ free(t);
+ }
+ free(_this);
+}
+
+static void TrueTypeTableDispose_name(TrueTypeTable *_this)
+{
+ if (_this) {
+ if (_this->data) listDispose(static_cast<list>(_this->data));
+ free(_this);
+ }
+}
+
+static void TrueTypeTableDispose_post(TrueTypeTable *_this)
+{
+ if (!_this) return;
+
+ tdata_post *p = static_cast<tdata_post *>(_this->data);
+ if (p) {
+ if (p->format == 0x00030000) {
+ /* do nothing */
+ } else {
+ SAL_WARN("vcl.fonts", "Unsupported format of a 'post' table: "
+ << std::setfill('0')
+ << std::setw(8)
+ << std::hex
+ << std::uppercase
+ << static_cast<int>(p->format) << ".");
+ }
+ free(p);
+ }
+ free(_this);
+}
+
+/* destructor vtable */
+
+struct {
+ sal_uInt32 tag;
+ void (*f)(TrueTypeTable *);
+} const vtable1[] =
+{
+ {0, TrueTypeTableDispose_generic},
+ {T_head, TrueTypeTableDispose_head},
+ {T_hhea, TrueTypeTableDispose_hhea},
+ {T_loca, TrueTypeTableDispose_loca},
+ {T_maxp, TrueTypeTableDispose_maxp},
+ {T_glyf, TrueTypeTableDispose_glyf},
+ {T_cmap, TrueTypeTableDispose_cmap},
+ {T_name, TrueTypeTableDispose_name},
+ {T_post, TrueTypeTableDispose_post}
+
+};
+
+static int GetRawData_generic(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ assert(_this != nullptr);
+ assert(_this->data != nullptr);
+
+ *ptr = static_cast<tdata_generic *>(_this->data)->ptr;
+ *len = static_cast<tdata_generic *>(_this->data)->nbytes;
+ *tag = static_cast<tdata_generic *>(_this->data)->tag;
+
+ return TTCR_OK;
+}
+
+static int GetRawData_head(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ *len = HEAD_Length;
+ *ptr = static_cast<sal_uInt8 *>(_this->data);
+ *tag = T_head;
+
+ return TTCR_OK;
+}
+
+static int GetRawData_hhea(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ *len = HHEA_Length;
+ *ptr = static_cast<sal_uInt8 *>(_this->data);
+ *tag = T_hhea;
+
+ return TTCR_OK;
+}
+
+static int GetRawData_loca(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ tdata_loca *p;
+
+ assert(_this->data != nullptr);
+
+ p = static_cast<tdata_loca *>(_this->data);
+
+ if (p->nbytes == 0) return TTCR_ZEROGLYPHS;
+
+ *ptr = p->ptr;
+ *len = p->nbytes;
+ *tag = T_loca;
+
+ return TTCR_OK;
+}
+
+static int GetRawData_maxp(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ *len = MAXP_Version1Length;
+ *ptr = static_cast<sal_uInt8 *>(_this->data);
+ *tag = T_maxp;
+
+ return TTCR_OK;
+}
+
+static int GetRawData_glyf(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ sal_uInt32 n, nbytes = 0;
+ list l = static_cast<list>(_this->data);
+ /* sal_uInt16 curID = 0; */ /* to check if glyph IDs are sequential and start from zero */
+ sal_uInt8 *p;
+
+ *ptr = nullptr;
+ *len = 0;
+ *tag = 0;
+
+ if (listCount(l) == 0) return TTCR_ZEROGLYPHS;
+
+ listToFirst(l);
+ do {
+ /* if (((GlyphData *) listCurrent(l))->glyphID != curID++) return TTCR_GLYPHSEQ; */
+ nbytes += static_cast<GlyphData *>(listCurrent(l))->nbytes;
+ } while (listNext(l));
+
+ p = _this->rawdata = ttmalloc(nbytes);
+
+ listToFirst(l);
+ do {
+ n = static_cast<GlyphData *>(listCurrent(l))->nbytes;
+ if (n != 0) {
+ memcpy(p, static_cast<GlyphData *>(listCurrent(l))->ptr, n);
+ p += n;
+ }
+ } while (listNext(l));
+
+ *len = nbytes;
+ *ptr = _this->rawdata;
+ *tag = T_glyf;
+
+ return TTCR_OK;
+}
+
+/* cmap packers */
+static sal_uInt8 *PackCmapType0(CmapSubTable const *s, sal_uInt32 *length)
+{
+ sal_uInt8* ptr = static_cast<sal_uInt8*>(smalloc(262));
+ sal_uInt8 *p = ptr + 6;
+ sal_uInt32 i, j;
+ sal_uInt16 g;
+
+ PutUInt16(0, ptr, 0);
+ PutUInt16(262, ptr, 2);
+ PutUInt16(0, ptr, 4);
+
+ for (i = 0; i < 256; i++) {
+ g = 0;
+ for (j = 0; j < s->n; j++) {
+ if (s->xc[j] == i) {
+ g = static_cast<sal_uInt16>(s->xg[j]);
+ }
+ }
+ p[i] = static_cast<sal_uInt8>(g);
+ }
+ *length = 262;
+ return ptr;
+}
+
+static sal_uInt8 *PackCmapType6(CmapSubTable const *s, sal_uInt32 *length)
+{
+ sal_uInt8* ptr = static_cast<sal_uInt8*>(smalloc(s->n*2 + 10));
+ sal_uInt8 *p = ptr + 10;
+ sal_uInt32 i, j;
+ sal_uInt16 g;
+
+ PutUInt16(6, ptr, 0);
+ PutUInt16(static_cast<sal_uInt16>(s->n*2+10), ptr, 2);
+ PutUInt16(0, ptr, 4);
+ PutUInt16(0, ptr, 6);
+ PutUInt16(static_cast<sal_uInt16>(s->n), ptr, 8 );
+
+ for (i = 0; i < s->n; i++) {
+ g = 0;
+ for (j = 0; j < s->n; j++) {
+ if (s->xc[j] == i) {
+ g = static_cast<sal_uInt16>(s->xg[j]);
+ }
+ }
+ PutUInt16( g, p, 2*i );
+ }
+ *length = s->n*2+10;
+ return ptr;
+}
+
+/* XXX it only handles Format 0 encoding tables */
+static sal_uInt8 *PackCmap(CmapSubTable const *s, sal_uInt32 *length)
+{
+ if( s->xg[s->n-1] > 0xff )
+ return PackCmapType6(s, length);
+ else
+ return PackCmapType0(s, length);
+}
+
+static int GetRawData_cmap(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ table_cmap *t;
+ sal_uInt32 i;
+ sal_uInt32 tlen = 0;
+ sal_uInt32 l;
+ sal_uInt32 cmapsize;
+ sal_uInt8 *cmap;
+ sal_uInt32 coffset;
+
+ assert(_this != nullptr);
+ t = static_cast<table_cmap *>(_this->data);
+ assert(t != nullptr);
+ assert(t->n != 0);
+
+ sal_uInt8** subtables = static_cast<sal_uInt8**>(scalloc(t->n, sizeof(sal_uInt8 *)));
+ sal_uInt32* sizes = static_cast<sal_uInt32*>(scalloc(t->n, sizeof(sal_uInt32)));
+
+ for (i = 0; i < t->n; i++) {
+ subtables[i] = PackCmap(t->s+i, &l);
+ sizes[i] = l;
+ tlen += l;
+ }
+
+ cmapsize = tlen + 4 + 8 * t->n;
+ _this->rawdata = cmap = ttmalloc(cmapsize);
+
+ PutUInt16(0, cmap, 0);
+ PutUInt16(static_cast<sal_uInt16>(t->n), cmap, 2);
+ coffset = 4 + t->n * 8;
+
+ for (i = 0; i < t->n; i++) {
+ PutUInt16(static_cast<sal_uInt16>(t->s[i].id >> 16), cmap + 4, i * 8);
+ PutUInt16(static_cast<sal_uInt16>(t->s[i].id & 0xFF), cmap + 4, 2 + i * 8);
+ PutUInt32(coffset, cmap + 4, 4 + i * 8);
+ memcpy(cmap + coffset, subtables[i], sizes[i]);
+ free(subtables[i]);
+ coffset += sizes[i];
+ }
+
+ free(subtables);
+ free(sizes);
+
+ *ptr = cmap;
+ *len = cmapsize;
+ *tag = T_cmap;
+
+ return TTCR_OK;
+}
+
+static int GetRawData_name(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ list l;
+ sal_Int16 i=0, n; /* number of Name Records */
+ int stringLen = 0;
+ sal_uInt8 *p1, *p2;
+
+ *ptr = nullptr;
+ *len = 0;
+ *tag = 0;
+
+ assert(_this != nullptr);
+ l = static_cast<list>(_this->data);
+ assert(l != nullptr);
+
+ if ((n = static_cast<sal_Int16>(listCount(l))) == 0) return TTCR_NONAMES;
+
+ NameRecord* nr = static_cast<NameRecord*>(scalloc(n, sizeof(NameRecord)));
+
+ listToFirst(l);
+
+ do {
+ memcpy(nr+i, listCurrent(l), sizeof(NameRecord));
+ stringLen += nr[i].slen;
+ i++;
+ } while (listNext(l));
+
+ if (stringLen > 65535) {
+ free(nr);
+ return TTCR_NAMETOOLONG;
+ }
+
+ qsort(nr, n, sizeof(NameRecord), NameRecordCompareF);
+
+ int nameLen = stringLen + 12 * n + 6;
+ sal_uInt8* name = ttmalloc(nameLen);
+
+ PutUInt16(0, name, 0);
+ PutUInt16(n, name, 2);
+ PutUInt16(static_cast<sal_uInt16>(6 + 12 * n), name, 4);
+
+ p1 = name + 6;
+ p2 = p1 + 12 * n;
+
+ for (i = 0; i < n; i++) {
+ PutUInt16(nr[i].platformID, p1, 0);
+ PutUInt16(nr[i].encodingID, p1, 2);
+ PutUInt16(static_cast<sal_uInt16>(nr[i].languageID), p1, 4);
+ PutUInt16(nr[i].nameID, p1, 6);
+ PutUInt16(nr[i].slen, p1, 8);
+ PutUInt16(static_cast<sal_uInt16>(p2 - (name + 6 + 12 * n)), p1, 10);
+ if (nr[i].slen) {
+ memcpy(p2, nr[i].sptr, nr[i].slen);
+ }
+ /* {int j; for(j=0; j<nr[i].slen; j++) printf("%c", nr[i].sptr[j]); printf("\n"); }; */
+ p2 += nr[i].slen;
+ p1 += 12;
+ }
+
+ free(nr);
+ _this->rawdata = name;
+
+ *ptr = name;
+ *len = static_cast<sal_uInt16>(nameLen);
+ *tag = T_name;
+
+ /*{int j; for(j=0; j<nameLen; j++) printf("%c", name[j]); }; */
+
+ return TTCR_OK;
+}
+
+static int GetRawData_post(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ tdata_post *p = static_cast<tdata_post *>(_this->data);
+ sal_uInt8 *post = nullptr;
+ sal_uInt32 postLen = 0;
+ int ret;
+
+ if (_this->rawdata) free(_this->rawdata);
+
+ if (p->format == 0x00030000) {
+ postLen = 32;
+ post = ttmalloc(postLen);
+ PutUInt32(0x00030000, post, 0);
+ PutUInt32(p->italicAngle, post, 4);
+ PutUInt16(p->underlinePosition, post, 8);
+ PutUInt16(p->underlineThickness, post, 10);
+ PutUInt16(static_cast<sal_uInt16>(p->isFixedPitch), post, 12);
+ ret = TTCR_OK;
+ } else {
+ SAL_WARN("vcl.fonts", "Unrecognized format of a post table: "
+ << std::setfill('0')
+ << std::setw(8)
+ << std::hex
+ << std::uppercase
+ << static_cast<int>(p->format) << ".");
+ ret = TTCR_POSTFORMAT;
+ }
+
+ *ptr = _this->rawdata = post;
+ *len = postLen;
+ *tag = T_post;
+
+ return ret;
+}
+
+struct {
+ sal_uInt32 tag;
+ int (*f)(TrueTypeTable *, sal_uInt8 **, sal_uInt32 *, sal_uInt32 *);
+} const vtable2[] =
+{
+ {0, GetRawData_generic},
+ {T_head, GetRawData_head},
+ {T_hhea, GetRawData_hhea},
+ {T_loca, GetRawData_loca},
+ {T_maxp, GetRawData_maxp},
+ {T_glyf, GetRawData_glyf},
+ {T_cmap, GetRawData_cmap},
+ {T_name, GetRawData_name},
+ {T_post, GetRawData_post}
+
+};
+
+/*
+ * TrueTypeTable public methods
+ */
+
+/* Note: Type42 fonts only need these tables:
+ * head, hhea, loca, maxp, cvt, prep, glyf, hmtx, fpgm
+ *
+ * Microsoft required tables
+ * cmap, glyf, head, hhea, hmtx, loca, maxp, name, post, OS/2
+ *
+ * Apple required tables
+ * cmap, glyf, head, hhea, hmtx, loca, maxp, name, post
+ *
+ */
+
+TrueTypeTable *TrueTypeTableNew(sal_uInt32 tag,
+ sal_uInt32 nbytes,
+ const sal_uInt8* ptr)
+{
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ tdata_generic* pdata = static_cast<tdata_generic*>(smalloc(sizeof(tdata_generic)));
+ pdata->nbytes = nbytes;
+ pdata->tag = tag;
+ if (nbytes) {
+ pdata->ptr = ttmalloc(nbytes);
+ memcpy(pdata->ptr, ptr, nbytes);
+ } else {
+ pdata->ptr = nullptr;
+ }
+
+ table->tag = 0;
+ table->data = pdata;
+ table->rawdata = nullptr;
+
+ return table;
+}
+
+TrueTypeTable *TrueTypeTableNew_head(sal_uInt32 fontRevision,
+ sal_uInt16 flags,
+ sal_uInt16 unitsPerEm,
+ const sal_uInt8* created,
+ sal_uInt16 macStyle,
+ sal_uInt16 lowestRecPPEM,
+ sal_Int16 fontDirectionHint)
+{
+ assert(created != nullptr);
+
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ sal_uInt8* ptr = ttmalloc(HEAD_Length);
+
+ PutUInt32(0x00010000, ptr, 0); /* version */
+ PutUInt32(fontRevision, ptr, 4);
+ PutUInt32(0x5F0F3CF5, ptr, 12); /* magic number */
+ PutUInt16(flags, ptr, 16);
+ PutUInt16(unitsPerEm, ptr, 18);
+ memcpy(ptr+20, created, 8); /* Created Long Date */
+ memset(ptr+28, 0, 8); /* Modified Long Date */
+ PutUInt16(macStyle, ptr, 44);
+ PutUInt16(lowestRecPPEM, ptr, 46);
+ PutUInt16(fontDirectionHint, ptr, 48);
+ PutUInt16(0, ptr, 52); /* glyph data format: 0 */
+
+ table->data = static_cast<void *>(ptr);
+ table->tag = T_head;
+ table->rawdata = nullptr;
+
+ return table;
+}
+
+TrueTypeTable *TrueTypeTableNew_hhea(sal_Int16 ascender,
+ sal_Int16 descender,
+ sal_Int16 linegap,
+ sal_Int16 caretSlopeRise,
+ sal_Int16 caretSlopeRun)
+{
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ sal_uInt8* ptr = ttmalloc(HHEA_Length);
+
+ PutUInt32(0x00010000, ptr, 0); /* version */
+ PutUInt16(ascender, ptr, 4);
+ PutUInt16(descender, ptr, 6);
+ PutUInt16(linegap, ptr, 8);
+ PutUInt16(caretSlopeRise, ptr, 18);
+ PutUInt16(caretSlopeRun, ptr, 20);
+ PutUInt16(0, ptr, 22); /* reserved 1 */
+ PutUInt16(0, ptr, 24); /* reserved 2 */
+ PutUInt16(0, ptr, 26); /* reserved 3 */
+ PutUInt16(0, ptr, 28); /* reserved 4 */
+ PutUInt16(0, ptr, 30); /* reserved 5 */
+ PutUInt16(0, ptr, 32); /* metricDataFormat */
+
+ table->data = static_cast<void *>(ptr);
+ table->tag = T_hhea;
+ table->rawdata = nullptr;
+
+ return table;
+}
+
+TrueTypeTable *TrueTypeTableNew_loca()
+{
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ table->data = smalloc(sizeof(tdata_loca));
+
+ static_cast<tdata_loca *>(table->data)->nbytes = 0;
+ static_cast<tdata_loca *>(table->data)->ptr = nullptr;
+
+ table->tag = T_loca;
+ table->rawdata = nullptr;
+
+ return table;
+}
+
+TrueTypeTable *TrueTypeTableNew_maxp( const sal_uInt8* maxp, int size)
+{
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ table->data = ttmalloc(MAXP_Version1Length);
+
+ if (maxp && size == MAXP_Version1Length) {
+ memcpy(table->data, maxp, MAXP_Version1Length);
+ }
+
+ table->tag = T_maxp;
+ table->rawdata = nullptr;
+
+ return table;
+}
+
+TrueTypeTable *TrueTypeTableNew_glyf()
+{
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ list l = listNewEmpty();
+
+ assert(l != nullptr);
+
+ listSetElementDtor(l, FreeGlyphData);
+
+ table->data = l;
+ table->rawdata = nullptr;
+ table->tag = T_glyf;
+
+ return table;
+}
+
+TrueTypeTable *TrueTypeTableNew_cmap()
+{
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ table_cmap* cmap = static_cast<table_cmap*>(smalloc(sizeof(table_cmap)));
+
+ cmap->n = 0;
+ cmap->m = CMAP_SUBTABLE_INIT;
+ cmap->s = static_cast<CmapSubTable *>(scalloc(CMAP_SUBTABLE_INIT, sizeof(CmapSubTable)));
+
+ table->data = cmap;
+
+ table->rawdata = nullptr;
+ table->tag = T_cmap;
+
+ return table;
+}
+
+static void DisposeNameRecord(void *ptr)
+{
+ if (ptr != nullptr) {
+ NameRecord *nr = static_cast<NameRecord *>(ptr);
+ if (nr->sptr) free(nr->sptr);
+ free(ptr);
+ }
+}
+
+static NameRecord* NameRecordNewCopy(NameRecord const *nr)
+{
+ NameRecord* p = static_cast<NameRecord*>(smalloc(sizeof(NameRecord)));
+
+ memcpy(p, nr, sizeof(NameRecord));
+
+ if (p->slen) {
+ p->sptr = static_cast<sal_uInt8*>(smalloc(p->slen));
+ memcpy(p->sptr, nr->sptr, p->slen);
+ }
+
+ return p;
+}
+
+TrueTypeTable *TrueTypeTableNew_name(int n, NameRecord const *nr)
+{
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ list l = listNewEmpty();
+
+ assert(l != nullptr);
+
+ listSetElementDtor(l, DisposeNameRecord);
+
+ if (n != 0) {
+ int i;
+ for (i = 0; i < n; i++) {
+ listAppend(l, NameRecordNewCopy(nr+i));
+ }
+ }
+
+ table->data = l;
+ table->rawdata = nullptr;
+ table->tag = T_name;
+
+ return table;
+}
+
+TrueTypeTable *TrueTypeTableNew_post(sal_Int32 format,
+ sal_Int32 italicAngle,
+ sal_Int16 underlinePosition,
+ sal_Int16 underlineThickness,
+ sal_uInt32 isFixedPitch)
+{
+ assert(format == 0x00030000); /* Only format 3.0 is supported at this time */
+ TrueTypeTable* table = static_cast<TrueTypeTable*>(smalloc(sizeof(TrueTypeTable)));
+ tdata_post* post = static_cast<tdata_post*>(smalloc(sizeof(tdata_post)));
+
+ post->format = format;
+ post->italicAngle = italicAngle;
+ post->underlinePosition = underlinePosition;
+ post->underlineThickness = underlineThickness;
+ post->isFixedPitch = isFixedPitch;
+ post->ptr = nullptr;
+
+ table->data = post;
+ table->rawdata = nullptr;
+ table->tag = T_post;
+
+ return table;
+}
+
+int GetRawData(TrueTypeTable *_this, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag)
+{
+ /* XXX do a binary search */
+ assert(_this != nullptr);
+ assert(ptr != nullptr);
+ assert(len != nullptr);
+ assert(tag != nullptr);
+
+ *ptr = nullptr; *len = 0; *tag = 0;
+
+ if (_this->rawdata) {
+ free(_this->rawdata);
+ _this->rawdata = nullptr;
+ }
+
+ for(size_t i=0; i < SAL_N_ELEMENTS(vtable2); i++) {
+ if (_this->tag == vtable2[i].tag) {
+ return vtable2[i].f(_this, ptr, len, tag);
+ }
+ }
+
+ assert(!"Unknown TrueType table.");
+ return TTCR_UNKNOWN;
+}
+
+void cmapAdd(TrueTypeTable *table, sal_uInt32 id, sal_uInt32 c, sal_uInt32 g)
+{
+ sal_uInt32 i, found;
+ table_cmap *t;
+ CmapSubTable *s;
+
+ assert(table != nullptr);
+ assert(table->tag == T_cmap);
+ t = static_cast<table_cmap *>(table->data); assert(t != nullptr);
+ s = t->s; assert(s != nullptr);
+
+ found = 0;
+
+ for (i = 0; i < t->n; i++) {
+ if (s[i].id == id) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ if (t->n == t->m) {
+ CmapSubTable* tmp = static_cast<CmapSubTable*>(scalloc(t->m + CMAP_SUBTABLE_INCR, sizeof(CmapSubTable)));
+ memcpy(tmp, s, sizeof(CmapSubTable) * t->m);
+ t->m += CMAP_SUBTABLE_INCR;
+ free(s);
+ s = tmp;
+ t->s = s;
+ }
+
+ for (i = 0; i < t->n; i++) {
+ if (s[i].id > id) break;
+ }
+
+ if (i < t->n) {
+ memmove(s+i+1, s+i, t->n-i);
+ }
+
+ t->n++;
+
+ s[i].id = id;
+ s[i].n = 0;
+ s[i].m = CMAP_PAIR_INIT;
+ s[i].xc = static_cast<sal_uInt32*>(scalloc(CMAP_PAIR_INIT, sizeof(sal_uInt32)));
+ s[i].xg = static_cast<sal_uInt32*>(scalloc(CMAP_PAIR_INIT, sizeof(sal_uInt32)));
+ }
+
+ if (s[i].n == s[i].m) {
+ sal_uInt32* tmp1 = static_cast<sal_uInt32*>(scalloc(s[i].m + CMAP_PAIR_INCR, sizeof(sal_uInt32)));
+ sal_uInt32* tmp2 = static_cast<sal_uInt32*>(scalloc(s[i].m + CMAP_PAIR_INCR, sizeof(sal_uInt32)));
+ assert(tmp1 != nullptr);
+ assert(tmp2 != nullptr);
+ memcpy(tmp1, s[i].xc, sizeof(sal_uInt32) * s[i].m);
+ memcpy(tmp2, s[i].xg, sizeof(sal_uInt32) * s[i].m);
+ s[i].m += CMAP_PAIR_INCR;
+ free(s[i].xc);
+ free(s[i].xg);
+ s[i].xc = tmp1;
+ s[i].xg = tmp2;
+ }
+
+ s[i].xc[s[i].n] = c;
+ s[i].xg[s[i].n] = g;
+ s[i].n++;
+}
+
+sal_uInt32 glyfAdd(TrueTypeTable *table, GlyphData *glyphdata, AbstractTrueTypeFont *fnt)
+{
+ list l;
+ sal_uInt32 currentID;
+ int ret, n, ncomponents;
+
+ assert(table != nullptr);
+ assert(table->tag == T_glyf);
+
+ if (!glyphdata) return sal_uInt32(~0);
+
+ std::vector< sal_uInt32 > glyphlist;
+
+ ncomponents = GetTTGlyphComponents(fnt, glyphdata->glyphID, glyphlist);
+
+ l = static_cast<list>(table->data);
+ if (listCount(l) > 0) {
+ listToLast(l);
+ ret = n = static_cast<GlyphData *>(listCurrent(l))->newID + 1;
+ } else {
+ ret = n = 0;
+ }
+ glyphdata->newID = n++;
+ listAppend(l, glyphdata);
+
+ if (ncomponents > 1 && glyphlist.size() > 1 )
+ {
+ std::vector< sal_uInt32 >::const_iterator it = glyphlist.begin();
+ ++it;
+ /* glyphData->glyphID is always the first glyph on the list */
+ do
+ {
+ int found = 0;
+ currentID = *it;
+ /* XXX expensive! should be rewritten with sorted arrays! */
+ listToFirst(l);
+ do {
+ if (static_cast<GlyphData *>(listCurrent(l))->glyphID == currentID) {
+ found = 1;
+ break;
+ }
+ } while (listNext(l));
+
+ if (!found) {
+ GlyphData *gd = GetTTRawGlyphData(fnt, currentID);
+ gd->newID = n++;
+ listAppend(l, gd);
+ }
+ } while( ++it != glyphlist.end() );
+ }
+
+ return ret;
+}
+
+sal_uInt32 glyfCount(const TrueTypeTable *table)
+{
+ assert(table != nullptr);
+ assert(table->tag == T_glyf);
+ return listCount(static_cast<list>(table->data));
+}
+
+static TrueTypeTable *FindTable(TrueTypeCreator *tt, sal_uInt32 tag)
+{
+ if (listIsEmpty(tt->tables)) return nullptr;
+
+ listToFirst(tt->tables);
+
+ do {
+ if (static_cast<TrueTypeTable *>(listCurrent(tt->tables))->tag == tag) {
+ return static_cast<TrueTypeTable*>(listCurrent(tt->tables));
+ }
+ } while (listNext(tt->tables));
+
+ return nullptr;
+}
+
+/* This function processes all the tables and synchronizes them before creating
+ * the output TrueType stream.
+ *
+ * *** It adds two TrueType tables to the font: 'loca' and 'hmtx' ***
+ *
+ * It does:
+ *
+ * - Re-numbers glyph IDs and creates 'glyf', 'loca', and 'hmtx' tables.
+ * - Calculates xMin, yMin, xMax, and yMax and stores values in 'head' table.
+ * - Stores indexToLocFormat in 'head'
+ * - updates 'maxp' table
+ * - Calculates advanceWidthMax, minLSB, minRSB, xMaxExtent and numberOfHMetrics
+ * in 'hhea' table
+ *
+ */
+static void ProcessTables(TrueTypeCreator *tt)
+{
+ TrueTypeTable *glyf, *loca, *head, *maxp, *hhea;
+ list glyphlist;
+ sal_uInt32 nGlyphs, locaLen = 0, glyfLen = 0;
+ sal_Int16 xMin = 0, yMin = 0, xMax = 0, yMax = 0;
+ sal_uInt32 i = 0;
+ sal_Int16 indexToLocFormat;
+ sal_uInt8 *hmtxPtr, *hheaPtr;
+ sal_uInt32 hmtxSize;
+ sal_uInt8 *p1, *p2;
+ sal_uInt16 maxPoints = 0, maxContours = 0, maxCompositePoints = 0, maxCompositeContours = 0;
+ int nlsb = 0;
+ sal_uInt32 *gid; /* array of old glyphIDs */
+
+ glyf = FindTable(tt, T_glyf);
+ glyphlist = static_cast<list>(glyf->data);
+ nGlyphs = listCount(glyphlist);
+ if (!nGlyphs)
+ {
+ SAL_WARN("vcl.fonts", "no glyphs found in ProcessTables");
+ return;
+ }
+ gid = static_cast<sal_uInt32*>(scalloc(nGlyphs, sizeof(sal_uInt32)));
+
+ RemoveTable(tt, T_loca);
+ RemoveTable(tt, T_hmtx);
+
+ /* XXX Need to make sure that composite glyphs do not break during glyph renumbering */
+
+ listToFirst(glyphlist);
+ do {
+ GlyphData *gd = static_cast<GlyphData *>(listCurrent(glyphlist));
+ glyfLen += gd->nbytes;
+ /* XXX if (gd->nbytes & 1) glyfLen++; */
+
+ assert(gd->newID == i);
+ gid[i++] = gd->glyphID;
+ /* gd->glyphID = i++; */
+
+ /* printf("IDs: %d %d.\n", gd->glyphID, gd->newID); */
+
+ if (gd->nbytes >= 10) {
+ sal_Int16 z = GetInt16(gd->ptr, 2);
+ if (z < xMin) xMin = z;
+
+ z = GetInt16(gd->ptr, 4);
+ if (z < yMin) yMin = z;
+
+ z = GetInt16(gd->ptr, 6);
+ if (z > xMax) xMax = z;
+
+ z = GetInt16(gd->ptr, 8);
+ if (z > yMax) yMax = z;
+ }
+
+ if (!gd->compflag) { /* non-composite glyph */
+ if (gd->npoints > maxPoints) maxPoints = gd->npoints;
+ if (gd->ncontours > maxContours) maxContours = gd->ncontours;
+ } else { /* composite glyph */
+ if (gd->npoints > maxCompositePoints) maxCompositePoints = gd->npoints;
+ if (gd->ncontours > maxCompositeContours) maxCompositeContours = gd->ncontours;
+ }
+
+ } while (listNext(glyphlist));
+
+ indexToLocFormat = (glyfLen / 2 > 0xFFFF) ? 1 : 0;
+ locaLen = indexToLocFormat ? (nGlyphs + 1) << 2 : (nGlyphs + 1) << 1;
+
+ sal_uInt8* glyfPtr = ttmalloc(glyfLen);
+ sal_uInt8* locaPtr = ttmalloc(locaLen);
+ TTSimpleGlyphMetrics* met = static_cast<TTSimpleGlyphMetrics*>(scalloc(nGlyphs, sizeof(TTSimpleGlyphMetrics)));
+ i = 0;
+
+ listToFirst(glyphlist);
+ p1 = glyfPtr;
+ p2 = locaPtr;
+ do {
+ GlyphData *gd = static_cast<GlyphData *>(listCurrent(glyphlist));
+
+ if (gd->compflag && gd->nbytes > 10) { /* re-number all components */
+ sal_uInt16 flags, index;
+ sal_uInt8 *ptr = gd->ptr + 10;
+ size_t nRemaining = gd->nbytes - 10;
+ do {
+ if (nRemaining < 4)
+ {
+ SAL_WARN("vcl.fonts", "truncated font");
+ break;
+ }
+ flags = GetUInt16(ptr, 0);
+ index = GetUInt16(ptr, 2);
+
+ /* XXX use the sorted array of old to new glyphID mapping and do a binary search */
+ sal_uInt32 j;
+ for (j = 0; j < nGlyphs; j++) {
+ if (gid[j] == index) {
+ break;
+ }
+ }
+ /* printf("X: %d -> %d.\n", index, j); */
+
+ PutUInt16(static_cast<sal_uInt16>(j), ptr, 2);
+
+ ptr += 4;
+ nRemaining -= 4;
+
+ sal_uInt32 nAdvance = 0;
+ if (flags & ARG_1_AND_2_ARE_WORDS) {
+ nAdvance += 4;
+ } else {
+ nAdvance += 2;
+ }
+
+ if (flags & WE_HAVE_A_SCALE) {
+ nAdvance += 2;
+ } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
+ nAdvance += 4;
+ } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
+ nAdvance += 8;
+ }
+
+ if (nRemaining < nAdvance)
+ {
+ SAL_WARN("vcl.fonts", "truncated font");
+ break;
+ }
+
+ ptr += nAdvance;
+ nRemaining -= nAdvance;
+
+ } while (flags & MORE_COMPONENTS);
+ }
+
+ if (gd->nbytes != 0) {
+ memcpy(p1, gd->ptr, gd->nbytes);
+ }
+ if (indexToLocFormat == 1) {
+ PutUInt32(p1 - glyfPtr, p2, 0);
+ p2 += 4;
+ } else {
+ PutUInt16(static_cast<sal_uInt16>((p1 - glyfPtr) >> 1), p2, 0);
+ p2 += 2;
+ }
+ p1 += gd->nbytes;
+
+ /* fill the array of metrics */
+ met[i].adv = gd->aw;
+ met[i].sb = gd->lsb;
+ i++;
+ } while (listNext(glyphlist));
+
+ free(gid);
+
+ if (indexToLocFormat == 1) {
+ PutUInt32(p1 - glyfPtr, p2, 0);
+ } else {
+ PutUInt16(static_cast<sal_uInt16>((p1 - glyfPtr) >> 1), p2, 0);
+ }
+
+ glyf->rawdata = glyfPtr;
+
+ loca = TrueTypeTableNew_loca(); assert(loca != nullptr);
+ static_cast<tdata_loca *>(loca->data)->ptr = locaPtr;
+ static_cast<tdata_loca *>(loca->data)->nbytes = locaLen;
+
+ AddTable(tt, loca);
+
+ head = FindTable(tt, T_head);
+ sal_uInt8* const pHeadData = static_cast<sal_uInt8*>(head->data);
+ PutInt16(xMin, pHeadData, HEAD_xMin_offset);
+ PutInt16(yMin, pHeadData, HEAD_yMin_offset);
+ PutInt16(xMax, pHeadData, HEAD_xMax_offset);
+ PutInt16(yMax, pHeadData, HEAD_yMax_offset);
+ PutInt16(indexToLocFormat, pHeadData, HEAD_indexToLocFormat_offset);
+
+ maxp = FindTable(tt, T_maxp);
+
+ sal_uInt8* const pMaxpData = static_cast<sal_uInt8*>(maxp->data);
+ PutUInt16(static_cast<sal_uInt16>(nGlyphs), pMaxpData, MAXP_numGlyphs_offset);
+ PutUInt16(maxPoints, pMaxpData, MAXP_maxPoints_offset);
+ PutUInt16(maxContours, pMaxpData, MAXP_maxContours_offset);
+ PutUInt16(maxCompositePoints, pMaxpData, MAXP_maxCompositePoints_offset);
+ PutUInt16(maxCompositeContours, pMaxpData, MAXP_maxCompositeContours_offset);
+
+ /*
+ * Generate an htmx table and update hhea table
+ */
+ hhea = FindTable(tt, T_hhea); assert(hhea != nullptr);
+ hheaPtr = static_cast<sal_uInt8 *>(hhea->data);
+ if (nGlyphs > 2) {
+ for (i = nGlyphs - 1; i > 0; i--) {
+ if (met[i].adv != met[i-1].adv) break;
+ }
+ nlsb = nGlyphs - 1 - i;
+ }
+ hmtxSize = (nGlyphs - nlsb) * 4 + nlsb * 2;
+ hmtxPtr = ttmalloc(hmtxSize);
+ p1 = hmtxPtr;
+
+ for (i = 0; i < nGlyphs; i++) {
+ if (i < nGlyphs - nlsb) {
+ PutUInt16(met[i].adv, p1, 0);
+ PutUInt16(met[i].sb, p1, 2);
+ p1 += 4;
+ } else {
+ PutUInt16(met[i].sb, p1, 0);
+ p1 += 2;
+ }
+ }
+
+ AddTable(tt, TrueTypeTableNew(T_hmtx, hmtxSize, hmtxPtr));
+ PutUInt16(static_cast<sal_uInt16>(nGlyphs - nlsb), hheaPtr, 34);
+ free(hmtxPtr);
+ free(met);
+}
+
+} // namespace vcl
+
+extern "C"
+{
+ /**
+ * TrueTypeCreator destructor. It calls destructors for all TrueTypeTables added to it.
+ */
+ void TrueTypeCreatorDispose(vcl::TrueTypeCreator *_this)
+ {
+ listDispose(_this->tables);
+ free(_this);
+ }
+
+ /**
+ * Destructor for the TrueTypeTable object.
+ */
+ void TrueTypeTableDispose(void * arg)
+ {
+ vcl::TrueTypeTable *_this = static_cast<vcl::TrueTypeTable *>(arg);
+ /* XXX do a binary search */
+ assert(_this != nullptr);
+
+ if (_this->rawdata) free(_this->rawdata);
+
+ for(size_t i=0; i < SAL_N_ELEMENTS(vcl::vtable1); i++) {
+ if (_this->tag == vcl::vtable1[i].tag) {
+ vcl::vtable1[i].f(_this);
+ return;
+ }
+ }
+ assert(!"Unknown TrueType table.");
+ }
+}
+
+#ifdef TEST_TTCR
+static sal_uInt32 mkTag(sal_uInt8 a, sal_uInt8 b, sal_uInt8 c, sal_uInt8 d) {
+ return (a << 24) | (b << 16) | (c << 8) | d;
+}
+
+int main()
+{
+ TrueTypeCreator *ttcr;
+ sal_uInt8 *t1, *t2, *t3, *t4, *t5, *t6;
+
+ TrueTypeCreatorNewEmpty(mkTag('t','r','u','e'), &ttcr);
+
+ t1 = malloc(1000); memset(t1, 'a', 1000);
+ t2 = malloc(2000); memset(t2, 'b', 2000);
+ t3 = malloc(3000); memset(t3, 'c', 3000);
+ t4 = malloc(4000); memset(t4, 'd', 4000);
+ t5 = malloc(5000); memset(t5, 'e', 5000);
+ t6 = malloc(6000); memset(t6, 'f', 6000);
+
+ AddTable(ttcr, TrueTypeTableNew(T_maxp, 1000, t1));
+ AddTable(ttcr, TrueTypeTableNew(T_OS2, 2000, t2));
+ AddTable(ttcr, TrueTypeTableNew(T_cmap, 3000, t3));
+ AddTable(ttcr, TrueTypeTableNew(T_loca, 4000, t4));
+ AddTable(ttcr, TrueTypeTableNew(T_hhea, 5000, t5));
+ AddTable(ttcr, TrueTypeTableNew(T_glyf, 6000, t6));
+
+ free(t1);
+ free(t2);
+ free(t3);
+ free(t4);
+ free(t5);
+ free(t6);
+
+ StreamToFile(ttcr, "ttcrout.ttf");
+
+ TrueTypeCreatorDispose(ttcr);
+ return 0;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/ttcr.hxx b/vcl/source/fontsubset/ttcr.hxx
new file mode 100644
index 000000000..76709e5dc
--- /dev/null
+++ b/vcl/source/fontsubset/ttcr.hxx
@@ -0,0 +1,220 @@
+/* -*- 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 .
+ */
+
+/**
+ * @file ttcr.hxx
+ * @brief TrueType font creator
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FONTSUBSET_TTCR_HXX
+#define INCLUDED_VCL_SOURCE_FONTSUBSET_TTCR_HXX
+
+#include <sft.hxx>
+
+namespace vcl
+{
+ struct TrueTypeCreator;
+
+/* TrueType data types */
+ typedef struct {
+ sal_uInt16 aw;
+ sal_Int16 lsb;
+ } longHorMetrics;
+
+/* A generic base class for all TrueType tables */
+ struct TrueTypeTable {
+ sal_uInt32 tag; /* table tag */
+ sal_uInt8 *rawdata; /* raw data allocated by GetRawData_*() */
+ void *data; /* table specific data */
+ };
+
+/** Error codes for most functions */
+ enum TTCRErrCodes {
+ TTCR_OK, /**< no error */
+ TTCR_ZEROGLYPHS, /**< At least one glyph should be defined */
+ TTCR_UNKNOWN, /**< Unknown TrueType table */
+ TTCR_NONAMES, /**< 'name' table does not contain any names */
+ TTCR_NAMETOOLONG, /**< 'name' table is too long (string data > 64K) */
+ TTCR_POSTFORMAT /**< unsupported format of a 'post' table */
+ };
+
+/**
+ * TrueTypeCreator constructor.
+ * Allocates all internal structures.
+ */
+ void TrueTypeCreatorNewEmpty(sal_uInt32 tag, TrueTypeCreator **_this);
+
+/**
+ * Adds a TrueType table to the TrueType creator.
+ */
+ void AddTable(TrueTypeCreator *_this, TrueTypeTable *table);
+
+/**
+ * Removes a TrueType table from the TrueType creator if it is stored there.
+ * It also calls a TrueTypeTable destructor.
+ * Note: all generic tables (with tag 0) will be removed if this function is
+ * called with the second argument of 0.
+ * @return value of SFErrCodes type
+ */
+ void RemoveTable(TrueTypeCreator *_this, sal_uInt32 tag);
+
+/**
+ * Writes a TrueType font generated by the TrueTypeCreator to a segment of
+ * memory that this method allocates. When it is not needed anymore the caller
+ * is supposed to call free() on it.
+ * @return value of SFErrCodes type
+ */
+ SFErrCodes StreamToMemory(TrueTypeCreator *_this, sal_uInt8 **ptr, sal_uInt32 *length);
+
+/**
+ * Writes a TrueType font generated by the TrueTypeCreator to a file
+ * @return value of SFErrCodes type
+ */
+ SFErrCodes StreamToFile(TrueTypeCreator *_this, const char* fname);
+
+/**
+ * This function converts the data of a TrueType table to a raw array of bytes.
+ * It may allocates the memory for it and returns the size of the raw data in bytes.
+ * If memory is allocated it does not need to be freed by the caller of this function,
+ * since the pointer to it is stored in the TrueTypeTable and it is freed by the destructor
+ * @return TTCRErrCode
+ *
+ */
+
+ int GetRawData(TrueTypeTable *, sal_uInt8 **ptr, sal_uInt32 *len, sal_uInt32 *tag);
+
+/**
+ *
+ * Creates a new raw TrueType table. The difference between this constructor and
+ * TrueTypeTableNew_tag constructors is that the latter create structured tables
+ * while this constructor just copies memory pointed to by ptr to its buffer
+ * and stores its length. This constructor is suitable for data that is not
+ * supposed to be processed in any way, just written to the resulting TTF file.
+ */
+ TrueTypeTable *TrueTypeTableNew(sal_uInt32 tag,
+ sal_uInt32 nbytes,
+ const sal_uInt8* ptr);
+
+/**
+ * Creates a new 'head' table for a TrueType font.
+ * Allocates memory for it. Since a lot of values in the 'head' table depend on the
+ * rest of the tables in the TrueType font this table should be the last one added
+ * to the font.
+ */
+ TrueTypeTable *TrueTypeTableNew_head(sal_uInt32 fontRevision,
+ sal_uInt16 flags,
+ sal_uInt16 unitsPerEm,
+ const sal_uInt8 *created,
+ sal_uInt16 macStyle,
+ sal_uInt16 lowestRecPPEM,
+ sal_Int16 fontDirectionHint);
+
+/**
+ * Creates a new 'hhea' table for a TrueType font.
+ * Allocates memory for it and stores it in the hhea pointer.
+ */
+ TrueTypeTable *TrueTypeTableNew_hhea(sal_Int16 ascender,
+ sal_Int16 descender,
+ sal_Int16 linegap,
+ sal_Int16 caretSlopeRise,
+ sal_Int16 caretSlopeRun);
+
+/**
+ * Creates a new empty 'loca' table for a TrueType font.
+ *
+ * INTERNAL: gets called only from ProcessTables();
+ */
+ TrueTypeTable *TrueTypeTableNew_loca();
+
+/**
+ * Creates a new 'maxp' table based on an existing maxp table.
+ * If maxp is 0, a new empty maxp table is created
+ * size specifies the size of existing maxp table for
+ * error-checking purposes
+ */
+ TrueTypeTable *TrueTypeTableNew_maxp( const sal_uInt8* maxp, int size);
+
+/**
+ * Creates a new empty 'glyf' table.
+ */
+ TrueTypeTable *TrueTypeTableNew_glyf();
+
+/**
+ * Creates a new empty 'cmap' table.
+ */
+ TrueTypeTable *TrueTypeTableNew_cmap();
+
+/**
+ * Creates a new 'name' table. If n != 0 the table gets populated by
+ * the Name Records stored in the nr array. This function allocates
+ * memory for its own copy of NameRecords, so nr array has to
+ * be explicitly deallocated when it is not needed.
+ */
+ TrueTypeTable *TrueTypeTableNew_name(int n, NameRecord const *nr);
+
+/**
+ * Creates a new 'post' table of one of the supported formats
+ */
+ TrueTypeTable *TrueTypeTableNew_post(sal_Int32 format,
+ sal_Int32 italicAngle,
+ sal_Int16 underlinePosition,
+ sal_Int16 underlineThickness,
+ sal_uInt32 isFixedPitch);
+
+// Table manipulation functions
+
+/**
+ * Add a character/glyph pair to a cmap table
+ */
+ void cmapAdd(TrueTypeTable *, sal_uInt32 id, sal_uInt32 c, sal_uInt32 g);
+
+/**
+ * Add a glyph to a glyf table.
+ *
+ * @return glyphID of the glyph in the new font
+ *
+ * NOTE: This function does not duplicate GlyphData, so memory will be
+ * deallocated in the table destructor
+ */
+ sal_uInt32 glyfAdd(TrueTypeTable *, GlyphData *glyphdata, AbstractTrueTypeFont *fnt);
+
+/**
+ * Query the number of glyphs currently stored in the 'glyf' table
+ *
+ */
+ sal_uInt32 glyfCount(const TrueTypeTable *);
+
+} // namespace
+
+extern "C"
+{
+/**
+ * Destructor for the TrueTypeTable object.
+ */
+ void TrueTypeTableDispose(void *);
+
+/**
+ * TrueTypeCreator destructor. It calls destructors for all TrueTypeTables added to it.
+ */
+ void TrueTypeCreatorDispose(vcl::TrueTypeCreator *_this);
+}
+
+#endif // INCLUDED_VCL_SOURCE_FONTSUBSET_TTCR_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/xlat.cxx b/vcl/source/fontsubset/xlat.cxx
new file mode 100644
index 000000000..b7966f279
--- /dev/null
+++ b/vcl/source/fontsubset/xlat.cxx
@@ -0,0 +1,144 @@
+/* -*- 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 <rtl/textcvt.h>
+#include "xlat.hxx"
+
+namespace {
+
+#define MAX_CVT_SELECT 6
+
+class ConverterCache
+{
+public:
+ explicit ConverterCache();
+ ~ConverterCache();
+ sal_uInt16 convertOne( int nSelect, sal_Unicode );
+private:
+ void ensureConverter( int nSelect );
+ rtl_UnicodeToTextConverter maConverterCache[ MAX_CVT_SELECT+1 ];
+ rtl_UnicodeToTextContext maContexts[ MAX_CVT_SELECT+1 ];
+};
+
+ConverterCache::ConverterCache()
+{
+ for( int i = 0; i <= MAX_CVT_SELECT; ++i)
+ {
+ maConverterCache[i] = nullptr;
+ maContexts[i] = nullptr;
+ }
+}
+
+ConverterCache::~ConverterCache()
+{
+ for( int i = 0; i <= MAX_CVT_SELECT; ++i)
+ {
+ if( !maContexts[i] )
+ continue;
+ rtl_destroyUnicodeToTextContext( maConverterCache[i], maContexts[i] );
+ rtl_destroyUnicodeToTextConverter( maConverterCache[i] );
+ }
+}
+
+void ConverterCache::ensureConverter( int nSelect )
+{
+ // SAL_WARN_IF( (2>nSelect) || (nSelect>MAX_CVT_SELECT)), "vcl", "invalid XLAT.Converter requested" );
+ rtl_UnicodeToTextContext aContext = maContexts[ nSelect ];
+ if( !aContext )
+ {
+ rtl_TextEncoding eRecodeFrom = RTL_TEXTENCODING_UNICODE;
+ switch( nSelect )
+ {
+ default: nSelect = 1; [[fallthrough]]; // to unicode recoding
+ case 1: eRecodeFrom = RTL_TEXTENCODING_UNICODE; break;
+ case 2: eRecodeFrom = RTL_TEXTENCODING_SHIFT_JIS; break;
+ case 3: eRecodeFrom = RTL_TEXTENCODING_GB_2312; break;
+ case 4: eRecodeFrom = RTL_TEXTENCODING_BIG5; break;
+ case 5: eRecodeFrom = RTL_TEXTENCODING_MS_949; break;
+ case 6: eRecodeFrom = RTL_TEXTENCODING_MS_1361; break;
+ }
+ rtl_UnicodeToTextConverter aRecodeConverter = rtl_createUnicodeToTextConverter( eRecodeFrom );
+ maConverterCache[ nSelect ] = aRecodeConverter;
+
+ aContext = rtl_createUnicodeToTextContext( aRecodeConverter );
+ maContexts[ nSelect ] = aContext;
+ }
+
+ rtl_resetUnicodeToTextContext( maConverterCache[ nSelect ], aContext );
+}
+
+sal_uInt16 ConverterCache::convertOne( int nSelect, sal_Unicode aChar )
+{
+ ensureConverter( nSelect );
+
+ sal_Unicode aUCS2Char = aChar;
+ char aTempArray[8];
+ sal_Size nTempSize;
+ sal_uInt32 nCvtInfo;
+
+ // TODO: use direct unicode->mbcs converter should there ever be one
+ int nCodeLen = rtl_convertUnicodeToText(
+ maConverterCache[ nSelect ], maContexts[ nSelect ],
+ &aUCS2Char, 1, aTempArray, sizeof(aTempArray),
+ RTL_UNICODETOTEXT_FLAGS_UNDEFINED_0
+ | RTL_UNICODETOTEXT_FLAGS_INVALID_0,
+ &nCvtInfo, &nTempSize );
+
+ sal_uInt16 aCode = aTempArray[0];
+ for( int i = 1; i < nCodeLen; ++i )
+ aCode = (aCode << 8) + (aTempArray[i] & 0xFF);
+ return aCode;
+}
+
+} // anonymous namespace
+
+namespace vcl
+{
+
+static ConverterCache aCC;
+
+sal_uInt16 TranslateChar12(sal_uInt16 src)
+{
+ return aCC.convertOne( 2, src);
+}
+
+sal_uInt16 TranslateChar13(sal_uInt16 src)
+{
+ return aCC.convertOne( 3, src);
+}
+
+sal_uInt16 TranslateChar14(sal_uInt16 src)
+{
+ return aCC.convertOne( 4, src);
+}
+
+sal_uInt16 TranslateChar15(sal_uInt16 src)
+{
+ return aCC.convertOne( 5, src);
+}
+
+sal_uInt16 TranslateChar16(sal_uInt16 src)
+{
+ return aCC.convertOne( 6, src);
+}
+
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/xlat.hxx b/vcl/source/fontsubset/xlat.hxx
new file mode 100644
index 000000000..61e8534b1
--- /dev/null
+++ b/vcl/source/fontsubset/xlat.hxx
@@ -0,0 +1,40 @@
+/* -*- 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 .
+ */
+
+/*| Author: Alexander Gelfenbain |*/
+
+#ifndef INCLUDED_VCL_SOURCE_FONTSUBSET_XLAT_HXX
+#define INCLUDED_VCL_SOURCE_FONTSUBSET_XLAT_HXX
+
+#include <sal/types.h>
+
+namespace vcl
+{
+// TODO: sal_UCS4
+
+sal_uInt16 TranslateChar12(sal_uInt16);
+sal_uInt16 TranslateChar13(sal_uInt16);
+sal_uInt16 TranslateChar14(sal_uInt16);
+sal_uInt16 TranslateChar15(sal_uInt16);
+sal_uInt16 TranslateChar16(sal_uInt16);
+}
+
+#endif // INCLUDED_VCL_SOURCE_FONTSUBSET_XLAT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/CommonSalLayout.cxx b/vcl/source/gdi/CommonSalLayout.cxx
new file mode 100644
index 000000000..7e2bcd629
--- /dev/null
+++ b/vcl/source/gdi/CommonSalLayout.cxx
@@ -0,0 +1,861 @@
+/* -*- 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 <sal/log.hxx>
+#include <unotools/configmgr.hxx>
+#include <o3tl/temporary.hxx>
+
+#include <vcl/unohelp.hxx>
+#include <vcl/font/Feature.hxx>
+#include <vcl/font/FeatureParser.hxx>
+
+#include <ImplLayoutArgs.hxx>
+#include <TextLayoutCache.hxx>
+#include <font/FontSelectPattern.hxx>
+#include <salgdi.hxx>
+#include <sallayout.hxx>
+
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+
+#include <unicode/uchar.h>
+#include <hb-ot.h>
+#include <hb-graphite2.h>
+
+#include <memory>
+
+#if !HB_VERSION_ATLEAST(1, 1, 0)
+// Disabled Unicode compatibility decomposition, see fdo#66715
+static unsigned int unicodeDecomposeCompatibility(hb_unicode_funcs_t* /*ufuncs*/,
+ hb_codepoint_t /*u*/,
+ hb_codepoint_t* /*decomposed*/,
+ void* /*user_data*/)
+{
+ return 0;
+}
+
+static hb_unicode_funcs_t* getUnicodeFuncs()
+{
+ static hb_unicode_funcs_t* ufuncs = hb_unicode_funcs_create(hb_icu_get_unicode_funcs());
+ hb_unicode_funcs_set_decompose_compatibility_func(ufuncs, unicodeDecomposeCompatibility, nullptr, nullptr);
+ return ufuncs;
+}
+#endif
+
+GenericSalLayout::GenericSalLayout(LogicalFontInstance &rFont)
+ : m_GlyphItems(rFont)
+ , mpVertGlyphs(nullptr)
+ , mbFuzzing(utl::ConfigManager::IsFuzzing())
+{
+}
+
+GenericSalLayout::~GenericSalLayout()
+{
+}
+
+void GenericSalLayout::ParseFeatures(std::u16string_view aName)
+{
+ vcl::font::FeatureParser aParser(aName);
+ const OUString& sLanguage = aParser.getLanguage();
+ if (!sLanguage.isEmpty())
+ msLanguage = OUStringToOString(sLanguage, RTL_TEXTENCODING_ASCII_US);
+
+ for (auto const &rFeat : aParser.getFeatures())
+ {
+ hb_feature_t aFeature { rFeat.m_nTag, rFeat.m_nValue, rFeat.m_nStart, rFeat.m_nEnd };
+ maFeatures.push_back(aFeature);
+ }
+}
+
+namespace {
+
+struct SubRun
+{
+ int32_t mnMin;
+ int32_t mnEnd;
+ hb_script_t maScript;
+ hb_direction_t maDirection;
+};
+
+}
+
+namespace {
+#if U_ICU_VERSION_MAJOR_NUM >= 63
+ enum class VerticalOrientation {
+ Upright = U_VO_UPRIGHT,
+ Rotated = U_VO_ROTATED,
+ TransformedUpright = U_VO_TRANSFORMED_UPRIGHT,
+ TransformedRotated = U_VO_TRANSFORMED_ROTATED
+ };
+#else
+ #include "VerticalOrientationData.cxx"
+
+ // These must match the values in the file included above.
+ enum class VerticalOrientation {
+ Upright = 0,
+ Rotated = 1,
+ TransformedUpright = 2,
+ TransformedRotated = 3
+ };
+#endif
+
+ VerticalOrientation GetVerticalOrientation(sal_UCS4 cCh, const LanguageTag& rTag)
+ {
+ // Override orientation of fullwidth colon , semi-colon,
+ // and Bopomofo tonal marks.
+ if ((cCh == 0xff1a || cCh == 0xff1b
+ || cCh == 0x2ca || cCh == 0x2cb || cCh == 0x2c7 || cCh == 0x2d9)
+ && rTag.getLanguage() == "zh")
+ return VerticalOrientation::TransformedUpright;
+
+#if U_ICU_VERSION_MAJOR_NUM >= 63
+ int32_t nRet = u_getIntPropertyValue(cCh, UCHAR_VERTICAL_ORIENTATION);
+#else
+ uint8_t nRet = 1;
+
+ if (cCh < 0x10000)
+ {
+ nRet = sVerticalOrientationValues[sVerticalOrientationPages[0][cCh >> kVerticalOrientationCharBits]]
+ [cCh & ((1 << kVerticalOrientationCharBits) - 1)];
+ }
+ else if (cCh < (kVerticalOrientationMaxPlane + 1) * 0x10000)
+ {
+ nRet = sVerticalOrientationValues[sVerticalOrientationPages[sVerticalOrientationPlanes[(cCh >> 16) - 1]]
+ [(cCh & 0xffff) >> kVerticalOrientationCharBits]]
+ [cCh & ((1 << kVerticalOrientationCharBits) - 1)];
+ }
+ else
+ {
+ // Default value for unassigned
+ SAL_WARN("vcl.gdi", "Getting VerticalOrientation for codepoint outside Unicode range");
+ }
+#endif
+
+ return VerticalOrientation(nRet);
+ }
+
+} // namespace
+
+SalLayoutGlyphs GenericSalLayout::GetGlyphs() const
+{
+ SalLayoutGlyphs glyphs;
+ glyphs.AppendImpl(m_GlyphItems.clone());
+ return glyphs;
+}
+
+void GenericSalLayout::SetNeedFallback(vcl::text::ImplLayoutArgs& rArgs, sal_Int32 nCharPos, bool bRightToLeft)
+{
+ if (nCharPos < 0 || mbFuzzing)
+ return;
+
+ using namespace ::com::sun::star;
+
+ if (!mxBreak.is())
+ mxBreak = vcl::unohelper::CreateBreakIterator();
+
+ lang::Locale aLocale(rArgs.maLanguageTag.getLocale());
+
+ //if position nCharPos is missing in the font, grab the entire grapheme and
+ //mark all glyphs as missing so the whole thing is rendered with the same
+ //font
+ sal_Int32 nDone;
+ int nGraphemeEndPos =
+ mxBreak->nextCharacters(rArgs.mrStr, nCharPos, aLocale,
+ i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
+ // Safely advance nCharPos in case it is a non-BMP character.
+ rArgs.mrStr.iterateCodePoints(&nCharPos);
+ int nGraphemeStartPos =
+ mxBreak->previousCharacters(rArgs.mrStr, nCharPos, aLocale,
+ i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
+
+ //stay inside the Layout range (e.g. with tdf124116-1.odt)
+ nGraphemeStartPos = std::max(rArgs.mnMinCharPos, nGraphemeStartPos);
+ nGraphemeEndPos = std::min(rArgs.mnEndCharPos, nGraphemeEndPos);
+
+ rArgs.AddFallbackRun(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft);
+}
+
+void GenericSalLayout::AdjustLayout(vcl::text::ImplLayoutArgs& rArgs)
+{
+ SalLayout::AdjustLayout(rArgs);
+
+ if (rArgs.mpAltNaturalDXArray) // Used when "TextRenderModeForResolutionIndependentLayout" is set
+ ApplyDXArray(rArgs.mpAltNaturalDXArray, rArgs.mnFlags);
+ else if (rArgs.mpDXArray) // Normal case
+ ApplyDXArray(rArgs.mpDXArray, rArgs.mnFlags);
+ else if (rArgs.mnLayoutWidth)
+ Justify(rArgs.mnLayoutWidth);
+ // apply asian kerning if the glyphs are not already formatted
+ else if ((rArgs.mnFlags & SalLayoutFlags::KerningAsian)
+ && !(rArgs.mnFlags & SalLayoutFlags::Vertical))
+ ApplyAsianKerning(rArgs.mrStr);
+}
+
+void GenericSalLayout::DrawText(SalGraphics& rSalGraphics) const
+{
+ //call platform dependent DrawText functions
+ rSalGraphics.DrawTextLayout( *this );
+}
+
+// Find if the nominal glyph of the character is an input to “vert” feature.
+// We don’t check for a specific script or language as it shouldn’t matter
+// here; if the glyph would be the result from applying “vert” for any
+// script/language then we want to always treat it as upright glyph.
+bool GenericSalLayout::HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aVariationSelector)
+{
+ hb_codepoint_t nGlyphIndex = 0;
+ hb_font_t *pHbFont = GetFont().GetHbFont();
+ if (!hb_font_get_glyph(pHbFont, aChar, aVariationSelector, &nGlyphIndex))
+ return false;
+
+ if (!mpVertGlyphs)
+ {
+ hb_face_t* pHbFace = hb_font_get_face(pHbFont);
+ mpVertGlyphs = hb_set_create();
+
+ // Find all GSUB lookups for “vert” feature.
+ hb_set_t* pLookups = hb_set_create();
+ hb_tag_t const pFeatures[] = { HB_TAG('v','e','r','t'), HB_TAG_NONE };
+ hb_ot_layout_collect_lookups(pHbFace, HB_OT_TAG_GSUB, nullptr, nullptr, pFeatures, pLookups);
+ if (!hb_set_is_empty(pLookups))
+ {
+ // Find the output glyphs in each lookup (i.e. the glyphs that
+ // would result from applying this lookup).
+ hb_codepoint_t nIdx = HB_SET_VALUE_INVALID;
+ while (hb_set_next(pLookups, &nIdx))
+ {
+ hb_set_t* pGlyphs = hb_set_create();
+ hb_ot_layout_lookup_collect_glyphs(pHbFace, HB_OT_TAG_GSUB, nIdx,
+ nullptr, // glyphs before
+ pGlyphs, // glyphs input
+ nullptr, // glyphs after
+ nullptr); // glyphs out
+ hb_set_union(mpVertGlyphs, pGlyphs);
+ }
+ }
+ }
+
+ return hb_set_has(mpVertGlyphs, nGlyphIndex) != 0;
+}
+
+bool GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLayoutGlyphsImpl* pGlyphs)
+{
+ // No need to touch m_GlyphItems at all for an empty string.
+ if (rArgs.mnEndCharPos - rArgs.mnMinCharPos <= 0)
+ return true;
+
+ if (pGlyphs)
+ {
+ // Work with pre-computed glyph items.
+ m_GlyphItems = *pGlyphs;
+ for(const GlyphItem& item : m_GlyphItems)
+ if(!item.glyphId())
+ SetNeedFallback(rArgs, item.charPos(), item.IsRTLGlyph());
+ // Some flags are set as a side effect of text layout, restore them here.
+ rArgs.mnFlags |= pGlyphs->GetFlags();
+ return true;
+ }
+
+ hb_font_t *pHbFont = GetFont().GetHbFont();
+ bool isGraphite = GetFont().IsGraphiteFont();
+
+ int nGlyphCapacity = 2 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos);
+ m_GlyphItems.reserve(nGlyphCapacity);
+
+ const int nLength = rArgs.mrStr.getLength();
+ const sal_Unicode *pStr = rArgs.mrStr.getStr();
+
+ std::optional<vcl::text::TextLayoutCache> oNewScriptRun;
+ vcl::text::TextLayoutCache const* pTextLayout;
+ if (rArgs.m_pTextLayoutCache)
+ {
+ pTextLayout = rArgs.m_pTextLayoutCache; // use cache!
+ }
+ else
+ {
+ oNewScriptRun.emplace(pStr, rArgs.mnEndCharPos);
+ pTextLayout = &*oNewScriptRun;
+ }
+
+ // nBaseOffset is used to align vertical text to the center of rotated
+ // horizontal text. That is the offset from original baseline to
+ // the center of EM box. Maybe we can use OpenType base table to improve this
+ // in the future.
+ DeviceCoordinate nBaseOffset = 0;
+ if (rArgs.mnFlags & SalLayoutFlags::Vertical)
+ {
+ hb_font_extents_t extents;
+ if (hb_font_get_h_extents(pHbFont, &extents))
+ nBaseOffset = ( extents.ascender + extents.descender ) / 2;
+ }
+
+ hb_buffer_t* pHbBuffer = hb_buffer_create();
+ hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity);
+#if !HB_VERSION_ATLEAST(1, 1, 0)
+ static hb_unicode_funcs_t* pHbUnicodeFuncs = getUnicodeFuncs();
+ hb_buffer_set_unicode_funcs(pHbBuffer, pHbUnicodeFuncs);
+#endif
+
+ const vcl::font::FontSelectPattern& rFontSelData = GetFont().GetFontSelectPattern();
+ if (rArgs.mnFlags & SalLayoutFlags::DisableKerning)
+ {
+ SAL_INFO("vcl.harfbuzz", "Disabling kerning for font: " << rFontSelData.maTargetName);
+ maFeatures.push_back({ HB_TAG('k','e','r','n'), 0, 0, static_cast<unsigned int>(-1) });
+ }
+
+ if (rFontSelData.GetPitch() == PITCH_FIXED)
+ {
+ SAL_INFO("vcl.harfbuzz", "Disabling ligatures for font: " << rFontSelData.maTargetName);
+ maFeatures.push_back({ HB_TAG('l','i','g','a'), 0, 0, static_cast<unsigned int>(-1) });
+ }
+
+ ParseFeatures(rFontSelData.maTargetName);
+
+ double nXScale = 0;
+ double nYScale = 0;
+ GetFont().GetScale(&nXScale, &nYScale);
+
+ DevicePoint aCurrPos(0, 0);
+ while (true)
+ {
+ int nBidiMinRunPos, nBidiEndRunPos;
+ bool bRightToLeft;
+ if (!rArgs.GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft))
+ break;
+
+ // Find script subruns.
+ std::vector<SubRun> aSubRuns;
+ int nCurrentPos = nBidiMinRunPos;
+ size_t k = 0;
+ for (; k < pTextLayout->runs.size(); ++k)
+ {
+ vcl::text::Run const& rRun(pTextLayout->runs[k]);
+ if (rRun.nStart <= nCurrentPos && nCurrentPos < rRun.nEnd)
+ {
+ break;
+ }
+ }
+
+ if (isGraphite)
+ {
+ hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
+ aSubRuns.push_back({ nBidiMinRunPos, nBidiEndRunPos, aScript, bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR });
+ }
+ else
+ {
+ while (nCurrentPos < nBidiEndRunPos && k < pTextLayout->runs.size())
+ {
+ int32_t nMinRunPos = nCurrentPos;
+ int32_t nEndRunPos = std::min(pTextLayout->runs[k].nEnd, nBidiEndRunPos);
+ hb_direction_t aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
+ hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
+ // For vertical text, further divide the runs based on character
+ // orientation.
+ if (rArgs.mnFlags & SalLayoutFlags::Vertical)
+ {
+ sal_Int32 nIdx = nMinRunPos;
+ while (nIdx < nEndRunPos)
+ {
+ sal_Int32 nPrevIdx = nIdx;
+ sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&nIdx);
+ VerticalOrientation aVo = GetVerticalOrientation(aChar, rArgs.maLanguageTag);
+
+ sal_UCS4 aVariationSelector = 0;
+ if (nIdx < nEndRunPos)
+ {
+ sal_Int32 nNextIdx = nIdx;
+ sal_UCS4 aNextChar = rArgs.mrStr.iterateCodePoints(&nNextIdx);
+ if (u_hasBinaryProperty(aNextChar, UCHAR_VARIATION_SELECTOR))
+ {
+ nIdx = nNextIdx;
+ aVariationSelector = aNextChar;
+ }
+ }
+
+ // Characters with U and Tu vertical orientation should
+ // be shaped in vertical direction. But characters
+ // with Tr should be shaped in vertical direction
+ // only if they have vertical alternates, otherwise
+ // they should be shaped in horizontal direction
+ // and then rotated.
+ // See http://unicode.org/reports/tr50/#vo
+ if (aVo == VerticalOrientation::Upright ||
+ aVo == VerticalOrientation::TransformedUpright ||
+ (aVo == VerticalOrientation::TransformedRotated &&
+ HasVerticalAlternate(aChar, aVariationSelector)))
+ {
+ aDirection = HB_DIRECTION_TTB;
+ }
+ else
+ {
+ aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
+ }
+
+ if (aSubRuns.empty() || aSubRuns.back().maDirection != aDirection)
+ aSubRuns.push_back({ nPrevIdx, nIdx, aScript, aDirection });
+ else
+ aSubRuns.back().mnEnd = nIdx;
+ }
+ }
+ else
+ {
+ aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript, aDirection });
+ }
+
+ nCurrentPos = nEndRunPos;
+ ++k;
+ }
+ }
+
+ // RTL subruns should be reversed to ensure that final glyph order is
+ // correct.
+ if (bRightToLeft)
+ std::reverse(aSubRuns.begin(), aSubRuns.end());
+
+ for (const auto& aSubRun : aSubRuns)
+ {
+ hb_buffer_clear_contents(pHbBuffer);
+
+ const int nMinRunPos = aSubRun.mnMin;
+ const int nEndRunPos = aSubRun.mnEnd;
+ const int nRunLen = nEndRunPos - nMinRunPos;
+
+ int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
+ if (nMinRunPos == 0)
+ nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */
+ if (nEndRunPos == nLength)
+ nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */
+
+ hb_buffer_set_direction(pHbBuffer, aSubRun.maDirection);
+ hb_buffer_set_script(pHbBuffer, aSubRun.maScript);
+ if (!msLanguage.isEmpty())
+ {
+ hb_buffer_set_language(pHbBuffer, hb_language_from_string(msLanguage.getStr(), msLanguage.getLength()));
+ }
+ else
+ {
+ OString sLanguage = OUStringToOString(rArgs.maLanguageTag.getBcp47(), RTL_TEXTENCODING_ASCII_US);
+ hb_buffer_set_language(pHbBuffer, hb_language_from_string(sLanguage.getStr(), sLanguage.getLength()));
+ }
+ hb_buffer_set_flags(pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags));
+ hb_buffer_add_utf16(
+ pHbBuffer, reinterpret_cast<uint16_t const *>(pStr), nLength,
+ nMinRunPos, nRunLen);
+ hb_buffer_set_cluster_level(pHbBuffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
+
+ // The shapers that we want HarfBuzz to use, in the order of
+ // preference. The coretext_aat shaper is available only on macOS,
+ // but there is no harm in always including it, HarfBuzz will
+ // ignore unavailable shapers.
+ const char*const pHbShapers[] = { "graphite2", "coretext_aat", "ot", "fallback", nullptr };
+ bool ok = hb_shape_full(pHbFont, pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers);
+ assert(ok);
+ (void) ok;
+
+ int nRunGlyphCount = hb_buffer_get_length(pHbBuffer);
+ hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer, nullptr);
+ hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer, nullptr);
+
+ for (int i = 0; i < nRunGlyphCount; ++i) {
+ int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint;
+ int32_t nCharPos = pHbGlyphInfos[i].cluster;
+ int32_t nCharCount = 0;
+ bool bInCluster = false;
+ bool bClusterStart = false;
+
+ // Find the number of characters that make up this glyph.
+ if (!bRightToLeft)
+ {
+ // If the cluster is the same as previous glyph, then this
+ // already consumed, skip.
+ if (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster)
+ {
+ nCharCount = 0;
+ bInCluster = true;
+ }
+ else
+ {
+ // Find the next glyph with a different cluster, or the
+ // end of text.
+ int j = i;
+ int32_t nNextCharPos = nCharPos;
+ while (nNextCharPos == nCharPos && j < nRunGlyphCount)
+ nNextCharPos = pHbGlyphInfos[j++].cluster;
+
+ if (nNextCharPos == nCharPos)
+ nNextCharPos = nEndRunPos;
+ nCharCount = nNextCharPos - nCharPos;
+ if ((i == 0 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i - 1].cluster) &&
+ (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster))
+ bClusterStart = true;
+ }
+ }
+ else
+ {
+ // If the cluster is the same as previous glyph, then this
+ // will be consumed later, skip.
+ if (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster)
+ {
+ nCharCount = 0;
+ bInCluster = true;
+ }
+ else
+ {
+ // Find the previous glyph with a different cluster, or
+ // the end of text.
+ int j = i;
+ int32_t nNextCharPos = nCharPos;
+ while (nNextCharPos == nCharPos && j >= 0)
+ nNextCharPos = pHbGlyphInfos[j--].cluster;
+
+ if (nNextCharPos == nCharPos)
+ nNextCharPos = nEndRunPos;
+ nCharCount = nNextCharPos - nCharPos;
+ if ((i == nRunGlyphCount - 1 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i + 1].cluster) &&
+ (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster))
+ bClusterStart = true;
+ }
+ }
+
+ // if needed request glyph fallback by updating LayoutArgs
+ if (!nGlyphIndex)
+ {
+ SetNeedFallback(rArgs, nCharPos, bRightToLeft);
+ if (SalLayoutFlags::ForFallback & rArgs.mnFlags)
+ continue;
+ }
+
+ GlyphItemFlags nGlyphFlags = GlyphItemFlags::NONE;
+ if (bRightToLeft)
+ nGlyphFlags |= GlyphItemFlags::IS_RTL_GLYPH;
+
+ if (bClusterStart)
+ nGlyphFlags |= GlyphItemFlags::IS_CLUSTER_START;
+
+ if (bInCluster)
+ nGlyphFlags |= GlyphItemFlags::IS_IN_CLUSTER;
+
+ sal_UCS4 aChar
+ = rArgs.mrStr.iterateCodePoints(&o3tl::temporary(sal_Int32(nCharPos)), 0);
+
+ if (u_getIntPropertyValue(aChar, UCHAR_GENERAL_CATEGORY) == U_NON_SPACING_MARK)
+ nGlyphFlags |= GlyphItemFlags::IS_DIACRITIC;
+
+ if (u_isUWhiteSpace(aChar))
+ nGlyphFlags |= GlyphItemFlags::IS_SPACING;
+
+ if (aSubRun.maScript == HB_SCRIPT_ARABIC &&
+ HB_DIRECTION_IS_BACKWARD(aSubRun.maDirection) &&
+ !(nGlyphFlags & GlyphItemFlags::IS_SPACING))
+ {
+ nGlyphFlags |= GlyphItemFlags::ALLOW_KASHIDA;
+ rArgs.mnFlags |= SalLayoutFlags::KashidaJustification;
+ }
+
+#if HB_VERSION_ATLEAST(1, 5, 0)
+ if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) & HB_GLYPH_FLAG_UNSAFE_TO_BREAK)
+ nGlyphFlags |= GlyphItemFlags::IS_UNSAFE_TO_BREAK;
+#else
+ // If support is not present, then always prevent breaking.
+ nGlyphFlags |= GlyphItemFlags::IS_UNSAFE_TO_BREAK;
+#endif
+
+ DeviceCoordinate nAdvance, nXOffset, nYOffset;
+ if (aSubRun.maDirection == HB_DIRECTION_TTB)
+ {
+ nGlyphFlags |= GlyphItemFlags::IS_VERTICAL;
+
+ nAdvance = -pHbPositions[i].y_advance;
+ nXOffset = -pHbPositions[i].y_offset;
+ nYOffset = -pHbPositions[i].x_offset - nBaseOffset;
+
+ if (GetFont().NeedOffsetCorrection(pHbPositions[i].y_offset))
+ {
+ // We need glyph's advance, top bearing, and height to
+ // correct y offset.
+ tools::Rectangle aRect;
+ // Get cached bound rect value for the font,
+ GetFont().GetGlyphBoundRect(nGlyphIndex, aRect, true);
+
+ nXOffset = -(aRect.Top() / nXScale + ( pHbPositions[i].y_advance
+ + ( aRect.GetHeight() / nXScale ) ) / 2 );
+ }
+
+ }
+ else
+ {
+ nAdvance = pHbPositions[i].x_advance;
+ nXOffset = pHbPositions[i].x_offset;
+ nYOffset = -pHbPositions[i].y_offset;
+ }
+
+ nAdvance = std::lround(nAdvance * nXScale);
+ nXOffset = std::lround(nXOffset * nXScale);
+ nYOffset = std::lround(nYOffset * nYScale);
+
+ DevicePoint aNewPos(aCurrPos.getX() + nXOffset, aCurrPos.getY() + nYOffset);
+ const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags,
+ nAdvance, nXOffset, nYOffset);
+ m_GlyphItems.push_back(aGI);
+
+ aCurrPos.adjustX(nAdvance);
+ }
+ }
+ }
+
+ hb_buffer_destroy(pHbBuffer);
+
+ // Some flags are set as a side effect of text layout, save them here.
+ if (rArgs.mnFlags & SalLayoutFlags::GlyphItemsOnly)
+ m_GlyphItems.SetFlags(rArgs.mnFlags);
+
+ return true;
+}
+
+void GenericSalLayout::GetCharWidths(std::vector<DeviceCoordinate>& rCharWidths) const
+{
+ const int nCharCount = mnEndCharPos - mnMinCharPos;
+
+ rCharWidths.clear();
+ rCharWidths.resize(nCharCount, 0);
+
+ for (auto const& aGlyphItem : m_GlyphItems)
+ {
+ const int nIndex = aGlyphItem.charPos() - mnMinCharPos;
+ if (nIndex >= nCharCount)
+ continue;
+ rCharWidths[nIndex] += aGlyphItem.newWidth();
+ }
+}
+
+// A note on how Kashida justification is implemented (because it took me 5
+// years to figure it out):
+// The decision to insert Kashidas, where and how much is taken by Writer.
+// This decision is communicated to us in a very indirect way; by increasing
+// the width of the character after which Kashidas should be inserted by the
+// desired amount.
+//
+// Writer eventually calls IsKashidaPosValid() to check whether it can insert a
+// Kashida between two characters or not.
+//
+// Here we do:
+// - In LayoutText() set KashidaJustification flag based on text script.
+// - In ApplyDXArray():
+// * Check the above flag to decide whether to insert Kashidas or not.
+// * For any RTL glyph that has DX adjustment, insert enough Kashidas to
+// fill in the added space.
+template<typename DC>
+void GenericSalLayout::ApplyDXArray(const DC* pDXArray, SalLayoutFlags nLayoutFlags)
+{
+ int nCharCount = mnEndCharPos - mnMinCharPos;
+ std::vector<DeviceCoordinate> aOldCharWidths;
+ std::unique_ptr<DC[]> const pNewCharWidths(new DC[nCharCount]);
+
+ // Get the natural character widths (i.e. before applying DX adjustments).
+ GetCharWidths(aOldCharWidths);
+
+ // Calculate the character widths after DX adjustments.
+ for (int i = 0; i < nCharCount; ++i)
+ {
+ if (i == 0)
+ pNewCharWidths[i] = pDXArray[i];
+ else
+ pNewCharWidths[i] = pDXArray[i] - pDXArray[i - 1];
+ }
+
+ bool bKashidaJustify = false;
+ DeviceCoordinate nKashidaWidth = 0;
+ hb_codepoint_t nKashidaIndex = 0;
+ if (nLayoutFlags & SalLayoutFlags::KashidaJustification)
+ {
+ hb_font_t *pHbFont = GetFont().GetHbFont();
+ // Find Kashida glyph width and index.
+ if (hb_font_get_glyph(pHbFont, 0x0640, 0, &nKashidaIndex))
+ nKashidaWidth = GetFont().GetKashidaWidth();
+ bKashidaJustify = nKashidaWidth != 0;
+ }
+
+ // Map of Kashida insertion points (in the glyph items vector) and the
+ // requested width.
+ std::map<size_t, DeviceCoordinate> pKashidas;
+
+ // The accumulated difference in X position.
+ DC nDelta = 0;
+
+ // Apply the DX adjustments to glyph positions and widths.
+ size_t i = 0;
+ while (i < m_GlyphItems.size())
+ {
+ // Accumulate the width difference for all characters corresponding to
+ // this glyph.
+ int nCharPos = m_GlyphItems[i].charPos() - mnMinCharPos;
+ DC nDiff = 0;
+ for (int j = 0; j < m_GlyphItems[i].charCount(); j++)
+ nDiff += pNewCharWidths[nCharPos + j] - aOldCharWidths[nCharPos + j];
+
+ if (!m_GlyphItems[i].IsRTLGlyph())
+ {
+ // Adjust the width and position of the first (leftmost) glyph in
+ // the cluster.
+ m_GlyphItems[i].addNewWidth(nDiff);
+ m_GlyphItems[i].adjustLinearPosX(nDelta);
+
+ // Adjust the position of the rest of the glyphs in the cluster.
+ while (++i < m_GlyphItems.size())
+ {
+ if (!m_GlyphItems[i].IsInCluster())
+ break;
+ m_GlyphItems[i].adjustLinearPosX(nDelta);
+ }
+ }
+ else if (m_GlyphItems[i].IsInCluster())
+ {
+ // RTL glyph in the middle of the cluster, will be handled in the
+ // loop below.
+ i++;
+ }
+ else
+ {
+ // Adjust the width and position of the first (rightmost) glyph in
+ // the cluster.
+ // For RTL, we put all the adjustment to the left of the glyph.
+ m_GlyphItems[i].addNewWidth(nDiff);
+ m_GlyphItems[i].adjustLinearPosX(nDelta + nDiff);
+
+ // Adjust the X position of all glyphs in the cluster.
+ size_t j = i;
+ while (j > 0)
+ {
+ --j;
+ if (!m_GlyphItems[j].IsInCluster())
+ break;
+ m_GlyphItems[j].adjustLinearPosX(nDelta + nDiff);
+ }
+
+ // If this glyph is Kashida-justifiable, then mark this as a
+ // Kashida position. Since this must be a RTL glyph, we mark the
+ // last glyph in the cluster not the first as this would be the
+ // base glyph.
+ if (bKashidaJustify && m_GlyphItems[i].AllowKashida() &&
+ nDiff > m_GlyphItems[i].charCount()) // Rounding errors, 1 pixel per character!
+ {
+ pKashidas[i] = nDiff;
+ // Move any non-spacing marks attached to this cluster as well.
+ // Looping backward because this is RTL glyph.
+ while (j > 0)
+ {
+ if (!m_GlyphItems[j].IsDiacritic())
+ break;
+ m_GlyphItems[j--].adjustLinearPosX(nDiff);
+ }
+ }
+ i++;
+ }
+
+ // Increment the delta, the loop above makes sure we do so only once
+ // for every character (cluster) not for every glyph (otherwise we
+ // would apply it multiple times for each glyphs belonging to the same
+ // character which is wrong since DX adjustments are character based).
+ nDelta += nDiff;
+ }
+
+ // Insert Kashida glyphs.
+ if (!bKashidaJustify || pKashidas.empty())
+ return;
+
+ size_t nInserted = 0;
+ for (auto const& pKashida : pKashidas)
+ {
+ auto pGlyphIter = m_GlyphItems.begin() + nInserted + pKashida.first;
+
+ // The total Kashida width.
+ DeviceCoordinate nTotalWidth = pKashida.second;
+
+ // Number of times to repeat each Kashida.
+ int nCopies = 1;
+ if (nTotalWidth > nKashidaWidth)
+ nCopies = nTotalWidth / nKashidaWidth;
+
+ // See if we can improve the fit by adding an extra Kashidas and
+ // squeezing them together a bit.
+ DeviceCoordinate nOverlap = 0;
+ DeviceCoordinate nShortfall = nTotalWidth - nKashidaWidth * nCopies;
+ if (nShortfall > 0)
+ {
+ ++nCopies;
+ DeviceCoordinate nExcess = nCopies * nKashidaWidth - nTotalWidth;
+ if (nExcess > 0)
+ nOverlap = nExcess / (nCopies - 1);
+ }
+
+ DevicePoint aPos(pGlyphIter->linearPos().getX() - nTotalWidth, 0);
+ int nCharPos = pGlyphIter->charPos();
+ GlyphItemFlags const nFlags = GlyphItemFlags::IS_IN_CLUSTER | GlyphItemFlags::IS_RTL_GLYPH;
+ while (nCopies--)
+ {
+ GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, nKashidaWidth, 0, 0);
+ pGlyphIter = m_GlyphItems.insert(pGlyphIter, aKashida);
+ aPos.adjustX(nKashidaWidth - nOverlap);
+ ++pGlyphIter;
+ ++nInserted;
+ }
+ }
+}
+
+bool GenericSalLayout::IsKashidaPosValid(int nCharPos) const
+{
+ for (auto pIter = m_GlyphItems.begin(); pIter != m_GlyphItems.end(); ++pIter)
+ {
+ if (pIter->charPos() == nCharPos)
+ {
+ // The position is the first glyph, this would happen if we
+ // changed the text styling in the middle of a word. Since we don’t
+ // do ligatures across layout engine instances, this can’t be a
+ // ligature so it should be fine.
+ if (pIter == m_GlyphItems.begin())
+ return true;
+
+ // If the character is not supported by this layout, return false
+ // so that fallback layouts would be checked for it.
+ if (pIter->glyphId() == 0)
+ break;
+
+ // Search backwards for previous glyph belonging to a different
+ // character. We are looking backwards because we are dealing with
+ // RTL glyphs, which will be in visual order.
+ for (auto pPrev = pIter - 1; pPrev != m_GlyphItems.begin(); --pPrev)
+ {
+ if (pPrev->charPos() != nCharPos)
+ {
+ // Check if the found glyph belongs to the next character,
+ // otherwise the current glyph will be a ligature which is
+ // invalid kashida position.
+ if (pPrev->charPos() == (nCharPos + 1))
+ return true;
+ break;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/FileDefinitionWidgetDraw.cxx b/vcl/source/gdi/FileDefinitionWidgetDraw.cxx
new file mode 100644
index 000000000..88f4d6189
--- /dev/null
+++ b/vcl/source/gdi/FileDefinitionWidgetDraw.cxx
@@ -0,0 +1,1129 @@
+/* -*- 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/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <FileDefinitionWidgetDraw.hxx>
+#include <widgetdraw/WidgetDefinitionReader.hxx>
+
+#include <svdata.hxx>
+#include <rtl/bootstrap.hxx>
+#include <config_folders.h>
+#include <osl/file.hxx>
+
+#include <basegfx/range/b2drectangle.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/tuple/b2dtuple.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <tools/stream.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <vcl/gradient.hxx>
+
+#include <comphelper/seqstream.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/lok.hxx>
+#include <comphelper/string.hxx>
+
+#include <com/sun/star/graphic/SvgTools.hpp>
+#include <basegfx/DrawCommands.hxx>
+#include <o3tl/string_view.hxx>
+
+using namespace css;
+
+namespace vcl
+{
+namespace
+{
+OUString lcl_getThemeDefinitionPath()
+{
+ OUString sPath("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/theme_definitions/");
+ rtl::Bootstrap::expandMacros(sPath);
+ return sPath;
+}
+
+bool lcl_directoryExists(OUString const& sDirectory)
+{
+ osl::DirectoryItem aDirectoryItem;
+ osl::FileBase::RC eRes = osl::DirectoryItem::get(sDirectory, aDirectoryItem);
+ return eRes == osl::FileBase::E_None;
+}
+
+bool lcl_fileExists(OUString const& sFilename)
+{
+ osl::File aFile(sFilename);
+ osl::FileBase::RC eRC = aFile.open(osl_File_OpenFlag_Read);
+ return osl::FileBase::E_None == eRC;
+}
+
+std::shared_ptr<WidgetDefinition> getWidgetDefinition(OUString const& rDefinitionFile,
+ OUString const& rDefinitionResourcesPath)
+{
+ auto pWidgetDefinition = std::make_shared<WidgetDefinition>();
+ WidgetDefinitionReader aReader(rDefinitionFile, rDefinitionResourcesPath);
+ if (aReader.read(*pWidgetDefinition))
+ return pWidgetDefinition;
+ return std::shared_ptr<WidgetDefinition>();
+}
+
+std::shared_ptr<WidgetDefinition> const&
+getWidgetDefinitionForTheme(std::u16string_view rThemenName)
+{
+ static std::shared_ptr<WidgetDefinition> spDefinition;
+ if (!spDefinition)
+ {
+ OUString sSharedDefinitionBasePath = lcl_getThemeDefinitionPath();
+ OUString sThemeFolder = sSharedDefinitionBasePath + rThemenName + "/";
+ OUString sThemeDefinitionFile = sThemeFolder + "definition.xml";
+ if (lcl_directoryExists(sThemeFolder) && lcl_fileExists(sThemeDefinitionFile))
+ spDefinition = getWidgetDefinition(sThemeDefinitionFile, sThemeFolder);
+ }
+ return spDefinition;
+}
+
+int getSettingValueInteger(std::string_view rValue, int nDefault)
+{
+ if (rValue.empty())
+ return nDefault;
+ if (!comphelper::string::isdigitAsciiString(rValue))
+ return nDefault;
+ return o3tl::toInt32(rValue);
+}
+
+bool getSettingValueBool(std::string_view rValue, bool bDefault)
+{
+ if (rValue.empty())
+ return bDefault;
+ if (rValue == "true" || rValue == "false")
+ return rValue == "true";
+ return bDefault;
+}
+
+} // end anonymous namespace
+
+FileDefinitionWidgetDraw::FileDefinitionWidgetDraw(SalGraphics& rGraphics)
+ : m_rGraphics(rGraphics)
+ , m_bIsActive(false)
+{
+ m_pWidgetDefinition = getWidgetDefinitionForTheme(u"online");
+#ifdef IOS
+ if (!m_pWidgetDefinition)
+ m_pWidgetDefinition = getWidgetDefinitionForTheme(u"ios");
+#endif
+
+ if (!m_pWidgetDefinition)
+ return;
+
+ auto& pSettings = m_pWidgetDefinition->mpSettings;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maNWFData.mbNoFocusRects = true;
+ pSVData->maNWFData.mbNoFocusRectsForFlatButtons = true;
+ pSVData->maNWFData.mbNoActiveTabTextRaise
+ = getSettingValueBool(pSettings->msNoActiveTabTextRaise, true);
+ pSVData->maNWFData.mbCenteredTabs = getSettingValueBool(pSettings->msCenteredTabs, true);
+ pSVData->maNWFData.mbProgressNeedsErase = true;
+ pSVData->maNWFData.mnStatusBarLowerRightOffset = 10;
+ pSVData->maNWFData.mbCanDrawWidgetAnySize = true;
+
+ int nDefaultListboxEntryMargin = pSVData->maNWFData.mnListBoxEntryMargin;
+ pSVData->maNWFData.mnListBoxEntryMargin
+ = getSettingValueInteger(pSettings->msListBoxEntryMargin, nDefaultListboxEntryMargin);
+
+ m_bIsActive = true;
+}
+
+bool FileDefinitionWidgetDraw::isNativeControlSupported(ControlType eType, ControlPart ePart)
+{
+ switch (eType)
+ {
+ case ControlType::Generic:
+ case ControlType::Pushbutton:
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ return true;
+ case ControlType::Combobox:
+ if (ePart == ControlPart::HasBackgroundTexture)
+ return false;
+ return true;
+ case ControlType::Editbox:
+ case ControlType::EditboxNoBorder:
+ case ControlType::MultilineEditbox:
+ return true;
+ case ControlType::Listbox:
+ if (ePart == ControlPart::HasBackgroundTexture)
+ return false;
+ return true;
+ case ControlType::Spinbox:
+ if (ePart == ControlPart::AllButtons)
+ return false;
+ return true;
+ case ControlType::SpinButtons:
+ return false;
+ case ControlType::TabItem:
+ case ControlType::TabPane:
+ case ControlType::TabHeader:
+ case ControlType::TabBody:
+ return true;
+ case ControlType::Scrollbar:
+ if (ePart == ControlPart::DrawBackgroundHorz
+ || ePart == ControlPart::DrawBackgroundVert)
+ return false;
+ return true;
+ case ControlType::Slider:
+ case ControlType::Fixedline:
+ case ControlType::Toolbar:
+ return true;
+ case ControlType::Menubar:
+ case ControlType::MenuPopup:
+ return true;
+ case ControlType::Progress:
+ return true;
+ case ControlType::IntroProgress:
+ return false;
+ case ControlType::Tooltip:
+ return true;
+ case ControlType::WindowBackground:
+ case ControlType::Frame:
+ case ControlType::ListNode:
+ case ControlType::ListNet:
+ case ControlType::ListHeader:
+ return true;
+ }
+
+ return false;
+}
+
+bool FileDefinitionWidgetDraw::hitTestNativeControl(
+ ControlType /*eType*/, ControlPart /*ePart*/,
+ const tools::Rectangle& /*rBoundingControlRegion*/, const Point& /*aPos*/, bool& /*rIsInside*/)
+{
+ return false;
+}
+
+void FileDefinitionWidgetDraw::drawPolyPolygon(SalGraphics& rGraphics,
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& i_rPolyPolygon,
+ double i_fTransparency)
+{
+ rGraphics.drawPolyPolygon(rObjectToDevice, i_rPolyPolygon, i_fTransparency);
+}
+
+void FileDefinitionWidgetDraw::drawPolyLine(
+ SalGraphics& rGraphics, const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& i_rPolygon, double i_fTransparency, double i_fLineWidth,
+ const std::vector<double>* i_pStroke, basegfx::B2DLineJoin i_eLineJoin,
+ css::drawing::LineCap i_eLineCap, double i_fMiterMinimumAngle, bool bPixelSnapHairline)
+{
+ rGraphics.drawPolyLine(rObjectToDevice, i_rPolygon, i_fTransparency, i_fLineWidth, i_pStroke,
+ i_eLineJoin, i_eLineCap, i_fMiterMinimumAngle, bPixelSnapHairline);
+}
+
+void FileDefinitionWidgetDraw::drawBitmap(SalGraphics& rGraphics, const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap)
+{
+ rGraphics.drawBitmap(rPosAry, rSalBitmap);
+}
+
+void FileDefinitionWidgetDraw::drawBitmap(SalGraphics& rGraphics, const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap)
+{
+ rGraphics.drawBitmap(rPosAry, rSalBitmap, rTransparentBitmap);
+}
+
+void FileDefinitionWidgetDraw::implDrawGradient(SalGraphics& rGraphics,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ const SalGradient& rGradient)
+{
+ rGraphics.implDrawGradient(rPolyPolygon, rGradient);
+}
+
+namespace
+{
+void drawFromDrawCommands(gfx::DrawRoot const& rDrawRoot, SalGraphics& rGraphics, tools::Long nX,
+ tools::Long nY, tools::Long nWidth, tools::Long nHeight)
+{
+ basegfx::B2DRectangle aSVGRect = rDrawRoot.maRectangle;
+
+ basegfx::B2DRange aTargetSurface(nX, nY, nX + nWidth + 1, nY + nHeight + 1);
+
+ for (std::shared_ptr<gfx::DrawBase> const& pDrawBase : rDrawRoot.maChildren)
+ {
+ switch (pDrawBase->getType())
+ {
+ case gfx::DrawCommandType::Rectangle:
+ {
+ auto const& rRectangle = static_cast<gfx::DrawRectangle const&>(*pDrawBase);
+
+ basegfx::B2DRange aInputRectangle(rRectangle.maRectangle);
+
+ double fDeltaX = aTargetSurface.getWidth() - aSVGRect.getWidth();
+ double fDeltaY = aTargetSurface.getHeight() - aSVGRect.getHeight();
+
+ basegfx::B2DRange aFinalRectangle(
+ aInputRectangle.getMinX(), aInputRectangle.getMinY(),
+ aInputRectangle.getMaxX() + fDeltaX, aInputRectangle.getMaxY() + fDeltaY);
+
+ aFinalRectangle.transform(basegfx::utils::createTranslateB2DHomMatrix(
+ aTargetSurface.getMinX() - 0.5, aTargetSurface.getMinY() - 0.5));
+
+ basegfx::B2DPolygon aB2DPolygon = basegfx::utils::createPolygonFromRect(
+ aFinalRectangle, rRectangle.mnRx / aFinalRectangle.getWidth() * 2.0,
+ rRectangle.mnRy / aFinalRectangle.getHeight() * 2.0);
+
+ if (rRectangle.mpFillColor)
+ {
+ rGraphics.SetLineColor();
+ rGraphics.SetFillColor(Color(*rRectangle.mpFillColor));
+ FileDefinitionWidgetDraw::drawPolyPolygon(rGraphics, basegfx::B2DHomMatrix(),
+ basegfx::B2DPolyPolygon(aB2DPolygon),
+ 1.0 - rRectangle.mnOpacity);
+ }
+ else if (rRectangle.mpFillGradient)
+ {
+ rGraphics.SetLineColor();
+ rGraphics.SetFillColor();
+ if (rRectangle.mpFillGradient->meType == gfx::GradientType::Linear)
+ {
+ auto* pLinearGradient = static_cast<gfx::LinearGradientInfo*>(
+ rRectangle.mpFillGradient.get());
+ SalGradient aGradient;
+ double x, y;
+
+ x = pLinearGradient->x1;
+ y = pLinearGradient->y1;
+
+ if (x > aSVGRect.getCenterX())
+ x = x + fDeltaX;
+ if (y > aSVGRect.getCenterY())
+ y = y + fDeltaY;
+
+ aGradient.maPoint1 = basegfx::B2DPoint(x, y);
+ aGradient.maPoint1 *= basegfx::utils::createTranslateB2DHomMatrix(
+ aTargetSurface.getMinX() - 0.5, aTargetSurface.getMinY() - 0.5);
+
+ x = pLinearGradient->x2;
+ y = pLinearGradient->y2;
+
+ if (x > aSVGRect.getCenterX())
+ x = x + fDeltaX;
+ if (y > aSVGRect.getCenterY())
+ y = y + fDeltaY;
+
+ aGradient.maPoint2 = basegfx::B2DPoint(x, y);
+ aGradient.maPoint2 *= basegfx::utils::createTranslateB2DHomMatrix(
+ aTargetSurface.getMinX() - 0.5, aTargetSurface.getMinY() - 0.5);
+
+ for (gfx::GradientStop const& rStop : pLinearGradient->maGradientStops)
+ {
+ Color aColor(rStop.maColor);
+ aColor.SetAlpha(255
+ - (rStop.mfOpacity * (1.0f - rRectangle.mnOpacity)));
+ aGradient.maStops.emplace_back(aColor, rStop.mfOffset);
+ }
+ FileDefinitionWidgetDraw::implDrawGradient(
+ rGraphics, basegfx::B2DPolyPolygon(aB2DPolygon), aGradient);
+ }
+ }
+ if (rRectangle.mpStrokeColor)
+ {
+ rGraphics.SetLineColor(Color(*rRectangle.mpStrokeColor));
+ rGraphics.SetFillColor();
+ FileDefinitionWidgetDraw::drawPolyLine(
+ rGraphics, basegfx::B2DHomMatrix(), aB2DPolygon, 1.0 - rRectangle.mnOpacity,
+ rRectangle.mnStrokeWidth,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false);
+ }
+ }
+ break;
+ case gfx::DrawCommandType::Path:
+ {
+ auto const& rPath = static_cast<gfx::DrawPath const&>(*pDrawBase);
+
+ double fDeltaX = aTargetSurface.getWidth() - aSVGRect.getWidth();
+ double fDeltaY = aTargetSurface.getHeight() - aSVGRect.getHeight();
+
+ basegfx::B2DPolyPolygon aPolyPolygon(rPath.maPolyPolygon);
+ for (auto& rPolygon : aPolyPolygon)
+ {
+ for (size_t i = 0; i < rPolygon.count(); ++i)
+ {
+ auto& rPoint = rPolygon.getB2DPoint(i);
+ double x = rPoint.getX();
+ double y = rPoint.getY();
+
+ if (x > aSVGRect.getCenterX())
+ x = x + fDeltaX;
+ if (y > aSVGRect.getCenterY())
+ y = y + fDeltaY;
+ rPolygon.setB2DPoint(i, basegfx::B2DPoint(x, y));
+ }
+ }
+ aPolyPolygon.transform(basegfx::utils::createTranslateB2DHomMatrix(
+ aTargetSurface.getMinX() - 0.5, aTargetSurface.getMinY() - 0.5));
+
+ if (rPath.mpFillColor)
+ {
+ rGraphics.SetLineColor();
+ rGraphics.SetFillColor(Color(*rPath.mpFillColor));
+ FileDefinitionWidgetDraw::drawPolyPolygon(rGraphics, basegfx::B2DHomMatrix(),
+ aPolyPolygon, 1.0 - rPath.mnOpacity);
+ }
+ if (rPath.mpStrokeColor)
+ {
+ rGraphics.SetLineColor(Color(*rPath.mpStrokeColor));
+ rGraphics.SetFillColor();
+ for (auto const& rPolygon : std::as_const(aPolyPolygon))
+ {
+ FileDefinitionWidgetDraw::drawPolyLine(
+ rGraphics, basegfx::B2DHomMatrix(), rPolygon, 1.0 - rPath.mnOpacity,
+ rPath.mnStrokeWidth,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false);
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+void munchDrawCommands(std::vector<std::shared_ptr<WidgetDrawAction>> const& rDrawActions,
+ SalGraphics& rGraphics, tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ for (std::shared_ptr<WidgetDrawAction> const& pDrawAction : rDrawActions)
+ {
+ switch (pDrawAction->maType)
+ {
+ case WidgetDrawActionType::RECTANGLE:
+ {
+ auto const& rWidgetDraw
+ = static_cast<WidgetDrawActionRectangle const&>(*pDrawAction);
+
+ basegfx::B2DRectangle rRect(
+ nX + (nWidth * rWidgetDraw.mfX1), nY + (nHeight * rWidgetDraw.mfY1),
+ nX + (nWidth * rWidgetDraw.mfX2), nY + (nHeight * rWidgetDraw.mfY2));
+
+ basegfx::B2DPolygon aB2DPolygon = basegfx::utils::createPolygonFromRect(
+ rRect, rWidgetDraw.mnRx / rRect.getWidth() * 2.0,
+ rWidgetDraw.mnRy / rRect.getHeight() * 2.0);
+
+ rGraphics.SetLineColor();
+ rGraphics.SetFillColor(rWidgetDraw.maFillColor);
+ FileDefinitionWidgetDraw::drawPolyPolygon(
+ rGraphics, basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aB2DPolygon), 0.0f);
+ rGraphics.SetLineColor(rWidgetDraw.maStrokeColor);
+ rGraphics.SetFillColor();
+ FileDefinitionWidgetDraw::drawPolyLine(
+ rGraphics, basegfx::B2DHomMatrix(), aB2DPolygon, 0.0f,
+ rWidgetDraw.mnStrokeWidth, nullptr, // MM01
+ basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false);
+ }
+ break;
+ case WidgetDrawActionType::LINE:
+ {
+ auto const& rWidgetDraw = static_cast<WidgetDrawActionLine const&>(*pDrawAction);
+ Point aRectPoint(nX + 1, nY + 1);
+
+ Size aRectSize(nWidth - 1, nHeight - 1);
+
+ rGraphics.SetFillColor();
+ rGraphics.SetLineColor(rWidgetDraw.maStrokeColor);
+
+ basegfx::B2DPolygon aB2DPolygon{
+ { aRectPoint.X() + (aRectSize.Width() * rWidgetDraw.mfX1),
+ aRectPoint.Y() + (aRectSize.Height() * rWidgetDraw.mfY1) },
+ { aRectPoint.X() + (aRectSize.Width() * rWidgetDraw.mfX2),
+ aRectPoint.Y() + (aRectSize.Height() * rWidgetDraw.mfY2) },
+ };
+
+ FileDefinitionWidgetDraw::drawPolyLine(
+ rGraphics, basegfx::B2DHomMatrix(), aB2DPolygon, 0.0f,
+ rWidgetDraw.mnStrokeWidth, nullptr, // MM01
+ basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false);
+ }
+ break;
+ case WidgetDrawActionType::IMAGE:
+ {
+ double nScaleFactor = 1.0;
+ if (comphelper::LibreOfficeKit::isActive())
+ nScaleFactor = comphelper::LibreOfficeKit::getDPIScale();
+
+ auto const& rWidgetDraw = static_cast<WidgetDrawActionImage const&>(*pDrawAction);
+ auto& rCacheImages = ImplGetSVData()->maGDIData.maThemeImageCache;
+ OUString rCacheKey = rWidgetDraw.msSource + "@" + OUString::number(nScaleFactor);
+ auto aIterator = rCacheImages.find(rCacheKey);
+
+ BitmapEx aBitmap;
+ if (aIterator == rCacheImages.end())
+ {
+ SvFileStream aFileStream(rWidgetDraw.msSource, StreamMode::READ);
+
+ vcl::bitmap::loadFromSvg(aFileStream, "", aBitmap, nScaleFactor);
+ if (!aBitmap.IsEmpty())
+ {
+ rCacheImages.insert(std::make_pair(rCacheKey, aBitmap));
+ }
+ }
+ else
+ {
+ aBitmap = aIterator->second;
+ }
+
+ tools::Long nImageWidth = aBitmap.GetSizePixel().Width();
+ tools::Long nImageHeight = aBitmap.GetSizePixel().Height();
+ SalTwoRect aTR(0, 0, nImageWidth, nImageHeight, nX, nY, nImageWidth / nScaleFactor,
+ nImageHeight / nScaleFactor);
+ if (!aBitmap.IsEmpty())
+ {
+ const std::shared_ptr<SalBitmap> pSalBitmap
+ = aBitmap.GetBitmap().ImplGetSalBitmap();
+ if (aBitmap.IsAlpha())
+ {
+ const std::shared_ptr<SalBitmap> pSalBitmapAlpha
+ = aBitmap.GetAlpha().ImplGetSalBitmap();
+ FileDefinitionWidgetDraw::drawBitmap(rGraphics, aTR, *pSalBitmap,
+ *pSalBitmapAlpha);
+ }
+ else
+ {
+ FileDefinitionWidgetDraw::drawBitmap(rGraphics, aTR, *pSalBitmap);
+ }
+ }
+ }
+ break;
+ case WidgetDrawActionType::EXTERNAL:
+ {
+ auto const& rWidgetDraw
+ = static_cast<WidgetDrawActionExternal const&>(*pDrawAction);
+
+ auto& rCacheDrawCommands = ImplGetSVData()->maGDIData.maThemeDrawCommandsCache;
+
+ auto aIterator = rCacheDrawCommands.find(rWidgetDraw.msSource);
+
+ if (aIterator == rCacheDrawCommands.end())
+ {
+ SvFileStream aFileStream(rWidgetDraw.msSource, StreamMode::READ);
+
+ uno::Reference<uno::XComponentContext> xContext(
+ comphelper::getProcessComponentContext());
+ const uno::Reference<graphic::XSvgParser> xSvgParser
+ = graphic::SvgTools::create(xContext);
+
+ std::size_t nSize = aFileStream.remainingSize();
+ std::vector<sal_Int8> aBuffer(nSize + 1);
+ aFileStream.ReadBytes(aBuffer.data(), nSize);
+ aBuffer[nSize] = 0;
+
+ uno::Sequence<sal_Int8> aData(aBuffer.data(), nSize + 1);
+ uno::Reference<io::XInputStream> aInputStream(
+ new comphelper::SequenceInputStream(aData));
+
+ uno::Any aAny = xSvgParser->getDrawCommands(aInputStream, "");
+ if (aAny.has<sal_uInt64>())
+ {
+ auto* pDrawRoot = reinterpret_cast<gfx::DrawRoot*>(aAny.get<sal_uInt64>());
+ if (pDrawRoot)
+ {
+ rCacheDrawCommands.insert(
+ std::make_pair(rWidgetDraw.msSource, *pDrawRoot));
+ drawFromDrawCommands(*pDrawRoot, rGraphics, nX, nY, nWidth, nHeight);
+ }
+ }
+ }
+ else
+ {
+ drawFromDrawCommands(aIterator->second, rGraphics, nX, nY, nWidth, nHeight);
+ }
+ }
+ break;
+ }
+ }
+}
+
+} // end anonymous namespace
+
+bool FileDefinitionWidgetDraw::resolveDefinition(ControlType eType, ControlPart ePart,
+ ControlState eState,
+ const ImplControlValue& rValue, tools::Long nX,
+ tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ bool bOK = false;
+ auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ePart);
+ if (pPart)
+ {
+ auto const& aStates = pPart->getStates(eType, ePart, eState, rValue);
+ if (!aStates.empty())
+ {
+ // use last defined state
+ auto const& pState = aStates.back();
+ {
+ munchDrawCommands(pState->mpWidgetDrawActions, m_rGraphics, nX, nY, nWidth,
+ nHeight);
+ bOK = true;
+ }
+ }
+ }
+ return bOK;
+}
+
+bool FileDefinitionWidgetDraw::drawNativeControl(ControlType eType, ControlPart ePart,
+ const tools::Rectangle& rControlRegion,
+ ControlState eState,
+ const ImplControlValue& rValue,
+ const OUString& /*aCaptions*/,
+ const Color& /*rBackgroundColor*/)
+{
+ bool bOldAA = m_rGraphics.getAntiAlias();
+ m_rGraphics.setAntiAlias(true);
+
+ tools::Long nWidth = rControlRegion.GetWidth() - 1;
+ tools::Long nHeight = rControlRegion.GetHeight() - 1;
+ tools::Long nX = rControlRegion.Left();
+ tools::Long nY = rControlRegion.Top();
+
+ bool bOK = false;
+
+ switch (eType)
+ {
+ case ControlType::Pushbutton:
+ {
+ /*bool bIsAction = false;
+ const PushButtonValue* pPushButtonValue = static_cast<const PushButtonValue*>(&rValue);
+ if (pPushButtonValue)
+ bIsAction = pPushButtonValue->mbIsAction;*/
+
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Radiobutton:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Checkbox:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Combobox:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Editbox:
+ case ControlType::EditboxNoBorder:
+ case ControlType::MultilineEditbox:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Listbox:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Spinbox:
+ {
+ if (rValue.getType() == ControlType::SpinButtons)
+ {
+ const SpinbuttonValue* pSpinVal = static_cast<const SpinbuttonValue*>(&rValue);
+
+ {
+ ControlPart eUpButtonPart = pSpinVal->mnUpperPart;
+ ControlState eUpButtonState = pSpinVal->mnUpperState;
+
+ tools::Long nUpperX = pSpinVal->maUpperRect.Left();
+ tools::Long nUpperY = pSpinVal->maUpperRect.Top();
+ tools::Long nUpperWidth = pSpinVal->maUpperRect.GetWidth() - 1;
+ tools::Long nUpperHeight = pSpinVal->maUpperRect.GetHeight() - 1;
+
+ bOK = resolveDefinition(eType, eUpButtonPart, eUpButtonState,
+ ImplControlValue(), nUpperX, nUpperY, nUpperWidth,
+ nUpperHeight);
+ }
+
+ if (bOK)
+ {
+ ControlPart eDownButtonPart = pSpinVal->mnLowerPart;
+ ControlState eDownButtonState = pSpinVal->mnLowerState;
+
+ tools::Long nLowerX = pSpinVal->maLowerRect.Left();
+ tools::Long nLowerY = pSpinVal->maLowerRect.Top();
+ tools::Long nLowerWidth = pSpinVal->maLowerRect.GetWidth() - 1;
+ tools::Long nLowerHeight = pSpinVal->maLowerRect.GetHeight() - 1;
+
+ bOK = resolveDefinition(eType, eDownButtonPart, eDownButtonState,
+ ImplControlValue(), nLowerX, nLowerY, nLowerWidth,
+ nLowerHeight);
+ }
+ }
+ else
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ }
+ break;
+ case ControlType::SpinButtons:
+ break;
+ case ControlType::TabItem:
+ case ControlType::TabHeader:
+ case ControlType::TabPane:
+ case ControlType::TabBody:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Slider:
+ {
+ const SliderValue* pSliderValue = static_cast<const SliderValue*>(&rValue);
+ tools::Long nThumbX = pSliderValue->maThumbRect.Left();
+ tools::Long nThumbY = pSliderValue->maThumbRect.Top();
+ tools::Long nThumbWidth = pSliderValue->maThumbRect.GetWidth() - 1;
+ tools::Long nThumbHeight = pSliderValue->maThumbRect.GetHeight() - 1;
+
+ if (ePart == ControlPart::TrackHorzArea)
+ {
+ tools::Long nCenterX = nThumbX + nThumbWidth / 2;
+
+ bOK = resolveDefinition(eType, ControlPart::TrackHorzLeft, eState, rValue, nX, nY,
+ nCenterX - nX, nHeight);
+ if (bOK)
+ bOK = resolveDefinition(eType, ControlPart::TrackHorzRight, eState, rValue,
+ nCenterX, nY, nX + nWidth - nCenterX, nHeight);
+ }
+ else if (ePart == ControlPart::TrackVertArea)
+ {
+ tools::Long nCenterY = nThumbY + nThumbHeight / 2;
+
+ bOK = resolveDefinition(eType, ControlPart::TrackVertUpper, eState, rValue, nX, nY,
+ nWidth, nCenterY - nY);
+ if (bOK)
+ bOK = resolveDefinition(eType, ControlPart::TrackVertLower, eState, rValue, nY,
+ nCenterY, nWidth, nY + nHeight - nCenterY);
+ }
+
+ if (bOK)
+ {
+ bOK = resolveDefinition(eType, ControlPart::Button,
+ eState | pSliderValue->mnThumbState, rValue, nThumbX,
+ nThumbY, nThumbWidth, nThumbHeight);
+ }
+ }
+ break;
+ case ControlType::Fixedline:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Toolbar:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Menubar:
+ case ControlType::MenuPopup:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Progress:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::IntroProgress:
+ break;
+ case ControlType::Tooltip:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::WindowBackground:
+ case ControlType::Frame:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::ListNode:
+ case ControlType::ListNet:
+ case ControlType::ListHeader:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ default:
+ break;
+ }
+
+ m_rGraphics.setAntiAlias(bOldAA);
+
+ return bOK;
+}
+
+bool FileDefinitionWidgetDraw::getNativeControlRegion(
+ ControlType eType, ControlPart ePart, const tools::Rectangle& rBoundingControlRegion,
+ ControlState /*eState*/, const ImplControlValue& /*aValue*/, const OUString& /*aCaption*/,
+ tools::Rectangle& rNativeBoundingRegion, tools::Rectangle& rNativeContentRegion)
+{
+ Point aLocation(rBoundingControlRegion.TopLeft());
+
+ switch (eType)
+ {
+ case ControlType::Spinbox:
+ {
+ auto const& pButtonUpPart
+ = m_pWidgetDefinition->getDefinition(eType, ControlPart::ButtonUp);
+ if (!pButtonUpPart)
+ return false;
+ Size aButtonSizeUp(pButtonUpPart->mnWidth, pButtonUpPart->mnHeight);
+
+ auto const& pButtonDownPart
+ = m_pWidgetDefinition->getDefinition(eType, ControlPart::ButtonDown);
+ if (!pButtonDownPart)
+ return false;
+ Size aButtonSizeDown(pButtonDownPart->mnWidth, pButtonDownPart->mnHeight);
+
+ auto const& pEntirePart
+ = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire);
+
+ OString sOrientation = pEntirePart->msOrientation;
+
+ if (sOrientation.isEmpty() || sOrientation == "stacked")
+ {
+ return false;
+ }
+ else if (sOrientation == "decrease-edit-increase")
+ {
+ if (ePart == ControlPart::ButtonUp)
+ {
+ Point aPoint(aLocation.X() + rBoundingControlRegion.GetWidth()
+ - aButtonSizeUp.Width(),
+ aLocation.Y());
+ rNativeContentRegion = tools::Rectangle(aPoint, aButtonSizeUp);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::ButtonDown)
+ {
+ rNativeContentRegion = tools::Rectangle(aLocation, aButtonSizeDown);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::SubEdit)
+ {
+ Point aPoint(aLocation.X() + aButtonSizeDown.Width(), aLocation.Y());
+ Size aSize(rBoundingControlRegion.GetWidth()
+ - (aButtonSizeDown.Width() + aButtonSizeUp.Width()),
+ std::max(aButtonSizeUp.Height(), aButtonSizeDown.Height()));
+ rNativeContentRegion = tools::Rectangle(aPoint, aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::Entire)
+ {
+ Size aSize(rBoundingControlRegion.GetWidth(),
+ std::max(aButtonSizeUp.Height(), aButtonSizeDown.Height()));
+ rNativeContentRegion = tools::Rectangle(aLocation, aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ }
+ else if (sOrientation == "edit-decrease-increase")
+ {
+ if (ePart == ControlPart::ButtonUp)
+ {
+ Point aPoint(aLocation.X() + rBoundingControlRegion.GetWidth()
+ - aButtonSizeUp.Width(),
+ aLocation.Y());
+ rNativeContentRegion = tools::Rectangle(aPoint, aButtonSizeUp);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::ButtonDown)
+ {
+ Point aPoint(aLocation.X() + rBoundingControlRegion.GetWidth()
+ - (aButtonSizeDown.Width() + aButtonSizeUp.Width()),
+ aLocation.Y());
+ rNativeContentRegion = tools::Rectangle(aPoint, aButtonSizeDown);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::SubEdit)
+ {
+ Size aSize(rBoundingControlRegion.GetWidth()
+ - (aButtonSizeDown.Width() + aButtonSizeUp.Width()),
+ std::max(aButtonSizeUp.Height(), aButtonSizeDown.Height()));
+ rNativeContentRegion = tools::Rectangle(aLocation, aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::Entire)
+ {
+ Size aSize(rBoundingControlRegion.GetWidth(),
+ std::max(aButtonSizeUp.Height(), aButtonSizeDown.Height()));
+ rNativeContentRegion = tools::Rectangle(aLocation, aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ }
+ }
+ break;
+ case ControlType::Checkbox:
+ {
+ auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire);
+ if (!pPart)
+ return false;
+
+ Size aSize(pPart->mnWidth, pPart->mnHeight);
+ rNativeContentRegion = tools::Rectangle(Point(), aSize);
+ return true;
+ }
+ case ControlType::Radiobutton:
+ {
+ auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire);
+ if (!pPart)
+ return false;
+
+ Size aSize(pPart->mnWidth, pPart->mnHeight);
+ rNativeContentRegion = tools::Rectangle(Point(), aSize);
+ return true;
+ }
+ case ControlType::TabItem:
+ {
+ auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire);
+ if (!pPart)
+ return false;
+
+ tools::Long nWidth = std::max(rBoundingControlRegion.GetWidth() + pPart->mnMarginWidth,
+ tools::Long(pPart->mnWidth));
+ tools::Long nHeight
+ = std::max(rBoundingControlRegion.GetHeight() + pPart->mnMarginHeight,
+ tools::Long(pPart->mnHeight));
+
+ rNativeBoundingRegion = tools::Rectangle(aLocation, Size(nWidth, nHeight));
+ rNativeContentRegion = rNativeBoundingRegion;
+ return true;
+ }
+ case ControlType::Editbox:
+ case ControlType::EditboxNoBorder:
+ case ControlType::MultilineEditbox:
+ {
+ sal_Int32 nHeight = rBoundingControlRegion.GetHeight();
+
+ auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire);
+ if (pPart)
+ nHeight = std::max(nHeight, pPart->mnHeight);
+
+ Size aSize(rBoundingControlRegion.GetWidth(), nHeight);
+ rNativeContentRegion = tools::Rectangle(aLocation, aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ rNativeBoundingRegion.expand(2);
+ return true;
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ if (ePart == ControlPart::ButtonUp || ePart == ControlPart::ButtonDown
+ || ePart == ControlPart::ButtonLeft || ePart == ControlPart::ButtonRight)
+ {
+ rNativeContentRegion = tools::Rectangle(aLocation, Size(0, 0));
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else
+ {
+ rNativeBoundingRegion = rBoundingControlRegion;
+ rNativeContentRegion = rNativeBoundingRegion;
+ return true;
+ }
+ }
+ break;
+ case ControlType::Combobox:
+ case ControlType::Listbox:
+ {
+ auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::ButtonDown);
+ Size aComboButtonSize(pPart->mnWidth, pPart->mnHeight);
+
+ if (ePart == ControlPart::ButtonDown)
+ {
+ Point aPoint(aLocation.X() + rBoundingControlRegion.GetWidth()
+ - aComboButtonSize.Width() - 1,
+ aLocation.Y());
+ rNativeContentRegion = tools::Rectangle(aPoint, aComboButtonSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::SubEdit)
+ {
+ Size aSize(rBoundingControlRegion.GetWidth() - aComboButtonSize.Width(),
+ aComboButtonSize.Height());
+ rNativeContentRegion = tools::Rectangle(aLocation + Point(1, 1), aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::Entire)
+ {
+ Size aSize(rBoundingControlRegion.GetWidth(), aComboButtonSize.Height());
+ rNativeContentRegion = tools::Rectangle(aLocation, aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ rNativeBoundingRegion.expand(2);
+ return true;
+ }
+ }
+ break;
+ case ControlType::Slider:
+ if (ePart == ControlPart::ThumbHorz || ePart == ControlPart::ThumbVert)
+ {
+ rNativeContentRegion = tools::Rectangle(aLocation, Size(28, 28));
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool FileDefinitionWidgetDraw::updateSettings(AllSettings& rSettings)
+{
+ StyleSettings aStyleSet = rSettings.GetStyleSettings();
+
+ auto& pDefinitionStyle = m_pWidgetDefinition->mpStyle;
+
+ aStyleSet.SetFaceColor(pDefinitionStyle->maFaceColor);
+ aStyleSet.SetCheckedColor(pDefinitionStyle->maCheckedColor);
+ aStyleSet.SetLightColor(pDefinitionStyle->maLightColor);
+ aStyleSet.SetLightBorderColor(pDefinitionStyle->maLightBorderColor);
+ aStyleSet.SetShadowColor(pDefinitionStyle->maShadowColor);
+ aStyleSet.SetDarkShadowColor(pDefinitionStyle->maDarkShadowColor);
+ aStyleSet.SetDefaultButtonTextColor(pDefinitionStyle->maDefaultButtonTextColor);
+ aStyleSet.SetButtonTextColor(pDefinitionStyle->maButtonTextColor);
+ aStyleSet.SetDefaultActionButtonTextColor(pDefinitionStyle->maDefaultActionButtonTextColor);
+ aStyleSet.SetActionButtonTextColor(pDefinitionStyle->maActionButtonTextColor);
+ aStyleSet.SetFlatButtonTextColor(pDefinitionStyle->maFlatButtonTextColor);
+ aStyleSet.SetDefaultButtonRolloverTextColor(pDefinitionStyle->maDefaultButtonRolloverTextColor);
+ aStyleSet.SetButtonRolloverTextColor(pDefinitionStyle->maButtonRolloverTextColor);
+ aStyleSet.SetDefaultActionButtonRolloverTextColor(
+ pDefinitionStyle->maDefaultActionButtonRolloverTextColor);
+ aStyleSet.SetActionButtonRolloverTextColor(pDefinitionStyle->maActionButtonRolloverTextColor);
+ aStyleSet.SetFlatButtonRolloverTextColor(pDefinitionStyle->maFlatButtonRolloverTextColor);
+ aStyleSet.SetDefaultButtonPressedRolloverTextColor(
+ pDefinitionStyle->maDefaultButtonPressedRolloverTextColor);
+ aStyleSet.SetButtonPressedRolloverTextColor(pDefinitionStyle->maButtonPressedRolloverTextColor);
+ aStyleSet.SetDefaultActionButtonPressedRolloverTextColor(
+ pDefinitionStyle->maDefaultActionButtonPressedRolloverTextColor);
+ aStyleSet.SetActionButtonPressedRolloverTextColor(
+ pDefinitionStyle->maActionButtonPressedRolloverTextColor);
+ aStyleSet.SetFlatButtonPressedRolloverTextColor(
+ pDefinitionStyle->maFlatButtonPressedRolloverTextColor);
+ aStyleSet.SetRadioCheckTextColor(pDefinitionStyle->maRadioCheckTextColor);
+ aStyleSet.SetGroupTextColor(pDefinitionStyle->maGroupTextColor);
+ aStyleSet.SetLabelTextColor(pDefinitionStyle->maLabelTextColor);
+ aStyleSet.SetWindowColor(pDefinitionStyle->maWindowColor);
+ aStyleSet.SetWindowTextColor(pDefinitionStyle->maWindowTextColor);
+ aStyleSet.SetDialogColor(pDefinitionStyle->maDialogColor);
+ aStyleSet.SetDialogTextColor(pDefinitionStyle->maDialogTextColor);
+ aStyleSet.SetWorkspaceColor(pDefinitionStyle->maWorkspaceColor);
+ aStyleSet.SetMonoColor(pDefinitionStyle->maMonoColor);
+ aStyleSet.SetFieldColor(pDefinitionStyle->maFieldColor);
+ aStyleSet.SetFieldTextColor(pDefinitionStyle->maFieldTextColor);
+ aStyleSet.SetFieldRolloverTextColor(pDefinitionStyle->maFieldRolloverTextColor);
+ aStyleSet.SetActiveColor(pDefinitionStyle->maActiveColor);
+ aStyleSet.SetActiveTextColor(pDefinitionStyle->maActiveTextColor);
+ aStyleSet.SetActiveBorderColor(pDefinitionStyle->maActiveBorderColor);
+ aStyleSet.SetDeactiveColor(pDefinitionStyle->maDeactiveColor);
+ aStyleSet.SetDeactiveTextColor(pDefinitionStyle->maDeactiveTextColor);
+ aStyleSet.SetDeactiveBorderColor(pDefinitionStyle->maDeactiveBorderColor);
+ aStyleSet.SetMenuColor(pDefinitionStyle->maMenuColor);
+ aStyleSet.SetMenuBarColor(pDefinitionStyle->maMenuBarColor);
+ aStyleSet.SetMenuBarRolloverColor(pDefinitionStyle->maMenuBarRolloverColor);
+ aStyleSet.SetMenuBorderColor(pDefinitionStyle->maMenuBorderColor);
+ aStyleSet.SetMenuTextColor(pDefinitionStyle->maMenuTextColor);
+ aStyleSet.SetMenuBarTextColor(pDefinitionStyle->maMenuBarTextColor);
+ aStyleSet.SetMenuBarRolloverTextColor(pDefinitionStyle->maMenuBarRolloverTextColor);
+ aStyleSet.SetMenuBarHighlightTextColor(pDefinitionStyle->maMenuBarHighlightTextColor);
+ aStyleSet.SetMenuHighlightColor(pDefinitionStyle->maMenuHighlightColor);
+ aStyleSet.SetMenuHighlightTextColor(pDefinitionStyle->maMenuHighlightTextColor);
+ aStyleSet.SetHighlightColor(pDefinitionStyle->maHighlightColor);
+ aStyleSet.SetHighlightTextColor(pDefinitionStyle->maHighlightTextColor);
+ aStyleSet.SetActiveTabColor(pDefinitionStyle->maActiveTabColor);
+ aStyleSet.SetInactiveTabColor(pDefinitionStyle->maInactiveTabColor);
+ aStyleSet.SetTabTextColor(pDefinitionStyle->maTabTextColor);
+ aStyleSet.SetTabRolloverTextColor(pDefinitionStyle->maTabRolloverTextColor);
+ aStyleSet.SetTabHighlightTextColor(pDefinitionStyle->maTabHighlightTextColor);
+ aStyleSet.SetDisableColor(pDefinitionStyle->maDisableColor);
+ aStyleSet.SetHelpColor(pDefinitionStyle->maHelpColor);
+ aStyleSet.SetHelpTextColor(pDefinitionStyle->maHelpTextColor);
+ aStyleSet.SetLinkColor(pDefinitionStyle->maLinkColor);
+ aStyleSet.SetVisitedLinkColor(pDefinitionStyle->maVisitedLinkColor);
+ aStyleSet.SetToolTextColor(pDefinitionStyle->maToolTextColor);
+ aStyleSet.SetFontColor(pDefinitionStyle->maFontColor);
+
+ auto& pSettings = m_pWidgetDefinition->mpSettings;
+
+ int nFontSize = getSettingValueInteger(pSettings->msDefaultFontSize, 10);
+ vcl::Font aFont(FAMILY_SWISS, Size(0, nFontSize));
+ aFont.SetCharSet(osl_getThreadTextEncoding());
+ aFont.SetWeight(WEIGHT_NORMAL);
+ aFont.SetFamilyName("Liberation Sans");
+ aStyleSet.SetAppFont(aFont);
+ aStyleSet.SetHelpFont(aFont);
+ aStyleSet.SetMenuFont(aFont);
+ aStyleSet.SetToolFont(aFont);
+ aStyleSet.SetGroupFont(aFont);
+ aStyleSet.SetLabelFont(aFont);
+ aStyleSet.SetRadioCheckFont(aFont);
+ aStyleSet.SetPushButtonFont(aFont);
+ aStyleSet.SetFieldFont(aFont);
+ aStyleSet.SetIconFont(aFont);
+ aStyleSet.SetTabFont(aFont);
+
+ aFont.SetWeight(WEIGHT_BOLD);
+ aStyleSet.SetFloatTitleFont(aFont);
+ aStyleSet.SetTitleFont(aFont);
+
+ int nTitleHeight = getSettingValueInteger(pSettings->msTitleHeight, aStyleSet.GetTitleHeight());
+ aStyleSet.SetTitleHeight(nTitleHeight);
+
+ int nFloatTitleHeight
+ = getSettingValueInteger(pSettings->msFloatTitleHeight, aStyleSet.GetFloatTitleHeight());
+ aStyleSet.SetFloatTitleHeight(nFloatTitleHeight);
+
+ int nLogicWidth = getSettingValueInteger(pSettings->msListBoxPreviewDefaultLogicWidth,
+ 15); // See vcl/source/app/settings.cxx
+ int nLogicHeight = getSettingValueInteger(pSettings->msListBoxPreviewDefaultLogicHeight, 7);
+ aStyleSet.SetListBoxPreviewDefaultLogicSize(Size(nLogicWidth, nLogicHeight));
+
+ rSettings.SetStyleSettings(aStyleSet);
+
+ return true;
+}
+
+} // end vcl namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/TypeSerializer.cxx b/vcl/source/gdi/TypeSerializer.cxx
new file mode 100644
index 000000000..cad183128
--- /dev/null
+++ b/vcl/source/gdi/TypeSerializer.cxx
@@ -0,0 +1,486 @@
+/* -*- 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 <vcl/TypeSerializer.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/fract.hxx>
+#include <sal/log.hxx>
+#include <comphelper/fileformat.h>
+#include <vcl/filter/SvmReader.hxx>
+#include <vcl/filter/SvmWriter.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/dibtools.hxx>
+
+TypeSerializer::TypeSerializer(SvStream& rStream)
+ : GenericTypeSerializer(rStream)
+{
+}
+
+void TypeSerializer::readGradient(Gradient& rGradient)
+{
+ VersionCompatRead aCompat(mrStream);
+
+ sal_uInt16 nStyle = 0;
+ Color aStartColor;
+ Color aEndColor;
+ sal_uInt16 nAngle = 0;
+ sal_uInt16 nBorder = 0;
+ sal_uInt16 nOffsetX = 0;
+ sal_uInt16 nOffsetY = 0;
+ sal_uInt16 nIntensityStart = 0;
+ sal_uInt16 nIntensityEnd = 0;
+ sal_uInt16 nStepCount = 0;
+
+ mrStream.ReadUInt16(nStyle);
+ readColor(aStartColor);
+ readColor(aEndColor);
+ mrStream.ReadUInt16(nAngle);
+ mrStream.ReadUInt16(nBorder);
+ mrStream.ReadUInt16(nOffsetX);
+ mrStream.ReadUInt16(nOffsetY);
+ mrStream.ReadUInt16(nIntensityStart);
+ mrStream.ReadUInt16(nIntensityEnd);
+ mrStream.ReadUInt16(nStepCount);
+
+ rGradient.SetStyle(static_cast<GradientStyle>(nStyle));
+ rGradient.SetStartColor(aStartColor);
+ rGradient.SetEndColor(aEndColor);
+ if (nAngle > 3600)
+ {
+ SAL_WARN("vcl", "angle out of range " << nAngle);
+ nAngle = 0;
+ }
+ rGradient.SetAngle(Degree10(nAngle));
+ rGradient.SetBorder(nBorder);
+ rGradient.SetOfsX(nOffsetX);
+ rGradient.SetOfsY(nOffsetY);
+ rGradient.SetStartIntensity(nIntensityStart);
+ rGradient.SetEndIntensity(nIntensityEnd);
+ rGradient.SetSteps(nStepCount);
+}
+
+void TypeSerializer::writeGradient(const Gradient& rGradient)
+{
+ VersionCompatWrite aCompat(mrStream, 1);
+
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(rGradient.GetStyle()));
+ writeColor(rGradient.GetStartColor());
+ writeColor(rGradient.GetEndColor());
+ mrStream.WriteUInt16(rGradient.GetAngle().get());
+ mrStream.WriteUInt16(rGradient.GetBorder());
+ mrStream.WriteUInt16(rGradient.GetOfsX());
+ mrStream.WriteUInt16(rGradient.GetOfsY());
+ mrStream.WriteUInt16(rGradient.GetStartIntensity());
+ mrStream.WriteUInt16(rGradient.GetEndIntensity());
+ mrStream.WriteUInt16(rGradient.GetSteps());
+}
+
+void TypeSerializer::readGfxLink(GfxLink& rGfxLink)
+{
+ sal_uInt16 nType = 0;
+ sal_uInt32 nDataSize = 0;
+ sal_uInt32 nUserId = 0;
+
+ Size aSize;
+ MapMode aMapMode;
+ bool bMapAndSizeValid = false;
+
+ {
+ VersionCompatRead aCompat(mrStream);
+
+ // Version 1
+ mrStream.ReadUInt16(nType);
+ mrStream.ReadUInt32(nDataSize);
+ mrStream.ReadUInt32(nUserId);
+
+ if (aCompat.GetVersion() >= 2)
+ {
+ readSize(aSize);
+ readMapMode(aMapMode);
+ bMapAndSizeValid = true;
+ }
+ }
+
+ auto nRemainingData = mrStream.remainingSize();
+ if (nDataSize > nRemainingData)
+ {
+ SAL_WARN("vcl", "graphic link stream is smaller than requested size");
+ nDataSize = nRemainingData;
+ }
+
+ std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nDataSize]);
+ mrStream.ReadBytes(pBuffer.get(), nDataSize);
+
+ rGfxLink = GfxLink(std::move(pBuffer), nDataSize, static_cast<GfxLinkType>(nType));
+ rGfxLink.SetUserId(nUserId);
+
+ if (bMapAndSizeValid)
+ {
+ rGfxLink.SetPrefSize(aSize);
+ rGfxLink.SetPrefMapMode(aMapMode);
+ }
+}
+
+void TypeSerializer::writeGfxLink(const GfxLink& rGfxLink)
+{
+ {
+ VersionCompatWrite aCompat(mrStream, 2);
+
+ // Version 1
+ mrStream.WriteUInt16(sal_uInt16(rGfxLink.GetType()));
+ mrStream.WriteUInt32(rGfxLink.GetDataSize());
+ mrStream.WriteUInt32(rGfxLink.GetUserId());
+
+ // Version 2
+ writeSize(rGfxLink.GetPrefSize());
+ writeMapMode(rGfxLink.GetPrefMapMode());
+ }
+
+ if (rGfxLink.GetDataSize())
+ {
+ if (rGfxLink.GetData())
+ mrStream.WriteBytes(rGfxLink.GetData(), rGfxLink.GetDataSize());
+ }
+}
+
+namespace
+{
+#define NATIVE_FORMAT_50 COMPAT_FORMAT('N', 'A', 'T', '5')
+
+} // end anonymous namespace
+
+void TypeSerializer::readGraphic(Graphic& rGraphic)
+{
+ if (mrStream.GetError())
+ return;
+
+ const sal_uInt64 nInitialStreamPosition = mrStream.Tell();
+ sal_uInt32 nType;
+
+ // if there is no more data, avoid further expensive
+ // reading which will create VDevs and other stuff, just to
+ // read nothing.
+ if (mrStream.remainingSize() < 4)
+ return;
+
+ // read Id
+ mrStream.ReadUInt32(nType);
+
+ if (NATIVE_FORMAT_50 == nType)
+ {
+ Graphic aGraphic;
+ GfxLink aLink;
+
+ // read compat info, destructor writes stuff into the header
+ {
+ VersionCompatRead aCompat(mrStream);
+ }
+
+ readGfxLink(aLink);
+
+ if (!mrStream.GetError() && aLink.LoadNative(aGraphic))
+ {
+ if (aLink.IsPrefMapModeValid())
+ aGraphic.SetPrefMapMode(aLink.GetPrefMapMode());
+
+ if (aLink.IsPrefSizeValid())
+ aGraphic.SetPrefSize(aLink.GetPrefSize());
+ }
+ else
+ {
+ mrStream.Seek(nInitialStreamPosition);
+ mrStream.SetError(ERRCODE_IO_WRONGFORMAT);
+ }
+ rGraphic = aGraphic;
+ }
+ else
+ {
+ BitmapEx aBitmapEx;
+ const SvStreamEndian nOldFormat = mrStream.GetEndian();
+
+ mrStream.SeekRel(-4);
+ mrStream.SetEndian(SvStreamEndian::LITTLE);
+ ReadDIBBitmapEx(aBitmapEx, mrStream);
+
+ if (!mrStream.GetError())
+ {
+ sal_uInt32 nMagic1 = 0;
+ sal_uInt32 nMagic2 = 0;
+ if (mrStream.remainingSize() >= 8)
+ {
+ sal_uInt64 nBeginPosition = mrStream.Tell();
+ mrStream.ReadUInt32(nMagic1);
+ mrStream.ReadUInt32(nMagic2);
+ mrStream.Seek(nBeginPosition);
+ }
+ if (!mrStream.GetError())
+ {
+ if (nMagic1 == 0x5344414e && nMagic2 == 0x494d4931)
+ {
+ Animation aAnimation;
+ ReadAnimation(mrStream, aAnimation);
+
+ // #108077# manually set loaded BmpEx to Animation
+ // (which skips loading its BmpEx if already done)
+ aAnimation.SetBitmapEx(aBitmapEx);
+ rGraphic = Graphic(aAnimation);
+ }
+ else
+ {
+ rGraphic = Graphic(aBitmapEx);
+ }
+ }
+ else
+ {
+ mrStream.ResetError();
+ }
+ }
+ else
+ {
+ GDIMetaFile aMetaFile;
+
+ mrStream.Seek(nInitialStreamPosition);
+ mrStream.ResetError();
+ SvmReader aReader(mrStream);
+ aReader.Read(aMetaFile);
+
+ if (!mrStream.GetError())
+ {
+ rGraphic = Graphic(aMetaFile);
+ }
+ else
+ {
+ ErrCode nOriginalError = mrStream.GetErrorCode();
+ // try to stream in Svg defining data (length, byte array and evtl. path)
+ // See below (operator<<) for more information
+ sal_uInt32 nMagic;
+ mrStream.Seek(nInitialStreamPosition);
+ mrStream.ResetError();
+ mrStream.ReadUInt32(nMagic);
+
+ if (constSvgMagic == nMagic || constWmfMagic == nMagic || constEmfMagic == nMagic
+ || constPdfMagic == nMagic)
+ {
+ sal_uInt32 nLength = 0;
+ mrStream.ReadUInt32(nLength);
+
+ if (nLength)
+ {
+ auto rData = std::make_unique<std::vector<sal_uInt8>>(nLength);
+ mrStream.ReadBytes(rData->data(), rData->size());
+ BinaryDataContainer aDataContainer(std::move(rData));
+
+ if (!mrStream.GetError())
+ {
+ VectorGraphicDataType aDataType(VectorGraphicDataType::Svg);
+
+ switch (nMagic)
+ {
+ case constWmfMagic:
+ aDataType = VectorGraphicDataType::Wmf;
+ break;
+ case constEmfMagic:
+ aDataType = VectorGraphicDataType::Emf;
+ break;
+ case constPdfMagic:
+ aDataType = VectorGraphicDataType::Pdf;
+ break;
+ }
+
+ auto aVectorGraphicDataPtr
+ = std::make_shared<VectorGraphicData>(aDataContainer, aDataType);
+ rGraphic = Graphic(aVectorGraphicDataPtr);
+ }
+ }
+ }
+ else
+ {
+ mrStream.SetError(nOriginalError);
+ }
+
+ mrStream.Seek(nInitialStreamPosition);
+ }
+ }
+ mrStream.SetEndian(nOldFormat);
+ }
+}
+
+void TypeSerializer::writeGraphic(const Graphic& rGraphic)
+{
+ Graphic aGraphic(rGraphic);
+
+ if (!aGraphic.makeAvailable())
+ return;
+
+ auto pGfxLink = aGraphic.GetSharedGfxLink();
+
+ if (mrStream.GetVersion() >= SOFFICE_FILEFORMAT_50
+ && (mrStream.GetCompressMode() & SvStreamCompressFlags::NATIVE) && pGfxLink
+ && pGfxLink->IsNative())
+ {
+ // native format
+ mrStream.WriteUInt32(NATIVE_FORMAT_50);
+
+ // write compat info, destructor writes stuff into the header
+ {
+ VersionCompatWrite aCompat(mrStream, 1);
+ }
+ pGfxLink->SetPrefMapMode(aGraphic.GetPrefMapMode());
+ pGfxLink->SetPrefSize(aGraphic.GetPrefSize());
+ writeGfxLink(*pGfxLink);
+ }
+ else
+ {
+ // own format
+ const SvStreamEndian nOldFormat = mrStream.GetEndian();
+ mrStream.SetEndian(SvStreamEndian::LITTLE);
+
+ switch (aGraphic.GetType())
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ auto pVectorGraphicData = aGraphic.getVectorGraphicData();
+ if (pVectorGraphicData)
+ {
+ // stream out Vector Graphic defining data (length, byte array and evtl. path)
+ // this is used e.g. in swapping out graphic data and in transporting it over UNO API
+ // as sequence of bytes, but AFAIK not written anywhere to any kind of file, so it should be
+ // no problem to extend it; only used at runtime
+ switch (pVectorGraphicData->getType())
+ {
+ case VectorGraphicDataType::Wmf:
+ {
+ mrStream.WriteUInt32(constWmfMagic);
+ break;
+ }
+ case VectorGraphicDataType::Emf:
+ {
+ mrStream.WriteUInt32(constEmfMagic);
+ break;
+ }
+ case VectorGraphicDataType::Svg:
+ {
+ mrStream.WriteUInt32(constSvgMagic);
+ break;
+ }
+ case VectorGraphicDataType::Pdf:
+ {
+ mrStream.WriteUInt32(constPdfMagic);
+ break;
+ }
+ }
+
+ sal_uInt32 nSize = pVectorGraphicData->getBinaryDataContainer().getSize();
+ mrStream.WriteUInt32(nSize);
+ mrStream.WriteBytes(pVectorGraphicData->getBinaryDataContainer().getData(),
+ nSize);
+ // For backwards compatibility, used to serialize path
+ mrStream.WriteUniOrByteString(u"", mrStream.GetStreamCharSet());
+ }
+ else if (aGraphic.IsAnimated())
+ {
+ WriteAnimation(mrStream, aGraphic.GetAnimation());
+ }
+ else
+ {
+ WriteDIBBitmapEx(aGraphic.GetBitmapEx(), mrStream);
+ }
+ }
+ break;
+
+ default:
+ {
+ if (aGraphic.IsSupportedGraphic())
+ {
+ if (!mrStream.GetError())
+ {
+ SvmWriter aWriter(mrStream);
+ aWriter.Write(rGraphic.GetGDIMetaFile());
+ }
+ }
+ }
+ break;
+ }
+ mrStream.SetEndian(nOldFormat);
+ }
+}
+
+bool TooLargeScaleForMapMode(const Fraction& rScale, int nDPI)
+{
+ // ImplLogicToPixel will multiply its values by this numerator * dpi and then double that
+ // result before dividing
+ if (rScale.GetNumerator() > std::numeric_limits<sal_Int32>::max() / nDPI / 2)
+ return true;
+ if (rScale.GetNumerator() < std::numeric_limits<sal_Int32>::min() / nDPI / 2)
+ return true;
+ return false;
+}
+
+static bool UselessScaleForMapMode(const Fraction& rScale)
+{
+ if (!rScale.IsValid())
+ return true;
+ if (TooLargeScaleForMapMode(rScale, 96))
+ return true;
+ if (static_cast<double>(rScale) < 0.0)
+ return true;
+ return false;
+}
+
+void TypeSerializer::readMapMode(MapMode& rMapMode)
+{
+ VersionCompatRead aCompat(mrStream);
+ sal_uInt16 nTmp16(0);
+ Point aOrigin;
+ Fraction aScaleX;
+ Fraction aScaleY;
+ bool bSimple(true);
+
+ mrStream.ReadUInt16(nTmp16);
+ MapUnit eUnit = static_cast<MapUnit>(nTmp16);
+ readPoint(aOrigin);
+ readFraction(aScaleX);
+ readFraction(aScaleY);
+ mrStream.ReadCharAsBool(bSimple);
+
+ const bool bBogus = UselessScaleForMapMode(aScaleX) || UselessScaleForMapMode(aScaleY);
+ SAL_WARN_IF(bBogus, "vcl", "invalid scale");
+
+ if (bSimple || bBogus)
+ rMapMode = MapMode(eUnit);
+ else
+ rMapMode = MapMode(eUnit, aOrigin, aScaleX, aScaleY);
+}
+
+void TypeSerializer::writeMapMode(MapMode const& rMapMode)
+{
+ VersionCompatWrite aCompat(mrStream, 1);
+
+ mrStream.WriteUInt16(sal_uInt16(rMapMode.GetMapUnit()));
+ writePoint(rMapMode.GetOrigin());
+ writeFraction(rMapMode.GetScaleX());
+ writeFraction(rMapMode.GetScaleY());
+ mrStream.WriteBool(rMapMode.IsSimple());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/VerticalOrientationData.cxx b/vcl/source/gdi/VerticalOrientationData.cxx
new file mode 100644
index 000000000..1016b3656
--- /dev/null
+++ b/vcl/source/gdi/VerticalOrientationData.cxx
@@ -0,0 +1,78 @@
+
+/*
+ * 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/.
+ */
+
+/*
+ * Derived from the Unicode Character Database by genVerticalOrientationData.pl
+ *
+ * For Unicode terms of use, see http://www.unicode.org/terms_of_use.html
+ */
+
+/*
+ * Created on Wed Nov 9 21:29:02 2016 from UCD data files with version info:
+ *
+
+
+# VerticalOrientation-17.txt
+# Date: 2016-10-20, 07:00:00 GMT [EM, KI, LI]
+
+ *
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
+
+#define kVerticalOrientationMaxPlane 16
+#define kVerticalOrientationIndexBits 9
+#define kVerticalOrientationCharBits 7
+static const uint8_t sVerticalOrientationPlanes[16] = {1,2,2,3,3,3,3,3,3,3,3,3,3,3,2,2};
+
+static const uint8_t sVerticalOrientationPages[4][512] = {
+ {0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,4,3,3,3,3,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,7,8,9,10,0,11,12,13,3,0,14,15,3,16,17,0,0,0,0,0,0,18,19,0,0,0,0,0,3,3,3,20,21,22,23,3,3,24,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,25,0,0,0,0,0,0,0,0,26,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,27,0,28,29},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,0,0,3,0,0,0,0,0,0,0,0,0,3,3,3,3,3,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,32,3,3,3,3,3,3,3,3,3,3,3,0,0,3,3,0,0,0,0,0,0,0,0,0,0,0,0},
+ {3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,33},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+};
+
+static const uint8_t sVerticalOrientationValues[34][128] = {
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,0,1,1,1,1,0,0,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,0,1,1,1,0,0,1,0,0,1,1,1,1,1,1,0,0,0,0,0,0,1,0,1,0,1,0,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,3,3,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,2,2,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,2,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,2,0,2,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,2,2,0,0,0,0,2,2,0,0,0,3,2,0,2,0,2,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,2,2,0,0,0,0,0,3,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2},
+ {2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,2,2,2,0,0,0,0,0,1,3,3,3,3,3,3,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,2,0,0,0,0,0,0,3,3,0,0,2,1,2,0,0,0,0,0,0,0,0,0,0,0,3,3,1,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,3,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,1},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1}
+};
+/*
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
diff --git a/vcl/source/gdi/WidgetDefinition.cxx b/vcl/source/gdi/WidgetDefinition.cxx
new file mode 100644
index 000000000..3c93f1ac5
--- /dev/null
+++ b/vcl/source/gdi/WidgetDefinition.cxx
@@ -0,0 +1,186 @@
+/* -*- 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/.
+ *
+ */
+
+#include <widgetdraw/WidgetDefinition.hxx>
+
+#include <sal/config.h>
+#include <unordered_map>
+
+namespace vcl
+{
+std::shared_ptr<WidgetDefinitionPart> WidgetDefinition::getDefinition(ControlType eType,
+ ControlPart ePart)
+{
+ auto aIterator = maDefinitions.find(ControlTypeAndPart(eType, ePart));
+
+ if (aIterator != maDefinitions.end())
+ return aIterator->second;
+ return std::shared_ptr<WidgetDefinitionPart>();
+}
+
+std::vector<std::shared_ptr<WidgetDefinitionState>>
+WidgetDefinitionPart::getStates(ControlType eType, ControlPart ePart, ControlState eState,
+ ImplControlValue const& rValue)
+{
+ std::vector<std::shared_ptr<WidgetDefinitionState>> aStatesToAdd;
+
+ for (const auto& state : maStates)
+ {
+ bool bAdd = true;
+
+ if (state->msEnabled != "any"
+ && !((state->msEnabled == "true" && eState & ControlState::ENABLED)
+ || (state->msEnabled == "false" && !(eState & ControlState::ENABLED))))
+ bAdd = false;
+ if (state->msFocused != "any"
+ && !((state->msFocused == "true" && eState & ControlState::FOCUSED)
+ || (state->msFocused == "false" && !(eState & ControlState::FOCUSED))))
+ bAdd = false;
+ if (state->msPressed != "any"
+ && !((state->msPressed == "true" && eState & ControlState::PRESSED)
+ || (state->msPressed == "false" && !(eState & ControlState::PRESSED))))
+ bAdd = false;
+ if (state->msRollover != "any"
+ && !((state->msRollover == "true" && eState & ControlState::ROLLOVER)
+ || (state->msRollover == "false" && !(eState & ControlState::ROLLOVER))))
+ bAdd = false;
+ if (state->msDefault != "any"
+ && !((state->msDefault == "true" && eState & ControlState::DEFAULT)
+ || (state->msDefault == "false" && !(eState & ControlState::DEFAULT))))
+ bAdd = false;
+ if (state->msSelected != "any"
+ && !((state->msSelected == "true" && eState & ControlState::SELECTED)
+ || (state->msSelected == "false" && !(eState & ControlState::SELECTED))))
+ bAdd = false;
+
+ ButtonValue eButtonValue = rValue.getTristateVal();
+
+ if (state->msButtonValue != "any"
+ && !((state->msButtonValue == "true" && eButtonValue == ButtonValue::On)
+ || (state->msButtonValue == "false" && eButtonValue == ButtonValue::Off)))
+ {
+ bAdd = false;
+ }
+
+ OString sExtra = "any";
+
+ switch (eType)
+ {
+ case ControlType::TabItem:
+ {
+ auto const& rTabItemValue = static_cast<TabitemValue const&>(rValue);
+
+ if (rTabItemValue.isLeftAligned() && rTabItemValue.isRightAligned()
+ && rTabItemValue.isFirst() && rTabItemValue.isLast())
+ sExtra = "first_last";
+ else if (rTabItemValue.isLeftAligned() || rTabItemValue.isFirst())
+ sExtra = "first";
+ else if (rTabItemValue.isRightAligned() || rTabItemValue.isLast())
+ sExtra = "last";
+ else
+ sExtra = "middle";
+ }
+ break;
+ case ControlType::ListHeader:
+ {
+ if (ePart == ControlPart::Arrow)
+ {
+ if (rValue.getNumericVal() == 1)
+ sExtra = "down";
+ else
+ sExtra = "up";
+ }
+ }
+ break;
+ case ControlType::Pushbutton:
+ {
+ auto const& rPushButtonValue = static_cast<PushButtonValue const&>(rValue);
+ if (rPushButtonValue.mbIsAction)
+ sExtra = "action";
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (state->msExtra != "any" && state->msExtra != sExtra)
+ {
+ bAdd = false;
+ }
+
+ if (bAdd)
+ aStatesToAdd.push_back(state);
+ }
+
+ return aStatesToAdd;
+}
+
+WidgetDefinitionState::WidgetDefinitionState(OString const& sEnabled, OString const& sFocused,
+ OString const& sPressed, OString const& sRollover,
+ OString const& sDefault, OString const& sSelected,
+ OString const& sButtonValue, OString const& sExtra)
+ : msEnabled(sEnabled)
+ , msFocused(sFocused)
+ , msPressed(sPressed)
+ , msRollover(sRollover)
+ , msDefault(sDefault)
+ , msSelected(sSelected)
+ , msButtonValue(sButtonValue)
+ , msExtra(sExtra)
+{
+}
+
+void WidgetDefinitionState::addDrawRectangle(Color aStrokeColor, sal_Int32 nStrokeWidth,
+ Color aFillColor, float fX1, float fY1, float fX2,
+ float fY2, sal_Int32 nRx, sal_Int32 nRy)
+{
+ auto pCommand(std::make_shared<WidgetDrawActionRectangle>());
+ pCommand->maStrokeColor = aStrokeColor;
+ pCommand->maFillColor = aFillColor;
+ pCommand->mnStrokeWidth = nStrokeWidth;
+ pCommand->mnRx = nRx;
+ pCommand->mnRy = nRy;
+ pCommand->mfX1 = fX1;
+ pCommand->mfY1 = fY1;
+ pCommand->mfX2 = fX2;
+ pCommand->mfY2 = fY2;
+ mpWidgetDrawActions.push_back(std::move(pCommand));
+}
+
+void WidgetDefinitionState::addDrawLine(Color aStrokeColor, sal_Int32 nStrokeWidth, float fX1,
+ float fY1, float fX2, float fY2)
+{
+ auto pCommand(std::make_shared<WidgetDrawActionLine>());
+ pCommand->maStrokeColor = aStrokeColor;
+ pCommand->mnStrokeWidth = nStrokeWidth;
+ pCommand->mfX1 = fX1;
+ pCommand->mfY1 = fY1;
+ pCommand->mfX2 = fX2;
+ pCommand->mfY2 = fY2;
+ mpWidgetDrawActions.push_back(std::move(pCommand));
+}
+
+void WidgetDefinitionState::addDrawImage(OUString const& sSource)
+{
+ auto pCommand(std::make_shared<WidgetDrawActionImage>());
+ pCommand->msSource = sSource;
+ mpWidgetDrawActions.push_back(std::move(pCommand));
+}
+
+void WidgetDefinitionState::addDrawExternal(OUString const& sSource)
+{
+ auto pCommand(std::make_shared<WidgetDrawActionExternal>());
+ pCommand->msSource = sSource;
+ mpWidgetDrawActions.push_back(std::move(pCommand));
+}
+
+} // end vcl namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/WidgetDefinitionReader.cxx b/vcl/source/gdi/WidgetDefinitionReader.cxx
new file mode 100644
index 000000000..4f68c35e4
--- /dev/null
+++ b/vcl/source/gdi/WidgetDefinitionReader.cxx
@@ -0,0 +1,495 @@
+/* -*- 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/.
+ *
+ */
+
+#include <widgetdraw/WidgetDefinitionReader.hxx>
+
+#include <sal/config.h>
+#include <osl/file.hxx>
+#include <tools/stream.hxx>
+#include <o3tl/string_view.hxx>
+#include <unordered_map>
+
+namespace vcl
+{
+namespace
+{
+bool lcl_fileExists(OUString const& sFilename)
+{
+ osl::File aFile(sFilename);
+ osl::FileBase::RC eRC = aFile.open(osl_File_OpenFlag_Read);
+ return osl::FileBase::E_None == eRC;
+}
+
+int lcl_gethex(char aChar)
+{
+ if (aChar >= '0' && aChar <= '9')
+ return aChar - '0';
+ else if (aChar >= 'a' && aChar <= 'f')
+ return aChar - 'a' + 10;
+ else if (aChar >= 'A' && aChar <= 'F')
+ return aChar - 'A' + 10;
+ else
+ return 0;
+}
+
+bool readColor(OString const& rString, Color& rColor)
+{
+ if (rString.getLength() != 7)
+ return false;
+
+ const char aChar(rString[0]);
+
+ if (aChar != '#')
+ return false;
+
+ rColor.SetRed((lcl_gethex(rString[1]) << 4) | lcl_gethex(rString[2]));
+ rColor.SetGreen((lcl_gethex(rString[3]) << 4) | lcl_gethex(rString[4]));
+ rColor.SetBlue((lcl_gethex(rString[5]) << 4) | lcl_gethex(rString[6]));
+
+ return true;
+}
+
+bool readSetting(OString const& rInputString, OString& rOutputString)
+{
+ if (!rInputString.isEmpty())
+ rOutputString = rInputString;
+ return true;
+}
+
+OString getValueOrAny(OString const& rInputString)
+{
+ if (rInputString.isEmpty())
+ return "any";
+ return rInputString;
+}
+
+ControlPart xmlStringToControlPart(std::string_view sPart)
+{
+ if (o3tl::equalsIgnoreAsciiCase(sPart, "NONE"))
+ return ControlPart::NONE;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "Entire"))
+ return ControlPart::Entire;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "ListboxWindow"))
+ return ControlPart::ListboxWindow;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "Button"))
+ return ControlPart::Button;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "ButtonUp"))
+ return ControlPart::ButtonUp;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "ButtonDown"))
+ return ControlPart::ButtonDown;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "ButtonLeft"))
+ return ControlPart::ButtonLeft;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "ButtonRight"))
+ return ControlPart::ButtonRight;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "AllButtons"))
+ return ControlPart::AllButtons;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "SeparatorHorz"))
+ return ControlPart::SeparatorHorz;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "SeparatorVert"))
+ return ControlPart::SeparatorVert;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackHorzLeft"))
+ return ControlPart::TrackHorzLeft;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackVertUpper"))
+ return ControlPart::TrackVertUpper;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackHorzRight"))
+ return ControlPart::TrackHorzRight;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackVertLower"))
+ return ControlPart::TrackVertLower;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackHorzArea"))
+ return ControlPart::TrackHorzArea;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackVertArea"))
+ return ControlPart::TrackVertArea;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "Arrow"))
+ return ControlPart::Arrow;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "ThumbHorz"))
+ return ControlPart::ThumbHorz;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "ThumbVert"))
+ return ControlPart::ThumbVert;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "MenuItem"))
+ return ControlPart::MenuItem;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "MenuItemCheckMark"))
+ return ControlPart::MenuItemCheckMark;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "MenuItemRadioMark"))
+ return ControlPart::MenuItemRadioMark;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "Separator"))
+ return ControlPart::Separator;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "SubmenuArrow"))
+ return ControlPart::SubmenuArrow;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "SubEdit"))
+ return ControlPart::SubEdit;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "DrawBackgroundHorz"))
+ return ControlPart::DrawBackgroundHorz;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "DrawBackgroundVert"))
+ return ControlPart::DrawBackgroundVert;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "TabsDrawRtl"))
+ return ControlPart::TabsDrawRtl;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "HasBackgroundTexture"))
+ return ControlPart::HasBackgroundTexture;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "HasThreeButtons"))
+ return ControlPart::HasThreeButtons;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "BackgroundWindow"))
+ return ControlPart::BackgroundWindow;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "BackgroundDialog"))
+ return ControlPart::BackgroundDialog;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "Border"))
+ return ControlPart::Border;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "Focus"))
+ return ControlPart::Focus;
+ return ControlPart::NONE;
+}
+
+bool getControlTypeForXmlString(OString const& rString, ControlType& reType)
+{
+ static std::unordered_map<OString, ControlType> aPartMap = {
+ { "pushbutton", ControlType::Pushbutton },
+ { "radiobutton", ControlType::Radiobutton },
+ { "checkbox", ControlType::Checkbox },
+ { "combobox", ControlType::Combobox },
+ { "editbox", ControlType::Editbox },
+ { "listbox", ControlType::Listbox },
+ { "scrollbar", ControlType::Scrollbar },
+ { "spinbox", ControlType::Spinbox },
+ { "slider", ControlType::Slider },
+ { "fixedline", ControlType::Fixedline },
+ { "progress", ControlType::Progress },
+ { "tabitem", ControlType::TabItem },
+ { "tabheader", ControlType::TabHeader },
+ { "tabpane", ControlType::TabPane },
+ { "tabbody", ControlType::TabBody },
+ { "frame", ControlType::Frame },
+ { "windowbackground", ControlType::WindowBackground },
+ { "toolbar", ControlType::Toolbar },
+ { "listnode", ControlType::ListNode },
+ { "listnet", ControlType::ListNet },
+ { "listheader", ControlType::ListHeader },
+ { "menubar", ControlType::Menubar },
+ { "menupopup", ControlType::MenuPopup },
+ { "tooltip", ControlType::Tooltip },
+ };
+
+ auto const& rIterator = aPartMap.find(rString);
+ if (rIterator != aPartMap.end())
+ {
+ reType = rIterator->second;
+ return true;
+ }
+ return false;
+}
+
+} // end anonymous namespace
+
+WidgetDefinitionReader::WidgetDefinitionReader(OUString const& rDefinitionFile,
+ OUString const& rResourcePath)
+ : m_rDefinitionFile(rDefinitionFile)
+ , m_rResourcePath(rResourcePath)
+{
+}
+
+void WidgetDefinitionReader::readDrawingDefinition(
+ tools::XmlWalker& rWalker, const std::shared_ptr<WidgetDefinitionState>& rpState)
+{
+ rWalker.children();
+ while (rWalker.isValid())
+ {
+ if (rWalker.name() == "rect")
+ {
+ Color aStrokeColor;
+ readColor(rWalker.attribute("stroke"), aStrokeColor);
+ Color aFillColor;
+ readColor(rWalker.attribute("fill"), aFillColor);
+ OString sStrokeWidth = rWalker.attribute("stroke-width");
+ sal_Int32 nStrokeWidth = -1;
+ if (!sStrokeWidth.isEmpty())
+ nStrokeWidth = sStrokeWidth.toInt32();
+
+ sal_Int32 nRx = -1;
+ OString sRx = rWalker.attribute("rx");
+ if (!sRx.isEmpty())
+ nRx = sRx.toInt32();
+
+ sal_Int32 nRy = -1;
+ OString sRy = rWalker.attribute("ry");
+ if (!sRy.isEmpty())
+ nRy = sRy.toInt32();
+
+ OString sX1 = rWalker.attribute("x1");
+ float fX1 = sX1.isEmpty() ? 0.0 : sX1.toFloat();
+
+ OString sY1 = rWalker.attribute("y1");
+ float fY1 = sY1.isEmpty() ? 0.0 : sY1.toFloat();
+
+ OString sX2 = rWalker.attribute("x2");
+ float fX2 = sX2.isEmpty() ? 1.0 : sX2.toFloat();
+
+ OString sY2 = rWalker.attribute("y2");
+ float fY2 = sY2.isEmpty() ? 1.0 : sY2.toFloat();
+
+ rpState->addDrawRectangle(aStrokeColor, nStrokeWidth, aFillColor, fX1, fY1, fX2, fY2,
+ nRx, nRy);
+ }
+ else if (rWalker.name() == "line")
+ {
+ Color aStrokeColor;
+ readColor(rWalker.attribute("stroke"), aStrokeColor);
+
+ OString sStrokeWidth = rWalker.attribute("stroke-width");
+ sal_Int32 nStrokeWidth = -1;
+ if (!sStrokeWidth.isEmpty())
+ nStrokeWidth = sStrokeWidth.toInt32();
+
+ OString sX1 = rWalker.attribute("x1");
+ float fX1 = sX1.isEmpty() ? -1.0 : sX1.toFloat();
+
+ OString sY1 = rWalker.attribute("y1");
+ float fY1 = sY1.isEmpty() ? -1.0 : sY1.toFloat();
+
+ OString sX2 = rWalker.attribute("x2");
+ float fX2 = sX2.isEmpty() ? -1.0 : sX2.toFloat();
+
+ OString sY2 = rWalker.attribute("y2");
+ float fY2 = sY2.isEmpty() ? -1.0 : sY2.toFloat();
+
+ rpState->addDrawLine(aStrokeColor, nStrokeWidth, fX1, fY1, fX2, fY2);
+ }
+ else if (rWalker.name() == "image")
+ {
+ OString sSource = rWalker.attribute("source");
+ rpState->addDrawImage(m_rResourcePath
+ + OStringToOUString(sSource, RTL_TEXTENCODING_UTF8));
+ }
+ else if (rWalker.name() == "external")
+ {
+ OString sSource = rWalker.attribute("source");
+ rpState->addDrawExternal(m_rResourcePath
+ + OStringToOUString(sSource, RTL_TEXTENCODING_UTF8));
+ }
+ rWalker.next();
+ }
+ rWalker.parent();
+}
+
+void WidgetDefinitionReader::readDefinition(tools::XmlWalker& rWalker,
+ WidgetDefinition& rWidgetDefinition, ControlType eType)
+{
+ rWalker.children();
+ while (rWalker.isValid())
+ {
+ if (rWalker.name() == "part")
+ {
+ OString sPart = rWalker.attribute("value");
+ ControlPart ePart = xmlStringToControlPart(sPart);
+
+ std::shared_ptr<WidgetDefinitionPart> pPart = std::make_shared<WidgetDefinitionPart>();
+
+ OString sWidth = rWalker.attribute("width");
+ if (!sWidth.isEmpty())
+ {
+ sal_Int32 nWidth = sWidth.isEmpty() ? 0 : sWidth.toInt32();
+ pPart->mnWidth = nWidth;
+ }
+
+ OString sHeight = rWalker.attribute("height");
+ if (!sHeight.isEmpty())
+ {
+ sal_Int32 nHeight = sHeight.isEmpty() ? 0 : sHeight.toInt32();
+ pPart->mnHeight = nHeight;
+ }
+
+ OString sMarginHeight = rWalker.attribute("margin-height");
+ if (!sMarginHeight.isEmpty())
+ {
+ sal_Int32 nMarginHeight = sMarginHeight.isEmpty() ? 0 : sMarginHeight.toInt32();
+ pPart->mnMarginHeight = nMarginHeight;
+ }
+
+ OString sMarginWidth = rWalker.attribute("margin-width");
+ if (!sMarginWidth.isEmpty())
+ {
+ sal_Int32 nMarginWidth = sMarginWidth.isEmpty() ? 0 : sMarginWidth.toInt32();
+ pPart->mnMarginWidth = nMarginWidth;
+ }
+
+ OString sOrientation = rWalker.attribute("orientation");
+ if (!sOrientation.isEmpty())
+ {
+ pPart->msOrientation = sOrientation;
+ }
+
+ rWidgetDefinition.maDefinitions.emplace(ControlTypeAndPart(eType, ePart), pPart);
+ readPart(rWalker, pPart);
+ }
+ rWalker.next();
+ }
+ rWalker.parent();
+}
+
+void WidgetDefinitionReader::readPart(tools::XmlWalker& rWalker,
+ std::shared_ptr<WidgetDefinitionPart> rpPart)
+{
+ rWalker.children();
+ while (rWalker.isValid())
+ {
+ if (rWalker.name() == "state")
+ {
+ OString sEnabled = getValueOrAny(rWalker.attribute("enabled"));
+ OString sFocused = getValueOrAny(rWalker.attribute("focused"));
+ OString sPressed = getValueOrAny(rWalker.attribute("pressed"));
+ OString sRollover = getValueOrAny(rWalker.attribute("rollover"));
+ OString sDefault = getValueOrAny(rWalker.attribute("default"));
+ OString sSelected = getValueOrAny(rWalker.attribute("selected"));
+ OString sButtonValue = getValueOrAny(rWalker.attribute("button-value"));
+ OString sExtra = getValueOrAny(rWalker.attribute("extra"));
+
+ std::shared_ptr<WidgetDefinitionState> pState = std::make_shared<WidgetDefinitionState>(
+ sEnabled, sFocused, sPressed, sRollover, sDefault, sSelected, sButtonValue, sExtra);
+
+ rpPart->maStates.push_back(pState);
+ readDrawingDefinition(rWalker, pState);
+ }
+ rWalker.next();
+ }
+ rWalker.parent();
+}
+
+bool WidgetDefinitionReader::read(WidgetDefinition& rWidgetDefinition)
+{
+ if (!lcl_fileExists(m_rDefinitionFile))
+ return false;
+
+ auto pStyle = std::make_shared<WidgetDefinitionStyle>();
+
+ std::unordered_map<OString, Color*> aStyleColorMap = {
+ { "faceColor", &pStyle->maFaceColor },
+ { "checkedColor", &pStyle->maCheckedColor },
+ { "lightColor", &pStyle->maLightColor },
+ { "lightBorderColor", &pStyle->maLightBorderColor },
+ { "shadowColor", &pStyle->maShadowColor },
+ { "darkShadowColor", &pStyle->maDarkShadowColor },
+ { "buttonTextColor", &pStyle->maButtonTextColor },
+ { "defaultActionButtonTextColor", &pStyle->maDefaultActionButtonTextColor },
+ { "actionButtonTextColor", &pStyle->maActionButtonTextColor },
+ { "actionButtonRolloverTextColor", &pStyle->maActionButtonRolloverTextColor },
+ { "buttonRolloverTextColor", &pStyle->maButtonRolloverTextColor },
+ { "radioCheckTextColor", &pStyle->maRadioCheckTextColor },
+ { "groupTextColor", &pStyle->maGroupTextColor },
+ { "labelTextColor", &pStyle->maLabelTextColor },
+ { "windowColor", &pStyle->maWindowColor },
+ { "windowTextColor", &pStyle->maWindowTextColor },
+ { "dialogColor", &pStyle->maDialogColor },
+ { "dialogTextColor", &pStyle->maDialogTextColor },
+ { "workspaceColor", &pStyle->maWorkspaceColor },
+ { "monoColor", &pStyle->maMonoColor },
+ { "fieldColor", &pStyle->maFieldColor },
+ { "fieldTextColor", &pStyle->maFieldTextColor },
+ { "fieldRolloverTextColor", &pStyle->maFieldRolloverTextColor },
+ { "activeColor", &pStyle->maActiveColor },
+ { "activeTextColor", &pStyle->maActiveTextColor },
+ { "activeBorderColor", &pStyle->maActiveBorderColor },
+ { "deactiveColor", &pStyle->maDeactiveColor },
+ { "deactiveTextColor", &pStyle->maDeactiveTextColor },
+ { "deactiveBorderColor", &pStyle->maDeactiveBorderColor },
+ { "menuColor", &pStyle->maMenuColor },
+ { "menuBarColor", &pStyle->maMenuBarColor },
+ { "menuBarRolloverColor", &pStyle->maMenuBarRolloverColor },
+ { "menuBorderColor", &pStyle->maMenuBorderColor },
+ { "menuTextColor", &pStyle->maMenuTextColor },
+ { "menuBarTextColor", &pStyle->maMenuBarTextColor },
+ { "menuBarRolloverTextColor", &pStyle->maMenuBarRolloverTextColor },
+ { "menuBarHighlightTextColor", &pStyle->maMenuBarHighlightTextColor },
+ { "menuHighlightColor", &pStyle->maMenuHighlightColor },
+ { "menuHighlightTextColor", &pStyle->maMenuHighlightTextColor },
+ { "highlightColor", &pStyle->maHighlightColor },
+ { "highlightTextColor", &pStyle->maHighlightTextColor },
+ { "activeTabColor", &pStyle->maActiveTabColor },
+ { "inactiveTabColor", &pStyle->maInactiveTabColor },
+ { "tabTextColor", &pStyle->maTabTextColor },
+ { "tabRolloverTextColor", &pStyle->maTabRolloverTextColor },
+ { "tabHighlightTextColor", &pStyle->maTabHighlightTextColor },
+ { "disableColor", &pStyle->maDisableColor },
+ { "helpColor", &pStyle->maHelpColor },
+ { "helpTextColor", &pStyle->maHelpTextColor },
+ { "linkColor", &pStyle->maLinkColor },
+ { "visitedLinkColor", &pStyle->maVisitedLinkColor },
+ { "toolTextColor", &pStyle->maToolTextColor },
+ { "fontColor", &pStyle->maFontColor },
+ };
+
+ rWidgetDefinition.mpStyle = pStyle;
+
+ auto pSettings = std::make_shared<WidgetDefinitionSettings>();
+
+ std::unordered_map<OString, OString*> aSettingMap = {
+ { "noActiveTabTextRaise", &pSettings->msNoActiveTabTextRaise },
+ { "centeredTabs", &pSettings->msCenteredTabs },
+ { "listBoxEntryMargin", &pSettings->msListBoxEntryMargin },
+ { "defaultFontSize", &pSettings->msDefaultFontSize },
+ { "titleHeight", &pSettings->msTitleHeight },
+ { "floatTitleHeight", &pSettings->msFloatTitleHeight },
+ { "listBoxPreviewDefaultLogicWidth", &pSettings->msListBoxPreviewDefaultLogicWidth },
+ { "listBoxPreviewDefaultLogicHeight", &pSettings->msListBoxPreviewDefaultLogicHeight },
+ };
+
+ rWidgetDefinition.mpSettings = pSettings;
+
+ SvFileStream aFileStream(m_rDefinitionFile, StreamMode::READ);
+
+ tools::XmlWalker aWalker;
+ if (!aWalker.open(&aFileStream))
+ return false;
+
+ if (aWalker.name() != "widgets")
+ return false;
+
+ aWalker.children();
+ while (aWalker.isValid())
+ {
+ ControlType eType;
+ if (aWalker.name() == "style")
+ {
+ aWalker.children();
+ while (aWalker.isValid())
+ {
+ auto pair = aStyleColorMap.find(aWalker.name());
+ if (pair != aStyleColorMap.end())
+ {
+ readColor(aWalker.attribute("value"), *pair->second);
+ }
+ aWalker.next();
+ }
+ aWalker.parent();
+ }
+ if (aWalker.name() == "settings")
+ {
+ aWalker.children();
+ while (aWalker.isValid())
+ {
+ auto pair = aSettingMap.find(aWalker.name());
+ if (pair != aSettingMap.end())
+ {
+ readSetting(aWalker.attribute("value"), *pair->second);
+ }
+ aWalker.next();
+ }
+ aWalker.parent();
+ }
+ else if (getControlTypeForXmlString(aWalker.name(), eType))
+ {
+ readDefinition(aWalker, rWidgetDefinition, eType);
+ }
+ aWalker.next();
+ }
+ aWalker.parent();
+
+ return true;
+}
+
+} // end vcl namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/configsettings.cxx b/vcl/source/gdi/configsettings.cxx
new file mode 100644
index 000000000..5586f67a6
--- /dev/null
+++ b/vcl/source/gdi/configsettings.cxx
@@ -0,0 +1,135 @@
+/* -*- 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 <configsettings.hxx>
+
+#include <svdata.hxx>
+
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <o3tl/any.hxx>
+#include <sal/log.hxx>
+
+using namespace utl;
+using namespace vcl;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::container;
+
+constexpr OUStringLiteral SETTINGS_CONFIGNODE = u"VCL/Settings";
+
+SettingsConfigItem* SettingsConfigItem::get()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( ! pSVData->mpSettingsConfigItem )
+ pSVData->mpSettingsConfigItem.reset( new SettingsConfigItem() );
+ return pSVData->mpSettingsConfigItem.get();
+}
+
+SettingsConfigItem::SettingsConfigItem()
+ : ConfigItem( SETTINGS_CONFIGNODE, ConfigItemMode::NONE ),
+ m_aSettings( 0 )
+{
+ getValues();
+}
+
+SettingsConfigItem::~SettingsConfigItem()
+{
+ assert(!IsModified()); // should have been committed
+}
+
+void SettingsConfigItem::ImplCommit()
+{
+ for (auto const& setting : m_aSettings)
+ {
+ OUString aKeyName( setting.first );
+ /*bool bAdded =*/ AddNode( OUString(), aKeyName );
+ Sequence< PropertyValue > aValues( setting.second.size() );
+ PropertyValue* pValues = aValues.getArray();
+ int nIndex = 0;
+ for (auto const& elem : setting.second)
+ {
+ pValues[nIndex].Name = aKeyName + "/" + elem.first;
+ pValues[nIndex].Handle = 0;
+ pValues[nIndex].Value <<= elem.second;
+ pValues[nIndex].State = PropertyState_DIRECT_VALUE;
+ nIndex++;
+ }
+ ReplaceSetProperties( aKeyName, aValues );
+ }
+}
+
+void SettingsConfigItem::Notify( const Sequence< OUString >& )
+{
+ getValues();
+}
+
+void SettingsConfigItem::getValues()
+{
+ m_aSettings.clear();
+
+ const Sequence< OUString > aNames( GetNodeNames( OUString() ) );
+
+ for( const auto& aKeyName : aNames )
+ {
+#if OSL_DEBUG_LEVEL > 2
+ SAL_INFO( "vcl", "found settings data for " << aKeyName );
+#endif
+ const Sequence< OUString > aKeys( GetNodeNames( aKeyName ) );
+ Sequence< OUString > aSettingsKeys( aKeys.getLength() );
+ std::transform(aKeys.begin(), aKeys.end(), aSettingsKeys.getArray(),
+ [&aKeyName](const OUString& rKey) -> OUString { return aKeyName + "/" + rKey; });
+ const Sequence< Any > aValues( GetProperties( aSettingsKeys ) );
+ for( int i = 0; i < aValues.getLength(); i++ )
+ {
+ if( auto pLine = o3tl::tryAccess<OUString>(aValues[i]) )
+ {
+ if( !pLine->isEmpty() )
+ m_aSettings[ aKeyName ][ aKeys[i] ] = *pLine;
+#if OSL_DEBUG_LEVEL > 2
+ SAL_INFO( "vcl", " \"" << aKeys.getConstArray()[i] << "\"=\"" << *pLine << "\"" );
+#endif
+ }
+ }
+ }
+}
+
+OUString SettingsConfigItem::getValue( const OUString& rGroup, const OUString& rKey ) const
+{
+ std::unordered_map< OUString, SmallOUStrMap >::const_iterator group = m_aSettings.find( rGroup );
+ if( group == m_aSettings.end() || group->second.find( rKey ) == group->second.end() )
+ {
+ return OUString();
+ }
+ return group->second.find(rKey)->second;
+}
+
+void SettingsConfigItem::setValue( const OUString& rGroup, const OUString& rKey, const OUString& rValue )
+{
+ bool bModified = m_aSettings[ rGroup ][ rKey ] != rValue;
+ if( bModified )
+ {
+ m_aSettings[ rGroup ][ rKey ] = rValue;
+ SetModified();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/cvtgrf.cxx b/vcl/source/gdi/cvtgrf.cxx
new file mode 100644
index 000000000..51fb6df10
--- /dev/null
+++ b/vcl/source/gdi/cvtgrf.cxx
@@ -0,0 +1,69 @@
+/* -*- 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 <vcl/cvtgrf.hxx>
+#include <tools/stream.hxx>
+
+#include <svdata.hxx>
+
+// Callback
+GraphicConverter::GraphicConverter()
+{
+}
+
+ErrCode GraphicConverter::Import( SvStream& rIStm, Graphic& rGraphic, ConvertDataFormat nFormat )
+{
+ GraphicConverter* pCvt = ImplGetSVData()->maGDIData.mxGrfConverter.get();
+ ErrCode nRet = ERRCODE_IO_GENERAL;
+
+ if( pCvt && pCvt->GetFilterHdl().IsSet() )
+ {
+ ConvertData aData( rGraphic, rIStm, nFormat );
+
+ if( pCvt->GetFilterHdl().Call( aData ) )
+ {
+ rGraphic = aData.maGraphic;
+ nRet = ERRCODE_NONE;
+ }
+ else if( rIStm.GetError() )
+ nRet = rIStm.GetError();
+ }
+
+ return nRet;
+}
+
+ErrCode GraphicConverter::Export( SvStream& rOStm, const Graphic& rGraphic, ConvertDataFormat nFormat )
+{
+ GraphicConverter* pCvt = ImplGetSVData()->maGDIData.mxGrfConverter.get();
+ ErrCode nRet = ERRCODE_IO_GENERAL;
+
+ if( pCvt && pCvt->GetFilterHdl().IsSet() )
+ {
+ ConvertData aData( rGraphic, rOStm, nFormat );
+
+ if( pCvt->GetFilterHdl().Call( aData ) )
+ nRet = ERRCODE_NONE;
+ else if( rOStm.GetError() )
+ nRet = rOStm.GetError();
+ }
+
+ return nRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/embeddedfontshelper.cxx b/vcl/source/gdi/embeddedfontshelper.cxx
new file mode 100644
index 000000000..34d227e5f
--- /dev/null
+++ b/vcl/source/gdi/embeddedfontshelper.cxx
@@ -0,0 +1,335 @@
+/* -*- 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/.
+ */
+
+#include <sal/config.h>
+
+#include <memory>
+#include <config_folders.h>
+#include <config_eot.h>
+
+#include <osl/file.hxx>
+#include <rtl/bootstrap.hxx>
+#include <sal/log.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/embeddedfontshelper.hxx>
+#include <com/sun/star/io/XInputStream.hpp>
+
+#include <font/PhysicalFontFaceCollection.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <salgdi.hxx>
+#include <sft.hxx>
+
+
+#if ENABLE_EOT
+extern "C"
+{
+namespace libeot
+{
+#include <libeot/libeot.h>
+} // namespace libeot
+} // extern "C"
+#endif
+
+using namespace com::sun::star;
+using namespace vcl;
+
+static void clearDir( const OUString& path )
+{
+ osl::Directory dir( path );
+ if( dir.reset() == osl::Directory::E_None )
+ {
+ for(;;)
+ {
+ osl::DirectoryItem item;
+ if( dir.getNextItem( item ) != osl::Directory::E_None )
+ break;
+ osl::FileStatus status( osl_FileStatus_Mask_FileURL );
+ if( item.getFileStatus( status ) == osl::File::E_None )
+ osl::File::remove( status.getFileURL());
+ }
+ }
+}
+
+void EmbeddedFontsHelper::clearTemporaryFontFiles()
+{
+ OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
+ rtl::Bootstrap::expandMacros( path );
+ path += "/user/temp/embeddedfonts/";
+ clearDir( path + "fromdocs/" );
+ clearDir( path + "fromsystem/" );
+}
+
+bool EmbeddedFontsHelper::addEmbeddedFont( const uno::Reference< io::XInputStream >& stream, const OUString& fontName,
+ const char* extra, std::vector< unsigned char > const & key, bool eot )
+{
+ OUString fileUrl = EmbeddedFontsHelper::fileUrlForTemporaryFont( fontName, extra );
+ osl::File file( fileUrl );
+ switch( file.open( osl_File_OpenFlag_Create | osl_File_OpenFlag_Write ))
+ {
+ case osl::File::E_None:
+ break; // ok
+ case osl::File::E_EXIST:
+ return true; // Assume it's already been added correctly.
+ default:
+ SAL_WARN( "vcl.fonts", "Cannot open file for temporary font" );
+ return false;
+ }
+ size_t keyPos = 0;
+ std::vector< char > fontData;
+ fontData.reserve( 1000000 );
+ for(;;)
+ {
+ uno::Sequence< sal_Int8 > buffer;
+ sal_uInt64 read = stream->readBytes( buffer, 1024 );
+ auto bufferRange = asNonConstRange(buffer);
+ for( sal_uInt64 pos = 0;
+ pos < read && keyPos < key.size();
+ ++pos )
+ bufferRange[ pos ] ^= key[ keyPos++ ];
+ // if eot, don't write the file out yet, since we need to unpack it first.
+ if( !eot && read > 0 )
+ {
+ sal_uInt64 writtenTotal = 0;
+ while( writtenTotal < read )
+ {
+ sal_uInt64 written;
+ file.write( buffer.getConstArray(), read, written );
+ writtenTotal += written;
+ }
+ }
+ fontData.insert( fontData.end(), buffer.getConstArray(), buffer.getConstArray() + read );
+ if( read <= 0 )
+ break;
+ }
+ bool sufficientFontRights(false);
+#if ENABLE_EOT
+ if( eot )
+ {
+ unsigned uncompressedFontSize = 0;
+ unsigned char *nakedPointerToUncompressedFont = nullptr;
+ libeot::EOTMetadata eotMetadata;
+ libeot::EOTError uncompressError =
+ libeot::EOT2ttf_buffer( reinterpret_cast<unsigned char *>(fontData.data()), fontData.size(), &eotMetadata, &nakedPointerToUncompressedFont, &uncompressedFontSize );
+ std::shared_ptr<unsigned char> uncompressedFont( nakedPointerToUncompressedFont, libeot::EOTfreeBuffer );
+ if( uncompressError != libeot::EOT_SUCCESS )
+ {
+ SAL_WARN( "vcl.fonts", "Failed to uncompress font" );
+ osl::File::remove( fileUrl );
+ return false;
+ }
+ sal_uInt64 writtenTotal = 0;
+ while( writtenTotal < uncompressedFontSize )
+ {
+ sal_uInt64 written;
+ if( file.write( uncompressedFont.get() + writtenTotal, uncompressedFontSize - writtenTotal, written ) != osl::File::E_None )
+ {
+ SAL_WARN( "vcl.fonts", "Error writing temporary font file" );
+ osl::File::remove( fileUrl );
+ return false;
+ }
+ writtenTotal += written;
+ }
+ sufficientFontRights = libeot::EOTcanLegallyEdit( &eotMetadata );
+ libeot::EOTfreeMetadata( &eotMetadata );
+ }
+#endif
+
+ if( file.close() != osl::File::E_None )
+ {
+ SAL_WARN( "vcl.fonts", "Writing temporary font file failed" );
+ osl::File::remove( fileUrl );
+ return false;
+ }
+ if( !eot )
+ {
+ sufficientFontRights = sufficientTTFRights(fontData.data(), fontData.size(), FontRights::EditingAllowed);
+ }
+ if( !sufficientFontRights )
+ {
+ // It would be actually better to open the document in read-only mode in this case,
+ // warn the user about this, and provide a button to drop the font(s) in order
+ // to switch to editing.
+ SAL_INFO( "vcl.fonts", "Ignoring embedded font that is not usable for editing" );
+ osl::File::remove( fileUrl );
+ return false;
+ }
+ m_aAccumulatedFonts.emplace_back(std::make_pair(fontName, fileUrl));
+ return true;
+}
+
+namespace
+{
+ struct UpdateFontsGuard
+ {
+ UpdateFontsGuard()
+ {
+ OutputDevice::ImplClearAllFontData(true);
+ }
+
+ ~UpdateFontsGuard()
+ {
+ OutputDevice::ImplRefreshAllFontData(true);
+ }
+ };
+}
+
+void EmbeddedFontsHelper::activateFonts()
+{
+ if (m_aAccumulatedFonts.empty())
+ return;
+ UpdateFontsGuard aUpdateFontsGuard;
+ for (const auto& rEntry : m_aAccumulatedFonts)
+ EmbeddedFontsHelper::activateFont(rEntry.first, rEntry.second);
+ m_aAccumulatedFonts.clear();
+}
+
+OUString EmbeddedFontsHelper::fileUrlForTemporaryFont( const OUString& fontName, const char* extra )
+{
+ OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
+ rtl::Bootstrap::expandMacros( path );
+ path += "/user/temp/embeddedfonts/fromdocs/";
+ osl::Directory::createPath( path );
+ OUString filename = fontName;
+ static int uniqueCounter = 0;
+ if( strcmp( extra, "?" ) == 0 )
+ filename += OUString::number( uniqueCounter++ );
+ else
+ filename += OStringToOUString( extra, RTL_TEXTENCODING_ASCII_US );
+ filename += ".ttf"; // TODO is it always ttf?
+ return path + filename;
+}
+
+void EmbeddedFontsHelper::activateFont( const OUString& fontName, const OUString& fileUrl )
+{
+ OutputDevice *pDevice = Application::GetDefaultDevice();
+ pDevice->AddTempDevFont(fileUrl, fontName);
+}
+
+// Check if it's (legally) allowed to embed the font file into a document
+// (ttf has a flag allowing this). PhysicalFontFace::IsEmbeddable() appears
+// to have a different meaning (guessing from code, IsSubsettable() might
+// possibly mean it's ttf, while IsEmbeddable() might mean it's type1).
+// So just try to open the data as ttf and see.
+bool EmbeddedFontsHelper::sufficientTTFRights( const void* data, tools::Long size, FontRights rights )
+{
+ TrueTypeFont* font;
+ if( OpenTTFontBuffer( data, size, 0 /*TODO*/, &font ) == SFErrCodes::Ok )
+ {
+ TTGlobalFontInfo info;
+ GetTTGlobalFontInfo( font, &info );
+ CloseTTFont( font );
+ // https://www.microsoft.com/typography/otspec/os2.htm#fst
+ int copyright = info.typeFlags;
+ switch( rights )
+ {
+ case FontRights::ViewingAllowed:
+ // Embedding not restricted completely.
+ return ( copyright & 0x02 ) != 0x02;
+ case FontRights::EditingAllowed:
+ // Font is installable or editable.
+ return copyright == 0 || ( copyright & 0x08 );
+ }
+ }
+ return true; // no known restriction
+}
+
+OUString EmbeddedFontsHelper::fontFileUrl( std::u16string_view familyName, FontFamily family, FontItalic italic,
+ FontWeight weight, FontPitch pitch, FontRights rights )
+{
+ OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
+ rtl::Bootstrap::expandMacros( path );
+ path += "/user/temp/embeddedfonts/fromsystem/";
+ osl::Directory::createPath( path );
+ OUString filename = OUString::Concat(familyName) + "_" + OUString::number( family ) + "_" + OUString::number( italic )
+ + "_" + OUString::number( weight ) + "_" + OUString::number( pitch )
+ + ".ttf"; // TODO is it always ttf?
+ OUString url = path + filename;
+ if( osl::File( url ).open( osl_File_OpenFlag_Read ) == osl::File::E_None ) // = exists()
+ {
+ // File with contents of the font file already exists, assume it's been created by a previous call.
+ return url;
+ }
+ bool ok = false;
+ SalGraphics* graphics = Application::GetDefaultDevice()->GetGraphics();
+ vcl::font::PhysicalFontCollection fonts;
+ graphics->GetDevFontList( &fonts );
+ std::unique_ptr< vcl::font::PhysicalFontFaceCollection > fontInfo( fonts.GetFontFaceCollection());
+ vcl::font::PhysicalFontFace* selected = nullptr;
+ for( int i = 0;
+ i < fontInfo->Count();
+ ++i )
+ {
+ vcl::font::PhysicalFontFace* f = fontInfo->Get( i );
+ if( f->GetFamilyName() == familyName )
+ {
+ // Ignore comparing text encodings, at least for now. They cannot be trivially compared
+ // (e.g. UCS2 and UTF8 are technically the same characters, just have different encoding,
+ // and just having a unicode font doesn't say what glyphs it actually contains).
+ // It is possible that it still may be needed to do at least some checks here
+ // for some encodings (can one font have more font files for more encodings?).
+ if(( family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
+ && ( italic == ITALIC_DONTKNOW || f->GetItalic() == italic )
+ && ( weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
+ && ( pitch == PITCH_DONTKNOW || f->GetPitch() == pitch ))
+ { // Exact match, return it immediately.
+ selected = f;
+ break;
+ }
+ if(( f->GetFamilyType() == FAMILY_DONTKNOW || family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
+ && ( f->GetItalic() == ITALIC_DONTKNOW || italic == ITALIC_DONTKNOW || f->GetItalic() == italic )
+ && ( f->GetWeight() == WEIGHT_DONTKNOW || weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
+ && ( f->GetPitch() == PITCH_DONTKNOW || pitch == PITCH_DONTKNOW || f->GetPitch() == pitch ))
+ { // Some fonts specify 'DONTKNOW' for some things, still a good match, if we don't find a better one.
+ selected = f;
+ }
+ }
+ }
+ if( selected != nullptr )
+ {
+ tools::Long size;
+ if (const void* data = graphics->GetEmbedFontData(selected, &size))
+ {
+ if( sufficientTTFRights( data, size, rights ))
+ {
+ osl::File file( url );
+ if( file.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ) == osl::File::E_None )
+ {
+ sal_uInt64 written = 0;
+ sal_uInt64 totalSize = size;
+ bool error = false;
+ while( written < totalSize && !error)
+ {
+ sal_uInt64 nowWritten;
+ switch( file.write( static_cast< const char* >( data ) + written, size - written, nowWritten ))
+ {
+ case osl::File::E_None:
+ written += nowWritten;
+ break;
+ case osl::File::E_AGAIN:
+ case osl::File::E_INTR:
+ break;
+ default:
+ error = true;
+ break;
+ }
+ }
+ file.close();
+ if( error )
+ osl::File::remove( url );
+ else
+ ok = true;
+ }
+ }
+ graphics->FreeEmbedFontData( data, size );
+ }
+ }
+ return ok ? url : "";
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/gdi/extoutdevdata.cxx b/vcl/source/gdi/extoutdevdata.cxx
new file mode 100644
index 000000000..1bb84a669
--- /dev/null
+++ b/vcl/source/gdi/extoutdevdata.cxx
@@ -0,0 +1,27 @@
+/* -*- 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 <vcl/extoutdevdata.hxx>
+
+namespace vcl
+{
+ExtOutDevData::~ExtOutDevData() {}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/gdimetafiletools.cxx b/vcl/source/gdi/gdimetafiletools.cxx
new file mode 100644
index 000000000..982cb0d7f
--- /dev/null
+++ b/vcl/source/gdi/gdimetafiletools.cxx
@@ -0,0 +1,1086 @@
+/* -*- 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 <vcl/gdimetafiletools.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/canvastools.hxx>
+#include <basegfx/polygon/b2dpolygonclipper.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/graphictools.hxx>
+#include <osl/diagnose.h>
+#include <tools/stream.hxx>
+
+// helpers
+
+namespace
+{
+ bool handleGeometricContent(
+ const basegfx::B2DPolyPolygon& rClip,
+ const basegfx::B2DPolyPolygon& rSource,
+ GDIMetaFile& rTarget,
+ bool bStroke)
+ {
+ if(rSource.count() && rClip.count())
+ {
+ const basegfx::B2DPolyPolygon aResult(
+ basegfx::utils::clipPolyPolygonOnPolyPolygon(
+ rSource,
+ rClip,
+ true, // inside
+ bStroke));
+
+ if(aResult.count())
+ {
+ if(aResult == rSource)
+ {
+ // not clipped, but inside. Add original
+ return false;
+ }
+ else
+ {
+ // add clipped geometry
+ if(bStroke)
+ {
+ for(auto const& rB2DPolygon : aResult)
+ {
+ rTarget.AddAction(
+ new MetaPolyLineAction(
+ tools::Polygon(rB2DPolygon)));
+ }
+ }
+ else
+ {
+ rTarget.AddAction(
+ new MetaPolyPolygonAction(
+ tools::PolyPolygon(aResult)));
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ bool handleGradientContent(
+ const basegfx::B2DPolyPolygon& rClip,
+ const basegfx::B2DPolyPolygon& rSource,
+ const Gradient& rGradient,
+ GDIMetaFile& rTarget)
+ {
+ if(rSource.count() && rClip.count())
+ {
+ const basegfx::B2DPolyPolygon aResult(
+ basegfx::utils::clipPolyPolygonOnPolyPolygon(
+ rSource,
+ rClip,
+ true, // inside
+ false)); // stroke
+
+ if(aResult.count())
+ {
+ if(aResult == rSource)
+ {
+ // not clipped, but inside. Add original
+ return false;
+ }
+ else
+ {
+ // add clipped geometry
+ rTarget.AddAction(
+ new MetaGradientExAction(
+ tools::PolyPolygon(aResult),
+ rGradient));
+ }
+ }
+ }
+
+ return true;
+ }
+
+ bool handleBitmapContent(
+ const basegfx::B2DPolyPolygon& rClip,
+ const Point& rPoint,
+ const Size& rSize,
+ const BitmapEx& rBitmapEx,
+ GDIMetaFile& rTarget)
+ {
+ if(!rSize.Width() || !rSize.Height() || rBitmapEx.IsEmpty())
+ {
+ // bitmap or size is empty
+ return true;
+ }
+
+ const basegfx::B2DRange aLogicBitmapRange(
+ rPoint.X(), rPoint.Y(),
+ rPoint.X() + rSize.Width(), rPoint.Y() + rSize.Height());
+ const basegfx::B2DPolyPolygon aClipOfBitmap(
+ basegfx::utils::clipPolyPolygonOnRange(
+ rClip,
+ aLogicBitmapRange,
+ true,
+ false)); // stroke
+
+ if(!aClipOfBitmap.count())
+ {
+ // outside clip region
+ return true;
+ }
+
+ // inside or overlapping. Use area to find out if it is completely
+ // covering (inside) or overlapping
+ const double fClipArea(basegfx::utils::getArea(aClipOfBitmap));
+ const double fBitmapArea(
+ aLogicBitmapRange.getWidth() * aLogicBitmapRange.getWidth() +
+ aLogicBitmapRange.getHeight() * aLogicBitmapRange.getHeight());
+ const double fFactor(fClipArea / fBitmapArea);
+
+ if(basegfx::fTools::more(fFactor, 1.0 - 0.001))
+ {
+ // completely covering (with 0.1% tolerance)
+ return false;
+ }
+
+ // needs clipping (with 0.1% tolerance). Prepare VirtualDevice
+ // in pixel mode for alpha channel painting (black is transparent,
+ // white to paint 100% opacity)
+ const Size aSizePixel(rBitmapEx.GetSizePixel());
+ ScopedVclPtrInstance< VirtualDevice > aVDev;
+
+ aVDev->SetOutputSizePixel(aSizePixel);
+ aVDev->EnableMapMode(false);
+ aVDev->SetFillColor( COL_WHITE);
+ aVDev->SetLineColor();
+
+ if(rBitmapEx.IsAlpha())
+ {
+ // use given alpha channel
+ aVDev->DrawBitmap(Point(0, 0), rBitmapEx.GetAlpha().GetBitmap());
+ }
+ else
+ {
+ // reset alpha channel
+ aVDev->SetBackground(Wallpaper(COL_BLACK));
+ aVDev->Erase();
+ }
+
+ // transform polygon from clipping to pixel coordinates
+ basegfx::B2DPolyPolygon aPixelPoly(aClipOfBitmap);
+ basegfx::B2DHomMatrix aTransform;
+
+ aTransform.translate(-aLogicBitmapRange.getMinX(), -aLogicBitmapRange.getMinY());
+ aTransform.scale(
+ static_cast< double >(aSizePixel.Width()) / aLogicBitmapRange.getWidth(),
+ static_cast< double >(aSizePixel.Height()) / aLogicBitmapRange.getHeight());
+ aPixelPoly.transform(aTransform);
+
+ // to fill the non-covered parts, use the Xor fill rule of
+ // tools::PolyPolygon painting. Start with an all-covering polygon and
+ // add the clip polygon one
+ basegfx::B2DPolyPolygon aInvertPixelPoly;
+
+ aInvertPixelPoly.append(
+ basegfx::utils::createPolygonFromRect(
+ basegfx::B2DRange(
+ 0.0, 0.0,
+ aSizePixel.Width(), aSizePixel.Height())));
+ aInvertPixelPoly.append(aPixelPoly);
+
+ // paint as alpha
+ aVDev->DrawPolyPolygon(aInvertPixelPoly);
+
+ // get created alpha mask and set defaults
+ AlphaMask aAlpha(
+ aVDev->GetBitmap(
+ Point(0, 0),
+ aSizePixel));
+
+ aAlpha.SetPrefSize(rBitmapEx.GetPrefSize());
+ aAlpha.SetPrefMapMode(rBitmapEx.GetPrefMapMode());
+
+ // add new action replacing the old one
+ rTarget.AddAction(
+ new MetaBmpExScaleAction(
+ Point(
+ basegfx::fround(aLogicBitmapRange.getMinX()),
+ basegfx::fround(aLogicBitmapRange.getMinY())),
+ Size(
+ basegfx::fround(aLogicBitmapRange.getWidth()),
+ basegfx::fround(aLogicBitmapRange.getHeight())),
+ BitmapEx(rBitmapEx.GetBitmap(), aAlpha)));
+
+ return true;
+ }
+
+ void addSvtGraphicStroke(const SvtGraphicStroke& rStroke, GDIMetaFile& rTarget)
+ {
+ // write SvtGraphicFill
+ SvMemoryStream aMemStm;
+ WriteSvtGraphicStroke( aMemStm, rStroke );
+ rTarget.AddAction(
+ new MetaCommentAction(
+ "XPATHSTROKE_SEQ_BEGIN",
+ 0,
+ static_cast< const sal_uInt8* >(aMemStm.GetData()),
+ aMemStm.TellEnd()));
+ }
+
+ void addSvtGraphicFill(const SvtGraphicFill &rFilling, GDIMetaFile& rTarget)
+ {
+ // write SvtGraphicFill
+ SvMemoryStream aMemStm;
+ WriteSvtGraphicFill( aMemStm, rFilling );
+ rTarget.AddAction(
+ new MetaCommentAction(
+ "XPATHFILL_SEQ_BEGIN",
+ 0,
+ static_cast< const sal_uInt8* >(aMemStm.GetData()),
+ aMemStm.TellEnd()));
+ }
+} // end of anonymous namespace
+
+// #i121267# Tooling to internally clip geometry against internal clip regions
+
+void clipMetafileContentAgainstOwnRegions(GDIMetaFile& rSource)
+{
+ const sal_uLong nObjCount(rSource.GetActionSize());
+
+ if(!nObjCount)
+ {
+ return;
+ }
+
+ // prepare target data container and push/pop stack data
+ GDIMetaFile aTarget;
+ bool bChanged(false);
+ std::vector< basegfx::B2DPolyPolygon > aClips;
+ std::vector< vcl::PushFlags > aPushFlags;
+ std::vector< MapMode > aMapModes;
+
+ // start with empty region
+ aClips.emplace_back();
+
+ // start with default MapMode (MapUnit::MapPixel)
+ aMapModes.emplace_back();
+
+ for(sal_uLong i(0); i < nObjCount; ++i)
+ {
+ const MetaAction* pAction(rSource.GetAction(i));
+ const MetaActionType nType(pAction->GetType());
+ bool bDone(false);
+
+ // basic operation takes care of clipregion actions (four) and push/pop of these
+ // to steer the currently set clip region. There *is* an active
+ // clip region when (aClips.size() && aClips.back().count()), see
+ // below
+ switch(nType)
+ {
+ case MetaActionType::CLIPREGION :
+ {
+ const MetaClipRegionAction* pA = static_cast< const MetaClipRegionAction* >(pAction);
+
+ if(pA->IsClipping())
+ {
+ const vcl::Region& rRegion = pA->GetRegion();
+ const basegfx::B2DPolyPolygon aNewClip(rRegion.GetAsB2DPolyPolygon());
+
+ aClips.back() = aNewClip;
+ }
+ else
+ {
+ aClips.back() = basegfx::B2DPolyPolygon();
+ }
+
+ break;
+ }
+
+ case MetaActionType::ISECTRECTCLIPREGION :
+ {
+ const MetaISectRectClipRegionAction* pA = static_cast< const MetaISectRectClipRegionAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(!rRect.IsEmpty() && !aClips.empty() && aClips.back().count())
+ {
+ const basegfx::B2DRange aClipRange(vcl::unotools::b2DRectangleFromRectangle(rRect));
+
+ aClips.back() = basegfx::utils::clipPolyPolygonOnRange(
+ aClips.back(),
+ aClipRange,
+ true, // inside
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::ISECTREGIONCLIPREGION :
+ {
+ const MetaISectRegionClipRegionAction* pA = static_cast< const MetaISectRegionClipRegionAction* >(pAction);
+ const vcl::Region& rRegion = pA->GetRegion();
+
+ if(!rRegion.IsEmpty() && !aClips.empty() && aClips.back().count())
+ {
+ const basegfx::B2DPolyPolygon aNewClip(rRegion.GetAsB2DPolyPolygon());
+
+ aClips.back() = basegfx::utils::clipPolyPolygonOnPolyPolygon(
+ aClips.back(),
+ aNewClip,
+ true, // inside
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::MOVECLIPREGION :
+ {
+ const MetaMoveClipRegionAction* pA = static_cast< const MetaMoveClipRegionAction* >(pAction);
+ const tools::Long aHorMove(pA->GetHorzMove());
+ const tools::Long aVerMove(pA->GetVertMove());
+
+ if((aHorMove || aVerMove) && !aClips.empty() && aClips.back().count())
+ {
+ aClips.back().transform(
+ basegfx::utils::createTranslateB2DHomMatrix(
+ aHorMove,
+ aVerMove));
+ }
+ break;
+ }
+
+ case MetaActionType::PUSH :
+ {
+ const MetaPushAction* pA = static_cast< const MetaPushAction* >(pAction);
+ const vcl::PushFlags nFlags(pA->GetFlags());
+
+ aPushFlags.push_back(nFlags);
+
+ if(nFlags & vcl::PushFlags::CLIPREGION)
+ {
+ aClips.push_back(aClips.back());
+ }
+
+ if(nFlags & vcl::PushFlags::MAPMODE)
+ {
+ aMapModes.push_back(aMapModes.back());
+ }
+ break;
+ }
+
+ case MetaActionType::POP :
+ {
+
+ if(!aPushFlags.empty())
+ {
+ const vcl::PushFlags nFlags(aPushFlags.back());
+ aPushFlags.pop_back();
+
+ if(nFlags & vcl::PushFlags::CLIPREGION)
+ {
+ if(aClips.size() > 1)
+ {
+ aClips.pop_back();
+ }
+ else
+ {
+ OSL_ENSURE(false, "Wrong POP() in ClipRegions (!)");
+ }
+ }
+
+ if(nFlags & vcl::PushFlags::MAPMODE)
+ {
+ if(aMapModes.size() > 1)
+ {
+ aMapModes.pop_back();
+ }
+ else
+ {
+ OSL_ENSURE(false, "Wrong POP() in MapModes (!)");
+ }
+ }
+ }
+ else
+ {
+ OSL_ENSURE(false, "Invalid pop() without push() (!)");
+ }
+
+ break;
+ }
+
+ case MetaActionType::MAPMODE :
+ {
+ const MetaMapModeAction* pA = static_cast< const MetaMapModeAction* >(pAction);
+
+ aMapModes.back() = pA->GetMapMode();
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+
+ // this area contains all actions which could potentially be clipped. Since
+ // this tooling is only a fallback (see comments in header), only the needed
+ // actions will be implemented. Extend using the pattern for the already
+ // implemented actions.
+ if(!aClips.empty() && aClips.back().count())
+ {
+ switch(nType)
+ {
+
+ // pixel actions, just check on inside
+
+ case MetaActionType::PIXEL :
+ {
+ const MetaPixelAction* pA = static_cast< const MetaPixelAction* >(pAction);
+ const Point& rPoint = pA->GetPoint();
+
+ if(!basegfx::utils::isInside(
+ aClips.back(),
+ basegfx::B2DPoint(rPoint.X(), rPoint.Y())))
+ {
+ // when not inside, do not add original
+ bDone = true;
+ }
+ break;
+ }
+
+ case MetaActionType::POINT :
+ {
+ const MetaPointAction* pA = static_cast< const MetaPointAction* >(pAction);
+ const Point& rPoint = pA->GetPoint();
+
+ if(!basegfx::utils::isInside(
+ aClips.back(),
+ basegfx::B2DPoint(rPoint.X(), rPoint.Y())))
+ {
+ // when not inside, do not add original
+ bDone = true;
+ }
+ break;
+ }
+
+ // geometry actions
+
+ case MetaActionType::LINE :
+ {
+ const MetaLineAction* pA = static_cast< const MetaLineAction* >(pAction);
+ const Point& rStart(pA->GetStartPoint());
+ const Point& rEnd(pA->GetEndPoint());
+ basegfx::B2DPolygon aLine;
+
+ aLine.append(basegfx::B2DPoint(rStart.X(), rStart.Y()));
+ aLine.append(basegfx::B2DPoint(rEnd.X(), rEnd.Y()));
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(aLine),
+ aTarget,
+ true); // stroke
+ break;
+ }
+
+ case MetaActionType::RECT :
+ {
+ const MetaRectAction* pA = static_cast< const MetaRectAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect))),
+ aTarget,
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::ROUNDRECT :
+ {
+ const MetaRoundRectAction* pA = static_cast< const MetaRoundRectAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ const sal_uInt32 nHor(pA->GetHorzRound());
+ const sal_uInt32 nVer(pA->GetVertRound());
+ const basegfx::B2DRange aRange(vcl::unotools::b2DRectangleFromRectangle(rRect));
+ basegfx::B2DPolygon aOutline;
+
+ if(nHor || nVer)
+ {
+ double fRadiusX((nHor * 2.0) / (aRange.getWidth() > 0.0 ? aRange.getWidth() : 1.0));
+ double fRadiusY((nVer * 2.0) / (aRange.getHeight() > 0.0 ? aRange.getHeight() : 1.0));
+ fRadiusX = std::clamp(fRadiusX, 0.0, 1.0);
+ fRadiusY = std::clamp(fRadiusY, 0.0, 1.0);
+
+ aOutline = basegfx::utils::createPolygonFromRect(aRange, fRadiusX, fRadiusY);
+ }
+ else
+ {
+ aOutline = basegfx::utils::createPolygonFromRect(aRange);
+ }
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(aOutline),
+ aTarget,
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::ELLIPSE :
+ {
+ const MetaEllipseAction* pA = static_cast< const MetaEllipseAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ const basegfx::B2DRange aRange(vcl::unotools::b2DRectangleFromRectangle(rRect));
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromEllipse(
+ aRange.getCenter(),
+ aRange.getWidth() * 0.5,
+ aRange.getHeight() * 0.5)),
+ aTarget,
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::ARC :
+ {
+ const MetaArcAction* pA = static_cast< const MetaArcAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ const tools::Polygon aToolsPoly(
+ rRect,
+ pA->GetStartPoint(),
+ pA->GetEndPoint(),
+ PolyStyle::Arc);
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()),
+ aTarget,
+ true); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::PIE :
+ {
+ const MetaPieAction* pA = static_cast< const MetaPieAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ const tools::Polygon aToolsPoly(
+ rRect,
+ pA->GetStartPoint(),
+ pA->GetEndPoint(),
+ PolyStyle::Pie);
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()),
+ aTarget,
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::CHORD :
+ {
+ const MetaChordAction* pA = static_cast< const MetaChordAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ const tools::Polygon aToolsPoly(
+ rRect,
+ pA->GetStartPoint(),
+ pA->GetEndPoint(),
+ PolyStyle::Chord);
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()),
+ aTarget,
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::POLYLINE :
+ {
+ const MetaPolyLineAction* pA = static_cast< const MetaPolyLineAction* >(pAction);
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(pA->GetPolygon().getB2DPolygon()),
+ aTarget,
+ true); // stroke
+ break;
+ }
+
+ case MetaActionType::POLYGON :
+ {
+ const MetaPolygonAction* pA = static_cast< const MetaPolygonAction* >(pAction);
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(pA->GetPolygon().getB2DPolygon()),
+ aTarget,
+ false); // stroke
+ break;
+ }
+
+ case MetaActionType::POLYPOLYGON :
+ {
+ const MetaPolyPolygonAction* pA = static_cast< const MetaPolyPolygonAction* >(pAction);
+ const tools::PolyPolygon& rPoly = pA->GetPolyPolygon();
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ rPoly.getB2DPolyPolygon(),
+ aTarget,
+ false); // stroke
+ break;
+ }
+
+ // bitmap actions, create BitmapEx with alpha channel derived
+ // from clipping
+
+ case MetaActionType::BMPEX :
+ {
+ const MetaBmpExAction* pA = static_cast< const MetaBmpExAction* >(pAction);
+ const BitmapEx& rBitmapEx = pA->GetBitmapEx();
+
+ // the logical size depends on the PrefSize of the given bitmap in
+ // combination with the current MapMode
+ Size aLogicalSize(rBitmapEx.GetPrefSize());
+
+ if(MapUnit::MapPixel == rBitmapEx.GetPrefMapMode().GetMapUnit())
+ {
+ aLogicalSize = Application::GetDefaultDevice()->PixelToLogic(aLogicalSize, aMapModes.back());
+ }
+ else
+ {
+ aLogicalSize = OutputDevice::LogicToLogic(aLogicalSize, rBitmapEx.GetPrefMapMode(), aMapModes.back());
+ }
+
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetPoint(),
+ aLogicalSize,
+ rBitmapEx,
+ aTarget);
+ break;
+ }
+
+ case MetaActionType::BMP :
+ {
+ const MetaBmpAction* pA = static_cast< const MetaBmpAction* >(pAction);
+ const Bitmap& rBitmap = pA->GetBitmap();
+
+ // the logical size depends on the PrefSize of the given bitmap in
+ // combination with the current MapMode
+ Size aLogicalSize(rBitmap.GetPrefSize());
+
+ if(MapUnit::MapPixel == rBitmap.GetPrefMapMode().GetMapUnit())
+ {
+ aLogicalSize = Application::GetDefaultDevice()->PixelToLogic(aLogicalSize, aMapModes.back());
+ }
+ else
+ {
+ aLogicalSize = OutputDevice::LogicToLogic(aLogicalSize, rBitmap.GetPrefMapMode(), aMapModes.back());
+ }
+
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetPoint(),
+ aLogicalSize,
+ BitmapEx(rBitmap),
+ aTarget);
+ break;
+ }
+
+ case MetaActionType::BMPEXSCALE :
+ {
+ const MetaBmpExScaleAction* pA = static_cast< const MetaBmpExScaleAction* >(pAction);
+
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetPoint(),
+ pA->GetSize(),
+ pA->GetBitmapEx(),
+ aTarget);
+ break;
+ }
+
+ case MetaActionType::BMPSCALE :
+ {
+ const MetaBmpScaleAction* pA = static_cast< const MetaBmpScaleAction* >(pAction);
+
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetPoint(),
+ pA->GetSize(),
+ BitmapEx(pA->GetBitmap()),
+ aTarget);
+ break;
+ }
+
+ case MetaActionType::BMPEXSCALEPART :
+ {
+ const MetaBmpExScalePartAction* pA = static_cast< const MetaBmpExScalePartAction* >(pAction);
+ const BitmapEx& rBitmapEx = pA->GetBitmapEx();
+
+ if(rBitmapEx.IsEmpty())
+ {
+ // empty content
+ bDone = true;
+ }
+ else
+ {
+ BitmapEx aCroppedBitmapEx(rBitmapEx);
+ const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize());
+
+ if(aCropRectangle.IsEmpty())
+ {
+ // empty content
+ bDone = true;
+ }
+ else
+ {
+ aCroppedBitmapEx.Crop(aCropRectangle);
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetDestPoint(),
+ pA->GetDestSize(),
+ aCroppedBitmapEx,
+ aTarget);
+ }
+ }
+ break;
+ }
+
+ case MetaActionType::BMPSCALEPART :
+ {
+ const MetaBmpScalePartAction* pA = static_cast< const MetaBmpScalePartAction* >(pAction);
+ const Bitmap& rBitmap = pA->GetBitmap();
+
+ if(rBitmap.IsEmpty())
+ {
+ // empty content
+ bDone = true;
+ }
+ else
+ {
+ Bitmap aCroppedBitmap(rBitmap);
+ const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize());
+
+ if(aCropRectangle.IsEmpty())
+ {
+ // empty content
+ bDone = true;
+ }
+ else
+ {
+ aCroppedBitmap.Crop(aCropRectangle);
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetDestPoint(),
+ pA->GetDestSize(),
+ BitmapEx(aCroppedBitmap),
+ aTarget);
+ }
+ }
+ break;
+ }
+
+ // need to handle all those 'hacks' which hide data in comments
+
+ case MetaActionType::COMMENT :
+ {
+ const MetaCommentAction* pA = static_cast< const MetaCommentAction* >(pAction);
+ const OString& rComment = pA->GetComment();
+
+ if(rComment.equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN"))
+ {
+ // nothing to do; this just means that between here and XGRAD_SEQ_END
+ // exists a MetaActionType::GRADIENTEX mixed with Xor-tricked painting
+ // commands. This comment is used to scan over these and filter for
+ // the gradient action. It is needed to support MetaActionType::GRADIENTEX
+ // in this processor to solve usages.
+ }
+ else if(rComment.equalsIgnoreAsciiCase("XPATHFILL_SEQ_BEGIN"))
+ {
+ SvtGraphicFill aFilling;
+ tools::PolyPolygon aPath;
+
+ { // read SvtGraphicFill
+ SvMemoryStream aMemStm(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(),StreamMode::READ);
+ ReadSvtGraphicFill( aMemStm, aFilling );
+ }
+
+ aFilling.getPath(aPath);
+
+ if(aPath.Count())
+ {
+ const basegfx::B2DPolyPolygon aSource(aPath.getB2DPolyPolygon());
+ const basegfx::B2DPolyPolygon aResult(
+ basegfx::utils::clipPolyPolygonOnPolyPolygon(
+ aSource,
+ aClips.back(),
+ true, // inside
+ false)); // stroke
+
+ if(aResult.count())
+ {
+ if(aResult != aSource)
+ {
+ // add clipped geometry
+ aFilling.setPath(tools::PolyPolygon(aResult));
+ addSvtGraphicFill(aFilling, aTarget);
+ bDone = true;
+ }
+ }
+ else
+ {
+ // exchange with empty polygon
+ aFilling.setPath(tools::PolyPolygon());
+ addSvtGraphicFill(aFilling, aTarget);
+ bDone = true;
+ }
+ }
+ }
+ else if(rComment.equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_BEGIN"))
+ {
+ SvtGraphicStroke aStroke;
+ tools::Polygon aPath;
+
+ { // read SvtGraphicFill
+ SvMemoryStream aMemStm(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(),StreamMode::READ);
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+ }
+
+ aStroke.getPath(aPath);
+
+ if(aPath.GetSize())
+ {
+ const basegfx::B2DPolygon aSource(aPath.getB2DPolygon());
+ const basegfx::B2DPolyPolygon aResult(
+ basegfx::utils::clipPolygonOnPolyPolygon(
+ aSource,
+ aClips.back(),
+ true, // inside
+ true)); // stroke
+
+ if(aResult.count())
+ {
+ if(aResult.count() > 1 || aResult.getB2DPolygon(0) != aSource)
+ {
+ // add clipped geometry
+ for(auto const& rB2DPolygon : aResult)
+ {
+ aStroke.setPath(tools::Polygon(rB2DPolygon));
+ addSvtGraphicStroke(aStroke, aTarget);
+ }
+
+ bDone = true;
+ }
+ }
+ else
+ {
+ // exchange with empty polygon
+ aStroke.setPath(tools::Polygon());
+ addSvtGraphicStroke(aStroke, aTarget);
+ bDone = true;
+ }
+
+ }
+ }
+ break;
+ }
+
+ // need to handle gradient fills (hopefully only unrotated ones)
+
+ case MetaActionType::GRADIENT :
+ {
+ const MetaGradientAction* pA = static_cast< const MetaGradientAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ bDone = handleGradientContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect))),
+ pA->GetGradient(),
+ aTarget);
+ }
+
+ break;
+ }
+
+ case MetaActionType::GRADIENTEX :
+ {
+ const MetaGradientExAction* pA = static_cast< const MetaGradientExAction* >(pAction);
+ const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon();
+
+ bDone = handleGradientContent(
+ aClips.back(),
+ rPolyPoly.getB2DPolyPolygon(),
+ pA->GetGradient(),
+ aTarget);
+ break;
+ }
+
+ // not (yet) supported actions
+
+ // MetaActionType::NONE
+ // MetaActionType::TEXT
+ // MetaActionType::TEXTARRAY
+ // MetaActionType::STRETCHTEXT
+ // MetaActionType::TEXTRECT
+ // MetaActionType::MASK
+ // MetaActionType::MASKSCALE
+ // MetaActionType::MASKSCALEPART
+ // MetaActionType::HATCH
+ // MetaActionType::WALLPAPER
+ // MetaActionType::FILLCOLOR
+ // MetaActionType::TEXTCOLOR
+ // MetaActionType::TEXTFILLCOLOR
+ // MetaActionType::TEXTALIGN
+ // MetaActionType::MAPMODE
+ // MetaActionType::FONT
+ // MetaActionType::Transparent
+ // MetaActionType::EPS
+ // MetaActionType::REFPOINT
+ // MetaActionType::TEXTLINECOLOR
+ // MetaActionType::TEXTLINE
+ // MetaActionType::FLOATTRANSPARENT
+ // MetaActionType::LAYOUTMODE
+ // MetaActionType::TEXTLANGUAGE
+ // MetaActionType::OVERLINECOLOR
+
+ // if an action is not handled at all, it will simply get copied to the
+ // target (see below). This is the default for all non-implemented actions
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ if(bDone)
+ {
+ bChanged = true;
+ }
+ else
+ {
+ aTarget.AddAction(const_cast< MetaAction* >(pAction));
+ }
+ }
+
+ if(bChanged)
+ {
+ // when changed, copy back and do not forget to set MapMode
+ // and PrefSize
+ aTarget.SetPrefMapMode(rSource.GetPrefMapMode());
+ aTarget.SetPrefSize(rSource.GetPrefSize());
+ rSource = aTarget;
+ }
+}
+
+bool usesClipActions(const GDIMetaFile& rSource)
+{
+ const sal_uLong nObjCount(rSource.GetActionSize());
+
+ for(sal_uLong i(0); i < nObjCount; ++i)
+ {
+ const MetaAction* pAction(rSource.GetAction(i));
+ const MetaActionType nType(pAction->GetType());
+
+ switch(nType)
+ {
+ case MetaActionType::CLIPREGION :
+ case MetaActionType::ISECTRECTCLIPREGION :
+ case MetaActionType::ISECTREGIONCLIPREGION :
+ case MetaActionType::MOVECLIPREGION :
+ {
+ return true;
+ }
+
+ default: break;
+ }
+ }
+
+ return false;
+}
+
+MetafileAccessor::~MetafileAccessor()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/gdimtf.cxx b/vcl/source/gdi/gdimtf.cxx
new file mode 100644
index 000000000..68c48bf6e
--- /dev/null
+++ b/vcl/source/gdi/gdimtf.cxx
@@ -0,0 +1,2347 @@
+/* -*- 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 <cstdlib>
+#include <memory>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <tools/diagnose_ex.h>
+#include <tools/helpers.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/fract.hxx>
+#include <vcl/BitmapPalette.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/window.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/graphictools.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/mtfxmldump.hxx>
+
+#include <vcl/TypeSerializer.hxx>
+
+#include <com/sun/star/beans/XFastPropertySet.hpp>
+#include <com/sun/star/rendering/MtfRenderer.hpp>
+#include <com/sun/star/rendering/XBitmapCanvas.hpp>
+#include <com/sun/star/rendering/XCanvas.hpp>
+#include <comphelper/processfactory.hxx>
+
+using namespace com::sun::star;
+
+namespace {
+
+struct ImplColAdjustParam
+{
+ std::unique_ptr<sal_uInt8[]> pMapR;
+ std::unique_ptr<sal_uInt8[]> pMapG;
+ std::unique_ptr<sal_uInt8[]> pMapB;
+};
+
+struct ImplBmpAdjustParam
+{
+ short nLuminancePercent;
+ short nContrastPercent;
+ short nChannelRPercent;
+ short nChannelGPercent;
+ short nChannelBPercent;
+ double fGamma;
+ bool bInvert;
+};
+
+struct ImplColConvertParam
+{
+ MtfConversion eConversion;
+};
+
+struct ImplBmpConvertParam
+{
+ BmpConversion eConversion;
+};
+
+struct ImplColMonoParam
+{
+ Color aColor;
+};
+
+struct ImplBmpMonoParam
+{
+ Color aColor;
+};
+
+struct ImplColReplaceParam
+{
+ std::unique_ptr<sal_uLong[]> pMinR;
+ std::unique_ptr<sal_uLong[]> pMaxR;
+ std::unique_ptr<sal_uLong[]> pMinG;
+ std::unique_ptr<sal_uLong[]> pMaxG;
+ std::unique_ptr<sal_uLong[]> pMinB;
+ std::unique_ptr<sal_uLong[]> pMaxB;
+ const Color * pDstCols;
+ sal_uLong nCount;
+};
+
+struct ImplBmpReplaceParam
+{
+ const Color* pSrcCols;
+ const Color* pDstCols;
+ sal_uLong nCount;
+};
+
+}
+
+GDIMetaFile::GDIMetaFile() :
+ m_nCurrentActionElement( 0 ),
+ m_aPrefSize ( 1, 1 ),
+ m_pPrev ( nullptr ),
+ m_pNext ( nullptr ),
+ m_pOutDev ( nullptr ),
+ m_bPause ( false ),
+ m_bRecord ( false ),
+ m_bUseCanvas ( false )
+{
+}
+
+GDIMetaFile::GDIMetaFile( const GDIMetaFile& rMtf ) :
+ m_nCurrentActionElement( rMtf.m_nCurrentActionElement ),
+ m_aPrefMapMode ( rMtf.m_aPrefMapMode ),
+ m_aPrefSize ( rMtf.m_aPrefSize ),
+ m_pPrev ( rMtf.m_pPrev ),
+ m_pNext ( rMtf.m_pNext ),
+ m_pOutDev ( nullptr ),
+ m_bPause ( false ),
+ m_bRecord ( false ),
+ m_bUseCanvas ( rMtf.m_bUseCanvas )
+{
+ for( size_t i = 0, n = rMtf.GetActionSize(); i < n; ++i )
+ {
+ m_aList.push_back( rMtf.GetAction( i ) );
+ }
+
+ if( rMtf.m_bRecord )
+ {
+ Record( rMtf.m_pOutDev );
+
+ if ( rMtf.m_bPause )
+ Pause( true );
+ }
+}
+
+GDIMetaFile::~GDIMetaFile()
+{
+ Clear();
+}
+
+bool GDIMetaFile::HasTransparentActions() const
+{
+ MetaAction* pCurrAct;
+
+ // watch for transparent drawing actions
+ for(pCurrAct = const_cast<GDIMetaFile*>(this)->FirstAction();
+ pCurrAct;
+ pCurrAct = const_cast<GDIMetaFile*>(this)->NextAction())
+ {
+ // #i10613# determine if the action is transparency capable
+
+ // #107169# Also examine metafiles with masked bitmaps in
+ // detail. Further down, this is optimized in such a way
+ // that there's no unnecessary painting of masked bitmaps
+ // (which are _always_ subdivided into rectangular regions
+ // of uniform opacity): if a masked bitmap is printed over
+ // empty background, we convert to a plain bitmap with
+ // white background.
+ if (pCurrAct->IsTransparent())
+ return true;
+ }
+
+ return false;
+}
+
+size_t GDIMetaFile::GetActionSize() const
+{
+ return m_aList.size();
+}
+
+MetaAction* GDIMetaFile::GetAction( size_t nAction ) const
+{
+ return (nAction < m_aList.size()) ? m_aList[ nAction ].get() : nullptr;
+}
+
+MetaAction* GDIMetaFile::FirstAction()
+{
+ m_nCurrentActionElement = 0;
+ return m_aList.empty() ? nullptr : m_aList[ 0 ].get();
+}
+
+MetaAction* GDIMetaFile::NextAction()
+{
+ return ( m_nCurrentActionElement + 1 < m_aList.size() ) ? m_aList[ ++m_nCurrentActionElement ].get() : nullptr;
+}
+
+void GDIMetaFile::ReplaceAction( rtl::Reference<MetaAction> pAction, size_t nAction )
+{
+ if ( nAction >= m_aList.size() )
+ {
+ return;
+ }
+ //fdo#39995 This doesn't increment the incoming action ref-count nor does it
+ //decrement the outgoing action ref-count
+ std::swap(pAction, m_aList[nAction]);
+}
+
+GDIMetaFile& GDIMetaFile::operator=( const GDIMetaFile& rMtf )
+{
+ if( this != &rMtf )
+ {
+ Clear();
+
+ // Increment RefCount of MetaActions
+ for( size_t i = 0, n = rMtf.GetActionSize(); i < n; ++i )
+ {
+ m_aList.push_back( rMtf.GetAction( i ) );
+ }
+
+ m_aPrefMapMode = rMtf.m_aPrefMapMode;
+ m_aPrefSize = rMtf.m_aPrefSize;
+ m_pPrev = rMtf.m_pPrev;
+ m_pNext = rMtf.m_pNext;
+ m_pOutDev = nullptr;
+ m_bPause = false;
+ m_bRecord = false;
+ m_bUseCanvas = rMtf.m_bUseCanvas;
+
+ if( rMtf.m_bRecord )
+ {
+ Record( rMtf.m_pOutDev );
+
+ if( rMtf.m_bPause )
+ Pause( true );
+ }
+ }
+
+ return *this;
+}
+
+bool GDIMetaFile::operator==( const GDIMetaFile& rMtf ) const
+{
+ const size_t nObjCount = m_aList.size();
+ bool bRet = false;
+
+ if( this == &rMtf )
+ bRet = true;
+ else if( rMtf.GetActionSize() == nObjCount &&
+ rMtf.GetPrefSize() == m_aPrefSize &&
+ rMtf.GetPrefMapMode() == m_aPrefMapMode )
+ {
+ bRet = true;
+
+ for( size_t n = 0; n < nObjCount; n++ )
+ {
+ if( m_aList[ n ] != rMtf.GetAction( n ) )
+ {
+ bRet = false;
+ break;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+void GDIMetaFile::Clear()
+{
+ if( m_bRecord )
+ Stop();
+
+ m_aList.clear();
+}
+
+void GDIMetaFile::Linker( OutputDevice* pOut, bool bLink )
+{
+ if( bLink )
+ {
+ m_pNext = nullptr;
+ m_pPrev = pOut->GetConnectMetaFile();
+ pOut->SetConnectMetaFile( this );
+
+ if( m_pPrev )
+ m_pPrev->m_pNext = this;
+ }
+ else
+ {
+ if( m_pNext )
+ {
+ m_pNext->m_pPrev = m_pPrev;
+
+ if( m_pPrev )
+ m_pPrev->m_pNext = m_pNext;
+ }
+ else
+ {
+ if( m_pPrev )
+ m_pPrev->m_pNext = nullptr;
+
+ pOut->SetConnectMetaFile( m_pPrev );
+ }
+
+ m_pPrev = nullptr;
+ m_pNext = nullptr;
+ }
+}
+
+void GDIMetaFile::Record( OutputDevice* pOut )
+{
+ if( m_bRecord )
+ Stop();
+
+ m_nCurrentActionElement = m_aList.empty() ? 0 : (m_aList.size() - 1);
+ m_pOutDev = pOut;
+ m_bRecord = true;
+ Linker( pOut, true );
+}
+
+void GDIMetaFile::Play( GDIMetaFile& rMtf )
+{
+ if (m_bRecord || rMtf.m_bRecord)
+ return;
+
+ MetaAction* pAction = GetCurAction();
+ const size_t nObjCount = m_aList.size();
+
+ rMtf.UseCanvas( rMtf.GetUseCanvas() || m_bUseCanvas );
+
+ for( size_t nCurPos = m_nCurrentActionElement; nCurPos < nObjCount; nCurPos++ )
+ {
+ if( pAction )
+ {
+ rMtf.AddAction( pAction );
+ }
+
+ pAction = NextAction();
+ }
+}
+
+void GDIMetaFile::Play(OutputDevice& rOut, size_t nPos)
+{
+ if( m_bRecord )
+ return;
+
+ MetaAction* pAction = GetCurAction();
+ const size_t nObjCount = m_aList.size();
+ size_t nSyncCount = rOut.GetSyncCount();
+
+ if( nPos > nObjCount )
+ nPos = nObjCount;
+
+ // #i23407# Set backwards-compatible text language and layout mode
+ // This is necessary, since old metafiles don't even know of these
+ // recent add-ons. Newer metafiles must of course explicitly set
+ // those states.
+ rOut.Push(vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE);
+ rOut.SetLayoutMode(vcl::text::ComplexTextLayoutFlags::Default);
+ rOut.SetDigitLanguage(LANGUAGE_SYSTEM);
+
+ SAL_INFO( "vcl.gdi", "GDIMetaFile::Play on device of size: " << rOut.GetOutputSizePixel().Width() << " " << rOut.GetOutputSizePixel().Height());
+
+ if (!ImplPlayWithRenderer(rOut, Point(0,0), rOut.GetOutputSize())) {
+ size_t i = 0;
+ for( size_t nCurPos = m_nCurrentActionElement; nCurPos < nPos; nCurPos++ )
+ {
+ if( pAction )
+ {
+ pAction->Execute(&rOut);
+
+ // flush output from time to time
+ if( i++ > nSyncCount )
+ {
+ rOut.Flush();
+ i = 0;
+ }
+ }
+
+ pAction = NextAction();
+ }
+ }
+ rOut.Pop();
+}
+
+bool GDIMetaFile::ImplPlayWithRenderer(OutputDevice& rOut, const Point& rPos, Size rLogicDestSize)
+{
+ if (!m_bUseCanvas)
+ return false;
+
+ Size rDestSize(rOut.LogicToPixel(rLogicDestSize));
+
+ const vcl::Window* win = rOut.GetOwnerWindow();
+
+ if (!win)
+ win = Application::GetActiveTopWindow();
+ if (!win)
+ win = Application::GetFirstTopLevelWindow();
+
+ if (!win)
+ return false;
+
+ try
+ {
+ uno::Reference<rendering::XCanvas> xCanvas = win->GetOutDev()->GetCanvas ();
+
+ if (!xCanvas.is())
+ return false;
+
+ Size aSize (rDestSize.Width () + 1, rDestSize.Height () + 1);
+ uno::Reference<rendering::XBitmap> xBitmap = xCanvas->getDevice ()->createCompatibleAlphaBitmap (vcl::unotools::integerSize2DFromSize( aSize));
+ if( xBitmap.is () )
+ {
+ uno::Reference< rendering::XBitmapCanvas > xBitmapCanvas( xBitmap, uno::UNO_QUERY );
+ if( xBitmapCanvas.is() )
+ {
+ uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext();
+ uno::Reference< rendering::XMtfRenderer > xMtfRenderer = rendering::MtfRenderer::createWithBitmapCanvas( xContext, xBitmapCanvas );
+
+ xBitmapCanvas->clear();
+ uno::Reference< beans::XFastPropertySet > xMtfFastPropertySet( xMtfRenderer, uno::UNO_QUERY );
+ if( xMtfFastPropertySet.is() )
+ // set this metafile to the renderer to
+ // speedup things (instead of copying data to
+ // sequence of bytes passed to renderer)
+ xMtfFastPropertySet->setFastPropertyValue( 0, uno::Any( reinterpret_cast<sal_Int64>( this ) ) );
+
+ xMtfRenderer->draw( rDestSize.Width(), rDestSize.Height() );
+
+ BitmapEx aBitmapEx;
+ if( aBitmapEx.Create( xBitmapCanvas, aSize ) )
+ {
+ if (rOut.GetMapMode().GetMapUnit() == MapUnit::MapPixel)
+ rOut.DrawBitmapEx( rPos, aBitmapEx );
+ else
+ rOut.DrawBitmapEx( rPos, rLogicDestSize, aBitmapEx );
+ return true;
+ }
+ }
+ }
+ }
+ catch (const uno::RuntimeException& )
+ {
+ throw; // runtime errors are fatal
+ }
+ catch (const uno::Exception&)
+ {
+ // ignore errors, no way of reporting them here
+ TOOLS_WARN_EXCEPTION("vcl.gdi", "GDIMetaFile::ImplPlayWithRenderer");
+ }
+
+ return false;
+}
+
+void GDIMetaFile::Play(OutputDevice& rOut, const Point& rPos,
+ const Size& rSize)
+{
+ MapMode aDrawMap( GetPrefMapMode() );
+ Size aDestSize(rOut.LogicToPixel(rSize));
+
+ if (aDestSize.Width() <= 0 || aDestSize.Height() <= 0)
+ return;
+
+ if (aDestSize.Width() > std::numeric_limits<sal_Int32>::max() ||
+ aDestSize.Height() > std::numeric_limits<sal_Int32>::max())
+ return;
+
+ GDIMetaFile* pMtf = rOut.GetConnectMetaFile();
+
+ if (ImplPlayWithRenderer(rOut, rPos, rSize))
+ return;
+
+ Size aTmpPrefSize(rOut.LogicToPixel(GetPrefSize(), aDrawMap));
+
+ if( !aTmpPrefSize.Width() )
+ aTmpPrefSize.setWidth( aDestSize.Width() );
+
+ if( !aTmpPrefSize.Height() )
+ aTmpPrefSize.setHeight( aDestSize.Height() );
+
+ Fraction aScaleX( aDestSize.Width(), aTmpPrefSize.Width() );
+ Fraction aScaleY( aDestSize.Height(), aTmpPrefSize.Height() );
+
+ aScaleX *= aDrawMap.GetScaleX();
+ aScaleY *= aDrawMap.GetScaleY();
+ // try reducing inaccurary first and abandon if the scaling
+ // still cannot be achieved
+ if (TooLargeScaleForMapMode(aScaleX, rOut.GetDPIX()))
+ aScaleX.ReduceInaccurate(10);
+ if (TooLargeScaleForMapMode(aScaleY, rOut.GetDPIY()))
+ aScaleY.ReduceInaccurate(10);
+ if (TooLargeScaleForMapMode(aScaleX, rOut.GetDPIX()) ||
+ TooLargeScaleForMapMode(aScaleY, rOut.GetDPIY()))
+ {
+ SAL_WARN("vcl", "GDIMetaFile Scaling is too high");
+ return;
+ }
+
+ aDrawMap.SetScaleX(aScaleX);
+ aDrawMap.SetScaleY(aScaleY);
+
+ // #i47260# Convert logical output position to offset within
+ // the metafile's mapmode. Therefore, disable pixel offset on
+ // outdev, it's inverse mnOutOffLogicX/Y is calculated for a
+ // different mapmode (the one currently set on rOut, that is)
+ // - thus, aDrawMap's origin would generally be wrong. And
+ // even _if_ aDrawMap is similar to pOutDev's current mapmode,
+ // it's _still_ undesirable to have pixel offset unequal zero,
+ // because one would still get round-off errors (the
+ // round-trip error for LogicToPixel( PixelToLogic() ) was the
+ // reason for having pixel offset in the first place).
+ const Size& rOldOffset(rOut.GetPixelOffset());
+ const Size aEmptySize;
+ rOut.SetPixelOffset(aEmptySize);
+ aDrawMap.SetOrigin(rOut.PixelToLogic(rOut.LogicToPixel(rPos), aDrawMap));
+ rOut.SetPixelOffset(rOldOffset);
+
+ rOut.Push();
+
+ bool bIsRecord = (pMtf && pMtf->IsRecord());
+ rOut.SetMetafileMapMode(aDrawMap, bIsRecord);
+
+ // #i23407# Set backwards-compatible text language and layout mode
+ // This is necessary, since old metafiles don't even know of these
+ // recent add-ons. Newer metafiles must of course explicitly set
+ // those states.
+ rOut.SetLayoutMode(vcl::text::ComplexTextLayoutFlags::Default);
+ rOut.SetDigitLanguage(LANGUAGE_SYSTEM);
+
+ Play(rOut);
+
+ rOut.Pop();
+}
+
+void GDIMetaFile::Pause( bool _bPause )
+{
+ if( !m_bRecord )
+ return;
+
+ if( _bPause )
+ {
+ if( !m_bPause )
+ Linker( m_pOutDev, false );
+ }
+ else
+ {
+ if( m_bPause )
+ Linker( m_pOutDev, true );
+ }
+
+ m_bPause = _bPause;
+}
+
+void GDIMetaFile::Stop()
+{
+ if( m_bRecord )
+ {
+ m_bRecord = false;
+
+ if( !m_bPause )
+ Linker( m_pOutDev, false );
+ else
+ m_bPause = false;
+ }
+}
+
+void GDIMetaFile::WindStart()
+{
+ if( !m_bRecord )
+ m_nCurrentActionElement = 0;
+}
+
+void GDIMetaFile::WindPrev()
+{
+ if( !m_bRecord )
+ if ( m_nCurrentActionElement > 0 )
+ --m_nCurrentActionElement;
+}
+
+void GDIMetaFile::AddAction(const rtl::Reference<MetaAction>& pAction)
+{
+ m_aList.push_back( pAction );
+
+ if( m_pPrev )
+ {
+ m_pPrev->AddAction( pAction );
+ }
+}
+
+void GDIMetaFile::AddAction(const rtl::Reference<MetaAction>& pAction, size_t nPos)
+{
+ if ( nPos < m_aList.size() )
+ {
+ m_aList.insert( m_aList.begin() + nPos, pAction );
+ }
+ else
+ {
+ m_aList.push_back( pAction );
+ }
+
+ if( m_pPrev )
+ {
+ m_pPrev->AddAction( pAction, nPos );
+ }
+}
+
+void GDIMetaFile::push_back(const rtl::Reference<MetaAction>& pAction)
+{
+ m_aList.push_back( pAction );
+}
+
+void GDIMetaFile::Mirror( BmpMirrorFlags nMirrorFlags )
+{
+ const Size aOldPrefSize( GetPrefSize() );
+ tools::Long nMoveX, nMoveY;
+ double fScaleX, fScaleY;
+
+ if( nMirrorFlags & BmpMirrorFlags::Horizontal )
+ {
+ nMoveX = std::abs( aOldPrefSize.Width() ) - 1;
+ fScaleX = -1.0;
+ }
+ else
+ {
+ nMoveX = 0;
+ fScaleX = 1.0;
+ }
+
+ if( nMirrorFlags & BmpMirrorFlags::Vertical )
+ {
+ nMoveY = std::abs( aOldPrefSize.Height() ) - 1;
+ fScaleY = -1.0;
+ }
+ else
+ {
+ nMoveY = 0;
+ fScaleY = 1.0;
+ }
+
+ if( ( fScaleX != 1.0 ) || ( fScaleY != 1.0 ) )
+ {
+ Scale( fScaleX, fScaleY );
+ Move( nMoveX, nMoveY );
+ SetPrefSize( aOldPrefSize );
+ }
+}
+
+void GDIMetaFile::Move( tools::Long nX, tools::Long nY )
+{
+ const Size aBaseOffset( nX, nY );
+ Size aOffset( aBaseOffset );
+ ScopedVclPtrInstance< VirtualDevice > aMapVDev;
+
+ aMapVDev->EnableOutput( false );
+ aMapVDev->SetMapMode( GetPrefMapMode() );
+
+ for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() )
+ {
+ const MetaActionType nType = pAct->GetType();
+ MetaAction* pModAct;
+
+ if( pAct->GetRefCount() > 1 )
+ {
+ m_aList[ m_nCurrentActionElement ] = pAct->Clone();
+ pModAct = m_aList[ m_nCurrentActionElement ].get();
+ }
+ else
+ pModAct = pAct;
+
+ if( ( MetaActionType::MAPMODE == nType ) ||
+ ( MetaActionType::PUSH == nType ) ||
+ ( MetaActionType::POP == nType ) )
+ {
+ pModAct->Execute( aMapVDev.get() );
+ aOffset = OutputDevice::LogicToLogic( aBaseOffset, GetPrefMapMode(), aMapVDev->GetMapMode() );
+ }
+
+ pModAct->Move( aOffset.Width(), aOffset.Height() );
+ }
+}
+
+void GDIMetaFile::Move( tools::Long nX, tools::Long nY, tools::Long nDPIX, tools::Long nDPIY )
+{
+ const Size aBaseOffset( nX, nY );
+ Size aOffset( aBaseOffset );
+ ScopedVclPtrInstance< VirtualDevice > aMapVDev;
+
+ aMapVDev->EnableOutput( false );
+ aMapVDev->SetReferenceDevice( nDPIX, nDPIY );
+ aMapVDev->SetMapMode( GetPrefMapMode() );
+
+ for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() )
+ {
+ const MetaActionType nType = pAct->GetType();
+ MetaAction* pModAct;
+
+ if( pAct->GetRefCount() > 1 )
+ {
+ m_aList[ m_nCurrentActionElement ] = pAct->Clone();
+ pModAct = m_aList[ m_nCurrentActionElement ].get();
+ }
+ else
+ pModAct = pAct;
+
+ if( ( MetaActionType::MAPMODE == nType ) ||
+ ( MetaActionType::PUSH == nType ) ||
+ ( MetaActionType::POP == nType ) )
+ {
+ pModAct->Execute( aMapVDev.get() );
+ if( aMapVDev->GetMapMode().GetMapUnit() == MapUnit::MapPixel )
+ {
+ aOffset = aMapVDev->LogicToPixel( aBaseOffset, GetPrefMapMode() );
+ MapMode aMap( aMapVDev->GetMapMode() );
+ aOffset.setWidth( static_cast<tools::Long>(aOffset.Width() * static_cast<double>(aMap.GetScaleX())) );
+ aOffset.setHeight( static_cast<tools::Long>(aOffset.Height() * static_cast<double>(aMap.GetScaleY())) );
+ }
+ else
+ aOffset = OutputDevice::LogicToLogic( aBaseOffset, GetPrefMapMode(), aMapVDev->GetMapMode() );
+ }
+
+ pModAct->Move( aOffset.Width(), aOffset.Height() );
+ }
+}
+
+void GDIMetaFile::Scale( double fScaleX, double fScaleY )
+{
+ for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() )
+ {
+ MetaAction* pModAct;
+
+ if( pAct->GetRefCount() > 1 )
+ {
+ m_aList[ m_nCurrentActionElement ] = pAct->Clone();
+ pModAct = m_aList[ m_nCurrentActionElement ].get();
+ }
+ else
+ pModAct = pAct;
+
+ pModAct->Scale( fScaleX, fScaleY );
+ }
+
+ m_aPrefSize.setWidth( FRound( m_aPrefSize.Width() * fScaleX ) );
+ m_aPrefSize.setHeight( FRound( m_aPrefSize.Height() * fScaleY ) );
+}
+
+void GDIMetaFile::Scale( const Fraction& rScaleX, const Fraction& rScaleY )
+{
+ Scale( static_cast<double>(rScaleX), static_cast<double>(rScaleY) );
+}
+
+void GDIMetaFile::Clip( const tools::Rectangle& i_rClipRect )
+{
+ tools::Rectangle aCurRect( i_rClipRect );
+ ScopedVclPtrInstance< VirtualDevice > aMapVDev;
+
+ aMapVDev->EnableOutput( false );
+ aMapVDev->SetMapMode( GetPrefMapMode() );
+
+ for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() )
+ {
+ const MetaActionType nType = pAct->GetType();
+
+ if( ( MetaActionType::MAPMODE == nType ) ||
+ ( MetaActionType::PUSH == nType ) ||
+ ( MetaActionType::POP == nType ) )
+ {
+ pAct->Execute( aMapVDev.get() );
+ aCurRect = OutputDevice::LogicToLogic( i_rClipRect, GetPrefMapMode(), aMapVDev->GetMapMode() );
+ }
+ else if( nType == MetaActionType::CLIPREGION )
+ {
+ MetaClipRegionAction* pOldAct = static_cast<MetaClipRegionAction*>(pAct);
+ vcl::Region aNewReg( aCurRect );
+ if( pOldAct->IsClipping() )
+ aNewReg.Intersect( pOldAct->GetRegion() );
+ MetaClipRegionAction* pNewAct = new MetaClipRegionAction( aNewReg, true );
+ m_aList[ m_nCurrentActionElement ] = pNewAct;
+ }
+ }
+}
+
+Point GDIMetaFile::ImplGetRotatedPoint( const Point& rPt, const Point& rRotatePt,
+ const Size& rOffset, double fSin, double fCos )
+{
+ const tools::Long nX = rPt.X() - rRotatePt.X();
+ const tools::Long nY = rPt.Y() - rRotatePt.Y();
+
+ return Point( FRound( fCos * nX + fSin * nY ) + rRotatePt.X() + rOffset.Width(),
+ -FRound( fSin * nX - fCos * nY ) + rRotatePt.Y() + rOffset.Height() );
+}
+
+tools::Polygon GDIMetaFile::ImplGetRotatedPolygon( const tools::Polygon& rPoly, const Point& rRotatePt,
+ const Size& rOffset, double fSin, double fCos )
+{
+ tools::Polygon aRet( rPoly );
+
+ aRet.Rotate( rRotatePt, fSin, fCos );
+ aRet.Move( rOffset.Width(), rOffset.Height() );
+
+ return aRet;
+}
+
+tools::PolyPolygon GDIMetaFile::ImplGetRotatedPolyPolygon( const tools::PolyPolygon& rPolyPoly, const Point& rRotatePt,
+ const Size& rOffset, double fSin, double fCos )
+{
+ tools::PolyPolygon aRet( rPolyPoly );
+
+ aRet.Rotate( rRotatePt, fSin, fCos );
+ aRet.Move( rOffset.Width(), rOffset.Height() );
+
+ return aRet;
+}
+
+void GDIMetaFile::ImplAddGradientEx( GDIMetaFile& rMtf,
+ const OutputDevice& rMapDev,
+ const tools::PolyPolygon& rPolyPoly,
+ const Gradient& rGrad )
+{
+ // Generate comment, GradientEx and Gradient actions (within DrawGradient)
+ ScopedVclPtrInstance< VirtualDevice > aVDev(rMapDev, DeviceFormat::DEFAULT);
+ aVDev->EnableOutput( false );
+ GDIMetaFile aGradMtf;
+
+ aGradMtf.Record( aVDev.get() );
+ aVDev->DrawGradient( rPolyPoly, rGrad );
+ aGradMtf.Stop();
+
+ size_t i, nAct( aGradMtf.GetActionSize() );
+ for( i=0; i < nAct; ++i )
+ {
+ MetaAction* pMetaAct = aGradMtf.GetAction( i );
+ rMtf.AddAction( pMetaAct );
+ }
+}
+
+void GDIMetaFile::Rotate( Degree10 nAngle10 )
+{
+ nAngle10 %= 3600_deg10;
+ nAngle10 = ( nAngle10 < 0_deg10 ) ? ( Degree10(3599) + nAngle10 ) : nAngle10;
+
+ if( !nAngle10 )
+ return;
+
+ GDIMetaFile aMtf;
+ ScopedVclPtrInstance< VirtualDevice > aMapVDev;
+ const double fAngle = toRadians(nAngle10);
+ const double fSin = sin( fAngle );
+ const double fCos = cos( fAngle );
+ tools::Rectangle aRect( Point(), GetPrefSize() );
+ tools::Polygon aPoly( aRect );
+
+ aPoly.Rotate( Point(), fSin, fCos );
+
+ aMapVDev->EnableOutput( false );
+ aMapVDev->SetMapMode( GetPrefMapMode() );
+
+ const tools::Rectangle aNewBound( aPoly.GetBoundRect() );
+
+ const Point aOrigin( GetPrefMapMode().GetOrigin().X(), GetPrefMapMode().GetOrigin().Y() );
+ const Size aOffset( -aNewBound.Left(), -aNewBound.Top() );
+
+ Point aRotAnchor( aOrigin );
+ Size aRotOffset( aOffset );
+
+ for( MetaAction* pAction = FirstAction(); pAction; pAction = NextAction() )
+ {
+ const MetaActionType nActionType = pAction->GetType();
+
+ switch( nActionType )
+ {
+ case MetaActionType::PIXEL:
+ {
+ MetaPixelAction* pAct = static_cast<MetaPixelAction*>(pAction);
+ aMtf.AddAction( new MetaPixelAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetColor() ) );
+ }
+ break;
+
+ case MetaActionType::POINT:
+ {
+ MetaPointAction* pAct = static_cast<MetaPointAction*>(pAction);
+ aMtf.AddAction( new MetaPointAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ MetaLineAction* pAct = static_cast<MetaLineAction*>(pAction);
+ aMtf.AddAction( new MetaLineAction( ImplGetRotatedPoint( pAct->GetStartPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ ImplGetRotatedPoint( pAct->GetEndPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetLineInfo() ) );
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ MetaRectAction* pAct = static_cast<MetaRectAction*>(pAction);
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( pAct->GetRect(), aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ MetaRoundRectAction* pAct = static_cast<MetaRoundRectAction*>(pAction);
+ const tools::Polygon aRoundRectPoly( pAct->GetRect(), pAct->GetHorzRound(), pAct->GetVertRound() );
+
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aRoundRectPoly, aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ MetaEllipseAction* pAct = static_cast<MetaEllipseAction*>(pAction);
+ const tools::Polygon aEllipsePoly( pAct->GetRect().Center(), pAct->GetRect().GetWidth() >> 1, pAct->GetRect().GetHeight() >> 1 );
+
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aEllipsePoly, aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::ARC:
+ {
+ MetaArcAction* pAct = static_cast<MetaArcAction*>(pAction);
+ const tools::Polygon aArcPoly( pAct->GetRect(), pAct->GetStartPoint(), pAct->GetEndPoint(), PolyStyle::Arc );
+
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aArcPoly, aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::PIE:
+ {
+ MetaPieAction* pAct = static_cast<MetaPieAction*>(pAction);
+ const tools::Polygon aPiePoly( pAct->GetRect(), pAct->GetStartPoint(), pAct->GetEndPoint(), PolyStyle::Pie );
+
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aPiePoly, aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::CHORD:
+ {
+ MetaChordAction* pAct = static_cast<MetaChordAction*>(pAction);
+ const tools::Polygon aChordPoly( pAct->GetRect(), pAct->GetStartPoint(), pAct->GetEndPoint(), PolyStyle::Chord );
+
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aChordPoly, aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ MetaPolyLineAction* pAct = static_cast<MetaPolyLineAction*>(pAction);
+ aMtf.AddAction( new MetaPolyLineAction( ImplGetRotatedPolygon( pAct->GetPolygon(), aRotAnchor, aRotOffset, fSin, fCos ), pAct->GetLineInfo() ) );
+ }
+ break;
+
+ case MetaActionType::POLYGON:
+ {
+ MetaPolygonAction* pAct = static_cast<MetaPolygonAction*>(pAction);
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( pAct->GetPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ {
+ MetaPolyPolygonAction* pAct = static_cast<MetaPolyPolygonAction*>(pAction);
+ aMtf.AddAction( new MetaPolyPolygonAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ MetaTextAction* pAct = static_cast<MetaTextAction*>(pAction);
+ aMtf.AddAction( new MetaTextAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetText(), pAct->GetIndex(), pAct->GetLen() ) );
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pAction);
+ aMtf.AddAction( new MetaTextArrayAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetText(), pAct->GetDXArray(), pAct->GetIndex(), pAct->GetLen() ) );
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ MetaStretchTextAction* pAct = static_cast<MetaStretchTextAction*>(pAction);
+ aMtf.AddAction( new MetaStretchTextAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetWidth(), pAct->GetText(), pAct->GetIndex(), pAct->GetLen() ) );
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ {
+ MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pAction);
+ aMtf.AddAction( new MetaTextLineAction( ImplGetRotatedPoint( pAct->GetStartPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetWidth(), pAct->GetStrikeout(), pAct->GetUnderline(), pAct->GetOverline() ) );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction);
+ tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Rectangle( pAct->GetPoint(), pAct->GetSize() ), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() );
+ BitmapEx aBmpEx( pAct->GetBitmap() );
+
+ aBmpEx.Rotate( nAngle10, COL_TRANSPARENT );
+ aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(),
+ aBmpEx ) );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction);
+ tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Rectangle( pAct->GetDestPoint(), pAct->GetDestSize() ), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() );
+ BitmapEx aBmpEx( pAct->GetBitmap() );
+
+ aBmpEx.Crop( tools::Rectangle( pAct->GetSrcPoint(), pAct->GetSrcSize() ) );
+ aBmpEx.Rotate( nAngle10, COL_TRANSPARENT );
+
+ aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), aBmpEx ) );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction);
+ tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Rectangle( pAct->GetPoint(), pAct->GetSize() ), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() );
+ BitmapEx aBmpEx( pAct->GetBitmapEx() );
+
+ aBmpEx.Rotate( nAngle10, COL_TRANSPARENT );
+
+ aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), aBmpEx ) );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction);
+ tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Rectangle( pAct->GetDestPoint(), pAct->GetDestSize() ), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() );
+ BitmapEx aBmpEx( pAct->GetBitmapEx() );
+
+ aBmpEx.Crop( tools::Rectangle( pAct->GetSrcPoint(), pAct->GetSrcSize() ) );
+ aBmpEx.Rotate( nAngle10, COL_TRANSPARENT );
+
+ aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), aBmpEx ) );
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction);
+
+ ImplAddGradientEx( aMtf, *aMapVDev,
+ ImplGetRotatedPolygon( pAct->GetRect(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetGradient() );
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ {
+ MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction);
+ aMtf.AddAction( new MetaGradientExAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetGradient() ) );
+ }
+ break;
+
+ // Handle gradientex comment block correctly
+ case MetaActionType::COMMENT:
+ {
+ MetaCommentAction* pCommentAct = static_cast<MetaCommentAction*>(pAction);
+ if( pCommentAct->GetComment() == "XGRAD_SEQ_BEGIN" )
+ {
+ int nBeginComments( 1 );
+ pAction = NextAction();
+
+ // skip everything, except gradientex action
+ while( pAction )
+ {
+ const MetaActionType nType = pAction->GetType();
+
+ if( MetaActionType::GRADIENTEX == nType )
+ {
+ // Add rotated gradientex
+ MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction);
+ ImplAddGradientEx( aMtf, *aMapVDev,
+ ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetGradient() );
+ }
+ else if( MetaActionType::COMMENT == nType)
+ {
+ MetaCommentAction* pAct = static_cast<MetaCommentAction*>(pAction);
+ if( pAct->GetComment() == "XGRAD_SEQ_END" )
+ {
+ // handle nested blocks
+ --nBeginComments;
+
+ // gradientex comment block: end reached, done.
+ if( !nBeginComments )
+ break;
+ }
+ else if( pAct->GetComment() == "XGRAD_SEQ_BEGIN" )
+ {
+ // handle nested blocks
+ ++nBeginComments;
+ }
+
+ }
+
+ pAction =NextAction();
+ }
+ }
+ else
+ {
+ bool bPathStroke = (pCommentAct->GetComment() == "XPATHSTROKE_SEQ_BEGIN");
+ if ( bPathStroke || pCommentAct->GetComment() == "XPATHFILL_SEQ_BEGIN" )
+ {
+ if ( pCommentAct->GetDataSize() )
+ {
+ SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pCommentAct->GetData()), pCommentAct->GetDataSize(), StreamMode::READ );
+ SvMemoryStream aDest;
+ if ( bPathStroke )
+ {
+ SvtGraphicStroke aStroke;
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+ tools::Polygon aPath;
+ aStroke.getPath( aPath );
+ aStroke.setPath( ImplGetRotatedPolygon( aPath, aRotAnchor, aRotOffset, fSin, fCos ) );
+ WriteSvtGraphicStroke( aDest, aStroke );
+ aMtf.AddAction( new MetaCommentAction( "XPATHSTROKE_SEQ_BEGIN", 0,
+ static_cast<const sal_uInt8*>( aDest.GetData()), aDest.Tell() ) );
+ }
+ else
+ {
+ SvtGraphicFill aFill;
+ ReadSvtGraphicFill( aMemStm, aFill );
+ tools::PolyPolygon aPath;
+ aFill.getPath( aPath );
+ aFill.setPath( ImplGetRotatedPolyPolygon( aPath, aRotAnchor, aRotOffset, fSin, fCos ) );
+ WriteSvtGraphicFill( aDest, aFill );
+ aMtf.AddAction( new MetaCommentAction( "XPATHFILL_SEQ_BEGIN", 0,
+ static_cast<const sal_uInt8*>( aDest.GetData()), aDest.Tell() ) );
+ }
+ }
+ }
+ else if ( pCommentAct->GetComment() == "XPATHSTROKE_SEQ_END"
+ || pCommentAct->GetComment() == "XPATHFILL_SEQ_END" )
+ {
+ pAction->Execute( aMapVDev.get() );
+ aMtf.AddAction( pAction );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ MetaHatchAction* pAct = static_cast<MetaHatchAction*>(pAction);
+ Hatch aHatch( pAct->GetHatch() );
+
+ aHatch.SetAngle( aHatch.GetAngle() + nAngle10 );
+ aMtf.AddAction( new MetaHatchAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ),
+ aHatch ) );
+ }
+ break;
+
+ case MetaActionType::Transparent:
+ {
+ MetaTransparentAction* pAct = static_cast<MetaTransparentAction*>(pAction);
+ aMtf.AddAction( new MetaTransparentAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetTransparence() ) );
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pAction);
+ GDIMetaFile aTransMtf( pAct->GetGDIMetaFile() );
+ tools::Polygon aMtfPoly( ImplGetRotatedPolygon( tools::Rectangle( pAct->GetPoint(), pAct->GetSize() ), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aMtfRect( aMtfPoly.GetBoundRect() );
+
+ aTransMtf.Rotate( nAngle10 );
+ aMtf.AddAction( new MetaFloatTransparentAction( aTransMtf, aMtfRect.TopLeft(), aMtfRect.GetSize(),
+ pAct->GetGradient() ) );
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction);
+ GDIMetaFile aEPSMtf( pAct->GetSubstitute() );
+ tools::Polygon aEPSPoly( ImplGetRotatedPolygon( tools::Rectangle( pAct->GetPoint(), pAct->GetSize() ), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aEPSRect( aEPSPoly.GetBoundRect() );
+
+ aEPSMtf.Rotate( nAngle10 );
+ aMtf.AddAction( new MetaEPSAction( aEPSRect.TopLeft(), aEPSRect.GetSize(),
+ pAct->GetLink(), aEPSMtf ) );
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ {
+ MetaClipRegionAction* pAct = static_cast<MetaClipRegionAction*>(pAction);
+
+ if( pAct->IsClipping() && pAct->GetRegion().HasPolyPolygonOrB2DPolyPolygon() )
+ aMtf.AddAction( new MetaClipRegionAction( vcl::Region( ImplGetRotatedPolyPolygon( pAct->GetRegion().GetAsPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ), true ) );
+ else
+ {
+ aMtf.AddAction( pAction );
+ }
+ }
+ break;
+
+ case MetaActionType::ISECTRECTCLIPREGION:
+ {
+ MetaISectRectClipRegionAction* pAct = static_cast<MetaISectRectClipRegionAction*>(pAction);
+ aMtf.AddAction( new MetaISectRegionClipRegionAction(vcl::Region(
+ ImplGetRotatedPolygon( pAct->GetRect(), aRotAnchor,
+ aRotOffset, fSin, fCos )) ) );
+ }
+ break;
+
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ {
+ MetaISectRegionClipRegionAction* pAct = static_cast<MetaISectRegionClipRegionAction*>(pAction);
+ const vcl::Region& rRegion = pAct->GetRegion();
+
+ if( rRegion.HasPolyPolygonOrB2DPolyPolygon() )
+ aMtf.AddAction( new MetaISectRegionClipRegionAction( vcl::Region( ImplGetRotatedPolyPolygon( rRegion.GetAsPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ) ) );
+ else
+ {
+ aMtf.AddAction( pAction );
+ }
+ }
+ break;
+
+ case MetaActionType::REFPOINT:
+ {
+ MetaRefPointAction* pAct = static_cast<MetaRefPointAction*>(pAction);
+ aMtf.AddAction( new MetaRefPointAction( ImplGetRotatedPoint( pAct->GetRefPoint(), aRotAnchor, aRotOffset, fSin, fCos ), pAct->IsSetting() ) );
+ }
+ break;
+
+ case MetaActionType::FONT:
+ {
+ MetaFontAction* pAct = static_cast<MetaFontAction*>(pAction);
+ vcl::Font aFont( pAct->GetFont() );
+
+ aFont.SetOrientation( aFont.GetOrientation() + nAngle10 );
+ aMtf.AddAction( new MetaFontAction( aFont ) );
+ }
+ break;
+
+ case MetaActionType::BMP:
+ case MetaActionType::BMPEX:
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ case MetaActionType::MASKSCALEPART:
+ case MetaActionType::WALLPAPER:
+ case MetaActionType::TEXTRECT:
+ case MetaActionType::MOVECLIPREGION:
+ {
+ OSL_FAIL( "GDIMetaFile::Rotate(): unsupported action" );
+ }
+ break;
+
+ default:
+ {
+ pAction->Execute( aMapVDev.get() );
+ aMtf.AddAction( pAction );
+
+ // update rotation point and offset, if necessary
+ if( ( MetaActionType::MAPMODE == nActionType ) ||
+ ( MetaActionType::PUSH == nActionType ) ||
+ ( MetaActionType::POP == nActionType ) )
+ {
+ aRotAnchor = OutputDevice::LogicToLogic( aOrigin, m_aPrefMapMode, aMapVDev->GetMapMode() );
+ aRotOffset = OutputDevice::LogicToLogic( aOffset, m_aPrefMapMode, aMapVDev->GetMapMode() );
+ }
+ }
+ break;
+ }
+ }
+
+ aMtf.m_aPrefMapMode = m_aPrefMapMode;
+ aMtf.m_aPrefSize = aNewBound.GetSize();
+
+ *this = aMtf;
+
+}
+
+static void ImplActionBounds( tools::Rectangle& o_rOutBounds,
+ const tools::Rectangle& i_rInBounds,
+ const std::vector<tools::Rectangle>& i_rClipStack )
+{
+ tools::Rectangle aBounds( i_rInBounds );
+ if( ! i_rInBounds.IsEmpty() && ! i_rClipStack.empty() && ! i_rClipStack.back().IsEmpty() )
+ aBounds.Intersection( i_rClipStack.back() );
+ if( aBounds.IsEmpty() )
+ return;
+
+ if( ! o_rOutBounds.IsEmpty() )
+ o_rOutBounds.Union( aBounds );
+ else
+ o_rOutBounds = aBounds;
+}
+
+tools::Rectangle GDIMetaFile::GetBoundRect( OutputDevice& i_rReference ) const
+{
+ ScopedVclPtrInstance< VirtualDevice > aMapVDev( i_rReference );
+
+ aMapVDev->EnableOutput( false );
+ aMapVDev->SetMapMode( GetPrefMapMode() );
+
+ std::vector<tools::Rectangle> aClipStack( 1, tools::Rectangle() );
+ std::vector<vcl::PushFlags> aPushFlagStack;
+
+ tools::Rectangle aBound;
+ const sal_uLong nCount(GetActionSize());
+
+ for(sal_uLong a(0); a < nCount; a++)
+ {
+ MetaAction* pAction = GetAction(a);
+ const MetaActionType nActionType = pAction->GetType();
+
+ switch( nActionType )
+ {
+ case MetaActionType::PIXEL:
+ {
+ MetaPixelAction* pAct = static_cast<MetaPixelAction*>(pAction);
+ ImplActionBounds( aBound,
+ tools::Rectangle( OutputDevice::LogicToLogic( pAct->GetPoint(), aMapVDev->GetMapMode(), GetPrefMapMode() ),
+ aMapVDev->PixelToLogic( Size( 1, 1 ), GetPrefMapMode() ) ),
+ aClipStack );
+ }
+ break;
+
+ case MetaActionType::POINT:
+ {
+ MetaPointAction* pAct = static_cast<MetaPointAction*>(pAction);
+ ImplActionBounds( aBound,
+ tools::Rectangle( OutputDevice::LogicToLogic( pAct->GetPoint(), aMapVDev->GetMapMode(), GetPrefMapMode() ),
+ aMapVDev->PixelToLogic( Size( 1, 1 ), GetPrefMapMode() ) ),
+ aClipStack );
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ MetaLineAction* pAct = static_cast<MetaLineAction*>(pAction);
+ Point aP1( pAct->GetStartPoint() ), aP2( pAct->GetEndPoint() );
+ tools::Rectangle aRect( aP1, aP2 );
+ aRect.Justify();
+
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ MetaRectAction* pAct = static_cast<MetaRectAction*>(pAction);
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ MetaRoundRectAction* pAct = static_cast<MetaRoundRectAction*>(pAction);
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ MetaEllipseAction* pAct = static_cast<MetaEllipseAction*>(pAction);
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::ARC:
+ {
+ MetaArcAction* pAct = static_cast<MetaArcAction*>(pAction);
+ // FIXME: this is imprecise
+ // e.g. for small arcs the whole rectangle is WAY too large
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::PIE:
+ {
+ MetaPieAction* pAct = static_cast<MetaPieAction*>(pAction);
+ // FIXME: this is imprecise
+ // e.g. for small arcs the whole rectangle is WAY too large
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::CHORD:
+ {
+ MetaChordAction* pAct = static_cast<MetaChordAction*>(pAction);
+ // FIXME: this is imprecise
+ // e.g. for small arcs the whole rectangle is WAY too large
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ MetaPolyLineAction* pAct = static_cast<MetaPolyLineAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPolygon().GetBoundRect() );
+
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::POLYGON:
+ {
+ MetaPolygonAction* pAct = static_cast<MetaPolygonAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPolygon().GetBoundRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ {
+ MetaPolyPolygonAction* pAct = static_cast<MetaPolyPolygonAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ MetaTextAction* pAct = static_cast<MetaTextAction*>(pAction);
+ tools::Rectangle aRect;
+ // hdu said base = index
+ aMapVDev->GetTextBoundRect( aRect, pAct->GetText(), pAct->GetIndex(), pAct->GetIndex(), pAct->GetLen() );
+ Point aPt( pAct->GetPoint() );
+ aRect.Move( aPt.X(), aPt.Y() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pAction);
+ tools::Rectangle aRect;
+ // hdu said base = index
+ aMapVDev->GetTextBoundRect( aRect, pAct->GetText(), pAct->GetIndex(), pAct->GetIndex(), pAct->GetLen(),
+ 0, pAct->GetDXArray() );
+ Point aPt( pAct->GetPoint() );
+ aRect.Move( aPt.X(), aPt.Y() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ MetaStretchTextAction* pAct = static_cast<MetaStretchTextAction*>(pAction);
+ tools::Rectangle aRect;
+ // hdu said base = index
+ aMapVDev->GetTextBoundRect( aRect, pAct->GetText(), pAct->GetIndex(), pAct->GetIndex(), pAct->GetLen(),
+ pAct->GetWidth() );
+ Point aPt( pAct->GetPoint() );
+ aRect.Move( aPt.X(), aPt.Y() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ {
+ MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pAction);
+ // measure a test string to get ascend and descent right
+ static constexpr OUStringLiteral pStr = u"\u00c4g";
+ OUString aStr( pStr );
+
+ tools::Rectangle aRect;
+ aMapVDev->GetTextBoundRect( aRect, aStr, 0, 0, aStr.getLength() );
+ Point aPt( pAct->GetStartPoint() );
+ aRect.Move( aPt.X(), aPt.Y() );
+ aRect.SetRight( aRect.Left() + pAct->GetWidth() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ {
+ MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::COMMENT:
+ {
+ // nothing to do
+ };
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ MetaHatchAction* pAct = static_cast<MetaHatchAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::Transparent:
+ {
+ MetaTransparentAction* pAct = static_cast<MetaTransparentAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pAction);
+ // MetaFloatTransparentAction is defined limiting its content Metafile
+ // to its geometry definition(Point, Size), so use these directly
+ const tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ {
+ MetaClipRegionAction* pAct = static_cast<MetaClipRegionAction*>(pAction);
+ if( pAct->IsClipping() )
+ aClipStack.back() = OutputDevice::LogicToLogic( pAct->GetRegion().GetBoundRect(), aMapVDev->GetMapMode(), GetPrefMapMode() );
+ else
+ aClipStack.back() = tools::Rectangle();
+ }
+ break;
+
+ case MetaActionType::ISECTRECTCLIPREGION:
+ {
+ MetaISectRectClipRegionAction* pAct = static_cast<MetaISectRectClipRegionAction*>(pAction);
+ tools::Rectangle aRect( OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ) );
+ if( aClipStack.back().IsEmpty() )
+ aClipStack.back() = aRect;
+ else
+ aClipStack.back().Intersection( aRect );
+ }
+ break;
+
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ {
+ MetaISectRegionClipRegionAction* pAct = static_cast<MetaISectRegionClipRegionAction*>(pAction);
+ tools::Rectangle aRect( OutputDevice::LogicToLogic( pAct->GetRegion().GetBoundRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ) );
+ if( aClipStack.back().IsEmpty() )
+ aClipStack.back() = aRect;
+ else
+ aClipStack.back().Intersection( aRect );
+ }
+ break;
+
+ case MetaActionType::BMP:
+ {
+ MetaBmpAction* pAct = static_cast<MetaBmpAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPoint(), aMapVDev->PixelToLogic( pAct->GetBitmap().GetSizePixel() ) );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ MetaBmpExAction* pAct = static_cast<MetaBmpExAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPoint(), aMapVDev->PixelToLogic( pAct->GetBitmapEx().GetSizePixel() ) );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::MASK:
+ {
+ MetaMaskAction* pAct = static_cast<MetaMaskAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPoint(), aMapVDev->PixelToLogic( pAct->GetBitmap().GetSizePixel() ) );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::MASKSCALE:
+ {
+ MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ {
+ MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::WALLPAPER:
+ {
+ MetaWallpaperAction* pAct = static_cast<MetaWallpaperAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ MetaTextRectAction* pAct = static_cast<MetaTextRectAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::MOVECLIPREGION:
+ {
+ MetaMoveClipRegionAction* pAct = static_cast<MetaMoveClipRegionAction*>(pAction);
+ if( ! aClipStack.back().IsEmpty() )
+ {
+ Size aDelta( pAct->GetHorzMove(), pAct->GetVertMove() );
+ aDelta = OutputDevice::LogicToLogic( aDelta, aMapVDev->GetMapMode(), GetPrefMapMode() );
+ aClipStack.back().Move( aDelta.Width(), aDelta.Width() );
+ }
+ }
+ break;
+
+ default:
+ {
+ pAction->Execute( aMapVDev.get() );
+
+ if( nActionType == MetaActionType::PUSH )
+ {
+ MetaPushAction* pAct = static_cast<MetaPushAction*>(pAction);
+ aPushFlagStack.push_back( pAct->GetFlags() );
+ if( aPushFlagStack.back() & vcl::PushFlags::CLIPREGION )
+ {
+ tools::Rectangle aRect( aClipStack.back() );
+ aClipStack.push_back( aRect );
+ }
+ }
+ else if( nActionType == MetaActionType::POP )
+ {
+ // sanity check
+ if( ! aPushFlagStack.empty() )
+ {
+ if( aPushFlagStack.back() & vcl::PushFlags::CLIPREGION )
+ {
+ if( aClipStack.size() > 1 )
+ aClipStack.pop_back();
+ }
+ aPushFlagStack.pop_back();
+ }
+ }
+ }
+ break;
+ }
+ }
+ return aBound;
+}
+
+Color GDIMetaFile::ImplColAdjustFnc( const Color& rColor, const void* pColParam )
+{
+ return Color( ColorAlpha, rColor.GetAlpha(),
+ static_cast<const ImplColAdjustParam*>(pColParam)->pMapR[ rColor.GetRed() ],
+ static_cast<const ImplColAdjustParam*>(pColParam)->pMapG[ rColor.GetGreen() ],
+ static_cast<const ImplColAdjustParam*>(pColParam)->pMapB[ rColor.GetBlue() ] );
+
+}
+
+BitmapEx GDIMetaFile::ImplBmpAdjustFnc( const BitmapEx& rBmpEx, const void* pBmpParam )
+{
+ const ImplBmpAdjustParam* p = static_cast<const ImplBmpAdjustParam*>(pBmpParam);
+ BitmapEx aRet( rBmpEx );
+
+ aRet.Adjust( p->nLuminancePercent, p->nContrastPercent,
+ p->nChannelRPercent, p->nChannelGPercent, p->nChannelBPercent,
+ p->fGamma, p->bInvert );
+
+ return aRet;
+}
+
+Color GDIMetaFile::ImplColConvertFnc( const Color& rColor, const void* pColParam )
+{
+ sal_uInt8 cLum = rColor.GetLuminance();
+
+ if( MtfConversion::N1BitThreshold == static_cast<const ImplColConvertParam*>(pColParam)->eConversion )
+ cLum = ( cLum < 128 ) ? 0 : 255;
+
+ return Color( ColorTransparency, 255 - rColor.GetAlpha(), cLum, cLum, cLum );
+}
+
+BitmapEx GDIMetaFile::ImplBmpConvertFnc( const BitmapEx& rBmpEx, const void* pBmpParam )
+{
+ BitmapEx aRet( rBmpEx );
+
+ aRet.Convert( static_cast<const ImplBmpConvertParam*>(pBmpParam)->eConversion );
+
+ return aRet;
+}
+
+Color GDIMetaFile::ImplColMonoFnc( const Color&, const void* pColParam )
+{
+ return static_cast<const ImplColMonoParam*>(pColParam)->aColor;
+}
+
+BitmapEx GDIMetaFile::ImplBmpMonoFnc( const BitmapEx& rBmpEx, const void* pBmpParam )
+{
+ BitmapPalette aPal( 3 );
+ aPal[ 0 ] = COL_BLACK;
+ aPal[ 1 ] = COL_WHITE;
+ aPal[ 2 ] = static_cast<const ImplBmpMonoParam*>(pBmpParam)->aColor;
+
+ Bitmap aBmp(rBmpEx.GetSizePixel(), vcl::PixelFormat::N8_BPP, &aPal);
+ aBmp.Erase( static_cast<const ImplBmpMonoParam*>(pBmpParam)->aColor );
+
+ if( rBmpEx.IsAlpha() )
+ return BitmapEx( aBmp, rBmpEx.GetAlpha() );
+ else
+ return BitmapEx( aBmp );
+}
+
+Color GDIMetaFile::ImplColReplaceFnc( const Color& rColor, const void* pColParam )
+{
+ const sal_uLong nR = rColor.GetRed(), nG = rColor.GetGreen(), nB = rColor.GetBlue();
+
+ for( sal_uLong i = 0; i < static_cast<const ImplColReplaceParam*>(pColParam)->nCount; i++ )
+ {
+ if( ( static_cast<const ImplColReplaceParam*>(pColParam)->pMinR[ i ] <= nR ) &&
+ ( static_cast<const ImplColReplaceParam*>(pColParam)->pMaxR[ i ] >= nR ) &&
+ ( static_cast<const ImplColReplaceParam*>(pColParam)->pMinG[ i ] <= nG ) &&
+ ( static_cast<const ImplColReplaceParam*>(pColParam)->pMaxG[ i ] >= nG ) &&
+ ( static_cast<const ImplColReplaceParam*>(pColParam)->pMinB[ i ] <= nB ) &&
+ ( static_cast<const ImplColReplaceParam*>(pColParam)->pMaxB[ i ] >= nB ) )
+ {
+ return static_cast<const ImplColReplaceParam*>(pColParam)->pDstCols[ i ];
+ }
+ }
+
+ return rColor;
+}
+
+BitmapEx GDIMetaFile::ImplBmpReplaceFnc( const BitmapEx& rBmpEx, const void* pBmpParam )
+{
+ const ImplBmpReplaceParam* p = static_cast<const ImplBmpReplaceParam*>(pBmpParam);
+ BitmapEx aRet( rBmpEx );
+
+ aRet.Replace( p->pSrcCols, p->pDstCols, p->nCount );
+
+ return aRet;
+}
+
+void GDIMetaFile::ImplExchangeColors( ColorExchangeFnc pFncCol, const void* pColParam,
+ BmpExchangeFnc pFncBmp, const void* pBmpParam )
+{
+ GDIMetaFile aMtf;
+
+ aMtf.m_aPrefSize = m_aPrefSize;
+ aMtf.m_aPrefMapMode = m_aPrefMapMode;
+ aMtf.m_bUseCanvas = m_bUseCanvas;
+
+ for( MetaAction* pAction = FirstAction(); pAction; pAction = NextAction() )
+ {
+ const MetaActionType nType = pAction->GetType();
+
+ switch( nType )
+ {
+ case MetaActionType::PIXEL:
+ {
+ MetaPixelAction* pAct = static_cast<MetaPixelAction*>(pAction);
+ aMtf.push_back( new MetaPixelAction( pAct->GetPoint(), pFncCol( pAct->GetColor(), pColParam ) ) );
+ }
+ break;
+
+ case MetaActionType::LINECOLOR:
+ {
+ MetaLineColorAction* pAct = static_cast<MetaLineColorAction*>(pAction);
+
+ if( pAct->IsSetting() )
+ pAct = new MetaLineColorAction( pFncCol( pAct->GetColor(), pColParam ), true );
+
+ aMtf.push_back( pAct );
+ }
+ break;
+
+ case MetaActionType::FILLCOLOR:
+ {
+ MetaFillColorAction* pAct = static_cast<MetaFillColorAction*>(pAction);
+
+ if( pAct->IsSetting() )
+ pAct = new MetaFillColorAction( pFncCol( pAct->GetColor(), pColParam ), true );
+
+ aMtf.push_back( pAct );
+ }
+ break;
+
+ case MetaActionType::TEXTCOLOR:
+ {
+ MetaTextColorAction* pAct = static_cast<MetaTextColorAction*>(pAction);
+ aMtf.push_back( new MetaTextColorAction( pFncCol( pAct->GetColor(), pColParam ) ) );
+ }
+ break;
+
+ case MetaActionType::TEXTFILLCOLOR:
+ {
+ MetaTextFillColorAction* pAct = static_cast<MetaTextFillColorAction*>(pAction);
+
+ if( pAct->IsSetting() )
+ pAct = new MetaTextFillColorAction( pFncCol( pAct->GetColor(), pColParam ), true );
+
+ aMtf.push_back( pAct );
+ }
+ break;
+
+ case MetaActionType::TEXTLINECOLOR:
+ {
+ MetaTextLineColorAction* pAct = static_cast<MetaTextLineColorAction*>(pAction);
+
+ if( pAct->IsSetting() )
+ pAct = new MetaTextLineColorAction( pFncCol( pAct->GetColor(), pColParam ), true );
+
+ aMtf.push_back( pAct );
+ }
+ break;
+
+ case MetaActionType::OVERLINECOLOR:
+ {
+ MetaOverlineColorAction* pAct = static_cast<MetaOverlineColorAction*>(pAction);
+
+ if( pAct->IsSetting() )
+ pAct = new MetaOverlineColorAction( pFncCol( pAct->GetColor(), pColParam ), true );
+
+ aMtf.push_back( pAct );
+ }
+ break;
+
+ case MetaActionType::FONT:
+ {
+ MetaFontAction* pAct = static_cast<MetaFontAction*>(pAction);
+ vcl::Font aFont( pAct->GetFont() );
+
+ aFont.SetColor( pFncCol( aFont.GetColor(), pColParam ) );
+ aFont.SetFillColor( pFncCol( aFont.GetFillColor(), pColParam ) );
+ aMtf.push_back( new MetaFontAction( aFont ) );
+ }
+ break;
+
+ case MetaActionType::WALLPAPER:
+ {
+ MetaWallpaperAction* pAct = static_cast<MetaWallpaperAction*>(pAction);
+ Wallpaper aWall( pAct->GetWallpaper() );
+ const tools::Rectangle& rRect = pAct->GetRect();
+
+ aWall.SetColor( pFncCol( aWall.GetColor(), pColParam ) );
+
+ if( aWall.IsBitmap() )
+ aWall.SetBitmap( pFncBmp( aWall.GetBitmap(), pBmpParam ) );
+
+ if( aWall.IsGradient() )
+ {
+ Gradient aGradient( aWall.GetGradient() );
+
+ aGradient.SetStartColor( pFncCol( aGradient.GetStartColor(), pColParam ) );
+ aGradient.SetEndColor( pFncCol( aGradient.GetEndColor(), pColParam ) );
+ aWall.SetGradient( aGradient );
+ }
+
+ aMtf.push_back( new MetaWallpaperAction( rRect, aWall ) );
+ }
+ break;
+
+ case MetaActionType::BMP:
+ case MetaActionType::BMPEX:
+ case MetaActionType::MASK:
+ {
+ OSL_FAIL( "Don't use bitmap actions of this type in metafiles!" );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction);
+ aMtf.push_back( new MetaBmpScaleAction( pAct->GetPoint(), pAct->GetSize(),
+ pFncBmp( BitmapEx(pAct->GetBitmap()), pBmpParam ).GetBitmap() ) );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction);
+ aMtf.push_back( new MetaBmpScalePartAction( pAct->GetDestPoint(), pAct->GetDestSize(),
+ pAct->GetSrcPoint(), pAct->GetSrcSize(),
+ pFncBmp( BitmapEx(pAct->GetBitmap()), pBmpParam ).GetBitmap() )
+ );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction);
+ aMtf.push_back( new MetaBmpExScaleAction( pAct->GetPoint(), pAct->GetSize(),
+ pFncBmp( pAct->GetBitmapEx(), pBmpParam ) )
+ );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction);
+ aMtf.push_back( new MetaBmpExScalePartAction( pAct->GetDestPoint(), pAct->GetDestSize(),
+ pAct->GetSrcPoint(), pAct->GetSrcSize(),
+ pFncBmp( pAct->GetBitmapEx(), pBmpParam ) )
+ );
+ }
+ break;
+
+ case MetaActionType::MASKSCALE:
+ {
+ MetaMaskScaleAction* pAct = static_cast<MetaMaskScaleAction*>(pAction);
+ aMtf.push_back( new MetaMaskScaleAction( pAct->GetPoint(), pAct->GetSize(),
+ pAct->GetBitmap(),
+ pFncCol( pAct->GetColor(), pColParam ) )
+ );
+ }
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ {
+ MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction);
+ aMtf.push_back( new MetaMaskScalePartAction( pAct->GetDestPoint(), pAct->GetDestSize(),
+ pAct->GetSrcPoint(), pAct->GetSrcSize(),
+ pAct->GetBitmap(),
+ pFncCol( pAct->GetColor(), pColParam ) )
+ );
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction);
+ Gradient aGradient( pAct->GetGradient() );
+
+ aGradient.SetStartColor( pFncCol( aGradient.GetStartColor(), pColParam ) );
+ aGradient.SetEndColor( pFncCol( aGradient.GetEndColor(), pColParam ) );
+ aMtf.push_back( new MetaGradientAction( pAct->GetRect(), aGradient ) );
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ {
+ MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction);
+ Gradient aGradient( pAct->GetGradient() );
+
+ aGradient.SetStartColor( pFncCol( aGradient.GetStartColor(), pColParam ) );
+ aGradient.SetEndColor( pFncCol( aGradient.GetEndColor(), pColParam ) );
+ aMtf.push_back( new MetaGradientExAction( pAct->GetPolyPolygon(), aGradient ) );
+ }
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ MetaHatchAction* pAct = static_cast<MetaHatchAction*>(pAction);
+ Hatch aHatch( pAct->GetHatch() );
+
+ aHatch.SetColor( pFncCol( aHatch.GetColor(), pColParam ) );
+ aMtf.push_back( new MetaHatchAction( pAct->GetPolyPolygon(), aHatch ) );
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pAction);
+ GDIMetaFile aTransMtf( pAct->GetGDIMetaFile() );
+
+ aTransMtf.ImplExchangeColors( pFncCol, pColParam, pFncBmp, pBmpParam );
+ aMtf.push_back( new MetaFloatTransparentAction( aTransMtf,
+ pAct->GetPoint(), pAct->GetSize(),
+ pAct->GetGradient() )
+ );
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction);
+ GDIMetaFile aSubst( pAct->GetSubstitute() );
+
+ aSubst.ImplExchangeColors( pFncCol, pColParam, pFncBmp, pBmpParam );
+ aMtf.push_back( new MetaEPSAction( pAct->GetPoint(), pAct->GetSize(),
+ pAct->GetLink(), aSubst )
+ );
+ }
+ break;
+
+ default:
+ {
+ aMtf.push_back( pAction );
+ }
+ break;
+ }
+ }
+
+ *this = aMtf;
+}
+
+void GDIMetaFile::Adjust( short nLuminancePercent, short nContrastPercent,
+ short nChannelRPercent, short nChannelGPercent,
+ short nChannelBPercent, double fGamma, bool bInvert, bool msoBrightness )
+{
+ // nothing to do? => return quickly
+ if( !(nLuminancePercent || nContrastPercent ||
+ nChannelRPercent || nChannelGPercent || nChannelBPercent ||
+ ( fGamma != 1.0 ) || bInvert) )
+ return;
+
+ double fM, fROff, fGOff, fBOff, fOff;
+ ImplColAdjustParam aColParam;
+ ImplBmpAdjustParam aBmpParam;
+
+ aColParam.pMapR.reset(new sal_uInt8[ 256 ]);
+ aColParam.pMapG.reset(new sal_uInt8[ 256 ]);
+ aColParam.pMapB.reset(new sal_uInt8[ 256 ]);
+
+ // calculate slope
+ if( nContrastPercent >= 0 )
+ fM = 128.0 / ( 128.0 - 1.27 * MinMax( nContrastPercent, 0, 100 ) );
+ else
+ fM = ( 128.0 + 1.27 * MinMax( nContrastPercent, -100, 0 ) ) / 128.0;
+
+ if(!msoBrightness)
+ // total offset = luminance offset + contrast offset
+ fOff = MinMax( nLuminancePercent, -100, 100 ) * 2.55 + 128.0 - fM * 128.0;
+ else
+ fOff = MinMax( nLuminancePercent, -100, 100 ) * 2.55;
+
+ // channel offset = channel offset + total offset
+ fROff = nChannelRPercent * 2.55 + fOff;
+ fGOff = nChannelGPercent * 2.55 + fOff;
+ fBOff = nChannelBPercent * 2.55 + fOff;
+
+ // calculate gamma value
+ fGamma = ( fGamma <= 0.0 || fGamma > 10.0 ) ? 1.0 : ( 1.0 / fGamma );
+ const bool bGamma = ( fGamma != 1.0 );
+
+ // create mapping table
+ for( tools::Long nX = 0; nX < 256; nX++ )
+ {
+ if(!msoBrightness)
+ {
+ aColParam.pMapR[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fROff ), 0, 255 ));
+ aColParam.pMapG[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fGOff ), 0, 255 ));
+ aColParam.pMapB[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fBOff ), 0, 255 ));
+ }
+ else
+ {
+ aColParam.pMapR[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fROff/2-128) * fM + 128 + fROff/2 ), 0, 255 ));
+ aColParam.pMapG[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fGOff/2-128) * fM + 128 + fGOff/2 ), 0, 255 ));
+ aColParam.pMapB[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fBOff/2-128) * fM + 128 + fBOff/2 ), 0, 255 ));
+ }
+ if( bGamma )
+ {
+ aColParam.pMapR[ nX ] = GAMMA( aColParam.pMapR[ nX ], fGamma );
+ aColParam.pMapG[ nX ] = GAMMA( aColParam.pMapG[ nX ], fGamma );
+ aColParam.pMapB[ nX ] = GAMMA( aColParam.pMapB[ nX ], fGamma );
+ }
+
+ if( bInvert )
+ {
+ aColParam.pMapR[ nX ] = ~aColParam.pMapR[ nX ];
+ aColParam.pMapG[ nX ] = ~aColParam.pMapG[ nX ];
+ aColParam.pMapB[ nX ] = ~aColParam.pMapB[ nX ];
+ }
+ }
+
+ aBmpParam.nLuminancePercent = nLuminancePercent;
+ aBmpParam.nContrastPercent = nContrastPercent;
+ aBmpParam.nChannelRPercent = nChannelRPercent;
+ aBmpParam.nChannelGPercent = nChannelGPercent;
+ aBmpParam.nChannelBPercent = nChannelBPercent;
+ aBmpParam.fGamma = fGamma;
+ aBmpParam.bInvert = bInvert;
+
+ // do color adjustment
+ ImplExchangeColors( ImplColAdjustFnc, &aColParam, ImplBmpAdjustFnc, &aBmpParam );
+}
+
+void GDIMetaFile::Convert( MtfConversion eConversion )
+{
+ ImplColConvertParam aColParam;
+ ImplBmpConvertParam aBmpParam;
+
+ aColParam.eConversion = eConversion;
+ aBmpParam.eConversion = ( MtfConversion::N1BitThreshold == eConversion ) ? BmpConversion::N1BitThreshold : BmpConversion::N8BitGreys;
+
+ ImplExchangeColors( ImplColConvertFnc, &aColParam, ImplBmpConvertFnc, &aBmpParam );
+}
+
+void GDIMetaFile::ReplaceColors( const Color* pSearchColors, const Color* pReplaceColors, sal_uLong nColorCount )
+{
+ ImplColReplaceParam aColParam;
+ ImplBmpReplaceParam aBmpParam;
+
+ aColParam.pMinR.reset(new sal_uLong[ nColorCount ]);
+ aColParam.pMaxR.reset(new sal_uLong[ nColorCount ]);
+ aColParam.pMinG.reset(new sal_uLong[ nColorCount ]);
+ aColParam.pMaxG.reset(new sal_uLong[ nColorCount ]);
+ aColParam.pMinB.reset(new sal_uLong[ nColorCount ]);
+ aColParam.pMaxB.reset(new sal_uLong[ nColorCount ]);
+
+ for( sal_uLong i = 0; i < nColorCount; i++ )
+ {
+ tools::Long nVal;
+
+ nVal = pSearchColors[ i ].GetRed();
+ aColParam.pMinR[ i ] = static_cast<sal_uLong>(std::max( nVal, tools::Long(0) ));
+ aColParam.pMaxR[ i ] = static_cast<sal_uLong>(std::min( nVal, tools::Long(255) ));
+
+ nVal = pSearchColors[ i ].GetGreen();
+ aColParam.pMinG[ i ] = static_cast<sal_uLong>(std::max( nVal, tools::Long(0) ));
+ aColParam.pMaxG[ i ] = static_cast<sal_uLong>(std::min( nVal, tools::Long(255) ));
+
+ nVal = pSearchColors[ i ].GetBlue();
+ aColParam.pMinB[ i ] = static_cast<sal_uLong>(std::max( nVal, tools::Long(0) ));
+ aColParam.pMaxB[ i ] = static_cast<sal_uLong>(std::min( nVal, tools::Long(255) ));
+ }
+
+ aColParam.pDstCols = pReplaceColors;
+ aColParam.nCount = nColorCount;
+
+ aBmpParam.pSrcCols = pSearchColors;
+ aBmpParam.pDstCols = pReplaceColors;
+ aBmpParam.nCount = nColorCount;
+
+ ImplExchangeColors( ImplColReplaceFnc, &aColParam, ImplBmpReplaceFnc, &aBmpParam );
+};
+
+GDIMetaFile GDIMetaFile::GetMonochromeMtf( const Color& rColor ) const
+{
+ GDIMetaFile aRet( *this );
+
+ ImplColMonoParam aColParam;
+ ImplBmpMonoParam aBmpParam;
+
+ aColParam.aColor = rColor;
+ aBmpParam.aColor = rColor;
+
+ aRet.ImplExchangeColors( ImplColMonoFnc, &aColParam, ImplBmpMonoFnc, &aBmpParam );
+
+ return aRet;
+}
+
+sal_uLong GDIMetaFile::GetSizeBytes() const
+{
+ sal_uLong nSizeBytes = 0;
+
+ for( size_t i = 0, nObjCount = GetActionSize(); i < nObjCount; ++i )
+ {
+ MetaAction* pAction = GetAction( i );
+
+ // default action size is set to 32 (=> not the exact value)
+ nSizeBytes += 32;
+
+ // add sizes for large action content
+ switch( pAction->GetType() )
+ {
+ case MetaActionType::BMP: nSizeBytes += static_cast<MetaBmpAction*>( pAction )->GetBitmap().GetSizeBytes(); break;
+ case MetaActionType::BMPSCALE: nSizeBytes += static_cast<MetaBmpScaleAction*>( pAction )->GetBitmap().GetSizeBytes(); break;
+ case MetaActionType::BMPSCALEPART: nSizeBytes += static_cast<MetaBmpScalePartAction*>( pAction )->GetBitmap().GetSizeBytes(); break;
+
+ case MetaActionType::BMPEX: nSizeBytes += static_cast<MetaBmpExAction*>( pAction )->GetBitmapEx().GetSizeBytes(); break;
+ case MetaActionType::BMPEXSCALE: nSizeBytes += static_cast<MetaBmpExScaleAction*>( pAction )->GetBitmapEx().GetSizeBytes(); break;
+ case MetaActionType::BMPEXSCALEPART: nSizeBytes += static_cast<MetaBmpExScalePartAction*>( pAction )->GetBitmapEx().GetSizeBytes(); break;
+
+ case MetaActionType::MASK: nSizeBytes += static_cast<MetaMaskAction*>( pAction )->GetBitmap().GetSizeBytes(); break;
+ case MetaActionType::MASKSCALE: nSizeBytes += static_cast<MetaMaskScaleAction*>( pAction )->GetBitmap().GetSizeBytes(); break;
+ case MetaActionType::MASKSCALEPART: nSizeBytes += static_cast<MetaMaskScalePartAction*>( pAction )->GetBitmap().GetSizeBytes(); break;
+
+ case MetaActionType::POLYLINE: nSizeBytes += static_cast<MetaPolyLineAction*>( pAction )->GetPolygon().GetSize() * sizeof( Point ); break;
+ case MetaActionType::POLYGON: nSizeBytes += static_cast<MetaPolygonAction*>( pAction )->GetPolygon().GetSize() * sizeof( Point ); break;
+ case MetaActionType::POLYPOLYGON:
+ {
+ const tools::PolyPolygon& rPolyPoly = static_cast<MetaPolyPolygonAction*>( pAction )->GetPolyPolygon();
+
+ for( sal_uInt16 n = 0; n < rPolyPoly.Count(); ++n )
+ nSizeBytes += ( rPolyPoly[ n ].GetSize() * sizeof( Point ) );
+ }
+ break;
+
+ case MetaActionType::TEXT: nSizeBytes += static_cast<MetaTextAction*>( pAction )->GetText().getLength() * sizeof( sal_Unicode ); break;
+ case MetaActionType::STRETCHTEXT: nSizeBytes += static_cast<MetaStretchTextAction*>( pAction )->GetText().getLength() * sizeof( sal_Unicode ); break;
+ case MetaActionType::TEXTRECT: nSizeBytes += static_cast<MetaTextRectAction*>( pAction )->GetText().getLength() * sizeof( sal_Unicode ); break;
+ case MetaActionType::TEXTARRAY:
+ {
+ MetaTextArrayAction* pTextArrayAction = static_cast<MetaTextArrayAction*>(pAction);
+
+ nSizeBytes += ( pTextArrayAction->GetText().getLength() * sizeof( sal_Unicode ) );
+
+ if( !pTextArrayAction->GetDXArray().empty() )
+ nSizeBytes += ( pTextArrayAction->GetLen() << 2 );
+ }
+ break;
+ default: break;
+ }
+ }
+
+ return nSizeBytes;
+}
+
+bool GDIMetaFile::CreateThumbnail(BitmapEx& rBitmapEx, BmpConversion eColorConversion, BmpScaleFlag nScaleFlag) const
+{
+ // initialization seems to be complicated but is used to avoid rounding errors
+ ScopedVclPtrInstance< VirtualDevice > aVDev;
+ // set Enable to tease the rendering down the code paths which use B2DPolygon and
+ // avoid integer overflows on scaling tools::Polygon, e.g. moz1545040-1.svg
+ // note: this is similar to DocumentToGraphicRenderer::renderToGraphic
+ aVDev->SetAntialiasing(AntialiasingFlags::Enable | aVDev->GetAntialiasing());
+ const Point aNullPt;
+ const Point aTLPix( aVDev->LogicToPixel( aNullPt, GetPrefMapMode() ) );
+ const Point aBRPix( aVDev->LogicToPixel( Point( GetPrefSize().Width() - 1, GetPrefSize().Height() - 1 ), GetPrefMapMode() ) );
+ Size aDrawSize( aVDev->LogicToPixel( GetPrefSize(), GetPrefMapMode() ) );
+ Size aSizePix( std::abs( aBRPix.X() - aTLPix.X() ) + 1, std::abs( aBRPix.Y() - aTLPix.Y() ) + 1 );
+ sal_uInt32 nMaximumExtent = 512;
+
+ if (!rBitmapEx.IsEmpty())
+ rBitmapEx.SetEmpty();
+
+ // determine size that has the same aspect ratio as image size and
+ // fits into the rectangle determined by nMaximumExtent
+ if ( aSizePix.Width() && aSizePix.Height()
+ && ( sal::static_int_cast< tools::ULong >(aSizePix.Width()) >
+ nMaximumExtent ||
+ sal::static_int_cast< tools::ULong >(aSizePix.Height()) >
+ nMaximumExtent ) )
+ {
+ const Size aOldSizePix( aSizePix );
+ double fWH = static_cast< double >( aSizePix.Width() ) / aSizePix.Height();
+
+ if ( fWH <= 1.0 )
+ {
+ aSizePix.setWidth( FRound( nMaximumExtent * fWH ) );
+ aSizePix.setHeight( nMaximumExtent );
+ }
+ else
+ {
+ aSizePix.setWidth( nMaximumExtent );
+ aSizePix.setHeight( FRound( nMaximumExtent / fWH ) );
+ }
+
+ aDrawSize.setWidth( FRound( ( static_cast< double >( aDrawSize.Width() ) * aSizePix.Width() ) / aOldSizePix.Width() ) );
+ aDrawSize.setHeight( FRound( ( static_cast< double >( aDrawSize.Height() ) * aSizePix.Height() ) / aOldSizePix.Height() ) );
+ }
+
+ // draw image(s) into VDev and get resulting image
+ // do it 4x larger to be able to scale it down & get beautiful antialias
+ Size aAntialiasSize(aSizePix.Width() * 4, aSizePix.Height() * 4);
+ if (aVDev->SetOutputSizePixel(aAntialiasSize))
+ {
+ // antialias: provide 4x larger size, and then scale down the result
+ Size aAntialias(aDrawSize.Width() * 4, aDrawSize.Height() * 4);
+
+ // draw metafile into VDev
+ const_cast<GDIMetaFile *>(this)->WindStart();
+ const_cast<GDIMetaFile *>(this)->Play(*aVDev, Point(), aAntialias);
+
+ // get paint bitmap
+ BitmapEx aBitmap( aVDev->GetBitmapEx( aNullPt, aVDev->GetOutputSizePixel() ) );
+
+ // scale down the image to the desired size - use the input scaler for the scaling operation
+ aBitmap.Scale(aDrawSize, nScaleFlag);
+
+ // convert to desired bitmap color format
+ Size aSize(aBitmap.GetSizePixel());
+ if (aSize.Width() && aSize.Height())
+ aBitmap.Convert(eColorConversion);
+
+ rBitmapEx = aBitmap;
+ }
+
+ return !rBitmapEx.IsEmpty();
+}
+
+void GDIMetaFile::UseCanvas( bool _bUseCanvas )
+{
+ m_bUseCanvas = _bUseCanvas;
+}
+
+void GDIMetaFile::dumpAsXml(const char* pFileName) const
+{
+ SvFileStream aStream(pFileName ? OUString::fromUtf8(pFileName) : OUString("file:///tmp/metafile.xml"),
+ StreamMode::STD_READWRITE | StreamMode::TRUNC);
+ assert(aStream.good());
+ MetafileXmlDump aDumper;
+ aDumper.dump(*this, aStream);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/genVerticalOrientationData.pl b/vcl/source/gdi/genVerticalOrientationData.pl
new file mode 100755
index 000000000..328727b26
--- /dev/null
+++ b/vcl/source/gdi/genVerticalOrientationData.pl
@@ -0,0 +1,206 @@
+#!/usr/bin/env perl
+
+# 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 tool is used to prepare lookup tables of Unicode character properties.
+# The properties are read from the Unicode Character Database and compiled into
+# multi-level arrays for efficient lookup.
+#
+# To regenerate the tables in VerticalOrientationData.cxx:
+#
+# (1) Download the current Unicode data files from
+#
+# We require the latest data file for UTR50, currently revision-17:
+# http://www.unicode.org/Public/vertical/revision-17/VerticalOrientation-17.txt
+#
+#
+# (2) Run this tool using a command line of the form
+#
+# perl genVerticalOrientationData.pl \
+# /path/to/VerticalOrientation-17.txt
+#
+# This will generate (or overwrite!) the files
+#
+# VerticalOrientationData.cxx
+#
+# in the current directory.
+
+use strict;
+use List::Util qw(first);
+
+my $DATA_FILE = $ARGV[0];
+
+my %verticalOrientationCode = (
+ 'U' => 0, # U - Upright, the same orientation as in the code charts
+ 'R' => 1, # R - Rotated 90 degrees clockwise compared to the code charts
+ 'Tu' => 2, # Tu - Transformed typographically, with fallback to Upright
+ 'Tr' => 3 # Tr - Transformed typographically, with fallback to Rotated
+);
+
+my @verticalOrientation;
+for (my $i = 0; $i < 0x110000; ++$i) {
+ $verticalOrientation[$i] = 1; # default for unlisted codepoints is 'R'
+}
+
+# read VerticalOrientation-17.txt
+my @versionInfo;
+open FH, "< $DATA_FILE" or die "can't open UTR50 data file VerticalOrientation-17.txt\n";
+push @versionInfo, "";
+while (<FH>) {
+ chomp;
+ push @versionInfo, $_;
+ last if /Date:/;
+}
+while (<FH>) {
+ chomp;
+ s/#.*//;
+ if (m/([0-9A-F]{4,6})(?:\.\.([0-9A-F]{4,6}))*\s*;\s*([^ ]+)/) {
+ my $vo = $3;
+ warn "unknown Vertical_Orientation code $vo"
+ unless exists $verticalOrientationCode{$vo};
+ $vo = $verticalOrientationCode{$vo};
+ my $start = hex "0x$1";
+ my $end = (defined $2) ? hex "0x$2" : $start;
+ for (my $i = $start; $i <= $end; ++$i) {
+ $verticalOrientation[$i] = $vo;
+ }
+ }
+}
+close FH;
+
+my $timestamp = gmtime();
+
+open DATA_TABLES, "> VerticalOrientationData.cxx" or die "unable to open VerticalOrientationData.cxx for output";
+
+my $licenseBlock = q[
+/*
+ * 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/.
+ */
+
+/*
+ * Derived from the Unicode Character Database by genVerticalOrientationData.pl
+ *
+ * For Unicode terms of use, see http://www.unicode.org/terms_of_use.html
+ */
+];
+
+my $versionInfo = join("\n", @versionInfo);
+
+print DATA_TABLES <<__END;
+$licenseBlock
+/*
+ * Created on $timestamp from UCD data files with version info:
+ *
+
+$versionInfo
+
+ *
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
+
+__END
+
+our $totalData = 0;
+
+sub sprintVerticalOrientation
+{
+ my $usv = shift;
+ return sprintf("%d,",
+ $verticalOrientation[$usv]);
+}
+
+&genTables("VerticalOrientation", "uint8_t", 9, 7, \&sprintVerticalOrientation, 16, 1, 1);
+
+sub genTables
+{
+ my ($prefix, $type, $indexBits, $charBits, $func, $maxPlane, $bytesPerEntry, $charsPerEntry) = @_;
+
+ print DATA_TABLES "#define k${prefix}MaxPlane $maxPlane\n";
+ print DATA_TABLES "#define k${prefix}IndexBits $indexBits\n";
+ print DATA_TABLES "#define k${prefix}CharBits $charBits\n";
+
+ my $indexLen = 1 << $indexBits;
+ my $charsPerPage = 1 << $charBits;
+ my %charIndex = ();
+ my %pageMapIndex = ();
+ my @pageMap = ();
+ my @char = ();
+
+ my $planeMap = "\x00" x $maxPlane;
+ foreach my $plane (0 .. $maxPlane) {
+ my $pageMap = "\x00" x $indexLen * 2;
+ foreach my $page (0 .. $indexLen - 1) {
+ my $charValues = "";
+ for (my $ch = 0; $ch < $charsPerPage; $ch += $charsPerEntry) {
+ my $usv = $plane * 0x10000 + $page * $charsPerPage + $ch;
+ $charValues .= &$func($usv);
+ }
+ chop $charValues;
+
+ unless (exists $charIndex{$charValues}) {
+ $charIndex{$charValues} = scalar keys %charIndex;
+ $char[$charIndex{$charValues}] = $charValues;
+ }
+ substr($pageMap, $page * 2, 2) = pack('S', $charIndex{$charValues});
+ }
+
+ unless (exists $pageMapIndex{$pageMap}) {
+ $pageMapIndex{$pageMap} = scalar keys %pageMapIndex;
+ $pageMap[$pageMapIndex{$pageMap}] = $pageMap;
+ }
+ if ($plane > 0) {
+ substr($planeMap, $plane - 1, 1) = pack('C', $pageMapIndex{$pageMap});
+ }
+ }
+
+ if ($maxPlane) {
+ print DATA_TABLES "static const uint8_t s${prefix}Planes[$maxPlane] = {";
+ print DATA_TABLES join(',', map { sprintf("%d", $_) } unpack('C*', $planeMap));
+ print DATA_TABLES "};\n\n";
+ }
+
+ my $chCount = scalar @char;
+ my $pmBits = $chCount > 255 ? 16 : 8;
+ my $pmCount = scalar @pageMap;
+ if ($maxPlane == 0) {
+ die "there should only be one pageMap entry!" if $pmCount > 1;
+ print DATA_TABLES "static const uint${pmBits}_t s${prefix}Pages[$indexLen] = {\n";
+ } else {
+ print DATA_TABLES "static const uint${pmBits}_t s${prefix}Pages[$pmCount][$indexLen] = {\n";
+ }
+ for (my $i = 0; $i < scalar @pageMap; ++$i) {
+ print DATA_TABLES $maxPlane > 0 ? " {" : " ";
+ print DATA_TABLES join(',', map { sprintf("%d", $_) } unpack('S*', $pageMap[$i]));
+ print DATA_TABLES $maxPlane > 0 ? ($i < $#pageMap ? "},\n" : "}\n") : "\n";
+ }
+ print DATA_TABLES "};\n\n";
+
+ my $pageLen = $charsPerPage / $charsPerEntry;
+ print DATA_TABLES "static const $type s${prefix}Values[$chCount][$pageLen] = {\n";
+ for (my $i = 0; $i < scalar @char; ++$i) {
+ print DATA_TABLES " {";
+ print DATA_TABLES $char[$i];
+ print DATA_TABLES $i < $#char ? "},\n" : "}\n";
+ }
+ print DATA_TABLES "};\n";
+
+ my $dataSize = $pmCount * $indexLen * $pmBits/8 +
+ $chCount * $pageLen * $bytesPerEntry +
+ $maxPlane;
+ $totalData += $dataSize;
+
+ print STDERR "Data for $prefix = $dataSize\n";
+}
+print DATA_TABLES <<__END;
+/*
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
+__END
+
+close DATA_TABLES;
diff --git a/vcl/source/gdi/gfxlink.cxx b/vcl/source/gdi/gfxlink.cxx
new file mode 100644
index 000000000..e959e4651
--- /dev/null
+++ b/vcl/source/gdi/gfxlink.cxx
@@ -0,0 +1,182 @@
+/* -*- 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/log.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/gfxlink.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <memory>
+#include <o3tl/hash_combine.hxx>
+
+GfxLink::GfxLink()
+ : meType(GfxLinkType::NONE)
+ , mnUserId(0)
+ , maHash(0)
+ , mbPrefMapModeValid(false)
+ , mbPrefSizeValid(false)
+{
+}
+
+GfxLink::GfxLink(std::unique_ptr<sal_uInt8[]> pBuf, sal_uInt32 nSize, GfxLinkType nType)
+ : meType(nType)
+ , mnUserId(0)
+ , maDataContainer(pBuf.get(), nSize)
+ , maHash(0)
+ , mbPrefMapModeValid(false)
+ , mbPrefSizeValid(false)
+{
+}
+
+GfxLink::GfxLink(BinaryDataContainer const & rDataConainer, GfxLinkType nType)
+ : meType(nType)
+ , mnUserId(0)
+ , maDataContainer(rDataConainer)
+ , maHash(0)
+ , mbPrefMapModeValid(false)
+ , mbPrefSizeValid(false)
+{
+}
+
+size_t GfxLink::GetHash() const
+{
+ if (!maHash)
+ {
+ std::size_t seed = maDataContainer.calculateHash();
+ o3tl::hash_combine(seed, meType);
+ maHash = seed;
+ }
+ return maHash;
+}
+
+bool GfxLink::operator==( const GfxLink& rGfxLink ) const
+{
+ if (GetDataSize() != rGfxLink.GetDataSize()
+ || meType != rGfxLink.meType
+ || GetHash() != rGfxLink.GetHash())
+ return false;
+
+ const sal_uInt8* pSource = GetData();
+ const sal_uInt8* pDestination = rGfxLink.GetData();
+
+ if (pSource == pDestination)
+ return true;
+
+ sal_uInt32 nSourceSize = GetDataSize();
+ sal_uInt32 nDestSize = rGfxLink.GetDataSize();
+
+ if (pSource && pDestination && (nSourceSize == nDestSize))
+ return memcmp(pSource, pDestination, nSourceSize) == 0;
+
+ return false;
+}
+
+bool GfxLink::IsNative() const
+{
+ return meType >= GfxLinkType::NativeFirst && meType <= GfxLinkType::NativeLast;
+}
+
+const sal_uInt8* GfxLink::GetData() const
+{
+ return maDataContainer.getData();
+}
+
+void GfxLink::SetPrefSize( const Size& rPrefSize )
+{
+ maPrefSize = rPrefSize;
+ mbPrefSizeValid = true;
+}
+
+void GfxLink::SetPrefMapMode( const MapMode& rPrefMapMode )
+{
+ maPrefMapMode = rPrefMapMode;
+ mbPrefMapModeValid = true;
+}
+
+bool GfxLink::LoadNative( Graphic& rGraphic ) const
+{
+ bool bRet = false;
+
+ if (IsNative() && !maDataContainer.isEmpty())
+ {
+ const sal_uInt8* pData = GetData();
+ if (pData)
+ {
+ SvMemoryStream aMemoryStream(const_cast<sal_uInt8*>(pData), GetDataSize(), StreamMode::READ | StreamMode::WRITE);
+ OUString aShortName;
+
+ switch (meType)
+ {
+ case GfxLinkType::NativeGif: aShortName = GIF_SHORTNAME; break;
+ case GfxLinkType::NativeJpg: aShortName = JPG_SHORTNAME; break;
+ case GfxLinkType::NativePng: aShortName = PNG_SHORTNAME; break;
+ case GfxLinkType::NativeTif: aShortName = TIF_SHORTNAME; break;
+ case GfxLinkType::NativeWmf: aShortName = WMF_SHORTNAME; break;
+ case GfxLinkType::NativeMet: aShortName = MET_SHORTNAME; break;
+ case GfxLinkType::NativePct: aShortName = PCT_SHORTNAME; break;
+ case GfxLinkType::NativeSvg: aShortName = SVG_SHORTNAME; break;
+ case GfxLinkType::NativeBmp: aShortName = BMP_SHORTNAME; break;
+ case GfxLinkType::NativePdf: aShortName = PDF_SHORTNAME; break;
+ case GfxLinkType::NativeWebp: aShortName = WEBP_SHORTNAME; break;
+ default: break;
+ }
+ if (!aShortName.isEmpty())
+ {
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ sal_uInt16 nFormat = rFilter.GetImportFormatNumberForShortName(aShortName);
+ ErrCode nResult = rFilter.ImportGraphic(rGraphic, u"", aMemoryStream, nFormat);
+ if (nResult == ERRCODE_NONE)
+ bRet = true;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+bool GfxLink::ExportNative( SvStream& rOStream ) const
+{
+ if( GetDataSize() )
+ {
+ auto pData = GetData();
+ if (pData)
+ rOStream.WriteBytes(pData, GetDataSize());
+ }
+
+ return ( rOStream.GetError() == ERRCODE_NONE );
+}
+
+bool GfxLink::IsEMF() const
+{
+ const sal_uInt8* pGraphicAry = GetData();
+ if ((GetType() == GfxLinkType::NativeWmf) && pGraphicAry && (GetDataSize() > 0x2c))
+ {
+ // check the magic number
+ if ((pGraphicAry[0x28] == 0x20) && (pGraphicAry[0x29] == 0x45)
+ && (pGraphicAry[0x2a] == 0x4d) && (pGraphicAry[0x2b] == 0x46))
+ {
+ //emf detected
+ return true;
+ }
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/gradient.cxx b/vcl/source/gdi/gradient.cxx
new file mode 100644
index 000000000..b4e7db904
--- /dev/null
+++ b/vcl/source/gdi/gradient.cxx
@@ -0,0 +1,681 @@
+/* -*- 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 <tools/gen.hxx>
+
+#include <vcl/gradient.hxx>
+#include <vcl/metaact.hxx>
+#include <cmath>
+
+class Gradient::Impl
+{
+public:
+ GradientStyle meStyle;
+ Color maStartColor;
+ Color maEndColor;
+ Degree10 mnAngle;
+ sal_uInt16 mnBorder;
+ sal_uInt16 mnOfsX;
+ sal_uInt16 mnOfsY;
+ sal_uInt16 mnIntensityStart;
+ sal_uInt16 mnIntensityEnd;
+ sal_uInt16 mnStepCount;
+
+ Impl()
+ : meStyle (GradientStyle::Linear)
+ , maStartColor(COL_BLACK)
+ , maEndColor(COL_WHITE)
+ , mnAngle(0)
+ , mnBorder(0)
+ , mnOfsX(50)
+ , mnOfsY(50)
+ , mnIntensityStart(100)
+ , mnIntensityEnd(100)
+ , mnStepCount(0)
+ {
+ }
+
+ Impl(const Impl& rImplGradient)
+ : meStyle (rImplGradient.meStyle)
+ , maStartColor(rImplGradient.maStartColor)
+ , maEndColor(rImplGradient.maEndColor)
+ , mnAngle(rImplGradient.mnAngle)
+ , mnBorder(rImplGradient.mnBorder)
+ , mnOfsX(rImplGradient.mnOfsX)
+ , mnOfsY(rImplGradient.mnOfsY)
+ , mnIntensityStart(rImplGradient.mnIntensityStart)
+ , mnIntensityEnd(rImplGradient.mnIntensityEnd)
+ , mnStepCount(rImplGradient.mnStepCount)
+ {
+ }
+
+ bool operator==(const Impl& rImpl_Gradient) const
+ {
+ return (meStyle == rImpl_Gradient.meStyle)
+ && (mnAngle == rImpl_Gradient.mnAngle)
+ && (mnBorder == rImpl_Gradient.mnBorder)
+ && (mnOfsX == rImpl_Gradient.mnOfsX)
+ && (mnOfsY == rImpl_Gradient.mnOfsY)
+ && (mnStepCount == rImpl_Gradient.mnStepCount)
+ && (mnIntensityStart == rImpl_Gradient.mnIntensityStart)
+ && (mnIntensityEnd == rImpl_Gradient.mnIntensityEnd)
+ && (maStartColor == rImpl_Gradient.maStartColor)
+ && (maEndColor == rImpl_Gradient.maEndColor);
+ }
+};
+
+Gradient::Gradient() = default;
+
+Gradient::Gradient( const Gradient& ) = default;
+
+Gradient::Gradient( Gradient&& ) = default;
+
+Gradient::Gradient( GradientStyle eStyle,
+ const Color& rStartColor, const Color& rEndColor )
+{
+ mpImplGradient->meStyle = eStyle;
+ mpImplGradient->maStartColor = rStartColor;
+ mpImplGradient->maEndColor = rEndColor;
+}
+
+Gradient::~Gradient() = default;
+
+
+GradientStyle Gradient::GetStyle() const
+{
+ return mpImplGradient->meStyle;
+}
+
+void Gradient::SetStyle( GradientStyle eStyle )
+{
+ mpImplGradient->meStyle = eStyle;
+}
+
+const Color& Gradient::GetStartColor() const
+{
+ return mpImplGradient->maStartColor;
+}
+
+void Gradient::SetStartColor( const Color& rColor )
+{
+ mpImplGradient->maStartColor = rColor;
+}
+
+const Color& Gradient::GetEndColor() const
+{
+ return mpImplGradient->maEndColor;
+}
+
+void Gradient::SetEndColor( const Color& rColor )
+{
+ mpImplGradient->maEndColor = rColor;
+}
+
+Degree10 Gradient::GetAngle() const
+{
+ return mpImplGradient->mnAngle;
+}
+
+void Gradient::SetAngle( Degree10 nAngle )
+{
+ mpImplGradient->mnAngle = nAngle;
+}
+
+sal_uInt16 Gradient::GetBorder() const
+{
+ return mpImplGradient->mnBorder;
+}
+
+void Gradient::SetBorder( sal_uInt16 nBorder )
+{
+ mpImplGradient->mnBorder = nBorder;
+}
+
+sal_uInt16 Gradient::GetOfsX() const
+{
+ return mpImplGradient->mnOfsX;
+}
+
+void Gradient::SetOfsX( sal_uInt16 nOfsX )
+{
+ mpImplGradient->mnOfsX = nOfsX;
+}
+
+sal_uInt16 Gradient::GetOfsY() const
+{
+ return mpImplGradient->mnOfsY;
+}
+
+void Gradient::SetOfsY( sal_uInt16 nOfsY )
+{
+ mpImplGradient->mnOfsY = nOfsY;
+}
+
+sal_uInt16 Gradient::GetStartIntensity() const
+{
+ return mpImplGradient->mnIntensityStart;
+}
+
+void Gradient::SetStartIntensity( sal_uInt16 nIntens )
+{
+ mpImplGradient->mnIntensityStart = nIntens;
+}
+
+sal_uInt16 Gradient::GetEndIntensity() const
+{
+ return mpImplGradient->mnIntensityEnd;
+}
+
+void Gradient::SetEndIntensity( sal_uInt16 nIntens )
+{
+ mpImplGradient->mnIntensityEnd = nIntens;
+}
+
+sal_uInt16 Gradient::GetSteps() const
+{
+ return mpImplGradient->mnStepCount;
+}
+
+void Gradient::SetSteps( sal_uInt16 nSteps )
+{
+ mpImplGradient->mnStepCount = nSteps;
+}
+
+void Gradient::GetBoundRect( const tools::Rectangle& rRect, tools::Rectangle& rBoundRect, Point& rCenter ) const
+{
+ tools::Rectangle aRect( rRect );
+ Degree10 nAngle = GetAngle() % 3600_deg10;
+
+ if( GetStyle() == GradientStyle::Linear || GetStyle() == GradientStyle::Axial )
+ {
+ const double fAngle = toRadians(nAngle);
+ const double fWidth = aRect.GetWidth();
+ const double fHeight = aRect.GetHeight();
+ double fDX = fWidth * fabs( cos( fAngle ) ) +
+ fHeight * fabs( sin( fAngle ) );
+ double fDY = fHeight * fabs( cos( fAngle ) ) +
+ fWidth * fabs( sin( fAngle ) );
+ fDX = (fDX - fWidth) * 0.5 + 0.5;
+ fDY = (fDY - fHeight) * 0.5 + 0.5;
+ aRect.AdjustLeft( -static_cast<tools::Long>(fDX) );
+ aRect.AdjustRight(static_cast<tools::Long>(fDX) );
+ aRect.AdjustTop( -static_cast<tools::Long>(fDY) );
+ aRect.AdjustBottom(static_cast<tools::Long>(fDY) );
+
+ rBoundRect = aRect;
+ rCenter = rRect.Center();
+ }
+ else
+ {
+ if( GetStyle() == GradientStyle::Square || GetStyle() == GradientStyle::Rect )
+ {
+ const double fAngle = toRadians(nAngle);
+ const double fWidth = aRect.GetWidth();
+ const double fHeight = aRect.GetHeight();
+ double fDX = fWidth * fabs( cos( fAngle ) ) + fHeight * fabs( sin( fAngle ) );
+ double fDY = fHeight * fabs( cos( fAngle ) ) + fWidth * fabs( sin( fAngle ) );
+
+ fDX = ( fDX - fWidth ) * 0.5 + 0.5;
+ fDY = ( fDY - fHeight ) * 0.5 + 0.5;
+
+ aRect.AdjustLeft( -static_cast<tools::Long>(fDX) );
+ aRect.AdjustRight(static_cast<tools::Long>(fDX) );
+ aRect.AdjustTop( -static_cast<tools::Long>(fDY) );
+ aRect.AdjustBottom(static_cast<tools::Long>(fDY) );
+ }
+
+ Size aSize( aRect.GetSize() );
+
+ if( GetStyle() == GradientStyle::Radial )
+ {
+ // Calculation of radii for circle
+ aSize.setWidth( static_cast<tools::Long>(0.5 + sqrt(static_cast<double>(aSize.Width())*static_cast<double>(aSize.Width()) + static_cast<double>(aSize.Height())*static_cast<double>(aSize.Height()))) );
+ aSize.setHeight( aSize.Width() );
+ }
+ else if( GetStyle() == GradientStyle::Elliptical )
+ {
+ // Calculation of radii for ellipse
+ aSize.setWidth( static_cast<tools::Long>( 0.5 + static_cast<double>(aSize.Width()) * M_SQRT2 ) );
+ aSize.setHeight( static_cast<tools::Long>( 0.5 + static_cast<double>(aSize.Height()) * M_SQRT2) );
+ }
+
+ // Calculate new centers
+ tools::Long nZWidth = aRect.GetWidth() * static_cast<tools::Long>(GetOfsX()) / 100;
+ tools::Long nZHeight = aRect.GetHeight() * static_cast<tools::Long>(GetOfsY()) / 100;
+ tools::Long nBorderX = static_cast<tools::Long>(GetBorder()) * aSize.Width() / 100;
+ tools::Long nBorderY = static_cast<tools::Long>(GetBorder()) * aSize.Height() / 100;
+ rCenter = Point( aRect.Left() + nZWidth, aRect.Top() + nZHeight );
+
+ // Respect borders
+ aSize.AdjustWidth( -nBorderX );
+ aSize.AdjustHeight( -nBorderY );
+
+ // Recalculate output rectangle
+ aRect.SetLeft( rCenter.X() - ( aSize.Width() >> 1 ) );
+ aRect.SetTop( rCenter.Y() - ( aSize.Height() >> 1 ) );
+
+ aRect.SetSize( aSize );
+ rBoundRect = aRect;
+ }
+}
+
+void Gradient::MakeGrayscale()
+{
+ Color aStartCol(GetStartColor());
+ Color aEndCol(GetEndColor());
+ sal_uInt8 cStartLum = aStartCol.GetLuminance();
+ sal_uInt8 cEndLum = aEndCol.GetLuminance();
+
+ aStartCol = Color(cStartLum, cStartLum, cStartLum);
+ aEndCol = Color(cEndLum, cEndLum, cEndLum);
+
+ SetStartColor(aStartCol);
+ SetEndColor(aEndCol);
+}
+
+Gradient& Gradient::operator=( const Gradient& ) = default;
+
+Gradient& Gradient::operator=( Gradient&& ) = default;
+
+bool Gradient::operator==( const Gradient& rGradient ) const
+{
+ return mpImplGradient == rGradient.mpImplGradient;
+}
+
+const sal_uInt32 GRADIENT_DEFAULT_STEPCOUNT = 0;
+
+void Gradient::AddGradientActions(tools::Rectangle const& rRect, GDIMetaFile& rMetaFile)
+{
+ tools::Rectangle aRect(rRect);
+ aRect.Justify();
+
+ // do nothing if the rectangle is empty
+ if (aRect.IsEmpty())
+ return;
+
+ rMetaFile.AddAction(new MetaPushAction(vcl::PushFlags::ALL));
+ rMetaFile.AddAction(new MetaISectRectClipRegionAction( aRect));
+ rMetaFile.AddAction(new MetaLineColorAction(Color(), false));
+
+ // because we draw with no border line, we have to expand gradient
+ // rect to avoid missing lines on the right and bottom edge
+ aRect.AdjustLeft( -1 );
+ aRect.AdjustTop( -1 );
+ aRect.AdjustRight( 1 );
+ aRect.AdjustBottom( 1 );
+
+ // calculate step count if necessary
+ if (!GetSteps())
+ SetSteps(GRADIENT_DEFAULT_STEPCOUNT);
+
+ if (GetStyle() == GradientStyle::Linear || GetStyle() == GradientStyle::Axial)
+ DrawLinearGradientToMetafile(aRect, rMetaFile);
+ else
+ DrawComplexGradientToMetafile(aRect, rMetaFile);
+
+ rMetaFile.AddAction(new MetaPopAction());
+}
+
+tools::Long Gradient::GetMetafileSteps(tools::Rectangle const& rRect) const
+{
+ // calculate step count
+ tools::Long nStepCount = GetSteps();
+
+ if (nStepCount)
+ return nStepCount;
+
+ if (GetStyle() == GradientStyle::Linear || GetStyle() == GradientStyle::Axial)
+ return rRect.GetHeight();
+ else
+ return std::min(rRect.GetWidth(), rRect.GetHeight());
+}
+
+
+static sal_uInt8 GetGradientColorValue(tools::Long nValue)
+{
+ if ( nValue < 0 )
+ return 0;
+ else if ( nValue > 0xFF )
+ return 0xFF;
+ else
+ return static_cast<sal_uInt8>(nValue);
+}
+
+void Gradient::DrawLinearGradientToMetafile(tools::Rectangle const& rRect, GDIMetaFile& rMetaFile) const
+{
+ // get BoundRect of rotated rectangle
+ tools::Rectangle aRect;
+ Point aCenter;
+ Degree10 nAngle = GetAngle() % 3600_deg10;
+
+ GetBoundRect(rRect, aRect, aCenter);
+
+ bool bLinear = (GetStyle() == GradientStyle::Linear);
+ double fBorder = GetBorder() * aRect.GetHeight() / 100.0;
+ if ( !bLinear )
+ {
+ fBorder /= 2.0;
+ }
+ tools::Rectangle aMirrorRect = aRect; // used in style axial
+ aMirrorRect.SetTop( ( aRect.Top() + aRect.Bottom() ) / 2 );
+ if ( !bLinear )
+ {
+ aRect.SetBottom( aMirrorRect.Top() );
+ }
+
+ // colour-intensities of start- and finish; change if needed
+ tools::Long nFactor;
+ Color aStartCol = GetStartColor();
+ Color aEndCol = GetEndColor();
+ tools::Long nStartRed = aStartCol.GetRed();
+ tools::Long nStartGreen = aStartCol.GetGreen();
+ tools::Long nStartBlue = aStartCol.GetBlue();
+ tools::Long nEndRed = aEndCol.GetRed();
+ tools::Long nEndGreen = aEndCol.GetGreen();
+ tools::Long nEndBlue = aEndCol.GetBlue();
+ nFactor = GetStartIntensity();
+ nStartRed = (nStartRed * nFactor) / 100;
+ nStartGreen = (nStartGreen * nFactor) / 100;
+ nStartBlue = (nStartBlue * nFactor) / 100;
+ nFactor = GetEndIntensity();
+ nEndRed = (nEndRed * nFactor) / 100;
+ nEndGreen = (nEndGreen * nFactor) / 100;
+ nEndBlue = (nEndBlue * nFactor) / 100;
+
+ // gradient style axial has exchanged start and end colors
+ if ( !bLinear)
+ {
+ tools::Long nTempColor = nStartRed;
+ nStartRed = nEndRed;
+ nEndRed = nTempColor;
+ nTempColor = nStartGreen;
+ nStartGreen = nEndGreen;
+ nEndGreen = nTempColor;
+ nTempColor = nStartBlue;
+ nStartBlue = nEndBlue;
+ nEndBlue = nTempColor;
+ }
+
+ sal_uInt8 nRed;
+ sal_uInt8 nGreen;
+ sal_uInt8 nBlue;
+
+ // Create border
+ tools::Rectangle aBorderRect = aRect;
+ tools::Polygon aPoly( 4 );
+ if (fBorder > 0.0)
+ {
+ nRed = static_cast<sal_uInt8>(nStartRed);
+ nGreen = static_cast<sal_uInt8>(nStartGreen);
+ nBlue = static_cast<sal_uInt8>(nStartBlue);
+
+ rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ aBorderRect.SetBottom( static_cast<tools::Long>( aBorderRect.Top() + fBorder ) );
+ aRect.SetTop( aBorderRect.Bottom() );
+ aPoly[0] = aBorderRect.TopLeft();
+ aPoly[1] = aBorderRect.TopRight();
+ aPoly[2] = aBorderRect.BottomRight();
+ aPoly[3] = aBorderRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ rMetaFile.AddAction( new MetaPolygonAction( aPoly ) );
+
+ if ( !bLinear)
+ {
+ aBorderRect = aMirrorRect;
+ aBorderRect.SetTop( static_cast<tools::Long>( aBorderRect.Bottom() - fBorder ) );
+ aMirrorRect.SetBottom( aBorderRect.Top() );
+ aPoly[0] = aBorderRect.TopLeft();
+ aPoly[1] = aBorderRect.TopRight();
+ aPoly[2] = aBorderRect.BottomRight();
+ aPoly[3] = aBorderRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ rMetaFile.AddAction( new MetaPolygonAction( aPoly ) );
+ }
+ }
+
+ tools::Long nStepCount = GetMetafileSteps(aRect);
+
+ // minimal three steps and maximal as max color steps
+ tools::Long nAbsRedSteps = std::abs( nEndRed - nStartRed );
+ tools::Long nAbsGreenSteps = std::abs( nEndGreen - nStartGreen );
+ tools::Long nAbsBlueSteps = std::abs( nEndBlue - nStartBlue );
+ tools::Long nMaxColorSteps = std::max( nAbsRedSteps , nAbsGreenSteps );
+ nMaxColorSteps = std::max( nMaxColorSteps, nAbsBlueSteps );
+ tools::Long nSteps = std::min( nStepCount, nMaxColorSteps );
+ if ( nSteps < 3)
+ {
+ nSteps = 3;
+ }
+
+ double fScanInc = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps);
+ double fGradientLine = static_cast<double>(aRect.Top());
+ double fMirrorGradientLine = static_cast<double>(aMirrorRect.Bottom());
+
+ const double fStepsMinus1 = static_cast<double>(nSteps) - 1.0;
+ if ( !bLinear)
+ {
+ nSteps -= 1; // draw middle polygons as one polygon after loop to avoid gap
+ }
+ for ( tools::Long i = 0; i < nSteps; i++ )
+ {
+ // linear interpolation of color
+ double fAlpha = static_cast<double>(i) / fStepsMinus1;
+ double fTempColor = static_cast<double>(nStartRed) * (1.0-fAlpha) + static_cast<double>(nEndRed) * fAlpha;
+ nRed = GetGradientColorValue(static_cast<tools::Long>(fTempColor));
+ fTempColor = static_cast<double>(nStartGreen) * (1.0-fAlpha) + static_cast<double>(nEndGreen) * fAlpha;
+ nGreen = GetGradientColorValue(static_cast<tools::Long>(fTempColor));
+ fTempColor = static_cast<double>(nStartBlue) * (1.0-fAlpha) + static_cast<double>(nEndBlue) * fAlpha;
+ nBlue = GetGradientColorValue(static_cast<tools::Long>(fTempColor));
+
+ rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ // Polygon for this color step
+ aRect.SetTop( static_cast<tools::Long>( fGradientLine + static_cast<double>(i) * fScanInc ) );
+ aRect.SetBottom( static_cast<tools::Long>( fGradientLine + ( static_cast<double>(i) + 1.0 ) * fScanInc ) );
+ aPoly[0] = aRect.TopLeft();
+ aPoly[1] = aRect.TopRight();
+ aPoly[2] = aRect.BottomRight();
+ aPoly[3] = aRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ rMetaFile.AddAction( new MetaPolygonAction( aPoly ) );
+
+ if ( !bLinear )
+ {
+ aMirrorRect.SetBottom( static_cast<tools::Long>( fMirrorGradientLine - static_cast<double>(i) * fScanInc ) );
+ aMirrorRect.SetTop( static_cast<tools::Long>( fMirrorGradientLine - (static_cast<double>(i) + 1.0)* fScanInc ) );
+ aPoly[0] = aMirrorRect.TopLeft();
+ aPoly[1] = aMirrorRect.TopRight();
+ aPoly[2] = aMirrorRect.BottomRight();
+ aPoly[3] = aMirrorRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ rMetaFile.AddAction( new MetaPolygonAction( aPoly ) );
+ }
+ }
+ if ( bLinear)
+ return;
+
+ // draw middle polygon with end color
+ nRed = GetGradientColorValue(nEndRed);
+ nGreen = GetGradientColorValue(nEndGreen);
+ nBlue = GetGradientColorValue(nEndBlue);
+
+ rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ aRect.SetTop( static_cast<tools::Long>( fGradientLine + static_cast<double>(nSteps) * fScanInc ) );
+ aRect.SetBottom( static_cast<tools::Long>( fMirrorGradientLine - static_cast<double>(nSteps) * fScanInc ) );
+ aPoly[0] = aRect.TopLeft();
+ aPoly[1] = aRect.TopRight();
+ aPoly[2] = aRect.BottomRight();
+ aPoly[3] = aRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ rMetaFile.AddAction( new MetaPolygonAction( aPoly ) );
+
+}
+
+void Gradient::DrawComplexGradientToMetafile(tools::Rectangle const& rRect, GDIMetaFile& rMetaFile) const
+{
+ // Determine if we output via Polygon or PolyPolygon
+ // For all rasteroperations other than Overpaint always use PolyPolygon,
+ // as we will get wrong results if we output multiple times on top of each other.
+ // Also for printers always use PolyPolygon, as not all printers
+ // can print polygons on top of each other.
+
+ tools::Rectangle aRect;
+ Point aCenter;
+ GetBoundRect(rRect, aRect, aCenter);
+
+ std::optional<tools::PolyPolygon> xPolyPoly;
+ xPolyPoly = tools::PolyPolygon( 2 );
+
+ // last parameter - true if complex gradient, false if linear
+ tools::Long nStepCount = GetMetafileSteps(rRect);
+
+ // at least three steps and at most the number of colour differences
+ tools::Long nSteps = std::max(nStepCount, tools::Long(2));
+
+ Color aStartCol(GetStartColor());
+ Color aEndCol(GetEndColor());
+
+ tools::Long nStartRed = (static_cast<tools::Long>(aStartCol.GetRed()) * GetStartIntensity()) / 100;
+ tools::Long nStartGreen = (static_cast<tools::Long>(aStartCol.GetGreen()) * GetStartIntensity()) / 100;
+ tools::Long nStartBlue = (static_cast<tools::Long>(aStartCol.GetBlue()) * GetStartIntensity()) / 100;
+
+ tools::Long nEndRed = (static_cast<tools::Long>(aEndCol.GetRed()) * GetEndIntensity()) / 100;
+ tools::Long nEndGreen = (static_cast<tools::Long>(aEndCol.GetGreen()) * GetEndIntensity()) / 100;
+ tools::Long nEndBlue = (static_cast<tools::Long>(aEndCol.GetBlue()) * GetEndIntensity()) / 100;
+
+ tools::Long nRedSteps = nEndRed - nStartRed;
+ tools::Long nGreenSteps = nEndGreen - nStartGreen;
+ tools::Long nBlueSteps = nEndBlue - nStartBlue;
+
+ tools::Long nCalcSteps = std::abs(nRedSteps);
+ tools::Long nTempSteps = std::abs(nGreenSteps);
+
+ if (nTempSteps > nCalcSteps)
+ nCalcSteps = nTempSteps;
+
+ nTempSteps = std::abs( nBlueSteps );
+
+ if (nTempSteps > nCalcSteps)
+ nCalcSteps = nTempSteps;
+
+ if (nCalcSteps < nSteps)
+ nSteps = nCalcSteps;
+
+ if ( !nSteps )
+ nSteps = 1;
+
+ // determine output limits and stepsizes for all directions
+ tools::Polygon aPoly;
+ double fScanLeft = aRect.Left();
+ double fScanTop = aRect.Top();
+ double fScanRight = aRect.Right();
+ double fScanBottom = aRect.Bottom();
+ double fScanIncX = static_cast<double>(aRect.GetWidth()) / static_cast<double>(nSteps) * 0.5;
+ double fScanIncY = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps) * 0.5;
+
+ // all gradients are rendered as nested rectangles which shrink
+ // equally in each dimension - except for 'square' gradients
+ // which shrink to a central vertex but are not per-se square.
+ if (GetStyle() != GradientStyle::Square)
+ {
+ fScanIncY = std::min( fScanIncY, fScanIncX );
+ fScanIncX = fScanIncY;
+ }
+ sal_uInt8 nRed = static_cast<sal_uInt8>(nStartRed), nGreen = static_cast<sal_uInt8>(nStartGreen), nBlue = static_cast<sal_uInt8>(nStartBlue);
+ bool bPaintLastPolygon( false ); // #107349# Paint last polygon only if loop has generated any output
+
+ rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ aPoly = rRect;
+ xPolyPoly->Insert( aPoly );
+ xPolyPoly->Insert( aPoly );
+
+ // loop to output Polygon/PolyPolygon sequentially
+ for( tools::Long i = 1; i < nSteps; i++ )
+ {
+ // calculate new Polygon
+ fScanLeft += fScanIncX;
+ aRect.SetLeft( static_cast<tools::Long>( fScanLeft ) );
+ fScanTop += fScanIncY;
+ aRect.SetTop( static_cast<tools::Long>( fScanTop ) );
+ fScanRight -= fScanIncX;
+ aRect.SetRight( static_cast<tools::Long>( fScanRight ) );
+ fScanBottom -= fScanIncY;
+ aRect.SetBottom( static_cast<tools::Long>( fScanBottom ) );
+
+ if( ( aRect.GetWidth() < 2 ) || ( aRect.GetHeight() < 2 ) )
+ break;
+
+ if (GetStyle() == GradientStyle::Radial || GetStyle() == GradientStyle::Elliptical)
+ aPoly = tools::Polygon( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 );
+ else
+ aPoly = tools::Polygon( aRect );
+
+ aPoly.Rotate(aCenter, GetAngle() % 3600_deg10);
+
+ // adapt colour accordingly
+ const tools::Long nStepIndex = ( xPolyPoly ? i : ( i + 1 ) );
+ nRed = GetGradientColorValue( nStartRed + ( ( nRedSteps * nStepIndex ) / nSteps ) );
+ nGreen = GetGradientColorValue( nStartGreen + ( ( nGreenSteps * nStepIndex ) / nSteps ) );
+ nBlue = GetGradientColorValue( nStartBlue + ( ( nBlueSteps * nStepIndex ) / nSteps ) );
+
+ bPaintLastPolygon = true; // #107349# Paint last polygon only if loop has generated any output
+
+ xPolyPoly->Replace( xPolyPoly->GetObject( 1 ), 0 );
+ xPolyPoly->Replace( aPoly, 1 );
+
+ rMetaFile.AddAction( new MetaPolyPolygonAction( *xPolyPoly ) );
+
+ // #107349# Set fill color _after_ geometry painting:
+ // xPolyPoly's geometry is the band from last iteration's
+ // aPoly to current iteration's aPoly. The window outdev
+ // path (see else below), on the other hand, paints the
+ // full aPoly. Thus, here, we're painting the band before
+ // the one painted in the window outdev path below. To get
+ // matching colors, have to delay color setting here.
+ rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+ }
+
+ const tools::Polygon& rPoly = xPolyPoly->GetObject( 1 );
+
+ if( rPoly.GetBoundRect().IsEmpty() )
+ return;
+
+ // #107349# Paint last polygon with end color only if loop
+ // has generated output. Otherwise, the current
+ // (i.e. start) color is taken, to generate _any_ output.
+ if( bPaintLastPolygon )
+ {
+ nRed = GetGradientColorValue( nEndRed );
+ nGreen = GetGradientColorValue( nEndGreen );
+ nBlue = GetGradientColorValue( nEndBlue );
+ }
+
+ rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+ rMetaFile.AddAction( new MetaPolygonAction( rPoly ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/graph.cxx b/vcl/source/gdi/graph.cxx
new file mode 100644
index 000000000..c56ddb720
--- /dev/null
+++ b/vcl/source/gdi/graph.cxx
@@ -0,0 +1,564 @@
+/* -*- 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 <tools/fract.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/image.hxx>
+#include <impgraph.hxx>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <comphelper/servicehelper.hxx>
+#include <graphic/UnoGraphic.hxx>
+#include <vcl/GraphicExternalLink.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+
+void ImplDrawDefault(OutputDevice& rOutDev, const OUString* pText,
+ vcl::Font* pFont, const BitmapEx* pBitmapEx,
+ const Point& rDestPt, const Size& rDestSize)
+{
+ sal_uInt16 nPixel = static_cast<sal_uInt16>(rOutDev.PixelToLogic( Size( 1, 1 ) ).Width());
+ sal_uInt16 nPixelWidth = nPixel;
+ Point aPoint( rDestPt.X() + nPixelWidth, rDestPt.Y() + nPixelWidth );
+ Size aSize( rDestSize.Width() - ( nPixelWidth << 1 ), rDestSize.Height() - ( nPixelWidth << 1 ) );
+ bool bFilled = ( pBitmapEx != nullptr || pFont != nullptr );
+ tools::Rectangle aBorderRect( aPoint, aSize );
+
+ rOutDev.Push();
+
+ rOutDev.SetFillColor();
+
+ // On the printer a black rectangle and on the screen one with 3D effect
+ rOutDev.DrawBorder(aBorderRect);
+
+ aPoint.AdjustX(nPixelWidth + 2*nPixel );
+ aPoint.AdjustY(nPixelWidth + 2*nPixel );
+ aSize.AdjustWidth( -(2*nPixelWidth + 4*nPixel) );
+ aSize.AdjustHeight( -(2*nPixelWidth + 4*nPixel) );
+
+ if( !aSize.IsEmpty() && pBitmapEx && !pBitmapEx->IsEmpty() )
+ {
+ Size aBitmapSize( rOutDev.PixelToLogic( pBitmapEx->GetSizePixel() ) );
+
+ if( aSize.Height() > aBitmapSize.Height() && aSize.Width() > aBitmapSize.Width() )
+ {
+ rOutDev.DrawBitmapEx( aPoint, *pBitmapEx );
+ aPoint.AdjustX(aBitmapSize.Width() + 2*nPixel );
+ aSize.AdjustWidth( -(aBitmapSize.Width() + 2*nPixel) );
+ }
+ }
+
+ if ( !aSize.IsEmpty() && pFont && pText && pText->getLength() && rOutDev.IsOutputEnabled() )
+ {
+ MapMode aMapMode( MapUnit::MapPoint );
+ Size aSz = rOutDev.LogicToLogic( Size( 0, 12 ), &aMapMode, nullptr );
+ tools::Long nThreshold = aSz.Height() / 2;
+ tools::Long nStep = nThreshold / 3;
+
+ if ( !nStep )
+ nStep = aSz.Height() - nThreshold;
+
+ for(;; aSz.AdjustHeight( -nStep ) )
+ {
+ pFont->SetFontSize( aSz );
+ rOutDev.SetFont( *pFont );
+
+ tools::Long nTextHeight = rOutDev.GetTextHeight();
+ tools::Long nTextWidth = rOutDev.GetTextWidth( *pText );
+ if ( nTextHeight )
+ {
+ // The approximation does not respect imprecisions caused
+ // by word wraps
+ tools::Long nLines = aSize.Height() / nTextHeight;
+ tools::Long nWidth = aSize.Width() * nLines; // Approximation!!!
+
+ if ( nTextWidth <= nWidth || aSz.Height() <= nThreshold )
+ {
+ sal_Int32 nStart = 0;
+ sal_Int32 nLen = 0;
+
+ while( nStart < pText->getLength() && (*pText)[nStart] == ' ' )
+ nStart++;
+ while( nStart+nLen < pText->getLength() && (*pText)[nStart+nLen] != ' ' )
+ nLen++;
+ while( nStart < pText->getLength() && nLines-- )
+ {
+ sal_Int32 nNext = nLen;
+ do
+ {
+ while ( nStart+nNext < pText->getLength() && (*pText)[nStart+nNext] == ' ' )
+ nNext++;
+ while ( nStart+nNext < pText->getLength() && (*pText)[nStart+nNext] != ' ' )
+ nNext++;
+ nTextWidth = rOutDev.GetTextWidth( *pText, nStart, nNext );
+ if ( nTextWidth > aSize.Width() )
+ break;
+ nLen = nNext;
+ }
+ while ( nStart+nNext < pText->getLength() );
+
+ sal_Int32 n = nLen;
+ nTextWidth = rOutDev.GetTextWidth( *pText, nStart, n );
+ while( nTextWidth > aSize.Width() )
+ nTextWidth = rOutDev.GetTextWidth( *pText, nStart, --n );
+ rOutDev.DrawText( aPoint, *pText, nStart, n );
+
+ aPoint.AdjustY(nTextHeight );
+ nStart = sal::static_int_cast<sal_uInt16>(nStart + nLen);
+ nLen = nNext-nLen;
+ while( nStart < pText->getLength() && (*pText)[nStart] == ' ' )
+ {
+ nStart++;
+ nLen--;
+ }
+ }
+ break;
+ }
+ }
+ else
+ break;
+ }
+ }
+
+ // If the default graphic does not have content, we draw a red rectangle
+ if( !bFilled )
+ {
+ aBorderRect.AdjustLeft( 1 );
+ aBorderRect.AdjustTop( 1 );
+ aBorderRect.AdjustRight( -1 );
+ aBorderRect.AdjustBottom( -1 );
+
+ rOutDev.SetLineColor( COL_LIGHTRED );
+ rOutDev.DrawLine( aBorderRect.TopLeft(), aBorderRect.BottomRight() );
+ rOutDev.DrawLine( aBorderRect.TopRight(), aBorderRect.BottomLeft() );
+ }
+
+ rOutDev.Pop();
+}
+
+} // end anonymous namespace
+
+Graphic::Graphic()
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance())
+{
+}
+
+Graphic::Graphic(const Graphic& rGraphic)
+{
+ if( rGraphic.IsAnimated() )
+ mxImpGraphic = vcl::graphic::Manager::get().copy(rGraphic.mxImpGraphic);
+ else
+ mxImpGraphic = rGraphic.mxImpGraphic;
+}
+
+Graphic::Graphic(Graphic&& rGraphic) noexcept
+ : mxImpGraphic(std::move(rGraphic.mxImpGraphic))
+{
+}
+
+Graphic::Graphic(std::shared_ptr<GfxLink> const & rGfxLink, sal_Int32 nPageIndex)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rGfxLink, nPageIndex))
+{
+}
+
+Graphic::Graphic(GraphicExternalLink const & rGraphicExternalLink)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rGraphicExternalLink))
+{
+}
+
+Graphic::Graphic(const BitmapEx& rBmpEx)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rBmpEx))
+{
+}
+
+// We use XGraphic for passing toolbar images across app UNO aps
+// and we need to be able to see and preserve 'stock' images too.
+Graphic::Graphic(const Image& rImage)
+ // FIXME: should really defer the BitmapEx load.
+ : mxImpGraphic(std::make_shared<ImpGraphic>(rImage.GetBitmapEx()))
+{
+ OUString aStock = rImage.GetStock();
+ if (aStock.getLength())
+ mxImpGraphic->setOriginURL("private:graphicrepository/" + aStock);
+}
+
+Graphic::Graphic(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rVectorGraphicDataPtr))
+{
+}
+
+Graphic::Graphic(const Animation& rAnimation)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rAnimation))
+{
+}
+
+Graphic::Graphic(const GDIMetaFile& rMtf)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rMtf))
+{
+}
+
+Graphic::Graphic( const css::uno::Reference< css::graphic::XGraphic >& rxGraphic )
+{
+ const ::Graphic* pGraphic = comphelper::getFromUnoTunnel<::Graphic>(rxGraphic);
+
+ if( pGraphic )
+ {
+ if (pGraphic->IsAnimated())
+ mxImpGraphic = vcl::graphic::Manager::get().copy(pGraphic->mxImpGraphic);
+ else
+ mxImpGraphic = pGraphic->mxImpGraphic;
+ }
+ else
+ mxImpGraphic = vcl::graphic::Manager::get().newInstance();
+}
+
+void Graphic::ImplTestRefCount()
+{
+ if (mxImpGraphic.use_count() > 1)
+ {
+ mxImpGraphic = vcl::graphic::Manager::get().copy(mxImpGraphic);
+ }
+}
+
+bool Graphic::isAvailable() const
+{
+ return mxImpGraphic->isAvailable();
+}
+
+bool Graphic::makeAvailable()
+{
+ return mxImpGraphic->makeAvailable();
+}
+
+Graphic& Graphic::operator=( const Graphic& rGraphic )
+{
+ if( &rGraphic != this )
+ {
+ if( rGraphic.IsAnimated() )
+ mxImpGraphic = vcl::graphic::Manager::get().copy(rGraphic.mxImpGraphic);
+ else
+ mxImpGraphic = rGraphic.mxImpGraphic;
+ }
+
+ return *this;
+}
+
+Graphic& Graphic::operator=(Graphic&& rGraphic) noexcept
+{
+ mxImpGraphic = std::move(rGraphic.mxImpGraphic);
+ return *this;
+}
+
+bool Graphic::operator==( const Graphic& rGraphic ) const
+{
+ return (*mxImpGraphic == *rGraphic.mxImpGraphic);
+}
+
+bool Graphic::operator!=( const Graphic& rGraphic ) const
+{
+ return (*mxImpGraphic != *rGraphic.mxImpGraphic);
+}
+
+bool Graphic::IsNone() const
+{
+ return GraphicType::NONE == mxImpGraphic->getType();
+}
+
+void Graphic::Clear()
+{
+ ImplTestRefCount();
+ mxImpGraphic->clear();
+}
+
+GraphicType Graphic::GetType() const
+{
+ return mxImpGraphic->getType();
+}
+
+void Graphic::SetDefaultType()
+{
+ ImplTestRefCount();
+ mxImpGraphic->setDefaultType();
+}
+
+bool Graphic::IsSupportedGraphic() const
+{
+ return mxImpGraphic->isSupportedGraphic();
+}
+
+bool Graphic::IsTransparent() const
+{
+ return mxImpGraphic->isTransparent();
+}
+
+bool Graphic::IsAlpha() const
+{
+ return mxImpGraphic->isAlpha();
+}
+
+bool Graphic::IsAnimated() const
+{
+ return mxImpGraphic->isAnimated();
+}
+
+bool Graphic::IsEPS() const
+{
+ return mxImpGraphic->isEPS();
+}
+
+BitmapEx Graphic::GetBitmapEx(const GraphicConversionParameters& rParameters) const
+{
+ return mxImpGraphic->getBitmapEx(rParameters);
+}
+
+Animation Graphic::GetAnimation() const
+{
+ return mxImpGraphic->getAnimation();
+}
+
+const GDIMetaFile& Graphic::GetGDIMetaFile() const
+{
+ return mxImpGraphic->getGDIMetaFile();
+}
+
+const BitmapEx& Graphic::GetBitmapExRef() const
+{
+ return mxImpGraphic->getBitmapExRef();
+}
+
+uno::Reference<css::graphic::XGraphic> Graphic::GetXGraphic() const
+{
+ uno::Reference<css::graphic::XGraphic> xGraphic;
+
+ if (GetType() != GraphicType::NONE)
+ {
+ rtl::Reference<unographic::Graphic> pUnoGraphic = new unographic::Graphic;
+ pUnoGraphic->init(*this);
+ xGraphic = pUnoGraphic;
+ }
+
+ return xGraphic;
+}
+
+Size Graphic::GetPrefSize() const
+{
+ return mxImpGraphic->getPrefSize();
+}
+
+void Graphic::SetPrefSize( const Size& rPrefSize )
+{
+ ImplTestRefCount();
+ mxImpGraphic->setPrefSize( rPrefSize );
+}
+
+MapMode Graphic::GetPrefMapMode() const
+{
+ return mxImpGraphic->getPrefMapMode();
+}
+
+void Graphic::SetPrefMapMode( const MapMode& rPrefMapMode )
+{
+ ImplTestRefCount();
+ mxImpGraphic->setPrefMapMode( rPrefMapMode );
+}
+
+basegfx::B2DSize Graphic::GetPPI() const
+{
+ double nGrfDPIx;
+ double nGrfDPIy;
+
+ const MapMode aGrfMap(GetPrefMapMode());
+ const Size aGrfPixelSize(GetSizePixel());
+ const Size aGrfPrefMapModeSize(GetPrefSize());
+ if (aGrfMap.GetMapUnit() == MapUnit::MapInch)
+ {
+ nGrfDPIx = aGrfPixelSize.Width() / ( static_cast<double>(aGrfMap.GetScaleX()) * aGrfPrefMapModeSize.Width() );
+ nGrfDPIy = aGrfPixelSize.Height() / ( static_cast<double>(aGrfMap.GetScaleY()) * aGrfPrefMapModeSize.Height() );
+ }
+ else
+ {
+ const Size aGrf1000thInchSize = OutputDevice::LogicToLogic(
+ aGrfPrefMapModeSize, aGrfMap, MapMode(MapUnit::Map1000thInch));
+ nGrfDPIx = aGrf1000thInchSize.Width() == 0
+ ? 0.0 : 1000.0 * aGrfPixelSize.Width() / aGrf1000thInchSize.Width();
+ nGrfDPIy = aGrf1000thInchSize.Height() == 0
+ ? 0.0 : 1000.0 * aGrfPixelSize.Height() / aGrf1000thInchSize.Height();
+ }
+
+ return basegfx::B2DSize(nGrfDPIx, nGrfDPIy);
+}
+
+Size Graphic::GetSizePixel( const OutputDevice* pRefDevice ) const
+{
+ Size aRet;
+
+ if( GraphicType::Bitmap == mxImpGraphic->getType() )
+ aRet = mxImpGraphic->getSizePixel();
+ else
+ aRet = ( pRefDevice ? pRefDevice : Application::GetDefaultDevice() )->LogicToPixel( GetPrefSize(), GetPrefMapMode() );
+
+ return aRet;
+}
+
+sal_uLong Graphic::GetSizeBytes() const
+{
+ return mxImpGraphic->getSizeBytes();
+}
+
+void Graphic::Draw(OutputDevice& rOutDev, const Point& rDestPt) const
+{
+ mxImpGraphic->draw(rOutDev, rDestPt);
+}
+
+void Graphic::Draw(OutputDevice& rOutDev, const Point& rDestPt,
+ const Size& rDestSz) const
+{
+ if( GraphicType::Default == mxImpGraphic->getType() )
+ ImplDrawDefault(rOutDev, nullptr, nullptr, nullptr, rDestPt, rDestSz);
+ else
+ mxImpGraphic->draw(rOutDev, rDestPt, rDestSz);
+}
+
+void Graphic::DrawEx(OutputDevice& rOutDev, const OUString& rText,
+ vcl::Font& rFont, const BitmapEx& rBitmap,
+ const Point& rDestPt, const Size& rDestSz)
+{
+ ImplDrawDefault(rOutDev, &rText, &rFont, &rBitmap, rDestPt, rDestSz);
+}
+
+void Graphic::StartAnimation(OutputDevice& rOutDev, const Point& rDestPt,
+ const Size& rDestSz, tools::Long nExtraData,
+ OutputDevice* pFirstFrameOutDev)
+{
+ ImplTestRefCount();
+ mxImpGraphic->startAnimation(rOutDev, rDestPt, rDestSz, nExtraData, pFirstFrameOutDev);
+}
+
+void Graphic::StopAnimation( const OutputDevice* pOutDev, tools::Long nExtraData )
+{
+ ImplTestRefCount();
+ mxImpGraphic->stopAnimation( pOutDev, nExtraData );
+}
+
+void Graphic::SetAnimationNotifyHdl( const Link<Animation*,void>& rLink )
+{
+ mxImpGraphic->setAnimationNotifyHdl( rLink );
+}
+
+Link<Animation*,void> Graphic::GetAnimationNotifyHdl() const
+{
+ return mxImpGraphic->getAnimationNotifyHdl();
+}
+
+sal_uInt32 Graphic::GetAnimationLoopCount() const
+{
+ return mxImpGraphic->getAnimationLoopCount();
+}
+
+std::shared_ptr<GraphicReader>& Graphic::GetReaderContext()
+{
+ return mxImpGraphic->getContext();
+}
+
+void Graphic::SetReaderContext( const std::shared_ptr<GraphicReader> &pReader )
+{
+ mxImpGraphic->setContext( pReader );
+}
+
+void Graphic::SetDummyContext( bool value )
+{
+ mxImpGraphic->setDummyContext( value );
+}
+
+bool Graphic::IsDummyContext() const
+{
+ return mxImpGraphic->isDummyContext();
+}
+
+void Graphic::SetGfxLink( const std::shared_ptr<GfxLink>& rGfxLink )
+{
+ ImplTestRefCount();
+ mxImpGraphic->setGfxLink(rGfxLink);
+}
+
+const std::shared_ptr<GfxLink> & Graphic::GetSharedGfxLink() const
+{
+ return mxImpGraphic->getSharedGfxLink();
+}
+
+GfxLink Graphic::GetGfxLink() const
+{
+ return mxImpGraphic->getGfxLink();
+}
+
+bool Graphic::IsGfxLink() const
+{
+ return mxImpGraphic->isGfxLink();
+}
+
+BitmapChecksum Graphic::GetChecksum() const
+{
+ return mxImpGraphic->getChecksum();
+}
+
+const std::shared_ptr<VectorGraphicData>& Graphic::getVectorGraphicData() const
+{
+ return mxImpGraphic->getVectorGraphicData();
+}
+
+sal_Int32 Graphic::getPageNumber() const
+{
+ return mxImpGraphic->getPageNumber();
+}
+
+OUString Graphic::getOriginURL() const
+{
+ if (mxImpGraphic)
+ {
+ return mxImpGraphic->getOriginURL();
+ }
+ return OUString();
+}
+
+void Graphic::setOriginURL(OUString const & rOriginURL)
+{
+ if (mxImpGraphic)
+ {
+ mxImpGraphic->setOriginURL(rOriginURL);
+ }
+}
+
+OString Graphic::getUniqueID() const
+{
+ OString aUniqueString;
+ if (mxImpGraphic)
+ aUniqueString = mxImpGraphic->getUniqueID();
+ return aUniqueString;
+}
+
+const css::uno::Sequence<sal_Int8> & Graphic::getUnoTunnelId() {
+ static const comphelper::UnoIdInit gId;
+ return gId.getSeq();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/graphictools.cxx b/vcl/source/gdi/graphictools.cxx
new file mode 100644
index 000000000..b14f4263c
--- /dev/null
+++ b/vcl/source/gdi/graphictools.cxx
@@ -0,0 +1,288 @@
+/* -*- 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 <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <vcl/TypeSerializer.hxx>
+#include <vcl/graphictools.hxx>
+
+SvtGraphicFill::Transform::Transform()
+{
+ matrix[0] = 1.0; matrix[1] = 0.0; matrix[2] = 0.0;
+ matrix[3] = 0.0; matrix[4] = 1.0; matrix[5] = 0.0;
+}
+
+SvtGraphicStroke::SvtGraphicStroke() :
+ mfTransparency(),
+ mfStrokeWidth(),
+ maCapType(),
+ maJoinType(),
+ mfMiterLimit( 3.0 )
+{
+}
+
+SvtGraphicStroke::SvtGraphicStroke( const tools::Polygon& rPath,
+ const tools::PolyPolygon& rStartArrow,
+ const tools::PolyPolygon& rEndArrow,
+ double fTransparency,
+ double fStrokeWidth,
+ CapType aCap,
+ JoinType aJoin,
+ double fMiterLimit,
+ DashArray&& rDashArray ) :
+ maPath( rPath ),
+ maStartArrow( rStartArrow ),
+ maEndArrow( rEndArrow ),
+ mfTransparency( fTransparency ),
+ mfStrokeWidth( fStrokeWidth ),
+ maCapType( aCap ),
+ maJoinType( aJoin ),
+ mfMiterLimit( fMiterLimit ),
+ maDashArray( std::move(rDashArray) )
+{
+}
+
+void SvtGraphicStroke::getPath( tools::Polygon& rPath ) const
+{
+ rPath = maPath;
+}
+
+void SvtGraphicStroke::getStartArrow( tools::PolyPolygon& rPath ) const
+{
+ rPath = maStartArrow;
+}
+
+void SvtGraphicStroke::getEndArrow( tools::PolyPolygon& rPath ) const
+{
+ rPath = maEndArrow;
+}
+
+
+void SvtGraphicStroke::getDashArray( DashArray& rDashArray ) const
+{
+ rDashArray = maDashArray;
+}
+
+void SvtGraphicStroke::setPath( const tools::Polygon& rPoly )
+{
+ maPath = rPoly;
+}
+
+void SvtGraphicStroke::setStartArrow( const tools::PolyPolygon& rPoly )
+{
+ maStartArrow = rPoly;
+}
+
+void SvtGraphicStroke::setEndArrow( const tools::PolyPolygon& rPoly )
+{
+ maEndArrow = rPoly;
+}
+
+void SvtGraphicStroke::scale( double fXScale, double fYScale )
+{
+ // Clearly scaling stroke-width for fat lines is rather a problem
+ maPath.Scale( fXScale, fYScale );
+
+ double fScale = sqrt (fabs (fXScale * fYScale) ); // clearly not ideal.
+ mfStrokeWidth *= fScale;
+ mfMiterLimit *= fScale;
+
+ maStartArrow.Scale( fXScale, fYScale );
+ maEndArrow.Scale( fXScale, fYScale );
+}
+
+SvStream& WriteSvtGraphicStroke( SvStream& rOStm, const SvtGraphicStroke& rClass )
+{
+ VersionCompatWrite aCompat( rOStm, 1 );
+
+ rClass.maPath.Write( rOStm );
+ rClass.maStartArrow.Write( rOStm );
+ rClass.maEndArrow.Write( rOStm );
+ rOStm.WriteDouble( rClass.mfTransparency );
+ rOStm.WriteDouble( rClass.mfStrokeWidth );
+ sal_uInt16 nTmp = sal::static_int_cast<sal_uInt16>( rClass.maCapType );
+ rOStm.WriteUInt16( nTmp );
+ nTmp = sal::static_int_cast<sal_uInt16>( rClass.maJoinType );
+ rOStm.WriteUInt16( nTmp );
+ rOStm.WriteDouble( rClass.mfMiterLimit );
+
+ rOStm.WriteUInt32( rClass.maDashArray.size() );
+ size_t i;
+ for(i=0; i<rClass.maDashArray.size(); ++i)
+ rOStm.WriteDouble( rClass.maDashArray[i] );
+
+ return rOStm;
+}
+
+SvStream& ReadSvtGraphicStroke( SvStream& rIStm, SvtGraphicStroke& rClass )
+{
+ VersionCompatRead aCompat( rIStm );
+
+ rClass.maPath.Read( rIStm );
+ rClass.maStartArrow.Read( rIStm );
+ rClass.maEndArrow.Read( rIStm );
+ rIStm.ReadDouble( rClass.mfTransparency );
+ rIStm.ReadDouble( rClass.mfStrokeWidth );
+ sal_uInt16 nTmp;
+ rIStm.ReadUInt16( nTmp );
+ rClass.maCapType = SvtGraphicStroke::CapType(nTmp);
+ rIStm.ReadUInt16( nTmp );
+ rClass.maJoinType = SvtGraphicStroke::JoinType(nTmp);
+ rIStm.ReadDouble( rClass.mfMiterLimit );
+
+ sal_uInt32 nSize;
+ rIStm.ReadUInt32( nSize );
+ rClass.maDashArray.resize(nSize);
+ size_t i;
+ for(i=0; i<rClass.maDashArray.size(); ++i)
+ rIStm.ReadDouble( rClass.maDashArray[i] );
+
+ return rIStm;
+}
+
+SvtGraphicFill::SvtGraphicFill() :
+ maFillColor( COL_BLACK ),
+ mfTransparency(),
+ maFillRule(),
+ maFillType(),
+ mbTiling( false ),
+ maHatchType(),
+ maHatchColor( COL_BLACK ),
+ maGradientType(),
+ maGradient1stColor( COL_BLACK ),
+ maGradient2ndColor( COL_BLACK ),
+ maGradientStepCount( gradientStepsInfinite )
+{
+}
+
+SvtGraphicFill::SvtGraphicFill( const tools::PolyPolygon& rPath,
+ Color aFillColor,
+ double fTransparency,
+ FillRule aFillRule,
+ FillType aFillType,
+ const Transform& aFillTransform,
+ bool bTiling,
+ HatchType aHatchType,
+ Color aHatchColor,
+ GradientType aGradientType,
+ Color aGradient1stColor,
+ Color aGradient2ndColor,
+ sal_Int32 aGradientStepCount,
+ const Graphic& aFillGraphic ) :
+ maPath( rPath ),
+ maFillColor( aFillColor ),
+ mfTransparency( fTransparency ),
+ maFillRule( aFillRule ),
+ maFillType( aFillType ),
+ maFillTransform( aFillTransform ),
+ mbTiling( bTiling ),
+ maHatchType( aHatchType ),
+ maHatchColor( aHatchColor ),
+ maGradientType( aGradientType ),
+ maGradient1stColor( aGradient1stColor ),
+ maGradient2ndColor( aGradient2ndColor ),
+ maGradientStepCount( aGradientStepCount ),
+ maFillGraphic( aFillGraphic )
+{
+}
+
+void SvtGraphicFill::getPath( tools::PolyPolygon& rPath ) const
+{
+ rPath = maPath;
+}
+
+
+void SvtGraphicFill::getTransform( Transform& rTrans ) const
+{
+ rTrans = maFillTransform;
+}
+
+
+void SvtGraphicFill::getGraphic( Graphic& rGraphic ) const
+{
+ rGraphic = maFillGraphic;
+}
+
+void SvtGraphicFill::setPath( const tools::PolyPolygon& rPath )
+{
+ maPath = rPath;
+}
+
+SvStream& WriteSvtGraphicFill( SvStream& rOStm, const SvtGraphicFill& rClass )
+{
+ VersionCompatWrite aCompat( rOStm, 1 );
+
+ rClass.maPath.Write( rOStm );
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeColor(rClass.maFillColor);
+ rOStm.WriteDouble( rClass.mfTransparency );
+ sal_uInt16 nTmp = sal::static_int_cast<sal_uInt16>( rClass.maFillRule );
+ rOStm.WriteUInt16( nTmp );
+ nTmp = sal::static_int_cast<sal_uInt16>( rClass.maFillType );
+ rOStm.WriteUInt16( nTmp );
+ int i;
+ for(i=0; i<SvtGraphicFill::Transform::MatrixSize; ++i)
+ rOStm.WriteDouble( rClass.maFillTransform.matrix[i] );
+ nTmp = sal_uInt16(rClass.mbTiling);
+ rOStm.WriteUInt16( nTmp );
+ nTmp = sal::static_int_cast<sal_uInt16>( rClass.maHatchType );
+ rOStm.WriteUInt16( nTmp );
+ aSerializer.writeColor(rClass.maHatchColor);
+ nTmp = sal::static_int_cast<sal_uInt16>( rClass.maGradientType );
+ rOStm.WriteUInt16( nTmp );
+ aSerializer.writeColor(rClass.maGradient1stColor);
+ aSerializer.writeColor(rClass.maGradient2ndColor);
+ rOStm.WriteInt32( rClass.maGradientStepCount );
+ aSerializer.writeGraphic(rClass.maFillGraphic);
+
+ return rOStm;
+}
+
+SvStream& ReadSvtGraphicFill( SvStream& rIStm, SvtGraphicFill& rClass )
+{
+ VersionCompatRead aCompat( rIStm );
+
+ rClass.maPath.Read( rIStm );
+
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readColor(rClass.maFillColor);
+ rIStm.ReadDouble( rClass.mfTransparency );
+ sal_uInt16 nTmp;
+ rIStm.ReadUInt16( nTmp );
+ rClass.maFillRule = SvtGraphicFill::FillRule( nTmp );
+ rIStm.ReadUInt16( nTmp );
+ rClass.maFillType = SvtGraphicFill::FillType( nTmp );
+ for (int i = 0; i < SvtGraphicFill::Transform::MatrixSize; ++i)
+ rIStm.ReadDouble( rClass.maFillTransform.matrix[i] );
+ rIStm.ReadUInt16( nTmp );
+ rClass.mbTiling = nTmp;
+ rIStm.ReadUInt16( nTmp );
+ rClass.maHatchType = SvtGraphicFill::HatchType( nTmp );
+ aSerializer.readColor(rClass.maHatchColor);
+ rIStm.ReadUInt16( nTmp );
+ rClass.maGradientType = SvtGraphicFill::GradientType( nTmp );
+ aSerializer.readColor(rClass.maGradient1stColor);
+ aSerializer.readColor(rClass.maGradient2ndColor);
+ rIStm.ReadInt32( rClass.maGradientStepCount );
+ aSerializer.readGraphic(rClass.maFillGraphic);
+
+ return rIStm;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/hatch.cxx b/vcl/source/gdi/hatch.cxx
new file mode 100644
index 000000000..e097f2f36
--- /dev/null
+++ b/vcl/source/gdi/hatch.cxx
@@ -0,0 +1,114 @@
+/* -*- 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 <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/GenericTypeSerializer.hxx>
+#include <vcl/hatch.hxx>
+
+ImplHatch::ImplHatch() :
+ maColor ( COL_BLACK ),
+ meStyle ( HatchStyle::Single ),
+ mnDistance ( 1 ),
+ mnAngle ( 0 )
+{
+}
+
+bool ImplHatch::operator==( const ImplHatch& rImplHatch ) const
+{
+ return maColor == rImplHatch.maColor &&
+ meStyle == rImplHatch.meStyle &&
+ mnDistance == rImplHatch.mnDistance &&
+ mnAngle == rImplHatch.mnAngle;
+}
+
+Hatch::Hatch() = default;
+
+Hatch::Hatch( const Hatch& ) = default;
+
+Hatch::Hatch( HatchStyle eStyle, const Color& rColor,
+ tools::Long nDistance, Degree10 nAngle10 )
+{
+ mpImplHatch->maColor = rColor;
+ mpImplHatch->meStyle = eStyle;
+ mpImplHatch->mnDistance = nDistance;
+ mpImplHatch->mnAngle = nAngle10;
+}
+
+Hatch::~Hatch() = default;
+
+Hatch& Hatch::operator=( const Hatch& ) = default;
+
+bool Hatch::operator==( const Hatch& rHatch ) const
+{
+ return mpImplHatch == rHatch.mpImplHatch;
+}
+
+
+void Hatch::SetColor( const Color& rColor )
+{
+ mpImplHatch->maColor = rColor;
+}
+
+void Hatch::SetDistance( tools::Long nDistance )
+{
+ mpImplHatch->mnDistance = nDistance;
+}
+
+void Hatch::SetAngle( Degree10 nAngle10 )
+{
+ mpImplHatch->mnAngle = nAngle10;
+}
+
+SvStream& ReadHatch( SvStream& rIStm, Hatch& rHatch )
+{
+ VersionCompatRead aCompat(rIStm);
+
+ sal_uInt16 nTmpStyle(0);
+ rIStm.ReadUInt16(nTmpStyle);
+ rHatch.mpImplHatch->meStyle = static_cast<HatchStyle>(nTmpStyle);
+
+ tools::GenericTypeSerializer aSerializer(rIStm);
+ aSerializer.readColor(rHatch.mpImplHatch->maColor);
+
+ sal_Int32 nTmp32(0);
+ rIStm.ReadInt32(nTmp32);
+ rHatch.mpImplHatch->mnDistance = nTmp32;
+
+ sal_Int16 nTmpAngle(0);
+ rIStm.ReadInt16(nTmpAngle);
+ rHatch.mpImplHatch->mnAngle = Degree10(nTmpAngle);
+
+ return rIStm;
+}
+
+SvStream& WriteHatch( SvStream& rOStm, const Hatch& rHatch )
+{
+ VersionCompatWrite aCompat(rOStm, 1);
+
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rHatch.mpImplHatch->meStyle) );
+
+ tools::GenericTypeSerializer aSerializer(rOStm);
+ aSerializer.writeColor(rHatch.mpImplHatch->maColor);
+ rOStm.WriteInt32( rHatch.mpImplHatch->mnDistance ).WriteInt16( rHatch.mpImplHatch->mnAngle.get() );
+
+ return rOStm;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/impanmvw.cxx b/vcl/source/gdi/impanmvw.cxx
new file mode 100644
index 000000000..00a02912a
--- /dev/null
+++ b/vcl/source/gdi/impanmvw.cxx
@@ -0,0 +1,324 @@
+/* -*- 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 <memory>
+#include <impanmvw.hxx>
+
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+#include <tools/helpers.hxx>
+
+#include <window.h>
+
+ImplAnimView::ImplAnimView( Animation* pParent, OutputDevice* pOut,
+ const Point& rPt, const Size& rSz,
+ sal_uLong nExtraData,
+ OutputDevice* pFirstFrameOutDev ) :
+ mpParent ( pParent ),
+ mpRenderContext ( pFirstFrameOutDev ? pFirstFrameOutDev : pOut ),
+ mnExtraData ( nExtraData ),
+ maPt ( rPt ),
+ maSz ( rSz ),
+ maSzPix ( mpRenderContext->LogicToPixel( maSz ) ),
+ maClip ( mpRenderContext->GetClipRegion() ),
+ mpBackground ( VclPtr<VirtualDevice>::Create() ),
+ mpRestore ( VclPtr<VirtualDevice>::Create() ),
+ mnActPos ( 0 ),
+ meLastDisposal ( Disposal::Back ),
+ mbIsPaused ( false ),
+ mbIsMarked ( false ),
+ mbIsMirroredHorizontally ( maSz.Width() < 0 ),
+ mbIsMirroredVertically ( maSz.Height() < 0 )
+{
+ Animation::ImplIncAnimCount();
+
+ // Mirrored horizontally?
+ if( mbIsMirroredHorizontally )
+ {
+ maDispPt.setX( maPt.X() + maSz.Width() + 1 );
+ maDispSz.setWidth( -maSz.Width() );
+ maSzPix.setWidth( -maSzPix.Width() );
+ }
+ else
+ {
+ maDispPt.setX( maPt.X() );
+ maDispSz.setWidth( maSz.Width() );
+ }
+
+ // Mirrored vertically?
+ if( mbIsMirroredVertically )
+ {
+ maDispPt.setY( maPt.Y() + maSz.Height() + 1 );
+ maDispSz.setHeight( -maSz.Height() );
+ maSzPix.setHeight( -maSzPix.Height() );
+ }
+ else
+ {
+ maDispPt.setY( maPt.Y() );
+ maDispSz.setHeight( maSz.Height() );
+ }
+
+ // save background
+ mpBackground->SetOutputSizePixel( maSzPix );
+ mpRenderContext->SaveBackground(*mpBackground, maDispPt, maDispSz, maSzPix);
+
+ // Initialize drawing to actual position
+ drawToPos( mpParent->ImplGetCurPos() );
+
+ // If first frame OutputDevice is set, update variables now for real OutputDevice
+ if( pFirstFrameOutDev )
+ {
+ mpRenderContext = pOut;
+ maClip = mpRenderContext->GetClipRegion();
+ }
+}
+
+ImplAnimView::~ImplAnimView()
+{
+ mpBackground.disposeAndClear();
+ mpRestore.disposeAndClear();
+
+ Animation::ImplDecAnimCount();
+}
+
+bool ImplAnimView::matches(const OutputDevice* pOut, tools::Long nExtraData) const
+{
+ return (!pOut || pOut == mpRenderContext) && (nExtraData == 0 || nExtraData == mnExtraData);
+}
+
+void ImplAnimView::getPosSize( const AnimationBitmap& rAnimationBitmap, Point& rPosPix, Size& rSizePix )
+{
+ const Size& rAnmSize = mpParent->GetDisplaySizePixel();
+ Point aPt2( rAnimationBitmap.maPositionPixel.X() + rAnimationBitmap.maSizePixel.Width() - 1,
+ rAnimationBitmap.maPositionPixel.Y() + rAnimationBitmap.maSizePixel.Height() - 1 );
+ double fFactX, fFactY;
+
+ // calculate x scaling
+ if( rAnmSize.Width() > 1 )
+ fFactX = static_cast<double>( maSzPix.Width() - 1 ) / ( rAnmSize.Width() - 1 );
+ else
+ fFactX = 1.0;
+
+ // calculate y scaling
+ if( rAnmSize.Height() > 1 )
+ fFactY = static_cast<double>( maSzPix.Height() - 1 ) / ( rAnmSize.Height() - 1 );
+ else
+ fFactY = 1.0;
+
+ rPosPix.setX( FRound( rAnimationBitmap.maPositionPixel.X() * fFactX ) );
+ rPosPix.setY( FRound( rAnimationBitmap.maPositionPixel.Y() * fFactY ) );
+
+ aPt2.setX( FRound( aPt2.X() * fFactX ) );
+ aPt2.setY( FRound( aPt2.Y() * fFactY ) );
+
+ rSizePix.setWidth( aPt2.X() - rPosPix.X() + 1 );
+ rSizePix.setHeight( aPt2.Y() - rPosPix.Y() + 1 );
+
+ // Mirrored horizontally?
+ if( mbIsMirroredHorizontally )
+ rPosPix.setX( maSzPix.Width() - 1 - aPt2.X() );
+
+ // Mirrored vertically?
+ if( mbIsMirroredVertically )
+ rPosPix.setY( maSzPix.Height() - 1 - aPt2.Y() );
+}
+
+void ImplAnimView::drawToPos( sal_uLong nPos )
+{
+ VclPtr<vcl::RenderContext> pRenderContext = mpRenderContext;
+
+ vcl::PaintBufferGuardPtr pGuard;
+ if (mpRenderContext->GetOutDevType() == OUTDEV_WINDOW)
+ {
+ vcl::Window* pWindow = static_cast<vcl::WindowOutputDevice*>(mpRenderContext.get())->GetOwnerWindow();
+ pGuard.reset(new vcl::PaintBufferGuard(pWindow->ImplGetWindowImpl()->mpFrameData, pWindow));
+ pRenderContext = pGuard->GetRenderContext();
+ }
+
+ ScopedVclPtrInstance<VirtualDevice> aVDev;
+ std::optional<vcl::Region> xOldClip;
+ if (!maClip.IsNull())
+ xOldClip = pRenderContext->GetClipRegion();
+
+ aVDev->SetOutputSizePixel( maSzPix, false );
+ nPos = std::min( nPos, static_cast<sal_uLong>(mpParent->Count()) - 1 );
+
+ for( sal_uLong i = 0; i <= nPos; i++ )
+ draw( i, aVDev.get() );
+
+ if (xOldClip)
+ pRenderContext->SetClipRegion( maClip );
+
+ pRenderContext->DrawOutDev( maDispPt, maDispSz, Point(), maSzPix, *aVDev );
+ if (pGuard)
+ pGuard->SetPaintRect(tools::Rectangle(maDispPt, maDispSz));
+
+ if (xOldClip)
+ pRenderContext->SetClipRegion(*xOldClip);
+}
+
+void ImplAnimView::draw( sal_uLong nPos, VirtualDevice* pVDev )
+{
+ VclPtr<vcl::RenderContext> pRenderContext = mpRenderContext;
+
+ vcl::PaintBufferGuardPtr pGuard;
+ if (!pVDev && mpRenderContext->GetOutDevType() == OUTDEV_WINDOW)
+ {
+ vcl::Window* pWindow = static_cast<vcl::WindowOutputDevice*>(mpRenderContext.get())->GetOwnerWindow();
+ pGuard.reset(new vcl::PaintBufferGuard(pWindow->ImplGetWindowImpl()->mpFrameData, pWindow));
+ pRenderContext = pGuard->GetRenderContext();
+ }
+
+ tools::Rectangle aOutRect( pRenderContext->PixelToLogic( Point() ), pRenderContext->GetOutputSize() );
+
+ // check, if output lies out of display
+ if( aOutRect.Intersection( tools::Rectangle( maDispPt, maDispSz ) ).IsEmpty() )
+ setMarked( true );
+ else if( !mbIsPaused )
+ {
+ VclPtr<VirtualDevice> pDev;
+ Point aPosPix;
+ Point aBmpPosPix;
+ Size aSizePix;
+ Size aBmpSizePix;
+ const sal_uLong nLastPos = mpParent->Count() - 1;
+ mnActPos = std::min( nPos, nLastPos );
+ const AnimationBitmap& rAnimationBitmap = mpParent->Get( static_cast<sal_uInt16>( mnActPos ) );
+
+ getPosSize( rAnimationBitmap, aPosPix, aSizePix );
+
+ // Mirrored horizontally?
+ if( mbIsMirroredHorizontally )
+ {
+ aBmpPosPix.setX( aPosPix.X() + aSizePix.Width() - 1 );
+ aBmpSizePix.setWidth( -aSizePix.Width() );
+ }
+ else
+ {
+ aBmpPosPix.setX( aPosPix.X() );
+ aBmpSizePix.setWidth( aSizePix.Width() );
+ }
+
+ // Mirrored vertically?
+ if( mbIsMirroredVertically )
+ {
+ aBmpPosPix.setY( aPosPix.Y() + aSizePix.Height() - 1 );
+ aBmpSizePix.setHeight( -aSizePix.Height() );
+ }
+ else
+ {
+ aBmpPosPix.setY( aPosPix.Y() );
+ aBmpSizePix.setHeight( aSizePix.Height() );
+ }
+
+ // get output device
+ if( !pVDev )
+ {
+ pDev = VclPtr<VirtualDevice>::Create();
+ pDev->SetOutputSizePixel( maSzPix, false );
+ pDev->DrawOutDev( Point(), maSzPix, maDispPt, maDispSz, *pRenderContext );
+ }
+ else
+ pDev = pVDev;
+
+ // restore background after each run
+ if( !nPos )
+ {
+ meLastDisposal = Disposal::Back;
+ maRestPt = Point();
+ maRestSz = maSzPix;
+ }
+
+ // restore
+ if( ( Disposal::Not != meLastDisposal ) && maRestSz.Width() && maRestSz.Height() )
+ {
+ if( Disposal::Back == meLastDisposal )
+ pDev->DrawOutDev( maRestPt, maRestSz, maRestPt, maRestSz, *mpBackground );
+ else
+ pDev->DrawOutDev( maRestPt, maRestSz, Point(), maRestSz, *mpRestore );
+ }
+
+ meLastDisposal = rAnimationBitmap.meDisposal;
+ maRestPt = aPosPix;
+ maRestSz = aSizePix;
+
+ // What do we need to restore the next time?
+ // Put it into a bitmap if needed, else delete
+ // SaveBitmap to conserve memory
+ if( ( meLastDisposal == Disposal::Back ) || ( meLastDisposal == Disposal::Not ) )
+ mpRestore->SetOutputSizePixel( Size( 1, 1 ), false );
+ else
+ {
+ mpRestore->SetOutputSizePixel( maRestSz, false );
+ mpRestore->DrawOutDev( Point(), maRestSz, aPosPix, aSizePix, *pDev );
+ }
+
+ pDev->DrawBitmapEx( aBmpPosPix, aBmpSizePix, rAnimationBitmap.maBitmapEx );
+
+ if( !pVDev )
+ {
+ std::optional<vcl::Region> xOldClip;
+ if (!maClip.IsNull())
+ xOldClip = pRenderContext->GetClipRegion();
+
+ if (xOldClip)
+ pRenderContext->SetClipRegion( maClip );
+
+ pRenderContext->DrawOutDev( maDispPt, maDispSz, Point(), maSzPix, *pDev );
+ if (pGuard)
+ pGuard->SetPaintRect(tools::Rectangle(maDispPt, maDispSz));
+
+ if( xOldClip)
+ {
+ pRenderContext->SetClipRegion(*xOldClip);
+ xOldClip.reset();
+ }
+
+ pDev.disposeAndClear();
+ pRenderContext->Flush();
+ }
+ }
+}
+
+void ImplAnimView::repaint()
+{
+ const bool bOldPause = mbIsPaused;
+
+ mpRenderContext->SaveBackground(*mpBackground, maDispPt, maDispSz, maSzPix);
+
+ mbIsPaused = false;
+ drawToPos( mnActPos );
+ mbIsPaused = bOldPause;
+}
+
+AInfo* ImplAnimView::createAInfo() const
+{
+ AInfo* pAInfo = new AInfo;
+
+ pAInfo->aStartOrg = maPt;
+ pAInfo->aStartSize = maSz;
+ pAInfo->pOutDev = mpRenderContext;
+ pAInfo->pViewData = const_cast<ImplAnimView *>(this);
+ pAInfo->nExtraData = mnExtraData;
+ pAInfo->bPause = mbIsPaused;
+
+ return pAInfo;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/impglyphitem.cxx b/vcl/source/gdi/impglyphitem.cxx
new file mode 100644
index 000000000..8ec137434
--- /dev/null
+++ b/vcl/source/gdi/impglyphitem.cxx
@@ -0,0 +1,560 @@
+/* -*- 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 <impglyphitem.hxx>
+#include <vcl/glyphitemcache.hxx>
+#include <vcl/vcllayout.hxx>
+#include <vcl/lazydelete.hxx>
+#include <tools/stream.hxx>
+#include <unotools/configmgr.hxx>
+#include <TextLayoutCache.hxx>
+#include <officecfg/Office/Common.hxx>
+
+#include <unicode/ubidi.h>
+#include <unicode/uchar.h>
+
+// These need being explicit because of SalLayoutGlyphsImpl being private in vcl.
+SalLayoutGlyphs::SalLayoutGlyphs() {}
+
+SalLayoutGlyphs::~SalLayoutGlyphs() {}
+
+SalLayoutGlyphs::SalLayoutGlyphs(SalLayoutGlyphs&& rOther) noexcept
+{
+ std::swap(m_pImpl, rOther.m_pImpl);
+ std::swap(m_pExtraImpls, rOther.m_pExtraImpls);
+}
+
+SalLayoutGlyphs& SalLayoutGlyphs::operator=(SalLayoutGlyphs&& rOther) noexcept
+{
+ if (this != &rOther)
+ {
+ std::swap(m_pImpl, rOther.m_pImpl);
+ std::swap(m_pExtraImpls, rOther.m_pExtraImpls);
+ }
+ return *this;
+}
+
+bool SalLayoutGlyphs::IsValid() const
+{
+ if (m_pImpl == nullptr)
+ return false;
+ if (!m_pImpl->IsValid())
+ return false;
+ if (m_pExtraImpls)
+ for (std::unique_ptr<SalLayoutGlyphsImpl> const& impl : *m_pExtraImpls)
+ if (!impl->IsValid())
+ return false;
+ return true;
+}
+
+void SalLayoutGlyphs::Invalidate()
+{
+ // Invalidating is in fact simply clearing.
+ m_pImpl.reset();
+ m_pExtraImpls.reset();
+}
+
+SalLayoutGlyphsImpl* SalLayoutGlyphs::Impl(unsigned int nLevel) const
+{
+ if (nLevel == 0)
+ return m_pImpl.get();
+ if (m_pExtraImpls != nullptr && nLevel - 1 < m_pExtraImpls->size())
+ return (*m_pExtraImpls)[nLevel - 1].get();
+ return nullptr;
+}
+
+void SalLayoutGlyphs::AppendImpl(SalLayoutGlyphsImpl* pImpl)
+{
+ if (!m_pImpl)
+ m_pImpl.reset(pImpl);
+ else
+ {
+ if (!m_pExtraImpls)
+ m_pExtraImpls.reset(new std::vector<std::unique_ptr<SalLayoutGlyphsImpl>>);
+ m_pExtraImpls->emplace_back(pImpl);
+ }
+}
+
+SalLayoutGlyphsImpl* SalLayoutGlyphsImpl::clone() const { return new SalLayoutGlyphsImpl(*this); }
+
+// Clone, but only glyphs in the given range in the original text string.
+// It is possible the given range may not be cloned, in which case this returns nullptr.
+SalLayoutGlyphsImpl* SalLayoutGlyphsImpl::cloneCharRange(sal_Int32 index, sal_Int32 length) const
+{
+ std::unique_ptr<SalLayoutGlyphsImpl> copy(new SalLayoutGlyphsImpl(*GetFont()));
+ copy->SetFlags(GetFlags());
+ if (empty())
+ return copy.release();
+ bool rtl = front().IsRTLGlyph();
+ // Avoid mixing LTR/RTL or layouts that do not have it set explicitly (BiDiStrong). Otherwise
+ // the subset may not quite match what would a real layout call give (e.g. some characters with neutral
+ // direction such as space might have different LTR/RTL flag). It seems bailing out here mostly
+ // avoid relatively rare corner cases and doesn't matter for performance.
+ // This is also checked in SalLayoutGlyphsCache::GetLayoutGlyphs() below.
+ if (!(GetFlags() & SalLayoutFlags::BiDiStrong)
+ || rtl != bool(GetFlags() & SalLayoutFlags::BiDiRtl))
+ return nullptr;
+ copy->reserve(std::min<size_t>(size(), length));
+ sal_Int32 beginPos = index;
+ sal_Int32 endPos = index + length;
+ const_iterator pos;
+ if (rtl)
+ {
+ // Glyphs are in reverse order for RTL.
+ beginPos = index + length - 1;
+ endPos = index - 1;
+ // Skip glyphs that are in the string after the given index, i.e. are before the glyphs
+ // we want.
+ pos = std::partition_point(
+ begin(), end(), [beginPos](const GlyphItem& it) { return it.charPos() > beginPos; });
+ }
+ else
+ {
+ // Skip glyphs that are in the string before the given index (glyphs are sorted by charPos()).
+ pos = std::partition_point(
+ begin(), end(), [beginPos](const GlyphItem& it) { return it.charPos() < beginPos; });
+ }
+ if (pos == end())
+ return nullptr;
+ // Require a start at the exact position given, otherwise bail out.
+ if (pos->charPos() != beginPos)
+ return nullptr;
+ // For RTL make sure we're not cutting in the middle of a multi-character glyph
+ // (for non-RTL charPos is always the start of a multi-character glyph).
+ if (rtl && pos->charPos() + pos->charCount() > beginPos + 1)
+ return nullptr;
+ if (!isSafeToBreak(pos, rtl))
+ return nullptr;
+ // LinearPos needs adjusting to start at xOffset/yOffset for the first item,
+ // that's how it's computed in GenericSalLayout::LayoutText().
+ DevicePoint zeroPoint = pos->linearPos() - DevicePoint(pos->xOffset(), pos->yOffset());
+ // Add and adjust all glyphs until the given length.
+ // The check is written as 'charPos + charCount <= endPos' rather than 'charPos < endPos'
+ // (or similarly for RTL) to make sure we include complete glyphs. If a glyph is composed
+ // from several characters, we should not cut in the middle of those characters, so this
+ // checks the glyph is entirely in the given character range. If it is not, this will end
+ // the loop and the later 'pos->charPos() != endPos' check will fail and bail out.
+ // CppunitTest_sw_layoutwriter's testCombiningCharacterCursorPosition would fail without this.
+ while (pos != end()
+ && (rtl ? pos->charPos() - pos->charCount() >= endPos
+ : pos->charPos() + pos->charCount() <= endPos))
+ {
+ if (pos->IsRTLGlyph() != rtl)
+ return nullptr; // Don't mix RTL and non-RTL runs.
+ // HACK: When running CppunitTest_sw_uiwriter3's testTdf104649 on Mac there's glyph
+ // with id 1232 that has 0 charCount, 0 origWidth and inconsistent xOffset (sometimes 0,
+ // but sometimes not). Possibly font or Harfbuzz bug? It's extremely rare, so simply bail out.
+ if (pos->charCount() == 0 && pos->origWidth() == 0)
+ return nullptr;
+ copy->push_back(*pos);
+ copy->back().setLinearPos(copy->back().linearPos() - zeroPoint);
+ ++pos;
+ }
+ if (pos != end())
+ {
+ // Fail if the next character is not at the expected past-end position. For RTL check
+ // that we're not cutting in the middle of a multi-character glyph.
+ if (rtl ? pos->charPos() + pos->charCount() != endPos + 1 : pos->charPos() != endPos)
+ return nullptr;
+ if (!isSafeToBreak(pos, rtl))
+ return nullptr;
+ }
+ return copy.release();
+}
+
+bool SalLayoutGlyphsImpl::isSafeToBreak(const_iterator pos, bool rtl) const
+{
+ if (rtl)
+ {
+ // RTL is more complicated, because HB_GLYPH_FLAG_UNSAFE_TO_BREAK talks about beginning
+ // of a cluster, which refers to the text, not glyphs. This function is called
+ // for the first glyph of the subset and the first glyph after the subset, but since
+ // the glyphs are backwards, and we need the beginning of cluster at the start of the text
+ // and beginning of the cluster after the text, we need to check glyphs before this position.
+ if (pos == begin())
+ return true;
+ --pos;
+ }
+ // Don't create a subset if it's not safe to break at the beginning or end of the sequence
+ // (https://harfbuzz.github.io/harfbuzz-hb-buffer.html#hb-glyph-flags-t).
+ if (pos->IsUnsafeToBreak() || (pos->IsInCluster() && !pos->IsClusterStart()))
+ return false;
+ return true;
+}
+
+#ifdef DBG_UTIL
+bool SalLayoutGlyphsImpl::isEqual(const SalLayoutGlyphsImpl* other) const
+{
+ if (!GetFont()->mxFontMetric->CompareDeviceIndependentFontAttributes(
+ *other->GetFont()->mxFontMetric))
+ return false;
+ if (GetFlags() != other->GetFlags())
+ return false;
+ if (empty() || other->empty())
+ return empty() == other->empty();
+ if (size() != other->size())
+ return false;
+ for (size_t pos = 0; pos < size(); ++pos)
+ {
+ if ((*this)[pos] != (*other)[pos])
+ return false;
+ }
+ return true;
+}
+#endif
+
+bool SalLayoutGlyphsImpl::IsValid() const
+{
+ if (!m_rFontInstance.is())
+ return false;
+ return true;
+}
+
+void SalLayoutGlyphsCache::clear() { mCachedGlyphs.clear(); }
+
+SalLayoutGlyphsCache* SalLayoutGlyphsCache::self()
+{
+ static vcl::DeleteOnDeinit<SalLayoutGlyphsCache> cache(
+ !utl::ConfigManager::IsFuzzing()
+ ? officecfg::Office::Common::Cache::Font::GlyphsCacheSize::get()
+ : 20000);
+ return cache.get();
+}
+
+static UBiDiDirection getBiDiDirection(const OUString& text, sal_Int32 index, sal_Int32 len)
+{
+ // Return whether all character are LTR, RTL, neutral or whether it's mixed.
+ // This is sort of ubidi_getBaseDirection() and ubidi_getDirection(),
+ // but it's meant to be fast but also check all characters.
+ sal_Int32 end = index + len;
+ UBiDiDirection direction = UBIDI_NEUTRAL;
+ while (index < end)
+ {
+ switch (u_charDirection(text.iterateCodePoints(&index)))
+ {
+ // Only characters with strong direction.
+ case U_LEFT_TO_RIGHT:
+ if (direction == UBIDI_RTL)
+ return UBIDI_MIXED;
+ direction = UBIDI_LTR;
+ break;
+ case U_RIGHT_TO_LEFT:
+ case U_RIGHT_TO_LEFT_ARABIC:
+ if (direction == UBIDI_LTR)
+ return UBIDI_MIXED;
+ direction = UBIDI_RTL;
+ break;
+ default:
+ break;
+ }
+ }
+ return direction;
+}
+
+static SalLayoutGlyphs makeGlyphsSubset(const SalLayoutGlyphs& source,
+ const OutputDevice* outputDevice, const OUString& text,
+ sal_Int32 index, sal_Int32 len)
+{
+ // tdf#149264: We need to check if the text is LTR, RTL or mixed. Apparently
+ // harfbuzz doesn't give reproducible results (or possibly HB_GLYPH_FLAG_UNSAFE_TO_BREAK
+ // is not reliable?) when asked to lay out RTL text as LTR. So require that the whole
+ // subset ir either LTR or RTL.
+ UBiDiDirection direction = getBiDiDirection(text, index, len);
+ if (direction == UBIDI_MIXED)
+ return SalLayoutGlyphs();
+ SalLayoutGlyphs ret;
+ for (int level = 0;; ++level)
+ {
+ const SalLayoutGlyphsImpl* sourceLevel = source.Impl(level);
+ if (sourceLevel == nullptr)
+ break;
+ bool sourceRtl = bool(sourceLevel->GetFlags() & SalLayoutFlags::BiDiRtl);
+ if ((direction == UBIDI_LTR && sourceRtl) || (direction == UBIDI_RTL && !sourceRtl))
+ return SalLayoutGlyphs();
+ SalLayoutGlyphsImpl* cloned = sourceLevel->cloneCharRange(index, len);
+ // If the glyphs range cannot be cloned, bail out.
+ if (cloned == nullptr)
+ return SalLayoutGlyphs();
+ // If the entire string is mixed LTR/RTL but the subset is only LTR,
+ // then make sure the flags match that, otherwise checkGlyphsEqual()
+ // would assert on flags being different.
+ cloned->SetFlags(cloned->GetFlags()
+ | outputDevice->GetBiDiLayoutFlags(text, index, index + len));
+ // SalLayoutFlags::KashidaJustification is set only if any glyph
+ // in the range has GlyphItemFlags::ALLOW_KASHIDA (otherwise unset it).
+ if (cloned->GetFlags() & SalLayoutFlags::KashidaJustification)
+ {
+ bool hasKashida = false;
+ for (const GlyphItem& item : *cloned)
+ {
+ if (item.AllowKashida())
+ {
+ hasKashida = true;
+ break;
+ }
+ }
+ if (!hasKashida)
+ cloned->SetFlags(cloned->GetFlags() & ~SalLayoutFlags::KashidaJustification);
+ }
+#ifdef DBG_UTIL
+ else
+ for (const GlyphItem& item : *cloned)
+ assert(!item.AllowKashida());
+#endif
+ ret.AppendImpl(cloned);
+ }
+ return ret;
+}
+
+#ifdef DBG_UTIL
+static void checkGlyphsEqual(const SalLayoutGlyphs& g1, const SalLayoutGlyphs& g2)
+{
+ for (int level = 0;; ++level)
+ {
+ const SalLayoutGlyphsImpl* l1 = g1.Impl(level);
+ const SalLayoutGlyphsImpl* l2 = g2.Impl(level);
+ if (l1 == nullptr || l2 == nullptr)
+ {
+ assert(l1 == l2);
+ break;
+ }
+ assert(l1->isEqual(l2));
+ }
+}
+#endif
+
+const SalLayoutGlyphs*
+SalLayoutGlyphsCache::GetLayoutGlyphs(VclPtr<const OutputDevice> outputDevice, const OUString& text,
+ sal_Int32 nIndex, sal_Int32 nLen, tools::Long nLogicWidth,
+ const vcl::text::TextLayoutCache* layoutCache)
+{
+ if (nLen == 0)
+ return nullptr;
+ const CachedGlyphsKey key(outputDevice, text, nIndex, nLen, nLogicWidth);
+ GlyphsCache::const_iterator it = mCachedGlyphs.find(key);
+ if (it != mCachedGlyphs.end())
+ {
+ if (it->second.IsValid())
+ return &it->second;
+ // Do not try to create the layout here. If a cache item exists, it's already
+ // been attempted and the layout was invalid (this happens with MultiSalLayout).
+ // So in that case this is a cached failure.
+ return nullptr;
+ }
+ bool resetLastSubstringKey = true;
+ const sal_Unicode nbSpace = 0xa0; // non-breaking space
+ // SalLayoutGlyphsImpl::cloneCharRange() requires BiDiStrong, so if not set, do not even try.
+ bool skipGlyphSubsets
+ = !(outputDevice->GetLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiStrong);
+ if ((nIndex != 0 || nLen != text.getLength()) && !skipGlyphSubsets)
+ {
+ // The glyphs functions are often called first for an entire string
+ // and then with an increasing starting index until the end of the string.
+ // Which means it's possible to get the glyphs faster by just copying
+ // a subset of the full glyphs and adjusting as necessary.
+ if (mLastTemporaryKey.has_value() && mLastTemporaryKey == key)
+ return &mLastTemporaryGlyphs;
+ const CachedGlyphsKey keyWhole(outputDevice, text, 0, text.getLength(), nLogicWidth);
+ GlyphsCache::const_iterator itWhole = mCachedGlyphs.find(keyWhole);
+ if (itWhole == mCachedGlyphs.end())
+ {
+ // This function may often be called repeatedly for segments of the same string,
+ // in which case it is more efficient to cache glyphs for the entire string
+ // and then return subsets of them. So if a second call either starts at the same
+ // position or starts at the end of the previous call, cache the entire string.
+ // This used to do this only for the first two segments of the string,
+ // but that missed the case when the font slightly changed e.g. because of the first
+ // part being underlined. Doing this for any two segments allows this optimization
+ // even when the prefix of the string would use a different font.
+ // TODO: Can those font differences be ignored?
+ // Writer layouts tests enable SAL_ABORT_ON_NON_APPLICATION_FONT_USE in order
+ // to make PrintFontManager::Substitute() abort if font fallback happens. When
+ // laying out the entire string the chance this happens increases (e.g. testAbi11870
+ // normally calls this function only for a part of a string, but this optimization
+ // lays out the entire string and causes a fallback). Since this optimization
+ // does not change result of this function, simply disable it for those tests.
+ static bool bAbortOnFontSubstitute
+ = getenv("SAL_ABORT_ON_NON_APPLICATION_FONT_USE") != nullptr;
+ if (mLastSubstringKey.has_value() && !bAbortOnFontSubstitute)
+ {
+ sal_Int32 pos = nIndex;
+ if (mLastSubstringKey->len < pos && text[pos - 1] == nbSpace)
+ --pos; // Writer skips a non-breaking space, so skip that character too.
+ if ((mLastSubstringKey->len == pos || mLastSubstringKey->index == nIndex)
+ && mLastSubstringKey
+ == CachedGlyphsKey(outputDevice, text, mLastSubstringKey->index,
+ mLastSubstringKey->len, nLogicWidth))
+ {
+ GetLayoutGlyphs(outputDevice, text, 0, text.getLength(), nLogicWidth,
+ layoutCache);
+ itWhole = mCachedGlyphs.find(keyWhole);
+ }
+ else
+ mLastSubstringKey.reset();
+ }
+ if (!mLastSubstringKey.has_value())
+ {
+ mLastSubstringKey = key;
+ resetLastSubstringKey = false;
+ }
+ }
+ if (itWhole != mCachedGlyphs.end() && itWhole->second.IsValid())
+ {
+ mLastSubstringKey.reset();
+ mLastTemporaryGlyphs
+ = makeGlyphsSubset(itWhole->second, outputDevice, text, nIndex, nLen);
+ if (mLastTemporaryGlyphs.IsValid())
+ {
+ mLastTemporaryKey = std::move(key);
+#ifdef DBG_UTIL
+ std::shared_ptr<const vcl::text::TextLayoutCache> tmpLayoutCache;
+ if (layoutCache == nullptr)
+ {
+ tmpLayoutCache = vcl::text::TextLayoutCache::Create(text);
+ layoutCache = tmpLayoutCache.get();
+ }
+ // Check if the subset result really matches what we would get normally,
+ // to make sure corner cases are handled well (see SalLayoutGlyphsImpl::cloneCharRange()).
+ std::unique_ptr<SalLayout> layout
+ = outputDevice->ImplLayout(text, nIndex, nLen, Point(0, 0), nLogicWidth, {},
+ SalLayoutFlags::GlyphItemsOnly, layoutCache);
+ assert(layout);
+ checkGlyphsEqual(mLastTemporaryGlyphs, layout->GetGlyphs());
+#endif
+ return &mLastTemporaryGlyphs;
+ }
+ }
+ }
+ if (resetLastSubstringKey)
+ {
+ // Writer does non-breaking space differently (not as part of the string), so in that
+ // case ignore that call and still allow finding two adjacent substrings that have
+ // the non-breaking space between them.
+ if (nLen != 1 || text[nIndex] != nbSpace)
+ mLastSubstringKey.reset();
+ }
+
+ std::shared_ptr<const vcl::text::TextLayoutCache> tmpLayoutCache;
+ if (layoutCache == nullptr)
+ {
+ tmpLayoutCache = vcl::text::TextLayoutCache::Create(text);
+ layoutCache = tmpLayoutCache.get();
+ }
+ std::unique_ptr<SalLayout> layout
+ = outputDevice->ImplLayout(text, nIndex, nLen, Point(0, 0), nLogicWidth, {},
+ SalLayoutFlags::GlyphItemsOnly, layoutCache);
+ if (layout)
+ {
+ SalLayoutGlyphs glyphs = layout->GetGlyphs();
+ if (glyphs.IsValid())
+ {
+ // TODO: Fallbacks do not work reliably (fallback font not included in the key),
+ // so do not cache (but still return once, using the temporary without a key set).
+ if (glyphs.Impl(1) != nullptr)
+ {
+ mLastTemporaryGlyphs = std::move(glyphs);
+ mLastTemporaryKey.reset();
+ return &mLastTemporaryGlyphs;
+ }
+ mCachedGlyphs.insert(std::make_pair(key, layout->GetGlyphs()));
+ assert(mCachedGlyphs.find(key)
+ == mCachedGlyphs.begin()); // newly inserted item is first
+ return &mCachedGlyphs.begin()->second;
+ }
+ }
+ // Failure, cache it too as invalid glyphs.
+ mCachedGlyphs.insert(std::make_pair(key, SalLayoutGlyphs()));
+ return nullptr;
+}
+
+SalLayoutGlyphsCache::CachedGlyphsKey::CachedGlyphsKey(
+ const VclPtr<const OutputDevice>& outputDevice, const OUString& t, sal_Int32 i, sal_Int32 l,
+ tools::Long w)
+ : text(t)
+ , index(i)
+ , len(l)
+ , logicWidth(w)
+ // we also need to save things used in OutputDevice::ImplPrepareLayoutArgs(), in case they
+ // change in the output device, plus mapMode affects the sizes.
+ , fontMetric(outputDevice->GetFontMetric())
+ // TODO It would be possible to get a better hit ratio if mapMode wasn't part of the key
+ // and results that differ only in mapmode would have coordinates adjusted based on that.
+ // That would occasionally lead to rounding errors (at least differences that would
+ // make checkGlyphsEqual() fail).
+ , mapMode(outputDevice->GetMapMode())
+ , rtl(outputDevice->IsRTLEnabled())
+ , layoutMode(outputDevice->GetLayoutMode())
+ , digitLanguage(outputDevice->GetDigitLanguage())
+{
+ const LogicalFontInstance* fi = outputDevice->GetFontInstance();
+ fi->GetScale(&fontScaleX, &fontScaleY);
+
+ const vcl::font::FontSelectPattern& rFSD = fi->GetFontSelectPattern();
+ artificialItalic = rFSD.GetItalic() != ITALIC_NONE && fontMetric.GetItalic() == ITALIC_NONE;
+ artificialBold = rFSD.GetWeight() > WEIGHT_MEDIUM && fontMetric.GetWeight() <= WEIGHT_MEDIUM;
+
+ hashValue = 0;
+ o3tl::hash_combine(hashValue, vcl::text::FirstCharsStringHash()(text));
+ o3tl::hash_combine(hashValue, index);
+ o3tl::hash_combine(hashValue, len);
+ o3tl::hash_combine(hashValue, logicWidth);
+ o3tl::hash_combine(hashValue, outputDevice.get());
+ // Need to use IgnoreColor, because sometimes the color changes, but it's irrelevant
+ // for text layout (and also obsolete in vcl::Font).
+ o3tl::hash_combine(hashValue, fontMetric.GetHashValueIgnoreColor());
+ // For some reason font scale may differ even if vcl::Font is the same,
+ // so explicitly check it too.
+ o3tl::hash_combine(hashValue, fontScaleX);
+ o3tl::hash_combine(hashValue, fontScaleY);
+ o3tl::hash_combine(hashValue, mapMode.GetHashValue());
+ o3tl::hash_combine(hashValue, rtl);
+ o3tl::hash_combine(hashValue, artificialItalic);
+ o3tl::hash_combine(hashValue, artificialBold);
+ o3tl::hash_combine(hashValue, layoutMode);
+ o3tl::hash_combine(hashValue, digitLanguage.get());
+}
+
+inline bool SalLayoutGlyphsCache::CachedGlyphsKey::operator==(const CachedGlyphsKey& other) const
+{
+ return hashValue == other.hashValue && index == other.index && len == other.len
+ && logicWidth == other.logicWidth && mapMode == other.mapMode && rtl == other.rtl
+ && artificialItalic == other.artificialItalic && artificialBold == other.artificialBold
+ && layoutMode == other.layoutMode && digitLanguage == other.digitLanguage
+ && fontScaleX == other.fontScaleX && fontScaleY == other.fontScaleY
+ && fontMetric.EqualIgnoreColor(other.fontMetric)
+ && vcl::text::FastStringCompareEqual()(text, other.text);
+ // Slower things last in the comparison.
+}
+
+size_t SalLayoutGlyphsCache::GlyphsCost::operator()(const SalLayoutGlyphs& glyphs) const
+{
+ size_t cost = 0;
+ for (int level = 0;; ++level)
+ {
+ const SalLayoutGlyphsImpl* impl = glyphs.Impl(level);
+ if (impl == nullptr)
+ break;
+ // Count size in bytes, both the SalLayoutGlyphsImpl instance and contained GlyphItem's.
+ cost += sizeof(*impl);
+ cost += impl->size() * sizeof(impl->front());
+ }
+ return cost;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/impgraph.cxx b/vcl/source/gdi/impgraph.cxx
new file mode 100644
index 000000000..37e13abc5
--- /dev/null
+++ b/vcl/source/gdi/impgraph.cxx
@@ -0,0 +1,1759 @@
+/* -*- 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 <sal/log.hxx>
+
+#include <SwapFile.hxx>
+
+#include <comphelper/fileformat.h>
+#include <o3tl/make_shared.hxx>
+#include <tools/fract.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/stream.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <unotools/tempfile.hxx>
+#include <vcl/filter/SvmReader.hxx>
+#include <vcl/filter/SvmWriter.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/gfxlink.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/metaact.hxx>
+#include <impgraph.hxx>
+#include <com/sun/star/graphic/XPrimitive2D.hpp>
+#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
+#include <vcl/dibtools.hxx>
+#include <map>
+#include <memory>
+#include <vcl/gdimetafiletools.hxx>
+#include <vcl/TypeSerializer.hxx>
+#include <vcl/pdfread.hxx>
+#include <graphic/VectorGraphicLoader.hxx>
+
+#define GRAPHIC_MTFTOBMP_MAXEXT 2048
+#define GRAPHIC_STREAMBUFSIZE 8192UL
+
+#define SWAP_FORMAT_ID COMPAT_FORMAT( 'S', 'W', 'A', 'P' )
+
+using namespace com::sun::star;
+
+class ImpSwapFile : public vcl::SwapFile
+{
+private:
+ OUString maOriginURL;
+
+public:
+ ImpSwapFile(INetURLObject const & rSwapURL, OUString const & rOriginURL)
+ : SwapFile(rSwapURL)
+ , maOriginURL(rOriginURL)
+ {
+ }
+
+ OUString const & getOriginURL() const { return maOriginURL; }
+};
+
+OUString ImpGraphic::getSwapFileURL() const
+{
+ if (mpSwapFile)
+ return mpSwapFile->getSwapURL().GetMainURL(INetURLObject::DecodeMechanism::NONE);
+ return OUString();
+}
+
+ImpGraphic::ImpGraphic() :
+ meType ( GraphicType::NONE ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ maLastUsed (std::chrono::high_resolution_clock::now()),
+ mbPrepared ( false )
+{
+}
+
+ImpGraphic::ImpGraphic(const ImpGraphic& rImpGraphic)
+ : maMetaFile(rImpGraphic.maMetaFile)
+ , maBitmapEx(rImpGraphic.maBitmapEx)
+ , maSwapInfo(rImpGraphic.maSwapInfo)
+ , mpContext(rImpGraphic.mpContext)
+ , mpSwapFile(rImpGraphic.mpSwapFile)
+ , mpGfxLink(rImpGraphic.mpGfxLink)
+ , meType(rImpGraphic.meType)
+ , mnSizeBytes(rImpGraphic.mnSizeBytes)
+ , mbSwapOut(rImpGraphic.mbSwapOut)
+ , mbDummyContext(rImpGraphic.mbDummyContext)
+ , maVectorGraphicData(rImpGraphic.maVectorGraphicData)
+ , maGraphicExternalLink(rImpGraphic.maGraphicExternalLink)
+ , maLastUsed (std::chrono::high_resolution_clock::now())
+ , mbPrepared (rImpGraphic.mbPrepared)
+{
+ if( rImpGraphic.mpAnimation )
+ {
+ mpAnimation = std::make_unique<Animation>( *rImpGraphic.mpAnimation );
+ maBitmapEx = mpAnimation->GetBitmapEx();
+ }
+}
+
+ImpGraphic::ImpGraphic(ImpGraphic&& rImpGraphic) noexcept
+ : maMetaFile(std::move(rImpGraphic.maMetaFile))
+ , maBitmapEx(std::move(rImpGraphic.maBitmapEx))
+ , maSwapInfo(std::move(rImpGraphic.maSwapInfo))
+ , mpAnimation(std::move(rImpGraphic.mpAnimation))
+ , mpContext(std::move(rImpGraphic.mpContext))
+ , mpSwapFile(std::move(rImpGraphic.mpSwapFile))
+ , mpGfxLink(std::move(rImpGraphic.mpGfxLink))
+ , meType(rImpGraphic.meType)
+ , mnSizeBytes(rImpGraphic.mnSizeBytes)
+ , mbSwapOut(rImpGraphic.mbSwapOut)
+ , mbDummyContext(rImpGraphic.mbDummyContext)
+ , maVectorGraphicData(std::move(rImpGraphic.maVectorGraphicData))
+ , maGraphicExternalLink(rImpGraphic.maGraphicExternalLink)
+ , maLastUsed (std::chrono::high_resolution_clock::now())
+ , mbPrepared (rImpGraphic.mbPrepared)
+{
+ rImpGraphic.clear();
+ rImpGraphic.mbDummyContext = false;
+}
+
+ImpGraphic::ImpGraphic(std::shared_ptr<GfxLink> const & rGfxLink, sal_Int32 nPageIndex)
+ : mpGfxLink(rGfxLink)
+ , meType(GraphicType::Bitmap)
+ , mnSizeBytes(0)
+ , mbSwapOut(true)
+ , mbDummyContext(false)
+ , maLastUsed (std::chrono::high_resolution_clock::now())
+ , mbPrepared (false)
+{
+ maSwapInfo.mbIsTransparent = true;
+ maSwapInfo.mbIsAlpha = true;
+ maSwapInfo.mbIsEPS = false;
+ maSwapInfo.mbIsAnimated = false;
+ maSwapInfo.mnAnimationLoopCount = 0;
+ maSwapInfo.mnPageIndex = nPageIndex;
+}
+
+ImpGraphic::ImpGraphic(GraphicExternalLink const & rGraphicExternalLink) :
+ meType ( GraphicType::Default ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ maGraphicExternalLink(rGraphicExternalLink),
+ maLastUsed (std::chrono::high_resolution_clock::now()),
+ mbPrepared (false)
+{
+}
+
+ImpGraphic::ImpGraphic( const BitmapEx& rBitmapEx ) :
+ maBitmapEx ( rBitmapEx ),
+ meType ( !rBitmapEx.IsEmpty() ? GraphicType::Bitmap : GraphicType::NONE ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ maLastUsed (std::chrono::high_resolution_clock::now()),
+ mbPrepared (false)
+{
+}
+
+ImpGraphic::ImpGraphic(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr)
+: meType( rVectorGraphicDataPtr ? GraphicType::Bitmap : GraphicType::NONE ),
+ mnSizeBytes( 0 ),
+ mbSwapOut( false ),
+ mbDummyContext ( false ),
+ maVectorGraphicData(rVectorGraphicDataPtr),
+ maLastUsed (std::chrono::high_resolution_clock::now()),
+ mbPrepared (false)
+{
+}
+
+ImpGraphic::ImpGraphic( const Animation& rAnimation ) :
+ maBitmapEx ( rAnimation.GetBitmapEx() ),
+ mpAnimation ( std::make_unique<Animation>( rAnimation ) ),
+ meType ( GraphicType::Bitmap ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ maLastUsed (std::chrono::high_resolution_clock::now()),
+ mbPrepared (false)
+{
+}
+
+ImpGraphic::ImpGraphic( const GDIMetaFile& rMtf ) :
+ maMetaFile ( rMtf ),
+ meType ( GraphicType::GdiMetafile ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ maLastUsed (std::chrono::high_resolution_clock::now()),
+ mbPrepared (false)
+{
+}
+
+ImpGraphic::~ImpGraphic()
+{
+ vcl::graphic::Manager::get().unregisterGraphic(this);
+}
+
+ImpGraphic& ImpGraphic::operator=( const ImpGraphic& rImpGraphic )
+{
+ if( &rImpGraphic != this )
+ {
+ sal_Int64 aOldSizeBytes = mnSizeBytes;
+
+ maMetaFile = rImpGraphic.maMetaFile;
+ meType = rImpGraphic.meType;
+ mnSizeBytes = rImpGraphic.mnSizeBytes;
+
+ maSwapInfo = rImpGraphic.maSwapInfo;
+ mpContext = rImpGraphic.mpContext;
+ mbDummyContext = rImpGraphic.mbDummyContext;
+ maGraphicExternalLink = rImpGraphic.maGraphicExternalLink;
+
+ mpAnimation.reset();
+
+ if ( rImpGraphic.mpAnimation )
+ {
+ mpAnimation = std::make_unique<Animation>( *rImpGraphic.mpAnimation );
+ maBitmapEx = mpAnimation->GetBitmapEx();
+ }
+ else
+ {
+ maBitmapEx = rImpGraphic.maBitmapEx;
+ }
+
+ mbSwapOut = rImpGraphic.mbSwapOut;
+ mpSwapFile = rImpGraphic.mpSwapFile;
+ mbPrepared = rImpGraphic.mbPrepared;
+
+ mpGfxLink = rImpGraphic.mpGfxLink;
+
+ maVectorGraphicData = rImpGraphic.maVectorGraphicData;
+ maLastUsed = std::chrono::high_resolution_clock::now();
+
+ vcl::graphic::Manager::get().changeExisting(this, aOldSizeBytes);
+ }
+
+ return *this;
+}
+
+ImpGraphic& ImpGraphic::operator=(ImpGraphic&& rImpGraphic)
+{
+ sal_Int64 aOldSizeBytes = mnSizeBytes;
+
+ maMetaFile = std::move(rImpGraphic.maMetaFile);
+ meType = rImpGraphic.meType;
+ mnSizeBytes = rImpGraphic.mnSizeBytes;
+ maSwapInfo = std::move(rImpGraphic.maSwapInfo);
+ mpContext = std::move(rImpGraphic.mpContext);
+ mbDummyContext = rImpGraphic.mbDummyContext;
+ mpAnimation = std::move(rImpGraphic.mpAnimation);
+ maBitmapEx = std::move(rImpGraphic.maBitmapEx);
+ mbSwapOut = rImpGraphic.mbSwapOut;
+ mpSwapFile = std::move(rImpGraphic.mpSwapFile);
+ mpGfxLink = std::move(rImpGraphic.mpGfxLink);
+ maVectorGraphicData = std::move(rImpGraphic.maVectorGraphicData);
+ maGraphicExternalLink = rImpGraphic.maGraphicExternalLink;
+ mbPrepared = rImpGraphic.mbPrepared;
+
+ rImpGraphic.clear();
+ rImpGraphic.mbDummyContext = false;
+ maLastUsed = std::chrono::high_resolution_clock::now();
+
+ vcl::graphic::Manager::get().changeExisting(this, aOldSizeBytes);
+
+ return *this;
+}
+
+bool ImpGraphic::operator==( const ImpGraphic& rImpGraphic ) const
+{
+ bool bRet = false;
+
+ if( this == &rImpGraphic )
+ bRet = true;
+ else if (mbPrepared && rImpGraphic.mbPrepared)
+ {
+ bRet = (*mpGfxLink == *rImpGraphic.mpGfxLink);
+ }
+ else if (isAvailable() && rImpGraphic.isAvailable())
+ {
+ switch( meType )
+ {
+ case GraphicType::NONE:
+ bRet = true;
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ if( rImpGraphic.maMetaFile == maMetaFile )
+ bRet = true;
+ }
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if(maVectorGraphicData)
+ {
+ if(maVectorGraphicData == rImpGraphic.maVectorGraphicData)
+ {
+ // equal instances
+ bRet = true;
+ }
+ else if(rImpGraphic.maVectorGraphicData)
+ {
+ // equal content
+ bRet = (*maVectorGraphicData) == (*rImpGraphic.maVectorGraphicData);
+ }
+ }
+ else if( mpAnimation )
+ {
+ if( rImpGraphic.mpAnimation && ( *rImpGraphic.mpAnimation == *mpAnimation ) )
+ bRet = true;
+ }
+ else if( !rImpGraphic.mpAnimation && ( rImpGraphic.maBitmapEx == maBitmapEx ) )
+ {
+ bRet = true;
+ }
+ }
+ break;
+
+ case GraphicType::Default:
+ break;
+ }
+ }
+
+ return bRet;
+}
+
+const std::shared_ptr<VectorGraphicData>& ImpGraphic::getVectorGraphicData() const
+{
+ ensureAvailable();
+
+ return maVectorGraphicData;
+}
+
+void ImpGraphic::createSwapInfo()
+{
+ if (isSwappedOut())
+ return;
+
+ if (!maBitmapEx.IsEmpty())
+ maSwapInfo.maSizePixel = maBitmapEx.GetSizePixel();
+ else
+ maSwapInfo.maSizePixel = Size();
+
+ maSwapInfo.maPrefMapMode = getPrefMapMode();
+ maSwapInfo.maPrefSize = getPrefSize();
+ maSwapInfo.mbIsAnimated = isAnimated();
+ maSwapInfo.mbIsEPS = isEPS();
+ maSwapInfo.mbIsTransparent = isTransparent();
+ maSwapInfo.mbIsAlpha = isAlpha();
+ maSwapInfo.mnAnimationLoopCount = getAnimationLoopCount();
+ maSwapInfo.mnPageIndex = getPageNumber();
+}
+
+void ImpGraphic::clearGraphics()
+{
+ maBitmapEx.Clear();
+ maMetaFile.Clear();
+ mpAnimation.reset();
+ maVectorGraphicData.reset();
+}
+
+void ImpGraphic::setPrepared(bool bAnimated, const Size* pSizeHint)
+{
+ mbPrepared = true;
+ mbSwapOut = true;
+ meType = GraphicType::Bitmap;
+
+ SvMemoryStream aMemoryStream(const_cast<sal_uInt8*>(mpGfxLink->GetData()), mpGfxLink->GetDataSize(), StreamMode::READ | StreamMode::WRITE);
+
+ if (pSizeHint)
+ {
+ maSwapInfo.maPrefSize = *pSizeHint;
+ maSwapInfo.maPrefMapMode = MapMode(MapUnit::Map100thMM);
+ }
+
+ GraphicDescriptor aDescriptor(aMemoryStream, nullptr);
+ if (aDescriptor.Detect(true))
+ {
+ if (!pSizeHint)
+ {
+ // If we have logic size, work with that, as later pixel -> logic
+ // conversion will work with the output device DPI, not the graphic
+ // DPI.
+ Size aLogSize = aDescriptor.GetSize_100TH_MM();
+ if (aDescriptor.GetPreferredLogSize() && aDescriptor.GetPreferredMapMode())
+ {
+ maSwapInfo.maPrefSize = *aDescriptor.GetPreferredLogSize();
+ maSwapInfo.maPrefMapMode = *aDescriptor.GetPreferredMapMode();
+ }
+ else if (aLogSize.getWidth() && aLogSize.getHeight())
+ {
+ maSwapInfo.maPrefSize = aLogSize;
+ maSwapInfo.maPrefMapMode = MapMode(MapUnit::Map100thMM);
+ }
+ else
+ {
+ maSwapInfo.maPrefSize = aDescriptor.GetSizePixel();
+ maSwapInfo.maPrefMapMode = MapMode(MapUnit::MapPixel);
+ }
+ }
+
+ maSwapInfo.maSizePixel = aDescriptor.GetSizePixel();
+ maSwapInfo.mbIsTransparent = aDescriptor.IsTransparent();
+ maSwapInfo.mbIsAlpha = aDescriptor.IsAlpha();
+ } else {
+ maSwapInfo.mbIsTransparent = false;
+ maSwapInfo.mbIsAlpha = false;
+ }
+
+ maSwapInfo.mnAnimationLoopCount = 0;
+ maSwapInfo.mbIsEPS = false;
+ maSwapInfo.mbIsAnimated = bAnimated;
+
+ if (maVectorGraphicData)
+ maSwapInfo.mnPageIndex = maVectorGraphicData->getPageIndex();
+}
+
+void ImpGraphic::clear()
+{
+ mpSwapFile.reset();
+ mbSwapOut = false;
+ mbPrepared = false;
+
+ // cleanup
+ clearGraphics();
+ meType = GraphicType::NONE;
+ sal_Int64 nOldSize = mnSizeBytes;
+ mnSizeBytes = 0;
+ vcl::graphic::Manager::get().changeExisting(this, nOldSize);
+ maGraphicExternalLink.msURL.clear();
+}
+
+void ImpGraphic::setDefaultType()
+{
+ clear();
+ meType = GraphicType::Default;
+}
+
+bool ImpGraphic::isSupportedGraphic() const
+{
+ return( meType != GraphicType::NONE );
+}
+
+bool ImpGraphic::isTransparent() const
+{
+ bool bRet(true);
+
+ if (mbSwapOut)
+ {
+ bRet = maSwapInfo.mbIsTransparent;
+ }
+ else if (meType == GraphicType::Bitmap && !maVectorGraphicData)
+ {
+ bRet = mpAnimation ? mpAnimation->IsTransparent() : maBitmapEx.IsAlpha();
+ }
+
+ return bRet;
+}
+
+bool ImpGraphic::isAlpha() const
+{
+ bool bRet(false);
+
+ if (mbSwapOut)
+ {
+ bRet = maSwapInfo.mbIsAlpha;
+ }
+ else if (maVectorGraphicData)
+ {
+ bRet = true;
+ }
+ else if (meType == GraphicType::Bitmap)
+ {
+ bRet = (nullptr == mpAnimation && maBitmapEx.IsAlpha());
+ }
+
+ return bRet;
+}
+
+bool ImpGraphic::isAnimated() const
+{
+ return mbSwapOut ? maSwapInfo.mbIsAnimated : mpAnimation != nullptr;
+}
+
+bool ImpGraphic::isEPS() const
+{
+ if (mbSwapOut)
+ return maSwapInfo.mbIsEPS;
+
+ return( ( meType == GraphicType::GdiMetafile ) &&
+ ( maMetaFile.GetActionSize() > 0 ) &&
+ ( maMetaFile.GetAction( 0 )->GetType() == MetaActionType::EPS ) );
+}
+
+bool ImpGraphic::isAvailable() const
+{
+ return !mbPrepared && !mbSwapOut;
+}
+
+bool ImpGraphic::makeAvailable()
+{
+ return ensureAvailable();
+}
+
+BitmapEx ImpGraphic::getVectorGraphicReplacement() const
+{
+ BitmapEx aRet = maVectorGraphicData->getReplacement();
+
+ if (maExPrefSize.getWidth() && maExPrefSize.getHeight())
+ {
+ aRet.SetPrefSize(maExPrefSize);
+ }
+
+ return aRet;
+}
+
+Bitmap ImpGraphic::getBitmap(const GraphicConversionParameters& rParameters) const
+{
+ Bitmap aRetBmp;
+
+ ensureAvailable();
+
+ if( meType == GraphicType::Bitmap )
+ {
+ if(maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // use maBitmapEx as local buffer for rendered svg
+ const_cast< ImpGraphic* >(this)->maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ const BitmapEx& rRetBmpEx = ( mpAnimation ? mpAnimation->GetBitmapEx() : maBitmapEx );
+
+ aRetBmp = rRetBmpEx.GetBitmap( COL_WHITE );
+
+ if(rParameters.getSizePixel().Width() || rParameters.getSizePixel().Height())
+ aRetBmp.Scale(rParameters.getSizePixel());
+ }
+ else if( ( meType != GraphicType::Default ) && isSupportedGraphic() )
+ {
+ if(maBitmapEx.IsEmpty())
+ {
+ // calculate size
+ ScopedVclPtrInstance< VirtualDevice > aVDev;
+ Size aDrawSize(aVDev->LogicToPixel(maMetaFile.GetPrefSize(), maMetaFile.GetPrefMapMode()));
+
+ if(rParameters.getSizePixel().Width() && rParameters.getSizePixel().Height())
+ {
+ // apply given size if exists
+ aDrawSize = rParameters.getSizePixel();
+ }
+
+ if(aDrawSize.Width() && aDrawSize.Height() && !rParameters.getUnlimitedSize()
+ && (aDrawSize.Width() > GRAPHIC_MTFTOBMP_MAXEXT || aDrawSize.Height() > GRAPHIC_MTFTOBMP_MAXEXT))
+ {
+ // limit bitmap size to a maximum of GRAPHIC_MTFTOBMP_MAXEXT x GRAPHIC_MTFTOBMP_MAXEXT
+ double fWH(static_cast<double>(aDrawSize.Width()) / static_cast<double>(aDrawSize.Height()));
+
+ if(fWH <= 1.0)
+ {
+ aDrawSize.setWidth(basegfx::fround(GRAPHIC_MTFTOBMP_MAXEXT * fWH));
+ aDrawSize.setHeight(GRAPHIC_MTFTOBMP_MAXEXT);
+ }
+ else
+ {
+ aDrawSize.setWidth(GRAPHIC_MTFTOBMP_MAXEXT);
+ aDrawSize.setHeight(basegfx::fround(GRAPHIC_MTFTOBMP_MAXEXT / fWH));
+ }
+ }
+
+ // calculate pixel size. Normally, it's the same as aDrawSize, but may
+ // need to be extended when hairlines are on the right or bottom edge
+ Size aPixelSize(aDrawSize);
+
+ if(GraphicType::GdiMetafile == getType())
+ {
+ // tdf#126319 Removed correction based on hairline-at-the-extremes of
+ // the metafile. The task shows that this is no longer sufficient since
+ // less hairlines get used in general - what is good, but breaks that
+ // old fix. Anyways, hairlines are a left-over from non-AA times
+ // when it was not possible to paint lines taller than one pixel.
+ // This might need to be corrected further using primitives and
+ // the possibility to get better-quality ranges for correction. For
+ // now, always add that one pixel.
+ aPixelSize.setWidth(aPixelSize.getWidth() + 1);
+ aPixelSize.setHeight(aPixelSize.getHeight() + 1);
+ }
+
+ if(aVDev->SetOutputSizePixel(aPixelSize))
+ {
+ if(rParameters.getAntiAliase())
+ {
+ aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::Enable);
+ }
+
+ if(rParameters.getSnapHorVerLines())
+ {
+ aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::PixelSnapHairline);
+ }
+
+ draw(*aVDev, Point(), aDrawSize);
+
+ // use maBitmapEx as local buffer for rendered metafile
+ const_cast< ImpGraphic* >(this)->maBitmapEx = aVDev->GetBitmapEx( Point(), aVDev->GetOutputSizePixel() );
+ }
+ }
+
+ aRetBmp = maBitmapEx.GetBitmap();
+ }
+
+ if( !aRetBmp.IsEmpty() )
+ {
+ aRetBmp.SetPrefMapMode(getPrefMapMode());
+ aRetBmp.SetPrefSize(getPrefSize());
+ }
+
+ return aRetBmp;
+}
+
+BitmapEx ImpGraphic::getBitmapEx(const GraphicConversionParameters& rParameters) const
+{
+ BitmapEx aRetBmpEx;
+
+ ensureAvailable();
+
+ if( meType == GraphicType::Bitmap )
+ {
+ if(maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // use maBitmapEx as local buffer for rendered svg
+ const_cast< ImpGraphic* >(this)->maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ aRetBmpEx = ( mpAnimation ? mpAnimation->GetBitmapEx() : maBitmapEx );
+
+ if(rParameters.getSizePixel().Width() || rParameters.getSizePixel().Height())
+ {
+ aRetBmpEx.Scale(
+ rParameters.getSizePixel(),
+ BmpScaleFlag::Fast);
+ }
+ }
+ else if( ( meType != GraphicType::Default ) && isSupportedGraphic() )
+ {
+ if(maBitmapEx.IsEmpty())
+ {
+ const ImpGraphic aMonoMask( maMetaFile.GetMonochromeMtf( COL_BLACK ) );
+
+ // use maBitmapEx as local buffer for rendered metafile
+ const_cast< ImpGraphic* >(this)->maBitmapEx = BitmapEx(getBitmap(rParameters), aMonoMask.getBitmap(rParameters));
+ }
+
+ aRetBmpEx = maBitmapEx;
+ }
+
+ return aRetBmpEx;
+}
+
+Animation ImpGraphic::getAnimation() const
+{
+ Animation aAnimation;
+
+ ensureAvailable();
+ if( mpAnimation )
+ aAnimation = *mpAnimation;
+
+ return aAnimation;
+}
+
+const BitmapEx& ImpGraphic::getBitmapExRef() const
+{
+ ensureAvailable();
+ return maBitmapEx;
+}
+
+const GDIMetaFile& ImpGraphic::getGDIMetaFile() const
+{
+ ensureAvailable();
+ if (!maMetaFile.GetActionSize()
+ && maVectorGraphicData
+ && (VectorGraphicDataType::Emf == maVectorGraphicData->getType()
+ || VectorGraphicDataType::Wmf == maVectorGraphicData->getType()))
+ {
+ // If we have a Emf/Wmf VectorGraphic object, we
+ // need a way to get the Metafile data out of the primitive
+ // representation. Use a strict virtual hook (MetafileAccessor)
+ // to access the MetafilePrimitive2D directly. Also see comments in
+ // XEmfParser about this.
+ const std::deque< css::uno::Reference< css::graphic::XPrimitive2D > > aSequence(maVectorGraphicData->getPrimitive2DSequence());
+
+ if (1 == aSequence.size())
+ {
+ // try to cast to MetafileAccessor implementation
+ const css::uno::Reference< css::graphic::XPrimitive2D > xReference(aSequence[0]);
+ auto pUnoPrimitive = static_cast< const drawinglayer::primitive2d::UnoPrimitive2D* >(xReference.get());
+ if (pUnoPrimitive)
+ {
+ const MetafileAccessor* pMetafileAccessor = dynamic_cast< const MetafileAccessor* >(pUnoPrimitive->getBasePrimitive2D().get());
+
+ if (pMetafileAccessor)
+ {
+ // it is a MetafileAccessor implementation, get Metafile
+ pMetafileAccessor->accessMetafile(const_cast< ImpGraphic* >(this)->maMetaFile);
+ }
+ }
+ }
+ }
+
+ if (GraphicType::Bitmap == meType && !maMetaFile.GetActionSize())
+ {
+ // #i119735#
+ // Use the local maMetaFile as container for a metafile-representation
+ // of the bitmap graphic. This will be done only once, thus be buffered.
+ // I checked all usages of maMetaFile, it is only used when type is not
+ // GraphicType::Bitmap. In operator= it will get copied, thus buffering will
+ // survive copying (change this if not wanted)
+ ImpGraphic* pThat = const_cast< ImpGraphic* >(this);
+
+ if(maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // use maBitmapEx as local buffer for rendered svg
+ pThat->maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ // #123983# directly create a metafile with the same PrefSize and PrefMapMode
+ // the bitmap has, this will be an always correct metafile
+ if(maBitmapEx.IsAlpha())
+ {
+ pThat->maMetaFile.AddAction(new MetaBmpExScaleAction(Point(), maBitmapEx.GetPrefSize(), maBitmapEx));
+ }
+ else
+ {
+ pThat->maMetaFile.AddAction(new MetaBmpScaleAction(Point(), maBitmapEx.GetPrefSize(), maBitmapEx.GetBitmap()));
+ }
+
+ pThat->maMetaFile.Stop();
+ pThat->maMetaFile.WindStart();
+ pThat->maMetaFile.SetPrefSize(maBitmapEx.GetPrefSize());
+ pThat->maMetaFile.SetPrefMapMode(maBitmapEx.GetPrefMapMode());
+ }
+
+ return maMetaFile;
+}
+
+Size ImpGraphic::getSizePixel() const
+{
+ Size aSize;
+
+ if (isSwappedOut())
+ aSize = maSwapInfo.maSizePixel;
+ else
+ aSize = getBitmapEx(GraphicConversionParameters()).GetSizePixel();
+
+ return aSize;
+}
+
+Size ImpGraphic::getPrefSize() const
+{
+ Size aSize;
+
+ if (isSwappedOut())
+ {
+ aSize = maSwapInfo.maPrefSize;
+ }
+ else
+ {
+ switch (meType)
+ {
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ if (!maExPrefSize.getWidth() || !maExPrefSize.getHeight())
+ {
+ // svg not yet buffered in maBitmapEx, return size derived from range
+ const basegfx::B2DRange& rRange = maVectorGraphicData->getRange();
+
+ aSize = Size(basegfx::fround(rRange.getWidth()), basegfx::fround(rRange.getHeight()));
+ }
+ else
+ {
+ aSize = maExPrefSize;
+ }
+ }
+ else
+ {
+ aSize = maBitmapEx.GetPrefSize();
+
+ if( !aSize.Width() || !aSize.Height() )
+ {
+ aSize = maBitmapEx.GetSizePixel();
+ }
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ aSize = maMetaFile.GetPrefSize();
+ }
+ break;
+
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+ }
+ }
+
+ return aSize;
+}
+
+void ImpGraphic::setValuesForPrefSize(const Size& rPrefSize)
+{
+ switch (meType)
+ {
+ case GraphicType::Bitmap:
+ {
+ // used when importing a writer FlyFrame with SVG as graphic, added conversion
+ // to allow setting the PrefSize at the BitmapEx to hold it
+ if (maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ maExPrefSize = rPrefSize;
+ }
+
+ // #108077# Push through pref size to animation object,
+ // will be lost on copy otherwise
+ if (isAnimated())
+ {
+ const_cast< BitmapEx& >(mpAnimation->GetBitmapEx()).SetPrefSize(rPrefSize);
+ }
+
+ if (!maExPrefSize.getWidth() || !maExPrefSize.getHeight())
+ {
+ maBitmapEx.SetPrefSize(rPrefSize);
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ if (isSupportedGraphic())
+ maMetaFile.SetPrefSize(rPrefSize);
+ }
+ break;
+
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+ }
+}
+
+void ImpGraphic::setPrefSize(const Size& rPrefSize)
+{
+ ensureAvailable();
+ setValuesForPrefSize(rPrefSize);
+}
+
+MapMode ImpGraphic::getPrefMapMode() const
+{
+ MapMode aMapMode;
+
+ if (isSwappedOut())
+ {
+ aMapMode = maSwapInfo.maPrefMapMode;
+ }
+ else
+ {
+ switch (meType)
+ {
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // svg not yet buffered in maBitmapEx, return default PrefMapMode
+ aMapMode = MapMode(MapUnit::Map100thMM);
+ }
+ else
+ {
+ const Size aSize(maBitmapEx.GetPrefSize());
+
+ if (aSize.Width() && aSize.Height())
+ aMapMode = maBitmapEx.GetPrefMapMode();
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ return maMetaFile.GetPrefMapMode();
+ }
+ break;
+
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+ }
+ }
+
+ return aMapMode;
+}
+
+void ImpGraphic::setValuesForPrefMapMod(const MapMode& rPrefMapMode)
+{
+ switch (meType)
+ {
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData)
+ {
+ // ignore for Vector Graphic Data. If this is really used (except the grfcache)
+ // it can be extended by using maBitmapEx as buffer for getVectorGraphicReplacement()
+ }
+ else
+ {
+ // #108077# Push through pref mapmode to animation object,
+ // will be lost on copy otherwise
+ if (isAnimated())
+ {
+ const_cast<BitmapEx&>(mpAnimation->GetBitmapEx()).SetPrefMapMode(rPrefMapMode);
+ }
+
+ maBitmapEx.SetPrefMapMode(rPrefMapMode);
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ maMetaFile.SetPrefMapMode(rPrefMapMode);
+ }
+ break;
+
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+ }
+}
+
+void ImpGraphic::setPrefMapMode(const MapMode& rPrefMapMode)
+{
+ ensureAvailable();
+ setValuesForPrefMapMod(rPrefMapMode);
+}
+
+sal_uLong ImpGraphic::getSizeBytes() const
+{
+ if (mnSizeBytes > 0)
+ return mnSizeBytes;
+
+ if (mbPrepared)
+ ensureAvailable();
+
+ switch (meType)
+ {
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData)
+ {
+ std::pair<VectorGraphicData::State, size_t> aPair(maVectorGraphicData->getSizeBytes());
+ if (VectorGraphicData::State::UNPARSED == aPair.first)
+ {
+ return aPair.second; // don't cache it until Vector Graphic Data is parsed
+ }
+ mnSizeBytes = aPair.second;
+ }
+ else
+ {
+ mnSizeBytes = mpAnimation ? mpAnimation->GetSizeBytes() : maBitmapEx.GetSizeBytes();
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ mnSizeBytes = maMetaFile.GetSizeBytes();
+ }
+ break;
+
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+ }
+
+ return mnSizeBytes;
+}
+
+void ImpGraphic::draw(OutputDevice& rOutDev, const Point& rDestPt) const
+{
+ ensureAvailable();
+
+ if (isSwappedOut())
+ return;
+
+ switch (meType)
+ {
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // use maBitmapEx as local buffer for rendered svg
+ const_cast<ImpGraphic*>(this)->maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ if (mpAnimation)
+ {
+ mpAnimation->Draw(rOutDev, rDestPt);
+ }
+ else
+ {
+ maBitmapEx.Draw(&rOutDev, rDestPt);
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ draw(rOutDev, rDestPt, maMetaFile.GetPrefSize());
+ }
+ break;
+
+ case GraphicType::Default:
+ case GraphicType::NONE:
+ break;
+ }
+}
+
+void ImpGraphic::draw(OutputDevice& rOutDev,
+ const Point& rDestPt, const Size& rDestSize) const
+{
+ ensureAvailable();
+
+ if (isSwappedOut())
+ return;
+
+ switch (meType)
+ {
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // use maBitmapEx as local buffer for rendered svg
+ const_cast<ImpGraphic*>(this)->maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ if (mpAnimation)
+ {
+ mpAnimation->Draw(rOutDev, rDestPt, rDestSize);
+ }
+ else
+ {
+ maBitmapEx.Draw(&rOutDev, rDestPt, rDestSize);
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ const_cast<ImpGraphic*>(this)->maMetaFile.WindStart();
+ const_cast<ImpGraphic*>(this)->maMetaFile.Play(rOutDev, rDestPt, rDestSize);
+ const_cast<ImpGraphic*>(this)->maMetaFile.WindStart();
+ }
+ break;
+
+ case GraphicType::Default:
+ case GraphicType::NONE:
+ break;
+ }
+}
+
+void ImpGraphic::startAnimation(OutputDevice& rOutDev, const Point& rDestPt,
+ const Size& rDestSize, tools::Long nExtraData,
+ OutputDevice* pFirstFrameOutDev )
+{
+ ensureAvailable();
+
+ if( isSupportedGraphic() && !isSwappedOut() && mpAnimation )
+ mpAnimation->Start(rOutDev, rDestPt, rDestSize, nExtraData, pFirstFrameOutDev);
+}
+
+void ImpGraphic::stopAnimation( const OutputDevice* pOutDev, tools::Long nExtraData )
+{
+ ensureAvailable();
+
+ if( isSupportedGraphic() && !isSwappedOut() && mpAnimation )
+ mpAnimation->Stop( pOutDev, nExtraData );
+}
+
+void ImpGraphic::setAnimationNotifyHdl( const Link<Animation*,void>& rLink )
+{
+ ensureAvailable();
+
+ if( mpAnimation )
+ mpAnimation->SetNotifyHdl( rLink );
+}
+
+Link<Animation*,void> ImpGraphic::getAnimationNotifyHdl() const
+{
+ Link<Animation*,void> aLink;
+
+ ensureAvailable();
+
+ if( mpAnimation )
+ aLink = mpAnimation->GetNotifyHdl();
+
+ return aLink;
+}
+
+sal_uInt32 ImpGraphic::getAnimationLoopCount() const
+{
+ if (mbSwapOut)
+ return maSwapInfo.mnAnimationLoopCount;
+
+ return mpAnimation ? mpAnimation->GetLoopCount() : 0;
+}
+
+void ImpGraphic::setContext( const std::shared_ptr<GraphicReader>& pReader )
+{
+ mpContext = pReader;
+ mbDummyContext = false;
+}
+
+bool ImpGraphic::swapInContent(SvStream& rStream)
+{
+ bool bRet = false;
+
+ sal_uInt32 nId;
+ sal_Int32 nType;
+ sal_Int32 nLength;
+
+ rStream.ReadUInt32(nId);
+
+ // check version
+ if (SWAP_FORMAT_ID != nId)
+ {
+ SAL_WARN("vcl", "Incompatible swap file!");
+ return false;
+ }
+
+ rStream.ReadInt32(nType);
+ rStream.ReadInt32(nLength);
+
+ meType = static_cast<GraphicType>(nType);
+
+ if (meType == GraphicType::NONE || meType == GraphicType::Default)
+ {
+ return true;
+ }
+ else
+ {
+ bRet = swapInGraphic(rStream);
+ }
+
+ return bRet;
+}
+
+bool ImpGraphic::swapOutGraphic(SvStream& rStream)
+{
+ if (rStream.GetError())
+ return false;
+
+ ensureAvailable();
+
+ if (isSwappedOut())
+ {
+ rStream.SetError(SVSTREAM_GENERALERROR);
+ return false;
+ }
+
+ switch (meType)
+ {
+ case GraphicType::GdiMetafile:
+ {
+ if(!rStream.GetError())
+ {
+ SvmWriter aWriter(rStream);
+ aWriter.Write(maMetaFile);
+ }
+ }
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData)
+ {
+ rStream.WriteInt32(sal_Int32(GraphicContentType::Vector));
+ // stream out Vector Graphic defining data (length, byte array and evtl. path)
+ // this is used e.g. in swapping out graphic data and in transporting it over UNO API
+ // as sequence of bytes, but AFAIK not written anywhere to any kind of file, so it should be
+ // no problem to extend it; only used at runtime
+ switch (maVectorGraphicData->getType())
+ {
+ case VectorGraphicDataType::Wmf:
+ {
+ rStream.WriteUInt32(constWmfMagic);
+ break;
+ }
+ case VectorGraphicDataType::Emf:
+ {
+ rStream.WriteUInt32(constEmfMagic);
+ break;
+ }
+ case VectorGraphicDataType::Svg:
+ {
+ rStream.WriteUInt32(constSvgMagic);
+ break;
+ }
+ case VectorGraphicDataType::Pdf:
+ {
+ rStream.WriteUInt32(constPdfMagic);
+ break;
+ }
+ }
+
+ rStream.WriteUInt32(maVectorGraphicData->getBinaryDataContainer().getSize());
+
+ rStream.WriteBytes(
+ maVectorGraphicData->getBinaryDataContainer().getData(),
+ maVectorGraphicData->getBinaryDataContainer().getSize());
+ }
+ else if (isAnimated())
+ {
+ rStream.WriteInt32(sal_Int32(GraphicContentType::Animation));
+ WriteAnimation(rStream, *mpAnimation);
+ }
+ else
+ {
+ rStream.WriteInt32(sal_Int32(GraphicContentType::Bitmap));
+ WriteDIBBitmapEx(maBitmapEx, rStream);
+ }
+ }
+ break;
+
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+ }
+
+ return true;
+}
+
+bool ImpGraphic::swapOutContent(SvStream& rStream)
+{
+ ensureAvailable();
+
+ bool bRet = false;
+
+ if (meType == GraphicType::NONE || meType == GraphicType::Default || isSwappedOut())
+ return false;
+
+ sal_uLong nDataFieldPos;
+
+ // Write the SWAP ID
+ rStream.WriteUInt32(SWAP_FORMAT_ID);
+
+ rStream.WriteInt32(static_cast<sal_Int32>(meType));
+
+ // data size is updated later
+ nDataFieldPos = rStream.Tell();
+ rStream.WriteInt32(0);
+
+ // write data block
+ const sal_uInt64 nDataStart = rStream.Tell();
+
+ swapOutGraphic(rStream);
+
+ if (!rStream.GetError())
+ {
+ // Write the written length th the header
+ const sal_uInt64 nCurrentPosition = rStream.Tell();
+ rStream.Seek(nDataFieldPos);
+ rStream.WriteInt32(nCurrentPosition - nDataStart);
+ rStream.Seek(nCurrentPosition);
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool ImpGraphic::swapOut()
+{
+ if (isSwappedOut())
+ return false;
+
+ bool bResult = false;
+
+ sal_Int64 nByteSize = getSizeBytes();
+
+ // We have GfxLink so we have the source available
+ if (mpGfxLink && mpGfxLink->IsNative())
+ {
+ createSwapInfo();
+
+ clearGraphics();
+
+ // reset the swap file
+ mpSwapFile.reset();
+
+ // mark as swapped out
+ mbSwapOut = true;
+
+ bResult = true;
+ }
+ else
+ {
+ // Create a temp filename for the swap file
+ utl::TempFile aTempFile;
+ const INetURLObject aTempFileURL(aTempFile.GetURL());
+
+ // Create a swap file
+ auto pSwapFile = o3tl::make_shared<ImpSwapFile>(aTempFileURL, getOriginURL());
+
+ // Open a stream to write the swap file to
+ {
+ std::unique_ptr<SvStream> xOutputStream = pSwapFile->openOutputStream();
+
+ if (!xOutputStream)
+ return false;
+
+ // Write to stream
+ xOutputStream->SetVersion(SOFFICE_FILEFORMAT_50);
+ xOutputStream->SetCompressMode(SvStreamCompressFlags::NATIVE);
+ xOutputStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE);
+
+ if (!xOutputStream->GetError() && swapOutContent(*xOutputStream))
+ {
+ xOutputStream->FlushBuffer();
+ bResult = !xOutputStream->GetError();
+ }
+ }
+
+ // Check if writing was successful
+ if (bResult)
+ {
+ // We have swapped out, so can clean memory and prepare swap info
+ createSwapInfo();
+ clearGraphics();
+
+ mpSwapFile = std::move(pSwapFile);
+ mbSwapOut = true;
+ }
+ }
+
+ if (bResult)
+ {
+ // Signal to manager that we have swapped out
+ vcl::graphic::Manager::get().swappedOut(this, nByteSize);
+ }
+
+ return bResult;
+}
+
+bool ImpGraphic::ensureAvailable() const
+{
+ auto pThis = const_cast<ImpGraphic*>(this);
+
+ bool bResult = true;
+
+ if (isSwappedOut())
+ bResult = pThis->swapIn();
+
+ pThis->maLastUsed = std::chrono::high_resolution_clock::now();
+ return bResult;
+}
+
+void ImpGraphic::updateFromLoadedGraphic(const ImpGraphic* pGraphic)
+{
+ if (mbPrepared)
+ {
+ GraphicExternalLink aLink = maGraphicExternalLink;
+ Size aPrefSize = maSwapInfo.maPrefSize;
+ MapMode aPrefMapMode = maSwapInfo.maPrefMapMode;
+ *this = *pGraphic;
+ if (aPrefSize.getWidth() && aPrefSize.getHeight() && aPrefMapMode == getPrefMapMode())
+ {
+ // Use custom preferred size if it was set when the graphic was still unloaded.
+ // Only set the size in case the unloaded and loaded unit matches.
+ setPrefSize(aPrefSize);
+ }
+ maGraphicExternalLink = aLink;
+ }
+ else
+ {
+ // Move over only graphic content
+ mpAnimation.reset();
+ if (pGraphic->mpAnimation)
+ {
+ mpAnimation = std::make_unique<Animation>(*pGraphic->mpAnimation);
+ maBitmapEx = mpAnimation->GetBitmapEx();
+ }
+ else
+ {
+ maBitmapEx = pGraphic->maBitmapEx;
+ }
+
+ maMetaFile = pGraphic->maMetaFile;
+ maVectorGraphicData = pGraphic->maVectorGraphicData;
+
+ // Set to 0, to force recalculation
+ mnSizeBytes = 0;
+ mnChecksum = 0;
+
+ restoreFromSwapInfo();
+
+ mbSwapOut = false;
+ }
+}
+
+void ImpGraphic::restoreFromSwapInfo()
+{
+ setValuesForPrefMapMod(maSwapInfo.maPrefMapMode);
+ setValuesForPrefSize(maSwapInfo.maPrefSize);
+
+ if (maVectorGraphicData)
+ {
+ maVectorGraphicData->setPageIndex(maSwapInfo.mnPageIndex);
+ }
+}
+
+namespace
+{
+
+std::optional<VectorGraphicDataType> lclConvertToVectorGraphicType(GfxLink const & rLink)
+{
+ switch(rLink.GetType())
+ {
+ case GfxLinkType::NativePdf:
+ return VectorGraphicDataType::Pdf;
+
+ case GfxLinkType::NativeWmf:
+ if (rLink.IsEMF())
+ return VectorGraphicDataType::Emf;
+ else
+ return VectorGraphicDataType::Wmf;
+
+ case GfxLinkType::NativeSvg:
+ return VectorGraphicDataType::Svg;
+
+ default:
+ break;
+ }
+ return std::optional<VectorGraphicDataType>();
+}
+
+} // end namespace
+
+bool ImpGraphic::swapIn()
+{
+ if (!isSwappedOut())
+ return false;
+
+ bool bReturn = false;
+
+ if (mbPrepared)
+ {
+ Graphic aGraphic;
+ if (!mpGfxLink->LoadNative(aGraphic))
+ return false;
+
+ updateFromLoadedGraphic(aGraphic.ImplGetImpGraphic());
+
+ maLastUsed = std::chrono::high_resolution_clock::now();
+ bReturn = true;
+ }
+ else if (mpGfxLink && mpGfxLink->IsNative())
+ {
+ std::optional<VectorGraphicDataType> oType = lclConvertToVectorGraphicType(*mpGfxLink);
+ if (oType)
+ {
+ maVectorGraphicData = vcl::loadVectorGraphic(mpGfxLink->getDataContainer(), *oType);
+
+ // Set to 0, to force recalculation
+ mnSizeBytes = 0;
+ mnChecksum = 0;
+
+ restoreFromSwapInfo();
+
+ mbSwapOut = false;
+ }
+ else
+ {
+ Graphic aGraphic;
+ if (!mpGfxLink->LoadNative(aGraphic))
+ return false;
+
+ ImpGraphic* pImpGraphic = aGraphic.ImplGetImpGraphic();
+ if (meType != pImpGraphic->meType)
+ return false;
+
+ updateFromLoadedGraphic(pImpGraphic);
+ }
+
+ maLastUsed = std::chrono::high_resolution_clock::now();
+ bReturn = true;
+ }
+ else
+ {
+ OUString aSwapURL;
+
+ if (mpSwapFile)
+ aSwapURL = mpSwapFile->getSwapURL().GetMainURL(INetURLObject::DecodeMechanism::NONE);
+
+ if (!aSwapURL.isEmpty())
+ {
+ std::unique_ptr<SvStream> xStream;
+ try
+ {
+ xStream = ::utl::UcbStreamHelper::CreateStream(aSwapURL, StreamMode::READWRITE | StreamMode::SHARE_DENYWRITE);
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ if (xStream)
+ {
+ xStream->SetVersion(SOFFICE_FILEFORMAT_50);
+ xStream->SetCompressMode(SvStreamCompressFlags::NATIVE);
+ xStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE);
+
+ bReturn = swapInFromStream(*xStream);
+
+ xStream.reset();
+
+ restoreFromSwapInfo();
+
+ if (mpSwapFile)
+ setOriginURL(mpSwapFile->getOriginURL());
+
+ mpSwapFile.reset();
+ }
+ }
+ }
+
+ if (bReturn)
+ {
+ vcl::graphic::Manager::get().swappedIn(this, getSizeBytes());
+ }
+
+ return bReturn;
+}
+
+bool ImpGraphic::swapInFromStream(SvStream& rStream)
+{
+ bool bRet = false;
+
+ if (rStream.GetError())
+ return false;
+
+ clearGraphics();
+ mnSizeBytes = 0;
+ mnChecksum = 0;
+
+ bRet = swapInContent(rStream);
+
+ if (!bRet)
+ {
+ //throw away swapfile, etc.
+ clear();
+ }
+
+ mbSwapOut = false;
+
+ return bRet;
+}
+
+bool ImpGraphic::swapInGraphic(SvStream& rStream)
+{
+ bool bReturn = false;
+
+ if (rStream.GetError())
+ return bReturn;
+
+ if (meType == GraphicType::Bitmap)
+ {
+ sal_Int32 nContentType = -1;
+ rStream.ReadInt32(nContentType);
+ if (nContentType < 0)
+ return false;
+
+ auto eContentType = static_cast<GraphicContentType>(nContentType);
+
+ switch (eContentType)
+ {
+ case GraphicContentType::Bitmap:
+ {
+ BitmapEx aBitmapEx;
+ ReadDIBBitmapEx(aBitmapEx, rStream);
+ if (!rStream.GetError())
+ {
+ maBitmapEx = aBitmapEx;
+ bReturn = true;
+ }
+ }
+ break;
+
+ case GraphicContentType::Animation:
+ {
+ auto pAnimation = std::make_unique<Animation>();
+ ReadAnimation(rStream, *pAnimation);
+ if (!rStream.GetError())
+ {
+ mpAnimation = std::move(pAnimation);
+ maBitmapEx = mpAnimation->GetBitmapEx();
+ bReturn = true;
+ }
+ }
+ break;
+
+ case GraphicContentType::Vector:
+ {
+ // try to stream in Svg defining data (length, byte array and evtl. path)
+ // See below (operator<<) for more information
+ sal_uInt32 nMagic;
+ rStream.ReadUInt32(nMagic);
+
+ if (constSvgMagic == nMagic || constWmfMagic == nMagic || constEmfMagic == nMagic || constPdfMagic == nMagic)
+ {
+ sal_uInt32 nVectorGraphicDataSize(0);
+ rStream.ReadUInt32(nVectorGraphicDataSize);
+
+ if (nVectorGraphicDataSize)
+ {
+ auto rData = std::make_unique<std::vector<sal_uInt8>>(nVectorGraphicDataSize);
+ rStream.ReadBytes(rData->data(), nVectorGraphicDataSize);
+ BinaryDataContainer aDataContainer(std::move(rData));
+
+ if (rStream.GetError())
+ return false;
+
+ VectorGraphicDataType aDataType;
+
+ switch (nMagic)
+ {
+ case constSvgMagic:
+ aDataType = VectorGraphicDataType::Svg;
+ break;
+ case constWmfMagic:
+ aDataType = VectorGraphicDataType::Wmf;
+ break;
+ case constEmfMagic:
+ aDataType = VectorGraphicDataType::Emf;
+ break;
+ case constPdfMagic:
+ aDataType = VectorGraphicDataType::Pdf;
+ break;
+ default:
+ return false;
+ }
+
+ auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aDataContainer, aDataType);
+
+ if (!rStream.GetError())
+ {
+ maVectorGraphicData = aVectorGraphicDataPtr;
+ bReturn = true;
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ else if (meType == GraphicType::GdiMetafile)
+ {
+ GDIMetaFile aMetaFile;
+ SvmReader aReader(rStream);
+ aReader.Read(aMetaFile);
+ if (!rStream.GetError())
+ {
+ maMetaFile = aMetaFile;
+ bReturn = true;
+ }
+ }
+ return bReturn;
+}
+
+void ImpGraphic::setGfxLink(const std::shared_ptr<GfxLink>& rGfxLink)
+{
+ ensureAvailable();
+
+ mpGfxLink = rGfxLink;
+}
+
+const std::shared_ptr<GfxLink> & ImpGraphic::getSharedGfxLink() const
+{
+ return mpGfxLink;
+}
+
+GfxLink ImpGraphic::getGfxLink() const
+{
+ ensureAvailable();
+
+ return( mpGfxLink ? *mpGfxLink : GfxLink() );
+}
+
+bool ImpGraphic::isGfxLink() const
+{
+ return ( bool(mpGfxLink) );
+}
+
+BitmapChecksum ImpGraphic::getChecksum() const
+{
+ if (mnChecksum != 0)
+ return mnChecksum;
+
+ ensureAvailable();
+
+ switch (meType)
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData)
+ mnChecksum = maVectorGraphicData->GetChecksum();
+ else if (mpAnimation)
+ mnChecksum = mpAnimation->GetChecksum();
+ else
+ mnChecksum = maBitmapEx.GetChecksum();
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ mnChecksum = SvmWriter::GetChecksum(maMetaFile);
+ }
+ break;
+ }
+ return mnChecksum;
+}
+
+sal_Int32 ImpGraphic::getPageNumber() const
+{
+ if (isSwappedOut())
+ return maSwapInfo.mnPageIndex;
+
+ if (maVectorGraphicData)
+ return maVectorGraphicData->getPageIndex();
+ return -1;
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/jobset.cxx b/vcl/source/gdi/jobset.cxx
new file mode 100644
index 000000000..04ba708ef
--- /dev/null
+++ b/vcl/source/gdi/jobset.cxx
@@ -0,0 +1,410 @@
+/* -*- 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 <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <tools/solar.h>
+#include <tools/stream.hxx>
+#include <vcl/jobset.hxx>
+#include <jobset.h>
+#include <memory>
+
+#define JOBSET_FILE364_SYSTEM (sal_uInt16(0xFFFF))
+#define JOBSET_FILE605_SYSTEM (sal_uInt16(0xFFFE))
+
+namespace {
+
+struct ImplOldJobSetupData
+{
+ char cPrinterName[64];
+ char cDeviceName[32];
+ char cPortName[32];
+ char cDriverName[32];
+};
+
+struct Impl364JobSetupData
+{
+ SVBT16 nSize;
+ SVBT16 nSystem;
+ SVBT32 nDriverDataLen;
+ SVBT16 nOrientation;
+ SVBT16 nPaperBin;
+ SVBT16 nPaperFormat;
+ SVBT32 nPaperWidth;
+ SVBT32 nPaperHeight;
+};
+
+}
+
+ImplJobSetup::ImplJobSetup()
+{
+ mnSystem = 0;
+ meOrientation = Orientation::Portrait;
+ meDuplexMode = DuplexMode::Unknown;
+ mnPaperBin = 0;
+ mePaperFormat = PAPER_USER;
+ mnPaperWidth = 0;
+ mnPaperHeight = 0;
+ mnDriverDataLen = 0;
+ mpDriverData = nullptr;
+ mbPapersizeFromSetup = false;
+ meSetupMode = PrinterSetupMode::DocumentGlobal;
+}
+
+ImplJobSetup::ImplJobSetup( const ImplJobSetup& rJobSetup ) :
+ mnSystem( rJobSetup.GetSystem() ),
+ maPrinterName( rJobSetup.GetPrinterName() ),
+ maDriver( rJobSetup.GetDriver() ),
+ meOrientation( rJobSetup.GetOrientation() ),
+ meDuplexMode( rJobSetup.GetDuplexMode() ),
+ mnPaperBin( rJobSetup.GetPaperBin() ),
+ mePaperFormat( rJobSetup.GetPaperFormat() ),
+ mnPaperWidth( rJobSetup.GetPaperWidth() ),
+ mnPaperHeight( rJobSetup.GetPaperHeight() ),
+ mnDriverDataLen( rJobSetup.GetDriverDataLen() ),
+ mbPapersizeFromSetup( rJobSetup.GetPapersizeFromSetup() ),
+ meSetupMode( rJobSetup.GetPrinterSetupMode() ),
+ maValueMap( rJobSetup.GetValueMap() )
+ {
+ if ( rJobSetup.GetDriverData() )
+ {
+ mpDriverData = static_cast<sal_uInt8*>(std::malloc( mnDriverDataLen ));
+ memcpy( mpDriverData, rJobSetup.GetDriverData(), mnDriverDataLen );
+ }
+ else
+ mpDriverData = nullptr;
+}
+
+ImplJobSetup::~ImplJobSetup()
+{
+ std::free( mpDriverData );
+}
+
+void ImplJobSetup::SetSystem(sal_uInt16 nSystem)
+{
+ mnSystem = nSystem;
+}
+
+void ImplJobSetup::SetPrinterName(const OUString& rPrinterName)
+{
+ maPrinterName = rPrinterName;
+}
+
+void ImplJobSetup::SetDriver(const OUString& rDriver)
+{
+ maDriver = rDriver;
+}
+
+void ImplJobSetup::SetOrientation(Orientation eOrientation)
+{
+ meOrientation = eOrientation;
+}
+
+void ImplJobSetup::SetDuplexMode(DuplexMode eDuplexMode)
+{
+ meDuplexMode = eDuplexMode;
+}
+
+void ImplJobSetup::SetPaperBin(sal_uInt16 nPaperBin)
+{
+ mnPaperBin = nPaperBin;
+}
+
+void ImplJobSetup::SetPaperFormat(Paper ePaperFormat)
+{
+ mePaperFormat = ePaperFormat;
+}
+
+void ImplJobSetup::SetPaperWidth(tools::Long nPaperWidth)
+{
+ mnPaperWidth = nPaperWidth;
+}
+
+void ImplJobSetup::SetPaperHeight(tools::Long nPaperHeight)
+{
+ mnPaperHeight = nPaperHeight;
+}
+
+void ImplJobSetup::SetDriverDataLen(sal_uInt32 nDriverDataLen)
+{
+ mnDriverDataLen = nDriverDataLen;
+}
+
+void ImplJobSetup::SetDriverData(sal_uInt8* pDriverData)
+{
+ mpDriverData = pDriverData;
+}
+
+void ImplJobSetup::SetPapersizeFromSetup(bool bPapersizeFromSetup)
+{
+ mbPapersizeFromSetup = bPapersizeFromSetup;
+}
+
+void ImplJobSetup::SetPrinterSetupMode(PrinterSetupMode eMode)
+{
+ meSetupMode = eMode;
+}
+
+void ImplJobSetup::SetValueMap( const OUString& rKey, const OUString& rValue )
+{
+ maValueMap [ rKey ] = rValue;
+}
+
+JobSetup& JobSetup::operator=( const JobSetup& ) = default;
+
+JobSetup& JobSetup::operator=( JobSetup&& ) = default;
+
+bool ImplJobSetup::operator==( const ImplJobSetup& rImplJobSetup ) const
+{
+ return mnSystem == rImplJobSetup.mnSystem &&
+ maPrinterName == rImplJobSetup.maPrinterName &&
+ maDriver == rImplJobSetup.maDriver &&
+ meOrientation == rImplJobSetup.meOrientation &&
+ meDuplexMode == rImplJobSetup.meDuplexMode &&
+ mnPaperBin == rImplJobSetup.mnPaperBin &&
+ mePaperFormat == rImplJobSetup.mePaperFormat &&
+ mnPaperWidth == rImplJobSetup.mnPaperWidth &&
+ mnPaperHeight == rImplJobSetup.mnPaperHeight &&
+ mbPapersizeFromSetup == rImplJobSetup.mbPapersizeFromSetup &&
+ mnDriverDataLen == rImplJobSetup.mnDriverDataLen &&
+ maValueMap == rImplJobSetup.maValueMap &&
+ memcmp( mpDriverData, rImplJobSetup.mpDriverData, mnDriverDataLen ) == 0;
+}
+
+namespace
+{
+ JobSetup::ImplType& GetGlobalDefault()
+ {
+ static JobSetup::ImplType gDefault;
+ return gDefault;
+ }
+}
+
+JobSetup::JobSetup() : mpData(GetGlobalDefault())
+{
+}
+
+JobSetup::JobSetup( const JobSetup& ) = default;
+
+JobSetup::~JobSetup() = default;
+
+bool JobSetup::operator==( const JobSetup& rJobSetup ) const
+{
+ return mpData == rJobSetup.mpData;
+}
+
+const ImplJobSetup& JobSetup::ImplGetConstData() const
+{
+ return *mpData;
+}
+
+ImplJobSetup& JobSetup::ImplGetData()
+{
+ return *mpData;
+}
+
+OUString const & JobSetup::GetPrinterName() const
+{
+ return mpData->GetPrinterName();
+}
+
+bool JobSetup::IsDefault() const
+{
+ return mpData.same_object(GetGlobalDefault());
+}
+
+SvStream& ReadJobSetup( SvStream& rIStream, JobSetup& rJobSetup )
+{
+ {
+ sal_uInt16 nLen = 0;
+ rIStream.ReadUInt16( nLen );
+ if (nLen <= 4)
+ return rIStream;
+
+ sal_uInt16 nSystem = 0;
+ rIStream.ReadUInt16( nSystem );
+ size_t nRead = nLen - sizeof(nLen) - sizeof(nSystem);
+ if (nRead > rIStream.remainingSize())
+ {
+ SAL_WARN("vcl", "Parsing error: " << rIStream.remainingSize() <<
+ " max possible entries, but " << nRead << " claimed, truncating");
+ return rIStream;
+ }
+ sal_uInt64 const nFirstPos = rIStream.Tell();
+ std::unique_ptr<char[]> pTempBuf(new char[nRead]);
+ nRead = rIStream.ReadBytes(pTempBuf.get(), nRead);
+ if (nRead >= sizeof(ImplOldJobSetupData))
+ {
+ ImplOldJobSetupData* pData = reinterpret_cast<ImplOldJobSetupData*>(pTempBuf.get());
+
+ rtl_TextEncoding aStreamEncoding = RTL_TEXTENCODING_UTF8;
+ if( nSystem == JOBSET_FILE364_SYSTEM )
+ aStreamEncoding = rIStream.GetStreamCharSet();
+
+ ImplJobSetup& rJobData = rJobSetup.ImplGetData();
+
+ pData->cPrinterName[SAL_N_ELEMENTS(pData->cPrinterName) - 1] = 0;
+ rJobData.SetPrinterName( OStringToOUString(pData->cPrinterName, aStreamEncoding) );
+ pData->cDriverName[SAL_N_ELEMENTS(pData->cDriverName) - 1] = 0;
+ rJobData.SetDriver( OStringToOUString(pData->cDriverName, aStreamEncoding) );
+
+ // Are these our new JobSetup files?
+ if ( nSystem == JOBSET_FILE364_SYSTEM ||
+ nSystem == JOBSET_FILE605_SYSTEM )
+ {
+ if (nRead < sizeof(ImplOldJobSetupData) + sizeof(Impl364JobSetupData))
+ {
+ SAL_WARN("vcl", "Parsing error: " << sizeof(ImplOldJobSetupData) + sizeof(Impl364JobSetupData) <<
+ " required, but " << nRead << " available");
+ return rIStream;
+ }
+
+ Impl364JobSetupData* pOldJobData = reinterpret_cast<Impl364JobSetupData*>(pTempBuf.get() + sizeof( ImplOldJobSetupData ));
+ sal_uInt16 nOldJobDataSize = SVBT16ToUInt16( pOldJobData->nSize );
+ rJobData.SetSystem( SVBT16ToUInt16( pOldJobData->nSystem ) );
+ rJobData.SetDriverDataLen( SVBT32ToUInt32( pOldJobData->nDriverDataLen ) );
+ rJobData.SetOrientation( static_cast<Orientation>(SVBT16ToUInt16( pOldJobData->nOrientation )) );
+ rJobData.SetDuplexMode( DuplexMode::Unknown );
+ rJobData.SetPaperBin( SVBT16ToUInt16( pOldJobData->nPaperBin ) );
+ sal_uInt16 nPaperFormat = SVBT16ToUInt16(pOldJobData->nPaperFormat);
+ if (nPaperFormat < NUM_PAPER_ENTRIES)
+ rJobData.SetPaperFormat(static_cast<Paper>(nPaperFormat));
+ else
+ {
+ SAL_WARN("vcl", "Parsing error: " << nPaperFormat <<
+ " paper format, but legal max is " << NUM_PAPER_ENTRIES);
+ }
+ rJobData.SetPaperWidth( static_cast<tools::Long>(SVBT32ToUInt32( pOldJobData->nPaperWidth )) );
+ rJobData.SetPaperHeight( static_cast<tools::Long>(SVBT32ToUInt32( pOldJobData->nPaperHeight )) );
+ if ( rJobData.GetDriverDataLen() )
+ {
+ const char* pDriverData = reinterpret_cast<const char*>(pOldJobData) + nOldJobDataSize;
+ const char* pDriverDataEnd = pDriverData + rJobData.GetDriverDataLen();
+ if (pDriverDataEnd > pTempBuf.get() + nRead)
+ {
+ SAL_WARN("vcl", "corrupted job setup");
+ }
+ else
+ {
+ sal_uInt8* pNewDriverData = static_cast<sal_uInt8*>(
+ std::malloc( rJobData.GetDriverDataLen() ));
+ memcpy( pNewDriverData, pDriverData, rJobData.GetDriverDataLen() );
+ rJobData.SetDriverData( pNewDriverData );
+ }
+ }
+ if( nSystem == JOBSET_FILE605_SYSTEM )
+ {
+ rIStream.Seek( nFirstPos + sizeof( ImplOldJobSetupData ) +
+ sizeof( Impl364JobSetupData ) + rJobData.GetDriverDataLen() );
+ while( rIStream.Tell() < nFirstPos + nRead )
+ {
+ OUString aKey = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStream, RTL_TEXTENCODING_UTF8);
+ OUString aValue = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStream, RTL_TEXTENCODING_UTF8);
+ if( aKey == "COMPAT_DUPLEX_MODE" )
+ {
+ if( aValue == "DuplexMode::Unknown" )
+ rJobData.SetDuplexMode( DuplexMode::Unknown );
+ else if( aValue == "DuplexMode::Off" )
+ rJobData.SetDuplexMode( DuplexMode::Off );
+ else if( aValue == "DuplexMode::ShortEdge" )
+ rJobData.SetDuplexMode( DuplexMode::ShortEdge );
+ else if( aValue == "DuplexMode::LongEdge" )
+ rJobData.SetDuplexMode( DuplexMode::LongEdge );
+ }
+ else
+ rJobData.SetValueMap(aKey, aValue);
+ }
+ SAL_WARN_IF( rIStream.Tell() != nFirstPos+nRead, "vcl", "corrupted job setup" );
+ // ensure correct stream position
+ rIStream.Seek(nFirstPos + nRead);
+ }
+ }
+ }
+ }
+
+ return rIStream;
+}
+
+SvStream& WriteJobSetup( SvStream& rOStream, const JobSetup& rJobSetup )
+{
+ {
+ sal_uInt16 nLen = 0;
+ if ( rJobSetup.IsDefault() )
+ rOStream.WriteUInt16( nLen );
+ else
+ {
+ const ImplJobSetup& rJobData = rJobSetup.ImplGetConstData();
+ Impl364JobSetupData aOldJobData;
+ sal_uInt16 nOldJobDataSize = sizeof( aOldJobData );
+ ShortToSVBT16( nOldJobDataSize, aOldJobData.nSize );
+ ShortToSVBT16( rJobData.GetSystem(), aOldJobData.nSystem );
+ UInt32ToSVBT32( rJobData.GetDriverDataLen(), aOldJobData.nDriverDataLen );
+ ShortToSVBT16( static_cast<sal_uInt16>(rJobData.GetOrientation()), aOldJobData.nOrientation );
+ ShortToSVBT16( rJobData.GetPaperBin(), aOldJobData.nPaperBin );
+ ShortToSVBT16( static_cast<sal_uInt16>(rJobData.GetPaperFormat()), aOldJobData.nPaperFormat );
+ UInt32ToSVBT32( static_cast<sal_uLong>(rJobData.GetPaperWidth()), aOldJobData.nPaperWidth );
+ UInt32ToSVBT32( static_cast<sal_uLong>(rJobData.GetPaperHeight()), aOldJobData.nPaperHeight );
+
+ ImplOldJobSetupData aOldData = {};
+ OString aPrnByteName(OUStringToOString(rJobData.GetPrinterName(), RTL_TEXTENCODING_UTF8));
+ strncpy(aOldData.cPrinterName, aPrnByteName.getStr(), SAL_N_ELEMENTS(aOldData.cPrinterName) - 1);
+ OString aDriverByteName(OUStringToOString(rJobData.GetDriver(), RTL_TEXTENCODING_UTF8));
+ strncpy(aOldData.cDriverName, aDriverByteName.getStr(), SAL_N_ELEMENTS(aOldData.cDriverName) - 1);
+ int nPos = rOStream.Tell();
+ rOStream.WriteUInt16( 0 );
+ rOStream.WriteUInt16( JOBSET_FILE605_SYSTEM );
+ rOStream.WriteBytes( &aOldData, sizeof( aOldData ) );
+ rOStream.WriteBytes( &aOldJobData, nOldJobDataSize );
+ rOStream.WriteBytes( rJobData.GetDriverData(), rJobData.GetDriverDataLen() );
+
+ const std::unordered_map< OUString, OUString >& rValueMap(
+ rJobData.GetValueMap());
+
+ for (auto const& value : rValueMap)
+ {
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, value.first, RTL_TEXTENCODING_UTF8);
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, value.second, RTL_TEXTENCODING_UTF8);
+ }
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "COMPAT_DUPLEX_MODE");
+ switch( rJobData.GetDuplexMode() )
+ {
+ case DuplexMode::Unknown:
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::Unknown");
+ break;
+ case DuplexMode::Off:
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::Off");
+ break;
+ case DuplexMode::ShortEdge:
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::ShortEdge");
+ break;
+ case DuplexMode::LongEdge:
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::LongEdge");
+ break;
+ }
+ nLen = sal::static_int_cast<sal_uInt16>(rOStream.Tell() - nPos);
+ rOStream.Seek( nPos );
+ rOStream.WriteUInt16( nLen );
+ rOStream.Seek( nPos + nLen );
+ }
+ }
+
+ return rOStream;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/lineinfo.cxx b/vcl/source/gdi/lineinfo.cxx
new file mode 100644
index 000000000..490b1d920
--- /dev/null
+++ b/vcl/source/gdi/lineinfo.cxx
@@ -0,0 +1,293 @@
+/* -*- 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/log.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <vcl/lineinfo.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+#include <numeric>
+
+
+ImplLineInfo::ImplLineInfo()
+ : mnWidth(0)
+ , mnDashLen(0)
+ , mnDotLen(0)
+ , mnDistance(0)
+ , meLineJoin(basegfx::B2DLineJoin::Round)
+ , meLineCap(css::drawing::LineCap_BUTT)
+ , meStyle(LineStyle::Solid)
+ , mnDashCount(0)
+ , mnDotCount(0)
+{
+}
+
+inline bool ImplLineInfo::operator==( const ImplLineInfo& rB ) const
+{
+ return(meStyle == rB.meStyle
+ && mnWidth == rB.mnWidth
+ && mnDashCount == rB.mnDashCount
+ && mnDashLen == rB.mnDashLen
+ && mnDotCount == rB.mnDotCount
+ && mnDotLen == rB.mnDotLen
+ && mnDistance == rB.mnDistance
+ && meLineJoin == rB.meLineJoin
+ && meLineCap == rB.meLineCap);
+}
+
+
+LineInfo::LineInfo( LineStyle eStyle, double nWidth )
+{
+ mpImplLineInfo->meStyle = eStyle;
+ mpImplLineInfo->mnWidth = nWidth;
+}
+
+LineInfo::LineInfo( const LineInfo& ) = default;
+
+LineInfo::LineInfo( LineInfo&& ) = default;
+
+LineInfo::~LineInfo() = default;
+
+LineInfo& LineInfo::operator=( const LineInfo& ) = default;
+
+LineInfo& LineInfo::operator=( LineInfo&& ) = default;
+
+bool LineInfo::operator==( const LineInfo& rLineInfo ) const
+{
+ return mpImplLineInfo == rLineInfo.mpImplLineInfo;
+}
+
+void LineInfo::SetStyle( LineStyle eStyle )
+{
+ mpImplLineInfo->meStyle = eStyle;
+}
+
+void LineInfo::SetWidth( double nWidth )
+{
+ mpImplLineInfo->mnWidth = nWidth;
+}
+
+void LineInfo::SetDashCount( sal_uInt16 nDashCount )
+{
+ mpImplLineInfo->mnDashCount = nDashCount;
+}
+
+void LineInfo::SetDashLen( double nDashLen )
+{
+ mpImplLineInfo->mnDashLen = nDashLen;
+}
+
+void LineInfo::SetDotCount( sal_uInt16 nDotCount )
+{
+ mpImplLineInfo->mnDotCount = nDotCount;
+}
+
+void LineInfo::SetDotLen( double nDotLen )
+{
+ mpImplLineInfo->mnDotLen = nDotLen;
+}
+
+void LineInfo::SetDistance( double nDistance )
+{
+ mpImplLineInfo->mnDistance = nDistance;
+}
+
+void LineInfo::SetLineJoin(basegfx::B2DLineJoin eLineJoin)
+{
+ mpImplLineInfo->meLineJoin = eLineJoin;
+}
+
+void LineInfo::SetLineCap(css::drawing::LineCap eLineCap)
+{
+ mpImplLineInfo->meLineCap = eLineCap;
+}
+
+bool LineInfo::IsDefault() const
+{
+ return( !mpImplLineInfo->mnWidth
+ && ( LineStyle::Solid == mpImplLineInfo->meStyle )
+ && ( css::drawing::LineCap_BUTT == mpImplLineInfo->meLineCap));
+}
+
+static void ReadLimitedDouble(SvStream& rIStm, double &fDest)
+{
+ double fTmp(0.0);
+ rIStm.ReadDouble(fTmp);
+ if (!std::isfinite(fTmp) || fTmp < std::numeric_limits<sal_Int32>::min() || fTmp > std::numeric_limits<sal_Int32>::max())
+ {
+ SAL_WARN("vcl", "Parsing error: out of range double: " << fTmp);
+ return;
+ }
+ fDest = fTmp;
+}
+
+SvStream& ReadLineInfo( SvStream& rIStm, LineInfo& rLineInfo )
+{
+ VersionCompatRead aCompat( rIStm );
+ sal_uInt16 nTmp16(0);
+ sal_Int32 nTmp32(0);
+
+ rIStm.ReadUInt16( nTmp16 );
+ rLineInfo.mpImplLineInfo->meStyle = static_cast<LineStyle>(nTmp16);
+ rIStm.ReadInt32( nTmp32 );
+ rLineInfo.mpImplLineInfo->mnWidth = nTmp32;
+
+ if( aCompat.GetVersion() >= 2 )
+ {
+ // version 2
+ rIStm.ReadUInt16( rLineInfo.mpImplLineInfo->mnDashCount ).ReadInt32( nTmp32 );
+ rLineInfo.mpImplLineInfo->mnDashLen = nTmp32;
+ rIStm.ReadUInt16( rLineInfo.mpImplLineInfo->mnDotCount ).ReadInt32( nTmp32 );
+ rLineInfo.mpImplLineInfo->mnDotLen = nTmp32;
+ rIStm.ReadInt32( nTmp32 );
+ rLineInfo.mpImplLineInfo->mnDistance = nTmp32;
+ }
+
+ if( aCompat.GetVersion() >= 3 )
+ {
+ // version 3
+ rIStm.ReadUInt16( nTmp16 );
+ rLineInfo.mpImplLineInfo->meLineJoin = static_cast<basegfx::B2DLineJoin>(nTmp16);
+ }
+
+ if( aCompat.GetVersion() >= 4 )
+ {
+ // version 4
+ rIStm.ReadUInt16( nTmp16 );
+ rLineInfo.mpImplLineInfo->meLineCap = static_cast<css::drawing::LineCap>(nTmp16);
+ }
+
+ if( aCompat.GetVersion() >= 5 )
+ {
+ // version 5
+ ReadLimitedDouble(rIStm, rLineInfo.mpImplLineInfo->mnWidth);
+ ReadLimitedDouble(rIStm, rLineInfo.mpImplLineInfo->mnDashLen);
+ ReadLimitedDouble(rIStm, rLineInfo.mpImplLineInfo->mnDotLen);
+ ReadLimitedDouble(rIStm, rLineInfo.mpImplLineInfo->mnDistance);
+ }
+
+ return rIStm;
+}
+
+SvStream& WriteLineInfo( SvStream& rOStm, const LineInfo& rLineInfo )
+{
+ VersionCompatWrite aCompat( rOStm, 5 );
+
+ // version 1
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rLineInfo.mpImplLineInfo->meStyle) )
+ .WriteInt32( basegfx::fround( rLineInfo.mpImplLineInfo->mnWidth ));
+
+ // since version2
+ rOStm.WriteUInt16( rLineInfo.mpImplLineInfo->mnDashCount )
+ .WriteInt32( basegfx::fround( rLineInfo.mpImplLineInfo->mnDashLen ));
+ rOStm.WriteUInt16( rLineInfo.mpImplLineInfo->mnDotCount )
+ .WriteInt32( basegfx::fround( rLineInfo.mpImplLineInfo->mnDotLen ));
+ rOStm.WriteInt32( basegfx::fround( rLineInfo.mpImplLineInfo->mnDistance ));
+
+ // since version3
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rLineInfo.mpImplLineInfo->meLineJoin) );
+
+ // since version4
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rLineInfo.mpImplLineInfo->meLineCap) );
+
+ // since version5
+ rOStm.WriteDouble( rLineInfo.mpImplLineInfo->mnWidth );
+ rOStm.WriteDouble( rLineInfo.mpImplLineInfo->mnDashLen );
+ rOStm.WriteDouble( rLineInfo.mpImplLineInfo->mnDotLen );
+ rOStm.WriteDouble( rLineInfo.mpImplLineInfo->mnDistance );
+
+ return rOStm;
+}
+
+std::vector< double > LineInfo::GetDotDashArray() const
+{
+ ::std::vector< double > fDotDashArray;
+ if ( GetStyle() != LineStyle::Dash )
+ return fDotDashArray;
+
+ const double fDashLen(GetDashLen());
+ const double fDotLen(GetDotLen());
+ const double fDistance(GetDistance());
+
+ for(sal_uInt16 a(0); a < GetDashCount(); a++)
+ {
+ fDotDashArray.push_back(fDashLen);
+ fDotDashArray.push_back(fDistance);
+ }
+
+ for(sal_uInt16 b(0); b < GetDotCount(); b++)
+ {
+ fDotDashArray.push_back(fDotLen);
+ fDotDashArray.push_back(fDistance);
+ }
+ return fDotDashArray;
+}
+
+void LineInfo::applyToB2DPolyPolygon(
+ basegfx::B2DPolyPolygon& io_rLinePolyPolygon,
+ basegfx::B2DPolyPolygon& o_rFillPolyPolygon) const
+{
+ o_rFillPolyPolygon.clear();
+
+ if(!io_rLinePolyPolygon.count())
+ return;
+
+ if(LineStyle::Dash == GetStyle())
+ {
+ ::std::vector< double > fDotDashArray = GetDotDashArray();
+ const double fAccumulated(::std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0));
+
+ if(fAccumulated > 0.0)
+ {
+ basegfx::B2DPolyPolygon aResult;
+
+ for(auto const& rPolygon : std::as_const(io_rLinePolyPolygon))
+ {
+ basegfx::B2DPolyPolygon aLineTarget;
+ basegfx::utils::applyLineDashing(
+ rPolygon,
+ fDotDashArray,
+ &aLineTarget);
+ aResult.append(aLineTarget);
+ }
+
+ io_rLinePolyPolygon = aResult;
+ }
+ }
+
+ if(!(GetWidth() > 1 && io_rLinePolyPolygon.count()))
+ return;
+
+ const double fHalfLineWidth((GetWidth() * 0.5) + 0.5);
+
+ for(auto const& rPolygon : std::as_const(io_rLinePolyPolygon))
+ {
+ o_rFillPolyPolygon.append(basegfx::utils::createAreaGeometry(
+ rPolygon,
+ fHalfLineWidth,
+ GetLineJoin(),
+ GetLineCap()));
+ }
+
+ io_rLinePolyPolygon.clear();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/mapmod.cxx b/vcl/source/gdi/mapmod.cxx
new file mode 100644
index 000000000..3efb05293
--- /dev/null
+++ b/vcl/source/gdi/mapmod.cxx
@@ -0,0 +1,160 @@
+/* -*- 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 <vcl/mapmod.hxx>
+
+#include <o3tl/hash_combine.hxx>
+#include <tools/gen.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <vcl/TypeSerializer.hxx>
+
+struct MapMode::ImplMapMode
+{
+ Point maOrigin;
+ MapUnit meUnit;
+ // NOTE: these Fraction must NOT have more than 32 bits precision
+ // because ReadFraction / WriteFraction do only 32 bits, so more than
+ // that cannot be stored in MetaFiles!
+ // => call ReduceInaccurate whenever setting these
+ Fraction maScaleX;
+ Fraction maScaleY;
+ bool mbSimple;
+
+ ImplMapMode();
+ ImplMapMode(const ImplMapMode& rImpMapMode);
+
+ bool operator==( const ImplMapMode& rImpMapMode ) const;
+};
+
+MapMode::ImplMapMode::ImplMapMode() :
+ maOrigin( 0, 0 ),
+ maScaleX( 1, 1 ),
+ maScaleY( 1, 1 )
+{
+ meUnit = MapUnit::MapPixel;
+ mbSimple = true;
+}
+
+MapMode::ImplMapMode::ImplMapMode( const ImplMapMode& ) = default;
+
+bool MapMode::ImplMapMode::operator==( const ImplMapMode& rImpMapMode ) const
+{
+ return meUnit == rImpMapMode.meUnit
+ && maOrigin == rImpMapMode.maOrigin
+ && maScaleX == rImpMapMode.maScaleX
+ && maScaleY == rImpMapMode.maScaleY;
+}
+
+namespace
+{
+ MapMode::ImplType& GetGlobalDefault()
+ {
+ static MapMode::ImplType gDefault;
+ return gDefault;
+ }
+}
+
+MapMode::MapMode() : mpImplMapMode(GetGlobalDefault())
+{
+}
+
+MapMode::MapMode( const MapMode& ) = default;
+
+MapMode::MapMode( MapUnit eUnit )
+{
+ mpImplMapMode->meUnit = eUnit;
+}
+
+MapMode::MapMode( MapUnit eUnit, const Point& rLogicOrg,
+ const Fraction& rScaleX, const Fraction& rScaleY )
+{
+ mpImplMapMode->meUnit = eUnit;
+ mpImplMapMode->maOrigin = rLogicOrg;
+ mpImplMapMode->maScaleX = rScaleX;
+ mpImplMapMode->maScaleY = rScaleY;
+ mpImplMapMode->maScaleX.ReduceInaccurate(32);
+ mpImplMapMode->maScaleY.ReduceInaccurate(32);
+ mpImplMapMode->mbSimple = false;
+}
+
+MapMode::~MapMode() = default;
+
+void MapMode::SetMapUnit( MapUnit eUnit )
+{
+ mpImplMapMode->meUnit = eUnit;
+}
+
+void MapMode::SetOrigin( const Point& rLogicOrg )
+{
+ mpImplMapMode->maOrigin = rLogicOrg;
+ mpImplMapMode->mbSimple = false;
+}
+
+void MapMode::SetScaleX( const Fraction& rScaleX )
+{
+ mpImplMapMode->maScaleX = rScaleX;
+ mpImplMapMode->maScaleX.ReduceInaccurate(32);
+ mpImplMapMode->mbSimple = false;
+}
+
+void MapMode::SetScaleY( const Fraction& rScaleY )
+{
+ mpImplMapMode->maScaleY = rScaleY;
+ mpImplMapMode->maScaleY.ReduceInaccurate(32);
+ mpImplMapMode->mbSimple = false;
+}
+
+MapMode& MapMode::operator=( const MapMode& ) = default;
+
+MapMode& MapMode::operator=( MapMode&& ) = default;
+
+bool MapMode::operator==( const MapMode& rMapMode ) const
+{
+ return mpImplMapMode == rMapMode.mpImplMapMode;
+}
+
+bool MapMode::IsDefault() const
+{
+ return mpImplMapMode.same_object(GetGlobalDefault());
+}
+
+size_t MapMode::GetHashValue() const
+{
+ size_t hash = 0;
+ o3tl::hash_combine( hash, mpImplMapMode->meUnit );
+ o3tl::hash_combine( hash, mpImplMapMode->maOrigin.GetHashValue());
+ o3tl::hash_combine( hash, mpImplMapMode->maScaleX.GetHashValue());
+ o3tl::hash_combine( hash, mpImplMapMode->maScaleY.GetHashValue());
+ o3tl::hash_combine( hash, mpImplMapMode->mbSimple );
+ return hash;
+}
+
+MapUnit MapMode::GetMapUnit() const { return mpImplMapMode->meUnit; }
+
+const Point& MapMode::GetOrigin() const { return mpImplMapMode->maOrigin; }
+
+const Fraction& MapMode::GetScaleX() const { return mpImplMapMode->maScaleX; }
+
+const Fraction& MapMode::GetScaleY() const { return mpImplMapMode->maScaleY; }
+
+bool MapMode::IsSimple() const { return mpImplMapMode->mbSimple; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/metaact.cxx b/vcl/source/gdi/metaact.cxx
new file mode 100644
index 000000000..9a85d805b
--- /dev/null
+++ b/vcl/source/gdi/metaact.cxx
@@ -0,0 +1,2122 @@
+/* -*- 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 <stdio.h>
+#include <string.h>
+#include <osl/thread.h>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/helpers.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/filter/SvmReader.hxx>
+#include <vcl/filter/SvmWriter.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/graphictools.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/fontdefs.hxx>
+#include <vcl/TypeSerializer.hxx>
+
+namespace
+{
+
+void ImplScalePoint( Point& rPt, double fScaleX, double fScaleY )
+{
+ rPt.setX( FRound( fScaleX * rPt.X() ) );
+ rPt.setY( FRound( fScaleY * rPt.Y() ) );
+}
+
+void ImplScaleRect( tools::Rectangle& rRect, double fScaleX, double fScaleY )
+{
+ Point aTL( rRect.TopLeft() );
+ Point aBR( rRect.BottomRight() );
+
+ ImplScalePoint( aTL, fScaleX, fScaleY );
+ ImplScalePoint( aBR, fScaleX, fScaleY );
+
+ rRect = tools::Rectangle( aTL, aBR );
+ rRect.Justify();
+}
+
+void ImplScalePoly( tools::Polygon& rPoly, double fScaleX, double fScaleY )
+{
+ for( sal_uInt16 i = 0, nCount = rPoly.GetSize(); i < nCount; i++ )
+ ImplScalePoint( rPoly[ i ], fScaleX, fScaleY );
+}
+
+void ImplScaleLineInfo( LineInfo& rLineInfo, double fScaleX, double fScaleY )
+{
+ if( !rLineInfo.IsDefault() )
+ {
+ const double fScale = ( fabs(fScaleX) + fabs(fScaleY) ) * 0.5;
+
+ rLineInfo.SetWidth( FRound( fScale * rLineInfo.GetWidth() ) );
+ rLineInfo.SetDashLen( FRound( fScale * rLineInfo.GetDashLen() ) );
+ rLineInfo.SetDotLen( FRound( fScale * rLineInfo.GetDotLen() ) );
+ rLineInfo.SetDistance( FRound( fScale * rLineInfo.GetDistance() ) );
+ }
+}
+
+} //anonymous namespace
+
+MetaAction::MetaAction() :
+ mnType( MetaActionType::NONE )
+{
+}
+
+MetaAction::MetaAction( MetaActionType nType ) :
+ mnType( nType )
+{
+}
+
+MetaAction::MetaAction( MetaAction const & rOther ) :
+ SimpleReferenceObject(), mnType( rOther.mnType )
+{
+}
+
+MetaAction::~MetaAction()
+{
+}
+
+void MetaAction::Execute( OutputDevice* )
+{
+}
+
+rtl::Reference<MetaAction> MetaAction::Clone() const
+{
+ return new MetaAction;
+}
+
+void MetaAction::Move( tools::Long, tools::Long )
+{
+}
+
+void MetaAction::Scale( double, double )
+{
+}
+
+MetaPixelAction::MetaPixelAction() :
+ MetaAction(MetaActionType::PIXEL)
+{}
+
+MetaPixelAction::~MetaPixelAction()
+{}
+
+MetaPixelAction::MetaPixelAction( const Point& rPt, const Color& rColor ) :
+ MetaAction ( MetaActionType::PIXEL ),
+ maPt ( rPt ),
+ maColor ( rColor )
+{}
+
+void MetaPixelAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawPixel( maPt, maColor );
+}
+
+rtl::Reference<MetaAction> MetaPixelAction::Clone() const
+{
+ return new MetaPixelAction( *this );
+}
+
+void MetaPixelAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaPixelAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+MetaPointAction::MetaPointAction() :
+ MetaAction(MetaActionType::POINT)
+{}
+
+MetaPointAction::~MetaPointAction()
+{}
+
+MetaPointAction::MetaPointAction( const Point& rPt ) :
+ MetaAction ( MetaActionType::POINT ),
+ maPt ( rPt )
+{}
+
+void MetaPointAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawPixel( maPt );
+}
+
+rtl::Reference<MetaAction> MetaPointAction::Clone() const
+{
+ return new MetaPointAction( *this );
+}
+
+void MetaPointAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaPointAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+MetaLineAction::MetaLineAction() :
+ MetaAction(MetaActionType::LINE)
+{}
+
+MetaLineAction::~MetaLineAction()
+{}
+
+MetaLineAction::MetaLineAction( const Point& rStart, const Point& rEnd ) :
+ MetaAction ( MetaActionType::LINE ),
+ maStartPt ( rStart ),
+ maEndPt ( rEnd )
+{}
+
+MetaLineAction::MetaLineAction( const Point& rStart, const Point& rEnd,
+ const LineInfo& rLineInfo ) :
+ MetaAction ( MetaActionType::LINE ),
+ maLineInfo ( rLineInfo ),
+ maStartPt ( rStart ),
+ maEndPt ( rEnd )
+{}
+
+void MetaLineAction::Execute( OutputDevice* pOut )
+{
+ if( maLineInfo.IsDefault() )
+ pOut->DrawLine( maStartPt, maEndPt );
+ else
+ pOut->DrawLine( maStartPt, maEndPt, maLineInfo );
+}
+
+rtl::Reference<MetaAction> MetaLineAction::Clone() const
+{
+ return new MetaLineAction( *this );
+}
+
+void MetaLineAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maStartPt.Move( nHorzMove, nVertMove );
+ maEndPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaLineAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maStartPt, fScaleX, fScaleY );
+ ImplScalePoint( maEndPt, fScaleX, fScaleY );
+ ImplScaleLineInfo( maLineInfo, fScaleX, fScaleY );
+}
+
+MetaRectAction::MetaRectAction() :
+ MetaAction(MetaActionType::RECT)
+{}
+
+MetaRectAction::~MetaRectAction()
+{}
+
+MetaRectAction::MetaRectAction( const tools::Rectangle& rRect ) :
+ MetaAction ( MetaActionType::RECT ),
+ maRect ( rRect )
+{}
+
+void MetaRectAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawRect( maRect );
+}
+
+rtl::Reference<MetaAction> MetaRectAction::Clone() const
+{
+ return new MetaRectAction( *this );
+}
+
+void MetaRectAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaRectAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+MetaRoundRectAction::MetaRoundRectAction() :
+ MetaAction ( MetaActionType::ROUNDRECT ),
+ mnHorzRound ( 0 ),
+ mnVertRound ( 0 )
+{}
+
+MetaRoundRectAction::~MetaRoundRectAction()
+{}
+
+MetaRoundRectAction::MetaRoundRectAction( const tools::Rectangle& rRect,
+ sal_uInt32 nHorzRound, sal_uInt32 nVertRound ) :
+ MetaAction ( MetaActionType::ROUNDRECT ),
+ maRect ( rRect ),
+ mnHorzRound ( nHorzRound ),
+ mnVertRound ( nVertRound )
+{}
+
+void MetaRoundRectAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawRect( maRect, mnHorzRound, mnVertRound );
+}
+
+rtl::Reference<MetaAction> MetaRoundRectAction::Clone() const
+{
+ return new MetaRoundRectAction( *this );
+}
+
+void MetaRoundRectAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaRoundRectAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+ mnHorzRound = FRound( mnHorzRound * fabs(fScaleX) );
+ mnVertRound = FRound( mnVertRound * fabs(fScaleY) );
+}
+
+MetaEllipseAction::MetaEllipseAction() :
+ MetaAction(MetaActionType::ELLIPSE)
+{}
+
+MetaEllipseAction::~MetaEllipseAction()
+{}
+
+MetaEllipseAction::MetaEllipseAction( const tools::Rectangle& rRect ) :
+ MetaAction ( MetaActionType::ELLIPSE ),
+ maRect ( rRect )
+{}
+
+void MetaEllipseAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawEllipse( maRect );
+}
+
+rtl::Reference<MetaAction> MetaEllipseAction::Clone() const
+{
+ return new MetaEllipseAction( *this );
+}
+
+void MetaEllipseAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaEllipseAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+MetaArcAction::MetaArcAction() :
+ MetaAction(MetaActionType::ARC)
+{}
+
+MetaArcAction::~MetaArcAction()
+{}
+
+MetaArcAction::MetaArcAction( const tools::Rectangle& rRect,
+ const Point& rStart, const Point& rEnd ) :
+ MetaAction ( MetaActionType::ARC ),
+ maRect ( rRect ),
+ maStartPt ( rStart ),
+ maEndPt ( rEnd )
+{}
+
+void MetaArcAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawArc( maRect, maStartPt, maEndPt );
+}
+
+rtl::Reference<MetaAction> MetaArcAction::Clone() const
+{
+ return new MetaArcAction( *this );
+}
+
+void MetaArcAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+ maStartPt.Move( nHorzMove, nVertMove );
+ maEndPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaArcAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+ ImplScalePoint( maStartPt, fScaleX, fScaleY );
+ ImplScalePoint( maEndPt, fScaleX, fScaleY );
+}
+
+MetaPieAction::MetaPieAction() :
+ MetaAction(MetaActionType::PIE)
+{}
+
+MetaPieAction::~MetaPieAction()
+{}
+
+MetaPieAction::MetaPieAction( const tools::Rectangle& rRect,
+ const Point& rStart, const Point& rEnd ) :
+ MetaAction ( MetaActionType::PIE ),
+ maRect ( rRect ),
+ maStartPt ( rStart ),
+ maEndPt ( rEnd )
+{}
+
+void MetaPieAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawPie( maRect, maStartPt, maEndPt );
+}
+
+rtl::Reference<MetaAction> MetaPieAction::Clone() const
+{
+ return new MetaPieAction( *this );
+}
+
+void MetaPieAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+ maStartPt.Move( nHorzMove, nVertMove );
+ maEndPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaPieAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+ ImplScalePoint( maStartPt, fScaleX, fScaleY );
+ ImplScalePoint( maEndPt, fScaleX, fScaleY );
+}
+
+MetaChordAction::MetaChordAction() :
+ MetaAction(MetaActionType::CHORD)
+{}
+
+MetaChordAction::~MetaChordAction()
+{}
+
+MetaChordAction::MetaChordAction( const tools::Rectangle& rRect,
+ const Point& rStart, const Point& rEnd ) :
+ MetaAction ( MetaActionType::CHORD ),
+ maRect ( rRect ),
+ maStartPt ( rStart ),
+ maEndPt ( rEnd )
+{}
+
+void MetaChordAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawChord( maRect, maStartPt, maEndPt );
+}
+
+rtl::Reference<MetaAction> MetaChordAction::Clone() const
+{
+ return new MetaChordAction( *this );
+}
+
+void MetaChordAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+ maStartPt.Move( nHorzMove, nVertMove );
+ maEndPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaChordAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+ ImplScalePoint( maStartPt, fScaleX, fScaleY );
+ ImplScalePoint( maEndPt, fScaleX, fScaleY );
+}
+
+MetaPolyLineAction::MetaPolyLineAction() :
+ MetaAction(MetaActionType::POLYLINE)
+{}
+
+MetaPolyLineAction::~MetaPolyLineAction()
+{}
+
+MetaPolyLineAction::MetaPolyLineAction( const tools::Polygon& rPoly ) :
+ MetaAction ( MetaActionType::POLYLINE ),
+ maPoly ( rPoly )
+{}
+
+MetaPolyLineAction::MetaPolyLineAction( const tools::Polygon& rPoly, const LineInfo& rLineInfo ) :
+ MetaAction ( MetaActionType::POLYLINE ),
+ maLineInfo ( rLineInfo ),
+ maPoly ( rPoly )
+{}
+
+void MetaPolyLineAction::Execute( OutputDevice* pOut )
+{
+ if( maLineInfo.IsDefault() )
+ pOut->DrawPolyLine( maPoly );
+ else
+ pOut->DrawPolyLine( maPoly, maLineInfo );
+}
+
+rtl::Reference<MetaAction> MetaPolyLineAction::Clone() const
+{
+ return new MetaPolyLineAction( *this );
+}
+
+void MetaPolyLineAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaPolyLineAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoly( maPoly, fScaleX, fScaleY );
+ ImplScaleLineInfo( maLineInfo, fScaleX, fScaleY );
+}
+
+MetaPolygonAction::MetaPolygonAction() :
+ MetaAction(MetaActionType::POLYGON)
+{}
+
+MetaPolygonAction::~MetaPolygonAction()
+{}
+
+MetaPolygonAction::MetaPolygonAction( const tools::Polygon& rPoly ) :
+ MetaAction ( MetaActionType::POLYGON ),
+ maPoly ( rPoly )
+{}
+
+void MetaPolygonAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawPolygon( maPoly );
+}
+
+rtl::Reference<MetaAction> MetaPolygonAction::Clone() const
+{
+ return new MetaPolygonAction( *this );
+}
+
+void MetaPolygonAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaPolygonAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoly( maPoly, fScaleX, fScaleY );
+}
+
+MetaPolyPolygonAction::MetaPolyPolygonAction() :
+ MetaAction(MetaActionType::POLYPOLYGON)
+{}
+
+MetaPolyPolygonAction::~MetaPolyPolygonAction()
+{}
+
+MetaPolyPolygonAction::MetaPolyPolygonAction( const tools::PolyPolygon& rPolyPoly ) :
+ MetaAction ( MetaActionType::POLYPOLYGON ),
+ maPolyPoly ( rPolyPoly )
+{}
+
+void MetaPolyPolygonAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawPolyPolygon( maPolyPoly );
+}
+
+rtl::Reference<MetaAction> MetaPolyPolygonAction::Clone() const
+{
+ return new MetaPolyPolygonAction( *this );
+}
+
+void MetaPolyPolygonAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPolyPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaPolyPolygonAction::Scale( double fScaleX, double fScaleY )
+{
+ for( sal_uInt16 i = 0, nCount = maPolyPoly.Count(); i < nCount; i++ )
+ ImplScalePoly( maPolyPoly[ i ], fScaleX, fScaleY );
+}
+
+MetaTextAction::MetaTextAction() :
+ MetaAction ( MetaActionType::TEXT ),
+ mnIndex ( 0 ),
+ mnLen ( 0 )
+{}
+
+MetaTextAction::~MetaTextAction()
+{}
+
+MetaTextAction::MetaTextAction( const Point& rPt, const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::TEXT ),
+ maPt ( rPt ),
+ maStr ( rStr ),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{}
+
+void MetaTextAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawText( maPt, maStr, mnIndex, mnLen );
+}
+
+rtl::Reference<MetaAction> MetaTextAction::Clone() const
+{
+ return new MetaTextAction( *this );
+}
+
+void MetaTextAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+MetaTextArrayAction::MetaTextArrayAction() :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ mnIndex ( 0 ),
+ mnLen ( 0 )
+{}
+
+MetaTextArrayAction::MetaTextArrayAction( const MetaTextArrayAction& rAction ) :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ maStartPt ( rAction.maStartPt ),
+ maStr ( rAction.maStr ),
+ maDXAry ( rAction.maDXAry ),
+ mnIndex ( rAction.mnIndex ),
+ mnLen ( rAction.mnLen )
+{
+}
+
+MetaTextArrayAction::MetaTextArrayAction( const Point& rStartPt,
+ const OUString& rStr,
+ const std::vector<sal_Int32>& rDXAry,
+ sal_Int32 nIndex,
+ sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ maStartPt ( rStartPt ),
+ maStr ( rStr ),
+ maDXAry ( rDXAry ),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{
+}
+
+MetaTextArrayAction::MetaTextArrayAction( const Point& rStartPt,
+ const OUString& rStr,
+ o3tl::span<const sal_Int32> pDXAry,
+ sal_Int32 nIndex,
+ sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ maStartPt ( rStartPt ),
+ maStr ( rStr ),
+ maDXAry ( pDXAry.begin(), pDXAry.end() ),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{
+}
+
+MetaTextArrayAction::~MetaTextArrayAction()
+{
+}
+
+void MetaTextArrayAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawTextArray( maStartPt, maStr, maDXAry, mnIndex, mnLen );
+}
+
+rtl::Reference<MetaAction> MetaTextArrayAction::Clone() const
+{
+ return new MetaTextArrayAction( *this );
+}
+
+void MetaTextArrayAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maStartPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextArrayAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maStartPt, fScaleX, fScaleY );
+
+ if ( !maDXAry.empty() && mnLen )
+ {
+ for ( sal_uInt16 i = 0, nCount = mnLen; i < nCount; i++ )
+ maDXAry[ i ] = FRound( maDXAry[ i ] * fabs(fScaleX) );
+ }
+}
+
+void MetaTextArrayAction::SetDXArray(std::vector<sal_Int32> aArray)
+{
+ maDXAry = std::move(aArray);
+}
+
+MetaStretchTextAction::MetaStretchTextAction() :
+ MetaAction ( MetaActionType::STRETCHTEXT ),
+ mnWidth ( 0 ),
+ mnIndex ( 0 ),
+ mnLen ( 0 )
+{}
+
+MetaStretchTextAction::~MetaStretchTextAction()
+{}
+
+MetaStretchTextAction::MetaStretchTextAction( const Point& rPt, sal_uInt32 nWidth,
+ const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::STRETCHTEXT ),
+ maPt ( rPt ),
+ maStr ( rStr ),
+ mnWidth ( nWidth ),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{}
+
+void MetaStretchTextAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawStretchText( maPt, mnWidth, maStr, mnIndex, mnLen );
+}
+
+rtl::Reference<MetaAction> MetaStretchTextAction::Clone() const
+{
+ return new MetaStretchTextAction( *this );
+}
+
+void MetaStretchTextAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaStretchTextAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+ mnWidth = static_cast<sal_uLong>(FRound( mnWidth * fabs(fScaleX) ));
+}
+MetaTextRectAction::MetaTextRectAction() :
+ MetaAction ( MetaActionType::TEXTRECT ),
+ mnStyle ( DrawTextFlags::NONE )
+{}
+
+MetaTextRectAction::~MetaTextRectAction()
+{}
+
+MetaTextRectAction::MetaTextRectAction( const tools::Rectangle& rRect,
+ const OUString& rStr, DrawTextFlags nStyle ) :
+ MetaAction ( MetaActionType::TEXTRECT ),
+ maRect ( rRect ),
+ maStr ( rStr ),
+ mnStyle ( nStyle )
+{}
+
+void MetaTextRectAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawText( maRect, maStr, mnStyle );
+}
+
+rtl::Reference<MetaAction> MetaTextRectAction::Clone() const
+{
+ return new MetaTextRectAction( *this );
+}
+
+void MetaTextRectAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextRectAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+MetaTextLineAction::MetaTextLineAction() :
+ MetaAction ( MetaActionType::TEXTLINE ),
+ mnWidth ( 0 ),
+ meStrikeout ( STRIKEOUT_NONE ),
+ meUnderline ( LINESTYLE_NONE ),
+ meOverline ( LINESTYLE_NONE )
+{}
+
+MetaTextLineAction::~MetaTextLineAction()
+{}
+
+MetaTextLineAction::MetaTextLineAction( const Point& rPos, tools::Long nWidth,
+ FontStrikeout eStrikeout,
+ FontLineStyle eUnderline,
+ FontLineStyle eOverline ) :
+ MetaAction ( MetaActionType::TEXTLINE ),
+ maPos ( rPos ),
+ mnWidth ( nWidth ),
+ meStrikeout ( eStrikeout ),
+ meUnderline ( eUnderline ),
+ meOverline ( eOverline )
+{}
+
+void MetaTextLineAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawTextLine( maPos, mnWidth, meStrikeout, meUnderline, meOverline );
+}
+
+rtl::Reference<MetaAction> MetaTextLineAction::Clone() const
+{
+ return new MetaTextLineAction( *this );
+}
+
+void MetaTextLineAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPos.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextLineAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPos, fScaleX, fScaleY );
+ mnWidth = FRound( mnWidth * fabs(fScaleX) );
+}
+
+MetaBmpAction::MetaBmpAction() :
+ MetaAction(MetaActionType::BMP)
+{}
+
+MetaBmpAction::~MetaBmpAction()
+{}
+
+MetaBmpAction::MetaBmpAction( const Point& rPt, const Bitmap& rBmp ) :
+ MetaAction ( MetaActionType::BMP ),
+ maBmp ( rBmp ),
+ maPt ( rPt )
+{}
+
+void MetaBmpAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawBitmap( maPt, maBmp );
+}
+
+rtl::Reference<MetaAction> MetaBmpAction::Clone() const
+{
+ return new MetaBmpAction( *this );
+}
+
+void MetaBmpAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+MetaBmpScaleAction::MetaBmpScaleAction() :
+ MetaAction(MetaActionType::BMPSCALE)
+{}
+
+MetaBmpScaleAction::~MetaBmpScaleAction()
+{}
+
+MetaBmpScaleAction::MetaBmpScaleAction( const Point& rPt, const Size& rSz,
+ const Bitmap& rBmp ) :
+ MetaAction ( MetaActionType::BMPSCALE ),
+ maBmp ( rBmp ),
+ maPt ( rPt ),
+ maSz ( rSz )
+{}
+
+static bool AllowScale(const Size& rSource, const Size& rDest)
+{
+ if (utl::ConfigManager::IsFuzzing())
+ {
+ constexpr int nMaxScaleWhenFuzzing = 512;
+
+ auto nSourceHeight = rSource.Height();
+ auto nDestHeight = rDest.Height();
+ if (nSourceHeight && nDestHeight > nSourceHeight && nDestHeight / nSourceHeight > nMaxScaleWhenFuzzing)
+ {
+ SAL_WARN("vcl", "skipping large vertical scaling: " << nSourceHeight << " to " << nDestHeight);
+ return false;
+ }
+
+ if (nDestHeight && nSourceHeight > nDestHeight && nSourceHeight / nDestHeight > nMaxScaleWhenFuzzing)
+ {
+ SAL_WARN("vcl", "skipping large vertical scaling: " << nSourceHeight << " to " << nDestHeight);
+ return false;
+ }
+
+ auto nSourceWidth = rSource.Width();
+ auto nDestWidth = rDest.Width();
+ if (nSourceWidth && nDestWidth > nSourceWidth && nDestWidth / nSourceWidth > nMaxScaleWhenFuzzing)
+ {
+ SAL_WARN("vcl", "skipping large horizontal scaling: " << nSourceWidth << " to " << nDestWidth);
+ return false;
+ }
+
+ if (nDestWidth && nSourceWidth > nDestWidth && nSourceWidth / nDestWidth > nMaxScaleWhenFuzzing)
+ {
+ SAL_WARN("vcl", "skipping large horizontal scaling: " << nSourceWidth << " to " << nDestWidth);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void MetaBmpScaleAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowScale(maBmp.GetSizePixel(), maSz))
+ return;
+
+ pOut->DrawBitmap( maPt, maSz, maBmp );
+}
+
+rtl::Reference<MetaAction> MetaBmpScaleAction::Clone() const
+{
+ return new MetaBmpScaleAction( *this );
+}
+
+void MetaBmpScaleAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpScaleAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maPt, maSz);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maPt = aRectangle.TopLeft();
+ maSz = aRectangle.GetSize();
+}
+
+MetaBmpScalePartAction::MetaBmpScalePartAction() :
+ MetaAction(MetaActionType::BMPSCALEPART)
+{}
+
+MetaBmpScalePartAction::~MetaBmpScalePartAction()
+{}
+
+MetaBmpScalePartAction::MetaBmpScalePartAction( const Point& rDstPt, const Size& rDstSz,
+ const Point& rSrcPt, const Size& rSrcSz,
+ const Bitmap& rBmp ) :
+ MetaAction ( MetaActionType::BMPSCALEPART ),
+ maBmp ( rBmp ),
+ maDstPt ( rDstPt ),
+ maDstSz ( rDstSz ),
+ maSrcPt ( rSrcPt ),
+ maSrcSz ( rSrcSz )
+{}
+
+void MetaBmpScalePartAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawBitmap( maDstPt, maDstSz, maSrcPt, maSrcSz, maBmp );
+}
+
+rtl::Reference<MetaAction> MetaBmpScalePartAction::Clone() const
+{
+ return new MetaBmpScalePartAction( *this );
+}
+
+void MetaBmpScalePartAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maDstPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpScalePartAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maDstPt, maDstSz);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maDstPt = aRectangle.TopLeft();
+ maDstSz = aRectangle.GetSize();
+}
+
+MetaBmpExAction::MetaBmpExAction() :
+ MetaAction(MetaActionType::BMPEX)
+{}
+
+MetaBmpExAction::~MetaBmpExAction()
+{}
+
+MetaBmpExAction::MetaBmpExAction( const Point& rPt, const BitmapEx& rBmpEx ) :
+ MetaAction ( MetaActionType::BMPEX ),
+ maBmpEx ( rBmpEx ),
+ maPt ( rPt )
+{}
+
+void MetaBmpExAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawBitmapEx( maPt, maBmpEx );
+}
+
+rtl::Reference<MetaAction> MetaBmpExAction::Clone() const
+{
+ return new MetaBmpExAction( *this );
+}
+
+void MetaBmpExAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpExAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+MetaBmpExScaleAction::MetaBmpExScaleAction() :
+ MetaAction(MetaActionType::BMPEXSCALE)
+{}
+
+MetaBmpExScaleAction::~MetaBmpExScaleAction()
+{}
+
+MetaBmpExScaleAction::MetaBmpExScaleAction( const Point& rPt, const Size& rSz,
+ const BitmapEx& rBmpEx ) :
+ MetaAction ( MetaActionType::BMPEXSCALE ),
+ maBmpEx ( rBmpEx ),
+ maPt ( rPt ),
+ maSz ( rSz )
+{}
+
+void MetaBmpExScaleAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowScale(maBmpEx.GetSizePixel(), maSz))
+ return;
+
+ pOut->DrawBitmapEx( maPt, maSz, maBmpEx );
+}
+
+rtl::Reference<MetaAction> MetaBmpExScaleAction::Clone() const
+{
+ return new MetaBmpExScaleAction( *this );
+}
+
+void MetaBmpExScaleAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpExScaleAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maPt, maSz);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maPt = aRectangle.TopLeft();
+ maSz = aRectangle.GetSize();
+}
+
+MetaBmpExScalePartAction::MetaBmpExScalePartAction() :
+ MetaAction(MetaActionType::BMPEXSCALEPART)
+{}
+
+MetaBmpExScalePartAction::~MetaBmpExScalePartAction()
+{}
+
+MetaBmpExScalePartAction::MetaBmpExScalePartAction( const Point& rDstPt, const Size& rDstSz,
+ const Point& rSrcPt, const Size& rSrcSz,
+ const BitmapEx& rBmpEx ) :
+ MetaAction ( MetaActionType::BMPEXSCALEPART ),
+ maBmpEx ( rBmpEx ),
+ maDstPt ( rDstPt ),
+ maDstSz ( rDstSz ),
+ maSrcPt ( rSrcPt ),
+ maSrcSz ( rSrcSz )
+{}
+
+void MetaBmpExScalePartAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawBitmapEx( maDstPt, maDstSz, maSrcPt, maSrcSz, maBmpEx );
+}
+
+rtl::Reference<MetaAction> MetaBmpExScalePartAction::Clone() const
+{
+ return new MetaBmpExScalePartAction( *this );
+}
+
+void MetaBmpExScalePartAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maDstPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpExScalePartAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maDstPt, maDstSz);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maDstPt = aRectangle.TopLeft();
+ maDstSz = aRectangle.GetSize();
+}
+
+MetaMaskAction::MetaMaskAction() :
+ MetaAction(MetaActionType::MASK)
+{}
+
+MetaMaskAction::~MetaMaskAction()
+{}
+
+MetaMaskAction::MetaMaskAction( const Point& rPt,
+ const Bitmap& rBmp,
+ const Color& rColor ) :
+ MetaAction ( MetaActionType::MASK ),
+ maBmp ( rBmp ),
+ maColor ( rColor ),
+ maPt ( rPt )
+{}
+
+void MetaMaskAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawMask( maPt, maBmp, maColor );
+}
+
+rtl::Reference<MetaAction> MetaMaskAction::Clone() const
+{
+ return new MetaMaskAction( *this );
+}
+
+void MetaMaskAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaMaskAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+MetaMaskScaleAction::MetaMaskScaleAction() :
+ MetaAction(MetaActionType::MASKSCALE)
+{}
+
+MetaMaskScaleAction::~MetaMaskScaleAction()
+{}
+
+MetaMaskScaleAction::MetaMaskScaleAction( const Point& rPt, const Size& rSz,
+ const Bitmap& rBmp,
+ const Color& rColor ) :
+ MetaAction ( MetaActionType::MASKSCALE ),
+ maBmp ( rBmp ),
+ maColor ( rColor ),
+ maPt ( rPt ),
+ maSz ( rSz )
+{}
+
+void MetaMaskScaleAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawMask( maPt, maSz, maBmp, maColor );
+}
+
+rtl::Reference<MetaAction> MetaMaskScaleAction::Clone() const
+{
+ return new MetaMaskScaleAction( *this );
+}
+
+void MetaMaskScaleAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaMaskScaleAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maPt, maSz);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maPt = aRectangle.TopLeft();
+ maSz = aRectangle.GetSize();
+}
+
+MetaMaskScalePartAction::MetaMaskScalePartAction() :
+ MetaAction(MetaActionType::MASKSCALEPART)
+{}
+
+MetaMaskScalePartAction::~MetaMaskScalePartAction()
+{}
+
+MetaMaskScalePartAction::MetaMaskScalePartAction( const Point& rDstPt, const Size& rDstSz,
+ const Point& rSrcPt, const Size& rSrcSz,
+ const Bitmap& rBmp,
+ const Color& rColor ) :
+ MetaAction ( MetaActionType::MASKSCALEPART ),
+ maBmp ( rBmp ),
+ maColor ( rColor ),
+ maDstPt ( rDstPt ),
+ maDstSz ( rDstSz ),
+ maSrcPt ( rSrcPt ),
+ maSrcSz ( rSrcSz )
+{}
+
+void MetaMaskScalePartAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawMask( maDstPt, maDstSz, maSrcPt, maSrcSz, maBmp, maColor, MetaActionType::MASKSCALE );
+}
+
+rtl::Reference<MetaAction> MetaMaskScalePartAction::Clone() const
+{
+ return new MetaMaskScalePartAction( *this );
+}
+
+void MetaMaskScalePartAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maDstPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaMaskScalePartAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maDstPt, maDstSz);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maDstPt = aRectangle.TopLeft();
+ maDstSz = aRectangle.GetSize();
+}
+
+MetaGradientAction::MetaGradientAction() :
+ MetaAction(MetaActionType::GRADIENT)
+{}
+
+MetaGradientAction::~MetaGradientAction()
+{}
+
+MetaGradientAction::MetaGradientAction( const tools::Rectangle& rRect, const Gradient& rGradient ) :
+ MetaAction ( MetaActionType::GRADIENT ),
+ maRect ( rRect ),
+ maGradient ( rGradient )
+{}
+
+void MetaGradientAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawGradient( maRect, maGradient );
+}
+
+rtl::Reference<MetaAction> MetaGradientAction::Clone() const
+{
+ return new MetaGradientAction( *this );
+}
+
+void MetaGradientAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaGradientAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+MetaGradientExAction::MetaGradientExAction() :
+ MetaAction ( MetaActionType::GRADIENTEX )
+{}
+
+MetaGradientExAction::MetaGradientExAction( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient ) :
+ MetaAction ( MetaActionType::GRADIENTEX ),
+ maPolyPoly ( rPolyPoly ),
+ maGradient ( rGradient )
+{}
+
+MetaGradientExAction::~MetaGradientExAction()
+{}
+
+void MetaGradientExAction::Execute( OutputDevice* pOut )
+{
+ if( pOut->GetConnectMetaFile() )
+ {
+ pOut->GetConnectMetaFile()->AddAction( this );
+ }
+}
+
+rtl::Reference<MetaAction> MetaGradientExAction::Clone() const
+{
+ return new MetaGradientExAction( *this );
+}
+
+void MetaGradientExAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPolyPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaGradientExAction::Scale( double fScaleX, double fScaleY )
+{
+ for( sal_uInt16 i = 0, nCount = maPolyPoly.Count(); i < nCount; i++ )
+ ImplScalePoly( maPolyPoly[ i ], fScaleX, fScaleY );
+}
+
+MetaHatchAction::MetaHatchAction() :
+ MetaAction(MetaActionType::HATCH)
+{}
+
+MetaHatchAction::~MetaHatchAction()
+{}
+
+MetaHatchAction::MetaHatchAction( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch ) :
+ MetaAction ( MetaActionType::HATCH ),
+ maPolyPoly ( rPolyPoly ),
+ maHatch ( rHatch )
+{}
+
+void MetaHatchAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawHatch( maPolyPoly, maHatch );
+}
+
+rtl::Reference<MetaAction> MetaHatchAction::Clone() const
+{
+ return new MetaHatchAction( *this );
+}
+
+void MetaHatchAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPolyPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaHatchAction::Scale( double fScaleX, double fScaleY )
+{
+ for( sal_uInt16 i = 0, nCount = maPolyPoly.Count(); i < nCount; i++ )
+ ImplScalePoly( maPolyPoly[ i ], fScaleX, fScaleY );
+}
+
+MetaWallpaperAction::MetaWallpaperAction() :
+ MetaAction(MetaActionType::WALLPAPER)
+{}
+
+MetaWallpaperAction::~MetaWallpaperAction()
+{}
+
+MetaWallpaperAction::MetaWallpaperAction( const tools::Rectangle& rRect,
+ const Wallpaper& rPaper ) :
+ MetaAction ( MetaActionType::WALLPAPER ),
+ maRect ( rRect ),
+ maWallpaper ( rPaper )
+{}
+
+void MetaWallpaperAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawWallpaper( maRect, maWallpaper );
+}
+
+rtl::Reference<MetaAction> MetaWallpaperAction::Clone() const
+{
+ return new MetaWallpaperAction( *this );
+}
+
+void MetaWallpaperAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaWallpaperAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+MetaClipRegionAction::MetaClipRegionAction() :
+ MetaAction ( MetaActionType::CLIPREGION ),
+ mbClip ( false )
+{}
+
+MetaClipRegionAction::~MetaClipRegionAction()
+{}
+
+MetaClipRegionAction::MetaClipRegionAction( const vcl::Region& rRegion, bool bClip ) :
+ MetaAction ( MetaActionType::CLIPREGION ),
+ maRegion ( rRegion ),
+ mbClip ( bClip )
+{}
+
+void MetaClipRegionAction::Execute( OutputDevice* pOut )
+{
+ if( mbClip )
+ pOut->SetClipRegion( maRegion );
+ else
+ pOut->SetClipRegion();
+}
+
+rtl::Reference<MetaAction> MetaClipRegionAction::Clone() const
+{
+ return new MetaClipRegionAction( *this );
+}
+
+void MetaClipRegionAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRegion.Move( nHorzMove, nVertMove );
+}
+
+void MetaClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ maRegion.Scale( fScaleX, fScaleY );
+}
+
+MetaISectRectClipRegionAction::MetaISectRectClipRegionAction() :
+ MetaAction(MetaActionType::ISECTRECTCLIPREGION)
+{}
+
+MetaISectRectClipRegionAction::~MetaISectRectClipRegionAction()
+{}
+
+MetaISectRectClipRegionAction::MetaISectRectClipRegionAction( const tools::Rectangle& rRect ) :
+ MetaAction ( MetaActionType::ISECTRECTCLIPREGION ),
+ maRect ( rRect )
+{}
+
+void MetaISectRectClipRegionAction::Execute( OutputDevice* pOut )
+{
+ pOut->IntersectClipRegion( maRect );
+}
+
+rtl::Reference<MetaAction> MetaISectRectClipRegionAction::Clone() const
+{
+ return new MetaISectRectClipRegionAction( *this );
+}
+
+void MetaISectRectClipRegionAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaISectRectClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+MetaISectRegionClipRegionAction::MetaISectRegionClipRegionAction() :
+ MetaAction(MetaActionType::ISECTREGIONCLIPREGION)
+{}
+
+MetaISectRegionClipRegionAction::~MetaISectRegionClipRegionAction()
+{}
+
+MetaISectRegionClipRegionAction::MetaISectRegionClipRegionAction( const vcl::Region& rRegion ) :
+ MetaAction ( MetaActionType::ISECTREGIONCLIPREGION ),
+ maRegion ( rRegion )
+{
+}
+
+void MetaISectRegionClipRegionAction::Execute( OutputDevice* pOut )
+{
+ pOut->IntersectClipRegion( maRegion );
+}
+
+rtl::Reference<MetaAction> MetaISectRegionClipRegionAction::Clone() const
+{
+ return new MetaISectRegionClipRegionAction( *this );
+}
+
+void MetaISectRegionClipRegionAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRegion.Move( nHorzMove, nVertMove );
+}
+
+void MetaISectRegionClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ maRegion.Scale( fScaleX, fScaleY );
+}
+
+MetaMoveClipRegionAction::MetaMoveClipRegionAction() :
+ MetaAction ( MetaActionType::MOVECLIPREGION ),
+ mnHorzMove ( 0 ),
+ mnVertMove ( 0 )
+{}
+
+MetaMoveClipRegionAction::~MetaMoveClipRegionAction()
+{}
+
+MetaMoveClipRegionAction::MetaMoveClipRegionAction( tools::Long nHorzMove, tools::Long nVertMove ) :
+ MetaAction ( MetaActionType::MOVECLIPREGION ),
+ mnHorzMove ( nHorzMove ),
+ mnVertMove ( nVertMove )
+{}
+
+void MetaMoveClipRegionAction::Execute( OutputDevice* pOut )
+{
+ pOut->MoveClipRegion( mnHorzMove, mnVertMove );
+}
+
+rtl::Reference<MetaAction> MetaMoveClipRegionAction::Clone() const
+{
+ return new MetaMoveClipRegionAction( *this );
+}
+
+void MetaMoveClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ mnHorzMove = FRound( mnHorzMove * fScaleX );
+ mnVertMove = FRound( mnVertMove * fScaleY );
+}
+
+MetaLineColorAction::MetaLineColorAction() :
+ MetaAction ( MetaActionType::LINECOLOR ),
+ mbSet ( false )
+{}
+
+MetaLineColorAction::~MetaLineColorAction()
+{}
+
+MetaLineColorAction::MetaLineColorAction( const Color& rColor, bool bSet ) :
+ MetaAction ( MetaActionType::LINECOLOR ),
+ maColor ( rColor ),
+ mbSet ( bSet )
+{}
+
+void MetaLineColorAction::Execute( OutputDevice* pOut )
+{
+ if( mbSet )
+ pOut->SetLineColor( maColor );
+ else
+ pOut->SetLineColor();
+}
+
+rtl::Reference<MetaAction> MetaLineColorAction::Clone() const
+{
+ return new MetaLineColorAction( *this );
+}
+
+MetaFillColorAction::MetaFillColorAction() :
+ MetaAction ( MetaActionType::FILLCOLOR ),
+ mbSet ( false )
+{}
+
+MetaFillColorAction::~MetaFillColorAction()
+{}
+
+MetaFillColorAction::MetaFillColorAction( const Color& rColor, bool bSet ) :
+ MetaAction ( MetaActionType::FILLCOLOR ),
+ maColor ( rColor ),
+ mbSet ( bSet )
+{}
+
+void MetaFillColorAction::Execute( OutputDevice* pOut )
+{
+ if( mbSet )
+ pOut->SetFillColor( maColor );
+ else
+ pOut->SetFillColor();
+}
+
+rtl::Reference<MetaAction> MetaFillColorAction::Clone() const
+{
+ return new MetaFillColorAction( *this );
+}
+
+MetaTextColorAction::MetaTextColorAction() :
+ MetaAction(MetaActionType::TEXTCOLOR)
+{}
+
+MetaTextColorAction::~MetaTextColorAction()
+{}
+
+MetaTextColorAction::MetaTextColorAction( const Color& rColor ) :
+ MetaAction ( MetaActionType::TEXTCOLOR ),
+ maColor ( rColor )
+{}
+
+void MetaTextColorAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetTextColor( maColor );
+}
+
+rtl::Reference<MetaAction> MetaTextColorAction::Clone() const
+{
+ return new MetaTextColorAction( *this );
+}
+
+MetaTextFillColorAction::MetaTextFillColorAction() :
+ MetaAction ( MetaActionType::TEXTFILLCOLOR ),
+ mbSet ( false )
+{}
+
+MetaTextFillColorAction::~MetaTextFillColorAction()
+{}
+
+MetaTextFillColorAction::MetaTextFillColorAction( const Color& rColor, bool bSet ) :
+ MetaAction ( MetaActionType::TEXTFILLCOLOR ),
+ maColor ( rColor ),
+ mbSet ( bSet )
+{}
+
+void MetaTextFillColorAction::Execute( OutputDevice* pOut )
+{
+ if( mbSet )
+ pOut->SetTextFillColor( maColor );
+ else
+ pOut->SetTextFillColor();
+}
+
+rtl::Reference<MetaAction> MetaTextFillColorAction::Clone() const
+{
+ return new MetaTextFillColorAction( *this );
+}
+
+MetaTextLineColorAction::MetaTextLineColorAction() :
+ MetaAction ( MetaActionType::TEXTLINECOLOR ),
+ mbSet ( false )
+{}
+
+MetaTextLineColorAction::~MetaTextLineColorAction()
+{}
+
+MetaTextLineColorAction::MetaTextLineColorAction( const Color& rColor, bool bSet ) :
+ MetaAction ( MetaActionType::TEXTLINECOLOR ),
+ maColor ( rColor ),
+ mbSet ( bSet )
+{}
+
+void MetaTextLineColorAction::Execute( OutputDevice* pOut )
+{
+ if( mbSet )
+ pOut->SetTextLineColor( maColor );
+ else
+ pOut->SetTextLineColor();
+}
+
+rtl::Reference<MetaAction> MetaTextLineColorAction::Clone() const
+{
+ return new MetaTextLineColorAction( *this );
+}
+
+MetaOverlineColorAction::MetaOverlineColorAction() :
+ MetaAction ( MetaActionType::OVERLINECOLOR ),
+ mbSet ( false )
+{}
+
+MetaOverlineColorAction::~MetaOverlineColorAction()
+{}
+
+MetaOverlineColorAction::MetaOverlineColorAction( const Color& rColor, bool bSet ) :
+ MetaAction ( MetaActionType::OVERLINECOLOR ),
+ maColor ( rColor ),
+ mbSet ( bSet )
+{}
+
+void MetaOverlineColorAction::Execute( OutputDevice* pOut )
+{
+ if( mbSet )
+ pOut->SetOverlineColor( maColor );
+ else
+ pOut->SetOverlineColor();
+}
+
+rtl::Reference<MetaAction> MetaOverlineColorAction::Clone() const
+{
+ return new MetaOverlineColorAction( *this );
+}
+
+MetaTextAlignAction::MetaTextAlignAction() :
+ MetaAction ( MetaActionType::TEXTALIGN ),
+ maAlign ( ALIGN_TOP )
+{}
+
+MetaTextAlignAction::~MetaTextAlignAction()
+{}
+
+MetaTextAlignAction::MetaTextAlignAction( TextAlign aAlign ) :
+ MetaAction ( MetaActionType::TEXTALIGN ),
+ maAlign ( aAlign )
+{}
+
+void MetaTextAlignAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetTextAlign( maAlign );
+}
+
+rtl::Reference<MetaAction> MetaTextAlignAction::Clone() const
+{
+ return new MetaTextAlignAction( *this );
+}
+
+MetaMapModeAction::MetaMapModeAction() :
+ MetaAction(MetaActionType::MAPMODE)
+{}
+
+MetaMapModeAction::~MetaMapModeAction()
+{}
+
+MetaMapModeAction::MetaMapModeAction( const MapMode& rMapMode ) :
+ MetaAction ( MetaActionType::MAPMODE ),
+ maMapMode ( rMapMode )
+{}
+
+void MetaMapModeAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetMapMode( maMapMode );
+}
+
+rtl::Reference<MetaAction> MetaMapModeAction::Clone() const
+{
+ return new MetaMapModeAction( *this );
+}
+
+void MetaMapModeAction::Scale( double fScaleX, double fScaleY )
+{
+ Point aPoint( maMapMode.GetOrigin() );
+
+ ImplScalePoint( aPoint, fScaleX, fScaleY );
+ maMapMode.SetOrigin( aPoint );
+}
+
+MetaFontAction::MetaFontAction() :
+ MetaAction(MetaActionType::FONT)
+{}
+
+MetaFontAction::~MetaFontAction()
+{}
+
+MetaFontAction::MetaFontAction( const vcl::Font& rFont ) :
+ MetaAction ( MetaActionType::FONT ),
+ maFont ( rFont )
+{
+ // #96876: because RTL_TEXTENCODING_SYMBOL is often set at the StarSymbol font,
+ // we change the textencoding to RTL_TEXTENCODING_UNICODE here, which seems
+ // to be the right way; changing the textencoding at other sources
+ // is too dangerous at the moment
+ if ( IsStarSymbol( maFont.GetFamilyName() )
+ && ( maFont.GetCharSet() != RTL_TEXTENCODING_UNICODE ) )
+ {
+ maFont.SetCharSet( RTL_TEXTENCODING_UNICODE );
+ }
+}
+
+void MetaFontAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetFont( maFont );
+}
+
+rtl::Reference<MetaAction> MetaFontAction::Clone() const
+{
+ return new MetaFontAction( *this );
+}
+
+void MetaFontAction::Scale( double fScaleX, double fScaleY )
+{
+ const Size aSize(
+ FRound(maFont.GetFontSize().Width() * fabs(fScaleX)),
+ FRound(maFont.GetFontSize().Height() * fabs(fScaleY)));
+ maFont.SetFontSize( aSize );
+}
+
+MetaPushAction::MetaPushAction() :
+ MetaAction ( MetaActionType::PUSH ),
+ mnFlags ( vcl::PushFlags::NONE )
+{}
+
+MetaPushAction::~MetaPushAction()
+{}
+
+MetaPushAction::MetaPushAction( vcl::PushFlags nFlags ) :
+ MetaAction ( MetaActionType::PUSH ),
+ mnFlags ( nFlags )
+{}
+
+void MetaPushAction::Execute( OutputDevice* pOut )
+{
+ pOut->Push( mnFlags );
+}
+
+rtl::Reference<MetaAction> MetaPushAction::Clone() const
+{
+ return new MetaPushAction( *this );
+}
+
+MetaPopAction::MetaPopAction() :
+ MetaAction(MetaActionType::POP)
+{}
+
+MetaPopAction::~MetaPopAction()
+{}
+
+void MetaPopAction::Execute( OutputDevice* pOut )
+{
+ pOut->Pop();
+}
+
+rtl::Reference<MetaAction> MetaPopAction::Clone() const
+{
+ return new MetaPopAction( *this );
+}
+
+MetaRasterOpAction::MetaRasterOpAction() :
+ MetaAction ( MetaActionType::RASTEROP ),
+ meRasterOp ( RasterOp::OverPaint )
+{}
+
+MetaRasterOpAction::~MetaRasterOpAction()
+{}
+
+MetaRasterOpAction::MetaRasterOpAction( RasterOp eRasterOp ) :
+ MetaAction ( MetaActionType::RASTEROP ),
+ meRasterOp ( eRasterOp )
+{
+}
+
+void MetaRasterOpAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetRasterOp( meRasterOp );
+}
+
+rtl::Reference<MetaAction> MetaRasterOpAction::Clone() const
+{
+ return new MetaRasterOpAction( *this );
+}
+
+MetaTransparentAction::MetaTransparentAction() :
+ MetaAction ( MetaActionType::Transparent ),
+ mnTransPercent ( 0 )
+{}
+
+MetaTransparentAction::~MetaTransparentAction()
+{}
+
+MetaTransparentAction::MetaTransparentAction( const tools::PolyPolygon& rPolyPoly, sal_uInt16 nTransPercent ) :
+ MetaAction ( MetaActionType::Transparent ),
+ maPolyPoly ( rPolyPoly ),
+ mnTransPercent ( nTransPercent )
+{}
+
+void MetaTransparentAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawTransparent( maPolyPoly, mnTransPercent );
+}
+
+rtl::Reference<MetaAction> MetaTransparentAction::Clone() const
+{
+ return new MetaTransparentAction( *this );
+}
+
+void MetaTransparentAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPolyPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaTransparentAction::Scale( double fScaleX, double fScaleY )
+{
+ for( sal_uInt16 i = 0, nCount = maPolyPoly.Count(); i < nCount; i++ )
+ ImplScalePoly( maPolyPoly[ i ], fScaleX, fScaleY );
+}
+
+MetaFloatTransparentAction::MetaFloatTransparentAction() :
+ MetaAction(MetaActionType::FLOATTRANSPARENT)
+{}
+
+MetaFloatTransparentAction::~MetaFloatTransparentAction()
+{}
+
+MetaFloatTransparentAction::MetaFloatTransparentAction( const GDIMetaFile& rMtf, const Point& rPos,
+ const Size& rSize, const Gradient& rGradient ) :
+ MetaAction ( MetaActionType::FLOATTRANSPARENT ),
+ maMtf ( rMtf ),
+ maPoint ( rPos ),
+ maSize ( rSize ),
+ maGradient ( rGradient )
+{}
+
+void MetaFloatTransparentAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawTransparent( maMtf, maPoint, maSize, maGradient );
+}
+
+rtl::Reference<MetaAction> MetaFloatTransparentAction::Clone() const
+{
+ return new MetaFloatTransparentAction( *this );
+}
+
+void MetaFloatTransparentAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPoint.Move( nHorzMove, nVertMove );
+}
+
+void MetaFloatTransparentAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maPoint, maSize);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maPoint = aRectangle.TopLeft();
+ maSize = aRectangle.GetSize();
+}
+
+MetaEPSAction::MetaEPSAction() :
+ MetaAction(MetaActionType::EPS)
+{}
+
+MetaEPSAction::~MetaEPSAction()
+{}
+
+MetaEPSAction::MetaEPSAction( const Point& rPoint, const Size& rSize,
+ const GfxLink& rGfxLink, const GDIMetaFile& rSubst ) :
+ MetaAction ( MetaActionType::EPS ),
+ maGfxLink ( rGfxLink ),
+ maSubst ( rSubst ),
+ maPoint ( rPoint ),
+ maSize ( rSize )
+{}
+
+void MetaEPSAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawEPS( maPoint, maSize, maGfxLink, &maSubst );
+}
+
+rtl::Reference<MetaAction> MetaEPSAction::Clone() const
+{
+ return new MetaEPSAction( *this );
+}
+
+void MetaEPSAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPoint.Move( nHorzMove, nVertMove );
+}
+
+void MetaEPSAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maPoint, maSize);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maPoint = aRectangle.TopLeft();
+ maSize = aRectangle.GetSize();
+}
+
+MetaRefPointAction::MetaRefPointAction() :
+ MetaAction ( MetaActionType::REFPOINT ),
+ mbSet ( false )
+{}
+
+MetaRefPointAction::~MetaRefPointAction()
+{}
+
+MetaRefPointAction::MetaRefPointAction( const Point& rRefPoint, bool bSet ) :
+ MetaAction ( MetaActionType::REFPOINT ),
+ maRefPoint ( rRefPoint ),
+ mbSet ( bSet )
+{}
+
+void MetaRefPointAction::Execute( OutputDevice* pOut )
+{
+ if( mbSet )
+ pOut->SetRefPoint( maRefPoint );
+ else
+ pOut->SetRefPoint();
+}
+
+rtl::Reference<MetaAction> MetaRefPointAction::Clone() const
+{
+ return new MetaRefPointAction( *this );
+}
+
+MetaCommentAction::MetaCommentAction() :
+ MetaAction ( MetaActionType::COMMENT ),
+ mnValue ( 0 )
+{
+ ImplInitDynamicData( nullptr, 0UL );
+}
+
+MetaCommentAction::MetaCommentAction( const MetaCommentAction& rAct ) :
+ MetaAction ( MetaActionType::COMMENT ),
+ maComment ( rAct.maComment ),
+ mnValue ( rAct.mnValue )
+{
+ ImplInitDynamicData( rAct.mpData.get(), rAct.mnDataSize );
+}
+
+MetaCommentAction::MetaCommentAction( const OString& rComment, sal_Int32 nValue, const sal_uInt8* pData, sal_uInt32 nDataSize ) :
+ MetaAction ( MetaActionType::COMMENT ),
+ maComment ( rComment ),
+ mnValue ( nValue )
+{
+ ImplInitDynamicData( pData, nDataSize );
+}
+
+MetaCommentAction::~MetaCommentAction()
+{
+}
+
+void MetaCommentAction::ImplInitDynamicData( const sal_uInt8* pData, sal_uInt32 nDataSize )
+{
+ if ( nDataSize && pData )
+ {
+ mnDataSize = nDataSize;
+ mpData.reset( new sal_uInt8[ mnDataSize ] );
+ memcpy( mpData.get(), pData, mnDataSize );
+ }
+ else
+ {
+ mnDataSize = 0;
+ mpData = nullptr;
+ }
+}
+
+void MetaCommentAction::Execute( OutputDevice* pOut )
+{
+ if ( pOut->GetConnectMetaFile() )
+ {
+ pOut->GetConnectMetaFile()->AddAction( this );
+ }
+}
+
+rtl::Reference<MetaAction> MetaCommentAction::Clone() const
+{
+ return new MetaCommentAction( *this );
+}
+
+void MetaCommentAction::Move( tools::Long nXMove, tools::Long nYMove )
+{
+ if ( !(nXMove || nYMove) )
+ return;
+
+ if ( !(mnDataSize && mpData) )
+ return;
+
+ bool bPathStroke = (maComment == "XPATHSTROKE_SEQ_BEGIN");
+ if ( !(bPathStroke || maComment == "XPATHFILL_SEQ_BEGIN") )
+ return;
+
+ SvMemoryStream aMemStm( static_cast<void*>(mpData.get()), mnDataSize, StreamMode::READ );
+ SvMemoryStream aDest;
+ if ( bPathStroke )
+ {
+ SvtGraphicStroke aStroke;
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+
+ tools::Polygon aPath;
+ aStroke.getPath( aPath );
+ aPath.Move( nXMove, nYMove );
+ aStroke.setPath( aPath );
+
+ tools::PolyPolygon aStartArrow;
+ aStroke.getStartArrow(aStartArrow);
+ aStartArrow.Move(nXMove, nYMove);
+ aStroke.setStartArrow(aStartArrow);
+
+ tools::PolyPolygon aEndArrow;
+ aStroke.getEndArrow(aEndArrow);
+ aEndArrow.Move(nXMove, nYMove);
+ aStroke.setEndArrow(aEndArrow);
+
+ WriteSvtGraphicStroke( aDest, aStroke );
+ }
+ else
+ {
+ SvtGraphicFill aFill;
+ ReadSvtGraphicFill( aMemStm, aFill );
+
+ tools::PolyPolygon aPath;
+ aFill.getPath( aPath );
+ aPath.Move( nXMove, nYMove );
+ aFill.setPath( aPath );
+
+ WriteSvtGraphicFill( aDest, aFill );
+ }
+ mpData.reset();
+ ImplInitDynamicData( static_cast<const sal_uInt8*>( aDest.GetData() ), aDest.Tell() );
+}
+
+// SJ: 25.07.06 #i56656# we are not able to mirror certain kind of
+// comments properly, especially the XPATHSTROKE and XPATHFILL lead to
+// problems, so it is better to remove these comments when mirroring
+// FIXME: fake comment to apply the next hunk in the right location
+void MetaCommentAction::Scale( double fXScale, double fYScale )
+{
+ if (( fXScale == 1.0 ) && ( fYScale == 1.0 ))
+ return;
+
+ if ( !(mnDataSize && mpData) )
+ return;
+
+ bool bPathStroke = (maComment == "XPATHSTROKE_SEQ_BEGIN");
+ if ( bPathStroke || maComment == "XPATHFILL_SEQ_BEGIN" )
+ {
+ SvMemoryStream aMemStm( static_cast<void*>(mpData.get()), mnDataSize, StreamMode::READ );
+ SvMemoryStream aDest;
+ if ( bPathStroke )
+ {
+ SvtGraphicStroke aStroke;
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+ aStroke.scale( fXScale, fYScale );
+ WriteSvtGraphicStroke( aDest, aStroke );
+ }
+ else
+ {
+ SvtGraphicFill aFill;
+ ReadSvtGraphicFill( aMemStm, aFill );
+ tools::PolyPolygon aPath;
+ aFill.getPath( aPath );
+ aPath.Scale( fXScale, fYScale );
+ aFill.setPath( aPath );
+ WriteSvtGraphicFill( aDest, aFill );
+ }
+ mpData.reset();
+ ImplInitDynamicData( static_cast<const sal_uInt8*>( aDest.GetData() ), aDest.Tell() );
+ } else if( maComment == "EMF_PLUS_HEADER_INFO" ){
+ SvMemoryStream aMemStm( static_cast<void*>(mpData.get()), mnDataSize, StreamMode::READ );
+ SvMemoryStream aDest;
+
+ sal_Int32 nLeft(0), nRight(0), nTop(0), nBottom(0);
+ sal_Int32 nPixX(0), nPixY(0), nMillX(0), nMillY(0);
+ float m11(0), m12(0), m21(0), m22(0), mdx(0), mdy(0);
+
+ // read data
+ aMemStm.ReadInt32( nLeft ).ReadInt32( nTop ).ReadInt32( nRight ).ReadInt32( nBottom );
+ aMemStm.ReadInt32( nPixX ).ReadInt32( nPixY ).ReadInt32( nMillX ).ReadInt32( nMillY );
+ aMemStm.ReadFloat( m11 ).ReadFloat( m12 ).ReadFloat( m21 ).ReadFloat( m22 ).ReadFloat( mdx ).ReadFloat( mdy );
+
+ // add scale to the transformation
+ m11 *= fXScale;
+ m12 *= fXScale;
+ m22 *= fYScale;
+ m21 *= fYScale;
+
+ // prepare new data
+ aDest.WriteInt32( nLeft ).WriteInt32( nTop ).WriteInt32( nRight ).WriteInt32( nBottom );
+ aDest.WriteInt32( nPixX ).WriteInt32( nPixY ).WriteInt32( nMillX ).WriteInt32( nMillY );
+ aDest.WriteFloat( m11 ).WriteFloat( m12 ).WriteFloat( m21 ).WriteFloat( m22 ).WriteFloat( mdx ).WriteFloat( mdy );
+
+ // save them
+ ImplInitDynamicData( static_cast<const sal_uInt8*>( aDest.GetData() ), aDest.Tell() );
+ }
+}
+
+MetaLayoutModeAction::MetaLayoutModeAction() :
+ MetaAction ( MetaActionType::LAYOUTMODE ),
+ mnLayoutMode( vcl::text::ComplexTextLayoutFlags::Default )
+{}
+
+MetaLayoutModeAction::~MetaLayoutModeAction()
+{}
+
+MetaLayoutModeAction::MetaLayoutModeAction( vcl::text::ComplexTextLayoutFlags nLayoutMode ) :
+ MetaAction ( MetaActionType::LAYOUTMODE ),
+ mnLayoutMode( nLayoutMode )
+{}
+
+void MetaLayoutModeAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetLayoutMode( mnLayoutMode );
+}
+
+rtl::Reference<MetaAction> MetaLayoutModeAction::Clone() const
+{
+ return new MetaLayoutModeAction( *this );
+}
+
+MetaTextLanguageAction::MetaTextLanguageAction() :
+ MetaAction ( MetaActionType::TEXTLANGUAGE ),
+ meTextLanguage( LANGUAGE_DONTKNOW )
+{}
+
+MetaTextLanguageAction::~MetaTextLanguageAction()
+{}
+
+MetaTextLanguageAction::MetaTextLanguageAction( LanguageType eTextLanguage ) :
+ MetaAction ( MetaActionType::TEXTLANGUAGE ),
+ meTextLanguage( eTextLanguage )
+{}
+
+void MetaTextLanguageAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetDigitLanguage( meTextLanguage );
+}
+
+rtl::Reference<MetaAction> MetaTextLanguageAction::Clone() const
+{
+ return new MetaTextLanguageAction( *this );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/mtfxmldump.cxx b/vcl/source/gdi/mtfxmldump.cxx
new file mode 100644
index 000000000..2dc7e7b9d
--- /dev/null
+++ b/vcl/source/gdi/mtfxmldump.cxx
@@ -0,0 +1,1491 @@
+/* -*- 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/.
+ */
+
+#include <vcl/mtfxmldump.hxx>
+#include <tools/XmlWriter.hxx>
+#include <tools/fract.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <rtl/string.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <sstream>
+
+namespace
+{
+
+OUString collectPushFlags(vcl::PushFlags nFlags)
+{
+ if ((nFlags & vcl::PushFlags::ALL) == vcl::PushFlags::ALL)
+ return "PushAll";
+ else if ((nFlags & PUSH_ALLFONT) == PUSH_ALLFONT)
+ return "PushAllFont";
+
+ std::vector<OUString> aStrings;
+
+ if (nFlags & vcl::PushFlags::LINECOLOR)
+ aStrings.emplace_back("PushLineColor");
+ if (nFlags & vcl::PushFlags::FILLCOLOR)
+ aStrings.emplace_back("PushFillColor");
+ if (nFlags & vcl::PushFlags::FONT)
+ aStrings.emplace_back("PushFont");
+ if (nFlags & vcl::PushFlags::TEXTCOLOR)
+ aStrings.emplace_back("PushTextColor");
+ if (nFlags & vcl::PushFlags::MAPMODE)
+ aStrings.emplace_back("PushMapMode");
+ if (nFlags & vcl::PushFlags::CLIPREGION)
+ aStrings.emplace_back("PushClipRegion");
+ if (nFlags & vcl::PushFlags::RASTEROP)
+ aStrings.emplace_back("PushRasterOp");
+ if (nFlags & vcl::PushFlags::TEXTFILLCOLOR)
+ aStrings.emplace_back("PushTextFillColor");
+ if (nFlags & vcl::PushFlags::TEXTALIGN)
+ aStrings.emplace_back("PushTextAlign");
+ if (nFlags & vcl::PushFlags::REFPOINT)
+ aStrings.emplace_back("PushRefPoint");
+ if (nFlags & vcl::PushFlags::TEXTLINECOLOR)
+ aStrings.emplace_back("PushTextLineColor");
+ if (nFlags & vcl::PushFlags::TEXTLAYOUTMODE)
+ aStrings.emplace_back("PushTextLayoutMode");
+ if (nFlags & vcl::PushFlags::TEXTLANGUAGE)
+ aStrings.emplace_back("PushTextLanguage");
+ if (nFlags & vcl::PushFlags::OVERLINECOLOR)
+ aStrings.emplace_back("PushOverlineColor");
+
+ OUString aString;
+
+ if (aStrings.empty())
+ return aString;
+
+ aString = aStrings[0];
+ for (size_t i = 1; i < aStrings.size(); ++i)
+ {
+ aString += ", " + aStrings[i];
+ }
+ return aString;
+}
+
+OUString convertDrawTextFlagsToString(DrawTextFlags eDrawTextFlags)
+{
+ std::vector<OUString> aStrings;
+ if (eDrawTextFlags & DrawTextFlags::Disable)
+ aStrings.emplace_back("Disable");
+ if (eDrawTextFlags & DrawTextFlags::Mnemonic)
+ aStrings.emplace_back("Mnemonic");
+ if (eDrawTextFlags & DrawTextFlags::Mono)
+ aStrings.emplace_back("Mono");
+ if (eDrawTextFlags & DrawTextFlags::Clip)
+ aStrings.emplace_back("Clip");
+ if (eDrawTextFlags & DrawTextFlags::Left)
+ aStrings.emplace_back("Left");
+ if (eDrawTextFlags & DrawTextFlags::Center)
+ aStrings.emplace_back("Center");
+ if (eDrawTextFlags & DrawTextFlags::Right)
+ aStrings.emplace_back("Right");
+ if (eDrawTextFlags & DrawTextFlags::Top)
+ aStrings.emplace_back("Top");
+ if (eDrawTextFlags & DrawTextFlags::VCenter)
+ aStrings.emplace_back("VCenter");
+ if (eDrawTextFlags & DrawTextFlags::Bottom)
+ aStrings.emplace_back("Bottom");
+ if (eDrawTextFlags & DrawTextFlags::EndEllipsis)
+ aStrings.emplace_back("EndEllipsis");
+ if (eDrawTextFlags & DrawTextFlags::PathEllipsis)
+ aStrings.emplace_back("PathEllipsis");
+ if (eDrawTextFlags & DrawTextFlags::MultiLine)
+ aStrings.emplace_back("MultiLine");
+ if (eDrawTextFlags & DrawTextFlags::WordBreak)
+ aStrings.emplace_back("WordBreak");
+ if (eDrawTextFlags & DrawTextFlags::NewsEllipsis)
+ aStrings.emplace_back("NewsEllipsis");
+ if (eDrawTextFlags & DrawTextFlags::WordBreakHyphenation)
+ aStrings.emplace_back("WordBreakHyphenation");
+ if (eDrawTextFlags & DrawTextFlags::CenterEllipsis)
+ aStrings.emplace_back("CenterEllipsis");
+ if (eDrawTextFlags & DrawTextFlags::HideMnemonic)
+ aStrings.emplace_back("HideMnemonic");
+
+ OUString aString;
+
+ if (aStrings.empty())
+ return "None";
+
+ aString = aStrings[0];
+ for (size_t i = 1; i < aStrings.size(); ++i)
+ {
+ aString += " " + aStrings[i];
+ }
+ return aString;
+};
+
+OUString convertRopToString(RasterOp eRop)
+{
+ switch (eRop)
+ {
+ case RasterOp::OverPaint: return "overpaint";
+ case RasterOp::Xor: return "xor";
+ case RasterOp::N0: return "0";
+ case RasterOp::N1: return "1";
+ case RasterOp::Invert: return "invert";
+ }
+ return OUString();
+}
+
+OUString convertTextAlignToString(TextAlign eAlign)
+{
+ switch (eAlign)
+ {
+ case ALIGN_BASELINE: return "baseline";
+ case ALIGN_BOTTOM: return "bottom";
+ case ALIGN_TOP: return "top";
+ case TextAlign_FORCE_EQUAL_SIZE: return "equalsize";
+ }
+ return OUString();
+}
+
+OUString convertColorToString(Color aColor)
+{
+ OUString aRGBString = aColor.AsRGBHexString();
+ return "#" + aRGBString;
+}
+
+OUString convertLineStyleToString(LineStyle eAlign)
+{
+ switch (eAlign)
+ {
+ case LineStyle::NONE: return "none";
+ case LineStyle::Solid: return "solid";
+ case LineStyle::Dash: return "dash";
+ default: break;
+ }
+ return OUString();
+}
+
+OUString convertLineJoinToString(basegfx::B2DLineJoin eJoin)
+{
+ switch (eJoin)
+ {
+ default:
+ case basegfx::B2DLineJoin::NONE: return "none";
+ case basegfx::B2DLineJoin::Bevel: return "bevel";
+ case basegfx::B2DLineJoin::Miter: return "miter";
+ case basegfx::B2DLineJoin::Round: return "round";
+ }
+}
+
+OUString convertLineCapToString(css::drawing::LineCap eCap)
+{
+ switch (eCap)
+ {
+ default:
+ case css::drawing::LineCap_BUTT: return "butt";
+ case css::drawing::LineCap_ROUND: return "round";
+ case css::drawing::LineCap_SQUARE: return "square";
+ }
+}
+
+OUString convertPolygonFlags(PolyFlags eFlags)
+{
+ switch (eFlags)
+ {
+ default:
+ case PolyFlags::Normal: return "normal";
+ case PolyFlags::Control: return "control";
+ case PolyFlags::Smooth: return "smooth";
+ case PolyFlags::Symmetric: return "symmetric";
+ }
+}
+
+OUString convertFontWeightToString(FontWeight eFontWeight)
+{
+ switch (eFontWeight)
+ {
+ case WEIGHT_DONTKNOW: return "unknown";
+ case WEIGHT_THIN: return "thin";
+ case WEIGHT_ULTRALIGHT: return "ultralight";
+ case WEIGHT_LIGHT: return "light";
+ case WEIGHT_SEMILIGHT: return "semilight";
+ case WEIGHT_NORMAL: return "normal";
+ case WEIGHT_MEDIUM: return "medium";
+ case WEIGHT_SEMIBOLD: return "semibold";
+ case WEIGHT_BOLD: return "bold";
+ case WEIGHT_ULTRABOLD: return "ultrabold";
+ case WEIGHT_BLACK: return "black";
+ case FontWeight_FORCE_EQUAL_SIZE: return "equalsize";
+ }
+ return OUString();
+}
+
+OUString convertFontStrikeoutToString(FontStrikeout eFontStrikeout)
+{
+ switch (eFontStrikeout)
+ {
+ case STRIKEOUT_NONE: return "none";
+ case STRIKEOUT_SINGLE: return "single";
+ case STRIKEOUT_DOUBLE: return "double";
+ case STRIKEOUT_DONTKNOW: return "dontknow";
+ case STRIKEOUT_BOLD: return "bold";
+ case STRIKEOUT_SLASH: return "slash";
+ case STRIKEOUT_X: return "x";
+ case FontStrikeout_FORCE_EQUAL_SIZE: return "equalsize";
+ }
+ return OUString();
+}
+
+OUString convertFontLineStyleToString(FontLineStyle eFontLineStyle)
+{
+ switch (eFontLineStyle)
+ {
+ case LINESTYLE_NONE: return "none";
+ case LINESTYLE_SINGLE: return "single";
+ case LINESTYLE_DOUBLE: return "double";
+ case LINESTYLE_DOTTED: return "dotted";
+ case LINESTYLE_DONTKNOW: return "dontknow";
+ case LINESTYLE_DASH: return "dash";
+ case LINESTYLE_LONGDASH: return "longdash";
+ case LINESTYLE_DASHDOT: return "dashdot";
+ case LINESTYLE_DASHDOTDOT: return "dashdotdot";
+ case LINESTYLE_SMALLWAVE: return "smallwave";
+ case LINESTYLE_WAVE: return "wave";
+ case LINESTYLE_DOUBLEWAVE: return "doublewave";
+ case LINESTYLE_BOLD: return "bold";
+ case LINESTYLE_BOLDDOTTED: return "bolddotted";
+ case LINESTYLE_BOLDDASH: return "bolddash";
+ case LINESTYLE_BOLDLONGDASH: return "boldlongdash";
+ case LINESTYLE_BOLDDASHDOT: return "bolddashdot";
+ case LINESTYLE_BOLDDASHDOTDOT: return "bolddashdotdot";
+ case LINESTYLE_BOLDWAVE: return "boldwave";
+ case FontLineStyle_FORCE_EQUAL_SIZE: return "equalsize";
+ }
+ return OUString();
+}
+
+OString convertLineStyleToString(const MetaActionType nActionType)
+{
+ switch (nActionType)
+ {
+ case MetaActionType::NONE: return "null";
+ case MetaActionType::PIXEL: return "pixel";
+ case MetaActionType::POINT: return "point";
+ case MetaActionType::LINE: return "line";
+ case MetaActionType::RECT: return "rect";
+ case MetaActionType::ROUNDRECT: return "roundrect";
+ case MetaActionType::ELLIPSE: return "ellipse";
+ case MetaActionType::ARC: return "arc";
+ case MetaActionType::PIE: return "pie";
+ case MetaActionType::CHORD: return "chord";
+ case MetaActionType::POLYLINE: return "polyline";
+ case MetaActionType::POLYGON: return "polygon";
+ case MetaActionType::POLYPOLYGON: return "polypolygon";
+ case MetaActionType::TEXT: return "text";
+ case MetaActionType::TEXTARRAY: return "textarray";
+ case MetaActionType::STRETCHTEXT: return "stretchtext";
+ case MetaActionType::TEXTRECT: return "textrect";
+ case MetaActionType::TEXTLINE: return "textline";
+ case MetaActionType::BMP: return "bmp";
+ case MetaActionType::BMPSCALE: return "bmpscale";
+ case MetaActionType::BMPSCALEPART: return "bmpscalepart";
+ case MetaActionType::BMPEX: return "bmpex";
+ case MetaActionType::BMPEXSCALE: return "bmpexscale";
+ case MetaActionType::BMPEXSCALEPART: return "bmpexscalepart";
+ case MetaActionType::MASK: return "mask";
+ case MetaActionType::MASKSCALE: return "maskscale";
+ case MetaActionType::MASKSCALEPART: return "maskscalepart";
+ case MetaActionType::GRADIENT: return "gradient";
+ case MetaActionType::GRADIENTEX: return "gradientex";
+ case MetaActionType::HATCH: return "hatch";
+ case MetaActionType::WALLPAPER: return "wallpaper";
+ case MetaActionType::CLIPREGION: return "clipregion";
+ case MetaActionType::ISECTRECTCLIPREGION: return "sectrectclipregion";
+ case MetaActionType::ISECTREGIONCLIPREGION: return "sectregionclipregion";
+ case MetaActionType::MOVECLIPREGION: return "moveclipregion";
+ case MetaActionType::LINECOLOR: return "linecolor";
+ case MetaActionType::FILLCOLOR: return "fillcolor";
+ case MetaActionType::TEXTCOLOR: return "textcolor";
+ case MetaActionType::TEXTFILLCOLOR: return "textfillcolor";
+ case MetaActionType::TEXTLINECOLOR: return "textlinecolor";
+ case MetaActionType::OVERLINECOLOR: return "overlinecolor";
+ case MetaActionType::TEXTALIGN: return "textalign";
+ case MetaActionType::MAPMODE: return "mapmode";
+ case MetaActionType::FONT: return "font";
+ case MetaActionType::PUSH: return "push";
+ case MetaActionType::POP: return "pop";
+ case MetaActionType::RASTEROP: return "rasterop";
+ case MetaActionType::Transparent: return "transparent";
+ case MetaActionType::FLOATTRANSPARENT: return "floattransparent";
+ case MetaActionType::EPS: return "eps";
+ case MetaActionType::REFPOINT: return "refpoint";
+ case MetaActionType::COMMENT: return "comment";
+ case MetaActionType::LAYOUTMODE: return "layoutmode";
+ case MetaActionType::TEXTLANGUAGE: return "textlanguage";
+ }
+ return "";
+}
+
+OUString convertBitmapExTransparentType(BitmapEx const & rBitmapEx)
+{
+ if (rBitmapEx.IsAlpha())
+ return "bitmap";
+ else
+ return "none";
+}
+
+OUString convertMapUnitToString(MapUnit eUnit)
+{
+ switch (eUnit)
+ {
+ default:
+ case MapUnit::LASTENUMDUMMY: return "LASTENUMDUMMY";
+ case MapUnit::Map1000thInch: return "Map1000thInch";
+ case MapUnit::Map100thInch: return "Map100thInch";
+ case MapUnit::Map100thMM: return "Map100thMM";
+ case MapUnit::Map10thInch: return "Map10thInch";
+ case MapUnit::Map10thMM: return "Map10thMM";
+ case MapUnit::MapAppFont: return "MapAppFont";
+ case MapUnit::MapCM: return "MapCM";
+ case MapUnit::MapInch: return "MapInch";
+ case MapUnit::MapMM: return "MapMM";
+ case MapUnit::MapPixel: return "MapPixel";
+ case MapUnit::MapPoint: return "MapPoint";
+ case MapUnit::MapRelative: return "MapRelative";
+ case MapUnit::MapSysFont: return "MapSysFont";
+ case MapUnit::MapTwip: return "MapTwip";
+ }
+}
+
+OUString convertFractionToString(const Fraction& aFraction)
+{
+ std::stringstream ss;
+
+ ss << aFraction;
+
+ return OUString::createFromAscii(ss.str().c_str());
+}
+
+OUString convertGradientStyle(GradientStyle eStyle)
+{
+ switch (eStyle)
+ {
+ case GradientStyle::Linear: return "Linear";
+ case GradientStyle::Axial: return "Axial";
+ case GradientStyle::Radial: return "Radial";
+ case GradientStyle::Elliptical: return "Elliptical";
+ case GradientStyle::Square: return "Square";
+ case GradientStyle::Rect: return "Rect";
+ case GradientStyle::FORCE_EQUAL_SIZE: return "ForceEqualSize";
+ }
+ return OUString();
+}
+
+OUString convertHatchStyle(HatchStyle eStyle)
+{
+ switch (eStyle)
+ {
+ case HatchStyle::Single: return "Single";
+ case HatchStyle::Double: return "Double";
+ case HatchStyle::Triple: return "Triple";
+ case HatchStyle::FORCE_EQUAL_SIZE: return "ForceEqualSize";
+ }
+ return OUString();
+}
+
+OUString convertLanguageTypeToString(LanguageType rLanguageType)
+{
+ std::stringstream ss;
+ ss << std::hex << std::setfill ('0') << std::setw(4) << rLanguageType.get();
+ return "#" + OUString::createFromAscii(ss.str().c_str());
+}
+
+OUString convertWallpaperStyleToString(WallpaperStyle eWallpaperStyle)
+{
+ switch (eWallpaperStyle)
+ {
+ case WallpaperStyle::NONE: return "NONE";
+ case WallpaperStyle::Tile: return "Tile";
+ case WallpaperStyle::Center: return "Center";
+ case WallpaperStyle::Scale: return "Scale";
+ case WallpaperStyle::TopLeft: return "TopLeft";
+ case WallpaperStyle::Top: return "Top";
+ case WallpaperStyle::TopRight: return "TopRight";
+ case WallpaperStyle::Left: return "Left";
+ case WallpaperStyle::Right: return "Right";
+ case WallpaperStyle::BottomLeft: return "BottomLeft";
+ case WallpaperStyle::Bottom: return "Bottom";
+ case WallpaperStyle::BottomRight: return "BottomRight";
+ case WallpaperStyle::ApplicationGradient: return "ApplicationGradient";
+ }
+ return OUString();
+}
+
+OUString convertPixelFormatToString(vcl::PixelFormat ePixelFormat)
+{
+ switch (ePixelFormat)
+ {
+ case vcl::PixelFormat::INVALID: return "INVALID";
+ case vcl::PixelFormat::N1_BPP: return "1BPP";
+ case vcl::PixelFormat::N8_BPP: return "8BPP";
+ case vcl::PixelFormat::N24_BPP: return "24BPP";
+ case vcl::PixelFormat::N32_BPP: return "32BPP";
+ }
+ return OUString();
+}
+
+OUString convertComplexTestLayoutFlags(vcl::text::ComplexTextLayoutFlags eComplexTestLayoutFlags)
+{
+ switch(eComplexTestLayoutFlags)
+ {
+ default:
+ case vcl::text::ComplexTextLayoutFlags::Default: return "#0000";
+ case vcl::text::ComplexTextLayoutFlags::BiDiRtl: return "#0001";
+ case vcl::text::ComplexTextLayoutFlags::BiDiStrong: return "#0002";
+ case vcl::text::ComplexTextLayoutFlags::TextOriginLeft: return "#0004";
+ case vcl::text::ComplexTextLayoutFlags::TextOriginRight: return "#0008";
+ }
+}
+
+OUString convertGfxLinkTypeToString(GfxLinkType eGfxLinkType)
+{
+ switch(eGfxLinkType)
+ {
+ case GfxLinkType::EpsBuffer: return "EpsBuffer";
+ case GfxLinkType::NativeBmp: return "NativeBmp";
+ case GfxLinkType::NativeGif: return "NativeGif";
+ case GfxLinkType::NativeJpg: return "NativeJpg";
+ case GfxLinkType::NativeMet: return "NativeMet";
+ case GfxLinkType::NativeMov: return "NativeMov";
+ case GfxLinkType::NativePct: return "NativePct";
+ case GfxLinkType::NativePdf: return "NativePdf";
+ case GfxLinkType::NativePng: return "NativePng";
+ case GfxLinkType::NativeSvg: return "NativeSvg";
+ case GfxLinkType::NativeTif: return "NativeTif";
+ case GfxLinkType::NativeWmf: return "NativeWmf";
+ case GfxLinkType::NativeWebp: return "NativeWebp";
+ case GfxLinkType::NONE: return "None";
+ }
+ return OUString();
+}
+
+OUString hex32(sal_uInt32 nNumber)
+{
+ std::stringstream ss;
+ ss << std::hex << std::setfill('0') << std::setw(8) << nNumber;
+ return OUString::createFromAscii(ss.str().c_str());
+}
+
+OUString toHexString(const sal_uInt8* nData, sal_uInt32 nDataSize){
+
+ std::stringstream aStrm;
+ for (sal_uInt32 i = 0; i < nDataSize; i++)
+ {
+ aStrm << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(nData[i]);
+ }
+
+ return OUString::createFromAscii(aStrm.str().c_str());
+}
+
+void writePoint(tools::XmlWriter& rWriter, Point const& rPoint)
+{
+ rWriter.attribute("x", rPoint.X());
+ rWriter.attribute("y", rPoint.Y());
+}
+
+void writeStartPoint(tools::XmlWriter& rWriter, Point const& rPoint)
+{
+ rWriter.attribute("startx", rPoint.X());
+ rWriter.attribute("starty", rPoint.Y());
+}
+
+void writeEndPoint(tools::XmlWriter& rWriter, Point const& rPoint)
+{
+ rWriter.attribute("endx", rPoint.X());
+ rWriter.attribute("endy", rPoint.Y());
+}
+
+void writeSize(tools::XmlWriter& rWriter, Size const& rSize)
+{
+ rWriter.attribute("width", rSize.Width());
+ rWriter.attribute("height", rSize.Height());
+}
+
+void writeRectangle(tools::XmlWriter& rWriter, tools::Rectangle const& rRectangle)
+{
+ rWriter.attribute("left", rRectangle.Left());
+ rWriter.attribute("top", rRectangle.Top());
+ if (rRectangle.IsWidthEmpty())
+ rWriter.attribute("right", OString("empty"));
+ else
+ rWriter.attribute("right", rRectangle.Right());
+ if (rRectangle.IsHeightEmpty())
+ rWriter.attribute("bottom", OString("empty"));
+ else
+ rWriter.attribute("bottom", rRectangle.Bottom());
+}
+
+void writeMapMode(tools::XmlWriter& rWriter, MapMode const& rMapMode)
+{
+ rWriter.attribute("mapunit", convertMapUnitToString( rMapMode.GetMapUnit() ));
+ writePoint(rWriter, rMapMode.GetOrigin());
+ rWriter.attribute("scalex", convertFractionToString(rMapMode.GetScaleX()));
+ rWriter.attribute("scaley", convertFractionToString(rMapMode.GetScaleY()));
+}
+
+void writeLineInfo(tools::XmlWriter& rWriter, LineInfo const& rLineInfo)
+{
+ rWriter.attribute("style", convertLineStyleToString(rLineInfo.GetStyle()));
+ rWriter.attribute("width", rLineInfo.GetWidth());
+ rWriter.attribute("dashlen", rLineInfo.GetDashLen());
+ rWriter.attribute("dashcount", rLineInfo.GetDashCount());
+ rWriter.attribute("dotlen", rLineInfo.GetDotLen());
+ rWriter.attribute("dotcount", rLineInfo.GetDotCount());
+ rWriter.attribute("distance", rLineInfo.GetDistance());
+ rWriter.attribute("join", convertLineJoinToString(rLineInfo.GetLineJoin()));
+ rWriter.attribute("cap", convertLineCapToString(rLineInfo.GetLineCap()));
+}
+
+void writeGradient(tools::XmlWriter& rWriter, Gradient const& rGradient)
+{
+ rWriter.attribute("style", convertGradientStyle(rGradient.GetStyle()));
+ rWriter.attribute("startcolor", convertColorToString(rGradient.GetStartColor()));
+ rWriter.attribute("endcolor", convertColorToString(rGradient.GetEndColor()));
+ rWriter.attribute("angle", rGradient.GetAngle().get());
+ rWriter.attribute("border", rGradient.GetBorder());
+ rWriter.attribute("offsetx", rGradient.GetOfsX());
+ rWriter.attribute("offsety", rGradient.GetOfsY());
+ rWriter.attribute("startintensity", rGradient.GetStartIntensity());
+ rWriter.attribute("endintensity", rGradient.GetEndIntensity());
+ rWriter.attribute("steps", rGradient.GetSteps());
+}
+
+} // anonymous namespace
+
+MetafileXmlDump::MetafileXmlDump()
+{
+ maFilter.fill(false);
+}
+
+void MetafileXmlDump::filterActionType(const MetaActionType nActionType, bool bShouldFilter)
+{
+ maFilter[nActionType] = bShouldFilter;
+}
+
+void MetafileXmlDump::filterAllActionTypes()
+{
+ maFilter.fill(true);
+}
+
+void MetafileXmlDump::dump(const GDIMetaFile& rMetaFile, SvStream& rStream)
+{
+ tools::XmlWriter aWriter(&rStream);
+ aWriter.startDocument();
+ aWriter.startElement("metafile");
+
+ writeXml(rMetaFile, aWriter);
+
+ aWriter.endElement();
+ aWriter.endDocument();
+}
+
+void MetafileXmlDump::writeXml(const GDIMetaFile& rMetaFile, tools::XmlWriter& rWriter)
+{
+ MapMode aMtfMapMode = rMetaFile.GetPrefMapMode();
+ rWriter.attribute("mapunit", convertMapUnitToString(aMtfMapMode.GetMapUnit()));
+ writePoint(rWriter, aMtfMapMode.GetOrigin());
+ rWriter.attribute("scalex", convertFractionToString(aMtfMapMode.GetScaleX()));
+ rWriter.attribute("scaley", convertFractionToString(aMtfMapMode.GetScaleY()));
+
+ Size aMtfSize = rMetaFile.GetPrefSize();
+ writeSize(rWriter, aMtfSize);
+
+ for(size_t nAction = 0; nAction < rMetaFile.GetActionSize(); ++nAction)
+ {
+ MetaAction* pAction = rMetaFile.GetAction(nAction);
+ const MetaActionType nActionType = pAction->GetType();
+ if (maFilter[nActionType])
+ continue;
+
+ OString sCurrentElementTag = convertLineStyleToString(nActionType);
+
+ switch (nActionType)
+ {
+ case MetaActionType::NONE:
+ {
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::PIXEL:
+ {
+ auto* pMetaAction = static_cast<MetaPixelAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMetaAction->GetPoint());
+ rWriter.attribute("color", convertColorToString(pMetaAction->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::POINT:
+ {
+ auto* pMetaAction = static_cast<MetaPointAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMetaAction->GetPoint());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ MetaLineAction* pMetaLineAction = static_cast<MetaLineAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeStartPoint(rWriter, pMetaLineAction->GetStartPoint());
+ writeEndPoint(rWriter, pMetaLineAction->GetEndPoint());
+
+ writeLineInfo(rWriter, pMetaLineAction->GetLineInfo());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ MetaRectAction* pMetaAction = static_cast<MetaRectAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeRectangle(rWriter, pMetaAction->GetRect());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ auto pMetaAction = static_cast<MetaRoundRectAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeRectangle(rWriter, pMetaAction->GetRect());
+ rWriter.attribute("horizontalround", pMetaAction->GetHorzRound());
+ rWriter.attribute("verticalround", pMetaAction->GetVertRound());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ auto pMetaAction = static_cast<MetaEllipseAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeRectangle(rWriter, pMetaAction->GetRect());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::ARC:
+ {
+ auto pMetaAction = static_cast<MetaArcAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeRectangle(rWriter, pMetaAction->GetRect());
+ writeStartPoint(rWriter, pMetaAction->GetStartPoint());
+ writeEndPoint(rWriter, pMetaAction->GetEndPoint());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::PIE:
+ {
+ auto pMetaAction = static_cast<MetaPieAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeRectangle(rWriter, pMetaAction->GetRect());
+ writeStartPoint(rWriter, pMetaAction->GetStartPoint());
+ writeEndPoint(rWriter, pMetaAction->GetEndPoint());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::CHORD:
+ {
+ auto pMetaAction = static_cast<MetaChordAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeRectangle(rWriter, pMetaAction->GetRect());
+ writeStartPoint(rWriter, pMetaAction->GetStartPoint());
+ writeEndPoint(rWriter, pMetaAction->GetEndPoint());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ MetaPolyLineAction* pMetaPolyLineAction = static_cast<MetaPolyLineAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ writeLineInfo(rWriter, pMetaPolyLineAction->GetLineInfo());
+
+ tools::Polygon aPolygon = pMetaPolyLineAction->GetPolygon();
+ bool bFlags = aPolygon.HasFlags();
+ for (sal_uInt16 i = 0; i < aPolygon.GetSize(); i++)
+ {
+ rWriter.startElement("point");
+ writePoint(rWriter, aPolygon[i]);
+ if (bFlags)
+ rWriter.attribute("flags", convertPolygonFlags(aPolygon.GetFlags(i)));
+ rWriter.endElement();
+ }
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::POLYGON:
+ {
+ MetaPolygonAction* pMetaPolygonAction = static_cast<MetaPolygonAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ tools::Polygon aPolygon = pMetaPolygonAction->GetPolygon();
+ bool bFlags = aPolygon.HasFlags();
+ for (sal_uInt16 i = 0; i < aPolygon.GetSize(); i++)
+ {
+ rWriter.startElement("point");
+ writePoint(rWriter, aPolygon[i]);
+ if (bFlags)
+ rWriter.attribute("flags", convertPolygonFlags(aPolygon.GetFlags(i)));
+ rWriter.endElement();
+ }
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ {
+ MetaPolyPolygonAction *const pMetaPolyPolygonAction = static_cast<MetaPolyPolygonAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ tools::PolyPolygon const& rPolyPolygon(pMetaPolyPolygonAction->GetPolyPolygon());
+
+ for (sal_uInt16 j = 0; j < rPolyPolygon.Count(); ++j)
+ {
+ rWriter.startElement("polygon");
+ tools::Polygon const& rPolygon = rPolyPolygon[j];
+ bool bFlags = rPolygon.HasFlags();
+ for (sal_uInt16 i = 0; i < rPolygon.GetSize(); ++i)
+ {
+ rWriter.startElement("point");
+ writePoint(rWriter, rPolygon[i]);
+ if (bFlags)
+ rWriter.attribute("flags", convertPolygonFlags(rPolygon.GetFlags(i)));
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ auto* pMeta = static_cast<MetaTextAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ rWriter.attribute("index", pMeta->GetIndex());
+ rWriter.attribute("length", pMeta->GetLen());
+ rWriter.startElement("textcontent");
+ rWriter.content(pMeta->GetText());
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ MetaTextArrayAction* pMetaTextArrayAction = static_cast<MetaTextArrayAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ sal_Int32 aIndex = pMetaTextArrayAction->GetIndex();
+ sal_Int32 aLength = pMetaTextArrayAction->GetLen();
+
+ writePoint(rWriter, pMetaTextArrayAction->GetPoint());
+ rWriter.attribute("index", aIndex);
+ rWriter.attribute("length", aLength);
+
+ if (!pMetaTextArrayAction->GetDXArray().empty())
+ {
+ rWriter.startElement("dxarray");
+ OUStringBuffer sDxLengthString;
+ for (sal_Int32 i = 0; i < aLength - aIndex; ++i)
+ {
+ sDxLengthString.append(pMetaTextArrayAction->GetDXArray()[aIndex + i]);
+ sDxLengthString.append(" ");
+ }
+ rWriter.content(sDxLengthString);
+ rWriter.endElement();
+ }
+
+ rWriter.startElement("text");
+
+ const OUString& rStr = pMetaTextArrayAction->GetText();
+ // fix bad XML dump by removing forbidden 0x01
+ // FIXME: expand footnote anchor point 0x01 instead of this
+ if ( rStr.indexOf(0x01) > -1 )
+ rWriter.content(rStr.replaceAll("\001", ""));
+ else
+ rWriter.content(rStr);
+
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ auto* pMeta = static_cast<MetaStretchTextAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ writePoint(rWriter, pMeta->GetPoint());
+ rWriter.attribute("index", pMeta->GetIndex());
+ rWriter.attribute("length", pMeta->GetLen());
+ rWriter.attribute("width", pMeta->GetWidth());
+
+ rWriter.startElement("textcontent");
+ rWriter.content(pMeta->GetText());
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ auto* pMeta = static_cast<MetaTextRectAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeRectangle(rWriter, pMeta->GetRect());
+ rWriter.startElement("textcontent");
+ rWriter.content(pMeta->GetText());
+ rWriter.endElement();
+
+ rWriter.startElement("style");
+ rWriter.content(convertDrawTextFlagsToString(pMeta->GetStyle()));
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMP:
+ {
+ auto pMeta = static_cast<MetaBmpAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ auto pMeta = static_cast<MetaBmpScaleAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeSize(rWriter, pMeta->GetSize());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ auto pMeta = static_cast<MetaBmpScalePartAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("destx", pMeta->GetDestPoint().X());
+ rWriter.attribute("desty", pMeta->GetDestPoint().Y());
+ rWriter.attribute("destwidth", pMeta->GetDestSize().Width());
+ rWriter.attribute("destheight", pMeta->GetDestSize().Height());
+ rWriter.attribute("srcx", pMeta->GetSrcPoint().X());
+ rWriter.attribute("srcy", pMeta->GetSrcPoint().Y());
+ rWriter.attribute("srcwidth", pMeta->GetSrcSize().Width());
+ rWriter.attribute("srcheight", pMeta->GetSrcSize().Height());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ auto pMeta = static_cast<MetaBmpExAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmapEx().GetBitmap().GetChecksum()));
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ auto pMeta = static_cast<MetaBmpExScaleAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeSize(rWriter, pMeta->GetSize());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmapEx().GetBitmap().GetChecksum()));
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ auto pMeta = static_cast<MetaBmpExScalePartAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("destx", pMeta->GetDestPoint().X());
+ rWriter.attribute("desty", pMeta->GetDestPoint().Y());
+ rWriter.attribute("destwidth", pMeta->GetDestSize().Width());
+ rWriter.attribute("destheight", pMeta->GetDestSize().Height());
+ rWriter.attribute("srcx", pMeta->GetSrcPoint().X());
+ rWriter.attribute("srcy", pMeta->GetSrcPoint().Y());
+ rWriter.attribute("srcwidth", pMeta->GetSrcSize().Width());
+ rWriter.attribute("srcheight", pMeta->GetSrcSize().Height());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmapEx().GetBitmap().GetChecksum()));
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::MASK:
+ {
+ auto pMeta = static_cast<MetaMaskAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum()));
+ rWriter.attribute("color", convertColorToString(pMeta->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::MASKSCALE:
+ {
+ auto pMeta = static_cast<MetaMaskScaleAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeSize(rWriter, pMeta->GetSize());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum()));
+ rWriter.attribute("color", convertColorToString(pMeta->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ {
+ auto pMeta = static_cast<MetaMaskScalePartAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("destx", pMeta->GetDestPoint().X());
+ rWriter.attribute("desty", pMeta->GetDestPoint().Y());
+ rWriter.attribute("destwidth", pMeta->GetDestSize().Width());
+ rWriter.attribute("destheight", pMeta->GetDestSize().Height());
+ rWriter.attribute("srcx", pMeta->GetSrcPoint().X());
+ rWriter.attribute("srcy", pMeta->GetSrcPoint().Y());
+ rWriter.attribute("srcwidth", pMeta->GetSrcSize().Width());
+ rWriter.attribute("srcheight", pMeta->GetSrcSize().Height());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum()));
+ rWriter.attribute("color", convertColorToString(pMeta->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ const MetaGradientAction* pMeta = static_cast<MetaGradientAction*>(pAction);
+
+ rWriter.startElement(sCurrentElementTag);
+ writeGradient(rWriter, pMeta->GetGradient());
+
+ rWriter.startElement("rectangle");
+ writeRectangle(rWriter, pMeta->GetRect());
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ auto* const pMetaHatchAction = static_cast<MetaHatchAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ tools::PolyPolygon const& rPolyPolygon(pMetaHatchAction->GetPolyPolygon());
+
+ for (sal_uInt16 j = 0; j < rPolyPolygon.Count(); ++j)
+ {
+ rWriter.startElement("polygon");
+ tools::Polygon const& rPolygon = rPolyPolygon[j];
+ bool bFlags = rPolygon.HasFlags();
+ for (sal_uInt16 i = 0; i < rPolygon.GetSize(); ++i)
+ {
+ rWriter.startElement("point");
+ writePoint(rWriter, rPolygon[i]);
+ if (bFlags)
+ rWriter.attribute("flags", convertPolygonFlags(rPolygon.GetFlags(i)));
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+
+ rWriter.startElement("hatch");
+ const auto& rHatch = pMetaHatchAction->GetHatch();
+ rWriter.attribute("style", convertHatchStyle(rHatch.GetStyle()));
+ rWriter.attribute("color", convertColorToString(rHatch.GetColor()));
+ rWriter.attribute("distance", sal_Int32(rHatch.GetDistance()));
+ rWriter.attribute("angle", sal_Int32(rHatch.GetAngle().get()));
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::WALLPAPER:
+ {
+ const auto* pMetaAction = static_cast<const MetaWallpaperAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ writeRectangle(rWriter, pMetaAction->GetRect());
+
+ rWriter.startElement("wallpaper");
+ const auto& rWallpaper = pMetaAction->GetWallpaper();
+
+ rWriter.attribute("color", convertColorToString(rWallpaper.GetColor()));
+
+ WallpaperStyle eStyle = rWallpaper.GetStyle();
+ rWriter.attribute("style", convertWallpaperStyleToString(eStyle));
+
+ if (rWallpaper.IsBitmap())
+ {
+ rWriter.startElement("bitmap");
+ BitmapEx const & rBitmapEx = rWallpaper.GetBitmap();
+ rWriter.attribute("crc", hex32(rBitmapEx.GetChecksum()));
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(rBitmapEx));
+ rWriter.attribute("pixelformat", convertPixelFormatToString(rBitmapEx.GetBitmap().getPixelFormat()));
+ rWriter.attribute("width", hex32(rBitmapEx.GetSizePixel().Width()));
+ rWriter.attribute("height", hex32(rBitmapEx.GetSizePixel().Height()));
+ rWriter.endElement();
+ }
+
+ if (rWallpaper.IsGradient())
+ {
+ rWriter.startElement("gradient");
+ Gradient aGradient = rWallpaper.GetGradient();
+ writeGradient(rWriter, aGradient);
+ rWriter.endElement();
+ }
+
+ if (rWallpaper.IsRect())
+ {
+ tools::Rectangle aRect = rWallpaper.GetRect();
+ rWriter.startElement("rectangle");
+ writeRectangle(rWriter, aRect);
+ rWriter.endElement();
+ }
+
+ rWriter.attribute("fixed", rWallpaper.IsFixed() ? "true" : "false");
+ rWriter.attribute("scrollable", rWallpaper.IsScrollable() ? "true" : "false");
+
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ {
+ const auto* pMetaClipRegionAction = static_cast<const MetaClipRegionAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ tools::Rectangle aRectangle = pMetaClipRegionAction->GetRegion().GetBoundRect();
+ writeRectangle(rWriter, aRectangle);
+
+ vcl::Region aRegion = pMetaClipRegionAction->GetRegion();
+
+ if (aRegion.HasPolyPolygonOrB2DPolyPolygon())
+ {
+ tools::PolyPolygon aPolyPolygon = aRegion.GetAsPolyPolygon();
+
+ for (sal_uInt16 j = 0; j < aPolyPolygon.Count(); ++j)
+ {
+ rWriter.startElement("polygon");
+ tools::Polygon const& rPolygon = aPolyPolygon[j];
+ bool bFlags = rPolygon.HasFlags();
+ for (sal_uInt16 i = 0; i < rPolygon.GetSize(); ++i)
+ {
+ rWriter.startElement("point");
+ writePoint(rWriter, rPolygon[i]);
+ if (bFlags)
+ rWriter.attribute("flags", convertPolygonFlags(rPolygon.GetFlags(i)));
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+ }
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::ISECTRECTCLIPREGION:
+ {
+ MetaISectRectClipRegionAction* pMetaISectRectClipRegionAction = static_cast<MetaISectRectClipRegionAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ tools::Rectangle aRectangle = pMetaISectRectClipRegionAction->GetRect();
+ writeRectangle(rWriter, aRectangle);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ {
+ MetaISectRegionClipRegionAction* pMetaISectRegionClipRegionAction = static_cast<MetaISectRegionClipRegionAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ // FIXME for now we dump only the bounding box; this is
+ // enough for the tests we have, but may need extending to
+ // dumping the real polypolygon in the future
+ tools::Rectangle aRectangle = pMetaISectRegionClipRegionAction->GetRegion().GetBoundRect();
+ writeRectangle(rWriter, aRectangle);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::MOVECLIPREGION:
+ {
+ const auto* pMetaMoveClipRegionAction = static_cast<MetaMoveClipRegionAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("horzmove", pMetaMoveClipRegionAction->GetHorzMove());
+ rWriter.attribute("vertmove", pMetaMoveClipRegionAction->GetVertMove());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::LINECOLOR:
+ {
+ MetaLineColorAction* pMetaLineColorAction = static_cast<MetaLineColorAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("color", convertColorToString(pMetaLineColorAction->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::FILLCOLOR:
+ {
+ MetaFillColorAction* pMetaFillColorAction = static_cast<MetaFillColorAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("color", convertColorToString(pMetaFillColorAction->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTCOLOR:
+ {
+ MetaTextColorAction* pMetaTextColorAction = static_cast<MetaTextColorAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("color", convertColorToString(pMetaTextColorAction->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTFILLCOLOR:
+ {
+ MetaTextFillColorAction* pMetaTextFillColorAction = static_cast<MetaTextFillColorAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("color", convertColorToString(pMetaTextFillColorAction->GetColor()));
+
+ if (pMetaTextFillColorAction->IsSetting())
+ rWriter.attribute("setting", u"true");
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTALIGN:
+ {
+ MetaTextAlignAction* pMetaTextAlignAction = static_cast<MetaTextAlignAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ OUString sAlign = convertTextAlignToString(pMetaTextAlignAction->GetTextAlign());
+ if (!sAlign.isEmpty())
+ rWriter.attribute("align", sAlign);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::MAPMODE:
+ {
+ const MetaMapModeAction* pMeta = static_cast<MetaMapModeAction*>(pAction);
+ MapMode aMapMode = pMeta->GetMapMode();
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("mapunit", convertMapUnitToString( aMapMode.GetMapUnit() ));
+ writePoint(rWriter, aMapMode.GetOrigin());
+ rWriter.attribute("scalex", convertFractionToString(aMapMode.GetScaleX()));
+ rWriter.attribute("scaley", convertFractionToString(aMapMode.GetScaleY()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::FONT:
+ {
+ MetaFontAction* pMetaFontAction = static_cast<MetaFontAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ vcl::Font aFont = pMetaFontAction->GetFont();
+
+ rWriter.attribute("color", convertColorToString(aFont.GetColor()));
+ rWriter.attribute("fillcolor", convertColorToString(aFont.GetFillColor()));
+ rWriter.attribute("name", aFont.GetFamilyName());
+ rWriter.attribute("stylename", aFont.GetStyleName());
+ rWriter.attribute("width", aFont.GetFontSize().Width());
+ rWriter.attribute("height", aFont.GetFontSize().Height());
+ rWriter.attribute("orientation", aFont.GetOrientation().get());
+ rWriter.attribute("weight", convertFontWeightToString(aFont.GetWeight()));
+ rWriter.attribute("vertical", aFont.IsVertical() ? "true" : "false");
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::PUSH:
+ {
+ MetaPushAction* pMetaPushAction = static_cast<MetaPushAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("flags", collectPushFlags(pMetaPushAction->GetFlags()));
+ }
+ break;
+
+ case MetaActionType::POP:
+ {
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::RASTEROP:
+ {
+ MetaRasterOpAction* pMetaRasterOpAction = static_cast<MetaRasterOpAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ if (pMetaRasterOpAction->GetRasterOp() != RasterOp::OverPaint)
+ {
+ rWriter.attribute("operation", convertRopToString(pMetaRasterOpAction->GetRasterOp()));
+ }
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::Transparent:
+ {
+ const MetaTransparentAction* pMeta = static_cast<MetaTransparentAction*>(pAction);
+
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("transparence", pMeta->GetTransparence());
+
+ tools::PolyPolygon const& rPolyPolygon(pMeta->GetPolyPolygon());
+
+ for (sal_uInt16 j = 0; j < rPolyPolygon.Count(); ++j)
+ {
+ rWriter.startElement("polygon");
+ tools::Polygon const& rPolygon = rPolyPolygon[j];
+ bool bFlags = rPolygon.HasFlags();
+ for (sal_uInt16 i = 0; i < rPolygon.GetSize(); ++i)
+ {
+ rWriter.startElement("point");
+ writePoint(rWriter, rPolygon[i]);
+ if (bFlags)
+ rWriter.attribute("flags", convertPolygonFlags(rPolygon.GetFlags(i)));
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ MetaEPSAction* pMetaEPSAction = static_cast<MetaEPSAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ writePoint(rWriter, pMetaEPSAction->GetPoint());
+ writeSize(rWriter, pMetaEPSAction->GetSize());
+
+ rWriter.startElement("gfxlink");
+ writeSize(rWriter, pMetaEPSAction->GetLink().GetPrefSize());
+ rWriter.attribute("type", convertGfxLinkTypeToString(pMetaEPSAction->GetLink().GetType()));
+ rWriter.attribute("userid", pMetaEPSAction->GetLink().GetUserId());
+ rWriter.attribute("datasize", pMetaEPSAction->GetLink().GetDataSize());
+ rWriter.attribute("data", toHexString(pMetaEPSAction->GetLink().GetData(), pMetaEPSAction->GetLink().GetDataSize()));
+ rWriter.attribute("native", pMetaEPSAction->GetLink().IsNative() ? "true" : "false");
+ rWriter.attribute("emf", pMetaEPSAction->GetLink().IsEMF() ? "true" : "false");
+ rWriter.attribute("validmapmode", pMetaEPSAction->GetLink().IsPrefMapModeValid() ? "true" : "false");
+ rWriter.startElement("prefmapmode");
+ writeMapMode(rWriter, pMetaEPSAction->GetLink().GetPrefMapMode());
+ rWriter.endElement();
+ rWriter.endElement();
+
+ rWriter.startElement("metafile");
+ writeXml(pMetaEPSAction->GetSubstitute(), rWriter);
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::REFPOINT:
+ {
+ auto* pMeta = static_cast<MetaRefPointAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetRefPoint());
+ rWriter.attribute("set", pMeta->IsSetting() ? "true" : "false");
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTLINECOLOR:
+ {
+ auto* pMeta = static_cast<MetaTextLineColorAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("color", convertColorToString(pMeta->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ {
+ auto* pMeta = static_cast<MetaTextLineAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetStartPoint());
+ rWriter.attribute("width", pMeta->GetWidth());
+ rWriter.attribute("strikeout", convertFontStrikeoutToString(pMeta->GetStrikeout()));
+ rWriter.attribute("underline", convertFontLineStyleToString(pMeta->GetUnderline()));
+ rWriter.attribute("overline", convertFontLineStyleToString(pMeta->GetOverline()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ const auto* pMeta = static_cast<MetaFloatTransparentAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeSize(rWriter, pMeta->GetSize());
+ rWriter.attribute("transparent", pMeta->IsTransparent() ? "true" : "false");
+
+ rWriter.startElement("gradient");
+ writeGradient(rWriter, pMeta->GetGradient());
+ rWriter.endElement();
+
+ rWriter.startElement("metafile");
+ writeXml(pMeta->GetGDIMetaFile(), rWriter);
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ {
+ const MetaGradientExAction* pMetaGradientExAction = static_cast<MetaGradientExAction*>(pAction);
+
+ rWriter.startElement(sCurrentElementTag);
+ writeGradient(rWriter, pMetaGradientExAction->GetGradient());
+
+ tools::PolyPolygon const& rPolyPolygon(pMetaGradientExAction->GetPolyPolygon());
+ for (sal_uInt16 j = 0; j < rPolyPolygon.Count(); ++j)
+ {
+ rWriter.startElement("polygon");
+ tools::Polygon const& rPolygon = rPolyPolygon[j];
+ bool bFlags = rPolygon.HasFlags();
+ for (sal_uInt16 i = 0; i < rPolygon.GetSize(); ++i)
+ {
+ rWriter.startElement("point");
+ writePoint(rWriter, rPolygon[i]);
+ if (bFlags)
+ rWriter.attribute("flags", convertPolygonFlags(rPolygon.GetFlags(i)));
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::LAYOUTMODE:
+ {
+ const MetaLayoutModeAction* pMetaLayoutModeAction = static_cast<MetaLayoutModeAction*>(pAction);
+
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("textlayout", convertComplexTestLayoutFlags(pMetaLayoutModeAction->GetLayoutMode()));
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTLANGUAGE:
+ {
+ const MetaTextLanguageAction* pMetaTextLanguageAction = static_cast<MetaTextLanguageAction*>(pAction);
+
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("language", convertLanguageTypeToString(pMetaTextLanguageAction->GetTextLanguage()));
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::OVERLINECOLOR:
+ {
+ const auto* pMetaAction = static_cast<MetaOverlineColorAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("color", convertColorToString(pMetaAction->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::COMMENT:
+ {
+ MetaCommentAction* pMetaCommentAction = static_cast<MetaCommentAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ if (pMetaCommentAction->GetDataSize() > 0)
+ {
+ rWriter.attribute("datasize", pMetaCommentAction->GetDataSize());
+ rWriter.attribute("data", toHexString(pMetaCommentAction->GetData(), pMetaCommentAction->GetDataSize()));
+ }
+ rWriter.attribute("value", pMetaCommentAction->GetValue());
+
+ if (!pMetaCommentAction->GetComment().isEmpty())
+ {
+ rWriter.startElement("comment");
+ rWriter.content(pMetaCommentAction->GetComment());
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+ break;
+
+ default:
+ {
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("note", OString("not implemented in xml dump"));
+ rWriter.endElement();
+ }
+ break;
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/oldprintadaptor.cxx b/vcl/source/gdi/oldprintadaptor.cxx
new file mode 100644
index 000000000..599e1a543
--- /dev/null
+++ b/vcl/source/gdi/oldprintadaptor.cxx
@@ -0,0 +1,109 @@
+/* -*- 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 <comphelper/propertyvalue.hxx>
+#include <vcl/oldprintadaptor.hxx>
+#include <vcl/gdimtf.hxx>
+
+#include <com/sun/star/awt/Size.hpp>
+
+#include <vector>
+
+using namespace vcl;
+using namespace cppu;
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::beans;
+
+namespace vcl
+{
+ namespace {
+
+ struct AdaptorPage
+ {
+ GDIMetaFile maPage;
+ css::awt::Size maPageSize;
+ };
+
+ }
+
+ struct ImplOldStyleAdaptorData
+ {
+ std::vector< AdaptorPage > maPages;
+ };
+}
+
+OldStylePrintAdaptor::OldStylePrintAdaptor(const VclPtr<Printer>& i_xPrinter, weld::Window* i_pWindow)
+ : PrinterController(i_xPrinter, i_pWindow)
+ , mpData(new ImplOldStyleAdaptorData)
+{
+}
+
+OldStylePrintAdaptor::~OldStylePrintAdaptor()
+{
+}
+
+void OldStylePrintAdaptor::StartPage()
+{
+ Size aPaperSize( getPrinter()->PixelToLogic( getPrinter()->GetPaperSizePixel(), MapMode( MapUnit::Map100thMM ) ) );
+ mpData->maPages.emplace_back( );
+ mpData->maPages.back().maPageSize.Width = aPaperSize.getWidth();
+ mpData->maPages.back().maPageSize.Height = aPaperSize.getHeight();
+ getPrinter()->SetConnectMetaFile( &mpData->maPages.back().maPage );
+
+ // copy state into metafile
+ VclPtr<Printer> xPrinter( getPrinter() );
+ xPrinter->SetMapMode(xPrinter->GetMapMode());
+ xPrinter->SetFont(xPrinter->GetFont());
+ xPrinter->SetDrawMode(xPrinter->GetDrawMode());
+ xPrinter->SetLineColor(xPrinter->GetLineColor());
+ xPrinter->SetFillColor(xPrinter->GetFillColor());
+}
+
+void OldStylePrintAdaptor::EndPage()
+{
+ getPrinter()->SetConnectMetaFile( nullptr );
+ mpData->maPages.back().maPage.WindStart();
+}
+
+int OldStylePrintAdaptor::getPageCount() const
+{
+ return int(mpData->maPages.size());
+}
+
+Sequence< PropertyValue > OldStylePrintAdaptor::getPageParameters( int i_nPage ) const
+{
+ css::awt::Size aSize;
+ if( i_nPage < int(mpData->maPages.size() ) )
+ aSize = mpData->maPages[i_nPage].maPageSize;
+ return { comphelper::makePropertyValue("PageSize", css::uno::Any(aSize)) };
+}
+
+void OldStylePrintAdaptor::printPage( int i_nPage ) const
+{
+ if( i_nPage < int(mpData->maPages.size()) )
+ {
+ mpData->maPages[ i_nPage ].maPage.WindStart();
+ mpData->maPages[ i_nPage ].maPage.Play(*getPrinter());
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfbuildin_fonts.cxx b/vcl/source/gdi/pdfbuildin_fonts.cxx
new file mode 100644
index 000000000..d6504c5c0
--- /dev/null
+++ b/vcl/source/gdi/pdfbuildin_fonts.cxx
@@ -0,0 +1,764 @@
+/* -*- 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 <rtl/strbuf.hxx>
+
+#include <pdf/pdfbuildin_fonts.hxx>
+
+namespace vcl::pdf
+{
+OString BuildinFont::getNameObject() const
+{
+ OStringBuffer aBuf(16);
+ aBuf.append('/');
+ const char* pRun = m_pPSName;
+
+ unsigned int nCopied = 0;
+ while (*pRun)
+ {
+ if (*pRun >= 'A' && *pRun <= 'Z')
+ nCopied = 0;
+ if (nCopied++ < 2)
+ aBuf.append(*pRun);
+ pRun++;
+ }
+ return aBuf.makeStringAndClear();
+}
+
+const FontCharMapRef& BuildinFont::GetFontCharMap() const
+{
+ assert(false && "pdf::BuildinFont doesn't provide correct char maps!");
+ if (m_xFontCharMap.is())
+ return m_xFontCharMap;
+
+ m_xFontCharMap = FontCharMap::GetDefaultMap(m_eCharSet != RTL_TEXTENCODING_MS_1252);
+ return m_xFontCharMap;
+}
+
+FontAttributes BuildinFont::GetFontAttributes() const
+{
+ FontAttributes aDFA;
+ aDFA.SetFamilyName(OUString::createFromAscii(m_pName));
+ aDFA.SetStyleName(OUString::createFromAscii(m_pStyleName));
+ aDFA.SetFamilyType(m_eFamily);
+ aDFA.SetSymbolFlag(m_eCharSet != RTL_TEXTENCODING_MS_1252);
+ aDFA.SetPitch(m_ePitch);
+ aDFA.SetWeight(m_eWeight);
+ aDFA.SetItalic(m_eItalic);
+ aDFA.SetWidthType(m_eWidthType);
+ aDFA.SetQuality(50000);
+ return aDFA;
+}
+
+const BuildinFont BuildinFontFace::m_aBuildinFonts[14]
+ = { { "Courier", // family name
+ "Normal", // style
+ "Courier", // PSName
+ 629,
+ -157, // ascend, descend
+ FAMILY_MODERN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_FIXED, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 600, 600, 600, 600, 600, 600, 600, 600, // 32 - 39
+ 600, 600, 600, 600, 600, 600, 600, 600, // 40 - 47
+ 600, 600, 600, 600, 600, 600, 600, 600, // 48 - 55
+ 600, 600, 600, 600, 600, 600, 600, 600, // 56 - 63
+ 600, 600, 600, 600, 600, 600, 600, 600, // 64 - 71
+ 600, 600, 600, 600, 600, 600, 600, 600, // 72 - 79
+ 600, 600, 600, 600, 600, 600, 600, 600, // 80 - 87
+ 600, 600, 600, 600, 600, 600, 600, 600, // 88 - 95
+ 600, 600, 600, 600, 600, 600, 600, 600, // 96 - 103
+ 600, 600, 600, 600, 600, 600, 600, 600, // 104 - 111
+ 600, 600, 600, 600, 600, 600, 600, 600, // 112 - 119
+ 600, 600, 600, 600, 600, 600, 600, 0, // 120 - 127
+ 600, 0, 600, 600, 600, 600, 600, 600, // 128 - 135
+ 600, 600, 600, 600, 600, 0, 600, 0, // 136 - 143
+ 0, 600, 600, 600, 600, 600, 600, 600, // 144 - 151
+ 600, 600, 600, 600, 600, 0, 600, 600, // 152 - 159
+ 600, 600, 600, 600, 600, 600, 600, 600, // 160 - 167
+ 600, 600, 600, 600, 600, 600, 600, 600, // 168 - 175
+ 600, 600, 600, 600, 600, 600, 600, 600, // 176 - 183
+ 600, 600, 600, 600, 600, 600, 600, 600, // 184 - 191
+ 600, 600, 600, 600, 600, 600, 600, 600, // 192 - 199
+ 600, 600, 600, 600, 600, 600, 600, 600, // 200 - 207
+ 600, 600, 600, 600, 600, 600, 600, 600, // 208 - 215
+ 600, 600, 600, 600, 600, 600, 600, 600, // 216 - 223
+ 600, 600, 600, 600, 600, 600, 600, 600, // 224 - 231
+ 600, 600, 600, 600, 600, 600, 600, 600, // 232 - 239
+ 600, 600, 600, 600, 600, 600, 600, 600, // 240 - 247
+ 600, 600, 600, 600, 600, 600, 600, 600 // 248 - 255
+ },
+ nullptr },
+
+ { "Courier", // family name
+ "Italic", // style
+ "Courier-Oblique", // PSName
+ 629,
+ -157, // ascend, descend
+ FAMILY_MODERN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_FIXED, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NORMAL, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 600, 600, 600, 600, 600, 600, 600, 600, // 32 - 39
+ 600, 600, 600, 600, 600, 600, 600, 600, // 40 - 47
+ 600, 600, 600, 600, 600, 600, 600, 600, // 48 - 55
+ 600, 600, 600, 600, 600, 600, 600, 600, // 56 - 63
+ 600, 600, 600, 600, 600, 600, 600, 600, // 64 - 71
+ 600, 600, 600, 600, 600, 600, 600, 600, // 72 - 79
+ 600, 600, 600, 600, 600, 600, 600, 600, // 80 - 87
+ 600, 600, 600, 600, 600, 600, 600, 600, // 88 - 95
+ 600, 600, 600, 600, 600, 600, 600, 600, // 96 - 103
+ 600, 600, 600, 600, 600, 600, 600, 600, // 104 - 111
+ 600, 600, 600, 600, 600, 600, 600, 600, // 112 - 119
+ 600, 600, 600, 600, 600, 600, 600, 0, // 120 - 127
+ 600, 0, 600, 600, 600, 600, 600, 600, // 128 - 135
+ 600, 600, 600, 600, 600, 0, 600, 0, // 136 - 143
+ 0, 600, 600, 600, 600, 600, 600, 600, // 144 - 151
+ 600, 600, 600, 600, 600, 0, 600, 600, // 152 - 159
+ 600, 600, 600, 600, 600, 600, 600, 600, // 160 - 167
+ 600, 600, 600, 600, 600, 600, 600, 600, // 168 - 175
+ 600, 600, 600, 600, 600, 600, 600, 600, // 176 - 183
+ 600, 600, 600, 600, 600, 600, 600, 600, // 184 - 191
+ 600, 600, 600, 600, 600, 600, 600, 600, // 192 - 199
+ 600, 600, 600, 600, 600, 600, 600, 600, // 200 - 207
+ 600, 600, 600, 600, 600, 600, 600, 600, // 208 - 215
+ 600, 600, 600, 600, 600, 600, 600, 600, // 216 - 223
+ 600, 600, 600, 600, 600, 600, 600, 600, // 224 - 231
+ 600, 600, 600, 600, 600, 600, 600, 600, // 232 - 239
+ 600, 600, 600, 600, 600, 600, 600, 600, // 240 - 247
+ 600, 600, 600, 600, 600, 600, 600, 600 // 248 - 255
+ },
+ nullptr },
+
+ { "Courier", // family name
+ "Bold", // style
+ "Courier-Bold", // PSName
+ 629,
+ -157, // ascend, descend
+ FAMILY_MODERN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_FIXED, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_BOLD, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 600, 600, 600, 600, 600, 600, 600, 600, // 32 - 39
+ 600, 600, 600, 600, 600, 600, 600, 600, // 40 - 47
+ 600, 600, 600, 600, 600, 600, 600, 600, // 48 - 55
+ 600, 600, 600, 600, 600, 600, 600, 600, // 56 - 63
+ 600, 600, 600, 600, 600, 600, 600, 600, // 64 - 71
+ 600, 600, 600, 600, 600, 600, 600, 600, // 72 - 79
+ 600, 600, 600, 600, 600, 600, 600, 600, // 80 - 87
+ 600, 600, 600, 600, 600, 600, 600, 600, // 88 - 95
+ 600, 600, 600, 600, 600, 600, 600, 600, // 96 - 103
+ 600, 600, 600, 600, 600, 600, 600, 600, // 104 - 111
+ 600, 600, 600, 600, 600, 600, 600, 600, // 112 - 119
+ 600, 600, 600, 600, 600, 600, 600, 0, // 120 - 127
+ 600, 0, 600, 600, 600, 600, 600, 600, // 128 - 135
+ 600, 600, 600, 600, 600, 0, 600, 0, // 136 - 143
+ 0, 600, 600, 600, 600, 600, 600, 600, // 144 - 151
+ 600, 600, 600, 600, 600, 0, 600, 600, // 152 - 159
+ 600, 600, 600, 600, 600, 600, 600, 600, // 160 - 167
+ 600, 600, 600, 600, 600, 600, 600, 600, // 168 - 175
+ 600, 600, 600, 600, 600, 600, 600, 600, // 176 - 183
+ 600, 600, 600, 600, 600, 600, 600, 600, // 184 - 191
+ 600, 600, 600, 600, 600, 600, 600, 600, // 192 - 199
+ 600, 600, 600, 600, 600, 600, 600, 600, // 200 - 207
+ 600, 600, 600, 600, 600, 600, 600, 600, // 208 - 215
+ 600, 600, 600, 600, 600, 600, 600, 600, // 216 - 223
+ 600, 600, 600, 600, 600, 600, 600, 600, // 224 - 231
+ 600, 600, 600, 600, 600, 600, 600, 600, // 232 - 239
+ 600, 600, 600, 600, 600, 600, 600, 600, // 240 - 247
+ 600, 600, 600, 600, 600, 600, 600, 600 // 248 - 255
+ },
+ nullptr },
+
+ { "Courier", // family name
+ "Bold Italic", // style
+ "Courier-BoldOblique", // PSName
+ 629,
+ -157, // ascend, descend
+ FAMILY_MODERN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_FIXED, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_BOLD, // weight type
+ ITALIC_NORMAL, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 600, 600, 600, 600, 600, 600, 600, 600, // 32 - 39
+ 600, 600, 600, 600, 600, 600, 600, 600, // 40 - 47
+ 600, 600, 600, 600, 600, 600, 600, 600, // 48 - 55
+ 600, 600, 600, 600, 600, 600, 600, 600, // 56 - 63
+ 600, 600, 600, 600, 600, 600, 600, 600, // 64 - 71
+ 600, 600, 600, 600, 600, 600, 600, 600, // 72 - 79
+ 600, 600, 600, 600, 600, 600, 600, 600, // 80 - 87
+ 600, 600, 600, 600, 600, 600, 600, 600, // 88 - 95
+ 600, 600, 600, 600, 600, 600, 600, 600, // 96 - 103
+ 600, 600, 600, 600, 600, 600, 600, 600, // 104 - 111
+ 600, 600, 600, 600, 600, 600, 600, 600, // 112 - 119
+ 600, 600, 600, 600, 600, 600, 600, 0, // 120 - 127
+ 600, 0, 600, 600, 600, 600, 600, 600, // 128 - 135
+ 600, 600, 600, 600, 600, 0, 600, 0, // 136 - 143
+ 0, 600, 600, 600, 600, 600, 600, 600, // 144 - 151
+ 600, 600, 600, 600, 600, 0, 600, 600, // 152 - 159
+ 600, 600, 600, 600, 600, 600, 600, 600, // 160 - 167
+ 600, 600, 600, 600, 600, 600, 600, 600, // 168 - 175
+ 600, 600, 600, 600, 600, 600, 600, 600, // 176 - 183
+ 600, 600, 600, 600, 600, 600, 600, 600, // 184 - 191
+ 600, 600, 600, 600, 600, 600, 600, 600, // 192 - 199
+ 600, 600, 600, 600, 600, 600, 600, 600, // 200 - 207
+ 600, 600, 600, 600, 600, 600, 600, 600, // 208 - 215
+ 600, 600, 600, 600, 600, 600, 600, 600, // 216 - 223
+ 600, 600, 600, 600, 600, 600, 600, 600, // 224 - 231
+ 600, 600, 600, 600, 600, 600, 600, 600, // 232 - 239
+ 600, 600, 600, 600, 600, 600, 600, 600, // 240 - 247
+ 600, 600, 600, 600, 600, 600, 600, 600 // 248 - 255
+ },
+ nullptr },
+
+ { "Helvetica", // family name
+ "Normal", // style
+ "Helvetica", // PSName
+ 718,
+ -207, // ascend, descend
+ FAMILY_SWISS, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 278, 278, 355, 556, 556, 889, 667, 191, // 32 - 39
+ 333, 333, 389, 584, 278, 333, 278, 278, // 40 - 47
+ 556, 556, 556, 556, 556, 556, 556, 556, // 48 - 55
+ 556, 556, 278, 278, 584, 584, 584, 556, // 56 - 63
+ 1015, 667, 667, 722, 722, 667, 611, 778, // 64 - 71
+ 722, 278, 500, 667, 556, 833, 722, 778, // 72 - 79
+ 667, 778, 722, 667, 611, 722, 667, 944, // 80 - 87
+ 667, 667, 611, 278, 278, 278, 469, 556, // 88 - 95
+ 333, 556, 556, 500, 556, 556, 278, 556, // 96 - 103
+ 556, 222, 222, 500, 222, 833, 556, 556, // 104 - 111
+ 556, 556, 333, 500, 278, 556, 500, 722, // 112 - 119
+ 500, 500, 500, 334, 260, 334, 584, 0, // 120 - 127
+ 556, 0, 222, 556, 333, 1000, 556, 556, // 128 - 135
+ 333, 1000, 667, 333, 1000, 0, 500, 0, // 136 - 143
+ 0, 222, 222, 333, 333, 350, 556, 1000, // 144 - 151
+ 333, 1000, 500, 333, 944, 0, 500, 667, // 152 - 159
+ 278, 333, 556, 556, 556, 556, 260, 556, // 160 - 167
+ 333, 737, 370, 556, 584, 333, 737, 333, // 168 - 175
+ 400, 584, 333, 333, 333, 556, 537, 278, // 176 - 183
+ 333, 333, 365, 556, 834, 834, 834, 611, // 184 - 191
+ 667, 667, 667, 667, 667, 667, 1000, 722, // 192 - 199
+ 667, 667, 667, 667, 278, 278, 278, 278, // 200 - 207
+ 722, 722, 778, 778, 778, 778, 778, 584, // 208 - 215
+ 778, 722, 722, 722, 722, 667, 667, 611, // 216 - 223
+ 556, 556, 556, 556, 556, 556, 889, 500, // 224 - 231
+ 556, 556, 556, 556, 278, 278, 278, 278, // 232 - 239
+ 556, 556, 556, 556, 556, 556, 556, 584, // 240 - 247
+ 611, 556, 556, 556, 556, 500, 556, 500 // 248 - 255
+ },
+ nullptr },
+
+ { "Helvetica", // family name
+ "Italic", // style
+ "Helvetica-Oblique", // PSName
+ 718,
+ -207, // ascend, descend
+ FAMILY_SWISS, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NORMAL, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 278, 278, 355, 556, 556, 889, 667, 191, // 32 - 39
+ 333, 333, 389, 584, 278, 333, 278, 278, // 40 - 47
+ 556, 556, 556, 556, 556, 556, 556, 556, // 48 - 55
+ 556, 556, 278, 278, 584, 584, 584, 556, // 56 - 63
+ 1015, 667, 667, 722, 722, 667, 611, 778, // 64 - 71
+ 722, 278, 500, 667, 556, 833, 722, 778, // 72 - 79
+ 667, 778, 722, 667, 611, 722, 667, 944, // 80 - 87
+ 667, 667, 611, 278, 278, 278, 469, 556, // 88 - 95
+ 333, 556, 556, 500, 556, 556, 278, 556, // 96 - 103
+ 556, 222, 222, 500, 222, 833, 556, 556, // 104 - 111
+ 556, 556, 333, 500, 278, 556, 500, 722, // 112 - 119
+ 500, 500, 500, 334, 260, 334, 584, 0, // 120 - 127
+ 556, 0, 222, 556, 333, 1000, 556, 556, // 128 - 135
+ 333, 1000, 667, 333, 1000, 0, 500, 0, // 136 - 143
+ 0, 222, 222, 333, 333, 350, 556, 1000, // 144 - 151
+ 333, 1000, 500, 333, 944, 0, 500, 667, // 152 - 159
+ 278, 333, 556, 556, 556, 556, 260, 556, // 160 - 167
+ 333, 737, 370, 556, 584, 333, 737, 333, // 168 - 175
+ 400, 584, 333, 333, 333, 556, 537, 278, // 176 - 183
+ 333, 333, 365, 556, 834, 834, 834, 611, // 184 - 191
+ 667, 667, 667, 667, 667, 667, 1000, 722, // 192 - 199
+ 667, 667, 667, 667, 278, 278, 278, 278, // 200 - 207
+ 722, 722, 778, 778, 778, 778, 778, 584, // 208 - 215
+ 778, 722, 722, 722, 722, 667, 667, 611, // 216 - 223
+ 556, 556, 556, 556, 556, 556, 889, 500, // 224 - 231
+ 556, 556, 556, 556, 278, 278, 278, 278, // 232 - 239
+ 556, 556, 556, 556, 556, 556, 556, 584, // 240 - 247
+ 611, 556, 556, 556, 556, 500, 556, 500 // 248 - 255
+ },
+ nullptr },
+
+ { "Helvetica", // family name
+ "Bold", // style
+ "Helvetica-Bold", // PSName
+ 718,
+ -207, // ascend, descend
+ FAMILY_SWISS, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_BOLD, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 278, 333, 474, 556, 556, 889, 722, 238, // 32 - 39
+ 333, 333, 389, 584, 278, 333, 278, 278, // 40 - 47
+ 556, 556, 556, 556, 556, 556, 556, 556, // 48 - 55
+ 556, 556, 333, 333, 584, 584, 584, 611, // 56 - 63
+ 975, 722, 722, 722, 722, 667, 611, 778, // 64 - 71
+ 722, 278, 556, 722, 611, 833, 722, 778, // 72 - 79
+ 667, 778, 722, 667, 611, 722, 667, 944, // 80 - 87
+ 667, 667, 611, 333, 278, 333, 584, 556, // 88 - 95
+ 333, 556, 611, 556, 611, 556, 333, 611, // 96 - 103
+ 611, 278, 278, 556, 278, 889, 611, 611, // 104 - 111
+ 611, 611, 389, 556, 333, 611, 556, 778, // 112 - 119
+ 556, 556, 500, 389, 280, 389, 584, 0, // 120 - 127
+ 556, 0, 278, 556, 500, 1000, 556, 556, // 128 - 135
+ 333, 1000, 667, 333, 1000, 0, 500, 0, // 136 - 143
+ 0, 278, 278, 500, 500, 350, 556, 1000, // 144 - 151
+ 333, 1000, 556, 333, 944, 0, 500, 667, // 152 - 159
+ 278, 333, 556, 556, 556, 556, 280, 556, // 160 - 167
+ 333, 737, 370, 556, 584, 333, 737, 333, // 168 - 175
+ 400, 584, 333, 333, 333, 611, 556, 278, // 176 - 183
+ 333, 333, 365, 556, 834, 834, 834, 611, // 184 - 191
+ 722, 722, 722, 722, 722, 722, 1000, 722, // 192 - 199
+ 667, 667, 667, 667, 278, 278, 278, 278, // 200 - 207
+ 722, 722, 778, 778, 778, 778, 778, 584, // 208 - 215
+ 778, 722, 722, 722, 722, 667, 667, 611, // 216 - 223
+ 556, 556, 556, 556, 556, 556, 889, 556, // 224 - 231
+ 556, 556, 556, 556, 278, 278, 278, 278, // 232 - 239
+ 611, 611, 611, 611, 611, 611, 611, 584, // 240 - 247
+ 611, 611, 611, 611, 611, 556, 611, 556 // 248 - 255
+ },
+ nullptr },
+
+ { "Helvetica", // family name
+ "Bold Italic", // style
+ "Helvetica-BoldOblique", // PSName
+ 718,
+ -207, // ascend, descend
+ FAMILY_SWISS, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_BOLD, // weight type
+ ITALIC_NORMAL, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 278, 333, 474, 556, 556, 889, 722, 238, // 32 - 39
+ 333, 333, 389, 584, 278, 333, 278, 278, // 40 - 47
+ 556, 556, 556, 556, 556, 556, 556, 556, // 48 - 55
+ 556, 556, 333, 333, 584, 584, 584, 611, // 56 - 63
+ 975, 722, 722, 722, 722, 667, 611, 778, // 64 - 71
+ 722, 278, 556, 722, 611, 833, 722, 778, // 72 - 79
+ 667, 778, 722, 667, 611, 722, 667, 944, // 80 - 87
+ 667, 667, 611, 333, 278, 333, 584, 556, // 88 - 95
+ 333, 556, 611, 556, 611, 556, 333, 611, // 96 - 103
+ 611, 278, 278, 556, 278, 889, 611, 611, // 104 - 111
+ 611, 611, 389, 556, 333, 611, 556, 778, // 112 - 119
+ 556, 556, 500, 389, 280, 389, 584, 0, // 120 - 127
+ 556, 0, 278, 556, 500, 1000, 556, 556, // 128 - 135
+ 333, 1000, 667, 333, 1000, 0, 500, 0, // 136 - 143
+ 0, 278, 278, 500, 500, 350, 556, 1000, // 144 - 151
+ 333, 1000, 556, 333, 944, 0, 500, 667, // 152 - 159
+ 278, 333, 556, 556, 556, 556, 280, 556, // 160 - 167
+ 333, 737, 370, 556, 584, 333, 737, 333, // 168 - 175
+ 400, 584, 333, 333, 333, 611, 556, 278, // 176 - 183
+ 333, 333, 365, 556, 834, 834, 834, 611, // 184 - 191
+ 722, 722, 722, 722, 722, 722, 1000, 722, // 192 - 199
+ 667, 667, 667, 667, 278, 278, 278, 278, // 200 - 207
+ 722, 722, 778, 778, 778, 778, 778, 584, // 208 - 215
+ 778, 722, 722, 722, 722, 667, 667, 611, // 216 - 223
+ 556, 556, 556, 556, 556, 556, 889, 556, // 224 - 231
+ 556, 556, 556, 556, 278, 278, 278, 278, // 232 - 239
+ 611, 611, 611, 611, 611, 611, 611, 584, // 240 - 247
+ 611, 611, 611, 611, 611, 556, 611, 556 // 248 - 255
+ },
+ nullptr },
+
+ { "Times", // family name
+ "Normal", // style
+ "Times-Roman", // PSName
+ 683,
+ -217, // ascend, descend
+ FAMILY_ROMAN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 250, 333, 408, 500, 500, 833, 778, 180, // 32 - 39
+ 333, 333, 500, 564, 250, 333, 250, 278, // 40 - 47
+ 500, 500, 500, 500, 500, 500, 500, 500, // 48 - 55
+ 500, 500, 278, 278, 564, 564, 564, 444, // 56 - 63
+ 921, 722, 667, 667, 722, 611, 556, 722, // 64 - 71
+ 722, 333, 389, 722, 611, 889, 722, 722, // 72 - 79
+ 556, 722, 667, 556, 611, 722, 722, 944, // 80 - 87
+ 722, 722, 611, 333, 278, 333, 469, 500, // 88 - 95
+ 333, 444, 500, 444, 500, 444, 333, 500, // 96 - 103
+ 500, 278, 278, 500, 278, 778, 500, 500, // 104 - 111
+ 500, 500, 333, 389, 278, 500, 500, 722, // 112 - 119
+ 500, 500, 444, 480, 200, 480, 541, 0, // 120 - 127
+ 500, 0, 333, 500, 444, 1000, 500, 500, // 128 - 135
+ 333, 1000, 556, 333, 889, 0, 444, 0, // 136 - 143
+ 0, 333, 333, 444, 444, 350, 500, 1000, // 144 - 151
+ 333, 980, 389, 333, 722, 0, 444, 722, // 152 - 159
+ 250, 333, 500, 500, 500, 500, 200, 500, // 160 - 167
+ 333, 760, 276, 500, 564, 333, 760, 333, // 168 - 175
+ 400, 564, 300, 300, 333, 500, 453, 250, // 176 - 183
+ 333, 300, 310, 500, 750, 750, 750, 444, // 184 - 191
+ 722, 722, 722, 722, 722, 722, 889, 667, // 192 - 199
+ 611, 611, 611, 611, 333, 333, 333, 333, // 200 - 207
+ 722, 722, 722, 722, 722, 722, 722, 564, // 208 - 215
+ 722, 722, 722, 722, 722, 722, 556, 500, // 216 - 223
+ 444, 444, 444, 444, 444, 444, 667, 444, // 224 - 231
+ 444, 444, 444, 444, 278, 278, 278, 278, // 232 - 239
+ 500, 500, 500, 500, 500, 500, 500, 564, // 240 - 247
+ 500, 500, 500, 500, 500, 500, 500, 500 // 248 - 255
+ },
+ nullptr },
+
+ { "Times", // family name
+ "Italic", // style
+ "Times-Italic", // PSName
+ 683,
+ -217, // ascend, descend
+ FAMILY_ROMAN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NORMAL, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 250, 333, 420, 500, 500, 833, 778, 214, // 32 - 39
+ 333, 333, 500, 675, 250, 333, 250, 278, // 40 - 47
+ 500, 500, 500, 500, 500, 500, 500, 500, // 48 - 55
+ 500, 500, 333, 333, 675, 675, 675, 500, // 56 - 63
+ 920, 611, 611, 667, 722, 611, 611, 722, // 64 - 71
+ 722, 333, 444, 667, 556, 833, 667, 722, // 72 - 79
+ 611, 722, 611, 500, 556, 722, 611, 833, // 80 - 87
+ 611, 556, 556, 389, 278, 389, 422, 500, // 88 - 95
+ 333, 500, 500, 444, 500, 444, 278, 500, // 96 - 103
+ 500, 278, 278, 444, 278, 722, 500, 500, // 104 - 111
+ 500, 500, 389, 389, 278, 500, 444, 667, // 112 - 119
+ 444, 444, 389, 400, 275, 400, 541, 0, // 120 - 127
+ 500, 0, 333, 500, 556, 889, 500, 500, // 128 - 135
+ 333, 1000, 500, 333, 944, 0, 389, 0, // 136 - 143
+ 0, 333, 333, 556, 556, 350, 500, 889, // 144 - 151
+ 333, 980, 389, 333, 667, 0, 389, 556, // 152 - 159
+ 250, 389, 500, 500, 500, 500, 275, 500, // 160 - 167
+ 333, 760, 276, 500, 675, 333, 760, 333, // 168 - 175
+ 400, 675, 300, 300, 333, 500, 523, 250, // 176 - 183
+ 333, 300, 310, 500, 750, 750, 750, 500, // 184 - 191
+ 611, 611, 611, 611, 611, 611, 889, 667, // 192 - 199
+ 611, 611, 611, 611, 333, 333, 333, 333, // 200 - 207
+ 722, 667, 722, 722, 722, 722, 722, 675, // 208 - 215
+ 722, 722, 722, 722, 722, 556, 611, 500, // 216 - 223
+ 500, 500, 500, 500, 500, 500, 667, 444, // 224 - 231
+ 444, 444, 444, 444, 278, 278, 278, 278, // 232 - 239
+ 500, 500, 500, 500, 500, 500, 500, 675, // 240 - 247
+ 500, 500, 500, 500, 500, 444, 500, 444 // 248 - 255
+ },
+ nullptr },
+
+ { "Times", // family name
+ "Bold", // style
+ "Times-Bold", // PSName
+ 683,
+ -217, // ascend, descend
+ FAMILY_ROMAN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_BOLD, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 250, 333, 555, 500, 500, 1000, 833, 278, // 32 - 39
+ 333, 333, 500, 570, 250, 333, 250, 278, // 40 - 47
+ 500, 500, 500, 500, 500, 500, 500, 500, // 48 - 55
+ 500, 500, 333, 333, 570, 570, 570, 500, // 56 - 63
+ 930, 722, 667, 722, 722, 667, 611, 778, // 64 - 71
+ 778, 389, 500, 778, 667, 944, 722, 778, // 72 - 79
+ 611, 778, 722, 556, 667, 722, 722, 1000, // 80 - 87
+ 722, 722, 667, 333, 278, 333, 581, 500, // 88 - 95
+ 333, 500, 556, 444, 556, 444, 333, 500, // 96 - 103
+ 556, 278, 333, 556, 278, 833, 556, 500, // 104 - 111
+ 556, 556, 444, 389, 333, 556, 500, 722, // 112 - 119
+ 500, 500, 444, 394, 220, 394, 520, 0, // 120 - 127
+ 500, 0, 333, 500, 500, 1000, 500, 500, // 128 - 135
+ 333, 1000, 556, 333, 1000, 0, 444, 0, // 136 - 143
+ 0, 333, 333, 500, 500, 350, 500, 1000, // 144 - 151
+ 333, 1000, 389, 333, 722, 0, 444, 722, // 152 - 159
+ 250, 333, 500, 500, 500, 500, 220, 500, // 160 - 167
+ 333, 747, 300, 500, 570, 333, 747, 333, // 168 - 175
+ 400, 570, 300, 300, 333, 556, 540, 250, // 176 - 183
+ 333, 300, 330, 500, 750, 750, 750, 500, // 184 - 191
+ 722, 722, 722, 722, 722, 722, 1000, 722, // 192 - 199
+ 667, 667, 667, 667, 389, 389, 389, 389, // 200 - 207
+ 722, 722, 778, 778, 778, 778, 778, 570, // 208 - 215
+ 778, 722, 722, 722, 722, 722, 611, 556, // 216 - 223
+ 500, 500, 500, 500, 500, 500, 722, 444, // 224 - 231
+ 444, 444, 444, 444, 278, 278, 278, 278, // 232 - 239
+ 500, 556, 500, 500, 500, 500, 500, 570, // 240 - 247
+ 500, 556, 556, 556, 556, 500, 556, 500 // 248 - 255
+ },
+ nullptr },
+
+ { "Times", // family name
+ "Bold Italic", // style
+ "Times-BoldItalic", // PSName
+ 683,
+ -217, // ascend, descend
+ FAMILY_ROMAN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_BOLD, // weight type
+ ITALIC_NORMAL, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 250, 389, 555, 500, 500, 833, 778, 278, // 32 - 39
+ 333, 333, 500, 570, 250, 333, 250, 278, // 40 - 47
+ 500, 500, 500, 500, 500, 500, 500, 500, // 48 - 55
+ 500, 500, 333, 333, 570, 570, 570, 500, // 56 - 63
+ 832, 667, 667, 667, 722, 667, 667, 722, // 64 - 71
+ 778, 389, 500, 667, 611, 889, 722, 722, // 72 - 79
+ 611, 722, 667, 556, 611, 722, 667, 889, // 80 - 87
+ 667, 611, 611, 333, 278, 333, 570, 500, // 88 - 95
+ 333, 500, 500, 444, 500, 444, 333, 500, // 96 - 103
+ 556, 278, 278, 500, 278, 778, 556, 500, // 104 - 111
+ 500, 500, 389, 389, 278, 556, 444, 667, // 112 - 119
+ 500, 444, 389, 348, 220, 348, 570, 0, // 120 - 127
+ 500, 0, 333, 500, 500, 1000, 500, 500, // 128 - 135
+ 333, 1000, 556, 333, 944, 0, 389, 0, // 136 - 143
+ 0, 333, 333, 500, 500, 350, 500, 1000, // 144 - 151
+ 333, 1000, 389, 333, 722, 0, 389, 611, // 152 - 159
+ 250, 389, 500, 500, 500, 500, 220, 500, // 160 - 167
+ 333, 747, 266, 500, 606, 333, 747, 333, // 168 - 175
+ 400, 570, 300, 300, 333, 576, 500, 250, // 176 - 183
+ 333, 300, 300, 500, 750, 750, 750, 500, // 184 - 191
+ 667, 667, 667, 667, 667, 667, 944, 667, // 192 - 199
+ 667, 667, 667, 667, 389, 389, 389, 389, // 200 - 207
+ 722, 722, 722, 722, 722, 722, 722, 570, // 208 - 215
+ 722, 722, 722, 722, 722, 611, 611, 500, // 216 - 223
+ 500, 500, 500, 500, 500, 500, 722, 444, // 224 - 231
+ 444, 444, 444, 444, 278, 278, 278, 278, // 232 - 239
+ 500, 556, 500, 500, 500, 500, 500, 570, // 240 - 247
+ 500, 556, 556, 556, 556, 444, 500, 444 // 248 - 255
+ },
+ nullptr },
+
+ // The font name "Symbol" is too generic and causes plenty of trouble.
+ // To ensure WYSIWIG the PDF-Base14 variant gets a not-confusable name
+ { "PDF_Base14_Symbol", // family name
+ "Normal", // style
+ "Symbol", // PSName
+ 1010,
+ -293, // ascend, descend
+ FAMILY_DONTKNOW, // family style
+ RTL_TEXTENCODING_ADOBE_SYMBOL, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 250, 333, 713, 500, 549, 833, 778, 439, // 32 - 39
+ 333, 333, 500, 549, 250, 549, 250, 278, // 40 - 47
+ 500, 500, 500, 500, 500, 500, 500, 500, // 48 - 55
+ 500, 500, 278, 278, 549, 549, 549, 444, // 56 - 63
+ 549, 722, 667, 722, 612, 611, 763, 603, // 64 - 71
+ 722, 333, 631, 722, 686, 889, 722, 722, // 72 - 79
+ 768, 741, 556, 592, 611, 690, 439, 768, // 80 - 87
+ 645, 795, 611, 333, 863, 333, 658, 500, // 88 - 95
+ 500, 631, 549, 549, 494, 439, 521, 411, // 96 - 103
+ 603, 329, 603, 549, 549, 576, 521, 549, // 104 - 111
+ 549, 521, 549, 603, 439, 576, 713, 686, // 112 - 119
+ 493, 686, 494, 480, 200, 480, 549, 0, // 120 - 127
+ 0, 0, 0, 0, 0, 0, 0, 0, // 128 - 135
+ 0, 0, 0, 0, 0, 0, 0, 0, // 136 - 143
+ 0, 0, 0, 0, 0, 0, 0, 0, // 144 - 151
+ 0, 0, 0, 0, 0, 0, 0, 0, // 152 - 159
+ 750, 620, 247, 549, 167, 713, 500, 753, // 160 - 167
+ 753, 753, 753, 1042, 987, 603, 987, 603, // 168 - 175
+ 400, 549, 411, 549, 549, 713, 494, 460, // 176 - 183
+ 549, 549, 549, 549, 1000, 603, 1000, 658, // 184 - 191
+ 823, 686, 795, 987, 768, 768, 823, 768, // 192 - 199
+ 768, 713, 713, 713, 713, 713, 713, 713, // 200 - 207
+ 768, 713, 790, 790, 890, 823, 549, 250, // 208 - 215
+ 713, 603, 603, 1042, 987, 603, 987, 603, // 216 - 223
+ 494, 329, 790, 790, 786, 713, 384, 384, // 224 - 231
+ 384, 384, 384, 384, 494, 494, 494, 494, // 232 - 239
+ 0, 329, 274, 686, 686, 686, 384, 384, // 240 - 247
+ 384, 384, 384, 384, 494, 494, 494, 0 // 248 - 255
+ },
+ nullptr },
+
+ { "ZapfDingbats", // family name
+ "Normal", // style
+ "ZapfDingbats", // PSName
+ 820,
+ -143, // ascend, descend
+ FAMILY_DONTKNOW, // family style
+ RTL_TEXTENCODING_ADOBE_DINGBATS, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 278, 974, 961, 974, 980, 719, 789, 790, // 32 - 39
+ 791, 690, 960, 939, 549, 855, 911, 933, // 40 - 47
+ 911, 945, 974, 755, 846, 762, 761, 571, // 48 - 55
+ 677, 763, 760, 759, 754, 494, 552, 537, // 56 - 63
+ 577, 692, 786, 788, 788, 790, 793, 794, // 64 - 71
+ 816, 823, 789, 841, 823, 833, 816, 831, // 72 - 79
+ 923, 744, 723, 749, 790, 792, 695, 776, // 80 - 87
+ 768, 792, 759, 707, 708, 682, 701, 826, // 88 - 95
+ 815, 789, 789, 707, 687, 696, 689, 786, // 96 - 103
+ 787, 713, 791, 785, 791, 873, 761, 762, // 104 - 111
+ 762, 759, 759, 892, 892, 788, 784, 438, // 112 - 119
+ 138, 277, 415, 392, 392, 668, 668, 0, // 120 - 127
+ 390, 390, 317, 317, 276, 276, 509, 509, // 128 - 135
+ 410, 410, 234, 234, 334, 334, 0, 0, // 136 - 143
+ 0, 0, 0, 0, 0, 0, 0, 0, // 144 - 151
+ 0, 0, 0, 0, 0, 0, 0, 0, // 152 - 159
+ 0, 732, 544, 544, 910, 667, 760, 760, // 160 - 167
+ 776, 595, 694, 626, 788, 788, 788, 788, // 168 - 175
+ 788, 788, 788, 788, 788, 788, 788, 788, // 176 - 183
+ 788, 788, 788, 788, 788, 788, 788, 788, // 184 - 191
+ 788, 788, 788, 788, 788, 788, 788, 788, // 192 - 199
+ 788, 788, 788, 788, 788, 788, 788, 788, // 200 - 207
+ 788, 788, 788, 788, 894, 838, 1016, 458, // 208 - 215
+ 748, 924, 748, 918, 927, 928, 928, 834, // 216 - 223
+ 873, 828, 924, 924, 917, 930, 931, 463, // 224 - 231
+ 883, 836, 836, 867, 867, 696, 696, 874, // 232 - 239
+ 0, 874, 760, 946, 771, 865, 771, 888, // 240 - 247
+ 967, 888, 831, 873, 927, 970, 918, 0 // 248 - 255
+ },
+ nullptr }
+
+ };
+
+BuildinFontInstance::BuildinFontInstance(const vcl::font::PhysicalFontFace& rFontFace,
+ const vcl::font::FontSelectPattern& rFSP)
+ : LogicalFontInstance(rFontFace, rFSP)
+{
+}
+
+bool BuildinFontInstance::ImplGetGlyphBoundRect(sal_GlyphId, tools::Rectangle&, bool) const
+{
+ return false;
+}
+
+bool BuildinFontInstance::GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const
+{
+ return false;
+}
+
+BuildinFontFace::BuildinFontFace(int nId)
+ : vcl::font::PhysicalFontFace(m_aBuildinFonts[nId].GetFontAttributes())
+ , mrBuildin(m_aBuildinFonts[nId])
+{
+}
+
+rtl::Reference<LogicalFontInstance>
+BuildinFontFace::CreateFontInstance(const vcl::font::FontSelectPattern& rFSP) const
+{
+ return new BuildinFontInstance(*this, rFSP);
+}
+
+} // namespace vcl::pdf
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfextoutdevdata.cxx b/vcl/source/gdi/pdfextoutdevdata.cxx
new file mode 100644
index 000000000..00e4f4a9c
--- /dev/null
+++ b/vcl/source/gdi/pdfextoutdevdata.cxx
@@ -0,0 +1,893 @@
+/* -*- 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 <vcl/canvastools.hxx>
+#include <vcl/pdfextoutdevdata.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/gfxlink.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <tools/stream.hxx>
+
+#include <memory>
+#include <map>
+
+namespace vcl
+{
+namespace {
+
+struct PDFExtOutDevDataSync
+{
+ enum Action{ CreateNamedDest,
+ CreateDest,
+ CreateLink,
+ CreateScreen,
+ SetLinkDest,
+ SetLinkURL,
+ SetScreenURL,
+ SetScreenStream,
+ RegisterDest,
+ CreateOutlineItem,
+ CreateNote,
+ SetPageTransition,
+
+ BeginStructureElement,
+ EndStructureElement,
+ SetCurrentStructureElement,
+ SetStructureAttribute,
+ SetStructureAttributeNumerical,
+ SetStructureBoundingBox,
+ SetActualText,
+ SetAlternateText,
+ CreateControl,
+ BeginGroup,
+ EndGroupGfxLink
+ };
+
+ sal_uInt32 nIdx;
+ Action eAct;
+};
+
+struct PDFLinkDestination
+{
+ tools::Rectangle mRect;
+ MapMode mMapMode;
+ sal_Int32 mPageNr;
+ PDFWriter::DestAreaType mAreaType;
+};
+
+}
+
+struct GlobalSyncData
+{
+ std::deque< PDFExtOutDevDataSync::Action > mActions;
+ std::deque< MapMode > mParaMapModes;
+ std::deque< tools::Rectangle > mParaRects;
+ std::deque< sal_Int32 > mParaInts;
+ std::deque< sal_uInt32 > mParauInts;
+ std::deque< OUString > mParaOUStrings;
+ std::deque< PDFWriter::DestAreaType > mParaDestAreaTypes;
+ std::deque< PDFNote > mParaPDFNotes;
+ std::deque< PDFWriter::PageTransition > mParaPageTransitions;
+ ::std::map< sal_Int32, PDFLinkDestination > mFutureDestinations;
+
+ sal_Int32 GetMappedId();
+ sal_Int32 GetMappedStructId( sal_Int32 );
+
+ sal_Int32 mCurId;
+ std::vector< sal_Int32 > mParaIds;
+ std::vector< sal_Int32 > mStructIdMap;
+
+ sal_Int32 mCurrentStructElement;
+ std::vector< sal_Int32 > mStructParents;
+ GlobalSyncData() :
+ mCurId ( 0 ),
+ mCurrentStructElement( 0 )
+ {
+ mStructParents.push_back( 0 );
+ mStructIdMap.push_back( 0 );
+ }
+ void PlayGlobalActions( PDFWriter& rWriter );
+};
+
+sal_Int32 GlobalSyncData::GetMappedId()
+{
+ sal_Int32 nLinkId = mParaInts.front();
+ mParaInts.pop_front();
+
+ /* negative values are intentionally passed as invalid IDs
+ * e.g. to create a new top level outline item
+ */
+ if( nLinkId >= 0 )
+ {
+ if ( o3tl::make_unsigned(nLinkId) < mParaIds.size() )
+ nLinkId = mParaIds[ nLinkId ];
+ else
+ nLinkId = -1;
+
+ SAL_WARN_IF( nLinkId < 0, "vcl", "unmapped id in GlobalSyncData" );
+ }
+
+ return nLinkId;
+}
+
+sal_Int32 GlobalSyncData::GetMappedStructId( sal_Int32 nStructId )
+{
+ if ( o3tl::make_unsigned(nStructId) < mStructIdMap.size() )
+ nStructId = mStructIdMap[ nStructId ];
+ else
+ nStructId = -1;
+
+ SAL_WARN_IF( nStructId < 0, "vcl", "unmapped structure id in GlobalSyncData" );
+
+ return nStructId;
+}
+
+void GlobalSyncData::PlayGlobalActions( PDFWriter& rWriter )
+{
+ for (auto const& action : mActions)
+ {
+ switch (action)
+ {
+ case PDFExtOutDevDataSync::CreateNamedDest : //i56629
+ {
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( mParaMapModes.front() );
+ mParaMapModes.pop_front();
+ mParaIds.push_back( rWriter.CreateNamedDest( mParaOUStrings.front(), mParaRects.front(), mParaInts.front(), mParaDestAreaTypes.front() ) );
+ mParaOUStrings.pop_front();
+ mParaRects.pop_front();
+ mParaInts.pop_front();
+ mParaDestAreaTypes.pop_front();
+ rWriter.Pop();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateDest :
+ {
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( mParaMapModes.front() );
+ mParaMapModes.pop_front();
+ mParaIds.push_back( rWriter.CreateDest( mParaRects.front(), mParaInts.front(), mParaDestAreaTypes.front() ) );
+ mParaRects.pop_front();
+ mParaInts.pop_front();
+ mParaDestAreaTypes.pop_front();
+ rWriter.Pop();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateLink :
+ {
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( mParaMapModes.front() );
+ mParaMapModes.pop_front();
+ mParaIds.push_back( rWriter.CreateLink(mParaRects.front(), mParaInts.front(), mParaOUStrings.front()) );
+ // resolve LinkAnnotation structural attribute
+ rWriter.SetLinkPropertyID( mParaIds.back(), sal_Int32(mParaIds.size()-1) );
+ mParaRects.pop_front();
+ mParaInts.pop_front();
+ mParaOUStrings.pop_front();
+ rWriter.Pop();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateScreen:
+ {
+ rWriter.Push(PushFlags::MAPMODE);
+ rWriter.SetMapMode(mParaMapModes.front());
+ mParaMapModes.pop_front();
+ mParaIds.push_back(rWriter.CreateScreen(mParaRects.front(), mParaInts.front()));
+ mParaRects.pop_front();
+ mParaInts.pop_front();
+ rWriter.Pop();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetLinkDest :
+ {
+ sal_Int32 nLinkId = GetMappedId();
+ sal_Int32 nDestId = GetMappedId();
+ rWriter.SetLinkDest( nLinkId, nDestId );
+ }
+ break;
+ case PDFExtOutDevDataSync::SetLinkURL :
+ {
+ sal_Int32 nLinkId = GetMappedId();
+ rWriter.SetLinkURL( nLinkId, mParaOUStrings.front() );
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetScreenURL:
+ {
+ sal_Int32 nScreenId = GetMappedId();
+ rWriter.SetScreenURL(nScreenId, mParaOUStrings.front());
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetScreenStream:
+ {
+ sal_Int32 nScreenId = GetMappedId();
+ rWriter.SetScreenStream(nScreenId, mParaOUStrings.front());
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::RegisterDest :
+ {
+ const sal_Int32 nDestId = mParaInts.front();
+ mParaInts.pop_front();
+ OSL_ENSURE( mFutureDestinations.find( nDestId ) != mFutureDestinations.end(),
+ "GlobalSyncData::PlayGlobalActions: DescribeRegisteredRequest has not been called for that destination!" );
+
+ PDFLinkDestination& rDest = mFutureDestinations[ nDestId ];
+
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( rDest.mMapMode );
+ mParaIds.push_back( rWriter.RegisterDestReference( nDestId, rDest.mRect, rDest.mPageNr, rDest.mAreaType ) );
+ rWriter.Pop();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateOutlineItem :
+ {
+ sal_Int32 nParent = GetMappedId();
+ sal_Int32 nLinkId = GetMappedId();
+ mParaIds.push_back( rWriter.CreateOutlineItem( nParent, mParaOUStrings.front(), nLinkId ) );
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateNote :
+ {
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( mParaMapModes.front() );
+ rWriter.CreateNote( mParaRects.front(), mParaPDFNotes.front(), mParaInts.front() );
+ mParaMapModes.pop_front();
+ mParaRects.pop_front();
+ mParaPDFNotes.pop_front();
+ mParaInts.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetPageTransition :
+ {
+ rWriter.SetPageTransition( mParaPageTransitions.front(), mParauInts.front(), mParaInts.front() );
+ mParaPageTransitions.pop_front();
+ mParauInts.pop_front();
+ mParaInts.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::BeginStructureElement:
+ case PDFExtOutDevDataSync::EndStructureElement:
+ case PDFExtOutDevDataSync::SetCurrentStructureElement:
+ case PDFExtOutDevDataSync::SetStructureAttribute:
+ case PDFExtOutDevDataSync::SetStructureAttributeNumerical:
+ case PDFExtOutDevDataSync::SetStructureBoundingBox:
+ case PDFExtOutDevDataSync::SetActualText:
+ case PDFExtOutDevDataSync::SetAlternateText:
+ case PDFExtOutDevDataSync::CreateControl:
+ case PDFExtOutDevDataSync::BeginGroup:
+ case PDFExtOutDevDataSync::EndGroupGfxLink:
+ break;
+ }
+ }
+}
+
+struct PageSyncData
+{
+ std::deque< PDFExtOutDevDataSync > mActions;
+ std::deque< tools::Rectangle > mParaRects;
+ std::deque< sal_Int32 > mParaInts;
+ std::deque< OUString > mParaOUStrings;
+ std::deque< PDFWriter::StructElement > mParaStructElements;
+ std::deque< PDFWriter::StructAttribute > mParaStructAttributes;
+ std::deque< PDFWriter::StructAttributeValue > mParaStructAttributeValues;
+ std::deque< Graphic > mGraphics;
+ Graphic mCurrentGraphic;
+ std::deque< std::shared_ptr< PDFWriter::AnyWidget > >
+ mControls;
+ GlobalSyncData* mpGlobalData;
+
+ bool mbGroupIgnoreGDIMtfActions;
+
+
+ explicit PageSyncData( GlobalSyncData* pGlobal )
+ : mbGroupIgnoreGDIMtfActions ( false )
+ { mpGlobalData = pGlobal; }
+
+ void PushAction( const OutputDevice& rOutDev, const PDFExtOutDevDataSync::Action eAct );
+ bool PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAction, const GDIMetaFile& rMtf, const PDFExtOutDevData& rOutDevData );
+};
+
+void PageSyncData::PushAction( const OutputDevice& rOutDev, const PDFExtOutDevDataSync::Action eAct )
+{
+ GDIMetaFile* pMtf = rOutDev.GetConnectMetaFile();
+ SAL_WARN_IF( !pMtf, "vcl", "PageSyncData::PushAction -> no ConnectMetaFile !!!" );
+
+ PDFExtOutDevDataSync aSync;
+ aSync.eAct = eAct;
+ if ( pMtf )
+ aSync.nIdx = pMtf->GetActionSize();
+ else
+ aSync.nIdx = 0x7fffffff; // sync not possible
+ mActions.push_back( aSync );
+}
+bool PageSyncData::PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAction, const GDIMetaFile& rMtf, const PDFExtOutDevData& rOutDevData )
+{
+ bool bRet = false;
+ if ( !mActions.empty() && ( mActions.front().nIdx == rCurGDIMtfAction ) )
+ {
+ bRet = true;
+ PDFExtOutDevDataSync aDataSync = mActions.front();
+ mActions.pop_front();
+ switch( aDataSync.eAct )
+ {
+ case PDFExtOutDevDataSync::BeginStructureElement :
+ {
+ sal_Int32 nNewEl = rWriter.BeginStructureElement( mParaStructElements.front(), mParaOUStrings.front() ) ;
+ mParaStructElements.pop_front();
+ mParaOUStrings.pop_front();
+ mpGlobalData->mStructIdMap.push_back( nNewEl );
+ }
+ break;
+ case PDFExtOutDevDataSync::EndStructureElement :
+ {
+ rWriter.EndStructureElement();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetCurrentStructureElement:
+ {
+ rWriter.SetCurrentStructureElement( mpGlobalData->GetMappedStructId( mParaInts.front() ) );
+ mParaInts.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetStructureAttribute :
+ {
+ rWriter.SetStructureAttribute( mParaStructAttributes.front(), mParaStructAttributeValues.front() );
+ mParaStructAttributeValues.pop_front();
+ mParaStructAttributes.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetStructureAttributeNumerical :
+ {
+ rWriter.SetStructureAttributeNumerical( mParaStructAttributes.front(), mParaInts.front() );
+ mParaStructAttributes.pop_front();
+ mParaInts.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetStructureBoundingBox :
+ {
+ rWriter.SetStructureBoundingBox( mParaRects.front() );
+ mParaRects.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetActualText :
+ {
+ rWriter.SetActualText( mParaOUStrings.front() );
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetAlternateText :
+ {
+ rWriter.SetAlternateText( mParaOUStrings.front() );
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateControl:
+ {
+ std::shared_ptr< PDFWriter::AnyWidget > pControl( mControls.front() );
+ SAL_WARN_IF( !pControl, "vcl", "PageSyncData::PlaySyncPageAct: invalid widget!" );
+ if ( pControl )
+ rWriter.CreateControl( *pControl );
+ mControls.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::BeginGroup :
+ {
+ /* first determining if this BeginGroup is starting a GfxLink,
+ by searching for an EndGroup or an EndGroupGfxLink */
+ mbGroupIgnoreGDIMtfActions = false;
+ auto isStartingGfxLink = std::any_of(mActions.begin(), mActions.end(),
+ [](const PDFExtOutDevDataSync& rAction) { return rAction.eAct == PDFExtOutDevDataSync::EndGroupGfxLink; });
+ if ( isStartingGfxLink )
+ {
+ Graphic& rGraphic = mGraphics.front();
+ if ( rGraphic.IsGfxLink() && mParaRects.size() >= 2 )
+ {
+ GfxLinkType eType = rGraphic.GetGfxLink().GetType();
+ if ( eType == GfxLinkType::NativeJpg )
+ {
+ mbGroupIgnoreGDIMtfActions = rOutDevData.HasAdequateCompression(rGraphic, mParaRects[0], mParaRects[1]);
+ if ( !mbGroupIgnoreGDIMtfActions )
+ mCurrentGraphic = rGraphic;
+ }
+ else if ( eType == GfxLinkType::NativePng || eType == GfxLinkType::NativePdf )
+ {
+ if ( eType == GfxLinkType::NativePdf || rOutDevData.HasAdequateCompression(rGraphic, mParaRects[0], mParaRects[1]) )
+ mCurrentGraphic = rGraphic;
+ }
+ }
+ }
+ }
+ break;
+ case PDFExtOutDevDataSync::EndGroupGfxLink :
+ {
+ tools::Rectangle aOutputRect, aVisibleOutputRect;
+ Graphic aGraphic( mGraphics.front() );
+
+ mGraphics.pop_front();
+ sal_Int32 nTransparency = mParaInts.front();
+ mParaInts.pop_front();
+ aOutputRect = mParaRects.front();
+ mParaRects.pop_front();
+ aVisibleOutputRect = mParaRects.front();
+ mParaRects.pop_front();
+
+ if ( mbGroupIgnoreGDIMtfActions )
+ {
+ bool bClippingNeeded = ( aOutputRect != aVisibleOutputRect ) && !aVisibleOutputRect.IsEmpty();
+
+ GfxLink aGfxLink( aGraphic.GetGfxLink() );
+ if ( aGfxLink.GetType() == GfxLinkType::NativeJpg )
+ {
+ if ( bClippingNeeded )
+ {
+ rWriter.Push();
+ basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(aVisibleOutputRect) ) );
+ rWriter.SetClipRegion( aRect);
+ }
+
+ AlphaMask aAlphaMask;
+ if (nTransparency)
+ {
+ aAlphaMask = AlphaMask(aGraphic.GetSizePixel());
+ aAlphaMask.Erase(nTransparency);
+ }
+
+ SvMemoryStream aTmp;
+ const sal_uInt8* pData = aGfxLink.GetData();
+ sal_uInt32 nBytes = aGfxLink.GetDataSize();
+ if( pData && nBytes )
+ {
+ aTmp.WriteBytes( pData, nBytes );
+
+ // Look up the output rectangle from the previous
+ // bitmap scale action if possible. This has the
+ // correct position and size for images with a
+ // custom translation (Writer header) or scaling
+ // (Impress notes page).
+ if (rCurGDIMtfAction > 0)
+ {
+ const MetaAction* pAction = rMtf.GetAction(rCurGDIMtfAction - 1);
+ if (pAction && pAction->GetType() == MetaActionType::BMPSCALE)
+ {
+ const MetaBmpScaleAction* pA
+ = static_cast<const MetaBmpScaleAction*>(pAction);
+ aOutputRect.SetPos(pA->GetPoint());
+ aOutputRect.SetSize(pA->GetSize());
+ }
+ }
+ auto ePixelFormat = aGraphic.GetBitmapEx().getPixelFormat();
+ rWriter.DrawJPGBitmap(aTmp, ePixelFormat > vcl::PixelFormat::N8_BPP, aGraphic.GetSizePixel(), aOutputRect, aAlphaMask, aGraphic);
+ }
+
+ if ( bClippingNeeded )
+ rWriter.Pop();
+ }
+ mbGroupIgnoreGDIMtfActions = false;
+ }
+ mCurrentGraphic.Clear();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateNamedDest:
+ case PDFExtOutDevDataSync::CreateDest:
+ case PDFExtOutDevDataSync::CreateLink:
+ case PDFExtOutDevDataSync::CreateScreen:
+ case PDFExtOutDevDataSync::SetLinkDest:
+ case PDFExtOutDevDataSync::SetLinkURL:
+ case PDFExtOutDevDataSync::SetScreenURL:
+ case PDFExtOutDevDataSync::SetScreenStream:
+ case PDFExtOutDevDataSync::RegisterDest:
+ case PDFExtOutDevDataSync::CreateOutlineItem:
+ case PDFExtOutDevDataSync::CreateNote:
+ case PDFExtOutDevDataSync::SetPageTransition:
+ break;
+ }
+ }
+ else if ( mbGroupIgnoreGDIMtfActions )
+ {
+ rCurGDIMtfAction++;
+ bRet = true;
+ }
+ return bRet;
+}
+
+PDFExtOutDevData::PDFExtOutDevData( const OutputDevice& rOutDev ) :
+ mrOutDev ( rOutDev ),
+ mbTaggedPDF ( false ),
+ mbExportNotes ( true ),
+ mbExportNotesPages ( false ),
+ mbTransitionEffects ( true ),
+ mbUseLosslessCompression( true ),
+ mbReduceImageResolution ( false ),
+ mbExportFormFields ( false ),
+ mbExportBookmarks ( false ),
+ mbExportHiddenSlides ( false ),
+ mbSinglePageSheets ( false ),
+ mbExportNDests ( false ),
+ mnPage ( -1 ),
+ mnCompressionQuality ( 90 ),
+ mpGlobalSyncData ( new GlobalSyncData() )
+{
+ mpPageSyncData.reset( new PageSyncData( mpGlobalSyncData.get() ) );
+}
+
+PDFExtOutDevData::~PDFExtOutDevData()
+{
+ mpPageSyncData.reset();
+ mpGlobalSyncData.reset();
+}
+
+const Graphic& PDFExtOutDevData::GetCurrentGraphic() const
+{
+ return mpPageSyncData->mCurrentGraphic;
+}
+
+void PDFExtOutDevData::SetDocumentLocale( const css::lang::Locale& rLoc )
+{
+ maDocLocale = rLoc;
+}
+void PDFExtOutDevData::SetCurrentPageNumber( const sal_Int32 nPage )
+{
+ mnPage = nPage;
+}
+void PDFExtOutDevData::SetIsLosslessCompression( const bool bUseLosslessCompression )
+{
+ mbUseLosslessCompression = bUseLosslessCompression;
+}
+void PDFExtOutDevData::SetCompressionQuality( const sal_Int32 nQuality )
+{
+ mnCompressionQuality = nQuality;
+}
+void PDFExtOutDevData::SetIsReduceImageResolution( const bool bReduceImageResolution )
+{
+ mbReduceImageResolution = bReduceImageResolution;
+}
+void PDFExtOutDevData::SetIsExportNotes( const bool bExportNotes )
+{
+ mbExportNotes = bExportNotes;
+}
+void PDFExtOutDevData::SetIsExportNotesPages( const bool bExportNotesPages )
+{
+ mbExportNotesPages = bExportNotesPages;
+}
+void PDFExtOutDevData::SetIsExportTaggedPDF( const bool bTaggedPDF )
+{
+ mbTaggedPDF = bTaggedPDF;
+}
+void PDFExtOutDevData::SetIsExportTransitionEffects( const bool bTransitionEffects )
+{
+ mbTransitionEffects = bTransitionEffects;
+}
+void PDFExtOutDevData::SetIsExportFormFields( const bool bExportFomtFields )
+{
+ mbExportFormFields = bExportFomtFields;
+}
+void PDFExtOutDevData::SetIsExportBookmarks( const bool bExportBookmarks )
+{
+ mbExportBookmarks = bExportBookmarks;
+}
+void PDFExtOutDevData::SetIsExportHiddenSlides( const bool bExportHiddenSlides )
+{
+ mbExportHiddenSlides = bExportHiddenSlides;
+}
+void PDFExtOutDevData::SetIsSinglePageSheets( const bool bSinglePageSheets )
+{
+ mbSinglePageSheets = bSinglePageSheets;
+}
+void PDFExtOutDevData::SetIsExportNamedDestinations( const bool bExportNDests )
+{
+ mbExportNDests = bExportNDests;
+}
+void PDFExtOutDevData::ResetSyncData()
+{
+ *mpPageSyncData = PageSyncData( mpGlobalSyncData.get() );
+}
+bool PDFExtOutDevData::PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rIdx, const GDIMetaFile& rMtf )
+{
+ return mpPageSyncData->PlaySyncPageAct( rWriter, rIdx, rMtf, *this );
+}
+void PDFExtOutDevData::PlayGlobalActions( PDFWriter& rWriter )
+{
+ mpGlobalSyncData->PlayGlobalActions( rWriter );
+}
+
+/* global actions, synchronisation to the recorded metafile isn't needed,
+ all actions will be played after the last page was recorded
+*/
+//--->i56629
+sal_Int32 PDFExtOutDevData::CreateNamedDest(const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::CreateNamedDest );
+ mpGlobalSyncData->mParaOUStrings.push_back( sDestName );
+ mpGlobalSyncData->mParaRects.push_back( rRect );
+ mpGlobalSyncData->mParaMapModes.push_back( mrOutDev.GetMapMode() );
+ mpGlobalSyncData->mParaInts.push_back( nPageNr == -1 ? mnPage : nPageNr );
+ mpGlobalSyncData->mParaDestAreaTypes.push_back( PDFWriter::DestAreaType::XYZ );
+
+ return mpGlobalSyncData->mCurId++;
+}
+//<---i56629
+sal_Int32 PDFExtOutDevData::RegisterDest()
+{
+ const sal_Int32 nLinkDestID = mpGlobalSyncData->mCurId++;
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::RegisterDest );
+ mpGlobalSyncData->mParaInts.push_back( nLinkDestID );
+
+ return nLinkDestID;
+}
+void PDFExtOutDevData::DescribeRegisteredDest( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ OSL_PRECOND( nDestId != -1, "PDFExtOutDevData::DescribeRegisteredDest: invalid destination Id!" );
+ PDFLinkDestination aLinkDestination;
+ aLinkDestination.mRect = rRect;
+ aLinkDestination.mMapMode = mrOutDev.GetMapMode();
+ aLinkDestination.mPageNr = nPageNr == -1 ? mnPage : nPageNr;
+ aLinkDestination.mAreaType = eType;
+ mpGlobalSyncData->mFutureDestinations[ nDestId ] = aLinkDestination;
+}
+sal_Int32 PDFExtOutDevData::CreateDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::CreateDest );
+ mpGlobalSyncData->mParaRects.push_back( rRect );
+ mpGlobalSyncData->mParaMapModes.push_back( mrOutDev.GetMapMode() );
+ mpGlobalSyncData->mParaInts.push_back( nPageNr == -1 ? mnPage : nPageNr );
+ mpGlobalSyncData->mParaDestAreaTypes.push_back( eType );
+ return mpGlobalSyncData->mCurId++;
+}
+sal_Int32 PDFExtOutDevData::CreateLink(const tools::Rectangle& rRect, OUString const& rAltText, sal_Int32 nPageNr)
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::CreateLink );
+ mpGlobalSyncData->mParaRects.push_back( rRect );
+ mpGlobalSyncData->mParaMapModes.push_back( mrOutDev.GetMapMode() );
+ mpGlobalSyncData->mParaInts.push_back( nPageNr == -1 ? mnPage : nPageNr );
+ mpGlobalSyncData->mParaOUStrings.push_back(rAltText);
+ return mpGlobalSyncData->mCurId++;
+}
+
+sal_Int32 PDFExtOutDevData::CreateScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr)
+{
+ mpGlobalSyncData->mActions.push_back(PDFExtOutDevDataSync::CreateScreen);
+ mpGlobalSyncData->mParaRects.push_back(rRect);
+ mpGlobalSyncData->mParaMapModes.push_back(mrOutDev.GetMapMode());
+ mpGlobalSyncData->mParaInts.push_back(nPageNr);
+ return mpGlobalSyncData->mCurId++;
+}
+
+void PDFExtOutDevData::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::SetLinkDest );
+ mpGlobalSyncData->mParaInts.push_back( nLinkId );
+ mpGlobalSyncData->mParaInts.push_back( nDestId );
+}
+void PDFExtOutDevData::SetLinkURL( sal_Int32 nLinkId, const OUString& rURL )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::SetLinkURL );
+ mpGlobalSyncData->mParaInts.push_back( nLinkId );
+ mpGlobalSyncData->mParaOUStrings.push_back( rURL );
+}
+
+void PDFExtOutDevData::SetScreenURL(sal_Int32 nScreenId, const OUString& rURL)
+{
+ mpGlobalSyncData->mActions.push_back(PDFExtOutDevDataSync::SetScreenURL);
+ mpGlobalSyncData->mParaInts.push_back(nScreenId);
+ mpGlobalSyncData->mParaOUStrings.push_back(rURL);
+}
+
+void PDFExtOutDevData::SetScreenStream(sal_Int32 nScreenId, const OUString& rURL)
+{
+ mpGlobalSyncData->mActions.push_back(PDFExtOutDevDataSync::SetScreenStream);
+ mpGlobalSyncData->mParaInts.push_back(nScreenId);
+ mpGlobalSyncData->mParaOUStrings.push_back(rURL);
+}
+
+sal_Int32 PDFExtOutDevData::CreateOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID )
+{
+ if (nParent == -1)
+ // Has no parent, it's a chapter / heading 1.
+ maChapterNames.push_back(rText);
+
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::CreateOutlineItem );
+ mpGlobalSyncData->mParaInts.push_back( nParent );
+ mpGlobalSyncData->mParaOUStrings.push_back( rText );
+ mpGlobalSyncData->mParaInts.push_back( nDestID );
+ return mpGlobalSyncData->mCurId++;
+}
+void PDFExtOutDevData::CreateNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::CreateNote );
+ mpGlobalSyncData->mParaRects.push_back( rRect );
+ mpGlobalSyncData->mParaMapModes.push_back( mrOutDev.GetMapMode() );
+ mpGlobalSyncData->mParaPDFNotes.push_back( rNote );
+ mpGlobalSyncData->mParaInts.push_back( nPageNr == -1 ? mnPage : nPageNr );
+}
+void PDFExtOutDevData::SetPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::SetPageTransition );
+ mpGlobalSyncData->mParaPageTransitions.push_back( eType );
+ mpGlobalSyncData->mParauInts.push_back( nMilliSec );
+ mpGlobalSyncData->mParaInts.push_back( mnPage );
+}
+
+/* local (page), actions have to be played synchronously to the actions of
+ of the recorded metafile (created by each xRenderable->render()) */
+ sal_Int32 PDFExtOutDevData::BeginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::BeginStructureElement );
+ mpPageSyncData->mParaStructElements.push_back( eType );
+ mpPageSyncData->mParaOUStrings.push_back( rAlias );
+ // need a global id
+ sal_Int32 nNewId = mpGlobalSyncData->mStructParents.size();
+ mpGlobalSyncData->mStructParents.push_back( mpGlobalSyncData->mCurrentStructElement );
+ mpGlobalSyncData->mCurrentStructElement = nNewId;
+ return nNewId;
+}
+void PDFExtOutDevData::EndStructureElement()
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::EndStructureElement );
+ mpGlobalSyncData->mCurrentStructElement = mpGlobalSyncData->mStructParents[ mpGlobalSyncData->mCurrentStructElement ];
+}
+bool PDFExtOutDevData::SetCurrentStructureElement( sal_Int32 nStructId )
+{
+ bool bSuccess = false;
+ if( o3tl::make_unsigned(nStructId) < mpGlobalSyncData->mStructParents.size() )
+ {
+ mpGlobalSyncData->mCurrentStructElement = nStructId;
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetCurrentStructureElement );
+ mpPageSyncData->mParaInts.push_back( nStructId );
+ bSuccess = true;
+ }
+ return bSuccess;
+}
+sal_Int32 PDFExtOutDevData::GetCurrentStructureElement() const
+{
+ return mpGlobalSyncData->mCurrentStructElement;
+}
+void PDFExtOutDevData::SetStructureAttribute( PDFWriter::StructAttribute eAttr, PDFWriter::StructAttributeValue eVal )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetStructureAttribute );
+ mpPageSyncData->mParaStructAttributes.push_back( eAttr );
+ mpPageSyncData->mParaStructAttributeValues.push_back( eVal );
+}
+void PDFExtOutDevData::SetStructureAttributeNumerical( PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetStructureAttributeNumerical );
+ mpPageSyncData->mParaStructAttributes.push_back( eAttr );
+ mpPageSyncData->mParaInts.push_back( nValue );
+}
+void PDFExtOutDevData::SetStructureBoundingBox( const tools::Rectangle& rRect )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetStructureBoundingBox );
+ mpPageSyncData->mParaRects.push_back( rRect );
+}
+void PDFExtOutDevData::SetActualText( const OUString& rText )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetActualText );
+ mpPageSyncData->mParaOUStrings.push_back( rText );
+}
+void PDFExtOutDevData::SetAlternateText( const OUString& rText )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetAlternateText );
+ mpPageSyncData->mParaOUStrings.push_back( rText );
+}
+
+void PDFExtOutDevData::CreateControl( const PDFWriter::AnyWidget& rControlType )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::CreateControl );
+
+ std::shared_ptr< PDFWriter::AnyWidget > pClone( rControlType.Clone() );
+ mpPageSyncData->mControls.push_back( pClone );
+}
+
+void PDFExtOutDevData::BeginGroup()
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::BeginGroup );
+}
+
+void PDFExtOutDevData::EndGroup( const Graphic& rGraphic,
+ sal_uInt8 nTransparency,
+ const tools::Rectangle& rOutputRect,
+ const tools::Rectangle& rVisibleOutputRect )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::EndGroupGfxLink );
+ mpPageSyncData->mGraphics.push_back( rGraphic );
+ mpPageSyncData->mParaInts.push_back( nTransparency );
+ mpPageSyncData->mParaRects.push_back( rOutputRect );
+ mpPageSyncData->mParaRects.push_back( rVisibleOutputRect );
+}
+
+// Avoids expensive de-compression and re-compression of large images.
+bool PDFExtOutDevData::HasAdequateCompression( const Graphic &rGraphic,
+ const tools::Rectangle & rOutputRect,
+ const tools::Rectangle & rVisibleOutputRect ) const
+{
+ assert(rGraphic.IsGfxLink() &&
+ (rGraphic.GetGfxLink().GetType() == GfxLinkType::NativeJpg ||
+ rGraphic.GetGfxLink().GetType() == GfxLinkType::NativePng ||
+ rGraphic.GetGfxLink().GetType() == GfxLinkType::NativePdf));
+
+ if (rOutputRect != rVisibleOutputRect)
+ // rOutputRect is the crop rectangle, re-compress cropped image.
+ return false;
+
+ if (mbReduceImageResolution)
+ // Reducing resolution was requested, implies that re-compressing is
+ // wanted.
+ return false;
+
+ auto nSize = rGraphic.GetGfxLink().GetDataSize();
+ if (nSize == 0)
+ return false;
+
+ GfxLink aLink = rGraphic.GetGfxLink();
+ SvMemoryStream aMemoryStream(const_cast<sal_uInt8*>(aLink.GetData()), aLink.GetDataSize(),
+ StreamMode::READ | StreamMode::WRITE);
+ GraphicDescriptor aDescriptor(aMemoryStream, nullptr);
+ if (aDescriptor.Detect(true) && aDescriptor.GetNumberOfImageComponents() == 4)
+ // 4 means CMYK, which is not handled.
+ return false;
+
+ const Size aSize = rGraphic.GetSizePixel();
+
+ // small items better off as PNG anyway
+ if ( aSize.Width() < 32 &&
+ aSize.Height() < 32 )
+ return false;
+
+ if (GetIsLosslessCompression())
+ return !GetIsReduceImageResolution();
+
+ // FIXME: ideally we'd also pre-empt the DPI related scaling too.
+ sal_Int32 nCurrentRatio = (100 * aSize.Width() * aSize.Height() * 4) /
+ nSize;
+
+ static const struct {
+ sal_Int32 mnQuality;
+ sal_Int32 mnRatio;
+ } aRatios[] = { // minimum tolerable compression ratios
+ { 100, 400 }, { 95, 700 }, { 90, 1000 }, { 85, 1200 },
+ { 80, 1500 }, { 75, 1700 }
+ };
+ sal_Int32 nTargetRatio = 10000;
+ bool bIsTargetRatioReached = false;
+ for (auto & rRatio : aRatios)
+ {
+ if ( mnCompressionQuality > rRatio.mnQuality )
+ {
+ bIsTargetRatioReached = true;
+ break;
+ }
+ nTargetRatio = rRatio.mnRatio;
+ }
+
+ return ((nCurrentRatio > nTargetRatio) && bIsTargetRatioReached);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdffontcache.cxx b/vcl/source/gdi/pdffontcache.cxx
new file mode 100644
index 000000000..e4646f877
--- /dev/null
+++ b/vcl/source/gdi/pdffontcache.cxx
@@ -0,0 +1,66 @@
+/* -*- 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 <sal/types.h>
+
+#include <font/PhysicalFontFace.hxx>
+#include <pdf/pdffontcache.hxx>
+#include <salgdi.hxx>
+
+#include <typeinfo>
+
+using namespace vcl;
+
+PDFFontCache::FontIdentifier::FontIdentifier( const vcl::font::PhysicalFontFace* pFont, bool bVertical ) :
+ m_nFontId( pFont->GetFontId() ),
+ m_bVertical( bVertical ),
+ m_typeFontFace( const_cast<std::type_info*>(&typeid(pFont)) )
+{
+}
+
+PDFFontCache::FontData& PDFFontCache::getFont( const vcl::font::PhysicalFontFace* pFont, bool bVertical )
+{
+ FontIdentifier aId( pFont, bVertical );
+ FontToIndexMap::iterator it = m_aFontToIndex.find( aId );
+ if( it != m_aFontToIndex.end() )
+ return m_aFonts[ it->second ];
+ m_aFontToIndex[ aId ] = sal_uInt32(m_aFonts.size());
+ m_aFonts.emplace_back( );
+ return m_aFonts.back();
+}
+
+sal_Int32 PDFFontCache::getGlyphWidth( const vcl::font::PhysicalFontFace* pFont, sal_GlyphId nGlyph, bool bVertical, SalGraphics* pGraphics )
+{
+ sal_Int32 nWidth = 0;
+ FontData& rFontData( getFont( pFont, bVertical ) );
+ if( rFontData.m_nWidths.empty() )
+ {
+ pGraphics->GetGlyphWidths( pFont, bVertical, rFontData.m_nWidths, rFontData.m_aGlyphIdToIndex );
+ }
+ if( ! rFontData.m_nWidths.empty() )
+ {
+ if (nGlyph < rFontData.m_nWidths.size())
+ nWidth = rFontData.m_nWidths[nGlyph];
+ }
+ return nWidth;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfobjectcopier.cxx b/vcl/source/gdi/pdfobjectcopier.cxx
new file mode 100644
index 000000000..93b7b4989
--- /dev/null
+++ b/vcl/source/gdi/pdfobjectcopier.cxx
@@ -0,0 +1,318 @@
+/* -*- 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/.
+ */
+
+#include <sal/log.hxx>
+#include <sal/types.h>
+#include <rtl/strbuf.hxx>
+#include <tools/stream.hxx>
+#include <tools/zcodec.hxx>
+
+#include <vcl/filter/pdfdocument.hxx>
+#include <vcl/filter/pdfobjectcontainer.hxx>
+
+#include <pdf/objectcopier.hxx>
+#include <pdf/pdfwriter_impl.hxx>
+
+namespace vcl
+{
+PDFObjectCopier::PDFObjectCopier(PDFObjectContainer& rContainer)
+ : m_rContainer(rContainer)
+{
+}
+
+void PDFObjectCopier::copyRecursively(OStringBuffer& rLine, filter::PDFElement& rInputElement,
+ SvMemoryStream& rDocBuffer,
+ std::map<sal_Int32, sal_Int32>& rCopiedResources)
+{
+ if (auto pReference = dynamic_cast<filter::PDFReferenceElement*>(&rInputElement))
+ {
+ filter::PDFObjectElement* pReferenced = pReference->LookupObject();
+ if (pReferenced)
+ {
+ // Copy the referenced object.
+ sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources);
+
+ // Write the updated reference.
+ rLine.append(nRef);
+ rLine.append(" 0 R");
+ }
+ }
+ else if (auto pInputArray = dynamic_cast<filter::PDFArrayElement*>(&rInputElement))
+ {
+ rLine.append("[ ");
+ for (auto const& pElement : pInputArray->GetElements())
+ {
+ copyRecursively(rLine, *pElement, rDocBuffer, rCopiedResources);
+ rLine.append(" ");
+ }
+ rLine.append("] ");
+ }
+ else if (auto pInputDictionary = dynamic_cast<filter::PDFDictionaryElement*>(&rInputElement))
+ {
+ rLine.append("<< ");
+ for (auto const& pPair : pInputDictionary->GetItems())
+ {
+ rLine.append("/");
+ rLine.append(pPair.first);
+ rLine.append(" ");
+ copyRecursively(rLine, *pPair.second, rDocBuffer, rCopiedResources);
+ rLine.append(" ");
+ }
+ rLine.append(">> ");
+ }
+ else
+ {
+ rInputElement.writeString(rLine);
+ }
+}
+
+sal_Int32 PDFObjectCopier::copyExternalResource(SvMemoryStream& rDocBuffer,
+ filter::PDFObjectElement& rObject,
+ std::map<sal_Int32, sal_Int32>& rCopiedResources)
+{
+ auto it = rCopiedResources.find(rObject.GetObjectValue());
+ if (it != rCopiedResources.end())
+ {
+ // This resource was already copied once, nothing to do.
+ return it->second;
+ }
+
+ sal_Int32 nObject = m_rContainer.createObject();
+ // Remember what is the ID of this object in our output.
+ rCopiedResources[rObject.GetObjectValue()] = nObject;
+ SAL_INFO("vcl.pdfwriter", "PDFObjectCopier::copyExternalResource: " << rObject.GetObjectValue()
+ << " -> " << nObject);
+
+ OStringBuffer aLine;
+ aLine.append(nObject);
+ aLine.append(" 0 obj\n");
+
+ if (rObject.GetDictionary())
+ {
+ aLine.append("<< ");
+ bool bFirst = true;
+ for (auto const& rPair : rObject.GetDictionaryItems())
+ {
+ if (bFirst)
+ bFirst = false;
+ else
+ aLine.append(" ");
+
+ aLine.append("/");
+ aLine.append(rPair.first);
+ aLine.append(" ");
+ copyRecursively(aLine, *rPair.second, rDocBuffer, rCopiedResources);
+ }
+
+ aLine.append(" >>\n");
+ }
+
+ filter::PDFStreamElement* pStream = rObject.GetStream();
+ if (pStream)
+ {
+ aLine.append("stream\n");
+ }
+
+ if (filter::PDFArrayElement* pArray = rObject.GetArray())
+ {
+ aLine.append("[ ");
+
+ const std::vector<filter::PDFElement*>& rElements = pArray->GetElements();
+
+ bool bFirst = true;
+ for (auto const& pElement : rElements)
+ {
+ if (bFirst)
+ bFirst = false;
+ else
+ aLine.append(" ");
+ copyRecursively(aLine, *pElement, rDocBuffer, rCopiedResources);
+ }
+ aLine.append("]\n");
+ }
+
+ // If the object has a number element outside a dictionary or array, copy that.
+ if (filter::PDFNumberElement* pNumber = rObject.GetNumberElement())
+ {
+ pNumber->writeString(aLine);
+ aLine.append("\n");
+ }
+
+ // We have the whole object, now write it to the output.
+ if (!m_rContainer.updateObject(nObject))
+ return -1;
+ if (!m_rContainer.writeBuffer(aLine.getStr(), aLine.getLength()))
+ return -1;
+ aLine.setLength(0);
+
+ if (pStream)
+ {
+ SvMemoryStream& rStream = pStream->GetMemory();
+ m_rContainer.checkAndEnableStreamEncryption(nObject);
+ aLine.append(static_cast<const char*>(rStream.GetData()), rStream.GetSize());
+ if (!m_rContainer.writeBuffer(aLine.getStr(), aLine.getLength()))
+ return -1;
+ aLine.setLength(0);
+ m_rContainer.disableStreamEncryption();
+
+ aLine.append("\nendstream\n");
+ if (!m_rContainer.writeBuffer(aLine.getStr(), aLine.getLength()))
+ return -1;
+ aLine.setLength(0);
+ }
+
+ aLine.append("endobj\n\n");
+ if (!m_rContainer.writeBuffer(aLine.getStr(), aLine.getLength()))
+ return -1;
+
+ return nObject;
+}
+
+OString PDFObjectCopier::copyExternalResources(filter::PDFObjectElement& rPage,
+ const OString& rKind,
+ std::map<sal_Int32, sal_Int32>& rCopiedResources)
+{
+ // A name - object ID map, IDs as they appear in our output, not the
+ // original ones.
+ std::map<OString, sal_Int32> aRet;
+
+ // Get the rKind subset of the resource dictionary.
+ std::map<OString, filter::PDFElement*> aItems;
+ if (auto pResources = dynamic_cast<filter::PDFDictionaryElement*>(rPage.Lookup("Resources")))
+ {
+ // Resources is a direct dictionary.
+ filter::PDFElement* pLookup = pResources->LookupElement(rKind);
+ if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pLookup))
+ {
+ // rKind is an inline dictionary.
+ aItems = pDictionary->GetItems();
+ }
+ else if (auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pLookup))
+ {
+ // rKind refers to a dictionary.
+ filter::PDFObjectElement* pReferenced = pReference->LookupObject();
+ if (!pReferenced)
+ {
+ return {};
+ }
+
+ aItems = pReferenced->GetDictionaryItems();
+ }
+ }
+ else if (filter::PDFObjectElement* pPageResources = rPage.LookupObject("Resources"))
+ {
+ // Resources is an indirect object.
+ filter::PDFElement* pValue = pPageResources->Lookup(rKind);
+ if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pValue))
+ // Kind is a direct dictionary.
+ aItems = pDictionary->GetItems();
+ else if (filter::PDFObjectElement* pObject = pPageResources->LookupObject(rKind))
+ // Kind is an indirect object.
+ aItems = pObject->GetDictionaryItems();
+ }
+ if (aItems.empty())
+ return {};
+
+ SvMemoryStream& rDocBuffer = rPage.GetDocument().GetEditBuffer();
+
+ for (const auto& rItem : aItems)
+ {
+ // For each item copy it over to our output then insert it into aRet.
+ auto pReference = dynamic_cast<filter::PDFReferenceElement*>(rItem.second);
+ if (!pReference)
+ continue;
+
+ filter::PDFObjectElement* pValue = pReference->LookupObject();
+ if (!pValue)
+ continue;
+
+ // Then copying over an object copy its dictionary and its stream.
+ sal_Int32 nObject = copyExternalResource(rDocBuffer, *pValue, rCopiedResources);
+ aRet[rItem.first] = nObject;
+ }
+
+ // Build the dictionary entry string.
+ OStringBuffer sRet("/" + rKind + "<<");
+ for (const auto& rPair : aRet)
+ {
+ sRet.append("/" + rPair.first + " " + OString::number(rPair.second) + " 0 R");
+ }
+ sRet.append(">>");
+
+ return sRet.makeStringAndClear();
+}
+
+void PDFObjectCopier::copyPageResources(filter::PDFObjectElement* pPage, OStringBuffer& rLine)
+{
+ // Maps from source object id (PDF image) to target object id (export result).
+ std::map<sal_Int32, sal_Int32> aCopiedResources;
+ copyPageResources(pPage, rLine, aCopiedResources);
+}
+
+void PDFObjectCopier::copyPageResources(filter::PDFObjectElement* pPage, OStringBuffer& rLine,
+ std::map<sal_Int32, sal_Int32>& rCopiedResources)
+{
+ rLine.append(" /Resources <<");
+ static const std::initializer_list<OString> aKeys
+ = { "ColorSpace", "ExtGState", "Font", "XObject", "Shading" };
+ for (const auto& rKey : aKeys)
+ {
+ rLine.append(copyExternalResources(*pPage, rKey, rCopiedResources));
+ }
+ rLine.append(">>");
+}
+
+sal_Int32 PDFObjectCopier::copyPageStreams(std::vector<filter::PDFObjectElement*>& rContentStreams,
+ SvMemoryStream& rStream, bool& rCompressed)
+{
+ for (auto pContent : rContentStreams)
+ {
+ filter::PDFStreamElement* pPageStream = pContent->GetStream();
+ if (!pPageStream)
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFObjectCopier::copyPageStreams: contents has no stream");
+ continue;
+ }
+
+ SvMemoryStream& rPageStream = pPageStream->GetMemory();
+
+ auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter"));
+ if (pFilter)
+ {
+ if (pFilter->GetValue() != "FlateDecode")
+ {
+ continue;
+ }
+
+ SvMemoryStream aMemoryStream;
+ ZCodec aZCodec;
+ rPageStream.Seek(0);
+ aZCodec.BeginCompression();
+ aZCodec.Decompress(rPageStream, aMemoryStream);
+ if (!aZCodec.EndCompression())
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFObjectCopier::copyPageStreams: decompression failed");
+ continue;
+ }
+
+ rStream.WriteBytes(aMemoryStream.GetData(), aMemoryStream.GetSize());
+ }
+ else
+ {
+ rStream.WriteBytes(rPageStream.GetData(), rPageStream.GetSize());
+ }
+ }
+
+ rCompressed = PDFWriterImpl::compressStream(&rStream);
+
+ return rStream.Tell();
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfwriter.cxx b/vcl/source/gdi/pdfwriter.cxx
new file mode 100644
index 000000000..70b6a2345
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter.cxx
@@ -0,0 +1,468 @@
+/* -*- 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 <vcl/bitmapex.hxx>
+
+#include <pdf/pdfwriter_impl.hxx>
+
+using namespace vcl;
+
+PDFWriter::AnyWidget::~AnyWidget()
+{
+}
+
+PDFWriter::PDFWriter( const PDFWriter::PDFWriterContext& rContext, const css::uno::Reference< css::beans::XMaterialHolder >& xEnc )
+ :
+ xImplementation( VclPtr<PDFWriterImpl>::Create(rContext, xEnc, *this) )
+{
+}
+
+PDFWriter::~PDFWriter()
+{
+ xImplementation.disposeAndClear();
+}
+
+OutputDevice* PDFWriter::GetReferenceDevice()
+{
+ return xImplementation.get();
+}
+
+void PDFWriter::NewPage( double nPageWidth, double nPageHeight, Orientation eOrientation )
+{
+ xImplementation->newPage( nPageWidth, nPageHeight, eOrientation );
+}
+
+bool PDFWriter::Emit()
+{
+ return xImplementation->emit();
+}
+
+void PDFWriter::SetDocumentLocale( const css::lang::Locale& rLoc )
+{
+ xImplementation->setDocumentLocale( rLoc );
+}
+
+void PDFWriter::SetFont( const vcl::Font& rFont )
+{
+ xImplementation->setFont( rFont );
+}
+
+void PDFWriter::DrawText( const Point& rPos, const OUString& rText )
+{
+ xImplementation->drawText( rPos, rText, 0, rText.getLength() );
+}
+
+void PDFWriter::DrawTextLine(
+ const Point& rPos,
+ tools::Long nWidth,
+ FontStrikeout eStrikeout,
+ FontLineStyle eUnderline,
+ FontLineStyle eOverline )
+{
+ xImplementation->drawTextLine( rPos, nWidth, eStrikeout, eUnderline, eOverline, false/*bUnderlineAbove*/ );
+}
+
+void PDFWriter::DrawTextArray(
+ const Point& rStartPt,
+ const OUString& rStr,
+ o3tl::span<const sal_Int32> pDXAry,
+ sal_Int32 nIndex,
+ sal_Int32 nLen )
+{
+ xImplementation->drawTextArray( rStartPt, rStr, pDXAry, nIndex, nLen );
+}
+
+void PDFWriter::DrawStretchText(
+ const Point& rStartPt,
+ sal_uLong nWidth,
+ const OUString& rStr,
+ sal_Int32 nIndex,
+ sal_Int32 nLen )
+{
+ xImplementation->drawStretchText( rStartPt, nWidth, rStr, nIndex, nLen );
+}
+
+void PDFWriter::DrawText(
+ const tools::Rectangle& rRect,
+ const OUString& rStr,
+ DrawTextFlags nStyle )
+{
+ xImplementation->drawText( rRect, rStr, nStyle );
+}
+
+void PDFWriter::DrawLine( const Point& rStart, const Point& rStop )
+{
+ xImplementation->drawLine( rStart, rStop );
+}
+
+void PDFWriter::DrawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
+{
+ xImplementation->drawLine( rStart, rStop, rInfo );
+}
+
+void PDFWriter::DrawPolygon( const tools::Polygon& rPoly )
+{
+ xImplementation->drawPolygon( rPoly );
+}
+
+void PDFWriter::DrawPolyLine( const tools::Polygon& rPoly )
+{
+ xImplementation->drawPolyLine( rPoly );
+}
+
+void PDFWriter::DrawRect( const tools::Rectangle& rRect )
+{
+ xImplementation->drawRectangle( rRect );
+}
+
+void PDFWriter::DrawRect( const tools::Rectangle& rRect, sal_uLong nHorzRound, sal_uLong nVertRound )
+{
+ xImplementation->drawRectangle( rRect, nHorzRound, nVertRound );
+}
+
+void PDFWriter::DrawEllipse( const tools::Rectangle& rRect )
+{
+ xImplementation->drawEllipse( rRect );
+}
+
+void PDFWriter::DrawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop )
+{
+ xImplementation->drawArc( rRect, rStart, rStop, false, false );
+}
+
+void PDFWriter::DrawPie( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop )
+{
+ xImplementation->drawArc( rRect, rStart, rStop, true, false );
+}
+
+void PDFWriter::DrawChord( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop )
+{
+ xImplementation->drawArc( rRect, rStart, rStop, false, true );
+}
+
+void PDFWriter::DrawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
+{
+ xImplementation->drawPolyLine( rPoly, rInfo );
+}
+
+void PDFWriter::DrawPolyLine( const tools::Polygon& rPoly, const ExtLineInfo& rInfo )
+{
+ xImplementation->drawPolyLine( rPoly, rInfo );
+}
+
+void PDFWriter::DrawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
+{
+ xImplementation->drawPolyPolygon( rPolyPoly );
+}
+
+void PDFWriter::DrawPixel( const Point& rPos, const Color& rColor )
+{
+ xImplementation->drawPixel( rPos, rColor );
+}
+
+void PDFWriter::DrawBitmap( const Point& rDestPt, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
+{
+ xImplementation->drawBitmap( rDestPt, rDestSize, rBitmap, rGraphic );
+}
+
+void PDFWriter::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize, const BitmapEx& rBitmap )
+{
+ xImplementation->drawBitmap( rDestPt, rDestSize, rBitmap );
+}
+
+void PDFWriter::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
+{
+ xImplementation->drawHatch( rPolyPoly, rHatch );
+}
+
+void PDFWriter::DrawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
+{
+ xImplementation->drawGradient( rRect, rGradient );
+}
+
+void PDFWriter::DrawGradient( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient )
+{
+ xImplementation->push(PushFlags::CLIPREGION);
+ xImplementation->setClipRegion( rPolyPoly.getB2DPolyPolygon() );
+ xImplementation->drawGradient( rPolyPoly.GetBoundRect(), rGradient );
+ xImplementation->pop();
+}
+
+void PDFWriter::DrawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWallpaper )
+{
+ xImplementation->drawWallpaper( rRect, rWallpaper );
+}
+
+void PDFWriter::DrawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt16 nTransparencePercent )
+{
+ xImplementation->drawTransparent( rPolyPoly, nTransparencePercent );
+}
+
+void PDFWriter::BeginTransparencyGroup()
+{
+ xImplementation->beginTransparencyGroup();
+}
+
+void PDFWriter::EndTransparencyGroup( const tools::Rectangle& rRect, sal_uInt16 nTransparentPercent )
+{
+ xImplementation->endTransparencyGroup( rRect, nTransparentPercent );
+}
+
+void PDFWriter::Push( PushFlags nFlags )
+{
+ xImplementation->push( nFlags );
+}
+
+void PDFWriter::Pop()
+{
+ xImplementation->pop();
+}
+
+void PDFWriter::SetMapMode( const MapMode& rMapMode )
+{
+ xImplementation->setMapMode( rMapMode );
+}
+
+void PDFWriter::SetLineColor( const Color& rColor )
+{
+ xImplementation->setLineColor( rColor );
+}
+
+void PDFWriter::SetFillColor( const Color& rColor )
+{
+ xImplementation->setFillColor( rColor );
+}
+
+void PDFWriter::SetClipRegion()
+{
+ xImplementation->clearClipRegion();
+}
+
+void PDFWriter::SetClipRegion( const basegfx::B2DPolyPolygon& rRegion )
+{
+ xImplementation->setClipRegion( rRegion );
+}
+
+void PDFWriter::MoveClipRegion( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ xImplementation->moveClipRegion( nHorzMove, nVertMove );
+}
+
+void PDFWriter::IntersectClipRegion( const basegfx::B2DPolyPolygon& rRegion )
+{
+ xImplementation->intersectClipRegion( rRegion );
+}
+
+void PDFWriter::IntersectClipRegion( const tools::Rectangle& rRect )
+{
+ xImplementation->intersectClipRegion( rRect );
+}
+
+void PDFWriter::SetLayoutMode( vcl::text::ComplexTextLayoutFlags nMode )
+{
+ xImplementation->setLayoutMode( nMode );
+}
+
+void PDFWriter::SetDigitLanguage( LanguageType eLang )
+{
+ xImplementation->setDigitLanguage( eLang );
+}
+
+void PDFWriter::SetTextColor( const Color& rColor )
+{
+ xImplementation->setTextColor( rColor );
+}
+
+void PDFWriter::SetTextFillColor()
+{
+ xImplementation->setTextFillColor();
+}
+
+void PDFWriter::SetTextFillColor( const Color& rColor )
+{
+ xImplementation->setTextFillColor( rColor );
+}
+
+void PDFWriter::SetTextLineColor()
+{
+ xImplementation->setTextLineColor();
+}
+
+void PDFWriter::SetTextLineColor( const Color& rColor )
+{
+ xImplementation->setTextLineColor( rColor );
+}
+
+void PDFWriter::SetOverlineColor()
+{
+ xImplementation->setOverlineColor();
+}
+
+void PDFWriter::SetOverlineColor( const Color& rColor )
+{
+ xImplementation->setOverlineColor( rColor );
+}
+
+void PDFWriter::SetTextAlign( ::TextAlign eAlign )
+{
+ xImplementation->setTextAlign( eAlign );
+}
+
+void PDFWriter::DrawJPGBitmap( SvStream& rStreamData, bool bIsTrueColor, const Size& rSrcSizePixel, const tools::Rectangle& rTargetArea, const AlphaMask& rAlphaMask, const Graphic& rGraphic )
+{
+ xImplementation->drawJPGBitmap( rStreamData, bIsTrueColor, rSrcSizePixel, rTargetArea, rAlphaMask, rGraphic );
+}
+
+sal_Int32 PDFWriter::CreateLink(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText)
+{
+ return xImplementation->createLink(rRect, nPageNr, rAltText);
+}
+
+sal_Int32 PDFWriter::CreateScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr)
+{
+ return xImplementation->createScreen(rRect, nPageNr);
+}
+
+sal_Int32 PDFWriter::RegisterDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, DestAreaType eType )
+{
+ return xImplementation->registerDestReference( nDestId, rRect, nPageNr, eType );
+}
+//--->i56629
+sal_Int32 PDFWriter::CreateNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ return xImplementation->createNamedDest( sDestName, rRect, nPageNr, eType );
+}
+sal_Int32 PDFWriter::CreateDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ return xImplementation->createDest( rRect, nPageNr, eType );
+}
+
+void PDFWriter::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
+{
+ xImplementation->setLinkDest( nLinkId, nDestId );
+}
+
+void PDFWriter::SetLinkURL( sal_Int32 nLinkId, const OUString& rURL )
+{
+ xImplementation->setLinkURL( nLinkId, rURL );
+}
+
+void PDFWriter::SetScreenURL(sal_Int32 nScreenId, const OUString& rURL)
+{
+ xImplementation->setScreenURL(nScreenId, rURL);
+}
+
+void PDFWriter::SetScreenStream(sal_Int32 nScreenId, const OUString& rURL)
+{
+ xImplementation->setScreenStream(nScreenId, rURL);
+}
+
+void PDFWriter::SetLinkPropertyID( sal_Int32 nLinkId, sal_Int32 nPropertyId )
+{
+ xImplementation->setLinkPropertyId( nLinkId, nPropertyId );
+}
+
+sal_Int32 PDFWriter::CreateOutlineItem( sal_Int32 nParent, std::u16string_view rText, sal_Int32 nDestID )
+{
+ return xImplementation->createOutlineItem( nParent, rText, nDestID );
+}
+
+void PDFWriter::CreateNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
+{
+ xImplementation->createNote( rRect, rNote, nPageNr );
+}
+
+sal_Int32 PDFWriter::BeginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias )
+{
+ return xImplementation->beginStructureElement( eType, rAlias );
+}
+
+void PDFWriter::EndStructureElement()
+{
+ xImplementation->endStructureElement();
+}
+
+void PDFWriter::SetCurrentStructureElement( sal_Int32 nID )
+{
+ xImplementation->setCurrentStructureElement( nID );
+}
+
+void PDFWriter::SetStructureAttribute( enum StructAttribute eAttr, enum StructAttributeValue eVal )
+{
+ xImplementation->setStructureAttribute( eAttr, eVal );
+}
+
+void PDFWriter::SetStructureAttributeNumerical( enum StructAttribute eAttr, sal_Int32 nValue )
+{
+ xImplementation->setStructureAttributeNumerical( eAttr, nValue );
+}
+
+void PDFWriter::SetStructureBoundingBox( const tools::Rectangle& rRect )
+{
+ xImplementation->setStructureBoundingBox( rRect );
+}
+
+void PDFWriter::SetActualText( const OUString& rText )
+{
+ xImplementation->setActualText( rText );
+}
+
+void PDFWriter::SetAlternateText( const OUString& rText )
+{
+ xImplementation->setAlternateText( rText );
+}
+
+void PDFWriter::SetPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
+{
+ xImplementation->setPageTransition( eType, nMilliSec, nPageNr );
+}
+
+sal_Int32 PDFWriter::CreateControl( const PDFWriter::AnyWidget& rControl )
+{
+ return xImplementation->createControl( rControl );
+}
+
+PDFOutputStream::~PDFOutputStream()
+{
+}
+
+void PDFWriter::AddStream( const OUString& rMimeType, PDFOutputStream* pStream )
+{
+ xImplementation->addStream( rMimeType, pStream );
+}
+
+std::set< PDFWriter::ErrorCode > const & PDFWriter::GetErrors() const
+{
+ return xImplementation->getErrors();
+}
+
+css::uno::Reference< css::beans::XMaterialHolder >
+PDFWriter::InitEncryption( const OUString& i_rOwnerPassword,
+ const OUString& i_rUserPassword
+ )
+{
+ return PDFWriterImpl::initEncryption( i_rOwnerPassword, i_rUserPassword );
+}
+
+void PDFWriter::PlayMetafile( const GDIMetaFile& i_rMTF, const vcl::PDFWriter::PlayMetafileContext& i_rPlayContext, PDFExtOutDevData* i_pData )
+{
+ xImplementation->playMetafile( i_rMTF, i_pData, i_rPlayContext );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
new file mode 100644
index 000000000..3455c3a0d
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -0,0 +1,11182 @@
+/* -*- 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_crypto.h>
+
+#include <sal/types.h>
+
+#include <math.h>
+#include <algorithm>
+#include <string_view>
+
+#include <lcms2.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <memory>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/util/URL.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <o3tl/numeric.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/temporary.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <osl/file.hxx>
+#include <osl/thread.h>
+#include <rtl/digest.h>
+#include <rtl/uri.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <svl/urihelper.hxx>
+#include <tools/fract.hxx>
+#include <tools/helpers.hxx>
+#include <tools/stream.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/zcodec.hxx>
+#include <svl/cryptosign.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <vcl/glyphitemcache.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/settings.hxx>
+#include <strhelper.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/filter/pdfdocument.hxx>
+#include <comphelper/hash.hxx>
+
+#include <svdata.hxx>
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <fontsubset.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <salgdi.hxx>
+#include <textlayout.hxx>
+#include <textlineinfo.hxx>
+#include <impglyphitem.hxx>
+#include <pdf/XmpMetadata.hxx>
+#include <pdf/objectcopier.hxx>
+#include <pdf/pdfwriter_impl.hxx>
+#include <pdf/PdfConfig.hxx>
+#include <o3tl/sorted_vector.hxx>
+
+#include <config_eot.h>
+
+#if ENABLE_EOT
+#include <libeot/libeot.h>
+#endif
+
+using namespace::com::sun::star;
+
+static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION");
+
+namespace
+{
+
+constexpr sal_Int32 nLog10Divisor = 3;
+constexpr double fDivisor = 1000.0;
+
+constexpr double pixelToPoint(double px)
+{
+ return px / fDivisor;
+}
+
+constexpr sal_Int32 pointToPixel(double pt)
+{
+ return sal_Int32(pt * fDivisor);
+}
+
+void appendHex(sal_Int8 nInt, OStringBuffer& rBuffer)
+{
+ static const char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+ rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] );
+ rBuffer.append( pHexDigits[ nInt & 15 ] );
+}
+
+void appendName( std::u16string_view rStr, OStringBuffer& rBuffer )
+{
+// FIXME i59651 add a check for max length of 127 chars? Per PDF spec 1.4, appendix C.1
+// I guess than when reading the #xx sequence it will count for a single character.
+ OString aStr( OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ) );
+ int nLen = aStr.getLength();
+ for( int i = 0; i < nLen; i++ )
+ {
+ /* #i16920# PDF recommendation: output UTF8, any byte
+ * outside the interval [33(=ASCII'!');126(=ASCII'~')]
+ * should be escaped hexadecimal
+ * for the sake of ghostscript which also reads PDF
+ * but has a narrower acceptance rate we only pass
+ * alphanumerics and '-' literally.
+ */
+ if( (aStr[i] >= 'A' && aStr[i] <= 'Z' ) ||
+ (aStr[i] >= 'a' && aStr[i] <= 'z' ) ||
+ (aStr[i] >= '0' && aStr[i] <= '9' ) ||
+ aStr[i] == '-' )
+ {
+ rBuffer.append( aStr[i] );
+ }
+ else
+ {
+ rBuffer.append( '#' );
+ appendHex( static_cast<sal_Int8>(aStr[i]), rBuffer );
+ }
+ }
+}
+
+void appendName( const char* pStr, OStringBuffer& rBuffer )
+{
+ // FIXME i59651 see above
+ while( pStr && *pStr )
+ {
+ if( (*pStr >= 'A' && *pStr <= 'Z' ) ||
+ (*pStr >= 'a' && *pStr <= 'z' ) ||
+ (*pStr >= '0' && *pStr <= '9' ) ||
+ *pStr == '-' )
+ {
+ rBuffer.append( *pStr );
+ }
+ else
+ {
+ rBuffer.append( '#' );
+ appendHex( static_cast<sal_Int8>(*pStr), rBuffer );
+ }
+ pStr++;
+ }
+}
+
+//used only to emit encoded passwords
+void appendLiteralString( const char* pStr, sal_Int32 nLength, OStringBuffer& rBuffer )
+{
+ while( nLength )
+ {
+ switch( *pStr )
+ {
+ case '\n' :
+ rBuffer.append( "\\n" );
+ break;
+ case '\r' :
+ rBuffer.append( "\\r" );
+ break;
+ case '\t' :
+ rBuffer.append( "\\t" );
+ break;
+ case '\b' :
+ rBuffer.append( "\\b" );
+ break;
+ case '\f' :
+ rBuffer.append( "\\f" );
+ break;
+ case '(' :
+ case ')' :
+ case '\\' :
+ rBuffer.append( "\\" );
+ rBuffer.append( static_cast<char>(*pStr) );
+ break;
+ default:
+ rBuffer.append( static_cast<char>(*pStr) );
+ break;
+ }
+ pStr++;
+ nLength--;
+ }
+}
+
+/*
+ * Convert a string before using it.
+ *
+ * This string conversion function is needed because the destination name
+ * in a PDF file seen through an Internet browser should be
+ * specially crafted, in order to be used directly by the browser.
+ * In this way the fragment part of a hyperlink to a PDF file (e.g. something
+ * as 'test1/test2/a-file.pdf\#thefragment) will be (hopefully) interpreted by the
+ * PDF reader (currently only Adobe Reader plug-in seems to be working that way) called
+ * from inside the Internet browser as: 'open the file test1/test2/a-file.pdf
+ * and go to named destination thefragment using default zoom'.
+ * The conversion is needed because in case of a fragment in the form: Slide%201
+ * (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201
+ * using this conversion, in both the generated named destinations, fragment and GoToR
+ * destination.
+ *
+ * The names for destinations are name objects and so they don't need to be encrypted
+ * even though they expose the content of PDF file (e.g. guessing the PDF content from the
+ * destination name).
+ *
+ * Further limitation: it is advisable to use standard ASCII characters for
+ * OOo bookmarks.
+*/
+void appendDestinationName( const OUString& rString, OStringBuffer& rBuffer )
+{
+ const sal_Unicode* pStr = rString.getStr();
+ sal_Int32 nLen = rString.getLength();
+ for( int i = 0; i < nLen; i++ )
+ {
+ sal_Unicode aChar = pStr[i];
+ if( (aChar >= '0' && aChar <= '9' ) ||
+ (aChar >= 'a' && aChar <= 'z' ) ||
+ (aChar >= 'A' && aChar <= 'Z' ) ||
+ aChar == '-' )
+ {
+ rBuffer.append(static_cast<char>(aChar));
+ }
+ else
+ {
+ sal_Int8 aValueHigh = sal_Int8(aChar >> 8);
+ if(aValueHigh > 0)
+ appendHex( aValueHigh, rBuffer );
+ appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer );
+ }
+ }
+}
+
+} // end anonymous namespace
+
+namespace vcl
+{
+const sal_uInt8 PDFWriterImpl::s_nPadString[32] =
+{
+ 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
+ 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A
+};
+
+namespace
+{
+
+template < class GEOMETRY >
+GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevice* _pPixelConversion, const GEOMETRY& _rObject )
+{
+ GEOMETRY aPoint;
+ if ( MapUnit::MapPixel == _rSource.GetMapUnit() )
+ {
+ aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest );
+ }
+ else
+ {
+ aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest );
+ }
+ return aPoint;
+}
+
+} // end anonymous namespace
+
+void PDFWriter::AppendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer)
+{
+ rBuffer.append( "FEFF" );
+ const sal_Unicode* pStr = rString.getStr();
+ sal_Int32 nLen = rString.getLength();
+ for( int i = 0; i < nLen; i++ )
+ {
+ sal_Unicode aChar = pStr[i];
+ appendHex( static_cast<sal_Int8>(aChar >> 8), rBuffer );
+ appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer );
+ }
+}
+
+void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl )
+{
+ /* #i80258# previously we use appendName here
+ however we need a slightly different coding scheme than the normal
+ name encoding for field names
+ */
+ const OUString& rName = (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2) ? i_rControl.Name : i_rControl.Text;
+ OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) );
+ int nLen = aStr.getLength();
+
+ OStringBuffer aBuffer( rName.getLength()+64 );
+ for( int i = 0; i < nLen; i++ )
+ {
+ /* #i16920# PDF recommendation: output UTF8, any byte
+ * outside the interval [32(=ASCII' ');126(=ASCII'~')]
+ * should be escaped hexadecimal
+ */
+ if( aStr[i] >= 32 && aStr[i] <= 126 )
+ aBuffer.append( aStr[i] );
+ else
+ {
+ aBuffer.append( '#' );
+ appendHex( static_cast<sal_Int8>(aStr[i]), aBuffer );
+ }
+ }
+
+ OString aFullName( aBuffer.makeStringAndClear() );
+
+ /* #i82785# create hierarchical fields down to the for each dot in i_rName */
+ sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0;
+ OString aPartialName;
+ OString aDomain;
+ do
+ {
+ nLastTokenIndex = nTokenIndex;
+ aPartialName = aFullName.getToken( 0, '.', nTokenIndex );
+ if( nTokenIndex != -1 )
+ {
+ // find or create a hierarchical field
+ // first find the fully qualified name up to this field
+ aDomain = aFullName.copy( 0, nTokenIndex-1 );
+ std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
+ if( it == m_aFieldNameMap.end() )
+ {
+ // create new hierarchy field
+ sal_Int32 nNewWidget = m_aWidgets.size();
+ m_aWidgets.emplace_back( );
+ m_aWidgets[nNewWidget].m_nObject = createObject();
+ m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy;
+ m_aWidgets[nNewWidget].m_aName = aPartialName;
+ m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
+ m_aFieldNameMap[aDomain] = nNewWidget;
+ m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
+ if( nLastTokenIndex > 0 )
+ {
+ // this field is not a root field and
+ // needs to be inserted to its parent
+ OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) );
+ it = m_aFieldNameMap.find( aParentDomain );
+ OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" );
+ if( it != m_aFieldNameMap.end() )
+ {
+ OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" );
+ if( it->second < sal_Int32(m_aWidgets.size()) )
+ {
+ PDFWidget& rParentField( m_aWidgets[it->second] );
+ rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject );
+ rParentField.m_aKidsIndex.push_back( nNewWidget );
+ m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject;
+ }
+ }
+ }
+ }
+ else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy )
+ {
+ // this is invalid, someone tries to have a terminal field as parent
+ // example: a button with the name foo.bar exists and
+ // another button is named foo.bar.no
+ // workaround: put the second terminal field as much up in the hierarchy as
+ // necessary to have a non-terminal field as parent (or none at all)
+ // since it->second already is terminal, we just need to use its parent
+ aDomain.clear();
+ aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
+ if( nLastTokenIndex > 0 )
+ {
+ aDomain = aFullName.copy( 0, nLastTokenIndex-1 );
+ aFullName = aDomain + "." + aPartialName;
+ }
+ else
+ aFullName = aPartialName;
+ break;
+ }
+ }
+ } while( nTokenIndex != -1 );
+
+ // insert widget into its hierarchy field
+ if( !aDomain.isEmpty() )
+ {
+ std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
+ if( it != m_aFieldNameMap.end() )
+ {
+ OSL_ENSURE( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size(), "invalid field index" );
+ if( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size() )
+ {
+ m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject;
+ m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject);
+ m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex );
+ }
+ }
+ }
+
+ if( aPartialName.isEmpty() )
+ {
+ // how funny, an empty field name
+ if( i_rControl.getType() == PDFWriter::RadioButton )
+ {
+ aPartialName = "RadioGroup" +
+ OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup );
+ }
+ else
+ aPartialName = OString( "Widget" );
+ }
+
+ if( ! m_aContext.AllowDuplicateFieldNames )
+ {
+ std::unordered_map<OString, sal_Int32>::iterator it = m_aFieldNameMap.find( aFullName );
+
+ if( it != m_aFieldNameMap.end() ) // not unique
+ {
+ std::unordered_map< OString, sal_Int32 >::const_iterator check_it;
+ OString aTry;
+ sal_Int32 nTry = 2;
+ do
+ {
+ OStringBuffer aUnique( aFullName.getLength() + 16 );
+ aUnique.append( aFullName );
+ aUnique.append( '_' );
+ aUnique.append( nTry++ );
+ aTry = aUnique.makeStringAndClear();
+ check_it = m_aFieldNameMap.find( aTry );
+ } while( check_it != m_aFieldNameMap.end() );
+ aFullName = aTry;
+ m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
+ aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
+ }
+ else
+ m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
+ }
+
+ // finally
+ m_aWidgets[i_nWidgetIndex].m_aName = aPartialName;
+}
+
+namespace
+{
+
+void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer )
+{
+ if( nValue < 0 )
+ {
+ rBuffer.append( '-' );
+ nValue = -nValue;
+ }
+ sal_Int32 nFactor = 1, nDiv = nLog10Divisor;
+ while( nDiv-- )
+ nFactor *= 10;
+
+ sal_Int32 nInt = nValue / nFactor;
+ rBuffer.append( nInt );
+ if (nFactor > 1 && nValue % nFactor)
+ {
+ rBuffer.append( '.' );
+ do
+ {
+ nFactor /= 10;
+ rBuffer.append((nValue / nFactor) % 10);
+ }
+ while (nFactor > 1 && nValue % nFactor); // omit trailing zeros
+ }
+}
+
+// appends a double. PDF does not accept exponential format, only fixed point
+void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 10 )
+{
+ bool bNeg = false;
+ if( fValue < 0.0 )
+ {
+ bNeg = true;
+ fValue=-fValue;
+ }
+
+ sal_Int64 nInt = static_cast<sal_Int64>(fValue);
+ fValue -= static_cast<double>(nInt);
+ // optimizing hardware may lead to a value of 1.0 after the subtraction
+ if( rtl::math::approxEqual(fValue, 1.0) || log10( 1.0-fValue ) <= -nPrecision )
+ {
+ nInt++;
+ fValue = 0.0;
+ }
+ sal_Int64 nFrac = 0;
+ if( fValue )
+ {
+ fValue *= pow( 10.0, static_cast<double>(nPrecision) );
+ nFrac = static_cast<sal_Int64>(fValue);
+ }
+ if( bNeg && ( nInt || nFrac ) )
+ rBuffer.append( '-' );
+ rBuffer.append( nInt );
+ if( !nFrac )
+ return;
+
+ int i;
+ rBuffer.append( '.' );
+ sal_Int64 nBound = static_cast<sal_Int64>(pow( 10.0, nPrecision - 1.0 )+0.5);
+ for ( i = 0; ( i < nPrecision ) && nFrac; i++ )
+ {
+ sal_Int64 nNumb = nFrac / nBound;
+ nFrac -= nNumb * nBound;
+ rBuffer.append( nNumb );
+ nBound /= 10;
+ }
+}
+
+void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey )
+{
+
+ if( rColor == COL_TRANSPARENT )
+ return;
+
+ if( bConvertToGrey )
+ {
+ sal_uInt8 cByte = rColor.GetLuminance();
+ appendDouble( static_cast<double>(cByte) / 255.0, rBuffer );
+ }
+ else
+ {
+ appendDouble( static_cast<double>(rColor.GetRed()) / 255.0, rBuffer );
+ rBuffer.append( ' ' );
+ appendDouble( static_cast<double>(rColor.GetGreen()) / 255.0, rBuffer );
+ rBuffer.append( ' ' );
+ appendDouble( static_cast<double>(rColor.GetBlue()) / 255.0, rBuffer );
+ }
+}
+
+} // end anonymous namespace
+
+void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
+{
+ if( rColor != COL_TRANSPARENT )
+ {
+ bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
+ appendColor( rColor, rBuffer, bGrey );
+ rBuffer.append( bGrey ? " G" : " RG" );
+ }
+}
+
+void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
+{
+ if( rColor != COL_TRANSPARENT )
+ {
+ bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
+ appendColor( rColor, rBuffer, bGrey );
+ rBuffer.append( bGrey ? " g" : " rg" );
+ }
+}
+
+namespace
+{
+
+void appendPdfTimeDate(OStringBuffer & rBuffer,
+ sal_Int16 year, sal_uInt16 month, sal_uInt16 day, sal_uInt16 hours, sal_uInt16 minutes, sal_uInt16 seconds, sal_Int32 tzDelta)
+{
+ rBuffer.append("D:");
+ rBuffer.append(char('0' + ((year / 1000) % 10)));
+ rBuffer.append(char('0' + ((year / 100) % 10)));
+ rBuffer.append(char('0' + ((year / 10) % 10)));
+ rBuffer.append(char('0' + (year % 10)));
+ rBuffer.append(char('0' + ((month / 10) % 10)));
+ rBuffer.append(char('0' + (month % 10)));
+ rBuffer.append(char('0' + ((day / 10) % 10)));
+ rBuffer.append(char('0' + (day % 10)));
+ rBuffer.append(char('0' + ((hours / 10) % 10)));
+ rBuffer.append(char('0' + (hours % 10)));
+ rBuffer.append(char('0' + ((minutes / 10) % 10)));
+ rBuffer.append(char('0' + (minutes % 10)));
+ rBuffer.append(char('0' + ((seconds / 10) % 10)));
+ rBuffer.append(char('0' + (seconds % 10)));
+
+ if (tzDelta == 0)
+ {
+ rBuffer.append("Z");
+ }
+ else
+ {
+ if (tzDelta > 0 )
+ rBuffer.append("+");
+ else
+ {
+ rBuffer.append("-");
+ tzDelta = -tzDelta;
+ }
+
+ rBuffer.append(char('0' + ((tzDelta / 36000) % 10)));
+ rBuffer.append(char('0' + ((tzDelta / 3600) % 10)));
+ rBuffer.append("'");
+ rBuffer.append(char('0' + ((tzDelta / 600) % 6)));
+ rBuffer.append(char('0' + ((tzDelta / 60) % 10)));
+ }
+}
+
+} // end namespace
+
+PDFPage::PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
+ :
+ m_pWriter( pWriter ),
+ m_nPageWidth( nPageWidth ),
+ m_nPageHeight( nPageHeight ),
+ m_nUserUnit( 1 ),
+ m_eOrientation( eOrientation ),
+ m_nPageObject( 0 ), // invalid object number
+ m_nStreamLengthObject( 0 ),
+ m_nBeginStreamPos( 0 ),
+ m_eTransition( PDFWriter::PageTransition::Regular ),
+ m_nTransTime( 0 )
+{
+ // object ref must be only ever updated in emit()
+ m_nPageObject = m_pWriter->createObject();
+
+ switch (m_pWriter->m_aContext.Version)
+ {
+ case PDFWriter::PDFVersion::PDF_1_6:
+ m_nUserUnit = std::ceil(std::max(nPageWidth, nPageHeight) / 14400.0);
+ break;
+ default:
+ // 1.2 -> 1.5
+ break;
+ }
+}
+
+void PDFPage::beginStream()
+{
+ if (g_bDebugDisableCompression)
+ {
+ m_pWriter->emitComment("PDFWriterImpl::PDFPage::beginStream, +");
+ }
+ m_aStreamObjects.push_back(m_pWriter->createObject());
+ if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) )
+ return;
+
+ m_nStreamLengthObject = m_pWriter->createObject();
+ // write content stream header
+ OStringBuffer aLine;
+ aLine.append( m_aStreamObjects.back() );
+ aLine.append( " 0 obj\n<</Length " );
+ aLine.append( m_nStreamLengthObject );
+ aLine.append( " 0 R" );
+ if (!g_bDebugDisableCompression)
+ aLine.append( "/Filter/FlateDecode" );
+ aLine.append( ">>\nstream\n" );
+ if( ! m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ) )
+ return;
+ if (osl::File::E_None != m_pWriter->m_aFile.getPos(m_nBeginStreamPos))
+ {
+ m_pWriter->m_aFile.close();
+ m_pWriter->m_bOpen = false;
+ }
+ if (!g_bDebugDisableCompression)
+ m_pWriter->beginCompression();
+ m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() );
+}
+
+void PDFPage::endStream()
+{
+ if (!g_bDebugDisableCompression)
+ m_pWriter->endCompression();
+ sal_uInt64 nEndStreamPos;
+ if (osl::File::E_None != m_pWriter->m_aFile.getPos(nEndStreamPos))
+ {
+ m_pWriter->m_aFile.close();
+ m_pWriter->m_bOpen = false;
+ return;
+ }
+ m_pWriter->disableStreamEncryption();
+ if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
+ return;
+ // emit stream length object
+ if( ! m_pWriter->updateObject( m_nStreamLengthObject ) )
+ return;
+ OString aLine =
+ OString::number( m_nStreamLengthObject ) +
+ " 0 obj\n" +
+ OString::number( static_cast<sal_Int64>(nEndStreamPos-m_nBeginStreamPos) ) +
+ "\nendobj\n\n";
+ m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+bool PDFPage::emit(sal_Int32 nParentObject )
+{
+ m_pWriter->MARK("PDFPage::emit");
+ // emit page object
+ if( ! m_pWriter->updateObject( m_nPageObject ) )
+ return false;
+ OStringBuffer aLine;
+
+ aLine.append( m_nPageObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/Page/Parent " );
+ aLine.append( nParentObject );
+ aLine.append( " 0 R" );
+ aLine.append( "/Resources " );
+ aLine.append( m_pWriter->getResourceDictObj() );
+ aLine.append( " 0 R" );
+ if( m_nPageWidth && m_nPageHeight )
+ {
+ aLine.append( "/MediaBox[0 0 " );
+ aLine.append(m_nPageWidth / m_nUserUnit);
+ aLine.append( ' ' );
+ aLine.append(m_nPageHeight / m_nUserUnit);
+ aLine.append( "]" );
+ if (m_nUserUnit > 1)
+ {
+ aLine.append("\n/UserUnit ");
+ aLine.append(m_nUserUnit);
+ }
+ }
+ switch( m_eOrientation )
+ {
+ case PDFWriter::Orientation::Portrait: aLine.append( "/Rotate 0\n" );break;
+ case PDFWriter::Orientation::Inherit: break;
+ }
+ int nAnnots = m_aAnnotations.size();
+ if( nAnnots > 0 )
+ {
+ aLine.append( "/Annots[\n" );
+ for( int i = 0; i < nAnnots; i++ )
+ {
+ aLine.append( m_aAnnotations[i] );
+ aLine.append( " 0 R" );
+ aLine.append( ((i+1)%15) ? " " : "\n" );
+ }
+ aLine.append( "]\n" );
+ if (m_pWriter->m_aContext.Version != PDFWriter::PDFVersion::PDF_A_1
+ && PDFWriter::PDFVersion::PDF_1_5 <= m_pWriter->m_aContext.Version)
+ {
+ // ISO 14289-1:2014, Clause: 7.18.3
+ aLine.append( "/Tabs(S)\n" );
+ }
+ }
+ if( !m_aMCIDParents.empty() )
+ {
+ OStringBuffer aStructParents( 1024 );
+ aStructParents.append( "[ " );
+ int nParents = m_aMCIDParents.size();
+ for( int i = 0; i < nParents; i++ )
+ {
+ aStructParents.append( m_aMCIDParents[i] );
+ aStructParents.append( " 0 R" );
+ aStructParents.append( ((i%10) == 9) ? "\n" : " " );
+ }
+ aStructParents.append( "]" );
+ m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() );
+
+ aLine.append( "/StructParents " );
+ aLine.append( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) );
+ aLine.append( "\n" );
+ }
+ if( m_eTransition != PDFWriter::PageTransition::Regular && m_nTransTime > 0 )
+ {
+ // transition duration
+ aLine.append( "/Trans<</D " );
+ appendDouble( static_cast<double>(m_nTransTime)/1000.0, aLine, 3 );
+ aLine.append( "\n" );
+ const char *pStyle = nullptr, *pDm = nullptr, *pM = nullptr, *pDi = nullptr;
+ switch( m_eTransition )
+ {
+ case PDFWriter::PageTransition::SplitHorizontalInward:
+ pStyle = "Split"; pDm = "H"; pM = "I"; break;
+ case PDFWriter::PageTransition::SplitHorizontalOutward:
+ pStyle = "Split"; pDm = "H"; pM = "O"; break;
+ case PDFWriter::PageTransition::SplitVerticalInward:
+ pStyle = "Split"; pDm = "V"; pM = "I"; break;
+ case PDFWriter::PageTransition::SplitVerticalOutward:
+ pStyle = "Split"; pDm = "V"; pM = "O"; break;
+ case PDFWriter::PageTransition::BlindsHorizontal:
+ pStyle = "Blinds"; pDm = "H"; break;
+ case PDFWriter::PageTransition::BlindsVertical:
+ pStyle = "Blinds"; pDm = "V"; break;
+ case PDFWriter::PageTransition::BoxInward:
+ pStyle = "Box"; pM = "I"; break;
+ case PDFWriter::PageTransition::BoxOutward:
+ pStyle = "Box"; pM = "O"; break;
+ case PDFWriter::PageTransition::WipeLeftToRight:
+ pStyle = "Wipe"; pDi = "0"; break;
+ case PDFWriter::PageTransition::WipeBottomToTop:
+ pStyle = "Wipe"; pDi = "90"; break;
+ case PDFWriter::PageTransition::WipeRightToLeft:
+ pStyle = "Wipe"; pDi = "180"; break;
+ case PDFWriter::PageTransition::WipeTopToBottom:
+ pStyle = "Wipe"; pDi = "270"; break;
+ case PDFWriter::PageTransition::Dissolve:
+ pStyle = "Dissolve"; break;
+ case PDFWriter::PageTransition::Regular:
+ break;
+ }
+ // transition style
+ if( pStyle )
+ {
+ aLine.append( "/S/" );
+ aLine.append( pStyle );
+ aLine.append( "\n" );
+ }
+ if( pDm )
+ {
+ aLine.append( "/Dm/" );
+ aLine.append( pDm );
+ aLine.append( "\n" );
+ }
+ if( pM )
+ {
+ aLine.append( "/M/" );
+ aLine.append( pM );
+ aLine.append( "\n" );
+ }
+ if( pDi )
+ {
+ aLine.append( "/Di " );
+ aLine.append( pDi );
+ aLine.append( "\n" );
+ }
+ aLine.append( ">>\n" );
+ }
+
+ aLine.append( "/Contents" );
+ unsigned int nStreamObjects = m_aStreamObjects.size();
+ if( nStreamObjects > 1 )
+ aLine.append( '[' );
+ for(sal_Int32 i : m_aStreamObjects)
+ {
+ aLine.append( ' ' );
+ aLine.append( i );
+ aLine.append( " 0 R" );
+ }
+ if( nStreamObjects > 1 )
+ aLine.append( ']' );
+ aLine.append( ">>\nendobj\n\n" );
+ return m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const
+{
+ Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
+ m_pWriter->m_aMapMode,
+ m_pWriter,
+ rPoint ) );
+
+ sal_Int32 nValue = aPoint.X();
+
+ appendFixedInt( nValue, rBuffer );
+
+ rBuffer.append( ' ' );
+
+ nValue = pointToPixel(getHeight()) - aPoint.Y();
+
+ appendFixedInt( nValue, rBuffer );
+}
+
+void PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const
+{
+ double fValue = pixelToPoint(rPoint.getX());
+
+ appendDouble( fValue, rBuffer, nLog10Divisor );
+ rBuffer.append( ' ' );
+ fValue = getHeight() - pixelToPoint(rPoint.getY());
+ appendDouble( fValue, rBuffer, nLog10Divisor );
+}
+
+void PDFPage::appendRect( const tools::Rectangle& rRect, OStringBuffer& rBuffer ) const
+{
+ appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer );
+ rBuffer.append( ' ' );
+ appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), rBuffer, false );
+ rBuffer.append( ' ' );
+ appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), rBuffer );
+ rBuffer.append( " re" );
+}
+
+void PDFPage::convertRect( tools::Rectangle& rRect ) const
+{
+ Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
+ m_pWriter->m_aMapMode,
+ m_pWriter,
+ rRect.BottomLeft() + Point( 0, 1 )
+ );
+ Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
+ m_pWriter->m_aMapMode,
+ m_pWriter,
+ rRect.GetSize() );
+ rRect.SetLeft( aLL.X() );
+ rRect.SetRight( aLL.X() + aSize.Width() );
+ rRect.SetTop( pointToPixel(getHeight()) - aLL.Y() );
+ rRect.SetBottom( rRect.Top() + aSize.Height() );
+}
+
+void PDFPage::appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const
+{
+ sal_uInt16 nPoints = rPoly.GetSize();
+ /*
+ * #108582# applications do weird things
+ */
+ sal_uInt32 nBufLen = rBuffer.getLength();
+ if( nPoints <= 0 )
+ return;
+
+ const PolyFlags* pFlagArray = rPoly.GetConstFlagAry();
+ appendPoint( rPoly[0], rBuffer );
+ rBuffer.append( " m\n" );
+ for( sal_uInt16 i = 1; i < nPoints; i++ )
+ {
+ if( pFlagArray && pFlagArray[i] == PolyFlags::Control && nPoints-i > 2 )
+ {
+ // bezier
+ SAL_WARN_IF( pFlagArray[i+1] != PolyFlags::Control || pFlagArray[i+2] == PolyFlags::Control, "vcl.pdfwriter", "unexpected sequence of control points" );
+ appendPoint( rPoly[i], rBuffer );
+ rBuffer.append( " " );
+ appendPoint( rPoly[i+1], rBuffer );
+ rBuffer.append( " " );
+ appendPoint( rPoly[i+2], rBuffer );
+ rBuffer.append( " c" );
+ i += 2; // add additionally consumed points
+ }
+ else
+ {
+ // line
+ appendPoint( rPoly[i], rBuffer );
+ rBuffer.append( " l" );
+ }
+ if( (rBuffer.getLength() - nBufLen) > 65 )
+ {
+ rBuffer.append( "\n" );
+ nBufLen = rBuffer.getLength();
+ }
+ else
+ rBuffer.append( " " );
+ }
+ if( bClose )
+ rBuffer.append( "h\n" );
+}
+
+void PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const
+{
+ basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
+ m_pWriter->m_aMapMode,
+ m_pWriter,
+ rPoly ) );
+
+ if( basegfx::utils::isRectangle( aPoly ) )
+ {
+ basegfx::B2DRange aRange( aPoly.getB2DRange() );
+ basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() );
+ appendPixelPoint( aBL, rBuffer );
+ rBuffer.append( ' ' );
+ appendMappedLength( aRange.getWidth(), rBuffer, false, nLog10Divisor );
+ rBuffer.append( ' ' );
+ appendMappedLength( aRange.getHeight(), rBuffer, true, nLog10Divisor );
+ rBuffer.append( " re\n" );
+ return;
+ }
+ sal_uInt32 nPoints = aPoly.count();
+ if( nPoints <= 0 )
+ return;
+
+ sal_uInt32 nBufLen = rBuffer.getLength();
+ basegfx::B2DPoint aLastPoint( aPoly.getB2DPoint( 0 ) );
+ appendPixelPoint( aLastPoint, rBuffer );
+ rBuffer.append( " m\n" );
+ for( sal_uInt32 i = 1; i <= nPoints; i++ )
+ {
+ if( i != nPoints || aPoly.isClosed() )
+ {
+ sal_uInt32 nCurPoint = i % nPoints;
+ sal_uInt32 nLastPoint = i-1;
+ basegfx::B2DPoint aPoint( aPoly.getB2DPoint( nCurPoint ) );
+ if( aPoly.isNextControlPointUsed( nLastPoint ) &&
+ aPoly.isPrevControlPointUsed( nCurPoint ) )
+ {
+ appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
+ rBuffer.append( ' ' );
+ appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
+ rBuffer.append( ' ' );
+ appendPixelPoint( aPoint, rBuffer );
+ rBuffer.append( " c" );
+ }
+ else if( aPoly.isNextControlPointUsed( nLastPoint ) )
+ {
+ appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
+ rBuffer.append( ' ' );
+ appendPixelPoint( aPoint, rBuffer );
+ rBuffer.append( " y" );
+ }
+ else if( aPoly.isPrevControlPointUsed( nCurPoint ) )
+ {
+ appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
+ rBuffer.append( ' ' );
+ appendPixelPoint( aPoint, rBuffer );
+ rBuffer.append( " v" );
+ }
+ else
+ {
+ appendPixelPoint( aPoint, rBuffer );
+ rBuffer.append( " l" );
+ }
+ if( (rBuffer.getLength() - nBufLen) > 65 )
+ {
+ rBuffer.append( "\n" );
+ nBufLen = rBuffer.getLength();
+ }
+ else
+ rBuffer.append( " " );
+ }
+ }
+ rBuffer.append( "h\n" );
+}
+
+void PDFPage::appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
+{
+ sal_uInt16 nPolygons = rPolyPoly.Count();
+ for( sal_uInt16 n = 0; n < nPolygons; n++ )
+ appendPolygon( rPolyPoly[n], rBuffer );
+}
+
+void PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
+{
+ for(auto const& rPolygon : rPolyPoly)
+ appendPolygon( rPolygon, rBuffer );
+}
+
+void PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const
+{
+ sal_Int32 nValue = nLength;
+ if ( nLength < 0 )
+ {
+ rBuffer.append( '-' );
+ nValue = -nLength;
+ }
+ Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
+ m_pWriter->m_aMapMode,
+ m_pWriter,
+ Size( nValue, nValue ) ) );
+ nValue = bVertical ? aSize.Height() : aSize.Width();
+ if( pOutLength )
+ *pOutLength = ((nLength < 0 ) ? -nValue : nValue);
+
+ appendFixedInt( nValue, rBuffer );
+}
+
+void PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32 nPrecision ) const
+{
+ Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
+ m_pWriter->m_aMapMode,
+ m_pWriter,
+ Size( 1000, 1000 ) ) );
+ fLength *= pixelToPoint(static_cast<double>(bVertical ? aSize.Height() : aSize.Width()) / 1000.0);
+ appendDouble( fLength, rBuffer, nPrecision );
+}
+
+bool PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const
+{
+ if(LineStyle::Dash == rInfo.GetStyle() && rInfo.GetDashLen() != rInfo.GetDotLen())
+ {
+ // dashed and non-degraded case, check for implementation limits of dash array
+ // in PDF reader apps (e.g. acroread)
+ if(2 * (rInfo.GetDashCount() + rInfo.GetDotCount()) > 10)
+ {
+ return false;
+ }
+ }
+
+ if(basegfx::B2DLineJoin::NONE != rInfo.GetLineJoin())
+ {
+ // LineJoin used, ExtLineInfo required
+ return false;
+ }
+
+ if(css::drawing::LineCap_BUTT != rInfo.GetLineCap())
+ {
+ // LineCap used, ExtLineInfo required
+ return false;
+ }
+
+ if( rInfo.GetStyle() == LineStyle::Dash )
+ {
+ rBuffer.append( "[ " );
+ if( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case
+ {
+ appendMappedLength( rInfo.GetDashLen(), rBuffer );
+ rBuffer.append( ' ' );
+ appendMappedLength( rInfo.GetDistance(), rBuffer );
+ rBuffer.append( ' ' );
+ }
+ else
+ {
+ for( int n = 0; n < rInfo.GetDashCount(); n++ )
+ {
+ appendMappedLength( rInfo.GetDashLen(), rBuffer );
+ rBuffer.append( ' ' );
+ appendMappedLength( rInfo.GetDistance(), rBuffer );
+ rBuffer.append( ' ' );
+ }
+ for( int m = 0; m < rInfo.GetDotCount(); m++ )
+ {
+ appendMappedLength( rInfo.GetDotLen(), rBuffer );
+ rBuffer.append( ' ' );
+ appendMappedLength( rInfo.GetDistance(), rBuffer );
+ rBuffer.append( ' ' );
+ }
+ }
+ rBuffer.append( "] 0 d\n" );
+ }
+
+ if( rInfo.GetWidth() > 1 )
+ {
+ appendMappedLength( rInfo.GetWidth(), rBuffer );
+ rBuffer.append( " w\n" );
+ }
+ else if( rInfo.GetWidth() == 0 )
+ {
+ // "pixel" line
+ appendDouble( 72.0/double(m_pWriter->GetDPIX()), rBuffer );
+ rBuffer.append( " w\n" );
+ }
+
+ return true;
+}
+
+void PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const
+{
+ if( nWidth <= 0 )
+ return;
+ if( nDelta < 1 )
+ nDelta = 1;
+
+ rBuffer.append( "0 " );
+ appendMappedLength( nY, rBuffer );
+ rBuffer.append( " m\n" );
+ for( sal_Int32 n = 0; n < nWidth; )
+ {
+ n += nDelta;
+ appendMappedLength( n, rBuffer, false );
+ rBuffer.append( ' ' );
+ appendMappedLength( nDelta+nY, rBuffer );
+ rBuffer.append( ' ' );
+ n += nDelta;
+ appendMappedLength( n, rBuffer, false );
+ rBuffer.append( ' ' );
+ appendMappedLength( nY, rBuffer );
+ rBuffer.append( " v " );
+ if( n < nWidth )
+ {
+ n += nDelta;
+ appendMappedLength( n, rBuffer, false );
+ rBuffer.append( ' ' );
+ appendMappedLength( nY-nDelta, rBuffer );
+ rBuffer.append( ' ' );
+ n += nDelta;
+ appendMappedLength( n, rBuffer, false );
+ rBuffer.append( ' ' );
+ appendMappedLength( nY, rBuffer );
+ rBuffer.append( " v\n" );
+ }
+ }
+ rBuffer.append( "S\n" );
+}
+
+void PDFPage::appendMatrix3(Matrix3 const & rMatrix, OStringBuffer& rBuffer)
+{
+ appendDouble(rMatrix.get(0), rBuffer);
+ rBuffer.append(' ');
+ appendDouble(rMatrix.get(1), rBuffer);
+ rBuffer.append(' ');
+ appendDouble(rMatrix.get(2), rBuffer);
+ rBuffer.append(' ');
+ appendDouble(rMatrix.get(3), rBuffer);
+ rBuffer.append(' ');
+ appendPoint(Point(tools::Long(rMatrix.get(4)), tools::Long(rMatrix.get(5))), rBuffer);
+}
+
+double PDFPage::getHeight() const
+{
+ double fRet = m_nPageHeight ? m_nPageHeight : vcl::pdf::g_nInheritedPageHeight;
+
+ if (m_nUserUnit > 1)
+ {
+ fRet /= m_nUserUnit;
+ }
+
+ return fRet;
+}
+
+PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext,
+ const css::uno::Reference< css::beans::XMaterialHolder >& xEnc,
+ PDFWriter& i_rOuterFace)
+ : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::DEFAULT, DeviceFormat::NONE, OUTDEV_PDF),
+ m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ),
+ m_aWidgetStyleSettings(Application::GetSettings().GetStyleSettings()),
+ m_nCurrentStructElement( 0 ),
+ m_bEmitStructure( true ),
+ m_nNextFID( 1 ),
+ m_aPDFBmpCache(
+ officecfg::Office::Common::VCL::PDFExportImageCacheSize::get() ),
+ m_nCurrentPage( -1 ),
+ m_nCatalogObject(0),
+ m_nSignatureObject( -1 ),
+ m_nSignatureContentOffset( 0 ),
+ m_nSignatureLastByteRangeNoOffset( 0 ),
+ m_nResourceDict( -1 ),
+ m_nFontDictObject( -1 ),
+ m_aContext(rContext),
+ m_aFile(m_aContext.URL),
+ m_bOpen(false),
+ m_DocDigest(::comphelper::HashType::MD5),
+ m_aCipher( nullptr ),
+ m_nKeyLength(0),
+ m_nRC4KeyLength(0),
+ m_bEncryptThisStream( false ),
+ m_nAccessPermissions(0),
+ m_bIsPDF_A1( false ),
+ m_bIsPDF_A2( false ),
+ m_bIsPDF_UA( false ),
+ m_bIsPDF_A3( false ),
+ m_rOuterFace( i_rOuterFace )
+{
+ m_aStructure.emplace_back( );
+ m_aStructure[0].m_nOwnElement = 0;
+ m_aStructure[0].m_nParentElement = 0;
+
+ Font aFont;
+ aFont.SetFamilyName( "Times" );
+ aFont.SetFontSize( Size( 0, 12 ) );
+
+ // tdf#150786 use the same settings for widgets regardless of theme
+ m_aWidgetStyleSettings.SetStandardStyles();
+
+ GraphicsState aState;
+ aState.m_aMapMode = m_aMapMode;
+ aState.m_aFont = aFont;
+ m_aGraphicsStack.push_front( aState );
+
+ osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
+ if (aError != osl::File::E_None)
+ {
+ if (aError == osl::File::E_EXIST)
+ {
+ aError = m_aFile.open(osl_File_OpenFlag_Write);
+ if (aError == osl::File::E_None)
+ aError = m_aFile.setSize(0);
+ }
+ }
+ if (aError != osl::File::E_None)
+ return;
+
+ m_bOpen = true;
+
+ // setup DocInfo
+ setupDocInfo();
+
+ /* prepare the cypher engine, can be done in CTOR, free in DTOR */
+ m_aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
+
+ if( xEnc.is() )
+ prepareEncryption( xEnc );
+
+ if( m_aContext.Encryption.Encrypt() )
+ {
+ // sanity check
+ if( m_aContext.Encryption.OValue.size() != ENCRYPTED_PWD_SIZE ||
+ m_aContext.Encryption.UValue.size() != ENCRYPTED_PWD_SIZE ||
+ m_aContext.Encryption.EncryptionKey.size() != MAXIMUM_RC4_KEY_LENGTH
+ )
+ {
+ // the field lengths are invalid ? This was not setup by initEncryption.
+ // do not encrypt after all
+ m_aContext.Encryption.OValue.clear();
+ m_aContext.Encryption.UValue.clear();
+ OSL_ENSURE( false, "encryption data failed sanity check, encryption disabled" );
+ }
+ else // setup key lengths
+ m_nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, m_nKeyLength, m_nRC4KeyLength );
+ }
+
+ // write header
+ OStringBuffer aBuffer( 20 );
+ aBuffer.append( "%PDF-" );
+ switch( m_aContext.Version )
+ {
+ case PDFWriter::PDFVersion::PDF_1_2: aBuffer.append( "1.2" );break;
+ case PDFWriter::PDFVersion::PDF_1_3: aBuffer.append( "1.3" );break;
+ case PDFWriter::PDFVersion::PDF_A_1:
+ case PDFWriter::PDFVersion::PDF_1_4: aBuffer.append( "1.4" );break;
+ case PDFWriter::PDFVersion::PDF_1_5: aBuffer.append( "1.5" );break;
+ default:
+ case PDFWriter::PDFVersion::PDF_1_6: aBuffer.append( "1.6" );break;
+ }
+ // append something binary as comment (suggested in PDF Reference)
+ aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" );
+ if( !writeBuffer( aBuffer.getStr(), aBuffer.getLength() ) )
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ return;
+ }
+
+ // insert outline root
+ m_aOutline.emplace_back( );
+
+ m_bIsPDF_A1 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_1);
+ if( m_bIsPDF_A1 )
+ m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour
+
+ m_bIsPDF_A2 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_2);
+ if( m_bIsPDF_A2 )
+ m_aContext.Version = PDFWriter::PDFVersion::PDF_1_6; //we could even use 1.7 features
+
+ m_bIsPDF_A3 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_3);
+ if( m_bIsPDF_A3 )
+ m_aContext.Version = PDFWriter::PDFVersion::PDF_1_6; //we could even use 1.7 features
+
+ if (m_aContext.UniversalAccessibilityCompliance)
+ {
+ m_bIsPDF_UA = true;
+ m_aContext.Tagged = true;
+ }
+
+ if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
+ SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
+ else
+ SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
+
+ SetOutputSizePixel( Size( 640, 480 ) );
+ SetMapMode(MapMode(MapUnit::MapMM));
+}
+
+PDFWriterImpl::~PDFWriterImpl()
+{
+ disposeOnce();
+}
+
+void PDFWriterImpl::dispose()
+{
+ if( m_aCipher )
+ rtl_cipher_destroyARCFOUR( m_aCipher );
+ m_aPages.clear();
+ VirtualDevice::dispose();
+}
+
+bool PDFWriterImpl::ImplNewFont() const
+{
+ const ImplSVData* pSVData = ImplGetSVData();
+
+ if( mxFontCollection == pSVData->maGDIData.mxScreenFontList
+ || mxFontCache == pSVData->maGDIData.mxScreenFontCache )
+ {
+ const_cast<vcl::PDFWriterImpl&>(*this).ImplUpdateFontData();
+ }
+
+ return OutputDevice::ImplNewFont();
+}
+
+void PDFWriterImpl::setupDocInfo()
+{
+ std::vector< sal_uInt8 > aId;
+ m_aCreationDateString = PDFWriter::GetDateTime();
+ computeDocumentIdentifier( aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aCreationMetaDateString );
+ if( m_aContext.Encryption.DocumentIdentifier.empty() )
+ m_aContext.Encryption.DocumentIdentifier = aId;
+}
+
+OString PDFWriter::GetDateTime()
+{
+ OStringBuffer aRet;
+
+ TimeValue aTVal, aGMT;
+ oslDateTime aDT;
+ osl_getSystemTime(&aGMT);
+ osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
+ osl_getDateTimeFromTimeValue(&aTVal, &aDT);
+
+ sal_Int32 nDelta = aTVal.Seconds-aGMT.Seconds;
+
+ appendPdfTimeDate(aRet, aDT.Year, aDT.Month, aDT.Day, aDT.Hours, aDT.Minutes, aDT.Seconds, nDelta);
+
+ aRet.append("'");
+ return aRet.makeStringAndClear();
+}
+
+void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier,
+ const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
+ const OString& i_rCString1,
+ OString& o_rCString2
+ )
+{
+ o_rIdentifier.clear();
+
+ //build the document id
+ OString aInfoValuesOut;
+ OStringBuffer aID( 1024 );
+ if( !i_rDocInfo.Title.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Title, aID);
+ if( !i_rDocInfo.Author.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Author, aID);
+ if( !i_rDocInfo.Subject.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Subject, aID);
+ if( !i_rDocInfo.Keywords.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Keywords, aID);
+ if( !i_rDocInfo.Creator.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Creator, aID);
+ if( !i_rDocInfo.Producer.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Producer, aID);
+
+ TimeValue aTVal, aGMT;
+ oslDateTime aDT;
+ osl_getSystemTime( &aGMT );
+ osl_getLocalTimeFromSystemTime( &aGMT, &aTVal );
+ osl_getDateTimeFromTimeValue( &aTVal, &aDT );
+ OStringBuffer aCreationMetaDateString(64);
+
+ // i59651: we fill the Metadata date string as well, if PDF/A is requested
+ // according to ISO 19005-1:2005 6.7.3 the date is corrected for
+ // local time zone offset UTC only, whereas Acrobat 8 seems
+ // to use the localtime notation only
+ // according to a recommendation in XMP Specification (Jan 2004, page 75)
+ // the Acrobat way seems the right approach
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/1000)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/100)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year)%10)) );
+ aCreationMetaDateString.append( "-" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Month/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Month)%10)) );
+ aCreationMetaDateString.append( "-" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Day/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Day)%10)) );
+ aCreationMetaDateString.append( "T" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Hours/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Hours)%10)) );
+ aCreationMetaDateString.append( ":" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Minutes/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Minutes)%10)) );
+ aCreationMetaDateString.append( ":" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Seconds/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Seconds)%10)) );
+
+ sal_uInt32 nDelta = 0;
+ if( aGMT.Seconds > aTVal.Seconds )
+ {
+ nDelta = aGMT.Seconds-aTVal.Seconds;
+ aCreationMetaDateString.append( "-" );
+ }
+ else if( aGMT.Seconds < aTVal.Seconds )
+ {
+ nDelta = aTVal.Seconds-aGMT.Seconds;
+ aCreationMetaDateString.append( "+" );
+ }
+ else
+ {
+ aCreationMetaDateString.append( "Z" );
+
+ }
+ if( nDelta )
+ {
+ aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/36000)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/3600)%10)) );
+ aCreationMetaDateString.append( ":" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/600)%6)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/60)%10)) );
+ }
+ aID.append( i_rCString1.getStr(), i_rCString1.getLength() );
+
+ aInfoValuesOut = aID.makeStringAndClear();
+ o_rCString2 = aCreationMetaDateString.makeStringAndClear();
+
+ ::comphelper::Hash aDigest(::comphelper::HashType::MD5);
+ aDigest.update(reinterpret_cast<unsigned char const*>(&aGMT), sizeof(aGMT));
+ aDigest.update(reinterpret_cast<unsigned char const*>(aInfoValuesOut.getStr()), aInfoValuesOut.getLength());
+ //the binary form of the doc id is needed for encryption stuff
+ o_rIdentifier = aDigest.finalize();
+}
+
+/* i12626 methods */
+/*
+check if the Unicode string must be encrypted or not, perform the requested task,
+append the string as unicode hex, encrypted if needed
+ */
+inline void PDFWriterImpl::appendUnicodeTextStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
+{
+ rOutBuffer.append( "<" );
+ if( m_aContext.Encryption.Encrypt() )
+ {
+ const sal_Unicode* pStr = rInString.getStr();
+ sal_Int32 nLen = rInString.getLength();
+ //prepare a unicode string, encrypt it
+ enableStringEncryption( nInObjectNumber );
+ sal_uInt8 *pCopy = m_vEncryptionBuffer.data();
+ sal_Int32 nChars = 2 + (nLen * 2);
+ m_vEncryptionBuffer.resize(nChars);
+ *pCopy++ = 0xFE;
+ *pCopy++ = 0xFF;
+ // we need to prepare a byte stream from the unicode string buffer
+ for( int i = 0; i < nLen; i++ )
+ {
+ sal_Unicode aUnChar = pStr[i];
+ *pCopy++ = static_cast<sal_uInt8>( aUnChar >> 8 );
+ *pCopy++ = static_cast<sal_uInt8>( aUnChar & 255 );
+ }
+ //encrypt in place
+ rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChars, m_vEncryptionBuffer.data(), nChars );
+ //now append, hexadecimal (appendHex), the encrypted result
+ for(int i = 0; i < nChars; i++)
+ appendHex( m_vEncryptionBuffer[i], rOutBuffer );
+ }
+ else
+ PDFWriter::AppendUnicodeTextString(rInString, rOutBuffer);
+ rOutBuffer.append( ">" );
+}
+
+inline void PDFWriterImpl::appendLiteralStringEncrypt( std::string_view rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
+{
+ rOutBuffer.append( "(" );
+ sal_Int32 nChars = rInString.size();
+ //check for encryption, if ok, encrypt the string, then convert with appndLiteralString
+ if( m_aContext.Encryption.Encrypt() )
+ {
+ m_vEncryptionBuffer.resize(nChars);
+ //encrypt the string in a buffer, then append it
+ enableStringEncryption( nInObjectNumber );
+ rtl_cipher_encodeARCFOUR( m_aCipher, rInString.data(), nChars, m_vEncryptionBuffer.data(), nChars );
+ appendLiteralString( reinterpret_cast<char*>(m_vEncryptionBuffer.data()), nChars, rOutBuffer );
+ }
+ else
+ appendLiteralString( rInString.data(), nChars , rOutBuffer );
+ rOutBuffer.append( ")" );
+}
+
+void PDFWriterImpl::appendLiteralStringEncrypt( std::u16string_view rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc )
+{
+ OString aBufferString( OUStringToOString( rInString, nEnc ) );
+ sal_Int32 nLen = aBufferString.getLength();
+ OStringBuffer aBuf( nLen );
+ const char* pT = aBufferString.getStr();
+
+ for( sal_Int32 i = 0; i < nLen; i++, pT++ )
+ {
+ if( (*pT & 0x80) == 0 )
+ aBuf.append( *pT );
+ else
+ {
+ aBuf.append( '<' );
+ appendHex( *pT, aBuf );
+ aBuf.append( '>' );
+ }
+ }
+ aBufferString = aBuf.makeStringAndClear();
+ appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
+}
+
+/* end i12626 methods */
+
+void PDFWriterImpl::emitComment( const char* pComment )
+{
+ OString aLine = OString::Concat("% ") + pComment + "\n";
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
+{
+ if (!g_bDebugDisableCompression)
+ {
+ sal_uInt64 nEndPos = pStream->TellEnd();
+ pStream->Seek( STREAM_SEEK_TO_BEGIN );
+ ZCodec aCodec( 0x4000, 0x4000 );
+ SvMemoryStream aStream;
+ aCodec.BeginCompression();
+ aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos );
+ aCodec.EndCompression();
+ nEndPos = aStream.Tell();
+ pStream->Seek( STREAM_SEEK_TO_BEGIN );
+ aStream.Seek( STREAM_SEEK_TO_BEGIN );
+ pStream->SetStreamSize( nEndPos );
+ pStream->WriteBytes( aStream.GetData(), nEndPos );
+ return true;
+ }
+ else
+ return false;
+}
+
+void PDFWriterImpl::beginCompression()
+{
+ if (!g_bDebugDisableCompression)
+ {
+ m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 );
+ m_pMemStream = std::make_unique<SvMemoryStream>();
+ m_pCodec->BeginCompression();
+ }
+}
+
+void PDFWriterImpl::endCompression()
+{
+ if (!g_bDebugDisableCompression && m_pCodec)
+ {
+ m_pCodec->EndCompression();
+ m_pCodec.reset();
+ sal_uInt64 nLen = m_pMemStream->Tell();
+ m_pMemStream->Seek( 0 );
+ writeBuffer( m_pMemStream->GetData(), nLen );
+ m_pMemStream.reset();
+ }
+}
+
+bool PDFWriterImpl::writeBuffer( const void* pBuffer, sal_uInt64 nBytes )
+{
+ if( ! m_bOpen ) // we are already down the drain
+ return false;
+
+ if( ! nBytes ) // huh ?
+ return true;
+
+ if( !m_aOutputStreams.empty() )
+ {
+ m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
+ m_aOutputStreams.front().m_pStream->WriteBytes(
+ pBuffer, sal::static_int_cast<std::size_t>(nBytes));
+ return true;
+ }
+
+ sal_uInt64 nWritten;
+ if( m_pCodec )
+ {
+ m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), static_cast<sal_uLong>(nBytes) );
+ nWritten = nBytes;
+ }
+ else
+ {
+ bool buffOK = true;
+ if( m_bEncryptThisStream )
+ {
+ /* implement the encryption part of the PDF spec encryption algorithm 3.1 */
+ m_vEncryptionBuffer.resize(nBytes);
+ if( buffOK )
+ rtl_cipher_encodeARCFOUR( m_aCipher,
+ pBuffer, static_cast<sal_Size>(nBytes),
+ m_vEncryptionBuffer.data(), static_cast<sal_Size>(nBytes) );
+ }
+
+ const void* pWriteBuffer = ( m_bEncryptThisStream && buffOK ) ? m_vEncryptionBuffer.data() : pBuffer;
+ m_DocDigest.update(static_cast<unsigned char const*>(pWriteBuffer), static_cast<sal_uInt32>(nBytes));
+
+ if (m_aFile.write(pWriteBuffer, nBytes, nWritten) != osl::File::E_None)
+ nWritten = 0;
+
+ if( nWritten != nBytes )
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ }
+ }
+
+ return nWritten == nBytes;
+}
+
+void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
+{
+ endPage();
+ m_nCurrentPage = m_aPages.size();
+ m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation );
+
+ sal_Int32 nUserUnit = m_aPages.back().m_nUserUnit;
+ if (nUserUnit > 1)
+ {
+ m_aMapMode = MapMode(MapUnit::MapPoint, Point(), Fraction(nUserUnit, pointToPixel(1)),
+ Fraction(nUserUnit, pointToPixel(1)));
+ }
+
+ m_aPages.back().beginStream();
+
+ // setup global graphics state
+ // linewidth is "1 pixel" by default
+ OStringBuffer aBuf( 16 );
+ appendDouble( 72.0/double(GetDPIX()), aBuf );
+ aBuf.append( " w\n" );
+ writeBuffer( aBuf.getStr(), aBuf.getLength() );
+}
+
+void PDFWriterImpl::endPage()
+{
+ if( m_aPages.empty() )
+ return;
+
+ // close eventual MC sequence
+ endStructureElementMCSeq();
+
+ // sanity check
+ if( !m_aOutputStreams.empty() )
+ {
+ OSL_FAIL( "redirection across pages !!!" );
+ m_aOutputStreams.clear(); // leak !
+ m_aMapMode.SetOrigin( Point() );
+ }
+
+ m_aGraphicsStack.clear();
+ m_aGraphicsStack.emplace_back( );
+
+ // this should pop the PDF graphics stack if necessary
+ updateGraphicsState();
+
+ m_aPages.back().endStream();
+
+ // reset the default font
+ Font aFont;
+ aFont.SetFamilyName( "Times" );
+ aFont.SetFontSize( Size( 0, 12 ) );
+
+ m_aCurrentPDFState = m_aGraphicsStack.front();
+ m_aGraphicsStack.front().m_aFont = aFont;
+
+ for (auto & bitmap : m_aBitmaps)
+ {
+ if( ! bitmap.m_aBitmap.IsEmpty() )
+ {
+ writeBitmapObject(bitmap);
+ bitmap.m_aBitmap = BitmapEx();
+ }
+ }
+ for (auto & jpeg : m_aJPGs)
+ {
+ if( jpeg.m_pStream )
+ {
+ writeJPG( jpeg );
+ jpeg.m_pStream.reset();
+ jpeg.m_aAlphaMask = AlphaMask();
+ }
+ }
+ for (auto & item : m_aTransparentObjects)
+ {
+ if( item.m_pContentStream )
+ {
+ writeTransparentObject(item);
+ item.m_pContentStream.reset();
+ }
+ }
+
+}
+
+sal_Int32 PDFWriterImpl::createObject()
+{
+ m_aObjects.push_back( ~0U );
+ return m_aObjects.size();
+}
+
+bool PDFWriterImpl::updateObject( sal_Int32 n )
+{
+ if( ! m_bOpen )
+ return false;
+
+ sal_uInt64 nOffset = ~0U;
+ osl::File::RC aError = m_aFile.getPos(nOffset);
+ SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" );
+ if (aError != osl::File::E_None)
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ }
+ m_aObjects[ n-1 ] = nOffset;
+ return aError == osl::File::E_None;
+}
+
+#define CHECK_RETURN( x ) if( !(x) ) return 0
+#define CHECK_RETURN2( x ) if( !(x) ) return
+
+sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject )
+{
+ if( nObject > 0 )
+ {
+ OStringBuffer aLine( 1024 );
+
+ aLine.append( nObject );
+ aLine.append( " 0 obj\n"
+ "<</Nums[\n" );
+ sal_Int32 nTreeItems = m_aStructParentTree.size();
+ for( sal_Int32 n = 0; n < nTreeItems; n++ )
+ {
+ aLine.append( n );
+ aLine.append( ' ' );
+ aLine.append( m_aStructParentTree[n] );
+ aLine.append( "\n" );
+ }
+ aLine.append( "]>>\nendobj\n\n" );
+ CHECK_RETURN( updateObject( nObject ) );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ }
+ return nObject;
+}
+
+const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
+{
+ static std::map< PDFWriter::StructAttribute, const char* > aAttributeStrings;
+ // fill maps once
+ if( aAttributeStrings.empty() )
+ {
+ aAttributeStrings[ PDFWriter::Placement ] = "Placement";
+ aAttributeStrings[ PDFWriter::WritingMode ] = "WritingMode";
+ aAttributeStrings[ PDFWriter::SpaceBefore ] = "SpaceBefore";
+ aAttributeStrings[ PDFWriter::SpaceAfter ] = "SpaceAfter";
+ aAttributeStrings[ PDFWriter::StartIndent ] = "StartIndent";
+ aAttributeStrings[ PDFWriter::EndIndent ] = "EndIndent";
+ aAttributeStrings[ PDFWriter::TextIndent ] = "TextIndent";
+ aAttributeStrings[ PDFWriter::TextAlign ] = "TextAlign";
+ aAttributeStrings[ PDFWriter::Width ] = "Width";
+ aAttributeStrings[ PDFWriter::Height ] = "Height";
+ aAttributeStrings[ PDFWriter::BlockAlign ] = "BlockAlign";
+ aAttributeStrings[ PDFWriter::InlineAlign ] = "InlineAlign";
+ aAttributeStrings[ PDFWriter::LineHeight ] = "LineHeight";
+ aAttributeStrings[ PDFWriter::BaselineShift ] = "BaselineShift";
+ aAttributeStrings[ PDFWriter::TextDecorationType ] = "TextDecorationType";
+ aAttributeStrings[ PDFWriter::ListNumbering ] = "ListNumbering";
+ aAttributeStrings[ PDFWriter::RowSpan ] = "RowSpan";
+ aAttributeStrings[ PDFWriter::ColSpan ] = "ColSpan";
+ aAttributeStrings[ PDFWriter::Scope ] = "Scope";
+ aAttributeStrings[ PDFWriter::LinkAnnotation ] = "LinkAnnotation";
+ }
+
+ std::map< PDFWriter::StructAttribute, const char* >::const_iterator it =
+ aAttributeStrings.find( eAttr );
+
+ if( it == aAttributeStrings.end() )
+ SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr);
+
+ return it != aAttributeStrings.end() ? it->second : "";
+}
+
+const char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal )
+{
+ static std::map< PDFWriter::StructAttributeValue, const char* > aValueStrings;
+
+ if( aValueStrings.empty() )
+ {
+ aValueStrings[ PDFWriter::NONE ] = "None";
+ aValueStrings[ PDFWriter::Block ] = "Block";
+ aValueStrings[ PDFWriter::Inline ] = "Inline";
+ aValueStrings[ PDFWriter::Before ] = "Before";
+ aValueStrings[ PDFWriter::After ] = "After";
+ aValueStrings[ PDFWriter::Start ] = "Start";
+ aValueStrings[ PDFWriter::End ] = "End";
+ aValueStrings[ PDFWriter::LrTb ] = "LrTb";
+ aValueStrings[ PDFWriter::RlTb ] = "RlTb";
+ aValueStrings[ PDFWriter::TbRl ] = "TbRl";
+ aValueStrings[ PDFWriter::Center ] = "Center";
+ aValueStrings[ PDFWriter::Justify ] = "Justify";
+ aValueStrings[ PDFWriter::Auto ] = "Auto";
+ aValueStrings[ PDFWriter::Middle ] = "Middle";
+ aValueStrings[ PDFWriter::Normal ] = "Normal";
+ aValueStrings[ PDFWriter::Underline ] = "Underline";
+ aValueStrings[ PDFWriter::Overline ] = "Overline";
+ aValueStrings[ PDFWriter::LineThrough ] = "LineThrough";
+ aValueStrings[ PDFWriter::Row ] = "Row";
+ aValueStrings[ PDFWriter::Column ] = "Column";
+ aValueStrings[ PDFWriter::Both ] = "Both";
+ aValueStrings[ PDFWriter::Disc ] = "Disc";
+ aValueStrings[ PDFWriter::Circle ] = "Circle";
+ aValueStrings[ PDFWriter::Square ] = "Square";
+ aValueStrings[ PDFWriter::Decimal ] = "Decimal";
+ aValueStrings[ PDFWriter::UpperRoman ] = "UpperRoman";
+ aValueStrings[ PDFWriter::LowerRoman ] = "LowerRoman";
+ aValueStrings[ PDFWriter::UpperAlpha ] = "UpperAlpha";
+ aValueStrings[ PDFWriter::LowerAlpha ] = "LowerAlpha";
+ }
+
+ std::map< PDFWriter::StructAttributeValue, const char* >::const_iterator it =
+ aValueStrings.find( eVal );
+
+ if( it == aValueStrings.end() )
+ SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal);
+
+ return it != aValueStrings.end() ? it->second : "";
+}
+
+static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt )
+{
+ o_rLine.append( "/" );
+ o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) );
+
+ if( i_rVal.eValue != PDFWriter::Invalid )
+ {
+ o_rLine.append( "/" );
+ o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) );
+ }
+ else
+ {
+ // numerical value
+ o_rLine.append( " " );
+ if( i_bIsFixedInt )
+ appendFixedInt( i_rVal.nValue, o_rLine );
+ else
+ o_rLine.append( i_rVal.nValue );
+ }
+ o_rLine.append( "\n" );
+}
+
+OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
+{
+ // create layout, list and table attribute sets
+ OStringBuffer aLayout(256), aList(64), aTable(64);
+ for (auto const& attribute : i_rEle.m_aAttributes)
+ {
+ if( attribute.first == PDFWriter::ListNumbering )
+ appendStructureAttributeLine( attribute.first, attribute.second, aList, true );
+ else if( attribute.first == PDFWriter::RowSpan ||
+ attribute.first == PDFWriter::ColSpan ||
+ attribute.first == PDFWriter::Scope)
+ {
+ appendStructureAttributeLine( attribute.first, attribute.second, aTable, false );
+ }
+ else if( attribute.first == PDFWriter::LinkAnnotation )
+ {
+ sal_Int32 nLink = attribute.second.nValue;
+ std::map< sal_Int32, sal_Int32 >::const_iterator link_it =
+ m_aLinkPropertyMap.find( nLink );
+ if( link_it != m_aLinkPropertyMap.end() )
+ nLink = link_it->second;
+ if( nLink >= 0 && o3tl::make_unsigned(nLink) < m_aLinks.size() )
+ {
+ // update struct parent of link
+ OString aStructParentEntry =
+ OString::number( i_rEle.m_nObject ) +
+ " 0 R";
+ m_aStructParentTree.push_back( aStructParentEntry );
+ m_aLinks[ nLink ].m_nStructParent = m_aStructParentTree.size()-1;
+
+ sal_Int32 nRefObject = createObject();
+ if (updateObject(nRefObject))
+ {
+ OString aRef =
+ OString::number( nRefObject ) +
+ " 0 obj\n"
+ "<</Type/OBJR/Obj " +
+ OString::number( m_aLinks[ nLink ].m_nObject ) +
+ " 0 R>>\n"
+ "endobj\n\n";
+ writeBuffer( aRef.getStr(), aRef.getLength() );
+ }
+
+ i_rEle.m_aKids.emplace_back( nRefObject );
+ }
+ else
+ {
+ OSL_FAIL( "unresolved link id for Link structure" );
+ SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
+ if (g_bDebugDisableCompression)
+ {
+ OString aLine = "unresolved link id " +
+ OString::number( nLink ) +
+ " for Link structure";
+ emitComment( aLine.getStr() );
+ }
+ }
+ }
+ else
+ appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true );
+ }
+ if( ! i_rEle.m_aBBox.IsEmpty() )
+ {
+ aLayout.append( "/BBox[" );
+ appendFixedInt( i_rEle.m_aBBox.Left(), aLayout );
+ aLayout.append( " " );
+ appendFixedInt( i_rEle.m_aBBox.Top(), aLayout );
+ aLayout.append( " " );
+ appendFixedInt( i_rEle.m_aBBox.Right(), aLayout );
+ aLayout.append( " " );
+ appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout );
+ aLayout.append( "]\n" );
+ }
+
+ std::vector< sal_Int32 > aAttribObjects;
+ if( !aLayout.isEmpty() )
+ {
+ aAttribObjects.push_back( createObject() );
+ if (updateObject( aAttribObjects.back() ))
+ {
+ OStringBuffer aObj( 64 );
+ aObj.append( aAttribObjects.back() );
+ aObj.append( " 0 obj\n"
+ "<</O/Layout\n" );
+ aLayout.append( ">>\nendobj\n\n" );
+ writeBuffer( aObj.getStr(), aObj.getLength() );
+ writeBuffer( aLayout.getStr(), aLayout.getLength() );
+ }
+ }
+ if( !aList.isEmpty() )
+ {
+ aAttribObjects.push_back( createObject() );
+ if (updateObject( aAttribObjects.back() ))
+ {
+ OStringBuffer aObj( 64 );
+ aObj.append( aAttribObjects.back() );
+ aObj.append( " 0 obj\n"
+ "<</O/List\n" );
+ aList.append( ">>\nendobj\n\n" );
+ writeBuffer( aObj.getStr(), aObj.getLength() );
+ writeBuffer( aList.getStr(), aList.getLength() );
+ }
+ }
+ if( !aTable.isEmpty() )
+ {
+ aAttribObjects.push_back( createObject() );
+ if (updateObject( aAttribObjects.back() ))
+ {
+ OStringBuffer aObj( 64 );
+ aObj.append( aAttribObjects.back() );
+ aObj.append( " 0 obj\n"
+ "<</O/Table\n" );
+ aTable.append( ">>\nendobj\n\n" );
+ writeBuffer( aObj.getStr(), aObj.getLength() );
+ writeBuffer( aTable.getStr(), aTable.getLength() );
+ }
+ }
+
+ OStringBuffer aRet( 64 );
+ if( aAttribObjects.size() > 1 )
+ aRet.append( " [" );
+ for (auto const& attrib : aAttribObjects)
+ {
+ aRet.append( " " );
+ aRet.append( attrib );
+ aRet.append( " 0 R" );
+ }
+ if( aAttribObjects.size() > 1 )
+ aRet.append( " ]" );
+ return aRet.makeStringAndClear();
+}
+
+sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
+{
+ if(
+ // do not emit NonStruct and its children
+ rEle.m_eType == PDFWriter::NonStructElement &&
+ rEle.m_nOwnElement != rEle.m_nParentElement // but of course emit the struct tree root
+ )
+ return 0;
+
+ for (auto const& child : rEle.m_aChildren)
+ {
+ if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() )
+ {
+ PDFStructureElement& rChild = m_aStructure[ child ];
+ if( rChild.m_eType != PDFWriter::NonStructElement )
+ {
+ if( rChild.m_nParentElement == rEle.m_nOwnElement )
+ emitStructure( rChild );
+ else
+ {
+ OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" );
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure element with id " << child);
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << child);
+ }
+ }
+
+ OStringBuffer aLine( 512 );
+ aLine.append( rEle.m_nObject );
+ aLine.append( " 0 obj\n"
+ "<</Type" );
+ sal_Int32 nParentTree = -1;
+ if( rEle.m_nOwnElement == rEle.m_nParentElement )
+ {
+ nParentTree = createObject();
+ CHECK_RETURN( nParentTree );
+ aLine.append( "/StructTreeRoot\n" );
+ aLine.append( "/ParentTree " );
+ aLine.append( nParentTree );
+ aLine.append( " 0 R\n" );
+ if( ! m_aRoleMap.empty() )
+ {
+ aLine.append( "/RoleMap<<" );
+ for (auto const& role : m_aRoleMap)
+ {
+ aLine.append( '/' );
+ aLine.append(role.first);
+ aLine.append( '/' );
+ aLine.append( role.second );
+ aLine.append( '\n' );
+ }
+ aLine.append( ">>\n" );
+ }
+ }
+ else
+ {
+ aLine.append( "/StructElem\n"
+ "/S/" );
+ if( !rEle.m_aAlias.isEmpty() )
+ aLine.append( rEle.m_aAlias );
+ else
+ aLine.append( getStructureTag( rEle.m_eType ) );
+ aLine.append( "\n"
+ "/P " );
+ aLine.append( m_aStructure[ rEle.m_nParentElement ].m_nObject );
+ aLine.append( " 0 R\n"
+ "/Pg " );
+ aLine.append( rEle.m_nFirstPageObject );
+ aLine.append( " 0 R\n" );
+ if( !rEle.m_aActualText.isEmpty() )
+ {
+ aLine.append( "/ActualText" );
+ appendUnicodeTextStringEncrypt( rEle.m_aActualText, rEle.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !rEle.m_aAltText.isEmpty() )
+ {
+ aLine.append( "/Alt" );
+ appendUnicodeTextStringEncrypt( rEle.m_aAltText, rEle.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+ }
+ if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) )
+ {
+ OString aAttribs = emitStructureAttributes( rEle );
+ if( !aAttribs.isEmpty() )
+ {
+ aLine.append( "/A" );
+ aLine.append( aAttribs );
+ aLine.append( "\n" );
+ }
+ }
+ if( !rEle.m_aLocale.Language.isEmpty() )
+ {
+ /* PDF allows only RFC 3066, which is only partly BCP 47 and does not
+ * include script tags and others.
+ * http://pdf.editme.com/pdfua-naturalLanguageSpecification
+ * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886
+ * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification
+ * */
+ LanguageTag aLanguageTag( rEle.m_aLocale);
+ OUString aLanguage, aScript, aCountry;
+ aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
+ if (!aLanguage.isEmpty())
+ {
+ OUStringBuffer aLocBuf( 16 );
+ aLocBuf.append( aLanguage );
+ if( !aCountry.isEmpty() )
+ {
+ aLocBuf.append( '-' );
+ aLocBuf.append( aCountry );
+ }
+ aLine.append( "/Lang" );
+ appendLiteralStringEncrypt( aLocBuf, rEle.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+ }
+ if( ! rEle.m_aKids.empty() )
+ {
+ unsigned int i = 0;
+ aLine.append( "/K[" );
+ for (auto const& kid : rEle.m_aKids)
+ {
+ if( kid.nMCID == -1 )
+ {
+ aLine.append( kid.nObject );
+ aLine.append( " 0 R" );
+ aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
+ }
+ else
+ {
+ if( kid.nObject == rEle.m_nFirstPageObject )
+ {
+ aLine.append( kid.nMCID );
+ aLine.append( " " );
+ }
+ else
+ {
+ aLine.append( "<</Type/MCR/Pg " );
+ aLine.append( kid.nObject );
+ aLine.append( " 0 R /MCID " );
+ aLine.append( kid.nMCID );
+ aLine.append( ">>\n" );
+ }
+ }
+ ++i;
+ }
+ aLine.append( "]\n" );
+ }
+ aLine.append( ">>\nendobj\n\n" );
+
+ CHECK_RETURN( updateObject( rEle.m_nObject ) );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ CHECK_RETURN( emitStructParentTree( nParentTree ) );
+
+ return rEle.m_nObject;
+}
+
+bool PDFWriterImpl::emitGradients()
+{
+ for (auto const& gradient : m_aGradients)
+ {
+ if ( !writeGradientFunction( gradient ) ) return false;
+ }
+ return true;
+}
+
+bool PDFWriterImpl::emitTilings()
+{
+ OStringBuffer aTilingObj( 1024 );
+
+ for (auto & tiling : m_aTilings)
+ {
+ SAL_WARN_IF( !tiling.m_pTilingStream, "vcl.pdfwriter", "tiling without stream" );
+ if( ! tiling.m_pTilingStream )
+ continue;
+
+ aTilingObj.setLength( 0 );
+
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::emitTilings" );
+ }
+
+ sal_Int32 nX = static_cast<sal_Int32>(tiling.m_aRectangle.Left());
+ sal_Int32 nY = static_cast<sal_Int32>(tiling.m_aRectangle.Top());
+ sal_Int32 nW = static_cast<sal_Int32>(tiling.m_aRectangle.GetWidth());
+ sal_Int32 nH = static_cast<sal_Int32>(tiling.m_aRectangle.GetHeight());
+ if( tiling.m_aCellSize.Width() == 0 )
+ tiling.m_aCellSize.setWidth( nW );
+ if( tiling.m_aCellSize.Height() == 0 )
+ tiling.m_aCellSize.setHeight( nH );
+
+ bool bDeflate = compressStream( tiling.m_pTilingStream.get() );
+ sal_uInt64 const nTilingStreamSize = tiling.m_pTilingStream->TellEnd();
+ tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN );
+
+ // write pattern object
+ aTilingObj.append( tiling.m_nObject );
+ aTilingObj.append( " 0 obj\n" );
+ aTilingObj.append( "<</Type/Pattern/PatternType 1\n"
+ "/PaintType 1\n"
+ "/TilingType 2\n"
+ "/BBox[" );
+ appendFixedInt( nX, aTilingObj );
+ aTilingObj.append( ' ' );
+ appendFixedInt( nY, aTilingObj );
+ aTilingObj.append( ' ' );
+ appendFixedInt( nX+nW, aTilingObj );
+ aTilingObj.append( ' ' );
+ appendFixedInt( nY+nH, aTilingObj );
+ aTilingObj.append( "]\n"
+ "/XStep " );
+ appendFixedInt( tiling.m_aCellSize.Width(), aTilingObj );
+ aTilingObj.append( "\n"
+ "/YStep " );
+ appendFixedInt( tiling.m_aCellSize.Height(), aTilingObj );
+ aTilingObj.append( "\n" );
+ if( tiling.m_aTransform.matrix[0] != 1.0 ||
+ tiling.m_aTransform.matrix[1] != 0.0 ||
+ tiling.m_aTransform.matrix[3] != 0.0 ||
+ tiling.m_aTransform.matrix[4] != 1.0 ||
+ tiling.m_aTransform.matrix[2] != 0.0 ||
+ tiling.m_aTransform.matrix[5] != 0.0 )
+ {
+ aTilingObj.append( "/Matrix [" );
+ // TODO: scaling, mirroring on y, etc
+ appendDouble( tiling.m_aTransform.matrix[0], aTilingObj );
+ aTilingObj.append( ' ' );
+ appendDouble( tiling.m_aTransform.matrix[1], aTilingObj );
+ aTilingObj.append( ' ' );
+ appendDouble( tiling.m_aTransform.matrix[3], aTilingObj );
+ aTilingObj.append( ' ' );
+ appendDouble( tiling.m_aTransform.matrix[4], aTilingObj );
+ aTilingObj.append( ' ' );
+ appendDouble( tiling.m_aTransform.matrix[2], aTilingObj );
+ aTilingObj.append( ' ' );
+ appendDouble( tiling.m_aTransform.matrix[5], aTilingObj );
+ aTilingObj.append( "]\n" );
+ }
+ aTilingObj.append( "/Resources" );
+ tiling.m_aResources.append( aTilingObj, getFontDictObject() );
+ if( bDeflate )
+ aTilingObj.append( "/Filter/FlateDecode" );
+ aTilingObj.append( "/Length " );
+ aTilingObj.append( static_cast<sal_Int32>(nTilingStreamSize) );
+ aTilingObj.append( ">>\nstream\n" );
+ if ( !updateObject( tiling.m_nObject ) ) return false;
+ if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
+ checkAndEnableStreamEncryption( tiling.m_nObject );
+ bool written = writeBuffer( tiling.m_pTilingStream->GetData(), nTilingStreamSize );
+ tiling.m_pTilingStream.reset();
+ if( !written )
+ return false;
+ disableStreamEncryption();
+ aTilingObj.setLength( 0 );
+ aTilingObj.append( "\nendstream\nendobj\n\n" );
+ if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
+ }
+ return true;
+}
+
+sal_Int32 PDFWriterImpl::emitBuildinFont(const pdf::BuildinFontFace* pFD, sal_Int32 nFontObject)
+{
+ if( !pFD )
+ return 0;
+ const pdf::BuildinFont& rBuildinFont = pFD->GetBuildinFont();
+
+ OStringBuffer aLine( 1024 );
+
+ if( nFontObject <= 0 )
+ nFontObject = createObject();
+ CHECK_RETURN( updateObject( nFontObject ) );
+ aLine.append( nFontObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/Font/Subtype/Type1/BaseFont/" );
+ appendName( rBuildinFont.m_pPSName, aLine );
+ aLine.append( "\n" );
+ if( rBuildinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 )
+ aLine.append( "/Encoding/WinAnsiEncoding\n" );
+ aLine.append( ">>\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ return nFontObject;
+}
+
+std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const vcl::font::PhysicalFontFace* pFont, EmbedFont const & rEmbed )
+{
+ std::map< sal_Int32, sal_Int32 > aRet;
+
+ sal_Int32 nFontDescriptor = 0;
+ OString aSubType( "/Type1" );
+ FontSubsetInfo aInfo;
+ // fill in dummy values
+ aInfo.m_nAscent = 1000;
+ aInfo.m_nDescent = 200;
+ aInfo.m_nCapHeight = 1000;
+ aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) );
+ aInfo.m_aPSName = pFont->GetFamilyName();
+ sal_Int32 pWidths[256] = {};
+
+ SalGraphics *pGraphics = GetGraphics();
+
+ assert(pGraphics);
+
+ aSubType = OString( "/TrueType" );
+ std::vector< sal_Int32 > aGlyphWidths;
+ Ucs2UIntMap aUnicodeMap;
+ pGraphics->GetGlyphWidths( pFont, false, aGlyphWidths, aUnicodeMap );
+
+ OUString aTmpName;
+ osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
+ sal_GlyphId aGlyphIds[ 256 ] = {};
+ sal_uInt8 pEncoding[ 256 ] = {};
+ sal_Int32 pDuWidths[ 256 ] = {};
+
+ for( sal_Ucs c = 32; c < 256; c++ )
+ {
+ pEncoding[c] = c;
+ aGlyphIds[c] = 0;
+ if( aUnicodeMap.find( c ) != aUnicodeMap.end() )
+ pWidths[ c ] = aGlyphWidths[ aUnicodeMap[ c ] ];
+ }
+ //TODO: surely this is utterly broken because aGlyphIds is just all zeros, if we
+ //had the right glyphids here then I imagine we could replace pDuWidths with
+ //pWidths and remove pWidths assignment above. i.e. start with the glyph ids
+ //and map those to unicode rather than try and reverse map them ?
+ pGraphics->CreateFontSubset( aTmpName, pFont, aGlyphIds, pEncoding, pDuWidths, 256, aInfo );
+ osl_removeFile( aTmpName.pData );
+
+ // write font descriptor
+ nFontDescriptor = emitFontDescriptor( pFont, aInfo, 0, 0 );
+ if( nFontDescriptor )
+ {
+ // write font object
+ sal_Int32 nObject = createObject();
+ if( updateObject( nObject ) )
+ {
+ OStringBuffer aLine( 1024 );
+ aLine.append( nObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/Font/Subtype" );
+ aLine.append( aSubType );
+ aLine.append( "/BaseFont/" );
+ appendName( aInfo.m_aPSName, aLine );
+ aLine.append( "\n" );
+ if( !pFont->IsSymbolFont() )
+ aLine.append( "/Encoding/WinAnsiEncoding\n" );
+ aLine.append( "/FirstChar 32 /LastChar 255\n"
+ "/Widths[" );
+ for( int i = 32; i < 256; i++ )
+ {
+ aLine.append( pWidths[i] );
+ aLine.append( ((i&15) == 15) ? "\n" : " " );
+ }
+ aLine.append( "]\n"
+ "/FontDescriptor " );
+ aLine.append( nFontDescriptor );
+ aLine.append( " 0 R>>\n"
+ "endobj\n\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ aRet[ rEmbed.m_nNormalFontID ] = nObject;
+ }
+ }
+
+ return aRet;
+}
+
+typedef int ThreeInts[3];
+static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen,
+ ThreeInts& rSegmentLengths )
+{
+ if( !pFontBytes || (nByteLen < 0) )
+ return false;
+ const unsigned char* pPtr = pFontBytes;
+ const unsigned char* pEnd = pFontBytes + nByteLen;
+
+ for(int & rSegmentLength : rSegmentLengths) {
+ // read segment1 header
+ if( pPtr+6 >= pEnd )
+ return false;
+ if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) )
+ return false;
+ const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2];
+ if( nLen <= 0)
+ return false;
+ rSegmentLength = nLen;
+ pPtr += nLen + 6;
+ }
+
+ // read segment-end header
+ if( pPtr+2 >= pEnd )
+ return false;
+ if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) )
+ return false;
+
+ return true;
+}
+
+static void appendSubsetName( int nSubsetID, std::u16string_view rPSName, OStringBuffer& rBuffer )
+{
+ if( nSubsetID )
+ {
+ for( int i = 0; i < 6; i++ )
+ {
+ int nOffset = nSubsetID % 26;
+ nSubsetID /= 26;
+ rBuffer.append( static_cast<char>('A'+nOffset) );
+ }
+ rBuffer.append( '+' );
+ }
+ appendName( rPSName, rBuffer );
+}
+
+sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8 const * pEncoding,
+ const sal_Ucs* pCodeUnits,
+ const sal_Int32* pCodeUnitsPerGlyph,
+ const sal_Int32* pEncToUnicodeIndex,
+ int nGlyphs )
+{
+ int nMapped = 0;
+ for (int n = 0; n < nGlyphs; ++n)
+ if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
+ nMapped++;
+
+ if( nMapped == 0 )
+ return 0;
+
+ sal_Int32 nStream = createObject();
+ CHECK_RETURN( updateObject( nStream ) );
+
+ OStringBuffer aContents( 1024 );
+ aContents.append(
+ "/CIDInit/ProcSet findresource begin\n"
+ "12 dict begin\n"
+ "begincmap\n"
+ "/CIDSystemInfo<<\n"
+ "/Registry (Adobe)\n"
+ "/Ordering (UCS)\n"
+ "/Supplement 0\n"
+ ">> def\n"
+ "/CMapName/Adobe-Identity-UCS def\n"
+ "/CMapType 2 def\n"
+ "1 begincodespacerange\n"
+ "<00> <FF>\n"
+ "endcodespacerange\n"
+ );
+ int nCount = 0;
+ for (int n = 0; n < nGlyphs; ++n)
+ {
+ if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
+ {
+ if( (nCount % 100) == 0 )
+ {
+ if( nCount )
+ aContents.append( "endbfchar\n" );
+ aContents.append( static_cast<sal_Int32>(std::min(nMapped-nCount, 100)) );
+ aContents.append( " beginbfchar\n" );
+ }
+ aContents.append( '<' );
+ appendHex( static_cast<sal_Int8>(pEncoding[n]), aContents );
+ aContents.append( "> <" );
+ // TODO: handle code points>U+FFFF
+ sal_Int32 nIndex = pEncToUnicodeIndex[n];
+ for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ )
+ {
+ appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] / 256), aContents );
+ appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] & 255), aContents );
+ }
+ aContents.append( ">\n" );
+ nCount++;
+ }
+ }
+ aContents.append( "endbfchar\n"
+ "endcmap\n"
+ "CMapName currentdict /CMap defineresource pop\n"
+ "end\n"
+ "end\n" );
+ SvMemoryStream aStream;
+ if (!g_bDebugDisableCompression)
+ {
+ ZCodec aCodec( 0x4000, 0x4000 );
+ aCodec.BeginCompression();
+ aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() );
+ aCodec.EndCompression();
+ }
+
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::createToUnicodeCMap" );
+ }
+ OStringBuffer aLine( 40 );
+
+ aLine.append( nStream );
+ aLine.append( " 0 obj\n<</Length " );
+ sal_Int32 nLen = 0;
+ if (!g_bDebugDisableCompression)
+ {
+ nLen = static_cast<sal_Int32>(aStream.Tell());
+ aStream.Seek( 0 );
+ aLine.append( nLen );
+ aLine.append( "/Filter/FlateDecode" );
+ }
+ else
+ aLine.append( aContents.getLength() );
+ aLine.append( ">>\nstream\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ checkAndEnableStreamEncryption( nStream );
+ if (!g_bDebugDisableCompression)
+ {
+ CHECK_RETURN( writeBuffer( aStream.GetData(), nLen ) );
+ }
+ else
+ {
+ CHECK_RETURN( writeBuffer( aContents.getStr(), aContents.getLength() ) );
+ }
+ disableStreamEncryption();
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\n"
+ "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ return nStream;
+}
+
+sal_Int32 PDFWriterImpl::emitFontDescriptor( const vcl::font::PhysicalFontFace* pFont, FontSubsetInfo const & rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream )
+{
+ OStringBuffer aLine( 1024 );
+ // get font flags, see PDF reference 1.4 p. 358
+ // possibly characters outside Adobe standard encoding
+ // so set Symbolic flag
+ sal_Int32 nFontFlags = (1<<2);
+ if( pFont->GetItalic() == ITALIC_NORMAL || pFont->GetItalic() == ITALIC_OBLIQUE )
+ nFontFlags |= (1 << 6);
+ if( pFont->GetPitch() == PITCH_FIXED )
+ nFontFlags |= 1;
+ if( pFont->GetFamilyType() == FAMILY_SCRIPT )
+ nFontFlags |= (1 << 3);
+ else if( pFont->GetFamilyType() == FAMILY_ROMAN )
+ nFontFlags |= (1 << 1);
+
+ sal_Int32 nFontDescriptor = createObject();
+ CHECK_RETURN( updateObject( nFontDescriptor ) );
+ aLine.setLength( 0 );
+ aLine.append( nFontDescriptor );
+ aLine.append( " 0 obj\n"
+ "<</Type/FontDescriptor/FontName/" );
+ appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
+ aLine.append( "\n"
+ "/Flags " );
+ aLine.append( nFontFlags );
+ aLine.append( "\n"
+ "/FontBBox[" );
+ // note: Top and Bottom are reversed in VCL and PDF rectangles
+ aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.Left()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.Top()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.Right()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.Bottom()+1) );
+ aLine.append( "]/ItalicAngle " );
+ if( pFont->GetItalic() == ITALIC_OBLIQUE || pFont->GetItalic() == ITALIC_NORMAL )
+ aLine.append( "-30" );
+ else
+ aLine.append( "0" );
+ aLine.append( "\n"
+ "/Ascent " );
+ aLine.append( static_cast<sal_Int32>(rInfo.m_nAscent) );
+ aLine.append( "\n"
+ "/Descent " );
+ aLine.append( static_cast<sal_Int32>(-rInfo.m_nDescent) );
+ aLine.append( "\n"
+ "/CapHeight " );
+ aLine.append( static_cast<sal_Int32>(rInfo.m_nCapHeight) );
+ // According to PDF reference 1.4 StemV is required
+ // seems a tad strange to me, but well ...
+ aLine.append( "\n"
+ "/StemV 80\n" );
+ if( nFontStream )
+ {
+ aLine.append( "/FontFile" );
+ switch( rInfo.m_nFontType )
+ {
+ case FontType::SFNT_TTF:
+ aLine.append( '2' );
+ break;
+ case FontType::TYPE1_PFA:
+ case FontType::TYPE1_PFB:
+ case FontType::ANY_TYPE1:
+ break;
+ default:
+ OSL_FAIL( "unknown fonttype in PDF font descriptor" );
+ return 0;
+ }
+ aLine.append( ' ' );
+ aLine.append( nFontStream );
+ aLine.append( " 0 R\n" );
+ }
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ return nFontDescriptor;
+}
+
+void PDFWriterImpl::appendBuildinFontsToDict( OStringBuffer& rDict ) const
+{
+ for (auto const& item : m_aBuildinFontToObjectMap)
+ {
+ rDict.append( pdf::BuildinFontFace::Get(item.first).getNameObject() );
+ rDict.append( ' ' );
+ rDict.append( item.second );
+ rDict.append( " 0 R" );
+ }
+}
+
+bool PDFWriterImpl::emitFonts()
+{
+ SalGraphics *pGraphics = GetGraphics();
+
+ if (!pGraphics)
+ return false;
+
+ OStringBuffer aLine( 1024 );
+
+ std::map< sal_Int32, sal_Int32 > aFontIDToObject;
+
+ OUString aTmpName;
+ osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
+ for (const auto & subset : m_aSubsets)
+ {
+ for (auto & s_subset :subset.second.m_aSubsets)
+ {
+ sal_GlyphId aGlyphIds[ 256 ] = {};
+ sal_Int32 pWidths[ 256 ];
+ sal_uInt8 pEncoding[ 256 ] = {};
+ sal_Int32 pEncToUnicodeIndex[ 256 ] = {};
+ sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {};
+ std::vector<sal_Ucs> aCodeUnits;
+ aCodeUnits.reserve( 256 );
+ // fill arrays and prepare encoding index map
+ sal_Int32 nToUnicodeStream = 0;
+
+ pWidths[0] = 0; // if it gets used then it will appear in s_subset.m_aMapping, otherwise 0 is fine
+ int nGlyphs = 1;
+
+ for (auto const& item : s_subset.m_aMapping)
+ {
+ sal_uInt8 nEnc = item.second.getGlyphId();
+
+ SAL_WARN_IF( aGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter", "duplicate glyph" );
+ SAL_WARN_IF( nEnc > s_subset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding" );
+
+ aGlyphIds[ nEnc ] = item.first;
+ pEncoding[ nEnc ] = nEnc;
+ pEncToUnicodeIndex[ nEnc ] = static_cast<sal_Int32>(aCodeUnits.size());
+ pCodeUnitsPerGlyph[ nEnc ] = item.second.countCodes();
+ pWidths[ nEnc ] = item.second.getGlyphWidth();
+ for( sal_Int32 n = 0; n < pCodeUnitsPerGlyph[ nEnc ]; n++ )
+ aCodeUnits.push_back( item.second.getCode( n ) );
+ if( item.second.getCode(0) )
+ nToUnicodeStream = 1;
+ if( nGlyphs < 256 )
+ nGlyphs++;
+ else
+ {
+ OSL_FAIL( "too many glyphs for subset" );
+ }
+ }
+ FontSubsetInfo aSubsetInfo;
+ if( pGraphics->CreateFontSubset( aTmpName, subset.first, aGlyphIds, pEncoding, nullptr, nGlyphs, aSubsetInfo ) )
+ {
+ // create font stream
+ osl::File aFontFile(aTmpName);
+ if (osl::File::E_None != aFontFile.open(osl_File_OpenFlag_Read)) return false;
+ // get file size
+ sal_uInt64 nLength1;
+ if ( osl::File::E_None != aFontFile.setPos(osl_Pos_End, 0) ) return false;
+ if ( osl::File::E_None != aFontFile.getPos(nLength1) ) return false;
+ if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
+
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::emitFonts" );
+ }
+ sal_Int32 nFontStream = createObject();
+ sal_Int32 nStreamLengthObject = createObject();
+ if ( !updateObject( nFontStream ) ) return false;
+ aLine.setLength( 0 );
+ aLine.append( nFontStream );
+ aLine.append( " 0 obj\n"
+ "<</Length " );
+ aLine.append( nStreamLengthObject );
+ if (!g_bDebugDisableCompression)
+ aLine.append( " 0 R"
+ "/Filter/FlateDecode"
+ "/Length1 " );
+ else
+ aLine.append( " 0 R"
+ "/Length1 " );
+
+ sal_uInt64 nStartPos = 0;
+ if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF )
+ {
+ aLine.append( static_cast<sal_Int32>(nLength1) );
+
+ aLine.append( ">>\n"
+ "stream\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
+ if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
+
+ // copy font file
+ beginCompression();
+ checkAndEnableStreamEncryption( nFontStream );
+ sal_Bool bEOF = false;
+ do
+ {
+ char buf[8192];
+ sal_uInt64 nRead;
+ if ( osl::File::E_None != aFontFile.read(buf, sizeof(buf), nRead) ) return false;
+ if ( !writeBuffer( buf, nRead ) ) return false;
+ if ( osl::File::E_None != aFontFile.isEndOfFile(&bEOF) ) return false;
+ } while( ! bEOF );
+ }
+ else if( aSubsetInfo.m_nFontType & FontType::CFF_FONT)
+ {
+ // TODO: implement
+ OSL_FAIL( "PDFWriterImpl does not support CFF-font subsets yet!" );
+ }
+ else if( aSubsetInfo.m_nFontType & FontType::TYPE1_PFB) // TODO: also support PFA?
+ {
+ std::unique_ptr<unsigned char[]> xBuffer(new unsigned char[nLength1]);
+
+ sal_uInt64 nBytesRead = 0;
+ if ( osl::File::E_None != aFontFile.read(xBuffer.get(), nLength1, nBytesRead) ) return false;
+ SAL_WARN_IF( nBytesRead!=nLength1, "vcl.pdfwriter", "PDF-FontSubset read incomplete!" );
+ if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
+ // get the PFB-segment lengths
+ ThreeInts aSegmentLengths = {0,0,0};
+ getPfbSegmentLengths(xBuffer.get(), static_cast<int>(nBytesRead), aSegmentLengths);
+ // the lengths below are mandatory for PDF-exported Type1 fonts
+ // because the PFB segment headers get stripped! WhyOhWhy.
+ aLine.append( static_cast<sal_Int32>(aSegmentLengths[0]) );
+ aLine.append( "/Length2 " );
+ aLine.append( static_cast<sal_Int32>(aSegmentLengths[1]) );
+ aLine.append( "/Length3 " );
+ aLine.append( static_cast<sal_Int32>(aSegmentLengths[2]) );
+
+ aLine.append( ">>\n"
+ "stream\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
+ if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
+
+ // emit PFB-sections without section headers
+ beginCompression();
+ checkAndEnableStreamEncryption( nFontStream );
+ if ( !writeBuffer( &xBuffer[6], aSegmentLengths[0] ) ) return false;
+ if ( !writeBuffer( &xBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false;
+ if ( !writeBuffer( &xBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ) return false;
+ }
+ else
+ {
+ SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << static_cast<int>(aSubsetInfo.m_nFontType));
+ aLine.append( "0 >>\nstream\n" );
+ }
+
+ endCompression();
+ disableStreamEncryption();
+ // close the file
+ aFontFile.close();
+
+ sal_uInt64 nEndPos = 0;
+ if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false;
+ // end the stream
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\nendobj\n\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
+
+ // emit stream length object
+ if ( !updateObject( nStreamLengthObject ) ) return false;
+ aLine.setLength( 0 );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
+ aLine.append( "\nendobj\n\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
+
+ // write font descriptor
+ sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream );
+
+ if( nToUnicodeStream )
+ nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits.data(), pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs );
+
+ sal_Int32 nFontObject = createObject();
+ if ( !updateObject( nFontObject ) ) return false;
+ aLine.setLength( 0 );
+ aLine.append( nFontObject );
+
+ aLine.append( " 0 obj\n" );
+ aLine.append( (aSubsetInfo.m_nFontType & FontType::ANY_TYPE1) ?
+ "<</Type/Font/Subtype/Type1/BaseFont/" :
+ "<</Type/Font/Subtype/TrueType/BaseFont/" );
+ appendSubsetName( s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine );
+ aLine.append( "\n"
+ "/FirstChar 0\n"
+ "/LastChar " );
+ aLine.append( static_cast<sal_Int32>(nGlyphs-1) );
+ aLine.append( "\n"
+ "/Widths[" );
+ for( int i = 0; i < nGlyphs; i++ )
+ {
+ aLine.append( pWidths[ i ] );
+ aLine.append( ((i & 15) == 15) ? "\n" : " " );
+ }
+ aLine.append( "]\n"
+ "/FontDescriptor " );
+ aLine.append( nFontDescriptor );
+ aLine.append( " 0 R\n" );
+ if( nToUnicodeStream )
+ {
+ aLine.append( "/ToUnicode " );
+ aLine.append( nToUnicodeStream );
+ aLine.append( " 0 R\n" );
+ }
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
+
+ aFontIDToObject[ s_subset.m_nFontID ] = nFontObject;
+ }
+ else
+ {
+ const vcl::font::PhysicalFontFace* pFont = subset.first;
+ OStringBuffer aErrorComment( 256 );
+ aErrorComment.append( "CreateFontSubset failed for font \"" );
+ aErrorComment.append( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) );
+ aErrorComment.append( '\"' );
+ if( pFont->GetItalic() == ITALIC_NORMAL )
+ aErrorComment.append( " italic" );
+ else if( pFont->GetItalic() == ITALIC_OBLIQUE )
+ aErrorComment.append( " oblique" );
+ aErrorComment.append( " weight=" );
+ aErrorComment.append( sal_Int32(pFont->GetWeight()) );
+ emitComment( aErrorComment.getStr() );
+ }
+ }
+ }
+ osl_removeFile( aTmpName.pData );
+
+ // emit system fonts
+ for (auto const& systemFont : m_aSystemFonts)
+ {
+ std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( systemFont.first, systemFont.second );
+ for (auto const& item : aObjects)
+ {
+ if ( !item.second ) return false;
+ aFontIDToObject[ item.first ] = item.second;
+ }
+ }
+
+ OStringBuffer aFontDict( 1024 );
+ aFontDict.append( getFontDictObject() );
+ aFontDict.append( " 0 obj\n"
+ "<<" );
+ int ni = 0;
+ for (auto const& itemMap : aFontIDToObject)
+ {
+ aFontDict.append( "/F" );
+ aFontDict.append( itemMap.first );
+ aFontDict.append( ' ' );
+ aFontDict.append( itemMap.second );
+ aFontDict.append( " 0 R" );
+ if( ((++ni) & 7) == 0 )
+ aFontDict.append( '\n' );
+ }
+ // emit builtin font for widget appearances / variable text
+ for (auto & item : m_aBuildinFontToObjectMap)
+ {
+ rtl::Reference<pdf::BuildinFontFace> aData(new pdf::BuildinFontFace(item.first));
+ item.second = emitBuildinFont( aData.get(), item.second );
+ }
+
+ appendBuildinFontsToDict(aFontDict);
+ aFontDict.append( "\n>>\nendobj\n\n" );
+
+ if ( !updateObject( getFontDictObject() ) ) return false;
+ if ( !writeBuffer( aFontDict.getStr(), aFontDict.getLength() ) ) return false;
+ return true;
+}
+
+sal_Int32 PDFWriterImpl::emitResources()
+{
+ // emit shadings
+ if( ! m_aGradients.empty() )
+ CHECK_RETURN( emitGradients() );
+ // emit tilings
+ if( ! m_aTilings.empty() )
+ CHECK_RETURN( emitTilings() );
+
+ // emit font dict
+ CHECK_RETURN( emitFonts() );
+
+ // emit Resource dict
+ OStringBuffer aLine( 512 );
+ sal_Int32 nResourceDict = getResourceDictObj();
+ CHECK_RETURN( updateObject( nResourceDict ) );
+ aLine.setLength( 0 );
+ aLine.append( nResourceDict );
+ aLine.append( " 0 obj\n" );
+ m_aGlobalResourceDict.append( aLine, getFontDictObject() );
+ aLine.append( "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ return nResourceDict;
+}
+
+sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
+ sal_Int32 nItemLevel,
+ sal_Int32 nCurrentItemId )
+{
+ /* The /Count number of an item is
+ positive: the number of visible subitems
+ negative: the negative number of subitems that will become visible if
+ the item gets opened
+ see PDF ref 1.4 p 478
+ */
+
+ sal_Int32 nCount = 0;
+
+ if( m_aContext.OpenBookmarkLevels < 0 || // all levels are visible
+ m_aContext.OpenBookmarkLevels >= nItemLevel // this level is visible
+ )
+ {
+ PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
+ sal_Int32 nChildren = rItem.m_aChildren.size();
+ for( sal_Int32 i = 0; i < nChildren; i++ )
+ nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
+ rCounts[nCurrentItemId] = nCount;
+ // return 1 (this item) + visible sub items
+ if( nCount < 0 )
+ nCount = 0;
+ nCount++;
+ }
+ else
+ {
+ // this bookmark level is invisible
+ PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
+ sal_Int32 nChildren = rItem.m_aChildren.size();
+ rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size());
+ for( sal_Int32 i = 0; i < nChildren; i++ )
+ updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
+ nCount = -1;
+ }
+
+ return nCount;
+}
+
+sal_Int32 PDFWriterImpl::emitOutline()
+{
+ int i, nItems = m_aOutline.size();
+
+ // do we have an outline at all ?
+ if( nItems < 2 )
+ return 0;
+
+ // reserve object numbers for all outline items
+ for( i = 0; i < nItems; ++i )
+ m_aOutline[i].m_nObject = createObject();
+
+ // update all parent, next and prev object ids
+ for( i = 0; i < nItems; ++i )
+ {
+ PDFOutlineEntry& rItem = m_aOutline[i];
+ int nChildren = rItem.m_aChildren.size();
+
+ if( nChildren )
+ {
+ for( int n = 0; n < nChildren; ++n )
+ {
+ PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ];
+
+ rChild.m_nParentObject = rItem.m_nObject;
+ rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0;
+ rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0;
+ }
+
+ }
+ }
+
+ // calculate Count entries for all items
+ std::vector< sal_Int32 > aCounts( nItems );
+ updateOutlineItemCount( aCounts, 0, 0 );
+
+ // emit hierarchy
+ for( i = 0; i < nItems; ++i )
+ {
+ PDFOutlineEntry& rItem = m_aOutline[i];
+ OStringBuffer aLine( 1024 );
+
+ CHECK_RETURN( updateObject( rItem.m_nObject ) );
+ aLine.append( rItem.m_nObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( "<<" );
+ // number of visible children (all levels)
+ if( i > 0 || aCounts[0] > 0 )
+ {
+ aLine.append( "/Count " );
+ aLine.append( aCounts[i] );
+ }
+ if( ! rItem.m_aChildren.empty() )
+ {
+ // children list: First, Last
+ aLine.append( "/First " );
+ aLine.append( m_aOutline[rItem.m_aChildren.front()].m_nObject );
+ aLine.append( " 0 R/Last " );
+ aLine.append( m_aOutline[rItem.m_aChildren.back()].m_nObject );
+ aLine.append( " 0 R\n" );
+ }
+ if( i > 0 )
+ {
+ // Title, Dest, Parent, Prev, Next
+ aLine.append( "/Title" );
+ appendUnicodeTextStringEncrypt( rItem.m_aTitle, rItem.m_nObject, aLine );
+ aLine.append( "\n" );
+ // Dest is not required
+ if( rItem.m_nDestID >= 0 && o3tl::make_unsigned(rItem.m_nDestID) < m_aDests.size() )
+ {
+ aLine.append( "/Dest" );
+ appendDest( rItem.m_nDestID, aLine );
+ }
+ aLine.append( "/Parent " );
+ aLine.append( rItem.m_nParentObject );
+ aLine.append( " 0 R" );
+ if( rItem.m_nPrevObject )
+ {
+ aLine.append( "/Prev " );
+ aLine.append( rItem.m_nPrevObject );
+ aLine.append( " 0 R" );
+ }
+ if( rItem.m_nNextObject )
+ {
+ aLine.append( "/Next " );
+ aLine.append( rItem.m_nNextObject );
+ aLine.append( " 0 R" );
+ }
+ }
+ aLine.append( ">>\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ }
+
+ return m_aOutline[0].m_nObject;
+}
+
+#undef CHECK_RETURN
+#define CHECK_RETURN( x ) if( !x ) return false
+
+bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer )
+{
+ if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() )
+ {
+ SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << static_cast<int>(nDestID) << " requested");
+ return false;
+ }
+
+ const PDFDest& rDest = m_aDests[ nDestID ];
+ const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
+
+ rBuffer.append( '[' );
+ rBuffer.append( rDestPage.m_nPageObject );
+ rBuffer.append( " 0 R" );
+
+ switch( rDest.m_eType )
+ {
+ case PDFWriter::DestAreaType::XYZ:
+ default:
+ rBuffer.append( "/XYZ " );
+ appendFixedInt( rDest.m_aRect.Left(), rBuffer );
+ rBuffer.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
+ rBuffer.append( " 0" );
+ break;
+ case PDFWriter::DestAreaType::FitRectangle:
+ rBuffer.append( "/FitR " );
+ appendFixedInt( rDest.m_aRect.Left(), rBuffer );
+ rBuffer.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Top(), rBuffer );
+ rBuffer.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Right(), rBuffer );
+ rBuffer.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
+ break;
+ }
+ rBuffer.append( ']' );
+
+ return true;
+}
+
+bool PDFWriterImpl::emitScreenAnnotations()
+{
+ int nAnnots = m_aScreens.size();
+ for (int i = 0; i < nAnnots; i++)
+ {
+ const PDFScreen& rScreen = m_aScreens[i];
+
+ OStringBuffer aLine;
+ bool bEmbed = false;
+ if (!rScreen.m_aTempFileURL.isEmpty())
+ {
+ bEmbed = true;
+ if (!updateObject(rScreen.m_nTempFileObject))
+ continue;
+
+ SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ);
+ SvMemoryStream aMemoryStream;
+ aMemoryStream.WriteStream(aFileStream);
+
+ aLine.append(rScreen.m_nTempFileObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /EmbeddedFile /Length ");
+ aLine.append(static_cast<sal_Int64>(aMemoryStream.GetSize()));
+ aLine.append(" >>\nstream\n");
+ CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
+ aLine.setLength(0);
+
+ CHECK_RETURN(writeBuffer(aMemoryStream.GetData(), aMemoryStream.GetSize()));
+
+ aLine.append("\nendstream\nendobj\n\n");
+ CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
+ aLine.setLength(0);
+ }
+
+ if (!updateObject(rScreen.m_nObject))
+ continue;
+
+ // Annot dictionary.
+ aLine.append(rScreen.m_nObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<</Type/Annot");
+ aLine.append("/Subtype/Screen/Rect[");
+ appendFixedInt(rScreen.m_aRect.Left(), aLine);
+ aLine.append(' ');
+ appendFixedInt(rScreen.m_aRect.Top(), aLine);
+ aLine.append(' ');
+ appendFixedInt(rScreen.m_aRect.Right(), aLine);
+ aLine.append(' ');
+ appendFixedInt(rScreen.m_aRect.Bottom(), aLine);
+ aLine.append("]");
+
+ // Action dictionary.
+ aLine.append("/A<</Type/Action /S/Rendition /AN ");
+ aLine.append(rScreen.m_nObject);
+ aLine.append(" 0 R ");
+
+ // Rendition dictionary.
+ aLine.append("/R<</Type/Rendition /S/MR ");
+
+ // MediaClip dictionary.
+ aLine.append("/C<</Type/MediaClip /S/MCD ");
+ if (bEmbed)
+ {
+ aLine.append("/D << /Type /Filespec /F (<embedded file>) /EF << /F ");
+ aLine.append(rScreen.m_nTempFileObject);
+ aLine.append(" 0 R >> >>");
+ }
+ else
+ {
+ // Linked.
+ aLine.append("/D << /Type /Filespec /FS /URL /F ");
+ appendLiteralStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine, osl_getThreadTextEncoding());
+ aLine.append(" >>");
+ }
+ // Allow playing the video via a tempfile.
+ aLine.append("/P <</TF (TEMPACCESS)>>");
+ // Until the real MIME type (instead of application/vnd.sun.star.media) is available here.
+ aLine.append("/CT (video/mp4)");
+ aLine.append(">>");
+
+ // End Rendition dictionary by requesting play/pause/stop controls.
+ aLine.append("/P<</BE<</C true >>>>");
+ aLine.append(">>");
+
+ // End Action dictionary.
+ aLine.append("/OP 0 >>");
+
+ // End Annot dictionary.
+ aLine.append("/P ");
+ aLine.append(m_aPages[rScreen.m_nPage].m_nPageObject);
+ aLine.append(" 0 R\n>>\nendobj\n\n");
+ CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
+ }
+
+ return true;
+}
+
+bool PDFWriterImpl::emitLinkAnnotations()
+{
+ MARK("PDFWriterImpl::emitLinkAnnotations");
+ int nAnnots = m_aLinks.size();
+ for( int i = 0; i < nAnnots; i++ )
+ {
+ const PDFLink& rLink = m_aLinks[i];
+ if( ! updateObject( rLink.m_nObject ) )
+ continue;
+
+ OStringBuffer aLine( 1024 );
+ aLine.append( rLink.m_nObject );
+ aLine.append( " 0 obj\n" );
+// i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
+// see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
+ aLine.append( "<</Type/Annot" );
+ if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3)
+ aLine.append( "/F 4" );
+ aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" );
+
+ appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle
+ aLine.append( ' ' );
+ appendFixedInt( rLink.m_aRect.Top(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle
+ aLine.append( ' ' );
+ appendFixedInt( rLink.m_aRect.Bottom(), aLine );
+ aLine.append( "]" );
+ // ISO 14289-1:2014, Clause: 7.18.5
+ aLine.append("/Contents");
+ appendUnicodeTextStringEncrypt(rLink.m_AltText, rLink.m_nObject, aLine);
+ if( rLink.m_nDest >= 0 )
+ {
+ aLine.append( "/Dest" );
+ appendDest( rLink.m_nDest, aLine );
+ }
+ else
+ {
+/*
+destination is external to the document, so
+we check in the following sequence:
+
+ if target type is neither .pdf, nor .od[tpgs], then
+ check if relative or absolute and act accordingly (use URI or 'launch application' as requested)
+ end processing
+ else if target is .od[tpgs]: then
+ if conversion of type from od[tpgs] to pdf is requested, convert it and this becomes the new target file
+ processing continue
+
+ if (new)target is .pdf : then
+ if GotToR is requested, then
+ convert the target in GoToR where the fragment of the URI is
+ considered the named destination in the target file, set relative or absolute as requested
+ else strip the fragment from URL and then set URI or 'launch application' as requested
+*/
+
+// FIXME: check if the decode mechanisms for URL processing throughout this implementation
+// are the correct one!!
+
+// extract target file type
+ auto url(URIHelper::resolveIdnaHost(rLink.m_aURL));
+
+ INetURLObject aDocumentURL( m_aContext.BaseURL );
+ INetURLObject aTargetURL( url );
+ bool bSetGoToRMode = false;
+ bool bTargetHasPDFExtension = false;
+ INetProtocol eTargetProtocol = aTargetURL.GetProtocol();
+ bool bIsUNCPath = false;
+ bool bUnparsedURI = false;
+
+ // check if the protocol is a known one, or if there is no protocol at all (on target only)
+ // if there is no protocol, make the target relative to the current document directory
+ // getting the needed URL information from the current document path
+ if( eTargetProtocol == INetProtocol::NotValid )
+ {
+ if( url.getLength() > 4 && url.startsWith("\\\\\\\\"))
+ {
+ bIsUNCPath = true;
+ }
+ else
+ {
+ INetURLObject aNewURL(rtl::Uri::convertRelToAbs(m_aContext.BaseURL, url));
+ aTargetURL = aNewURL; //reassign the new target URL
+
+ //recompute the target protocol, with the new URL
+ //normal URL processing resumes
+ eTargetProtocol = aTargetURL.GetProtocol();
+
+ bUnparsedURI = eTargetProtocol == INetProtocol::NotValid;
+ }
+ }
+
+ OUString aFileExtension = aTargetURL.GetFileExtension();
+
+ // Check if the URL ends in '/': if yes it's a directory,
+ // it will be forced to a URI link.
+ // possibly a malformed URI, leave it as it is, force as URI
+ if( aTargetURL.hasFinalSlash() )
+ m_aContext.DefaultLinkAction = PDFWriter::URIAction;
+
+ if( !aFileExtension.isEmpty() )
+ {
+ if( m_aContext.ConvertOOoTargetToPDFTarget )
+ {
+ bool bChangeFileExtensionToPDF = false;
+ //examine the file type (.odm .odt. .odp, odg, ods)
+ if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) )
+ bChangeFileExtensionToPDF = true;
+ if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) )
+ bChangeFileExtensionToPDF = true;
+ else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) )
+ bChangeFileExtensionToPDF = true;
+ else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) )
+ bChangeFileExtensionToPDF = true;
+ else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) )
+ bChangeFileExtensionToPDF = true;
+ if( bChangeFileExtensionToPDF )
+ aTargetURL.setExtension(u"pdf" );
+ }
+ //check if extension is pdf, see if GoToR should be forced
+ bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" );
+ if( m_aContext.ForcePDFAction && bTargetHasPDFExtension )
+ bSetGoToRMode = true;
+ }
+ //prepare the URL, if relative or not
+ INetProtocol eBaseProtocol = aDocumentURL.GetProtocol();
+ //queue the string common to all types of actions
+ aLine.append( "/A<</Type/Action/S");
+ if( bIsUNCPath ) // handle Win UNC paths
+ {
+ aLine.append( "/Launch/Win<</F" );
+ // INetURLObject is not good with UNC paths, use original path
+ appendLiteralStringEncrypt( url, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
+ aLine.append( ">>" );
+ }
+ else
+ {
+ bool bSetRelative = false;
+ bool bFileSpec = false;
+ //check if relative file link is requested and if the protocol is 'file://'
+ if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File )
+ bSetRelative = true;
+
+ OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is,
+ if( !bSetGoToRMode )
+ {
+ switch( m_aContext.DefaultLinkAction )
+ {
+ default:
+ case PDFWriter::URIAction :
+ case PDFWriter::URIActionDestination :
+ aLine.append( "/URI/URI" );
+ break;
+ case PDFWriter::LaunchAction:
+ // now:
+ // if a launch action is requested and the hyperlink target has a fragment
+ // and the target file does not have a pdf extension, or it's not a 'file:://'
+ // protocol then force the uri action on it
+ // This code will permit the correct opening of application on web pages,
+ // the one that normally have fragments (but I may be wrong...)
+ // and will force the use of URI when the protocol is not file:
+ if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) ||
+ eTargetProtocol != INetProtocol::File )
+ {
+ aLine.append( "/URI/URI" );
+ }
+ else
+ {
+ aLine.append( "/Launch/F" );
+ bFileSpec = true;
+ }
+ break;
+ }
+ }
+
+ //fragment are encoded in the same way as in the named destination processing
+ if( bSetGoToRMode )
+ {
+ //add the fragment
+ OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset );
+ aLine.append("/GoToR");
+ aLine.append("/F");
+ appendLiteralStringEncrypt( bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark,
+ INetURLObject::EncodeMechanism::WasEncoded,
+ INetURLObject::DecodeMechanism::WithCharset ) :
+ aURLNoMark, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
+ if( !aFragment.isEmpty() )
+ {
+ aLine.append("/D/");
+ appendDestinationName( aFragment , aLine );
+ }
+ }
+ else
+ {
+ // change the fragment to accommodate the bookmark (only if the file extension
+ // is PDF and the requested action is of the correct type)
+ if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination &&
+ bTargetHasPDFExtension && !aFragment.isEmpty() )
+ {
+ OStringBuffer aLineLoc( 1024 );
+ appendDestinationName( aFragment , aLineLoc );
+ //substitute the fragment
+ aTargetURL.SetMark( OStringToOUString(aLineLoc, RTL_TEXTENCODING_ASCII_US) );
+ }
+ OUString aURL = bUnparsedURI ? url :
+ aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset :
+ INetURLObject::DecodeMechanism::NONE );
+ appendLiteralStringEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL,
+ INetURLObject::EncodeMechanism::WasEncoded,
+ bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE
+ ) :
+ aURL , rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
+ }
+ }
+ aLine.append( ">>\n" );
+ }
+ if( rLink.m_nStructParent > 0 )
+ {
+ aLine.append( "/StructParent " );
+ aLine.append( rLink.m_nStructParent );
+ }
+ aLine.append( ">>\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ }
+
+ return true;
+}
+
+namespace
+{
+
+void appendAnnotationRect(tools::Rectangle const & rRectangle, OStringBuffer & aLine)
+{
+ aLine.append("/Rect[");
+ appendFixedInt(rRectangle.Left(), aLine);
+ aLine.append(' ');
+ appendFixedInt(rRectangle.Top(), aLine);
+ aLine.append(' ');
+ appendFixedInt(rRectangle.Right(), aLine);
+ aLine.append(' ');
+ appendFixedInt(rRectangle.Bottom(), aLine);
+ aLine.append("] ");
+}
+
+void appendObjectID(sal_Int32 nObjectID, OStringBuffer & aLine)
+{
+ aLine.append(nObjectID);
+ aLine.append(" 0 obj\n");
+}
+
+void appendObjectReference(sal_Int32 nObjectID, OStringBuffer & aLine)
+{
+ aLine.append(nObjectID);
+ aLine.append(" 0 R ");
+}
+
+} // end anonymous namespace
+
+void PDFWriterImpl::emitTextAnnotationLine(OStringBuffer & aLine, PDFNoteEntry const & rNote)
+{
+ appendObjectID(rNote.m_nObject, aLine);
+
+ aLine.append("<</Type /Annot /Subtype /Text ");
+
+// i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
+// see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
+ if (m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3)
+ aLine.append("/F 4 ");
+
+ appendAnnotationRect(rNote.m_aRect, aLine);
+
+ aLine.append("/Popup ");
+ appendObjectReference(rNote.m_aPopUpAnnotation.m_nObject, aLine);
+
+ auto & rDateTime = rNote.m_aContents.maModificationDate;
+
+ aLine.append("/M (");
+ appendPdfTimeDate(aLine, rDateTime.Year, rDateTime.Month, rDateTime.Day, rDateTime.Hours, rDateTime.Minutes, rDateTime.Seconds, 0);
+ aLine.append(") ");
+
+ // contents of the note (type text string)
+ aLine.append("/Contents ");
+ appendUnicodeTextStringEncrypt(rNote.m_aContents.Contents, rNote.m_nObject, aLine);
+ aLine.append("\n");
+
+ // optional title
+ if (!rNote.m_aContents.Title.isEmpty())
+ {
+ aLine.append("/T ");
+ appendUnicodeTextStringEncrypt(rNote.m_aContents.Title, rNote.m_nObject, aLine);
+ aLine.append("\n");
+ }
+ aLine.append(">>\n");
+ aLine.append("endobj\n\n");
+}
+
+void PDFWriterImpl::emitPopupAnnotationLine(OStringBuffer & aLine, PDFPopupAnnotation const & rPopUp)
+{
+ appendObjectID(rPopUp.m_nObject, aLine);
+ aLine.append("<</Type /Annot /Subtype /Popup ");
+ aLine.append("/Parent ");
+ appendObjectReference(rPopUp.m_nParentObject, aLine);
+ aLine.append(">>\n");
+ aLine.append("endobj\n\n");
+}
+
+bool PDFWriterImpl::emitNoteAnnotations()
+{
+ // emit note annotations
+ int nAnnots = m_aNotes.size();
+ for( int i = 0; i < nAnnots; i++ )
+ {
+ const PDFNoteEntry& rNote = m_aNotes[i];
+ const PDFPopupAnnotation& rPopUp = rNote.m_aPopUpAnnotation;
+
+ {
+ if (!updateObject(rNote.m_nObject))
+ return false;
+
+ OStringBuffer aLine(1024);
+
+ emitTextAnnotationLine(aLine, rNote);
+
+ if (!writeBuffer(aLine.getStr(), aLine.getLength()))
+ return false;
+ }
+
+ {
+
+ if (!updateObject(rPopUp.m_nObject))
+ return false;
+
+ OStringBuffer aLine(1024);
+
+ emitPopupAnnotationLine(aLine, rPopUp);
+
+ if (!writeBuffer(aLine.getStr(), aLine.getLength()))
+ return false;
+ }
+ }
+ return true;
+}
+
+Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font& rAppSetFont )
+{
+ bool bAdjustSize = false;
+
+ Font aFont( rControlFont );
+ if( aFont.GetFamilyName().isEmpty() )
+ {
+ aFont = rAppSetFont;
+ if( rControlFont.GetFontHeight() )
+ aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) );
+ else
+ bAdjustSize = true;
+ if( rControlFont.GetItalic() != ITALIC_DONTKNOW )
+ aFont.SetItalic( rControlFont.GetItalic() );
+ if( rControlFont.GetWeight() != WEIGHT_DONTKNOW )
+ aFont.SetWeight( rControlFont.GetWeight() );
+ }
+ else if( ! aFont.GetFontHeight() )
+ {
+ aFont.SetFontSize( rAppSetFont.GetFontSize() );
+ bAdjustSize = true;
+ }
+ if( bAdjustSize )
+ {
+ Size aFontSize = aFont.GetFontSize();
+ OutputDevice* pDefDev = Application::GetDefaultDevice();
+ aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() );
+ aFont.SetFontSize( aFontSize );
+ }
+ return aFont;
+}
+
+sal_Int32 PDFWriterImpl::getBestBuildinFont( const vcl::Font& rFont )
+{
+ sal_Int32 nBest = 4; // default to Helvetica
+
+ if (rFont.GetFamilyType() == FAMILY_ROMAN)
+ {
+ // Serif: default to Times-Roman.
+ nBest = 8;
+ }
+
+ OUString aFontName( rFont.GetFamilyName() );
+ aFontName = aFontName.toAsciiLowerCase();
+
+ if( aFontName.indexOf( "times" ) != -1 )
+ nBest = 8;
+ else if( aFontName.indexOf( "courier" ) != -1 )
+ nBest = 0;
+ else if( aFontName.indexOf( "dingbats" ) != -1 )
+ nBest = 13;
+ else if( aFontName.indexOf( "symbol" ) != -1 )
+ nBest = 12;
+ if( nBest < 12 )
+ {
+ if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL )
+ nBest += 1;
+ if( rFont.GetWeight() > WEIGHT_MEDIUM )
+ nBest += 2;
+ }
+
+ if( m_aBuildinFontToObjectMap.find( nBest ) == m_aBuildinFontToObjectMap.end() )
+ m_aBuildinFontToObjectMap[ nBest ] = createObject();
+
+ return nBest;
+}
+
+static const Color& replaceColor( const Color& rCol1, const Color& rCol2 )
+{
+ return (rCol1 == COL_TRANSPARENT) ? rCol2 : rCol1;
+}
+
+void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget )
+{
+ const StyleSettings& rSettings = m_aWidgetStyleSettings;
+
+ // save graphics state
+ push( PushFlags::ALL );
+
+ // transform relative to control's coordinates since an
+ // appearance stream is a form XObject
+ // this relies on the m_aRect member of rButton NOT already being transformed
+ // to default user space
+ if( rWidget.Background || rWidget.Border )
+ {
+ setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT );
+ setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : COL_TRANSPARENT );
+ drawRectangle( rWidget.Location );
+ }
+ // prepare font to use
+ Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() );
+ setFont( aFont );
+ setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) );
+
+ drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle );
+
+ // create DA string while local mapmode is still in place
+ // (that is before endRedirect())
+ OStringBuffer aDA( 256 );
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA );
+ Font aDummyFont( "Helvetica", aFont.GetFontSize() );
+ sal_Int32 nDummyBuildin = getBestBuildinFont( aDummyFont );
+ aDA.append( ' ' );
+ aDA.append(pdf::BuildinFontFace::Get(nDummyBuildin).getNameObject());
+ aDA.append( ' ' );
+ m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
+ aDA.append( " Tf" );
+ rButton.m_aDAString = aDA.makeStringAndClear();
+
+ pop();
+
+ rButton.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
+
+ /* seems like a bad hack but at least works in both AR5 and 6:
+ we draw the button ourselves and tell AR
+ the button would be totally transparent with no text
+
+ One would expect that simply setting a normal appearance
+ should suffice, but no, as soon as the user actually presses
+ the button and an action is tied to it (gasp! a button that
+ does something) the appearance gets replaced by some crap that AR
+ creates on the fly even if no DA or MK is given. On AR6 at least
+ the DA and MK work as expected, but on AR5 this creates a region
+ filled with the background color but nor text. Urgh.
+ */
+ rButton.m_aMKDict = "/BC [] /BG [] /CA";
+ rButton.m_aMKDictCAString = "";
+}
+
+Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern,
+ const PDFWriter::AnyWidget& rWidget,
+ const StyleSettings& rSettings )
+{
+ Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() );
+
+ if( rWidget.Background || rWidget.Border )
+ {
+ if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT )
+ {
+ sal_Int32 nDelta = GetDPIX() / 500;
+ if( nDelta < 1 )
+ nDelta = 1;
+ setLineColor( COL_TRANSPARENT );
+ tools::Rectangle aRect = rIntern.m_aRect;
+ setFillColor( rSettings.GetLightBorderColor() );
+ drawRectangle( aRect );
+ aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta );
+ aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta );
+ setFillColor( rSettings.GetFieldColor() );
+ drawRectangle( aRect );
+ setFillColor( rSettings.GetLightColor() );
+ drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) );
+ drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) );
+ setFillColor( rSettings.GetDarkShadowColor() );
+ drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) );
+ drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) );
+ }
+ else
+ {
+ setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT );
+ setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
+ drawRectangle( rIntern.m_aRect );
+ }
+
+ if( rWidget.Border )
+ {
+ // adjust edit area accounting for border
+ sal_Int32 nDelta = aFont.GetFontHeight()/4;
+ if( nDelta < 1 )
+ nDelta = 1;
+ rIntern.m_aRect.AdjustLeft(nDelta );
+ rIntern.m_aRect.AdjustTop(nDelta );
+ rIntern.m_aRect.AdjustRight( -nDelta );
+ rIntern.m_aRect.AdjustBottom( -nDelta );
+ }
+ }
+ return aFont;
+}
+
+void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget )
+{
+ const StyleSettings& rSettings = m_aWidgetStyleSettings;
+ SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 );
+
+ push( PushFlags::ALL );
+
+ // prepare font to use, draw field border
+ Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
+ // Get the built-in font which is closest to aFont.
+ sal_Int32 nBest = getBestBuildinFont(aFont);
+
+ // prepare DA string
+ OStringBuffer aDA( 32 );
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
+ aDA.append( ' ' );
+ aDA.append(pdf::BuildinFontFace::Get(nBest).getNameObject());
+
+ OStringBuffer aDR( 32 );
+ aDR.append( "/Font " );
+ aDR.append( getFontDictObject() );
+ aDR.append( " 0 R" );
+ rEdit.m_aDRDict = aDR.makeStringAndClear();
+ aDA.append( ' ' );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
+ aDA.append( " Tf" );
+
+ /* create an empty appearance stream, let the viewer create
+ the appearance at runtime. This is because AR5 seems to
+ paint the widget appearance always, and a dynamically created
+ appearance on top of it. AR6 is well behaved in that regard, so
+ that behaviour seems to be a bug. Anyway this empty appearance
+ relies on /NeedAppearances in the AcroForm dictionary set to "true"
+ */
+ beginRedirect( pEditStream, rEdit.m_aRect );
+ OString aAppearance = "/Tx BMC\nEMC\n";
+ writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
+
+ endRedirect();
+ pop();
+
+ rEdit.m_aAppearances[ "N" ][ "Standard" ] = pEditStream;
+
+ rEdit.m_aDAString = aDA.makeStringAndClear();
+}
+
+void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget )
+{
+ const StyleSettings& rSettings = m_aWidgetStyleSettings;
+ SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 );
+
+ push( PushFlags::ALL );
+
+ // prepare font to use, draw field border
+ Font aFont = drawFieldBorder( rBox, rWidget, rSettings );
+ sal_Int32 nBest = getSystemFont( aFont );
+
+ beginRedirect( pListBoxStream, rBox.m_aRect );
+
+ setLineColor( COL_TRANSPARENT );
+ setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) );
+ drawRectangle( rBox.m_aRect );
+
+ // empty appearance, see createDefaultEditAppearance for reference
+ OString aAppearance = "/Tx BMC\nEMC\n";
+ writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
+
+ endRedirect();
+ pop();
+
+ rBox.m_aAppearances[ "N" ][ "Standard" ] = pListBoxStream;
+
+ // prepare DA string
+ OStringBuffer aDA( 256 );
+ // prepare DA string
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
+ aDA.append( ' ' );
+ aDA.append( "/F" );
+ aDA.append( nBest );
+
+ OStringBuffer aDR( 32 );
+ aDR.append( "/Font " );
+ aDR.append( getFontDictObject() );
+ aDR.append( " 0 R" );
+ rBox.m_aDRDict = aDR.makeStringAndClear();
+ aDA.append( ' ' );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
+ aDA.append( " Tf" );
+ rBox.m_aDAString = aDA.makeStringAndClear();
+}
+
+void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget )
+{
+ const StyleSettings& rSettings = m_aWidgetStyleSettings;
+
+ // save graphics state
+ push( PushFlags::ALL );
+
+ if( rWidget.Background || rWidget.Border )
+ {
+ setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
+ setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
+ drawRectangle( rBox.m_aRect );
+ }
+
+ Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
+ setFont( aFont );
+ Size aFontSize = aFont.GetFontSize();
+ if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
+ aFontSize.setHeight( rBox.m_aRect.GetHeight() );
+ sal_Int32 nDelta = aFontSize.Height()/10;
+ if( nDelta < 1 )
+ nDelta = 1;
+
+ tools::Rectangle aCheckRect, aTextRect;
+ {
+ aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
+ aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
+ aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
+ aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
+
+ // #i74206# handle small controls without text area
+ while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
+ {
+ aCheckRect.AdjustRight( -nDelta );
+ aCheckRect.AdjustTop(nDelta/2 );
+ aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
+ }
+
+ aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
+ aTextRect.SetTop( rBox.m_aRect.Top() );
+ aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
+ aTextRect.SetBottom( rBox.m_aRect.Bottom() );
+ }
+ setLineColor( COL_BLACK );
+ setFillColor( COL_TRANSPARENT );
+ OStringBuffer aLW( 32 );
+ aLW.append( "q " );
+ m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW );
+ aLW.append( " w " );
+ writeBuffer( aLW.getStr(), aLW.getLength() );
+ drawRectangle( aCheckRect );
+ writeBuffer( " Q\n", 3 );
+ setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
+ drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
+
+ pop();
+
+ OStringBuffer aDA( 256 );
+
+ // tdf#93853 don't rely on Zapf (or any other 'standard' font)
+ // being present, but our own OpenSymbol - N.B. PDF/A for good
+ // reasons require even the standard PS fonts to be embedded!
+ Push();
+ SetFont( Font( OUString( "OpenSymbol" ), aFont.GetFontSize() ) );
+ FontCharMapRef pMap;
+ GetFontCharMap(pMap);
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ const vcl::font::PhysicalFontFace* pDevFont = pFontInstance->GetFontFace();
+ Pop();
+
+ // make sure OpenSymbol is embedded, and includes our checkmark
+ const sal_Unicode cMark=0x2713;
+ const GlyphItem aItem(0, 0, pMap->GetGlyphIndex(cMark),
+ DevicePoint(), GlyphItemFlags::NONE, 0, 0, 0);
+ const std::vector<sal_Ucs> aCodeUnits={ cMark };
+ sal_Int32 nGlyphWidth = 0;
+ SalGraphics *pGraphics = GetGraphics();
+ if (pGraphics)
+ nGlyphWidth = m_aFontCache.getGlyphWidth(pDevFont,
+ aItem.glyphId(),
+ aItem.IsVertical(),
+ pGraphics);
+
+ sal_uInt8 nMappedGlyph;
+ sal_Int32 nMappedFontObject;
+ registerGlyph(&aItem, pDevFont, aCodeUnits, nGlyphWidth, nMappedGlyph, nMappedFontObject);
+
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
+ aDA.append( ' ' );
+ aDA.append( "/F" );
+ aDA.append( nMappedFontObject );
+ aDA.append( " 0 Tf" );
+
+ OStringBuffer aDR( 32 );
+ aDR.append( "/Font " );
+ aDR.append( getFontDictObject() );
+ aDR.append( " 0 R" );
+ rBox.m_aDRDict = aDR.makeStringAndClear();
+ rBox.m_aDAString = aDA.makeStringAndClear();
+ rBox.m_aMKDict = "/CA";
+ rBox.m_aMKDictCAString = "8";
+ rBox.m_aRect = aCheckRect;
+
+ // create appearance streams
+ sal_Int32 nCharXOffset = 1000 - 787; // metrics from OpenSymbol
+ nCharXOffset *= aCheckRect.GetHeight();
+ nCharXOffset /= 2000;
+ sal_Int32 nCharYOffset = 1000 - (820-143); // metrics from Zapf
+ nCharYOffset *= aCheckRect.GetHeight();
+ nCharYOffset /= 2000;
+
+ // write 'checked' appearance stream
+ SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
+ beginRedirect( pCheckStream, aCheckRect );
+ aDA.append( "/Tx BMC\nq BT\n" );
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
+ aDA.append( ' ' );
+ aDA.append( "/F" );
+ aDA.append( nMappedFontObject );
+ aDA.append( ' ' );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
+ aDA.append( " Tf\n" );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA );
+ aDA.append( " " );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA );
+ aDA.append( " Td <" );
+ appendHex( nMappedGlyph, aDA );
+ aDA.append( "> Tj\nET\nQ\nEMC\n" );
+ writeBuffer( aDA.getStr(), aDA.getLength() );
+ endRedirect();
+ rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
+
+ // write 'unchecked' appearance stream
+ SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
+ beginRedirect( pUncheckStream, aCheckRect );
+ writeBuffer( "/Tx BMC\nEMC\n", 12 );
+ endRedirect();
+ rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
+}
+
+void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget )
+{
+ const StyleSettings& rSettings = m_aWidgetStyleSettings;
+
+ // save graphics state
+ push( PushFlags::ALL );
+
+ if( rWidget.Background || rWidget.Border )
+ {
+ setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
+ setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
+ drawRectangle( rBox.m_aRect );
+ }
+
+ Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
+ setFont( aFont );
+ Size aFontSize = aFont.GetFontSize();
+ if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
+ aFontSize.setHeight( rBox.m_aRect.GetHeight() );
+ sal_Int32 nDelta = aFontSize.Height()/10;
+ if( nDelta < 1 )
+ nDelta = 1;
+
+ tools::Rectangle aCheckRect, aTextRect;
+ {
+ aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
+ aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
+ aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
+ aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
+
+ // #i74206# handle small controls without text area
+ while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
+ {
+ aCheckRect.AdjustRight( -nDelta );
+ aCheckRect.AdjustTop(nDelta/2 );
+ aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
+ }
+
+ aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
+ aTextRect.SetTop( rBox.m_aRect.Top() );
+ aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
+ aTextRect.SetBottom( rBox.m_aRect.Bottom() );
+ }
+ setLineColor( COL_BLACK );
+ setFillColor( COL_TRANSPARENT );
+ OStringBuffer aLW( 32 );
+ aLW.append( "q " );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW );
+ aLW.append( " w " );
+ writeBuffer( aLW.getStr(), aLW.getLength() );
+ drawEllipse( aCheckRect );
+ writeBuffer( " Q\n", 3 );
+ setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
+ drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
+
+ pop();
+
+ //to encrypt this (el)
+ rBox.m_aMKDict = "/CA";
+ //after this assignment, to m_aMKDic cannot be added anything
+ rBox.m_aMKDictCAString = "l";
+
+ rBox.m_aRect = aCheckRect;
+
+ // create appearance streams
+ push( PushFlags::ALL);
+ SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
+
+ beginRedirect( pCheckStream, aCheckRect );
+ OStringBuffer aDA( 256 );
+ aDA.append( "/Tx BMC\nq BT\n" );
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
+ aDA.append( ' ' );
+ m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
+ aDA.append( " 0 0 Td\nET\nQ\n" );
+ writeBuffer( aDA.getStr(), aDA.getLength() );
+ setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
+ setLineColor( COL_TRANSPARENT );
+ aCheckRect.AdjustLeft(3*nDelta );
+ aCheckRect.AdjustTop(3*nDelta );
+ aCheckRect.AdjustBottom( -(3*nDelta) );
+ aCheckRect.AdjustRight( -(3*nDelta) );
+ drawEllipse( aCheckRect );
+ writeBuffer( "\nEMC\n", 5 );
+ endRedirect();
+
+ pop();
+ rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
+
+ SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
+ beginRedirect( pUncheckStream, aCheckRect );
+ writeBuffer( "/Tx BMC\nEMC\n", 12 );
+ endRedirect();
+ rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
+}
+
+bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict )
+{
+ // TODO: check and insert default streams
+ OString aStandardAppearance;
+ switch( rWidget.m_eType )
+ {
+ case PDFWriter::CheckBox:
+ aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US );
+ break;
+ default:
+ break;
+ }
+
+ if( !rWidget.m_aAppearances.empty() )
+ {
+ rAnnotDict.append( "/AP<<\n" );
+ for (auto & dict_item : rWidget.m_aAppearances)
+ {
+ rAnnotDict.append( "/" );
+ rAnnotDict.append( dict_item.first );
+ bool bUseSubDict = (dict_item.second.size() > 1);
+
+ // PDF/A requires sub-dicts for /FT/Btn objects (clause
+ // 6.3.3)
+ if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3)
+ {
+ if( rWidget.m_eType == PDFWriter::RadioButton ||
+ rWidget.m_eType == PDFWriter::CheckBox ||
+ rWidget.m_eType == PDFWriter::PushButton )
+ {
+ bUseSubDict = true;
+ }
+ }
+
+ rAnnotDict.append( bUseSubDict ? "<<" : " " );
+
+ for (auto const& stream_item : dict_item.second)
+ {
+ SvMemoryStream* pAppearanceStream = stream_item.second;
+ dict_item.second[ stream_item.first ] = nullptr;
+
+ bool bDeflate = compressStream( pAppearanceStream );
+
+ sal_Int64 nStreamLen = pAppearanceStream->TellEnd();
+ pAppearanceStream->Seek( STREAM_SEEK_TO_BEGIN );
+ sal_Int32 nObject = createObject();
+ CHECK_RETURN( updateObject( nObject ) );
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::emitAppearances" );
+ }
+ OStringBuffer aLine;
+ aLine.append( nObject );
+
+ aLine.append( " 0 obj\n"
+ "<</Type/XObject\n"
+ "/Subtype/Form\n"
+ "/BBox[0 0 " );
+ appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine );
+ aLine.append( " " );
+ appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine );
+ aLine.append( "]\n"
+ "/Resources " );
+ aLine.append( getResourceDictObj() );
+ aLine.append( " 0 R\n"
+ "/Length " );
+ aLine.append( nStreamLen );
+ aLine.append( "\n" );
+ if( bDeflate )
+ aLine.append( "/Filter/FlateDecode\n" );
+ aLine.append( ">>\nstream\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ checkAndEnableStreamEncryption( nObject );
+ CHECK_RETURN( writeBuffer( pAppearanceStream->GetData(), nStreamLen ) );
+ disableStreamEncryption();
+ CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n", 19 ) );
+
+ if( bUseSubDict )
+ {
+ rAnnotDict.append( " /" );
+ rAnnotDict.append( stream_item.first );
+ rAnnotDict.append( " " );
+ }
+ rAnnotDict.append( nObject );
+ rAnnotDict.append( " 0 R" );
+
+ delete pAppearanceStream;
+ }
+
+ rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" );
+ }
+ rAnnotDict.append( ">>\n" );
+ if( !aStandardAppearance.isEmpty() )
+ {
+ rAnnotDict.append( "/AS /" );
+ rAnnotDict.append( aStandardAppearance );
+ rAnnotDict.append( "\n" );
+ }
+ }
+
+ return true;
+}
+
+bool PDFWriterImpl::emitWidgetAnnotations()
+{
+ ensureUniqueRadioOnValues();
+
+ int nAnnots = m_aWidgets.size();
+ for( int a = 0; a < nAnnots; a++ )
+ {
+ PDFWidget& rWidget = m_aWidgets[a];
+
+ if( rWidget.m_eType == PDFWriter::CheckBox )
+ {
+ if ( !rWidget.m_aOnValue.isEmpty() )
+ {
+ auto app_it = rWidget.m_aAppearances.find( "N" );
+ if( app_it != rWidget.m_aAppearances.end() )
+ {
+ auto stream_it = app_it->second.find( "Yes" );
+ if( stream_it != app_it->second.end() )
+ {
+ SvMemoryStream* pStream = stream_it->second;
+ app_it->second.erase( stream_it );
+ OStringBuffer aBuf( rWidget.m_aOnValue.getLength()*2 );
+ appendName( rWidget.m_aOnValue, aBuf );
+ (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
+ }
+ else
+ SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Yes\" stream" );
+ }
+ }
+
+ if ( !rWidget.m_aOffValue.isEmpty() )
+ {
+ auto app_it = rWidget.m_aAppearances.find( "N" );
+ if( app_it != rWidget.m_aAppearances.end() )
+ {
+ auto stream_it = app_it->second.find( "Off" );
+ if( stream_it != app_it->second.end() )
+ {
+ SvMemoryStream* pStream = stream_it->second;
+ app_it->second.erase( stream_it );
+ OStringBuffer aBuf( rWidget.m_aOffValue.getLength()*2 );
+ appendName( rWidget.m_aOffValue, aBuf );
+ (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
+ }
+ else
+ SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Off\" stream" );
+ }
+ }
+ }
+
+ OStringBuffer aLine( 1024 );
+ OStringBuffer aValue( 256 );
+ aLine.append( rWidget.m_nObject );
+ aLine.append( " 0 obj\n"
+ "<<" );
+ if( rWidget.m_eType != PDFWriter::Hierarchy )
+ {
+ // emit widget annotation only for terminal fields
+ if( rWidget.m_aKids.empty() )
+ {
+ int iRectMargin;
+
+ aLine.append( "/Type/Annot/Subtype/Widget/F " );
+
+ if (rWidget.m_eType == PDFWriter::Signature)
+ {
+ aLine.append( "132\n" ); // Print & Locked
+ iRectMargin = 0;
+ }
+ else
+ {
+ aLine.append( "4\n" );
+ iRectMargin = 1;
+ }
+
+ aLine.append("/Rect[" );
+ appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine );
+ aLine.append( "]\n" );
+ }
+ aLine.append( "/FT/" );
+ switch( rWidget.m_eType )
+ {
+ case PDFWriter::RadioButton:
+ case PDFWriter::CheckBox:
+ // for radio buttons only the RadioButton field, not the
+ // CheckBox children should have a value, else acrobat reader
+ // does not always check the right button
+ // of course real check boxes (not belonging to a radio group)
+ // need their values, too
+ if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 )
+ {
+ aValue.append( "/" );
+ // check for radio group with all buttons unpressed
+ if( rWidget.m_aValue.isEmpty() )
+ aValue.append( "Off" );
+ else
+ appendName( rWidget.m_aValue, aValue );
+ }
+ [[fallthrough]];
+ case PDFWriter::PushButton:
+ aLine.append( "Btn" );
+ break;
+ case PDFWriter::ListBox:
+ if( rWidget.m_nFlags & 0x200000 ) // multiselect
+ {
+ aValue.append( "[" );
+ for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ )
+ {
+ sal_Int32 nEntry = rWidget.m_aSelectedEntries[i];
+ if( nEntry >= 0
+ && o3tl::make_unsigned(nEntry) < rWidget.m_aListEntries.size() )
+ appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject, aValue );
+ }
+ aValue.append( "]" );
+ }
+ else if( !rWidget.m_aSelectedEntries.empty() &&
+ rWidget.m_aSelectedEntries[0] >= 0 &&
+ o3tl::make_unsigned(rWidget.m_aSelectedEntries[0]) < rWidget.m_aListEntries.size() )
+ {
+ appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject, aValue );
+ }
+ else
+ appendUnicodeTextStringEncrypt( OUString(), rWidget.m_nObject, aValue );
+ aLine.append( "Ch" );
+ break;
+ case PDFWriter::ComboBox:
+ appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
+ aLine.append( "Ch" );
+ break;
+ case PDFWriter::Edit:
+ aLine.append( "Tx" );
+ appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
+ break;
+ case PDFWriter::Signature:
+ aLine.append( "Sig" );
+ aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US));
+ break;
+ case PDFWriter::Hierarchy: // make the compiler happy
+ break;
+ }
+ aLine.append( "\n" );
+ aLine.append( "/P " );
+ aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject );
+ aLine.append( " 0 R\n" );
+ }
+ if( rWidget.m_nParent )
+ {
+ aLine.append( "/Parent " );
+ aLine.append( rWidget.m_nParent );
+ aLine.append( " 0 R\n" );
+ }
+ if( !rWidget.m_aKids.empty() )
+ {
+ aLine.append( "/Kids[" );
+ for( size_t i = 0; i < rWidget.m_aKids.size(); i++ )
+ {
+ aLine.append( rWidget.m_aKids[i] );
+ aLine.append( " 0 R" );
+ aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
+ }
+ aLine.append( "]\n" );
+ }
+ if( !rWidget.m_aName.isEmpty() )
+ {
+ aLine.append( "/T" );
+ appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !rWidget.m_aDescription.isEmpty() )
+ {
+ // the alternate field name should be unicode able since it is
+ // supposed to be used in UI
+ aLine.append( "/TU" );
+ appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+
+ if( rWidget.m_nFlags )
+ {
+ aLine.append( "/Ff " );
+ aLine.append( rWidget.m_nFlags );
+ aLine.append( "\n" );
+ }
+ if( !aValue.isEmpty() )
+ {
+ OString aVal = aValue.makeStringAndClear();
+ aLine.append( "/V " );
+ aLine.append( aVal );
+ aLine.append( "\n"
+ "/DV " );
+ aLine.append( aVal );
+ aLine.append( "\n" );
+ }
+ if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox )
+ {
+ sal_Int32 nTI = -1;
+ aLine.append( "/Opt[\n" );
+ sal_Int32 i = 0;
+ for (auto const& entry : rWidget.m_aListEntries)
+ {
+ appendUnicodeTextStringEncrypt( entry, rWidget.m_nObject, aLine );
+ aLine.append( "\n" );
+ if( entry == rWidget.m_aValue )
+ nTI = i;
+ ++i;
+ }
+ aLine.append( "]\n" );
+ if( nTI > 0 )
+ {
+ aLine.append( "/TI " );
+ aLine.append( nTI );
+ aLine.append( "\n" );
+ if( rWidget.m_nFlags & 0x200000 ) // Multiselect
+ {
+ aLine.append( "/I [" );
+ aLine.append( nTI );
+ aLine.append( "]\n" );
+ }
+ }
+ }
+ if( rWidget.m_eType == PDFWriter::Edit )
+ {
+ if ( rWidget.m_nMaxLen > 0 )
+ {
+ aLine.append( "/MaxLen " );
+ aLine.append( rWidget.m_nMaxLen );
+ aLine.append( "\n" );
+ }
+
+ if ( rWidget.m_nFormat == PDFWriter::Number )
+ {
+ OString aHexText;
+
+ if ( !rWidget.m_aCurrencySymbol.isEmpty() )
+ {
+ // Get the hexadecimal code
+ sal_UCS4 cChar = rWidget.m_aCurrencySymbol.iterateCodePoints(&o3tl::temporary(sal_Int32(1)), -1);
+ aHexText = "\\\\u" + OString::number(cChar, 16);
+ }
+
+ aLine.append("/AA<<\n");
+ aLine.append("/F<</JS(AFNumber_Format\\(");
+ aLine.append(OString::number(rWidget.m_nDecimalAccuracy));
+ aLine.append(", 0, 0, 0, \"");
+ aLine.append( aHexText );
+ aLine.append("\",");
+ aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol));
+ aLine.append("\\);)");
+ aLine.append("/S/JavaScript>>\n");
+ aLine.append("/K<</JS(AFNumber_Keystroke\\(");
+ aLine.append(OString::number(rWidget.m_nDecimalAccuracy));
+ aLine.append(", 0, 0, 0, \"");
+ aLine.append( aHexText );
+ aLine.append("\",");
+ aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol));
+ aLine.append("\\);)");
+ aLine.append("/S/JavaScript>>\n");
+ aLine.append(">>\n");
+ }
+ else if ( rWidget.m_nFormat == PDFWriter::Time )
+ {
+ aLine.append("/AA<<\n");
+ aLine.append("/F<</JS(AFTime_FormatEx\\(\"");
+ aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US));
+ aLine.append("\"\\);)");
+ aLine.append("/S/JavaScript>>\n");
+ aLine.append("/K<</JS(AFTime_KeystrokeEx\\(\"");
+ aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US));
+ aLine.append("\"\\);)");
+ aLine.append("/S/JavaScript>>\n");
+ aLine.append(">>\n");
+ }
+ else if ( rWidget.m_nFormat == PDFWriter::Date )
+ {
+ aLine.append("/AA<<\n");
+ aLine.append("/F<</JS(AFDate_FormatEx\\(\"");
+ aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US));
+ aLine.append("\"\\);)");
+ aLine.append("/S/JavaScript>>\n");
+ aLine.append("/K<</JS(AFDate_KeystrokeEx\\(\"");
+ aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US));
+ aLine.append("\"\\);)");
+ aLine.append("/S/JavaScript>>\n");
+ aLine.append(">>\n");
+ }
+ }
+ if( rWidget.m_eType == PDFWriter::PushButton )
+ {
+ if(!m_bIsPDF_A1)
+ {
+ OStringBuffer aDest;
+ if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) )
+ {
+ aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " );
+ aLine.append( aDest );
+ aLine.append( ">>>>\n" );
+ }
+ else if( rWidget.m_aListEntries.empty() )
+ {
+ if( !m_bIsPDF_A2 && !m_bIsPDF_A3 )
+ {
+ // create a reset form action
+ aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
+ }
+ }
+ else if( rWidget.m_bSubmit )
+ {
+ // create a submit form action
+ aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" );
+ appendLiteralStringEncrypt( rWidget.m_aListEntries.front(), rWidget.m_nObject, aLine, osl_getThreadTextEncoding() );
+ aLine.append( "/Flags " );
+
+ sal_Int32 nFlags = 0;
+ switch( m_aContext.SubmitFormat )
+ {
+ case PDFWriter::HTML:
+ nFlags |= 4;
+ break;
+ case PDFWriter::XML:
+ if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ nFlags |= 32;
+ break;
+ case PDFWriter::PDF:
+ if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ nFlags |= 256;
+ break;
+ case PDFWriter::FDF:
+ default:
+ break;
+ }
+ if( rWidget.m_bSubmitGet )
+ nFlags |= 8;
+ aLine.append( nFlags );
+ aLine.append( ">>>>\n" );
+ }
+ else
+ {
+ // create a URI action
+ aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" );
+ aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) );
+ aLine.append( ")>>>>\n" );
+ }
+ }
+ else
+ m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA );
+ }
+ if( !rWidget.m_aDAString.isEmpty() )
+ {
+ if( !rWidget.m_aDRDict.isEmpty() )
+ {
+ aLine.append( "/DR<<" );
+ aLine.append( rWidget.m_aDRDict );
+ aLine.append( ">>\n" );
+ }
+ else
+ {
+ aLine.append( "/DR<</Font<<" );
+ appendBuildinFontsToDict( aLine );
+ aLine.append( ">>>>\n" );
+ }
+ aLine.append( "/DA" );
+ appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine );
+ aLine.append( "\n" );
+ if( rWidget.m_nTextStyle & DrawTextFlags::Center )
+ aLine.append( "/Q 1\n" );
+ else if( rWidget.m_nTextStyle & DrawTextFlags::Right )
+ aLine.append( "/Q 2\n" );
+ }
+ // appearance characteristics for terminal fields
+ // which are supposed to have an appearance constructed
+ // by the viewer application
+ if( !rWidget.m_aMKDict.isEmpty() )
+ {
+ aLine.append( "/MK<<" );
+ aLine.append( rWidget.m_aMKDict );
+ //add the CA string, encrypting it
+ appendLiteralStringEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject, aLine);
+ aLine.append( ">>\n" );
+ }
+
+ CHECK_RETURN( emitAppearances( rWidget, aLine ) );
+
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ CHECK_RETURN( updateObject( rWidget.m_nObject ) );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ }
+ return true;
+}
+
+bool PDFWriterImpl::emitAnnotations()
+{
+ if( m_aPages.empty() )
+ return false;
+
+ CHECK_RETURN( emitLinkAnnotations() );
+ CHECK_RETURN(emitScreenAnnotations());
+ CHECK_RETURN( emitNoteAnnotations() );
+ CHECK_RETURN( emitWidgetAnnotations() );
+
+ return true;
+}
+
+bool PDFWriterImpl::emitEmbeddedFiles()
+{
+ for (const auto& rEmbeddedFile : m_aEmbeddedFiles)
+ {
+ if (!updateObject(rEmbeddedFile.m_nObject))
+ continue;
+
+ OStringBuffer aLine;
+ aLine.append(rEmbeddedFile.m_nObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /EmbeddedFile /Length ");
+ aLine.append(static_cast<sal_Int64>(rEmbeddedFile.m_aDataContainer.getSize()));
+ aLine.append(" >>\nstream\n");
+ CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
+ aLine.setLength(0);
+
+ CHECK_RETURN(writeBuffer(rEmbeddedFile.m_aDataContainer.getData(), rEmbeddedFile.m_aDataContainer.getSize()));
+
+ aLine.append("\nendstream\nendobj\n\n");
+ CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
+ }
+ return true;
+}
+
+#undef CHECK_RETURN
+#define CHECK_RETURN( x ) if( !x ) return false
+
+bool PDFWriterImpl::emitCatalog()
+{
+ // build page tree
+ // currently there is only one node that contains all leaves
+
+ // first create a page tree node id
+ sal_Int32 nTreeNode = createObject();
+
+ // emit global resource dictionary (page emit needs it)
+ CHECK_RETURN( emitResources() );
+
+ // emit all pages
+ for (auto & page : m_aPages)
+ if( ! page.emit( nTreeNode ) )
+ return false;
+
+ sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations();
+
+ sal_Int32 nOutlineDict = emitOutline();
+
+ // emit Output intent
+ sal_Int32 nOutputIntentObject = emitOutputIntent();
+
+ // emit metadata
+ sal_Int32 nMetadataObject = emitDocumentMetadata();
+
+ sal_Int32 nStructureDict = 0;
+ if(m_aStructure.size() > 1)
+ {
+ // check if dummy structure containers are needed
+ addInternalStructureContainer(m_aStructure[0]);
+ nStructureDict = m_aStructure[0].m_nObject = createObject();
+ emitStructure( m_aStructure[ 0 ] );
+ }
+
+ // adjust tree node file offset
+ if( ! updateObject( nTreeNode ) )
+ return false;
+
+ // emit tree node
+ OStringBuffer aLine( 2048 );
+ aLine.append( nTreeNode );
+ aLine.append( " 0 obj\n" );
+ aLine.append( "<</Type/Pages\n" );
+ aLine.append( "/Resources " );
+ aLine.append( getResourceDictObj() );
+ aLine.append( " 0 R\n" );
+
+ double nMediaBoxWidth = 0;
+ double nMediaBoxHeight = 0;
+ sal_Int32 nUserUnit = 1;
+ if( m_aPages.empty() ) // sanity check, this should not happen
+ {
+ nMediaBoxWidth = g_nInheritedPageWidth;
+ nMediaBoxHeight = g_nInheritedPageHeight;
+ }
+ else
+ {
+ for (auto const& page : m_aPages)
+ {
+ if( page.m_nPageWidth > nMediaBoxWidth )
+ {
+ nMediaBoxWidth = page.m_nPageWidth;
+ nUserUnit = page.m_nUserUnit;
+ }
+ if( page.m_nPageHeight > nMediaBoxHeight )
+ {
+ nMediaBoxHeight = page.m_nPageHeight;
+ nUserUnit = page.m_nUserUnit;
+ }
+ }
+ }
+ aLine.append( "/MediaBox[ 0 0 " );
+ aLine.append(nMediaBoxWidth / nUserUnit);
+ aLine.append( ' ' );
+ aLine.append(nMediaBoxHeight / nUserUnit);
+ aLine.append(" ]\n");
+ if (nUserUnit > 1)
+ {
+ aLine.append("/UserUnit ");
+ aLine.append(nUserUnit);
+ aLine.append("\n");
+ }
+ aLine.append("/Kids[ ");
+ unsigned int i = 0;
+ for (const auto & page : m_aPages)
+ {
+ aLine.append( page.m_nPageObject );
+ aLine.append( " 0 R" );
+ aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
+ ++i;
+ }
+ aLine.append( "]\n"
+ "/Count " );
+ aLine.append( static_cast<sal_Int32>(m_aPages.size()) );
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ // emit annotation objects
+ CHECK_RETURN( emitAnnotations() );
+ CHECK_RETURN( emitEmbeddedFiles() );
+
+ // emit Catalog
+ m_nCatalogObject = createObject();
+ if( ! updateObject( m_nCatalogObject ) )
+ return false;
+ aLine.setLength( 0 );
+ aLine.append( m_nCatalogObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/Catalog/Pages " );
+ aLine.append( nTreeNode );
+ aLine.append( " 0 R\n" );
+
+ // check if there are named destinations to emit (root must be inside the catalog)
+ if( nNamedDestinationsDictionary )
+ {
+ aLine.append("/Dests ");
+ aLine.append( nNamedDestinationsDictionary );
+ aLine.append( " 0 R\n" );
+ }
+
+ if( m_aContext.PageLayout != PDFWriter::DefaultLayout )
+ switch( m_aContext.PageLayout )
+ {
+ default :
+ case PDFWriter::SinglePage :
+ aLine.append( "/PageLayout/SinglePage\n" );
+ break;
+ case PDFWriter::Continuous :
+ aLine.append( "/PageLayout/OneColumn\n" );
+ break;
+ case PDFWriter::ContinuousFacing :
+ // the flag m_aContext.FirstPageLeft below is used to set the page on the left side
+ aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side
+ break;
+ }
+ if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode )
+ switch( m_aContext.PDFDocumentMode )
+ {
+ default :
+ aLine.append( "/PageMode/UseNone\n" );
+ break;
+ case PDFWriter::UseOutlines :
+ aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open
+ break;
+ case PDFWriter::UseThumbs :
+ aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open
+ break;
+ }
+ else if( m_aContext.OpenInFullScreenMode )
+ aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen
+
+ OStringBuffer aInitPageRef;
+ if( m_aContext.InitialPage >= 0 && o3tl::make_unsigned(m_aContext.InitialPage) < m_aPages.size() )
+ {
+ aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject );
+ aInitPageRef.append( " 0 R" );
+ }
+ else
+ aInitPageRef.append( "0" );
+
+ switch( m_aContext.PDFDocumentAction )
+ {
+ case PDFWriter::ActionDefault : //do nothing, this is the Acrobat default
+ default:
+ if( aInitPageRef.getLength() > 1 )
+ {
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef );
+ aLine.append( " /XYZ null null 0]\n" );
+ }
+ break;
+ case PDFWriter::FitInWindow :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef );
+ aLine.append( " /Fit]\n" ); //Open fit page
+ break;
+ case PDFWriter::FitWidth :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef );
+ aLine.append( " /FitH " );
+ aLine.append( g_nInheritedPageHeight );//Open fit width
+ aLine.append( "]\n" );
+ break;
+ case PDFWriter::FitVisible :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef );
+ aLine.append( " /FitBH " );
+ aLine.append( g_nInheritedPageHeight );//Open fit visible
+ aLine.append( "]\n" );
+ break;
+ case PDFWriter::ActionZoom :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef );
+ aLine.append( " /XYZ null null " );
+ if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 )
+ aLine.append( static_cast<double>(m_aContext.Zoom)/100.0 );
+ else
+ aLine.append( "0" );
+ aLine.append( "]\n" );
+ break;
+ }
+
+ // viewer preferences, if we had some, then emit
+ if( m_aContext.HideViewerToolbar ||
+ ( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle ) ||
+ m_aContext.HideViewerMenubar ||
+ m_aContext.HideViewerWindowControls || m_aContext.FitWindow ||
+ m_aContext.CenterWindow || (m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) ||
+ m_aContext.OpenInFullScreenMode )
+ {
+ aLine.append( "/ViewerPreferences<<" );
+ if( m_aContext.HideViewerToolbar )
+ aLine.append( "/HideToolbar true\n" );
+ if( m_aContext.HideViewerMenubar )
+ aLine.append( "/HideMenubar true\n" );
+ if( m_aContext.HideViewerWindowControls )
+ aLine.append( "/HideWindowUI true\n" );
+ if( m_aContext.FitWindow )
+ aLine.append( "/FitWindow true\n" );
+ if( m_aContext.CenterWindow )
+ aLine.append( "/CenterWindow true\n" );
+ if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle )
+ aLine.append( "/DisplayDocTitle true\n" );
+ if( m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing )
+ aLine.append( "/Direction/R2L\n" );
+ if( m_aContext.OpenInFullScreenMode )
+ switch( m_aContext.PDFDocumentMode )
+ {
+ default :
+ case PDFWriter::ModeDefault :
+ aLine.append( "/NonFullScreenPageMode/UseNone\n" );
+ break;
+ case PDFWriter::UseOutlines :
+ aLine.append( "/NonFullScreenPageMode/UseOutlines\n" );
+ break;
+ case PDFWriter::UseThumbs :
+ aLine.append( "/NonFullScreenPageMode/UseThumbs\n" );
+ break;
+ }
+ aLine.append( ">>\n" );
+ }
+
+ if( nOutlineDict )
+ {
+ aLine.append( "/Outlines " );
+ aLine.append( nOutlineDict );
+ aLine.append( " 0 R\n" );
+ }
+ if( nStructureDict )
+ {
+ aLine.append( "/StructTreeRoot " );
+ aLine.append( nStructureDict );
+ aLine.append( " 0 R\n" );
+ }
+ if( !m_aContext.DocumentLocale.Language.isEmpty() )
+ {
+ /* PDF allows only RFC 3066, see above in emitStructure(). */
+ LanguageTag aLanguageTag( m_aContext.DocumentLocale);
+ OUString aLanguage, aScript, aCountry;
+ aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
+ if (!aLanguage.isEmpty())
+ {
+ OUStringBuffer aLocBuf( 16 );
+ aLocBuf.append( aLanguage );
+ if( !aCountry.isEmpty() )
+ {
+ aLocBuf.append( '-' );
+ aLocBuf.append( aCountry );
+ }
+ aLine.append( "/Lang" );
+ appendLiteralStringEncrypt( aLocBuf, m_nCatalogObject, aLine );
+ aLine.append( "\n" );
+ }
+ }
+ if( m_aContext.Tagged && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ {
+ aLine.append( "/MarkInfo<</Marked true>>\n" );
+ }
+ if( !m_aWidgets.empty() )
+ {
+ aLine.append( "/AcroForm<</Fields[\n" );
+ int nWidgets = m_aWidgets.size();
+ int nOut = 0;
+ for( int j = 0; j < nWidgets; j++ )
+ {
+ // output only root fields
+ if( m_aWidgets[j].m_nParent < 1 )
+ {
+ aLine.append( m_aWidgets[j].m_nObject );
+ aLine.append( (nOut++ % 5)==4 ? " 0 R\n" : " 0 R " );
+ }
+ }
+ aLine.append( "\n]" );
+
+#if HAVE_FEATURE_NSS
+ if (m_nSignatureObject != -1)
+ aLine.append( "/SigFlags 3");
+#endif
+
+ aLine.append( "/DR " );
+ aLine.append( getResourceDictObj() );
+ aLine.append( " 0 R" );
+ // NeedAppearances must not be used if PDF is signed
+ if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3
+#if HAVE_FEATURE_NSS
+ || ( m_nSignatureObject != -1 )
+#endif
+ )
+ aLine.append( ">>\n" );
+ else
+ aLine.append( "/NeedAppearances true>>\n" );
+ }
+
+ //check if there is a Metadata object
+ if( nOutputIntentObject )
+ {
+ aLine.append("/OutputIntents[");
+ aLine.append( nOutputIntentObject );
+ aLine.append( " 0 R]" );
+ }
+
+ if( nMetadataObject )
+ {
+ aLine.append("/Metadata ");
+ aLine.append( nMetadataObject );
+ aLine.append( " 0 R" );
+ }
+
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ return writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+#if HAVE_FEATURE_NSS
+
+bool PDFWriterImpl::emitSignature()
+{
+ if( !updateObject( m_nSignatureObject ) )
+ return false;
+
+ OStringBuffer aLine( 0x5000 );
+ aLine.append( m_nSignatureObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append("<</Contents <" );
+
+ sal_uInt64 nOffset = ~0U;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
+
+ m_nSignatureContentOffset = nOffset + aLine.getLength();
+
+ // reserve some space for the PKCS#7 object
+ OStringBuffer aContentFiller( MAX_SIGNATURE_CONTENT_LENGTH );
+ comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0');
+ aLine.append( aContentFiller );
+ aLine.append( ">\n/Type/Sig/SubFilter/adbe.pkcs7.detached");
+
+ if( !m_aContext.DocumentInfo.Author.isEmpty() )
+ {
+ aLine.append( "/Name" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, m_nSignatureObject, aLine );
+ }
+
+ aLine.append( " /M ");
+ appendLiteralStringEncrypt( m_aCreationDateString, m_nSignatureObject, aLine );
+
+ aLine.append( " /ByteRange [ 0 ");
+ aLine.append( m_nSignatureContentOffset - 1 );
+ aLine.append( " " );
+ aLine.append( m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1 );
+ aLine.append( " " );
+
+ m_nSignatureLastByteRangeNoOffset = nOffset + aLine.getLength();
+
+ // mark the last ByteRange no and add some space. Now, we don't know
+ // how many bytes we need for this ByteRange value
+ // The real value will be overwritten in the finalizeSignature method
+ OStringBuffer aByteRangeFiller( 100 );
+ comphelper::string::padToLength(aByteRangeFiller, 100, ' ');
+ aLine.append( aByteRangeFiller );
+ aLine.append(" /Filter/Adobe.PPKMS");
+
+ //emit reason, location and contactinfo
+ if ( !m_aContext.SignReason.isEmpty() )
+ {
+ aLine.append("/Reason");
+ appendUnicodeTextStringEncrypt( m_aContext.SignReason, m_nSignatureObject, aLine );
+ }
+
+ if ( !m_aContext.SignLocation.isEmpty() )
+ {
+ aLine.append("/Location");
+ appendUnicodeTextStringEncrypt( m_aContext.SignLocation, m_nSignatureObject, aLine );
+ }
+
+ if ( !m_aContext.SignContact.isEmpty() )
+ {
+ aLine.append("/ContactInfo");
+ appendUnicodeTextStringEncrypt( m_aContext.SignContact, m_nSignatureObject, aLine );
+ }
+
+ aLine.append(" >>\nendobj\n\n" );
+
+ return writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+bool PDFWriterImpl::finalizeSignature()
+{
+ if (!m_aContext.SignCertificate.is())
+ return false;
+
+ // 1- calculate last ByteRange value
+ sal_uInt64 nOffset = ~0U;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
+
+ sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
+
+ // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position
+ sal_uInt64 nWritten = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset) ) );
+ OString aByteRangeNo = OString::number( nLastByteRangeNo ) + " ]";
+
+ if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None)
+ {
+ CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset)) );
+ return false;
+ }
+
+ // 3- create the PKCS#7 object using NSS
+
+ // Prepare buffer and calculate PDF file digest
+ CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, 0)) );
+
+ std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]);
+ sal_uInt64 bytesRead1;
+
+ //FIXME: Check if hash is calculated from the correct byterange
+ if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) ||
+ bytesRead1 != static_cast<sal_uInt64>(m_nSignatureContentOffset) - 1)
+ {
+ SAL_WARN("vcl.pdfwriter", "First buffer read failed");
+ return false;
+ }
+
+ std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]);
+ sal_uInt64 bytesRead2;
+
+ if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) ||
+ osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) ||
+ bytesRead2 != static_cast<sal_uInt64>(nLastByteRangeNo))
+ {
+ SAL_WARN("vcl.pdfwriter", "Second buffer read failed");
+ return false;
+ }
+
+ OStringBuffer aCMSHexBuffer;
+ svl::crypto::Signing aSigning(m_aContext.SignCertificate);
+ aSigning.AddDataRange(buffer1.get(), bytesRead1);
+ aSigning.AddDataRange(buffer2.get(), bytesRead2);
+ aSigning.SetSignTSA(m_aContext.SignTSA);
+ aSigning.SetSignPassword(m_aContext.SignPassword);
+ if (!aSigning.Sign(aCMSHexBuffer))
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed");
+ return false;
+ }
+
+ assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
+
+ // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object
+ nWritten = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset)) );
+ m_aFile.write(aCMSHexBuffer.getStr(), aCMSHexBuffer.getLength(), nWritten);
+
+ return osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset);
+}
+
+#endif //HAVE_FEATURE_NSS
+
+sal_Int32 PDFWriterImpl::emitInfoDict( )
+{
+ sal_Int32 nObject = createObject();
+
+ if( updateObject( nObject ) )
+ {
+ OStringBuffer aLine( 1024 );
+ aLine.append( nObject );
+ aLine.append( " 0 obj\n"
+ "<<" );
+ if( !m_aContext.DocumentInfo.Title.isEmpty() )
+ {
+ aLine.append( "/Title" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Title, nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !m_aContext.DocumentInfo.Author.isEmpty() )
+ {
+ aLine.append( "/Author" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !m_aContext.DocumentInfo.Subject.isEmpty() )
+ {
+ aLine.append( "/Subject" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Subject, nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !m_aContext.DocumentInfo.Keywords.isEmpty() )
+ {
+ aLine.append( "/Keywords" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Keywords, nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !m_aContext.DocumentInfo.Creator.isEmpty() )
+ {
+ aLine.append( "/Creator" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Creator, nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !m_aContext.DocumentInfo.Producer.isEmpty() )
+ {
+ aLine.append( "/Producer" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Producer, nObject, aLine );
+ aLine.append( "\n" );
+ }
+
+ aLine.append( "/CreationDate" );
+ appendLiteralStringEncrypt( m_aCreationDateString, nObject, aLine );
+ aLine.append( ">>\nendobj\n\n" );
+ if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
+ nObject = 0;
+ }
+ else
+ nObject = 0;
+
+ return nObject;
+}
+
+// Part of this function may be shared with method appendDest.
+sal_Int32 PDFWriterImpl::emitNamedDestinations()
+{
+ sal_Int32 nCount = m_aNamedDests.size();
+ if( nCount <= 0 )
+ return 0;//define internal error
+
+ //get the object number for all the destinations
+ sal_Int32 nObject = createObject();
+
+ if( updateObject( nObject ) )
+ {
+ //emit the dictionary
+ OStringBuffer aLine( 1024 );
+ aLine.append( nObject );
+ aLine.append( " 0 obj\n"
+ "<<" );
+
+ sal_Int32 nDestID;
+ for( nDestID = 0; nDestID < nCount; nDestID++ )
+ {
+ const PDFNamedDest& rDest = m_aNamedDests[ nDestID ];
+ // In order to correctly function both under an Internet browser and
+ // directly with a reader (provided the reader has the feature) we
+ // need to set the name of the destination the same way it will be encoded
+ // in an Internet link
+ INetURLObject aLocalURL( u"http://ahost.ax" ); //dummy location, won't be used
+ aLocalURL.SetMark( rDest.m_aDestName );
+
+ const OUString aName = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as
+ // in link creation ( see PDFWriterImpl::emitLinkAnnotations )
+ const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
+
+ aLine.append( '/' );
+ appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog )
+ aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function
+ //maps the preceding character properly
+ aLine.append( rDestPage.m_nPageObject );
+ aLine.append( " 0 R" );
+
+ switch( rDest.m_eType )
+ {
+ case PDFWriter::DestAreaType::XYZ:
+ default:
+ aLine.append( "/XYZ " );
+ appendFixedInt( rDest.m_aRect.Left(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Bottom(), aLine );
+ aLine.append( " 0" );
+ break;
+ case PDFWriter::DestAreaType::FitRectangle:
+ aLine.append( "/FitR " );
+ appendFixedInt( rDest.m_aRect.Left(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Top(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Right(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Bottom(), aLine );
+ break;
+ }
+ aLine.append( "]\n" );
+ }
+
+ //close
+ aLine.append( ">>\nendobj\n\n" );
+ if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
+ nObject = 0;
+ }
+ else
+ nObject = 0;
+
+ return nObject;
+}
+
+// emits the output intent dictionary
+sal_Int32 PDFWriterImpl::emitOutputIntent()
+{
+ if( !m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_A3 )
+ return 0;
+
+ //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1
+
+ OStringBuffer aLine( 1024 );
+ sal_Int32 nICCObject = createObject();
+ sal_Int32 nStreamLengthObject = createObject();
+
+ aLine.append( nICCObject );
+// sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16)
+ aLine.append( " 0 obj\n<</N 3/Length " );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 R" );
+ if (!g_bDebugDisableCompression)
+ aLine.append( "/Filter/FlateDecode" );
+ aLine.append( ">>\nstream\n" );
+ if ( !updateObject( nICCObject ) ) return 0;
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
+ //get file position
+ sal_uInt64 nBeginStreamPos = 0;
+ if (osl::File::E_None != m_aFile.getPos(nBeginStreamPos))
+ return 0;
+ beginCompression();
+ checkAndEnableStreamEncryption( nICCObject );
+ cmsHPROFILE hProfile = cmsCreate_sRGBProfile();
+ //force ICC profile version 2.1
+ cmsSetProfileVersion(hProfile, 2.1);
+ cmsUInt32Number nBytesNeeded = 0;
+ cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded);
+ if (!nBytesNeeded)
+ return 0;
+ std::vector<unsigned char> aBuffer(nBytesNeeded);
+ cmsSaveProfileToMem(hProfile, aBuffer.data(), &nBytesNeeded);
+ cmsCloseProfile(hProfile);
+ bool written = writeBuffer( aBuffer.data(), static_cast<sal_Int32>(aBuffer.size()) );
+ disableStreamEncryption();
+ endCompression();
+
+ sal_uInt64 nEndStreamPos = 0;
+ if (m_aFile.getPos(nEndStreamPos) != osl::File::E_None)
+ return 0;
+
+ if( !written )
+ return 0;
+ if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
+ return 0 ;
+ aLine.setLength( 0 );
+
+ //emit the stream length object
+ if ( !updateObject( nStreamLengthObject ) ) return 0;
+ aLine.setLength( 0 );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
+ aLine.append( "\nendobj\n\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
+ aLine.setLength( 0 );
+
+ //emit the OutputIntent dictionary
+ sal_Int32 nOIObject = createObject();
+ if ( !updateObject( nOIObject ) ) return 0;
+ aLine.append( nOIObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier");
+
+ appendLiteralStringEncrypt( std::string_view("sRGB IEC61966-2.1") ,nOIObject, aLine );
+ aLine.append("/DestOutputProfile ");
+ aLine.append( nICCObject );
+ aLine.append( " 0 R>>\nendobj\n\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
+
+ return nOIObject;
+}
+
+// formats the string for the XML stream
+void escapeStringXML(const OUString& rStr, OUString &rValue)
+{
+ const sal_Unicode* pUni = rStr.getStr();
+ int nLen = rStr.getLength();
+ for( ; nLen; nLen--, pUni++ )
+ {
+ switch( *pUni )
+ {
+ case u'&':
+ rValue += "&amp;";
+ break;
+ case u'<':
+ rValue += "&lt;";
+ break;
+ case u'>':
+ rValue += "&gt;";
+ break;
+ case u'\'':
+ rValue += "&apos;";
+ break;
+ case u'"':
+ rValue += "&quot;";
+ break;
+ default:
+ rValue += OUStringChar( *pUni );
+ break;
+ }
+ }
+}
+
+static void lcl_assignMeta(const OUString& aValue, OString& aMeta)
+{
+ if (!aValue.isEmpty())
+ {
+ OUString aTempString;
+ escapeStringXML(aValue, aTempString);
+ aMeta = OUStringToOString(aTempString, RTL_TEXTENCODING_UTF8);
+ }
+}
+
+// emits the document metadata
+sal_Int32 PDFWriterImpl::emitDocumentMetadata()
+{
+ if( !m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_A3 && !m_bIsPDF_UA)
+ return 0;
+
+ //get the object number for all the destinations
+ sal_Int32 nObject = createObject();
+
+ if( updateObject( nObject ) )
+ {
+ pdf::XmpMetadata aMetadata;
+
+ if (m_bIsPDF_A1)
+ aMetadata.mnPDF_A = 1;
+ else if (m_bIsPDF_A2)
+ aMetadata.mnPDF_A = 2;
+ else if (m_bIsPDF_A3)
+ aMetadata.mnPDF_A = 3;
+
+ aMetadata.mbPDF_UA = m_bIsPDF_UA;
+
+ lcl_assignMeta(m_aContext.DocumentInfo.Title, aMetadata.msTitle);
+ lcl_assignMeta(m_aContext.DocumentInfo.Author, aMetadata.msAuthor);
+ lcl_assignMeta(m_aContext.DocumentInfo.Subject, aMetadata.msSubject);
+ lcl_assignMeta(m_aContext.DocumentInfo.Producer, aMetadata.msProducer);
+ lcl_assignMeta(m_aContext.DocumentInfo.Keywords, aMetadata.msKeywords);
+ lcl_assignMeta(m_aContext.DocumentInfo.Creator, aMetadata.m_sCreatorTool);
+ aMetadata.m_sCreateDate = m_aCreationMetaDateString;
+
+ OStringBuffer aMetadataObj( 1024 );
+
+ aMetadataObj.append( nObject );
+ aMetadataObj.append( " 0 obj\n" );
+
+ aMetadataObj.append( "<</Type/Metadata/Subtype/XML/Length " );
+
+ aMetadataObj.append( sal_Int32(aMetadata.getSize()) );
+ aMetadataObj.append( ">>\nstream\n" );
+ if ( !writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
+ return 0;
+ //emit the stream
+ if ( !writeBuffer( aMetadata.getData(), aMetadata.getSize() ) )
+ return 0;
+
+ aMetadataObj.setLength( 0 );
+ aMetadataObj.append( "\nendstream\nendobj\n\n" );
+ if( ! writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
+ nObject = 0;
+ }
+ else
+ nObject = 0;
+
+ return nObject;
+}
+
+bool PDFWriterImpl::emitTrailer()
+{
+ // emit doc info
+ sal_Int32 nDocInfoObject = emitInfoDict( );
+
+ sal_Int32 nSecObject = 0;
+
+ if( m_aContext.Encryption.Encrypt() )
+ {
+ //emit the security information
+ //must be emitted as indirect dictionary object, since
+ //Acrobat Reader 5 works only with this kind of implementation
+ nSecObject = createObject();
+
+ if( updateObject( nSecObject ) )
+ {
+ OStringBuffer aLineS( 1024 );
+ aLineS.append( nSecObject );
+ aLineS.append( " 0 obj\n"
+ "<</Filter/Standard/V " );
+ // check the version
+ aLineS.append( "2/Length 128/R 3" );
+
+ // emit the owner password, must not be encrypted
+ aLineS.append( "/O(" );
+ appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.OValue.data()), sal_Int32(m_aContext.Encryption.OValue.size()), aLineS );
+ aLineS.append( ")/U(" );
+ appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.UValue.data()), sal_Int32(m_aContext.Encryption.UValue.size()), aLineS );
+ aLineS.append( ")/P " );// the permission set
+ aLineS.append( m_nAccessPermissions );
+ aLineS.append( ">>\nendobj\n\n" );
+ if( !writeBuffer( aLineS.getStr(), aLineS.getLength() ) )
+ nSecObject = 0;
+ }
+ else
+ nSecObject = 0;
+ }
+ // emit xref table
+ // remember start
+ sal_uInt64 nXRefOffset = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nXRefOffset )) );
+ CHECK_RETURN( writeBuffer( "xref\n", 5 ) );
+
+ sal_Int32 nObjects = m_aObjects.size();
+ OStringBuffer aLine;
+ aLine.append( "0 " );
+ aLine.append( static_cast<sal_Int32>(nObjects+1) );
+ aLine.append( "\n" );
+ aLine.append( "0000000000 65535 f \n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ for( sal_Int32 i = 0; i < nObjects; i++ )
+ {
+ aLine.setLength( 0 );
+ OString aOffset = OString::number( m_aObjects[i] );
+ for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ )
+ aLine.append( '0' );
+ aLine.append( aOffset );
+ aLine.append( " 00000 n \n" );
+ SAL_WARN_IF( aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ }
+
+ // prepare document checksum
+ OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 );
+ ::std::vector<unsigned char> const nMD5Sum(m_DocDigest.finalize());
+ for (sal_uInt8 i : nMD5Sum)
+ appendHex( i, aDocChecksum );
+ // document id set in setDocInfo method
+ // emit trailer
+ aLine.setLength( 0 );
+ aLine.append( "trailer\n"
+ "<</Size " );
+ aLine.append( static_cast<sal_Int32>(nObjects+1) );
+ aLine.append( "/Root " );
+ aLine.append( m_nCatalogObject );
+ aLine.append( " 0 R\n" );
+ if( nSecObject )
+ {
+ aLine.append( "/Encrypt ");
+ aLine.append( nSecObject );
+ aLine.append( " 0 R\n" );
+ }
+ if( nDocInfoObject )
+ {
+ aLine.append( "/Info " );
+ aLine.append( nDocInfoObject );
+ aLine.append( " 0 R\n" );
+ }
+ if( ! m_aContext.Encryption.DocumentIdentifier.empty() )
+ {
+ aLine.append( "/ID [ <" );
+ for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
+ {
+ appendHex( sal_Int8(item), aLine );
+ }
+ aLine.append( ">\n"
+ "<" );
+ for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
+ {
+ appendHex( sal_Int8(item), aLine );
+ }
+ aLine.append( "> ]\n" );
+ }
+ if( !aDocChecksum.isEmpty() )
+ {
+ aLine.append( "/DocChecksum /" );
+ aLine.append( aDocChecksum );
+ aLine.append( "\n" );
+ }
+ if( !m_aAdditionalStreams.empty() )
+ {
+ aLine.append( "/AdditionalStreams [" );
+ for(const PDFAddStream & rAdditionalStream : m_aAdditionalStreams)
+ {
+ aLine.append( "/" );
+ appendName( rAdditionalStream.m_aMimeType, aLine );
+ aLine.append( " " );
+ aLine.append( rAdditionalStream.m_nStreamObject );
+ aLine.append( " 0 R\n" );
+ }
+ aLine.append( "]\n" );
+ }
+ aLine.append( ">>\n"
+ "startxref\n" );
+ aLine.append( static_cast<sal_Int64>(nXRefOffset) );
+ aLine.append( "\n"
+ "%%EOF\n" );
+ return writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+namespace {
+
+struct AnnotationSortEntry
+{
+ sal_Int32 nTabOrder;
+ sal_Int32 nObject;
+ sal_Int32 nWidgetIndex;
+
+ AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) :
+ nTabOrder( nTab ),
+ nObject( nObj ),
+ nWidgetIndex( nI )
+ {}
+};
+
+struct AnnotSortContainer
+{
+ o3tl::sorted_vector< sal_Int32 > aObjects;
+ std::vector< AnnotationSortEntry > aSortedAnnots;
+};
+
+struct AnnotSorterLess
+{
+ std::vector<PDFWidget>& m_rWidgets;
+
+ explicit AnnotSorterLess( std::vector<PDFWidget>& rWidgets ) : m_rWidgets( rWidgets ) {}
+
+ bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight )
+ {
+ if( rLeft.nTabOrder < rRight.nTabOrder )
+ return true;
+ if( rRight.nTabOrder < rLeft.nTabOrder )
+ return false;
+ if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 )
+ return false;
+ if( rRight.nWidgetIndex < 0 )
+ return true;
+ if( rLeft.nWidgetIndex < 0 )
+ return false;
+ // remember: widget rects are in PDF coordinates, so they are ordered down up
+ if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() >
+ m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() )
+ return true;
+ if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() >
+ m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() )
+ return false;
+ if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() <
+ m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() )
+ return true;
+ return false;
+ }
+};
+
+}
+
+void PDFWriterImpl::sortWidgets()
+{
+ // sort widget annotations on each page as per their
+ // TabOrder attribute
+ std::unordered_map< sal_Int32, AnnotSortContainer > sorted;
+ int nWidgets = m_aWidgets.size();
+ for( int nW = 0; nW < nWidgets; nW++ )
+ {
+ const PDFWidget& rWidget = m_aWidgets[nW];
+ if( rWidget.m_nPage >= 0 )
+ {
+ AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ];
+ // optimize vector allocation
+ if( rCont.aSortedAnnots.empty() )
+ rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() );
+ // insert widget to tab sorter
+ // RadioButtons are not page annotations, only their individual check boxes are
+ if( rWidget.m_eType != PDFWriter::RadioButton )
+ {
+ rCont.aObjects.insert( rWidget.m_nObject );
+ rCont.aSortedAnnots.emplace_back( rWidget.m_nTabOrder, rWidget.m_nObject, nW );
+ }
+ }
+ }
+ for (auto & item : sorted)
+ {
+ // append entries for non widget annotations
+ PDFPage& rPage = m_aPages[ item.first ];
+ unsigned int nAnnots = rPage.m_aAnnotations.size();
+ for( unsigned int nA = 0; nA < nAnnots; nA++ )
+ if( item.second.aObjects.find( rPage.m_aAnnotations[nA] ) == item.second.aObjects.end())
+ item.second.aSortedAnnots.emplace_back( 10000, rPage.m_aAnnotations[nA], -1 );
+
+ AnnotSorterLess aLess( m_aWidgets );
+ std::stable_sort( item.second.aSortedAnnots.begin(), item.second.aSortedAnnots.end(), aLess );
+ // sanity check
+ if( item.second.aSortedAnnots.size() == nAnnots)
+ {
+ for( unsigned int nA = 0; nA < nAnnots; nA++ )
+ rPage.m_aAnnotations[nA] = item.second.aSortedAnnots[nA].nObject;
+ }
+ else
+ {
+ SAL_WARN( "vcl.pdfwriter", "wrong number of sorted annotations" );
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions "
+ "on page nr " << item.first << ", " <<
+ static_cast<tools::Long>(item.second.aSortedAnnots.size()) << " sorted and " <<
+ static_cast<tools::Long>(nAnnots) << " unsorted");
+ }
+ }
+
+ // FIXME: implement tab order in structure tree for PDF 1.5
+}
+
+class PDFStreamIf :
+ public cppu::WeakImplHelper< css::io::XOutputStream >
+{
+ VclPtr<PDFWriterImpl> m_pWriter;
+ bool m_bWrite;
+ public:
+ explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {}
+
+ virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override;
+ virtual void SAL_CALL flush() override;
+ virtual void SAL_CALL closeOutput() override;
+};
+
+void SAL_CALL PDFStreamIf::writeBytes( const css::uno::Sequence< sal_Int8 >& aData )
+{
+ if( m_bWrite && aData.hasElements() )
+ {
+ sal_Int32 nBytes = aData.getLength();
+ m_pWriter->writeBuffer( aData.getConstArray(), nBytes );
+ }
+}
+
+void SAL_CALL PDFStreamIf::flush()
+{
+}
+
+void SAL_CALL PDFStreamIf::closeOutput()
+{
+ m_bWrite = false;
+}
+
+bool PDFWriterImpl::emitAdditionalStreams()
+{
+ unsigned int nStreams = m_aAdditionalStreams.size();
+ for( unsigned int i = 0; i < nStreams; i++ )
+ {
+ PDFAddStream& rStream = m_aAdditionalStreams[i];
+ rStream.m_nStreamObject = createObject();
+ sal_Int32 nSizeObject = createObject();
+
+ if( ! updateObject( rStream.m_nStreamObject ) )
+ return false;
+
+ OStringBuffer aLine;
+ aLine.append( rStream.m_nStreamObject );
+ aLine.append( " 0 obj\n<</Length " );
+ aLine.append( nSizeObject );
+ aLine.append( " 0 R" );
+ if( rStream.m_bCompress )
+ aLine.append( "/Filter/FlateDecode" );
+ aLine.append( ">>\nstream\n" );
+ if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
+ return false;
+ sal_uInt64 nBeginStreamPos = 0, nEndStreamPos = 0;
+ if( osl::File::E_None != m_aFile.getPos(nBeginStreamPos) )
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ }
+ if( rStream.m_bCompress )
+ beginCompression();
+
+ checkAndEnableStreamEncryption( rStream.m_nStreamObject );
+ css::uno::Reference< css::io::XOutputStream > xStream( new PDFStreamIf( this ) );
+ assert(rStream.m_pStream);
+ if (!rStream.m_pStream)
+ return false;
+ rStream.m_pStream->write( xStream );
+ xStream.clear();
+ delete rStream.m_pStream;
+ rStream.m_pStream = nullptr;
+ disableStreamEncryption();
+
+ if( rStream.m_bCompress )
+ endCompression();
+
+ if (osl::File::E_None != m_aFile.getPos(nEndStreamPos))
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ return false;
+ }
+ if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
+ return false ;
+ // emit stream length object
+ if( ! updateObject( nSizeObject ) )
+ return false;
+ aLine.setLength( 0 );
+ aLine.append( nSizeObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
+ aLine.append( "\nendobj\n\n" );
+ if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
+ return false;
+ }
+ return true;
+}
+
+bool PDFWriterImpl::emit()
+{
+ endPage();
+
+ // resort structure tree and annotations if necessary
+ // needed for widget tab order
+ sortWidgets();
+
+#if HAVE_FEATURE_NSS
+ if( m_aContext.SignPDF )
+ {
+ // sign the document
+ PDFWriter::SignatureWidget aSignature;
+ aSignature.Name = "Signature1";
+ createControl( aSignature, 0 );
+ }
+#endif
+
+ // emit additional streams
+ CHECK_RETURN( emitAdditionalStreams() );
+
+ // emit catalog
+ CHECK_RETURN( emitCatalog() );
+
+#if HAVE_FEATURE_NSS
+ if (m_nSignatureObject != -1) // if document is signed, emit sigdict
+ {
+ if( !emitSignature() )
+ {
+ m_aErrors.insert( PDFWriter::Error_Signature_Failed );
+ return false;
+ }
+ }
+#endif
+
+ // emit trailer
+ CHECK_RETURN( emitTrailer() );
+
+#if HAVE_FEATURE_NSS
+ if (m_nSignatureObject != -1) // finalize the signature
+ {
+ if( !finalizeSignature() )
+ {
+ m_aErrors.insert( PDFWriter::Error_Signature_Failed );
+ return false;
+ }
+ }
+#endif
+
+ m_aFile.close();
+ m_bOpen = false;
+
+ return true;
+}
+
+
+sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont )
+{
+ Push();
+
+ SetFont( i_rFont );
+
+ const vcl::font::PhysicalFontFace* pDevFont = GetFontInstance()->GetFontFace();
+ sal_Int32 nFontID = 0;
+ auto it = m_aSystemFonts.find( pDevFont );
+ if( it != m_aSystemFonts.end() )
+ nFontID = it->second.m_nNormalFontID;
+ else
+ {
+ nFontID = m_nNextFID++;
+ m_aSystemFonts[ pDevFont ] = EmbedFont();
+ m_aSystemFonts[ pDevFont ].m_nNormalFontID = nFontID;
+ }
+
+ Pop();
+ return nFontID;
+}
+
+void PDFWriterImpl::registerGlyph(const GlyphItem* pGlyph,
+ const vcl::font::PhysicalFontFace* pFont,
+ const std::vector<sal_Ucs>& rCodeUnits,
+ sal_Int32 nGlyphWidth,
+ sal_uInt8& nMappedGlyph,
+ sal_Int32& nMappedFontObject)
+{
+ const int nFontGlyphId = pGlyph->glyphId();
+ FontSubset& rSubset = m_aSubsets[ pFont ];
+ // search for font specific glyphID
+ auto it = rSubset.m_aMapping.find( nFontGlyphId );
+ if( it != rSubset.m_aMapping.end() )
+ {
+ nMappedFontObject = it->second.m_nFontID;
+ nMappedGlyph = it->second.m_nSubsetGlyphID;
+ }
+ else
+ {
+ // create new subset if necessary
+ if( rSubset.m_aSubsets.empty()
+ || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) )
+ {
+ rSubset.m_aSubsets.emplace_back( m_nNextFID++ );
+ }
+
+ // copy font id
+ nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
+ // create new glyph in subset
+ sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1);
+ nMappedGlyph = nNewId;
+
+ // add new glyph to emitted font subset
+ GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ];
+ rNewGlyphEmit.setGlyphId( nNewId );
+ rNewGlyphEmit.setGlyphWidth( nGlyphWidth );
+ for (const auto nCode : rCodeUnits)
+ rNewGlyphEmit.addCode(nCode);
+
+ // add new glyph to font mapping
+ Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ];
+ rNewGlyph.m_nFontID = nMappedFontObject;
+ rNewGlyph.m_nSubsetGlyphID = nNewId;
+ }
+}
+
+void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines )
+{
+ push( PushFlags::ALL );
+
+ FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief();
+
+ Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor();
+ Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
+ Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
+ Color aReliefColor( COL_LIGHTGRAY );
+ if( aTextColor == COL_BLACK )
+ aTextColor = COL_WHITE;
+ if( aTextLineColor == COL_BLACK )
+ aTextLineColor = COL_WHITE;
+ if( aOverlineColor == COL_BLACK )
+ aOverlineColor = COL_WHITE;
+ // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct
+ if( aTextColor == COL_WHITE )
+ aReliefColor = COL_BLACK;
+
+ Font aSetFont = m_aCurrentPDFState.m_aFont;
+ aSetFont.SetRelief( FontRelief::NONE );
+ aSetFont.SetShadow( false );
+
+ aSetFont.SetColor( aReliefColor );
+ setTextLineColor( aReliefColor );
+ setOverlineColor( aReliefColor );
+ setFont( aSetFont );
+ tools::Long nOff = 1 + GetDPIX()/300;
+ if( eRelief == FontRelief::Engraved )
+ nOff = -nOff;
+
+ rLayout.DrawOffset() += Point( nOff, nOff );
+ updateGraphicsState();
+ drawLayout( rLayout, rText, bTextLines );
+
+ rLayout.DrawOffset() -= Point( nOff, nOff );
+ setTextLineColor( aTextLineColor );
+ setOverlineColor( aOverlineColor );
+ aSetFont.SetColor( aTextColor );
+ setFont( aSetFont );
+ updateGraphicsState();
+ drawLayout( rLayout, rText, bTextLines );
+
+ // clean up the mess
+ pop();
+}
+
+void PDFWriterImpl::drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines )
+{
+ Font aSaveFont = m_aCurrentPDFState.m_aFont;
+ Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
+ Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
+
+ Font& rFont = m_aCurrentPDFState.m_aFont;
+ if( rFont.GetColor() == COL_BLACK || rFont.GetColor().GetLuminance() < 8 )
+ rFont.SetColor( COL_LIGHTGRAY );
+ else
+ rFont.SetColor( COL_BLACK );
+ rFont.SetShadow( false );
+ rFont.SetOutline( false );
+ setFont( rFont );
+ setTextLineColor( rFont.GetColor() );
+ setOverlineColor( rFont.GetColor() );
+ updateGraphicsState();
+
+ tools::Long nOff = 1 + ((GetFontInstance()->mnLineHeight-24)/24);
+ if( rFont.IsOutline() )
+ nOff++;
+ rLayout.DrawBase() += DevicePoint(nOff, nOff);
+ drawLayout( rLayout, rText, bTextLines );
+ rLayout.DrawBase() -= DevicePoint(nOff, nOff);
+
+ setFont( aSaveFont );
+ setTextLineColor( aSaveTextLineColor );
+ setOverlineColor( aSaveOverlineColor );
+ updateGraphicsState();
+}
+
+void PDFWriterImpl::drawVerticalGlyphs(
+ const std::vector<PDFGlyph>& rGlyphs,
+ OStringBuffer& rLine,
+ const Point& rAlignOffset,
+ const Matrix3& rRotScale,
+ double fAngle,
+ double fXScale,
+ double fSkew,
+ sal_Int32 nFontHeight )
+{
+ tools::Long nXOffset = 0;
+ Point aCurPos( rGlyphs[0].m_aPos );
+ aCurPos = PixelToLogic( aCurPos );
+ aCurPos += rAlignOffset;
+ for( size_t i = 0; i < rGlyphs.size(); i++ )
+ {
+ // have to emit each glyph on its own
+ double fDeltaAngle = 0.0;
+ double fYScale = 1.0;
+ double fTempXScale = fXScale;
+ double fSkewB = fSkew;
+ double fSkewA = 0.0;
+
+ Point aDeltaPos;
+ if (rGlyphs[i].m_pGlyph->IsVertical())
+ {
+ fDeltaAngle = M_PI/2.0;
+ fYScale = fXScale;
+ fTempXScale = 1.0;
+ fSkewA = -fSkewB;
+ fSkewB = 0.0;
+ }
+ aDeltaPos += PixelToLogic( Point( static_cast<int>(static_cast<double>(nXOffset)/fXScale), 0 ) ) - PixelToLogic( Point() );
+ if( i < rGlyphs.size()-1 )
+ // #i120627# the text on the Y axis is reversed when export ppt file to PDF format
+ {
+ tools::Long nOffsetX = rGlyphs[i+1].m_aPos.X() - rGlyphs[i].m_aPos.X();
+ tools::Long nOffsetY = rGlyphs[i+1].m_aPos.Y() - rGlyphs[i].m_aPos.Y();
+ nXOffset += static_cast<int>(sqrt(double(nOffsetX*nOffsetX + nOffsetY*nOffsetY)));
+ }
+ if (!rGlyphs[i].m_pGlyph->glyphId())
+ continue;
+
+ aDeltaPos = rRotScale.transform( aDeltaPos );
+
+ Matrix3 aMat;
+ if( fSkewB != 0.0 || fSkewA != 0.0 )
+ aMat.skew( fSkewA, fSkewB );
+ aMat.scale( fTempXScale, fYScale );
+ aMat.rotate( fAngle+fDeltaAngle );
+ aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() );
+ m_aPages.back().appendMatrix3(aMat, rLine);
+ rLine.append( " Tm" );
+ if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId )
+ {
+ rLine.append( " /F" );
+ rLine.append( rGlyphs[i].m_nMappedFontId );
+ rLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nFontHeight, rLine );
+ rLine.append( " Tf" );
+ }
+ rLine.append( "<" );
+ appendHex( rGlyphs[i].m_nMappedGlyphId, rLine );
+ rLine.append( ">Tj\n" );
+ }
+}
+
+void PDFWriterImpl::drawHorizontalGlyphs(
+ const std::vector<PDFGlyph>& rGlyphs,
+ OStringBuffer& rLine,
+ const Point& rAlignOffset,
+ bool bFirst,
+ double fAngle,
+ double fXScale,
+ double fSkew,
+ sal_Int32 nFontHeight,
+ sal_Int32 nPixelFontHeight
+ )
+{
+ // horizontal (= normal) case
+
+ // fill in run end indices
+ // end is marked by index of the first glyph of the next run
+ // a run is marked by same mapped font id and same Y position
+ std::vector< sal_uInt32 > aRunEnds;
+ aRunEnds.reserve( rGlyphs.size() );
+ for( size_t i = 1; i < rGlyphs.size(); i++ )
+ {
+ if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId ||
+ rGlyphs[i].m_aPos.Y() != rGlyphs[i-1].m_aPos.Y() )
+ {
+ aRunEnds.push_back(i);
+ }
+ }
+ // last run ends at last glyph
+ aRunEnds.push_back( rGlyphs.size() );
+
+ // loop over runs of the same font
+ sal_uInt32 nBeginRun = 0;
+ for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ )
+ {
+ // setup text matrix
+ Point aCurPos = rGlyphs[nBeginRun].m_aPos;
+ // back transformation to current coordinate system
+ aCurPos = PixelToLogic( aCurPos );
+ aCurPos += rAlignOffset;
+ // the first run can be set with "Td" operator
+ // subsequent use of that operator would move
+ // the textline matrix relative to what was set before
+ // making use of that would drive us into rounding issues
+ Matrix3 aMat;
+ if( bFirst && nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 )
+ {
+ m_aPages.back().appendPoint( aCurPos, rLine );
+ rLine.append( " Td " );
+ }
+ else
+ {
+ if( fSkew != 0.0 )
+ aMat.skew( 0.0, fSkew );
+ aMat.scale( fXScale, 1.0 );
+ aMat.rotate( fAngle );
+ aMat.translate( aCurPos.X(), aCurPos.Y() );
+ m_aPages.back().appendMatrix3(aMat, rLine);
+ rLine.append( " Tm\n" );
+ }
+ // set up correct font
+ rLine.append( "/F" );
+ rLine.append( rGlyphs[nBeginRun].m_nMappedFontId );
+ rLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nFontHeight, rLine );
+ rLine.append( " Tf" );
+
+ // output glyphs using Tj or TJ
+ OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 );
+ aKernedLine.append( "[<" );
+ aUnkernedLine.append( '<' );
+ appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine );
+ appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine );
+
+ aMat.invert();
+ bool bNeedKern = false;
+ for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ )
+ {
+ appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine );
+ // check if default glyph positioning is sufficient
+ const Point aThisPos = aMat.transform( rGlyphs[nPos].m_aPos );
+ const Point aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos );
+ double fAdvance = aThisPos.X() - aPrevPos.X();
+ fAdvance *= 1000.0 / nPixelFontHeight;
+ const double fAdjustment = rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5;
+ SAL_WARN_IF(
+ fAdjustment < SAL_MIN_INT32 || fAdjustment > SAL_MAX_INT32, "vcl.pdfwriter",
+ "adjustment " << fAdjustment << " outside 32-bit int");
+ const sal_Int32 nAdjustment = static_cast<sal_Int32>(
+ std::clamp(fAdjustment, double(SAL_MIN_INT32), double(SAL_MAX_INT32)));
+ if( nAdjustment != 0 )
+ {
+ // apply individual glyph positioning
+ bNeedKern = true;
+ aKernedLine.append( ">" );
+ aKernedLine.append( nAdjustment );
+ aKernedLine.append( "<" );
+ }
+ appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine );
+ }
+ aKernedLine.append( ">]TJ\n" );
+ aUnkernedLine.append( ">Tj\n" );
+ rLine.append( bNeedKern ? aKernedLine : aUnkernedLine );
+
+ // set beginning of next run
+ nBeginRun = aRunEnds[nRun];
+ }
+}
+
+void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines )
+{
+ // relief takes precedence over shadow (see outdev3.cxx)
+ if( m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE )
+ {
+ drawRelief( rLayout, rText, bTextLines );
+ return;
+ }
+ else if( m_aCurrentPDFState.m_aFont.IsShadow() )
+ drawShadow( rLayout, rText, bTextLines );
+
+ OStringBuffer aLine( 512 );
+
+ const int nMaxGlyphs = 256;
+
+ std::vector<sal_Ucs> aCodeUnits;
+ bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical();
+ int nIndex = 0;
+ double fXScale = 1.0;
+ double fSkew = 0.0;
+ sal_Int32 nPixelFontHeight = GetFontInstance()->GetFontSelectPattern().mnHeight;
+ TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
+
+ // transform font height back to current units
+ // note: the layout calculates in outdevs device pixel !!
+ sal_Int32 nFontHeight = ImplDevicePixelToLogicHeight( nPixelFontHeight );
+ if( m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
+ {
+ Font aFont( m_aCurrentPDFState.m_aFont );
+ aFont.SetAverageFontWidth( 0 );
+ FontMetric aMetric = GetFontMetric( aFont );
+ if( aMetric.GetAverageFontWidth() != m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
+ {
+ fXScale =
+ static_cast<double>(m_aCurrentPDFState.m_aFont.GetAverageFontWidth()) /
+ static_cast<double>(aMetric.GetAverageFontWidth());
+ }
+ }
+
+ // perform artificial italics if necessary
+ if( ( m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_NORMAL ||
+ m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_OBLIQUE ) &&
+ ( GetFontInstance()->GetFontFace()->GetItalic() != ITALIC_NORMAL &&
+ GetFontInstance()->GetFontFace()->GetItalic() != ITALIC_OBLIQUE )
+ )
+ {
+ fSkew = M_PI/12.0;
+ }
+
+ // if the mapmode is distorted we need to adjust for that also
+ if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() )
+ {
+ fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY());
+ }
+
+ Degree10 nAngle = m_aCurrentPDFState.m_aFont.GetOrientation();
+ // normalize angles
+ while( nAngle < 0_deg10 )
+ nAngle += 3600_deg10;
+ nAngle = nAngle % 3600_deg10;
+ double fAngle = toRadians(nAngle);
+
+ Matrix3 aRotScale;
+ aRotScale.scale( fXScale, 1.0 );
+ if( fAngle != 0.0 )
+ aRotScale.rotate( -fAngle );
+
+ bool bPop = false;
+ bool bABold = false;
+ // artificial bold necessary ?
+ if( GetFontInstance()->GetFontFace()->GetWeight() <= WEIGHT_MEDIUM &&
+ GetFontInstance()->GetFontSelectPattern().GetWeight() > WEIGHT_MEDIUM )
+ {
+ aLine.append("q ");
+ bPop = true;
+ bABold = true;
+ }
+ // setup text colors (if necessary)
+ Color aStrokeColor( COL_TRANSPARENT );
+ Color aNonStrokeColor( COL_TRANSPARENT );
+
+ if( m_aCurrentPDFState.m_aFont.IsOutline() )
+ {
+ aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
+ aNonStrokeColor = COL_WHITE;
+ }
+ else
+ aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
+ if( bABold )
+ aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
+
+ if( aStrokeColor != COL_TRANSPARENT && aStrokeColor != m_aCurrentPDFState.m_aLineColor )
+ {
+ if( ! bPop )
+ aLine.append( "q " );
+ bPop = true;
+ appendStrokingColor( aStrokeColor, aLine );
+ aLine.append( "\n" );
+ }
+ if( aNonStrokeColor != COL_TRANSPARENT && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor )
+ {
+ if( ! bPop )
+ aLine.append( "q " );
+ bPop = true;
+ appendNonStrokingColor( aNonStrokeColor, aLine );
+ aLine.append( "\n" );
+ }
+
+ // begin text object
+ aLine.append( "BT\n" );
+ // outline attribute ?
+ if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold )
+ {
+ // set correct text mode, set stroke width
+ aLine.append( "2 Tr " ); // fill, then stroke
+
+ if( m_aCurrentPDFState.m_aFont.IsOutline() )
+ {
+ // unclear what to do in case of outline and artificial bold
+ // for the time being outline wins
+ aLine.append( "0.25 w \n" );
+ }
+ else
+ {
+ double fW = static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontHeight()) / 30.0;
+ m_aPages.back().appendMappedLength( fW, aLine );
+ aLine.append ( " w\n" );
+ }
+ }
+
+ FontMetric aRefDevFontMetric = GetFontMetric();
+ const vcl::font::PhysicalFontFace* pDevFont = GetFontInstance()->GetFontFace();
+ const GlyphItem* pGlyph = nullptr;
+ const vcl::font::PhysicalFontFace* pFallbackFont = nullptr;
+
+ // collect the glyphs into a single array
+ std::vector< PDFGlyph > aGlyphs;
+ aGlyphs.reserve( nMaxGlyphs );
+ // first get all the glyphs and register them; coordinates still in Pixel
+ DevicePoint aPos;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, nullptr, &pFallbackFont))
+ {
+ const auto* pFont = pFallbackFont ? pFallbackFont : pDevFont;
+
+ aCodeUnits.clear();
+
+ // tdf#66597, tdf#115117
+ //
+ // Here is how we embed textual content in PDF files, to allow for
+ // better text extraction for complex and typography-rich text.
+ //
+ // * If there is many to one or many to many mapping, use an
+ // ActualText span embedding the original string, since ToUnicode
+ // can’t handle these.
+ // * If the one glyph is used for several Unicode code points, also
+ // use ActualText since ToUnicode can map each glyph in the font
+ // only once.
+ // * Limit ActualText to single cluster at a time, since using it
+ // for whole words or sentences breaks text selection and
+ // highlighting in PDF viewers (there will be no way to tell
+ // which glyphs belong to which characters).
+ // * Keep generating (now) redundant ToUnicode entries for
+ // compatibility with old tools not supporting ActualText.
+
+ assert(pGlyph->charCount() >= 0);
+ for (int n = 0; n < pGlyph->charCount(); n++)
+ aCodeUnits.push_back(rText[pGlyph->charPos() + n]);
+
+ bool bUseActualText = false;
+
+ // If this is a start of complex cluster, use ActualText.
+ if (pGlyph->IsClusterStart())
+ bUseActualText = true;
+
+ // Or part of a complex cluster, will be handled by the ActualText
+ // of its cluster start.
+ if (pGlyph->IsInCluster())
+ assert(aCodeUnits.empty());
+
+ // A glyph can’t have more than one ToUnicode entry, use ActualText
+ // instead.
+ if (!aCodeUnits.empty() && !bUseActualText)
+ {
+ for (const auto& rSubset : m_aSubsets[pFont].m_aSubsets)
+ {
+ const auto& it = rSubset.m_aMapping.find(pGlyph->glyphId());
+ if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits)
+ {
+ bUseActualText = true;
+ aCodeUnits.clear();
+ }
+ }
+ }
+
+ assert(!aCodeUnits.empty() || bUseActualText || pGlyph->IsInCluster());
+
+ sal_Int32 nGlyphWidth = 0;
+ SalGraphics *pGraphics = GetGraphics();
+ if (pGraphics)
+ nGlyphWidth = m_aFontCache.getGlyphWidth(pFont,
+ pGlyph->glyphId(),
+ pGlyph->IsVertical(),
+ pGraphics);
+
+ sal_uInt8 nMappedGlyph;
+ sal_Int32 nMappedFontObject;
+ registerGlyph(pGlyph, pFont, aCodeUnits, nGlyphWidth, nMappedGlyph, nMappedFontObject);
+
+ int nCharPos = -1;
+ if (bUseActualText || pGlyph->IsInCluster())
+ nCharPos = pGlyph->charPos();
+
+ aGlyphs.emplace_back(Point(aPos.getX(), aPos.getY()),
+ pGlyph,
+ nGlyphWidth,
+ nMappedFontObject,
+ nMappedGlyph,
+ nCharPos);
+ }
+
+ // Avoid fill color when map mode is in pixels, the below code assumes
+ // logic map mode.
+ bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel;
+ if (m_aCurrentPDFState.m_aFont.GetFillColor() != COL_TRANSPARENT && !bPixel)
+ {
+ // PDF doesn't have a text fill color, so draw a rectangle before
+ // drawing the actual text.
+ push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
+ setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor());
+ // Avoid border around the rectangle for Writer shape text.
+ setLineColor(COL_TRANSPARENT);
+
+ // The rectangle is the bounding box of the text, but also includes
+ // ascent / descent to match the on-screen rendering.
+ // This is the top left of the text without ascent / descent.
+ DevicePoint aDrawPosition(rLayout.GetDrawPosition());
+ tools::Rectangle aRectangle(PixelToLogic(Point(aDrawPosition.getX(), aDrawPosition.getY())),
+ Size(ImplDevicePixelToLogicWidth(rLayout.GetTextWidth()), 0));
+ aRectangle.AdjustTop(-aRefDevFontMetric.GetAscent());
+ // This includes ascent / descent.
+ aRectangle.setHeight(aRefDevFontMetric.GetLineHeight());
+
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ if (pFontInstance->mnOrientation)
+ {
+ // Adapt rectangle for rotated text.
+ tools::Polygon aPolygon(aRectangle);
+ aPolygon.Rotate(PixelToLogic(Point(aDrawPosition.getX(), aDrawPosition.getY())), pFontInstance->mnOrientation);
+ drawPolygon(aPolygon);
+ }
+ else
+ drawRectangle(aRectangle);
+
+ pop();
+ }
+
+ Point aAlignOffset;
+ if ( eAlign == ALIGN_BOTTOM )
+ aAlignOffset.AdjustY( -(aRefDevFontMetric.GetDescent()) );
+ else if ( eAlign == ALIGN_TOP )
+ aAlignOffset.AdjustY(aRefDevFontMetric.GetAscent() );
+ if( aAlignOffset.X() || aAlignOffset.Y() )
+ aAlignOffset = aRotScale.transform( aAlignOffset );
+
+ /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original
+ string contained only one of the UTF16 BOMs
+ */
+ if( ! aGlyphs.empty() )
+ {
+ size_t nStart = 0;
+ size_t nEnd = 0;
+ while (nStart < aGlyphs.size())
+ {
+ while (nEnd < aGlyphs.size() && aGlyphs[nEnd].m_nCharPos == aGlyphs[nStart].m_nCharPos)
+ nEnd++;
+
+ std::vector<PDFGlyph> aRun(aGlyphs.begin() + nStart, aGlyphs.begin() + nEnd);
+
+ int nCharPos, nCharCount;
+ if (!aRun.front().m_pGlyph->IsRTLGlyph())
+ {
+ nCharPos = aRun.front().m_nCharPos;
+ nCharCount = aRun.front().m_pGlyph->charCount();
+ }
+ else
+ {
+ nCharPos = aRun.back().m_nCharPos;
+ nCharCount = aRun.back().m_pGlyph->charCount();
+ }
+
+ if (nCharPos >= 0 && nCharCount)
+ {
+ aLine.append("/Span<</ActualText<FEFF");
+ for (int i = 0; i < nCharCount; i++)
+ {
+ sal_Unicode aChar = rText[nCharPos + i];
+ appendHex(static_cast<sal_Int8>(aChar >> 8), aLine);
+ appendHex(static_cast<sal_Int8>(aChar & 255), aLine);
+ }
+ aLine.append( ">>>\nBDC\n" );
+ }
+
+ if (bVertical)
+ drawVerticalGlyphs(aRun, aLine, aAlignOffset, aRotScale, fAngle, fXScale, fSkew, nFontHeight);
+ else
+ drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, fSkew, nFontHeight, nPixelFontHeight);
+
+ if (nCharPos >= 0 && nCharCount)
+ aLine.append( "EMC\n" );
+
+ nStart = nEnd;
+ }
+ }
+
+ // end textobject
+ aLine.append( "ET\n" );
+ if( bPop )
+ aLine.append( "Q\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ // draw eventual textlines
+ FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout();
+ FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline();
+ FontLineStyle eOverline = m_aCurrentPDFState.m_aFont.GetOverline();
+ if( bTextLines &&
+ (
+ ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) ||
+ ( eOverline != LINESTYLE_NONE && eOverline != LINESTYLE_DONTKNOW ) ||
+ ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW )
+ )
+ )
+ {
+ bool bUnderlineAbove = m_aCurrentPDFState.m_aFont.IsUnderlineAbove();
+ if( m_aCurrentPDFState.m_aFont.IsWordLineMode() )
+ {
+ Point aStartPt;
+ sal_Int32 nWidth = 0;
+ nIndex = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
+ {
+ if (!pGlyph->IsSpacing())
+ {
+ if( !nWidth )
+ aStartPt = Point(aPos.getX(), aPos.getY());
+
+ nWidth += pGlyph->newWidth();
+ }
+ else if( nWidth > 0 )
+ {
+ drawTextLine( PixelToLogic( aStartPt ),
+ ImplDevicePixelToLogicWidth( nWidth ),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ nWidth = 0;
+ }
+ }
+
+ if( nWidth > 0 )
+ {
+ drawTextLine( PixelToLogic( aStartPt ),
+ ImplDevicePixelToLogicWidth( nWidth ),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+ }
+ else
+ {
+ DevicePoint aStartPt = rLayout.GetDrawPosition();
+ int nWidth = rLayout.GetTextWidth();
+ drawTextLine( PixelToLogic(Point(aStartPt.getX(), aStartPt.getY()) ),
+ ImplDevicePixelToLogicWidth( nWidth ),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+ }
+
+ // write eventual emphasis marks
+ if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) )
+ return;
+
+ tools::PolyPolygon aEmphPoly;
+ tools::Rectangle aEmphRect1;
+ tools::Rectangle aEmphRect2;
+ tools::Long nEmphYOff;
+ tools::Long nEmphWidth;
+ tools::Long nEmphHeight;
+ bool bEmphPolyLine;
+ FontEmphasisMark nEmphMark;
+
+ push( PushFlags::ALL );
+
+ aLine.setLength( 0 );
+ aLine.append( "q\n" );
+
+ nEmphMark = OutputDevice::ImplGetEmphasisMarkStyle( m_aCurrentPDFState.m_aFont );
+ if ( nEmphMark & FontEmphasisMark::PosBelow )
+ nEmphHeight = GetEmphasisDescent();
+ else
+ nEmphHeight = GetEmphasisAscent();
+ ImplGetEmphasisMark( aEmphPoly,
+ bEmphPolyLine,
+ aEmphRect1,
+ aEmphRect2,
+ nEmphYOff,
+ nEmphWidth,
+ nEmphMark,
+ ImplDevicePixelToLogicWidth(nEmphHeight) );
+ if ( bEmphPolyLine )
+ {
+ setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
+ setFillColor( COL_TRANSPARENT );
+ }
+ else
+ {
+ setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
+ setLineColor( COL_TRANSPARENT );
+ }
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ Point aOffset(0,0);
+
+ if ( nEmphMark & FontEmphasisMark::PosBelow )
+ aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + nEmphYOff );
+ else
+ aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + nEmphYOff) );
+
+ tools::Long nEmphWidth2 = nEmphWidth / 2;
+ tools::Long nEmphHeight2 = nEmphHeight / 2;
+ aOffset += Point( nEmphWidth2, nEmphHeight2 );
+
+ if ( eAlign == ALIGN_BOTTOM )
+ aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetDescent()) );
+ else if ( eAlign == ALIGN_TOP )
+ aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetAscent() );
+
+ nIndex = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
+ {
+ if (pGlyph->IsSpacing())
+ {
+ Point aAdjOffset = aOffset;
+ aAdjOffset.AdjustX((pGlyph->newWidth() - nEmphWidth) / 2 );
+ aAdjOffset = aRotScale.transform( aAdjOffset );
+
+ aAdjOffset -= Point( nEmphWidth2, nEmphHeight2 );
+
+ Point aMarkPos(aPos.getX(), aPos.getY());
+ aMarkPos += aAdjOffset;
+ aMarkPos = PixelToLogic(aMarkPos);
+ drawEmphasisMark( aMarkPos.X(), aMarkPos.Y(),
+ aEmphPoly, bEmphPolyLine,
+ aEmphRect1, aEmphRect2 );
+ }
+ }
+
+ writeBuffer( "Q\n", 2 );
+ pop();
+
+}
+
+void PDFWriterImpl::drawEmphasisMark( tools::Long nX, tools::Long nY,
+ const tools::PolyPolygon& rPolyPoly, bool bPolyLine,
+ const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 )
+{
+ // TODO: pass nWidth as width of this mark
+ // long nWidth = 0;
+
+ if ( rPolyPoly.Count() )
+ {
+ if ( bPolyLine )
+ {
+ tools::Polygon aPoly = rPolyPoly.GetObject( 0 );
+ aPoly.Move( nX, nY );
+ drawPolyLine( aPoly );
+ }
+ else
+ {
+ tools::PolyPolygon aPolyPoly = rPolyPoly;
+ aPolyPoly.Move( nX, nY );
+ drawPolyPolygon( aPolyPoly );
+ }
+ }
+
+ if ( !rRect1.IsEmpty() )
+ {
+ tools::Rectangle aRect( Point( nX+rRect1.Left(),
+ nY+rRect1.Top() ), rRect1.GetSize() );
+ drawRectangle( aRect );
+ }
+
+ if ( !rRect2.IsEmpty() )
+ {
+ tools::Rectangle aRect( Point( nX+rRect2.Left(),
+ nY+rRect2.Top() ), rRect2.GetSize() );
+
+ drawRectangle( aRect );
+ }
+}
+
+void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines )
+{
+ MARK( "drawText" );
+
+ updateGraphicsState();
+
+ // get a layout from the OutputDevice's SalGraphics
+ // this also enforces font substitution and sets the font on SalGraphics
+ const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
+ GetLayoutGlyphs( this, rText, nIndex, nLen );
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos,
+ 0, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
+ if( pLayout )
+ {
+ drawLayout( *pLayout, rText, bTextLines );
+ }
+}
+
+void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, o3tl::span<const sal_Int32> pDXArray, sal_Int32 nIndex, sal_Int32 nLen )
+{
+ MARK( "drawText with array" );
+
+ updateGraphicsState();
+
+ // get a layout from the OutputDevice's SalGraphics
+ // this also enforces font substitution and sets the font on SalGraphics
+ const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
+ GetLayoutGlyphs( this, rText, nIndex, nLen );
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray,
+ SalLayoutFlags::NONE, nullptr, layoutGlyphs );
+ if( pLayout )
+ {
+ drawLayout( *pLayout, rText, true );
+ }
+}
+
+void PDFWriterImpl::drawStretchText( const Point& rPos, sal_uLong nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen )
+{
+ MARK( "drawStretchText" );
+
+ updateGraphicsState();
+
+ // get a layout from the OutputDevice's SalGraphics
+ // this also enforces font substitution and sets the font on SalGraphics
+ const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
+ GetLayoutGlyphs( this, rText, nIndex, nLen, nWidth );
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth,
+ {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
+ if( pLayout )
+ {
+ drawLayout( *pLayout, rText, true );
+ }
+}
+
+void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle )
+{
+ tools::Long nWidth = rRect.GetWidth();
+ tools::Long nHeight = rRect.GetHeight();
+
+ if ( nWidth <= 0 || nHeight <= 0 )
+ return;
+
+ MARK( "drawText with rectangle" );
+
+ updateGraphicsState();
+
+ // clip with rectangle
+ OStringBuffer aLine;
+ aLine.append( "q " );
+ m_aPages.back().appendRect( rRect, aLine );
+ aLine.append( " W* n\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ // if disabled text is needed, put in here
+
+ Point aPos = rRect.TopLeft();
+
+ tools::Long nTextHeight = GetTextHeight();
+ sal_Int32 nMnemonicPos = -1;
+
+ OUString aStr = rOrigStr;
+ if ( nStyle & DrawTextFlags::Mnemonic )
+ aStr = OutputDevice::GetNonMnemonicString( aStr, nMnemonicPos );
+
+ // multiline text
+ if ( nStyle & DrawTextFlags::MultiLine )
+ {
+ ImplMultiTextLineInfo aMultiLineInfo;
+ sal_Int32 i;
+ sal_Int32 nFormatLines;
+
+ if ( nTextHeight )
+ {
+ vcl::DefaultTextLayout aLayout( *this );
+ OUString aLastLine;
+ OutputDevice::ImplGetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle, aLayout );
+ sal_Int32 nLines = nHeight/nTextHeight;
+ nFormatLines = aMultiLineInfo.Count();
+ if ( !nLines )
+ nLines = 1;
+ if ( nFormatLines > nLines )
+ {
+ if ( nStyle & DrawTextFlags::EndEllipsis )
+ {
+ // handle last line
+ nFormatLines = nLines-1;
+
+ ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines );
+ aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF);
+ // replace line feed by space
+ aLastLine = aLastLine.replace('\n', ' ');
+ aLastLine = GetEllipsisString( aLastLine, nWidth, nStyle );
+ nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
+ nStyle |= DrawTextFlags::Top;
+ }
+ }
+
+ // vertical alignment
+ if ( nStyle & DrawTextFlags::Bottom )
+ aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) );
+ else if ( nStyle & DrawTextFlags::VCenter )
+ aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 );
+
+ // draw all lines excluding the last
+ for ( i = 0; i < nFormatLines; i++ )
+ {
+ ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
+ if ( nStyle & DrawTextFlags::Right )
+ aPos.AdjustX(nWidth-rLineInfo.GetWidth() );
+ else if ( nStyle & DrawTextFlags::Center )
+ aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 );
+ sal_Int32 nIndex = rLineInfo.GetIndex();
+ sal_Int32 nLineLen = rLineInfo.GetLen();
+ drawText( aPos, aStr, nIndex, nLineLen );
+ // mnemonics should not appear in documents,
+ // if the need arises, put them in here
+ aPos.AdjustY(nTextHeight );
+ aPos.setX( rRect.Left() );
+ }
+
+ // output last line left adjusted since it was shortened
+ if (!aLastLine.isEmpty())
+ drawText( aPos, aLastLine, 0, aLastLine.getLength() );
+ }
+ }
+ else
+ {
+ tools::Long nTextWidth = GetTextWidth( aStr );
+
+ // Evt. Text kuerzen
+ if ( nTextWidth > nWidth )
+ {
+ if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) )
+ {
+ aStr = GetEllipsisString( aStr, nWidth, nStyle );
+ nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
+ nStyle |= DrawTextFlags::Left;
+ nTextWidth = GetTextWidth( aStr );
+ }
+ }
+
+ // vertical alignment
+ if ( nStyle & DrawTextFlags::Right )
+ aPos.AdjustX(nWidth-nTextWidth );
+ else if ( nStyle & DrawTextFlags::Center )
+ aPos.AdjustX((nWidth-nTextWidth)/2 );
+
+ if ( nStyle & DrawTextFlags::Bottom )
+ aPos.AdjustY(nHeight-nTextHeight );
+ else if ( nStyle & DrawTextFlags::VCenter )
+ aPos.AdjustY((nHeight-nTextHeight)/2 );
+
+ // mnemonics should be inserted here if the need arises
+
+ // draw the actual text
+ drawText( aPos, aStr, 0, aStr.getLength() );
+ }
+
+ // reset clip region to original value
+ aLine.setLength( 0 );
+ aLine.append( "Q\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop )
+{
+ MARK( "drawLine" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
+ return;
+
+ OStringBuffer aLine;
+ m_aPages.back().appendPoint( rStart, aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendPoint( rStop, aLine );
+ aLine.append( " l S\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
+{
+ MARK( "drawLine with LineInfo" );
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
+ return;
+
+ if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 )
+ {
+ drawLine( rStart, rStop );
+ return;
+ }
+
+ OStringBuffer aLine;
+
+ aLine.append( "q " );
+ if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
+ {
+ m_aPages.back().appendPoint( rStart, aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendPoint( rStop, aLine );
+ aLine.append( " l S Q\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+ }
+ else
+ {
+ PDFWriter::ExtLineInfo aInfo;
+ convertLineInfoToExtLineInfo( rInfo, aInfo );
+ Point aPolyPoints[2] = { rStart, rStop };
+ tools::Polygon aPoly( 2, aPolyPoints );
+ drawPolyLine( aPoly, aInfo );
+ }
+}
+
+#define HCONV( x ) ImplDevicePixelToLogicHeight( x )
+
+void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
+{
+ // note: units in pFontInstance are ref device pixel
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ tools::Long nLineHeight = 0;
+ tools::Long nLinePos = 0;
+
+ appendStrokingColor( aColor, aLine );
+ aLine.append( "\n" );
+
+ if ( bIsAbove )
+ {
+ if ( !pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() )
+ ImplInitAboveTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset() );
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetWavelineUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineOffset() );
+ }
+ if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
+ nLineHeight = 3;
+
+ tools::Long nLineWidth = GetDPIX()/450;
+ if ( ! nLineWidth )
+ nLineWidth = 1;
+
+ if ( eTextLine == LINESTYLE_BOLDWAVE )
+ nLineWidth = 3*nLineWidth;
+
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineWidth), aLine );
+ aLine.append( " w " );
+
+ if ( eTextLine == LINESTYLE_DOUBLEWAVE )
+ {
+ tools::Long nOrgLineHeight = nLineHeight;
+ nLineHeight /= 3;
+ if ( nLineHeight < 2 )
+ {
+ if ( nOrgLineHeight > 1 )
+ nLineHeight = 2;
+ else
+ nLineHeight = 1;
+ }
+ tools::Long nLineDY = nOrgLineHeight-(nLineHeight*2);
+ if ( nLineDY < nLineWidth )
+ nLineDY = nLineWidth;
+ tools::Long nLineDY2 = nLineDY/2;
+ if ( !nLineDY2 )
+ nLineDY2 = 1;
+
+ nLinePos -= nLineWidth-nLineDY2;
+
+ m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
+
+ nLinePos += nLineWidth+nLineDY;
+ m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
+ }
+ else
+ {
+ if ( eTextLine != LINESTYLE_BOLDWAVE )
+ nLinePos -= nLineWidth/2;
+ m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine );
+ }
+}
+
+void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
+{
+ // note: units in pFontInstance are ref device pixel
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ tools::Long nLineHeight = 0;
+ tools::Long nLinePos = 0;
+ tools::Long nLinePos2 = 0;
+
+ if ( eTextLine > LINESTYLE_BOLDWAVE )
+ eTextLine = LINESTYLE_SINGLE;
+
+ switch ( eTextLine )
+ {
+ case LINESTYLE_SINGLE:
+ case LINESTYLE_DOTTED:
+ case LINESTYLE_DASH:
+ case LINESTYLE_LONGDASH:
+ case LINESTYLE_DASHDOT:
+ case LINESTYLE_DASHDOTDOT:
+ if ( bIsAbove )
+ {
+ if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() )
+ ImplInitAboveTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineOffset() );
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetUnderlineOffset() );
+ }
+ break;
+ case LINESTYLE_BOLD:
+ case LINESTYLE_BOLDDOTTED:
+ case LINESTYLE_BOLDDASH:
+ case LINESTYLE_BOLDLONGDASH:
+ case LINESTYLE_BOLDDASHDOT:
+ case LINESTYLE_BOLDDASHDOTDOT:
+ if ( bIsAbove )
+ {
+ if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() )
+ ImplInitAboveTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset() );
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineOffset() );
+ nLinePos += nLineHeight/2;
+ }
+ break;
+ case LINESTYLE_DOUBLE:
+ if ( bIsAbove )
+ {
+ if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() )
+ ImplInitAboveTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1() );
+ nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2() );
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1() );
+ nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2() );
+ }
+ break;
+ default:
+ break;
+ }
+
+ if ( !nLineHeight )
+ return;
+
+ // outline attribute ?
+ if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
+ {
+ appendStrokingColor(aColor, aLine); // stroke with text color
+ aLine.append( " " );
+ appendNonStrokingColor(COL_WHITE, aLine); // fill with white
+ aLine.append( "\n" );
+ aLine.append( "0.25 w \n" ); // same line thickness as in drawLayout
+
+ // draw rectangle instead
+ aLine.append( "0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos * 1.5), aLine );
+ aLine.append( " " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
+ aLine.append( " re h B\n" );
+ return;
+ }
+
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
+ aLine.append( " w " );
+ appendStrokingColor( aColor, aLine );
+ aLine.append( "\n" );
+
+ switch ( eTextLine )
+ {
+ case LINESTYLE_DOTTED:
+ case LINESTYLE_BOLDDOTTED:
+ aLine.append( "[ " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
+ aLine.append( " ] 0 d\n" );
+ break;
+ case LINESTYLE_DASH:
+ case LINESTYLE_LONGDASH:
+ case LINESTYLE_BOLDDASH:
+ case LINESTYLE_BOLDLONGDASH:
+ {
+ sal_Int32 nDashLength = 4*nLineHeight;
+ sal_Int32 nVoidLength = 2*nLineHeight;
+ if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) )
+ nDashLength = 8*nLineHeight;
+
+ aLine.append( "[ " );
+ m_aPages.back().appendMappedLength( nDashLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( " ] 0 d\n" );
+ }
+ break;
+ case LINESTYLE_DASHDOT:
+ case LINESTYLE_BOLDDASHDOT:
+ {
+ sal_Int32 nDashLength = 4*nLineHeight;
+ sal_Int32 nVoidLength = 2*nLineHeight;
+ aLine.append( "[ " );
+ m_aPages.back().appendMappedLength( nDashLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( " ] 0 d\n" );
+ }
+ break;
+ case LINESTYLE_DASHDOTDOT:
+ case LINESTYLE_BOLDDASHDOTDOT:
+ {
+ sal_Int32 nDashLength = 4*nLineHeight;
+ sal_Int32 nVoidLength = 2*nLineHeight;
+ aLine.append( "[ " );
+ m_aPages.back().appendMappedLength( nDashLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( " ] 0 d\n" );
+ }
+ break;
+ default:
+ break;
+ }
+
+ aLine.append( "0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
+ aLine.append( " l S\n" );
+ if ( eTextLine == LINESTYLE_DOUBLE )
+ {
+ aLine.append( "0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
+ aLine.append( " l S\n" );
+ }
+
+}
+
+void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, tools::Long nWidth, FontStrikeout eStrikeout, Color aColor )
+{
+ // note: units in pFontInstance are ref device pixel
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ tools::Long nLineHeight = 0;
+ tools::Long nLinePos = 0;
+ tools::Long nLinePos2 = 0;
+
+ if ( eStrikeout > STRIKEOUT_X )
+ eStrikeout = STRIKEOUT_SINGLE;
+
+ switch ( eStrikeout )
+ {
+ case STRIKEOUT_SINGLE:
+ if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetStrikeoutSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetStrikeoutOffset() );
+ break;
+ case STRIKEOUT_BOLD:
+ if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutOffset() );
+ break;
+ case STRIKEOUT_DOUBLE:
+ if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1() );
+ nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2() );
+ break;
+ default:
+ break;
+ }
+
+ if ( !nLineHeight )
+ return;
+
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
+ aLine.append( " w " );
+ appendStrokingColor( aColor, aLine );
+ aLine.append( "\n" );
+
+ aLine.append( "0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
+ aLine.append( " l S\n" );
+
+ if ( eStrikeout == STRIKEOUT_DOUBLE )
+ {
+ aLine.append( "0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
+ aLine.append( " l S\n" );
+ }
+
+}
+
+void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout )
+{
+ //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
+ //to tweak this
+
+ OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? OUString( "/" ) : OUString( "X" );
+ OUString aStrikeout = aStrikeoutChar;
+ while( GetTextWidth( aStrikeout ) < nWidth )
+ aStrikeout += aStrikeout;
+
+ // do not get broader than nWidth modulo 1 character
+ while( GetTextWidth( aStrikeout ) >= nWidth )
+ aStrikeout = aStrikeout.replaceAt( 0, 1, u"" );
+ aStrikeout += aStrikeoutChar;
+ bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow();
+ if ( bShadow )
+ {
+ Font aFont = m_aCurrentPDFState.m_aFont;
+ aFont.SetShadow( false );
+ setFont( aFont );
+ updateGraphicsState();
+ }
+
+ // strikeout string is left aligned non-CTL text
+ vcl::text::ComplexTextLayoutFlags nOrigTLM = GetLayoutMode();
+ SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiStrong);
+
+ push( PushFlags::CLIPREGION );
+ FontMetric aRefDevFontMetric = GetFontMetric();
+ tools::Rectangle aRect;
+ aRect.SetLeft( rPos.X() );
+ aRect.SetRight( aRect.Left()+nWidth );
+ aRect.SetBottom( rPos.Y()+aRefDevFontMetric.GetDescent() );
+ aRect.SetTop( rPos.Y()-aRefDevFontMetric.GetAscent() );
+
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ if (pFontInstance->mnOrientation)
+ {
+ tools::Polygon aPoly( aRect );
+ aPoly.Rotate( rPos, pFontInstance->mnOrientation);
+ aRect = aPoly.GetBoundRect();
+ }
+
+ intersectClipRegion( aRect );
+ drawText( rPos, aStrikeout, 0, aStrikeout.getLength(), false );
+ pop();
+
+ SetLayoutMode( nOrigTLM );
+
+ if ( bShadow )
+ {
+ Font aFont = m_aCurrentPDFState.m_aFont;
+ aFont.SetShadow( true );
+ setFont( aFont );
+ updateGraphicsState();
+ }
+}
+
+void PDFWriterImpl::drawTextLine( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove )
+{
+ if ( !nWidth ||
+ ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) &&
+ ((eUnderline == LINESTYLE_NONE)||(eUnderline == LINESTYLE_DONTKNOW)) &&
+ ((eOverline == LINESTYLE_NONE)||(eOverline == LINESTYLE_DONTKNOW)) ) )
+ return;
+
+ MARK( "drawTextLine" );
+ updateGraphicsState();
+
+ // note: units in pFontInstance are ref device pixel
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ Color aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor;
+ Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
+ Color aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor();
+ bool bStrikeoutDone = false;
+ bool bUnderlineDone = false;
+ bool bOverlineDone = false;
+
+ if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) )
+ {
+ drawStrikeoutChar( rPos, nWidth, eStrikeout );
+ bStrikeoutDone = true;
+ }
+
+ Point aPos( rPos );
+ TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
+ if( eAlign == ALIGN_TOP )
+ aPos.AdjustY(HCONV( pFontInstance->mxFontMetric->GetAscent() ));
+ else if( eAlign == ALIGN_BOTTOM )
+ aPos.AdjustY( -HCONV( pFontInstance->mxFontMetric->GetDescent() ) );
+
+ OStringBuffer aLine( 512 );
+ // save GS
+ aLine.append( "q " );
+
+ // rotate and translate matrix
+ double fAngle = toRadians(m_aCurrentPDFState.m_aFont.GetOrientation());
+ Matrix3 aMat;
+ aMat.rotate( fAngle );
+ aMat.translate( aPos.X(), aPos.Y() );
+ m_aPages.back().appendMatrix3(aMat, aLine);
+ aLine.append( " cm\n" );
+
+ if ( aUnderlineColor.IsTransparent() )
+ aUnderlineColor = aStrikeoutColor;
+
+ if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
+ (eUnderline == LINESTYLE_WAVE) ||
+ (eUnderline == LINESTYLE_DOUBLEWAVE) ||
+ (eUnderline == LINESTYLE_BOLDWAVE) )
+ {
+ drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
+ bUnderlineDone = true;
+ }
+
+ if ( (eOverline == LINESTYLE_SMALLWAVE) ||
+ (eOverline == LINESTYLE_WAVE) ||
+ (eOverline == LINESTYLE_DOUBLEWAVE) ||
+ (eOverline == LINESTYLE_BOLDWAVE) )
+ {
+ drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
+ bOverlineDone = true;
+ }
+
+ if ( !bUnderlineDone )
+ {
+ drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
+ }
+
+ if ( !bOverlineDone )
+ {
+ drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
+ }
+
+ if ( !bStrikeoutDone )
+ {
+ drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor );
+ }
+
+ aLine.append( "Q\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly )
+{
+ MARK( "drawPolygon" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ int nPoints = rPoly.GetSize();
+ OStringBuffer aLine( 20 * nPoints );
+ m_aPages.back().appendPolygon( rPoly, aLine );
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( "B*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "S\n" );
+ else
+ aLine.append( "f*\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
+{
+ MARK( "drawPolyPolygon" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ int nPolygons = rPolyPoly.Count();
+
+ OStringBuffer aLine( 40 * nPolygons );
+ m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( "B*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "S\n" );
+ else
+ aLine.append( "f*\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent )
+{
+ SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
+ nTransparentPercent = nTransparentPercent % 100;
+
+ MARK( "drawTransparent" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
+ {
+ m_aErrors.insert( m_bIsPDF_A1 ?
+ PDFWriter::Warning_Transparency_Omitted_PDFA :
+ PDFWriter::Warning_Transparency_Omitted_PDF13 );
+
+ drawPolyPolygon( rPolyPoly );
+ return;
+ }
+
+ // create XObject
+ m_aTransparentObjects.emplace_back( );
+ // FIXME: polygons with beziers may yield incorrect bound rect
+ m_aTransparentObjects.back().m_aBoundRect = rPolyPoly.GetBoundRect();
+ // convert rectangle to default user space
+ m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
+ m_aTransparentObjects.back().m_nObject = createObject();
+ m_aTransparentObjects.back().m_nExtGStateObject = createObject();
+ m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0;
+ m_aTransparentObjects.back().m_pContentStream.reset(new SvMemoryStream( 256, 256 ));
+ // create XObject's content stream
+ OStringBuffer aContent( 256 );
+ m_aPages.back().appendPolyPolygon( rPolyPoly, aContent );
+ if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT &&
+ m_aCurrentPDFState.m_aFillColor != COL_TRANSPARENT )
+ aContent.append( " B*\n" );
+ else if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT )
+ aContent.append( " S\n" );
+ else
+ aContent.append( " f*\n" );
+ m_aTransparentObjects.back().m_pContentStream->WriteBytes(
+ aContent.getStr(), aContent.getLength() );
+
+ OStringBuffer aObjName( 16 );
+ aObjName.append( "Tr" );
+ aObjName.append( m_aTransparentObjects.back().m_nObject );
+ OString aTrName( aObjName.makeStringAndClear() );
+ aObjName.append( "EGS" );
+ aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
+ OString aExtName( aObjName.makeStringAndClear() );
+
+ OString aLine =
+ // insert XObject
+ "q /" +
+ aExtName +
+ " gs /" +
+ aTrName +
+ " Do Q\n";
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
+ pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
+}
+
+void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject )
+{
+ if( nObject < 0 )
+ return;
+
+ switch( eKind )
+ {
+ case ResourceKind::XObject:
+ m_aGlobalResourceDict.m_aXObjects[ rResource ] = nObject;
+ if( ! m_aOutputStreams.empty() )
+ m_aOutputStreams.front().m_aResourceDict.m_aXObjects[ rResource ] = nObject;
+ break;
+ case ResourceKind::ExtGState:
+ m_aGlobalResourceDict.m_aExtGStates[ rResource ] = nObject;
+ if( ! m_aOutputStreams.empty() )
+ m_aOutputStreams.front().m_aResourceDict.m_aExtGStates[ rResource ] = nObject;
+ break;
+ case ResourceKind::Shading:
+ m_aGlobalResourceDict.m_aShadings[ rResource ] = nObject;
+ if( ! m_aOutputStreams.empty() )
+ m_aOutputStreams.front().m_aResourceDict.m_aShadings[ rResource ] = nObject;
+ break;
+ case ResourceKind::Pattern:
+ m_aGlobalResourceDict.m_aPatterns[ rResource ] = nObject;
+ if( ! m_aOutputStreams.empty() )
+ m_aOutputStreams.front().m_aResourceDict.m_aPatterns[ rResource ] = nObject;
+ break;
+ }
+}
+
+void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect )
+{
+ push( PushFlags::ALL );
+
+ // force reemitting clip region inside the new stream, and
+ // prevent emitting an unbalanced "Q" at the start
+ clearClipRegion();
+ // this is needed to point m_aCurrentPDFState at the pushed state
+ // ... but it's pointless to actually write into the "outer" stream here!
+ updateGraphicsState(Mode::NOWRITE);
+
+ m_aOutputStreams.push_front( StreamRedirect() );
+ m_aOutputStreams.front().m_pStream = pStream;
+ m_aOutputStreams.front().m_aMapMode = m_aMapMode;
+
+ if( !rTargetRect.IsEmpty() )
+ {
+ m_aOutputStreams.front().m_aTargetRect =
+ lcl_convert( m_aGraphicsStack.front().m_aMapMode,
+ m_aMapMode,
+ this,
+ rTargetRect );
+ Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft();
+ tools::Long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight());
+ aDelta.setY( -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()) );
+ m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta );
+ }
+
+ // setup graphics state for independent object stream
+
+ // force reemitting colors
+ m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
+ m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
+}
+
+SvStream* PDFWriterImpl::endRedirect()
+{
+ SvStream* pStream = nullptr;
+ if( ! m_aOutputStreams.empty() )
+ {
+ pStream = m_aOutputStreams.front().m_pStream;
+ m_aMapMode = m_aOutputStreams.front().m_aMapMode;
+ m_aOutputStreams.pop_front();
+ }
+
+ pop();
+
+ m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
+ m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
+
+ // needed after pop() to set m_aCurrentPDFState
+ updateGraphicsState(Mode::NOWRITE);
+
+ return pStream;
+}
+
+void PDFWriterImpl::beginTransparencyGroup()
+{
+ updateGraphicsState();
+ if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
+ beginRedirect( new SvMemoryStream( 1024, 1024 ), tools::Rectangle() );
+}
+
+void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent )
+{
+ SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
+ nTransparentPercent = nTransparentPercent % 100;
+
+ if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
+ return;
+
+ // create XObject
+ m_aTransparentObjects.emplace_back( );
+ m_aTransparentObjects.back().m_aBoundRect = rBoundingBox;
+ // convert rectangle to default user space
+ m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
+ m_aTransparentObjects.back().m_nObject = createObject();
+ m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0;
+ // get XObject's content stream
+ m_aTransparentObjects.back().m_pContentStream.reset( static_cast<SvMemoryStream*>(endRedirect()) );
+ m_aTransparentObjects.back().m_nExtGStateObject = createObject();
+
+ OStringBuffer aObjName( 16 );
+ aObjName.append( "Tr" );
+ aObjName.append( m_aTransparentObjects.back().m_nObject );
+ OString aTrName( aObjName.makeStringAndClear() );
+ aObjName.append( "EGS" );
+ aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
+ OString aExtName( aObjName.makeStringAndClear() );
+
+ OString aLine =
+ // insert XObject
+ "q /" +
+ aExtName +
+ " gs /" +
+ aTrName +
+ " Do Q\n";
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
+ pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
+
+}
+
+void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect )
+{
+ MARK( "drawRectangle" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ OStringBuffer aLine( 40 );
+ m_aPages.back().appendRect( rRect, aLine );
+
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( " B*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( " S\n" );
+ else
+ aLine.append( " f*\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
+{
+ MARK( "drawRectangle with rounded edges" );
+
+ if( !nHorzRound && !nVertRound )
+ drawRectangle( rRect );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ if( nHorzRound > static_cast<sal_uInt32>(rRect.GetWidth())/2 )
+ nHorzRound = rRect.GetWidth()/2;
+ if( nVertRound > static_cast<sal_uInt32>(rRect.GetHeight())/2 )
+ nVertRound = rRect.GetHeight()/2;
+
+ Point aPoints[16];
+ const double kappa = 0.5522847498;
+ const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(nHorzRound))+0.5);
+ const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(nVertRound))+0.5);
+
+ aPoints[1] = Point( rRect.Left() + nHorzRound, rRect.Top() );
+ aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
+ aPoints[2] = Point( rRect.Right()+1 - nHorzRound, aPoints[1].Y() );
+ aPoints[3] = Point( aPoints[2].X()+kx, aPoints[2].Y() );
+
+ aPoints[5] = Point( rRect.Right()+1, rRect.Top()+nVertRound );
+ aPoints[4] = Point( aPoints[5].X(), aPoints[5].Y()-ky );
+ aPoints[6] = Point( aPoints[5].X(), rRect.Bottom()+1 - nVertRound );
+ aPoints[7] = Point( aPoints[6].X(), aPoints[6].Y()+ky );
+
+ aPoints[9] = Point( rRect.Right()+1-nHorzRound, rRect.Bottom()+1 );
+ aPoints[8] = Point( aPoints[9].X()+kx, aPoints[9].Y() );
+ aPoints[10] = Point( rRect.Left() + nHorzRound, aPoints[9].Y() );
+ aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() );
+
+ aPoints[13] = Point( rRect.Left(), rRect.Bottom()+1-nVertRound );
+ aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky );
+ aPoints[14] = Point( rRect.Left(), rRect.Top()+nVertRound );
+ aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky );
+
+ OStringBuffer aLine( 80 );
+ m_aPages.back().appendPoint( aPoints[1], aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendPoint( aPoints[2], aLine );
+ aLine.append( " l " );
+ m_aPages.back().appendPoint( aPoints[3], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[4], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[5], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[6], aLine );
+ aLine.append( " l " );
+ m_aPages.back().appendPoint( aPoints[7], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[8], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[9], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[10], aLine );
+ aLine.append( " l " );
+ m_aPages.back().appendPoint( aPoints[11], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[12], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[13], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[14], aLine );
+ aLine.append( " l " );
+ m_aPages.back().appendPoint( aPoints[15], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[0], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[1], aLine );
+ aLine.append( " c " );
+
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( "b*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "s\n" );
+ else
+ aLine.append( "f*\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect )
+{
+ MARK( "drawEllipse" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ Point aPoints[12];
+ const double kappa = 0.5522847498;
+ const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetWidth())/2.0)+0.5);
+ const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetHeight())/2.0)+0.5);
+
+ aPoints[1] = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Top() );
+ aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
+ aPoints[2] = Point( aPoints[1].X() + kx, aPoints[1].Y() );
+
+ aPoints[4] = Point( rRect.Right()+1, rRect.Top() + rRect.GetHeight()/2 );
+ aPoints[3] = Point( aPoints[4].X(), aPoints[4].Y() - ky );
+ aPoints[5] = Point( aPoints[4].X(), aPoints[4].Y() + ky );
+
+ aPoints[7] = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Bottom()+1 );
+ aPoints[6] = Point( aPoints[7].X() + kx, aPoints[7].Y() );
+ aPoints[8] = Point( aPoints[7].X() - kx, aPoints[7].Y() );
+
+ aPoints[10] = Point( rRect.Left(), rRect.Top() + rRect.GetHeight()/2 );
+ aPoints[9] = Point( aPoints[10].X(), aPoints[10].Y() + ky );
+ aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky );
+
+ OStringBuffer aLine( 80 );
+ m_aPages.back().appendPoint( aPoints[1], aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendPoint( aPoints[2], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[3], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[4], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[5], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[6], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[7], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[8], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[9], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[10], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[11], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[0], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[1], aLine );
+ aLine.append( " c " );
+
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( "b*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "s\n" );
+ else
+ aLine.append( "f*\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint )
+{
+ Point aOrigin((rRect.Left()+rRect.Right()+1)/2,
+ (rRect.Top()+rRect.Bottom()+1)/2);
+ Point aPoint = rPoint - aOrigin;
+
+ double fX = static_cast<double>(aPoint.X());
+ double fY = static_cast<double>(-aPoint.Y());
+
+ if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0))
+ throw o3tl::divide_by_zero();
+
+ if( rRect.GetWidth() > rRect.GetHeight() )
+ fY = fY*(static_cast<double>(rRect.GetWidth())/static_cast<double>(rRect.GetHeight()));
+ else if( rRect.GetHeight() > rRect.GetWidth() )
+ fX = fX*(static_cast<double>(rRect.GetHeight())/static_cast<double>(rRect.GetWidth()));
+ return atan2( fY, fX );
+}
+
+void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord )
+{
+ MARK( "drawArc" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ // calculate start and stop angles
+ const double fStartAngle = calcAngle( rRect, rStart );
+ double fStopAngle = calcAngle( rRect, rStop );
+ while( fStopAngle < fStartAngle )
+ fStopAngle += 2.0*M_PI;
+ const int nFragments = static_cast<int>((fStopAngle-fStartAngle)/(M_PI/2.0))+1;
+ const double fFragmentDelta = (fStopAngle-fStartAngle)/static_cast<double>(nFragments);
+ const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0);
+ const double halfWidth = static_cast<double>(rRect.GetWidth())/2.0;
+ const double halfHeight = static_cast<double>(rRect.GetHeight())/2.0;
+
+ const Point aCenter( (rRect.Left()+rRect.Right()+1)/2,
+ (rRect.Top()+rRect.Bottom()+1)/2 );
+
+ OStringBuffer aLine( 30*nFragments );
+ Point aPoint( static_cast<int>(halfWidth * cos(fStartAngle) ),
+ -static_cast<int>(halfHeight * sin(fStartAngle) ) );
+ aPoint += aCenter;
+ m_aPages.back().appendPoint( aPoint, aLine );
+ aLine.append( " m " );
+ if( !basegfx::fTools::equal(fStartAngle, fStopAngle) )
+ {
+ for( int i = 0; i < nFragments; i++ )
+ {
+ const double fStartFragment = fStartAngle + static_cast<double>(i)*fFragmentDelta;
+ const double fStopFragment = fStartFragment + fFragmentDelta;
+ aPoint = Point( static_cast<int>(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ),
+ -static_cast<int>(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) );
+ aPoint += aCenter;
+ m_aPages.back().appendPoint( aPoint, aLine );
+ aLine.append( ' ' );
+
+ aPoint = Point( static_cast<int>(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ),
+ -static_cast<int>(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) );
+ aPoint += aCenter;
+ m_aPages.back().appendPoint( aPoint, aLine );
+ aLine.append( ' ' );
+
+ aPoint = Point( static_cast<int>(halfWidth * cos(fStopFragment) ),
+ -static_cast<int>(halfHeight * sin(fStopFragment) ) );
+ aPoint += aCenter;
+ m_aPages.back().appendPoint( aPoint, aLine );
+ aLine.append( " c\n" );
+ }
+ }
+ if( bWithChord || bWithPie )
+ {
+ if( bWithPie )
+ {
+ m_aPages.back().appendPoint( aCenter, aLine );
+ aLine.append( " l " );
+ }
+ aLine.append( "h " );
+ }
+ if( ! bWithChord && ! bWithPie )
+ aLine.append( "S\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( "B*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "S\n" );
+ else
+ aLine.append( "f*\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly )
+{
+ MARK( "drawPolyLine" );
+
+ sal_uInt16 nPoints = rPoly.GetSize();
+ if( nPoints < 2 )
+ return;
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
+ return;
+
+ OStringBuffer aLine( 20 * nPoints );
+ m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] );
+ aLine.append( "S\n" );
+
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
+{
+ MARK( "drawPolyLine with LineInfo" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
+ return;
+
+ OStringBuffer aLine;
+ aLine.append( "q " );
+ if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
+ {
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+ drawPolyLine( rPoly );
+ writeBuffer( "Q\n", 2 );
+ }
+ else
+ {
+ PDFWriter::ExtLineInfo aInfo;
+ convertLineInfoToExtLineInfo( rInfo, aInfo );
+ drawPolyLine( rPoly, aInfo );
+ }
+}
+
+void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut )
+{
+ SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" );
+ rOut.m_fLineWidth = rIn.GetWidth();
+ rOut.m_fTransparency = 0.0;
+ rOut.m_eCap = PDFWriter::capButt;
+ rOut.m_eJoin = PDFWriter::joinMiter;
+ rOut.m_fMiterLimit = 10;
+ rOut.m_aDashArray = rIn.GetDotDashArray();
+
+ // add LineJoin
+ switch(rIn.GetLineJoin())
+ {
+ case basegfx::B2DLineJoin::Bevel :
+ {
+ rOut.m_eJoin = PDFWriter::joinBevel;
+ break;
+ }
+ // Pdf has no 'none' lineJoin, default is miter
+ case basegfx::B2DLineJoin::NONE :
+ case basegfx::B2DLineJoin::Miter :
+ {
+ rOut.m_eJoin = PDFWriter::joinMiter;
+ break;
+ }
+ case basegfx::B2DLineJoin::Round :
+ {
+ rOut.m_eJoin = PDFWriter::joinRound;
+ break;
+ }
+ }
+
+ // add LineCap
+ switch(rIn.GetLineCap())
+ {
+ default: /* css::drawing::LineCap_BUTT */
+ {
+ rOut.m_eCap = PDFWriter::capButt;
+ break;
+ }
+ case css::drawing::LineCap_ROUND:
+ {
+ rOut.m_eCap = PDFWriter::capRound;
+ break;
+ }
+ case css::drawing::LineCap_SQUARE:
+ {
+ rOut.m_eCap = PDFWriter::capSquare;
+ break;
+ }
+ }
+}
+
+void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo )
+{
+ MARK( "drawPolyLine with ExtLineInfo" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
+ return;
+
+ if( rInfo.m_fTransparency >= 1.0 )
+ return;
+
+ if( rInfo.m_fTransparency != 0.0 )
+ beginTransparencyGroup();
+
+ OStringBuffer aLine;
+ aLine.append( "q " );
+ m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine );
+ aLine.append( " w" );
+ if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader
+ {
+ switch( rInfo.m_eCap )
+ {
+ default:
+ case PDFWriter::capButt: aLine.append( " 0 J" );break;
+ case PDFWriter::capRound: aLine.append( " 1 J" );break;
+ case PDFWriter::capSquare: aLine.append( " 2 J" );break;
+ }
+ switch( rInfo.m_eJoin )
+ {
+ default:
+ case PDFWriter::joinMiter:
+ {
+ double fLimit = rInfo.m_fMiterLimit;
+ if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit )
+ fLimit = fLimit / rInfo.m_fLineWidth;
+ if( fLimit < 1.0 )
+ fLimit = 1.0;
+ aLine.append( " 0 j " );
+ appendDouble( fLimit, aLine );
+ aLine.append( " M" );
+ }
+ break;
+ case PDFWriter::joinRound: aLine.append( " 1 j" );break;
+ case PDFWriter::joinBevel: aLine.append( " 2 j" );break;
+ }
+ if( !rInfo.m_aDashArray.empty() )
+ {
+ aLine.append( " [ " );
+ for (auto const& dash : rInfo.m_aDashArray)
+ {
+ m_aPages.back().appendMappedLength( dash, aLine );
+ aLine.append( ' ' );
+ }
+ aLine.append( "] 0 d" );
+ }
+ aLine.append( "\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+ drawPolyLine( rPoly );
+ }
+ else
+ {
+ basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon());
+ basegfx::B2DPolyPolygon aPolyPoly;
+
+ basegfx::utils::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly);
+
+ // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments.
+ // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality)
+ // this line needs to be removed and the loop below adapted accordingly
+ aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly);
+
+ const sal_uInt32 nPolygonCount(aPolyPoly.count());
+
+ for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ )
+ {
+ aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " );
+ aPoly = aPolyPoly.getB2DPolygon( nPoly );
+ const sal_uInt32 nPointCount(aPoly.count());
+
+ if(nPointCount)
+ {
+ const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1);
+ basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ if( a > 0 )
+ aLine.append( " " );
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex));
+
+ m_aPages.back().appendPoint( Point( FRound(aCurrent.getX()),
+ FRound(aCurrent.getY()) ),
+ aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendPoint( Point( FRound(aNext.getX()),
+ FRound(aNext.getY()) ),
+ aLine );
+ aLine.append( " l" );
+
+ // prepare next edge
+ aCurrent = aNext;
+ }
+ }
+ }
+ aLine.append( " S " );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+ }
+ writeBuffer( "Q\n", 2 );
+
+ if( rInfo.m_fTransparency == 0.0 )
+ return;
+
+ // FIXME: actually this may be incorrect with bezier polygons
+ tools::Rectangle aBoundRect( rPoly.GetBoundRect() );
+ // avoid clipping with thick lines
+ if( rInfo.m_fLineWidth > 0.0 )
+ {
+ sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth);
+ aBoundRect.AdjustTop( -nLW );
+ aBoundRect.AdjustLeft( -nLW );
+ aBoundRect.AdjustRight(nLW );
+ aBoundRect.AdjustBottom(nLW );
+ }
+ endTransparencyGroup( aBoundRect, static_cast<sal_uInt16>(100.0*rInfo.m_fTransparency) );
+}
+
+void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor )
+{
+ MARK( "drawPixel" );
+
+ Color aColor = ( rColor == COL_TRANSPARENT ? m_aGraphicsStack.front().m_aLineColor : rColor );
+
+ if( aColor == COL_TRANSPARENT )
+ return;
+
+ // pixels are drawn in line color, so have to set
+ // the nonstroking color to line color
+ Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
+ setFillColor( aColor );
+
+ updateGraphicsState();
+
+ OStringBuffer aLine( 20 );
+ m_aPages.back().appendPoint( rPoint, aLine );
+ aLine.append( ' ' );
+ appendDouble( 1.0/double(GetDPIX()), aLine );
+ aLine.append( ' ' );
+ appendDouble( 1.0/double(GetDPIY()), aLine );
+ aLine.append( " re f\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ setFillColor( aOldFillColor );
+}
+
+void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject )
+{
+ CHECK_RETURN2( updateObject( rObject.m_nObject ) );
+
+ bool bFlateFilter = compressStream( rObject.m_pContentStream.get() );
+ sal_uInt64 nSize = rObject.m_pContentStream->TellEnd();
+ rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN );
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::writeTransparentObject" );
+ }
+ OStringBuffer aLine( 512 );
+ CHECK_RETURN2( updateObject( rObject.m_nObject ) );
+ aLine.append( rObject.m_nObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/XObject\n"
+ "/Subtype/Form\n"
+ "/BBox[ " );
+ appendFixedInt( rObject.m_aBoundRect.Left(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rObject.m_aBoundRect.Top(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rObject.m_aBoundRect.Right(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine );
+ aLine.append( " ]\n" );
+ if( ! m_bIsPDF_A1 )
+ {
+ // 7.8.3 Resource dicts are required for content streams
+ aLine.append( "/Resources " );
+ aLine.append( getResourceDictObj() );
+ aLine.append( " 0 R\n" );
+
+ aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" );
+ }
+
+ aLine.append( "/Length " );
+ aLine.append( static_cast<sal_Int32>(nSize) );
+ aLine.append( "\n" );
+ if( bFlateFilter )
+ aLine.append( "/Filter/FlateDecode\n" );
+ aLine.append( ">>\n"
+ "stream\n" );
+ CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ checkAndEnableStreamEncryption( rObject.m_nObject );
+ CHECK_RETURN2( writeBuffer( rObject.m_pContentStream->GetData(), nSize ) );
+ disableStreamEncryption();
+ aLine.setLength( 0 );
+ aLine.append( "\n"
+ "endstream\n"
+ "endobj\n\n" );
+ CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ // write ExtGState dict for this XObject
+ aLine.setLength( 0 );
+ aLine.append( rObject.m_nExtGStateObject );
+ aLine.append( " 0 obj\n"
+ "<<" );
+
+ if( m_bIsPDF_A1 )
+ {
+ aLine.append( "/CA 1.0/ca 1.0" );
+ m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
+ }
+ else
+ {
+ aLine.append( "/CA " );
+ appendDouble( rObject.m_fAlpha, aLine );
+ aLine.append( "\n"
+ " /ca " );
+ appendDouble( rObject.m_fAlpha, aLine );
+ }
+ aLine.append( "\n" );
+
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ CHECK_RETURN2( updateObject( rObject.m_nExtGStateObject ) );
+ CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+}
+
+bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject )
+{
+ // LO internal gradient -> PDF shading type:
+ // * GradientStyle::Linear: axial shading, using sampled-function with 2 samples
+ // [t=0:colorStart, t=1:colorEnd]
+ // * GradientStyle::Axial: axial shading, using sampled-function with 3 samples
+ // [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd]
+ // * other styles: function shading with aSize.Width() * aSize.Height() samples
+ sal_Int32 nFunctionObject = createObject();
+ CHECK_RETURN( updateObject( nFunctionObject ) );
+
+ ScopedVclPtrInstance< VirtualDevice > aDev;
+ aDev->SetOutputSizePixel( rObject.m_aSize );
+ aDev->SetMapMode( MapMode( MapUnit::MapPixel ) );
+ if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
+ aDev->SetDrawMode( aDev->GetDrawMode() |
+ ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
+ DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
+ aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient );
+
+ Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize );
+ Bitmap::ScopedReadAccess pAccess(aSample);
+
+ Size aSize = aSample.GetSizePixel();
+
+ sal_Int32 nStreamLengthObject = createObject();
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::writeGradientFunction" );
+ }
+ OStringBuffer aLine( 120 );
+ aLine.append( nFunctionObject );
+ aLine.append( " 0 obj\n"
+ "<</FunctionType 0\n");
+ switch (rObject.m_aGradient.GetStyle())
+ {
+ case GradientStyle::Linear:
+ case GradientStyle::Axial:
+ aLine.append("/Domain[ 0 1]\n");
+ break;
+ default:
+ aLine.append("/Domain[ 0 1 0 1]\n");
+ }
+ aLine.append("/Size[ " );
+ switch (rObject.m_aGradient.GetStyle())
+ {
+ case GradientStyle::Linear:
+ aLine.append('2');
+ break;
+ case GradientStyle::Axial:
+ aLine.append('3');
+ break;
+ default:
+ aLine.append( static_cast<sal_Int32>(aSize.Width()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(aSize.Height()) );
+ }
+ aLine.append( " ]\n"
+ "/BitsPerSample 8\n"
+ "/Range[ 0 1 0 1 0 1 ]\n"
+ "/Order 3\n"
+ "/Length " );
+ aLine.append( nStreamLengthObject );
+ if (!g_bDebugDisableCompression)
+ aLine.append( " 0 R\n"
+ "/Filter/FlateDecode"
+ ">>\n"
+ "stream\n" );
+ else
+ aLine.append( " 0 R\n"
+ ">>\n"
+ "stream\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ sal_uInt64 nStartStreamPos = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartStreamPos)) );
+
+ checkAndEnableStreamEncryption( nFunctionObject );
+ beginCompression();
+ sal_uInt8 aCol[3];
+ switch (rObject.m_aGradient.GetStyle())
+ {
+ case GradientStyle::Axial:
+ aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
+ aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
+ aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
+ CHECK_RETURN( writeBuffer( aCol, 3 ) );
+ [[fallthrough]];
+ case GradientStyle::Linear:
+ {
+ aCol[0] = rObject.m_aGradient.GetStartColor().GetRed();
+ aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen();
+ aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue();
+ CHECK_RETURN( writeBuffer( aCol, 3 ) );
+
+ aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
+ aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
+ aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
+ CHECK_RETURN( writeBuffer( aCol, 3 ) );
+ break;
+ }
+ default:
+ for( int y = aSize.Height()-1; y >= 0; y-- )
+ {
+ for( tools::Long x = 0; x < aSize.Width(); x++ )
+ {
+ BitmapColor aColor = pAccess->GetColor( y, x );
+ aCol[0] = aColor.GetRed();
+ aCol[1] = aColor.GetGreen();
+ aCol[2] = aColor.GetBlue();
+ CHECK_RETURN( writeBuffer( aCol, 3 ) );
+ }
+ }
+ }
+ endCompression();
+ disableStreamEncryption();
+
+ sal_uInt64 nEndStreamPos = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndStreamPos)) );
+
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ // write stream length
+ CHECK_RETURN( updateObject( nStreamLengthObject ) );
+ aLine.setLength( 0 );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( static_cast<sal_Int64>(nEndStreamPos-nStartStreamPos) );
+ aLine.append( "\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ CHECK_RETURN( updateObject( rObject.m_nObject ) );
+ aLine.setLength( 0 );
+ aLine.append( rObject.m_nObject );
+ aLine.append( " 0 obj\n");
+ switch (rObject.m_aGradient.GetStyle())
+ {
+ case GradientStyle::Linear:
+ case GradientStyle::Axial:
+ aLine.append("<</ShadingType 2\n");
+ break;
+ default:
+ aLine.append("<</ShadingType 1\n");
+ }
+ aLine.append("/ColorSpace/DeviceRGB\n"
+ "/AntiAlias true\n");
+
+ // Determination of shading axis
+ // See: OutputDevice::ImplDrawLinearGradient for reference
+ tools::Rectangle aRect;
+ aRect.SetLeft(0);
+ aRect.SetTop(0);
+ aRect.SetRight( aSize.Width() );
+ aRect.SetBottom( aSize.Height() );
+
+ tools::Rectangle aBoundRect;
+ Point aCenter;
+ Degree10 nAngle = rObject.m_aGradient.GetAngle() % 3600_deg10;
+ rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter );
+
+ const bool bLinear = (rObject.m_aGradient.GetStyle() == GradientStyle::Linear);
+ double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0;
+ if ( !bLinear )
+ {
+ fBorder /= 2.0;
+ }
+
+ aBoundRect.AdjustBottom( -fBorder );
+ if (!bLinear)
+ {
+ aBoundRect.AdjustTop(fBorder );
+ }
+
+ switch (rObject.m_aGradient.GetStyle())
+ {
+ case GradientStyle::Linear:
+ case GradientStyle::Axial:
+ {
+ aLine.append("/Domain[ 0 1 ]\n"
+ "/Coords[ " );
+ tools::Polygon aPoly( 2 );
+ aPoly[0] = aBoundRect.BottomCenter();
+ aPoly[1] = aBoundRect.TopCenter();
+ aPoly.Rotate( aCenter, 3600_deg10 - nAngle );
+
+ aLine.append( static_cast<sal_Int32>(aPoly[0].X()) );
+ aLine.append( " " );
+ aLine.append( static_cast<sal_Int32>(aPoly[0].Y()) );
+ aLine.append( " " );
+ aLine.append( static_cast<sal_Int32>(aPoly[1].X()));
+ aLine.append( " ");
+ aLine.append( static_cast<sal_Int32>(aPoly[1].Y()));
+ aLine.append( " ]\n");
+ aLine.append("/Extend [true true]\n");
+ break;
+ }
+ default:
+ aLine.append("/Domain[ 0 1 0 1 ]\n"
+ "/Matrix[ " );
+ aLine.append( static_cast<sal_Int32>(aSize.Width()) );
+ aLine.append( " 0 0 " );
+ aLine.append( static_cast<sal_Int32>(aSize.Height()) );
+ aLine.append( " 0 0 ]\n");
+ }
+ aLine.append("/Function " );
+ aLine.append( nFunctionObject );
+ aLine.append( " 0 R\n"
+ ">>\n"
+ "endobj\n\n" );
+ return writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::writeJPG( const JPGEmit& rObject )
+{
+ if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject)
+ {
+ writeReferenceXObject(rObject.m_aReferenceXObject);
+ return;
+ }
+
+ CHECK_RETURN2( rObject.m_pStream );
+ CHECK_RETURN2( updateObject( rObject.m_nObject ) );
+
+ sal_Int32 nLength = rObject.m_pStream->TellEnd();
+ rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN );
+
+ sal_Int32 nMaskObject = 0;
+ if( !rObject.m_aAlphaMask.IsEmpty() )
+ {
+ if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4
+ && !m_bIsPDF_A1)
+ {
+ nMaskObject = createObject();
+ }
+ else if( m_bIsPDF_A1 )
+ m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
+ else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
+ m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 );
+
+ }
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::writeJPG" );
+ }
+
+ OStringBuffer aLine(200);
+ aLine.append( rObject.m_nObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/XObject/Subtype/Image/Width " );
+ aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Width()) );
+ aLine.append( " /Height " );
+ aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Height()) );
+ aLine.append( " /BitsPerComponent 8 " );
+ if( rObject.m_bTrueColor )
+ aLine.append( "/ColorSpace/DeviceRGB" );
+ else
+ aLine.append( "/ColorSpace/DeviceGray" );
+ aLine.append( "/Filter/DCTDecode/Length " );
+ aLine.append( nLength );
+ if( nMaskObject )
+ {
+ aLine.append(" /SMask ");
+ aLine.append( nMaskObject );
+ aLine.append( " 0 R " );
+ }
+ aLine.append( ">>\nstream\n" );
+ CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ checkAndEnableStreamEncryption( rObject.m_nObject );
+ CHECK_RETURN2( writeBuffer( rObject.m_pStream->GetData(), nLength ) );
+ disableStreamEncryption();
+
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\nendobj\n\n" );
+ CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ if( nMaskObject )
+ {
+ BitmapEmit aEmit;
+ aEmit.m_nObject = nMaskObject;
+ aEmit.m_aBitmap = BitmapEx( rObject.m_aAlphaMask, rObject.m_aAlphaMask );
+ writeBitmapObject( aEmit, true );
+ }
+
+ writeReferenceXObject(rObject.m_aReferenceXObject);
+}
+
+void PDFWriterImpl::writeReferenceXObject(const ReferenceXObjectEmit& rEmit)
+{
+ if (rEmit.m_nFormObject <= 0)
+ return;
+
+ // Count /Matrix and /BBox.
+ // vcl::ImportPDF() uses getDefaultPdfResolutionDpi to set the desired
+ // rendering DPI so we have to take into account that here too.
+ static const double fResolutionDPI = vcl::pdf::getDefaultPdfResolutionDpi();
+
+ sal_Int32 nOldDPIX = GetDPIX();
+ sal_Int32 nOldDPIY = GetDPIY();
+ SetDPIX(fResolutionDPI);
+ SetDPIY(fResolutionDPI);
+ Size aSize = PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit()));
+ SetDPIX(nOldDPIX);
+ SetDPIY(nOldDPIY);
+ double fScaleX = 1.0 / aSize.Width();
+ double fScaleY = 1.0 / aSize.Height();
+
+ sal_Int32 nWrappedFormObject = 0;
+ if (!m_aContext.UseReferenceXObject)
+ {
+ // Parse the PDF data, we need that to write the PDF dictionary of our
+ // object.
+ if (rEmit.m_nExternalPDFDataIndex < 0)
+ return;
+ auto& rExternalPDFStream = m_aExternalPDFStreams.get(rEmit.m_nExternalPDFDataIndex);
+ auto& pPDFDocument = rExternalPDFStream.getPDFDocument();
+ if (!pPDFDocument)
+ {
+ // Couldn't parse the document and can't continue
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: failed to parse the document");
+ return;
+ }
+
+ std::vector<filter::PDFObjectElement*> aPages = pPDFDocument->GetPages();
+ if (aPages.empty())
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages");
+ return;
+ }
+
+ size_t nPageIndex = rEmit.m_nExternalPDFPageIndex >= 0 ? rEmit.m_nExternalPDFPageIndex : 0;
+
+ filter::PDFObjectElement* pPage = aPages[nPageIndex];
+ if (!pPage)
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page");
+ return;
+ }
+
+ std::vector<filter::PDFObjectElement*> aContentStreams;
+ if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"))
+ aContentStreams.push_back(pContentStream);
+ else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents")))
+ {
+ for (const auto pElement : pArray->GetElements())
+ {
+ auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
+ if (!pReference)
+ continue;
+
+ filter::PDFObjectElement* pObject = pReference->LookupObject();
+ if (!pObject)
+ continue;
+
+ aContentStreams.push_back(pObject);
+ }
+ }
+
+ if (aContentStreams.empty())
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream");
+ return;
+ }
+
+ // Merge link annotations from pPage to our page.
+ std::vector<filter::PDFObjectElement*> aAnnots;
+ if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Annots")))
+ {
+ for (const auto pElement : pArray->GetElements())
+ {
+ auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
+ if (!pReference)
+ {
+ continue;
+ }
+
+ filter::PDFObjectElement* pObject = pReference->LookupObject();
+ if (!pObject)
+ {
+ continue;
+ }
+
+ auto pType = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Type"));
+ if (!pType || pType->GetValue() != "Annot")
+ {
+ continue;
+ }
+
+ auto pSubtype = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Subtype"));
+ if (!pSubtype || pSubtype->GetValue() != "Link")
+ {
+ continue;
+ }
+
+ // Reference to a link annotation object, remember it.
+ aAnnots.push_back(pObject);
+ }
+ }
+ if (!aAnnots.empty())
+ {
+ PDFObjectCopier aCopier(*this);
+ SvMemoryStream& rDocBuffer = pPage->GetDocument().GetEditBuffer();
+ std::map<sal_Int32, sal_Int32> aMap;
+ for (const auto& pAnnot : aAnnots)
+ {
+ // Copy over the annotation and refer to its new id.
+ sal_Int32 nNewId = aCopier.copyExternalResource(rDocBuffer, *pAnnot, aMap);
+ m_aPages.back().m_aAnnotations.push_back(nNewId);
+ }
+ }
+
+ nWrappedFormObject = createObject();
+ // Write the form XObject wrapped below. This is a separate object from
+ // the wrapper, this way there is no need to alter the stream contents.
+
+ OStringBuffer aLine;
+ aLine.append(nWrappedFormObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /XObject");
+ aLine.append(" /Subtype /Form");
+
+ tools::Long nWidth = aSize.Width();
+ tools::Long nHeight = aSize.Height();
+ basegfx::B2DRange aBBox(0, 0, aSize.Width(), aSize.Height());
+ if (auto pRotate = dynamic_cast<filter::PDFNumberElement*>(pPage->Lookup("Rotate")))
+ {
+ // The original page was rotated, then construct a transformation matrix which does the
+ // same with our form object.
+ sal_Int32 nRotAngle = static_cast<sal_Int32>(pRotate->GetValue()) % 360;
+ // /Rotate is clockwise, matrix rotate is counter-clockwise.
+ sal_Int32 nAngle = -1 * nRotAngle;
+
+ // The bounding box just rotates.
+ basegfx::B2DHomMatrix aBBoxMat;
+ aBBoxMat.rotate(basegfx::deg2rad(pRotate->GetValue()));
+ aBBox.transform(aBBoxMat);
+
+ // Now transform the object: rotate around the center and make sure that the rotation
+ // doesn't affect the aspect ratio.
+ basegfx::B2DHomMatrix aMat;
+ aMat.translate(-0.5 * aBBox.getWidth(), -0.5 * aBBox.getHeight());
+ aMat.rotate(basegfx::deg2rad(nAngle));
+ aMat.translate(0.5 * nWidth, 0.5 * nHeight);
+
+ aLine.append(" /Matrix [ ");
+ aLine.append(aMat.a());
+ aLine.append(" ");
+ aLine.append(aMat.b());
+ aLine.append(" ");
+ aLine.append(aMat.c());
+ aLine.append(" ");
+ aLine.append(aMat.d());
+ aLine.append(" ");
+ aLine.append(aMat.e());
+ aLine.append(" ");
+ aLine.append(aMat.f());
+ aLine.append(" ] ");
+ }
+
+ PDFObjectCopier aCopier(*this);
+ auto & rResources = rExternalPDFStream.getCopiedResources();
+ aCopier.copyPageResources(pPage, aLine, rResources);
+
+ aLine.append(" /BBox [ 0 0 ");
+ aLine.append(aBBox.getWidth());
+ aLine.append(" ");
+ aLine.append(aBBox.getHeight());
+ aLine.append(" ]");
+
+ if (!g_bDebugDisableCompression)
+ aLine.append(" /Filter/FlateDecode");
+ aLine.append(" /Length ");
+
+ SvMemoryStream aStream;
+ bool bCompressed = false;
+ sal_Int32 nLength = PDFObjectCopier::copyPageStreams(aContentStreams, aStream, bCompressed);
+ aLine.append(nLength);
+
+ aLine.append(">>\nstream\n");
+ if (g_bDebugDisableCompression)
+ {
+ emitComment("PDFWriterImpl::writeReferenceXObject, WrappedFormObject");
+ }
+ if (!updateObject(nWrappedFormObject))
+ return;
+ if (!writeBuffer(aLine.getStr(), aLine.getLength()))
+ return;
+ aLine.setLength(0);
+
+ checkAndEnableStreamEncryption(nWrappedFormObject);
+ // Copy the original page streams to the form XObject stream.
+ aLine.append(static_cast<const char*>(aStream.GetData()), aStream.GetSize());
+ if (!writeBuffer(aLine.getStr(), aLine.getLength()))
+ return;
+ aLine.setLength(0);
+ disableStreamEncryption();
+
+ aLine.append("\nendstream\nendobj\n\n");
+ if (!writeBuffer(aLine.getStr(), aLine.getLength()))
+ return;
+ }
+
+ OStringBuffer aLine;
+ if (g_bDebugDisableCompression)
+ {
+ emitComment("PDFWriterImpl::writeReferenceXObject, FormObject");
+ }
+ if (!updateObject(rEmit.m_nFormObject))
+ return;
+
+ // Now have all the info to write the form XObject.
+ aLine.append(rEmit.m_nFormObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /XObject");
+ aLine.append(" /Subtype /Form");
+ aLine.append(" /Resources << /XObject<<");
+
+ sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
+ aLine.append(" /Im");
+ aLine.append(nObject);
+ aLine.append(" ");
+ aLine.append(nObject);
+ aLine.append(" 0 R");
+
+ aLine.append(">> >>");
+ aLine.append(" /Matrix [ ");
+ appendDouble(fScaleX, aLine);
+ aLine.append(" 0 0 ");
+ appendDouble(fScaleY, aLine);
+ aLine.append(" 0 0 ]");
+ aLine.append(" /BBox [ 0 0 ");
+ aLine.append(aSize.Width());
+ aLine.append(" ");
+ aLine.append(aSize.Height());
+ aLine.append(" ]\n");
+
+ if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0)
+ {
+ // Write the reference dictionary.
+ aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) /EF << /F ");
+ aLine.append(rEmit.m_nEmbeddedObject);
+ aLine.append(" 0 R >> >> /Page 0 >>\n");
+ }
+
+ aLine.append("/Length ");
+
+ OStringBuffer aStream;
+ aStream.append("q ");
+ if (m_aContext.UseReferenceXObject)
+ {
+ // Reference XObject markup is used, just refer to the fallback bitmap
+ // here.
+ aStream.append(aSize.Width());
+ aStream.append(" 0 0 ");
+ aStream.append(aSize.Height());
+ aStream.append(" 0 0 cm\n");
+ aStream.append("/Im");
+ aStream.append(rEmit.m_nBitmapObject);
+ aStream.append(" Do\n");
+ }
+ else
+ {
+ // Reset line width to the default.
+ aStream.append(" 1 w\n");
+
+ // vcl::RenderPDFBitmaps() effectively renders a white background for transparent input, be
+ // consistent with that.
+ aStream.append("1 1 1 rg\n");
+ aStream.append("0 0 ");
+ aStream.append(aSize.Width());
+ aStream.append(" ");
+ aStream.append(aSize.Height());
+ aStream.append(" re\n");
+ aStream.append("f*\n");
+
+ // No reference XObject, draw the form XObject containing the original
+ // page streams.
+ aStream.append("/Im");
+ aStream.append(nWrappedFormObject);
+ aStream.append(" Do\n");
+ }
+ aStream.append("Q");
+ aLine.append(aStream.getLength());
+
+ aLine.append(">>\nstream\n");
+ if (!writeBuffer(aLine.getStr(), aLine.getLength()))
+ return;
+ aLine.setLength(0);
+
+ checkAndEnableStreamEncryption(rEmit.m_nFormObject);
+ aLine.append(aStream.getStr());
+ if (!writeBuffer(aLine.getStr(), aLine.getLength()))
+ return;
+ aLine.setLength(0);
+ disableStreamEncryption();
+
+ aLine.append("\nendstream\nendobj\n\n");
+ CHECK_RETURN2(writeBuffer(aLine.getStr(), aLine.getLength()));
+}
+
+namespace
+{
+ unsigned char reverseByte(unsigned char b)
+ {
+ b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
+ b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
+ b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
+ return b;
+ }
+
+ //tdf#103051 convert any N1BitLsbPal to N1BitMsbPal
+ Bitmap getExportBitmap(const Bitmap &rBitmap)
+ {
+ Bitmap::ScopedReadAccess pAccess(const_cast<Bitmap&>(rBitmap));
+ const ScanlineFormat eFormat = pAccess->GetScanlineFormat();
+ if (eFormat != ScanlineFormat::N1BitLsbPal)
+ return rBitmap;
+ Bitmap aNewBmp(rBitmap);
+ BitmapScopedWriteAccess xWriteAcc(aNewBmp);
+ const int nScanLineBytes = (pAccess->Width() + 7U) / 8U;
+ for (tools::Long nY = 0L; nY < xWriteAcc->Height(); ++nY)
+ {
+ Scanline pBitSwap = xWriteAcc->GetScanline(nY);
+ for (int x = 0; x < nScanLineBytes; ++x)
+ pBitSwap[x] = reverseByte(pBitSwap[x]);
+ }
+ return aNewBmp;
+ }
+}
+
+bool PDFWriterImpl::writeBitmapObject( const BitmapEmit& rObject, bool bMask )
+{
+ if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject)
+ {
+ writeReferenceXObject(rObject.m_aReferenceXObject);
+ return true;
+ }
+
+ CHECK_RETURN( updateObject( rObject.m_nObject ) );
+
+ Bitmap aBitmap;
+ bool bWriteMask = false;
+ if( ! bMask )
+ {
+ aBitmap = getExportBitmap(rObject.m_aBitmap.GetBitmap());
+ if( rObject.m_aBitmap.IsAlpha() )
+ {
+ if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
+ bWriteMask = true;
+ // else draw without alpha channel
+ }
+ }
+ else
+ {
+ if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() )
+ {
+ aBitmap = getExportBitmap(rObject.m_aBitmap.GetAlpha());
+ aBitmap.Convert( BmpConversion::N1BitThreshold );
+ SAL_WARN_IF(aBitmap.getPixelFormat() != vcl::PixelFormat::N1_BPP, "vcl.pdfwriter", "mask conversion failed" );
+ }
+ else if (aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP)
+ {
+ aBitmap = getExportBitmap(rObject.m_aBitmap.GetAlpha().GetBitmap());
+ aBitmap.Convert( BmpConversion::N8BitGreys );
+ SAL_WARN_IF(aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP, "vcl.pdfwriter", "alpha mask conversion failed" );
+ }
+ }
+
+ Bitmap::ScopedReadAccess pAccess(aBitmap);
+
+ bool bTrueColor = true;
+ sal_Int32 nBitsPerComponent = 0;
+ auto const ePixelFormat = aBitmap.getPixelFormat();
+ switch (ePixelFormat)
+ {
+ case vcl::PixelFormat::N1_BPP:
+ case vcl::PixelFormat::N8_BPP:
+ bTrueColor = false;
+ nBitsPerComponent = vcl::pixelFormatBitCount(ePixelFormat);
+ break;
+ case vcl::PixelFormat::N24_BPP:
+ case vcl::PixelFormat::N32_BPP:
+ bTrueColor = true;
+ nBitsPerComponent = 8;
+ break;
+ case vcl::PixelFormat::INVALID:
+ return false;
+ }
+
+ sal_Int32 nStreamLengthObject = createObject();
+ sal_Int32 nMaskObject = 0;
+
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::writeBitmapObject" );
+ }
+ OStringBuffer aLine(1024);
+ aLine.append( rObject.m_nObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/XObject/Subtype/Image/Width " );
+ aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
+ aLine.append( "/Height " );
+ aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Height()) );
+ aLine.append( "/BitsPerComponent " );
+ aLine.append( nBitsPerComponent );
+ aLine.append( "/Length " );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 R\n" );
+ if (!g_bDebugDisableCompression)
+ {
+ if( nBitsPerComponent != 1 )
+ {
+ aLine.append( "/Filter/FlateDecode" );
+ }
+ else
+ {
+ aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " );
+ aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
+ aLine.append( ">>\n" );
+ }
+ }
+ if( ! bMask )
+ {
+ aLine.append( "/ColorSpace" );
+ if( bTrueColor )
+ aLine.append( "/DeviceRGB\n" );
+ else if( aBitmap.HasGreyPaletteAny() )
+ {
+ aLine.append( "/DeviceGray\n" );
+ if (aBitmap.getPixelFormat() == vcl::PixelFormat::N1_BPP)
+ {
+ // #i47395# 1 bit bitmaps occasionally have an inverted grey palette
+ sal_uInt16 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) );
+ assert( nBlackIndex == 0 || nBlackIndex == 1);
+ sal_uInt16 nWhiteIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_WHITE ) );
+ if( pAccess->GetPalette()[nBlackIndex] == BitmapColor( COL_BLACK ) &&
+ pAccess->GetPalette()[nWhiteIndex] == BitmapColor( COL_WHITE ) )
+ {
+ // It is black and white
+ if( nBlackIndex == 1 )
+ aLine.append( "/Decode[1 0]\n" );
+ }
+ else
+ {
+ // It is two levels of grey
+ aLine.append( "/Decode[" );
+ assert( pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetGreen() &&
+ pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetBlue() &&
+ pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetGreen() &&
+ pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetBlue() );
+ aLine.append( pAccess->GetPalette()[0].GetRed() / 255.0 );
+ aLine.append( " " );
+ aLine.append( pAccess->GetPalette()[1].GetRed() / 255.0 );
+ aLine.append( "]\n" );
+ }
+ }
+ }
+ else
+ {
+ aLine.append( "[ /Indexed/DeviceRGB " );
+ aLine.append( static_cast<sal_Int32>(pAccess->GetPaletteEntryCount()-1) );
+ aLine.append( "\n<" );
+ if( m_aContext.Encryption.Encrypt() )
+ {
+ enableStringEncryption( rObject.m_nObject );
+ //check encryption buffer size
+ m_vEncryptionBuffer.resize(pAccess->GetPaletteEntryCount()*3);
+ int nChar = 0;
+ //fill the encryption buffer
+ for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
+ {
+ const BitmapColor& rColor = pAccess->GetPaletteColor( i );
+ m_vEncryptionBuffer[nChar++] = rColor.GetRed();
+ m_vEncryptionBuffer[nChar++] = rColor.GetGreen();
+ m_vEncryptionBuffer[nChar++] = rColor.GetBlue();
+ }
+ //encrypt the colorspace lookup table
+ rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChar, m_vEncryptionBuffer.data(), nChar );
+ //now queue the data for output
+ nChar = 0;
+ for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
+ {
+ appendHex(m_vEncryptionBuffer[nChar++], aLine );
+ appendHex(m_vEncryptionBuffer[nChar++], aLine );
+ appendHex(m_vEncryptionBuffer[nChar++], aLine );
+ }
+ }
+ else //no encryption requested (PDF/A-1a program flow drops here)
+ {
+ for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
+ {
+ const BitmapColor& rColor = pAccess->GetPaletteColor( i );
+ appendHex( rColor.GetRed(), aLine );
+ appendHex( rColor.GetGreen(), aLine );
+ appendHex( rColor.GetBlue(), aLine );
+ }
+ }
+ aLine.append( ">\n]\n" );
+ }
+ }
+ else
+ {
+ if (aBitmap.getPixelFormat() == vcl::PixelFormat::N1_BPP)
+ {
+ aLine.append( "/ImageMask true\n" );
+ sal_Int32 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) );
+ SAL_WARN_IF( nBlackIndex != 0 && nBlackIndex != 1, "vcl.pdfwriter", "wrong black index" );
+ if( nBlackIndex )
+ aLine.append( "/Decode[ 1 0 ]\n" );
+ else
+ aLine.append( "/Decode[ 0 1 ]\n" );
+ }
+ else if (aBitmap.getPixelFormat() == vcl::PixelFormat::N8_BPP)
+ {
+ aLine.append( "/ColorSpace/DeviceGray\n"
+ "/Decode [ 1 0 ]\n" );
+ }
+ }
+
+ if( ! bMask && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !m_bIsPDF_A1 )
+ {
+ if( bWriteMask )
+ {
+ nMaskObject = createObject();
+ if( rObject.m_aBitmap.IsAlpha() && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ aLine.append( "/SMask " );
+ else
+ aLine.append( "/Mask " );
+ aLine.append( nMaskObject );
+ aLine.append( " 0 R\n" );
+ }
+ }
+ else if( m_bIsPDF_A1 && bWriteMask )
+ m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
+
+ aLine.append( ">>\n"
+ "stream\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ sal_uInt64 nStartPos = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartPos)) );
+
+ checkAndEnableStreamEncryption( rObject.m_nObject );
+ if (!g_bDebugDisableCompression && nBitsPerComponent == 1)
+ {
+ writeG4Stream(pAccess.get());
+ }
+ else
+ {
+ beginCompression();
+ if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
+ {
+ //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
+ const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
+
+ for( tools::Long i = 0; i < pAccess->Height(); i++ )
+ {
+ CHECK_RETURN( writeBuffer( pAccess->GetScanline( i ), nScanLineBytes ) );
+ }
+ }
+ else
+ {
+ const int nScanLineBytes = pAccess->Width()*3;
+ std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]);
+ for( tools::Long y = 0; y < pAccess->Height(); y++ )
+ {
+ for( tools::Long x = 0; x < pAccess->Width(); x++ )
+ {
+ BitmapColor aColor = pAccess->GetColor( y, x );
+ xCol[3*x+0] = aColor.GetRed();
+ xCol[3*x+1] = aColor.GetGreen();
+ xCol[3*x+2] = aColor.GetBlue();
+ }
+ CHECK_RETURN(writeBuffer(xCol.get(), nScanLineBytes));
+ }
+ }
+ endCompression();
+ }
+ disableStreamEncryption();
+
+ sal_uInt64 nEndPos = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndPos)) );
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ CHECK_RETURN( updateObject( nStreamLengthObject ) );
+ aLine.setLength( 0 );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
+ aLine.append( "\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ if( nMaskObject )
+ {
+ BitmapEmit aEmit;
+ aEmit.m_nObject = nMaskObject;
+ aEmit.m_aBitmap = rObject.m_aBitmap;
+ return writeBitmapObject( aEmit, true );
+ }
+
+ writeReferenceXObject(rObject.m_aReferenceXObject);
+
+ return true;
+}
+
+void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject)
+{
+ // The bitmap object is always a valid identifier, even if the graphic has
+ // no pdf data.
+ rEmit.m_nBitmapObject = nBitmapObject;
+
+ if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf)
+ return;
+
+ BinaryDataContainer const & rDataContainer = rGraphic.getVectorGraphicData()->getBinaryDataContainer();
+
+ if (m_aContext.UseReferenceXObject)
+ {
+ // Store the original PDF data as an embedded file.
+ m_aEmbeddedFiles.emplace_back();
+ m_aEmbeddedFiles.back().m_nObject = createObject();
+ m_aEmbeddedFiles.back().m_aDataContainer = rDataContainer;
+ rEmit.m_nEmbeddedObject = m_aEmbeddedFiles.back().m_nObject;
+ }
+ else
+ {
+ sal_Int32 aIndex = m_aExternalPDFStreams.store(rDataContainer);
+ rEmit.m_nExternalPDFPageIndex = rGraphic.getVectorGraphicData()->getPageIndex();
+ rEmit.m_nExternalPDFDataIndex = aIndex;
+ }
+
+ rEmit.m_nFormObject = createObject();
+ rEmit.m_aPixelSize = rGraphic.GetPrefSize();
+}
+
+void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const AlphaMask& rAlphaMask, const Graphic& rGraphic )
+{
+ MARK( "drawJPGBitmap" );
+
+ OStringBuffer aLine( 80 );
+ updateGraphicsState();
+
+ // #i40055# sanity check
+ if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) )
+ return;
+ if( ! (rSizePixel.Width() && rSizePixel.Height()) )
+ return;
+
+ rDCTData.Seek( 0 );
+ if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale )
+ {
+ // need to convert to grayscale;
+ // load stream to bitmap and draw the bitmap instead
+ Graphic aGraphic;
+ GraphicConverter::Import( rDCTData, aGraphic, ConvertDataFormat::JPG );
+ if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == aGraphic.GetSizePixel() )
+ {
+ Bitmap aBmp( aGraphic.GetBitmapEx().GetBitmap() );
+ BitmapEx aBmpEx( aBmp, rAlphaMask );
+ drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmpEx );
+ }
+ else
+ drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aGraphic.GetBitmapEx() );
+ return;
+ }
+
+ std::unique_ptr<SvMemoryStream> pStream(new SvMemoryStream);
+ pStream->WriteStream( rDCTData );
+ pStream->Seek( STREAM_SEEK_TO_END );
+
+ BitmapID aID;
+ aID.m_aPixelSize = rSizePixel;
+ aID.m_nSize = pStream->Tell();
+ pStream->Seek( STREAM_SEEK_TO_BEGIN );
+ aID.m_nChecksum = vcl_get_checksum( 0, pStream->GetData(), aID.m_nSize );
+ if( ! rAlphaMask.IsEmpty() )
+ aID.m_nMaskChecksum = rAlphaMask.GetChecksum();
+
+ std::vector< JPGEmit >::const_iterator it = std::find_if(m_aJPGs.begin(), m_aJPGs.end(),
+ [&](const JPGEmit& arg) { return aID == arg.m_aID; });
+ if( it == m_aJPGs.end() )
+ {
+ m_aJPGs.emplace( m_aJPGs.begin() );
+ JPGEmit& rEmit = m_aJPGs.front();
+ if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
+ rEmit.m_nObject = createObject();
+ rEmit.m_aID = aID;
+ rEmit.m_pStream = std::move( pStream );
+ rEmit.m_bTrueColor = bIsTrueColor;
+ if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == rSizePixel )
+ rEmit.m_aAlphaMask = rAlphaMask;
+ createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject);
+
+ it = m_aJPGs.begin();
+ }
+
+ aLine.append( "q " );
+ sal_Int32 nCheckWidth = 0;
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetWidth()), aLine, false, &nCheckWidth );
+ aLine.append( " 0 0 " );
+ sal_Int32 nCheckHeight = 0;
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetHeight()), aLine, true, &nCheckHeight );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine );
+ aLine.append( " cm\n/Im" );
+ sal_Int32 nObject = it->m_aReferenceXObject.getObject();
+ aLine.append(nObject);
+ aLine.append( " Do Q\n" );
+ if( nCheckWidth == 0 || nCheckHeight == 0 )
+ {
+ // #i97512# avoid invalid current matrix
+ aLine.setLength( 0 );
+ aLine.append( "\n%jpeg image /Im" );
+ aLine.append( it->m_nObject );
+ aLine.append( " scaled to zero size, omitted\n" );
+ }
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ OString aObjName = "Im" + OString::number(nObject);
+ pushResource( ResourceKind::XObject, aObjName, nObject );
+
+}
+
+void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor )
+{
+ OStringBuffer aLine( 80 );
+ updateGraphicsState();
+
+ aLine.append( "q " );
+ if( rFillColor != COL_TRANSPARENT )
+ {
+ appendNonStrokingColor( rFillColor, aLine );
+ aLine.append( ' ' );
+ }
+ sal_Int32 nCheckWidth = 0;
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Width()), aLine, false, &nCheckWidth );
+ aLine.append( " 0 0 " );
+ sal_Int32 nCheckHeight = 0;
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Height()), aLine, true, &nCheckHeight );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), aLine );
+ aLine.append( " cm\n/Im" );
+ sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject();
+ aLine.append(nObject);
+ aLine.append( " Do Q\n" );
+ if( nCheckWidth == 0 || nCheckHeight == 0 )
+ {
+ // #i97512# avoid invalid current matrix
+ aLine.setLength( 0 );
+ aLine.append( "\n%bitmap image /Im" );
+ aLine.append( rBitmap.m_nObject );
+ aLine.append( " scaled to zero size, omitted\n" );
+ }
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+const BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& i_rBitmap, const Graphic& rGraphic )
+{
+ BitmapEx aBitmap( i_rBitmap );
+ auto ePixelFormat = aBitmap.GetBitmap().getPixelFormat();
+ if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
+ {
+ if (ePixelFormat != vcl::PixelFormat::N1_BPP)
+ aBitmap.Convert(BmpConversion::N8BitGreys);
+ }
+ BitmapID aID;
+ aID.m_aPixelSize = aBitmap.GetSizePixel();
+ aID.m_nSize = vcl::pixelFormatBitCount(ePixelFormat);
+ aID.m_nChecksum = aBitmap.GetBitmap().GetChecksum();
+ aID.m_nMaskChecksum = 0;
+ if( aBitmap.IsAlpha() )
+ aID.m_nMaskChecksum = aBitmap.GetAlpha().GetChecksum();
+ std::list< BitmapEmit >::const_iterator it = std::find_if(m_aBitmaps.begin(), m_aBitmaps.end(),
+ [&](const BitmapEmit& arg) { return aID == arg.m_aID; });
+ if( it == m_aBitmaps.end() )
+ {
+ m_aBitmaps.push_front( BitmapEmit() );
+ m_aBitmaps.front().m_aID = aID;
+ m_aBitmaps.front().m_aBitmap = aBitmap;
+ if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
+ m_aBitmaps.front().m_nObject = createObject();
+ createEmbeddedFile(rGraphic, m_aBitmaps.front().m_aReferenceXObject, m_aBitmaps.front().m_nObject);
+ it = m_aBitmaps.begin();
+ }
+
+ sal_Int32 nObject = it->m_aReferenceXObject.getObject();
+ OString aObjName = "Im" + OString::number(nObject);
+ pushResource( ResourceKind::XObject, aObjName, nObject );
+
+ return *it;
+}
+
+void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
+{
+ MARK( "drawBitmap (Bitmap)" );
+
+ // #i40055# sanity check
+ if( ! (rDestSize.Width() && rDestSize.Height()) )
+ return;
+
+ const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( rBitmap ), rGraphic );
+ drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
+}
+
+void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap )
+{
+ MARK( "drawBitmap (BitmapEx)" );
+
+ // #i40055# sanity check
+ if( ! (rDestSize.Width() && rDestSize.Height()) )
+ return;
+
+ const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() );
+ drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
+}
+
+sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize )
+{
+ Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
+ MapMode( MapUnit::MapPoint ),
+ this,
+ rSize ) );
+ // check if we already have this gradient
+ // rounding to point will generally lose some pixels
+ // round up to point boundary
+ aPtSize.AdjustWidth( 1 );
+ aPtSize.AdjustHeight( 1 );
+ std::list< GradientEmit >::const_iterator it = std::find_if(m_aGradients.begin(), m_aGradients.end(),
+ [&](const GradientEmit& arg) { return ((rGradient == arg.m_aGradient) && (aPtSize == arg.m_aSize) ); });
+
+ if( it == m_aGradients.end() )
+ {
+ m_aGradients.push_front( GradientEmit() );
+ m_aGradients.front().m_aGradient = rGradient;
+ m_aGradients.front().m_nObject = createObject();
+ m_aGradients.front().m_aSize = aPtSize;
+ it = m_aGradients.begin();
+ }
+
+ OStringBuffer aObjName( 16 );
+ aObjName.append( 'P' );
+ aObjName.append( it->m_nObject );
+ pushResource( ResourceKind::Shading, aObjName.makeStringAndClear(), it->m_nObject );
+
+ return it->m_nObject;
+}
+
+void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
+{
+ MARK( "drawGradient (Rectangle)" );
+
+ if( m_aContext.Version == PDFWriter::PDFVersion::PDF_1_2 )
+ {
+ drawRectangle( rRect );
+ return;
+ }
+
+ sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() );
+
+ Point aTranslate( rRect.BottomLeft() );
+ aTranslate += Point( 0, 1 );
+
+ updateGraphicsState();
+
+ OStringBuffer aLine( 80 );
+ aLine.append( "q 1 0 0 1 " );
+ m_aPages.back().appendPoint( aTranslate, aLine );
+ aLine.append( " cm " );
+ // if a stroke is appended reset the clip region before stroke
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "q " );
+ aLine.append( "0 0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
+ aLine.append( " re W n\n" );
+
+ aLine.append( "/P" );
+ aLine.append( nGradient );
+ aLine.append( " sh " );
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ {
+ aLine.append( "Q 0 0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
+ aLine.append( " re S " );
+ }
+ aLine.append( "Q\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
+{
+ MARK( "drawHatch" );
+
+ updateGraphicsState();
+
+ if( rPolyPoly.Count() )
+ {
+ tools::PolyPolygon aPolyPoly( rPolyPoly );
+
+ aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
+ push( PushFlags::LINECOLOR );
+ setLineColor( rHatch.GetColor() );
+ DrawHatch( aPolyPoly, rHatch, false );
+ pop();
+ }
+}
+
+void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall )
+{
+ MARK( "drawWallpaper" );
+
+ bool bDrawColor = false;
+ bool bDrawGradient = false;
+ bool bDrawBitmap = false;
+
+ BitmapEx aBitmap;
+ Point aBmpPos = rRect.TopLeft();
+ Size aBmpSize;
+ if( rWall.IsBitmap() )
+ {
+ aBitmap = rWall.GetBitmap();
+ aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(),
+ getMapMode(),
+ this,
+ aBitmap.GetPrefSize() );
+ tools::Rectangle aRect( rRect );
+ if( rWall.IsRect() )
+ {
+ aRect = rWall.GetRect();
+ aBmpPos = aRect.TopLeft();
+ aBmpSize = aRect.GetSize();
+ }
+ if( rWall.GetStyle() != WallpaperStyle::Scale )
+ {
+ if( rWall.GetStyle() != WallpaperStyle::Tile )
+ {
+ bDrawBitmap = true;
+ if( rWall.IsGradient() )
+ bDrawGradient = true;
+ else
+ bDrawColor = true;
+ switch( rWall.GetStyle() )
+ {
+ case WallpaperStyle::TopLeft:
+ break;
+ case WallpaperStyle::Top:
+ aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
+ break;
+ case WallpaperStyle::Left:
+ aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
+ break;
+ case WallpaperStyle::TopRight:
+ aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
+ break;
+ case WallpaperStyle::Center:
+ aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
+ aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
+ break;
+ case WallpaperStyle::Right:
+ aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
+ aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
+ break;
+ case WallpaperStyle::BottomLeft:
+ aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
+ break;
+ case WallpaperStyle::Bottom:
+ aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
+ aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
+ break;
+ case WallpaperStyle::BottomRight:
+ aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
+ aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
+ break;
+ default: ;
+ }
+ }
+ else
+ {
+ // push the bitmap
+ const BitmapEmit& rEmit = createBitmapEmit( aBitmap, Graphic() );
+
+ // convert to page coordinates; this needs to be done here
+ // since the emit does not know the page anymore
+ tools::Rectangle aConvertRect( aBmpPos, aBmpSize );
+ m_aPages.back().convertRect( aConvertRect );
+
+ OString aImageName = "Im" + OString::number( rEmit.m_nObject );
+
+ // push the pattern
+ OStringBuffer aTilingStream( 32 );
+ appendFixedInt( aConvertRect.GetWidth(), aTilingStream );
+ aTilingStream.append( " 0 0 " );
+ appendFixedInt( aConvertRect.GetHeight(), aTilingStream );
+ aTilingStream.append( " 0 0 cm\n/" );
+ aTilingStream.append( aImageName );
+ aTilingStream.append( " Do\n" );
+
+ m_aTilings.emplace_back( );
+ m_aTilings.back().m_nObject = createObject();
+ m_aTilings.back().m_aRectangle = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() );
+ m_aTilings.back().m_pTilingStream.reset(new SvMemoryStream());
+ m_aTilings.back().m_pTilingStream->WriteBytes(
+ aTilingStream.getStr(), aTilingStream.getLength() );
+ // phase the tiling so wallpaper begins on upper left
+ if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0))
+ throw o3tl::divide_by_zero();
+ m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor;
+ m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor;
+ m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject;
+
+ updateGraphicsState();
+
+ OStringBuffer aObjName( 16 );
+ aObjName.append( 'P' );
+ aObjName.append( m_aTilings.back().m_nObject );
+ OString aPatternName( aObjName.makeStringAndClear() );
+ pushResource( ResourceKind::Pattern, aPatternName, m_aTilings.back().m_nObject );
+
+ // fill a rRect with the pattern
+ OStringBuffer aLine( 100 );
+ aLine.append( "q /Pattern cs /" );
+ aLine.append( aPatternName );
+ aLine.append( " scn " );
+ m_aPages.back().appendRect( rRect, aLine );
+ aLine.append( " f Q\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+ }
+ }
+ else
+ {
+ aBmpPos = aRect.TopLeft();
+ aBmpSize = aRect.GetSize();
+ bDrawBitmap = true;
+ }
+
+ if( aBitmap.IsAlpha() )
+ {
+ if( rWall.IsGradient() )
+ bDrawGradient = true;
+ else
+ bDrawColor = true;
+ }
+ }
+ else if( rWall.IsGradient() )
+ bDrawGradient = true;
+ else
+ bDrawColor = true;
+
+ if( bDrawGradient )
+ {
+ drawGradient( rRect, rWall.GetGradient() );
+ }
+ if( bDrawColor )
+ {
+ Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor;
+ Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
+ setLineColor( COL_TRANSPARENT );
+ setFillColor( rWall.GetColor() );
+ drawRectangle( rRect );
+ setLineColor( aOldLineColor );
+ setFillColor( aOldFillColor );
+ }
+ if( bDrawBitmap )
+ {
+ // set temporary clip region since aBmpPos and aBmpSize
+ // may be outside rRect
+ OStringBuffer aLine( 20 );
+ aLine.append( "q " );
+ m_aPages.back().appendRect( rRect, aLine );
+ aLine.append( " W n\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+ drawBitmap( aBmpPos, aBmpSize, aBitmap );
+ writeBuffer( "Q\n", 2 );
+ }
+}
+
+void PDFWriterImpl::updateGraphicsState(Mode const mode)
+{
+ OStringBuffer aLine( 256 );
+ GraphicsState& rNewState = m_aGraphicsStack.front();
+ // first set clip region since it might invalidate everything else
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion;
+
+ if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion ||
+ ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) )
+ {
+ if( m_aCurrentPDFState.m_bClipRegion )
+ {
+ aLine.append( "Q " );
+ // invalidate everything but the clip region
+ m_aCurrentPDFState = GraphicsState();
+ rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion;
+ }
+ if( rNewState.m_bClipRegion )
+ {
+ // clip region is always stored in private PDF mapmode
+ MapMode aNewMapMode = rNewState.m_aMapMode;
+ rNewState.m_aMapMode = m_aMapMode;
+ SetMapMode( rNewState.m_aMapMode );
+ m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
+
+ aLine.append("q ");
+ if ( rNewState.m_aClipRegion.count() )
+ {
+ m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, aLine );
+ }
+ else
+ {
+ // tdf#130150 Need to revert tdf#99680, that breaks the
+ // rule that an set but empty clip-region clips everything
+ // aka draws nothing -> nothing is in an empty clip-region
+ aLine.append( "0 0 m h " ); // NULL clip, i.e. nothing visible
+ }
+ aLine.append( "W* n\n" );
+
+ rNewState.m_aMapMode = aNewMapMode;
+ SetMapMode( rNewState.m_aMapMode );
+ m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
+ }
+ }
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode;
+ SetMapMode( rNewState.m_aMapMode );
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font;
+ SetFont( rNewState.m_aFont );
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode;
+ SetLayoutMode( rNewState.m_nLayoutMode );
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage;
+ SetDigitLanguage( rNewState.m_aDigitLanguage );
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor;
+ if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor &&
+ rNewState.m_aLineColor != COL_TRANSPARENT )
+ {
+ appendStrokingColor( rNewState.m_aLineColor, aLine );
+ aLine.append( "\n" );
+ }
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor;
+ if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor &&
+ rNewState.m_aFillColor != COL_TRANSPARENT )
+ {
+ appendNonStrokingColor( rNewState.m_aFillColor, aLine );
+ aLine.append( "\n" );
+ }
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent;
+ if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
+ {
+ // TODO: switch extended graphicsstate
+ }
+ }
+
+ // everything is up to date now
+ m_aCurrentPDFState = m_aGraphicsStack.front();
+ if ((mode != Mode::NOWRITE) && !aLine.isEmpty())
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+/* #i47544# imitate OutputDevice behaviour:
+* if a font with a nontransparent color is set, it overwrites the current
+* text color. OTOH setting the text color will overwrite the color of the font.
+*/
+void PDFWriterImpl::setFont( const vcl::Font& rFont )
+{
+ Color aColor = rFont.GetColor();
+ if( aColor == COL_TRANSPARENT )
+ aColor = m_aGraphicsStack.front().m_aFont.GetColor();
+ m_aGraphicsStack.front().m_aFont = rFont;
+ m_aGraphicsStack.front().m_aFont.SetColor( aColor );
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
+}
+
+void PDFWriterImpl::push( PushFlags nFlags )
+{
+ OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" );
+ m_aGraphicsStack.push_front( m_aGraphicsStack.front() );
+ m_aGraphicsStack.front().m_nFlags = nFlags;
+}
+
+void PDFWriterImpl::pop()
+{
+ OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" );
+ if( m_aGraphicsStack.size() < 2 )
+ return;
+
+ GraphicsState aState = m_aGraphicsStack.front();
+ m_aGraphicsStack.pop_front();
+ GraphicsState& rOld = m_aGraphicsStack.front();
+
+ // move those parameters back that were not pushed
+ // in the first place
+ if( ! (aState.m_nFlags & PushFlags::LINECOLOR) )
+ setLineColor( aState.m_aLineColor );
+ if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) )
+ setFillColor( aState.m_aFillColor );
+ if( ! (aState.m_nFlags & PushFlags::FONT) )
+ setFont( aState.m_aFont );
+ if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) )
+ setTextColor( aState.m_aFont.GetColor() );
+ if( ! (aState.m_nFlags & PushFlags::MAPMODE) )
+ setMapMode( aState.m_aMapMode );
+ if( ! (aState.m_nFlags & PushFlags::CLIPREGION) )
+ {
+ // do not use setClipRegion here
+ // it would convert again assuming the current mapmode
+ rOld.m_aClipRegion = aState.m_aClipRegion;
+ rOld.m_bClipRegion = aState.m_bClipRegion;
+ }
+ if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) )
+ setTextLineColor( aState.m_aTextLineColor );
+ if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) )
+ setOverlineColor( aState.m_aOverlineColor );
+ if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) )
+ setTextAlign( aState.m_aFont.GetAlignment() );
+ if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) )
+ setTextFillColor( aState.m_aFont.GetFillColor() );
+ if( ! (aState.m_nFlags & PushFlags::REFPOINT) )
+ {
+ // what ?
+ }
+ // invalidate graphics state
+ m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All;
+}
+
+void PDFWriterImpl::setMapMode( const MapMode& rMapMode )
+{
+ m_aGraphicsStack.front().m_aMapMode = rMapMode;
+ SetMapMode( rMapMode );
+ m_aCurrentPDFState.m_aMapMode = rMapMode;
+}
+
+void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion )
+{
+ // tdf#130150 improve coordinate manipulations to double precision transformations
+ const basegfx::B2DHomMatrix aCurrentTransform(
+ GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
+ basegfx::B2DPolyPolygon aRegion(rRegion);
+
+ aRegion.transform(aCurrentTransform);
+ m_aGraphicsStack.front().m_aClipRegion = aRegion;
+ m_aGraphicsStack.front().m_bClipRegion = true;
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
+}
+
+void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY )
+{
+ if( !(m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count()) )
+ return;
+
+ // tdf#130150 improve coordinate manipulations to double precision transformations
+ basegfx::B2DHomMatrix aConvertA;
+
+ if(MapUnit::MapPixel == m_aGraphicsStack.front().m_aMapMode.GetMapUnit())
+ {
+ aConvertA = GetInverseViewTransformation(m_aMapMode);
+ }
+ else
+ {
+ aConvertA = LogicToLogic(m_aGraphicsStack.front().m_aMapMode, m_aMapMode);
+ }
+
+ basegfx::B2DPoint aB2DPointA(nX, nY);
+ basegfx::B2DPoint aB2DPointB(0.0, 0.0);
+ aB2DPointA *= aConvertA;
+ aB2DPointB *= aConvertA;
+ aB2DPointA -= aB2DPointB;
+ basegfx::B2DHomMatrix aMat;
+
+ aMat.translate(aB2DPointA.getX(), aB2DPointA.getY());
+ m_aGraphicsStack.front().m_aClipRegion.transform( aMat );
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
+}
+
+void PDFWriterImpl::intersectClipRegion( const tools::Rectangle& rRect )
+{
+ basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect) ) );
+ intersectClipRegion( aRect );
+}
+
+void PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion )
+{
+ // tdf#130150 improve coordinate manipulations to double precision transformations
+ const basegfx::B2DHomMatrix aCurrentTransform(
+ GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
+ basegfx::B2DPolyPolygon aRegion(rRegion);
+
+ aRegion.transform(aCurrentTransform);
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
+
+ if( m_aGraphicsStack.front().m_bClipRegion )
+ {
+ basegfx::B2DPolyPolygon aOld( basegfx::utils::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) );
+ aRegion = basegfx::utils::prepareForPolygonOperation( aRegion );
+ m_aGraphicsStack.front().m_aClipRegion = basegfx::utils::solvePolygonOperationAnd( aOld, aRegion );
+ }
+ else
+ {
+ m_aGraphicsStack.front().m_aClipRegion = aRegion;
+ m_aGraphicsStack.front().m_bClipRegion = true;
+ }
+}
+
+void PDFWriterImpl::createNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
+{
+ if (nPageNr < 0)
+ nPageNr = m_nCurrentPage;
+
+ if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size())
+ return;
+
+ m_aNotes.emplace_back();
+ auto & rNoteEntry = m_aNotes.back();
+ rNoteEntry.m_nObject = createObject();
+ rNoteEntry.m_aPopUpAnnotation.m_nObject = createObject();
+ rNoteEntry.m_aPopUpAnnotation.m_nParentObject = rNoteEntry.m_nObject;
+ rNoteEntry.m_aContents = rNote;
+ rNoteEntry.m_aRect = rRect;
+ // convert to default user space now, since the mapmode may change
+ m_aPages[nPageNr].convertRect(rNoteEntry.m_aRect);
+
+ // insert note to page's annotation list
+ m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_nObject);
+ m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_aPopUpAnnotation.m_nObject);
+}
+
+sal_Int32 PDFWriterImpl::createLink(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText)
+{
+ if( nPageNr < 0 )
+ nPageNr = m_nCurrentPage;
+
+ if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
+ return -1;
+
+ sal_Int32 nRet = m_aLinks.size();
+
+ m_aLinks.emplace_back(rAltText);
+ m_aLinks.back().m_nObject = createObject();
+ m_aLinks.back().m_nPage = nPageNr;
+ m_aLinks.back().m_aRect = rRect;
+ // convert to default user space now, since the mapmode may change
+ m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect );
+
+ // insert link to page's annotation list
+ m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject );
+
+ return nRet;
+}
+
+sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr)
+{
+ if (nPageNr < 0)
+ nPageNr = m_nCurrentPage;
+
+ if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size())
+ return -1;
+
+ sal_Int32 nRet = m_aScreens.size();
+
+ m_aScreens.emplace_back();
+ m_aScreens.back().m_nObject = createObject();
+ m_aScreens.back().m_nPage = nPageNr;
+ m_aScreens.back().m_aRect = rRect;
+ // Convert to default user space now, since the mapmode may change.
+ m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect);
+
+ // Insert link to page's annotation list.
+ m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject);
+
+ return nRet;
+}
+
+sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ if( nPageNr < 0 )
+ nPageNr = m_nCurrentPage;
+
+ if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
+ return -1;
+
+ sal_Int32 nRet = m_aNamedDests.size();
+
+ m_aNamedDests.emplace_back( );
+ m_aNamedDests.back().m_aDestName = sDestName;
+ m_aNamedDests.back().m_nPage = nPageNr;
+ m_aNamedDests.back().m_eType = eType;
+ m_aNamedDests.back().m_aRect = rRect;
+ // convert to default user space now, since the mapmode may change
+ m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect );
+
+ return nRet;
+}
+
+sal_Int32 PDFWriterImpl::createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ if( nPageNr < 0 )
+ nPageNr = m_nCurrentPage;
+
+ if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
+ return -1;
+
+ sal_Int32 nRet = m_aDests.size();
+
+ m_aDests.emplace_back( );
+ m_aDests.back().m_nPage = nPageNr;
+ m_aDests.back().m_eType = eType;
+ m_aDests.back().m_aRect = rRect;
+ // convert to default user space now, since the mapmode may change
+ m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect );
+
+ return nRet;
+}
+
+sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType );
+ return m_aDestinationIdTranslation[ nDestId ];
+}
+
+void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
+{
+ if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() )
+ return;
+ if( nDestId < 0 || o3tl::make_unsigned(nDestId) >= m_aDests.size() )
+ return;
+
+ m_aLinks[ nLinkId ].m_nDest = nDestId;
+}
+
+void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL )
+{
+ if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() )
+ return;
+
+ m_aLinks[ nLinkId ].m_nDest = -1;
+
+ using namespace ::com::sun::star;
+
+ if (!m_xTrans.is())
+ {
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ m_xTrans = util::URLTransformer::create(xContext);
+ }
+
+ util::URL aURL;
+ aURL.Complete = rURL;
+
+ m_xTrans->parseStrict( aURL );
+
+ m_aLinks[ nLinkId ].m_aURL = aURL.Complete;
+}
+
+void PDFWriterImpl::setScreenURL(sal_Int32 nScreenId, const OUString& rURL)
+{
+ if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size())
+ return;
+
+ m_aScreens[nScreenId].m_aURL = rURL;
+}
+
+void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL)
+{
+ if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size())
+ return;
+
+ m_aScreens[nScreenId].m_aTempFileURL = rURL;
+ m_aScreens[nScreenId].m_nTempFileObject = createObject();
+}
+
+void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId )
+{
+ m_aLinkPropertyMap[ nPropertyId ] = nLinkId;
+}
+
+sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, std::u16string_view rText, sal_Int32 nDestID )
+{
+ // create new item
+ sal_Int32 nNewItem = m_aOutline.size();
+ m_aOutline.emplace_back( );
+
+ // set item attributes
+ setOutlineItemParent( nNewItem, nParent );
+ setOutlineItemText( nNewItem, rText );
+ setOutlineItemDest( nNewItem, nDestID );
+
+ return nNewItem;
+}
+
+void PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent )
+{
+ if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() )
+ return;
+
+ if( nNewParent < 0 || o3tl::make_unsigned(nNewParent) >= m_aOutline.size() || nNewParent == nItem )
+ {
+ nNewParent = 0;
+ }
+ // insert item to new parent's list of children
+ m_aOutline[ nNewParent ].m_aChildren.push_back( nItem );
+}
+
+void PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, std::u16string_view rText )
+{
+ if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() )
+ return;
+
+ m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText );
+}
+
+void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID )
+{
+ if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) // item does not exist
+ return;
+ if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() ) // dest does not exist
+ return;
+ m_aOutline[nItem].m_nDestID = nDestID;
+}
+
+const char* PDFWriterImpl::getStructureTag( PDFWriter::StructElement eType )
+{
+ static std::map< PDFWriter::StructElement, const char* > aTagStrings;
+ if( aTagStrings.empty() )
+ {
+ aTagStrings[ PDFWriter::NonStructElement] = "NonStruct";
+ aTagStrings[ PDFWriter::Document ] = "Document";
+ aTagStrings[ PDFWriter::Part ] = "Part";
+ aTagStrings[ PDFWriter::Article ] = "Art";
+ aTagStrings[ PDFWriter::Section ] = "Sect";
+ aTagStrings[ PDFWriter::Division ] = "Div";
+ aTagStrings[ PDFWriter::BlockQuote ] = "BlockQuote";
+ aTagStrings[ PDFWriter::Caption ] = "Caption";
+ aTagStrings[ PDFWriter::TOC ] = "TOC";
+ aTagStrings[ PDFWriter::TOCI ] = "TOCI";
+ aTagStrings[ PDFWriter::Index ] = "Index";
+ aTagStrings[ PDFWriter::Paragraph ] = "P";
+ aTagStrings[ PDFWriter::Heading ] = "H";
+ aTagStrings[ PDFWriter::H1 ] = "H1";
+ aTagStrings[ PDFWriter::H2 ] = "H2";
+ aTagStrings[ PDFWriter::H3 ] = "H3";
+ aTagStrings[ PDFWriter::H4 ] = "H4";
+ aTagStrings[ PDFWriter::H5 ] = "H5";
+ aTagStrings[ PDFWriter::H6 ] = "H6";
+ aTagStrings[ PDFWriter::List ] = "L";
+ aTagStrings[ PDFWriter::ListItem ] = "LI";
+ aTagStrings[ PDFWriter::LILabel ] = "Lbl";
+ aTagStrings[ PDFWriter::LIBody ] = "LBody";
+ aTagStrings[ PDFWriter::Table ] = "Table";
+ aTagStrings[ PDFWriter::TableRow ] = "TR";
+ aTagStrings[ PDFWriter::TableHeader ] = "TH";
+ aTagStrings[ PDFWriter::TableData ] = "TD";
+ aTagStrings[ PDFWriter::Span ] = "Span";
+ aTagStrings[ PDFWriter::Quote ] = "Quote";
+ aTagStrings[ PDFWriter::Note ] = "Note";
+ aTagStrings[ PDFWriter::Reference ] = "Reference";
+ aTagStrings[ PDFWriter::BibEntry ] = "BibEntry";
+ aTagStrings[ PDFWriter::Code ] = "Code";
+ aTagStrings[ PDFWriter::Link ] = "Link";
+ aTagStrings[ PDFWriter::Figure ] = "Figure";
+ aTagStrings[ PDFWriter::Formula ] = "Formula";
+ aTagStrings[ PDFWriter::Form ] = "Form";
+ }
+
+ std::map< PDFWriter::StructElement, const char* >::const_iterator it = aTagStrings.find( eType );
+
+ return it != aTagStrings.end() ? it->second : "Div";
+}
+
+void PDFWriterImpl::addRoleMap(OString aAlias, PDFWriter::StructElement eType)
+{
+ OString aTag = getStructureTag(eType);
+ // For PDF/UA it's not allowed to map an alias with the same name.
+ // Not sure if this allowed, necessary or recommended otherwise, so
+ // only enable filtering when PDF/UA is enabled.
+ if (!m_bIsPDF_UA || aAlias != aTag)
+ m_aRoleMap[aAlias] = aTag;
+}
+
+void PDFWriterImpl::beginStructureElementMCSeq()
+{
+ if( m_bEmitStructure &&
+ m_nCurrentStructElement > 0 && // StructTreeRoot
+ ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
+ )
+ {
+ PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ];
+ OStringBuffer aLine( 128 );
+ sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size();
+ aLine.append( "/" );
+ if( !rEle.m_aAlias.isEmpty() )
+ aLine.append( rEle.m_aAlias );
+ else
+ aLine.append( getStructureTag( rEle.m_eType ) );
+ aLine.append( "<</MCID " );
+ aLine.append( nMCID );
+ aLine.append( ">>BDC\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ // update the element's content list
+ SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object "
+ << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = "
+ << rEle.m_nFirstPageObject);
+ rEle.m_aKids.emplace_back( nMCID, m_aPages[m_nCurrentPage].m_nPageObject );
+ // update the page's mcid parent list
+ m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject );
+ // mark element MC sequence as open
+ rEle.m_bOpenMCSeq = true;
+ }
+ // handle artifacts
+ else if( ! m_bEmitStructure && m_aContext.Tagged &&
+ m_nCurrentStructElement > 0 &&
+ m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement &&
+ ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
+ )
+ {
+ OString aLine = "/Artifact BMC\n";
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+ // mark element MC sequence as open
+ m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true;
+ }
+}
+
+void PDFWriterImpl::endStructureElementMCSeq()
+{
+ if( m_nCurrentStructElement > 0 && // StructTreeRoot
+ ( m_bEmitStructure || m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement ) &&
+ m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // must have an opened MC sequence
+ )
+ {
+ writeBuffer( "EMC\n", 4 );
+ m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false;
+ }
+}
+
+bool PDFWriterImpl::checkEmitStructure()
+{
+ bool bEmit = false;
+ if( m_aContext.Tagged )
+ {
+ bEmit = true;
+ sal_Int32 nEle = m_nCurrentStructElement;
+ while( nEle > 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() )
+ {
+ if( m_aStructure[ nEle ].m_eType == PDFWriter::NonStructElement )
+ {
+ bEmit = false;
+ break;
+ }
+ nEle = m_aStructure[ nEle ].m_nParentElement;
+ }
+ }
+ return bEmit;
+}
+
+sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias )
+{
+ if( m_nCurrentPage < 0 )
+ return -1;
+
+ if( ! m_aContext.Tagged )
+ return -1;
+
+ // close eventual current MC sequence
+ endStructureElementMCSeq();
+
+ if( m_nCurrentStructElement == 0 &&
+ eType != PDFWriter::Document && eType != PDFWriter::NonStructElement )
+ {
+ // struct tree root hit, but not beginning document
+ // this might happen with setCurrentStructureElement
+ // silently insert structure into document again if one properly exists
+ if( ! m_aStructure[ 0 ].m_aChildren.empty() )
+ {
+ const std::list< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren;
+ auto it = std::find_if(rRootChildren.begin(), rRootChildren.end(),
+ [&](sal_Int32 nElement) { return m_aStructure[ nElement ].m_eType == PDFWriter::Document; });
+ if( it != rRootChildren.end() )
+ {
+ m_nCurrentStructElement = *it;
+ SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" );
+ }
+ else {
+ OSL_FAIL( "document structure in disorder !" );
+ }
+ }
+ else {
+ OSL_FAIL( "PDF document structure MUST be contained in a Document element" );
+ }
+ }
+
+ sal_Int32 nNewId = sal_Int32(m_aStructure.size());
+ m_aStructure.emplace_back( );
+ PDFStructureElement& rEle = m_aStructure.back();
+ rEle.m_eType = eType;
+ rEle.m_nOwnElement = nNewId;
+ rEle.m_nParentElement = m_nCurrentStructElement;
+ rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
+ m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
+ m_nCurrentStructElement = nNewId;
+
+ // handle alias names
+ if( !rAlias.isEmpty() && eType != PDFWriter::NonStructElement )
+ {
+ OStringBuffer aNameBuf( rAlias.getLength() );
+ appendName( rAlias, aNameBuf );
+ OString aAliasName( aNameBuf.makeStringAndClear() );
+ rEle.m_aAlias = aAliasName;
+ addRoleMap(aAliasName, eType);
+ }
+
+ if (g_bDebugDisableCompression)
+ {
+ OStringBuffer aLine( "beginStructureElement " );
+ aLine.append( m_nCurrentStructElement );
+ aLine.append( ": " );
+ aLine.append( getStructureTag( eType ) );
+ if( !rEle.m_aAlias.isEmpty() )
+ {
+ aLine.append( " aliased as \"" );
+ aLine.append( rEle.m_aAlias );
+ aLine.append( '\"' );
+ }
+ emitComment( aLine.getStr() );
+ }
+
+ // check whether to emit structure henceforth
+ m_bEmitStructure = checkEmitStructure();
+
+ if( m_bEmitStructure ) // don't create nonexistent objects
+ {
+ rEle.m_nObject = createObject();
+ // update parent's kids list
+ m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(rEle.m_nObject);
+ }
+ return nNewId;
+}
+
+void PDFWriterImpl::endStructureElement()
+{
+ if( m_nCurrentPage < 0 )
+ return;
+
+ if( ! m_aContext.Tagged )
+ return;
+
+ if( m_nCurrentStructElement == 0 )
+ {
+ // hit the struct tree root, that means there is an endStructureElement
+ // without corresponding beginStructureElement
+ return;
+ }
+
+ // end the marked content sequence
+ endStructureElementMCSeq();
+
+ OStringBuffer aLine;
+ if (g_bDebugDisableCompression)
+ {
+ aLine.append( "endStructureElement " );
+ aLine.append( m_nCurrentStructElement );
+ aLine.append( ": " );
+ aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
+ if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
+ {
+ aLine.append( " aliased as \"" );
+ aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
+ aLine.append( '\"' );
+ }
+ }
+
+ // "end" the structure element, the parent becomes current element
+ m_nCurrentStructElement = m_aStructure[ m_nCurrentStructElement ].m_nParentElement;
+
+ // check whether to emit structure henceforth
+ m_bEmitStructure = checkEmitStructure();
+
+ if (g_bDebugDisableCompression && m_bEmitStructure)
+ {
+ emitComment( aLine.getStr() );
+ }
+}
+
+/*
+ * This function adds an internal structure list container to overcome the 8191 elements array limitation
+ * in kids element emission.
+ * Recursive function
+ *
+ */
+void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle )
+{
+ if( rEle.m_eType == PDFWriter::NonStructElement &&
+ rEle.m_nOwnElement != rEle.m_nParentElement )
+ return;
+
+ for (auto const& child : rEle.m_aChildren)
+ {
+ if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() )
+ {
+ PDFStructureElement& rChild = m_aStructure[ child ];
+ if( rChild.m_eType != PDFWriter::NonStructElement )
+ {
+ //triggered when a child of the rEle element is found
+ if( rChild.m_nParentElement == rEle.m_nOwnElement )
+ addInternalStructureContainer( rChild );//examine the child
+ else
+ {
+ OSL_FAIL( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" );
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure element with id " << child );
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " << child );
+ }
+ }
+
+ if( rEle.m_nOwnElement == rEle.m_nParentElement )
+ return;
+
+ if( rEle.m_aKids.empty() )
+ return;
+
+ if( rEle.m_aKids.size() <= ncMaxPDFArraySize ) return;
+
+ //then we need to add the containers for the kids elements
+ // a list to be used for the new kid element
+ std::list< PDFStructureElementKid > aNewKids;
+ std::list< sal_Int32 > aNewChildren;
+
+ // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?)
+ OString aAliasName("Div");
+ addRoleMap(aAliasName, PDFWriter::Division);
+
+ while( rEle.m_aKids.size() > ncMaxPDFArraySize )
+ {
+ sal_Int32 nCurrentStructElement = rEle.m_nOwnElement;
+ sal_Int32 nNewId = sal_Int32(m_aStructure.size());
+ m_aStructure.emplace_back( );
+ PDFStructureElement& rEleNew = m_aStructure.back();
+ rEleNew.m_aAlias = aAliasName;
+ rEleNew.m_eType = PDFWriter::Division; // a new Div type container
+ rEleNew.m_nOwnElement = nNewId;
+ rEleNew.m_nParentElement = nCurrentStructElement;
+ //inherit the same page as the first child to be reparented
+ rEleNew.m_nFirstPageObject = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject;
+ rEleNew.m_nObject = createObject();//assign a PDF object number
+ //add the object to the kid list of the parent
+ aNewKids.emplace_back( rEleNew.m_nObject );
+ aNewChildren.push_back( nNewId );
+
+ std::list< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() );
+ std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() );
+ advance( aChildEndIt, ncMaxPDFArraySize );
+ advance( aKidEndIt, ncMaxPDFArraySize );
+
+ rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(),
+ rEle.m_aKids,
+ rEle.m_aKids.begin(),
+ aKidEndIt );
+ rEleNew.m_aChildren.splice( rEleNew.m_aChildren.begin(),
+ rEle.m_aChildren,
+ rEle.m_aChildren.begin(),
+ aChildEndIt );
+ // set the kid's new parent
+ for (auto const& child : rEleNew.m_aChildren)
+ {
+ m_aStructure[ child ].m_nParentElement = nNewId;
+ }
+ }
+ //finally add the new kids resulting from the container added
+ rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() );
+ rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() );
+}
+
+bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle )
+{
+ bool bSuccess = false;
+
+ if( m_aContext.Tagged && nEle >= 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() )
+ {
+ // end eventual previous marked content sequence
+ endStructureElementMCSeq();
+
+ m_nCurrentStructElement = nEle;
+ m_bEmitStructure = checkEmitStructure();
+ if (g_bDebugDisableCompression)
+ {
+ OStringBuffer aLine( "setCurrentStructureElement " );
+ aLine.append( m_nCurrentStructElement );
+ aLine.append( ": " );
+ aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
+ if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
+ {
+ aLine.append( " aliased as \"" );
+ aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
+ aLine.append( '\"' );
+ }
+ if( ! m_bEmitStructure )
+ aLine.append( " (inside NonStruct)" );
+ emitComment( aLine.getStr() );
+ }
+ bSuccess = true;
+ }
+
+ return bSuccess;
+}
+
+bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal )
+{
+ if( !m_aContext.Tagged )
+ return false;
+
+ bool bInsert = false;
+ if( m_nCurrentStructElement > 0 && m_bEmitStructure )
+ {
+ PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
+ switch( eAttr )
+ {
+ case PDFWriter::Placement:
+ if( eVal == PDFWriter::Block ||
+ eVal == PDFWriter::Inline ||
+ eVal == PDFWriter::Before ||
+ eVal == PDFWriter::Start ||
+ eVal == PDFWriter::End )
+ bInsert = true;
+ break;
+ case PDFWriter::WritingMode:
+ if( eVal == PDFWriter::LrTb ||
+ eVal == PDFWriter::RlTb ||
+ eVal == PDFWriter::TbRl )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::TextAlign:
+ if( eVal == PDFWriter::Start ||
+ eVal == PDFWriter::Center ||
+ eVal == PDFWriter::End ||
+ eVal == PDFWriter::Justify )
+ {
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::List ||
+ eType == PDFWriter::ListItem ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableRow ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::Width:
+ case PDFWriter::Height:
+ if( eVal == PDFWriter::Auto )
+ {
+ if( eType == PDFWriter::Figure ||
+ eType == PDFWriter::Formula ||
+ eType == PDFWriter::Form ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::BlockAlign:
+ if( eVal == PDFWriter::Before ||
+ eVal == PDFWriter::Middle ||
+ eVal == PDFWriter::After ||
+ eVal == PDFWriter::Justify )
+ {
+ if( eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::InlineAlign:
+ if( eVal == PDFWriter::Start ||
+ eVal == PDFWriter::Center ||
+ eVal == PDFWriter::End )
+ {
+ if( eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::LineHeight:
+ if( eVal == PDFWriter::Normal ||
+ eVal == PDFWriter::Auto )
+ {
+ // only for ILSE and BLSE
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::List ||
+ eType == PDFWriter::ListItem ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableRow ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData ||
+ eType == PDFWriter::Span ||
+ eType == PDFWriter::Quote ||
+ eType == PDFWriter::Note ||
+ eType == PDFWriter::Reference ||
+ eType == PDFWriter::BibEntry ||
+ eType == PDFWriter::Code ||
+ eType == PDFWriter::Link )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::TextDecorationType:
+ if( eVal == PDFWriter::NONE ||
+ eVal == PDFWriter::Underline ||
+ eVal == PDFWriter::Overline ||
+ eVal == PDFWriter::LineThrough )
+ {
+ // only for ILSE and BLSE
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::List ||
+ eType == PDFWriter::ListItem ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableRow ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData ||
+ eType == PDFWriter::Span ||
+ eType == PDFWriter::Quote ||
+ eType == PDFWriter::Note ||
+ eType == PDFWriter::Reference ||
+ eType == PDFWriter::BibEntry ||
+ eType == PDFWriter::Code ||
+ eType == PDFWriter::Link )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::Scope:
+ if (eVal == PDFWriter::Row || eVal == PDFWriter::Column || eVal == PDFWriter::Both)
+ {
+ if (eType == PDFWriter::TableHeader
+ && m_aContext.Version != PDFWriter::PDFVersion::PDF_A_1
+ && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::ListNumbering:
+ if( eVal == PDFWriter::NONE ||
+ eVal == PDFWriter::Disc ||
+ eVal == PDFWriter::Circle ||
+ eVal == PDFWriter::Square ||
+ eVal == PDFWriter::Decimal ||
+ eVal == PDFWriter::UpperRoman ||
+ eVal == PDFWriter::LowerRoman ||
+ eVal == PDFWriter::UpperAlpha ||
+ eVal == PDFWriter::LowerAlpha )
+ {
+ if( eType == PDFWriter::List )
+ bInsert = true;
+ }
+ break;
+ default: break;
+ }
+ }
+
+ if( bInsert )
+ m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal );
+ else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
+ SAL_INFO("vcl.pdfwriter",
+ "rejecting setStructureAttribute( " << getAttributeTag( eAttr )
+ << ", " << getAttributeValueTag( eVal )
+ << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType )
+ << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
+ << ") element");
+
+ return bInsert;
+}
+
+bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
+{
+ if( ! m_aContext.Tagged )
+ return false;
+
+ bool bInsert = false;
+ if( m_nCurrentStructElement > 0 && m_bEmitStructure )
+ {
+ if( eAttr == PDFWriter::Language )
+ {
+ m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale();
+ return true;
+ }
+
+ PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
+ switch( eAttr )
+ {
+ case PDFWriter::SpaceBefore:
+ case PDFWriter::SpaceAfter:
+ case PDFWriter::StartIndent:
+ case PDFWriter::EndIndent:
+ // just for BLSE
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::List ||
+ eType == PDFWriter::ListItem ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableRow ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::TextIndent:
+ // paragraph like BLSE and additional elements
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::Width:
+ case PDFWriter::Height:
+ if( eType == PDFWriter::Figure ||
+ eType == PDFWriter::Formula ||
+ eType == PDFWriter::Form ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::LineHeight:
+ case PDFWriter::BaselineShift:
+ // only for ILSE and BLSE
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::List ||
+ eType == PDFWriter::ListItem ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableRow ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData ||
+ eType == PDFWriter::Span ||
+ eType == PDFWriter::Quote ||
+ eType == PDFWriter::Note ||
+ eType == PDFWriter::Reference ||
+ eType == PDFWriter::BibEntry ||
+ eType == PDFWriter::Code ||
+ eType == PDFWriter::Link )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::RowSpan:
+ case PDFWriter::ColSpan:
+ // only for table cells
+ if( eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::LinkAnnotation:
+ if( eType == PDFWriter::Link )
+ bInsert = true;
+ break;
+ default: break;
+ }
+ }
+
+ if( bInsert )
+ m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue );
+ else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
+ SAL_INFO("vcl.pdfwriter",
+ "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr )
+ << ", " << static_cast<int>(nValue)
+ << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType )
+ << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
+ << ") element");
+
+ return bInsert;
+}
+
+void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect )
+{
+ sal_Int32 nPageNr = m_nCurrentPage;
+ if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() || !m_aContext.Tagged )
+ return;
+
+ if( !(m_nCurrentStructElement > 0 && m_bEmitStructure) )
+ return;
+
+ PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
+ if( eType == PDFWriter::Figure ||
+ eType == PDFWriter::Formula ||
+ eType == PDFWriter::Form ||
+ eType == PDFWriter::Division ||
+ eType == PDFWriter::Table )
+ {
+ m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect;
+ // convert to default user space now, since the mapmode may change
+ m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox );
+ }
+}
+
+void PDFWriterImpl::setActualText( const OUString& rText )
+{
+ if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
+ {
+ m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText;
+ }
+}
+
+void PDFWriterImpl::setAlternateText( const OUString& rText )
+{
+ if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
+ {
+ m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText;
+ }
+}
+
+void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
+{
+ if( nPageNr < 0 )
+ nPageNr = m_nCurrentPage;
+
+ if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
+ return;
+
+ m_aPages[ nPageNr ].m_eTransition = eType;
+ m_aPages[ nPageNr ].m_nTransTime = nMilliSec;
+}
+
+void PDFWriterImpl::ensureUniqueRadioOnValues()
+{
+ // loop over radio groups
+ for (auto const& group : m_aRadioGroupWidgets)
+ {
+ PDFWidget& rGroupWidget = m_aWidgets[ group.second ];
+ // check whether all kids have a unique OnValue
+ std::unordered_map< OUString, sal_Int32 > aOnValues;
+ bool bIsUnique = true;
+ for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
+ {
+ const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue;
+ SAL_INFO("vcl.pdfwriter", "OnValue: " << rVal);
+ if( aOnValues.find( rVal ) == aOnValues.end() )
+ {
+ aOnValues[ rVal ] = 1;
+ }
+ else
+ {
+ bIsUnique = false;
+ break;
+ }
+ }
+ if( ! bIsUnique )
+ {
+ SAL_INFO("vcl.pdfwriter", "enforcing unique OnValues" );
+ // make unique by using ascending OnValues
+ int nKid = 0;
+ for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
+ {
+ PDFWidget& rKid = m_aWidgets[nKidIndex];
+ rKid.m_aOnValue = OUString::number( nKid+1 );
+ if( rKid.m_aValue != "Off" )
+ rKid.m_aValue = rKid.m_aOnValue;
+ ++nKid;
+ }
+ }
+ // finally move the "Yes" appearance to the OnValue appearance
+ for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
+ {
+ PDFWidget& rKid = m_aWidgets[nKidIndex];
+ if ( !rKid.m_aOnValue.isEmpty() )
+ {
+ auto app_it = rKid.m_aAppearances.find( "N" );
+ if( app_it != rKid.m_aAppearances.end() )
+ {
+ auto stream_it = app_it->second.find( "Yes" );
+ if( stream_it != app_it->second.end() )
+ {
+ SvMemoryStream* pStream = stream_it->second;
+ app_it->second.erase( stream_it );
+ OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 );
+ appendName( rKid.m_aOnValue, aBuf );
+ (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
+ }
+ else
+ SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" );
+ }
+ }
+
+ if ( !rKid.m_aOffValue.isEmpty() )
+ {
+ auto app_it = rKid.m_aAppearances.find( "N" );
+ if( app_it != rKid.m_aAppearances.end() )
+ {
+ auto stream_it = app_it->second.find( "Off" );
+ if( stream_it != app_it->second.end() )
+ {
+ SvMemoryStream* pStream = stream_it->second;
+ app_it->second.erase( stream_it );
+ OStringBuffer aBuf( rKid.m_aOffValue.getLength()*2 );
+ appendName( rKid.m_aOffValue, aBuf );
+ (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
+ }
+ else
+ SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Off\" stream" );
+ }
+ }
+
+ // update selected radio button
+ if( rKid.m_aValue != "Off" )
+ {
+ rGroupWidget.m_aValue = rKid.m_aValue;
+ }
+ }
+ }
+}
+
+sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn )
+{
+ sal_Int32 nRadioGroupWidget = -1;
+
+ std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup );
+
+ if( it == m_aRadioGroupWidgets.end() )
+ {
+ m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget =
+ sal_Int32(m_aWidgets.size());
+
+ // new group, insert the radiobutton
+ m_aWidgets.emplace_back( );
+ m_aWidgets.back().m_nObject = createObject();
+ m_aWidgets.back().m_nPage = m_nCurrentPage;
+ m_aWidgets.back().m_eType = PDFWriter::RadioButton;
+ m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup;
+ m_aWidgets.back().m_nFlags |= 0x0000C000; // NoToggleToOff and Radio bits
+
+ createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn );
+ }
+ else
+ nRadioGroupWidget = it->second;
+
+ return nRadioGroupWidget;
+}
+
+sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr )
+{
+ if( nPageNr < 0 )
+ nPageNr = m_nCurrentPage;
+
+ if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
+ return -1;
+
+ bool sigHidden(true);
+ sal_Int32 nNewWidget = m_aWidgets.size();
+ m_aWidgets.emplace_back( );
+
+ m_aWidgets.back().m_nObject = createObject();
+ m_aWidgets.back().m_aRect = rControl.Location;
+ m_aWidgets.back().m_nPage = nPageNr;
+ m_aWidgets.back().m_eType = rControl.getType();
+
+ sal_Int32 nRadioGroupWidget = -1;
+ // for unknown reasons the radio buttons of a radio group must not have a
+ // field name, else the buttons are in fact check boxes -
+ // that is multiple buttons of the radio group can be selected
+ if( rControl.getType() == PDFWriter::RadioButton )
+ nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) );
+ else
+ {
+ createWidgetFieldName( nNewWidget, rControl );
+ }
+
+ // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid
+ PDFWidget& rNewWidget = m_aWidgets[nNewWidget];
+ rNewWidget.m_aDescription = rControl.Description;
+ rNewWidget.m_aText = rControl.Text;
+ rNewWidget.m_nTextStyle = rControl.TextStyle &
+ ( DrawTextFlags::Left | DrawTextFlags::Center | DrawTextFlags::Right | DrawTextFlags::Top |
+ DrawTextFlags::VCenter | DrawTextFlags::Bottom |
+ DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
+ rNewWidget.m_nTabOrder = rControl.TabOrder;
+
+ // various properties are set via the flags (/Ff) property of the field dict
+ if( rControl.ReadOnly )
+ rNewWidget.m_nFlags |= 1;
+ if( rControl.getType() == PDFWriter::PushButton )
+ {
+ const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl);
+ if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
+ rNewWidget.m_nTextStyle =
+ DrawTextFlags::Center | DrawTextFlags::VCenter |
+ DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
+
+ rNewWidget.m_nFlags |= 0x00010000;
+ if( !rBtn.URL.isEmpty() )
+ rNewWidget.m_aListEntries.push_back( rBtn.URL );
+ rNewWidget.m_bSubmit = rBtn.Submit;
+ rNewWidget.m_bSubmitGet = rBtn.SubmitGet;
+ rNewWidget.m_nDest = rBtn.Dest;
+ createDefaultPushButtonAppearance( rNewWidget, rBtn );
+ }
+ else if( rControl.getType() == PDFWriter::RadioButton )
+ {
+ const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl);
+ if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
+ rNewWidget.m_nTextStyle =
+ DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
+ /* PDF sees a RadioButton group as one radio button with
+ * children which are in turn check boxes
+ *
+ * so we need to create a radio button on demand for a new group
+ * and insert a checkbox for each RadioButtonWidget as its child
+ */
+ rNewWidget.m_eType = PDFWriter::CheckBox;
+ rNewWidget.m_nRadioGroup = rBtn.RadioGroup;
+
+ SAL_WARN_IF( nRadioGroupWidget < 0 || o3tl::make_unsigned(nRadioGroupWidget) >= m_aWidgets.size(), "vcl.pdfwriter", "no radio group parent" );
+
+ PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget];
+ rRadioButton.m_aKids.push_back( rNewWidget.m_nObject );
+ rRadioButton.m_aKidsIndex.push_back( nNewWidget );
+ rNewWidget.m_nParent = rRadioButton.m_nObject;
+
+ rNewWidget.m_aValue = "Off";
+ rNewWidget.m_aOnValue = rBtn.OnValue;
+ rNewWidget.m_aOffValue = rBtn.OffValue;
+ if( rRadioButton.m_aValue.isEmpty() && rBtn.Selected )
+ {
+ rNewWidget.m_aValue = rNewWidget.m_aOnValue;
+ rRadioButton.m_aValue = rNewWidget.m_aOnValue;
+ }
+ createDefaultRadioButtonAppearance( rNewWidget, rBtn );
+
+ // union rect of radio group
+ tools::Rectangle aRect = rNewWidget.m_aRect;
+ m_aPages[ nPageNr ].convertRect( aRect );
+ rRadioButton.m_aRect.Union( aRect );
+ }
+ else if( rControl.getType() == PDFWriter::CheckBox )
+ {
+ const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl);
+ if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
+ rNewWidget.m_nTextStyle =
+ DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
+
+ rNewWidget.m_aValue
+ = rBox.Checked ? std::u16string_view(u"Yes") : std::u16string_view(u"Off" );
+ rNewWidget.m_aOnValue = rBox.OnValue;
+ rNewWidget.m_aOffValue = rBox.OffValue;
+ // create default appearance before m_aRect gets transformed
+ createDefaultCheckBoxAppearance( rNewWidget, rBox );
+ }
+ else if( rControl.getType() == PDFWriter::ListBox )
+ {
+ if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
+ rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
+
+ const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl);
+ rNewWidget.m_aListEntries = rLstBox.Entries;
+ rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries;
+ rNewWidget.m_aValue = rLstBox.Text;
+ if( rLstBox.DropDown )
+ rNewWidget.m_nFlags |= 0x00020000;
+ if( rLstBox.MultiSelect && !rLstBox.DropDown && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ rNewWidget.m_nFlags |= 0x00200000;
+
+ createDefaultListBoxAppearance( rNewWidget, rLstBox );
+ }
+ else if( rControl.getType() == PDFWriter::ComboBox )
+ {
+ if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
+ rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
+
+ const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl);
+ rNewWidget.m_aValue = rBox.Text;
+ rNewWidget.m_aListEntries = rBox.Entries;
+ rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag
+
+ PDFWriter::ListBoxWidget aLBox;
+ aLBox.Name = rBox.Name;
+ aLBox.Description = rBox.Description;
+ aLBox.Text = rBox.Text;
+ aLBox.TextStyle = rBox.TextStyle;
+ aLBox.ReadOnly = rBox.ReadOnly;
+ aLBox.Border = rBox.Border;
+ aLBox.BorderColor = rBox.BorderColor;
+ aLBox.Background = rBox.Background;
+ aLBox.BackgroundColor = rBox.BackgroundColor;
+ aLBox.TextFont = rBox.TextFont;
+ aLBox.TextColor = rBox.TextColor;
+ aLBox.DropDown = true;
+ aLBox.MultiSelect = false;
+ aLBox.Entries = rBox.Entries;
+
+ createDefaultListBoxAppearance( rNewWidget, aLBox );
+ }
+ else if( rControl.getType() == PDFWriter::Edit )
+ {
+ if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
+ rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
+
+ const PDFWriter::EditWidget& rEdit = static_cast<const PDFWriter::EditWidget&>(rControl);
+ if( rEdit.MultiLine )
+ {
+ rNewWidget.m_nFlags |= 0x00001000;
+ rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
+ }
+ if( rEdit.Password )
+ rNewWidget.m_nFlags |= 0x00002000;
+ if( rEdit.FileSelect && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ rNewWidget.m_nFlags |= 0x00100000;
+ rNewWidget.m_nMaxLen = rEdit.MaxLen;
+ rNewWidget.m_nFormat = rEdit.Format;
+ rNewWidget.m_aCurrencySymbol = rEdit.CurrencySymbol;
+ rNewWidget.m_nDecimalAccuracy = rEdit.DecimalAccuracy;
+ rNewWidget.m_bPrependCurrencySymbol = rEdit.PrependCurrencySymbol;
+ rNewWidget.m_aTimeFormat = rEdit.TimeFormat;
+ rNewWidget.m_aDateFormat = rEdit.DateFormat;
+ rNewWidget.m_aValue = rEdit.Text;
+
+ createDefaultEditAppearance( rNewWidget, rEdit );
+ }
+#if HAVE_FEATURE_NSS
+ else if( rControl.getType() == PDFWriter::Signature)
+ {
+ sigHidden = true;
+
+ rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0);
+
+ m_nSignatureObject = createObject();
+ rNewWidget.m_aValue = OUString::number( m_nSignatureObject );
+ rNewWidget.m_aValue += " 0 R";
+ // let's add a fake appearance
+ rNewWidget.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
+ }
+#endif
+
+ // if control is a hidden signature, do not convert coordinates since we
+ // need /Rect [ 0 0 0 0 ]
+ if ( ! ( ( rControl.getType() == PDFWriter::Signature ) && sigHidden ) )
+ {
+ // convert to default user space now, since the mapmode may change
+ // note: create default appearances before m_aRect gets transformed
+ m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect );
+ }
+
+ // insert widget to page's annotation list
+ m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject );
+
+ return nNewWidget;
+}
+
+void PDFWriterImpl::addStream( const OUString& rMimeType, PDFOutputStream* pStream )
+{
+ if( pStream )
+ {
+ m_aAdditionalStreams.emplace_back( );
+ PDFAddStream& rStream = m_aAdditionalStreams.back();
+ rStream.m_aMimeType = !rMimeType.isEmpty()
+ ? rMimeType
+ : OUString( "application/octet-stream" );
+ rStream.m_pStream = pStream;
+ rStream.m_bCompress = false;
+ }
+}
+
+void PDFWriterImpl::MARK( const char* pString )
+{
+ beginStructureElementMCSeq();
+ if (g_bDebugDisableCompression)
+ emitComment( pString );
+}
+
+sal_Int32 ReferenceXObjectEmit::getObject() const
+{
+ if (m_nFormObject > 0)
+ return m_nFormObject;
+ else
+ return m_nBitmapObject;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfwriter_impl2.cxx b/vcl/source/gdi/pdfwriter_impl2.cxx
new file mode 100644
index 000000000..f4c60a661
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter_impl2.cxx
@@ -0,0 +1,1990 @@
+/* -*- 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 <pdf/pdfwriter_impl.hxx>
+
+#include <vcl/pdfextoutdevdata.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/graph.hxx>
+
+#include <unotools/streamwrap.hxx>
+
+#include <tools/helpers.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+
+#include <comphelper/fileformat.h>
+#include <comphelper/hash.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+#include <com/sun/star/graphic/GraphicProvider.hpp>
+#include <com/sun/star/graphic/XGraphicProvider.hpp>
+#include <com/sun/star/beans/XMaterialHolder.hpp>
+
+#include <cppuhelper/implbase.hxx>
+#include <o3tl/unit_conversion.hxx>
+
+#include <sal/log.hxx>
+#include <memory>
+
+using namespace vcl;
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::beans;
+
+static bool lcl_canUsePDFAxialShading(const Gradient& rGradient);
+
+void PDFWriterImpl::implWriteGradient( const tools::PolyPolygon& i_rPolyPoly, const Gradient& i_rGradient,
+ VirtualDevice* i_pDummyVDev, const vcl::PDFWriter::PlayMetafileContext& i_rContext )
+{
+ GDIMetaFile aTmpMtf;
+ Gradient aGradient(i_rGradient);
+
+ aGradient.AddGradientActions( i_rPolyPoly.GetBoundRect(), aTmpMtf );
+
+ m_rOuterFace.Push();
+ m_rOuterFace.IntersectClipRegion( i_rPolyPoly.getB2DPolyPolygon() );
+ playMetafile( aTmpMtf, nullptr, i_rContext, i_pDummyVDev );
+ m_rOuterFace.Pop();
+}
+
+void PDFWriterImpl::implWriteBitmapEx( const Point& i_rPoint, const Size& i_rSize, const BitmapEx& i_rBitmapEx, const Graphic& i_Graphic,
+ VirtualDevice const * i_pDummyVDev, const vcl::PDFWriter::PlayMetafileContext& i_rContext )
+{
+ if ( i_rBitmapEx.IsEmpty() || !i_rSize.Width() || !i_rSize.Height() )
+ return;
+
+ BitmapEx aBitmapEx( i_rBitmapEx );
+ Point aPoint( i_rPoint );
+ Size aSize( i_rSize );
+
+ // #i19065# Negative sizes have mirror semantics on
+ // OutputDevice. BitmapEx and co. have no idea about that, so
+ // perform that _before_ doing anything with aBitmapEx.
+ BmpMirrorFlags nMirrorFlags(BmpMirrorFlags::NONE);
+ if( aSize.Width() < 0 )
+ {
+ aSize.setWidth( aSize.Width() * -1 );
+ aPoint.AdjustX( -(aSize.Width()) );
+ nMirrorFlags |= BmpMirrorFlags::Horizontal;
+ }
+ if( aSize.Height() < 0 )
+ {
+ aSize.setHeight( aSize.Height() * -1 );
+ aPoint.AdjustY( -(aSize.Height()) );
+ nMirrorFlags |= BmpMirrorFlags::Vertical;
+ }
+
+ if( nMirrorFlags != BmpMirrorFlags::NONE )
+ {
+ aBitmapEx.Mirror( nMirrorFlags );
+ }
+
+ bool bIsJpeg = false, bIsPng = false;
+ if( i_Graphic.GetType() != GraphicType::NONE && i_Graphic.GetBitmapEx() == aBitmapEx )
+ {
+ GfxLinkType eType = i_Graphic.GetGfxLink().GetType();
+ bIsJpeg = (eType == GfxLinkType::NativeJpg);
+ bIsPng = (eType == GfxLinkType::NativePng);
+ }
+
+ // Do not downsample images smaller than 50x50px.
+ const Size aBmpSize(aBitmapEx.GetSizePixel());
+ if (i_rContext.m_nMaxImageResolution > 50 && aBmpSize.getWidth() > 50
+ && aBmpSize.getHeight() > 50)
+ {
+ // do downsampling if necessary
+ const Size aDstSizeTwip( i_pDummyVDev->PixelToLogic(i_pDummyVDev->LogicToPixel(aSize), MapMode(MapUnit::MapTwip)) );
+ const double fBmpPixelX = aBmpSize.Width();
+ const double fBmpPixelY = aBmpSize.Height();
+ const double fMaxPixelX
+ = o3tl::convert<double>(aDstSizeTwip.Width(), o3tl::Length::twip, o3tl::Length::in)
+ * i_rContext.m_nMaxImageResolution;
+ const double fMaxPixelY
+ = o3tl::convert<double>(aDstSizeTwip.Height(), o3tl::Length::twip, o3tl::Length::in)
+ * i_rContext.m_nMaxImageResolution;
+
+ // check, if the bitmap DPI exceeds the maximum DPI (allow 4 pixel rounding tolerance)
+ if( ( ( fBmpPixelX > ( fMaxPixelX + 4 ) ) ||
+ ( fBmpPixelY > ( fMaxPixelY + 4 ) ) ) &&
+ ( fBmpPixelY > 0.0 ) && ( fMaxPixelY > 0.0 ) )
+ {
+ // do scaling
+ Size aNewBmpSize;
+ const double fBmpWH = fBmpPixelX / fBmpPixelY;
+ const double fMaxWH = fMaxPixelX / fMaxPixelY;
+
+ if( fBmpWH < fMaxWH )
+ {
+ aNewBmpSize.setWidth( FRound( fMaxPixelY * fBmpWH ) );
+ aNewBmpSize.setHeight( FRound( fMaxPixelY ) );
+ }
+ else if( fBmpWH > 0.0 )
+ {
+ aNewBmpSize.setWidth( FRound( fMaxPixelX ) );
+ aNewBmpSize.setHeight( FRound( fMaxPixelX / fBmpWH) );
+ }
+
+ if( aNewBmpSize.Width() && aNewBmpSize.Height() )
+ {
+ // #i121233# Use best quality for PDF exports
+ aBitmapEx.Scale( aNewBmpSize, BmpScaleFlag::BestQuality );
+ }
+ else
+ {
+ aBitmapEx.SetEmpty();
+ }
+ }
+ }
+
+ const Size aSizePixel( aBitmapEx.GetSizePixel() );
+ if ( !(aSizePixel.Width() && aSizePixel.Height()) )
+ return;
+
+ if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
+ {
+ auto ePixelFormat = aBitmapEx.GetBitmap().getPixelFormat();
+ if (ePixelFormat != vcl::PixelFormat::N1_BPP)
+ aBitmapEx.Convert(BmpConversion::N8BitGreys);
+ }
+ bool bUseJPGCompression = !i_rContext.m_bOnlyLosslessCompression;
+ if ( bIsPng || ( aSizePixel.Width() < 32 ) || ( aSizePixel.Height() < 32 ) )
+ bUseJPGCompression = false;
+
+ auto pStrm=std::make_shared<SvMemoryStream>();
+ AlphaMask aAlphaMask;
+
+ bool bTrueColorJPG = true;
+ if ( bUseJPGCompression )
+ {
+ // TODO this checks could be done much earlier, saving us
+ // from trying conversion & stores before...
+ if ( !aBitmapEx.IsAlpha() )
+ {
+ const auto& rCacheEntry=m_aPDFBmpCache.find(
+ aBitmapEx.GetChecksum());
+ if ( rCacheEntry != m_aPDFBmpCache.end() )
+ {
+ m_rOuterFace.DrawJPGBitmap( *rCacheEntry->second, true, aSizePixel,
+ tools::Rectangle( aPoint, aSize ), aAlphaMask, i_Graphic );
+ return;
+ }
+ }
+ sal_uInt32 nZippedFileSize = 0; // sj: we will calculate the filesize of a zipped bitmap
+ if ( !bIsJpeg ) // to determine if jpeg compression is useful
+ {
+ SvMemoryStream aTemp;
+ aTemp.SetCompressMode( aTemp.GetCompressMode() | SvStreamCompressFlags::ZBITMAP );
+ aTemp.SetVersion( SOFFICE_FILEFORMAT_40 ); // sj: up from version 40 our bitmap stream operator
+ WriteDIBBitmapEx(aBitmapEx, aTemp); // is capable of zlib stream compression
+ nZippedFileSize = aTemp.TellEnd();
+ }
+ if ( aBitmapEx.IsAlpha() )
+ aAlphaMask = aBitmapEx.GetAlpha();
+ Graphic aGraphic(BitmapEx(aBitmapEx.GetBitmap()));
+
+ Sequence< PropertyValue > aFilterData{
+ comphelper::makePropertyValue("Quality", sal_Int32(i_rContext.m_nJPEGQuality)),
+ comphelper::makePropertyValue("ColorMode", sal_Int32(0))
+ };
+
+ try
+ {
+ uno::Reference < io::XStream > xStream = new utl::OStreamWrapper( *pStrm );
+ uno::Reference< io::XSeekable > xSeekable( xStream, UNO_QUERY_THROW );
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ uno::Reference< graphic::XGraphicProvider > xGraphicProvider( graphic::GraphicProvider::create(xContext) );
+ uno::Reference< graphic::XGraphic > xGraphic( aGraphic.GetXGraphic() );
+ uno::Reference < io::XOutputStream > xOut( xStream->getOutputStream() );
+ uno::Sequence< beans::PropertyValue > aOutMediaProperties{
+ comphelper::makePropertyValue("OutputStream", xOut),
+ comphelper::makePropertyValue("MimeType", OUString("image/jpeg")),
+ comphelper::makePropertyValue("FilterData", aFilterData)
+ };
+ xGraphicProvider->storeGraphic( xGraphic, aOutMediaProperties );
+ xOut->flush();
+ if ( !bIsJpeg && xSeekable->getLength() > nZippedFileSize )
+ {
+ bUseJPGCompression = false;
+ }
+ else
+ {
+ pStrm->Seek( STREAM_SEEK_TO_END );
+
+ xSeekable->seek( 0 );
+ Sequence< PropertyValue > aArgs{ comphelper::makePropertyValue("InputStream",
+ xStream) };
+ uno::Reference< XPropertySet > xPropSet( xGraphicProvider->queryGraphicDescriptor( aArgs ) );
+ if ( xPropSet.is() )
+ {
+ sal_Int16 nBitsPerPixel = 24;
+ if ( xPropSet->getPropertyValue("BitsPerPixel") >>= nBitsPerPixel )
+ {
+ bTrueColorJPG = nBitsPerPixel != 8;
+ }
+ }
+ }
+ }
+ catch( uno::Exception& )
+ {
+ bUseJPGCompression = false;
+ }
+ }
+ if ( bUseJPGCompression )
+ {
+ m_rOuterFace.DrawJPGBitmap( *pStrm, bTrueColorJPG, aSizePixel, tools::Rectangle( aPoint, aSize ), aAlphaMask, i_Graphic );
+ if (!aBitmapEx.IsAlpha() && bTrueColorJPG)
+ {
+ // Cache last jpeg export
+ m_aPDFBmpCache.insert(
+ {aBitmapEx.GetChecksum(), pStrm});
+ }
+ }
+ else if ( aBitmapEx.IsAlpha() )
+ m_rOuterFace.DrawBitmapEx( aPoint, aSize, aBitmapEx );
+ else
+ m_rOuterFace.DrawBitmap( aPoint, aSize, aBitmapEx.GetBitmap(), i_Graphic );
+
+}
+
+void PDFWriterImpl::playMetafile( const GDIMetaFile& i_rMtf, vcl::PDFExtOutDevData* i_pOutDevData, const vcl::PDFWriter::PlayMetafileContext& i_rContext, VirtualDevice* pDummyVDev )
+{
+ bool bAssertionFired( false );
+
+ ScopedVclPtr<VirtualDevice> xPrivateDevice;
+ if( ! pDummyVDev )
+ {
+ xPrivateDevice.disposeAndReset(VclPtr<VirtualDevice>::Create());
+ pDummyVDev = xPrivateDevice.get();
+ pDummyVDev->EnableOutput( false );
+ pDummyVDev->SetMapMode( i_rMtf.GetPrefMapMode() );
+ }
+ const GDIMetaFile& aMtf( i_rMtf );
+
+ for( sal_uInt32 i = 0, nCount = aMtf.GetActionSize(); i < nCount; )
+ {
+ if ( !i_pOutDevData || !i_pOutDevData->PlaySyncPageAct( m_rOuterFace, i, aMtf ) )
+ {
+ const MetaAction* pAction = aMtf.GetAction( i );
+ const MetaActionType nType = pAction->GetType();
+
+ switch( nType )
+ {
+ case MetaActionType::PIXEL:
+ {
+ const MetaPixelAction* pA = static_cast<const MetaPixelAction*>(pAction);
+ m_rOuterFace.DrawPixel( pA->GetPoint(), pA->GetColor() );
+ }
+ break;
+
+ case MetaActionType::POINT:
+ {
+ const MetaPointAction* pA = static_cast<const MetaPointAction*>(pAction);
+ m_rOuterFace.DrawPixel( pA->GetPoint() );
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ const MetaLineAction* pA = static_cast<const MetaLineAction*>(pAction);
+ if ( pA->GetLineInfo().IsDefault() )
+ m_rOuterFace.DrawLine( pA->GetStartPoint(), pA->GetEndPoint() );
+ else
+ m_rOuterFace.DrawLine( pA->GetStartPoint(), pA->GetEndPoint(), pA->GetLineInfo() );
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ const MetaRectAction* pA = static_cast<const MetaRectAction*>(pAction);
+ m_rOuterFace.DrawRect( pA->GetRect() );
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ const MetaRoundRectAction* pA = static_cast<const MetaRoundRectAction*>(pAction);
+ m_rOuterFace.DrawRect( pA->GetRect(), pA->GetHorzRound(), pA->GetVertRound() );
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ const MetaEllipseAction* pA = static_cast<const MetaEllipseAction*>(pAction);
+ m_rOuterFace.DrawEllipse( pA->GetRect() );
+ }
+ break;
+
+ case MetaActionType::ARC:
+ {
+ const MetaArcAction* pA = static_cast<const MetaArcAction*>(pAction);
+ m_rOuterFace.DrawArc( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() );
+ }
+ break;
+
+ case MetaActionType::PIE:
+ {
+ const MetaArcAction* pA = static_cast<const MetaArcAction*>(pAction);
+ m_rOuterFace.DrawPie( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() );
+ }
+ break;
+
+ case MetaActionType::CHORD:
+ {
+ const MetaChordAction* pA = static_cast<const MetaChordAction*>(pAction);
+ m_rOuterFace.DrawChord( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() );
+ }
+ break;
+
+ case MetaActionType::POLYGON:
+ {
+ const MetaPolygonAction* pA = static_cast<const MetaPolygonAction*>(pAction);
+ m_rOuterFace.DrawPolygon( pA->GetPolygon() );
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ const MetaPolyLineAction* pA = static_cast<const MetaPolyLineAction*>(pAction);
+ if ( pA->GetLineInfo().IsDefault() )
+ m_rOuterFace.DrawPolyLine( pA->GetPolygon() );
+ else
+ m_rOuterFace.DrawPolyLine( pA->GetPolygon(), pA->GetLineInfo() );
+ }
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ {
+ const MetaPolyPolygonAction* pA = static_cast<const MetaPolyPolygonAction*>(pAction);
+ m_rOuterFace.DrawPolyPolygon( pA->GetPolyPolygon() );
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pAction);
+ const Gradient& rGradient = pA->GetGradient();
+ if (lcl_canUsePDFAxialShading(rGradient))
+ {
+ m_rOuterFace.DrawGradient( pA->GetRect(), rGradient );
+ }
+ else
+ {
+ const tools::PolyPolygon aPolyPoly( pA->GetRect() );
+ implWriteGradient( aPolyPoly, rGradient, pDummyVDev, i_rContext );
+ }
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ {
+ const MetaGradientExAction* pA = static_cast<const MetaGradientExAction*>(pAction);
+ const Gradient& rGradient = pA->GetGradient();
+
+ if (lcl_canUsePDFAxialShading(rGradient))
+ m_rOuterFace.DrawGradient( pA->GetPolyPolygon(), rGradient );
+ else
+ implWriteGradient( pA->GetPolyPolygon(), rGradient, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ const MetaHatchAction* pA = static_cast<const MetaHatchAction*>(pAction);
+ m_rOuterFace.DrawHatch( pA->GetPolyPolygon(), pA->GetHatch() );
+ }
+ break;
+
+ case MetaActionType::Transparent:
+ {
+ const MetaTransparentAction* pA = static_cast<const MetaTransparentAction*>(pAction);
+ m_rOuterFace.DrawTransparent( pA->GetPolyPolygon(), pA->GetTransparence() );
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pAction);
+
+ GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() );
+ const Point& rPos = pA->GetPoint();
+ const Size& rSize= pA->GetSize();
+ const Gradient& rTransparenceGradient = pA->GetGradient();
+
+ // special case constant alpha value
+ if( rTransparenceGradient.GetStartColor() == rTransparenceGradient.GetEndColor() )
+ {
+ const Color aTransCol( rTransparenceGradient.GetStartColor() );
+ const sal_uInt16 nTransPercent = aTransCol.GetLuminance() * 100 / 255;
+ m_rOuterFace.BeginTransparencyGroup();
+
+ // tdf#138826 adjust the aTmpMtf to start at rPos (see also #i112076#)
+ Point aMtfOrigin(aTmpMtf.GetPrefMapMode().GetOrigin());
+ if (rPos != aMtfOrigin)
+ aTmpMtf.Move(rPos.X() - aMtfOrigin.X(), rPos.Y() - aMtfOrigin.Y());
+
+ playMetafile( aTmpMtf, nullptr, i_rContext, pDummyVDev );
+ m_rOuterFace.EndTransparencyGroup( tools::Rectangle( rPos, rSize ), nTransPercent );
+ }
+ else
+ {
+ const Size aDstSizeTwip( pDummyVDev->PixelToLogic(pDummyVDev->LogicToPixel(rSize), MapMode(MapUnit::MapTwip)) );
+
+ // i#115962# Always use at least 300 DPI for bitmap conversion of transparence gradients,
+ // else the quality is not acceptable (see bugdoc as example)
+ sal_Int32 nMaxBmpDPI(300);
+
+ if( i_rContext.m_nMaxImageResolution > 50 )
+ {
+ if ( nMaxBmpDPI > i_rContext.m_nMaxImageResolution )
+ nMaxBmpDPI = i_rContext.m_nMaxImageResolution;
+ }
+ const sal_Int32 nPixelX = o3tl::convert<double>(aDstSizeTwip.Width(), o3tl::Length::twip, o3tl::Length::in) * nMaxBmpDPI;
+ const sal_Int32 nPixelY = o3tl::convert<double>(aDstSizeTwip.Height(), o3tl::Length::twip, o3tl::Length::in) * nMaxBmpDPI;
+ if ( nPixelX && nPixelY )
+ {
+ Size aDstSizePixel( nPixelX, nPixelY );
+ ScopedVclPtrInstance<VirtualDevice> xVDev;
+ if( xVDev->SetOutputSizePixel( aDstSizePixel ) )
+ {
+ Bitmap aPaint, aMask;
+ AlphaMask aAlpha;
+ Point aPoint;
+
+ MapMode aMapMode( pDummyVDev->GetMapMode() );
+ aMapMode.SetOrigin( aPoint );
+ xVDev->SetMapMode( aMapMode );
+ Size aDstSize( xVDev->PixelToLogic( aDstSizePixel ) );
+
+ Point aMtfOrigin( aTmpMtf.GetPrefMapMode().GetOrigin() );
+ if ( aMtfOrigin.X() || aMtfOrigin.Y() )
+ aTmpMtf.Move( -aMtfOrigin.X(), -aMtfOrigin.Y() );
+ double fScaleX = static_cast<double>(aDstSize.Width()) / static_cast<double>(aTmpMtf.GetPrefSize().Width());
+ double fScaleY = static_cast<double>(aDstSize.Height()) / static_cast<double>(aTmpMtf.GetPrefSize().Height());
+ if( fScaleX != 1.0 || fScaleY != 1.0 )
+ aTmpMtf.Scale( fScaleX, fScaleY );
+ aTmpMtf.SetPrefMapMode( aMapMode );
+
+ // create paint bitmap
+ aTmpMtf.WindStart();
+ aTmpMtf.Play(*xVDev, aPoint, aDstSize);
+ aTmpMtf.WindStart();
+
+ xVDev->EnableMapMode( false );
+ aPaint = xVDev->GetBitmap( aPoint, aDstSizePixel );
+ xVDev->EnableMapMode();
+
+ // create mask bitmap
+ xVDev->SetLineColor( COL_BLACK );
+ xVDev->SetFillColor( COL_BLACK );
+ xVDev->DrawRect( tools::Rectangle( aPoint, aDstSize ) );
+ xVDev->SetDrawMode( DrawModeFlags::WhiteLine | DrawModeFlags::WhiteFill | DrawModeFlags::WhiteText |
+ DrawModeFlags::WhiteBitmap | DrawModeFlags::WhiteGradient );
+ aTmpMtf.WindStart();
+ aTmpMtf.Play(*xVDev, aPoint, aDstSize);
+ aTmpMtf.WindStart();
+ xVDev->EnableMapMode( false );
+ aMask = xVDev->GetBitmap( aPoint, aDstSizePixel );
+ xVDev->EnableMapMode();
+
+ // create alpha mask from gradient
+ xVDev->SetDrawMode( DrawModeFlags::GrayGradient );
+ xVDev->DrawGradient( tools::Rectangle( aPoint, aDstSize ), rTransparenceGradient );
+ xVDev->SetDrawMode( DrawModeFlags::Default );
+ xVDev->EnableMapMode( false );
+ xVDev->DrawMask( aPoint, aDstSizePixel, aMask, COL_WHITE );
+ aAlpha = xVDev->GetBitmap( aPoint, aDstSizePixel );
+
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( rPos, rSize, BitmapEx( aPaint, aAlpha ), aGraphic, pDummyVDev, i_rContext );
+ }
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pAction);
+ const GDIMetaFile& aSubstitute( pA->GetSubstitute() );
+
+ m_rOuterFace.Push();
+ pDummyVDev->Push();
+
+ MapMode aMapMode( aSubstitute.GetPrefMapMode() );
+ Size aOutSize( OutputDevice::LogicToLogic( pA->GetSize(), pDummyVDev->GetMapMode(), aMapMode ) );
+ aMapMode.SetScaleX( Fraction( aOutSize.Width(), aSubstitute.GetPrefSize().Width() ) );
+ aMapMode.SetScaleY( Fraction( aOutSize.Height(), aSubstitute.GetPrefSize().Height() ) );
+ aMapMode.SetOrigin( OutputDevice::LogicToLogic( pA->GetPoint(), pDummyVDev->GetMapMode(), aMapMode ) );
+
+ m_rOuterFace.SetMapMode( aMapMode );
+ pDummyVDev->SetMapMode( aMapMode );
+ playMetafile( aSubstitute, nullptr, i_rContext, pDummyVDev );
+ pDummyVDev->Pop();
+ m_rOuterFace.Pop();
+ }
+ break;
+
+ case MetaActionType::COMMENT:
+ if( ! i_rContext.m_bTransparenciesWereRemoved )
+ {
+ const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
+
+ if( pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN"))
+ {
+ const MetaGradientExAction* pGradAction = nullptr;
+ bool bDone = false;
+
+ while( !bDone && ( ++i < nCount ) )
+ {
+ pAction = aMtf.GetAction( i );
+
+ if( pAction->GetType() == MetaActionType::GRADIENTEX )
+ pGradAction = static_cast<const MetaGradientExAction*>(pAction);
+ else if( ( pAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END")) )
+ {
+ bDone = true;
+ }
+ }
+
+ if( pGradAction )
+ {
+ if (lcl_canUsePDFAxialShading(pGradAction->GetGradient()))
+ {
+ m_rOuterFace.DrawGradient( pGradAction->GetPolyPolygon(), pGradAction->GetGradient() );
+ }
+ else
+ {
+ implWriteGradient( pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), pDummyVDev, i_rContext );
+ }
+ }
+ }
+ else
+ {
+ const sal_uInt8* pData = pA->GetData();
+ if ( pData )
+ {
+ SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pData), pA->GetDataSize(), StreamMode::READ );
+ bool bSkipSequence = false;
+ OString sSeqEnd;
+
+ if( pA->GetComment() == "XPATHSTROKE_SEQ_BEGIN" )
+ {
+ sSeqEnd = OString("XPATHSTROKE_SEQ_END");
+ SvtGraphicStroke aStroke;
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+
+ tools::Polygon aPath;
+ aStroke.getPath( aPath );
+
+ tools::PolyPolygon aStartArrow;
+ tools::PolyPolygon aEndArrow;
+ double fTransparency( aStroke.getTransparency() );
+ double fStrokeWidth( aStroke.getStrokeWidth() );
+ SvtGraphicStroke::DashArray aDashArray;
+
+ aStroke.getStartArrow( aStartArrow );
+ aStroke.getEndArrow( aEndArrow );
+ aStroke.getDashArray( aDashArray );
+
+ bSkipSequence = true;
+ if ( aStartArrow.Count() || aEndArrow.Count() )
+ bSkipSequence = false;
+ if ( !aDashArray.empty() && ( fStrokeWidth != 0.0 ) && ( fTransparency == 0.0 ) )
+ bSkipSequence = false;
+ if ( bSkipSequence )
+ {
+ PDFWriter::ExtLineInfo aInfo;
+ aInfo.m_fLineWidth = fStrokeWidth;
+ aInfo.m_fTransparency = fTransparency;
+ aInfo.m_fMiterLimit = aStroke.getMiterLimit();
+ switch( aStroke.getCapType() )
+ {
+ default:
+ case SvtGraphicStroke::capButt: aInfo.m_eCap = PDFWriter::capButt;break;
+ case SvtGraphicStroke::capRound: aInfo.m_eCap = PDFWriter::capRound;break;
+ case SvtGraphicStroke::capSquare: aInfo.m_eCap = PDFWriter::capSquare;break;
+ }
+ switch( aStroke.getJoinType() )
+ {
+ default:
+ case SvtGraphicStroke::joinMiter: aInfo.m_eJoin = PDFWriter::joinMiter;break;
+ case SvtGraphicStroke::joinRound: aInfo.m_eJoin = PDFWriter::joinRound;break;
+ case SvtGraphicStroke::joinBevel: aInfo.m_eJoin = PDFWriter::joinBevel;break;
+ case SvtGraphicStroke::joinNone:
+ aInfo.m_eJoin = PDFWriter::joinMiter;
+ aInfo.m_fMiterLimit = 0.0;
+ break;
+ }
+ aInfo.m_aDashArray = aDashArray;
+
+ if(SvtGraphicStroke::joinNone == aStroke.getJoinType()
+ && fStrokeWidth > 0.0)
+ {
+ // emulate no edge rounding by handling single edges
+ const sal_uInt16 nPoints(aPath.GetSize());
+ const bool bCurve(aPath.HasFlags());
+
+ for(sal_uInt16 a(0); a + 1 < nPoints; a++)
+ {
+ if(bCurve
+ && PolyFlags::Normal != aPath.GetFlags(a + 1)
+ && a + 2 < nPoints
+ && PolyFlags::Normal != aPath.GetFlags(a + 2)
+ && a + 3 < nPoints)
+ {
+ const tools::Polygon aSnippet(4,
+ aPath.GetConstPointAry() + a,
+ aPath.GetConstFlagAry() + a);
+ m_rOuterFace.DrawPolyLine( aSnippet, aInfo );
+ a += 2;
+ }
+ else
+ {
+ const tools::Polygon aSnippet(2,
+ aPath.GetConstPointAry() + a);
+ m_rOuterFace.DrawPolyLine( aSnippet, aInfo );
+ }
+ }
+ }
+ else
+ {
+ m_rOuterFace.DrawPolyLine( aPath, aInfo );
+ }
+ }
+ }
+ else if ( pA->GetComment() == "XPATHFILL_SEQ_BEGIN" )
+ {
+ sSeqEnd = OString("XPATHFILL_SEQ_END");
+ SvtGraphicFill aFill;
+ ReadSvtGraphicFill( aMemStm, aFill );
+
+ if ( ( aFill.getFillType() == SvtGraphicFill::fillSolid ) && ( aFill.getFillRule() == SvtGraphicFill::fillEvenOdd ) )
+ {
+ double fTransparency = aFill.getTransparency();
+ if ( fTransparency == 0.0 )
+ {
+ tools::PolyPolygon aPath;
+ aFill.getPath( aPath );
+
+ bSkipSequence = true;
+ m_rOuterFace.DrawPolyPolygon( aPath );
+ }
+ else if ( fTransparency == 1.0 )
+ bSkipSequence = true;
+ }
+ }
+ if ( bSkipSequence )
+ {
+ while( ++i < nCount )
+ {
+ pAction = aMtf.GetAction( i );
+ if ( pAction->GetType() == MetaActionType::COMMENT )
+ {
+ OString sComment( static_cast<const MetaCommentAction*>(pAction)->GetComment() );
+ if (sComment == sSeqEnd)
+ break;
+ }
+ // #i44496#
+ // the replacement action for stroke is a filled rectangle
+ // the set fillcolor of the replacement is part of the graphics
+ // state and must not be skipped
+ else if( pAction->GetType() == MetaActionType::FILLCOLOR )
+ {
+ const MetaFillColorAction* pMA = static_cast<const MetaFillColorAction*>(pAction);
+ if( pMA->IsSetting() )
+ m_rOuterFace.SetFillColor( pMA->GetColor() );
+ else
+ m_rOuterFace.SetFillColor();
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::BMP:
+ {
+ const MetaBmpAction* pA = static_cast<const MetaBmpAction*>(pAction);
+ BitmapEx aBitmapEx( pA->GetBitmap() );
+ Size aSize( OutputDevice::LogicToLogic( aBitmapEx.GetPrefSize(),
+ aBitmapEx.GetPrefMapMode(), pDummyVDev->GetMapMode() ) );
+ if( ! ( aSize.Width() && aSize.Height() ) )
+ aSize = pDummyVDev->PixelToLogic( aBitmapEx.GetSizePixel() );
+
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetPoint(), aSize, aBitmapEx, aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetPoint(), pA->GetSize(), BitmapEx( pA->GetBitmap() ), aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pAction);
+ BitmapEx aBitmapEx( pA->GetBitmap() );
+ aBitmapEx.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) );
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetDestPoint(), pA->GetDestSize(), aBitmapEx, aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ const MetaBmpExAction* pA = static_cast<const MetaBmpExAction*>(pAction);
+ const BitmapEx& aBitmapEx( pA->GetBitmapEx() );
+ Size aSize( OutputDevice::LogicToLogic( aBitmapEx.GetPrefSize(),
+ aBitmapEx.GetPrefMapMode(), pDummyVDev->GetMapMode() ) );
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetPoint(), aSize, aBitmapEx, aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetPoint(), pA->GetSize(), pA->GetBitmapEx(), aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction);
+ BitmapEx aBitmapEx( pA->GetBitmapEx() );
+ aBitmapEx.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) );
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetDestPoint(), pA->GetDestSize(), aBitmapEx, aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ case MetaActionType::MASKSCALEPART:
+ {
+ SAL_WARN( "vcl", "MetaMask...Action not supported yet" );
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction);
+ m_rOuterFace.DrawText( pA->GetPoint(), pA->GetText().copy( pA->GetIndex(), std::min<sal_Int32>(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) ) );
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction);
+ m_rOuterFace.DrawText( pA->GetRect(), pA->GetText(), pA->GetStyle() );
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction);
+ m_rOuterFace.DrawTextArray( pA->GetPoint(), pA->GetText(), pA->GetDXArray(), pA->GetIndex(), pA->GetLen() );
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction);
+ m_rOuterFace.DrawStretchText( pA->GetPoint(), pA->GetWidth(), pA->GetText(), pA->GetIndex(), pA->GetLen() );
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ {
+ const MetaTextLineAction* pA = static_cast<const MetaTextLineAction*>(pAction);
+ m_rOuterFace.DrawTextLine( pA->GetStartPoint(), pA->GetWidth(), pA->GetStrikeout(), pA->GetUnderline(), pA->GetOverline() );
+
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ {
+ const MetaClipRegionAction* pA = static_cast<const MetaClipRegionAction*>(pAction);
+
+ if( pA->IsClipping() )
+ {
+ if( pA->GetRegion().IsEmpty() )
+ m_rOuterFace.SetClipRegion( basegfx::B2DPolyPolygon() );
+ else
+ {
+ const vcl::Region& aReg( pA->GetRegion() );
+ m_rOuterFace.SetClipRegion( aReg.GetAsB2DPolyPolygon() );
+ }
+ }
+ else
+ m_rOuterFace.SetClipRegion();
+ }
+ break;
+
+ case MetaActionType::ISECTRECTCLIPREGION:
+ {
+ const MetaISectRectClipRegionAction* pA = static_cast<const MetaISectRectClipRegionAction*>(pAction);
+ m_rOuterFace.IntersectClipRegion( pA->GetRect() );
+ }
+ break;
+
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ {
+ const MetaISectRegionClipRegionAction* pA = static_cast<const MetaISectRegionClipRegionAction*>(pAction);
+ const vcl::Region& aReg( pA->GetRegion() );
+ m_rOuterFace.IntersectClipRegion( aReg.GetAsB2DPolyPolygon() );
+ }
+ break;
+
+ case MetaActionType::MOVECLIPREGION:
+ {
+ const MetaMoveClipRegionAction* pA = static_cast<const MetaMoveClipRegionAction*>(pAction);
+ m_rOuterFace.MoveClipRegion( pA->GetHorzMove(), pA->GetVertMove() );
+ }
+ break;
+
+ case MetaActionType::MAPMODE:
+ {
+ const_cast< MetaAction* >( pAction )->Execute( pDummyVDev );
+ m_rOuterFace.SetMapMode( pDummyVDev->GetMapMode() );
+ }
+ break;
+
+ case MetaActionType::LINECOLOR:
+ {
+ const MetaLineColorAction* pA = static_cast<const MetaLineColorAction*>(pAction);
+
+ if( pA->IsSetting() )
+ m_rOuterFace.SetLineColor( pA->GetColor() );
+ else
+ m_rOuterFace.SetLineColor();
+ }
+ break;
+
+ case MetaActionType::FILLCOLOR:
+ {
+ const MetaFillColorAction* pA = static_cast<const MetaFillColorAction*>(pAction);
+
+ if( pA->IsSetting() )
+ m_rOuterFace.SetFillColor( pA->GetColor() );
+ else
+ m_rOuterFace.SetFillColor();
+ }
+ break;
+
+ case MetaActionType::TEXTLINECOLOR:
+ {
+ const MetaTextLineColorAction* pA = static_cast<const MetaTextLineColorAction*>(pAction);
+
+ if( pA->IsSetting() )
+ m_rOuterFace.SetTextLineColor( pA->GetColor() );
+ else
+ m_rOuterFace.SetTextLineColor();
+ }
+ break;
+
+ case MetaActionType::OVERLINECOLOR:
+ {
+ const MetaOverlineColorAction* pA = static_cast<const MetaOverlineColorAction*>(pAction);
+
+ if( pA->IsSetting() )
+ m_rOuterFace.SetOverlineColor( pA->GetColor() );
+ else
+ m_rOuterFace.SetOverlineColor();
+ }
+ break;
+
+ case MetaActionType::TEXTFILLCOLOR:
+ {
+ const MetaTextFillColorAction* pA = static_cast<const MetaTextFillColorAction*>(pAction);
+
+ if( pA->IsSetting() )
+ m_rOuterFace.SetTextFillColor( pA->GetColor() );
+ else
+ m_rOuterFace.SetTextFillColor();
+ }
+ break;
+
+ case MetaActionType::TEXTCOLOR:
+ {
+ const MetaTextColorAction* pA = static_cast<const MetaTextColorAction*>(pAction);
+ m_rOuterFace.SetTextColor( pA->GetColor() );
+ }
+ break;
+
+ case MetaActionType::TEXTALIGN:
+ {
+ const MetaTextAlignAction* pA = static_cast<const MetaTextAlignAction*>(pAction);
+ m_rOuterFace.SetTextAlign( pA->GetTextAlign() );
+ }
+ break;
+
+ case MetaActionType::FONT:
+ {
+ const MetaFontAction* pA = static_cast<const MetaFontAction*>(pAction);
+ m_rOuterFace.SetFont( pA->GetFont() );
+ }
+ break;
+
+ case MetaActionType::PUSH:
+ {
+ const MetaPushAction* pA = static_cast<const MetaPushAction*>(pAction);
+
+ pDummyVDev->Push( pA->GetFlags() );
+ m_rOuterFace.Push( pA->GetFlags() );
+ }
+ break;
+
+ case MetaActionType::POP:
+ {
+ pDummyVDev->Pop();
+ m_rOuterFace.Pop();
+ }
+ break;
+
+ case MetaActionType::LAYOUTMODE:
+ {
+ const MetaLayoutModeAction* pA = static_cast<const MetaLayoutModeAction*>(pAction);
+ m_rOuterFace.SetLayoutMode( pA->GetLayoutMode() );
+ }
+ break;
+
+ case MetaActionType::TEXTLANGUAGE:
+ {
+ const MetaTextLanguageAction* pA = static_cast<const MetaTextLanguageAction*>(pAction);
+ m_rOuterFace.SetDigitLanguage( pA->GetTextLanguage() );
+ }
+ break;
+
+ case MetaActionType::WALLPAPER:
+ {
+ const MetaWallpaperAction* pA = static_cast<const MetaWallpaperAction*>(pAction);
+ m_rOuterFace.DrawWallpaper( pA->GetRect(), pA->GetWallpaper() );
+ }
+ break;
+
+ case MetaActionType::RASTEROP:
+ {
+ // !!! >>> we don't want to support this actions
+ }
+ break;
+
+ case MetaActionType::REFPOINT:
+ {
+ // !!! >>> we don't want to support this actions
+ }
+ break;
+
+ default:
+ // #i24604# Made assertion fire only once per
+ // metafile. The asserted actions here are all
+ // deprecated
+ if( !bAssertionFired )
+ {
+ bAssertionFired = true;
+ SAL_WARN( "vcl", "PDFExport::ImplWriteActions: deprecated and unsupported MetaAction encountered " << static_cast<int>(nType) );
+ }
+ break;
+ }
+ i++;
+ }
+ }
+}
+
+// Encryption methods
+
+/* a crutch to transport a ::comphelper::Hash safely though UNO API
+ this is needed for the PDF export dialog, which otherwise would have to pass
+ clear text passwords down till they can be used in PDFWriter. Unfortunately
+ the MD5 sum of the password (which is needed to create the PDF encryption key)
+ is not sufficient, since an MD5 digest cannot be created in an arbitrary state
+ which would be needed in PDFWriterImpl::computeEncryptionKey.
+*/
+class EncHashTransporter : public cppu::WeakImplHelper < css::beans::XMaterialHolder >
+{
+ ::std::unique_ptr<::comphelper::Hash> m_pDigest;
+ sal_IntPtr maID;
+ std::vector< sal_uInt8 > maOValue;
+
+ static std::map< sal_IntPtr, EncHashTransporter* > sTransporters;
+public:
+ EncHashTransporter()
+ : m_pDigest(new ::comphelper::Hash(::comphelper::HashType::MD5))
+ {
+ maID = reinterpret_cast< sal_IntPtr >(this);
+ while( sTransporters.find( maID ) != sTransporters.end() ) // paranoia mode
+ maID++;
+ sTransporters[ maID ] = this;
+ }
+
+ virtual ~EncHashTransporter() override
+ {
+ sTransporters.erase( maID );
+ SAL_INFO( "vcl", "EncHashTransporter freed" );
+ }
+
+ ::comphelper::Hash* getUDigest() { return m_pDigest.get(); };
+ std::vector< sal_uInt8 >& getOValue() { return maOValue; }
+ void invalidate()
+ {
+ m_pDigest.reset();
+ }
+
+ // XMaterialHolder
+ virtual uno::Any SAL_CALL getMaterial() override
+ {
+ return uno::Any( sal_Int64(maID) );
+ }
+
+ static EncHashTransporter* getEncHashTransporter( const uno::Reference< beans::XMaterialHolder >& );
+
+};
+
+std::map< sal_IntPtr, EncHashTransporter* > EncHashTransporter::sTransporters;
+
+EncHashTransporter* EncHashTransporter::getEncHashTransporter( const uno::Reference< beans::XMaterialHolder >& xRef )
+{
+ EncHashTransporter* pResult = nullptr;
+ if( xRef.is() )
+ {
+ uno::Any aMat( xRef->getMaterial() );
+ sal_Int64 nMat = 0;
+ if( aMat >>= nMat )
+ {
+ std::map< sal_IntPtr, EncHashTransporter* >::iterator it = sTransporters.find( static_cast<sal_IntPtr>(nMat) );
+ if( it != sTransporters.end() )
+ pResult = it->second;
+ }
+ }
+ return pResult;
+}
+
+void PDFWriterImpl::checkAndEnableStreamEncryption( sal_Int32 nObject )
+{
+ if( !m_aContext.Encryption.Encrypt() )
+ return;
+
+ m_bEncryptThisStream = true;
+ sal_Int32 i = m_nKeyLength;
+ m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>(nObject);
+ m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 8 );
+ m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 16 );
+ // the other location of m_nEncryptionKey is already set to 0, our fixed generation number
+ // do the MD5 hash
+ ::std::vector<unsigned char> const nMD5Sum(::comphelper::Hash::calculateHash(
+ m_aContext.Encryption.EncryptionKey.data(), i+2, ::comphelper::HashType::MD5));
+ // the i+2 to take into account the generation number, always zero
+ // initialize the RC4 with the key
+ // key length: see algorithm 3.1, step 4: (N+5) max 16
+ rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode, nMD5Sum.data(), m_nRC4KeyLength, nullptr, 0 );
+}
+
+void PDFWriterImpl::enableStringEncryption( sal_Int32 nObject )
+{
+ if( !m_aContext.Encryption.Encrypt() )
+ return;
+
+ sal_Int32 i = m_nKeyLength;
+ m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>(nObject);
+ m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 8 );
+ m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 16 );
+ // the other location of m_nEncryptionKey is already set to 0, our fixed generation number
+ // do the MD5 hash
+ // the i+2 to take into account the generation number, always zero
+ ::std::vector<unsigned char> const nMD5Sum(::comphelper::Hash::calculateHash(
+ m_aContext.Encryption.EncryptionKey.data(), i+2, ::comphelper::HashType::MD5));
+ // initialize the RC4 with the key
+ // key length: see algorithm 3.1, step 4: (N+5) max 16
+ rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode, nMD5Sum.data(), m_nRC4KeyLength, nullptr, 0 );
+}
+
+/* init the encryption engine
+1. init the document id, used both for building the document id and for building the encryption key(s)
+2. build the encryption key following algorithms described in the PDF specification
+ */
+uno::Reference< beans::XMaterialHolder > PDFWriterImpl::initEncryption( const OUString& i_rOwnerPassword,
+ const OUString& i_rUserPassword
+ )
+{
+ uno::Reference< beans::XMaterialHolder > xResult;
+ if( !i_rOwnerPassword.isEmpty() || !i_rUserPassword.isEmpty() )
+ {
+ rtl::Reference<EncHashTransporter> pTransporter = new EncHashTransporter;
+ xResult = pTransporter;
+
+ // get padded passwords
+ sal_uInt8 aPadUPW[ENCRYPTED_PWD_SIZE], aPadOPW[ENCRYPTED_PWD_SIZE];
+ padPassword( i_rOwnerPassword.isEmpty() ? i_rUserPassword : i_rOwnerPassword, aPadOPW );
+ padPassword( i_rUserPassword, aPadUPW );
+
+ if( computeODictionaryValue( aPadOPW, aPadUPW, pTransporter->getOValue(), SECUR_128BIT_KEY ) )
+ {
+ pTransporter->getUDigest()->update(aPadUPW, ENCRYPTED_PWD_SIZE);
+ }
+ else
+ xResult.clear();
+
+ // trash temporary padded cleartext PWDs
+ rtl_secureZeroMemory (aPadOPW, sizeof(aPadOPW));
+ rtl_secureZeroMemory (aPadUPW, sizeof(aPadUPW));
+ }
+ return xResult;
+}
+
+bool PDFWriterImpl::prepareEncryption( const uno::Reference< beans::XMaterialHolder >& xEnc )
+{
+ bool bSuccess = false;
+ EncHashTransporter* pTransporter = EncHashTransporter::getEncHashTransporter( xEnc );
+ if( pTransporter )
+ {
+ sal_Int32 nKeyLength = 0, nRC4KeyLength = 0;
+ sal_Int32 nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, nKeyLength, nRC4KeyLength );
+ m_aContext.Encryption.OValue = pTransporter->getOValue();
+ bSuccess = computeUDictionaryValue( pTransporter, m_aContext.Encryption, nKeyLength, nAccessPermissions );
+ }
+ if( ! bSuccess )
+ {
+ m_aContext.Encryption.OValue.clear();
+ m_aContext.Encryption.UValue.clear();
+ m_aContext.Encryption.EncryptionKey.clear();
+ }
+ return bSuccess;
+}
+
+sal_Int32 PDFWriterImpl::computeAccessPermissions( const vcl::PDFWriter::PDFEncryptionProperties& i_rProperties,
+ sal_Int32& o_rKeyLength, sal_Int32& o_rRC4KeyLength )
+{
+ /*
+ 2) compute the access permissions, in numerical form
+
+ the default value depends on the revision 2 (40 bit) or 3 (128 bit security):
+ - for 40 bit security the unused bit must be set to 1, since they are not used
+ - for 128 bit security the same bit must be preset to 0 and set later if needed
+ according to the table 3.15, pdf v 1.4 */
+ sal_Int32 nAccessPermissions = 0xfffff0c0;
+
+ o_rKeyLength = SECUR_128BIT_KEY;
+ o_rRC4KeyLength = 16; // for this value see PDF spec v 1.4, algorithm 3.1 step 4, where n is 16,
+ // thus maximum permitted value is 16
+
+ nAccessPermissions |= ( i_rProperties.CanPrintTheDocument ) ? 1 << 2 : 0;
+ nAccessPermissions |= ( i_rProperties.CanModifyTheContent ) ? 1 << 3 : 0;
+ nAccessPermissions |= ( i_rProperties.CanCopyOrExtract ) ? 1 << 4 : 0;
+ nAccessPermissions |= ( i_rProperties.CanAddOrModify ) ? 1 << 5 : 0;
+ nAccessPermissions |= ( i_rProperties.CanFillInteractive ) ? 1 << 8 : 0;
+ nAccessPermissions |= ( i_rProperties.CanExtractForAccessibility ) ? 1 << 9 : 0;
+ nAccessPermissions |= ( i_rProperties.CanAssemble ) ? 1 << 10 : 0;
+ nAccessPermissions |= ( i_rProperties.CanPrintFull ) ? 1 << 11 : 0;
+ return nAccessPermissions;
+}
+
+/*************************************************************
+begin i12626 methods
+
+Implements Algorithm 3.2, step 1 only
+*/
+void PDFWriterImpl::padPassword( std::u16string_view i_rPassword, sal_uInt8* o_pPaddedPW )
+{
+ // get ansi-1252 version of the password string CHECKIT ! i12626
+ OString aString( OUStringToOString( i_rPassword, RTL_TEXTENCODING_MS_1252 ) );
+
+ //copy the string to the target
+ sal_Int32 nToCopy = ( aString.getLength() < ENCRYPTED_PWD_SIZE ) ? aString.getLength() : ENCRYPTED_PWD_SIZE;
+ sal_Int32 nCurrentChar;
+
+ for( nCurrentChar = 0; nCurrentChar < nToCopy; nCurrentChar++ )
+ o_pPaddedPW[nCurrentChar] = static_cast<sal_uInt8>( aString[nCurrentChar] );
+
+ //pad it with standard byte string
+ sal_Int32 i,y;
+ for( i = nCurrentChar, y = 0 ; i < ENCRYPTED_PWD_SIZE; i++, y++ )
+ o_pPaddedPW[i] = s_nPadString[y];
+}
+
+/**********************************
+Algorithm 3.2 Compute the encryption key used
+
+step 1 should already be done before calling, the paThePaddedPassword parameter should contain
+the padded password and must be 32 byte long, the encryption key is returned into the paEncryptionKey parameter,
+it will be 16 byte long for 128 bit security; for 40 bit security only the first 5 bytes are used
+
+TODO: in pdf ver 1.5 and 1.6 the step 6 is different, should be implemented. See spec.
+
+*/
+bool PDFWriterImpl::computeEncryptionKey( EncHashTransporter* i_pTransporter, vcl::PDFWriter::PDFEncryptionProperties& io_rProperties, sal_Int32 i_nAccessPermissions )
+{
+ bool bSuccess = true;
+ ::std::vector<unsigned char> nMD5Sum;
+
+ // transporter contains an MD5 digest with the padded user password already
+ ::comphelper::Hash *const pDigest = i_pTransporter->getUDigest();
+ if (pDigest)
+ {
+ //step 3
+ if( ! io_rProperties.OValue.empty() )
+ pDigest->update(io_rProperties.OValue.data(), io_rProperties.OValue.size());
+ else
+ bSuccess = false;
+ //Step 4
+ sal_uInt8 nPerm[4];
+
+ nPerm[0] = static_cast<sal_uInt8>(i_nAccessPermissions);
+ nPerm[1] = static_cast<sal_uInt8>( i_nAccessPermissions >> 8 );
+ nPerm[2] = static_cast<sal_uInt8>( i_nAccessPermissions >> 16 );
+ nPerm[3] = static_cast<sal_uInt8>( i_nAccessPermissions >> 24 );
+
+ pDigest->update(nPerm, sizeof(nPerm));
+
+ //step 5, get the document ID, binary form
+ pDigest->update(io_rProperties.DocumentIdentifier.data(), io_rProperties.DocumentIdentifier.size());
+ //get the digest
+ nMD5Sum = pDigest->finalize();
+
+ //step 6, only if 128 bit
+ for (sal_Int32 i = 0; i < 50; i++)
+ {
+ nMD5Sum = ::comphelper::Hash::calculateHash(nMD5Sum.data(), nMD5Sum.size(), ::comphelper::HashType::MD5);
+ }
+ }
+ else
+ bSuccess = false;
+
+ i_pTransporter->invalidate();
+
+ //Step 7
+ if( bSuccess )
+ {
+ io_rProperties.EncryptionKey.resize( MAXIMUM_RC4_KEY_LENGTH );
+ for( sal_Int32 i = 0; i < MD5_DIGEST_SIZE; i++ )
+ io_rProperties.EncryptionKey[i] = nMD5Sum[i];
+ }
+ else
+ io_rProperties.EncryptionKey.clear();
+
+ return bSuccess;
+}
+
+/**********************************
+Algorithm 3.3 Compute the encryption dictionary /O value, save into the class data member
+the step numbers down here correspond to the ones in PDF v.1.4 specification
+*/
+bool PDFWriterImpl::computeODictionaryValue( const sal_uInt8* i_pPaddedOwnerPassword,
+ const sal_uInt8* i_pPaddedUserPassword,
+ std::vector< sal_uInt8 >& io_rOValue,
+ sal_Int32 i_nKeyLength
+ )
+{
+ bool bSuccess = true;
+
+ io_rOValue.resize( ENCRYPTED_PWD_SIZE );
+
+ rtlCipher aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
+ if (aCipher)
+ {
+ //step 1 already done, data is in i_pPaddedOwnerPassword
+ //step 2
+
+ ::std::vector<unsigned char> nMD5Sum(::comphelper::Hash::calculateHash(
+ i_pPaddedOwnerPassword, ENCRYPTED_PWD_SIZE, ::comphelper::HashType::MD5));
+ //step 3, only if 128 bit
+ if (i_nKeyLength == SECUR_128BIT_KEY)
+ {
+ sal_Int32 i;
+ for (i = 0; i < 50; i++)
+ {
+ nMD5Sum = ::comphelper::Hash::calculateHash(nMD5Sum.data(), nMD5Sum.size(), ::comphelper::HashType::MD5);
+ }
+ }
+ //Step 4, the key is in nMD5Sum
+ //step 5 already done, data is in i_pPaddedUserPassword
+ //step 6
+ if (rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode,
+ nMD5Sum.data(), i_nKeyLength , nullptr, 0 )
+ == rtl_Cipher_E_None)
+ {
+ // encrypt the user password using the key set above
+ rtl_cipher_encodeARCFOUR( aCipher, i_pPaddedUserPassword, ENCRYPTED_PWD_SIZE, // the data to be encrypted
+ io_rOValue.data(), sal_Int32(io_rOValue.size()) ); //encrypted data
+ //Step 7, only if 128 bit
+ if( i_nKeyLength == SECUR_128BIT_KEY )
+ {
+ sal_uInt32 i;
+ size_t y;
+ sal_uInt8 nLocalKey[ SECUR_128BIT_KEY ]; // 16 = 128 bit key
+
+ for( i = 1; i <= 19; i++ ) // do it 19 times, start with 1
+ {
+ for( y = 0; y < sizeof( nLocalKey ); y++ )
+ nLocalKey[y] = static_cast<sal_uInt8>( nMD5Sum[y] ^ i );
+
+ if (rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode,
+ nLocalKey, SECUR_128BIT_KEY, nullptr, 0 ) //destination data area, on init can be NULL
+ != rtl_Cipher_E_None)
+ {
+ bSuccess = false;
+ break;
+ }
+ rtl_cipher_encodeARCFOUR( aCipher, io_rOValue.data(), sal_Int32(io_rOValue.size()), // the data to be encrypted
+ io_rOValue.data(), sal_Int32(io_rOValue.size()) ); // encrypted data, can be the same as the input, encrypt "in place"
+ //step 8, store in class data member
+ }
+ }
+ }
+ else
+ bSuccess = false;
+ }
+ else
+ bSuccess = false;
+
+ if( aCipher )
+ rtl_cipher_destroyARCFOUR( aCipher );
+
+ if( ! bSuccess )
+ io_rOValue.clear();
+ return bSuccess;
+}
+
+/**********************************
+Algorithms 3.4 and 3.5 Compute the encryption dictionary /U value, save into the class data member, revision 2 (40 bit) or 3 (128 bit)
+*/
+bool PDFWriterImpl::computeUDictionaryValue( EncHashTransporter* i_pTransporter,
+ vcl::PDFWriter::PDFEncryptionProperties& io_rProperties,
+ sal_Int32 i_nKeyLength,
+ sal_Int32 i_nAccessPermissions
+ )
+{
+ bool bSuccess = true;
+
+ io_rProperties.UValue.resize( ENCRYPTED_PWD_SIZE );
+
+ ::comphelper::Hash aDigest(::comphelper::HashType::MD5);
+ rtlCipher aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
+ if (aCipher)
+ {
+ //step 1, common to both 3.4 and 3.5
+ if( computeEncryptionKey( i_pTransporter, io_rProperties, i_nAccessPermissions ) )
+ {
+ // prepare encryption key for object
+ for( sal_Int32 i = i_nKeyLength, y = 0; y < 5 ; y++ )
+ io_rProperties.EncryptionKey[i++] = 0;
+
+ //or 3.5, for 128 bit security
+ //step6, initialize the last 16 bytes of the encrypted user password to 0
+ for(sal_uInt32 i = MD5_DIGEST_SIZE; i < sal_uInt32(io_rProperties.UValue.size()); i++)
+ io_rProperties.UValue[i] = 0;
+ //steps 2 and 3
+ aDigest.update(s_nPadString, sizeof(s_nPadString));
+ aDigest.update(io_rProperties.DocumentIdentifier.data(), io_rProperties.DocumentIdentifier.size());
+
+ ::std::vector<unsigned char> const nMD5Sum(aDigest.finalize());
+ //Step 4
+ rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode,
+ io_rProperties.EncryptionKey.data(), SECUR_128BIT_KEY, nullptr, 0 ); //destination data area
+ rtl_cipher_encodeARCFOUR( aCipher, nMD5Sum.data(), nMD5Sum.size(), // the data to be encrypted
+ io_rProperties.UValue.data(), SECUR_128BIT_KEY ); //encrypted data, stored in class data member
+ //step 5
+ sal_uInt32 i;
+ size_t y;
+ sal_uInt8 nLocalKey[SECUR_128BIT_KEY];
+
+ for( i = 1; i <= 19; i++ ) // do it 19 times, start with 1
+ {
+ for( y = 0; y < sizeof( nLocalKey ) ; y++ )
+ nLocalKey[y] = static_cast<sal_uInt8>( io_rProperties.EncryptionKey[y] ^ i );
+
+ rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode,
+ nLocalKey, SECUR_128BIT_KEY, // key and key length
+ nullptr, 0 ); //destination data area, on init can be NULL
+ rtl_cipher_encodeARCFOUR( aCipher, io_rProperties.UValue.data(), SECUR_128BIT_KEY, // the data to be encrypted
+ io_rProperties.UValue.data(), SECUR_128BIT_KEY ); // encrypted data, can be the same as the input, encrypt "in place"
+ }
+ }
+ else
+ bSuccess = false;
+ }
+ else
+ bSuccess = false;
+
+ if( aCipher )
+ rtl_cipher_destroyARCFOUR( aCipher );
+
+ if( ! bSuccess )
+ io_rProperties.UValue.clear();
+ return bSuccess;
+}
+
+/* end i12626 methods */
+
+const tools::Long unsetRun[256] =
+{
+ 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, /* 0x00 - 0x0f */
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 - 0x1f */
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x20 - 0x2f */
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x30 - 0x3f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 - 0x4f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x50 - 0x5f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 - 0x6f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x70 - 0x7f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf0 - 0xff */
+};
+
+const tools::Long setRun[256] =
+{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 - 0x0f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x6f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x80 - 0x8f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x90 - 0x9f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xa0 - 0xaf */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xb0 - 0xbf */
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xc0 - 0xcf */
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xd0 - 0xdf */
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xe0 - 0xef */
+ 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8, /* 0xf0 - 0xff */
+};
+
+static bool isSet( const Scanline i_pLine, tools::Long i_nIndex )
+{
+ return (i_pLine[ i_nIndex/8 ] & (0x80 >> (i_nIndex&7))) != 0;
+}
+
+static tools::Long findBitRunImpl( const Scanline i_pLine, tools::Long i_nStartIndex, tools::Long i_nW, bool i_bSet )
+{
+ tools::Long nIndex = i_nStartIndex;
+ if( nIndex < i_nW )
+ {
+ const sal_uInt8 * pByte = i_pLine + (nIndex/8);
+ sal_uInt8 nByte = *pByte;
+
+ // run up to byte boundary
+ tools::Long nBitInByte = (nIndex & 7);
+ if( nBitInByte )
+ {
+ sal_uInt8 nMask = 0x80 >> nBitInByte;
+ while( nBitInByte != 8 )
+ {
+ if( (nByte & nMask) != (i_bSet ? nMask : 0) )
+ return std::min(nIndex, i_nW);
+ nMask = nMask >> 1;
+ nBitInByte++;
+ nIndex++;
+ }
+ if( nIndex < i_nW )
+ {
+ pByte++;
+ nByte = *pByte;
+ }
+ }
+
+ sal_uInt8 nRunByte;
+ const tools::Long* pRunTable;
+ if( i_bSet )
+ {
+ nRunByte = 0xff;
+ pRunTable = setRun;
+ }
+ else
+ {
+ nRunByte = 0;
+ pRunTable = unsetRun;
+ }
+
+ if( nIndex < i_nW )
+ {
+ while( nByte == nRunByte )
+ {
+ nIndex += 8;
+
+ if (nIndex >= i_nW)
+ break;
+
+ pByte++;
+ nByte = *pByte;
+ }
+ }
+
+ if( nIndex < i_nW )
+ {
+ nIndex += pRunTable[nByte];
+ }
+ }
+ return std::min(nIndex, i_nW);
+}
+
+static tools::Long findBitRun(const Scanline i_pLine, tools::Long i_nStartIndex, tools::Long i_nW, bool i_bSet)
+{
+ if (i_nStartIndex < 0)
+ return i_nW;
+
+ return findBitRunImpl(i_pLine, i_nStartIndex, i_nW, i_bSet);
+}
+
+static tools::Long findBitRun(const Scanline i_pLine, tools::Long i_nStartIndex, tools::Long i_nW)
+{
+ if (i_nStartIndex < 0)
+ return i_nW;
+
+ const bool bSet = i_nStartIndex < i_nW && isSet(i_pLine, i_nStartIndex);
+
+ return findBitRunImpl(i_pLine, i_nStartIndex, i_nW, bSet);
+}
+
+struct BitStreamState
+{
+ sal_uInt8 mnBuffer;
+ sal_uInt32 mnNextBitPos;
+
+ BitStreamState()
+ : mnBuffer( 0 )
+ , mnNextBitPos( 8 )
+ {
+ }
+
+ const sal_uInt8& getByte() const { return mnBuffer; }
+ void flush() { mnNextBitPos = 8; mnBuffer = 0; }
+};
+
+void PDFWriterImpl::putG4Bits( sal_uInt32 i_nLength, sal_uInt32 i_nCode, BitStreamState& io_rState )
+{
+ while( i_nLength > io_rState.mnNextBitPos )
+ {
+ io_rState.mnBuffer |= static_cast<sal_uInt8>( i_nCode >> (i_nLength - io_rState.mnNextBitPos) );
+ i_nLength -= io_rState.mnNextBitPos;
+ writeBuffer( &io_rState.getByte(), 1 );
+ io_rState.flush();
+ }
+ assert(i_nLength < 9);
+ static const unsigned int msbmask[9] = { 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff };
+ io_rState.mnBuffer |= static_cast<sal_uInt8>( (i_nCode & msbmask[i_nLength]) << (io_rState.mnNextBitPos - i_nLength) );
+ io_rState.mnNextBitPos -= i_nLength;
+ if( io_rState.mnNextBitPos == 0 )
+ {
+ writeBuffer( &io_rState.getByte(), 1 );
+ io_rState.flush();
+ }
+}
+
+namespace {
+
+struct PixelCode
+{
+ sal_uInt32 mnEncodedPixels;
+ sal_uInt32 mnCodeBits;
+ sal_uInt32 mnCode;
+};
+
+}
+
+const PixelCode WhitePixelCodes[] =
+{
+ { 0, 8, 0x35 }, // 0011 0101
+ { 1, 6, 0x7 }, // 0001 11
+ { 2, 4, 0x7 }, // 0111
+ { 3, 4, 0x8 }, // 1000
+ { 4, 4, 0xB }, // 1011
+ { 5, 4, 0xC }, // 1100
+ { 6, 4, 0xE }, // 1110
+ { 7, 4, 0xF }, // 1111
+ { 8, 5, 0x13 }, // 1001 1
+ { 9, 5, 0x14 }, // 1010 0
+ { 10, 5, 0x7 }, // 0011 1
+ { 11, 5, 0x8 }, // 0100 0
+ { 12, 6, 0x8 }, // 0010 00
+ { 13, 6, 0x3 }, // 0000 11
+ { 14, 6, 0x34 }, // 1101 00
+ { 15, 6, 0x35 }, // 1101 01
+ { 16, 6, 0x2A }, // 1010 10
+ { 17, 6, 0x2B }, // 1010 11
+ { 18, 7, 0x27 }, // 0100 111
+ { 19, 7, 0xC }, // 0001 100
+ { 20, 7, 0x8 }, // 0001 000
+ { 21, 7, 0x17 }, // 0010 111
+ { 22, 7, 0x3 }, // 0000 011
+ { 23, 7, 0x4 }, // 0000 100
+ { 24, 7, 0x28 }, // 0101 000
+ { 25, 7, 0x2B }, // 0101 011
+ { 26, 7, 0x13 }, // 0010 011
+ { 27, 7, 0x24 }, // 0100 100
+ { 28, 7, 0x18 }, // 0011 000
+ { 29, 8, 0x2 }, // 0000 0010
+ { 30, 8, 0x3 }, // 0000 0011
+ { 31, 8, 0x1A }, // 0001 1010
+ { 32, 8, 0x1B }, // 0001 1011
+ { 33, 8, 0x12 }, // 0001 0010
+ { 34, 8, 0x13 }, // 0001 0011
+ { 35, 8, 0x14 }, // 0001 0100
+ { 36, 8, 0x15 }, // 0001 0101
+ { 37, 8, 0x16 }, // 0001 0110
+ { 38, 8, 0x17 }, // 0001 0111
+ { 39, 8, 0x28 }, // 0010 1000
+ { 40, 8, 0x29 }, // 0010 1001
+ { 41, 8, 0x2A }, // 0010 1010
+ { 42, 8, 0x2B }, // 0010 1011
+ { 43, 8, 0x2C }, // 0010 1100
+ { 44, 8, 0x2D }, // 0010 1101
+ { 45, 8, 0x4 }, // 0000 0100
+ { 46, 8, 0x5 }, // 0000 0101
+ { 47, 8, 0xA }, // 0000 1010
+ { 48, 8, 0xB }, // 0000 1011
+ { 49, 8, 0x52 }, // 0101 0010
+ { 50, 8, 0x53 }, // 0101 0011
+ { 51, 8, 0x54 }, // 0101 0100
+ { 52, 8, 0x55 }, // 0101 0101
+ { 53, 8, 0x24 }, // 0010 0100
+ { 54, 8, 0x25 }, // 0010 0101
+ { 55, 8, 0x58 }, // 0101 1000
+ { 56, 8, 0x59 }, // 0101 1001
+ { 57, 8, 0x5A }, // 0101 1010
+ { 58, 8, 0x5B }, // 0101 1011
+ { 59, 8, 0x4A }, // 0100 1010
+ { 60, 8, 0x4B }, // 0100 1011
+ { 61, 8, 0x32 }, // 0011 0010
+ { 62, 8, 0x33 }, // 0011 0011
+ { 63, 8, 0x34 }, // 0011 0100
+ { 64, 5, 0x1B }, // 1101 1
+ { 128, 5, 0x12 }, // 1001 0
+ { 192, 6, 0x17 }, // 0101 11
+ { 256, 7, 0x37 }, // 0110 111
+ { 320, 8, 0x36 }, // 0011 0110
+ { 384, 8, 0x37 }, // 0011 0111
+ { 448, 8, 0x64 }, // 0110 0100
+ { 512, 8, 0x65 }, // 0110 0101
+ { 576, 8, 0x68 }, // 0110 1000
+ { 640, 8, 0x67 }, // 0110 0111
+ { 704, 9, 0xCC }, // 0110 0110 0
+ { 768, 9, 0xCD }, // 0110 0110 1
+ { 832, 9, 0xD2 }, // 0110 1001 0
+ { 896, 9, 0xD3 }, // 0110 1001 1
+ { 960, 9, 0xD4 }, // 0110 1010 0
+ { 1024, 9, 0xD5 }, // 0110 1010 1
+ { 1088, 9, 0xD6 }, // 0110 1011 0
+ { 1152, 9, 0xD7 }, // 0110 1011 1
+ { 1216, 9, 0xD8 }, // 0110 1100 0
+ { 1280, 9, 0xD9 }, // 0110 1100 1
+ { 1344, 9, 0xDA }, // 0110 1101 0
+ { 1408, 9, 0xDB }, // 0110 1101 1
+ { 1472, 9, 0x98 }, // 0100 1100 0
+ { 1536, 9, 0x99 }, // 0100 1100 1
+ { 1600, 9, 0x9A }, // 0100 1101 0
+ { 1664, 6, 0x18 }, // 0110 00
+ { 1728, 9, 0x9B }, // 0100 1101 1
+ { 1792, 11, 0x8 }, // 0000 0001 000
+ { 1856, 11, 0xC }, // 0000 0001 100
+ { 1920, 11, 0xD }, // 0000 0001 101
+ { 1984, 12, 0x12 }, // 0000 0001 0010
+ { 2048, 12, 0x13 }, // 0000 0001 0011
+ { 2112, 12, 0x14 }, // 0000 0001 0100
+ { 2176, 12, 0x15 }, // 0000 0001 0101
+ { 2240, 12, 0x16 }, // 0000 0001 0110
+ { 2304, 12, 0x17 }, // 0000 0001 0111
+ { 2368, 12, 0x1C }, // 0000 0001 1100
+ { 2432, 12, 0x1D }, // 0000 0001 1101
+ { 2496, 12, 0x1E }, // 0000 0001 1110
+ { 2560, 12, 0x1F } // 0000 0001 1111
+};
+
+const PixelCode BlackPixelCodes[] =
+{
+ { 0, 10, 0x37 }, // 0000 1101 11
+ { 1, 3, 0x2 }, // 010
+ { 2, 2, 0x3 }, // 11
+ { 3, 2, 0x2 }, // 10
+ { 4, 3, 0x3 }, // 011
+ { 5, 4, 0x3 }, // 0011
+ { 6, 4, 0x2 }, // 0010
+ { 7, 5, 0x3 }, // 0001 1
+ { 8, 6, 0x5 }, // 0001 01
+ { 9, 6, 0x4 }, // 0001 00
+ { 10, 7, 0x4 }, // 0000 100
+ { 11, 7, 0x5 }, // 0000 101
+ { 12, 7, 0x7 }, // 0000 111
+ { 13, 8, 0x4 }, // 0000 0100
+ { 14, 8, 0x7 }, // 0000 0111
+ { 15, 9, 0x18 }, // 0000 1100 0
+ { 16, 10, 0x17 }, // 0000 0101 11
+ { 17, 10, 0x18 }, // 0000 0110 00
+ { 18, 10, 0x8 }, // 0000 0010 00
+ { 19, 11, 0x67 }, // 0000 1100 111
+ { 20, 11, 0x68 }, // 0000 1101 000
+ { 21, 11, 0x6C }, // 0000 1101 100
+ { 22, 11, 0x37 }, // 0000 0110 111
+ { 23, 11, 0x28 }, // 0000 0101 000
+ { 24, 11, 0x17 }, // 0000 0010 111
+ { 25, 11, 0x18 }, // 0000 0011 000
+ { 26, 12, 0xCA }, // 0000 1100 1010
+ { 27, 12, 0xCB }, // 0000 1100 1011
+ { 28, 12, 0xCC }, // 0000 1100 1100
+ { 29, 12, 0xCD }, // 0000 1100 1101
+ { 30, 12, 0x68 }, // 0000 0110 1000
+ { 31, 12, 0x69 }, // 0000 0110 1001
+ { 32, 12, 0x6A }, // 0000 0110 1010
+ { 33, 12, 0x6B }, // 0000 0110 1011
+ { 34, 12, 0xD2 }, // 0000 1101 0010
+ { 35, 12, 0xD3 }, // 0000 1101 0011
+ { 36, 12, 0xD4 }, // 0000 1101 0100
+ { 37, 12, 0xD5 }, // 0000 1101 0101
+ { 38, 12, 0xD6 }, // 0000 1101 0110
+ { 39, 12, 0xD7 }, // 0000 1101 0111
+ { 40, 12, 0x6C }, // 0000 0110 1100
+ { 41, 12, 0x6D }, // 0000 0110 1101
+ { 42, 12, 0xDA }, // 0000 1101 1010
+ { 43, 12, 0xDB }, // 0000 1101 1011
+ { 44, 12, 0x54 }, // 0000 0101 0100
+ { 45, 12, 0x55 }, // 0000 0101 0101
+ { 46, 12, 0x56 }, // 0000 0101 0110
+ { 47, 12, 0x57 }, // 0000 0101 0111
+ { 48, 12, 0x64 }, // 0000 0110 0100
+ { 49, 12, 0x65 }, // 0000 0110 0101
+ { 50, 12, 0x52 }, // 0000 0101 0010
+ { 51, 12, 0x53 }, // 0000 0101 0011
+ { 52, 12, 0x24 }, // 0000 0010 0100
+ { 53, 12, 0x37 }, // 0000 0011 0111
+ { 54, 12, 0x38 }, // 0000 0011 1000
+ { 55, 12, 0x27 }, // 0000 0010 0111
+ { 56, 12, 0x28 }, // 0000 0010 1000
+ { 57, 12, 0x58 }, // 0000 0101 1000
+ { 58, 12, 0x59 }, // 0000 0101 1001
+ { 59, 12, 0x2B }, // 0000 0010 1011
+ { 60, 12, 0x2C }, // 0000 0010 1100
+ { 61, 12, 0x5A }, // 0000 0101 1010
+ { 62, 12, 0x66 }, // 0000 0110 0110
+ { 63, 12, 0x67 }, // 0000 0110 0111
+ { 64, 10, 0xF }, // 0000 0011 11
+ { 128, 12, 0xC8 }, // 0000 1100 1000
+ { 192, 12, 0xC9 }, // 0000 1100 1001
+ { 256, 12, 0x5B }, // 0000 0101 1011
+ { 320, 12, 0x33 }, // 0000 0011 0011
+ { 384, 12, 0x34 }, // 0000 0011 0100
+ { 448, 12, 0x35 }, // 0000 0011 0101
+ { 512, 13, 0x6C }, // 0000 0011 0110 0
+ { 576, 13, 0x6D }, // 0000 0011 0110 1
+ { 640, 13, 0x4A }, // 0000 0010 0101 0
+ { 704, 13, 0x4B }, // 0000 0010 0101 1
+ { 768, 13, 0x4C }, // 0000 0010 0110 0
+ { 832, 13, 0x4D }, // 0000 0010 0110 1
+ { 896, 13, 0x72 }, // 0000 0011 1001 0
+ { 960, 13, 0x73 }, // 0000 0011 1001 1
+ { 1024, 13, 0x74 }, // 0000 0011 1010 0
+ { 1088, 13, 0x75 }, // 0000 0011 1010 1
+ { 1152, 13, 0x76 }, // 0000 0011 1011 0
+ { 1216, 13, 0x77 }, // 0000 0011 1011 1
+ { 1280, 13, 0x52 }, // 0000 0010 1001 0
+ { 1344, 13, 0x53 }, // 0000 0010 1001 1
+ { 1408, 13, 0x54 }, // 0000 0010 1010 0
+ { 1472, 13, 0x55 }, // 0000 0010 1010 1
+ { 1536, 13, 0x5A }, // 0000 0010 1101 0
+ { 1600, 13, 0x5B }, // 0000 0010 1101 1
+ { 1664, 13, 0x64 }, // 0000 0011 0010 0
+ { 1728, 13, 0x65 }, // 0000 0011 0010 1
+ { 1792, 11, 0x8 }, // 0000 0001 000
+ { 1856, 11, 0xC }, // 0000 0001 100
+ { 1920, 11, 0xD }, // 0000 0001 101
+ { 1984, 12, 0x12 }, // 0000 0001 0010
+ { 2048, 12, 0x13 }, // 0000 0001 0011
+ { 2112, 12, 0x14 }, // 0000 0001 0100
+ { 2176, 12, 0x15 }, // 0000 0001 0101
+ { 2240, 12, 0x16 }, // 0000 0001 0110
+ { 2304, 12, 0x17 }, // 0000 0001 0111
+ { 2368, 12, 0x1C }, // 0000 0001 1100
+ { 2432, 12, 0x1D }, // 0000 0001 1101
+ { 2496, 12, 0x1E }, // 0000 0001 1110
+ { 2560, 12, 0x1F } // 0000 0001 1111
+};
+
+void PDFWriterImpl::putG4Span( tools::Long i_nSpan, bool i_bWhitePixel, BitStreamState& io_rState )
+{
+ const PixelCode* pTable = i_bWhitePixel ? WhitePixelCodes : BlackPixelCodes;
+ // maximum encoded span is 2560 consecutive pixels
+ while( i_nSpan > 2623 )
+ {
+ // write 2560 bits, that is entry (63 + (2560 >> 6)) == 103 in the appropriate table
+ putG4Bits( pTable[103].mnCodeBits, pTable[103].mnCode, io_rState );
+ i_nSpan -= pTable[103].mnEncodedPixels;
+ }
+ // write multiples of 64 pixels up to 2560
+ if( i_nSpan > 63 )
+ {
+ sal_uInt32 nTabIndex = 63 + (i_nSpan >> 6);
+ OSL_ASSERT( pTable[nTabIndex].mnEncodedPixels == static_cast<sal_uInt32>(64*(i_nSpan >> 6)) );
+ putG4Bits( pTable[nTabIndex].mnCodeBits, pTable[nTabIndex].mnCode, io_rState );
+ i_nSpan -= pTable[nTabIndex].mnEncodedPixels;
+ }
+ putG4Bits( pTable[i_nSpan].mnCodeBits, pTable[i_nSpan].mnCode, io_rState );
+}
+
+void PDFWriterImpl::writeG4Stream( BitmapReadAccess const * i_pBitmap )
+{
+ tools::Long nW = i_pBitmap->Width();
+ tools::Long nH = i_pBitmap->Height();
+ if( nW <= 0 || nH <= 0 )
+ return;
+ if( i_pBitmap->GetBitCount() != 1 )
+ return;
+
+ BitStreamState aBitState;
+
+ // the first reference line is virtual and completely empty
+ std::unique_ptr<sal_uInt8[]> pFirstRefLine(new sal_uInt8[nW/8 + 1]);
+ memset(pFirstRefLine.get(), 0, nW/8 + 1);
+ Scanline pRefLine = pFirstRefLine.get();
+ for( tools::Long nY = 0; nY < nH; nY++ )
+ {
+ const Scanline pCurLine = i_pBitmap->GetScanline( nY );
+ tools::Long nLineIndex = 0;
+ bool bRunSet = (*pCurLine & 0x80) != 0;
+ bool bRefSet = (*pRefLine & 0x80) != 0;
+ tools::Long nRunIndex1 = bRunSet ? 0 : findBitRun( pCurLine, 0, nW, bRunSet );
+ tools::Long nRefIndex1 = bRefSet ? 0 : findBitRun( pRefLine, 0, nW, bRefSet );
+ for( ; nLineIndex < nW; )
+ {
+ tools::Long nRefIndex2 = findBitRun( pRefLine, nRefIndex1, nW );
+ if( nRefIndex2 >= nRunIndex1 )
+ {
+ tools::Long nDiff = nRefIndex1 - nRunIndex1;
+ if( -3 <= nDiff && nDiff <= 3 )
+ { // vertical coding
+ static const struct
+ {
+ sal_uInt32 mnCodeBits;
+ sal_uInt32 mnCode;
+ } VerticalCodes[7] = {
+ { 7, 0x03 }, // 0000 011
+ { 6, 0x03 }, // 0000 11
+ { 3, 0x03 }, // 011
+ { 1, 0x1 }, // 1
+ { 3, 0x2 }, // 010
+ { 6, 0x02 }, // 0000 10
+ { 7, 0x02 } // 0000 010
+ };
+ // convert to index
+ nDiff += 3;
+
+ // emit diff code
+ putG4Bits( VerticalCodes[nDiff].mnCodeBits, VerticalCodes[nDiff].mnCode, aBitState );
+ nLineIndex = nRunIndex1;
+ }
+ else
+ { // difference too large, horizontal coding
+ // emit horz code 001
+ putG4Bits( 3, 0x1, aBitState );
+ tools::Long nRunIndex2 = findBitRun( pCurLine, nRunIndex1, nW );
+ bool bWhiteFirst = ( nLineIndex + nRunIndex1 == 0 || ! isSet( pCurLine, nLineIndex ) );
+ putG4Span( nRunIndex1 - nLineIndex, bWhiteFirst, aBitState );
+ putG4Span( nRunIndex2 - nRunIndex1, ! bWhiteFirst, aBitState );
+ nLineIndex = nRunIndex2;
+ }
+ }
+ else
+ { // emit pass code 0001
+ putG4Bits( 4, 0x1, aBitState );
+ nLineIndex = nRefIndex2;
+ }
+ if( nLineIndex < nW )
+ {
+ bool bSet = isSet( pCurLine, nLineIndex );
+ nRunIndex1 = findBitRun( pCurLine, nLineIndex, nW, bSet );
+ nRefIndex1 = findBitRun( pRefLine, nLineIndex, nW, ! bSet );
+ nRefIndex1 = findBitRun( pRefLine, nRefIndex1, nW, bSet );
+ }
+ }
+
+ // the current line is the reference for the next line
+ pRefLine = pCurLine;
+ }
+ // terminate strip with EOFB
+ putG4Bits( 12, 1, aBitState );
+ putG4Bits( 12, 1, aBitState );
+ if( aBitState.mnNextBitPos != 8 )
+ {
+ writeBuffer( &aBitState.getByte(), 1 );
+ aBitState.flush();
+ }
+}
+
+void PDFWriterImpl::DrawHatchLine_DrawLine(const Point& rStartPoint, const Point& rEndPoint)
+{
+ drawLine(rStartPoint, rEndPoint);
+}
+
+static bool lcl_canUsePDFAxialShading(const Gradient& rGradient) {
+ switch (rGradient.GetStyle())
+ {
+ case GradientStyle::Linear:
+ case GradientStyle::Axial:
+ break;
+ default:
+ return false;
+ }
+
+ // TODO: handle step count
+ return rGradient.GetSteps() <= 0;
+}
+
+void PDFWriterImpl::ImplClearFontData(bool bNewFontLists)
+{
+ VirtualDevice::ImplClearFontData(bNewFontLists);
+ if (bNewFontLists && AcquireGraphics())
+ {
+ ReleaseFontCollection();
+ ReleaseFontCache();
+ }
+}
+
+void PDFWriterImpl::ImplRefreshFontData(bool bNewFontLists)
+{
+ if (bNewFontLists && AcquireGraphics())
+ {
+ SetFontCollectionFromSVData();
+ ResetNewFontCache();
+ }
+}
+
+vcl::Region PDFWriterImpl::ClipToDeviceBounds(vcl::Region aRegion) const
+{
+ return aRegion;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/print.cxx b/vcl/source/gdi/print.cxx
new file mode 100644
index 000000000..001bb28e9
--- /dev/null
+++ b/vcl/source/gdi/print.cxx
@@ -0,0 +1,1694 @@
+/* -*- 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 <sal/types.h>
+#include <sal/log.hxx>
+#include <comphelper/processfactory.hxx>
+#include <o3tl/safeint.hxx>
+#include <tools/debug.hxx>
+#include <tools/helpers.hxx>
+
+#include <vcl/QueueInfo.hxx>
+#include <vcl/event.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/print.hxx>
+#include <vcl/printer/Options.hxx>
+
+#include <jobset.h>
+#include <print.h>
+#include <ImplOutDevData.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <font/PhysicalFontFaceCollection.hxx>
+#include <font/fontsubstitution.hxx>
+#include <impfontcache.hxx>
+#include <print.hrc>
+#include <salgdi.hxx>
+#include <salinst.hxx>
+#include <salprn.hxx>
+#include <salptype.hxx>
+#include <salvd.hxx>
+#include <svdata.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/uno/Sequence.h>
+
+int nImplSysDialog = 0;
+
+namespace
+{
+ Paper ImplGetPaperFormat( tools::Long nWidth100thMM, tools::Long nHeight100thMM )
+ {
+ PaperInfo aInfo(nWidth100thMM, nHeight100thMM);
+ aInfo.doSloppyFit();
+ return aInfo.getPaper();
+ }
+
+ const PaperInfo& ImplGetEmptyPaper()
+ {
+ static PaperInfo aInfo(PAPER_USER);
+ return aInfo;
+ }
+}
+
+void ImplUpdateJobSetupPaper( JobSetup& rJobSetup )
+{
+ const ImplJobSetup& rConstData = rJobSetup.ImplGetConstData();
+
+ if ( !rConstData.GetPaperWidth() || !rConstData.GetPaperHeight() )
+ {
+ if ( rConstData.GetPaperFormat() != PAPER_USER )
+ {
+ PaperInfo aInfo(rConstData.GetPaperFormat());
+
+ ImplJobSetup& rData = rJobSetup.ImplGetData();
+ rData.SetPaperWidth( aInfo.getWidth() );
+ rData.SetPaperHeight( aInfo.getHeight() );
+ }
+ }
+ else if ( rConstData.GetPaperFormat() == PAPER_USER )
+ {
+ Paper ePaper = ImplGetPaperFormat( rConstData.GetPaperWidth(), rConstData.GetPaperHeight() );
+ if ( ePaper != PAPER_USER )
+ rJobSetup.ImplGetData().SetPaperFormat(ePaper);
+ }
+}
+
+void Printer::ImplPrintTransparent( const Bitmap& rBmp, const Bitmap& rMask,
+ const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel )
+{
+ Point aDestPt( LogicToPixel( rDestPt ) );
+ Size aDestSz( LogicToPixel( rDestSize ) );
+ tools::Rectangle aSrcRect( rSrcPtPixel, rSrcSizePixel );
+
+ aSrcRect.Justify();
+
+ if( rBmp.IsEmpty() || !aSrcRect.GetWidth() || !aSrcRect.GetHeight() || !aDestSz.Width() || !aDestSz.Height() )
+ return;
+
+ Bitmap aPaint( rBmp ), aMask( rMask );
+ BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE;
+
+ if (aMask.getPixelFormat() > vcl::PixelFormat::N1_BPP)
+ aMask.Convert( BmpConversion::N1BitThreshold );
+
+ // mirrored horizontally
+ if( aDestSz.Width() < 0 )
+ {
+ aDestSz.setWidth( -aDestSz.Width() );
+ aDestPt.AdjustX( -( aDestSz.Width() - 1 ) );
+ nMirrFlags |= BmpMirrorFlags::Horizontal;
+ }
+
+ // mirrored vertically
+ if( aDestSz.Height() < 0 )
+ {
+ aDestSz.setHeight( -aDestSz.Height() );
+ aDestPt.AdjustY( -( aDestSz.Height() - 1 ) );
+ nMirrFlags |= BmpMirrorFlags::Vertical;
+ }
+
+ // source cropped?
+ if( aSrcRect != tools::Rectangle( Point(), aPaint.GetSizePixel() ) )
+ {
+ aPaint.Crop( aSrcRect );
+ aMask.Crop( aSrcRect );
+ }
+
+ // destination mirrored
+ if( nMirrFlags != BmpMirrorFlags::NONE )
+ {
+ aPaint.Mirror( nMirrFlags );
+ aMask.Mirror( nMirrFlags );
+ }
+
+ // we always want to have a mask
+ if( aMask.IsEmpty() )
+ {
+ aMask = Bitmap(aSrcRect.GetSize(), vcl::PixelFormat::N1_BPP);
+ aMask.Erase( COL_BLACK );
+ }
+
+ // do painting
+ const tools::Long nSrcWidth = aSrcRect.GetWidth(), nSrcHeight = aSrcRect.GetHeight();
+ tools::Long nX, nY; // , nWorkX, nWorkY, nWorkWidth, nWorkHeight;
+ std::unique_ptr<tools::Long[]> pMapX(new tools::Long[ nSrcWidth + 1 ]);
+ std::unique_ptr<tools::Long[]> pMapY(new tools::Long[ nSrcHeight + 1 ]);
+ const bool bOldMap = mbMap;
+
+ mbMap = false;
+
+ // create forward mapping tables
+ for( nX = 0; nX <= nSrcWidth; nX++ )
+ pMapX[ nX ] = aDestPt.X() + FRound( static_cast<double>(aDestSz.Width()) * nX / nSrcWidth );
+
+ for( nY = 0; nY <= nSrcHeight; nY++ )
+ pMapY[ nY ] = aDestPt.Y() + FRound( static_cast<double>(aDestSz.Height()) * nY / nSrcHeight );
+
+ // walk through all rectangles of mask
+ const vcl::Region aWorkRgn(aMask.CreateRegion(COL_BLACK, tools::Rectangle(Point(), aMask.GetSizePixel())));
+ RectangleVector aRectangles;
+ aWorkRgn.GetRegionRectangles(aRectangles);
+
+ for (auto const& rectangle : aRectangles)
+ {
+ const Point aMapPt(pMapX[rectangle.Left()], pMapY[rectangle.Top()]);
+ const Size aMapSz( pMapX[rectangle.Right() + 1] - aMapPt.X(), // pMapX[L + W] -> L + ((R - L) + 1) -> R + 1
+ pMapY[rectangle.Bottom() + 1] - aMapPt.Y()); // same for Y
+ Bitmap aBandBmp(aPaint);
+
+ aBandBmp.Crop(rectangle);
+ DrawBitmap(aMapPt, aMapSz, Point(), aBandBmp.GetSizePixel(), aBandBmp);
+ }
+
+ mbMap = bOldMap;
+
+}
+
+bool Printer::DrawTransformBitmapExDirect(
+ const basegfx::B2DHomMatrix& /*aFullTransform*/,
+ const BitmapEx& /*rBitmapEx*/,
+ double /*fAlpha*/)
+{
+ // printers can't draw bitmaps directly
+ return false;
+}
+
+bool Printer::TransformAndReduceBitmapExToTargetRange(
+ const basegfx::B2DHomMatrix& /*aFullTransform*/,
+ basegfx::B2DRange& /*aVisibleRange*/,
+ double& /*fMaximumArea*/)
+{
+ // deliberately do nothing - you can't reduce the
+ // target range for a printer at all
+ return true;
+}
+
+void Printer::DrawDeviceBitmapEx( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ BitmapEx& rBmpEx )
+{
+ if( rBmpEx.IsAlpha() )
+ {
+ // #107169# For true alpha bitmaps, no longer masking the
+ // bitmap, but perform a full alpha blend against a white
+ // background here.
+ Bitmap aBmp( rBmpEx.GetBitmap() );
+ aBmp.Blend( rBmpEx.GetAlpha(), COL_WHITE );
+ DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmp );
+ }
+ else
+ {
+ Bitmap aBmp( rBmpEx.GetBitmap() );
+ ImplPrintTransparent( aBmp, Bitmap(), rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel );
+ }
+}
+
+void Printer::EmulateDrawTransparent ( const tools::PolyPolygon& rPolyPoly,
+ sal_uInt16 nTransparencePercent )
+{
+ // #110958# Disable alpha VDev, we perform the necessary
+ VirtualDevice* pOldAlphaVDev = mpAlphaVDev;
+
+ // operation explicitly further below.
+ if( mpAlphaVDev )
+ mpAlphaVDev = nullptr;
+
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ mpMetaFile = nullptr;
+
+ mpMetaFile = pOldMetaFile;
+
+ // #110958# Restore disabled alpha VDev
+ mpAlphaVDev = pOldAlphaVDev;
+
+ tools::Rectangle aPolyRect( LogicToPixel( rPolyPoly ).GetBoundRect() );
+ const Size aDPISize( LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)) );
+ const tools::Long nBaseExtent = std::max<tools::Long>( FRound( aDPISize.Width() / 300. ), 1 );
+ tools::Long nMove;
+ const sal_uInt16 nTrans = ( nTransparencePercent < 13 ) ? 0 :
+ ( nTransparencePercent < 38 ) ? 25 :
+ ( nTransparencePercent < 63 ) ? 50 :
+ ( nTransparencePercent < 88 ) ? 75 : 100;
+
+ switch( nTrans )
+ {
+ case 25: nMove = nBaseExtent * 3; break;
+ case 50: nMove = nBaseExtent * 4; break;
+ case 75: nMove = nBaseExtent * 6; break;
+
+ // #i112959# very transparent (88 < nTransparencePercent <= 99)
+ case 100: nMove = nBaseExtent * 8; break;
+
+ // #i112959# not transparent (nTransparencePercent < 13)
+ default: nMove = 0; break;
+ }
+
+ Push( vcl::PushFlags::CLIPREGION | vcl::PushFlags::LINECOLOR );
+ IntersectClipRegion(vcl::Region(rPolyPoly));
+ SetLineColor( GetFillColor() );
+ const bool bOldMap = mbMap;
+ EnableMapMode( false );
+
+ if(nMove)
+ {
+ tools::Rectangle aRect( aPolyRect.TopLeft(), Size( aPolyRect.GetWidth(), nBaseExtent ) );
+ while( aRect.Top() <= aPolyRect.Bottom() )
+ {
+ DrawRect( aRect );
+ aRect.Move( 0, nMove );
+ }
+
+ aRect = tools::Rectangle( aPolyRect.TopLeft(), Size( nBaseExtent, aPolyRect.GetHeight() ) );
+ while( aRect.Left() <= aPolyRect.Right() )
+ {
+ DrawRect( aRect );
+ aRect.Move( nMove, 0 );
+ }
+ }
+ else
+ {
+ // #i112959# if not transparent, draw full rectangle in clip region
+ DrawRect( aPolyRect );
+ }
+
+ EnableMapMode( bOldMap );
+ Pop();
+
+ mpMetaFile = pOldMetaFile;
+
+ // #110958# Restore disabled alpha VDev
+ mpAlphaVDev = pOldAlphaVDev;
+}
+
+void Printer::DrawOutDev( const Point& /*rDestPt*/, const Size& /*rDestSize*/,
+ const Point& /*rSrcPt*/, const Size& /*rSrcSize*/ )
+{
+ SAL_WARN( "vcl.gdi", "Don't use OutputDevice::DrawOutDev(...) with printer devices!" );
+}
+
+void Printer::DrawOutDev( const Point& /*rDestPt*/, const Size& /*rDestSize*/,
+ const Point& /*rSrcPt*/, const Size& /*rSrcSize*/,
+ const OutputDevice& /*rOutDev*/ )
+{
+ SAL_WARN( "vcl.gdi", "Don't use OutputDevice::DrawOutDev(...) with printer devices!" );
+}
+
+void Printer::CopyArea( const Point& /*rDestPt*/,
+ const Point& /*rSrcPt*/, const Size& /*rSrcSize*/,
+ bool /*bWindowInvalidate*/ )
+{
+ SAL_WARN( "vcl.gdi", "Don't use OutputDevice::CopyArea(...) with printer devices!" );
+}
+
+tools::Rectangle Printer::GetBackgroundComponentBounds() const
+{
+ Point aPageOffset = Point( 0, 0 ) - this->GetPageOffsetPixel();
+ Size aSize = this->GetPaperSizePixel();
+ return tools::Rectangle( aPageOffset, aSize );
+}
+
+void Printer::SetPrinterOptions( const vcl::printer::Options& i_rOptions )
+{
+ *mpPrinterOptions = i_rOptions;
+}
+
+bool Printer::HasMirroredGraphics() const
+{
+ // due to a "hotfix" for AOO bug i55719, this needs to return false
+ return false;
+}
+
+SalPrinterQueueInfo::SalPrinterQueueInfo()
+{
+ mnStatus = PrintQueueFlags::NONE;
+ mnJobs = QUEUE_JOBS_DONTKNOW;
+}
+
+SalPrinterQueueInfo::~SalPrinterQueueInfo()
+{
+}
+
+ImplPrnQueueList::~ImplPrnQueueList()
+{
+}
+
+void ImplPrnQueueList::Add( std::unique_ptr<SalPrinterQueueInfo> pData )
+{
+ std::unordered_map< OUString, sal_Int32 >::iterator it =
+ m_aNameToIndex.find( pData->maPrinterName );
+ if( it == m_aNameToIndex.end() )
+ {
+ m_aNameToIndex[ pData->maPrinterName ] = m_aQueueInfos.size();
+ m_aPrinterList.push_back( pData->maPrinterName );
+ m_aQueueInfos.push_back( ImplPrnQueueData() );
+ m_aQueueInfos.back().mpQueueInfo = nullptr;
+ m_aQueueInfos.back().mpSalQueueInfo = std::move(pData);
+ }
+ else // this should not happen, but ...
+ {
+ ImplPrnQueueData& rData = m_aQueueInfos[ it->second ];
+ rData.mpQueueInfo.reset();
+ rData.mpSalQueueInfo = std::move(pData);
+ }
+}
+
+ImplPrnQueueData* ImplPrnQueueList::Get( const OUString& rPrinter )
+{
+ ImplPrnQueueData* pData = nullptr;
+ std::unordered_map<OUString,sal_Int32>::iterator it =
+ m_aNameToIndex.find( rPrinter );
+ if( it != m_aNameToIndex.end() )
+ pData = &m_aQueueInfos[it->second];
+ return pData;
+}
+
+static void ImplInitPrnQueueList()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ pSVData->maGDIData.mpPrinterQueueList.reset(new ImplPrnQueueList);
+
+ static const char* pEnv = getenv( "SAL_DISABLE_PRINTERLIST" );
+ if( !pEnv || !*pEnv )
+ pSVData->mpDefInst->GetPrinterQueueInfo( pSVData->maGDIData.mpPrinterQueueList.get() );
+}
+
+void ImplDeletePrnQueueList()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maGDIData.mpPrinterQueueList.reset();
+}
+
+const std::vector<OUString>& Printer::GetPrinterQueues()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData->maGDIData.mpPrinterQueueList )
+ ImplInitPrnQueueList();
+ assert(pSVData->maGDIData.mpPrinterQueueList && "mpPrinterQueueList exists by now");
+ return pSVData->maGDIData.mpPrinterQueueList->m_aPrinterList;
+}
+
+const QueueInfo* Printer::GetQueueInfo( const OUString& rPrinterName, bool bStatusUpdate )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( !pSVData->maGDIData.mpPrinterQueueList )
+ ImplInitPrnQueueList();
+
+ if ( !pSVData->maGDIData.mpPrinterQueueList )
+ return nullptr;
+
+ ImplPrnQueueData* pInfo = pSVData->maGDIData.mpPrinterQueueList->Get( rPrinterName );
+ if( pInfo )
+ {
+ if( !pInfo->mpQueueInfo || bStatusUpdate )
+ pSVData->mpDefInst->GetPrinterQueueState( pInfo->mpSalQueueInfo.get() );
+
+ if ( !pInfo->mpQueueInfo )
+ pInfo->mpQueueInfo.reset(new QueueInfo);
+
+ pInfo->mpQueueInfo->maPrinterName = pInfo->mpSalQueueInfo->maPrinterName;
+ pInfo->mpQueueInfo->maDriver = pInfo->mpSalQueueInfo->maDriver;
+ pInfo->mpQueueInfo->maLocation = pInfo->mpSalQueueInfo->maLocation;
+ pInfo->mpQueueInfo->maComment = pInfo->mpSalQueueInfo->maComment;
+ pInfo->mpQueueInfo->mnStatus = pInfo->mpSalQueueInfo->mnStatus;
+ pInfo->mpQueueInfo->mnJobs = pInfo->mpSalQueueInfo->mnJobs;
+ return pInfo->mpQueueInfo.get();
+ }
+ return nullptr;
+}
+
+OUString Printer::GetDefaultPrinterName()
+{
+ static const char* pEnv = getenv( "SAL_DISABLE_DEFAULTPRINTER" );
+ if( !pEnv || !*pEnv )
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+
+ return pSVData->mpDefInst->GetDefaultPrinter();
+ }
+ return OUString();
+}
+
+void Printer::ImplInitData()
+{
+ mbDevOutput = false;
+ mbDefPrinter = false;
+ mnError = ERRCODE_NONE;
+ mnPageQueueSize = 0;
+ mnCopyCount = 1;
+ mbCollateCopy = false;
+ mbPrinting = false;
+ mbJobActive = false;
+ mbPrintFile = false;
+ mbInPrintPage = false;
+ mbNewJobSetup = false;
+ mbSinglePrintJobs = false;
+ mpInfoPrinter = nullptr;
+ mpPrinter = nullptr;
+ mpDisplayDev = nullptr;
+ mpPrinterOptions.reset(new vcl::printer::Options);
+
+ // Add printer to the list
+ ImplSVData* pSVData = ImplGetSVData();
+ mpNext = pSVData->maGDIData.mpFirstPrinter;
+ mpPrev = nullptr;
+ if ( mpNext )
+ mpNext->mpPrev = this;
+ pSVData->maGDIData.mpFirstPrinter = this;
+}
+
+bool Printer::AcquireGraphics() const
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( mpGraphics )
+ return true;
+
+ mbInitLineColor = true;
+ mbInitFillColor = true;
+ mbInitFont = true;
+ mbInitTextColor = true;
+ mbInitClipRegion = true;
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( mpJobGraphics )
+ mpGraphics = mpJobGraphics;
+ else if ( mpDisplayDev )
+ {
+ const VirtualDevice* pVirDev = mpDisplayDev;
+ mpGraphics = pVirDev->mpVirDev->AcquireGraphics();
+ // if needed retry after releasing least recently used virtual device graphics
+ while ( !mpGraphics )
+ {
+ if ( !pSVData->maGDIData.mpLastVirGraphics )
+ break;
+ pSVData->maGDIData.mpLastVirGraphics->ReleaseGraphics();
+ mpGraphics = pVirDev->mpVirDev->AcquireGraphics();
+ }
+ // update global LRU list of virtual device graphics
+ if ( mpGraphics )
+ {
+ mpNextGraphics = pSVData->maGDIData.mpFirstVirGraphics;
+ pSVData->maGDIData.mpFirstVirGraphics = const_cast<Printer*>(this);
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = const_cast<Printer*>(this);
+ if ( !pSVData->maGDIData.mpLastVirGraphics )
+ pSVData->maGDIData.mpLastVirGraphics = const_cast<Printer*>(this);
+ }
+ }
+ else
+ {
+ mpGraphics = mpInfoPrinter->AcquireGraphics();
+ // if needed retry after releasing least recently used printer graphics
+ while ( !mpGraphics )
+ {
+ if ( !pSVData->maGDIData.mpLastPrnGraphics )
+ break;
+ pSVData->maGDIData.mpLastPrnGraphics->ReleaseGraphics();
+ mpGraphics = mpInfoPrinter->AcquireGraphics();
+ }
+ // update global LRU list of printer graphics
+ if ( mpGraphics )
+ {
+ mpNextGraphics = pSVData->maGDIData.mpFirstPrnGraphics;
+ pSVData->maGDIData.mpFirstPrnGraphics = const_cast<Printer*>(this);
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = const_cast<Printer*>(this);
+ if ( !pSVData->maGDIData.mpLastPrnGraphics )
+ pSVData->maGDIData.mpLastPrnGraphics = const_cast<Printer*>(this);
+ }
+ }
+
+ if ( mpGraphics )
+ {
+ mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp), RasterOp::Invert == meRasterOp );
+ mpGraphics->setAntiAlias(bool(mnAntialiasing & AntialiasingFlags::Enable));
+ }
+
+ return mpGraphics != nullptr;
+}
+
+void Printer::ImplReleaseFonts()
+{
+ mpGraphics->ReleaseFonts();
+ mbNewFont = true;
+ mbInitFont = true;
+
+ mpFontInstance.clear();
+ mpFontFaceCollection.reset();
+}
+
+void Printer::ImplReleaseGraphics(bool bRelease)
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( !mpGraphics )
+ return;
+
+ // release the fonts of the physically released graphics device
+ if( bRelease )
+ ImplReleaseFonts();
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ Printer* pPrinter = this;
+
+ if ( !pPrinter->mpJobGraphics )
+ {
+ if ( pPrinter->mpDisplayDev )
+ {
+ VirtualDevice* pVirDev = pPrinter->mpDisplayDev;
+ if ( bRelease )
+ pVirDev->mpVirDev->ReleaseGraphics( mpGraphics );
+ // remove from global LRU list of virtual device graphics
+ if ( mpPrevGraphics )
+ mpPrevGraphics->mpNextGraphics = mpNextGraphics;
+ else
+ pSVData->maGDIData.mpFirstVirGraphics = mpNextGraphics;
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = mpPrevGraphics;
+ else
+ pSVData->maGDIData.mpLastVirGraphics = mpPrevGraphics;
+ }
+ else
+ {
+ if ( bRelease )
+ pPrinter->mpInfoPrinter->ReleaseGraphics( mpGraphics );
+ // remove from global LRU list of printer graphics
+ if ( mpPrevGraphics )
+ mpPrevGraphics->mpNextGraphics = mpNextGraphics;
+ else
+ pSVData->maGDIData.mpFirstPrnGraphics = static_cast<Printer*>(mpNextGraphics.get());
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = mpPrevGraphics;
+ else
+ pSVData->maGDIData.mpLastPrnGraphics = static_cast<Printer*>(mpPrevGraphics.get());
+ }
+ }
+
+ mpGraphics = nullptr;
+ mpPrevGraphics = nullptr;
+ mpNextGraphics = nullptr;
+}
+
+void Printer::ReleaseGraphics(bool bRelease)
+{
+ ImplReleaseGraphics(bRelease);
+}
+
+void Printer::ImplInit( SalPrinterQueueInfo* pInfo )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ // #i74084# update info for this specific SalPrinterQueueInfo
+ pSVData->mpDefInst->GetPrinterQueueState( pInfo );
+
+ // Test whether the driver actually matches the JobSetup
+ ImplJobSetup& rData = maJobSetup.ImplGetData();
+ if ( rData.GetDriverData() )
+ {
+ if ( rData.GetPrinterName() != pInfo->maPrinterName ||
+ rData.GetDriver() != pInfo->maDriver )
+ {
+ std::free( const_cast<sal_uInt8*>(rData.GetDriverData()) );
+ rData.SetDriverData(nullptr);
+ rData.SetDriverDataLen(0);
+ }
+ }
+
+ // Remember printer name
+ maPrinterName = pInfo->maPrinterName;
+ maDriver = pInfo->maDriver;
+
+ // Add printer name to JobSetup
+ rData.SetPrinterName( maPrinterName );
+ rData.SetDriver( maDriver );
+
+ mpInfoPrinter = pSVData->mpDefInst->CreateInfoPrinter( pInfo, &rData );
+ mpPrinter = nullptr;
+ mpJobGraphics = nullptr;
+ ImplUpdateJobSetupPaper( maJobSetup );
+
+ if ( !mpInfoPrinter )
+ {
+ ImplInitDisplay();
+ return;
+ }
+
+ // we need a graphics
+ if ( !AcquireGraphics() )
+ {
+ ImplInitDisplay();
+ return;
+ }
+
+ // Init data
+ ImplUpdatePageData();
+ mxFontCollection = std::make_shared<vcl::font::PhysicalFontCollection>();
+ mxFontCache = std::make_shared<ImplFontCache>();
+ mpGraphics->GetDevFontList(mxFontCollection.get());
+}
+
+void Printer::ImplInitDisplay()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ mpInfoPrinter = nullptr;
+ mpPrinter = nullptr;
+ mpJobGraphics = nullptr;
+
+ mpDisplayDev = VclPtr<VirtualDevice>::Create();
+ mxFontCollection = pSVData->maGDIData.mxScreenFontList;
+ mxFontCache = pSVData->maGDIData.mxScreenFontCache;
+ mnDPIX = mpDisplayDev->mnDPIX;
+ mnDPIY = mpDisplayDev->mnDPIY;
+}
+
+void Printer::DrawDeviceMask( const Bitmap& rMask, const Color& rMaskColor,
+ const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel )
+{
+ Point aDestPt( LogicToPixel( rDestPt ) );
+ Size aDestSz( LogicToPixel( rDestSize ) );
+ tools::Rectangle aSrcRect( rSrcPtPixel, rSrcSizePixel );
+
+ aSrcRect.Justify();
+
+ if( !(!rMask.IsEmpty() && aSrcRect.GetWidth() && aSrcRect.GetHeight() && aDestSz.Width() && aDestSz.Height()) )
+ return;
+
+ Bitmap aMask( rMask );
+ BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE;
+
+ if (aMask.getPixelFormat() > vcl::PixelFormat::N1_BPP)
+ aMask.Convert( BmpConversion::N1BitThreshold );
+
+ // mirrored horizontally
+ if( aDestSz.Width() < 0 )
+ {
+ aDestSz.setWidth( -aDestSz.Width() );
+ aDestPt.AdjustX( -( aDestSz.Width() - 1 ) );
+ nMirrFlags |= BmpMirrorFlags::Horizontal;
+ }
+
+ // mirrored vertically
+ if( aDestSz.Height() < 0 )
+ {
+ aDestSz.setHeight( -aDestSz.Height() );
+ aDestPt.AdjustY( -( aDestSz.Height() - 1 ) );
+ nMirrFlags |= BmpMirrorFlags::Vertical;
+ }
+
+ // source cropped?
+ if( aSrcRect != tools::Rectangle( Point(), aMask.GetSizePixel() ) )
+ aMask.Crop( aSrcRect );
+
+ // destination mirrored
+ if( nMirrFlags != BmpMirrorFlags::NONE)
+ aMask.Mirror( nMirrFlags );
+
+ // do painting
+ const tools::Long nSrcWidth = aSrcRect.GetWidth(), nSrcHeight = aSrcRect.GetHeight();
+ tools::Long nX, nY; //, nWorkX, nWorkY, nWorkWidth, nWorkHeight;
+ std::unique_ptr<tools::Long[]> pMapX( new tools::Long[ nSrcWidth + 1 ] );
+ std::unique_ptr<tools::Long[]> pMapY( new tools::Long[ nSrcHeight + 1 ] );
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ const bool bOldMap = mbMap;
+
+ mpMetaFile = nullptr;
+ mbMap = false;
+ Push( vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR );
+ SetLineColor( rMaskColor );
+ SetFillColor( rMaskColor );
+ InitLineColor();
+ InitFillColor();
+
+ // create forward mapping tables
+ for( nX = 0; nX <= nSrcWidth; nX++ )
+ pMapX[ nX ] = aDestPt.X() + FRound( static_cast<double>(aDestSz.Width()) * nX / nSrcWidth );
+
+ for( nY = 0; nY <= nSrcHeight; nY++ )
+ pMapY[ nY ] = aDestPt.Y() + FRound( static_cast<double>(aDestSz.Height()) * nY / nSrcHeight );
+
+ // walk through all rectangles of mask
+ const vcl::Region aWorkRgn(aMask.CreateRegion(COL_BLACK, tools::Rectangle(Point(), aMask.GetSizePixel())));
+ RectangleVector aRectangles;
+ aWorkRgn.GetRegionRectangles(aRectangles);
+
+ for (auto const& rectangle : aRectangles)
+ {
+ const Point aMapPt(pMapX[rectangle.Left()], pMapY[rectangle.Top()]);
+ const Size aMapSz(
+ pMapX[rectangle.Right() + 1] - aMapPt.X(), // pMapX[L + W] -> L + ((R - L) + 1) -> R + 1
+ pMapY[rectangle.Bottom() + 1] - aMapPt.Y()); // same for Y
+
+ DrawRect(tools::Rectangle(aMapPt, aMapSz));
+ }
+
+ Pop();
+ mbMap = bOldMap;
+ mpMetaFile = pOldMetaFile;
+}
+
+SalPrinterQueueInfo* Printer::ImplGetQueueInfo( const OUString& rPrinterName,
+ const OUString* pDriver )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData->maGDIData.mpPrinterQueueList )
+ ImplInitPrnQueueList();
+
+ ImplPrnQueueList* pPrnList = pSVData->maGDIData.mpPrinterQueueList.get();
+ if ( pPrnList && !pPrnList->m_aQueueInfos.empty() )
+ {
+ // first search for the printer name directly
+ ImplPrnQueueData* pInfo = pPrnList->Get( rPrinterName );
+ if( pInfo )
+ return pInfo->mpSalQueueInfo.get();
+
+ // then search case insensitive
+ for(const ImplPrnQueueData & rQueueInfo : pPrnList->m_aQueueInfos)
+ {
+ if( rQueueInfo.mpSalQueueInfo->maPrinterName.equalsIgnoreAsciiCase( rPrinterName ) )
+ return rQueueInfo.mpSalQueueInfo.get();
+ }
+
+ // then search for driver name
+ if ( pDriver )
+ {
+ for(const ImplPrnQueueData & rQueueInfo : pPrnList->m_aQueueInfos)
+ {
+ if( rQueueInfo.mpSalQueueInfo->maDriver == *pDriver )
+ return rQueueInfo.mpSalQueueInfo.get();
+ }
+ }
+
+ // then the default printer
+ pInfo = pPrnList->Get( GetDefaultPrinterName() );
+ if( pInfo )
+ return pInfo->mpSalQueueInfo.get();
+
+ // last chance: the first available printer
+ return pPrnList->m_aQueueInfos[0].mpSalQueueInfo.get();
+ }
+
+ return nullptr;
+}
+
+void Printer::ImplUpdatePageData()
+{
+ // we need a graphics
+ if ( !AcquireGraphics() )
+ return;
+
+ mpGraphics->GetResolution( mnDPIX, mnDPIY );
+ mpInfoPrinter->GetPageInfo( &maJobSetup.ImplGetConstData(),
+ mnOutWidth, mnOutHeight,
+ maPageOffset,
+ maPaperSize );
+}
+
+void Printer::ImplUpdateFontList()
+{
+ ImplUpdateFontData();
+}
+
+tools::Long Printer::GetGradientStepCount( tools::Long nMinRect )
+{
+ // use display-equivalent step size calculation
+ tools::Long nInc = (nMinRect < 800) ? 10 : 20;
+
+ return nInc;
+}
+
+Printer::Printer()
+ : OutputDevice(OUTDEV_PRINTER)
+{
+ ImplInitData();
+ SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( GetDefaultPrinterName(), nullptr );
+ if ( pInfo )
+ {
+ ImplInit( pInfo );
+ if ( !IsDisplayPrinter() )
+ mbDefPrinter = true;
+ }
+ else
+ ImplInitDisplay();
+}
+
+Printer::Printer( const JobSetup& rJobSetup )
+ : OutputDevice(OUTDEV_PRINTER)
+ , maJobSetup(rJobSetup)
+{
+ ImplInitData();
+ const ImplJobSetup& rConstData = rJobSetup.ImplGetConstData();
+ OUString aDriver = rConstData.GetDriver();
+ SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( rConstData.GetPrinterName(),
+ &aDriver );
+ if ( pInfo )
+ {
+ ImplInit( pInfo );
+ SetJobSetup( rJobSetup );
+ }
+ else
+ {
+ ImplInitDisplay();
+ maJobSetup = JobSetup();
+ }
+}
+
+Printer::Printer( const QueueInfo& rQueueInfo )
+ : OutputDevice(OUTDEV_PRINTER)
+{
+ ImplInitData();
+ SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( rQueueInfo.GetPrinterName(),
+ &rQueueInfo.GetDriver() );
+ if ( pInfo )
+ ImplInit( pInfo );
+ else
+ ImplInitDisplay();
+}
+
+Printer::Printer( const OUString& rPrinterName )
+ : OutputDevice(OUTDEV_PRINTER)
+{
+ ImplInitData();
+ SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( rPrinterName, nullptr );
+ if ( pInfo )
+ ImplInit( pInfo );
+ else
+ ImplInitDisplay();
+}
+
+Printer::~Printer()
+{
+ disposeOnce();
+}
+
+void Printer::dispose()
+{
+ SAL_WARN_IF( IsPrinting(), "vcl.gdi", "Printer::~Printer() - Job is printing" );
+ SAL_WARN_IF( IsJobActive(), "vcl.gdi", "Printer::~Printer() - Job is active" );
+
+ mpPrinterOptions.reset();
+
+ ImplReleaseGraphics();
+ if ( mpInfoPrinter )
+ ImplGetSVData()->mpDefInst->DestroyInfoPrinter( mpInfoPrinter );
+ if ( mpDisplayDev )
+ mpDisplayDev.disposeAndClear();
+ else
+ {
+ // OutputDevice Dtor is trying the same thing; that why we need to set
+ // the FontEntry to NULL here
+ // TODO: consolidate duplicate cleanup by Printer and OutputDevice
+ mpFontInstance.clear();
+ mpFontFaceCollection.reset();
+ mxFontCache.reset();
+ // font list deleted by OutputDevice dtor
+ }
+
+ // Add printer from the list
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( mpPrev )
+ mpPrev->mpNext = mpNext;
+ else
+ pSVData->maGDIData.mpFirstPrinter = mpNext;
+ if ( mpNext )
+ mpNext->mpPrev = mpPrev;
+
+ mpPrev.clear();
+ mpNext.clear();
+ OutputDevice::dispose();
+}
+
+Size Printer::GetButtonBorderSize()
+{
+ Size aBrdSize(LogicToPixel(Size(20, 20), MapMode(MapUnit::Map100thMM)));
+
+ if (!aBrdSize.Width())
+ aBrdSize.setWidth(1);
+
+ if (!aBrdSize.Height())
+ aBrdSize.setHeight(1);
+
+ return aBrdSize;
+}
+
+sal_uInt32 Printer::GetCapabilities( PrinterCapType nType ) const
+{
+ if ( IsDisplayPrinter() )
+ return 0;
+
+ if( mpInfoPrinter )
+ return mpInfoPrinter->GetCapabilities( &maJobSetup.ImplGetConstData(), nType );
+ else
+ return 0;
+}
+
+bool Printer::HasSupport( PrinterSupport eFeature ) const
+{
+ switch ( eFeature )
+ {
+ case PrinterSupport::SetOrientation:
+ return GetCapabilities( PrinterCapType::SetOrientation ) != 0;
+ case PrinterSupport::SetPaperSize:
+ return GetCapabilities( PrinterCapType::SetPaperSize ) != 0;
+ case PrinterSupport::SetPaper:
+ return GetCapabilities( PrinterCapType::SetPaper ) != 0;
+ case PrinterSupport::CollateCopy:
+ return (GetCapabilities( PrinterCapType::CollateCopies ) != 0);
+ case PrinterSupport::SetupDialog:
+ return GetCapabilities( PrinterCapType::SupportDialog ) != 0;
+ }
+
+ return true;
+}
+
+bool Printer::SetJobSetup( const JobSetup& rSetup )
+{
+ if ( IsDisplayPrinter() || mbInPrintPage )
+ return false;
+
+ JobSetup aJobSetup = rSetup;
+
+ ReleaseGraphics();
+ if ( mpInfoPrinter->SetPrinterData( &aJobSetup.ImplGetData() ) )
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ return true;
+ }
+
+ return false;
+}
+
+bool Printer::Setup(weld::Window* pWindow, PrinterSetupMode eMode)
+{
+ if ( IsDisplayPrinter() )
+ return false;
+
+ if ( IsJobActive() || IsPrinting() )
+ return false;
+
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+ rData.SetPrinterSetupMode( eMode );
+ // TODO: orig page size
+
+ if (!pWindow)
+ {
+ vcl::Window* pDefWin = ImplGetDefaultWindow();
+ pWindow = pDefWin ? pDefWin->GetFrameWeld() : nullptr;
+ }
+ if( !pWindow )
+ return false;
+
+ ReleaseGraphics();
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mnModalMode++;
+ nImplSysDialog++;
+ bool bSetup = mpInfoPrinter->Setup(pWindow, &rData);
+ pSVData->maAppData.mnModalMode--;
+ nImplSysDialog--;
+ if ( bSetup )
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ return true;
+ }
+ return false;
+}
+
+bool Printer::SetPrinterProps( const Printer* pPrinter )
+{
+ if ( IsJobActive() || IsPrinting() )
+ return false;
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ mbDefPrinter = pPrinter->mbDefPrinter;
+ maPrintFile = pPrinter->maPrintFile;
+ mbPrintFile = pPrinter->mbPrintFile;
+ mnCopyCount = pPrinter->mnCopyCount;
+ mbCollateCopy = pPrinter->mbCollateCopy;
+ mnPageQueueSize = pPrinter->mnPageQueueSize;
+ *mpPrinterOptions = *pPrinter->mpPrinterOptions;
+
+ if ( pPrinter->IsDisplayPrinter() )
+ {
+ // Destroy old printer
+ if ( !IsDisplayPrinter() )
+ {
+ ReleaseGraphics();
+ pSVData->mpDefInst->DestroyInfoPrinter( mpInfoPrinter );
+ mpFontInstance.clear();
+ mpFontFaceCollection.reset();
+ // clean up font list
+ mxFontCache.reset();
+ mxFontCollection.reset();
+
+ mbInitFont = true;
+ mbNewFont = true;
+ mpInfoPrinter = nullptr;
+ }
+
+ // Construct new printer
+ ImplInitDisplay();
+ return true;
+ }
+
+ // Destroy old printer?
+ if ( GetName() != pPrinter->GetName() )
+ {
+ ReleaseGraphics();
+ if ( mpDisplayDev )
+ {
+ mpDisplayDev.disposeAndClear();
+ }
+ else
+ {
+ pSVData->mpDefInst->DestroyInfoPrinter( mpInfoPrinter );
+
+ mpFontInstance.clear();
+ mpFontFaceCollection.reset();
+ mxFontCache.reset();
+ mxFontCollection.reset();
+ mbInitFont = true;
+ mbNewFont = true;
+ mpInfoPrinter = nullptr;
+ }
+
+ // Construct new printer
+ OUString aDriver = pPrinter->GetDriverName();
+ SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( pPrinter->GetName(), &aDriver );
+ if ( pInfo )
+ {
+ ImplInit( pInfo );
+ SetJobSetup( pPrinter->GetJobSetup() );
+ }
+ else
+ ImplInitDisplay();
+ }
+ else
+ SetJobSetup( pPrinter->GetJobSetup() );
+
+ return false;
+}
+
+bool Printer::SetOrientation( Orientation eOrientation )
+{
+ if ( mbInPrintPage )
+ return false;
+
+ if ( maJobSetup.ImplGetConstData().GetOrientation() != eOrientation )
+ {
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+
+ rData.SetOrientation(eOrientation);
+
+ if ( IsDisplayPrinter() )
+ {
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ return true;
+ }
+
+ ReleaseGraphics();
+ if ( mpInfoPrinter->SetData( JobSetFlags::ORIENTATION, &rData ) )
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ return true;
+ }
+ else
+ return false;
+ }
+
+ return true;
+}
+
+Orientation Printer::GetOrientation() const
+{
+ return maJobSetup.ImplGetConstData().GetOrientation();
+}
+
+bool Printer::SetPaperBin( sal_uInt16 nPaperBin )
+{
+ if ( mbInPrintPage )
+ return false;
+
+ if ( maJobSetup.ImplGetConstData().GetPaperBin() != nPaperBin &&
+ nPaperBin < GetPaperBinCount() )
+ {
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+ rData.SetPaperBin(nPaperBin);
+
+ if ( IsDisplayPrinter() )
+ {
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ return true;
+ }
+
+ ReleaseGraphics();
+ if ( mpInfoPrinter->SetData( JobSetFlags::PAPERBIN, &rData ) )
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ return true;
+ }
+ else
+ return false;
+ }
+
+ return true;
+}
+
+sal_uInt16 Printer::GetPaperBin() const
+{
+ return maJobSetup.ImplGetConstData().GetPaperBin();
+}
+
+bool Printer::GetPrinterSettingsPreferred() const
+{
+ return maJobSetup.ImplGetConstData().GetPapersizeFromSetup();
+}
+
+// dear loplugins, DO NOT REMOVE this code
+// it will be used in follow-up commits
+void Printer::SetPrinterSettingsPreferred( bool bPaperSizeFromSetup)
+{
+ if ( maJobSetup.ImplGetConstData().GetPapersizeFromSetup() != bPaperSizeFromSetup )
+ {
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+ rData.SetPapersizeFromSetup(bPaperSizeFromSetup);
+
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ }
+}
+
+// Map user paper format to an available printer paper format
+void Printer::ImplFindPaperFormatForUserSize( JobSetup& aJobSetup )
+{
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+
+ // The angle that a landscape page will be turned counterclockwise wrt to portrait.
+ int nLandscapeAngle = mpInfoPrinter ? mpInfoPrinter->GetLandscapeAngle( &maJobSetup.ImplGetConstData() ) : 900;
+ int nPaperCount = GetPaperInfoCount();
+ PaperInfo aInfo(rData.GetPaperWidth(), rData.GetPaperHeight());
+
+ // Compare all paper formats and get the appropriate one
+ for ( int i = 0; i < nPaperCount; i++ )
+ {
+ const PaperInfo& rPaperInfo = GetPaperInfo( i );
+
+ if ( aInfo.sloppyEqual(rPaperInfo) )
+ {
+ rData.SetPaperFormat(
+ ImplGetPaperFormat( rPaperInfo.getWidth(),
+ rPaperInfo.getHeight() ));
+ rData.SetOrientation( Orientation::Portrait );
+ return;
+ }
+ }
+
+ // If the printer supports landscape orientation, check paper sizes again
+ // with landscape orientation. This is necessary as a printer driver provides
+ // all paper sizes with portrait orientation only!!
+ if ( !(rData.GetPaperFormat() == PAPER_USER &&
+ nLandscapeAngle != 0 &&
+ HasSupport( PrinterSupport::SetOrientation )))
+ return;
+
+ const tools::Long nRotatedWidth = rData.GetPaperHeight();
+ const tools::Long nRotatedHeight = rData.GetPaperWidth();
+ PaperInfo aRotatedInfo(nRotatedWidth, nRotatedHeight);
+
+ for ( int i = 0; i < nPaperCount; i++ )
+ {
+ const PaperInfo& rPaperInfo = GetPaperInfo( i );
+
+ if ( aRotatedInfo.sloppyEqual( rPaperInfo ) )
+ {
+ rData.SetPaperFormat(
+ ImplGetPaperFormat( rPaperInfo.getWidth(),
+ rPaperInfo.getHeight() ));
+ rData.SetOrientation( Orientation::Landscape );
+ return;
+ }
+ }
+}
+
+void Printer::SetPaper( Paper ePaper )
+{
+ if ( mbInPrintPage )
+ return;
+
+ if ( maJobSetup.ImplGetConstData().GetPaperFormat() == ePaper )
+ return;
+
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+
+ rData.SetPaperFormat( ePaper );
+ if ( ePaper != PAPER_USER )
+ {
+ PaperInfo aInfo(ePaper);
+ rData.SetPaperWidth( aInfo.getWidth() );
+ rData.SetPaperHeight( aInfo.getHeight() );
+ }
+
+ if ( IsDisplayPrinter() )
+ {
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ return;
+ }
+
+ ReleaseGraphics();
+ if ( ePaper == PAPER_USER )
+ ImplFindPaperFormatForUserSize( aJobSetup );
+ if ( mpInfoPrinter->SetData( JobSetFlags::PAPERSIZE | JobSetFlags::ORIENTATION, &rData ))
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ }
+}
+
+bool Printer::SetPaperSizeUser( const Size& rSize )
+{
+ if ( mbInPrintPage )
+ return false;
+
+ const Size aPixSize = LogicToPixel( rSize );
+ const Size aPageSize = PixelToLogic(aPixSize, MapMode(MapUnit::Map100thMM));
+ bool bNeedToChange(maJobSetup.ImplGetConstData().GetPaperWidth() != aPageSize.Width() ||
+ maJobSetup.ImplGetConstData().GetPaperHeight() != aPageSize.Height());
+
+ if(!bNeedToChange)
+ {
+ // #i122984# only need to change when Paper is different from PAPER_USER and
+ // the mapped Paper which will created below in the call to ImplFindPaperFormatForUserSize
+ // and will replace maJobSetup.ImplGetConstData()->GetPaperFormat(). This leads to
+ // unnecessary JobSetups, e.g. when printing a multi-page fax, but also with
+ // normal print
+ const Paper aPaper = ImplGetPaperFormat(aPageSize.Width(), aPageSize.Height());
+
+ bNeedToChange = maJobSetup.ImplGetConstData().GetPaperFormat() != PAPER_USER &&
+ maJobSetup.ImplGetConstData().GetPaperFormat() != aPaper;
+ }
+
+ if(bNeedToChange)
+ {
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+ rData.SetPaperFormat( PAPER_USER );
+ rData.SetPaperWidth( aPageSize.Width() );
+ rData.SetPaperHeight( aPageSize.Height() );
+
+ if ( IsDisplayPrinter() )
+ {
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ return true;
+ }
+
+ ReleaseGraphics();
+ ImplFindPaperFormatForUserSize( aJobSetup );
+
+ // Changing the paper size can also change the orientation!
+ if ( mpInfoPrinter->SetData( JobSetFlags::PAPERSIZE | JobSetFlags::ORIENTATION, &rData ))
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ return true;
+ }
+ else
+ return false;
+ }
+
+ return true;
+}
+
+int Printer::GetPaperInfoCount() const
+{
+ if( ! mpInfoPrinter )
+ return 0;
+ if( ! mpInfoPrinter->m_bPapersInit )
+ mpInfoPrinter->InitPaperFormats( &maJobSetup.ImplGetConstData() );
+ return mpInfoPrinter->m_aPaperFormats.size();
+}
+
+OUString Printer::GetPaperName( Paper ePaper )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->maPaperNames.empty() )
+ {
+ // This array must (probably) match exactly the enum Paper in <i18nutil/paper.hxx>
+ static const int PaperIndex[] =
+ {
+ PAPER_A0, PAPER_A1, PAPER_A2, PAPER_A3, PAPER_A4, PAPER_A5, PAPER_B4_ISO, PAPER_B5_ISO,
+ PAPER_LETTER, PAPER_LEGAL, PAPER_TABLOID, PAPER_USER, PAPER_B6_ISO, PAPER_ENV_C4, PAPER_ENV_C5,
+ PAPER_ENV_C6, PAPER_ENV_C65, PAPER_ENV_DL, PAPER_SLIDE_DIA, PAPER_SCREEN_4_3, PAPER_C, PAPER_D,
+ PAPER_E, PAPER_EXECUTIVE, PAPER_FANFOLD_LEGAL_DE, PAPER_ENV_MONARCH, PAPER_ENV_PERSONAL, PAPER_ENV_9,
+ PAPER_ENV_10, PAPER_ENV_11, PAPER_ENV_12, PAPER_KAI16, PAPER_KAI32, PAPER_KAI32BIG, PAPER_B4_JIS,
+ PAPER_B5_JIS, PAPER_B6_JIS, PAPER_LEDGER, PAPER_STATEMENT, PAPER_QUARTO, PAPER_10x14, PAPER_ENV_14,
+ PAPER_ENV_C3, PAPER_ENV_ITALY, PAPER_FANFOLD_US, PAPER_FANFOLD_DE, PAPER_POSTCARD_JP, PAPER_9x11,
+ PAPER_10x11, PAPER_15x11, PAPER_ENV_INVITE, PAPER_A_PLUS, PAPER_B_PLUS, PAPER_LETTER_PLUS, PAPER_A4_PLUS,
+ PAPER_DOUBLEPOSTCARD_JP, PAPER_A6, PAPER_12x11, PAPER_A7, PAPER_A8, PAPER_A9, PAPER_A10, PAPER_B0_ISO,
+ PAPER_B1_ISO, PAPER_B2_ISO, PAPER_B3_ISO, PAPER_B7_ISO, PAPER_B8_ISO, PAPER_B9_ISO, PAPER_B10_ISO,
+ PAPER_ENV_C2, PAPER_ENV_C7, PAPER_ENV_C8, PAPER_ARCHA, PAPER_ARCHB, PAPER_ARCHC, PAPER_ARCHD,
+ PAPER_ARCHE, PAPER_SCREEN_16_9, PAPER_SCREEN_16_10, PAPER_16K_195x270, PAPER_16K_197x273,
+ PAPER_WIDESCREEN, PAPER_ONSCREENSHOW_4_3, PAPER_ONSCREENSHOW_16_9, PAPER_ONSCREENSHOW_16_10
+ };
+ static_assert(SAL_N_ELEMENTS(PaperIndex) == SAL_N_ELEMENTS(RID_STR_PAPERNAMES), "localized paper name count wrong");
+ for (size_t i = 0; i < SAL_N_ELEMENTS(PaperIndex); ++i)
+ pSVData->maPaperNames[PaperIndex[i]] = VclResId(RID_STR_PAPERNAMES[i]);
+ }
+
+ std::unordered_map<int,OUString>::const_iterator it = pSVData->maPaperNames.find( static_cast<int>(ePaper) );
+ return (it != pSVData->maPaperNames.end()) ? it->second : OUString();
+}
+
+const PaperInfo& Printer::GetPaperInfo( int nPaper ) const
+{
+ if( ! mpInfoPrinter )
+ return ImplGetEmptyPaper();
+ if( ! mpInfoPrinter->m_bPapersInit )
+ mpInfoPrinter->InitPaperFormats( &maJobSetup.ImplGetConstData() );
+ if( mpInfoPrinter->m_aPaperFormats.empty() || nPaper < 0 || o3tl::make_unsigned(nPaper) >= mpInfoPrinter->m_aPaperFormats.size() )
+ return ImplGetEmptyPaper();
+ return mpInfoPrinter->m_aPaperFormats[nPaper];
+}
+
+Size Printer::GetPaperSize( int nPaper ) const
+{
+ PaperInfo aInfo = GetPaperInfo( nPaper );
+ return PixelToLogic( Size( aInfo.getWidth(), aInfo.getHeight() ) );
+}
+
+void Printer::SetDuplexMode( DuplexMode eDuplex )
+{
+ if ( mbInPrintPage )
+ return;
+
+ if ( maJobSetup.ImplGetConstData().GetDuplexMode() == eDuplex )
+ return;
+
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+
+ rData.SetDuplexMode( eDuplex );
+
+ if ( IsDisplayPrinter() )
+ {
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ return;
+ }
+
+ ReleaseGraphics();
+ if ( mpInfoPrinter->SetData( JobSetFlags::DUPLEXMODE, &rData ) )
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ }
+}
+
+DuplexMode Printer::GetDuplexMode() const
+{
+ return maJobSetup.ImplGetConstData().GetDuplexMode();
+}
+
+Paper Printer::GetPaper() const
+{
+ return maJobSetup.ImplGetConstData().GetPaperFormat();
+}
+
+Size Printer::GetSizeOfPaper() const
+{
+ return Size(maJobSetup.ImplGetConstData().GetPaperWidth(), maJobSetup.ImplGetConstData().GetPaperHeight());
+}
+
+sal_uInt16 Printer::GetPaperBinCount() const
+{
+ if ( IsDisplayPrinter() )
+ return 0;
+
+ return mpInfoPrinter->GetPaperBinCount( &maJobSetup.ImplGetConstData() );
+}
+
+OUString Printer::GetPaperBinName( sal_uInt16 nPaperBin ) const
+{
+ if ( IsDisplayPrinter() )
+ return OUString();
+
+ if ( nPaperBin < GetPaperBinCount() )
+ return mpInfoPrinter->GetPaperBinName( &maJobSetup.ImplGetConstData(), nPaperBin );
+ else
+ return OUString();
+}
+
+void Printer::SetCopyCount( sal_uInt16 nCopy, bool bCollate )
+{
+ mnCopyCount = nCopy;
+ mbCollateCopy = bCollate;
+}
+
+ErrCode Printer::ImplSalPrinterErrorCodeToVCL( SalPrinterError nError )
+{
+ ErrCode nVCLError;
+ switch ( nError )
+ {
+ case SalPrinterError::NONE:
+ nVCLError = ERRCODE_NONE;
+ break;
+ case SalPrinterError::Abort:
+ nVCLError = PRINTER_ABORT;
+ break;
+ default:
+ nVCLError = PRINTER_GENERALERROR;
+ break;
+ }
+
+ return nVCLError;
+}
+
+void Printer::EndJob()
+{
+ if ( !IsJobActive() )
+ return;
+
+ SAL_WARN_IF( mbInPrintPage, "vcl.gdi", "Printer::EndJob() - StartPage() without EndPage() called" );
+
+ mbJobActive = false;
+
+ if ( mpPrinter )
+ {
+ ReleaseGraphics();
+
+ mbPrinting = false;
+
+ mbDevOutput = false;
+ mpPrinter->EndJob();
+ mpPrinter.reset();
+ }
+}
+
+void Printer::ImplStartPage()
+{
+ if ( !IsJobActive() )
+ return;
+
+ if ( !mpPrinter )
+ return;
+
+ SalGraphics* pGraphics = mpPrinter->StartPage( &maJobSetup.ImplGetData(),
+ mbNewJobSetup );
+ if ( pGraphics )
+ {
+ ReleaseGraphics();
+ mpJobGraphics = pGraphics;
+ }
+ mbDevOutput = true;
+
+ // PrintJob not aborted ???
+ if ( IsJobActive() )
+ mbInPrintPage = true;
+}
+
+void Printer::ImplEndPage()
+{
+ if ( !IsJobActive() )
+ return;
+
+ mbInPrintPage = false;
+
+ if ( mpPrinter )
+ {
+ ReleaseGraphics();
+ mpPrinter->EndPage();
+ mbDevOutput = false;
+
+ mpJobGraphics = nullptr;
+ mbNewJobSetup = false;
+ }
+}
+
+void Printer::updatePrinters()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ ImplPrnQueueList* pPrnList = pSVData->maGDIData.mpPrinterQueueList.get();
+
+ if ( !pPrnList )
+ return;
+
+ std::unique_ptr<ImplPrnQueueList> pNewList(new ImplPrnQueueList);
+ pSVData->mpDefInst->GetPrinterQueueInfo( pNewList.get() );
+
+ bool bChanged = pPrnList->m_aQueueInfos.size() != pNewList->m_aQueueInfos.size();
+ for( decltype(pPrnList->m_aQueueInfos)::size_type i = 0; ! bChanged && i < pPrnList->m_aQueueInfos.size(); i++ )
+ {
+ ImplPrnQueueData& rInfo = pPrnList->m_aQueueInfos[i];
+ ImplPrnQueueData& rNewInfo = pNewList->m_aQueueInfos[i];
+ if( ! rInfo.mpSalQueueInfo || ! rNewInfo.mpSalQueueInfo || // sanity check
+ rInfo.mpSalQueueInfo->maPrinterName != rNewInfo.mpSalQueueInfo->maPrinterName )
+ {
+ bChanged = true;
+ }
+ }
+ if( !bChanged )
+ return;
+
+ ImplDeletePrnQueueList();
+ pSVData->maGDIData.mpPrinterQueueList = std::move(pNewList);
+
+ Application* pApp = GetpApp();
+ if( pApp )
+ {
+ DataChangedEvent aDCEvt( DataChangedEventType::PRINTER );
+ Application::ImplCallEventListenersApplicationDataChanged(&aDCEvt);
+ Application::NotifyAllWindows( aDCEvt );
+ }
+}
+
+bool Printer::UsePolyPolygonForComplexGradient()
+{
+ return true;
+}
+
+void Printer::ClipAndDrawGradientMetafile ( const Gradient &rGradient, const tools::PolyPolygon &rPolyPoly )
+{
+ const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
+
+ Push( vcl::PushFlags::CLIPREGION );
+ IntersectClipRegion(vcl::Region(rPolyPoly));
+ DrawGradient( aBoundRect, rGradient );
+ Pop();
+}
+
+void Printer::SetFontOrientation( LogicalFontInstance* const pFontEntry ) const
+{
+ pFontEntry->mnOrientation = pFontEntry->mxFontMetric->GetOrientation();
+}
+
+vcl::Region Printer::ClipToDeviceBounds(vcl::Region aRegion) const
+{
+ return aRegion;
+}
+
+Bitmap Printer::GetBitmap( const Point& rSrcPt, const Size& rSize ) const
+{
+ SAL_WARN("vcl.gdi", "GetBitmap(): This should never be called on by a Printer instance");
+
+ return OutputDevice::GetBitmap( rSrcPt, rSize );
+}
+
+css::awt::DeviceInfo Printer::GetDeviceInfo() const
+{
+ Size aDevSz = GetPaperSizePixel();
+ css::awt::DeviceInfo aInfo = GetCommonDeviceInfo(aDevSz);
+ Size aOutSz = GetOutputSizePixel();
+ Point aOffset = GetPageOffset();
+ aInfo.LeftInset = aOffset.X();
+ aInfo.TopInset = aOffset.Y();
+ aInfo.RightInset = aDevSz.Width() - aOutSz.Width() - aOffset.X();
+ aInfo.BottomInset = aDevSz.Height() - aOutSz.Height() - aOffset.Y();
+ aInfo.Capabilities = 0;
+
+ return aInfo;
+}
+
+void Printer::SetWaveLineColors(Color const& rColor, tools::Long)
+{
+ if (mbLineColor || mbInitLineColor)
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+
+ mpGraphics->SetFillColor(rColor);
+ mbInitFillColor = true;
+}
+
+Size Printer::GetWaveLineSize(tools::Long nLineWidth) const
+{
+ // FIXME - do we have a bug here? If the linewidth is 0, then we will return
+ // Size(0, 0) - is this correct?
+ return Size(nLineWidth, ((nLineWidth*mnDPIX)+(mnDPIY/2))/mnDPIY);
+}
+
+void Printer::SetSystemTextColor(SystemTextColorFlags, bool)
+{
+ SetTextColor(COL_BLACK);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/gdi/print2.cxx b/vcl/source/gdi/print2.cxx
new file mode 100644
index 000000000..85b39c683
--- /dev/null
+++ b/vcl/source/gdi/print2.cxx
@@ -0,0 +1,70 @@
+/* -*- 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/log.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/print.hxx>
+#include <vcl/printer/Options.hxx>
+
+#include <utility>
+#include <vector>
+
+void Printer::DrawGradientEx( OutputDevice* pOut, const tools::Rectangle& rRect, const Gradient& rGradient )
+{
+ const vcl::printer::Options& rPrinterOptions = GetPrinterOptions();
+
+ if( rPrinterOptions.IsReduceGradients() )
+ {
+ if( vcl::printer::GradientMode::Stripes == rPrinterOptions.GetReducedGradientMode() )
+ {
+ if( !rGradient.GetSteps() || ( rGradient.GetSteps() > rPrinterOptions.GetReducedGradientStepCount() ) )
+ {
+ Gradient aNewGradient( rGradient );
+
+ aNewGradient.SetSteps( rPrinterOptions.GetReducedGradientStepCount() );
+ pOut->DrawGradient( rRect, aNewGradient );
+ }
+ else
+ pOut->DrawGradient( rRect, rGradient );
+ }
+ else
+ {
+ const Color& rStartColor = rGradient.GetStartColor();
+ const Color& rEndColor = rGradient.GetEndColor();
+ const tools::Long nR = ( ( static_cast<tools::Long>(rStartColor.GetRed()) * rGradient.GetStartIntensity() ) / 100 +
+ ( static_cast<tools::Long>(rEndColor.GetRed()) * rGradient.GetEndIntensity() ) / 100 ) >> 1;
+ const tools::Long nG = ( ( static_cast<tools::Long>(rStartColor.GetGreen()) * rGradient.GetStartIntensity() ) / 100 +
+ ( static_cast<tools::Long>(rEndColor.GetGreen()) * rGradient.GetEndIntensity() ) / 100 ) >> 1;
+ const tools::Long nB = ( ( static_cast<tools::Long>(rStartColor.GetBlue()) * rGradient.GetStartIntensity() ) / 100 +
+ ( static_cast<tools::Long>(rEndColor.GetBlue()) * rGradient.GetEndIntensity() ) / 100 ) >> 1;
+ const Color aColor( static_cast<sal_uInt8>(nR), static_cast<sal_uInt8>(nG), static_cast<sal_uInt8>(nB) );
+
+ pOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
+ pOut->SetLineColor( aColor );
+ pOut->SetFillColor( aColor );
+ pOut->DrawRect( rRect );
+ pOut->Pop();
+ }
+ }
+ else
+ pOut->DrawGradient( rRect, rGradient );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/print3.cxx b/vcl/source/gdi/print3.cxx
new file mode 100644
index 000000000..c02023dd1
--- /dev/null
+++ b/vcl/source/gdi/print3.cxx
@@ -0,0 +1,2146 @@
+/* -*- 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/types.h>
+#include <sal/log.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/sequence.hxx>
+#include <o3tl/safeint.hxx>
+#include <tools/diagnose_ex.h>
+#include <tools/debug.hxx>
+#include <tools/urlobj.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/print.hxx>
+#include <vcl/printer/Options.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+
+#include <configsettings.hxx>
+#include <printdlg.hxx>
+#include <salinst.hxx>
+#include <salprn.hxx>
+#include <strings.hrc>
+#include <svdata.hxx>
+
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/ui/dialogs/FilePicker.hpp>
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/view/DuplexMode.hpp>
+
+#include <unordered_map>
+#include <unordered_set>
+
+using namespace vcl;
+
+namespace {
+
+class ImplPageCache
+{
+ struct CacheEntry
+ {
+ GDIMetaFile aPage;
+ PrinterController::PageSize aSize;
+ };
+
+ std::vector< CacheEntry > maPages;
+ std::vector< sal_Int32 > maPageNumbers;
+ std::vector< sal_Int32 > maCacheRanking;
+
+ static const sal_Int32 nCacheSize = 6;
+
+ void updateRanking( sal_Int32 nLastHit )
+ {
+ if( maCacheRanking[0] != nLastHit )
+ {
+ for( sal_Int32 i = nCacheSize-1; i > 0; i-- )
+ maCacheRanking[i] = maCacheRanking[i-1];
+ maCacheRanking[0] = nLastHit;
+ }
+ }
+
+public:
+ ImplPageCache()
+ : maPages( nCacheSize )
+ , maPageNumbers( nCacheSize, -1 )
+ , maCacheRanking( nCacheSize )
+ {
+ for( sal_Int32 i = 0; i < nCacheSize; i++ )
+ maCacheRanking[i] = nCacheSize - i - 1;
+ }
+
+ // caution: does not ensure uniqueness
+ void insert( sal_Int32 i_nPageNo, const GDIMetaFile& i_rPage, const PrinterController::PageSize& i_rSize )
+ {
+ sal_Int32 nReplacePage = maCacheRanking.back();
+ maPages[ nReplacePage ].aPage = i_rPage;
+ maPages[ nReplacePage ].aSize = i_rSize;
+ maPageNumbers[ nReplacePage ] = i_nPageNo;
+ // cache insertion means in our case, the page was just queried
+ // so update the ranking
+ updateRanking( nReplacePage );
+ }
+
+ // caution: bad algorithm; should there ever be reason to increase the cache size beyond 6
+ // this needs to be urgently rewritten. However do NOT increase the cache size lightly,
+ // whole pages can be rather memory intensive
+ bool get( sal_Int32 i_nPageNo, GDIMetaFile& o_rPageFile, PrinterController::PageSize& o_rSize )
+ {
+ for( sal_Int32 i = 0; i < nCacheSize; ++i )
+ {
+ if( maPageNumbers[i] == i_nPageNo )
+ {
+ updateRanking( i );
+ o_rPageFile = maPages[i].aPage;
+ o_rSize = maPages[i].aSize;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void invalidate()
+ {
+ for( sal_Int32 i = 0; i < nCacheSize; ++i )
+ {
+ maPageNumbers[i] = -1;
+ maPages[i].aPage.Clear();
+ maCacheRanking[i] = nCacheSize - i - 1;
+ }
+ }
+};
+
+}
+
+class vcl::ImplPrinterControllerData
+{
+public:
+ struct ControlDependency
+ {
+ OUString maDependsOnName;
+ sal_Int32 mnDependsOnEntry;
+
+ ControlDependency() : mnDependsOnEntry( -1 ) {}
+ };
+
+ typedef std::unordered_map< OUString, size_t > PropertyToIndexMap;
+ typedef std::unordered_map< OUString, ControlDependency > ControlDependencyMap;
+ typedef std::unordered_map< OUString, css::uno::Sequence< sal_Bool > > ChoiceDisableMap;
+
+ VclPtr< Printer > mxPrinter;
+ weld::Window* mpWindow;
+ css::uno::Sequence< css::beans::PropertyValue > maUIOptions;
+ std::vector< css::beans::PropertyValue > maUIProperties;
+ std::vector< bool > maUIPropertyEnabled;
+ PropertyToIndexMap maPropertyToIndex;
+ ControlDependencyMap maControlDependencies;
+ ChoiceDisableMap maChoiceDisableMap;
+ bool mbFirstPage;
+ bool mbLastPage;
+ bool mbReversePageOrder;
+ bool mbPapersizeFromSetup;
+ bool mbPapersizeFromUser;
+ bool mbOrientationFromUser;
+ bool mbPrinterModified;
+ css::view::PrintableState meJobState;
+
+ vcl::PrinterController::MultiPageSetup maMultiPage;
+
+ std::shared_ptr<vcl::PrintProgressDialog> mxProgress;
+
+ ImplPageCache maPageCache;
+
+ // set by user through printer properties subdialog of printer settings dialog
+ Size maDefaultPageSize;
+ // set by user through print dialog
+ Size maUserPageSize;
+ // set by user through print dialog
+ Orientation meUserOrientation;
+ // set by user through printer properties subdialog of printer settings dialog
+ sal_Int32 mnDefaultPaperBin;
+ // Set by user through printer properties subdialog of print dialog.
+ // Overrides application-set tray for a page.
+ sal_Int32 mnFixedPaperBin;
+
+ // N.B. Apparently we have three levels of paper tray settings
+ // (latter overrides former):
+ // 1. default tray
+ // 2. tray set for a concrete page by an application, e.g., writer
+ // allows setting a printer tray (for the default printer) for a
+ // page style. This setting can be overridden by user by selecting
+ // "Use only paper tray from printer preferences" on the Options
+ // page in the print dialog, in which case the default tray is
+ // used for all pages.
+ // 3. tray set in printer properties the printer dialog
+ // I'm not quite sure why 1. and 3. are distinct, but the commit
+ // history suggests this is intentional...
+
+ ImplPrinterControllerData() :
+ mpWindow( nullptr ),
+ mbFirstPage( true ),
+ mbLastPage( false ),
+ mbReversePageOrder( false ),
+ mbPapersizeFromSetup( false ),
+ mbPapersizeFromUser( false ),
+ mbOrientationFromUser( false ),
+ mbPrinterModified( false ),
+ meJobState( css::view::PrintableState_JOB_STARTED ),
+ meUserOrientation( Orientation::Portrait ),
+ mnDefaultPaperBin( -1 ),
+ mnFixedPaperBin( -1 )
+ {}
+
+ ~ImplPrinterControllerData()
+ {
+ if (mxProgress)
+ {
+ mxProgress->response(RET_CANCEL);
+ mxProgress.reset();
+ }
+ }
+
+ Size getRealPaperSize( const Size& i_rPageSize, bool bNoNUP ) const
+ {
+ Size size;
+ if ( mbPapersizeFromUser )
+ size = maUserPageSize;
+ else if( mbPapersizeFromSetup )
+ size = maDefaultPageSize;
+ else if( maMultiPage.nRows * maMultiPage.nColumns > 1 && ! bNoNUP )
+ size = maMultiPage.aPaperSize;
+ else
+ size = i_rPageSize;
+ if(mbOrientationFromUser)
+ {
+ if ( (meUserOrientation == Orientation::Portrait && size.Width() > size.Height()) ||
+ (meUserOrientation == Orientation::Landscape && size.Width() < size.Height()) )
+ {
+ // coverity[swapped_arguments : FALSE] - this is in the correct order
+ size = Size( size.Height(), size.Width() );
+ }
+ }
+ return size;
+ }
+ PrinterController::PageSize modifyJobSetup( const css::uno::Sequence< css::beans::PropertyValue >& i_rProps );
+ void resetPaperToLastConfigured();
+};
+
+PrinterController::PrinterController(const VclPtr<Printer>& i_xPrinter, weld::Window* i_pWindow)
+ : mpImplData( new ImplPrinterControllerData )
+{
+ mpImplData->mxPrinter = i_xPrinter;
+ mpImplData->mpWindow = i_pWindow;
+}
+
+static OUString queryFile( Printer const * pPrinter )
+{
+ OUString aResult;
+
+ css::uno::Reference< css::uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
+ css::uno::Reference< css::ui::dialogs::XFilePicker3 > xFilePicker = css::ui::dialogs::FilePicker::createWithMode(xContext, css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION);
+
+ try
+ {
+#ifdef UNX
+ // add PostScript and PDF
+ bool bPS = true, bPDF = true;
+ if( pPrinter )
+ {
+ if( pPrinter->GetCapabilities( PrinterCapType::PDF ) )
+ bPS = false;
+ else
+ bPDF = false;
+ }
+ if( bPS )
+ xFilePicker->appendFilter( "PostScript", "*.ps" );
+ if( bPDF )
+ xFilePicker->appendFilter( "Portable Document Format", "*.pdf" );
+#elif defined _WIN32
+ (void)pPrinter;
+ xFilePicker->appendFilter( "*.PRN", "*.prn" );
+#endif
+ // add arbitrary files
+ xFilePicker->appendFilter(VclResId(SV_STDTEXT_ALLFILETYPES), "*.*");
+ }
+ catch (const css::lang::IllegalArgumentException&)
+ {
+ TOOLS_WARN_EXCEPTION( "vcl.gdi", "caught IllegalArgumentException when registering filter" );
+ }
+
+ if( xFilePicker->execute() == css::ui::dialogs::ExecutableDialogResults::OK )
+ {
+ css::uno::Sequence< OUString > aPathSeq( xFilePicker->getSelectedFiles() );
+ INetURLObject aObj( aPathSeq[0] );
+ aResult = aObj.PathToFileName();
+ }
+ return aResult;
+}
+
+namespace {
+
+struct PrintJobAsync
+{
+ std::shared_ptr<PrinterController> mxController;
+ JobSetup maInitSetup;
+
+ PrintJobAsync(const std::shared_ptr<PrinterController>& i_xController,
+ const JobSetup& i_rInitSetup)
+ : mxController( i_xController ), maInitSetup( i_rInitSetup )
+ {}
+
+ DECL_LINK( ExecJob, void*, void );
+};
+
+}
+
+IMPL_LINK_NOARG(PrintJobAsync, ExecJob, void*, void)
+{
+ Printer::ImplPrintJob(mxController, maInitSetup);
+
+ // clean up, do not access members after this
+ delete this;
+}
+
+void Printer::PrintJob(const std::shared_ptr<PrinterController>& i_xController,
+ const JobSetup& i_rInitSetup)
+{
+ bool bSynchronous = false;
+ css::beans::PropertyValue* pVal = i_xController->getValue( "Wait" );
+ if( pVal )
+ pVal->Value >>= bSynchronous;
+
+ if( bSynchronous )
+ ImplPrintJob(i_xController, i_rInitSetup);
+ else
+ {
+ PrintJobAsync* pAsync = new PrintJobAsync(i_xController, i_rInitSetup);
+ Application::PostUserEvent( LINK( pAsync, PrintJobAsync, ExecJob ) );
+ }
+}
+
+bool Printer::PreparePrintJob(std::shared_ptr<PrinterController> xController,
+ const JobSetup& i_rInitSetup)
+{
+ // check if there is a default printer; if not, show an error box (if appropriate)
+ if( GetDefaultPrinterName().isEmpty() )
+ {
+ if (xController->isShowDialogs())
+ {
+ std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(xController->getWindow(), "vcl/ui/errornoprinterdialog.ui"));
+ std::unique_ptr<weld::MessageDialog> xBox(xBuilder->weld_message_dialog("ErrorNoPrinterDialog"));
+ xBox->run();
+ }
+ xController->setValue( "IsDirect",
+ css::uno::Any( false ) );
+ }
+
+ // setup printer
+
+ // #i114306# changed behavior back from persistence
+ // if no specific printer is already set, create the default printer
+ if (!xController->getPrinter())
+ {
+ OUString aPrinterName( i_rInitSetup.GetPrinterName() );
+ VclPtrInstance<Printer> xPrinter( aPrinterName );
+ xPrinter->SetJobSetup(i_rInitSetup);
+ xController->setPrinter(xPrinter);
+ xController->setPapersizeFromSetup(xPrinter->GetPrinterSettingsPreferred());
+ }
+
+ // reset last page property
+ xController->setLastPage(false);
+
+ // update "PageRange" property inferring from other properties:
+ // case 1: "Pages" set from UNO API ->
+ // setup "Print Selection" and insert "PageRange" attribute
+ // case 2: "All pages" is selected
+ // update "Page range" attribute to have a sensible default,
+ // but leave "All" as selected
+
+ // "Pages" attribute from API is now equivalent to "PageRange"
+ // AND "PrintContent" = 1 except calc where it is "PrintRange" = 1
+ // Argh ! That sure needs cleaning up
+ css::beans::PropertyValue* pContentVal = xController->getValue("PrintRange");
+ if( ! pContentVal )
+ pContentVal = xController->getValue("PrintContent");
+
+ // case 1: UNO API has set "Pages"
+ css::beans::PropertyValue* pPagesVal = xController->getValue("Pages");
+ if( pPagesVal )
+ {
+ OUString aPagesVal;
+ pPagesVal->Value >>= aPagesVal;
+ if( !aPagesVal.isEmpty() )
+ {
+ // "Pages" attribute from API is now equivalent to "PageRange"
+ // AND "PrintContent" = 1 except calc where it is "PrintRange" = 1
+ // Argh ! That sure needs cleaning up
+ if( pContentVal )
+ {
+ pContentVal->Value <<= sal_Int32( 1 );
+ xController->setValue("PageRange", pPagesVal->Value);
+ }
+ }
+ }
+ // case 2: is "All" selected ?
+ else if( pContentVal )
+ {
+ sal_Int32 nContent = -1;
+ if( pContentVal->Value >>= nContent )
+ {
+ if( nContent == 0 )
+ {
+ // do not overwrite PageRange if it is already set
+ css::beans::PropertyValue* pRangeVal = xController->getValue("PageRange");
+ OUString aRange;
+ if( pRangeVal )
+ pRangeVal->Value >>= aRange;
+ if( aRange.isEmpty() )
+ {
+ sal_Int32 nPages = xController->getPageCount();
+ if( nPages > 0 )
+ {
+ OUStringBuffer aBuf( 32 );
+ aBuf.append( "1" );
+ if( nPages > 1 )
+ {
+ aBuf.append( "-" );
+ aBuf.append( nPages );
+ }
+ xController->setValue("PageRange", css::uno::Any(aBuf.makeStringAndClear()));
+ }
+ }
+ }
+ }
+ }
+
+ css::beans::PropertyValue* pReverseVal = xController->getValue("PrintReverse");
+ if( pReverseVal )
+ {
+ bool bReverse = false;
+ pReverseVal->Value >>= bReverse;
+ xController->setReversePrint( bReverse );
+ }
+
+ css::beans::PropertyValue* pPapersizeFromSetupVal = xController->getValue("PapersizeFromSetup");
+ if( pPapersizeFromSetupVal )
+ {
+ bool bPapersizeFromSetup = false;
+ pPapersizeFromSetupVal->Value >>= bPapersizeFromSetup;
+ xController->setPapersizeFromSetup(bPapersizeFromSetup);
+ }
+
+ // setup NUp printing from properties
+ sal_Int32 nRows = xController->getIntProperty("NUpRows", 1);
+ sal_Int32 nCols = xController->getIntProperty("NUpColumns", 1);
+ if( nRows > 1 || nCols > 1 )
+ {
+ PrinterController::MultiPageSetup aMPS;
+ aMPS.nRows = std::max<sal_Int32>(nRows, 1);
+ aMPS.nColumns = std::max<sal_Int32>(nCols, 1);
+ sal_Int32 nValue = xController->getIntProperty("NUpPageMarginLeft", aMPS.nLeftMargin);
+ if( nValue >= 0 )
+ aMPS.nLeftMargin = nValue;
+ nValue = xController->getIntProperty("NUpPageMarginRight", aMPS.nRightMargin);
+ if( nValue >= 0 )
+ aMPS.nRightMargin = nValue;
+ nValue = xController->getIntProperty( "NUpPageMarginTop", aMPS.nTopMargin );
+ if( nValue >= 0 )
+ aMPS.nTopMargin = nValue;
+ nValue = xController->getIntProperty( "NUpPageMarginBottom", aMPS.nBottomMargin );
+ if( nValue >= 0 )
+ aMPS.nBottomMargin = nValue;
+ nValue = xController->getIntProperty( "NUpHorizontalSpacing", aMPS.nHorizontalSpacing );
+ if( nValue >= 0 )
+ aMPS.nHorizontalSpacing = nValue;
+ nValue = xController->getIntProperty( "NUpVerticalSpacing", aMPS.nVerticalSpacing );
+ if( nValue >= 0 )
+ aMPS.nVerticalSpacing = nValue;
+ aMPS.bDrawBorder = xController->getBoolProperty( "NUpDrawBorder", aMPS.bDrawBorder );
+ aMPS.nOrder = static_cast<NupOrderType>(xController->getIntProperty( "NUpSubPageOrder", static_cast<sal_Int32>(aMPS.nOrder) ));
+ aMPS.aPaperSize = xController->getPrinter()->PixelToLogic( xController->getPrinter()->GetPaperSizePixel(), MapMode( MapUnit::Map100thMM ) );
+ css::beans::PropertyValue* pPgSizeVal = xController->getValue( "NUpPaperSize" );
+ css::awt::Size aSizeVal;
+ if( pPgSizeVal && (pPgSizeVal->Value >>= aSizeVal) )
+ {
+ aMPS.aPaperSize.setWidth( aSizeVal.Width );
+ aMPS.aPaperSize.setHeight( aSizeVal.Height );
+ }
+
+ xController->setMultipage( aMPS );
+ }
+
+ // in direct print case check whether there is anything to print.
+ // if not, show an errorbox (if appropriate)
+ if( xController->isShowDialogs() && xController->isDirectPrint() )
+ {
+ if( xController->getFilteredPageCount() == 0 )
+ {
+ std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(xController->getWindow(), "vcl/ui/errornocontentdialog.ui"));
+ std::unique_ptr<weld::MessageDialog> xBox(xBuilder->weld_message_dialog("ErrorNoContentDialog"));
+ xBox->run();
+ return false;
+ }
+ }
+
+ // check if the printer brings up its own dialog
+ // in that case leave the work to that dialog
+ if( ! xController->getPrinter()->GetCapabilities( PrinterCapType::ExternalDialog ) &&
+ ! xController->isDirectPrint() &&
+ xController->isShowDialogs()
+ )
+ {
+ try
+ {
+ PrintDialog aDlg(xController->getWindow(), xController);
+ if (!aDlg.run())
+ {
+ xController->abortJob();
+ return false;
+ }
+ if (aDlg.isPrintToFile())
+ {
+ OUString aFile = queryFile( xController->getPrinter().get() );
+ if( aFile.isEmpty() )
+ {
+ xController->abortJob();
+ return false;
+ }
+ xController->setValue( "LocalFileName",
+ css::uno::Any( aFile ) );
+ }
+ else if (aDlg.isSingleJobs())
+ {
+ xController->getPrinter()->SetSinglePrintJobs(true);
+ }
+ }
+ catch (const std::bad_alloc&)
+ {
+ }
+ }
+
+ xController->pushPropertiesToPrinter();
+ return true;
+}
+
+bool Printer::ExecutePrintJob(const std::shared_ptr<PrinterController>& xController)
+{
+ OUString aJobName;
+ css::beans::PropertyValue* pJobNameVal = xController->getValue( "JobName" );
+ if( pJobNameVal )
+ pJobNameVal->Value >>= aJobName;
+
+ return xController->getPrinter()->StartJob( aJobName, xController );
+}
+
+void Printer::FinishPrintJob(const std::shared_ptr<PrinterController>& xController)
+{
+ xController->resetPaperToLastConfigured();
+ xController->jobFinished( xController->getJobState() );
+}
+
+void Printer::ImplPrintJob(const std::shared_ptr<PrinterController>& xController,
+ const JobSetup& i_rInitSetup)
+{
+ if (PreparePrintJob(xController, i_rInitSetup))
+ {
+ ExecutePrintJob(xController);
+ }
+ FinishPrintJob(xController);
+}
+
+bool Printer::StartJob( const OUString& i_rJobName, std::shared_ptr<vcl::PrinterController> const & i_xController)
+{
+ mnError = ERRCODE_NONE;
+
+ if ( IsDisplayPrinter() )
+ return false;
+
+ if ( IsJobActive() || IsPrinting() )
+ return false;
+
+ sal_uInt32 nCopies = mnCopyCount;
+ bool bCollateCopy = mbCollateCopy;
+ bool bUserCopy = false;
+
+ if ( nCopies > 1 )
+ {
+ const sal_uInt32 nDevCopy = GetCapabilities( bCollateCopy
+ ? PrinterCapType::CollateCopies
+ : PrinterCapType::Copies );
+
+ // need to do copies by hand ?
+ if ( nCopies > nDevCopy )
+ {
+ bUserCopy = true;
+ nCopies = 1;
+ bCollateCopy = false;
+ }
+ }
+ else
+ bCollateCopy = false;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ mpPrinter = pSVData->mpDefInst->CreatePrinter( mpInfoPrinter );
+
+ if (!mpPrinter)
+ return false;
+
+ bool bSinglePrintJobs = i_xController->getPrinter()->IsSinglePrintJobs();
+
+ css::beans::PropertyValue* pFileValue = i_xController->getValue("LocalFileName");
+ if( pFileValue )
+ {
+ OUString aFile;
+ pFileValue->Value >>= aFile;
+ if( !aFile.isEmpty() )
+ {
+ mbPrintFile = true;
+ maPrintFile = aFile;
+ bSinglePrintJobs = false;
+ }
+ }
+
+ OUString* pPrintFile = nullptr;
+ if ( mbPrintFile )
+ pPrintFile = &maPrintFile;
+ mpPrinterOptions->ReadFromConfig( mbPrintFile );
+
+ mbPrinting = true;
+ if( GetCapabilities( PrinterCapType::UsePullModel ) )
+ {
+ mbJobActive = true;
+ // SAL layer does all necessary page printing
+ // and also handles showing a dialog
+ // that also means it must call jobStarted when the dialog is finished
+ // it also must set the JobState of the Controller
+ if( mpPrinter->StartJob( pPrintFile,
+ i_rJobName,
+ Application::GetDisplayName(),
+ &maJobSetup.ImplGetData(),
+ *i_xController) )
+ {
+ EndJob();
+ }
+ else
+ {
+ mnError = ImplSalPrinterErrorCodeToVCL(mpPrinter->GetErrorCode());
+ if ( !mnError )
+ mnError = PRINTER_GENERALERROR;
+ mbPrinting = false;
+ mpPrinter.reset();
+ mbJobActive = false;
+
+ GDIMetaFile aDummyFile;
+ i_xController->setLastPage(true);
+ i_xController->getFilteredPageFile(0, aDummyFile);
+
+ return false;
+ }
+ }
+ else
+ {
+ // possibly a dialog has been shown
+ // now the real job starts
+ i_xController->setJobState( css::view::PrintableState_JOB_STARTED );
+ i_xController->jobStarted();
+
+ int nJobs = 1;
+ int nOuterRepeatCount = 1;
+ int nInnerRepeatCount = 1;
+ if( bUserCopy )
+ {
+ if( mbCollateCopy )
+ nOuterRepeatCount = mnCopyCount;
+ else
+ nInnerRepeatCount = mnCopyCount;
+ }
+ if( bSinglePrintJobs )
+ {
+ nJobs = mnCopyCount;
+ nCopies = 1;
+ nOuterRepeatCount = nInnerRepeatCount = 1;
+ }
+
+ for( int nJobIteration = 0; nJobIteration < nJobs; nJobIteration++ )
+ {
+ bool bError = false;
+ if( mpPrinter->StartJob( pPrintFile,
+ i_rJobName,
+ Application::GetDisplayName(),
+ nCopies,
+ bCollateCopy,
+ i_xController->isDirectPrint(),
+ &maJobSetup.ImplGetData() ) )
+ {
+ bool bAborted = false;
+ mbJobActive = true;
+ i_xController->createProgressDialog();
+ const int nPages = i_xController->getFilteredPageCount();
+ // abort job, if no pages will be printed.
+ if ( nPages == 0 )
+ {
+ i_xController->abortJob();
+ bAborted = true;
+ }
+ for( int nOuterIteration = 0; nOuterIteration < nOuterRepeatCount && ! bAborted; nOuterIteration++ )
+ {
+ for( int nPage = 0; nPage < nPages && ! bAborted; nPage++ )
+ {
+ for( int nInnerIteration = 0; nInnerIteration < nInnerRepeatCount && ! bAborted; nInnerIteration++ )
+ {
+ if( nPage == nPages-1 &&
+ nOuterIteration == nOuterRepeatCount-1 &&
+ nInnerIteration == nInnerRepeatCount-1 &&
+ nJobIteration == nJobs-1 )
+ {
+ i_xController->setLastPage(true);
+ }
+ i_xController->printFilteredPage(nPage);
+ if (i_xController->isProgressCanceled())
+ {
+ i_xController->abortJob();
+ }
+ if (i_xController->getJobState() ==
+ css::view::PrintableState_JOB_ABORTED)
+ {
+ bAborted = true;
+ }
+ }
+ }
+ // FIXME: duplex ?
+ }
+ EndJob();
+
+ if( nJobIteration < nJobs-1 )
+ {
+ mpPrinter = pSVData->mpDefInst->CreatePrinter( mpInfoPrinter );
+
+ if ( mpPrinter )
+ mbPrinting = true;
+ else
+ bError = true;
+ }
+ }
+ else
+ bError = true;
+
+ if( bError )
+ {
+ mnError = mpPrinter ? ImplSalPrinterErrorCodeToVCL(mpPrinter->GetErrorCode()) : ERRCODE_NONE;
+ if ( !mnError )
+ mnError = PRINTER_GENERALERROR;
+ i_xController->setJobState( mnError == PRINTER_ABORT
+ ? css::view::PrintableState_JOB_ABORTED
+ : css::view::PrintableState_JOB_FAILED );
+ mbPrinting = false;
+ mpPrinter.reset();
+
+ return false;
+ }
+ }
+
+ if (i_xController->getJobState() == css::view::PrintableState_JOB_STARTED)
+ i_xController->setJobState(css::view::PrintableState_JOB_SPOOLED);
+ }
+
+ // make last used printer persistent for UI jobs
+ if (i_xController->isShowDialogs() && !i_xController->isDirectPrint())
+ {
+ SettingsConfigItem* pItem = SettingsConfigItem::get();
+ pItem->setValue( "PrintDialog",
+ "LastPrinterUsed",
+ GetName()
+ );
+ }
+
+ return true;
+}
+
+PrinterController::~PrinterController()
+{
+}
+
+css::view::PrintableState PrinterController::getJobState() const
+{
+ return mpImplData->meJobState;
+}
+
+void PrinterController::setJobState( css::view::PrintableState i_eState )
+{
+ mpImplData->meJobState = i_eState;
+}
+
+const VclPtr<Printer>& PrinterController::getPrinter() const
+{
+ return mpImplData->mxPrinter;
+}
+
+weld::Window* PrinterController::getWindow() const
+{
+ return mpImplData->mpWindow;
+}
+
+void PrinterController::dialogsParentClosing()
+{
+ mpImplData->mpWindow = nullptr;
+ if (mpImplData->mxProgress)
+ {
+ // close the dialog without doing anything, just get rid of it
+ mpImplData->mxProgress->response(RET_OK);
+ mpImplData->mxProgress.reset();
+ }
+}
+
+void PrinterController::setPrinter( const VclPtr<Printer>& i_rPrinter )
+{
+ VclPtr<Printer> xPrinter = mpImplData->mxPrinter;
+
+ Size aPaperSize; // Save current paper size
+ Orientation eOrientation = Orientation::Portrait; // Save current paper orientation
+ bool bSavedSizeOrientation = false;
+
+ // #tdf 126744 Transfer paper size and orientation settings to newly selected printer
+ if ( xPrinter )
+ {
+ aPaperSize = xPrinter->GetPaperSize();
+ eOrientation = xPrinter->GetOrientation();
+ bSavedSizeOrientation = true;
+ }
+
+ mpImplData->mxPrinter = i_rPrinter;
+ setValue( "Name",
+ css::uno::Any( i_rPrinter->GetName() ) );
+ mpImplData->mnDefaultPaperBin = mpImplData->mxPrinter->GetPaperBin();
+ mpImplData->mxPrinter->Push();
+ mpImplData->mxPrinter->SetMapMode(MapMode(MapUnit::Map100thMM));
+ mpImplData->maDefaultPageSize = mpImplData->mxPrinter->GetPaperSize();
+
+ if ( bSavedSizeOrientation )
+ {
+ mpImplData->mxPrinter->SetPaperSizeUser(aPaperSize);
+ mpImplData->mxPrinter->SetOrientation(eOrientation);
+ }
+
+ mpImplData->mbPapersizeFromUser = false;
+ mpImplData->mbOrientationFromUser = false;
+ mpImplData->mxPrinter->Pop();
+ mpImplData->mnFixedPaperBin = -1;
+}
+
+void PrinterController::resetPrinterOptions( bool i_bFileOutput )
+{
+ vcl::printer::Options aOpt;
+ aOpt.ReadFromConfig( i_bFileOutput );
+ mpImplData->mxPrinter->SetPrinterOptions( aOpt );
+}
+
+void PrinterController::setupPrinter( weld::Window* i_pParent )
+{
+ bool bRet = false;
+
+ // Important to hold printer alive while doing setup etc.
+ VclPtr< Printer > xPrinter = mpImplData->mxPrinter;
+
+ if( !xPrinter )
+ return;
+
+ xPrinter->Push();
+ xPrinter->SetMapMode(MapMode(MapUnit::Map100thMM));
+
+ // get current data
+ Size aPaperSize(xPrinter->GetPaperSize());
+ Orientation eOrientation = xPrinter->GetOrientation();
+ sal_uInt16 nPaperBin = xPrinter->GetPaperBin();
+
+ // reset paper size back to last configured size, not
+ // whatever happens to be the current page
+ // (but only if the printer config has changed, otherwise
+ // don't override printer page auto-detection - tdf#91362)
+ if (getPrinterModified() || getPapersizeFromSetup())
+ {
+ resetPaperToLastConfigured();
+ }
+
+ // call driver setup
+ bRet = xPrinter->Setup( i_pParent, PrinterSetupMode::SingleJob );
+ SAL_WARN_IF(xPrinter != mpImplData->mxPrinter, "vcl.gdi",
+ "Printer changed underneath us during setup");
+ xPrinter = mpImplData->mxPrinter;
+
+ Size aNewPaperSize(xPrinter->GetPaperSize());
+ if (bRet)
+ {
+ bool bInvalidateCache = false;
+ setPapersizeFromSetup(xPrinter->GetPrinterSettingsPreferred());
+
+ // was papersize overridden ? if so we need to take action if we're
+ // configured to use the driver papersize
+ if (aNewPaperSize != mpImplData->maDefaultPageSize)
+ {
+ mpImplData->maDefaultPageSize = aNewPaperSize;
+ bInvalidateCache = getPapersizeFromSetup();
+ }
+
+ // was bin overridden ? if so we need to take action
+ sal_uInt16 nNewPaperBin = xPrinter->GetPaperBin();
+ if (nNewPaperBin != nPaperBin)
+ {
+ mpImplData->mnFixedPaperBin = nNewPaperBin;
+ bInvalidateCache = true;
+ }
+
+ if (bInvalidateCache)
+ {
+ mpImplData->maPageCache.invalidate();
+ }
+ }
+ else
+ {
+ //restore to whatever it was before we entered this method
+ xPrinter->SetOrientation( eOrientation );
+ if (aPaperSize != aNewPaperSize)
+ xPrinter->SetPaperSizeUser(aPaperSize);
+ }
+ xPrinter->Pop();
+}
+
+PrinterController::PageSize vcl::ImplPrinterControllerData::modifyJobSetup( const css::uno::Sequence< css::beans::PropertyValue >& i_rProps )
+{
+ PrinterController::PageSize aPageSize;
+ aPageSize.aSize = mxPrinter->GetPaperSize();
+ css::awt::Size aSetSize, aIsSize;
+ sal_Int32 nPaperBin = mnDefaultPaperBin;
+ for( const auto& rProp : i_rProps )
+ {
+ if ( rProp.Name == "PreferredPageSize" )
+ {
+ rProp.Value >>= aSetSize;
+ }
+ else if ( rProp.Name == "PageSize" )
+ {
+ rProp.Value >>= aIsSize;
+ }
+ else if ( rProp.Name == "PageIncludesNonprintableArea" )
+ {
+ bool bVal = false;
+ rProp.Value >>= bVal;
+ aPageSize.bFullPaper = bVal;
+ }
+ else if ( rProp.Name == "PrinterPaperTray" )
+ {
+ sal_Int32 nBin = -1;
+ rProp.Value >>= nBin;
+ if( nBin >= 0 && o3tl::make_unsigned(nBin) < mxPrinter->GetPaperBinCount() )
+ nPaperBin = nBin;
+ }
+ }
+
+ Size aCurSize( mxPrinter->GetPaperSize() );
+ if( aSetSize.Width && aSetSize.Height )
+ {
+ Size aSetPaperSize( aSetSize.Width, aSetSize.Height );
+ Size aRealPaperSize( getRealPaperSize( aSetPaperSize, true/*bNoNUP*/ ) );
+ if( aRealPaperSize != aCurSize )
+ aIsSize = aSetSize;
+ }
+
+ if( aIsSize.Width && aIsSize.Height )
+ {
+ aPageSize.aSize.setWidth( aIsSize.Width );
+ aPageSize.aSize.setHeight( aIsSize.Height );
+
+ Size aRealPaperSize( getRealPaperSize( aPageSize.aSize, true/*bNoNUP*/ ) );
+ if( aRealPaperSize != aCurSize )
+ mxPrinter->SetPaperSizeUser( aRealPaperSize );
+ }
+
+ // paper bin set from properties in print dialog overrides
+ // application default for a page
+ if ( mnFixedPaperBin != -1 )
+ nPaperBin = mnFixedPaperBin;
+
+ if( nPaperBin != -1 && nPaperBin != mxPrinter->GetPaperBin() )
+ mxPrinter->SetPaperBin( nPaperBin );
+
+ return aPageSize;
+}
+
+//fdo#61886
+
+//when printing is finished, set the paper size of the printer to either what
+//the user explicitly set as the desired paper size, or fallback to whatever
+//the printer had before printing started. That way it doesn't contain the last
+//paper size of a multiple paper size using document when we are in our normal
+//auto accept document paper size mode and end up overwriting the original
+//paper size setting for file->printer_settings just by pressing "ok" in the
+//print dialog
+void vcl::ImplPrinterControllerData::resetPaperToLastConfigured()
+{
+ mxPrinter->Push();
+ mxPrinter->SetMapMode(MapMode(MapUnit::Map100thMM));
+ Size aCurSize(mxPrinter->GetPaperSize());
+ if (aCurSize != maDefaultPageSize)
+ mxPrinter->SetPaperSizeUser(maDefaultPageSize);
+ mxPrinter->Pop();
+}
+
+int PrinterController::getPageCountProtected() const
+{
+ const MapMode aMapMode( MapUnit::Map100thMM );
+
+ mpImplData->mxPrinter->Push();
+ mpImplData->mxPrinter->SetMapMode( aMapMode );
+ int nPages = getPageCount();
+ mpImplData->mxPrinter->Pop();
+ return nPages;
+}
+
+css::uno::Sequence< css::beans::PropertyValue > PrinterController::getPageParametersProtected( int i_nPage ) const
+{
+ const MapMode aMapMode( MapUnit::Map100thMM );
+
+ mpImplData->mxPrinter->Push();
+ mpImplData->mxPrinter->SetMapMode( aMapMode );
+ css::uno::Sequence< css::beans::PropertyValue > aResult( getPageParameters( i_nPage ) );
+ mpImplData->mxPrinter->Pop();
+ return aResult;
+}
+
+PrinterController::PageSize PrinterController::getPageFile( int i_nUnfilteredPage, GDIMetaFile& o_rMtf, bool i_bMayUseCache )
+{
+ // update progress if necessary
+ if( mpImplData->mxProgress )
+ {
+ // do nothing if printing is canceled
+ if( mpImplData->mxProgress->isCanceled() )
+ return PrinterController::PageSize();
+ mpImplData->mxProgress->tick();
+ Application::Reschedule( true );
+ }
+
+ if( i_bMayUseCache )
+ {
+ PrinterController::PageSize aPageSize;
+ if( mpImplData->maPageCache.get( i_nUnfilteredPage, o_rMtf, aPageSize ) )
+ {
+ return aPageSize;
+ }
+ }
+ else
+ mpImplData->maPageCache.invalidate();
+
+ o_rMtf.Clear();
+
+ // get page parameters
+ css::uno::Sequence< css::beans::PropertyValue > aPageParm( getPageParametersProtected( i_nUnfilteredPage ) );
+ const MapMode aMapMode( MapUnit::Map100thMM );
+
+ mpImplData->mxPrinter->Push();
+ mpImplData->mxPrinter->SetMapMode( aMapMode );
+
+ // modify job setup if necessary
+ PrinterController::PageSize aPageSize = mpImplData->modifyJobSetup( aPageParm );
+
+ o_rMtf.SetPrefSize( aPageSize.aSize );
+ o_rMtf.SetPrefMapMode( aMapMode );
+
+ mpImplData->mxPrinter->EnableOutput( false );
+
+ o_rMtf.Record( mpImplData->mxPrinter.get() );
+
+ printPage( i_nUnfilteredPage );
+
+ o_rMtf.Stop();
+ o_rMtf.WindStart();
+ mpImplData->mxPrinter->Pop();
+
+ if( i_bMayUseCache )
+ mpImplData->maPageCache.insert( i_nUnfilteredPage, o_rMtf, aPageSize );
+
+ // reset "FirstPage" property to false now we've gotten at least our first one
+ mpImplData->mbFirstPage = false;
+
+ return aPageSize;
+}
+
+static void appendSubPage( GDIMetaFile& o_rMtf, const tools::Rectangle& i_rClipRect, GDIMetaFile& io_rSubPage, bool i_bDrawBorder )
+{
+ // intersect all clipregion actions with our clip rect
+ io_rSubPage.WindStart();
+ io_rSubPage.Clip( i_rClipRect );
+
+ // save gstate
+ o_rMtf.AddAction( new MetaPushAction( PushFlags::ALL ) );
+
+ // clip to page rect
+ o_rMtf.AddAction( new MetaClipRegionAction( vcl::Region( i_rClipRect ), true ) );
+
+ // append the subpage
+ io_rSubPage.WindStart();
+ io_rSubPage.Play( o_rMtf );
+
+ // restore gstate
+ o_rMtf.AddAction( new MetaPopAction() );
+
+ // draw a border
+ if( !i_bDrawBorder )
+ return;
+
+ // save gstate
+ o_rMtf.AddAction( new MetaPushAction( PushFlags::LINECOLOR | PushFlags::FILLCOLOR | PushFlags::CLIPREGION | PushFlags::MAPMODE ) );
+ o_rMtf.AddAction( new MetaMapModeAction( MapMode( MapUnit::Map100thMM ) ) );
+
+ tools::Rectangle aBorderRect( i_rClipRect );
+ o_rMtf.AddAction( new MetaLineColorAction( COL_BLACK, true ) );
+ o_rMtf.AddAction( new MetaFillColorAction( COL_TRANSPARENT, false ) );
+ o_rMtf.AddAction( new MetaRectAction( aBorderRect ) );
+
+ // restore gstate
+ o_rMtf.AddAction( new MetaPopAction() );
+}
+
+PrinterController::PageSize PrinterController::getFilteredPageFile( int i_nFilteredPage, GDIMetaFile& o_rMtf, bool i_bMayUseCache )
+{
+ const MultiPageSetup& rMPS( mpImplData->maMultiPage );
+ int nSubPages = rMPS.nRows * rMPS.nColumns;
+ if( nSubPages < 1 )
+ nSubPages = 1;
+
+ // reverse sheet order
+ if( mpImplData->mbReversePageOrder )
+ {
+ int nDocPages = getFilteredPageCount();
+ i_nFilteredPage = nDocPages - 1 - i_nFilteredPage;
+ }
+
+ // there is no filtering to be done (and possibly the page size of the
+ // original page is to be set), when N-Up is "neutral" that is there is
+ // only one subpage and the margins are 0
+ if( nSubPages == 1 &&
+ rMPS.nLeftMargin == 0 && rMPS.nRightMargin == 0 &&
+ rMPS.nTopMargin == 0 && rMPS.nBottomMargin == 0 )
+ {
+ PrinterController::PageSize aPageSize = getPageFile( i_nFilteredPage, o_rMtf, i_bMayUseCache );
+ if (mpImplData->meJobState != css::view::PrintableState_JOB_STARTED)
+ { // rhbz#657394: check that we are still printing...
+ return PrinterController::PageSize();
+ }
+ Size aPaperSize = mpImplData->getRealPaperSize( aPageSize.aSize, true );
+ mpImplData->mxPrinter->SetMapMode( MapMode( MapUnit::Map100thMM ) );
+ mpImplData->mxPrinter->SetPaperSizeUser( aPaperSize );
+ if( aPaperSize != aPageSize.aSize )
+ {
+ // user overridden page size, center Metafile
+ o_rMtf.WindStart();
+ tools::Long nDX = (aPaperSize.Width() - aPageSize.aSize.Width()) / 2;
+ tools::Long nDY = (aPaperSize.Height() - aPageSize.aSize.Height()) / 2;
+ o_rMtf.Move( nDX, nDY, mpImplData->mxPrinter->GetDPIX(), mpImplData->mxPrinter->GetDPIY() );
+ o_rMtf.WindStart();
+ o_rMtf.SetPrefSize( aPaperSize );
+ aPageSize.aSize = aPaperSize;
+ }
+ return aPageSize;
+ }
+
+ // set last page property really only on the very last page to be rendered
+ // that is on the last subpage of a NUp run
+ bool bIsLastPage = mpImplData->mbLastPage;
+ mpImplData->mbLastPage = false;
+
+ Size aPaperSize( mpImplData->getRealPaperSize( mpImplData->maMultiPage.aPaperSize, false ) );
+
+ // multi page area: page size minus margins + one time spacing right and down
+ // the added spacing is so each subpage can be calculated including its spacing
+ Size aMPArea( aPaperSize );
+ aMPArea.AdjustWidth( -(rMPS.nLeftMargin + rMPS.nRightMargin) );
+ aMPArea.AdjustWidth(rMPS.nHorizontalSpacing );
+ aMPArea.AdjustHeight( -(rMPS.nTopMargin + rMPS.nBottomMargin) );
+ aMPArea.AdjustHeight(rMPS.nVerticalSpacing );
+
+ // determine offsets
+ tools::Long nAdvX = aMPArea.Width() / rMPS.nColumns;
+ tools::Long nAdvY = aMPArea.Height() / rMPS.nRows;
+
+ // determine size of a "cell" subpage, leave a little space around pages
+ Size aSubPageSize( nAdvX - rMPS.nHorizontalSpacing, nAdvY - rMPS.nVerticalSpacing );
+
+ o_rMtf.Clear();
+ o_rMtf.SetPrefSize( aPaperSize );
+ o_rMtf.SetPrefMapMode( MapMode( MapUnit::Map100thMM ) );
+ o_rMtf.AddAction( new MetaMapModeAction( MapMode( MapUnit::Map100thMM ) ) );
+
+ int nDocPages = getPageCountProtected();
+ if (mpImplData->meJobState != css::view::PrintableState_JOB_STARTED)
+ { // rhbz#657394: check that we are still printing...
+ return PrinterController::PageSize();
+ }
+ for( int nSubPage = 0; nSubPage < nSubPages; nSubPage++ )
+ {
+ // map current sub page to real page
+ int nPage = i_nFilteredPage * nSubPages + nSubPage;
+ if( nSubPage == nSubPages-1 ||
+ nPage == nDocPages-1 )
+ {
+ mpImplData->mbLastPage = bIsLastPage;
+ }
+ if( nPage >= 0 && nPage < nDocPages )
+ {
+ GDIMetaFile aPageFile;
+ PrinterController::PageSize aPageSize = getPageFile( nPage, aPageFile, i_bMayUseCache );
+ if( aPageSize.aSize.Width() && aPageSize.aSize.Height() )
+ {
+ tools::Long nCellX = 0, nCellY = 0;
+ switch( rMPS.nOrder )
+ {
+ case NupOrderType::LRTB:
+ nCellX = (nSubPage % rMPS.nColumns);
+ nCellY = (nSubPage / rMPS.nColumns);
+ break;
+ case NupOrderType::TBLR:
+ nCellX = (nSubPage / rMPS.nRows);
+ nCellY = (nSubPage % rMPS.nRows);
+ break;
+ case NupOrderType::RLTB:
+ nCellX = rMPS.nColumns - 1 - (nSubPage % rMPS.nColumns);
+ nCellY = (nSubPage / rMPS.nColumns);
+ break;
+ case NupOrderType::TBRL:
+ nCellX = rMPS.nColumns - 1 - (nSubPage / rMPS.nRows);
+ nCellY = (nSubPage % rMPS.nRows);
+ break;
+ }
+ // scale the metafile down to a sub page size
+ double fScaleX = double(aSubPageSize.Width())/double(aPageSize.aSize.Width());
+ double fScaleY = double(aSubPageSize.Height())/double(aPageSize.aSize.Height());
+ double fScale = std::min( fScaleX, fScaleY );
+ aPageFile.Scale( fScale, fScale );
+ aPageFile.WindStart();
+
+ // move the subpage so it is centered in its "cell"
+ tools::Long nOffX = (aSubPageSize.Width() - tools::Long(double(aPageSize.aSize.Width()) * fScale)) / 2;
+ tools::Long nOffY = (aSubPageSize.Height() - tools::Long(double(aPageSize.aSize.Height()) * fScale)) / 2;
+ tools::Long nX = rMPS.nLeftMargin + nOffX + nAdvX * nCellX;
+ tools::Long nY = rMPS.nTopMargin + nOffY + nAdvY * nCellY;
+ aPageFile.Move( nX, nY, mpImplData->mxPrinter->GetDPIX(), mpImplData->mxPrinter->GetDPIY() );
+ aPageFile.WindStart();
+ // calculate border rectangle
+ tools::Rectangle aSubPageRect( Point( nX, nY ),
+ Size( tools::Long(double(aPageSize.aSize.Width())*fScale),
+ tools::Long(double(aPageSize.aSize.Height())*fScale) ) );
+
+ // append subpage to page
+ appendSubPage( o_rMtf, aSubPageRect, aPageFile, rMPS.bDrawBorder );
+ }
+ }
+ }
+ o_rMtf.WindStart();
+
+ // subsequent getPageFile calls have changed the paper, reset it to current value
+ mpImplData->mxPrinter->SetMapMode( MapMode( MapUnit::Map100thMM ) );
+ mpImplData->mxPrinter->SetPaperSizeUser( aPaperSize );
+
+ return PrinterController::PageSize( aPaperSize, true );
+}
+
+int PrinterController::getFilteredPageCount() const
+{
+ int nDiv = mpImplData->maMultiPage.nRows * mpImplData->maMultiPage.nColumns;
+ if( nDiv < 1 )
+ nDiv = 1;
+ return (getPageCountProtected() + (nDiv-1)) / nDiv;
+}
+
+DrawModeFlags PrinterController::removeTransparencies( GDIMetaFile const & i_rIn, GDIMetaFile& o_rOut )
+{
+ DrawModeFlags nRestoreDrawMode = mpImplData->mxPrinter->GetDrawMode();
+ sal_Int32 nMaxBmpDPIX = mpImplData->mxPrinter->GetDPIX();
+ sal_Int32 nMaxBmpDPIY = mpImplData->mxPrinter->GetDPIY();
+
+ const vcl::printer::Options& rPrinterOptions = mpImplData->mxPrinter->GetPrinterOptions();
+
+ static const sal_Int32 OPTIMAL_BMP_RESOLUTION = 300;
+ static const sal_Int32 NORMAL_BMP_RESOLUTION = 200;
+
+ if( rPrinterOptions.IsReduceBitmaps() )
+ {
+ // calculate maximum resolution for bitmap graphics
+ if( printer::BitmapMode::Optimal == rPrinterOptions.GetReducedBitmapMode() )
+ {
+ nMaxBmpDPIX = std::min( sal_Int32(OPTIMAL_BMP_RESOLUTION), nMaxBmpDPIX );
+ nMaxBmpDPIY = std::min( sal_Int32(OPTIMAL_BMP_RESOLUTION), nMaxBmpDPIY );
+ }
+ else if( printer::BitmapMode::Normal == rPrinterOptions.GetReducedBitmapMode() )
+ {
+ nMaxBmpDPIX = std::min( sal_Int32(NORMAL_BMP_RESOLUTION), nMaxBmpDPIX );
+ nMaxBmpDPIY = std::min( sal_Int32(NORMAL_BMP_RESOLUTION), nMaxBmpDPIY );
+ }
+ else
+ {
+ nMaxBmpDPIX = std::min( sal_Int32(rPrinterOptions.GetReducedBitmapResolution()), nMaxBmpDPIX );
+ nMaxBmpDPIY = std::min( sal_Int32(rPrinterOptions.GetReducedBitmapResolution()), nMaxBmpDPIY );
+ }
+ }
+
+ // convert to greyscales
+ if( rPrinterOptions.IsConvertToGreyscales() )
+ {
+ mpImplData->mxPrinter->SetDrawMode( mpImplData->mxPrinter->GetDrawMode() |
+ ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
+ DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
+ }
+
+ // disable transparency output
+ if( rPrinterOptions.IsReduceTransparency() && ( vcl::printer::TransparencyMode::NONE == rPrinterOptions.GetReducedTransparencyMode() ) )
+ {
+ mpImplData->mxPrinter->SetDrawMode( mpImplData->mxPrinter->GetDrawMode() | DrawModeFlags::NoTransparency );
+ }
+
+ Color aBg( COL_TRANSPARENT ); // default: let RemoveTransparenciesFromMetaFile do its own background logic
+ if( mpImplData->maMultiPage.nRows * mpImplData->maMultiPage.nColumns > 1 )
+ {
+ // in N-Up printing we have no "page" background operation
+ // we also have no way to determine the paper color
+ // so let's go for white, which will kill 99.9% of the real cases
+ aBg = COL_WHITE;
+ }
+ mpImplData->mxPrinter->RemoveTransparenciesFromMetaFile( i_rIn, o_rOut, nMaxBmpDPIX, nMaxBmpDPIY,
+ rPrinterOptions.IsReduceTransparency(),
+ rPrinterOptions.GetReducedTransparencyMode() == vcl::printer::TransparencyMode::Auto,
+ rPrinterOptions.IsReduceBitmaps() && rPrinterOptions.IsReducedBitmapIncludesTransparency(),
+ aBg
+ );
+ return nRestoreDrawMode;
+}
+
+void PrinterController::printFilteredPage( int i_nPage )
+{
+ if( mpImplData->meJobState != css::view::PrintableState_JOB_STARTED )
+ return; // rhbz#657394: check that we are still printing...
+
+ GDIMetaFile aPageFile;
+ PrinterController::PageSize aPageSize = getFilteredPageFile( i_nPage, aPageFile );
+
+ if( mpImplData->mxProgress )
+ {
+ // do nothing if printing is canceled
+ if( mpImplData->mxProgress->isCanceled() )
+ {
+ setJobState( css::view::PrintableState_JOB_ABORTED );
+ return;
+ }
+ }
+
+ // in N-Up printing set the correct page size
+ mpImplData->mxPrinter->SetMapMode(MapMode(MapUnit::Map100thMM));
+ // aPageSize was filtered through mpImplData->getRealPaperSize already by getFilteredPageFile()
+ mpImplData->mxPrinter->SetPaperSizeUser( aPageSize.aSize );
+ if( mpImplData->mnFixedPaperBin != -1 &&
+ mpImplData->mxPrinter->GetPaperBin() != mpImplData->mnFixedPaperBin )
+ {
+ mpImplData->mxPrinter->SetPaperBin( mpImplData->mnFixedPaperBin );
+ }
+
+ // if full paper is meant to be used, move the output to accommodate for pageoffset
+ if( aPageSize.bFullPaper )
+ {
+ Point aPageOffset( mpImplData->mxPrinter->GetPageOffset() );
+ aPageFile.WindStart();
+ aPageFile.Move( -aPageOffset.X(), -aPageOffset.Y(), mpImplData->mxPrinter->GetDPIX(), mpImplData->mxPrinter->GetDPIY() );
+ }
+
+ GDIMetaFile aCleanedFile;
+ DrawModeFlags nRestoreDrawMode = removeTransparencies( aPageFile, aCleanedFile );
+
+ mpImplData->mxPrinter->EnableOutput();
+
+ // actually print the page
+ mpImplData->mxPrinter->ImplStartPage();
+
+ mpImplData->mxPrinter->Push();
+ aCleanedFile.WindStart();
+ aCleanedFile.Play(*mpImplData->mxPrinter);
+ mpImplData->mxPrinter->Pop();
+
+ mpImplData->mxPrinter->ImplEndPage();
+
+ mpImplData->mxPrinter->SetDrawMode( nRestoreDrawMode );
+}
+
+void PrinterController::jobStarted()
+{
+}
+
+void PrinterController::jobFinished( css::view::PrintableState )
+{
+}
+
+void PrinterController::abortJob()
+{
+ setJobState( css::view::PrintableState_JOB_ABORTED );
+ // applications (well, sw) depend on a page request with "IsLastPage" = true
+ // to free resources, else they (well, sw) will crash eventually
+ setLastPage( true );
+
+ if (mpImplData->mxProgress)
+ {
+ mpImplData->mxProgress->response(RET_CANCEL);
+ mpImplData->mxProgress.reset();
+ }
+
+ GDIMetaFile aMtf;
+ getPageFile( 0, aMtf );
+}
+
+void PrinterController::setLastPage( bool i_bLastPage )
+{
+ mpImplData->mbLastPage = i_bLastPage;
+}
+
+void PrinterController::setReversePrint( bool i_bReverse )
+{
+ mpImplData->mbReversePageOrder = i_bReverse;
+}
+
+void PrinterController::setPapersizeFromSetup( bool i_bPapersizeFromSetup )
+{
+ mpImplData->mbPapersizeFromSetup = i_bPapersizeFromSetup;
+ mpImplData->mxPrinter->SetPrinterSettingsPreferred( i_bPapersizeFromSetup );
+ if ( i_bPapersizeFromSetup )
+ {
+ mpImplData->mbPapersizeFromUser = false;
+ mpImplData->mbOrientationFromUser = false;
+ }
+}
+
+bool PrinterController::getPapersizeFromSetup() const
+{
+ return mpImplData->mbPapersizeFromSetup;
+}
+
+void PrinterController::setPaperSizeFromUser( Size i_aUserSize )
+{
+ mpImplData->mbPapersizeFromUser = true;
+ mpImplData->mbPapersizeFromSetup = false;
+ mpImplData->mxPrinter->SetPrinterSettingsPreferred( false );
+
+ mpImplData->maUserPageSize = i_aUserSize;
+}
+
+void PrinterController::setOrientationFromUser( Orientation eOrientation, bool set )
+{
+ mpImplData->mbOrientationFromUser = set;
+ mpImplData->meUserOrientation = eOrientation;
+}
+
+void PrinterController::setPrinterModified( bool i_bPrinterModified )
+{
+ mpImplData->mbPrinterModified = i_bPrinterModified;
+}
+
+bool PrinterController::getPrinterModified() const
+{
+ return mpImplData->mbPrinterModified;
+}
+
+css::uno::Sequence< css::beans::PropertyValue > PrinterController::getJobProperties( const css::uno::Sequence< css::beans::PropertyValue >& i_rMergeList ) const
+{
+ std::unordered_set< OUString > aMergeSet;
+ size_t nResultLen = size_t(i_rMergeList.getLength()) + mpImplData->maUIProperties.size() + 3;
+ for( const auto& rPropVal : i_rMergeList )
+ aMergeSet.insert( rPropVal.Name );
+
+ css::uno::Sequence< css::beans::PropertyValue > aResult( nResultLen );
+ auto pResult = aResult.getArray();
+ std::copy(i_rMergeList.begin(), i_rMergeList.end(), pResult);
+ int nCur = i_rMergeList.getLength();
+ for(const css::beans::PropertyValue & rPropVal : mpImplData->maUIProperties)
+ {
+ if( aMergeSet.find( rPropVal.Name ) == aMergeSet.end() )
+ pResult[nCur++] = rPropVal;
+ }
+ // append IsFirstPage
+ if( aMergeSet.find( "IsFirstPage" ) == aMergeSet.end() )
+ {
+ css::beans::PropertyValue aVal;
+ aVal.Name = "IsFirstPage";
+ aVal.Value <<= mpImplData->mbFirstPage;
+ pResult[nCur++] = aVal;
+ }
+ // append IsLastPage
+ if( aMergeSet.find( "IsLastPage" ) == aMergeSet.end() )
+ {
+ css::beans::PropertyValue aVal;
+ aVal.Name = "IsLastPage";
+ aVal.Value <<= mpImplData->mbLastPage;
+ pResult[nCur++] = aVal;
+ }
+ // append IsPrinter
+ if( aMergeSet.find( "IsPrinter" ) == aMergeSet.end() )
+ {
+ css::beans::PropertyValue aVal;
+ aVal.Name = "IsPrinter";
+ aVal.Value <<= true;
+ pResult[nCur++] = aVal;
+ }
+ aResult.realloc( nCur );
+ return aResult;
+}
+
+const css::uno::Sequence< css::beans::PropertyValue >& PrinterController::getUIOptions() const
+{
+ return mpImplData->maUIOptions;
+}
+
+css::beans::PropertyValue* PrinterController::getValue( const OUString& i_rProperty )
+{
+ std::unordered_map< OUString, size_t >::const_iterator it =
+ mpImplData->maPropertyToIndex.find( i_rProperty );
+ return it != mpImplData->maPropertyToIndex.end() ? &mpImplData->maUIProperties[it->second] : nullptr;
+}
+
+const css::beans::PropertyValue* PrinterController::getValue( const OUString& i_rProperty ) const
+{
+ std::unordered_map< OUString, size_t >::const_iterator it =
+ mpImplData->maPropertyToIndex.find( i_rProperty );
+ return it != mpImplData->maPropertyToIndex.end() ? &mpImplData->maUIProperties[it->second] : nullptr;
+}
+
+void PrinterController::setValue( const OUString& i_rPropertyName, const css::uno::Any& i_rValue )
+{
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rPropertyName;
+ aVal.Value = i_rValue;
+
+ setValue( aVal );
+}
+
+void PrinterController::setValue( const css::beans::PropertyValue& i_rPropertyValue )
+{
+ std::unordered_map< OUString, size_t >::const_iterator it =
+ mpImplData->maPropertyToIndex.find( i_rPropertyValue.Name );
+ if( it != mpImplData->maPropertyToIndex.end() )
+ mpImplData->maUIProperties[ it->second ] = i_rPropertyValue;
+ else
+ {
+ // insert correct index into property map
+ mpImplData->maPropertyToIndex[ i_rPropertyValue.Name ] = mpImplData->maUIProperties.size();
+ mpImplData->maUIProperties.push_back( i_rPropertyValue );
+ mpImplData->maUIPropertyEnabled.push_back( true );
+ }
+}
+
+void PrinterController::setUIOptions( const css::uno::Sequence< css::beans::PropertyValue >& i_rOptions )
+{
+ SAL_WARN_IF( mpImplData->maUIOptions.hasElements(), "vcl.gdi", "setUIOptions called twice !" );
+
+ mpImplData->maUIOptions = i_rOptions;
+
+ for( const auto& rOpt : i_rOptions )
+ {
+ css::uno::Sequence< css::beans::PropertyValue > aOptProp;
+ rOpt.Value >>= aOptProp;
+ bool bIsEnabled = true;
+ bool bHaveProperty = false;
+ OUString aPropName;
+ vcl::ImplPrinterControllerData::ControlDependency aDep;
+ css::uno::Sequence< sal_Bool > aChoicesDisabled;
+ for( const css::beans::PropertyValue& rEntry : std::as_const(aOptProp) )
+ {
+ if ( rEntry.Name == "Property" )
+ {
+ css::beans::PropertyValue aVal;
+ rEntry.Value >>= aVal;
+ DBG_ASSERT( mpImplData->maPropertyToIndex.find( aVal.Name )
+ == mpImplData->maPropertyToIndex.end(), "duplicate property entry" );
+ setValue( aVal );
+ aPropName = aVal.Name;
+ bHaveProperty = true;
+ }
+ else if ( rEntry.Name == "Enabled" )
+ {
+ bool bValue = true;
+ rEntry.Value >>= bValue;
+ bIsEnabled = bValue;
+ }
+ else if ( rEntry.Name == "DependsOnName" )
+ {
+ rEntry.Value >>= aDep.maDependsOnName;
+ }
+ else if ( rEntry.Name == "DependsOnEntry" )
+ {
+ rEntry.Value >>= aDep.mnDependsOnEntry;
+ }
+ else if ( rEntry.Name == "ChoicesDisabled" )
+ {
+ rEntry.Value >>= aChoicesDisabled;
+ }
+ }
+ if( bHaveProperty )
+ {
+ vcl::ImplPrinterControllerData::PropertyToIndexMap::const_iterator it =
+ mpImplData->maPropertyToIndex.find( aPropName );
+ // sanity check
+ if( it != mpImplData->maPropertyToIndex.end() )
+ {
+ mpImplData->maUIPropertyEnabled[ it->second ] = bIsEnabled;
+ }
+ if( !aDep.maDependsOnName.isEmpty() )
+ mpImplData->maControlDependencies[ aPropName ] = aDep;
+ if( aChoicesDisabled.hasElements() )
+ mpImplData->maChoiceDisableMap[ aPropName ] = aChoicesDisabled;
+ }
+ }
+}
+
+bool PrinterController::isUIOptionEnabled( const OUString& i_rProperty ) const
+{
+ bool bEnabled = false;
+ std::unordered_map< OUString, size_t >::const_iterator prop_it =
+ mpImplData->maPropertyToIndex.find( i_rProperty );
+ if( prop_it != mpImplData->maPropertyToIndex.end() )
+ {
+ bEnabled = mpImplData->maUIPropertyEnabled[prop_it->second];
+
+ if( bEnabled )
+ {
+ // check control dependencies
+ vcl::ImplPrinterControllerData::ControlDependencyMap::const_iterator it =
+ mpImplData->maControlDependencies.find( i_rProperty );
+ if( it != mpImplData->maControlDependencies.end() )
+ {
+ // check if the dependency is enabled
+ // if the dependency is disabled, we are too
+ bEnabled = isUIOptionEnabled( it->second.maDependsOnName );
+
+ if( bEnabled )
+ {
+ // does the dependency have the correct value ?
+ const css::beans::PropertyValue* pVal = getValue( it->second.maDependsOnName );
+ OSL_ENSURE( pVal, "unknown property in dependency" );
+ if( pVal )
+ {
+ sal_Int32 nDepVal = 0;
+ bool bDepVal = false;
+ if( pVal->Value >>= nDepVal )
+ {
+ bEnabled = (nDepVal == it->second.mnDependsOnEntry) || (it->second.mnDependsOnEntry == -1);
+ }
+ else if( pVal->Value >>= bDepVal )
+ {
+ // could be a dependency on a checked boolean
+ // in this case the dependency is on a non zero for checked value
+ bEnabled = ( bDepVal && it->second.mnDependsOnEntry != 0) ||
+ ( ! bDepVal && it->second.mnDependsOnEntry == 0);
+ }
+ else
+ {
+ // if the type does not match something is awry
+ OSL_FAIL( "strange type in control dependency" );
+ bEnabled = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ return bEnabled;
+}
+
+bool PrinterController::isUIChoiceEnabled( const OUString& i_rProperty, sal_Int32 i_nValue ) const
+{
+ bool bEnabled = true;
+ ImplPrinterControllerData::ChoiceDisableMap::const_iterator it =
+ mpImplData->maChoiceDisableMap.find( i_rProperty );
+ if(it != mpImplData->maChoiceDisableMap.end() )
+ {
+ const css::uno::Sequence< sal_Bool >& rDisabled( it->second );
+ if( i_nValue >= 0 && i_nValue < rDisabled.getLength() )
+ bEnabled = ! rDisabled[i_nValue];
+ }
+ return bEnabled;
+}
+
+OUString PrinterController::makeEnabled( const OUString& i_rProperty )
+{
+ OUString aDependency;
+
+ vcl::ImplPrinterControllerData::ControlDependencyMap::const_iterator it =
+ mpImplData->maControlDependencies.find( i_rProperty );
+ if( it != mpImplData->maControlDependencies.end() )
+ {
+ if( isUIOptionEnabled( it->second.maDependsOnName ) )
+ {
+ aDependency = it->second.maDependsOnName;
+ const css::beans::PropertyValue* pVal = getValue( aDependency );
+ OSL_ENSURE( pVal, "unknown property in dependency" );
+ if( pVal )
+ {
+ sal_Int32 nDepVal = 0;
+ bool bDepVal = false;
+ if( pVal->Value >>= nDepVal )
+ {
+ if( it->second.mnDependsOnEntry != -1 )
+ {
+ setValue( aDependency, css::uno::Any( sal_Int32( it->second.mnDependsOnEntry ) ) );
+ }
+ }
+ else if( pVal->Value >>= bDepVal )
+ {
+ setValue( aDependency, css::uno::Any( it->second.mnDependsOnEntry != 0 ) );
+ }
+ else
+ {
+ // if the type does not match something is awry
+ OSL_FAIL( "strange type in control dependency" );
+ }
+ }
+ }
+ }
+
+ return aDependency;
+}
+
+void PrinterController::createProgressDialog()
+{
+ if (!mpImplData->mxProgress)
+ {
+ bool bShow = true;
+ css::beans::PropertyValue* pMonitor = getValue( "MonitorVisible" );
+ if( pMonitor )
+ pMonitor->Value >>= bShow;
+ else
+ {
+ const css::beans::PropertyValue* pVal = getValue( "IsApi" );
+ if( pVal )
+ {
+ bool bApi = false;
+ pVal->Value >>= bApi;
+ bShow = ! bApi;
+ }
+ }
+
+ if( bShow && ! Application::IsHeadlessModeEnabled() )
+ {
+ mpImplData->mxProgress = std::make_shared<PrintProgressDialog>(getWindow(), getPageCountProtected());
+ weld::DialogController::runAsync(mpImplData->mxProgress, [](sal_Int32 /*nResult*/){});
+ }
+ }
+ else
+ {
+ mpImplData->mxProgress->response(RET_CANCEL);
+ mpImplData->mxProgress.reset();
+ }
+}
+
+bool PrinterController::isProgressCanceled() const
+{
+ return mpImplData->mxProgress && mpImplData->mxProgress->isCanceled();
+}
+
+void PrinterController::setMultipage( const MultiPageSetup& i_rMPS )
+{
+ mpImplData->maMultiPage = i_rMPS;
+}
+
+const PrinterController::MultiPageSetup& PrinterController::getMultipage() const
+{
+ return mpImplData->maMultiPage;
+}
+
+void PrinterController::resetPaperToLastConfigured()
+{
+ mpImplData->resetPaperToLastConfigured();
+}
+
+void PrinterController::pushPropertiesToPrinter()
+{
+ sal_Int32 nCopyCount = 1;
+ // set copycount and collate
+ const css::beans::PropertyValue* pVal = getValue( "CopyCount" );
+ if( pVal )
+ pVal->Value >>= nCopyCount;
+ bool bCollate = false;
+ pVal = getValue( "Collate" );
+ if( pVal )
+ pVal->Value >>= bCollate;
+ mpImplData->mxPrinter->SetCopyCount( static_cast<sal_uInt16>(nCopyCount), bCollate );
+
+ pVal = getValue("SinglePrintJobs");
+ bool bSinglePrintJobs = false;
+ if (pVal)
+ pVal->Value >>= bSinglePrintJobs;
+ mpImplData->mxPrinter->SetSinglePrintJobs(bSinglePrintJobs);
+
+ // duplex mode
+ pVal = getValue( "DuplexMode" );
+ if( pVal )
+ {
+ sal_Int16 nDuplex = css::view::DuplexMode::UNKNOWN;
+ pVal->Value >>= nDuplex;
+ switch( nDuplex )
+ {
+ case css::view::DuplexMode::OFF: mpImplData->mxPrinter->SetDuplexMode( DuplexMode::Off ); break;
+ case css::view::DuplexMode::LONGEDGE: mpImplData->mxPrinter->SetDuplexMode( DuplexMode::LongEdge ); break;
+ case css::view::DuplexMode::SHORTEDGE: mpImplData->mxPrinter->SetDuplexMode( DuplexMode::ShortEdge ); break;
+ }
+ }
+}
+
+bool PrinterController::isShowDialogs() const
+{
+ bool bApi = getBoolProperty( "IsApi", false );
+ return ! bApi && ! Application::IsHeadlessModeEnabled();
+}
+
+bool PrinterController::isDirectPrint() const
+{
+ bool bDirect = getBoolProperty( "IsDirect", false );
+ return bDirect;
+}
+
+bool PrinterController::getBoolProperty( const OUString& i_rProperty, bool i_bFallback ) const
+{
+ bool bRet = i_bFallback;
+ const css::beans::PropertyValue* pVal = getValue( i_rProperty );
+ if( pVal )
+ pVal->Value >>= bRet;
+ return bRet;
+}
+
+sal_Int32 PrinterController::getIntProperty( const OUString& i_rProperty, sal_Int32 i_nFallback ) const
+{
+ sal_Int32 nRet = i_nFallback;
+ const css::beans::PropertyValue* pVal = getValue( i_rProperty );
+ if( pVal )
+ pVal->Value >>= nRet;
+ return nRet;
+}
+
+/*
+ * PrinterOptionsHelper
+**/
+css::uno::Any PrinterOptionsHelper::getValue( const OUString& i_rPropertyName ) const
+{
+ css::uno::Any aRet;
+ std::unordered_map< OUString, css::uno::Any >::const_iterator it =
+ m_aPropertyMap.find( i_rPropertyName );
+ if( it != m_aPropertyMap.end() )
+ aRet = it->second;
+ return aRet;
+}
+
+bool PrinterOptionsHelper::getBoolValue( const OUString& i_rPropertyName, bool i_bDefault ) const
+{
+ bool bRet = false;
+ css::uno::Any aVal( getValue( i_rPropertyName ) );
+ return (aVal >>= bRet) ? bRet : i_bDefault;
+}
+
+sal_Int64 PrinterOptionsHelper::getIntValue( const OUString& i_rPropertyName, sal_Int64 i_nDefault ) const
+{
+ sal_Int64 nRet = 0;
+ css::uno::Any aVal( getValue( i_rPropertyName ) );
+ return (aVal >>= nRet) ? nRet : i_nDefault;
+}
+
+OUString PrinterOptionsHelper::getStringValue( const OUString& i_rPropertyName ) const
+{
+ OUString aRet;
+ css::uno::Any aVal( getValue( i_rPropertyName ) );
+ return (aVal >>= aRet) ? aRet : OUString();
+}
+
+bool PrinterOptionsHelper::processProperties( const css::uno::Sequence< css::beans::PropertyValue >& i_rNewProp )
+{
+ bool bChanged = false;
+
+ for( const auto& rVal : i_rNewProp )
+ {
+ std::unordered_map< OUString, css::uno::Any >::iterator it =
+ m_aPropertyMap.find( rVal.Name );
+
+ bool bElementChanged = (it == m_aPropertyMap.end()) || (it->second != rVal.Value);
+ if( bElementChanged )
+ {
+ m_aPropertyMap[ rVal.Name ] = rVal.Value;
+ bChanged = true;
+ }
+ }
+ return bChanged;
+}
+
+void PrinterOptionsHelper::appendPrintUIOptions( css::uno::Sequence< css::beans::PropertyValue >& io_rProps ) const
+{
+ if( !m_aUIProperties.empty() )
+ {
+ sal_Int32 nIndex = io_rProps.getLength();
+ io_rProps.realloc( nIndex+1 );
+ io_rProps.getArray()[ nIndex ] = comphelper::makePropertyValue(
+ "ExtraPrintUIOptions", comphelper::containerToSequence(m_aUIProperties));
+ }
+}
+
+css::uno::Any PrinterOptionsHelper::setUIControlOpt(const css::uno::Sequence< OUString >& i_rIDs,
+ const OUString& i_rTitle,
+ const css::uno::Sequence< OUString >& i_rHelpIds,
+ const OUString& i_rType,
+ const css::beans::PropertyValue* i_pVal,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ sal_Int32 nElements =
+ 2 // ControlType + ID
+ + (i_rTitle.isEmpty() ? 0 : 1) // Text
+ + (i_rHelpIds.hasElements() ? 1 : 0) // HelpId
+ + (i_pVal ? 1 : 0) // Property
+ + i_rControlOptions.maAddProps.size() // additional props
+ + (i_rControlOptions.maGroupHint.isEmpty() ? 0 : 1) // grouping
+ + (i_rControlOptions.mbInternalOnly ? 1 : 0) // internal hint
+ + (i_rControlOptions.mbEnabled ? 0 : 1) // enabled
+ ;
+ if( !i_rControlOptions.maDependsOnName.isEmpty() )
+ {
+ nElements += 1;
+ if( i_rControlOptions.mnDependsOnEntry != -1 )
+ nElements += 1;
+ if( i_rControlOptions.mbAttachToDependency )
+ nElements += 1;
+ }
+
+ css::uno::Sequence< css::beans::PropertyValue > aCtrl( nElements );
+ auto pCtrl = aCtrl.getArray();
+ sal_Int32 nUsed = 0;
+ if( !i_rTitle.isEmpty() )
+ {
+ pCtrl[nUsed ].Name = "Text";
+ pCtrl[nUsed++].Value <<= i_rTitle;
+ }
+ if( i_rHelpIds.hasElements() )
+ {
+ pCtrl[nUsed ].Name = "HelpId";
+ pCtrl[nUsed++].Value <<= i_rHelpIds;
+ }
+ pCtrl[nUsed ].Name = "ControlType";
+ pCtrl[nUsed++].Value <<= i_rType;
+ pCtrl[nUsed ].Name = "ID";
+ pCtrl[nUsed++].Value <<= i_rIDs;
+ if( i_pVal )
+ {
+ pCtrl[nUsed ].Name = "Property";
+ pCtrl[nUsed++].Value <<= *i_pVal;
+ }
+ if( !i_rControlOptions.maDependsOnName.isEmpty() )
+ {
+ pCtrl[nUsed ].Name = "DependsOnName";
+ pCtrl[nUsed++].Value <<= i_rControlOptions.maDependsOnName;
+ if( i_rControlOptions.mnDependsOnEntry != -1 )
+ {
+ pCtrl[nUsed ].Name = "DependsOnEntry";
+ pCtrl[nUsed++].Value <<= i_rControlOptions.mnDependsOnEntry;
+ }
+ if( i_rControlOptions.mbAttachToDependency )
+ {
+ pCtrl[nUsed ].Name = "AttachToDependency";
+ pCtrl[nUsed++].Value <<= i_rControlOptions.mbAttachToDependency;
+ }
+ }
+ if( !i_rControlOptions.maGroupHint.isEmpty() )
+ {
+ pCtrl[nUsed ].Name = "GroupingHint";
+ pCtrl[nUsed++].Value <<= i_rControlOptions.maGroupHint;
+ }
+ if( i_rControlOptions.mbInternalOnly )
+ {
+ pCtrl[nUsed ].Name = "InternalUIOnly";
+ pCtrl[nUsed++].Value <<= true;
+ }
+ if( ! i_rControlOptions.mbEnabled )
+ {
+ pCtrl[nUsed ].Name = "Enabled";
+ pCtrl[nUsed++].Value <<= false;
+ }
+
+ sal_Int32 nAddProps = i_rControlOptions.maAddProps.size();
+ for( sal_Int32 i = 0; i < nAddProps; i++ )
+ pCtrl[ nUsed++ ] = i_rControlOptions.maAddProps[i];
+
+ SAL_WARN_IF( nUsed != nElements, "vcl.gdi", "nUsed != nElements, probable heap corruption" );
+
+ return css::uno::Any( aCtrl );
+}
+
+css::uno::Any PrinterOptionsHelper::setGroupControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const OUString& i_rHelpId)
+{
+ css::uno::Sequence< OUString > aHelpId;
+ if( !i_rHelpId.isEmpty() )
+ {
+ aHelpId.realloc( 1 );
+ *aHelpId.getArray() = i_rHelpId;
+ }
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, aHelpId, "Group");
+}
+
+css::uno::Any PrinterOptionsHelper::setSubgroupControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const OUString& i_rHelpId,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ css::uno::Sequence< OUString > aHelpId;
+ if( !i_rHelpId.isEmpty() )
+ {
+ aHelpId.realloc( 1 );
+ *aHelpId.getArray() = i_rHelpId;
+ }
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, aHelpId, "Subgroup", nullptr, i_rControlOptions);
+}
+
+css::uno::Any PrinterOptionsHelper::setBoolControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const OUString& i_rHelpId,
+ const OUString& i_rProperty,
+ bool i_bValue,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ css::uno::Sequence< OUString > aHelpId;
+ if( !i_rHelpId.isEmpty() )
+ {
+ aHelpId.realloc( 1 );
+ *aHelpId.getArray() = i_rHelpId;
+ }
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rProperty;
+ aVal.Value <<= i_bValue;
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, aHelpId, "Bool", &aVal, i_rControlOptions);
+}
+
+css::uno::Any PrinterOptionsHelper::setChoiceRadiosControlOpt(const css::uno::Sequence< OUString >& i_rIDs,
+ const OUString& i_rTitle,
+ const css::uno::Sequence< OUString >& i_rHelpId,
+ const OUString& i_rProperty,
+ const css::uno::Sequence< OUString >& i_rChoices,
+ sal_Int32 i_nValue,
+ const css::uno::Sequence< sal_Bool >& i_rDisabledChoices,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ UIControlOptions aOpt( i_rControlOptions );
+ sal_Int32 nUsed = aOpt.maAddProps.size();
+ aOpt.maAddProps.resize( nUsed + 1 + (i_rDisabledChoices.hasElements() ? 1 : 0) );
+ aOpt.maAddProps[nUsed].Name = "Choices";
+ aOpt.maAddProps[nUsed].Value <<= i_rChoices;
+ if( i_rDisabledChoices.hasElements() )
+ {
+ aOpt.maAddProps[nUsed+1].Name = "ChoicesDisabled";
+ aOpt.maAddProps[nUsed+1].Value <<= i_rDisabledChoices;
+ }
+
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rProperty;
+ aVal.Value <<= i_nValue;
+ return setUIControlOpt(i_rIDs, i_rTitle, i_rHelpId, "Radio", &aVal, aOpt);
+}
+
+css::uno::Any PrinterOptionsHelper::setChoiceListControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const css::uno::Sequence< OUString >& i_rHelpId,
+ const OUString& i_rProperty,
+ const css::uno::Sequence< OUString >& i_rChoices,
+ sal_Int32 i_nValue,
+ const css::uno::Sequence< sal_Bool >& i_rDisabledChoices,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ UIControlOptions aOpt( i_rControlOptions );
+ sal_Int32 nUsed = aOpt.maAddProps.size();
+ aOpt.maAddProps.resize( nUsed + 1 + (i_rDisabledChoices.hasElements() ? 1 : 0) );
+ aOpt.maAddProps[nUsed].Name = "Choices";
+ aOpt.maAddProps[nUsed].Value <<= i_rChoices;
+ if( i_rDisabledChoices.hasElements() )
+ {
+ aOpt.maAddProps[nUsed+1].Name = "ChoicesDisabled";
+ aOpt.maAddProps[nUsed+1].Value <<= i_rDisabledChoices;
+ }
+
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rProperty;
+ aVal.Value <<= i_nValue;
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, i_rHelpId, "List", &aVal, aOpt);
+}
+
+css::uno::Any PrinterOptionsHelper::setRangeControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const OUString& i_rHelpId,
+ const OUString& i_rProperty,
+ sal_Int32 i_nValue,
+ sal_Int32 i_nMinValue,
+ sal_Int32 i_nMaxValue,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ UIControlOptions aOpt( i_rControlOptions );
+ if( i_nMaxValue >= i_nMinValue )
+ {
+ sal_Int32 nUsed = aOpt.maAddProps.size();
+ aOpt.maAddProps.resize( nUsed + 2 );
+ aOpt.maAddProps[nUsed ].Name = "MinValue";
+ aOpt.maAddProps[nUsed++].Value <<= i_nMinValue;
+ aOpt.maAddProps[nUsed ].Name = "MaxValue";
+ aOpt.maAddProps[nUsed++].Value <<= i_nMaxValue;
+ }
+
+ css::uno::Sequence< OUString > aHelpId;
+ if( !i_rHelpId.isEmpty() )
+ {
+ aHelpId.realloc( 1 );
+ *aHelpId.getArray() = i_rHelpId;
+ }
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rProperty;
+ aVal.Value <<= i_nValue;
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, aHelpId, "Range", &aVal, aOpt);
+}
+
+css::uno::Any PrinterOptionsHelper::setEditControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const OUString& i_rHelpId,
+ const OUString& i_rProperty,
+ const OUString& i_rValue,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ css::uno::Sequence< OUString > aHelpId;
+ if( !i_rHelpId.isEmpty() )
+ {
+ aHelpId.realloc( 1 );
+ *aHelpId.getArray() = i_rHelpId;
+ }
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rProperty;
+ aVal.Value <<= i_rValue;
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, aHelpId, "Edit", &aVal, i_rControlOptions);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/regband.cxx b/vcl/source/gdi/regband.cxx
new file mode 100644
index 000000000..675d99bc0
--- /dev/null
+++ b/vcl/source/gdi/regband.cxx
@@ -0,0 +1,885 @@
+/* -*- 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 <tools/helpers.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+#include <regband.hxx>
+
+// ImplRegionBand
+
+// Each band contains all rectangles between upper and lower border.
+// For Union, Intersect, Xor and Exclude operations rectangles of
+// equal height are evaluated. The borders of the bands should always
+// be chosen such that this is possible.
+
+// If possible, rectangles within the bands are condensed.
+
+// When converting polygons all points of the polygon are registered
+// in the individual bands (for each band they are stored as
+// points in a list). After registration of these points they are
+// converted to rectangles and the points in the list are deleted.
+
+ImplRegionBand::ImplRegionBand( tools::Long nTop, tools::Long nBottom )
+{
+ // save boundaries
+ mnYTop = nTop;
+ mnYBottom = nBottom;
+
+ // initialize lists
+ mpNextBand = nullptr;
+ mpPrevBand = nullptr;
+ mpFirstSep = nullptr;
+ mpFirstBandPoint = nullptr;
+ mbTouched = false;
+}
+
+ImplRegionBand::ImplRegionBand(
+ const ImplRegionBand& rRegionBand,
+ const bool bIgnorePoints)
+{
+ // copy boundaries
+ mnYTop = rRegionBand.mnYTop;
+ mnYBottom = rRegionBand.mnYBottom;
+ mbTouched = rRegionBand.mbTouched;
+
+ // initialisation
+ mpNextBand = nullptr;
+ mpPrevBand = nullptr;
+ mpFirstSep = nullptr;
+ mpFirstBandPoint = nullptr;
+
+ // copy all elements of the list with separations
+ ImplRegionBandSep* pNewSep;
+ ImplRegionBandSep* pPrevSep = nullptr;
+ ImplRegionBandSep* pSep = rRegionBand.mpFirstSep;
+ while ( pSep )
+ {
+ // create new and copy data
+ pNewSep = new ImplRegionBandSep;
+ pNewSep->mnXLeft = pSep->mnXLeft;
+ pNewSep->mnXRight = pSep->mnXRight;
+ pNewSep->mbRemoved = pSep->mbRemoved;
+ pNewSep->mpNextSep = nullptr;
+ if ( pSep == rRegionBand.mpFirstSep )
+ mpFirstSep = pNewSep;
+ else
+ pPrevSep->mpNextSep = pNewSep;
+
+ pPrevSep = pNewSep;
+ pSep = pSep->mpNextSep;
+ }
+
+ if ( bIgnorePoints)
+ return;
+
+ // Copy points.
+ ImplRegionBandPoint* pPoint = rRegionBand.mpFirstBandPoint;
+ ImplRegionBandPoint* pPrevPointCopy = nullptr;
+ while (pPoint != nullptr)
+ {
+ ImplRegionBandPoint* pPointCopy = new ImplRegionBandPoint;
+ pPointCopy->mpNextBandPoint = nullptr;
+ pPointCopy->mnX = pPoint->mnX;
+ pPointCopy->mnLineId = pPoint->mnLineId;
+ pPointCopy->mbEndPoint = pPoint->mbEndPoint;
+ pPointCopy->meLineType = pPoint->meLineType;
+
+ if (pPrevPointCopy != nullptr)
+ pPrevPointCopy->mpNextBandPoint = pPointCopy;
+ else
+ mpFirstBandPoint = pPointCopy;
+
+ pPrevPointCopy = pPointCopy;
+ pPoint = pPoint->mpNextBandPoint;
+ }
+}
+
+ImplRegionBand::~ImplRegionBand()
+{
+ SAL_WARN_IF( mpFirstBandPoint != nullptr, "vcl", "ImplRegionBand::~ImplRegionBand -> pointlist not empty" );
+
+ // delete elements of the list
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ ImplRegionBandSep* pTempSep = pSep->mpNextSep;
+ delete pSep;
+ pSep = pTempSep;
+ }
+
+ // delete elements of the list
+ ImplRegionBandPoint* pPoint = mpFirstBandPoint;
+ while ( pPoint )
+ {
+ ImplRegionBandPoint* pTempPoint = pPoint->mpNextBandPoint;
+ delete pPoint;
+ pPoint = pTempPoint;
+ }
+}
+
+// generate separations from lines and process union with existing
+// separations
+
+void ImplRegionBand::ProcessPoints()
+{
+ // check Pointlist
+ ImplRegionBandPoint* pRegionBandPoint = mpFirstBandPoint;
+ while ( pRegionBandPoint )
+ {
+ // within list?
+ if ( pRegionBandPoint->mpNextBandPoint )
+ {
+ // start/stop?
+ if ( pRegionBandPoint->mbEndPoint && pRegionBandPoint->mpNextBandPoint->mbEndPoint )
+ {
+ // same direction? -> remove next point!
+ if ( pRegionBandPoint->meLineType == pRegionBandPoint->mpNextBandPoint->meLineType )
+ {
+ ImplRegionBandPoint* pSaveRegionBandPoint = pRegionBandPoint->mpNextBandPoint;
+ pRegionBandPoint->mpNextBandPoint = pRegionBandPoint->mpNextBandPoint->mpNextBandPoint;
+ delete pSaveRegionBandPoint;
+ }
+ }
+ }
+
+ // continue with next element in the list
+ pRegionBandPoint = pRegionBandPoint->mpNextBandPoint;
+ }
+
+ pRegionBandPoint = mpFirstBandPoint;
+ while ( pRegionBandPoint && pRegionBandPoint->mpNextBandPoint )
+ {
+ Union( pRegionBandPoint->mnX, pRegionBandPoint->mpNextBandPoint->mnX );
+
+ ImplRegionBandPoint* pNextBandPoint = pRegionBandPoint->mpNextBandPoint->mpNextBandPoint;
+
+ // remove already processed points
+ delete pRegionBandPoint->mpNextBandPoint;
+ delete pRegionBandPoint;
+
+ // continue with next element in the list
+ pRegionBandPoint = pNextBandPoint;
+ }
+
+ // remove last element if necessary
+ delete pRegionBandPoint;
+
+ // list is now empty
+ mpFirstBandPoint = nullptr;
+}
+
+// generate separations from lines and process union with existing
+// separations
+
+bool ImplRegionBand::InsertPoint( tools::Long nX, tools::Long nLineId,
+ bool bEndPoint, LineType eLineType )
+{
+ if ( !mpFirstBandPoint )
+ {
+ mpFirstBandPoint = new ImplRegionBandPoint;
+ mpFirstBandPoint->mnX = nX;
+ mpFirstBandPoint->mnLineId = nLineId;
+ mpFirstBandPoint->mbEndPoint = bEndPoint;
+ mpFirstBandPoint->meLineType = eLineType;
+ mpFirstBandPoint->mpNextBandPoint = nullptr;
+ return true;
+ }
+
+ // look if line already touched the band
+ ImplRegionBandPoint* pRegionBandPoint = mpFirstBandPoint;
+ ImplRegionBandPoint* pLastTestedRegionBandPoint = nullptr;
+ while( pRegionBandPoint )
+ {
+ if ( pRegionBandPoint->mnLineId == nLineId )
+ {
+ if ( bEndPoint )
+ {
+ if( !pRegionBandPoint->mbEndPoint )
+ {
+ // remove old band point
+ if( !mpFirstBandPoint->mpNextBandPoint )
+ {
+ // if we've only got one point => replace first point
+ pRegionBandPoint->mnX = nX;
+ pRegionBandPoint->mbEndPoint = true;
+ return true;
+ }
+ else
+ {
+ // remove current point
+ if( !pLastTestedRegionBandPoint )
+ {
+ // remove and delete old first point
+ ImplRegionBandPoint* pSaveBandPoint = mpFirstBandPoint;
+ mpFirstBandPoint = mpFirstBandPoint->mpNextBandPoint;
+ delete pSaveBandPoint;
+ }
+ else
+ {
+ // remove and delete current band point
+ pLastTestedRegionBandPoint->mpNextBandPoint = pRegionBandPoint->mpNextBandPoint;
+ delete pRegionBandPoint;
+ }
+
+ break;
+ }
+ }
+ }
+ else
+ return false;
+ }
+
+ // use next element
+ pLastTestedRegionBandPoint = pRegionBandPoint;
+ pRegionBandPoint = pRegionBandPoint->mpNextBandPoint;
+ }
+
+ // search appropriate position and insert point into the list
+ ImplRegionBandPoint* pNewRegionBandPoint;
+
+ pRegionBandPoint = mpFirstBandPoint;
+ pLastTestedRegionBandPoint = nullptr;
+ while ( pRegionBandPoint )
+ {
+ // new point completely left? -> insert as first point
+ if ( nX <= pRegionBandPoint->mnX )
+ {
+ pNewRegionBandPoint = new ImplRegionBandPoint;
+ pNewRegionBandPoint->mnX = nX;
+ pNewRegionBandPoint->mnLineId = nLineId;
+ pNewRegionBandPoint->mbEndPoint = bEndPoint;
+ pNewRegionBandPoint->meLineType = eLineType;
+ pNewRegionBandPoint->mpNextBandPoint = pRegionBandPoint;
+
+ // connections to the new point
+ if ( !pLastTestedRegionBandPoint )
+ mpFirstBandPoint = pNewRegionBandPoint;
+ else
+ pLastTestedRegionBandPoint->mpNextBandPoint = pNewRegionBandPoint;
+
+ return true;
+ }
+
+ // use next element
+ pLastTestedRegionBandPoint = pRegionBandPoint;
+ pRegionBandPoint = pRegionBandPoint->mpNextBandPoint;
+ }
+
+ // not inserted -> add to the end of the list
+ pNewRegionBandPoint = new ImplRegionBandPoint;
+ pNewRegionBandPoint->mnX = nX;
+ pNewRegionBandPoint->mnLineId = nLineId;
+ pNewRegionBandPoint->mbEndPoint = bEndPoint;
+ pNewRegionBandPoint->meLineType = eLineType;
+ pNewRegionBandPoint->mpNextBandPoint = nullptr;
+
+ // connections to the new point
+ pLastTestedRegionBandPoint->mpNextBandPoint = pNewRegionBandPoint;
+
+ return true;
+}
+
+void ImplRegionBand::MoveX( tools::Long nHorzMove )
+{
+ // move all x-separations
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ pSep->mnXLeft += nHorzMove;
+ pSep->mnXRight += nHorzMove;
+ pSep = pSep->mpNextSep;
+ }
+}
+
+void ImplRegionBand::ScaleX( double fHorzScale )
+{
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ pSep->mnXLeft = FRound( pSep->mnXLeft * fHorzScale );
+ pSep->mnXRight = FRound( pSep->mnXRight * fHorzScale );
+ pSep = pSep->mpNextSep;
+ }
+}
+
+// combine overlapping separations
+
+void ImplRegionBand::OptimizeBand()
+{
+ ImplRegionBandSep* pPrevSep = nullptr;
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ // remove?
+ if ( pSep->mbRemoved || (pSep->mnXRight < pSep->mnXLeft) )
+ {
+ ImplRegionBandSep* pOldSep = pSep;
+ if ( pSep == mpFirstSep )
+ mpFirstSep = pSep->mpNextSep;
+ else
+ pPrevSep->mpNextSep = pSep->mpNextSep;
+ pSep = pSep->mpNextSep;
+ delete pOldSep;
+ continue;
+ }
+
+ // overlapping separations? -> combine!
+ if ( pSep->mpNextSep )
+ {
+ if ( (pSep->mnXRight+1) >= pSep->mpNextSep->mnXLeft )
+ {
+ if ( pSep->mpNextSep->mnXRight > pSep->mnXRight )
+ pSep->mnXRight = pSep->mpNextSep->mnXRight;
+
+ ImplRegionBandSep* pOldSep = pSep->mpNextSep;
+ pSep->mpNextSep = pOldSep->mpNextSep;
+ delete pOldSep;
+ continue;
+ }
+ }
+
+ pPrevSep = pSep;
+ pSep = pSep->mpNextSep;
+ }
+}
+
+void ImplRegionBand::Union( tools::Long nXLeft, tools::Long nXRight )
+{
+ SAL_WARN_IF( nXLeft > nXRight, "vcl", "ImplRegionBand::Union(): nxLeft > nXRight" );
+
+ // band empty? -> add element
+ if ( !mpFirstSep )
+ {
+ mpFirstSep = new ImplRegionBandSep;
+ mpFirstSep->mnXLeft = nXLeft;
+ mpFirstSep->mnXRight = nXRight;
+ mpFirstSep->mbRemoved = false;
+ mpFirstSep->mpNextSep = nullptr;
+ return;
+ }
+
+ // process real union
+ ImplRegionBandSep* pNewSep;
+ ImplRegionBandSep* pPrevSep = nullptr;
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ // new separation completely inside? nothing to do!
+ if ( (nXLeft >= pSep->mnXLeft) && (nXRight <= pSep->mnXRight) )
+ return;
+
+ // new separation completely left? -> new separation!
+ if ( nXRight < pSep->mnXLeft )
+ {
+ pNewSep = new ImplRegionBandSep;
+ pNewSep->mnXLeft = nXLeft;
+ pNewSep->mnXRight = nXRight;
+ pNewSep->mbRemoved = false;
+
+ pNewSep->mpNextSep = pSep;
+ if ( pSep == mpFirstSep )
+ mpFirstSep = pNewSep;
+ else
+ pPrevSep->mpNextSep = pNewSep;
+ break;
+ }
+
+ // new separation overlapping from left? -> extend boundary
+ if ( (nXRight >= pSep->mnXLeft) && (nXLeft <= pSep->mnXLeft) )
+ pSep->mnXLeft = nXLeft;
+
+ // new separation overlapping from right? -> extend boundary
+ if ( (nXLeft <= pSep->mnXRight) && (nXRight > pSep->mnXRight) )
+ {
+ pSep->mnXRight = nXRight;
+ break;
+ }
+
+ // not inserted, but last element? -> add to the end of the list
+ if ( !pSep->mpNextSep && (nXLeft > pSep->mnXRight) )
+ {
+ pNewSep = new ImplRegionBandSep;
+ pNewSep->mnXLeft = nXLeft;
+ pNewSep->mnXRight = nXRight;
+ pNewSep->mbRemoved = false;
+
+ pSep->mpNextSep = pNewSep;
+ pNewSep->mpNextSep = nullptr;
+ break;
+ }
+
+ pPrevSep = pSep;
+ pSep = pSep->mpNextSep;
+ }
+
+ OptimizeBand();
+}
+
+void ImplRegionBand::Intersect( tools::Long nXLeft, tools::Long nXRight )
+{
+ SAL_WARN_IF( nXLeft > nXRight, "vcl", "ImplRegionBand::Intersect(): nxLeft > nXRight" );
+
+ // band has been touched
+ mbTouched = true;
+
+ // band empty? -> nothing to do
+ if ( !mpFirstSep )
+ return;
+
+ // process real intersection
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ // new separation completely outside? -> remove separation
+ if ( (nXRight < pSep->mnXLeft) || (nXLeft > pSep->mnXRight) )
+ // will be removed from the optimizer
+ pSep->mbRemoved = true;
+
+ // new separation overlapping from left? -> reduce right boundary
+ if ( (nXLeft <= pSep->mnXLeft) &&
+ (nXRight <= pSep->mnXRight) &&
+ (nXRight >= pSep->mnXLeft) )
+ pSep->mnXRight = nXRight;
+
+ // new separation overlapping from right? -> reduce right boundary
+ if ( (nXLeft >= pSep->mnXLeft) &&
+ (nXLeft <= pSep->mnXRight) &&
+ (nXRight >= pSep->mnXRight) )
+ pSep->mnXLeft = nXLeft;
+
+ // new separation within the actual one? -> reduce both boundaries
+ if ( (nXLeft >= pSep->mnXLeft) && (nXRight <= pSep->mnXRight) )
+ {
+ pSep->mnXRight = nXRight;
+ pSep->mnXLeft = nXLeft;
+ }
+
+ pSep = pSep->mpNextSep;
+ }
+
+ OptimizeBand();
+}
+
+void ImplRegionBand::Exclude( tools::Long nXLeft, tools::Long nXRight )
+{
+ SAL_WARN_IF( nXLeft > nXRight, "vcl", "ImplRegionBand::Exclude(): nxLeft > nXRight" );
+
+ // band has been touched
+ mbTouched = true;
+
+ // band empty? -> nothing to do
+ if ( !mpFirstSep )
+ return;
+
+ // process real exclusion
+ ImplRegionBandSep* pNewSep;
+ ImplRegionBandSep* pPrevSep = nullptr;
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ bool bSepProcessed = false;
+
+ // new separation completely overlapping? -> remove separation
+ if ( (nXLeft <= pSep->mnXLeft) && (nXRight >= pSep->mnXRight) )
+ {
+ // will be removed from the optimizer
+ pSep->mbRemoved = true;
+ bSepProcessed = true;
+ }
+
+ // new separation overlapping from left? -> reduce boundary
+ if ( !bSepProcessed )
+ {
+ if ( (nXRight >= pSep->mnXLeft) && (nXLeft <= pSep->mnXLeft) )
+ {
+ pSep->mnXLeft = nXRight+1;
+ bSepProcessed = true;
+ }
+ }
+
+ // new separation overlapping from right? -> reduce boundary
+ if ( !bSepProcessed )
+ {
+ if ( (nXLeft <= pSep->mnXRight) && (nXRight > pSep->mnXRight) )
+ {
+ pSep->mnXRight = nXLeft-1;
+ bSepProcessed = true;
+ }
+ }
+
+ // new separation within the actual one? -> reduce boundary
+ // and add new entry for reminder
+ if ( !bSepProcessed )
+ {
+ if ( (nXLeft >= pSep->mnXLeft) && (nXRight <= pSep->mnXRight) )
+ {
+ pNewSep = new ImplRegionBandSep;
+ pNewSep->mnXLeft = pSep->mnXLeft;
+ pNewSep->mnXRight = nXLeft-1;
+ pNewSep->mbRemoved = false;
+
+ pSep->mnXLeft = nXRight+1;
+
+ // connections from the new separation
+ pNewSep->mpNextSep = pSep;
+
+ // connections to the new separation
+ if ( pSep == mpFirstSep )
+ mpFirstSep = pNewSep;
+ else
+ pPrevSep->mpNextSep = pNewSep;
+ }
+ }
+
+ pPrevSep = pSep;
+ pSep = pSep->mpNextSep;
+ }
+
+ OptimizeBand();
+}
+
+void ImplRegionBand::XOr( tools::Long nXLeft, tools::Long nXRight )
+{
+ SAL_WARN_IF( nXLeft > nXRight, "vcl", "ImplRegionBand::XOr(): nxLeft > nXRight" );
+
+ // #i46602# Reworked rectangle Xor
+
+ // In general, we can distinguish 11 cases of intersection
+ // (details below). The old implementation explicitly handled 7
+ // cases (numbered in the order of appearance, use CVS to get your
+ // hands on the old version), therefore, I've sticked to that
+ // order, and added four more cases. The code below references
+ // those numbers via #1, #2, etc.
+
+ // Num Mnem newX:oldX newY:oldY Description Result Can quit?
+
+ // #1 Empty band - - The band is empty, thus, simply add new bandSep just add Yes
+
+ // #2 apart - - The rectangles are disjunct, add new one as is just add Yes
+
+ // #3 atop == == The rectangles are _exactly_ the same, remove existing just remove Yes
+
+ // #4 around < > The new rectangle extends the old to both sides intersect No
+
+ // #5 left < < The new rectangle is left of the old (but intersects) intersect Yes
+
+ // #5b left-atop < == The new is left of the old, and coincides on the right intersect Yes
+
+ // #6 right > > The new is right of the old (but intersects) intersect No
+
+ // #6b right-atop == > The new is right of the old, and coincides on the left intersect No
+
+ // #7 inside > < The new is fully inside the old intersect Yes
+
+ // #8 inside-right > == The new is fully inside the old, coincides on the right intersect Yes
+
+ // #9 inside-left == < The new is fully inside the old, coincides on the left intersect Yes
+
+ // Then, to correctly perform XOr, the segment that's switched off
+ // (i.e. the overlapping part of the old and the new segment) must
+ // be extended by one pixel value at each border:
+ // 1 1
+ // 0 4 0 4
+ // 111100000001111
+
+ // Clearly, the leading band sep now goes from 0 to 3, and the
+ // trailing band sep from 11 to 14. This mimics the xor look of a
+ // bitmap operation.
+
+ // band empty? -> add element
+ if ( !mpFirstSep )
+ {
+ mpFirstSep = new ImplRegionBandSep;
+ mpFirstSep->mnXLeft = nXLeft;
+ mpFirstSep->mnXRight = nXRight;
+ mpFirstSep->mbRemoved = false;
+ mpFirstSep->mpNextSep = nullptr;
+ return;
+ }
+
+ // process real xor
+ ImplRegionBandSep* pNewSep;
+ ImplRegionBandSep* pPrevSep = nullptr;
+ ImplRegionBandSep* pSep = mpFirstSep;
+
+ while ( pSep )
+ {
+ tools::Long nOldLeft( pSep->mnXLeft );
+ tools::Long nOldRight( pSep->mnXRight );
+
+ // did the current segment actually touch the new rect? If
+ // not, skip all comparisons, go on, loop and try to find
+ // intersecting bandSep
+ if( nXLeft <= nOldRight )
+ {
+ if( nXRight < nOldLeft )
+ {
+ // #2
+
+ // add _before_ current bandSep
+ pNewSep = new ImplRegionBandSep;
+ pNewSep->mnXLeft = nXLeft;
+ pNewSep->mnXRight = nXRight;
+ pNewSep->mpNextSep = pSep;
+ pNewSep->mbRemoved = false;
+
+ // connections from the new separation
+ pNewSep->mpNextSep = pSep;
+
+ // connections to the new separation
+ if ( pSep == mpFirstSep )
+ mpFirstSep = pNewSep;
+ else
+ pPrevSep->mpNextSep = pNewSep;
+ pPrevSep = nullptr; // do not run accidentally into the "right" case when breaking the loop
+ break;
+ }
+ else if( nXLeft == nOldLeft && nXRight == nOldRight )
+ {
+ // #3
+ pSep->mbRemoved = true;
+ pPrevSep = nullptr; // do not run accidentally into the "right" case when breaking the loop
+ break;
+ }
+ else if( nXLeft != nOldLeft && nXRight == nOldRight )
+ {
+ // # 5b, 8
+ if( nXLeft < nOldLeft )
+ {
+ nXRight = nOldLeft; // 5b
+ }
+ else
+ {
+ nXRight = nXLeft; // 8
+ nXLeft = nOldLeft;
+ }
+
+ pSep->mnXLeft = nXLeft;
+ pSep->mnXRight = nXRight-1;
+
+ pPrevSep = nullptr; // do not run accidentally into the "right" case when breaking the loop
+ break;
+ }
+ else if( nXLeft == nOldLeft && nXRight != nOldRight )
+ {
+ // # 6b, 9
+
+ if( nXRight > nOldRight )
+ {
+ nXLeft = nOldRight+1; // 6b
+
+ // cannot break here, simply mark segment as removed,
+ // and go on with adapted nXLeft/nXRight
+ pSep->mbRemoved = true;
+ }
+ else
+ {
+ pSep->mnXLeft = nXRight+1; // 9
+
+ pPrevSep = nullptr; // do not run accidentally into the "right" case when breaking the loop
+ break;
+ }
+ }
+ else // if( nXLeft != nOldLeft && nXRight != nOldRight ) follows automatically
+ {
+ // #4,5,6,7
+ SAL_WARN_IF( nXLeft == nOldLeft || nXRight == nOldRight, "vcl",
+ "ImplRegionBand::XOr(): Case 4,5,6,7 expected all coordinates to be not equal!" );
+
+ // The plain-jane check would look like this:
+
+ // if( nXLeft < nOldLeft )
+ // {
+ // // #4,5
+ // if( nXRight > nOldRight )
+ // {
+ // // #4
+ // }
+ // else
+ // {
+ // // #5 done!
+ // }
+ // }
+ // else
+ // {
+ // // #6,7
+ // if( nXRight > nOldRight )
+ // {
+ // // #6
+ // }
+ // else
+ // {
+ // // #7 done!
+ // }
+ // }
+
+ // but since we generally don't have to care whether
+ // it's 4 or 6 (only that we must not stop processing
+ // here), condensed that in such a way that only the
+ // coordinates get shuffled into correct ordering.
+
+ if( nXLeft < nOldLeft )
+ ::std::swap( nOldLeft, nXLeft );
+
+ bool bDone( false );
+
+ if( nXRight < nOldRight )
+ {
+ ::std::swap( nOldRight, nXRight );
+ bDone = true;
+ }
+
+ // now, nOldLeft<nXLeft<=nOldRight<nXRight always
+ // holds. Note that we need the nXLeft<=nOldRight here, as
+ // the intersection part might be only one pixel (original
+ // nXLeft==nXRight)
+ SAL_WARN_IF( nOldLeft==nXLeft || nXLeft>nOldRight || nOldRight>=nXRight, "vcl",
+ "ImplRegionBand::XOr(): Case 4,5,6,7 expected coordinates to be ordered now!" );
+
+ pSep->mnXLeft = nOldLeft;
+ pSep->mnXRight = nXLeft-1;
+
+ nXLeft = nOldRight+1;
+ // nxRight is already setup correctly
+
+ if( bDone )
+ {
+ // add behind current bandSep
+ pNewSep = new ImplRegionBandSep;
+
+ pNewSep->mnXLeft = nXLeft;
+ pNewSep->mnXRight = nXRight;
+ pNewSep->mpNextSep = pSep->mpNextSep;
+ pNewSep->mbRemoved = false;
+
+ // connections from the new separation
+ pSep->mpNextSep = pNewSep;
+
+ pPrevSep = nullptr; // do not run accidentally into the "right" case when breaking the loop
+ break;
+ }
+ }
+ }
+
+ pPrevSep = pSep;
+ pSep = pSep->mpNextSep;
+ }
+
+ // new separation completely right of existing bandSeps ?
+ if( pPrevSep && nXLeft >= pPrevSep->mnXRight )
+ {
+ pNewSep = new ImplRegionBandSep;
+ pNewSep->mnXLeft = nXLeft;
+ pNewSep->mnXRight = nXRight;
+ pNewSep->mpNextSep = nullptr;
+ pNewSep->mbRemoved = false;
+
+ // connections from the new separation
+ pPrevSep->mpNextSep = pNewSep;
+ }
+
+ OptimizeBand();
+}
+
+bool ImplRegionBand::Contains( tools::Long nX )
+{
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ if ( (pSep->mnXLeft <= nX) && (pSep->mnXRight >= nX) )
+ return true;
+
+ pSep = pSep->mpNextSep;
+ }
+
+ return false;
+}
+
+tools::Long ImplRegionBand::GetXLeftBoundary() const
+{
+ assert(mpFirstSep && "ImplRegionBand::XLeftBoundary -> no separation in band!");
+
+ return mpFirstSep->mnXLeft;
+}
+
+tools::Long ImplRegionBand::GetXRightBoundary() const
+{
+ SAL_WARN_IF( mpFirstSep == nullptr, "vcl", "ImplRegionBand::XRightBoundary -> no separation in band!" );
+
+ // search last separation
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep->mpNextSep )
+ pSep = pSep->mpNextSep;
+ return pSep->mnXRight;
+}
+
+bool ImplRegionBand::operator==( const ImplRegionBand& rRegionBand ) const
+{
+ ImplRegionBandSep* pOwnRectBandSep = mpFirstSep;
+ ImplRegionBandSep* pSecondRectBandSep = rRegionBand.mpFirstSep;
+ while ( pOwnRectBandSep && pSecondRectBandSep )
+ {
+ // get boundaries of current rectangle
+ tools::Long nOwnXLeft = pOwnRectBandSep->mnXLeft;
+ tools::Long nSecondXLeft = pSecondRectBandSep->mnXLeft;
+ if ( nOwnXLeft != nSecondXLeft )
+ return false;
+
+ tools::Long nOwnXRight = pOwnRectBandSep->mnXRight;
+ tools::Long nSecondXRight = pSecondRectBandSep->mnXRight;
+ if ( nOwnXRight != nSecondXRight )
+ return false;
+
+ // get next separation from current band
+ pOwnRectBandSep = pOwnRectBandSep->mpNextSep;
+
+ // get next separation from current band
+ pSecondRectBandSep = pSecondRectBandSep->mpNextSep;
+ }
+
+ // different number of separations?
+ return !(pOwnRectBandSep || pSecondRectBandSep);
+}
+
+ImplRegionBand* ImplRegionBand::SplitBand (const sal_Int32 nY)
+{
+ OSL_ASSERT(nY>mnYTop);
+ OSL_ASSERT(nY<=mnYBottom);
+
+ // Create a copy of the given band (we tell the constructor to copy the points together
+ // with the seps.)
+ ImplRegionBand* pLowerBand = new ImplRegionBand(*this, false);
+
+ // Adapt vertical coordinates.
+ mnYBottom = nY-1;
+ pLowerBand->mnYTop = nY;
+
+ // Insert new band into list of bands.
+ pLowerBand->mpNextBand = mpNextBand;
+ mpNextBand = pLowerBand;
+ pLowerBand->mpPrevBand = this;
+ if (pLowerBand->mpNextBand != nullptr)
+ pLowerBand->mpNextBand->mpPrevBand = pLowerBand;
+
+ return pLowerBand;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/region.cxx b/vcl/source/gdi/region.cxx
new file mode 100644
index 000000000..576ae0eb2
--- /dev/null
+++ b/vcl/source/gdi/region.cxx
@@ -0,0 +1,1792 @@
+/* -*- 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 <memory>
+#include <tools/vcompat.hxx>
+#include <tools/stream.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/region.hxx>
+#include <regionband.hxx>
+
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolygonclipper.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <tools/poly.hxx>
+#include <unotools/configmgr.hxx>
+
+namespace
+{
+ /** Return <TRUE/> when the given polygon is rectilinear and oriented so that
+ all sides are either horizontal or vertical.
+ */
+ bool ImplIsPolygonRectilinear (const tools::PolyPolygon& rPolyPoly)
+ {
+ // Iterate over all polygons.
+ const sal_uInt16 nPolyCount = rPolyPoly.Count();
+ for (sal_uInt16 nPoly = 0; nPoly < nPolyCount; ++nPoly)
+ {
+ const tools::Polygon& aPoly = rPolyPoly.GetObject(nPoly);
+
+ // Iterate over all edges of the current polygon.
+ const sal_uInt16 nSize = aPoly.GetSize();
+
+ if (nSize < 2)
+ continue;
+ Point aPoint (aPoly.GetPoint(0));
+ const Point aLastPoint (aPoint);
+ for (sal_uInt16 nPoint = 1; nPoint < nSize; ++nPoint)
+ {
+ const Point aNextPoint (aPoly.GetPoint(nPoint));
+ // When there is at least one edge that is neither vertical nor
+ // horizontal then the entire polygon is not rectilinear (and
+ // oriented along primary axes.)
+ if (aPoint.X() != aNextPoint.X() && aPoint.Y() != aNextPoint.Y())
+ return false;
+
+ aPoint = aNextPoint;
+ }
+ // Compare closing edge.
+ if (aLastPoint.X() != aPoint.X() && aLastPoint.Y() != aPoint.Y())
+ return false;
+ }
+ return true;
+ }
+
+ /** Convert a rectilinear polygon (that is oriented along the primary axes)
+ to a list of bands. For this special form of polygon we can use an
+ optimization that prevents the creation of one band per y value.
+ However, it still is possible that some temporary bands are created that
+ later can be optimized away.
+ @param rPolyPolygon
+ A set of zero, one, or more polygons, nested or not, that are
+ converted into a list of bands.
+ @return
+ A new RegionBand object is returned that contains the bands that
+ represent the given poly-polygon.
+ */
+ std::shared_ptr<RegionBand> ImplRectilinearPolygonToBands(const tools::PolyPolygon& rPolyPoly)
+ {
+ OSL_ASSERT(ImplIsPolygonRectilinear (rPolyPoly));
+
+ // Create a new RegionBand object as container of the bands.
+ std::shared_ptr<RegionBand> pRegionBand( std::make_shared<RegionBand>() );
+ tools::Long nLineId = 0;
+
+ // Iterate over all polygons.
+ const sal_uInt16 nPolyCount = rPolyPoly.Count();
+ for (sal_uInt16 nPoly = 0; nPoly < nPolyCount; ++nPoly)
+ {
+ const tools::Polygon& aPoly = rPolyPoly.GetObject(nPoly);
+
+ // Iterate over all edges of the current polygon.
+ const sal_uInt16 nSize = aPoly.GetSize();
+ if (nSize < 2)
+ continue;
+ // Avoid fetching every point twice (each point is the start point
+ // of one and the end point of another edge.)
+ Point aStart (aPoly.GetPoint(0));
+ Point aEnd;
+ for (sal_uInt16 nPoint = 1; nPoint <= nSize; ++nPoint, aStart=aEnd)
+ {
+ // We take the implicit closing edge into account by mapping
+ // index nSize to 0.
+ aEnd = aPoly.GetPoint(nPoint%nSize);
+ if (aStart.Y() == aEnd.Y())
+ {
+ // Horizontal lines are ignored.
+ continue;
+ }
+
+ // At this point the line has to be vertical.
+ OSL_ASSERT(aStart.X() == aEnd.X());
+
+ // Sort y-coordinates to simplify the algorithm and store the
+ // direction separately. The direction is calculated as it is
+ // in other places (but seems to be the wrong way.)
+ const tools::Long nTop (::std::min(aStart.Y(), aEnd.Y()));
+ const tools::Long nBottom (::std::max(aStart.Y(), aEnd.Y()));
+ const LineType eLineType (aStart.Y() > aEnd.Y() ? LineType::Descending : LineType::Ascending);
+
+ // Make sure that the current line is covered by bands.
+ pRegionBand->ImplAddMissingBands(nTop,nBottom);
+
+ // Find top-most band that may contain nTop.
+ ImplRegionBand* pBand = pRegionBand->ImplGetFirstRegionBand();
+ while (pBand!=nullptr && pBand->mnYBottom < nTop)
+ pBand = pBand->mpNextBand;
+ ImplRegionBand* pTopBand = pBand;
+ // If necessary split the band at nTop so that nTop is contained
+ // in the lower band.
+ if (pBand!=nullptr
+ // Prevent the current band from becoming 0 pixel high
+ && pBand->mnYTop<nTop
+ // this allows the lowest pixel of the band to be split off
+ && pBand->mnYBottom>=nTop
+ // do not split a band that is just one pixel high
+ && pBand->mnYTop<pBand->mnYBottom-1)
+ {
+ // Split the top band.
+ pTopBand = pBand->SplitBand(nTop);
+ }
+
+ // Advance to band that may contain nBottom.
+ while (pBand!=nullptr && pBand->mnYBottom < nBottom)
+ pBand = pBand->mpNextBand;
+ // The lowest band may have to be split at nBottom so that
+ // nBottom itself remains in the upper band.
+ if (pBand!=nullptr
+ // allow the current band becoming 1 pixel high
+ && pBand->mnYTop<=nBottom
+ // prevent splitting off a band that is 0 pixel high
+ && pBand->mnYBottom>nBottom
+ // do not split a band that is just one pixel high
+ && pBand->mnYTop<pBand->mnYBottom-1)
+ {
+ // Split the bottom band.
+ pBand->SplitBand(nBottom+1);
+ }
+
+ // Note that we remember the top band (in pTopBand) but not the
+ // bottom band. The later can be determined by comparing y
+ // coordinates.
+
+ // Add the x-value as point to all bands in the nTop->nBottom range.
+ for (pBand=pTopBand; pBand!=nullptr&&pBand->mnYTop<=nBottom; pBand=pBand->mpNextBand)
+ pBand->InsertPoint(aStart.X(), nLineId++, true, eLineType);
+ }
+ }
+
+ return pRegionBand;
+ }
+
+ /** Convert a general polygon (one for which ImplIsPolygonRectilinear()
+ returns <FALSE/>) to bands.
+ */
+ std::shared_ptr<RegionBand> ImplGeneralPolygonToBands(const tools::PolyPolygon& rPolyPoly, const tools::Rectangle& rPolygonBoundingBox)
+ {
+ tools::Long nLineID = 0;
+
+ // initialisation and creation of Bands
+ std::shared_ptr<RegionBand> pRegionBand( std::make_shared<RegionBand>() );
+ pRegionBand->CreateBandRange(rPolygonBoundingBox.Top(), rPolygonBoundingBox.Bottom());
+
+ // insert polygons
+ const sal_uInt16 nPolyCount = rPolyPoly.Count();
+
+ for ( sal_uInt16 nPoly = 0; nPoly < nPolyCount; nPoly++ )
+ {
+ // get reference to current polygon
+ const tools::Polygon& aPoly = rPolyPoly.GetObject( nPoly );
+ const sal_uInt16 nSize = aPoly.GetSize();
+
+ // not enough points ( <= 2 )? -> nothing to do!
+ if ( nSize <= 2 )
+ continue;
+
+ // band the polygon
+ for ( sal_uInt16 nPoint = 1; nPoint < nSize; nPoint++ )
+ {
+ pRegionBand->InsertLine( aPoly.GetPoint(nPoint-1), aPoly.GetPoint(nPoint), nLineID++ );
+ }
+
+ // close polygon with line from first point to last point, if necessary
+ const Point rLastPoint = aPoly.GetPoint(nSize-1);
+ const Point rFirstPoint = aPoly.GetPoint(0);
+
+ if ( rLastPoint != rFirstPoint )
+ {
+ pRegionBand->InsertLine( rLastPoint, rFirstPoint, nLineID++ );
+ }
+ }
+
+ return pRegionBand;
+ }
+} // end of anonymous namespace
+
+namespace vcl {
+
+bool vcl::Region::IsEmpty() const
+{
+ return !mbIsNull && !mpB2DPolyPolygon && !mpPolyPolygon && !mpRegionBand;
+}
+
+
+static std::shared_ptr<RegionBand> ImplCreateRegionBandFromPolyPolygon(const tools::PolyPolygon& rPolyPolygon)
+{
+ std::shared_ptr<RegionBand> pRetval;
+
+ if(rPolyPolygon.Count())
+ {
+ // ensure to subdivide when bezier segments are used, it's going to
+ // be expanded to rectangles
+ tools::PolyPolygon aPolyPolygon;
+
+ rPolyPolygon.AdaptiveSubdivide(aPolyPolygon);
+
+ if(aPolyPolygon.Count())
+ {
+ const tools::Rectangle aRect(aPolyPolygon.GetBoundRect());
+
+ if(!aRect.IsEmpty())
+ {
+ if(ImplIsPolygonRectilinear(aPolyPolygon))
+ {
+ // For rectilinear polygons there is an optimized band conversion.
+ pRetval = ImplRectilinearPolygonToBands(aPolyPolygon);
+ }
+ else
+ {
+ pRetval = ImplGeneralPolygonToBands(aPolyPolygon, aRect);
+ }
+
+ // Convert points into seps.
+ if(pRetval)
+ {
+ pRetval->processPoints();
+
+ // Optimize list of bands. Adjacent bands with identical lists
+ // of seps are joined.
+ if(!pRetval->OptimizeBandList())
+ {
+ pRetval.reset();
+ }
+ }
+ }
+ }
+ }
+
+ return pRetval;
+}
+
+tools::PolyPolygon vcl::Region::ImplCreatePolyPolygonFromRegionBand() const
+{
+ tools::PolyPolygon aRetval;
+
+ if(getRegionBand())
+ {
+ RectangleVector aRectangles;
+ GetRegionRectangles(aRectangles);
+
+ for (auto const& rectangle : aRectangles)
+ {
+ aRetval.Insert( tools::Polygon(rectangle) );
+ }
+ }
+ else
+ {
+ OSL_ENSURE(false, "Called with no local RegionBand (!)");
+ }
+
+ return aRetval;
+}
+
+basegfx::B2DPolyPolygon vcl::Region::ImplCreateB2DPolyPolygonFromRegionBand() const
+{
+ tools::PolyPolygon aPoly(ImplCreatePolyPolygonFromRegionBand());
+
+ return aPoly.getB2DPolyPolygon();
+}
+
+Region::Region(bool bIsNull)
+: mbIsNull(bIsNull)
+{
+}
+
+Region::Region(const tools::Rectangle& rRect)
+: mbIsNull(false)
+{
+ mpRegionBand.reset(rRect.IsEmpty() ? nullptr : new RegionBand(rRect));
+}
+
+Region::Region(const tools::Polygon& rPolygon)
+: mbIsNull(false)
+{
+
+ if(rPolygon.GetSize())
+ {
+ ImplCreatePolyPolyRegion(rPolygon);
+ }
+}
+
+Region::Region(const tools::PolyPolygon& rPolyPoly)
+: mbIsNull(false)
+{
+
+ if(rPolyPoly.Count())
+ {
+ ImplCreatePolyPolyRegion(rPolyPoly);
+ }
+}
+
+Region::Region(const basegfx::B2DPolyPolygon& rPolyPoly)
+: mbIsNull(false)
+{
+
+ if(rPolyPoly.count())
+ {
+ ImplCreatePolyPolyRegion(rPolyPoly);
+ }
+}
+
+Region::Region(const vcl::Region&) = default;
+
+Region::Region(vcl::Region&& rRegion) noexcept
+: mpB2DPolyPolygon(std::move(rRegion.mpB2DPolyPolygon)),
+ mpPolyPolygon(std::move(rRegion.mpPolyPolygon)),
+ mpRegionBand(std::move(rRegion.mpRegionBand)),
+ mbIsNull(rRegion.mbIsNull)
+{
+ rRegion.mbIsNull = true;
+}
+
+Region::~Region() = default;
+
+void vcl::Region::ImplCreatePolyPolyRegion( const tools::PolyPolygon& rPolyPoly )
+{
+ const sal_uInt16 nPolyCount = rPolyPoly.Count();
+
+ if(!nPolyCount)
+ return;
+
+ // polypolygon empty? -> empty region
+ const tools::Rectangle aRect(rPolyPoly.GetBoundRect());
+
+ if(aRect.IsEmpty())
+ return;
+
+ // width OR height == 1 ? => Rectangular region
+ if((1 == aRect.GetWidth()) || (1 == aRect.GetHeight()) || rPolyPoly.IsRect())
+ {
+ mpRegionBand = std::make_shared<RegionBand>(aRect);
+ }
+ else
+ {
+ mpPolyPolygon = rPolyPoly;
+ }
+
+ mbIsNull = false;
+}
+
+void vcl::Region::ImplCreatePolyPolyRegion( const basegfx::B2DPolyPolygon& rPolyPoly )
+{
+ if(rPolyPoly.count() && !rPolyPoly.getB2DRange().isEmpty())
+ {
+ mpB2DPolyPolygon = rPolyPoly;
+ mbIsNull = false;
+ }
+}
+
+void vcl::Region::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ if(IsNull() || IsEmpty())
+ {
+ // empty or null need no move
+ return;
+ }
+
+ if(!nHorzMove && !nVertMove)
+ {
+ // no move defined
+ return;
+ }
+
+ if(getB2DPolyPolygon())
+ {
+ basegfx::B2DPolyPolygon aPoly(*getB2DPolyPolygon());
+
+ aPoly.transform(basegfx::utils::createTranslateB2DHomMatrix(nHorzMove, nVertMove));
+ if (aPoly.count())
+ mpB2DPolyPolygon = aPoly;
+ else
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+ else if(getPolyPolygon())
+ {
+ tools::PolyPolygon aPoly(*getPolyPolygon());
+
+ aPoly.Move(nHorzMove, nVertMove);
+ mpB2DPolyPolygon.reset();
+ if (aPoly.Count())
+ mpPolyPolygon = aPoly;
+ else
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+ else if(getRegionBand())
+ {
+ RegionBand* pNew = new RegionBand(*getRegionBand());
+
+ pNew->Move(nHorzMove, nVertMove);
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset(pNew);
+ }
+ else
+ {
+ OSL_ENSURE(false, "Region::Move error: impossible combination (!)");
+ }
+}
+
+void vcl::Region::Scale( double fScaleX, double fScaleY )
+{
+ if(IsNull() || IsEmpty())
+ {
+ // empty or null need no scale
+ return;
+ }
+
+ if(basegfx::fTools::equalZero(fScaleX) && basegfx::fTools::equalZero(fScaleY))
+ {
+ // no scale defined
+ return;
+ }
+
+ if(getB2DPolyPolygon())
+ {
+ basegfx::B2DPolyPolygon aPoly(*getB2DPolyPolygon());
+
+ aPoly.transform(basegfx::utils::createScaleB2DHomMatrix(fScaleX, fScaleY));
+ if (aPoly.count())
+ mpB2DPolyPolygon = aPoly;
+ else
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+ else if(getPolyPolygon())
+ {
+ tools::PolyPolygon aPoly(*getPolyPolygon());
+
+ aPoly.Scale(fScaleX, fScaleY);
+ mpB2DPolyPolygon.reset();
+ if (aPoly.Count())
+ mpPolyPolygon = aPoly;
+ else
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+ else if(getRegionBand())
+ {
+ RegionBand* pNew = new RegionBand(*getRegionBand());
+
+ pNew->Scale(fScaleX, fScaleY);
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset(pNew);
+ }
+ else
+ {
+ OSL_ENSURE(false, "Region::Scale error: impossible combination (!)");
+ }
+}
+
+void vcl::Region::Union( const tools::Rectangle& rRect )
+{
+ if(rRect.IsEmpty())
+ {
+ // empty rectangle will not expand the existing union, nothing to do
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // no local data, the union will be equal to source. Create using rectangle
+ *this = rRect;
+ return;
+ }
+
+ if(HasPolyPolygonOrB2DPolyPolygon())
+ {
+ // get this B2DPolyPolygon, solve on polygon base
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly);
+
+ if(!aThisPolyPoly.count())
+ {
+ // no local polygon, use the rectangle as new region
+ *this = rRect;
+ }
+ else
+ {
+ // get the other B2DPolyPolygon and use logical Or-Operation
+ const basegfx::B2DPolygon aRectPoly(
+ basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect)));
+ const basegfx::B2DPolyPolygon aClip(
+ basegfx::utils::solvePolygonOperationOr(
+ aThisPolyPoly,
+ basegfx::B2DPolyPolygon(aRectPoly)));
+ *this = vcl::Region(aClip);
+ }
+
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // no region band, create using the rectangle
+ *this = rRect;
+ return;
+ }
+
+ std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*pCurrent);
+
+ // get justified rectangle
+ const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
+ const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));
+
+ // insert bands if the boundaries are not already in the list
+ pNew->InsertBands(nTop, nBottom);
+
+ // process union
+ pNew->Union(nLeft, nTop, nRight, nBottom);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+}
+
+void vcl::Region::Intersect( const tools::Rectangle& rRect )
+{
+ if ( rRect.IsEmpty() )
+ {
+ // empty rectangle will create empty region
+ SetEmpty();
+ return;
+ }
+
+ if(IsNull())
+ {
+ // null region (everything) intersect with rect will give rect
+ *this = rRect;
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // no content, cannot get more empty
+ return;
+ }
+
+ if(HasPolyPolygonOrB2DPolyPolygon())
+ {
+ // if polygon data prefer double precision, the other will be lost (if buffered)
+ if(getB2DPolyPolygon())
+ {
+ const basegfx::B2DPolyPolygon aPoly(
+ basegfx::utils::clipPolyPolygonOnRange(
+ *getB2DPolyPolygon(),
+ basegfx::B2DRange(
+ rRect.Left(),
+ rRect.Top(),
+ rRect.Right() + 1,
+ rRect.Bottom() + 1),
+ true,
+ false));
+
+ if (aPoly.count())
+ mpB2DPolyPolygon = aPoly;
+ else
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+ else // if(getPolyPolygon())
+ {
+ tools::PolyPolygon aPoly(*getPolyPolygon());
+
+ // use the PolyPolygon::Clip method for rectangles, this is
+ // fairly simple (does not even use GPC) and saves us from
+ // unnecessary banding
+ aPoly.Clip(rRect);
+
+ mpB2DPolyPolygon.reset();
+ if (aPoly.Count())
+ mpPolyPolygon = aPoly;
+ else
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // region is empty -> nothing to do!
+ return;
+ }
+
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
+
+ // get justified rectangle
+ const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
+ const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));
+
+ // insert bands if the boundaries are not already in the list
+ pNew->InsertBands(nTop, nBottom);
+
+ // process intersect
+ pNew->Intersect(nLeft, nTop, nRight, nBottom);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+}
+
+void vcl::Region::Exclude( const tools::Rectangle& rRect )
+{
+ if ( rRect.IsEmpty() )
+ {
+ // excluding nothing will do no change
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // cannot exclude from empty, done
+ return;
+ }
+
+ if(IsNull())
+ {
+ // error; cannot exclude from null region since this is not representable
+ // in the data
+ OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)");
+ return;
+ }
+
+ if( HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ // get this B2DPolyPolygon
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly);
+
+ if(!aThisPolyPoly.count())
+ {
+ // when local polygon is empty, nothing can be excluded
+ return;
+ }
+
+ // get the other B2DPolyPolygon
+ const basegfx::B2DPolygon aRectPoly(
+ basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect)));
+ const basegfx::B2DPolyPolygon aOtherPolyPoly(aRectPoly);
+ const basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationDiff(aThisPolyPoly, aOtherPolyPoly);
+
+ *this = vcl::Region(aClip);
+
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // empty? -> done!
+ return;
+ }
+
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
+
+ // get justified rectangle
+ const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
+ const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));
+
+ // insert bands if the boundaries are not already in the list
+ pNew->InsertBands(nTop, nBottom);
+
+ // process exclude
+ pNew->Exclude(nLeft, nTop, nRight, nBottom);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+}
+
+void vcl::Region::XOr( const tools::Rectangle& rRect )
+{
+ if ( rRect.IsEmpty() )
+ {
+ // empty rectangle will not change local content
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // rRect will be the xored-form (local off, rect on)
+ *this = rRect;
+ return;
+ }
+
+ if(IsNull())
+ {
+ // error; cannot exclude from null region since this is not representable
+ // in the data
+ OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
+ return;
+ }
+
+ if( HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ // get this B2DPolyPolygon
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ aThisPolyPoly = basegfx::utils::prepareForPolygonOperation( aThisPolyPoly );
+
+ if(!aThisPolyPoly.count())
+ {
+ // no local content, XOr will be equal to rectangle
+ *this = rRect;
+ return;
+ }
+
+ // get the other B2DPolyPolygon
+ const basegfx::B2DPolygon aRectPoly(
+ basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect)));
+ const basegfx::B2DPolyPolygon aOtherPolyPoly(aRectPoly);
+ const basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationXor(aThisPolyPoly, aOtherPolyPoly);
+
+ *this = vcl::Region(aClip);
+
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // rRect will be the xored-form (local off, rect on)
+ *this = rRect;
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*getRegionBand()));
+
+ // get justified rectangle
+ const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
+ const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));
+
+ // insert bands if the boundaries are not already in the list
+ pNew->InsertBands(nTop, nBottom);
+
+ // process xor
+ pNew->XOr(nLeft, nTop, nRight, nBottom);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+}
+
+void vcl::Region::Union( const vcl::Region& rRegion )
+{
+ if(rRegion.IsEmpty())
+ {
+ // no extension at all
+ return;
+ }
+
+ if(rRegion.IsNull())
+ {
+ // extending with null region -> null region
+ *this = vcl::Region(true);
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // local is empty, union will give source region
+ *this = rRegion;
+ return;
+ }
+
+ if(IsNull())
+ {
+ // already fully expanded (is null region), cannot be extended
+ return;
+ }
+
+ if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ // get this B2DPolyPolygon
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly);
+
+ if(!aThisPolyPoly.count())
+ {
+ // when no local content, union will be equal to rRegion
+ *this = rRegion;
+ return;
+ }
+
+ // get the other B2DPolyPolygon
+ basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
+ aOtherPolyPoly = basegfx::utils::prepareForPolygonOperation(aOtherPolyPoly);
+
+ // use logical OR operation
+ basegfx::B2DPolyPolygon aClip(basegfx::utils::solvePolygonOperationOr(aThisPolyPoly, aOtherPolyPoly));
+
+ *this = vcl::Region( aClip );
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // local is empty, union will give source region
+ *this = rRegion;
+ return;
+ }
+
+ const RegionBand* pSource = rRegion.getRegionBand();
+
+ if(!pSource)
+ {
+ // no extension at all
+ return;
+ }
+
+ // prepare source and target
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
+
+ // union with source
+ pNew->Union(*pSource);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+}
+
+void vcl::Region::Intersect( const vcl::Region& rRegion )
+{
+ // same instance data? -> nothing to do!
+ if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion.getB2DPolyPolygon())
+ {
+ return;
+ }
+
+ if(getPolyPolygon() && getPolyPolygon() == rRegion.getPolyPolygon())
+ {
+ return;
+ }
+
+ if(getRegionBand() && getRegionBand() == rRegion.getRegionBand())
+ {
+ return;
+ }
+
+ if(rRegion.IsNull())
+ {
+ // source region is null-region, intersect will not change local region
+ return;
+ }
+
+ if(IsNull())
+ {
+ // when local region is null-region, intersect will be equal to source
+ *this = rRegion;
+ return;
+ }
+
+ if(rRegion.IsEmpty())
+ {
+ // source region is empty, intersection will always be empty
+ SetEmpty();
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // local region is empty, cannot get more empty than that. Nothing to do
+ return;
+ }
+
+ if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ // get this B2DPolyPolygon
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ if(!aThisPolyPoly.count())
+ {
+ // local region is empty, cannot get more empty than that. Nothing to do
+ return;
+ }
+
+ // get the other B2DPolyPolygon
+ basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
+
+ if(!aOtherPolyPoly.count())
+ {
+ // source region is empty, intersection will always be empty
+ SetEmpty();
+ return;
+ }
+
+ static size_t gPointLimit = !utl::ConfigManager::IsFuzzing() ? SAL_MAX_SIZE : 8192;
+ size_t nPointLimit(gPointLimit);
+ const basegfx::B2DPolyPolygon aClip(
+ basegfx::utils::clipPolyPolygonOnPolyPolygon(
+ aOtherPolyPoly,
+ aThisPolyPoly,
+ true,
+ false,
+ &nPointLimit));
+ *this = vcl::Region( aClip );
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // local region is empty, cannot get more empty than that. Nothing to do
+ return;
+ }
+
+ const RegionBand* pSource = rRegion.getRegionBand();
+
+ if(!pSource)
+ {
+ // source region is empty, intersection will always be empty
+ SetEmpty();
+ return;
+ }
+
+ // both RegionBands exist and are not empty
+ if(pCurrent->getRectangleCount() + 2 < pSource->getRectangleCount())
+ {
+ // when we have less rectangles, turn around the call
+ vcl::Region aTempRegion = rRegion;
+ aTempRegion.Intersect( *this );
+ *this = aTempRegion;
+ }
+ else
+ {
+ // prepare new regionBand
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
+
+ // intersect with source
+ pNew->Intersect(*pSource);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+ }
+}
+
+void vcl::Region::Exclude( const vcl::Region& rRegion )
+{
+ if ( rRegion.IsEmpty() )
+ {
+ // excluding nothing will do no change
+ return;
+ }
+
+ if ( rRegion.IsNull() )
+ {
+ // excluding everything will create empty region
+ SetEmpty();
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // cannot exclude from empty, done
+ return;
+ }
+
+ if(IsNull())
+ {
+ // error; cannot exclude from null region since this is not representable
+ // in the data
+ OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)");
+ return;
+ }
+
+ if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ // get this B2DPolyPolygon
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ if(!aThisPolyPoly.count())
+ {
+ // cannot exclude from empty, done
+ return;
+ }
+
+ aThisPolyPoly = basegfx::utils::prepareForPolygonOperation( aThisPolyPoly );
+
+ // get the other B2DPolyPolygon
+ basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
+ aOtherPolyPoly = basegfx::utils::prepareForPolygonOperation( aOtherPolyPoly );
+
+ basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationDiff( aThisPolyPoly, aOtherPolyPoly );
+ *this = vcl::Region( aClip );
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // cannot exclude from empty, done
+ return;
+ }
+
+ const RegionBand* pSource = rRegion.getRegionBand();
+
+ if(!pSource)
+ {
+ // excluding nothing will do no change
+ return;
+ }
+
+ // prepare source and target
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
+
+ // union with source
+ const bool bSuccess(pNew->Exclude(*pSource));
+
+ // cleanup
+ if(!bSuccess)
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+}
+
+bool vcl::Region::XOr( const vcl::Region& rRegion )
+{
+ if ( rRegion.IsEmpty() )
+ {
+ // empty region will not change local content
+ return true;
+ }
+
+ if ( rRegion.IsNull() )
+ {
+ // error; cannot exclude null region from local since this is not representable
+ // in the data
+ OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
+ return true;
+ }
+
+ if(IsEmpty())
+ {
+ // rRect will be the xored-form (local off, rect on)
+ *this = rRegion;
+ return true;
+ }
+
+ if(IsNull())
+ {
+ // error: cannot exclude from null region since this is not representable
+ // in the data
+ OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
+ return false;
+ }
+
+ if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ // get this B2DPolyPolygon
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ if(!aThisPolyPoly.count())
+ {
+ // rRect will be the xored-form (local off, rect on)
+ *this = rRegion;
+ return true;
+ }
+
+ aThisPolyPoly = basegfx::utils::prepareForPolygonOperation( aThisPolyPoly );
+
+ // get the other B2DPolyPolygon
+ basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
+ aOtherPolyPoly = basegfx::utils::prepareForPolygonOperation( aOtherPolyPoly );
+
+ basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationXor( aThisPolyPoly, aOtherPolyPoly );
+ *this = vcl::Region( aClip );
+ return true;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // rRect will be the xored-form (local off, rect on)
+ *this = rRegion;
+ return true;
+ }
+
+ const RegionBand* pSource = rRegion.getRegionBand();
+
+ if(!pSource)
+ {
+ // empty region will not change local content
+ return true;
+ }
+
+ // prepare source and target
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
+
+ // union with source
+ pNew->XOr(*pSource);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+
+ return true;
+}
+
+tools::Rectangle vcl::Region::GetBoundRect() const
+{
+ if(IsEmpty())
+ {
+ // no internal data? -> region is empty!
+ return tools::Rectangle();
+ }
+
+ if(IsNull())
+ {
+ // error; null region has no BoundRect
+ // OSL_ENSURE(false, "Region::GetBoundRect error: null region has unlimited bound rect, not representable (!)");
+ return tools::Rectangle();
+ }
+
+ // prefer double precision source
+ if(getB2DPolyPolygon())
+ {
+ const basegfx::B2DRange aRange(basegfx::utils::getRange(*getB2DPolyPolygon()));
+
+ if(aRange.isEmpty())
+ {
+ // emulate PolyPolygon::GetBoundRect() when empty polygon
+ return tools::Rectangle();
+ }
+ else
+ {
+ // #i122149# corrected rounding, no need for ceil() and floor() here
+ return tools::Rectangle(
+ basegfx::fround(aRange.getMinX()), basegfx::fround(aRange.getMinY()),
+ basegfx::fround(aRange.getMaxX()), basegfx::fround(aRange.getMaxY()));
+ }
+ }
+
+ if(getPolyPolygon())
+ {
+ return getPolyPolygon()->GetBoundRect();
+ }
+
+ if(getRegionBand())
+ {
+ return getRegionBand()->GetBoundRect();
+ }
+
+ return tools::Rectangle();
+}
+
+tools::PolyPolygon vcl::Region::GetAsPolyPolygon() const
+{
+ if(getPolyPolygon())
+ {
+ return *getPolyPolygon();
+ }
+
+ if(getB2DPolyPolygon())
+ {
+ // the polygon needs to be converted, buffer the down conversion
+ const tools::PolyPolygon aPolyPolgon(*getB2DPolyPolygon());
+ const_cast< vcl::Region* >(this)->mpPolyPolygon = aPolyPolgon;
+
+ return *getPolyPolygon();
+ }
+
+ if(getRegionBand())
+ {
+ // the BandRegion needs to be converted, buffer the conversion
+ const tools::PolyPolygon aPolyPolgon(ImplCreatePolyPolygonFromRegionBand());
+ const_cast< vcl::Region* >(this)->mpPolyPolygon = aPolyPolgon;
+
+ return *getPolyPolygon();
+ }
+
+ return tools::PolyPolygon();
+}
+
+basegfx::B2DPolyPolygon vcl::Region::GetAsB2DPolyPolygon() const
+{
+ if(getB2DPolyPolygon())
+ {
+ return *getB2DPolyPolygon();
+ }
+
+ if(getPolyPolygon())
+ {
+ // the polygon needs to be converted, buffer the up conversion. This will be preferred from now.
+ const basegfx::B2DPolyPolygon aB2DPolyPolygon(getPolyPolygon()->getB2DPolyPolygon());
+ const_cast< vcl::Region* >(this)->mpB2DPolyPolygon = aB2DPolyPolygon;
+
+ return *getB2DPolyPolygon();
+ }
+
+ if(getRegionBand())
+ {
+ // the BandRegion needs to be converted, buffer the conversion
+ const basegfx::B2DPolyPolygon aB2DPolyPolygon(ImplCreateB2DPolyPolygonFromRegionBand());
+ const_cast< vcl::Region* >(this)->mpB2DPolyPolygon = aB2DPolyPolygon;
+
+ return *getB2DPolyPolygon();
+ }
+
+ return basegfx::B2DPolyPolygon();
+}
+
+const RegionBand* vcl::Region::GetAsRegionBand() const
+{
+ if(!getRegionBand())
+ {
+ if(getB2DPolyPolygon())
+ {
+ // convert B2DPolyPolygon to RegionBand, buffer it and return it
+ const_cast< vcl::Region* >(this)->mpRegionBand = ImplCreateRegionBandFromPolyPolygon(tools::PolyPolygon(*getB2DPolyPolygon()));
+ }
+ else if(getPolyPolygon())
+ {
+ // convert B2DPolyPolygon to RegionBand, buffer it and return it
+ const_cast< vcl::Region* >(this)->mpRegionBand = ImplCreateRegionBandFromPolyPolygon(*getPolyPolygon());
+ }
+ }
+
+ return getRegionBand();
+}
+
+bool vcl::Region::Contains( const Point& rPoint ) const
+{
+ if(IsEmpty())
+ {
+ // no point can be in empty region
+ return false;
+ }
+
+ if(IsNull())
+ {
+ // all points are inside null-region
+ return true;
+ }
+
+ // Too expensive (?)
+ //if(mpImplRegion->getRegionPolyPoly())
+ //{
+ // return mpImplRegion->getRegionPolyPoly()->Contains( rPoint );
+ //}
+
+ // ensure RegionBand existence
+ const RegionBand* pRegionBand = GetAsRegionBand();
+
+ if(pRegionBand)
+ {
+ return pRegionBand->Contains(rPoint);
+ }
+
+ return false;
+}
+
+bool vcl::Region::Overlaps( const tools::Rectangle& rRect ) const
+{
+ if(IsEmpty())
+ {
+ // nothing can be over something empty
+ return false;
+ }
+
+ if(IsNull())
+ {
+ // everything is over null region
+ return true;
+ }
+
+ // Can we optimize this ??? - is used in StarDraw for brushes pointers
+ // Why we have no IsOver for Regions ???
+ // create region from rectangle and intersect own region
+ vcl::Region aRegion(rRect);
+ aRegion.Intersect( *this );
+
+ // rectangle is over if include is not empty
+ return !aRegion.IsEmpty();
+}
+
+bool vcl::Region::IsRectangle() const
+{
+ if( IsEmpty() || IsNull() )
+ return false;
+
+ if( getB2DPolyPolygon() )
+ return basegfx::utils::isRectangle( *getB2DPolyPolygon() );
+
+ if( getPolyPolygon() )
+ return getPolyPolygon()->IsRect();
+
+ if( getRegionBand() )
+ return (getRegionBand()->getRectangleCount() == 1);
+
+ return false;
+}
+
+void vcl::Region::SetNull()
+{
+ // reset all content
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ mbIsNull = true;
+}
+
+void vcl::Region::SetEmpty()
+{
+ // reset all content
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ mbIsNull = false;
+}
+
+Region& vcl::Region::operator=( const vcl::Region& ) = default;
+
+Region& vcl::Region::operator=( vcl::Region&& rRegion ) noexcept
+{
+ mpB2DPolyPolygon = std::move(rRegion.mpB2DPolyPolygon);
+ mpPolyPolygon = std::move(rRegion.mpPolyPolygon);
+ mpRegionBand = std::move(rRegion.mpRegionBand);
+ mbIsNull = rRegion.mbIsNull;
+ rRegion.mbIsNull = true;
+
+ return *this;
+}
+
+Region& vcl::Region::operator=( const tools::Rectangle& rRect )
+{
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset(rRect.IsEmpty() ? nullptr : new RegionBand(rRect));
+ mbIsNull = false;
+
+ return *this;
+}
+
+bool vcl::Region::operator==( const vcl::Region& rRegion ) const
+{
+ if(IsNull() && rRegion.IsNull())
+ {
+ // both are null region
+ return true;
+ }
+
+ if(IsEmpty() && rRegion.IsEmpty())
+ {
+ // both are empty
+ return true;
+ }
+
+ if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion.getB2DPolyPolygon())
+ {
+ // same instance data? -> equal
+ return true;
+ }
+
+ if(getPolyPolygon() && getPolyPolygon() == rRegion.getPolyPolygon())
+ {
+ // same instance data? -> equal
+ return true;
+ }
+
+ if(getRegionBand() && getRegionBand() == rRegion.getRegionBand())
+ {
+ // same instance data? -> equal
+ return true;
+ }
+
+ if(IsNull() || IsEmpty())
+ {
+ return false;
+ }
+
+ if(rRegion.IsNull() || rRegion.IsEmpty())
+ {
+ return false;
+ }
+
+ if(rRegion.getB2DPolyPolygon() || getB2DPolyPolygon())
+ {
+ // one of both has a B2DPolyPolygon based region, ensure both have it
+ // by evtl. conversion
+ GetAsB2DPolyPolygon();
+ rRegion.GetAsB2DPolyPolygon();
+
+ return *rRegion.getB2DPolyPolygon() == *getB2DPolyPolygon();
+ }
+
+ if(rRegion.getPolyPolygon() || getPolyPolygon())
+ {
+ // one of both has a B2DPolyPolygon based region, ensure both have it
+ // by evtl. conversion
+ GetAsPolyPolygon();
+ rRegion.GetAsPolyPolygon();
+
+ return *rRegion.getPolyPolygon() == *getPolyPolygon();
+ }
+
+ // both are not empty or null (see above) and if content supported polygon
+ // data the comparison is already done. Only both on RegionBand base can be left,
+ // but better check
+ if(rRegion.getRegionBand() && getRegionBand())
+ {
+ return *rRegion.getRegionBand() == *getRegionBand();
+ }
+
+ // should not happen, but better deny equality
+ return false;
+}
+
+SvStream& ReadRegion(SvStream& rIStrm, vcl::Region& rRegion)
+{
+ VersionCompatRead aCompat(rIStrm);
+ sal_uInt16 nVersion(0);
+ sal_uInt16 nTmp16(0);
+
+ // clear region to be loaded
+ rRegion.SetEmpty();
+
+ // get version of streamed region
+ rIStrm.ReadUInt16( nVersion );
+
+ // get type of region
+ rIStrm.ReadUInt16( nTmp16 );
+
+ enum RegionType { REGION_NULL, REGION_EMPTY, REGION_RECTANGLE, REGION_COMPLEX };
+ auto eStreamedType = nTmp16;
+
+ switch (eStreamedType)
+ {
+ case REGION_NULL:
+ {
+ rRegion.SetNull();
+ break;
+ }
+
+ case REGION_EMPTY:
+ {
+ rRegion.SetEmpty();
+ break;
+ }
+
+ default:
+ {
+ std::shared_ptr<RegionBand> xNewRegionBand(std::make_shared<RegionBand>());
+ bool bSuccess = xNewRegionBand->load(rIStrm);
+ rRegion.mpRegionBand = xNewRegionBand;
+
+ bool bHasPolyPolygon(false);
+ if (aCompat.GetVersion() >= 2)
+ {
+ rIStrm.ReadCharAsBool( bHasPolyPolygon );
+
+ if (bHasPolyPolygon)
+ {
+ tools::PolyPolygon aNewPoly;
+ ReadPolyPolygon(rIStrm, aNewPoly);
+ const auto nPolygons = aNewPoly.Count();
+ if (nPolygons > 128)
+ {
+ SAL_WARN("vcl.gdi", "suspiciously high no of polygons in clip:" << nPolygons);
+ if (utl::ConfigManager::IsFuzzing())
+ aNewPoly.Clear();
+ }
+ rRegion.mpPolyPolygon = aNewPoly;
+ }
+ }
+
+ if (!bSuccess && !bHasPolyPolygon)
+ {
+ SAL_WARN("vcl.gdi", "bad region band:" << bHasPolyPolygon);
+ rRegion.SetNull();
+ }
+
+ break;
+ }
+ }
+
+ return rIStrm;
+}
+
+SvStream& WriteRegion( SvStream& rOStrm, const vcl::Region& rRegion )
+{
+ const sal_uInt16 nVersion(2);
+ VersionCompatWrite aCompat(rOStrm, nVersion);
+
+ // put version
+ rOStrm.WriteUInt16( nVersion );
+
+ // put type
+ enum RegionType { REGION_NULL, REGION_EMPTY, REGION_RECTANGLE, REGION_COMPLEX };
+ RegionType aRegionType(REGION_COMPLEX);
+ bool bEmpty(rRegion.IsEmpty());
+
+ if(!bEmpty && rRegion.getB2DPolyPolygon() && 0 == rRegion.getB2DPolyPolygon()->count())
+ {
+ OSL_ENSURE(false, "Region with empty B2DPolyPolygon, should not be created (!)");
+ bEmpty = true;
+ }
+
+ if(!bEmpty && rRegion.getPolyPolygon() && 0 == rRegion.getPolyPolygon()->Count())
+ {
+ OSL_ENSURE(false, "Region with empty PolyPolygon, should not be created (!)");
+ bEmpty = true;
+ }
+
+ if(bEmpty)
+ {
+ aRegionType = REGION_EMPTY;
+ }
+ else if(rRegion.IsNull())
+ {
+ aRegionType = REGION_NULL;
+ }
+ else if(rRegion.getRegionBand() && rRegion.getRegionBand()->isSingleRectangle())
+ {
+ aRegionType = REGION_RECTANGLE;
+ }
+
+ rOStrm.WriteUInt16( aRegionType );
+
+ // get RegionBand
+ const RegionBand* pRegionBand = rRegion.getRegionBand();
+
+ if(pRegionBand)
+ {
+ pRegionBand->save(rOStrm);
+ }
+ else
+ {
+ // for compatibility, write an empty RegionBand (will only write
+ // the end marker STREAMENTRY_END, but this *is* needed)
+ const RegionBand aRegionBand;
+
+ aRegionBand.save(rOStrm);
+ }
+
+ // write polypolygon if available
+ const bool bHasPolyPolygon(rRegion.HasPolyPolygonOrB2DPolyPolygon());
+ rOStrm.WriteBool( bHasPolyPolygon );
+
+ if(bHasPolyPolygon)
+ {
+ // #i105373#
+ tools::PolyPolygon aNoCurvePolyPolygon;
+ rRegion.GetAsPolyPolygon().AdaptiveSubdivide(aNoCurvePolyPolygon);
+
+ WritePolyPolygon( rOStrm, aNoCurvePolyPolygon );
+ }
+
+ return rOStrm;
+}
+
+void vcl::Region::GetRegionRectangles(RectangleVector& rTarget) const
+{
+ // clear returnvalues
+ rTarget.clear();
+
+ // ensure RegionBand existence
+ const RegionBand* pRegionBand = GetAsRegionBand();
+
+ if(pRegionBand)
+ {
+ pRegionBand->GetRegionRectangles(rTarget);
+ }
+}
+
+static bool ImplPolygonRectTest( const tools::Polygon& rPoly, tools::Rectangle* pRectOut = nullptr )
+{
+ bool bIsRect = false;
+ const Point* pPoints = rPoly.GetConstPointAry();
+ sal_uInt16 nPoints = rPoly.GetSize();
+
+ if( nPoints == 4 || (nPoints == 5 && pPoints[0] == pPoints[4]) )
+ {
+ tools::Long nX1 = pPoints[0].X(), nX2 = pPoints[2].X(), nY1 = pPoints[0].Y(), nY2 = pPoints[2].Y();
+
+ if( ( (pPoints[1].X() == nX1 && pPoints[3].X() == nX2) && (pPoints[1].Y() == nY2 && pPoints[3].Y() == nY1) )
+ || ( (pPoints[1].X() == nX2 && pPoints[3].X() == nX1) && (pPoints[1].Y() == nY1 && pPoints[3].Y() == nY2) ) )
+ {
+ bIsRect = true;
+
+ if( pRectOut )
+ {
+ tools::Long nSwap;
+
+ if( nX2 < nX1 )
+ {
+ nSwap = nX2;
+ nX2 = nX1;
+ nX1 = nSwap;
+ }
+
+ if( nY2 < nY1 )
+ {
+ nSwap = nY2;
+ nY2 = nY1;
+ nY1 = nSwap;
+ }
+
+ if( nX2 != nX1 )
+ {
+ nX2--;
+ }
+
+ if( nY2 != nY1 )
+ {
+ nY2--;
+ }
+
+ pRectOut->SetLeft( nX1 );
+ pRectOut->SetRight( nX2 );
+ pRectOut->SetTop( nY1 );
+ pRectOut->SetBottom( nY2 );
+ }
+ }
+ }
+
+ return bIsRect;
+}
+
+vcl::Region vcl::Region::GetRegionFromPolyPolygon( const tools::PolyPolygon& rPolyPoly )
+{
+ //return vcl::Region( rPolyPoly );
+
+ // check if it's worth extracting the XOr'ing the Rectangles
+ // empiricism shows that break even between XOr'ing rectangles separately
+ // and ImplCreateRegionBandFromPolyPolygon is at half rectangles/half polygons
+ int nPolygonRects = 0, nPolygonPolygons = 0;
+ int nPolygons = rPolyPoly.Count();
+
+ for( int i = 0; i < nPolygons; i++ )
+ {
+ const tools::Polygon& rPoly = rPolyPoly[i];
+
+ if( ImplPolygonRectTest( rPoly ) )
+ {
+ nPolygonRects++;
+ }
+ else
+ {
+ nPolygonPolygons++;
+ }
+ }
+
+ if( nPolygonPolygons > nPolygonRects )
+ {
+ return vcl::Region( rPolyPoly );
+ }
+
+ vcl::Region aResult;
+ tools::Rectangle aRect;
+
+ for( int i = 0; i < nPolygons; i++ )
+ {
+ const tools::Polygon& rPoly = rPolyPoly[i];
+
+ if( ImplPolygonRectTest( rPoly, &aRect ) )
+ {
+ aResult.XOr( aRect );
+ }
+ else
+ {
+ aResult.XOr( vcl::Region(rPoly) );
+ }
+ }
+
+ return aResult;
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/regionband.cxx b/vcl/source/gdi/regionband.cxx
new file mode 100644
index 000000000..e772d5b27
--- /dev/null
+++ b/vcl/source/gdi/regionband.cxx
@@ -0,0 +1,1362 @@
+/* -*- 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 <cstdlib>
+
+#include <tools/stream.hxx>
+#include <regionband.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+RegionBand::RegionBand()
+: mpFirstBand(nullptr),
+ mpLastCheckedBand(nullptr)
+{
+}
+
+RegionBand::RegionBand(const RegionBand& rRef)
+: mpFirstBand(nullptr),
+ mpLastCheckedBand(nullptr)
+{
+ *this = rRef;
+}
+
+RegionBand& RegionBand::operator=(const RegionBand& rRef)
+{
+ if (this != &rRef)
+ {
+ ImplRegionBand* pPrevBand = nullptr;
+ ImplRegionBand* pBand = rRef.mpFirstBand;
+
+ while(pBand)
+ {
+ ImplRegionBand* pNewBand = new ImplRegionBand(*pBand);
+
+ // first element? -> set as first into the list
+ if(pBand == rRef.mpFirstBand)
+ {
+ mpFirstBand = pNewBand;
+ }
+ else
+ {
+ pPrevBand->mpNextBand = pNewBand;
+ }
+
+ pPrevBand = pNewBand;
+ pBand = pBand->mpNextBand;
+ }
+ }
+ return *this;
+}
+
+RegionBand::RegionBand(const tools::Rectangle& rRect)
+: mpFirstBand(nullptr),
+ mpLastCheckedBand(nullptr)
+{
+ const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));
+ const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
+
+ // add band with boundaries of the rectangle
+ mpFirstBand = new ImplRegionBand(nTop, nBottom);
+
+ // Set left and right boundaries of the band
+ mpFirstBand->Union(nLeft, nRight);
+
+}
+
+void RegionBand::implReset()
+{
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ ImplRegionBand* pTempBand = pBand->mpNextBand;
+ delete pBand;
+ pBand = pTempBand;
+ }
+
+ mpLastCheckedBand = nullptr;
+ mpFirstBand = nullptr;
+}
+
+RegionBand::~RegionBand()
+{
+ implReset();
+}
+
+bool RegionBand::operator==( const RegionBand& rRegionBand ) const
+{
+
+ // initialise pointers
+ ImplRegionBand* pOwnRectBand = mpFirstBand;
+ ImplRegionBandSep* pOwnRectBandSep = pOwnRectBand->mpFirstSep;
+ ImplRegionBand* pSecondRectBand = rRegionBand.mpFirstBand;
+ ImplRegionBandSep* pSecondRectBandSep = pSecondRectBand->mpFirstSep;
+
+ while ( pOwnRectBandSep && pSecondRectBandSep )
+ {
+ // get boundaries of current rectangle
+ tools::Long nOwnXLeft = pOwnRectBandSep->mnXLeft;
+ tools::Long nSecondXLeft = pSecondRectBandSep->mnXLeft;
+
+ if ( nOwnXLeft != nSecondXLeft )
+ {
+ return false;
+ }
+
+ tools::Long nOwnYTop = pOwnRectBand->mnYTop;
+ tools::Long nSecondYTop = pSecondRectBand->mnYTop;
+
+ if ( nOwnYTop != nSecondYTop )
+ {
+ return false;
+ }
+
+ tools::Long nOwnXRight = pOwnRectBandSep->mnXRight;
+ tools::Long nSecondXRight = pSecondRectBandSep->mnXRight;
+
+ if ( nOwnXRight != nSecondXRight )
+ {
+ return false;
+ }
+
+ tools::Long nOwnYBottom = pOwnRectBand->mnYBottom;
+ tools::Long nSecondYBottom = pSecondRectBand->mnYBottom;
+
+ if ( nOwnYBottom != nSecondYBottom )
+ {
+ return false;
+ }
+
+ // get next separation from current band
+ pOwnRectBandSep = pOwnRectBandSep->mpNextSep;
+
+ // no separation found? -> go to next band!
+ if ( !pOwnRectBandSep )
+ {
+ // get next band
+ pOwnRectBand = pOwnRectBand->mpNextBand;
+
+ // get first separation in current band
+ if( pOwnRectBand )
+ {
+ pOwnRectBandSep = pOwnRectBand->mpFirstSep;
+ }
+ }
+
+ // get next separation from current band
+ pSecondRectBandSep = pSecondRectBandSep->mpNextSep;
+
+ // no separation found? -> go to next band!
+ if ( !pSecondRectBandSep )
+ {
+ // get next band
+ pSecondRectBand = pSecondRectBand->mpNextBand;
+
+ // get first separation in current band
+ if( pSecondRectBand )
+ {
+ pSecondRectBandSep = pSecondRectBand->mpFirstSep;
+ }
+ }
+
+ if ( pOwnRectBandSep && !pSecondRectBandSep )
+ {
+ return false;
+ }
+
+ if ( !pOwnRectBandSep && pSecondRectBandSep )
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+namespace {
+
+enum StreamEntryType { STREAMENTRY_BANDHEADER, STREAMENTRY_SEPARATION, STREAMENTRY_END };
+
+}
+
+bool RegionBand::load(SvStream& rIStrm)
+{
+ // clear this instance data
+ implReset();
+
+ // get all bands
+ ImplRegionBand* pCurrBand = nullptr;
+
+ // get header from first element
+ sal_uInt16 nTmp16(STREAMENTRY_END);
+ rIStrm.ReadUInt16(nTmp16);
+
+ if (STREAMENTRY_END == static_cast<StreamEntryType>(nTmp16))
+ return false;
+
+ size_t nRecordsPossible = rIStrm.remainingSize() / (2*sizeof(sal_Int32));
+ if (!nRecordsPossible)
+ {
+ OSL_ENSURE(false, "premature end of region stream" );
+ implReset();
+ return false;
+ }
+
+ do
+ {
+ // insert new band or new separation?
+ if(STREAMENTRY_BANDHEADER == static_cast<StreamEntryType>(nTmp16))
+ {
+ sal_Int32 nYTop(0);
+ sal_Int32 nYBottom(0);
+
+ rIStrm.ReadInt32( nYTop );
+ rIStrm.ReadInt32( nYBottom );
+
+ // create band
+ ImplRegionBand* pNewBand = new ImplRegionBand( nYTop, nYBottom );
+
+ // first element? -> set as first into the list
+ if ( !pCurrBand )
+ {
+ mpFirstBand = pNewBand;
+ }
+ else
+ {
+ pCurrBand->mpNextBand = pNewBand;
+ }
+
+ // save pointer for next creation
+ pCurrBand = pNewBand;
+ }
+ else
+ {
+ sal_Int32 nXLeft(0);
+ sal_Int32 nXRight(0);
+
+ rIStrm.ReadInt32( nXLeft );
+ rIStrm.ReadInt32( nXRight );
+
+ // add separation
+ if ( pCurrBand )
+ {
+ pCurrBand->Union( nXLeft, nXRight );
+ }
+ }
+
+ if( rIStrm.eof() )
+ {
+ OSL_ENSURE(false, "premature end of region stream" );
+ implReset();
+ return false;
+ }
+
+ // get next header
+ rIStrm.ReadUInt16( nTmp16 );
+ }
+ while (STREAMENTRY_END != static_cast<StreamEntryType>(nTmp16) && rIStrm.good());
+ if (!CheckConsistency())
+ {
+ implReset();
+ return false;
+ }
+ return true;
+}
+
+void RegionBand::save(SvStream& rOStrm) const
+{
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ // put boundaries
+ rOStrm.WriteUInt16( STREAMENTRY_BANDHEADER );
+ rOStrm.WriteInt32( pBand->mnYTop );
+ rOStrm.WriteInt32( pBand->mnYBottom );
+
+ // put separations of current band
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while(pSep)
+ {
+ // put separation
+ rOStrm.WriteUInt16( STREAMENTRY_SEPARATION );
+ rOStrm.WriteInt32( pSep->mnXLeft );
+ rOStrm.WriteInt32( pSep->mnXRight );
+
+ // next separation from current band
+ pSep = pSep->mpNextSep;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+ // put endmarker
+ rOStrm.WriteUInt16( STREAMENTRY_END );
+}
+
+bool RegionBand::isSingleRectangle() const
+{
+ // just one band?
+ if(mpFirstBand && !mpFirstBand->mpNextBand)
+ {
+ // just one sep?
+ if(mpFirstBand->mpFirstSep && !mpFirstBand->mpFirstSep->mpNextSep)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void RegionBand::InsertBand(ImplRegionBand* pPreviousBand, ImplRegionBand* pBandToInsert)
+{
+ OSL_ASSERT(pBandToInsert!=nullptr);
+
+ if(!pPreviousBand)
+ {
+ // Insert band before all others.
+ if(mpFirstBand)
+ {
+ mpFirstBand->mpPrevBand = pBandToInsert;
+ }
+
+ pBandToInsert->mpNextBand = mpFirstBand;
+ mpFirstBand = pBandToInsert;
+ }
+ else
+ {
+ // Insert band directly after pPreviousBand.
+ pBandToInsert->mpNextBand = pPreviousBand->mpNextBand;
+ pPreviousBand->mpNextBand = pBandToInsert;
+ pBandToInsert->mpPrevBand = pPreviousBand;
+ }
+
+}
+
+void RegionBand::processPoints()
+{
+ ImplRegionBand* pRegionBand = mpFirstBand;
+
+ while(pRegionBand)
+ {
+ // generate separations from the lines and process union
+ pRegionBand->ProcessPoints();
+ pRegionBand = pRegionBand->mpNextBand;
+ }
+
+}
+
+/** This function is similar to the RegionBand::InsertBands() method.
+ It creates a minimal set of missing bands so that the entire vertical
+ interval from nTop to nBottom is covered by bands.
+*/
+void RegionBand::ImplAddMissingBands(const tools::Long nTop, const tools::Long nBottom)
+{
+ // Iterate over already existing bands and add missing bands atop the
+ // first and between two bands.
+ ImplRegionBand* pPreviousBand = nullptr;
+ ImplRegionBand* pBand = ImplGetFirstRegionBand();
+ tools::Long nCurrentTop (nTop);
+
+ while (pBand != nullptr && nCurrentTop<nBottom)
+ {
+ if (nCurrentTop < pBand->mnYTop)
+ {
+ // Create new band above the current band.
+ ImplRegionBand* pAboveBand = new ImplRegionBand(
+ nCurrentTop,
+ ::std::min(nBottom,pBand->mnYTop-1));
+ InsertBand(pPreviousBand, pAboveBand);
+ }
+
+ // Adapt the top of the interval to prevent overlapping bands.
+ nCurrentTop = ::std::max(nTop, pBand->mnYBottom+1);
+
+ // Advance to next band.
+ pPreviousBand = pBand;
+ pBand = pBand->mpNextBand;
+ }
+
+ // We still have to cover two cases:
+ // 1. The region does not yet contain any bands.
+ // 2. The interval nTop->nBottom extends past the bottom most band.
+ if (nCurrentTop <= nBottom
+ && (pBand==nullptr || nBottom>pBand->mnYBottom))
+ {
+ // When there is no previous band then the new one will be the
+ // first. Otherwise the new band is inserted behind the last band.
+ InsertBand(
+ pPreviousBand,
+ new ImplRegionBand(
+ nCurrentTop,
+ nBottom));
+ }
+
+}
+
+void RegionBand::CreateBandRange(tools::Long nYTop, tools::Long nYBottom)
+{
+ // add top band
+ mpFirstBand = new ImplRegionBand( nYTop-1, nYTop-1 );
+
+ // begin first search from the first element
+ mpLastCheckedBand = mpFirstBand;
+ ImplRegionBand* pBand = mpFirstBand;
+
+ for ( tools::Long i = nYTop; i <= nYBottom+1; i++ )
+ {
+ // create new band
+ ImplRegionBand* pNewBand = new ImplRegionBand( i, i );
+ pBand->mpNextBand = pNewBand;
+
+ if ( pBand != mpFirstBand )
+ {
+ pNewBand->mpPrevBand = pBand;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::InsertLine(const Point& rStartPt, const Point& rEndPt, tools::Long nLineId)
+{
+ tools::Long nX, nY;
+
+ // lines consisting of a single point do not interest here
+ if ( rStartPt == rEndPt )
+ {
+ return;
+ }
+
+ LineType eLineType = (rStartPt.Y() > rEndPt.Y()) ? LineType::Descending : LineType::Ascending;
+ if ( rStartPt.X() == rEndPt.X() )
+ {
+ // vertical line
+ const tools::Long nEndY = rEndPt.Y();
+
+ nX = rStartPt.X();
+ nY = rStartPt.Y();
+
+ if( nEndY > nY )
+ {
+ for ( ; nY <= nEndY; nY++ )
+ {
+ Point aNewPoint( nX, nY );
+ InsertPoint( aNewPoint, nLineId,
+ (aNewPoint == rEndPt) || (aNewPoint == rStartPt),
+ eLineType );
+ }
+ }
+ else
+ {
+ for ( ; nY >= nEndY; nY-- )
+ {
+ Point aNewPoint( nX, nY );
+ InsertPoint( aNewPoint, nLineId,
+ (aNewPoint == rEndPt) || (aNewPoint == rStartPt),
+ eLineType );
+ }
+ }
+ }
+ else if ( rStartPt.Y() != rEndPt.Y() )
+ {
+ const tools::Long nDX = std::abs( rEndPt.X() - rStartPt.X() );
+ const tools::Long nDY = std::abs( rEndPt.Y() - rStartPt.Y() );
+ const tools::Long nStartX = rStartPt.X();
+ const tools::Long nStartY = rStartPt.Y();
+ const tools::Long nEndX = rEndPt.X();
+ const tools::Long nEndY = rEndPt.Y();
+ const tools::Long nXInc = ( nStartX < nEndX ) ? 1 : -1;
+ const tools::Long nYInc = ( nStartY < nEndY ) ? 1 : -1;
+
+ if ( nDX >= nDY )
+ {
+ const tools::Long nDYX = ( nDY - nDX ) * 2;
+ const tools::Long nDY2 = nDY << 1;
+ tools::Long nD = nDY2 - nDX;
+
+ for ( nX = nStartX, nY = nStartY; nX != nEndX; nX += nXInc )
+ {
+ InsertPoint( Point( nX, nY ), nLineId, nStartX == nX, eLineType );
+
+ if ( nD < 0 )
+ nD += nDY2;
+ else
+ {
+ nD += nDYX;
+ nY += nYInc;
+ }
+ }
+ }
+ else
+ {
+ const tools::Long nDYX = ( nDX - nDY ) * 2;
+ const tools::Long nDY2 = nDX << 1;
+ tools::Long nD = nDY2 - nDY;
+
+ for ( nX = nStartX, nY = nStartY; nY != nEndY; nY += nYInc )
+ {
+ InsertPoint( Point( nX, nY ), nLineId, nStartY == nY, eLineType );
+
+ if ( nD < 0 )
+ nD += nDY2;
+ else
+ {
+ nD += nDYX;
+ nX += nXInc;
+ }
+ }
+ }
+
+ // last point
+ InsertPoint( Point( nEndX, nEndY ), nLineId, true, eLineType );
+ }
+}
+
+void RegionBand::InsertPoint(const Point &rPoint, tools::Long nLineID, bool bEndPoint, LineType eLineType)
+{
+ SAL_WARN_IF( mpFirstBand == nullptr, "vcl", "RegionBand::InsertPoint - no bands available!" );
+
+ if ( rPoint.Y() == mpLastCheckedBand->mnYTop )
+ {
+ mpLastCheckedBand->InsertPoint( rPoint.X(), nLineID, bEndPoint, eLineType );
+ return;
+ }
+
+ if ( rPoint.Y() > mpLastCheckedBand->mnYTop )
+ {
+ // Search ascending
+ while ( mpLastCheckedBand )
+ {
+ // Insert point if possible
+ if ( rPoint.Y() == mpLastCheckedBand->mnYTop )
+ {
+ mpLastCheckedBand->InsertPoint( rPoint.X(), nLineID, bEndPoint, eLineType );
+ return;
+ }
+
+ mpLastCheckedBand = mpLastCheckedBand->mpNextBand;
+ }
+
+ OSL_ENSURE(false, "RegionBand::InsertPoint reached the end of the list!" );
+ }
+ else
+ {
+ // Search descending
+ while ( mpLastCheckedBand )
+ {
+ // Insert point if possible
+ if ( rPoint.Y() == mpLastCheckedBand->mnYTop )
+ {
+ mpLastCheckedBand->InsertPoint( rPoint.X(), nLineID, bEndPoint, eLineType );
+ return;
+ }
+
+ mpLastCheckedBand = mpLastCheckedBand->mpPrevBand;
+ }
+
+ OSL_ENSURE(false, "RegionBand::InsertPoint reached the beginning of the list!" );
+ }
+
+ OSL_ENSURE(false, "RegionBand::InsertPoint point not inserted!" );
+
+ // reinitialize pointer (should never be reached!)
+ mpLastCheckedBand = mpFirstBand;
+}
+
+bool RegionBand::OptimizeBandList()
+{
+ ImplRegionBand* pPrevBand = nullptr;
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while ( pBand )
+ {
+ const bool bBTEqual = pBand->mpNextBand && (pBand->mnYBottom == pBand->mpNextBand->mnYTop);
+
+ // no separation? -> remove!
+ if ( pBand->IsEmpty() || (bBTEqual && (pBand->mnYBottom == pBand->mnYTop)) )
+ {
+ // save pointer
+ ImplRegionBand* pOldBand = pBand;
+
+ // previous element of the list
+ if ( pBand == mpFirstBand )
+ mpFirstBand = pBand->mpNextBand;
+ else
+ pPrevBand->mpNextBand = pBand->mpNextBand;
+
+ pBand = pBand->mpNextBand;
+ delete pOldBand;
+ }
+ else
+ {
+ // fixup
+ if ( bBTEqual )
+ pBand->mnYBottom = pBand->mpNextBand->mnYTop-1;
+
+ // this and next band with equal separations? -> combine!
+ if ( pBand->mpNextBand &&
+ ((pBand->mnYBottom+1) == pBand->mpNextBand->mnYTop) &&
+ (*pBand == *pBand->mpNextBand) )
+ {
+ // expand current height
+ pBand->mnYBottom = pBand->mpNextBand->mnYBottom;
+
+ // remove next band from list
+ ImplRegionBand* pDeletedBand = pBand->mpNextBand;
+ pBand->mpNextBand = pDeletedBand->mpNextBand;
+ delete pDeletedBand;
+
+ // check band again!
+ }
+ else
+ {
+ // count rectangles within band
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+ while ( pSep )
+ {
+ pSep = pSep->mpNextSep;
+ }
+
+ pPrevBand = pBand;
+ pBand = pBand->mpNextBand;
+ }
+ }
+ }
+
+#ifdef DBG_UTIL
+ pBand = mpFirstBand;
+ while ( pBand )
+ {
+ SAL_WARN_IF( pBand->mpFirstSep == nullptr, "vcl", "Exiting RegionBand::OptimizeBandList(): empty band in region!" );
+
+ if ( pBand->mnYBottom < pBand->mnYTop )
+ OSL_ENSURE(false, "RegionBand::OptimizeBandList(): YBottomBoundary < YTopBoundary" );
+
+ if ( pBand->mpNextBand && pBand->mnYBottom >= pBand->mpNextBand->mnYTop )
+ OSL_ENSURE(false, "RegionBand::OptimizeBandList(): overlapping bands in region!" );
+
+ pBand = pBand->mpNextBand;
+ }
+#endif
+
+ return (nullptr != mpFirstBand);
+}
+
+void RegionBand::Move(tools::Long nHorzMove, tools::Long nVertMove)
+{
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ // process the vertical move
+ if(nVertMove)
+ {
+ pBand->mnYTop = pBand->mnYTop + nVertMove;
+ pBand->mnYBottom = pBand->mnYBottom + nVertMove;
+ }
+
+ // process the horizontal move
+ if(nHorzMove)
+ {
+ pBand->MoveX(nHorzMove);
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::Scale(double fScaleX, double fScaleY)
+{
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ // process the vertical move
+ if(0.0 != fScaleY)
+ {
+ pBand->mnYTop = basegfx::fround(pBand->mnYTop * fScaleY);
+ pBand->mnYBottom = basegfx::fround(pBand->mnYBottom * fScaleY);
+ }
+
+ // process the horizontal move
+ if(0.0 != fScaleX)
+ {
+ pBand->ScaleX(fScaleX);
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::InsertBands(tools::Long nTop, tools::Long nBottom)
+{
+ // region empty? -> set rectangle as first entry!
+ if ( !mpFirstBand )
+ {
+ // add band with boundaries of the rectangle
+ mpFirstBand = new ImplRegionBand( nTop, nBottom );
+ return;
+ }
+
+ // find/insert bands for the boundaries of the rectangle
+ bool bTopBoundaryInserted = false;
+ bool bTop2BoundaryInserted = false;
+ bool bBottomBoundaryInserted = false;
+
+ // special case: top boundary is above the first band
+ ImplRegionBand* pNewBand;
+
+ if ( nTop < mpFirstBand->mnYTop )
+ {
+ // create new band above the first in the list
+ pNewBand = new ImplRegionBand( nTop, mpFirstBand->mnYTop );
+
+ if ( nBottom < mpFirstBand->mnYTop )
+ {
+ pNewBand->mnYBottom = nBottom;
+ }
+
+ // insert band into the list
+ pNewBand->mpNextBand = mpFirstBand;
+ mpFirstBand = pNewBand;
+
+ bTopBoundaryInserted = true;
+ }
+
+ // insert band(s) into the list
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while ( pBand )
+ {
+ // Insert Bands if possible
+ if ( !bTopBoundaryInserted )
+ {
+ bTopBoundaryInserted = InsertSingleBand( pBand, nTop - 1 );
+ }
+
+ if ( !bTop2BoundaryInserted )
+ {
+ bTop2BoundaryInserted = InsertSingleBand( pBand, nTop );
+ }
+
+ if ( !bBottomBoundaryInserted && (nTop != nBottom) )
+ {
+ bBottomBoundaryInserted = InsertSingleBand( pBand, nBottom );
+ }
+
+ // both boundaries inserted? -> nothing more to do
+ if ( bTopBoundaryInserted && bTop2BoundaryInserted && bBottomBoundaryInserted )
+ {
+ break;
+ }
+
+ // insert bands between two bands if necessary
+ if ( pBand->mpNextBand )
+ {
+ if ( (pBand->mnYBottom + 1) < pBand->mpNextBand->mnYTop )
+ {
+ // copy band with list and set new boundary
+ pNewBand = new ImplRegionBand( pBand->mnYBottom+1, pBand->mpNextBand->mnYTop-1 );
+
+ // insert band into the list
+ pNewBand->mpNextBand = pBand->mpNextBand;
+ pBand->mpNextBand = pNewBand;
+ }
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+bool RegionBand::InsertSingleBand(ImplRegionBand* pBand, tools::Long nYBandPosition)
+{
+ // boundary already included in band with height 1? -> nothing to do!
+ if ( (pBand->mnYTop == pBand->mnYBottom) && (nYBandPosition == pBand->mnYTop) )
+ {
+ return true;
+ }
+
+ // insert single height band on top?
+ ImplRegionBand* pNewBand;
+
+ if ( nYBandPosition == pBand->mnYTop )
+ {
+ // copy band with list and set new boundary
+ pNewBand = new ImplRegionBand( *pBand );
+ pNewBand->mnYTop = nYBandPosition+1;
+
+ // insert band into the list
+ pNewBand->mpNextBand = pBand->mpNextBand;
+ pBand->mnYBottom = nYBandPosition;
+ pBand->mpNextBand = pNewBand;
+
+ return true;
+ }
+
+ // top of new rectangle within the current band? -> insert new band and copy data
+ if ( (nYBandPosition > pBand->mnYTop) && (nYBandPosition < pBand->mnYBottom) )
+ {
+ // copy band with list and set new boundary
+ pNewBand = new ImplRegionBand( *pBand );
+ pNewBand->mnYTop = nYBandPosition;
+
+ // insert band into the list
+ pNewBand->mpNextBand = pBand->mpNextBand;
+ pBand->mnYBottom = nYBandPosition;
+ pBand->mpNextBand = pNewBand;
+
+ // copy band with list and set new boundary
+ pNewBand = new ImplRegionBand( *pBand );
+ pNewBand->mnYTop = nYBandPosition;
+
+ // insert band into the list
+ pBand->mpNextBand->mnYTop = nYBandPosition+1;
+
+ pNewBand->mpNextBand = pBand->mpNextBand;
+ pBand->mnYBottom = nYBandPosition - 1;
+ pBand->mpNextBand = pNewBand;
+
+ return true;
+ }
+
+ // create new band behind the current in the list
+ if ( !pBand->mpNextBand )
+ {
+ if ( nYBandPosition == pBand->mnYBottom )
+ {
+ // copy band with list and set new boundary
+ pNewBand = new ImplRegionBand( *pBand );
+ pNewBand->mnYTop = pBand->mnYBottom;
+ pNewBand->mnYBottom = nYBandPosition;
+
+ pBand->mnYBottom = nYBandPosition-1;
+
+ // append band to the list
+ pBand->mpNextBand = pNewBand;
+ return true;
+ }
+
+ if ( nYBandPosition > pBand->mnYBottom )
+ {
+ // create new band
+ pNewBand = new ImplRegionBand( pBand->mnYBottom + 1, nYBandPosition );
+
+ // append band to the list
+ pBand->mpNextBand = pNewBand;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void RegionBand::Union(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom)
+{
+ SAL_WARN_IF( nLeft > nRight, "vcl", "RegionBand::Union() - nLeft > nRight" );
+ SAL_WARN_IF( nTop > nBottom, "vcl", "RegionBand::Union() - nTop > nBottom" );
+
+ // process union
+ ImplRegionBand* pBand = mpFirstBand;
+ while ( pBand )
+ {
+ if ( pBand->mnYTop >= nTop )
+ {
+ if ( pBand->mnYBottom <= nBottom )
+ pBand->Union( nLeft, nRight );
+ else
+ {
+#ifdef DBG_UTIL
+ tools::Long nCurY = pBand->mnYBottom;
+ pBand = pBand->mpNextBand;
+ while ( pBand )
+ {
+ if ( (pBand->mnYTop < nCurY) || (pBand->mnYBottom < nCurY) )
+ {
+ OSL_ENSURE(false, "RegionBand::Union() - Bands not sorted!" );
+ }
+ pBand = pBand->mpNextBand;
+ }
+#endif
+ break;
+ }
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::Intersect(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom)
+{
+ // process intersections
+ ImplRegionBand* pPrevBand = nullptr;
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ // band within intersection boundary? -> process. otherwise remove
+ if((pBand->mnYTop >= nTop) && (pBand->mnYBottom <= nBottom))
+ {
+ // process intersection
+ pBand->Intersect(nLeft, nRight);
+ pPrevBand = pBand;
+ pBand = pBand->mpNextBand;
+ }
+ else
+ {
+ ImplRegionBand* pOldBand = pBand;
+
+ if(pBand == mpFirstBand)
+ {
+ mpFirstBand = pBand->mpNextBand;
+ }
+ else
+ {
+ pPrevBand->mpNextBand = pBand->mpNextBand;
+ }
+
+ pBand = pBand->mpNextBand;
+ delete pOldBand;
+ }
+ }
+
+}
+
+void RegionBand::Union(const RegionBand& rSource)
+{
+ // apply all rectangles from rSource to this
+ ImplRegionBand* pBand = rSource.mpFirstBand;
+
+ while ( pBand )
+ {
+ // insert bands if the boundaries are not already in the list
+ InsertBands(pBand->mnYTop, pBand->mnYBottom);
+
+ // process all elements of the list
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while(pSep)
+ {
+ Union(pSep->mnXLeft, pBand->mnYTop, pSep->mnXRight, pBand->mnYBottom);
+ pSep = pSep->mpNextSep;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::Exclude(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom)
+{
+ SAL_WARN_IF( nLeft > nRight, "vcl", "RegionBand::Exclude() - nLeft > nRight" );
+ SAL_WARN_IF( nTop > nBottom, "vcl", "RegionBand::Exclude() - nTop > nBottom" );
+
+ // process exclude
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ if(pBand->mnYTop >= nTop)
+ {
+ if(pBand->mnYBottom <= nBottom)
+ {
+ pBand->Exclude(nLeft, nRight);
+ }
+ else
+ {
+#ifdef DBG_UTIL
+ tools::Long nCurY = pBand->mnYBottom;
+ pBand = pBand->mpNextBand;
+
+ while(pBand)
+ {
+ if((pBand->mnYTop < nCurY) || (pBand->mnYBottom < nCurY))
+ {
+ OSL_ENSURE(false, "RegionBand::Exclude() - Bands not sorted!" );
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+#endif
+ break;
+ }
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::XOr(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom)
+{
+ SAL_WARN_IF( nLeft > nRight, "vcl", "RegionBand::Exclude() - nLeft > nRight" );
+ SAL_WARN_IF( nTop > nBottom, "vcl", "RegionBand::Exclude() - nTop > nBottom" );
+
+ // process xor
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ if(pBand->mnYTop >= nTop)
+ {
+ if(pBand->mnYBottom <= nBottom)
+ {
+ pBand->XOr(nLeft, nRight);
+ }
+ else
+ {
+#ifdef DBG_UTIL
+ tools::Long nCurY = pBand->mnYBottom;
+ pBand = pBand->mpNextBand;
+
+ while(pBand)
+ {
+ if((pBand->mnYTop < nCurY) || (pBand->mnYBottom < nCurY))
+ {
+ OSL_ENSURE(false, "RegionBand::XOr() - Bands not sorted!" );
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+#endif
+ break;
+ }
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::Intersect(const RegionBand& rSource)
+{
+ // mark all bands as untouched
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while ( pBand )
+ {
+ pBand->mbTouched = false;
+ pBand = pBand->mpNextBand;
+ }
+
+ pBand = rSource.mpFirstBand;
+
+ while ( pBand )
+ {
+ // insert bands if the boundaries are not already in the list
+ InsertBands( pBand->mnYTop, pBand->mnYBottom );
+
+ // process all elements of the list
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while ( pSep )
+ {
+ // left boundary?
+ if ( pSep == pBand->mpFirstSep )
+ {
+ // process intersection and do not remove untouched bands
+ Exclude( LONG_MIN+1, pBand->mnYTop, pSep->mnXLeft-1, pBand->mnYBottom );
+ }
+
+ // right boundary?
+ if ( pSep->mpNextSep == nullptr )
+ {
+ // process intersection and do not remove untouched bands
+ Exclude( pSep->mnXRight+1, pBand->mnYTop, LONG_MAX-1, pBand->mnYBottom );
+ }
+ else
+ {
+ // process intersection and do not remove untouched bands
+ Exclude( pSep->mnXRight+1, pBand->mnYTop, pSep->mpNextSep->mnXLeft-1, pBand->mnYBottom );
+ }
+
+ pSep = pSep->mpNextSep;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+ // remove all untouched bands if bands already left
+ ImplRegionBand* pPrevBand = nullptr;
+ pBand = mpFirstBand;
+
+ while ( pBand )
+ {
+ if ( !pBand->mbTouched )
+ {
+ // save pointer
+ ImplRegionBand* pOldBand = pBand;
+
+ // previous element of the list
+ if ( pBand == mpFirstBand )
+ {
+ mpFirstBand = pBand->mpNextBand;
+ }
+ else
+ {
+ pPrevBand->mpNextBand = pBand->mpNextBand;
+ }
+
+ pBand = pBand->mpNextBand;
+ delete pOldBand;
+ }
+ else
+ {
+ pPrevBand = pBand;
+ pBand = pBand->mpNextBand;
+ }
+ }
+
+}
+
+bool RegionBand::Exclude(const RegionBand& rSource)
+{
+ // apply all rectangles to the region passed to this region
+ ImplRegionBand* pBand = rSource.mpFirstBand;
+
+ while ( pBand )
+ {
+ // insert bands if the boundaries are not already in the list
+ InsertBands( pBand->mnYTop, pBand->mnYBottom );
+
+ // process all elements of the list
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while ( pSep )
+ {
+ Exclude( pSep->mnXLeft, pBand->mnYTop, pSep->mnXRight, pBand->mnYBottom );
+ pSep = pSep->mpNextSep;
+ }
+
+ // to test less bands, already check in the loop
+ if ( !OptimizeBandList() )
+ {
+ return false;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+ return true;
+}
+
+bool RegionBand::CheckConsistency() const
+{
+ if (!mpFirstBand)
+ return true;
+ // look in the band list (don't test first band again!)
+ const ImplRegionBand* pBand = mpFirstBand->mpNextBand;
+ while (pBand)
+ {
+ if (!pBand->mpFirstSep)
+ return false;
+ pBand = pBand->mpNextBand;
+ }
+ return true;
+}
+
+tools::Rectangle RegionBand::GetBoundRect() const
+{
+
+ // get the boundaries of the first band
+ tools::Long nYTop(mpFirstBand->mnYTop);
+ tools::Long nYBottom(mpFirstBand->mnYBottom);
+ tools::Long nXLeft(mpFirstBand->GetXLeftBoundary());
+ tools::Long nXRight(mpFirstBand->GetXRightBoundary());
+
+ // look in the band list (don't test first band again!)
+ ImplRegionBand* pBand = mpFirstBand->mpNextBand;
+
+ while ( pBand )
+ {
+ nYBottom = pBand->mnYBottom;
+ nXLeft = std::min( nXLeft, pBand->GetXLeftBoundary() );
+ nXRight = std::max( nXRight, pBand->GetXRightBoundary() );
+
+ pBand = pBand->mpNextBand;
+ }
+
+ return tools::Rectangle( nXLeft, nYTop, nXRight, nYBottom );
+}
+
+void RegionBand::XOr(const RegionBand& rSource)
+{
+ ImplRegionBand* pBand = rSource.mpFirstBand;
+
+ while ( pBand )
+ {
+ // insert bands if the boundaries are not already in the list
+ InsertBands( pBand->mnYTop, pBand->mnYBottom );
+
+ // process all elements of the list
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while ( pSep )
+ {
+ XOr( pSep->mnXLeft, pBand->mnYTop, pSep->mnXRight, pBand->mnYBottom );
+ pSep = pSep->mpNextSep;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+}
+
+bool RegionBand::Contains(const Point& rPoint) const
+{
+
+ // search band list
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ // is point within band?
+ if((pBand->mnYTop <= rPoint.Y()) && (pBand->mnYBottom >= rPoint.Y()))
+ {
+ // is point within separation of the band?
+ return pBand->Contains(rPoint.X());
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+ return false;
+}
+
+void RegionBand::GetRegionRectangles(RectangleVector& rTarget) const
+{
+ // clear result vector
+ rTarget.clear();
+ ImplRegionBand* pCurrRectBand = mpFirstBand;
+ tools::Rectangle aRectangle;
+
+ while(pCurrRectBand)
+ {
+ ImplRegionBandSep* pCurrRectBandSep = pCurrRectBand->mpFirstSep;
+
+ aRectangle.SetTop( pCurrRectBand->mnYTop );
+ aRectangle.SetBottom( pCurrRectBand->mnYBottom );
+
+ while(pCurrRectBandSep)
+ {
+ aRectangle.SetLeft( pCurrRectBandSep->mnXLeft );
+ aRectangle.SetRight( pCurrRectBandSep->mnXRight );
+ rTarget.push_back(aRectangle);
+ pCurrRectBandSep = pCurrRectBandSep->mpNextSep;
+ }
+
+ pCurrRectBand = pCurrRectBand->mpNextBand;
+ }
+}
+
+sal_uInt32 RegionBand::getRectangleCount() const
+{
+ sal_uInt32 nCount = 0;
+ const ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while(pSep)
+ {
+ nCount++;
+ pSep = pSep->mpNextSep;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+ return nCount;
+}
+
+#ifdef DBG_UTIL
+const char* ImplDbgTestRegionBand(const void* pObj)
+{
+ const RegionBand* pRegionBand = static_cast< const RegionBand* >(pObj);
+
+ if(pRegionBand)
+ {
+ const ImplRegionBand* pBand = pRegionBand->ImplGetFirstRegionBand();
+
+ while(pBand)
+ {
+ if(pBand->mnYBottom < pBand->mnYTop)
+ {
+ return "YBottom < YTop";
+ }
+
+ if(pBand->mpNextBand)
+ {
+ if(pBand->mnYBottom >= pBand->mpNextBand->mnYTop)
+ {
+ return "overlapping bands in region";
+ }
+ }
+
+ if(pBand->mbTouched)
+ {
+ return "Band-mbTouched overwrite";
+ }
+
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while(pSep)
+ {
+ if(pSep->mnXRight < pSep->mnXLeft)
+ {
+ return "XLeft < XRight";
+ }
+
+ if(pSep->mpNextSep)
+ {
+ if(pSep->mnXRight >= pSep->mpNextSep->mnXLeft)
+ {
+ return "overlapping separations in region";
+ }
+ }
+
+ if ( pSep->mbRemoved )
+ {
+ return "Sep-mbRemoved overwrite";
+ }
+
+ pSep = pSep->mpNextSep;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+ }
+
+ return nullptr;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/salgdiimpl.cxx b/vcl/source/gdi/salgdiimpl.cxx
new file mode 100644
index 000000000..622c86c39
--- /dev/null
+++ b/vcl/source/gdi/salgdiimpl.cxx
@@ -0,0 +1,24 @@
+/* -*- 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 <salgdiimpl.hxx>
+
+SalGraphicsImpl::~SalGraphicsImpl() {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/salgdilayout.cxx b/vcl/source/gdi/salgdilayout.cxx
new file mode 100644
index 000000000..c557912d8
--- /dev/null
+++ b/vcl/source/gdi/salgdilayout.cxx
@@ -0,0 +1,1097 @@
+/* -*- 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 <memory>
+#include <config_features.h>
+#include <sal/log.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <fontsubset.hxx>
+#include <salgdi.hxx>
+#include <salframe.hxx>
+#include <sft.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <FileDefinitionWidgetDraw.hxx>
+#include <rtl/math.hxx>
+#include <comphelper/lok.hxx>
+#include <toolbarvalue.hxx>
+
+// The only common SalFrame method
+
+SalFrameGeometry SalFrame::GetGeometry() const
+{
+ // mirror frame coordinates at parent
+ SalFrame *pParent = GetParent();
+ if( pParent && AllSettings::GetLayoutRTL() )
+ {
+ SalFrameGeometry aGeom = maGeometry;
+ int parent_x = aGeom.nX - pParent->maGeometry.nX;
+ aGeom.nX = pParent->maGeometry.nX + pParent->maGeometry.nWidth - maGeometry.nWidth - parent_x;
+ return aGeom;
+ }
+ else
+ return maGeometry;
+}
+
+SalGraphics::SalGraphics()
+: m_nLayout( SalLayoutFlags::NONE ),
+ m_eLastMirrorMode(MirrorMode::NONE),
+ m_nLastMirrorTranslation(0),
+ m_bAntiAlias(false),
+ m_bTextRenderModeForResolutionIndependentLayout(false)
+{
+ // read global RTL settings
+ if( AllSettings::GetLayoutRTL() )
+ m_nLayout = SalLayoutFlags::BiDiRtl;
+}
+
+bool SalGraphics::initWidgetDrawBackends(bool bForce)
+{
+ bool bFileDefinitionsWidgetDraw = !!getenv("VCL_DRAW_WIDGETS_FROM_FILE");
+
+ if (bFileDefinitionsWidgetDraw || bForce)
+ {
+ m_pWidgetDraw.reset(new vcl::FileDefinitionWidgetDraw(*this));
+ auto pFileDefinitionWidgetDraw = static_cast<vcl::FileDefinitionWidgetDraw*>(m_pWidgetDraw.get());
+ if (!pFileDefinitionWidgetDraw->isActive())
+ {
+ m_pWidgetDraw.reset();
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+SalGraphics::~SalGraphics() COVERITY_NOEXCEPT_FALSE
+{
+ // can't call ReleaseFonts here, as the destructor just calls this classes SetFont (pure virtual)!
+}
+
+bool SalGraphics::drawTransformedBitmap(
+ const basegfx::B2DPoint& /* rNull */,
+ const basegfx::B2DPoint& /* rX */,
+ const basegfx::B2DPoint& /* rY */,
+ const SalBitmap& /* rSourceBitmap */,
+ const SalBitmap* /* pAlphaBitmap */,
+ double /* fAlpha */)
+{
+ // here direct support for transformed bitmaps can be implemented
+ return false;
+}
+
+tools::Long SalGraphics::mirror2( tools::Long x, const OutputDevice& rOutDev ) const
+{
+ mirror(x, rOutDev);
+ return x;
+}
+
+inline tools::Long SalGraphics::GetDeviceWidth(const OutputDevice& rOutDev) const
+{
+ return rOutDev.IsVirtual() ? rOutDev.GetOutputWidthPixel() : GetGraphicsWidth();
+}
+
+void SalGraphics::mirror( tools::Long& x, const OutputDevice& rOutDev ) const
+{
+ const tools::Long w = GetDeviceWidth(rOutDev);
+ if( !w )
+ return;
+
+ if (rOutDev.ImplIsAntiparallel() )
+ {
+ // mirror this window back
+ if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ tools::Long devX = w - rOutDev.GetOutputWidthPixel() - rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ x = devX + (x - rOutDev.GetOutOffXPixel());
+ }
+ else
+ {
+ tools::Long devX = rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ x = rOutDev.GetOutputWidthPixel() - (x - devX) + rOutDev.GetOutOffXPixel() - 1;
+ }
+ }
+ else if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ x = w-1-x;
+}
+
+void SalGraphics::mirror( tools::Long& x, tools::Long nWidth, const OutputDevice& rOutDev, bool bBack ) const
+{
+ const tools::Long w = GetDeviceWidth(rOutDev);
+ if( !w )
+ return;
+
+ if (rOutDev.ImplIsAntiparallel() )
+ {
+ // mirror this window back
+ if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ tools::Long devX = w - rOutDev.GetOutputWidthPixel() - rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ if( bBack )
+ x = x - devX + rOutDev.GetOutOffXPixel();
+ else
+ x = devX + (x - rOutDev.GetOutOffXPixel());
+ }
+ else
+ {
+ tools::Long devX = rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ if( bBack )
+ x = devX + (rOutDev.GetOutputWidthPixel() + devX) - (x + nWidth);
+ else
+ x = rOutDev.GetOutputWidthPixel() - (x - devX) + rOutDev.GetOutOffXPixel() - nWidth;
+ }
+ }
+ else if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ x = w-nWidth-x;
+}
+
+bool SalGraphics::mirror( sal_uInt32 nPoints, const Point *pPtAry, Point *pPtAry2, const OutputDevice& rOutDev ) const
+{
+ const tools::Long w = GetDeviceWidth(rOutDev);
+ if( w )
+ {
+ sal_uInt32 i, j;
+
+ if (rOutDev.ImplIsAntiparallel())
+ {
+ // mirror this window back
+ if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ tools::Long devX = w - rOutDev.GetOutputWidthPixel() - rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ for( i=0, j=nPoints-1; i<nPoints; i++,j-- )
+ {
+ pPtAry2[j].setX( devX + (pPtAry[i].getX() - rOutDev.GetOutOffXPixel()) );
+ pPtAry2[j].setY( pPtAry[i].getY() );
+ }
+ }
+ else
+ {
+ tools::Long devX = rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ for( i=0, j=nPoints-1; i<nPoints; i++,j-- )
+ {
+ pPtAry2[j].setX( rOutDev.GetOutputWidthPixel() - (pPtAry[i].getX() - devX) + rOutDev.GetOutOffXPixel() - 1 );
+ pPtAry2[j].setY( pPtAry[i].getY() );
+ }
+ }
+ }
+ else if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ for( i=0, j=nPoints-1; i<nPoints; i++,j-- )
+ {
+ pPtAry2[j].setX( w-1-pPtAry[i].getX() );
+ pPtAry2[j].setY( pPtAry[i].getY() );
+ }
+ }
+ return true;
+ }
+ else
+ return false;
+}
+
+void SalGraphics::mirror( vcl::Region& rRgn, const OutputDevice& rOutDev ) const
+{
+ if( rRgn.HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ const basegfx::B2DPolyPolygon aPolyPoly(mirror(rRgn.GetAsB2DPolyPolygon(), rOutDev));
+
+ rRgn = vcl::Region(aPolyPoly);
+ }
+ else
+ {
+ RectangleVector aRectangles;
+ rRgn.GetRegionRectangles(aRectangles);
+ rRgn.SetEmpty();
+
+ for (auto & rectangle : aRectangles)
+ {
+ mirror(rectangle, rOutDev);
+ rRgn.Union(rectangle);
+ }
+
+ //ImplRegionInfo aInfo;
+ //bool bRegionRect;
+ //Region aMirroredRegion;
+ //long nX, nY, nWidth, nHeight;
+
+ //bRegionRect = rRgn.ImplGetFirstRect( aInfo, nX, nY, nWidth, nHeight );
+ //while ( bRegionRect )
+ //{
+ // Rectangle aRect( Point(nX, nY), Size(nWidth, nHeight) );
+ // mirror( aRect, rOutDev, bBack );
+ // aMirroredRegion.Union( aRect );
+ // bRegionRect = rRgn.ImplGetNextRect( aInfo, nX, nY, nWidth, nHeight );
+ //}
+ //rRgn = aMirroredRegion;
+ }
+}
+
+void SalGraphics::mirror( tools::Rectangle& rRect, const OutputDevice& rOutDev, bool bBack ) const
+{
+ tools::Long nWidth = rRect.GetWidth();
+ tools::Long x = rRect.Left();
+ tools::Long x_org = x;
+
+ mirror( x, nWidth, rOutDev, bBack );
+ rRect.Move( x - x_org, 0 );
+}
+
+basegfx::B2DPolyPolygon SalGraphics::mirror( const basegfx::B2DPolyPolygon& i_rPoly, const OutputDevice& i_rOutDev ) const
+{
+ const basegfx::B2DHomMatrix& rMirror(getMirror(i_rOutDev));
+
+ if(rMirror.isIdentity())
+ {
+ return i_rPoly;
+ }
+ else
+ {
+ basegfx::B2DPolyPolygon aRet(i_rPoly);
+ aRet.transform(rMirror);
+ aRet.flip();
+ return aRet;
+ }
+}
+
+SalGraphics::MirrorMode SalGraphics::GetMirrorMode(const OutputDevice& rOutDev) const
+{
+ if (rOutDev.ImplIsAntiparallel())
+ {
+ if (m_nLayout & SalLayoutFlags::BiDiRtl)
+ return MirrorMode::AntiparallelBiDi;
+ else
+ return MirrorMode::Antiparallel;
+ }
+ else if (m_nLayout & SalLayoutFlags::BiDiRtl)
+ return MirrorMode::BiDi;
+ return MirrorMode::NONE;
+}
+
+const basegfx::B2DHomMatrix& SalGraphics::getMirror( const OutputDevice& i_rOutDev ) const
+{
+ // get mirroring transformation
+ MirrorMode eNewMirrorMode = GetMirrorMode(i_rOutDev);
+ tools::Long nTranslate(0);
+
+ switch (eNewMirrorMode)
+ {
+ case MirrorMode::AntiparallelBiDi:
+ {
+ const tools::Long w = GetDeviceWidth(i_rOutDev);
+ SAL_WARN_IF(!w, "vcl", "missing graphics width");
+ nTranslate = w - i_rOutDev.GetOutputWidthPixel() - (2 * i_rOutDev.GetOutOffXPixel());
+ break;
+ }
+ case MirrorMode::Antiparallel:
+ {
+ nTranslate = i_rOutDev.GetOutputWidthPixel() + (2 * i_rOutDev.GetOutOffXPixel()) - 1;
+ break;
+ }
+ case MirrorMode::BiDi:
+ {
+ const tools::Long w = GetDeviceWidth(i_rOutDev);
+ SAL_WARN_IF(!w, "vcl", "missing graphics width");
+ nTranslate = w - 1;
+ break;
+ }
+ case MirrorMode::NONE:
+ break;
+ }
+
+ // if the translation (due to device width), or mirror state of the device changed, then m_aLastMirror is invalid
+ bool bLastMirrorValid = eNewMirrorMode == m_eLastMirrorMode && nTranslate == m_nLastMirrorTranslation;
+ if (!bLastMirrorValid)
+ {
+ const_cast<SalGraphics*>(this)->m_nLastMirrorTranslation = nTranslate;
+ const_cast<SalGraphics*>(this)->m_eLastMirrorMode = eNewMirrorMode;
+
+ switch (eNewMirrorMode)
+ {
+ // mirror this window back
+ case MirrorMode::AntiparallelBiDi:
+ {
+ /* This path gets exercised in calc's RTL UI (e.g. SAL_RTL_ENABLED=1)
+ with its LTR horizontal scrollbar */
+
+ // Original code was:
+ // double devX = w-i_rOutDev.GetOutputWidthPixel()-i_rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ // aRet.setX( devX + (i_rPoint.getX() - i_rOutDev.GetOutOffXPixel()) );
+ const_cast<SalGraphics*>(this)->m_aLastMirror = basegfx::utils::createTranslateB2DHomMatrix(
+ nTranslate, 0.0);
+ break;
+ }
+ case MirrorMode::Antiparallel:
+ {
+ /* This path gets exercised in writers's LTR UI with a RTL horizontal
+ scrollbar, cross-reference dialog populated from contents from a
+ RTL document tdf#131725 */
+
+ // Original code was;
+ // tools::Long devX = rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ // x = rOutDev.GetOutputWidthPixel() - (x - devX) + rOutDev.GetOutOffXPixel() - 1;
+ const_cast<SalGraphics*>(this)->m_aLastMirror = basegfx::utils::createScaleTranslateB2DHomMatrix(
+ -1.0,
+ 1.0,
+ nTranslate,
+ 0.0);
+ break;
+ }
+ case MirrorMode::BiDi:
+ {
+ // Original code was:
+ // aRet.setX( w-1-i_rPoint.getX() );
+ // -mirror X -> scale(-1.0, 1.0)
+ // -translate X -> translate(w-1, 0)
+ // Checked this one, works as expected.
+ const_cast<SalGraphics*>(this)->m_aLastMirror = basegfx::utils::createScaleTranslateB2DHomMatrix(
+ -1.0,
+ 1.0,
+ nTranslate,
+ 0.0);
+ break;
+ }
+ case MirrorMode::NONE:
+ const_cast<SalGraphics*>(this)->m_aLastMirror.identity();
+ break;
+ }
+ }
+
+ return m_aLastMirror;
+}
+
+bool SalGraphics::SetClipRegion( const vcl::Region& i_rClip, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ vcl::Region aMirror( i_rClip );
+ mirror( aMirror, rOutDev );
+ return setClipRegion( aMirror );
+ }
+ return setClipRegion( i_rClip );
+}
+
+void SalGraphics::DrawPixel( tools::Long nX, tools::Long nY, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, rOutDev );
+ drawPixel( nX, nY );
+}
+
+void SalGraphics::DrawPixel( tools::Long nX, tools::Long nY, Color nColor, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, rOutDev );
+ drawPixel( nX, nY, nColor );
+}
+
+void SalGraphics::DrawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ mirror( nX1, rOutDev );
+ mirror( nX2, rOutDev );
+ }
+ drawLine( nX1, nY1, nX2, nY2 );
+}
+
+void SalGraphics::DrawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, nWidth, rOutDev );
+ drawRect( nX, nY, nWidth, nHeight );
+}
+
+void SalGraphics::DrawPolyLine( sal_uInt32 nPoints, Point const * pPtAry, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ std::unique_ptr<Point[]> pPtAry2(new Point[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), rOutDev );
+ drawPolyLine( nPoints, bCopied ? pPtAry2.get() : pPtAry );
+ }
+ else
+ drawPolyLine( nPoints, pPtAry );
+}
+
+void SalGraphics::DrawPolygon( sal_uInt32 nPoints, const Point* pPtAry, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ std::unique_ptr<Point[]> pPtAry2(new Point[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), rOutDev );
+ drawPolygon( nPoints, bCopied ? pPtAry2.get() : pPtAry );
+ }
+ else
+ drawPolygon( nPoints, pPtAry );
+}
+
+void SalGraphics::DrawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, const Point** pPtAry, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ // TODO: optimize, reduce new/delete calls
+ std::unique_ptr<Point*[]> pPtAry2( new Point*[nPoly] );
+ sal_uLong i;
+ for(i=0; i<nPoly; i++)
+ {
+ sal_uLong nPoints = pPoints[i];
+ pPtAry2[i] = new Point[ nPoints ];
+ mirror( nPoints, pPtAry[i], pPtAry2[i], rOutDev );
+ }
+
+ drawPolyPolygon( nPoly, pPoints, const_cast<const Point**>(pPtAry2.get()) );
+
+ for(i=0; i<nPoly; i++)
+ delete [] pPtAry2[i];
+ }
+ else
+ drawPolyPolygon( nPoly, pPoints, pPtAry );
+}
+
+namespace
+{
+ basegfx::B2DHomMatrix createTranslateToMirroredBounds(const basegfx::B2DRange &rBoundingBox, const basegfx::B2DHomMatrix& rMirror)
+ {
+ basegfx::B2DRange aRTLBoundingBox(rBoundingBox);
+ aRTLBoundingBox *= rMirror;
+ return basegfx::utils::createTranslateB2DHomMatrix(aRTLBoundingBox.getMinX() - rBoundingBox.getMinX(), 0);
+ }
+}
+
+bool SalGraphics::DrawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& i_rPolyPolygon,
+ double i_fTransparency,
+ const OutputDevice& i_rOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || i_rOutDev.IsRTLEnabled() )
+ {
+ // mirroring set
+ const basegfx::B2DHomMatrix& rMirror(getMirror(i_rOutDev));
+ if(!rMirror.isIdentity())
+ {
+ return drawPolyPolygon(
+ rMirror * rObjectToDevice,
+ i_rPolyPolygon,
+ i_fTransparency);
+ }
+ }
+
+ return drawPolyPolygon(
+ rObjectToDevice,
+ i_rPolyPolygon,
+ i_fTransparency);
+}
+
+bool SalGraphics::DrawPolyLineBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry, const OutputDevice& rOutDev )
+{
+ bool bResult = false;
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ std::unique_ptr<Point[]> pPtAry2(new Point[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), rOutDev );
+ bResult = drawPolyLineBezier( nPoints, bCopied ? pPtAry2.get() : pPtAry, pFlgAry );
+ }
+ else
+ bResult = drawPolyLineBezier( nPoints, pPtAry, pFlgAry );
+ return bResult;
+}
+
+bool SalGraphics::DrawPolygonBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry, const OutputDevice& rOutDev )
+{
+ bool bResult = false;
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ std::unique_ptr<Point[]> pPtAry2(new Point[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), rOutDev );
+ bResult = drawPolygonBezier( nPoints, bCopied ? pPtAry2.get() : pPtAry, pFlgAry );
+ }
+ else
+ bResult = drawPolygonBezier( nPoints, pPtAry, pFlgAry );
+ return bResult;
+}
+
+bool SalGraphics::DrawPolyPolygonBezier( sal_uInt32 i_nPoly, const sal_uInt32* i_pPoints,
+ const Point* const* i_pPtAry, const PolyFlags* const* i_pFlgAry, const OutputDevice& i_rOutDev )
+{
+ bool bRet = false;
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || i_rOutDev.IsRTLEnabled() )
+ {
+ // TODO: optimize, reduce new/delete calls
+ std::unique_ptr<Point*[]> pPtAry2( new Point*[i_nPoly] );
+ sal_uLong i;
+ for(i=0; i<i_nPoly; i++)
+ {
+ sal_uLong nPoints = i_pPoints[i];
+ pPtAry2[i] = new Point[ nPoints ];
+ mirror( nPoints, i_pPtAry[i], pPtAry2[i], i_rOutDev );
+ }
+
+ bRet = drawPolyPolygonBezier( i_nPoly, i_pPoints, const_cast<const Point* const *>(pPtAry2.get()), i_pFlgAry );
+
+ for(i=0; i<i_nPoly; i++)
+ delete [] pPtAry2[i];
+ }
+ else
+ bRet = drawPolyPolygonBezier( i_nPoly, i_pPoints, i_pPtAry, i_pFlgAry );
+ return bRet;
+}
+
+bool SalGraphics::DrawPolyLine(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& i_rPolygon,
+ double i_fTransparency,
+ double i_rLineWidth,
+ const std::vector< double >* i_pStroke, // MM01
+ basegfx::B2DLineJoin i_eLineJoin,
+ css::drawing::LineCap i_eLineCap,
+ double i_fMiterMinimumAngle,
+ bool bPixelSnapHairline,
+ const OutputDevice& i_rOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || i_rOutDev.IsRTLEnabled() )
+ {
+ // mirroring set
+ const basegfx::B2DHomMatrix& rMirror(getMirror(i_rOutDev));
+ if(!rMirror.isIdentity())
+ {
+ return drawPolyLine(
+ rMirror * rObjectToDevice,
+ i_rPolygon,
+ i_fTransparency,
+ i_rLineWidth,
+ i_pStroke, // MM01
+ i_eLineJoin,
+ i_eLineCap,
+ i_fMiterMinimumAngle,
+ bPixelSnapHairline);
+ }
+ }
+
+ // no mirroring set (or identity), use standard call
+ return drawPolyLine(
+ rObjectToDevice,
+ i_rPolygon,
+ i_fTransparency,
+ i_rLineWidth,
+ i_pStroke, // MM01
+ i_eLineJoin,
+ i_eLineCap,
+ i_fMiterMinimumAngle,
+ bPixelSnapHairline);
+}
+
+bool SalGraphics::DrawGradient(const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, const OutputDevice& rOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ tools::PolyPolygon aMirrored(mirror(rPolyPoly.getB2DPolyPolygon(), rOutDev));
+ return drawGradient(aMirrored, rGradient);
+ }
+
+ return drawGradient( rPolyPoly, rGradient );
+}
+
+void SalGraphics::CopyArea( tools::Long nDestX, tools::Long nDestY,
+ tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight,
+ const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ mirror( nDestX, nSrcWidth, rOutDev );
+ mirror( nSrcX, nSrcWidth, rOutDev );
+ }
+ copyArea( nDestX, nDestY, nSrcX, nSrcY, nSrcWidth, nSrcHeight, true/*bWindowInvalidate*/ );
+}
+
+void SalGraphics::CopyBits(const SalTwoRect& rPosAry, const OutputDevice& rOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ copyBits( aPosAry2, nullptr );
+ }
+ else
+ copyBits( rPosAry, nullptr );
+}
+
+void SalGraphics::CopyBits(const SalTwoRect& rPosAry, SalGraphics& rSrcGraphics,
+ const OutputDevice& rOutDev, const OutputDevice& rSrcOutDev)
+{
+ if( ( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) ||
+ ( (rSrcGraphics.GetLayout() & SalLayoutFlags::BiDiRtl) || rSrcOutDev.IsRTLEnabled()) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ if( (rSrcGraphics.GetLayout() & SalLayoutFlags::BiDiRtl) || rSrcOutDev.IsRTLEnabled() )
+ mirror( aPosAry2.mnSrcX, aPosAry2.mnSrcWidth, rSrcOutDev );
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ copyBits( aPosAry2, &rSrcGraphics );
+ }
+ else
+ copyBits( rPosAry, &rSrcGraphics );
+}
+
+void SalGraphics::DrawBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ drawBitmap( aPosAry2, rSalBitmap );
+ }
+ else
+ drawBitmap( rPosAry, rSalBitmap );
+}
+
+void SalGraphics::DrawBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ drawBitmap( aPosAry2, rSalBitmap, rTransparentBitmap );
+ }
+ else
+ drawBitmap( rPosAry, rSalBitmap, rTransparentBitmap );
+}
+
+void SalGraphics::DrawMask( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ Color nMaskColor, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ drawMask( aPosAry2, rSalBitmap, nMaskColor );
+ }
+ else
+ drawMask( rPosAry, rSalBitmap, nMaskColor );
+}
+
+std::shared_ptr<SalBitmap> SalGraphics::GetBitmap( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, nWidth, rOutDev );
+ return getBitmap( nX, nY, nWidth, nHeight );
+}
+
+Color SalGraphics::GetPixel( tools::Long nX, tools::Long nY, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, rOutDev );
+ return getPixel( nX, nY );
+}
+
+void SalGraphics::Invert( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, SalInvert nFlags, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, nWidth, rOutDev );
+ invert( nX, nY, nWidth, nHeight, nFlags );
+}
+
+void SalGraphics::Invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ std::unique_ptr<Point[]> pPtAry2(new Point[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), rOutDev );
+ invert( nPoints, bCopied ? pPtAry2.get() : pPtAry, nFlags );
+ }
+ else
+ invert( nPoints, pPtAry, nFlags );
+}
+
+bool SalGraphics::DrawEPS( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, void* pPtr, sal_uInt32 nSize, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, nWidth, rOutDev );
+ return drawEPS( nX, nY, nWidth, nHeight, pPtr, nSize );
+}
+
+bool SalGraphics::HitTestNativeScrollbar(ControlPart nPart, const tools::Rectangle& rControlRegion,
+ const Point& aPos, bool& rIsInside, const OutputDevice& rOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ Point pt( aPos );
+ tools::Rectangle rgn( rControlRegion );
+ pt.setX( mirror2( pt.X(), rOutDev ) );
+ mirror( rgn, rOutDev );
+ return forWidget()->hitTestNativeControl( ControlType::Scrollbar, nPart, rgn, pt, rIsInside );
+ }
+ else
+ return forWidget()->hitTestNativeControl( ControlType::Scrollbar, nPart, rControlRegion, aPos, rIsInside );
+}
+
+void SalGraphics::mirror( ImplControlValue& rVal, const OutputDevice& rOutDev ) const
+{
+ switch( rVal.getType() )
+ {
+ case ControlType::Slider:
+ {
+ SliderValue* pSlVal = static_cast<SliderValue*>(&rVal);
+ mirror(pSlVal->maThumbRect, rOutDev);
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ ScrollbarValue* pScVal = static_cast<ScrollbarValue*>(&rVal);
+ mirror(pScVal->maThumbRect, rOutDev);
+ mirror(pScVal->maButton1Rect, rOutDev);
+ mirror(pScVal->maButton2Rect, rOutDev);
+ }
+ break;
+ case ControlType::Spinbox:
+ case ControlType::SpinButtons:
+ {
+ SpinbuttonValue* pSpVal = static_cast<SpinbuttonValue*>(&rVal);
+ mirror(pSpVal->maUpperRect, rOutDev);
+ mirror(pSpVal->maLowerRect, rOutDev);
+ }
+ break;
+ case ControlType::Toolbar:
+ {
+ ToolbarValue* pTVal = static_cast<ToolbarValue*>(&rVal);
+ mirror(pTVal->maGripRect, rOutDev);
+ }
+ break;
+ default: break;
+ }
+}
+
+bool SalGraphics::DrawNativeControl( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion,
+ ControlState nState, const ImplControlValue& aValue,
+ const OUString& aCaption, const OutputDevice& rOutDev,
+ const Color& rBackgroundColor)
+{
+ bool bRet = false;
+ tools::Rectangle aControlRegion(rControlRegion);
+ if (aControlRegion.IsEmpty() || aControlRegion.GetWidth() <= 0 || aControlRegion.GetHeight() <= 0)
+ return bRet;
+
+ bool bLayoutRTL = true && (m_nLayout & SalLayoutFlags::BiDiRtl);
+ bool bDevRTL = rOutDev.IsRTLEnabled();
+ bool bIsLOK = comphelper::LibreOfficeKit::isActive();
+ if( (bLayoutRTL || bDevRTL) && !bIsLOK )
+ {
+ mirror(aControlRegion, rOutDev);
+ std::unique_ptr< ImplControlValue > mirrorValue( aValue.clone());
+ mirror( *mirrorValue, rOutDev );
+ bRet = forWidget()->drawNativeControl(nType, nPart, aControlRegion, nState, *mirrorValue, aCaption, rBackgroundColor);
+ }
+ else
+ bRet = forWidget()->drawNativeControl(nType, nPart, aControlRegion, nState, aValue, aCaption, rBackgroundColor);
+
+ if (bRet && m_pWidgetDraw)
+ handleDamage(aControlRegion);
+ return bRet;
+}
+
+bool SalGraphics::GetNativeControlRegion( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion, ControlState nState,
+ const ImplControlValue& aValue,
+ tools::Rectangle &rNativeBoundingRegion, tools::Rectangle &rNativeContentRegion, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ tools::Rectangle rgn( rControlRegion );
+ mirror( rgn, rOutDev );
+ std::unique_ptr< ImplControlValue > mirrorValue( aValue.clone());
+ mirror( *mirrorValue, rOutDev );
+ if (forWidget()->getNativeControlRegion(nType, nPart, rgn, nState, *mirrorValue, OUString(), rNativeBoundingRegion, rNativeContentRegion))
+ {
+ mirror( rNativeBoundingRegion, rOutDev, true );
+ mirror( rNativeContentRegion, rOutDev, true );
+ return true;
+ }
+ return false;
+ }
+ else
+ return forWidget()->getNativeControlRegion(nType, nPart, rControlRegion, nState, aValue, OUString(), rNativeBoundingRegion, rNativeContentRegion);
+}
+
+bool SalGraphics::BlendBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rBitmap,
+ const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ return blendBitmap( aPosAry2, rBitmap );
+ }
+ else
+ return blendBitmap( rPosAry, rBitmap );
+}
+
+bool SalGraphics::BlendAlphaBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap,
+ const SalBitmap& rAlphaBitmap,
+ const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ return blendAlphaBitmap( aPosAry2, rSrcBitmap, rMaskBitmap, rAlphaBitmap );
+ }
+ else
+ return blendAlphaBitmap( rPosAry, rSrcBitmap, rMaskBitmap, rAlphaBitmap );
+}
+
+bool SalGraphics::DrawAlphaBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap,
+ const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ return drawAlphaBitmap( aPosAry2, rSourceBitmap, rAlphaBitmap );
+ }
+ else
+ return drawAlphaBitmap( rPosAry, rSourceBitmap, rAlphaBitmap );
+}
+
+bool SalGraphics::DrawTransformedBitmap(
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap,
+ double fAlpha,
+ const OutputDevice& rOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ // mirroring set
+ const basegfx::B2DHomMatrix& rMirror(getMirror(rOutDev));
+ if (!rMirror.isIdentity())
+ {
+ basegfx::B2DPolygon aPoints({rNull, rX, rY});
+ basegfx::B2DRange aBoundingBox(aPoints.getB2DRange());
+ auto aTranslateToMirroredBounds = createTranslateToMirroredBounds(aBoundingBox, rMirror);
+
+ basegfx::B2DPoint aNull = aTranslateToMirroredBounds * rNull;
+ basegfx::B2DPoint aX = aTranslateToMirroredBounds * rX;
+ basegfx::B2DPoint aY = aTranslateToMirroredBounds * rY;
+
+ return drawTransformedBitmap(aNull, aX, aY, rSourceBitmap, pAlphaBitmap, fAlpha);
+ }
+ }
+
+ return drawTransformedBitmap(rNull, rX, rY, rSourceBitmap, pAlphaBitmap, fAlpha);
+}
+
+bool SalGraphics::HasFastDrawTransformedBitmap() const
+{
+ return hasFastDrawTransformedBitmap();
+}
+
+bool SalGraphics::DrawAlphaRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ sal_uInt8 nTransparency, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, nWidth, rOutDev );
+
+ return drawAlphaRect( nX, nY, nWidth, nHeight, nTransparency );
+}
+
+OUString SalGraphics::getRenderBackendName() const
+{
+ if (GetImpl())
+ return GetImpl()->getRenderBackendName();
+ return OUString();
+}
+
+void SalGraphics::GetGlyphWidths(const vcl::AbstractTrueTypeFont& rTTF,
+ const vcl::font::PhysicalFontFace& rFontFace, const bool bVertical,
+ std::vector<sal_Int32>& rWidths, Ucs2UIntMap& rUnicodeEnc)
+{
+ rWidths.clear();
+ rUnicodeEnc.clear();
+
+ const int nGlyphCount = rTTF.glyphCount();
+ if (nGlyphCount <= 0)
+ return;
+
+ FontCharMapRef xFCMap = rFontFace.GetFontCharMap();
+ if (!xFCMap.is() || !xFCMap->GetCharCount())
+ {
+ SAL_WARN("vcl.fonts", "no charmap");
+ return;
+ }
+
+ rWidths.resize(nGlyphCount);
+ std::vector<sal_uInt16> aGlyphIds(nGlyphCount);
+ for (int i = 0; i < nGlyphCount; i++)
+ aGlyphIds[i] = static_cast<sal_uInt16>(i);
+
+ std::unique_ptr<sal_uInt16[]> pGlyphMetrics
+ = GetTTSimpleGlyphMetrics(&rTTF, aGlyphIds.data(), nGlyphCount, bVertical);
+ if (pGlyphMetrics)
+ {
+ for (int i = 0; i < nGlyphCount; ++i)
+ rWidths[i] = pGlyphMetrics[i];
+ pGlyphMetrics.reset();
+ }
+
+ int nCharCount = xFCMap->GetCharCount();
+ sal_uInt32 nChar = xFCMap->GetFirstChar();
+ for (; --nCharCount >= 0; nChar = xFCMap->GetNextChar(nChar))
+ {
+ if (nChar > 0xFFFF)
+ continue;
+
+ sal_Ucs nUcsChar = static_cast<sal_Ucs>(nChar);
+ sal_uInt32 nGlyph = xFCMap->GetGlyphIndex(nUcsChar);
+ if (nGlyph > 0)
+ rUnicodeEnc[nUcsChar] = nGlyph;
+ }
+}
+
+bool SalGraphics::CreateTTFfontSubset(vcl::AbstractTrueTypeFont& rTTF, const OString& rSysPath,
+ const bool bVertical, const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pEncoding, sal_Int32* pGlyphWidths,
+ const int nOrigGlyphCount)
+{
+ // Multiple questions:
+ // - Why is there a glyph limit?
+ // MacOS used to handle 257 glyphs...
+ // Also the much more complex PrintFontManager variant has this limit.
+ // Also the very first implementation has the limit in
+ // commit 8789ed701e98031f2a1657ea0dfd6f7a0b050992
+ // - Why doesn't the PrintFontManager care about the fake glyph? It
+ // is used on all unx platforms to create the subset font.
+ // - Should the SAL_WARN actually be asserts, like on MacOS?
+ if (nOrigGlyphCount > 256)
+ {
+ SAL_WARN("vcl.fonts", "too many glyphs for subsetting");
+ return false;
+ }
+
+ int nGlyphCount = nOrigGlyphCount;
+ sal_uInt16 aShortIDs[256];
+ sal_uInt8 aTempEncs[256];
+
+ // handle the undefined / first font glyph
+ int nNotDef = -1, i;
+ for (i = 0; i < nGlyphCount; ++i)
+ {
+ aTempEncs[i] = pEncoding[i];
+ aShortIDs[i] = static_cast<sal_uInt16>(pGlyphIds[i]);
+ if (!aShortIDs[i])
+ if (nNotDef < 0)
+ nNotDef = i;
+ }
+
+ // nNotDef glyph must be in pos 0 => swap glyphids
+ if (nNotDef != 0)
+ {
+ if (nNotDef < 0)
+ {
+ if (nGlyphCount == 256)
+ {
+ SAL_WARN("vcl.fonts", "too many glyphs for subsetting");
+ return false;
+ }
+ nNotDef = nGlyphCount++;
+ }
+
+ aShortIDs[nNotDef] = aShortIDs[0];
+ aTempEncs[nNotDef] = aTempEncs[0];
+ aShortIDs[0] = 0;
+ aTempEncs[0] = 0;
+ }
+
+ if (pGlyphWidths)
+ {
+ std::unique_ptr<sal_uInt16[]> pMetrics
+ = GetTTSimpleGlyphMetrics(&rTTF, aShortIDs, nGlyphCount, bVertical);
+ if (!pMetrics)
+ return false;
+
+ sal_uInt16 nNotDefAdv = pMetrics[0];
+ pMetrics[0] = pMetrics[nNotDef];
+ pMetrics[nNotDef] = nNotDefAdv;
+ for (i = 0; i < nOrigGlyphCount; ++i)
+ pGlyphWidths[i] = pMetrics[i];
+ pMetrics.reset();
+ }
+
+ // write subset into destination file
+ return (CreateTTFromTTGlyphs(&rTTF, rSysPath.getStr(), aShortIDs, aTempEncs, nGlyphCount)
+ == vcl::SFErrCodes::Ok);
+}
+
+bool SalGraphics::CreateCFFfontSubset(const unsigned char* pFontBytes, int nByteLength,
+ const OString& rSysPath, const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pEncoding, sal_Int32* pGlyphWidths,
+ int nGlyphCount, FontSubsetInfo& rInfo)
+{
+ FILE* pOutFile = fopen(rSysPath.getStr(), "wb");
+ if (!pOutFile)
+ return false;
+ rInfo.LoadFont(FontType::CFF_FONT, pFontBytes, nByteLength);
+ bool bRet = rInfo.CreateFontSubset(FontType::TYPE1_PFB, pOutFile, nullptr, pGlyphIds, pEncoding,
+ nGlyphCount, pGlyphWidths);
+ fclose(pOutFile);
+ return bRet;
+}
+
+void SalGraphics::FillFontSubsetInfo(const vcl::TTGlobalFontInfo& rTTInfo, const OUString& pPSName,
+ FontSubsetInfo& rInfo)
+{
+ rInfo.m_aPSName = pPSName;
+ rInfo.m_nFontType = FontType::SFNT_TTF;
+ rInfo.m_aFontBBox
+ = tools::Rectangle(Point(rTTInfo.xMin, rTTInfo.yMin), Point(rTTInfo.xMax, rTTInfo.yMax));
+ rInfo.m_nCapHeight = rTTInfo.yMax; // Well ...
+ rInfo.m_nAscent = rTTInfo.winAscent;
+ rInfo.m_nDescent = rTTInfo.winDescent;
+
+ // mac fonts usually do not have an OS2-table
+ // => get valid ascent/descent values from other tables
+ if (!rInfo.m_nAscent)
+ rInfo.m_nAscent = +rTTInfo.typoAscender;
+ if (!rInfo.m_nAscent)
+ rInfo.m_nAscent = +rTTInfo.ascender;
+ if (!rInfo.m_nDescent)
+ rInfo.m_nDescent = +rTTInfo.typoDescender;
+ if (!rInfo.m_nDescent)
+ rInfo.m_nDescent = -rTTInfo.descender;
+}
+
+bool SalGraphics::ShouldDownscaleIconsAtSurface(double* pScaleOut) const
+{
+ if (pScaleOut)
+ *pScaleOut = comphelper::LibreOfficeKit::getDPIScale();
+ return comphelper::LibreOfficeKit::isActive();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/sallayout.cxx b/vcl/source/gdi/sallayout.cxx
new file mode 100644
index 000000000..48f808235
--- /dev/null
+++ b/vcl/source/gdi/sallayout.cxx
@@ -0,0 +1,1170 @@
+/* -*- 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 <iostream>
+#include <iomanip>
+
+#include <sal/log.hxx>
+
+#include <cstdio>
+
+#include <math.h>
+
+#include <ImplLayoutArgs.hxx>
+#include <salgdi.hxx>
+#include <sallayout.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <i18nlangtag/lang.h>
+
+#include <vcl/svapp.hxx>
+
+#include <unicode/ubidi.h>
+#include <unicode/uchar.h>
+
+#include <algorithm>
+#include <memory>
+
+#include <impglyphitem.hxx>
+
+// Glyph Flags
+#define GF_FONTMASK 0xF0000000
+#define GF_FONTSHIFT 28
+
+
+sal_UCS4 GetMirroredChar( sal_UCS4 nChar )
+{
+ nChar = u_charMirror( nChar );
+ return nChar;
+}
+
+sal_UCS4 GetLocalizedChar( sal_UCS4 nChar, LanguageType eLang )
+{
+ // currently only conversion from ASCII digits is interesting
+ if( (nChar < '0') || ('9' < nChar) )
+ return nChar;
+
+ int nOffset;
+ // eLang & LANGUAGE_MASK_PRIMARY catches language independent of region.
+ // CAVEAT! To some like Mongolian MS assigned the same primary language
+ // although the script type is different!
+ LanguageType pri = primary(eLang);
+ if( pri == primary(LANGUAGE_ARABIC_SAUDI_ARABIA) )
+ nOffset = 0x0660 - '0'; // arabic-indic digits
+ else if ( pri.anyOf(
+ primary(LANGUAGE_FARSI),
+ primary(LANGUAGE_URDU_PAKISTAN),
+ primary(LANGUAGE_PUNJABI), //???
+ primary(LANGUAGE_SINDHI)))
+ nOffset = 0x06F0 - '0'; // eastern arabic-indic digits
+ else if ( pri == primary(LANGUAGE_BENGALI) )
+ nOffset = 0x09E6 - '0'; // bengali
+ else if ( pri == primary(LANGUAGE_HINDI) )
+ nOffset = 0x0966 - '0'; // devanagari
+ else if ( pri.anyOf(
+ primary(LANGUAGE_AMHARIC_ETHIOPIA),
+ primary(LANGUAGE_TIGRIGNA_ETHIOPIA)))
+ // TODO case:
+ nOffset = 0x1369 - '0'; // ethiopic
+ else if ( pri == primary(LANGUAGE_GUJARATI) )
+ nOffset = 0x0AE6 - '0'; // gujarati
+#ifdef LANGUAGE_GURMUKHI // TODO case:
+ else if ( pri == primary(LANGUAGE_GURMUKHI) )
+ nOffset = 0x0A66 - '0'; // gurmukhi
+#endif
+ else if ( pri == primary(LANGUAGE_KANNADA) )
+ nOffset = 0x0CE6 - '0'; // kannada
+ else if ( pri == primary(LANGUAGE_KHMER))
+ nOffset = 0x17E0 - '0'; // khmer
+ else if ( pri == primary(LANGUAGE_LAO) )
+ nOffset = 0x0ED0 - '0'; // lao
+ else if ( pri == primary(LANGUAGE_MALAYALAM) )
+ nOffset = 0x0D66 - '0'; // malayalam
+ else if ( pri == primary(LANGUAGE_MONGOLIAN_MONGOLIAN_LSO))
+ {
+ if (eLang.anyOf(
+ LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA,
+ LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA,
+ LANGUAGE_MONGOLIAN_MONGOLIAN_LSO))
+ nOffset = 0x1810 - '0'; // mongolian
+ else
+ nOffset = 0; // mongolian cyrillic
+ }
+ else if ( pri == primary(LANGUAGE_BURMESE) )
+ nOffset = 0x1040 - '0'; // myanmar
+ else if ( pri == primary(LANGUAGE_ODIA) )
+ nOffset = 0x0B66 - '0'; // odia
+ else if ( pri == primary(LANGUAGE_TAMIL) )
+ nOffset = 0x0BE7 - '0'; // tamil
+ else if ( pri == primary(LANGUAGE_TELUGU) )
+ nOffset = 0x0C66 - '0'; // telugu
+ else if ( pri == primary(LANGUAGE_THAI) )
+ nOffset = 0x0E50 - '0'; // thai
+ else if ( pri == primary(LANGUAGE_TIBETAN) )
+ nOffset = 0x0F20 - '0'; // tibetan
+ else
+ {
+ nOffset = 0;
+ }
+
+ nChar += nOffset;
+ return nChar;
+}
+
+SalLayout::SalLayout()
+: mnMinCharPos( -1 ),
+ mnEndCharPos( -1 ),
+ mnOrientation( 0 ),
+ maDrawOffset( 0, 0 ),
+ mbTextRenderModeForResolutionIndependentLayout(false)
+{}
+
+SalLayout::~SalLayout()
+{}
+
+void SalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs )
+{
+ mnMinCharPos = rArgs.mnMinCharPos;
+ mnEndCharPos = rArgs.mnEndCharPos;
+ mnOrientation = rArgs.mnOrientation;
+}
+
+DevicePoint SalLayout::GetDrawPosition(const DevicePoint& rRelative) const
+{
+ DevicePoint aPos(maDrawBase);
+ DevicePoint aOfs(rRelative.getX() + maDrawOffset.X(),
+ rRelative.getY() + maDrawOffset.Y());
+
+ if( mnOrientation == 0_deg10 )
+ aPos += aOfs;
+ else
+ {
+ // cache trigonometric results
+ static Degree10 nOldOrientation(0);
+ static double fCos = 1.0, fSin = 0.0;
+ if( nOldOrientation != mnOrientation )
+ {
+ nOldOrientation = mnOrientation;
+ double fRad = toRadians(mnOrientation);
+ fCos = cos( fRad );
+ fSin = sin( fRad );
+ }
+
+ double fX = aOfs.getX();
+ double fY = aOfs.getY();
+ if (mbTextRenderModeForResolutionIndependentLayout)
+ {
+ double nX = +fCos * fX + fSin * fY;
+ double nY = +fCos * fY - fSin * fX;
+ aPos += DevicePoint(nX, nY);
+ }
+ else
+ {
+ tools::Long nX = static_cast<tools::Long>( +fCos * fX + fSin * fY );
+ tools::Long nY = static_cast<tools::Long>( +fCos * fY - fSin * fX );
+ aPos += DevicePoint(nX, nY);
+ }
+ }
+
+ return aPos;
+}
+
+bool SalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rVector) const
+{
+ bool bAllOk = true;
+ bool bOneOk = false;
+
+ basegfx::B2DPolyPolygon aGlyphOutline;
+
+ DevicePoint aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ const LogicalFontInstance* pGlyphFont;
+ while (GetNextGlyph(&pGlyph, aPos, nStart, &pGlyphFont))
+ {
+ // get outline of individual glyph, ignoring "empty" glyphs
+ bool bSuccess = pGlyph->GetGlyphOutline(pGlyphFont, aGlyphOutline);
+ bAllOk &= bSuccess;
+ bOneOk |= bSuccess;
+ // only add non-empty outlines
+ if( bSuccess && (aGlyphOutline.count() > 0) )
+ {
+ if( aPos.getX() || aPos.getY() )
+ {
+ aGlyphOutline.transform(basegfx::utils::createTranslateB2DHomMatrix(aPos.getX(), aPos.getY()));
+ }
+
+ // insert outline at correct position
+ rVector.push_back( aGlyphOutline );
+ }
+ }
+
+ return (bAllOk && bOneOk);
+}
+
+bool SalLayout::GetBoundRect(tools::Rectangle& rRect) const
+{
+ bool bRet = false;
+ rRect.SetEmpty();
+
+ tools::Rectangle aRectangle;
+
+ DevicePoint aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ const LogicalFontInstance* pGlyphFont;
+ while (GetNextGlyph(&pGlyph, aPos, nStart, &pGlyphFont))
+ {
+ // get bounding rectangle of individual glyph
+ if (pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle))
+ {
+ if (!aRectangle.IsEmpty())
+ {
+ aRectangle.AdjustLeft(std::floor(aPos.getX()));
+ aRectangle.AdjustRight(std::ceil(aPos.getX()));
+ aRectangle.AdjustTop(std::floor(aPos.getY()));
+ aRectangle.AdjustBottom(std::ceil(aPos.getY()));
+
+ // merge rectangle
+ if (rRect.IsEmpty())
+ rRect = aRectangle;
+ else
+ rRect.Union(aRectangle);
+ }
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+SalLayoutGlyphs SalLayout::GetGlyphs() const
+{
+ return SalLayoutGlyphs(); // invalid
+}
+
+DeviceCoordinate GenericSalLayout::FillDXArray( std::vector<DeviceCoordinate>* pCharWidths ) const
+{
+ if (pCharWidths)
+ GetCharWidths(*pCharWidths);
+
+ return GetTextWidth();
+}
+
+// the text width is the maximum logical extent of all glyphs
+DeviceCoordinate GenericSalLayout::GetTextWidth() const
+{
+ if (!m_GlyphItems.IsValid())
+ return 0;
+
+ // initialize the extent
+ DeviceCoordinate nMinPos = 0;
+ DeviceCoordinate nMaxPos = 0;
+
+ for (auto const& aGlyphItem : m_GlyphItems)
+ {
+ // update the text extent with the glyph extent
+ DeviceCoordinate nXPos = aGlyphItem.linearPos().getX();
+ if( nMinPos > nXPos )
+ nMinPos = nXPos;
+ nXPos += aGlyphItem.newWidth() - aGlyphItem.xOffset();
+ if( nMaxPos < nXPos )
+ nMaxPos = nXPos;
+ }
+
+ DeviceCoordinate nWidth = nMaxPos - nMinPos;
+ return nWidth;
+}
+
+void GenericSalLayout::Justify( DeviceCoordinate nNewWidth )
+{
+ DeviceCoordinate nOldWidth = GetTextWidth();
+ if( !nOldWidth || nNewWidth==nOldWidth )
+ return;
+
+ if (!m_GlyphItems.IsValid())
+ {
+ return;
+ }
+ // find rightmost glyph, it won't get stretched
+ std::vector<GlyphItem>::iterator pGlyphIterRight = m_GlyphItems.begin();
+ pGlyphIterRight += m_GlyphItems.size() - 1;
+ std::vector<GlyphItem>::iterator pGlyphIter;
+ // count stretchable glyphs
+ int nStretchable = 0;
+ int nMaxGlyphWidth = 0;
+ for(pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter)
+ {
+ if( !pGlyphIter->IsDiacritic() )
+ ++nStretchable;
+ if (nMaxGlyphWidth < pGlyphIter->origWidth())
+ nMaxGlyphWidth = pGlyphIter->origWidth();
+ }
+
+ // move rightmost glyph to requested position
+ nOldWidth -= pGlyphIterRight->origWidth();
+ if( nOldWidth <= 0 )
+ return;
+ if( nNewWidth < nMaxGlyphWidth)
+ nNewWidth = nMaxGlyphWidth;
+ nNewWidth -= pGlyphIterRight->origWidth();
+ pGlyphIterRight->setLinearPosX( nNewWidth );
+
+ // justify glyph widths and positions
+ int nDiffWidth = nNewWidth - nOldWidth;
+ if( nDiffWidth >= 0) // expanded case
+ {
+ // expand width by distributing space between glyphs evenly
+ int nDeltaSum = 0;
+ for( pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter )
+ {
+ // move glyph to justified position
+ pGlyphIter->adjustLinearPosX(nDeltaSum);
+
+ // do not stretch non-stretchable glyphs
+ if( pGlyphIter->IsDiacritic() || (nStretchable <= 0) )
+ continue;
+
+ // distribute extra space equally to stretchable glyphs
+ int nDeltaWidth = nDiffWidth / nStretchable--;
+ nDiffWidth -= nDeltaWidth;
+ pGlyphIter->addNewWidth(nDeltaWidth);
+ nDeltaSum += nDeltaWidth;
+ }
+ }
+ else // condensed case
+ {
+ // squeeze width by moving glyphs proportionally
+ double fSqueeze = static_cast<double>(nNewWidth) / nOldWidth;
+ if(m_GlyphItems.size() > 1)
+ {
+ for( pGlyphIter = m_GlyphItems.begin(); ++pGlyphIter != pGlyphIterRight;)
+ {
+ int nX = pGlyphIter->linearPos().getX();
+ nX = static_cast<int>(nX * fSqueeze);
+ pGlyphIter->setLinearPosX( nX );
+ }
+ }
+ // adjust glyph widths to new positions
+ for( pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter )
+ pGlyphIter->setNewWidth( pGlyphIter[1].linearPos().getX() - pGlyphIter[0].linearPos().getX());
+ }
+}
+
+// returns asian kerning values in quarter of character width units
+// to enable automatic halfwidth substitution for fullwidth punctuation
+// return value is negative for l, positive for r, zero for neutral
+// TODO: handle vertical layout as proposed in commit 43bf2ad49c2b3989bbbe893e4fee2e032a3920f5?
+static int lcl_CalcAsianKerning(sal_UCS4 c, bool bLeft)
+{
+ // http://www.asahi-net.or.jp/~sd5a-ucd/freetexts/jis/x4051/1995/appendix.html
+ static const signed char nTable[0x30] =
+ {
+ 0, -2, -2, 0, 0, 0, 0, 0, +2, -2, +2, -2, +2, -2, +2, -2,
+ +2, -2, 0, 0, +2, -2, +2, -2, 0, 0, 0, 0, 0, +2, -2, -2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -2, +2, +2, -2, -2
+ };
+
+ int nResult = 0;
+ if( (c >= 0x3000) && (c < 0x3030) )
+ nResult = nTable[ c - 0x3000 ];
+ else switch( c )
+ {
+ case 0x30FB:
+ nResult = bLeft ? -1 : +1; // 25% left/right/top/bottom
+ break;
+ case 0x2019: case 0x201D:
+ case 0xFF01: case 0xFF09: case 0xFF0C:
+ case 0xFF1A: case 0xFF1B:
+ nResult = -2;
+ break;
+ case 0x2018: case 0x201C:
+ case 0xFF08:
+ nResult = +2;
+ break;
+ default:
+ break;
+ }
+
+ return nResult;
+}
+
+static bool lcl_CanApplyAsianKerning(sal_Unicode cp)
+{
+ return (0x3000 == (cp & 0xFF00)) || (0xFF00 == (cp & 0xFF00)) || (0x2010 == (cp & 0xFFF0));
+}
+
+void GenericSalLayout::ApplyAsianKerning(const OUString& rStr)
+{
+ const int nLength = rStr.getLength();
+ double nOffset = 0;
+
+ for (std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.begin(),
+ pGlyphIterEnd = m_GlyphItems.end();
+ pGlyphIter != pGlyphIterEnd; ++pGlyphIter)
+ {
+ const int n = pGlyphIter->charPos();
+ if (n < nLength - 1)
+ {
+ // ignore code ranges that are not affected by asian punctuation compression
+ const sal_Unicode cCurrent = rStr[n];
+ if (!lcl_CanApplyAsianKerning(cCurrent))
+ continue;
+ const sal_Unicode cNext = rStr[n + 1];
+ if (!lcl_CanApplyAsianKerning(cNext))
+ continue;
+
+ // calculate compression values
+ const int nKernCurrent = +lcl_CalcAsianKerning(cCurrent, true);
+ if (nKernCurrent == 0)
+ continue;
+ const int nKernNext = -lcl_CalcAsianKerning(cNext, false);
+ if (nKernNext == 0)
+ continue;
+
+ // apply punctuation compression to logical glyph widths
+ DeviceCoordinate nDelta = (nKernCurrent < nKernNext) ? nKernCurrent : nKernNext;
+ if (nDelta < 0)
+ {
+ nDelta = (nDelta * pGlyphIter->origWidth() + 2) / 4;
+ if( pGlyphIter+1 == pGlyphIterEnd )
+ pGlyphIter->addNewWidth( nDelta );
+ nOffset += nDelta;
+ }
+ }
+
+ // adjust the glyph positions to the new glyph widths
+ if( pGlyphIter+1 != pGlyphIterEnd )
+ pGlyphIter->adjustLinearPosX(nOffset);
+ }
+}
+
+void GenericSalLayout::GetCaretPositions( int nMaxIndex, sal_Int32* pCaretXArray ) const
+{
+ // initialize result array
+ for (int i = 0; i < nMaxIndex; ++i)
+ pCaretXArray[i] = -1;
+
+ // calculate caret positions using glyph array
+ for (auto const& aGlyphItem : m_GlyphItems)
+ {
+ tools::Long nXPos = aGlyphItem.linearPos().getX();
+ tools::Long nXRight = nXPos + aGlyphItem.origWidth();
+ int n = aGlyphItem.charPos();
+ int nCurrIdx = 2 * (n - mnMinCharPos);
+ // tdf#86399 if this is not the start of a cluster, don't overwrite the caret bounds of the cluster start
+ if (aGlyphItem.IsInCluster() && pCaretXArray[nCurrIdx] != -1)
+ continue;
+ if (!aGlyphItem.IsRTLGlyph() )
+ {
+ // normal positions for LTR case
+ pCaretXArray[ nCurrIdx ] = nXPos;
+ pCaretXArray[ nCurrIdx+1 ] = nXRight;
+ }
+ else
+ {
+ // reverse positions for RTL case
+ pCaretXArray[ nCurrIdx ] = nXRight;
+ pCaretXArray[ nCurrIdx+1 ] = nXPos;
+ }
+ }
+}
+
+sal_Int32 GenericSalLayout::GetTextBreak( DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor ) const
+{
+ std::vector<DeviceCoordinate> aCharWidths;
+ GetCharWidths(aCharWidths);
+
+ DeviceCoordinate nWidth = 0;
+ for( int i = mnMinCharPos; i < mnEndCharPos; ++i )
+ {
+ DeviceCoordinate nDelta = aCharWidths[ i - mnMinCharPos ] * nFactor;
+
+ if (nDelta != 0)
+ {
+ nWidth += nDelta;
+ if( nWidth > nMaxWidth )
+ return i;
+
+ nWidth += nCharExtra;
+ }
+ }
+
+ return -1;
+}
+
+bool GenericSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
+ DevicePoint& rPos, int& nStart,
+ const LogicalFontInstance** ppGlyphFont,
+ const vcl::font::PhysicalFontFace**) const
+{
+ std::vector<GlyphItem>::const_iterator pGlyphIter = m_GlyphItems.begin();
+ std::vector<GlyphItem>::const_iterator pGlyphIterEnd = m_GlyphItems.end();
+ pGlyphIter += nStart;
+
+ // find next glyph in substring
+ for(; pGlyphIter != pGlyphIterEnd; ++nStart, ++pGlyphIter )
+ {
+ int n = pGlyphIter->charPos();
+ if( (mnMinCharPos <= n) && (n < mnEndCharPos) )
+ break;
+ }
+
+ // return zero if no more glyph found
+ if( nStart >= static_cast<int>(m_GlyphItems.size()) )
+ return false;
+
+ if( pGlyphIter == pGlyphIterEnd )
+ return false;
+
+ // update return data with glyph info
+ *pGlyph = &(*pGlyphIter);
+ ++nStart;
+ if (ppGlyphFont)
+ *ppGlyphFont = m_GlyphItems.GetFont().get();
+
+ // calculate absolute position in pixel units
+ DevicePoint aRelativePos = pGlyphIter->linearPos();
+
+ rPos = GetDrawPosition( aRelativePos );
+
+ return true;
+}
+
+void GenericSalLayout::MoveGlyph(int nStart, double nNewXPos)
+{
+ if( nStart >= static_cast<int>(m_GlyphItems.size()) )
+ return;
+
+ std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.begin();
+ pGlyphIter += nStart;
+
+ // the nNewXPos argument determines the new cell position
+ // as RTL-glyphs are right justified in their cell
+ // the cell position needs to be adjusted to the glyph position
+ if( pGlyphIter->IsRTLGlyph() )
+ nNewXPos += pGlyphIter->newWidth() - pGlyphIter->origWidth();
+ // calculate the x-offset to the old position
+ double nXDelta = nNewXPos - pGlyphIter->linearPos().getX() + pGlyphIter->xOffset();
+ // adjust all following glyph positions if needed
+ if( nXDelta != 0 )
+ {
+ for( std::vector<GlyphItem>::iterator pGlyphIterEnd = m_GlyphItems.end(); pGlyphIter != pGlyphIterEnd; ++pGlyphIter )
+ {
+ pGlyphIter->adjustLinearPosX(nXDelta);
+ }
+ }
+}
+
+void GenericSalLayout::DropGlyph( int nStart )
+{
+ if( nStart >= static_cast<int>(m_GlyphItems.size()))
+ return;
+
+ std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.begin();
+ pGlyphIter += nStart;
+ pGlyphIter->dropGlyph();
+}
+
+void GenericSalLayout::Simplify( bool bIsBase )
+{
+ // remove dropped glyphs inplace
+ size_t j = 0;
+ for(size_t i = 0; i < m_GlyphItems.size(); i++ )
+ {
+ if (bIsBase && m_GlyphItems[i].IsDropped())
+ continue;
+ if (!bIsBase && m_GlyphItems[i].glyphId() == 0)
+ continue;
+
+ if( i != j )
+ {
+ m_GlyphItems[j] = m_GlyphItems[i];
+ }
+ j += 1;
+ }
+ m_GlyphItems.erase(m_GlyphItems.begin() + j, m_GlyphItems.end());
+}
+
+MultiSalLayout::MultiSalLayout( std::unique_ptr<SalLayout> pBaseLayout )
+: mnLevel( 1 )
+, mbIncomplete( false )
+{
+ assert(dynamic_cast<GenericSalLayout*>(pBaseLayout.get()));
+
+ mpLayouts[ 0 ].reset(static_cast<GenericSalLayout*>(pBaseLayout.release()));
+}
+
+std::unique_ptr<SalLayout> MultiSalLayout::ReleaseBaseLayout()
+{
+ return std::move(mpLayouts[0]);
+}
+
+void MultiSalLayout::SetIncomplete(bool bIncomplete)
+{
+ mbIncomplete = bIncomplete;
+ maFallbackRuns[mnLevel-1] = ImplLayoutRuns();
+}
+
+MultiSalLayout::~MultiSalLayout()
+{
+}
+
+void MultiSalLayout::AddFallback( std::unique_ptr<SalLayout> pFallback,
+ ImplLayoutRuns const & rFallbackRuns)
+{
+ assert(dynamic_cast<GenericSalLayout*>(pFallback.get()));
+ if( mnLevel >= MAX_FALLBACK )
+ return;
+
+ mpLayouts[ mnLevel ].reset(static_cast<GenericSalLayout*>(pFallback.release()));
+ maFallbackRuns[ mnLevel-1 ] = rFallbackRuns;
+ ++mnLevel;
+}
+
+bool MultiSalLayout::LayoutText( vcl::text::ImplLayoutArgs& rArgs, const SalLayoutGlyphsImpl* )
+{
+ if( mnLevel <= 1 )
+ return false;
+ if (!mbIncomplete)
+ maFallbackRuns[ mnLevel-1 ] = rArgs.maRuns;
+ return true;
+}
+
+void MultiSalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs )
+{
+ SalLayout::AdjustLayout( rArgs );
+ vcl::text::ImplLayoutArgs aMultiArgs = rArgs;
+ std::vector<DeviceCoordinate> aJustificationArray;
+
+ if( !rArgs.HasDXArray() && rArgs.mnLayoutWidth )
+ {
+ // for stretched text in a MultiSalLayout the target width needs to be
+ // distributed by individually adjusting its virtual character widths
+ DeviceCoordinate nTargetWidth = aMultiArgs.mnLayoutWidth;
+ aMultiArgs.mnLayoutWidth = 0;
+
+ // we need to get the original unmodified layouts ready
+ for( int n = 0; n < mnLevel; ++n )
+ mpLayouts[n]->SalLayout::AdjustLayout( aMultiArgs );
+ // then we can measure the unmodified metrics
+ int nCharCount = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
+ FillDXArray( &aJustificationArray );
+ // #i17359# multilayout is not simplified yet, so calculating the
+ // unjustified width needs handholding; also count the number of
+ // stretchable virtual char widths
+ DeviceCoordinate nOrigWidth = 0;
+ int nStretchable = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ // convert array from widths to sum of widths
+ nOrigWidth += aJustificationArray[i];
+ if( aJustificationArray[i] > 0 )
+ ++nStretchable;
+ }
+
+ // now we are able to distribute the extra width over the virtual char widths
+ if( nOrigWidth && (nTargetWidth != nOrigWidth) )
+ {
+ DeviceCoordinate nDiffWidth = nTargetWidth - nOrigWidth;
+ DeviceCoordinate nWidthSum = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ DeviceCoordinate nJustWidth = aJustificationArray[i];
+ if( (nJustWidth > 0) && (nStretchable > 0) )
+ {
+ DeviceCoordinate nDeltaWidth = nDiffWidth / nStretchable;
+ nJustWidth += nDeltaWidth;
+ nDiffWidth -= nDeltaWidth;
+ --nStretchable;
+ }
+ nWidthSum += nJustWidth;
+ aJustificationArray[i] = nWidthSum;
+ }
+ if( nWidthSum != nTargetWidth )
+ aJustificationArray[ nCharCount-1 ] = nTargetWidth;
+
+ // change the mpDXArray temporarily (just for the justification)
+ aMultiArgs.mpDXArray = aJustificationArray.data();
+ }
+ }
+
+ if (aMultiArgs.mpAltNaturalDXArray)
+ ImplAdjustMultiLayout(rArgs, aMultiArgs, aMultiArgs.mpAltNaturalDXArray);
+ else
+ ImplAdjustMultiLayout(rArgs, aMultiArgs, aMultiArgs.mpDXArray);
+}
+
+template<typename DC>
+void MultiSalLayout::ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs& rArgs,
+ vcl::text::ImplLayoutArgs& rMultiArgs,
+ const DC* pMultiDXArray)
+{
+ // Compute rtl flags, since in some scripts glyphs/char order can be
+ // reversed for a few character sequences e.g. Myanmar
+ std::vector<bool> vRtl(rArgs.mnEndCharPos - rArgs.mnMinCharPos, false);
+ rArgs.ResetPos();
+ bool bRtl;
+ int nRunStart, nRunEnd;
+ while (rArgs.GetNextRun(&nRunStart, &nRunEnd, &bRtl))
+ {
+ if (bRtl) std::fill(vRtl.begin() + (nRunStart - rArgs.mnMinCharPos),
+ vRtl.begin() + (nRunEnd - rArgs.mnMinCharPos), true);
+ }
+ rArgs.ResetPos();
+
+ // prepare "merge sort"
+ int nStartOld[ MAX_FALLBACK ];
+ int nStartNew[ MAX_FALLBACK ];
+ const GlyphItem* pGlyphs[MAX_FALLBACK];
+ bool bValid[MAX_FALLBACK] = { false };
+
+ DevicePoint aPos;
+ int nLevel = 0, n;
+ for( n = 0; n < mnLevel; ++n )
+ {
+ // now adjust the individual components
+ if( n > 0 )
+ {
+ rMultiArgs.maRuns = maFallbackRuns[ n-1 ];
+ rMultiArgs.mnFlags |= SalLayoutFlags::ForFallback;
+ }
+ mpLayouts[n]->AdjustLayout( rMultiArgs );
+
+ // remove unused parts of component
+ if( n > 0 )
+ {
+ if (mbIncomplete && (n == mnLevel-1))
+ mpLayouts[n]->Simplify( true );
+ else
+ mpLayouts[n]->Simplify( false );
+ }
+
+ // prepare merging components
+ nStartNew[ nLevel ] = nStartOld[ nLevel ] = 0;
+ bValid[nLevel] = mpLayouts[n]->GetNextGlyph(&pGlyphs[nLevel], aPos, nStartNew[nLevel]);
+
+ if( (n > 0) && !bValid[ nLevel ] )
+ {
+ // an empty fallback layout can be released
+ mpLayouts[n].reset();
+ }
+ else
+ {
+ // reshuffle used fallbacks if needed
+ if( nLevel != n )
+ {
+ mpLayouts[ nLevel ] = std::move(mpLayouts[ n ]);
+ maFallbackRuns[ nLevel ] = maFallbackRuns[ n ];
+ }
+ ++nLevel;
+ }
+ }
+ mnLevel = nLevel;
+
+ // prepare merge the fallback levels
+ DC nXPos = 0;
+ for( n = 0; n < nLevel; ++n )
+ maFallbackRuns[n].ResetPos();
+
+ int nFirstValid = -1;
+ for( n = 0; n < nLevel; ++n )
+ {
+ if(bValid[n])
+ {
+ nFirstValid = n;
+ break;
+ }
+ }
+ assert(nFirstValid >= 0);
+
+ // get the next codepoint index that needs fallback
+ int nActiveCharPos = pGlyphs[nFirstValid]->charPos();
+ int nActiveCharIndex = nActiveCharPos - mnMinCharPos;
+ // get the end index of the active run
+ int nLastRunEndChar = (nActiveCharIndex >= 0 && vRtl[nActiveCharIndex]) ?
+ rArgs.mnEndCharPos : rArgs.mnMinCharPos - 1;
+ int nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos();
+ // merge the fallback levels
+ while( bValid[nFirstValid] && (nLevel > 0))
+ {
+ // find best fallback level
+ for( n = 0; n < nLevel; ++n )
+ if( bValid[n] && !maFallbackRuns[n].PosIsInAnyRun( nActiveCharPos ) )
+ // fallback level n wins when it requested no further fallback
+ break;
+ int nFBLevel = n;
+
+ if( n < nLevel )
+ {
+ // use base(n==0) or fallback(n>=1) level
+ mpLayouts[n]->MoveGlyph( nStartOld[n], nXPos );
+ }
+ else
+ {
+ n = 0; // keep NotDef in base level
+ }
+
+ if( n > 0 )
+ {
+ // drop the NotDef glyphs in the base layout run if a fallback run exists
+ while (
+ (maFallbackRuns[n-1].PosIsInRun(pGlyphs[nFirstValid]->charPos())) &&
+ (!maFallbackRuns[n].PosIsInAnyRun(pGlyphs[nFirstValid]->charPos()))
+ )
+ {
+ mpLayouts[0]->DropGlyph( nStartOld[0] );
+ nStartOld[0] = nStartNew[0];
+ bValid[nFirstValid] = mpLayouts[0]->GetNextGlyph(&pGlyphs[nFirstValid], aPos, nStartNew[0]);
+
+ if( !bValid[nFirstValid] )
+ break;
+ }
+ }
+
+ // skip to end of layout run and calculate its advance width
+ DC nRunAdvance = 0;
+ bool bKeepNotDef = (nFBLevel >= nLevel);
+ for(;;)
+ {
+ nRunAdvance += pGlyphs[n]->newWidth();
+
+ // proceed to next glyph
+ nStartOld[n] = nStartNew[n];
+ int nOrigCharPos = pGlyphs[n]->charPos();
+ bValid[n] = mpLayouts[n]->GetNextGlyph(&pGlyphs[n], aPos, nStartNew[n]);
+ // break after last glyph of active layout
+ if( !bValid[n] )
+ {
+ // performance optimization (when a fallback layout is no longer needed)
+ if( n >= nLevel-1 )
+ --nLevel;
+ break;
+ }
+
+ //If the next character is one which belongs to the next level, then we
+ //are finished here for now, and we'll pick up after the next level has
+ //been processed
+ if ((n+1 < nLevel) && (pGlyphs[n]->charPos() != nOrigCharPos))
+ {
+ if (nOrigCharPos < pGlyphs[n]->charPos())
+ {
+ if (pGlyphs[n+1]->charPos() > nOrigCharPos && (pGlyphs[n+1]->charPos() < pGlyphs[n]->charPos()))
+ break;
+ }
+ else if (nOrigCharPos > pGlyphs[n]->charPos())
+ {
+ if (pGlyphs[n+1]->charPos() > pGlyphs[n]->charPos() && (pGlyphs[n+1]->charPos() < nOrigCharPos))
+ break;
+ }
+ }
+
+ // break at end of layout run
+ if( n > 0 )
+ {
+ // skip until end of fallback run
+ if (!maFallbackRuns[n-1].PosIsInRun(pGlyphs[n]->charPos()))
+ break;
+ }
+ else
+ {
+ // break when a fallback is needed and available
+ bool bNeedFallback = maFallbackRuns[0].PosIsInRun(pGlyphs[nFirstValid]->charPos());
+ if( bNeedFallback )
+ if (!maFallbackRuns[nLevel-1].PosIsInRun(pGlyphs[nFirstValid]->charPos()))
+ break;
+ // break when change from resolved to unresolved base layout run
+ if( bKeepNotDef && !bNeedFallback )
+ { maFallbackRuns[0].NextRun(); break; }
+ bKeepNotDef = bNeedFallback;
+ }
+ // check for reordered glyphs
+ if (pMultiDXArray &&
+ nRunVisibleEndChar < mnEndCharPos &&
+ nRunVisibleEndChar >= mnMinCharPos &&
+ pGlyphs[n]->charPos() < mnEndCharPos &&
+ pGlyphs[n]->charPos() >= mnMinCharPos)
+ {
+ if (vRtl[nActiveCharPos - mnMinCharPos])
+ {
+ if (pMultiDXArray[nRunVisibleEndChar-mnMinCharPos]
+ >= pMultiDXArray[pGlyphs[n]->charPos() - mnMinCharPos])
+ {
+ nRunVisibleEndChar = pGlyphs[n]->charPos();
+ }
+ }
+ else if (pMultiDXArray[nRunVisibleEndChar-mnMinCharPos]
+ <= pMultiDXArray[pGlyphs[n]->charPos() - mnMinCharPos])
+ {
+ nRunVisibleEndChar = pGlyphs[n]->charPos();
+ }
+ }
+ }
+
+ // if a justification array is available
+ // => use it directly to calculate the corresponding run width
+ if (pMultiDXArray)
+ {
+ // the run advance is the width from the first char
+ // in the run to the first char in the next run
+ nRunAdvance = 0;
+ nActiveCharIndex = nActiveCharPos - mnMinCharPos;
+ if (nActiveCharIndex >= 0 && vRtl[nActiveCharIndex])
+ {
+ if (nRunVisibleEndChar > mnMinCharPos && nRunVisibleEndChar <= mnEndCharPos)
+ nRunAdvance -= pMultiDXArray[nRunVisibleEndChar - 1 - mnMinCharPos];
+ if (nLastRunEndChar > mnMinCharPos && nLastRunEndChar <= mnEndCharPos)
+ nRunAdvance += pMultiDXArray[nLastRunEndChar - 1 - mnMinCharPos];
+ }
+ else
+ {
+ if (nRunVisibleEndChar >= mnMinCharPos)
+ nRunAdvance += pMultiDXArray[nRunVisibleEndChar - mnMinCharPos];
+ if (nLastRunEndChar >= mnMinCharPos)
+ nRunAdvance -= pMultiDXArray[nLastRunEndChar - mnMinCharPos];
+ }
+ nLastRunEndChar = nRunVisibleEndChar;
+ nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos();
+ }
+
+ // calculate new x position
+ nXPos += nRunAdvance;
+
+ // prepare for next fallback run
+ nActiveCharPos = pGlyphs[nFirstValid]->charPos();
+ // it essential that the runs don't get ahead of themselves and in the
+ // if( bKeepNotDef && !bNeedFallback ) statement above, the next run may
+ // have already been reached on the base level
+ for( int i = nFBLevel; --i >= 0;)
+ {
+ if (maFallbackRuns[i].GetRun(&nRunStart, &nRunEnd, &bRtl))
+ {
+ if (bRtl)
+ {
+ if (nRunStart > nActiveCharPos)
+ maFallbackRuns[i].NextRun();
+ }
+ else
+ {
+ if (nRunEnd <= nActiveCharPos)
+ maFallbackRuns[i].NextRun();
+ }
+ }
+ }
+ }
+
+ mpLayouts[0]->Simplify( true );
+}
+
+void MultiSalLayout::InitFont() const
+{
+ if( mnLevel > 0 )
+ mpLayouts[0]->InitFont();
+}
+
+void MultiSalLayout::DrawText( SalGraphics& rGraphics ) const
+{
+ for( int i = mnLevel; --i >= 0; )
+ {
+ SalLayout& rLayout = *mpLayouts[ i ];
+ rLayout.DrawBase() += maDrawBase;
+ rLayout.DrawOffset() += maDrawOffset;
+ rLayout.InitFont();
+ rLayout.DrawText( rGraphics );
+ rLayout.DrawOffset() -= maDrawOffset;
+ rLayout.DrawBase() -= maDrawBase;
+ }
+ // NOTE: now the baselevel font is active again
+}
+
+sal_Int32 MultiSalLayout::GetTextBreak( DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor ) const
+{
+ if( mnLevel <= 0 )
+ return -1;
+ if( mnLevel == 1 )
+ return mpLayouts[0]->GetTextBreak( nMaxWidth, nCharExtra, nFactor );
+
+ int nCharCount = mnEndCharPos - mnMinCharPos;
+ std::vector<DeviceCoordinate> aCharWidths;
+ std::vector<DeviceCoordinate> aFallbackCharWidths;
+ mpLayouts[0]->FillDXArray( &aCharWidths );
+
+ for( int n = 1; n < mnLevel; ++n )
+ {
+ SalLayout& rLayout = *mpLayouts[ n ];
+ rLayout.FillDXArray( &aFallbackCharWidths );
+ for( int i = 0; i < nCharCount; ++i )
+ if( aCharWidths[ i ] == 0 )
+ aCharWidths[i] = aFallbackCharWidths[i];
+ }
+
+ DeviceCoordinate nWidth = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ nWidth += aCharWidths[ i ] * nFactor;
+ if( nWidth > nMaxWidth )
+ return (i + mnMinCharPos);
+ nWidth += nCharExtra;
+ }
+
+ return -1;
+}
+
+DeviceCoordinate MultiSalLayout::FillDXArray( std::vector<DeviceCoordinate>* pCharWidths ) const
+{
+ DeviceCoordinate nMaxWidth = 0;
+
+ // prepare merging of fallback levels
+ std::vector<DeviceCoordinate> aTempWidths;
+ const int nCharCount = mnEndCharPos - mnMinCharPos;
+ if( pCharWidths )
+ {
+ pCharWidths->clear();
+ pCharWidths->resize(nCharCount, 0);
+ }
+
+ for( int n = mnLevel; --n >= 0; )
+ {
+ // query every fallback level
+ DeviceCoordinate nTextWidth = mpLayouts[n]->FillDXArray( &aTempWidths );
+ if( !nTextWidth )
+ continue;
+ // merge results from current level
+ if( nMaxWidth < nTextWidth )
+ nMaxWidth = nTextWidth;
+ if( !pCharWidths )
+ continue;
+ // calculate virtual char widths using most probable fallback layout
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ // #i17359# restriction:
+ // one char cannot be resolved from different fallbacks
+ if( (*pCharWidths)[i] != 0 )
+ continue;
+ DeviceCoordinate nCharWidth = aTempWidths[i];
+ if( !nCharWidth )
+ continue;
+ (*pCharWidths)[i] = nCharWidth;
+ }
+ }
+
+ return nMaxWidth;
+}
+
+void MultiSalLayout::GetCaretPositions( int nMaxIndex, sal_Int32* pCaretXArray ) const
+{
+ SalLayout& rLayout = *mpLayouts[ 0 ];
+ rLayout.GetCaretPositions( nMaxIndex, pCaretXArray );
+
+ if( mnLevel <= 1 )
+ return;
+
+ std::unique_ptr<sal_Int32[]> const pTempPos(new sal_Int32[nMaxIndex]);
+ for( int n = 1; n < mnLevel; ++n )
+ {
+ mpLayouts[ n ]->GetCaretPositions( nMaxIndex, pTempPos.get() );
+ for( int i = 0; i < nMaxIndex; ++i )
+ if( pTempPos[i] >= 0 )
+ pCaretXArray[i] = pTempPos[i];
+ }
+}
+
+bool MultiSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
+ DevicePoint& rPos, int& nStart,
+ const LogicalFontInstance** ppGlyphFont,
+ const vcl::font::PhysicalFontFace** pFallbackFont) const
+{
+ // NOTE: nStart is tagged with current font index
+ int nLevel = static_cast<unsigned>(nStart) >> GF_FONTSHIFT;
+ nStart &= ~GF_FONTMASK;
+ for(; nLevel < mnLevel; ++nLevel, nStart=0 )
+ {
+ GenericSalLayout& rLayout = *mpLayouts[ nLevel ];
+ rLayout.InitFont();
+ const vcl::font::PhysicalFontFace* pFontFace = rLayout.GetFont().GetFontFace();
+ if (rLayout.GetNextGlyph(pGlyph, rPos, nStart, ppGlyphFont))
+ {
+ int nFontTag = nLevel << GF_FONTSHIFT;
+ nStart |= nFontTag;
+ if (pFallbackFont)
+ *pFallbackFont = pFontFace;
+ rPos.adjustX(maDrawBase.getX() + maDrawOffset.X());
+ rPos.adjustY(maDrawBase.getY() + maDrawOffset.Y());
+ return true;
+ }
+ }
+
+ // #111016# reset to base level font when done
+ mpLayouts[0]->InitFont();
+ return false;
+}
+
+bool MultiSalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rPPV) const
+{
+ bool bRet = false;
+
+ for( int i = mnLevel; --i >= 0; )
+ {
+ SalLayout& rLayout = *mpLayouts[ i ];
+ rLayout.DrawBase() = maDrawBase;
+ rLayout.DrawOffset() += maDrawOffset;
+ rLayout.InitFont();
+ bRet |= rLayout.GetOutline(rPPV);
+ rLayout.DrawOffset() -= maDrawOffset;
+ }
+
+ return bRet;
+}
+
+bool MultiSalLayout::IsKashidaPosValid(int nCharPos) const
+{
+ // Check the base layout
+ bool bValid = mpLayouts[0]->IsKashidaPosValid(nCharPos);
+
+ // If base layout returned false, it might be because the character was not
+ // supported there, so we check fallback layouts.
+ if (!bValid)
+ {
+ for (int i = 1; i < mnLevel; ++i)
+ {
+ // - 1 because there is no fallback run for the base layout, IIUC.
+ if (maFallbackRuns[i - 1].PosIsInAnyRun(nCharPos))
+ {
+ bValid = mpLayouts[i]->IsKashidaPosValid(nCharPos);
+ break;
+ }
+ }
+ }
+
+ return bValid;
+}
+
+SalLayoutGlyphs MultiSalLayout::GetGlyphs() const
+{
+ SalLayoutGlyphs glyphs;
+ for( int n = 0; n < mnLevel; ++n )
+ glyphs.AppendImpl(mpLayouts[n]->GlyphsImpl().clone());
+ return glyphs;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/salmisc.cxx b/vcl/source/gdi/salmisc.cxx
new file mode 100644
index 000000000..83ebbd000
--- /dev/null
+++ b/vcl/source/gdi/salmisc.cxx
@@ -0,0 +1,435 @@
+/* -*- 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 <vcl/BitmapReadAccess.hxx>
+#include <vcl/salgtype.hxx>
+#include <bitmap/bmpfast.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <tools/helpers.hxx>
+#include <memory>
+
+#define IMPL_CASE_SET_FORMAT( Format, BitCount ) \
+case( ScanlineFormat::Format ): \
+{ \
+ pFncSetPixel = BitmapReadAccess::SetPixelFor##Format; \
+ pDstBuffer->mnBitCount = BitCount; \
+} \
+break
+
+#define DOUBLE_SCANLINES() \
+while( ( nActY < nHeight1 ) && ( pMapY[ nActY + 1 ] == nMapY ) ) \
+{ \
+ memcpy( pDstScanMap[ nActY + 1 ], pDstScan, rDstBuffer.mnScanlineSize ); \
+ nActY++; \
+}
+
+constexpr int TC_TO_PAL_COLORS = 4096;
+
+static tools::Long ImplIndexFromColor( const BitmapColor& rCol )
+{
+ return ( ( static_cast<tools::Long>(rCol.GetBlue()) >> 4) << 8 ) |
+ ( ( static_cast<tools::Long>(rCol.GetGreen()) >> 4 ) << 4 ) |
+ ( static_cast<tools::Long>(rCol.GetRed()) >> 4 );
+}
+
+static void ImplPALToPAL( const BitmapBuffer& rSrcBuffer, BitmapBuffer& rDstBuffer,
+ FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel,
+ Scanline* pSrcScanMap, Scanline* pDstScanMap, sal_Int32 const * pMapX, const sal_Int32* pMapY )
+{
+ const tools::Long nHeight1 = rDstBuffer.mnHeight - 1;
+ const ColorMask& rSrcMask = rSrcBuffer.maColorMask;
+ const ColorMask& rDstMask = rDstBuffer.maColorMask;
+ BitmapPalette aColMap( rSrcBuffer.maPalette.GetEntryCount() );
+ BitmapColor* pColMapBuf = aColMap.ImplGetColorBuffer();
+ BitmapColor aIndex( 0 );
+
+ for( sal_uInt16 i = 0, nSrcCount = aColMap.GetEntryCount(), nDstCount = rDstBuffer.maPalette.GetEntryCount(); i < nSrcCount; i++ )
+ {
+ if( ( i < nDstCount ) && ( rSrcBuffer.maPalette[ i ] == rDstBuffer.maPalette[ i ] ) )
+ aIndex.SetIndex( sal::static_int_cast<sal_uInt8>(i) );
+ else
+ aIndex.SetIndex( sal::static_int_cast<sal_uInt8>(rDstBuffer.maPalette.GetBestIndex( rSrcBuffer.maPalette[ i ] )) );
+
+ pColMapBuf[ i ] = aIndex;
+ }
+
+ for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ tools::Long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ pFncSetPixel( pDstScan, nX, pColMapBuf[ pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ).GetIndex() ], rDstMask );
+
+ DOUBLE_SCANLINES();
+ }
+}
+
+static void ImplPALToTC( const BitmapBuffer& rSrcBuffer, BitmapBuffer const & rDstBuffer,
+ FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel,
+ Scanline* pSrcScanMap, Scanline* pDstScanMap, sal_Int32 const * pMapX, const sal_Int32* pMapY )
+{
+ const tools::Long nHeight1 = rDstBuffer.mnHeight - 1;
+ const ColorMask& rSrcMask = rSrcBuffer.maColorMask;
+ const ColorMask& rDstMask = rDstBuffer.maColorMask;
+ const BitmapColor* pColBuf = rSrcBuffer.maPalette.ImplGetColorBuffer();
+
+ if( RemoveScanline( rSrcBuffer.mnFormat ) == ScanlineFormat::N1BitMsbPal )
+ {
+ const BitmapColor aCol0( pColBuf[ 0 ] );
+ const BitmapColor aCol1( pColBuf[ 1 ] );
+ tools::Long nMapX;
+
+ for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ tools::Long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (tools::Long nX = 0; nX < rDstBuffer.mnWidth;)
+ {
+ nMapX = pMapX[ nX ];
+ pFncSetPixel( pDstScan, nX++,
+ pSrcScan[ nMapX >> 3 ] & ( 1 << ( 7 - ( nMapX & 7 ) ) ) ? aCol1 : aCol0,
+ rDstMask );
+ }
+
+ DOUBLE_SCANLINES();
+ }
+ }
+ else if( RemoveScanline( rSrcBuffer.mnFormat ) == ScanlineFormat::N8BitPal )
+ {
+ for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ tools::Long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ pFncSetPixel( pDstScan, nX, pColBuf[ pSrcScan[ pMapX[ nX ] ] ], rDstMask );
+
+ DOUBLE_SCANLINES();
+ }
+ }
+ else
+ {
+ for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ tools::Long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ pFncSetPixel( pDstScan, nX, pColBuf[ pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ).GetIndex() ], rDstMask );
+
+ DOUBLE_SCANLINES();
+ }
+ }
+}
+
+static void ImplTCToTC( const BitmapBuffer& rSrcBuffer, BitmapBuffer const & rDstBuffer,
+ FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel,
+ Scanline* pSrcScanMap, Scanline* pDstScanMap, sal_Int32 const * pMapX, const sal_Int32* pMapY )
+{
+ const tools::Long nHeight1 = rDstBuffer.mnHeight - 1;
+ const ColorMask& rSrcMask = rSrcBuffer.maColorMask;
+ const ColorMask& rDstMask = rDstBuffer.maColorMask;
+
+ if( RemoveScanline( rSrcBuffer.mnFormat ) == ScanlineFormat::N24BitTcBgr )
+ {
+ BitmapColor aCol;
+ sal_uInt8* pPixel = nullptr;
+
+ for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ tools::Long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ {
+ pPixel = pSrcScan + pMapX[ nX ] * 3;
+ aCol.SetBlue( *pPixel++ );
+ aCol.SetGreen( *pPixel++ );
+ aCol.SetRed( *pPixel );
+ pFncSetPixel( pDstScan, nX, aCol, rDstMask );
+ }
+
+ DOUBLE_SCANLINES()
+ }
+ }
+ else
+ {
+ for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ tools::Long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ pFncSetPixel( pDstScan, nX, pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ), rDstMask );
+
+ DOUBLE_SCANLINES();
+ }
+ }
+}
+
+static void ImplTCToPAL( const BitmapBuffer& rSrcBuffer, BitmapBuffer const & rDstBuffer,
+ FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel,
+ Scanline* pSrcScanMap, Scanline* pDstScanMap, sal_Int32 const * pMapX, const sal_Int32* pMapY )
+{
+ const tools::Long nHeight1 = rDstBuffer.mnHeight- 1;
+ const ColorMask& rSrcMask = rSrcBuffer.maColorMask;
+ const ColorMask& rDstMask = rDstBuffer.maColorMask;
+ std::unique_ptr<sal_uInt8[]> pColToPalMap(new sal_uInt8[ TC_TO_PAL_COLORS ]);
+ BitmapColor aIndex( 0 );
+
+ for( tools::Long nR = 0; nR < 16; nR++ )
+ {
+ for( tools::Long nG = 0; nG < 16; nG++ )
+ {
+ for( tools::Long nB = 0; nB < 16; nB++ )
+ {
+ BitmapColor aCol( sal::static_int_cast<sal_uInt8>(nR << 4),
+ sal::static_int_cast<sal_uInt8>(nG << 4),
+ sal::static_int_cast<sal_uInt8>(nB << 4) );
+ pColToPalMap[ ImplIndexFromColor( aCol ) ] = static_cast<sal_uInt8>(rDstBuffer.maPalette.GetBestIndex( aCol ));
+ }
+ }
+ }
+
+ for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ tools::Long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ {
+ aIndex.SetIndex( pColToPalMap[ ImplIndexFromColor( pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ) ) ] );
+ pFncSetPixel( pDstScan, nX, aIndex, rDstMask );
+ }
+
+ DOUBLE_SCANLINES();
+ }
+}
+
+std::unique_ptr<BitmapBuffer> StretchAndConvert(
+ const BitmapBuffer& rSrcBuffer, const SalTwoRect& rTwoRect,
+ ScanlineFormat nDstBitmapFormat, std::optional<BitmapPalette> pDstPal, const ColorMask* pDstMask )
+{
+ FncGetPixel pFncGetPixel;
+ FncSetPixel pFncSetPixel;
+ std::unique_ptr<BitmapBuffer> pDstBuffer(new BitmapBuffer);
+
+ // set function for getting pixels
+ pFncGetPixel = BitmapReadAccess::GetPixelFunction( rSrcBuffer.mnFormat );
+ if( !pFncGetPixel )
+ {
+ // should never come here
+ // initialize pFncGetPixel to something valid that is
+ // least likely to crash
+ pFncGetPixel = BitmapReadAccess::GetPixelForN1BitMsbPal;
+ OSL_FAIL( "unknown read format" );
+ }
+
+ // set function for setting pixels
+ const ScanlineFormat nDstScanlineFormat = RemoveScanline( nDstBitmapFormat );
+ switch( nDstScanlineFormat )
+ {
+ IMPL_CASE_SET_FORMAT( N1BitMsbPal, 1 );
+ IMPL_CASE_SET_FORMAT( N1BitLsbPal, 1 );
+ IMPL_CASE_SET_FORMAT( N8BitPal, 8 );
+ IMPL_CASE_SET_FORMAT( N24BitTcBgr, 24 );
+ IMPL_CASE_SET_FORMAT( N24BitTcRgb, 24 );
+ IMPL_CASE_SET_FORMAT( N32BitTcAbgr, 32 );
+ IMPL_CASE_SET_FORMAT( N32BitTcArgb, 32 );
+ IMPL_CASE_SET_FORMAT( N32BitTcBgra, 32 );
+ IMPL_CASE_SET_FORMAT( N32BitTcRgba, 32 );
+ IMPL_CASE_SET_FORMAT( N32BitTcMask, 32 );
+
+ default:
+ // should never come here
+ // initialize pFncSetPixel to something valid that is
+ // least likely to crash
+ pFncSetPixel = BitmapReadAccess::SetPixelForN1BitMsbPal;
+ pDstBuffer->mnBitCount = 1;
+ OSL_FAIL( "unknown write format" );
+ break;
+ }
+
+ // fill destination buffer
+ pDstBuffer->mnFormat = nDstBitmapFormat;
+ pDstBuffer->mnWidth = rTwoRect.mnDestWidth;
+ pDstBuffer->mnHeight = rTwoRect.mnDestHeight;
+ tools::Long nScanlineBase;
+ bool bFail = o3tl::checked_multiply<tools::Long>(pDstBuffer->mnBitCount, pDstBuffer->mnWidth, nScanlineBase);
+ if (bFail)
+ {
+ SAL_WARN("vcl.gdi", "checked multiply failed");
+ pDstBuffer->mpBits = nullptr;
+ return nullptr;
+ }
+ pDstBuffer->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
+ if (pDstBuffer->mnScanlineSize < nScanlineBase/8)
+ {
+ SAL_WARN("vcl.gdi", "scanline calculation wraparound");
+ pDstBuffer->mpBits = nullptr;
+ return nullptr;
+ }
+ try
+ {
+ pDstBuffer->mpBits = new sal_uInt8[ pDstBuffer->mnScanlineSize * pDstBuffer->mnHeight ];
+ }
+ catch( const std::bad_alloc& )
+ {
+ // memory exception, clean up
+ pDstBuffer->mpBits = nullptr;
+ return nullptr;
+ }
+
+ // do we need a destination palette or color mask?
+ if( ( nDstScanlineFormat == ScanlineFormat::N1BitMsbPal ) ||
+ ( nDstScanlineFormat == ScanlineFormat::N1BitLsbPal ) ||
+ ( nDstScanlineFormat == ScanlineFormat::N8BitPal ) )
+ {
+ assert(pDstPal && "destination buffer requires palette");
+ if (!pDstPal)
+ {
+ return nullptr;
+ }
+ pDstBuffer->maPalette = *pDstPal;
+ }
+ else if(nDstScanlineFormat == ScanlineFormat::N32BitTcMask )
+ {
+ assert(pDstMask && "destination buffer requires color mask");
+ if (!pDstMask)
+ {
+ return nullptr;
+ }
+ pDstBuffer->maColorMask = *pDstMask;
+ }
+
+ // short circuit the most important conversions
+ bool bFastConvert = ImplFastBitmapConversion( *pDstBuffer, rSrcBuffer, rTwoRect );
+ if( bFastConvert )
+ return pDstBuffer;
+
+ std::unique_ptr<Scanline[]> pSrcScan;
+ std::unique_ptr<Scanline[]> pDstScan;
+ std::unique_ptr<sal_Int32[]> pMapX;
+ std::unique_ptr<sal_Int32[]> pMapY;
+
+ try
+ {
+ pSrcScan.reset(new Scanline[rSrcBuffer.mnHeight]);
+ pDstScan.reset(new Scanline[pDstBuffer->mnHeight]);
+ pMapX.reset(new sal_Int32[pDstBuffer->mnWidth]);
+ pMapY.reset(new sal_Int32[pDstBuffer->mnHeight]);
+ }
+ catch( const std::bad_alloc& )
+ {
+ // memory exception, clean up
+ // remark: the buffer ptr causing the exception
+ // is still NULL here
+ return nullptr;
+ }
+
+ // horizontal mapping table
+ if( (pDstBuffer->mnWidth != rTwoRect.mnSrcWidth) && (pDstBuffer->mnWidth != 0) )
+ {
+ const double fFactorX = static_cast<double>(rTwoRect.mnSrcWidth) / pDstBuffer->mnWidth;
+
+ for (tools::Long i = 0; i < pDstBuffer->mnWidth; ++i)
+ pMapX[ i ] = rTwoRect.mnSrcX + static_cast<int>( i * fFactorX );
+ }
+ else
+ {
+ for (tools::Long i = 0, nTmp = rTwoRect.mnSrcX ; i < pDstBuffer->mnWidth; ++i)
+ pMapX[ i ] = nTmp++;
+ }
+
+ // vertical mapping table
+ if( (pDstBuffer->mnHeight != rTwoRect.mnSrcHeight) && (pDstBuffer->mnHeight != 0) )
+ {
+ const double fFactorY = static_cast<double>(rTwoRect.mnSrcHeight) / pDstBuffer->mnHeight;
+
+ for (tools::Long i = 0; i < pDstBuffer->mnHeight; ++i)
+ pMapY[ i ] = rTwoRect.mnSrcY + static_cast<int>( i * fFactorY );
+ }
+ else
+ {
+ for (tools::Long i = 0, nTmp = rTwoRect.mnSrcY; i < pDstBuffer->mnHeight; ++i)
+ pMapY[ i ] = nTmp++;
+ }
+
+ // source scanline buffer
+ Scanline pTmpScan;
+ tools::Long nOffset;
+ if( rSrcBuffer.mnFormat & ScanlineFormat::TopDown )
+ {
+ pTmpScan = rSrcBuffer.mpBits;
+ nOffset = rSrcBuffer.mnScanlineSize;
+ }
+ else
+ {
+ pTmpScan = rSrcBuffer.mpBits + ( rSrcBuffer.mnHeight - 1 ) * rSrcBuffer.mnScanlineSize;
+ nOffset = -rSrcBuffer.mnScanlineSize;
+ }
+
+ for (tools::Long i = 0; i < rSrcBuffer.mnHeight; i++, pTmpScan += nOffset)
+ pSrcScan[ i ] = pTmpScan;
+
+ // destination scanline buffer
+ if( pDstBuffer->mnFormat & ScanlineFormat::TopDown )
+ {
+ pTmpScan = pDstBuffer->mpBits;
+ nOffset = pDstBuffer->mnScanlineSize;
+ }
+ else
+ {
+ pTmpScan = pDstBuffer->mpBits + ( pDstBuffer->mnHeight - 1 ) * pDstBuffer->mnScanlineSize;
+ nOffset = -pDstBuffer->mnScanlineSize;
+ }
+
+ for (tools::Long i = 0; i < pDstBuffer->mnHeight; i++, pTmpScan += nOffset)
+ pDstScan[ i ] = pTmpScan;
+
+ // do buffer scaling and conversion
+ if( rSrcBuffer.mnBitCount <= 8 && pDstBuffer->mnBitCount <= 8 )
+ {
+ ImplPALToPAL( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel,
+ pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() );
+ }
+ else if( rSrcBuffer.mnBitCount <= 8 && pDstBuffer->mnBitCount > 8 )
+ {
+ ImplPALToTC( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel,
+ pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() );
+ }
+ else if( rSrcBuffer.mnBitCount > 8 && pDstBuffer->mnBitCount > 8 )
+ {
+ ImplTCToTC( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel,
+ pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() );
+ }
+ else
+ {
+ ImplTCToPAL( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel,
+ pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() );
+ }
+
+ return pDstBuffer;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/scrptrun.cxx b/vcl/source/gdi/scrptrun.cxx
new file mode 100644
index 000000000..fbc68ca41
--- /dev/null
+++ b/vcl/source/gdi/scrptrun.cxx
@@ -0,0 +1,251 @@
+/*
+ *******************************************************************************
+ *
+ * Copyright (c) 1995-2013 International Business Machines Corporation and others
+ *
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * provided that the above copyright notice(s) and this permission notice appear
+ * in all copies of the Software and that both the above copyright notice(s) and
+ * this permission notice appear in supporting documentation.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN
+ * NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE
+ * LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY
+ * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Except as contained in this notice, the name of a copyright holder shall not be
+ * used in advertising or otherwise to promote the sale, use or other dealings in
+ * this Software without prior written authorization of the copyright holder.
+ *
+ *******************************************************************************
+ * file name: scrptrun.cpp
+ *
+ * created on: 10/17/2001
+ * created by: Eric R. Mader
+ */
+/**
+ * This file is largely copied from the ICU project,
+ * under folder source/extra/scrptrun/scrptrun.cpp
+ */
+
+#include <sal/config.h>
+
+#include <rtl/character.hxx>
+#include <unicode/utypes.h>
+#include <unicode/uscript.h>
+
+#include <scrptrun.h>
+#include <algorithm>
+
+namespace {
+
+struct PairIndices
+{
+ int8_t ma00[0xff];
+ int8_t ma20[0x7f];
+ int8_t ma30[0x7f];
+
+ PairIndices()
+ {
+ std::fill_n(ma00, 0xff, -1);
+ std::fill_n(ma20, 0x7f, -1);
+ std::fill_n(ma30, 0x7f, -1);
+
+ // characters in the range 0x0000 - 0x007e (inclusive)
+ // ascii paired punctuation
+ ma00[0x28] = 0;
+ ma00[0x29] = 1;
+ ma00[0x3c] = 2;
+ ma00[0x3e] = 3;
+ ma00[0x5b] = 4;
+ ma00[0x5d] = 5;
+ ma00[0x7b] = 6;
+ ma00[0x7d] = 7;
+ // guillemets
+ ma00[0xab] = 8;
+ ma00[0xbb] = 9;
+
+ // characters in the range 0x2000 - 0x207e (inclusive)
+ // general punctuation
+ ma20[0x18] = 10;
+ ma20[0x19] = 11;
+ ma20[0x1c] = 12;
+ ma20[0x1d] = 13;
+ ma20[0x39] = 14;
+ ma20[0x3a] = 15;
+
+ // characters in the range 0x3000 - 0x307e (inclusive)
+ // chinese paired punctuation
+ ma30[0x08] = 16;
+ ma30[0x09] = 17;
+ ma30[0x0a] = 18;
+ ma30[0x0b] = 19;
+ ma30[0x0c] = 20;
+ ma30[0x0d] = 21;
+ ma30[0x0e] = 22;
+ ma30[0x0f] = 23;
+ ma30[0x10] = 24;
+ ma30[0x11] = 25;
+ ma30[0x14] = 26;
+ ma30[0x15] = 27;
+ ma30[0x16] = 28;
+ ma30[0x17] = 29;
+ ma30[0x18] = 30;
+ ma30[0x19] = 31;
+ ma30[0x1a] = 32;
+ ma30[0x1b] = 33;
+ }
+
+ int32_t getPairIndex(UChar32 ch) const
+ {
+ if (ch < 0xff)
+ return ma00[ch];
+ if (ch >= 0x2000 && ch < 0x207f)
+ return ma20[ch - 0x2000];
+ if (ch >= 0x3000 && ch < 0x307f)
+ return ma30[ch - 0x3000];
+ return -1;
+ }
+
+};
+
+// There are three Unicode script codes for Japanese text, but only one
+// OpenType script tag, so we want to keep them in one run as splitting is
+// pointless for the purpose of OpenType shaping.
+UScriptCode getScript(UChar32 ch, UErrorCode* status)
+{
+ UScriptCode script = uscript_getScript(ch, status);
+ if (U_FAILURE(*status))
+ return script;
+ if (script == USCRIPT_KATAKANA || script == USCRIPT_KATAKANA_OR_HIRAGANA)
+ return USCRIPT_HIRAGANA;
+ return script;
+}
+
+}
+
+const PairIndices gPairIndices;
+
+
+namespace vcl {
+
+const char ScriptRun::fgClassID=0;
+
+static bool sameScript(int32_t scriptOne, int32_t scriptTwo)
+{
+ return scriptOne <= USCRIPT_INHERITED || scriptTwo <= USCRIPT_INHERITED || scriptOne == scriptTwo;
+}
+
+UBool ScriptRun::next()
+{
+ int32_t startSP = parenSP; // used to find the first new open character
+ UErrorCode error = U_ZERO_ERROR;
+
+ // if we've fallen off the end of the text, we're done
+ if (scriptEnd >= charLimit) {
+ return false;
+ }
+
+ scriptCode = USCRIPT_COMMON;
+
+ for (scriptStart = scriptEnd; scriptEnd < charLimit; scriptEnd += 1) {
+ UChar high = charArray[scriptEnd];
+ UChar32 ch = high;
+
+ // if the character is a high surrogate and it's not the last one
+ // in the text, see if it's followed by a low surrogate
+ if (rtl::isHighSurrogate(high) && scriptEnd < charLimit - 1)
+ {
+ UChar low = charArray[scriptEnd + 1];
+
+ // if it is followed by a low surrogate,
+ // consume it and form the full character
+ if (rtl::isLowSurrogate(low)) {
+ ch = rtl::combineSurrogates(high, low);
+ scriptEnd += 1;
+ }
+ }
+
+ UScriptCode sc = getScript(ch, &error);
+ int32_t pairIndex = gPairIndices.getPairIndex(ch);
+
+ // Paired character handling:
+
+ // if it's an open character, push it onto the stack.
+ // if it's a close character, find the matching open on the
+ // stack, and use that script code. Any non-matching open
+ // characters above it on the stack will be popped.
+ if (pairIndex >= 0) {
+ if ((pairIndex & 1) == 0) {
+ ++parenSP;
+ int32_t nVecSize = parenStack.size();
+ if (parenSP == nVecSize)
+ parenStack.resize(nVecSize + 128);
+ parenStack[parenSP].pairIndex = pairIndex;
+ parenStack[parenSP].scriptCode = scriptCode;
+ } else if (parenSP >= 0) {
+ int32_t pi = pairIndex & ~1;
+
+ while (parenSP >= 0 && parenStack[parenSP].pairIndex != pi) {
+ parenSP -= 1;
+ }
+
+ if (parenSP < startSP) {
+ startSP = parenSP;
+ }
+
+ if (parenSP >= 0) {
+ sc = parenStack[parenSP].scriptCode;
+ }
+ }
+ }
+
+ if (sameScript(scriptCode, sc)) {
+ if (scriptCode <= USCRIPT_INHERITED && sc > USCRIPT_INHERITED) {
+ scriptCode = sc;
+
+ // now that we have a final script code, fix any open
+ // characters we pushed before we knew the script code.
+ while (startSP < parenSP) {
+ parenStack[++startSP].scriptCode = scriptCode;
+ }
+ }
+
+ // if this character is a close paired character,
+ // pop it from the stack
+ if (pairIndex >= 0 && (pairIndex & 1) != 0 && parenSP >= 0) {
+ parenSP -= 1;
+ /* decrement startSP only if it is >= 0,
+ decrementing it unnecessarily will lead to memory corruption
+ while processing the above while block.
+ e.g. startSP = -4 , parenSP = -1
+ */
+ if (startSP >= 0) {
+ startSP -= 1;
+ }
+ }
+ } else {
+ // if the run broke on a surrogate pair,
+ // end it before the high surrogate
+ if (ch >= 0x10000) {
+ scriptEnd -= 1;
+ }
+
+ break;
+ }
+ }
+
+ return true;
+}
+
+}
diff --git a/vcl/source/gdi/textlayout.cxx b/vcl/source/gdi/textlayout.cxx
new file mode 100644
index 000000000..416c68343
--- /dev/null
+++ b/vcl/source/gdi/textlayout.cxx
@@ -0,0 +1,348 @@
+/* -*- 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 <vcl/ctrl.hxx>
+#include <vcl/outdev.hxx>
+
+#include <textlayout.hxx>
+
+#include <osl/diagnose.h>
+#include <tools/fract.hxx>
+#include <sal/log.hxx>
+
+#if OSL_DEBUG_LEVEL > 1
+#include <rtl/strbuf.hxx>
+#endif
+
+#include <memory>
+#include <iterator>
+
+namespace vcl
+{
+
+ DefaultTextLayout::~DefaultTextLayout()
+ {
+ }
+
+ tools::Long DefaultTextLayout::GetTextWidth( const OUString& _rText, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ return m_rTargetDevice.GetTextWidth( _rText, _nStartIndex, _nLength );
+ }
+
+ void DefaultTextLayout::DrawText( const Point& _rStartPoint, const OUString& _rText, sal_Int32 _nStartIndex,
+ sal_Int32 _nLength, std::vector< tools::Rectangle >* _pVector, OUString* _pDisplayText )
+ {
+ m_rTargetDevice.DrawText( _rStartPoint, _rText, _nStartIndex, _nLength, _pVector, _pDisplayText );
+ }
+
+ void DefaultTextLayout::GetCaretPositions( const OUString& _rText, sal_Int32* _pCaretXArray,
+ sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ m_rTargetDevice.GetCaretPositions( _rText, _pCaretXArray, _nStartIndex, _nLength );
+ }
+
+ sal_Int32 DefaultTextLayout::GetTextBreak( const OUString& _rText, tools::Long _nMaxTextWidth, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ return m_rTargetDevice.GetTextBreak( _rText, _nMaxTextWidth, _nStartIndex, _nLength );
+ }
+
+ bool DefaultTextLayout::DecomposeTextRectAction() const
+ {
+ return false;
+ }
+
+ class ReferenceDeviceTextLayout : public ITextLayout
+ {
+ public:
+ ReferenceDeviceTextLayout( const Control& _rControl, OutputDevice& _rTargetDevice, OutputDevice& _rReferenceDevice );
+ virtual ~ReferenceDeviceTextLayout();
+
+ // ITextLayout
+ virtual tools::Long GetTextWidth( const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen ) const override;
+ virtual void DrawText( const Point& _rStartPoint, const OUString& _rText, sal_Int32 _nStartIndex, sal_Int32 _nLength, std::vector< tools::Rectangle >* _pVector, OUString* _pDisplayText ) override;
+ virtual void GetCaretPositions( const OUString& _rText, sal_Int32* _pCaretXArray, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const override;
+ virtual sal_Int32 GetTextBreak(const OUString& _rText, tools::Long _nMaxTextWidth, sal_Int32 _nStartIndex, sal_Int32 _nLength) const override;
+ virtual bool DecomposeTextRectAction() const override;
+
+ public:
+ // equivalents to the respective OutputDevice methods, which take the reference device into account
+ tools::Rectangle DrawText( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle, std::vector< tools::Rectangle >* _pVector, OUString* _pDisplayText, const Size* i_pDeviceSize );
+ tools::Rectangle GetTextRect( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle, Size* o_pDeviceSize );
+
+ private:
+ tools::Long GetTextArray( const OUString& _rText, std::vector<sal_Int32>* _pDXAry, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const;
+
+ OutputDevice& m_rTargetDevice;
+ OutputDevice& m_rReferenceDevice;
+ const bool m_bRTLEnabled;
+
+ tools::Rectangle m_aCompleteTextRect;
+ };
+
+ ReferenceDeviceTextLayout::ReferenceDeviceTextLayout( const Control& _rControl, OutputDevice& _rTargetDevice,
+ OutputDevice& _rReferenceDevice )
+ :m_rTargetDevice( _rTargetDevice )
+ ,m_rReferenceDevice( _rReferenceDevice )
+ ,m_bRTLEnabled( _rControl.IsRTLEnabled() )
+ {
+ Font const aUnzoomedPointFont( _rControl.GetUnzoomedControlPointFont() );
+ const Fraction& aZoom( _rControl.GetZoom() );
+ m_rTargetDevice.Push( PushFlags::MAPMODE | PushFlags::FONT | PushFlags::TEXTLAYOUTMODE );
+
+ MapMode aTargetMapMode( m_rTargetDevice.GetMapMode() );
+ OSL_ENSURE( aTargetMapMode.GetOrigin() == Point(), "ReferenceDeviceTextLayout::ReferenceDeviceTextLayout: uhm, the code below won't work here ..." );
+
+ // normally, controls simulate "zoom" by "zooming" the font. This is responsible for (part of) the discrepancies
+ // between text in Writer and text in controls in Writer, though both have the same font.
+ // So, if we have a zoom set at the control, then we do not scale the font, but instead modify the map mode
+ // to accommodate for the zoom.
+ aTargetMapMode.SetScaleX( aZoom ); // TODO: shouldn't this be "current_scale * zoom"?
+ aTargetMapMode.SetScaleY( aZoom );
+
+ // also, use a higher-resolution map unit than "pixels", which should save us some rounding errors when
+ // translating coordinates between the reference device and the target device.
+ OSL_ENSURE( aTargetMapMode.GetMapUnit() == MapUnit::MapPixel,
+ "ReferenceDeviceTextLayout::ReferenceDeviceTextLayout: this class is not expected to work with such target devices!" );
+ // we *could* adjust all the code in this class to handle this case, but at the moment, it's not necessary
+ const MapUnit eTargetMapUnit = m_rReferenceDevice.GetMapMode().GetMapUnit();
+ aTargetMapMode.SetMapUnit( eTargetMapUnit );
+ OSL_ENSURE( aTargetMapMode.GetMapUnit() != MapUnit::MapPixel,
+ "ReferenceDeviceTextLayout::ReferenceDeviceTextLayout: a reference device which has map mode PIXEL?!" );
+
+ m_rTargetDevice.SetMapMode( aTargetMapMode );
+
+ // now that the Zoom is part of the map mode, reset the target device's font to the "unzoomed" version
+ Font aDrawFont( aUnzoomedPointFont );
+ aDrawFont.SetFontSize( OutputDevice::LogicToLogic(aDrawFont.GetFontSize(), MapMode(MapUnit::MapPoint), MapMode(eTargetMapUnit)) );
+ _rTargetDevice.SetFont( aDrawFont );
+
+ // transfer font to the reference device
+ m_rReferenceDevice.Push( PushFlags::FONT | PushFlags::TEXTLAYOUTMODE );
+ Font aRefFont( aUnzoomedPointFont );
+ aRefFont.SetFontSize( OutputDevice::LogicToLogic(
+ aRefFont.GetFontSize(), MapMode(MapUnit::MapPoint), m_rReferenceDevice.GetMapMode()) );
+ m_rReferenceDevice.SetFont( aRefFont );
+ }
+
+ ReferenceDeviceTextLayout::~ReferenceDeviceTextLayout()
+ {
+ m_rReferenceDevice.Pop();
+ m_rTargetDevice.Pop();
+ }
+
+ namespace
+ {
+ bool lcl_normalizeLength( const OUString& _rText, const sal_Int32 _nStartIndex, sal_Int32& _io_nLength )
+ {
+ sal_Int32 nTextLength = _rText.getLength();
+ if ( _nStartIndex > nTextLength )
+ return false;
+ if ( _nStartIndex + _io_nLength > nTextLength )
+ _io_nLength = nTextLength - _nStartIndex;
+ return true;
+ }
+ }
+
+ tools::Long ReferenceDeviceTextLayout::GetTextArray( const OUString& _rText, std::vector<sal_Int32>* _pDXAry, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
+ return 0;
+
+ // retrieve the character widths from the reference device
+ tools::Long nTextWidth = m_rReferenceDevice.GetTextArray( _rText, _pDXAry, _nStartIndex, _nLength );
+#if OSL_DEBUG_LEVEL > 1
+ if ( _pDXAry )
+ {
+ OStringBuffer aTrace;
+ aTrace.append( "ReferenceDeviceTextLayout::GetTextArray( " );
+ aTrace.append( OUStringToOString( _rText, RTL_TEXTENCODING_UTF8 ) );
+ aTrace.append( " ): " );
+ aTrace.append( nTextWidth );
+ aTrace.append( " = ( " );
+ for ( sal_Int32 i=0; i<_nLength; )
+ {
+ aTrace.append( _pDXAry->at(i) );
+ if ( ++i < _nLength )
+ aTrace.append( ", " );
+ }
+ aTrace.append( ")" );
+ SAL_INFO( "vcl", aTrace.makeStringAndClear() );
+ }
+#endif
+ return nTextWidth;
+ }
+
+ tools::Long ReferenceDeviceTextLayout::GetTextWidth( const OUString& _rText, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ return GetTextArray( _rText, nullptr, _nStartIndex, _nLength );
+ }
+
+ void ReferenceDeviceTextLayout::DrawText( const Point& _rStartPoint, const OUString& _rText, sal_Int32 _nStartIndex, sal_Int32 _nLength, std::vector< tools::Rectangle >* _pVector, OUString* _pDisplayText )
+ {
+ if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
+ return;
+
+ if ( _pVector && _pDisplayText )
+ {
+ std::vector< tools::Rectangle > aGlyphBounds;
+ m_rReferenceDevice.GetGlyphBoundRects( _rStartPoint, _rText, _nStartIndex, _nLength, aGlyphBounds );
+ _pVector->insert( _pVector->end(), aGlyphBounds.begin(), aGlyphBounds.end() );
+ *_pDisplayText += _rText.subView( _nStartIndex, _nLength );
+ return;
+ }
+
+ std::vector<sal_Int32> aCharWidths;
+ tools::Long nTextWidth = GetTextArray( _rText, &aCharWidths, _nStartIndex, _nLength );
+ m_rTargetDevice.DrawTextArray( _rStartPoint, _rText, aCharWidths, _nStartIndex, _nLength );
+
+ m_aCompleteTextRect.Union( tools::Rectangle( _rStartPoint, Size( nTextWidth, m_rTargetDevice.GetTextHeight() ) ) );
+ }
+
+ void ReferenceDeviceTextLayout::GetCaretPositions( const OUString& _rText, sal_Int32* _pCaretXArray,
+ sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
+ return;
+
+ // retrieve the caret positions from the reference device
+ m_rReferenceDevice.GetCaretPositions( _rText, _pCaretXArray, _nStartIndex, _nLength );
+ }
+
+ sal_Int32 ReferenceDeviceTextLayout::GetTextBreak( const OUString& _rText, tools::Long _nMaxTextWidth, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
+ return 0;
+
+ return m_rReferenceDevice.GetTextBreak( _rText, _nMaxTextWidth, _nStartIndex, _nLength );
+ }
+
+ bool ReferenceDeviceTextLayout::DecomposeTextRectAction() const
+ {
+ return true;
+ }
+
+ tools::Rectangle ReferenceDeviceTextLayout::DrawText( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle,
+ std::vector< tools::Rectangle >* _pVector, OUString* _pDisplayText, const Size* i_pDeviceSize )
+ {
+ if ( _rText.isEmpty() )
+ return tools::Rectangle();
+
+ // determine text layout mode from the RTL-ness of the control whose text we render
+ text::ComplexTextLayoutFlags nTextLayoutMode = m_bRTLEnabled ? text::ComplexTextLayoutFlags::BiDiRtl : text::ComplexTextLayoutFlags::Default;
+ m_rReferenceDevice.SetLayoutMode( nTextLayoutMode );
+ m_rTargetDevice.SetLayoutMode( nTextLayoutMode | text::ComplexTextLayoutFlags::TextOriginLeft );
+
+ // ComplexTextLayoutFlags::TextOriginLeft is because when we do actually draw the text (in DrawText( Point, ... )), then
+ // our caller gives us the left border of the draw position, regardless of script type, text layout,
+ // and the like in our ctor, we set the map mode of the target device from pixel to twip, but our caller doesn't know this,
+ // but passed pixel coordinates. So, adjust the rect.
+ tools::Rectangle aRect( m_rTargetDevice.PixelToLogic( _rRect ) );
+ if (i_pDeviceSize)
+ {
+ //if i_pDeviceSize is passed in here, it was the original pre logic-to-pixel size of _rRect
+ SAL_WARN_IF(std::abs(_rRect.GetSize().Width() - m_rTargetDevice.LogicToPixel(*i_pDeviceSize).Width()) > 1, "vcl", "DeviceSize width was expected to match Pixel width");
+ SAL_WARN_IF(std::abs(_rRect.GetSize().Height() - m_rTargetDevice.LogicToPixel(*i_pDeviceSize).Height()) > 1, "vcl", "DeviceSize height was expected to match Pixel height");
+ aRect.SetSize(*i_pDeviceSize);
+ }
+
+ m_aCompleteTextRect.SetEmpty();
+ m_rTargetDevice.DrawText( aRect, _rText, _nStyle, _pVector, _pDisplayText, this );
+ tools::Rectangle aTextRect = m_aCompleteTextRect;
+
+ if ( aTextRect.IsEmpty() && !aRect.IsEmpty() )
+ {
+ // this happens for instance if we're in a PaintToDevice call, where only a MetaFile is recorded,
+ // but no actual painting happens, so our "DrawText( Point, ... )" is never called
+ // In this case, calculate the rect from what OutputDevice::GetTextRect would give us. This has
+ // the disadvantage of less accuracy, compared with the approach to calculate the rect from the
+ // single "DrawText( Point, ... )" calls, since more intermediate arithmetic will translate
+ // from ref- to target-units.
+ aTextRect = m_rTargetDevice.GetTextRect( aRect, _rText, _nStyle, nullptr, this );
+ }
+
+ // similar to above, the text rect now contains TWIPs (or whatever unit the ref device has), but the caller
+ // expects pixel coordinates
+ aTextRect = m_rTargetDevice.LogicToPixel( aTextRect );
+
+ // convert the metric vector
+ if ( _pVector )
+ {
+ for ( auto& rCharRect : *_pVector )
+ {
+ rCharRect = m_rTargetDevice.LogicToPixel( rCharRect );
+ }
+ }
+
+ return aTextRect;
+ }
+
+ tools::Rectangle ReferenceDeviceTextLayout::GetTextRect( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle, Size* o_pDeviceSize )
+ {
+ if ( _rText.isEmpty() )
+ return tools::Rectangle();
+
+ // determine text layout mode from the RTL-ness of the control whose text we render
+ text::ComplexTextLayoutFlags nTextLayoutMode = m_bRTLEnabled ? text::ComplexTextLayoutFlags::BiDiRtl : text::ComplexTextLayoutFlags::Default;
+ m_rReferenceDevice.SetLayoutMode( nTextLayoutMode );
+ m_rTargetDevice.SetLayoutMode( nTextLayoutMode | text::ComplexTextLayoutFlags::TextOriginLeft );
+
+ // ComplexTextLayoutFlags::TextOriginLeft is because when we do actually draw the text (in DrawText( Point, ... )), then
+ // our caller gives us the left border of the draw position, regardless of script type, text layout,
+ // and the like in our ctor, we set the map mode of the target device from pixel to twip, but our caller doesn't know this,
+ // but passed pixel coordinates. So, adjust the rect.
+ tools::Rectangle aRect( m_rTargetDevice.PixelToLogic( _rRect ) );
+
+ tools::Rectangle aTextRect = m_rTargetDevice.GetTextRect( aRect, _rText, _nStyle, nullptr, this );
+
+ //if o_pDeviceSize is available, stash the pre logic-to-pixel size in it
+ if (o_pDeviceSize)
+ {
+ *o_pDeviceSize = aTextRect.GetSize();
+ }
+
+ // similar to above, the text rect now contains TWIPs (or whatever unit the ref device has), but the caller
+ // expects pixel coordinates
+ aTextRect = m_rTargetDevice.LogicToPixel( aTextRect );
+
+ return aTextRect;
+ }
+
+ ControlTextRenderer::ControlTextRenderer( const Control& _rControl, OutputDevice& _rTargetDevice, OutputDevice& _rReferenceDevice )
+ :m_pImpl( new ReferenceDeviceTextLayout( _rControl, _rTargetDevice, _rReferenceDevice ) )
+ {
+ }
+
+ ControlTextRenderer::~ControlTextRenderer()
+ {
+ }
+
+ tools::Rectangle ControlTextRenderer::DrawText( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle,
+ std::vector< tools::Rectangle >* _pVector, OUString* _pDisplayText, const Size* i_pDeviceSize )
+ {
+ return m_pImpl->DrawText( _rRect, _rText, _nStyle, _pVector, _pDisplayText, i_pDeviceSize );
+ }
+
+ tools::Rectangle ControlTextRenderer::GetTextRect( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle, Size* o_pDeviceSize = nullptr )
+ {
+ return m_pImpl->GetTextRect( _rRect, _rText, _nStyle, o_pDeviceSize );
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/vectorgraphicdata.cxx b/vcl/source/gdi/vectorgraphicdata.cxx
new file mode 100644
index 000000000..9ad27f9ca
--- /dev/null
+++ b/vcl/source/gdi/vectorgraphicdata.cxx
@@ -0,0 +1,362 @@
+/* -*- 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 <tools/diagnose_ex.h>
+#include <tools/stream.hxx>
+#include <sal/log.hxx>
+#include <vcl/vectorgraphicdata.hxx>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/graphic/PdfTools.hpp>
+#include <com/sun/star/graphic/SvgTools.hpp>
+#include <com/sun/star/graphic/EmfTools.hpp>
+#include <com/sun/star/graphic/Primitive2DTools.hpp>
+#include <com/sun/star/rendering/XIntegerReadOnlyBitmap.hpp>
+#include <com/sun/star/util/XAccounting.hpp>
+#include <com/sun/star/util/XBinaryDataContainer.hpp>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <vcl/canvastools.hxx>
+#include <comphelper/seqstream.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/wmfexternal.hxx>
+#include <vcl/pdfread.hxx>
+#include <unotools/streamwrap.hxx>
+#include <graphic/UnoBinaryDataContainer.hxx>
+
+using namespace ::com::sun::star;
+
+BitmapEx convertPrimitive2DSequenceToBitmapEx(
+ const std::deque< css::uno::Reference< css::graphic::XPrimitive2D > >& rSequence,
+ const basegfx::B2DRange& rTargetRange,
+ const sal_uInt32 nMaximumQuadraticPixels,
+ const o3tl::Length eTargetUnit,
+ const std::optional<Size>& rTargetDPI)
+{
+ BitmapEx aRetval;
+
+ if(!rSequence.empty())
+ {
+ // create replacement graphic from maSequence
+ // create XPrimitive2DRenderer
+ try
+ {
+ uno::Reference< uno::XComponentContext > xContext(::comphelper::getProcessComponentContext());
+ const uno::Reference< graphic::XPrimitive2DRenderer > xPrimitive2DRenderer = graphic::Primitive2DTools::create(xContext);
+
+ uno::Sequence< beans::PropertyValue > aViewParameters = {
+ comphelper::makePropertyValue("RangeUnit", static_cast<sal_Int32>(eTargetUnit)),
+ };
+ geometry::RealRectangle2D aRealRect;
+
+ aRealRect.X1 = rTargetRange.getMinX();
+ aRealRect.Y1 = rTargetRange.getMinY();
+ aRealRect.X2 = rTargetRange.getMaxX();
+ aRealRect.Y2 = rTargetRange.getMaxY();
+
+ // get system DPI
+ Size aDPI(Application::GetDefaultDevice()->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)));
+ if (rTargetDPI.has_value())
+ {
+ aDPI = *rTargetDPI;
+ }
+
+ const uno::Reference< rendering::XBitmap > xBitmap(
+ xPrimitive2DRenderer->rasterize(
+ comphelper::containerToSequence(rSequence),
+ aViewParameters,
+ aDPI.getWidth(),
+ aDPI.getHeight(),
+ aRealRect,
+ nMaximumQuadraticPixels));
+
+ if(xBitmap.is())
+ {
+ const uno::Reference< rendering::XIntegerReadOnlyBitmap> xIntBmp(xBitmap, uno::UNO_QUERY_THROW);
+ aRetval = vcl::unotools::bitmapExFromXBitmap(xIntBmp);
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("vcl", "Got no graphic::XPrimitive2DRenderer!");
+ }
+ catch (const std::exception& e)
+ {
+ SAL_WARN("vcl", "Got no graphic::XPrimitive2DRenderer! : " << e.what());
+ }
+ }
+
+ return aRetval;
+}
+
+static size_t estimateSize(
+ std::deque<uno::Reference<graphic::XPrimitive2D>> const& rSequence)
+{
+ size_t nRet(0);
+ for (auto& it : rSequence)
+ {
+ uno::Reference<util::XAccounting> const xAcc(it, uno::UNO_QUERY);
+ assert(xAcc.is()); // we expect only BasePrimitive2D from SVG parser
+ nRet += xAcc->estimateUsage();
+ }
+ return nRet;
+}
+
+bool VectorGraphicData::operator==(const VectorGraphicData& rCandidate) const
+{
+ if (getType() == rCandidate.getType())
+ {
+ if (maDataContainer.getSize() == rCandidate.maDataContainer.getSize())
+ {
+ if (0 == memcmp(
+ maDataContainer.getData(),
+ rCandidate.maDataContainer.getData(),
+ maDataContainer.getSize()))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void VectorGraphicData::ensurePdfReplacement()
+{
+ assert(getType() == VectorGraphicDataType::Pdf);
+
+ if (!maReplacement.IsEmpty())
+ return; // nothing to do
+
+ // use PDFium directly
+ std::vector<BitmapEx> aBitmaps;
+ sal_Int32 nUsePageIndex = 0;
+ if (mnPageIndex >= 0)
+ nUsePageIndex = mnPageIndex;
+ vcl::RenderPDFBitmaps(maDataContainer.getData(),
+ maDataContainer.getSize(), aBitmaps, nUsePageIndex, 1,
+ &maSizeHint);
+ if (!aBitmaps.empty())
+ maReplacement = aBitmaps[0];
+}
+
+void VectorGraphicData::ensureReplacement()
+{
+ if (!maReplacement.IsEmpty())
+ return; // nothing to do
+
+ // shortcut for PDF - PDFium can generate the replacement bitmap for us
+ // directly
+ if (getType() == VectorGraphicDataType::Pdf)
+ {
+ ensurePdfReplacement();
+ return;
+ }
+
+ ensureSequenceAndRange();
+
+ if (!maSequence.empty())
+ {
+ maReplacement = convertPrimitive2DSequenceToBitmapEx(maSequence, getRange());
+ }
+}
+
+void VectorGraphicData::ensureSequenceAndRange()
+{
+ if (mbSequenceCreated || maDataContainer.isEmpty())
+ return;
+
+ // import SVG to maSequence, also set maRange
+ maRange.reset();
+
+ // create Vector Graphic Data interpreter
+ uno::Reference<uno::XComponentContext> xContext(::comphelper::getProcessComponentContext());
+
+ switch (getType())
+ {
+ case VectorGraphicDataType::Svg:
+ {
+ css::uno::Sequence<sal_Int8> aDataSequence(maDataContainer.getSize());
+ std::copy(maDataContainer.cbegin(), maDataContainer.cend(), aDataSequence.getArray());
+ const uno::Reference<io::XInputStream> xInputStream(new comphelper::SequenceInputStream(aDataSequence));
+
+
+ const uno::Reference< graphic::XSvgParser > xSvgParser = graphic::SvgTools::create(xContext);
+
+ if (xInputStream.is())
+ maSequence = comphelper::sequenceToContainer<std::deque<css::uno::Reference< css::graphic::XPrimitive2D >>>(xSvgParser->getDecomposition(xInputStream, OUString()));
+
+ break;
+ }
+ case VectorGraphicDataType::Emf:
+ case VectorGraphicDataType::Wmf:
+ {
+ const uno::Reference< graphic::XEmfParser > xEmfParser = graphic::EmfTools::create(xContext);
+
+ css::uno::Sequence<sal_Int8> aDataSequence(maDataContainer.getSize());
+ std::copy(maDataContainer.cbegin(), maDataContainer.cend(), aDataSequence.getArray());
+ const uno::Reference<io::XInputStream> xInputStream(new comphelper::SequenceInputStream(aDataSequence));
+
+ if (xInputStream.is())
+ {
+ uno::Sequence< ::beans::PropertyValue > aPropertySequence;
+
+ // Pass the size hint of the graphic to the EMF parser.
+ geometry::RealPoint2D aSizeHint;
+ aSizeHint.X = maSizeHint.getX();
+ aSizeHint.Y = maSizeHint.getY();
+ xEmfParser->setSizeHint(aSizeHint);
+
+ if (!mbEnableEMFPlus)
+ {
+ aPropertySequence = { comphelper::makePropertyValue("EMFPlusEnable", uno::Any(false)) };
+ }
+
+ maSequence = comphelper::sequenceToContainer<std::deque<css::uno::Reference< css::graphic::XPrimitive2D >>>(xEmfParser->getDecomposition(xInputStream, OUString(), aPropertySequence));
+ }
+
+ break;
+ }
+ case VectorGraphicDataType::Pdf:
+ {
+ const uno::Reference<graphic::XPdfDecomposer> xPdfDecomposer = graphic::PdfTools::create(xContext);
+ uno::Sequence<beans::PropertyValue> aDecompositionParameters = comphelper::InitPropertySequence({
+ {"PageIndex", uno::Any(sal_Int32(mnPageIndex))},
+ });
+
+ rtl::Reference<UnoBinaryDataContainer> xDataContainer = new UnoBinaryDataContainer(getBinaryDataContainer());
+
+ auto xPrimitive2D = xPdfDecomposer->getDecomposition(xDataContainer, aDecompositionParameters);
+ maSequence = comphelper::sequenceToContainer<std::deque<uno::Reference<graphic::XPrimitive2D>>>(xPrimitive2D);
+
+ break;
+ }
+ }
+
+ if(!maSequence.empty())
+ {
+ const sal_Int32 nCount(maSequence.size());
+ geometry::RealRectangle2D aRealRect;
+ uno::Sequence< beans::PropertyValue > aViewParameters;
+
+ for(sal_Int32 a(0); a < nCount; a++)
+ {
+ // get reference
+ const css::uno::Reference< css::graphic::XPrimitive2D > xReference(maSequence[a]);
+
+ if(xReference.is())
+ {
+ aRealRect = xReference->getRange(aViewParameters);
+
+ maRange.expand(
+ basegfx::B2DRange(
+ aRealRect.X1,
+ aRealRect.Y1,
+ aRealRect.X2,
+ aRealRect.Y2));
+ }
+ }
+ }
+ mNestedBitmapSize = estimateSize(maSequence);
+ mbSequenceCreated = true;
+}
+
+std::pair<VectorGraphicData::State, size_t> VectorGraphicData::getSizeBytes() const
+{
+ if (!maSequence.empty() && !maDataContainer.isEmpty())
+ {
+ return std::make_pair(State::PARSED, maDataContainer.getSize() + mNestedBitmapSize);
+ }
+ else
+ {
+ return std::make_pair(State::UNPARSED, maDataContainer.getSize());
+ }
+}
+
+VectorGraphicData::VectorGraphicData(
+ const BinaryDataContainer& rDataContainer,
+ VectorGraphicDataType eVectorDataType,
+ sal_Int32 nPageIndex)
+: maDataContainer(rDataContainer),
+ mbSequenceCreated(false),
+ mNestedBitmapSize(0),
+ meType(eVectorDataType),
+ mnPageIndex(nPageIndex)
+{
+}
+
+VectorGraphicData::VectorGraphicData(
+ const OUString& rPath,
+ VectorGraphicDataType eVectorDataType)
+: mbSequenceCreated(false),
+ mNestedBitmapSize(0),
+ meType(eVectorDataType),
+ mnPageIndex(-1)
+{
+ SvFileStream rIStm(rPath, StreamMode::STD_READ);
+ if(rIStm.GetError())
+ return;
+ const sal_uInt32 nStmLen(rIStm.remainingSize());
+ if (nStmLen)
+ {
+ VectorGraphicDataArray aVectorGraphicDataArray(nStmLen);
+ auto pData = aVectorGraphicDataArray.getArray();
+ rIStm.ReadBytes(pData, nStmLen);
+
+ if (!rIStm.GetError())
+ {
+ maDataContainer = BinaryDataContainer(reinterpret_cast<const sal_uInt8*>(pData), nStmLen);
+ }
+ }
+}
+
+VectorGraphicData::~VectorGraphicData()
+{
+}
+
+const basegfx::B2DRange& VectorGraphicData::getRange() const
+{
+ const_cast< VectorGraphicData* >(this)->ensureSequenceAndRange();
+
+ return maRange;
+}
+
+const std::deque< css::uno::Reference< css::graphic::XPrimitive2D > >& VectorGraphicData::getPrimitive2DSequence() const
+{
+ const_cast< VectorGraphicData* >(this)->ensureSequenceAndRange();
+
+ return maSequence;
+}
+
+const BitmapEx& VectorGraphicData::getReplacement() const
+{
+ const_cast< VectorGraphicData* >(this)->ensureReplacement();
+
+ return maReplacement;
+}
+
+BitmapChecksum VectorGraphicData::GetChecksum() const
+{
+ return vcl_get_checksum(0, maDataContainer.getData(), maDataContainer.getSize());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/virdev.cxx b/vcl/source/gdi/virdev.cxx
new file mode 100644
index 000000000..e082e4b21
--- /dev/null
+++ b/vcl/source/gdi/virdev.cxx
@@ -0,0 +1,524 @@
+/* -*- 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 <comphelper/lok.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/pdfextoutdevdata.hxx>
+#include <vcl/virdev.hxx>
+
+#include <ImplOutDevData.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <font/PhysicalFontFaceCollection.hxx>
+#include <impfontcache.hxx>
+#include <salinst.hxx>
+#include <salgdi.hxx>
+#include <salvd.hxx>
+#include <svdata.hxx>
+
+using namespace ::com::sun::star::uno;
+
+bool VirtualDevice::CanEnableNativeWidget() const
+{
+ const vcl::ExtOutDevData* pOutDevData(GetExtOutDevData());
+ const vcl::PDFExtOutDevData* pPDFData(dynamic_cast<const vcl::PDFExtOutDevData*>(pOutDevData));
+ return pPDFData == nullptr;
+}
+
+bool VirtualDevice::AcquireGraphics() const
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( mpGraphics )
+ return true;
+
+ mbInitLineColor = true;
+ mbInitFillColor = true;
+ mbInitFont = true;
+ mbInitTextColor = true;
+ mbInitClipRegion = true;
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( mpVirDev )
+ {
+ mpGraphics = mpVirDev->AcquireGraphics();
+ // if needed retry after releasing least recently used virtual device graphics
+ while ( !mpGraphics )
+ {
+ if ( !pSVData->maGDIData.mpLastVirGraphics )
+ break;
+ pSVData->maGDIData.mpLastVirGraphics->ReleaseGraphics();
+ mpGraphics = mpVirDev->AcquireGraphics();
+ }
+ // update global LRU list of virtual device graphics
+ if ( mpGraphics )
+ {
+ mpNextGraphics = pSVData->maGDIData.mpFirstVirGraphics;
+ pSVData->maGDIData.mpFirstVirGraphics = const_cast<VirtualDevice*>(this);
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = const_cast<VirtualDevice*>(this);
+ if ( !pSVData->maGDIData.mpLastVirGraphics )
+ pSVData->maGDIData.mpLastVirGraphics = const_cast<VirtualDevice*>(this);
+ }
+ }
+
+ if ( mpGraphics )
+ {
+ mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp), RasterOp::Invert == meRasterOp );
+ mpGraphics->setAntiAlias(bool(mnAntialiasing & AntialiasingFlags::Enable));
+ }
+
+ return mpGraphics != nullptr;
+}
+
+void VirtualDevice::ReleaseGraphics( bool bRelease )
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( !mpGraphics )
+ return;
+
+ // release the fonts of the physically released graphics device
+ if ( bRelease )
+ ImplReleaseFonts();
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ VirtualDevice* pVirDev = this;
+
+ if ( bRelease )
+ pVirDev->mpVirDev->ReleaseGraphics( mpGraphics );
+ // remove from global LRU list of virtual device graphics
+ if ( mpPrevGraphics )
+ mpPrevGraphics->mpNextGraphics = mpNextGraphics;
+ else
+ pSVData->maGDIData.mpFirstVirGraphics = mpNextGraphics;
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = mpPrevGraphics;
+ else
+ pSVData->maGDIData.mpLastVirGraphics = mpPrevGraphics;
+
+ mpGraphics = nullptr;
+ mpPrevGraphics = nullptr;
+ mpNextGraphics = nullptr;
+}
+
+void VirtualDevice::ImplInitVirDev( const OutputDevice* pOutDev,
+ tools::Long nDX, tools::Long nDY, const SystemGraphicsData *pData )
+{
+ SAL_INFO( "vcl.virdev", "ImplInitVirDev(" << nDX << "," << nDY << ")" );
+
+ meRefDevMode = RefDevMode::NONE;
+ mbForceZeroExtleadBug = false;
+ mnBitCount = 0;
+ mbScreenComp = false;
+
+
+ bool bErase = nDX > 0 && nDY > 0;
+
+ if ( nDX < 1 )
+ nDX = 1;
+
+ if ( nDY < 1 )
+ nDY = 1;
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( !pOutDev )
+ pOutDev = ImplGetDefaultWindow()->GetOutDev();
+ if( !pOutDev )
+ return;
+
+ SalGraphics* pGraphics;
+ if ( !pOutDev->mpGraphics )
+ (void)pOutDev->AcquireGraphics();
+ pGraphics = pOutDev->mpGraphics;
+ if ( pGraphics )
+ mpVirDev = pSVData->mpDefInst->CreateVirtualDevice(*pGraphics, nDX, nDY, meFormat, pData);
+ else
+ mpVirDev = nullptr;
+ if ( !mpVirDev )
+ {
+ // do not abort but throw an exception, may be the current thread terminates anyway (plugin-scenario)
+ throw css::uno::RuntimeException(
+ "Could not create system bitmap!",
+ css::uno::Reference< css::uno::XInterface >() );
+ }
+
+ mnBitCount = pOutDev->GetBitCount();
+ mnOutWidth = nDX;
+ mnOutHeight = nDY;
+
+ mbScreenComp = pOutDev->IsScreenComp();
+
+ mbDevOutput = true;
+ mxFontCollection = pSVData->maGDIData.mxScreenFontList;
+ mxFontCache = pSVData->maGDIData.mxScreenFontCache;
+ mnDPIX = pOutDev->mnDPIX;
+ mnDPIY = pOutDev->mnDPIY;
+ mnDPIScalePercentage = pOutDev->mnDPIScalePercentage;
+ maFont = pOutDev->maFont;
+
+ if( maTextColor != pOutDev->maTextColor )
+ {
+ maTextColor = pOutDev->maTextColor;
+ mbInitTextColor = true;
+ }
+
+ // virtual devices have white background by default
+ SetBackground( Wallpaper( COL_WHITE ) );
+
+ // #i59283# don't erase user-provided surface
+ if( !pData && bErase)
+ Erase();
+
+ // register VirDev in the list
+ mpNext = pSVData->maGDIData.mpFirstVirDev;
+ mpPrev = nullptr;
+ if ( mpNext )
+ mpNext->mpPrev = this;
+ pSVData->maGDIData.mpFirstVirDev = this;
+}
+
+VirtualDevice::VirtualDevice(const OutputDevice* pCompDev, DeviceFormat eFormat,
+ DeviceFormat eAlphaFormat, OutDevType eOutDevType)
+ : OutputDevice(eOutDevType)
+ , meFormat(eFormat)
+ , meAlphaFormat(eAlphaFormat)
+{
+ SAL_INFO( "vcl.virdev", "VirtualDevice::VirtualDevice( " << static_cast<int>(eFormat)
+ << ", " << static_cast<int>(eAlphaFormat)
+ << ", " << static_cast<int>(eOutDevType) << " )" );
+
+ ImplInitVirDev(pCompDev ? pCompDev : Application::GetDefaultDevice(), 0, 0);
+}
+
+VirtualDevice::VirtualDevice(const SystemGraphicsData& rData, const Size &rSize,
+ DeviceFormat eFormat)
+ : OutputDevice(OUTDEV_VIRDEV)
+ , meFormat(eFormat)
+ , meAlphaFormat(DeviceFormat::NONE)
+{
+ SAL_INFO( "vcl.virdev", "VirtualDevice::VirtualDevice( " << static_cast<int>(eFormat) << " )" );
+
+ ImplInitVirDev(Application::GetDefaultDevice(), rSize.Width(), rSize.Height(), &rData);
+}
+
+VirtualDevice::~VirtualDevice()
+{
+ SAL_INFO( "vcl.virdev", "VirtualDevice::~VirtualDevice()" );
+ disposeOnce();
+}
+
+void VirtualDevice::dispose()
+{
+ SAL_INFO( "vcl.virdev", "VirtualDevice::dispose()" );
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ ReleaseGraphics();
+
+ mpVirDev.reset();
+
+ // remove this VirtualDevice from the double-linked global list
+ if( mpPrev )
+ mpPrev->mpNext = mpNext;
+ else
+ pSVData->maGDIData.mpFirstVirDev = mpNext;
+
+ if( mpNext )
+ mpNext->mpPrev = mpPrev;
+
+ OutputDevice::dispose();
+}
+
+bool VirtualDevice::InnerImplSetOutputSizePixel( const Size& rNewSize, bool bErase,
+ sal_uInt8 *const pBuffer)
+{
+ SAL_INFO( "vcl.virdev",
+ "VirtualDevice::InnerImplSetOutputSizePixel( " << rNewSize.Width() << ", "
+ << rNewSize.Height() << ", " << int(bErase) << " )" );
+
+ if ( !mpVirDev )
+ return false;
+ else if ( rNewSize == GetOutputSizePixel() )
+ {
+ if ( bErase )
+ Erase();
+ SAL_INFO( "vcl.virdev", "Trying to re-use a VirtualDevice but this time using a pre-allocated buffer");
+ return true;
+ }
+
+ bool bRet;
+ tools::Long nNewWidth = rNewSize.Width(), nNewHeight = rNewSize.Height();
+
+ if ( nNewWidth < 1 )
+ nNewWidth = 1;
+
+ if ( nNewHeight < 1 )
+ nNewHeight = 1;
+
+ if ( bErase )
+ {
+ if ( pBuffer )
+ bRet = mpVirDev->SetSizeUsingBuffer( nNewWidth, nNewHeight, pBuffer );
+ else
+ bRet = mpVirDev->SetSize( nNewWidth, nNewHeight );
+
+ if ( bRet )
+ {
+ mnOutWidth = rNewSize.Width();
+ mnOutHeight = rNewSize.Height();
+ Erase();
+ }
+ }
+ else
+ {
+ std::unique_ptr<SalVirtualDevice> pNewVirDev;
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+
+ assert(mpGraphics);
+
+ pNewVirDev = pSVData->mpDefInst->CreateVirtualDevice(*mpGraphics, nNewWidth, nNewHeight, meFormat);
+ if ( pNewVirDev )
+ {
+ SalGraphics* pGraphics = pNewVirDev->AcquireGraphics();
+ if ( pGraphics )
+ {
+ tools::Long nWidth;
+ tools::Long nHeight;
+ if ( mnOutWidth < nNewWidth )
+ nWidth = mnOutWidth;
+ else
+ nWidth = nNewWidth;
+ if ( mnOutHeight < nNewHeight )
+ nHeight = mnOutHeight;
+ else
+ nHeight = nNewHeight;
+ SalTwoRect aPosAry(0, 0, nWidth, nHeight, 0, 0, nWidth, nHeight);
+ pGraphics->CopyBits( aPosAry, *mpGraphics, *this, *this );
+ pNewVirDev->ReleaseGraphics( pGraphics );
+ ReleaseGraphics();
+ mpVirDev = std::move(pNewVirDev);
+ mnOutWidth = rNewSize.Width();
+ mnOutHeight = rNewSize.Height();
+ bRet = true;
+ }
+ else
+ {
+ bRet = false;
+ }
+ }
+ else
+ bRet = false;
+ }
+
+ return bRet;
+}
+
+// #i32109#: Fill opaque areas correctly (without relying on
+// fill/linecolor state)
+void VirtualDevice::ImplFillOpaqueRectangle( const tools::Rectangle& rRect )
+{
+ // Set line and fill color to black (->opaque),
+ // fill rect with that (linecolor, too, because of
+ // those pesky missing pixel problems)
+ Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
+ SetLineColor( COL_BLACK );
+ SetFillColor( COL_BLACK );
+ DrawRect( rRect );
+ Pop();
+}
+
+bool VirtualDevice::ImplSetOutputSizePixel( const Size& rNewSize, bool bErase,
+ sal_uInt8 *const pBuffer)
+{
+ if( InnerImplSetOutputSizePixel(rNewSize, bErase, pBuffer) )
+ {
+ if (meAlphaFormat != DeviceFormat::NONE)
+ {
+ // #110958# Setup alpha bitmap
+ if(mpAlphaVDev && mpAlphaVDev->GetOutputSizePixel() != rNewSize)
+ {
+ mpAlphaVDev.disposeAndClear();
+ }
+
+ if( !mpAlphaVDev )
+ {
+ mpAlphaVDev = VclPtr<VirtualDevice>::Create(*this, meAlphaFormat);
+ mpAlphaVDev->InnerImplSetOutputSizePixel(rNewSize, bErase, nullptr);
+ }
+
+ // TODO: copy full outdev state to new one, here. Also needed in outdev2.cxx:DrawOutDev
+ if( GetLineColor() != COL_TRANSPARENT )
+ mpAlphaVDev->SetLineColor( COL_BLACK );
+
+ if( GetFillColor() != COL_TRANSPARENT )
+ mpAlphaVDev->SetFillColor( COL_BLACK );
+
+ mpAlphaVDev->SetMapMode( GetMapMode() );
+
+ mpAlphaVDev->SetAntialiasing( GetAntialiasing() );
+
+ mpAlphaVDev->SetTextRenderModeForResolutionIndependentLayout(GetTextRenderModeForResolutionIndependentLayout());
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+void VirtualDevice::EnableRTL( bool bEnable )
+{
+ // virdevs default to not mirroring, they will only be set to mirroring
+ // under rare circumstances in the UI, eg the valueset control
+ // because each virdev has its own SalGraphics we can safely switch the SalGraphics here
+ // ...hopefully
+ if( AcquireGraphics() )
+ mpGraphics->SetLayout( bEnable ? SalLayoutFlags::BiDiRtl : SalLayoutFlags::NONE );
+
+ OutputDevice::EnableRTL(bEnable);
+}
+
+bool VirtualDevice::SetOutputSizePixel( const Size& rNewSize, bool bErase )
+{
+ return ImplSetOutputSizePixel(rNewSize, bErase, nullptr);
+}
+
+bool VirtualDevice::SetOutputSizePixelScaleOffsetAndLOKBuffer(
+ const Size& rNewSize, const Fraction& rScale, const Point& rNewOffset,
+ sal_uInt8 *const pBuffer)
+{
+ // If this is ever needed for something else than LOK, changes will
+ // be needed in SvpSalVirtualDevice::CreateSurface() .
+ assert(comphelper::LibreOfficeKit::isActive());
+ assert(pBuffer);
+ MapMode mm = GetMapMode();
+ mm.SetOrigin( rNewOffset );
+ mm.SetScaleX( rScale );
+ mm.SetScaleY( rScale );
+ SetMapMode( mm );
+ return ImplSetOutputSizePixel(rNewSize, true, pBuffer);
+}
+
+void VirtualDevice::SetReferenceDevice( RefDevMode i_eRefDevMode )
+{
+ sal_Int32 nDPIX = 600, nDPIY = 600;
+ switch( i_eRefDevMode )
+ {
+ case RefDevMode::NONE:
+ default:
+ SAL_WARN( "vcl.virdev", "VDev::SetRefDev illegal argument!" );
+ break;
+ case RefDevMode::Dpi600:
+ nDPIX = nDPIY = 600;
+ break;
+ case RefDevMode::MSO1:
+ nDPIX = nDPIY = 6*1440;
+ break;
+ case RefDevMode::PDF1:
+ nDPIX = nDPIY = 720;
+ break;
+ }
+ ImplSetReferenceDevice( i_eRefDevMode, nDPIX, nDPIY );
+}
+
+void VirtualDevice::SetReferenceDevice( sal_Int32 i_nDPIX, sal_Int32 i_nDPIY )
+{
+ ImplSetReferenceDevice( RefDevMode::Custom, i_nDPIX, i_nDPIY );
+}
+
+bool VirtualDevice::IsVirtual() const
+{
+ return true;
+}
+
+void VirtualDevice::ImplSetReferenceDevice( RefDevMode i_eRefDevMode, sal_Int32 i_nDPIX, sal_Int32 i_nDPIY )
+{
+ mnDPIX = i_nDPIX;
+ mnDPIY = i_nDPIY;
+ mnDPIScalePercentage = 100;
+
+ EnableOutput( false ); // prevent output on reference device
+ mbScreenComp = false;
+
+ // invalidate currently selected fonts
+ mbInitFont = true;
+ mbNewFont = true;
+
+ // avoid adjusting font lists when already in refdev mode
+ RefDevMode nOldRefDevMode = meRefDevMode;
+ meRefDevMode = i_eRefDevMode;
+ if( nOldRefDevMode != RefDevMode::NONE )
+ return;
+
+ // the reference device should have only scalable fonts
+ // => clean up the original font lists before getting new ones
+ mpFontInstance.clear();
+ mpFontFaceCollection.reset();
+
+ // preserve global font lists
+ ImplSVData* pSVData = ImplGetSVData();
+ mxFontCollection.reset();
+ mxFontCache.reset();
+
+ // get font list with scalable fonts only
+ (void)AcquireGraphics();
+ mxFontCollection = pSVData->maGDIData.mxScreenFontList->Clone();
+
+ // prepare to use new font lists
+ mxFontCache = std::make_shared<ImplFontCache>();
+}
+
+sal_uInt16 VirtualDevice::GetBitCount() const
+{
+ return mnBitCount;
+}
+
+bool VirtualDevice::UsePolyPolygonForComplexGradient()
+{
+ return true;
+}
+
+void VirtualDevice::Compat_ZeroExtleadBug()
+{
+ mbForceZeroExtleadBug = true;
+}
+
+tools::Long VirtualDevice::GetFontExtLeading() const
+{
+#ifdef UNX
+ // backwards compatible line metrics after fixing #i60945#
+ if ( mbForceZeroExtleadBug )
+ return 0;
+#endif
+
+ return mpFontInstance->mxFontMetric->GetExternalLeading();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/gdi/wall.cxx b/vcl/source/gdi/wall.cxx
new file mode 100644
index 000000000..7ca75b1b1
--- /dev/null
+++ b/vcl/source/gdi/wall.cxx
@@ -0,0 +1,271 @@
+/* -*- 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 <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/wall.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/TypeSerializer.hxx>
+
+SvStream& ReadWallpaper( SvStream& rIStm, Wallpaper& rImplWallpaper )
+{
+ VersionCompatRead aCompat(rIStm);
+
+ rImplWallpaper.maRect.SetEmpty();
+ rImplWallpaper.mpGradient.reset();
+ rImplWallpaper.maBitmap.SetEmpty();
+
+ // version 1
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readColor(rImplWallpaper.maColor);
+ sal_uInt16 nTmp16(0);
+ rIStm.ReadUInt16(nTmp16);
+ rImplWallpaper.meStyle = static_cast<WallpaperStyle>(nTmp16);
+
+ // version 2
+ if( aCompat.GetVersion() >= 2 )
+ {
+ bool bRect(false), bGrad(false), bBmp(false), bDummy;
+
+ rIStm.ReadCharAsBool( bRect ).ReadCharAsBool( bGrad ).ReadCharAsBool( bBmp ).ReadCharAsBool( bDummy ).ReadCharAsBool( bDummy ).ReadCharAsBool( bDummy );
+
+ if( bRect )
+ {
+ rImplWallpaper.maRect = tools::Rectangle();
+ aSerializer.readRectangle(rImplWallpaper.maRect);
+ }
+
+ if( bGrad )
+ {
+ rImplWallpaper.mpGradient.emplace();
+ aSerializer.readGradient(*rImplWallpaper.mpGradient);
+ }
+
+ if( bBmp )
+ {
+ rImplWallpaper.maBitmap.SetEmpty();
+ ReadDIBBitmapEx(rImplWallpaper.maBitmap, rIStm);
+ }
+
+ // version 3 (new color format)
+ if( aCompat.GetVersion() >= 3 )
+ {
+ sal_uInt32 nTmp;
+ rIStm.ReadUInt32(nTmp);
+ rImplWallpaper.maColor = ::Color(ColorTransparency, nTmp);
+ }
+ }
+
+ return rIStm;
+}
+
+SvStream& WriteWallpaper( SvStream& rOStm, const Wallpaper& rImplWallpaper )
+{
+ VersionCompatWrite aCompat(rOStm, 3);
+ bool bRect = !rImplWallpaper.maRect.IsEmpty();
+ bool bGrad = bool(rImplWallpaper.mpGradient);
+ bool bBmp = !rImplWallpaper.maBitmap.IsEmpty();
+ bool bDummy = false;
+
+ // version 1
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeColor(rImplWallpaper.maColor);
+
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rImplWallpaper.meStyle) );
+
+ // version 2
+ rOStm.WriteBool( bRect ).WriteBool( bGrad ).WriteBool( bBmp ).WriteBool( bDummy ).WriteBool( bDummy ).WriteBool( bDummy );
+
+ if( bRect )
+ {
+ aSerializer.writeRectangle(rImplWallpaper.maRect);
+ }
+
+ if (bGrad)
+ {
+ aSerializer.writeGradient(*rImplWallpaper.mpGradient);
+ }
+
+ if( bBmp )
+ WriteDIBBitmapEx(rImplWallpaper.maBitmap, rOStm);
+
+ // version 3 (new color format)
+ rOStm.WriteUInt32(static_cast<sal_uInt32>(rImplWallpaper.maColor));
+
+ return rOStm;
+}
+
+Wallpaper::Wallpaper() :
+ maColor( COL_TRANSPARENT ), meStyle( WallpaperStyle::NONE )
+{
+}
+
+Wallpaper::Wallpaper( const Wallpaper& ) = default;
+
+Wallpaper::Wallpaper( Wallpaper&& ) = default;
+
+Wallpaper::Wallpaper( const Color& rColor )
+{
+ maColor = rColor;
+ meStyle = WallpaperStyle::Tile;
+}
+
+Wallpaper::Wallpaper( const BitmapEx& rBmpEx )
+{
+ maBitmap = rBmpEx;
+ meStyle = WallpaperStyle::Tile;
+}
+
+Wallpaper::~Wallpaper() = default;
+
+void Wallpaper::ImplSetCachedBitmap( const BitmapEx& rBmp ) const
+{
+ maCache = rBmp;
+}
+
+const BitmapEx* Wallpaper::ImplGetCachedBitmap() const
+{
+ return maCache.IsEmpty() ? nullptr : &maCache;
+}
+
+void Wallpaper::ImplReleaseCachedBitmap() const
+{
+ maCache.SetEmpty();
+}
+
+void Wallpaper::SetColor( const Color& rColor )
+{
+ maCache.SetEmpty();
+ maColor = rColor;
+
+ if( WallpaperStyle::NONE == meStyle || WallpaperStyle::ApplicationGradient == meStyle )
+ meStyle = WallpaperStyle::Tile;
+}
+
+void Wallpaper::SetStyle( WallpaperStyle eStyle )
+{
+ if( eStyle == WallpaperStyle::ApplicationGradient )
+ // set a dummy gradient, the correct gradient
+ // will be created dynamically in GetGradient()
+ SetGradient( ImplGetApplicationGradient() );
+
+ meStyle = eStyle;
+}
+
+void Wallpaper::SetBitmap( const BitmapEx& rBitmap )
+{
+ maCache.SetEmpty();
+ maBitmap = rBitmap;
+
+ if( WallpaperStyle::NONE == meStyle || WallpaperStyle::ApplicationGradient == meStyle)
+ meStyle = WallpaperStyle::Tile;
+}
+
+const BitmapEx & Wallpaper::GetBitmap() const
+{
+ return maBitmap;
+}
+
+bool Wallpaper::IsBitmap() const
+{
+ return !maBitmap.IsEmpty();
+}
+
+void Wallpaper::SetGradient( const Gradient& rGradient )
+{
+ maCache.SetEmpty();
+ mpGradient = rGradient;
+
+ if( WallpaperStyle::NONE == meStyle || WallpaperStyle::ApplicationGradient == meStyle )
+ meStyle = WallpaperStyle::Tile;
+}
+
+Gradient Wallpaper::GetGradient() const
+{
+ if( WallpaperStyle::ApplicationGradient == meStyle )
+ return ImplGetApplicationGradient();
+ else if ( mpGradient )
+ return *mpGradient;
+ else
+ return Gradient();
+}
+
+bool Wallpaper::IsGradient() const
+{
+ return bool(mpGradient);
+}
+
+Gradient Wallpaper::ImplGetApplicationGradient()
+{
+ Gradient g;
+ g.SetAngle( 900_deg10 );
+ g.SetStyle( GradientStyle::Linear );
+ g.SetStartColor( Application::GetSettings().GetStyleSettings().GetFaceColor() );
+ // no 'extreme' gradient when high contrast
+ if( Application::GetSettings().GetStyleSettings().GetHighContrastMode() )
+ g.SetEndColor( Application::GetSettings().GetStyleSettings().GetFaceColor() );
+ else
+ g.SetEndColor( Application::GetSettings().GetStyleSettings().GetFaceGradientColor() );
+ return g;
+}
+
+bool Wallpaper::IsRect() const
+{
+ return !maRect.IsEmpty();
+}
+
+bool Wallpaper::IsFixed() const
+{
+ if ( meStyle == WallpaperStyle::NONE )
+ return false;
+ else
+ return (maBitmap.IsEmpty() && !mpGradient);
+}
+
+bool Wallpaper::IsScrollable() const
+{
+ if ( meStyle == WallpaperStyle::NONE )
+ return false;
+ else if ( maBitmap.IsEmpty() && !mpGradient )
+ return true;
+ else if ( !maBitmap.IsEmpty() )
+ return (meStyle == WallpaperStyle::Tile);
+ else
+ return false;
+}
+
+Wallpaper& Wallpaper::operator=( const Wallpaper& ) = default;
+
+Wallpaper& Wallpaper::operator=( Wallpaper&& ) = default;
+
+bool Wallpaper::operator==( const Wallpaper& rOther ) const
+{
+ return meStyle == rOther.meStyle &&
+ maColor == rOther.maColor &&
+ maRect == rOther.maRect &&
+ maBitmap == rOther.maBitmap &&
+ mpGradient == rOther.mpGradient;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/BinaryDataContainer.cxx b/vcl/source/graphic/BinaryDataContainer.cxx
new file mode 100644
index 000000000..339d28d60
--- /dev/null
+++ b/vcl/source/graphic/BinaryDataContainer.cxx
@@ -0,0 +1,36 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/BinaryDataContainer.hxx>
+#include <o3tl/hash_combine.hxx>
+
+BinaryDataContainer::BinaryDataContainer() = default;
+
+BinaryDataContainer::BinaryDataContainer(const sal_uInt8* pData, size_t nSize)
+ : mpData(std::make_shared<std::vector<sal_uInt8>>(nSize))
+{
+ std::copy(pData, pData + nSize, mpData->data());
+}
+
+BinaryDataContainer::BinaryDataContainer(std::unique_ptr<std::vector<sal_uInt8>> aData)
+ : mpData(std::shared_ptr<std::vector<sal_uInt8>>(aData.release(), aData.get_deleter()))
+{
+}
+
+size_t BinaryDataContainer::calculateHash() const
+{
+ size_t nSeed = 0;
+ o3tl::hash_combine(nSeed, getSize());
+ for (sal_uInt8 const& rByte : *mpData)
+ o3tl::hash_combine(nSeed, rByte);
+ return nSeed;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/BinaryDataContainerTools.cxx b/vcl/source/graphic/BinaryDataContainerTools.cxx
new file mode 100644
index 000000000..359a8664d
--- /dev/null
+++ b/vcl/source/graphic/BinaryDataContainerTools.cxx
@@ -0,0 +1,28 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/BinaryDataContainerTools.hxx>
+#include <graphic/UnoBinaryDataContainer.hxx>
+
+namespace vcl
+{
+BinaryDataContainer convertUnoBinaryDataContainer(
+ const css::uno::Reference<css::util::XBinaryDataContainer>& rxBinaryDataContainer)
+{
+ BinaryDataContainer aBinaryDataContainer;
+ UnoBinaryDataContainer* pUnoBinaryDataContainer
+ = comphelper::getFromUnoTunnel<UnoBinaryDataContainer>(rxBinaryDataContainer);
+ if (pUnoBinaryDataContainer)
+ aBinaryDataContainer = pUnoBinaryDataContainer->getBinaryDataContainer();
+ return aBinaryDataContainer;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/GraphicID.cxx b/vcl/source/graphic/GraphicID.cxx
new file mode 100644
index 000000000..61cdee3c2
--- /dev/null
+++ b/vcl/source/graphic/GraphicID.cxx
@@ -0,0 +1,100 @@
+/* -*- 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 <graphic/GraphicID.hxx>
+
+#include <impgraph.hxx>
+#include <rtl/strbuf.hxx>
+
+GraphicID::GraphicID(ImpGraphic const& rGraphic)
+{
+ rGraphic.ensureAvailable();
+
+ mnID1 = static_cast<sal_uLong>(rGraphic.getType()) << 28;
+ mnID2 = mnID3 = mnID4 = 0;
+
+ if (rGraphic.getType() == GraphicType::Bitmap)
+ {
+ auto const& rVectorGraphicDataPtr = rGraphic.getVectorGraphicData();
+ if (rVectorGraphicDataPtr)
+ {
+ const basegfx::B2DRange& rRange = rVectorGraphicDataPtr->getRange();
+
+ mnID1 |= rVectorGraphicDataPtr->getBinaryDataContainer().getSize();
+ mnID2 = basegfx::fround(rRange.getWidth());
+ mnID3 = basegfx::fround(rRange.getHeight());
+ mnID4 = vcl_get_checksum(0, rVectorGraphicDataPtr->getBinaryDataContainer().getData(),
+ rVectorGraphicDataPtr->getBinaryDataContainer().getSize());
+ }
+ else if (rGraphic.isAnimated())
+ {
+ const Animation aAnimation(rGraphic.getAnimation());
+
+ mnID1 |= (aAnimation.Count() & 0x0fffffff);
+ mnID2 = aAnimation.GetDisplaySizePixel().Width();
+ mnID3 = aAnimation.GetDisplaySizePixel().Height();
+ mnID4 = rGraphic.getChecksum();
+ }
+ else
+ {
+ const BitmapEx aBmpEx(rGraphic.getBitmapEx(GraphicConversionParameters()));
+
+ mnID1 |= aBmpEx.IsAlpha() ? 1 : 0;
+ mnID2 = aBmpEx.GetSizePixel().Width();
+ mnID3 = aBmpEx.GetSizePixel().Height();
+ mnID4 = rGraphic.getChecksum();
+ }
+ }
+ else if (rGraphic.getType() == GraphicType::GdiMetafile)
+ {
+ const GDIMetaFile& rMtf = rGraphic.getGDIMetaFile();
+
+ mnID1 |= (rMtf.GetActionSize() & 0x0fffffff);
+ mnID2 = rMtf.GetPrefSize().Width();
+ mnID3 = rMtf.GetPrefSize().Height();
+ mnID4 = rGraphic.getChecksum();
+ }
+}
+
+OString GraphicID::getIDString() const
+{
+ static const char aHexData[]
+ = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+ sal_Int32 nShift, nIndex = 0;
+ sal_Int32 nLen = 24 + (2 * BITMAP_CHECKSUM_SIZE);
+ OStringBuffer aHexStr(nLen);
+ aHexStr.setLength(nLen);
+
+ for (nShift = 28; nShift >= 0; nShift -= 4)
+ aHexStr[nIndex++] = aHexData[(mnID1 >> static_cast<sal_uInt32>(nShift)) & 0xf];
+
+ for (nShift = 28; nShift >= 0; nShift -= 4)
+ aHexStr[nIndex++] = aHexData[(mnID2 >> static_cast<sal_uInt32>(nShift)) & 0xf];
+
+ for (nShift = 28; nShift >= 0; nShift -= 4)
+ aHexStr[nIndex++] = aHexData[(mnID3 >> static_cast<sal_uInt32>(nShift)) & 0xf];
+
+ for (nShift = (8 * BITMAP_CHECKSUM_SIZE) - 4; nShift >= 0; nShift -= 4)
+ aHexStr[nIndex++] = aHexData[(mnID4 >> static_cast<sal_uInt32>(nShift)) & 0xf];
+
+ return aHexStr.makeStringAndClear();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/GraphicLoader.cxx b/vcl/source/graphic/GraphicLoader.cxx
new file mode 100644
index 000000000..63d35efb6
--- /dev/null
+++ b/vcl/source/graphic/GraphicLoader.cxx
@@ -0,0 +1,44 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/GraphicLoader.hxx>
+
+#include <com/sun/star/awt/XWindow.hpp>
+#include <unotools/ucbstreamhelper.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/weld.hxx>
+
+using namespace css;
+
+namespace vcl::graphic
+{
+Graphic loadFromURL(OUString const& rURL, weld::Window* pParentWin)
+{
+ Graphic aGraphic;
+
+ std::unique_ptr<SvStream> pInputStream = utl::UcbStreamHelper::CreateStream(
+ rURL, StreamMode::READ, pParentWin ? pParentWin->GetXWindow() : nullptr);
+
+ if (pInputStream)
+ {
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+
+ ErrCode nError
+ = rFilter.ImportGraphic(aGraphic, rURL, *pInputStream, GRFILTER_FORMAT_DONTKNOW,
+ nullptr, GraphicFilterImportFlags::NONE);
+ if (nError != ERRCODE_NONE || aGraphic.GetType() == GraphicType::NONE)
+ return Graphic();
+ }
+
+ return aGraphic;
+}
+} // end vcl::graphic
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/GraphicObject.cxx b/vcl/source/graphic/GraphicObject.cxx
new file mode 100644
index 000000000..86b490778
--- /dev/null
+++ b/vcl/source/graphic/GraphicObject.cxx
@@ -0,0 +1,934 @@
+/* -*- 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 <algorithm>
+
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+#include <tools/fract.hxx>
+#include <tools/helpers.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/GraphicObject.hxx>
+#include <vcl/GraphicLoader.hxx>
+#include <vcl/outdev.hxx>
+
+#include <com/sun/star/container/XNameContainer.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <memory>
+
+
+using namespace css;
+using com::sun::star::uno::Reference;
+using com::sun::star::uno::XInterface;
+using com::sun::star::uno::UNO_QUERY;
+using com::sun::star::uno::Sequence;
+using com::sun::star::container::XNameContainer;
+using com::sun::star::beans::XPropertySet;
+
+#define WATERMARK_LUM_OFFSET 50
+#define WATERMARK_CON_OFFSET -70
+
+namespace vcl::graphic
+{
+
+void SearchForGraphics(uno::Reference<uno::XInterface> const & xInterface,
+ std::vector<uno::Reference<css::graphic::XGraphic>> & raGraphicList)
+{
+ uno::Reference<beans::XPropertySet> xPropertySet(xInterface, UNO_QUERY);
+ if (xPropertySet.is())
+ {
+ if (xPropertySet->getPropertySetInfo()->hasPropertyByName("ImageURL"))
+ {
+ OUString sURL;
+ xPropertySet->getPropertyValue("ImageURL") >>= sURL;
+ if (!sURL.isEmpty() && !GraphicObject::isGraphicObjectUniqueIdURL(sURL))
+ {
+ Graphic aGraphic = vcl::graphic::loadFromURL(sURL);
+ if (!aGraphic.IsNone())
+ {
+ raGraphicList.push_back(aGraphic.GetXGraphic());
+ }
+ }
+ } else if (xPropertySet->getPropertySetInfo()->hasPropertyByName("Graphic"))
+ {
+ uno::Reference<css::graphic::XGraphic> xGraphic;
+ xPropertySet->getPropertyValue("Graphic") >>= xGraphic;
+ if (xGraphic.is())
+ {
+ raGraphicList.push_back(xGraphic);
+ }
+ }
+ }
+ Reference<XNameContainer> xContainer(xInterface, UNO_QUERY);
+ if (xContainer.is())
+ {
+ const css::uno::Sequence<OUString> aElementNames = xContainer->getElementNames();
+ for (OUString const & rName : aElementNames)
+ {
+ uno::Reference<XInterface> xInnerInterface;
+ xContainer->getByName(rName) >>= xInnerInterface;
+ SearchForGraphics(xInnerInterface, raGraphicList);
+ }
+ }
+}
+
+} // end namespace vcl::graphic
+
+namespace
+{
+
+bool lclDrawObj(OutputDevice& rOut, const Point& rPt, const Size& rSz,
+ GraphicObject const & rObj, const GraphicAttr& rAttr)
+{
+ Point aPt( rPt );
+ Size aSz( rSz );
+ bool bRet = false;
+
+ if( ( rObj.GetType() == GraphicType::Bitmap ) || ( rObj.GetType() == GraphicType::GdiMetafile ) )
+ {
+ // simple output of transformed graphic
+ const Graphic aGraphic( rObj.GetTransformedGraphic( &rAttr ) );
+
+ if( aGraphic.IsSupportedGraphic() )
+ {
+ const Degree10 nRot10 = rAttr.GetRotation() % 3600_deg10;
+
+ if( nRot10 )
+ {
+ tools::Polygon aPoly( tools::Rectangle( aPt, aSz ) );
+
+ aPoly.Rotate( aPt, nRot10 );
+ const tools::Rectangle aRotBoundRect( aPoly.GetBoundRect() );
+ aPt = aRotBoundRect.TopLeft();
+ aSz = aRotBoundRect.GetSize();
+ }
+
+ aGraphic.Draw(rOut, aPt, aSz);
+ }
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+void lclImplAdjust( BitmapEx& rBmpEx, const GraphicAttr& rAttr, GraphicAdjustmentFlags nAdjustmentFlags )
+{
+ GraphicAttr aAttr( rAttr );
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::DRAWMODE ) && aAttr.IsSpecialDrawMode() )
+ {
+ switch( aAttr.GetDrawMode() )
+ {
+ case GraphicDrawMode::Mono:
+ rBmpEx.Convert( BmpConversion::N1BitThreshold );
+ break;
+
+ case GraphicDrawMode::Greys:
+ rBmpEx.Convert( BmpConversion::N8BitGreys );
+ break;
+
+ case GraphicDrawMode::Watermark:
+ {
+ aAttr.SetLuminance( aAttr.GetLuminance() + WATERMARK_LUM_OFFSET );
+ aAttr.SetContrast( aAttr.GetContrast() + WATERMARK_CON_OFFSET );
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::COLORS ) && aAttr.IsAdjusted() )
+ {
+ rBmpEx.Adjust( aAttr.GetLuminance(), aAttr.GetContrast(),
+ aAttr.GetChannelR(), aAttr.GetChannelG(), aAttr.GetChannelB(),
+ aAttr.GetGamma(), aAttr.IsInvert() );
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::MIRROR ) && aAttr.IsMirrored() )
+ {
+ rBmpEx.Mirror( aAttr.GetMirrorFlags() );
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::ROTATE ) && aAttr.IsRotated() )
+ {
+ rBmpEx.Rotate( aAttr.GetRotation(), COL_TRANSPARENT );
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::TRANSPARENCY ) && aAttr.IsTransparent() )
+ {
+ rBmpEx.AdjustTransparency(255 - aAttr.GetAlpha());
+ }
+}
+
+void lclImplAdjust( GDIMetaFile& rMtf, const GraphicAttr& rAttr, GraphicAdjustmentFlags nAdjustmentFlags )
+{
+ GraphicAttr aAttr( rAttr );
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::DRAWMODE ) && aAttr.IsSpecialDrawMode() )
+ {
+ switch( aAttr.GetDrawMode() )
+ {
+ case GraphicDrawMode::Mono:
+ rMtf.Convert( MtfConversion::N1BitThreshold );
+ break;
+
+ case GraphicDrawMode::Greys:
+ rMtf.Convert( MtfConversion::N8BitGreys );
+ break;
+
+ case GraphicDrawMode::Watermark:
+ {
+ aAttr.SetLuminance( aAttr.GetLuminance() + WATERMARK_LUM_OFFSET );
+ aAttr.SetContrast( aAttr.GetContrast() + WATERMARK_CON_OFFSET );
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::COLORS ) && aAttr.IsAdjusted() )
+ {
+ rMtf.Adjust( aAttr.GetLuminance(), aAttr.GetContrast(),
+ aAttr.GetChannelR(), aAttr.GetChannelG(), aAttr.GetChannelB(),
+ aAttr.GetGamma(), aAttr.IsInvert() );
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::MIRROR ) && aAttr.IsMirrored() )
+ {
+ rMtf.Mirror( aAttr.GetMirrorFlags() );
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::ROTATE ) && aAttr.IsRotated() )
+ {
+ rMtf.Rotate( aAttr.GetRotation() );
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::TRANSPARENCY ) && aAttr.IsTransparent() )
+ {
+ OSL_FAIL( "Missing implementation: Mtf-Transparency" );
+ }
+}
+
+void lclImplAdjust( Animation& rAnimation, const GraphicAttr& rAttr, GraphicAdjustmentFlags nAdjustmentFlags )
+{
+ GraphicAttr aAttr( rAttr );
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::DRAWMODE ) && aAttr.IsSpecialDrawMode() )
+ {
+ switch( aAttr.GetDrawMode() )
+ {
+ case GraphicDrawMode::Mono:
+ rAnimation.Convert( BmpConversion::N1BitThreshold );
+ break;
+
+ case GraphicDrawMode::Greys:
+ rAnimation.Convert( BmpConversion::N8BitGreys );
+ break;
+
+ case GraphicDrawMode::Watermark:
+ {
+ aAttr.SetLuminance( aAttr.GetLuminance() + WATERMARK_LUM_OFFSET );
+ aAttr.SetContrast( aAttr.GetContrast() + WATERMARK_CON_OFFSET );
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::COLORS ) && aAttr.IsAdjusted() )
+ {
+ rAnimation.Adjust( aAttr.GetLuminance(), aAttr.GetContrast(),
+ aAttr.GetChannelR(), aAttr.GetChannelG(), aAttr.GetChannelB(),
+ aAttr.GetGamma(), aAttr.IsInvert() );
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::MIRROR ) && aAttr.IsMirrored() )
+ {
+ rAnimation.Mirror( aAttr.GetMirrorFlags() );
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::ROTATE ) && aAttr.IsRotated() )
+ {
+ OSL_FAIL( "Missing implementation: Animation-Rotation" );
+ }
+
+ if( ( nAdjustmentFlags & GraphicAdjustmentFlags::TRANSPARENCY ) && aAttr.IsTransparent() )
+ {
+ OSL_FAIL( "Missing implementation: Animation-Transparency" );
+ }
+}
+
+} // end anonymous namespace
+
+struct GrfSimpleCacheObj
+{
+ Graphic maGraphic;
+ GraphicAttr maAttr;
+
+ GrfSimpleCacheObj( const Graphic& rGraphic, const GraphicAttr& rAttr ) :
+ maGraphic( rGraphic ), maAttr( rAttr ) {}
+};
+
+GraphicObject::GraphicObject()
+{
+}
+
+GraphicObject::GraphicObject(const Graphic& rGraphic)
+ : maGraphic(rGraphic)
+{
+}
+
+GraphicObject::GraphicObject(const GraphicObject& rGraphicObj)
+ : maGraphic(rGraphicObj.GetGraphic())
+ , maAttr(rGraphicObj.maAttr)
+ , maUserData(rGraphicObj.maUserData)
+{
+}
+
+GraphicObject::~GraphicObject()
+{
+}
+
+GraphicType GraphicObject::GetType() const
+{
+ return maGraphic.GetType();
+}
+
+Size GraphicObject::GetPrefSize() const
+{
+ return maGraphic.GetPrefSize();
+}
+
+MapMode GraphicObject::GetPrefMapMode() const
+{
+ return maGraphic.GetPrefMapMode();
+}
+
+bool GraphicObject::IsTransparent() const
+{
+ return maGraphic.IsTransparent();
+}
+
+bool GraphicObject::IsAnimated() const
+{
+ return maGraphic.IsAnimated();
+}
+
+bool GraphicObject::IsEPS() const
+{
+ return maGraphic.IsEPS();
+}
+
+bool GraphicObject::ImplGetCropParams(const OutputDevice& rOut, Point& rPt, Size& rSz, const GraphicAttr* pAttr,
+ tools::PolyPolygon& rClipPolyPoly, bool& bRectClipRegion) const
+{
+ bool bRet = false;
+
+ if( GetType() != GraphicType::NONE )
+ {
+ tools::Polygon aClipPoly( tools::Rectangle( rPt, rSz ) );
+ const Degree10 nRot10 = pAttr->GetRotation() % 3600_deg10;
+ const Point aOldOrigin( rPt );
+ const MapMode aMap100( MapUnit::Map100thMM );
+ Size aSize100;
+ tools::Long nTotalWidth, nTotalHeight;
+
+ if( nRot10 )
+ {
+ aClipPoly.Rotate( rPt, nRot10 );
+ bRectClipRegion = false;
+ }
+ else
+ bRectClipRegion = true;
+
+ rClipPolyPoly = aClipPoly;
+
+ if (maGraphic.GetPrefMapMode().GetMapUnit() == MapUnit::MapPixel)
+ aSize100 = Application::GetDefaultDevice()->PixelToLogic( maGraphic.GetPrefSize(), aMap100 );
+ else
+ {
+ MapMode m(maGraphic.GetPrefMapMode());
+ aSize100 = rOut.LogicToLogic( maGraphic.GetPrefSize(), &m, &aMap100 );
+ }
+
+ nTotalWidth = aSize100.Width() - pAttr->GetLeftCrop() - pAttr->GetRightCrop();
+ nTotalHeight = aSize100.Height() - pAttr->GetTopCrop() - pAttr->GetBottomCrop();
+
+ if( !aSize100.IsEmpty() && nTotalWidth > 0 && nTotalHeight > 0 )
+ {
+ double fScale = static_cast<double>(aSize100.Width()) / nTotalWidth;
+ const tools::Long nNewLeft = -FRound( ( ( pAttr->GetMirrorFlags() & BmpMirrorFlags::Horizontal ) ? pAttr->GetRightCrop() : pAttr->GetLeftCrop() ) * fScale );
+ const tools::Long nNewRight = nNewLeft + FRound( aSize100.Width() * fScale ) - 1;
+
+ fScale = static_cast<double>(rSz.Width()) / aSize100.Width();
+ rPt.AdjustX(FRound( nNewLeft * fScale ) );
+ rSz.setWidth( FRound( ( nNewRight - nNewLeft + 1 ) * fScale ) );
+
+ fScale = static_cast<double>(aSize100.Height()) / nTotalHeight;
+ const tools::Long nNewTop = -FRound( ( ( pAttr->GetMirrorFlags() & BmpMirrorFlags::Vertical ) ? pAttr->GetBottomCrop() : pAttr->GetTopCrop() ) * fScale );
+ const tools::Long nNewBottom = nNewTop + FRound( aSize100.Height() * fScale ) - 1;
+
+ fScale = static_cast<double>(rSz.Height()) / aSize100.Height();
+ rPt.AdjustY(FRound( nNewTop * fScale ) );
+ rSz.setHeight( FRound( ( nNewBottom - nNewTop + 1 ) * fScale ) );
+
+ if( nRot10 )
+ {
+ tools::Polygon aOriginPoly( 1 );
+
+ aOriginPoly[ 0 ] = rPt;
+ aOriginPoly.Rotate( aOldOrigin, nRot10 );
+ rPt = aOriginPoly[ 0 ];
+ }
+
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+GraphicObject& GraphicObject::operator=( const GraphicObject& rGraphicObj )
+{
+ if( &rGraphicObj != this )
+ {
+ mxSimpleCache.reset();
+ maGraphic = rGraphicObj.GetGraphic();
+ maAttr = rGraphicObj.maAttr;
+ maUserData = rGraphicObj.maUserData;
+ }
+
+ return *this;
+}
+
+bool GraphicObject::operator==( const GraphicObject& rGraphicObj ) const
+{
+ return rGraphicObj.maGraphic == maGraphic
+ && rGraphicObj.maAttr == maAttr;
+}
+
+OString GraphicObject::GetUniqueID() const
+{
+ return GetGraphic().getUniqueID();
+}
+
+void GraphicObject::SetAttr( const GraphicAttr& rAttr )
+{
+ maAttr = rAttr;
+
+ if (mxSimpleCache && (mxSimpleCache->maAttr != rAttr))
+ mxSimpleCache.reset();
+}
+
+void GraphicObject::SetUserData()
+{
+ maUserData.clear();
+}
+
+void GraphicObject::SetUserData( const OUString& rUserData )
+{
+ maUserData = rUserData;
+}
+
+bool GraphicObject::Draw(OutputDevice& rOut, const Point& rPt, const Size& rSz,
+ const GraphicAttr* pAttr) const
+{
+ GraphicAttr aAttr( pAttr ? *pAttr : GetAttr() );
+ Point aPt( rPt );
+ Size aSz( rSz );
+ const DrawModeFlags nOldDrawMode = rOut.GetDrawMode();
+ bool bCropped = aAttr.IsCropped();
+ bool bRet;
+
+ rOut.SetDrawMode(nOldDrawMode & ~DrawModeFlags( DrawModeFlags::SettingsLine | DrawModeFlags::SettingsFill | DrawModeFlags::SettingsText | DrawModeFlags::SettingsGradient ));
+
+ // mirrored horizontally
+ if( aSz.Width() < 0 )
+ {
+ aPt.AdjustX(aSz.Width() + 1 );
+ aSz.setWidth( -aSz.Width() );
+ aAttr.SetMirrorFlags( aAttr.GetMirrorFlags() ^ BmpMirrorFlags::Horizontal );
+ }
+
+ // mirrored vertically
+ if( aSz.Height() < 0 )
+ {
+ aPt.AdjustY(aSz.Height() + 1 );
+ aSz.setHeight( -aSz.Height() );
+ aAttr.SetMirrorFlags( aAttr.GetMirrorFlags() ^ BmpMirrorFlags::Vertical );
+ }
+
+ if( bCropped )
+ {
+ tools::PolyPolygon aClipPolyPoly;
+ bool bRectClip;
+ const bool bCrop = ImplGetCropParams(rOut, aPt, aSz, &aAttr, aClipPolyPoly, bRectClip);
+
+ rOut.Push(vcl::PushFlags::CLIPREGION);
+
+ if( bCrop )
+ {
+ if( bRectClip )
+ {
+ // #i29534# Store crop rect for later forwarding to
+ // PDF writer
+ tools::Rectangle aCropRect = aClipPolyPoly.GetBoundRect();
+ rOut.IntersectClipRegion(aCropRect);
+ }
+ else
+ {
+ rOut.IntersectClipRegion(vcl::Region(aClipPolyPoly));
+ }
+ }
+ }
+
+ bRet = lclDrawObj(rOut, aPt, aSz, *this, aAttr);
+
+ if( bCropped )
+ rOut.Pop();
+
+ rOut.SetDrawMode( nOldDrawMode );
+
+ return bRet;
+}
+
+void GraphicObject::DrawTiled(OutputDevice& rOut, const tools::Rectangle& rArea, const Size& rSize,
+ const Size& rOffset, int nTileCacheSize1D)
+{
+ if (rSize.IsEmpty())
+ return;
+
+ const MapMode aOutMapMode(rOut.GetMapMode());
+ // #106258# Clamp size to 1 for zero values. This is okay, since
+ // logical size of zero is handled above already
+ const Size aOutTileSize( ::std::max( tools::Long(1), rOut.LogicToPixel( rSize, aOutMapMode ).Width() ),
+ ::std::max( tools::Long(1), rOut.LogicToPixel( rSize, aOutMapMode ).Height() ) );
+
+ //#i69780 clip final tile size to a sane max size
+ while ((static_cast<sal_Int64>(rSize.Width()) * nTileCacheSize1D) > SAL_MAX_UINT16)
+ nTileCacheSize1D /= 2;
+ while ((static_cast<sal_Int64>(rSize.Height()) * nTileCacheSize1D) > SAL_MAX_UINT16)
+ nTileCacheSize1D /= 2;
+
+ ImplDrawTiled(rOut, rArea, aOutTileSize, rOffset, nullptr, nTileCacheSize1D);
+}
+
+bool GraphicObject::StartAnimation(OutputDevice& rOut, const Point& rPt, const Size& rSz,
+ tools::Long nExtraData,
+ OutputDevice* pFirstFrameOutDev)
+{
+ bool bRet = false;
+
+ GetGraphic();
+
+ const GraphicAttr aAttr( GetAttr() );
+
+ if (IsAnimated())
+ {
+ Point aPt( rPt );
+ Size aSz( rSz );
+ bool bCropped = aAttr.IsCropped();
+
+ if( bCropped )
+ {
+ tools::PolyPolygon aClipPolyPoly;
+ bool bRectClip;
+ const bool bCrop = ImplGetCropParams(rOut, aPt, aSz, &aAttr, aClipPolyPoly, bRectClip);
+
+ rOut.Push(vcl::PushFlags::CLIPREGION);
+
+ if( bCrop )
+ {
+ if( bRectClip )
+ rOut.IntersectClipRegion(aClipPolyPoly.GetBoundRect());
+ else
+ rOut.IntersectClipRegion(vcl::Region(aClipPolyPoly));
+ }
+ }
+
+ if (!mxSimpleCache || (mxSimpleCache->maAttr != aAttr) || pFirstFrameOutDev)
+ {
+ mxSimpleCache.reset(new GrfSimpleCacheObj(GetTransformedGraphic(&aAttr), aAttr));
+ mxSimpleCache->maGraphic.SetAnimationNotifyHdl(GetGraphic().GetAnimationNotifyHdl());
+ }
+
+ mxSimpleCache->maGraphic.StartAnimation(rOut, aPt, aSz, nExtraData, pFirstFrameOutDev);
+
+ if( bCropped )
+ rOut.Pop();
+
+ bRet = true;
+ }
+ else
+ bRet = Draw(rOut, rPt, rSz, &aAttr);
+
+ return bRet;
+}
+
+void GraphicObject::StopAnimation( const OutputDevice* pOut, tools::Long nExtraData )
+{
+ if (mxSimpleCache)
+ mxSimpleCache->maGraphic.StopAnimation(pOut, nExtraData);
+}
+
+const Graphic& GraphicObject::GetGraphic() const
+{
+ return maGraphic;
+}
+
+void GraphicObject::SetGraphic( const Graphic& rGraphic)
+{
+ maGraphic = rGraphic;
+}
+
+Graphic GraphicObject::GetTransformedGraphic( const Size& rDestSize, const MapMode& rDestMap, const GraphicAttr& rAttr ) const
+{
+ // #104550# Extracted from svx/source/svdraw/svdograf.cxx
+ Graphic aTransGraphic( GetGraphic() );
+ const GraphicType eType = GetType();
+ const Size aSrcSize( aTransGraphic.GetPrefSize() );
+
+ // #104115# Convert the crop margins to graphic object mapmode
+ const MapMode aMapGraph( aTransGraphic.GetPrefMapMode() );
+ const MapMode aMap100( MapUnit::Map100thMM );
+
+ Size aCropLeftTop;
+ Size aCropRightBottom;
+
+ if( GraphicType::GdiMetafile == eType )
+ {
+ GDIMetaFile aMtf( aTransGraphic.GetGDIMetaFile() );
+
+ if (aMapGraph.GetMapUnit() == MapUnit::MapPixel)
+ {
+ // crops are in 1/100th mm -> to aMapGraph -> to MapUnit::MapPixel
+ aCropLeftTop = Application::GetDefaultDevice()->LogicToPixel(
+ Size(rAttr.GetLeftCrop(), rAttr.GetTopCrop()),
+ aMap100);
+ aCropRightBottom = Application::GetDefaultDevice()->LogicToPixel(
+ Size(rAttr.GetRightCrop(), rAttr.GetBottomCrop()),
+ aMap100);
+ }
+ else
+ {
+ // crops are in GraphicObject units -> to aMapGraph
+ aCropLeftTop = OutputDevice::LogicToLogic(
+ Size(rAttr.GetLeftCrop(), rAttr.GetTopCrop()),
+ aMap100,
+ aMapGraph);
+ aCropRightBottom = OutputDevice::LogicToLogic(
+ Size(rAttr.GetRightCrop(), rAttr.GetBottomCrop()),
+ aMap100,
+ aMapGraph);
+ }
+
+ // #104115# If the metafile is cropped, give it a special
+ // treatment: clip against the remaining area, scale up such
+ // that this area later fills the desired size, and move the
+ // origin to the upper left edge of that area.
+ if( rAttr.IsCropped() )
+ {
+ const MapMode aMtfMapMode( aMtf.GetPrefMapMode() );
+
+ tools::Rectangle aClipRect( aMtfMapMode.GetOrigin().X() + aCropLeftTop.Width(),
+ aMtfMapMode.GetOrigin().Y() + aCropLeftTop.Height(),
+ aMtfMapMode.GetOrigin().X() + aSrcSize.Width() - aCropRightBottom.Width(),
+ aMtfMapMode.GetOrigin().Y() + aSrcSize.Height() - aCropRightBottom.Height() );
+
+ // #104115# To correctly crop rotated metafiles, clip by view rectangle
+ aMtf.AddAction( new MetaISectRectClipRegionAction( aClipRect ), 0 );
+
+ // #104115# To crop the metafile, scale larger than the output rectangle
+ aMtf.Scale( static_cast<double>(rDestSize.Width()) / (aSrcSize.Width() - aCropLeftTop.Width() - aCropRightBottom.Width()),
+ static_cast<double>(rDestSize.Height()) / (aSrcSize.Height() - aCropLeftTop.Height() - aCropRightBottom.Height()) );
+
+ // #104115# Adapt the pref size by hand (scale changes it
+ // proportionally, but we want it to be smaller than the
+ // former size, to crop the excess out)
+ aMtf.SetPrefSize( Size( static_cast<tools::Long>(static_cast<double>(rDestSize.Width()) * (1.0 + (aCropLeftTop.Width() + aCropRightBottom.Width()) / aSrcSize.Width()) + .5),
+ static_cast<tools::Long>(static_cast<double>(rDestSize.Height()) * (1.0 + (aCropLeftTop.Height() + aCropRightBottom.Height()) / aSrcSize.Height()) + .5) ) );
+
+ // #104115# Adapt the origin of the new mapmode, such that it
+ // is shifted to the place where the cropped output starts
+ Point aNewOrigin( static_cast<tools::Long>(static_cast<double>(aMtfMapMode.GetOrigin().X()) + rDestSize.Width() * aCropLeftTop.Width() / (aSrcSize.Width() - aCropLeftTop.Width() - aCropRightBottom.Width()) + .5),
+ static_cast<tools::Long>(static_cast<double>(aMtfMapMode.GetOrigin().Y()) + rDestSize.Height() * aCropLeftTop.Height() / (aSrcSize.Height() - aCropLeftTop.Height() - aCropRightBottom.Height()) + .5) );
+ MapMode aNewMap( rDestMap );
+ aNewMap.SetOrigin( OutputDevice::LogicToLogic(aNewOrigin, aMtfMapMode, rDestMap) );
+ aMtf.SetPrefMapMode( aNewMap );
+ }
+ else
+ {
+ aMtf.Scale( Fraction( rDestSize.Width(), aSrcSize.Width() ), Fraction( rDestSize.Height(), aSrcSize.Height() ) );
+ aMtf.SetPrefMapMode( rDestMap );
+ }
+
+ aTransGraphic = aMtf;
+ }
+ else if( GraphicType::Bitmap == eType )
+ {
+ BitmapEx aBitmapEx( aTransGraphic.GetBitmapEx() );
+ tools::Rectangle aCropRect;
+
+ // convert crops to pixel
+ if(rAttr.IsCropped())
+ {
+ if (aMapGraph.GetMapUnit() == MapUnit::MapPixel)
+ {
+ // crops are in 1/100th mm -> to MapUnit::MapPixel
+ aCropLeftTop = Application::GetDefaultDevice()->LogicToPixel(
+ Size(rAttr.GetLeftCrop(), rAttr.GetTopCrop()),
+ aMap100);
+ aCropRightBottom = Application::GetDefaultDevice()->LogicToPixel(
+ Size(rAttr.GetRightCrop(), rAttr.GetBottomCrop()),
+ aMap100);
+ }
+ else
+ {
+ // crops are in GraphicObject units -> to MapUnit::MapPixel
+ aCropLeftTop = Application::GetDefaultDevice()->LogicToPixel(
+ Size(rAttr.GetLeftCrop(), rAttr.GetTopCrop()),
+ aMapGraph);
+ aCropRightBottom = Application::GetDefaultDevice()->LogicToPixel(
+ Size(rAttr.GetRightCrop(), rAttr.GetBottomCrop()),
+ aMapGraph);
+ }
+
+ // convert from prefmapmode to pixel
+ Size aSrcSizePixel(
+ Application::GetDefaultDevice()->LogicToPixel(
+ aSrcSize,
+ aMapGraph));
+
+ if(rAttr.IsCropped()
+ && (aSrcSizePixel.Width() != aBitmapEx.GetSizePixel().Width() || aSrcSizePixel.Height() != aBitmapEx.GetSizePixel().Height())
+ && aSrcSizePixel.Width())
+ {
+ // the size in pixels calculated from Graphic's internal MapMode (aTransGraphic.GetPrefMapMode())
+ // and its internal size (aTransGraphic.GetPrefSize()) is different from its real pixel size.
+ // This can be interpreted as this values to be set wrong, but needs to be corrected since e.g.
+ // existing cropping is calculated based on this logic values already.
+ // aBitmapEx.Scale(aSrcSizePixel);
+
+ // another possibility is to adapt the values created so far with a factor; this
+ // will keep the original Bitmap untouched and thus quality will not change
+ // caution: convert to double first, else pretty big errors may occur
+ const double fFactorX(static_cast<double>(aBitmapEx.GetSizePixel().Width()) / aSrcSizePixel.Width());
+ const double fFactorY(static_cast<double>(aBitmapEx.GetSizePixel().Height()) / aSrcSizePixel.Height());
+
+ aCropLeftTop.setWidth( basegfx::fround(aCropLeftTop.Width() * fFactorX) );
+ aCropLeftTop.setHeight( basegfx::fround(aCropLeftTop.Height() * fFactorY) );
+ aCropRightBottom.setWidth( basegfx::fround(aCropRightBottom.Width() * fFactorX) );
+ aCropRightBottom.setHeight( basegfx::fround(aCropRightBottom.Height() * fFactorY) );
+
+ aSrcSizePixel = aBitmapEx.GetSizePixel();
+ }
+
+ // setup crop rectangle in pixel
+ aCropRect = tools::Rectangle( aCropLeftTop.Width(), aCropLeftTop.Height(),
+ aSrcSizePixel.Width() - aCropRightBottom.Width(),
+ aSrcSizePixel.Height() - aCropRightBottom.Height() );
+ }
+
+ // #105641# Also crop animations
+ if( aTransGraphic.IsAnimated() )
+ {
+ Animation aAnim( aTransGraphic.GetAnimation() );
+
+ for( size_t nFrame=0; nFrame<aAnim.Count(); ++nFrame )
+ {
+ AnimationBitmap aAnimationBitmap( aAnim.Get( nFrame ) );
+
+ if( !aCropRect.Contains( tools::Rectangle(aAnimationBitmap.maPositionPixel, aAnimationBitmap.maSizePixel) ) )
+ {
+ // setup actual cropping (relative to frame position)
+ tools::Rectangle aCropRectRel( aCropRect );
+ aCropRectRel.Move( -aAnimationBitmap.maPositionPixel.X(),
+ -aAnimationBitmap.maPositionPixel.Y() );
+
+ // cropping affects this frame, apply it then
+ // do _not_ apply enlargement, this is done below
+ ImplTransformBitmap( aAnimationBitmap.maBitmapEx, rAttr, Size(), Size(),
+ aCropRectRel, rDestSize, false );
+
+ aAnim.Replace( aAnimationBitmap, nFrame );
+ }
+ // else: bitmap completely within crop area,
+ // i.e. nothing is cropped away
+ }
+
+ // now, apply enlargement (if any) through global animation size
+ if( aCropLeftTop.Width() < 0 ||
+ aCropLeftTop.Height() < 0 ||
+ aCropRightBottom.Width() < 0 ||
+ aCropRightBottom.Height() < 0 )
+ {
+ Size aNewSize( aAnim.GetDisplaySizePixel() );
+ aNewSize.AdjustWidth(aCropRightBottom.Width() < 0 ? -aCropRightBottom.Width() : 0 );
+ aNewSize.AdjustWidth(aCropLeftTop.Width() < 0 ? -aCropLeftTop.Width() : 0 );
+ aNewSize.AdjustHeight(aCropRightBottom.Height() < 0 ? -aCropRightBottom.Height() : 0 );
+ aNewSize.AdjustHeight(aCropLeftTop.Height() < 0 ? -aCropLeftTop.Height() : 0 );
+ aAnim.SetDisplaySizePixel( aNewSize );
+ }
+
+ // if topleft has changed, we must move all frames to the
+ // right and bottom, resp.
+ if( aCropLeftTop.Width() < 0 ||
+ aCropLeftTop.Height() < 0 )
+ {
+ Point aPosOffset( aCropLeftTop.Width() < 0 ? -aCropLeftTop.Width() : 0,
+ aCropLeftTop.Height() < 0 ? -aCropLeftTop.Height() : 0 );
+
+ for( size_t nFrame=0; nFrame<aAnim.Count(); ++nFrame )
+ {
+ AnimationBitmap aAnimationBitmap( aAnim.Get( nFrame ) );
+
+ aAnimationBitmap.maPositionPixel += aPosOffset;
+
+ aAnim.Replace( aAnimationBitmap, nFrame );
+ }
+ }
+
+ aTransGraphic = aAnim;
+ }
+ else
+ {
+ ImplTransformBitmap( aBitmapEx, rAttr, aCropLeftTop, aCropRightBottom,
+ aCropRect, rDestSize, true );
+
+ aTransGraphic = aBitmapEx;
+ }
+
+ aTransGraphic.SetPrefSize( rDestSize );
+ aTransGraphic.SetPrefMapMode( rDestMap );
+ }
+
+ GraphicObject aGrfObj( aTransGraphic );
+ aTransGraphic = aGrfObj.GetTransformedGraphic( &rAttr );
+
+ return aTransGraphic;
+}
+
+Graphic GraphicObject::GetTransformedGraphic( const GraphicAttr* pAttr ) const
+{
+ GetGraphic();
+
+ Graphic aGraphic;
+ GraphicAttr aAttr( pAttr ? *pAttr : GetAttr() );
+
+ if (maGraphic.IsSupportedGraphic())
+ {
+ if( aAttr.IsSpecialDrawMode() || aAttr.IsAdjusted() || aAttr.IsMirrored() || aAttr.IsRotated() || aAttr.IsTransparent() )
+ {
+ if( GetType() == GraphicType::Bitmap )
+ {
+ if( IsAnimated() )
+ {
+ Animation aAnimation( maGraphic.GetAnimation() );
+ lclImplAdjust( aAnimation, aAttr, GraphicAdjustmentFlags::ALL );
+ aAnimation.SetLoopCount(maGraphic.GetAnimationLoopCount());
+ aGraphic = aAnimation;
+ }
+ else
+ {
+ BitmapEx aBmpEx( maGraphic.GetBitmapEx() );
+ lclImplAdjust( aBmpEx, aAttr, GraphicAdjustmentFlags::ALL );
+ aGraphic = aBmpEx;
+ }
+ }
+ else
+ {
+ GDIMetaFile aMtf( maGraphic.GetGDIMetaFile() );
+ lclImplAdjust( aMtf, aAttr, GraphicAdjustmentFlags::ALL );
+ aGraphic = aMtf;
+ }
+ }
+ else
+ {
+ if( ( GetType() == GraphicType::Bitmap ) && IsAnimated() )
+ {
+ Animation aAnimation( maGraphic.GetAnimation() );
+ aAnimation.SetLoopCount(maGraphic.GetAnimationLoopCount());
+ aGraphic = aAnimation;
+ }
+ else
+ aGraphic = maGraphic;
+ }
+ }
+
+ return aGraphic;
+}
+
+bool GraphicObject::isGraphicObjectUniqueIdURL(std::u16string_view rURL)
+{
+ return o3tl::starts_with(rURL, u"vnd.sun.star.GraphicObject:");
+}
+
+// calculate scalings between real image size and logic object size. This
+// is necessary since the crop values are relative to original bitmap size
+basegfx::B2DVector GraphicObject::calculateCropScaling(
+ double fWidth,
+ double fHeight,
+ double fLeftCrop,
+ double fTopCrop,
+ double fRightCrop,
+ double fBottomCrop) const
+{
+ const MapMode aMapMode100thmm(MapUnit::Map100thMM);
+ Size aBitmapSize(GetPrefSize());
+ double fFactorX(1.0);
+ double fFactorY(1.0);
+
+ if(MapUnit::MapPixel == GetPrefMapMode().GetMapUnit())
+ {
+ aBitmapSize = Application::GetDefaultDevice()->PixelToLogic(aBitmapSize, aMapMode100thmm);
+ }
+ else
+ {
+ aBitmapSize = OutputDevice::LogicToLogic(aBitmapSize, GetPrefMapMode(), aMapMode100thmm);
+ }
+
+ const double fDivX(aBitmapSize.Width() - fLeftCrop - fRightCrop);
+ const double fDivY(aBitmapSize.Height() - fTopCrop - fBottomCrop);
+
+ if(!basegfx::fTools::equalZero(fDivX))
+ {
+ fFactorX = fabs(fWidth) / fDivX;
+ }
+
+ if(!basegfx::fTools::equalZero(fDivY))
+ {
+ fFactorY = fabs(fHeight) / fDivY;
+ }
+
+ return basegfx::B2DVector(fFactorX,fFactorY);
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/GraphicObject2.cxx b/vcl/source/graphic/GraphicObject2.cxx
new file mode 100644
index 000000000..89af907e2
--- /dev/null
+++ b/vcl/source/graphic/GraphicObject2.cxx
@@ -0,0 +1,503 @@
+/* -*- 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 <tools/gen.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/alpha.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/GraphicObject.hxx>
+#include <memory>
+
+struct ImplTileInfo
+{
+ ImplTileInfo() : nTilesEmptyX(0), nTilesEmptyY(0) {}
+
+ Point aTileTopLeft; // top, left position of the rendered tile
+ Point aNextTileTopLeft; // top, left position for next recursion
+ // level's tile
+ Size aTileSizePixel; // size of the generated tile (might
+ // differ from
+ // aNextTileTopLeft-aTileTopLeft, because
+ // this is nExponent*prevTileSize. The
+ // generated tile is always nExponent
+ // times the previous tile, such that it
+ // can be used in the next stage. The
+ // required area coverage is often
+ // less. The extraneous area covered is
+ // later overwritten by the next stage)
+ int nTilesEmptyX; // number of original tiles empty right of
+ // this tile. This counts from
+ // aNextTileTopLeft, i.e. the additional
+ // area covered by aTileSizePixel is not
+ // considered here. This is for
+ // unification purposes, as the iterative
+ // calculation of the next level's empty
+ // tiles has to be based on this value.
+ int nTilesEmptyY; // as above, for Y
+};
+
+
+bool GraphicObject::ImplRenderTempTile( VirtualDevice& rVDev,
+ int nNumTilesX, int nNumTilesY,
+ const Size& rTileSizePixel,
+ const GraphicAttr* pAttr )
+{
+ // how many tiles to generate per recursion step
+ const int nExponent = 2;
+
+ // determine MSB factor
+ int nMSBFactor( 1 );
+ while( nNumTilesX / nMSBFactor != 0 ||
+ nNumTilesY / nMSBFactor != 0 )
+ {
+ nMSBFactor *= nExponent;
+ }
+
+ // one less
+ if(nMSBFactor > 1)
+ {
+ nMSBFactor /= nExponent;
+ }
+ ImplTileInfo aTileInfo;
+
+ // #105229# Switch off mapping (converting to logic and back to
+ // pixel might cause roundoff errors)
+ bool bOldMap( rVDev.IsMapModeEnabled() );
+ rVDev.EnableMapMode( false );
+
+ bool bRet( ImplRenderTileRecursive( rVDev, nExponent, nMSBFactor, nNumTilesX, nNumTilesY,
+ nNumTilesX, nNumTilesY, rTileSizePixel, pAttr, aTileInfo ) );
+
+ rVDev.EnableMapMode( bOldMap );
+
+ return bRet;
+}
+
+// define for debug drawings
+//#define DBG_TEST
+
+// see header comment. this works similar to base conversion of a
+// number, i.e. if the exponent is 10, then the number for every tile
+// size is given by the decimal place of the corresponding decimal
+// representation.
+bool GraphicObject::ImplRenderTileRecursive( VirtualDevice& rVDev, int nExponent, int nMSBFactor,
+ int nNumOrigTilesX, int nNumOrigTilesY,
+ int nRemainderTilesX, int nRemainderTilesY,
+ const Size& rTileSizePixel, const GraphicAttr* pAttr,
+ ImplTileInfo& rTileInfo )
+{
+ // gets loaded with our tile bitmap
+ std::unique_ptr<GraphicObject> xTmpGraphic;
+ GraphicObject* pTileGraphic;
+
+ // stores a flag that renders the zero'th tile position
+ // (i.e. (0,0)+rCurrPos) only if we're at the bottom of the
+ // recursion stack. All other position already have that tile
+ // rendered, because the lower levels painted their generated tile
+ // there.
+ bool bNoFirstTileDraw( false );
+
+ // what's left when we're done with our tile size
+ const int nNewRemainderX( nRemainderTilesX % nMSBFactor );
+ const int nNewRemainderY( nRemainderTilesY % nMSBFactor );
+
+ // gets filled out from the recursive call with info of what's
+ // been generated
+ ImplTileInfo aTileInfo;
+
+ // check for recursion's end condition: LSB place reached?
+ if( nMSBFactor == 1 )
+ {
+ pTileGraphic = this;
+
+ // set initial tile size -> orig size
+ aTileInfo.aTileSizePixel = rTileSizePixel;
+ aTileInfo.nTilesEmptyX = nNumOrigTilesX;
+ aTileInfo.nTilesEmptyY = nNumOrigTilesY;
+ }
+ else if( ImplRenderTileRecursive( rVDev, nExponent, nMSBFactor/nExponent,
+ nNumOrigTilesX, nNumOrigTilesY,
+ nNewRemainderX, nNewRemainderY,
+ rTileSizePixel, pAttr, aTileInfo ) )
+ {
+ // extract generated tile -> see comment on the first loop below
+ BitmapEx aTileBitmap( rVDev.GetBitmap( aTileInfo.aTileTopLeft, aTileInfo.aTileSizePixel ) );
+
+ xTmpGraphic.reset(new GraphicObject(aTileBitmap));
+ pTileGraphic = xTmpGraphic.get();
+
+ // fill stripes left over from upstream levels:
+
+ // x0000
+ // 0
+ // 0
+ // 0
+ // 0
+
+ // where x denotes the place filled by our recursive predecessors
+
+ // check whether we have to fill stripes here. Although not
+ // obvious, there is one case where we can skip this step: if
+ // the previous recursion level (the one who filled our
+ // aTileInfo) had zero area to fill, then there are no white
+ // stripes left, naturally. This happens if the digit
+ // associated to that level has a zero, and can be checked via
+ // aTileTopLeft==aNextTileTopLeft.
+ if( aTileInfo.aTileTopLeft != aTileInfo.aNextTileTopLeft )
+ {
+ // now fill one row from aTileInfo.aNextTileTopLeft.X() all
+ // the way to the right
+ // current output position while drawing
+ Point aCurrPos(aTileInfo.aNextTileTopLeft.X(), aTileInfo.aTileTopLeft.Y());
+ for (int nX=0; nX < aTileInfo.nTilesEmptyX; nX += nMSBFactor)
+ {
+ if (!pTileGraphic->Draw(rVDev, aCurrPos, aTileInfo.aTileSizePixel, pAttr))
+ return false;
+
+ aCurrPos.AdjustX(aTileInfo.aTileSizePixel.Width() );
+ }
+
+#ifdef DBG_TEST
+// rVDev.SetFillCOL_WHITE );
+ rVDev.SetFillColor();
+ rVDev.SetLineColor( Color( 255 * nExponent / nMSBFactor, 255 - 255 * nExponent / nMSBFactor, 128 - 255 * nExponent / nMSBFactor ) );
+ rVDev.DrawEllipse( tools::Rectangle(aTileInfo.aNextTileTopLeft.X(), aTileInfo.aTileTopLeft.Y(),
+ aTileInfo.aNextTileTopLeft.X() - 1 + (aTileInfo.nTilesEmptyX/nMSBFactor)*aTileInfo.aTileSizePixel.Width(),
+ aTileInfo.aTileTopLeft.Y() + aTileInfo.aTileSizePixel.Height() - 1) );
+#endif
+
+ // now fill one column from aTileInfo.aNextTileTopLeft.Y() all
+ // the way to the bottom
+ aCurrPos.setX( aTileInfo.aTileTopLeft.X() );
+ aCurrPos.setY( aTileInfo.aNextTileTopLeft.Y() );
+ for (int nY=0; nY < aTileInfo.nTilesEmptyY; nY += nMSBFactor)
+ {
+ if (!pTileGraphic->Draw(rVDev, aCurrPos, aTileInfo.aTileSizePixel, pAttr))
+ return false;
+
+ aCurrPos.AdjustY(aTileInfo.aTileSizePixel.Height() );
+ }
+
+#ifdef DBG_TEST
+ rVDev.DrawEllipse( tools::Rectangle(aTileInfo.aTileTopLeft.X(), aTileInfo.aNextTileTopLeft.Y(),
+ aTileInfo.aTileTopLeft.X() + aTileInfo.aTileSizePixel.Width() - 1,
+ aTileInfo.aNextTileTopLeft.Y() - 1 + (aTileInfo.nTilesEmptyY/nMSBFactor)*aTileInfo.aTileSizePixel.Height()) );
+#endif
+ }
+ else
+ {
+ // Thought that aTileInfo.aNextTileTopLeft tile has always
+ // been drawn already, but that's wrong: typically,
+ // _parts_ of that tile have been drawn, since the
+ // previous level generated the tile there. But when
+ // aTileInfo.aNextTileTopLeft!=aTileInfo.aTileTopLeft, the
+ // difference between these two values is missing in the
+ // lower right corner of this first tile. So, can do that
+ // only here.
+ bNoFirstTileDraw = true;
+ }
+ }
+ else
+ {
+ return false;
+ }
+
+ // calc number of original tiles in our drawing area without
+ // remainder
+ nRemainderTilesX -= nNewRemainderX;
+ nRemainderTilesY -= nNewRemainderY;
+
+ // fill tile info for calling method
+ rTileInfo.aTileTopLeft = aTileInfo.aNextTileTopLeft;
+ rTileInfo.aNextTileTopLeft = Point( rTileInfo.aTileTopLeft.X() + rTileSizePixel.Width()*nRemainderTilesX,
+ rTileInfo.aTileTopLeft.Y() + rTileSizePixel.Height()*nRemainderTilesY );
+ rTileInfo.aTileSizePixel = Size( rTileSizePixel.Width()*nMSBFactor*nExponent,
+ rTileSizePixel.Height()*nMSBFactor*nExponent );
+ rTileInfo.nTilesEmptyX = aTileInfo.nTilesEmptyX - nRemainderTilesX;
+ rTileInfo.nTilesEmptyY = aTileInfo.nTilesEmptyY - nRemainderTilesY;
+
+ // init output position
+ Point aCurrPos = aTileInfo.aNextTileTopLeft;
+
+ // fill our drawing area. Fill possibly more, to create the next
+ // bigger tile size -> see bitmap extraction above. This does no
+ // harm, since everything right or below our actual area is
+ // overdrawn by our caller. Just in case we're in the last level,
+ // we don't draw beyond the right or bottom border.
+ for (int nY=0; nY < aTileInfo.nTilesEmptyY && nY < nExponent*nMSBFactor; nY += nMSBFactor)
+ {
+ aCurrPos.setX( aTileInfo.aNextTileTopLeft.X() );
+
+ for (int nX=0; nX < aTileInfo.nTilesEmptyX && nX < nExponent*nMSBFactor; nX += nMSBFactor)
+ {
+ if( bNoFirstTileDraw )
+ bNoFirstTileDraw = false; // don't draw first tile position
+ else if (!pTileGraphic->Draw(rVDev, aCurrPos, aTileInfo.aTileSizePixel, pAttr))
+ return false;
+
+ aCurrPos.AdjustX(aTileInfo.aTileSizePixel.Width() );
+ }
+
+ aCurrPos.AdjustY(aTileInfo.aTileSizePixel.Height() );
+ }
+
+#ifdef DBG_TEST
+// rVDev.SetFillCOL_WHITE );
+ rVDev.SetFillColor();
+ rVDev.SetLineColor( Color( 255 * nExponent / nMSBFactor, 255 - 255 * nExponent / nMSBFactor, 128 - 255 * nExponent / nMSBFactor ) );
+ rVDev.DrawRect( tools::Rectangle((rTileInfo.aTileTopLeft.X())*rTileSizePixel.Width(),
+ (rTileInfo.aTileTopLeft.Y())*rTileSizePixel.Height(),
+ (rTileInfo.aNextTileTopLeft.X())*rTileSizePixel.Width()-1,
+ (rTileInfo.aNextTileTopLeft.Y())*rTileSizePixel.Height()-1) );
+#endif
+
+ return true;
+}
+
+bool GraphicObject::ImplDrawTiled(OutputDevice& rOut, const tools::Rectangle& rArea, const Size& rSizePixel,
+ const Size& rOffset, const GraphicAttr* pAttr, int nTileCacheSize1D)
+{
+ const MapMode aOutMapMode(rOut.GetMapMode());
+ const MapMode aMapMode( aOutMapMode.GetMapUnit(), Point(), aOutMapMode.GetScaleX(), aOutMapMode.GetScaleY() );
+ bool bRet( false );
+
+ // #i42643# Casting to Int64, to avoid integer overflow for
+ // huge-DPI output devices
+ if( GetGraphic().GetType() == GraphicType::Bitmap &&
+ static_cast<sal_Int64>(rSizePixel.Width()) * rSizePixel.Height() <
+ static_cast<sal_Int64>(nTileCacheSize1D)*nTileCacheSize1D )
+ {
+ // First combine very small bitmaps into a larger tile
+
+
+ ScopedVclPtrInstance< VirtualDevice > aVDev;
+ const int nNumTilesInCacheX( (nTileCacheSize1D + rSizePixel.Width()-1) / rSizePixel.Width() );
+ const int nNumTilesInCacheY( (nTileCacheSize1D + rSizePixel.Height()-1) / rSizePixel.Height() );
+
+ aVDev->SetOutputSizePixel( Size( nNumTilesInCacheX*rSizePixel.Width(),
+ nNumTilesInCacheY*rSizePixel.Height() ) );
+ aVDev->SetMapMode( aMapMode );
+
+ // draw bitmap content
+ if( ImplRenderTempTile( *aVDev, nNumTilesInCacheX,
+ nNumTilesInCacheY, rSizePixel, pAttr ) )
+ {
+ BitmapEx aTileBitmap( aVDev->GetBitmap( Point(0,0), aVDev->GetOutputSize() ) );
+
+ // draw alpha content, if any
+ if( IsTransparent() )
+ {
+ GraphicObject aAlphaGraphic;
+
+ if( GetGraphic().IsAlpha() )
+ aAlphaGraphic.SetGraphic(BitmapEx(GetGraphic().GetBitmapEx().GetAlpha().GetBitmap()));
+ else
+ aAlphaGraphic.SetGraphic(BitmapEx(Bitmap()));
+
+ if( aAlphaGraphic.ImplRenderTempTile( *aVDev, nNumTilesInCacheX,
+ nNumTilesInCacheY, rSizePixel, pAttr ) )
+ {
+ // Combine bitmap and alpha/mask
+ if( GetGraphic().IsAlpha() )
+ aTileBitmap = BitmapEx( aTileBitmap.GetBitmap(),
+ AlphaMask( aVDev->GetBitmap( Point(0,0), aVDev->GetOutputSize() ) ) );
+ else
+ aTileBitmap = BitmapEx( aTileBitmap.GetBitmap(),
+ aVDev->GetBitmap( Point(0,0), aVDev->GetOutputSize() ).CreateMask( COL_WHITE ) );
+ }
+ }
+
+ // paint generated tile
+ GraphicObject aTmpGraphic( aTileBitmap );
+ bRet = aTmpGraphic.ImplDrawTiled(rOut, rArea,
+ aTileBitmap.GetSizePixel(),
+ rOffset, pAttr, nTileCacheSize1D);
+ }
+ }
+ else
+ {
+ const Size aOutOffset( rOut.LogicToPixel( rOffset, aOutMapMode ) );
+ const tools::Rectangle aOutArea( rOut.LogicToPixel( rArea, aOutMapMode ) );
+
+ // number of invisible (because out-of-area) tiles
+ int nInvisibleTilesX;
+ int nInvisibleTilesY;
+
+ // round towards -infty for negative offset
+ if( aOutOffset.Width() < 0 )
+ nInvisibleTilesX = (aOutOffset.Width() - rSizePixel.Width() + 1) / rSizePixel.Width();
+ else
+ nInvisibleTilesX = aOutOffset.Width() / rSizePixel.Width();
+
+ // round towards -infty for negative offset
+ if( aOutOffset.Height() < 0 )
+ nInvisibleTilesY = (aOutOffset.Height() - rSizePixel.Height() + 1) / rSizePixel.Height();
+ else
+ nInvisibleTilesY = aOutOffset.Height() / rSizePixel.Height();
+
+ // origin from where to 'virtually' start drawing in pixel
+ const Point aOutOrigin( rOut.LogicToPixel( Point( rArea.Left() - rOffset.Width(),
+ rArea.Top() - rOffset.Height() ) ) );
+ // position in pixel from where to really start output
+ const Point aOutStart( aOutOrigin.X() + nInvisibleTilesX*rSizePixel.Width(),
+ aOutOrigin.Y() + nInvisibleTilesY*rSizePixel.Height() );
+
+ rOut.Push( vcl::PushFlags::CLIPREGION );
+ rOut.IntersectClipRegion( rArea );
+
+ // Paint all tiles
+
+
+ bRet = ImplDrawTiled(rOut, aOutStart,
+ (aOutArea.GetWidth() + aOutArea.Left() - aOutStart.X() + rSizePixel.Width() - 1) / rSizePixel.Width(),
+ (aOutArea.GetHeight() + aOutArea.Top() - aOutStart.Y() + rSizePixel.Height() - 1) / rSizePixel.Height(),
+ rSizePixel, pAttr);
+
+ rOut.Pop();
+ }
+
+ return bRet;
+}
+
+bool GraphicObject::ImplDrawTiled( OutputDevice& rOut, const Point& rPosPixel,
+ int nNumTilesX, int nNumTilesY,
+ const Size& rTileSizePixel, const GraphicAttr* pAttr ) const
+{
+ Point aCurrPos( rPosPixel );
+ Size aTileSizeLogic( rOut.PixelToLogic( rTileSizePixel ) );
+ int nX, nY;
+
+ // #107607# Use logical coordinates for metafile playing, too
+ bool bDrawInPixel( rOut.GetConnectMetaFile() == nullptr && GraphicType::Bitmap == GetType() );
+ bool bRet = false;
+
+ // #105229# Switch off mapping (converting to logic and back to
+ // pixel might cause roundoff errors)
+ bool bOldMap( rOut.IsMapModeEnabled() );
+
+ if( bDrawInPixel )
+ rOut.EnableMapMode( false );
+
+ for( nY=0; nY < nNumTilesY; ++nY )
+ {
+ aCurrPos.setX( rPosPixel.X() );
+
+ for( nX=0; nX < nNumTilesX; ++nX )
+ {
+ // #105229# work with pixel coordinates here, mapping is disabled!
+ // #104004# don't disable mapping for metafile recordings
+ // #108412# don't quit the loop if one draw fails
+
+ // update return value. This method should return true, if
+ // at least one of the looped Draws succeeded.
+ bRet |= Draw(rOut,
+ bDrawInPixel ? aCurrPos : rOut.PixelToLogic(aCurrPos),
+ bDrawInPixel ? rTileSizePixel : aTileSizeLogic,
+ pAttr);
+
+ aCurrPos.AdjustX(rTileSizePixel.Width() );
+ }
+
+ aCurrPos.AdjustY(rTileSizePixel.Height() );
+ }
+
+ if( bDrawInPixel )
+ rOut.EnableMapMode( bOldMap );
+
+ return bRet;
+}
+
+void GraphicObject::ImplTransformBitmap( BitmapEx& rBmpEx,
+ const GraphicAttr& rAttr,
+ const Size& rCropLeftTop,
+ const Size& rCropRightBottom,
+ const tools::Rectangle& rCropRect,
+ const Size& rDstSize,
+ bool bEnlarge ) const
+{
+ // #107947# Extracted from svdograf.cxx
+
+ // #104115# Crop the bitmap
+ if( rAttr.IsCropped() )
+ {
+ rBmpEx.Crop( rCropRect );
+
+ // #104115# Negative crop sizes mean: enlarge bitmap and pad
+ if( bEnlarge && (
+ rCropLeftTop.Width() < 0 ||
+ rCropLeftTop.Height() < 0 ||
+ rCropRightBottom.Width() < 0 ||
+ rCropRightBottom.Height() < 0 ) )
+ {
+ Size aBmpSize( rBmpEx.GetSizePixel() );
+ sal_Int32 nPadLeft( rCropLeftTop.Width() < 0 ? -rCropLeftTop.Width() : 0 );
+ sal_Int32 nPadTop( rCropLeftTop.Height() < 0 ? -rCropLeftTop.Height() : 0 );
+ sal_Int32 nPadTotalWidth( aBmpSize.Width() + nPadLeft + (rCropRightBottom.Width() < 0 ? -rCropRightBottom.Width() : 0) );
+ sal_Int32 nPadTotalHeight( aBmpSize.Height() + nPadTop + (rCropRightBottom.Height() < 0 ? -rCropRightBottom.Height() : 0) );
+
+ BitmapEx aBmpEx2;
+
+ if( rBmpEx.IsAlpha() )
+ {
+ aBmpEx2 = BitmapEx( rBmpEx.GetBitmap(), rBmpEx.GetAlpha() );
+ }
+ else
+ {
+ // #104115# Generate mask bitmap and init to zero
+ Bitmap aMask(aBmpSize, vcl::PixelFormat::N8_BPP, &Bitmap::GetGreyPalette(256));
+ aMask.Erase( Color(0,0,0) );
+
+ // #104115# Always generate transparent bitmap, we need the border transparent
+ aBmpEx2 = BitmapEx( rBmpEx.GetBitmap(), aMask );
+
+ // #104115# Add opaque mask to source bitmap, otherwise the destination remains transparent
+ rBmpEx = aBmpEx2;
+ }
+
+ aBmpEx2.Scale(Size(nPadTotalWidth, nPadTotalHeight));
+ aBmpEx2.Erase( Color(ColorAlpha,0,0,0,0) );
+ aBmpEx2.CopyPixel( tools::Rectangle( Point(nPadLeft, nPadTop), aBmpSize ), tools::Rectangle( Point(0, 0), aBmpSize ), &rBmpEx );
+ rBmpEx = aBmpEx2;
+ }
+ }
+
+ const Size aSizePixel( rBmpEx.GetSizePixel() );
+
+ if( rAttr.GetRotation() == 0_deg10 || IsAnimated() )
+ return;
+
+ if( !(aSizePixel.Width() && aSizePixel.Height() && rDstSize.Width() && rDstSize.Height()) )
+ return;
+
+ double fSrcWH = static_cast<double>(aSizePixel.Width()) / aSizePixel.Height();
+ double fDstWH = static_cast<double>(rDstSize.Width()) / rDstSize.Height();
+ double fScaleX = 1.0, fScaleY = 1.0;
+
+ // always choose scaling to shrink bitmap
+ if( fSrcWH < fDstWH )
+ fScaleY = aSizePixel.Width() / ( fDstWH * aSizePixel.Height() );
+ else
+ fScaleX = fDstWH * aSizePixel.Height() / aSizePixel.Width();
+
+ rBmpEx.Scale( fScaleX, fScaleY );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/GraphicReader.cxx b/vcl/source/graphic/GraphicReader.cxx
new file mode 100644
index 000000000..9137ebd8a
--- /dev/null
+++ b/vcl/source/graphic/GraphicReader.cxx
@@ -0,0 +1,29 @@
+/* -*- 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 <graphic/GraphicReader.hxx>
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+GraphicReader::GraphicReader() {}
+
+GraphicReader::~GraphicReader() {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/Manager.cxx b/vcl/source/graphic/Manager.cxx
new file mode 100644
index 000000000..bdb5ba7ef
--- /dev/null
+++ b/vcl/source/graphic/Manager.cxx
@@ -0,0 +1,293 @@
+/* -*- 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 <graphic/Manager.hxx>
+#include <impgraph.hxx>
+#include <sal/log.hxx>
+
+#include <officecfg/Office/Common.hxx>
+#include <unotools/configmgr.hxx>
+
+using namespace css;
+
+namespace vcl::graphic
+{
+namespace
+{
+void setupConfigurationValuesIfPossible(sal_Int64& rMemoryLimit,
+ std::chrono::seconds& rAllowedIdleTime, bool& bSwapEnabled)
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return;
+
+ try
+ {
+ using officecfg::Office::Common::Cache;
+
+ rMemoryLimit = Cache::GraphicManager::GraphicMemoryLimit::get();
+ rAllowedIdleTime
+ = std::chrono::seconds(Cache::GraphicManager::GraphicAllowedIdleTime::get());
+ bSwapEnabled = Cache::GraphicManager::GraphicSwappingEnabled::get();
+ }
+ catch (...)
+ {
+ }
+}
+}
+
+Manager& Manager::get()
+{
+ static Manager gStaticManager;
+ return gStaticManager;
+}
+
+Manager::Manager()
+ : mnAllowedIdleTime(10)
+ , mbSwapEnabled(true)
+ , mbReducingGraphicMemory(false)
+ , mnMemoryLimit(300000000)
+ , mnUsedSize(0)
+ , maSwapOutTimer("graphic::Manager maSwapOutTimer")
+{
+ setupConfigurationValuesIfPossible(mnMemoryLimit, mnAllowedIdleTime, mbSwapEnabled);
+
+ if (mbSwapEnabled)
+ {
+ maSwapOutTimer.SetInvokeHandler(LINK(this, Manager, SwapOutTimerHandler));
+ maSwapOutTimer.SetTimeout(10000);
+ maSwapOutTimer.Start();
+ }
+}
+
+void Manager::loopGraphicsAndSwapOut(std::unique_lock<std::mutex>& rGuard)
+{
+ // make a copy of m_pImpGraphicList because if we swap out a svg, the svg
+ // filter may create more temp Graphics which are auto-added to
+ // m_pImpGraphicList invalidating a loop over m_pImpGraphicList, e.g.
+ // reexport of tdf118346-1.odg
+ o3tl::sorted_vector<ImpGraphic*> aImpGraphicList = m_pImpGraphicList;
+
+ for (ImpGraphic* pEachImpGraphic : aImpGraphicList)
+ {
+ if (mnUsedSize < sal_Int64(mnMemoryLimit * 0.7))
+ return;
+
+ if (pEachImpGraphic->isSwappedOut())
+ continue;
+
+ sal_Int64 nCurrentGraphicSize = getGraphicSizeBytes(pEachImpGraphic);
+ if (nCurrentGraphicSize > 100000)
+ {
+ if (!pEachImpGraphic->mpContext)
+ {
+ auto aCurrent = std::chrono::high_resolution_clock::now();
+ auto aDeltaTime = aCurrent - pEachImpGraphic->maLastUsed;
+ auto aSeconds = std::chrono::duration_cast<std::chrono::seconds>(aDeltaTime);
+
+ if (aSeconds > mnAllowedIdleTime)
+ {
+ // unlock because svgio can call back into us
+ rGuard.unlock();
+ pEachImpGraphic->swapOut();
+ rGuard.lock();
+ }
+ }
+ }
+ }
+}
+
+void Manager::reduceGraphicMemory(std::unique_lock<std::mutex>& rGuard)
+{
+ // maMutex is locked in callers
+
+ if (!mbSwapEnabled)
+ return;
+
+ if (mnUsedSize < mnMemoryLimit)
+ return;
+
+ // avoid recursive reduceGraphicMemory on reexport of tdf118346-1.odg to odg
+ if (mbReducingGraphicMemory)
+ return;
+ mbReducingGraphicMemory = true;
+
+ loopGraphicsAndSwapOut(rGuard);
+
+ sal_Int64 calculatedSize = 0;
+ for (ImpGraphic* pEachImpGraphic : m_pImpGraphicList)
+ {
+ if (!pEachImpGraphic->isSwappedOut())
+ {
+ calculatedSize += getGraphicSizeBytes(pEachImpGraphic);
+ }
+ }
+
+ if (calculatedSize != mnUsedSize)
+ {
+ assert(rGuard.owns_lock() && rGuard.mutex() == &maMutex);
+ // coverity[missing_lock: FALSE] - as above assert
+ mnUsedSize = calculatedSize;
+ }
+
+ mbReducingGraphicMemory = false;
+}
+
+sal_Int64 Manager::getGraphicSizeBytes(const ImpGraphic* pImpGraphic)
+{
+ if (!pImpGraphic->isAvailable())
+ return 0;
+ return pImpGraphic->getSizeBytes();
+}
+
+IMPL_LINK(Manager, SwapOutTimerHandler, Timer*, pTimer, void)
+{
+ std::unique_lock aGuard(maMutex);
+
+ pTimer->Stop();
+ reduceGraphicMemory(aGuard);
+ pTimer->Start();
+}
+
+void Manager::registerGraphic(const std::shared_ptr<ImpGraphic>& pImpGraphic)
+{
+ std::unique_lock aGuard(maMutex);
+
+ // make some space first
+ if (mnUsedSize > mnMemoryLimit)
+ reduceGraphicMemory(aGuard);
+
+ // Insert and update the used size (bytes)
+ assert(aGuard.owns_lock() && aGuard.mutex() == &maMutex);
+ // coverity[missing_lock: FALSE] - as above assert
+ mnUsedSize += getGraphicSizeBytes(pImpGraphic.get());
+ m_pImpGraphicList.insert(pImpGraphic.get());
+
+ // calculate size of the graphic set
+ sal_Int64 calculatedSize = 0;
+ for (ImpGraphic* pEachImpGraphic : m_pImpGraphicList)
+ {
+ if (!pEachImpGraphic->isSwappedOut())
+ {
+ calculatedSize += getGraphicSizeBytes(pEachImpGraphic);
+ }
+ }
+
+ if (calculatedSize != mnUsedSize)
+ {
+ SAL_INFO_IF(calculatedSize != mnUsedSize, "vcl.gdi",
+ "Calculated size mismatch. Variable size is '"
+ << mnUsedSize << "' but calculated size is '" << calculatedSize << "'");
+ mnUsedSize = calculatedSize;
+ }
+}
+
+void Manager::unregisterGraphic(ImpGraphic* pImpGraphic)
+{
+ std::scoped_lock aGuard(maMutex);
+
+ mnUsedSize -= getGraphicSizeBytes(pImpGraphic);
+ m_pImpGraphicList.erase(pImpGraphic);
+}
+
+std::shared_ptr<ImpGraphic> Manager::copy(std::shared_ptr<ImpGraphic> const& rImpGraphicPtr)
+{
+ auto pReturn = std::make_shared<ImpGraphic>(*rImpGraphicPtr);
+ registerGraphic(pReturn);
+ return pReturn;
+}
+
+std::shared_ptr<ImpGraphic> Manager::newInstance()
+{
+ auto pReturn = std::make_shared<ImpGraphic>();
+ registerGraphic(pReturn);
+ return pReturn;
+}
+
+std::shared_ptr<ImpGraphic> Manager::newInstance(std::shared_ptr<GfxLink> const& rGfxLink,
+ sal_Int32 nPageIndex)
+{
+ auto pReturn = std::make_shared<ImpGraphic>(rGfxLink, nPageIndex);
+ registerGraphic(pReturn);
+ return pReturn;
+}
+
+std::shared_ptr<ImpGraphic> Manager::newInstance(const BitmapEx& rBitmapEx)
+{
+ auto pReturn = std::make_shared<ImpGraphic>(rBitmapEx);
+ registerGraphic(pReturn);
+ return pReturn;
+}
+
+std::shared_ptr<ImpGraphic> Manager::newInstance(const Animation& rAnimation)
+{
+ auto pReturn = std::make_shared<ImpGraphic>(rAnimation);
+ registerGraphic(pReturn);
+ return pReturn;
+}
+
+std::shared_ptr<ImpGraphic>
+Manager::newInstance(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr)
+{
+ auto pReturn = std::make_shared<ImpGraphic>(rVectorGraphicDataPtr);
+ registerGraphic(pReturn);
+ return pReturn;
+}
+
+std::shared_ptr<ImpGraphic> Manager::newInstance(const GDIMetaFile& rMetaFile)
+{
+ auto pReturn = std::make_shared<ImpGraphic>(rMetaFile);
+ registerGraphic(pReturn);
+ return pReturn;
+}
+
+std::shared_ptr<ImpGraphic> Manager::newInstance(const GraphicExternalLink& rGraphicLink)
+{
+ auto pReturn = std::make_shared<ImpGraphic>(rGraphicLink);
+ registerGraphic(pReturn);
+ return pReturn;
+}
+
+void Manager::swappedIn(const ImpGraphic* pImpGraphic, sal_Int64 nSizeBytes)
+{
+ std::scoped_lock aGuard(maMutex);
+ if (pImpGraphic)
+ {
+ mnUsedSize += nSizeBytes;
+ }
+}
+
+void Manager::swappedOut(const ImpGraphic* pImpGraphic, sal_Int64 nSizeBytes)
+{
+ std::scoped_lock aGuard(maMutex);
+ if (pImpGraphic)
+ {
+ mnUsedSize -= nSizeBytes;
+ }
+}
+
+void Manager::changeExisting(const ImpGraphic* pImpGraphic, sal_Int64 nOldSizeBytes)
+{
+ std::scoped_lock aGuard(maMutex);
+
+ mnUsedSize -= nOldSizeBytes;
+ mnUsedSize += getGraphicSizeBytes(pImpGraphic);
+}
+} // end vcl::graphic
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/UnoBinaryDataContainer.cxx b/vcl/source/graphic/UnoBinaryDataContainer.cxx
new file mode 100644
index 000000000..8ee3660be
--- /dev/null
+++ b/vcl/source/graphic/UnoBinaryDataContainer.cxx
@@ -0,0 +1,34 @@
+/* -*- 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/.
+ *
+ */
+
+#include <graphic/UnoBinaryDataContainer.hxx>
+
+#include <cppuhelper/queryinterface.hxx>
+
+using namespace css;
+
+// css::lang::XUnoTunnel
+UNO3_GETIMPLEMENTATION_IMPL(UnoBinaryDataContainer);
+
+css::uno::Sequence<sal_Int8> SAL_CALL UnoBinaryDataContainer::getCopyAsByteSequence()
+{
+ if (maBinaryDataContainer.isEmpty())
+ return css::uno::Sequence<sal_Int8>();
+
+ size_t nSize = maBinaryDataContainer.getSize();
+
+ css::uno::Sequence<sal_Int8> aData(nSize);
+
+ std::copy(maBinaryDataContainer.cbegin(), maBinaryDataContainer.cend(), aData.getArray());
+
+ return aData;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/UnoGraphic.cxx b/vcl/source/graphic/UnoGraphic.cxx
new file mode 100644
index 000000000..4d5f42dfc
--- /dev/null
+++ b/vcl/source/graphic/UnoGraphic.cxx
@@ -0,0 +1,276 @@
+/* -*- 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 <graphic/UnoGraphic.hxx>
+
+#include <tools/stream.hxx>
+#include <vcl/svapp.hxx>
+#include <com/sun/star/graphic/GraphicType.hpp>
+#include <com/sun/star/graphic/XGraphicTransformer.hpp>
+#include <vcl/dibtools.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/BitmapColor.hxx>
+#include <vcl/BitmapDuoToneFilter.hxx>
+#include <comphelper/servicehelper.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/queryinterface.hxx>
+#include <cppuhelper/typeprovider.hxx>
+
+using namespace com::sun::star;
+
+namespace unographic {
+
+Graphic::Graphic()
+{
+}
+
+Graphic::~Graphic() noexcept
+{
+}
+
+void Graphic::init(const ::Graphic& rGraphic)
+{
+ maGraphic = rGraphic;
+ unographic::GraphicDescriptor::init(maGraphic);
+}
+
+uno::Any SAL_CALL Graphic::queryAggregation( const uno::Type& rType )
+{
+ uno::Any aAny;
+ if( rType == cppu::UnoType<graphic::XGraphic>::get())
+ aAny <<= uno::Reference< graphic::XGraphic >( this );
+ else if( rType == cppu::UnoType<awt::XBitmap>::get())
+ aAny <<= uno::Reference< awt::XBitmap >( this );
+ else if( rType == cppu::UnoType<lang::XUnoTunnel>::get())
+ aAny <<= uno::Reference< lang::XUnoTunnel >(this);
+ else
+ aAny = ::unographic::GraphicDescriptor::queryAggregation( rType );
+
+ return aAny;
+}
+
+uno::Any SAL_CALL Graphic::queryInterface( const uno::Type & rType )
+{
+ css::uno::Any aReturn = ::unographic::GraphicDescriptor::queryInterface( rType );
+ if ( !aReturn.hasValue() )
+ aReturn = ::cppu::queryInterface ( rType, static_cast< graphic::XGraphicTransformer*>( this ) );
+ return aReturn;
+}
+
+void SAL_CALL Graphic::acquire()
+ noexcept
+{
+ unographic::GraphicDescriptor::acquire();
+}
+
+void SAL_CALL Graphic::release() noexcept
+{
+ unographic::GraphicDescriptor::release();
+}
+
+OUString SAL_CALL Graphic::getImplementationName()
+{
+ return "com.sun.star.comp.graphic.Graphic";
+}
+
+sal_Bool SAL_CALL Graphic::supportsService( const OUString& rServiceName )
+{
+ return cppu::supportsService( this, rServiceName );
+}
+
+uno::Sequence< OUString > SAL_CALL Graphic::getSupportedServiceNames()
+{
+ uno::Sequence< OUString > aRet( ::unographic::GraphicDescriptor::getSupportedServiceNames() );
+ const uno::Sequence< OUString > aNew { "com.sun.star.graphic.Graphic" };
+ sal_Int32 nOldCount = aRet.getLength();
+
+ aRet.realloc( nOldCount + aNew.getLength() );
+
+ std::copy(aNew.begin(), aNew.end(), std::next(aRet.getArray(), nOldCount));
+
+ return aRet;
+}
+
+uno::Sequence< uno::Type > SAL_CALL Graphic::getTypes()
+{
+ return cppu::OTypeCollection(
+ cppu::UnoType<graphic::XGraphic>::get(),
+ cppu::UnoType<awt::XBitmap>::get(),
+ ::unographic::GraphicDescriptor::getTypes()
+ ).getTypes();
+}
+
+uno::Sequence< sal_Int8 > SAL_CALL Graphic::getImplementationId()
+{
+ return css::uno::Sequence<sal_Int8>();
+}
+
+sal_Int8 SAL_CALL Graphic::getType()
+{
+ sal_Int8 cRet = graphic::GraphicType::EMPTY;
+
+ if (!maGraphic.IsNone())
+ {
+ cRet = (maGraphic.GetType() == ::GraphicType::Bitmap) ? graphic::GraphicType::PIXEL
+ : graphic::GraphicType::VECTOR;
+ }
+
+ return cRet;
+}
+
+// XBitmap
+
+awt::Size SAL_CALL Graphic::getSize()
+{
+ SolarMutexGuard aGuard;
+
+ Size aVclSize;
+ if (!maGraphic.IsNone())
+ {
+ aVclSize = maGraphic.GetSizePixel();
+ }
+ return awt::Size(aVclSize.Width(), aVclSize.Height());
+}
+
+uno::Sequence<sal_Int8> SAL_CALL Graphic::getDIB()
+{
+ SolarMutexGuard aGuard;
+
+ if (!maGraphic.IsNone())
+ {
+ SvMemoryStream aMemoryStream;
+
+ WriteDIB(maGraphic.GetBitmapEx().GetBitmap(), aMemoryStream, false, true);
+ return css::uno::Sequence<sal_Int8>(static_cast<sal_Int8 const *>(aMemoryStream.GetData()), aMemoryStream.Tell());
+ }
+ else
+ {
+ return uno::Sequence<sal_Int8>();
+ }
+}
+
+uno::Sequence<sal_Int8> SAL_CALL Graphic::getMaskDIB()
+{
+ SolarMutexGuard aGuard;
+
+ if (!maGraphic.IsNone())
+ {
+ SvMemoryStream aMemoryStream;
+
+ WriteDIB(maGraphic.GetBitmapEx().GetAlpha(), aMemoryStream, false, true);
+ return css::uno::Sequence<sal_Int8>( static_cast<sal_Int8 const *>(aMemoryStream.GetData()), aMemoryStream.Tell() );
+ }
+ else
+ {
+ return uno::Sequence<sal_Int8>();
+ }
+}
+
+sal_Int64 SAL_CALL Graphic::getSomething( const uno::Sequence< sal_Int8 >& rId )
+{
+ return comphelper::getSomethingImpl(rId, &maGraphic);
+}
+
+
+// XGraphicTransformer
+uno::Reference< graphic::XGraphic > SAL_CALL Graphic::colorChange(
+ const uno::Reference< graphic::XGraphic >& rxGraphic, sal_Int32 nColorFrom, sal_Int8 nTolerance, sal_Int32 nColorTo, sal_Int8 nAlphaTo )
+{
+ ::Graphic aGraphic(rxGraphic);
+ ::Graphic aReturnGraphic;
+
+ BitmapColor aBmpColorFrom(Color(ColorTransparency, static_cast<sal_uInt32>(nColorFrom)));
+ BitmapColor aBmpColorTo(Color(ColorTransparency, static_cast<sal_uInt32>(nColorTo)));
+
+ Color aColorFrom(aBmpColorFrom);
+ Color aColorTo(aBmpColorTo);
+
+ const sal_uInt8 cIndexFrom = aBmpColorFrom.GetIndex();
+
+ //TODO This code convert GdiMetafile(vector graphic) to Bitmap, which cause to information lost
+ if (aGraphic.GetType() == GraphicType::Bitmap ||
+ aGraphic.GetType() == GraphicType::GdiMetafile)
+ {
+ BitmapEx aBitmapEx(aGraphic.GetBitmapEx());
+
+ if (aBitmapEx.IsAlpha())
+ {
+ aBitmapEx.setAlphaFrom( cIndexFrom, 0xff - nAlphaTo );
+ aBitmapEx.Replace(aColorFrom, aColorTo, nTolerance);
+ aReturnGraphic = ::Graphic(aBitmapEx);
+ }
+ else
+ {
+ if ((nAlphaTo == 0) || (nAlphaTo == sal::static_int_cast< sal_Int8 >(0xff)))
+ {
+ Bitmap aBitmap(aBitmapEx.GetBitmap());
+ Bitmap aMask(aBitmap.CreateMask(aColorFrom, nTolerance));
+ aBitmap.Replace(aColorFrom, aColorTo, nTolerance);
+ aReturnGraphic = ::Graphic(BitmapEx(aBitmap, aMask));
+ }
+ else
+ {
+ aBitmapEx.setAlphaFrom(cIndexFrom, nAlphaTo);
+ aBitmapEx.Replace(aColorFrom, aColorTo, nTolerance);
+ aReturnGraphic = ::Graphic(aBitmapEx);
+ }
+ }
+ }
+
+ aReturnGraphic.setOriginURL(aGraphic.getOriginURL());
+ return aReturnGraphic.GetXGraphic();
+}
+
+uno::Reference< graphic::XGraphic > SAL_CALL Graphic::applyDuotone(
+ const uno::Reference< graphic::XGraphic >& rxGraphic, sal_Int32 nColorOne, sal_Int32 nColorTwo )
+{
+ ::Graphic aGraphic(rxGraphic);
+ ::Graphic aReturnGraphic;
+
+ BitmapEx aBitmapEx( aGraphic.GetBitmapEx() );
+ AlphaMask aMask( aBitmapEx.GetAlpha() );
+
+ BitmapEx aTmpBmpEx(aBitmapEx.GetBitmap());
+ BitmapFilter::Filter(aTmpBmpEx,
+ BitmapDuoToneFilter(
+ Color(ColorTransparency, nColorOne),
+ Color(ColorTransparency, nColorTwo)));
+
+ aReturnGraphic = ::Graphic( BitmapEx( aTmpBmpEx.GetBitmap(), aMask ) );
+ aReturnGraphic.setOriginURL(aGraphic.getOriginURL());
+ return aReturnGraphic.GetXGraphic();
+}
+
+uno::Reference< graphic::XGraphic > SAL_CALL Graphic::applyBrightnessContrast(
+ const uno::Reference< graphic::XGraphic >& rxGraphic, sal_Int32 nBrightness, sal_Int32 nContrast, sal_Bool mso )
+{
+ ::Graphic aGraphic(rxGraphic);
+ ::Graphic aReturnGraphic;
+
+ BitmapEx aBitmapEx(aGraphic.GetBitmapEx());
+ aBitmapEx.Adjust(nBrightness, nContrast, 0, 0, 0, 0, false, mso);
+ aReturnGraphic = ::Graphic(aBitmapEx);
+ aReturnGraphic.setOriginURL(aGraphic.getOriginURL());
+ return aReturnGraphic.GetXGraphic();
+}
+
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/UnoGraphicDescriptor.cxx b/vcl/source/graphic/UnoGraphicDescriptor.cxx
new file mode 100644
index 000000000..74a99eb0a
--- /dev/null
+++ b/vcl/source/graphic/UnoGraphicDescriptor.cxx
@@ -0,0 +1,427 @@
+/* -*- 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 <graphic/UnoGraphicDescriptor.hxx>
+
+#include <unotools/ucbstreamhelper.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/graphic/GraphicType.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+
+#include <vcl/outdev.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/svapp.hxx>
+#include <memory>
+
+namespace {
+
+enum class UnoGraphicProperty
+{
+ GraphicType = 1
+ , MimeType = 2
+ , SizePixel = 3
+ , Size100thMM = 4
+ , BitsPerPixel = 5
+ , Transparent = 6
+ , Alpha = 7
+ , Animated = 8
+ , Linked = 9
+ , OriginURL = 10
+};
+
+}
+
+using namespace ::com::sun::star;
+
+namespace unographic {
+
+
+GraphicDescriptor::GraphicDescriptor() :
+ ::comphelper::PropertySetHelper( createPropertySetInfo() ),
+ mpGraphic( nullptr ),
+ meType( GraphicType::NONE ),
+ mnBitsPerPixel ( 0 ),
+ mbTransparent ( false )
+{
+}
+
+GraphicDescriptor::~GraphicDescriptor()
+ noexcept
+{
+}
+
+void GraphicDescriptor::init( const ::Graphic& rGraphic )
+{
+ mpGraphic = &rGraphic;
+}
+
+void GraphicDescriptor::init( const OUString& rURL )
+{
+ std::unique_ptr<SvStream> pIStm(::utl::UcbStreamHelper::CreateStream( rURL, StreamMode::READ ));
+
+ if( pIStm )
+ implCreate( *pIStm, &rURL );
+}
+
+void GraphicDescriptor::init( const uno::Reference< io::XInputStream >& rxIStm, const OUString& rURL )
+{
+ std::unique_ptr<SvStream> pIStm(::utl::UcbStreamHelper::CreateStream( rxIStm ));
+
+ if( pIStm )
+ implCreate( *pIStm, &rURL );
+}
+
+void GraphicDescriptor::implCreate( SvStream& rIStm, const OUString* pURL )
+{
+ OUString aURL;
+ if( pURL )
+ aURL = *pURL;
+ ::GraphicDescriptor aDescriptor( rIStm, &aURL );
+
+ mpGraphic = nullptr;
+ maMimeType.clear();
+ meType = GraphicType::NONE;
+ mnBitsPerPixel = 0;
+ mbTransparent = false;
+
+ if( !(aDescriptor.Detect( true ) && aDescriptor.GetFileFormat() != GraphicFileFormat::NOT) )
+ return;
+
+ const char* pMimeType = nullptr;
+ sal_uInt8 cType = graphic::GraphicType::EMPTY;
+
+ switch( aDescriptor.GetFileFormat() )
+ {
+ case GraphicFileFormat::BMP: pMimeType = MIMETYPE_BMP; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::GIF: pMimeType = MIMETYPE_GIF; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::JPG: pMimeType = MIMETYPE_JPG; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PCD: pMimeType = MIMETYPE_PCD; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PCX: pMimeType = MIMETYPE_PCX; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PNG: pMimeType = MIMETYPE_PNG; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::TIF: pMimeType = MIMETYPE_TIF; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::XBM: pMimeType = MIMETYPE_XBM; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::XPM: pMimeType = MIMETYPE_XPM; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PBM: pMimeType = MIMETYPE_PBM; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PGM: pMimeType = MIMETYPE_PGM; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PPM: pMimeType = MIMETYPE_PPM; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::RAS: pMimeType = MIMETYPE_RAS; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::TGA: pMimeType = MIMETYPE_TGA; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::PSD: pMimeType = MIMETYPE_PSD; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::WEBP: pMimeType = MIMETYPE_WEBP; cType = graphic::GraphicType::PIXEL; break;
+
+ case GraphicFileFormat::EPS: pMimeType = MIMETYPE_EPS; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::DXF: pMimeType = MIMETYPE_DXF; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::MET: pMimeType = MIMETYPE_MET; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::PCT: pMimeType = MIMETYPE_PCT; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::SVM: pMimeType = MIMETYPE_SVM; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::WMF: pMimeType = MIMETYPE_WMF; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::EMF: pMimeType = MIMETYPE_EMF; cType = graphic::GraphicType::VECTOR; break;
+ case GraphicFileFormat::SVG: pMimeType = MIMETYPE_SVG; cType = graphic::GraphicType::VECTOR; break;
+
+ default:
+ break;
+ }
+
+ if( graphic::GraphicType::EMPTY != cType )
+ {
+ meType = ( ( graphic::GraphicType::PIXEL == cType ) ? GraphicType::Bitmap : GraphicType::GdiMetafile );
+ maMimeType = OUString( pMimeType, strlen(pMimeType), RTL_TEXTENCODING_ASCII_US );
+ maSizePixel = aDescriptor.GetSizePixel();
+ maSize100thMM = aDescriptor.GetSize_100TH_MM();
+ mnBitsPerPixel = aDescriptor.GetBitsPerPixel();
+ mbTransparent = ( graphic::GraphicType::VECTOR == cType );
+ }
+}
+
+
+uno::Any SAL_CALL GraphicDescriptor::queryAggregation( const uno::Type & rType )
+{
+ uno::Any aAny;
+
+ if( rType == cppu::UnoType<lang::XServiceInfo>::get())
+ aAny <<= uno::Reference< lang::XServiceInfo >(this);
+ else if( rType == cppu::UnoType<lang::XTypeProvider>::get())
+ aAny <<= uno::Reference< lang::XTypeProvider >(this);
+ else if( rType == cppu::UnoType<beans::XPropertySet>::get())
+ aAny <<= uno::Reference< beans::XPropertySet >(this);
+ else if( rType == cppu::UnoType<beans::XPropertyState>::get())
+ aAny <<= uno::Reference< beans::XPropertyState >(this);
+ else if( rType == cppu::UnoType<beans::XMultiPropertySet>::get())
+ aAny <<= uno::Reference< beans::XMultiPropertySet >(this);
+ else
+ aAny = OWeakAggObject::queryAggregation( rType );
+
+ return aAny;
+}
+
+
+uno::Any SAL_CALL GraphicDescriptor::queryInterface( const uno::Type & rType )
+{
+ return OWeakAggObject::queryInterface( rType );
+}
+
+
+void SAL_CALL GraphicDescriptor::acquire()
+ noexcept
+{
+ OWeakAggObject::acquire();
+}
+
+
+void SAL_CALL GraphicDescriptor::release()
+ noexcept
+{
+ OWeakAggObject::release();
+}
+
+
+OUString SAL_CALL GraphicDescriptor::getImplementationName()
+{
+ return "com.sun.star.comp.graphic.GraphicDescriptor";
+}
+
+sal_Bool SAL_CALL GraphicDescriptor::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+
+uno::Sequence< OUString > SAL_CALL GraphicDescriptor::getSupportedServiceNames()
+{
+ return { "com.sun.star.graphic.GraphicDescriptor" };
+}
+
+
+uno::Sequence< uno::Type > SAL_CALL GraphicDescriptor::getTypes()
+{
+ static const uno::Sequence< uno::Type > aTypes {
+ cppu::UnoType<uno::XAggregation>::get(),
+ cppu::UnoType<lang::XServiceInfo>::get(),
+ cppu::UnoType<lang::XTypeProvider>::get(),
+ cppu::UnoType<beans::XPropertySet>::get(),
+ cppu::UnoType<beans::XPropertyState>::get(),
+ cppu::UnoType<beans::XMultiPropertySet>::get() };
+ return aTypes;
+}
+
+uno::Sequence< sal_Int8 > SAL_CALL GraphicDescriptor::getImplementationId()
+{
+ return css::uno::Sequence<sal_Int8>();
+}
+
+
+rtl::Reference<::comphelper::PropertySetInfo> GraphicDescriptor::createPropertySetInfo()
+{
+ static ::comphelper::PropertyMapEntry const aEntries[] =
+ {
+ { OUString( "GraphicType" ), static_cast< sal_Int32 >( UnoGraphicProperty::GraphicType ), cppu::UnoType< sal_Int8 >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString( "MimeType" ), static_cast< sal_Int32 >( UnoGraphicProperty::MimeType ), cppu::UnoType< OUString >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString( "SizePixel" ), static_cast< sal_Int32 >( UnoGraphicProperty::SizePixel ), cppu::UnoType< awt::Size >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString( "Size100thMM" ), static_cast< sal_Int32 >( UnoGraphicProperty::Size100thMM ), cppu::UnoType< awt::Size >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString( "BitsPerPixel" ), static_cast< sal_Int32 >( UnoGraphicProperty::BitsPerPixel ), cppu::UnoType< sal_uInt8 >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString( "Transparent" ), static_cast< sal_Int32 >( UnoGraphicProperty::Transparent ), cppu::UnoType< sal_Bool >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString( "Alpha" ), static_cast< sal_Int32 >( UnoGraphicProperty::Alpha ), cppu::UnoType< sal_Bool >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString( "Animated" ), static_cast< sal_Int32 >( UnoGraphicProperty::Animated ), cppu::UnoType< sal_Bool >::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString("Linked"), sal_Int32(UnoGraphicProperty::Linked), cppu::UnoType<sal_Bool>::get(), beans::PropertyAttribute::READONLY, 0 },
+ { OUString("OriginURL"), sal_Int32(UnoGraphicProperty::OriginURL), cppu::UnoType<OUString>::get(), beans::PropertyAttribute::READONLY, 0 },
+ };
+
+ return rtl::Reference<::comphelper::PropertySetInfo>( new ::comphelper::PropertySetInfo(aEntries) );
+}
+
+
+void GraphicDescriptor::_setPropertyValues( const comphelper::PropertyMapEntry** /*ppEntries*/, const uno::Any* /*pValues*/ )
+{
+ // we only have readonly attributes
+}
+
+
+void GraphicDescriptor::_getPropertyValues( const comphelper::PropertyMapEntry** ppEntries, uno::Any* pValues )
+{
+ SolarMutexGuard aGuard;
+
+ while( *ppEntries )
+ {
+ UnoGraphicProperty theProperty = static_cast< UnoGraphicProperty >( (*ppEntries)->mnHandle );
+ switch( theProperty )
+ {
+ case UnoGraphicProperty::GraphicType:
+ {
+ const GraphicType eType( mpGraphic ? mpGraphic->GetType() : meType );
+
+ *pValues <<= ( eType == GraphicType::Bitmap ? graphic::GraphicType::PIXEL :
+ ( eType == GraphicType::GdiMetafile ? graphic::GraphicType::VECTOR :
+ graphic::GraphicType::EMPTY ) );
+ }
+ break;
+
+ case UnoGraphicProperty::MimeType:
+ {
+ OUString aMimeType;
+
+ if( mpGraphic )
+ {
+ if( mpGraphic->IsGfxLink() )
+ {
+ const char* pMimeType;
+
+ switch( mpGraphic->GetGfxLink().GetType() )
+ {
+ case GfxLinkType::NativeGif: pMimeType = MIMETYPE_GIF; break;
+
+ // #i15508# added BMP type for better exports (checked, works)
+ case GfxLinkType::NativeBmp: pMimeType = MIMETYPE_BMP; break;
+
+ case GfxLinkType::NativeJpg: pMimeType = MIMETYPE_JPG; break;
+ case GfxLinkType::NativePng: pMimeType = MIMETYPE_PNG; break;
+ case GfxLinkType::NativeWmf: pMimeType = MIMETYPE_WMF; break;
+ case GfxLinkType::NativeMet: pMimeType = MIMETYPE_MET; break;
+ case GfxLinkType::NativePct: pMimeType = MIMETYPE_PCT; break;
+ case GfxLinkType::NativeWebp: pMimeType = MIMETYPE_WEBP; break;
+
+ // added Svg mimetype support
+ case GfxLinkType::NativeSvg: pMimeType = MIMETYPE_SVG; break;
+ case GfxLinkType::NativePdf: pMimeType = MIMETYPE_PDF; break;
+
+ default:
+ pMimeType = nullptr;
+ break;
+ }
+
+ if( pMimeType )
+ aMimeType = OUString::createFromAscii( pMimeType );
+ }
+
+ if( aMimeType.isEmpty() && ( mpGraphic->GetType() != GraphicType::NONE ) )
+ aMimeType = MIMETYPE_VCLGRAPHIC;
+ }
+ else
+ aMimeType = maMimeType;
+
+ *pValues <<= aMimeType;
+ }
+ break;
+
+ case UnoGraphicProperty::SizePixel:
+ {
+ awt::Size aAWTSize( 0, 0 );
+
+ if( mpGraphic )
+ {
+ if( mpGraphic->GetType() == GraphicType::Bitmap )
+ {
+ const Size aSizePix( mpGraphic->GetSizePixel() );
+ aAWTSize = awt::Size( aSizePix.Width(), aSizePix.Height() );
+ }
+ }
+ else
+ aAWTSize = awt::Size( maSizePixel.Width(), maSizePixel.Height() );
+
+ *pValues <<= aAWTSize;
+ }
+ break;
+
+ case UnoGraphicProperty::Size100thMM:
+ {
+ awt::Size aAWTSize( 0, 0 );
+
+ if( mpGraphic )
+ {
+ if( mpGraphic->GetPrefMapMode().GetMapUnit() != MapUnit::MapPixel )
+ {
+ const Size aSizeLog( OutputDevice::LogicToLogic(
+ mpGraphic->GetPrefSize(),
+ mpGraphic->GetPrefMapMode(),
+ MapMode(MapUnit::Map100thMM)) );
+ aAWTSize = awt::Size( aSizeLog.Width(), aSizeLog.Height() );
+ }
+ }
+ else
+ aAWTSize = awt::Size( maSize100thMM.Width(), maSize100thMM.Height() );
+
+ *pValues <<= aAWTSize;
+ }
+ break;
+
+ case UnoGraphicProperty::BitsPerPixel:
+ {
+ sal_uInt16 nBitsPerPixel = 0;
+
+ if( mpGraphic )
+ {
+ if( mpGraphic->GetType() == GraphicType::Bitmap )
+ {
+ auto ePixelFormat = mpGraphic->GetBitmapEx().GetBitmap().getPixelFormat();
+ nBitsPerPixel = vcl::pixelFormatBitCount(ePixelFormat);
+ }
+ }
+ else
+ nBitsPerPixel = mnBitsPerPixel;
+
+ *pValues <<= sal::static_int_cast< sal_Int8 >(nBitsPerPixel);
+ }
+ break;
+
+ case UnoGraphicProperty::Transparent:
+ {
+ *pValues <<= mpGraphic ? mpGraphic->IsTransparent() : mbTransparent;
+ }
+ break;
+
+ case UnoGraphicProperty::Alpha:
+ {
+ *pValues <<= mpGraphic && mpGraphic->IsAlpha();
+ }
+ break;
+
+ case UnoGraphicProperty::Animated:
+ {
+ *pValues <<= mpGraphic && mpGraphic->IsAnimated();
+ }
+ break;
+
+ case UnoGraphicProperty::Linked:
+ {
+ *pValues <<= mpGraphic && !mpGraphic->getOriginURL().isEmpty();
+ }
+ break;
+
+ case UnoGraphicProperty::OriginURL:
+ {
+ OUString aOriginURL;
+ if (mpGraphic)
+ aOriginURL = mpGraphic->getOriginURL();
+
+ *pValues <<= aOriginURL;
+ }
+ break;
+ }
+
+ ++ppEntries;
+ ++pValues;
+ }
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/UnoGraphicMapper.cxx b/vcl/source/graphic/UnoGraphicMapper.cxx
new file mode 100644
index 000000000..6bde78097
--- /dev/null
+++ b/vcl/source/graphic/UnoGraphicMapper.cxx
@@ -0,0 +1,87 @@
+/* -*- 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/.
+ */
+
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/graphic/XGraphicMapper.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <comphelper/unique_disposing_ptr.hxx>
+
+#include <memory>
+#include <unordered_map>
+
+using namespace css;
+
+namespace
+{
+class GraphicMapper : public cppu::WeakImplHelper<graphic::XGraphicMapper, lang::XServiceInfo>
+{
+private:
+ std::unordered_map<OUString, uno::Reference<graphic::XGraphic>> maGraphicMap;
+
+public:
+ GraphicMapper() = default;
+
+protected:
+ // XServiceInfo
+ OUString SAL_CALL getImplementationName() override
+ {
+ return "com.sun.star.comp.graphic.GraphicMapper";
+ }
+ sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+ {
+ return { "com.sun.star.graphic.GraphicMapper" };
+ }
+
+ // XTypeProvider
+ css::uno::Sequence<css::uno::Type> SAL_CALL getTypes() override
+ {
+ static const uno::Sequence<uno::Type> aTypes{
+ cppu::UnoType<lang::XServiceInfo>::get(), cppu::UnoType<lang::XTypeProvider>::get(),
+ cppu::UnoType<graphic::XGraphicMapper>::get()
+ };
+ return aTypes;
+ }
+ css::uno::Sequence<sal_Int8> SAL_CALL getImplementationId() override
+ {
+ return css::uno::Sequence<sal_Int8>();
+ }
+
+ // XGraphicMapper
+ css::uno::Reference<css::graphic::XGraphic> SAL_CALL findGraphic(const OUString& rId) override
+ {
+ auto aIterator = maGraphicMap.find(rId);
+
+ if (aIterator == maGraphicMap.end())
+ return css::uno::Reference<css::graphic::XGraphic>();
+
+ return aIterator->second;
+ }
+ void SAL_CALL putGraphic(const OUString& rId,
+ css::uno::Reference<css::graphic::XGraphic> const& rGraphic) override
+ {
+ maGraphicMap.emplace(rId, rGraphic);
+ }
+};
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_comp_graphic_GraphicMapper_get_implementation(css::uno::XComponentContext*,
+ css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new GraphicMapper);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/UnoGraphicObject.cxx b/vcl/source/graphic/UnoGraphicObject.cxx
new file mode 100644
index 000000000..73bbcef3e
--- /dev/null
+++ b/vcl/source/graphic/UnoGraphicObject.cxx
@@ -0,0 +1,101 @@
+/* -*- 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 <memory>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/graphic/XGraphicObject.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <vcl/GraphicObject.hxx>
+#include <mutex>
+
+using namespace css;
+
+namespace {
+
+typedef ::cppu::WeakImplHelper<graphic::XGraphicObject, css::lang::XServiceInfo> GraphicObject_BASE;
+
+ // Simple uno wrapper around the GraphicObject class to allow basic
+ // access. ( and solves a horrible cyclic link problem between
+ // goodies/toolkit/extensions )
+class GraphicObjectImpl : public GraphicObject_BASE
+{
+ std::mutex m_aMutex;
+ std::optional<GraphicObject> mpGraphicObject;
+
+public:
+ /// @throws uno::RuntimeException
+ explicit GraphicObjectImpl(uno::Sequence<uno::Any> const & rArgs);
+
+ // XGraphicObject
+ virtual uno::Reference<graphic::XGraphic> SAL_CALL getGraphic() override;
+ virtual void SAL_CALL setGraphic(uno::Reference<graphic::XGraphic> const & rxGraphic) override;
+
+ virtual OUString SAL_CALL getImplementationName() override
+ {
+ return "com.sun.star.graphic.GraphicObject";
+ }
+
+ virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+ {
+ return { "com.sun.star.graphic.GraphicObject" };
+ }
+};
+
+GraphicObjectImpl::GraphicObjectImpl(const uno::Sequence<uno::Any>& /*rArgs*/)
+{
+ mpGraphicObject.emplace();
+}
+
+uno::Reference<graphic::XGraphic> SAL_CALL GraphicObjectImpl::getGraphic()
+{
+ std::scoped_lock aGuard(m_aMutex);
+
+ if (!mpGraphicObject)
+ throw uno::RuntimeException();
+ return mpGraphicObject->GetGraphic().GetXGraphic();
+}
+
+void SAL_CALL GraphicObjectImpl::setGraphic(uno::Reference<graphic::XGraphic> const & rxGraphic)
+{
+ std::scoped_lock aGuard(m_aMutex);
+
+ if (!mpGraphicObject)
+ throw uno::RuntimeException();
+ Graphic aGraphic(rxGraphic);
+ mpGraphicObject->SetGraphic(aGraphic);
+}
+
+} // end anonymous namespace
+
+extern "C" SAL_DLLPUBLIC_EXPORT
+css::uno::XInterface* com_sun_star_graphic_GraphicObject_get_implementation(
+ SAL_UNUSED_PARAMETER uno::XComponentContext*,
+ uno::Sequence<uno::Any> const & rArguments)
+{
+ return cppu::acquire(new GraphicObjectImpl(rArguments));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/UnoGraphicProvider.cxx b/vcl/source/graphic/UnoGraphicProvider.cxx
new file mode 100644
index 000000000..f0e0d12e7
--- /dev/null
+++ b/vcl/source/graphic/UnoGraphicProvider.cxx
@@ -0,0 +1,836 @@
+/* -*- 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 <o3tl/string_view.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/image.hxx>
+#include <vcl/metaact.hxx>
+#include <imagerepository.hxx>
+#include <tools/fract.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/stdtext.hxx>
+#include <vcl/wmfexternal.hxx>
+#include <vcl/virdev.hxx>
+#include <com/sun/star/awt/XBitmap.hpp>
+#include <com/sun/star/graphic/XGraphicProvider2.hpp>
+#include <com/sun/star/io/XStream.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/text/GraphicCrop.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <comphelper/fileformat.h>
+#include <comphelper/servicehelper.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <sal/log.hxx>
+
+#include <graphic/UnoGraphicDescriptor.hxx>
+#include <graphic/UnoGraphic.hxx>
+#include <rtl/ref.hxx>
+#include <vcl/dibtools.hxx>
+#include <comphelper/sequence.hxx>
+#include <memory>
+#include <string_view>
+
+#include <vcl/TypeSerializer.hxx>
+
+using namespace com::sun::star;
+
+namespace {
+
+class GraphicProvider : public ::cppu::WeakImplHelper< css::graphic::XGraphicProvider2,
+ css::lang::XServiceInfo >
+{
+public:
+
+ GraphicProvider();
+
+protected:
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XTypeProvider
+ virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override;
+ virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override;
+
+ // XGraphicProvider
+ virtual css::uno::Reference< css::beans::XPropertySet > SAL_CALL queryGraphicDescriptor( const css::uno::Sequence< css::beans::PropertyValue >& MediaProperties ) override;
+ virtual css::uno::Reference< css::graphic::XGraphic > SAL_CALL queryGraphic( const css::uno::Sequence< css::beans::PropertyValue >& MediaProperties ) override;
+ virtual void SAL_CALL storeGraphic( const css::uno::Reference< css::graphic::XGraphic >& Graphic, const css::uno::Sequence< css::beans::PropertyValue >& MediaProperties ) override;
+
+ // XGraphicProvider2
+ uno::Sequence< uno::Reference<graphic::XGraphic> > SAL_CALL queryGraphics(const uno::Sequence< uno::Sequence<beans::PropertyValue> >& MediaPropertiesSeq ) override;
+
+private:
+
+ static css::uno::Reference< css::graphic::XGraphic > implLoadMemory( std::u16string_view rResourceURL );
+ static css::uno::Reference< css::graphic::XGraphic > implLoadRepositoryImage( std::u16string_view rResourceURL );
+ static css::uno::Reference< css::graphic::XGraphic > implLoadBitmap( const css::uno::Reference< css::awt::XBitmap >& rBitmap );
+ static css::uno::Reference< css::graphic::XGraphic > implLoadStandardImage( std::u16string_view rResourceURL );
+};
+
+GraphicProvider::GraphicProvider()
+{
+}
+
+OUString SAL_CALL GraphicProvider::getImplementationName()
+{
+ return "com.sun.star.comp.graphic.GraphicProvider";
+}
+
+sal_Bool SAL_CALL GraphicProvider::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService( this, ServiceName );
+}
+
+uno::Sequence< OUString > SAL_CALL GraphicProvider::getSupportedServiceNames()
+{
+ return { "com.sun.star.graphic.GraphicProvider" };
+}
+
+uno::Sequence< uno::Type > SAL_CALL GraphicProvider::getTypes()
+{
+ static const uno::Sequence< uno::Type > aTypes {
+ cppu::UnoType<lang::XServiceInfo>::get(),
+ cppu::UnoType<lang::XTypeProvider>::get(),
+ cppu::UnoType<graphic::XGraphicProvider>::get()
+ };
+ return aTypes;
+}
+
+uno::Sequence< sal_Int8 > SAL_CALL GraphicProvider::getImplementationId()
+{
+ return css::uno::Sequence<sal_Int8>();
+}
+
+uno::Reference< ::graphic::XGraphic > GraphicProvider::implLoadMemory( std::u16string_view rResourceURL )
+{
+ uno::Reference< ::graphic::XGraphic > xRet;
+ sal_Int32 nIndex = 0;
+
+ if( o3tl::getToken(rResourceURL, 0, '/', nIndex ) == u"private:memorygraphic" )
+ {
+ sal_Int64 nGraphicAddress = o3tl::toInt64(o3tl::getToken(rResourceURL, 0, '/', nIndex ));
+
+ if( nGraphicAddress )
+ {
+ rtl::Reference<::unographic::Graphic> pUnoGraphic = new ::unographic::Graphic;
+
+ pUnoGraphic->init( *reinterpret_cast< ::Graphic* >( nGraphicAddress ) );
+ xRet = pUnoGraphic;
+ }
+ }
+
+ return xRet;
+}
+
+uno::Reference< ::graphic::XGraphic > GraphicProvider::implLoadRepositoryImage( std::u16string_view rResourceURL )
+{
+ uno::Reference< ::graphic::XGraphic > xRet;
+
+ std::u16string_view sPathName;
+ if( o3tl::starts_with(rResourceURL, u"private:graphicrepository/", &sPathName) )
+ {
+ BitmapEx aBitmap;
+ if ( vcl::ImageRepository::loadImage( OUString(sPathName), aBitmap ) )
+ {
+ Graphic aGraphic(aBitmap);
+ aGraphic.setOriginURL(OUString(rResourceURL));
+ xRet = aGraphic.GetXGraphic();
+ }
+ }
+ return xRet;
+}
+
+uno::Reference< ::graphic::XGraphic > GraphicProvider::implLoadStandardImage( std::u16string_view rResourceURL )
+{
+ uno::Reference< ::graphic::XGraphic > xRet;
+
+ std::u16string_view sImageName;
+ if( o3tl::starts_with(rResourceURL, u"private:standardimage/", &sImageName) )
+ {
+ if ( sImageName == u"info" )
+ {
+ xRet = Graphic(GetStandardInfoBoxImage().GetBitmapEx()).GetXGraphic();
+ }
+ else if ( sImageName == u"warning" )
+ {
+ xRet = Graphic(GetStandardWarningBoxImage().GetBitmapEx()).GetXGraphic();
+ }
+ else if ( sImageName == u"error" )
+ {
+ xRet = Graphic(GetStandardErrorBoxImage().GetBitmapEx()).GetXGraphic();
+ }
+ else if ( sImageName == u"query" )
+ {
+ xRet = Graphic(GetStandardQueryBoxImage().GetBitmapEx()).GetXGraphic();
+ }
+ }
+ return xRet;
+}
+
+
+uno::Reference< ::graphic::XGraphic > GraphicProvider::implLoadBitmap( const uno::Reference< awt::XBitmap >& xBtm )
+{
+ uno::Reference< ::graphic::XGraphic > xRet;
+ uno::Sequence< sal_Int8 > aBmpSeq( xBtm->getDIB() );
+ uno::Sequence< sal_Int8 > aMaskSeq( xBtm->getMaskDIB() );
+ SvMemoryStream aBmpStream( aBmpSeq.getArray(), aBmpSeq.getLength(), StreamMode::READ );
+ Bitmap aBmp;
+ BitmapEx aBmpEx;
+
+ ReadDIB(aBmp, aBmpStream, true);
+
+ if( aMaskSeq.hasElements() )
+ {
+ SvMemoryStream aMaskStream( aMaskSeq.getArray(), aMaskSeq.getLength(), StreamMode::READ );
+ Bitmap aMask;
+
+ ReadDIB(aMask, aMaskStream, true);
+ aBmpEx = BitmapEx( aBmp, aMask );
+ }
+ else
+ aBmpEx = BitmapEx( aBmp );
+
+ if( !aBmpEx.IsEmpty() )
+ {
+ rtl::Reference<::unographic::Graphic> pUnoGraphic = new ::unographic::Graphic;
+
+ pUnoGraphic->init( aBmpEx );
+ xRet = pUnoGraphic;
+ }
+ return xRet;
+}
+
+uno::Reference< beans::XPropertySet > SAL_CALL GraphicProvider::queryGraphicDescriptor( const uno::Sequence< beans::PropertyValue >& rMediaProperties )
+{
+ uno::Reference< beans::XPropertySet > xRet;
+
+ OUString aURL;
+ uno::Reference< io::XInputStream > xIStm;
+ uno::Reference< awt::XBitmap >xBtm;
+
+ for( const auto& rMediaProperty : rMediaProperties )
+ {
+ if (xRet.is())
+ break;
+
+ const OUString aName( rMediaProperty.Name );
+ const uno::Any aValue( rMediaProperty.Value );
+
+ if (aName == "URL")
+ {
+ aValue >>= aURL;
+ }
+ else if (aName == "InputStream")
+ {
+ aValue >>= xIStm;
+ }
+ else if (aName == "Bitmap")
+ {
+ aValue >>= xBtm;
+ }
+ }
+
+ SolarMutexGuard g;
+
+ if( xIStm.is() )
+ {
+ rtl::Reference<unographic::GraphicDescriptor> pDescriptor = new unographic::GraphicDescriptor;
+ pDescriptor->init( xIStm, aURL );
+ xRet = pDescriptor;
+ }
+ else if( !aURL.isEmpty() )
+ {
+ uno::Reference< ::graphic::XGraphic > xGraphic( implLoadMemory( aURL ) );
+
+ if ( !xGraphic.is() )
+ xGraphic = implLoadRepositoryImage( aURL );
+
+ if ( !xGraphic.is() )
+ xGraphic = implLoadStandardImage( aURL );
+
+ if( xGraphic.is() )
+ {
+ xRet.set( xGraphic, uno::UNO_QUERY );
+ }
+ else
+ {
+ rtl::Reference<unographic::GraphicDescriptor> pDescriptor = new unographic::GraphicDescriptor;
+ pDescriptor->init( aURL );
+ xRet = pDescriptor;
+ }
+ }
+ else if( xBtm.is() )
+ {
+ uno::Reference< ::graphic::XGraphic > xGraphic( implLoadBitmap( xBtm ) );
+ if( xGraphic.is() )
+ xRet.set( xGraphic, uno::UNO_QUERY );
+ }
+
+ return xRet;
+}
+
+
+uno::Reference< ::graphic::XGraphic > SAL_CALL GraphicProvider::queryGraphic( const uno::Sequence< ::beans::PropertyValue >& rMediaProperties )
+{
+ uno::Reference< ::graphic::XGraphic > xRet;
+ OUString aPath;
+
+ uno::Reference< io::XInputStream > xIStm;
+ uno::Reference< awt::XBitmap >xBtm;
+
+ uno::Sequence< ::beans::PropertyValue > aFilterData;
+
+ bool bLazyRead = false;
+ bool bLoadAsLink = false;
+
+ for (const auto& rMediaProperty : rMediaProperties)
+ {
+ if (xRet.is())
+ break;
+
+ const OUString aName( rMediaProperty.Name );
+ const uno::Any aValue( rMediaProperty.Value );
+
+ if (aName == "URL")
+ {
+ OUString aURL;
+ aValue >>= aURL;
+ aPath = aURL;
+ }
+ else if (aName == "InputStream")
+ {
+ aValue >>= xIStm;
+ }
+ else if (aName == "Bitmap")
+ {
+ aValue >>= xBtm;
+ }
+ else if (aName == "FilterData")
+ {
+ aValue >>= aFilterData;
+ }
+ else if (aName == "LazyRead")
+ {
+ aValue >>= bLazyRead;
+ }
+ else if (aName == "LoadAsLink")
+ {
+ aValue >>= bLoadAsLink;
+ }
+ }
+
+ // Check for the goal width and height if they are defined
+ sal_uInt16 nExtWidth = 0;
+ sal_uInt16 nExtHeight = 0;
+ sal_uInt16 nExtMapMode = 0;
+ for( const auto& rProp : std::as_const(aFilterData) )
+ {
+ const OUString aName( rProp.Name );
+ const uno::Any aValue( rProp.Value );
+
+ if (aName == "ExternalWidth")
+ {
+ aValue >>= nExtWidth;
+ }
+ else if (aName == "ExternalHeight")
+ {
+ aValue >>= nExtHeight;
+ }
+ else if (aName == "ExternalMapMode")
+ {
+ aValue >>= nExtMapMode;
+ }
+ }
+
+ SolarMutexGuard g;
+
+ std::unique_ptr<SvStream> pIStm;
+
+ if( xIStm.is() )
+ {
+ pIStm = ::utl::UcbStreamHelper::CreateStream( xIStm );
+ }
+ else if( !aPath.isEmpty() )
+ {
+ xRet = implLoadMemory( aPath );
+
+ if ( !xRet.is() )
+ xRet = implLoadRepositoryImage( aPath );
+
+ if ( !xRet.is() )
+ xRet = implLoadStandardImage( aPath );
+
+ if( !xRet.is() )
+ pIStm = ::utl::UcbStreamHelper::CreateStream( aPath, StreamMode::READ );
+ }
+ else if( xBtm.is() )
+ {
+ xRet = implLoadBitmap( xBtm );
+ }
+
+ if( pIStm )
+ {
+ ::GraphicFilter& rFilter = ::GraphicFilter::GetGraphicFilter();
+
+ {
+ Graphic aVCLGraphic;
+
+ // Define APM Header if goal height and width are defined
+ WmfExternal aExtHeader;
+ aExtHeader.xExt = nExtWidth;
+ aExtHeader.yExt = nExtHeight;
+ aExtHeader.mapMode = nExtMapMode;
+ if ( nExtMapMode > 0 )
+ {
+ bLazyRead = false;
+ }
+
+ ErrCode error = ERRCODE_NONE;
+ if (bLazyRead)
+ {
+ Graphic aGraphic = rFilter.ImportUnloadedGraphic(*pIStm);
+ if (!aGraphic.IsNone())
+ aVCLGraphic = aGraphic;
+ }
+ if (aVCLGraphic.IsNone())
+ error = rFilter.ImportGraphic(aVCLGraphic, aPath, *pIStm, GRFILTER_FORMAT_DONTKNOW, nullptr, GraphicFilterImportFlags::NONE);
+
+ if( (error == ERRCODE_NONE ) &&
+ ( aVCLGraphic.GetType() != GraphicType::NONE ) )
+ {
+ if (!aPath.isEmpty() && bLoadAsLink)
+ aVCLGraphic.setOriginURL(aPath);
+
+ rtl::Reference<::unographic::Graphic> pUnoGraphic = new ::unographic::Graphic;
+
+ pUnoGraphic->init( aVCLGraphic );
+ xRet = pUnoGraphic;
+ }
+ else{
+ SAL_WARN("svtools", "Could not create graphic for:" << aPath << " error: " << error);
+ }
+ }
+ }
+
+ return xRet;
+}
+
+uno::Sequence< uno::Reference<graphic::XGraphic> > SAL_CALL GraphicProvider::queryGraphics(const uno::Sequence< uno::Sequence<beans::PropertyValue> >& rMediaPropertiesSeq)
+{
+ // Turn properties into streams.
+ std::vector< std::unique_ptr<SvStream> > aStreams;
+ for (const auto& rMediaProperties : rMediaPropertiesSeq)
+ {
+ std::unique_ptr<SvStream> pStream;
+ uno::Reference<io::XInputStream> xStream;
+
+ auto pProp = std::find_if(rMediaProperties.begin(), rMediaProperties.end(),
+ [](const beans::PropertyValue& rProp) { return rProp.Name == "InputStream"; });
+ if (pProp != rMediaProperties.end())
+ {
+ pProp->Value >>= xStream;
+ if (xStream.is())
+ pStream = utl::UcbStreamHelper::CreateStream(xStream);
+ }
+
+ aStreams.push_back(std::move(pStream));
+ }
+
+ // Import: streams to graphics.
+ std::vector< std::shared_ptr<Graphic> > aGraphics;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.ImportGraphics(aGraphics, std::move(aStreams));
+
+ // Returning: graphics to UNO objects.
+ std::vector< uno::Reference<graphic::XGraphic> > aRet;
+ for (const auto& pGraphic : aGraphics)
+ {
+ uno::Reference<graphic::XGraphic> xGraphic;
+
+ if (pGraphic)
+ {
+ rtl::Reference<unographic::Graphic> pUnoGraphic = new unographic::Graphic();
+ pUnoGraphic->init(*pGraphic);
+ xGraphic = pUnoGraphic;
+ }
+
+ aRet.push_back(xGraphic);
+ }
+
+ return comphelper::containerToSequence(aRet);
+}
+
+void ImplCalculateCropRect( ::Graphic const & rGraphic, const text::GraphicCrop& rGraphicCropLogic, tools::Rectangle& rGraphicCropPixel )
+{
+ if ( !(rGraphicCropLogic.Left || rGraphicCropLogic.Top || rGraphicCropLogic.Right || rGraphicCropLogic.Bottom) )
+ return;
+
+ Size aSourceSizePixel( rGraphic.GetSizePixel() );
+ if ( !(aSourceSizePixel.Width() && aSourceSizePixel.Height()) )
+ return;
+
+ if ( !(rGraphicCropLogic.Left || rGraphicCropLogic.Top || rGraphicCropLogic.Right || rGraphicCropLogic.Bottom) )
+ return;
+
+ Size aSize100thMM( 0, 0 );
+ if( rGraphic.GetPrefMapMode().GetMapUnit() != MapUnit::MapPixel )
+ {
+ aSize100thMM = OutputDevice::LogicToLogic(rGraphic.GetPrefSize(), rGraphic.GetPrefMapMode(), MapMode(MapUnit::Map100thMM));
+ }
+ else
+ {
+ aSize100thMM = Application::GetDefaultDevice()->PixelToLogic(rGraphic.GetPrefSize(), MapMode(MapUnit::Map100thMM));
+ }
+ if ( aSize100thMM.Width() && aSize100thMM.Height() )
+ {
+ double fSourceSizePixelWidth = static_cast<double>(aSourceSizePixel.Width());
+ double fSourceSizePixelHeight= static_cast<double>(aSourceSizePixel.Height());
+ rGraphicCropPixel.SetLeft( static_cast< sal_Int32 >((fSourceSizePixelWidth * rGraphicCropLogic.Left ) / aSize100thMM.Width()) );
+ rGraphicCropPixel.SetTop( static_cast< sal_Int32 >((fSourceSizePixelHeight * rGraphicCropLogic.Top ) / aSize100thMM.Height()) );
+ rGraphicCropPixel.SetRight( static_cast< sal_Int32 >(( fSourceSizePixelWidth * ( aSize100thMM.Width() - rGraphicCropLogic.Right ) ) / aSize100thMM.Width() ) );
+ rGraphicCropPixel.SetBottom( static_cast< sal_Int32 >(( fSourceSizePixelHeight * ( aSize100thMM.Height() - rGraphicCropLogic.Bottom ) ) / aSize100thMM.Height() ) );
+ }
+}
+
+void ImplApplyBitmapScaling( ::Graphic& rGraphic, sal_Int32 nPixelWidth, sal_Int32 nPixelHeight )
+{
+ if ( nPixelWidth && nPixelHeight )
+ {
+ BitmapEx aBmpEx( rGraphic.GetBitmapEx() );
+ MapMode aPrefMapMode( aBmpEx.GetPrefMapMode() );
+ Size aPrefSize( aBmpEx.GetPrefSize() );
+ aBmpEx.Scale( Size( nPixelWidth, nPixelHeight ) );
+ aBmpEx.SetPrefMapMode( aPrefMapMode );
+ aBmpEx.SetPrefSize( aPrefSize );
+ rGraphic = aBmpEx;
+ }
+}
+
+void ImplApplyBitmapResolution( ::Graphic& rGraphic, sal_Int32 nImageResolution, const Size& rVisiblePixelSize, const awt::Size& rLogicalSize )
+{
+ if ( !(nImageResolution && rLogicalSize.Width && rLogicalSize.Height) )
+ return;
+
+ const double fImageResolution = static_cast<double>( nImageResolution );
+ const double fSourceDPIX = ( static_cast<double>(rVisiblePixelSize.Width()) * 2540.0 ) / static_cast<double>(rLogicalSize.Width);
+ const double fSourceDPIY = ( static_cast<double>(rVisiblePixelSize.Height()) * 2540.0 ) / static_cast<double>(rLogicalSize.Height);
+ const sal_Int32 nSourcePixelWidth( rGraphic.GetSizePixel().Width() );
+ const sal_Int32 nSourcePixelHeight( rGraphic.GetSizePixel().Height() );
+ const double fSourcePixelWidth = static_cast<double>( nSourcePixelWidth );
+ const double fSourcePixelHeight= static_cast<double>( nSourcePixelHeight );
+
+ sal_Int32 nDestPixelWidth = nSourcePixelWidth;
+ sal_Int32 nDestPixelHeight = nSourcePixelHeight;
+
+ // check, if the bitmap DPI exceeds the maximum DPI
+ if( fSourceDPIX > fImageResolution )
+ {
+ nDestPixelWidth = static_cast<sal_Int32>(( fSourcePixelWidth * fImageResolution ) / fSourceDPIX);
+ if ( !nDestPixelWidth || ( nDestPixelWidth > nSourcePixelWidth ) )
+ nDestPixelWidth = nSourcePixelWidth;
+ }
+ if ( fSourceDPIY > fImageResolution )
+ {
+ nDestPixelHeight= static_cast<sal_Int32>(( fSourcePixelHeight* fImageResolution ) / fSourceDPIY);
+ if ( !nDestPixelHeight || ( nDestPixelHeight > nSourcePixelHeight ) )
+ nDestPixelHeight = nSourcePixelHeight;
+ }
+ if ( ( nDestPixelWidth != nSourcePixelWidth ) || ( nDestPixelHeight != nSourcePixelHeight ) )
+ ImplApplyBitmapScaling( rGraphic, nDestPixelWidth, nDestPixelHeight );
+}
+
+void ImplApplyFilterData( ::Graphic& rGraphic, const uno::Sequence< beans::PropertyValue >& rFilterData )
+{
+ /* this method applies following attributes to the graphic, in the first step the
+ cropping area (logical size in 100thmm) is applied, in the second step the resolution
+ is applied, in the third step the graphic is scaled to the corresponding pixelsize.
+ if a parameter value is zero or not available the corresponding step will be skipped */
+
+ sal_Int32 nPixelWidth = 0;
+ sal_Int32 nPixelHeight= 0;
+ sal_Int32 nImageResolution = 0;
+ awt::Size aLogicalSize( 0, 0 );
+ text::GraphicCrop aCropLogic( 0, 0, 0, 0 );
+ bool bRemoveCropArea = true;
+
+ for( const auto& rProp : rFilterData )
+ {
+ const OUString aName( rProp.Name );
+ const uno::Any aValue( rProp.Value );
+
+ if (aName == "PixelWidth")
+ aValue >>= nPixelWidth;
+ else if (aName == "PixelHeight")
+ aValue >>= nPixelHeight;
+ else if (aName == "LogicalSize")
+ aValue >>= aLogicalSize;
+ else if (aName == "GraphicCropLogic")
+ aValue >>= aCropLogic;
+ else if (aName == "RemoveCropArea")
+ aValue >>= bRemoveCropArea;
+ else if (aName == "ImageResolution")
+ aValue >>= nImageResolution;
+ }
+ if ( rGraphic.GetType() == GraphicType::Bitmap )
+ {
+ if(rGraphic.getVectorGraphicData())
+ {
+ // embedded Vector Graphic Data, no need to scale. Also no method to apply crop data currently
+ }
+ else
+ {
+ tools::Rectangle aCropPixel( Point( 0, 0 ), rGraphic.GetSizePixel() );
+ ImplCalculateCropRect( rGraphic, aCropLogic, aCropPixel );
+ if ( bRemoveCropArea )
+ {
+ BitmapEx aBmpEx( rGraphic.GetBitmapEx() );
+ aBmpEx.Crop( aCropPixel );
+ rGraphic = aBmpEx;
+ }
+ Size aVisiblePixelSize( bRemoveCropArea ? rGraphic.GetSizePixel() : aCropPixel.GetSize() );
+ ImplApplyBitmapResolution( rGraphic, nImageResolution, aVisiblePixelSize, aLogicalSize );
+ ImplApplyBitmapScaling( rGraphic, nPixelWidth, nPixelHeight );
+ }
+ }
+ else if ( ( rGraphic.GetType() == GraphicType::GdiMetafile ) && nImageResolution )
+ {
+ ScopedVclPtrInstance< VirtualDevice > aDummyVDev;
+ GDIMetaFile aMtf( rGraphic.GetGDIMetaFile() );
+ Size aMtfSize( OutputDevice::LogicToLogic(aMtf.GetPrefSize(), aMtf.GetPrefMapMode(), MapMode(MapUnit::Map100thMM)) );
+ if ( aMtfSize.Width() && aMtfSize.Height() )
+ {
+ MapMode aNewMapMode( MapUnit::Map100thMM );
+ aNewMapMode.SetScaleX( Fraction( aLogicalSize.Width, aMtfSize.Width() ) );
+ aNewMapMode.SetScaleY( Fraction( aLogicalSize.Height, aMtfSize.Height() ) );
+ aDummyVDev->EnableOutput( false );
+ aDummyVDev->SetMapMode( aNewMapMode );
+
+ for( size_t i = 0, nObjCount = aMtf.GetActionSize(); i < nObjCount; i++ )
+ {
+ MetaAction* pAction = aMtf.GetAction( i );
+ switch( pAction->GetType() )
+ {
+ // only optimizing common bitmap actions:
+ case MetaActionType::MAPMODE:
+ {
+ pAction->Execute( aDummyVDev.get() );
+ break;
+ }
+ case MetaActionType::PUSH:
+ {
+ const MetaPushAction* pA = static_cast<const MetaPushAction*>(pAction);
+ aDummyVDev->Push( pA->GetFlags() );
+ break;
+ }
+ case MetaActionType::POP:
+ {
+ aDummyVDev->Pop();
+ break;
+ }
+ case MetaActionType::BMPSCALE:
+ case MetaActionType::BMPEXSCALE:
+ {
+ BitmapEx aBmpEx;
+ Point aPos;
+ Size aSize;
+ if ( pAction->GetType() == MetaActionType::BMPSCALE )
+ {
+ MetaBmpScaleAction* pScaleAction = dynamic_cast< MetaBmpScaleAction* >( pAction );
+ assert(pScaleAction);
+ aBmpEx = pScaleAction->GetBitmap();
+ aPos = pScaleAction->GetPoint();
+ aSize = pScaleAction->GetSize();
+ }
+ else
+ {
+ MetaBmpExScaleAction* pScaleAction = dynamic_cast< MetaBmpExScaleAction* >( pAction );
+ assert(pScaleAction);
+ aBmpEx = pScaleAction->GetBitmapEx();
+ aPos = pScaleAction->GetPoint();
+ aSize = pScaleAction->GetSize();
+ }
+ ::Graphic aGraphic( aBmpEx );
+ const Size aSize100thmm( aDummyVDev->LogicToPixel( aSize ) );
+ Size aSize100thmm2( aDummyVDev->PixelToLogic(aSize100thmm, MapMode(MapUnit::Map100thMM)) );
+
+ ImplApplyBitmapResolution( aGraphic, nImageResolution,
+ aGraphic.GetSizePixel(), awt::Size( aSize100thmm2.Width(), aSize100thmm2.Height() ) );
+
+ rtl::Reference<MetaAction> pNewAction = new MetaBmpExScaleAction( aPos, aSize, aGraphic.GetBitmapEx() );
+ aMtf.ReplaceAction( pNewAction, i );
+ break;
+ }
+ default:
+ case MetaActionType::BMP:
+ case MetaActionType::BMPSCALEPART:
+ case MetaActionType::BMPEX:
+ case MetaActionType::BMPEXSCALEPART:
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ break;
+ }
+ }
+ rGraphic = aMtf;
+ }
+ }
+}
+
+
+void SAL_CALL GraphicProvider::storeGraphic( const uno::Reference< ::graphic::XGraphic >& rxGraphic, const uno::Sequence< beans::PropertyValue >& rMediaProperties )
+{
+ std::unique_ptr<SvStream> pOStm;
+ OUString aPath;
+
+ for( const auto& rMediaProperty : rMediaProperties )
+ {
+ const OUString aName( rMediaProperty.Name );
+ const uno::Any aValue( rMediaProperty.Value );
+
+ if (aName == "URL")
+ {
+ OUString aURL;
+
+ aValue >>= aURL;
+ pOStm = ::utl::UcbStreamHelper::CreateStream( aURL, StreamMode::WRITE | StreamMode::TRUNC );
+ aPath = aURL;
+ }
+ else if (aName == "OutputStream")
+ {
+ uno::Reference< io::XStream > xOStm;
+
+ aValue >>= xOStm;
+
+ if( xOStm.is() )
+ pOStm = ::utl::UcbStreamHelper::CreateStream( xOStm );
+ }
+
+ if( pOStm )
+ break;
+ }
+
+ if( !pOStm )
+ return;
+
+ uno::Sequence< beans::PropertyValue > aFilterDataSeq;
+ OUString sFilterShortName;
+
+ for( const auto& rMediaProperty : rMediaProperties )
+ {
+ const OUString aName( rMediaProperty.Name );
+ const uno::Any aValue( rMediaProperty.Value );
+
+ if (aName == "FilterData")
+ {
+ aValue >>= aFilterDataSeq;
+ }
+ else if (aName == "MimeType")
+ {
+ OUString aMimeType;
+
+ aValue >>= aMimeType;
+
+ if (aMimeType == MIMETYPE_BMP)
+ sFilterShortName = "bmp";
+ else if (aMimeType == MIMETYPE_EPS)
+ sFilterShortName = "eps";
+ else if (aMimeType == MIMETYPE_GIF)
+ sFilterShortName = "gif";
+ else if (aMimeType == MIMETYPE_JPG)
+ sFilterShortName = "jpg";
+ else if (aMimeType == MIMETYPE_MET)
+ sFilterShortName = "met";
+ else if (aMimeType == MIMETYPE_PNG)
+ sFilterShortName = "png";
+ else if (aMimeType == MIMETYPE_PCT)
+ sFilterShortName = "pct";
+ else if (aMimeType == MIMETYPE_PBM)
+ sFilterShortName = "pbm";
+ else if (aMimeType == MIMETYPE_PGM)
+ sFilterShortName = "pgm";
+ else if (aMimeType == MIMETYPE_PPM)
+ sFilterShortName = "ppm";
+ else if (aMimeType == MIMETYPE_RAS)
+ sFilterShortName = "ras";
+ else if (aMimeType == MIMETYPE_SVM)
+ sFilterShortName = "svm";
+ else if (aMimeType == MIMETYPE_TIF)
+ sFilterShortName = "tif";
+ else if (aMimeType == MIMETYPE_EMF)
+ sFilterShortName = "emf";
+ else if (aMimeType == MIMETYPE_WMF)
+ sFilterShortName = "wmf";
+ else if (aMimeType == MIMETYPE_XPM)
+ sFilterShortName = "xpm";
+ else if (aMimeType == MIMETYPE_SVG)
+ sFilterShortName = "svg";
+ else if (aMimeType == MIMETYPE_VCLGRAPHIC)
+ sFilterShortName = MIMETYPE_VCLGRAPHIC;
+ }
+ }
+
+ if( sFilterShortName.isEmpty() )
+ return;
+
+ ::GraphicFilter& rFilter = ::GraphicFilter::GetGraphicFilter();
+
+ {
+ const uno::Reference< XInterface > xIFace( rxGraphic, uno::UNO_QUERY );
+ const ::Graphic* pGraphic = comphelper::getFromUnoTunnel<::Graphic>( xIFace );
+
+ if( pGraphic && ( pGraphic->GetType() != GraphicType::NONE ) )
+ {
+ ::Graphic aGraphic( *pGraphic );
+ ImplApplyFilterData( aGraphic, aFilterDataSeq );
+
+ /* sj: using a temporary memory stream, because some graphic filters are seeking behind
+ stream end (which leads to an invalid argument exception then). */
+ SvMemoryStream aMemStrm;
+ aMemStrm.SetVersion( SOFFICE_FILEFORMAT_CURRENT );
+ if( sFilterShortName == MIMETYPE_VCLGRAPHIC )
+ {
+ TypeSerializer aSerializer(aMemStrm);
+ aSerializer.writeGraphic(aGraphic);
+ }
+ else
+ {
+ rFilter.ExportGraphic( aGraphic, aPath, aMemStrm,
+ rFilter.GetExportFormatNumberForShortName( sFilterShortName ),
+ ( aFilterDataSeq.hasElements() ? &aFilterDataSeq : nullptr ) );
+ }
+ pOStm->WriteBytes( aMemStrm.GetData(), aMemStrm.TellEnd() );
+ }
+ }
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_graphic_GraphicProvider_get_implementation(
+ css::uno::XComponentContext *,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new GraphicProvider);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/VectorGraphicLoader.cxx b/vcl/source/graphic/VectorGraphicLoader.cxx
new file mode 100644
index 000000000..997681092
--- /dev/null
+++ b/vcl/source/graphic/VectorGraphicLoader.cxx
@@ -0,0 +1,26 @@
+/* -*- 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/.
+ *
+ */
+
+#include <tools/stream.hxx>
+#include <graphic/VectorGraphicLoader.hxx>
+
+namespace vcl
+{
+std::shared_ptr<VectorGraphicData> loadVectorGraphic(BinaryDataContainer const& rDataContainer,
+ VectorGraphicDataType eType)
+{
+ if (rDataContainer.isEmpty())
+ return std::shared_ptr<VectorGraphicData>();
+
+ return std::make_shared<VectorGraphicData>(rDataContainer, eType);
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/graphic/VectorGraphicSearch.cxx b/vcl/source/graphic/VectorGraphicSearch.cxx
new file mode 100644
index 000000000..63dae6e52
--- /dev/null
+++ b/vcl/source/graphic/VectorGraphicSearch.cxx
@@ -0,0 +1,306 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/VectorGraphicSearch.hxx>
+
+#include <vcl/filter/PDFiumLibrary.hxx>
+#include <tools/UnitConversion.hxx>
+
+#include <sal/config.h>
+
+namespace
+{
+class SearchContext
+{
+private:
+ std::unique_ptr<vcl::pdf::PDFiumDocument>& mpPdfDocument;
+ std::unique_ptr<vcl::pdf::PDFiumPage> mpPage;
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> mpTextPage;
+ std::unique_ptr<vcl::pdf::PDFiumSearchHandle> mpSearchHandle;
+
+public:
+ sal_Int32 mnPageIndex;
+ int mnCurrentIndex;
+ OUString maSearchString;
+ VectorGraphicSearchOptions maOptions;
+
+ SearchContext(std::unique_ptr<vcl::pdf::PDFiumDocument>& pPdfDocument, sal_Int32 nPageIndex)
+ : mpPdfDocument(pPdfDocument)
+ , mnPageIndex(nPageIndex)
+ , mnCurrentIndex(-1)
+ {
+ }
+
+ ~SearchContext()
+ {
+ if (mpSearchHandle)
+ mpSearchHandle.reset();
+ if (mpTextPage)
+ mpTextPage.reset();
+ if (mpPage)
+ mpPage.reset();
+ }
+
+ basegfx::B2DSize getPageSize()
+ {
+ basegfx::B2DSize aSize;
+ if (!mpPdfDocument)
+ return aSize;
+
+ basegfx::B2DSize aPDFSize = mpPdfDocument->getPageSize(mnPageIndex);
+ aSize = basegfx::B2DSize(convertPointToMm100(aPDFSize.getX()),
+ convertPointToMm100(aPDFSize.getY()));
+ return aSize;
+ }
+
+ bool initialize(OUString const& rSearchString, VectorGraphicSearchOptions const& rOptions)
+ {
+ if (!mpPdfDocument)
+ return false;
+
+ if (rSearchString == maSearchString)
+ return true;
+
+ if (mpSearchHandle)
+ mpSearchHandle.reset();
+
+ if (mpTextPage)
+ mpTextPage.reset();
+
+ if (mpPage)
+ mpPage.reset();
+
+ maSearchString = rSearchString;
+ maOptions = rOptions;
+
+ mpPage = mpPdfDocument->openPage(mnPageIndex);
+ if (!mpPage)
+ return false;
+
+ mpTextPage = mpPage->getTextPage();
+ if (!mpTextPage)
+ return false;
+
+ // Index where to start to search. -1 => at the end
+ int nStartIndex = maOptions.meStartPosition == SearchStartPosition::End ? -1 : 0;
+
+ if (mnCurrentIndex >= 0)
+ nStartIndex = mnCurrentIndex;
+
+ // vcl::pdf::PDFFindFlags::MatchCase, vcl::pdf::PDFFindFlags::MatchWholeWord, vcl::pdf::PDFFindFlags::Consecutive
+ // vcl::pdf::PDFFindFlags::MatchCase - If not set, it will not match case by default.
+ // vcl::pdf::PDFFindFlags::MatchWholeWord - If not set, it will not match the whole word by default.
+ // vcl::pdf::PDFFindFlags::Consecutive - If not set, it will skip past the current match to look for the next match.
+ vcl::pdf::PDFFindFlags nSearchFlags{};
+ if (maOptions.mbMatchCase)
+ nSearchFlags |= vcl::pdf::PDFFindFlags::MatchCase;
+ if (maOptions.mbMatchWholeWord)
+ nSearchFlags |= vcl::pdf::PDFFindFlags::MatchWholeWord;
+
+ mpSearchHandle = mpTextPage->findStart(maSearchString, nSearchFlags, nStartIndex);
+
+ return mpSearchHandle != nullptr;
+ }
+
+ bool next()
+ {
+ if (mpSearchHandle && mpSearchHandle->findNext())
+ {
+ mnCurrentIndex = index();
+ return true;
+ }
+ return false;
+ }
+
+ bool previous()
+ {
+ if (mpSearchHandle && mpSearchHandle->findPrev())
+ {
+ mnCurrentIndex = index();
+ return true;
+ }
+ return false;
+ }
+
+ int index()
+ {
+ if (mpSearchHandle)
+ return mpSearchHandle->getSearchResultIndex();
+ return -1;
+ }
+
+ int size()
+ {
+ if (mpSearchHandle)
+ return mpSearchHandle->getSearchCount();
+ return -1;
+ }
+
+ std::vector<basegfx::B2DRectangle> getTextRectangles()
+ {
+ std::vector<basegfx::B2DRectangle> aRectangles;
+
+ if (!mpTextPage || !mpSearchHandle)
+ return aRectangles;
+
+ int nIndex = index();
+ if (nIndex < 0)
+ return aRectangles;
+
+ int nSize = size();
+ if (nSize <= 0)
+ return aRectangles;
+
+ double fPageHeight = getPageSize().getY();
+
+ for (int nCount = 0; nCount < nSize; nCount++)
+ {
+ basegfx::B2DRectangle aRectangle = mpTextPage->getCharBox(nIndex + nCount, fPageHeight);
+ if (!aRectangle.isEmpty())
+ {
+ aRectangles.push_back(aRectangle);
+ }
+ }
+
+ return aRectangles;
+ }
+};
+
+} // end anonymous namespace
+
+class VectorGraphicSearch::Implementation
+{
+public:
+ std::shared_ptr<vcl::pdf::PDFium> mpPDFium;
+ std::unique_ptr<vcl::pdf::PDFiumDocument> mpPdfDocument;
+
+ std::unique_ptr<SearchContext> mpSearchContext;
+
+ Implementation()
+ : mpPDFium(vcl::pdf::PDFiumLibrary::get())
+ {
+ }
+
+ ~Implementation() { mpSearchContext.reset(); }
+};
+
+VectorGraphicSearch::VectorGraphicSearch(Graphic const& rGraphic)
+ : mpImplementation(std::make_unique<VectorGraphicSearch::Implementation>())
+ , maGraphic(rGraphic)
+{
+}
+
+VectorGraphicSearch::~VectorGraphicSearch() { mpImplementation.reset(); }
+
+bool VectorGraphicSearch::search(OUString const& rSearchString,
+ VectorGraphicSearchOptions const& rOptions)
+{
+ if (!mpImplementation->mpPDFium)
+ {
+ return false;
+ }
+
+ if (!mpImplementation->mpSearchContext)
+ {
+ auto pData = maGraphic.getVectorGraphicData();
+
+ if (pData && pData->getType() == VectorGraphicDataType::Pdf)
+ {
+ if (searchPDF(pData))
+ {
+ return mpImplementation->mpSearchContext->initialize(rSearchString, rOptions);
+ }
+ }
+ return false;
+ }
+ return mpImplementation->mpSearchContext->initialize(rSearchString, rOptions);
+}
+
+bool VectorGraphicSearch::searchPDF(std::shared_ptr<VectorGraphicData> const& rData)
+{
+ if (!mpImplementation->mpPDFium)
+ {
+ return false;
+ }
+
+ mpImplementation->mpPdfDocument = mpImplementation->mpPDFium->openDocument(
+ rData->getBinaryDataContainer().getData(), rData->getBinaryDataContainer().getSize(),
+ OString());
+
+ if (!mpImplementation->mpPdfDocument)
+ {
+ //TODO: Handle failure to load.
+ switch (mpImplementation->mpPDFium->getLastErrorCode())
+ {
+ case vcl::pdf::PDFErrorType::Success:
+ break;
+ case vcl::pdf::PDFErrorType::Unknown:
+ break;
+ case vcl::pdf::PDFErrorType::File:
+ break;
+ case vcl::pdf::PDFErrorType::Format:
+ break;
+ case vcl::pdf::PDFErrorType::Password:
+ break;
+ case vcl::pdf::PDFErrorType::Security:
+ break;
+ case vcl::pdf::PDFErrorType::Page:
+ break;
+ default:
+ break;
+ }
+ return false;
+ }
+
+ sal_Int32 nPageIndex = std::max(rData->getPageIndex(), sal_Int32(0));
+
+ mpImplementation->mpSearchContext.reset(
+ new SearchContext(mpImplementation->mpPdfDocument, nPageIndex));
+ return true;
+}
+
+basegfx::B2DSize VectorGraphicSearch::pageSize()
+{
+ basegfx::B2DSize aSize;
+ if (mpImplementation->mpSearchContext)
+ aSize = mpImplementation->mpSearchContext->getPageSize();
+ return aSize;
+}
+
+bool VectorGraphicSearch::next()
+{
+ if (mpImplementation->mpSearchContext)
+ return mpImplementation->mpSearchContext->next();
+ return false;
+}
+
+bool VectorGraphicSearch::previous()
+{
+ if (mpImplementation->mpSearchContext)
+ return mpImplementation->mpSearchContext->previous();
+ return false;
+}
+
+int VectorGraphicSearch::index()
+{
+ if (mpImplementation->mpSearchContext)
+ return mpImplementation->mpSearchContext->index();
+ return -1;
+}
+
+std::vector<basegfx::B2DRectangle> VectorGraphicSearch::getTextRectangles()
+{
+ if (mpImplementation->mpSearchContext)
+ return mpImplementation->mpSearchContext->getTextRectangles();
+
+ return std::vector<basegfx::B2DRectangle>();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/canvasbitmap.cxx b/vcl/source/helper/canvasbitmap.cxx
new file mode 100644
index 000000000..8807ce7a1
--- /dev/null
+++ b/vcl/source/helper/canvasbitmap.cxx
@@ -0,0 +1,1348 @@
+/* -*- 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 <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+#include <com/sun/star/util/Endianness.hpp>
+#include <com/sun/star/rendering/ColorComponentTag.hpp>
+#include <com/sun/star/rendering/ColorSpaceType.hpp>
+#include <com/sun/star/rendering/RenderingIntent.hpp>
+
+#include <tools/diagnose_ex.h>
+#include <canvasbitmap.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/svapp.hxx>
+
+#include <algorithm>
+
+using namespace vcl::unotools;
+using namespace ::com::sun::star;
+
+namespace
+{
+ // TODO(Q3): move to o3tl bithacks or somesuch. A similar method is in canvas/canvastools.hxx
+
+ // Good ole HAKMEM tradition. Calc number of 1 bits in 32bit word,
+ // unrolled loop. See e.g. Hackers Delight, p. 66
+ sal_Int32 bitcount( sal_uInt32 val )
+ {
+ val = val - ((val >> 1) & 0x55555555);
+ val = (val & 0x33333333) + ((val >> 2) & 0x33333333);
+ val = (val + (val >> 4)) & 0x0F0F0F0F;
+ val = val + (val >> 8);
+ val = val + (val >> 16);
+ return sal_Int32(val & 0x0000003F);
+ }
+}
+
+void VclCanvasBitmap::setComponentInfo( sal_uInt32 redShift, sal_uInt32 greenShift, sal_uInt32 blueShift )
+{
+ // sort channels in increasing order of appearance in the pixel
+ // (starting with the least significant bits)
+ sal_Int8 redPos(0);
+ sal_Int8 greenPos(1);
+ sal_Int8 bluePos(2);
+
+ if( redShift > greenShift )
+ {
+ std::swap(redPos,greenPos);
+ if( redShift > blueShift )
+ {
+ std::swap(redPos,bluePos);
+ if( greenShift > blueShift )
+ std::swap(greenPos,bluePos);
+ }
+ }
+ else
+ {
+ if( greenShift > blueShift )
+ {
+ std::swap(greenPos,bluePos);
+ if( redShift > blueShift )
+ std::swap(redPos,bluePos);
+ }
+ }
+
+ m_aComponentTags.realloc(3);
+ sal_Int8* pTags = m_aComponentTags.getArray();
+ pTags[redPos] = rendering::ColorComponentTag::RGB_RED;
+ pTags[greenPos] = rendering::ColorComponentTag::RGB_GREEN;
+ pTags[bluePos] = rendering::ColorComponentTag::RGB_BLUE;
+
+ m_aComponentBitCounts.realloc(3);
+ sal_Int32* pCounts = m_aComponentBitCounts.getArray();
+ pCounts[redPos] = bitcount(redShift);
+ pCounts[greenPos] = bitcount(greenShift);
+ pCounts[bluePos] = bitcount(blueShift);
+}
+
+Bitmap::ScopedReadAccess& VclCanvasBitmap::getBitmapReadAccess()
+{
+ // BitmapReadAccess is more expensive than BitmapInfoAccess,
+ // as the latter requires also pixels, which may need converted
+ // from the system format (and even fetched). Most calls here
+ // need only info access, create read access only on demand.
+ if(!m_pBmpReadAcc)
+ m_pBmpReadAcc.emplace(m_aBitmap);
+ return *m_pBmpReadAcc;
+}
+
+Bitmap::ScopedReadAccess& VclCanvasBitmap::getAlphaReadAccess()
+{
+ if(!m_pAlphaReadAcc)
+ m_pAlphaReadAcc.emplace(m_aAlpha);
+ return *m_pAlphaReadAcc;
+}
+
+VclCanvasBitmap::VclCanvasBitmap( const BitmapEx& rBitmap ) :
+ m_aBmpEx( rBitmap ),
+ m_aBitmap( rBitmap.GetBitmap() ),
+ m_pBmpAcc( m_aBitmap ),
+ m_nBitsPerInputPixel(0),
+ m_nBitsPerOutputPixel(0),
+ m_nRedIndex(-1),
+ m_nGreenIndex(-1),
+ m_nBlueIndex(-1),
+ m_nAlphaIndex(-1),
+ m_nIndexIndex(-1),
+ m_bPalette(false)
+{
+ if( m_aBmpEx.IsAlpha() )
+ {
+ m_aAlpha = m_aBmpEx.GetAlpha().GetBitmap();
+ m_pAlphaAcc = Bitmap::ScopedInfoAccess(m_aAlpha);
+ }
+
+ m_aLayout.ScanLines = 0;
+ m_aLayout.ScanLineBytes = 0;
+ m_aLayout.ScanLineStride = 0;
+ m_aLayout.PlaneStride = 0;
+ m_aLayout.ColorSpace.clear();
+ m_aLayout.Palette.clear();
+ m_aLayout.IsMsbFirst = false;
+
+ if( !m_pBmpAcc )
+ return;
+
+ m_aLayout.ScanLines = m_pBmpAcc->Height();
+ m_aLayout.ScanLineBytes = (m_pBmpAcc->GetBitCount()*m_pBmpAcc->Width() + 7) / 8;
+ m_aLayout.ScanLineStride = m_pBmpAcc->GetScanlineSize();
+ m_aLayout.PlaneStride = 0;
+
+ switch( m_pBmpAcc->GetScanlineFormat() )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ m_bPalette = true;
+ m_nBitsPerInputPixel = 1;
+ m_aLayout.IsMsbFirst = true;
+ break;
+
+ case ScanlineFormat::N1BitLsbPal:
+ m_bPalette = true;
+ m_nBitsPerInputPixel = 1;
+ m_aLayout.IsMsbFirst = false;
+ break;
+
+ case ScanlineFormat::N8BitPal:
+ m_bPalette = true;
+ m_nBitsPerInputPixel = 8;
+ m_aLayout.IsMsbFirst = false; // doesn't matter
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ m_bPalette = false;
+ m_nBitsPerInputPixel = 24;
+ m_aLayout.IsMsbFirst = false; // doesn't matter
+ setComponentInfo( static_cast<sal_uInt32>(0xff0000UL),
+ static_cast<sal_uInt32>(0x00ff00UL),
+ static_cast<sal_uInt32>(0x0000ffUL) );
+ break;
+
+ case ScanlineFormat::N24BitTcRgb:
+ m_bPalette = false;
+ m_nBitsPerInputPixel = 24;
+ m_aLayout.IsMsbFirst = false; // doesn't matter
+ setComponentInfo( static_cast<sal_uInt32>(0x0000ffUL),
+ static_cast<sal_uInt32>(0x00ff00UL),
+ static_cast<sal_uInt32>(0xff0000UL) );
+ break;
+
+ case ScanlineFormat::N32BitTcAbgr:
+ {
+ m_bPalette = false;
+ m_nBitsPerInputPixel = 32;
+ m_aLayout.IsMsbFirst = false; // doesn't matter
+
+ m_aComponentTags = { /* 0 */ rendering::ColorComponentTag::ALPHA,
+ /* 1 */ rendering::ColorComponentTag::RGB_BLUE,
+ /* 2 */ rendering::ColorComponentTag::RGB_GREEN,
+ /* 3 */ rendering::ColorComponentTag::RGB_RED };
+
+ m_aComponentBitCounts = { /* 0 */ 8,
+ /* 1 */ 8,
+ /* 2 */ 8,
+ /* 3 */ 8 };
+
+ m_nRedIndex = 3;
+ m_nGreenIndex = 2;
+ m_nBlueIndex = 1;
+ m_nAlphaIndex = 0;
+ }
+ break;
+
+ case ScanlineFormat::N32BitTcArgb:
+ {
+ m_bPalette = false;
+ m_nBitsPerInputPixel = 32;
+ m_aLayout.IsMsbFirst = false; // doesn't matter
+
+ m_aComponentTags = { /* 0 */ rendering::ColorComponentTag::ALPHA,
+ /* 1 */ rendering::ColorComponentTag::RGB_RED,
+ /* 2 */ rendering::ColorComponentTag::RGB_GREEN,
+ /* 3 */ rendering::ColorComponentTag::RGB_BLUE };
+
+ m_aComponentBitCounts = { /* 0 */ 8,
+ /* 1 */ 8,
+ /* 2 */ 8,
+ /* 3 */ 8 };
+
+ m_nRedIndex = 1;
+ m_nGreenIndex = 2;
+ m_nBlueIndex = 3;
+ m_nAlphaIndex = 0;
+ }
+ break;
+
+ case ScanlineFormat::N32BitTcBgra:
+ {
+ m_bPalette = false;
+ m_nBitsPerInputPixel = 32;
+ m_aLayout.IsMsbFirst = false; // doesn't matter
+
+ m_aComponentTags = { /* 0 */ rendering::ColorComponentTag::RGB_BLUE,
+ /* 1 */ rendering::ColorComponentTag::RGB_GREEN,
+ /* 2 */ rendering::ColorComponentTag::RGB_RED,
+ /* 3 */ rendering::ColorComponentTag::ALPHA };
+
+ m_aComponentBitCounts = { /* 0 */ 8,
+ /* 1 */ 8,
+ /* 2 */ 8,
+ /* 3 */ 8 };
+
+ m_nRedIndex = 2;
+ m_nGreenIndex = 1;
+ m_nBlueIndex = 0;
+ m_nAlphaIndex = 3;
+ }
+ break;
+
+ case ScanlineFormat::N32BitTcRgba:
+ {
+ m_bPalette = false;
+ m_nBitsPerInputPixel = 32;
+ m_aLayout.IsMsbFirst = false; // doesn't matter
+
+ m_aComponentTags = { /* 0 */ rendering::ColorComponentTag::RGB_RED,
+ /* 1 */ rendering::ColorComponentTag::RGB_GREEN,
+ /* 2 */ rendering::ColorComponentTag::RGB_BLUE,
+ /* 3 */ rendering::ColorComponentTag::ALPHA };
+
+ m_aComponentBitCounts = { /* 0 */ 8,
+ /* 1 */ 8,
+ /* 2 */ 8,
+ /* 3 */ 8 };
+
+ m_nRedIndex = 0;
+ m_nGreenIndex = 1;
+ m_nBlueIndex = 2;
+ m_nAlphaIndex = 3;
+ }
+ break;
+
+ case ScanlineFormat::N32BitTcMask:
+ m_bPalette = false;
+ m_nBitsPerInputPixel = 32;
+ m_aLayout.IsMsbFirst = false; // doesn't matter
+ setComponentInfo( m_pBmpAcc->GetColorMask().GetRedMask(),
+ m_pBmpAcc->GetColorMask().GetGreenMask(),
+ m_pBmpAcc->GetColorMask().GetBlueMask() );
+ break;
+
+ default:
+ OSL_FAIL( "unsupported bitmap format" );
+ break;
+ }
+
+ if( m_bPalette )
+ {
+ m_aComponentTags = { rendering::ColorComponentTag::INDEX };
+
+ m_aComponentBitCounts = { m_nBitsPerInputPixel };
+
+ m_nIndexIndex = 0;
+ }
+
+ m_nBitsPerOutputPixel = m_nBitsPerInputPixel;
+ if( !m_aBmpEx.IsAlpha() )
+ return;
+
+ // TODO(P1): need to interleave alpha with bitmap data -
+ // won't fuss with less-than-8 bit for now
+ m_nBitsPerOutputPixel = std::max(sal_Int32(8),m_nBitsPerInputPixel);
+
+ // check whether alpha goes in front or behind the
+ // bitcount sequence. If pixel format is little endian,
+ // put it behind all the other channels. If it's big
+ // endian, put it in front (because later, the actual data
+ // always gets written after the pixel data)
+
+ // TODO(Q1): slight catch - in the case of the
+ // BMP_FORMAT_32BIT_XX_ARGB formats, duplicate alpha
+ // channels might happen!
+ m_aComponentTags.realloc(m_aComponentTags.getLength()+1);
+ m_aComponentTags.getArray()[m_aComponentTags.getLength()-1] = rendering::ColorComponentTag::ALPHA;
+
+ m_aComponentBitCounts.realloc(m_aComponentBitCounts.getLength()+1);
+ m_aComponentBitCounts.getArray()[m_aComponentBitCounts.getLength()-1] = m_aBmpEx.IsAlpha() ? 8 : 1;
+
+ // always add a full byte to the pixel size, otherwise
+ // pixel packing hell breaks loose.
+ m_nBitsPerOutputPixel += 8;
+
+ // adapt scanline parameters
+ const Size aSize = m_aBitmap.GetSizePixel();
+ m_aLayout.ScanLineBytes =
+ m_aLayout.ScanLineStride = (aSize.Width()*m_nBitsPerOutputPixel + 7)/8;
+}
+
+VclCanvasBitmap::~VclCanvasBitmap()
+{
+}
+
+// XBitmap
+geometry::IntegerSize2D SAL_CALL VclCanvasBitmap::getSize()
+{
+ SolarMutexGuard aGuard;
+ return integerSize2DFromSize( m_aBitmap.GetSizePixel() );
+}
+
+sal_Bool SAL_CALL VclCanvasBitmap::hasAlpha()
+{
+ SolarMutexGuard aGuard;
+ return m_aBmpEx.IsAlpha();
+}
+
+uno::Reference< rendering::XBitmap > SAL_CALL VclCanvasBitmap::getScaledBitmap( const geometry::RealSize2D& newSize,
+ sal_Bool beFast )
+{
+ SolarMutexGuard aGuard;
+
+ BitmapEx aNewBmp( m_aBitmap );
+ aNewBmp.Scale( sizeFromRealSize2D( newSize ), beFast ? BmpScaleFlag::Default : BmpScaleFlag::BestQuality );
+ return uno::Reference<rendering::XBitmap>( new VclCanvasBitmap( aNewBmp ) );
+}
+
+// XIntegerReadOnlyBitmap
+uno::Sequence< sal_Int8 > SAL_CALL VclCanvasBitmap::getData( rendering::IntegerBitmapLayout& bitmapLayout,
+ const geometry::IntegerRectangle2D& rect )
+{
+ SolarMutexGuard aGuard;
+
+ bitmapLayout = getMemoryLayout();
+
+ const ::tools::Rectangle aRequestedArea( vcl::unotools::rectangleFromIntegerRectangle2D(rect) );
+ if( aRequestedArea.IsEmpty() )
+ return uno::Sequence< sal_Int8 >();
+
+ // Invalid/empty bitmap: no data available
+ if( !m_pBmpAcc )
+ throw lang::IndexOutOfBoundsException();
+ if( m_aBmpEx.IsAlpha() && !m_pAlphaAcc )
+ throw lang::IndexOutOfBoundsException();
+
+ if( aRequestedArea.Left() < 0 || aRequestedArea.Top() < 0 ||
+ aRequestedArea.Right() > m_pBmpAcc->Width() ||
+ aRequestedArea.Bottom() > m_pBmpAcc->Height() )
+ {
+ throw lang::IndexOutOfBoundsException();
+ }
+
+ uno::Sequence< sal_Int8 > aRet;
+ tools::Rectangle aRequestedBytes( aRequestedArea );
+
+ // adapt to byte boundaries
+ aRequestedBytes.SetLeft( aRequestedArea.Left()*m_nBitsPerOutputPixel/8 );
+ aRequestedBytes.SetRight( (aRequestedArea.Right()*m_nBitsPerOutputPixel + 7)/8 );
+
+ // copy stuff to output sequence
+ aRet.realloc(aRequestedBytes.getWidth()*aRequestedBytes.getHeight());
+ sal_Int8* pOutBuf = aRet.getArray();
+
+ bitmapLayout.ScanLines = aRequestedBytes.getHeight();
+ bitmapLayout.ScanLineBytes =
+ bitmapLayout.ScanLineStride= aRequestedBytes.getWidth();
+
+ sal_Int32 nScanlineStride=bitmapLayout.ScanLineStride;
+ if( !(m_pBmpAcc->GetScanlineFormat() & ScanlineFormat::TopDown) )
+ {
+ pOutBuf += bitmapLayout.ScanLineStride*(aRequestedBytes.getHeight()-1);
+ nScanlineStride *= -1;
+ }
+
+ if( !m_aBmpEx.IsAlpha() )
+ {
+ Bitmap::ScopedReadAccess& pBmpAcc = getBitmapReadAccess();
+ OSL_ENSURE(pBmpAcc,"Invalid bmp read access");
+
+ // can return bitmap data as-is
+ for( tools::Long y=aRequestedBytes.Top(); y<aRequestedBytes.Bottom(); ++y )
+ {
+ Scanline pScan = pBmpAcc->GetScanline(y);
+ memcpy(pOutBuf, pScan+aRequestedBytes.Left(), aRequestedBytes.getWidth());
+ pOutBuf += nScanlineStride;
+ }
+ }
+ else
+ {
+ Bitmap::ScopedReadAccess& pBmpAcc = getBitmapReadAccess();
+ Bitmap::ScopedReadAccess& pAlphaAcc = getAlphaReadAccess();
+ OSL_ENSURE(pBmpAcc,"Invalid bmp read access");
+ OSL_ENSURE(pAlphaAcc,"Invalid alpha read access");
+
+ // interleave alpha with bitmap data - note, bitcount is
+ // always integer multiple of 8
+ OSL_ENSURE((m_nBitsPerOutputPixel & 0x07) == 0,
+ "Transparent bitmap bitcount not integer multiple of 8" );
+
+ for( tools::Long y=aRequestedArea.Top(); y<aRequestedArea.Bottom(); ++y )
+ {
+ sal_Int8* pOutScan = pOutBuf;
+
+ if( m_nBitsPerInputPixel < 8 )
+ {
+ // input less than a byte - copy via GetPixel()
+ for( tools::Long x=aRequestedArea.Left(); x<aRequestedArea.Right(); ++x )
+ {
+ *pOutScan++ = pBmpAcc->GetPixelIndex(y,x);
+ *pOutScan++ = pAlphaAcc->GetPixelIndex(y,x);
+ }
+ }
+ else
+ {
+ const tools::Long nNonAlphaBytes( m_nBitsPerInputPixel/8 );
+ const tools::Long nScanlineOffsetLeft(aRequestedArea.Left()*nNonAlphaBytes);
+ Scanline pScan = pBmpAcc->GetScanline(y) + nScanlineOffsetLeft;
+ Scanline pScanlineAlpha = pAlphaAcc->GetScanline( y );
+
+ // input integer multiple of byte - copy directly
+ for( tools::Long x=aRequestedArea.Left(); x<aRequestedArea.Right(); ++x )
+ {
+ for( tools::Long i=0; i<nNonAlphaBytes; ++i )
+ *pOutScan++ = *pScan++;
+ *pOutScan++ = pAlphaAcc->GetIndexFromData( pScanlineAlpha, x );
+ }
+ }
+
+ pOutBuf += nScanlineStride;
+ }
+ }
+
+ return aRet;
+}
+
+uno::Sequence< sal_Int8 > SAL_CALL VclCanvasBitmap::getPixel( rendering::IntegerBitmapLayout& bitmapLayout,
+ const geometry::IntegerPoint2D& pos )
+{
+ SolarMutexGuard aGuard;
+
+ bitmapLayout = getMemoryLayout();
+
+ // Invalid/empty bitmap: no data available
+ if( !m_pBmpAcc )
+ throw lang::IndexOutOfBoundsException();
+ if( m_aBmpEx.IsAlpha() && !m_pAlphaAcc )
+ throw lang::IndexOutOfBoundsException();
+
+ if( pos.X < 0 || pos.Y < 0 ||
+ pos.X > m_pBmpAcc->Width() || pos.Y > m_pBmpAcc->Height() )
+ {
+ throw lang::IndexOutOfBoundsException();
+ }
+
+ uno::Sequence< sal_Int8 > aRet((m_nBitsPerOutputPixel + 7)/8);
+ sal_Int8* pOutBuf = aRet.getArray();
+
+ // copy stuff to output sequence
+ bitmapLayout.ScanLines = 1;
+ bitmapLayout.ScanLineBytes =
+ bitmapLayout.ScanLineStride= aRet.getLength();
+
+ const tools::Long nScanlineLeftOffset( pos.X*m_nBitsPerInputPixel/8 );
+ if( !m_aBmpEx.IsAlpha() )
+ {
+ Bitmap::ScopedReadAccess& pBmpAcc = getBitmapReadAccess();
+ assert(pBmpAcc && "Invalid bmp read access");
+
+ // can return bitmap data as-is
+ Scanline pScan = pBmpAcc->GetScanline(pos.Y);
+ memcpy(pOutBuf, pScan+nScanlineLeftOffset, aRet.getLength() );
+ }
+ else
+ {
+ Bitmap::ScopedReadAccess& pBmpAcc = getBitmapReadAccess();
+ Bitmap::ScopedReadAccess& pAlphaAcc = getAlphaReadAccess();
+ assert(pBmpAcc && "Invalid bmp read access");
+ assert(pAlphaAcc && "Invalid alpha read access");
+
+ // interleave alpha with bitmap data - note, bitcount is
+ // always integer multiple of 8
+ assert((m_nBitsPerOutputPixel & 0x07) == 0 &&
+ "Transparent bitmap bitcount not integer multiple of 8" );
+
+ if( m_nBitsPerInputPixel < 8 )
+ {
+ // input less than a byte - copy via GetPixel()
+ *pOutBuf++ = pBmpAcc->GetPixelIndex(pos.Y,pos.X);
+ *pOutBuf = pAlphaAcc->GetPixelIndex(pos.Y,pos.X);
+ }
+ else
+ {
+ const tools::Long nNonAlphaBytes( m_nBitsPerInputPixel/8 );
+ Scanline pScan = pBmpAcc->GetScanline(pos.Y);
+
+ // input integer multiple of byte - copy directly
+ memcpy(pOutBuf, pScan+nScanlineLeftOffset, nNonAlphaBytes );
+ pOutBuf += nNonAlphaBytes;
+ *pOutBuf++ = pAlphaAcc->GetPixelIndex(pos.Y,pos.X);
+ }
+ }
+
+ return aRet;
+}
+
+uno::Reference< rendering::XBitmapPalette > VclCanvasBitmap::getPalette()
+{
+ SolarMutexGuard aGuard;
+
+ uno::Reference< XBitmapPalette > aRet;
+ if( m_bPalette )
+ aRet.set(this);
+
+ return aRet;
+}
+
+rendering::IntegerBitmapLayout SAL_CALL VclCanvasBitmap::getMemoryLayout()
+{
+ SolarMutexGuard aGuard;
+
+ rendering::IntegerBitmapLayout aLayout( m_aLayout );
+
+ // only set references to self on separate copy of
+ // IntegerBitmapLayout - if we'd set that on m_aLayout, we'd have
+ // a circular reference!
+ if( m_bPalette )
+ aLayout.Palette.set( this );
+
+ aLayout.ColorSpace.set( this );
+
+ return aLayout;
+}
+
+sal_Int32 SAL_CALL VclCanvasBitmap::getNumberOfEntries()
+{
+ SolarMutexGuard aGuard;
+
+ if( !m_pBmpAcc )
+ return 0;
+
+ return m_pBmpAcc->HasPalette() ? m_pBmpAcc->GetPaletteEntryCount() : 0 ;
+}
+
+sal_Bool SAL_CALL VclCanvasBitmap::getIndex( uno::Sequence< double >& o_entry, sal_Int32 nIndex )
+{
+ SolarMutexGuard aGuard;
+
+ const sal_uInt16 nCount( m_pBmpAcc ?
+ (m_pBmpAcc->HasPalette() ? m_pBmpAcc->GetPaletteEntryCount() : 0 ) : 0 );
+ OSL_ENSURE(nIndex >= 0 && nIndex < nCount,"Palette index out of range");
+ if( nIndex < 0 || nIndex >= nCount )
+ throw lang::IndexOutOfBoundsException("Palette index out of range",
+ static_cast<rendering::XBitmapPalette*>(this));
+
+ const BitmapColor aCol = m_pBmpAcc->GetPaletteColor(sal::static_int_cast<sal_uInt16>(nIndex));
+ o_entry.realloc(3);
+ double* pColor=o_entry.getArray();
+ pColor[0] = aCol.GetRed();
+ pColor[1] = aCol.GetGreen();
+ pColor[2] = aCol.GetBlue();
+
+ return true; // no palette transparency here.
+}
+
+sal_Bool SAL_CALL VclCanvasBitmap::setIndex( const uno::Sequence< double >&, sal_Bool, sal_Int32 nIndex )
+{
+ SolarMutexGuard aGuard;
+
+ const sal_uInt16 nCount( m_pBmpAcc ?
+ (m_pBmpAcc->HasPalette() ? m_pBmpAcc->GetPaletteEntryCount() : 0 ) : 0 );
+
+ OSL_ENSURE(nIndex >= 0 && nIndex < nCount,"Palette index out of range");
+ if( nIndex < 0 || nIndex >= nCount )
+ throw lang::IndexOutOfBoundsException("Palette index out of range",
+ static_cast<rendering::XBitmapPalette*>(this));
+
+ return false; // read-only implementation
+}
+
+uno::Reference< rendering::XColorSpace > SAL_CALL VclCanvasBitmap::getColorSpace( )
+{
+ // this is the method from XBitmapPalette. Return palette color
+ // space here
+ static uno::Reference<rendering::XColorSpace> gColorSpace = vcl::unotools::createStandardColorSpace();
+ return gColorSpace;
+}
+
+sal_Int8 SAL_CALL VclCanvasBitmap::getType( )
+{
+ return rendering::ColorSpaceType::RGB;
+}
+
+uno::Sequence< ::sal_Int8 > SAL_CALL VclCanvasBitmap::getComponentTags( )
+{
+ SolarMutexGuard aGuard;
+ return m_aComponentTags;
+}
+
+sal_Int8 SAL_CALL VclCanvasBitmap::getRenderingIntent( )
+{
+ return rendering::RenderingIntent::PERCEPTUAL;
+}
+
+uno::Sequence< ::beans::PropertyValue > SAL_CALL VclCanvasBitmap::getProperties( )
+{
+ return uno::Sequence< ::beans::PropertyValue >();
+}
+
+uno::Sequence< double > SAL_CALL VclCanvasBitmap::convertColorSpace( const uno::Sequence< double >& deviceColor,
+ const uno::Reference< ::rendering::XColorSpace >& targetColorSpace )
+{
+ // TODO(P3): if we know anything about target
+ // colorspace, this can be greatly sped up
+ uno::Sequence<rendering::ARGBColor> aIntermediate(
+ convertToARGB(deviceColor));
+ return targetColorSpace->convertFromARGB(aIntermediate);
+}
+
+uno::Sequence<rendering::RGBColor> SAL_CALL VclCanvasBitmap::convertToRGB( const uno::Sequence< double >& deviceColor )
+{
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( deviceColor.getLength() );
+ const sal_Int32 nComponentsPerPixel(m_aComponentTags.getLength());
+ ENSURE_ARG_OR_THROW2(nLen%nComponentsPerPixel==0,
+ "number of channels no multiple of pixel element count",
+ static_cast<rendering::XBitmapPalette*>(this), 01);
+
+ uno::Sequence< rendering::RGBColor > aRes(nLen/nComponentsPerPixel);
+ rendering::RGBColor* pOut( aRes.getArray() );
+
+ if( m_bPalette )
+ {
+ OSL_ENSURE(m_nIndexIndex != -1,
+ "Invalid color channel indices");
+ ENSURE_OR_THROW(m_pBmpAcc,
+ "Unable to get BitmapAccess");
+
+ for( std::size_t i=0; i<nLen; i+=nComponentsPerPixel )
+ {
+ const BitmapColor aCol = m_pBmpAcc->GetPaletteColor(
+ sal::static_int_cast<sal_uInt16>(deviceColor[i+m_nIndexIndex]));
+
+ // TODO(F3): Convert result to sRGB color space
+ *pOut++ = rendering::RGBColor(toDoubleColor(aCol.GetRed()),
+ toDoubleColor(aCol.GetGreen()),
+ toDoubleColor(aCol.GetBlue()));
+ }
+ }
+ else
+ {
+ OSL_ENSURE(m_nRedIndex != -1 && m_nGreenIndex != -1 && m_nBlueIndex != -1,
+ "Invalid color channel indices");
+
+ for( std::size_t i=0; i<nLen; i+=nComponentsPerPixel )
+ {
+ // TODO(F3): Convert result to sRGB color space
+ *pOut++ = rendering::RGBColor(
+ deviceColor[i+m_nRedIndex],
+ deviceColor[i+m_nGreenIndex],
+ deviceColor[i+m_nBlueIndex]);
+ }
+ }
+
+ return aRes;
+}
+
+uno::Sequence<rendering::ARGBColor> SAL_CALL VclCanvasBitmap::convertToARGB( const uno::Sequence< double >& deviceColor )
+{
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( deviceColor.getLength() );
+ const sal_Int32 nComponentsPerPixel(m_aComponentTags.getLength());
+ ENSURE_ARG_OR_THROW2(nLen%nComponentsPerPixel==0,
+ "number of channels no multiple of pixel element count",
+ static_cast<rendering::XBitmapPalette*>(this), 01);
+
+ uno::Sequence< rendering::ARGBColor > aRes(nLen/nComponentsPerPixel);
+ rendering::ARGBColor* pOut( aRes.getArray() );
+
+ if( m_bPalette )
+ {
+ OSL_ENSURE(m_nIndexIndex != -1,
+ "Invalid color channel indices");
+ ENSURE_OR_THROW(m_pBmpAcc,
+ "Unable to get BitmapAccess");
+
+ for( std::size_t i=0; i<nLen; i+=nComponentsPerPixel )
+ {
+ const BitmapColor aCol = m_pBmpAcc->GetPaletteColor(
+ sal::static_int_cast<sal_uInt16>(deviceColor[i+m_nIndexIndex]));
+
+ // TODO(F3): Convert result to sRGB color space
+ const double nAlpha( m_nAlphaIndex != -1 ? 1.0 - deviceColor[i+m_nAlphaIndex] : 1.0 );
+ *pOut++ = rendering::ARGBColor(nAlpha,
+ toDoubleColor(aCol.GetRed()),
+ toDoubleColor(aCol.GetGreen()),
+ toDoubleColor(aCol.GetBlue()));
+ }
+ }
+ else
+ {
+ OSL_ENSURE(m_nRedIndex != -1 && m_nGreenIndex != -1 && m_nBlueIndex != -1,
+ "Invalid color channel indices");
+
+ for( std::size_t i=0; i<nLen; i+=nComponentsPerPixel )
+ {
+ // TODO(F3): Convert result to sRGB color space
+ const double nAlpha( m_nAlphaIndex != -1 ? 1.0 - deviceColor[i+m_nAlphaIndex] : 1.0 );
+ *pOut++ = rendering::ARGBColor(
+ nAlpha,
+ deviceColor[i+m_nRedIndex],
+ deviceColor[i+m_nGreenIndex],
+ deviceColor[i+m_nBlueIndex]);
+ }
+ }
+
+ return aRes;
+}
+
+uno::Sequence<rendering::ARGBColor> SAL_CALL VclCanvasBitmap::convertToPARGB( const uno::Sequence< double >& deviceColor )
+{
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( deviceColor.getLength() );
+ const sal_Int32 nComponentsPerPixel(m_aComponentTags.getLength());
+ ENSURE_ARG_OR_THROW2(nLen%nComponentsPerPixel==0,
+ "number of channels no multiple of pixel element count",
+ static_cast<rendering::XBitmapPalette*>(this), 01);
+
+ uno::Sequence< rendering::ARGBColor > aRes(nLen/nComponentsPerPixel);
+ rendering::ARGBColor* pOut( aRes.getArray() );
+
+ if( m_bPalette )
+ {
+ OSL_ENSURE(m_nIndexIndex != -1,
+ "Invalid color channel indices");
+ ENSURE_OR_THROW(m_pBmpAcc,
+ "Unable to get BitmapAccess");
+
+ for( std::size_t i=0; i<nLen; i+=nComponentsPerPixel )
+ {
+ const BitmapColor aCol = m_pBmpAcc->GetPaletteColor(
+ sal::static_int_cast<sal_uInt16>(deviceColor[i+m_nIndexIndex]));
+
+ // TODO(F3): Convert result to sRGB color space
+ const double nAlpha( m_nAlphaIndex != -1 ? 1.0 - deviceColor[i+m_nAlphaIndex] : 1.0 );
+ *pOut++ = rendering::ARGBColor(nAlpha,
+ nAlpha*toDoubleColor(aCol.GetRed()),
+ nAlpha*toDoubleColor(aCol.GetGreen()),
+ nAlpha*toDoubleColor(aCol.GetBlue()));
+ }
+ }
+ else
+ {
+ OSL_ENSURE(m_nRedIndex != -1 && m_nGreenIndex != -1 && m_nBlueIndex != -1,
+ "Invalid color channel indices");
+
+ for( std::size_t i=0; i<nLen; i+=nComponentsPerPixel )
+ {
+ // TODO(F3): Convert result to sRGB color space
+ const double nAlpha( m_nAlphaIndex != -1 ? 1.0 - deviceColor[i+m_nAlphaIndex] : 1.0 );
+ *pOut++ = rendering::ARGBColor(
+ nAlpha,
+ nAlpha*deviceColor[i+m_nRedIndex],
+ nAlpha*deviceColor[i+m_nGreenIndex],
+ nAlpha*deviceColor[i+m_nBlueIndex]);
+ }
+ }
+
+ return aRes;
+}
+
+uno::Sequence< double > SAL_CALL VclCanvasBitmap::convertFromRGB( const uno::Sequence<rendering::RGBColor>& rgbColor )
+{
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( rgbColor.getLength() );
+ const sal_Int32 nComponentsPerPixel(m_aComponentTags.getLength());
+
+ uno::Sequence< double > aRes(nLen*nComponentsPerPixel);
+ double* pColors=aRes.getArray();
+
+ if( m_bPalette )
+ {
+ for( const auto& rIn : rgbColor )
+ {
+ pColors[m_nIndexIndex] = m_pBmpAcc->GetBestPaletteIndex(
+ BitmapColor(toByteColor(rIn.Red),
+ toByteColor(rIn.Green),
+ toByteColor(rIn.Blue)));
+ if( m_nAlphaIndex != -1 )
+ pColors[m_nAlphaIndex] = 1.0;
+
+ pColors += nComponentsPerPixel;
+ }
+ }
+ else
+ {
+ for( const auto& rIn : rgbColor )
+ {
+ pColors[m_nRedIndex] = rIn.Red;
+ pColors[m_nGreenIndex] = rIn.Green;
+ pColors[m_nBlueIndex] = rIn.Blue;
+ if( m_nAlphaIndex != -1 )
+ pColors[m_nAlphaIndex] = 1.0;
+
+ pColors += nComponentsPerPixel;
+ }
+ }
+ return aRes;
+}
+
+uno::Sequence< double > SAL_CALL VclCanvasBitmap::convertFromARGB( const uno::Sequence<rendering::ARGBColor>& rgbColor )
+{
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( rgbColor.getLength() );
+ const sal_Int32 nComponentsPerPixel(m_aComponentTags.getLength());
+
+ uno::Sequence< double > aRes(nLen*nComponentsPerPixel);
+ double* pColors=aRes.getArray();
+
+ if( m_bPalette )
+ {
+ for( const auto& rIn : rgbColor )
+ {
+ pColors[m_nIndexIndex] = m_pBmpAcc->GetBestPaletteIndex(
+ BitmapColor(toByteColor(rIn.Red),
+ toByteColor(rIn.Green),
+ toByteColor(rIn.Blue)));
+ if( m_nAlphaIndex != -1 )
+ pColors[m_nAlphaIndex] = rIn.Alpha;
+
+ pColors += nComponentsPerPixel;
+ }
+ }
+ else
+ {
+ for( const auto& rIn : rgbColor )
+ {
+ pColors[m_nRedIndex] = rIn.Red;
+ pColors[m_nGreenIndex] = rIn.Green;
+ pColors[m_nBlueIndex] = rIn.Blue;
+ if( m_nAlphaIndex != -1 )
+ pColors[m_nAlphaIndex] = rIn.Alpha;
+
+ pColors += nComponentsPerPixel;
+ }
+ }
+ return aRes;
+}
+
+uno::Sequence< double > SAL_CALL VclCanvasBitmap::convertFromPARGB( const uno::Sequence<rendering::ARGBColor>& rgbColor )
+{
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( rgbColor.getLength() );
+ const sal_Int32 nComponentsPerPixel(m_aComponentTags.getLength());
+
+ uno::Sequence< double > aRes(nLen*nComponentsPerPixel);
+ double* pColors=aRes.getArray();
+
+ if( m_bPalette )
+ {
+ for( const auto& rIn : rgbColor )
+ {
+ const double nAlpha( rIn.Alpha );
+ pColors[m_nIndexIndex] = m_pBmpAcc->GetBestPaletteIndex(
+ BitmapColor(toByteColor(rIn.Red / nAlpha),
+ toByteColor(rIn.Green / nAlpha),
+ toByteColor(rIn.Blue / nAlpha)));
+ if( m_nAlphaIndex != -1 )
+ pColors[m_nAlphaIndex] = nAlpha;
+
+ pColors += nComponentsPerPixel;
+ }
+ }
+ else
+ {
+ for( const auto& rIn : rgbColor )
+ {
+ const double nAlpha( rIn.Alpha );
+ pColors[m_nRedIndex] = rIn.Red / nAlpha;
+ pColors[m_nGreenIndex] = rIn.Green / nAlpha;
+ pColors[m_nBlueIndex] = rIn.Blue / nAlpha;
+ if( m_nAlphaIndex != -1 )
+ pColors[m_nAlphaIndex] = nAlpha;
+
+ pColors += nComponentsPerPixel;
+ }
+ }
+ return aRes;
+}
+
+sal_Int32 SAL_CALL VclCanvasBitmap::getBitsPerPixel( )
+{
+ SolarMutexGuard aGuard;
+ return m_nBitsPerOutputPixel;
+}
+
+uno::Sequence< ::sal_Int32 > SAL_CALL VclCanvasBitmap::getComponentBitCounts( )
+{
+ SolarMutexGuard aGuard;
+ return m_aComponentBitCounts;
+}
+
+sal_Int8 SAL_CALL VclCanvasBitmap::getEndianness( )
+{
+ return util::Endianness::LITTLE;
+}
+
+uno::Sequence<double> SAL_CALL VclCanvasBitmap::convertFromIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor,
+ const uno::Reference< ::rendering::XColorSpace >& targetColorSpace )
+{
+ if( dynamic_cast<VclCanvasBitmap*>(targetColorSpace.get()) )
+ {
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( deviceColor.getLength() );
+ const sal_Int32 nComponentsPerPixel(m_aComponentTags.getLength());
+ ENSURE_ARG_OR_THROW2(nLen%nComponentsPerPixel==0,
+ "number of channels no multiple of pixel element count",
+ static_cast<rendering::XBitmapPalette*>(this), 01);
+
+ uno::Sequence<double> aRes(nLen);
+ double* pOut( aRes.getArray() );
+
+ if( m_bPalette )
+ {
+ OSL_ENSURE(m_nIndexIndex != -1,
+ "Invalid color channel indices");
+ ENSURE_OR_THROW(m_pBmpAcc,
+ "Unable to get BitmapAccess");
+
+ for( std::size_t i=0; i<nLen; i+=nComponentsPerPixel )
+ {
+ const BitmapColor aCol = m_pBmpAcc->GetPaletteColor(
+ sal::static_int_cast<sal_uInt16>(deviceColor[i+m_nIndexIndex]));
+
+ // TODO(F3): Convert result to sRGB color space
+ const double nAlpha( m_nAlphaIndex != -1 ? 1.0 - deviceColor[i+m_nAlphaIndex] : 1.0 );
+ *pOut++ = toDoubleColor(aCol.GetRed());
+ *pOut++ = toDoubleColor(aCol.GetGreen());
+ *pOut++ = toDoubleColor(aCol.GetBlue());
+ *pOut++ = nAlpha;
+ }
+ }
+ else
+ {
+ OSL_ENSURE(m_nRedIndex != -1 && m_nGreenIndex != -1 && m_nBlueIndex != -1,
+ "Invalid color channel indices");
+
+ for( std::size_t i=0; i<nLen; i+=nComponentsPerPixel )
+ {
+ // TODO(F3): Convert result to sRGB color space
+ const double nAlpha( m_nAlphaIndex != -1 ? 1.0 - deviceColor[i+m_nAlphaIndex] : 1.0 );
+ *pOut++ = deviceColor[i+m_nRedIndex];
+ *pOut++ = deviceColor[i+m_nGreenIndex];
+ *pOut++ = deviceColor[i+m_nBlueIndex];
+ *pOut++ = nAlpha;
+ }
+ }
+
+ return aRes;
+ }
+ else
+ {
+ // TODO(P3): if we know anything about target
+ // colorspace, this can be greatly sped up
+ uno::Sequence<rendering::ARGBColor> aIntermediate(
+ convertIntegerToARGB(deviceColor));
+ return targetColorSpace->convertFromARGB(aIntermediate);
+ }
+}
+
+uno::Sequence< ::sal_Int8 > SAL_CALL VclCanvasBitmap::convertToIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor,
+ const uno::Reference< ::rendering::XIntegerBitmapColorSpace >& targetColorSpace )
+{
+ if( dynamic_cast<VclCanvasBitmap*>(targetColorSpace.get()) )
+ {
+ // it's us, so simply pass-through the data
+ return deviceColor;
+ }
+ else
+ {
+ // TODO(P3): if we know anything about target
+ // colorspace, this can be greatly sped up
+ uno::Sequence<rendering::ARGBColor> aIntermediate(
+ convertIntegerToARGB(deviceColor));
+ return targetColorSpace->convertIntegerFromARGB(aIntermediate);
+ }
+}
+
+uno::Sequence<rendering::RGBColor> SAL_CALL VclCanvasBitmap::convertIntegerToRGB( const uno::Sequence< ::sal_Int8 >& deviceColor )
+{
+ SolarMutexGuard aGuard;
+
+ const sal_uInt8* pIn( reinterpret_cast<const sal_uInt8*>(deviceColor.getConstArray()) );
+ const std::size_t nLen( deviceColor.getLength() );
+ const sal_Int32 nNumColors((nLen*8 + m_nBitsPerOutputPixel-1)/m_nBitsPerOutputPixel);
+
+ uno::Sequence< rendering::RGBColor > aRes(nNumColors);
+ rendering::RGBColor* pOut( aRes.getArray() );
+
+ Bitmap::ScopedReadAccess& pBmpAcc = getBitmapReadAccess();
+ ENSURE_OR_THROW(pBmpAcc,
+ "Unable to get BitmapAccess");
+
+ if( m_aBmpEx.IsAlpha() )
+ {
+ const sal_Int32 nBytesPerPixel((m_nBitsPerOutputPixel+7)/8);
+ for( std::size_t i=0; i<nLen; i+=nBytesPerPixel )
+ {
+ // if palette, index is guaranteed to be 8 bit
+ const BitmapColor aCol =
+ m_bPalette ?
+ pBmpAcc->GetPaletteColor(*pIn) :
+ pBmpAcc->GetPixelFromData(pIn,0);
+
+ // TODO(F3): Convert result to sRGB color space
+ *pOut++ = rendering::RGBColor(toDoubleColor(aCol.GetRed()),
+ toDoubleColor(aCol.GetGreen()),
+ toDoubleColor(aCol.GetBlue()));
+ // skips alpha
+ pIn += nBytesPerPixel;
+ }
+ }
+ else
+ {
+ for( sal_Int32 i=0; i<nNumColors; ++i )
+ {
+ const BitmapColor aCol =
+ m_bPalette ?
+ pBmpAcc->GetPaletteColor( pBmpAcc->GetPixelFromData( pIn, i ).GetIndex()) :
+ pBmpAcc->GetPixelFromData(pIn, i);
+
+ // TODO(F3): Convert result to sRGB color space
+ *pOut++ = rendering::RGBColor(toDoubleColor(aCol.GetRed()),
+ toDoubleColor(aCol.GetGreen()),
+ toDoubleColor(aCol.GetBlue()));
+ }
+ }
+
+ return aRes;
+}
+
+uno::Sequence<rendering::ARGBColor> SAL_CALL VclCanvasBitmap::convertIntegerToARGB( const uno::Sequence< ::sal_Int8 >& deviceColor )
+{
+ SolarMutexGuard aGuard;
+
+ const sal_uInt8* pIn( reinterpret_cast<const sal_uInt8*>(deviceColor.getConstArray()) );
+ const std::size_t nLen( deviceColor.getLength() );
+ const sal_Int32 nNumColors((nLen*8 + m_nBitsPerOutputPixel-1)/m_nBitsPerOutputPixel);
+
+ uno::Sequence< rendering::ARGBColor > aRes(nNumColors);
+ rendering::ARGBColor* pOut( aRes.getArray() );
+
+ Bitmap::ScopedReadAccess& pBmpAcc = getBitmapReadAccess();
+ ENSURE_OR_THROW(pBmpAcc,
+ "Unable to get BitmapAccess");
+
+ if( m_aBmpEx.IsAlpha() )
+ {
+ const tools::Long nNonAlphaBytes( (m_nBitsPerInputPixel+7)/8 );
+ const sal_Int32 nBytesPerPixel((m_nBitsPerOutputPixel+7)/8);
+ const sal_uInt8 nAlphaFactor( m_aBmpEx.IsAlpha() ? 1 : 255 );
+ for( std::size_t i=0; i<nLen; i+=nBytesPerPixel )
+ {
+ // if palette, index is guaranteed to be 8 bit
+ const BitmapColor aCol =
+ m_bPalette ?
+ pBmpAcc->GetPaletteColor(*pIn) :
+ pBmpAcc->GetPixelFromData(pIn,0);
+
+ // TODO(F3): Convert result to sRGB color space
+ *pOut++ = rendering::ARGBColor(1.0 - toDoubleColor(nAlphaFactor*pIn[nNonAlphaBytes]),
+ toDoubleColor(aCol.GetRed()),
+ toDoubleColor(aCol.GetGreen()),
+ toDoubleColor(aCol.GetBlue()));
+ pIn += nBytesPerPixel;
+ }
+ }
+ else
+ {
+ for( sal_Int32 i=0; i<nNumColors; ++i )
+ {
+ const BitmapColor aCol =
+ m_bPalette ?
+ pBmpAcc->GetPaletteColor( pBmpAcc->GetPixelFromData( pIn, i ).GetIndex() ) :
+ pBmpAcc->GetPixelFromData(pIn, i);
+
+ // TODO(F3): Convert result to sRGB color space
+ *pOut++ = rendering::ARGBColor(1.0,
+ toDoubleColor(aCol.GetRed()),
+ toDoubleColor(aCol.GetGreen()),
+ toDoubleColor(aCol.GetBlue()));
+ }
+ }
+
+ return aRes;
+}
+
+uno::Sequence<rendering::ARGBColor> SAL_CALL VclCanvasBitmap::convertIntegerToPARGB( const uno::Sequence< ::sal_Int8 >& deviceColor )
+{
+ SolarMutexGuard aGuard;
+
+ const sal_uInt8* pIn( reinterpret_cast<const sal_uInt8*>(deviceColor.getConstArray()) );
+ const std::size_t nLen( deviceColor.getLength() );
+ const sal_Int32 nNumColors((nLen*8 + m_nBitsPerOutputPixel-1)/m_nBitsPerOutputPixel);
+
+ uno::Sequence< rendering::ARGBColor > aRes(nNumColors);
+ rendering::ARGBColor* pOut( aRes.getArray() );
+
+ Bitmap::ScopedReadAccess& pBmpAcc = getBitmapReadAccess();
+ ENSURE_OR_THROW(pBmpAcc,
+ "Unable to get BitmapAccess");
+
+ if( m_aBmpEx.IsAlpha() )
+ {
+ const tools::Long nNonAlphaBytes( (m_nBitsPerInputPixel+7)/8 );
+ const sal_Int32 nBytesPerPixel((m_nBitsPerOutputPixel+7)/8);
+ const sal_uInt8 nAlphaFactor( m_aBmpEx.IsAlpha() ? 1 : 255 );
+ for( std::size_t i=0; i<nLen; i+=nBytesPerPixel )
+ {
+ // if palette, index is guaranteed to be 8 bit
+ const BitmapColor aCol =
+ m_bPalette ?
+ pBmpAcc->GetPaletteColor(*pIn) :
+ pBmpAcc->GetPixelFromData(pIn,0);
+
+ // TODO(F3): Convert result to sRGB color space
+ const double nAlpha( 1.0 - toDoubleColor(nAlphaFactor*pIn[nNonAlphaBytes]) );
+ *pOut++ = rendering::ARGBColor(nAlpha,
+ nAlpha*toDoubleColor(aCol.GetRed()),
+ nAlpha*toDoubleColor(aCol.GetGreen()),
+ nAlpha*toDoubleColor(aCol.GetBlue()));
+ pIn += nBytesPerPixel;
+ }
+ }
+ else
+ {
+ for( sal_Int32 i=0; i<nNumColors; ++i )
+ {
+ const BitmapColor aCol =
+ m_bPalette ?
+ pBmpAcc->GetPaletteColor( pBmpAcc->GetPixelFromData( pIn, i ).GetIndex() ) :
+ pBmpAcc->GetPixelFromData(pIn, i);
+
+ // TODO(F3): Convert result to sRGB color space
+ *pOut++ = rendering::ARGBColor(1.0,
+ toDoubleColor(aCol.GetRed()),
+ toDoubleColor(aCol.GetGreen()),
+ toDoubleColor(aCol.GetBlue()));
+ }
+ }
+
+ return aRes;
+}
+
+uno::Sequence< ::sal_Int8 > SAL_CALL VclCanvasBitmap::convertIntegerFromRGB( const uno::Sequence<rendering::RGBColor>& rgbColor )
+{
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( rgbColor.getLength() );
+ const sal_Int32 nNumBytes((nLen*m_nBitsPerOutputPixel+7)/8);
+
+ uno::Sequence< sal_Int8 > aRes(nNumBytes);
+ sal_uInt8* pColors=reinterpret_cast<sal_uInt8*>(aRes.getArray());
+ Bitmap::ScopedReadAccess& pBmpAcc = getBitmapReadAccess();
+
+ if( m_aBmpEx.IsAlpha() )
+ {
+ const tools::Long nNonAlphaBytes( (m_nBitsPerInputPixel+7)/8 );
+ for( std::size_t i=0; i<nLen; ++i )
+ {
+ const BitmapColor aCol(toByteColor(rgbColor[i].Red),
+ toByteColor(rgbColor[i].Green),
+ toByteColor(rgbColor[i].Blue));
+ const BitmapColor aCol2 =
+ m_bPalette ?
+ BitmapColor(
+ sal::static_int_cast<sal_uInt8>(pBmpAcc->GetBestPaletteIndex( aCol ))) :
+ aCol;
+
+ pBmpAcc->SetPixelOnData(pColors,i,aCol2);
+ pColors += nNonAlphaBytes;
+ *pColors++ = sal_uInt8(255);
+ }
+ }
+ else
+ {
+ for( std::size_t i=0; i<nLen; ++i )
+ {
+ const BitmapColor aCol(toByteColor(rgbColor[i].Red),
+ toByteColor(rgbColor[i].Green),
+ toByteColor(rgbColor[i].Blue));
+ const BitmapColor aCol2 =
+ m_bPalette ?
+ BitmapColor(
+ sal::static_int_cast<sal_uInt8>(pBmpAcc->GetBestPaletteIndex( aCol ))) :
+ aCol;
+
+ pBmpAcc->SetPixelOnData(pColors,i,aCol2);
+ }
+ }
+
+ return aRes;
+}
+
+uno::Sequence< ::sal_Int8 > SAL_CALL VclCanvasBitmap::convertIntegerFromARGB( const uno::Sequence<rendering::ARGBColor>& rgbColor )
+{
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( rgbColor.getLength() );
+ const sal_Int32 nNumBytes((nLen*m_nBitsPerOutputPixel+7)/8);
+
+ uno::Sequence< sal_Int8 > aRes(nNumBytes);
+ sal_uInt8* pColors=reinterpret_cast<sal_uInt8*>(aRes.getArray());
+ Bitmap::ScopedReadAccess& pBmpAcc = getBitmapReadAccess();
+
+ if( m_aBmpEx.IsAlpha() )
+ {
+ const tools::Long nNonAlphaBytes( (m_nBitsPerInputPixel+7)/8 );
+ for( std::size_t i=0; i<nLen; ++i )
+ {
+ const BitmapColor aCol(toByteColor(rgbColor[i].Red),
+ toByteColor(rgbColor[i].Green),
+ toByteColor(rgbColor[i].Blue));
+ const BitmapColor aCol2 =
+ m_bPalette ?
+ BitmapColor(
+ sal::static_int_cast<sal_uInt8>(pBmpAcc->GetBestPaletteIndex( aCol ))) :
+ aCol;
+
+ pBmpAcc->SetPixelOnData(pColors,i,aCol2);
+ pColors += nNonAlphaBytes;
+ *pColors++ = 255 - toByteColor(rgbColor[i].Alpha);
+ }
+ }
+ else
+ {
+ for( std::size_t i=0; i<nLen; ++i )
+ {
+ const BitmapColor aCol(toByteColor(rgbColor[i].Red),
+ toByteColor(rgbColor[i].Green),
+ toByteColor(rgbColor[i].Blue));
+ const BitmapColor aCol2 =
+ m_bPalette ?
+ BitmapColor(
+ sal::static_int_cast<sal_uInt8>(pBmpAcc->GetBestPaletteIndex( aCol ))) :
+ aCol;
+
+ pBmpAcc->SetPixelOnData(pColors,i,aCol2);
+ }
+ }
+
+ return aRes;
+}
+
+uno::Sequence< ::sal_Int8 > SAL_CALL VclCanvasBitmap::convertIntegerFromPARGB( const uno::Sequence<rendering::ARGBColor>& rgbColor )
+{
+ SolarMutexGuard aGuard;
+
+ const std::size_t nLen( rgbColor.getLength() );
+ const sal_Int32 nNumBytes((nLen*m_nBitsPerOutputPixel+7)/8);
+
+ uno::Sequence< sal_Int8 > aRes(nNumBytes);
+ sal_uInt8* pColors=reinterpret_cast<sal_uInt8*>(aRes.getArray());
+ Bitmap::ScopedReadAccess& pBmpAcc = getBitmapReadAccess();
+
+ if( m_aBmpEx.IsAlpha() )
+ {
+ const tools::Long nNonAlphaBytes( (m_nBitsPerInputPixel+7)/8 );
+ for( std::size_t i=0; i<nLen; ++i )
+ {
+ const double nAlpha( rgbColor[i].Alpha );
+ const BitmapColor aCol(toByteColor(rgbColor[i].Red / nAlpha),
+ toByteColor(rgbColor[i].Green / nAlpha),
+ toByteColor(rgbColor[i].Blue / nAlpha));
+ const BitmapColor aCol2 =
+ m_bPalette ?
+ BitmapColor(
+ sal::static_int_cast<sal_uInt8>(pBmpAcc->GetBestPaletteIndex( aCol ))) :
+ aCol;
+
+ pBmpAcc->SetPixelOnData(pColors,i,aCol2);
+ pColors += nNonAlphaBytes;
+ *pColors++ = 255 - toByteColor(nAlpha);
+ }
+ }
+ else
+ {
+ for( std::size_t i=0; i<nLen; ++i )
+ {
+ const BitmapColor aCol(toByteColor(rgbColor[i].Red),
+ toByteColor(rgbColor[i].Green),
+ toByteColor(rgbColor[i].Blue));
+ const BitmapColor aCol2 =
+ m_bPalette ?
+ BitmapColor(
+ sal::static_int_cast<sal_uInt8>(pBmpAcc->GetBestPaletteIndex( aCol ))) :
+ aCol;
+
+ pBmpAcc->SetPixelOnData(pColors,i,aCol2);
+ }
+ }
+
+ return aRes;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/canvastools.cxx b/vcl/source/helper/canvastools.cxx
new file mode 100644
index 000000000..c547dace4
--- /dev/null
+++ b/vcl/source/helper/canvastools.cxx
@@ -0,0 +1,624 @@
+/* -*- 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 <com/sun/star/geometry/RealSize2D.hpp>
+#include <com/sun/star/geometry/IntegerSize2D.hpp>
+#include <com/sun/star/geometry/IntegerPoint2D.hpp>
+#include <com/sun/star/geometry/IntegerRectangle2D.hpp>
+
+#include <com/sun/star/rendering/ColorSpaceType.hpp>
+#include <com/sun/star/rendering/RenderingIntent.hpp>
+#include <com/sun/star/rendering/VolatileContentDestroyedException.hpp>
+#include <com/sun/star/rendering/XBitmap.hpp>
+#include <com/sun/star/rendering/IntegerBitmapLayout.hpp>
+#include <com/sun/star/rendering/ColorComponentTag.hpp>
+
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/range/b2drectangle.hxx>
+#include <basegfx/point/b2ipoint.hxx>
+#include <basegfx/range/b2irectangle.hxx>
+
+#include <sal/log.hxx>
+#include <tools/helpers.hxx>
+#include <tools/diagnose_ex.h>
+
+#include <vcl/bitmapex.hxx>
+
+#include <canvasbitmap.hxx>
+#include <vcl/canvastools.hxx>
+#include <bitmap/BitmapWriteAccess.hxx>
+
+using namespace ::com::sun::star;
+
+namespace vcl::unotools
+{
+ uno::Reference< rendering::XBitmap > xBitmapFromBitmapEx(const ::BitmapEx& inputBitmap )
+ {
+ SAL_INFO( "vcl.helper", "vcl::unotools::xBitmapFromBitmapEx()" );
+
+ return new vcl::unotools::VclCanvasBitmap( inputBitmap );
+ }
+
+ namespace
+ {
+ bool equalsLayout( const rendering::IntegerBitmapLayout& rLHS,
+ const rendering::IntegerBitmapLayout& rRHS )
+ {
+ return
+ rLHS.ScanLineBytes == rRHS.ScanLineBytes &&
+ rLHS.ScanLineStride == rRHS.ScanLineStride &&
+ rLHS.PlaneStride == rRHS.PlaneStride &&
+ rLHS.ColorSpace == rRHS.ColorSpace &&
+ rLHS.Palette == rRHS.Palette &&
+ rLHS.IsMsbFirst == rRHS.IsMsbFirst;
+ }
+ bool readBmp( sal_Int32 nWidth,
+ sal_Int32 nHeight,
+ const rendering::IntegerBitmapLayout& rLayout,
+ const uno::Reference< rendering::XIntegerReadOnlyBitmap >& xInputBitmap,
+ BitmapScopedWriteAccess& rWriteAcc,
+ BitmapScopedWriteAccess& rAlphaAcc )
+ {
+ rendering::IntegerBitmapLayout aCurrLayout;
+ geometry::IntegerRectangle2D aRect;
+ uno::Sequence<sal_Int8> aPixelData;
+ uno::Sequence<rendering::RGBColor> aRGBColors;
+ uno::Sequence<rendering::ARGBColor> aARGBColors;
+
+ for( aRect.Y1=0; aRect.Y1<nHeight; ++aRect.Y1 )
+ {
+ aRect.X1 = 0; aRect.X2 = nWidth; aRect.Y2 = aRect.Y1+1;
+ try
+ {
+ aPixelData = xInputBitmap->getData(aCurrLayout,aRect);
+ }
+ catch( rendering::VolatileContentDestroyedException& )
+ {
+ // re-read bmp from the start
+ return false;
+ }
+ if( !equalsLayout(aCurrLayout, rLayout) )
+ return false; // re-read bmp from the start
+
+ Scanline pScanline = rWriteAcc->GetScanline( aRect.Y1 );
+ if( rAlphaAcc.get() )
+ {
+ Scanline pScanlineAlpha = rAlphaAcc->GetScanline( aRect.Y1 );
+ // read ARGB color
+ aARGBColors = rLayout.ColorSpace->convertIntegerToARGB(aPixelData);
+
+ if( rWriteAcc->HasPalette() )
+ {
+ for( sal_Int32 x=0; x<nWidth; ++x )
+ {
+ const rendering::ARGBColor& rColor=aARGBColors[x];
+ rWriteAcc->SetPixelOnData( pScanline, x,
+ BitmapColor(static_cast<sal_uInt8>(rWriteAcc->GetBestPaletteIndex(
+ BitmapColor( toByteColor(rColor.Red),
+ toByteColor(rColor.Green),
+ toByteColor(rColor.Blue))))) );
+ rAlphaAcc->SetPixelOnData( pScanlineAlpha, x,
+ BitmapColor( 255 - toByteColor(rColor.Alpha) ));
+ }
+ }
+ else
+ {
+ for( sal_Int32 x=0; x<nWidth; ++x )
+ {
+ const rendering::ARGBColor& rColor=aARGBColors[x];
+ rWriteAcc->SetPixelOnData( pScanline, x,
+ BitmapColor( toByteColor(rColor.Red),
+ toByteColor(rColor.Green),
+ toByteColor(rColor.Blue) ));
+ rAlphaAcc->SetPixelOnData( pScanlineAlpha, x,
+ BitmapColor( 255 - toByteColor(rColor.Alpha) ));
+ }
+ }
+ }
+ else
+ {
+ // read RGB color
+ aRGBColors = rLayout.ColorSpace->convertIntegerToRGB(aPixelData);
+ if( rWriteAcc->HasPalette() )
+ {
+ for( sal_Int32 x=0; x<nWidth; ++x )
+ {
+ const rendering::RGBColor& rColor=aRGBColors[x];
+ rWriteAcc->SetPixelOnData( pScanline, x,
+ BitmapColor(static_cast<sal_uInt8>(rWriteAcc->GetBestPaletteIndex(
+ BitmapColor( toByteColor(rColor.Red),
+ toByteColor(rColor.Green),
+ toByteColor(rColor.Blue))))) );
+ }
+ }
+ else
+ {
+ for( sal_Int32 x=0; x<nWidth; ++x )
+ {
+ const rendering::RGBColor& rColor=aRGBColors[x];
+ rWriteAcc->SetPixelOnData( pScanline, x,
+ BitmapColor( toByteColor(rColor.Red),
+ toByteColor(rColor.Green),
+ toByteColor(rColor.Blue) ));
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+
+ ::BitmapEx bitmapExFromXBitmap( const uno::Reference< rendering::XIntegerReadOnlyBitmap >& xInputBitmap )
+ {
+ SAL_INFO( "vcl.helper", "vcl::unotools::bitmapExFromXBitmap()" );
+
+ if( !xInputBitmap.is() )
+ return ::BitmapEx();
+
+ // tunnel directly for known implementation
+ VclCanvasBitmap* pImplBitmap = dynamic_cast<VclCanvasBitmap*>(xInputBitmap.get());
+ if( pImplBitmap )
+ return pImplBitmap->getBitmapEx();
+
+ // retrieve data via UNO interface
+
+ // volatile bitmaps are a bit more complicated to read
+ // from...
+
+ // loop a few times, until successfully read (for XVolatileBitmap)
+ for( int i=0; i<10; ++i )
+ {
+ sal_Int32 nDepth=0;
+ sal_Int32 nAlphaDepth=0;
+ const rendering::IntegerBitmapLayout aLayout(
+ xInputBitmap->getMemoryLayout());
+
+ OSL_ENSURE(aLayout.ColorSpace.is(),
+ "Cannot convert image without color space!");
+ if( !aLayout.ColorSpace.is() )
+ return ::BitmapEx();
+
+ nDepth = aLayout.ColorSpace->getBitsPerPixel();
+
+ if( xInputBitmap->hasAlpha() )
+ {
+ // determine alpha channel depth
+ const uno::Sequence<sal_Int8> aTags(
+ aLayout.ColorSpace->getComponentTags() );
+ const sal_Int8* pStart(aTags.getConstArray());
+ const std::size_t nLen(aTags.getLength());
+ const sal_Int8* pEnd(pStart+nLen);
+
+ const std::ptrdiff_t nAlphaIndex =
+ std::find(pStart,pEnd,
+ rendering::ColorComponentTag::ALPHA) - pStart;
+
+ if( nAlphaIndex < sal::static_int_cast<std::ptrdiff_t>(nLen) )
+ {
+ nAlphaDepth = aLayout.ColorSpace->getComponentBitCounts()[nAlphaIndex] > 1 ? 8 : 1;
+ nDepth -= nAlphaDepth;
+ }
+ }
+
+ BitmapPalette aPalette;
+ if( aLayout.Palette.is() )
+ {
+ uno::Reference< rendering::XColorSpace > xPaletteColorSpace(
+ aLayout.Palette->getColorSpace());
+ ENSURE_OR_THROW(xPaletteColorSpace.is(),
+ "Palette without color space");
+
+ const sal_Int32 nEntryCount( aLayout.Palette->getNumberOfEntries() );
+ if( nEntryCount <= 256 )
+ {
+ if( nEntryCount <= 2 )
+ nDepth = 1;
+ else
+ nDepth = 8;
+
+ const sal_uInt16 nPaletteEntries(
+ sal::static_int_cast<sal_uInt16>(
+ std::min(sal_Int32(255), nEntryCount)));
+
+ // copy palette entries
+ aPalette.SetEntryCount(nPaletteEntries);
+ uno::Reference<rendering::XBitmapPalette> xPalette( aLayout.Palette );
+ uno::Reference<rendering::XColorSpace> xPalColorSpace( xPalette->getColorSpace() );
+
+ uno::Sequence<double> aPaletteEntry;
+ for( sal_uInt16 j=0; j<nPaletteEntries; ++j )
+ {
+ if( !xPalette->getIndex(aPaletteEntry,j) &&
+ nAlphaDepth == 0 )
+ {
+ nAlphaDepth = 1;
+ }
+ uno::Sequence<rendering::RGBColor> aColors=xPalColorSpace->convertToRGB(aPaletteEntry);
+ ENSURE_OR_THROW(aColors.getLength() == 1,
+ "Palette returned more or less than one entry");
+ const rendering::RGBColor& rColor=aColors[0];
+ aPalette[j] = BitmapColor(toByteColor(rColor.Red),
+ toByteColor(rColor.Green),
+ toByteColor(rColor.Blue));
+ }
+ }
+ }
+
+ const ::Size aPixelSize(
+ sizeFromIntegerSize2D(xInputBitmap->getSize()));
+
+ // normalize bitcount
+ auto ePixelFormat =
+ ( nDepth <= 1 ) ? vcl::PixelFormat::N1_BPP :
+ ( nDepth <= 8 ) ? vcl::PixelFormat::N8_BPP :
+ vcl::PixelFormat::N24_BPP;
+ auto eAlphaPixelFormat =
+ ( nAlphaDepth <= 1 ) ? vcl::PixelFormat::N1_BPP :
+ vcl::PixelFormat::N8_BPP;
+
+ ::Bitmap aBitmap( aPixelSize,
+ ePixelFormat,
+ aLayout.Palette.is() ? &aPalette : nullptr );
+ ::Bitmap aAlpha;
+ if( nAlphaDepth )
+ aAlpha = Bitmap(aPixelSize,
+ eAlphaPixelFormat,
+ &Bitmap::GetGreyPalette(
+ sal::static_int_cast<sal_uInt16>(1 << nAlphaDepth)) );
+
+ { // limit scoped access
+ BitmapScopedWriteAccess pWriteAccess( aBitmap );
+ BitmapScopedWriteAccess pAlphaWriteAccess( nAlphaDepth ? aAlpha.AcquireWriteAccess() : nullptr,
+ aAlpha );
+
+ ENSURE_OR_THROW(pWriteAccess.get() != nullptr,
+ "Cannot get write access to bitmap");
+
+ const sal_Int32 nWidth(aPixelSize.Width());
+ const sal_Int32 nHeight(aPixelSize.Height());
+
+ if( !readBmp(nWidth,nHeight,aLayout,xInputBitmap,
+ pWriteAccess,pAlphaWriteAccess) )
+ continue;
+ } // limit scoped access
+
+ if( nAlphaDepth )
+ return ::BitmapEx( aBitmap,
+ AlphaMask( aAlpha ) );
+ else
+ return ::BitmapEx( aBitmap );
+ }
+
+ // failed to read data 10 times - bail out
+ return ::BitmapEx();
+ }
+
+ geometry::RealSize2D size2DFromSize( const Size& rSize )
+ {
+ return geometry::RealSize2D( rSize.Width(),
+ rSize.Height() );
+ }
+
+ Size sizeFromRealSize2D( const geometry::RealSize2D& rSize )
+ {
+ return Size( static_cast<tools::Long>(rSize.Width + .5),
+ static_cast<tools::Long>(rSize.Height + .5) );
+ }
+
+ ::Size sizeFromB2DSize( const basegfx::B2DVector& rVec )
+ {
+ return ::Size( FRound( rVec.getX() ),
+ FRound( rVec.getY() ) );
+ }
+
+ ::Point pointFromB2DPoint( const basegfx::B2DPoint& rPoint )
+ {
+ return pointFromB2IPoint(basegfx::fround(rPoint));
+ }
+
+ ::tools::Rectangle rectangleFromB2DRectangle( const basegfx::B2DRange& rRect )
+ {
+ return rectangleFromB2IRectangle(basegfx::fround(rRect));
+ }
+
+ Point pointFromB2IPoint( const basegfx::B2IPoint& rPoint )
+ {
+ return ::Point( rPoint.getX(),
+ rPoint.getY() );
+ }
+
+ basegfx::B2IPoint b2IPointFromPoint(Point const& rPoint)
+ {
+ return basegfx::B2IPoint(rPoint.X(), rPoint.Y());
+ }
+
+ tools::Rectangle rectangleFromB2IRectangle( const basegfx::B2IRange& rRect )
+ {
+ return ::tools::Rectangle( rRect.getMinX(),
+ rRect.getMinY(),
+ rRect.getMaxX(),
+ rRect.getMaxY() );
+ }
+
+ basegfx::B2IRectangle b2IRectangleFromRectangle(tools::Rectangle const& rRect)
+ {
+ // although B2IRange internally has separate height/width emptiness, it doesn't
+ // expose any API to let us set them separately, so just do the best we can.
+ if (rRect.IsWidthEmpty() && rRect.IsHeightEmpty())
+ return basegfx::B2IRange( basegfx::B2ITuple( rRect.Left(), rRect.Top() ) );
+ return basegfx::B2IRange( rRect.Left(),
+ rRect.Top(),
+ rRect.IsWidthEmpty() ? rRect.Left() : rRect.Right(),
+ rRect.IsHeightEmpty() ? rRect.Top() : rRect.Bottom() );
+ }
+
+ basegfx::B2DVector b2DSizeFromSize( const ::Size& rSize )
+ {
+ return basegfx::B2DVector( rSize.Width(),
+ rSize.Height() );
+ }
+
+ basegfx::B2DPoint b2DPointFromPoint( const ::Point& rPoint )
+ {
+ return basegfx::B2DPoint( rPoint.X(),
+ rPoint.Y() );
+ }
+
+ basegfx::B2DRange b2DRectangleFromRectangle( const ::tools::Rectangle& rRect )
+ {
+ // although B2DRange internally has separate height/width emptiness, it doesn't
+ // expose any API to let us set them separately, so just do the best we can.
+ if (rRect.IsWidthEmpty() && rRect.IsHeightEmpty())
+ return basegfx::B2DRange( basegfx::B2DTuple( rRect.Left(), rRect.Top() ) );
+ return basegfx::B2DRectangle( rRect.Left(),
+ rRect.Top(),
+ rRect.IsWidthEmpty() ? rRect.Left() : rRect.Right(),
+ rRect.IsHeightEmpty() ? rRect.Top() : rRect.Bottom() );
+ }
+
+ geometry::IntegerSize2D integerSize2DFromSize( const Size& rSize )
+ {
+ return geometry::IntegerSize2D( rSize.Width(),
+ rSize.Height() );
+ }
+
+ Size sizeFromIntegerSize2D( const geometry::IntegerSize2D& rSize )
+ {
+ return Size( rSize.Width,
+ rSize.Height );
+ }
+
+ Point pointFromIntegerPoint2D( const geometry::IntegerPoint2D& rPoint )
+ {
+ return Point( rPoint.X,
+ rPoint.Y );
+ }
+
+ tools::Rectangle rectangleFromIntegerRectangle2D( const geometry::IntegerRectangle2D& rRectangle )
+ {
+ return tools::Rectangle( rRectangle.X1, rRectangle.Y1,
+ rRectangle.X2, rRectangle.Y2 );
+ }
+
+ namespace
+ {
+ class StandardColorSpace : public cppu::WeakImplHelper< css::rendering::XColorSpace >
+ {
+ private:
+ uno::Sequence< sal_Int8 > m_aComponentTags;
+
+ virtual ::sal_Int8 SAL_CALL getType( ) override
+ {
+ return rendering::ColorSpaceType::RGB;
+ }
+ virtual uno::Sequence< ::sal_Int8 > SAL_CALL getComponentTags( ) override
+ {
+ return m_aComponentTags;
+ }
+ virtual ::sal_Int8 SAL_CALL getRenderingIntent( ) override
+ {
+ return rendering::RenderingIntent::PERCEPTUAL;
+ }
+ virtual uno::Sequence< beans::PropertyValue > SAL_CALL getProperties( ) override
+ {
+ return uno::Sequence< beans::PropertyValue >();
+ }
+ virtual uno::Sequence< double > SAL_CALL convertColorSpace( const uno::Sequence< double >& deviceColor,
+ const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override
+ {
+ // TODO(P3): if we know anything about target
+ // colorspace, this can be greatly sped up
+ uno::Sequence<rendering::ARGBColor> aIntermediate(
+ convertToARGB(deviceColor));
+ return targetColorSpace->convertFromARGB(aIntermediate);
+ }
+ virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertToRGB( const uno::Sequence< double >& deviceColor ) override
+ {
+ const double* pIn( deviceColor.getConstArray() );
+ const std::size_t nLen( deviceColor.getLength() );
+ ENSURE_ARG_OR_THROW2(nLen%4==0,
+ "number of channels no multiple of 4",
+ static_cast<rendering::XColorSpace*>(this), 0);
+
+ uno::Sequence< rendering::RGBColor > aRes(nLen/4);
+ rendering::RGBColor* pOut( aRes.getArray() );
+ for( std::size_t i=0; i<nLen; i+=4 )
+ {
+ *pOut++ = rendering::RGBColor(pIn[0],pIn[1],pIn[2]);
+ pIn += 4;
+ }
+ return aRes;
+ }
+ virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToARGB( const uno::Sequence< double >& deviceColor ) override
+ {
+ const double* pIn( deviceColor.getConstArray() );
+ const std::size_t nLen( deviceColor.getLength() );
+ ENSURE_ARG_OR_THROW2(nLen%4==0,
+ "number of channels no multiple of 4",
+ static_cast<rendering::XColorSpace*>(this), 0);
+
+ uno::Sequence< rendering::ARGBColor > aRes(nLen/4);
+ rendering::ARGBColor* pOut( aRes.getArray() );
+ for( std::size_t i=0; i<nLen; i+=4 )
+ {
+ *pOut++ = rendering::ARGBColor(pIn[3],pIn[0],pIn[1],pIn[2]);
+ pIn += 4;
+ }
+ return aRes;
+ }
+ virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToPARGB( const uno::Sequence< double >& deviceColor ) override
+ {
+ const double* pIn( deviceColor.getConstArray() );
+ const std::size_t nLen( deviceColor.getLength() );
+ ENSURE_ARG_OR_THROW2(nLen%4==0,
+ "number of channels no multiple of 4",
+ static_cast<rendering::XColorSpace*>(this), 0);
+
+ uno::Sequence< rendering::ARGBColor > aRes(nLen/4);
+ rendering::ARGBColor* pOut( aRes.getArray() );
+ for( std::size_t i=0; i<nLen; i+=4 )
+ {
+ *pOut++ = rendering::ARGBColor(pIn[3],pIn[3]*pIn[0],pIn[3]*pIn[1],pIn[3]*pIn[2]);
+ pIn += 4;
+ }
+ return aRes;
+ }
+ virtual uno::Sequence< double > SAL_CALL convertFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override
+ {
+ const std::size_t nLen( rgbColor.getLength() );
+
+ uno::Sequence< double > aRes(nLen*4);
+ double* pColors=aRes.getArray();
+ for( const auto& rIn : rgbColor )
+ {
+ *pColors++ = rIn.Red;
+ *pColors++ = rIn.Green;
+ *pColors++ = rIn.Blue;
+ *pColors++ = 1.0;
+ }
+ return aRes;
+ }
+ virtual uno::Sequence< double > SAL_CALL convertFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override
+ {
+ const std::size_t nLen( rgbColor.getLength() );
+
+ uno::Sequence< double > aRes(nLen*4);
+ double* pColors=aRes.getArray();
+ for( const auto& rIn : rgbColor )
+ {
+ *pColors++ = rIn.Red;
+ *pColors++ = rIn.Green;
+ *pColors++ = rIn.Blue;
+ *pColors++ = rIn.Alpha;
+ }
+ return aRes;
+ }
+ virtual uno::Sequence< double > SAL_CALL convertFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override
+ {
+ const std::size_t nLen( rgbColor.getLength() );
+
+ uno::Sequence< double > aRes(nLen*4);
+ double* pColors=aRes.getArray();
+ for( const auto& rIn : rgbColor )
+ {
+ *pColors++ = rIn.Red/rIn.Alpha;
+ *pColors++ = rIn.Green/rIn.Alpha;
+ *pColors++ = rIn.Blue/rIn.Alpha;
+ *pColors++ = rIn.Alpha;
+ }
+ return aRes;
+ }
+
+ public:
+ StandardColorSpace() : m_aComponentTags(4)
+ {
+ sal_Int8* pTags = m_aComponentTags.getArray();
+ pTags[0] = rendering::ColorComponentTag::RGB_RED;
+ pTags[1] = rendering::ColorComponentTag::RGB_GREEN;
+ pTags[2] = rendering::ColorComponentTag::RGB_BLUE;
+ pTags[3] = rendering::ColorComponentTag::ALPHA;
+ }
+ };
+ }
+
+ uno::Reference<rendering::XColorSpace> createStandardColorSpace()
+ {
+ return new StandardColorSpace();
+ }
+
+ uno::Sequence< double > colorToStdColorSpaceSequence( const Color& rColor )
+ {
+ return
+ {
+ toDoubleColor(rColor.GetRed()),
+ toDoubleColor(rColor.GetGreen()),
+ toDoubleColor(rColor.GetBlue()),
+ toDoubleColor(rColor.GetAlpha())
+ };
+ }
+
+ Color stdColorSpaceSequenceToColor( const uno::Sequence< double >& rColor )
+ {
+ ENSURE_ARG_OR_THROW( rColor.getLength() == 4,
+ "color must have 4 channels" );
+
+ Color aColor;
+
+ aColor.SetRed ( toByteColor(rColor[0]) );
+ aColor.SetGreen( toByteColor(rColor[1]) );
+ aColor.SetBlue ( toByteColor(rColor[2]) );
+ aColor.SetAlpha( toByteColor(rColor[3]) );
+
+ return aColor;
+ }
+
+ uno::Sequence< double > colorToDoubleSequence(
+ const Color& rColor,
+ const uno::Reference< rendering::XColorSpace >& xColorSpace )
+ {
+ uno::Sequence<rendering::ARGBColor> aSeq
+ {
+ {
+ toDoubleColor(rColor.GetAlpha()),
+ toDoubleColor(rColor.GetRed()),
+ toDoubleColor(rColor.GetGreen()),
+ toDoubleColor(rColor.GetBlue())
+ }
+ };
+
+ return xColorSpace->convertFromARGB(aSeq);
+ }
+
+ Color doubleSequenceToColor(
+ const uno::Sequence< double >& rColor,
+ const uno::Reference< rendering::XColorSpace >& xColorSpace )
+ {
+ const rendering::ARGBColor aARGBColor(
+ xColorSpace->convertToARGB(rColor)[0]);
+
+ return Color( ColorAlpha, toByteColor(aARGBColor.Alpha),
+ toByteColor(aARGBColor.Red),
+ toByteColor(aARGBColor.Green),
+ toByteColor(aARGBColor.Blue) );
+ }
+
+} // namespace canvas
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/commandinfoprovider.cxx b/vcl/source/helper/commandinfoprovider.cxx
new file mode 100644
index 000000000..5c280bb66
--- /dev/null
+++ b/vcl/source/helper/commandinfoprovider.cxx
@@ -0,0 +1,480 @@
+/* -*- 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 <vcl/commandinfoprovider.hxx>
+#include <vcl/keycod.hxx>
+#include <vcl/mnemonic.hxx>
+#include <comphelper/string.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/weakref.hxx>
+
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <com/sun/star/frame/theUICommandDescription.hpp>
+#include <com/sun/star/ui/GlobalAcceleratorConfiguration.hpp>
+#include <com/sun/star/ui/XUIConfigurationManagerSupplier.hpp>
+#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp>
+#include <com/sun/star/ui/ImageType.hpp>
+#include <com/sun/star/ui/XImageManager.hpp>
+#include <com/sun/star/awt/KeyModifier.hpp>
+
+using namespace css;
+using namespace css::uno;
+
+namespace vcl::CommandInfoProvider {
+
+static Reference<container::XNameAccess> GetCommandDescription()
+{
+ static WeakReference<container::XNameAccess> xWeakRef;
+ css::uno::Reference<container::XNameAccess> xRef(xWeakRef);
+
+ if (!xRef.is())
+ {
+ xRef = frame::theUICommandDescription::get(comphelper::getProcessComponentContext());
+ xWeakRef = xRef;
+ }
+
+ return xRef;
+}
+
+static Reference<ui::XModuleUIConfigurationManagerSupplier> GetModuleConfigurationSupplier()
+{
+ static WeakReference<ui::XModuleUIConfigurationManagerSupplier> xWeakRef;
+ css::uno::Reference<ui::XModuleUIConfigurationManagerSupplier> xRef(xWeakRef);
+
+ if (!xRef.is())
+ {
+ xRef = ui::theModuleUIConfigurationManagerSupplier::get(comphelper::getProcessComponentContext());
+ xWeakRef = xRef;
+ }
+
+ return xRef;
+}
+
+static Reference<ui::XAcceleratorConfiguration> GetGlobalAcceleratorConfiguration()
+{
+ static WeakReference<ui::XAcceleratorConfiguration> xWeakRef;
+ css::uno::Reference<ui::XAcceleratorConfiguration> xRef(xWeakRef);
+
+ if (!xRef.is())
+ {
+ xRef = ui::GlobalAcceleratorConfiguration::create(comphelper::getProcessComponentContext());
+ xWeakRef = xRef;
+ }
+
+ return xRef;
+}
+
+static Reference<ui::XAcceleratorConfiguration> GetDocumentAcceleratorConfiguration(const Reference<frame::XFrame>& rxFrame)
+{
+ Reference<frame::XController> xController = rxFrame->getController();
+ if (xController.is())
+ {
+ Reference<ui::XUIConfigurationManagerSupplier> xSupplier(xController->getModel(), UNO_QUERY);
+ if (xSupplier.is())
+ {
+ Reference<ui::XUIConfigurationManager> xConfigurationManager(
+ xSupplier->getUIConfigurationManager());
+ if (xConfigurationManager.is())
+ {
+ return xConfigurationManager->getShortCutManager();
+ }
+ }
+ }
+ return nullptr;
+}
+
+static Reference<ui::XAcceleratorConfiguration> GetModuleAcceleratorConfiguration(const Reference<frame::XFrame>& rxFrame)
+{
+ css::uno::Reference<css::ui::XAcceleratorConfiguration> curModuleAcceleratorConfiguration;
+ try
+ {
+ Reference<ui::XModuleUIConfigurationManagerSupplier> xSupplier(GetModuleConfigurationSupplier());
+ Reference<ui::XUIConfigurationManager> xManager (
+ xSupplier->getUIConfigurationManager(GetModuleIdentifier(rxFrame)));
+ if (xManager.is())
+ {
+ curModuleAcceleratorConfiguration = xManager->getShortCutManager();
+ }
+ }
+ catch (Exception&)
+ {
+ }
+ return curModuleAcceleratorConfiguration;
+}
+
+static vcl::KeyCode AWTKey2VCLKey(const awt::KeyEvent& aAWTKey)
+{
+ bool bShift = ((aAWTKey.Modifiers & awt::KeyModifier::SHIFT) == awt::KeyModifier::SHIFT );
+ bool bMod1 = ((aAWTKey.Modifiers & awt::KeyModifier::MOD1 ) == awt::KeyModifier::MOD1 );
+ bool bMod2 = ((aAWTKey.Modifiers & awt::KeyModifier::MOD2 ) == awt::KeyModifier::MOD2 );
+ bool bMod3 = ((aAWTKey.Modifiers & awt::KeyModifier::MOD3 ) == awt::KeyModifier::MOD3 );
+ sal_uInt16 nKey = static_cast<sal_uInt16>(aAWTKey.KeyCode);
+
+ return vcl::KeyCode(nKey, bShift, bMod1, bMod2, bMod3);
+}
+
+static OUString RetrieveShortcutsFromConfiguration(
+ const Reference<ui::XAcceleratorConfiguration>& rxConfiguration,
+ const OUString& rsCommandName)
+{
+ if (rxConfiguration.is())
+ {
+ try
+ {
+ Sequence<OUString> aCommands { rsCommandName };
+
+ Sequence<Any> aKeyCodes (rxConfiguration->getPreferredKeyEventsForCommandList(aCommands));
+ if (aCommands.getLength() == 1)
+ {
+ awt::KeyEvent aKeyEvent;
+ if (aKeyCodes[0] >>= aKeyEvent)
+ {
+ return AWTKey2VCLKey(aKeyEvent).GetName();
+ }
+ }
+ }
+ catch (css::lang::IllegalArgumentException&)
+ {
+ }
+ }
+ return OUString();
+}
+
+static vcl::KeyCode RetrieveKeyCodeShortcutsFromConfiguration(
+ const Reference<ui::XAcceleratorConfiguration>& rxConfiguration,
+ const OUString& rsCommandName)
+{
+ if (rxConfiguration.is())
+ {
+ try
+ {
+ Sequence<OUString> aCommands { rsCommandName };
+
+ Sequence<Any> aKeyCodes (rxConfiguration->getPreferredKeyEventsForCommandList(aCommands));
+ if (aCommands.getLength() == 1)
+ {
+ awt::KeyEvent aKeyEvent;
+ if (aKeyCodes[0] >>= aKeyEvent)
+ {
+ return AWTKey2VCLKey(aKeyEvent);
+ }
+ }
+ }
+ catch (css::lang::IllegalArgumentException&)
+ {
+ }
+ }
+ return vcl::KeyCode();
+}
+
+static bool ResourceHasKey(const OUString& rsResourceName, const OUString& rsCommandName, const OUString& rsModuleName)
+{
+ Sequence< OUString > aSequence;
+ try
+ {
+ if (!rsModuleName.isEmpty())
+ {
+ Reference<container::XNameAccess> xNameAccess(GetCommandDescription());
+ Reference<container::XNameAccess> xUICommandLabels;
+ if (xNameAccess->getByName(rsModuleName) >>= xUICommandLabels)
+ {
+ xUICommandLabels->getByName(rsResourceName) >>= aSequence;
+ if (comphelper::findValue(aSequence, rsCommandName) != -1)
+ return true;
+ }
+ }
+ }
+ catch (Exception&)
+ {
+ }
+ return false;
+}
+
+Sequence<beans::PropertyValue> GetCommandProperties(const OUString& rsCommandName, const OUString& rsModuleName)
+{
+ Sequence<beans::PropertyValue> aProperties;
+
+ try
+ {
+ if (!rsModuleName.isEmpty())
+ {
+ Reference<container::XNameAccess> xNameAccess(GetCommandDescription());
+ Reference<container::XNameAccess> xUICommandLabels;
+ if ((xNameAccess->getByName(rsModuleName) >>= xUICommandLabels) && xUICommandLabels->hasByName(rsCommandName))
+ xUICommandLabels->getByName(rsCommandName) >>= aProperties;
+ }
+ }
+ catch (Exception&)
+ {
+ }
+
+ return aProperties;
+}
+
+static OUString GetCommandProperty(const OUString& rsProperty, const Sequence<beans::PropertyValue> &rProperties)
+{
+ auto pProp = std::find_if(rProperties.begin(), rProperties.end(),
+ [&rsProperty](const beans::PropertyValue& rProp) { return rProp.Name == rsProperty; });
+ if (pProp != rProperties.end())
+ {
+ OUString sLabel;
+ pProp->Value >>= sLabel;
+ return sLabel;
+ }
+ return OUString();
+}
+
+OUString GetLabelForCommand(const css::uno::Sequence<css::beans::PropertyValue>& rProperties)
+{
+ return GetCommandProperty("Name", rProperties);
+}
+
+OUString GetMenuLabelForCommand(const css::uno::Sequence<css::beans::PropertyValue>& rProperties)
+{
+ // Here we want to use "Label", not "Name". "Name" is a stripped-down version of "Label" without accelerators
+ // and ellipsis. In the menu, we want to have those accelerators and ellipsis.
+ return GetCommandProperty("Label", rProperties);
+}
+
+OUString GetPopupLabelForCommand(const css::uno::Sequence<css::beans::PropertyValue>& rProperties)
+{
+ OUString sPopupLabel(GetCommandProperty("PopupLabel", rProperties));
+ if (!sPopupLabel.isEmpty())
+ return sPopupLabel;
+ return GetCommandProperty("Label", rProperties);
+}
+
+OUString GetTooltipLabelForCommand(const css::uno::Sequence<css::beans::PropertyValue>& rProperties)
+{
+ OUString sLabel(GetCommandProperty("TooltipLabel", rProperties));
+ if (!sLabel.isEmpty())
+ return sLabel;
+ return GetCommandProperty("Label", rProperties);
+}
+
+OUString GetTooltipForCommand(
+ const OUString& rsCommandName,
+ const css::uno::Sequence<css::beans::PropertyValue>& rProperties,
+ const Reference<frame::XFrame>& rxFrame)
+{
+ OUString sLabel(GetCommandProperty("TooltipLabel", rProperties));
+ if (sLabel.isEmpty()) {
+ sLabel = GetPopupLabelForCommand(rProperties);
+ // Remove '...' at the end and mnemonics (we don't want those in tooltips)
+ sLabel = comphelper::string::stripEnd(sLabel, '.');
+ sLabel = MnemonicGenerator::EraseAllMnemonicChars(sLabel);
+ }
+
+ // Command can be just an alias to another command,
+ // so need to get the shortcut of the "real" command.
+ const OUString sRealCommand(GetRealCommandForCommand(rProperties));
+ const OUString sShortCut(GetCommandShortcut(!sRealCommand.isEmpty() ? sRealCommand : rsCommandName, rxFrame));
+ if (!sShortCut.isEmpty())
+ return sLabel + " (" + sShortCut + ")";
+ return sLabel;
+}
+
+OUString GetCommandShortcut (const OUString& rsCommandName,
+ const Reference<frame::XFrame>& rxFrame)
+{
+
+ OUString sShortcut;
+
+ sShortcut = RetrieveShortcutsFromConfiguration(GetDocumentAcceleratorConfiguration(rxFrame), rsCommandName);
+ if (sShortcut.getLength() > 0)
+ return sShortcut;
+
+ sShortcut = RetrieveShortcutsFromConfiguration(GetModuleAcceleratorConfiguration(rxFrame), rsCommandName);
+ if (sShortcut.getLength() > 0)
+ return sShortcut;
+
+ sShortcut = RetrieveShortcutsFromConfiguration(GetGlobalAcceleratorConfiguration(), rsCommandName);
+ if (sShortcut.getLength() > 0)
+ return sShortcut;
+
+ return OUString();
+}
+
+vcl::KeyCode GetCommandKeyCodeShortcut (const OUString& rsCommandName, const Reference<frame::XFrame>& rxFrame)
+{
+ vcl::KeyCode aKeyCodeShortcut;
+
+ aKeyCodeShortcut = RetrieveKeyCodeShortcutsFromConfiguration(GetDocumentAcceleratorConfiguration(rxFrame), rsCommandName);
+ if (aKeyCodeShortcut.GetCode())
+ return aKeyCodeShortcut;
+
+ aKeyCodeShortcut = RetrieveKeyCodeShortcutsFromConfiguration(GetModuleAcceleratorConfiguration(rxFrame), rsCommandName);
+ if (aKeyCodeShortcut.GetCode())
+ return aKeyCodeShortcut;
+
+ aKeyCodeShortcut = RetrieveKeyCodeShortcutsFromConfiguration(GetGlobalAcceleratorConfiguration(), rsCommandName);
+ if (aKeyCodeShortcut.GetCode())
+ return aKeyCodeShortcut;
+
+ return vcl::KeyCode();
+}
+
+OUString GetRealCommandForCommand(const css::uno::Sequence<css::beans::PropertyValue>& rProperties)
+{
+ return GetCommandProperty("TargetURL", rProperties);
+}
+
+Reference<graphic::XGraphic> GetXGraphicForCommand(const OUString& rsCommandName,
+ const Reference<frame::XFrame>& rxFrame,
+ vcl::ImageType eImageType)
+{
+ if (rsCommandName.isEmpty())
+ return nullptr;
+
+ sal_Int16 nImageType(ui::ImageType::COLOR_NORMAL | ui::ImageType::SIZE_DEFAULT);
+
+ if (eImageType == vcl::ImageType::Size26)
+ nImageType |= ui::ImageType::SIZE_LARGE;
+ else if (eImageType == vcl::ImageType::Size32)
+ nImageType |= ui::ImageType::SIZE_32;
+
+ try
+ {
+ Reference<frame::XController> xController(rxFrame->getController(), UNO_SET_THROW);
+ Reference<ui::XUIConfigurationManagerSupplier> xSupplier(xController->getModel(), UNO_QUERY);
+ if (xSupplier.is())
+ {
+ Reference<ui::XUIConfigurationManager> xDocUICfgMgr(xSupplier->getUIConfigurationManager());
+ Reference<ui::XImageManager> xDocImgMgr(xDocUICfgMgr->getImageManager(), UNO_QUERY);
+
+ Sequence< Reference<graphic::XGraphic> > aGraphicSeq;
+ Sequence<OUString> aImageCmdSeq { rsCommandName };
+
+ aGraphicSeq = xDocImgMgr->getImages( nImageType, aImageCmdSeq );
+ Reference<graphic::XGraphic> xGraphic = aGraphicSeq[0];
+ if (xGraphic.is())
+ return xGraphic;
+ }
+ }
+ catch (Exception&)
+ {
+ }
+
+ try {
+ Reference<ui::XModuleUIConfigurationManagerSupplier> xModuleCfgMgrSupplier(GetModuleConfigurationSupplier());
+ Reference<ui::XUIConfigurationManager> xUICfgMgr(xModuleCfgMgrSupplier->getUIConfigurationManager(GetModuleIdentifier(rxFrame)));
+
+ Sequence< Reference<graphic::XGraphic> > aGraphicSeq;
+ Reference<ui::XImageManager> xModuleImageManager(xUICfgMgr->getImageManager(), UNO_QUERY);
+
+ Sequence<OUString> aImageCmdSeq { rsCommandName };
+
+ aGraphicSeq = xModuleImageManager->getImages(nImageType, aImageCmdSeq);
+
+ Reference<graphic::XGraphic> xGraphic(aGraphicSeq[0]);
+
+ return xGraphic;
+ }
+ catch (Exception&)
+ {
+ }
+
+ return nullptr;
+}
+
+Image GetImageForCommand(const OUString& rsCommandName,
+ const Reference<frame::XFrame>& rxFrame,
+ vcl::ImageType eImageType)
+{
+ return Image(GetXGraphicForCommand(rsCommandName, rxFrame, eImageType));
+}
+
+sal_Int32 GetPropertiesForCommand (
+ const OUString& rsCommandName,
+ const OUString& rsModuleName)
+{
+ sal_Int32 nValue = 0;
+ const Sequence<beans::PropertyValue> aProperties (GetCommandProperties(rsCommandName, rsModuleName));
+
+ auto pProp = std::find_if(aProperties.begin(), aProperties.end(),
+ [](const beans::PropertyValue& rProp) { return rProp.Name == "Properties"; });
+ if (pProp != aProperties.end())
+ pProp->Value >>= nValue;
+
+ return nValue;
+}
+
+bool IsRotated(const OUString& rsCommandName, const OUString& rsModuleName)
+{
+ return ResourceHasKey("private:resource/image/commandrotateimagelist", rsCommandName, rsModuleName);
+}
+
+bool IsMirrored(const OUString& rsCommandName, const OUString& rsModuleName)
+{
+ return ResourceHasKey("private:resource/image/commandmirrorimagelist", rsCommandName, rsModuleName);
+}
+
+bool IsExperimental(const OUString& rsCommandName, const OUString& rModuleName)
+{
+ Sequence<beans::PropertyValue> aProperties;
+ try
+ {
+ if( rModuleName.getLength() > 0)
+ {
+ Reference<container::XNameAccess> xNameAccess(GetCommandDescription());
+ Reference<container::XNameAccess> xUICommandLabels;
+ if (xNameAccess->getByName( rModuleName ) >>= xUICommandLabels )
+ xUICommandLabels->getByName(rsCommandName) >>= aProperties;
+
+ auto pProp = std::find_if(std::cbegin(aProperties), std::cend(aProperties),
+ [](const beans::PropertyValue& rProp) { return rProp.Name == "IsExperimental"; });
+ if (pProp != std::cend(aProperties))
+ {
+ bool bValue;
+ return (pProp->Value >>= bValue) && bValue;
+ }
+ }
+ }
+ catch (Exception&)
+ {
+ }
+ return false;
+}
+
+OUString GetModuleIdentifier(const Reference<frame::XFrame>& rxFrame)
+{
+ static WeakReference<frame::XModuleManager2> xWeakRef;
+ css::uno::Reference<frame::XModuleManager2> xRef(xWeakRef);
+
+ if (!xRef.is())
+ {
+ xRef = frame::ModuleManager::create(comphelper::getProcessComponentContext());
+ xWeakRef = xRef;
+ }
+
+ try
+ {
+ return xRef->identify(rxFrame);
+ }
+ catch (const Exception&)
+ {}
+
+ return OUString();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/displayconnectiondispatch.cxx b/vcl/source/helper/displayconnectiondispatch.cxx
new file mode 100644
index 000000000..664446cd8
--- /dev/null
+++ b/vcl/source/helper/displayconnectiondispatch.cxx
@@ -0,0 +1,111 @@
+/* -*- 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 <vcl/svapp.hxx>
+#include <tools/debug.hxx>
+
+#include <displayconnectiondispatch.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+using namespace osl;
+using namespace vcl;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::awt;
+
+DisplayConnectionDispatch::DisplayConnectionDispatch()
+{
+ m_ConnectionIdentifier = ImplGetSVData()->mpDefInst->GetConnectionIdentifier();
+}
+
+DisplayConnectionDispatch::~DisplayConnectionDispatch()
+{}
+
+void DisplayConnectionDispatch::start()
+{
+ DBG_TESTSOLARMUTEX();
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->mpDefInst->SetEventCallback( this );
+}
+
+void DisplayConnectionDispatch::terminate()
+{
+ DBG_TESTSOLARMUTEX();
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if( pSVData )
+ {
+ pSVData->mpDefInst->SetEventCallback( nullptr );
+ }
+
+ SolarMutexReleaser aRel;
+
+ std::scoped_lock aGuard( m_aMutex );
+ Any aEvent;
+ std::vector< css::uno::Reference< XEventHandler > > aLocalList( m_aHandlers );
+ for (auto const& elem : aLocalList)
+ elem->handleEvent( aEvent );
+}
+
+void SAL_CALL DisplayConnectionDispatch::addEventHandler( const Any& /*window*/, const css::uno::Reference< XEventHandler >& handler, sal_Int32 /*eventMask*/ )
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ m_aHandlers.push_back( handler );
+}
+
+void SAL_CALL DisplayConnectionDispatch::removeEventHandler( const Any& /*window*/, const css::uno::Reference< XEventHandler >& handler )
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ m_aHandlers.erase( std::remove(m_aHandlers.begin(), m_aHandlers.end(), handler), m_aHandlers.end() );
+}
+
+void SAL_CALL DisplayConnectionDispatch::addErrorHandler( const css::uno::Reference< XEventHandler >& )
+{
+}
+
+void SAL_CALL DisplayConnectionDispatch::removeErrorHandler( const css::uno::Reference< XEventHandler >& )
+{
+}
+
+Any SAL_CALL DisplayConnectionDispatch::getIdentifier()
+{
+ return Any(m_ConnectionIdentifier);
+}
+
+bool DisplayConnectionDispatch::dispatchEvent( void const * pData, int nBytes )
+{
+ SolarMutexReleaser aRel;
+
+ Sequence< sal_Int8 > aSeq( static_cast<const sal_Int8*>(pData), nBytes );
+ Any aEvent;
+ aEvent <<= aSeq;
+ ::std::vector< css::uno::Reference< XEventHandler > > handlers;
+ {
+ std::scoped_lock aGuard( m_aMutex );
+ handlers = m_aHandlers;
+ }
+ for (auto const& handle : handlers)
+ if( handle->handleEvent( aEvent ) )
+ return true;
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/driverblocklist.cxx b/vcl/source/helper/driverblocklist.cxx
new file mode 100644
index 000000000..855376dc1
--- /dev/null
+++ b/vcl/source/helper/driverblocklist.cxx
@@ -0,0 +1,768 @@
+/* -*- 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/.
+ */
+
+#include <driverblocklist.hxx>
+
+#include <algorithm>
+#include <string_view>
+
+#include <sal/log.hxx>
+
+#ifdef _WIN32
+#if !defined WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#endif
+
+namespace DriverBlocklist
+{
+static OperatingSystem getOperatingSystem(std::string_view rString)
+{
+ if (rString == "all")
+ return DRIVER_OS_ALL;
+ else if (rString == "7")
+ return DRIVER_OS_WINDOWS_7;
+ else if (rString == "8")
+ return DRIVER_OS_WINDOWS_8;
+ else if (rString == "8_1")
+ return DRIVER_OS_WINDOWS_8_1;
+ else if (rString == "10")
+ return DRIVER_OS_WINDOWS_10;
+ else if (rString == "windows")
+ return DRIVER_OS_WINDOWS_ALL;
+ else if (rString == "linux")
+ return DRIVER_OS_LINUX;
+ else if (rString == "osx_10_5")
+ return DRIVER_OS_OSX_10_5;
+ else if (rString == "osx_10_6")
+ return DRIVER_OS_OSX_10_6;
+ else if (rString == "osx_10_7")
+ return DRIVER_OS_OSX_10_7;
+ else if (rString == "osx_10_8")
+ return DRIVER_OS_OSX_10_8;
+ else if (rString == "osx")
+ return DRIVER_OS_OSX_ALL;
+ else if (rString == "android")
+ return DRIVER_OS_ANDROID;
+ return DRIVER_OS_UNKNOWN;
+}
+
+static VersionComparisonOp getComparison(std::string_view rString)
+{
+ if (rString == "less")
+ {
+ return DRIVER_LESS_THAN;
+ }
+ else if (rString == "less_equal")
+ {
+ return DRIVER_LESS_THAN_OR_EQUAL;
+ }
+ else if (rString == "greater")
+ {
+ return DRIVER_GREATER_THAN;
+ }
+ else if (rString == "greater_equal")
+ {
+ return DRIVER_GREATER_THAN_OR_EQUAL;
+ }
+ else if (rString == "equal")
+ {
+ return DRIVER_EQUAL;
+ }
+ else if (rString == "not_equal")
+ {
+ return DRIVER_NOT_EQUAL;
+ }
+ else if (rString == "between_exclusive")
+ {
+ return DRIVER_BETWEEN_EXCLUSIVE;
+ }
+ else if (rString == "between_inclusive")
+ {
+ return DRIVER_BETWEEN_INCLUSIVE;
+ }
+ else if (rString == "between_inclusive_start")
+ {
+ return DRIVER_BETWEEN_INCLUSIVE_START;
+ }
+
+ throw InvalidFileException();
+}
+
+static OUString GetVendorId(std::string_view rString)
+{
+ if (rString == "all")
+ {
+ return "";
+ }
+ else if (rString == "intel")
+ {
+ return "0x8086";
+ }
+ else if (rString == "nvidia")
+ {
+ return "0x10de";
+ }
+ else if (rString == "amd")
+ {
+ return "0x1002";
+ }
+ else if (rString == "microsoft")
+ {
+ return "0x1414";
+ }
+ else
+ {
+ // Allow having simply the hex number as such there, too.
+ return OStringToOUString(rString, RTL_TEXTENCODING_UTF8);
+ }
+}
+
+OUString GetVendorId(DeviceVendor id)
+{
+ assert(id >= 0 && id < DeviceVendorMax);
+
+ switch (id)
+ {
+ case VendorAll:
+ return "";
+ case VendorIntel:
+ return "0x8086";
+ case VendorNVIDIA:
+ return "0x10de";
+ case VendorAMD:
+ return "0x1002";
+ case VendorMicrosoft:
+ return "0x1414";
+ }
+ abort();
+}
+
+DeviceVendor GetVendorFromId(uint32_t id)
+{
+ switch (id)
+ {
+ case 0x8086:
+ return VendorIntel;
+ case 0x10de:
+ return VendorNVIDIA;
+ case 0x1002:
+ return VendorAMD;
+ case 0x1414:
+ return VendorMicrosoft;
+ default:
+ return VendorAll;
+ }
+}
+
+std::string_view GetVendorNameFromId(uint32_t id)
+{
+ switch (id)
+ {
+ case 0x8086:
+ return "Intel";
+ case 0x10de:
+ return "Nvidia";
+ case 0x1002:
+ return "AMD";
+ case 0x1414:
+ return "Microsoft";
+ default:
+ return "?";
+ }
+}
+
+Parser::Parser(const OUString& rURL, std::vector<DriverInfo>& rDriverList, VersionType versionType)
+ : meBlockType(BlockType::UNKNOWN)
+ , mrDriverList(rDriverList)
+ , maURL(rURL)
+ , mVersionType(versionType)
+{
+}
+
+bool Parser::parse()
+{
+ try
+ {
+ xmlreader::XmlReader aReader(maURL);
+ handleContent(aReader);
+ }
+ catch (...)
+ {
+ mrDriverList.clear();
+ return false;
+ }
+ return true;
+}
+
+// This allows us to pad driver version 'substrings' with 0s, this
+// effectively allows us to treat the version numbers as 'decimals'. This is
+// a little strange but this method seems to do the right thing for all
+// different vendor's driver strings. i.e. .98 will become 9800, which is
+// larger than .978 which would become 9780.
+static void PadDriverDecimal(char* aString)
+{
+ for (int i = 0; i < 4; i++)
+ {
+ if (!aString[i])
+ {
+ for (int c = i; c < 4; c++)
+ {
+ aString[c] = '0';
+ }
+ break;
+ }
+ }
+ aString[4] = 0;
+}
+
+// All destination string storage needs to have at least 5 bytes available.
+static bool SplitDriverVersion(const char* aSource, char* aAStr, char* aBStr, char* aCStr,
+ char* aDStr, VersionType versionType)
+{
+ // sscanf doesn't do what we want here to we parse this manually.
+ int len = strlen(aSource);
+ char* dest[4] = { aAStr, aBStr, aCStr, aDStr };
+ unsigned destIdx = 0;
+ unsigned destPos = 0;
+
+ for (int i = 0; i < len; i++)
+ {
+ if (destIdx >= SAL_N_ELEMENTS(dest))
+ {
+ // Invalid format found. Ensure we don't access dest beyond bounds.
+ return false;
+ }
+
+ if (aSource[i] == '.')
+ {
+ dest[destIdx++][destPos] = 0;
+ destPos = 0;
+ continue;
+ }
+
+ if (destPos > 3)
+ {
+ // Ignore more than 4 chars. Ensure we never access dest[destIdx]
+ // beyond its bounds.
+ continue;
+ }
+
+ dest[destIdx][destPos++] = aSource[i];
+ }
+
+ // Add last terminator.
+ dest[destIdx][destPos] = 0;
+
+ // Vulkan version numbers have only 3 fields.
+ if (versionType == VersionType::Vulkan && destIdx == SAL_N_ELEMENTS(dest) - 2)
+ dest[++destIdx][0] = '\0';
+ if (destIdx != SAL_N_ELEMENTS(dest) - 1)
+ {
+ return false;
+ }
+ return true;
+}
+
+static bool ParseDriverVersion(std::u16string_view aVersion, uint64_t& rNumericVersion,
+ VersionType versionType)
+{
+ rNumericVersion = 0;
+
+ int a, b, c, d;
+ char aStr[8], bStr[8], cStr[8], dStr[8];
+ /* honestly, why do I even bother */
+ OString aOVersion = OUStringToOString(aVersion, RTL_TEXTENCODING_UTF8);
+ if (!SplitDriverVersion(aOVersion.getStr(), aStr, bStr, cStr, dStr, versionType))
+ return false;
+
+ if (versionType == VersionType::OpenGL)
+ {
+ PadDriverDecimal(bStr);
+ PadDriverDecimal(cStr);
+ PadDriverDecimal(dStr);
+ }
+
+ a = atoi(aStr);
+ b = atoi(bStr);
+ c = atoi(cStr);
+ d = atoi(dStr);
+
+ if (versionType == VersionType::Vulkan)
+ assert(d == 0);
+
+ if (a < 0 || a > 0xffff)
+ return false;
+ if (b < 0 || b > 0xffff)
+ return false;
+ if (c < 0 || c > 0xffff)
+ return false;
+ if (d < 0 || d > 0xffff)
+ return false;
+
+ rNumericVersion = GFX_DRIVER_VERSION(a, b, c, d);
+ return true;
+}
+
+uint64_t Parser::getVersion(std::string_view rString)
+{
+ OUString aString = OStringToOUString(rString, RTL_TEXTENCODING_UTF8);
+ uint64_t nVersion;
+ bool bResult = ParseDriverVersion(aString, nVersion, mVersionType);
+
+ if (!bResult)
+ {
+ throw InvalidFileException();
+ }
+
+ return nVersion;
+}
+
+void Parser::handleDevices(DriverInfo& rDriver, xmlreader::XmlReader& rReader)
+{
+ int nLevel = 1;
+ bool bInMsg = false;
+
+ while (true)
+ {
+ xmlreader::Span name;
+ int nsId;
+
+ xmlreader::XmlReader::Result res
+ = rReader.nextItem(xmlreader::XmlReader::Text::Normalized, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ ++nLevel;
+ if (nLevel > 2)
+ throw InvalidFileException();
+
+ if (name == "msg")
+ {
+ bInMsg = true;
+ }
+ else if (name == "device")
+ {
+ int nsIdDeveice;
+ while (rReader.nextAttribute(&nsIdDeveice, &name))
+ {
+ if (name == "id")
+ {
+ name = rReader.getAttributeValue(false);
+ OString aDeviceId(name.begin, name.length);
+ rDriver.maDevices.push_back(
+ OStringToOUString(aDeviceId, RTL_TEXTENCODING_UTF8));
+ }
+ }
+ }
+ else
+ throw InvalidFileException();
+ }
+ else if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ bInMsg = false;
+ if (!nLevel)
+ break;
+ }
+ else if (res == xmlreader::XmlReader::Result::Text)
+ {
+ if (bInMsg)
+ {
+ OString sMsg(name.begin, name.length);
+ rDriver.maMsg = OStringToOUString(sMsg, RTL_TEXTENCODING_UTF8);
+ }
+ }
+ }
+}
+
+void Parser::handleEntry(DriverInfo& rDriver, xmlreader::XmlReader& rReader)
+{
+ if (meBlockType == BlockType::ALLOWLIST)
+ {
+ rDriver.mbAllowlisted = true;
+ }
+ else if (meBlockType == BlockType::DENYLIST)
+ {
+ rDriver.mbAllowlisted = false;
+ }
+ else if (meBlockType == BlockType::UNKNOWN)
+ {
+ throw InvalidFileException();
+ }
+
+ xmlreader::Span name;
+ int nsId;
+
+ while (rReader.nextAttribute(&nsId, &name))
+ {
+ if (name == "os")
+ {
+ name = rReader.getAttributeValue(false);
+ OString sOS(name.begin, name.length);
+ rDriver.meOperatingSystem = getOperatingSystem(sOS);
+ }
+ else if (name == "vendor")
+ {
+ name = rReader.getAttributeValue(false);
+ OString sVendor(name.begin, name.length);
+ rDriver.maAdapterVendor = GetVendorId(sVendor);
+ }
+ else if (name == "compare")
+ {
+ name = rReader.getAttributeValue(false);
+ OString sCompare(name.begin, name.length);
+ rDriver.meComparisonOp = getComparison(sCompare);
+ }
+ else if (name == "version")
+ {
+ name = rReader.getAttributeValue(false);
+ OString sVersion(name.begin, name.length);
+ rDriver.mnDriverVersion = getVersion(sVersion);
+ }
+ else if (name == "minVersion")
+ {
+ name = rReader.getAttributeValue(false);
+ OString sMinVersion(name.begin, name.length);
+ rDriver.mnDriverVersion = getVersion(sMinVersion);
+ }
+ else if (name == "maxVersion")
+ {
+ name = rReader.getAttributeValue(false);
+ OString sMaxVersion(name.begin, name.length);
+ rDriver.mnDriverVersionMax = getVersion(sMaxVersion);
+ }
+ else
+ {
+ OString aAttrName(name.begin, name.length);
+ SAL_WARN("vcl.driver", "unsupported attribute: " << aAttrName);
+ }
+ }
+
+ handleDevices(rDriver, rReader);
+}
+
+void Parser::handleList(xmlreader::XmlReader& rReader)
+{
+ xmlreader::Span name;
+ int nsId;
+
+ while (true)
+ {
+ xmlreader::XmlReader::Result res
+ = rReader.nextItem(xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ if (name == "entry")
+ {
+ DriverInfo aDriver;
+ handleEntry(aDriver, rReader);
+ mrDriverList.push_back(aDriver);
+ }
+ else if (name == "entryRange")
+ {
+ DriverInfo aDriver;
+ handleEntry(aDriver, rReader);
+ mrDriverList.push_back(aDriver);
+ }
+ else
+ {
+ throw InvalidFileException();
+ }
+ }
+ else if (res == xmlreader::XmlReader::Result::End)
+ {
+ break;
+ }
+ }
+}
+
+void Parser::handleContent(xmlreader::XmlReader& rReader)
+{
+ while (true)
+ {
+ xmlreader::Span name;
+ int nsId;
+
+ xmlreader::XmlReader::Result res
+ = rReader.nextItem(xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ if (name == "allowlist")
+ {
+ meBlockType = BlockType::ALLOWLIST;
+ handleList(rReader);
+ }
+ else if (name == "denylist")
+ {
+ meBlockType = BlockType::DENYLIST;
+ handleList(rReader);
+ }
+ else if (name == "root")
+ {
+ }
+ else
+ {
+ throw InvalidFileException();
+ }
+ }
+ else if (res == xmlreader::XmlReader::Result::End)
+ {
+ if (name == "allowlist" || name == "denylist")
+ {
+ meBlockType = BlockType::UNKNOWN;
+ }
+ }
+ else if (res == xmlreader::XmlReader::Result::Done)
+ {
+ break;
+ }
+ }
+}
+
+static OperatingSystem getOperatingSystem()
+{
+#ifdef _WIN32
+ // OS version in 16.16 major/minor form
+ // based on http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx
+ switch (DriverBlocklist::GetWindowsVersion())
+ {
+ case 0x00060001:
+ return DRIVER_OS_WINDOWS_7;
+ case 0x00060002:
+ return DRIVER_OS_WINDOWS_8;
+ case 0x00060003:
+ return DRIVER_OS_WINDOWS_8_1;
+ case 0x000A0000: // Major 10 Minor 0
+ return DRIVER_OS_WINDOWS_10;
+ default:
+ return DRIVER_OS_UNKNOWN;
+ }
+#elif defined LINUX
+ return DRIVER_OS_LINUX;
+#else
+ return DRIVER_OS_UNKNOWN;
+#endif
+}
+
+namespace
+{
+struct compareIgnoreAsciiCase
+{
+ explicit compareIgnoreAsciiCase(const OUString& rString)
+ : maString(rString)
+ {
+ }
+
+ bool operator()(std::u16string_view rCompare)
+ {
+ return maString.equalsIgnoreAsciiCase(rCompare);
+ }
+
+private:
+ OUString maString;
+};
+}
+
+const uint64_t allDriverVersions = ~(uint64_t(0));
+
+DriverInfo::DriverInfo()
+ : meOperatingSystem(DRIVER_OS_UNKNOWN)
+ , maAdapterVendor(GetVendorId(VendorAll))
+ , mbAllowlisted(false)
+ , meComparisonOp(DRIVER_COMPARISON_IGNORED)
+ , mnDriverVersion(0)
+ , mnDriverVersionMax(0)
+{
+}
+
+DriverInfo::DriverInfo(OperatingSystem os, const OUString& vendor, VersionComparisonOp op,
+ uint64_t driverVersion, bool bAllowlisted,
+ const char* suggestedVersion /* = nullptr */)
+ : meOperatingSystem(os)
+ , maAdapterVendor(vendor)
+ , mbAllowlisted(bAllowlisted)
+ , meComparisonOp(op)
+ , mnDriverVersion(driverVersion)
+ , mnDriverVersionMax(0)
+{
+ if (suggestedVersion)
+ maSuggestedVersion
+ = OStringToOUString(std::string_view(suggestedVersion), RTL_TEXTENCODING_UTF8);
+}
+
+bool FindBlocklistedDeviceInList(std::vector<DriverInfo>& aDeviceInfos, VersionType versionType,
+ std::u16string_view sDriverVersion,
+ std::u16string_view sAdapterVendorID,
+ OUString const& sAdapterDeviceID, OperatingSystem system,
+ const OUString& blocklistURL)
+{
+ uint64_t driverVersion;
+ ParseDriverVersion(sDriverVersion, driverVersion, versionType);
+
+ bool match = false;
+ for (std::vector<DriverInfo>::size_type i = 0; i < aDeviceInfos.size(); i++)
+ {
+ bool osMatch = false;
+ if (aDeviceInfos[i].meOperatingSystem == DRIVER_OS_ALL)
+ osMatch = true;
+ else if (aDeviceInfos[i].meOperatingSystem == system)
+ osMatch = true;
+ else if (aDeviceInfos[i].meOperatingSystem == DRIVER_OS_WINDOWS_ALL
+ && system >= DRIVER_OS_WINDOWS_FIRST && system <= DRIVER_OS_WINDOWS_LAST)
+ osMatch = true;
+ else if (aDeviceInfos[i].meOperatingSystem == DRIVER_OS_OSX_ALL
+ && system >= DRIVER_OS_OSX_FIRST && system <= DRIVER_OS_OSX_LAST)
+ osMatch = true;
+ if (!osMatch)
+ {
+ continue;
+ }
+
+ if (!aDeviceInfos[i].maAdapterVendor.equalsIgnoreAsciiCase(GetVendorId(VendorAll))
+ && !aDeviceInfos[i].maAdapterVendor.equalsIgnoreAsciiCase(sAdapterVendorID))
+ {
+ continue;
+ }
+
+ if (std::none_of(aDeviceInfos[i].maDevices.begin(), aDeviceInfos[i].maDevices.end(),
+ compareIgnoreAsciiCase("all"))
+ && std::none_of(aDeviceInfos[i].maDevices.begin(), aDeviceInfos[i].maDevices.end(),
+ compareIgnoreAsciiCase(sAdapterDeviceID)))
+ {
+ continue;
+ }
+
+ switch (aDeviceInfos[i].meComparisonOp)
+ {
+ case DRIVER_LESS_THAN:
+ match = driverVersion < aDeviceInfos[i].mnDriverVersion;
+ break;
+ case DRIVER_LESS_THAN_OR_EQUAL:
+ match = driverVersion <= aDeviceInfos[i].mnDriverVersion;
+ break;
+ case DRIVER_GREATER_THAN:
+ match = driverVersion > aDeviceInfos[i].mnDriverVersion;
+ break;
+ case DRIVER_GREATER_THAN_OR_EQUAL:
+ match = driverVersion >= aDeviceInfos[i].mnDriverVersion;
+ break;
+ case DRIVER_EQUAL:
+ match = driverVersion == aDeviceInfos[i].mnDriverVersion;
+ break;
+ case DRIVER_NOT_EQUAL:
+ match = driverVersion != aDeviceInfos[i].mnDriverVersion;
+ break;
+ case DRIVER_BETWEEN_EXCLUSIVE:
+ match = driverVersion > aDeviceInfos[i].mnDriverVersion
+ && driverVersion < aDeviceInfos[i].mnDriverVersionMax;
+ break;
+ case DRIVER_BETWEEN_INCLUSIVE:
+ match = driverVersion >= aDeviceInfos[i].mnDriverVersion
+ && driverVersion <= aDeviceInfos[i].mnDriverVersionMax;
+ break;
+ case DRIVER_BETWEEN_INCLUSIVE_START:
+ match = driverVersion >= aDeviceInfos[i].mnDriverVersion
+ && driverVersion < aDeviceInfos[i].mnDriverVersionMax;
+ break;
+ case DRIVER_COMPARISON_IGNORED:
+ // We don't have a comparison op, so we match everything.
+ match = true;
+ break;
+ default:
+ SAL_WARN("vcl.driver", "Bogus op in " << blocklistURL);
+ break;
+ }
+
+ if (match || aDeviceInfos[i].mnDriverVersion == allDriverVersions)
+ {
+ // white listed drivers
+ if (aDeviceInfos[i].mbAllowlisted)
+ {
+ SAL_INFO("vcl.driver", "allowlisted driver");
+ return false;
+ }
+
+ match = true;
+ if (!aDeviceInfos[i].maSuggestedVersion.isEmpty())
+ {
+ SAL_WARN("vcl.driver", "use : " << aDeviceInfos[i].maSuggestedVersion);
+ }
+ break;
+ }
+ }
+
+ SAL_INFO("vcl.driver", (match ? "denylisted" : "not denylisted") << " in " << blocklistURL);
+ return match;
+}
+
+bool IsDeviceBlocked(const OUString& blocklistURL, VersionType versionType,
+ std::u16string_view driverVersion, std::u16string_view vendorId,
+ const OUString& deviceId)
+{
+ std::vector<DriverInfo> driverList;
+ Parser parser(blocklistURL, driverList, versionType);
+ if (!parser.parse())
+ {
+ SAL_WARN("vcl.driver", "error parsing denylist " << blocklistURL);
+ return false;
+ }
+ return FindBlocklistedDeviceInList(driverList, versionType, driverVersion, vendorId, deviceId,
+ getOperatingSystem(), blocklistURL);
+}
+
+#ifdef _WIN32
+int32_t GetWindowsVersion()
+{
+ static int32_t winVersion = []() {
+ // GetVersion(Ex) and VersionHelpers (based on VerifyVersionInfo) API are
+ // subject to manifest-based behavior since Windows 8.1, so give wrong results.
+ // Another approach would be to use NetWkstaGetInfo, but that has some small
+ // reported delays (some milliseconds), and might get slower in domains with
+ // poor network connections.
+ // So go with a solution described at https://msdn.microsoft.com/en-us/library/ms724429
+ HINSTANCE hLibrary = LoadLibraryW(L"kernel32.dll");
+ if (hLibrary != nullptr)
+ {
+ wchar_t szPath[MAX_PATH];
+ DWORD dwCount = GetModuleFileNameW(hLibrary, szPath, SAL_N_ELEMENTS(szPath));
+ FreeLibrary(hLibrary);
+ if (dwCount != 0 && dwCount < SAL_N_ELEMENTS(szPath))
+ {
+ dwCount = GetFileVersionInfoSizeW(szPath, nullptr);
+ if (dwCount != 0)
+ {
+ std::unique_ptr<char[]> ver(new char[dwCount]);
+ if (GetFileVersionInfoW(szPath, 0, dwCount, ver.get()) != FALSE)
+ {
+ void* pBlock = nullptr;
+ UINT dwBlockSz = 0;
+ if (VerQueryValueW(ver.get(), L"\\", &pBlock, &dwBlockSz) != FALSE
+ && dwBlockSz >= sizeof(VS_FIXEDFILEINFO))
+ {
+ VS_FIXEDFILEINFO* vinfo = static_cast<VS_FIXEDFILEINFO*>(pBlock);
+ return int32_t(vinfo->dwProductVersionMS);
+ }
+ }
+ }
+ }
+ }
+ return 0;
+ }();
+
+ return winVersion;
+}
+#endif
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/errcode.cxx b/vcl/source/helper/errcode.cxx
new file mode 100644
index 000000000..d50bf522c
--- /dev/null
+++ b/vcl/source/helper/errcode.cxx
@@ -0,0 +1,147 @@
+/* -*- 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 <vcl/errcode.hxx>
+
+VCL_DLLPUBLIC std::ostream& operator<<(std::ostream& os, const ErrCode& err)
+{
+ os << err.toHexString() << "(" << (err.IsWarning() ? "Warning" : "Error");
+ if (err.IsDynamic())
+ os << " Dynamic";
+ else
+ {
+ os << " Area:";
+ switch (err.GetArea())
+ {
+ case ErrCodeArea::Io:
+ os << "Io";
+ break;
+ case ErrCodeArea::Sfx:
+ os << "Sfx";
+ break;
+ case ErrCodeArea::Inet:
+ os << "Inet";
+ break;
+ case ErrCodeArea::Vcl:
+ os << "Vcl";
+ break;
+ case ErrCodeArea::Svx:
+ os << "Svx";
+ break;
+ case ErrCodeArea::So:
+ os << "So";
+ break;
+ case ErrCodeArea::Sbx:
+ os << "Sbx";
+ break;
+ case ErrCodeArea::Uui:
+ os << "Uui";
+ break;
+ case ErrCodeArea::Sc:
+ os << "Sc";
+ break;
+ case ErrCodeArea::Sd:
+ os << "Sd";
+ break;
+ case ErrCodeArea::Sw:
+ os << "Sw";
+ break;
+ default:
+ os << "Unknown";
+ }
+ os << " Class:";
+ switch (err.GetClass())
+ {
+ case ErrCodeClass::NONE:
+ os << "NONE";
+ break;
+ case ErrCodeClass::Abort:
+ os << "Abort";
+ break;
+ case ErrCodeClass::General:
+ os << "General";
+ break;
+ case ErrCodeClass::NotExists:
+ os << "NotExists";
+ break;
+ case ErrCodeClass::AlreadyExists:
+ os << "AlreadyExists";
+ break;
+ case ErrCodeClass::Access:
+ os << "Access";
+ break;
+ case ErrCodeClass::Path:
+ os << "Path";
+ break;
+ case ErrCodeClass::Locking:
+ os << "Locking";
+ break;
+ case ErrCodeClass::Parameter:
+ os << "Parameter";
+ break;
+ case ErrCodeClass::Space:
+ os << "Space";
+ break;
+ case ErrCodeClass::NotSupported:
+ os << "NotSupported";
+ break;
+ case ErrCodeClass::Read:
+ os << "Read";
+ break;
+ case ErrCodeClass::Write:
+ os << "Write";
+ break;
+ case ErrCodeClass::Unknown:
+ os << "Unknown";
+ break;
+ case ErrCodeClass::Version:
+ os << "Version";
+ break;
+ case ErrCodeClass::Format:
+ os << "Format";
+ break;
+ case ErrCodeClass::Create:
+ os << "Create";
+ break;
+ case ErrCodeClass::Import:
+ os << "Import";
+ break;
+ case ErrCodeClass::Export:
+ os << "Export";
+ break;
+ case ErrCodeClass::So:
+ os << "So";
+ break;
+ case ErrCodeClass::Sbx:
+ os << "Sbx";
+ break;
+ case ErrCodeClass::Runtime:
+ os << "Runtime";
+ break;
+ case ErrCodeClass::Compiler:
+ os << "Compiler";
+ break;
+ }
+ os << " Code:" << OUString::number(err.GetCode());
+ }
+ os << ")";
+ return os;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/evntpost.cxx b/vcl/source/helper/evntpost.cxx
new file mode 100644
index 000000000..9e2106f49
--- /dev/null
+++ b/vcl/source/helper/evntpost.cxx
@@ -0,0 +1,57 @@
+/* -*- 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 <tools/debug.hxx>
+#include <vcl/evntpost.hxx>
+#include <vcl/svapp.hxx>
+
+namespace vcl
+{
+
+EventPoster::EventPoster( const Link<LinkParamNone*,void>& rLink )
+ : m_aLink(rLink)
+{
+ m_nId = nullptr;
+}
+
+EventPoster::~EventPoster()
+{
+ DBG_TESTSOLARMUTEX();
+ if ( m_nId )
+ Application::RemoveUserEvent( m_nId );
+}
+
+void EventPoster::Post()
+{
+ DBG_TESTSOLARMUTEX();
+ m_nId = Application::PostUserEvent( LINK( this, EventPoster, DoEvent_Impl ) );
+}
+
+IMPL_LINK( EventPoster, DoEvent_Impl, void*, /*p*/, void )
+{
+ DBG_TESTSOLARMUTEX();
+ m_nId = nullptr;
+ m_aLink.Call( nullptr );
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/lazydelete.cxx b/vcl/source/helper/lazydelete.cxx
new file mode 100644
index 000000000..6d39e9765
--- /dev/null
+++ b/vcl/source/helper/lazydelete.cxx
@@ -0,0 +1,58 @@
+/* -*- 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 <vcl/lazydelete.hxx>
+#include <svdata.hxx>
+#include <sal/log.hxx>
+
+namespace vcl
+{
+DeleteOnDeinitBase::~DeleteOnDeinitBase()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if (!pSVData)
+ return;
+ auto& rList = pSVData->maDeinitDeleteList;
+ rList.erase(std::remove(rList.begin(), rList.end(), this), rList.end());
+}
+
+void DeleteOnDeinitBase::addDeinitContainer(DeleteOnDeinitBase* i_pContainer)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ SAL_WARN_IF(pSVData->mbDeInit, "vcl", "DeleteOnDeinit added after DeiInitVCL !");
+ if (pSVData->mbDeInit)
+ return;
+
+ pSVData->maDeinitDeleteList.push_back(i_pContainer);
+}
+
+void DeleteOnDeinitBase::ImplDeleteOnDeInit()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ for (auto const& deinitDelete : pSVData->maDeinitDeleteList)
+ {
+ deinitDelete->doCleanup();
+ }
+ pSVData->maDeinitDeleteList.clear();
+}
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/strhelper.cxx b/vcl/source/helper/strhelper.cxx
new file mode 100644
index 000000000..9d05b2c98
--- /dev/null
+++ b/vcl/source/helper/strhelper.cxx
@@ -0,0 +1,380 @@
+/* -*- 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 <strhelper.hxx>
+
+namespace {
+
+bool isSpace( sal_Unicode cChar )
+{
+ return
+ cChar == ' ' || cChar == '\t' ||
+ cChar == '\r' || cChar == '\n' ||
+ cChar == 0x0c || cChar == 0x0b;
+}
+
+bool isProtect( sal_Unicode cChar )
+{
+ return cChar == '`' || cChar == '\'' || cChar == '"';
+}
+
+void CopyUntil( char*& pTo, const char*& pFrom, char cUntil, bool bIncludeUntil = false )
+{
+ do
+ {
+ if( *pFrom == '\\' )
+ {
+ pFrom++;
+ if( *pFrom )
+ {
+ *pTo = *pFrom;
+ pTo++;
+ }
+ }
+ else if( bIncludeUntil || ! isProtect( *pFrom ) )
+ {
+ *pTo = *pFrom;
+ pTo++;
+ }
+ pFrom++;
+ } while( *pFrom && *pFrom != cUntil );
+ // copy the terminating character unless zero or protector
+ if( ! isProtect( *pFrom ) || bIncludeUntil )
+ {
+ *pTo = *pFrom;
+ if( *pTo )
+ pTo++;
+ }
+ if( *pFrom )
+ pFrom++;
+}
+
+void CopyUntil( sal_Unicode*& pTo, const sal_Unicode*& pFrom, sal_Unicode cUntil, bool bIncludeUntil = false )
+{
+ do
+ {
+ if( *pFrom == '\\' )
+ {
+ pFrom++;
+ if( *pFrom )
+ {
+ *pTo = *pFrom;
+ pTo++;
+ }
+ }
+ else if( bIncludeUntil || ! isProtect( *pFrom ) )
+ {
+ *pTo = *pFrom;
+ pTo++;
+ }
+ if( *pFrom )
+ pFrom++;
+ } while( *pFrom && *pFrom != cUntil );
+ // copy the terminating character unless zero or protector
+ if( ! isProtect( *pFrom ) || bIncludeUntil )
+ {
+ *pTo = *pFrom;
+ if( *pTo )
+ pTo++;
+ }
+ if( *pFrom )
+ pFrom++;
+}
+
+}
+
+namespace psp {
+
+OUString GetCommandLineToken( int nToken, const OUString& rLine )
+{
+ sal_Int32 nLen = rLine.getLength();
+ if( ! nLen )
+ return OUString();
+
+ int nActualToken = 0;
+ sal_Unicode* pBuffer = static_cast<sal_Unicode*>(alloca( sizeof(sal_Unicode)*( nLen + 1 ) ));
+ const sal_Unicode* pRun = rLine.getStr();
+ sal_Unicode* pLeap = nullptr;
+
+ while( *pRun && nActualToken <= nToken )
+ {
+ while( *pRun && isSpace( *pRun ) )
+ pRun++;
+ pLeap = pBuffer;
+ while( *pRun && ! isSpace( *pRun ) )
+ {
+ if( *pRun == '\\' )
+ {
+ // escapement
+ pRun++;
+ *pLeap = *pRun;
+ pLeap++;
+ if( *pRun )
+ pRun++;
+ }
+ else if( *pRun == '`' )
+ CopyUntil( pLeap, pRun, '`' );
+ else if( *pRun == '\'' )
+ CopyUntil( pLeap, pRun, '\'' );
+ else if( *pRun == '"' )
+ CopyUntil( pLeap, pRun, '"' );
+ else
+ {
+ *pLeap = *pRun;
+ pLeap++;
+ pRun++;
+ }
+ }
+ if( nActualToken != nToken )
+ pBuffer[0] = 0;
+ nActualToken++;
+ }
+
+ *pLeap = 0;
+
+ return OUString(pBuffer);
+}
+
+OString GetCommandLineToken(int nToken, const OString& rLine)
+{
+ sal_Int32 nLen = rLine.getLength();
+ if (!nLen)
+ return rLine;
+
+ int nActualToken = 0;
+ char* pBuffer = static_cast<char*>(alloca( nLen + 1 ));
+ const char* pRun = rLine.getStr();
+ char* pLeap = nullptr;
+
+ while( *pRun && nActualToken <= nToken )
+ {
+ while( *pRun && isSpace( *pRun ) )
+ pRun++;
+ pLeap = pBuffer;
+ while( *pRun && ! isSpace( *pRun ) )
+ {
+ if( *pRun == '\\' )
+ {
+ // escapement
+ pRun++;
+ *pLeap = *pRun;
+ pLeap++;
+ if( *pRun )
+ pRun++;
+ }
+ else if( *pRun == '`' )
+ CopyUntil( pLeap, pRun, '`' );
+ else if( *pRun == '\'' )
+ CopyUntil( pLeap, pRun, '\'' );
+ else if( *pRun == '"' )
+ CopyUntil( pLeap, pRun, '"' );
+ else
+ {
+ *pLeap = *pRun;
+ pLeap++;
+ pRun++;
+ }
+ }
+ if( nActualToken != nToken )
+ pBuffer[0] = 0;
+ nActualToken++;
+ }
+
+ *pLeap = 0;
+
+ return pBuffer;
+}
+
+int GetCommandLineTokenCount(const OUString& rLine)
+{
+ if (rLine.isEmpty())
+ return 0;
+
+ int nTokenCount = 0;
+ const sal_Unicode *pRun = rLine.getStr();
+
+ while( *pRun )
+ {
+ while( *pRun && isSpace( *pRun ) )
+ pRun++;
+ if( ! *pRun )
+ break;
+ while( *pRun && ! isSpace( *pRun ) )
+ {
+ if( *pRun == '\\' )
+ {
+ // escapement
+ pRun++;
+ if( *pRun )
+ pRun++;
+ }
+ else if( *pRun == '`' )
+ {
+ do pRun++; while( *pRun && *pRun != '`' );
+ if( *pRun )
+ pRun++;
+ }
+ else if( *pRun == '\'' )
+ {
+ do pRun++; while( *pRun && *pRun != '\'' );
+ if( *pRun )
+ pRun++;
+ }
+ else if( *pRun == '"' )
+ {
+ do pRun++; while( *pRun && *pRun != '"' );
+ if( *pRun )
+ pRun++;
+ }
+ else
+ pRun++;
+ }
+ nTokenCount++;
+ }
+
+ return nTokenCount;
+}
+
+OUString WhitespaceToSpace( std::u16string_view rLine, bool bProtect )
+{
+ size_t nLen = rLine.size();
+ if( ! nLen )
+ return OUString();
+
+ sal_Unicode *pBuffer = static_cast<sal_Unicode*>(alloca( sizeof(sal_Unicode)*(nLen + 1) ));
+ const sal_Unicode *pRun = rLine.data();
+ const sal_Unicode * const pEnd = rLine.data() + rLine.size();
+ sal_Unicode *pLeap = pBuffer;
+
+ while( pRun != pEnd )
+ {
+ if( pRun != pEnd && isSpace( *pRun ) )
+ {
+ *pLeap = ' ';
+ pLeap++;
+ pRun++;
+ }
+ while( pRun != pEnd && isSpace( *pRun ) )
+ pRun++;
+ while( pRun != pEnd && ! isSpace( *pRun ) )
+ {
+ if( *pRun == '\\' )
+ {
+ // escapement
+ pRun++;
+ *pLeap = *pRun;
+ pLeap++;
+ if( pRun != pEnd )
+ pRun++;
+ }
+ else if( bProtect && *pRun == '`' )
+ CopyUntil( pLeap, pRun, '`', true );
+ else if( bProtect && *pRun == '\'' )
+ CopyUntil( pLeap, pRun, '\'', true );
+ else if( bProtect && *pRun == '"' )
+ CopyUntil( pLeap, pRun, '"', true );
+ else
+ {
+ *pLeap = *pRun;
+ ++pLeap;
+ ++pRun;
+ }
+ }
+ }
+
+ *pLeap = 0;
+
+ // there might be a space at beginning or end
+ if (pLeap > pBuffer)
+ {
+ pLeap--;
+ if( *pLeap == ' ' )
+ *pLeap = 0;
+ }
+
+ return OUString(*pBuffer == ' ' ? pBuffer+1 : pBuffer);
+}
+
+OString WhitespaceToSpace(std::string_view rLine)
+{
+ size_t nLen = rLine.size();
+ if (!nLen)
+ return OString();
+
+ char *pBuffer = static_cast<char*>(alloca( nLen + 1 ));
+ const char *pRun = rLine.data();
+ const char * const pEnd = rLine.data() + rLine.size();
+ char *pLeap = pBuffer;
+
+ while( pRun != pEnd )
+ {
+ if( pRun != pEnd && isSpace( *pRun ) )
+ {
+ *pLeap = ' ';
+ pLeap++;
+ pRun++;
+ }
+ while( pRun != pEnd && isSpace( *pRun ) )
+ pRun++;
+ while( pRun != pEnd && ! isSpace( *pRun ) )
+ {
+ if( *pRun == '\\' )
+ {
+ // escapement
+ pRun++;
+ *pLeap = *pRun;
+ pLeap++;
+ if( pRun != pEnd )
+ pRun++;
+ }
+ else if( *pRun == '`' )
+ CopyUntil( pLeap, pRun, '`', true );
+ else if( *pRun == '\'' )
+ CopyUntil( pLeap, pRun, '\'', true );
+ else if( *pRun == '"' )
+ CopyUntil( pLeap, pRun, '"', true );
+ else
+ {
+ *pLeap = *pRun;
+ ++pLeap;
+ ++pRun;
+ }
+ }
+ }
+
+ *pLeap = 0;
+
+ // there might be a space at beginning or end
+ assert(pLeap > pBuffer);
+ pLeap--;
+#if defined(__GNUC__) && __GNUC__ == 12
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
+ if( *pLeap == ' ' )
+ *pLeap = 0;
+#if defined(__GNUC__) && __GNUC__ == 12
+#pragma GCC diagnostic pop
+#endif
+ return *pBuffer == ' ' ? pBuffer+1 : pBuffer;
+}
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/svtaccessiblefactory.cxx b/vcl/source/helper/svtaccessiblefactory.cxx
new file mode 100644
index 000000000..f6728732a
--- /dev/null
+++ b/vcl/source/helper/svtaccessiblefactory.cxx
@@ -0,0 +1,289 @@
+/* -*- 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 <config_feature_desktop.h>
+#include <config_wasm_strip.h>
+
+#include <vcl/svtaccessiblefactory.hxx>
+#include <vcl/accessiblefactory.hxx>
+
+#include <tools/svlibrary.h>
+#include <tools/debug.hxx>
+
+#include <osl/module.h>
+#include <osl/diagnose.h>
+#include <rtl/ref.hxx>
+
+namespace vcl
+{
+ using namespace ::com::sun::star::uno;
+ using namespace ::com::sun::star::awt;
+ using namespace ::com::sun::star::accessibility;
+
+ namespace
+ {
+#ifndef DISABLE_DYNLOADING
+ oslModule s_hAccessibleImplementationModule = nullptr;
+#endif
+#if HAVE_FEATURE_DESKTOP
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+ GetSvtAccessibilityComponentFactory s_pAccessibleFactoryFunc = nullptr;
+#endif
+#endif
+ ::rtl::Reference< IAccessibleFactory > s_pFactory;
+
+
+ //= AccessibleDummyFactory
+
+ class AccessibleDummyFactory:
+ public IAccessibleFactory
+ {
+ public:
+ AccessibleDummyFactory();
+ AccessibleDummyFactory(const AccessibleDummyFactory&) = delete;
+ AccessibleDummyFactory& operator=(const AccessibleDummyFactory&) = delete;
+
+ protected:
+ virtual ~AccessibleDummyFactory() override;
+
+ public:
+ // IAccessibleFactory
+ virtual vcl::IAccessibleTabListBox*
+ createAccessibleTabListBox(
+ const css::uno::Reference< css::accessibility::XAccessible >& /*rxParent*/,
+ SvHeaderTabListBox& /*rBox*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual css::uno::Reference< css::accessibility::XAccessible >
+ createAccessibleTreeListBox(
+ SvTreeListBox& /*_rListBox*/,
+ const css::uno::Reference< css::accessibility::XAccessible >& /*_xParent*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual css::uno::Reference< css::accessibility::XAccessible >
+ createAccessibleIconView(
+ SvTreeListBox& /*_rListBox*/,
+ const css::uno::Reference< css::accessibility::XAccessible >& /*_xParent*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual vcl::IAccessibleBrowseBox*
+ createAccessibleBrowseBox(
+ const css::uno::Reference< css::accessibility::XAccessible >& /*_rxParent*/,
+ vcl::IAccessibleTableProvider& /*_rBrowseBox*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual table::IAccessibleTableControl*
+ createAccessibleTableControl(
+ const css::uno::Reference< css::accessibility::XAccessible >& /*_rxParent*/,
+ table::IAccessibleTable& /*_rTable*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual css::uno::Reference< css::accessibility::XAccessible >
+ createAccessibleIconChoiceCtrl(
+ SvtIconChoiceCtrl& /*_rIconCtrl*/,
+ const css::uno::Reference< css::accessibility::XAccessible >& /*_xParent*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual css::uno::Reference< css::accessibility::XAccessible >
+ createAccessibleTabBar(
+ TabBar& /*_rTabBar*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual css::uno::Reference< css::accessibility::XAccessibleContext >
+ createAccessibleTextWindowContext(
+ VCLXWindow* /*pVclXWindow*/, TextEngine& /*rEngine*/, TextView& /*rView*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual css::uno::Reference< css::accessibility::XAccessible >
+ createAccessibleBrowseBoxHeaderBar(
+ const css::uno::Reference< css::accessibility::XAccessible >& /*rxParent*/,
+ vcl::IAccessibleTableProvider& /*_rOwningTable*/,
+ AccessibleBrowseBoxObjType /*_eObjType*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual css::uno::Reference< css::accessibility::XAccessible >
+ createAccessibleBrowseBoxTableCell(
+ const css::uno::Reference< css::accessibility::XAccessible >& /*_rxParent*/,
+ vcl::IAccessibleTableProvider& /*_rBrowseBox*/,
+ const css::uno::Reference< css::awt::XWindow >& /*_xFocusWindow*/,
+ sal_Int32 /*_nRowId*/,
+ sal_uInt16 /*_nColId*/,
+ sal_Int32 /*_nOffset*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual css::uno::Reference< css::accessibility::XAccessible >
+ createAccessibleBrowseBoxHeaderCell(
+ sal_Int32 /*_nColumnRowId*/,
+ const css::uno::Reference< css::accessibility::XAccessible >& /*rxParent*/,
+ vcl::IAccessibleTableProvider& /*_rBrowseBox*/,
+ const css::uno::Reference< css::awt::XWindow >& /*_xFocusWindow*/,
+ AccessibleBrowseBoxObjType /*_eObjType*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual css::uno::Reference< css::accessibility::XAccessible >
+ createAccessibleCheckBoxCell(
+ const css::uno::Reference< css::accessibility::XAccessible >& /*_rxParent*/,
+ vcl::IAccessibleTableProvider& /*_rBrowseBox*/,
+ const css::uno::Reference< css::awt::XWindow >& /*_xFocusWindow*/,
+ sal_Int32 /*_nRowPos*/,
+ sal_uInt16 /*_nColPos*/,
+ const TriState& /*_eState*/,
+ bool /*_bIsTriState*/
+ ) const override
+ {
+ return nullptr;
+ }
+
+ virtual css::uno::Reference< css::accessibility::XAccessible >
+ createEditBrowseBoxTableCellAccess(
+ const css::uno::Reference< css::accessibility::XAccessible >& /*_rxParent*/,
+ const css::uno::Reference< css::accessibility::XAccessible >& /*_rxControlAccessible*/,
+ const css::uno::Reference< css::awt::XWindow >& /*_rxFocusWindow*/,
+ vcl::IAccessibleTableProvider& /*_rBrowseBox*/,
+ sal_Int32 /*_nRowPos*/,
+ sal_uInt16 /*_nColPos*/
+ ) const override
+ {
+ return nullptr;
+ }
+ };
+
+
+ AccessibleDummyFactory::AccessibleDummyFactory()
+ {
+ }
+
+
+ AccessibleDummyFactory::~AccessibleDummyFactory()
+ {
+ }
+
+ }
+
+
+ //= AccessibleFactoryAccess
+
+
+ AccessibleFactoryAccess::AccessibleFactoryAccess()
+ :m_bInitialized( false )
+ {
+ }
+
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+#if HAVE_FEATURE_DESKTOP
+#ifndef DISABLE_DYNLOADING
+ extern "C" { static void thisModule() {} }
+#else
+ extern "C" void* getSvtAccessibilityComponentFactory();
+#endif
+#endif // HAVE_FEATURE_DESKTOP
+#endif // ENABLE_WASM_STRIP_ACCESSIBILITY
+
+ void AccessibleFactoryAccess::ensureInitialized()
+ {
+ if ( m_bInitialized )
+ return;
+
+ ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() );
+
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+#if HAVE_FEATURE_DESKTOP
+ // load the library implementing the factory
+ if (!s_pFactory)
+ {
+#ifndef DISABLE_DYNLOADING
+ const OUString sModuleName( SVLIBRARY( "acc" ));
+ s_hAccessibleImplementationModule = osl_loadModuleRelative( &thisModule, sModuleName.pData, 0 );
+ if ( s_hAccessibleImplementationModule != nullptr )
+ {
+ const OUString sFactoryCreationFunc( "getSvtAccessibilityComponentFactory" );
+ s_pAccessibleFactoryFunc = reinterpret_cast<GetSvtAccessibilityComponentFactory>(
+ osl_getFunctionSymbol( s_hAccessibleImplementationModule, sFactoryCreationFunc.pData ));
+
+ }
+ OSL_ENSURE( s_pAccessibleFactoryFunc, "ac_registerClient: could not load the library, or not retrieve the needed symbol!" );
+#else
+ s_pAccessibleFactoryFunc = getSvtAccessibilityComponentFactory;
+#endif // DISABLE_DYNLOADING
+
+ // get a factory instance
+ if ( s_pAccessibleFactoryFunc )
+ {
+ IAccessibleFactory* pFactory = static_cast< IAccessibleFactory* >( (*s_pAccessibleFactoryFunc)() );
+ if ( pFactory )
+ {
+ s_pFactory = pFactory;
+ pFactory->release();
+ }
+ }
+ }
+#endif // HAVE_FEATURE_DESKTOP
+#endif // ENABLE_WASM_STRIP_ACCESSIBILITY
+
+ if (!s_pFactory)
+ // the attempt to load the lib, or to create the factory, failed
+ // -> fall back to a dummy factory
+ s_pFactory = new AccessibleDummyFactory;
+
+ m_bInitialized = true;
+ }
+
+ IAccessibleFactory& AccessibleFactoryAccess::getFactory()
+ {
+ ensureInitialized();
+ DBG_ASSERT( s_pFactory.is(), "AccessibleFactoryAccess::getFactory: at least a dummy factory should have been created!" );
+ return *s_pFactory;
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/helper/threadex.cxx b/vcl/source/helper/threadex.cxx
new file mode 100644
index 000000000..cbd342bd2
--- /dev/null
+++ b/vcl/source/helper/threadex.cxx
@@ -0,0 +1,68 @@
+/* -*- 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 <vcl/threadex.hxx>
+#include <vcl/svapp.hxx>
+
+using namespace vcl;
+
+SolarThreadExecutor::SolarThreadExecutor()
+ : m_bTimeout(false)
+{
+}
+
+SolarThreadExecutor::~SolarThreadExecutor() {}
+
+IMPL_LINK_NOARG(SolarThreadExecutor, worker, void*, void)
+{
+ if (!m_bTimeout)
+ {
+ m_aStart.set();
+ doIt();
+ m_aFinish.set();
+ }
+}
+
+void SolarThreadExecutor::execute()
+{
+ if (Application::IsMainThread())
+ {
+ m_aStart.set();
+ doIt();
+ m_aFinish.set();
+ }
+ else
+ {
+ m_aStart.reset();
+ m_aFinish.reset();
+ ImplSVEvent* nEvent = Application::PostUserEvent(LINK(this, SolarThreadExecutor, worker));
+ SolarMutexReleaser aReleaser;
+ if (m_aStart.wait() == osl::Condition::result_timeout)
+ {
+ m_bTimeout = true;
+ Application::RemoveUserEvent(nEvent);
+ }
+ else
+ {
+ m_aFinish.wait();
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/image/Image.cxx b/vcl/source/image/Image.cxx
new file mode 100644
index 000000000..06937ab7f
--- /dev/null
+++ b/vcl/source/image/Image.cxx
@@ -0,0 +1,165 @@
+/* -*- 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 <vcl/settings.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/image.hxx>
+#include <sal/types.h>
+#include <image.h>
+
+#include <bitmap/BitmapColorizeFilter.hxx>
+
+using namespace css;
+
+Image::Image()
+{
+}
+
+Image::Image(const BitmapEx& rBitmapEx)
+{
+ ImplInit(rBitmapEx);
+}
+
+Image::Image(uno::Reference<graphic::XGraphic> const & rxGraphic)
+{
+ if (rxGraphic.is())
+ {
+ const Graphic aGraphic(rxGraphic);
+
+ OUString aPath;
+ if (aGraphic.getOriginURL().startsWith("private:graphicrepository/", &aPath))
+ mpImplData = std::make_shared<ImplImage>(aPath);
+ else if (aGraphic.GetType() == GraphicType::GdiMetafile)
+ mpImplData = std::make_shared<ImplImage>(aGraphic.GetGDIMetaFile());
+ else
+ ImplInit(aGraphic.GetBitmapEx());
+ }
+}
+
+Image::Image(const OUString & rFileUrl)
+{
+ OUString sImageName;
+ if (rFileUrl.startsWith("private:graphicrepository/", &sImageName))
+ mpImplData = std::make_shared<ImplImage>(sImageName);
+ else
+ {
+ Graphic aGraphic;
+ if (ERRCODE_NONE == GraphicFilter::LoadGraphic(rFileUrl, IMP_PNG, aGraphic))
+ ImplInit(aGraphic.GetBitmapEx());
+ }
+}
+
+Image::Image(StockImage, const OUString & rFileUrl)
+ : mpImplData(std::make_shared<ImplImage>(rFileUrl))
+{
+}
+
+void Image::ImplInit(const BitmapEx& rBitmapEx)
+{
+ if (!rBitmapEx.IsEmpty())
+ mpImplData = std::make_shared<ImplImage>(rBitmapEx);
+}
+
+OUString Image::GetStock() const
+{
+ if (mpImplData)
+ return mpImplData->getStock();
+ return OUString();
+}
+
+Size Image::GetSizePixel() const
+{
+ if (mpImplData)
+ return mpImplData->getSizePixel();
+ else
+ return Size();
+}
+
+BitmapEx Image::GetBitmapEx() const
+{
+ if (mpImplData)
+ return mpImplData->getBitmapEx();
+ else
+ return BitmapEx();
+}
+
+bool Image::operator==(const Image& rImage) const
+{
+ bool bRet = false;
+
+ if (rImage.mpImplData == mpImplData)
+ bRet = true;
+ else if (!rImage.mpImplData || !mpImplData)
+ bRet = false;
+ else
+ bRet = rImage.mpImplData->isEqual(*mpImplData);
+
+ return bRet;
+}
+
+void Image::Draw(OutputDevice* pOutDev, const Point& rPos, DrawImageFlags nStyle, const Size* pSize)
+{
+ if (!mpImplData || (!pOutDev->IsDeviceOutputNecessary() && pOutDev->GetConnectMetaFile() == nullptr))
+ return;
+
+ Size aOutSize = pSize ? *pSize : pOutDev->PixelToLogic(mpImplData->getSizePixel());
+
+ BitmapEx aRenderBmp = mpImplData->getBitmapExForHiDPI(bool(nStyle & DrawImageFlags::Disable), pOutDev->GetGraphics());
+
+ if (!(nStyle & DrawImageFlags::Disable) &&
+ (nStyle & (DrawImageFlags::ColorTransform | DrawImageFlags::Highlight |
+ DrawImageFlags::Deactive | DrawImageFlags::SemiTransparent)))
+ {
+ BitmapEx aTempBitmapEx(aRenderBmp);
+
+ if (nStyle & (DrawImageFlags::Highlight | DrawImageFlags::Deactive))
+ {
+ const StyleSettings& rSettings = pOutDev->GetSettings().GetStyleSettings();
+ Color aColor;
+ if (nStyle & DrawImageFlags::Highlight)
+ aColor = rSettings.GetHighlightColor();
+ else
+ aColor = rSettings.GetDeactiveColor();
+
+ BitmapFilter::Filter(aTempBitmapEx, BitmapColorizeFilter(aColor));
+ }
+
+ if (nStyle & DrawImageFlags::SemiTransparent)
+ {
+ if (aTempBitmapEx.IsAlpha())
+ {
+ Bitmap aAlphaBmp(aTempBitmapEx.GetAlpha().GetBitmap());
+ aAlphaBmp.Adjust(50);
+ aTempBitmapEx = BitmapEx(aTempBitmapEx.GetBitmap(), AlphaMask(aAlphaBmp));
+ }
+ else
+ {
+ sal_uInt8 cErase = 128;
+ aTempBitmapEx = BitmapEx(aTempBitmapEx.GetBitmap(), AlphaMask(aTempBitmapEx.GetSizePixel(), &cErase));
+ }
+ }
+ aRenderBmp = aTempBitmapEx;
+ }
+
+ pOutDev->DrawBitmapEx(rPos, aOutSize, aRenderBmp);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/image/ImageRepository.cxx b/vcl/source/image/ImageRepository.cxx
new file mode 100644
index 000000000..6bf57e699
--- /dev/null
+++ b/vcl/source/image/ImageRepository.cxx
@@ -0,0 +1,37 @@
+/* -*- 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 <vcl/bitmapex.hxx>
+#include <imagerepository.hxx>
+#include <vcl/ImageTree.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+
+namespace vcl
+{
+ bool ImageRepository::loadImage( const OUString& _rName, BitmapEx& _out_rImage )
+ {
+ OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+
+ return ImageTree::get().loadImage( _rName, sIconTheme, _out_rImage, false/*_bSearchLanguageDependent*/ );
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/image/ImageTree.cxx b/vcl/source/image/ImageTree.cxx
new file mode 100644
index 000000000..fdc47cbfe
--- /dev/null
+++ b/vcl/source/image/ImageTree.cxx
@@ -0,0 +1,67 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/ImageTree.hxx>
+#include <implimagetree.hxx>
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/container/XNameAccess.hpp>
+
+ImageTree & ImageTree::get() {
+ static ImageTree s_ImageTree;
+ return s_ImageTree;
+}
+
+ImageTree::ImageTree()
+ : mpImplImageTree(new ImplImageTree)
+{
+}
+
+OUString ImageTree::getImageUrl(OUString const & rName, OUString const & rStyle, OUString const & rLang)
+
+{
+ return mpImplImageTree->getImageUrl(rName, rStyle, rLang);
+}
+
+std::shared_ptr<SvMemoryStream> ImageTree::getImageStream(OUString const & rName, OUString const & rStyle, OUString const & rLang)
+{
+ return mpImplImageTree->getImageStream(rName, rStyle, rLang);
+}
+
+css::uno::Reference<css::io::XInputStream> ImageTree::getImageXInputStream(OUString const & rName, OUString const & rStyle, OUString const & rLang)
+{
+ return mpImplImageTree->getImageXInputStream(rName, rStyle, rLang);
+}
+
+bool ImageTree::loadImage(OUString const & rName, OUString const & rStyle,
+ BitmapEx & rBitmap, bool bLocalized,
+ sal_Int32 nScalePercentage,
+ const ImageLoadFlags eFlags)
+{
+ return mpImplImageTree->loadImage(rName, rStyle, rBitmap, bLocalized, eFlags, nScalePercentage);
+}
+
+bool ImageTree::loadImage(OUString const & rName, OUString const & rStyle,
+ BitmapEx & rBitmap, bool bLocalized,
+ const ImageLoadFlags eFlags)
+{
+ return loadImage(rName, rStyle, rBitmap, bLocalized, -1, eFlags);
+}
+
+css::uno::Reference<css::container::XNameAccess> const & ImageTree::getNameAccess()
+{
+ return mpImplImageTree->getNameAccess();
+}
+
+void ImageTree::shutdown()
+{
+ mpImplImageTree->shutdown();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/image/ImplImage.cxx b/vcl/source/image/ImplImage.cxx
new file mode 100644
index 000000000..756b8ddcd
--- /dev/null
+++ b/vcl/source/image/ImplImage.cxx
@@ -0,0 +1,176 @@
+/* -*- 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/log.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/BitmapFilter.hxx>
+#include <vcl/ImageTree.hxx>
+#include <bitmap/BitmapDisabledImageFilter.hxx>
+#include <comphelper/lok.hxx>
+
+#include <image.h>
+#include <salgdi.hxx>
+
+ImplImage::ImplImage(const BitmapEx &rBitmapEx)
+ : maBitmapChecksum(0)
+ , maSizePixel(rBitmapEx.GetSizePixel())
+ , maBitmapEx(rBitmapEx)
+{
+}
+
+ImplImage::ImplImage(const OUString &aStockName)
+ : maBitmapChecksum(0)
+ , maStockName(aStockName)
+{
+}
+
+ImplImage::ImplImage(const GDIMetaFile& rMetaFile)
+ : maBitmapChecksum(0)
+ , maSizePixel(rMetaFile.GetPrefSize())
+ , mxMetaFile(new GDIMetaFile(rMetaFile))
+{
+}
+
+bool ImplImage::loadStockAtScale(SalGraphics* pGraphics, BitmapEx &rBitmapEx)
+{
+ BitmapEx aBitmapEx;
+
+ ImageLoadFlags eScalingFlags = ImageLoadFlags::NONE;
+ sal_Int32 nScalePercentage = -1;
+
+ double fScale(1.0);
+ if (pGraphics && pGraphics->ShouldDownscaleIconsAtSurface(&fScale)) // scale at the surface
+ {
+ nScalePercentage = fScale * 100.0;
+ eScalingFlags = ImageLoadFlags::IgnoreScalingFactor;
+ }
+
+ OUString aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ if (!ImageTree::get().loadImage(maStockName, aIconTheme, aBitmapEx, true,
+ nScalePercentage, eScalingFlags))
+ {
+ /* If the uno command has parameters, passed in from a toolbar,
+ * recover from failure by removing the parameters from the file name
+ */
+ if (maStockName.indexOf("%3f") > 0)
+ {
+ sal_Int32 nStart = maStockName.indexOf("%3f");
+ sal_Int32 nEnd = maStockName.lastIndexOf(".");
+
+ OUString aFileName = maStockName.replaceAt(nStart, nEnd - nStart, u"");
+ if (!ImageTree::get().loadImage(aFileName, aIconTheme, aBitmapEx, true,
+ nScalePercentage, eScalingFlags))
+ {
+ SAL_WARN("vcl", "Failed to load scaled image from " << maStockName <<
+ " and " << aFileName << " at " << fScale);
+ return false;
+ }
+ }
+ else
+ {
+ SAL_WARN("vcl", "Failed to load scaled image from " << maStockName <<
+ " at " << fScale);
+ return false;
+ }
+ }
+ rBitmapEx = aBitmapEx;
+ return true;
+}
+
+Size ImplImage::getSizePixel()
+{
+ Size aRet;
+ if (!isSizeEmpty())
+ aRet = maSizePixel;
+ else if (isStock())
+ {
+ if (loadStockAtScale(nullptr, maBitmapEx))
+ {
+ assert(maDisabledBitmapEx.IsEmpty());
+ assert(maBitmapChecksum == 0);
+ maSizePixel = maBitmapEx.GetSizePixel();
+ aRet = maSizePixel;
+ }
+ else
+ SAL_WARN("vcl", "Failed to load stock icon " << maStockName);
+ }
+ return aRet;
+}
+
+/// non-HiDPI compatibility method.
+BitmapEx const & ImplImage::getBitmapEx(bool bDisabled)
+{
+ getSizePixel(); // force load, and at unity scale.
+ if (bDisabled)
+ {
+ // Changed since we last generated this.
+ BitmapChecksum aChecksum = maBitmapEx.GetChecksum();
+ if (maBitmapChecksum != aChecksum ||
+ maDisabledBitmapEx.GetSizePixel() != maBitmapEx.GetSizePixel())
+ {
+ maDisabledBitmapEx = maBitmapEx;
+ BitmapFilter::Filter(maDisabledBitmapEx, BitmapDisabledImageFilter());
+ maBitmapChecksum = aChecksum;
+ }
+ return maDisabledBitmapEx;
+ }
+
+ return maBitmapEx;
+}
+
+bool ImplImage::isEqual(const ImplImage &ref) const
+{
+ if (isStock() != ref.isStock())
+ return false;
+ if (isStock())
+ return maStockName == ref.maStockName;
+ else
+ return maBitmapEx == ref.maBitmapEx;
+}
+
+BitmapEx const & ImplImage::getBitmapExForHiDPI(bool bDisabled, SalGraphics* pGraphics)
+{
+ if ((isStock() || mxMetaFile) && pGraphics)
+ { // check we have the right bitmap cached.
+ double fScale = 1.0;
+ pGraphics->ShouldDownscaleIconsAtSurface(&fScale);
+ Size aTarget(maSizePixel.Width()*fScale,
+ maSizePixel.Height()*fScale);
+ if (maBitmapEx.GetSizePixel() != aTarget)
+ {
+ if (isStock())
+ loadStockAtScale(pGraphics, maBitmapEx);
+ else // if (mxMetaFile)
+ {
+ ScopedVclPtrInstance<VirtualDevice> aVDev(DeviceFormat::DEFAULT, DeviceFormat::DEFAULT);
+ aVDev->SetOutputSizePixel(aTarget);
+ mxMetaFile->WindStart();
+ mxMetaFile->Play(*aVDev, Point(), aTarget);
+ maBitmapEx = aVDev->GetBitmapEx(Point(), aTarget);
+ }
+ }
+ }
+ return getBitmapEx(bDisabled);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/image/ImplImageTree.cxx b/vcl/source/image/ImplImageTree.cxx
new file mode 100644
index 000000000..36ac5f4de
--- /dev/null
+++ b/vcl/source/image/ImplImageTree.cxx
@@ -0,0 +1,714 @@
+/* -*- 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 <config_folders.h>
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <deque>
+#include <string_view>
+
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/packages/zip/ZipFileAccess.hpp>
+#include <com/sun/star/ucb/SimpleFileAccess.hpp>
+#include <com/sun/star/uno/Exception.hpp>
+#include <com/sun/star/uno/RuntimeException.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <osl/file.hxx>
+#include <osl/diagnose.h>
+#include <osl/process.h>
+#include <rtl/bootstrap.hxx>
+#include <rtl/uri.hxx>
+#include <rtl/strbuf.hxx>
+
+#include <tools/diagnose_ex.h>
+#include <tools/stream.hxx>
+#include <tools/urlobj.hxx>
+#include <implimagetree.hxx>
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <IconThemeScanner.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/pngwrite.hxx>
+#include <o3tl/string_view.hxx>
+#include <bitmap/BitmapLightenFilter.hxx>
+
+using namespace css;
+
+bool ImageRequestParameters::convertToDarkTheme()
+{
+ static bool bIconsForDarkTheme = !!getenv("VCL_ICONS_FOR_DARK_THEME");
+
+ bool bConvertToDarkTheme = false;
+ if (!(meFlags & ImageLoadFlags::IgnoreDarkTheme))
+ bConvertToDarkTheme = bIconsForDarkTheme;
+
+ return bConvertToDarkTheme;
+}
+
+sal_Int32 ImageRequestParameters::scalePercentage()
+{
+ sal_Int32 aScalePercentage = 100;
+ if (!(meFlags & ImageLoadFlags::IgnoreScalingFactor))
+ aScalePercentage = Application::GetDefaultDevice()->GetDPIScalePercentage();
+ else if (mnScalePercentage > 0)
+ aScalePercentage = mnScalePercentage;
+ return aScalePercentage;
+}
+
+namespace
+{
+
+OUString convertLcTo32Path(std::u16string_view rPath)
+{
+ OUString aResult;
+ size_t nSlashPos = rPath.rfind('/');
+ if (nSlashPos != std::u16string_view::npos)
+ {
+ sal_Int32 nCopyFrom = nSlashPos + 1;
+ std::u16string_view sFile = rPath.substr(nCopyFrom);
+ std::u16string_view sDir = rPath.substr(0, nSlashPos);
+ if (!sFile.empty() && o3tl::starts_with(sFile, u"lc_"))
+ {
+ aResult = OUString::Concat(sDir) + "/32/" + sFile.substr(3);
+ }
+ }
+ return aResult;
+}
+
+OUString createPath(std::u16string_view name, sal_Int32 pos, std::u16string_view locale)
+{
+ return OUString::Concat(name.substr(0, pos + 1)) + locale + name.substr(pos);
+}
+
+OUString getIconCacheUrl(std::u16string_view sVariant, ImageRequestParameters const & rParameters)
+{
+ // the macro expansion can be expensive in bulk, so cache that
+ static OUString CACHE_DIR = []()
+ {
+ OUString sDir = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/";
+ rtl::Bootstrap::expandMacros(sDir);
+ return sDir;
+ }();
+ return CACHE_DIR + rParameters.msStyle + "/" + sVariant + "/" + rParameters.msName;
+}
+
+OUString createIconCacheUrl(
+ std::u16string_view sVariant, ImageRequestParameters const & rParameters)
+{
+ OUString sUrl(getIconCacheUrl(sVariant, rParameters));
+ OUString sDir = sUrl.copy(0, sUrl.lastIndexOf('/'));
+ osl::Directory::createPath(sDir);
+ return sUrl;
+}
+
+bool urlExists(OUString const & sUrl)
+{
+ osl::File aFile(sUrl);
+ osl::FileBase::RC eRC = aFile.open(osl_File_OpenFlag_Read);
+ return osl::FileBase::E_None == eRC;
+}
+
+OUString getNameNoExtension(std::u16string_view sName)
+{
+ size_t nDotPosition = sName.rfind('.');
+ return OUString(sName.substr(0, nDotPosition));
+}
+
+std::shared_ptr<SvMemoryStream> wrapStream(uno::Reference<io::XInputStream> const & rInputStream)
+{
+ // This could use SvInputStream instead if that did not have a broken
+ // SeekPos implementation for an XInputStream that is not also XSeekable
+ // (cf. "@@@" at tags/DEV300_m37/svtools/source/misc1/strmadpt.cxx@264807
+ // l. 593):
+ OSL_ASSERT(rInputStream.is());
+ std::shared_ptr<SvMemoryStream> aMemoryStream(std::make_shared<SvMemoryStream>());
+ for (;;)
+ {
+ const sal_Int32 nSize(2048);
+ uno::Sequence<sal_Int8> aData(nSize);
+ sal_Int32 nRead = rInputStream->readBytes(aData, nSize);
+ aMemoryStream->WriteBytes(aData.getConstArray(), nRead);
+ if (nRead < nSize)
+ break;
+ }
+ aMemoryStream->Seek(0);
+ rInputStream->closeInput();
+ return aMemoryStream;
+}
+
+void loadImageFromStream(std::shared_ptr<SvStream> const & xStream, OUString const & rPath, ImageRequestParameters& rParameters)
+{
+ bool bConvertToDarkTheme = rParameters.convertToDarkTheme();
+ sal_Int32 aScalePercentage = rParameters.scalePercentage();
+
+ if (rPath.endsWith(".png"))
+ {
+ vcl::PngImageReader aPNGReader(*xStream);
+ aPNGReader.read(rParameters.mrBitmap);
+ }
+ else if (rPath.endsWith(".svg"))
+ {
+ rParameters.mbWriteImageToCache = true; // We always want to cache a SVG image
+ vcl::bitmap::loadFromSvg(*xStream, rPath, rParameters.mrBitmap, aScalePercentage / 100.0);
+
+ if (bConvertToDarkTheme)
+ BitmapFilter::Filter(rParameters.mrBitmap, BitmapLightenFilter());
+
+ return;
+ }
+ else
+ {
+ ReadDIBBitmapEx(rParameters.mrBitmap, *xStream);
+ }
+
+ if (bConvertToDarkTheme)
+ {
+ rParameters.mbWriteImageToCache = true; // Cache the dark variant
+ BitmapFilter::Filter(rParameters.mrBitmap, BitmapLightenFilter());
+ }
+
+ if (aScalePercentage > 100)
+ {
+ rParameters.mbWriteImageToCache = true; // Cache the scaled variant
+ double aScaleFactor(aScalePercentage / 100.0);
+ // when scaling use the full 24bit RGB values
+ rParameters.mrBitmap.Convert(BmpConversion::N24Bit);
+ rParameters.mrBitmap.Scale(aScaleFactor, aScaleFactor, BmpScaleFlag::Fast);
+ }
+}
+
+} // end anonymous namespace
+
+ImplImageTree::ImplImageTree()
+{
+}
+
+ImplImageTree::~ImplImageTree()
+{
+}
+
+std::vector<OUString> ImplImageTree::getPaths(OUString const & name, LanguageTag const & rLanguageTag)
+{
+ std::vector<OUString> sPaths;
+
+ sal_Int32 pos = name.lastIndexOf('/');
+ if (pos != -1)
+ {
+ for (const OUString& rFallback : rLanguageTag.getFallbackStrings(true))
+ {
+ OUString aFallbackName = getNameNoExtension(getRealImageName(createPath(name, pos, rFallback)));
+ sPaths.emplace_back(aFallbackName + ".png");
+ sPaths.emplace_back(aFallbackName + ".svg");
+ }
+ }
+
+ OUString aRealName = getNameNoExtension(getRealImageName(name));
+ sPaths.emplace_back(aRealName + ".png");
+ sPaths.emplace_back(aRealName + ".svg");
+
+ return sPaths;
+}
+
+OUString ImplImageTree::getImageUrl(OUString const & rName, OUString const & rStyle, OUString const & rLang)
+{
+ OUString aStyle(rStyle);
+
+ while (!aStyle.isEmpty())
+ {
+ try
+ {
+ setStyle(aStyle);
+
+ if (checkPathAccess())
+ {
+ IconSet& rIconSet = getCurrentIconSet();
+ const uno::Reference<container::XNameAccess> & rNameAccess = rIconSet.maNameAccess;
+
+ LanguageTag aLanguageTag(rLang);
+
+ for (const OUString& rPath: getPaths(rName, aLanguageTag))
+ {
+ if (rNameAccess->hasByName(rPath))
+ {
+ return "vnd.sun.star.zip://"
+ + rtl::Uri::encode(rIconSet.maURL, rtl_UriCharClassRegName,
+ rtl_UriEncodeIgnoreEscapes, RTL_TEXTENCODING_UTF8)
+ + "/" + rPath;
+ }
+ }
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_INFO_EXCEPTION("vcl", "");
+ }
+
+ aStyle = fallbackStyle(aStyle);
+ }
+ return OUString();
+}
+
+uno::Reference<io::XInputStream> ImplImageTree::getImageXInputStream(OUString const & rName, OUString const & rStyle, OUString const & rLang)
+{
+ OUString aStyle(rStyle);
+
+ while (!aStyle.isEmpty())
+ {
+ try
+ {
+ setStyle(aStyle);
+
+ if (checkPathAccess())
+ {
+ IconSet& rIconSet = getCurrentIconSet();
+ const uno::Reference<container::XNameAccess>& rNameAccess = rIconSet.maNameAccess;
+
+ LanguageTag aLanguageTag(rLang);
+
+ for (const OUString& rPath: getPaths(rName, aLanguageTag))
+ {
+ if (rNameAccess->hasByName(rPath))
+ {
+ uno::Reference<io::XInputStream> aStream;
+ bool ok = rNameAccess->getByName(rPath) >>= aStream;
+ assert(ok);
+ (void)ok; // prevent unused warning in release build
+ return aStream;
+ }
+ }
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_INFO_EXCEPTION("vcl", "");
+ }
+
+ aStyle = fallbackStyle(aStyle);
+ }
+ return nullptr;
+}
+
+std::shared_ptr<SvMemoryStream> ImplImageTree::getImageStream(OUString const & rName, OUString const & rStyle, OUString const & rLang)
+{
+ uno::Reference<io::XInputStream> xStream = getImageXInputStream(rName, rStyle, rLang);
+ if (xStream)
+ return wrapStream(xStream);
+ return std::shared_ptr<SvMemoryStream>();
+}
+
+OUString ImplImageTree::fallbackStyle(std::u16string_view rsStyle)
+{
+ OUString sResult;
+
+ if (rsStyle == u"colibre" || rsStyle == u"helpimg")
+ sResult = "";
+ else if (rsStyle == u"sifr" || rsStyle == u"breeze_dark")
+ sResult = "breeze";
+ else if (rsStyle == u"sifr_dark" )
+ sResult = "breeze_dark";
+ else
+ sResult = "colibre";
+
+ return sResult;
+}
+
+bool ImplImageTree::loadImage(OUString const & rName, OUString const & rStyle, BitmapEx & rBitmap, bool localized,
+ const ImageLoadFlags eFlags, sal_Int32 nScalePercentage)
+{
+ OUString aCurrentStyle(rStyle);
+ while (!aCurrentStyle.isEmpty())
+ {
+ try
+ {
+ ImageRequestParameters aParameters(rName, aCurrentStyle, rBitmap, localized, eFlags, nScalePercentage);
+ if (doLoadImage(aParameters))
+ return true;
+ }
+ catch (uno::RuntimeException &)
+ {}
+
+ aCurrentStyle = fallbackStyle(aCurrentStyle);
+ }
+ return false;
+}
+
+namespace
+{
+
+OUString createVariant(ImageRequestParameters& rParameters)
+{
+ bool bConvertToDarkTheme = rParameters.convertToDarkTheme();
+ sal_Int32 aScalePercentage = rParameters.scalePercentage();
+
+ OUString aVariant = OUString::number(aScalePercentage);
+
+ if (bConvertToDarkTheme)
+ aVariant += "-dark";
+
+ return aVariant;
+}
+
+bool loadDiskCachedVersion(std::u16string_view sVariant, ImageRequestParameters& rParameters)
+{
+ OUString sUrl(getIconCacheUrl(sVariant, rParameters));
+ if (!urlExists(sUrl))
+ return false;
+ SvFileStream aFileStream(sUrl, StreamMode::READ);
+ vcl::PngImageReader aPNGReader(aFileStream);
+ aPNGReader.read(rParameters.mrBitmap);
+ return true;
+}
+
+void cacheBitmapToDisk(std::u16string_view sVariant, ImageRequestParameters const & rParameters)
+{
+ OUString sUrl(createIconCacheUrl(sVariant, rParameters));
+ vcl::PNGWriter aWriter(rParameters.mrBitmap);
+ try
+ {
+ SvFileStream aStream(sUrl, StreamMode::WRITE);
+ aWriter.Write(aStream);
+ aStream.Close();
+ }
+ catch (...)
+ {}
+}
+
+} // end anonymous namespace
+
+bool ImplImageTree::doLoadImage(ImageRequestParameters& rParameters)
+{
+ setStyle(rParameters.msStyle);
+
+ if (iconCacheLookup(rParameters))
+ return true;
+
+ OUString aVariant = createVariant(rParameters);
+ if (loadDiskCachedVersion(aVariant, rParameters))
+ return true;
+
+ if (!rParameters.mrBitmap.IsEmpty())
+ rParameters.mrBitmap.SetEmpty();
+
+ LanguageTag aLanguageTag = Application::GetSettings().GetUILanguageTag();
+
+ std::vector<OUString> aPaths = getPaths(rParameters.msName, aLanguageTag);
+
+ bool bFound = false;
+
+ try
+ {
+ bFound = findImage(aPaths, rParameters);
+ }
+ catch (uno::RuntimeException&)
+ {
+ throw;
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_INFO_EXCEPTION("vcl", "ImplImageTree::doLoadImage");
+ }
+
+ if (bFound)
+ {
+ if (rParameters.mbWriteImageToCache)
+ {
+ cacheBitmapToDisk(aVariant, rParameters);
+ }
+ getIconCache(rParameters)[rParameters.msName] = std::make_pair(rParameters.mbLocalized, rParameters.mrBitmap);
+ }
+
+ return bFound;
+}
+
+void ImplImageTree::shutdown()
+{
+ maCurrentStyle.clear();
+ maIconSets.clear();
+}
+
+void ImplImageTree::setStyle(OUString const & style)
+{
+ assert(!style.isEmpty());
+ if (style != maCurrentStyle)
+ {
+ maCurrentStyle = style;
+ createStyle();
+ }
+}
+
+/**
+ * The vcldemo app doesn't set up all the config stuff that the main app does, so we need another
+ * way of finding the cursor images.
+ */
+static bool isVclDemo()
+{
+ static const bool bVclDemoOverride = std::getenv("LIBO_VCL_DEMO") != nullptr;
+ return bVclDemoOverride;
+}
+
+void ImplImageTree::createStyle()
+{
+ if (maIconSets.find(maCurrentStyle) != maIconSets.end())
+ return;
+
+ OUString sThemeUrl;
+
+ if (isVclDemo())
+ {
+ if (maCurrentStyle == "default")
+ sThemeUrl = "file://" SRC_ROOT "/icon-themes/colibre-svg";
+ else
+ sThemeUrl = "file://" SRC_ROOT "/icon-themes/" + maCurrentStyle;
+ }
+ else if (maCurrentStyle != "default")
+ {
+ OUString paths = vcl::IconThemeScanner::GetStandardIconThemePath();
+ std::deque<OUString> aPaths;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ aPaths.push_front(paths.getToken(0, ';', nIndex));
+ }
+ while (nIndex >= 0);
+
+ for (const auto& path : aPaths)
+ {
+ INetURLObject aUrl(path);
+ OSL_ASSERT(!aUrl.HasError());
+
+ bool ok = aUrl.Append(OUStringConcatenation("images_" + maCurrentStyle), INetURLObject::EncodeMechanism::All);
+ OSL_ASSERT(ok);
+ sThemeUrl = aUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE) + ".zip";
+ if (urlExists(sThemeUrl))
+ break;
+ sThemeUrl.clear();
+ }
+
+ if (sThemeUrl.isEmpty())
+ return;
+ }
+ else
+ {
+ sThemeUrl += "images";
+ if (!urlExists(sThemeUrl))
+ return;
+ }
+
+ maIconSets[maCurrentStyle] = IconSet(sThemeUrl);
+
+ loadImageLinks();
+}
+
+/// Find an icon cache for the right scale factor
+ImplImageTree::IconCache &ImplImageTree::getIconCache(const ImageRequestParameters& rParameters)
+{
+ IconSet &rSet = getCurrentIconSet();
+ auto it = rSet.maScaledIconCaches.find(rParameters.mnScalePercentage);
+ if ( it != rSet.maScaledIconCaches.end() )
+ return it->second;
+ rSet.maScaledIconCaches.emplace(rParameters.mnScalePercentage, IconCache());
+ return rSet.maScaledIconCaches[rParameters.mnScalePercentage];
+}
+
+bool ImplImageTree::iconCacheLookup(ImageRequestParameters& rParameters)
+{
+ IconCache& rIconCache = getIconCache(rParameters);
+
+ IconCache::iterator i(rIconCache.find(getRealImageName(rParameters.msName)));
+ if (i != rIconCache.end() && i->second.first == rParameters.mbLocalized)
+ {
+ rParameters.mrBitmap = i->second.second;
+ return true;
+ }
+
+ return false;
+}
+
+bool ImplImageTree::findImage(std::vector<OUString> const & rPaths, ImageRequestParameters& rParameters)
+{
+ if (!checkPathAccess())
+ return false;
+
+ uno::Reference<container::XNameAccess> const & rNameAccess = getCurrentIconSet().maNameAccess;
+
+ for (OUString const & rPath : rPaths)
+ {
+ if (rNameAccess->hasByName(rPath))
+ {
+ uno::Reference<io::XInputStream> aStream;
+ bool ok = rNameAccess->getByName(rPath) >>= aStream;
+ assert(ok);
+ (void)ok; // prevent unused warning in release build
+
+ loadImageFromStream(wrapStream(aStream), rPath, rParameters);
+
+ return true;
+ }
+ }
+ return false;
+}
+
+void ImplImageTree::loadImageLinks()
+{
+ static const OUStringLiteral aLinkFilename(u"links.txt");
+
+ if (!checkPathAccess())
+ return;
+
+ const uno::Reference<container::XNameAccess> &rNameAccess = getCurrentIconSet().maNameAccess;
+
+ if (rNameAccess->hasByName(aLinkFilename))
+ {
+ uno::Reference<io::XInputStream> xStream;
+ bool ok = rNameAccess->getByName(aLinkFilename) >>= xStream;
+ assert(ok);
+ (void)ok; // prevent unused warning in release build
+
+ parseLinkFile(wrapStream(xStream));
+ return;
+ }
+}
+
+void ImplImageTree::parseLinkFile(std::shared_ptr<SvStream> const & xStream)
+{
+ OStringBuffer aLine;
+ OUString aLink, aOriginal;
+ int nLineNo = 0;
+ while (xStream->ReadLine(aLine))
+ {
+ ++nLineNo;
+ if (aLine.isEmpty())
+ continue;
+
+ sal_Int32 nIndex = 0;
+ aLink = OStringToOUString(o3tl::getToken(aLine, 0, ' ', nIndex), RTL_TEXTENCODING_UTF8);
+ aOriginal = OStringToOUString(o3tl::getToken(aLine, 0, ' ', nIndex), RTL_TEXTENCODING_UTF8);
+
+ // skip comments, or incomplete entries
+ if (aLink.isEmpty() || aLink[0] == '#' || aOriginal.isEmpty())
+ {
+ if (aLink.isEmpty() || aOriginal.isEmpty())
+ SAL_WARN("vcl", "ImplImageTree::parseLinkFile: icon links.txt parse error, incomplete link at line " << nLineNo);
+ continue;
+ }
+
+ getCurrentIconSet().maLinkHash[aLink] = aOriginal;
+
+ OUString aOriginal32 = convertLcTo32Path(aOriginal);
+ OUString aLink32 = convertLcTo32Path(aLink);
+
+ if (!aOriginal32.isEmpty() && !aLink32.isEmpty())
+ getCurrentIconSet().maLinkHash[aLink32] = aOriginal32;
+ }
+}
+
+OUString const & ImplImageTree::getRealImageName(OUString const & rIconName)
+{
+ IconLinkHash & rLinkHash = maIconSets[maCurrentStyle].maLinkHash;
+
+ OUString sNameWithNoExtension = getNameNoExtension(rIconName);
+
+ // PNG is priority
+ auto it = rLinkHash.find(sNameWithNoExtension + ".png");
+ if (it != rLinkHash.end())
+ return it->second;
+
+ // also check SVG name
+ it = rLinkHash.find(sNameWithNoExtension + ".svg");
+ if (it != rLinkHash.end())
+ return it->second;
+
+ // neither was found so just return the original name
+ return rIconName;
+}
+
+namespace {
+
+class FolderFileAccess : public ::cppu::WeakImplHelper<css::container::XNameAccess>
+{
+public:
+ uno::Reference< uno::XComponentContext > mxContext;
+ OUString maURL;
+ FolderFileAccess(uno::Reference< uno::XComponentContext > const & context, OUString const & url)
+ : mxContext(context), maURL(url) {}
+ // XElementAccess
+ virtual css::uno::Type SAL_CALL getElementType() override { return cppu::UnoType<io::XInputStream>::get(); }
+ virtual sal_Bool SAL_CALL hasElements() override { return true; }
+ // XNameAccess
+ virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override
+ {
+ uno::Reference< io::XInputStream > xInputStream = ucb::SimpleFileAccess::create(mxContext)->openFileRead( maURL + "/" + aName );
+ return css::uno::Any(xInputStream);
+ }
+ virtual css::uno::Sequence< OUString > SAL_CALL getElementNames() override
+ {
+ return {};
+ }
+ virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override
+ {
+ osl::File aBaseFile(maURL + "/" + aName);
+ return osl::File::E_None == aBaseFile.open(osl_File_OpenFlag_Read);
+ }
+};
+
+}
+
+bool ImplImageTree::checkPathAccess()
+{
+ IconSet& rIconSet = getCurrentIconSet();
+ uno::Reference<container::XNameAccess> & rNameAccess = rIconSet.maNameAccess;
+ if (rNameAccess.is())
+ return true;
+
+ try
+ {
+ if (isVclDemo())
+ rNameAccess = new FolderFileAccess(comphelper::getProcessComponentContext(), rIconSet.maURL);
+ else
+ rNameAccess = packages::zip::ZipFileAccess::createWithURL(comphelper::getProcessComponentContext(), rIconSet.maURL);
+ }
+ catch (const uno::RuntimeException &)
+ {
+ throw;
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_INFO_EXCEPTION("vcl", "ImplImageTree::zip file location " << rIconSet.maURL);
+ return false;
+ }
+ return rNameAccess.is();
+}
+
+uno::Reference<container::XNameAccess> const & ImplImageTree::getNameAccess()
+{
+ (void)checkPathAccess();
+ return getCurrentIconSet().maNameAccess;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/GLMHelper.hxx b/vcl/source/opengl/GLMHelper.hxx
new file mode 100644
index 000000000..9694a3c34
--- /dev/null
+++ b/vcl/source/opengl/GLMHelper.hxx
@@ -0,0 +1,21 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <glm/glm.hpp>
+#include <vcl/dllapi.h>
+
+#include <ostream>
+
+std::ostream& operator<<(std::ostream& rStrm, const glm::mat4& rMatrix);
+std::ostream& operator<<(std::ostream& rStrm, const glm::vec4& rPos);
+std::ostream& operator<<(std::ostream& rStrm, const glm::vec3& rPos);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/OpenGLContext.cxx b/vcl/source/opengl/OpenGLContext.cxx
new file mode 100644
index 000000000..b703356ee
--- /dev/null
+++ b/vcl/source/opengl/OpenGLContext.cxx
@@ -0,0 +1,495 @@
+/* -*- 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/.
+ */
+
+#include <chrono>
+
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <vcl/opengl/OpenGLWrapper.hxx>
+#include <vcl/syschild.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <osl/thread.hxx>
+#include <sal/log.hxx>
+
+#include <svdata.hxx>
+#include <salgdi.hxx>
+#include <salinst.hxx>
+
+#include <opengl/zone.hxx>
+
+#include <config_features.h>
+
+using namespace com::sun::star;
+
+static sal_Int64 nBufferSwapCounter = 0;
+
+GLWindow::~GLWindow()
+{
+}
+
+bool GLWindow::Synchronize(bool /*bOnoff*/) const
+{
+ return false;
+}
+
+OpenGLContext::OpenGLContext():
+ mpWindow(nullptr),
+ m_pChildWindow(nullptr),
+ mbInitialized(false),
+ mnRefCount(0),
+ mbRequestLegacyContext(false),
+ mpPrevContext(nullptr),
+ mpNextContext(nullptr)
+{
+ VCL_GL_INFO("new context: " << this);
+
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->maGDIData.mpLastContext )
+ {
+ pSVData->maGDIData.mpLastContext->mpNextContext = this;
+ mpPrevContext = pSVData->maGDIData.mpLastContext;
+ }
+ pSVData->maGDIData.mpLastContext = this;
+
+ // FIXME: better hope we call 'makeCurrent' soon to preserve
+ // the invariant that the last item is the current context.
+}
+
+OpenGLContext::~OpenGLContext()
+{
+ assert (mnRefCount == 0);
+
+ mnRefCount = 1; // guard the shutdown paths.
+ VCL_GL_INFO("delete context: " << this);
+
+ reset();
+
+ ImplSVData* pSVData = ImplGetSVData();
+ if( mpPrevContext )
+ mpPrevContext->mpNextContext = mpNextContext;
+ if( mpNextContext )
+ mpNextContext->mpPrevContext = mpPrevContext;
+ else
+ pSVData->maGDIData.mpLastContext = mpPrevContext;
+
+ m_pChildWindow.disposeAndClear();
+ assert (mnRefCount == 1);
+}
+
+// release associated child-window if we have one
+void OpenGLContext::dispose()
+{
+ reset();
+ m_pChildWindow.disposeAndClear();
+}
+
+rtl::Reference<OpenGLContext> OpenGLContext::Create()
+{
+ return rtl::Reference<OpenGLContext>(ImplGetSVData()->mpDefInst->CreateOpenGLContext());
+}
+
+void OpenGLContext::requestLegacyContext()
+{
+ mbRequestLegacyContext = true;
+}
+
+#ifdef DBG_UTIL
+
+namespace {
+
+const char* getSeverityString(GLenum severity)
+{
+ switch(severity)
+ {
+ case GL_DEBUG_SEVERITY_LOW:
+ return "low";
+ case GL_DEBUG_SEVERITY_MEDIUM:
+ return "medium";
+ case GL_DEBUG_SEVERITY_HIGH:
+ return "high";
+ default:
+ ;
+ }
+
+ return "unknown";
+}
+
+const char* getSourceString(GLenum source)
+{
+ switch(source)
+ {
+ case GL_DEBUG_SOURCE_API:
+ return "API";
+ case GL_DEBUG_SOURCE_SHADER_COMPILER:
+ return "shader compiler";
+ case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
+ return "window system";
+ case GL_DEBUG_SOURCE_THIRD_PARTY:
+ return "third party";
+ case GL_DEBUG_SOURCE_APPLICATION:
+ return "Libreoffice";
+ case GL_DEBUG_SOURCE_OTHER:
+ return "unknown";
+ default:
+ ;
+ }
+
+ return "unknown";
+}
+
+const char* getTypeString(GLenum type)
+{
+ switch(type)
+ {
+ case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
+ return "deprecated behavior";
+ case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
+ return "undefined behavior";
+ case GL_DEBUG_TYPE_PERFORMANCE:
+ return "performance";
+ case GL_DEBUG_TYPE_PORTABILITY:
+ return "portability";
+ case GL_DEBUG_TYPE_MARKER:
+ return "marker";
+ case GL_DEBUG_TYPE_PUSH_GROUP:
+ return "push group";
+ case GL_DEBUG_TYPE_POP_GROUP:
+ return "pop group";
+ case GL_DEBUG_TYPE_OTHER:
+ return "other";
+ case GL_DEBUG_TYPE_ERROR:
+ return "error";
+ default:
+ ;
+ }
+
+ return "unknown";
+}
+
+extern "C" void
+#if defined _WIN32
+APIENTRY
+#endif
+debug_callback(GLenum source, GLenum type, GLuint id,
+ GLenum severity, GLsizei , const GLchar* message,
+ const GLvoid*)
+{
+ // ignore Nvidia's 131218: "Program/shader state performance warning: Fragment Shader is going to be recompiled because the shader key based on GL state mismatches."
+ // the GLSL compiler is a bit too aggressive in optimizing the state based on the current OpenGL state
+
+ // ignore 131185: "Buffer detailed info: Buffer object x (bound to GL_ARRAY_BUFFER_ARB,
+ // usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations."
+ if (id == 131218 || id == 131185)
+ return;
+
+ SAL_WARN("vcl.opengl", "OpenGL debug message: source: " << getSourceString(source) << ", type: "
+ << getTypeString(type) << ", id: " << id << ", severity: " << getSeverityString(severity) << ", with message: " << message);
+}
+
+}
+
+#endif
+
+bool OpenGLContext::init( vcl::Window* pParent )
+{
+ if(mbInitialized)
+ return true;
+
+ OpenGLZone aZone;
+
+ m_xWindow.reset(pParent ? nullptr : VclPtr<vcl::Window>::Create(nullptr, WB_NOBORDER|WB_NODIALOGCONTROL));
+ mpWindow = pParent ? pParent : m_xWindow.get();
+ if(m_xWindow)
+ m_xWindow->setPosSizePixel(0,0,0,0);
+ //tdf#108069 we may be initted twice, so dispose earlier effort
+ m_pChildWindow.disposeAndClear();
+ initWindow();
+ return ImplInit();
+}
+
+bool OpenGLContext::ImplInit()
+{
+ VCL_GL_INFO("OpenGLContext not implemented for this platform");
+ return false;
+}
+
+static OUString getGLString(GLenum eGlEnum)
+{
+ OUString sString;
+ const GLubyte* pString = glGetString(eGlEnum);
+ if (pString)
+ {
+ sString = OUString::createFromAscii(reinterpret_cast<const char*>(pString));
+ }
+
+ CHECK_GL_ERROR();
+ return sString;
+}
+
+bool OpenGLContext::InitGL()
+{
+ VCL_GL_INFO("OpenGLContext::ImplInit----end");
+ VCL_GL_INFO("Vendor: " << getGLString(GL_VENDOR) << " Renderer: " << getGLString(GL_RENDERER) << " GL version: " << OpenGLHelper::getGLVersion());
+ mbInitialized = true;
+
+ // I think we need at least GL 3.0
+ if (epoxy_gl_version() < 30)
+ {
+ SAL_WARN("vcl.opengl", "We don't have at least OpenGL 3.0");
+ return false;
+ }
+
+ // Check that some "optional" APIs that we use unconditionally are present
+ if (!glBindFramebuffer)
+ {
+ SAL_WARN("vcl.opengl", "We don't have glBindFramebuffer");
+ return false;
+ }
+
+ return true;
+}
+
+void OpenGLContext::InitGLDebugging()
+{
+#ifdef DBG_UTIL
+ // only enable debug output in dbgutil build
+ if (epoxy_has_gl_extension("GL_ARB_debug_output"))
+ {
+ OpenGLZone aZone;
+
+ if (glDebugMessageCallbackARB)
+ {
+ glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
+ glDebugMessageCallbackARB(&debug_callback, nullptr);
+
+#ifdef GL_DEBUG_SEVERITY_NOTIFICATION_ARB
+ // Ignore i965’s shader compiler notification flood.
+ glDebugMessageControlARB(GL_DEBUG_SOURCE_SHADER_COMPILER_ARB, GL_DEBUG_TYPE_OTHER_ARB, GL_DEBUG_SEVERITY_NOTIFICATION_ARB, 0, nullptr, true);
+#endif
+ }
+ else if ( glDebugMessageCallback )
+ {
+ glEnable(GL_DEBUG_OUTPUT);
+ glDebugMessageCallback(&debug_callback, nullptr);
+
+ // Ignore i965’s shader compiler notification flood.
+ glDebugMessageControl(GL_DEBUG_SOURCE_SHADER_COMPILER, GL_DEBUG_TYPE_OTHER, GL_DEBUG_SEVERITY_NOTIFICATION, 0, nullptr, true);
+ }
+ }
+
+ // Test hooks for inserting tracing messages into the stream
+ VCL_GL_INFO("LibreOffice GLContext initialized");
+#endif
+}
+
+void OpenGLContext::restoreDefaultFramebuffer()
+{
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+void OpenGLContext::setWinPosAndSize(const Point &rPos, const Size& rSize)
+{
+ if (m_xWindow)
+ m_xWindow->SetPosSizePixel(rPos, rSize);
+ if (m_pChildWindow)
+ m_pChildWindow->SetPosSizePixel(rPos, rSize);
+
+ GLWindow& rGLWin = getModifiableOpenGLWindow();
+ rGLWin.Width = rSize.Width();
+ rGLWin.Height = rSize.Height();
+ adjustToNewSize();
+}
+
+void OpenGLContext::adjustToNewSize()
+{
+ const GLWindow& rGLWin = getOpenGLWindow();
+ glViewport(0, 0, rGLWin.Width, rGLWin.Height);
+}
+
+void OpenGLContext::InitChildWindow(SystemChildWindow *pChildWindow)
+{
+ pChildWindow->SetMouseTransparent(true);
+ pChildWindow->SetParentClipMode(ParentClipMode::Clip);
+ pChildWindow->EnableEraseBackground(false);
+ pChildWindow->SetControlForeground();
+ pChildWindow->SetControlBackground();
+}
+
+void OpenGLContext::initWindow()
+{
+}
+
+void OpenGLContext::destroyCurrentContext()
+{
+ //nothing by default
+}
+
+void OpenGLContext::reset()
+{
+ if( !mbInitialized )
+ return;
+
+ OpenGLZone aZone;
+
+ if( isCurrent() )
+ resetCurrent();
+
+ mbInitialized = false;
+
+ // destroy the context itself
+ destroyCurrentContext();
+}
+
+SystemWindowData OpenGLContext::generateWinData(vcl::Window* /*pParent*/, bool /*bRequestLegacyContext*/)
+{
+ return {};
+}
+
+bool OpenGLContext::isCurrent()
+{
+ (void) this; // loplugin:staticmethods
+ return false;
+}
+
+void OpenGLContext::makeCurrent()
+{
+ if (isCurrent())
+ return;
+
+ OpenGLZone aZone;
+
+ clearCurrent();
+
+ // by default nothing else to do
+
+ registerAsCurrent();
+}
+
+bool OpenGLContext::isAnyCurrent()
+{
+ return false;
+}
+
+bool OpenGLContext::hasCurrent()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ rtl::Reference<OpenGLContext> pCurrentCtx = pSVData->maGDIData.mpLastContext;
+ return pCurrentCtx.is() && pCurrentCtx->isAnyCurrent();
+}
+
+void OpenGLContext::clearCurrent()
+{
+}
+
+void OpenGLContext::prepareForYield()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // release all framebuffers from the old context so we can re-attach the
+ // texture in the new context
+ rtl::Reference<OpenGLContext> pCurrentCtx = pSVData->maGDIData.mpLastContext;
+
+ if ( !pCurrentCtx.is() )
+ return; // Not using OpenGL
+
+ SAL_INFO("vcl.opengl", "Unbinding contexts in preparation for yield");
+
+ // Find the first context that is current and reset it.
+ // Usually the last context is the current, but not in case a new
+ // OpenGLContext is created already but not yet initialized.
+ while (pCurrentCtx.is())
+ {
+ if (pCurrentCtx->isCurrent())
+ {
+ pCurrentCtx->resetCurrent();
+ break;
+ }
+
+ pCurrentCtx = pCurrentCtx->mpPrevContext;
+ }
+
+ assert (!hasCurrent());
+}
+
+void OpenGLContext::registerAsCurrent()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // move the context to the end of the contexts list
+ static int nSwitch = 0;
+ VCL_GL_INFO("******* CONTEXT SWITCH " << ++nSwitch << " *********");
+ if( mpNextContext )
+ {
+ if( mpPrevContext )
+ mpPrevContext->mpNextContext = mpNextContext;
+ mpNextContext->mpPrevContext = mpPrevContext;
+
+ mpPrevContext = pSVData->maGDIData.mpLastContext;
+ mpNextContext = nullptr;
+ pSVData->maGDIData.mpLastContext->mpNextContext = this;
+ pSVData->maGDIData.mpLastContext = this;
+ }
+}
+
+void OpenGLContext::resetCurrent()
+{
+ clearCurrent();
+ // by default nothing else to do
+}
+
+void OpenGLContext::swapBuffers()
+{
+ // by default nothing else to do
+ BuffersSwapped();
+}
+
+void OpenGLContext::BuffersSwapped()
+{
+ nBufferSwapCounter++;
+
+ static bool bSleep = getenv("SAL_GL_SLEEP_ON_SWAP");
+ if (bSleep)
+ {
+ // half a second.
+ osl::Thread::wait( std::chrono::milliseconds(500) );
+ }
+}
+
+
+sal_Int64 OpenGLWrapper::getBufferSwapCounter()
+{
+ return nBufferSwapCounter;
+}
+
+void OpenGLContext::sync()
+{
+ // default is nothing
+ (void) this; // loplugin:staticmethods
+}
+
+void OpenGLContext::show()
+{
+ if (m_pChildWindow)
+ m_pChildWindow->Show();
+ else if (m_xWindow)
+ m_xWindow->Show();
+}
+
+SystemChildWindow* OpenGLContext::getChildWindow()
+{
+ return m_pChildWindow;
+}
+
+const SystemChildWindow* OpenGLContext::getChildWindow() const
+{
+ return m_pChildWindow;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/OpenGLHelper.cxx b/vcl/source/opengl/OpenGLHelper.cxx
new file mode 100644
index 000000000..a0dff4a5f
--- /dev/null
+++ b/vcl/source/opengl/OpenGLHelper.cxx
@@ -0,0 +1,939 @@
+/* -*- 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/.
+ */
+
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <config_vclplug.h>
+
+#include <osl/file.hxx>
+#include <rtl/bootstrap.hxx>
+#include <rtl/digest.h>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <config_folders.h>
+#include <memory>
+#include <vcl/pngwrite.hxx>
+#include <vcl/svapp.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <com/sun/star/util/XFlushable.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+
+#include <stdarg.h>
+#include <string_view>
+#include <vector>
+#include <unordered_map>
+
+#include <opengl/zone.hxx>
+#include <vcl/opengl/OpenGLWrapper.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <desktop/crashreport.hxx>
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <watchdog.hxx>
+#include <vcl/skia/SkiaHelper.hxx>
+#include <salinst.hxx>
+#include <svdata.hxx>
+
+#if defined (_WIN32)
+#include <opengl/win/WinDeviceInfo.hxx>
+#endif
+
+#include "GLMHelper.hxx"
+
+static bool volatile gbInShaderCompile = false;
+
+namespace {
+
+using namespace rtl;
+
+OUString getShaderFolder()
+{
+ OUString aUrl("$BRAND_BASE_DIR/" LIBO_ETC_FOLDER);
+ rtl::Bootstrap::expandMacros(aUrl);
+
+ return aUrl + "/opengl/";
+}
+
+OString loadShader(std::u16string_view rFilename)
+{
+ OUString aFileURL = getShaderFolder() + rFilename +".glsl";
+ osl::File aFile(aFileURL);
+ if(aFile.open(osl_File_OpenFlag_Read) == osl::FileBase::E_None)
+ {
+ sal_uInt64 nSize = 0;
+ aFile.getSize(nSize);
+ std::unique_ptr<char[]> content(new char[nSize+1]);
+ sal_uInt64 nBytesRead = 0;
+ aFile.read(content.get(), nSize, nBytesRead);
+ assert(nSize == nBytesRead);
+ content.get()[nBytesRead] = 0;
+ SAL_INFO("vcl.opengl", "Read " << nBytesRead << " bytes from " << aFileURL);
+ return content.get();
+ }
+ else
+ {
+ SAL_WARN("vcl.opengl", "Could not open " << aFileURL);
+ }
+
+ return OString();
+}
+
+OString& getShaderSource(const OUString& rFilename)
+{
+ static std::unordered_map<OUString, OString> aMap;
+
+ if (aMap.find(rFilename) == aMap.end())
+ {
+ aMap[rFilename] = loadShader(rFilename);
+ }
+
+ return aMap[rFilename];
+}
+
+}
+
+namespace {
+ int LogCompilerError(GLuint nId, const OUString &rDetail,
+ const OUString &rName, bool bShaderNotProgram)
+ {
+ OpenGLZone aZone;
+
+ int InfoLogLength = 0;
+
+ CHECK_GL_ERROR();
+
+ if (bShaderNotProgram)
+ glGetShaderiv (nId, GL_INFO_LOG_LENGTH, &InfoLogLength);
+ else
+ glGetProgramiv(nId, GL_INFO_LOG_LENGTH, &InfoLogLength);
+
+ CHECK_GL_ERROR();
+
+ if ( InfoLogLength > 0 )
+ {
+ std::vector<char> ErrorMessage(InfoLogLength+1);
+ if (bShaderNotProgram)
+ glGetShaderInfoLog (nId, InfoLogLength, nullptr, ErrorMessage.data());
+ else
+ glGetProgramInfoLog(nId, InfoLogLength, nullptr, ErrorMessage.data());
+ CHECK_GL_ERROR();
+
+ ErrorMessage.push_back('\0');
+ SAL_WARN("vcl.opengl", rDetail << " shader " << nId << " compile for " << rName << " failed : " << ErrorMessage.data());
+ }
+ else
+ SAL_WARN("vcl.opengl", rDetail << " shader: " << rName << " compile " << nId << " failed without error log");
+
+#ifdef DBG_UTIL
+ abort();
+#endif
+ return 0;
+ }
+}
+
+static void addPreamble(OString& rShaderSource, std::string_view rPreamble)
+{
+ if (rPreamble.empty())
+ return;
+
+ int nVersionStrStartPos = rShaderSource.indexOf("#version");
+
+ if (nVersionStrStartPos == -1)
+ {
+ rShaderSource = OString::Concat(rPreamble) + "\n" + rShaderSource;
+ }
+ else
+ {
+ int nVersionStrEndPos = rShaderSource.indexOf('\n', nVersionStrStartPos);
+
+ SAL_WARN_IF(nVersionStrEndPos == -1, "vcl.opengl", "syntax error in shader");
+
+ if (nVersionStrEndPos == -1)
+ nVersionStrEndPos = nVersionStrStartPos + 8;
+
+ OString aVersionLine = rShaderSource.copy(0, nVersionStrEndPos);
+ OString aShaderBody = rShaderSource.copy(nVersionStrEndPos + 1);
+
+ rShaderSource = aVersionLine + "\n" + rPreamble + "\n" + aShaderBody;
+ }
+}
+
+namespace
+{
+ const sal_uInt32 GLenumSize = sizeof(GLenum);
+
+#if defined _WIN32
+ OString getHexString(const sal_uInt8* pData, sal_uInt32 nLength)
+ {
+ static const char* const pHexData = "0123456789ABCDEF";
+
+ bool bIsZero = true;
+ OStringBuffer aHexStr;
+ for(size_t i = 0; i < nLength; ++i)
+ {
+ sal_uInt8 val = pData[i];
+ if( val != 0 )
+ bIsZero = false;
+ aHexStr.append( pHexData[ val & 0xf ] );
+ aHexStr.append( pHexData[ val >> 4 ] );
+ }
+ if( bIsZero )
+ return OString();
+ else
+ return aHexStr.makeStringAndClear();
+ }
+
+ OString generateMD5(const void* pData, size_t length)
+ {
+ sal_uInt8 pBuffer[RTL_DIGEST_LENGTH_MD5];
+ rtlDigestError aError = rtl_digest_MD5(pData, length,
+ pBuffer, RTL_DIGEST_LENGTH_MD5);
+ SAL_WARN_IF(aError != rtl_Digest_E_None, "vcl.opengl", "md5 generation failed");
+
+ return getHexString(pBuffer, RTL_DIGEST_LENGTH_MD5);
+ }
+
+ OString getDeviceInfoString()
+ {
+ const WinOpenGLDeviceInfo aInfo;
+ return OUStringToOString(aInfo.GetAdapterVendorID(), RTL_TEXTENCODING_UTF8) +
+ OUStringToOString(aInfo.GetAdapterDeviceID(), RTL_TEXTENCODING_UTF8) +
+ OUStringToOString(aInfo.GetDriverVersion(), RTL_TEXTENCODING_UTF8) +
+ OString::number(DriverBlocklist::GetWindowsVersion());
+ }
+
+ OString getStringDigest( const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ std::string_view rPreamble )
+ {
+ // read shaders source
+ OString aVertexShaderSource = getShaderSource( rVertexShaderName );
+ OString aFragmentShaderSource = getShaderSource( rFragmentShaderName );
+
+ // get info about the graphic device
+ static const OString aDeviceInfo (getDeviceInfoString());
+
+ OString aMessage = rPreamble +
+ aVertexShaderSource +
+ aFragmentShaderSource +
+ aDeviceInfo;
+
+ return generateMD5(aMessage.getStr(), aMessage.getLength());
+ }
+#endif
+
+ OString getCacheFolder()
+ {
+ OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
+ rtl::Bootstrap::expandMacros(url);
+
+ osl::Directory::create(url);
+
+ return OUStringToOString(url, RTL_TEXTENCODING_UTF8);
+ }
+
+
+ bool writeProgramBinary( const OString& rBinaryFileName,
+ const std::vector<sal_uInt8>& rBinary )
+ {
+ osl::File aFile(OStringToOUString(rBinaryFileName, RTL_TEXTENCODING_UTF8));
+ osl::FileBase::RC eStatus = aFile.open(
+ osl_File_OpenFlag_Write | osl_File_OpenFlag_Create );
+
+ if( eStatus != osl::FileBase::E_None )
+ {
+ // when file already exists we do not have to save it:
+ // we can be sure that the binary to save is exactly equal
+ // to the already saved binary, since they have the same hash value
+ if( eStatus == osl::FileBase::E_EXIST )
+ {
+ SAL_INFO( "vcl.opengl",
+ "No binary program saved. A file with the same hash already exists: '" << rBinaryFileName << "'" );
+ return true;
+ }
+ return false;
+ }
+
+ sal_uInt64 nBytesWritten = 0;
+ aFile.write( rBinary.data(), rBinary.size(), nBytesWritten );
+
+ assert( rBinary.size() == nBytesWritten );
+
+ return true;
+ }
+
+ bool readProgramBinary( const OString& rBinaryFileName,
+ std::vector<sal_uInt8>& rBinary )
+ {
+ osl::File aFile( OStringToOUString( rBinaryFileName, RTL_TEXTENCODING_UTF8 ) );
+ if(aFile.open( osl_File_OpenFlag_Read ) == osl::FileBase::E_None)
+ {
+ sal_uInt64 nSize = 0;
+ aFile.getSize( nSize );
+ rBinary.resize( nSize );
+ sal_uInt64 nBytesRead = 0;
+ aFile.read( rBinary.data(), nSize, nBytesRead );
+ assert( nSize == nBytesRead );
+ VCL_GL_INFO("Loading file: '" << rBinaryFileName << "': success" );
+ return true;
+ }
+ else
+ {
+ VCL_GL_INFO("Loading file: '" << rBinaryFileName << "': FAIL");
+ }
+
+ return false;
+ }
+
+ OString createFileName( std::u16string_view rVertexShaderName,
+ std::u16string_view rFragmentShaderName,
+ std::u16string_view rGeometryShaderName,
+ std::string_view rDigest )
+ {
+ OString aFileName = getCacheFolder() +
+ OUStringToOString( rVertexShaderName, RTL_TEXTENCODING_UTF8 ) + "-" +
+ OUStringToOString( rFragmentShaderName, RTL_TEXTENCODING_UTF8 ) + "-";
+ if (!rGeometryShaderName.empty())
+ aFileName += OUStringToOString( rGeometryShaderName, RTL_TEXTENCODING_UTF8 ) + "-";
+ aFileName += OString::Concat(rDigest) + ".bin";
+ return aFileName;
+ }
+
+ GLint loadProgramBinary( GLuint nProgramID, const OString& rBinaryFileName )
+ {
+ GLint nResult = GL_FALSE;
+ GLenum nBinaryFormat;
+ std::vector<sal_uInt8> aBinary;
+ if( readProgramBinary( rBinaryFileName, aBinary ) && aBinary.size() > GLenumSize )
+ {
+ GLint nBinaryLength = aBinary.size() - GLenumSize;
+
+ // Extract binary format
+ sal_uInt8* pBF = reinterpret_cast<sal_uInt8*>(&nBinaryFormat);
+ for( size_t i = 0; i < GLenumSize; ++i )
+ {
+ pBF[i] = aBinary[nBinaryLength + i];
+ }
+
+ // Load the program
+ glProgramBinary( nProgramID, nBinaryFormat, aBinary.data(), nBinaryLength );
+
+ // Check the program
+ glGetProgramiv(nProgramID, GL_LINK_STATUS, &nResult);
+ }
+ return nResult;
+ }
+
+ void saveProgramBinary( GLint nProgramID, const OString& rBinaryFileName )
+ {
+ GLint nBinaryLength = 0;
+ GLenum nBinaryFormat = GL_NONE;
+
+ glGetProgramiv( nProgramID, GL_PROGRAM_BINARY_LENGTH, &nBinaryLength );
+ if( nBinaryLength <= 0 )
+ {
+ SAL_WARN( "vcl.opengl", "Binary size is zero" );
+ return;
+ }
+
+ std::vector<sal_uInt8> aBinary( nBinaryLength + GLenumSize );
+
+ glGetProgramBinary( nProgramID, nBinaryLength, nullptr, &nBinaryFormat, aBinary.data() );
+
+ const sal_uInt8* pBF = reinterpret_cast<const sal_uInt8*>(&nBinaryFormat);
+ aBinary.insert( aBinary.end(), pBF, pBF + GLenumSize );
+
+ SAL_INFO("vcl.opengl", "Program id: " << nProgramID );
+ SAL_INFO("vcl.opengl", "Binary length: " << nBinaryLength );
+ SAL_INFO("vcl.opengl", "Binary format: " << nBinaryFormat );
+
+ if( !writeProgramBinary( rBinaryFileName, aBinary ) )
+ SAL_WARN("vcl.opengl", "Writing binary file '" << rBinaryFileName << "': FAIL");
+ else
+ SAL_INFO("vcl.opengl", "Writing binary file '" << rBinaryFileName << "': success");
+ }
+}
+
+#if defined _WIN32
+OString OpenGLHelper::GetDigest( const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ std::string_view rPreamble )
+{
+ return getStringDigest(rVertexShaderName, rFragmentShaderName, rPreamble);
+}
+#endif
+
+GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ const OUString& rGeometryShaderName,
+ std::string_view preamble,
+ std::string_view rDigest)
+{
+ OpenGLZone aZone;
+
+ gbInShaderCompile = true;
+
+ bool bHasGeometryShader = !rGeometryShaderName.isEmpty();
+
+ // create the program object
+ GLint ProgramID = glCreateProgram();
+
+ // read shaders from file
+ OString aVertexShaderSource = getShaderSource(rVertexShaderName);
+ OString aFragmentShaderSource = getShaderSource(rFragmentShaderName);
+ OString aGeometryShaderSource;
+ if (bHasGeometryShader)
+ aGeometryShaderSource = getShaderSource(rGeometryShaderName);
+
+ GLint bBinaryResult = GL_FALSE;
+ if (epoxy_has_gl_extension("GL_ARB_get_program_binary") && !rDigest.empty())
+ {
+ OString aFileName =
+ createFileName(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, rDigest);
+ bBinaryResult = loadProgramBinary(ProgramID, aFileName);
+ CHECK_GL_ERROR();
+ }
+
+ if( bBinaryResult != GL_FALSE )
+ return ProgramID;
+
+ if (bHasGeometryShader)
+ VCL_GL_INFO("Load shader: vertex " << rVertexShaderName << " fragment " << rFragmentShaderName << " geometry " << rGeometryShaderName);
+ else
+ VCL_GL_INFO("Load shader: vertex " << rVertexShaderName << " fragment " << rFragmentShaderName);
+ // Create the shaders
+ GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
+ GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
+ GLuint GeometryShaderID = 0;
+ if (bHasGeometryShader)
+ GeometryShaderID = glCreateShader(GL_GEOMETRY_SHADER);
+
+ GLint Result = GL_FALSE;
+
+ // Compile Vertex Shader
+ if( !preamble.empty())
+ addPreamble( aVertexShaderSource, preamble );
+ char const * VertexSourcePointer = aVertexShaderSource.getStr();
+ glShaderSource(VertexShaderID, 1, &VertexSourcePointer , nullptr);
+ glCompileShader(VertexShaderID);
+
+ // Check Vertex Shader
+ glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
+ if (!Result)
+ return LogCompilerError(VertexShaderID, "vertex",
+ rVertexShaderName, true);
+
+ // Compile Fragment Shader
+ if( !preamble.empty())
+ addPreamble( aFragmentShaderSource, preamble );
+ char const * FragmentSourcePointer = aFragmentShaderSource.getStr();
+ glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , nullptr);
+ glCompileShader(FragmentShaderID);
+
+ // Check Fragment Shader
+ glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
+ if (!Result)
+ return LogCompilerError(FragmentShaderID, "fragment",
+ rFragmentShaderName, true);
+
+ if (bHasGeometryShader)
+ {
+ // Compile Geometry Shader
+ if( !preamble.empty())
+ addPreamble( aGeometryShaderSource, preamble );
+ char const * GeometrySourcePointer = aGeometryShaderSource.getStr();
+ glShaderSource(GeometryShaderID, 1, &GeometrySourcePointer , nullptr);
+ glCompileShader(GeometryShaderID);
+
+ // Check Geometry Shader
+ glGetShaderiv(GeometryShaderID, GL_COMPILE_STATUS, &Result);
+ if (!Result)
+ return LogCompilerError(GeometryShaderID, "geometry",
+ rGeometryShaderName, true);
+ }
+
+ // Link the program
+ glAttachShader(ProgramID, VertexShaderID);
+ glAttachShader(ProgramID, FragmentShaderID);
+ if (bHasGeometryShader)
+ glAttachShader(ProgramID, GeometryShaderID);
+
+ if (epoxy_has_gl_extension("GL_ARB_get_program_binary") && !rDigest.empty())
+ {
+ glProgramParameteri(ProgramID, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
+ glLinkProgram(ProgramID);
+ glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
+ if (!Result)
+ {
+ SAL_WARN("vcl.opengl", "linking failed: " << Result );
+ return LogCompilerError(ProgramID, "program", "<both>", false);
+ }
+ OString aFileName =
+ createFileName(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, rDigest);
+ saveProgramBinary(ProgramID, aFileName);
+ }
+ else
+ {
+ glLinkProgram(ProgramID);
+ }
+
+ glDeleteShader(VertexShaderID);
+ glDeleteShader(FragmentShaderID);
+ if (bHasGeometryShader)
+ glDeleteShader(GeometryShaderID);
+
+ // Check the program
+ glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
+ if (!Result)
+ return LogCompilerError(ProgramID, "program", "<both>", false);
+
+ CHECK_GL_ERROR();
+
+ // Ensure we bump our counts before we leave the shader zone.
+ { OpenGLZone aMakeProgress; }
+ gbInShaderCompile = false;
+
+ return ProgramID;
+}
+
+GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ std::string_view preamble,
+ std::string_view rDigest)
+{
+ return LoadShaders(rVertexShaderName, rFragmentShaderName, OUString(), preamble, rDigest);
+}
+
+GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ const OUString& rGeometryShaderName)
+{
+ return LoadShaders(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, std::string_view(), std::string_view());
+}
+
+GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName)
+{
+ return LoadShaders(rVertexShaderName, rFragmentShaderName, OUString(), "", "");
+}
+
+void OpenGLHelper::renderToFile(tools::Long nWidth, tools::Long nHeight, const OUString& rFileName)
+{
+ OpenGLZone aZone;
+
+ std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nWidth*nHeight*4]);
+ glReadPixels(0, 0, nWidth, nHeight, OptimalBufferFormat(), GL_UNSIGNED_BYTE, pBuffer.get());
+ BitmapEx aBitmap = ConvertBufferToBitmapEx(pBuffer.get(), nWidth, nHeight);
+ try {
+ vcl::PNGWriter aWriter( aBitmap );
+ SvFileStream sOutput( rFileName, StreamMode::WRITE );
+ aWriter.Write( sOutput );
+ sOutput.Close();
+ } catch (...) {
+ SAL_WARN("vcl.opengl", "Error writing png to " << rFileName);
+ }
+
+ CHECK_GL_ERROR();
+}
+
+GLenum OpenGLHelper::OptimalBufferFormat()
+{
+#ifdef _WIN32
+ return GL_BGRA; // OpenGLSalBitmap is internally ScanlineFormat::N24BitTcBgr
+#else
+ return GL_RGBA; // OpenGLSalBitmap is internally ScanlineFormat::N24BitTcRgb
+#endif
+}
+
+BitmapEx OpenGLHelper::ConvertBufferToBitmapEx(const sal_uInt8* const pBuffer, tools::Long nWidth, tools::Long nHeight)
+{
+ assert(pBuffer);
+ Bitmap aBitmap(Size(nWidth, nHeight), vcl::PixelFormat::N24_BPP);
+ AlphaMask aAlpha(Size(nWidth, nHeight));
+
+ {
+ BitmapScopedWriteAccess pWriteAccess( aBitmap );
+ AlphaScopedWriteAccess pAlphaWriteAccess( aAlpha );
+#ifdef _WIN32
+ assert(pWriteAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr);
+ assert(pWriteAccess->IsTopDown());
+ assert(pAlphaWriteAccess->IsTopDown());
+#else
+ assert(pWriteAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb);
+ assert(!pWriteAccess->IsTopDown());
+ assert(!pAlphaWriteAccess->IsTopDown());
+#endif
+ assert(pAlphaWriteAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal);
+
+ size_t nCurPos = 0;
+ for( tools::Long y = 0; y < nHeight; ++y)
+ {
+#ifdef _WIN32
+ Scanline pScan = pWriteAccess->GetScanline(y);
+ Scanline pAlphaScan = pAlphaWriteAccess->GetScanline(y);
+#else
+ Scanline pScan = pWriteAccess->GetScanline(nHeight-1-y);
+ Scanline pAlphaScan = pAlphaWriteAccess->GetScanline(nHeight-1-y);
+#endif
+ for( tools::Long x = 0; x < nWidth; ++x )
+ {
+ *pScan++ = pBuffer[nCurPos];
+ *pScan++ = pBuffer[nCurPos+1];
+ *pScan++ = pBuffer[nCurPos+2];
+
+ nCurPos += 3;
+ *pAlphaScan++ = static_cast<sal_uInt8>( 255 - pBuffer[nCurPos++] );
+ }
+ }
+ }
+ return BitmapEx(aBitmap, aAlpha);
+}
+
+const char* OpenGLHelper::GLErrorString(GLenum errorCode)
+{
+ static const struct {
+ GLenum code;
+ const char *string;
+ } errors[]=
+ {
+ /* GL */
+ {GL_NO_ERROR, "no error"},
+ {GL_INVALID_ENUM, "invalid enumerant"},
+ {GL_INVALID_VALUE, "invalid value"},
+ {GL_INVALID_OPERATION, "invalid operation"},
+ {GL_STACK_OVERFLOW, "stack overflow"},
+ {GL_STACK_UNDERFLOW, "stack underflow"},
+ {GL_OUT_OF_MEMORY, "out of memory"},
+ {GL_INVALID_FRAMEBUFFER_OPERATION, "invalid framebuffer operation"},
+
+ {0, nullptr }
+ };
+
+ int i;
+
+ for (i=0; errors[i].string; i++)
+ {
+ if (errors[i].code == errorCode)
+ {
+ return errors[i].string;
+ }
+ }
+
+ return nullptr;
+}
+
+std::ostream& operator<<(std::ostream& rStrm, const glm::vec4& rPos)
+{
+ rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ", " << rPos[3] << ")";
+ return rStrm;
+}
+
+std::ostream& operator<<(std::ostream& rStrm, const glm::vec3& rPos)
+{
+ rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ")";
+ return rStrm;
+}
+
+std::ostream& operator<<(std::ostream& rStrm, const glm::mat4& rMatrix)
+{
+ for(int i = 0; i < 4; ++i)
+ {
+ rStrm << "\n( ";
+ for(int j = 0; j < 4; ++j)
+ {
+ rStrm << rMatrix[j][i];
+ rStrm << " ";
+ }
+ rStrm << ")\n";
+ }
+ return rStrm;
+}
+
+void OpenGLHelper::createFramebuffer(tools::Long nWidth, tools::Long nHeight, GLuint& nFramebufferId,
+ GLuint& nRenderbufferDepthId, GLuint& nRenderbufferColorId)
+{
+ OpenGLZone aZone;
+
+ // create a renderbuffer for depth attachment
+ glGenRenderbuffers(1, &nRenderbufferDepthId);
+ glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferDepthId);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, nWidth, nHeight);
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+
+ glGenTextures(1, &nRenderbufferColorId);
+ glBindTexture(GL_TEXTURE_2D, nRenderbufferColorId);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, nWidth, nHeight, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, nRenderbufferColorId, 0);
+
+ // create a framebuffer object and attach renderbuffer
+ glGenFramebuffers(1, &nFramebufferId);
+ glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ glBindFramebuffer(GL_FRAMEBUFFER, nFramebufferId);
+ // attach a renderbuffer to FBO color attachment point
+ glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferColorId);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, nRenderbufferColorId);
+ glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ // attach a renderbuffer to depth attachment point
+ glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferDepthId);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, nRenderbufferDepthId);
+ GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_FRAMEBUFFER_COMPLETE)
+ {
+ SAL_WARN("vcl.opengl", "invalid framebuffer status");
+ }
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ CHECK_GL_ERROR();
+}
+
+float OpenGLHelper::getGLVersion()
+{
+ float fVersion = 1.0;
+ const GLubyte* aVersion = glGetString( GL_VERSION );
+ if( aVersion && aVersion[0] )
+ {
+ fVersion = aVersion[0] - '0';
+ if( aVersion[1] == '.' && aVersion[2] )
+ {
+ fVersion += (aVersion[2] - '0')/10.0;
+ }
+ }
+
+ CHECK_GL_ERROR();
+ return fVersion;
+}
+
+void OpenGLHelper::checkGLError(const char* pFile, size_t nLine)
+{
+ OpenGLZone aZone;
+
+ int nErrors = 0;
+ for (;;)
+ {
+ GLenum glErr = glGetError();
+ if (glErr == GL_NO_ERROR)
+ {
+ break;
+ }
+ const char* sError = OpenGLHelper::GLErrorString(glErr);
+ if (!sError)
+ sError = "no message available";
+
+ SAL_WARN("vcl.opengl", "GL Error " << std::hex << std::setw(4) << std::setfill('0') << glErr << std::dec << std::setw(0) << std::setfill(' ') << " (" << sError << ") in file " << pFile << " at line " << nLine);
+
+ // tdf#93798 - apitrace appears to sometimes cause issues with an infinite loop here.
+ if (++nErrors >= 8)
+ {
+ SAL_WARN("vcl.opengl", "Breaking potentially recursive glGetError loop");
+ break;
+ }
+ }
+}
+
+bool OpenGLHelper::isDeviceDenylisted()
+{
+ static bool bSet = false;
+ static bool bDenylisted = true; // assume the worst
+ if (!bSet)
+ {
+ OpenGLZone aZone;
+
+#if defined( _WIN32 )
+ WinOpenGLDeviceInfo aInfo;
+ bDenylisted = aInfo.isDeviceBlocked();
+
+ if (DriverBlocklist::GetWindowsVersion() == 0x00060001 && /* Windows 7 */
+ (aInfo.GetAdapterVendorID() == "0x1002" || aInfo.GetAdapterVendorID() == "0x1022")) /* AMD */
+ {
+ SAL_INFO("vcl.opengl", "Relaxing watchdog timings.");
+ OpenGLZone::relaxWatchdogTimings();
+ }
+#else
+ bDenylisted = false;
+#endif
+ bSet = true;
+ }
+
+ return bDenylisted;
+}
+
+bool OpenGLHelper::supportsOpenGL()
+{
+ if( getenv("SAL_DISABLEGL") != nullptr )
+ return false;
+ if (!ImplGetSVData()->mpDefInst->supportsOpenGL())
+ return false;
+ if( isDeviceDenylisted())
+ return false;
+ if( officecfg::Office::Common::VCL::DisableOpenGL::get())
+ return false;
+ WatchdogThread::start();
+ return true;
+}
+
+namespace
+{
+
+enum class CrashWatchdogTimingMode
+{
+ NORMAL,
+ SHADER_COMPILE
+};
+
+class CrashWatchdogTimings
+{
+private:
+ std::vector<CrashWatchdogTimingsValues> maTimingValues;
+ std::atomic<bool> mbRelaxed;
+
+public:
+ CrashWatchdogTimings();
+
+ void setRelax(bool bRelaxed)
+ {
+ mbRelaxed = bRelaxed;
+ }
+
+ CrashWatchdogTimingsValues const & getWatchdogTimingsValues(CrashWatchdogTimingMode eMode)
+ {
+ size_t index = (eMode == CrashWatchdogTimingMode::SHADER_COMPILE) ? 1 : 0;
+ index = mbRelaxed ? index + 2 : index;
+
+ return maTimingValues[index];
+ }
+};
+
+CrashWatchdogTimings gWatchdogTimings;
+
+CrashWatchdogTimings::CrashWatchdogTimings()
+ : maTimingValues{
+ {{6, 20} /* 1.5s, 5s */, {20, 120} /* 5s, 30s */,
+ {60, 240} /* 15s, 60s */, {60, 240} /* 15s, 60s */}
+ }
+ , mbRelaxed(false)
+{
+}
+
+} // namespace
+
+/**
+ * Called from a signal handler or watchdog thread if we get
+ * a crash or hang in some GL code.
+ */
+void OpenGLZone::hardDisable()
+{
+ // protect ourselves from double calling etc.
+ static bool bDisabled = false;
+ if (bDisabled)
+ return;
+
+ bDisabled = true;
+
+ // Disable the OpenGL support
+ std::shared_ptr<comphelper::ConfigurationChanges> xChanges(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::VCL::DisableOpenGL::set(true, xChanges);
+ xChanges->commit();
+
+ // Force synchronous config write
+ css::uno::Reference< css::util::XFlushable >(
+ css::configuration::theDefaultProvider::get(
+ comphelper::getProcessComponentContext()),
+ css::uno::UNO_QUERY_THROW)->flush();
+}
+
+void OpenGLZone::relaxWatchdogTimings()
+{
+ gWatchdogTimings.setRelax(true);
+}
+
+void OpenGLZone::checkDebug( int nUnchanged, const CrashWatchdogTimingsValues& aTimingValues )
+{
+ SAL_INFO("vcl.watchdog", "GL watchdog - unchanged "
+ << nUnchanged << " enter count " << enterCount() << " type "
+ << (gbInShaderCompile ? "in shader" : "normal gl")
+ << " breakpoints mid: " << aTimingValues.mnDisableEntries
+ << " max " << aTimingValues.mnAbortAfter);
+}
+
+const CrashWatchdogTimingsValues& OpenGLZone::getCrashWatchdogTimingsValues()
+{
+ // The shader compiler can take a long time, first time.
+ CrashWatchdogTimingMode eMode = gbInShaderCompile ? CrashWatchdogTimingMode::SHADER_COMPILE : CrashWatchdogTimingMode::NORMAL;
+ return gWatchdogTimings.getWatchdogTimingsValues(eMode);
+}
+
+void OpenGLHelper::debugMsgStream(std::ostringstream const &pStream)
+{
+ debugMsgPrint(
+ 0, "%" SAL_PRIxUINT32 ": %s", osl_getThreadIdentifier(nullptr), pStream.str().c_str());
+}
+
+void OpenGLHelper::debugMsgStreamWarn(std::ostringstream const &pStream)
+{
+ debugMsgPrint(
+ 1, "%" SAL_PRIxUINT32 ": %s", osl_getThreadIdentifier(nullptr), pStream.str().c_str());
+}
+
+void OpenGLHelper::debugMsgPrint(const int nType, const char *pFormat, ...)
+{
+ va_list aArgs;
+ va_start (aArgs, pFormat);
+
+ char pStr[1044];
+#ifdef _WIN32
+#define vsnprintf _vsnprintf
+#endif
+ vsnprintf(pStr, sizeof(pStr), pFormat, aArgs);
+ pStr[sizeof(pStr)-20] = '\0';
+
+ bool bHasContext = OpenGLContext::hasCurrent();
+ if (!bHasContext)
+ strcat(pStr, " (no GL context)");
+
+ if (nType == 0)
+ {
+ SAL_INFO("vcl.opengl", pStr);
+ }
+ else if (nType == 1)
+ {
+ SAL_WARN("vcl.opengl", pStr);
+ }
+
+ if (bHasContext)
+ {
+ OpenGLZone aZone;
+
+ if (epoxy_has_gl_extension("GL_KHR_debug"))
+ glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION,
+ GL_DEBUG_TYPE_OTHER,
+ 1, // one[sic] id is as good as another ?
+ // GL_DEBUG_SEVERITY_NOTIFICATION for >= GL4.3 ?
+ GL_DEBUG_SEVERITY_LOW,
+ strlen(pStr), pStr);
+ else if (epoxy_has_gl_extension("GL_AMD_debug_output"))
+ glDebugMessageInsertAMD(GL_DEBUG_CATEGORY_APPLICATION_AMD,
+ GL_DEBUG_SEVERITY_LOW_AMD,
+ 1, // one[sic] id is as good as another ?
+ strlen(pStr), pStr);
+ }
+
+ va_end (aArgs);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/README.deprecated b/vcl/source/opengl/README.deprecated
new file mode 100644
index 000000000..eb033a0fd
--- /dev/null
+++ b/vcl/source/opengl/README.deprecated
@@ -0,0 +1,23 @@
+deprecated features
+
+GL_LIGHTING
+GL_TEXTURE_2D
+GL_POINT_SMOOTH
+GL_TEXTURE_WRAP_S
+GL_TEXTURE_WRAP_T
+glBegin
+glEnd
+
+
+GLSL
+
+texture*D
+varying
+attribute
+missing version string
+
+gl_FragColor
+gl_FragData
+gl_Normal
+gl_NormalMatrix
+gl_Vertex
diff --git a/vcl/source/opengl/opengl_denylist_windows.xml b/vcl/source/opengl/opengl_denylist_windows.xml
new file mode 100644
index 000000000..70f4e0192
--- /dev/null
+++ b/vcl/source/opengl/opengl_denylist_windows.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+* 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/.
+-->
+
+<!--
+ entry attributes:
+ os - "all", "7", "8", "8_1", "10"
+ vendor - "all", "intel", "amd", "nvidia", "microsoft"
+ compare - "less", "less_equal", "greater", "greater_equal", "equal", "not_equal", "between_exclusive", "between_inclusive", "between_inclusive_start"
+ version
+ minVersion
+ maxVersion
+-->
+
+<root>
+ <allowlist>
+ </allowlist>
+ <denylist>
+ <!-- tdf#125516: crash on preview of slide transitions, or in slideshow, when OpenGL rendering enabled Windows 10, with Intel DCH packaged driver -->
+ <entry os="10" vendor="intel" compare="between_inclusive_start" minVersion="26.20.100.6861" maxVersion="26.20.100.7584"><!-- tdf#125516 -->
+ <device id="all"/>
+ </entry>
+ </denylist>
+</root>
diff --git a/vcl/source/opengl/win/WinDeviceInfo.cxx b/vcl/source/opengl/win/WinDeviceInfo.cxx
new file mode 100644
index 000000000..ef6a840f5
--- /dev/null
+++ b/vcl/source/opengl/win/WinDeviceInfo.cxx
@@ -0,0 +1,491 @@
+/* -*- 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/.
+ */
+
+#include <opengl/win/WinDeviceInfo.hxx>
+
+#include <driverblocklist.hxx>
+#include <config_folders.h>
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <objbase.h>
+#include <setupapi.h>
+#include <algorithm>
+#include <cstdint>
+#include <memory>
+#include <string_view>
+
+#include <osl/file.hxx>
+#include <rtl/bootstrap.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+#include <desktop/crashreport.hxx>
+
+namespace {
+
+bool GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName, OUString& destString, int type)
+{
+ HKEY key;
+ DWORD dwcbData;
+ DWORD dValue;
+ DWORD resultType;
+ LONG result;
+ bool retval = true;
+
+ result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyLocation, 0, KEY_QUERY_VALUE, &key);
+ if (result != ERROR_SUCCESS)
+ {
+ return false;
+ }
+
+ switch (type)
+ {
+ case REG_DWORD:
+ {
+ // We only use this for vram size
+ dwcbData = sizeof(dValue);
+ result = RegQueryValueExW(key, keyName, nullptr, &resultType,
+ reinterpret_cast<LPBYTE>(&dValue), &dwcbData);
+ if (result == ERROR_SUCCESS && resultType == REG_DWORD)
+ {
+ dValue = dValue / 1024 / 1024;
+ destString += OUString::number(int32_t(dValue));
+ }
+ else
+ {
+ retval = false;
+ }
+ break;
+ }
+ case REG_MULTI_SZ:
+ {
+ // A chain of null-separated strings; we convert the nulls to spaces
+ WCHAR wCharValue[1024];
+ dwcbData = sizeof(wCharValue);
+
+ result = RegQueryValueExW(key, keyName, nullptr, &resultType,
+ reinterpret_cast<LPBYTE>(wCharValue), &dwcbData);
+ if (result == ERROR_SUCCESS && resultType == REG_MULTI_SZ)
+ {
+ // This bit here could probably be cleaner.
+ bool isValid = false;
+
+ DWORD strLen = dwcbData/sizeof(wCharValue[0]);
+ for (DWORD i = 0; i < strLen; i++)
+ {
+ if (wCharValue[i] == '\0')
+ {
+ if (i < strLen - 1 && wCharValue[i + 1] == '\0')
+ {
+ isValid = true;
+ break;
+ }
+ else
+ {
+ wCharValue[i] = ' ';
+ }
+ }
+ }
+
+ // ensure wCharValue is null terminated
+ wCharValue[strLen-1] = '\0';
+
+ if (isValid)
+ destString = OUString(o3tl::toU(wCharValue));
+
+ }
+ else
+ {
+ retval = false;
+ }
+
+ break;
+ }
+ }
+ RegCloseKey(key);
+
+ return retval;
+}
+
+// The device ID is a string like PCI\VEN_15AD&DEV_0405&SUBSYS_040515AD
+// this function is used to extract the id's out of it
+uint32_t ParseIDFromDeviceID(const OUString &key, const char *prefix, int length)
+{
+ OUString id = key.toAsciiUpperCase();
+ OUString aPrefix = OUString::fromUtf8(prefix);
+ int32_t start = id.indexOf(aPrefix);
+ if (start != -1)
+ {
+ id = id.copy(start + aPrefix.getLength(), length);
+ }
+ return id.toUInt32(16);
+}
+
+/* Other interesting places for info:
+ * IDXGIAdapter::GetDesc()
+ * IDirectDraw7::GetAvailableVidMem()
+ * e->GetAvailableTextureMem()
+ * */
+
+template<typename T> void appendIntegerWithPadding(OUString& rString, T value, sal_uInt32 nChars)
+{
+ rString += "0x";
+ OUString aValue = OUString::number(value, 16);
+ sal_Int32 nLength = aValue.getLength();
+ sal_uInt32 nPadLength = nChars - nLength;
+ assert(nPadLength >= 0);
+ OUStringBuffer aBuffer;
+ for (sal_uInt32 i = 0; i < nPadLength; ++i)
+ {
+ aBuffer.append("0");
+ }
+ rString += aBuffer.makeStringAndClear() + aValue;
+}
+
+#define DEVICE_KEY_PREFIX L"\\Registry\\Machine\\"
+}
+
+WinOpenGLDeviceInfo::WinOpenGLDeviceInfo():
+ mbHasDualGPU(false),
+ mbRDP(false)
+{
+ GetData();
+}
+
+static OUString getDenylistFile()
+{
+ OUString url("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER);
+ rtl::Bootstrap::expandMacros(url);
+
+ return url + "/opengl/opengl_denylist_windows.xml";
+}
+
+bool WinOpenGLDeviceInfo::FindBlocklistedDeviceInList()
+{
+ return DriverBlocklist::IsDeviceBlocked( getDenylistFile(), DriverBlocklist::VersionType::OpenGL,
+ maDriverVersion, maAdapterVendorID, maAdapterDeviceID);
+}
+
+namespace {
+
+OUString getCacheFolder()
+{
+ OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
+ rtl::Bootstrap::expandMacros(url);
+
+ osl::Directory::create(url);
+
+ return url;
+}
+
+void writeToLog(SvStream& rStrm, const char* pKey, std::u16string_view rVal)
+{
+ rStrm.WriteCharPtr(pKey);
+ rStrm.WriteCharPtr(": ");
+ rStrm.WriteOString(OUStringToOString(rVal, RTL_TEXTENCODING_UTF8));
+ rStrm.WriteChar('\n');
+}
+
+}
+
+bool WinOpenGLDeviceInfo::isDeviceBlocked()
+{
+ CrashReporter::addKeyValue("OpenGLVendor", maAdapterVendorID, CrashReporter::AddItem);
+ CrashReporter::addKeyValue("OpenGLDevice", maAdapterDeviceID, CrashReporter::AddItem);
+ CrashReporter::addKeyValue("OpenGLDriver", maDriverVersion, CrashReporter::Write);
+
+ SAL_INFO("vcl.opengl", maDriverVersion);
+ SAL_INFO("vcl.opengl", maDriverDate);
+ SAL_INFO("vcl.opengl", maDeviceID);
+ SAL_INFO("vcl.opengl", maAdapterVendorID);
+ SAL_INFO("vcl.opengl", maAdapterDeviceID);
+ SAL_INFO("vcl.opengl", maAdapterSubsysID);
+ SAL_INFO("vcl.opengl", maDeviceKey);
+ SAL_INFO("vcl.opengl", maDeviceString);
+
+ OUString aCacheFolder = getCacheFolder();
+
+ OUString aCacheFile(aCacheFolder + "/opengl_device.log");
+ SvFileStream aOpenGLLogFile(aCacheFile, StreamMode::WRITE|StreamMode::TRUNC);
+
+ writeToLog(aOpenGLLogFile, "DriverVersion", maDriverVersion);
+ writeToLog(aOpenGLLogFile, "DriverDate", maDriverDate);
+ writeToLog(aOpenGLLogFile, "DeviceID", maDeviceID);
+ writeToLog(aOpenGLLogFile, "AdapterVendorID", maAdapterVendorID);
+ writeToLog(aOpenGLLogFile, "AdapterDeviceID", maAdapterDeviceID);
+ writeToLog(aOpenGLLogFile, "AdapterSubsysID", maAdapterSubsysID);
+ writeToLog(aOpenGLLogFile, "DeviceKey", maDeviceKey);
+ writeToLog(aOpenGLLogFile, "DeviceString", maDeviceString);
+
+ // Check if the device is blocked from the downloaded blocklist. If not, check
+ // the static list after that. This order is used so that we can later escape
+ // out of static blocks (i.e. if we were wrong or something was patched, we
+ // can back out our static block without doing a release).
+ if (mbRDP)
+ {
+ SAL_WARN("vcl.opengl", "all OpenGL blocked for RDP sessions");
+ return true;
+ }
+
+ return FindBlocklistedDeviceInList();
+}
+
+void WinOpenGLDeviceInfo::GetData()
+{
+ DISPLAY_DEVICEW displayDevice;
+ displayDevice.cb = sizeof(displayDevice);
+
+ int deviceIndex = 0;
+
+ while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0))
+ {
+ if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
+ {
+ break;
+ }
+ deviceIndex++;
+ }
+
+ // make sure the string is null terminated
+ // (using the term "null" here to mean a zero UTF-16 unit)
+ if (wcsnlen(displayDevice.DeviceKey, SAL_N_ELEMENTS(displayDevice.DeviceKey))
+ == SAL_N_ELEMENTS(displayDevice.DeviceKey))
+ {
+ // we did not find a null
+ SAL_WARN("vcl.opengl", "string not null terminated");
+ return;
+ }
+
+ /* DeviceKey is "reserved" according to MSDN so we'll be careful with it */
+ /* check that DeviceKey begins with DEVICE_KEY_PREFIX */
+ /* some systems have a DeviceKey starting with \REGISTRY\Machine\ so we need to compare case insensitively */
+ if (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX, SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1) != 0)
+ {
+ SAL_WARN("vcl.opengl", "incorrect DeviceKey");
+ return;
+ }
+
+ // chop off DEVICE_KEY_PREFIX
+ maDeviceKey = o3tl::toU(displayDevice.DeviceKey) + SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1;
+
+ maDeviceID = o3tl::toU(displayDevice.DeviceID);
+ maDeviceString = o3tl::toU(displayDevice.DeviceString);
+
+ if (maDeviceID.isEmpty() &&
+ (maDeviceString == "RDPDD Chained DD" ||
+ (maDeviceString == "RDPUDD Chained DD")))
+ {
+ // we need to block RDP as it does not provide OpenGL 2.1+
+ mbRDP = true;
+ SAL_WARN("vcl.opengl", "RDP => blocked");
+ return;
+ }
+
+ /* create a device information set composed of the current display device */
+ HDEVINFO devinfo = SetupDiGetClassDevsW(nullptr, o3tl::toW(maDeviceID.getStr()), nullptr,
+ DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES);
+
+ if (devinfo != INVALID_HANDLE_VALUE)
+ {
+ HKEY key;
+ LONG result;
+ WCHAR value[255];
+ DWORD dwcbData;
+ SP_DEVINFO_DATA devinfoData;
+ DWORD memberIndex = 0;
+
+ devinfoData.cbSize = sizeof(devinfoData);
+ /* enumerate device information elements in the device information set */
+ while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData))
+ {
+ /* get a string that identifies the device's driver key */
+ if (SetupDiGetDeviceRegistryPropertyW(devinfo,
+ &devinfoData,
+ SPDRP_DRIVER,
+ nullptr,
+ reinterpret_cast<PBYTE>(value),
+ sizeof(value),
+ nullptr))
+ {
+ OUString driverKey(OUString::Concat("System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value));
+ result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey.getStr()), 0, KEY_QUERY_VALUE, &key);
+ if (result == ERROR_SUCCESS)
+ {
+ /* we've found the driver we're looking for */
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
+ reinterpret_cast<LPBYTE>(value), &dwcbData);
+ if (result == ERROR_SUCCESS)
+ {
+ maDriverVersion = OUString(o3tl::toU(value));
+ }
+ else
+ {
+ // If the entry wasn't found, assume the worst (0.0.0.0).
+ maDriverVersion = OUString("0.0.0.0");
+ }
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
+ reinterpret_cast<LPBYTE>(value), &dwcbData);
+ if (result == ERROR_SUCCESS)
+ {
+ maDriverDate = o3tl::toU(value);
+ }
+ else
+ {
+ // Again, assume the worst
+ maDriverDate = OUString("01-01-1970");
+ }
+ RegCloseKey(key);
+ break;
+ }
+ }
+ }
+
+ SetupDiDestroyDeviceInfoList(devinfo);
+ }
+ else
+ {
+ SAL_WARN("vcl.opengl", "invalid handle value");
+ }
+
+ appendIntegerWithPadding(maAdapterVendorID, ParseIDFromDeviceID(maDeviceID, "VEN_", 4), 4);
+ appendIntegerWithPadding(maAdapterDeviceID, ParseIDFromDeviceID(maDeviceID, "&DEV_", 4), 4);
+ appendIntegerWithPadding(maAdapterSubsysID, ParseIDFromDeviceID(maDeviceID, "&SUBSYS_", 8), 8);
+
+ // We now check for second display adapter.
+
+ // Device interface class for display adapters.
+ CLSID GUID_DISPLAY_DEVICE_ARRIVAL;
+ HRESULT hresult = CLSIDFromString(L"{1CA05180-A699-450A-9A0C-DE4FBE3DDD89}",
+ &GUID_DISPLAY_DEVICE_ARRIVAL);
+ if (hresult == NOERROR)
+ {
+ devinfo = SetupDiGetClassDevsW(&GUID_DISPLAY_DEVICE_ARRIVAL,
+ nullptr, nullptr,
+ DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
+
+ if (devinfo != INVALID_HANDLE_VALUE)
+ {
+ HKEY key;
+ LONG result;
+ WCHAR value[255];
+ DWORD dwcbData;
+ SP_DEVINFO_DATA devinfoData;
+ DWORD memberIndex = 0;
+ devinfoData.cbSize = sizeof(devinfoData);
+
+ OUString aAdapterDriver2;
+ OUString aDeviceID2;
+ OUString aDriverVersion2;
+ OUString aDriverDate2;
+ uint32_t adapterVendorID2;
+ uint32_t adapterDeviceID2;
+
+ /* enumerate device information elements in the device information set */
+ while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData))
+ {
+ /* get a string that identifies the device's driver key */
+ if (SetupDiGetDeviceRegistryPropertyW(devinfo,
+ &devinfoData,
+ SPDRP_DRIVER,
+ nullptr,
+ reinterpret_cast<PBYTE>(value),
+ sizeof(value),
+ nullptr))
+ {
+ OUString driverKey2(OUString::Concat("System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value));
+ result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey2.getStr()), 0, KEY_QUERY_VALUE, &key);
+ if (result == ERROR_SUCCESS)
+ {
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"MatchingDeviceId", nullptr,
+ nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData);
+ if (result != ERROR_SUCCESS)
+ {
+ continue;
+ }
+ aDeviceID2 = o3tl::toU(value);
+ OUString aAdapterVendorID2String;
+ OUString aAdapterDeviceID2String;
+ adapterVendorID2 = ParseIDFromDeviceID(aDeviceID2, "VEN_", 4);
+ appendIntegerWithPadding(aAdapterVendorID2String, adapterVendorID2, 4);
+ adapterDeviceID2 = ParseIDFromDeviceID(aDeviceID2, "&DEV_", 4);
+ appendIntegerWithPadding(aAdapterDeviceID2String, adapterDeviceID2, 4);
+ if (maAdapterVendorID == aAdapterVendorID2String &&
+ maAdapterDeviceID == aAdapterDeviceID2String)
+ {
+ RegCloseKey(key);
+ continue;
+ }
+
+ // If this device is missing driver information, it is unlikely to
+ // be a real display adapter.
+ if (!GetKeyValue(o3tl::toW(driverKey2.getStr()), L"InstalledDisplayDrivers",
+ aAdapterDriver2, REG_MULTI_SZ))
+ {
+ RegCloseKey(key);
+ continue;
+ }
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
+ reinterpret_cast<LPBYTE>(value), &dwcbData);
+ if (result != ERROR_SUCCESS)
+ {
+ RegCloseKey(key);
+ continue;
+ }
+ aDriverVersion2 = o3tl::toU(value);
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
+ reinterpret_cast<LPBYTE>(value), &dwcbData);
+ if (result != ERROR_SUCCESS)
+ {
+ RegCloseKey(key);
+ continue;
+ }
+ aDriverDate2 = o3tl::toU(value);
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"Device Description", nullptr,
+ nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData);
+ if (result != ERROR_SUCCESS)
+ {
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverDesc", nullptr, nullptr,
+ reinterpret_cast<LPBYTE>(value), &dwcbData);
+ }
+ RegCloseKey(key);
+ if (result == ERROR_SUCCESS)
+ {
+ mbHasDualGPU = true;
+ maDeviceString2 = o3tl::toU(value);
+ maDeviceID2 = aDeviceID2;
+ maDeviceKey2 = driverKey2;
+ maDriverVersion2 = aDriverVersion2;
+ maDriverDate2 = aDriverDate2;
+ appendIntegerWithPadding(maAdapterVendorID2, adapterVendorID2, 4);
+ appendIntegerWithPadding(maAdapterDeviceID2, adapterDeviceID2, 4);
+ appendIntegerWithPadding(maAdapterSubsysID2, ParseIDFromDeviceID(maDeviceID2, "&SUBSYS_", 8), 8);
+ break;
+ }
+ }
+ }
+ }
+
+ SetupDiDestroyDeviceInfoList(devinfo);
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/win/context.cxx b/vcl/source/opengl/win/context.cxx
new file mode 100644
index 000000000..5031ec6f0
--- /dev/null
+++ b/vcl/source/opengl/win/context.cxx
@@ -0,0 +1,663 @@
+/* -*- 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/.
+ */
+
+#include <memory>
+#include <string_view>
+#include <thread>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <vcl/syschild.hxx>
+
+#include <sal/log.hxx>
+#include <comphelper/windowserrorstring.hxx>
+#include <opengl/zone.hxx>
+#include <win/wincomp.hxx>
+#include <win/saldata.hxx>
+#include <win/salframe.h>
+#include <win/salinst.h>
+#include <epoxy/wgl.h>
+#include <ControlCacheKey.hxx>
+
+static std::vector<HGLRC> g_vShareList;
+static bool g_bAnyCurrent;
+
+namespace {
+
+class GLWinWindow : public GLWindow
+{
+public:
+ HWND hWnd;
+ HDC hDC;
+ HGLRC hRC;
+ GLWinWindow();
+};
+
+}
+
+GLWinWindow::GLWinWindow()
+ : hWnd(nullptr)
+ , hDC(nullptr)
+ , hRC(nullptr)
+{
+}
+
+namespace {
+
+class WinOpenGLContext : public OpenGLContext
+{
+public:
+ virtual void initWindow() override;
+private:
+ GLWinWindow m_aGLWin;
+ virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
+ virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
+ virtual bool ImplInit() override;
+ virtual void makeCurrent() override;
+ virtual void destroyCurrentContext() override;
+ virtual bool isCurrent() override;
+ virtual bool isAnyCurrent() override;
+ virtual void resetCurrent() override;
+ virtual void swapBuffers() override;
+};
+
+}
+
+void WinOpenGLContext::swapBuffers()
+{
+ OpenGLZone aZone;
+
+ SwapBuffers(m_aGLWin.hDC);
+
+ BuffersSwapped();
+}
+
+void WinOpenGLContext::resetCurrent()
+{
+ clearCurrent();
+
+ OpenGLZone aZone;
+
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+}
+
+static void ensureDispatchTable()
+{
+ thread_local bool bEpoxyDispatchMakeCurrentCalled = false;
+ if (!bEpoxyDispatchMakeCurrentCalled)
+ {
+ epoxy_handle_external_wglMakeCurrent();
+ bEpoxyDispatchMakeCurrentCalled = true;
+ }
+}
+
+bool WinOpenGLContext::isCurrent()
+{
+ OpenGLZone aZone;
+ if (!g_bAnyCurrent || !m_aGLWin.hRC)
+ return false;
+ ensureDispatchTable();
+ return wglGetCurrentContext() == m_aGLWin.hRC && wglGetCurrentDC() == m_aGLWin.hDC;
+}
+
+bool WinOpenGLContext::isAnyCurrent()
+{
+ return g_bAnyCurrent && wglGetCurrentContext() != nullptr;
+}
+
+void WinOpenGLContext::makeCurrent()
+{
+ if (isCurrent())
+ return;
+
+ OpenGLZone aZone;
+
+ clearCurrent();
+
+ ensureDispatchTable();
+
+ if (!wglMakeCurrent(m_aGLWin.hDC, m_aGLWin.hRC))
+ {
+ g_bAnyCurrent = false;
+ DWORD nLastError = GetLastError();
+ if (nLastError != ERROR_SUCCESS)
+ SAL_WARN("vcl.opengl", "wglMakeCurrent failed: " << WindowsErrorString(nLastError));
+ return;
+ }
+
+ g_bAnyCurrent = true;
+
+ registerAsCurrent();
+}
+
+void WinOpenGLContext::initWindow()
+{
+ if( !m_pChildWindow )
+ {
+ SystemWindowData winData = generateWinData(mpWindow, false);
+ m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
+ }
+
+ if (m_pChildWindow)
+ {
+ InitChildWindow(m_pChildWindow.get());
+ const SystemEnvData* sysData(m_pChildWindow->GetSystemData());
+ m_aGLWin.hWnd = sysData->hWnd;
+ }
+
+ m_aGLWin.hDC = GetDC(m_aGLWin.hWnd);
+}
+
+void WinOpenGLContext::destroyCurrentContext()
+{
+ if (m_aGLWin.hRC)
+ {
+ std::vector<HGLRC>::iterator itr = std::remove(g_vShareList.begin(), g_vShareList.end(), m_aGLWin.hRC);
+ if (itr != g_vShareList.end())
+ g_vShareList.erase(itr);
+
+ if (wglGetCurrentContext() != nullptr)
+ {
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ }
+ wglDeleteContext( m_aGLWin.hRC );
+ ReleaseDC( m_aGLWin.hWnd, m_aGLWin.hDC );
+ m_aGLWin.hRC = nullptr;
+ }
+}
+
+static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch (message)
+ {
+ case WM_CREATE:
+ return 0;
+ case WM_CLOSE:
+ PostQuitMessage(0);
+ return 0;
+ case WM_DESTROY:
+ return 0;
+ default:
+ return DefWindowProcW(hwnd, message, wParam, lParam);
+ }
+}
+
+static bool InitTempWindow(HWND& hwnd, int width, int height, const PIXELFORMATDESCRIPTOR& inPfd, GLWinWindow& glWin)
+{
+ OpenGLZone aZone;
+
+ PIXELFORMATDESCRIPTOR pfd = inPfd;
+ int ret;
+ WNDCLASSW wc;
+ wc.style = 0;
+ wc.lpfnWndProc = WndProc;
+ wc.cbClsExtra = wc.cbWndExtra = 0;
+ wc.hInstance = nullptr;
+ wc.hIcon = nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = nullptr;
+ wc.lpszMenuName = nullptr;
+ wc.lpszClassName = L"GLRenderer";
+ RegisterClassW(&wc);
+ hwnd = CreateWindowW(wc.lpszClassName, nullptr, WS_DISABLED, 0, 0, width, height, nullptr, nullptr, wc.hInstance, nullptr);
+ glWin.hDC = GetDC(hwnd);
+
+ int nPixelFormat = ChoosePixelFormat(glWin.hDC, &pfd);
+ if (!nPixelFormat)
+ {
+ ReleaseDC(hwnd, glWin.hDC);
+ DestroyWindow(hwnd);
+ return false;
+ }
+ ret = SetPixelFormat(glWin.hDC, nPixelFormat, &pfd);
+ if(!ret)
+ {
+ ReleaseDC(hwnd, glWin.hDC);
+ DestroyWindow(hwnd);
+ return false;
+ }
+ glWin.hRC = wglCreateContext(glWin.hDC);
+ if(!(glWin.hRC))
+ {
+ ReleaseDC(hwnd, glWin.hDC);
+ DestroyWindow(hwnd);
+ return false;
+ }
+ ret = wglMakeCurrent(glWin.hDC, glWin.hRC);
+ if(!ret)
+ {
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hwnd, glWin.hDC);
+ DestroyWindow(hwnd);
+ return false;
+ }
+ g_bAnyCurrent = false;
+
+ return true;
+}
+
+static bool WGLisExtensionSupported(const char *extension)
+{
+ OpenGLZone aZone;
+
+ const size_t extlen = strlen(extension);
+ const char *supported = nullptr;
+
+ // Try to use wglGetExtensionStringARB on current DC, if possible
+ PROC wglGetExtString = wglGetProcAddress("wglGetExtensionsStringARB");
+
+ if (wglGetExtString)
+ supported = reinterpret_cast<char*(__stdcall*)(HDC)>(wglGetExtString)(wglGetCurrentDC());
+ // If that failed, try standard OpenGL extensions string
+ if (supported == nullptr)
+ supported = reinterpret_cast<char const *>(glGetString(GL_EXTENSIONS));
+ // If that failed too, must be no extensions supported
+ if (supported == nullptr)
+ return false;
+
+ // Begin examination at start of string, increment by 1 on false match
+ for (const char* p = supported; ; p++)
+ {
+ // Advance p up to the next possible match
+ p = strstr(p, extension);
+
+ if (p == nullptr)
+ return false; // No Match
+
+ // Make sure that match is at the start of the string or that
+ // the previous char is a space, or else we could accidentally
+ // match "wglFunkywglExtension" with "wglExtension"
+
+ // Also, make sure that the following character is space or null
+ // or else "wglExtensionTwo" might match "wglExtension"
+ if ((p==supported || p[-1]==' ') && (p[extlen]=='\0' || p[extlen]==' '))
+ return true; // Match
+ }
+}
+
+static bool InitMultisample(const PIXELFORMATDESCRIPTOR& pfd, int& rPixelFormat,
+ bool bUseDoubleBufferedRendering, bool bRequestVirtualDevice)
+{
+ OpenGLZone aZone;
+
+ HWND hWnd = nullptr;
+ GLWinWindow glWin;
+ // Create a temp window to check whether support multi-sample, if support, get the format
+ if (!InitTempWindow(hWnd, 32, 32, pfd, glWin))
+ {
+ SAL_WARN("vcl.opengl", "Can't create temp window to test");
+ return false;
+ }
+
+ // See if the string exists in WGL
+ if (!WGLisExtensionSupported("WGL_ARB_multisample"))
+ {
+ SAL_WARN("vcl.opengl", "Device doesn't support multisample");
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hWnd, glWin.hDC);
+ DestroyWindow(hWnd);
+ return false;
+ }
+ // Get our pixel format
+ PFNWGLCHOOSEPIXELFORMATARBPROC fn_wglChoosePixelFormatARB = reinterpret_cast<PFNWGLCHOOSEPIXELFORMATARBPROC>(wglGetProcAddress("wglChoosePixelFormatARB"));
+ if (!fn_wglChoosePixelFormatARB)
+ {
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hWnd, glWin.hDC);
+ DestroyWindow(hWnd);
+ return false;
+ }
+ // Get our current device context
+ HDC hDC = GetDC(hWnd);
+
+ int pixelFormat;
+ int valid;
+ UINT numFormats;
+ float fAttributes[] = {0,0};
+ // These attributes are the bits we want to test for in our sample.
+ // Everything is pretty standard, the only one we want to
+ // really focus on is the WGL_SAMPLE_BUFFERS_ARB and WGL_SAMPLES_ARB.
+ // These two are going to do the main testing for whether or not
+ // we support multisampling on this hardware.
+ int iAttributes[] =
+ {
+ WGL_DOUBLE_BUFFER_ARB,GL_TRUE,
+ WGL_DRAW_TO_WINDOW_ARB,GL_TRUE,
+ WGL_SUPPORT_OPENGL_ARB,GL_TRUE,
+ WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
+ WGL_COLOR_BITS_ARB,24,
+ WGL_ALPHA_BITS_ARB,8,
+ WGL_DEPTH_BITS_ARB,24,
+ WGL_STENCIL_BITS_ARB,0,
+ WGL_SAMPLE_BUFFERS_ARB,GL_TRUE,
+ WGL_SAMPLES_ARB,8,
+ 0,0
+ };
+
+ if (!bUseDoubleBufferedRendering)
+ {
+ // Use asserts to make sure the iAttributes array is not changed without changing these ugly
+ // hardcode indexes into it.
+ assert(iAttributes[0] == WGL_DOUBLE_BUFFER_ARB);
+ iAttributes[1] = GL_FALSE;
+ }
+
+ if (bRequestVirtualDevice)
+ {
+ assert(iAttributes[2] == WGL_DRAW_TO_WINDOW_ARB);
+ iAttributes[2] = WGL_DRAW_TO_BITMAP_ARB;
+ }
+
+ bool bArbMultisampleSupported = false;
+
+ // First we check to see if we can get a pixel format for 8 samples
+ valid = fn_wglChoosePixelFormatARB(hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats);
+ // If we returned true, and our format count is greater than 1
+ if (valid && numFormats >= 1)
+ {
+ bArbMultisampleSupported = true;
+ rPixelFormat = pixelFormat;
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hWnd, glWin.hDC);
+ DestroyWindow(hWnd);
+ return bArbMultisampleSupported;
+ }
+ // Our pixel format with 8 samples failed, test for 2 samples
+ assert(iAttributes[18] == WGL_SAMPLES_ARB);
+ iAttributes[19] = 2;
+ valid = fn_wglChoosePixelFormatARB(hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats);
+ if (valid && numFormats >= 1)
+ {
+ bArbMultisampleSupported = true;
+ rPixelFormat = pixelFormat;
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hWnd, glWin.hDC);
+ DestroyWindow(hWnd);
+ return bArbMultisampleSupported;
+ }
+ // Return the valid format
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hWnd, glWin.hDC);
+ DestroyWindow(hWnd);
+
+ return bArbMultisampleSupported;
+}
+
+namespace
+{
+
+bool tryShaders(const OUString& rVertexShader, const OUString& rFragmentShader, const OUString& rGeometryShader = "", std::string_view rPreamble = "")
+{
+ GLint nId;
+
+ // Somewhat mysteriously, the OpenGLHelper::LoadShaders() API saves a compiled binary of the
+ // shader only if you give it the digest of the shaders. We have API to calculate the digest
+ // only of the combination of vertex and fragment (but not geometry) shader. So if we have a
+ // geometry shader, we should not save the binary.
+ if (rGeometryShader.isEmpty())
+ {
+ nId = OpenGLHelper::LoadShaders(rVertexShader, rFragmentShader, rPreamble, OpenGLHelper::GetDigest( rVertexShader, rFragmentShader, rPreamble));
+ }
+ else
+ {
+ assert(rPreamble.empty());
+ nId = OpenGLHelper::LoadShaders(rVertexShader, rFragmentShader, rGeometryShader);
+ }
+ if (!nId)
+ return false;
+
+ // We're interested in the error returned by glDeleteProgram().
+ glGetError();
+
+ glDeleteProgram(nId);
+ return glGetError() == GL_NO_ERROR;
+}
+
+bool compiledShaderBinariesWork()
+{
+ static bool bBeenHere = false;
+ static bool bResult;
+
+ if (bBeenHere)
+ return bResult;
+
+ bBeenHere = true;
+
+ bResult =
+ (
+#if 0 // Only look at shaders used by slideshow for now
+ // canvas
+ tryShaders("dummyVertexShader", "linearMultiColorGradientFragmentShader") &&
+ tryShaders("dummyVertexShader", "linearTwoColorGradientFragmentShader") &&
+ tryShaders("dummyVertexShader", "radialMultiColorGradientFragmentShader") &&
+ tryShaders("dummyVertexShader", "radialTwoColorGradientFragmentShader") &&
+ tryShaders("dummyVertexShader", "rectangularMultiColorGradientFragmentShader") &&
+ tryShaders("dummyVertexShader", "rectangularTwoColorGradientFragmentShader") &&
+#endif
+ // slideshow
+ tryShaders("reflectionVertexShader", "reflectionFragmentShader") &&
+ tryShaders("basicVertexShader", "basicFragmentShader") &&
+ tryShaders("vortexVertexShader", "vortexFragmentShader", "vortexGeometryShader") &&
+ tryShaders("basicVertexShader", "rippleFragmentShader") &&
+ tryShaders("glitterVertexShader", "glitterFragmentShader") &&
+ tryShaders("honeycombVertexShader", "honeycombFragmentShader", "honeycombGeometryShader"));
+
+ return bResult;
+}
+
+} // unnamed namespace
+
+bool WinOpenGLContext::ImplInit()
+{
+ static bool bFirstCall = true;
+
+ OpenGLZone aZone;
+
+ VCL_GL_INFO("OpenGLContext::ImplInit----start");
+ // PixelFormat tells Windows how we want things to be
+ PIXELFORMATDESCRIPTOR PixelFormatFront =
+ {
+ sizeof(PIXELFORMATDESCRIPTOR),
+ 1, // Version Number
+ PFD_SUPPORT_OPENGL,
+ PFD_TYPE_RGBA, // Request An RGBA Format
+ BYTE(32), // Select Our Color Depth
+ 0, 0, 0, 0, 0, 0, // Color Bits Ignored
+ 0, // No Alpha Buffer
+ 0, // Shift Bit Ignored
+ 0, // No Accumulation Buffer
+ 0, 0, 0, 0, // Accumulation Bits Ignored
+ 24, // 24 bit z-buffer
+ 8, // stencil buffer
+ 0, // No Auxiliary Buffer
+ 0, // now ignored
+ 0, // Reserved
+ 0, 0, 0 // Layer Masks Ignored
+ };
+
+ PixelFormatFront.dwFlags |= PFD_DOUBLEBUFFER;
+ PixelFormatFront.dwFlags |= PFD_DRAW_TO_WINDOW;
+
+ // we must check whether can set the MSAA
+ int WindowPix = 0;
+ bool bMultiSampleSupport = false;
+
+ bMultiSampleSupport = InitMultisample(PixelFormatFront, WindowPix, /*bUseDoubleBufferedRendering*/true, false);
+
+ if (bMultiSampleSupport && WindowPix != 0)
+ {
+ m_aGLWin.bMultiSampleSupported = true;
+ }
+ else
+ {
+ WindowPix = ChoosePixelFormat(m_aGLWin.hDC, &PixelFormatFront);
+#if OSL_DEBUG_LEVEL > 0
+ PIXELFORMATDESCRIPTOR pfd;
+ DescribePixelFormat(m_aGLWin.hDC, WindowPix, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
+ SAL_WARN("vcl.opengl", "Render Target: Window: " << static_cast<int>((pfd.dwFlags & PFD_DRAW_TO_WINDOW) != 0) << ", Bitmap: " << static_cast<int>((pfd.dwFlags & PFD_DRAW_TO_BITMAP) != 0));
+ SAL_WARN("vcl.opengl", "Supports OpenGL: " << static_cast<int>((pfd.dwFlags & PFD_SUPPORT_OPENGL) != 0));
+#endif
+ }
+
+ if (WindowPix == 0)
+ {
+ SAL_WARN("vcl.opengl", "Invalid pixelformat");
+ return false;
+ }
+
+ if (!SetPixelFormat(m_aGLWin.hDC, WindowPix, &PixelFormatFront))
+ {
+ SAL_WARN("vcl.opengl", "SetPixelFormat failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+
+ HGLRC hTempRC = wglCreateContext(m_aGLWin.hDC);
+ if (hTempRC == nullptr)
+ {
+ SAL_WARN("vcl.opengl", "wglCreateContext failed: "<< WindowsErrorString(GetLastError()));
+ return false;
+ }
+
+ if (!wglMakeCurrent(m_aGLWin.hDC, hTempRC))
+ {
+ g_bAnyCurrent = false;
+ SAL_WARN("vcl.opengl", "wglMakeCurrent failed: "<< WindowsErrorString(GetLastError()));
+ return false;
+ }
+
+ g_bAnyCurrent = true;
+
+ if (!InitGL())
+ {
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(hTempRC);
+ return false;
+ }
+
+ HGLRC hSharedCtx = nullptr;
+ if (!g_vShareList.empty())
+ hSharedCtx = g_vShareList.front();
+
+ if (!wglCreateContextAttribsARB)
+ {
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(hTempRC);
+ return false;
+ }
+
+ // now setup the shared context; this needs a temporary context already
+ // set up in order to work
+ int const attribs [] =
+ {
+#ifdef DBG_UTIL
+ WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB,
+#endif
+ 0
+ };
+ m_aGLWin.hRC = wglCreateContextAttribsARB(m_aGLWin.hDC, hSharedCtx, attribs);
+ if (m_aGLWin.hRC == nullptr)
+ {
+ SAL_WARN("vcl.opengl", "wglCreateContextAttribsARB failed: "<< WindowsErrorString(GetLastError()));
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(hTempRC);
+ return false;
+ }
+
+ if (!compiledShaderBinariesWork())
+ {
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(hTempRC);
+ return false;
+ }
+
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(hTempRC);
+
+ if (!wglMakeCurrent(m_aGLWin.hDC, m_aGLWin.hRC))
+ {
+ g_bAnyCurrent = false;
+ SAL_WARN("vcl.opengl", "wglMakeCurrent failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+
+ g_bAnyCurrent = true;
+
+ if (bFirstCall)
+ {
+ // Checking texture size
+ GLint nMaxTextureSize;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &nMaxTextureSize);
+ if (nMaxTextureSize <= 4096)
+ SAL_WARN("vcl.opengl", "Max texture size is " << nMaxTextureSize
+ << ". This may not be enough for normal operation.");
+ else
+ VCL_GL_INFO("Max texture size: " << nMaxTextureSize);
+
+ // Trying to make a texture and check its size
+ for (GLint nWidthHeight = 1023; nWidthHeight < nMaxTextureSize; nWidthHeight += (nWidthHeight + 1))
+ {
+ glTexImage2D(GL_PROXY_TEXTURE_2D, 0, 4, nWidthHeight, nWidthHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, nullptr);
+ CHECK_GL_ERROR();
+ if (glGetError() == GL_NO_ERROR)
+ {
+ GLint nWidth = 0;
+ GLint nHeight = 0;
+ glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &nWidth);
+ glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &nHeight);
+ VCL_GL_INFO("Created texture " << nWidthHeight << "," << nWidthHeight << " reports size: " << nWidth << ", " << nHeight);
+ }
+ else
+ {
+ SAL_WARN("vcl.opengl", "Error when creating a " << nWidthHeight << ", " << nWidthHeight << " test texture.");
+ }
+ }
+ }
+
+ InitGLDebugging();
+
+ g_vShareList.push_back(m_aGLWin.hRC);
+
+ RECT clientRect;
+ GetClientRect(WindowFromDC(m_aGLWin.hDC), &clientRect);
+ m_aGLWin.Width = clientRect.right - clientRect.left;
+ m_aGLWin.Height = clientRect.bottom - clientRect.top;
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+ registerAsCurrent();
+
+ bFirstCall = false;
+
+ return true;
+}
+
+OpenGLContext* WinSalInstance::CreateOpenGLContext()
+{
+ return new WinOpenGLContext;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/x11/context.cxx b/vcl/source/opengl/x11/context.cxx
new file mode 100644
index 000000000..97822b310
--- /dev/null
+++ b/vcl/source/opengl/x11/context.cxx
@@ -0,0 +1,519 @@
+/* -*- 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/.
+ */
+
+#include <memory>
+#include <vcl/lazydelete.hxx>
+#include <vcl/syschild.hxx>
+
+#include <svdata.hxx>
+
+#include <unx/saldisp.hxx>
+#include <unx/salframe.h>
+#include <unx/salgdi.h>
+#include <unx/salinst.h>
+#include <unx/salvd.h>
+#include <unx/x11/xlimits.hxx>
+
+#include <opengl/zone.hxx>
+
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <sal/log.hxx>
+#include <o3tl/string_view.hxx>
+
+static std::vector<GLXContext> g_vShareList;
+static bool g_bAnyCurrent;
+
+namespace {
+
+class X11OpenGLContext : public OpenGLContext
+{
+public:
+ virtual void initWindow() override;
+private:
+ GLX11Window m_aGLWin;
+ virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
+ virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
+ virtual bool ImplInit() override;
+ void initGLWindow(Visual* pVisual);
+ virtual SystemWindowData generateWinData(vcl::Window* pParent, bool bRequestLegacyContext) override;
+ virtual void makeCurrent() override;
+ virtual void destroyCurrentContext() override;
+ virtual bool isCurrent() override;
+ virtual bool isAnyCurrent() override;
+ virtual void sync() override;
+ virtual void resetCurrent() override;
+ virtual void swapBuffers() override;
+};
+
+#ifdef DBG_UTIL
+ int unxErrorHandler(Display* dpy, XErrorEvent* event)
+ {
+ char err[256];
+ char req[256];
+ char minor[256];
+ XGetErrorText(dpy, event->error_code, err, 256);
+ XGetErrorText(dpy, event->request_code, req, 256);
+ XGetErrorText(dpy, event->minor_code, minor, 256);
+ SAL_WARN("vcl.opengl", "Error: " << err << ", Req: " << req << ", Minor: " << minor);
+ return 0;
+ }
+#endif
+
+ typedef int (*errorHandler)(Display* /*dpy*/, XErrorEvent* /*evnt*/);
+
+ class TempErrorHandler
+ {
+ private:
+ errorHandler oldErrorHandler;
+ Display* mdpy;
+
+ public:
+ TempErrorHandler(Display* dpy, errorHandler newErrorHandler)
+ : oldErrorHandler(nullptr)
+ , mdpy(dpy)
+ {
+ if (mdpy)
+ {
+ XLockDisplay(dpy);
+ XSync(dpy, false);
+ oldErrorHandler = XSetErrorHandler(newErrorHandler);
+ }
+ }
+
+ ~TempErrorHandler()
+ {
+ if (mdpy)
+ {
+ // sync so that we possibly get an XError
+ glXWaitGL();
+ XSync(mdpy, false);
+ XSetErrorHandler(oldErrorHandler);
+ XUnlockDisplay(mdpy);
+ }
+ }
+ };
+
+ bool errorTriggered;
+ int oglErrorHandler( Display* /*dpy*/, XErrorEvent* /*evnt*/ )
+ {
+ errorTriggered = true;
+
+ return 0;
+ }
+
+ GLXFBConfig* getFBConfig(Display* dpy, Window win, int& nBestFBC)
+ {
+ OpenGLZone aZone;
+
+ if( dpy == nullptr || !glXQueryExtension( dpy, nullptr, nullptr ) )
+ return nullptr;
+
+ VCL_GL_INFO("window: " << win);
+
+ XWindowAttributes xattr;
+ if( !XGetWindowAttributes( dpy, win, &xattr ) )
+ {
+ SAL_WARN("vcl.opengl", "Failed to get window attributes for fbconfig " << win);
+ xattr.screen = nullptr;
+ xattr.visual = nullptr;
+ }
+
+ int screen = XScreenNumberOfScreen( xattr.screen );
+
+ // TODO: moggi: Select colour channel depth based on visual attributes, not hardcoded */
+ static int visual_attribs[] =
+ {
+ GLX_DOUBLEBUFFER, True,
+ GLX_X_RENDERABLE, True,
+ GLX_RED_SIZE, 8,
+ GLX_GREEN_SIZE, 8,
+ GLX_BLUE_SIZE, 8,
+ GLX_ALPHA_SIZE, 8,
+ GLX_DEPTH_SIZE, 24,
+ GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
+ None
+ };
+
+ int fbCount = 0;
+ GLXFBConfig* pFBC = glXChooseFBConfig( dpy,
+ screen,
+ visual_attribs, &fbCount );
+
+ if(!pFBC)
+ {
+ SAL_WARN("vcl.opengl", "no suitable fb format found");
+ return nullptr;
+ }
+
+ int best_num_samp = -1;
+ for(int i = 0; i < fbCount; ++i)
+ {
+ XVisualInfo* pVi = glXGetVisualFromFBConfig( dpy, pFBC[i] );
+ if(pVi && (xattr.visual && pVi->visualid == xattr.visual->visualid) )
+ {
+ // pick the one with the most samples per pixel
+ int nSampleBuf = 0;
+ int nSamples = 0;
+ glXGetFBConfigAttrib( dpy, pFBC[i], GLX_SAMPLE_BUFFERS, &nSampleBuf );
+ glXGetFBConfigAttrib( dpy, pFBC[i], GLX_SAMPLES , &nSamples );
+
+ if ( nBestFBC < 0 || (nSampleBuf && ( nSamples > best_num_samp )) )
+ {
+ nBestFBC = i;
+ best_num_samp = nSamples;
+ }
+ }
+ XFree( pVi );
+ }
+
+ return pFBC;
+ }
+}
+
+void X11OpenGLContext::sync()
+{
+ OpenGLZone aZone;
+ glXWaitGL();
+ XSync(m_aGLWin.dpy, false);
+}
+
+void X11OpenGLContext::swapBuffers()
+{
+ OpenGLZone aZone;
+
+ glXSwapBuffers(m_aGLWin.dpy, m_aGLWin.win);
+
+ BuffersSwapped();
+}
+
+void X11OpenGLContext::resetCurrent()
+{
+ clearCurrent();
+
+ OpenGLZone aZone;
+
+ if (m_aGLWin.dpy)
+ {
+ glXMakeCurrent(m_aGLWin.dpy, None, nullptr);
+ g_bAnyCurrent = false;
+ }
+}
+
+bool X11OpenGLContext::isCurrent()
+{
+ OpenGLZone aZone;
+ return g_bAnyCurrent && m_aGLWin.ctx && glXGetCurrentContext() == m_aGLWin.ctx &&
+ glXGetCurrentDrawable() == m_aGLWin.win;
+}
+
+bool X11OpenGLContext::isAnyCurrent()
+{
+ return g_bAnyCurrent && glXGetCurrentContext() != None;
+}
+
+SystemWindowData X11OpenGLContext::generateWinData(vcl::Window* pParent, bool /*bRequestLegacyContext*/)
+{
+ OpenGLZone aZone;
+
+ SystemWindowData aWinData;
+ aWinData.pVisual = nullptr;
+ aWinData.bClipUsingNativeWidget = false;
+
+ const SystemEnvData* sysData(pParent->GetSystemData());
+
+ Display *dpy = static_cast<Display*>(sysData->pDisplay);
+ Window win = sysData->GetWindowHandle(pParent->ImplGetFrame());
+
+ if( dpy == nullptr || !glXQueryExtension( dpy, nullptr, nullptr ) )
+ return aWinData;
+
+ int best_fbc = -1;
+ GLXFBConfig* pFBC = getFBConfig(dpy, win, best_fbc);
+
+ if (!pFBC)
+ return aWinData;
+
+ XVisualInfo* vi = nullptr;
+ if( best_fbc != -1 )
+ vi = glXGetVisualFromFBConfig( dpy, pFBC[best_fbc] );
+
+ XFree(pFBC);
+
+ if( vi )
+ {
+ VCL_GL_INFO("using VisualID " << vi->visualid);
+ aWinData.pVisual = static_cast<void*>(vi->visual);
+ }
+
+ return aWinData;
+}
+
+bool X11OpenGLContext::ImplInit()
+{
+ if (!m_aGLWin.dpy)
+ return false;
+
+ OpenGLZone aZone;
+
+ GLXContext pSharedCtx( nullptr );
+#ifdef DBG_UTIL
+ TempErrorHandler aErrorHandler(m_aGLWin.dpy, unxErrorHandler);
+#endif
+
+ VCL_GL_INFO("OpenGLContext::ImplInit----start");
+
+ if (!g_vShareList.empty())
+ pSharedCtx = g_vShareList.front();
+
+ //tdf#112166 for, e.g. VirtualBox GL, claiming OpenGL 2.1
+ static bool hasCreateContextAttribsARB = glXGetProcAddress(reinterpret_cast<const GLubyte*>("glXCreateContextAttribsARB")) != nullptr;
+ if (hasCreateContextAttribsARB && !mbRequestLegacyContext)
+ {
+ int best_fbc = -1;
+ GLXFBConfig* pFBC = getFBConfig(m_aGLWin.dpy, m_aGLWin.win, best_fbc);
+
+ if (pFBC && best_fbc != -1)
+ {
+ int const pContextAttribs[] =
+ {
+#if 0 // defined(DBG_UTIL)
+ GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
+ GLX_CONTEXT_MINOR_VERSION_ARB, 2,
+#endif
+ None
+
+ };
+ m_aGLWin.ctx = glXCreateContextAttribsARB(m_aGLWin.dpy, pFBC[best_fbc], pSharedCtx, /* direct, not via X */ GL_TRUE, pContextAttribs);
+ SAL_INFO_IF(m_aGLWin.ctx, "vcl.opengl", "created a 3.2 core context");
+ }
+ else
+ SAL_WARN("vcl.opengl", "unable to find correct FBC");
+ }
+
+ if (!m_aGLWin.ctx)
+ {
+ if (!m_aGLWin.vi)
+ return false;
+
+ SAL_WARN("vcl.opengl", "attempting to create a non-double-buffered "
+ "visual matching the context");
+
+ m_aGLWin.ctx = glXCreateContext(m_aGLWin.dpy,
+ m_aGLWin.vi,
+ pSharedCtx,
+ GL_TRUE /* direct, not via X server */);
+ }
+
+ if( m_aGLWin.ctx )
+ {
+ g_vShareList.push_back( m_aGLWin.ctx );
+ }
+ else
+ {
+ SAL_WARN("vcl.opengl", "unable to create GLX context");
+ return false;
+ }
+
+ if( !glXMakeCurrent( m_aGLWin.dpy, m_aGLWin.win, m_aGLWin.ctx ) )
+ {
+ g_bAnyCurrent = false;
+ SAL_WARN("vcl.opengl", "unable to select current GLX context");
+ return false;
+ }
+
+ g_bAnyCurrent = true;
+
+ int glxMinor, glxMajor;
+ double nGLXVersion = 0;
+ if( glXQueryVersion( m_aGLWin.dpy, &glxMajor, &glxMinor ) )
+ nGLXVersion = glxMajor + 0.1*glxMinor;
+ SAL_INFO("vcl.opengl", "available GLX version: " << nGLXVersion);
+
+ SAL_INFO("vcl.opengl", "available GL extensions: " << glGetString(GL_EXTENSIONS));
+
+ XWindowAttributes aWinAttr;
+ if( !XGetWindowAttributes( m_aGLWin.dpy, m_aGLWin.win, &aWinAttr ) )
+ {
+ SAL_WARN("vcl.opengl", "Failed to get window attributes on " << m_aGLWin.win);
+ m_aGLWin.Width = 0;
+ m_aGLWin.Height = 0;
+ }
+ else
+ {
+ m_aGLWin.Width = aWinAttr.width;
+ m_aGLWin.Height = aWinAttr.height;
+ }
+
+ if( m_aGLWin.HasGLXExtension("GLX_SGI_swap_control" ) )
+ {
+ // enable vsync
+ typedef GLint (*glXSwapIntervalProc)(GLint);
+ glXSwapIntervalProc glXSwapInterval = reinterpret_cast<glXSwapIntervalProc>(glXGetProcAddress( reinterpret_cast<const GLubyte*>("glXSwapIntervalSGI") ));
+ if( glXSwapInterval )
+ {
+ TempErrorHandler aLocalErrorHandler(m_aGLWin.dpy, oglErrorHandler);
+
+ errorTriggered = false;
+
+ glXSwapInterval( 1 );
+
+ if( errorTriggered )
+ SAL_WARN("vcl.opengl", "error when trying to set swap interval, NVIDIA or Mesa bug?");
+ else
+ VCL_GL_INFO("set swap interval to 1 (enable vsync)");
+ }
+ }
+
+ bool bRet = InitGL();
+ InitGLDebugging();
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+ registerAsCurrent();
+
+ return bRet;
+}
+
+void X11OpenGLContext::makeCurrent()
+{
+ if (isCurrent())
+ return;
+
+ OpenGLZone aZone;
+
+ clearCurrent();
+
+#ifdef DBG_UTIL
+ TempErrorHandler aErrorHandler(m_aGLWin.dpy, unxErrorHandler);
+#endif
+
+ if (m_aGLWin.dpy)
+ {
+ if (!glXMakeCurrent( m_aGLWin.dpy, m_aGLWin.win, m_aGLWin.ctx ))
+ {
+ g_bAnyCurrent = false;
+ SAL_WARN("vcl.opengl", "OpenGLContext::makeCurrent failed "
+ "on drawable " << m_aGLWin.win);
+ return;
+ }
+ g_bAnyCurrent = true;
+ }
+
+ registerAsCurrent();
+}
+
+void X11OpenGLContext::destroyCurrentContext()
+{
+ if(!m_aGLWin.ctx)
+ return;
+
+ std::vector<GLXContext>::iterator itr = std::remove( g_vShareList.begin(), g_vShareList.end(), m_aGLWin.ctx );
+ if (itr != g_vShareList.end())
+ g_vShareList.erase(itr);
+
+ glXMakeCurrent(m_aGLWin.dpy, None, nullptr);
+ g_bAnyCurrent = false;
+ if( glGetError() != GL_NO_ERROR )
+ {
+ SAL_WARN("vcl.opengl", "glError: " << glGetError());
+ }
+ glXDestroyContext(m_aGLWin.dpy, m_aGLWin.ctx);
+ m_aGLWin.ctx = nullptr;
+}
+
+void X11OpenGLContext::initGLWindow(Visual* pVisual)
+{
+ OpenGLZone aZone;
+
+ // Get visual info
+ {
+ XVisualInfo aTemplate;
+ aTemplate.visualid = XVisualIDFromVisual( pVisual );
+ int nVisuals = 0;
+ XVisualInfo* pInfo = XGetVisualInfo( m_aGLWin.dpy, VisualIDMask, &aTemplate, &nVisuals );
+ if( nVisuals != 1 )
+ SAL_WARN( "vcl.opengl", "match count for visual id is not 1" );
+ m_aGLWin.vi = pInfo;
+ }
+
+ // Check multisample support
+ /* TODO: moggi: This is not necessarily correct in the DBG_UTIL path, as it picks
+ * an FBConfig instead ... */
+ int nSamples = 0;
+ glXGetConfig(m_aGLWin.dpy, m_aGLWin.vi, GLX_SAMPLES, &nSamples);
+ if( nSamples > 0 )
+ m_aGLWin.bMultiSampleSupported = true;
+
+ m_aGLWin.GLXExtensions = glXQueryExtensionsString( m_aGLWin.dpy, m_aGLWin.screen );
+ SAL_INFO("vcl.opengl", "available GLX extensions: " << m_aGLWin.GLXExtensions);
+}
+
+void X11OpenGLContext::initWindow()
+{
+ const SystemEnvData* pChildSysData = nullptr;
+ SystemWindowData winData = generateWinData(mpWindow, false);
+ if( winData.pVisual )
+ {
+ if( !m_pChildWindow )
+ {
+ m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
+ }
+ pChildSysData = m_pChildWindow->GetSystemData();
+ }
+
+ if (!m_pChildWindow || !pChildSysData)
+ return;
+
+ InitChildWindow(m_pChildWindow.get());
+
+ m_aGLWin.dpy = static_cast<Display*>(pChildSysData->pDisplay);
+ m_aGLWin.win = pChildSysData->GetWindowHandle(m_pChildWindow->ImplGetFrame());
+ m_aGLWin.screen = pChildSysData->nScreen;
+
+ Visual* pVisual = static_cast<Visual*>(pChildSysData->pVisual);
+ initGLWindow(pVisual);
+}
+
+GLX11Window::GLX11Window()
+ : dpy(nullptr)
+ , screen(0)
+ , win(0)
+ , vi(nullptr)
+ , ctx(nullptr)
+{
+}
+
+bool GLX11Window::HasGLXExtension( const char* name ) const
+{
+ for (sal_Int32 i = 0; i != -1;) {
+ if (o3tl::getToken(GLXExtensions, 0, ' ', i) == name) {
+ return true;
+ }
+ }
+ return false;
+}
+
+GLX11Window::~GLX11Window()
+{
+ XFree(vi);
+}
+
+bool GLX11Window::Synchronize(bool bOnoff) const
+{
+ XSynchronize(dpy, bOnoff);
+ return true;
+}
+
+OpenGLContext* X11SalInstance::CreateOpenGLContext()
+{
+ return new X11OpenGLContext;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/background.cxx b/vcl/source/outdev/background.cxx
new file mode 100644
index 000000000..a22c9e46e
--- /dev/null
+++ b/vcl/source/outdev/background.cxx
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <vcl/virdev.hxx>
+
+Color OutputDevice::GetBackgroundColor() const
+{
+ return GetBackground().GetColor();
+}
+
+void OutputDevice::SetBackground()
+{
+
+ maBackground = Wallpaper();
+ mbBackground = false;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetBackground();
+}
+
+void OutputDevice::SetBackground( const Wallpaper& rBackground )
+{
+
+ maBackground = rBackground;
+
+ if( rBackground.GetStyle() == WallpaperStyle::NONE )
+ mbBackground = false;
+ else
+ mbBackground = true;
+
+ if( !mpAlphaVDev )
+ return;
+
+ // Some of these are probably wrong (e.g. if the gradient has transparency),
+ // but hopefully nobody uses that. If you do, feel free to implement it properly.
+ if( rBackground.GetStyle() == WallpaperStyle::NONE )
+ {
+ mpAlphaVDev->SetBackground( rBackground );
+ }
+ else if( rBackground.IsBitmap())
+ {
+ BitmapEx bitmap = rBackground.GetBitmap();
+ if( bitmap.IsAlpha())
+ mpAlphaVDev->SetBackground( Wallpaper( BitmapEx( Bitmap( bitmap.GetAlpha()))));
+ else
+ mpAlphaVDev->SetBackground( Wallpaper( COL_BLACK ));
+ }
+ else if( rBackground.IsGradient())
+ {
+ mpAlphaVDev->SetBackground( Wallpaper( COL_BLACK ));
+ }
+ else
+ {
+ // Color background.
+ int transparency = 255 - rBackground.GetColor().GetAlpha();
+ mpAlphaVDev->SetBackground( Wallpaper( Color( transparency, transparency, transparency )));
+ }
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/outdev/bitmap.cxx b/vcl/source/outdev/bitmap.cxx
new file mode 100644
index 000000000..df916c0e6
--- /dev/null
+++ b/vcl/source/outdev/bitmap.cxx
@@ -0,0 +1,1039 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <config_features.h>
+
+#include <osl/diagnose.h>
+#include <tools/debug.hxx>
+#include <tools/helpers.hxx>
+
+#include <vcl/image.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/skia/SkiaHelper.hxx>
+#include <vcl/virdev.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <bitmap/bmpfast.hxx>
+#include <drawmode.hxx>
+#include <salbmp.hxx>
+#include <salgdi.hxx>
+
+void OutputDevice::DrawBitmap( const Point& rDestPt, const Bitmap& rBitmap )
+{
+ assert(!is_double_buffered_window());
+
+ const Size aSizePix( rBitmap.GetSizePixel() );
+ DrawBitmap( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmap, MetaActionType::BMP );
+}
+
+void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize, const Bitmap& rBitmap )
+{
+ assert(!is_double_buffered_window());
+
+ DrawBitmap( rDestPt, rDestSize, Point(), rBitmap.GetSizePixel(), rBitmap, MetaActionType::BMPSCALE );
+}
+
+void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ const Bitmap& rBitmap)
+{
+ assert(!is_double_buffered_window());
+
+ DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmap, MetaActionType::BMPSCALEPART );
+}
+
+void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ const Bitmap& rBitmap, const MetaActionType nAction )
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if ( RasterOp::Invert == meRasterOp )
+ {
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ return;
+ }
+
+ Bitmap aBmp( rBitmap );
+
+ if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap |
+ DrawModeFlags::GrayBitmap ) )
+ {
+ if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap ) )
+ {
+ sal_uInt8 cCmpVal;
+
+ if ( mnDrawMode & DrawModeFlags::BlackBitmap )
+ cCmpVal = 0;
+ else
+ cCmpVal = 255;
+
+ Color aCol( cCmpVal, cCmpVal, cCmpVal );
+ Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
+ SetLineColor( aCol );
+ SetFillColor( aCol );
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ Pop();
+ return;
+ }
+ else if( !aBmp.IsEmpty() )
+ {
+ if ( mnDrawMode & DrawModeFlags::GrayBitmap )
+ aBmp.Convert( BmpConversion::N8BitGreys );
+ }
+ }
+
+ if ( mpMetaFile )
+ {
+ switch( nAction )
+ {
+ case MetaActionType::BMP:
+ mpMetaFile->AddAction( new MetaBmpAction( rDestPt, aBmp ) );
+ break;
+
+ case MetaActionType::BMPSCALE:
+ mpMetaFile->AddAction( new MetaBmpScaleAction( rDestPt, rDestSize, aBmp ) );
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ mpMetaFile->AddAction( new MetaBmpScalePartAction(
+ rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmp ) );
+ break;
+
+ default: break;
+ }
+ }
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ if (!mpGraphics && !AcquireGraphics())
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if( !aBmp.IsEmpty() )
+ {
+ SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(),
+ ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()),
+ ImplLogicWidthToDevicePixel(rDestSize.Width()),
+ ImplLogicHeightToDevicePixel(rDestSize.Height()));
+
+ if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight )
+ {
+ const BmpMirrorFlags nMirrFlags = AdjustTwoRect( aPosAry, aBmp.GetSizePixel() );
+
+ if ( nMirrFlags != BmpMirrorFlags::NONE )
+ aBmp.Mirror( nMirrFlags );
+
+ if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight )
+ {
+ if (nAction == MetaActionType::BMPSCALE && CanSubsampleBitmap())
+ {
+ double nScaleX = aPosAry.mnDestWidth / static_cast<double>(aPosAry.mnSrcWidth);
+ double nScaleY = aPosAry.mnDestHeight / static_cast<double>(aPosAry.mnSrcHeight);
+
+ // If subsampling, use Bitmap::Scale() for subsampling of better quality.
+
+ // but hidpi surfaces like the cairo one have their own scale, so don't downscale
+ // past the surface scaling which can retain the extra detail
+ double fScale(1.0);
+ if (mpGraphics->ShouldDownscaleIconsAtSurface(&fScale))
+ {
+ nScaleX *= fScale;
+ nScaleY *= fScale;
+ }
+
+ if ( nScaleX < 1.0 || nScaleY < 1.0 )
+ {
+ aBmp.Scale(nScaleX, nScaleY);
+ aPosAry.mnSrcWidth = aPosAry.mnDestWidth * fScale;
+ aPosAry.mnSrcHeight = aPosAry.mnDestHeight * fScale;
+ }
+ }
+
+ mpGraphics->DrawBitmap( aPosAry, *aBmp.ImplGetSalBitmap(), *this );
+ }
+ }
+ }
+
+ if( mpAlphaVDev )
+ {
+ // #i32109#: Make bitmap area opaque
+ mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) );
+ }
+}
+
+Bitmap OutputDevice::GetBitmap( const Point& rSrcPt, const Size& rSize ) const
+{
+ Bitmap aBmp;
+ tools::Long nX = ImplLogicXToDevicePixel( rSrcPt.X() );
+ tools::Long nY = ImplLogicYToDevicePixel( rSrcPt.Y() );
+ tools::Long nWidth = ImplLogicWidthToDevicePixel( rSize.Width() );
+ tools::Long nHeight = ImplLogicHeightToDevicePixel( rSize.Height() );
+
+ if ( mpGraphics || AcquireGraphics() )
+ {
+ assert(mpGraphics);
+
+ if ( nWidth > 0 && nHeight > 0 && nX <= (mnOutWidth + mnOutOffX) && nY <= (mnOutHeight + mnOutOffY))
+ {
+ tools::Rectangle aRect( Point( nX, nY ), Size( nWidth, nHeight ) );
+ bool bClipped = false;
+
+ // X-Coordinate outside of draw area?
+ if ( nX < mnOutOffX )
+ {
+ nWidth -= ( mnOutOffX - nX );
+ nX = mnOutOffX;
+ bClipped = true;
+ }
+
+ // Y-Coordinate outside of draw area?
+ if ( nY < mnOutOffY )
+ {
+ nHeight -= ( mnOutOffY - nY );
+ nY = mnOutOffY;
+ bClipped = true;
+ }
+
+ // Width outside of draw area?
+ if ( (nWidth + nX) > (mnOutWidth + mnOutOffX) )
+ {
+ nWidth = mnOutOffX + mnOutWidth - nX;
+ bClipped = true;
+ }
+
+ // Height outside of draw area?
+ if ( (nHeight + nY) > (mnOutHeight + mnOutOffY) )
+ {
+ nHeight = mnOutOffY + mnOutHeight - nY;
+ bClipped = true;
+ }
+
+ if ( bClipped )
+ {
+ // If the visible part has been clipped, we have to create a
+ // Bitmap with the correct size in which we copy the clipped
+ // Bitmap to the correct position.
+ ScopedVclPtrInstance< VirtualDevice > aVDev( *this );
+
+ if ( aVDev->SetOutputSizePixel( aRect.GetSize() ) )
+ {
+ if ( aVDev->mpGraphics || aVDev->AcquireGraphics() )
+ {
+ if ( (nWidth > 0) && (nHeight > 0) )
+ {
+ SalTwoRect aPosAry(nX, nY, nWidth, nHeight,
+ (aRect.Left() < mnOutOffX) ? (mnOutOffX - aRect.Left()) : 0L,
+ (aRect.Top() < mnOutOffY) ? (mnOutOffY - aRect.Top()) : 0L,
+ nWidth, nHeight);
+ aVDev->mpGraphics->CopyBits(aPosAry, *mpGraphics, *this, *this);
+ }
+ else
+ {
+ OSL_ENSURE(false, "CopyBits with zero or negative width or height");
+ }
+
+ aBmp = aVDev->GetBitmap( Point(), aVDev->GetOutputSizePixel() );
+ }
+ else
+ bClipped = false;
+ }
+ else
+ bClipped = false;
+ }
+
+ if ( !bClipped )
+ {
+ std::shared_ptr<SalBitmap> pSalBmp = mpGraphics->GetBitmap( nX, nY, nWidth, nHeight, *this );
+
+ if( pSalBmp )
+ {
+ aBmp.ImplSetSalBitmap(pSalBmp);
+ }
+ }
+ }
+ }
+
+ return aBmp;
+}
+
+void OutputDevice::DrawDeviceAlphaBitmap( const Bitmap& rBmp, const AlphaMask& rAlpha,
+ const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel )
+{
+ assert(!is_double_buffered_window());
+
+ Point aOutPt(LogicToPixel(rDestPt));
+ Size aOutSz(LogicToPixel(rDestSize));
+ tools::Rectangle aDstRect(Point(), GetOutputSizePixel());
+
+ const bool bHMirr = aOutSz.Width() < 0;
+ const bool bVMirr = aOutSz.Height() < 0;
+
+ ClipToPaintRegion(aDstRect);
+
+ BmpMirrorFlags mirrorFlags = BmpMirrorFlags::NONE;
+ if (bHMirr)
+ {
+ aOutSz.setWidth( -aOutSz.Width() );
+ aOutPt.AdjustX( -(aOutSz.Width() - 1) );
+ mirrorFlags |= BmpMirrorFlags::Horizontal;
+ }
+
+ if (bVMirr)
+ {
+ aOutSz.setHeight( -aOutSz.Height() );
+ aOutPt.AdjustY( -(aOutSz.Height() - 1) );
+ mirrorFlags |= BmpMirrorFlags::Vertical;
+ }
+
+ if (aDstRect.Intersection(tools::Rectangle(aOutPt, aOutSz)).IsEmpty())
+ return;
+
+ {
+ Point aRelPt = aOutPt + Point(mnOutOffX, mnOutOffY);
+ SalTwoRect aTR(
+ rSrcPtPixel.X(), rSrcPtPixel.Y(),
+ rSrcSizePixel.Width(), rSrcSizePixel.Height(),
+ aRelPt.X(), aRelPt.Y(),
+ aOutSz.Width(), aOutSz.Height());
+
+ Bitmap bitmap(rBmp);
+ AlphaMask alpha(rAlpha);
+ if(bHMirr || bVMirr)
+ {
+ bitmap.Mirror(mirrorFlags);
+ alpha.Mirror(mirrorFlags);
+ }
+ SalBitmap* pSalSrcBmp = bitmap.ImplGetSalBitmap().get();
+ SalBitmap* pSalAlphaBmp = alpha.ImplGetSalBitmap().get();
+
+ // #i83087# Naturally, system alpha blending (SalGraphics::DrawAlphaBitmap) cannot work
+ // with separate alpha VDev
+
+ // try to blend the alpha bitmap with the alpha virtual device
+ if (mpAlphaVDev)
+ {
+ Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aRelPt, aOutSz ) );
+ if (SalBitmap* pSalAlphaBmp2 = aAlphaBitmap.ImplGetSalBitmap().get())
+ {
+ if (mpGraphics->BlendAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, *pSalAlphaBmp2, *this))
+ {
+ mpAlphaVDev->BlendBitmap(aTR, rAlpha);
+ return;
+ }
+ }
+ }
+ else
+ {
+ if (mpGraphics->DrawAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, *this))
+ return;
+ }
+
+ // we need to make sure Skia never reaches this slow code path
+ assert(!SkiaHelper::isVCLSkiaEnabled());
+ }
+
+ tools::Rectangle aBmpRect(Point(), rBmp.GetSizePixel());
+ if (aBmpRect.Intersection(tools::Rectangle(rSrcPtPixel, rSrcSizePixel)).IsEmpty())
+ return;
+
+ Point auxOutPt(LogicToPixel(rDestPt));
+ Size auxOutSz(LogicToPixel(rDestSize));
+
+ // HACK: The function is broken with alpha vdev and mirroring, mirror here.
+ Bitmap bitmap(rBmp);
+ AlphaMask alpha(rAlpha);
+ if(mpAlphaVDev && (bHMirr || bVMirr))
+ {
+ bitmap.Mirror(mirrorFlags);
+ alpha.Mirror(mirrorFlags);
+ auxOutPt = aOutPt;
+ auxOutSz = aOutSz;
+ }
+ DrawDeviceAlphaBitmapSlowPath(bitmap, alpha, aDstRect, aBmpRect, auxOutSz, auxOutPt);
+}
+
+namespace
+{
+
+struct LinearScaleContext
+{
+ std::unique_ptr<sal_Int32[]> mpMapX;
+ std::unique_ptr<sal_Int32[]> mpMapY;
+
+ std::unique_ptr<sal_Int32[]> mpMapXOffset;
+ std::unique_ptr<sal_Int32[]> mpMapYOffset;
+
+ LinearScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect,
+ Size const & aOutSize, tools::Long nOffX, tools::Long nOffY)
+
+ : mpMapX(new sal_Int32[aDstRect.GetWidth()])
+ , mpMapY(new sal_Int32[aDstRect.GetHeight()])
+ , mpMapXOffset(new sal_Int32[aDstRect.GetWidth()])
+ , mpMapYOffset(new sal_Int32[aDstRect.GetHeight()])
+ {
+ const tools::Long nSrcWidth = aBitmapRect.GetWidth();
+ const tools::Long nSrcHeight = aBitmapRect.GetHeight();
+
+ generateSimpleMap(
+ nSrcWidth, aDstRect.GetWidth(), aBitmapRect.Left(),
+ aOutSize.Width(), nOffX, mpMapX.get(), mpMapXOffset.get());
+
+ generateSimpleMap(
+ nSrcHeight, aDstRect.GetHeight(), aBitmapRect.Top(),
+ aOutSize.Height(), nOffY, mpMapY.get(), mpMapYOffset.get());
+ }
+
+private:
+
+ static void generateSimpleMap(tools::Long nSrcDimension, tools::Long nDstDimension, tools::Long nDstLocation,
+ tools::Long nOutDimension, tools::Long nOffset, sal_Int32* pMap, sal_Int32* pMapOffset)
+ {
+
+ const double fReverseScale = (std::abs(nOutDimension) > 1) ? (nSrcDimension - 1) / double(std::abs(nOutDimension) - 1) : 0.0;
+
+ tools::Long nSampleRange = std::max(tools::Long(0), nSrcDimension - 2);
+
+ for (tools::Long i = 0; i < nDstDimension; i++)
+ {
+ double fTemp = std::abs((nOffset + i) * fReverseScale);
+
+ pMap[i] = MinMax(nDstLocation + tools::Long(fTemp), 0, nSampleRange);
+ pMapOffset[i] = static_cast<tools::Long>((fTemp - pMap[i]) * 128.0);
+ }
+ }
+
+public:
+ bool blendBitmap(
+ const BitmapWriteAccess* pDestination,
+ const BitmapReadAccess* pSource,
+ const BitmapReadAccess* pSourceAlpha,
+ const tools::Long nDstWidth,
+ const tools::Long nDstHeight)
+ {
+ if (pSource && pSourceAlpha && pDestination)
+ {
+ ScanlineFormat nSourceFormat = pSource->GetScanlineFormat();
+ ScanlineFormat nDestinationFormat = pDestination->GetScanlineFormat();
+
+ switch (nSourceFormat)
+ {
+ case ScanlineFormat::N24BitTcRgb:
+ case ScanlineFormat::N24BitTcBgr:
+ {
+ if ( (nSourceFormat == ScanlineFormat::N24BitTcBgr && nDestinationFormat == ScanlineFormat::N32BitTcBgra)
+ || (nSourceFormat == ScanlineFormat::N24BitTcRgb && nDestinationFormat == ScanlineFormat::N32BitTcRgba))
+ {
+ blendBitmap24(pDestination, pSource, pSourceAlpha, nDstWidth, nDstHeight);
+ return true;
+ }
+ }
+ break;
+ default: break;
+ }
+ }
+ return false;
+ }
+
+ void blendBitmap24(
+ const BitmapWriteAccess* pDestination,
+ const BitmapReadAccess* pSource,
+ const BitmapReadAccess* pSourceAlpha,
+ const tools::Long nDstWidth,
+ const tools::Long nDstHeight)
+ {
+ Scanline pLine0, pLine1;
+ Scanline pLineAlpha0, pLineAlpha1;
+ Scanline pColorSample1, pColorSample2;
+ Scanline pDestScanline;
+
+ tools::Long nColor1Line1, nColor2Line1, nColor3Line1;
+ tools::Long nColor1Line2, nColor2Line2, nColor3Line2;
+ tools::Long nAlphaLine1, nAlphaLine2;
+
+ sal_uInt8 nColor1, nColor2, nColor3, nAlpha;
+
+ for (tools::Long nY = 0; nY < nDstHeight; nY++)
+ {
+ const tools::Long nMapY = mpMapY[nY];
+ const tools::Long nMapFY = mpMapYOffset[nY];
+
+ pLine0 = pSource->GetScanline(nMapY);
+ // tdf#95481 guard nMapY + 1 to be within bounds
+ pLine1 = (nMapY + 1 < pSource->Height()) ? pSource->GetScanline(nMapY + 1) : pLine0;
+
+ pLineAlpha0 = pSourceAlpha->GetScanline(nMapY);
+ // tdf#95481 guard nMapY + 1 to be within bounds
+ pLineAlpha1 = (nMapY + 1 < pSourceAlpha->Height()) ? pSourceAlpha->GetScanline(nMapY + 1) : pLineAlpha0;
+
+ pDestScanline = pDestination->GetScanline(nY);
+
+ for (tools::Long nX = 0; nX < nDstWidth; nX++)
+ {
+ const tools::Long nMapX = mpMapX[nX];
+ const tools::Long nMapFX = mpMapXOffset[nX];
+
+ pColorSample1 = pLine0 + 3 * nMapX;
+ pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1;
+ nColor1Line1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor2Line1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor3Line1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1 = pLine1 + 3 * nMapX;
+ pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1;
+ nColor1Line2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor2Line2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor3Line2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1 = pLineAlpha0 + nMapX;
+ pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1;
+ nAlphaLine1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1 = pLineAlpha1 + nMapX;
+ pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1;
+ nAlphaLine2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ nColor1 = (nColor1Line1 + nMapFY * ((nColor1Line2 >> 7) - (nColor1Line1 >> 7))) >> 7;
+ nColor2 = (nColor2Line1 + nMapFY * ((nColor2Line2 >> 7) - (nColor2Line1 >> 7))) >> 7;
+ nColor3 = (nColor3Line1 + nMapFY * ((nColor3Line2 >> 7) - (nColor3Line1 >> 7))) >> 7;
+
+ nAlpha = (nAlphaLine1 + nMapFY * ((nAlphaLine2 >> 7) - (nAlphaLine1 >> 7))) >> 7;
+
+ *pDestScanline = color::ColorChannelMerge(*pDestScanline, nColor1, nAlpha);
+ pDestScanline++;
+ *pDestScanline = color::ColorChannelMerge(*pDestScanline, nColor2, nAlpha);
+ pDestScanline++;
+ *pDestScanline = color::ColorChannelMerge(*pDestScanline, nColor3, nAlpha);
+ pDestScanline++;
+ pDestScanline++;
+ }
+ }
+ }
+};
+
+struct TradScaleContext
+{
+ std::unique_ptr<sal_Int32[]> mpMapX;
+ std::unique_ptr<sal_Int32[]> mpMapY;
+
+ TradScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect,
+ Size const & aOutSize, tools::Long nOffX, tools::Long nOffY)
+
+ : mpMapX(new sal_Int32[aDstRect.GetWidth()])
+ , mpMapY(new sal_Int32[aDstRect.GetHeight()])
+ {
+ const tools::Long nSrcWidth = aBitmapRect.GetWidth();
+ const tools::Long nSrcHeight = aBitmapRect.GetHeight();
+
+ const bool bHMirr = aOutSize.Width() < 0;
+ const bool bVMirr = aOutSize.Height() < 0;
+
+ generateSimpleMap(
+ nSrcWidth, aDstRect.GetWidth(), aBitmapRect.Left(),
+ aOutSize.Width(), nOffX, bHMirr, mpMapX.get());
+
+ generateSimpleMap(
+ nSrcHeight, aDstRect.GetHeight(), aBitmapRect.Top(),
+ aOutSize.Height(), nOffY, bVMirr, mpMapY.get());
+ }
+
+private:
+
+ static void generateSimpleMap(tools::Long nSrcDimension, tools::Long nDstDimension, tools::Long nDstLocation,
+ tools::Long nOutDimension, tools::Long nOffset, bool bMirror, sal_Int32* pMap)
+ {
+ tools::Long nMirrorOffset = 0;
+
+ if (bMirror)
+ nMirrorOffset = (nDstLocation << 1) + nSrcDimension - 1;
+
+ for (tools::Long i = 0; i < nDstDimension; ++i, ++nOffset)
+ {
+ pMap[i] = nDstLocation + nOffset * nSrcDimension / nOutDimension;
+ if (bMirror)
+ pMap[i] = nMirrorOffset - pMap[i];
+ }
+ }
+};
+
+
+} // end anonymous namespace
+
+void OutputDevice::DrawDeviceAlphaBitmapSlowPath(const Bitmap& rBitmap,
+ const AlphaMask& rAlpha, tools::Rectangle aDstRect, tools::Rectangle aBmpRect, Size const & aOutSize, Point const & aOutPoint)
+{
+ assert(!is_double_buffered_window());
+
+ VirtualDevice* pOldVDev = mpAlphaVDev;
+
+ const bool bHMirr = aOutSize.Width() < 0;
+ const bool bVMirr = aOutSize.Height() < 0;
+
+ // The scaling in this code path produces really ugly results - it
+ // does the most trivial scaling with no smoothing.
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ const bool bOldMap = mbMap;
+
+ mpMetaFile = nullptr; // fdo#55044 reset before GetBitmap!
+ mbMap = false;
+
+ Bitmap aBmp(GetBitmap(aDstRect.TopLeft(), aDstRect.GetSize()));
+
+ // #109044# The generated bitmap need not necessarily be
+ // of aDstRect dimensions, it's internally clipped to
+ // window bounds. Thus, we correct the dest size here,
+ // since we later use it (in nDstWidth/Height) for pixel
+ // access)
+ // #i38887# reading from screen may sometimes fail
+ if (aBmp.ImplGetSalBitmap())
+ {
+ aDstRect.SetSize(aBmp.GetSizePixel());
+ }
+
+ const tools::Long nDstWidth = aDstRect.GetWidth();
+ const tools::Long nDstHeight = aDstRect.GetHeight();
+
+ // calculate offset in original bitmap
+ // in RTL case this is a little more complicated since the contents of the
+ // bitmap is not mirrored (it never is), however the paint region and bmp region
+ // are in mirrored coordinates, so the intersection of (aOutPt,aOutSz) with these
+ // is content wise somewhere else and needs to take mirroring into account
+ const tools::Long nOffX = IsRTLEnabled()
+ ? aOutSize.Width() - aDstRect.GetWidth() - (aDstRect.Left() - aOutPoint.X())
+ : aDstRect.Left() - aOutPoint.X();
+
+ const tools::Long nOffY = aDstRect.Top() - aOutPoint.Y();
+
+ TradScaleContext aTradContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY);
+
+ Bitmap::ScopedReadAccess pBitmapReadAccess(const_cast<Bitmap&>(rBitmap));
+ AlphaMask::ScopedReadAccess pAlphaReadAccess(const_cast<AlphaMask&>(rAlpha));
+
+ DBG_ASSERT( pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal,
+ "OutputDevice::ImplDrawAlpha(): non-8bit alpha no longer supported!" );
+
+ // #i38887# reading from screen may sometimes fail
+ if (aBmp.ImplGetSalBitmap())
+ {
+ Bitmap aNewBitmap;
+
+ if (mpAlphaVDev)
+ {
+ aNewBitmap = BlendBitmapWithAlpha(
+ aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(),
+ aDstRect,
+ nOffY, nDstHeight,
+ nOffX, nDstWidth,
+ aTradContext.mpMapX.get(), aTradContext.mpMapY.get() );
+ }
+ else
+ {
+ LinearScaleContext aLinearContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY);
+
+ if (aLinearContext.blendBitmap( BitmapScopedWriteAccess(aBmp).get(), pBitmapReadAccess.get(), pAlphaReadAccess.get(),
+ nDstWidth, nDstHeight))
+ {
+ aNewBitmap = aBmp;
+ }
+ else
+ {
+ aNewBitmap = BlendBitmap(
+ aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(),
+ nOffY, nDstHeight,
+ nOffX, nDstWidth,
+ aBmpRect, aOutSize,
+ bHMirr, bVMirr,
+ aTradContext.mpMapX.get(), aTradContext.mpMapY.get() );
+ }
+ }
+
+ // #110958# Disable alpha VDev, we're doing the necessary
+ // stuff explicitly further below
+ if (mpAlphaVDev)
+ mpAlphaVDev = nullptr;
+
+ DrawBitmap(aDstRect.TopLeft(), aNewBitmap);
+
+ // #110958# Enable alpha VDev again
+ mpAlphaVDev = pOldVDev;
+ }
+
+ mbMap = bOldMap;
+ mpMetaFile = pOldMetaFile;
+}
+
+bool OutputDevice::HasFastDrawTransformedBitmap() const
+{
+ if( ImplIsRecordLayout() )
+ return false;
+
+ if (!mpGraphics && !AcquireGraphics())
+ return false;
+ assert(mpGraphics);
+
+ return mpGraphics->HasFastDrawTransformedBitmap();
+}
+
+void OutputDevice::DrawImage( const Point& rPos, const Image& rImage, DrawImageFlags nStyle )
+{
+ assert(!is_double_buffered_window());
+
+ DrawImage( rPos, Size(), rImage, nStyle );
+}
+
+void OutputDevice::DrawImage( const Point& rPos, const Size& rSize,
+ const Image& rImage, DrawImageFlags nStyle )
+{
+ assert(!is_double_buffered_window());
+
+ bool bIsSizeValid = !rSize.IsEmpty();
+
+ if (!ImplIsRecordLayout())
+ {
+ Image& rNonConstImage = const_cast<Image&>(rImage);
+ if (bIsSizeValid)
+ rNonConstImage.Draw(this, rPos, nStyle, &rSize);
+ else
+ rNonConstImage.Draw(this, rPos, nStyle);
+ }
+}
+
+namespace
+{
+ // Co = Cs + Cd*(1-As) premultiplied alpha -or-
+ // Co = (AsCs + AdCd*(1-As)) / Ao
+ sal_uInt8 CalcColor( const sal_uInt8 nSourceColor, const sal_uInt8 nSourceAlpha,
+ const sal_uInt8 nDstAlpha, const sal_uInt8 nResAlpha, const sal_uInt8 nDestColor )
+ {
+ int c = nResAlpha ? ( static_cast<int>(nSourceAlpha)*nSourceColor + static_cast<int>(nDstAlpha)*nDestColor -
+ static_cast<int>(nDstAlpha)*nDestColor*nSourceAlpha/255 ) / static_cast<int>(nResAlpha) : 0;
+ return sal_uInt8( c );
+ }
+
+ BitmapColor AlphaBlend( int nX, int nY,
+ const tools::Long nMapX,
+ const tools::Long nMapY,
+ BitmapReadAccess const * pP,
+ BitmapReadAccess const * pA,
+ BitmapReadAccess const * pB,
+ BitmapWriteAccess const * pAlphaW,
+ sal_uInt8& nResAlpha )
+ {
+ BitmapColor aDstCol,aSrcCol;
+ aSrcCol = pP->GetColor( nMapY, nMapX );
+ aDstCol = pB->GetColor( nY, nX );
+
+ // vcl stores transparency, not alpha - invert it
+ const sal_uInt8 nSrcAlpha = 255 - pA->GetPixelIndex( nMapY, nMapX );
+ const sal_uInt8 nDstAlpha = 255 - pAlphaW->GetPixelIndex( nY, nX );
+
+ // Perform porter-duff compositing 'over' operation
+
+ // Co = Cs + Cd*(1-As)
+ // Ad = As + Ad*(1-As)
+ nResAlpha = static_cast<int>(nSrcAlpha) + static_cast<int>(nDstAlpha) - static_cast<int>(nDstAlpha)*nSrcAlpha/255;
+
+ aDstCol.SetRed( CalcColor( aSrcCol.GetRed(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetRed() ) );
+ aDstCol.SetBlue( CalcColor( aSrcCol.GetBlue(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetBlue() ) );
+ aDstCol.SetGreen( CalcColor( aSrcCol.GetGreen(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetGreen() ) );
+
+ return aDstCol;
+ }
+}
+
+void OutputDevice::BlendBitmap(
+ const SalTwoRect& rPosAry,
+ const Bitmap& rBmp )
+{
+ mpGraphics->BlendBitmap( rPosAry, *rBmp.ImplGetSalBitmap(), *this );
+}
+
+Bitmap OutputDevice::BlendBitmapWithAlpha(
+ Bitmap& aBmp,
+ BitmapReadAccess const * pP,
+ BitmapReadAccess const * pA,
+ const tools::Rectangle& aDstRect,
+ const sal_Int32 nOffY,
+ const sal_Int32 nDstHeight,
+ const sal_Int32 nOffX,
+ const sal_Int32 nDstWidth,
+ const sal_Int32* pMapX,
+ const sal_Int32* pMapY )
+
+{
+ BitmapColor aDstCol;
+ Bitmap res;
+ int nX, nY;
+ sal_uInt8 nResAlpha;
+
+ SAL_WARN_IF( !mpAlphaVDev, "vcl.gdi", "BlendBitmapWithAlpha(): call me only with valid alpha VirtualDevice!" );
+
+ bool bOldMapMode( mpAlphaVDev->IsMapModeEnabled() );
+ mpAlphaVDev->EnableMapMode(false);
+
+ Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aDstRect.TopLeft(), aDstRect.GetSize() ) );
+ BitmapScopedWriteAccess pAlphaW(aAlphaBitmap);
+
+ if( GetBitCount() <= 8 )
+ {
+ Bitmap aDither(aBmp.GetSizePixel(), vcl::PixelFormat::N8_BPP);
+ BitmapColor aIndex( 0 );
+ Bitmap::ScopedReadAccess pB(aBmp);
+ BitmapScopedWriteAccess pW(aDither);
+
+ if (pB && pP && pA && pW && pAlphaW)
+ {
+ int nOutY;
+
+ for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ )
+ {
+ const tools::Long nMapY = pMapY[ nY ];
+ const tools::Long nModY = ( nOutY & 0x0FL ) << 4;
+ int nOutX;
+
+ Scanline pScanline = pW->GetScanline(nY);
+ Scanline pScanlineAlpha = pAlphaW->GetScanline(nY);
+ for( nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ )
+ {
+ const tools::Long nMapX = pMapX[ nX ];
+ const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ];
+
+ aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha );
+
+ aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] +
+ nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] +
+ nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) );
+ pW->SetPixelOnData( pScanline, nX, aIndex );
+
+ aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] +
+ nVCLGLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] +
+ nVCLBLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] ) );
+ pAlphaW->SetPixelOnData( pScanlineAlpha, nX, aIndex );
+ }
+ }
+ }
+ pB.reset();
+ pW.reset();
+ res = aDither;
+ }
+ else
+ {
+ BitmapScopedWriteAccess pB(aBmp);
+ if (pB && pP && pA && pAlphaW)
+ {
+ for( nY = 0; nY < nDstHeight; nY++ )
+ {
+ const tools::Long nMapY = pMapY[ nY ];
+ Scanline pScanlineB = pB->GetScanline(nY);
+ Scanline pScanlineAlpha = pAlphaW->GetScanline(nY);
+
+ for( nX = 0; nX < nDstWidth; nX++ )
+ {
+ const tools::Long nMapX = pMapX[ nX ];
+ aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha );
+
+ pB->SetPixelOnData(pScanlineB, nX, pB->GetBestMatchingColor(aDstCol));
+ pAlphaW->SetPixelOnData(pScanlineAlpha, nX, pB->GetBestMatchingColor(Color(255L-nResAlpha, 255L-nResAlpha, 255L-nResAlpha)));
+ }
+ }
+ }
+ pB.reset();
+ res = aBmp;
+ }
+
+ pAlphaW.reset();
+ mpAlphaVDev->DrawBitmap( aDstRect.TopLeft(), aAlphaBitmap );
+ mpAlphaVDev->EnableMapMode( bOldMapMode );
+
+ return res;
+}
+
+Bitmap OutputDevice::BlendBitmap(
+ Bitmap& aBmp,
+ BitmapReadAccess const * pP,
+ BitmapReadAccess const * pA,
+ const sal_Int32 nOffY,
+ const sal_Int32 nDstHeight,
+ const sal_Int32 nOffX,
+ const sal_Int32 nDstWidth,
+ const tools::Rectangle& aBmpRect,
+ const Size& aOutSz,
+ const bool bHMirr,
+ const bool bVMirr,
+ const sal_Int32* pMapX,
+ const sal_Int32* pMapY )
+{
+ BitmapColor aDstCol;
+ Bitmap res;
+ int nX, nY;
+
+ if( GetBitCount() <= 8 )
+ {
+ Bitmap aDither(aBmp.GetSizePixel(), vcl::PixelFormat::N8_BPP);
+ BitmapColor aIndex( 0 );
+ Bitmap::ScopedReadAccess pB(aBmp);
+ BitmapScopedWriteAccess pW(aDither);
+
+ if( pB && pP && pA && pW )
+ {
+ int nOutY;
+
+ for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ )
+ {
+ tools::Long nMapY = pMapY[ nY ];
+ if (bVMirr)
+ {
+ nMapY = aBmpRect.Bottom() - nMapY;
+ }
+ const tools::Long nModY = ( nOutY & 0x0FL ) << 4;
+ int nOutX;
+
+ Scanline pScanline = pW->GetScanline(nY);
+ Scanline pScanlineAlpha = pA->GetScanline(nMapY);
+ for( nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ )
+ {
+ tools::Long nMapX = pMapX[ nX ];
+ if (bHMirr)
+ {
+ nMapX = aBmpRect.Right() - nMapX;
+ }
+ const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ];
+
+ aDstCol = pB->GetColor( nY, nX );
+ aDstCol.Merge( pP->GetColor( nMapY, nMapX ), pA->GetIndexFromData( pScanlineAlpha, nMapX ) );
+ aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] +
+ nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] +
+ nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) );
+ pW->SetPixelOnData( pScanline, nX, aIndex );
+ }
+ }
+ }
+
+ pB.reset();
+ pW.reset();
+ res = aDither;
+ }
+ else
+ {
+ BitmapScopedWriteAccess pB(aBmp);
+
+ bool bFastBlend = false;
+ if( pP && pA && pB && !bHMirr && !bVMirr )
+ {
+ SalTwoRect aTR(aBmpRect.Left(), aBmpRect.Top(), aBmpRect.GetWidth(), aBmpRect.GetHeight(),
+ nOffX, nOffY, aOutSz.Width(), aOutSz.Height());
+
+ bFastBlend = ImplFastBitmapBlending( *pB,*pP,*pA, aTR );
+ }
+
+ if( pP && pA && pB && !bFastBlend )
+ {
+ switch( pP->GetScanlineFormat() )
+ {
+ case ScanlineFormat::N8BitPal:
+ {
+ for( nY = 0; nY < nDstHeight; nY++ )
+ {
+ tools::Long nMapY = pMapY[ nY ];
+ if ( bVMirr )
+ {
+ nMapY = aBmpRect.Bottom() - nMapY;
+ }
+ Scanline pPScan = pP->GetScanline( nMapY );
+ Scanline pAScan = pA->GetScanline( nMapY );
+ Scanline pBScan = pB->GetScanline( nY );
+
+ for( nX = 0; nX < nDstWidth; nX++ )
+ {
+ tools::Long nMapX = pMapX[ nX ];
+
+ if ( bHMirr )
+ {
+ nMapX = aBmpRect.Right() - nMapX;
+ }
+ aDstCol = pB->GetPixelFromData( pBScan, nX );
+ aDstCol.Merge( pP->GetPaletteColor( pPScan[ nMapX ] ), pAScan[ nMapX ] );
+ pB->SetPixelOnData( pBScan, nX, aDstCol );
+ }
+ }
+ }
+ break;
+
+ default:
+ {
+
+ for( nY = 0; nY < nDstHeight; nY++ )
+ {
+ tools::Long nMapY = pMapY[ nY ];
+
+ if ( bVMirr )
+ {
+ nMapY = aBmpRect.Bottom() - nMapY;
+ }
+ Scanline pAScan = pA->GetScanline( nMapY );
+ Scanline pBScan = pB->GetScanline(nY);
+ for( nX = 0; nX < nDstWidth; nX++ )
+ {
+ tools::Long nMapX = pMapX[ nX ];
+
+ if ( bHMirr )
+ {
+ nMapX = aBmpRect.Right() - nMapX;
+ }
+ aDstCol = pB->GetPixelFromData( pBScan, nX );
+ aDstCol.Merge( pP->GetColor( nMapY, nMapX ), pAScan[ nMapX ] );
+ pB->SetPixelOnData( pBScan, nX, aDstCol );
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ pB.reset();
+ res = aBmp;
+ }
+
+ return res;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/bitmapex.cxx b/vcl/source/outdev/bitmapex.cxx
new file mode 100644
index 000000000..fc966b53c
--- /dev/null
+++ b/vcl/source/outdev/bitmapex.cxx
@@ -0,0 +1,682 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <config_features.h>
+
+#include <rtl/math.hxx>
+#include <comphelper/lok.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <vcl/canvastools.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+
+void OutputDevice::DrawBitmapEx( const Point& rDestPt,
+ const BitmapEx& rBitmapEx )
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if( !rBitmapEx.IsAlpha() )
+ {
+ DrawBitmap( rDestPt, rBitmapEx.GetBitmap() );
+ }
+ else
+ {
+ const Size aSizePix( rBitmapEx.GetSizePixel() );
+ DrawBitmapEx( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmapEx, MetaActionType::BMPEX );
+ }
+}
+
+void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize,
+ const BitmapEx& rBitmapEx )
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if ( !rBitmapEx.IsAlpha() )
+ {
+ DrawBitmap( rDestPt, rDestSize, rBitmapEx.GetBitmap() );
+ }
+ else
+ {
+ DrawBitmapEx( rDestPt, rDestSize, Point(), rBitmapEx.GetSizePixel(), rBitmapEx, MetaActionType::BMPEXSCALE );
+ }
+}
+
+void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ const BitmapEx& rBitmapEx)
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if ( !rBitmapEx.IsAlpha() )
+ {
+ DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx.GetBitmap() );
+ }
+ else
+ {
+ DrawBitmapEx( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx, MetaActionType::BMPEXSCALEPART );
+ }
+}
+
+void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ const BitmapEx& rBitmapEx, const MetaActionType nAction )
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if( !rBitmapEx.IsAlpha() )
+ {
+ DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx.GetBitmap() );
+ }
+ else
+ {
+ if ( RasterOp::Invert == meRasterOp )
+ {
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ return;
+ }
+
+ BitmapEx aBmpEx(vcl::drawmode::GetBitmapEx(rBitmapEx, GetDrawMode()));
+
+ if ( mpMetaFile )
+ {
+ switch( nAction )
+ {
+ case MetaActionType::BMPEX:
+ mpMetaFile->AddAction( new MetaBmpExAction( rDestPt, aBmpEx ) );
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ mpMetaFile->AddAction( new MetaBmpExScaleAction( rDestPt, rDestSize, aBmpEx ) );
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ mpMetaFile->AddAction( new MetaBmpExScalePartAction( rDestPt, rDestSize,
+ rSrcPtPixel, rSrcSizePixel, aBmpEx ) );
+ break;
+
+ default: break;
+ }
+ }
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ DrawDeviceBitmapEx( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmpEx );
+ }
+}
+
+BitmapEx OutputDevice::GetBitmapEx( const Point& rSrcPt, const Size& rSize ) const
+{
+
+ // #110958# Extract alpha value from VDev, if any
+ if( mpAlphaVDev )
+ {
+ Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( rSrcPt, rSize ) );
+
+ // ensure 8 bit alpha
+ if (aAlphaBitmap.getPixelFormat() > vcl::PixelFormat::N8_BPP)
+ aAlphaBitmap.Convert( BmpConversion::N8BitNoConversion );
+
+ return BitmapEx(GetBitmap( rSrcPt, rSize ), AlphaMask( aAlphaBitmap ) );
+ }
+ else
+ return BitmapEx(GetBitmap( rSrcPt, rSize ));
+}
+
+void OutputDevice::DrawDeviceBitmapEx( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ BitmapEx& rBitmapEx )
+{
+ assert(!is_double_buffered_window());
+
+ if (rBitmapEx.IsAlpha())
+ {
+ DrawDeviceAlphaBitmap(rBitmapEx.GetBitmap(), rBitmapEx.GetAlpha(), rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel);
+ }
+ else if (!rBitmapEx.IsEmpty())
+ {
+ SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(),
+ ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()),
+ ImplLogicWidthToDevicePixel(rDestSize.Width()),
+ ImplLogicHeightToDevicePixel(rDestSize.Height()));
+
+ const BmpMirrorFlags nMirrFlags = AdjustTwoRect(aPosAry, rBitmapEx.GetSizePixel());
+
+ if (aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight)
+ {
+
+ if (nMirrFlags != BmpMirrorFlags::NONE)
+ rBitmapEx.Mirror(nMirrFlags);
+
+ const SalBitmap* pSalSrcBmp = rBitmapEx.ImplGetBitmapSalBitmap().get();
+ std::shared_ptr<SalBitmap> xMaskBmp = rBitmapEx.maAlphaMask.ImplGetSalBitmap();
+
+ if (xMaskBmp)
+ {
+ bool bTryDirectPaint(pSalSrcBmp);
+
+ if (bTryDirectPaint && mpGraphics->DrawAlphaBitmap(aPosAry, *pSalSrcBmp, *xMaskBmp, *this))
+ {
+ // tried to paint as alpha directly. If this worked, we are done (except
+ // alpha, see below)
+ }
+ else
+ {
+ // #4919452# reduce operation area to bounds of
+ // cliprect. since masked transparency involves
+ // creation of a large vdev and copying the screen
+ // content into that (slooow read from framebuffer),
+ // that should considerably increase performance for
+ // large bitmaps and small clippings.
+
+ // Note that this optimization is a workaround for a
+ // Writer peculiarity, namely, to decompose background
+ // graphics into myriads of disjunct, tiny
+ // rectangles. That otherwise kills us here, since for
+ // transparent output, SAL always prepares the whole
+ // bitmap, if aPosAry contains the whole bitmap (and
+ // it's _not_ to blame for that).
+
+ // Note the call to ImplPixelToDevicePixel(), since
+ // aPosAry already contains the mnOutOff-offsets, they
+ // also have to be applied to the region
+ tools::Rectangle aClipRegionBounds( ImplPixelToDevicePixel(maRegion).GetBoundRect() );
+
+ // TODO: Also respect scaling (that's a bit tricky,
+ // since the source points have to move fractional
+ // amounts (which is not possible, thus has to be
+ // emulated by increases copy area)
+ // const double nScaleX( aPosAry.mnDestWidth / aPosAry.mnSrcWidth );
+ // const double nScaleY( aPosAry.mnDestHeight / aPosAry.mnSrcHeight );
+
+ // for now, only identity scales allowed
+ if (!aClipRegionBounds.IsEmpty() &&
+ aPosAry.mnDestWidth == aPosAry.mnSrcWidth &&
+ aPosAry.mnDestHeight == aPosAry.mnSrcHeight)
+ {
+ // now intersect dest rect with clip region
+ aClipRegionBounds.Intersection(tools::Rectangle(aPosAry.mnDestX,
+ aPosAry.mnDestY,
+ aPosAry.mnDestX + aPosAry.mnDestWidth - 1,
+ aPosAry.mnDestY + aPosAry.mnDestHeight - 1));
+
+ // Note: I could theoretically optimize away the
+ // DrawBitmap below, if the region is empty
+ // here. Unfortunately, cannot rule out that
+ // somebody relies on the side effects.
+ if (!aClipRegionBounds.IsEmpty())
+ {
+ aPosAry.mnSrcX += aClipRegionBounds.Left() - aPosAry.mnDestX;
+ aPosAry.mnSrcY += aClipRegionBounds.Top() - aPosAry.mnDestY;
+ aPosAry.mnSrcWidth = aClipRegionBounds.GetWidth();
+ aPosAry.mnSrcHeight = aClipRegionBounds.GetHeight();
+
+ aPosAry.mnDestX = aClipRegionBounds.Left();
+ aPosAry.mnDestY = aClipRegionBounds.Top();
+ aPosAry.mnDestWidth = aClipRegionBounds.GetWidth();
+ aPosAry.mnDestHeight = aClipRegionBounds.GetHeight();
+ }
+ }
+
+ mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, *xMaskBmp, *this);
+ }
+
+ // #110958# Paint mask to alpha channel. Luckily, the
+ // black and white representation of the mask maps to
+ // the alpha channel
+
+ // #i25167# Restrict mask painting to _opaque_ areas
+ // of the mask, otherwise we spoil areas where no
+ // bitmap content was ever visible. Interestingly
+ // enough, this can be achieved by taking the mask as
+ // the transparency mask of itself
+ if (mpAlphaVDev)
+ mpAlphaVDev->DrawBitmapEx(rDestPt,
+ rDestSize,
+ BitmapEx(rBitmapEx.GetAlpha(),
+ rBitmapEx.GetAlpha()));
+ }
+ else
+ {
+ mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, *this);
+
+ if (mpAlphaVDev)
+ {
+ // #i32109#: Make bitmap area opaque
+ mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) );
+ }
+ }
+ }
+ }
+}
+
+bool OutputDevice::DrawTransformBitmapExDirect(
+ const basegfx::B2DHomMatrix& aFullTransform,
+ const BitmapEx& rBitmapEx,
+ double fAlpha)
+{
+ assert(!is_double_buffered_window());
+
+ bool bDone = false;
+
+ // try to paint directly
+ const basegfx::B2DPoint aNull(aFullTransform * basegfx::B2DPoint(0.0, 0.0));
+ const basegfx::B2DPoint aTopX(aFullTransform * basegfx::B2DPoint(1.0, 0.0));
+ const basegfx::B2DPoint aTopY(aFullTransform * basegfx::B2DPoint(0.0, 1.0));
+ SalBitmap* pSalSrcBmp = rBitmapEx.GetBitmap().ImplGetSalBitmap().get();
+ Bitmap aAlphaBitmap;
+
+ if(rBitmapEx.IsAlpha())
+ {
+ aAlphaBitmap = rBitmapEx.GetAlpha();
+ }
+ else if (mpAlphaVDev)
+ {
+ aAlphaBitmap = AlphaMask(rBitmapEx.GetSizePixel());
+ aAlphaBitmap.Erase(COL_BLACK); // opaque
+ }
+
+ SalBitmap* pSalAlphaBmp = aAlphaBitmap.ImplGetSalBitmap().get();
+
+ bDone = mpGraphics->DrawTransformedBitmap(
+ aNull,
+ aTopX,
+ aTopY,
+ *pSalSrcBmp,
+ pSalAlphaBmp,
+ fAlpha,
+ *this);
+
+ if (mpAlphaVDev)
+ {
+ // Merge bitmap alpha to alpha device
+ AlphaMask aAlpha(rBitmapEx.GetSizePixel());
+ aAlpha.Erase( ( 1 - fAlpha ) * 255 );
+ mpAlphaVDev->DrawTransformBitmapExDirect(aFullTransform, BitmapEx(aAlpha, aAlphaBitmap));
+ }
+
+ return bDone;
+};
+
+bool OutputDevice::TransformAndReduceBitmapExToTargetRange(
+ const basegfx::B2DHomMatrix& aFullTransform,
+ basegfx::B2DRange &aVisibleRange,
+ double &fMaximumArea)
+{
+ // limit TargetRange to existing pixels (if pixel device)
+ // first get discrete range of object
+ basegfx::B2DRange aFullPixelRange(aVisibleRange);
+
+ aFullPixelRange.transform(aFullTransform);
+
+ if(basegfx::fTools::equalZero(aFullPixelRange.getWidth()) || basegfx::fTools::equalZero(aFullPixelRange.getHeight()))
+ {
+ // object is outside of visible area
+ return false;
+ }
+
+ // now get discrete target pixels; start with OutDev pixel size and evtl.
+ // intersect with active clipping area
+ basegfx::B2DRange aOutPixel(
+ 0.0,
+ 0.0,
+ GetOutputSizePixel().Width(),
+ GetOutputSizePixel().Height());
+
+ if(IsClipRegion())
+ {
+ tools::Rectangle aRegionRectangle(GetActiveClipRegion().GetBoundRect());
+
+ // caution! Range from rectangle, one too much (!)
+ aRegionRectangle.AdjustRight(-1);
+ aRegionRectangle.AdjustBottom(-1);
+ aOutPixel.intersect( vcl::unotools::b2DRectangleFromRectangle(aRegionRectangle) );
+ }
+
+ if(aOutPixel.isEmpty())
+ {
+ // no active output area
+ return false;
+ }
+
+ // if aFullPixelRange is not completely inside of aOutPixel,
+ // reduction of target pixels is possible
+ basegfx::B2DRange aVisiblePixelRange(aFullPixelRange);
+
+ if(!aOutPixel.isInside(aFullPixelRange))
+ {
+ aVisiblePixelRange.intersect(aOutPixel);
+
+ if(aVisiblePixelRange.isEmpty())
+ {
+ // nothing in visible part, reduces to nothing
+ return false;
+ }
+
+ // aVisiblePixelRange contains the reduced output area in
+ // discrete coordinates. To make it useful everywhere, make it relative to
+ // the object range
+ basegfx::B2DHomMatrix aMakeVisibleRangeRelative;
+
+ aVisibleRange = aVisiblePixelRange;
+ aMakeVisibleRangeRelative.translate(
+ -aFullPixelRange.getMinX(),
+ -aFullPixelRange.getMinY());
+ aMakeVisibleRangeRelative.scale(
+ 1.0 / aFullPixelRange.getWidth(),
+ 1.0 / aFullPixelRange.getHeight());
+ aVisibleRange.transform(aMakeVisibleRangeRelative);
+ }
+
+ // for pixel devices, do *not* limit size, else OutputDevice::DrawDeviceAlphaBitmap
+ // will create another, badly scaled bitmap to do the job. Nonetheless, do a
+ // maximum clipping of something big (1600x1280x2). Add 1.0 to avoid rounding
+ // errors in rough estimations
+ const double fNewMaxArea(aVisiblePixelRange.getWidth() * aVisiblePixelRange.getHeight());
+
+ fMaximumArea = std::min(4096000.0, fNewMaxArea + 1.0);
+
+ return true;
+}
+
+// MM02 add some test class to get a simple timer-based output to be able
+// to check if it gets faster - and how much. Uncomment next line or set
+// DO_TIME_TEST for compile time if you want to use it
+// #define DO_TIME_TEST
+#ifdef DO_TIME_TEST
+#include <tools/time.hxx>
+struct LocalTimeTest
+{
+ const sal_uInt64 nStartTime;
+ LocalTimeTest() : nStartTime(tools::Time::GetSystemTicks()) {}
+ ~LocalTimeTest()
+ {
+ const sal_uInt64 nEndTime(tools::Time::GetSystemTicks());
+ const sal_uInt64 nDiffTime(nEndTime - nStartTime);
+
+ if(nDiffTime > 0)
+ {
+ OStringBuffer aOutput("Time: ");
+ OString aNumber(OString::number(nDiffTime));
+ aOutput.append(aNumber);
+ OSL_FAIL(aOutput.getStr());
+ }
+ }
+};
+#endif
+
+void OutputDevice::DrawTransformedBitmapEx(
+ const basegfx::B2DHomMatrix& rTransformation,
+ const BitmapEx& rBitmapEx,
+ double fAlpha)
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if(rBitmapEx.IsEmpty())
+ return;
+
+ if(rtl::math::approxEqual( fAlpha, 0.0 ))
+ return;
+
+ // MM02 compared to other public methods of OutputDevice
+ // this test was missing and led to zero-ptr-accesses
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ const bool bMetafile(nullptr != mpMetaFile);
+ /*
+ tdf#135325 typically in these OutputDevice methods, for the in
+ record-to-metafile case the MetaFile is already written to before the
+ test against mbOutputClipped to determine that output to the current
+ device would result in no visual output. In this case the metafile is
+ written after the test, so we must continue past mbOutputClipped if
+ recording to a metafile. It's typical to record with a device of nominal
+ size and play back later against something of a totally different size.
+ */
+ if (mbOutputClipped && !bMetafile)
+ return;
+
+#ifdef DO_TIME_TEST
+ // MM02 start time test when some data (not for trivial stuff). Will
+ // trigger and show data when leaving this method by destructing helper
+ static const char* pEnableBitmapDrawTimerTimer(getenv("SAL_ENABLE_TIMER_BITMAPDRAW"));
+ static bool bUseTimer(nullptr != pEnableBitmapDrawTimerTimer);
+ std::unique_ptr<LocalTimeTest> aTimeTest(
+ bUseTimer && rBitmapEx.GetSizeBytes() > 10000
+ ? new LocalTimeTest()
+ : nullptr);
+#endif
+
+ BitmapEx bitmapEx = rBitmapEx;
+
+ const bool bInvert(RasterOp::Invert == meRasterOp);
+ const bool bBitmapChangedColor(mnDrawMode & (DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | DrawModeFlags::GrayBitmap ));
+ const bool bTryDirectPaint(!bInvert && !bBitmapChangedColor && !bMetafile);
+ // tdf#130768 CAUTION(!) using GetViewTransformation() is *not* enough here, it may
+ // be that mnOutOffX/mnOutOffY is used - see AOO bug 75163, mentioned at
+ // ImplGetDeviceTransformation declaration
+ basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rTransformation);
+
+ // First try to handle additional alpha blending, either directly, or modify the bitmap.
+ if(!rtl::math::approxEqual( fAlpha, 1.0 ))
+ {
+ if(bTryDirectPaint)
+ {
+ if(DrawTransformBitmapExDirect(aFullTransform, bitmapEx, fAlpha))
+ {
+ // we are done
+ return;
+ }
+ }
+ // Apply the alpha manually.
+ sal_uInt8 nColor( static_cast<sal_uInt8>( ::basegfx::fround( 255.0*(1.0 - fAlpha) + .5) ) );
+ AlphaMask aAlpha( bitmapEx.GetSizePixel(), &nColor );
+ if( bitmapEx.IsAlpha())
+ aAlpha.BlendWith( bitmapEx.GetAlpha());
+ bitmapEx = BitmapEx( bitmapEx.GetBitmap(), aAlpha );
+ }
+ if(rtl::math::approxEqual( fAlpha, 1.0 ))
+ fAlpha = 1.0; // avoid the need for approxEqual in backends
+
+ // If the backend's implementation is known to not need any optimizations here, pass to it directly.
+ // With most backends it's more performant to try to simplify to DrawBitmapEx() first.
+ if(bTryDirectPaint && mpGraphics->HasFastDrawTransformedBitmap() && DrawTransformBitmapExDirect(aFullTransform, bitmapEx))
+ return;
+
+ // decompose matrix to check rotation and shear
+ basegfx::B2DVector aScale, aTranslate;
+ double fRotate, fShearX;
+ rTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
+ const bool bRotated(!basegfx::fTools::equalZero(fRotate));
+ const bool bSheared(!basegfx::fTools::equalZero(fShearX));
+ const bool bMirroredX(basegfx::fTools::less(aScale.getX(), 0.0));
+ const bool bMirroredY(basegfx::fTools::less(aScale.getY(), 0.0));
+
+ if(!bRotated && !bSheared && !bMirroredX && !bMirroredY)
+ {
+ // with no rotation, shear or mirroring it can be mapped to DrawBitmapEx
+ // do *not* execute the mirroring here, it's done in the fallback
+ // #i124580# the correct DestSize needs to be calculated based on MaxXY values
+ Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY()));
+ const Size aDestSize(
+ basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(),
+ basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y());
+ const Point aOrigin = GetMapMode().GetOrigin();
+ if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel)
+ {
+ aDestPt.Move(aOrigin.getX(), aOrigin.getY());
+ EnableMapMode(false);
+ }
+
+ DrawBitmapEx(aDestPt, aDestSize, bitmapEx);
+ if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel)
+ {
+ EnableMapMode();
+ aDestPt.Move(-aOrigin.getX(), -aOrigin.getY());
+ }
+ return;
+ }
+
+ // Try the backend's implementation before resorting to the slower fallback here.
+ if(bTryDirectPaint && DrawTransformBitmapExDirect(aFullTransform, bitmapEx))
+ return;
+
+ // take the fallback when no rotate and shear, but mirror (else we would have done this above)
+ if(!bRotated && !bSheared)
+ {
+ // with no rotation or shear it can be mapped to DrawBitmapEx
+ // do *not* execute the mirroring here, it's done in the fallback
+ // #i124580# the correct DestSize needs to be calculated based on MaxXY values
+ const Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY()));
+ const Size aDestSize(
+ basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(),
+ basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y());
+
+ DrawBitmapEx(aDestPt, aDestSize, bitmapEx);
+ return;
+ }
+
+ // at this point we are either sheared or rotated or both
+ assert(bSheared || bRotated);
+
+ // fallback; create transformed bitmap the hard way (back-transform
+ // the pixels) and paint
+ basegfx::B2DRange aVisibleRange(0.0, 0.0, 1.0, 1.0);
+
+ // limit maximum area to something looking good for non-pixel-based targets (metafile, printer)
+ // by using a fixed minimum (allow at least, but no need to utilize) for good smoothing and an area
+ // dependent of original size for good quality when e.g. rotated/sheared. Still, limit to a maximum
+ // to avoid crashes/resource problems (ca. 1500x3000 here)
+ const Size& rOriginalSizePixel(bitmapEx.GetSizePixel());
+ const double fOrigArea(rOriginalSizePixel.Width() * rOriginalSizePixel.Height() * 0.5);
+ const double fOrigAreaScaled(fOrigArea * 1.44);
+ double fMaximumArea(std::clamp(fOrigAreaScaled, 1000000.0, 4500000.0));
+
+ if(!bMetafile)
+ {
+ if ( !TransformAndReduceBitmapExToTargetRange( aFullTransform, aVisibleRange, fMaximumArea ) )
+ return;
+ }
+
+ if(aVisibleRange.isEmpty())
+ return;
+
+ BitmapEx aTransformed(bitmapEx);
+
+ // #122923# when the result needs an alpha channel due to being rotated or sheared
+ // and thus uncovering areas, add these channels so that the own transformer (used
+ // in getTransformed) also creates a transformed alpha channel
+ if(!aTransformed.IsAlpha() && (bSheared || bRotated))
+ {
+ // parts will be uncovered, extend aTransformed with a mask bitmap
+ const Bitmap aContent(aTransformed.GetBitmap());
+
+ AlphaMask aMaskBmp(aContent.GetSizePixel());
+ aMaskBmp.Erase(0);
+
+ aTransformed = BitmapEx(aContent, aMaskBmp);
+ }
+
+ basegfx::B2DVector aFullScale, aFullTranslate;
+ double fFullRotate, fFullShearX;
+ aFullTransform.decompose(aFullScale, aFullTranslate, fFullRotate, fFullShearX);
+
+ double fSourceRatio = 1.0;
+ if (rOriginalSizePixel.getHeight() != 0)
+ {
+ fSourceRatio = rOriginalSizePixel.getWidth() / rOriginalSizePixel.getHeight();
+ }
+ double fTargetRatio = 1.0;
+ if (aFullScale.getY() != 0)
+ {
+ fTargetRatio = aFullScale.getX() / aFullScale.getY();
+ }
+ bool bAspectRatioKept = rtl::math::approxEqual(fSourceRatio, fTargetRatio);
+ if (bSheared || !bAspectRatioKept)
+ {
+ // Not only rotation, or scaling does not keep aspect ratio.
+ aTransformed = aTransformed.getTransformed(
+ aFullTransform,
+ aVisibleRange,
+ fMaximumArea);
+ }
+ else
+ {
+ // Just rotation, can do that directly.
+ fFullRotate = fmod(fFullRotate * -1, 2 * M_PI);
+ if (fFullRotate < 0)
+ {
+ fFullRotate += 2 * M_PI;
+ }
+ Degree10 nAngle10(basegfx::fround(basegfx::rad2deg<10>(fFullRotate)));
+ aTransformed.Rotate(nAngle10, COL_TRANSPARENT);
+ }
+ basegfx::B2DRange aTargetRange(0.0, 0.0, 1.0, 1.0);
+
+ // get logic object target range
+ aTargetRange.transform(rTransformation);
+
+ // get from unified/relative VisibleRange to logoc one
+ aVisibleRange.transform(
+ basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aTargetRange.getRange(),
+ aTargetRange.getMinimum()));
+
+ // extract point and size; do not remove size, the bitmap may have been prepared reduced by purpose
+ // #i124580# the correct DestSize needs to be calculated based on MaxXY values
+ const Point aDestPt(basegfx::fround(aVisibleRange.getMinX()), basegfx::fround(aVisibleRange.getMinY()));
+ const Size aDestSize(
+ basegfx::fround(aVisibleRange.getMaxX()) - aDestPt.X(),
+ basegfx::fround(aVisibleRange.getMaxY()) - aDestPt.Y());
+
+ DrawBitmapEx(aDestPt, aDestSize, aTransformed);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/outdev/clipping.cxx b/vcl/source/outdev/clipping.cxx
new file mode 100644
index 000000000..0570719a1
--- /dev/null
+++ b/vcl/source/outdev/clipping.cxx
@@ -0,0 +1,227 @@
+/* -*- 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 <osl/diagnose.h>
+
+#include <tools/debug.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/outdev.hxx>
+
+#include <salgdi.hxx>
+
+void OutputDevice::SaveBackground(VirtualDevice& rSaveDevice,
+ const Point& rPos, const Size& rSize, const Size& rBackgroundSize) const
+{
+ rSaveDevice.DrawOutDev(Point(), rBackgroundSize, rPos, rSize, *this);
+}
+
+vcl::Region OutputDevice::GetClipRegion() const
+{
+
+ return PixelToLogic( maRegion );
+}
+
+void OutputDevice::SetClipRegion()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaClipRegionAction( vcl::Region(), false ) );
+
+ SetDeviceClipRegion( nullptr );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetClipRegion();
+}
+
+void OutputDevice::SetClipRegion( const vcl::Region& rRegion )
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaClipRegionAction( rRegion, true ) );
+
+ if ( rRegion.IsNull() )
+ {
+ SetDeviceClipRegion( nullptr );
+ }
+ else
+ {
+ vcl::Region aRegion = LogicToPixel( rRegion );
+ SetDeviceClipRegion( &aRegion );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetClipRegion( rRegion );
+}
+
+bool OutputDevice::SelectClipRegion( const vcl::Region& rRegion, SalGraphics* pGraphics )
+{
+ DBG_TESTSOLARMUTEX();
+
+ if( !pGraphics )
+ {
+ if( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+ pGraphics = mpGraphics;
+ }
+
+ bool bClipRegion = pGraphics->SetClipRegion( rRegion, *this );
+ OSL_ENSURE( bClipRegion, "OutputDevice::SelectClipRegion() - can't create region" );
+ return bClipRegion;
+}
+
+void OutputDevice::MoveClipRegion( tools::Long nHorzMove, tools::Long nVertMove )
+{
+
+ if ( mbClipRegion )
+ {
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaMoveClipRegionAction( nHorzMove, nVertMove ) );
+
+ maRegion.Move( ImplLogicWidthToDevicePixel( nHorzMove ),
+ ImplLogicHeightToDevicePixel( nVertMove ) );
+ mbInitClipRegion = true;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->MoveClipRegion( nHorzMove, nVertMove );
+}
+
+void OutputDevice::IntersectClipRegion( const tools::Rectangle& rRect )
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaISectRectClipRegionAction( rRect ) );
+
+ tools::Rectangle aRect = LogicToPixel( rRect );
+ maRegion.Intersect( aRect );
+ mbClipRegion = true;
+ mbInitClipRegion = true;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->IntersectClipRegion( rRect );
+}
+
+void OutputDevice::IntersectClipRegion( const vcl::Region& rRegion )
+{
+
+ if(!rRegion.IsNull())
+ {
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaISectRegionClipRegionAction( rRegion ) );
+
+ vcl::Region aRegion = LogicToPixel( rRegion );
+ maRegion.Intersect( aRegion );
+ mbClipRegion = true;
+ mbInitClipRegion = true;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->IntersectClipRegion( rRegion );
+}
+
+void OutputDevice::InitClipRegion()
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( mbClipRegion )
+ {
+ if ( maRegion.IsEmpty() )
+ mbOutputClipped = true;
+ else
+ {
+ mbOutputClipped = false;
+
+ // #102532# Respect output offset also for clip region
+ vcl::Region aRegion = ClipToDeviceBounds(ImplPixelToDevicePixel(maRegion));
+
+ if ( aRegion.IsEmpty() )
+ {
+ mbOutputClipped = true;
+ }
+ else
+ {
+ mbOutputClipped = false;
+ SelectClipRegion( aRegion );
+ }
+ }
+
+ mbClipRegionSet = true;
+ }
+ else
+ {
+ if ( mbClipRegionSet )
+ {
+ if (mpGraphics)
+ mpGraphics->ResetClipRegion();
+ mbClipRegionSet = false;
+ }
+
+ mbOutputClipped = false;
+ }
+
+ mbInitClipRegion = false;
+}
+
+vcl::Region OutputDevice::ClipToDeviceBounds(vcl::Region aRegion) const
+{
+ aRegion.Intersect(tools::Rectangle{mnOutOffX,
+ mnOutOffY,
+ mnOutOffX + GetOutputWidthPixel() - 1,
+ mnOutOffY + GetOutputHeightPixel() - 1
+ });
+ return aRegion;
+}
+
+vcl::Region OutputDevice::GetActiveClipRegion() const
+{
+ return GetClipRegion();
+}
+
+void OutputDevice::ClipToPaintRegion(tools::Rectangle& /*rDstRect*/)
+{
+ // this is only used in Window, but we still need it as it's called
+ // on in other clipping functions
+}
+
+void OutputDevice::SetDeviceClipRegion( const vcl::Region* pRegion )
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( !pRegion )
+ {
+ if ( mbClipRegion )
+ {
+ maRegion = vcl::Region(true);
+ mbClipRegion = false;
+ mbInitClipRegion = true;
+ }
+ }
+ else
+ {
+ maRegion = *pRegion;
+ mbClipRegion = true;
+ mbInitClipRegion = true;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/curvedshapes.cxx b/vcl/source/outdev/curvedshapes.cxx
new file mode 100644
index 000000000..de7379dd3
--- /dev/null
+++ b/vcl/source/outdev/curvedshapes.cxx
@@ -0,0 +1,214 @@
+/* -*- 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 <cassert>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+void OutputDevice::DrawEllipse( const tools::Rectangle& rRect )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaEllipseAction( rRect ) );
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() )
+ return;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+ if ( aRect.IsEmpty() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ tools::Polygon aRectPoly( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 );
+ if ( aRectPoly.GetSize() >= 2 )
+ {
+ Point* pPtAry = aRectPoly.GetPointAry();
+ if ( !mbFillColor )
+ mpGraphics->DrawPolyLine( aRectPoly.GetSize(), pPtAry, *this );
+ else
+ {
+ if ( mbInitFillColor )
+ InitFillColor();
+ mpGraphics->DrawPolygon( aRectPoly.GetSize(), pPtAry, *this );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawEllipse( rRect );
+}
+
+void OutputDevice::DrawArc( const tools::Rectangle& rRect,
+ const Point& rStartPt, const Point& rEndPt )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaArcAction( rRect, rStartPt, rEndPt ) );
+
+ if ( !IsDeviceOutputNecessary() || !mbLineColor || ImplIsRecordLayout() )
+ return;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+ if ( aRect.IsEmpty() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ const Point aStart( ImplLogicToDevicePixel( rStartPt ) );
+ const Point aEnd( ImplLogicToDevicePixel( rEndPt ) );
+ tools::Polygon aArcPoly( aRect, aStart, aEnd, PolyStyle::Arc );
+
+ if ( aArcPoly.GetSize() >= 2 )
+ {
+ Point* pPtAry = aArcPoly.GetPointAry();
+ mpGraphics->DrawPolyLine( aArcPoly.GetSize(), pPtAry, *this );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawArc( rRect, rStartPt, rEndPt );
+}
+
+void OutputDevice::DrawPie( const tools::Rectangle& rRect,
+ const Point& rStartPt, const Point& rEndPt )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPieAction( rRect, rStartPt, rEndPt ) );
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() )
+ return;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+ if ( aRect.IsEmpty() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ const Point aStart( ImplLogicToDevicePixel( rStartPt ) );
+ const Point aEnd( ImplLogicToDevicePixel( rEndPt ) );
+ tools::Polygon aPiePoly( aRect, aStart, aEnd, PolyStyle::Pie );
+
+ if ( aPiePoly.GetSize() >= 2 )
+ {
+ Point* pPtAry = aPiePoly.GetPointAry();
+ if ( !mbFillColor )
+ mpGraphics->DrawPolyLine( aPiePoly.GetSize(), pPtAry, *this );
+ else
+ {
+ if ( mbInitFillColor )
+ InitFillColor();
+ mpGraphics->DrawPolygon( aPiePoly.GetSize(), pPtAry, *this );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPie( rRect, rStartPt, rEndPt );
+}
+
+void OutputDevice::DrawChord( const tools::Rectangle& rRect,
+ const Point& rStartPt, const Point& rEndPt )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaChordAction( rRect, rStartPt, rEndPt ) );
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() )
+ return;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+ if ( aRect.IsEmpty() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ const Point aStart( ImplLogicToDevicePixel( rStartPt ) );
+ const Point aEnd( ImplLogicToDevicePixel( rEndPt ) );
+ tools::Polygon aChordPoly( aRect, aStart, aEnd, PolyStyle::Chord );
+
+ if ( aChordPoly.GetSize() >= 2 )
+ {
+ Point* pPtAry = aChordPoly.GetPointAry();
+ if ( !mbFillColor )
+ mpGraphics->DrawPolyLine( aChordPoly.GetSize(), pPtAry, *this );
+ else
+ {
+ if ( mbInitFillColor )
+ InitFillColor();
+ mpGraphics->DrawPolygon( aChordPoly.GetSize(), pPtAry, *this );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawChord( rRect, rStartPt, rEndPt );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/eps.cxx b/vcl/source/outdev/eps.cxx
new file mode 100644
index 000000000..e3512652b
--- /dev/null
+++ b/vcl/source/outdev/eps.cxx
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <vcl/gfxlink.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+bool OutputDevice::DrawEPS( const Point& rPoint, const Size& rSize,
+ const GfxLink& rGfxLink, GDIMetaFile* pSubst )
+{
+ if ( mpMetaFile )
+ {
+ GDIMetaFile aSubst;
+
+ if( pSubst )
+ aSubst = *pSubst;
+
+ mpMetaFile->AddAction( new MetaEPSAction( rPoint, rSize, rGfxLink, aSubst ) );
+ }
+
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return true;
+
+ if( mbOutputClipped )
+ return true;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( tools::Rectangle( rPoint, rSize ) ) );
+
+ bool bDrawn = true;
+
+ if( !aRect.IsEmpty() )
+ {
+ // draw the real EPS graphics
+ if( rGfxLink.GetData() && rGfxLink.GetDataSize() )
+ {
+ if( !mpGraphics && !AcquireGraphics() )
+ return bDrawn;
+ assert(mpGraphics);
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ aRect.Justify();
+ bDrawn = mpGraphics->DrawEPS( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(),
+ const_cast<sal_uInt8*>(rGfxLink.GetData()), rGfxLink.GetDataSize(), *this );
+ }
+
+ // else draw the substitution graphics
+ if( !bDrawn && pSubst )
+ {
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+
+ mpMetaFile = nullptr;
+ Graphic(*pSubst).Draw(*this, rPoint, rSize);
+ mpMetaFile = pOldMetaFile;
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawEPS( rPoint, rSize, rGfxLink, pSubst );
+
+ return bDrawn;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/outdev/fill.cxx b/vcl/source/outdev/fill.cxx
new file mode 100644
index 000000000..bbe627063
--- /dev/null
+++ b/vcl/source/outdev/fill.cxx
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <tools/debug.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+
+void OutputDevice::SetFillColor()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaFillColorAction( Color(), false ) );
+
+ if ( mbFillColor )
+ {
+ mbInitFillColor = true;
+ mbFillColor = false;
+ maFillColor = COL_TRANSPARENT;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetFillColor();
+}
+
+void OutputDevice::SetFillColor( const Color& rColor )
+{
+ Color aColor(vcl::drawmode::GetFillColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaFillColorAction( aColor, true ) );
+
+ if ( aColor.IsTransparent() )
+ {
+ if ( mbFillColor )
+ {
+ mbInitFillColor = true;
+ mbFillColor = false;
+ maFillColor = COL_TRANSPARENT;
+ }
+ }
+ else
+ {
+ if ( maFillColor != aColor )
+ {
+ mbInitFillColor = true;
+ mbFillColor = true;
+ maFillColor = aColor;
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetFillColor( COL_BLACK );
+}
+
+void OutputDevice::InitFillColor()
+{
+ DBG_TESTSOLARMUTEX();
+
+ if( mbFillColor )
+ {
+ if( RasterOp::N0 == meRasterOp )
+ mpGraphics->SetROPFillColor( SalROPColor::N0 );
+ else if( RasterOp::N1 == meRasterOp )
+ mpGraphics->SetROPFillColor( SalROPColor::N1 );
+ else if( RasterOp::Invert == meRasterOp )
+ mpGraphics->SetROPFillColor( SalROPColor::Invert );
+ else
+ mpGraphics->SetFillColor( maFillColor );
+ }
+ else
+ {
+ mpGraphics->SetFillColor();
+ }
+
+ mbInitFillColor = false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/outdev/font.cxx b/vcl/source/outdev/font.cxx
new file mode 100644
index 000000000..071056e83
--- /dev/null
+++ b/vcl/source/outdev/font.cxx
@@ -0,0 +1,1408 @@
+/* -*- 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 <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <i18nlangtag/lang.h>
+#include <unotools/configmgr.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/print.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/virdev.hxx>
+
+#include <window.h>
+
+#include <ImplLayoutArgs.hxx>
+#include <drawmode.hxx>
+#include <impfontcache.hxx>
+#include <font/DirectFontSubstitution.hxx>
+#include <font/PhysicalFontFaceCollection.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <font/FeatureCollector.hxx>
+#include <impglyphitem.hxx>
+#include <sallayout.hxx>
+#include <salgdi.hxx>
+#include <svdata.hxx>
+
+#include <strings.hrc>
+
+void OutputDevice::SetFont( const vcl::Font& rNewFont )
+{
+ vcl::Font aFont = vcl::drawmode::GetFont(rNewFont, GetDrawMode(), GetSettings().GetStyleSettings());
+
+ if ( mpMetaFile )
+ {
+ mpMetaFile->AddAction( new MetaFontAction( aFont ) );
+ // the color and alignment actions don't belong here
+ // TODO: get rid of them without breaking anything...
+ mpMetaFile->AddAction( new MetaTextAlignAction( aFont.GetAlignment() ) );
+ mpMetaFile->AddAction( new MetaTextFillColorAction( aFont.GetFillColor(), !aFont.IsTransparent() ) );
+ }
+
+ if ( maFont.IsSameInstance( aFont ) )
+ return;
+
+ // Optimization MT/HDU: COL_TRANSPARENT means SetFont should ignore the font color,
+ // because SetTextColor() is used for this.
+ // #i28759# maTextColor might have been changed behind our back, commit then, too.
+ if( aFont.GetColor() != COL_TRANSPARENT
+ && (aFont.GetColor() != maFont.GetColor() || aFont.GetColor() != maTextColor ) )
+ {
+ maTextColor = aFont.GetColor();
+ mbInitTextColor = true;
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextColorAction( aFont.GetColor() ) );
+ }
+ maFont = aFont;
+ mbNewFont = true;
+
+ if( !mpAlphaVDev )
+ return;
+
+ // #i30463#
+ // Since SetFont might change the text color, apply that only
+ // selectively to alpha vdev (which normally paints opaque text
+ // with COL_BLACK)
+ if( aFont.GetColor() != COL_TRANSPARENT )
+ {
+ mpAlphaVDev->SetTextColor( COL_BLACK );
+ aFont.SetColor( COL_TRANSPARENT );
+ }
+
+ mpAlphaVDev->SetFont( aFont );
+}
+
+FontMetric OutputDevice::GetFontMetricFromCollection(int nDevFontIndex) const
+{
+ ImplInitFontList();
+
+ if (nDevFontIndex < GetFontFaceCollectionCount())
+ return FontMetric(*mpFontFaceCollection->Get(nDevFontIndex));
+
+ return FontMetric();
+}
+
+int OutputDevice::GetFontFaceCollectionCount() const
+{
+ if( !mpFontFaceCollection )
+ {
+ if (!mxFontCollection)
+ {
+ return 0;
+ }
+
+ mpFontFaceCollection = mxFontCollection->GetFontFaceCollection();
+
+ if (!mpFontFaceCollection->Count())
+ {
+ mpFontFaceCollection.reset();
+ return 0;
+ }
+ }
+ return mpFontFaceCollection->Count();
+}
+
+bool OutputDevice::IsFontAvailable( std::u16string_view rFontName ) const
+{
+ ImplInitFontList();
+ vcl::font::PhysicalFontFamily* pFound = mxFontCollection->FindFontFamily( rFontName );
+ return (pFound != nullptr);
+}
+
+bool OutputDevice::AddTempDevFont( const OUString& rFileURL, const OUString& rFontName )
+{
+ ImplInitFontList();
+
+ if( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+
+ bool bRC = mpGraphics->AddTempDevFont( mxFontCollection.get(), rFileURL, rFontName );
+ if( !bRC )
+ return false;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->AddTempDevFont( rFileURL, rFontName );
+
+ return true;
+}
+
+bool OutputDevice::GetFontFeatures(std::vector<vcl::font::Feature>& rFontFeatures) const
+{
+ if (!ImplNewFont())
+ return false;
+
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ if (!pFontInstance)
+ return false;
+
+ hb_font_t* pHbFont = pFontInstance->GetHbFont();
+ if (!pHbFont)
+ return false;
+
+ hb_face_t* pHbFace = hb_font_get_face(pHbFont);
+ if (!pHbFace)
+ return false;
+
+ const LanguageType eOfficeLanguage = Application::GetSettings().GetLanguageTag().getLanguageType();
+
+ vcl::font::FeatureCollector aFeatureCollector(pHbFace, rFontFeatures, eOfficeLanguage);
+ aFeatureCollector.collect();
+
+ return true;
+}
+
+FontMetric OutputDevice::GetFontMetric() const
+{
+ FontMetric aMetric;
+ if (!ImplNewFont())
+ return aMetric;
+
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ ImplFontMetricDataRef xFontMetric = pFontInstance->mxFontMetric;
+
+ // prepare metric
+ aMetric = maFont;
+
+ // set aMetric with info from font
+ aMetric.SetFamilyName( maFont.GetFamilyName() );
+ aMetric.SetStyleName( xFontMetric->GetStyleName() );
+ aMetric.SetFontSize( PixelToLogic( Size( xFontMetric->GetWidth(), xFontMetric->GetAscent() + xFontMetric->GetDescent() - xFontMetric->GetInternalLeading() ) ) );
+ aMetric.SetCharSet( xFontMetric->IsSymbolFont() ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE );
+ aMetric.SetFamily( xFontMetric->GetFamilyType() );
+ aMetric.SetPitch( xFontMetric->GetPitch() );
+ aMetric.SetWeight( xFontMetric->GetWeight() );
+ aMetric.SetItalic( xFontMetric->GetItalic() );
+ aMetric.SetAlignment( TextAlign::ALIGN_TOP );
+ aMetric.SetWidthType( xFontMetric->GetWidthType() );
+ if ( pFontInstance->mnOwnOrientation )
+ aMetric.SetOrientation( pFontInstance->mnOwnOrientation );
+ else
+ aMetric.SetOrientation( xFontMetric->GetOrientation() );
+
+ // set remaining metric fields
+ aMetric.SetFullstopCenteredFlag( xFontMetric->IsFullstopCentered() );
+ aMetric.SetBulletOffset( xFontMetric->GetBulletOffset() );
+ aMetric.SetAscent( ImplDevicePixelToLogicHeight( xFontMetric->GetAscent() + mnEmphasisAscent ) );
+ aMetric.SetDescent( ImplDevicePixelToLogicHeight( xFontMetric->GetDescent() + mnEmphasisDescent ) );
+ aMetric.SetInternalLeading( ImplDevicePixelToLogicHeight( xFontMetric->GetInternalLeading() + mnEmphasisAscent ) );
+ // OutputDevice has its own external leading function due to #i60945#
+ aMetric.SetExternalLeading( ImplDevicePixelToLogicHeight( GetFontExtLeading() ) );
+ aMetric.SetLineHeight( ImplDevicePixelToLogicHeight( xFontMetric->GetAscent() + xFontMetric->GetDescent() + mnEmphasisAscent + mnEmphasisDescent ) );
+ aMetric.SetSlant( ImplDevicePixelToLogicHeight( xFontMetric->GetSlant() ) );
+ aMetric.SetHangingBaseline( ImplDevicePixelToLogicHeight( xFontMetric->GetHangingBaseline() ) );
+
+ // get miscellaneous data
+ aMetric.SetQuality( xFontMetric->GetQuality() );
+
+ SAL_INFO("vcl.gdi.fontmetric", "OutputDevice::GetFontMetric:" << aMetric);
+
+ xFontMetric = nullptr;
+
+ return aMetric;
+}
+
+FontMetric OutputDevice::GetFontMetric( const vcl::Font& rFont ) const
+{
+ // select font, query metrics, select original font again
+ vcl::Font aOldFont = GetFont();
+ const_cast<OutputDevice*>(this)->SetFont( rFont );
+ FontMetric aMetric( GetFontMetric() );
+ const_cast<OutputDevice*>(this)->SetFont( aOldFont );
+ return aMetric;
+}
+
+bool OutputDevice::GetFontCharMap( FontCharMapRef& rxFontCharMap ) const
+{
+ if (!InitFont())
+ return false;
+
+ FontCharMapRef xFontCharMap ( mpGraphics->GetFontCharMap() );
+ if (!xFontCharMap.is())
+ {
+ FontCharMapRef xDefaultMap( new FontCharMap() );
+ rxFontCharMap = xDefaultMap;
+ }
+ else
+ rxFontCharMap = xFontCharMap;
+
+ return !rxFontCharMap->IsDefaultMap();
+}
+
+bool OutputDevice::GetFontCapabilities( vcl::FontCapabilities& rFontCapabilities ) const
+{
+ if (!InitFont())
+ return false;
+ return mpGraphics->GetFontCapabilities(rFontCapabilities);
+}
+
+void OutputDevice::ImplGetEmphasisMark( tools::PolyPolygon& rPolyPoly, bool& rPolyLine,
+ tools::Rectangle& rRect1, tools::Rectangle& rRect2,
+ tools::Long& rYOff, tools::Long& rWidth,
+ FontEmphasisMark eEmphasis,
+ tools::Long nHeight )
+{
+ static const PolyFlags aAccentPolyFlags[24] =
+ {
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Normal, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control
+ };
+
+ static const Point aAccentPos[24] =
+ {
+ { 78, 0 },
+ { 348, 79 },
+ { 599, 235 },
+ { 843, 469 },
+ { 938, 574 },
+ { 990, 669 },
+ { 990, 773 },
+ { 990, 843 },
+ { 964, 895 },
+ { 921, 947 },
+ { 886, 982 },
+ { 860, 999 },
+ { 825, 999 },
+ { 764, 999 },
+ { 721, 964 },
+ { 686, 895 },
+ { 625, 791 },
+ { 556, 660 },
+ { 469, 504 },
+ { 400, 400 },
+ { 261, 252 },
+ { 61, 61 },
+ { 0, 27 },
+ { 9, 0 }
+ };
+
+ rWidth = 0;
+ rYOff = 0;
+ rPolyLine = false;
+
+ if ( !nHeight )
+ return;
+
+ FontEmphasisMark nEmphasisStyle = eEmphasis & FontEmphasisMark::Style;
+ tools::Long nDotSize = 0;
+ switch ( nEmphasisStyle )
+ {
+ case FontEmphasisMark::Dot:
+ // Dot has 55% of the height
+ nDotSize = (nHeight*550)/1000;
+ if ( !nDotSize )
+ nDotSize = 1;
+ if ( nDotSize <= 2 )
+ rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ else
+ {
+ tools::Long nRad = nDotSize/2;
+ tools::Polygon aPoly( Point( nRad, nRad ), nRad, nRad );
+ rPolyPoly.Insert( aPoly );
+ }
+ rYOff = ((nHeight*250)/1000)/2; // Center to the another EmphasisMarks
+ rWidth = nDotSize;
+ break;
+
+ case FontEmphasisMark::Circle:
+ // Dot has 80% of the height
+ nDotSize = (nHeight*800)/1000;
+ if ( !nDotSize )
+ nDotSize = 1;
+ if ( nDotSize <= 2 )
+ rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ else
+ {
+ tools::Long nRad = nDotSize/2;
+ tools::Polygon aPoly( Point( nRad, nRad ), nRad, nRad );
+ rPolyPoly.Insert( aPoly );
+ // BorderWidth is 15%
+ tools::Long nBorder = (nDotSize*150)/1000;
+ if ( nBorder <= 1 )
+ rPolyLine = true;
+ else
+ {
+ tools::Polygon aPoly2( Point( nRad, nRad ),
+ nRad-nBorder, nRad-nBorder );
+ rPolyPoly.Insert( aPoly2 );
+ }
+ }
+ rWidth = nDotSize;
+ break;
+
+ case FontEmphasisMark::Disc:
+ // Dot has 80% of the height
+ nDotSize = (nHeight*800)/1000;
+ if ( !nDotSize )
+ nDotSize = 1;
+ if ( nDotSize <= 2 )
+ rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ else
+ {
+ tools::Long nRad = nDotSize/2;
+ tools::Polygon aPoly( Point( nRad, nRad ), nRad, nRad );
+ rPolyPoly.Insert( aPoly );
+ }
+ rWidth = nDotSize;
+ break;
+
+ case FontEmphasisMark::Accent:
+ // Dot has 80% of the height
+ nDotSize = (nHeight*800)/1000;
+ if ( !nDotSize )
+ nDotSize = 1;
+ if ( nDotSize <= 2 )
+ {
+ if ( nDotSize == 1 )
+ {
+ rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ rWidth = nDotSize;
+ }
+ else
+ {
+ rRect1 = tools::Rectangle( Point(), Size( 1, 1 ) );
+ rRect2 = tools::Rectangle( Point( 1, 1 ), Size( 1, 1 ) );
+ }
+ }
+ else
+ {
+ tools::Polygon aPoly( SAL_N_ELEMENTS(aAccentPos), aAccentPos,
+ aAccentPolyFlags );
+ double dScale = static_cast<double>(nDotSize)/1000.0;
+ aPoly.Scale( dScale, dScale );
+ tools::Polygon aTemp;
+ aPoly.AdaptiveSubdivide( aTemp );
+ tools::Rectangle aBoundRect = aTemp.GetBoundRect();
+ rWidth = aBoundRect.GetWidth();
+ nDotSize = aBoundRect.GetHeight();
+ rPolyPoly.Insert( aTemp );
+ }
+ break;
+ default: break;
+ }
+
+ // calculate position
+ tools::Long nOffY = 1+(mnDPIY/300); // one visible pixel space
+ tools::Long nSpaceY = nHeight-nDotSize;
+ if ( nSpaceY >= nOffY*2 )
+ rYOff += nOffY;
+ if ( !(eEmphasis & FontEmphasisMark::PosBelow) )
+ rYOff += nDotSize;
+}
+
+FontEmphasisMark OutputDevice::ImplGetEmphasisMarkStyle( const vcl::Font& rFont )
+{
+ FontEmphasisMark nEmphasisMark = rFont.GetEmphasisMark();
+
+ // If no Position is set, then calculate the default position, which
+ // depends on the language
+ if ( !(nEmphasisMark & (FontEmphasisMark::PosAbove | FontEmphasisMark::PosBelow)) )
+ {
+ LanguageType eLang = rFont.GetLanguage();
+ // In Chinese Simplified the EmphasisMarks are below/left
+ if (MsLangId::isSimplifiedChinese(eLang))
+ nEmphasisMark |= FontEmphasisMark::PosBelow;
+ else
+ {
+ eLang = rFont.GetCJKContextLanguage();
+ // In Chinese Simplified the EmphasisMarks are below/left
+ if (MsLangId::isSimplifiedChinese(eLang))
+ nEmphasisMark |= FontEmphasisMark::PosBelow;
+ else
+ nEmphasisMark |= FontEmphasisMark::PosAbove;
+ }
+ }
+
+ return nEmphasisMark;
+}
+
+tools::Long OutputDevice::GetFontExtLeading() const
+{
+ return mpFontInstance->mxFontMetric->GetExternalLeading();
+}
+
+void OutputDevice::ImplClearFontData( const bool bNewFontLists )
+{
+ // the currently selected logical font is no longer needed
+ mpFontInstance.clear();
+
+ mbInitFont = true;
+ mbNewFont = true;
+
+ if ( bNewFontLists )
+ {
+ mpFontFaceCollection.reset();
+
+ // release all physically selected fonts on this device
+ if( AcquireGraphics() )
+ mpGraphics->ReleaseFonts();
+ }
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if (mxFontCache && mxFontCache != pSVData->maGDIData.mxScreenFontCache)
+ mxFontCache->Invalidate();
+
+ if (bNewFontLists && AcquireGraphics())
+ {
+ if (mxFontCollection && mxFontCollection != pSVData->maGDIData.mxScreenFontList)
+ mxFontCollection->Clear();
+ }
+}
+
+void OutputDevice::RefreshFontData( const bool bNewFontLists )
+{
+ ImplRefreshFontData( bNewFontLists );
+}
+
+void OutputDevice::ImplRefreshFontData( const bool bNewFontLists )
+{
+ if (bNewFontLists && AcquireGraphics())
+ mpGraphics->GetDevFontList( mxFontCollection.get() );
+}
+
+void OutputDevice::ImplUpdateFontData()
+{
+ ImplClearFontData( true/*bNewFontLists*/ );
+ ImplRefreshFontData( true/*bNewFontLists*/ );
+}
+
+void OutputDevice::ImplClearAllFontData(bool bNewFontLists)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ ImplUpdateFontDataForAllFrames( &OutputDevice::ImplClearFontData, bNewFontLists );
+
+ // clear global font lists to have them updated
+ pSVData->maGDIData.mxScreenFontCache->Invalidate();
+ if ( !bNewFontLists )
+ return;
+
+ pSVData->maGDIData.mxScreenFontList->Clear();
+ vcl::Window * pFrame = pSVData->maFrameData.mpFirstFrame;
+ if ( pFrame )
+ {
+ if ( pFrame->GetOutDev()->AcquireGraphics() )
+ {
+ OutputDevice *pDevice = pFrame->GetOutDev();
+ pDevice->mpGraphics->ClearDevFontCache();
+ pDevice->mpGraphics->GetDevFontList(pFrame->mpWindowImpl->mpFrameData->mxFontCollection.get());
+ }
+ }
+}
+
+void OutputDevice::ImplRefreshAllFontData(bool bNewFontLists)
+{
+ ImplUpdateFontDataForAllFrames( &OutputDevice::ImplRefreshFontData, bNewFontLists );
+}
+
+void OutputDevice::ImplUpdateAllFontData(bool bNewFontLists)
+{
+ OutputDevice::ImplClearAllFontData(bNewFontLists);
+ OutputDevice::ImplRefreshAllFontData(bNewFontLists);
+}
+
+void OutputDevice::ImplUpdateFontDataForAllFrames( const FontUpdateHandler_t pHdl, const bool bNewFontLists )
+{
+ ImplSVData* const pSVData = ImplGetSVData();
+
+ // update all windows
+ vcl::Window* pFrame = pSVData->maFrameData.mpFirstFrame;
+ while ( pFrame )
+ {
+ ( pFrame->GetOutDev()->*pHdl )( bNewFontLists );
+
+ vcl::Window* pSysWin = pFrame->mpWindowImpl->mpFrameData->mpFirstOverlap;
+ while ( pSysWin )
+ {
+ ( pSysWin->GetOutDev()->*pHdl )( bNewFontLists );
+ pSysWin = pSysWin->mpWindowImpl->mpNextOverlap;
+ }
+
+ pFrame = pFrame->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+
+ // update all virtual devices
+ VirtualDevice* pVirDev = pSVData->maGDIData.mpFirstVirDev;
+ while ( pVirDev )
+ {
+ ( pVirDev->*pHdl )( bNewFontLists );
+ pVirDev = pVirDev->mpNext;
+ }
+
+ // update all printers
+ Printer* pPrinter = pSVData->maGDIData.mpFirstPrinter;
+ while ( pPrinter )
+ {
+ ( pPrinter->*pHdl )( bNewFontLists );
+ pPrinter = pPrinter->mpNext;
+ }
+}
+
+void OutputDevice::BeginFontSubstitution()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maGDIData.mbFontSubChanged = false;
+}
+
+void OutputDevice::EndFontSubstitution()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( pSVData->maGDIData.mbFontSubChanged )
+ {
+ ImplUpdateAllFontData( false );
+
+ DataChangedEvent aDCEvt( DataChangedEventType::FONTSUBSTITUTION );
+ Application::ImplCallEventListenersApplicationDataChanged(&aDCEvt);
+ Application::NotifyAllWindows( aDCEvt );
+ pSVData->maGDIData.mbFontSubChanged = false;
+ }
+}
+
+void OutputDevice::AddFontSubstitute( const OUString& rFontName,
+ const OUString& rReplaceFontName,
+ AddFontSubstituteFlags nFlags )
+{
+ vcl::font::DirectFontSubstitution*& rpSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst;
+ if( !rpSubst )
+ rpSubst = new vcl::font::DirectFontSubstitution;
+ rpSubst->AddFontSubstitute( rFontName, rReplaceFontName, nFlags );
+ ImplGetSVData()->maGDIData.mbFontSubChanged = true;
+}
+
+void OutputDevice::RemoveFontsSubstitute()
+{
+ vcl::font::DirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst;
+ if( pSubst )
+ pSubst->RemoveFontsSubstitute();
+}
+
+//hidpi TODO: This routine has hard-coded font-sizes that break places such as DialControl
+vcl::Font OutputDevice::GetDefaultFont( DefaultFontType nType, LanguageType eLang,
+ GetDefaultFontFlags nFlags, const OutputDevice* pOutDev )
+{
+ if (!pOutDev && !utl::ConfigManager::IsFuzzing()) // default is NULL
+ pOutDev = Application::GetDefaultDevice();
+
+ OUString aSearch;
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ LanguageTag aLanguageTag(
+ ( eLang == LANGUAGE_NONE || eLang == LANGUAGE_SYSTEM || eLang == LANGUAGE_DONTKNOW ) ?
+ Application::GetSettings().GetUILanguageTag() :
+ LanguageTag( eLang ));
+
+ utl::DefaultFontConfiguration& rDefaults = utl::DefaultFontConfiguration::get();
+ OUString aDefault = rDefaults.getDefaultFont( aLanguageTag, nType );
+
+ if( !aDefault.isEmpty() )
+ aSearch = aDefault;
+ else
+ aSearch = rDefaults.getUserInterfaceFont( aLanguageTag ); // use the UI font as a fallback
+ }
+ else
+ aSearch = "Liberation Serif";
+
+ vcl::Font aFont;
+ aFont.SetPitch( PITCH_VARIABLE );
+
+ switch ( nType )
+ {
+ case DefaultFontType::SANS_UNICODE:
+ case DefaultFontType::UI_SANS:
+ aFont.SetFamily( FAMILY_SWISS );
+ break;
+
+ case DefaultFontType::SANS:
+ case DefaultFontType::LATIN_HEADING:
+ case DefaultFontType::LATIN_SPREADSHEET:
+ case DefaultFontType::LATIN_DISPLAY:
+ aFont.SetFamily( FAMILY_SWISS );
+ break;
+
+ case DefaultFontType::SERIF:
+ case DefaultFontType::LATIN_TEXT:
+ case DefaultFontType::LATIN_PRESENTATION:
+ aFont.SetFamily( FAMILY_ROMAN );
+ break;
+
+ case DefaultFontType::FIXED:
+ case DefaultFontType::LATIN_FIXED:
+ case DefaultFontType::UI_FIXED:
+ aFont.SetPitch( PITCH_FIXED );
+ aFont.SetFamily( FAMILY_MODERN );
+ break;
+
+ case DefaultFontType::SYMBOL:
+ aFont.SetCharSet( RTL_TEXTENCODING_SYMBOL );
+ break;
+
+ case DefaultFontType::CJK_TEXT:
+ case DefaultFontType::CJK_PRESENTATION:
+ case DefaultFontType::CJK_SPREADSHEET:
+ case DefaultFontType::CJK_HEADING:
+ case DefaultFontType::CJK_DISPLAY:
+ aFont.SetFamily( FAMILY_SYSTEM ); // don't care, but don't use font subst config later...
+ break;
+
+ case DefaultFontType::CTL_TEXT:
+ case DefaultFontType::CTL_PRESENTATION:
+ case DefaultFontType::CTL_SPREADSHEET:
+ case DefaultFontType::CTL_HEADING:
+ case DefaultFontType::CTL_DISPLAY:
+ aFont.SetFamily( FAMILY_SYSTEM ); // don't care, but don't use font subst config later...
+ break;
+ }
+
+ if ( !aSearch.isEmpty() )
+ {
+ aFont.SetFontHeight( 12 ); // corresponds to nDefaultHeight
+ aFont.SetWeight( WEIGHT_NORMAL );
+ aFont.SetLanguage( eLang );
+
+ if ( aFont.GetCharSet() == RTL_TEXTENCODING_DONTKNOW )
+ aFont.SetCharSet( osl_getThreadTextEncoding() );
+
+ // Should we only return available fonts on the given device
+ if ( pOutDev )
+ {
+ pOutDev->ImplInitFontList();
+
+ // Search Font in the FontList
+ OUString aName;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ vcl::font::PhysicalFontFamily* pFontFamily = pOutDev->mxFontCollection->FindFontFamily( GetNextFontToken( aSearch, nIndex ) );
+ if( pFontFamily )
+ {
+ AddTokenFontName( aName, pFontFamily->GetFamilyName() );
+ if( nFlags & GetDefaultFontFlags::OnlyOne )
+ break;
+ }
+ }
+ while ( nIndex != -1 );
+ aFont.SetFamilyName( aName );
+ }
+
+ // No Name, then set all names
+ if ( aFont.GetFamilyName().isEmpty() )
+ {
+ if ( nFlags & GetDefaultFontFlags::OnlyOne )
+ {
+ if( !pOutDev )
+ {
+ SAL_WARN_IF(!utl::ConfigManager::IsFuzzing(), "vcl.gdi", "No default window has been set for the application - we really shouldn't be able to get here");
+ aFont.SetFamilyName( aSearch.getToken( 0, ';' ) );
+ }
+ else
+ {
+ pOutDev->ImplInitFontList();
+
+ aFont.SetFamilyName( aSearch );
+
+ // convert to pixel height
+ Size aSize = pOutDev->ImplLogicToDevicePixel( aFont.GetFontSize() );
+ if ( !aSize.Height() )
+ {
+ // use default pixel height only when logical height is zero
+ if ( aFont.GetFontHeight() )
+ aSize.setHeight( 1 );
+ else
+ aSize.setHeight( (12*pOutDev->mnDPIY)/72 );
+ }
+
+ // use default width only when logical width is zero
+ if( (0 == aSize.Width()) && (0 != aFont.GetFontSize().Width()) )
+ aSize.setWidth( 1 );
+
+ // get the name of the first available font
+ float fExactHeight = static_cast<float>(aSize.Height());
+ rtl::Reference<LogicalFontInstance> pFontInstance = pOutDev->mxFontCache->GetFontInstance( pOutDev->mxFontCollection.get(), aFont, aSize, fExactHeight );
+ if (pFontInstance)
+ {
+ assert(pFontInstance->GetFontFace());
+ aFont.SetFamilyName(pFontInstance->GetFontFace()->GetFamilyName());
+ }
+ }
+ }
+ else
+ aFont.SetFamilyName( aSearch );
+ }
+ }
+
+#if OSL_DEBUG_LEVEL > 2
+ const char* s = "SANS_UNKNOWN";
+ switch ( nType )
+ {
+ case DefaultFontType::SANS_UNICODE: s = "SANS_UNICODE"; break;
+ case DefaultFontType::UI_SANS: s = "UI_SANS"; break;
+
+ case DefaultFontType::SANS: s = "SANS"; break;
+ case DefaultFontType::LATIN_HEADING: s = "LATIN_HEADING"; break;
+ case DefaultFontType::LATIN_SPREADSHEET: s = "LATIN_SPREADSHEET"; break;
+ case DefaultFontType::LATIN_DISPLAY: s = "LATIN_DISPLAY"; break;
+
+ case DefaultFontType::SERIF: s = "SERIF"; break;
+ case DefaultFontType::LATIN_TEXT: s = "LATIN_TEXT"; break;
+ case DefaultFontType::LATIN_PRESENTATION: s = "LATIN_PRESENTATION"; break;
+
+ case DefaultFontType::FIXED: s = "FIXED"; break;
+ case DefaultFontType::LATIN_FIXED: s = "LATIN_FIXED"; break;
+ case DefaultFontType::UI_FIXED: s = "UI_FIXED"; break;
+
+ case DefaultFontType::SYMBOL: s = "SYMBOL"; break;
+
+ case DefaultFontType::CJK_TEXT: s = "CJK_TEXT"; break;
+ case DefaultFontType::CJK_PRESENTATION: s = "CJK_PRESENTATION"; break;
+ case DefaultFontType::CJK_SPREADSHEET: s = "CJK_SPREADSHEET"; break;
+ case DefaultFontType::CJK_HEADING: s = "CJK_HEADING"; break;
+ case DefaultFontType::CJK_DISPLAY: s = "CJK_DISPLAY"; break;
+
+ case DefaultFontType::CTL_TEXT: s = "CTL_TEXT"; break;
+ case DefaultFontType::CTL_PRESENTATION: s = "CTL_PRESENTATION"; break;
+ case DefaultFontType::CTL_SPREADSHEET: s = "CTL_SPREADSHEET"; break;
+ case DefaultFontType::CTL_HEADING: s = "CTL_HEADING"; break;
+ case DefaultFontType::CTL_DISPLAY: s = "CTL_DISPLAY"; break;
+ }
+ SAL_INFO("vcl.gdi",
+ "OutputDevice::GetDefaultFont() Type=" << s
+ << " lang=" << eLang
+ << " flags=" << static_cast<int>(nFlags)
+ << " family=\"" << aFont.GetFamilyName() << "\"");
+#endif
+
+ return aFont;
+}
+
+void OutputDevice::ImplInitFontList() const
+{
+ if( mxFontCollection->Count() )
+ return;
+
+ if( !(mpGraphics || AcquireGraphics()) )
+ return;
+ assert(mpGraphics);
+
+ SAL_INFO( "vcl.gdi", "OutputDevice::ImplInitFontList()" );
+ mpGraphics->GetDevFontList(mxFontCollection.get());
+
+ // There is absolutely no way there should be no fonts available on the device
+ if( !mxFontCollection->Count() )
+ {
+ OUString aError( "Application error: no fonts and no vcl resource found on your system" );
+ OUString aResStr(VclResId(SV_ACCESSERROR_NO_FONTS));
+ if (!aResStr.isEmpty())
+ aError = aResStr;
+ Application::Abort(aError);
+ }
+}
+
+bool OutputDevice::InitFont() const
+{
+ DBG_TESTSOLARMUTEX();
+
+ if (!ImplNewFont())
+ return false;
+ if (!mpFontInstance)
+ return false;
+ if (!mpGraphics)
+ {
+ if (!AcquireGraphics())
+ return false;
+ }
+ else if (!mbInitFont)
+ return true;
+
+ assert(mpGraphics);
+ mpGraphics->SetFont(mpFontInstance.get(), 0);
+ mbInitFont = false;
+ return true;
+}
+
+const LogicalFontInstance* OutputDevice::GetFontInstance() const
+{
+ if (!InitFont())
+ return nullptr;
+ return mpFontInstance.get();
+}
+
+bool OutputDevice::ImplNewFont() const
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( !mbNewFont )
+ return true;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ {
+ SAL_WARN("vcl.gdi", "OutputDevice::ImplNewFont(): no Graphics, no Font");
+ return false;
+ }
+ assert(mpGraphics);
+
+ ImplInitFontList();
+
+ // convert to pixel height
+ // TODO: replace integer based aSize completely with subpixel accurate type
+ float fExactHeight = ImplFloatLogicHeightToDevicePixel( static_cast<float>(maFont.GetFontHeight()) );
+ Size aSize = ImplLogicToDevicePixel( maFont.GetFontSize() );
+ if ( !aSize.Height() )
+ {
+ // use default pixel height only when logical height is zero
+ if ( maFont.GetFontSize().Height() )
+ aSize.setHeight( 1 );
+ else
+ aSize.setHeight( (12*mnDPIY)/72 );
+ fExactHeight = static_cast<float>(aSize.Height());
+ }
+
+ // select the default width only when logical width is zero
+ if( (0 == aSize.Width()) && (0 != maFont.GetFontSize().Width()) )
+ aSize.setWidth( 1 );
+
+ // decide if antialiasing is appropriate
+ bool bNonAntialiased(GetAntialiasing() & AntialiasingFlags::DisableText);
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ bNonAntialiased |= bool(rStyleSettings.GetDisplayOptions() & DisplayOptions::AADisable);
+ bNonAntialiased |= (int(rStyleSettings.GetAntialiasingMinPixelHeight()) > maFont.GetFontSize().Height());
+ }
+
+ // get font entry
+ rtl::Reference<LogicalFontInstance> pOldFontInstance = mpFontInstance;
+ mpFontInstance = mxFontCache->GetFontInstance(mxFontCollection.get(), maFont, aSize, fExactHeight, bNonAntialiased);
+ const bool bNewFontInstance = pOldFontInstance.get() != mpFontInstance.get();
+ pOldFontInstance.clear();
+
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+
+ if (!pFontInstance)
+ {
+ SAL_WARN("vcl.gdi", "OutputDevice::ImplNewFont(): no LogicalFontInstance, no Font");
+ return false;
+ }
+
+ // mark when lower layers need to get involved
+ mbNewFont = false;
+ if( bNewFontInstance )
+ mbInitFont = true;
+
+ // select font when it has not been initialized yet
+ if (!pFontInstance->mbInit && InitFont())
+ {
+ // get metric data from device layers
+ pFontInstance->mbInit = true;
+
+ pFontInstance->mxFontMetric->SetOrientation( mpFontInstance->GetFontSelectPattern().mnOrientation );
+ mpGraphics->GetFontMetric( pFontInstance->mxFontMetric, 0 );
+
+ pFontInstance->mxFontMetric->ImplInitTextLineSize( this );
+ pFontInstance->mxFontMetric->ImplInitAboveTextLineSize();
+ pFontInstance->mxFontMetric->ImplInitFlags( this );
+
+ pFontInstance->mnLineHeight = pFontInstance->mxFontMetric->GetAscent() + pFontInstance->mxFontMetric->GetDescent();
+
+ SetFontOrientation( pFontInstance );
+ }
+
+ // calculate EmphasisArea
+ mnEmphasisAscent = 0;
+ mnEmphasisDescent = 0;
+ if ( maFont.GetEmphasisMark() & FontEmphasisMark::Style )
+ {
+ FontEmphasisMark nEmphasisMark = ImplGetEmphasisMarkStyle( maFont );
+ tools::Long nEmphasisHeight = (pFontInstance->mnLineHeight*250)/1000;
+ if ( nEmphasisHeight < 1 )
+ nEmphasisHeight = 1;
+ if ( nEmphasisMark & FontEmphasisMark::PosBelow )
+ mnEmphasisDescent = nEmphasisHeight;
+ else
+ mnEmphasisAscent = nEmphasisHeight;
+ }
+
+ // calculate text offset depending on TextAlignment
+ TextAlign eAlign = maFont.GetAlignment();
+ if ( eAlign == ALIGN_BASELINE )
+ {
+ mnTextOffX = 0;
+ mnTextOffY = 0;
+ }
+ else if ( eAlign == ALIGN_TOP )
+ {
+ mnTextOffX = 0;
+ mnTextOffY = +pFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;
+ if ( pFontInstance->mnOrientation )
+ {
+ Point aOriginPt(0, 0);
+ aOriginPt.RotateAround( mnTextOffX, mnTextOffY, pFontInstance->mnOrientation );
+ }
+ }
+ else // eAlign == ALIGN_BOTTOM
+ {
+ mnTextOffX = 0;
+ mnTextOffY = -pFontInstance->mxFontMetric->GetDescent() + mnEmphasisDescent;
+ if ( pFontInstance->mnOrientation )
+ {
+ Point aOriginPt(0, 0);
+ aOriginPt.RotateAround( mnTextOffX, mnTextOffY, pFontInstance->mnOrientation );
+ }
+ }
+
+ mbTextLines = ((maFont.GetUnderline() != LINESTYLE_NONE) && (maFont.GetUnderline() != LINESTYLE_DONTKNOW)) ||
+ ((maFont.GetOverline() != LINESTYLE_NONE) && (maFont.GetOverline() != LINESTYLE_DONTKNOW)) ||
+ ((maFont.GetStrikeout() != STRIKEOUT_NONE) && (maFont.GetStrikeout() != STRIKEOUT_DONTKNOW));
+ mbTextSpecial = maFont.IsShadow() || maFont.IsOutline() ||
+ (maFont.GetRelief() != FontRelief::NONE);
+
+
+ bool bRet = true;
+
+ // #95414# fix for OLE objects which use scale factors very creatively
+ if (mbMap && !aSize.Width())
+ bRet = AttemptOLEFontScaleFix(const_cast<vcl::Font&>(maFont), aSize.Height());
+
+ return bRet;
+}
+
+bool OutputDevice::AttemptOLEFontScaleFix(vcl::Font& rFont, tools::Long nHeight) const
+{
+ const float fDenominator = static_cast<float>(maMapRes.mnMapScNumY) * maMapRes.mnMapScDenomX;
+ if (fDenominator == 0.0)
+ return false;
+ const float fNumerator = static_cast<float>(maMapRes.mnMapScNumX) * maMapRes.mnMapScDenomY;
+ float fStretch = fNumerator / fDenominator;
+ int nOrigWidth = mpFontInstance->mxFontMetric->GetWidth();
+ int nNewWidth = static_cast<int>(nOrigWidth * fStretch + 0.5);
+ bool bRet = true;
+ if (nNewWidth != nOrigWidth && nNewWidth != 0)
+ {
+ Size aOrigSize = rFont.GetFontSize();
+ rFont.SetFontSize(Size(nNewWidth, nHeight));
+ mbMap = false;
+ mbNewFont = true;
+ bRet = ImplNewFont(); // recurse once using stretched width
+ mbMap = true;
+ rFont.SetFontSize(aOrigSize);
+ }
+ return bRet;
+}
+
+void OutputDevice::SetFontOrientation( LogicalFontInstance* const pFontInstance ) const
+{
+ if( pFontInstance->GetFontSelectPattern().mnOrientation && !pFontInstance->mxFontMetric->GetOrientation() )
+ {
+ pFontInstance->mnOwnOrientation = pFontInstance->GetFontSelectPattern().mnOrientation;
+ pFontInstance->mnOrientation = pFontInstance->mnOwnOrientation;
+ }
+ else
+ {
+ pFontInstance->mnOrientation = pFontInstance->mxFontMetric->GetOrientation();
+ }
+}
+
+void OutputDevice::ImplDrawEmphasisMark( tools::Long nBaseX, tools::Long nX, tools::Long nY,
+ const tools::PolyPolygon& rPolyPoly, bool bPolyLine,
+ const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 )
+{
+ if( IsRTLEnabled() )
+ nX = nBaseX - (nX - nBaseX - 1);
+
+ nX -= mnOutOffX;
+ nY -= mnOutOffY;
+
+ if ( rPolyPoly.Count() )
+ {
+ if ( bPolyLine )
+ {
+ tools::Polygon aPoly = rPolyPoly.GetObject( 0 );
+ aPoly.Move( nX, nY );
+ DrawPolyLine( aPoly );
+ }
+ else
+ {
+ tools::PolyPolygon aPolyPoly = rPolyPoly;
+ aPolyPoly.Move( nX, nY );
+ DrawPolyPolygon( aPolyPoly );
+ }
+ }
+
+ if ( !rRect1.IsEmpty() )
+ {
+ tools::Rectangle aRect( Point( nX+rRect1.Left(),
+ nY+rRect1.Top() ), rRect1.GetSize() );
+ DrawRect( aRect );
+ }
+
+ if ( !rRect2.IsEmpty() )
+ {
+ tools::Rectangle aRect( Point( nX+rRect2.Left(),
+ nY+rRect2.Top() ), rRect2.GetSize() );
+
+ DrawRect( aRect );
+ }
+}
+
+void OutputDevice::ImplDrawEmphasisMarks( SalLayout& rSalLayout )
+{
+ Color aOldLineColor = GetLineColor();
+ Color aOldFillColor = GetFillColor();
+ bool bOldMap = mbMap;
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ mpMetaFile = nullptr;
+ EnableMapMode( false );
+
+ FontEmphasisMark nEmphasisMark = ImplGetEmphasisMarkStyle( maFont );
+ tools::PolyPolygon aPolyPoly;
+ tools::Rectangle aRect1;
+ tools::Rectangle aRect2;
+ tools::Long nEmphasisYOff;
+ tools::Long nEmphasisWidth;
+ tools::Long nEmphasisHeight;
+ bool bPolyLine;
+
+ if ( nEmphasisMark & FontEmphasisMark::PosBelow )
+ nEmphasisHeight = mnEmphasisDescent;
+ else
+ nEmphasisHeight = mnEmphasisAscent;
+
+ ImplGetEmphasisMark( aPolyPoly, bPolyLine,
+ aRect1, aRect2,
+ nEmphasisYOff, nEmphasisWidth,
+ nEmphasisMark,
+ nEmphasisHeight );
+
+ if ( bPolyLine )
+ {
+ SetLineColor( GetTextColor() );
+ SetFillColor();
+ }
+ else
+ {
+ SetLineColor();
+ SetFillColor( GetTextColor() );
+ }
+
+ Point aOffset(0,0);
+
+ if ( nEmphasisMark & FontEmphasisMark::PosBelow )
+ aOffset.AdjustY(mpFontInstance->mxFontMetric->GetDescent() + nEmphasisYOff );
+ else
+ aOffset.AdjustY( -(mpFontInstance->mxFontMetric->GetAscent() + nEmphasisYOff) );
+
+ tools::Long nEmphasisWidth2 = nEmphasisWidth / 2;
+ tools::Long nEmphasisHeight2 = nEmphasisHeight / 2;
+ aOffset += Point( nEmphasisWidth2, nEmphasisHeight2 );
+
+ DevicePoint aOutPoint;
+ tools::Rectangle aRectangle;
+ const GlyphItem* pGlyph;
+ const LogicalFontInstance* pGlyphFont;
+ int nStart = 0;
+ while (rSalLayout.GetNextGlyph(&pGlyph, aOutPoint, nStart, &pGlyphFont))
+ {
+ if (!pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle))
+ continue;
+
+ if (!pGlyph->IsSpacing())
+ {
+ Point aAdjPoint = aOffset;
+ aAdjPoint.AdjustX(aRectangle.Left() + (aRectangle.GetWidth() - nEmphasisWidth) / 2 );
+ if ( mpFontInstance->mnOrientation )
+ {
+ Point aOriginPt(0, 0);
+ aOriginPt.RotateAround( aAdjPoint, mpFontInstance->mnOrientation );
+ }
+ aOutPoint.adjustX(aAdjPoint.X() - nEmphasisWidth2);
+ aOutPoint.adjustY(aAdjPoint.Y() - nEmphasisHeight2);
+ ImplDrawEmphasisMark( rSalLayout.DrawBase().getX(),
+ aOutPoint.getX(), aOutPoint.getY(),
+ aPolyPoly, bPolyLine, aRect1, aRect2 );
+ }
+ }
+
+ SetLineColor( aOldLineColor );
+ SetFillColor( aOldFillColor );
+ EnableMapMode( bOldMap );
+ mpMetaFile = pOldMetaFile;
+}
+
+std::unique_ptr<SalLayout> OutputDevice::getFallbackLayout(
+ LogicalFontInstance* pLogicalFont, int nFallbackLevel,
+ vcl::text::ImplLayoutArgs& rLayoutArgs, const SalLayoutGlyphs* pGlyphs) const
+{
+ // we need a graphics
+ if (!mpGraphics && !AcquireGraphics())
+ return nullptr;
+
+ assert(mpGraphics != nullptr);
+ mpGraphics->SetFont( pLogicalFont, nFallbackLevel );
+
+ rLayoutArgs.ResetPos();
+ std::unique_ptr<GenericSalLayout> pFallback = mpGraphics->GetTextLayout(nFallbackLevel);
+
+ if (!pFallback)
+ return nullptr;
+
+ if (!pFallback->LayoutText(rLayoutArgs, pGlyphs ? pGlyphs->Impl(nFallbackLevel) : nullptr))
+ {
+ // there is no need for a font that couldn't resolve anything
+ return nullptr;
+ }
+
+ return pFallback;
+}
+
+std::unique_ptr<SalLayout> OutputDevice::ImplGlyphFallbackLayout( std::unique_ptr<SalLayout> pSalLayout,
+ vcl::text::ImplLayoutArgs& rLayoutArgs, const SalLayoutGlyphs* pGlyphs ) const
+{
+ // This function relies on a valid mpFontInstance, if it doesn't exist bail out
+ // - we'd have crashed later on anyway. At least here we can catch the error in debug
+ // mode.
+ if ( !mpFontInstance )
+ {
+ SAL_WARN ("vcl.gdi", "No font entry set in OutputDevice");
+ assert(mpFontInstance);
+ return nullptr;
+ }
+
+ // prepare multi level glyph fallback
+ std::unique_ptr<MultiSalLayout> pMultiSalLayout;
+ ImplLayoutRuns aLayoutRuns = rLayoutArgs.maRuns;
+ rLayoutArgs.PrepareFallback(nullptr);
+ rLayoutArgs.mnFlags |= SalLayoutFlags::ForFallback;
+
+ // get list of code units that need glyph fallback
+ int nCharPos = -1;
+ bool bRTL = false;
+ OUStringBuffer aMissingCodeBuf(512);
+ while (rLayoutArgs.GetNextPos( &nCharPos, &bRTL))
+ aMissingCodeBuf.append(rLayoutArgs.mrStr[nCharPos]);
+ rLayoutArgs.ResetPos();
+ OUString aMissingCodes = aMissingCodeBuf.makeStringAndClear();
+
+ vcl::font::FontSelectPattern aFontSelData(mpFontInstance->GetFontSelectPattern());
+ SalLayoutGlyphsImpl* pGlyphsImpl = pGlyphs ? pGlyphs->Impl(1) : nullptr;
+
+ // try if fallback fonts support the missing code units
+ for( int nFallbackLevel = 1; nFallbackLevel < MAX_FALLBACK; ++nFallbackLevel )
+ {
+ rtl::Reference<LogicalFontInstance> pFallbackFont;
+ if(pGlyphsImpl != nullptr)
+ pFallbackFont = pGlyphsImpl->GetFont();
+ // find a font family suited for glyph fallback
+ // GetGlyphFallbackFont() needs a valid FontInstance
+ // if the system-specific glyph fallback is active
+ OUString oldMissingCodes = aMissingCodes;
+ if( !pFallbackFont )
+ pFallbackFont = mxFontCache->GetGlyphFallbackFont( mxFontCollection.get(),
+ aFontSelData, mpFontInstance.get(), nFallbackLevel, aMissingCodes );
+ if( !pFallbackFont )
+ break;
+
+ if( nFallbackLevel < MAX_FALLBACK-1)
+ {
+ // ignore fallback font if it is the same as the original font
+ // TODO: This seems broken. Either the font does not provide any of the missing
+ // codes, in which case the fallback should not select it. Or it does provide
+ // some of the missing codes, and then why weren't they used the first time?
+ // This will just loop repeatedly finding the same font (it used to remove
+ // the found font from mxFontCache, but doesn't do that anymore and I don't
+ // see how doing that would remove the font from consideration for fallback).
+ if( mpFontInstance->GetFontFace() == pFallbackFont->GetFontFace())
+ {
+ if(aMissingCodes != oldMissingCodes)
+ {
+ SAL_WARN("vcl.gdi", "Font fallback to the same font, but has missing codes");
+ // Restore the missing codes if we're not going to use this font.
+ aMissingCodes = oldMissingCodes;
+ }
+ continue;
+ }
+ }
+
+ // create and add glyph fallback layout to multilayout
+ std::unique_ptr<SalLayout> pFallback = getFallbackLayout(pFallbackFont.get(),
+ nFallbackLevel, rLayoutArgs, pGlyphs);
+ if (pFallback)
+ {
+ if( !pMultiSalLayout )
+ pMultiSalLayout.reset( new MultiSalLayout( std::move(pSalLayout) ) );
+ pMultiSalLayout->AddFallback(std::move(pFallback), rLayoutArgs.maRuns);
+ if (nFallbackLevel == MAX_FALLBACK-1)
+ pMultiSalLayout->SetIncomplete(true);
+ }
+
+ if (pGlyphs != nullptr)
+ pGlyphsImpl = pGlyphs->Impl(nFallbackLevel + 1);
+
+ // break when this fallback was sufficient
+ if( !rLayoutArgs.PrepareFallback(pGlyphsImpl) )
+ break;
+ }
+
+ if (pMultiSalLayout) // due to missing glyphs, multilevel layout fallback attempted
+ {
+ // if it works, use that Layout
+ if (pMultiSalLayout->LayoutText(rLayoutArgs, nullptr))
+ pSalLayout = std::move(pMultiSalLayout);
+ else
+ {
+ // if it doesn't, give up and restore ownership of the pSalLayout
+ // back to its original state
+ pSalLayout = pMultiSalLayout->ReleaseBaseLayout();
+ }
+ }
+
+ // restore orig font settings
+ pSalLayout->InitFont();
+ rLayoutArgs.maRuns = aLayoutRuns;
+
+ return pSalLayout;
+}
+
+tools::Long OutputDevice::GetMinKashida() const
+{
+ if (!ImplNewFont())
+ return 0;
+
+ return ImplDevicePixelToLogicWidth( mpFontInstance->mxFontMetric->GetMinKashida() );
+}
+
+sal_Int32 OutputDevice::ValidateKashidas ( const OUString& rTxt,
+ sal_Int32 nIdx, sal_Int32 nLen,
+ sal_Int32 nKashCount,
+ const sal_Int32* pKashidaPos,
+ sal_Int32* pKashidaPosDropped ) const
+{
+ // do layout
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rTxt, nIdx, nLen );
+ if( !pSalLayout )
+ return 0;
+ sal_Int32 nDropped = 0;
+ for( int i = 0; i < nKashCount; ++i )
+ {
+ if( !pSalLayout->IsKashidaPosValid( pKashidaPos[ i ] ))
+ {
+ pKashidaPosDropped[ nDropped ] = pKashidaPos [ i ];
+ ++nDropped;
+ }
+ }
+ return nDropped;
+}
+
+bool OutputDevice::GetGlyphBoundRects( const Point& rOrigin, const OUString& rStr,
+ int nIndex, int nLen, std::vector< tools::Rectangle >& rVector ) const
+{
+ rVector.clear();
+
+ if( nIndex >= rStr.getLength() )
+ return false;
+
+ if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ tools::Rectangle aRect;
+ for( int i = 0; i < nLen; i++ )
+ {
+ if( !GetTextBoundRect( aRect, rStr, nIndex, nIndex + i, 1 ) )
+ break;
+ aRect.Move( rOrigin.X(), rOrigin.Y() );
+ rVector.push_back( aRect );
+ }
+
+ return (nLen == static_cast<int>(rVector.size()));
+}
+
+sal_Int32 OutputDevice::HasGlyphs( const vcl::Font& rTempFont, const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen ) const
+{
+ if( nIndex >= rStr.getLength() )
+ return nIndex;
+ sal_Int32 nEnd;
+ if( nLen == -1 )
+ nEnd = rStr.getLength();
+ else
+ nEnd = std::min( rStr.getLength(), nIndex + nLen );
+
+ SAL_WARN_IF( nIndex >= nEnd, "vcl.gdi", "StartPos >= EndPos?" );
+ SAL_WARN_IF( nEnd > rStr.getLength(), "vcl.gdi", "String too short" );
+
+ // to get the map temporarily set font
+ const vcl::Font aOrigFont = GetFont();
+ const_cast<OutputDevice&>(*this).SetFont( rTempFont );
+ FontCharMapRef xFontCharMap;
+ bool bRet = GetFontCharMap( xFontCharMap );
+ const_cast<OutputDevice&>(*this).SetFont( aOrigFont );
+
+ // if fontmap is unknown assume it doesn't have the glyphs
+ if( !bRet )
+ return nIndex;
+
+ for( sal_Int32 i = nIndex; nIndex < nEnd; ++i, ++nIndex )
+ if( ! xFontCharMap->HasChar( rStr[i] ) )
+ return nIndex;
+
+ return -1;
+}
+
+void OutputDevice::ReleaseFontCache() { mxFontCache.reset(); }
+
+void OutputDevice::ReleaseFontCollection() { mxFontCollection.reset(); }
+
+void OutputDevice::SetFontCollectionFromSVData()
+{
+ mxFontCollection = ImplGetSVData()->maGDIData.mxScreenFontList->Clone();
+}
+
+void OutputDevice::ResetNewFontCache()
+{
+ mxFontCache = std::make_shared<ImplFontCache>();
+}
+
+void OutputDevice::ImplReleaseFonts()
+{
+ mpGraphics->ReleaseFonts();
+
+ mbNewFont = true;
+ mbInitFont = true;
+
+ mpFontInstance.clear();
+ mpFontFaceCollection.reset();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/outdev/gradient.cxx b/vcl/source/outdev/gradient.cxx
new file mode 100644
index 000000000..eb2fd4645
--- /dev/null
+++ b/vcl/source/outdev/gradient.cxx
@@ -0,0 +1,631 @@
+/* -*- 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 <memory>
+#include <cassert>
+
+#include <tools/poly.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+
+#include <salgdi.hxx>
+
+#define GRADIENT_DEFAULT_STEPCOUNT 0
+
+void OutputDevice::DrawGradient( const tools::Rectangle& rRect,
+ const Gradient& rGradient )
+{
+ assert(!is_double_buffered_window());
+
+ // Convert rectangle to a tools::PolyPolygon by first converting to a Polygon
+ tools::Polygon aPolygon ( rRect );
+ tools::PolyPolygon aPolyPoly ( aPolygon );
+
+ DrawGradient ( aPolyPoly, rGradient );
+}
+
+void OutputDevice::DrawGradient( const tools::PolyPolygon& rPolyPoly,
+ const Gradient& rGradient )
+{
+ assert(!is_double_buffered_window());
+
+ if (mbInitClipRegion)
+ InitClipRegion();
+ // don't return on mbOutputClipped here, as we may need to draw the clipped metafile, even if the output is clipped
+
+ if ( rPolyPoly.Count() && rPolyPoly[ 0 ].GetSize() )
+ {
+ if ( mnDrawMode & ( DrawModeFlags::BlackGradient | DrawModeFlags::WhiteGradient | DrawModeFlags::SettingsGradient) )
+ {
+ Color aColor = GetSingleColorGradientFill();
+
+ Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
+ SetLineColor( aColor );
+ SetFillColor( aColor );
+ DrawPolyPolygon( rPolyPoly );
+ Pop();
+ return;
+ }
+
+ Gradient aGradient( rGradient );
+
+ if ( mnDrawMode & DrawModeFlags::GrayGradient )
+ aGradient.MakeGrayscale();
+
+ DrawGradientToMetafile( rPolyPoly, rGradient );
+
+ if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ // Clip and then draw the gradient
+ if( !tools::Rectangle( PixelToLogic( Point() ), GetOutputSize() ).IsEmpty() )
+ {
+ const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
+
+ // convert rectangle to pixels
+ tools::Rectangle aRect( ImplLogicToDevicePixel( aBoundRect ) );
+ aRect.Justify();
+
+ // do nothing if the rectangle is empty
+ if ( !aRect.IsEmpty() )
+ {
+ tools::PolyPolygon aClixPolyPoly( ImplLogicToDevicePixel( rPolyPoly ) );
+ bool bDrawn = false;
+
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ // secure clip region
+ Push( vcl::PushFlags::CLIPREGION );
+ IntersectClipRegion( aBoundRect );
+
+ if (mbInitClipRegion)
+ InitClipRegion();
+
+ // try to draw gradient natively
+ if (!mbOutputClipped)
+ bDrawn = mpGraphics->DrawGradient( aClixPolyPoly, aGradient, *this );
+
+ if (!bDrawn && !mbOutputClipped)
+ {
+ // draw gradients without border
+ if( mbLineColor || mbInitLineColor )
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+
+ mbInitFillColor = true;
+
+ // calculate step count if necessary
+ if ( !aGradient.GetSteps() )
+ aGradient.SetSteps( GRADIENT_DEFAULT_STEPCOUNT );
+
+ if ( rPolyPoly.IsRect() )
+ {
+ // because we draw with no border line, we have to expand gradient
+ // rect to avoid missing lines on the right and bottom edge
+ aRect.AdjustLeft( -1 );
+ aRect.AdjustTop( -1 );
+ aRect.AdjustRight( 1 );
+ aRect.AdjustBottom( 1 );
+ }
+
+ // if the clipping polypolygon is a rectangle, then it's the same size as the bounding of the
+ // polypolygon, so pass in a NULL for the clipping parameter
+ if( aGradient.GetStyle() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial )
+ DrawLinearGradient( aRect, aGradient, aClixPolyPoly.IsRect() ? nullptr : &aClixPolyPoly );
+ else
+ DrawComplexGradient( aRect, aGradient, aClixPolyPoly.IsRect() ? nullptr : &aClixPolyPoly );
+ }
+
+ Pop();
+ }
+ }
+ }
+
+ if( mpAlphaVDev )
+ {
+ const Color aFillCol( mpAlphaVDev->GetFillColor() );
+ mpAlphaVDev->SetFillColor( COL_BLACK );
+ mpAlphaVDev->DrawPolyPolygon( rPolyPoly );
+ mpAlphaVDev->SetFillColor( aFillCol );
+ }
+}
+
+void OutputDevice::ClipAndDrawGradientMetafile ( const Gradient &rGradient, const tools::PolyPolygon &rPolyPoly )
+{
+ const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
+ const bool bOldOutput = IsOutputEnabled();
+
+ EnableOutput( false );
+ Push( vcl::PushFlags::RASTEROP );
+ SetRasterOp( RasterOp::Xor );
+ DrawGradient( aBoundRect, rGradient );
+ SetFillColor( COL_BLACK );
+ SetRasterOp( RasterOp::N0 );
+ DrawPolyPolygon( rPolyPoly );
+ SetRasterOp( RasterOp::Xor );
+ DrawGradient( aBoundRect, rGradient );
+ Pop();
+ EnableOutput( bOldOutput );
+}
+
+void OutputDevice::DrawGradientToMetafile ( const tools::PolyPolygon& rPolyPoly,
+ const Gradient& rGradient )
+{
+ assert(!is_double_buffered_window());
+
+ if ( !mpMetaFile )
+ return;
+
+ if ( !(rPolyPoly.Count() && rPolyPoly[ 0 ].GetSize()) )
+ return;
+
+ const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
+
+ if (aBoundRect.IsEmpty())
+ return;
+
+ Gradient aGradient( rGradient );
+
+ if (mnDrawMode & DrawModeFlags::GrayGradient)
+ aGradient.MakeGrayscale();
+
+ if ( rPolyPoly.IsRect() )
+ {
+ mpMetaFile->AddAction( new MetaGradientAction( aBoundRect, aGradient ) );
+ }
+ else
+ {
+ mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_BEGIN" ) );
+ mpMetaFile->AddAction( new MetaGradientExAction( rPolyPoly, rGradient ) );
+
+ ClipAndDrawGradientMetafile ( rGradient, rPolyPoly );
+
+ mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_END" ) );
+ }
+}
+
+namespace
+{
+ sal_uInt8 GetGradientColorValue( tools::Long nValue )
+ {
+ if ( nValue < 0 )
+ return 0;
+ else if ( nValue > 0xFF )
+ return 0xFF;
+ else
+ return static_cast<sal_uInt8>(nValue);
+ }
+}
+
+void OutputDevice::DrawLinearGradient( const tools::Rectangle& rRect,
+ const Gradient& rGradient,
+ const tools::PolyPolygon* pClixPolyPoly )
+{
+ assert(!is_double_buffered_window());
+
+ // get BoundRect of rotated rectangle
+ tools::Rectangle aRect;
+ Point aCenter;
+ Degree10 nAngle = rGradient.GetAngle() % 3600_deg10;
+
+ rGradient.GetBoundRect( rRect, aRect, aCenter );
+
+ bool bLinear = (rGradient.GetStyle() == GradientStyle::Linear);
+ double fBorder = rGradient.GetBorder() * aRect.GetHeight() / 100.0;
+ if ( !bLinear )
+ {
+ fBorder /= 2.0;
+ }
+ tools::Rectangle aMirrorRect = aRect; // used in style axial
+ aMirrorRect.SetTop( ( aRect.Top() + aRect.Bottom() ) / 2 );
+ if ( !bLinear )
+ {
+ aRect.SetBottom( aMirrorRect.Top() );
+ }
+
+ // colour-intensities of start- and finish; change if needed
+ tools::Long nFactor;
+ Color aStartCol = rGradient.GetStartColor();
+ Color aEndCol = rGradient.GetEndColor();
+ tools::Long nStartRed = aStartCol.GetRed();
+ tools::Long nStartGreen = aStartCol.GetGreen();
+ tools::Long nStartBlue = aStartCol.GetBlue();
+ tools::Long nEndRed = aEndCol.GetRed();
+ tools::Long nEndGreen = aEndCol.GetGreen();
+ tools::Long nEndBlue = aEndCol.GetBlue();
+ nFactor = rGradient.GetStartIntensity();
+ nStartRed = (nStartRed * nFactor) / 100;
+ nStartGreen = (nStartGreen * nFactor) / 100;
+ nStartBlue = (nStartBlue * nFactor) / 100;
+ nFactor = rGradient.GetEndIntensity();
+ nEndRed = (nEndRed * nFactor) / 100;
+ nEndGreen = (nEndGreen * nFactor) / 100;
+ nEndBlue = (nEndBlue * nFactor) / 100;
+
+ // gradient style axial has exchanged start and end colors
+ if ( !bLinear)
+ {
+ tools::Long nTempColor = nStartRed;
+ nStartRed = nEndRed;
+ nEndRed = nTempColor;
+ nTempColor = nStartGreen;
+ nStartGreen = nEndGreen;
+ nEndGreen = nTempColor;
+ nTempColor = nStartBlue;
+ nStartBlue = nEndBlue;
+ nEndBlue = nTempColor;
+ }
+
+ sal_uInt8 nRed;
+ sal_uInt8 nGreen;
+ sal_uInt8 nBlue;
+
+ // Create border
+ tools::Rectangle aBorderRect = aRect;
+ tools::Polygon aPoly( 4 );
+ if (fBorder > 0.0)
+ {
+ nRed = static_cast<sal_uInt8>(nStartRed);
+ nGreen = static_cast<sal_uInt8>(nStartGreen);
+ nBlue = static_cast<sal_uInt8>(nStartBlue);
+
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+
+ aBorderRect.SetBottom( static_cast<tools::Long>( aBorderRect.Top() + fBorder ) );
+ aRect.SetTop( aBorderRect.Bottom() );
+ aPoly[0] = aBorderRect.TopLeft();
+ aPoly[1] = aBorderRect.TopRight();
+ aPoly[2] = aBorderRect.BottomRight();
+ aPoly[3] = aBorderRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+
+ if ( !bLinear)
+ {
+ aBorderRect = aMirrorRect;
+ aBorderRect.SetTop( static_cast<tools::Long>( aBorderRect.Bottom() - fBorder ) );
+ aMirrorRect.SetBottom( aBorderRect.Top() );
+ aPoly[0] = aBorderRect.TopLeft();
+ aPoly[1] = aBorderRect.TopRight();
+ aPoly[2] = aBorderRect.BottomRight();
+ aPoly[3] = aBorderRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+ }
+ }
+
+ // calculate step count
+ tools::Long nStepCount = GetGradientSteps(rGradient, aRect);
+
+ // minimal three steps and maximal as max color steps
+ tools::Long nAbsRedSteps = std::abs( nEndRed - nStartRed );
+ tools::Long nAbsGreenSteps = std::abs( nEndGreen - nStartGreen );
+ tools::Long nAbsBlueSteps = std::abs( nEndBlue - nStartBlue );
+ tools::Long nMaxColorSteps = std::max( nAbsRedSteps , nAbsGreenSteps );
+ nMaxColorSteps = std::max( nMaxColorSteps, nAbsBlueSteps );
+ tools::Long nSteps = std::min( nStepCount, nMaxColorSteps );
+ if ( nSteps < 3)
+ {
+ nSteps = 3;
+ }
+
+ double fScanInc = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps);
+ double fGradientLine = static_cast<double>(aRect.Top());
+ double fMirrorGradientLine = static_cast<double>(aMirrorRect.Bottom());
+
+ const double fStepsMinus1 = static_cast<double>(nSteps) - 1.0;
+ if ( !bLinear)
+ {
+ nSteps -= 1; // draw middle polygons as one polygon after loop to avoid gap
+ }
+
+ for ( tools::Long i = 0; i < nSteps; i++ )
+ {
+ // linear interpolation of color
+ const double fAlpha = static_cast<double>(i) / fStepsMinus1;
+ double fTempColor = static_cast<double>(nStartRed) * (1.0-fAlpha) + static_cast<double>(nEndRed) * fAlpha;
+ nRed = GetGradientColorValue(static_cast<tools::Long>(fTempColor));
+ fTempColor = static_cast<double>(nStartGreen) * (1.0-fAlpha) + static_cast<double>(nEndGreen) * fAlpha;
+ nGreen = GetGradientColorValue(static_cast<tools::Long>(fTempColor));
+ fTempColor = static_cast<double>(nStartBlue) * (1.0-fAlpha) + static_cast<double>(nEndBlue) * fAlpha;
+ nBlue = GetGradientColorValue(static_cast<tools::Long>(fTempColor));
+
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+
+ // Polygon for this color step
+ aRect.SetTop( static_cast<tools::Long>( fGradientLine + static_cast<double>(i) * fScanInc ) );
+ aRect.SetBottom( static_cast<tools::Long>( fGradientLine + ( static_cast<double>(i) + 1.0 ) * fScanInc ) );
+ aPoly[0] = aRect.TopLeft();
+ aPoly[1] = aRect.TopRight();
+ aPoly[2] = aRect.BottomRight();
+ aPoly[3] = aRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+
+ if ( !bLinear )
+ {
+ aMirrorRect.SetBottom( static_cast<tools::Long>( fMirrorGradientLine - static_cast<double>(i) * fScanInc ) );
+ aMirrorRect.SetTop( static_cast<tools::Long>( fMirrorGradientLine - (static_cast<double>(i) + 1.0)* fScanInc ) );
+ aPoly[0] = aMirrorRect.TopLeft();
+ aPoly[1] = aMirrorRect.TopRight();
+ aPoly[2] = aMirrorRect.BottomRight();
+ aPoly[3] = aMirrorRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+ }
+ }
+ if ( bLinear)
+ return;
+
+ // draw middle polygon with end color
+ nRed = GetGradientColorValue(nEndRed);
+ nGreen = GetGradientColorValue(nEndGreen);
+ nBlue = GetGradientColorValue(nEndBlue);
+
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+
+ aRect.SetTop( static_cast<tools::Long>( fGradientLine + static_cast<double>(nSteps) * fScanInc ) );
+ aRect.SetBottom( static_cast<tools::Long>( fMirrorGradientLine - static_cast<double>(nSteps) * fScanInc ) );
+ aPoly[0] = aRect.TopLeft();
+ aPoly[1] = aRect.TopRight();
+ aPoly[2] = aRect.BottomRight();
+ aPoly[3] = aRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+
+}
+
+bool OutputDevice::is_double_buffered_window() const
+{
+ auto pOwnerWindow = GetOwnerWindow();
+ return pOwnerWindow && pOwnerWindow->SupportsDoubleBuffering();
+}
+
+void OutputDevice::DrawComplexGradient( const tools::Rectangle& rRect,
+ const Gradient& rGradient,
+ const tools::PolyPolygon* pClixPolyPoly )
+{
+ assert(!is_double_buffered_window());
+
+ // Determine if we output via Polygon or PolyPolygon
+ // For all rasteroperations other than Overpaint always use PolyPolygon,
+ // as we will get wrong results if we output multiple times on top of each other.
+ // Also for printers always use PolyPolygon, as not all printers
+ // can print polygons on top of each other.
+
+ std::optional<tools::PolyPolygon> xPolyPoly;
+ tools::Rectangle aRect;
+ Point aCenter;
+ Color aStartCol( rGradient.GetStartColor() );
+ Color aEndCol( rGradient.GetEndColor() );
+ tools::Long nStartRed = ( static_cast<tools::Long>(aStartCol.GetRed()) * rGradient.GetStartIntensity() ) / 100;
+ tools::Long nStartGreen = ( static_cast<tools::Long>(aStartCol.GetGreen()) * rGradient.GetStartIntensity() ) / 100;
+ tools::Long nStartBlue = ( static_cast<tools::Long>(aStartCol.GetBlue()) * rGradient.GetStartIntensity() ) / 100;
+ tools::Long nEndRed = ( static_cast<tools::Long>(aEndCol.GetRed()) * rGradient.GetEndIntensity() ) / 100;
+ tools::Long nEndGreen = ( static_cast<tools::Long>(aEndCol.GetGreen()) * rGradient.GetEndIntensity() ) / 100;
+ tools::Long nEndBlue = ( static_cast<tools::Long>(aEndCol.GetBlue()) * rGradient.GetEndIntensity() ) / 100;
+ tools::Long nRedSteps = nEndRed - nStartRed;
+ tools::Long nGreenSteps = nEndGreen - nStartGreen;
+ tools::Long nBlueSteps = nEndBlue - nStartBlue;
+ Degree10 nAngle = rGradient.GetAngle() % 3600_deg10;
+
+ rGradient.GetBoundRect( rRect, aRect, aCenter );
+
+ if ( UsePolyPolygonForComplexGradient() )
+ xPolyPoly = tools::PolyPolygon( 2 );
+
+ tools::Long nStepCount = GetGradientSteps(rGradient, rRect);
+
+ // at least three steps and at most the number of colour differences
+ tools::Long nSteps = std::max( nStepCount, tools::Long(2) );
+ tools::Long nCalcSteps = std::abs( nRedSteps );
+ tools::Long nTempSteps = std::abs( nGreenSteps );
+ if ( nTempSteps > nCalcSteps )
+ nCalcSteps = nTempSteps;
+ nTempSteps = std::abs( nBlueSteps );
+ if ( nTempSteps > nCalcSteps )
+ nCalcSteps = nTempSteps;
+ if ( nCalcSteps < nSteps )
+ nSteps = nCalcSteps;
+ if ( !nSteps )
+ nSteps = 1;
+
+ // determine output limits and stepsizes for all directions
+ tools::Polygon aPoly;
+ double fScanLeft = aRect.Left();
+ double fScanTop = aRect.Top();
+ double fScanRight = aRect.Right();
+ double fScanBottom = aRect.Bottom();
+ double fScanIncX = static_cast<double>(aRect.GetWidth()) / static_cast<double>(nSteps) * 0.5;
+ double fScanIncY = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps) * 0.5;
+
+ // all gradients are rendered as nested rectangles which shrink
+ // equally in each dimension - except for 'square' gradients
+ // which shrink to a central vertex but are not per-se square.
+ if( rGradient.GetStyle() != GradientStyle::Square )
+ {
+ fScanIncY = std::min( fScanIncY, fScanIncX );
+ fScanIncX = fScanIncY;
+ }
+ sal_uInt8 nRed = static_cast<sal_uInt8>(nStartRed), nGreen = static_cast<sal_uInt8>(nStartGreen), nBlue = static_cast<sal_uInt8>(nStartBlue);
+ bool bPaintLastPolygon( false ); // #107349# Paint last polygon only if loop has generated any output
+
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+
+ if( xPolyPoly )
+ {
+ aPoly = rRect;
+ xPolyPoly->Insert( aPoly );
+ xPolyPoly->Insert( aPoly );
+ }
+ else
+ {
+ // extend rect, to avoid missing bounding line
+ tools::Rectangle aExtRect( rRect );
+
+ aExtRect.AdjustLeft( -1 );
+ aExtRect.AdjustTop( -1 );
+ aExtRect.AdjustRight(1 );
+ aExtRect.AdjustBottom(1 );
+
+ aPoly = aExtRect;
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+ }
+
+ // loop to output Polygon/PolyPolygon sequentially
+ for( tools::Long i = 1; i < nSteps; i++ )
+ {
+ // calculate new Polygon
+ fScanLeft += fScanIncX;
+ aRect.SetLeft( static_cast<tools::Long>( fScanLeft ) );
+ fScanTop += fScanIncY;
+ aRect.SetTop( static_cast<tools::Long>( fScanTop ) );
+ fScanRight -= fScanIncX;
+ aRect.SetRight( static_cast<tools::Long>( fScanRight ) );
+ fScanBottom -= fScanIncY;
+ aRect.SetBottom( static_cast<tools::Long>( fScanBottom ) );
+
+ if( ( aRect.GetWidth() < 2 ) || ( aRect.GetHeight() < 2 ) )
+ break;
+
+ if( rGradient.GetStyle() == GradientStyle::Radial || rGradient.GetStyle() == GradientStyle::Elliptical )
+ aPoly = tools::Polygon( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 );
+ else
+ aPoly = tools::Polygon( aRect );
+
+ aPoly.Rotate( aCenter, nAngle );
+
+ // adapt colour accordingly
+ const tools::Long nStepIndex = ( xPolyPoly ? i : ( i + 1 ) );
+ nRed = GetGradientColorValue( nStartRed + ( ( nRedSteps * nStepIndex ) / nSteps ) );
+ nGreen = GetGradientColorValue( nStartGreen + ( ( nGreenSteps * nStepIndex ) / nSteps ) );
+ nBlue = GetGradientColorValue( nStartBlue + ( ( nBlueSteps * nStepIndex ) / nSteps ) );
+
+ // either slow tools::PolyPolygon output or fast Polygon-Painting
+ if( xPolyPoly )
+ {
+ bPaintLastPolygon = true; // #107349# Paint last polygon only if loop has generated any output
+
+ xPolyPoly->Replace( xPolyPoly->GetObject( 1 ), 0 );
+ xPolyPoly->Replace( aPoly, 1 );
+
+ ImplDrawPolyPolygon( *xPolyPoly, pClixPolyPoly );
+
+ // #107349# Set fill color _after_ geometry painting:
+ // xPolyPoly's geometry is the band from last iteration's
+ // aPoly to current iteration's aPoly. The window outdev
+ // path (see else below), on the other hand, paints the
+ // full aPoly. Thus, here, we're painting the band before
+ // the one painted in the window outdev path below. To get
+ // matching colors, have to delay color setting here.
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+ }
+ else
+ {
+ // #107349# Set fill color _before_ geometry painting
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+ }
+ }
+
+ // we should draw last inner Polygon if we output PolyPolygon
+ if( !xPolyPoly )
+ return;
+
+ const tools::Polygon& rPoly = xPolyPoly->GetObject( 1 );
+
+ if( rPoly.GetBoundRect().IsEmpty() )
+ return;
+
+ // #107349# Paint last polygon with end color only if loop
+ // has generated output. Otherwise, the current
+ // (i.e. start) color is taken, to generate _any_ output.
+ if( bPaintLastPolygon )
+ {
+ nRed = GetGradientColorValue( nEndRed );
+ nGreen = GetGradientColorValue( nEndGreen );
+ nBlue = GetGradientColorValue( nEndBlue );
+ }
+
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+ ImplDrawPolygon( rPoly, pClixPolyPoly );
+}
+
+tools::Long OutputDevice::GetGradientStepCount( tools::Long nMinRect )
+{
+ tools::Long nInc = (nMinRect < 50) ? 2 : 4;
+
+ return nInc;
+}
+
+tools::Long OutputDevice::GetGradientSteps(Gradient const& rGradient, tools::Rectangle const& rRect)
+{
+ // calculate step count
+ tools::Long nStepCount = rGradient.GetSteps();
+
+ if (nStepCount)
+ return nStepCount;
+
+ tools::Long nMinRect = 0;
+
+ if (rGradient.GetStyle() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial)
+ nMinRect = rRect.GetHeight();
+ else
+ nMinRect = std::min(rRect.GetWidth(), rRect.GetHeight());
+
+ tools::Long nInc = GetGradientStepCount(nMinRect);
+
+ if (!nInc)
+ nInc = 1;
+
+ return nMinRect / nInc;
+}
+
+Color OutputDevice::GetSingleColorGradientFill()
+{
+ Color aColor;
+
+ // we should never call on this function if any of these aren't set!
+ assert( mnDrawMode & ( DrawModeFlags::BlackGradient | DrawModeFlags::WhiteGradient | DrawModeFlags::SettingsGradient) );
+
+ if ( mnDrawMode & DrawModeFlags::BlackGradient )
+ aColor = COL_BLACK;
+ else if ( mnDrawMode & DrawModeFlags::WhiteGradient )
+ aColor = COL_WHITE;
+ else if ( mnDrawMode & DrawModeFlags::SettingsGradient )
+ aColor = GetSettings().GetStyleSettings().GetWindowColor();
+
+ return aColor;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/hatch.cxx b/vcl/source/outdev/hatch.cxx
new file mode 100644
index 000000000..9ae44a563
--- /dev/null
+++ b/vcl/source/outdev/hatch.cxx
@@ -0,0 +1,442 @@
+/* -*- 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 <cassert>
+#include <cstdlib>
+
+#include <osl/diagnose.h>
+#include <tools/line.hxx>
+#include <tools/helpers.hxx>
+
+#include <unotools/configmgr.hxx>
+
+#include <vcl/hatch.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+
+#include <memory>
+
+#define HATCH_MAXPOINTS 1024
+
+extern "C" {
+
+static int HatchCmpFnc( const void* p1, const void* p2 )
+{
+ const tools::Long nX1 = static_cast<Point const *>(p1)->X();
+ const tools::Long nX2 = static_cast<Point const *>(p2)->X();
+ const tools::Long nY1 = static_cast<Point const *>(p1)->Y();
+ const tools::Long nY2 = static_cast<Point const *>(p2)->Y();
+
+ return ( nX1 > nX2 ? 1 : nX1 == nX2 ? nY1 > nY2 ? 1: nY1 == nY2 ? 0 : -1 : -1 );
+}
+
+}
+
+void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
+{
+ assert(!is_double_buffered_window());
+
+ Hatch aHatch( rHatch );
+ aHatch.SetColor(vcl::drawmode::GetHatchColor(rHatch.GetColor(), GetDrawMode(), GetSettings().GetStyleSettings()));
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaHatchAction( rPolyPoly, aHatch ) );
+
+ if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ if( rPolyPoly.Count() )
+ {
+ tools::PolyPolygon aPolyPoly( LogicToPixel( rPolyPoly ) );
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ bool bOldMap = mbMap;
+
+ aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
+ aHatch.SetDistance( ImplLogicWidthToDevicePixel( aHatch.GetDistance() ) );
+
+ mpMetaFile = nullptr;
+ EnableMapMode( false );
+ Push( vcl::PushFlags::LINECOLOR );
+ SetLineColor( aHatch.GetColor() );
+ InitLineColor();
+ DrawHatch( aPolyPoly, aHatch, false );
+ Pop();
+ EnableMapMode( bOldMap );
+ mpMetaFile = pOldMetaFile;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawHatch( rPolyPoly, rHatch );
+}
+
+void OutputDevice::AddHatchActions( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch,
+ GDIMetaFile& rMtf )
+{
+
+ tools::PolyPolygon aPolyPoly( rPolyPoly );
+ aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME | PolyOptimizeFlags::CLOSE );
+
+ if( aPolyPoly.Count() )
+ {
+ GDIMetaFile* pOldMtf = mpMetaFile;
+
+ mpMetaFile = &rMtf;
+ mpMetaFile->AddAction( new MetaPushAction( vcl::PushFlags::ALL ) );
+ mpMetaFile->AddAction( new MetaLineColorAction( rHatch.GetColor(), true ) );
+ DrawHatch( aPolyPoly, rHatch, true );
+ mpMetaFile->AddAction( new MetaPopAction() );
+ mpMetaFile = pOldMtf;
+ }
+}
+
+static bool HasSaneNSteps(const Point& rPt1, const Point& rEndPt1, const Size& rInc)
+{
+ tools::Long nVertSteps = -1;
+ if (rInc.Height())
+ {
+ bool bFail = o3tl::checked_sub(rEndPt1.Y(), rPt1.Y(), nVertSteps);
+ if (bFail)
+ nVertSteps = std::numeric_limits<tools::Long>::max();
+ else
+ nVertSteps = nVertSteps / rInc.Height();
+ }
+ tools::Long nHorzSteps = -1;
+ if (rInc.Width())
+ {
+ bool bFail = o3tl::checked_sub(rEndPt1.X(), rPt1.X(), nHorzSteps);
+ if (bFail)
+ nHorzSteps = std::numeric_limits<tools::Long>::max();
+ else
+ nHorzSteps = nHorzSteps / rInc.Width();
+ }
+ auto nSteps = std::max(nVertSteps, nHorzSteps);
+ if (nSteps > 1024)
+ {
+ SAL_WARN("vcl.gdi", "skipping slow hatch with " << nSteps << " steps");
+ return false;
+ }
+ return true;
+}
+
+void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch, bool bMtf )
+{
+ assert(!is_double_buffered_window());
+
+ if(!rPolyPoly.Count())
+ return;
+
+ // #i115630# DrawHatch does not work with beziers included in the polypolygon, take care of that
+ bool bIsCurve(false);
+
+ for(sal_uInt16 a(0); !bIsCurve && a < rPolyPoly.Count(); a++)
+ {
+ if(rPolyPoly[a].HasFlags())
+ {
+ bIsCurve = true;
+ }
+ }
+
+ if(bIsCurve)
+ {
+ OSL_ENSURE(false, "DrawHatch does *not* support curves, falling back to AdaptiveSubdivide()...");
+ tools::PolyPolygon aPolyPoly;
+
+ rPolyPoly.AdaptiveSubdivide(aPolyPoly);
+ DrawHatch(aPolyPoly, rHatch, bMtf);
+ }
+ else
+ {
+ tools::Rectangle aRect( rPolyPoly.GetBoundRect() );
+ const tools::Long nLogPixelWidth = ImplDevicePixelToLogicWidth( 1 );
+ const tools::Long nWidth = ImplDevicePixelToLogicWidth( std::max( ImplLogicWidthToDevicePixel( rHatch.GetDistance() ), tools::Long(3) ) );
+ std::unique_ptr<Point[]> pPtBuffer(new Point[ HATCH_MAXPOINTS ]);
+ Point aPt1, aPt2, aEndPt1;
+ Size aInc;
+
+ // Single hatch
+ aRect.AdjustLeft( -nLogPixelWidth ); aRect.AdjustTop( -nLogPixelWidth ); aRect.AdjustRight(nLogPixelWidth ); aRect.AdjustBottom(nLogPixelWidth );
+ CalcHatchValues( aRect, nWidth, rHatch.GetAngle(), aPt1, aPt2, aInc, aEndPt1 );
+ if (utl::ConfigManager::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc))
+ return;
+
+ if (aInc.Width() <= 0 && aInc.Height() <= 0)
+ SAL_WARN("vcl.gdi", "invalid increment");
+ else
+ {
+ do
+ {
+ DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf );
+ aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() );
+ aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() );
+ }
+ while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) );
+ }
+
+ if( ( rHatch.GetStyle() == HatchStyle::Double ) || ( rHatch.GetStyle() == HatchStyle::Triple ) )
+ {
+ // Double hatch
+ CalcHatchValues( aRect, nWidth, rHatch.GetAngle() + 900_deg10, aPt1, aPt2, aInc, aEndPt1 );
+ if (utl::ConfigManager::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc))
+ return;
+
+ do
+ {
+ DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf );
+ aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() );
+ aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() );
+ }
+ while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) );
+
+ if( rHatch.GetStyle() == HatchStyle::Triple )
+ {
+ // Triple hatch
+ CalcHatchValues( aRect, nWidth, rHatch.GetAngle() + 450_deg10, aPt1, aPt2, aInc, aEndPt1 );
+ if (utl::ConfigManager::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc))
+ return;
+
+ do
+ {
+ DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf );
+ aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() );
+ aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() );
+ }
+ while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) );
+ }
+ }
+ }
+}
+
+void OutputDevice::CalcHatchValues( const tools::Rectangle& rRect, tools::Long nDist, Degree10 nAngle10,
+ Point& rPt1, Point& rPt2, Size& rInc, Point& rEndPt1 )
+{
+ Point aRef;
+ Degree10 nAngle = nAngle10 % 1800_deg10;
+ tools::Long nOffset = 0;
+
+ if( nAngle > 900_deg10 )
+ nAngle -= 1800_deg10;
+
+ aRef = ( !IsRefPoint() ? rRect.TopLeft() : GetRefPoint() );
+
+ if( 0_deg10 == nAngle )
+ {
+ rInc = Size( 0, nDist );
+ rPt1 = rRect.TopLeft();
+ rPt2 = rRect.TopRight();
+ rEndPt1 = rRect.BottomLeft();
+
+ if( aRef.Y() <= rRect.Top() )
+ nOffset = ( ( rRect.Top() - aRef.Y() ) % nDist );
+ else
+ nOffset = ( nDist - ( ( aRef.Y() - rRect.Top() ) % nDist ) );
+
+ rPt1.AdjustY( -nOffset );
+ rPt2.AdjustY( -nOffset );
+ }
+ else if( 900_deg10 == nAngle )
+ {
+ rInc = Size( nDist, 0 );
+ rPt1 = rRect.TopLeft();
+ rPt2 = rRect.BottomLeft();
+ rEndPt1 = rRect.TopRight();
+
+ if( aRef.X() <= rRect.Left() )
+ nOffset = ( rRect.Left() - aRef.X() ) % nDist;
+ else
+ nOffset = nDist - ( ( aRef.X() - rRect.Left() ) % nDist );
+
+ rPt1.AdjustX( -nOffset );
+ rPt2.AdjustX( -nOffset );
+ }
+ else if( nAngle >= Degree10(-450) && nAngle <= 450_deg10 )
+ {
+ const double fAngle = std::abs( toRadians(nAngle) );
+ const double fTan = tan( fAngle );
+ const tools::Long nYOff = FRound( ( rRect.Right() - rRect.Left() ) * fTan );
+ tools::Long nPY;
+
+ nDist = FRound( nDist / cos( fAngle ) );
+ rInc = Size( 0, nDist );
+
+ if( nAngle > 0_deg10 )
+ {
+ rPt1 = rRect.TopLeft();
+ rPt2 = Point( rRect.Right(), rRect.Top() - nYOff );
+ rEndPt1 = Point( rRect.Left(), rRect.Bottom() + nYOff );
+ nPY = FRound( aRef.Y() - ( ( rPt1.X() - aRef.X() ) * fTan ) );
+ }
+ else
+ {
+ rPt1 = rRect.TopRight();
+ rPt2 = Point( rRect.Left(), rRect.Top() - nYOff );
+ rEndPt1 = Point( rRect.Right(), rRect.Bottom() + nYOff );
+ nPY = FRound( aRef.Y() + ( ( rPt1.X() - aRef.X() ) * fTan ) );
+ }
+
+ if( nPY <= rPt1.Y() )
+ nOffset = ( rPt1.Y() - nPY ) % nDist;
+ else
+ nOffset = nDist - ( ( nPY - rPt1.Y() ) % nDist );
+
+ rPt1.AdjustY( -nOffset );
+ rPt2.AdjustY( -nOffset );
+ }
+ else
+ {
+ const double fAngle = std::abs( toRadians(nAngle) );
+ const double fTan = tan( fAngle );
+ const tools::Long nXOff = FRound( ( rRect.Bottom() - rRect.Top() ) / fTan );
+ tools::Long nPX;
+
+ nDist = FRound( nDist / sin( fAngle ) );
+ rInc = Size( nDist, 0 );
+
+ if( nAngle > 0_deg10 )
+ {
+ rPt1 = rRect.TopLeft();
+ rPt2 = Point( rRect.Left() - nXOff, rRect.Bottom() );
+ rEndPt1 = Point( rRect.Right() + nXOff, rRect.Top() );
+ nPX = FRound( aRef.X() - ( ( rPt1.Y() - aRef.Y() ) / fTan ) );
+ }
+ else
+ {
+ rPt1 = rRect.BottomLeft();
+ rPt2 = Point( rRect.Left() - nXOff, rRect.Top() );
+ rEndPt1 = Point( rRect.Right() + nXOff, rRect.Bottom() );
+ nPX = FRound( aRef.X() + ( ( rPt1.Y() - aRef.Y() ) / fTan ) );
+ }
+
+ if( nPX <= rPt1.X() )
+ nOffset = ( rPt1.X() - nPX ) % nDist;
+ else
+ nOffset = nDist - ( ( nPX - rPt1.X() ) % nDist );
+
+ rPt1.AdjustX( -nOffset );
+ rPt2.AdjustX( -nOffset );
+ }
+}
+
+void OutputDevice::DrawHatchLine( const tools::Line& rLine, const tools::PolyPolygon& rPolyPoly,
+ Point* pPtBuffer, bool bMtf )
+{
+ assert(!is_double_buffered_window());
+
+ double fX, fY;
+ tools::Long nAdd, nPCounter = 0;
+
+ for( tools::Long nPoly = 0, nPolyCount = rPolyPoly.Count(); nPoly < nPolyCount; nPoly++ )
+ {
+ const tools::Polygon& rPoly = rPolyPoly[ static_cast<sal_uInt16>(nPoly) ];
+
+ if( rPoly.GetSize() > 1 )
+ {
+ tools::Line aCurSegment( rPoly[ 0 ], Point() );
+
+ for( tools::Long i = 1, nCount = rPoly.GetSize(); i <= nCount; i++ )
+ {
+ aCurSegment.SetEnd( rPoly[ static_cast<sal_uInt16>( i % nCount ) ] );
+ nAdd = 0;
+
+ if( rLine.Intersection( aCurSegment, fX, fY ) )
+ {
+ if( ( fabs( fX - aCurSegment.GetStart().X() ) <= 0.0000001 ) &&
+ ( fabs( fY - aCurSegment.GetStart().Y() ) <= 0.0000001 ) )
+ {
+ const tools::Line aPrevSegment( rPoly[ static_cast<sal_uInt16>( ( i > 1 ) ? ( i - 2 ) : ( nCount - 1 ) ) ], aCurSegment.GetStart() );
+ const double fPrevDistance = rLine.GetDistance( aPrevSegment.GetStart() );
+ const double fCurDistance = rLine.GetDistance( aCurSegment.GetEnd() );
+
+ if( ( fPrevDistance <= 0.0 && fCurDistance > 0.0 ) ||
+ ( fPrevDistance > 0.0 && fCurDistance < 0.0 ) )
+ {
+ nAdd = 1;
+ }
+ }
+ else if( ( fabs( fX - aCurSegment.GetEnd().X() ) <= 0.0000001 ) &&
+ ( fabs( fY - aCurSegment.GetEnd().Y() ) <= 0.0000001 ) )
+ {
+ const tools::Line aNextSegment( aCurSegment.GetEnd(), rPoly[ static_cast<sal_uInt16>( ( i + 1 ) % nCount ) ] );
+
+ if( ( fabs( rLine.GetDistance( aNextSegment.GetEnd() ) ) <= 0.0000001 ) &&
+ ( rLine.GetDistance( aCurSegment.GetStart() ) > 0.0 ) )
+ {
+ nAdd = 1;
+ }
+ }
+ else
+ nAdd = 1;
+
+ if( nAdd )
+ {
+ if (nPCounter == HATCH_MAXPOINTS)
+ {
+ SAL_WARN("vcl.gdi", "too many hatch points");
+ return;
+ }
+ pPtBuffer[ nPCounter++ ] = Point( FRound( fX ), FRound( fY ) );
+ }
+ }
+
+ aCurSegment.SetStart( aCurSegment.GetEnd() );
+ }
+ }
+ }
+
+ if( nPCounter <= 1 )
+ return;
+
+ qsort( pPtBuffer, nPCounter, sizeof( Point ), HatchCmpFnc );
+
+ if( nPCounter & 1 )
+ nPCounter--;
+
+ if( bMtf )
+ {
+ for( tools::Long i = 0; i < nPCounter; i += 2 )
+ mpMetaFile->AddAction( new MetaLineAction( pPtBuffer[ i ], pPtBuffer[ i + 1 ] ) );
+ }
+ else
+ {
+ for( tools::Long i = 0; i < nPCounter; i += 2 )
+ DrawHatchLine_DrawLine(pPtBuffer[i], pPtBuffer[i+1]);
+ }
+}
+
+void OutputDevice::DrawHatchLine_DrawLine(const Point& rStartPoint, const Point& rEndPoint)
+{
+ Point aPt1{ImplLogicToDevicePixel(rStartPoint)}, aPt2{ImplLogicToDevicePixel(rEndPoint)};
+ mpGraphics->DrawLine(aPt1.X(), aPt1.Y(), aPt2.X(), aPt2.Y(), *this);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/line.cxx b/vcl/source/outdev/line.cxx
new file mode 100644
index 000000000..2556a09bf
--- /dev/null
+++ b/vcl/source/outdev/line.cxx
@@ -0,0 +1,358 @@
+/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/lineinfo.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+
+#include <cassert>
+#include <numeric>
+
+void OutputDevice::SetLineColor()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaLineColorAction( Color(), false ) );
+
+ if ( mbLineColor )
+ {
+ mbInitLineColor = true;
+ mbLineColor = false;
+ maLineColor = COL_TRANSPARENT;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetLineColor();
+}
+
+void OutputDevice::SetLineColor( const Color& rColor )
+{
+
+ Color aColor = vcl::drawmode::GetLineColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings());
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaLineColorAction( aColor, true ) );
+
+ if( aColor.IsTransparent() )
+ {
+ if ( mbLineColor )
+ {
+ mbInitLineColor = true;
+ mbLineColor = false;
+ maLineColor = COL_TRANSPARENT;
+ }
+ }
+ else
+ {
+ if( maLineColor != aColor )
+ {
+ mbInitLineColor = true;
+ mbLineColor = true;
+ maLineColor = aColor;
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetLineColor( COL_BLACK );
+}
+
+void OutputDevice::InitLineColor()
+{
+ DBG_TESTSOLARMUTEX();
+
+ if( mbLineColor )
+ {
+ if( RasterOp::N0 == meRasterOp )
+ mpGraphics->SetROPLineColor( SalROPColor::N0 );
+ else if( RasterOp::N1 == meRasterOp )
+ mpGraphics->SetROPLineColor( SalROPColor::N1 );
+ else if( RasterOp::Invert == meRasterOp )
+ mpGraphics->SetROPLineColor( SalROPColor::Invert );
+ else
+ mpGraphics->SetLineColor( maLineColor );
+ }
+ else
+ {
+ mpGraphics->SetLineColor();
+ }
+
+ mbInitLineColor = false;
+}
+
+void OutputDevice::DrawLine( const Point& rStartPt, const Point& rEndPt,
+ const LineInfo& rLineInfo )
+{
+ assert(!is_double_buffered_window());
+
+ if ( rLineInfo.IsDefault() )
+ {
+ DrawLine( rStartPt, rEndPt );
+ return;
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaLineAction( rStartPt, rEndPt, rLineInfo ) );
+
+ if ( !IsDeviceOutputNecessary() || !mbLineColor || ( LineStyle::NONE == rLineInfo.GetStyle() ) || ImplIsRecordLayout() )
+ return;
+
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ const Point aStartPt( ImplLogicToDevicePixel( rStartPt ) );
+ const Point aEndPt( ImplLogicToDevicePixel( rEndPt ) );
+ const LineInfo aInfo( ImplLogicToDevicePixel( rLineInfo ) );
+ const bool bDashUsed(LineStyle::Dash == aInfo.GetStyle());
+ const bool bLineWidthUsed(aInfo.GetWidth() > 1);
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if(bDashUsed || bLineWidthUsed)
+ {
+ basegfx::B2DPolygon aLinePolygon;
+ aLinePolygon.append(basegfx::B2DPoint(aStartPt.X(), aStartPt.Y()));
+ aLinePolygon.append(basegfx::B2DPoint(aEndPt.X(), aEndPt.Y()));
+
+ drawLine( basegfx::B2DPolyPolygon(aLinePolygon), aInfo );
+ }
+ else
+ {
+ mpGraphics->DrawLine( aStartPt.X(), aStartPt.Y(), aEndPt.X(), aEndPt.Y(), *this );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawLine( rStartPt, rEndPt, rLineInfo );
+}
+
+void OutputDevice::DrawLine( const Point& rStartPt, const Point& rEndPt )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaLineAction( rStartPt, rEndPt ) );
+
+ if ( !IsDeviceOutputNecessary() || !mbLineColor || ImplIsRecordLayout() )
+ return;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ bool bDrawn = false;
+
+ // #i101598# support AA and snap for lines, too
+ if( mpGraphics->supportsOperation(OutDevSupportType::B2DDraw)
+ && RasterOp::OverPaint == GetRasterOp()
+ && IsLineColor())
+ {
+ // at least transform with double precision to device coordinates; this will
+ // avoid pixel snap of single, appended lines
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolygon aB2DPolyLine;
+
+ aB2DPolyLine.append(basegfx::B2DPoint(rStartPt.X(), rStartPt.Y()));
+ aB2DPolyLine.append(basegfx::B2DPoint(rEndPt.X(), rEndPt.Y()));
+ aB2DPolyLine.transform( aTransform );
+
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ bDrawn = mpGraphics->DrawPolyLine(
+ basegfx::B2DHomMatrix(),
+ aB2DPolyLine,
+ 0.0,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ *this);
+ }
+ if(!bDrawn)
+ {
+ const Point aStartPt(ImplLogicToDevicePixel(rStartPt));
+ const Point aEndPt(ImplLogicToDevicePixel(rEndPt));
+ mpGraphics->DrawLine( aStartPt.X(), aStartPt.Y(), aEndPt.X(), aEndPt.Y(), *this );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawLine( rStartPt, rEndPt );
+}
+
+void OutputDevice::drawLine( basegfx::B2DPolyPolygon aLinePolyPolygon, const LineInfo& rInfo )
+{
+ const bool bTryB2d(mpGraphics->supportsOperation(OutDevSupportType::B2DDraw)
+ && RasterOp::OverPaint == GetRasterOp()
+ && IsLineColor());
+ basegfx::B2DPolyPolygon aFillPolyPolygon;
+ const bool bDashUsed(LineStyle::Dash == rInfo.GetStyle());
+ const bool bLineWidthUsed(rInfo.GetWidth() > 1);
+
+ if(bDashUsed && aLinePolyPolygon.count())
+ {
+ ::std::vector< double > fDotDashArray = rInfo.GetDotDashArray();
+ const double fAccumulated(::std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0));
+
+ if(fAccumulated > 0.0)
+ {
+ basegfx::B2DPolyPolygon aResult;
+
+ for(auto const& rPolygon : std::as_const(aLinePolyPolygon))
+ {
+ basegfx::B2DPolyPolygon aLineTarget;
+ basegfx::utils::applyLineDashing(
+ rPolygon,
+ fDotDashArray,
+ &aLineTarget);
+ aResult.append(aLineTarget);
+ }
+
+ aLinePolyPolygon = aResult;
+ }
+ }
+
+ if(bLineWidthUsed && aLinePolyPolygon.count())
+ {
+ const double fHalfLineWidth((rInfo.GetWidth() * 0.5) + 0.5);
+
+ if(aLinePolyPolygon.areControlPointsUsed())
+ {
+ // #i110768# When area geometry has to be created, do not
+ // use the fallback bezier decomposition inside createAreaGeometry,
+ // but one that is at least as good as ImplSubdivideBezier was.
+ // There, Polygon::AdaptiveSubdivide was used with default parameter
+ // 1.0 as quality index.
+ aLinePolyPolygon = basegfx::utils::adaptiveSubdivideByDistance(aLinePolyPolygon, 1.0);
+ }
+
+ for(auto const& rPolygon : std::as_const(aLinePolyPolygon))
+ {
+ aFillPolyPolygon.append(basegfx::utils::createAreaGeometry(
+ rPolygon,
+ fHalfLineWidth,
+ rInfo.GetLineJoin(),
+ rInfo.GetLineCap()));
+ }
+
+ aLinePolyPolygon.clear();
+ }
+
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ mpMetaFile = nullptr;
+
+ if(aLinePolyPolygon.count())
+ {
+ for(auto const& rB2DPolygon : std::as_const(aLinePolyPolygon))
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+ bool bDone(false);
+
+ if(bTryB2d)
+ {
+ bDone = mpGraphics->DrawPolyLine(
+ basegfx::B2DHomMatrix(),
+ rB2DPolygon,
+ 0.0,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ *this);
+ }
+
+ if(!bDone)
+ {
+ tools::Polygon aPolygon(rB2DPolygon);
+ mpGraphics->DrawPolyLine(
+ aPolygon.GetSize(),
+ aPolygon.GetPointAry(),
+ *this);
+ }
+ }
+ }
+
+ if(aFillPolyPolygon.count())
+ {
+ const Color aOldLineColor( maLineColor );
+ const Color aOldFillColor( maFillColor );
+
+ SetLineColor();
+ InitLineColor();
+ SetFillColor( aOldLineColor );
+ InitFillColor();
+
+ bool bDone(false);
+
+ if(bTryB2d)
+ {
+ bDone = mpGraphics->DrawPolyPolygon(
+ basegfx::B2DHomMatrix(),
+ aFillPolyPolygon,
+ 0.0,
+ *this);
+ }
+
+ if(!bDone)
+ {
+ for(auto const& rB2DPolygon : std::as_const(aFillPolyPolygon))
+ {
+ tools::Polygon aPolygon(rB2DPolygon);
+
+ // need to subdivide, mpGraphics->DrawPolygon ignores curves
+ aPolygon.AdaptiveSubdivide(aPolygon);
+ mpGraphics->DrawPolygon(aPolygon.GetSize(), aPolygon.GetConstPointAry(), *this);
+ }
+ }
+
+ SetFillColor( aOldFillColor );
+ SetLineColor( aOldLineColor );
+ }
+
+ mpMetaFile = pOldMetaFile;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/map.cxx b/vcl/source/outdev/map.cxx
new file mode 100644
index 000000000..bb4683f37
--- /dev/null
+++ b/vcl/source/outdev/map.cxx
@@ -0,0 +1,1910 @@
+/* -*- 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 <tools/bigint.hxx>
+#include <tools/debug.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/wrkwin.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <ImplOutDevData.hxx>
+#include <svdata.hxx>
+#include <window.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <tools/UnitConversion.hxx>
+
+/*
+Reduces accuracy until it is a fraction (should become
+ctor fraction once); we could also do this with BigInts
+*/
+
+static Fraction ImplMakeFraction( tools::Long nN1, tools::Long nN2, tools::Long nD1, tools::Long nD2 )
+{
+ if( nD1 == 0 || nD2 == 0 ) //under these bad circumstances the following while loop will be endless
+ {
+ SAL_WARN("vcl.gdi", "Invalid parameter for ImplMakeFraction");
+ return Fraction( 1, 1 );
+ }
+
+ tools::Long i = 1;
+
+ if ( nN1 < 0 ) { i = -i; nN1 = -nN1; }
+ if ( nN2 < 0 ) { i = -i; nN2 = -nN2; }
+ if ( nD1 < 0 ) { i = -i; nD1 = -nD1; }
+ if ( nD2 < 0 ) { i = -i; nD2 = -nD2; }
+ // all positive; i sign
+
+ Fraction aF = Fraction( i*nN1, nD1 ) * Fraction( nN2, nD2 );
+
+ while ( !aF.IsValid() ) {
+ if ( nN1 > nN2 )
+ nN1 = (nN1 + 1) / 2;
+ else
+ nN2 = (nN2 + 1) / 2;
+ if ( nD1 > nD2 )
+ nD1 = (nD1 + 1) / 2;
+ else
+ nD2 = (nD2 + 1) / 2;
+
+ aF = Fraction( i*nN1, nD1 ) * Fraction( nN2, nD2 );
+ }
+
+ aF.ReduceInaccurate(32);
+ return aF;
+}
+
+static auto setMapRes(ImplMapRes& rMapRes, const o3tl::Length eUnit)
+{
+ const auto [nNum, nDen] = o3tl::getConversionMulDiv(eUnit, o3tl::Length::in);
+ rMapRes.mnMapScNumX = rMapRes.mnMapScNumY = nNum;
+ rMapRes.mnMapScDenomX = rMapRes.mnMapScDenomY = nDen;
+};
+
+static void ImplCalcMapResolution( const MapMode& rMapMode,
+ tools::Long nDPIX, tools::Long nDPIY, ImplMapRes& rMapRes )
+{
+ switch ( rMapMode.GetMapUnit() )
+ {
+ case MapUnit::MapRelative:
+ break;
+ case MapUnit::Map100thMM:
+ setMapRes(rMapRes, o3tl::Length::mm100);
+ break;
+ case MapUnit::Map10thMM:
+ setMapRes(rMapRes, o3tl::Length::mm10);
+ break;
+ case MapUnit::MapMM:
+ setMapRes(rMapRes, o3tl::Length::mm);
+ break;
+ case MapUnit::MapCM:
+ setMapRes(rMapRes, o3tl::Length::cm);
+ break;
+ case MapUnit::Map1000thInch:
+ setMapRes(rMapRes, o3tl::Length::in1000);
+ break;
+ case MapUnit::Map100thInch:
+ setMapRes(rMapRes, o3tl::Length::in100);
+ break;
+ case MapUnit::Map10thInch:
+ setMapRes(rMapRes, o3tl::Length::in10);
+ break;
+ case MapUnit::MapInch:
+ setMapRes(rMapRes, o3tl::Length::in);
+ break;
+ case MapUnit::MapPoint:
+ setMapRes(rMapRes, o3tl::Length::pt);
+ break;
+ case MapUnit::MapTwip:
+ setMapRes(rMapRes, o3tl::Length::twip);
+ break;
+ case MapUnit::MapPixel:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = nDPIX;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = nDPIY;
+ break;
+ case MapUnit::MapSysFont:
+ case MapUnit::MapAppFont:
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData->maGDIData.mnAppFontX )
+ {
+ if (pSVData->maFrameData.mpFirstFrame)
+ vcl::Window::ImplInitAppFontData(pSVData->maFrameData.mpFirstFrame);
+ else
+ {
+ ScopedVclPtrInstance<WorkWindow> pWin( nullptr, 0 );
+ vcl::Window::ImplInitAppFontData( pWin );
+ }
+ }
+ rMapRes.mnMapScNumX = pSVData->maGDIData.mnAppFontX;
+ rMapRes.mnMapScDenomX = nDPIX * 40;
+ rMapRes.mnMapScNumY = pSVData->maGDIData.mnAppFontY;
+ rMapRes.mnMapScDenomY = nDPIY * 80;
+ }
+ break;
+ default:
+ OSL_FAIL( "unhandled MapUnit" );
+ break;
+ }
+
+ const Fraction& aScaleX = rMapMode.GetScaleX();
+ const Fraction& aScaleY = rMapMode.GetScaleY();
+
+ // set offset according to MapMode
+ Point aOrigin = rMapMode.GetOrigin();
+ if ( rMapMode.GetMapUnit() != MapUnit::MapRelative )
+ {
+ rMapRes.mnMapOfsX = aOrigin.X();
+ rMapRes.mnMapOfsY = aOrigin.Y();
+ }
+ else
+ {
+ auto nXNumerator = aScaleX.GetNumerator();
+ auto nYNumerator = aScaleY.GetNumerator();
+ assert(nXNumerator != 0 && nYNumerator != 0);
+
+ BigInt aX( rMapRes.mnMapOfsX );
+ aX *= BigInt( aScaleX.GetDenominator() );
+ if ( rMapRes.mnMapOfsX >= 0 )
+ {
+ if (nXNumerator >= 0)
+ aX += BigInt(nXNumerator / 2);
+ else
+ aX -= BigInt((nXNumerator + 1) / 2);
+ }
+ else
+ {
+ if (nXNumerator >= 0 )
+ aX -= BigInt((nXNumerator - 1) / 2);
+ else
+ aX += BigInt(nXNumerator / 2);
+ }
+ aX /= BigInt(nXNumerator);
+ rMapRes.mnMapOfsX = static_cast<tools::Long>(aX) + aOrigin.X();
+ BigInt aY( rMapRes.mnMapOfsY );
+ aY *= BigInt( aScaleY.GetDenominator() );
+ if( rMapRes.mnMapOfsY >= 0 )
+ {
+ if (nYNumerator >= 0)
+ aY += BigInt(nYNumerator / 2);
+ else
+ aY -= BigInt((nYNumerator + 1) / 2);
+ }
+ else
+ {
+ if (nYNumerator >= 0)
+ aY -= BigInt((nYNumerator - 1) / 2);
+ else
+ aY += BigInt(nYNumerator / 2);
+ }
+ aY /= BigInt(nYNumerator);
+ rMapRes.mnMapOfsY = static_cast<tools::Long>(aY) + aOrigin.Y();
+ }
+
+ // calculate scaling factor according to MapMode
+ // aTemp? = rMapRes.mnMapSc? * aScale?
+ Fraction aTempX = ImplMakeFraction( rMapRes.mnMapScNumX,
+ aScaleX.GetNumerator(),
+ rMapRes.mnMapScDenomX,
+ aScaleX.GetDenominator() );
+ Fraction aTempY = ImplMakeFraction( rMapRes.mnMapScNumY,
+ aScaleY.GetNumerator(),
+ rMapRes.mnMapScDenomY,
+ aScaleY.GetDenominator() );
+ rMapRes.mnMapScNumX = aTempX.GetNumerator();
+ rMapRes.mnMapScDenomX = aTempX.GetDenominator();
+ rMapRes.mnMapScNumY = aTempY.GetNumerator();
+ rMapRes.mnMapScDenomY = aTempY.GetDenominator();
+}
+
+// #i75163#
+void OutputDevice::ImplInvalidateViewTransform()
+{
+ if(!mpOutDevData)
+ return;
+
+ if(mpOutDevData->mpViewTransform)
+ {
+ delete mpOutDevData->mpViewTransform;
+ mpOutDevData->mpViewTransform = nullptr;
+ }
+
+ if(mpOutDevData->mpInverseViewTransform)
+ {
+ delete mpOutDevData->mpInverseViewTransform;
+ mpOutDevData->mpInverseViewTransform = nullptr;
+ }
+}
+
+static tools::Long ImplLogicToPixel(tools::Long n, tools::Long nDPI, tools::Long nMapNum,
+ tools::Long nMapDenom)
+{
+ assert(nDPI > 0);
+ assert(nMapDenom != 0);
+ if constexpr (sizeof(tools::Long) >= 8)
+ {
+ assert(nMapNum >= 0);
+ //detect overflows
+ assert(nMapNum == 0
+ || std::abs(n) < std::numeric_limits<tools::Long>::max() / nMapNum / nDPI);
+ }
+ sal_Int64 n64 = n;
+ n64 *= nMapNum;
+ n64 *= nDPI;
+ if (nMapDenom == 1)
+ n = static_cast<tools::Long>(n64);
+ else
+ {
+ n64 = 2 * n64 / nMapDenom;
+ if (n64 < 0)
+ --n64;
+ else
+ ++n64;
+ n = static_cast<tools::Long>(n64 / 2);
+ }
+ return n;
+}
+
+static double ImplLogicToPixel(double n, tools::Long nDPI, tools::Long nMapNum,
+ tools::Long nMapDenom)
+{
+ assert(nDPI > 0);
+ assert(nMapDenom != 0);
+ return n * nMapNum * nDPI / nMapDenom;
+}
+
+static tools::Long ImplPixelToLogic(tools::Long n, tools::Long nDPI, tools::Long nMapNum,
+ tools::Long nMapDenom)
+{
+ assert(nDPI > 0);
+ if (nMapNum == 0)
+ return 0;
+ sal_Int64 nDenom = nDPI;
+ nDenom *= nMapNum;
+
+ sal_Int64 n64 = n;
+ n64 *= nMapDenom;
+ if (nDenom == 1)
+ n = static_cast<tools::Long>(n64);
+ else
+ {
+ n64 = 2 * n64 / nDenom;
+ if (n64 < 0)
+ --n64;
+ else
+ ++n64;
+ n = static_cast<tools::Long>(n64 / 2);
+ }
+ return n;
+}
+
+tools::Long OutputDevice::ImplLogicXToDevicePixel( tools::Long nX ) const
+{
+ if ( !mbMap )
+ return nX+mnOutOffX;
+
+ return ImplLogicToPixel( nX + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX;
+}
+
+tools::Long OutputDevice::ImplLogicYToDevicePixel( tools::Long nY ) const
+{
+ if ( !mbMap )
+ return nY+mnOutOffY;
+
+ return ImplLogicToPixel( nY + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY;
+}
+
+tools::Long OutputDevice::ImplLogicWidthToDevicePixel( tools::Long nWidth ) const
+{
+ if ( !mbMap )
+ return nWidth;
+
+ return ImplLogicToPixel(nWidth, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX);
+}
+
+tools::Long OutputDevice::ImplLogicHeightToDevicePixel( tools::Long nHeight ) const
+{
+ if ( !mbMap )
+ return nHeight;
+
+ return ImplLogicToPixel(nHeight, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY);
+}
+
+float OutputDevice::ImplFloatLogicHeightToDevicePixel( float fLogicHeight) const
+{
+ if( !mbMap)
+ return fLogicHeight;
+ float fPixelHeight = (fLogicHeight * mnDPIY * maMapRes.mnMapScNumY) / maMapRes.mnMapScDenomY;
+ return fPixelHeight;
+}
+
+tools::Long OutputDevice::ImplDevicePixelToLogicWidth( tools::Long nWidth ) const
+{
+ if ( !mbMap )
+ return nWidth;
+
+ return ImplPixelToLogic(nWidth, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX);
+}
+
+tools::Long OutputDevice::ImplDevicePixelToLogicHeight( tools::Long nHeight ) const
+{
+ if ( !mbMap )
+ return nHeight;
+
+ return ImplPixelToLogic(nHeight, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY);
+}
+
+Point OutputDevice::ImplLogicToDevicePixel( const Point& rLogicPt ) const
+{
+ if ( !mbMap )
+ return Point( rLogicPt.X()+mnOutOffX, rLogicPt.Y()+mnOutOffY );
+
+ return Point( ImplLogicToPixel( rLogicPt.X() + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicPt.Y() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY );
+}
+
+Size OutputDevice::ImplLogicToDevicePixel( const Size& rLogicSize ) const
+{
+ if ( !mbMap )
+ return rLogicSize;
+
+ return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ),
+ ImplLogicToPixel( rLogicSize.Height(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) );
+}
+
+tools::Rectangle OutputDevice::ImplLogicToDevicePixel( const tools::Rectangle& rLogicRect ) const
+{
+ // tdf#141761 IsEmpty() removed
+ // Even if rLogicRect.IsEmpty(), transform of the Position contained
+ // in the Rectangle is necessary. Due to Rectangle::Right() returning
+ // Left() when IsEmpty(), the code *could* stay unchanged (same for Bottom),
+ // but:
+ // The Rectangle constructor used with the four tools::Long values does not
+ // check for IsEmpty(), so to keep that state correct there are two possibilities:
+ // (1) Add a test to the Rectangle constructor in question
+ // (2) Do it handish here
+ // I have tried (1) first, but test Test::test_rectangle() claims that for
+ // tools::Rectangle aRect(1, 1, 1, 1);
+ // tools::Long(1) == aRect.GetWidth()
+ // tools::Long(0) == aRect.getWidth()
+ // (remember: this means Left == Right == 1 -> GetWidth => 1, getWidth == 0)
+ // so indeed the 1's have to go uncommented/unchecked into the data body
+ // of rectangle. Switching to (2) *is* needed, doing so
+ tools::Rectangle aRetval;
+
+ if ( !mbMap )
+ {
+ aRetval = tools::Rectangle(
+ rLogicRect.Left()+mnOutOffX,
+ rLogicRect.Top()+mnOutOffY,
+ rLogicRect.IsWidthEmpty() ? 0 : rLogicRect.Right()+mnOutOffX,
+ rLogicRect.IsHeightEmpty() ? 0 : rLogicRect.Bottom()+mnOutOffY );
+ }
+ else
+ {
+ aRetval = tools::Rectangle(
+ ImplLogicToPixel( rLogicRect.Left()+maMapRes.mnMapOfsX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Top()+maMapRes.mnMapOfsY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY,
+ rLogicRect.IsWidthEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Right()+maMapRes.mnMapOfsX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX,
+ rLogicRect.IsHeightEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Bottom()+maMapRes.mnMapOfsY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY );
+ }
+
+ if(rLogicRect.IsWidthEmpty())
+ aRetval.SetWidthEmpty();
+
+ if(rLogicRect.IsHeightEmpty())
+ aRetval.SetHeightEmpty();
+
+ return aRetval;
+}
+
+tools::Polygon OutputDevice::ImplLogicToDevicePixel( const tools::Polygon& rLogicPoly ) const
+{
+ if ( !mbMap && !mnOutOffX && !mnOutOffY )
+ return rLogicPoly;
+
+ sal_uInt16 i;
+ sal_uInt16 nPoints = rLogicPoly.GetSize();
+ tools::Polygon aPoly( rLogicPoly );
+
+ // get pointer to Point-array (copy data)
+ const Point* pPointAry = aPoly.GetConstPointAry();
+
+ if ( mbMap )
+ {
+ for ( i = 0; i < nPoints; i++ )
+ {
+ const Point& rPt = pPointAry[i];
+ Point aPt(ImplLogicToPixel( rPt.X()+maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( rPt.Y()+maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY);
+ aPoly[i] = aPt;
+ }
+ }
+ else
+ {
+ for ( i = 0; i < nPoints; i++ )
+ {
+ Point aPt = pPointAry[i];
+ aPt.AdjustX(mnOutOffX );
+ aPt.AdjustY(mnOutOffY );
+ aPoly[i] = aPt;
+ }
+ }
+
+ return aPoly;
+}
+
+basegfx::B2DPolygon OutputDevice::ImplLogicToDevicePixel(const basegfx::B2DPolygon& rLogicPoly) const
+{
+ if (!mbMap && !mnOutOffX && !mnOutOffY)
+ return rLogicPoly;
+
+ sal_uInt32 nPoints = rLogicPoly.count();
+ basegfx::B2DPolygon aPoly(rLogicPoly);
+
+ basegfx::B2DPoint aC1;
+ basegfx::B2DPoint aC2;
+
+ if (mbMap)
+ {
+ for (sal_uInt32 i = 0; i < nPoints; ++i)
+ {
+ const basegfx::B2DPoint& rPt = aPoly.getB2DPoint(i);
+ basegfx::B2DPoint aPt(ImplLogicToPixel( rPt.getX()+maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( rPt.getY()+maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY);
+
+ const bool bC1 = aPoly.isPrevControlPointUsed(i);
+ if (bC1)
+ {
+ const basegfx::B2DPoint aB2DC1(aPoly.getPrevControlPoint(i));
+
+ aC1 = basegfx::B2DPoint(ImplLogicToPixel( aB2DC1.getX()+maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( aB2DC1.getY()+maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY);
+ }
+
+ const bool bC2 = aPoly.isNextControlPointUsed(i);
+ if (bC2)
+ {
+ const basegfx::B2DPoint aB2DC2(aPoly.getNextControlPoint(i));
+
+ aC2 = basegfx::B2DPoint(ImplLogicToPixel( aB2DC2.getX()+maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( aB2DC2.getY()+maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY);
+ }
+
+ aPoly.setB2DPoint(i, aPt);
+
+ if (bC1)
+ aPoly.setPrevControlPoint(i, aC1);
+
+ if (bC2)
+ aPoly.setNextControlPoint(i, aC2);
+ }
+ }
+ else
+ {
+ for (sal_uInt32 i = 0; i < nPoints; ++i)
+ {
+ const basegfx::B2DPoint& rPt = aPoly.getB2DPoint(i);
+ basegfx::B2DPoint aPt(rPt.getX() + mnOutOffX, rPt.getY() + mnOutOffY);
+
+ const bool bC1 = aPoly.isPrevControlPointUsed(i);
+ if (bC1)
+ {
+ const basegfx::B2DPoint aB2DC1(aPoly.getPrevControlPoint(i));
+
+ aC1 = basegfx::B2DPoint(aB2DC1.getX() + mnOutOffX, aB2DC1.getY() + mnOutOffY);
+ }
+
+ const bool bC2 = aPoly.isNextControlPointUsed(i);
+ if (bC2)
+ {
+ const basegfx::B2DPoint aB2DC2(aPoly.getNextControlPoint(i));
+
+ aC1 = basegfx::B2DPoint(aB2DC2.getX() + mnOutOffX, aB2DC2.getY() + mnOutOffY);
+ }
+
+ aPoly.setB2DPoint(i, aPt);
+
+ if (bC1)
+ aPoly.setPrevControlPoint(i, aC1);
+
+ if (bC2)
+ aPoly.setNextControlPoint(i, aC2);
+ }
+ }
+
+ return aPoly;
+}
+
+tools::PolyPolygon OutputDevice::ImplLogicToDevicePixel( const tools::PolyPolygon& rLogicPolyPoly ) const
+{
+ if ( !mbMap && !mnOutOffX && !mnOutOffY )
+ return rLogicPolyPoly;
+
+ tools::PolyPolygon aPolyPoly( rLogicPolyPoly );
+ sal_uInt16 nPoly = aPolyPoly.Count();
+ for( sal_uInt16 i = 0; i < nPoly; i++ )
+ {
+ tools::Polygon& rPoly = aPolyPoly[i];
+ rPoly = ImplLogicToDevicePixel( rPoly );
+ }
+ return aPolyPoly;
+}
+
+LineInfo OutputDevice::ImplLogicToDevicePixel( const LineInfo& rLineInfo ) const
+{
+ LineInfo aInfo( rLineInfo );
+
+ if( aInfo.GetStyle() == LineStyle::Dash )
+ {
+ if( aInfo.GetDotCount() && aInfo.GetDotLen() )
+ aInfo.SetDotLen( std::max( ImplLogicWidthToDevicePixel( aInfo.GetDotLen() ), tools::Long(1) ) );
+ else
+ aInfo.SetDotCount( 0 );
+
+ if( aInfo.GetDashCount() && aInfo.GetDashLen() )
+ aInfo.SetDashLen( std::max( ImplLogicWidthToDevicePixel( aInfo.GetDashLen() ), tools::Long(1) ) );
+ else
+ aInfo.SetDashCount( 0 );
+
+ aInfo.SetDistance( ImplLogicWidthToDevicePixel( aInfo.GetDistance() ) );
+
+ if( ( !aInfo.GetDashCount() && !aInfo.GetDotCount() ) || !aInfo.GetDistance() )
+ aInfo.SetStyle( LineStyle::Solid );
+ }
+
+ aInfo.SetWidth( ImplLogicWidthToDevicePixel( aInfo.GetWidth() ) );
+
+ return aInfo;
+}
+
+tools::Rectangle OutputDevice::ImplDevicePixelToLogic( const tools::Rectangle& rPixelRect ) const
+{
+ // tdf#141761 see comments above, IsEmpty() removed
+ tools::Rectangle aRetval;
+
+ if ( !mbMap )
+ {
+ aRetval = tools::Rectangle(
+ rPixelRect.Left()-mnOutOffX,
+ rPixelRect.Top()-mnOutOffY,
+ rPixelRect.IsWidthEmpty() ? 0 : rPixelRect.Right()-mnOutOffX,
+ rPixelRect.IsHeightEmpty() ? 0 : rPixelRect.Bottom()-mnOutOffY );
+ }
+ else
+ {
+ aRetval = tools::Rectangle(
+ ImplPixelToLogic( rPixelRect.Left()-mnOutOffX-mnOutOffOrigX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )-maMapRes.mnMapOfsX,
+ ImplPixelToLogic( rPixelRect.Top()-mnOutOffY-mnOutOffOrigY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )-maMapRes.mnMapOfsY,
+ rPixelRect.IsWidthEmpty() ? 0 : ImplPixelToLogic( rPixelRect.Right()-mnOutOffX-mnOutOffOrigX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )-maMapRes.mnMapOfsX,
+ rPixelRect.IsHeightEmpty() ? 0 : ImplPixelToLogic( rPixelRect.Bottom()-mnOutOffY-mnOutOffOrigY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )-maMapRes.mnMapOfsY );
+ }
+
+ if(rPixelRect.IsWidthEmpty())
+ aRetval.SetWidthEmpty();
+
+ if(rPixelRect.IsHeightEmpty())
+ aRetval.SetHeightEmpty();
+
+ return aRetval;
+}
+
+vcl::Region OutputDevice::ImplPixelToDevicePixel( const vcl::Region& rRegion ) const
+{
+ if ( !mnOutOffX && !mnOutOffY )
+ return rRegion;
+
+ vcl::Region aRegion( rRegion );
+ aRegion.Move( mnOutOffX+mnOutOffOrigX, mnOutOffY+mnOutOffOrigY );
+ return aRegion;
+}
+
+void OutputDevice::EnableMapMode( bool bEnable )
+{
+ mbMap = bEnable;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->EnableMapMode( bEnable );
+}
+
+void OutputDevice::SetMapMode()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaMapModeAction( MapMode() ) );
+
+ if ( mbMap || !maMapMode.IsDefault() )
+ {
+ mbMap = false;
+ maMapMode = MapMode();
+
+ // create new objects (clip region are not re-scaled)
+ mbNewFont = true;
+ mbInitFont = true;
+ ImplInitMapModeObjects();
+
+ // #106426# Adapt logical offset when changing mapmode
+ mnOutOffLogicX = mnOutOffOrigX; // no mapping -> equal offsets
+ mnOutOffLogicY = mnOutOffOrigY;
+
+ // #i75163#
+ ImplInvalidateViewTransform();
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetMapMode();
+}
+
+void OutputDevice::SetMapMode( const MapMode& rNewMapMode )
+{
+
+ bool bRelMap = (rNewMapMode.GetMapUnit() == MapUnit::MapRelative);
+
+ if ( mpMetaFile )
+ {
+ mpMetaFile->AddAction( new MetaMapModeAction( rNewMapMode ) );
+ }
+
+ // do nothing if MapMode was not changed
+ if ( maMapMode == rNewMapMode )
+ return;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetMapMode( rNewMapMode );
+
+ // if default MapMode calculate nothing
+ bool bOldMap = mbMap;
+ mbMap = !rNewMapMode.IsDefault();
+ if ( mbMap )
+ {
+ // if only the origin is converted, do not scale new
+ if ( (rNewMapMode.GetMapUnit() == maMapMode.GetMapUnit()) &&
+ (rNewMapMode.GetScaleX() == maMapMode.GetScaleX()) &&
+ (rNewMapMode.GetScaleY() == maMapMode.GetScaleY()) &&
+ (bOldMap == mbMap) )
+ {
+ // set offset
+ Point aOrigin = rNewMapMode.GetOrigin();
+ maMapRes.mnMapOfsX = aOrigin.X();
+ maMapRes.mnMapOfsY = aOrigin.Y();
+ maMapMode = rNewMapMode;
+
+ // #i75163#
+ ImplInvalidateViewTransform();
+
+ return;
+ }
+ if ( !bOldMap && bRelMap )
+ {
+ maMapRes.mnMapScNumX = 1;
+ maMapRes.mnMapScNumY = 1;
+ maMapRes.mnMapScDenomX = mnDPIX;
+ maMapRes.mnMapScDenomY = mnDPIY;
+ maMapRes.mnMapOfsX = 0;
+ maMapRes.mnMapOfsY = 0;
+ }
+
+ // calculate new MapMode-resolution
+ ImplCalcMapResolution(rNewMapMode, mnDPIX, mnDPIY, maMapRes);
+ }
+
+ // set new MapMode
+ if ( bRelMap )
+ {
+ Point aOrigin( maMapRes.mnMapOfsX, maMapRes.mnMapOfsY );
+ // aScale? = maMapMode.GetScale?() * rNewMapMode.GetScale?()
+ Fraction aScaleX = ImplMakeFraction( maMapMode.GetScaleX().GetNumerator(),
+ rNewMapMode.GetScaleX().GetNumerator(),
+ maMapMode.GetScaleX().GetDenominator(),
+ rNewMapMode.GetScaleX().GetDenominator() );
+ Fraction aScaleY = ImplMakeFraction( maMapMode.GetScaleY().GetNumerator(),
+ rNewMapMode.GetScaleY().GetNumerator(),
+ maMapMode.GetScaleY().GetDenominator(),
+ rNewMapMode.GetScaleY().GetDenominator() );
+ maMapMode.SetOrigin( aOrigin );
+ maMapMode.SetScaleX( aScaleX );
+ maMapMode.SetScaleY( aScaleY );
+ }
+ else
+ maMapMode = rNewMapMode;
+
+ // create new objects (clip region are not re-scaled)
+ mbNewFont = true;
+ mbInitFont = true;
+ ImplInitMapModeObjects();
+
+ // #106426# Adapt logical offset when changing mapmode
+ mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX );
+ mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY );
+
+ // #i75163#
+ ImplInvalidateViewTransform();
+}
+
+void OutputDevice::SetMetafileMapMode(const MapMode& rNewMapMode, bool bIsRecord)
+{
+ if (bIsRecord)
+ SetRelativeMapMode(rNewMapMode);
+ else
+ SetMapMode(rNewMapMode);
+}
+
+void OutputDevice::ImplInitMapModeObjects() {}
+
+void OutputDevice::SetRelativeMapMode( const MapMode& rNewMapMode )
+{
+ // do nothing if MapMode did not change
+ if ( maMapMode == rNewMapMode )
+ return;
+
+ MapUnit eOld = maMapMode.GetMapUnit();
+ MapUnit eNew = rNewMapMode.GetMapUnit();
+
+ // a?F = rNewMapMode.GetScale?() / maMapMode.GetScale?()
+ Fraction aXF = ImplMakeFraction( rNewMapMode.GetScaleX().GetNumerator(),
+ maMapMode.GetScaleX().GetDenominator(),
+ rNewMapMode.GetScaleX().GetDenominator(),
+ maMapMode.GetScaleX().GetNumerator() );
+ Fraction aYF = ImplMakeFraction( rNewMapMode.GetScaleY().GetNumerator(),
+ maMapMode.GetScaleY().GetDenominator(),
+ rNewMapMode.GetScaleY().GetDenominator(),
+ maMapMode.GetScaleY().GetNumerator() );
+
+ Point aPt( LogicToLogic( Point(), nullptr, &rNewMapMode ) );
+ if ( eNew != eOld )
+ {
+ if ( eOld > MapUnit::MapPixel )
+ {
+ SAL_WARN( "vcl.gdi", "Not implemented MapUnit" );
+ }
+ else if ( eNew > MapUnit::MapPixel )
+ {
+ SAL_WARN( "vcl.gdi", "Not implemented MapUnit" );
+ }
+ else
+ {
+ const auto eFrom = MapToO3tlLength(eOld, o3tl::Length::in);
+ const auto eTo = MapToO3tlLength(eNew, o3tl::Length::in);
+ const auto& [mul, div] = o3tl::getConversionMulDiv(eFrom, eTo);
+ Fraction aF(div, mul);
+
+ // a?F = a?F * aF
+ aXF = ImplMakeFraction( aXF.GetNumerator(), aF.GetNumerator(),
+ aXF.GetDenominator(), aF.GetDenominator() );
+ aYF = ImplMakeFraction( aYF.GetNumerator(), aF.GetNumerator(),
+ aYF.GetDenominator(), aF.GetDenominator() );
+ if ( eOld == MapUnit::MapPixel )
+ {
+ aXF *= Fraction( mnDPIX, 1 );
+ aYF *= Fraction( mnDPIY, 1 );
+ }
+ else if ( eNew == MapUnit::MapPixel )
+ {
+ aXF *= Fraction( 1, mnDPIX );
+ aYF *= Fraction( 1, mnDPIY );
+ }
+ }
+ }
+
+ MapMode aNewMapMode( MapUnit::MapRelative, Point( -aPt.X(), -aPt.Y() ), aXF, aYF );
+ SetMapMode( aNewMapMode );
+
+ if ( eNew != eOld )
+ maMapMode = rNewMapMode;
+
+ // #106426# Adapt logical offset when changing MapMode
+ mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX );
+ mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetRelativeMapMode( rNewMapMode );
+}
+
+// #i75163#
+basegfx::B2DHomMatrix OutputDevice::GetViewTransformation() const
+{
+ if(mbMap && mpOutDevData)
+ {
+ if(!mpOutDevData->mpViewTransform)
+ {
+ mpOutDevData->mpViewTransform = new basegfx::B2DHomMatrix;
+
+ const double fScaleFactorX(static_cast<double>(mnDPIX) * static_cast<double>(maMapRes.mnMapScNumX) / static_cast<double>(maMapRes.mnMapScDenomX));
+ const double fScaleFactorY(static_cast<double>(mnDPIY) * static_cast<double>(maMapRes.mnMapScNumY) / static_cast<double>(maMapRes.mnMapScDenomY));
+ const double fZeroPointX((static_cast<double>(maMapRes.mnMapOfsX) * fScaleFactorX) + static_cast<double>(mnOutOffOrigX));
+ const double fZeroPointY((static_cast<double>(maMapRes.mnMapOfsY) * fScaleFactorY) + static_cast<double>(mnOutOffOrigY));
+
+ mpOutDevData->mpViewTransform->set(0, 0, fScaleFactorX);
+ mpOutDevData->mpViewTransform->set(1, 1, fScaleFactorY);
+ mpOutDevData->mpViewTransform->set(0, 2, fZeroPointX);
+ mpOutDevData->mpViewTransform->set(1, 2, fZeroPointY);
+ }
+
+ return *mpOutDevData->mpViewTransform;
+ }
+ else
+ {
+ return basegfx::B2DHomMatrix();
+ }
+}
+
+// #i75163#
+basegfx::B2DHomMatrix OutputDevice::GetInverseViewTransformation() const
+{
+ if(mbMap && mpOutDevData)
+ {
+ if(!mpOutDevData->mpInverseViewTransform)
+ {
+ GetViewTransformation();
+ mpOutDevData->mpInverseViewTransform = new basegfx::B2DHomMatrix(*mpOutDevData->mpViewTransform);
+ mpOutDevData->mpInverseViewTransform->invert();
+ }
+
+ return *mpOutDevData->mpInverseViewTransform;
+ }
+ else
+ {
+ return basegfx::B2DHomMatrix();
+ }
+}
+
+// #i75163#
+basegfx::B2DHomMatrix OutputDevice::GetViewTransformation( const MapMode& rMapMode ) const
+{
+ // #i82615#
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ basegfx::B2DHomMatrix aTransform;
+
+ const double fScaleFactorX(static_cast<double>(mnDPIX) * static_cast<double>(aMapRes.mnMapScNumX) / static_cast<double>(aMapRes.mnMapScDenomX));
+ const double fScaleFactorY(static_cast<double>(mnDPIY) * static_cast<double>(aMapRes.mnMapScNumY) / static_cast<double>(aMapRes.mnMapScDenomY));
+ const double fZeroPointX((static_cast<double>(aMapRes.mnMapOfsX) * fScaleFactorX) + static_cast<double>(mnOutOffOrigX));
+ const double fZeroPointY((static_cast<double>(aMapRes.mnMapOfsY) * fScaleFactorY) + static_cast<double>(mnOutOffOrigY));
+
+ aTransform.set(0, 0, fScaleFactorX);
+ aTransform.set(1, 1, fScaleFactorY);
+ aTransform.set(0, 2, fZeroPointX);
+ aTransform.set(1, 2, fZeroPointY);
+
+ return aTransform;
+}
+
+// #i75163#
+basegfx::B2DHomMatrix OutputDevice::GetInverseViewTransformation( const MapMode& rMapMode ) const
+{
+ basegfx::B2DHomMatrix aMatrix( GetViewTransformation( rMapMode ) );
+ aMatrix.invert();
+ return aMatrix;
+}
+
+basegfx::B2DHomMatrix OutputDevice::ImplGetDeviceTransformation() const
+{
+ basegfx::B2DHomMatrix aTransformation = GetViewTransformation();
+ // TODO: is it worth to cache the transformed result?
+ if( mnOutOffX || mnOutOffY )
+ aTransformation.translate( mnOutOffX, mnOutOffY );
+ return aTransformation;
+}
+
+Point OutputDevice::LogicToPixel( const Point& rLogicPt ) const
+{
+
+ if ( !mbMap )
+ return rLogicPt;
+
+ return Point( ImplLogicToPixel( rLogicPt.X() + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicPt.Y() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffOrigY );
+}
+
+Size OutputDevice::LogicToPixel( const Size& rLogicSize ) const
+{
+
+ if ( !mbMap )
+ return rLogicSize;
+
+ return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ),
+ ImplLogicToPixel( rLogicSize.Height(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) );
+}
+
+tools::Rectangle OutputDevice::LogicToPixel( const tools::Rectangle& rLogicRect ) const
+{
+ // tdf#141761 see comments above, IsEmpty() removed
+ if ( !mbMap )
+ return rLogicRect;
+
+ tools::Rectangle aRetval(
+ ImplLogicToPixel( rLogicRect.Left() + maMapRes.mnMapOfsX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Top() + maMapRes.mnMapOfsY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffOrigY,
+ rLogicRect.IsWidthEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Right() + maMapRes.mnMapOfsX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffOrigX,
+ rLogicRect.IsHeightEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Bottom() + maMapRes.mnMapOfsY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffOrigY );
+
+ if(rLogicRect.IsWidthEmpty())
+ aRetval.SetWidthEmpty();
+
+ if(rLogicRect.IsHeightEmpty())
+ aRetval.SetHeightEmpty();
+
+ return aRetval;
+}
+
+tools::Polygon OutputDevice::LogicToPixel( const tools::Polygon& rLogicPoly ) const
+{
+
+ if ( !mbMap )
+ return rLogicPoly;
+
+ sal_uInt16 i;
+ sal_uInt16 nPoints = rLogicPoly.GetSize();
+ tools::Polygon aPoly( rLogicPoly );
+
+ // get pointer to Point-array (copy data)
+ const Point* pPointAry = aPoly.GetConstPointAry();
+
+ for ( i = 0; i < nPoints; i++ )
+ {
+ const Point* pPt = &(pPointAry[i]);
+ Point aPt;
+ aPt.setX( ImplLogicToPixel( pPt->X() + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffOrigX );
+ aPt.setY( ImplLogicToPixel( pPt->Y() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffOrigY );
+ aPoly[i] = aPt;
+ }
+
+ return aPoly;
+}
+
+tools::PolyPolygon OutputDevice::LogicToPixel( const tools::PolyPolygon& rLogicPolyPoly ) const
+{
+
+ if ( !mbMap )
+ return rLogicPolyPoly;
+
+ tools::PolyPolygon aPolyPoly( rLogicPolyPoly );
+ sal_uInt16 nPoly = aPolyPoly.Count();
+ for( sal_uInt16 i = 0; i < nPoly; i++ )
+ {
+ tools::Polygon& rPoly = aPolyPoly[i];
+ rPoly = LogicToPixel( rPoly );
+ }
+ return aPolyPoly;
+}
+
+basegfx::B2DPolyPolygon OutputDevice::LogicToPixel( const basegfx::B2DPolyPolygon& rLogicPolyPoly ) const
+{
+ basegfx::B2DPolyPolygon aTransformedPoly = rLogicPolyPoly;
+ const basegfx::B2DHomMatrix& rTransformationMatrix = GetViewTransformation();
+ aTransformedPoly.transform( rTransformationMatrix );
+ return aTransformedPoly;
+}
+
+vcl::Region OutputDevice::LogicToPixel( const vcl::Region& rLogicRegion ) const
+{
+
+ if(!mbMap || rLogicRegion.IsNull() || rLogicRegion.IsEmpty())
+ {
+ return rLogicRegion;
+ }
+
+ vcl::Region aRegion;
+
+ if(rLogicRegion.getB2DPolyPolygon())
+ {
+ aRegion = vcl::Region(LogicToPixel(*rLogicRegion.getB2DPolyPolygon()));
+ }
+ else if(rLogicRegion.getPolyPolygon())
+ {
+ aRegion = vcl::Region(LogicToPixel(*rLogicRegion.getPolyPolygon()));
+ }
+ else if(rLogicRegion.getRegionBand())
+ {
+ RectangleVector aRectangles;
+ rLogicRegion.GetRegionRectangles(aRectangles);
+ const RectangleVector& rRectangles(aRectangles); // needed to make the '!=' work
+
+ // make reverse run to fill new region bottom-up, this will speed it up due to the used data structuring
+ for(RectangleVector::const_reverse_iterator aRectIter(rRectangles.rbegin()); aRectIter != rRectangles.rend(); ++aRectIter)
+ {
+ aRegion.Union(LogicToPixel(*aRectIter));
+ }
+ }
+
+ return aRegion;
+}
+
+Point OutputDevice::LogicToPixel( const Point& rLogicPt,
+ const MapMode& rMapMode ) const
+{
+
+ if ( rMapMode.IsDefault() )
+ return rLogicPt;
+
+ // convert MapMode resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ return Point( ImplLogicToPixel( rLogicPt.X() + aMapRes.mnMapOfsX, mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicPt.Y() + aMapRes.mnMapOfsY, mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY )+mnOutOffOrigY );
+}
+
+Size OutputDevice::LogicToPixel( const Size& rLogicSize,
+ const MapMode& rMapMode ) const
+{
+
+ if ( rMapMode.IsDefault() )
+ return rLogicSize;
+
+ // convert MapMode resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ),
+ ImplLogicToPixel( rLogicSize.Height(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) );
+}
+
+tools::Rectangle OutputDevice::LogicToPixel( const tools::Rectangle& rLogicRect,
+ const MapMode& rMapMode ) const
+{
+ // tdf#141761 see comments above, IsEmpty() removed
+ if ( rMapMode.IsDefault() )
+ return rLogicRect;
+
+ // convert MapMode resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ tools::Rectangle aRetval(
+ ImplLogicToPixel( rLogicRect.Left() + aMapRes.mnMapOfsX, mnDPIX, aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Top() + aMapRes.mnMapOfsY, mnDPIY, aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY )+mnOutOffOrigY,
+ rLogicRect.IsWidthEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Right() + aMapRes.mnMapOfsX, mnDPIX, aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX )+mnOutOffOrigX,
+ rLogicRect.IsHeightEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Bottom() + aMapRes.mnMapOfsY, mnDPIY, aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY )+mnOutOffOrigY );
+
+ if(rLogicRect.IsWidthEmpty())
+ aRetval.SetWidthEmpty();
+
+ if(rLogicRect.IsHeightEmpty())
+ aRetval.SetHeightEmpty();
+
+ return aRetval;
+}
+
+tools::Polygon OutputDevice::LogicToPixel( const tools::Polygon& rLogicPoly,
+ const MapMode& rMapMode ) const
+{
+
+ if ( rMapMode.IsDefault() )
+ return rLogicPoly;
+
+ // convert MapMode resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ sal_uInt16 i;
+ sal_uInt16 nPoints = rLogicPoly.GetSize();
+ tools::Polygon aPoly( rLogicPoly );
+
+ // get pointer to Point-array (copy data)
+ const Point* pPointAry = aPoly.GetConstPointAry();
+
+ for ( i = 0; i < nPoints; i++ )
+ {
+ const Point* pPt = &(pPointAry[i]);
+ Point aPt;
+ aPt.setX( ImplLogicToPixel( pPt->X() + aMapRes.mnMapOfsX, mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX )+mnOutOffOrigX );
+ aPt.setY( ImplLogicToPixel( pPt->Y() + aMapRes.mnMapOfsY, mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY )+mnOutOffOrigY );
+ aPoly[i] = aPt;
+ }
+
+ return aPoly;
+}
+
+basegfx::B2DPolyPolygon OutputDevice::LogicToPixel( const basegfx::B2DPolyPolygon& rLogicPolyPoly,
+ const MapMode& rMapMode ) const
+{
+ basegfx::B2DPolyPolygon aTransformedPoly = rLogicPolyPoly;
+ const basegfx::B2DHomMatrix& rTransformationMatrix = GetViewTransformation( rMapMode );
+ aTransformedPoly.transform( rTransformationMatrix );
+ return aTransformedPoly;
+}
+
+Point OutputDevice::PixelToLogic( const Point& rDevicePt ) const
+{
+
+ if ( !mbMap )
+ return rDevicePt;
+
+ return Point( ImplPixelToLogic( rDevicePt.X(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ) - maMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDevicePt.Y(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) - maMapRes.mnMapOfsY - mnOutOffLogicY );
+}
+
+Size OutputDevice::PixelToLogic( const Size& rDeviceSize ) const
+{
+
+ if ( !mbMap )
+ return rDeviceSize;
+
+ return Size( ImplPixelToLogic( rDeviceSize.Width(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ),
+ ImplPixelToLogic( rDeviceSize.Height(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) );
+}
+
+tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect ) const
+{
+ // tdf#141761 see comments above, IsEmpty() removed
+ if ( !mbMap )
+ return rDeviceRect;
+
+ tools::Rectangle aRetval(
+ ImplPixelToLogic( rDeviceRect.Left(), mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ) - maMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDeviceRect.Top(), mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) - maMapRes.mnMapOfsY - mnOutOffLogicY,
+ rDeviceRect.IsWidthEmpty() ? 0 : ImplPixelToLogic( rDeviceRect.Right(), mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ) - maMapRes.mnMapOfsX - mnOutOffLogicX,
+ rDeviceRect.IsHeightEmpty() ? 0 : ImplPixelToLogic( rDeviceRect.Bottom(), mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) - maMapRes.mnMapOfsY - mnOutOffLogicY );
+
+ if(rDeviceRect.IsWidthEmpty())
+ aRetval.SetWidthEmpty();
+
+ if(rDeviceRect.IsHeightEmpty())
+ aRetval.SetHeightEmpty();
+
+ return aRetval;
+}
+
+tools::Polygon OutputDevice::PixelToLogic( const tools::Polygon& rDevicePoly ) const
+{
+
+ if ( !mbMap )
+ return rDevicePoly;
+
+ sal_uInt16 i;
+ sal_uInt16 nPoints = rDevicePoly.GetSize();
+ tools::Polygon aPoly( rDevicePoly );
+
+ // get pointer to Point-array (copy data)
+ const Point* pPointAry = aPoly.GetConstPointAry();
+
+ for ( i = 0; i < nPoints; i++ )
+ {
+ const Point* pPt = &(pPointAry[i]);
+ Point aPt;
+ aPt.setX( ImplPixelToLogic( pPt->X(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ) - maMapRes.mnMapOfsX - mnOutOffLogicX );
+ aPt.setY( ImplPixelToLogic( pPt->Y(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) - maMapRes.mnMapOfsY - mnOutOffLogicY );
+ aPoly[i] = aPt;
+ }
+
+ return aPoly;
+}
+
+tools::PolyPolygon OutputDevice::PixelToLogic( const tools::PolyPolygon& rDevicePolyPoly ) const
+{
+
+ if ( !mbMap )
+ return rDevicePolyPoly;
+
+ tools::PolyPolygon aPolyPoly( rDevicePolyPoly );
+ sal_uInt16 nPoly = aPolyPoly.Count();
+ for( sal_uInt16 i = 0; i < nPoly; i++ )
+ {
+ tools::Polygon& rPoly = aPolyPoly[i];
+ rPoly = PixelToLogic( rPoly );
+ }
+ return aPolyPoly;
+}
+
+basegfx::B2DPolyPolygon OutputDevice::PixelToLogic( const basegfx::B2DPolyPolygon& rPixelPolyPoly ) const
+{
+ basegfx::B2DPolyPolygon aTransformedPoly = rPixelPolyPoly;
+ const basegfx::B2DHomMatrix& rTransformationMatrix = GetInverseViewTransformation();
+ aTransformedPoly.transform( rTransformationMatrix );
+ return aTransformedPoly;
+}
+
+vcl::Region OutputDevice::PixelToLogic( const vcl::Region& rDeviceRegion ) const
+{
+
+ if(!mbMap || rDeviceRegion.IsNull() || rDeviceRegion.IsEmpty())
+ {
+ return rDeviceRegion;
+ }
+
+ vcl::Region aRegion;
+
+ if(rDeviceRegion.getB2DPolyPolygon())
+ {
+ aRegion = vcl::Region(PixelToLogic(*rDeviceRegion.getB2DPolyPolygon()));
+ }
+ else if(rDeviceRegion.getPolyPolygon())
+ {
+ aRegion = vcl::Region(PixelToLogic(*rDeviceRegion.getPolyPolygon()));
+ }
+ else if(rDeviceRegion.getRegionBand())
+ {
+ RectangleVector aRectangles;
+ rDeviceRegion.GetRegionRectangles(aRectangles);
+ const RectangleVector& rRectangles(aRectangles); // needed to make the '!=' work
+
+ // make reverse run to fill new region bottom-up, this will speed it up due to the used data structuring
+ for(RectangleVector::const_reverse_iterator aRectIter(rRectangles.rbegin()); aRectIter != rRectangles.rend(); ++aRectIter)
+ {
+ aRegion.Union(PixelToLogic(*aRectIter));
+ }
+ }
+
+ return aRegion;
+}
+
+Point OutputDevice::PixelToLogic( const Point& rDevicePt,
+ const MapMode& rMapMode ) const
+{
+
+ // calculate nothing if default-MapMode
+ if ( rMapMode.IsDefault() )
+ return rDevicePt;
+
+ // calculate MapMode-resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ return Point( ImplPixelToLogic( rDevicePt.X(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ) - aMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDevicePt.Y(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) - aMapRes.mnMapOfsY - mnOutOffLogicY );
+}
+
+Size OutputDevice::PixelToLogic( const Size& rDeviceSize,
+ const MapMode& rMapMode ) const
+{
+
+ // calculate nothing if default-MapMode
+ if ( rMapMode.IsDefault() )
+ return rDeviceSize;
+
+ // calculate MapMode-resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ return Size( ImplPixelToLogic( rDeviceSize.Width(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ),
+ ImplPixelToLogic( rDeviceSize.Height(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) );
+}
+
+tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect,
+ const MapMode& rMapMode ) const
+{
+ // calculate nothing if default-MapMode
+ // tdf#141761 see comments above, IsEmpty() removed
+ if ( rMapMode.IsDefault() )
+ return rDeviceRect;
+
+ // calculate MapMode-resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ tools::Rectangle aRetval(
+ ImplPixelToLogic( rDeviceRect.Left(), mnDPIX, aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ) - aMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDeviceRect.Top(), mnDPIY, aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) - aMapRes.mnMapOfsY - mnOutOffLogicY,
+ rDeviceRect.IsWidthEmpty() ? 0 : ImplPixelToLogic( rDeviceRect.Right(), mnDPIX, aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ) - aMapRes.mnMapOfsX - mnOutOffLogicX,
+ rDeviceRect.IsHeightEmpty() ? 0 : ImplPixelToLogic( rDeviceRect.Bottom(), mnDPIY, aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) - aMapRes.mnMapOfsY - mnOutOffLogicY );
+
+ if(rDeviceRect.IsWidthEmpty())
+ aRetval.SetWidthEmpty();
+
+ if(rDeviceRect.IsHeightEmpty())
+ aRetval.SetHeightEmpty();
+
+ return aRetval;
+}
+
+tools::Polygon OutputDevice::PixelToLogic( const tools::Polygon& rDevicePoly,
+ const MapMode& rMapMode ) const
+{
+
+ // calculate nothing if default-MapMode
+ if ( rMapMode.IsDefault() )
+ return rDevicePoly;
+
+ // calculate MapMode-resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ sal_uInt16 i;
+ sal_uInt16 nPoints = rDevicePoly.GetSize();
+ tools::Polygon aPoly( rDevicePoly );
+
+ // get pointer to Point-array (copy data)
+ const Point* pPointAry = aPoly.GetConstPointAry();
+
+ for ( i = 0; i < nPoints; i++ )
+ {
+ const Point* pPt = &(pPointAry[i]);
+ Point aPt;
+ aPt.setX( ImplPixelToLogic( pPt->X(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ) - aMapRes.mnMapOfsX - mnOutOffLogicX );
+ aPt.setY( ImplPixelToLogic( pPt->Y(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) - aMapRes.mnMapOfsY - mnOutOffLogicY );
+ aPoly[i] = aPt;
+ }
+
+ return aPoly;
+}
+
+basegfx::B2DPolygon OutputDevice::PixelToLogic( const basegfx::B2DPolygon& rPixelPoly,
+ const MapMode& rMapMode ) const
+{
+ basegfx::B2DPolygon aTransformedPoly = rPixelPoly;
+ const basegfx::B2DHomMatrix& rTransformationMatrix = GetInverseViewTransformation( rMapMode );
+ aTransformedPoly.transform( rTransformationMatrix );
+ return aTransformedPoly;
+}
+
+basegfx::B2DPolyPolygon OutputDevice::PixelToLogic( const basegfx::B2DPolyPolygon& rPixelPolyPoly,
+ const MapMode& rMapMode ) const
+{
+ basegfx::B2DPolyPolygon aTransformedPoly = rPixelPolyPoly;
+ const basegfx::B2DHomMatrix& rTransformationMatrix = GetInverseViewTransformation( rMapMode );
+ aTransformedPoly.transform( rTransformationMatrix );
+ return aTransformedPoly;
+}
+
+#define ENTER1( rSource, pMapModeSource, pMapModeDest ) \
+ if ( !pMapModeSource ) \
+ pMapModeSource = &maMapMode; \
+ if ( !pMapModeDest ) \
+ pMapModeDest = &maMapMode; \
+ if ( *pMapModeSource == *pMapModeDest ) \
+ return rSource; \
+ \
+ ImplMapRes aMapResSource; \
+ ImplMapRes aMapResDest; \
+ \
+ if ( !mbMap || pMapModeSource != &maMapMode ) \
+ { \
+ if ( pMapModeSource->GetMapUnit() == MapUnit::MapRelative ) \
+ aMapResSource = maMapRes; \
+ ImplCalcMapResolution( *pMapModeSource, \
+ mnDPIX, mnDPIY, aMapResSource ); \
+ } \
+ else \
+ aMapResSource = maMapRes; \
+ if ( !mbMap || pMapModeDest != &maMapMode ) \
+ { \
+ if ( pMapModeDest->GetMapUnit() == MapUnit::MapRelative ) \
+ aMapResDest = maMapRes; \
+ ImplCalcMapResolution( *pMapModeDest, \
+ mnDPIX, mnDPIY, aMapResDest ); \
+ } \
+ else \
+ aMapResDest = maMapRes
+
+static void verifyUnitSourceDest( MapUnit eUnitSource, MapUnit eUnitDest )
+{
+ DBG_ASSERT( eUnitSource != MapUnit::MapSysFont
+ && eUnitSource != MapUnit::MapAppFont
+ && eUnitSource != MapUnit::MapRelative,
+ "Source MapUnit is not permitted" );
+ DBG_ASSERT( eUnitDest != MapUnit::MapSysFont
+ && eUnitDest != MapUnit::MapAppFont
+ && eUnitDest != MapUnit::MapRelative,
+ "Destination MapUnit is not permitted" );
+}
+
+namespace
+{
+auto getCorrectedUnit(MapUnit eMapSrc, MapUnit eMapDst)
+{
+ o3tl::Length eSrc = o3tl::Length::invalid;
+ o3tl::Length eDst = o3tl::Length::invalid;
+ if (eMapSrc > MapUnit::MapPixel)
+ SAL_WARN("vcl.gdi", "Invalid source map unit");
+ else if (eMapDst > MapUnit::MapPixel)
+ SAL_WARN("vcl.gdi", "Invalid destination map unit");
+ else if (eMapSrc != eMapDst)
+ {
+ // Here 72 PPI is assumed for MapPixel
+ eSrc = MapToO3tlLength(eMapSrc, o3tl::Length::pt);
+ eDst = MapToO3tlLength(eMapDst, o3tl::Length::pt);
+ }
+ return std::make_pair(eSrc, eDst);
+}
+}
+
+#define ENTER4( rMapModeSource, rMapModeDest ) \
+ ImplMapRes aMapResSource; \
+ ImplMapRes aMapResDest; \
+ \
+ ImplCalcMapResolution( rMapModeSource, 72, 72, aMapResSource ); \
+ ImplCalcMapResolution( rMapModeDest, 72, 72, aMapResDest )
+
+// return (n1 * n2 * n3) / (n4 * n5)
+static tools::Long fn5( const tools::Long n1,
+ const tools::Long n2,
+ const tools::Long n3,
+ const tools::Long n4,
+ const tools::Long n5 )
+{
+ if ( n1 == 0 || n2 == 0 || n3 == 0 || n4 == 0 || n5 == 0 )
+ return 0;
+ if (std::numeric_limits<tools::Long>::max() / std::abs(n2) < std::abs(n3))
+ {
+ // a6 is skipped
+ BigInt a7 = n2;
+ a7 *= n3;
+ a7 *= n1;
+
+ if (std::numeric_limits<tools::Long>::max() / std::abs(n4) < std::abs(n5))
+ {
+ BigInt a8 = n4;
+ a8 *= n5;
+
+ BigInt a9 = a8;
+ a9 /= 2;
+ if ( a7.IsNeg() )
+ a7 -= a9;
+ else
+ a7 += a9;
+
+ a7 /= a8;
+ } // of if
+ else
+ {
+ tools::Long n8 = n4 * n5;
+
+ if ( a7.IsNeg() )
+ a7 -= n8 / 2;
+ else
+ a7 += n8 / 2;
+
+ a7 /= n8;
+ } // of else
+ return static_cast<tools::Long>(a7);
+ } // of if
+ else
+ {
+ tools::Long n6 = n2 * n3;
+
+ if (std::numeric_limits<tools::Long>::max() / std::abs(n1) < std::abs(n6))
+ {
+ BigInt a7 = n1;
+ a7 *= n6;
+
+ if (std::numeric_limits<tools::Long>::max() / std::abs(n4) < std::abs(n5))
+ {
+ BigInt a8 = n4;
+ a8 *= n5;
+
+ BigInt a9 = a8;
+ a9 /= 2;
+ if ( a7.IsNeg() )
+ a7 -= a9;
+ else
+ a7 += a9;
+
+ a7 /= a8;
+ } // of if
+ else
+ {
+ tools::Long n8 = n4 * n5;
+
+ if ( a7.IsNeg() )
+ a7 -= n8 / 2;
+ else
+ a7 += n8 / 2;
+
+ a7 /= n8;
+ } // of else
+ return static_cast<tools::Long>(a7);
+ } // of if
+ else
+ {
+ tools::Long n7 = n1 * n6;
+
+ if (std::numeric_limits<tools::Long>::max() / std::abs(n4) < std::abs(n5))
+ {
+ BigInt a7 = n7;
+ BigInt a8 = n4;
+ a8 *= n5;
+
+ BigInt a9 = a8;
+ a9 /= 2;
+ if ( a7.IsNeg() )
+ a7 -= a9;
+ else
+ a7 += a9;
+
+ a7 /= a8;
+ return static_cast<tools::Long>(a7);
+ } // of if
+ else
+ {
+ const tools::Long n8 = n4 * n5;
+ const tools::Long n8_2 = n8 / 2;
+
+ if( n7 < 0 )
+ {
+ if ((n7 - std::numeric_limits<tools::Long>::min()) >= n8_2)
+ n7 -= n8_2;
+ }
+ else if ((std::numeric_limits<tools::Long>::max() - n7) >= n8_2)
+ n7 += n8_2;
+
+ return n7 / n8;
+ } // of else
+ } // of else
+ } // of else
+}
+
+static tools::Long fn3(const tools::Long n1, const o3tl::Length eFrom, const o3tl::Length eTo)
+{
+ if (n1 == 0 || eFrom == o3tl::Length::invalid || eTo == o3tl::Length::invalid)
+ return 0;
+ bool bOverflow;
+ const auto nResult = o3tl::convert(n1, eFrom, eTo, bOverflow);
+ if (bOverflow)
+ {
+ const auto& [n2, n3] = o3tl::getConversionMulDiv(eFrom, eTo);
+ BigInt a4 = n1;
+ a4 *= n2;
+
+ if ( a4.IsNeg() )
+ a4 -= n3 / 2;
+ else
+ a4 += n3 / 2;
+
+ a4 /= n3;
+ return static_cast<tools::Long>(a4);
+ } // of if
+ else
+ return nResult;
+}
+
+Point OutputDevice::LogicToLogic( const Point& rPtSource,
+ const MapMode* pMapModeSource,
+ const MapMode* pMapModeDest ) const
+{
+ ENTER1( rPtSource, pMapModeSource, pMapModeDest );
+
+ return Point( fn5( rPtSource.X() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX,
+ fn5( rPtSource.Y() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY );
+}
+
+Size OutputDevice::LogicToLogic( const Size& rSzSource,
+ const MapMode* pMapModeSource,
+ const MapMode* pMapModeDest ) const
+{
+ ENTER1( rSzSource, pMapModeSource, pMapModeDest );
+
+ return Size( fn5( rSzSource.Width(),
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ),
+ fn5( rSzSource.Height(),
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) );
+}
+
+tools::Rectangle OutputDevice::LogicToLogic( const tools::Rectangle& rRectSource,
+ const MapMode* pMapModeSource,
+ const MapMode* pMapModeDest ) const
+{
+ ENTER1( rRectSource, pMapModeSource, pMapModeDest );
+
+ return tools::Rectangle( fn5( rRectSource.Left() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX,
+ fn5( rRectSource.Top() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY,
+ fn5( rRectSource.Right() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX,
+ fn5( rRectSource.Bottom() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY );
+}
+
+Point OutputDevice::LogicToLogic( const Point& rPtSource,
+ const MapMode& rMapModeSource,
+ const MapMode& rMapModeDest )
+{
+ if ( rMapModeSource == rMapModeDest )
+ return rPtSource;
+
+ MapUnit eUnitSource = rMapModeSource.GetMapUnit();
+ MapUnit eUnitDest = rMapModeDest.GetMapUnit();
+ verifyUnitSourceDest( eUnitSource, eUnitDest );
+
+ if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple())
+ {
+ const auto& [eFrom, eTo] = getCorrectedUnit(eUnitSource, eUnitDest);
+ return Point(fn3(rPtSource.X(), eFrom, eTo), fn3(rPtSource.Y(), eFrom, eTo));
+ }
+ else
+ {
+ ENTER4( rMapModeSource, rMapModeDest );
+
+ return Point( fn5( rPtSource.X() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX,
+ fn5( rPtSource.Y() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY );
+ }
+}
+
+Size OutputDevice::LogicToLogic( const Size& rSzSource,
+ const MapMode& rMapModeSource,
+ const MapMode& rMapModeDest )
+{
+ if ( rMapModeSource == rMapModeDest )
+ return rSzSource;
+
+ MapUnit eUnitSource = rMapModeSource.GetMapUnit();
+ MapUnit eUnitDest = rMapModeDest.GetMapUnit();
+ verifyUnitSourceDest( eUnitSource, eUnitDest );
+
+ if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple())
+ {
+ const auto& [eFrom, eTo] = getCorrectedUnit(eUnitSource, eUnitDest);
+ return Size(fn3(rSzSource.Width(), eFrom, eTo), fn3(rSzSource.Height(), eFrom, eTo));
+ }
+ else
+ {
+ ENTER4( rMapModeSource, rMapModeDest );
+
+ return Size( fn5( rSzSource.Width(),
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ),
+ fn5( rSzSource.Height(),
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) );
+ }
+}
+
+basegfx::B2DPolygon OutputDevice::LogicToLogic( const basegfx::B2DPolygon& rPolySource,
+ const MapMode& rMapModeSource,
+ const MapMode& rMapModeDest )
+{
+ if(rMapModeSource == rMapModeDest)
+ {
+ return rPolySource;
+ }
+
+ const basegfx::B2DHomMatrix aTransform(LogicToLogic(rMapModeSource, rMapModeDest));
+ basegfx::B2DPolygon aPoly(rPolySource);
+
+ aPoly.transform(aTransform);
+ return aPoly;
+}
+
+basegfx::B2DHomMatrix OutputDevice::LogicToLogic(const MapMode& rMapModeSource, const MapMode& rMapModeDest)
+{
+ basegfx::B2DHomMatrix aTransform;
+
+ if(rMapModeSource == rMapModeDest)
+ {
+ return aTransform;
+ }
+
+ MapUnit eUnitSource = rMapModeSource.GetMapUnit();
+ MapUnit eUnitDest = rMapModeDest.GetMapUnit();
+ verifyUnitSourceDest(eUnitSource, eUnitDest);
+
+ if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple())
+ {
+ const auto& [eFrom, eTo] = getCorrectedUnit(eUnitSource, eUnitDest);
+ const double fScaleFactor(eFrom == o3tl::Length::invalid || eTo == o3tl::Length::invalid
+ ? std::numeric_limits<double>::quiet_NaN()
+ : o3tl::convert(1.0, eFrom, eTo));
+ aTransform.set(0, 0, fScaleFactor);
+ aTransform.set(1, 1, fScaleFactor);
+ }
+ else
+ {
+ ENTER4(rMapModeSource, rMapModeDest);
+
+ const double fScaleFactorX((double(aMapResSource.mnMapScNumX) * double(aMapResDest.mnMapScDenomX)) / (double(aMapResSource.mnMapScDenomX) * double(aMapResDest.mnMapScNumX)));
+ const double fScaleFactorY((double(aMapResSource.mnMapScNumY) * double(aMapResDest.mnMapScDenomY)) / (double(aMapResSource.mnMapScDenomY) * double(aMapResDest.mnMapScNumY)));
+ const double fZeroPointX(double(aMapResSource.mnMapOfsX) * fScaleFactorX - double(aMapResDest.mnMapOfsX));
+ const double fZeroPointY(double(aMapResSource.mnMapOfsY) * fScaleFactorY - double(aMapResDest.mnMapOfsY));
+
+ aTransform.set(0, 0, fScaleFactorX);
+ aTransform.set(1, 1, fScaleFactorY);
+ aTransform.set(0, 2, fZeroPointX);
+ aTransform.set(1, 2, fZeroPointY);
+ }
+
+ return aTransform;
+}
+
+tools::Rectangle OutputDevice::LogicToLogic( const tools::Rectangle& rRectSource,
+ const MapMode& rMapModeSource,
+ const MapMode& rMapModeDest )
+{
+ if ( rMapModeSource == rMapModeDest )
+ return rRectSource;
+
+ MapUnit eUnitSource = rMapModeSource.GetMapUnit();
+ MapUnit eUnitDest = rMapModeDest.GetMapUnit();
+ verifyUnitSourceDest( eUnitSource, eUnitDest );
+
+ tools::Rectangle aRetval;
+
+ if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple())
+ {
+ const auto& [eFrom, eTo] = getCorrectedUnit(eUnitSource, eUnitDest);
+
+ auto left = fn3(rRectSource.Left(), eFrom, eTo);
+ auto top = fn3(rRectSource.Top(), eFrom, eTo);
+
+ // tdf#141761 see comments above, IsEmpty() removed
+ auto right = rRectSource.IsWidthEmpty() ? 0 : fn3(rRectSource.Right(), eFrom, eTo);
+ auto bottom = rRectSource.IsHeightEmpty() ? 0 : fn3(rRectSource.Bottom(), eFrom, eTo);
+
+ aRetval = tools::Rectangle(left, top, right, bottom);
+ }
+ else
+ {
+ ENTER4( rMapModeSource, rMapModeDest );
+
+ auto left = fn5( rRectSource.Left() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX;
+ auto top = fn5( rRectSource.Top() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY;
+
+ // tdf#141761 see comments above, IsEmpty() removed
+ auto right = rRectSource.IsWidthEmpty() ? 0 : fn5( rRectSource.Right() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX;
+ auto bottom = rRectSource.IsHeightEmpty() ? 0 : fn5( rRectSource.Bottom() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY;
+
+ aRetval = tools::Rectangle(left, top, right, bottom);
+ }
+
+ if(rRectSource.IsWidthEmpty())
+ aRetval.SetWidthEmpty();
+
+ if(rRectSource.IsHeightEmpty())
+ aRetval.SetHeightEmpty();
+
+ return aRetval;
+}
+
+tools::Long OutputDevice::LogicToLogic( tools::Long nLongSource,
+ MapUnit eUnitSource, MapUnit eUnitDest )
+{
+ if ( eUnitSource == eUnitDest )
+ return nLongSource;
+
+ verifyUnitSourceDest( eUnitSource, eUnitDest );
+ const auto& [eFrom, eTo] = getCorrectedUnit(eUnitSource, eUnitDest);
+ return fn3(nLongSource, eFrom, eTo);
+}
+
+void OutputDevice::SetPixelOffset( const Size& rOffset )
+{
+ mnOutOffOrigX = rOffset.Width();
+ mnOutOffOrigY = rOffset.Height();
+
+ mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX );
+ mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetPixelOffset( rOffset );
+}
+
+
+DeviceCoordinate OutputDevice::LogicWidthToDeviceCoordinate( tools::Long nWidth ) const
+{
+ if ( !mbMap )
+ return static_cast<DeviceCoordinate>(nWidth);
+
+#if VCL_FLOAT_DEVICE_PIXEL
+ return ImplLogicToPixel(static_cast<double>(nWidth), mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX);
+#else
+ return ImplLogicToPixel(nWidth, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX);
+#endif
+}
+
+double OutputDevice::ImplLogicWidthToDeviceFontWidth(tools::Long nWidth) const
+{
+ if (!mbMap)
+ return nWidth;
+
+ return ImplLogicToPixel(static_cast<double>(nWidth), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX);
+}
+
+DevicePoint OutputDevice::ImplLogicToDeviceFontCoordinate(const Point& rPoint) const
+{
+ if (!mbMap)
+ return DevicePoint(rPoint.X() + mnOutOffX, rPoint.Y() + mnOutOffY);
+
+ return DevicePoint(ImplLogicToPixel(static_cast<double>(rPoint.X() + maMapRes.mnMapOfsX), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX)
+ + mnOutOffX + mnOutOffOrigX,
+ ImplLogicToPixel(static_cast<double>(rPoint.Y() + maMapRes.mnMapOfsY), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY)
+ + mnOutOffY + mnOutOffOrigY);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/mask.cxx b/vcl/source/outdev/mask.cxx
new file mode 100644
index 000000000..c1c3e3b01
--- /dev/null
+++ b/vcl/source/outdev/mask.cxx
@@ -0,0 +1,164 @@
+/* -*- 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 <cassert>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+#include <salbmp.hxx>
+
+void OutputDevice::DrawMask( const Point& rDestPt,
+ const Bitmap& rBitmap, const Color& rMaskColor )
+{
+ assert(!is_double_buffered_window());
+
+ const Size aSizePix( rBitmap.GetSizePixel() );
+ DrawMask( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmap, rMaskColor, MetaActionType::MASK );
+}
+
+void OutputDevice::DrawMask( const Point& rDestPt, const Size& rDestSize,
+ const Bitmap& rBitmap, const Color& rMaskColor )
+{
+ assert(!is_double_buffered_window());
+
+ DrawMask( rDestPt, rDestSize, Point(), rBitmap.GetSizePixel(), rBitmap, rMaskColor, MetaActionType::MASKSCALE );
+}
+
+void OutputDevice::DrawMask( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ const Bitmap& rBitmap, const Color& rMaskColor)
+{
+
+ assert(!is_double_buffered_window());
+
+ DrawMask( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmap, rMaskColor, MetaActionType::MASKSCALEPART );
+}
+
+void OutputDevice::DrawMask( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ const Bitmap& rBitmap, const Color& rMaskColor,
+ const MetaActionType nAction )
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if( RasterOp::Invert == meRasterOp )
+ {
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ return;
+ }
+
+ if ( mpMetaFile )
+ {
+ switch( nAction )
+ {
+ case MetaActionType::MASK:
+ mpMetaFile->AddAction( new MetaMaskAction( rDestPt,
+ rBitmap, rMaskColor ) );
+ break;
+
+ case MetaActionType::MASKSCALE:
+ mpMetaFile->AddAction( new MetaMaskScaleAction( rDestPt,
+ rDestSize, rBitmap, rMaskColor ) );
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ mpMetaFile->AddAction( new MetaMaskScalePartAction( rDestPt, rDestSize,
+ rSrcPtPixel, rSrcSizePixel, rBitmap, rMaskColor ) );
+ break;
+
+ default: break;
+ }
+ }
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ DrawDeviceMask( rBitmap, rMaskColor, rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel );
+
+}
+
+void OutputDevice::DrawDeviceMask( const Bitmap& rMask, const Color& rMaskColor,
+ const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel )
+{
+ assert(!is_double_buffered_window());
+
+ const std::shared_ptr<SalBitmap>& xImpBmp = rMask.ImplGetSalBitmap();
+ if (xImpBmp)
+ {
+ SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(),
+ ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()),
+ ImplLogicWidthToDevicePixel(rDestSize.Width()),
+ ImplLogicHeightToDevicePixel(rDestSize.Height()));
+
+ // we don't want to mirror via coordinates
+ const BmpMirrorFlags nMirrFlags = AdjustTwoRect( aPosAry, xImpBmp->GetSize() );
+
+ // check if output is necessary
+ if( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight )
+ {
+
+ if( nMirrFlags != BmpMirrorFlags::NONE )
+ {
+ Bitmap aTmp( rMask );
+ aTmp.Mirror( nMirrFlags );
+ mpGraphics->DrawMask( aPosAry, *aTmp.ImplGetSalBitmap(),
+ rMaskColor, *this);
+ }
+ else
+ mpGraphics->DrawMask( aPosAry, *xImpBmp, rMaskColor, *this );
+
+ }
+ }
+
+ // TODO: Use mask here
+ if( !mpAlphaVDev )
+ return;
+
+ const Bitmap& rAlphaMask( rMask.CreateMask( rMaskColor ) );
+
+ // #i25167# Restrict mask painting to _opaque_ areas
+ // of the mask, otherwise we spoil areas where no
+ // bitmap content was ever visible. Interestingly
+ // enough, this can be achieved by taking the mask as
+ // the transparency mask of itself
+ mpAlphaVDev->DrawBitmapEx( rDestPt,
+ rDestSize,
+ rSrcPtPixel,
+ rSrcSizePixel,
+ BitmapEx( rAlphaMask, rMask ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/nativecontrols.cxx b/vcl/source/outdev/nativecontrols.cxx
new file mode 100644
index 000000000..a8557259a
--- /dev/null
+++ b/vcl/source/outdev/nativecontrols.cxx
@@ -0,0 +1,326 @@
+/* -*- 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 <cassert>
+
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+#include <sal/log.hxx>
+#include <toolbarvalue.hxx>
+
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/pdfextoutdevdata.hxx>
+
+#include <salgdi.hxx>
+
+ImplControlValue::~ImplControlValue()
+{
+}
+
+ImplControlValue* ImplControlValue::clone() const
+{
+ assert( typeid( const ImplControlValue ) == typeid( *this ));
+ return new ImplControlValue( *this );
+}
+
+ScrollbarValue::~ScrollbarValue()
+{
+}
+
+ScrollbarValue* ScrollbarValue::clone() const
+{
+ assert( typeid( const ScrollbarValue ) == typeid( *this ));
+ return new ScrollbarValue( *this );
+}
+
+SliderValue::~SliderValue()
+{
+}
+
+SliderValue* SliderValue::clone() const
+{
+ assert( typeid( const SliderValue ) == typeid( *this ));
+ return new SliderValue( *this );
+}
+
+int TabPaneValue::m_nOverlap = 0;
+
+TabPaneValue* TabPaneValue::clone() const
+{
+ assert(typeid(const TabPaneValue) == typeid(*this));
+ return new TabPaneValue(*this);
+}
+
+TabitemValue::~TabitemValue()
+{
+}
+
+TabitemValue* TabitemValue::clone() const
+{
+ assert( typeid( const TabitemValue ) == typeid( *this ));
+ return new TabitemValue( *this );
+}
+
+SpinbuttonValue::~SpinbuttonValue()
+{
+}
+
+SpinbuttonValue* SpinbuttonValue::clone() const
+{
+ assert( typeid( const SpinbuttonValue ) == typeid( *this ));
+ return new SpinbuttonValue( *this );
+}
+
+ToolbarValue::~ToolbarValue()
+{
+}
+
+ToolbarValue* ToolbarValue::clone() const
+{
+ assert( typeid( const ToolbarValue ) == typeid( *this ));
+ return new ToolbarValue( *this );
+}
+
+MenubarValue::~MenubarValue()
+{
+}
+
+MenubarValue* MenubarValue::clone() const
+{
+ assert( typeid( const MenubarValue ) == typeid( *this ));
+ return new MenubarValue( *this );
+}
+
+MenupopupValue::~MenupopupValue()
+{
+}
+
+MenupopupValue* MenupopupValue::clone() const
+{
+ assert( typeid( const MenupopupValue ) == typeid( *this ));
+ return new MenupopupValue( *this );
+}
+
+PushButtonValue::~PushButtonValue()
+{
+}
+
+PushButtonValue* PushButtonValue::clone() const
+{
+ assert( typeid( const PushButtonValue ) == typeid( *this ));
+ return new PushButtonValue( *this );
+}
+
+// These functions are mainly passthrough functions that allow access to
+// the SalFrame behind a Window object for native widget rendering purposes.
+
+bool OutputDevice::IsNativeControlSupported( ControlType nType, ControlPart nPart ) const
+{
+ if( !CanEnableNativeWidget() )
+ return false;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+
+ return mpGraphics->IsNativeControlSupported(nType, nPart);
+}
+
+bool OutputDevice::HitTestNativeScrollbar(
+ ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ const Point& aPos,
+ bool& rIsInside ) const
+{
+ if( !CanEnableNativeWidget() )
+ return false;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+
+ Point aWinOffs( mnOutOffX, mnOutOffY );
+ tools::Rectangle screenRegion( rControlRegion );
+ screenRegion.Move( aWinOffs.X(), aWinOffs.Y());
+
+ return mpGraphics->HitTestNativeScrollbar( nPart, screenRegion, Point( aPos.X() + mnOutOffX, aPos.Y() + mnOutOffY ),
+ rIsInside, *this );
+}
+
+static std::unique_ptr< ImplControlValue > TransformControlValue( const ImplControlValue& rVal, const OutputDevice& rDev )
+{
+ std::unique_ptr< ImplControlValue > aResult;
+ switch( rVal.getType() )
+ {
+ case ControlType::Slider:
+ {
+ const SliderValue* pSlVal = static_cast<const SliderValue*>(&rVal);
+ SliderValue* pNew = new SliderValue( *pSlVal );
+ aResult.reset( pNew );
+ pNew->maThumbRect = rDev.ImplLogicToDevicePixel( pSlVal->maThumbRect );
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ const ScrollbarValue* pScVal = static_cast<const ScrollbarValue*>(&rVal);
+ ScrollbarValue* pNew = new ScrollbarValue( *pScVal );
+ aResult.reset( pNew );
+ pNew->maThumbRect = rDev.ImplLogicToDevicePixel( pScVal->maThumbRect );
+ pNew->maButton1Rect = rDev.ImplLogicToDevicePixel( pScVal->maButton1Rect );
+ pNew->maButton2Rect = rDev.ImplLogicToDevicePixel( pScVal->maButton2Rect );
+ }
+ break;
+ case ControlType::SpinButtons:
+ {
+ const SpinbuttonValue* pSpVal = static_cast<const SpinbuttonValue*>(&rVal);
+ SpinbuttonValue* pNew = new SpinbuttonValue( *pSpVal );
+ aResult.reset( pNew );
+ pNew->maUpperRect = rDev.ImplLogicToDevicePixel( pSpVal->maUpperRect );
+ pNew->maLowerRect = rDev.ImplLogicToDevicePixel( pSpVal->maLowerRect );
+ }
+ break;
+ case ControlType::Toolbar:
+ {
+ const ToolbarValue* pTVal = static_cast<const ToolbarValue*>(&rVal);
+ ToolbarValue* pNew = new ToolbarValue( *pTVal );
+ aResult.reset( pNew );
+ pNew->maGripRect = rDev.ImplLogicToDevicePixel( pTVal->maGripRect );
+ }
+ break;
+ case ControlType::TabPane:
+ {
+ const TabPaneValue* pTIVal = static_cast<const TabPaneValue*>(&rVal);
+ TabPaneValue* pNew = new TabPaneValue(*pTIVal);
+ pNew->m_aTabHeaderRect = rDev.ImplLogicToDevicePixel(pTIVal->m_aTabHeaderRect);
+ pNew->m_aSelectedTabRect = rDev.ImplLogicToDevicePixel(pTIVal->m_aSelectedTabRect);
+ aResult.reset(pNew);
+ }
+ break;
+ case ControlType::TabItem:
+ {
+ const TabitemValue* pTIVal = static_cast<const TabitemValue*>(&rVal);
+ TabitemValue* pNew = new TabitemValue( *pTIVal );
+ pNew->maContentRect = rDev.ImplLogicToDevicePixel(pTIVal->maContentRect);
+ aResult.reset( pNew );
+ }
+ break;
+ case ControlType::Menubar:
+ {
+ const MenubarValue* pMVal = static_cast<const MenubarValue*>(&rVal);
+ MenubarValue* pNew = new MenubarValue( *pMVal );
+ aResult.reset( pNew );
+ }
+ break;
+ case ControlType::Pushbutton:
+ {
+ const PushButtonValue* pBVal = static_cast<const PushButtonValue*>(&rVal);
+ PushButtonValue* pNew = new PushButtonValue( *pBVal );
+ aResult.reset( pNew );
+ }
+ break;
+ case ControlType::Generic:
+ aResult = std::make_unique<ImplControlValue>( rVal );
+ break;
+ case ControlType::MenuPopup:
+ {
+ const MenupopupValue* pMVal = static_cast<const MenupopupValue*>(&rVal);
+ MenupopupValue* pNew = new MenupopupValue( *pMVal );
+ pNew->maItemRect = rDev.ImplLogicToDevicePixel( pMVal->maItemRect );
+ aResult.reset( pNew );
+ }
+ break;
+ default:
+ std::abort();
+ break;
+ }
+ return aResult;
+}
+bool OutputDevice::DrawNativeControl( ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ ControlState nState,
+ const ImplControlValue& aValue,
+ const OUString& aCaption,
+ const Color& rBackgroundColor )
+{
+ assert(!is_double_buffered_window());
+
+ if( !CanEnableNativeWidget() )
+ return false;
+
+ // make sure the current clip region is initialized correctly
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+ if ( mbOutputClipped )
+ return true;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ // Convert the coordinates from relative to Window-absolute, so we draw
+ // in the correct place in platform code
+ std::unique_ptr< ImplControlValue > aScreenCtrlValue( TransformControlValue( aValue, *this ) );
+ tools::Rectangle screenRegion( ImplLogicToDevicePixel( rControlRegion ) );
+
+ bool bRet = mpGraphics->DrawNativeControl(nType, nPart, screenRegion, nState, *aScreenCtrlValue, aCaption, *this, rBackgroundColor);
+
+ return bRet;
+}
+
+bool OutputDevice::GetNativeControlRegion( ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ ControlState nState,
+ const ImplControlValue& aValue,
+ tools::Rectangle &rNativeBoundingRegion,
+ tools::Rectangle &rNativeContentRegion ) const
+{
+ if( !CanEnableNativeWidget() )
+ return false;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+
+ // Convert the coordinates from relative to Window-absolute, so we draw
+ // in the correct place in platform code
+ std::unique_ptr< ImplControlValue > aScreenCtrlValue( TransformControlValue( aValue, *this ) );
+ tools::Rectangle screenRegion( ImplLogicToDevicePixel( rControlRegion ) );
+
+ bool bRet = mpGraphics->GetNativeControlRegion(nType, nPart, screenRegion, nState, *aScreenCtrlValue,
+ rNativeBoundingRegion,
+ rNativeContentRegion, *this );
+ if( bRet )
+ {
+ // transform back native regions
+ rNativeBoundingRegion = ImplDevicePixelToLogic( rNativeBoundingRegion );
+ rNativeContentRegion = ImplDevicePixelToLogic( rNativeContentRegion );
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/outdev.cxx b/vcl/source/outdev/outdev.cxx
new file mode 100644
index 000000000..e831cf746
--- /dev/null
+++ b/vcl/source/outdev/outdev.cxx
@@ -0,0 +1,836 @@
+/* -*- 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 <sal/log.hxx>
+
+#include <tools/debug.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/lazydelete.hxx>
+#include <comphelper/processfactory.hxx>
+
+#include <ImplOutDevData.hxx>
+#include <font/PhysicalFontFaceCollection.hxx>
+#include <salgdi.hxx>
+#include <window.h>
+
+#include <com/sun/star/awt/DeviceCapability.hpp>
+#include <com/sun/star/awt/XWindow.hpp>
+#include <com/sun/star/rendering/CanvasFactory.hpp>
+#include <com/sun/star/rendering/XSpriteCanvas.hpp>
+
+#ifdef DISABLE_DYNLOADING
+// Linking all needed LO code into one .so/executable, these already
+// exist in the tools library, so put them in the anonymous namespace
+// here to avoid clash...
+namespace {
+#endif
+#ifdef DISABLE_DYNLOADING
+}
+#endif
+
+using namespace ::com::sun::star::uno;
+
+// Begin initializer and accessor public functions
+
+OutputDevice::OutputDevice(OutDevType eOutDevType) :
+ meOutDevType(eOutDevType),
+ maRegion(true),
+ maFillColor( COL_WHITE ),
+ maTextLineColor( COL_TRANSPARENT ),
+ mxSettings( new AllSettings(Application::GetSettings()) )
+{
+ mpGraphics = nullptr;
+ mpUnoGraphicsList = nullptr;
+ mpPrevGraphics = nullptr;
+ mpNextGraphics = nullptr;
+ mpMetaFile = nullptr;
+ mpFontInstance = nullptr;
+ mpFontFaceCollection = nullptr;
+ mpAlphaVDev = nullptr;
+ mpExtOutDevData = nullptr;
+ mnOutOffX = 0;
+ mnOutOffY = 0;
+ mnOutWidth = 0;
+ mnOutHeight = 0;
+ mnDPIX = 0;
+ mnDPIY = 0;
+ mnDPIScalePercentage = 100;
+ mnTextOffX = 0;
+ mnTextOffY = 0;
+ mnOutOffOrigX = 0;
+ mnOutOffLogicX = 0;
+ mnOutOffOrigY = 0;
+ mnOutOffLogicY = 0;
+ mnEmphasisAscent = 0;
+ mnEmphasisDescent = 0;
+ mnDrawMode = DrawModeFlags::Default;
+ mnTextLayoutMode = vcl::text::ComplexTextLayoutFlags::Default;
+
+ if( AllSettings::GetLayoutRTL() ) //#i84553# tip BiDi preference to RTL
+ mnTextLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
+
+ meOutDevViewType = OutDevViewType::DontKnow;
+ mbMap = false;
+ mbClipRegion = false;
+ mbBackground = false;
+ mbOutput = true;
+ mbDevOutput = false;
+ mbOutputClipped = false;
+ maTextColor = COL_BLACK;
+ maOverlineColor = COL_TRANSPARENT;
+ meRasterOp = RasterOp::OverPaint;
+ mnAntialiasing = AntialiasingFlags::NONE;
+ meTextLanguage = LANGUAGE_SYSTEM; // TODO: get default from configuration?
+ mbTextRenderModeForResolutionIndependentLayout = false;
+ mbLineColor = true;
+ mbFillColor = true;
+ mbInitLineColor = true;
+ mbInitFillColor = true;
+ mbInitFont = true;
+ mbInitTextColor = true;
+ mbInitClipRegion = true;
+ mbClipRegionSet = false;
+ mbNewFont = true;
+ mbTextLines = false;
+ mbTextSpecial = false;
+ mbRefPoint = false;
+ mbEnableRTL = false; // mirroring must be explicitly allowed (typically for windows only)
+
+ // struct ImplMapRes
+ maMapRes.mnMapOfsX = 0;
+ maMapRes.mnMapOfsY = 0;
+ maMapRes.mnMapScNumX = 1;
+ maMapRes.mnMapScNumY = 1;
+ maMapRes.mnMapScDenomX = 1;
+ maMapRes.mnMapScDenomY = 1;
+
+ // struct ImplOutDevData- see #i82615#
+ mpOutDevData.reset(new ImplOutDevData);
+ mpOutDevData->mpRotateDev = nullptr;
+ mpOutDevData->mpRecordLayout = nullptr;
+
+ // #i75163#
+ mpOutDevData->mpViewTransform = nullptr;
+ mpOutDevData->mpInverseViewTransform = nullptr;
+}
+
+OutputDevice::~OutputDevice()
+{
+ disposeOnce();
+}
+
+void OutputDevice::dispose()
+{
+ if ( GetUnoGraphicsList() )
+ {
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper( false );
+ if ( pWrapper )
+ pWrapper->ReleaseAllGraphics( this );
+ delete mpUnoGraphicsList;
+ mpUnoGraphicsList = nullptr;
+ }
+
+ mpOutDevData->mpRotateDev.disposeAndClear();
+
+ // #i75163#
+ ImplInvalidateViewTransform();
+
+ mpOutDevData.reset();
+
+ // for some reason, we haven't removed state from the stack properly
+ if ( !maOutDevStateStack.empty() )
+ SAL_WARN( "vcl.gdi", "OutputDevice::~OutputDevice(): OutputDevice::Push() calls != OutputDevice::Pop() calls" );
+ maOutDevStateStack.clear();
+
+ // release the active font instance
+ mpFontInstance.clear();
+
+ // remove cached results of GetDevFontList/GetDevSizeList
+ mpFontFaceCollection.reset();
+
+ // release ImplFontCache specific to this OutputDevice
+ mxFontCache.reset();
+
+ // release ImplFontList specific to this OutputDevice
+ mxFontCollection.reset();
+
+ mpAlphaVDev.disposeAndClear();
+ mpPrevGraphics.clear();
+ mpNextGraphics.clear();
+ VclReferenceBase::dispose();
+}
+
+bool OutputDevice::IsVirtual() const
+{
+ return false;
+}
+
+SalGraphics* OutputDevice::GetGraphics()
+{
+ DBG_TESTSOLARMUTEX();
+
+ if (!mpGraphics && !AcquireGraphics())
+ SAL_WARN("vcl.gdi", "No mpGraphics set");
+
+ return mpGraphics;
+}
+
+SalGraphics const *OutputDevice::GetGraphics() const
+{
+ DBG_TESTSOLARMUTEX();
+
+ if (!mpGraphics && !AcquireGraphics())
+ SAL_WARN("vcl.gdi", "No mpGraphics set");
+
+ return mpGraphics;
+}
+
+void OutputDevice::SetConnectMetaFile( GDIMetaFile* pMtf )
+{
+ mpMetaFile = pMtf;
+}
+
+void OutputDevice::SetSettings( const AllSettings& rSettings )
+{
+ *mxSettings = rSettings;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetSettings( rSettings );
+}
+
+SystemGraphicsData OutputDevice::GetSystemGfxData() const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return SystemGraphicsData();
+ assert(mpGraphics);
+
+ return mpGraphics->GetGraphicsData();
+}
+
+OUString OutputDevice::GetRenderBackendName() const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return {};
+ assert(mpGraphics);
+
+ return mpGraphics->getRenderBackendName();
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool OutputDevice::SupportsCairo() const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return false;
+ assert(mpGraphics);
+
+ return mpGraphics->SupportsCairo();
+}
+
+cairo::SurfaceSharedPtr OutputDevice::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return cairo::SurfaceSharedPtr();
+ assert(mpGraphics);
+ return mpGraphics->CreateSurface(rSurface);
+}
+
+cairo::SurfaceSharedPtr OutputDevice::CreateSurface(int x, int y, int width, int height) const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return cairo::SurfaceSharedPtr();
+ assert(mpGraphics);
+ return mpGraphics->CreateSurface(*this, x, y, width, height);
+}
+
+cairo::SurfaceSharedPtr OutputDevice::CreateBitmapSurface(const BitmapSystemData& rData, const Size& rSize) const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return cairo::SurfaceSharedPtr();
+ assert(mpGraphics);
+ return mpGraphics->CreateBitmapSurface(*this, rData, rSize);
+}
+
+css::uno::Any OutputDevice::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& rSurface, const basegfx::B2ISize& rSize) const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return css::uno::Any();
+ assert(mpGraphics);
+ return mpGraphics->GetNativeSurfaceHandle(rSurface, rSize);
+}
+
+#endif // ENABLE_CAIRO_CANVAS
+
+css::uno::Any OutputDevice::GetSystemGfxDataAny() const
+{
+ const SystemGraphicsData aSysData = GetSystemGfxData();
+ css::uno::Sequence< sal_Int8 > aSeq( reinterpret_cast<sal_Int8 const *>(&aSysData),
+ aSysData.nSize );
+
+ return css::uno::Any(aSeq);
+}
+
+void OutputDevice::SetRefPoint()
+{
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaRefPointAction( Point(), false ) );
+
+ mbRefPoint = false;
+ maRefPoint.setX(0);
+ maRefPoint.setY(0);
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetRefPoint();
+}
+
+void OutputDevice::SetRefPoint( const Point& rRefPoint )
+{
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaRefPointAction( rRefPoint, true ) );
+
+ mbRefPoint = true;
+ maRefPoint = rRefPoint;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetRefPoint( rRefPoint );
+}
+
+void OutputDevice::SetRasterOp( RasterOp eRasterOp )
+{
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaRasterOpAction( eRasterOp ) );
+
+ if ( meRasterOp != eRasterOp )
+ {
+ meRasterOp = eRasterOp;
+ mbInitLineColor = mbInitFillColor = true;
+
+ if( mpGraphics || AcquireGraphics() )
+ {
+ assert(mpGraphics);
+ mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp), RasterOp::Invert == meRasterOp );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetRasterOp( eRasterOp );
+}
+
+void OutputDevice::EnableOutput( bool bEnable )
+{
+ mbOutput = bEnable;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->EnableOutput( bEnable );
+}
+
+void OutputDevice::SetAntialiasing( AntialiasingFlags nMode )
+{
+ if ( mnAntialiasing != nMode )
+ {
+ mnAntialiasing = nMode;
+ mbInitFont = true;
+
+ if (mpGraphics)
+ mpGraphics->setAntiAlias(bool(mnAntialiasing & AntialiasingFlags::Enable));
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetAntialiasing( nMode );
+}
+
+void OutputDevice::SetTextRenderModeForResolutionIndependentLayout(bool bMode)
+{
+ if (mbTextRenderModeForResolutionIndependentLayout!= bMode)
+ {
+ mbTextRenderModeForResolutionIndependentLayout = bMode;
+
+ if (mpGraphics)
+ mpGraphics->setTextRenderModeForResolutionIndependentLayout(bMode);
+ }
+
+ if (mpAlphaVDev)
+ mpAlphaVDev->SetTextRenderModeForResolutionIndependentLayout(bMode);
+}
+
+void OutputDevice::SetDrawMode(DrawModeFlags nDrawMode)
+{
+ mnDrawMode = nDrawMode;
+
+ if (mpAlphaVDev)
+ mpAlphaVDev->SetDrawMode(nDrawMode);
+}
+
+sal_uInt16 OutputDevice::GetBitCount() const
+{
+ // we need a graphics instance
+ if ( !mpGraphics && !AcquireGraphics() )
+ return 0;
+ assert(mpGraphics);
+
+ return mpGraphics->GetBitCount();
+}
+
+void OutputDevice::SetOutOffXPixel(tools::Long nOutOffX)
+{
+ mnOutOffX = nOutOffX;
+}
+
+void OutputDevice::SetOutOffYPixel(tools::Long nOutOffY)
+{
+ mnOutOffY = nOutOffY;
+}
+
+css::uno::Reference< css::awt::XGraphics > OutputDevice::CreateUnoGraphics()
+{
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper();
+ return pWrapper ? pWrapper->CreateGraphics( this ) : css::uno::Reference< css::awt::XGraphics >();
+}
+
+std::vector< VCLXGraphics* > *OutputDevice::CreateUnoGraphicsList()
+{
+ mpUnoGraphicsList = new std::vector< VCLXGraphics* >;
+ return mpUnoGraphicsList;
+}
+
+// Helper public function
+
+bool OutputDevice::SupportsOperation( OutDevSupportType eType ) const
+{
+ if( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+ const bool bHasSupport = mpGraphics->supportsOperation( eType );
+ return bHasSupport;
+}
+
+// Direct OutputDevice drawing public functions
+
+void OutputDevice::DrawOutDev( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPt, const Size& rSrcSize )
+{
+ if( ImplIsRecordLayout() )
+ return;
+
+ if ( RasterOp::Invert == meRasterOp )
+ {
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ return;
+ }
+
+ if ( mpMetaFile )
+ {
+ const Bitmap aBmp( GetBitmap( rSrcPt, rSrcSize ) );
+ mpMetaFile->AddAction( new MetaBmpScaleAction( rDestPt, rDestSize, aBmp ) );
+ }
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ tools::Long nSrcWidth = ImplLogicWidthToDevicePixel( rSrcSize.Width() );
+ tools::Long nSrcHeight = ImplLogicHeightToDevicePixel( rSrcSize.Height() );
+ tools::Long nDestWidth = ImplLogicWidthToDevicePixel( rDestSize.Width() );
+ tools::Long nDestHeight = ImplLogicHeightToDevicePixel( rDestSize.Height() );
+
+ if (nSrcWidth && nSrcHeight && nDestWidth && nDestHeight)
+ {
+ SalTwoRect aPosAry(ImplLogicXToDevicePixel(rSrcPt.X()), ImplLogicYToDevicePixel(rSrcPt.Y()),
+ nSrcWidth, nSrcHeight,
+ ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()),
+ nDestWidth, nDestHeight);
+
+ AdjustTwoRect( aPosAry, GetOutputRectPixel() );
+
+ if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight )
+ mpGraphics->CopyBits(aPosAry, *this);
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawOutDev( rDestPt, rDestSize, rSrcPt, rSrcSize );
+}
+
+void OutputDevice::DrawOutDev( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPt, const Size& rSrcSize,
+ const OutputDevice& rOutDev )
+{
+ if ( ImplIsRecordLayout() )
+ return;
+
+ if ( RasterOp::Invert == meRasterOp )
+ {
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ return;
+ }
+
+ if ( mpMetaFile )
+ {
+ if (rOutDev.mpAlphaVDev)
+ {
+ const BitmapEx aBmpEx(rOutDev.GetBitmapEx(rSrcPt, rSrcSize));
+ mpMetaFile->AddAction(new MetaBmpExScaleAction(rDestPt, rDestSize, aBmpEx));
+ }
+ else
+ {
+ const Bitmap aBmp(rOutDev.GetBitmap(rSrcPt, rSrcSize));
+ mpMetaFile->AddAction(new MetaBmpScaleAction(rDestPt, rDestSize, aBmp));
+ }
+ }
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if (rOutDev.mpAlphaVDev)
+ {
+ // alpha-blend source over destination
+ DrawBitmapEx(rDestPt, rDestSize, rOutDev.GetBitmapEx(rSrcPt, rSrcSize));
+ }
+ else
+ {
+ SalTwoRect aPosAry(rOutDev.ImplLogicXToDevicePixel(rSrcPt.X()),
+ rOutDev.ImplLogicYToDevicePixel(rSrcPt.Y()),
+ rOutDev.ImplLogicWidthToDevicePixel(rSrcSize.Width()),
+ rOutDev.ImplLogicHeightToDevicePixel(rSrcSize.Height()),
+ ImplLogicXToDevicePixel(rDestPt.X()),
+ ImplLogicYToDevicePixel(rDestPt.Y()),
+ ImplLogicWidthToDevicePixel(rDestSize.Width()),
+ ImplLogicHeightToDevicePixel(rDestSize.Height()));
+
+ drawOutDevDirect(rOutDev, aPosAry);
+
+ // #i32109#: make destination rectangle opaque - source has no alpha
+ if (mpAlphaVDev)
+ mpAlphaVDev->ImplFillOpaqueRectangle(tools::Rectangle(rDestPt, rDestSize));
+ }
+}
+
+void OutputDevice::CopyArea( const Point& rDestPt,
+ const Point& rSrcPt, const Size& rSrcSize,
+ bool bWindowInvalidate )
+{
+ if ( ImplIsRecordLayout() )
+ return;
+
+ RasterOp eOldRop = GetRasterOp();
+ SetRasterOp( RasterOp::OverPaint );
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ tools::Long nSrcWidth = ImplLogicWidthToDevicePixel( rSrcSize.Width() );
+ tools::Long nSrcHeight = ImplLogicHeightToDevicePixel( rSrcSize.Height() );
+ if (nSrcWidth && nSrcHeight)
+ {
+ SalTwoRect aPosAry(ImplLogicXToDevicePixel(rSrcPt.X()), ImplLogicYToDevicePixel(rSrcPt.Y()),
+ nSrcWidth, nSrcHeight,
+ ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()),
+ nSrcWidth, nSrcHeight);
+
+ AdjustTwoRect( aPosAry, GetOutputRectPixel() );
+
+ CopyDeviceArea( aPosAry, bWindowInvalidate );
+ }
+
+ SetRasterOp( eOldRop );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->CopyArea( rDestPt, rSrcPt, rSrcSize, bWindowInvalidate );
+}
+
+// Direct OutputDevice drawing protected function
+
+void OutputDevice::CopyDeviceArea( SalTwoRect& aPosAry, bool /*bWindowInvalidate*/)
+{
+ if (aPosAry.mnSrcWidth == 0 || aPosAry.mnSrcHeight == 0 || aPosAry.mnDestWidth == 0 || aPosAry.mnDestHeight == 0)
+ return;
+
+ aPosAry.mnDestWidth = aPosAry.mnSrcWidth;
+ aPosAry.mnDestHeight = aPosAry.mnSrcHeight;
+ mpGraphics->CopyBits(aPosAry, *this);
+}
+
+// Direct OutputDevice drawing private function
+void OutputDevice::drawOutDevDirect(const OutputDevice& rSrcDev, SalTwoRect& rPosAry)
+{
+ SalGraphics* pSrcGraphics;
+ if (const OutputDevice* pCheckedSrc = DrawOutDevDirectCheck(rSrcDev))
+ {
+ if (!pCheckedSrc->mpGraphics && !pCheckedSrc->AcquireGraphics())
+ return;
+ pSrcGraphics = pCheckedSrc->mpGraphics;
+ }
+ else
+ pSrcGraphics = nullptr;
+
+ if (!mpGraphics && !AcquireGraphics())
+ return;
+ assert(mpGraphics);
+
+ // #102532# Offset only has to be pseudo window offset
+
+ AdjustTwoRect( rPosAry, rSrcDev.GetOutputRectPixel() );
+
+ if ( rPosAry.mnSrcWidth && rPosAry.mnSrcHeight && rPosAry.mnDestWidth && rPosAry.mnDestHeight )
+ {
+ // if this is no window, but rSrcDev is a window
+ // mirroring may be required
+ // because only windows have a SalGraphicsLayout
+ // mirroring is performed here
+ DrawOutDevDirectProcess(rSrcDev, rPosAry, pSrcGraphics);
+ }
+}
+
+const OutputDevice* OutputDevice::DrawOutDevDirectCheck(const OutputDevice& rSrcDev) const
+{
+ return this == &rSrcDev ? nullptr : &rSrcDev;
+}
+
+void OutputDevice::DrawOutDevDirectProcess(const OutputDevice& rSrcDev, SalTwoRect& rPosAry, SalGraphics* pSrcGraphics)
+{
+ if( pSrcGraphics && (pSrcGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ pSrcGraphics->mirror( aPosAry2.mnSrcX, aPosAry2.mnSrcWidth, rSrcDev );
+ mpGraphics->CopyBits( aPosAry2, *pSrcGraphics, *this, rSrcDev );
+ return;
+ }
+ if (pSrcGraphics)
+ mpGraphics->CopyBits( rPosAry, *pSrcGraphics, *this, rSrcDev );
+ else
+ mpGraphics->CopyBits( rPosAry, *this );
+}
+
+tools::Rectangle OutputDevice::GetBackgroundComponentBounds() const
+{
+ return tools::Rectangle( Point( 0, 0 ), GetOutputSizePixel() );
+}
+
+// Layout public functions
+
+void OutputDevice::EnableRTL( bool bEnable )
+{
+ mbEnableRTL = bEnable;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->EnableRTL( bEnable );
+}
+
+bool OutputDevice::ImplIsAntiparallel() const
+{
+ bool bRet = false;
+ if( AcquireGraphics() )
+ {
+ if( ( (mpGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) && ! IsRTLEnabled() ) ||
+ ( ! (mpGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) && IsRTLEnabled() ) )
+ {
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+
+// note: the coordinates to be remirrored are in frame coordinates !
+
+void OutputDevice::ReMirror( Point &rPoint ) const
+{
+ rPoint.setX( mnOutOffX + mnOutWidth - 1 - rPoint.X() + mnOutOffX );
+}
+void OutputDevice::ReMirror( tools::Rectangle &rRect ) const
+{
+ tools::Long nWidth = rRect.Right() - rRect.Left();
+
+ //long lc_x = rRect.nLeft - mnOutOffX; // normalize
+ //lc_x = mnOutWidth - nWidth - 1 - lc_x; // mirror
+ //rRect.nLeft = lc_x + mnOutOffX; // re-normalize
+
+ rRect.SetLeft( mnOutOffX + mnOutWidth - nWidth - 1 - rRect.Left() + mnOutOffX );
+ rRect.SetRight( rRect.Left() + nWidth );
+}
+
+void OutputDevice::ReMirror( vcl::Region &rRegion ) const
+{
+ RectangleVector aRectangles;
+ rRegion.GetRegionRectangles(aRectangles);
+ vcl::Region aMirroredRegion;
+
+ for (auto & rectangle : aRectangles)
+ {
+ ReMirror(rectangle);
+ aMirroredRegion.Union(rectangle);
+ }
+
+ rRegion = aMirroredRegion;
+
+}
+
+bool OutputDevice::HasMirroredGraphics() const
+{
+ return ( AcquireGraphics() && (mpGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) );
+}
+
+bool OutputDevice::ImplIsRecordLayout() const
+{
+ if (!mpOutDevData)
+ return false;
+
+ return mpOutDevData->mpRecordLayout;
+}
+
+css::awt::DeviceInfo OutputDevice::GetCommonDeviceInfo(Size const& rDevSz) const
+{
+ css::awt::DeviceInfo aInfo;
+
+ aInfo.Width = rDevSz.Width();
+ aInfo.Height = rDevSz.Height();
+
+ Size aTmpSz = LogicToPixel(Size(1000, 1000), MapMode(MapUnit::MapCM));
+ aInfo.PixelPerMeterX = aTmpSz.Width() / 10;
+ aInfo.PixelPerMeterY = aTmpSz.Height() / 10;
+ aInfo.BitsPerPixel = GetBitCount();
+
+ aInfo.Capabilities = css::awt::DeviceCapability::RASTEROPERATIONS |
+ css::awt::DeviceCapability::GETBITS;
+
+ return aInfo;
+}
+
+css::awt::DeviceInfo OutputDevice::GetDeviceInfo() const
+{
+ css::awt::DeviceInfo aInfo = GetCommonDeviceInfo(GetOutputSizePixel());
+
+ aInfo.LeftInset = 0;
+ aInfo.TopInset = 0;
+ aInfo.RightInset = 0;
+ aInfo.BottomInset = 0;
+
+ return aInfo;
+}
+
+Reference< css::rendering::XCanvas > OutputDevice::GetCanvas() const
+{
+ // try to retrieve hard reference from weak member
+ Reference< css::rendering::XCanvas > xCanvas( mxCanvas );
+ // canvas still valid? Then we're done.
+ if( xCanvas.is() )
+ return xCanvas;
+ xCanvas = ImplGetCanvas( false );
+ mxCanvas = xCanvas;
+ return xCanvas;
+}
+
+Reference< css::rendering::XSpriteCanvas > OutputDevice::GetSpriteCanvas() const
+{
+ Reference< css::rendering::XCanvas > xCanvas( mxCanvas );
+ Reference< css::rendering::XSpriteCanvas > xSpriteCanvas( xCanvas, UNO_QUERY );
+ if( xSpriteCanvas.is() )
+ return xSpriteCanvas;
+ xCanvas = ImplGetCanvas( true );
+ mxCanvas = xCanvas;
+ return Reference< css::rendering::XSpriteCanvas >( xCanvas, UNO_QUERY );
+}
+
+// Generic implementation, Window will override.
+com::sun::star::uno::Reference< css::rendering::XCanvas > OutputDevice::ImplGetCanvas( bool bSpriteCanvas ) const
+{
+ /* Arguments:
+ 0: ptr to creating instance (Window or VirtualDevice)
+ 1: current bounds of creating instance
+ 2: bool, denoting always on top state for Window (always false for VirtualDevice)
+ 3: XWindow for creating Window (or empty for VirtualDevice)
+ 4: SystemGraphicsData as a streamed Any
+ */
+ Sequence< Any > aArg{
+ Any(reinterpret_cast<sal_Int64>(this)),
+ Any(css::awt::Rectangle( mnOutOffX, mnOutOffY, mnOutWidth, mnOutHeight )),
+ Any(false),
+ Any(Reference< css::awt::XWindow >()),
+ GetSystemGfxDataAny()
+ };
+
+ Reference< XComponentContext > xContext = comphelper::getProcessComponentContext();
+
+ static vcl::DeleteUnoReferenceOnDeinit<css::lang::XMultiComponentFactory> xStaticCanvasFactory(
+ css::rendering::CanvasFactory::create( xContext ) );
+ Reference<css::lang::XMultiComponentFactory> xCanvasFactory(xStaticCanvasFactory.get());
+ Reference< css::rendering::XCanvas > xCanvas;
+
+ if(xCanvasFactory.is())
+ {
+ xCanvas.set( xCanvasFactory->createInstanceWithArgumentsAndContext(
+ bSpriteCanvas ?
+ OUString( "com.sun.star.rendering.SpriteCanvas" ) :
+ OUString( "com.sun.star.rendering.Canvas" ),
+ aArg,
+ xContext ),
+ UNO_QUERY );
+ }
+
+ // no factory??? Empty reference, then.
+ return xCanvas;
+}
+
+void OutputDevice::ImplDisposeCanvas()
+{
+ css::uno::Reference< css::rendering::XCanvas > xCanvas( mxCanvas );
+ if( xCanvas.is() )
+ {
+ css::uno::Reference< css::lang::XComponent > xCanvasComponent( xCanvas, css::uno::UNO_QUERY );
+ if( xCanvasComponent.is() )
+ xCanvasComponent->dispose();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/outdev/pixel.cxx b/vcl/source/outdev/pixel.cxx
new file mode 100644
index 000000000..b051e6a5a
--- /dev/null
+++ b/vcl/source/outdev/pixel.cxx
@@ -0,0 +1,120 @@
+/* -*- 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 <cassert>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+
+Color OutputDevice::GetPixel(const Point& rPoint) const
+{
+ Color aColor;
+
+ if (mpGraphics || AcquireGraphics())
+ {
+ assert(mpGraphics);
+ if (mbInitClipRegion)
+ const_cast<OutputDevice*>(this)->InitClipRegion();
+
+ if (!mbOutputClipped)
+ {
+ const tools::Long nX = ImplLogicXToDevicePixel(rPoint.X());
+ const tools::Long nY = ImplLogicYToDevicePixel(rPoint.Y());
+ aColor = mpGraphics->GetPixel(nX, nY, *this);
+
+ if (mpAlphaVDev)
+ {
+ Color aAlphaColor = mpAlphaVDev->GetPixel(rPoint);
+ aColor.SetAlpha(255 - aAlphaColor.GetBlue());
+ }
+ }
+ }
+ return aColor;
+}
+
+void OutputDevice::DrawPixel( const Point& rPt )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPointAction( rPt ) );
+
+ if ( !IsDeviceOutputNecessary() || !mbLineColor || ImplIsRecordLayout() )
+ return;
+
+ Point aPt = ImplLogicToDevicePixel( rPt );
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ mpGraphics->DrawPixel( aPt.X(), aPt.Y(), *this );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPixel( rPt );
+}
+
+void OutputDevice::DrawPixel( const Point& rPt, const Color& rColor )
+{
+ assert(!is_double_buffered_window());
+
+ Color aColor = vcl::drawmode::GetLineColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPixelAction( rPt, aColor ) );
+
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ Point aPt = ImplLogicToDevicePixel( rPt );
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ mpGraphics->DrawPixel( aPt.X(), aPt.Y(), aColor, *this );
+
+ if (mpAlphaVDev)
+ {
+ Color aAlphaColor(255 - rColor.GetAlpha(), 255 - rColor.GetAlpha(), 255 - rColor.GetAlpha());
+ mpAlphaVDev->DrawPixel(rPt, aAlphaColor);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/polygon.cxx b/vcl/source/outdev/polygon.cxx
new file mode 100644
index 000000000..772c2dbde
--- /dev/null
+++ b/vcl/source/outdev/polygon.cxx
@@ -0,0 +1,518 @@
+/* -*- 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 <cassert>
+
+#include <sal/types.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <memory>
+#include <tools/poly.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+#define OUTDEV_POLYPOLY_STACKBUF 32
+
+void OutputDevice::DrawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
+{
+ assert(!is_double_buffered_window());
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPolyPolygonAction( rPolyPoly ) );
+
+ sal_uInt16 nPoly = rPolyPoly.Count();
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || !nPoly || ImplIsRecordLayout() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ // use b2dpolygon drawing if possible
+ if(mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ RasterOp::OverPaint == GetRasterOp() &&
+ (IsLineColor() || IsFillColor()))
+ {
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon());
+ bool bSuccess(true);
+
+ // ensure closed - may be asserted, will prevent buffering
+ if(!aB2DPolyPolygon.isClosed())
+ {
+ aB2DPolyPolygon.setClosed(true);
+ }
+
+ if(IsFillColor())
+ {
+ bSuccess = mpGraphics->DrawPolyPolygon(
+ aTransform,
+ aB2DPolyPolygon,
+ 0.0,
+ *this);
+ }
+
+ if(bSuccess && IsLineColor())
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : std::as_const(aB2DPolyPolygon))
+ {
+ bSuccess = mpGraphics->DrawPolyLine(
+ aTransform,
+ rPolygon,
+ 0.0,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ *this);
+ if (!bSuccess)
+ break;
+ }
+ }
+
+ if(bSuccess)
+ {
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPolyPolygon( rPolyPoly );
+ return;
+ }
+ }
+
+ if ( nPoly == 1 )
+ {
+ // #100127# Map to DrawPolygon
+ const tools::Polygon& aPoly = rPolyPoly.GetObject( 0 );
+ if( aPoly.GetSize() >= 2 )
+ {
+ GDIMetaFile* pOldMF = mpMetaFile;
+ mpMetaFile = nullptr;
+
+ DrawPolygon( aPoly );
+
+ mpMetaFile = pOldMF;
+ }
+ }
+ else
+ {
+ // #100127# moved real tools::PolyPolygon draw to separate method,
+ // have to call recursively, avoiding duplicate
+ // ImplLogicToDevicePixel calls
+ ImplDrawPolyPolygon( nPoly, ImplLogicToDevicePixel( rPolyPoly ) );
+ }
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPolyPolygon( rPolyPoly );
+}
+
+void OutputDevice::DrawPolygon( const basegfx::B2DPolygon& rB2DPolygon)
+{
+ assert(!is_double_buffered_window());
+
+ // AW: Do NOT paint empty polygons
+ if(rB2DPolygon.count())
+ {
+ basegfx::B2DPolyPolygon aPP( rB2DPolygon );
+ DrawPolyPolygon( aPP );
+ }
+}
+
+void OutputDevice::DrawPolygon( const tools::Polygon& rPoly )
+{
+ assert(!is_double_buffered_window());
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPolygonAction( rPoly ) );
+
+ sal_uInt16 nPoints = rPoly.GetSize();
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || (nPoints < 2) || ImplIsRecordLayout() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ // use b2dpolygon drawing if possible
+ if(mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ RasterOp::OverPaint == GetRasterOp() &&
+ (IsLineColor() || IsFillColor()))
+ {
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolygon aB2DPolygon(rPoly.getB2DPolygon());
+ bool bSuccess(true);
+
+ // ensure closed - maybe assert, hinders buffering
+ if(!aB2DPolygon.isClosed())
+ {
+ aB2DPolygon.setClosed(true);
+ }
+
+ if(IsFillColor())
+ {
+ bSuccess = mpGraphics->DrawPolyPolygon(
+ aTransform,
+ basegfx::B2DPolyPolygon(aB2DPolygon),
+ 0.0,
+ *this);
+ }
+
+ if(bSuccess && IsLineColor())
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ bSuccess = mpGraphics->DrawPolyLine(
+ aTransform,
+ aB2DPolygon,
+ 0.0,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ *this);
+ }
+
+ if(bSuccess)
+ {
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPolygon( rPoly );
+ return;
+ }
+ }
+
+ tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly );
+ const Point* pPtAry = aPoly.GetConstPointAry();
+
+ // #100127# Forward beziers to sal, if any
+ if( aPoly.HasFlags() )
+ {
+ const PolyFlags* pFlgAry = aPoly.GetConstFlagAry();
+ if( !mpGraphics->DrawPolygonBezier( nPoints, pPtAry, pFlgAry, *this ) )
+ {
+ aPoly = tools::Polygon::SubdivideBezier(aPoly);
+ pPtAry = aPoly.GetConstPointAry();
+ mpGraphics->DrawPolygon( aPoly.GetSize(), pPtAry, *this );
+ }
+ }
+ else
+ {
+ mpGraphics->DrawPolygon( nPoints, pPtAry, *this );
+ }
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPolygon( rPoly );
+}
+
+// Caution: This method is nearly the same as
+// OutputDevice::DrawTransparent( const basegfx::B2DPolyPolygon& rB2DPolyPoly, double fTransparency),
+// so when changes are made here do not forget to make changes there, too
+
+void OutputDevice::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rB2DPolyPoly )
+{
+ assert(!is_double_buffered_window());
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPolyPolygonAction( tools::PolyPolygon( rB2DPolyPoly ) ) );
+
+ // call helper
+ ImplDrawPolyPolygonWithB2DPolyPolygon(rB2DPolyPoly);
+}
+
+void OutputDevice::ImplDrawPolyPolygonWithB2DPolyPolygon(const basegfx::B2DPolyPolygon& rB2DPolyPoly)
+{
+ // Do not paint empty PolyPolygons
+ if(!rB2DPolyPoly.count() || !IsDeviceOutputNecessary())
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ if( mbInitFillColor )
+ InitFillColor();
+
+ bool bSuccess(false);
+
+ if(mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ RasterOp::OverPaint == GetRasterOp() &&
+ (IsLineColor() || IsFillColor()))
+ {
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(rB2DPolyPoly);
+ bSuccess = true;
+
+ // ensure closed - maybe assert, hinders buffering
+ if(!aB2DPolyPolygon.isClosed())
+ {
+ aB2DPolyPolygon.setClosed(true);
+ }
+
+ if(IsFillColor())
+ {
+ bSuccess = mpGraphics->DrawPolyPolygon(
+ aTransform,
+ aB2DPolyPolygon,
+ 0.0,
+ *this);
+ }
+
+ if(bSuccess && IsLineColor())
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : std::as_const(aB2DPolyPolygon))
+ {
+ bSuccess = mpGraphics->DrawPolyLine(
+ aTransform,
+ rPolygon,
+ 0.0,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ *this);
+ if (!bSuccess)
+ break;
+ }
+ }
+ }
+
+ if (!bSuccess)
+ {
+ // fallback to old polygon drawing if needed
+ const tools::PolyPolygon aToolsPolyPolygon(rB2DPolyPoly);
+ const tools::PolyPolygon aPixelPolyPolygon = ImplLogicToDevicePixel(aToolsPolyPolygon);
+ ImplDrawPolyPolygon(aPixelPolyPolygon.Count(), aPixelPolyPolygon);
+ }
+
+ if (mpAlphaVDev)
+ mpAlphaVDev->ImplDrawPolyPolygonWithB2DPolyPolygon(rB2DPolyPoly);
+}
+
+// #100127# Extracted from OutputDevice::DrawPolyPolygon()
+void OutputDevice::ImplDrawPolyPolygon( sal_uInt16 nPoly, const tools::PolyPolygon& rPolyPoly )
+{
+ // AW: This crashes on empty PolyPolygons, avoid that
+ if(!nPoly)
+ return;
+
+ sal_uInt32 aStackAry1[OUTDEV_POLYPOLY_STACKBUF];
+ const Point* aStackAry2[OUTDEV_POLYPOLY_STACKBUF];
+ PolyFlags* aStackAry3[OUTDEV_POLYPOLY_STACKBUF];
+ sal_uInt32* pPointAry;
+ const Point** pPointAryAry;
+ const PolyFlags** pFlagAryAry;
+ sal_uInt16 i = 0;
+ sal_uInt16 j = 0;
+ sal_uInt16 last = 0;
+ bool bHaveBezier = false;
+ if ( nPoly > OUTDEV_POLYPOLY_STACKBUF )
+ {
+ pPointAry = new sal_uInt32[nPoly];
+ pPointAryAry = new const Point*[nPoly];
+ pFlagAryAry = new const PolyFlags*[nPoly];
+ }
+ else
+ {
+ pPointAry = aStackAry1;
+ pPointAryAry = aStackAry2;
+ pFlagAryAry = const_cast<const PolyFlags**>(aStackAry3);
+ }
+
+ do
+ {
+ const tools::Polygon& rPoly = rPolyPoly.GetObject( i );
+ sal_uInt16 nSize = rPoly.GetSize();
+ if ( nSize )
+ {
+ pPointAry[j] = nSize;
+ pPointAryAry[j] = rPoly.GetConstPointAry();
+ pFlagAryAry[j] = rPoly.GetConstFlagAry();
+ last = i;
+
+ if( pFlagAryAry[j] )
+ bHaveBezier = true;
+
+ ++j;
+ }
+ ++i;
+ }
+ while ( i < nPoly );
+
+ if ( j == 1 )
+ {
+ // #100127# Forward beziers to sal, if any
+ if( bHaveBezier )
+ {
+ if( !mpGraphics->DrawPolygonBezier( *pPointAry, *pPointAryAry, *pFlagAryAry, *this ) )
+ {
+ tools::Polygon aPoly = tools::Polygon::SubdivideBezier( rPolyPoly.GetObject( last ) );
+ mpGraphics->DrawPolygon( aPoly.GetSize(), aPoly.GetConstPointAry(), *this );
+ }
+ }
+ else
+ {
+ mpGraphics->DrawPolygon( *pPointAry, *pPointAryAry, *this );
+ }
+ }
+ else
+ {
+ // #100127# Forward beziers to sal, if any
+ if( bHaveBezier )
+ {
+ if (!mpGraphics->DrawPolyPolygonBezier(j, pPointAry, pPointAryAry, pFlagAryAry, *this))
+ {
+ tools::PolyPolygon aPolyPoly = tools::PolyPolygon::SubdivideBezier( rPolyPoly );
+ ImplDrawPolyPolygon( aPolyPoly.Count(), aPolyPoly );
+ }
+ }
+ else
+ {
+ mpGraphics->DrawPolyPolygon( j, pPointAry, pPointAryAry, *this );
+ }
+ }
+
+ if ( pPointAry != aStackAry1 )
+ {
+ delete[] pPointAry;
+ delete[] pPointAryAry;
+ delete[] pFlagAryAry;
+ }
+}
+
+void OutputDevice::ImplDrawPolygon( const tools::Polygon& rPoly, const tools::PolyPolygon* pClipPolyPoly )
+{
+ if( pClipPolyPoly )
+ {
+ ImplDrawPolyPolygon( rPoly, pClipPolyPoly );
+ }
+ else
+ {
+ sal_uInt16 nPoints = rPoly.GetSize();
+
+ if ( nPoints < 2 )
+ return;
+
+ const Point* pPtAry = rPoly.GetConstPointAry();
+ mpGraphics->DrawPolygon( nPoints, pPtAry, *this );
+ }
+}
+
+void OutputDevice::ImplDrawPolyPolygon( const tools::PolyPolygon& rPolyPoly, const tools::PolyPolygon* pClipPolyPoly )
+{
+ tools::PolyPolygon* pPolyPoly;
+
+ if( pClipPolyPoly )
+ {
+ pPolyPoly = new tools::PolyPolygon;
+ rPolyPoly.GetIntersection( *pClipPolyPoly, *pPolyPoly );
+ }
+ else
+ {
+ pPolyPoly = const_cast<tools::PolyPolygon*>(&rPolyPoly);
+ }
+ if( pPolyPoly->Count() == 1 )
+ {
+ const tools::Polygon& rPoly = pPolyPoly->GetObject( 0 );
+ sal_uInt16 nSize = rPoly.GetSize();
+
+ if( nSize >= 2 )
+ {
+ const Point* pPtAry = rPoly.GetConstPointAry();
+ mpGraphics->DrawPolygon( nSize, pPtAry, *this );
+ }
+ }
+ else if( pPolyPoly->Count() )
+ {
+ sal_uInt16 nCount = pPolyPoly->Count();
+ std::unique_ptr<sal_uInt32[]> pPointAry(new sal_uInt32[nCount]);
+ std::unique_ptr<const Point*[]> pPointAryAry(new const Point*[nCount]);
+ sal_uInt16 i = 0;
+ do
+ {
+ const tools::Polygon& rPoly = pPolyPoly->GetObject( i );
+ sal_uInt16 nSize = rPoly.GetSize();
+ if ( nSize )
+ {
+ pPointAry[i] = nSize;
+ pPointAryAry[i] = rPoly.GetConstPointAry();
+ i++;
+ }
+ else
+ nCount--;
+ }
+ while( i < nCount );
+
+ if( nCount == 1 )
+ mpGraphics->DrawPolygon( pPointAry[0], pPointAryAry[0], *this );
+ else
+ mpGraphics->DrawPolyPolygon( nCount, pPointAry.get(), pPointAryAry.get(), *this );
+ }
+
+ if( pClipPolyPoly )
+ delete pPolyPoly;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/polyline.cxx b/vcl/source/outdev/polyline.cxx
new file mode 100644
index 000000000..f699147c2
--- /dev/null
+++ b/vcl/source/outdev/polyline.cxx
@@ -0,0 +1,408 @@
+/* -*- 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 <cassert>
+
+#include <sal/types.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+void OutputDevice::DrawPolyLine( const tools::Polygon& rPoly )
+{
+ assert(!is_double_buffered_window());
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPolyLineAction( rPoly ) );
+
+ sal_uInt16 nPoints = rPoly.GetSize();
+
+ if ( !IsDeviceOutputNecessary() || !mbLineColor || (nPoints < 2) || ImplIsRecordLayout() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ // use b2dpolygon drawing if possible
+ if(DrawPolyLineDirectInternal(
+ basegfx::B2DHomMatrix(),
+ rPoly.getB2DPolygon()))
+ {
+ return;
+ }
+
+ const basegfx::B2DPolygon aB2DPolyLine(rPoly.getB2DPolygon());
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ bool bDrawn = mpGraphics->DrawPolyLine(
+ aTransform,
+ aB2DPolyLine,
+ 0.0,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0) /*default fMiterMinimumAngle, not used*/,
+ bPixelSnapHairline,
+ *this);
+
+ if(!bDrawn)
+ {
+ tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly );
+ Point* pPtAry = aPoly.GetPointAry();
+
+ // #100127# Forward beziers to sal, if any
+ if( aPoly.HasFlags() )
+ {
+ const PolyFlags* pFlgAry = aPoly.GetConstFlagAry();
+ if( !mpGraphics->DrawPolyLineBezier( nPoints, pPtAry, pFlgAry, *this ) )
+ {
+ aPoly = tools::Polygon::SubdivideBezier(aPoly);
+ pPtAry = aPoly.GetPointAry();
+ mpGraphics->DrawPolyLine( aPoly.GetSize(), pPtAry, *this );
+ }
+ }
+ else
+ {
+ mpGraphics->DrawPolyLine( nPoints, pPtAry, *this );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPolyLine( rPoly );
+}
+
+void OutputDevice::DrawPolyLine( const tools::Polygon& rPoly, const LineInfo& rLineInfo )
+{
+ assert(!is_double_buffered_window());
+
+ if ( rLineInfo.IsDefault() )
+ {
+ DrawPolyLine( rPoly );
+ return;
+ }
+
+ if (IsDeviceOutputNecessary())
+ {
+ auto eLineStyle = rLineInfo.GetStyle();
+ switch (eLineStyle)
+ {
+ case LineStyle::NONE:
+ case LineStyle::Dash:
+ // use drawPolyLine for these
+ break;
+ case LineStyle::Solid:
+ // #i101491# Try direct Fallback to B2D-Version of DrawPolyLine
+ DrawPolyLine(
+ rPoly.getB2DPolygon(),
+ rLineInfo.GetWidth(),
+ rLineInfo.GetLineJoin(),
+ rLineInfo.GetLineCap(),
+ basegfx::deg2rad(15.0) /* default fMiterMinimumAngle, value not available in LineInfo */);
+ return;
+ default:
+ SAL_WARN("vcl.gdi", "Unknown LineStyle: " << static_cast<int>(eLineStyle));
+ return;
+ }
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPolyLineAction( rPoly, rLineInfo ) );
+
+ drawPolyLine(rPoly, rLineInfo);
+}
+
+void OutputDevice::DrawPolyLine( const basegfx::B2DPolygon& rB2DPolygon,
+ double fLineWidth,
+ basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle)
+{
+ assert(!is_double_buffered_window());
+
+ if( mpMetaFile )
+ {
+ LineInfo aLineInfo;
+ if( fLineWidth != 0.0 )
+ aLineInfo.SetWidth( fLineWidth );
+
+ aLineInfo.SetLineJoin(eLineJoin);
+ aLineInfo.SetLineCap(eLineCap);
+
+ const tools::Polygon aToolsPolygon( rB2DPolygon );
+ mpMetaFile->AddAction( new MetaPolyLineAction( aToolsPolygon, aLineInfo ) );
+ }
+
+ // Do not paint empty PolyPolygons
+ if(!rB2DPolygon.count() || !IsDeviceOutputNecessary())
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ // use b2dpolygon drawing if possible
+ if(DrawPolyLineDirectInternal(
+ basegfx::B2DHomMatrix(),
+ rB2DPolygon,
+ fLineWidth,
+ 0.0,
+ nullptr, // MM01
+ eLineJoin,
+ eLineCap,
+ fMiterMinimumAngle))
+ {
+ return;
+ }
+
+ // #i101491#
+ // no output yet; fallback to geometry decomposition and use filled polygon paint
+ // when line is fat and not too complex. ImplDrawPolyPolygonWithB2DPolyPolygon
+ // will do internal needed AA checks etc.
+ if(fLineWidth >= 2.5 &&
+ rB2DPolygon.count() &&
+ rB2DPolygon.count() <= 1000)
+ {
+ const double fHalfLineWidth((fLineWidth * 0.5) + 0.5);
+ const basegfx::B2DPolyPolygon aAreaPolyPolygon(
+ basegfx::utils::createAreaGeometry( rB2DPolygon,
+ fHalfLineWidth,
+ eLineJoin,
+ eLineCap,
+ fMiterMinimumAngle));
+ const Color aOldLineColor(maLineColor);
+ const Color aOldFillColor(maFillColor);
+
+ SetLineColor();
+ InitLineColor();
+ SetFillColor(aOldLineColor);
+ InitFillColor();
+
+ // draw using a loop; else the topology will paint a PolyPolygon
+ for(auto const& rPolygon : aAreaPolyPolygon)
+ {
+ ImplDrawPolyPolygonWithB2DPolyPolygon(
+ basegfx::B2DPolyPolygon(rPolygon));
+ }
+
+ SetLineColor(aOldLineColor);
+ InitLineColor();
+ SetFillColor(aOldFillColor);
+ InitFillColor();
+
+ // when AA it is necessary to also paint the filled polygon's outline
+ // to avoid optical gaps
+ for(auto const& rPolygon : aAreaPolyPolygon)
+ {
+ (void)DrawPolyLineDirectInternal(
+ basegfx::B2DHomMatrix(),
+ rPolygon);
+ }
+ }
+ else
+ {
+ // fallback to old polygon drawing if needed
+ const tools::Polygon aToolsPolygon( rB2DPolygon );
+ LineInfo aLineInfo;
+ if( fLineWidth != 0.0 )
+ aLineInfo.SetWidth( fLineWidth );
+
+ drawPolyLine( aToolsPolygon, aLineInfo );
+ }
+}
+
+void OutputDevice::drawPolyLine(const tools::Polygon& rPoly, const LineInfo& rLineInfo)
+{
+ sal_uInt16 nPoints(rPoly.GetSize());
+
+ if ( !IsDeviceOutputNecessary() || !mbLineColor || ( nPoints < 2 ) || ( LineStyle::NONE == rLineInfo.GetStyle() ) || ImplIsRecordLayout() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ const LineInfo aInfo( ImplLogicToDevicePixel( rLineInfo ) );
+ const bool bDashUsed(LineStyle::Dash == aInfo.GetStyle());
+ const bool bLineWidthUsed(aInfo.GetWidth() > 1);
+
+ if (bDashUsed || bLineWidthUsed)
+ {
+ basegfx::B2DPolygon aPoly = ImplLogicToDevicePixel(rPoly.getB2DPolygon());
+ drawLine(basegfx::B2DPolyPolygon(aPoly), aInfo);
+ }
+ else
+ {
+ tools::Polygon aPoly = ImplLogicToDevicePixel(rPoly);
+
+ // #100127# the subdivision HAS to be done here since only a pointer
+ // to an array of points is given to the DrawPolyLine method, there is
+ // NO way to find out there that it's a curve.
+ if( aPoly.HasFlags() )
+ {
+ aPoly = tools::Polygon::SubdivideBezier( aPoly );
+ nPoints = aPoly.GetSize();
+ }
+
+ mpGraphics->DrawPolyLine(nPoints, aPoly.GetPointAry(), *this);
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPolyLine( rPoly, rLineInfo );
+}
+
+bool OutputDevice::DrawPolyLineDirect(
+ const basegfx::B2DHomMatrix& rObjectTransform,
+ const basegfx::B2DPolygon& rB2DPolygon,
+ double fLineWidth,
+ double fTransparency,
+ const std::vector< double >* pStroke, // MM01
+ basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle)
+{
+ if(DrawPolyLineDirectInternal(rObjectTransform, rB2DPolygon, fLineWidth, fTransparency,
+ pStroke, eLineJoin, eLineCap, fMiterMinimumAngle))
+ {
+ // Worked, add metafile action (if recorded). This is done only here,
+ // because this function is public, other OutDev functions already add metafile
+ // actions, so they call the internal function directly.
+ if( mpMetaFile )
+ {
+ LineInfo aLineInfo;
+ if( fLineWidth != 0.0 )
+ aLineInfo.SetWidth( fLineWidth );
+ // Transport known information, might be needed
+ aLineInfo.SetLineJoin(eLineJoin);
+ aLineInfo.SetLineCap(eLineCap);
+ // MiterMinimumAngle does not exist yet in LineInfo
+ const tools::Polygon aToolsPolygon( rB2DPolygon );
+ mpMetaFile->AddAction( new MetaPolyLineAction( aToolsPolygon, aLineInfo ) );
+ }
+ return true;
+ }
+ return false;
+}
+
+bool OutputDevice::DrawPolyLineDirectInternal(
+ const basegfx::B2DHomMatrix& rObjectTransform,
+ const basegfx::B2DPolygon& rB2DPolygon,
+ double fLineWidth,
+ double fTransparency,
+ const std::vector< double >* pStroke, // MM01
+ basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle)
+{
+ assert(!is_double_buffered_window());
+
+ // AW: Do NOT paint empty PolyPolygons
+ if(!rB2DPolygon.count())
+ return true;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return true;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ const bool bTryB2d(mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ RasterOp::OverPaint == GetRasterOp() &&
+ IsLineColor());
+
+ if(bTryB2d)
+ {
+ // combine rObjectTransform with WorldToDevice
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation() * rObjectTransform);
+ const bool bPixelSnapHairline((mnAntialiasing & AntialiasingFlags::PixelSnapHairline) && rB2DPolygon.count() < 1000);
+
+ const double fAdjustedTransparency = mpAlphaVDev ? 0 : fTransparency;
+ // draw the polyline
+ bool bDrawSuccess = mpGraphics->DrawPolyLine(
+ aTransform,
+ rB2DPolygon,
+ fAdjustedTransparency,
+ fLineWidth, // tdf#124848 use LineWidth direct, do not try to solve for zero-case (aka hairline)
+ pStroke, // MM01
+ eLineJoin,
+ eLineCap,
+ fMiterMinimumAngle,
+ bPixelSnapHairline,
+ *this);
+
+ if( bDrawSuccess )
+ {
+ if (mpAlphaVDev)
+ mpAlphaVDev->DrawPolyLineDirect(rObjectTransform, rB2DPolygon, fLineWidth,
+ fTransparency, pStroke, eLineJoin, eLineCap,
+ fMiterMinimumAngle);
+
+ return true;
+ }
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/rect.cxx b/vcl/source/outdev/rect.cxx
new file mode 100644
index 000000000..554cf5ec6
--- /dev/null
+++ b/vcl/source/outdev/rect.cxx
@@ -0,0 +1,439 @@
+/* -*- 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 <cassert>
+
+#include <sal/types.h>
+
+#include <tools/poly.hxx>
+#include <tools/helpers.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+void OutputDevice::DrawBorder(tools::Rectangle aBorderRect)
+{
+ sal_uInt16 nPixel = static_cast<sal_uInt16>(PixelToLogic(Size(1, 1)).Width());
+
+ aBorderRect.AdjustLeft(nPixel);
+ aBorderRect.AdjustTop(nPixel);
+
+ SetLineColor(COL_LIGHTGRAY);
+ DrawRect(aBorderRect);
+
+ aBorderRect.AdjustLeft(-nPixel);
+ aBorderRect.AdjustTop(-nPixel);
+ aBorderRect.AdjustRight(-nPixel);
+ aBorderRect.AdjustBottom(-nPixel);
+ SetLineColor(COL_GRAY);
+
+ DrawRect(aBorderRect);
+}
+
+void OutputDevice::DrawRect( const tools::Rectangle& rRect )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaRectAction( rRect ) );
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() )
+ return;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+
+ if ( aRect.IsEmpty() )
+ return;
+
+ aRect.Justify();
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ mpGraphics->DrawRect( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), *this );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawRect( rRect );
+}
+
+void OutputDevice::DrawRect( const tools::Rectangle& rRect,
+ sal_uLong nHorzRound, sal_uLong nVertRound )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaRoundRectAction( rRect, nHorzRound, nVertRound ) );
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() )
+ return;
+
+ const tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+
+ if ( aRect.IsEmpty() )
+ return;
+
+ nHorzRound = ImplLogicWidthToDevicePixel( nHorzRound );
+ nVertRound = ImplLogicHeightToDevicePixel( nVertRound );
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ if ( !nHorzRound && !nVertRound )
+ {
+ mpGraphics->DrawRect( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), *this );
+ }
+ else
+ {
+ tools::Polygon aRoundRectPoly( aRect, nHorzRound, nVertRound );
+
+ if ( aRoundRectPoly.GetSize() >= 2 )
+ {
+ Point* pPtAry = aRoundRectPoly.GetPointAry();
+
+ if ( !mbFillColor )
+ mpGraphics->DrawPolyLine( aRoundRectPoly.GetSize(), pPtAry, *this );
+ else
+ mpGraphics->DrawPolygon( aRoundRectPoly.GetSize(), pPtAry, *this );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawRect( rRect, nHorzRound, nVertRound );
+}
+
+void OutputDevice::Invert( const tools::Rectangle& rRect, InvertFlags nFlags )
+{
+ assert(!is_double_buffered_window());
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+
+ if ( aRect.IsEmpty() )
+ return;
+ aRect.Justify();
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ SalInvert nSalFlags = SalInvert::NONE;
+ if ( nFlags & InvertFlags::N50 )
+ nSalFlags |= SalInvert::N50;
+ if ( nFlags & InvertFlags::TrackFrame )
+ nSalFlags |= SalInvert::TrackFrame;
+ mpGraphics->Invert( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), nSalFlags, *this );
+}
+
+void OutputDevice::Invert( const tools::Polygon& rPoly, InvertFlags nFlags )
+{
+ assert(!is_double_buffered_window());
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ sal_uInt16 nPoints = rPoly.GetSize();
+
+ if ( nPoints < 2 )
+ return;
+
+ tools::Polygon aPoly( ImplLogicToDevicePixel( rPoly ) );
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ SalInvert nSalFlags = SalInvert::NONE;
+ if ( nFlags & InvertFlags::N50 )
+ nSalFlags |= SalInvert::N50;
+ if ( nFlags & InvertFlags::TrackFrame )
+ nSalFlags |= SalInvert::TrackFrame;
+ const Point* pPtAry = aPoly.GetConstPointAry();
+ mpGraphics->Invert( nPoints, pPtAry, nSalFlags, *this );
+}
+
+void OutputDevice::DrawCheckered(const Point& rPos, const Size& rSize, sal_uInt32 nLen, Color aStart, Color aEnd)
+{
+ assert(!is_double_buffered_window());
+
+ const sal_uInt32 nMaxX(rPos.X() + rSize.Width());
+ const sal_uInt32 nMaxY(rPos.Y() + rSize.Height());
+
+ Push(vcl::PushFlags::LINECOLOR|vcl::PushFlags::FILLCOLOR);
+ SetLineColor();
+
+ for(sal_uInt32 x(0), nX(rPos.X()); nX < nMaxX; x++, nX += nLen)
+ {
+ const sal_uInt32 nRight(std::min(nMaxX, nX + nLen));
+
+ for(sal_uInt32 y(0), nY(rPos.Y()); nY < nMaxY; y++, nY += nLen)
+ {
+ const sal_uInt32 nBottom(std::min(nMaxY, nY + nLen));
+
+ SetFillColor(((x & 0x0001) ^ (y & 0x0001)) ? aStart : aEnd);
+ DrawRect(tools::Rectangle(nX, nY, nRight, nBottom));
+ }
+ }
+
+ Pop();
+}
+
+void OutputDevice::DrawGrid( const tools::Rectangle& rRect, const Size& rDist, DrawGridFlags nFlags )
+{
+ assert(!is_double_buffered_window());
+
+ tools::Rectangle aDstRect( PixelToLogic( Point() ), GetOutputSize() );
+ aDstRect.Intersection( rRect );
+
+ if( aDstRect.IsEmpty() || ImplIsRecordLayout() )
+ return;
+
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ const tools::Long nDistX = std::max( rDist.Width(), tools::Long(1) );
+ const tools::Long nDistY = std::max( rDist.Height(), tools::Long(1) );
+ tools::Long nX = ( rRect.Left() >= aDstRect.Left() ) ? rRect.Left() : ( rRect.Left() + ( ( aDstRect.Left() - rRect.Left() ) / nDistX ) * nDistX );
+ tools::Long nY = ( rRect.Top() >= aDstRect.Top() ) ? rRect.Top() : ( rRect.Top() + ( ( aDstRect.Top() - rRect.Top() ) / nDistY ) * nDistY );
+ const tools::Long nRight = aDstRect.Right();
+ const tools::Long nBottom = aDstRect.Bottom();
+ const tools::Long nStartX = ImplLogicXToDevicePixel( nX );
+ const tools::Long nEndX = ImplLogicXToDevicePixel( nRight );
+ const tools::Long nStartY = ImplLogicYToDevicePixel( nY );
+ const tools::Long nEndY = ImplLogicYToDevicePixel( nBottom );
+ tools::Long nHorzCount = 0;
+ tools::Long nVertCount = 0;
+
+ std::vector< sal_Int32 > aVertBuf;
+ std::vector< sal_Int32 > aHorzBuf;
+
+ if( ( nFlags & DrawGridFlags::Dots ) || ( nFlags & DrawGridFlags::HorzLines ) )
+ {
+ aVertBuf.resize( aDstRect.GetHeight() / nDistY + 2 );
+ aVertBuf[ nVertCount++ ] = nStartY;
+ while( ( nY += nDistY ) <= nBottom )
+ {
+ aVertBuf[ nVertCount++ ] = ImplLogicYToDevicePixel( nY );
+ }
+ }
+
+ if( ( nFlags & DrawGridFlags::Dots ) || ( nFlags & DrawGridFlags::VertLines ) )
+ {
+ aHorzBuf.resize( aDstRect.GetWidth() / nDistX + 2 );
+ aHorzBuf[ nHorzCount++ ] = nStartX;
+ while( ( nX += nDistX ) <= nRight )
+ {
+ aHorzBuf[ nHorzCount++ ] = ImplLogicXToDevicePixel( nX );
+ }
+ }
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ if( mbInitFillColor )
+ InitFillColor();
+
+ const bool bOldMap = mbMap;
+ EnableMapMode( false );
+
+ if( nFlags & DrawGridFlags::Dots )
+ {
+ for( tools::Long i = 0; i < nVertCount; i++ )
+ {
+ for( tools::Long j = 0, Y = aVertBuf[ i ]; j < nHorzCount; j++ )
+ {
+ mpGraphics->DrawPixel( aHorzBuf[ j ], Y, *this );
+ }
+ }
+ }
+ else
+ {
+ if( nFlags & DrawGridFlags::HorzLines )
+ {
+ for( tools::Long i = 0; i < nVertCount; i++ )
+ {
+ nY = aVertBuf[ i ];
+ mpGraphics->DrawLine( nStartX, nY, nEndX, nY, *this );
+ }
+ }
+
+ if( nFlags & DrawGridFlags::VertLines )
+ {
+ for( tools::Long i = 0; i < nHorzCount; i++ )
+ {
+ nX = aHorzBuf[ i ];
+ mpGraphics->DrawLine( nX, nStartY, nX, nEndY, *this );
+ }
+ }
+ }
+
+ EnableMapMode( bOldMap );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawGrid( rRect, rDist, nFlags );
+}
+
+BmpMirrorFlags AdjustTwoRect( SalTwoRect& rTwoRect, const Size& rSizePix )
+{
+ BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE;
+
+ if ( rTwoRect.mnDestWidth < 0 )
+ {
+ rTwoRect.mnSrcX = rSizePix.Width() - rTwoRect.mnSrcX - rTwoRect.mnSrcWidth;
+ rTwoRect.mnDestWidth = -rTwoRect.mnDestWidth;
+ rTwoRect.mnDestX -= rTwoRect.mnDestWidth-1;
+ nMirrFlags |= BmpMirrorFlags::Horizontal;
+ }
+
+ if ( rTwoRect.mnDestHeight < 0 )
+ {
+ rTwoRect.mnSrcY = rSizePix.Height() - rTwoRect.mnSrcY - rTwoRect.mnSrcHeight;
+ rTwoRect.mnDestHeight = -rTwoRect.mnDestHeight;
+ rTwoRect.mnDestY -= rTwoRect.mnDestHeight-1;
+ nMirrFlags |= BmpMirrorFlags::Vertical;
+ }
+
+ if( ( rTwoRect.mnSrcX < 0 ) || ( rTwoRect.mnSrcX >= rSizePix.Width() ) ||
+ ( rTwoRect.mnSrcY < 0 ) || ( rTwoRect.mnSrcY >= rSizePix.Height() ) ||
+ ( ( rTwoRect.mnSrcX + rTwoRect.mnSrcWidth ) > rSizePix.Width() ) ||
+ ( ( rTwoRect.mnSrcY + rTwoRect.mnSrcHeight ) > rSizePix.Height() ) )
+ {
+ const tools::Rectangle aSourceRect( Point( rTwoRect.mnSrcX, rTwoRect.mnSrcY ),
+ Size( rTwoRect.mnSrcWidth, rTwoRect.mnSrcHeight ) );
+ tools::Rectangle aCropRect( aSourceRect );
+
+ aCropRect.Intersection( tools::Rectangle( Point(), rSizePix ) );
+
+ if( aCropRect.IsEmpty() )
+ {
+ rTwoRect.mnSrcWidth = rTwoRect.mnSrcHeight = rTwoRect.mnDestWidth = rTwoRect.mnDestHeight = 0;
+ }
+ else
+ {
+ const double fFactorX = ( rTwoRect.mnSrcWidth > 1 ) ? static_cast<double>( rTwoRect.mnDestWidth - 1 ) / ( rTwoRect.mnSrcWidth - 1 ) : 0.0;
+ const double fFactorY = ( rTwoRect.mnSrcHeight > 1 ) ? static_cast<double>( rTwoRect.mnDestHeight - 1 ) / ( rTwoRect.mnSrcHeight - 1 ) : 0.0;
+
+ const tools::Long nDstX1 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Left() - rTwoRect.mnSrcX ) );
+ const tools::Long nDstY1 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Top() - rTwoRect.mnSrcY ) );
+ const tools::Long nDstX2 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Right() - rTwoRect.mnSrcX ) );
+ const tools::Long nDstY2 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Bottom() - rTwoRect.mnSrcY ) );
+
+ rTwoRect.mnSrcX = aCropRect.Left();
+ rTwoRect.mnSrcY = aCropRect.Top();
+ rTwoRect.mnSrcWidth = aCropRect.GetWidth();
+ rTwoRect.mnSrcHeight = aCropRect.GetHeight();
+ rTwoRect.mnDestX = nDstX1;
+ rTwoRect.mnDestY = nDstY1;
+ rTwoRect.mnDestWidth = nDstX2 - nDstX1 + 1;
+ rTwoRect.mnDestHeight = nDstY2 - nDstY1 + 1;
+ }
+ }
+
+ return nMirrFlags;
+}
+
+void AdjustTwoRect( SalTwoRect& rTwoRect, const tools::Rectangle& rValidSrcRect )
+{
+ if( !(( rTwoRect.mnSrcX < rValidSrcRect.Left() ) || ( rTwoRect.mnSrcX >= rValidSrcRect.Right() ) ||
+ ( rTwoRect.mnSrcY < rValidSrcRect.Top() ) || ( rTwoRect.mnSrcY >= rValidSrcRect.Bottom() ) ||
+ ( ( rTwoRect.mnSrcX + rTwoRect.mnSrcWidth ) > rValidSrcRect.Right() ) ||
+ ( ( rTwoRect.mnSrcY + rTwoRect.mnSrcHeight ) > rValidSrcRect.Bottom() )) )
+ return;
+
+ const tools::Rectangle aSourceRect( Point( rTwoRect.mnSrcX, rTwoRect.mnSrcY ),
+ Size( rTwoRect.mnSrcWidth, rTwoRect.mnSrcHeight ) );
+ tools::Rectangle aCropRect( aSourceRect );
+
+ aCropRect.Intersection( rValidSrcRect );
+
+ if( aCropRect.IsEmpty() )
+ {
+ rTwoRect.mnSrcWidth = rTwoRect.mnSrcHeight = rTwoRect.mnDestWidth = rTwoRect.mnDestHeight = 0;
+ }
+ else
+ {
+ const double fFactorX = ( rTwoRect.mnSrcWidth > 1 ) ? static_cast<double>( rTwoRect.mnDestWidth - 1 ) / ( rTwoRect.mnSrcWidth - 1 ) : 0.0;
+ const double fFactorY = ( rTwoRect.mnSrcHeight > 1 ) ? static_cast<double>( rTwoRect.mnDestHeight - 1 ) / ( rTwoRect.mnSrcHeight - 1 ) : 0.0;
+
+ const tools::Long nDstX1 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Left() - rTwoRect.mnSrcX ) );
+ const tools::Long nDstY1 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Top() - rTwoRect.mnSrcY ) );
+ const tools::Long nDstX2 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Right() - rTwoRect.mnSrcX ) );
+ const tools::Long nDstY2 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Bottom() - rTwoRect.mnSrcY ) );
+
+ rTwoRect.mnSrcX = aCropRect.Left();
+ rTwoRect.mnSrcY = aCropRect.Top();
+ rTwoRect.mnSrcWidth = aCropRect.GetWidth();
+ rTwoRect.mnSrcHeight = aCropRect.GetHeight();
+ rTwoRect.mnDestX = nDstX1;
+ rTwoRect.mnDestY = nDstY1;
+ rTwoRect.mnDestWidth = nDstX2 - nDstX1 + 1;
+ rTwoRect.mnDestHeight = nDstY2 - nDstY1 + 1;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/stack.cxx b/vcl/source/outdev/stack.cxx
new file mode 100644
index 000000000..e11947de5
--- /dev/null
+++ b/vcl/source/outdev/stack.cxx
@@ -0,0 +1,200 @@
+/* -*- 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 <sal/log.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/rendercontext/State.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/settings.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+
+void OutputDevice::Push(vcl::PushFlags nFlags)
+{
+ if (mpMetaFile)
+ mpMetaFile->AddAction(new MetaPushAction(nFlags));
+
+ maOutDevStateStack.emplace_back();
+ vcl::State& rState = maOutDevStateStack.back();
+
+ rState.mnFlags = nFlags;
+
+ if (nFlags & vcl::PushFlags::LINECOLOR && mbLineColor)
+ rState.mpLineColor = maLineColor;
+
+ if (nFlags & vcl::PushFlags::FILLCOLOR && mbFillColor)
+ rState.mpFillColor = maFillColor;
+
+ if (nFlags & vcl::PushFlags::FONT)
+ rState.mpFont = maFont;
+
+ if (nFlags & vcl::PushFlags::TEXTCOLOR)
+ rState.mpTextColor = GetTextColor();
+
+ if (nFlags & vcl::PushFlags::TEXTFILLCOLOR && IsTextFillColor())
+ rState.mpTextFillColor = GetTextFillColor();
+
+ if (nFlags & vcl::PushFlags::TEXTLINECOLOR && IsTextLineColor())
+ rState.mpTextLineColor = GetTextLineColor();
+
+ if (nFlags & vcl::PushFlags::OVERLINECOLOR && IsOverlineColor())
+ rState.mpOverlineColor = GetOverlineColor();
+
+ if (nFlags & vcl::PushFlags::TEXTALIGN)
+ rState.meTextAlign = GetTextAlign();
+
+ if (nFlags & vcl::PushFlags::TEXTLAYOUTMODE)
+ rState.mnTextLayoutMode = GetLayoutMode();
+
+ if (nFlags & vcl::PushFlags::TEXTLANGUAGE)
+ rState.meTextLanguage = GetDigitLanguage();
+
+ if (nFlags & vcl::PushFlags::RASTEROP)
+ rState.meRasterOp = GetRasterOp();
+
+ if (nFlags & vcl::PushFlags::MAPMODE)
+ {
+ rState.mpMapMode = maMapMode;
+ rState.mbMapActive = mbMap;
+ }
+
+ if (nFlags & vcl::PushFlags::CLIPREGION && mbClipRegion)
+ rState.mpClipRegion.reset(new vcl::Region(maRegion));
+
+ if (nFlags & vcl::PushFlags::REFPOINT && mbRefPoint)
+ rState.mpRefPoint = maRefPoint;
+
+ if (mpAlphaVDev)
+ mpAlphaVDev->Push();
+}
+
+void OutputDevice::Pop()
+{
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPopAction() );
+
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ mpMetaFile = nullptr;
+
+ if ( maOutDevStateStack.empty() )
+ {
+ SAL_WARN( "vcl.gdi", "OutputDevice::Pop() without OutputDevice::Push()" );
+ return;
+ }
+ const vcl::State& rState = maOutDevStateStack.back();
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->Pop();
+
+ if ( rState.mnFlags & vcl::PushFlags::LINECOLOR )
+ {
+ if ( rState.mpLineColor )
+ SetLineColor( *rState.mpLineColor );
+ else
+ SetLineColor();
+ }
+
+ if ( rState.mnFlags & vcl::PushFlags::FILLCOLOR )
+ {
+ if ( rState.mpFillColor )
+ SetFillColor( *rState.mpFillColor );
+ else
+ SetFillColor();
+ }
+
+ if ( rState.mnFlags & vcl::PushFlags::FONT )
+ SetFont( *rState.mpFont );
+
+ if ( rState.mnFlags & vcl::PushFlags::TEXTCOLOR )
+ SetTextColor( *rState.mpTextColor );
+
+ if ( rState.mnFlags & vcl::PushFlags::TEXTFILLCOLOR )
+ {
+ if ( rState.mpTextFillColor )
+ SetTextFillColor( *rState.mpTextFillColor );
+ else
+ SetTextFillColor();
+ }
+
+ if ( rState.mnFlags & vcl::PushFlags::TEXTLINECOLOR )
+ {
+ if ( rState.mpTextLineColor )
+ SetTextLineColor( *rState.mpTextLineColor );
+ else
+ SetTextLineColor();
+ }
+
+ if ( rState.mnFlags & vcl::PushFlags::OVERLINECOLOR )
+ {
+ if ( rState.mpOverlineColor )
+ SetOverlineColor( *rState.mpOverlineColor );
+ else
+ SetOverlineColor();
+ }
+
+ if ( rState.mnFlags & vcl::PushFlags::TEXTALIGN )
+ SetTextAlign( rState.meTextAlign );
+
+ if( rState.mnFlags & vcl::PushFlags::TEXTLAYOUTMODE )
+ SetLayoutMode( rState.mnTextLayoutMode );
+
+ if( rState.mnFlags & vcl::PushFlags::TEXTLANGUAGE )
+ SetDigitLanguage( rState.meTextLanguage );
+
+ if ( rState.mnFlags & vcl::PushFlags::RASTEROP )
+ SetRasterOp( rState.meRasterOp );
+
+ if ( rState.mnFlags & vcl::PushFlags::MAPMODE )
+ {
+ if ( rState.mpMapMode )
+ SetMapMode( *rState.mpMapMode );
+ else
+ SetMapMode();
+ mbMap = rState.mbMapActive;
+ }
+
+ if ( rState.mnFlags & vcl::PushFlags::CLIPREGION )
+ SetDeviceClipRegion( rState.mpClipRegion.get() );
+
+ if ( rState.mnFlags & vcl::PushFlags::REFPOINT )
+ {
+ if ( rState.mpRefPoint )
+ SetRefPoint( *rState.mpRefPoint );
+ else
+ SetRefPoint();
+ }
+
+ maOutDevStateStack.pop_back();
+
+ mpMetaFile = pOldMetaFile;
+}
+
+void OutputDevice::ClearStack()
+{
+ sal_uInt32 nCount = maOutDevStateStack.size();
+ while( nCount-- )
+ Pop();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/text.cxx b/vcl/source/outdev/text.cxx
new file mode 100644
index 000000000..5d8595abe
--- /dev/null
+++ b/vcl/source/outdev/text.cxx
@@ -0,0 +1,2532 @@
+/* -*- 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 <memory>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <com/sun/star/linguistic2/LinguServiceManager.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <osl/file.h>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/lineend.hxx>
+#include <tools/debug.hxx>
+#include <vcl/ctrl.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/textrectinfo.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/unohelp.hxx>
+
+#include <ImplLayoutArgs.hxx>
+#include <ImplOutDevData.hxx>
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+#include <svdata.hxx>
+#include <textlayout.hxx>
+#include <textlineinfo.hxx>
+#include <impglyphitem.hxx>
+#include <optional>
+#include <TextLayoutCache.hxx>
+#include <font/PhysicalFontFace.hxx>
+
+#define TEXT_DRAW_ELLIPSIS (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis)
+
+void OutputDevice::SetLayoutMode( vcl::text::ComplexTextLayoutFlags nTextLayoutMode )
+{
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaLayoutModeAction( nTextLayoutMode ) );
+
+ mnTextLayoutMode = nTextLayoutMode;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetLayoutMode( nTextLayoutMode );
+}
+
+void OutputDevice::SetDigitLanguage( LanguageType eTextLanguage )
+{
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextLanguageAction( eTextLanguage ) );
+
+ meTextLanguage = eTextLanguage;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetDigitLanguage( eTextLanguage );
+}
+
+ImplMultiTextLineInfo::ImplMultiTextLineInfo()
+{
+}
+
+ImplMultiTextLineInfo::~ImplMultiTextLineInfo()
+{
+}
+
+void ImplMultiTextLineInfo::AddLine( const ImplTextLineInfo& rLine )
+{
+ mvLines.push_back(rLine);
+}
+
+void ImplMultiTextLineInfo::Clear()
+{
+ mvLines.clear();
+}
+
+void OutputDevice::ImplInitTextColor()
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( mbInitTextColor )
+ {
+ mpGraphics->SetTextColor( GetTextColor() );
+ mbInitTextColor = false;
+ }
+}
+
+void OutputDevice::ImplDrawTextRect( tools::Long nBaseX, tools::Long nBaseY,
+ tools::Long nDistX, tools::Long nDistY, tools::Long nWidth, tools::Long nHeight )
+{
+ tools::Long nX = nDistX;
+ tools::Long nY = nDistY;
+
+ Degree10 nOrientation = mpFontInstance->mnOrientation;
+ if ( nOrientation )
+ {
+ // Rotate rect without rounding problems for 90 degree rotations
+ if ( !(nOrientation % 900_deg10) )
+ {
+ if ( nOrientation == 900_deg10 )
+ {
+ tools::Long nTemp = nX;
+ nX = nY;
+ nY = -nTemp;
+ nTemp = nWidth;
+ nWidth = nHeight;
+ nHeight = nTemp;
+ nY -= nHeight;
+ }
+ else if ( nOrientation == 1800_deg10 )
+ {
+ nX = -nX;
+ nY = -nY;
+ nX -= nWidth;
+ nY -= nHeight;
+ }
+ else /* ( nOrientation == 2700 ) */
+ {
+ tools::Long nTemp = nX;
+ nX = -nY;
+ nY = nTemp;
+ nTemp = nWidth;
+ nWidth = nHeight;
+ nHeight = nTemp;
+ nX -= nWidth;
+ }
+ }
+ else
+ {
+ nX += nBaseX;
+ nY += nBaseY;
+ // inflate because polygons are drawn smaller
+ tools::Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) );
+ tools::Polygon aPoly( aRect );
+ aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontInstance->mnOrientation );
+ ImplDrawPolygon( aPoly );
+ return;
+ }
+ }
+
+ nX += nBaseX;
+ nY += nBaseY;
+ mpGraphics->DrawRect( nX, nY, nWidth, nHeight, *this ); // original code
+
+}
+
+void OutputDevice::ImplDrawTextBackground( const SalLayout& rSalLayout )
+{
+ const tools::Long nWidth = rSalLayout.GetTextWidth();
+ const DevicePoint aBase = rSalLayout.DrawBase();
+ const tools::Long nX = aBase.getX();
+ const tools::Long nY = aBase.getY();
+
+ if ( mbLineColor || mbInitLineColor )
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+ mpGraphics->SetFillColor( GetTextFillColor() );
+ mbInitFillColor = true;
+
+ ImplDrawTextRect( nX, nY, 0, -(mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent),
+ nWidth,
+ mpFontInstance->mnLineHeight+mnEmphasisAscent+mnEmphasisDescent );
+}
+
+tools::Rectangle OutputDevice::ImplGetTextBoundRect( const SalLayout& rSalLayout ) const
+{
+ DevicePoint aPoint = rSalLayout.GetDrawPosition();
+ tools::Long nX = aPoint.getX();
+ tools::Long nY = aPoint.getY();
+
+ tools::Long nWidth = rSalLayout.GetTextWidth();
+ tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
+
+ nY -= mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;
+
+ if ( mpFontInstance->mnOrientation )
+ {
+ tools::Long nBaseX = nX, nBaseY = nY;
+ if ( !(mpFontInstance->mnOrientation % 900_deg10) )
+ {
+ tools::Long nX2 = nX+nWidth;
+ tools::Long nY2 = nY+nHeight;
+
+ Point aBasePt( nBaseX, nBaseY );
+ aBasePt.RotateAround( nX, nY, mpFontInstance->mnOrientation );
+ aBasePt.RotateAround( nX2, nY2, mpFontInstance->mnOrientation );
+ nWidth = nX2-nX;
+ nHeight = nY2-nY;
+ }
+ else
+ {
+ // inflate by +1+1 because polygons are drawn smaller
+ tools::Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) );
+ tools::Polygon aPoly( aRect );
+ aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontInstance->mnOrientation );
+ return aPoly.GetBoundRect();
+ }
+ }
+
+ return tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) );
+}
+
+bool OutputDevice::ImplDrawRotateText( SalLayout& rSalLayout )
+{
+ tools::Long nX = rSalLayout.DrawBase().getX();
+ tools::Long nY = rSalLayout.DrawBase().getY();
+
+ tools::Rectangle aBoundRect;
+ rSalLayout.DrawBase() = DevicePoint( 0, 0 );
+ rSalLayout.DrawOffset() = Point( 0, 0 );
+ if (!rSalLayout.GetBoundRect(aBoundRect))
+ {
+ // guess vertical text extents if GetBoundRect failed
+ tools::Long nRight = rSalLayout.GetTextWidth();
+ tools::Long nTop = mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;
+ tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
+ aBoundRect = tools::Rectangle( 0, -nTop, nRight, nHeight - nTop );
+ }
+
+ // cache virtual device for rotation
+ if (!mpOutDevData->mpRotateDev)
+ mpOutDevData->mpRotateDev = VclPtr<VirtualDevice>::Create(*this);
+ VirtualDevice* pVDev = mpOutDevData->mpRotateDev;
+
+ // size it accordingly
+ if( !pVDev->SetOutputSizePixel( aBoundRect.GetSize() ) )
+ return false;
+
+ const vcl::font::FontSelectPattern& rPattern = mpFontInstance->GetFontSelectPattern();
+ vcl::Font aFont( GetFont() );
+ aFont.SetOrientation( 0_deg10 );
+ aFont.SetFontSize( Size( rPattern.mnWidth, rPattern.mnHeight ) );
+ pVDev->SetFont( aFont );
+ pVDev->SetTextColor( COL_BLACK );
+ pVDev->SetTextFillColor();
+ if (!pVDev->InitFont())
+ return false;
+ pVDev->ImplInitTextColor();
+
+ // draw text into upper left corner
+ rSalLayout.DrawBase().adjustX(-aBoundRect.Left());
+ rSalLayout.DrawBase().adjustY(-aBoundRect.Top());
+ rSalLayout.DrawText( *pVDev->mpGraphics );
+
+ Bitmap aBmp = pVDev->GetBitmap( Point(), aBoundRect.GetSize() );
+ if ( aBmp.IsEmpty() || !aBmp.Rotate( mpFontInstance->mnOwnOrientation, COL_WHITE ) )
+ return false;
+
+ // calculate rotation offset
+ tools::Polygon aPoly( aBoundRect );
+ aPoly.Rotate( Point(), mpFontInstance->mnOwnOrientation );
+ Point aPoint = aPoly.GetBoundRect().TopLeft();
+ aPoint += Point( nX, nY );
+
+ // mask output with text colored bitmap
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ tools::Long nOldOffX = mnOutOffX;
+ tools::Long nOldOffY = mnOutOffY;
+ bool bOldMap = mbMap;
+
+ mnOutOffX = 0;
+ mnOutOffY = 0;
+ mpMetaFile = nullptr;
+ EnableMapMode( false );
+
+ DrawMask( aPoint, aBmp, GetTextColor() );
+
+ EnableMapMode( bOldMap );
+ mnOutOffX = nOldOffX;
+ mnOutOffY = nOldOffY;
+ mpMetaFile = pOldMetaFile;
+
+ return true;
+}
+
+void OutputDevice::ImplDrawTextDirect( SalLayout& rSalLayout,
+ bool bTextLines)
+{
+ if( mpFontInstance->mnOwnOrientation )
+ if( ImplDrawRotateText( rSalLayout ) )
+ return;
+
+ auto nOldX = rSalLayout.DrawBase().getX();
+ if( HasMirroredGraphics() )
+ {
+ tools::Long w = IsVirtual() ? mnOutWidth : mpGraphics->GetGraphicsWidth();
+ auto x = rSalLayout.DrawBase().getX();
+ rSalLayout.DrawBase().setX( w - 1 - x );
+ if( !IsRTLEnabled() )
+ {
+ OutputDevice *pOutDevRef = this;
+ // mirror this window back
+ tools::Long devX = w-pOutDevRef->mnOutWidth-pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX
+ rSalLayout.DrawBase().setX( devX + ( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().getX() - devX) ) ) ;
+ }
+ }
+ else if( IsRTLEnabled() )
+ {
+ OutputDevice *pOutDevRef = this;
+
+ // mirror this window back
+ tools::Long devX = pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX
+ rSalLayout.DrawBase().setX( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().getX() - devX) + devX );
+ }
+
+ rSalLayout.DrawText( *mpGraphics );
+ rSalLayout.DrawBase().setX( nOldX );
+
+ if( bTextLines )
+ ImplDrawTextLines( rSalLayout,
+ maFont.GetStrikeout(), maFont.GetUnderline(), maFont.GetOverline(),
+ maFont.IsWordLineMode(), maFont.IsUnderlineAbove() );
+
+ // emphasis marks
+ if( maFont.GetEmphasisMark() & FontEmphasisMark::Style )
+ ImplDrawEmphasisMarks( rSalLayout );
+}
+
+void OutputDevice::ImplDrawSpecialText( SalLayout& rSalLayout )
+{
+ Color aOldColor = GetTextColor();
+ Color aOldTextLineColor = GetTextLineColor();
+ Color aOldOverlineColor = GetOverlineColor();
+ FontRelief eRelief = maFont.GetRelief();
+
+ DevicePoint aOrigPos = rSalLayout.DrawBase();
+ if ( eRelief != FontRelief::NONE )
+ {
+ Color aReliefColor( COL_LIGHTGRAY );
+ Color aTextColor( aOldColor );
+
+ Color aTextLineColor( aOldTextLineColor );
+ Color aOverlineColor( aOldOverlineColor );
+
+ // we don't have an automatic color, so black is always drawn on white
+ if ( aTextColor == COL_BLACK )
+ aTextColor = COL_WHITE;
+ if ( aTextLineColor == COL_BLACK )
+ aTextLineColor = COL_WHITE;
+ if ( aOverlineColor == COL_BLACK )
+ aOverlineColor = COL_WHITE;
+
+ // relief-color is black for white text, in all other cases
+ // we set this to LightGray
+ // coverity[copy_paste_error: FALSE] - this is intentional
+ if ( aTextColor == COL_WHITE )
+ aReliefColor = COL_BLACK;
+ SetTextLineColor( aReliefColor );
+ SetOverlineColor( aReliefColor );
+ SetTextColor( aReliefColor );
+ ImplInitTextColor();
+
+ // calculate offset - for high resolution printers the offset
+ // should be greater so that the effect is visible
+ tools::Long nOff = 1;
+ nOff += mnDPIX/300;
+
+ if ( eRelief == FontRelief::Engraved )
+ nOff = -nOff;
+ rSalLayout.DrawOffset() += Point( nOff, nOff);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawOffset() -= Point( nOff, nOff);
+
+ SetTextLineColor( aTextLineColor );
+ SetOverlineColor( aOverlineColor );
+ SetTextColor( aTextColor );
+ ImplInitTextColor();
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+
+ SetTextLineColor( aOldTextLineColor );
+ SetOverlineColor( aOldOverlineColor );
+
+ if ( aTextColor != aOldColor )
+ {
+ SetTextColor( aOldColor );
+ ImplInitTextColor();
+ }
+ }
+ else
+ {
+ if ( maFont.IsShadow() )
+ {
+ tools::Long nOff = 1 + ((mpFontInstance->mnLineHeight-24)/24);
+ if ( maFont.IsOutline() )
+ nOff++;
+ SetTextLineColor();
+ SetOverlineColor();
+ if ( (GetTextColor() == COL_BLACK)
+ || (GetTextColor().GetLuminance() < 8) )
+ SetTextColor( COL_LIGHTGRAY );
+ else
+ SetTextColor( COL_BLACK );
+ ImplInitTextColor();
+ rSalLayout.DrawBase() += DevicePoint( nOff, nOff );
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() -= DevicePoint( nOff, nOff );
+ SetTextColor( aOldColor );
+ SetTextLineColor( aOldTextLineColor );
+ SetOverlineColor( aOldOverlineColor );
+ ImplInitTextColor();
+
+ if ( !maFont.IsOutline() )
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ }
+
+ if ( maFont.IsOutline() )
+ {
+ rSalLayout.DrawBase() = aOrigPos + DevicePoint(-1,-1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + DevicePoint(+1,+1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + DevicePoint(-1,+0);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + DevicePoint(-1,+1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + DevicePoint(+0,+1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + DevicePoint(+0,-1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + DevicePoint(+1,-1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + DevicePoint(+1,+0);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos;
+
+ SetTextColor( COL_WHITE );
+ SetTextLineColor( COL_WHITE );
+ SetOverlineColor( COL_WHITE );
+ ImplInitTextColor();
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ SetTextColor( aOldColor );
+ SetTextLineColor( aOldTextLineColor );
+ SetOverlineColor( aOldOverlineColor );
+ ImplInitTextColor();
+ }
+ }
+}
+
+void OutputDevice::ImplDrawText( SalLayout& rSalLayout )
+{
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+ if( mbOutputClipped )
+ return;
+ if( mbInitTextColor )
+ ImplInitTextColor();
+
+ rSalLayout.DrawBase() += DevicePoint(mnTextOffX, mnTextOffY);
+
+ if( IsTextFillColor() )
+ ImplDrawTextBackground( rSalLayout );
+
+ if( mbTextSpecial )
+ ImplDrawSpecialText( rSalLayout );
+ else
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+}
+
+tools::Long OutputDevice::ImplGetTextLines( const tools::Rectangle& rRect, const tools::Long nTextHeight,
+ ImplMultiTextLineInfo& rLineInfo,
+ tools::Long nWidth, const OUString& rStr,
+ DrawTextFlags nStyle, const vcl::ITextLayout& _rLayout )
+{
+ SAL_WARN_IF( nWidth <= 0, "vcl", "ImplGetTextLines: nWidth <= 0!" );
+
+ if ( nWidth <= 0 )
+ nWidth = 1;
+
+ rLineInfo.Clear();
+ if (rStr.isEmpty())
+ return 0;
+
+ const bool bClipping = (nStyle & DrawTextFlags::Clip) && !(nStyle & DrawTextFlags::EndEllipsis);
+
+ tools::Long nMaxLineWidth = 0;
+ const bool bHyphenate = (nStyle & DrawTextFlags::WordBreakHyphenation) == DrawTextFlags::WordBreakHyphenation;
+ css::uno::Reference< css::linguistic2::XHyphenator > xHyph;
+ if (bHyphenate)
+ {
+ // get service provider
+ css::uno::Reference<css::uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
+ css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLinguMgr = css::linguistic2::LinguServiceManager::create(xContext);
+ xHyph = xLinguMgr->getHyphenator();
+ }
+
+ css::uno::Reference<css::i18n::XBreakIterator> xBI;
+ sal_Int32 nPos = 0;
+ sal_Int32 nLen = rStr.getLength();
+ sal_Int32 nCurrentTextY = 0;
+ while ( nPos < nLen )
+ {
+ sal_Int32 nBreakPos = nPos;
+
+ while ( ( nBreakPos < nLen ) && ( rStr[ nBreakPos ] != '\r' ) && ( rStr[ nBreakPos ] != '\n' ) )
+ nBreakPos++;
+
+ tools::Long nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos );
+ if ( ( nLineWidth > nWidth ) && ( nStyle & DrawTextFlags::WordBreak ) )
+ {
+ if ( !xBI.is() )
+ xBI = vcl::unohelper::CreateBreakIterator();
+
+ if ( xBI.is() )
+ {
+ nBreakPos = ImplBreakLinesWithIterator(nWidth, rStr, _rLayout, xHyph, xBI, bHyphenate, nPos, nBreakPos);
+ nLineWidth = _rLayout.GetTextWidth(rStr, nPos, nBreakPos - nPos);
+ }
+ else
+ // fallback to something really simple
+ nBreakPos = ImplBreakLinesSimple(nWidth, rStr, _rLayout, nPos, nBreakPos, nLineWidth);
+ }
+
+ if ( nLineWidth > nMaxLineWidth )
+ nMaxLineWidth = nLineWidth;
+
+ rLineInfo.AddLine( ImplTextLineInfo( nLineWidth, nPos, nBreakPos-nPos ) );
+
+ if ( nBreakPos == nPos )
+ nBreakPos++;
+ nPos = nBreakPos;
+
+ if ( nPos < nLen && ( ( rStr[ nPos ] == '\r' ) || ( rStr[ nPos ] == '\n' ) ) )
+ {
+ nPos++;
+ // CR/LF?
+ if ( ( nPos < nLen ) && ( rStr[ nPos ] == '\n' ) && ( rStr[ nPos-1 ] == '\r' ) )
+ nPos++;
+ }
+ nCurrentTextY += nTextHeight;
+ if (bClipping && nCurrentTextY > rRect.GetHeight())
+ break;
+ }
+
+#ifdef DBG_UTIL
+ for ( sal_Int32 nL = 0; nL < rLineInfo.Count(); nL++ )
+ {
+ ImplTextLineInfo& rLine = rLineInfo.GetLine( nL );
+ OUString aLine = rStr.copy( rLine.GetIndex(), rLine.GetLen() );
+ SAL_WARN_IF( aLine.indexOf( '\r' ) != -1, "vcl", "ImplGetTextLines - Found CR!" );
+ SAL_WARN_IF( aLine.indexOf( '\n' ) != -1, "vcl", "ImplGetTextLines - Found LF!" );
+ }
+#endif
+
+ return nMaxLineWidth;
+}
+
+sal_Int32 OutputDevice::ImplBreakLinesWithIterator(const tools::Long nWidth, const OUString& rStr, const vcl::ITextLayout& _rLayout,
+ const css::uno::Reference< css::linguistic2::XHyphenator >& xHyph,
+ const css::uno::Reference<css::i18n::XBreakIterator>& xBI,
+ const bool bHyphenate,
+ const sal_Int32 nPos, sal_Int32 nBreakPos)
+{
+ const css::lang::Locale& rDefLocale(Application::GetSettings().GetUILanguageTag().getLocale());
+ sal_Int32 nSoftBreak = _rLayout.GetTextBreak( rStr, nWidth, nPos, nBreakPos - nPos );
+ if (nSoftBreak == -1)
+ {
+ nSoftBreak = nPos;
+ }
+ SAL_WARN_IF( nSoftBreak >= nBreakPos, "vcl", "Break?!" );
+ css::i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, css::uno::Sequence <css::beans::PropertyValue>(), 1 );
+ css::i18n::LineBreakUserOptions aUserOptions;
+ css::i18n::LineBreakResults aLBR = xBI->getLineBreak( rStr, nSoftBreak, rDefLocale, nPos, aHyphOptions, aUserOptions );
+ nBreakPos = aLBR.breakIndex;
+ if ( nBreakPos <= nPos )
+ nBreakPos = nSoftBreak;
+ if ( !bHyphenate )
+ return nBreakPos;
+
+ // Whether hyphen or not: Put the word after the hyphen through
+ // word boundary.
+
+ // nMaxBreakPos the last char that fits into the line
+ // nBreakPos is the word's start
+
+ // We run into a problem if the doc is so narrow, that a word
+ // is broken into more than two lines ...
+ if ( !xHyph.is() )
+ return nBreakPos;
+
+ css::i18n::Boundary aBoundary = xBI->getWordBoundary( rStr, nBreakPos, rDefLocale, css::i18n::WordType::DICTIONARY_WORD, true );
+ sal_Int32 nWordStart = nPos;
+ sal_Int32 nWordEnd = aBoundary.endPos;
+ SAL_WARN_IF( nWordEnd <= nWordStart, "vcl", "ImpBreakLine: Start >= End?" );
+
+ sal_Int32 nWordLen = nWordEnd - nWordStart;
+ if ( ( nWordEnd < nSoftBreak ) || ( nWordLen <= 3 ) )
+ return nBreakPos;
+
+ // #104415# May happen, because getLineBreak may differ from getWordBoundary with DICTIONARY_WORD
+ // SAL_WARN_IF( nWordEnd < nMaxBreakPos, "vcl", "Hyph: Break?" );
+ OUString aWord = rStr.copy( nWordStart, nWordLen );
+ sal_Int32 nMinTrail = nWordEnd-nSoftBreak+1; //+1: Before the "broken off" char
+ css::uno::Reference< css::linguistic2::XHyphenatedWord > xHyphWord;
+ if (xHyph.is())
+ xHyphWord = xHyph->hyphenate( aWord, rDefLocale, aWord.getLength() - nMinTrail, css::uno::Sequence< css::beans::PropertyValue >() );
+ if (!xHyphWord.is())
+ return nBreakPos;
+
+ bool bAlternate = xHyphWord->isAlternativeSpelling();
+ sal_Int32 _nWordLen = 1 + xHyphWord->getHyphenPos();
+
+ if ( ( _nWordLen < 2 ) || ( (nWordStart+_nWordLen) < 2 ) )
+ return nBreakPos;
+
+ if ( bAlternate )
+ {
+ nBreakPos = nWordStart + _nWordLen;
+ return nBreakPos;
+ }
+
+
+ OUString aAlt( xHyphWord->getHyphenatedWord() );
+
+ // We can have two cases:
+ // 1) "packen" turns into "pak-ken"
+ // 2) "Schiffahrt" turns into "Schiff-fahrt"
+
+ // In case 1 we need to replace a char
+ // In case 2 we add a char
+
+ // Correct recognition is made harder by words such as
+ // "Schiffahrtsbrennesseln", as the Hyphenator splits all
+ // positions of the word and comes up with "Schifffahrtsbrennnesseln"
+ // Thus, we cannot infer the aWord from the AlternativeWord's
+ // index.
+ // TODO: The whole junk will be made easier by a function in
+ // the Hyphenator, as soon as AMA adds it.
+ sal_Int32 nAltStart = _nWordLen - 1;
+ sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength());
+ sal_Int32 nTxtEnd = nTxtStart;
+ sal_Int32 nAltEnd = nAltStart;
+
+ // The area between nStart and nEnd is the difference
+ // between AlternativeString and OriginalString
+ while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() &&
+ aWord[nTxtEnd] != aAlt[nAltEnd] )
+ {
+ ++nTxtEnd;
+ ++nAltEnd;
+ }
+
+ // If a char was added, we notice it now:
+ if( nAltEnd > nTxtEnd && nAltStart == nAltEnd &&
+ aWord[ nTxtEnd ] == aAlt[nAltEnd] )
+ {
+ ++nAltEnd;
+ ++nTxtStart;
+ ++nTxtEnd;
+ }
+
+ SAL_WARN_IF( ( nAltEnd - nAltStart ) != 1, "vcl", "Alternate: Wrong assumption!" );
+
+ sal_Unicode cAlternateReplChar = 0;
+ if ( nTxtEnd > nTxtStart )
+ cAlternateReplChar = aAlt[ nAltStart ];
+
+ nBreakPos = nWordStart + nTxtStart;
+ if ( cAlternateReplChar )
+ nBreakPos++;
+ return nBreakPos;
+}
+
+sal_Int32 OutputDevice::ImplBreakLinesSimple( const tools::Long nWidth, const OUString& rStr,
+ const vcl::ITextLayout& _rLayout, const sal_Int32 nPos, sal_Int32 nBreakPos, tools::Long& nLineWidth )
+{
+ sal_Int32 nSpacePos = rStr.getLength();
+ tools::Long nW = 0;
+ do
+ {
+ nSpacePos = rStr.lastIndexOf( ' ', nSpacePos );
+ if( nSpacePos != -1 )
+ {
+ if( nSpacePos > nPos )
+ nSpacePos--;
+ nW = _rLayout.GetTextWidth( rStr, nPos, nSpacePos-nPos );
+ }
+ } while( nW > nWidth );
+
+ if( nSpacePos != -1 )
+ {
+ nBreakPos = nSpacePos;
+ nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos );
+ if( nBreakPos < rStr.getLength()-1 )
+ nBreakPos++;
+ }
+ return nBreakPos;
+}
+
+
+void OutputDevice::SetTextColor( const Color& rColor )
+{
+
+ Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextColorAction( aColor ) );
+
+ if ( maTextColor != aColor )
+ {
+ maTextColor = aColor;
+ mbInitTextColor = true;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextColor( COL_BLACK );
+}
+
+void OutputDevice::SetTextFillColor()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextFillColorAction( Color(), false ) );
+
+ if ( maFont.GetColor() != COL_TRANSPARENT ) {
+ maFont.SetFillColor( COL_TRANSPARENT );
+ }
+ if ( !maFont.IsTransparent() )
+ maFont.SetTransparent( true );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextFillColor();
+}
+
+void OutputDevice::SetTextFillColor( const Color& rColor )
+{
+ Color aColor(vcl::drawmode::GetFillColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextFillColorAction( aColor, true ) );
+
+ if ( maFont.GetFillColor() != aColor )
+ maFont.SetFillColor( aColor );
+ if ( maFont.IsTransparent() != rColor.IsTransparent() )
+ maFont.SetTransparent( rColor.IsTransparent() );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextFillColor( COL_BLACK );
+}
+
+Color OutputDevice::GetTextFillColor() const
+{
+ if ( maFont.IsTransparent() )
+ return COL_TRANSPARENT;
+ else
+ return maFont.GetFillColor();
+}
+
+void OutputDevice::SetTextAlign( TextAlign eAlign )
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextAlignAction( eAlign ) );
+
+ if ( maFont.GetAlignment() != eAlign )
+ {
+ maFont.SetAlignment( eAlign );
+ mbNewFont = true;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextAlign( eAlign );
+}
+
+vcl::Region OutputDevice::GetOutputBoundsClipRegion() const
+{
+ return GetClipRegion();
+}
+
+const SalLayoutFlags eDefaultLayout = SalLayoutFlags::NONE;
+
+void OutputDevice::DrawText( const Point& rStartPt, const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
+ const SalLayoutGlyphs* pLayoutCache
+ )
+{
+ assert(!is_double_buffered_window());
+
+ if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ if (mpOutDevData->mpRecordLayout)
+ {
+ pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects;
+ pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText;
+ }
+
+#if OSL_DEBUG_LEVEL > 2
+ SAL_INFO("vcl.gdi", "OutputDevice::DrawText(\"" << rStr << "\")");
+#endif
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextAction( rStartPt, rStr, nIndex, nLen ) );
+ if( pVector )
+ {
+ vcl::Region aClip(GetOutputBoundsClipRegion());
+
+ if (mpOutDevData->mpRecordLayout)
+ {
+ mpOutDevData->mpRecordLayout->m_aLineIndices.push_back( mpOutDevData->mpRecordLayout->m_aDisplayText.getLength() );
+ aClip.Intersect( mpOutDevData->maRecordRect );
+ }
+ if( ! aClip.IsNull() )
+ {
+ std::vector< tools::Rectangle > aTmp;
+ GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, aTmp );
+
+ bool bInserted = false;
+ for( std::vector< tools::Rectangle >::const_iterator it = aTmp.begin(); it != aTmp.end(); ++it, nIndex++ )
+ {
+ bool bAppend = false;
+
+ if( aClip.Overlaps( *it ) )
+ bAppend = true;
+ else if( rStr[ nIndex ] == ' ' && bInserted )
+ {
+ std::vector< tools::Rectangle >::const_iterator next = it;
+ ++next;
+ if( next != aTmp.end() && aClip.Overlaps( *next ) )
+ bAppend = true;
+ }
+
+ if( bAppend )
+ {
+ pVector->push_back( *it );
+ if( pDisplayText )
+ *pDisplayText += OUStringChar(rStr[ nIndex ]);
+ bInserted = true;
+ }
+ }
+ }
+ else
+ {
+ GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, *pVector );
+ if( pDisplayText )
+ *pDisplayText += rStr.subView( nIndex, nLen );
+ }
+ }
+
+ if ( !IsDeviceOutputNecessary() || pVector )
+ return;
+
+ if(mpFontInstance)
+ // do not use cache with modified string
+ if(mpFontInstance->mpConversion)
+ pLayoutCache = nullptr;
+
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, {}, eDefaultLayout, nullptr, pLayoutCache);
+ if(pSalLayout)
+ {
+ ImplDrawText( *pSalLayout );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawText( rStartPt, rStr, nIndex, nLen, pVector, pDisplayText );
+}
+
+tools::Long OutputDevice::GetTextWidth( const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen,
+ vcl::text::TextLayoutCache const*const pLayoutCache,
+ SalLayoutGlyphs const*const pSalLayoutCache) const
+{
+
+ tools::Long nWidth = GetTextArray( rStr, nullptr, nIndex,
+ nLen, pLayoutCache, pSalLayoutCache );
+
+ return nWidth;
+}
+
+tools::Long OutputDevice::GetTextHeight() const
+{
+ if (!InitFont())
+ return 0;
+
+ tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
+
+ if ( mbMap )
+ nHeight = ImplDevicePixelToLogicHeight( nHeight );
+
+ return nHeight;
+}
+
+float OutputDevice::approximate_char_width() const
+{
+ //note pango uses "The quick brown fox jumps over the lazy dog." for english
+ //and has a bunch of per-language strings which corresponds somewhat with
+ //makeRepresentativeText in include/svtools/sampletext.hxx
+ return GetTextWidth("aemnnxEM") / 8.0;
+}
+
+float OutputDevice::approximate_digit_width() const
+{
+ return GetTextWidth("0123456789") / 10.0;
+}
+
+void OutputDevice::DrawTextArray( const Point& rStartPt, const OUString& rStr,
+ o3tl::span<const sal_Int32> pDXAry,
+ sal_Int32 nIndex, sal_Int32 nLen, SalLayoutFlags flags,
+ const SalLayoutGlyphs* pSalLayoutCache )
+{
+ assert(!is_double_buffered_window());
+
+ if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, nIndex, nLen ) );
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+ if( mbInitClipRegion )
+ InitClipRegion();
+ if( mbOutputClipped )
+ return;
+
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry, flags, nullptr, pSalLayoutCache);
+ if( pSalLayout )
+ {
+ ImplDrawText( *pSalLayout );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawTextArray( rStartPt, rStr, pDXAry, nIndex, nLen, flags );
+}
+
+tools::Long OutputDevice::GetTextArray( const OUString& rStr, std::vector<sal_Int32>* pDXAry,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ vcl::text::TextLayoutCache const*const pLayoutCache,
+ SalLayoutGlyphs const*const pSalLayoutCache) const
+{
+ if( nIndex >= rStr.getLength() )
+ return 0; // TODO: this looks like a buggy caller?
+
+ if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ // do layout
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen,
+ Point(0,0), 0, {}, eDefaultLayout, pLayoutCache, pSalLayoutCache);
+ if( !pSalLayout )
+ {
+ // The caller expects this to init the elements of pDXAry.
+ // Adapting all the callers to check that GetTextArray succeeded seems
+ // too much work.
+ // Init here to 0 only in the (rare) error case, so that any missing
+ // element init in the happy case will still be found by tools,
+ // and hope that is sufficient.
+ if (pDXAry)
+ std::fill(pDXAry->begin(), pDXAry->end(), 0);
+ return 0;
+ }
+
+#if VCL_FLOAT_DEVICE_PIXEL
+ std::unique_ptr<std::vector<DeviceCoordinate>> xDXPixelArray;
+ if(pDXAry)
+ {
+ xDXPixelArray.reset(new std::vector<DeviceCoordinate>(nLen));
+ }
+ std::vector<DeviceCoordinate>* pDXPixelArray = xDXPixelArray.get();
+ DeviceCoordinate nWidth = pSalLayout->FillDXArray(pDXPixelArray);
+
+ // convert virtual char widths to virtual absolute positions
+ if( pDXPixelArray )
+ {
+ for( int i = 1; i < nLen; ++i )
+ {
+ (*pDXPixelArray)[i] += (*pDXPixelArray)[i - 1];
+ }
+ }
+ if( mbMap )
+ {
+ if( pDXPixelArray )
+ {
+ for( int i = 0; i < nLen; ++i )
+ {
+ (*pDXPixelArray)[i] = ImplDevicePixelToLogicWidth((*pDXPixelArray)[i]);
+ }
+ }
+ nWidth = ImplDevicePixelToLogicWidth( nWidth );
+ }
+ if(pDXAry)
+ {
+ pDXAry->resize(nLen);
+ for( int i = 0; i < nLen; ++i )
+ {
+ (*pDXAry)[i] = basegfx::fround((*pDXPixelArray)[i]);
+ }
+ }
+ return basegfx::fround(nWidth);
+
+#else /* ! VCL_FLOAT_DEVICE_PIXEL */
+
+ tools::Long nWidth = pSalLayout->FillDXArray( pDXAry );
+
+ // convert virtual char widths to virtual absolute positions
+ if( pDXAry )
+ for( int i = 1; i < nLen; ++i )
+ (*pDXAry)[ i ] += (*pDXAry)[ i-1 ];
+
+ // convert from font units to logical units
+ if( mbMap )
+ {
+ if( pDXAry )
+ for( int i = 0; i < nLen; ++i )
+ (*pDXAry)[i] = ImplDevicePixelToLogicWidth( (*pDXAry)[i] );
+ nWidth = ImplDevicePixelToLogicWidth( nWidth );
+ }
+
+ return nWidth;
+#endif /* VCL_FLOAT_DEVICE_PIXEL */
+}
+
+void OutputDevice::GetCaretPositions( const OUString& rStr, sal_Int32* pCaretXArray,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ const SalLayoutGlyphs* pGlyphs ) const
+{
+
+ if( nIndex >= rStr.getLength() )
+ return;
+ if( nIndex+nLen >= rStr.getLength() )
+ nLen = rStr.getLength() - nIndex;
+
+ // layout complex text
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, Point(0, 0), 0, {},
+ eDefaultLayout, nullptr, pGlyphs);
+ if( !pSalLayout )
+ {
+ std::fill(pCaretXArray, pCaretXArray + nLen * 2, -1);
+ return;
+ }
+
+ pSalLayout->GetCaretPositions( 2*nLen, pCaretXArray );
+ tools::Long nWidth = pSalLayout->GetTextWidth();
+
+ // fixup unknown caret positions
+ int i;
+ for( i = 0; i < 2 * nLen; ++i )
+ if( pCaretXArray[ i ] >= 0 )
+ break;
+ tools::Long nXPos = (i < 2 * nLen) ? pCaretXArray[i] : -1;
+ for( i = 0; i < 2 * nLen; ++i )
+ {
+ if( pCaretXArray[ i ] >= 0 )
+ nXPos = pCaretXArray[ i ];
+ else
+ pCaretXArray[ i ] = nXPos;
+ }
+
+ // handle window mirroring
+ if( IsRTLEnabled() )
+ {
+ for( i = 0; i < 2 * nLen; ++i )
+ pCaretXArray[i] = nWidth - pCaretXArray[i] - 1;
+ }
+
+ // convert from font units to logical units
+ if( mbMap )
+ {
+ for( i = 0; i < 2*nLen; ++i )
+ pCaretXArray[i] = ImplDevicePixelToLogicWidth( pCaretXArray[i] );
+ }
+}
+
+void OutputDevice::DrawStretchText( const Point& rStartPt, sal_uLong nWidth,
+ const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen)
+{
+ assert(!is_double_buffered_window());
+
+ if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaStretchTextAction( rStartPt, nWidth, rStr, nIndex, nLen ) );
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, nWidth);
+ if( pSalLayout )
+ {
+ ImplDrawText( *pSalLayout );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawStretchText( rStartPt, nWidth, rStr, nIndex, nLen );
+}
+
+vcl::text::ImplLayoutArgs OutputDevice::ImplPrepareLayoutArgs( OUString& rStr,
+ const sal_Int32 nMinIndex, const sal_Int32 nLen,
+ DeviceCoordinate nPixelWidth,
+ SalLayoutFlags nLayoutFlags,
+ vcl::text::TextLayoutCache const*const pLayoutCache) const
+{
+ assert(nMinIndex >= 0);
+ assert(nLen >= 0);
+
+ // get string length for calculating extents
+ sal_Int32 nEndIndex = rStr.getLength();
+ if( nMinIndex + nLen < nEndIndex )
+ nEndIndex = nMinIndex + nLen;
+
+ // don't bother if there is nothing to do
+ if( nEndIndex < nMinIndex )
+ nEndIndex = nMinIndex;
+
+ nLayoutFlags |= GetBiDiLayoutFlags( rStr, nMinIndex, nEndIndex );
+
+ if( !maFont.IsKerning() )
+ nLayoutFlags |= SalLayoutFlags::DisableKerning;
+ if( maFont.GetKerning() & FontKerning::Asian )
+ nLayoutFlags |= SalLayoutFlags::KerningAsian;
+ if( maFont.IsVertical() )
+ nLayoutFlags |= SalLayoutFlags::Vertical;
+
+ if( meTextLanguage ) //TODO: (mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::SubstituteDigits)
+ {
+ // disable character localization when no digits used
+ const sal_Unicode* pBase = rStr.getStr();
+ const sal_Unicode* pStr = pBase + nMinIndex;
+ const sal_Unicode* pEnd = pBase + nEndIndex;
+ std::optional<OUStringBuffer> xTmpStr;
+ for( ; pStr < pEnd; ++pStr )
+ {
+ // TODO: are there non-digit localizations?
+ if( (*pStr >= '0') && (*pStr <= '9') )
+ {
+ // translate characters to local preference
+ sal_UCS4 cChar = GetLocalizedChar( *pStr, meTextLanguage );
+ if( cChar != *pStr )
+ {
+ if (!xTmpStr)
+ xTmpStr = OUStringBuffer(rStr);
+ // TODO: are the localized digit surrogates?
+ (*xTmpStr)[pStr - pBase] = cChar;
+ }
+ }
+ }
+ if (xTmpStr)
+ rStr = (*xTmpStr).makeStringAndClear();
+ }
+
+ // right align for RTL text, DRAWPOS_REVERSED, RTL window style
+ bool bRightAlign = bool(mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl);
+ if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginLeft )
+ bRightAlign = false;
+ else if ( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginRight )
+ bRightAlign = true;
+ // SSA: hack for western office, ie text get right aligned
+ // for debugging purposes of mirrored UI
+ bool bRTLWindow = IsRTLEnabled();
+ bRightAlign ^= bRTLWindow;
+ if( bRightAlign )
+ nLayoutFlags |= SalLayoutFlags::RightAlign;
+
+ // set layout options
+ vcl::text::ImplLayoutArgs aLayoutArgs(rStr, nMinIndex, nEndIndex, nLayoutFlags, maFont.GetLanguageTag(), pLayoutCache);
+
+ Degree10 nOrientation = mpFontInstance ? mpFontInstance->mnOrientation : 0_deg10;
+ aLayoutArgs.SetOrientation( nOrientation );
+
+ aLayoutArgs.SetLayoutWidth( nPixelWidth );
+
+ return aLayoutArgs;
+}
+
+SalLayoutFlags OutputDevice::GetBiDiLayoutFlags( std::u16string_view rStr,
+ const sal_Int32 nMinIndex,
+ const sal_Int32 nEndIndex ) const
+{
+ SalLayoutFlags nLayoutFlags = SalLayoutFlags::NONE;
+ if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl )
+ nLayoutFlags |= SalLayoutFlags::BiDiRtl;
+ if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiStrong )
+ nLayoutFlags |= SalLayoutFlags::BiDiStrong;
+ else if( !(mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl) )
+ {
+ // Disable Bidi if no RTL hint and only known LTR codes used.
+ bool bAllLtr = true;
+ for (sal_Int32 i = nMinIndex; i < nEndIndex; i++)
+ {
+ // [0x0000, 0x052F] are Latin, Greek and Cyrillic.
+ // [0x0370, 0x03FF] has a few holes as if Unicode 10.0.0, but
+ // hopefully no RTL character will be encoded there.
+ if (rStr[i] > 0x052F)
+ {
+ bAllLtr = false;
+ break;
+ }
+ }
+ if (bAllLtr)
+ nLayoutFlags |= SalLayoutFlags::BiDiStrong;
+ }
+ return nLayoutFlags;
+}
+
+static OutputDevice::FontMappingUseData* fontMappingUseData = nullptr;
+
+static inline bool IsTrackingFontMappingUse()
+{
+ return fontMappingUseData != nullptr;
+}
+
+static void TrackFontMappingUse( const vcl::Font& originalFont, const SalLayout* salLayout)
+{
+ assert(fontMappingUseData);
+ OUString originalName = originalFont.GetStyleName().isEmpty()
+ ? originalFont.GetFamilyName()
+ : originalFont.GetFamilyName() + "/" + originalFont.GetStyleName();
+ std::vector<OUString> usedFontNames;
+ SalLayoutGlyphs glyphs = salLayout->GetGlyphs(); // includes all font fallbacks
+ int level = 0;
+ while( const SalLayoutGlyphsImpl* impl = glyphs.Impl(level++))
+ {
+ const vcl::font::PhysicalFontFace* face = impl->GetFont()->GetFontFace();
+ OUString name = face->GetStyleName().isEmpty()
+ ? face->GetFamilyName()
+ : face->GetFamilyName() + "/" + face->GetStyleName();
+ usedFontNames.push_back( name );
+ }
+ for( OutputDevice::FontMappingUseItem& item : *fontMappingUseData )
+ {
+ if( item.mOriginalFont == originalName && item.mUsedFonts == usedFontNames )
+ {
+ ++item.mCount;
+ return;
+ }
+ }
+ fontMappingUseData->push_back( { originalName, usedFontNames, 1 } );
+}
+
+void OutputDevice::StartTrackingFontMappingUse()
+{
+ delete fontMappingUseData;
+ fontMappingUseData = new FontMappingUseData;
+}
+
+OutputDevice::FontMappingUseData OutputDevice::FinishTrackingFontMappingUse()
+{
+ if(!fontMappingUseData)
+ return {};
+ FontMappingUseData ret = std::move( *fontMappingUseData );
+ delete fontMappingUseData;
+ fontMappingUseData = nullptr;
+ return ret;
+}
+
+std::unique_ptr<SalLayout> OutputDevice::ImplLayout(const OUString& rOrigStr,
+ sal_Int32 nMinIndex, sal_Int32 nLen,
+ const Point& rLogicalPos, tools::Long nLogicalWidth,
+ o3tl::span<const sal_Int32> pDXArray, SalLayoutFlags flags,
+ vcl::text::TextLayoutCache const* pLayoutCache,
+ const SalLayoutGlyphs* pGlyphs) const
+{
+ if (pGlyphs && !pGlyphs->IsValid())
+ {
+ SAL_WARN("vcl", "Trying to setup invalid cached glyphs - falling back to relayout!");
+ pGlyphs = nullptr;
+ }
+#ifdef DBG_UTIL
+ if (pGlyphs)
+ {
+ for( int level = 0;; ++level )
+ {
+ SalLayoutGlyphsImpl* glyphsImpl = pGlyphs->Impl(level);
+ if(glyphsImpl == nullptr)
+ break;
+ // It is allowed to reuse only glyphs created with SalLayoutFlags::GlyphItemsOnly.
+ // If the glyphs have already been used, the AdjustLayout() call below might have
+ // altered them (MultiSalLayout::ImplAdjustMultiLayout() drops glyphs that need
+ // fallback from the base layout, but then GenericSalLayout::LayoutText()
+ // would not know to call SetNeedFallback()).
+ assert(glyphsImpl->GetFlags() & SalLayoutFlags::GlyphItemsOnly);
+ }
+ }
+#endif
+
+ if (!InitFont())
+ return nullptr;
+
+ // check string index and length
+ if( -1 == nLen || nMinIndex + nLen > rOrigStr.getLength() )
+ {
+ const sal_Int32 nNewLen = rOrigStr.getLength() - nMinIndex;
+ if( nNewLen <= 0 )
+ return nullptr;
+ nLen = nNewLen;
+ }
+
+ OUString aStr = rOrigStr;
+
+ // convert from logical units to physical units
+ // recode string if needed
+ if( mpFontInstance->mpConversion ) {
+ mpFontInstance->mpConversion->RecodeString( aStr, 0, aStr.getLength() );
+ pLayoutCache = nullptr; // don't use cache with modified string!
+ pGlyphs = nullptr;
+ }
+
+ DeviceCoordinate nPixelWidth = static_cast<DeviceCoordinate>(nLogicalWidth);
+ if( nLogicalWidth && mbMap )
+ {
+ nPixelWidth = LogicWidthToDeviceCoordinate( nLogicalWidth );
+ }
+
+ vcl::text::ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nMinIndex, nLen,
+ nPixelWidth, flags, pLayoutCache);
+
+ bool bTextRenderModeForResolutionIndependentLayout(false);
+ DeviceCoordinate nEndGlyphCoord(0);
+ std::unique_ptr<DeviceCoordinate[]> xDXPixelArray;
+ std::unique_ptr<double[]> xNaturalDXPixelArray;
+ if( !pDXArray.empty() )
+ {
+ DeviceCoordinate* pDXPixelArray(nullptr);
+ if (mbMap)
+ {
+ if (GetTextRenderModeForResolutionIndependentLayout())
+ {
+ bTextRenderModeForResolutionIndependentLayout = true;
+
+ // convert from logical units to font units using a temporary array
+ xNaturalDXPixelArray.reset(new double[nLen]);
+
+ for (int i = 0; i < nLen; ++i)
+ xNaturalDXPixelArray[i] = ImplLogicWidthToDeviceFontWidth(pDXArray[i]);
+
+ aLayoutArgs.SetAltNaturalDXArray(xNaturalDXPixelArray.get());
+ nEndGlyphCoord = std::lround(xNaturalDXPixelArray[nLen - 1]);
+ }
+ else
+ {
+ // convert from logical units to font units using a temporary array
+ xDXPixelArray.reset(new DeviceCoordinate[nLen]);
+ pDXPixelArray = xDXPixelArray.get();
+ // using base position for better rounding a.k.a. "dancing characters"
+ DeviceCoordinate nPixelXOfs2 = LogicWidthToDeviceCoordinate(rLogicalPos.X() * 2);
+ for( int i = 0; i < nLen; ++i )
+ {
+ pDXPixelArray[i] = (LogicWidthToDeviceCoordinate((rLogicalPos.X() + pDXArray[i]) * 2) - nPixelXOfs2) / 2;
+ }
+
+ aLayoutArgs.SetDXArray(pDXPixelArray);
+ nEndGlyphCoord = pDXPixelArray[nLen - 1];
+ }
+
+ }
+ else
+ {
+#if VCL_FLOAT_DEVICE_PIXEL
+ xDXPixelArray.reset(new DeviceCoordinate[nLen]);
+ pDXPixelArray = xDXPixelArray.get();
+ for( int i = 0; i < nLen; ++i )
+ {
+ pDXPixelArray[i] = pDXArray[i];
+ }
+#else /* !VCL_FLOAT_DEVICE_PIXEL */
+ pDXPixelArray = const_cast<DeviceCoordinate*>(pDXArray.data());
+#endif /* !VCL_FLOAT_DEVICE_PIXEL */
+
+ aLayoutArgs.SetDXArray(pDXPixelArray);
+ nEndGlyphCoord = pDXPixelArray[nLen - 1];
+ }
+ }
+
+ // get matching layout object for base font
+ std::unique_ptr<SalLayout> pSalLayout = mpGraphics->GetTextLayout(0);
+
+ // layout text
+ if( pSalLayout && !pSalLayout->LayoutText( aLayoutArgs, pGlyphs ? pGlyphs->Impl(0) : nullptr ) )
+ {
+ pSalLayout.reset();
+ }
+
+ if( !pSalLayout )
+ return nullptr;
+
+ pSalLayout->SetTextRenderModeForResolutionIndependentLayout(bTextRenderModeForResolutionIndependentLayout);
+
+ // do glyph fallback if needed
+ // #105768# avoid fallback for very small font sizes
+ if (aLayoutArgs.HasFallbackRun() && mpFontInstance->GetFontSelectPattern().mnHeight >= 3)
+ pSalLayout = ImplGlyphFallbackLayout(std::move(pSalLayout), aLayoutArgs, pGlyphs);
+
+ if (flags & SalLayoutFlags::GlyphItemsOnly)
+ // Return glyph items only after fallback handling. Otherwise they may
+ // contain invalid glyph IDs.
+ return pSalLayout;
+
+ // position, justify, etc. the layout
+ pSalLayout->AdjustLayout( aLayoutArgs );
+
+ if (bTextRenderModeForResolutionIndependentLayout)
+ pSalLayout->DrawBase() = ImplLogicToDeviceFontCoordinate(rLogicalPos);
+ else
+ {
+ Point aDevicePos = ImplLogicToDevicePixel(rLogicalPos);
+ pSalLayout->DrawBase() = DevicePoint(aDevicePos.X(), aDevicePos.Y());
+ }
+
+ // adjust to right alignment if necessary
+ if( aLayoutArgs.mnFlags & SalLayoutFlags::RightAlign )
+ {
+ DeviceCoordinate nRTLOffset;
+ if (!pDXArray.empty())
+ nRTLOffset = nEndGlyphCoord;
+ else if( nPixelWidth )
+ nRTLOffset = nPixelWidth;
+ else
+ nRTLOffset = pSalLayout->GetTextWidth();
+ pSalLayout->DrawOffset().setX( 1 - nRTLOffset );
+ }
+
+ if(IsTrackingFontMappingUse())
+ TrackFontMappingUse(GetFont(), pSalLayout.get());
+
+ return pSalLayout;
+}
+
+std::shared_ptr<const vcl::text::TextLayoutCache> OutputDevice::CreateTextLayoutCache(
+ OUString const& rString)
+{
+ return vcl::text::TextLayoutCache::Create(rString);
+}
+
+bool OutputDevice::GetTextIsRTL( const OUString& rString, sal_Int32 nIndex, sal_Int32 nLen ) const
+{
+ OUString aStr( rString );
+ vcl::text::ImplLayoutArgs aArgs = ImplPrepareLayoutArgs(aStr, nIndex, nLen, 0);
+ bool bRTL = false;
+ int nCharPos = -1;
+ if (!aArgs.GetNextPos(&nCharPos, &bRTL))
+ return false;
+ return (nCharPos != nIndex);
+}
+
+sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, tools::Long nTextWidth,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ tools::Long nCharExtra,
+ vcl::text::TextLayoutCache const*const pLayoutCache,
+ const SalLayoutGlyphs* pGlyphs) const
+{
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
+ Point(0,0), 0, {}, eDefaultLayout, pLayoutCache, pGlyphs);
+ sal_Int32 nRetVal = -1;
+ if( pSalLayout )
+ {
+ // convert logical widths into layout units
+ // NOTE: be very careful to avoid rounding errors for nCharExtra case
+ // problem with rounding errors especially for small nCharExtras
+ // TODO: remove when layout units have subpixel granularity
+ tools::Long nSubPixelFactor = 64;
+ nTextWidth *= nSubPixelFactor;
+ DeviceCoordinate nTextPixelWidth = LogicWidthToDeviceCoordinate( nTextWidth );
+ DeviceCoordinate nExtraPixelWidth = 0;
+ if( nCharExtra != 0 )
+ {
+ nCharExtra *= nSubPixelFactor;
+ nExtraPixelWidth = LogicWidthToDeviceCoordinate( nCharExtra );
+ }
+ nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor );
+ }
+
+ return nRetVal;
+}
+
+sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, tools::Long nTextWidth,
+ sal_Unicode nHyphenChar, sal_Int32& rHyphenPos,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ tools::Long nCharExtra,
+ vcl::text::TextLayoutCache const*const pLayoutCache,
+ const SalLayoutGlyphs* pGlyphs) const
+{
+ rHyphenPos = -1;
+
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
+ Point(0,0), 0, {}, eDefaultLayout, pLayoutCache, pGlyphs);
+ sal_Int32 nRetVal = -1;
+ if( pSalLayout )
+ {
+ // convert logical widths into layout units
+ // NOTE: be very careful to avoid rounding errors for nCharExtra case
+ // problem with rounding errors especially for small nCharExtras
+ // TODO: remove when layout units have subpixel granularity
+ tools::Long nSubPixelFactor = 64;
+
+ nTextWidth *= nSubPixelFactor;
+ DeviceCoordinate nTextPixelWidth = LogicWidthToDeviceCoordinate( nTextWidth );
+ DeviceCoordinate nExtraPixelWidth = 0;
+ if( nCharExtra != 0 )
+ {
+ nCharExtra *= nSubPixelFactor;
+ nExtraPixelWidth = LogicWidthToDeviceCoordinate( nCharExtra );
+ }
+
+ // calculate un-hyphenated break position
+ nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor );
+
+ // calculate hyphenated break position
+ OUString aHyphenStr(nHyphenChar);
+ std::unique_ptr<SalLayout> pHyphenLayout = ImplLayout( aHyphenStr, 0, 1 );
+ if( pHyphenLayout )
+ {
+ // calculate subpixel width of hyphenation character
+ tools::Long nHyphenPixelWidth = pHyphenLayout->GetTextWidth() * nSubPixelFactor;
+
+ // calculate hyphenated break position
+ nTextPixelWidth -= nHyphenPixelWidth;
+ if( nExtraPixelWidth > 0 )
+ nTextPixelWidth -= nExtraPixelWidth;
+
+ rHyphenPos = pSalLayout->GetTextBreak(nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor);
+
+ if( rHyphenPos > nRetVal )
+ rHyphenPos = nRetVal;
+ }
+ }
+
+ return nRetVal;
+}
+
+void OutputDevice::ImplDrawText( OutputDevice& rTargetDevice, const tools::Rectangle& rRect,
+ const OUString& rOrigStr, DrawTextFlags nStyle,
+ std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
+ vcl::ITextLayout& _rLayout )
+{
+
+ Color aOldTextColor;
+ Color aOldTextFillColor;
+ bool bRestoreFillColor = false;
+ if ( (nStyle & DrawTextFlags::Disable) && ! pVector )
+ {
+ bool bHighContrastBlack = false;
+ bool bHighContrastWhite = false;
+ const StyleSettings& rStyleSettings( rTargetDevice.GetSettings().GetStyleSettings() );
+ if( rStyleSettings.GetHighContrastMode() )
+ {
+ Color aCol;
+ if( rTargetDevice.IsBackground() )
+ aCol = rTargetDevice.GetBackground().GetColor();
+ else
+ // best guess is the face color here
+ // but it may be totally wrong. the background color
+ // was typically already reset
+ aCol = rStyleSettings.GetFaceColor();
+
+ bHighContrastBlack = aCol.IsDark();
+ bHighContrastWhite = aCol.IsBright();
+ }
+
+ aOldTextColor = rTargetDevice.GetTextColor();
+ if ( rTargetDevice.IsTextFillColor() )
+ {
+ bRestoreFillColor = true;
+ aOldTextFillColor = rTargetDevice.GetTextFillColor();
+ }
+ if( bHighContrastBlack )
+ rTargetDevice.SetTextColor( COL_GREEN );
+ else if( bHighContrastWhite )
+ rTargetDevice.SetTextColor( COL_LIGHTGREEN );
+ else
+ {
+ // draw disabled text always without shadow
+ // as it fits better with native look
+ rTargetDevice.SetTextColor( rTargetDevice.GetSettings().GetStyleSettings().GetDisableColor() );
+ }
+ }
+
+ tools::Long nWidth = rRect.GetWidth();
+ tools::Long nHeight = rRect.GetHeight();
+
+ if ( ((nWidth <= 0) || (nHeight <= 0)) && (nStyle & DrawTextFlags::Clip) )
+ return;
+
+ Point aPos = rRect.TopLeft();
+
+ tools::Long nTextHeight = rTargetDevice.GetTextHeight();
+ TextAlign eAlign = rTargetDevice.GetTextAlign();
+ sal_Int32 nMnemonicPos = -1;
+
+ OUString aStr = rOrigStr;
+ if ( nStyle & DrawTextFlags::Mnemonic )
+ aStr = GetNonMnemonicString( aStr, nMnemonicPos );
+
+ const bool bDrawMnemonics = !(rTargetDevice.GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector;
+
+ // We treat multiline text differently
+ if ( nStyle & DrawTextFlags::MultiLine )
+ {
+
+ ImplMultiTextLineInfo aMultiLineInfo;
+ sal_Int32 i;
+ sal_Int32 nFormatLines;
+
+ if ( nTextHeight )
+ {
+ tools::Long nMaxTextWidth = ImplGetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle, _rLayout );
+ sal_Int32 nLines = static_cast<sal_Int32>(nHeight/nTextHeight);
+ OUString aLastLine;
+ nFormatLines = aMultiLineInfo.Count();
+ if (nLines <= 0)
+ nLines = 1;
+ if ( nFormatLines > nLines )
+ {
+ if ( nStyle & DrawTextFlags::EndEllipsis )
+ {
+ // Create last line and shorten it
+ nFormatLines = nLines-1;
+
+ ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines );
+ aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF);
+ // Replace all LineFeeds with Spaces
+ OUStringBuffer aLastLineBuffer(aLastLine);
+ sal_Int32 nLastLineLen = aLastLineBuffer.getLength();
+ for ( i = 0; i < nLastLineLen; i++ )
+ {
+ if ( aLastLineBuffer[ i ] == '\n' )
+ aLastLineBuffer[ i ] = ' ';
+ }
+ aLastLine = aLastLineBuffer.makeStringAndClear();
+ aLastLine = ImplGetEllipsisString( rTargetDevice, aLastLine, nWidth, nStyle, _rLayout );
+ nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
+ nStyle |= DrawTextFlags::Top;
+ }
+ }
+ else
+ {
+ if ( nMaxTextWidth <= nWidth )
+ nStyle &= ~DrawTextFlags::Clip;
+ }
+
+ // Do we need to clip the height?
+ if ( nFormatLines*nTextHeight > nHeight )
+ nStyle |= DrawTextFlags::Clip;
+
+ // Set clipping
+ if ( nStyle & DrawTextFlags::Clip )
+ {
+ rTargetDevice.Push( vcl::PushFlags::CLIPREGION );
+ rTargetDevice.IntersectClipRegion( rRect );
+ }
+
+ // Vertical alignment
+ if ( nStyle & DrawTextFlags::Bottom )
+ aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) );
+ else if ( nStyle & DrawTextFlags::VCenter )
+ aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 );
+
+ // Font alignment
+ if ( eAlign == ALIGN_BOTTOM )
+ aPos.AdjustY(nTextHeight );
+ else if ( eAlign == ALIGN_BASELINE )
+ aPos.AdjustY(rTargetDevice.GetFontMetric().GetAscent() );
+
+ // Output all lines except for the last one
+ for ( i = 0; i < nFormatLines; i++ )
+ {
+ ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
+ if ( nStyle & DrawTextFlags::Right )
+ aPos.AdjustX(nWidth-rLineInfo.GetWidth() );
+ else if ( nStyle & DrawTextFlags::Center )
+ aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 );
+ sal_Int32 nIndex = rLineInfo.GetIndex();
+ sal_Int32 nLineLen = rLineInfo.GetLen();
+ _rLayout.DrawText( aPos, aStr, nIndex, nLineLen, pVector, pDisplayText );
+ if ( bDrawMnemonics )
+ {
+ if ( (nMnemonicPos >= nIndex) && (nMnemonicPos < nIndex+nLineLen) )
+ {
+ tools::Long nMnemonicX;
+ tools::Long nMnemonicY;
+ DeviceCoordinate nMnemonicWidth;
+
+ std::unique_ptr<sal_Int32[]> const pCaretXArray(new sal_Int32[2 * nLineLen]);
+ /*sal_Bool bRet =*/ _rLayout.GetCaretPositions( aStr, pCaretXArray.get(),
+ nIndex, nLineLen );
+ sal_Int32 lc_x1 = pCaretXArray[2*(nMnemonicPos - nIndex)];
+ sal_Int32 lc_x2 = pCaretXArray[2*(nMnemonicPos - nIndex)+1];
+ nMnemonicWidth = rTargetDevice.LogicWidthToDeviceCoordinate( std::abs(lc_x1 - lc_x2) );
+
+ Point aTempPos = rTargetDevice.LogicToPixel( aPos );
+ nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min( lc_x1, lc_x2 ) );
+ nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() );
+ rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
+ }
+ }
+ aPos.AdjustY(nTextHeight );
+ aPos.setX( rRect.Left() );
+ }
+
+ // If there still is a last line, we output it left-aligned as the line would be clipped
+ if ( !aLastLine.isEmpty() )
+ _rLayout.DrawText( aPos, aLastLine, 0, aLastLine.getLength(), pVector, pDisplayText );
+
+ // Reset clipping
+ if ( nStyle & DrawTextFlags::Clip )
+ rTargetDevice.Pop();
+ }
+ }
+ else
+ {
+ tools::Long nTextWidth = _rLayout.GetTextWidth( aStr, 0, -1 );
+
+ // Clip text if needed
+ if ( nTextWidth > nWidth )
+ {
+ if ( nStyle & TEXT_DRAW_ELLIPSIS )
+ {
+ aStr = ImplGetEllipsisString( rTargetDevice, aStr, nWidth, nStyle, _rLayout );
+ nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
+ nStyle |= DrawTextFlags::Left;
+ nTextWidth = _rLayout.GetTextWidth( aStr, 0, aStr.getLength() );
+ }
+ }
+ else
+ {
+ if ( nTextHeight <= nHeight )
+ nStyle &= ~DrawTextFlags::Clip;
+ }
+
+ // horizontal text alignment
+ if ( nStyle & DrawTextFlags::Right )
+ aPos.AdjustX(nWidth-nTextWidth );
+ else if ( nStyle & DrawTextFlags::Center )
+ aPos.AdjustX((nWidth-nTextWidth)/2 );
+
+ // vertical font alignment
+ if ( eAlign == ALIGN_BOTTOM )
+ aPos.AdjustY(nTextHeight );
+ else if ( eAlign == ALIGN_BASELINE )
+ aPos.AdjustY(rTargetDevice.GetFontMetric().GetAscent() );
+
+ if ( nStyle & DrawTextFlags::Bottom )
+ aPos.AdjustY(nHeight-nTextHeight );
+ else if ( nStyle & DrawTextFlags::VCenter )
+ aPos.AdjustY((nHeight-nTextHeight)/2 );
+
+ tools::Long nMnemonicX = 0;
+ tools::Long nMnemonicY = 0;
+ DeviceCoordinate nMnemonicWidth = 0;
+ if (nMnemonicPos != -1 && nMnemonicPos < aStr.getLength())
+ {
+ std::unique_ptr<sal_Int32[]> const pCaretXArray(new sal_Int32[2 * aStr.getLength()]);
+ /*sal_Bool bRet =*/ _rLayout.GetCaretPositions( aStr, pCaretXArray.get(), 0, aStr.getLength() );
+ sal_Int32 lc_x1 = pCaretXArray[2*nMnemonicPos];
+ sal_Int32 lc_x2 = pCaretXArray[2*nMnemonicPos+1];
+ nMnemonicWidth = rTargetDevice.LogicWidthToDeviceCoordinate( std::abs(lc_x1 - lc_x2) );
+
+ Point aTempPos = rTargetDevice.LogicToPixel( aPos );
+ nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min(lc_x1, lc_x2) );
+ nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() );
+ }
+
+ if ( nStyle & DrawTextFlags::Clip )
+ {
+ rTargetDevice.Push( vcl::PushFlags::CLIPREGION );
+ rTargetDevice.IntersectClipRegion( rRect );
+ _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText );
+ if ( bDrawMnemonics && nMnemonicPos != -1 )
+ rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
+ rTargetDevice.Pop();
+ }
+ else
+ {
+ _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText );
+ if ( bDrawMnemonics && nMnemonicPos != -1 )
+ rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
+ }
+ }
+
+ if ( nStyle & DrawTextFlags::Disable && !pVector )
+ {
+ rTargetDevice.SetTextColor( aOldTextColor );
+ if ( bRestoreFillColor )
+ rTargetDevice.SetTextFillColor( aOldTextFillColor );
+ }
+}
+
+void OutputDevice::AddTextRectActions( const tools::Rectangle& rRect,
+ const OUString& rOrigStr,
+ DrawTextFlags nStyle,
+ GDIMetaFile& rMtf )
+{
+
+ if ( rOrigStr.isEmpty() || rRect.IsEmpty() )
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ // temporarily swap in passed mtf for action generation, and
+ // disable output generation.
+ const bool bOutputEnabled( IsOutputEnabled() );
+ GDIMetaFile* pMtf = mpMetaFile;
+
+ mpMetaFile = &rMtf;
+ EnableOutput( false );
+
+ // #i47157# Factored out to ImplDrawTextRect(), to be shared
+ // between us and DrawText()
+ vcl::DefaultTextLayout aLayout( *this );
+ ImplDrawText( *this, rRect, rOrigStr, nStyle, nullptr, nullptr, aLayout );
+
+ // and restore again
+ EnableOutput( bOutputEnabled );
+ mpMetaFile = pMtf;
+}
+
+void OutputDevice::DrawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle,
+ std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
+ vcl::ITextLayout* _pTextLayout )
+{
+ assert(!is_double_buffered_window());
+
+ if (mpOutDevData->mpRecordLayout)
+ {
+ pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects;
+ pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText;
+ }
+
+ bool bDecomposeTextRectAction = ( _pTextLayout != nullptr ) && _pTextLayout->DecomposeTextRectAction();
+ if ( mpMetaFile && !bDecomposeTextRectAction )
+ mpMetaFile->AddAction( new MetaTextRectAction( rRect, rOrigStr, nStyle ) );
+
+ if ( ( !IsDeviceOutputNecessary() && !pVector && !bDecomposeTextRectAction ) || rOrigStr.isEmpty() || rRect.IsEmpty() )
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+ if( mbInitClipRegion )
+ InitClipRegion();
+ if (mbOutputClipped && !bDecomposeTextRectAction && !pDisplayText)
+ return;
+
+ // temporarily disable mtf action generation (ImplDrawText _does_
+ // create MetaActionType::TEXTs otherwise)
+ GDIMetaFile* pMtf = mpMetaFile;
+ if ( !bDecomposeTextRectAction )
+ mpMetaFile = nullptr;
+
+ // #i47157# Factored out to ImplDrawText(), to be used also
+ // from AddTextRectActions()
+ vcl::DefaultTextLayout aDefaultLayout( *this );
+ ImplDrawText( *this, rRect, rOrigStr, nStyle, pVector, pDisplayText, _pTextLayout ? *_pTextLayout : aDefaultLayout );
+
+ // and enable again
+ mpMetaFile = pMtf;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawText( rRect, rOrigStr, nStyle, pVector, pDisplayText );
+}
+
+tools::Rectangle OutputDevice::GetTextRect( const tools::Rectangle& rRect,
+ const OUString& rStr, DrawTextFlags nStyle,
+ TextRectInfo* pInfo,
+ const vcl::ITextLayout* _pTextLayout ) const
+{
+
+ tools::Rectangle aRect = rRect;
+ sal_Int32 nLines;
+ tools::Long nWidth = rRect.GetWidth();
+ tools::Long nMaxWidth;
+ tools::Long nTextHeight = GetTextHeight();
+
+ OUString aStr = rStr;
+ if ( nStyle & DrawTextFlags::Mnemonic )
+ aStr = GetNonMnemonicString( aStr );
+
+ if ( nStyle & DrawTextFlags::MultiLine )
+ {
+ ImplMultiTextLineInfo aMultiLineInfo;
+ sal_Int32 nFormatLines;
+ sal_Int32 i;
+
+ nMaxWidth = 0;
+ vcl::DefaultTextLayout aDefaultLayout( *const_cast< OutputDevice* >( this ) );
+ ImplGetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle, _pTextLayout ? *_pTextLayout : aDefaultLayout );
+ nFormatLines = aMultiLineInfo.Count();
+ if ( !nTextHeight )
+ nTextHeight = 1;
+ nLines = static_cast<sal_uInt16>(aRect.GetHeight()/nTextHeight);
+ if ( pInfo )
+ pInfo->mnLineCount = nFormatLines;
+ if ( !nLines )
+ nLines = 1;
+ if ( nFormatLines <= nLines )
+ nLines = nFormatLines;
+ else
+ {
+ if ( !(nStyle & DrawTextFlags::EndEllipsis) )
+ nLines = nFormatLines;
+ else
+ {
+ if ( pInfo )
+ pInfo->mbEllipsis = true;
+ nMaxWidth = nWidth;
+ }
+ }
+ if ( pInfo )
+ {
+ bool bMaxWidth = nMaxWidth == 0;
+ pInfo->mnMaxWidth = 0;
+ for ( i = 0; i < nLines; i++ )
+ {
+ ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
+ if ( bMaxWidth && (rLineInfo.GetWidth() > nMaxWidth) )
+ nMaxWidth = rLineInfo.GetWidth();
+ if ( rLineInfo.GetWidth() > pInfo->mnMaxWidth )
+ pInfo->mnMaxWidth = rLineInfo.GetWidth();
+ }
+ }
+ else if ( !nMaxWidth )
+ {
+ for ( i = 0; i < nLines; i++ )
+ {
+ ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
+ if ( rLineInfo.GetWidth() > nMaxWidth )
+ nMaxWidth = rLineInfo.GetWidth();
+ }
+ }
+ }
+ else
+ {
+ nLines = 1;
+ nMaxWidth = _pTextLayout ? _pTextLayout->GetTextWidth( aStr, 0, aStr.getLength() ) : GetTextWidth( aStr );
+
+ if ( pInfo )
+ {
+ pInfo->mnLineCount = 1;
+ pInfo->mnMaxWidth = nMaxWidth;
+ }
+
+ if ( (nMaxWidth > nWidth) && (nStyle & TEXT_DRAW_ELLIPSIS) )
+ {
+ if ( pInfo )
+ pInfo->mbEllipsis = true;
+ nMaxWidth = nWidth;
+ }
+ }
+
+ if ( nStyle & DrawTextFlags::Right )
+ aRect.SetLeft( aRect.Right()-nMaxWidth+1 );
+ else if ( nStyle & DrawTextFlags::Center )
+ {
+ aRect.AdjustLeft((nWidth-nMaxWidth)/2 );
+ aRect.SetRight( aRect.Left()+nMaxWidth-1 );
+ }
+ else
+ aRect.SetRight( aRect.Left()+nMaxWidth-1 );
+
+ if ( nStyle & DrawTextFlags::Bottom )
+ aRect.SetTop( aRect.Bottom()-(nTextHeight*nLines)+1 );
+ else if ( nStyle & DrawTextFlags::VCenter )
+ {
+ aRect.AdjustTop((aRect.GetHeight()-(nTextHeight*nLines))/2 );
+ aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 );
+ }
+ else
+ aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 );
+
+ // #99188# get rid of rounding problems when using this rect later
+ if (nStyle & DrawTextFlags::Right)
+ aRect.AdjustLeft( -1 );
+ else
+ aRect.AdjustRight( 1 );
+ return aRect;
+}
+
+static bool ImplIsCharIn( sal_Unicode c, const char* pStr )
+{
+ while ( *pStr )
+ {
+ if ( *pStr == c )
+ return true;
+ pStr++;
+ }
+
+ return false;
+}
+
+OUString OutputDevice::GetEllipsisString( const OUString& rOrigStr, tools::Long nMaxWidth,
+ DrawTextFlags nStyle ) const
+{
+ vcl::DefaultTextLayout aTextLayout( *const_cast< OutputDevice* >( this ) );
+ return ImplGetEllipsisString( *this, rOrigStr, nMaxWidth, nStyle, aTextLayout );
+}
+
+OUString OutputDevice::ImplGetEllipsisString( const OutputDevice& rTargetDevice, const OUString& rOrigStr, tools::Long nMaxWidth,
+ DrawTextFlags nStyle, const vcl::ITextLayout& _rLayout )
+{
+ OUString aStr = rOrigStr;
+ sal_Int32 nIndex = _rLayout.GetTextBreak( aStr, nMaxWidth, 0, aStr.getLength() );
+
+ if ( nIndex != -1 )
+ {
+ if( (nStyle & DrawTextFlags::CenterEllipsis) == DrawTextFlags::CenterEllipsis )
+ {
+ OUStringBuffer aTmpStr( aStr );
+ // speed it up by removing all but 1.33x as many as the break pos.
+ sal_Int32 nEraseChars = std::max<sal_Int32>(4, aStr.getLength() - (nIndex*4)/3);
+ while( nEraseChars < aStr.getLength() && _rLayout.GetTextWidth( aTmpStr.toString(), 0, aTmpStr.getLength() ) > nMaxWidth )
+ {
+ aTmpStr = aStr;
+ sal_Int32 i = (aTmpStr.getLength() - nEraseChars)/2;
+ aTmpStr.remove(i, nEraseChars++);
+ aTmpStr.insert(i, "...");
+ }
+ aStr = aTmpStr.makeStringAndClear();
+ }
+ else if ( nStyle & DrawTextFlags::EndEllipsis )
+ {
+ aStr = aStr.copy(0, nIndex);
+ if ( nIndex > 1 )
+ {
+ aStr += "...";
+ while ( !aStr.isEmpty() && (_rLayout.GetTextWidth( aStr, 0, aStr.getLength() ) > nMaxWidth) )
+ {
+ if ( (nIndex > 1) || (nIndex == aStr.getLength()) )
+ nIndex--;
+ aStr = aStr.replaceAt( nIndex, 1, u"");
+ }
+ }
+
+ if ( aStr.isEmpty() && (nStyle & DrawTextFlags::Clip) )
+ aStr += OUStringChar(rOrigStr[ 0 ]);
+ }
+ else if ( nStyle & DrawTextFlags::PathEllipsis )
+ {
+ OUString aPath( rOrigStr );
+ OUString aAbbreviatedPath;
+ osl_abbreviateSystemPath( aPath.pData, &aAbbreviatedPath.pData, nIndex, nullptr );
+ aStr = aAbbreviatedPath;
+ }
+ else if ( nStyle & DrawTextFlags::NewsEllipsis )
+ {
+ static char const pSepChars[] = ".";
+ // Determine last section
+ sal_Int32 nLastContent = aStr.getLength();
+ while ( nLastContent )
+ {
+ nLastContent--;
+ if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) )
+ break;
+ }
+ while ( nLastContent &&
+ ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) )
+ nLastContent--;
+
+ OUString aLastStr = aStr.copy(nLastContent);
+ OUString aTempLastStr1 = "..." + aLastStr;
+ if ( _rLayout.GetTextWidth( aTempLastStr1, 0, aTempLastStr1.getLength() ) > nMaxWidth )
+ aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout );
+ else
+ {
+ sal_Int32 nFirstContent = 0;
+ while ( nFirstContent < nLastContent )
+ {
+ nFirstContent++;
+ if ( ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) )
+ break;
+ }
+ while ( (nFirstContent < nLastContent) &&
+ ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) )
+ nFirstContent++;
+ // MEM continue here
+ if ( nFirstContent >= nLastContent )
+ aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout );
+ else
+ {
+ if ( nFirstContent > 4 )
+ nFirstContent = 4;
+ OUString aFirstStr = OUString::Concat(aStr.subView( 0, nFirstContent )) + "...";
+ OUString aTempStr = aFirstStr + aLastStr;
+ if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth )
+ aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout );
+ else
+ {
+ do
+ {
+ aStr = aTempStr;
+ if( nLastContent > aStr.getLength() )
+ nLastContent = aStr.getLength();
+ while ( nFirstContent < nLastContent )
+ {
+ nLastContent--;
+ if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) )
+ break;
+
+ }
+ while ( (nFirstContent < nLastContent) &&
+ ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) )
+ nLastContent--;
+
+ if ( nFirstContent < nLastContent )
+ {
+ std::u16string_view aTempLastStr = aStr.subView( nLastContent );
+ aTempStr = aFirstStr + aTempLastStr;
+
+ if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth )
+ break;
+ }
+ }
+ while ( nFirstContent < nLastContent );
+ }
+ }
+ }
+ }
+ }
+
+ return aStr;
+}
+
+void OutputDevice::DrawCtrlText( const Point& rPos, const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ DrawTextFlags nStyle, std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
+ const SalLayoutGlyphs* pGlyphs )
+{
+ assert(!is_double_buffered_window());
+
+ if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ if ( !IsDeviceOutputNecessary() || (nIndex >= rStr.getLength()) )
+ return;
+
+ // better get graphics here because ImplDrawMnemonicLine() will not
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+ if( mbInitClipRegion )
+ InitClipRegion();
+ if ( mbOutputClipped )
+ return;
+
+ if( nIndex >= rStr.getLength() )
+ return;
+
+ if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+ OUString aStr = rStr;
+ sal_Int32 nMnemonicPos = -1;
+
+ tools::Long nMnemonicX = 0;
+ tools::Long nMnemonicY = 0;
+ tools::Long nMnemonicWidth = 0;
+ if ( (nStyle & DrawTextFlags::Mnemonic) && nLen > 1 )
+ {
+ aStr = GetNonMnemonicString( aStr, nMnemonicPos );
+ if ( nMnemonicPos != -1 )
+ {
+ if( nMnemonicPos < nIndex )
+ {
+ --nIndex;
+ }
+ else
+ {
+ if( nMnemonicPos < (nIndex+nLen) )
+ --nLen;
+ SAL_WARN_IF( nMnemonicPos >= (nIndex+nLen), "vcl", "Mnemonic underline marker after last character" );
+ }
+ bool bInvalidPos = false;
+
+ if( nMnemonicPos >= nLen )
+ {
+ // may occur in BiDi-Strings: the '~' is sometimes found behind the last char
+ // due to some strange BiDi text editors
+ // -> place the underline behind the string to indicate a failure
+ bInvalidPos = true;
+ nMnemonicPos = nLen-1;
+ }
+
+ std::unique_ptr<sal_Int32[]> const pCaretXArray(new sal_Int32[2 * nLen]);
+ /*sal_Bool bRet =*/ GetCaretPositions( aStr, pCaretXArray.get(), nIndex, nLen, pGlyphs );
+ sal_Int32 lc_x1 = pCaretXArray[ 2*(nMnemonicPos - nIndex) ];
+ sal_Int32 lc_x2 = pCaretXArray[ 2*(nMnemonicPos - nIndex)+1 ];
+ nMnemonicWidth = ::abs(static_cast<int>(lc_x1 - lc_x2));
+
+ Point aTempPos( std::min(lc_x1,lc_x2), GetFontMetric().GetAscent() );
+ if( bInvalidPos ) // #106952#, place behind the (last) character
+ aTempPos = Point( std::max(lc_x1,lc_x2), GetFontMetric().GetAscent() );
+
+ aTempPos += rPos;
+ aTempPos = LogicToPixel( aTempPos );
+ nMnemonicX = mnOutOffX + aTempPos.X();
+ nMnemonicY = mnOutOffY + aTempPos.Y();
+ }
+ }
+
+ bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
+
+ if ( nStyle & DrawTextFlags::Disable && ! pVector )
+ {
+ Color aOldTextColor;
+ Color aOldTextFillColor;
+ bool bRestoreFillColor;
+ bool bHighContrastBlack = false;
+ bool bHighContrastWhite = false;
+ const StyleSettings& rStyleSettings( GetSettings().GetStyleSettings() );
+ if( rStyleSettings.GetHighContrastMode() )
+ {
+ if( IsBackground() )
+ {
+ Wallpaper aWall = GetBackground();
+ Color aCol = aWall.GetColor();
+ bHighContrastBlack = aCol.IsDark();
+ bHighContrastWhite = aCol.IsBright();
+ }
+ }
+
+ aOldTextColor = GetTextColor();
+ if ( IsTextFillColor() )
+ {
+ bRestoreFillColor = true;
+ aOldTextFillColor = GetTextFillColor();
+ }
+ else
+ bRestoreFillColor = false;
+
+ if( bHighContrastBlack )
+ SetTextColor( COL_GREEN );
+ else if( bHighContrastWhite )
+ SetTextColor( COL_LIGHTGREEN );
+ else
+ SetTextColor( GetSettings().GetStyleSettings().GetDisableColor() );
+
+ DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText );
+ if (!(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics)
+ && (!autoacc || !(nStyle & DrawTextFlags::HideMnemonic)) )
+ {
+ if ( nMnemonicPos != -1 )
+ ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
+ }
+ SetTextColor( aOldTextColor );
+ if ( bRestoreFillColor )
+ SetTextFillColor( aOldTextFillColor );
+ }
+ else
+ {
+ DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText, pGlyphs );
+ if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector
+ && (!autoacc || !(nStyle & DrawTextFlags::HideMnemonic)) )
+ {
+ if ( nMnemonicPos != -1 )
+ ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawCtrlText( rPos, rStr, nIndex, nLen, nStyle, pVector, pDisplayText );
+}
+
+tools::Long OutputDevice::GetCtrlTextWidth( const OUString& rStr, const SalLayoutGlyphs* pGlyphs ) const
+{
+ sal_Int32 nLen = rStr.getLength();
+ sal_Int32 nIndex = 0;
+
+ sal_Int32 nMnemonicPos;
+ OUString aStr = GetNonMnemonicString( rStr, nMnemonicPos );
+ if ( nMnemonicPos != -1 )
+ {
+ if ( nMnemonicPos < nIndex )
+ nIndex--;
+ else if (static_cast<sal_uLong>(nMnemonicPos) < static_cast<sal_uLong>(nIndex+nLen))
+ nLen--;
+ }
+ return GetTextWidth( aStr, nIndex, nLen, nullptr, pGlyphs );
+}
+
+OUString OutputDevice::GetNonMnemonicString( const OUString& rStr, sal_Int32& rMnemonicPos )
+{
+ OUString aStr = rStr;
+ sal_Int32 nLen = aStr.getLength();
+ sal_Int32 i = 0;
+
+ rMnemonicPos = -1;
+ while ( i < nLen )
+ {
+ if ( aStr[ i ] == '~' )
+ {
+ if ( nLen <= i+1 )
+ break;
+
+ if ( aStr[ i+1 ] != '~' )
+ {
+ if ( rMnemonicPos == -1 )
+ rMnemonicPos = i;
+ aStr = aStr.replaceAt( i, 1, u"" );
+ nLen--;
+ }
+ else
+ {
+ aStr = aStr.replaceAt( i, 1, u"" );
+ nLen--;
+ i++;
+ }
+ }
+ else
+ i++;
+ }
+
+ return aStr;
+}
+
+bool OutputDevice::GetTextBoundRect( tools::Rectangle& rRect,
+ const OUString& rStr, sal_Int32 nBase,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ sal_uLong nLayoutWidth, o3tl::span<const sal_Int32> pDXAry,
+ const SalLayoutGlyphs* pGlyphs ) const
+{
+ bool bRet = false;
+ rRect.SetEmpty();
+
+ std::unique_ptr<SalLayout> pSalLayout;
+ const Point aPoint;
+ // calculate offset when nBase!=nIndex
+ tools::Long nXOffset = 0;
+ if( nBase != nIndex )
+ {
+ sal_Int32 nStart = std::min( nBase, nIndex );
+ sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart;
+ pSalLayout = ImplLayout( rStr, nStart, nOfsLen, aPoint, nLayoutWidth, pDXAry );
+ if( pSalLayout )
+ {
+ nXOffset = pSalLayout->GetTextWidth();
+ // TODO: fix offset calculation for Bidi case
+ if( nBase < nIndex)
+ nXOffset = -nXOffset;
+ }
+ }
+
+ pSalLayout = ImplLayout(rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry, eDefaultLayout,
+ nullptr, pGlyphs);
+ if( pSalLayout )
+ {
+ tools::Rectangle aPixelRect;
+ bRet = pSalLayout->GetBoundRect(aPixelRect);
+
+ if( bRet )
+ {
+ Point aRotatedOfs( mnTextOffX, mnTextOffY );
+ DevicePoint aPos = pSalLayout->GetDrawPosition(DevicePoint(nXOffset, 0));
+ aRotatedOfs -= Point(aPos.getX(), aPos.getY());
+ aPixelRect += aRotatedOfs;
+ rRect = PixelToLogic( aPixelRect );
+ if( mbMap )
+ rRect += Point( maMapRes.mnMapOfsX, maMapRes.mnMapOfsY );
+ }
+ }
+
+ return bRet;
+}
+
+bool OutputDevice::GetTextOutlines( basegfx::B2DPolyPolygonVector& rVector,
+ const OUString& rStr, sal_Int32 nBase,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ sal_uLong nLayoutWidth, o3tl::span<const sal_Int32> pDXArray ) const
+{
+ if (!InitFont())
+ return false;
+
+ bool bRet = false;
+ rVector.clear();
+ if( nLen < 0 )
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+ rVector.reserve( nLen );
+
+ // we want to get the Rectangle in logical units, so to
+ // avoid rounding errors we just size the font in logical units
+ bool bOldMap = mbMap;
+ if( bOldMap )
+ {
+ const_cast<OutputDevice&>(*this).mbMap = false;
+ const_cast<OutputDevice&>(*this).mbNewFont = true;
+ }
+
+ std::unique_ptr<SalLayout> pSalLayout;
+
+ // calculate offset when nBase!=nIndex
+ tools::Long nXOffset = 0;
+ if( nBase != nIndex )
+ {
+ sal_Int32 nStart = std::min( nBase, nIndex );
+ sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart;
+ pSalLayout = ImplLayout( rStr, nStart, nOfsLen, Point(0,0), nLayoutWidth, pDXArray );
+ if( pSalLayout )
+ {
+ nXOffset = pSalLayout->GetTextWidth();
+ pSalLayout.reset();
+ // TODO: fix offset calculation for Bidi case
+ if( nBase > nIndex)
+ nXOffset = -nXOffset;
+ }
+ }
+
+ pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), nLayoutWidth, pDXArray );
+ if( pSalLayout )
+ {
+ bRet = pSalLayout->GetOutline(rVector);
+ if( bRet )
+ {
+ // transform polygon to pixel units
+ basegfx::B2DHomMatrix aMatrix;
+
+ if( nXOffset | mnTextOffX | mnTextOffY )
+ {
+ DevicePoint aRotatedOfs(mnTextOffX, mnTextOffY);
+ aRotatedOfs -= pSalLayout->GetDrawPosition(DevicePoint(nXOffset, 0));
+ aMatrix.translate( aRotatedOfs.getX(), aRotatedOfs.getY() );
+ }
+
+ if( !aMatrix.isIdentity() )
+ {
+ for (auto & elem : rVector)
+ elem.transform( aMatrix );
+ }
+ }
+
+ pSalLayout.reset();
+ }
+
+ if( bOldMap )
+ {
+ // restore original font size and map mode
+ const_cast<OutputDevice&>(*this).mbMap = bOldMap;
+ const_cast<OutputDevice&>(*this).mbNewFont = true;
+ }
+
+ return bRet;
+}
+
+bool OutputDevice::GetTextOutlines( PolyPolyVector& rResultVector,
+ const OUString& rStr, sal_Int32 nBase,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ sal_uLong nLayoutWidth, o3tl::span<const sal_Int32> pDXArray ) const
+{
+ rResultVector.clear();
+
+ // get the basegfx polypolygon vector
+ basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
+ if( !GetTextOutlines( aB2DPolyPolyVector, rStr, nBase, nIndex, nLen,
+ nLayoutWidth, pDXArray ) )
+ return false;
+
+ // convert to a tool polypolygon vector
+ rResultVector.reserve( aB2DPolyPolyVector.size() );
+ for (auto const& elem : aB2DPolyPolyVector)
+ rResultVector.emplace_back(elem); // #i76339#
+
+ return true;
+}
+
+bool OutputDevice::GetTextOutline( tools::PolyPolygon& rPolyPoly, const OUString& rStr ) const
+{
+ rPolyPoly.Clear();
+
+ // get the basegfx polypolygon vector
+ basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
+ if( !GetTextOutlines( aB2DPolyPolyVector, rStr, 0/*nBase*/, 0/*nIndex*/, /*nLen*/-1,
+ /*nLayoutWidth*/0, /*pDXArray*/{} ) )
+ return false;
+
+ // convert and merge into a tool polypolygon
+ for (auto const& elem : aB2DPolyPolyVector)
+ for(auto const& rB2DPolygon : elem)
+ rPolyPoly.Insert(tools::Polygon(rB2DPolygon)); // #i76339#
+
+ return true;
+}
+
+void OutputDevice::SetSystemTextColor(SystemTextColorFlags nFlags, bool bEnabled)
+{
+ if (nFlags & SystemTextColorFlags::Mono)
+ {
+ SetTextColor(COL_BLACK);
+ }
+ else
+ {
+ if (!bEnabled)
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ SetTextColor(rStyleSettings.GetDisableColor());
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/textline.cxx b/vcl/source/outdev/textline.cxx
new file mode 100644
index 000000000..bfe5120f9
--- /dev/null
+++ b/vcl/source/outdev/textline.cxx
@@ -0,0 +1,1117 @@
+/* -*- 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 <cassert>
+
+#include <sal/types.h>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/lazydelete.hxx>
+
+#include <tools/helpers.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+#include <impglyphitem.hxx>
+
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/WaveLine.hxx>
+#include <o3tl/hash_combine.hxx>
+#include <o3tl/lru_map.hxx>
+
+#define UNDERLINE_LAST LINESTYLE_BOLDWAVE
+#define STRIKEOUT_LAST STRIKEOUT_X
+
+namespace {
+ struct WavyLineCache final
+ {
+ WavyLineCache () : m_aItems( 10 ) {}
+
+ bool find( Color aLineColor, size_t nLineWidth, size_t nWaveHeight, size_t nWordWidth, BitmapEx& rOutput )
+ {
+ Key aKey = { nWaveHeight, sal_uInt32(aLineColor) };
+ auto item = m_aItems.find( aKey );
+ if ( item == m_aItems.end() )
+ return false;
+ // needs update
+ if ( item->second.m_aLineWidth != nLineWidth || item->second.m_aWordWidth < nWordWidth )
+ {
+ return false;
+ }
+ rOutput = item->second.m_Bitmap;
+ return true;
+ }
+
+ void insert( const BitmapEx& aBitmap, const Color& aLineColor, const size_t nLineWidth, const size_t nWaveHeight, const size_t nWordWidth, BitmapEx& rOutput )
+ {
+ Key aKey = { nWaveHeight, sal_uInt32(aLineColor) };
+ m_aItems.insert( std::pair< Key, WavyLineCacheItem>( aKey, { nLineWidth, nWordWidth, aBitmap } ) );
+ rOutput = aBitmap;
+ }
+
+ private:
+ struct WavyLineCacheItem
+ {
+ size_t m_aLineWidth;
+ size_t m_aWordWidth;
+ BitmapEx m_Bitmap;
+ };
+
+ struct Key
+ {
+ size_t m_aFirst;
+ size_t m_aSecond;
+ bool operator ==( const Key& rOther ) const
+ {
+ return ( m_aFirst == rOther.m_aFirst && m_aSecond == rOther.m_aSecond );
+ }
+ };
+
+ struct Hash
+ {
+ size_t operator() ( const Key& rKey ) const
+ {
+ size_t aSeed = 0;
+ o3tl::hash_combine(aSeed, rKey.m_aFirst);
+ o3tl::hash_combine(aSeed, rKey.m_aSecond);
+ return aSeed;
+ }
+ };
+
+ o3tl::lru_map< Key, WavyLineCacheItem, Hash > m_aItems;
+ };
+}
+
+void OutputDevice::ImplInitTextLineSize()
+{
+ mpFontInstance->mxFontMetric->ImplInitTextLineSize( this );
+}
+
+void OutputDevice::ImplInitAboveTextLineSize()
+{
+ mpFontInstance->mxFontMetric->ImplInitAboveTextLineSize();
+}
+
+void OutputDevice::ImplDrawWavePixel( tools::Long nOriginX, tools::Long nOriginY,
+ tools::Long nCurX, tools::Long nCurY,
+ tools::Long nWidth,
+ Degree10 nOrientation,
+ SalGraphics* pGraphics,
+ const OutputDevice& rOutDev,
+ tools::Long nPixWidth, tools::Long nPixHeight )
+{
+ if (nOrientation)
+ {
+ Point aPoint( nOriginX, nOriginY );
+ aPoint.RotateAround( nCurX, nCurY, nOrientation );
+ }
+
+ if (shouldDrawWavePixelAsRect(nWidth))
+ {
+ pGraphics->DrawRect( nCurX, nCurY, nPixWidth, nPixHeight, rOutDev );
+ }
+ else
+ {
+ pGraphics->DrawPixel( nCurX, nCurY, rOutDev );
+ }
+}
+
+bool OutputDevice::shouldDrawWavePixelAsRect(tools::Long nLineWidth) const
+{
+ if (nLineWidth > 1)
+ return true;
+
+ return false;
+}
+
+void OutputDevice::SetWaveLineColors(Color const& rColor, tools::Long nLineWidth)
+{
+ // On printers that output pixel via DrawRect()
+ if (nLineWidth > 1)
+ {
+ if (mbLineColor || mbInitLineColor)
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+
+ mpGraphics->SetFillColor( rColor );
+ mbInitFillColor = true;
+ }
+ else
+ {
+ mpGraphics->SetLineColor( rColor );
+ mbInitLineColor = true;
+ }
+}
+
+Size OutputDevice::GetWaveLineSize(tools::Long nLineWidth) const
+{
+ if (nLineWidth > 1)
+ return Size(nLineWidth, ((nLineWidth*mnDPIX)+(mnDPIY/2))/mnDPIY);
+
+ return Size(1, 1);
+}
+
+void OutputDevice::ImplDrawWaveLine( tools::Long nBaseX, tools::Long nBaseY,
+ tools::Long nDistX, tools::Long nDistY,
+ tools::Long nWidth, tools::Long nHeight,
+ tools::Long nLineWidth, Degree10 nOrientation,
+ const Color& rColor )
+{
+ if ( !nHeight )
+ return;
+
+ tools::Long nStartX = nBaseX + nDistX;
+ tools::Long nStartY = nBaseY + nDistY;
+
+ // If the height is 1 pixel, it's enough output a line
+ if ( (nLineWidth == 1) && (nHeight == 1) )
+ {
+ mpGraphics->SetLineColor( rColor );
+ mbInitLineColor = true;
+
+ tools::Long nEndX = nStartX+nWidth;
+ tools::Long nEndY = nStartY;
+ if ( nOrientation )
+ {
+ Point aOriginPt( nBaseX, nBaseY );
+ aOriginPt.RotateAround( nStartX, nStartY, nOrientation );
+ aOriginPt.RotateAround( nEndX, nEndY, nOrientation );
+ }
+ mpGraphics->DrawLine( nStartX, nStartY, nEndX, nEndY, *this );
+ }
+ else
+ {
+ tools::Long nCurX = nStartX;
+ tools::Long nCurY = nStartY;
+ tools::Long nDiffX = 2;
+ tools::Long nDiffY = nHeight-1;
+ tools::Long nCount = nWidth;
+ tools::Long nOffY = -1;
+
+ SetWaveLineColors(rColor, nLineWidth);
+ Size aSize(GetWaveLineSize(nLineWidth));
+
+ tools::Long nPixWidth = aSize.Width();
+ tools::Long nPixHeight = aSize.Height();
+
+ if ( !nDiffY )
+ {
+ while ( nWidth )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
+ mpGraphics, *this,
+ nPixWidth, nPixHeight );
+ nCurX++;
+ nWidth--;
+ }
+ }
+ else
+ {
+ nCurY += nDiffY;
+ tools::Long nFreq = nCount / (nDiffX+nDiffY);
+ while ( nFreq-- )
+ {
+ for( tools::Long i = nDiffY; i; --i )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
+ mpGraphics, *this,
+ nPixWidth, nPixHeight );
+ nCurX++;
+ nCurY += nOffY;
+ }
+ for( tools::Long i = nDiffX; i; --i )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
+ mpGraphics, *this,
+ nPixWidth, nPixHeight );
+ nCurX++;
+ }
+ nOffY = -nOffY;
+ }
+ nFreq = nCount % (nDiffX+nDiffY);
+ if ( nFreq )
+ {
+ for( tools::Long i = nDiffY; i && nFreq; --i, --nFreq )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
+ mpGraphics, *this,
+ nPixWidth, nPixHeight );
+ nCurX++;
+ nCurY += nOffY;
+
+ }
+ for( tools::Long i = nDiffX; i && nFreq; --i, --nFreq )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
+ mpGraphics, *this,
+ nPixWidth, nPixHeight );
+ nCurX++;
+ }
+ }
+ }
+ }
+}
+
+void OutputDevice::ImplDrawWaveTextLine( tools::Long nBaseX, tools::Long nBaseY,
+ tools::Long nDistX, tools::Long nDistY, tools::Long nWidth,
+ FontLineStyle eTextLine,
+ Color aColor,
+ bool bIsAbove )
+{
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ tools::Long nLineHeight;
+ tools::Long nLinePos;
+
+ if ( bIsAbove )
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize();
+ nLinePos = pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset();
+ }
+ else
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetWavelineUnderlineSize();
+ nLinePos = pFontInstance->mxFontMetric->GetWavelineUnderlineOffset();
+ }
+ if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
+ nLineHeight = 3;
+
+ tools::Long nLineWidth = mnDPIX / 300;
+ if ( !nLineWidth )
+ nLineWidth = 1;
+
+ if ( eTextLine == LINESTYLE_BOLDWAVE )
+ nLineWidth *= 2;
+
+ nLinePos += nDistY - (nLineHeight / 2);
+
+ tools::Long nLineWidthHeight = ((nLineWidth * mnDPIX) + (mnDPIY / 2)) / mnDPIY;
+ if ( eTextLine == LINESTYLE_DOUBLEWAVE )
+ {
+ tools::Long nOrgLineHeight = nLineHeight;
+ nLineHeight /= 3;
+ if ( nLineHeight < 2 )
+ {
+ if ( nOrgLineHeight > 1 )
+ nLineHeight = 2;
+ else
+ nLineHeight = 1;
+ }
+
+ tools::Long nLineDY = nOrgLineHeight-(nLineHeight*2);
+ if ( nLineDY < nLineWidthHeight )
+ nLineDY = nLineWidthHeight;
+
+ tools::Long nLineDY2 = nLineDY/2;
+ if ( !nLineDY2 )
+ nLineDY2 = 1;
+
+ nLinePos -= nLineWidthHeight-nLineDY2;
+ ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight,
+ nLineWidth, mpFontInstance->mnOrientation, aColor );
+ nLinePos += nLineWidthHeight+nLineDY;
+ ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight,
+ nLineWidth, mpFontInstance->mnOrientation, aColor );
+ }
+ else
+ {
+ nLinePos -= nLineWidthHeight/2;
+ ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight,
+ nLineWidth, mpFontInstance->mnOrientation, aColor );
+ }
+}
+
+void OutputDevice::ImplDrawStraightTextLine( tools::Long nBaseX, tools::Long nBaseY,
+ tools::Long nDistX, tools::Long nDistY, tools::Long nWidth,
+ FontLineStyle eTextLine,
+ Color aColor,
+ bool bIsAbove )
+{
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ tools::Long nLineHeight = 0;
+ tools::Long nLinePos = 0;
+ tools::Long nLinePos2 = 0;
+
+ const tools::Long nY = nDistY;
+
+ if ( eTextLine > UNDERLINE_LAST )
+ eTextLine = LINESTYLE_SINGLE;
+
+ switch ( eTextLine )
+ {
+ case LINESTYLE_SINGLE:
+ case LINESTYLE_DOTTED:
+ case LINESTYLE_DASH:
+ case LINESTYLE_LONGDASH:
+ case LINESTYLE_DASHDOT:
+ case LINESTYLE_DASHDOTDOT:
+ if ( bIsAbove )
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetAboveUnderlineSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetAboveUnderlineOffset();
+ }
+ else
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetUnderlineSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetUnderlineOffset();
+ }
+ break;
+ case LINESTYLE_BOLD:
+ case LINESTYLE_BOLDDOTTED:
+ case LINESTYLE_BOLDDASH:
+ case LINESTYLE_BOLDLONGDASH:
+ case LINESTYLE_BOLDDASHDOT:
+ case LINESTYLE_BOLDDASHDOTDOT:
+ if ( bIsAbove )
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset();
+ }
+ else
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetBoldUnderlineSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetBoldUnderlineOffset();
+ }
+ break;
+ case LINESTYLE_DOUBLE:
+ if ( bIsAbove )
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1();
+ nLinePos2 = nY + pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2();
+ }
+ else
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetDoubleUnderlineSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1();
+ nLinePos2 = nY + pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2();
+ }
+ break;
+ default:
+ break;
+ }
+
+ if ( !nLineHeight )
+ return;
+
+ if ( mbLineColor || mbInitLineColor )
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+ mpGraphics->SetFillColor( aColor );
+ mbInitFillColor = true;
+
+ tools::Long nLeft = nDistX;
+
+ switch ( eTextLine )
+ {
+ case LINESTYLE_SINGLE:
+ case LINESTYLE_BOLD:
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
+ break;
+ case LINESTYLE_DOUBLE:
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos2, nWidth, nLineHeight );
+ break;
+ case LINESTYLE_DOTTED:
+ case LINESTYLE_BOLDDOTTED:
+ {
+ tools::Long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ tools::Long nTempWidth = nDotWidth;
+ tools::Long nEnd = nLeft+nWidth;
+ while ( nLeft < nEnd )
+ {
+ if ( nLeft+nTempWidth > nEnd )
+ nTempWidth = nEnd-nLeft;
+
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempWidth, nLineHeight );
+ nLeft += nDotWidth*2;
+ }
+ }
+ break;
+ case LINESTYLE_DASH:
+ case LINESTYLE_LONGDASH:
+ case LINESTYLE_BOLDDASH:
+ case LINESTYLE_BOLDLONGDASH:
+ {
+ tools::Long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ tools::Long nMinDashWidth;
+ tools::Long nMinSpaceWidth;
+ tools::Long nSpaceWidth;
+ tools::Long nDashWidth;
+ if ( (eTextLine == LINESTYLE_LONGDASH) ||
+ (eTextLine == LINESTYLE_BOLDLONGDASH) )
+ {
+ nMinDashWidth = nDotWidth*6;
+ nMinSpaceWidth = nDotWidth*2;
+ nDashWidth = 200;
+ nSpaceWidth = 100;
+ }
+ else
+ {
+ nMinDashWidth = nDotWidth*4;
+ nMinSpaceWidth = (nDotWidth*150)/100;
+ nDashWidth = 100;
+ nSpaceWidth = 50;
+ }
+ nDashWidth = ((nDashWidth*mnDPIX)+1270)/2540;
+ nSpaceWidth = ((nSpaceWidth*mnDPIX)+1270)/2540;
+ // DashWidth will be increased if the line is getting too thick
+ // in proportion to the line's length
+ if ( nDashWidth < nMinDashWidth )
+ nDashWidth = nMinDashWidth;
+ if ( nSpaceWidth < nMinSpaceWidth )
+ nSpaceWidth = nMinSpaceWidth;
+
+ tools::Long nTempWidth = nDashWidth;
+ tools::Long nEnd = nLeft+nWidth;
+ while ( nLeft < nEnd )
+ {
+ if ( nLeft+nTempWidth > nEnd )
+ nTempWidth = nEnd-nLeft;
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempWidth, nLineHeight );
+ nLeft += nDashWidth+nSpaceWidth;
+ }
+ }
+ break;
+ case LINESTYLE_DASHDOT:
+ case LINESTYLE_BOLDDASHDOT:
+ {
+ tools::Long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ tools::Long nDashWidth = ((100*mnDPIX)+1270)/2540;
+ tools::Long nMinDashWidth = nDotWidth*4;
+ // DashWidth will be increased if the line is getting too thick
+ // in proportion to the line's length
+ if ( nDashWidth < nMinDashWidth )
+ nDashWidth = nMinDashWidth;
+
+ tools::Long nTempDotWidth = nDotWidth;
+ tools::Long nTempDashWidth = nDashWidth;
+ tools::Long nEnd = nLeft+nWidth;
+ while ( nLeft < nEnd )
+ {
+ if ( nLeft+nTempDotWidth > nEnd )
+ nTempDotWidth = nEnd-nLeft;
+
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight );
+ nLeft += nDotWidth*2;
+ if ( nLeft > nEnd )
+ break;
+
+ if ( nLeft+nTempDashWidth > nEnd )
+ nTempDashWidth = nEnd-nLeft;
+
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDashWidth, nLineHeight );
+ nLeft += nDashWidth+nDotWidth;
+ }
+ }
+ break;
+ case LINESTYLE_DASHDOTDOT:
+ case LINESTYLE_BOLDDASHDOTDOT:
+ {
+ tools::Long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ tools::Long nDashWidth = ((100*mnDPIX)+1270)/2540;
+ tools::Long nMinDashWidth = nDotWidth*4;
+ // DashWidth will be increased if the line is getting too thick
+ // in proportion to the line's length
+ if ( nDashWidth < nMinDashWidth )
+ nDashWidth = nMinDashWidth;
+
+ tools::Long nTempDotWidth = nDotWidth;
+ tools::Long nTempDashWidth = nDashWidth;
+ tools::Long nEnd = nLeft+nWidth;
+ while ( nLeft < nEnd )
+ {
+ if ( nLeft+nTempDotWidth > nEnd )
+ nTempDotWidth = nEnd-nLeft;
+
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight );
+ nLeft += nDotWidth*2;
+ if ( nLeft > nEnd )
+ break;
+
+ if ( nLeft+nTempDotWidth > nEnd )
+ nTempDotWidth = nEnd-nLeft;
+
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight );
+ nLeft += nDotWidth*2;
+ if ( nLeft > nEnd )
+ break;
+
+ if ( nLeft+nTempDashWidth > nEnd )
+ nTempDashWidth = nEnd-nLeft;
+
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDashWidth, nLineHeight );
+ nLeft += nDashWidth+nDotWidth;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void OutputDevice::ImplDrawStrikeoutLine( tools::Long nBaseX, tools::Long nBaseY,
+ tools::Long nDistX, tools::Long nDistY, tools::Long nWidth,
+ FontStrikeout eStrikeout,
+ Color aColor )
+{
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ tools::Long nLineHeight = 0;
+ tools::Long nLinePos = 0;
+ tools::Long nLinePos2 = 0;
+
+ tools::Long nY = nDistY;
+
+ if ( eStrikeout > STRIKEOUT_LAST )
+ eStrikeout = STRIKEOUT_SINGLE;
+
+ switch ( eStrikeout )
+ {
+ case STRIKEOUT_SINGLE:
+ nLineHeight = pFontInstance->mxFontMetric->GetStrikeoutSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetStrikeoutOffset();
+ break;
+ case STRIKEOUT_BOLD:
+ nLineHeight = pFontInstance->mxFontMetric->GetBoldStrikeoutSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetBoldStrikeoutOffset();
+ break;
+ case STRIKEOUT_DOUBLE:
+ nLineHeight = pFontInstance->mxFontMetric->GetDoubleStrikeoutSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1();
+ nLinePos2 = nY + pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2();
+ break;
+ default:
+ break;
+ }
+
+ if ( !nLineHeight )
+ return;
+
+ if ( mbLineColor || mbInitLineColor )
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+ mpGraphics->SetFillColor( aColor );
+ mbInitFillColor = true;
+
+ const tools::Long& nLeft = nDistX;
+
+ switch ( eStrikeout )
+ {
+ case STRIKEOUT_SINGLE:
+ case STRIKEOUT_BOLD:
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
+ break;
+ case STRIKEOUT_DOUBLE:
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos2, nWidth, nLineHeight );
+ break;
+ default:
+ break;
+ }
+}
+
+void OutputDevice::ImplDrawStrikeoutChar( tools::Long nBaseX, tools::Long nBaseY,
+ tools::Long nDistX, tools::Long nDistY, tools::Long nWidth,
+ FontStrikeout eStrikeout,
+ Color aColor )
+{
+ // See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
+ // to tweak this
+ if (!nWidth)
+ return;
+
+ // prepare string for strikeout measurement
+ const char cStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? '/' : 'X';
+ static const int nTestStrLen = 4;
+ static const int nMaxStrikeStrLen = 2048;
+ sal_Unicode aChars[nMaxStrikeStrLen+1]; // +1 for valgrind...
+
+ for( int i = 0; i < nTestStrLen; ++i)
+ aChars[i] = cStrikeoutChar;
+
+ const OUString aStrikeoutTest(aChars, nTestStrLen);
+
+ // calculate approximation of strikeout atom size
+ tools::Long nStrikeoutWidth = 0;
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( aStrikeoutTest, 0, nTestStrLen );
+ if( pLayout )
+ {
+ nStrikeoutWidth = pLayout->GetTextWidth() / nTestStrLen;
+ }
+ if( nStrikeoutWidth <= 0 ) // sanity check
+ return;
+
+ int nStrikeStrLen = (nWidth+(nStrikeoutWidth-1)) / nStrikeoutWidth;
+ if( nStrikeStrLen > nMaxStrikeStrLen )
+ nStrikeStrLen = nMaxStrikeStrLen;
+ else if (nStrikeStrLen < 0)
+ nStrikeStrLen = 0;
+
+ // build the strikeout string
+ for( int i = nTestStrLen; i < nStrikeStrLen; ++i)
+ aChars[i] = cStrikeoutChar;
+
+ const OUString aStrikeoutText(aChars, nStrikeStrLen);
+
+ if( mpFontInstance->mnOrientation )
+ {
+ Point aOriginPt(0, 0);
+ aOriginPt.RotateAround( nDistX, nDistY, mpFontInstance->mnOrientation );
+ }
+
+ nBaseX += nDistX;
+ nBaseY += nDistY;
+
+ // strikeout text has to be left aligned
+ vcl::text::ComplexTextLayoutFlags nOrigTLM = mnTextLayoutMode;
+ mnTextLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiStrong;
+ pLayout = ImplLayout( aStrikeoutText, 0, aStrikeoutText.getLength() );
+ mnTextLayoutMode = nOrigTLM;
+
+ if( !pLayout )
+ return;
+
+ // draw the strikeout text
+ const Color aOldColor = GetTextColor();
+ SetTextColor( aColor );
+ ImplInitTextColor();
+
+ pLayout->DrawBase() = DevicePoint(nBaseX + mnTextOffX, nBaseY + mnTextOffY);
+
+ tools::Rectangle aPixelRect;
+ aPixelRect.SetLeft( nBaseX+mnTextOffX );
+ aPixelRect.SetRight( aPixelRect.Left()+nWidth );
+ aPixelRect.SetBottom( nBaseY+mpFontInstance->mxFontMetric->GetDescent() );
+ aPixelRect.SetTop( nBaseY-mpFontInstance->mxFontMetric->GetAscent() );
+
+ if (mpFontInstance->mnOrientation)
+ {
+ tools::Polygon aPoly( aPixelRect );
+ aPoly.Rotate( Point(nBaseX+mnTextOffX, nBaseY+mnTextOffY), mpFontInstance->mnOrientation);
+ aPixelRect = aPoly.GetBoundRect();
+ }
+
+ Push( vcl::PushFlags::CLIPREGION );
+ IntersectClipRegion( PixelToLogic(aPixelRect) );
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ pLayout->DrawText( *mpGraphics );
+
+ Pop();
+
+ SetTextColor( aOldColor );
+ ImplInitTextColor();
+}
+
+void OutputDevice::ImplDrawTextLine( tools::Long nX, tools::Long nY,
+ tools::Long nDistX, DeviceCoordinate nWidth,
+ FontStrikeout eStrikeout,
+ FontLineStyle eUnderline,
+ FontLineStyle eOverline,
+ bool bUnderlineAbove )
+{
+ if ( !nWidth )
+ return;
+
+ Color aStrikeoutColor = GetTextColor();
+ Color aUnderlineColor = GetTextLineColor();
+ Color aOverlineColor = GetOverlineColor();
+ bool bStrikeoutDone = false;
+ bool bUnderlineDone = false;
+ bool bOverlineDone = false;
+
+ if ( IsRTLEnabled() )
+ {
+ tools::Long nXAdd = nWidth - nDistX;
+ if( mpFontInstance->mnOrientation )
+ nXAdd = FRound( nXAdd * cos( toRadians(mpFontInstance->mnOrientation) ) );
+
+ nX += nXAdd - 1;
+ }
+
+ if ( !IsTextLineColor() )
+ aUnderlineColor = GetTextColor();
+
+ if ( !IsOverlineColor() )
+ aOverlineColor = GetTextColor();
+
+ if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
+ (eUnderline == LINESTYLE_WAVE) ||
+ (eUnderline == LINESTYLE_DOUBLEWAVE) ||
+ (eUnderline == LINESTYLE_BOLDWAVE) )
+ {
+ ImplDrawWaveTextLine( nX, nY, nDistX, 0, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
+ bUnderlineDone = true;
+ }
+ if ( (eOverline == LINESTYLE_SMALLWAVE) ||
+ (eOverline == LINESTYLE_WAVE) ||
+ (eOverline == LINESTYLE_DOUBLEWAVE) ||
+ (eOverline == LINESTYLE_BOLDWAVE) )
+ {
+ ImplDrawWaveTextLine( nX, nY, nDistX, 0, nWidth, eOverline, aOverlineColor, true );
+ bOverlineDone = true;
+ }
+
+ if ( (eStrikeout == STRIKEOUT_SLASH) ||
+ (eStrikeout == STRIKEOUT_X) )
+ {
+ ImplDrawStrikeoutChar( nX, nY, nDistX, 0, nWidth, eStrikeout, aStrikeoutColor );
+ bStrikeoutDone = true;
+ }
+
+ if ( !bUnderlineDone )
+ ImplDrawStraightTextLine( nX, nY, nDistX, 0, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
+
+ if ( !bOverlineDone )
+ ImplDrawStraightTextLine( nX, nY, nDistX, 0, nWidth, eOverline, aOverlineColor, true );
+
+ if ( !bStrikeoutDone )
+ ImplDrawStrikeoutLine( nX, nY, nDistX, 0, nWidth, eStrikeout, aStrikeoutColor );
+}
+
+void OutputDevice::ImplDrawTextLines( SalLayout& rSalLayout, FontStrikeout eStrikeout,
+ FontLineStyle eUnderline, FontLineStyle eOverline,
+ bool bWordLine, bool bUnderlineAbove )
+{
+ if( bWordLine )
+ {
+ // draw everything relative to the layout base point
+ const DevicePoint aStartPt = rSalLayout.DrawBase();
+
+ // calculate distance of each word from the base point
+ DevicePoint aPos;
+ DeviceCoordinate nDist = 0;
+ DeviceCoordinate nWidth = 0;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (rSalLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ // calculate the boundaries of each word
+ if (!pGlyph->IsSpacing())
+ {
+ if( !nWidth )
+ {
+ // get the distance to the base point (as projected to baseline)
+ nDist = aPos.getX() - aStartPt.getX();
+ if( mpFontInstance->mnOrientation )
+ {
+ const DeviceCoordinate nDY = aPos.getY() - aStartPt.getY();
+ const double fRad = toRadians(mpFontInstance->mnOrientation);
+ nDist = FRound( nDist*cos(fRad) - nDY*sin(fRad) );
+ }
+ }
+
+ // update the length of the textline
+ nWidth += pGlyph->newWidth();
+ }
+ else if( nWidth > 0 )
+ {
+ // draw the textline for each word
+ ImplDrawTextLine( aStartPt.getX(), aStartPt.getY(), nDist, nWidth,
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ nWidth = 0;
+ }
+ }
+
+ // draw textline for the last word
+ if( nWidth > 0 )
+ {
+ ImplDrawTextLine( aStartPt.getX(), aStartPt.getY(), nDist, nWidth,
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+ }
+ else
+ {
+ DevicePoint aStartPt = rSalLayout.GetDrawPosition();
+ ImplDrawTextLine( aStartPt.getX(), aStartPt.getY(), 0,
+ rSalLayout.GetTextWidth(),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+}
+
+void OutputDevice::ImplDrawMnemonicLine( tools::Long nX, tools::Long nY, tools::Long nWidth )
+{
+ tools::Long nBaseX = nX;
+ if( /*HasMirroredGraphics() &&*/ IsRTLEnabled() )
+ {
+ // add some strange offset
+ nX += 2;
+ // revert the hack that will be done later in ImplDrawTextLine
+ nX = nBaseX - nWidth - (nX - nBaseX - 1);
+ }
+
+ ImplDrawTextLine( nX, nY, 0, nWidth, STRIKEOUT_NONE, LINESTYLE_SINGLE, LINESTYLE_NONE, false );
+}
+
+void OutputDevice::SetTextLineColor()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextLineColorAction( Color(), false ) );
+
+ maTextLineColor = COL_TRANSPARENT;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextLineColor();
+}
+
+void OutputDevice::SetTextLineColor( const Color& rColor )
+{
+ Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextLineColorAction( aColor, true ) );
+
+ maTextLineColor = aColor;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextLineColor( COL_BLACK );
+}
+
+void OutputDevice::SetOverlineColor()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaOverlineColorAction( Color(), false ) );
+
+ maOverlineColor = COL_TRANSPARENT;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetOverlineColor();
+}
+
+void OutputDevice::SetOverlineColor( const Color& rColor )
+{
+ Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaOverlineColorAction( aColor, true ) );
+
+ maOverlineColor = aColor;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetOverlineColor( COL_BLACK );
+}
+
+void OutputDevice::DrawTextLine( const Point& rPos, tools::Long nWidth,
+ FontStrikeout eStrikeout,
+ FontLineStyle eUnderline,
+ FontLineStyle eOverline,
+ bool bUnderlineAbove )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextLineAction( rPos, nWidth, eStrikeout, eUnderline, eOverline ) );
+
+ if ( ((eUnderline == LINESTYLE_NONE) || (eUnderline == LINESTYLE_DONTKNOW)) &&
+ ((eOverline == LINESTYLE_NONE) || (eOverline == LINESTYLE_DONTKNOW)) &&
+ ((eStrikeout == STRIKEOUT_NONE) || (eStrikeout == STRIKEOUT_DONTKNOW)) )
+ {
+ return;
+ }
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ // initialize font if needed to get text offsets
+ // TODO: only needed for mnTextOff!=(0,0)
+ if (!InitFont())
+ return;
+
+ Point aPos = ImplLogicToDevicePixel( rPos );
+ DeviceCoordinate fWidth;
+ fWidth = LogicWidthToDeviceCoordinate( nWidth );
+ aPos += Point( mnTextOffX, mnTextOffY );
+ ImplDrawTextLine( aPos.X(), aPos.X(), 0, fWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawTextLine( rPos, nWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+}
+
+void OutputDevice::DrawWaveLine(const Point& rStartPos, const Point& rEndPos, tools::Long nLineWidth, tools::Long nWaveHeight)
+{
+ assert(!is_double_buffered_window());
+
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if (!InitFont())
+ return;
+
+ Point aStartPt = ImplLogicToDevicePixel(rStartPos);
+ Point aEndPt = ImplLogicToDevicePixel(rEndPos);
+
+ tools::Long nStartX = aStartPt.X();
+ tools::Long nStartY = aStartPt.Y();
+ tools::Long nEndX = aEndPt.X();
+ tools::Long nEndY = aEndPt.Y();
+ double fOrientation = 0.0;
+
+ // handle rotation
+ if (nStartY != nEndY || nStartX > nEndX)
+ {
+ fOrientation = basegfx::rad2deg(std::atan2(nStartY - nEndY, nEndX - nStartX));
+ // un-rotate the end point
+ aStartPt.RotateAround(nEndX, nEndY, Degree10(static_cast<sal_Int16>(-fOrientation * 10.0)));
+ }
+
+ // Handle HiDPI
+ float fScaleFactor = GetDPIScaleFactor();
+ if (fScaleFactor > 1.0f)
+ {
+ nWaveHeight *= fScaleFactor;
+
+ nStartY += fScaleFactor - 1; // Shift down additional pixel(s) to create more visual separation.
+
+ // odd heights look better than even
+ if (nWaveHeight % 2 == 0)
+ {
+ nWaveHeight--;
+ }
+ }
+
+ // #109280# make sure the waveline does not exceed the descent to avoid paint problems
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ if (nWaveHeight > pFontInstance->mxFontMetric->GetWavelineUnderlineSize())
+ {
+ nWaveHeight = pFontInstance->mxFontMetric->GetWavelineUnderlineSize();
+ // tdf#124848 hairline
+ nLineWidth = 0;
+ }
+
+ if ( fOrientation == 0.0 )
+ {
+ static vcl::DeleteOnDeinit< WavyLineCache > snLineCache {};
+ if ( !snLineCache.get() )
+ return;
+ WavyLineCache& rLineCache = *snLineCache.get();
+ BitmapEx aWavylinebmp;
+ if ( !rLineCache.find( GetLineColor(), nLineWidth, nWaveHeight, nEndX - nStartX, aWavylinebmp ) )
+ {
+ size_t nWordLength = nEndX - nStartX;
+ // start with something big to avoid updating it frequently
+ nWordLength = nWordLength < 1024 ? 1024 : nWordLength;
+ ScopedVclPtrInstance< VirtualDevice > pVirtDev( *this, DeviceFormat::DEFAULT,
+ DeviceFormat::DEFAULT );
+ pVirtDev->SetOutputSizePixel( Size( nWordLength, nWaveHeight * 2 ), false );
+ pVirtDev->SetLineColor( GetLineColor() );
+ pVirtDev->SetBackground( Wallpaper( COL_TRANSPARENT ) );
+ pVirtDev->Erase();
+ pVirtDev->SetAntialiasing( AntialiasingFlags::Enable );
+ pVirtDev->ImplDrawWaveLineBezier( 0, 0, nWordLength, 0, nWaveHeight, fOrientation, nLineWidth );
+ BitmapEx aBitmapEx(pVirtDev->GetBitmapEx(Point(0, 0), pVirtDev->GetOutputSize()));
+
+ // Ideally we don't need this block, but in the split rgb surface + separate alpha surface
+ // with Antialiasing enabled and the svp/cairo backend we get both surfaces antialiased
+ // so their combination of aliases merge to overly wash-out the color. Hack it by taking just
+ // the alpha surface and use it to blend the original solid line color
+ Bitmap aSolidColor(aBitmapEx.GetBitmap());
+ aSolidColor.Erase(GetLineColor());
+ aBitmapEx = BitmapEx(aSolidColor, aBitmapEx.GetAlpha());
+
+ rLineCache.insert( aBitmapEx, GetLineColor(), nLineWidth, nWaveHeight, nWordLength, aWavylinebmp );
+ }
+ if ( aWavylinebmp.ImplGetBitmapSalBitmap() != nullptr )
+ {
+ Size _size( nEndX - nStartX, aWavylinebmp.GetSizePixel().Height() );
+ DrawBitmapEx(Point( rStartPos.X(), rStartPos.Y() ), PixelToLogic( _size ), Point(), _size, aWavylinebmp);
+ }
+ return;
+ }
+
+ ImplDrawWaveLineBezier( nStartX, nStartY, nEndX, nEndY, nWaveHeight, fOrientation, nLineWidth );
+}
+
+void OutputDevice::ImplDrawWaveLineBezier(tools::Long nStartX, tools::Long nStartY, tools::Long nEndX, tools::Long nEndY, tools::Long nWaveHeight, double fOrientation, tools::Long nLineWidth)
+{
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if (!InitFont())
+ return;
+
+ const basegfx::B2DRectangle aWaveLineRectangle(nStartX, nStartY, nEndX, nEndY + nWaveHeight);
+ const basegfx::B2DPolygon aWaveLinePolygon = basegfx::createWaveLinePolygon(aWaveLineRectangle);
+ const basegfx::B2DHomMatrix aRotationMatrix = basegfx::utils::createRotateAroundPoint(nStartX, nStartY, basegfx::deg2rad(-fOrientation));
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ mpGraphics->SetLineColor(GetLineColor());
+ mpGraphics->DrawPolyLine(
+ aRotationMatrix,
+ aWaveLinePolygon,
+ 0.0,
+ nLineWidth,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0),
+ bPixelSnapHairline,
+ *this);
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->ImplDrawWaveLineBezier(nStartX, nStartY, nEndX, nEndY, nWaveHeight, fOrientation, nLineWidth);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/transparent.cxx b/vcl/source/outdev/transparent.cxx
new file mode 100644
index 000000000..f747c0479
--- /dev/null
+++ b/vcl/source/outdev/transparent.cxx
@@ -0,0 +1,1914 @@
+/* -*- 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/types.h>
+#include <osl/diagnose.h>
+#include <rtl/math.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <tools/helpers.hxx>
+#include <officecfg/Office/Common.hxx>
+
+#include <vcl/BitmapTools.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/print.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <pdf/pdfwriter_impl.hxx>
+#include <salgdi.hxx>
+
+#include <list>
+#include <memory>
+
+#define MAX_TILE_WIDTH 1024
+#define MAX_TILE_HEIGHT 1024
+
+namespace
+{
+ /**
+ * Perform a safe approximation of a polygon from double-precision
+ * coordinates to integer coordinates, to ensure that it has at least 2
+ * pixels in both X and Y directions.
+ */
+ tools::Polygon toPolygon( const basegfx::B2DPolygon& rPoly )
+ {
+ basegfx::B2DRange aRange = rPoly.getB2DRange();
+ double fW = aRange.getWidth(), fH = aRange.getHeight();
+ if (0.0 < fW && 0.0 < fH && (fW <= 1.0 || fH <= 1.0))
+ {
+ // This polygon not empty but is too small to display. Approximate it
+ // with a rectangle large enough to be displayed.
+ double nX = aRange.getMinX(), nY = aRange.getMinY();
+ double nW = std::max<double>(1.0, rtl::math::round(fW));
+ double nH = std::max<double>(1.0, rtl::math::round(fH));
+
+ tools::Polygon aTarget;
+ aTarget.Insert(0, Point(nX, nY));
+ aTarget.Insert(1, Point(nX+nW, nY));
+ aTarget.Insert(2, Point(nX+nW, nY+nH));
+ aTarget.Insert(3, Point(nX, nY+nH));
+ aTarget.Insert(4, Point(nX, nY));
+ return aTarget;
+ }
+ return tools::Polygon(rPoly);
+ }
+
+ tools::PolyPolygon toPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly )
+ {
+ tools::PolyPolygon aTarget;
+ for (auto const& rB2DPolygon : rPolyPoly)
+ aTarget.Insert(toPolygon(rB2DPolygon));
+
+ return aTarget;
+ }
+}
+
+// Caution: This method is nearly the same as
+// void OutputDevice::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rB2DPolyPoly )
+// so when changes are made here do not forget to make changes there, too
+
+void OutputDevice::DrawTransparent(
+ const basegfx::B2DHomMatrix& rObjectTransform,
+ const basegfx::B2DPolyPolygon& rB2DPolyPoly,
+ double fTransparency)
+{
+ assert(!is_double_buffered_window());
+
+ // AW: Do NOT paint empty PolyPolygons
+ if(!rB2DPolyPoly.count())
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ if( mbInitFillColor )
+ InitFillColor();
+
+ if(mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ (RasterOp::OverPaint == GetRasterOp()) )
+ {
+ // b2dpolygon support not implemented yet on non-UNX platforms
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(rB2DPolyPoly);
+
+ // ensure it is closed
+ if(!aB2DPolyPolygon.isClosed())
+ {
+ // maybe assert, prevents buffering due to making a copy
+ aB2DPolyPolygon.setClosed( true );
+ }
+
+ // create ObjectToDevice transformation
+ const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rObjectTransform);
+ // TODO: this must not drop transparency for mpAlphaVDev case, but instead use premultiplied
+ // alpha... but that requires using premultiplied alpha also for already drawn data
+ const double fAdjustedTransparency = mpAlphaVDev ? 0 : fTransparency;
+ bool bDrawnOk(true);
+
+ if( IsFillColor() )
+ {
+ bDrawnOk = mpGraphics->DrawPolyPolygon(
+ aFullTransform,
+ aB2DPolyPolygon,
+ fAdjustedTransparency,
+ *this);
+ }
+
+ if( bDrawnOk && IsLineColor() )
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : std::as_const(aB2DPolyPolygon))
+ {
+ mpGraphics->DrawPolyLine(
+ aFullTransform,
+ rPolygon,
+ fAdjustedTransparency,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ *this );
+ }
+ }
+
+ if( bDrawnOk )
+ {
+ if( mpMetaFile )
+ {
+ // tdf#119843 need transformed Polygon here
+ basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly);
+ aB2DPolyPoly.transform(rObjectTransform);
+ mpMetaFile->AddAction(
+ new MetaTransparentAction(
+ tools::PolyPolygon(aB2DPolyPoly),
+ static_cast< sal_uInt16 >(fTransparency * 100.0)));
+ }
+
+ if (mpAlphaVDev)
+ mpAlphaVDev->DrawTransparent(rObjectTransform, rB2DPolyPoly, fTransparency);
+
+ return;
+ }
+ }
+
+ // fallback to old polygon drawing if needed
+ // tdf#119843 need transformed Polygon here
+ basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly);
+ aB2DPolyPoly.transform(rObjectTransform);
+ DrawTransparent(
+ toPolyPolygon(aB2DPolyPoly),
+ static_cast<sal_uInt16>(fTransparency * 100.0));
+}
+
+bool OutputDevice::DrawTransparentNatively ( const tools::PolyPolygon& rPolyPoly,
+ sal_uInt16 nTransparencePercent )
+{
+ assert(!is_double_buffered_window());
+
+ bool bDrawn = false;
+
+ if (mpGraphics->supportsOperation(OutDevSupportType::B2DDraw)
+#if defined UNX && ! defined MACOSX && ! defined IOS
+ && GetBitCount() > 8
+#endif
+#ifdef _WIN32
+ // workaround bad dithering on remote displaying when using GDI+ with toolbar button highlighting
+ && !rPolyPoly.IsRect()
+#endif
+ )
+ {
+ // prepare the graphics device
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return false;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ if( mbInitFillColor )
+ InitFillColor();
+
+ // get the polygon in device coordinates
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon());
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+
+ const double fTransparency = 0.01 * nTransparencePercent;
+ if( mbFillColor )
+ {
+ // #i121591#
+ // CAUTION: Only non printing (pixel-renderer) VCL commands from OutputDevices
+ // should be used when printing. Normally this is avoided by the printer being
+ // non-AAed and thus e.g. on WIN GdiPlus calls are not used. It may be necessary
+ // to figure out a way of moving this code to its own function that is
+ // overridden by the Print class, which will mean we deliberately override the
+ // functionality and we use the fallback some lines below (which is not very good,
+ // though. For now, WinSalGraphics::drawPolyPolygon will detect printer usage and
+ // correct the wrong mapping (see there for details)
+ bDrawn = mpGraphics->DrawPolyPolygon(
+ aTransform,
+ aB2DPolyPolygon,
+ fTransparency,
+ *this);
+ }
+
+ if( mbLineColor )
+ {
+ // disable the fill color for now
+ mpGraphics->SetFillColor();
+
+ // draw the border line
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : std::as_const(aB2DPolyPolygon))
+ {
+ bDrawn = mpGraphics->DrawPolyLine(
+ aTransform,
+ rPolygon,
+ fTransparency,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ *this );
+ }
+
+ // prepare to restore the fill color
+ mbInitFillColor = mbFillColor;
+ }
+ }
+
+ return bDrawn;
+}
+
+void OutputDevice::EmulateDrawTransparent ( const tools::PolyPolygon& rPolyPoly,
+ sal_uInt16 nTransparencePercent )
+{
+ // #110958# Disable alpha VDev, we perform the necessary
+ VirtualDevice* pOldAlphaVDev = mpAlphaVDev;
+
+ // operation explicitly further below.
+ if( mpAlphaVDev )
+ mpAlphaVDev = nullptr;
+
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ mpMetaFile = nullptr;
+
+ tools::PolyPolygon aPolyPoly( LogicToPixel( rPolyPoly ) );
+ tools::Rectangle aPolyRect( aPolyPoly.GetBoundRect() );
+ tools::Rectangle aDstRect( Point(), GetOutputSizePixel() );
+
+ aDstRect.Intersection( aPolyRect );
+
+ ClipToPaintRegion( aDstRect );
+
+ if( !aDstRect.IsEmpty() )
+ {
+ bool bDrawn = false;
+
+ // #i66849# Added fast path for exactly rectangular
+ // polygons
+ // #i83087# Naturally, system alpha blending cannot
+ // work with separate alpha VDev
+ if( !mpAlphaVDev && aPolyPoly.IsRect() )
+ {
+ // setup Graphics only here (other cases delegate
+ // to basic OutDev methods)
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ tools::Rectangle aLogicPolyRect( rPolyPoly.GetBoundRect() );
+ tools::Rectangle aPixelRect( ImplLogicToDevicePixel( aLogicPolyRect ) );
+
+ if( !mbOutputClipped )
+ {
+ bDrawn = mpGraphics->DrawAlphaRect( aPixelRect.Left(), aPixelRect.Top(),
+ // #i98405# use methods with small g, else one pixel too much will be painted.
+ // This is because the source is a polygon which when painted would not paint
+ // the rightmost and lowest pixel line(s), so use one pixel less for the
+ // rectangle, too.
+ aPixelRect.getWidth(), aPixelRect.getHeight(),
+ sal::static_int_cast<sal_uInt8>(nTransparencePercent),
+ *this );
+ }
+ else
+ {
+ bDrawn = true;
+ }
+ }
+
+ if( !bDrawn )
+ {
+ ScopedVclPtrInstance< VirtualDevice > aVDev(*this);
+ const Size aDstSz( aDstRect.GetSize() );
+ const sal_uInt8 cTrans = static_cast<sal_uInt8>(MinMax( FRound( nTransparencePercent * 2.55 ), 0, 255 ));
+
+ if( aDstRect.Left() || aDstRect.Top() )
+ aPolyPoly.Move( -aDstRect.Left(), -aDstRect.Top() );
+
+ if( aVDev->SetOutputSizePixel( aDstSz ) )
+ {
+ const bool bOldMap = mbMap;
+
+ EnableMapMode( false );
+
+ aVDev->SetLineColor( COL_BLACK );
+ aVDev->SetFillColor( COL_BLACK );
+ aVDev->DrawPolyPolygon( aPolyPoly );
+
+ Bitmap aPaint( GetBitmap( aDstRect.TopLeft(), aDstSz ) );
+ Bitmap aPolyMask( aVDev->GetBitmap( Point(), aDstSz ) );
+
+ // #107766# check for non-empty bitmaps before accessing them
+ if( !aPaint.IsEmpty() && !aPolyMask.IsEmpty() )
+ {
+ BitmapScopedWriteAccess pW(aPaint);
+ Bitmap::ScopedReadAccess pR(aPolyMask);
+
+ if( pW && pR )
+ {
+ BitmapColor aPixCol;
+ const BitmapColor aFillCol( GetFillColor() );
+ const BitmapColor aBlack( pR->GetBestMatchingColor( COL_BLACK ) );
+ const tools::Long nWidth = pW->Width();
+ const tools::Long nHeight = pW->Height();
+ const tools::Long nR = aFillCol.GetRed();
+ const tools::Long nG = aFillCol.GetGreen();
+ const tools::Long nB = aFillCol.GetBlue();
+ tools::Long nX, nY;
+
+ if (vcl::isPalettePixelFormat(aPaint.getPixelFormat()))
+ {
+ const BitmapPalette& rPal = pW->GetPalette();
+ const sal_uInt16 nCount = rPal.GetEntryCount();
+ std::unique_ptr<sal_uInt8[]> xMap(new sal_uInt8[ nCount * sizeof( BitmapColor )]);
+ BitmapColor* pMap = reinterpret_cast<BitmapColor*>(xMap.get());
+
+ for( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ BitmapColor aCol( rPal[ i ] );
+ aCol.Merge( aFillCol, cTrans );
+ pMap[ i ] = BitmapColor( static_cast<sal_uInt8>(rPal.GetBestIndex( aCol )) );
+ }
+
+ if( pR->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal &&
+ pW->GetScanlineFormat() == ScanlineFormat::N8BitPal )
+ {
+ const sal_uInt8 cBlack = aBlack.GetIndex();
+
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pWScan = pW->GetScanline( nY );
+ Scanline pRScan = pR->GetScanline( nY );
+ sal_uInt8 cBit = 128;
+
+ for( nX = 0; nX < nWidth; nX++, cBit >>= 1, pWScan++ )
+ {
+ if( !cBit )
+ {
+ cBit = 128;
+ pRScan += 1;
+ }
+ if( ( *pRScan & cBit ) == cBlack )
+ {
+ *pWScan = pMap[ *pWScan ].GetIndex();
+ }
+ }
+ }
+ }
+ else
+ {
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pW->GetScanline(nY);
+ Scanline pScanlineRead = pR->GetScanline(nY);
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+ if( pR->GetPixelFromData( pScanlineRead, nX ) == aBlack )
+ {
+ pW->SetPixelOnData( pScanline, nX, pMap[ pW->GetIndexFromData( pScanline, nX ) ] );
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ if( pR->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal &&
+ pW->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr )
+ {
+ const sal_uInt8 cBlack = aBlack.GetIndex();
+
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pWScan = pW->GetScanline( nY );
+ Scanline pRScan = pR->GetScanline( nY );
+ sal_uInt8 cBit = 128;
+
+ for( nX = 0; nX < nWidth; nX++, cBit >>= 1, pWScan += 3 )
+ {
+ if( !cBit )
+ {
+ cBit = 128;
+ pRScan += 1;
+ }
+ if( ( *pRScan & cBit ) == cBlack )
+ {
+ pWScan[ 0 ] = color::ColorChannelMerge( pWScan[ 0 ], nB, cTrans );
+ pWScan[ 1 ] = color::ColorChannelMerge( pWScan[ 1 ], nG, cTrans );
+ pWScan[ 2 ] = color::ColorChannelMerge( pWScan[ 2 ], nR, cTrans );
+ }
+ }
+ }
+ }
+ else
+ {
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pW->GetScanline(nY);
+ Scanline pScanlineRead = pR->GetScanline(nY);
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+ if( pR->GetPixelFromData( pScanlineRead, nX ) == aBlack )
+ {
+ aPixCol = pW->GetColor( nY, nX );
+ aPixCol.Merge(aFillCol, cTrans);
+ pW->SetPixelOnData(pScanline, nX, aPixCol);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ pR.reset();
+ pW.reset();
+
+ DrawBitmap( aDstRect.TopLeft(), aPaint );
+
+ EnableMapMode( bOldMap );
+
+ if( mbLineColor )
+ {
+ Push( vcl::PushFlags::FILLCOLOR );
+ SetFillColor();
+ DrawPolyPolygon( rPolyPoly );
+ Pop();
+ }
+ }
+ }
+ else
+ {
+ DrawPolyPolygon( rPolyPoly );
+ }
+ }
+ }
+
+ mpMetaFile = pOldMetaFile;
+
+ // #110958# Restore disabled alpha VDev
+ mpAlphaVDev = pOldAlphaVDev;
+}
+
+void OutputDevice::DrawTransparent( const tools::PolyPolygon& rPolyPoly,
+ sal_uInt16 nTransparencePercent )
+{
+ assert(!is_double_buffered_window());
+
+ // short circuit for drawing an opaque polygon
+ if( (nTransparencePercent < 1) || (mnDrawMode & DrawModeFlags::NoTransparency) )
+ {
+ DrawPolyPolygon( rPolyPoly );
+ return;
+ }
+
+ // short circuit for drawing an invisible polygon
+ if( (!mbFillColor && !mbLineColor) || (nTransparencePercent >= 100) )
+ return; // tdf#84294: do not record it in metafile
+
+ // handle metafile recording
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTransparentAction( rPolyPoly, nTransparencePercent ) );
+
+ bool bDrawn = !IsDeviceOutputNecessary() || ImplIsRecordLayout();
+ if( bDrawn )
+ return;
+
+ // get the device graphics as drawing target
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ // try hard to draw it directly, because the emulation layers are slower
+ bDrawn = DrawTransparentNatively( rPolyPoly, nTransparencePercent );
+
+ if (!bDrawn)
+ EmulateDrawTransparent( rPolyPoly, nTransparencePercent );
+
+ // #110958# Apply alpha value also to VDev alpha channel
+ if( mpAlphaVDev )
+ {
+ const Color aFillCol( mpAlphaVDev->GetFillColor() );
+ mpAlphaVDev->SetFillColor( Color(sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100),
+ sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100),
+ sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100)) );
+
+ mpAlphaVDev->DrawTransparent( rPolyPoly, nTransparencePercent );
+
+ mpAlphaVDev->SetFillColor( aFillCol );
+ }
+}
+
+void OutputDevice::DrawTransparent( const GDIMetaFile& rMtf, const Point& rPos,
+ const Size& rSize, const Gradient& rTransparenceGradient )
+{
+ assert(!is_double_buffered_window());
+
+ const Color aBlack( COL_BLACK );
+
+ if( mpMetaFile )
+ {
+ // missing here is to map the data using the DeviceTransformation
+ mpMetaFile->AddAction( new MetaFloatTransparentAction( rMtf, rPos, rSize, rTransparenceGradient ) );
+ }
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ if( ( rTransparenceGradient.GetStartColor() == aBlack && rTransparenceGradient.GetEndColor() == aBlack ) ||
+ ( mnDrawMode & DrawModeFlags::NoTransparency ) )
+ {
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ const_cast<GDIMetaFile&>(rMtf).Play(*this, rPos, rSize);
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ }
+ else
+ {
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ tools::Rectangle aOutRect( LogicToPixel( rPos ), LogicToPixel( rSize ) );
+ Point aPoint;
+ tools::Rectangle aDstRect( aPoint, GetOutputSizePixel() );
+
+ mpMetaFile = nullptr;
+ aDstRect.Intersection( aOutRect );
+
+ ClipToPaintRegion( aDstRect );
+
+ if( !aDstRect.IsEmpty() )
+ {
+ // Create transparent buffer
+ ScopedVclPtrInstance<VirtualDevice> xVDev(DeviceFormat::DEFAULT, DeviceFormat::DEFAULT);
+
+ xVDev->mnDPIX = mnDPIX;
+ xVDev->mnDPIY = mnDPIY;
+
+ if( xVDev->SetOutputSizePixel( aDstRect.GetSize() ) )
+ {
+ if(GetAntialiasing() != AntialiasingFlags::NONE)
+ {
+ // #i102109#
+ // For MetaFile replay (see task) it may now be necessary to take
+ // into account that the content is AntiAlialiased and needs to be masked
+ // like that. Instead of masking, i will use a copy-modify-paste cycle
+ // here (as i already use in the VclPrimiziveRenderer with success)
+ xVDev->SetAntialiasing(GetAntialiasing());
+
+ // create MapMode for buffer (offset needed) and set
+ MapMode aMap(GetMapMode());
+ const Point aOutPos(PixelToLogic(aDstRect.TopLeft()));
+ aMap.SetOrigin(Point(-aOutPos.X(), -aOutPos.Y()));
+ xVDev->SetMapMode(aMap);
+
+ // copy MapMode state and disable for target
+ const bool bOrigMapModeEnabled(IsMapModeEnabled());
+ EnableMapMode(false);
+
+ // copy MapMode state and disable for buffer
+ const bool bBufferMapModeEnabled(xVDev->IsMapModeEnabled());
+ xVDev->EnableMapMode(false);
+
+ // copy content from original to buffer
+ xVDev->DrawOutDev( aPoint, xVDev->GetOutputSizePixel(), // dest
+ aDstRect.TopLeft(), xVDev->GetOutputSizePixel(), // source
+ *this);
+
+ // draw MetaFile to buffer
+ xVDev->EnableMapMode(bBufferMapModeEnabled);
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ const_cast<GDIMetaFile&>(rMtf).Play(*xVDev, rPos, rSize);
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+
+ // get content bitmap from buffer
+ xVDev->EnableMapMode(false);
+
+ const Bitmap aPaint(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel()));
+
+ // create alpha mask from gradient and get as Bitmap
+ xVDev->EnableMapMode(bBufferMapModeEnabled);
+ xVDev->SetDrawMode(DrawModeFlags::GrayGradient);
+ xVDev->DrawGradient(tools::Rectangle(rPos, rSize), rTransparenceGradient);
+ xVDev->SetDrawMode(DrawModeFlags::Default);
+ xVDev->EnableMapMode(false);
+
+ const AlphaMask aAlpha(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel()));
+
+ xVDev.disposeAndClear();
+
+ // draw masked content to target and restore MapMode
+ DrawBitmapEx(aDstRect.TopLeft(), BitmapEx(aPaint, aAlpha));
+ EnableMapMode(bOrigMapModeEnabled);
+ }
+ else
+ {
+ MapMode aMap( GetMapMode() );
+ Point aOutPos( PixelToLogic( aDstRect.TopLeft() ) );
+ const bool bOldMap = mbMap;
+
+ aMap.SetOrigin( Point( -aOutPos.X(), -aOutPos.Y() ) );
+ xVDev->SetMapMode( aMap );
+ const bool bVDevOldMap = xVDev->IsMapModeEnabled();
+
+ // create paint bitmap
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ const_cast<GDIMetaFile&>(rMtf).Play(*xVDev, rPos, rSize);
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ xVDev->EnableMapMode( false );
+ BitmapEx aPaint = xVDev->GetBitmapEx(Point(), xVDev->GetOutputSizePixel());
+ xVDev->EnableMapMode( bVDevOldMap ); // #i35331#: MUST NOT use EnableMapMode( sal_True ) here!
+
+ // create alpha mask from gradient
+ xVDev->SetDrawMode( DrawModeFlags::GrayGradient );
+ xVDev->DrawGradient( tools::Rectangle( rPos, rSize ), rTransparenceGradient );
+ xVDev->SetDrawMode( DrawModeFlags::Default );
+ xVDev->EnableMapMode( false );
+
+ AlphaMask aAlpha(xVDev->GetBitmap(Point(), xVDev->GetOutputSizePixel()));
+ aAlpha.BlendWith(aPaint.GetAlpha());
+
+ xVDev.disposeAndClear();
+
+ EnableMapMode( false );
+ DrawBitmapEx(aDstRect.TopLeft(), BitmapEx(aPaint.GetBitmap(), aAlpha));
+ EnableMapMode( bOldMap );
+ }
+ }
+ }
+
+ mpMetaFile = pOldMetaFile;
+ }
+}
+
+typedef ::std::pair< MetaAction*, int > Component; // MetaAction plus index in metafile
+
+namespace {
+
+// List of (intersecting) actions, plus overall bounds
+struct ConnectedComponents
+{
+ ConnectedComponents() :
+ aComponentList(),
+ aBounds(),
+ aBgColor(COL_WHITE),
+ bIsSpecial(false),
+ bIsFullyTransparent(false)
+ {}
+
+ ::std::list< Component > aComponentList;
+ tools::Rectangle aBounds;
+ Color aBgColor;
+ bool bIsSpecial;
+ bool bIsFullyTransparent;
+};
+
+}
+
+namespace {
+
+/** Determines whether the action can handle transparency correctly
+ (i.e. when painted on white background, does the action still look
+ correct)?
+ */
+bool DoesActionHandleTransparency( const MetaAction& rAct )
+{
+ // MetaActionType::FLOATTRANSPARENT can contain a whole metafile,
+ // which is to be rendered with the given transparent gradient. We
+ // currently cannot emulate transparent painting on a white
+ // background reliably.
+
+ // the remainder can handle printing itself correctly on a uniform
+ // white background.
+ switch( rAct.GetType() )
+ {
+ case MetaActionType::Transparent:
+ case MetaActionType::BMPEX:
+ case MetaActionType::BMPEXSCALE:
+ case MetaActionType::BMPEXSCALEPART:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+bool doesRectCoverWithUniformColor(
+ tools::Rectangle const & rPrevRect,
+ tools::Rectangle const & rCurrRect,
+ OutputDevice const & rMapModeVDev)
+{
+ // shape needs to fully cover previous content, and have uniform
+ // color
+ return (rMapModeVDev.LogicToPixel(rCurrRect).Contains(rPrevRect) &&
+ rMapModeVDev.IsFillColor());
+}
+
+/** Check whether rCurrRect rectangle fully covers io_rPrevRect - if
+ yes, return true and update o_rBgColor
+ */
+bool checkRect( tools::Rectangle& io_rPrevRect,
+ Color& o_rBgColor,
+ const tools::Rectangle& rCurrRect,
+ OutputDevice const & rMapModeVDev )
+{
+ bool bRet = doesRectCoverWithUniformColor(io_rPrevRect, rCurrRect, rMapModeVDev);
+
+ if( bRet )
+ {
+ io_rPrevRect = rCurrRect;
+ o_rBgColor = rMapModeVDev.GetFillColor();
+ }
+
+ return bRet;
+}
+
+/** #107169# Convert BitmapEx to Bitmap with appropriately blended
+ color. Convert MetaTransparentAction to plain polygon,
+ appropriately colored
+
+ @param o_rMtf
+ Add converted actions to this metafile
+*/
+void ImplConvertTransparentAction( GDIMetaFile& o_rMtf,
+ const MetaAction& rAct,
+ const OutputDevice& rStateOutDev,
+ Color aBgColor )
+{
+ if (rAct.GetType() == MetaActionType::Transparent)
+ {
+ const MetaTransparentAction* pTransAct = static_cast<const MetaTransparentAction*>(&rAct);
+ sal_uInt16 nTransparency( pTransAct->GetTransparence() );
+
+ // #i10613# Respect transparency for draw color
+ if (nTransparency)
+ {
+ o_rMtf.AddAction(new MetaPushAction(vcl::PushFlags::LINECOLOR|vcl::PushFlags::FILLCOLOR));
+
+ // assume white background for alpha blending
+ Color aLineColor(rStateOutDev.GetLineColor());
+ aLineColor.SetRed(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency) * aLineColor.GetRed()) / 100));
+ aLineColor.SetGreen(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency) * aLineColor.GetGreen()) / 100));
+ aLineColor.SetBlue(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency) * aLineColor.GetBlue()) / 100));
+ o_rMtf.AddAction(new MetaLineColorAction(aLineColor, true));
+
+ Color aFillColor(rStateOutDev.GetFillColor());
+ aFillColor.SetRed(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency)*aFillColor.GetRed()) / 100));
+ aFillColor.SetGreen(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency)*aFillColor.GetGreen()) / 100));
+ aFillColor.SetBlue(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency)*aFillColor.GetBlue()) / 100));
+ o_rMtf.AddAction(new MetaFillColorAction(aFillColor, true));
+ }
+
+ o_rMtf.AddAction(new MetaPolyPolygonAction(pTransAct->GetPolyPolygon()));
+
+ if(nTransparency)
+ o_rMtf.AddAction(new MetaPopAction());
+ }
+ else
+ {
+ BitmapEx aBmpEx;
+
+ switch (rAct.GetType())
+ {
+ case MetaActionType::BMPEX:
+ aBmpEx = static_cast<const MetaBmpExAction&>(rAct).GetBitmapEx();
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ aBmpEx = static_cast<const MetaBmpExScaleAction&>(rAct).GetBitmapEx();
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ aBmpEx = static_cast<const MetaBmpExScaleAction&>(rAct).GetBitmapEx();
+ break;
+
+ case MetaActionType::Transparent:
+
+ default:
+ OSL_FAIL("Printer::GetPreparedMetafile impossible state reached");
+ break;
+ }
+
+ Bitmap aBmp(aBmpEx.GetBitmap());
+ if (aBmpEx.IsAlpha())
+ {
+ // blend with alpha channel
+ aBmp.Convert(BmpConversion::N24Bit);
+ aBmp.Blend(aBmpEx.GetAlpha(), aBgColor);
+ }
+
+ // add corresponding action
+ switch (rAct.GetType())
+ {
+ case MetaActionType::BMPEX:
+ o_rMtf.AddAction(new MetaBmpAction(
+ static_cast<const MetaBmpExAction&>(rAct).GetPoint(),
+ aBmp));
+ break;
+ case MetaActionType::BMPEXSCALE:
+ o_rMtf.AddAction(new MetaBmpScaleAction(
+ static_cast<const MetaBmpExScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaBmpExScaleAction&>(rAct).GetSize(),
+ aBmp));
+ break;
+ case MetaActionType::BMPEXSCALEPART:
+ o_rMtf.AddAction(new MetaBmpScalePartAction(
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestSize(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetSrcPoint(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetSrcSize(),
+ aBmp));
+ break;
+ default:
+ OSL_FAIL("Unexpected case");
+ break;
+ }
+ }
+}
+
+// #i10613# Extracted from ImplCheckRect::ImplCreate
+// Returns true, if given action creates visible (i.e. non-transparent) output
+bool ImplIsNotTransparent( const MetaAction& rAct, const OutputDevice& rOut )
+{
+ const bool bLineTransparency( !rOut.IsLineColor() || rOut.GetLineColor().IsFullyTransparent() );
+ const bool bFillTransparency( !rOut.IsFillColor() || rOut.GetFillColor().IsFullyTransparent() );
+ bool bRet( false );
+
+ switch( rAct.GetType() )
+ {
+ case MetaActionType::POINT:
+ if( !bLineTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::LINE:
+ if( !bLineTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::RECT:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::ELLIPSE:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::ARC:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::PIE:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::CHORD:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::POLYLINE:
+ if( !bLineTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::POLYGON:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction& rTextAct = static_cast<const MetaTextAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+ if (!aString.isEmpty())
+ bRet = true;
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction& rTextAct = static_cast<const MetaTextArrayAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+ if (!aString.isEmpty())
+ bRet = true;
+ }
+ break;
+
+ case MetaActionType::PIXEL:
+ case MetaActionType::BMP:
+ case MetaActionType::BMPSCALE:
+ case MetaActionType::BMPSCALEPART:
+ case MetaActionType::BMPEX:
+ case MetaActionType::BMPEXSCALE:
+ case MetaActionType::BMPEXSCALEPART:
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ case MetaActionType::MASKSCALEPART:
+ case MetaActionType::GRADIENT:
+ case MetaActionType::GRADIENTEX:
+ case MetaActionType::HATCH:
+ case MetaActionType::WALLPAPER:
+ case MetaActionType::Transparent:
+ case MetaActionType::FLOATTRANSPARENT:
+ case MetaActionType::EPS:
+ case MetaActionType::TEXTRECT:
+ case MetaActionType::STRETCHTEXT:
+ case MetaActionType::TEXTLINE:
+ // all other actions: generate non-transparent output
+ bRet = true;
+ break;
+
+ default:
+ break;
+ }
+
+ return bRet;
+}
+
+// #i10613# Extracted from ImplCheckRect::ImplCreate
+tools::Rectangle ImplCalcActionBounds( const MetaAction& rAct, const OutputDevice& rOut )
+{
+ tools::Rectangle aActionBounds;
+
+ switch( rAct.GetType() )
+ {
+ case MetaActionType::PIXEL:
+ aActionBounds = tools::Rectangle( static_cast<const MetaPixelAction&>(rAct).GetPoint(), Size( 1, 1 ) );
+ break;
+
+ case MetaActionType::POINT:
+ aActionBounds = tools::Rectangle( static_cast<const MetaPointAction&>(rAct).GetPoint(), Size( 1, 1 ) );
+ break;
+
+ case MetaActionType::LINE:
+ {
+ const MetaLineAction& rMetaLineAction = static_cast<const MetaLineAction&>(rAct);
+ aActionBounds = tools::Rectangle( rMetaLineAction.GetStartPoint(), rMetaLineAction.GetEndPoint() );
+ aActionBounds.Justify();
+ const tools::Long nLineWidth(rMetaLineAction.GetLineInfo().GetWidth());
+ if(nLineWidth)
+ {
+ const tools::Long nHalfLineWidth((nLineWidth + 1) / 2);
+ aActionBounds.AdjustLeft( -nHalfLineWidth );
+ aActionBounds.AdjustTop( -nHalfLineWidth );
+ aActionBounds.AdjustRight(nHalfLineWidth );
+ aActionBounds.AdjustBottom(nHalfLineWidth );
+ }
+ break;
+ }
+
+ case MetaActionType::RECT:
+ aActionBounds = static_cast<const MetaRectAction&>(rAct).GetRect();
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ aActionBounds = tools::Polygon( static_cast<const MetaRoundRectAction&>(rAct).GetRect(),
+ static_cast<const MetaRoundRectAction&>(rAct).GetHorzRound(),
+ static_cast<const MetaRoundRectAction&>(rAct).GetVertRound() ).GetBoundRect();
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ const tools::Rectangle& rRect = static_cast<const MetaEllipseAction&>(rAct).GetRect();
+ aActionBounds = tools::Polygon( rRect.Center(),
+ rRect.GetWidth() >> 1,
+ rRect.GetHeight() >> 1 ).GetBoundRect();
+ break;
+ }
+
+ case MetaActionType::ARC:
+ aActionBounds = tools::Polygon( static_cast<const MetaArcAction&>(rAct).GetRect(),
+ static_cast<const MetaArcAction&>(rAct).GetStartPoint(),
+ static_cast<const MetaArcAction&>(rAct).GetEndPoint(), PolyStyle::Arc ).GetBoundRect();
+ break;
+
+ case MetaActionType::PIE:
+ aActionBounds = tools::Polygon( static_cast<const MetaPieAction&>(rAct).GetRect(),
+ static_cast<const MetaPieAction&>(rAct).GetStartPoint(),
+ static_cast<const MetaPieAction&>(rAct).GetEndPoint(), PolyStyle::Pie ).GetBoundRect();
+ break;
+
+ case MetaActionType::CHORD:
+ aActionBounds = tools::Polygon( static_cast<const MetaChordAction&>(rAct).GetRect(),
+ static_cast<const MetaChordAction&>(rAct).GetStartPoint(),
+ static_cast<const MetaChordAction&>(rAct).GetEndPoint(), PolyStyle::Chord ).GetBoundRect();
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ const MetaPolyLineAction& rMetaPolyLineAction = static_cast<const MetaPolyLineAction&>(rAct);
+ aActionBounds = rMetaPolyLineAction.GetPolygon().GetBoundRect();
+ const tools::Long nLineWidth(rMetaPolyLineAction.GetLineInfo().GetWidth());
+ if(nLineWidth)
+ {
+ const tools::Long nHalfLineWidth((nLineWidth + 1) / 2);
+ aActionBounds.AdjustLeft( -nHalfLineWidth );
+ aActionBounds.AdjustTop( -nHalfLineWidth );
+ aActionBounds.AdjustRight(nHalfLineWidth );
+ aActionBounds.AdjustBottom(nHalfLineWidth );
+ }
+ break;
+ }
+
+ case MetaActionType::POLYGON:
+ aActionBounds = static_cast<const MetaPolygonAction&>(rAct).GetPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ aActionBounds = static_cast<const MetaPolyPolygonAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::BMP:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpAction&>(rAct).GetPoint(),
+ rOut.PixelToLogic( static_cast<const MetaBmpAction&>(rAct).GetBitmap().GetSizePixel() ) );
+ break;
+
+ case MetaActionType::BMPSCALE:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaBmpScaleAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaBmpScalePartAction&>(rAct).GetDestSize() );
+ break;
+
+ case MetaActionType::BMPEX:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpExAction&>(rAct).GetPoint(),
+ rOut.PixelToLogic( static_cast<const MetaBmpExAction&>(rAct).GetBitmapEx().GetSizePixel() ) );
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpExScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaBmpExScaleAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestSize() );
+ break;
+
+ case MetaActionType::MASK:
+ aActionBounds = tools::Rectangle( static_cast<const MetaMaskAction&>(rAct).GetPoint(),
+ rOut.PixelToLogic( static_cast<const MetaMaskAction&>(rAct).GetBitmap().GetSizePixel() ) );
+ break;
+
+ case MetaActionType::MASKSCALE:
+ aActionBounds = tools::Rectangle( static_cast<const MetaMaskScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaMaskScaleAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ aActionBounds = tools::Rectangle( static_cast<const MetaMaskScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaMaskScalePartAction&>(rAct).GetDestSize() );
+ break;
+
+ case MetaActionType::GRADIENT:
+ aActionBounds = static_cast<const MetaGradientAction&>(rAct).GetRect();
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ aActionBounds = static_cast<const MetaGradientExAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::HATCH:
+ aActionBounds = static_cast<const MetaHatchAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::WALLPAPER:
+ aActionBounds = static_cast<const MetaWallpaperAction&>(rAct).GetRect();
+ break;
+
+ case MetaActionType::Transparent:
+ aActionBounds = static_cast<const MetaTransparentAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ aActionBounds = tools::Rectangle( static_cast<const MetaFloatTransparentAction&>(rAct).GetPoint(),
+ static_cast<const MetaFloatTransparentAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::EPS:
+ aActionBounds = tools::Rectangle( static_cast<const MetaEPSAction&>(rAct).GetPoint(),
+ static_cast<const MetaEPSAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction& rTextAct = static_cast<const MetaTextAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+
+ if (!aString.isEmpty())
+ {
+ const Point aPtLog( rTextAct.GetPoint() );
+
+ // #105987# Use API method instead of Impl* methods
+ // #107490# Set base parameter equal to index parameter
+ rOut.GetTextBoundRect( aActionBounds, rTextAct.GetText(), rTextAct.GetIndex(),
+ rTextAct.GetIndex(), rTextAct.GetLen() );
+ aActionBounds.Move( aPtLog.X(), aPtLog.Y() );
+ }
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction& rTextAct = static_cast<const MetaTextArrayAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+
+ if( !aString.isEmpty() )
+ {
+ // #105987# ImplLayout takes everything in logical coordinates
+ std::unique_ptr<SalLayout> pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(),
+ rTextAct.GetLen(), rTextAct.GetPoint(),
+ 0, rTextAct.GetDXArray());
+ if( pSalLayout )
+ {
+ tools::Rectangle aBoundRect( rOut.ImplGetTextBoundRect( *pSalLayout ) );
+ aActionBounds = rOut.PixelToLogic( aBoundRect );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ aActionBounds = static_cast<const MetaTextRectAction&>(rAct).GetRect();
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ const MetaStretchTextAction& rTextAct = static_cast<const MetaStretchTextAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+
+ // #i16195# Literate copy from TextArray action, the
+ // semantics for the ImplLayout call are copied from the
+ // OutDev::DrawStretchText() code. Unfortunately, also in
+ // this case, public outdev methods such as GetTextWidth()
+ // don't provide enough info.
+ if( !aString.isEmpty() )
+ {
+ // #105987# ImplLayout takes everything in logical coordinates
+ std::unique_ptr<SalLayout> pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(),
+ rTextAct.GetLen(), rTextAct.GetPoint(),
+ rTextAct.GetWidth() );
+ if( pSalLayout )
+ {
+ tools::Rectangle aBoundRect( rOut.ImplGetTextBoundRect( *pSalLayout ) );
+ aActionBounds = rOut.PixelToLogic( aBoundRect );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ OSL_FAIL("MetaActionType::TEXTLINE not supported");
+ break;
+
+ default:
+ break;
+ }
+
+ if( !aActionBounds.IsEmpty() )
+ {
+ // fdo#40421 limit current action's output to clipped area
+ if( rOut.IsClipRegion() )
+ return rOut.LogicToPixel(
+ rOut.GetClipRegion().GetBoundRect().Intersection( aActionBounds ) );
+ else
+ return rOut.LogicToPixel( aActionBounds );
+ }
+ else
+ return tools::Rectangle();
+}
+
+} // end anon namespace
+
+// TODO: this massive function operates on metafiles, so eventually it should probably
+// be shifted to the GDIMetaFile class
+bool OutputDevice::RemoveTransparenciesFromMetaFile( const GDIMetaFile& rInMtf, GDIMetaFile& rOutMtf,
+ tools::Long nMaxBmpDPIX, tools::Long nMaxBmpDPIY,
+ bool bReduceTransparency, bool bTransparencyAutoMode,
+ bool bDownsampleBitmaps,
+ const Color& rBackground
+ )
+{
+ MetaAction* pCurrAct;
+ bool bTransparent( false );
+
+ rOutMtf.Clear();
+
+ if(!bReduceTransparency || bTransparencyAutoMode)
+ bTransparent = rInMtf.HasTransparentActions();
+
+ // #i10613# Determine set of connected components containing transparent objects. These are
+ // then processed as bitmaps, the original actions are removed from the metafile.
+ if( !bTransparent )
+ {
+ // nothing transparent -> just copy
+ rOutMtf = rInMtf;
+ }
+ else
+ {
+ // #i10613#
+ // This works as follows: we want a number of distinct sets of
+ // connected components, where each set contains metafile
+ // actions that are intersecting (note: there are possibly
+ // more actions contained as are directly intersecting,
+ // because we can only produce rectangular bitmaps later
+ // on. Thus, each set of connected components is the smallest
+ // enclosing, axis-aligned rectangle that completely bounds a
+ // number of intersecting metafile actions, plus any action
+ // that would otherwise be cut in two). Therefore, we
+ // iteratively add metafile actions from the original metafile
+ // to this connected components list (aCCList), by checking
+ // each element's bounding box against intersection with the
+ // metaaction at hand.
+ // All those intersecting elements are removed from aCCList
+ // and collected in a temporary list (aCCMergeList). After all
+ // elements have been checked, the aCCMergeList elements are
+ // merged with the metaaction at hand into one resulting
+ // connected component, with one big bounding box, and
+ // inserted into aCCList again.
+ // The time complexity of this algorithm is O(n^3), where n is
+ // the number of metafile actions, and it finds all distinct
+ // regions of rectangle-bounded connected components. This
+ // algorithm was designed by AF.
+
+ // STAGE 1: Detect background
+
+ // Receives uniform background content, and is _not_ merged
+ // nor checked for intersection against other aCCList elements
+ ConnectedComponents aBackgroundComponent;
+
+ // Read the configuration value of minimal object area where transparency will be removed
+ double fReduceTransparencyMinArea = officecfg::Office::Common::VCL::ReduceTransparencyMinArea::get() / 100.0;
+ SAL_WARN_IF(fReduceTransparencyMinArea > 1.0, "vcl",
+ "Value of ReduceTransparencyMinArea config option is too high");
+ SAL_WARN_IF(fReduceTransparencyMinArea < 0.0, "vcl",
+ "Value of ReduceTransparencyMinArea config option is too low");
+ fReduceTransparencyMinArea = std::clamp(fReduceTransparencyMinArea, 0.0, 1.0);
+
+ // create an OutputDevice to record mapmode changes and the like
+ ScopedVclPtrInstance< VirtualDevice > aMapModeVDev;
+ aMapModeVDev->mnDPIX = mnDPIX;
+ aMapModeVDev->mnDPIY = mnDPIY;
+ aMapModeVDev->EnableOutput(false);
+
+ // weed out page-filling background objects (if they are
+ // uniformly coloured). Keeping them outside the other
+ // connected components often prevents whole-page bitmap
+ // generation.
+ bool bStillBackground=true; // true until first non-bg action
+ int nActionNum = 0, nLastBgAction = -1;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction();
+ if( rBackground != COL_TRANSPARENT )
+ {
+ aBackgroundComponent.aBgColor = rBackground;
+ aBackgroundComponent.aBounds = GetBackgroundComponentBounds();
+ }
+ while( pCurrAct && bStillBackground )
+ {
+ switch( pCurrAct->GetType() )
+ {
+ case MetaActionType::RECT:
+ {
+ if( !checkRect(
+ aBackgroundComponent.aBounds,
+ aBackgroundComponent.aBgColor,
+ static_cast<const MetaRectAction*>(pCurrAct)->GetRect(),
+ *aMapModeVDev) )
+ bStillBackground=false; // incomplete occlusion of background
+ else
+ nLastBgAction=nActionNum; // this _is_ background
+ break;
+ }
+ case MetaActionType::POLYGON:
+ {
+ const tools::Polygon aPoly(
+ static_cast<const MetaPolygonAction*>(pCurrAct)->GetPolygon());
+ if( !basegfx::utils::isRectangle(
+ aPoly.getB2DPolygon()) ||
+ !checkRect(
+ aBackgroundComponent.aBounds,
+ aBackgroundComponent.aBgColor,
+ aPoly.GetBoundRect(),
+ *aMapModeVDev) )
+ bStillBackground=false; // incomplete occlusion of background
+ else
+ nLastBgAction=nActionNum; // this _is_ background
+ break;
+ }
+ case MetaActionType::POLYPOLYGON:
+ {
+ const tools::PolyPolygon aPoly(
+ static_cast<const MetaPolyPolygonAction*>(pCurrAct)->GetPolyPolygon());
+ if( aPoly.Count() != 1 ||
+ !basegfx::utils::isRectangle(
+ aPoly[0].getB2DPolygon()) ||
+ !checkRect(
+ aBackgroundComponent.aBounds,
+ aBackgroundComponent.aBgColor,
+ aPoly.GetBoundRect(),
+ *aMapModeVDev) )
+ bStillBackground=false; // incomplete occlusion of background
+ else
+ nLastBgAction=nActionNum; // this _is_ background
+ break;
+ }
+ case MetaActionType::WALLPAPER:
+ {
+ if( !checkRect(
+ aBackgroundComponent.aBounds,
+ aBackgroundComponent.aBgColor,
+ static_cast<const MetaWallpaperAction*>(pCurrAct)->GetRect(),
+ *aMapModeVDev) )
+ bStillBackground=false; // incomplete occlusion of background
+ else
+ nLastBgAction=nActionNum; // this _is_ background
+ break;
+ }
+ default:
+ {
+ if( ImplIsNotTransparent( *pCurrAct,
+ *aMapModeVDev ) )
+ bStillBackground=false; // non-transparent action, possibly
+ // not uniform
+ else
+ // extend current bounds (next uniform action
+ // needs to fully cover this area)
+ aBackgroundComponent.aBounds.Union(
+ ImplCalcActionBounds(*pCurrAct, *aMapModeVDev) );
+ break;
+ }
+ }
+
+ // execute action to get correct MapModes etc.
+ pCurrAct->Execute( aMapModeVDev.get() );
+
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction();
+ ++nActionNum;
+ }
+
+ if (nLastBgAction != -1)
+ {
+ size_t nActionSize = rInMtf.GetActionSize();
+ // tdf#134736 move nLastBgAction to also include any trailing pops
+ for (size_t nPostLastBgAction = nLastBgAction + 1; nPostLastBgAction < nActionSize; ++nPostLastBgAction)
+ {
+ if (rInMtf.GetAction(nPostLastBgAction)->GetType() != MetaActionType::POP)
+ break;
+ nLastBgAction = nPostLastBgAction;
+ }
+ }
+
+ aMapModeVDev->ClearStack(); // clean up aMapModeVDev
+
+ // fast-forward until one after the last background action
+ // (need to reconstruct map mode vdev state)
+ nActionNum=0;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction();
+ while( pCurrAct && nActionNum<=nLastBgAction )
+ {
+ // up to and including last ink-generating background
+ // action go to background component
+ aBackgroundComponent.aComponentList.emplace_back(
+ pCurrAct, nActionNum );
+
+ // execute action to get correct MapModes etc.
+ pCurrAct->Execute( aMapModeVDev.get() );
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction();
+ ++nActionNum;
+ }
+
+ // STAGE 2: Generate connected components list
+
+ ::std::vector<ConnectedComponents> aCCList; // contains distinct sets of connected components as elements.
+
+ // iterate over all actions (start where background action
+ // search left off)
+ for( ;
+ pCurrAct;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum )
+ {
+ // execute action to get correct MapModes etc.
+ pCurrAct->Execute( aMapModeVDev.get() );
+
+ // cache bounds of current action
+ const tools::Rectangle aBBCurrAct( ImplCalcActionBounds(*pCurrAct, *aMapModeVDev) );
+
+ // accumulate collected bounds here, initialize with current action
+ tools::Rectangle aTotalBounds( aBBCurrAct ); // thus, aTotalComponents.aBounds is empty
+ // for non-output-generating actions
+ bool bTreatSpecial( false );
+ ConnectedComponents aTotalComponents;
+
+ // STAGE 2.1: Search for intersecting cc entries
+
+ // if aBBCurrAct is empty, it will intersect with no
+ // aCCList member. Thus, we can save the check.
+ // Furthermore, this ensures that non-output-generating
+ // actions get their own aCCList entry, which is necessary
+ // when copying them to the output metafile (see stage 4
+ // below).
+
+ // #107169# Wholly transparent objects need
+ // not be considered for connected components,
+ // too. Just put each of them into a separate
+ // component.
+ aTotalComponents.bIsFullyTransparent = !ImplIsNotTransparent(*pCurrAct, *aMapModeVDev);
+
+ if( !aBBCurrAct.IsEmpty() &&
+ !aTotalComponents.bIsFullyTransparent )
+ {
+ if( !aBackgroundComponent.aComponentList.empty() &&
+ !aBackgroundComponent.aBounds.Contains(aTotalBounds) )
+ {
+ // it seems the background is not large enough. to
+ // be on the safe side, combine with this component.
+ aTotalBounds.Union( aBackgroundComponent.aBounds );
+
+ // extract all aCurr actions to aTotalComponents
+ aTotalComponents.aComponentList.splice( aTotalComponents.aComponentList.end(),
+ aBackgroundComponent.aComponentList );
+
+ if( aBackgroundComponent.bIsSpecial )
+ bTreatSpecial = true;
+ }
+
+ bool bSomeComponentsChanged;
+
+ // now, this is unfortunate: since changing anyone of
+ // the aCCList elements (e.g. by merging or addition
+ // of an action) might generate new intersection with
+ // other aCCList elements, have to repeat the whole
+ // element scanning, until nothing changes anymore.
+ // Thus, this loop here makes us O(n^3) in the worst
+ // case.
+ do
+ {
+ // only loop here if 'intersects' branch below was hit
+ bSomeComponentsChanged = false;
+
+ // iterate over all current members of aCCList
+ for( auto aCurrCC=aCCList.begin(); aCurrCC != aCCList.end(); )
+ {
+ // first check if current element's bounds are
+ // empty. This ensures that empty actions are not
+ // merged into one component, as a matter of fact,
+ // they have no position.
+
+ // #107169# Wholly transparent objects need
+ // not be considered for connected components,
+ // too. Just put each of them into a separate
+ // component.
+ if( !aCurrCC->aBounds.IsEmpty() &&
+ !aCurrCC->bIsFullyTransparent &&
+ aCurrCC->aBounds.Overlaps( aTotalBounds ) )
+ {
+ // union the intersecting aCCList element into aTotalComponents
+
+ // calc union bounding box
+ aTotalBounds.Union( aCurrCC->aBounds );
+
+ // extract all aCurr actions to aTotalComponents
+ aTotalComponents.aComponentList.splice( aTotalComponents.aComponentList.end(),
+ aCurrCC->aComponentList );
+
+ if( aCurrCC->bIsSpecial )
+ bTreatSpecial = true;
+
+ // remove and delete aCurrCC element from list (we've now merged its content)
+ aCurrCC = aCCList.erase( aCurrCC );
+
+ // at least one component changed, need to rescan everything
+ bSomeComponentsChanged = true;
+ }
+ else
+ {
+ ++aCurrCC;
+ }
+ }
+ }
+ while( bSomeComponentsChanged );
+ }
+
+ // STAGE 2.2: Determine special state for cc element
+
+ // now test whether the whole connected component must be
+ // treated specially (i.e. rendered as a bitmap): if the
+ // added action is the very first action, or all actions
+ // before it are completely transparent, the connected
+ // component need not be treated specially, not even if
+ // the added action contains transparency. This is because
+ // painting of transparent objects on _white background_
+ // works without alpha compositing (you just calculate the
+ // color). Note that for the test "all objects before me
+ // are transparent" no sorting is necessary, since the
+ // added metaaction pCurrAct is always in the order the
+ // metafile is painted. Generally, the order of the
+ // metaactions in the ConnectedComponents are not
+ // guaranteed to be the same as in the metafile.
+ if( bTreatSpecial )
+ {
+ // prev component(s) special -> this one, too
+ aTotalComponents.bIsSpecial = true;
+ }
+ else if(!pCurrAct->IsTransparent())
+ {
+ // added action and none of prev components special ->
+ // this one normal, too
+ aTotalComponents.bIsSpecial = false;
+ }
+ else
+ {
+ // added action is special and none of prev components
+ // special -> do the detailed tests
+
+ // can the action handle transparency correctly
+ // (i.e. when painted on white background, does the
+ // action still look correct)?
+ if( !DoesActionHandleTransparency( *pCurrAct ) )
+ {
+ // no, action cannot handle its transparency on
+ // a printer device, render to bitmap
+ aTotalComponents.bIsSpecial = true;
+ }
+ else
+ {
+ // yes, action can handle its transparency, so
+ // check whether we're on white background
+ if( aTotalComponents.aComponentList.empty() )
+ {
+ // nothing between pCurrAct and page
+ // background -> don't be special
+ aTotalComponents.bIsSpecial = false;
+ }
+ else
+ {
+ // #107169# Fixes above now ensure that _no_
+ // object in the list is fully transparent. Thus,
+ // if the component list is not empty above, we
+ // must assume that we have to treat this
+ // component special.
+
+ // there are non-transparent objects between
+ // pCurrAct and the empty sheet of paper -> be
+ // special, then
+ aTotalComponents.bIsSpecial = true;
+ }
+ }
+ }
+
+ // STAGE 2.3: Add newly generated CC list element
+
+ // set new bounds and add action to list
+ aTotalComponents.aBounds = aTotalBounds;
+ aTotalComponents.aComponentList.emplace_back(
+ pCurrAct, nActionNum );
+
+ // add aTotalComponents as a new entry to aCCList
+ aCCList.push_back( aTotalComponents );
+
+ SAL_WARN_IF( aTotalComponents.aComponentList.empty(), "vcl",
+ "Printer::GetPreparedMetaFile empty component" );
+ SAL_WARN_IF( aTotalComponents.aBounds.IsEmpty() && (aTotalComponents.aComponentList.size() != 1), "vcl",
+ "Printer::GetPreparedMetaFile non-output generating actions must be solitary");
+ SAL_WARN_IF( aTotalComponents.bIsFullyTransparent && (aTotalComponents.aComponentList.size() != 1), "vcl",
+ "Printer::GetPreparedMetaFile fully transparent actions must be solitary");
+ }
+
+ // well now, we've got the list of disjunct connected
+ // components. Now we've got to create a map, which contains
+ // the corresponding aCCList element for every
+ // metaaction. Later on, we always process the complete
+ // metafile for each bitmap to be generated, but switch on
+ // output only for actions contained in the then current
+ // aCCList element. This ensures correct mapmode and attribute
+ // settings for all cases.
+
+ // maps mtf actions to CC list entries
+ ::std::vector< const ConnectedComponents* > aCCList_MemberMap( rInMtf.GetActionSize() );
+
+ // iterate over all aCCList members and their contained metaactions
+ for (auto const& currentItem : aCCList)
+ {
+ for (auto const& currentAction : currentItem.aComponentList)
+ {
+ // set pointer to aCCList element for corresponding index
+ aCCList_MemberMap[ currentAction.second ] = &currentItem;
+ }
+ }
+
+ // STAGE 3.1: Output background mtf actions (if there are any)
+
+ for (auto & component : aBackgroundComponent.aComponentList)
+ {
+ // simply add this action (above, we inserted the actions
+ // starting at index 0 up to and including nLastBgAction)
+ rOutMtf.AddAction( component.first );
+ }
+
+ // STAGE 3.2: Generate banded bitmaps for special regions
+
+ Point aPageOffset;
+ Size aTmpSize( GetOutputSizePixel() );
+ if( meOutDevType == OUTDEV_PDF )
+ {
+ auto pPdfWriter = static_cast<vcl::PDFWriterImpl*>(this);
+ aTmpSize = LogicToPixel(pPdfWriter->getCurPageSize(), MapMode(MapUnit::MapPoint));
+
+ // also add error code to PDFWriter
+ pPdfWriter->insertError(vcl::PDFWriter::Warning_Transparency_Converted);
+ }
+ else if( meOutDevType == OUTDEV_PRINTER )
+ {
+ Printer* pThis = dynamic_cast<Printer*>(this);
+ assert(pThis);
+ aPageOffset = pThis->GetPageOffsetPixel();
+ aPageOffset = Point( 0, 0 ) - aPageOffset;
+ aTmpSize = pThis->GetPaperSizePixel();
+ }
+ const tools::Rectangle aOutputRect( aPageOffset, aTmpSize );
+ bool bTiling = dynamic_cast<Printer*>(this) != nullptr;
+
+ // iterate over all aCCList members and generate bitmaps for the special ones
+ for (auto & currentItem : aCCList)
+ {
+ if( currentItem.bIsSpecial )
+ {
+ tools::Rectangle aBoundRect( currentItem.aBounds );
+ aBoundRect.Intersection( aOutputRect );
+
+ const double fBmpArea( static_cast<double>(aBoundRect.GetWidth()) * aBoundRect.GetHeight() );
+ const double fOutArea( static_cast<double>(aOutputRect.GetWidth()) * aOutputRect.GetHeight() );
+
+ // check if output doesn't exceed given size
+ if( bReduceTransparency && bTransparencyAutoMode && ( fBmpArea > ( fReduceTransparencyMinArea * fOutArea ) ) )
+ {
+ // output normally. Therefore, we simply clear the
+ // special attribute, as everything non-special is
+ // copied to rOutMtf further below.
+ currentItem.bIsSpecial = false;
+ }
+ else
+ {
+ // create new bitmap action first
+ if( aBoundRect.GetWidth() && aBoundRect.GetHeight() )
+ {
+ Point aDstPtPix( aBoundRect.TopLeft() );
+ Size aDstSzPix;
+
+ ScopedVclPtrInstance<VirtualDevice> aMapVDev; // here, we record only mapmode information
+ aMapVDev->EnableOutput(false);
+
+ ScopedVclPtrInstance<VirtualDevice> aPaintVDev; // into this one, we render.
+ aPaintVDev->SetBackground( aBackgroundComponent.aBgColor );
+
+ rOutMtf.AddAction( new MetaPushAction( vcl::PushFlags::MAPMODE ) );
+ rOutMtf.AddAction( new MetaMapModeAction() );
+
+ aPaintVDev->SetDrawMode( GetDrawMode() );
+
+ while( aDstPtPix.Y() <= aBoundRect.Bottom() )
+ {
+ aDstPtPix.setX( aBoundRect.Left() );
+ aDstSzPix = bTiling ? Size( MAX_TILE_WIDTH, MAX_TILE_HEIGHT ) : aBoundRect.GetSize();
+
+ if( ( aDstPtPix.Y() + aDstSzPix.Height() - 1 ) > aBoundRect.Bottom() )
+ aDstSzPix.setHeight( aBoundRect.Bottom() - aDstPtPix.Y() + 1 );
+
+ while( aDstPtPix.X() <= aBoundRect.Right() )
+ {
+ if( ( aDstPtPix.X() + aDstSzPix.Width() - 1 ) > aBoundRect.Right() )
+ aDstSzPix.setWidth( aBoundRect.Right() - aDstPtPix.X() + 1 );
+
+ if( !tools::Rectangle( aDstPtPix, aDstSzPix ).Intersection( aBoundRect ).IsEmpty() &&
+ aPaintVDev->SetOutputSizePixel( aDstSzPix ) )
+ {
+ aPaintVDev->Push();
+ aMapVDev->Push();
+
+ aMapVDev->mnDPIX = aPaintVDev->mnDPIX = mnDPIX;
+ aMapVDev->mnDPIY = aPaintVDev->mnDPIY = mnDPIY;
+
+ aPaintVDev->EnableOutput(false);
+
+ // iterate over all actions
+ for( pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(), nActionNum=0;
+ pCurrAct;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum )
+ {
+ // enable output only for
+ // actions that are members of
+ // the current aCCList element
+ // (currentItem)
+ if( aCCList_MemberMap[nActionNum] == &currentItem )
+ aPaintVDev->EnableOutput();
+
+ // but process every action
+ const MetaActionType nType( pCurrAct->GetType() );
+
+ if( MetaActionType::MAPMODE == nType )
+ {
+ pCurrAct->Execute( aMapVDev.get() );
+
+ MapMode aMtfMap( aMapVDev->GetMapMode() );
+ const Point aNewOrg( aMapVDev->PixelToLogic( aDstPtPix ) );
+
+ aMtfMap.SetOrigin( Point( -aNewOrg.X(), -aNewOrg.Y() ) );
+ aPaintVDev->SetMapMode( aMtfMap );
+ }
+ else if( ( MetaActionType::PUSH == nType ) || MetaActionType::POP == nType )
+ {
+ pCurrAct->Execute( aMapVDev.get() );
+ pCurrAct->Execute( aPaintVDev.get() );
+ }
+ else if( MetaActionType::GRADIENT == nType )
+ {
+ MetaGradientAction* pGradientAction = static_cast<MetaGradientAction*>(pCurrAct);
+ Printer* pPrinter = dynamic_cast< Printer* >(this);
+ if( pPrinter )
+ pPrinter->DrawGradientEx( aPaintVDev.get(), pGradientAction->GetRect(), pGradientAction->GetGradient() );
+ else
+ DrawGradient( pGradientAction->GetRect(), pGradientAction->GetGradient() );
+ }
+ else
+ {
+ pCurrAct->Execute( aPaintVDev.get() );
+ }
+
+ Application::Reschedule( true );
+ }
+
+ const bool bOldMap = mbMap;
+ mbMap = aPaintVDev->mbMap = false;
+
+ Bitmap aBandBmp( aPaintVDev->GetBitmap( Point(), aDstSzPix ) );
+
+ // scale down bitmap, if requested
+ if( bDownsampleBitmaps )
+ aBandBmp = vcl::bitmap::GetDownsampledBitmap(PixelToLogic(LogicToPixel(aDstSzPix), MapMode(MapUnit::MapTwip)),
+ Point(), aBandBmp.GetSizePixel(),
+ aBandBmp, nMaxBmpDPIX, nMaxBmpDPIY);
+
+ rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_BEGIN" ) );
+ rOutMtf.AddAction( new MetaBmpScaleAction( aDstPtPix, aDstSzPix, aBandBmp ) );
+ rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_END" ) );
+
+ aPaintVDev->mbMap = true;
+ mbMap = bOldMap;
+ aMapVDev->Pop();
+ aPaintVDev->Pop();
+ }
+
+ // overlapping bands to avoid missing lines (e.g. PostScript)
+ aDstPtPix.AdjustX(aDstSzPix.Width() );
+ }
+
+ // overlapping bands to avoid missing lines (e.g. PostScript)
+ aDstPtPix.AdjustY(aDstSzPix.Height() );
+ }
+
+ rOutMtf.AddAction( new MetaPopAction() );
+ }
+ }
+ }
+ }
+
+ aMapModeVDev->ClearStack(); // clean up aMapModeVDev
+
+ // STAGE 4: Copy actions to output metafile
+
+ // iterate over all actions and duplicate the ones not in a
+ // special aCCList member into rOutMtf
+ for( pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(), nActionNum=0;
+ pCurrAct;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum )
+ {
+ const ConnectedComponents* pCurrAssociatedComponent = aCCList_MemberMap[nActionNum];
+
+ // NOTE: This relies on the fact that map-mode or draw
+ // mode changing actions are solitary aCCList elements and
+ // have empty bounding boxes, see comment on stage 2.1
+ // above
+ if( pCurrAssociatedComponent &&
+ (pCurrAssociatedComponent->aBounds.IsEmpty() ||
+ !pCurrAssociatedComponent->bIsSpecial) )
+ {
+ // #107169# Treat transparent bitmaps special, if they
+ // are the first (or sole) action in their bounds
+ // list. Note that we previously ensured that no
+ // fully-transparent objects are before us here.
+ if( DoesActionHandleTransparency( *pCurrAct ) &&
+ pCurrAssociatedComponent->aComponentList.begin()->first == pCurrAct )
+ {
+ // convert actions, where masked-out parts are of
+ // given background color
+ ImplConvertTransparentAction(rOutMtf,
+ *pCurrAct,
+ *aMapModeVDev,
+ aBackgroundComponent.aBgColor);
+ }
+ else
+ {
+ // simply add this action
+ rOutMtf.AddAction( pCurrAct );
+ }
+
+ pCurrAct->Execute(aMapModeVDev.get());
+ }
+ }
+
+ rOutMtf.SetPrefMapMode( rInMtf.GetPrefMapMode() );
+ rOutMtf.SetPrefSize( rInMtf.GetPrefSize() );
+
+#if OSL_DEBUG_LEVEL > 1
+ // iterate over all aCCList members and generate rectangles for the bounding boxes
+ rOutMtf.AddAction( new MetaFillColorAction( COL_WHITE, false ) );
+ for(auto const& aCurr:aCCList)
+ {
+ if( aCurr.bIsSpecial )
+ rOutMtf.AddAction( new MetaLineColorAction( COL_RED, true) );
+ else
+ rOutMtf.AddAction( new MetaLineColorAction( COL_BLUE, true) );
+
+ rOutMtf.AddAction( new MetaRectAction( aMapModeVDev->PixelToLogic( aCurr.aBounds ) ) );
+ }
+#endif
+ }
+ return bTransparent;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/vclreferencebase.cxx b/vcl/source/outdev/vclreferencebase.cxx
new file mode 100644
index 000000000..62dd0e67f
--- /dev/null
+++ b/vcl/source/outdev/vclreferencebase.cxx
@@ -0,0 +1,44 @@
+/* -*- 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 <vcl/vclreferencebase.hxx>
+
+VclReferenceBase::VclReferenceBase() :
+ mnRefCnt(1), // cf. VclPtrInstance and README.lifecycle
+ mbDisposed(false)
+{
+}
+
+VclReferenceBase::~VclReferenceBase()
+{
+ disposeOnce();
+}
+
+void VclReferenceBase::disposeOnce()
+{
+ if ( mbDisposed )
+ return;
+ mbDisposed = true;
+ dispose();
+}
+
+void VclReferenceBase::dispose()
+{
+}
+
diff --git a/vcl/source/outdev/wallpaper.cxx b/vcl/source/outdev/wallpaper.cxx
new file mode 100644
index 000000000..2cd655c0e
--- /dev/null
+++ b/vcl/source/outdev/wallpaper.cxx
@@ -0,0 +1,396 @@
+/* -*- 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 <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <cassert>
+
+Color OutputDevice::GetReadableFontColor(const Color& rFontColor, const Color& rBgColor) const
+{
+ if (rBgColor.IsDark() && rFontColor.IsDark())
+ return COL_WHITE;
+ else if (rBgColor.IsBright() && rFontColor.IsBright())
+ return COL_BLACK;
+ else
+ return rFontColor;
+}
+
+void OutputDevice::DrawWallpaper( const tools::Rectangle& rRect,
+ const Wallpaper& rWallpaper )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaWallpaperAction( rRect, rWallpaper ) );
+
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ if ( rWallpaper.GetStyle() != WallpaperStyle::NONE )
+ {
+ tools::Rectangle aRect = LogicToPixel( rRect );
+ aRect.Justify();
+
+ if ( !aRect.IsEmpty() )
+ {
+ DrawWallpaper( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(),
+ rWallpaper );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawWallpaper( rRect, rWallpaper );
+}
+
+void OutputDevice::DrawWallpaper( tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ const Wallpaper& rWallpaper )
+{
+ assert(!is_double_buffered_window());
+
+ if( rWallpaper.IsBitmap() )
+ DrawBitmapWallpaper( nX, nY, nWidth, nHeight, rWallpaper );
+ else if( rWallpaper.IsGradient() )
+ DrawGradientWallpaper( nX, nY, nWidth, nHeight, rWallpaper );
+ else
+ DrawColorWallpaper( nX, nY, nWidth, nHeight, rWallpaper );
+}
+
+void OutputDevice::DrawColorWallpaper( tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ const Wallpaper& rWallpaper )
+{
+ assert(!is_double_buffered_window());
+
+ // draw wallpaper without border
+ Color aOldLineColor = GetLineColor();
+ Color aOldFillColor = GetFillColor();
+ SetLineColor();
+ SetFillColor( rWallpaper.GetColor() );
+
+ bool bMap = mbMap;
+ EnableMapMode( false );
+ DrawRect( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ) );
+ SetLineColor( aOldLineColor );
+ SetFillColor( aOldFillColor );
+ EnableMapMode( bMap );
+}
+
+void OutputDevice::Erase()
+{
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ if ( mbBackground )
+ {
+ RasterOp eRasterOp = GetRasterOp();
+ if ( eRasterOp != RasterOp::OverPaint )
+ SetRasterOp( RasterOp::OverPaint );
+ DrawWallpaper( 0, 0, mnOutWidth, mnOutHeight, maBackground );
+ if ( eRasterOp != RasterOp::OverPaint )
+ SetRasterOp( eRasterOp );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->Erase();
+}
+
+void OutputDevice::Erase(const tools::Rectangle& rRect)
+{
+ const RasterOp eRasterOp = GetRasterOp();
+ if ( eRasterOp != RasterOp::OverPaint )
+ SetRasterOp( RasterOp::OverPaint );
+ DrawWallpaper(rRect, GetBackground());
+ if ( eRasterOp != RasterOp::OverPaint )
+ SetRasterOp( eRasterOp );
+
+ if (mpAlphaVDev)
+ mpAlphaVDev->Erase(rRect);
+}
+
+void OutputDevice::DrawBitmapWallpaper( tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ const Wallpaper& rWallpaper )
+{
+ assert(!is_double_buffered_window());
+
+ const BitmapEx* pCached = rWallpaper.ImplGetCachedBitmap();
+
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ const bool bOldMap = mbMap;
+
+ BitmapEx aBmpEx;
+ if( pCached )
+ aBmpEx = *pCached;
+ else
+ aBmpEx = rWallpaper.GetBitmap();
+
+ const tools::Long nBmpWidth = aBmpEx.GetSizePixel().Width();
+ const tools::Long nBmpHeight = aBmpEx.GetSizePixel().Height();
+ const bool bTransparent = aBmpEx.IsAlpha();
+
+ const WallpaperStyle eStyle = rWallpaper.GetStyle();
+
+ bool bDrawGradientBackground = false;
+ bool bDrawColorBackground = false;
+
+ // draw background
+ if( bTransparent )
+ {
+ if( rWallpaper.IsGradient() )
+ bDrawGradientBackground = true;
+ else
+ {
+ if( !pCached && !rWallpaper.GetColor().IsTransparent() )
+ {
+ ScopedVclPtrInstance< VirtualDevice > aVDev( *this );
+ aVDev->SetBackground( rWallpaper.GetColor() );
+ aVDev->SetOutputSizePixel( Size( nBmpWidth, nBmpHeight ) );
+ aVDev->DrawBitmapEx( Point(), aBmpEx );
+ aBmpEx = aVDev->GetBitmapEx( Point(), aVDev->GetOutputSizePixel() );
+ }
+
+ bDrawColorBackground = true;
+ }
+ }
+ else if( eStyle != WallpaperStyle::Tile && eStyle != WallpaperStyle::Scale )
+ {
+ if( rWallpaper.IsGradient() )
+ bDrawGradientBackground = true;
+ else
+ bDrawColorBackground = true;
+ }
+
+ // background of bitmap?
+ if( bDrawGradientBackground )
+ {
+ DrawGradientWallpaper( nX, nY, nWidth, nHeight, rWallpaper );
+ }
+ else if( bDrawColorBackground && bTransparent )
+ {
+ DrawColorWallpaper( nX, nY, nWidth, nHeight, rWallpaper );
+ bDrawColorBackground = false;
+ }
+
+ Point aPos;
+ Size aSize;
+
+ // calc pos and size
+ if( rWallpaper.IsRect() )
+ {
+ const tools::Rectangle aBound( LogicToPixel( rWallpaper.GetRect() ) );
+ aPos = aBound.TopLeft();
+ aSize = aBound.GetSize();
+ }
+ else
+ {
+ aPos = Point( 0, 0 );
+ aSize = Size( nWidth, nHeight );
+ }
+
+ mpMetaFile = nullptr;
+ EnableMapMode( false );
+ Push( vcl::PushFlags::CLIPREGION );
+ IntersectClipRegion( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ) );
+
+ bool bDrawn = false;
+
+ switch( eStyle )
+ {
+ case WallpaperStyle::Scale:
+ if( !pCached || ( pCached->GetSizePixel() != aSize ) )
+ {
+ if( pCached )
+ rWallpaper.ImplReleaseCachedBitmap();
+
+ aBmpEx = rWallpaper.GetBitmap();
+ aBmpEx.Scale( aSize );
+ aBmpEx = BitmapEx( aBmpEx.GetBitmap().CreateDisplayBitmap( this ), aBmpEx.GetAlpha() );
+ }
+ break;
+
+ case WallpaperStyle::TopLeft:
+ break;
+
+ case WallpaperStyle::Top:
+ aPos.AdjustX(( aSize.Width() - nBmpWidth ) >> 1 );
+ break;
+
+ case WallpaperStyle::TopRight:
+ aPos.AdjustX( aSize.Width() - nBmpWidth);
+ break;
+
+ case WallpaperStyle::Left:
+ aPos.AdjustY(( aSize.Height() - nBmpHeight ) >> 1 );
+ break;
+
+ case WallpaperStyle::Center:
+ aPos.AdjustX(( aSize.Width() - nBmpWidth ) >> 1 );
+ aPos.AdjustY(( aSize.Height() - nBmpHeight ) >> 1 );
+ break;
+
+ case WallpaperStyle::Right:
+ aPos.AdjustX(aSize.Width() - nBmpWidth);
+ aPos.AdjustY(( aSize.Height() - nBmpHeight ) >> 1 );
+ break;
+
+ case WallpaperStyle::BottomLeft:
+ aPos.AdjustY( aSize.Height() - nBmpHeight );
+ break;
+
+ case WallpaperStyle::Bottom:
+ aPos.AdjustX(( aSize.Width() - nBmpWidth ) >> 1 );
+ aPos.AdjustY( aSize.Height() - nBmpHeight );
+ break;
+
+ case WallpaperStyle::BottomRight:
+ aPos.AdjustX( aSize.Width() - nBmpWidth );
+ aPos.AdjustY( aSize.Height() - nBmpHeight );
+ break;
+
+ default:
+ {
+ const tools::Long nRight = nX + nWidth - 1;
+ const tools::Long nBottom = nY + nHeight - 1;
+ tools::Long nFirstX;
+ tools::Long nFirstY;
+
+ if( eStyle == WallpaperStyle::Tile )
+ {
+ nFirstX = aPos.X();
+ nFirstY = aPos.Y();
+ }
+ else
+ {
+ nFirstX = aPos.X() + ( ( aSize.Width() - nBmpWidth ) >> 1 );
+ nFirstY = aPos.Y() + ( ( aSize.Height() - nBmpHeight ) >> 1 );
+ }
+
+ const tools::Long nOffX = ( nFirstX - nX ) % nBmpWidth;
+ const tools::Long nOffY = ( nFirstY - nY ) % nBmpHeight;
+ tools::Long nStartX = nX + nOffX;
+ tools::Long nStartY = nY + nOffY;
+
+ if( nOffX > 0 )
+ nStartX -= nBmpWidth;
+
+ if( nOffY > 0 )
+ nStartY -= nBmpHeight;
+
+ for( tools::Long nBmpY = nStartY; nBmpY <= nBottom; nBmpY += nBmpHeight )
+ {
+ for( tools::Long nBmpX = nStartX; nBmpX <= nRight; nBmpX += nBmpWidth )
+ {
+ DrawBitmapEx( Point( nBmpX, nBmpY ), aBmpEx );
+ }
+ }
+ bDrawn = true;
+ }
+ break;
+ }
+
+ if( !bDrawn )
+ {
+ // optimized for non-transparent bitmaps
+ if( bDrawColorBackground )
+ {
+ const Size aBmpSize( aBmpEx.GetSizePixel() );
+ const Point aTmpPoint;
+ const tools::Rectangle aOutRect( aTmpPoint, GetOutputSizePixel() );
+ const tools::Rectangle aColRect( Point( nX, nY ), Size( nWidth, nHeight ) );
+
+ tools::Rectangle aWorkRect( 0, 0, aOutRect.Right(), aPos.Y() - 1 );
+ aWorkRect.Justify();
+ aWorkRect.Intersection( aColRect );
+ if( !aWorkRect.IsEmpty() )
+ {
+ DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(),
+ aWorkRect.GetWidth(), aWorkRect.GetHeight(),
+ rWallpaper );
+ }
+
+ aWorkRect = tools::Rectangle( 0, aPos.Y(), aPos.X() - 1, aPos.Y() + aBmpSize.Height() - 1 );
+ aWorkRect.Justify();
+ aWorkRect.Intersection( aColRect );
+ if( !aWorkRect.IsEmpty() )
+ {
+ DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(),
+ aWorkRect.GetWidth(), aWorkRect.GetHeight(),
+ rWallpaper );
+ }
+
+ aWorkRect = tools::Rectangle( aPos.X() + aBmpSize.Width(), aPos.Y(),
+ aOutRect.Right(), aPos.Y() + aBmpSize.Height() - 1 );
+ aWorkRect.Justify();
+ aWorkRect.Intersection( aColRect );
+ if( !aWorkRect.IsEmpty() )
+ {
+ DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(),
+ aWorkRect.GetWidth(), aWorkRect.GetHeight(),
+ rWallpaper );
+ }
+
+ aWorkRect = tools::Rectangle( 0, aPos.Y() + aBmpSize.Height(),
+ aOutRect.Right(), aOutRect.Bottom() );
+ aWorkRect.Justify();
+ aWorkRect.Intersection( aColRect );
+ if( !aWorkRect.IsEmpty() )
+ {
+ DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(),
+ aWorkRect.GetWidth(), aWorkRect.GetHeight(),
+ rWallpaper );
+ }
+ }
+
+ DrawBitmapEx( aPos, aBmpEx );
+ }
+
+ rWallpaper.ImplSetCachedBitmap( aBmpEx );
+
+ Pop();
+ EnableMapMode( bOldMap );
+ mpMetaFile = pOldMetaFile;
+}
+
+void OutputDevice::DrawGradientWallpaper( tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ const Wallpaper& rWallpaper )
+{
+ assert(!is_double_buffered_window());
+
+ tools::Rectangle aBound;
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ const bool bOldMap = mbMap;
+
+ aBound = tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) );
+
+ mpMetaFile = nullptr;
+ EnableMapMode( false );
+ Push( vcl::PushFlags::CLIPREGION );
+ IntersectClipRegion( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ) );
+
+ DrawGradient( aBound, rWallpaper.GetGradient() );
+
+ Pop();
+ EnableMapMode( bOldMap );
+ mpMetaFile = pOldMetaFile;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/pdf/DummyPDFiumLibrary.cxx b/vcl/source/pdf/DummyPDFiumLibrary.cxx
new file mode 100644
index 000000000..7c42084eb
--- /dev/null
+++ b/vcl/source/pdf/DummyPDFiumLibrary.cxx
@@ -0,0 +1,23 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/filter/PDFiumLibrary.hxx>
+
+namespace vcl::pdf
+{
+std::shared_ptr<PDFium>& PDFiumLibrary::get()
+{
+ static std::shared_ptr<PDFium> pInstance;
+ return pInstance;
+}
+
+} // end vcl::pdf
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/pdf/ExternalPDFStreams.cxx b/vcl/source/pdf/ExternalPDFStreams.cxx
new file mode 100644
index 000000000..e3716e1e0
--- /dev/null
+++ b/vcl/source/pdf/ExternalPDFStreams.cxx
@@ -0,0 +1,42 @@
+/* -*- 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/.
+ *
+ */
+
+#include <pdf/ExternalPDFStreams.hxx>
+#include <comphelper/hash.hxx>
+
+namespace vcl
+{
+sal_Int32 ExternalPDFStreams::store(BinaryDataContainer const& rDataContainer)
+{
+ sal_Int32 nIndex = -1;
+
+ std::vector<sal_uInt8> aHash = comphelper::Hash::calculateHash(
+ rDataContainer.getData(), rDataContainer.getSize(), comphelper::HashType::SHA1);
+
+ auto it = maStreamIndexMap.find(aHash);
+ if (it == maStreamIndexMap.end())
+ {
+ auto& rExternalStream = maStreamList.emplace_back();
+ rExternalStream.maDataContainer = rDataContainer;
+ nIndex = maStreamList.size() - 1;
+ maStreamIndexMap.emplace(aHash, nIndex);
+ }
+ else
+ {
+ nIndex = it->second;
+ }
+
+ return nIndex;
+}
+
+ExternalPDFStream& ExternalPDFStreams::get(sal_uInt32 nIndex) { return maStreamList.at(nIndex); }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/pdf/Matrix3.cxx b/vcl/source/pdf/Matrix3.cxx
new file mode 100644
index 000000000..475f87d96
--- /dev/null
+++ b/vcl/source/pdf/Matrix3.cxx
@@ -0,0 +1,116 @@
+/* -*- 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/.
+ *
+ */
+
+#include <pdf/Matrix3.hxx>
+#include <cmath>
+
+namespace vcl::pdf
+{
+Matrix3::Matrix3()
+{
+ // initialize to unity
+ f[0] = 1.0;
+ f[1] = 0.0;
+ f[2] = 0.0;
+ f[3] = 1.0;
+ f[4] = 0.0;
+ f[5] = 0.0;
+}
+
+Point Matrix3::transform(const Point& rOrig) const
+{
+ double x = static_cast<double>(rOrig.X()), y = static_cast<double>(rOrig.Y());
+ return Point(x * f[0] + y * f[2] + f[4], x * f[1] + y * f[3] + f[5]);
+}
+
+void Matrix3::skew(double alpha, double beta)
+{
+ double fn[6];
+ double tb = tan(beta);
+ fn[0] = f[0] + f[2] * tb;
+ fn[1] = f[1];
+ fn[2] = f[2] + f[3] * tb;
+ fn[3] = f[3];
+ fn[4] = f[4] + f[5] * tb;
+ fn[5] = f[5];
+ if (alpha != 0.0)
+ {
+ double ta = tan(alpha);
+ fn[1] += f[0] * ta;
+ fn[3] += f[2] * ta;
+ fn[5] += f[4] * ta;
+ }
+ set(fn);
+}
+
+void Matrix3::scale(double sx, double sy)
+{
+ double fn[6];
+ fn[0] = sx * f[0];
+ fn[1] = sy * f[1];
+ fn[2] = sx * f[2];
+ fn[3] = sy * f[3];
+ fn[4] = sx * f[4];
+ fn[5] = sy * f[5];
+ set(fn);
+}
+
+void Matrix3::rotate(double angle)
+{
+ double fn[6];
+ double fSin = sin(angle);
+ double fCos = cos(angle);
+ fn[0] = f[0] * fCos - f[1] * fSin;
+ fn[1] = f[0] * fSin + f[1] * fCos;
+ fn[2] = f[2] * fCos - f[3] * fSin;
+ fn[3] = f[2] * fSin + f[3] * fCos;
+ fn[4] = f[4] * fCos - f[5] * fSin;
+ fn[5] = f[4] * fSin + f[5] * fCos;
+ set(fn);
+}
+
+void Matrix3::translate(double tx, double ty)
+{
+ f[4] += tx;
+ f[5] += ty;
+}
+
+void Matrix3::invert()
+{
+ // short circuit trivial cases
+ if (f[1] == f[2] && f[1] == 0.0 && f[0] == f[3] && f[0] == 1.0)
+ {
+ f[4] = -f[4];
+ f[5] = -f[5];
+ return;
+ }
+
+ // check determinant
+ const double fDet = f[0] * f[3] - f[1] * f[2];
+ if (fDet == 0.0)
+ return;
+
+ // invert the matrix
+ double fn[6];
+ fn[0] = +f[3] / fDet;
+ fn[1] = -f[1] / fDet;
+ fn[2] = -f[2] / fDet;
+ fn[3] = +f[0] / fDet;
+
+ // apply inversion to translation
+ fn[4] = -(f[4] * fn[0] + f[5] * fn[2]);
+ fn[5] = -(f[4] * fn[1] + f[5] * fn[3]);
+
+ set(fn);
+}
+
+} // end vcl::pdf
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/pdf/PDFiumLibrary.cxx b/vcl/source/pdf/PDFiumLibrary.cxx
new file mode 100644
index 000000000..b02358d43
--- /dev/null
+++ b/vcl/source/pdf/PDFiumLibrary.cxx
@@ -0,0 +1,1333 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/filter/PDFiumLibrary.hxx>
+
+#include <cassert>
+
+#include <fpdf_doc.h>
+#include <fpdf_annot.h>
+#include <fpdf_edit.h>
+#include <fpdf_text.h>
+#include <fpdf_save.h>
+#include <fpdf_signature.h>
+#include <fpdf_formfill.h>
+
+#include <osl/endian.h>
+#include <vcl/bitmap.hxx>
+#include <tools/stream.hxx>
+#include <tools/UnitConversion.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+using namespace com::sun::star;
+
+static_assert(static_cast<int>(vcl::pdf::PDFPageObjectType::Unknown) == FPDF_PAGEOBJ_UNKNOWN,
+ "PDFPageObjectType::Unknown value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFPageObjectType::Text) == FPDF_PAGEOBJ_TEXT,
+ "PDFPageObjectType::Text value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFPageObjectType::Path) == FPDF_PAGEOBJ_PATH,
+ "PDFPageObjectType::Path value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFPageObjectType::Image) == FPDF_PAGEOBJ_IMAGE,
+ "PDFPageObjectType::Image value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFPageObjectType::Shading) == FPDF_PAGEOBJ_SHADING,
+ "PDFPageObjectType::Shading value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFPageObjectType::Form) == FPDF_PAGEOBJ_FORM,
+ "PDFPageObjectType::Form value mismatch");
+
+static_assert(static_cast<int>(vcl::pdf::PDFSegmentType::Unknown) == FPDF_SEGMENT_UNKNOWN,
+ "PDFSegmentType::Unknown value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFSegmentType::Lineto) == FPDF_SEGMENT_LINETO,
+ "PDFSegmentType::Lineto value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFSegmentType::Bezierto) == FPDF_SEGMENT_BEZIERTO,
+ "PDFSegmentType::Bezierto value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFSegmentType::Moveto) == FPDF_SEGMENT_MOVETO,
+ "PDFSegmentType::Moveto value mismatch");
+
+static_assert(static_cast<int>(vcl::pdf::PDFBitmapType::Unknown) == FPDFBitmap_Unknown,
+ "PDFBitmapType::Unknown value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFBitmapType::Gray) == FPDFBitmap_Gray,
+ "PDFBitmapType::Gray value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFBitmapType::BGR) == FPDFBitmap_BGR,
+ "PDFBitmapType::BGR value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFBitmapType::BGRx) == FPDFBitmap_BGRx,
+ "PDFBitmapType::BGRx value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFBitmapType::BGRA) == FPDFBitmap_BGRA,
+ "PDFBitmapType::BGRA value mismatch");
+
+static_assert(static_cast<int>(vcl::pdf::PDFObjectType::Unknown) == FPDF_OBJECT_UNKNOWN,
+ "PDFObjectType::Unknown value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFObjectType::Boolean) == FPDF_OBJECT_BOOLEAN,
+ "PDFObjectType::Boolean value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFObjectType::Number) == FPDF_OBJECT_NUMBER,
+ "PDFObjectType::Number value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFObjectType::String) == FPDF_OBJECT_STRING,
+ "PDFObjectType::String value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFObjectType::Name) == FPDF_OBJECT_NAME,
+ "PDFObjectType::Name value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFObjectType::Array) == FPDF_OBJECT_ARRAY,
+ "PDFObjectType::Array value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFObjectType::Dictionary) == FPDF_OBJECT_DICTIONARY,
+ "PDFObjectType::Dictionary value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFObjectType::Stream) == FPDF_OBJECT_STREAM,
+ "PDFObjectType::Stream value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFObjectType::Nullobj) == FPDF_OBJECT_NULLOBJ,
+ "PDFObjectType::Nullobj value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFObjectType::Reference) == FPDF_OBJECT_REFERENCE,
+ "PDFObjectType::Reference value mismatch");
+
+static_assert(static_cast<int>(vcl::pdf::PDFTextRenderMode::Unknown) == FPDF_TEXTRENDERMODE_UNKNOWN,
+ "PDFTextRenderMode::Unknown value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFTextRenderMode::Fill) == FPDF_TEXTRENDERMODE_FILL,
+ "PDFTextRenderMode::Fill value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFTextRenderMode::Stroke) == FPDF_TEXTRENDERMODE_STROKE,
+ "PDFTextRenderMode::Stroke value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFTextRenderMode::FillStroke)
+ == FPDF_TEXTRENDERMODE_FILL_STROKE,
+ "PDFTextRenderMode::FillStroke value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFTextRenderMode::Invisible)
+ == FPDF_TEXTRENDERMODE_INVISIBLE,
+ "PDFTextRenderMode::Invisible value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFTextRenderMode::FillClip)
+ == FPDF_TEXTRENDERMODE_FILL_CLIP,
+ "PDFTextRenderMode::FillClip value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFTextRenderMode::StrokeClip)
+ == FPDF_TEXTRENDERMODE_STROKE_CLIP,
+ "PDFTextRenderMode::StrokeClip value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFTextRenderMode::FillStrokeClip)
+ == FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP,
+ "PDFTextRenderMode::FillStrokeClip value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFTextRenderMode::Clip) == FPDF_TEXTRENDERMODE_CLIP,
+ "PDFTextRenderMode::Clip value mismatch");
+
+static_assert(static_cast<int>(vcl::pdf::PDFFillMode::None) == FPDF_FILLMODE_NONE,
+ "PDFFillMode::None value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFFillMode::Alternate) == FPDF_FILLMODE_ALTERNATE,
+ "PDFFillMode::Alternate value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFFillMode::Winding) == FPDF_FILLMODE_WINDING,
+ "PDFFillMode::Winding value mismatch");
+
+static_assert(static_cast<int>(vcl::pdf::PDFFindFlags::MatchCase) == FPDF_MATCHCASE,
+ "PDFFindFlags::MatchCase value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFFindFlags::MatchWholeWord) == FPDF_MATCHWHOLEWORD,
+ "PDFFindFlags::MatchWholeWord value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFFindFlags::Consecutive) == FPDF_CONSECUTIVE,
+ "PDFFindFlags::Consecutive value mismatch");
+
+static_assert(static_cast<int>(vcl::pdf::PDFErrorType::Success) == FPDF_ERR_SUCCESS,
+ "PDFErrorType::Success value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFErrorType::Unknown) == FPDF_ERR_UNKNOWN,
+ "PDFErrorType::Unknown value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFErrorType::File) == FPDF_ERR_FILE,
+ "PDFErrorType::File value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFErrorType::Format) == FPDF_ERR_FORMAT,
+ "PDFErrorType::Format value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFErrorType::Password) == FPDF_ERR_PASSWORD,
+ "PDFErrorType::Password value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFErrorType::Security) == FPDF_ERR_SECURITY,
+ "PDFErrorType::Security value mismatch");
+static_assert(static_cast<int>(vcl::pdf::PDFErrorType::Page) == FPDF_ERR_PAGE,
+ "PDFErrorType::Page value mismatch");
+
+namespace
+{
+/// Callback class to be used with FPDF_SaveWithVersion().
+struct CompatibleWriter : public FPDF_FILEWRITE
+{
+ CompatibleWriter(SvMemoryStream& rStream)
+ : m_rStream(rStream)
+ {
+ }
+
+ SvMemoryStream& m_rStream;
+};
+
+int CompatibleWriterCallback(FPDF_FILEWRITE* pFileWrite, const void* pData, unsigned long nSize)
+{
+ auto pImpl = static_cast<CompatibleWriter*>(pFileWrite);
+ pImpl->m_rStream.WriteBytes(pData, nSize);
+ return 1;
+}
+}
+
+namespace vcl::pdf
+{
+namespace
+{
+class PDFiumBitmapImpl final : public PDFiumBitmap
+{
+private:
+ FPDF_BITMAP mpBitmap;
+
+ PDFiumBitmapImpl(const PDFiumBitmapImpl&) = delete;
+ PDFiumBitmapImpl& operator=(const PDFiumBitmapImpl&) = delete;
+
+public:
+ PDFiumBitmapImpl(FPDF_BITMAP pBitmap);
+ ~PDFiumBitmapImpl() override;
+ FPDF_BITMAP getPointer() { return mpBitmap; }
+
+ void fillRect(int left, int top, int width, int height, sal_uInt32 nColor) override;
+ void renderPageBitmap(PDFiumDocument* pDoc, PDFiumPage* pPage, int nStartX, int nStartY,
+ int nSizeX, int nSizeY) override;
+ ConstScanline getBuffer() override;
+ int getStride() override;
+ int getWidth() override;
+ int getHeight() override;
+ PDFBitmapType getFormat() override;
+};
+
+class PDFiumPathSegmentImpl final : public PDFiumPathSegment
+{
+private:
+ FPDF_PATHSEGMENT mpPathSegment;
+
+ PDFiumPathSegmentImpl(const PDFiumPathSegmentImpl&) = delete;
+ PDFiumPathSegmentImpl& operator=(const PDFiumPathSegmentImpl&) = delete;
+
+public:
+ PDFiumPathSegmentImpl(FPDF_PATHSEGMENT pPathSegment);
+
+ basegfx::B2DPoint getPoint() const override;
+ bool isClosed() const override;
+ PDFSegmentType getType() const override;
+};
+
+class PDFiumAnnotationImpl final : public PDFiumAnnotation
+{
+private:
+ FPDF_ANNOTATION mpAnnotation;
+
+ PDFiumAnnotationImpl(const PDFiumAnnotationImpl&) = delete;
+ PDFiumAnnotationImpl& operator=(const PDFiumAnnotationImpl&) = delete;
+
+public:
+ PDFiumAnnotationImpl(FPDF_ANNOTATION pAnnotation);
+ ~PDFiumAnnotationImpl();
+ FPDF_ANNOTATION getPointer() { return mpAnnotation; }
+
+ PDFAnnotationSubType getSubType() override;
+ basegfx::B2DRectangle getRectangle() override;
+ bool hasKey(OString const& rKey) override;
+ PDFObjectType getValueType(OString const& rKey) override;
+ OUString getString(OString const& rKey) override;
+ std::unique_ptr<PDFiumAnnotation> getLinked(OString const& rKey) override;
+ int getObjectCount() override;
+ std::unique_ptr<PDFiumPageObject> getObject(int nIndex) override;
+ std::vector<std::vector<basegfx::B2DPoint>> getInkStrokes() override;
+ std::vector<basegfx::B2DPoint> getVertices() override;
+ Color getColor() override;
+ Color getInteriorColor() override;
+ float getBorderWidth() override;
+ basegfx::B2DSize getBorderCornerRadius() override;
+ size_t getAttachmentPointsCount() override;
+ std::vector<basegfx::B2DPoint> getAttachmentPoints(size_t nIndex) override;
+ std::vector<basegfx::B2DPoint> getLineGeometry() override;
+};
+
+class PDFiumPageObjectImpl final : public PDFiumPageObject
+{
+private:
+ FPDF_PAGEOBJECT mpPageObject;
+
+ PDFiumPageObjectImpl(const PDFiumPageObjectImpl&) = delete;
+ PDFiumPageObjectImpl& operator=(const PDFiumPageObjectImpl&) = delete;
+
+public:
+ PDFiumPageObjectImpl(FPDF_PAGEOBJECT pPageObject);
+
+ PDFPageObjectType getType() override;
+ OUString getText(std::unique_ptr<PDFiumTextPage> const& pTextPage) override;
+
+ int getFormObjectCount() override;
+ std::unique_ptr<PDFiumPageObject> getFormObject(int nIndex) override;
+
+ basegfx::B2DHomMatrix getMatrix() override;
+ basegfx::B2DRectangle getBounds() override;
+ double getFontSize() override;
+ OUString getFontName() override;
+ PDFTextRenderMode getTextRenderMode() override;
+ Color getFillColor() override;
+ Color getStrokeColor() override;
+ double getStrokeWidth() override;
+ // Path
+ int getPathSegmentCount() override;
+ std::unique_ptr<PDFiumPathSegment> getPathSegment(int index) override;
+ Size getImageSize(PDFiumPage& rPage) override;
+ std::unique_ptr<PDFiumBitmap> getImageBitmap() override;
+ bool getDrawMode(PDFFillMode& eFillMode, bool& bStroke) override;
+};
+
+class PDFiumSearchHandleImpl final : public PDFiumSearchHandle
+{
+private:
+ FPDF_SCHHANDLE mpSearchHandle;
+
+ PDFiumSearchHandleImpl(const PDFiumSearchHandleImpl&) = delete;
+ PDFiumSearchHandleImpl& operator=(const PDFiumSearchHandleImpl&) = delete;
+
+public:
+ PDFiumSearchHandleImpl(FPDF_SCHHANDLE pSearchHandle);
+ ~PDFiumSearchHandleImpl();
+
+ bool findNext() override;
+ bool findPrev() override;
+ int getSearchResultIndex() override;
+ int getSearchCount() override;
+};
+
+class PDFiumTextPageImpl final : public PDFiumTextPage
+{
+private:
+ FPDF_TEXTPAGE mpTextPage;
+
+ PDFiumTextPageImpl(const PDFiumTextPageImpl&) = delete;
+ PDFiumTextPageImpl& operator=(const PDFiumTextPageImpl&) = delete;
+
+public:
+ PDFiumTextPageImpl(FPDF_TEXTPAGE pTextPage);
+ ~PDFiumTextPageImpl();
+
+ FPDF_TEXTPAGE getPointer() { return mpTextPage; }
+
+ int countChars() override;
+ unsigned int getUnicode(int index) override;
+ std::unique_ptr<PDFiumSearchHandle> findStart(const OUString& rFindWhat, PDFFindFlags nFlags,
+ sal_Int32 nStartIndex) override;
+
+ /// Returned rect is no longer upside down and is in mm100.
+ basegfx::B2DRectangle getCharBox(int nIndex, double fPageHeight) override;
+};
+
+class PDFiumSignatureImpl final : public PDFiumSignature
+{
+private:
+ FPDF_SIGNATURE mpSignature;
+ PDFiumSignatureImpl(const PDFiumSignatureImpl&) = delete;
+ PDFiumSignatureImpl& operator=(const PDFiumSignatureImpl&) = delete;
+
+public:
+ PDFiumSignatureImpl(FPDF_SIGNATURE pSignature);
+
+ std::vector<int> getByteRange() override;
+ int getDocMDPPermission() override;
+ std::vector<unsigned char> getContents() override;
+ OString getSubFilter() override;
+ OUString getReason() override;
+ css::util::DateTime getTime() override;
+};
+
+class PDFiumPageImpl final : public PDFiumPage
+{
+private:
+ FPDF_PAGE mpPage;
+
+private:
+ PDFiumPageImpl(const PDFiumPageImpl&) = delete;
+ PDFiumPageImpl& operator=(const PDFiumPageImpl&) = delete;
+
+public:
+ PDFiumPageImpl(FPDF_PAGE pPage)
+ : mpPage(pPage)
+ {
+ }
+
+ ~PDFiumPageImpl() override
+ {
+ if (mpPage)
+ FPDF_ClosePage(mpPage);
+ }
+
+ FPDF_PAGE getPointer() { return mpPage; }
+
+ int getObjectCount() override;
+ std::unique_ptr<PDFiumPageObject> getObject(int nIndex) override;
+
+ int getAnnotationCount() override;
+ int getAnnotationIndex(std::unique_ptr<PDFiumAnnotation> const& rAnnotation) override;
+
+ std::unique_ptr<PDFiumAnnotation> getAnnotation(int nIndex) override;
+
+ std::unique_ptr<PDFiumTextPage> getTextPage() override;
+
+ BitmapChecksum getChecksum(int nMDPPerm) override;
+
+ double getWidth() override;
+ double getHeight() override;
+
+ bool hasTransparency() override;
+
+ bool hasLinks() override;
+};
+
+/// Wrapper around FPDF_FORMHANDLE.
+class PDFiumFormHandle final
+{
+private:
+ FPDF_FORMHANDLE mpHandle;
+
+ PDFiumFormHandle(const PDFiumFormHandle&) = delete;
+ PDFiumFormHandle& operator=(const PDFiumFormHandle&) = delete;
+
+public:
+ PDFiumFormHandle(FPDF_FORMHANDLE pHandle);
+ ~PDFiumFormHandle();
+ FPDF_FORMHANDLE getPointer();
+};
+
+class PDFiumDocumentImpl : public PDFiumDocument
+{
+private:
+ FPDF_DOCUMENT mpPdfDocument;
+ FPDF_FORMFILLINFO m_aFormCallbacks;
+ std::unique_ptr<PDFiumFormHandle> m_pFormHandle;
+
+private:
+ PDFiumDocumentImpl(const PDFiumDocumentImpl&) = delete;
+ PDFiumDocumentImpl& operator=(const PDFiumDocumentImpl&) = delete;
+
+public:
+ PDFiumDocumentImpl(FPDF_DOCUMENT pPdfDocument);
+ ~PDFiumDocumentImpl() override;
+ FPDF_FORMHANDLE getFormHandlePointer();
+
+ // Page size in points
+ basegfx::B2DSize getPageSize(int nIndex) override;
+ int getPageCount() override;
+ int getSignatureCount() override;
+ int getFileVersion() override;
+ bool saveWithVersion(SvMemoryStream& rStream, int nFileVersion) override;
+
+ std::unique_ptr<PDFiumPage> openPage(int nIndex) override;
+ std::unique_ptr<PDFiumSignature> getSignature(int nIndex) override;
+ std::vector<unsigned int> getTrailerEnds() override;
+};
+
+class PDFiumImpl : public PDFium
+{
+private:
+ PDFiumImpl(const PDFiumImpl&) = delete;
+ PDFiumImpl& operator=(const PDFiumImpl&) = delete;
+
+ OUString maLastError;
+
+public:
+ PDFiumImpl();
+ ~PDFiumImpl() override;
+
+ const OUString& getLastError() const override { return maLastError; }
+
+ std::unique_ptr<PDFiumDocument> openDocument(const void* pData, int nSize,
+ const OString& rPassword) override;
+ PDFErrorType getLastErrorCode() override;
+ std::unique_ptr<PDFiumBitmap> createBitmap(int nWidth, int nHeight, int nAlpha) override;
+};
+}
+
+PDFiumImpl::PDFiumImpl()
+{
+ FPDF_LIBRARY_CONFIG aConfig;
+ aConfig.version = 2;
+ aConfig.m_pUserFontPaths = nullptr;
+ aConfig.m_pIsolate = nullptr;
+ aConfig.m_v8EmbedderSlot = 0;
+ FPDF_InitLibraryWithConfig(&aConfig);
+}
+
+PDFiumImpl::~PDFiumImpl() { FPDF_DestroyLibrary(); }
+
+std::unique_ptr<PDFiumDocument> PDFiumImpl::openDocument(const void* pData, int nSize,
+ const OString& rPassword)
+{
+ maLastError = OUString();
+ std::unique_ptr<PDFiumDocument> pPDFiumDocument;
+
+ FPDF_BYTESTRING pPassword = nullptr;
+ if (!rPassword.isEmpty())
+ {
+ pPassword = rPassword.getStr();
+ }
+ FPDF_DOCUMENT pDocument = FPDF_LoadMemDocument(pData, nSize, pPassword);
+
+ if (!pDocument)
+ {
+ switch (FPDF_GetLastError())
+ {
+ case FPDF_ERR_SUCCESS:
+ maLastError = "Success";
+ break;
+ case FPDF_ERR_UNKNOWN:
+ maLastError = "Unknown error";
+ break;
+ case FPDF_ERR_FILE:
+ maLastError = "File not found";
+ break;
+ case FPDF_ERR_FORMAT:
+ maLastError = "Input is not a PDF format";
+ break;
+ case FPDF_ERR_PASSWORD:
+ maLastError = "Incorrect password or password is required";
+ break;
+ case FPDF_ERR_SECURITY:
+ maLastError = "Security error";
+ break;
+ case FPDF_ERR_PAGE:
+ maLastError = "Content error";
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ pPDFiumDocument = std::make_unique<PDFiumDocumentImpl>(pDocument);
+ }
+
+ return pPDFiumDocument;
+}
+
+PDFErrorType PDFiumImpl::getLastErrorCode()
+{
+ return static_cast<PDFErrorType>(FPDF_GetLastError());
+}
+
+std::unique_ptr<PDFiumBitmap> PDFiumImpl::createBitmap(int nWidth, int nHeight, int nAlpha)
+{
+ std::unique_ptr<PDFiumBitmap> pPDFiumBitmap;
+ FPDF_BITMAP pPdfBitmap = FPDFBitmap_Create(nWidth, nHeight, nAlpha);
+ if (!pPdfBitmap)
+ {
+ maLastError = "Failed to create bitmap";
+ }
+ else
+ {
+ pPDFiumBitmap = std::make_unique<PDFiumBitmapImpl>(pPdfBitmap);
+ }
+ return pPDFiumBitmap;
+}
+
+PDFiumSignatureImpl::PDFiumSignatureImpl(FPDF_SIGNATURE pSignature)
+ : mpSignature(pSignature)
+{
+}
+
+std::vector<int> PDFiumSignatureImpl::getByteRange()
+{
+ int nByteRangeLen = FPDFSignatureObj_GetByteRange(mpSignature, nullptr, 0);
+ std::vector<int> aByteRange(nByteRangeLen);
+ if (nByteRangeLen <= 0)
+ {
+ return aByteRange;
+ }
+
+ FPDFSignatureObj_GetByteRange(mpSignature, aByteRange.data(), aByteRange.size());
+ return aByteRange;
+}
+
+int PDFiumSignatureImpl::getDocMDPPermission()
+{
+ return FPDFSignatureObj_GetDocMDPPermission(mpSignature);
+}
+
+std::vector<unsigned char> PDFiumSignatureImpl::getContents()
+{
+ int nContentsLen = FPDFSignatureObj_GetContents(mpSignature, nullptr, 0);
+ std::vector<unsigned char> aContents(nContentsLen);
+ if (aContents.empty())
+ {
+ return aContents;
+ }
+
+ FPDFSignatureObj_GetContents(mpSignature, aContents.data(), aContents.size());
+ return aContents;
+}
+
+OString PDFiumSignatureImpl::getSubFilter()
+{
+ int nSubFilterLen = FPDFSignatureObj_GetSubFilter(mpSignature, nullptr, 0);
+ std::vector<char> aSubFilterBuf(nSubFilterLen);
+ FPDFSignatureObj_GetSubFilter(mpSignature, aSubFilterBuf.data(), aSubFilterBuf.size());
+ // Buffer is NUL-terminated.
+ OString aSubFilter(aSubFilterBuf.data(), aSubFilterBuf.size() - 1);
+ return aSubFilter;
+}
+
+OUString PDFiumSignatureImpl::getReason()
+{
+ int nReasonLen = FPDFSignatureObj_GetReason(mpSignature, nullptr, 0);
+ OUString aRet;
+ if (nReasonLen > 0)
+ {
+ std::vector<char16_t> aReasonBuf(nReasonLen);
+ FPDFSignatureObj_GetReason(mpSignature, aReasonBuf.data(), aReasonBuf.size());
+ aRet = OUString(aReasonBuf.data(), aReasonBuf.size() - 1);
+ }
+
+ return aRet;
+}
+
+util::DateTime PDFiumSignatureImpl::getTime()
+{
+ util::DateTime aRet;
+ int nTimeLen = FPDFSignatureObj_GetTime(mpSignature, nullptr, 0);
+ if (nTimeLen <= 0)
+ {
+ return aRet;
+ }
+
+ // Example: "D:20161027100104".
+ std::vector<char> aTimeBuf(nTimeLen);
+ FPDFSignatureObj_GetTime(mpSignature, aTimeBuf.data(), aTimeBuf.size());
+ OString aM(aTimeBuf.data(), aTimeBuf.size() - 1);
+ if (aM.startsWith("D:") && aM.getLength() >= 16)
+ {
+ aRet.Year = o3tl::toInt32(aM.subView(2, 4));
+ aRet.Month = o3tl::toInt32(aM.subView(6, 2));
+ aRet.Day = o3tl::toInt32(aM.subView(8, 2));
+ aRet.Hours = o3tl::toInt32(aM.subView(10, 2));
+ aRet.Minutes = o3tl::toInt32(aM.subView(12, 2));
+ aRet.Seconds = o3tl::toInt32(aM.subView(14, 2));
+ }
+ return aRet;
+}
+
+PDFiumDocumentImpl::PDFiumDocumentImpl(FPDF_DOCUMENT pPdfDocument)
+ : mpPdfDocument(pPdfDocument)
+ , m_aFormCallbacks()
+{
+ m_aFormCallbacks.version = 1;
+ m_pFormHandle = std::make_unique<PDFiumFormHandle>(
+ FPDFDOC_InitFormFillEnvironment(pPdfDocument, &m_aFormCallbacks));
+}
+
+PDFiumDocumentImpl::~PDFiumDocumentImpl()
+{
+ m_pFormHandle.reset();
+ if (mpPdfDocument)
+ FPDF_CloseDocument(mpPdfDocument);
+}
+
+FPDF_FORMHANDLE PDFiumDocumentImpl::getFormHandlePointer() { return m_pFormHandle->getPointer(); }
+
+std::unique_ptr<PDFiumPage> PDFiumDocumentImpl::openPage(int nIndex)
+{
+ std::unique_ptr<PDFiumPage> pPDFiumPage;
+ FPDF_PAGE pPage = FPDF_LoadPage(mpPdfDocument, nIndex);
+ if (pPage)
+ {
+ pPDFiumPage = std::make_unique<PDFiumPageImpl>(pPage);
+ }
+ return pPDFiumPage;
+}
+
+std::unique_ptr<PDFiumSignature> PDFiumDocumentImpl::getSignature(int nIndex)
+{
+ std::unique_ptr<PDFiumSignature> pPDFiumSignature;
+ FPDF_SIGNATURE pSignature = FPDF_GetSignatureObject(mpPdfDocument, nIndex);
+ if (pSignature)
+ {
+ pPDFiumSignature = std::make_unique<PDFiumSignatureImpl>(pSignature);
+ }
+ return pPDFiumSignature;
+}
+
+std::vector<unsigned int> PDFiumDocumentImpl::getTrailerEnds()
+{
+ int nNumTrailers = FPDF_GetTrailerEnds(mpPdfDocument, nullptr, 0);
+ std::vector<unsigned int> aTrailerEnds(nNumTrailers);
+ FPDF_GetTrailerEnds(mpPdfDocument, aTrailerEnds.data(), aTrailerEnds.size());
+ return aTrailerEnds;
+}
+
+basegfx::B2DSize PDFiumDocumentImpl::getPageSize(int nIndex)
+{
+ basegfx::B2DSize aSize;
+ FS_SIZEF aPDFSize;
+ if (FPDF_GetPageSizeByIndexF(mpPdfDocument, nIndex, &aPDFSize))
+ {
+ aSize = basegfx::B2DSize(aPDFSize.width, aPDFSize.height);
+ }
+ return aSize;
+}
+
+int PDFiumDocumentImpl::getPageCount() { return FPDF_GetPageCount(mpPdfDocument); }
+
+int PDFiumDocumentImpl::getSignatureCount() { return FPDF_GetSignatureCount(mpPdfDocument); }
+
+int PDFiumDocumentImpl::getFileVersion()
+{
+ int nFileVersion = 0;
+ FPDF_GetFileVersion(mpPdfDocument, &nFileVersion);
+ return nFileVersion;
+}
+
+bool PDFiumDocumentImpl::saveWithVersion(SvMemoryStream& rStream, int nFileVersion)
+{
+ CompatibleWriter aWriter(rStream);
+ aWriter.version = 1;
+ aWriter.WriteBlock = &CompatibleWriterCallback;
+ if (!FPDF_SaveWithVersion(mpPdfDocument, &aWriter, 0, nFileVersion))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+int PDFiumPageImpl::getObjectCount() { return FPDFPage_CountObjects(mpPage); }
+
+std::unique_ptr<PDFiumPageObject> PDFiumPageImpl::getObject(int nIndex)
+{
+ std::unique_ptr<PDFiumPageObject> pPDFiumPageObject;
+ FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(mpPage, nIndex);
+ if (pPageObject)
+ {
+ pPDFiumPageObject = std::make_unique<PDFiumPageObjectImpl>(pPageObject);
+ }
+ return pPDFiumPageObject;
+}
+
+int PDFiumPageImpl::getAnnotationCount() { return FPDFPage_GetAnnotCount(mpPage); }
+
+int PDFiumPageImpl::getAnnotationIndex(std::unique_ptr<PDFiumAnnotation> const& rAnnotation)
+{
+ auto pAnnotation = static_cast<PDFiumAnnotationImpl*>(rAnnotation.get());
+ return FPDFPage_GetAnnotIndex(mpPage, pAnnotation->getPointer());
+}
+
+std::unique_ptr<PDFiumAnnotation> PDFiumPageImpl::getAnnotation(int nIndex)
+{
+ std::unique_ptr<PDFiumAnnotation> pPDFiumAnnotation;
+ FPDF_ANNOTATION pAnnotation = FPDFPage_GetAnnot(mpPage, nIndex);
+ if (pAnnotation)
+ {
+ pPDFiumAnnotation = std::make_unique<PDFiumAnnotationImpl>(pAnnotation);
+ }
+ return pPDFiumAnnotation;
+}
+
+std::unique_ptr<PDFiumTextPage> PDFiumPageImpl::getTextPage()
+{
+ std::unique_ptr<PDFiumTextPage> pPDFiumTextPage;
+ FPDF_TEXTPAGE pTextPage = FPDFText_LoadPage(mpPage);
+ if (pTextPage)
+ {
+ pPDFiumTextPage = std::make_unique<PDFiumTextPageImpl>(pTextPage);
+ }
+ return pPDFiumTextPage;
+}
+
+bool PDFiumPageImpl::hasLinks()
+{
+ // This could be a full iterator, but at the moment we just determine if the list is empty or
+ // not.
+ int nStartPos = 0;
+ FPDF_LINK pLinkAnnot = nullptr;
+ return FPDFLink_Enumerate(mpPage, &nStartPos, &pLinkAnnot);
+}
+
+PDFiumPageObjectImpl::PDFiumPageObjectImpl(FPDF_PAGEOBJECT pPageObject)
+ : mpPageObject(pPageObject)
+{
+}
+
+OUString PDFiumPageObjectImpl::getText(std::unique_ptr<PDFiumTextPage> const& rTextPage)
+{
+ OUString sReturnText;
+
+ auto pTextPage = static_cast<PDFiumTextPageImpl*>(rTextPage.get());
+ int nBytes = FPDFTextObj_GetText(mpPageObject, pTextPage->getPointer(), nullptr, 0);
+ assert(nBytes % 2 == 0);
+ nBytes /= 2;
+
+ std::unique_ptr<sal_Unicode[]> pText(new sal_Unicode[nBytes]);
+
+ int nActualBytes = FPDFTextObj_GetText(mpPageObject, pTextPage->getPointer(),
+ reinterpret_cast<FPDF_WCHAR*>(pText.get()), nBytes * 2);
+ assert(nActualBytes % 2 == 0);
+ nActualBytes /= 2;
+ if (nActualBytes > 1)
+ {
+#if defined OSL_BIGENDIAN
+ // The data returned by FPDFTextObj_GetText is documented to always be UTF-16LE:
+ for (int i = 0; i != nActualBytes; ++i)
+ {
+ pText[i] = OSL_SWAPWORD(pText[i]);
+ }
+#endif
+ sReturnText = OUString(pText.get());
+ }
+
+ return sReturnText;
+}
+
+PDFPageObjectType PDFiumPageObjectImpl::getType()
+{
+ return static_cast<PDFPageObjectType>(FPDFPageObj_GetType(mpPageObject));
+}
+
+int PDFiumPageObjectImpl::getFormObjectCount() { return FPDFFormObj_CountObjects(mpPageObject); }
+
+std::unique_ptr<PDFiumPageObject> PDFiumPageObjectImpl::getFormObject(int nIndex)
+{
+ std::unique_ptr<PDFiumPageObject> pPDFiumFormObject;
+ FPDF_PAGEOBJECT pFormObject = FPDFFormObj_GetObject(mpPageObject, nIndex);
+ if (pFormObject)
+ {
+ pPDFiumFormObject = std::make_unique<PDFiumPageObjectImpl>(pFormObject);
+ }
+ return pPDFiumFormObject;
+}
+
+basegfx::B2DHomMatrix PDFiumPageObjectImpl::getMatrix()
+{
+ basegfx::B2DHomMatrix aB2DMatrix;
+ FS_MATRIX matrix;
+ if (FPDFPageObj_GetMatrix(mpPageObject, &matrix))
+ aB2DMatrix = basegfx::B2DHomMatrix::abcdef(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e,
+ matrix.f);
+ return aB2DMatrix;
+}
+
+basegfx::B2DRectangle PDFiumPageObjectImpl::getBounds()
+{
+ basegfx::B2DRectangle aB2DRectangle;
+
+ float left = 0;
+ float bottom = 0;
+ float right = 0;
+ float top = 0;
+ if (FPDFPageObj_GetBounds(mpPageObject, &left, &bottom, &right, &top))
+ {
+ aB2DRectangle = basegfx::B2DRectangle(left, top, right, bottom);
+ }
+ return aB2DRectangle;
+}
+
+double PDFiumPageObjectImpl::getFontSize()
+{
+ float nSize{};
+ FPDFTextObj_GetFontSize(mpPageObject, &nSize);
+ return nSize;
+}
+
+OUString PDFiumPageObjectImpl::getFontName()
+{
+ OUString sFontName;
+ const int nFontName = 80 + 1;
+ std::unique_ptr<char[]> pFontName(new char[nFontName]); // + terminating null
+ FPDF_FONT pFontObject = FPDFTextObj_GetFont(mpPageObject);
+ int nFontNameChars = FPDFFont_GetFontName(pFontObject, pFontName.get(), nFontName);
+ if (nFontName >= nFontNameChars)
+ {
+ sFontName = OUString::createFromAscii(pFontName.get());
+ }
+ return sFontName;
+}
+
+PDFTextRenderMode PDFiumPageObjectImpl::getTextRenderMode()
+{
+ return static_cast<PDFTextRenderMode>(FPDFTextObj_GetTextRenderMode(mpPageObject));
+}
+
+Color PDFiumPageObjectImpl::getFillColor()
+{
+ Color aColor = COL_TRANSPARENT;
+ unsigned int nR, nG, nB, nA;
+ if (FPDFPageObj_GetFillColor(mpPageObject, &nR, &nG, &nB, &nA))
+ {
+ aColor = Color(ColorAlpha, nA, nR, nG, nB);
+ }
+ return aColor;
+}
+
+Color PDFiumPageObjectImpl::getStrokeColor()
+{
+ Color aColor = COL_TRANSPARENT;
+ unsigned int nR, nG, nB, nA;
+ if (FPDFPageObj_GetStrokeColor(mpPageObject, &nR, &nG, &nB, &nA))
+ {
+ aColor = Color(ColorAlpha, nA, nR, nG, nB);
+ }
+ return aColor;
+}
+
+double PDFiumPageObjectImpl::getStrokeWidth()
+{
+ float fWidth = 1;
+ FPDFPageObj_GetStrokeWidth(mpPageObject, &fWidth);
+ return fWidth;
+}
+
+int PDFiumPageObjectImpl::getPathSegmentCount() { return FPDFPath_CountSegments(mpPageObject); }
+
+std::unique_ptr<PDFiumPathSegment> PDFiumPageObjectImpl::getPathSegment(int index)
+{
+ std::unique_ptr<PDFiumPathSegment> pPDFiumPathSegment;
+ FPDF_PATHSEGMENT pPathSegment = FPDFPath_GetPathSegment(mpPageObject, index);
+ if (pPathSegment)
+ {
+ pPDFiumPathSegment = std::make_unique<PDFiumPathSegmentImpl>(pPathSegment);
+ }
+ return pPDFiumPathSegment;
+}
+
+Size PDFiumPageObjectImpl::getImageSize(PDFiumPage& rPage)
+{
+ FPDF_IMAGEOBJ_METADATA aMeta;
+ auto& rPageImpl = static_cast<PDFiumPageImpl&>(rPage);
+ FPDFImageObj_GetImageMetadata(mpPageObject, rPageImpl.getPointer(), &aMeta);
+ return Size(aMeta.width, aMeta.height);
+}
+
+std::unique_ptr<PDFiumBitmap> PDFiumPageObjectImpl::getImageBitmap()
+{
+ std::unique_ptr<PDFiumBitmap> pPDFiumBitmap;
+ FPDF_BITMAP pBitmap = FPDFImageObj_GetBitmap(mpPageObject);
+ if (pBitmap)
+ {
+ pPDFiumBitmap = std::make_unique<PDFiumBitmapImpl>(pBitmap);
+ }
+ return pPDFiumBitmap;
+}
+
+bool PDFiumPageObjectImpl::getDrawMode(PDFFillMode& rFillMode, bool& rStroke)
+{
+ auto nFillMode = static_cast<int>(rFillMode);
+ auto bStroke = static_cast<FPDF_BOOL>(rStroke);
+ bool bRet = FPDFPath_GetDrawMode(mpPageObject, &nFillMode, &bStroke);
+ rFillMode = static_cast<PDFFillMode>(nFillMode);
+ rStroke = static_cast<bool>(bStroke);
+ return bRet;
+}
+
+BitmapChecksum PDFiumPageImpl::getChecksum(int nMDPPerm)
+{
+ size_t nPageWidth = getWidth();
+ size_t nPageHeight = getHeight();
+ auto pPdfBitmap = std::make_unique<PDFiumBitmapImpl>(
+ FPDFBitmap_Create(nPageWidth, nPageHeight, /*alpha=*/1));
+ if (!pPdfBitmap)
+ {
+ return 0;
+ }
+
+ int nFlags = 0;
+ if (nMDPPerm != 3)
+ {
+ // Annotations/commenting should affect the checksum, signature verification wants this.
+ nFlags = FPDF_ANNOT;
+ }
+ FPDF_RenderPageBitmap(pPdfBitmap->getPointer(), mpPage, /*start_x=*/0, /*start_y=*/0,
+ nPageWidth, nPageHeight,
+ /*rotate=*/0, nFlags);
+ Bitmap aBitmap(Size(nPageWidth, nPageHeight), vcl::PixelFormat::N24_BPP);
+ {
+ BitmapScopedWriteAccess pWriteAccess(aBitmap);
+ const auto pPdfBuffer
+ = static_cast<ConstScanline>(FPDFBitmap_GetBuffer(pPdfBitmap->getPointer()));
+ const int nStride = FPDFBitmap_GetStride(pPdfBitmap->getPointer());
+ for (size_t nRow = 0; nRow < nPageHeight; ++nRow)
+ {
+ ConstScanline pPdfLine = pPdfBuffer + (nStride * nRow);
+ pWriteAccess->CopyScanline(nRow, pPdfLine, ScanlineFormat::N32BitTcBgra, nStride);
+ }
+ }
+ return aBitmap.GetChecksum();
+}
+
+double PDFiumPageImpl::getWidth() { return FPDF_GetPageWidth(mpPage); }
+
+double PDFiumPageImpl::getHeight() { return FPDF_GetPageHeight(mpPage); }
+
+bool PDFiumPageImpl::hasTransparency() { return FPDFPage_HasTransparency(mpPage); }
+
+PDFiumPathSegmentImpl::PDFiumPathSegmentImpl(FPDF_PATHSEGMENT pPathSegment)
+ : mpPathSegment(pPathSegment)
+{
+}
+
+basegfx::B2DPoint PDFiumPathSegmentImpl::getPoint() const
+{
+ basegfx::B2DPoint aPoint;
+ float fx, fy;
+ if (FPDFPathSegment_GetPoint(mpPathSegment, &fx, &fy))
+ aPoint = basegfx::B2DPoint(fx, fy);
+ return aPoint;
+}
+
+bool PDFiumPathSegmentImpl::isClosed() const { return FPDFPathSegment_GetClose(mpPathSegment); }
+
+PDFSegmentType PDFiumPathSegmentImpl::getType() const
+{
+ return static_cast<PDFSegmentType>(FPDFPathSegment_GetType(mpPathSegment));
+}
+
+PDFiumFormHandle::PDFiumFormHandle(FPDF_FORMHANDLE pHandle)
+ : mpHandle(pHandle)
+{
+}
+
+PDFiumFormHandle::~PDFiumFormHandle() { FPDFDOC_ExitFormFillEnvironment(mpHandle); }
+
+FPDF_FORMHANDLE PDFiumFormHandle::getPointer() { return mpHandle; }
+
+PDFiumBitmapImpl::PDFiumBitmapImpl(FPDF_BITMAP pBitmap)
+ : mpBitmap(pBitmap)
+{
+}
+
+PDFiumBitmapImpl::~PDFiumBitmapImpl()
+{
+ if (mpBitmap)
+ {
+ FPDFBitmap_Destroy(mpBitmap);
+ }
+}
+
+void PDFiumBitmapImpl::fillRect(int left, int top, int width, int height, sal_uInt32 nColor)
+{
+ FPDFBitmap_FillRect(mpBitmap, left, top, width, height, nColor);
+}
+
+void PDFiumBitmapImpl::renderPageBitmap(PDFiumDocument* pDoc, PDFiumPage* pPage, int nStartX,
+ int nStartY, int nSizeX, int nSizeY)
+{
+ auto pPageImpl = static_cast<PDFiumPageImpl*>(pPage);
+ FPDF_RenderPageBitmap(mpBitmap, pPageImpl->getPointer(), nStartX, nStartY, nSizeX, nSizeY,
+ /*rotate=*/0, /*flags=*/0);
+
+ // Render widget annotations for FormFields.
+ auto pDocImpl = static_cast<PDFiumDocumentImpl*>(pDoc);
+ FPDF_FFLDraw(pDocImpl->getFormHandlePointer(), mpBitmap, pPageImpl->getPointer(), nStartX,
+ nStartY, nSizeX, nSizeY, /*rotate=*/0, /*flags=*/0);
+}
+
+ConstScanline PDFiumBitmapImpl::getBuffer()
+{
+ return static_cast<ConstScanline>(FPDFBitmap_GetBuffer(mpBitmap));
+}
+
+int PDFiumBitmapImpl::getStride() { return FPDFBitmap_GetStride(mpBitmap); }
+
+int PDFiumBitmapImpl::getWidth() { return FPDFBitmap_GetWidth(mpBitmap); }
+
+int PDFiumBitmapImpl::getHeight() { return FPDFBitmap_GetHeight(mpBitmap); }
+
+PDFBitmapType PDFiumBitmapImpl::getFormat()
+{
+ return static_cast<PDFBitmapType>(FPDFBitmap_GetFormat(mpBitmap));
+}
+
+PDFiumAnnotationImpl::PDFiumAnnotationImpl(FPDF_ANNOTATION pAnnotation)
+ : mpAnnotation(pAnnotation)
+{
+}
+
+PDFiumAnnotationImpl::~PDFiumAnnotationImpl()
+{
+ if (mpAnnotation)
+ FPDFPage_CloseAnnot(mpAnnotation);
+}
+
+PDFAnnotationSubType PDFiumAnnotationImpl::getSubType()
+{
+ return PDFAnnotationSubType(FPDFAnnot_GetSubtype(mpAnnotation));
+}
+
+basegfx::B2DRectangle PDFiumAnnotationImpl::getRectangle()
+{
+ basegfx::B2DRectangle aB2DRectangle;
+ FS_RECTF aRect;
+ if (FPDFAnnot_GetRect(mpAnnotation, &aRect))
+ {
+ aB2DRectangle = basegfx::B2DRectangle(aRect.left, aRect.top, aRect.right, aRect.bottom);
+ }
+ return aB2DRectangle;
+}
+
+Color PDFiumAnnotationImpl::getColor()
+{
+ Color aColor = COL_TRANSPARENT;
+ unsigned int nR, nG, nB, nA;
+ if (FPDFAnnot_GetColor(mpAnnotation, FPDFANNOT_COLORTYPE_Color, &nR, &nG, &nB, &nA))
+ {
+ aColor = Color(ColorAlpha, nA, nR, nG, nB);
+ }
+ return aColor;
+}
+
+Color PDFiumAnnotationImpl::getInteriorColor()
+{
+ Color aColor = COL_TRANSPARENT;
+ unsigned int nR, nG, nB, nA;
+ if (FPDFAnnot_GetColor(mpAnnotation, FPDFANNOT_COLORTYPE_InteriorColor, &nR, &nG, &nB, &nA))
+ {
+ aColor = Color(ColorAlpha, nA, nR, nG, nB);
+ }
+ return aColor;
+}
+
+size_t PDFiumAnnotationImpl::getAttachmentPointsCount()
+{
+ return FPDFAnnot_CountAttachmentPoints(mpAnnotation);
+}
+
+std::vector<basegfx::B2DPoint> PDFiumAnnotationImpl::getAttachmentPoints(size_t nIndex)
+{
+ std::vector<basegfx::B2DPoint> aQuads;
+
+ FS_QUADPOINTSF aQuadpoints;
+ if (FPDFAnnot_GetAttachmentPoints(mpAnnotation, nIndex, &aQuadpoints))
+ {
+ aQuads.emplace_back(aQuadpoints.x1, aQuadpoints.y1);
+ aQuads.emplace_back(aQuadpoints.x2, aQuadpoints.y2);
+ aQuads.emplace_back(aQuadpoints.x3, aQuadpoints.y3);
+ aQuads.emplace_back(aQuadpoints.x4, aQuadpoints.y4);
+ }
+ return aQuads;
+}
+
+std::vector<basegfx::B2DPoint> PDFiumAnnotationImpl::getLineGeometry()
+{
+ std::vector<basegfx::B2DPoint> aLine;
+ FS_POINTF aStart;
+ FS_POINTF aEnd;
+ if (FPDFAnnot_GetLine(mpAnnotation, &aStart, &aEnd))
+ {
+ aLine.emplace_back(aStart.x, aStart.y);
+ aLine.emplace_back(aEnd.x, aEnd.y);
+ }
+ return aLine;
+}
+
+namespace
+{
+bool getBorderProperties(FPDF_ANNOTATION mpAnnotation, float& rHorizontalCornerRadius,
+ float& rVerticalCornerRadius, float& rBorderWidth)
+{
+ float fHoriRadius = 0.0f;
+ float fVertRadius = 0.0f;
+ float fWidth = 0.0f;
+
+ if (!FPDFAnnot_GetBorder(mpAnnotation, &fHoriRadius, &fVertRadius, &fWidth))
+ return false;
+
+ rHorizontalCornerRadius = fHoriRadius;
+ rVerticalCornerRadius = fVertRadius;
+ rBorderWidth = fWidth;
+ return true;
+}
+}
+
+float PDFiumAnnotationImpl::getBorderWidth()
+{
+ float fHorizontalCornerRadius;
+ float fVerticalCornerRadius;
+ float fBorderWidth;
+
+ if (!getBorderProperties(mpAnnotation, fHorizontalCornerRadius, fVerticalCornerRadius,
+ fBorderWidth))
+ return 0.0f;
+ return fBorderWidth;
+}
+
+basegfx::B2DSize PDFiumAnnotationImpl::getBorderCornerRadius()
+{
+ float fHorizontalCornerRadius;
+ float fVerticalCornerRadius;
+ float fBorderWidth;
+
+ if (!getBorderProperties(mpAnnotation, fHorizontalCornerRadius, fVerticalCornerRadius,
+ fBorderWidth))
+ return basegfx::B2DSize(0.0, 0.0);
+ return basegfx::B2DSize(fHorizontalCornerRadius, fVerticalCornerRadius);
+}
+
+bool PDFiumAnnotationImpl::hasKey(OString const& rKey)
+{
+ return FPDFAnnot_HasKey(mpAnnotation, rKey.getStr());
+}
+
+PDFObjectType PDFiumAnnotationImpl::getValueType(OString const& rKey)
+{
+ return static_cast<PDFObjectType>(FPDFAnnot_GetValueType(mpAnnotation, rKey.getStr()));
+}
+
+OUString PDFiumAnnotationImpl::getString(OString const& rKey)
+{
+ OUString rString;
+ unsigned long nSize = FPDFAnnot_GetStringValue(mpAnnotation, rKey.getStr(), nullptr, 0);
+ assert(nSize % 2 == 0);
+ nSize /= 2;
+ if (nSize > 1)
+ {
+ std::unique_ptr<sal_Unicode[]> pText(new sal_Unicode[nSize]);
+ unsigned long nStringSize = FPDFAnnot_GetStringValue(
+ mpAnnotation, rKey.getStr(), reinterpret_cast<FPDF_WCHAR*>(pText.get()), nSize * 2);
+ assert(nStringSize % 2 == 0);
+ nStringSize /= 2;
+ if (nStringSize > 0)
+ {
+#if defined OSL_BIGENDIAN
+ // The data returned by FPDFAnnot_GetStringValue is documented to always be UTF-16LE:
+ for (unsigned long i = 0; i != nStringSize; ++i)
+ {
+ pText[i] = OSL_SWAPWORD(pText[i]);
+ }
+#endif
+ rString = OUString(pText.get());
+ }
+ }
+ return rString;
+}
+
+std::vector<std::vector<basegfx::B2DPoint>> PDFiumAnnotationImpl::getInkStrokes()
+{
+ std::vector<std::vector<basegfx::B2DPoint>> aB2DPointList;
+ int nInkStrokes = FPDFAnnot_GetInkListCount(mpAnnotation);
+ for (int i = 0; i < nInkStrokes; i++)
+ {
+ std::vector<basegfx::B2DPoint> aB2DPoints;
+ int nPoints = FPDFAnnot_GetInkListPath(mpAnnotation, i, nullptr, 0);
+ if (nPoints)
+ {
+ std::vector<FS_POINTF> aPoints(nPoints);
+ if (FPDFAnnot_GetInkListPath(mpAnnotation, i, aPoints.data(), aPoints.size()))
+ {
+ for (auto const& rPoint : aPoints)
+ {
+ aB2DPoints.emplace_back(rPoint.x, rPoint.y);
+ }
+ aB2DPointList.push_back(aB2DPoints);
+ }
+ }
+ }
+ return aB2DPointList;
+}
+
+std::vector<basegfx::B2DPoint> PDFiumAnnotationImpl::getVertices()
+{
+ std::vector<basegfx::B2DPoint> aB2DPoints;
+ int nPoints = FPDFAnnot_GetVertices(mpAnnotation, nullptr, 0);
+ if (nPoints)
+ {
+ std::vector<FS_POINTF> aPoints(nPoints);
+ if (FPDFAnnot_GetVertices(mpAnnotation, aPoints.data(), aPoints.size()))
+ {
+ for (auto const& rPoint : aPoints)
+ aB2DPoints.emplace_back(rPoint.x, rPoint.y);
+ }
+ }
+ return aB2DPoints;
+}
+
+std::unique_ptr<PDFiumAnnotation> PDFiumAnnotationImpl::getLinked(OString const& rKey)
+{
+ std::unique_ptr<PDFiumAnnotation> pPDFiumAnnotation;
+ FPDF_ANNOTATION pAnnotation = FPDFAnnot_GetLinkedAnnot(mpAnnotation, rKey.getStr());
+ if (pAnnotation)
+ {
+ pPDFiumAnnotation = std::make_unique<PDFiumAnnotationImpl>(pAnnotation);
+ }
+ return pPDFiumAnnotation;
+}
+
+int PDFiumAnnotationImpl::getObjectCount() { return FPDFAnnot_GetObjectCount(mpAnnotation); }
+
+std::unique_ptr<PDFiumPageObject> PDFiumAnnotationImpl::getObject(int nIndex)
+{
+ std::unique_ptr<PDFiumPageObject> pPDFiumPageObject;
+ FPDF_PAGEOBJECT pPageObject = FPDFAnnot_GetObject(mpAnnotation, nIndex);
+ if (pPageObject)
+ {
+ pPDFiumPageObject = std::make_unique<PDFiumPageObjectImpl>(pPageObject);
+ }
+ return pPDFiumPageObject;
+}
+
+PDFiumTextPageImpl::PDFiumTextPageImpl(FPDF_TEXTPAGE pTextPage)
+ : mpTextPage(pTextPage)
+{
+}
+
+PDFiumTextPageImpl::~PDFiumTextPageImpl()
+{
+ if (mpTextPage)
+ FPDFText_ClosePage(mpTextPage);
+}
+
+int PDFiumTextPageImpl::countChars() { return FPDFText_CountChars(mpTextPage); }
+
+basegfx::B2DRectangle PDFiumTextPageImpl::getCharBox(int nIndex, double fPageHeight)
+{
+ double left = 0.0;
+ double right = 0.0;
+ double bottom = 0.0;
+ double top = 0.0;
+
+ if (FPDFText_GetCharBox(mpTextPage, nIndex, &left, &right, &bottom, &top))
+ {
+ left = convertPointToMm100(left);
+ right = convertPointToMm100(right);
+ top = fPageHeight - convertPointToMm100(top);
+ bottom = fPageHeight - convertPointToMm100(bottom);
+
+ return basegfx::B2DRectangle(left, bottom, right, top);
+ }
+
+ return basegfx::B2DRectangle();
+}
+
+unsigned int PDFiumTextPageImpl::getUnicode(int index)
+{
+ return FPDFText_GetUnicode(mpTextPage, index);
+}
+
+std::unique_ptr<PDFiumSearchHandle>
+PDFiumTextPageImpl::findStart(const OUString& rFindWhat, PDFFindFlags nFlags, sal_Int32 nStartIndex)
+{
+ FPDF_WIDESTRING pFindWhat = reinterpret_cast<FPDF_WIDESTRING>(rFindWhat.getStr());
+ return std::make_unique<vcl::pdf::PDFiumSearchHandleImpl>(
+ FPDFText_FindStart(mpTextPage, pFindWhat, static_cast<sal_uInt32>(nFlags), nStartIndex));
+}
+
+PDFiumSearchHandleImpl::PDFiumSearchHandleImpl(FPDF_SCHHANDLE pSearchHandle)
+ : mpSearchHandle(pSearchHandle)
+{
+}
+
+PDFiumSearchHandleImpl::~PDFiumSearchHandleImpl()
+{
+ if (mpSearchHandle)
+ FPDFText_FindClose(mpSearchHandle);
+}
+
+bool PDFiumSearchHandleImpl::findNext() { return FPDFText_FindNext(mpSearchHandle); }
+
+bool PDFiumSearchHandleImpl::findPrev() { return FPDFText_FindPrev(mpSearchHandle); }
+
+int PDFiumSearchHandleImpl::getSearchResultIndex()
+{
+ return FPDFText_GetSchResultIndex(mpSearchHandle);
+}
+
+int PDFiumSearchHandleImpl::getSearchCount() { return FPDFText_GetSchCount(mpSearchHandle); }
+
+std::shared_ptr<PDFium>& PDFiumLibrary::get()
+{
+ static std::shared_ptr<PDFium> pInstance = std::make_shared<PDFiumImpl>();
+ return pInstance;
+}
+
+} // end vcl::pdf
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/pdf/PDFiumTools.cxx b/vcl/source/pdf/PDFiumTools.cxx
new file mode 100644
index 000000000..28fa53b83
--- /dev/null
+++ b/vcl/source/pdf/PDFiumTools.cxx
@@ -0,0 +1,73 @@
+/* -*- 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/.
+ *
+ */
+
+#include <vcl/filter/PDFiumLibrary.hxx>
+
+namespace vcl::pdf
+{
+OUString convertPdfDateToISO8601(OUString const& rInput)
+{
+ if (rInput.getLength() < 6)
+ return {};
+
+ std::u16string_view prefix = rInput.subView(0, 2);
+ if (prefix != u"D:")
+ return {};
+
+ std::u16string_view sYear = rInput.subView(2, 4);
+
+ std::u16string_view sMonth(u"01");
+ if (rInput.getLength() >= 8)
+ sMonth = rInput.subView(6, 2);
+
+ std::u16string_view sDay(u"01");
+ if (rInput.getLength() >= 10)
+ sDay = rInput.subView(8, 2);
+
+ std::u16string_view sHours(u"00");
+ if (rInput.getLength() >= 12)
+ sHours = rInput.subView(10, 2);
+
+ std::u16string_view sMinutes(u"00");
+ if (rInput.getLength() >= 14)
+ sMinutes = rInput.subView(12, 2);
+
+ std::u16string_view sSeconds(u"00");
+ if (rInput.getLength() >= 16)
+ sSeconds = rInput.subView(14, 2);
+
+ OUString sTimeZoneMark("Z");
+ if (rInput.getLength() >= 17)
+ sTimeZoneMark = rInput.subView(16, 1);
+
+ std::u16string_view sTimeZoneHours(u"00");
+ std::u16string_view sTimeZoneMinutes(u"00");
+ if ((sTimeZoneMark == "+" || sTimeZoneMark == "-") && rInput.getLength() >= 22)
+ {
+ std::u16string_view sTimeZoneSeparator = rInput.subView(19, 1);
+ if (sTimeZoneSeparator == u"'")
+ {
+ sTimeZoneHours = rInput.subView(17, 2);
+ sTimeZoneMinutes = rInput.subView(20, 2);
+ }
+ }
+
+ OUString sTimeZoneString;
+ if (sTimeZoneMark == "+" || sTimeZoneString == "-")
+ sTimeZoneString = sTimeZoneMark + sTimeZoneHours + ":" + sTimeZoneMinutes;
+ else if (sTimeZoneMark == "Z")
+ sTimeZoneString = sTimeZoneMark;
+
+ return OUString::Concat(sYear) + "-" + sMonth + "-" + sDay + "T" + sHours + ":" + sMinutes + ":"
+ + sSeconds + sTimeZoneString;
+}
+} // end vcl::pdf
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/pdf/PdfConfig.cxx b/vcl/source/pdf/PdfConfig.cxx
new file mode 100644
index 000000000..0e6a04360
--- /dev/null
+++ b/vcl/source/pdf/PdfConfig.cxx
@@ -0,0 +1,36 @@
+/* -*- 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/.
+ *
+ */
+
+#include <pdf/PdfConfig.hxx>
+#include <cstdlib>
+
+#include <vcl/svapp.hxx>
+#include <vcl/outdev.hxx>
+
+namespace vcl::pdf
+{
+/// Get the default PDF rendering resolution in DPI.
+double getDefaultPdfResolutionDpi()
+{
+ // If an overriding default is set, use it.
+ const char* envar = ::getenv("PDFIMPORT_RESOLUTION_DPI");
+ if (envar)
+ {
+ const double dpi = atof(envar);
+ if (dpi > 0)
+ return dpi;
+ }
+
+ // Fallback to a sensible default.
+ return Application::GetDefaultDevice()->GetDPIX();
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/pdf/ResourceDict.cxx b/vcl/source/pdf/ResourceDict.cxx
new file mode 100644
index 000000000..3490da30b
--- /dev/null
+++ b/vcl/source/pdf/ResourceDict.cxx
@@ -0,0 +1,60 @@
+/* -*- 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/.
+ *
+ */
+
+#include <pdf/ResourceDict.hxx>
+
+namespace vcl::pdf
+{
+namespace
+{
+void appendResourceMap(OStringBuffer& rBuf, const char* pPrefix,
+ std::map<OString, sal_Int32> const& rList)
+{
+ if (rList.empty())
+ return;
+ rBuf.append('/');
+ rBuf.append(pPrefix);
+ rBuf.append("<<");
+ int ni = 0;
+ for (auto const& item : rList)
+ {
+ if (!item.first.isEmpty() && item.second > 0)
+ {
+ rBuf.append('/');
+ rBuf.append(item.first);
+ rBuf.append(' ');
+ rBuf.append(item.second);
+ rBuf.append(" 0 R");
+ if (((++ni) & 7) == 0)
+ rBuf.append('\n');
+ }
+ }
+ rBuf.append(">>\n");
+}
+}
+
+void ResourceDict::append(OStringBuffer& rBuf, sal_Int32 nFontDictObject)
+{
+ rBuf.append("<</Font ");
+ rBuf.append(nFontDictObject);
+ rBuf.append(" 0 R\n");
+ appendResourceMap(rBuf, "XObject", m_aXObjects);
+ appendResourceMap(rBuf, "ExtGState", m_aExtGStates);
+ appendResourceMap(rBuf, "Shading", m_aShadings);
+ appendResourceMap(rBuf, "Pattern", m_aPatterns);
+ rBuf.append("/ProcSet[/PDF/Text");
+ if (!m_aXObjects.empty())
+ rBuf.append("/ImageC/ImageI/ImageB");
+ rBuf.append("]\n>>\n");
+}
+
+} // end vcl::pdf
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/pdf/XmpMetadata.cxx b/vcl/source/pdf/XmpMetadata.cxx
new file mode 100644
index 000000000..156e849f4
--- /dev/null
+++ b/vcl/source/pdf/XmpMetadata.cxx
@@ -0,0 +1,190 @@
+/* -*- 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/.
+ *
+ */
+
+#include <pdf/XmpMetadata.hxx>
+#include <tools/XmlWriter.hxx>
+
+namespace vcl::pdf
+{
+namespace
+{
+constexpr const char* constPadding = " "
+ " "
+ " "
+ " "
+ " "
+ "\n";
+}
+
+XmpMetadata::XmpMetadata()
+ : mbWritten(false)
+ , mnPDF_A(0)
+ , mbPDF_UA(false)
+{
+}
+
+void XmpMetadata::write()
+{
+ mpMemoryStream = std::make_unique<SvMemoryStream>(4096 /*Initial*/, 64 /*Resize*/);
+
+ // Header
+ mpMemoryStream->WriteOString(
+ OStringLiteral(u8"<?xpacket begin=\"\uFEFF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n"));
+
+ {
+ tools::XmlWriter aXmlWriter(mpMemoryStream.get());
+ aXmlWriter.startDocument(2, false);
+ aXmlWriter.startElement("x", "xmpmeta", "adobe:ns:meta/");
+ aXmlWriter.startElement("rdf", "RDF", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+
+ // PDF/A part ( ISO 19005-1:2005 - 6.7.11 )
+ if (mnPDF_A > 0)
+ {
+ OString sPdfVersion = OString::number(mnPDF_A);
+ OString sPdfConformance = (mnPDF_A == 1) ? "A" : "B";
+
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", OString(""));
+ aXmlWriter.attribute("xmlns:pdfaid", OString("http://www.aiim.org/pdfa/ns/id/"));
+
+ aXmlWriter.startElement("pdfaid:part");
+ aXmlWriter.content(sPdfVersion);
+ aXmlWriter.endElement();
+
+ aXmlWriter.startElement("pdfaid:conformance");
+ aXmlWriter.content(sPdfConformance);
+ aXmlWriter.endElement();
+
+ aXmlWriter.endElement();
+ }
+
+ // Dublin Core properties
+ if (!msTitle.isEmpty() || !msAuthor.isEmpty() || !msSubject.isEmpty())
+ {
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", OString(""));
+ aXmlWriter.attribute("xmlns:dc", OString("http://purl.org/dc/elements/1.1/"));
+ if (!msTitle.isEmpty())
+ {
+ // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01)
+ aXmlWriter.startElement("dc:title");
+ aXmlWriter.startElement("rdf:Alt");
+ aXmlWriter.startElement("rdf:li");
+ aXmlWriter.attribute("xml:lang", OString("x-default"));
+ aXmlWriter.content(msTitle);
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ }
+ if (!msAuthor.isEmpty())
+ {
+ aXmlWriter.startElement("dc:creator");
+ aXmlWriter.startElement("rdf:Seq");
+ aXmlWriter.startElement("rdf:li");
+ aXmlWriter.content(msAuthor);
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ }
+ if (!msSubject.isEmpty())
+ {
+ aXmlWriter.startElement("dc:description");
+ aXmlWriter.startElement("rdf:Alt");
+ aXmlWriter.startElement("rdf:li");
+ aXmlWriter.attribute("xml:lang", OString("x-default"));
+ aXmlWriter.content(msSubject);
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ }
+ aXmlWriter.endElement();
+ }
+
+ // PDF/UA
+ if (mbPDF_UA)
+ {
+ OString sPdfUaVersion = OString::number(1);
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", OString(""));
+ aXmlWriter.attribute("xmlns:pdfuaid", OString("http://www.aiim.org/pdfua/ns/id/"));
+
+ aXmlWriter.startElement("pdfuaid:part");
+ aXmlWriter.content(sPdfUaVersion);
+ aXmlWriter.endElement();
+
+ aXmlWriter.endElement();
+ }
+
+ // PDF properties
+ if (!msProducer.isEmpty() || !msKeywords.isEmpty())
+ {
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", OString(""));
+ aXmlWriter.attribute("xmlns:pdf", OString("http://ns.adobe.com/pdf/1.3/"));
+ if (!msProducer.isEmpty())
+ {
+ aXmlWriter.startElement("pdf:Producer");
+ aXmlWriter.content(msProducer);
+ aXmlWriter.endElement();
+ }
+ if (!msKeywords.isEmpty())
+ {
+ aXmlWriter.startElement("pdf:Keywords");
+ aXmlWriter.content(msKeywords);
+ aXmlWriter.endElement();
+ }
+ aXmlWriter.endElement();
+ }
+
+ // XMP Basic schema
+ aXmlWriter.startElement("rdf:Description");
+ aXmlWriter.attribute("rdf:about", OString(""));
+ aXmlWriter.attribute("xmlns:xmp", OString("http://ns.adobe.com/xap/1.0/"));
+ if (!m_sCreatorTool.isEmpty())
+ {
+ aXmlWriter.startElement("xmp:CreatorTool");
+ aXmlWriter.content(m_sCreatorTool);
+ aXmlWriter.endElement();
+ }
+ aXmlWriter.startElement("xmp:CreateDate");
+ aXmlWriter.content(m_sCreateDate);
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+ aXmlWriter.endDocument();
+ }
+
+ // add padding (needed so the metadata can be changed in-place"
+ for (sal_Int32 nSpaces = 1; nSpaces <= 21; nSpaces++)
+ mpMemoryStream->WriteOString(constPadding);
+
+ mpMemoryStream->WriteOString("<?xpacket end=\"w\"?>\n");
+ mbWritten = true;
+}
+
+sal_uInt64 XmpMetadata::getSize()
+{
+ if (!mbWritten)
+ write();
+ return mpMemoryStream->GetSize();
+}
+
+const void* XmpMetadata::getData()
+{
+ if (!mbWritten)
+ write();
+ return mpMemoryStream->GetData();
+}
+
+} // end vcl::pdf
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/printer/Options.cxx b/vcl/source/printer/Options.cxx
new file mode 100644
index 000000000..bc695622f
--- /dev/null
+++ b/vcl/source/printer/Options.cxx
@@ -0,0 +1,110 @@
+/* -*- 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 <comphelper/processfactory.hxx>
+
+#include <vcl/printer/Options.hxx>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+
+namespace vcl::printer
+{
+void Options::ReadFromConfig(bool i_bFile)
+{
+ bool bSuccess = false;
+ // save old state in case something goes wrong
+ Options aOldValues(*this);
+
+ // get the configuration service
+ css::uno::Reference<css::lang::XMultiServiceFactory> xConfigProvider;
+ css::uno::Reference<css::container::XNameAccess> xConfigAccess;
+ try
+ {
+ // get service provider
+ css::uno::Reference<css::uno::XComponentContext> xContext(
+ comphelper::getProcessComponentContext());
+ // create configuration hierarchical access name
+ try
+ {
+ xConfigProvider = css::configuration::theDefaultProvider::get(xContext);
+
+ css::beans::PropertyValue aVal;
+ aVal.Name = "nodepath";
+ if (i_bFile)
+ aVal.Value <<= OUString("/org.openoffice.Office.Common/Print/Option/File");
+ else
+ aVal.Value <<= OUString("/org.openoffice.Office.Common/Print/Option/Printer");
+ xConfigAccess.set(
+ xConfigProvider->createInstanceWithArguments(
+ "com.sun.star.configuration.ConfigurationAccess", { css::uno::Any(aVal) }),
+ css::uno::UNO_QUERY);
+ if (xConfigAccess.is())
+ {
+ css::uno::Reference<css::beans::XPropertySet> xSet(xConfigAccess,
+ css::uno::UNO_QUERY);
+ if (xSet.is())
+ {
+ sal_Int32 nValue = 0;
+ bool bValue = false;
+ if (xSet->getPropertyValue("ReduceTransparency") >>= bValue)
+ SetReduceTransparency(bValue);
+ if (xSet->getPropertyValue("ReducedTransparencyMode") >>= nValue)
+ SetReducedTransparencyMode(static_cast<TransparencyMode>(nValue));
+ if (xSet->getPropertyValue("ReduceGradients") >>= bValue)
+ SetReduceGradients(bValue);
+ if (xSet->getPropertyValue("ReducedGradientMode") >>= nValue)
+ SetReducedGradientMode(static_cast<GradientMode>(nValue));
+ if (xSet->getPropertyValue("ReducedGradientStepCount") >>= nValue)
+ SetReducedGradientStepCount(static_cast<sal_uInt16>(nValue));
+ if (xSet->getPropertyValue("ReduceBitmaps") >>= bValue)
+ SetReduceBitmaps(bValue);
+ if (xSet->getPropertyValue("ReducedBitmapMode") >>= nValue)
+ SetReducedBitmapMode(static_cast<BitmapMode>(nValue));
+ if (xSet->getPropertyValue("ReducedBitmapResolution") >>= nValue)
+ SetReducedBitmapResolution(static_cast<sal_uInt16>(nValue));
+ if (xSet->getPropertyValue("ReducedBitmapIncludesTransparency") >>= bValue)
+ SetReducedBitmapIncludesTransparency(bValue);
+ if (xSet->getPropertyValue("ConvertToGreyscales") >>= bValue)
+ SetConvertToGreyscales(bValue);
+ if (xSet->getPropertyValue("PDFAsStandardPrintJobFormat") >>= bValue)
+ SetPDFAsStandardPrintJobFormat(bValue);
+
+ bSuccess = true;
+ }
+ }
+ }
+ catch (const css::uno::Exception&)
+ {
+ }
+ }
+ catch (const css::lang::WrappedTargetException&)
+ {
+ }
+
+ if (!bSuccess)
+ *this = aOldValues;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/printer/QueueInfo.cxx b/vcl/source/printer/QueueInfo.cxx
new file mode 100644
index 000000000..8ec787cbd
--- /dev/null
+++ b/vcl/source/printer/QueueInfo.cxx
@@ -0,0 +1,35 @@
+/* -*- 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 <vcl/QueueInfo.hxx>
+
+QueueInfo::QueueInfo()
+{
+ mnStatus = PrintQueueFlags::NONE;
+ mnJobs = 0;
+}
+
+const OUString& QueueInfo::GetPrinterName() const { return maPrinterName; }
+const OUString& QueueInfo::GetDriver() const { return maDriver; }
+const OUString& QueueInfo::GetLocation() const { return maLocation; }
+const OUString& QueueInfo::GetComment() const { return maComment; }
+PrintQueueFlags QueueInfo::GetStatus() const { return mnStatus; }
+sal_uInt32 QueueInfo::GetJobs() const { return mnJobs; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/rendercontext/drawmode.cxx b/vcl/source/rendercontext/drawmode.cxx
new file mode 100644
index 000000000..b1b85d33f
--- /dev/null
+++ b/vcl/source/rendercontext/drawmode.cxx
@@ -0,0 +1,262 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <vcl/BitmapMonochromeFilter.hxx>
+#include <vcl/bitmap/BitmapTypes.hxx>
+#include <vcl/rendercontext/DrawModeFlags.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+
+#include <drawmode.hxx>
+
+namespace vcl::drawmode
+{
+Color GetLineColor(Color const& rColor, DrawModeFlags nDrawMode,
+ StyleSettings const& rStyleSettings)
+{
+ Color aColor(rColor);
+
+ if (nDrawMode
+ & (DrawModeFlags::BlackLine | DrawModeFlags::WhiteLine | DrawModeFlags::GrayLine
+ | DrawModeFlags::SettingsLine))
+ {
+ if (!aColor.IsTransparent())
+ {
+ if (nDrawMode & DrawModeFlags::BlackLine)
+ {
+ aColor = COL_BLACK;
+ }
+ else if (nDrawMode & DrawModeFlags::WhiteLine)
+ {
+ aColor = COL_WHITE;
+ }
+ else if (nDrawMode & DrawModeFlags::GrayLine)
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color(cLum, cLum, cLum);
+ }
+ else if (nDrawMode & DrawModeFlags::SettingsLine)
+ {
+ aColor = rStyleSettings.GetFontColor();
+ }
+ }
+ }
+
+ return aColor;
+}
+
+Color GetFillColor(Color const& rColor, DrawModeFlags nDrawMode,
+ StyleSettings const& rStyleSettings)
+{
+ Color aColor(rColor);
+
+ if (nDrawMode
+ & (DrawModeFlags::BlackFill | DrawModeFlags::WhiteFill | DrawModeFlags::GrayFill
+ | DrawModeFlags::NoFill | DrawModeFlags::SettingsFill))
+ {
+ if (!aColor.IsTransparent())
+ {
+ if (nDrawMode & DrawModeFlags::BlackFill)
+ {
+ aColor = COL_BLACK;
+ }
+ else if (nDrawMode & DrawModeFlags::WhiteFill)
+ {
+ aColor = COL_WHITE;
+ }
+ else if (nDrawMode & DrawModeFlags::GrayFill)
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color(cLum, cLum, cLum);
+ }
+ else if (nDrawMode & DrawModeFlags::NoFill)
+ {
+ aColor = COL_TRANSPARENT;
+ }
+ else if (nDrawMode & DrawModeFlags::SettingsFill)
+ {
+ aColor = rStyleSettings.GetWindowColor();
+ }
+ }
+ }
+
+ return aColor;
+}
+
+Color GetHatchColor(Color const& rColor, DrawModeFlags nDrawMode,
+ StyleSettings const& rStyleSettings)
+{
+ Color aColor(rColor);
+
+ if (nDrawMode & DrawModeFlags::BlackLine)
+ {
+ aColor = COL_BLACK;
+ }
+ else if (nDrawMode & DrawModeFlags::WhiteLine)
+ {
+ aColor = COL_WHITE;
+ }
+ else if (nDrawMode & DrawModeFlags::GrayLine)
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color(cLum, cLum, cLum);
+ }
+ else if (nDrawMode & DrawModeFlags::SettingsLine)
+ {
+ aColor = rStyleSettings.GetFontColor();
+ }
+
+ return aColor;
+}
+
+Color GetTextColor(Color const& rColor, DrawModeFlags nDrawMode,
+ StyleSettings const& rStyleSettings)
+{
+ Color aColor(rColor);
+
+ if (nDrawMode
+ & (DrawModeFlags::BlackText | DrawModeFlags::WhiteText | DrawModeFlags::GrayText
+ | DrawModeFlags::SettingsText))
+ {
+ if (nDrawMode & DrawModeFlags::BlackText)
+ {
+ aColor = COL_BLACK;
+ }
+ else if (nDrawMode & DrawModeFlags::WhiteText)
+ {
+ aColor = COL_WHITE;
+ }
+ else if (nDrawMode & DrawModeFlags::GrayText)
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color(cLum, cLum, cLum);
+ }
+ else if (nDrawMode & DrawModeFlags::SettingsText)
+ {
+ aColor = rStyleSettings.GetFontColor();
+ }
+ }
+
+ return aColor;
+}
+
+vcl::Font GetFont(vcl::Font const& rFont, DrawModeFlags nDrawMode,
+ StyleSettings const& rStyleSettings)
+{
+ vcl::Font aFont(rFont);
+
+ if (nDrawMode
+ & (DrawModeFlags::BlackText | DrawModeFlags::WhiteText | DrawModeFlags::GrayText
+ | DrawModeFlags::SettingsText | DrawModeFlags::BlackFill | DrawModeFlags::WhiteFill
+ | DrawModeFlags::GrayFill | DrawModeFlags::NoFill | DrawModeFlags::SettingsFill))
+ {
+ Color aTextColor(aFont.GetColor());
+
+ if (nDrawMode & DrawModeFlags::BlackText)
+ {
+ aTextColor = COL_BLACK;
+ }
+ else if (nDrawMode & DrawModeFlags::WhiteText)
+ {
+ aTextColor = COL_WHITE;
+ }
+ else if (nDrawMode & DrawModeFlags::GrayText)
+ {
+ const sal_uInt8 cLum = aTextColor.GetLuminance();
+ aTextColor = Color(cLum, cLum, cLum);
+ }
+ else if (nDrawMode & DrawModeFlags::SettingsText)
+ {
+ aTextColor = rStyleSettings.GetFontColor();
+ }
+
+ aFont.SetColor(aTextColor);
+
+ if (!aFont.IsTransparent())
+ {
+ Color aTextFillColor(aFont.GetFillColor());
+
+ if (nDrawMode & DrawModeFlags::BlackFill)
+ {
+ aTextFillColor = COL_BLACK;
+ }
+ else if (nDrawMode & DrawModeFlags::WhiteFill)
+ {
+ aTextFillColor = COL_WHITE;
+ }
+ else if (nDrawMode & DrawModeFlags::GrayFill)
+ {
+ const sal_uInt8 cLum = aTextFillColor.GetLuminance();
+ aTextFillColor = Color(cLum, cLum, cLum);
+ }
+ else if (nDrawMode & DrawModeFlags::SettingsFill)
+ {
+ aTextFillColor = rStyleSettings.GetWindowColor();
+ }
+ else if (nDrawMode & DrawModeFlags::NoFill)
+ {
+ aTextFillColor = COL_TRANSPARENT;
+ }
+
+ aFont.SetFillColor(aTextFillColor);
+ }
+ }
+
+ return aFont;
+}
+
+BitmapEx GetBitmapEx(BitmapEx const& rBitmapEx, DrawModeFlags nDrawMode)
+{
+ BitmapEx aBmpEx(rBitmapEx);
+
+ if (nDrawMode & (DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap))
+ {
+ Bitmap aColorBmp(aBmpEx.GetSizePixel(), vcl::PixelFormat::N1_BPP);
+ sal_uInt8 cCmpVal;
+
+ if (nDrawMode & DrawModeFlags::BlackBitmap)
+ cCmpVal = 0;
+ else
+ cCmpVal = 255;
+
+ aColorBmp.Erase(Color(cCmpVal, cCmpVal, cCmpVal));
+
+ if (aBmpEx.IsAlpha())
+ {
+ // Create one-bit mask out of alpha channel, by thresholding it at alpha=0.5. As
+ // DRAWMODE_BLACK/WHITEBITMAP requires monochrome output, having alpha-induced
+ // grey levels is not acceptable
+ BitmapEx aMaskEx(aBmpEx.GetAlpha().GetBitmap());
+ BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(129));
+ aBmpEx = BitmapEx(aColorBmp, aMaskEx.GetBitmap());
+ }
+ else
+ {
+ aBmpEx = BitmapEx(aColorBmp, aBmpEx.GetAlpha());
+ }
+ }
+
+ if (nDrawMode & DrawModeFlags::GrayBitmap && !aBmpEx.IsEmpty())
+ aBmpEx.Convert(BmpConversion::N8BitGreys);
+
+ return aBmpEx;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/salmain/salmain.cxx b/vcl/source/salmain/salmain.cxx
new file mode 100644
index 000000000..721ae910f
--- /dev/null
+++ b/vcl/source/salmain/salmain.cxx
@@ -0,0 +1,35 @@
+/* -*- 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_features.h>
+
+#include <sal/main.h>
+#include <tools/extendapplicationenvironment.hxx>
+#include <vcl/vclmain.hxx>
+#include <vcl/svmain.hxx>
+
+SAL_IMPLEMENT_MAIN()
+{
+ tools::extendApplicationEnvironment();
+ vclmain::createApplication();
+ return SVMain();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/text/ImplLayoutArgs.cxx b/vcl/source/text/ImplLayoutArgs.cxx
new file mode 100644
index 000000000..dbdc6e18f
--- /dev/null
+++ b/vcl/source/text/ImplLayoutArgs.cxx
@@ -0,0 +1,343 @@
+/* -*- 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 <ImplLayoutArgs.hxx>
+
+#include <unicode/ubidi.h>
+#include <unicode/uchar.h>
+
+#include <algorithm>
+#include <memory>
+
+namespace vcl::text
+{
+ImplLayoutArgs::ImplLayoutArgs(const OUString& rStr, int nMinCharPos, int nEndCharPos,
+ SalLayoutFlags nFlags, const LanguageTag& rLanguageTag,
+ vcl::text::TextLayoutCache const* const pLayoutCache)
+ : maLanguageTag(rLanguageTag)
+ , mnFlags(nFlags)
+ , mrStr(rStr)
+ , mnMinCharPos(nMinCharPos)
+ , mnEndCharPos(nEndCharPos)
+ , m_pTextLayoutCache(pLayoutCache)
+ , mpDXArray(nullptr)
+ , mpAltNaturalDXArray(nullptr)
+ , mnLayoutWidth(0)
+ , mnOrientation(0)
+{
+ if (mnFlags & SalLayoutFlags::BiDiStrong)
+ {
+ // handle strong BiDi mode
+
+ // do not bother to BiDi analyze strong LTR/RTL
+ // TODO: can we assume these strings do not have unicode control chars?
+ // if not remove the control characters from the runs
+ bool bRTL(mnFlags & SalLayoutFlags::BiDiRtl);
+ AddRun(mnMinCharPos, mnEndCharPos, bRTL);
+ }
+ else
+ {
+ // handle weak BiDi mode
+ UBiDiLevel nLevel = (mnFlags & SalLayoutFlags::BiDiRtl) ? 1 : 0;
+
+ // prepare substring for BiDi analysis
+ // TODO: reuse allocated pParaBidi
+ UErrorCode rcI18n = U_ZERO_ERROR;
+ const int nLength = mnEndCharPos - mnMinCharPos;
+ UBiDi* pParaBidi = ubidi_openSized(nLength, 0, &rcI18n);
+ if (!pParaBidi)
+ return;
+ ubidi_setPara(pParaBidi, reinterpret_cast<const UChar*>(mrStr.getStr()) + mnMinCharPos,
+ nLength, nLevel, nullptr, &rcI18n);
+
+ // run BiDi algorithm
+ const int nRunCount = ubidi_countRuns(pParaBidi, &rcI18n);
+ for (int i = 0; i < nRunCount; ++i)
+ {
+ int32_t nMinPos, nRunLength;
+ const UBiDiDirection nDir = ubidi_getVisualRun(pParaBidi, i, &nMinPos, &nRunLength);
+ const int nPos0 = nMinPos + mnMinCharPos;
+ const int nPos1 = nPos0 + nRunLength;
+
+ const bool bRTL = (nDir == UBIDI_RTL);
+ AddRun(nPos0, nPos1, bRTL);
+ }
+
+ // cleanup BiDi engine
+ ubidi_close(pParaBidi);
+ }
+
+ // prepare calls to GetNextPos/GetNextRun
+ maRuns.ResetPos();
+}
+
+void ImplLayoutArgs::SetLayoutWidth(DeviceCoordinate nWidth) { mnLayoutWidth = nWidth; }
+
+void ImplLayoutArgs::SetDXArray(DeviceCoordinate const* pDXArray) { mpDXArray = pDXArray; }
+
+void ImplLayoutArgs::SetAltNaturalDXArray(double const* pDXArray)
+{
+ mpAltNaturalDXArray = pDXArray;
+}
+
+void ImplLayoutArgs::SetOrientation(Degree10 nOrientation) { mnOrientation = nOrientation; }
+
+void ImplLayoutArgs::ResetPos() { maRuns.ResetPos(); }
+
+bool ImplLayoutArgs::GetNextPos(int* nCharPos, bool* bRTL)
+{
+ return maRuns.GetNextPos(nCharPos, bRTL);
+}
+
+void ImplLayoutArgs::AddFallbackRun(int nMinRunPos, int nEndRunPos, bool bRTL)
+{
+ maFallbackRuns.AddRun(nMinRunPos, nEndRunPos, bRTL);
+}
+
+bool ImplLayoutArgs::HasFallbackRun() const { return !maFallbackRuns.IsEmpty(); }
+
+static bool IsControlChar(sal_UCS4 cChar)
+{
+ // C0 control characters
+ if ((0x0001 <= cChar) && (cChar <= 0x001F))
+ return true;
+ // formatting characters
+ if ((0x200E <= cChar) && (cChar <= 0x200F))
+ return true;
+ if ((0x2028 <= cChar) && (cChar <= 0x202E))
+ return true;
+ // deprecated formatting characters
+ if ((0x206A <= cChar) && (cChar <= 0x206F))
+ return true;
+ if (0x2060 == cChar)
+ return true;
+ // byte order markers and invalid unicode
+ if ((cChar == 0xFEFF) || (cChar == 0xFFFE) || (cChar == 0xFFFF))
+ return true;
+ // drop null character too, broken documents may contain it (ofz34898-1.doc)
+ if (cChar == 0)
+ return true;
+ return false;
+}
+
+// add a run after splitting it up to get rid of control chars
+void ImplLayoutArgs::AddRun(int nCharPos0, int nCharPos1, bool bRTL)
+{
+ SAL_WARN_IF(nCharPos0 > nCharPos1, "vcl", "ImplLayoutArgs::AddRun() nCharPos0>=nCharPos1");
+
+ // remove control characters from runs by splitting them up
+ if (!bRTL)
+ {
+ for (int i = nCharPos0; i < nCharPos1; ++i)
+ if (IsControlChar(mrStr[i]))
+ {
+ // add run until control char
+ maRuns.AddRun(nCharPos0, i, bRTL);
+ nCharPos0 = i + 1;
+ }
+ }
+ else
+ {
+ for (int i = nCharPos1; --i >= nCharPos0;)
+ if (IsControlChar(mrStr[i]))
+ {
+ // add run until control char
+ maRuns.AddRun(i + 1, nCharPos1, bRTL);
+ nCharPos1 = i;
+ }
+ }
+
+ // add remainder of run
+ maRuns.AddRun(nCharPos0, nCharPos1, bRTL);
+}
+
+bool ImplLayoutArgs::PrepareFallback(const SalLayoutGlyphsImpl* pGlyphsImpl)
+{
+ // Generate runs with pre-calculated glyph items instead maFallbackRuns.
+ if (pGlyphsImpl != nullptr)
+ {
+ maRuns.Clear();
+ maFallbackRuns.Clear();
+
+ for (auto const& aGlyphItem : *pGlyphsImpl)
+ {
+ for (int i = aGlyphItem.charPos(); i < aGlyphItem.charPos() + aGlyphItem.charCount();
+ ++i)
+ maRuns.AddPos(i, aGlyphItem.IsRTLGlyph());
+ }
+
+ return !maRuns.IsEmpty();
+ }
+
+ // short circuit if no fallback is needed
+ if (maFallbackRuns.IsEmpty())
+ {
+ maRuns.Clear();
+ return false;
+ }
+
+ // convert the fallback requests to layout requests
+ bool bRTL;
+ int nMin, nEnd;
+
+ // get the individual fallback requests
+ std::vector<int> aPosVector;
+ aPosVector.reserve(mrStr.getLength());
+ maFallbackRuns.ResetPos();
+ for (; maFallbackRuns.GetRun(&nMin, &nEnd, &bRTL); maFallbackRuns.NextRun())
+ for (int i = nMin; i < nEnd; ++i)
+ aPosVector.push_back(i);
+ maFallbackRuns.Clear();
+
+ // sort the individual fallback requests
+ std::sort(aPosVector.begin(), aPosVector.end());
+
+ // adjust fallback runs to have the same order and limits of the original runs
+ ImplLayoutRuns aNewRuns;
+ maRuns.ResetPos();
+ for (; maRuns.GetRun(&nMin, &nEnd, &bRTL); maRuns.NextRun())
+ {
+ if (!bRTL)
+ {
+ auto it = std::lower_bound(aPosVector.begin(), aPosVector.end(), nMin);
+ for (; (it != aPosVector.end()) && (*it < nEnd); ++it)
+ aNewRuns.AddPos(*it, bRTL);
+ }
+ else
+ {
+ auto it = std::upper_bound(aPosVector.begin(), aPosVector.end(), nEnd);
+ while ((it != aPosVector.begin()) && (*--it >= nMin))
+ aNewRuns.AddPos(*it, bRTL);
+ }
+ }
+
+ maRuns = aNewRuns; // TODO: use vector<>::swap()
+ maRuns.ResetPos();
+ return true;
+}
+
+bool ImplLayoutArgs::GetNextRun(int* nMinRunPos, int* nEndRunPos, bool* bRTL)
+{
+ bool bValid = maRuns.GetRun(nMinRunPos, nEndRunPos, bRTL);
+ maRuns.NextRun();
+ return bValid;
+}
+}
+
+std::ostream& operator<<(std::ostream& s, vcl::text::ImplLayoutArgs const& rArgs)
+{
+#ifndef SAL_LOG_INFO
+ (void)rArgs;
+#else
+ s << "ImplLayoutArgs{";
+
+ s << "Flags=";
+ if (rArgs.mnFlags == SalLayoutFlags::NONE)
+ s << 0;
+ else
+ {
+ bool need_or = false;
+ s << "{";
+#define TEST(x) \
+ if (rArgs.mnFlags & SalLayoutFlags::x) \
+ { \
+ if (need_or) \
+ s << "|"; \
+ s << #x; \
+ need_or = true; \
+ }
+ TEST(BiDiRtl);
+ TEST(BiDiStrong);
+ TEST(RightAlign);
+ TEST(DisableKerning);
+ TEST(KerningAsian);
+ TEST(Vertical);
+ TEST(KashidaJustification);
+ TEST(ForFallback);
+#undef TEST
+ s << "}";
+ }
+
+ const int nLength = rArgs.mrStr.getLength();
+
+ s << ",Length=" << nLength;
+ s << ",MinCharPos=" << rArgs.mnMinCharPos;
+ s << ",EndCharPos=" << rArgs.mnEndCharPos;
+
+ s << ",Str=\"";
+ int lim = nLength;
+ if (lim > 10)
+ lim = 7;
+ for (int i = 0; i < lim; i++)
+ {
+ if (rArgs.mrStr[i] == '\n')
+ s << "\\n";
+ else if (rArgs.mrStr[i] < ' ' || (rArgs.mrStr[i] >= 0x7F && rArgs.mrStr[i] <= 0xFF))
+ s << "\\0x" << std::hex << std::setw(2) << std::setfill('0')
+ << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec;
+ else if (rArgs.mrStr[i] < 0x7F)
+ s << static_cast<char>(rArgs.mrStr[i]);
+ else
+ s << "\\u" << std::hex << std::setw(4) << std::setfill('0')
+ << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec;
+ }
+ if (nLength > lim)
+ s << "...";
+ s << "\"";
+
+ s << ",DXArray=";
+ if (rArgs.mpDXArray || rArgs.mpAltNaturalDXArray)
+ {
+ s << "[";
+ int count = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
+ lim = count;
+ if (lim > 10)
+ lim = 7;
+ for (int i = 0; i < lim; i++)
+ {
+ if (rArgs.mpDXArray)
+ s << rArgs.mpDXArray[i];
+ else
+ s << rArgs.mpAltNaturalDXArray[i];
+ if (i < lim - 1)
+ s << ",";
+ }
+ if (count > lim)
+ {
+ if (count > lim + 1)
+ s << "...";
+ if (rArgs.mpDXArray)
+ s << rArgs.mpDXArray[count - 1];
+ else
+ s << rArgs.mpAltNaturalDXArray[count - 1];
+ }
+ s << "]";
+ }
+ else
+ s << "NULL";
+
+ s << ",LayoutWidth=" << rArgs.mnLayoutWidth;
+
+ s << "}";
+
+#endif
+ return s;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/text/ImplLayoutRuns.cxx b/vcl/source/text/ImplLayoutRuns.cxx
new file mode 100644
index 000000000..a560e17e1
--- /dev/null
+++ b/vcl/source/text/ImplLayoutRuns.cxx
@@ -0,0 +1,179 @@
+/* -*- 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 <ImplLayoutRuns.hxx>
+
+void ImplLayoutRuns::AddPos( int nCharPos, bool bRTL )
+{
+ // check if charpos could extend current run
+ int nIndex = maRuns.size();
+ if( nIndex >= 2 )
+ {
+ int nRunPos0 = maRuns[ nIndex-2 ];
+ int nRunPos1 = maRuns[ nIndex-1 ];
+ if( ((nCharPos + int(bRTL)) == nRunPos1) && ((nRunPos0 > nRunPos1) == bRTL) )
+ {
+ // extend current run by new charpos
+ maRuns[ nIndex-1 ] = nCharPos + int(!bRTL);
+ return;
+ }
+ // ignore new charpos when it is in current run
+ if( (nRunPos0 <= nCharPos) && (nCharPos < nRunPos1) )
+ return;
+ if( (nRunPos1 <= nCharPos) && (nCharPos < nRunPos0) )
+ return;
+ }
+
+ // else append a new run consisting of the new charpos
+ maRuns.push_back( nCharPos + (bRTL ? 1 : 0) );
+ maRuns.push_back( nCharPos + (bRTL ? 0 : 1) );
+}
+
+void ImplLayoutRuns::AddRun( int nCharPos0, int nCharPos1, bool bRTL )
+{
+ if( nCharPos0 == nCharPos1 )
+ return;
+
+ // swap if needed
+ if( bRTL == (nCharPos0 < nCharPos1) )
+ {
+ int nTemp = nCharPos0;
+ nCharPos0 = nCharPos1;
+ nCharPos1 = nTemp;
+ }
+
+ if (maRuns.size() >= 2 && nCharPos0 == maRuns[maRuns.size() - 2] && nCharPos1 == maRuns[maRuns.size() - 1])
+ {
+ //this run is the same as the last
+ return;
+ }
+
+ // append new run
+ maRuns.push_back( nCharPos0 );
+ maRuns.push_back( nCharPos1 );
+}
+
+bool ImplLayoutRuns::PosIsInRun( int nCharPos ) const
+{
+ if( mnRunIndex >= static_cast<int>(maRuns.size()) )
+ return false;
+
+ int nMinCharPos = maRuns[ mnRunIndex+0 ];
+ int nEndCharPos = maRuns[ mnRunIndex+1 ];
+ if( nMinCharPos > nEndCharPos ) // reversed in RTL case
+ {
+ int nTemp = nMinCharPos;
+ nMinCharPos = nEndCharPos;
+ nEndCharPos = nTemp;
+ }
+
+ if( nCharPos < nMinCharPos )
+ return false;
+ if( nCharPos >= nEndCharPos )
+ return false;
+ return true;
+}
+
+bool ImplLayoutRuns::PosIsInAnyRun( int nCharPos ) const
+{
+ bool bRet = false;
+ int nRunIndex = mnRunIndex;
+
+ ImplLayoutRuns *pThis = const_cast<ImplLayoutRuns*>(this);
+
+ pThis->ResetPos();
+
+ for (size_t i = 0; i < maRuns.size(); i+=2)
+ {
+ bRet = PosIsInRun( nCharPos );
+ if( bRet )
+ break;
+ pThis->NextRun();
+ }
+
+ pThis->mnRunIndex = nRunIndex;
+ return bRet;
+}
+
+bool ImplLayoutRuns::GetNextPos( int* nCharPos, bool* bRightToLeft )
+{
+ // negative nCharPos => reset to first run
+ if( *nCharPos < 0 )
+ mnRunIndex = 0;
+
+ // return false when all runs completed
+ if( mnRunIndex >= static_cast<int>(maRuns.size()) )
+ return false;
+
+ int nRunPos0 = maRuns[ mnRunIndex+0 ];
+ int nRunPos1 = maRuns[ mnRunIndex+1 ];
+ *bRightToLeft = (nRunPos0 > nRunPos1);
+
+ if( *nCharPos < 0 )
+ {
+ // get first valid nCharPos in run
+ *nCharPos = nRunPos0;
+ }
+ else
+ {
+ // advance to next nCharPos for LTR case
+ if( !*bRightToLeft )
+ ++(*nCharPos);
+
+ // advance to next run if current run is completed
+ if( *nCharPos == nRunPos1 )
+ {
+ if( (mnRunIndex += 2) >= static_cast<int>(maRuns.size()) )
+ return false;
+ nRunPos0 = maRuns[ mnRunIndex+0 ];
+ nRunPos1 = maRuns[ mnRunIndex+1 ];
+ *bRightToLeft = (nRunPos0 > nRunPos1);
+ *nCharPos = nRunPos0;
+ }
+ }
+
+ // advance to next nCharPos for RTL case
+ if( *bRightToLeft )
+ --(*nCharPos);
+
+ return true;
+}
+
+bool ImplLayoutRuns::GetRun( int* nMinRunPos, int* nEndRunPos, bool* bRightToLeft ) const
+{
+ if( mnRunIndex >= static_cast<int>(maRuns.size()) )
+ return false;
+
+ int nRunPos0 = maRuns[ mnRunIndex+0 ];
+ int nRunPos1 = maRuns[ mnRunIndex+1 ];
+ *bRightToLeft = (nRunPos1 < nRunPos0) ;
+ if( !*bRightToLeft )
+ {
+ *nMinRunPos = nRunPos0;
+ *nEndRunPos = nRunPos1;
+ }
+ else
+ {
+ *nMinRunPos = nRunPos1;
+ *nEndRunPos = nRunPos0;
+ }
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/text/TextLayoutCache.cxx b/vcl/source/text/TextLayoutCache.cxx
new file mode 100644
index 000000000..1d3e8e504
--- /dev/null
+++ b/vcl/source/text/TextLayoutCache.cxx
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <TextLayoutCache.hxx>
+
+#include <scrptrun.h>
+
+#include <o3tl/hash_combine.hxx>
+#include <o3tl/lru_map.hxx>
+#include <unotools/configmgr.hxx>
+#include <vcl/lazydelete.hxx>
+#include <officecfg/Office/Common.hxx>
+
+namespace vcl::text
+{
+TextLayoutCache::TextLayoutCache(sal_Unicode const* pStr, sal_Int32 const nEnd)
+{
+ vcl::ScriptRun aScriptRun(reinterpret_cast<const UChar*>(pStr), nEnd);
+ while (aScriptRun.next())
+ {
+ runs.emplace_back(aScriptRun.getScriptStart(), aScriptRun.getScriptEnd(),
+ aScriptRun.getScriptCode());
+ }
+}
+
+namespace
+{
+struct TextLayoutCacheCost
+{
+ size_t operator()(const std::shared_ptr<const TextLayoutCache>& item) const
+ {
+ return item->runs.size() * sizeof(item->runs.front());
+ }
+};
+} // namespace
+
+std::shared_ptr<const TextLayoutCache> TextLayoutCache::Create(OUString const& rString)
+{
+ typedef o3tl::lru_map<OUString, std::shared_ptr<const TextLayoutCache>, FirstCharsStringHash,
+ FastStringCompareEqual, TextLayoutCacheCost>
+ Cache;
+ static vcl::DeleteOnDeinit<Cache> cache(
+ !utl::ConfigManager::IsFuzzing()
+ ? officecfg::Office::Common::Cache::Font::TextRunsCacheSize::get()
+ : 100);
+ if (Cache* map = cache.get())
+ {
+ auto it = map->find(rString);
+ if (it != map->end())
+ return it->second;
+ auto ret = std::make_shared<const TextLayoutCache>(rString.getStr(), rString.getLength());
+ map->insert({ rString, ret });
+ return ret;
+ }
+ return std::make_shared<const TextLayoutCache>(rString.getStr(), rString.getLength());
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/toolkit/README b/vcl/source/toolkit/README
new file mode 100644
index 000000000..78863390f
--- /dev/null
+++ b/vcl/source/toolkit/README
@@ -0,0 +1,2 @@
+These are controls which are now only used by the toolkit module, which exposes
+them via uno. Don't use these in any new code.
diff --git a/vcl/source/toolkit/group.cxx b/vcl/source/toolkit/group.cxx
new file mode 100644
index 000000000..b33da711f
--- /dev/null
+++ b/vcl/source/toolkit/group.cxx
@@ -0,0 +1,255 @@
+/* -*- 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 <vcl/event.hxx>
+#include <vcl/toolkit/group.hxx>
+#include <vcl/settings.hxx>
+
+#define GROUP_BORDER 12
+#define GROUP_TEXT_BORDER 2
+
+#define GROUP_VIEW_STYLE (WB_3DLOOK | WB_NOLABEL)
+
+void GroupBox::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle( nStyle );
+ Control::ImplInit( pParent, nStyle, nullptr );
+ SetMouseTransparent( true );
+ ImplInitSettings( true );
+}
+
+WinBits GroupBox::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+const vcl::Font& GroupBox::GetCanonicalFont( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetGroupFont();
+}
+
+const Color& GroupBox::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetGroupTextColor();
+}
+
+void GroupBox::ImplInitSettings( bool bBackground )
+{
+ Control::ImplInitSettings();
+
+ if ( !bBackground )
+ return;
+
+ vcl::Window* pParent = GetParent();
+ if ( (pParent->IsChildTransparentModeEnabled() ||
+ !(pParent->GetStyle() & WB_CLIPCHILDREN) ) &&
+ !IsControlBackground() )
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode( ParentClipMode::NoClip );
+ SetPaintTransparent( true );
+ SetBackground();
+ }
+ else
+ {
+ EnableChildTransparentMode( false );
+ SetParentClipMode();
+ SetPaintTransparent( false );
+
+ if ( IsControlBackground() )
+ SetBackground( GetControlBackground() );
+ else
+ SetBackground( pParent->GetBackground() );
+ }
+}
+
+GroupBox::GroupBox( vcl::Window* pParent, WinBits nStyle ) :
+ Control( WindowType::GROUPBOX )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void GroupBox::ImplDraw( OutputDevice* pDev, SystemTextColorFlags nSystemTextColorFlags,
+ const Point& rPos, const Size& rSize, bool bLayout )
+{
+ tools::Long nTop;
+ tools::Long nTextOff;
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ OUString aText( GetText() );
+ tools::Rectangle aRect( rPos, rSize );
+ DrawTextFlags nTextStyle = DrawTextFlags::Left | DrawTextFlags::Top | DrawTextFlags::EndEllipsis | DrawTextFlags::Mnemonic;
+
+ if ( GetStyle() & WB_NOLABEL )
+ nTextStyle &= ~DrawTextFlags::Mnemonic;
+ if ( !IsEnabled() )
+ nTextStyle |= DrawTextFlags::Disable;
+ if ( (nSystemTextColorFlags & SystemTextColorFlags::Mono) ||
+ (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) )
+ {
+ nTextStyle |= DrawTextFlags::Mono;
+ nSystemTextColorFlags |= SystemTextColorFlags::Mono;
+ }
+
+ if (aText.isEmpty())
+ {
+ nTop = rPos.Y();
+ nTextOff = 0;
+ }
+ else
+ {
+ aRect.AdjustLeft(GROUP_BORDER );
+ aRect.AdjustRight( -(GROUP_BORDER) );
+ aRect = pDev->GetTextRect( aRect, aText, nTextStyle );
+ nTop = rPos.Y();
+ nTop += aRect.GetHeight() / 2;
+ nTextOff = GROUP_TEXT_BORDER;
+ }
+
+ if( ! bLayout )
+ {
+ if ( nSystemTextColorFlags & SystemTextColorFlags::Mono )
+ pDev->SetLineColor( COL_BLACK );
+ else
+ pDev->SetLineColor( rStyleSettings.GetShadowColor() );
+
+ if (aText.isEmpty())
+ pDev->DrawLine( Point( rPos.X(), nTop ), Point( rPos.X()+rSize.Width()-2, nTop ) );
+ else
+ {
+ pDev->DrawLine( Point( rPos.X(), nTop ), Point( aRect.Left()-nTextOff, nTop ) );
+ pDev->DrawLine( Point( aRect.Right()+nTextOff, nTop ), Point( rPos.X()+rSize.Width()-2, nTop ) );
+ }
+ pDev->DrawLine( Point( rPos.X(), nTop ), Point( rPos.X(), rPos.Y()+rSize.Height()-2 ) );
+ pDev->DrawLine( Point( rPos.X(), rPos.Y()+rSize.Height()-2 ), Point( rPos.X()+rSize.Width()-2, rPos.Y()+rSize.Height()-2 ) );
+ pDev->DrawLine( Point( rPos.X()+rSize.Width()-2, rPos.Y()+rSize.Height()-2 ), Point( rPos.X()+rSize.Width()-2, nTop ) );
+
+ bool bIsPrinter = OUTDEV_PRINTER == pDev->GetOutDevType();
+ // if we're drawing onto a printer, spare the 3D effect #i46986#
+
+ if ( !bIsPrinter && !(nSystemTextColorFlags & SystemTextColorFlags::Mono) )
+ {
+ pDev->SetLineColor( rStyleSettings.GetLightColor() );
+ if (aText.isEmpty())
+ pDev->DrawLine( Point( rPos.X()+1, nTop+1 ), Point( rPos.X()+rSize.Width()-3, nTop+1 ) );
+ else
+ {
+ pDev->DrawLine( Point( rPos.X()+1, nTop+1 ), Point( aRect.Left()-nTextOff, nTop+1 ) );
+ pDev->DrawLine( Point( aRect.Right()+nTextOff, nTop+1 ), Point( rPos.X()+rSize.Width()-3, nTop+1 ) );
+ }
+ pDev->DrawLine( Point( rPos.X()+1, nTop+1 ), Point( rPos.X()+1, rPos.Y()+rSize.Height()-3 ) );
+ pDev->DrawLine( Point( rPos.X(), rPos.Y()+rSize.Height()-1 ), Point( rPos.X()+rSize.Width()-1, rPos.Y()+rSize.Height()-1 ) );
+ pDev->DrawLine( Point( rPos.X()+rSize.Width()-1, rPos.Y()+rSize.Height()-1 ), Point( rPos.X()+rSize.Width()-1, nTop ) );
+ }
+ }
+
+ std::vector< tools::Rectangle >* pVector = bLayout ? &mxLayoutData->m_aUnicodeBoundRects : nullptr;
+ OUString* pDisplayText = bLayout ? &mxLayoutData->m_aDisplayText : nullptr;
+ DrawControlText( *pDev, aRect, aText, nTextStyle, pVector, pDisplayText );
+}
+
+void GroupBox::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ const_cast<GroupBox*>(this)->ImplDraw( const_cast<GroupBox*>(this)->GetOutDev(), SystemTextColorFlags::NONE, Point(), GetOutputSizePixel(), true );
+}
+
+void GroupBox::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ ImplDraw(&rRenderContext, SystemTextColorFlags::NONE, Point(), GetOutputSizePixel());
+}
+
+void GroupBox::Draw( OutputDevice* pDev, const Point& rPos,
+ SystemTextColorFlags nFlags )
+{
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ vcl::Font aFont = GetDrawPixelFont( pDev );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ if ( nFlags & SystemTextColorFlags::Mono )
+ pDev->SetTextColor( COL_BLACK );
+ else
+ pDev->SetTextColor( GetTextColor() );
+ pDev->SetTextFillColor();
+
+ ImplDraw( pDev, nFlags, aPos, aSize );
+ pDev->Pop();
+}
+
+void GroupBox::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void GroupBox::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ if ( (GetPrevStyle() & GROUP_VIEW_STYLE) !=
+ (GetStyle() & GROUP_VIEW_STYLE) )
+ Invalidate();
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+void GroupBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/toolkit/morebtn.cxx b/vcl/source/toolkit/morebtn.cxx
new file mode 100644
index 000000000..9c0f6c505
--- /dev/null
+++ b/vcl/source/toolkit/morebtn.cxx
@@ -0,0 +1,123 @@
+/* -*- 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 <vcl/toolkit/morebtn.hxx>
+#include <vcl/stdtext.hxx>
+
+struct ImplMoreButtonData
+{
+ OUString maMoreText;
+ OUString maLessText;
+};
+
+void MoreButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ mpMBData.reset(new ImplMoreButtonData);
+ mbState = false;
+
+ PushButton::ImplInit( pParent, nStyle );
+
+ mpMBData->maMoreText = GetStandardText( StandardButtonType::More );
+ mpMBData->maLessText = GetStandardText( StandardButtonType::Less );
+
+ ShowState();
+
+ SetSymbolAlign(SymbolAlign::RIGHT);
+ SetImageAlign(ImageAlign::Right); //Resolves: fdo#31849 ensure button remains vertically centered
+ SetSmallSymbol();
+
+ if ( ! ( nStyle & ( WB_RIGHT | WB_LEFT ) ) )
+ {
+ nStyle |= WB_CENTER;
+ SetStyle( nStyle );
+ }
+}
+
+void MoreButton::ShowState()
+{
+ if ( mbState )
+ {
+ SetSymbol( SymbolType::PAGEUP );
+ SetText( mpMBData->maLessText );
+ }
+ else
+ {
+ SetSymbol( SymbolType::PAGEDOWN );
+ SetText( mpMBData->maMoreText );
+ }
+}
+
+MoreButton::MoreButton( vcl::Window* pParent, WinBits nStyle ) :
+ PushButton( WindowType::MOREBUTTON )
+{
+ ImplInit( pParent, nStyle );
+}
+
+MoreButton::~MoreButton()
+{
+ disposeOnce();
+}
+
+void MoreButton::dispose()
+{
+ mpMBData.reset();
+ PushButton::dispose();
+}
+
+void MoreButton::Click()
+{
+ vcl::Window* pParent = GetParent();
+ Size aSize( pParent->GetSizePixel() );
+ tools::Long nDeltaPixel = LogicToPixel(Size(0, 0), MapMode(MapUnit::MapPixel)).Height();
+
+ // Change status
+ mbState = !mbState;
+ ShowState();
+
+ // Update the windows according to the status
+ if ( mbState )
+ {
+ // Adapt dialogbox
+ Point aPos( pParent->GetPosPixel() );
+ tools::Rectangle aDeskRect( pParent->ImplGetFrameWindow()->GetDesktopRectPixel() );
+
+ aSize.AdjustHeight(nDeltaPixel );
+ if ( (aPos.Y()+aSize.Height()) > aDeskRect.Bottom() )
+ {
+ aPos.setY( aDeskRect.Bottom()-aSize.Height() );
+
+ if ( aPos.Y() < aDeskRect.Top() )
+ aPos.setY( aDeskRect.Top() );
+
+ pParent->SetPosSizePixel( aPos, aSize );
+ }
+ else
+ pParent->SetSizePixel( aSize );
+ }
+ else
+ {
+ // Adapt Dialogbox
+ aSize.AdjustHeight( -nDeltaPixel );
+ pParent->SetSizePixel( aSize );
+ }
+ // Call Click handler here, so that we can initialize the Controls
+ PushButton::Click();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/headbar.cxx b/vcl/source/treelist/headbar.cxx
new file mode 100644
index 000000000..3bd6bbaa3
--- /dev/null
+++ b/vcl/source/treelist/headbar.cxx
@@ -0,0 +1,1302 @@
+/* -*- 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 <vcl/headbar.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/help.hxx>
+#include <vcl/image.hxx>
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/ptrstyle.hxx>
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+
+class ImplHeadItem
+{
+public:
+ sal_uInt16 mnId;
+ HeaderBarItemBits mnBits;
+ tools::Long mnSize;
+ OString maHelpId;
+ Image maImage;
+ OUString maOutText;
+ OUString maText;
+ OUString maHelpText;
+};
+
+#define HEAD_ARROWSIZE1 4
+#define HEAD_ARROWSIZE2 7
+
+#define HEADERBAR_TEXTOFF 2
+#define HEADERBAR_ARROWOFF 5
+#define HEADERBAR_SPLITOFF 3
+
+#define HEADERBAR_DRAGOUTOFF 15
+
+#define HEAD_HITTEST_ITEM (sal_uInt16(0x0001))
+#define HEAD_HITTEST_DIVIDER (sal_uInt16(0x0002))
+
+void HeaderBar::ImplInit( WinBits nWinStyle )
+{
+ mnBorderOff1 = 0;
+ mnBorderOff2 = 0;
+ mnOffset = 0;
+ mnDX = 0;
+ mnDY = 0;
+ mnDragSize = 0;
+ mnStartPos = 0;
+ mnDragPos = 0;
+ mnMouseOff = 0;
+ mnCurItemId = 0;
+ mnItemDragPos = HEADERBAR_ITEM_NOTFOUND;
+ mbDrag = false;
+ mbItemDrag = false;
+ mbOutDrag = false;
+ mbItemMode = false;
+
+ // evaluate StyleBits
+ if ( nWinStyle & WB_DRAG )
+ mbDragable = true;
+ else
+ mbDragable = false;
+ if ( nWinStyle & WB_BUTTONSTYLE )
+ mbButtonStyle = true;
+ else
+ mbButtonStyle = false;
+ if ( nWinStyle & WB_BORDER )
+ {
+ mnBorderOff1 = 1;
+ mnBorderOff2 = 1;
+ }
+ else
+ {
+ if ( nWinStyle & WB_BOTTOMBORDER )
+ mnBorderOff2 = 1;
+ }
+
+ ImplInitSettings( true, true, true );
+}
+
+HeaderBar::HeaderBar(vcl::Window* pParent, WinBits nWinStyle)
+ : Window(pParent, nWinStyle & WB_3DLOOK)
+{
+ SetType(WindowType::HEADERBAR);
+ ImplInit(nWinStyle);
+ SetSizePixel( CalcWindowSizePixel() );
+}
+
+Size HeaderBar::GetOptimalSize() const
+{
+ return CalcWindowSizePixel();
+}
+
+HeaderBar::~HeaderBar() = default;
+
+void HeaderBar::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+
+ ApplyControlFont(rRenderContext, rStyleSettings.GetToolFont());
+
+ ApplyControlForeground(rRenderContext, rStyleSettings.GetButtonTextColor());
+ SetTextFillColor();
+
+ ApplyControlBackground(rRenderContext, rStyleSettings.GetFaceColor());
+}
+
+void HeaderBar::ImplInitSettings(bool bFont, bool bForeground, bool bBackground)
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+
+ if (bFont)
+ ApplyControlFont(*GetOutDev(), rStyleSettings.GetToolFont());
+
+ if (bForeground || bFont)
+ {
+ ApplyControlForeground(*GetOutDev(), rStyleSettings.GetButtonTextColor());
+ SetTextFillColor();
+ }
+
+ if (bBackground)
+ ApplyControlBackground(*GetOutDev(), rStyleSettings.GetFaceColor());
+}
+
+tools::Long HeaderBar::ImplGetItemPos( sal_uInt16 nPos ) const
+{
+ tools::Long nX = -mnOffset;
+ for ( size_t i = 0; i < nPos; i++ )
+ nX += mvItemList[ i ]->mnSize;
+ return nX;
+}
+
+tools::Rectangle HeaderBar::ImplGetItemRect( sal_uInt16 nPos ) const
+{
+ tools::Rectangle aRect( ImplGetItemPos( nPos ), 0, 0, mnDY-1 );
+ aRect.SetRight( aRect.Left() + mvItemList[ nPos ]->mnSize - 1 );
+ // check for overflow on various systems
+ if ( aRect.Right() > 16000 )
+ aRect.SetRight( 16000 );
+ return aRect;
+}
+
+sal_uInt16 HeaderBar::ImplDoHitTest( const Point& rPos,
+ tools::Long& nMouseOff, sal_uInt16& nPos ) const
+{
+ size_t nCount = static_cast<sal_uInt16>(mvItemList.size());
+ bool bLastFixed = true;
+ tools::Long nX = -mnOffset;
+
+ for ( size_t i = 0; i < nCount; i++ )
+ {
+ auto& pItem = mvItemList[ i ];
+
+ if ( rPos.X() < (nX+pItem->mnSize) )
+ {
+ sal_uInt16 nMode;
+
+ if ( !bLastFixed && (rPos.X() < (nX+HEADERBAR_SPLITOFF)) )
+ {
+ nMode = HEAD_HITTEST_DIVIDER;
+ nPos = i-1;
+ nMouseOff = rPos.X()-nX+1;
+ }
+ else
+ {
+ nPos = i;
+
+ if ( rPos.X() >= (nX+pItem->mnSize-HEADERBAR_SPLITOFF) )
+ {
+ nMode = HEAD_HITTEST_DIVIDER;
+ nMouseOff = rPos.X()-(nX+pItem->mnSize);
+ }
+ else
+ {
+ nMode = HEAD_HITTEST_ITEM;
+ nMouseOff = rPos.X()-nX;
+ }
+ }
+
+ return nMode;
+ }
+
+ bLastFixed = false;
+
+ nX += pItem->mnSize;
+ }
+
+ if ( !bLastFixed )
+ {
+ auto& pItem = mvItemList[ nCount-1 ];
+ if ( (pItem->mnSize < 4) && (rPos.X() < (nX+HEADERBAR_SPLITOFF)) )
+ {
+ nPos = nCount-1;
+ nMouseOff = rPos.X()-nX+1;
+ return HEAD_HITTEST_DIVIDER;
+ }
+ }
+
+ return 0;
+}
+
+void HeaderBar::ImplInvertDrag( sal_uInt16 nStartPos, sal_uInt16 nEndPos )
+{
+ tools::Rectangle aRect1 = ImplGetItemRect( nStartPos );
+ tools::Rectangle aRect2 = ImplGetItemRect( nEndPos );
+ Point aStartPos = aRect1.Center();
+ Point aEndPos = aStartPos;
+ tools::Rectangle aStartRect( aStartPos.X()-2, aStartPos.Y()-2,
+ aStartPos.X()+2, aStartPos.Y()+2 );
+
+ if ( nEndPos > nStartPos )
+ {
+ aStartPos.AdjustX(3 );
+ aEndPos.setX( aRect2.Right()-6 );
+ }
+ else
+ {
+ aStartPos.AdjustX( -3 );
+ aEndPos.setX( aRect2.Left()+6 );
+ }
+
+ GetOutDev()->SetRasterOp( RasterOp::Invert );
+ GetOutDev()->DrawRect( aStartRect );
+ GetOutDev()->DrawLine( aStartPos, aEndPos );
+ if ( nEndPos > nStartPos )
+ {
+ GetOutDev()->DrawLine( Point( aEndPos.X()+1, aEndPos.Y()-3 ),
+ Point( aEndPos.X()+1, aEndPos.Y()+3 ) );
+ GetOutDev()->DrawLine( Point( aEndPos.X()+2, aEndPos.Y()-2 ),
+ Point( aEndPos.X()+2, aEndPos.Y()+2 ) );
+ GetOutDev()->DrawLine( Point( aEndPos.X()+3, aEndPos.Y()-1 ),
+ Point( aEndPos.X()+3, aEndPos.Y()+1 ) );
+ GetOutDev()->DrawPixel( Point( aEndPos.X()+4, aEndPos.Y() ) );
+ }
+ else
+ {
+ GetOutDev()->DrawLine( Point( aEndPos.X()-1, aEndPos.Y()-3 ),
+ Point( aEndPos.X()-1, aEndPos.Y()+3 ) );
+ GetOutDev()->DrawLine( Point( aEndPos.X()-2, aEndPos.Y()-2 ),
+ Point( aEndPos.X()-2, aEndPos.Y()+2 ) );
+ GetOutDev()->DrawLine( Point( aEndPos.X()-3, aEndPos.Y()-1 ),
+ Point( aEndPos.X()-3, aEndPos.Y()+1 ) );
+ GetOutDev()->DrawPixel( Point( aEndPos.X()-4, aEndPos.Y() ) );
+ }
+ GetOutDev()->SetRasterOp( RasterOp::OverPaint );
+}
+
+void HeaderBar::ImplDrawItem(vcl::RenderContext& rRenderContext, sal_uInt16 nPos, bool bHigh,
+ const tools::Rectangle& rItemRect, const tools::Rectangle* pRect )
+{
+ ImplControlValue aControlValue(0);
+ tools::Rectangle aCtrlRegion;
+ ControlState nState(ControlState::NONE);
+
+ tools::Rectangle aRect = rItemRect;
+
+ // do not display if there is no space
+ if (aRect.GetWidth() <= 1)
+ return;
+
+ // check of rectangle is visible
+ if (pRect)
+ {
+ if (aRect.Right() < pRect->Left())
+ return;
+ else if (aRect.Left() > pRect->Right())
+ return;
+ }
+ else
+ {
+ if (aRect.Right() < 0)
+ return;
+ else if (aRect.Left() > mnDX)
+ return;
+ }
+
+ auto& pItem = mvItemList[nPos];
+ HeaderBarItemBits nBits = pItem->mnBits;
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::WindowBackground, ControlPart::Entire))
+ {
+ aCtrlRegion = aRect;
+ rRenderContext.DrawNativeControl(ControlType::WindowBackground, ControlPart::Entire,
+ aCtrlRegion, nState, aControlValue, OUString());
+
+ }
+ else
+ {
+ // do not draw border
+ aRect.AdjustTop(mnBorderOff1 );
+ aRect.AdjustBottom( -mnBorderOff2 );
+
+ // delete background
+ if ( !pRect )
+ {
+ rRenderContext.DrawWallpaper(aRect, rRenderContext.GetBackground());
+ }
+ }
+
+ Color aSelectionTextColor(COL_TRANSPARENT);
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::ListHeader, ControlPart::Button))
+ {
+ aCtrlRegion = aRect;
+ aControlValue.setTristateVal(ButtonValue::On);
+ nState |= ControlState::ENABLED;
+ if (bHigh)
+ nState |= ControlState::PRESSED;
+ rRenderContext.DrawNativeControl(ControlType::ListHeader, ControlPart::Button,
+ aCtrlRegion, nState, aControlValue, OUString());
+ }
+ else
+ {
+ // draw separation line
+ rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
+ rRenderContext.DrawLine(Point(aRect.Right(), aRect.Top()), Point(aRect.Right(), aRect.Bottom()));
+
+ // draw ButtonStyle
+ // avoid 3D borders
+ if (bHigh)
+ vcl::RenderTools::DrawSelectionBackground(rRenderContext, *this, aRect, 1, true, false, false, &aSelectionTextColor);
+ else if (!mbButtonStyle || (nBits & HeaderBarItemBits::FLAT))
+ vcl::RenderTools::DrawSelectionBackground(rRenderContext, *this, aRect, 0, true, false, false, &aSelectionTextColor);
+ }
+
+ // do not draw if there is no space
+ if (aRect.GetWidth() < 1)
+ return;
+
+ // calculate size and position and draw content
+ pItem->maOutText = pItem->maText;
+ Size aImageSize = pItem->maImage.GetSizePixel();
+ Size aTxtSize(rRenderContext.GetTextWidth(pItem->maOutText), 0);
+ if (!pItem->maOutText.isEmpty())
+ aTxtSize.setHeight( rRenderContext.GetTextHeight() );
+ tools::Long nArrowWidth = 0;
+ if (nBits & (HeaderBarItemBits::UPARROW | HeaderBarItemBits::DOWNARROW))
+ nArrowWidth = HEAD_ARROWSIZE2 + HEADERBAR_ARROWOFF;
+
+ // do not draw if there is not enough space for the image
+ tools::Long nTestHeight = aImageSize.Height();
+ if (!(nBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE)))
+ nTestHeight += aTxtSize.Height();
+ if ((aImageSize.Width() > aRect.GetWidth()) || (nTestHeight > aRect.GetHeight()))
+ {
+ aImageSize.setWidth( 0 );
+ aImageSize.setHeight( 0 );
+ }
+
+ // cut text to correct length
+ bool bLeftText = false;
+ tools::Long nMaxTxtWidth = aRect.GetWidth() - (HEADERBAR_TEXTOFF * 2) - nArrowWidth;
+ if (nBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE))
+ nMaxTxtWidth -= aImageSize.Width();
+ tools::Long nTxtWidth = aTxtSize.Width();
+ if (nTxtWidth > nMaxTxtWidth)
+ {
+ bLeftText = true;
+ OUStringBuffer aBuf(pItem->maOutText);
+ aBuf.append("...");
+ do
+ {
+ aBuf.remove(aBuf.getLength() - 3 - 1, 1);
+ nTxtWidth = rRenderContext.GetTextWidth(aBuf.toString());
+ }
+ while ((nTxtWidth > nMaxTxtWidth) && (aBuf.getLength() > 3));
+ pItem->maOutText = aBuf.makeStringAndClear();
+ if (pItem->maOutText.getLength() == 3)
+ {
+ nTxtWidth = 0;
+ pItem->maOutText.clear();
+ }
+ }
+
+ // calculate text/imageposition
+ tools::Long nTxtPos;
+ if (!bLeftText && (nBits & HeaderBarItemBits::RIGHT))
+ {
+ nTxtPos = aRect.Right() - nTxtWidth - HEADERBAR_TEXTOFF;
+ if (nBits & HeaderBarItemBits::RIGHTIMAGE)
+ nTxtPos -= aImageSize.Width();
+ }
+ else if (!bLeftText && (nBits & HeaderBarItemBits::CENTER))
+ {
+ tools::Long nTempWidth = nTxtWidth;
+ if (nBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE))
+ nTempWidth += aImageSize.Width();
+ nTxtPos = aRect.Left() + (aRect.GetWidth() - nTempWidth) / 2;
+ if (nBits & HeaderBarItemBits::LEFTIMAGE)
+ nTxtPos += aImageSize.Width();
+ if (nArrowWidth)
+ {
+ if (nTxtPos + nTxtWidth + nArrowWidth >= aRect.Right())
+ {
+ nTxtPos = aRect.Left() + HEADERBAR_TEXTOFF;
+ if (nBits & HeaderBarItemBits::LEFTIMAGE)
+ nTxtPos += aImageSize.Width();
+ }
+ }
+ }
+ else
+ {
+ nTxtPos = aRect.Left() + HEADERBAR_TEXTOFF;
+ if (nBits & HeaderBarItemBits::LEFTIMAGE)
+ nTxtPos += aImageSize.Width();
+ if (nBits & HeaderBarItemBits::RIGHT)
+ nTxtPos += nArrowWidth;
+ }
+
+ // calculate text/imageposition
+ tools::Long nTxtPosY = 0;
+ if (!pItem->maOutText.isEmpty() || (nArrowWidth && aTxtSize.Height()))
+ {
+ tools::Long nTempHeight = aTxtSize.Height();
+ nTempHeight += aImageSize.Height();
+ nTxtPosY = aRect.Top()+((aRect.GetHeight()-nTempHeight)/2);
+ if (!(nBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE)))
+ nTxtPosY += aImageSize.Height();
+ }
+
+ // display text
+ if (!pItem->maOutText.isEmpty())
+ {
+ if (aSelectionTextColor != COL_TRANSPARENT)
+ {
+ rRenderContext.Push(vcl::PushFlags::TEXTCOLOR);
+ rRenderContext.SetTextColor(aSelectionTextColor);
+ }
+ if (IsEnabled())
+ rRenderContext.DrawText(Point(nTxtPos, nTxtPosY), pItem->maOutText);
+ else
+ rRenderContext.DrawCtrlText(Point(nTxtPos, nTxtPosY), pItem->maOutText, 0, pItem->maOutText.getLength(), DrawTextFlags::Disable);
+ if (aSelectionTextColor != COL_TRANSPARENT)
+ rRenderContext.Pop();
+ }
+
+ // calculate the position and draw image if it is available
+ tools::Long nImagePosY = 0;
+ if (aImageSize.Width() && aImageSize.Height())
+ {
+ tools::Long nImagePos = nTxtPos;
+ if (nBits & HeaderBarItemBits::LEFTIMAGE)
+ {
+ nImagePos -= aImageSize.Width();
+ if (nBits & HeaderBarItemBits::RIGHT)
+ nImagePos -= nArrowWidth;
+ }
+ else if (nBits & HeaderBarItemBits::RIGHTIMAGE)
+ {
+ nImagePos += nTxtWidth;
+ if (!(nBits & HeaderBarItemBits::RIGHT))
+ nImagePos += nArrowWidth;
+ }
+ else
+ {
+ if (nBits & HeaderBarItemBits::RIGHT )
+ nImagePos = aRect.Right()-aImageSize.Width();
+ else if (nBits & HeaderBarItemBits::CENTER)
+ nImagePos = aRect.Left() + (aRect.GetWidth() - aImageSize.Width()) / 2;
+ else
+ nImagePos = aRect.Left() + HEADERBAR_TEXTOFF;
+ }
+
+ tools::Long nTempHeight = aImageSize.Height();
+ if (!(nBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE)))
+ nTempHeight += aTxtSize.Height();
+ nImagePosY = aRect.Top() + ((aRect.GetHeight() - nTempHeight) / 2);
+
+ if (nImagePos + aImageSize.Width() <= aRect.Right())
+ {
+ DrawImageFlags nStyle = DrawImageFlags::NONE;
+ if (!IsEnabled())
+ nStyle |= DrawImageFlags::Disable;
+ rRenderContext.DrawImage(Point(nImagePos, nImagePosY), pItem->maImage, nStyle);
+ }
+ }
+
+ if (!(nBits & (HeaderBarItemBits::UPARROW | HeaderBarItemBits::DOWNARROW)))
+ return;
+
+ tools::Long nArrowX = nTxtPos;
+ if (nBits & HeaderBarItemBits::RIGHT)
+ nArrowX -= nArrowWidth;
+ else
+ nArrowX += nTxtWidth + HEADERBAR_ARROWOFF;
+ if (!(nBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE)) && pItem->maText.isEmpty())
+ {
+ if (nBits & HeaderBarItemBits::RIGHT)
+ nArrowX -= aImageSize.Width();
+ else
+ nArrowX += aImageSize.Width();
+ }
+
+ // is there enough space to draw the item?
+ bool bDraw = true;
+ if (nArrowX < aRect.Left() + HEADERBAR_TEXTOFF)
+ bDraw = false;
+ else if (nArrowX + HEAD_ARROWSIZE2 > aRect.Right())
+ bDraw = false;
+
+ if (!bDraw)
+ return;
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::ListHeader, ControlPart::Arrow))
+ {
+ aCtrlRegion = tools::Rectangle(Point(nArrowX, aRect.Top()), Size(nArrowWidth, aRect.GetHeight()));
+ // control value passes 1 if arrow points down, 0 otherwise
+ aControlValue.setNumericVal((nBits & HeaderBarItemBits::DOWNARROW) ? 1 : 0);
+ nState |= ControlState::ENABLED;
+ if (bHigh)
+ nState |= ControlState::PRESSED;
+ rRenderContext.DrawNativeControl(ControlType::ListHeader, ControlPart::Arrow, aCtrlRegion,
+ nState, aControlValue, OUString());
+ }
+ else
+ {
+ tools::Long nArrowY;
+ if (aTxtSize.Height())
+ nArrowY = nTxtPosY + (aTxtSize.Height() / 2);
+ else if (aImageSize.Width() && aImageSize.Height())
+ nArrowY = nImagePosY + (aImageSize.Height() / 2);
+ else
+ nArrowY = aRect.Top() + ((aRect.GetHeight() - HEAD_ARROWSIZE2) / 2);
+ nArrowY -= HEAD_ARROWSIZE1 - 1;
+ if (nBits & HeaderBarItemBits::DOWNARROW)
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ rRenderContext.DrawLine(Point(nArrowX, nArrowY),
+ Point(nArrowX + HEAD_ARROWSIZE2, nArrowY));
+ rRenderContext.DrawLine(Point(nArrowX, nArrowY),
+ Point(nArrowX + HEAD_ARROWSIZE1, nArrowY + HEAD_ARROWSIZE2));
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(Point(nArrowX + HEAD_ARROWSIZE1, nArrowY + HEAD_ARROWSIZE2),
+ Point(nArrowX + HEAD_ARROWSIZE2, nArrowY));
+ }
+ else
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ rRenderContext.DrawLine(Point(nArrowX, nArrowY + HEAD_ARROWSIZE2),
+ Point(nArrowX + HEAD_ARROWSIZE1, nArrowY));
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(Point(nArrowX, nArrowY + HEAD_ARROWSIZE2),
+ Point(nArrowX + HEAD_ARROWSIZE2, nArrowY + HEAD_ARROWSIZE2));
+ rRenderContext.DrawLine(Point(nArrowX + HEAD_ARROWSIZE2, nArrowY + HEAD_ARROWSIZE2),
+ Point(nArrowX + HEAD_ARROWSIZE1, nArrowY));
+ }
+ }
+}
+
+void HeaderBar::ImplDrawItem(vcl::RenderContext& rRenderContext, sal_uInt16 nPos,
+ bool bHigh, const tools::Rectangle* pRect )
+{
+ tools::Rectangle aRect = ImplGetItemRect(nPos);
+ ImplDrawItem(rRenderContext, nPos, bHigh, aRect, pRect );
+}
+
+void HeaderBar::ImplUpdate(sal_uInt16 nPos, bool bEnd)
+{
+ if (!(IsVisible() && IsUpdateMode()))
+ return;
+
+ tools::Rectangle aRect;
+ size_t nItemCount = mvItemList.size();
+ if (nPos < nItemCount)
+ aRect = ImplGetItemRect(nPos);
+ else
+ {
+ aRect.SetBottom( mnDY - 1 );
+ if (nItemCount)
+ aRect.SetLeft( ImplGetItemRect(nItemCount - 1).Right() );
+ }
+ if (bEnd)
+ aRect.SetRight( mnDX - 1 );
+ aRect.AdjustTop(mnBorderOff1 );
+ aRect.AdjustBottom( -mnBorderOff2 );
+ Invalidate(aRect);
+}
+
+void HeaderBar::ImplStartDrag( const Point& rMousePos, bool bCommand )
+{
+ sal_uInt16 nPos;
+ sal_uInt16 nHitTest = ImplDoHitTest( rMousePos, mnMouseOff, nPos );
+ if ( !nHitTest )
+ return;
+
+ mbDrag = false;
+ auto& pItem = mvItemList[ nPos ];
+ if ( nHitTest & HEAD_HITTEST_DIVIDER )
+ mbDrag = true;
+ else
+ {
+ if ( ((pItem->mnBits & HeaderBarItemBits::CLICKABLE) && !(pItem->mnBits & HeaderBarItemBits::FLAT)) ||
+ mbDragable )
+ {
+ mbItemMode = true;
+ mbDrag = true;
+ if ( bCommand )
+ {
+ if ( mbDragable )
+ mbItemDrag = true;
+ else
+ {
+ mbItemMode = false;
+ mbDrag = false;
+ }
+ }
+ }
+ else
+ {
+ if ( !bCommand )
+ {
+ mnCurItemId = pItem->mnId;
+ Select();
+ mnCurItemId = 0;
+ }
+ }
+ }
+
+ if ( mbDrag )
+ {
+ mbOutDrag = false;
+ mnCurItemId = pItem->mnId;
+ mnItemDragPos = nPos;
+ StartTracking();
+ mnStartPos = rMousePos.X()-mnMouseOff;
+ mnDragPos = mnStartPos;
+ maStartDragHdl.Call( this );
+ if (mbItemMode)
+ Invalidate();
+ else
+ {
+ tools::Rectangle aSizeRect( mnDragPos, 0, mnDragPos, mnDragSize+mnDY );
+ ShowTracking( aSizeRect, ShowTrackFlags::Split );
+ }
+ }
+ else
+ mnMouseOff = 0;
+}
+
+void HeaderBar::ImplDrag( const Point& rMousePos )
+{
+ sal_uInt16 nPos = GetItemPos( mnCurItemId );
+
+ mnDragPos = rMousePos.X()-mnMouseOff;
+ if ( mbItemMode )
+ {
+ bool bNewOutDrag;
+
+ tools::Rectangle aItemRect = ImplGetItemRect( nPos );
+ bNewOutDrag = !aItemRect.Contains( rMousePos );
+
+ // if needed switch on ItemDrag
+ if ( bNewOutDrag && mbDragable && !mbItemDrag )
+ {
+ if ( (rMousePos.Y() >= aItemRect.Top()) && (rMousePos.Y() <= aItemRect.Bottom()) )
+ {
+ mbItemDrag = true;
+ Invalidate();
+ }
+ }
+
+ sal_uInt16 nOldItemDragPos = mnItemDragPos;
+ if ( mbItemDrag )
+ {
+ bNewOutDrag = (rMousePos.Y() < -HEADERBAR_DRAGOUTOFF) || (rMousePos.Y() > mnDY+HEADERBAR_DRAGOUTOFF);
+
+ if ( bNewOutDrag )
+ mnItemDragPos = HEADERBAR_ITEM_NOTFOUND;
+ else
+ {
+ sal_uInt16 nTempId = GetItemId( Point( rMousePos.X(), 2 ) );
+ if ( nTempId )
+ mnItemDragPos = GetItemPos( nTempId );
+ else
+ {
+ if ( rMousePos.X() <= 0 )
+ mnItemDragPos = 0;
+ else
+ mnItemDragPos = GetItemCount()-1;
+ }
+ }
+
+ if ( (mnItemDragPos != nOldItemDragPos) &&
+ (nOldItemDragPos != nPos) &&
+ (nOldItemDragPos != HEADERBAR_ITEM_NOTFOUND) )
+ {
+ ImplInvertDrag( nPos, nOldItemDragPos );
+ Invalidate();
+ }
+ }
+
+ if ( bNewOutDrag != mbOutDrag )
+ Invalidate();
+
+ if ( mbItemDrag )
+ {
+ if ( (mnItemDragPos != nOldItemDragPos) &&
+ (mnItemDragPos != nPos) &&
+ (mnItemDragPos != HEADERBAR_ITEM_NOTFOUND) )
+ {
+ Invalidate();
+ ImplInvertDrag( nPos, mnItemDragPos );
+ }
+ }
+
+ mbOutDrag = bNewOutDrag;
+ }
+ else
+ {
+ tools::Rectangle aItemRect = ImplGetItemRect( nPos );
+ if ( mnDragPos < aItemRect.Left() )
+ mnDragPos = aItemRect.Left();
+ if ( (mnDragPos < 0) || (mnDragPos > mnDX-1) )
+ HideTracking();
+ else
+ {
+ tools::Rectangle aSizeRect( mnDragPos, 0, mnDragPos, mnDragSize+mnDY );
+ ShowTracking( aSizeRect, ShowTrackFlags::Split );
+ }
+ }
+}
+
+void HeaderBar::ImplEndDrag( bool bCancel )
+{
+ HideTracking();
+
+ if ( bCancel || mbOutDrag )
+ {
+ if ( mbItemMode && (!mbOutDrag || mbItemDrag) )
+ {
+ Invalidate();
+ }
+
+ mnCurItemId = 0;
+ }
+ else
+ {
+ sal_uInt16 nPos = GetItemPos( mnCurItemId );
+ if ( mbItemMode )
+ {
+ if ( mbItemDrag )
+ {
+ SetPointer( PointerStyle::Arrow );
+ if ( (mnItemDragPos != nPos) &&
+ (mnItemDragPos != HEADERBAR_ITEM_NOTFOUND) )
+ {
+ ImplInvertDrag( nPos, mnItemDragPos );
+ MoveItem( mnCurItemId, mnItemDragPos );
+ }
+ else
+ Invalidate();
+ }
+ else
+ {
+ Select();
+ ImplUpdate( nPos );
+ }
+ }
+ else
+ {
+ tools::Long nDelta = mnDragPos - mnStartPos;
+ if ( nDelta )
+ {
+ auto& pItem = mvItemList[ nPos ];
+ pItem->mnSize += nDelta;
+ ImplUpdate( nPos, true );
+ }
+ }
+ }
+
+ mbDrag = false;
+ EndDrag();
+ mnCurItemId = 0;
+ mnItemDragPos = HEADERBAR_ITEM_NOTFOUND;
+ mbOutDrag = false;
+ mbItemMode = false;
+ mbItemDrag = false;
+}
+
+void HeaderBar::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( !rMEvt.IsLeft() )
+ return;
+
+ if ( rMEvt.GetClicks() == 2 )
+ {
+ tools::Long nTemp;
+ sal_uInt16 nPos;
+ sal_uInt16 nHitTest = ImplDoHitTest( rMEvt.GetPosPixel(), nTemp, nPos );
+ if ( nHitTest )
+ {
+ auto& pItem = mvItemList[ nPos ];
+ if ( nHitTest & HEAD_HITTEST_DIVIDER )
+ mbItemMode = false;
+ else
+ mbItemMode = true;
+ mnCurItemId = pItem->mnId;
+ DoubleClick();
+ mbItemMode = false;
+ mnCurItemId = 0;
+ }
+ }
+ else
+ ImplStartDrag( rMEvt.GetPosPixel(), false );
+}
+
+void HeaderBar::MouseMove( const MouseEvent& rMEvt )
+{
+ tools::Long nTemp1;
+ sal_uInt16 nTemp2;
+ PointerStyle eStyle = PointerStyle::Arrow;
+ sal_uInt16 nHitTest = ImplDoHitTest( rMEvt.GetPosPixel(), nTemp1, nTemp2 );
+
+ if ( nHitTest & HEAD_HITTEST_DIVIDER )
+ eStyle = PointerStyle::HSizeBar;
+ SetPointer( eStyle );
+}
+
+void HeaderBar::Tracking( const TrackingEvent& rTEvt )
+{
+ Point aMousePos = rTEvt.GetMouseEvent().GetPosPixel();
+
+ if ( rTEvt.IsTrackingEnded() )
+ ImplEndDrag( rTEvt.IsTrackingCanceled() );
+ else
+ ImplDrag( aMousePos );
+}
+
+void HeaderBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (mnBorderOff1 || mnBorderOff2)
+ {
+ rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetDarkShadowColor());
+ if (mnBorderOff1)
+ rRenderContext.DrawLine(Point(0, 0), Point(mnDX - 1, 0));
+ if (mnBorderOff2)
+ rRenderContext.DrawLine(Point(0, mnDY - 1), Point(mnDX - 1, mnDY - 1));
+ // #i40393# draw left and right border, if WB_BORDER was set in ImplInit()
+ if (mnBorderOff1 && mnBorderOff2)
+ {
+ rRenderContext.DrawLine(Point(0, 0), Point(0, mnDY - 1));
+ rRenderContext.DrawLine(Point(mnDX - 1, 0), Point(mnDX - 1, mnDY - 1));
+ }
+ }
+
+ sal_uInt16 nCurItemPos;
+ if (mbDrag)
+ nCurItemPos = GetItemPos(mnCurItemId);
+ else
+ nCurItemPos = HEADERBAR_ITEM_NOTFOUND;
+ sal_uInt16 nItemCount = static_cast<sal_uInt16>(mvItemList.size());
+ for (sal_uInt16 i = 0; i < nItemCount; i++)
+ ImplDrawItem(rRenderContext, i, (i == nCurItemPos), &rRect);
+}
+
+void HeaderBar::Draw( OutputDevice* pDev, const Point& rPos,
+ SystemTextColorFlags nFlags )
+{
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ tools::Rectangle aRect( aPos, aSize );
+ vcl::Font aFont = GetDrawPixelFont( pDev );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ if ( nFlags & SystemTextColorFlags::Mono )
+ pDev->SetTextColor( COL_BLACK );
+ else
+ pDev->SetTextColor( GetTextColor() );
+ pDev->SetTextFillColor();
+
+ // draw background
+ {
+ pDev->DrawWallpaper( aRect, GetBackground() );
+ if ( mnBorderOff1 || mnBorderOff2 )
+ {
+ pDev->SetLineColor( GetSettings().GetStyleSettings().GetDarkShadowColor() );
+ if ( mnBorderOff1 )
+ pDev->DrawLine( aRect.TopLeft(), Point( aRect.Right(), aRect.Top() ) );
+ if ( mnBorderOff2 )
+ pDev->DrawLine( Point( aRect.Left(), aRect.Bottom() ), Point( aRect.Right(), aRect.Bottom() ) );
+ // #i40393# draw left and right border, if WB_BORDER was set in ImplInit()
+ if ( mnBorderOff1 && mnBorderOff2 )
+ {
+ pDev->DrawLine( aRect.TopLeft(), Point( aRect.Left(), aRect.Bottom() ) );
+ pDev->DrawLine( Point( aRect.Right(), aRect.Top() ), Point( aRect.Right(), aRect.Bottom() ) );
+ }
+ }
+ }
+
+ tools::Rectangle aItemRect( aRect );
+ size_t nItemCount = mvItemList.size();
+ for ( size_t i = 0; i < nItemCount; i++ )
+ {
+ aItemRect.SetLeft( aRect.Left()+ImplGetItemPos( i ) );
+ aItemRect.SetRight( aItemRect.Left() + mvItemList[ i ]->mnSize - 1 );
+ // check for overflow on some systems
+ if ( aItemRect.Right() > 16000 )
+ aItemRect.SetRight( 16000 );
+ vcl::Region aRegion( aRect );
+ pDev->SetClipRegion( aRegion );
+ ImplDrawItem(*pDev, i, false, aItemRect, &aRect );
+ pDev->SetClipRegion();
+ }
+
+ pDev->Pop();
+}
+
+void HeaderBar::Resize()
+{
+ Size aSize = GetOutputSizePixel();
+ if ( IsVisible() && (mnDY != aSize.Height()) )
+ Invalidate();
+ mnDX = aSize.Width();
+ mnDY = aSize.Height();
+}
+
+void HeaderBar::Command( const CommandEvent& rCEvt )
+{
+ if ( rCEvt.IsMouseEvent() && (rCEvt.GetCommand() == CommandEventId::StartDrag) && !mbDrag )
+ {
+ ImplStartDrag( rCEvt.GetMousePosPixel(), true );
+ return;
+ }
+
+ Window::Command( rCEvt );
+}
+
+void HeaderBar::RequestHelp( const HelpEvent& rHEvt )
+{
+ sal_uInt16 nItemId = GetItemId( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) );
+ if ( nItemId )
+ {
+ if ( rHEvt.GetMode() & (HelpEventMode::QUICK | HelpEventMode::BALLOON) )
+ {
+ tools::Rectangle aItemRect = GetItemRect( nItemId );
+ Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
+ aItemRect.SetLeft( aPt.X() );
+ aItemRect.SetTop( aPt.Y() );
+ aPt = OutputToScreenPixel( aItemRect.BottomRight() );
+ aItemRect.SetRight( aPt.X() );
+ aItemRect.SetBottom( aPt.Y() );
+
+ OUString aStr = GetHelpText( nItemId );
+ if ( aStr.isEmpty() || !(rHEvt.GetMode() & HelpEventMode::BALLOON) )
+ {
+ auto& pItem = mvItemList[ GetItemPos( nItemId ) ];
+ // Quick-help is only displayed if the text is not fully visible.
+ // Otherwise we display Helptext only if the items do not contain text
+ if ( pItem->maOutText != pItem->maText )
+ aStr = pItem->maText;
+ else if (!pItem->maText.isEmpty())
+ aStr.clear();
+ }
+
+ if (!aStr.isEmpty())
+ {
+ if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
+ Help::ShowBalloon( this, aItemRect.Center(), aItemRect, aStr );
+ else
+ Help::ShowQuickHelp( this, aItemRect, aStr );
+ return;
+ }
+ }
+ }
+
+ Window::RequestHelp( rHEvt );
+}
+
+void HeaderBar::StateChanged( StateChangedType nType )
+{
+ Window::StateChanged( nType );
+
+ if ( nType == StateChangedType::Enable )
+ Invalidate();
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( true, false, false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false, true, false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( false, false, true );
+ Invalidate();
+ }
+}
+
+void HeaderBar::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Window::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings( true, true, true );
+ Invalidate();
+ }
+}
+
+void HeaderBar::EndDrag()
+{
+ maEndDragHdl.Call( this );
+}
+
+void HeaderBar::Select()
+{
+ maSelectHdl.Call( this );
+}
+
+void HeaderBar::DoubleClick()
+{
+}
+
+void HeaderBar::InsertItem( sal_uInt16 nItemId, const OUString& rText,
+ tools::Long nSize, HeaderBarItemBits nBits, sal_uInt16 nPos )
+{
+ DBG_ASSERT( nItemId, "HeaderBar::InsertItem(): ItemId == 0" );
+ DBG_ASSERT( GetItemPos( nItemId ) == HEADERBAR_ITEM_NOTFOUND,
+ "HeaderBar::InsertItem(): ItemId already exists" );
+
+ // create item and insert in the list
+ std::unique_ptr<ImplHeadItem> pItem(new ImplHeadItem);
+ pItem->mnId = nItemId;
+ pItem->mnBits = nBits;
+ pItem->mnSize = nSize;
+ pItem->maText = rText;
+ if ( nPos < mvItemList.size() ) {
+ auto it = mvItemList.begin();
+ it += nPos;
+ mvItemList.insert( it, std::move(pItem) );
+ } else {
+ mvItemList.push_back( std::move(pItem) );
+ }
+
+ // update display
+ ImplUpdate( nPos, true );
+}
+
+void HeaderBar::RemoveItem( sal_uInt16 nItemId )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+ if ( nPos != HEADERBAR_ITEM_NOTFOUND )
+ {
+ if ( nPos < mvItemList.size() ) {
+ auto it = mvItemList.begin();
+ it += nPos;
+ mvItemList.erase( it );
+ }
+ }
+}
+
+void HeaderBar::MoveItem( sal_uInt16 nItemId, sal_uInt16 nNewPos )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+ if ( nPos == HEADERBAR_ITEM_NOTFOUND )
+ return;
+
+ if ( nPos == nNewPos )
+ return;
+
+ auto it = mvItemList.begin();
+ it += nPos;
+ std::unique_ptr<ImplHeadItem> pItem = std::move(*it);
+ mvItemList.erase( it );
+ if ( nNewPos < nPos )
+ nPos = nNewPos;
+ it = mvItemList.begin();
+ it += nNewPos;
+ mvItemList.insert( it, std::move(pItem) );
+ ImplUpdate( nPos, true);
+}
+
+void HeaderBar::Clear()
+{
+ // delete all items
+ mvItemList.clear();
+
+ ImplUpdate( 0, true );
+}
+
+void HeaderBar::SetOffset( tools::Long nNewOffset )
+{
+ // tdf#129856 (see also #i40393#) invalidate old left and right border area if WB_BORDER was set in ImplInit()
+ if (mnBorderOff1 && mnBorderOff2)
+ {
+ Invalidate(tools::Rectangle(0, 0, 1, mnDY));
+ Invalidate(tools::Rectangle(mnDX - 1, 0, mnDX, mnDY));
+ }
+
+ // move area
+ tools::Rectangle aRect( 0, mnBorderOff1, mnDX-1, mnDY-mnBorderOff1-mnBorderOff2 );
+ tools::Long nDelta = mnOffset-nNewOffset;
+ mnOffset = nNewOffset;
+ Scroll( nDelta, 0, aRect );
+}
+
+sal_uInt16 HeaderBar::GetItemCount() const
+{
+ return static_cast<sal_uInt16>(mvItemList.size());
+}
+
+sal_uInt16 HeaderBar::GetItemPos( sal_uInt16 nItemId ) const
+{
+ for ( size_t i = 0, n = mvItemList.size(); i < n; ++i ) {
+ auto& pItem = mvItemList[ i ];
+ if ( pItem->mnId == nItemId )
+ return static_cast<sal_uInt16>(i);
+ }
+ return HEADERBAR_ITEM_NOTFOUND;
+}
+
+sal_uInt16 HeaderBar::GetItemId( sal_uInt16 nPos ) const
+{
+ ImplHeadItem* pItem = (nPos < mvItemList.size() ) ? mvItemList[ nPos ].get() : nullptr;
+ if ( pItem )
+ return pItem->mnId;
+ else
+ return 0;
+}
+
+sal_uInt16 HeaderBar::GetItemId( const Point& rPos ) const
+{
+ for ( size_t i = 0, n = mvItemList.size(); i < n; ++i ) {
+ if ( ImplGetItemRect( i ).Contains( rPos ) ) {
+ return GetItemId( i );
+ }
+ }
+ return 0;
+}
+
+tools::Rectangle HeaderBar::GetItemRect( sal_uInt16 nItemId ) const
+{
+ tools::Rectangle aRect;
+ sal_uInt16 nPos = GetItemPos( nItemId );
+ if ( nPos != HEADERBAR_ITEM_NOTFOUND )
+ aRect = ImplGetItemRect( nPos );
+ return aRect;
+}
+
+void HeaderBar::SetItemSize( sal_uInt16 nItemId, tools::Long nNewSize )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+ if ( nPos != HEADERBAR_ITEM_NOTFOUND )
+ {
+ auto& pItem = mvItemList[ nPos ];
+ if ( pItem->mnSize != nNewSize )
+ {
+ pItem->mnSize = nNewSize;
+ ImplUpdate( nPos, true );
+ }
+ }
+}
+
+tools::Long HeaderBar::GetItemSize( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+ if ( nPos != HEADERBAR_ITEM_NOTFOUND )
+ return mvItemList[ nPos ]->mnSize;
+ else
+ return 0;
+}
+
+void HeaderBar::SetItemBits( sal_uInt16 nItemId, HeaderBarItemBits nNewBits )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+ if ( nPos != HEADERBAR_ITEM_NOTFOUND )
+ {
+ auto& pItem = mvItemList[ nPos ];
+ if ( pItem->mnBits != nNewBits )
+ {
+ pItem->mnBits = nNewBits;
+ ImplUpdate( nPos );
+ }
+ }
+}
+
+HeaderBarItemBits HeaderBar::GetItemBits( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+ if ( nPos != HEADERBAR_ITEM_NOTFOUND )
+ return mvItemList[ nPos ]->mnBits;
+ else
+ return HeaderBarItemBits::NONE;
+}
+
+void HeaderBar::SetItemText( sal_uInt16 nItemId, const OUString& rText )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+ if ( nPos != HEADERBAR_ITEM_NOTFOUND )
+ {
+ mvItemList[ nPos ]->maText = rText;
+ ImplUpdate( nPos );
+ }
+}
+
+OUString HeaderBar::GetItemText( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+ if ( nPos != HEADERBAR_ITEM_NOTFOUND )
+ return mvItemList[ nPos ]->maText;
+ return OUString();
+}
+
+OUString HeaderBar::GetHelpText( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+ if ( nPos != HEADERBAR_ITEM_NOTFOUND )
+ {
+ auto& pItem = mvItemList[ nPos ];
+ if ( pItem->maHelpText.isEmpty() && !pItem->maHelpId.isEmpty() )
+ {
+ Help* pHelp = Application::GetHelp();
+ if ( pHelp )
+ pItem->maHelpText = pHelp->GetHelpText( OStringToOUString( pItem->maHelpId, RTL_TEXTENCODING_UTF8 ), this );
+ }
+
+ return pItem->maHelpText;
+ }
+
+ return OUString();
+}
+
+Size HeaderBar::CalcWindowSizePixel() const
+{
+ tools::Long nMaxImageSize = 0;
+ Size aSize( 0, GetTextHeight() );
+
+ for (auto& pItem : mvItemList)
+ {
+ // take image size into account
+ tools::Long nImageHeight = pItem->maImage.GetSizePixel().Height();
+ if ( !(pItem->mnBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE)) && !pItem->maText.isEmpty() )
+ nImageHeight += aSize.Height();
+ if ( nImageHeight > nMaxImageSize )
+ nMaxImageSize = nImageHeight;
+
+ // add width
+ aSize.AdjustWidth(pItem->mnSize );
+ }
+
+ if ( nMaxImageSize > aSize.Height() )
+ aSize.setHeight( nMaxImageSize );
+
+ // add border
+ if ( mbButtonStyle )
+ aSize.AdjustHeight(4 );
+ else
+ aSize.AdjustHeight(2 );
+ aSize.AdjustHeight(mnBorderOff1+mnBorderOff2 );
+
+ return aSize;
+}
+
+css::uno::Reference< css::accessibility::XAccessible > HeaderBar::CreateAccessible()
+{
+ if ( !mxAccessible.is() )
+ {
+ maCreateAccessibleHdl.Call( this );
+
+ if ( !mxAccessible.is() )
+ mxAccessible = Window::CreateAccessible();
+ }
+
+ return mxAccessible;
+}
+
+void HeaderBar::SetAccessible( const css::uno::Reference< css::accessibility::XAccessible >& _xAccessible )
+{
+ mxAccessible = _xAccessible;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/iconview.cxx b/vcl/source/treelist/iconview.cxx
new file mode 100644
index 000000000..7814810cb
--- /dev/null
+++ b/vcl/source/treelist/iconview.cxx
@@ -0,0 +1,315 @@
+/* -*- 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 <vcl/toolkit/treelistentry.hxx>
+#include <vcl/toolkit/viewdataentry.hxx>
+#include <iconview.hxx>
+#include "iconviewimpl.hxx"
+#include <vcl/accessiblefactory.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <tools/json_writer.hxx>
+#include <vcl/toolkit/svlbitm.hxx>
+#include <tools/stream.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <comphelper/base64.hxx>
+
+namespace
+{
+const int separatorHeight = 10;
+const int nSpacing = 5; // 5 pixels from top, from bottom, between icon and label
+}
+
+IconView::IconView(vcl::Window* pParent, WinBits nBits)
+ : SvTreeListBox(pParent, nBits)
+{
+ nColumns = 1;
+ mbCenterAndClipText = true;
+ SetEntryWidth(100);
+
+ pImpl.reset(new IconViewImpl(this, GetModel(), GetStyle()));
+}
+
+Size IconView::GetEntrySize(const SvTreeListEntry& entry) const
+{
+ if (entry.GetFlags() & SvTLEntryFlags::IS_SEPARATOR)
+ return { GetEntryWidth() * GetColumnsCount(), separatorHeight };
+ return { GetEntryWidth(), GetEntryHeight() };
+}
+
+void IconView::CalcEntryHeight(SvTreeListEntry const* pEntry)
+{
+ int nHeight = nSpacing * 2;
+ SvViewDataEntry* pViewData = GetViewDataEntry(pEntry);
+ const size_t nCount = pEntry->ItemCount();
+ bool bHasIcon = false;
+ for (size_t nCur = 0; nCur < nCount; ++nCur)
+ {
+ nHeight += SvLBoxItem::GetHeight(pViewData, nCur);
+
+ if (!bHasIcon && pEntry->GetItem(nCur).GetType() == SvLBoxItemType::ContextBmp)
+ bHasIcon = true;
+ }
+
+ if (bHasIcon && nCount > 1)
+ nHeight += nSpacing; // between icon and label
+
+ if (nHeight > nEntryHeight)
+ {
+ nEntryHeight = nHeight;
+ Control::SetFont(GetFont());
+ pImpl->SetEntryHeight();
+ }
+}
+
+void IconView::Resize()
+{
+ Size aBoxSize = Control::GetOutputSizePixel();
+
+ if (!aBoxSize.Width())
+ return;
+
+ nColumns = nEntryWidth ? aBoxSize.Width() / nEntryWidth : 1;
+
+ SvTreeListBox::Resize();
+}
+
+tools::Rectangle IconView::GetFocusRect(const SvTreeListEntry* pEntry, tools::Long)
+{
+ return { GetEntryPosition(pEntry), GetEntrySize(*pEntry) };
+}
+
+void IconView::PaintEntry(SvTreeListEntry& rEntry, tools::Long nX, tools::Long nY,
+ vcl::RenderContext& rRenderContext)
+{
+ pImpl->UpdateContextBmpWidthMax(&rEntry);
+
+ const Size entrySize = GetEntrySize(rEntry);
+ short nTempEntryHeight = entrySize.Height();
+ short nTempEntryWidth = entrySize.Width();
+
+ Point aEntryPos(nX, nY);
+
+ const Color aBackupTextColor(rRenderContext.GetTextColor());
+ const vcl::Font aBackupFont(rRenderContext.GetFont());
+ const Color aBackupColor = rRenderContext.GetFillColor();
+
+ const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ const Size aOutputSize = GetOutputSizePixel();
+ if (aOutputSize.getHeight() < nTempEntryHeight)
+ nTempEntryHeight = aOutputSize.getHeight();
+
+ const SvViewDataEntry* pViewDataEntry = GetViewDataEntry(&rEntry);
+
+ bool bCurFontIsSel = false;
+ if (pViewDataEntry->IsHighlighted())
+ {
+ vcl::Font aHighlightFont(rRenderContext.GetFont());
+ const Color aHighlightTextColor(rSettings.GetHighlightTextColor());
+ aHighlightFont.SetColor(aHighlightTextColor);
+
+ // set font color to highlight
+ rRenderContext.SetTextColor(aHighlightTextColor);
+ rRenderContext.SetFont(aHighlightFont);
+ bCurFontIsSel = true;
+ }
+
+ bool bFillColorSet = false;
+ // draw background
+ if (!(nTreeFlags & SvTreeFlags::USESEL))
+ {
+ // set background pattern/color
+ Wallpaper aWallpaper = rRenderContext.GetBackground();
+
+ if (pViewDataEntry->IsHighlighted())
+ {
+ Color aNewWallColor = rSettings.GetHighlightColor();
+ // if the face color is bright then the deactivate color is also bright
+ // -> so you can't see any deactivate selection
+ const WinBits nWindowStyle = GetStyle();
+ const bool bHideSelection = (nWindowStyle & WB_HIDESELECTION) != 0 && !HasFocus();
+ if (bHideSelection && !rSettings.GetFaceColor().IsBright()
+ && aWallpaper.GetColor().IsBright() != rSettings.GetDeactiveColor().IsBright())
+ {
+ aNewWallColor = rSettings.GetDeactiveColor();
+ }
+ aWallpaper.SetColor(aNewWallColor);
+ }
+ else // no selection
+ {
+ aWallpaper.SetColor(rEntry.GetBackColor());
+ }
+
+ Color aBackgroundColor = aWallpaper.GetColor();
+ if (aBackgroundColor != COL_TRANSPARENT)
+ {
+ rRenderContext.SetFillColor(aBackgroundColor);
+ bFillColorSet = true;
+ // this case may occur for smaller horizontal resizes
+ if (nTempEntryWidth > 1)
+ rRenderContext.DrawRect({ aEntryPos, Size(nTempEntryWidth, nTempEntryHeight) });
+ }
+ }
+
+ const size_t nItemCount = rEntry.ItemCount();
+ size_t nIconItem = nItemCount;
+
+ int nLabelHeight = 0;
+ std::vector<size_t> aTextItems;
+
+ for (size_t nCurItem = 0; nCurItem < nItemCount; ++nCurItem)
+ {
+ SvLBoxItem& rItem = rEntry.GetItem(nCurItem);
+ SvLBoxItemType nItemType = rItem.GetType();
+
+ if (nItemType == SvLBoxItemType::ContextBmp)
+ {
+ nIconItem = nCurItem;
+ continue;
+ }
+
+ aTextItems.push_back(nCurItem);
+ auto nItemHeight = SvLBoxItem::GetHeight(pViewDataEntry, nCurItem);
+ nLabelHeight += nItemHeight;
+ }
+
+ int nLabelYPos = nY + nTempEntryHeight - nLabelHeight - nSpacing; // padding from bottom
+ for (auto nCurItem : aTextItems)
+ {
+ aEntryPos.setY(nLabelYPos);
+
+ auto nItemHeight = SvLBoxItem::GetHeight(pViewDataEntry, nCurItem);
+ nLabelYPos += nItemHeight;
+
+ rEntry.GetItem(nCurItem).Paint(aEntryPos, *this, rRenderContext, pViewDataEntry, rEntry);
+ }
+
+ if (bFillColorSet)
+ rRenderContext.SetFillColor(aBackupColor);
+
+ // draw icon
+ if (nIconItem < nItemCount)
+ {
+ SvLBoxItem& rItem = rEntry.GetItem(nIconItem);
+ auto nItemWidth = rItem.GetWidth(this, pViewDataEntry, nIconItem);
+ auto nItemHeight = SvLBoxItem::GetHeight(pViewDataEntry, nIconItem);
+
+ aEntryPos.setY(nY);
+
+ // center horizontally
+ aEntryPos.AdjustX((nTempEntryWidth - nItemWidth) / 2);
+ // center vertically
+ int nImageAreaHeight = nTempEntryHeight - nSpacing * 2; // spacings from top, from bottom
+ if (nLabelHeight > 0)
+ {
+ nImageAreaHeight -= nLabelHeight + nSpacing; // spacing between icon and label
+ }
+ aEntryPos.AdjustY((nImageAreaHeight - nItemHeight) / 2 + nSpacing);
+
+ rItem.Paint(aEntryPos, *this, rRenderContext, pViewDataEntry, rEntry);
+ }
+
+ if (bCurFontIsSel)
+ {
+ rRenderContext.SetTextColor(aBackupTextColor);
+ rRenderContext.SetFont(aBackupFont);
+ }
+}
+
+css::uno::Reference<css::accessibility::XAccessible> IconView::CreateAccessible()
+{
+ if (vcl::Window* pParent = GetAccessibleParentWindow())
+ {
+ if (auto xAccParent = pParent->GetAccessible())
+ {
+ // need to be done here to get the vclxwindow later on in the accessible
+ css::uno::Reference<css::awt::XWindowPeer> xHoldAlive(GetComponentInterface());
+ return pImpl->m_aFactoryAccess.getFactory().createAccessibleIconView(*this, xAccParent);
+ }
+ }
+ return {};
+}
+
+OUString IconView::GetEntryAccessibleDescription(SvTreeListEntry* pEntry) const
+{
+ assert(pEntry);
+
+ if (maEntryAccessibleDescriptionHdl.IsSet())
+ return maEntryAccessibleDescriptionHdl.Call(pEntry);
+
+ return SvTreeListBox::GetEntryAccessibleDescription(pEntry);
+}
+
+FactoryFunction IconView::GetUITestFactory() const { return IconViewUIObject::create; }
+
+static OUString extractPngString(const SvLBoxContextBmp* pBmpItem)
+{
+ BitmapEx aImage = pBmpItem->GetBitmap1().GetBitmapEx();
+ SvMemoryStream aOStm(65535, 65535);
+ if (GraphicConverter::Export(aOStm, aImage, ConvertDataFormat::PNG) == ERRCODE_NONE)
+ {
+ css::uno::Sequence<sal_Int8> aSeq(static_cast<sal_Int8 const*>(aOStm.GetData()),
+ aOStm.Tell());
+ OUStringBuffer aBuffer("data:image/png;base64,");
+ ::comphelper::Base64::encode(aBuffer, aSeq);
+ return aBuffer.makeStringAndClear();
+ }
+
+ return "";
+}
+
+static void lcl_DumpEntryAndSiblings(tools::JsonWriter& rJsonWriter, SvTreeListEntry* pEntry,
+ const SvTreeListBox* pTabListBox)
+{
+ while (pEntry)
+ {
+ auto aNode = rJsonWriter.startStruct();
+
+ // simple listbox value
+ const SvLBoxItem* pIt = pEntry->GetFirstItem(SvLBoxItemType::String);
+ if (pIt)
+ rJsonWriter.put("text", static_cast<const SvLBoxString*>(pIt)->GetText());
+
+ pIt = pEntry->GetFirstItem(SvLBoxItemType::ContextBmp);
+ if (pIt)
+ {
+ const SvLBoxContextBmp* pBmpItem = static_cast<const SvLBoxContextBmp*>(pIt);
+ if (pBmpItem)
+ rJsonWriter.put("image", extractPngString(pBmpItem));
+ }
+
+ if (pTabListBox->IsSelected(pEntry))
+ rJsonWriter.put("selected", "true");
+
+ rJsonWriter.put("row",
+ OString::number(pTabListBox->GetModel()->GetAbsPos(pEntry)).getStr());
+
+ pEntry = pEntry->NextSibling();
+ }
+}
+
+void IconView::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ SvTreeListBox::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("type", "iconview");
+ auto aNode = rJsonWriter.startArray("entries");
+ lcl_DumpEntryAndSiblings(rJsonWriter, First(), this);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/iconviewimpl.cxx b/vcl/source/treelist/iconviewimpl.cxx
new file mode 100644
index 000000000..6839ff1db
--- /dev/null
+++ b/vcl/source/treelist/iconviewimpl.cxx
@@ -0,0 +1,741 @@
+/* -*- 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 <vcl/svapp.hxx>
+#include <vcl/toolkit/treelistentry.hxx>
+#include <tools/debug.hxx>
+#include <iconview.hxx>
+#include "iconviewimpl.hxx"
+
+IconViewImpl::IconViewImpl( SvTreeListBox* pTreeListBox, SvTreeList* pTreeList, WinBits nWinStyle )
+: SvImpLBox( pTreeListBox, pTreeList, nWinStyle )
+{
+}
+
+static bool IsSeparator(const SvTreeListEntry* entry)
+{
+ return entry && entry->GetFlags() & SvTLEntryFlags::IS_SEPARATOR;
+}
+
+Size IconViewImpl::GetEntrySize(const SvTreeListEntry& entry) const
+{
+ return static_cast<const IconView*>(m_pView.get())->GetEntrySize(entry);
+}
+
+void IconViewImpl::IterateVisibleEntryAreas(const IterateEntriesFunc& f, bool fromStartEntry) const
+{
+ tools::Long x = 0, y = 0;
+ short column = 0;
+ const tools::Long rowWidth = m_pView->GetEntryWidth() * m_pView->GetColumnsCount();
+ tools::Long nPrevHeight = 0;
+ for (auto entry = fromStartEntry ? m_pStartEntry : m_pView->FirstVisible(); entry;
+ entry = m_pView->NextVisible(entry))
+ {
+ const Size s = GetEntrySize(*entry);
+ if (x >= rowWidth || IsSeparator(entry))
+ {
+ column = 0;
+ x = 0;
+ y += nPrevHeight;
+ }
+ EntryAreaInfo info{ entry, column, tools::Rectangle{ Point{ x, y }, s } };
+ const auto result = f(info);
+ if (result == CallbackResult::Stop)
+ return;
+ ++column;
+ x += s.Width();
+ nPrevHeight = s.Height();
+ }
+}
+
+tools::Long IconViewImpl::GetEntryRow(const SvTreeListEntry* entry) const
+{
+ tools::Long nEntryRow = -1;
+ auto GetRow = [entry, &nEntryRow, row = -1](const EntryAreaInfo& info) mutable
+ {
+ if (info.column == 0 && !IsSeparator(info.entry))
+ ++row;
+ if (info.entry != entry)
+ return CallbackResult::Continue;
+ nEntryRow = row;
+ return CallbackResult::Stop;
+ };
+ IterateVisibleEntryAreas(GetRow);
+ return nEntryRow;
+}
+
+void IconViewImpl::SetStartEntry(SvTreeListEntry* entry)
+{
+ const tools::Long max = m_aVerSBar->GetRangeMax() - m_aVerSBar->GetVisibleSize();
+ tools::Long row = -1;
+ auto GetEntryAndRow = [&entry, &row, max, found = entry](const EntryAreaInfo& info) mutable
+ {
+ if (info.column == 0 && !IsSeparator(info.entry))
+ {
+ found = info.entry;
+ ++row;
+ }
+ if (row >= max || info.entry == entry)
+ {
+ entry = found;
+ return CallbackResult::Stop;
+ }
+ return CallbackResult::Continue;
+ };
+ IterateVisibleEntryAreas(GetEntryAndRow);
+
+ m_pStartEntry = entry;
+ m_aVerSBar->SetThumbPos(row);
+ m_pView->Invalidate(GetVisibleArea());
+}
+
+void IconViewImpl::ScrollTo(SvTreeListEntry* entry)
+{
+ if (!m_aVerSBar->IsVisible())
+ return;
+ const tools::Long entryRow = GetEntryRow(entry);
+ const tools::Long oldStartRow = m_aVerSBar->GetThumbPos();
+ if (entryRow < oldStartRow)
+ IconViewImpl::SetStartEntry(entry);
+ const tools::Long visibleRows = m_aVerSBar->GetVisibleSize();
+ const tools::Long posRelativeToBottom = entryRow - (oldStartRow + visibleRows) + 1;
+ if (posRelativeToBottom > 0)
+ IconViewImpl::SetStartEntry(GoToNextRow(m_pStartEntry, posRelativeToBottom));
+}
+
+SvTreeListEntry* IconViewImpl::GoToPrevRow(SvTreeListEntry* pEntry, int nRows) const
+{
+ SvTreeListEntry* pPrev = pEntry;
+ auto FindPrev = [this, pEntry, nRows, &pPrev,
+ prevs = std::vector<SvTreeListEntry*>()](const EntryAreaInfo& info) mutable
+ {
+ if (info.column == 0 && !IsSeparator(info.entry))
+ prevs.push_back(info.entry);
+ if (pEntry == info.entry)
+ {
+ if (prevs.size() > 1)
+ {
+ int i = std::max(0, static_cast<int>(prevs.size()) - nRows - 1);
+ pPrev = prevs[i];
+ for (short column = info.column; column; --column)
+ {
+ SvTreeListEntry* pNext = m_pView->NextVisible(pPrev);
+ if (!pNext || IsSeparator(pNext))
+ break;
+ pPrev = pNext;
+ }
+ }
+ return CallbackResult::Stop;
+ }
+ return CallbackResult::Continue;
+ };
+ IterateVisibleEntryAreas(FindPrev);
+
+ return pPrev;
+}
+
+SvTreeListEntry* IconViewImpl::GoToNextRow(SvTreeListEntry* pEntry, int nRows) const
+{
+ SvTreeListEntry* pNext = pEntry;
+ auto FindNext
+ = [pEntry, nRows, &pNext, column = -1](const EntryAreaInfo& info) mutable
+ {
+ if (info.column <= column && !IsSeparator(info.entry))
+ {
+ if (info.column == 0 && --nRows < 0)
+ return CallbackResult::Stop;
+ pNext = info.entry;
+ if (info.column == column && nRows == 0)
+ return CallbackResult::Stop;
+ }
+ else if (pEntry == info.entry)
+ {
+ column = info.column;
+ }
+ return CallbackResult::Continue;
+ };
+ IterateVisibleEntryAreas(FindNext);
+
+ return pNext;
+}
+
+void IconViewImpl::CursorUp()
+{
+ if (!m_pStartEntry)
+ return;
+
+ SvTreeListEntry* pPrevFirstToDraw = GoToPrevRow(m_pStartEntry, 1);
+
+ m_nFlags &= ~LBoxFlags::Filling;
+ ShowCursor( false );
+ SetStartEntry(pPrevFirstToDraw);
+ ShowCursor( true );
+ m_pView->NotifyScrolled();
+}
+
+void IconViewImpl::CursorDown()
+{
+ if (!m_pStartEntry)
+ return;
+
+ SvTreeListEntry* pNextFirstToDraw = GoToNextRow(m_pStartEntry, 1);
+
+ m_nFlags &= ~LBoxFlags::Filling;
+ ShowCursor( false );
+ SetStartEntry(pNextFirstToDraw);
+ ShowCursor( true );
+ m_pView->NotifyScrolled();
+}
+
+void IconViewImpl::PageDown( sal_uInt16 nDelta )
+{
+ if( !nDelta )
+ return;
+
+ if (!m_pStartEntry)
+ return;
+
+ SvTreeListEntry* pNext = GoToNextRow(m_pStartEntry, nDelta);
+
+ ShowCursor( false );
+
+ m_nFlags &= ~LBoxFlags::Filling;
+ SetStartEntry(pNext);
+
+ ShowCursor( true );
+}
+
+void IconViewImpl::PageUp( sal_uInt16 nDelta )
+{
+ if( !nDelta )
+ return;
+
+ if (!m_pStartEntry)
+ return;
+
+ SvTreeListEntry* pPrev = GoToPrevRow(m_pStartEntry, nDelta);
+
+ m_nFlags &= ~LBoxFlags::Filling;
+ ShowCursor( false );
+
+ SetStartEntry(pPrev);
+
+ ShowCursor( true );
+}
+
+void IconViewImpl::KeyDown( bool bPageDown )
+{
+ if( !m_aVerSBar->IsVisible() )
+ return;
+
+ tools::Long nDelta;
+ if( bPageDown )
+ nDelta = m_aVerSBar->GetPageSize();
+ else
+ nDelta = 1;
+
+ if( nDelta <= 0 )
+ return;
+
+ m_nFlags &= ~LBoxFlags::Filling;
+
+ if( bPageDown )
+ PageDown( static_cast<short>(nDelta) );
+ else
+ CursorDown();
+}
+
+void IconViewImpl::KeyUp( bool bPageUp )
+{
+ if( !m_aVerSBar->IsVisible() )
+ return;
+
+ tools::Long nDelta;
+ if( bPageUp )
+ nDelta = m_aVerSBar->GetPageSize();
+ else
+ nDelta = 1;
+
+ m_nFlags &= ~LBoxFlags::Filling;
+
+ if( bPageUp )
+ PageUp( static_cast<short>(nDelta) );
+ else
+ CursorUp();
+}
+
+tools::Long IconViewImpl::GetEntryLine(const SvTreeListEntry* pEntry) const
+{
+ if(!m_pStartEntry )
+ return -1; // invisible position
+
+ return IconViewImpl::GetEntryPosition(pEntry).Y();
+}
+
+Point IconViewImpl::GetEntryPosition(const SvTreeListEntry* pEntry) const
+{
+ Point result{ -m_pView->GetEntryWidth(), -m_pView->GetEntryHeight() }; // invisible
+ auto FindEntryPos = [pEntry, &result](const EntryAreaInfo& info)
+ {
+ if (pEntry == info.entry)
+ {
+ result = info.area.TopLeft();
+ return CallbackResult::Stop;
+ }
+ return CallbackResult::Continue;
+ };
+ IterateVisibleEntryAreas(FindEntryPos, true);
+
+ return result;
+}
+
+// Returns the last entry (in respective row) if position is just past the last entry
+SvTreeListEntry* IconViewImpl::GetClickedEntry( const Point& rPoint ) const
+{
+ DBG_ASSERT( m_pView->GetModel(), "IconViewImpl::GetClickedEntry: how can this ever happen?" );
+ if ( !m_pView->GetModel() )
+ return nullptr;
+ if( m_pView->GetEntryCount() == 0 || !m_pStartEntry || !m_pView->GetEntryHeight() || !m_pView->GetEntryWidth())
+ return nullptr;
+
+ SvTreeListEntry* pEntry = nullptr;
+ auto FindEntryByPos = [&pEntry, &rPoint](const EntryAreaInfo& info)
+ {
+ if (info.area.Contains(rPoint))
+ {
+ pEntry = info.entry;
+ return CallbackResult::Stop;
+ }
+ else if (info.area.Top() > rPoint.Y())
+ {
+ return CallbackResult::Stop; // we are already below the clicked row
+ }
+ else if (info.area.Bottom() > rPoint.Y())
+ {
+ pEntry = info.entry; // Same row; store the entry in case the click is past all entries
+ }
+ return CallbackResult::Continue;
+ };
+ IterateVisibleEntryAreas(FindEntryByPos, true);
+
+ return pEntry;
+}
+
+bool IconViewImpl::IsEntryInView( SvTreeListEntry* pEntry ) const
+{
+ // parent collapsed
+ if( !m_pView->IsEntryVisible(pEntry) )
+ return false;
+
+ tools::Long nY = GetEntryLine( pEntry );
+ if( nY < 0 )
+ return false;
+
+ tools::Long height = GetEntrySize(*pEntry).Height();
+ if (nY + height > m_aOutputSize.Height())
+ return false;
+
+ return true;
+}
+
+void IconViewImpl::AdjustScrollBars( Size& rSize )
+{
+ tools::Long nEntryHeight = m_pView->GetEntryHeight();
+ if( !nEntryHeight )
+ return;
+
+ sal_uInt16 nResult = 0;
+
+ Size aOSize( m_pView->Control::GetOutputSizePixel() );
+
+ const WinBits nWindowStyle = m_pView->GetStyle();
+ bool bVerSBar = ( nWindowStyle & WB_VSCROLL ) != 0;
+
+ // number of entries visible within the view
+ const tools::Long nVisibleRows = aOSize.Height() / nEntryHeight;
+ m_nVisibleCount = nVisibleRows * m_pView->GetColumnsCount();
+
+ tools::Long nTotalRows = 0;
+ tools::Long totalHeight = 0;
+ auto CountRowsAndHeight = [&nTotalRows, &totalHeight](const EntryAreaInfo& info)
+ {
+ totalHeight = std::max(totalHeight, info.area.Bottom());
+ if (info.column == 0 && !IsSeparator(info.entry))
+ ++nTotalRows;
+ return CallbackResult::Continue;
+ };
+ IterateVisibleEntryAreas(CountRowsAndHeight);
+
+ // do we need a vertical scrollbar?
+ if( bVerSBar || totalHeight > aOSize.Height())
+ {
+ nResult = 1;
+ }
+
+ // do we need a Horizontal scrollbar?
+ bool bHorSBar = (nWindowStyle & WB_HSCROLL) != 0;
+ if (bHorSBar || m_pView->GetEntryWidth() > aOSize.Width())
+ {
+ nResult += 2;
+ m_aHorSBar->SetRange(Range(0, m_pView->GetEntryWidth()));
+ m_aHorSBar->SetVisibleSize(aOSize.Width());
+ }
+
+ PositionScrollBars( aOSize, nResult );
+
+ // adapt Range, VisibleRange etc.
+
+ // refresh output size, in case we have to scroll
+ tools::Rectangle aRect;
+ aRect.SetSize( aOSize );
+ m_aSelEng.SetVisibleArea( aRect );
+
+ // vertical scrollbar
+ if( !m_bInVScrollHdl )
+ {
+ m_aVerSBar->SetRange(Range(0, nTotalRows));
+ m_aVerSBar->SetPageSize(nVisibleRows);
+ m_aVerSBar->SetVisibleSize(nVisibleRows);
+ }
+ else
+ {
+ m_nFlags |= LBoxFlags::EndScrollSetVisSize;
+ }
+
+ if( nResult & 0x0001 )
+ m_aVerSBar->Show();
+ else
+ m_aVerSBar->Hide();
+
+ if (nResult & 0x0002)
+ m_aHorSBar->Show();
+ else
+ m_aHorSBar->Hide();
+
+ rSize = aOSize;
+}
+
+// returns 0 if position is just past the last entry
+SvTreeListEntry* IconViewImpl::GetEntry( const Point& rPoint ) const
+{
+ if( (m_pView->GetEntryCount() == 0) || !m_pStartEntry ||
+ (rPoint.Y() > m_aOutputSize.Height())
+ || !m_pView->GetEntryHeight()
+ || !m_pView->GetEntryWidth())
+ return nullptr;
+
+ SvTreeListEntry* pEntry = nullptr;
+ auto FindEntryByPos = [&pEntry, &rPoint](const EntryAreaInfo& info)
+ {
+ if (info.area.Contains(rPoint))
+ {
+ pEntry = info.entry;
+ return CallbackResult::Stop;
+ }
+ else if (info.area.Top() > rPoint.Y())
+ {
+ return CallbackResult::Stop; // we are already below the clicked row
+ }
+ return CallbackResult::Continue;
+ };
+ IterateVisibleEntryAreas(FindEntryByPos, true);
+
+ return pEntry;
+}
+
+void IconViewImpl::SyncVerThumb()
+{
+ m_aVerSBar->SetThumbPos(GetEntryRow(m_pStartEntry));
+}
+
+void IconViewImpl::UpdateAll( bool bInvalidateCompleteView )
+{
+ FindMostRight();
+ SyncVerThumb();
+ FillView();
+ ShowVerSBar();
+ if( m_bSimpleTravel && m_pCursor && m_pView->HasFocus() )
+ m_pView->Select( m_pCursor );
+ ShowCursor( true );
+ if( bInvalidateCompleteView )
+ m_pView->Invalidate();
+ else
+ m_pView->Invalidate( GetVisibleArea() );
+}
+
+void IconViewImpl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (!m_pView->GetVisibleCount())
+ return;
+
+ m_nFlags |= LBoxFlags::InPaint;
+
+ if (m_nFlags & LBoxFlags::Filling)
+ {
+ SvTreeListEntry* pFirst = m_pView->First();
+ if (pFirst != m_pStartEntry)
+ {
+ ShowCursor(false);
+ m_pStartEntry = m_pView->First();
+ m_aVerSBar->SetThumbPos( 0 );
+ StopUserEvent();
+ ShowCursor(true);
+ m_nCurUserEvent = Application::PostUserEvent(LINK(this, SvImpLBox, MyUserEvent),
+ reinterpret_cast<void*>(1));
+ return;
+ }
+ }
+
+ if (!m_pStartEntry)
+ {
+ m_pStartEntry = m_pView->First();
+ }
+
+ if (!m_pCursor && !mbNoAutoCurEntry)
+ {
+ // do not select if multiselection or explicit set
+ bool bNotSelect = (m_aSelEng.GetSelectionMode() == SelectionMode::Multiple ) || ((m_nStyle & WB_NOINITIALSELECTION) == WB_NOINITIALSELECTION);
+ SetCursor(m_pStartEntry, bNotSelect);
+ }
+
+ auto PaintEntry = [iconView = static_cast<IconView*>(m_pView.get()), &rRect,
+ &rRenderContext](const EntryAreaInfo& info)
+ {
+ if (!info.area.GetIntersection(rRect).IsEmpty())
+ {
+ iconView->PaintEntry(*info.entry, info.area.Left(), info.area.Top(), rRenderContext);
+ }
+ else if (info.area.Top() > rRect.Bottom())
+ {
+ return CallbackResult::Stop; // we are already below the last visible row
+ }
+ return CallbackResult::Continue;
+ };
+ IterateVisibleEntryAreas(PaintEntry, true);
+
+ m_nFlags &= ~LBoxFlags::DeselectAll;
+ rRenderContext.SetClipRegion();
+ m_nFlags &= ~LBoxFlags::InPaint;
+}
+
+void IconViewImpl::InvalidateEntry( tools::Long nId ) const
+{
+ if( m_nFlags & LBoxFlags::InPaint )
+ return;
+ if (nId < 0)
+ return;
+
+ // nId is a Y coordinate of the top of the element, coming from GetEntryLine
+ tools::Rectangle aRect( GetVisibleArea() );
+ if (nId > aRect.Bottom())
+ return;
+ aRect.SetTop(nId); // Invalidate everything below
+ m_pView->Invalidate( aRect );
+}
+
+bool IconViewImpl::KeyInput( const KeyEvent& rKEvt )
+{
+ const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
+
+ if( rKeyCode.IsMod2() )
+ return false; // don't evaluate Alt key
+
+ m_nFlags &= ~LBoxFlags::Filling;
+
+ if( !m_pCursor )
+ m_pCursor = m_pStartEntry;
+ if( !m_pCursor )
+ return false;
+
+ sal_uInt16 aCode = rKeyCode.GetCode();
+
+ bool bShift = rKeyCode.IsShift();
+ bool bMod1 = rKeyCode.IsMod1();
+
+ SvTreeListEntry* pNewCursor;
+
+ bool bHandled = true;
+
+ switch( aCode )
+ {
+ case KEY_LEFT:
+ if( !IsEntryInView( m_pCursor ) )
+ MakeVisible( m_pCursor );
+
+ pNewCursor = m_pCursor;
+ do
+ {
+ pNewCursor = m_pView->PrevVisible(pNewCursor);
+ } while( pNewCursor && !IsSelectable(pNewCursor) );
+
+ // if there is no next entry, take the current one
+ // this ensures that in case of _one_ entry in the list, this entry is selected when pressing
+ // the cursor key
+ if (!pNewCursor)
+ pNewCursor = m_pCursor;
+
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on
+ if( !IsEntryInView( pNewCursor ) )
+ KeyUp( false );
+ break;
+
+ case KEY_RIGHT:
+ if( !IsEntryInView( m_pCursor ) )
+ MakeVisible( m_pCursor );
+
+ pNewCursor = m_pCursor;
+ do
+ {
+ pNewCursor = m_pView->NextVisible(pNewCursor);
+ } while( pNewCursor && !IsSelectable(pNewCursor) );
+
+ // if there is no next entry, take the current one
+ // this ensures that in case of _one_ entry in the list, this entry is selected when pressing
+ // the cursor key
+ if ( !pNewCursor && m_pCursor )
+ pNewCursor = m_pCursor;
+
+ if( pNewCursor )
+ {
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ if( IsEntryInView( pNewCursor ) )
+ SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on
+ else
+ {
+ if( m_pCursor )
+ m_pView->Select( m_pCursor, false );
+ KeyDown( false );
+ SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on
+ }
+ }
+ else
+ KeyDown( false ); // because scrollbar range might still
+ // allow scrolling
+ break;
+
+ case KEY_UP:
+ {
+ pNewCursor = GoToPrevRow(m_pCursor, 1);
+
+ if( pNewCursor )
+ {
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on
+ ScrollTo(pNewCursor);
+ }
+ break;
+ }
+
+ case KEY_DOWN:
+ {
+ pNewCursor = GoToNextRow(m_pCursor, 1);
+
+ if( pNewCursor )
+ {
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ ScrollTo(pNewCursor);
+ SetCursor(pNewCursor, bMod1); // no selection, when Ctrl is on
+ }
+ else
+ KeyDown( false ); // because scrollbar range might still
+ // allow scrolling
+ break;
+ }
+
+ case KEY_PAGEUP:
+ if (!bMod1)
+ {
+ const sal_uInt16 nDelta = m_aVerSBar->GetPageSize();
+ pNewCursor = GoToPrevRow(m_pCursor, nDelta);
+
+ if (pNewCursor)
+ {
+ m_aSelEng.CursorPosChanging(bShift, bMod1);
+ ScrollTo(pNewCursor);
+ SetCursor(pNewCursor);
+ }
+ }
+ else
+ bHandled = false;
+ break;
+
+ case KEY_PAGEDOWN:
+ if (!bMod1)
+ {
+ const sal_uInt16 nDelta = m_aVerSBar->GetPageSize();
+ pNewCursor = GoToNextRow(m_pCursor, nDelta);
+
+ if (pNewCursor)
+ {
+ m_aSelEng.CursorPosChanging(bShift, bMod1);
+ ScrollTo(pNewCursor);
+ SetCursor(pNewCursor);
+ }
+ else
+ KeyDown(false);
+ }
+ else
+ bHandled = false;
+ break;
+
+ case KEY_RETURN:
+ case KEY_SPACE:
+ {
+ bHandled = !m_pView->aDoubleClickHdl.Call(m_pView);
+ break;
+ }
+
+ case KEY_END:
+ {
+ pNewCursor = m_pView->GetModel()->Last();
+
+ while( pNewCursor && !IsSelectable(pNewCursor) )
+ {
+ pNewCursor = m_pView->PrevVisible(pNewCursor);
+ }
+
+ SetStartEntry(pNewCursor);
+
+ if( pNewCursor && pNewCursor != m_pCursor)
+ {
+// SelAllDestrAnch( false );
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ SetCursor( pNewCursor );
+ }
+
+ bHandled = true;
+
+ break;
+ }
+
+ default:
+ {
+ bHandled = false;
+ break;
+ }
+ }
+
+ if(!bHandled)
+ return SvImpLBox::KeyInput( rKEvt );
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/iconviewimpl.hxx b/vcl/source/treelist/iconviewimpl.hxx
new file mode 100644
index 000000000..c265ae5a2
--- /dev/null
+++ b/vcl/source/treelist/iconviewimpl.hxx
@@ -0,0 +1,91 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <svimpbox.hxx>
+
+class SvTreeListBox;
+class Point;
+
+class IconViewImpl : public SvImpLBox
+{
+public:
+ IconViewImpl(SvTreeListBox* pTreeListBox, SvTreeList* pTreeList, WinBits nWinStyle);
+
+ void KeyDown(bool bPageDown) override;
+
+ void KeyUp(bool bPageUp) override;
+
+ Point GetEntryPosition(const SvTreeListEntry* pEntry) const override;
+
+ SvTreeListEntry* GetClickedEntry(const Point& rPoint) const override;
+
+ bool IsEntryInView(SvTreeListEntry* pEntry) const override;
+
+ void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override;
+
+ // returns 0 if position is just past the last entry
+ SvTreeListEntry* GetEntry(const Point& rPoint) const override;
+
+ void UpdateAll(bool bInvalidateCompleteView) override;
+
+ bool KeyInput(const KeyEvent&) override;
+
+ void InvalidateEntry(tools::Long nId) const override;
+
+protected:
+ tools::Long GetEntryLine(const SvTreeListEntry* pEntry) const override;
+
+ void CursorUp() override;
+ void CursorDown() override;
+ void PageDown(sal_uInt16 nDelta) override;
+ void PageUp(sal_uInt16 nDelta) override;
+
+ void SyncVerThumb() override;
+ void AdjustScrollBars(Size& rSize) override;
+
+private:
+ enum class CallbackResult
+ {
+ Continue,
+ Stop, // Stop iteration
+ };
+ struct EntryAreaInfo
+ {
+ SvTreeListEntry* entry;
+ short column;
+ tools::Rectangle area; // The area for the entry
+ };
+ using IterateEntriesFunc = std::function<CallbackResult(const EntryAreaInfo&)>;
+
+ void IterateVisibleEntryAreas(const IterateEntriesFunc& f, bool fromStartEntry = false) const;
+
+ Size GetEntrySize(const SvTreeListEntry& entry) const;
+ // Get first entry at most n rows above; nullptr if no rows above
+ SvTreeListEntry* GoToPrevRow(SvTreeListEntry* pEntry, int n) const;
+ // Get first entry at most n rows below; nullptr if no rows below
+ SvTreeListEntry* GoToNextRow(SvTreeListEntry* pEntry, int n) const;
+
+ tools::Long GetEntryRow(const SvTreeListEntry* entry) const;
+ void SetStartEntry(SvTreeListEntry* entry);
+ void ScrollTo(SvTreeListEntry* entry);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/imap.cxx b/vcl/source/treelist/imap.cxx
new file mode 100644
index 000000000..84a0717c6
--- /dev/null
+++ b/vcl/source/treelist/imap.cxx
@@ -0,0 +1,994 @@
+/* -*- 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 <tools/urlobj.hxx>
+#include <tools/fract.hxx>
+#include <tools/GenericTypeSerializer.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/mapmod.hxx>
+#include <o3tl/numeric.hxx>
+#include <svl/urihelper.hxx>
+#include <vcl/imap.hxx>
+#include <vcl/imapobj.hxx>
+#include <vcl/imapcirc.hxx>
+#include <vcl/imaprect.hxx>
+#include <vcl/imappoly.hxx>
+
+#include <string.h>
+#include <math.h>
+#include <memory>
+#include <sal/log.hxx>
+
+
+#define SCALEPOINT(aPT,aFracX,aFracY) (aPT).setX(tools::Long((aPT).X()*aFracX)); \
+ (aPT).setY(tools::Long((aPT).Y()*aFracY));
+
+
+/******************************************************************************/
+
+
+IMapObject::IMapObject()
+ : bActive( false )
+ , nReadVersion( 0 )
+{
+}
+
+IMapObject::IMapObject( const OUString& rURL, const OUString& rAltText, const OUString& rDesc,
+ const OUString& rTarget, const OUString& rName, bool bURLActive )
+: aURL( rURL )
+, aAltText( rAltText )
+, aDesc( rDesc )
+, aTarget( rTarget )
+, aName( rName )
+, bActive( bURLActive )
+, nReadVersion( 0 )
+{
+}
+
+
+void IMapObject::Write( SvStream& rOStm ) const
+{
+ const rtl_TextEncoding eEncoding = osl_getThreadTextEncoding();
+
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(GetType()) );
+ rOStm.WriteUInt16( IMAP_OBJ_VERSION );
+ rOStm.WriteUInt16( eEncoding );
+
+ const OString aRelURL = OUStringToOString(
+ URIHelper::simpleNormalizedMakeRelative("", aURL), eEncoding);
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStm, aRelURL);
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStm, aAltText, eEncoding);
+ rOStm.WriteBool( bActive );
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStm, aTarget, eEncoding);
+
+ IMapCompat aCompat( rOStm, StreamMode::WRITE );
+
+ WriteIMapObject( rOStm );
+ aEventList.Write( rOStm ); // V4
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStm, aName, eEncoding); // V5
+}
+
+
+/******************************************************************************
+|*
+|* Binary import
+|*
+\******************************************************************************/
+
+void IMapObject::Read( SvStream& rIStm )
+{
+ rtl_TextEncoding nTextEncoding;
+
+ // read on type and version
+ rIStm.SeekRel( 2 );
+ rIStm.ReadUInt16( nReadVersion );
+ rIStm.ReadUInt16( nTextEncoding );
+ aURL = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStm, nTextEncoding);
+ aAltText = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStm, nTextEncoding);
+ rIStm.ReadCharAsBool( bActive );
+ aTarget = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStm, nTextEncoding);
+
+ // make URL absolute
+ aURL = URIHelper::SmartRel2Abs( INetURLObject(u""), aURL, URIHelper::GetMaybeFileHdl(), true, false, INetURLObject::EncodeMechanism::WasEncoded, INetURLObject::DecodeMechanism::Unambiguous );
+ IMapCompat aCompat( rIStm, StreamMode::READ );
+
+ ReadIMapObject( rIStm );
+
+ // from version 4 onwards we read an eventlist
+ if ( nReadVersion >= 0x0004 )
+ {
+ aEventList.Read(rIStm);
+
+ // from version 5 onwards an objectname could be available
+ if ( nReadVersion >= 0x0005 )
+ aName = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStm, nTextEncoding);
+ }
+}
+
+bool IMapObject::IsEqual( const IMapObject& rEqObj ) const
+{
+ return ( ( aURL == rEqObj.aURL ) &&
+ ( aAltText == rEqObj.aAltText ) &&
+ ( aDesc == rEqObj.aDesc ) &&
+ ( aTarget == rEqObj.aTarget ) &&
+ ( aName == rEqObj.aName ) &&
+ ( bActive == rEqObj.bActive ) );
+}
+
+IMapRectangleObject::IMapRectangleObject( const tools::Rectangle& rRect,
+ const OUString& rURL,
+ const OUString& rAltText,
+ const OUString& rDesc,
+ const OUString& rTarget,
+ const OUString& rName,
+ bool bURLActive,
+ bool bPixelCoords ) :
+ IMapObject ( rURL, rAltText, rDesc, rTarget, rName, bURLActive )
+{
+ ImpConstruct( rRect, bPixelCoords );
+}
+
+void IMapRectangleObject::ImpConstruct( const tools::Rectangle& rRect, bool bPixel )
+{
+ if ( bPixel )
+ aRect = Application::GetDefaultDevice()->PixelToLogic( rRect, MapMode( MapUnit::Map100thMM ) );
+ else
+ aRect = rRect;
+}
+
+
+/******************************************************************************
+|*
+|* Binary export
+|*
+\******************************************************************************/
+
+void IMapRectangleObject::WriteIMapObject( SvStream& rOStm ) const
+{
+ tools::GenericTypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(aRect);
+}
+
+
+/******************************************************************************
+|*
+|* Binary import
+|*
+\******************************************************************************/
+
+void IMapRectangleObject::ReadIMapObject( SvStream& rIStm )
+{
+ tools::GenericTypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(aRect);
+}
+
+
+/******************************************************************************
+|*
+|* return type
+|*
+\******************************************************************************/
+
+IMapObjectType IMapRectangleObject::GetType() const
+{
+ return IMapObjectType::Rectangle;
+}
+
+
+/******************************************************************************
+|*
+|* Hit test
+|*
+\******************************************************************************/
+
+bool IMapRectangleObject::IsHit( const Point& rPoint ) const
+{
+ return aRect.Contains( rPoint );
+}
+
+tools::Rectangle IMapRectangleObject::GetRectangle( bool bPixelCoords ) const
+{
+ tools::Rectangle aNewRect;
+
+ if ( bPixelCoords )
+ aNewRect = Application::GetDefaultDevice()->LogicToPixel( aRect, MapMode( MapUnit::Map100thMM ) );
+ else
+ aNewRect = aRect;
+
+ return aNewRect;
+}
+
+void IMapRectangleObject::Scale( const Fraction& rFracX, const Fraction& rFracY )
+{
+ Point aTL( aRect.TopLeft() );
+ Point aBR( aRect.BottomRight() );
+
+ if ( rFracX.GetDenominator() && rFracY.GetDenominator() )
+ {
+ SCALEPOINT( aTL, rFracX, rFracY );
+ SCALEPOINT( aBR, rFracX, rFracY );
+ }
+
+ aRect = tools::Rectangle( aTL, aBR );
+}
+
+bool IMapRectangleObject::IsEqual( const IMapRectangleObject& rEqObj ) const
+{
+ return ( IMapObject::IsEqual( rEqObj ) && ( aRect == rEqObj.aRect ) );
+}
+
+IMapCircleObject::IMapCircleObject( const Point& rCenter, sal_Int32 nCircleRadius,
+ const OUString& rURL,
+ const OUString& rAltText,
+ const OUString& rDesc,
+ const OUString& rTarget,
+ const OUString& rName,
+ bool bURLActive,
+ bool bPixelCoords ) :
+ IMapObject ( rURL, rAltText, rDesc, rTarget, rName, bURLActive )
+{
+ ImpConstruct( rCenter, nCircleRadius, bPixelCoords );
+}
+
+void IMapCircleObject::ImpConstruct( const Point& rCenter, sal_Int32 nRad, bool bPixel )
+{
+ if ( bPixel )
+ {
+ MapMode aMap100( MapUnit::Map100thMM );
+
+ aCenter = Application::GetDefaultDevice()->PixelToLogic( rCenter, aMap100 );
+ nRadius = Application::GetDefaultDevice()->PixelToLogic( Size( nRad, 0 ), aMap100 ).Width();
+ }
+ else
+ {
+ aCenter = rCenter;
+ nRadius = nRad;
+ }
+}
+
+
+/******************************************************************************
+|*
+|* Binary export
+|*
+\******************************************************************************/
+
+void IMapCircleObject::WriteIMapObject( SvStream& rOStm ) const
+{
+ sal_uInt32 nTmp = nRadius;
+ tools::GenericTypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(aCenter);
+ rOStm.WriteUInt32( nTmp );
+}
+
+
+/******************************************************************************
+|*
+|* Binary import
+|*
+\******************************************************************************/
+
+void IMapCircleObject::ReadIMapObject( SvStream& rIStm )
+{
+ sal_uInt32 nTmp;
+
+ tools::GenericTypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(aCenter);
+ rIStm.ReadUInt32( nTmp );
+
+ nRadius = nTmp;
+}
+
+
+/******************************************************************************
+|*
+|* return type
+|*
+\******************************************************************************/
+
+IMapObjectType IMapCircleObject::GetType() const
+{
+ return IMapObjectType::Circle;
+}
+
+
+/******************************************************************************
+|*
+|* Hit-Test
+|*
+\******************************************************************************/
+
+bool IMapCircleObject::IsHit( const Point& rPoint ) const
+{
+ const Point aPoint( aCenter - rPoint );
+ bool bRet = false;
+
+ if ( static_cast<sal_Int32>(sqrt( static_cast<double>(aPoint.X()) * aPoint.X() +
+ aPoint.Y() * aPoint.Y() )) <= nRadius )
+ {
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+Point IMapCircleObject::GetCenter( bool bPixelCoords ) const
+{
+ Point aNewPoint;
+
+ if ( bPixelCoords )
+ aNewPoint = Application::GetDefaultDevice()->LogicToPixel( aCenter, MapMode( MapUnit::Map100thMM ) );
+ else
+ aNewPoint = aCenter;
+
+ return aNewPoint;
+}
+
+sal_Int32 IMapCircleObject::GetRadius( bool bPixelCoords ) const
+{
+ sal_Int32 nNewRadius;
+
+ if ( bPixelCoords )
+ nNewRadius = Application::GetDefaultDevice()->LogicToPixel( Size( nRadius, 0 ), MapMode( MapUnit::Map100thMM ) ).Width();
+ else
+ nNewRadius = nRadius;
+
+ return nNewRadius;
+}
+
+void IMapCircleObject::Scale( const Fraction& rFracX, const Fraction& rFracY )
+{
+ Fraction aAverage( rFracX );
+
+ aAverage += rFracY;
+ aAverage *= Fraction( 1, 2 );
+
+ if ( rFracX.GetDenominator() && rFracY.GetDenominator() )
+ {
+ SCALEPOINT( aCenter, rFracX, rFracY );
+ }
+
+ if (!aAverage.GetDenominator())
+ throw o3tl::divide_by_zero();
+
+ nRadius = double(nRadius * aAverage);
+}
+
+bool IMapCircleObject::IsEqual( const IMapCircleObject& rEqObj ) const
+{
+ return ( IMapObject::IsEqual( rEqObj ) &&
+ ( aCenter == rEqObj.aCenter ) &&
+ ( nRadius == rEqObj.nRadius ) );
+}
+
+IMapPolygonObject::IMapPolygonObject( const tools::Polygon& rPoly,
+ const OUString& rURL,
+ const OUString& rAltText,
+ const OUString& rDesc,
+ const OUString& rTarget,
+ const OUString& rName,
+ bool bURLActive,
+ bool bPixelCoords ) :
+ IMapObject ( rURL, rAltText, rDesc, rTarget, rName, bURLActive ),
+ bEllipse ( false )
+{
+ ImpConstruct( rPoly, bPixelCoords );
+}
+
+void IMapPolygonObject::ImpConstruct( const tools::Polygon& rPoly, bool bPixel )
+{
+ if ( bPixel )
+ aPoly = Application::GetDefaultDevice()->PixelToLogic( rPoly, MapMode( MapUnit::Map100thMM ) );
+ else
+ aPoly = rPoly;
+}
+
+
+/******************************************************************************
+|*
+|* Binary export
+|*
+\******************************************************************************/
+
+void IMapPolygonObject::WriteIMapObject( SvStream& rOStm ) const
+{
+ tools::GenericTypeSerializer aSerializer(rOStm);
+ WritePolygon( rOStm, aPoly );
+ // Version 2
+ rOStm.WriteBool( bEllipse );
+ aSerializer.writeRectangle(aEllipse);
+}
+
+
+/******************************************************************************
+|*
+|* Binary import
+|*
+\******************************************************************************/
+
+void IMapPolygonObject::ReadIMapObject( SvStream& rIStm )
+{
+ ReadPolygon( rIStm, aPoly );
+
+ // Version >= 2 has additional ellipses information
+ if ( nReadVersion >= 2 )
+ {
+ rIStm.ReadCharAsBool( bEllipse );
+ tools::GenericTypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(aEllipse);
+ }
+}
+
+
+/******************************************************************************
+|*
+|* return type
+|*
+\******************************************************************************/
+
+IMapObjectType IMapPolygonObject::GetType() const
+{
+ return IMapObjectType::Polygon;
+}
+
+
+/******************************************************************************
+|*
+|* hit test
+|*
+\******************************************************************************/
+
+bool IMapPolygonObject::IsHit( const Point& rPoint ) const
+{
+ return aPoly.Contains( rPoint );
+}
+
+tools::Polygon IMapPolygonObject::GetPolygon( bool bPixelCoords ) const
+{
+ tools::Polygon aNewPoly;
+
+ if ( bPixelCoords )
+ aNewPoly = Application::GetDefaultDevice()->LogicToPixel( aPoly, MapMode( MapUnit::Map100thMM ) );
+ else
+ aNewPoly = aPoly;
+
+ return aNewPoly;
+}
+
+void IMapPolygonObject::SetExtraEllipse( const tools::Rectangle& rEllipse )
+{
+ if ( aPoly.GetSize() )
+ {
+ bEllipse = true;
+ aEllipse = rEllipse;
+ }
+}
+
+void IMapPolygonObject::Scale( const Fraction& rFracX, const Fraction& rFracY )
+{
+ sal_uInt16 nCount = aPoly.GetSize();
+
+ for ( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ Point aScaledPt( aPoly[ i ] );
+
+ if ( rFracX.GetDenominator() && rFracY.GetDenominator() )
+ {
+ SCALEPOINT( aScaledPt, rFracX, rFracY );
+ }
+
+ aPoly[ i ] = aScaledPt;
+ }
+
+ if ( !bEllipse )
+ return;
+
+ Point aTL( aEllipse.TopLeft() );
+ Point aBR( aEllipse.BottomRight() );
+
+ if ( rFracX.GetDenominator() && rFracY.GetDenominator() )
+ {
+ SCALEPOINT( aTL, rFracX, rFracY );
+ SCALEPOINT( aBR, rFracX, rFracY );
+ }
+
+ aEllipse = tools::Rectangle( aTL, aBR );
+}
+
+bool IMapPolygonObject::IsEqual( const IMapPolygonObject& rEqObj )
+{
+ bool bRet = false;
+
+ if ( IMapObject::IsEqual( rEqObj ) )
+ {
+ const tools::Polygon& rEqPoly = rEqObj.aPoly;
+ const sal_uInt16 nCount = aPoly.GetSize();
+ const sal_uInt16 nEqCount = rEqPoly.GetSize();
+
+ if ( nCount == nEqCount )
+ {
+ bool bDifferent = false;
+
+ for ( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ if ( aPoly[ i ] != rEqPoly[ i ] )
+ {
+ bDifferent = true;
+ break;
+ }
+ }
+
+ if ( !bDifferent )
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+/******************************************************************************
+|*
+|* Ctor
+|*
+\******************************************************************************/
+
+ImageMap::ImageMap( const OUString& rName )
+: aName( rName )
+{
+}
+
+
+/******************************************************************************
+|*
+|* Copy-Ctor
+|*
+\******************************************************************************/
+
+ImageMap::ImageMap( const ImageMap& rImageMap )
+{
+
+ size_t nCount = rImageMap.GetIMapObjectCount();
+
+ for ( size_t i = 0; i < nCount; i++ )
+ {
+ IMapObject* pCopyObj = rImageMap.GetIMapObject( i );
+
+ switch( pCopyObj->GetType() )
+ {
+ case IMapObjectType::Rectangle:
+ maList.emplace_back( new IMapRectangleObject( *static_cast<IMapRectangleObject*>( pCopyObj ) ) );
+ break;
+
+ case IMapObjectType::Circle:
+ maList.emplace_back( new IMapCircleObject( *static_cast<IMapCircleObject*>( pCopyObj ) ) );
+ break;
+
+ case IMapObjectType::Polygon:
+ maList.emplace_back( new IMapPolygonObject( *static_cast<IMapPolygonObject*>( pCopyObj ) ) );
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ aName = rImageMap.aName;
+}
+
+
+/******************************************************************************
+|*
+|* Dtor
+|*
+\******************************************************************************/
+
+ImageMap::~ImageMap()
+{
+}
+
+
+/******************************************************************************
+|*
+|* release internal memory
+|*
+\******************************************************************************/
+
+void ImageMap::ClearImageMap()
+{
+ maList.clear();
+
+ aName.clear();
+}
+
+
+/******************************************************************************
+|*
+|* assignment operator
+|*
+\******************************************************************************/
+
+ImageMap& ImageMap::operator=( const ImageMap& rImageMap )
+{
+ if (this != &rImageMap)
+ {
+ size_t nCount = rImageMap.GetIMapObjectCount();
+
+ ClearImageMap();
+
+ for ( size_t i = 0; i < nCount; i++ )
+ {
+ IMapObject* pCopyObj = rImageMap.GetIMapObject( i );
+
+ switch( pCopyObj->GetType() )
+ {
+ case IMapObjectType::Rectangle:
+ maList.emplace_back( new IMapRectangleObject( *static_cast<IMapRectangleObject*>(pCopyObj) ) );
+ break;
+
+ case IMapObjectType::Circle:
+ maList.emplace_back( new IMapCircleObject( *static_cast<IMapCircleObject*>(pCopyObj) ) );
+ break;
+
+ case IMapObjectType::Polygon:
+ maList.emplace_back( new IMapPolygonObject( *static_cast<IMapPolygonObject*>(pCopyObj) ) );
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ aName = rImageMap.aName;
+ }
+ return *this;
+}
+
+
+/******************************************************************************
+|*
+|* compare operator I
+|*
+\******************************************************************************/
+
+bool ImageMap::operator==( const ImageMap& rImageMap )
+{
+ const size_t nCount = maList.size();
+ const size_t nEqCount = rImageMap.GetIMapObjectCount();
+ bool bRet = false;
+
+ if ( nCount == nEqCount )
+ {
+ bool bDifferent = ( aName != rImageMap.aName );
+
+ for ( size_t i = 0; ( i < nCount ) && !bDifferent; i++ )
+ {
+ IMapObject* pObj = maList[ i ].get();
+ IMapObject* pEqObj = rImageMap.GetIMapObject( i );
+
+ if ( pObj->GetType() == pEqObj->GetType() )
+ {
+ switch( pObj->GetType() )
+ {
+ case IMapObjectType::Rectangle:
+ {
+ if ( ! static_cast<IMapRectangleObject*>(pObj)->IsEqual( *static_cast<IMapRectangleObject*>(pEqObj) ) )
+ bDifferent = true;
+ }
+ break;
+
+ case IMapObjectType::Circle:
+ {
+ if ( ! static_cast<IMapCircleObject*>(pObj)->IsEqual( *static_cast<IMapCircleObject*>(pEqObj) ) )
+ bDifferent = true;
+ }
+ break;
+
+ case IMapObjectType::Polygon:
+ {
+ if ( ! static_cast<IMapPolygonObject*>(pObj)->IsEqual( *static_cast<IMapPolygonObject*>(pEqObj) ) )
+ bDifferent = true;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ bDifferent = true;
+ }
+
+ if ( !bDifferent )
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+
+/******************************************************************************
+|*
+|* compare operator II
+|*
+\******************************************************************************/
+
+bool ImageMap::operator!=( const ImageMap& rImageMap )
+{
+ return !( *this == rImageMap );
+}
+
+
+/******************************************************************************
+|*
+|* insert new object
+|*
+\******************************************************************************/
+
+void ImageMap::InsertIMapObject( const IMapObject& rIMapObject )
+{
+ switch( rIMapObject.GetType() )
+ {
+ case IMapObjectType::Rectangle:
+ maList.emplace_back( new IMapRectangleObject( static_cast<const IMapRectangleObject&>( rIMapObject ) ) );
+ break;
+
+ case IMapObjectType::Circle:
+ maList.emplace_back( new IMapCircleObject( static_cast<const IMapCircleObject&>( rIMapObject ) ) );
+ break;
+
+ case IMapObjectType::Polygon:
+ maList.emplace_back( new IMapPolygonObject( static_cast<const IMapPolygonObject&>( rIMapObject ) ) );
+ break;
+
+ default:
+ break;
+ }
+}
+
+void ImageMap::InsertIMapObject( std::unique_ptr<IMapObject> pNewObject )
+{
+ maList.emplace_back( std::move(pNewObject) );
+}
+
+/******************************************************************************
+|*
+|* hit test
+|*
+\******************************************************************************/
+
+IMapObject* ImageMap::GetHitIMapObject( const Size& rTotalSize,
+ const Size& rDisplaySize,
+ const Point& rRelHitPoint,
+ sal_uLong nFlags )
+{
+ Point aRelPoint( rTotalSize.Width() * rRelHitPoint.X() / rDisplaySize.Width(),
+ rTotalSize.Height() * rRelHitPoint.Y() / rDisplaySize.Height() );
+
+ // transform point to check before checking if flags to mirror etc. are set,
+ if ( nFlags )
+ {
+ if ( nFlags & IMAP_MIRROR_HORZ )
+ aRelPoint.setX( rTotalSize.Width() - aRelPoint.X() );
+
+ if ( nFlags & IMAP_MIRROR_VERT )
+ aRelPoint.setY( rTotalSize.Height() - aRelPoint.Y() );
+ }
+
+ // walk over all objects and execute HitTest
+ IMapObject* pObj = nullptr;
+ for(const auto& i : maList) {
+ if ( i->IsHit( aRelPoint ) ) {
+ pObj = i.get();
+ break;
+ }
+ }
+
+ return( pObj ? ( pObj->IsActive() ? pObj : nullptr ) : nullptr );
+}
+
+void ImageMap::Scale( const Fraction& rFracX, const Fraction& rFracY )
+{
+ size_t nCount = maList.size();
+
+ for ( size_t i = 0; i < nCount; i++ )
+ {
+ IMapObject* pObj = maList[ i ].get();
+
+ switch( pObj->GetType() )
+ {
+ case IMapObjectType::Rectangle:
+ static_cast<IMapRectangleObject*>( pObj )->Scale( rFracX, rFracY );
+ break;
+
+ case IMapObjectType::Circle:
+ static_cast<IMapCircleObject*>( pObj )->Scale( rFracX, rFracY );
+ break;
+
+ case IMapObjectType::Polygon:
+ static_cast<IMapPolygonObject*>( pObj )->Scale( rFracX, rFracY );
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+
+/******************************************************************************
+|*
+|* sequentially write objects
+|*
+\******************************************************************************/
+
+void ImageMap::ImpWriteImageMap( SvStream& rOStm ) const
+{
+ size_t nCount = maList.size();
+
+ for ( size_t i = 0; i < nCount; i++ )
+ {
+ auto& pObj = maList[ i ];
+ pObj->Write( rOStm );
+ }
+}
+
+
+/******************************************************************************
+|*
+|* sequentially read objects
+|*
+\******************************************************************************/
+
+void ImageMap::ImpReadImageMap( SvStream& rIStm, size_t nCount )
+{
+ const size_t nMinRecordSize = 12; //circle, three 32bit numbers
+ const size_t nMaxRecords = rIStm.remainingSize() / nMinRecordSize;
+
+ if (nCount > nMaxRecords)
+ {
+ SAL_WARN("svtools.misc", "Parsing error: " << nMaxRecords << " max possible entries, but " <<
+ nCount << " claimed, truncating");
+ nCount = nMaxRecords;
+ }
+
+ // read new objects
+ for (size_t i = 0; i < nCount; ++i)
+ {
+ sal_uInt16 nType;
+
+ rIStm.ReadUInt16( nType );
+ rIStm.SeekRel( -2 );
+
+ switch( static_cast<IMapObjectType>(nType) )
+ {
+ case IMapObjectType::Rectangle:
+ {
+ IMapRectangleObject* pObj = new IMapRectangleObject;
+ pObj->Read( rIStm );
+ maList.emplace_back( pObj );
+ }
+ break;
+
+ case IMapObjectType::Circle:
+ {
+ IMapCircleObject* pObj = new IMapCircleObject;
+ pObj->Read( rIStm );
+ maList.emplace_back( pObj );
+ }
+ break;
+
+ case IMapObjectType::Polygon:
+ {
+ IMapPolygonObject* pObj = new IMapPolygonObject;
+ pObj->Read( rIStm );
+ maList.emplace_back( pObj );
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+
+/******************************************************************************
+|*
+|* store binary
+|*
+\******************************************************************************/
+
+void ImageMap::Write( SvStream& rOStm ) const
+{
+ IMapCompat* pCompat;
+ OUString aImageName( GetName() );
+ SvStreamEndian nOldFormat = rOStm.GetEndian();
+ sal_uInt16 nCount = static_cast<sal_uInt16>(GetIMapObjectCount());
+ const rtl_TextEncoding eEncoding = osl_getThreadTextEncoding(); //vomit!
+
+ rOStm.SetEndian( SvStreamEndian::LITTLE );
+
+ // write MagicCode
+ rOStm.WriteCharPtr( IMAPMAGIC );
+ rOStm.WriteUInt16( IMAGE_MAP_VERSION );
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStm, aImageName, eEncoding);
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStm, ""); //dummy
+ rOStm.WriteUInt16( nCount );
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStm, aImageName, eEncoding);
+
+ pCompat = new IMapCompat( rOStm, StreamMode::WRITE );
+
+ // here one can insert in newer versions
+
+ delete pCompat;
+
+ ImpWriteImageMap( rOStm );
+
+ rOStm.SetEndian( nOldFormat );
+}
+
+
+/******************************************************************************
+|*
+|* load binary
+|*
+\******************************************************************************/
+
+void ImageMap::Read( SvStream& rIStm )
+{
+ char cMagic[6];
+ SvStreamEndian nOldFormat = rIStm.GetEndian();
+
+ rIStm.SetEndian( SvStreamEndian::LITTLE );
+ rIStm.ReadBytes(cMagic, sizeof(cMagic));
+
+ if ( !memcmp( cMagic, IMAPMAGIC, sizeof( cMagic ) ) )
+ {
+ IMapCompat* pCompat;
+ sal_uInt16 nCount;
+
+ // delete old content
+ ClearImageMap();
+
+ // read on version
+ rIStm.SeekRel( 2 );
+
+ aName = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStm, osl_getThreadTextEncoding());
+ read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm); // Dummy
+ rIStm.ReadUInt16( nCount );
+ read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm); // Dummy
+
+ pCompat = new IMapCompat( rIStm, StreamMode::READ );
+
+ // here one can read in newer versions
+
+ delete pCompat;
+ ImpReadImageMap( rIStm, nCount );
+
+ }
+ else
+ rIStm.SetError( SVSTREAM_GENERALERROR );
+
+ rIStm.SetEndian( nOldFormat );
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/imap2.cxx b/vcl/source/treelist/imap2.cxx
new file mode 100644
index 000000000..980dae3ee
--- /dev/null
+++ b/vcl/source/treelist/imap2.cxx
@@ -0,0 +1,530 @@
+/* -*- 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 <comphelper/string.hxx>
+#include <string.h>
+#include <o3tl/string_view.hxx>
+#include <rtl/strbuf.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/svapp.hxx>
+#include <tools/urlobj.hxx>
+
+#include <svl/urihelper.hxx>
+#include <vcl/imap.hxx>
+#include <vcl/imapobj.hxx>
+#include <vcl/imaprect.hxx>
+#include <vcl/imapcirc.hxx>
+#include <vcl/imappoly.hxx>
+
+#include <math.h>
+
+#define NOTEOL(c) ((c)!='\0')
+
+void IMapObject::AppendCERNCoords(OStringBuffer& rBuf, const Point& rPoint100)
+{
+ const Point aPixPt( Application::GetDefaultDevice()->LogicToPixel( rPoint100, MapMode( MapUnit::Map100thMM ) ) );
+
+ rBuf.append('(');
+ rBuf.append(static_cast<sal_Int32>(aPixPt.X()));
+ rBuf.append(',');
+ rBuf.append(static_cast<sal_Int32>(aPixPt.Y()));
+ rBuf.append(") ");
+}
+
+void IMapObject::AppendNCSACoords(OStringBuffer& rBuf, const Point& rPoint100)
+{
+ const Point aPixPt( Application::GetDefaultDevice()->LogicToPixel( rPoint100, MapMode( MapUnit::Map100thMM ) ) );
+
+ rBuf.append(static_cast<sal_Int32>(aPixPt.X()));
+ rBuf.append(',');
+ rBuf.append(static_cast<sal_Int32>(aPixPt.Y()));
+ rBuf.append(' ');
+}
+
+void IMapObject::AppendCERNURL(OStringBuffer& rBuf) const
+{
+ rBuf.append(OUStringToOString(URIHelper::simpleNormalizedMakeRelative("", aURL), osl_getThreadTextEncoding()));
+}
+
+void IMapObject::AppendNCSAURL(OStringBuffer& rBuf) const
+{
+ rBuf.append(OUStringToOString(URIHelper::simpleNormalizedMakeRelative("", aURL), osl_getThreadTextEncoding()));
+ rBuf.append(' ');
+}
+
+void IMapRectangleObject::WriteCERN( SvStream& rOStm ) const
+{
+ OStringBuffer aStrBuf("rectangle ");
+
+ AppendCERNCoords(aStrBuf, aRect.TopLeft());
+ AppendCERNCoords(aStrBuf, aRect.BottomRight());
+ AppendCERNURL(aStrBuf);
+
+ rOStm.WriteLine(aStrBuf);
+}
+
+void IMapRectangleObject::WriteNCSA( SvStream& rOStm ) const
+{
+ OStringBuffer aStrBuf("rect ");
+
+ AppendNCSAURL(aStrBuf);
+ AppendNCSACoords(aStrBuf, aRect.TopLeft());
+ AppendNCSACoords(aStrBuf, aRect.BottomRight());
+
+ rOStm.WriteLine(aStrBuf);
+}
+
+void IMapCircleObject::WriteCERN( SvStream& rOStm ) const
+{
+ OStringBuffer aStrBuf("circle ");
+
+ AppendCERNCoords(aStrBuf, aCenter);
+ aStrBuf.append(nRadius);
+ aStrBuf.append(' ');
+ AppendCERNURL(aStrBuf);
+
+ rOStm.WriteLine(aStrBuf);
+}
+
+void IMapCircleObject::WriteNCSA( SvStream& rOStm ) const
+{
+ OStringBuffer aStrBuf("circle ");
+
+ AppendNCSAURL(aStrBuf);
+ AppendNCSACoords(aStrBuf, aCenter);
+ AppendNCSACoords(aStrBuf, aCenter + Point(nRadius, 0));
+
+ rOStm.WriteLine(aStrBuf);
+}
+
+void IMapPolygonObject::WriteCERN( SvStream& rOStm ) const
+{
+ OStringBuffer aStrBuf("polygon ");
+ const sal_uInt16 nCount = aPoly.GetSize();
+
+ for (sal_uInt16 i = 0; i < nCount; ++i)
+ AppendCERNCoords(aStrBuf, aPoly[i]);
+
+ AppendCERNURL(aStrBuf);
+
+ rOStm.WriteLine(aStrBuf);
+}
+
+void IMapPolygonObject::WriteNCSA( SvStream& rOStm ) const
+{
+ OStringBuffer aStrBuf("poly ");
+ const sal_uInt16 nCount = std::min( aPoly.GetSize(), sal_uInt16(100) );
+
+ AppendNCSAURL(aStrBuf);
+
+ for (sal_uInt16 i = 0; i < nCount; ++i)
+ AppendNCSACoords(aStrBuf, aPoly[i]);
+
+ rOStm.WriteLine(aStrBuf);
+}
+
+void ImageMap::Write( SvStream& rOStm, IMapFormat nFormat ) const
+{
+ switch( nFormat )
+ {
+ case IMapFormat::Binary : Write( rOStm ); break;
+ case IMapFormat::CERN : ImpWriteCERN( rOStm ); break;
+ case IMapFormat::NCSA : ImpWriteNCSA( rOStm ); break;
+
+ default:
+ break;
+ }
+}
+
+void ImageMap::ImpWriteCERN( SvStream& rOStm ) const
+{
+ size_t nCount = maList.size();
+
+ for ( size_t i = 0; i < nCount; i++ )
+ {
+ IMapObject* pObj = maList[ i ].get();
+
+ switch( pObj->GetType() )
+ {
+ case IMapObjectType::Rectangle:
+ static_cast<IMapRectangleObject*>( pObj )->WriteCERN( rOStm );
+ break;
+
+ case IMapObjectType::Circle:
+ static_cast<IMapCircleObject*>( pObj )->WriteCERN( rOStm );
+ break;
+
+ case IMapObjectType::Polygon:
+ static_cast<IMapPolygonObject*>( pObj )->WriteCERN( rOStm );
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+void ImageMap::ImpWriteNCSA( SvStream& rOStm ) const
+{
+ size_t nCount = maList.size();
+
+ for ( size_t i = 0; i < nCount; i++ )
+ {
+ IMapObject* pObj = maList[ i ].get();
+
+ switch( pObj->GetType() )
+ {
+ case IMapObjectType::Rectangle:
+ static_cast<IMapRectangleObject*>( pObj )->WriteNCSA( rOStm );
+ break;
+
+ case IMapObjectType::Circle:
+ static_cast<IMapCircleObject*>( pObj )->WriteNCSA( rOStm );
+ break;
+
+ case IMapObjectType::Polygon:
+ static_cast<IMapPolygonObject*>( pObj )->WriteNCSA( rOStm );
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+sal_uLong ImageMap::Read( SvStream& rIStm, IMapFormat nFormat )
+{
+ sal_uLong nRet = IMAP_ERR_FORMAT;
+
+ if ( nFormat == IMapFormat::Detect )
+ nFormat = ImpDetectFormat( rIStm );
+
+ switch ( nFormat )
+ {
+ case IMapFormat::Binary : Read( rIStm ); break;
+ case IMapFormat::CERN : ImpReadCERN( rIStm ); break;
+ case IMapFormat::NCSA : ImpReadNCSA( rIStm ); break;
+
+ default:
+ break;
+ }
+
+ if ( !rIStm.GetError() )
+ nRet = IMAP_ERR_OK;
+
+ return nRet;
+}
+
+void ImageMap::ImpReadCERN( SvStream& rIStm )
+{
+ // delete old content
+ ClearImageMap();
+
+ OStringBuffer aStr;
+ while ( rIStm.ReadLine( aStr ) )
+ ImpReadCERNLine( aStr );
+}
+
+void ImageMap::ImpReadCERNLine( std::string_view rLine )
+{
+ OString aStr( comphelper::string::stripStart(rLine, ' ') );
+ aStr = comphelper::string::stripStart(aStr, '\t');
+ aStr = aStr.replaceAll(";", "");
+ aStr = aStr.toAsciiLowerCase();
+
+ const char* pStr = aStr.getStr();
+ char cChar = *pStr++;
+
+ // find instruction
+ OStringBuffer aBuf;
+ while ((cChar >= 'a') && (cChar <= 'z'))
+ {
+ aBuf.append(cChar);
+ cChar = *pStr++;
+ }
+ OString aToken = aBuf.makeStringAndClear();
+
+ if ( !(NOTEOL( cChar )) )
+ return;
+
+ if ( ( aToken == "rectangle" ) || ( aToken == "rect" ) )
+ {
+ const Point aTopLeft( ImpReadCERNCoords( &pStr ) );
+ const Point aBottomRight( ImpReadCERNCoords( &pStr ) );
+ const OUString aURL( ImpReadCERNURL( &pStr ) );
+ const tools::Rectangle aRect( aTopLeft, aBottomRight );
+
+ maList.emplace_back( new IMapRectangleObject( aRect, aURL, OUString(), OUString(), OUString(), OUString() ) );
+ }
+ else if ( ( aToken == "circle" ) || ( aToken == "circ" ) )
+ {
+ const Point aCenter( ImpReadCERNCoords( &pStr ) );
+ const tools::Long nRadius = ImpReadCERNRadius( &pStr );
+ const OUString aURL( ImpReadCERNURL( &pStr ) );
+
+ maList.emplace_back( new IMapCircleObject( aCenter, nRadius, aURL, OUString(), OUString(), OUString(), OUString() ) );
+ }
+ else if ( ( aToken == "polygon" ) || ( aToken == "poly" ) )
+ {
+ const sal_uInt16 nCount = comphelper::string::getTokenCount(aStr, '(') - 1;
+ tools::Polygon aPoly( nCount );
+
+ for ( sal_uInt16 i = 0; i < nCount; i++ )
+ aPoly[ i ] = ImpReadCERNCoords( &pStr );
+
+ const OUString aURL = ImpReadCERNURL( &pStr );
+
+ maList.emplace_back( new IMapPolygonObject( aPoly, aURL, OUString(), OUString(), OUString(), OUString() ) );
+ }
+}
+
+Point ImageMap::ImpReadCERNCoords( const char** ppStr )
+{
+ OUStringBuffer aStrX;
+ OUStringBuffer aStrY;
+ Point aPt;
+ char cChar = *(*ppStr)++;
+
+ while( NOTEOL( cChar ) && ( ( cChar < '0' ) || ( cChar > '9' ) ) )
+ cChar = *(*ppStr)++;
+
+ if ( NOTEOL( cChar ) )
+ {
+ while( NOTEOL( cChar ) && ( cChar >= '0' ) && ( cChar <= '9' ) )
+ {
+ aStrX.append( cChar );
+ cChar = *(*ppStr)++;
+ }
+
+ if ( NOTEOL( cChar ) )
+ {
+ while( NOTEOL( cChar ) && ( ( cChar < '0' ) || ( cChar > '9' ) ) )
+ cChar = *(*ppStr)++;
+
+ while( NOTEOL( cChar ) && ( cChar >= '0' ) && ( cChar <= '9' ) )
+ {
+ aStrY.append( cChar );
+ cChar = *(*ppStr)++;
+ }
+
+ if ( NOTEOL( cChar ) )
+ while( NOTEOL( cChar ) && ( cChar != ')' ) )
+ cChar = *(*ppStr)++;
+
+ aPt = Point( o3tl::toInt32(aStrX), o3tl::toInt32(aStrY) );
+ }
+ }
+
+ return aPt;
+}
+
+tools::Long ImageMap::ImpReadCERNRadius( const char** ppStr )
+{
+ OUStringBuffer aStr;
+ char cChar = *(*ppStr)++;
+
+ while( NOTEOL( cChar ) && ( ( cChar < '0' ) || ( cChar > '9' ) ) )
+ cChar = *(*ppStr)++;
+
+ if ( NOTEOL( cChar ) )
+ {
+ while( NOTEOL( cChar ) && ( cChar >= '0' ) && ( cChar <= '9' ) )
+ {
+ aStr.append( cChar );
+ cChar = *(*ppStr)++;
+ }
+ }
+
+ return o3tl::toInt32(aStr);
+}
+
+OUString ImageMap::ImpReadCERNURL( const char** ppStr )
+{
+ OUString aStr(OUString::createFromAscii(*ppStr));
+
+ aStr = comphelper::string::strip(aStr, ' ');
+ aStr = comphelper::string::strip(aStr, '\t');
+
+ return INetURLObject::GetAbsURL( u"", aStr );
+}
+
+void ImageMap::ImpReadNCSA( SvStream& rIStm )
+{
+ // delete old content
+ ClearImageMap();
+
+ OStringBuffer aStr;
+ while ( rIStm.ReadLine( aStr ) )
+ ImpReadNCSALine( aStr );
+}
+
+void ImageMap::ImpReadNCSALine( std::string_view rLine )
+{
+ OString aStr( comphelper::string::stripStart(rLine, ' ') );
+ aStr = comphelper::string::stripStart(aStr, '\t');
+ aStr = aStr.replaceAll(";", "");
+ aStr = aStr.toAsciiLowerCase();
+
+ const char* pStr = aStr.getStr();
+ char cChar = *pStr++;
+
+ // find instruction
+ OStringBuffer aBuf;
+ while ((cChar >= 'a') && (cChar <= 'z'))
+ {
+ aBuf.append(cChar);
+ cChar = *pStr++;
+ }
+ OString aToken = aBuf.makeStringAndClear();
+
+ if ( !(NOTEOL( cChar )) )
+ return;
+
+ if ( aToken == "rect" )
+ {
+ const OUString aURL( ImpReadNCSAURL( &pStr ) );
+ const Point aTopLeft( ImpReadNCSACoords( &pStr ) );
+ const Point aBottomRight( ImpReadNCSACoords( &pStr ) );
+ const tools::Rectangle aRect( aTopLeft, aBottomRight );
+
+ maList.emplace_back( new IMapRectangleObject( aRect, aURL, OUString(), OUString(), OUString(), OUString() ) );
+ }
+ else if ( aToken == "circle" )
+ {
+ const OUString aURL( ImpReadNCSAURL( &pStr ) );
+ const Point aCenter( ImpReadNCSACoords( &pStr ) );
+ const Point aDX( aCenter - ImpReadNCSACoords( &pStr ) );
+ tools::Long nRadius = static_cast<tools::Long>(sqrt( static_cast<double>(aDX.X()) * aDX.X() +
+ static_cast<double>(aDX.Y()) * aDX.Y() ));
+
+ maList.emplace_back( new IMapCircleObject( aCenter, nRadius, aURL, OUString(), OUString(), OUString(), OUString() ) );
+ }
+ else if ( aToken == "poly" )
+ {
+ const sal_uInt16 nCount = comphelper::string::getTokenCount(aStr, ',') - 1;
+ const OUString aURL( ImpReadNCSAURL( &pStr ) );
+ tools::Polygon aPoly( nCount );
+
+ for ( sal_uInt16 i = 0; i < nCount; i++ )
+ aPoly[ i ] = ImpReadNCSACoords( &pStr );
+
+ maList.emplace_back( new IMapPolygonObject( aPoly, aURL, OUString(), OUString(), OUString(), OUString() ) );
+ }
+}
+
+OUString ImageMap::ImpReadNCSAURL( const char** ppStr )
+{
+ OUStringBuffer aStr;
+ char cChar = *(*ppStr)++;
+
+ while( NOTEOL( cChar ) && ( ( cChar == ' ' ) || ( cChar == '\t' ) ) )
+ cChar = *(*ppStr)++;
+
+ if ( NOTEOL( cChar ) )
+ {
+ while( NOTEOL( cChar ) && ( cChar != ' ' ) && ( cChar != '\t' ) )
+ {
+ aStr.append( cChar );
+ cChar = *(*ppStr)++;
+ }
+ }
+
+ return INetURLObject::GetAbsURL( u"", aStr.makeStringAndClear() );
+}
+
+Point ImageMap::ImpReadNCSACoords( const char** ppStr )
+{
+ OUStringBuffer aStrX;
+ OUStringBuffer aStrY;
+ Point aPt;
+ char cChar = *(*ppStr)++;
+
+ while( NOTEOL( cChar ) && ( ( cChar < '0' ) || ( cChar > '9' ) ) )
+ cChar = *(*ppStr)++;
+
+ if ( NOTEOL( cChar ) )
+ {
+ while( NOTEOL( cChar ) && ( cChar >= '0' ) && ( cChar <= '9' ) )
+ {
+ aStrX.append( cChar );
+ cChar = *(*ppStr)++;
+ }
+
+ if ( NOTEOL( cChar ) )
+ {
+ while( NOTEOL( cChar ) && ( ( cChar < '0' ) || ( cChar > '9' ) ) )
+ cChar = *(*ppStr)++;
+
+ while( NOTEOL( cChar ) && ( cChar >= '0' ) && ( cChar <= '9' ) )
+ {
+ aStrY.append( cChar );
+ cChar = *(*ppStr)++;
+ }
+
+ aPt = Point( o3tl::toInt32(aStrX), o3tl::toInt32(aStrY) );
+ }
+ }
+
+ return aPt;
+}
+
+IMapFormat ImageMap::ImpDetectFormat( SvStream& rIStm )
+{
+ sal_uInt64 nPos = rIStm.Tell();
+ IMapFormat nRet = IMapFormat::Binary;
+ char cMagic[6];
+
+ rIStm.ReadBytes(cMagic, sizeof(cMagic));
+
+ // if we do not have an internal formats
+ // we check the format
+ if ( memcmp( cMagic, IMAPMAGIC, sizeof( cMagic ) ) )
+ {
+ tools::Long nCount = 128;
+
+ rIStm.Seek( nPos );
+ OString aStr;
+ while ( rIStm.ReadLine( aStr ) && nCount-- )
+ {
+ aStr = aStr.toAsciiLowerCase();
+
+ if ( (aStr.indexOf("rect") != -1) ||
+ (aStr.indexOf("circ") != -1) ||
+ (aStr.indexOf("poly") != -1) )
+ {
+ if ( ( aStr.indexOf('(') != -1 ) &&
+ ( aStr.indexOf(')') != -1 ) )
+ {
+ nRet = IMapFormat::CERN;
+ }
+ else
+ nRet = IMapFormat::NCSA;
+
+ break;
+ }
+ }
+ }
+
+ rIStm.Seek( nPos );
+
+ return nRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/imap3.cxx b/vcl/source/treelist/imap3.cxx
new file mode 100644
index 000000000..073725f34
--- /dev/null
+++ b/vcl/source/treelist/imap3.cxx
@@ -0,0 +1,84 @@
+/* -*- 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 <vcl/imap.hxx>
+
+#include <tools/debug.hxx>
+
+/******************************************************************************
+|*
+|* Ctor
+|*
+\******************************************************************************/
+
+IMapCompat::IMapCompat(SvStream& rStm, const StreamMode nStreamMode)
+ : pRWStm(&rStm)
+ , nCompatPos(0)
+ , nTotalSize(0)
+ , nStmMode(nStreamMode)
+{
+ DBG_ASSERT(nStreamMode == StreamMode::READ || nStreamMode == StreamMode::WRITE, "Wrong Mode!");
+
+ if (pRWStm->GetError())
+ return;
+
+ if (nStmMode == StreamMode::WRITE)
+ {
+ nCompatPos = pRWStm->Tell();
+ pRWStm->SeekRel(4);
+ nTotalSize = nCompatPos + 4;
+ }
+ else
+ {
+ sal_uInt32 nTotalSizeTmp;
+ pRWStm->ReadUInt32(nTotalSizeTmp);
+ nTotalSize = nTotalSizeTmp;
+ nCompatPos = pRWStm->Tell();
+ }
+}
+
+/******************************************************************************
+|*
+|* Dtor
+|*
+\******************************************************************************/
+
+IMapCompat::~IMapCompat()
+{
+ if (pRWStm->GetError())
+ return;
+
+ if (nStmMode == StreamMode::WRITE)
+ {
+ const sal_uInt64 nEndPos = pRWStm->Tell();
+
+ pRWStm->Seek(nCompatPos);
+ pRWStm->WriteUInt32(nEndPos - nTotalSize);
+ pRWStm->Seek(nEndPos);
+ }
+ else
+ {
+ const sal_uInt64 nReadSize = pRWStm->Tell() - nCompatPos;
+
+ if (nTotalSize > nReadSize)
+ pRWStm->SeekRel(nTotalSize - nReadSize);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/inetimg.cxx b/vcl/source/treelist/inetimg.cxx
new file mode 100644
index 000000000..35c795cb0
--- /dev/null
+++ b/vcl/source/treelist/inetimg.cxx
@@ -0,0 +1,136 @@
+/* -*- 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 <osl/thread.h>
+#include <sot/formats.hxx>
+#include <tools/stream.hxx>
+
+#include <vcl/inetimg.hxx>
+#include <o3tl/string_view.hxx>
+
+const sal_Unicode TOKEN_SEPARATOR = '\001';
+
+void INetImage::Write( SvStream& rOStm, SotClipboardFormatId nFormat ) const
+{
+ switch( nFormat )
+ {
+ case SotClipboardFormatId::INET_IMAGE:
+ {
+ OUString sString(
+ aImageURL + OUStringChar(TOKEN_SEPARATOR) + aTargetURL
+ + OUStringChar(TOKEN_SEPARATOR) + aTargetFrame
+ + OUStringChar(TOKEN_SEPARATOR) /* + aAlternateText */
+ + OUStringChar(TOKEN_SEPARATOR)
+ + OUString::number(aSizePixel.Width())
+ + OUStringChar(TOKEN_SEPARATOR)
+ + OUString::number(aSizePixel.Height()));
+
+ OString sOut(OUStringToOString(sString,
+ RTL_TEXTENCODING_UTF8));
+
+ rOStm.WriteBytes(sOut.getStr(), sOut.getLength());
+ static const char aEndChar[2] = { 0 };
+ rOStm.WriteBytes(aEndChar, sizeof(aEndChar));
+ }
+ break;
+
+ case SotClipboardFormatId::NETSCAPE_IMAGE:
+ break;
+ default: break;
+ }
+}
+
+bool INetImage::Read( SvStream& rIStm, SotClipboardFormatId nFormat )
+{
+ bool bRet = false;
+ switch( nFormat )
+ {
+ case SotClipboardFormatId::INET_IMAGE:
+ {
+ OUString sINetImg = read_zeroTerminated_uInt8s_ToOUString(rIStm, RTL_TEXTENCODING_UTF8);
+ sal_Int32 nStart = 0;
+ aImageURL = sINetImg.getToken( 0, TOKEN_SEPARATOR, nStart );
+ aTargetURL = sINetImg.getToken( 0, TOKEN_SEPARATOR, nStart );
+ aTargetFrame = sINetImg.getToken( 0, TOKEN_SEPARATOR, nStart );
+ /*aAlternateText =*/ sINetImg.getToken( 0, TOKEN_SEPARATOR, nStart );
+ aSizePixel.setWidth( o3tl::toInt32(o3tl::getToken(sINetImg, 0, TOKEN_SEPARATOR,
+ nStart )) );
+ aSizePixel.setHeight(o3tl::toInt32(o3tl::getToken( sINetImg, 0, TOKEN_SEPARATOR,
+ nStart )) );
+ bRet = !sINetImg.isEmpty();
+ }
+ break;
+
+ case SotClipboardFormatId::NETSCAPE_IMAGE:
+ {
+/*
+ --> structure size MUST - alignment of 4!
+ int iSize; // size of all data, including variable length strings
+ sal_Bool bIsMap; // For server side maps
+ sal_Int32 iWidth; // Fixed size data correspond to fields in LO_ImageDataStruct
+ sal_Int32 iHeight; // and EDT_ImageData
+ sal_Int32 iHSpace;
+ sal_Int32 iVSpace;
+ sal_Int32 iBorder;
+ int iLowResOffset; // Offsets into string_data. If 0, string is NULL (not used)
+ int iAltOffset; // (alternate text?)
+ int iAnchorOffset; // HREF in image
+ int iExtraHTML_Offset; // Extra HTML (stored in CImageElement)
+ char pImageURL[1]; // Append all variable-length strings starting here
+*/
+ rtl_TextEncoding eSysCSet = osl_getThreadTextEncoding();
+ sal_Int32 nVal, nAnchorOffset, nAltOffset;
+ sal_uInt64 nFilePos;
+
+ nFilePos = rIStm.Tell();
+ // skip over iSize (int), bIsMao ( sal_Bool ) alignment of 4 !!!!
+ rIStm.SeekRel( 8 );
+ rIStm.ReadInt32( nVal ); aSizePixel.setWidth( nVal );
+ rIStm.ReadInt32( nVal ); aSizePixel.setHeight( nVal );
+ // skip over iHSpace, iVSpace, iBorder, iLowResOffset
+ rIStm.SeekRel( 3 * sizeof( sal_Int32 ) + sizeof( int ) );
+ rIStm.ReadInt32( nAltOffset );
+ rIStm.ReadInt32( nAnchorOffset );
+ // skip over iExtraHTML_Offset
+ rIStm.SeekRel( sizeof( int ) );
+
+ aImageURL = read_zeroTerminated_uInt8s_ToOUString(rIStm, eSysCSet);
+ if( nAltOffset )
+ {
+ rIStm.Seek( nFilePos + nAltOffset );
+ /*aAlternateText =*/ read_zeroTerminated_uInt8s_ToOUString(rIStm, eSysCSet);
+ }
+
+ if( nAnchorOffset )
+ {
+ rIStm.Seek( nFilePos + nAnchorOffset );
+ aTargetURL = read_zeroTerminated_uInt8s_ToOUString(rIStm, eSysCSet);
+ }
+ else if( !aTargetURL.isEmpty() )
+ aTargetURL.clear();
+
+ bRet = ERRCODE_NONE == rIStm.GetError();
+ }
+ break;
+ default: break;
+ }
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/svimpbox.cxx b/vcl/source/treelist/svimpbox.cxx
new file mode 100644
index 000000000..e75a13945
--- /dev/null
+++ b/vcl/source/treelist/svimpbox.cxx
@@ -0,0 +1,3142 @@
+/* -*- 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 <o3tl/safeint.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/help.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/commandevent.hxx>
+
+#include <cstdlib>
+#include <memory>
+#include <stack>
+
+#include <vcl/toolkit/treelistbox.hxx>
+#include <vcl/toolkit/svlbitm.hxx>
+#include <tools/wintypes.hxx>
+#include <bitmaps.hlst>
+#include <svimpbox.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/toolkit/treelistentry.hxx>
+#include <vcl/toolkit/viewdataentry.hxx>
+
+// #i27063# (pl), #i32300# (pb) never access VCL after DeInitVCL - also no destructors
+Image* SvImpLBox::s_pDefCollapsed = nullptr;
+Image* SvImpLBox::s_pDefExpanded = nullptr;
+oslInterlockedCount SvImpLBox::s_nImageRefCount = 0;
+
+SvImpLBox::SvImpLBox( SvTreeListBox* pLBView, SvTreeList* pLBTree, WinBits nWinStyle)
+ : m_aScrBarBox(VclPtr<ScrollBarBox>::Create(pLBView))
+ , m_aFctSet(this, pLBView)
+ , mbForceMakeVisible (false)
+ , m_aEditIdle("SvImpLBox m_aEditIdle")
+ , m_aHorSBar(VclPtr<ScrollBar>::Create(pLBView, WB_DRAG | WB_HSCROLL))
+ , m_aVerSBar(VclPtr<ScrollBar>::Create(pLBView, WB_DRAG | WB_VSCROLL))
+ , m_aOutputSize(0, 0)
+ , mbNoAutoCurEntry(false)
+ , m_aSelEng(pLBView, nullptr)
+ , m_nNextVerVisSize(0)
+{
+ osl_atomic_increment(&s_nImageRefCount);
+ m_pView = pLBView;
+ m_pTree = pLBTree;
+ m_aSelEng.SetFunctionSet( static_cast<FunctionSet*>(&m_aFctSet) );
+ m_aSelEng.ExpandSelectionOnMouseMove( false );
+ SetStyle( nWinStyle );
+ SetSelectionMode( SelectionMode::Single );
+ SetDragDropMode( DragDropMode::NONE );
+
+ m_aVerSBar->SetScrollHdl( LINK( this, SvImpLBox, ScrollUpDownHdl ) );
+ m_aHorSBar->SetScrollHdl( LINK( this, SvImpLBox, ScrollLeftRightHdl ) );
+ m_aHorSBar->SetEndScrollHdl( LINK( this, SvImpLBox, EndScrollHdl ) );
+ m_aVerSBar->SetEndScrollHdl( LINK( this, SvImpLBox, EndScrollHdl ) );
+ m_aVerSBar->SetRange( Range(0,0) );
+ m_aVerSBar->Hide();
+ m_aHorSBar->SetRange( Range(0,0) );
+ m_aHorSBar->SetPageSize( 24 ); // pixels
+ m_aHorSBar->SetLineSize( 8 ); // pixels
+
+ m_nHorSBarHeight = static_cast<short>(m_aHorSBar->GetSizePixel().Height());
+ m_nVerSBarWidth = static_cast<short>(m_aVerSBar->GetSizePixel().Width());
+
+ m_pStartEntry = nullptr;
+ m_pCursor = nullptr;
+ m_pCursorOld = nullptr;
+ m_pAnchor = nullptr;
+ m_nVisibleCount = 0; // number of rows of data in control
+ m_nNodeBmpTabDistance = NODE_BMP_TABDIST_NOTVALID;
+ m_nNodeBmpWidth = 0;
+
+ // button animation in listbox
+ m_pActiveButton = nullptr;
+ m_pActiveEntry = nullptr;
+ m_pActiveTab = nullptr;
+
+ m_nFlags = LBoxFlags::NONE;
+
+ m_aEditIdle.SetPriority( TaskPriority::LOWEST );
+ m_aEditIdle.SetInvokeHandler( LINK(this,SvImpLBox,EditTimerCall) );
+
+ m_nMostRight = -1;
+ m_pMostRightEntry = nullptr;
+ m_nCurUserEvent = nullptr;
+
+ m_bUpdateMode = true;
+ m_bInVScrollHdl = false;
+ m_nFlags |= LBoxFlags::Filling;
+
+ m_bSubLstOpLR = false;
+}
+
+SvImpLBox::~SvImpLBox()
+{
+ m_aEditIdle.Stop();
+ StopUserEvent();
+
+ if ( osl_atomic_decrement(&s_nImageRefCount) == 0 )
+ {
+ delete s_pDefCollapsed;
+ s_pDefCollapsed = nullptr;
+ delete s_pDefExpanded;
+ s_pDefExpanded = nullptr;
+ }
+ m_aVerSBar.disposeAndClear();
+ m_aHorSBar.disposeAndClear();
+ m_aScrBarBox.disposeAndClear();
+}
+
+void SvImpLBox::UpdateStringSorter()
+{
+ const css::lang::Locale& rNewLocale = Application::GetSettings().GetLanguageTag().getLocale();
+
+ if( m_pStringSorter )
+ {
+ // different Locale from the older one, drop it and force recreate
+ const css::lang::Locale &aLocale = m_pStringSorter->getLocale();
+ if( aLocale.Language != rNewLocale.Language ||
+ aLocale.Country != rNewLocale.Country ||
+ aLocale.Variant != rNewLocale.Variant )
+ m_pStringSorter.reset();
+ }
+
+ if( !m_pStringSorter )
+ {
+ m_pStringSorter.reset(new comphelper::string::NaturalStringSorter(
+ ::comphelper::getProcessComponentContext(),
+ rNewLocale));
+ }
+}
+
+short SvImpLBox::UpdateContextBmpWidthVector( SvTreeListEntry const * pEntry, short nWidth )
+{
+ DBG_ASSERT( m_pView->pModel, "View and Model aren't valid!" );
+
+ sal_uInt16 nDepth = m_pView->pModel->GetDepth( pEntry );
+ // initialize vector if necessary
+ std::vector< short >::size_type nSize = m_aContextBmpWidthVector.size();
+ while ( nDepth > nSize )
+ {
+ m_aContextBmpWidthVector.resize( nSize + 1 );
+ m_aContextBmpWidthVector.at( nSize ) = nWidth;
+ ++nSize;
+ }
+ if( m_aContextBmpWidthVector.size() == nDepth )
+ {
+ m_aContextBmpWidthVector.resize( nDepth + 1 );
+ m_aContextBmpWidthVector.at( nDepth ) = 0;
+ }
+ short nContextBmpWidth = m_aContextBmpWidthVector[ nDepth ];
+ if( nContextBmpWidth < nWidth )
+ {
+ m_aContextBmpWidthVector.at( nDepth ) = nWidth;
+ return nWidth;
+ }
+ else
+ return nContextBmpWidth;
+}
+
+void SvImpLBox::UpdateContextBmpWidthVectorFromMovedEntry( SvTreeListEntry* pEntry )
+{
+ DBG_ASSERT( pEntry, "Moved Entry is invalid!" );
+
+ SvLBoxContextBmp* pBmpItem = static_cast< SvLBoxContextBmp* >( pEntry->GetFirstItem(SvLBoxItemType::ContextBmp) );
+ short nExpWidth = static_cast<short>(pBmpItem->GetBitmap1().GetSizePixel().Width());
+ short nColWidth = static_cast<short>(pBmpItem->GetBitmap2().GetSizePixel().Width());
+ short nMax = std::max(nExpWidth, nColWidth);
+ UpdateContextBmpWidthVector( pEntry, nMax );
+
+ if( pEntry->HasChildren() ) // recursive call, whether expanded or not
+ {
+ SvTreeListEntry* pChild = m_pView->FirstChild( pEntry );
+ DBG_ASSERT( pChild, "The first child is invalid!" );
+ do
+ {
+ UpdateContextBmpWidthVectorFromMovedEntry( pChild );
+ pChild = m_pView->Next( pChild );
+ } while ( pChild );
+ }
+}
+
+void SvImpLBox::UpdateContextBmpWidthMax( SvTreeListEntry const * pEntry )
+{
+ sal_uInt16 nDepth = m_pView->pModel->GetDepth( pEntry );
+ if( m_aContextBmpWidthVector.empty() )
+ return;
+ short nWidth = m_aContextBmpWidthVector[ nDepth ];
+ if( nWidth != m_pView->nContextBmpWidthMax ) {
+ m_pView->nContextBmpWidthMax = nWidth;
+ m_nFlags |= LBoxFlags::IgnoreChangedTabs;
+ m_pView->SetTabs();
+ m_nFlags &= ~LBoxFlags::IgnoreChangedTabs;
+ }
+}
+
+void SvImpLBox::SetStyle( WinBits i_nWinStyle )
+{
+ m_nStyle = i_nWinStyle;
+ if ( ( m_nStyle & WB_SIMPLEMODE) && ( m_aSelEng.GetSelectionMode() == SelectionMode::Multiple ) )
+ m_aSelEng.AddAlways( true );
+}
+
+void SvImpLBox::SetNoAutoCurEntry( bool b )
+{
+ mbNoAutoCurEntry = b;
+}
+
+// don't touch the model any more
+void SvImpLBox::Clear()
+{
+ StopUserEvent();
+ m_pStartEntry = nullptr;
+ m_pAnchor = nullptr;
+
+ m_pActiveButton = nullptr;
+ m_pActiveEntry = nullptr;
+ m_pActiveTab = nullptr;
+
+ m_nMostRight = -1;
+ m_pMostRightEntry = nullptr;
+
+ // don't touch the cursor any more
+ if( m_pCursor )
+ {
+ if( m_pView->HasFocus() )
+ m_pView->HideFocus();
+ m_pCursor = nullptr;
+ }
+ m_pCursorOld = nullptr;
+ m_aVerSBar->Hide();
+ m_aVerSBar->SetThumbPos( 0 );
+ Range aRange( 0, 0 );
+ m_aVerSBar->SetRange( aRange );
+ m_aOutputSize = m_pView->Control::GetOutputSizePixel();
+ m_aHorSBar->Hide();
+ m_aHorSBar->SetThumbPos( 0 );
+ MapMode aMapMode( m_pView->GetMapMode());
+ aMapMode.SetOrigin( Point(0,0) );
+ m_pView->Control::SetMapMode( aMapMode );
+ m_aHorSBar->SetRange( aRange );
+ m_aHorSBar->SetSizePixel(Size(m_aOutputSize.Width(),m_nHorSBarHeight));
+ m_pView->GetOutDev()->SetClipRegion();
+ if( GetUpdateMode() )
+ m_pView->Invalidate( GetVisibleArea() );
+ m_nFlags |= LBoxFlags::Filling;
+ if( !m_aHorSBar->IsVisible() && !m_aVerSBar->IsVisible() )
+ m_aScrBarBox->Hide();
+
+ m_aContextBmpWidthVector.clear();
+
+ CallEventListeners( VclEventId::ListboxItemRemoved );
+}
+
+// *********************************************************************
+// Paint, navigate, scroll
+// *********************************************************************
+
+IMPL_LINK_NOARG(SvImpLBox, EndScrollHdl, ScrollBar*, void)
+{
+ if( m_nFlags & LBoxFlags::EndScrollSetVisSize )
+ {
+ m_aVerSBar->SetVisibleSize( m_nNextVerVisSize );
+ m_nFlags &= ~LBoxFlags::EndScrollSetVisSize;
+ }
+}
+
+// handler for vertical scrollbar
+
+IMPL_LINK( SvImpLBox, ScrollUpDownHdl, ScrollBar *, pScrollBar, void )
+{
+ DBG_ASSERT(!m_bInVScrollHdl,"Scroll handler out-paces itself!");
+ tools::Long nDelta = pScrollBar->GetDelta();
+ if( !nDelta )
+ return;
+
+ // when only one row don't skip lines
+ if (pScrollBar->GetPageSize() == 1)
+ nDelta = nDelta > 0 ? 1 : -1;
+
+ m_nFlags &= ~LBoxFlags::Filling;
+
+ m_bInVScrollHdl = true;
+
+ if( m_pView->IsEditingActive() )
+ {
+ m_pView->EndEditing( true ); // Cancel
+ m_pView->PaintImmediately();
+ }
+
+ if( nDelta > 0 )
+ {
+ if( nDelta == 1 && pScrollBar->GetPageSize() > 1)
+ CursorDown();
+ else
+ PageDown( static_cast<sal_uInt16>(nDelta) );
+ }
+ else
+ {
+ nDelta *= -1;
+ if( nDelta == 1 && pScrollBar->GetPageSize() > 1)
+ CursorUp();
+ else
+ PageUp( static_cast<sal_uInt16>(nDelta) );
+ }
+ m_bInVScrollHdl = false;
+}
+
+
+void SvImpLBox::CursorDown()
+{
+ if (!m_pStartEntry)
+ return;
+
+ SvTreeListEntry* pNextFirstToDraw = m_pView->NextVisible(m_pStartEntry);
+ if( pNextFirstToDraw )
+ {
+ m_nFlags &= ~LBoxFlags::Filling;
+ ShowCursor( false );
+ m_pView->PaintImmediately();
+ m_pStartEntry = pNextFirstToDraw;
+ tools::Rectangle aArea( GetVisibleArea() );
+ m_pView->Scroll( 0, -(m_pView->GetEntryHeight()), aArea, ScrollFlags::NoChildren );
+ m_pView->PaintImmediately();
+ ShowCursor( true );
+ m_pView->NotifyScrolled();
+ }
+}
+
+void SvImpLBox::CursorUp()
+{
+ if (!m_pStartEntry)
+ return;
+
+ SvTreeListEntry* pPrevFirstToDraw = m_pView->PrevVisible(m_pStartEntry);
+ if( !pPrevFirstToDraw )
+ return;
+
+ m_nFlags &= ~LBoxFlags::Filling;
+ tools::Long nEntryHeight = m_pView->GetEntryHeight();
+ ShowCursor( false );
+ m_pView->PaintImmediately();
+ m_pStartEntry = pPrevFirstToDraw;
+ tools::Rectangle aArea( GetVisibleArea() );
+ if (aArea.GetHeight() > nEntryHeight)
+ aArea.AdjustBottom(-nEntryHeight);
+ m_pView->Scroll( 0, nEntryHeight, aArea, ScrollFlags::NoChildren );
+ m_pView->PaintImmediately();
+ ShowCursor( true );
+ m_pView->NotifyScrolled();
+}
+
+void SvImpLBox::PageDown( sal_uInt16 nDelta )
+{
+ sal_uInt16 nRealDelta = nDelta;
+
+ if( !nDelta )
+ return;
+
+ if (!m_pStartEntry)
+ return;
+
+ SvTreeListEntry* pNext = m_pView->NextVisible(m_pStartEntry, nRealDelta);
+ if( pNext == m_pStartEntry )
+ return;
+
+ ShowCursor( false );
+
+ m_nFlags &= ~LBoxFlags::Filling;
+ m_pStartEntry = pNext;
+
+ if( nRealDelta >= m_nVisibleCount )
+ {
+ m_pView->Invalidate( GetVisibleArea() );
+ m_pView->PaintImmediately();
+ }
+ else
+ {
+ tools::Rectangle aArea( GetVisibleArea() );
+ tools::Long nScroll = m_pView->GetEntryHeight() * static_cast<tools::Long>(nRealDelta);
+ nScroll = -nScroll;
+ m_pView->PaintImmediately();
+ m_pView->Scroll( 0, nScroll, aArea, ScrollFlags::NoChildren );
+ m_pView->PaintImmediately();
+ }
+
+ ShowCursor( true );
+ m_pView->NotifyScrolled();
+}
+
+void SvImpLBox::PageUp( sal_uInt16 nDelta )
+{
+ sal_uInt16 nRealDelta = nDelta;
+ if( !nDelta )
+ return;
+
+ if (!m_pStartEntry)
+ return;
+
+ SvTreeListEntry* pPrev = m_pView->PrevVisible(m_pStartEntry, nRealDelta);
+ if( pPrev == m_pStartEntry )
+ return;
+
+ m_nFlags &= ~LBoxFlags::Filling;
+ ShowCursor( false );
+
+ m_pStartEntry = pPrev;
+ if( nRealDelta >= m_nVisibleCount )
+ {
+ m_pView->Invalidate( GetVisibleArea() );
+ m_pView->PaintImmediately();
+ }
+ else
+ {
+ tools::Long nEntryHeight = m_pView->GetEntryHeight();
+ tools::Rectangle aArea( GetVisibleArea() );
+ m_pView->PaintImmediately();
+ m_pView->Scroll( 0, nEntryHeight*nRealDelta, aArea, ScrollFlags::NoChildren );
+ m_pView->PaintImmediately();
+ }
+
+ ShowCursor( true );
+ m_pView->NotifyScrolled();
+}
+
+void SvImpLBox::KeyUp( bool bPageUp )
+{
+ if( !m_aVerSBar->IsVisible() )
+ return;
+
+ tools::Long nDelta;
+ if( bPageUp )
+ nDelta = m_aVerSBar->GetPageSize();
+ else
+ nDelta = 1;
+
+ tools::Long nThumbPos = m_aVerSBar->GetThumbPos();
+
+ if( nThumbPos < nDelta )
+ nDelta = nThumbPos;
+
+ if( nDelta <= 0 )
+ return;
+
+ m_nFlags &= ~LBoxFlags::Filling;
+
+ m_aVerSBar->SetThumbPos( nThumbPos - nDelta );
+ if( bPageUp )
+ PageUp( static_cast<short>(nDelta) );
+ else
+ CursorUp();
+}
+
+
+void SvImpLBox::KeyDown( bool bPageDown )
+{
+ if( !m_aVerSBar->IsVisible() )
+ return;
+
+ tools::Long nDelta;
+ if( bPageDown )
+ nDelta = m_aVerSBar->GetPageSize();
+ else
+ nDelta = 1;
+
+ tools::Long nThumbPos = m_aVerSBar->GetThumbPos();
+ tools::Long nVisibleSize = m_aVerSBar->GetVisibleSize();
+ tools::Long nRange = m_aVerSBar->GetRange().Len();
+
+ tools::Long nTmp = nThumbPos+nVisibleSize;
+ while( (nDelta > 0) && (nTmp+nDelta) >= nRange )
+ nDelta--;
+
+ if( nDelta <= 0 )
+ return;
+
+ m_nFlags &= ~LBoxFlags::Filling;
+
+ m_aVerSBar->SetThumbPos( nThumbPos+nDelta );
+ if( bPageDown )
+ PageDown( static_cast<short>(nDelta) );
+ else
+ CursorDown();
+}
+
+
+void SvImpLBox::InvalidateEntriesFrom( tools::Long nY ) const
+{
+ if( !(m_nFlags & LBoxFlags::InPaint ))
+ {
+ tools::Rectangle aRect( GetVisibleArea() );
+ aRect.SetTop( nY );
+ m_pView->Invalidate( aRect );
+ }
+}
+
+void SvImpLBox::InvalidateEntry( tools::Long nY ) const
+{
+ if( m_nFlags & LBoxFlags::InPaint )
+ return;
+
+ tools::Rectangle aRect( GetVisibleArea() );
+ tools::Long nMaxBottom = aRect.Bottom();
+ aRect.SetTop( nY );
+ aRect.SetBottom( nY ); aRect.AdjustBottom(m_pView->GetEntryHeight() );
+ if( aRect.Top() > nMaxBottom )
+ return;
+ if( aRect.Bottom() > nMaxBottom )
+ aRect.SetBottom( nMaxBottom );
+ if (m_pView->SupportsDoubleBuffering())
+ // Perform full paint when flicker is to be avoided explicitly.
+ m_pView->Invalidate();
+ else
+ m_pView->Invalidate(aRect);
+}
+
+void SvImpLBox::InvalidateEntry( SvTreeListEntry* pEntry )
+{
+ if( GetUpdateMode() )
+ {
+ tools::Long nPrev = m_nMostRight;
+ SetMostRight( pEntry );
+ if( nPrev < m_nMostRight )
+ ShowVerSBar();
+ }
+ if( !(m_nFlags & LBoxFlags::InPaint ))
+ {
+ bool bHasFocusRect = false;
+ if( pEntry==m_pCursor && m_pView->HasFocus() )
+ {
+ bHasFocusRect = true;
+ ShowCursor( false );
+ }
+ InvalidateEntry( GetEntryLine( pEntry ) );
+ if( bHasFocusRect )
+ ShowCursor( true );
+ }
+}
+
+
+void SvImpLBox::RecalcFocusRect()
+{
+ if( m_pView->HasFocus() && m_pCursor )
+ {
+ m_pView->HideFocus();
+ tools::Long nY = GetEntryLine( m_pCursor );
+ tools::Rectangle aRect = m_pView->GetFocusRect( m_pCursor, nY );
+ vcl::Region aOldClip( m_pView->GetOutDev()->GetClipRegion());
+ vcl::Region aClipRegion( GetClipRegionRect() );
+ m_pView->GetOutDev()->SetClipRegion( aClipRegion );
+ m_pView->ShowFocus( aRect );
+ m_pView->GetOutDev()->SetClipRegion( aOldClip );
+ }
+}
+
+
+// Sets cursor. When using SingleSelection, the selection is adjusted.
+void SvImpLBox::SetCursor( SvTreeListEntry* pEntry, bool bForceNoSelect )
+{
+ SvViewDataEntry* pViewDataNewCur = nullptr;
+ if( pEntry )
+ pViewDataNewCur= m_pView->GetViewDataEntry(pEntry);
+ if( pEntry &&
+ pEntry == m_pCursor &&
+ pViewDataNewCur &&
+ pViewDataNewCur->HasFocus() &&
+ pViewDataNewCur->IsSelected())
+ {
+ return;
+ }
+
+ // if this cursor is not selectable, find first visible that is and use it
+ while( pEntry && pViewDataNewCur && !pViewDataNewCur->IsSelectable() )
+ {
+ pEntry = m_pView->NextVisible(pEntry);
+ pViewDataNewCur = pEntry ? m_pView->GetViewDataEntry(pEntry) : nullptr;
+ }
+
+ SvTreeListEntry* pOldCursor = m_pCursor;
+ if( m_pCursor && pEntry != m_pCursor )
+ {
+ m_pView->SetEntryFocus( m_pCursor, false );
+ if( m_bSimpleTravel )
+ m_pView->Select( m_pCursor, false );
+ m_pView->HideFocus();
+ }
+ m_pCursor = pEntry;
+ if( m_pCursor )
+ {
+ if (pViewDataNewCur)
+ pViewDataNewCur->SetFocus( true );
+ if(!bForceNoSelect && m_bSimpleTravel && !(m_nFlags & LBoxFlags::DeselectAll) && GetUpdateMode())
+ {
+ m_pView->Select( m_pCursor );
+ CallEventListeners( VclEventId::ListboxTreeFocus, m_pCursor );
+ }
+ // multiple selection: select in cursor move if we're not in
+ // Add mode (Ctrl-F8)
+ else if( GetUpdateMode() &&
+ m_pView->GetSelectionMode() == SelectionMode::Multiple &&
+ !(m_nFlags & LBoxFlags::DeselectAll) && !m_aSelEng.IsAddMode() &&
+ !bForceNoSelect )
+ {
+ m_pView->Select( m_pCursor );
+ CallEventListeners( VclEventId::ListboxTreeFocus, m_pCursor );
+ }
+ else
+ {
+ ShowCursor( true );
+ if (bForceNoSelect && GetUpdateMode())
+ {
+ CallEventListeners( VclEventId::ListboxTreeFocus, m_pCursor);
+ }
+ }
+
+ if( m_pAnchor )
+ {
+ DBG_ASSERT(m_aSelEng.GetSelectionMode() != SelectionMode::Single,"Mode?");
+ SetAnchorSelection( pOldCursor, m_pCursor );
+ }
+ }
+ m_nFlags &= ~LBoxFlags::DeselectAll;
+
+ m_pView->OnCurrentEntryChanged();
+}
+
+void SvImpLBox::ShowCursor( bool bShow )
+{
+ if( !bShow || !m_pCursor || !m_pView->HasFocus() )
+ {
+ vcl::Region aOldClip( m_pView->GetOutDev()->GetClipRegion());
+ vcl::Region aClipRegion( GetClipRegionRect() );
+ m_pView->GetOutDev()->SetClipRegion( aClipRegion );
+ m_pView->HideFocus();
+ m_pView->GetOutDev()->SetClipRegion( aOldClip );
+ }
+ else
+ {
+ tools::Long nY = GetEntryLine( m_pCursor );
+ tools::Rectangle aRect = m_pView->GetFocusRect( m_pCursor, nY );
+ vcl::Region aOldClip( m_pView->GetOutDev()->GetClipRegion());
+ vcl::Region aClipRegion( GetClipRegionRect() );
+ m_pView->GetOutDev()->SetClipRegion( aClipRegion );
+ m_pView->ShowFocus( aRect );
+ m_pView->GetOutDev()->SetClipRegion( aOldClip );
+ }
+}
+
+
+void SvImpLBox::UpdateAll( bool bInvalidateCompleteView )
+{
+ FindMostRight();
+ m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1 ) );
+ SyncVerThumb();
+ FillView();
+ ShowVerSBar();
+ if( m_bSimpleTravel && m_pCursor && m_pView->HasFocus() )
+ m_pView->Select( m_pCursor );
+ ShowCursor( true );
+ if( bInvalidateCompleteView )
+ m_pView->Invalidate();
+ else
+ m_pView->Invalidate( GetVisibleArea() );
+}
+
+IMPL_LINK( SvImpLBox, ScrollLeftRightHdl, ScrollBar *, pScrollBar, void )
+{
+ tools::Long nDelta = pScrollBar->GetDelta();
+ if( nDelta )
+ {
+ if( m_pView->IsEditingActive() )
+ {
+ m_pView->EndEditing( true ); // Cancel
+ m_pView->PaintImmediately();
+ }
+ m_pView->nFocusWidth = -1;
+ KeyLeftRight( nDelta );
+ }
+}
+
+void SvImpLBox::KeyLeftRight( tools::Long nDelta )
+{
+ if( !(m_nFlags & LBoxFlags::InResize) )
+ m_pView->PaintImmediately();
+ m_nFlags &= ~LBoxFlags::Filling;
+ ShowCursor( false );
+
+ // calculate new origin
+ tools::Long nPos = m_aHorSBar->GetThumbPos();
+ Point aOrigin( -nPos, 0 );
+
+ MapMode aMapMode( m_pView->GetMapMode() );
+ aMapMode.SetOrigin( aOrigin );
+ m_pView->SetMapMode( aMapMode );
+
+ if( !(m_nFlags & LBoxFlags::InResize) )
+ {
+ tools::Rectangle aRect( GetVisibleArea() );
+ m_pView->Scroll( -nDelta, 0, aRect, ScrollFlags::NoChildren );
+ }
+ else
+ m_pView->Invalidate();
+ RecalcFocusRect();
+ ShowCursor( true );
+ m_pView->NotifyScrolled();
+}
+
+
+// returns the last entry if position is just past the last entry
+SvTreeListEntry* SvImpLBox::GetClickedEntry( const Point& rPoint ) const
+{
+ DBG_ASSERT( m_pView->GetModel(), "SvImpLBox::GetClickedEntry: how can this ever happen? Please tell me (frank.schoenheit@sun.com) how to reproduce!" );
+ if ( !m_pView->GetModel() )
+ // this is quite impossible. Nevertheless, stack traces from the crash reporter
+ // suggest it isn't. Okay, make it safe, and wait for somebody to reproduce it
+ // reliably :-\ ...
+ // #122359# / 2005-05-23 / frank.schoenheit@sun.com
+ return nullptr;
+ if( m_pView->GetEntryCount() == 0 || !m_pStartEntry || !m_pView->GetEntryHeight())
+ return nullptr;
+
+ sal_uInt16 nClickedEntry = static_cast<sal_uInt16>(rPoint.Y() / m_pView->GetEntryHeight() );
+ sal_uInt16 nTemp = nClickedEntry;
+ SvTreeListEntry* pEntry = m_pView->NextVisible(m_pStartEntry, nTemp);
+ return pEntry;
+}
+
+
+// checks if the entry was hit "the right way"
+// (Focusrect+ ContextBitmap at TreeListBox)
+
+bool SvImpLBox::EntryReallyHit(SvTreeListEntry* pEntry, const Point& rPosPixel, tools::Long nLine)
+{
+ bool bRet;
+ // we are not too exact when it comes to "special" entries
+ // (with CheckButtons etc.)
+ if( pEntry->ItemCount() >= 3 )
+ return true;
+
+ tools::Rectangle aRect( m_pView->GetFocusRect( pEntry, nLine ));
+ aRect.SetRight( GetOutputSize().Width() - m_pView->GetMapMode().GetOrigin().X() );
+
+ SvLBoxContextBmp* pBmp = static_cast<SvLBoxContextBmp*>(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp));
+ aRect.AdjustLeft( -pBmp->GetWidth(m_pView,pEntry) );
+ aRect.AdjustLeft( -4 ); // a little tolerance
+
+ Point aPos( rPosPixel );
+ aPos -= m_pView->GetMapMode().GetOrigin();
+ bRet = aRect.Contains( aPos );
+ return bRet;
+}
+
+
+// returns 0 if position is just past the last entry
+SvTreeListEntry* SvImpLBox::GetEntry( const Point& rPoint ) const
+{
+ if( (m_pView->GetEntryCount() == 0) || !m_pStartEntry ||
+ (rPoint.Y() > m_aOutputSize.Height())
+ || !m_pView->GetEntryHeight())
+ return nullptr;
+
+ sal_uInt16 nClickedEntry = static_cast<sal_uInt16>(rPoint.Y() / m_pView->GetEntryHeight() );
+ sal_uInt16 nTemp = nClickedEntry;
+ SvTreeListEntry* pEntry = m_pView->NextVisible(m_pStartEntry, nTemp);
+ if( nTemp != nClickedEntry )
+ pEntry = nullptr;
+ return pEntry;
+}
+
+
+SvTreeListEntry* SvImpLBox::MakePointVisible(const Point& rPoint)
+{
+ if( !m_pCursor )
+ return nullptr;
+ tools::Long nY = rPoint.Y();
+ SvTreeListEntry* pEntry = nullptr;
+ tools::Long nMax = m_aOutputSize.Height();
+ if( nY < 0 || nY >= nMax ) // aOutputSize.Height() )
+ {
+ if( nY < 0 )
+ pEntry = m_pView->PrevVisible(m_pCursor);
+ else
+ pEntry = m_pView->NextVisible(m_pCursor);
+
+ if( pEntry && pEntry != m_pCursor )
+ m_pView->SetEntryFocus( m_pCursor, false );
+
+ if( nY < 0 )
+ KeyUp( false );
+ else
+ KeyDown( false );
+ }
+ else
+ {
+ pEntry = GetClickedEntry( rPoint );
+ if( !pEntry )
+ {
+ sal_uInt16 nSteps = 0xFFFF;
+ // TODO: LastVisible is not yet implemented!
+ pEntry = m_pView->NextVisible(m_pStartEntry, nSteps);
+ }
+ if( pEntry )
+ {
+ if( pEntry != m_pCursor &&
+ m_aSelEng.GetSelectionMode() == SelectionMode::Single
+ )
+ m_pView->Select( m_pCursor, false );
+ }
+ }
+ return pEntry;
+}
+
+tools::Rectangle SvImpLBox::GetClipRegionRect() const
+{
+ Point aOrigin( m_pView->GetMapMode().GetOrigin() );
+ aOrigin.setX( aOrigin.X() * -1 ); // conversion document coordinates
+ tools::Rectangle aClipRect( aOrigin, m_aOutputSize );
+ aClipRect.AdjustBottom( 1 );
+ return aClipRect;
+}
+
+
+void SvImpLBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (!m_pView->GetVisibleCount())
+ return;
+
+ m_nFlags |= LBoxFlags::InPaint;
+
+ if (m_nFlags & LBoxFlags::Filling)
+ {
+ SvTreeListEntry* pFirst = m_pView->First();
+ if (pFirst != m_pStartEntry)
+ {
+ ShowCursor(false);
+ m_pStartEntry = m_pView->First();
+ m_aVerSBar->SetThumbPos( 0 );
+ StopUserEvent();
+ ShowCursor(true);
+ m_nCurUserEvent = Application::PostUserEvent(LINK(this, SvImpLBox, MyUserEvent),
+ reinterpret_cast<void*>(1));
+ return;
+ }
+ }
+
+ if (!m_pStartEntry)
+ {
+ m_pStartEntry = m_pView->First();
+ }
+
+ if (m_nNodeBmpTabDistance == NODE_BMP_TABDIST_NOTVALID)
+ SetNodeBmpTabDistance();
+
+ tools::Long nRectHeight = rRect.GetHeight();
+ tools::Long nEntryHeight = m_pView->GetEntryHeight();
+
+ // calculate area for the entries we want to draw
+ sal_uInt16 nStartLine = static_cast<sal_uInt16>(rRect.Top() / nEntryHeight);
+ sal_uInt16 nCount = static_cast<sal_uInt16>(nRectHeight / nEntryHeight);
+ nCount += 2; // don't miss a row
+
+ tools::Long nY = nStartLine * nEntryHeight;
+ SvTreeListEntry* pEntry = m_pStartEntry;
+ while (nStartLine && pEntry)
+ {
+ pEntry = m_pView->NextVisible(pEntry);
+ nStartLine--;
+ }
+
+ if (!m_pCursor && !mbNoAutoCurEntry)
+ {
+ // do not select if multiselection or explicit set
+ bool bNotSelect = (m_aSelEng.GetSelectionMode() == SelectionMode::Multiple ) || ((m_nStyle & WB_NOINITIALSELECTION) == WB_NOINITIALSELECTION);
+ SetCursor(m_pStartEntry, bNotSelect);
+ }
+
+ for(sal_uInt16 n=0; n< nCount && pEntry; n++)
+ {
+ /*long nMaxRight=*/
+ m_pView->PaintEntry1(*pEntry, nY, rRenderContext );
+ nY += nEntryHeight;
+ pEntry = m_pView->NextVisible(pEntry);
+ }
+
+ if (m_nStyle & (WB_HASLINES | WB_HASLINESATROOT))
+ DrawNet(rRenderContext);
+
+ m_nFlags &= ~LBoxFlags::DeselectAll;
+ m_nFlags &= ~LBoxFlags::InPaint;
+}
+
+void SvImpLBox::MakeVisible( SvTreeListEntry* pEntry, bool bMoveToTop )
+{
+ if( !pEntry )
+ return;
+
+ bool bInView = IsEntryInView( pEntry );
+
+ if( bInView && (!bMoveToTop || m_pStartEntry == pEntry) )
+ return; // is already visible
+
+ if( m_pStartEntry || mbForceMakeVisible )
+ m_nFlags &= ~LBoxFlags::Filling;
+ if( !bInView )
+ {
+ if( !m_pView->IsEntryVisible(pEntry) ) // Parent(s) collapsed?
+ {
+ SvTreeListEntry* pParent = m_pView->GetParent( pEntry );
+ while( pParent )
+ {
+ if( !m_pView->IsExpanded( pParent ) )
+ {
+ bool bRet = m_pView->Expand( pParent );
+ DBG_ASSERT(bRet,"Not expanded!");
+ }
+ pParent = m_pView->GetParent( pParent );
+ }
+ // do the parent's children fit into the view or do we have to scroll?
+ if( IsEntryInView( pEntry ) && !bMoveToTop )
+ return; // no need to scroll
+ }
+ }
+
+ m_pStartEntry = pEntry;
+ ShowCursor( false );
+ FillView();
+ m_aVerSBar->SetThumbPos( static_cast<tools::Long>(m_pView->GetVisiblePos( m_pStartEntry )) );
+ ShowCursor( true );
+ m_pView->NotifyScrolled();
+ m_pView->Invalidate();
+}
+
+void SvImpLBox::ScrollToAbsPos( tools::Long nPos )
+{
+ if( m_pView->GetVisibleCount() == 0 )
+ return;
+ tools::Long nLastEntryPos = m_pView->GetAbsPos( m_pView->Last() );
+
+ if( nPos < 0 )
+ nPos = 0;
+ else if( nPos > nLastEntryPos )
+ nPos = nLastEntryPos;
+
+ SvTreeListEntry* pEntry = m_pView->GetEntryAtAbsPos( nPos );
+ if( !pEntry || pEntry == m_pStartEntry )
+ return;
+
+ if( m_pStartEntry || mbForceMakeVisible )
+ m_nFlags &= ~LBoxFlags::Filling;
+
+ if( m_pView->IsEntryVisible(pEntry) )
+ {
+ m_pStartEntry = pEntry;
+ ShowCursor( false );
+ m_aVerSBar->SetThumbPos( nPos );
+ ShowCursor( true );
+ if (GetUpdateMode())
+ m_pView->Invalidate();
+ }
+}
+
+void SvImpLBox::DrawNet(vcl::RenderContext& rRenderContext)
+{
+ if (m_pView->GetVisibleCount() < 2 && !m_pStartEntry->HasChildrenOnDemand() &&
+ !m_pStartEntry->HasChildren())
+ {
+ return;
+ }
+
+ // for platforms that don't have nets, DrawNativeControl does nothing and returns true
+ // so that SvImpLBox::DrawNet() doesn't draw anything either
+ if (rRenderContext.IsNativeControlSupported(ControlType::ListNet, ControlPart::Entire))
+ {
+ ImplControlValue aControlValue;
+ if (rRenderContext.DrawNativeControl(ControlType::ListNet, ControlPart::Entire,
+ tools::Rectangle(), ControlState::ENABLED, aControlValue, OUString()))
+ {
+ return;
+ }
+ }
+
+ tools::Long nEntryHeight = m_pView->GetEntryHeight();
+ tools::Long nEntryHeightDIV2 = nEntryHeight / 2;
+ if( nEntryHeightDIV2 && !(nEntryHeight & 0x0001))
+ nEntryHeightDIV2--;
+
+ SvTreeListEntry* pChild;
+ SvTreeListEntry* pEntry = m_pStartEntry;
+
+ SvLBoxTab* pFirstDynamicTab = m_pView->GetFirstDynamicTab();
+ while (m_pTree->GetDepth( pEntry ) > 0)
+ {
+ pEntry = m_pView->GetParent(pEntry);
+ }
+ sal_uInt16 nOffs = static_cast<sal_uInt16>(m_pView->GetVisiblePos(m_pStartEntry) - m_pView->GetVisiblePos(pEntry));
+ tools::Long nY = 0;
+ nY -= (nOffs * nEntryHeight);
+
+ DBG_ASSERT(pFirstDynamicTab,"No Tree!");
+
+ rRenderContext.Push(vcl::PushFlags::LINECOLOR);
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ Color aCol = rStyleSettings.GetFaceColor();
+
+ if (aCol.IsRGBEqual(rRenderContext.GetBackground().GetColor()))
+ aCol = rStyleSettings.GetShadowColor();
+ rRenderContext.SetLineColor(aCol);
+ Point aPos1, aPos2;
+ sal_uInt16 nDistance;
+ sal_uLong nMax = m_nVisibleCount + nOffs + 1;
+
+ const Image& rExpandedNodeBitmap = GetExpandedNodeBmp();
+
+ for (sal_uLong n=0; n< nMax && pEntry; n++)
+ {
+ if (m_pView->IsExpanded(pEntry))
+ {
+ // draw vertical line
+ aPos1.setX(m_pView->GetTabPos(pEntry, pFirstDynamicTab) + m_nNodeBmpTabDistance +
+ rExpandedNodeBitmap.GetSizePixel().Width() / 2);
+ aPos1.setY(nY + nEntryHeight);
+ pChild = m_pView->FirstChild(pEntry);
+ assert(pChild && "Child?");
+ pChild = pChild->LastSibling();
+ nDistance = static_cast<sal_uInt16>(m_pView->GetVisiblePos(pChild) -
+ m_pView->GetVisiblePos(pEntry));
+ aPos2 = aPos1;
+ aPos2.AdjustY((nDistance * nEntryHeight) - (nEntryHeightDIV2 + 2));
+ rRenderContext.DrawLine(aPos1, aPos2);
+ }
+ // visible in control?
+ if (n >= nOffs && !m_pTree->IsAtRootDepth(pEntry))
+ {
+ // draw horizontal line
+ aPos1.setX(m_pView->GetTabPos(m_pView->GetParent(pEntry), pFirstDynamicTab)
+ + m_nNodeBmpTabDistance
+ + rExpandedNodeBitmap.GetSizePixel().Width() / 2);
+ aPos1.setY(nY + nEntryHeightDIV2);
+ aPos2 = aPos1;
+ aPos2.AdjustX(m_pView->GetIndent() / 2);
+ rRenderContext.DrawLine(aPos1, aPos2);
+ }
+ nY += nEntryHeight;
+ pEntry = m_pView->NextVisible(pEntry);
+ }
+
+ rRenderContext.Pop();
+}
+
+void SvImpLBox::PositionScrollBars( Size& rSize, sal_uInt16 nMask )
+{
+ tools::Long nOverlap = 0;
+
+ Size aVerSize( m_nVerSBarWidth, rSize.Height() );
+ Size aHorSize( rSize.Width(), m_nHorSBarHeight );
+
+ if( nMask & 0x0001 )
+ aHorSize.AdjustWidth( -m_nVerSBarWidth );
+ if( nMask & 0x0002 )
+ aVerSize.AdjustHeight( -m_nHorSBarHeight );
+
+ aVerSize.AdjustHeight(2 * nOverlap );
+ Point aVerPos( rSize.Width() - aVerSize.Width() + nOverlap, -nOverlap );
+ m_aVerSBar->SetPosSizePixel( aVerPos, aVerSize );
+
+ aHorSize.AdjustWidth(2 * nOverlap );
+ Point aHorPos( -nOverlap, rSize.Height() - aHorSize.Height() + nOverlap );
+
+ m_aHorSBar->SetPosSizePixel( aHorPos, aHorSize );
+
+ if( nMask & 0x0001 )
+ rSize.setWidth( aVerPos.X() );
+ if( nMask & 0x0002 )
+ rSize.setHeight( aHorPos.Y() );
+
+ if( (nMask & (0x0001|0x0002)) == (0x0001|0x0002) )
+ m_aScrBarBox->Show();
+ else
+ m_aScrBarBox->Hide();
+}
+
+void SvImpLBox::AdjustScrollBars( Size& rSize )
+{
+ tools::Long nEntryHeight = m_pView->GetEntryHeight();
+ if( !nEntryHeight )
+ return;
+
+ sal_uInt16 nResult = 0;
+
+ Size aOSize( m_pView->Control::GetOutputSizePixel() );
+
+ const WinBits nWindowStyle = m_pView->GetStyle();
+ bool bVerSBar = ( nWindowStyle & WB_VSCROLL ) != 0;
+ bool bHorBar = false;
+ tools::Long nMaxRight = aOSize.Width(); //GetOutputSize().Width();
+ Point aOrigin( m_pView->GetMapMode().GetOrigin() );
+ aOrigin.setX( aOrigin.X() * -1 );
+ nMaxRight += aOrigin.X() - 1;
+ tools::Long nVis = m_nMostRight - aOrigin.X();
+ if( (nWindowStyle & (WB_AUTOHSCROLL|WB_HSCROLL)) &&
+ (nVis < m_nMostRight || nMaxRight < m_nMostRight) )
+ {
+ bHorBar = true;
+ }
+
+ // number of entries that are not collapsed
+ sal_uLong nTotalCount = m_pView->GetVisibleCount();
+
+ // number of entries visible within the view
+ m_nVisibleCount = o3tl::make_unsigned(aOSize.Height() / nEntryHeight);
+
+ // do we need a vertical scrollbar?
+ if( bVerSBar || nTotalCount > m_nVisibleCount )
+ {
+ nResult = 1;
+ nMaxRight -= m_nVerSBarWidth;
+ if( !bHorBar )
+ {
+ if( (nWindowStyle & (WB_AUTOHSCROLL|WB_HSCROLL)) &&
+ (nVis < m_nMostRight || nMaxRight < m_nMostRight) )
+ bHorBar = true;
+ }
+ }
+
+ // do we need a horizontal scrollbar?
+ if( bHorBar )
+ {
+ nResult |= 0x0002;
+ // the number of entries visible within the view has to be recalculated
+ // because the horizontal scrollbar is now visible.
+ m_nVisibleCount = o3tl::make_unsigned(std::max<tools::Long>(0, aOSize.Height() - m_nHorSBarHeight) / nEntryHeight);
+ // we might actually need a vertical scrollbar now
+ if( !(nResult & 0x0001) &&
+ ((nTotalCount > m_nVisibleCount) || bVerSBar) )
+ {
+ nResult = 3;
+ }
+ }
+
+ PositionScrollBars( aOSize, nResult );
+
+ // adapt Range, VisibleRange etc.
+
+ // refresh output size, in case we have to scroll
+ tools::Rectangle aRect;
+ aRect.SetSize( aOSize );
+ m_aSelEng.SetVisibleArea( aRect );
+
+ // vertical scrollbar
+ tools::Long nTemp = static_cast<tools::Long>(m_nVisibleCount);
+ nTemp--;
+ if( nTemp != m_aVerSBar->GetVisibleSize() )
+ {
+ if( !m_bInVScrollHdl )
+ {
+ m_aVerSBar->SetPageSize( nTemp - 1 );
+ m_aVerSBar->SetVisibleSize( nTemp );
+ }
+ else
+ {
+ m_nFlags |= LBoxFlags::EndScrollSetVisSize;
+ m_nNextVerVisSize = nTemp;
+ }
+ }
+
+ // horizontal scrollbar
+ nTemp = m_aHorSBar->GetThumbPos();
+ m_aHorSBar->SetVisibleSize( aOSize.Width() );
+ tools::Long nNewThumbPos = m_aHorSBar->GetThumbPos();
+ Range aRange( m_aHorSBar->GetRange() );
+ if( aRange.Max() < m_nMostRight+25 )
+ {
+ aRange.Max() = m_nMostRight+25;
+ m_aHorSBar->SetRange( aRange );
+ }
+
+ if( nTemp != nNewThumbPos )
+ {
+ nTemp = nNewThumbPos - nTemp;
+ if( m_pView->IsEditingActive() )
+ {
+ m_pView->EndEditing( true ); // Cancel
+ m_pView->PaintImmediately();
+ }
+ m_pView->nFocusWidth = -1;
+ KeyLeftRight( nTemp );
+ }
+
+ if( nResult & 0x0001 )
+ m_aVerSBar->Show();
+ else
+ m_aVerSBar->Hide();
+
+ if( nResult & 0x0002 )
+ m_aHorSBar->Show();
+ else
+ {
+ m_aHorSBar->Hide();
+ }
+ rSize = aOSize;
+}
+
+void SvImpLBox::InitScrollBarBox()
+{
+ m_aScrBarBox->SetSizePixel( Size(m_nVerSBarWidth, m_nHorSBarHeight) );
+ Size aSize( m_pView->Control::GetOutputSizePixel() );
+ m_aScrBarBox->SetPosPixel( Point(aSize.Width()-m_nVerSBarWidth, aSize.Height()-m_nHorSBarHeight));
+}
+
+void SvImpLBox::Resize()
+{
+ m_aOutputSize = m_pView->Control::GetOutputSizePixel();
+ if( m_aOutputSize.IsEmpty() )
+ return;
+ m_nFlags |= LBoxFlags::InResize;
+ InitScrollBarBox();
+
+ if( m_pView->GetEntryHeight())
+ {
+ AdjustScrollBars( m_aOutputSize );
+ UpdateAll(false);
+ }
+ // HACK, as in floating and docked windows the scrollbars might not be drawn
+ // correctly/not be drawn at all after resizing!
+ if( m_aHorSBar->IsVisible())
+ m_aHorSBar->Invalidate();
+ if( m_aVerSBar->IsVisible())
+ m_aVerSBar->Invalidate();
+ m_nFlags &= ~LBoxFlags::InResize;
+}
+
+void SvImpLBox::FillView()
+{
+ if( !m_pStartEntry )
+ {
+ sal_uLong nVisibleViewCount = m_pView->GetVisibleCount();
+ tools::Long nTempThumb = m_aVerSBar->GetThumbPos();
+ if( nTempThumb < 0 )
+ nTempThumb = 0;
+ else if( o3tl::make_unsigned(nTempThumb) >= nVisibleViewCount )
+ nTempThumb = nVisibleViewCount == 0 ? 0 : nVisibleViewCount - 1;
+ m_pStartEntry = m_pView->GetEntryAtVisPos(nTempThumb);
+ }
+ if( !m_pStartEntry )
+ return;
+
+ sal_uInt16 nLast = static_cast<sal_uInt16>(m_pView->GetVisiblePos(m_pView->LastVisible()));
+ sal_uInt16 nThumb = static_cast<sal_uInt16>(m_pView->GetVisiblePos( m_pStartEntry ));
+ sal_uLong nCurDispEntries = nLast-nThumb+1;
+ if( nCurDispEntries >= m_nVisibleCount )
+ return;
+
+ ShowCursor( false );
+ // fill window by moving the thumb up incrementally
+ bool bFound = false;
+ SvTreeListEntry* pTemp = m_pStartEntry;
+ while( nCurDispEntries < m_nVisibleCount && pTemp )
+ {
+ pTemp = m_pView->PrevVisible(m_pStartEntry);
+ if( pTemp )
+ {
+ nThumb--;
+ m_pStartEntry = pTemp;
+ nCurDispEntries++;
+ bFound = true;
+ }
+ }
+ if( bFound )
+ {
+ m_aVerSBar->SetThumbPos( nThumb );
+ ShowCursor( true ); // recalculate focus rectangle
+ m_pView->Invalidate();
+ }
+}
+
+
+void SvImpLBox::ShowVerSBar()
+{
+ bool bVerBar = ( m_pView->GetStyle() & WB_VSCROLL ) != 0;
+ sal_uLong nVis = 0;
+ if( !bVerBar )
+ nVis = m_pView->GetVisibleCount();
+ if( bVerBar || (m_nVisibleCount && nVis > static_cast<sal_uLong>(m_nVisibleCount-1)) )
+ {
+ if( !m_aVerSBar->IsVisible() )
+ {
+ m_pView->nFocusWidth = -1;
+ AdjustScrollBars( m_aOutputSize );
+ if( GetUpdateMode() )
+ m_aVerSBar->Invalidate();
+ }
+ }
+ else
+ {
+ if( m_aVerSBar->IsVisible() )
+ {
+ m_pView->nFocusWidth = -1;
+ AdjustScrollBars( m_aOutputSize );
+ }
+ }
+
+ tools::Long nMaxRight = GetOutputSize().Width();
+ Point aPos( m_pView->GetMapMode().GetOrigin() );
+ aPos.setX( aPos.X() * -1 ); // convert document coordinates
+ nMaxRight = nMaxRight + aPos.X() - 1;
+ if( nMaxRight < m_nMostRight )
+ {
+ if( !m_aHorSBar->IsVisible() )
+ {
+ m_pView->nFocusWidth = -1;
+ AdjustScrollBars( m_aOutputSize );
+ if( GetUpdateMode() )
+ m_aHorSBar->Invalidate();
+ }
+ else
+ {
+ Range aRange( m_aHorSBar->GetRange() );
+ if( aRange.Max() < m_nMostRight+25 )
+ {
+ aRange.Max() = m_nMostRight+25;
+ m_aHorSBar->SetRange( aRange );
+ }
+ else
+ {
+ m_pView->nFocusWidth = -1;
+ AdjustScrollBars( m_aOutputSize );
+ }
+ }
+ }
+ else
+ {
+ if( m_aHorSBar->IsVisible() )
+ {
+ m_pView->nFocusWidth = -1;
+ AdjustScrollBars( m_aOutputSize );
+ }
+ }
+}
+
+
+void SvImpLBox::SyncVerThumb()
+{
+ if( m_pStartEntry )
+ {
+ tools::Long nEntryPos = m_pView->GetVisiblePos( m_pStartEntry );
+ m_aVerSBar->SetThumbPos( nEntryPos );
+ }
+ else
+ m_aVerSBar->SetThumbPos( 0 );
+}
+
+bool SvImpLBox::IsEntryInView( SvTreeListEntry* pEntry ) const
+{
+ // parent collapsed
+ if( !m_pView->IsEntryVisible(pEntry) )
+ return false;
+ tools::Long nY = GetEntryLine( pEntry );
+ if( nY < 0 )
+ return false;
+ tools::Long nMax = m_nVisibleCount * m_pView->GetEntryHeight();
+ return nY < nMax;
+}
+
+
+tools::Long SvImpLBox::GetEntryLine(const SvTreeListEntry* pEntry) const
+{
+ if(!m_pStartEntry )
+ return -1; // invisible position
+
+ tools::Long nFirstVisPos = m_pView->GetVisiblePos( m_pStartEntry );
+ tools::Long nEntryVisPos = m_pView->GetVisiblePos( pEntry );
+ nFirstVisPos = nEntryVisPos - nFirstVisPos;
+ nFirstVisPos *= m_pView->GetEntryHeight();
+ return nFirstVisPos;
+}
+
+void SvImpLBox::SetEntryHeight()
+{
+ SetNodeBmpWidth( GetExpandedNodeBmp() );
+ SetNodeBmpWidth( GetCollapsedNodeBmp() );
+ if(!m_pView->HasViewData()) // are we within the Clear?
+ {
+ Size aSize = m_pView->Control::GetOutputSizePixel();
+ AdjustScrollBars( aSize );
+ }
+ else
+ {
+ Resize();
+ if( GetUpdateMode() )
+ m_pView->Invalidate();
+ }
+}
+
+
+// ***********************************************************************
+// Callback Functions
+// ***********************************************************************
+
+void SvImpLBox::EntryExpanded( SvTreeListEntry* pEntry )
+{
+ // SelAllDestrAnch( false, true ); //DeselectAll();
+ if( !GetUpdateMode() )
+ return;
+
+ ShowCursor( false );
+ tools::Long nY = GetEntryLine( pEntry );
+ if( IsLineVisible(nY) )
+ {
+ InvalidateEntriesFrom( nY );
+ FindMostRight( pEntry );
+ }
+ m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1 ) );
+ // if we expanded before the thumb, the thumb's position has to be
+ // corrected
+ SyncVerThumb();
+ ShowVerSBar();
+ ShowCursor( true );
+}
+
+void SvImpLBox::EntryCollapsed( SvTreeListEntry* pEntry )
+{
+ if( !m_pView->IsEntryVisible( pEntry ) )
+ return;
+
+ ShowCursor( false );
+
+ if( !m_pMostRightEntry || m_pTree->IsChild( pEntry,m_pMostRightEntry ) )
+ {
+ FindMostRight();
+ }
+
+ if( m_pStartEntry )
+ {
+ tools::Long nOldThumbPos = m_aVerSBar->GetThumbPos();
+ sal_uLong nVisList = m_pView->GetVisibleCount();
+ m_aVerSBar->SetRange( Range(0, nVisList-1) );
+ tools::Long nNewThumbPos = m_aVerSBar->GetThumbPos();
+ if( nNewThumbPos != nOldThumbPos )
+ {
+ m_pStartEntry = m_pView->First();
+ sal_uInt16 nDistance = static_cast<sal_uInt16>(nNewThumbPos);
+ if( nDistance )
+ m_pStartEntry = m_pView->NextVisible(m_pStartEntry, nDistance);
+ if( GetUpdateMode() )
+ m_pView->Invalidate();
+ }
+ else
+ SyncVerThumb();
+ ShowVerSBar();
+ }
+ // has the cursor been collapsed?
+ if( m_pTree->IsChild( pEntry, m_pCursor ) )
+ SetCursor( pEntry );
+ if( GetUpdateMode() )
+ ShowVerSBar();
+ ShowCursor( true );
+ if( GetUpdateMode() && m_pCursor )
+ m_pView->Select( m_pCursor );
+}
+
+void SvImpLBox::CollapsingEntry( SvTreeListEntry* pEntry )
+{
+ if( !m_pView->IsEntryVisible( pEntry ) || !m_pStartEntry )
+ return;
+
+ SelAllDestrAnch( false ); // deselect all
+
+ // is the collapsed cursor visible?
+ tools::Long nY = GetEntryLine( pEntry );
+ if( IsLineVisible(nY) )
+ {
+ if( GetUpdateMode() )
+ InvalidateEntriesFrom( nY );
+ }
+ else
+ {
+ if( m_pTree->IsChild(pEntry, m_pStartEntry) )
+ {
+ m_pStartEntry = pEntry;
+ if( GetUpdateMode() )
+ m_pView->Invalidate();
+ }
+ }
+}
+
+
+void SvImpLBox::SetNodeBmpWidth( const Image& rBmp )
+{
+ const Size aSize( rBmp.GetSizePixel() );
+ m_nNodeBmpWidth = aSize.Width();
+}
+
+void SvImpLBox::SetNodeBmpTabDistance()
+{
+ m_nNodeBmpTabDistance = -m_pView->GetIndent();
+ if( m_pView->nContextBmpWidthMax )
+ {
+ // only if the first dynamic tab is centered (we currently assume that)
+ Size aSize = GetExpandedNodeBmp().GetSizePixel();
+ m_nNodeBmpTabDistance -= aSize.Width() / 2;
+ }
+}
+
+
+// corrects the cursor when using SingleSelection
+
+void SvImpLBox::EntrySelected( SvTreeListEntry* pEntry, bool bSelect )
+{
+ if( m_nFlags & LBoxFlags::IgnoreSelect )
+ return;
+
+ m_nFlags &= ~LBoxFlags::DeselectAll;
+ if( bSelect &&
+ m_aSelEng.GetSelectionMode() == SelectionMode::Single &&
+ pEntry != m_pCursor )
+ {
+ SetCursor( pEntry );
+ DBG_ASSERT(m_pView->GetSelectionCount()==1,"selection count?");
+ }
+
+ if( GetUpdateMode() && m_pView->IsEntryVisible(pEntry) )
+ {
+ tools::Long nY = GetEntryLine( pEntry );
+ if( IsLineVisible( nY ) )
+ {
+ ShowCursor(false);
+ InvalidateEntry(pEntry);
+ ShowCursor(true);
+ }
+ }
+}
+
+
+void SvImpLBox::RemovingEntry( SvTreeListEntry* pEntry )
+{
+ CallEventListeners( VclEventId::ListboxItemRemoved , pEntry );
+
+ DestroyAnchor();
+
+ if( !m_pView->IsEntryVisible( pEntry ) )
+ {
+ // if parent is collapsed => bye!
+ m_nFlags |= LBoxFlags::RemovedEntryInvisible;
+ return;
+ }
+
+ if( pEntry == m_pMostRightEntry || (
+ pEntry->HasChildren() && m_pView->IsExpanded(pEntry) &&
+ m_pTree->IsChild(pEntry, m_pMostRightEntry)))
+ {
+ m_nFlags |= LBoxFlags::RemovedRecalcMostRight;
+ }
+
+ SvTreeListEntry* pOldStartEntry = m_pStartEntry;
+
+ SvTreeListEntry* pParent = m_pView->GetModel()->GetParent(pEntry);
+
+ if (pParent && m_pView->GetModel()->GetChildList(pParent).size() == 1)
+ {
+ DBG_ASSERT( m_pView->IsExpanded( pParent ), "Parent not expanded");
+ pParent->SetFlags( pParent->GetFlags() | SvTLEntryFlags::NO_NODEBMP);
+ InvalidateEntry( pParent );
+ }
+
+ if( m_pCursor && m_pTree->IsChild( pEntry, m_pCursor) )
+ m_pCursor = pEntry;
+ if( m_pStartEntry && m_pTree->IsChild(pEntry,m_pStartEntry) )
+ m_pStartEntry = pEntry;
+
+ SvTreeListEntry* pTemp;
+ if( m_pCursor && m_pCursor == pEntry )
+ {
+ if( m_bSimpleTravel )
+ m_pView->Select( m_pCursor, false );
+ ShowCursor( false ); // focus rectangle gone
+ // NextSibling, because we also delete the children of the cursor
+ pTemp = m_pCursor->NextSibling();
+ if( !pTemp )
+ pTemp = m_pView->PrevVisible(m_pCursor);
+
+ SetCursor( pTemp, true );
+ }
+ if( m_pStartEntry && m_pStartEntry == pEntry )
+ {
+ pTemp = m_pStartEntry->NextSibling();
+ if( !pTemp )
+ pTemp = m_pView->PrevVisible(m_pStartEntry);
+ m_pStartEntry = pTemp;
+ }
+ if( GetUpdateMode())
+ {
+ // if it is the last one, we have to invalidate it, so the lines are
+ // drawn correctly (in this case they're deleted)
+ if( m_pStartEntry && (m_pStartEntry != pOldStartEntry || pEntry == m_pView->GetModel()->Last()) )
+ {
+ m_aVerSBar->SetThumbPos( m_pView->GetVisiblePos( m_pStartEntry ));
+ m_pView->Invalidate( GetVisibleArea() );
+ }
+ else
+ InvalidateEntriesFrom( GetEntryLine( pEntry ) );
+ }
+}
+
+void SvImpLBox::EntryRemoved()
+{
+ if( m_nFlags & LBoxFlags::RemovedEntryInvisible )
+ {
+ m_nFlags &= ~LBoxFlags::RemovedEntryInvisible;
+ return;
+ }
+ if( !m_pStartEntry )
+ m_pStartEntry = m_pTree->First();
+ if( !m_pCursor )
+ SetCursor( m_pStartEntry, true );
+
+ if( m_pCursor && (m_bSimpleTravel || !m_pView->GetSelectionCount() ))
+ m_pView->Select( m_pCursor );
+
+ if( GetUpdateMode())
+ {
+ if( m_nFlags & LBoxFlags::RemovedRecalcMostRight )
+ FindMostRight();
+ m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1 ) );
+ FillView();
+ if( m_pStartEntry )
+ // if something above the thumb was deleted
+ m_aVerSBar->SetThumbPos( m_pView->GetVisiblePos( m_pStartEntry) );
+
+ ShowVerSBar();
+ if( m_pCursor && m_pView->HasFocus() && !m_pView->IsSelected(m_pCursor) )
+ {
+ if( m_pView->GetSelectionCount() )
+ {
+ // is a neighboring entry selected?
+ SvTreeListEntry* pNextCursor = m_pView->PrevVisible( m_pCursor );
+ if( !pNextCursor || !m_pView->IsSelected( pNextCursor ))
+ pNextCursor = m_pView->NextVisible( m_pCursor );
+ if( !pNextCursor || !m_pView->IsSelected( pNextCursor ))
+ // no neighbor selected: use first selected
+ pNextCursor = m_pView->FirstSelected();
+ SetCursor( pNextCursor );
+ MakeVisible( m_pCursor );
+ }
+ else
+ m_pView->Select( m_pCursor );
+ }
+ ShowCursor( true );
+ }
+ m_nFlags &= ~LBoxFlags::RemovedRecalcMostRight;
+}
+
+
+void SvImpLBox::MovingEntry( SvTreeListEntry* pEntry )
+{
+ bool bDeselAll(m_nFlags & LBoxFlags::DeselectAll);
+ SelAllDestrAnch( false ); // DeselectAll();
+ if( !bDeselAll )
+ m_nFlags &= ~LBoxFlags::DeselectAll;
+
+ if( pEntry == m_pCursor )
+ ShowCursor( false );
+ if( IsEntryInView( pEntry ) )
+ m_pView->Invalidate();
+ if( pEntry != m_pStartEntry )
+ return;
+
+ SvTreeListEntry* pNew = nullptr;
+ if( !pEntry->HasChildren() )
+ {
+ pNew = m_pView->NextVisible(m_pStartEntry);
+ if( !pNew )
+ pNew = m_pView->PrevVisible(m_pStartEntry);
+ }
+ else
+ {
+ pNew = pEntry->NextSibling();
+ if( !pNew )
+ pNew = pEntry->PrevSibling();
+ }
+ m_pStartEntry = pNew;
+}
+
+void SvImpLBox::EntryMoved( SvTreeListEntry* pEntry )
+{
+ UpdateContextBmpWidthVectorFromMovedEntry( pEntry );
+
+ if ( !m_pStartEntry )
+ // this might happen if the only entry in the view is moved to its very same position
+ // #i97346#
+ m_pStartEntry = m_pView->First();
+
+ m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1));
+ sal_uInt16 nFirstPos = static_cast<sal_uInt16>(m_pTree->GetAbsPos( m_pStartEntry ));
+ sal_uInt16 nNewPos = static_cast<sal_uInt16>(m_pTree->GetAbsPos( pEntry ));
+ FindMostRight();
+ if( nNewPos < nFirstPos ) // HACK!
+ m_pStartEntry = pEntry;
+ SyncVerThumb();
+ if( pEntry == m_pCursor )
+ {
+ if( m_pView->IsEntryVisible( m_pCursor ) )
+ ShowCursor( true );
+ else
+ {
+ SvTreeListEntry* pParent = pEntry;
+ do {
+ pParent = m_pTree->GetParent( pParent );
+ }
+ while( !m_pView->IsEntryVisible( pParent ) );
+ SetCursor( pParent );
+ }
+ }
+ if( IsEntryInView( pEntry ) )
+ m_pView->Invalidate();
+}
+
+
+void SvImpLBox::EntryInserted( SvTreeListEntry* pEntry )
+{
+ if( !GetUpdateMode() )
+ return;
+
+ SvTreeListEntry* pParent = m_pTree->GetParent(pEntry);
+ if (pParent && m_pTree->GetChildList(pParent).size() == 1)
+ // draw plus sign
+ m_pTree->InvalidateEntry( pParent );
+
+ if( !m_pView->IsEntryVisible( pEntry ) )
+ return;
+ bool bDeselAll(m_nFlags & LBoxFlags::DeselectAll);
+ if( bDeselAll )
+ SelAllDestrAnch( false );
+ else
+ DestroyAnchor();
+ // nFlags &= (~LBoxFlags::DeselectAll);
+// ShowCursor( false ); // if cursor is moved lower
+ tools::Long nY = GetEntryLine( pEntry );
+ bool bEntryVisible = IsLineVisible( nY );
+ if( bEntryVisible )
+ {
+ ShowCursor( false ); // if cursor is moved lower
+ nY -= m_pView->GetEntryHeight(); // because of lines
+ InvalidateEntriesFrom( nY );
+ }
+ else if( m_pStartEntry && nY < GetEntryLine(m_pStartEntry) )
+ {
+ // Check if the view is filled completely. If not, then adjust
+ // pStartEntry and the Cursor (automatic scrolling).
+ sal_uInt16 nLast = static_cast<sal_uInt16>(m_pView->GetVisiblePos(m_pView->LastVisible()));
+ sal_uInt16 nThumb = static_cast<sal_uInt16>(m_pView->GetVisiblePos( m_pStartEntry ));
+ sal_uInt16 nCurDispEntries = nLast-nThumb+1;
+ if( nCurDispEntries < m_nVisibleCount )
+ {
+ // set at the next paint event
+ m_pStartEntry = nullptr;
+ SetCursor( nullptr );
+ m_pView->Invalidate();
+ }
+ }
+ else if( !m_pStartEntry )
+ m_pView->Invalidate();
+
+ SetMostRight( pEntry );
+ m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1));
+ SyncVerThumb(); // if something was inserted before the thumb
+ ShowVerSBar();
+ ShowCursor( true );
+ if( m_pStartEntry != m_pView->First() && (m_nFlags & LBoxFlags::Filling) )
+ m_pView->PaintImmediately();
+}
+
+
+// ********************************************************************
+// Event handler
+// ********************************************************************
+
+
+// ****** Control the control animation
+
+bool SvImpLBox::ButtonDownCheckCtrl(const MouseEvent& rMEvt, SvTreeListEntry* pEntry)
+{
+ SvLBoxItem* pItem = m_pView->GetItem(pEntry,rMEvt.GetPosPixel().X(),&m_pActiveTab);
+ if (pItem && pItem->GetType() == SvLBoxItemType::Button)
+ {
+ m_pActiveButton = static_cast<SvLBoxButton*>(pItem);
+ m_pActiveEntry = pEntry;
+ if( m_pCursor == m_pActiveEntry )
+ m_pView->HideFocus();
+ m_pView->CaptureMouse();
+ m_pActiveButton->SetStateHilighted( true );
+ InvalidateEntry(m_pActiveEntry);
+ return true;
+ }
+ else
+ m_pActiveButton = nullptr;
+ return false;
+}
+
+bool SvImpLBox::MouseMoveCheckCtrl(const MouseEvent& rMEvt, SvTreeListEntry const * pEntry)
+{
+ if( m_pActiveButton )
+ {
+ tools::Long nMouseX = rMEvt.GetPosPixel().X();
+ if( pEntry == m_pActiveEntry &&
+ m_pView->GetItem(m_pActiveEntry, nMouseX) == m_pActiveButton )
+ {
+ if( !m_pActiveButton->IsStateHilighted() )
+ {
+ m_pActiveButton->SetStateHilighted(true );
+ InvalidateEntry(m_pActiveEntry);
+ }
+ }
+ else
+ {
+ if( m_pActiveButton->IsStateHilighted() )
+ {
+ m_pActiveButton->SetStateHilighted(false );
+ InvalidateEntry(m_pActiveEntry);
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SvImpLBox::ButtonUpCheckCtrl( const MouseEvent& rMEvt )
+{
+ if( m_pActiveButton )
+ {
+ m_pView->ReleaseMouse();
+ SvTreeListEntry* pEntry = GetClickedEntry( rMEvt.GetPosPixel() );
+ m_pActiveButton->SetStateHilighted( false );
+ tools::Long nMouseX = rMEvt.GetPosPixel().X();
+ if (pEntry == m_pActiveEntry && m_pView->GetItem(m_pActiveEntry, nMouseX) == m_pActiveButton)
+ m_pActiveButton->ClickHdl(m_pActiveEntry);
+ InvalidateEntry(m_pActiveEntry);
+ if (m_pCursor == m_pActiveEntry)
+ ShowCursor(true);
+ m_pActiveButton = nullptr;
+ m_pActiveEntry = nullptr;
+ m_pActiveTab = nullptr;
+ return true;
+ }
+ return false;
+}
+
+// ******* Control plus/minus button for expanding/collapsing
+
+// false == no expand/collapse button hit
+bool SvImpLBox::IsNodeButton( const Point& rPosPixel, const SvTreeListEntry* pEntry ) const
+{
+ if( !pEntry->HasChildren() && !pEntry->HasChildrenOnDemand() )
+ return false;
+
+ SvLBoxTab* pFirstDynamicTab = m_pView->GetFirstDynamicTab();
+ if( !pFirstDynamicTab )
+ return false;
+
+ tools::Long nMouseX = rPosPixel.X();
+ // convert to document coordinates
+ Point aOrigin( m_pView->GetMapMode().GetOrigin() );
+ nMouseX -= aOrigin.X();
+
+ tools::Long nX = m_pView->GetTabPos( pEntry, pFirstDynamicTab);
+ nX += m_nNodeBmpTabDistance;
+ if( nMouseX < nX )
+ return false;
+ nX += m_nNodeBmpWidth;
+ return nMouseX <= nX;
+}
+
+// false == hit no node button
+bool SvImpLBox::ButtonDownCheckExpand( const MouseEvent& rMEvt, SvTreeListEntry* pEntry )
+{
+ bool bRet = false;
+
+ if ( m_pView->IsEditingActive() && pEntry == m_pView->pEdEntry )
+ // inplace editing -> nothing to do
+ bRet = true;
+ else if ( IsNodeButton( rMEvt.GetPosPixel(), pEntry ) )
+ {
+ if ( m_pView->IsExpanded( pEntry ) )
+ {
+ m_pView->EndEditing( true );
+ m_pView->Collapse( pEntry );
+ }
+ else
+ {
+ // you can expand an entry, which is in editing
+ m_pView->Expand( pEntry );
+ }
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+void SvImpLBox::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( !rMEvt.IsLeft() && !rMEvt.IsRight())
+ return;
+
+ m_aEditIdle.Stop();
+ Point aPos( rMEvt.GetPosPixel());
+
+ if( aPos.X() > m_aOutputSize.Width() || aPos.Y() > m_aOutputSize.Height() )
+ return;
+
+ if( !m_pCursor )
+ m_pCursor = m_pStartEntry;
+ m_nFlags &= ~LBoxFlags::Filling;
+ m_pView->GrabFocus();
+ //fdo#82270 Grabbing focus can invalidate the entries, re-fetch
+ SvTreeListEntry* pEntry = GetEntry(aPos);
+ // the entry can still be invalid!
+ if( !pEntry || !m_pView->GetViewData( pEntry ))
+ return;
+
+ tools::Long nY = GetEntryLine( pEntry );
+ // Node-Button?
+ if( ButtonDownCheckExpand( rMEvt, pEntry ) )
+ return;
+
+ if( !EntryReallyHit(pEntry,aPos,nY))
+ return;
+
+ SvLBoxItem* pXItem = m_pView->GetItem( pEntry, aPos.X() );
+ if( pXItem )
+ {
+ SvLBoxTab* pXTab = m_pView->GetTab( pEntry, pXItem );
+ if ( !rMEvt.IsMod1() && !rMEvt.IsMod2() && rMEvt.IsLeft() && pXTab->IsEditable()
+ && pEntry == m_pView->FirstSelected() && nullptr == m_pView->NextSelected( pEntry ) )
+ // #i8234# FirstSelected() and NextSelected() ensures, that inplace editing is only triggered, when only one entry is selected
+ m_nFlags |= LBoxFlags::StartEditTimer;
+ if ( !m_pView->IsSelected( pEntry ) )
+ m_nFlags &= ~LBoxFlags::StartEditTimer;
+ }
+
+
+ if( (rMEvt.GetClicks() % 2) == 0)
+ {
+ m_nFlags &= ~LBoxFlags::StartEditTimer;
+ m_pView->pHdlEntry = pEntry;
+ if( !m_pView->DoubleClickHdl() )
+ {
+ // Handler signals nothing to be done anymore, bail out, 'this' may
+ // even be dead and destroyed.
+ return;
+ }
+ else
+ {
+ // if the entry was deleted within the handler
+ pEntry = GetClickedEntry( aPos );
+ if( !pEntry )
+ return;
+ if( pEntry != m_pView->pHdlEntry )
+ {
+ // select anew & bye
+ if( !m_bSimpleTravel && !m_aSelEng.IsAlwaysAdding())
+ SelAllDestrAnch( false ); // DeselectAll();
+ SetCursor( pEntry );
+
+ return;
+ }
+ if( pEntry->HasChildren() || pEntry->HasChildrenOnDemand() )
+ {
+ if( m_pView->IsExpanded(pEntry) )
+ m_pView->Collapse( pEntry );
+ else
+ m_pView->Expand( pEntry );
+ if( pEntry == m_pCursor ) // only if Entryitem was clicked
+ // (Nodebutton is not an Entryitem!)
+ m_pView->Select( m_pCursor );
+ return;
+ }
+ }
+ }
+ else
+ {
+ // CheckButton? (TreeListBox: Check + Info)
+ if( ButtonDownCheckCtrl(rMEvt, pEntry) )
+ return;
+ // Inplace-Editing?
+ }
+ if ( m_aSelEng.GetSelectionMode() != SelectionMode::NONE
+ && !rMEvt.IsRight() ) // tdf#128824
+ m_aSelEng.SelMouseButtonDown( rMEvt );
+}
+
+void SvImpLBox::MouseButtonUp( const MouseEvent& rMEvt)
+{
+ if ( !ButtonUpCheckCtrl( rMEvt ) && ( m_aSelEng.GetSelectionMode() != SelectionMode::NONE ) )
+ m_aSelEng.SelMouseButtonUp( rMEvt );
+ if( m_nFlags & LBoxFlags::StartEditTimer )
+ {
+ m_nFlags &= ~LBoxFlags::StartEditTimer;
+ m_aEditClickPos = rMEvt.GetPosPixel();
+ m_aEditIdle.Start();
+ }
+
+ if (m_pView->mbActivateOnSingleClick)
+ {
+ Point aPos(rMEvt.GetPosPixel());
+ SvTreeListEntry* pEntry = GetEntry(aPos);
+ // tdf#143245 ActivateOnSingleClick only
+ // if the 'up' is at the active entry
+ // typically selected by the 'down'
+ if (!pEntry || pEntry != m_pCursor)
+ return;
+ m_pView->DoubleClickHdl();
+ }
+}
+
+void SvImpLBox::MouseMove( const MouseEvent& rMEvt)
+{
+ Point aPos = rMEvt.GetPosPixel();
+ SvTreeListEntry* pEntry = GetClickedEntry(aPos);
+ if ( MouseMoveCheckCtrl( rMEvt, pEntry ) || ( m_aSelEng.GetSelectionMode() == SelectionMode::NONE ) )
+ return;
+
+ m_aSelEng.SelMouseMove(rMEvt);
+ if (m_pView->mbHoverSelection)
+ {
+ if (aPos.X() < 0 || aPos.Y() < 0 || aPos.X() > m_aOutputSize.Width() || aPos.Y() > m_aOutputSize.Height())
+ pEntry = nullptr;
+ else
+ pEntry = GetEntry(aPos);
+ if (!pEntry)
+ m_pView->SelectAll(false);
+ else if (!m_pView->IsSelected(pEntry) && IsSelectable(pEntry))
+ {
+ m_pView->mbSelectingByHover = true;
+ m_pView->Select(pEntry);
+ m_pView->mbSelectingByHover = false;
+ }
+ }
+}
+
+void SvImpLBox::ExpandAll()
+{
+ sal_uInt16 nRefDepth = m_pTree->GetDepth(m_pCursor);
+ SvTreeListEntry* pCur = m_pTree->Next(m_pCursor);
+ while (pCur && m_pTree->GetDepth(pCur) > nRefDepth)
+ {
+ if (pCur->HasChildren() && !m_pView->IsExpanded(pCur))
+ m_pView->Expand(pCur);
+ pCur = m_pTree->Next(pCur);
+ }
+}
+
+void SvImpLBox::CollapseTo(SvTreeListEntry* pParentToCollapse)
+{
+ // collapse all parents until we get to the given parent to collapse
+ if (!pParentToCollapse)
+ return;
+
+ sal_uInt16 nRefDepth;
+ // special case explorer: if the root only has a single
+ // entry, don't collapse the root entry
+ if (m_pTree->GetChildList(nullptr).size() < 2)
+ {
+ nRefDepth = 1;
+ pParentToCollapse = m_pCursor;
+ while (m_pTree->GetParent(pParentToCollapse)
+ && m_pTree->GetDepth(m_pTree->GetParent(pParentToCollapse)) > 0)
+ {
+ pParentToCollapse = m_pTree->GetParent(pParentToCollapse);
+ }
+ }
+ else
+ nRefDepth = m_pTree->GetDepth(pParentToCollapse);
+
+ if (m_pView->IsExpanded(pParentToCollapse))
+ m_pView->Collapse(pParentToCollapse);
+ SvTreeListEntry* pCur = m_pTree->Next(pParentToCollapse);
+ while (pCur && m_pTree->GetDepth(pCur) > nRefDepth)
+ {
+ if (pCur->HasChildren() && m_pView->IsExpanded(pCur))
+ m_pView->Collapse(pCur);
+ pCur = m_pTree->Next(pCur);
+ }
+}
+
+bool SvImpLBox::KeyInput( const KeyEvent& rKEvt)
+{
+ m_aEditIdle.Stop();
+ const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
+
+ if( rKeyCode.IsMod2() )
+ return false; // don't evaluate Alt key
+
+ m_nFlags &= ~LBoxFlags::Filling;
+
+ if( !m_pCursor )
+ m_pCursor = m_pStartEntry;
+ if( !m_pCursor )
+ return false;
+
+ bool bKeyUsed = true;
+
+ sal_uInt16 nDelta = static_cast<sal_uInt16>(m_aVerSBar->GetPageSize());
+ sal_uInt16 aCode = rKeyCode.GetCode();
+
+ bool bShift = rKeyCode.IsShift();
+ bool bMod1 = rKeyCode.IsMod1();
+
+ SvTreeListEntry* pNewCursor;
+
+ switch( aCode )
+ {
+ case KEY_UP:
+ if( !IsEntryInView( m_pCursor ) )
+ MakeVisible( m_pCursor );
+
+ pNewCursor = m_pCursor;
+ do
+ {
+ pNewCursor = m_pView->PrevVisible(pNewCursor);
+ } while( pNewCursor && !IsSelectable(pNewCursor) );
+
+ // if there is no next entry, take the current one
+ // this ensures that in case of _one_ entry in the list, this entry is selected when pressing
+ // the cursor key
+ if (!pNewCursor)
+ pNewCursor = m_pCursor;
+
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on
+ if( !IsEntryInView( pNewCursor ) )
+ KeyUp( false );
+ break;
+
+ case KEY_DOWN:
+ if( !IsEntryInView( m_pCursor ) )
+ MakeVisible( m_pCursor );
+
+ pNewCursor = m_pCursor;
+ do
+ {
+ pNewCursor = m_pView->NextVisible(pNewCursor);
+ } while( pNewCursor && !IsSelectable(pNewCursor) );
+
+ // if there is no next entry, take the current one
+ // this ensures that in case of _one_ entry in the list, this entry is selected when pressing
+ // the cursor key
+ // 06.09.20001 - 83416 - frank.schoenheit@sun.com
+ if ( !pNewCursor && m_pCursor )
+ pNewCursor = m_pCursor;
+
+ if( pNewCursor )
+ {
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ if( IsEntryInView( pNewCursor ) )
+ SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on
+ else
+ {
+ if( m_pCursor )
+ m_pView->Select( m_pCursor, false );
+ KeyDown( false );
+ SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on
+ }
+ }
+ else
+ KeyDown( false ); // because scrollbar range might still
+ // allow scrolling
+ break;
+
+ case KEY_RIGHT:
+ {
+ if( m_bSubLstOpLR )
+ {
+ // only try to expand if sublist is expandable,
+ // otherwise ignore the key press
+ if( IsExpandable() && !m_pView->IsExpanded( m_pCursor ) )
+ m_pView->Expand( m_pCursor );
+ }
+ else if (m_aHorSBar->IsVisible())
+ {
+ tools::Long nThumb = m_aHorSBar->GetThumbPos();
+ nThumb += m_aHorSBar->GetLineSize();
+ tools::Long nOldThumb = m_aHorSBar->GetThumbPos();
+ m_aHorSBar->SetThumbPos( nThumb );
+ nThumb = nOldThumb;
+ nThumb -= m_aHorSBar->GetThumbPos();
+ nThumb *= -1;
+ if( nThumb )
+ {
+ KeyLeftRight( nThumb );
+ }
+ }
+ else
+ bKeyUsed = false;
+ break;
+ }
+
+ case KEY_LEFT:
+ {
+ if (m_aHorSBar->IsVisible())
+ {
+ tools::Long nThumb = m_aHorSBar->GetThumbPos();
+ nThumb -= m_aHorSBar->GetLineSize();
+ tools::Long nOldThumb = m_aHorSBar->GetThumbPos();
+ m_aHorSBar->SetThumbPos( nThumb );
+ nThumb = nOldThumb;
+ nThumb -= m_aHorSBar->GetThumbPos();
+ if( nThumb )
+ {
+ KeyLeftRight( -nThumb );
+ }
+ else if( m_bSubLstOpLR )
+ {
+ if( IsExpandable() && m_pView->IsExpanded( m_pCursor ) )
+ m_pView->Collapse( m_pCursor );
+ else
+ {
+ pNewCursor = m_pView->GetParent( m_pCursor );
+ if( pNewCursor )
+ SetCursor( pNewCursor );
+ }
+ }
+ }
+ else if( m_bSubLstOpLR )
+ {
+ if( IsExpandable() && m_pView->IsExpanded( m_pCursor ) )
+ m_pView->Collapse( m_pCursor );
+ else
+ {
+ pNewCursor = m_pView->GetParent( m_pCursor );
+ if( pNewCursor )
+ SetCursor( pNewCursor );
+ }
+ }
+ else
+ bKeyUsed = false;
+ break;
+ }
+
+ case KEY_PAGEUP:
+ if( !bMod1 )
+ {
+ pNewCursor = m_pView->PrevVisible(m_pCursor, nDelta);
+
+ while( nDelta && pNewCursor && !IsSelectable(pNewCursor) )
+ {
+ pNewCursor = m_pView->NextVisible(pNewCursor);
+ nDelta--;
+ }
+
+ if( nDelta )
+ {
+ DBG_ASSERT(pNewCursor && pNewCursor!=m_pCursor, "Cursor?");
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ if( IsEntryInView( pNewCursor ) )
+ SetCursor( pNewCursor );
+ else
+ {
+ SetCursor( pNewCursor );
+ KeyUp( true );
+ }
+ }
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_PAGEDOWN:
+ if( !bMod1 )
+ {
+ pNewCursor= m_pView->NextVisible(m_pCursor, nDelta);
+
+ while( nDelta && pNewCursor && !IsSelectable(pNewCursor) )
+ {
+ pNewCursor = m_pView->PrevVisible(pNewCursor);
+ nDelta--;
+ }
+
+ if( nDelta && pNewCursor )
+ {
+ DBG_ASSERT(pNewCursor && pNewCursor!=m_pCursor, "Cursor?");
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ if( IsEntryInView( pNewCursor ) )
+ SetCursor( pNewCursor );
+ else
+ {
+ SetCursor( pNewCursor );
+ KeyDown( true );
+ }
+ }
+ else
+ KeyDown( false ); // see also: KEY_DOWN
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_SPACE:
+ if ( m_pView->GetSelectionMode() != SelectionMode::NONE )
+ {
+ if ( bMod1 )
+ {
+ if ( m_pView->GetSelectionMode() == SelectionMode::Multiple && !bShift )
+ // toggle selection
+ m_pView->Select( m_pCursor, !m_pView->IsSelected( m_pCursor ) );
+ }
+ else if ( !bShift /*&& !bMod1*/ )
+ {
+ if ( m_aSelEng.IsAddMode() )
+ {
+ // toggle selection
+ m_pView->Select( m_pCursor, !m_pView->IsSelected( m_pCursor ) );
+ }
+ else if ( !m_pView->IsSelected( m_pCursor ) )
+ {
+ SelAllDestrAnch( false );
+ m_pView->Select( m_pCursor );
+ }
+ else
+ bKeyUsed = false;
+ }
+ else
+ bKeyUsed = false;
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_RETURN:
+ bKeyUsed = !m_pView->DoubleClickHdl();
+ break;
+
+ case KEY_F2:
+ if( !bShift && !bMod1 )
+ {
+ m_aEditClickPos = Point( -1, -1 );
+ EditTimerCall( nullptr );
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_F8:
+ if( bShift && m_pView->GetSelectionMode()==SelectionMode::Multiple &&
+ !(m_nStyle & WB_SIMPLEMODE))
+ {
+ if( m_aSelEng.IsAlwaysAdding() )
+ m_aSelEng.AddAlways( false );
+ else
+ m_aSelEng.AddAlways( true );
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_ADD:
+ if (!m_pView->IsExpanded(m_pCursor))
+ m_pView->Expand(m_pCursor);
+ if (bMod1)
+ ExpandAll();
+ break;
+
+ case KEY_A:
+ if( bMod1 )
+ SelAllDestrAnch( true );
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_SUBTRACT:
+ if (m_pView->IsExpanded(m_pCursor))
+ m_pView->Collapse(m_pCursor);
+ if (bMod1)
+ CollapseTo(m_pTree->GetRootLevelParent(m_pCursor));
+ break;
+
+ case KEY_MULTIPLY:
+ if( bMod1 )
+ {
+ // only try to expand/collapse if sublist is expandable,
+ // otherwise ignore the key press
+ if( IsExpandable() )
+ {
+ if (!m_pView->IsAllExpanded(m_pCursor))
+ {
+ m_pView->Expand(m_pCursor);
+ ExpandAll();
+ }
+ else
+ CollapseTo(m_pCursor);
+ }
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_DIVIDE :
+ if( bMod1 )
+ SelAllDestrAnch( true );
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_COMMA :
+ if( bMod1 )
+ SelAllDestrAnch( false );
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_HOME :
+ pNewCursor = m_pView->GetModel()->First();
+
+ while( pNewCursor && !IsSelectable(pNewCursor) )
+ {
+ pNewCursor = m_pView->NextVisible(pNewCursor);
+ }
+
+ if( pNewCursor && pNewCursor != m_pCursor )
+ {
+// SelAllDestrAnch( false );
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ SetCursor( pNewCursor );
+ if( !IsEntryInView( pNewCursor ) )
+ MakeVisible( pNewCursor );
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_END :
+ pNewCursor = m_pView->GetModel()->Last();
+
+ while( pNewCursor && !IsSelectable(pNewCursor) )
+ {
+ pNewCursor = m_pView->PrevVisible(pNewCursor);
+ }
+
+ if( pNewCursor && pNewCursor != m_pCursor)
+ {
+// SelAllDestrAnch( false );
+ m_aSelEng.CursorPosChanging( bShift, bMod1 );
+ SetCursor( pNewCursor );
+ if( !IsEntryInView( pNewCursor ) )
+ MakeVisible( pNewCursor );
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_ESCAPE:
+ case KEY_TAB:
+ case KEY_DELETE:
+ case KEY_BACKSPACE:
+ // must not be handled because this quits dialogs and does other magic things...
+ // if there are other single keys which should not be handled, they can be added here
+ bKeyUsed = false;
+ break;
+
+ default:
+ // is there any reason why we should eat the events here? The only place where this is called
+ // is from SvTreeListBox::KeyInput. If we set bKeyUsed to true here, then the key input
+ // is just silenced. However, we want SvLBox::KeyInput to get a chance, to do the QuickSelection
+ // handling.
+ // (The old code here which intentionally set bKeyUsed to sal_True said this was because of "quick search"
+ // handling, but actually there was no quick search handling anymore. We just re-implemented it.)
+ // #i31275# / 2009-06-16 / frank.schoenheit@sun.com
+ bKeyUsed = false;
+ break;
+ }
+ return bKeyUsed;
+}
+
+void SvImpLBox::GetFocus()
+{
+ if( m_pCursor )
+ {
+ m_pView->SetEntryFocus( m_pCursor, true );
+ ShowCursor( true );
+// auskommentiert wg. deselectall
+// if( bSimpleTravel && !pView->IsSelected(pCursor) )
+// pView->Select( pCursor, true );
+ }
+ if( m_nStyle & WB_HIDESELECTION )
+ {
+ SvTreeListEntry* pEntry = m_pView->FirstSelected();
+ while( pEntry )
+ {
+ InvalidateEntry( pEntry );
+ pEntry = m_pView->NextSelected( pEntry );
+ }
+ }
+}
+
+void SvImpLBox::LoseFocus()
+{
+ m_aEditIdle.Stop();
+ if( m_pCursor )
+ m_pView->SetEntryFocus( m_pCursor,false );
+ ShowCursor( false );
+
+ if( m_nStyle & WB_HIDESELECTION )
+ {
+ SvTreeListEntry* pEntry = m_pView ? m_pView->FirstSelected() : nullptr;
+ while( pEntry )
+ {
+ InvalidateEntry( pEntry );
+ pEntry = m_pView->NextSelected( pEntry );
+ }
+ }
+}
+
+
+// ********************************************************************
+// SelectionEngine
+// ********************************************************************
+
+void SvImpLBox::SelectEntry( SvTreeListEntry* pEntry, bool bSelect )
+{
+ m_pView->Select( pEntry, bSelect );
+}
+
+ImpLBSelEng::ImpLBSelEng( SvImpLBox* pImpl, SvTreeListBox* pV )
+{
+ pImp = pImpl;
+ pView = pV;
+}
+
+ImpLBSelEng::~ImpLBSelEng()
+{
+}
+
+void ImpLBSelEng::BeginDrag()
+{
+ pImp->BeginDrag();
+}
+
+void ImpLBSelEng::CreateAnchor()
+{
+ pImp->m_pAnchor = pImp->m_pCursor;
+}
+
+void ImpLBSelEng::DestroyAnchor()
+{
+ pImp->m_pAnchor = nullptr;
+}
+
+void ImpLBSelEng::SetCursorAtPoint(const Point& rPoint, bool bDontSelectAtCursor)
+{
+ SvTreeListEntry* pNewCursor = pImp->MakePointVisible( rPoint );
+ if( pNewCursor )
+ {
+ // at SimpleTravel, the SetCursor is selected and the select handler is
+ // called
+ //if( !bDontSelectAtCursor && !pImp->bSimpleTravel )
+ // pImp->SelectEntry( pNewCursor, true );
+ pImp->SetCursor( pNewCursor, bDontSelectAtCursor );
+ }
+}
+
+bool ImpLBSelEng::IsSelectionAtPoint( const Point& rPoint )
+{
+ SvTreeListEntry* pEntry = pImp->MakePointVisible( rPoint );
+ if( pEntry )
+ return pView->IsSelected(pEntry);
+ return false;
+}
+
+void ImpLBSelEng::DeselectAtPoint( const Point& rPoint )
+{
+ SvTreeListEntry* pEntry = pImp->MakePointVisible( rPoint );
+ if( !pEntry )
+ return;
+ pImp->SelectEntry( pEntry, false );
+}
+
+void ImpLBSelEng::DeselectAll()
+{
+ pImp->SelAllDestrAnch( false, false ); // don't reset SelectionEngine!
+ pImp->m_nFlags &= ~LBoxFlags::DeselectAll;
+}
+
+// ***********************************************************************
+// Selection
+// ***********************************************************************
+
+void SvImpLBox::SetAnchorSelection(SvTreeListEntry* pOldCursor,SvTreeListEntry* pNewCursor)
+{
+ SvTreeListEntry* pEntry;
+ sal_uLong nAnchorVisPos = m_pView->GetVisiblePos( m_pAnchor );
+ sal_uLong nOldVisPos = m_pView->GetVisiblePos( pOldCursor );
+ sal_uLong nNewVisPos = m_pView->GetVisiblePos( pNewCursor );
+
+ if( nOldVisPos > nAnchorVisPos ||
+ ( nAnchorVisPos==nOldVisPos && nNewVisPos > nAnchorVisPos) )
+ {
+ if( nNewVisPos > nOldVisPos )
+ {
+ pEntry = pOldCursor;
+ while( pEntry && pEntry != pNewCursor )
+ {
+ m_pView->Select( pEntry );
+ pEntry = m_pView->NextVisible(pEntry);
+ }
+ if( pEntry )
+ m_pView->Select( pEntry );
+ return;
+ }
+
+ if( nNewVisPos < nAnchorVisPos )
+ {
+ pEntry = m_pAnchor;
+ while( pEntry && pEntry != pOldCursor )
+ {
+ m_pView->Select( pEntry, false );
+ pEntry = m_pView->NextVisible(pEntry);
+ }
+ if( pEntry )
+ m_pView->Select( pEntry, false );
+
+ pEntry = pNewCursor;
+ while( pEntry && pEntry != m_pAnchor )
+ {
+ m_pView->Select( pEntry );
+ pEntry = m_pView->NextVisible(pEntry);
+ }
+ if( pEntry )
+ m_pView->Select( pEntry );
+ return;
+ }
+
+ if( nNewVisPos < nOldVisPos )
+ {
+ pEntry = m_pView->NextVisible(pNewCursor);
+ while( pEntry && pEntry != pOldCursor )
+ {
+ m_pView->Select( pEntry, false );
+ pEntry = m_pView->NextVisible(pEntry);
+ }
+ if( pEntry )
+ m_pView->Select( pEntry, false );
+ return;
+ }
+ }
+ else
+ {
+ if( nNewVisPos < nOldVisPos ) // enlarge selection
+ {
+ pEntry = pNewCursor;
+ while( pEntry && pEntry != pOldCursor )
+ {
+ m_pView->Select( pEntry );
+ pEntry = m_pView->NextVisible(pEntry);
+ }
+ if( pEntry )
+ m_pView->Select( pEntry );
+ return;
+ }
+
+ if( nNewVisPos > nAnchorVisPos )
+ {
+ pEntry = pOldCursor;
+ while( pEntry && pEntry != m_pAnchor )
+ {
+ m_pView->Select( pEntry, false );
+ pEntry = m_pView->NextVisible(pEntry);
+ }
+ if( pEntry )
+ m_pView->Select( pEntry, false );
+ pEntry = m_pAnchor;
+ while( pEntry && pEntry != pNewCursor )
+ {
+ m_pView->Select( pEntry );
+ pEntry = m_pView->NextVisible(pEntry);
+ }
+ if( pEntry )
+ m_pView->Select( pEntry );
+ return;
+ }
+
+ if( nNewVisPos > nOldVisPos )
+ {
+ pEntry = pOldCursor;
+ while( pEntry && pEntry != pNewCursor )
+ {
+ m_pView->Select( pEntry, false );
+ pEntry = m_pView->NextVisible(pEntry);
+ }
+ return;
+ }
+ }
+}
+
+void SvImpLBox::SelAllDestrAnch(
+ bool bSelect, bool bDestroyAnchor, bool bSingleSelToo )
+{
+ SvTreeListEntry* pEntry;
+ m_nFlags &= ~LBoxFlags::DeselectAll;
+ if( bSelect && m_bSimpleTravel )
+ {
+ if( m_pCursor && !m_pView->IsSelected( m_pCursor ))
+ {
+ m_pView->Select( m_pCursor );
+ }
+ return;
+ }
+ if( !bSelect && m_pView->GetSelectionCount() == 0 )
+ {
+ if( m_bSimpleTravel && ( !GetUpdateMode() || !m_pCursor) )
+ m_nFlags |= LBoxFlags::DeselectAll;
+ return;
+ }
+ if( bSelect && m_pView->GetSelectionCount() == m_pView->GetEntryCount())
+ return;
+ if( !bSingleSelToo && m_bSimpleTravel )
+ return;
+
+ if( !bSelect && m_pView->GetSelectionCount()==1 && m_pCursor &&
+ m_pView->IsSelected( m_pCursor ))
+ {
+ m_pView->Select( m_pCursor, false );
+ if( bDestroyAnchor )
+ DestroyAnchor(); // delete anchor & reset SelectionEngine
+ else
+ m_pAnchor = nullptr; // always delete internal anchor
+ return;
+ }
+
+ if( m_bSimpleTravel && !m_pCursor && !GetUpdateMode() )
+ m_nFlags |= LBoxFlags::DeselectAll;
+
+ ShowCursor( false );
+ bool bUpdate = GetUpdateMode();
+
+ m_nFlags |= LBoxFlags::IgnoreSelect; // EntryInserted should not do anything
+ pEntry = m_pTree->First();
+ while( pEntry )
+ {
+ if( m_pView->Select( pEntry, bSelect ) )
+ {
+ if( bUpdate && m_pView->IsEntryVisible(pEntry) )
+ {
+ tools::Long nY = GetEntryLine( pEntry );
+ if( IsLineVisible( nY ) )
+ InvalidateEntry(pEntry);
+ }
+ }
+ pEntry = m_pTree->Next( pEntry );
+ }
+ m_nFlags &= ~LBoxFlags::IgnoreSelect;
+
+ if( bDestroyAnchor )
+ DestroyAnchor(); // delete anchor & reset SelectionEngine
+ else
+ m_pAnchor = nullptr; // always delete internal anchor
+ ShowCursor( true );
+}
+
+void SvImpLBox::SetSelectionMode( SelectionMode eSelMode )
+{
+ m_aSelEng.SetSelectionMode( eSelMode);
+ if( eSelMode == SelectionMode::Single )
+ m_bSimpleTravel = true;
+ else
+ m_bSimpleTravel = false;
+ if( (m_nStyle & WB_SIMPLEMODE) && (eSelMode == SelectionMode::Multiple) )
+ m_aSelEng.AddAlways( true );
+}
+
+// ***********************************************************************
+// Drag & Drop
+// ***********************************************************************
+
+void SvImpLBox::SetDragDropMode( DragDropMode eDDMode )
+{
+ if( eDDMode != DragDropMode::NONE )
+ {
+ m_aSelEng.ExpandSelectionOnMouseMove( false );
+ m_aSelEng.EnableDrag( true );
+ }
+ else
+ {
+ m_aSelEng.ExpandSelectionOnMouseMove();
+ m_aSelEng.EnableDrag( false );
+ }
+}
+
+void SvImpLBox::BeginDrag()
+{
+ m_nFlags &= ~LBoxFlags::Filling;
+ m_pView->StartDrag( 0, m_aSelEng.GetMousePosPixel() );
+}
+
+void SvImpLBox::PaintDDCursor(SvTreeListEntry* pEntry, bool bShow)
+{
+ if (pEntry)
+ {
+
+ SvViewDataEntry* pViewData = m_pView->GetViewData(pEntry);
+ pViewData->SetDragTarget(bShow);
+#ifdef MACOSX
+ // in MacOS we need to draw directly (as we are synchronous) or no invalidation happens
+ m_pView->PaintEntry1(*pEntry, GetEntryLine(pEntry), *m_pView->GetOutDev());
+#else
+ InvalidateEntry(pEntry);
+#endif
+ }
+}
+
+void SvImpLBox::Command( const CommandEvent& rCEvt )
+{
+ CommandEventId nCommand = rCEvt.GetCommand();
+
+ if( nCommand == CommandEventId::ContextMenu )
+ m_aEditIdle.Stop();
+
+ // scroll mouse event?
+ if (nCommand == CommandEventId::Wheel ||
+ nCommand == CommandEventId::StartAutoScroll ||
+ nCommand == CommandEventId::AutoScroll ||
+ nCommand == CommandEventId::Gesture)
+ {
+ if (m_pView->HandleScrollCommand(rCEvt, m_aHorSBar.get(), m_aVerSBar.get()))
+ return;
+ }
+
+ const Point& rPos = rCEvt.GetMousePosPixel();
+ if( rPos.X() < m_aOutputSize.Width() && rPos.Y() < m_aOutputSize.Height() )
+ m_aSelEng.Command( rCEvt );
+}
+
+tools::Rectangle SvImpLBox::GetVisibleArea() const
+{
+ Point aPos( m_pView->GetMapMode().GetOrigin() );
+ aPos.setX( aPos.X() * -1 );
+ tools::Rectangle aRect( aPos, m_aOutputSize );
+ return aRect;
+}
+
+void SvImpLBox::Invalidate()
+{
+ m_pView->GetOutDev()->SetClipRegion();
+}
+
+void SvImpLBox::SetCurEntry( SvTreeListEntry* pEntry )
+{
+ if ( ( m_aSelEng.GetSelectionMode() != SelectionMode::Single )
+ && ( m_aSelEng.GetSelectionMode() != SelectionMode::NONE )
+ )
+ SelAllDestrAnch( false );
+ if ( pEntry )
+ MakeVisible( pEntry );
+ SetCursor( pEntry );
+ if ( pEntry && ( m_aSelEng.GetSelectionMode() != SelectionMode::NONE ) )
+ m_pView->Select( pEntry );
+}
+
+IMPL_LINK_NOARG(SvImpLBox, EditTimerCall, Timer *, void)
+{
+ if( !m_pView->IsInplaceEditingEnabled() )
+ return;
+
+ bool bIsMouseTriggered = m_aEditClickPos.X() >= 0;
+ if ( bIsMouseTriggered )
+ {
+ Point aCurrentMousePos = m_pView->GetPointerPosPixel();
+ if ( ( std::abs( aCurrentMousePos.X() - m_aEditClickPos.X() ) > 5 )
+ || ( std::abs( aCurrentMousePos.Y() - m_aEditClickPos.Y() ) > 5 )
+ )
+ {
+ return;
+ }
+ }
+
+ SvTreeListEntry* pEntry = GetCurEntry();
+ if( pEntry )
+ {
+ ShowCursor( false );
+ m_pView->ImplEditEntry( pEntry );
+ ShowCursor( true );
+ }
+}
+
+bool SvImpLBox::RequestHelp( const HelpEvent& rHEvt )
+{
+ if( rHEvt.GetMode() & HelpEventMode::QUICK )
+ {
+ Point aPos( m_pView->ScreenToOutputPixel( rHEvt.GetMousePosPixel() ));
+ if( !GetVisibleArea().Contains( aPos ))
+ return false;
+
+ SvTreeListEntry* pEntry = GetEntry( aPos );
+ if( pEntry )
+ {
+ // recalculate text rectangle
+ SvLBoxTab* pTab;
+ SvLBoxItem* pItem = m_pView->GetItem( pEntry, aPos.X(), &pTab );
+ if (!pItem || pItem->GetType() != SvLBoxItemType::String)
+ return false;
+
+ aPos = GetEntryPosition( pEntry );
+ aPos.setX( m_pView->GetTabPos( pEntry, pTab ) ); //pTab->GetPos();
+ Size aSize(pItem->GetWidth(m_pView, pEntry), pItem->GetHeight(m_pView, pEntry));
+ SvLBoxTab* pNextTab = NextTab( pTab );
+ bool bItemClipped = false;
+ // is the item cut off by its right neighbor?
+ if( pNextTab && m_pView->GetTabPos(pEntry,pNextTab) < aPos.X()+aSize.Width() )
+ {
+ aSize.setWidth( pNextTab->GetPos() - pTab->GetPos() );
+ bItemClipped = true;
+ }
+ tools::Rectangle aItemRect( aPos, aSize );
+
+ tools::Rectangle aViewRect( GetVisibleArea() );
+
+ if( bItemClipped || !aViewRect.Contains( aItemRect ) )
+ {
+ // clip the right edge of the item at the edge of the view
+ //if( aItemRect.Right() > aViewRect.Right() )
+ // aItemRect.Right() = aViewRect.Right();
+
+ Point aPt = m_pView->OutputToScreenPixel( aItemRect.TopLeft() );
+ aItemRect.SetLeft( aPt.X() );
+ aItemRect.SetTop( aPt.Y() );
+ aPt = m_pView->OutputToScreenPixel( aItemRect.BottomRight() );
+ aItemRect.SetRight( aPt.X() );
+ aItemRect.SetBottom( aPt.Y() );
+
+ Help::ShowQuickHelp( m_pView, aItemRect,
+ static_cast<SvLBoxString*>(pItem)->GetText(), QuickHelpFlags::Left | QuickHelpFlags::VCenter );
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+SvLBoxTab* SvImpLBox::NextTab( SvLBoxTab const * pTab )
+{
+ sal_uInt16 nTabCount = m_pView->TabCount();
+ if( nTabCount <= 1 )
+ return nullptr;
+ for( int nTab=0; nTab < (nTabCount-1); nTab++)
+ {
+ if( m_pView->aTabs[nTab].get() == pTab )
+ return m_pView->aTabs[nTab+1].get();
+ }
+ return nullptr;
+}
+
+void SvImpLBox::SetUpdateMode( bool bMode )
+{
+ if( m_bUpdateMode != bMode )
+ {
+ m_bUpdateMode = bMode;
+ if( m_bUpdateMode )
+ UpdateAll( false );
+ }
+}
+
+void SvImpLBox::SetMostRight( SvTreeListEntry* pEntry )
+{
+ if( m_pView->nTreeFlags & SvTreeFlags::RECALCTABS )
+ {
+ m_nFlags |= LBoxFlags::IgnoreChangedTabs;
+ m_pView->SetTabs();
+ m_nFlags &= ~LBoxFlags::IgnoreChangedTabs;
+ }
+
+ sal_uInt16 nLastTab = m_pView->aTabs.size() - 1;
+ sal_uInt16 nLastItem = pEntry->ItemCount() - 1;
+ if( m_pView->aTabs.empty() || nLastItem == USHRT_MAX )
+ return;
+
+ if( nLastItem < nLastTab )
+ nLastTab = nLastItem;
+
+ SvLBoxTab* pTab = m_pView->aTabs[ nLastTab ].get();
+ SvLBoxItem& rItem = pEntry->GetItem( nLastTab );
+
+ tools::Long nTabPos = m_pView->GetTabPos( pEntry, pTab );
+
+ tools::Long nMaxRight = GetOutputSize().Width();
+ Point aPos( m_pView->GetMapMode().GetOrigin() );
+ aPos.setX( aPos.X() * -1 ); // conversion document coordinates
+ nMaxRight = nMaxRight + aPos.X() - 1;
+
+ tools::Long nNextTab = nTabPos < nMaxRight ? nMaxRight : nMaxRight + 50;
+ tools::Long nTabWidth = nNextTab - nTabPos + 1;
+ auto nItemSize = rItem.GetWidth(m_pView,pEntry);
+ tools::Long nOffset = pTab->CalcOffset( nItemSize, nTabWidth );
+
+ tools::Long nRight = nTabPos + nOffset + nItemSize;
+ if( nRight > m_nMostRight )
+ {
+ m_nMostRight = nRight;
+ m_pMostRightEntry = pEntry;
+ }
+}
+
+void SvImpLBox::FindMostRight()
+{
+ m_nMostRight = -1;
+ m_pMostRightEntry = nullptr;
+ if( !m_pView->GetModel() )
+ return;
+
+ SvTreeListEntry* pEntry = m_pView->FirstVisible();
+ while( pEntry )
+ {
+ SetMostRight( pEntry );
+ pEntry = m_pView->NextVisible( pEntry );
+ }
+}
+
+void SvImpLBox::FindMostRight( SvTreeListEntry* pParent )
+{
+ if( !pParent )
+ FindMostRight();
+ else
+ FindMostRight_Impl( pParent );
+}
+
+void SvImpLBox::FindMostRight_Impl( SvTreeListEntry* pParent )
+{
+ SvTreeListEntries& rList = m_pTree->GetChildList( pParent );
+
+ size_t nCount = rList.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvTreeListEntry* pChild = rList[nCur].get();
+ SetMostRight( pChild );
+ if( pChild->HasChildren() && m_pView->IsExpanded( pChild ))
+ FindMostRight_Impl( pChild );
+ }
+}
+
+void SvImpLBox::NotifyTabsChanged()
+{
+ if( GetUpdateMode() && !(m_nFlags & LBoxFlags::IgnoreChangedTabs ) &&
+ m_nCurUserEvent == nullptr )
+ {
+ m_nCurUserEvent = Application::PostUserEvent(LINK(this,SvImpLBox,MyUserEvent));
+ }
+}
+
+bool SvImpLBox::IsExpandable() const
+{
+ return m_pCursor->HasChildren() || m_pCursor->HasChildrenOnDemand();
+}
+
+IMPL_LINK(SvImpLBox, MyUserEvent, void*, pArg, void )
+{
+ m_nCurUserEvent = nullptr;
+ if( !pArg )
+ {
+ m_pView->Invalidate();
+ m_pView->PaintImmediately();
+ }
+ else
+ {
+ FindMostRight();
+ ShowVerSBar();
+ m_pView->Invalidate( GetVisibleArea() );
+ }
+}
+
+
+void SvImpLBox::StopUserEvent()
+{
+ if( m_nCurUserEvent != nullptr )
+ {
+ Application::RemoveUserEvent( m_nCurUserEvent );
+ m_nCurUserEvent = nullptr;
+ }
+}
+
+void SvImpLBox::implInitDefaultNodeImages()
+{
+ if ( s_pDefCollapsed )
+ // assume that all or nothing is initialized
+ return;
+
+ s_pDefCollapsed = new Image(StockImage::Yes, RID_BMP_TREENODE_COLLAPSED);
+ s_pDefExpanded = new Image(StockImage::Yes, RID_BMP_TREENODE_EXPANDED);
+}
+
+
+const Image& SvImpLBox::GetDefaultExpandedNodeImage( )
+{
+ implInitDefaultNodeImages();
+ return *s_pDefExpanded;
+}
+
+
+const Image& SvImpLBox::GetDefaultCollapsedNodeImage( )
+{
+ implInitDefaultNodeImages();
+ return *s_pDefCollapsed;
+}
+
+
+void SvImpLBox::CallEventListeners( VclEventId nEvent, void* pData )
+{
+ if ( m_pView )
+ m_pView->CallImplEventListeners( nEvent, pData);
+}
+
+
+bool SvImpLBox::IsSelectable( const SvTreeListEntry* pEntry ) const
+{
+ if( pEntry )
+ {
+ SvViewDataEntry* pViewDataNewCur = m_pView->GetViewDataEntry(pEntry);
+ return (pViewDataNewCur == nullptr) || pViewDataNewCur->IsSelectable();
+ }
+ else
+ {
+ return false;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/svlbitm.cxx b/vcl/source/treelist/svlbitm.cxx
new file mode 100644
index 000000000..3e0243a8d
--- /dev/null
+++ b/vcl/source/treelist/svlbitm.cxx
@@ -0,0 +1,550 @@
+/* -*- 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 <vcl/toolkit/treelistbox.hxx>
+#include <vcl/toolkit/svlbitm.hxx>
+#include <vcl/toolkit/treelistentry.hxx>
+#include <vcl/toolkit/viewdataentry.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/settings.hxx>
+
+struct SvLBoxButtonData_Impl
+{
+ SvTreeListEntry* pEntry;
+ SvLBoxButton* pBox;
+ bool bDefaultImages;
+ bool bShowRadioButton;
+
+ SvLBoxButtonData_Impl() : pEntry(nullptr), pBox(nullptr), bDefaultImages(false), bShowRadioButton(false) {}
+};
+
+void SvLBoxButtonData::InitData( bool _bRadioBtn, const Control* pCtrl )
+{
+ nWidth = nHeight = 0;
+
+ aBmps.resize(int(SvBmp::HITRISTATE)+1);
+
+ bDataOk = false;
+ pImpl->bDefaultImages = true;
+ pImpl->bShowRadioButton = _bRadioBtn;
+
+ SetDefaultImages( pCtrl );
+}
+
+SvLBoxButtonData::SvLBoxButtonData( const Control* pControlForSettings, bool _bRadioBtn )
+ : pImpl( new SvLBoxButtonData_Impl )
+{
+ InitData( _bRadioBtn, pControlForSettings );
+}
+
+SvLBoxButtonData::~SvLBoxButtonData()
+{
+}
+
+void SvLBoxButtonData::CallLink()
+{
+ aLink.Call( this );
+}
+
+SvBmp SvLBoxButtonData::GetIndex( SvItemStateFlags nItemState )
+{
+ SvBmp nIdx;
+ if (nItemState == SvItemStateFlags::UNCHECKED)
+ nIdx = SvBmp::UNCHECKED;
+ else if (nItemState == SvItemStateFlags::CHECKED)
+ nIdx = SvBmp::CHECKED;
+ else if (nItemState == SvItemStateFlags::TRISTATE)
+ nIdx = SvBmp::TRISTATE;
+ else if (nItemState == (SvItemStateFlags::UNCHECKED | SvItemStateFlags::HILIGHTED))
+ nIdx = SvBmp::HIUNCHECKED;
+ else if (nItemState == (SvItemStateFlags::CHECKED | SvItemStateFlags::HILIGHTED))
+ nIdx = SvBmp::HICHECKED;
+ else if (nItemState == (SvItemStateFlags::TRISTATE | SvItemStateFlags::HILIGHTED))
+ nIdx = SvBmp::HITRISTATE;
+ else
+ nIdx = SvBmp::UNCHECKED;
+ return nIdx;
+}
+
+void SvLBoxButtonData::SetWidthAndHeight()
+{
+ Size aSize = aBmps[int(SvBmp::UNCHECKED)].GetSizePixel();
+ nWidth = aSize.Width();
+ nHeight = aSize.Height();
+ bDataOk = true;
+}
+
+void SvLBoxButtonData::StoreButtonState(SvTreeListEntry* pActEntry, SvLBoxButton* pActBox)
+{
+ pImpl->pEntry = pActEntry;
+ pImpl->pBox = pActBox;
+}
+
+SvButtonState SvLBoxButtonData::ConvertToButtonState( SvItemStateFlags nItemFlags )
+{
+ nItemFlags &= SvItemStateFlags::UNCHECKED |
+ SvItemStateFlags::CHECKED |
+ SvItemStateFlags::TRISTATE;
+ switch( nItemFlags )
+ {
+ case SvItemStateFlags::UNCHECKED:
+ return SvButtonState::Unchecked;
+ case SvItemStateFlags::CHECKED:
+ return SvButtonState::Checked;
+ case SvItemStateFlags::TRISTATE:
+ return SvButtonState::Tristate;
+ default:
+ return SvButtonState::Unchecked;
+ }
+}
+
+SvTreeListEntry* SvLBoxButtonData::GetActEntry() const
+{
+ assert(pImpl && "-SvLBoxButtonData::GetActEntry(): don't use me that way!");
+ return pImpl->pEntry;
+}
+
+SvLBoxButton* SvLBoxButtonData::GetActBox() const
+{
+ assert(pImpl && "-SvLBoxButtonData::GetActBox(): don't use me that way!");
+ return pImpl->pBox;
+}
+
+void SvLBoxButtonData::SetDefaultImages( const Control* pCtrl )
+{
+ const AllSettings& rSettings = pCtrl? pCtrl->GetSettings() : Application::GetSettings();
+
+ if ( pImpl->bShowRadioButton )
+ {
+ SetImage(SvBmp::UNCHECKED, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::Default ) );
+ SetImage(SvBmp::CHECKED, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::Checked ) );
+ SetImage(SvBmp::HICHECKED, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::Checked | DrawButtonFlags::Pressed ) );
+ SetImage(SvBmp::HIUNCHECKED, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::Default | DrawButtonFlags::Pressed ) );
+ SetImage(SvBmp::TRISTATE, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::DontKnow ) );
+ SetImage(SvBmp::HITRISTATE, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::DontKnow | DrawButtonFlags::Pressed ) );
+ }
+ else
+ {
+ SetImage(SvBmp::UNCHECKED, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::Default ) );
+ SetImage(SvBmp::CHECKED, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::Checked ) );
+ SetImage(SvBmp::HICHECKED, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::Checked | DrawButtonFlags::Pressed ) );
+ SetImage(SvBmp::HIUNCHECKED, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::Default | DrawButtonFlags::Pressed ) );
+ SetImage(SvBmp::TRISTATE, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::DontKnow ) );
+ SetImage(SvBmp::HITRISTATE, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::DontKnow | DrawButtonFlags::Pressed ) );
+ }
+}
+
+bool SvLBoxButtonData::HasDefaultImages() const
+{
+ return pImpl->bDefaultImages;
+}
+
+bool SvLBoxButtonData::IsRadio() const {
+ return pImpl->bShowRadioButton;
+}
+
+// ***************************************************************
+// class SvLBoxString
+// ***************************************************************
+
+
+SvLBoxString::SvLBoxString(const OUString& rStr)
+ : mbEmphasized(false)
+ , mbCustom(false)
+ , mfAlign(0.0)
+ , maText(rStr)
+{
+}
+
+SvLBoxString::SvLBoxString()
+ : mbEmphasized(false)
+ , mbCustom(false)
+ , mfAlign(0.0)
+{
+}
+
+SvLBoxString::~SvLBoxString()
+{
+}
+
+SvLBoxItemType SvLBoxString::GetType() const
+{
+ return SvLBoxItemType::String;
+}
+
+namespace
+{
+ void drawSeparator(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRegion)
+ {
+ Color aOldLineColor(rRenderContext.GetLineColor());
+ const StyleSettings& rStyle = rRenderContext.GetSettings().GetStyleSettings();
+ Point aTmpPos = rRegion.TopLeft();
+ Size aSize = rRegion.GetSize();
+ aTmpPos.AdjustY(aSize.Height() / 2 );
+ rRenderContext.SetLineColor(rStyle.GetShadowColor());
+ rRenderContext.DrawLine(aTmpPos, Point(aSize.Width() + aTmpPos.X(), aTmpPos.Y()));
+ rRenderContext.SetLineColor(aOldLineColor);
+ }
+}
+
+void SvLBoxString::Paint(
+ const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext,
+ const SvViewDataEntry* /*pView*/, const SvTreeListEntry& rEntry)
+{
+ DrawTextFlags nStyle = (rDev.IsEnabled() && !mbDisabled) ? DrawTextFlags::NONE : DrawTextFlags::Disable;
+ if (bool(rEntry.GetFlags() & SvTLEntryFlags::IS_SEPARATOR))
+ {
+ Point aStartPos(0, rPos.Y() - 2);
+ tools::Rectangle aRegion(aStartPos, Size(rDev.GetSizePixel().Width(), 4));
+ drawSeparator(rRenderContext, aRegion);
+ return;
+ }
+
+ Size aSize;
+ if (rDev.TextCenterAndClipEnabled())
+ {
+ nStyle |= DrawTextFlags::PathEllipsis | DrawTextFlags::Center;
+ aSize.setWidth( rDev.GetEntryWidth() );
+ }
+ else
+ {
+ if (mfAlign < 0.5 )
+ {
+ nStyle |= DrawTextFlags::Left;
+ aSize.setWidth(GetWidth(&rDev, &rEntry));
+ }
+ else if (mfAlign == 0.5)
+ {
+ nStyle |= DrawTextFlags::Center;
+ aSize.setWidth(rDev.GetBoundingRect(&rEntry).getWidth());
+ }
+ else if (mfAlign > 0.5)
+ {
+ nStyle |= DrawTextFlags::Right;
+ aSize.setWidth(rDev.GetBoundingRect(&rEntry).getWidth());
+ }
+ }
+ aSize.setHeight(GetHeight(&rDev, &rEntry));
+
+ if (mbEmphasized)
+ {
+ rRenderContext.Push();
+ vcl::Font aFont(rRenderContext.GetFont());
+ aFont.SetWeight(WEIGHT_BOLD);
+ rRenderContext.SetFont(aFont);
+ }
+
+ tools::Rectangle aRect(rPos, aSize);
+
+ if (mbCustom)
+ rDev.DrawCustomEntry(rRenderContext, aRect, rEntry);
+ else
+ rRenderContext.DrawText(aRect, maText, nStyle);
+
+ if (mbEmphasized)
+ rRenderContext.Pop();
+}
+
+std::unique_ptr<SvLBoxItem> SvLBoxString::Clone(SvLBoxItem const * pSource) const
+{
+ std::unique_ptr<SvLBoxString> pNew(new SvLBoxString);
+
+ const SvLBoxString* pOther = static_cast<const SvLBoxString*>(pSource);
+ pNew->maText = pOther->maText;
+ pNew->mbEmphasized = pOther->mbEmphasized;
+ pNew->mbCustom = pOther->mbCustom;
+ pNew->mfAlign = pOther->mfAlign;
+
+ return std::unique_ptr<SvLBoxItem>(pNew.release());
+}
+
+void SvLBoxString::InitViewData(
+ SvTreeListBox* pView, SvTreeListEntry* pEntry, SvViewDataItem* pViewData)
+{
+ if( !pViewData )
+ pViewData = pView->GetViewDataItem( pEntry, this );
+
+ if (bool(pEntry->GetFlags() & SvTLEntryFlags::IS_SEPARATOR))
+ {
+ pViewData->mnWidth = -1;
+ pViewData->mnHeight = 0;
+ return;
+ }
+
+ if (mbEmphasized)
+ {
+ pView->GetOutDev()->Push();
+ vcl::Font aFont( pView->GetFont());
+ aFont.SetWeight(WEIGHT_BOLD);
+ pView->Control::SetFont( aFont );
+ }
+
+ if (mbCustom)
+ {
+ Size aSize = pView->MeasureCustomEntry(*pView->GetOutDev(), *pEntry);
+ pViewData->mnWidth = aSize.Width();
+ pViewData->mnHeight = aSize.Height();
+ }
+ else
+ {
+ pViewData->mnWidth = -1; // calc on demand
+ pViewData->mnHeight = pView->GetTextHeight();
+ }
+
+ if (mbEmphasized)
+ pView->GetOutDev()->Pop();
+}
+
+int SvLBoxString::CalcWidth(const SvTreeListBox* pView) const
+{
+ return pView->GetTextWidth(maText);
+}
+
+// ***************************************************************
+// class SvLBoxButton
+// ***************************************************************
+
+
+SvLBoxButton::SvLBoxButton( SvLBoxButtonData* pBData )
+ : isVis(true)
+ , pData(pBData)
+ , nItemFlags(SvItemStateFlags::NONE)
+{
+ SetStateUnchecked();
+}
+
+SvLBoxButton::SvLBoxButton()
+ : isVis(false)
+ , pData(nullptr)
+ , nItemFlags(SvItemStateFlags::NONE)
+{
+ SetStateUnchecked();
+}
+
+SvLBoxButton::~SvLBoxButton()
+{
+}
+
+SvLBoxItemType SvLBoxButton::GetType() const
+{
+ return SvLBoxItemType::Button;
+}
+
+void SvLBoxButton::ClickHdl( SvTreeListEntry* pEntry )
+{
+ if ( IsStateChecked() )
+ SetStateUnchecked();
+ else
+ SetStateChecked();
+ pData->StoreButtonState(pEntry, this);
+ pData->CallLink();
+}
+
+void SvLBoxButton::Paint(
+ const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext,
+ const SvViewDataEntry* /*pView*/, const SvTreeListEntry& /*rEntry*/)
+{
+ SvBmp nIndex = SvLBoxButtonData::GetIndex(nItemFlags);
+ DrawImageFlags nStyle = rDev.IsEnabled() ? DrawImageFlags::NONE : DrawImageFlags::Disable;
+
+ //Native drawing
+ bool bNativeOK = false;
+ ControlType eCtrlType = (pData->IsRadio())? ControlType::Radiobutton : ControlType::Checkbox;
+ if ( rRenderContext.IsNativeControlSupported( eCtrlType, ControlPart::Entire) )
+ {
+ Size aSize(pData->Width(), pData->Height());
+ ImplAdjustBoxSize(aSize, eCtrlType, rRenderContext);
+ ImplControlValue aControlValue;
+ tools::Rectangle aCtrlRegion( rPos, aSize );
+ ControlState nState = ControlState::NONE;
+
+ //states ControlState::DEFAULT, ControlState::PRESSED and ControlState::ROLLOVER are not implemented
+ if (IsStateHilighted())
+ nState |= ControlState::FOCUSED;
+ if (nStyle != DrawImageFlags::Disable)
+ nState |= ControlState::ENABLED;
+ if (IsStateChecked())
+ aControlValue.setTristateVal(ButtonValue::On);
+ else if (IsStateUnchecked())
+ aControlValue.setTristateVal(ButtonValue::Off);
+ else if (IsStateTristate())
+ aControlValue.setTristateVal( ButtonValue::Mixed );
+
+ if (isVis)
+ bNativeOK = rRenderContext.DrawNativeControl(eCtrlType, ControlPart::Entire,
+ aCtrlRegion, nState, aControlValue, OUString());
+ }
+
+ if (!bNativeOK && isVis)
+ rRenderContext.DrawImage(rPos, pData->GetImage(nIndex), nStyle);
+}
+
+std::unique_ptr<SvLBoxItem> SvLBoxButton::Clone(SvLBoxItem const * pSource) const
+{
+ std::unique_ptr<SvLBoxButton> pNew(new SvLBoxButton);
+ pNew->pData = static_cast<SvLBoxButton const *>(pSource)->pData;
+ return std::unique_ptr<SvLBoxItem>(pNew.release());
+}
+
+void SvLBoxButton::ImplAdjustBoxSize(Size& io_rSize, ControlType i_eType, vcl::RenderContext const & rRenderContext)
+{
+ if (!rRenderContext.IsNativeControlSupported( i_eType, ControlPart::Entire) )
+ return;
+
+ ImplControlValue aControlValue;
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), io_rSize );
+
+ aControlValue.setTristateVal( ButtonValue::On );
+
+ tools::Rectangle aNativeBounds, aNativeContent;
+ bool bNativeOK = rRenderContext.GetNativeControlRegion( i_eType,
+ ControlPart::Entire,
+ aCtrlRegion,
+ ControlState::ENABLED,
+ aControlValue,
+ aNativeBounds,
+ aNativeContent );
+ if( bNativeOK )
+ {
+ Size aContentSize( aNativeContent.GetSize() );
+ // leave a little space around the box image (looks better)
+ if( aContentSize.Height() + 2 > io_rSize.Height() )
+ io_rSize.setHeight( aContentSize.Height() + 2 );
+ if( aContentSize.Width() + 2 > io_rSize.Width() )
+ io_rSize.setWidth( aContentSize.Width() + 2 );
+ }
+}
+
+void SvLBoxButton::InitViewData(SvTreeListBox* pView,SvTreeListEntry* pEntry, SvViewDataItem* pViewData)
+{
+ if( !pViewData )
+ pViewData = pView->GetViewDataItem( pEntry, this );
+ Size aSize( pData->Width(), pData->Height() );
+
+ ControlType eCtrlType = (pData->IsRadio())? ControlType::Radiobutton : ControlType::Checkbox;
+ if ( pView )
+ ImplAdjustBoxSize(aSize, eCtrlType, *pView->GetOutDev());
+ pViewData->mnWidth = aSize.Width();
+ pViewData->mnHeight = aSize.Height();
+}
+
+// ***************************************************************
+// class SvLBoxContextBmp
+// ***************************************************************
+
+struct SvLBoxContextBmp_Impl
+{
+ Image m_aImage1;
+ Image m_aImage2;
+
+ bool m_bExpanded;
+};
+
+// ***************************************************************
+
+SvLBoxContextBmp::SvLBoxContextBmp(const Image& aBmp1, const Image& aBmp2,
+ bool bExpanded)
+ :m_pImpl( new SvLBoxContextBmp_Impl )
+{
+
+ m_pImpl->m_bExpanded = bExpanded;
+ SetModeImages( aBmp1, aBmp2 );
+}
+
+SvLBoxContextBmp::SvLBoxContextBmp()
+ : m_pImpl( new SvLBoxContextBmp_Impl )
+{
+ m_pImpl->m_bExpanded = false;
+}
+
+SvLBoxContextBmp::~SvLBoxContextBmp()
+{
+}
+
+SvLBoxItemType SvLBoxContextBmp::GetType() const
+{
+ return SvLBoxItemType::ContextBmp;
+}
+
+void SvLBoxContextBmp::SetModeImages( const Image& _rBitmap1, const Image& _rBitmap2 )
+{
+ m_pImpl->m_aImage1 = _rBitmap1;
+ m_pImpl->m_aImage2 = _rBitmap2;
+}
+
+Image& SvLBoxContextBmp::implGetImageStore( bool _bFirst )
+{
+
+ // OJ: #i27071# wrong mode so we just return the normal images
+ return _bFirst ? m_pImpl->m_aImage1 : m_pImpl->m_aImage2;
+}
+
+void SvLBoxContextBmp::InitViewData( SvTreeListBox* pView,SvTreeListEntry* pEntry,
+ SvViewDataItem* pViewData)
+{
+ if( !pViewData )
+ pViewData = pView->GetViewDataItem( pEntry, this );
+ Size aSize = m_pImpl->m_aImage1.GetSizePixel();
+ pViewData->mnWidth = aSize.Width();
+ pViewData->mnHeight = aSize.Height();
+}
+
+void SvLBoxContextBmp::Paint(
+ const Point& _rPos, SvTreeListBox& _rDev, vcl::RenderContext& rRenderContext,
+ const SvViewDataEntry* pView, const SvTreeListEntry& rEntry)
+{
+
+ // get the image.
+ const Image& rImage = implGetImageStore(pView->IsExpanded() != m_pImpl->m_bExpanded);
+
+ bool _bSemiTransparent = bool( SvTLEntryFlags::SEMITRANSPARENT & rEntry.GetFlags( ) );
+ // draw
+ DrawImageFlags nStyle = _rDev.IsEnabled() ? DrawImageFlags::NONE : DrawImageFlags::Disable;
+ if (_bSemiTransparent)
+ nStyle |= DrawImageFlags::SemiTransparent;
+ rRenderContext.DrawImage(_rPos, rImage, nStyle);
+}
+
+std::unique_ptr<SvLBoxItem> SvLBoxContextBmp::Clone(SvLBoxItem const * pSource) const
+{
+ std::unique_ptr<SvLBoxContextBmp> pNew(new SvLBoxContextBmp);
+ pNew->m_pImpl->m_aImage1 = static_cast< SvLBoxContextBmp const * >( pSource )->m_pImpl->m_aImage1;
+ pNew->m_pImpl->m_aImage2 = static_cast< SvLBoxContextBmp const * >( pSource )->m_pImpl->m_aImage2;
+ pNew->m_pImpl->m_bExpanded = static_cast<SvLBoxContextBmp const *>(pSource)->m_pImpl->m_bExpanded;
+ return std::unique_ptr<SvLBoxItem>(pNew.release());
+}
+
+tools::Long SvLBoxButtonData::Width()
+{
+ if ( !bDataOk )
+ SetWidthAndHeight();
+ return nWidth;
+}
+
+tools::Long SvLBoxButtonData::Height()
+{
+ if ( !bDataOk )
+ SetWidthAndHeight();
+ return nHeight;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/svtabbx.cxx b/vcl/source/treelist/svtabbx.cxx
new file mode 100644
index 000000000..ec403682a
--- /dev/null
+++ b/vcl/source/treelist/svtabbx.cxx
@@ -0,0 +1,1078 @@
+/* -*- 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 <vcl/svtaccessiblefactory.hxx>
+#include <vcl/accessiblefactory.hxx>
+#include <vcl/toolkit/svtabbx.hxx>
+#include <vcl/headbar.hxx>
+#include <vcl/toolkit/svlbitm.hxx>
+#include <vcl/toolkit/treelistentry.hxx>
+#include <unotools/accessiblestatesethelper.hxx>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+#include <strings.hrc>
+#include <svdata.hxx>
+#include <memory>
+#include <tools/json_writer.hxx>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::accessibility;
+
+constexpr SvLBoxTabFlags MYTABMASK =
+ SvLBoxTabFlags::ADJUST_RIGHT | SvLBoxTabFlags::ADJUST_LEFT | SvLBoxTabFlags::ADJUST_CENTER | SvLBoxTabFlags::FORCE;
+
+static void lcl_DumpEntryAndSiblings(tools::JsonWriter& rJsonWriter,
+ SvTreeListEntry* pEntry,
+ SvTabListBox* pTabListBox,
+ bool bCheckButtons)
+{
+ while (pEntry)
+ {
+ auto aNode = rJsonWriter.startStruct();
+
+ // simple listbox value
+ const SvLBoxItem* pIt = pEntry->GetFirstItem(SvLBoxItemType::String);
+ if (pIt)
+ rJsonWriter.put("text", static_cast<const SvLBoxString*>(pIt)->GetText());
+
+ // column based data
+ {
+ auto aColumns = rJsonWriter.startArray("columns");
+
+ for (size_t i = 0; i < pEntry->ItemCount(); i++)
+ {
+ SvLBoxItem& rItem = pEntry->GetItem(i);
+ if (rItem.GetType() == SvLBoxItemType::String)
+ {
+ const SvLBoxString* pStringItem = dynamic_cast<const SvLBoxString*>(&rItem);
+ if (pStringItem)
+ {
+ auto aColumn = rJsonWriter.startStruct();
+ rJsonWriter.put("text", pStringItem->GetText());
+ }
+ }
+ }
+ }
+
+ // SalInstanceTreeView does not use the flag CHILDREN_ON_DEMAND
+ // and it creates a dummy child
+ const SvTreeListEntries& rChildren = pEntry->GetChildEntries();
+ if (rChildren.size() == 1)
+ {
+ auto& rChild = rChildren[0];
+ if (const SvLBoxItem* pChild = rChild->GetFirstItem(SvLBoxItemType::String))
+ {
+ if (static_cast<const SvLBoxString*>(pChild)->GetText() == "<dummy>")
+ rJsonWriter.put("ondemand", "true");
+ }
+ }
+
+ if (bCheckButtons)
+ {
+ SvButtonState eCheckState = pTabListBox->GetCheckButtonState(pEntry);
+ if (eCheckState == SvButtonState::Unchecked)
+ rJsonWriter.put("state", "false");
+ else if (eCheckState == SvButtonState::Checked)
+ rJsonWriter.put("state", "true");
+ }
+
+ if (pTabListBox->IsSelected(pEntry))
+ rJsonWriter.put("selected", "true");
+
+ rJsonWriter.put("row", OString::number(pTabListBox->GetModel()->GetAbsPos(pEntry)).getStr());
+
+ SvTreeListEntry* pChild = pTabListBox->FirstChild(pEntry);
+ if (pChild)
+ {
+ auto childrenNode = rJsonWriter.startArray("children");
+ lcl_DumpEntryAndSiblings(rJsonWriter, pChild, pTabListBox, bCheckButtons);
+ }
+
+ pEntry = pEntry->NextSibling();
+ }
+}
+
+void SvTabListBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ SvTreeListBox::DumpAsPropertyTree(rJsonWriter);
+
+ rJsonWriter.put("singleclickactivate", GetActivateOnSingleClick());
+
+ bool bCheckButtons = static_cast<int>(nTreeFlags & SvTreeFlags::CHKBTN);
+
+ auto entriesNode = rJsonWriter.startArray("entries");
+ lcl_DumpEntryAndSiblings(rJsonWriter, First(), this, bCheckButtons);
+}
+
+// SvTreeListBox callback
+
+void SvTabListBox::SetTabs()
+{
+ SvTreeListBox::SetTabs();
+ if( mvTabList.empty() )
+ return;
+
+ DBG_ASSERT(!mvTabList.empty(),"TabList ?");
+
+ // The tree listbox has now inserted its tabs into the list. Now we
+ // fluff up the list with additional tabs and adjust the rightmost tab
+ // of the tree listbox.
+
+ // Picking the rightmost tab.
+ // HACK for the explorer! If ViewParent != 0, the first tab of the tree
+ // listbox is calculated by the tree listbox itself! This behavior is
+ // necessary for ButtonsOnRoot, as the explorer does not know in this
+ // case, which additional offset it needs to add to the tabs in this mode
+ // -- the tree listbox knows that, though!
+ /*
+ if( !pViewParent )
+ {
+ SvLBoxTab* pFirstTab = (SvLBoxTab*)aTabs.GetObject( aTabs.Count()-1 );
+ pFirstTab->SetPos( pTabList[0].GetPos() );
+ pFirstTab->nFlags &= ~MYTABMASK;
+ pFirstTab->nFlags |= pTabList[0].nFlags;
+ }
+ */
+
+ // append all other tabs to the list
+ for( sal_uInt16 nCurTab = 1; nCurTab < sal_uInt16(mvTabList.size()); nCurTab++ )
+ {
+ SvLBoxTab& rTab = mvTabList[nCurTab];
+ AddTab( rTab.GetPos(), rTab.nFlags );
+ }
+}
+
+void SvTabListBox::InitEntry(SvTreeListEntry* pEntry, const OUString& rStr,
+ const Image& rColl, const Image& rExp)
+{
+ SvTreeListBox::InitEntry(pEntry, rStr, rColl, rExp);
+
+ sal_Int32 nIndex = 0;
+ // TODO: verify if nTabCount is always >0 here!
+ const sal_uInt16 nCount = mvTabList.size() - 1;
+ for( sal_uInt16 nToken = 0; nToken < nCount; nToken++ )
+ {
+ const std::u16string_view aToken = GetToken(aCurEntry, nIndex);
+ pEntry->AddItem(std::make_unique<SvLBoxString>(OUString(aToken)));
+ }
+}
+
+SvTabListBox::SvTabListBox( vcl::Window* pParent, WinBits nBits )
+ : SvTreeListBox( pParent, nBits )
+{
+ SetHighlightRange(); // select full width
+}
+
+SvTabListBox::~SvTabListBox()
+{
+ disposeOnce();
+}
+
+void SvTabListBox::dispose()
+{
+ mvTabList.clear();
+ SvTreeListBox::dispose();
+}
+
+void SvTabListBox::SetTabs(sal_uInt16 nTabs, tools::Long const pTabPositions[], MapUnit eMapUnit)
+{
+ mvTabList.resize(nTabs);
+
+ MapMode aMMSource( eMapUnit );
+ MapMode aMMDest( MapUnit::MapPixel );
+
+ for( sal_uInt16 nIdx = 0; nIdx < sal_uInt16(mvTabList.size()); nIdx++, pTabPositions++ )
+ {
+ Size aSize( *pTabPositions, 0 );
+ aSize = LogicToLogic( aSize, &aMMSource, &aMMDest );
+ tools::Long nNewTab = aSize.Width();
+ mvTabList[nIdx].SetPos( nNewTab );
+ mvTabList[nIdx].nFlags &= MYTABMASK;
+ }
+ SvTreeListBox::nTreeFlags |= SvTreeFlags::RECALCTABS;
+ if( IsUpdateMode() )
+ Invalidate();
+}
+
+SvTreeListEntry* SvTabListBox::InsertEntry( const OUString& rText, SvTreeListEntry* pParent,
+ bool /*bChildrenOnDemand*/,
+ sal_uInt32 nPos, void* pUserData )
+{
+ return InsertEntryToColumn( rText, pParent, nPos, 0xffff, pUserData );
+}
+
+SvTreeListEntry* SvTabListBox::InsertEntryToColumn(const OUString& rStr,SvTreeListEntry* pParent,sal_uInt32 nPos,sal_uInt16 nCol,
+ void* pUser )
+{
+ OUString aStr;
+ if( nCol != 0xffff )
+ {
+ while( nCol )
+ {
+ aStr += "\t";
+ nCol--;
+ }
+ }
+ aStr += rStr;
+ OUString aFirstStr( aStr );
+ sal_Int32 nEnd = aFirstStr.indexOf( '\t' );
+ if( nEnd != -1 )
+ {
+ aFirstStr = aFirstStr.copy(0, nEnd);
+ aCurEntry = aStr.copy(++nEnd);
+ }
+ else
+ aCurEntry.clear();
+ return SvTreeListBox::InsertEntry( aFirstStr, pParent, false, nPos, pUser );
+}
+
+OUString SvTabListBox::GetEntryText( SvTreeListEntry* pEntry ) const
+{
+ return GetEntryText( pEntry, 0xffff );
+}
+
+OUString SvTabListBox::GetEntryText( const SvTreeListEntry* pEntry, sal_uInt16 nCol )
+{
+ DBG_ASSERT(pEntry,"GetEntryText:Invalid Entry");
+ OUStringBuffer aResult;
+ if( pEntry )
+ {
+ sal_uInt16 nCount = pEntry->ItemCount();
+ sal_uInt16 nCur = 0;
+ while( nCur < nCount )
+ {
+ const SvLBoxItem& rStr = pEntry->GetItem( nCur );
+ if (rStr.GetType() == SvLBoxItemType::String)
+ {
+ if( nCol == 0xffff )
+ {
+ if (!aResult.isEmpty())
+ aResult.append("\t");
+ aResult.append(static_cast<const SvLBoxString&>(rStr).GetText());
+ }
+ else
+ {
+ if( nCol == 0 )
+ return static_cast<const SvLBoxString&>(rStr).GetText();
+ nCol--;
+ }
+ }
+ nCur++;
+ }
+ }
+ return aResult.makeStringAndClear();
+}
+
+OUString SvTabListBox::GetEntryText( sal_uInt32 nPos, sal_uInt16 nCol ) const
+{
+ SvTreeListEntry* pEntry = GetEntryOnPos( nPos );
+ return GetEntryText( pEntry, nCol );
+}
+
+OUString SvTabListBox::GetCellText( sal_uInt32 nPos, sal_uInt16 nCol ) const
+{
+ SvTreeListEntry* pEntry = GetEntryOnPos( nPos );
+ DBG_ASSERT( pEntry, "SvTabListBox::GetCellText(): Invalid Entry" );
+ OUString aResult;
+ if (pEntry && pEntry->ItemCount() > o3tl::make_unsigned(nCol+1))
+ {
+ const SvLBoxItem& rStr = pEntry->GetItem( nCol + 1 );
+ if (rStr.GetType() == SvLBoxItemType::String)
+ aResult = static_cast<const SvLBoxString&>(rStr).GetText();
+ }
+ return aResult;
+}
+
+sal_uInt32 SvTabListBox::GetEntryPos( const SvTreeListEntry* pEntry ) const
+{
+ sal_uInt32 nPos = 0;
+ SvTreeListEntry* pTmpEntry = First();
+ while( pTmpEntry )
+ {
+ if ( pTmpEntry == pEntry )
+ return nPos;
+ pTmpEntry = Next( pTmpEntry );
+ ++nPos;
+ }
+ return 0xffffffff;
+}
+
+// static
+std::u16string_view SvTabListBox::GetToken( std::u16string_view sStr, sal_Int32& nIndex )
+{
+ return o3tl::getToken(sStr, 0, '\t', nIndex);
+}
+
+OUString SvTabListBox::GetTabEntryText( sal_uInt32 nPos, sal_uInt16 nCol ) const
+{
+ SvTreeListEntry* pEntry = SvTreeListBox::GetEntry( nPos );
+ DBG_ASSERT( pEntry, "GetTabEntryText(): Invalid entry " );
+ OUStringBuffer aResult;
+ if ( pEntry )
+ {
+ sal_uInt16 nCount = pEntry->ItemCount();
+ sal_uInt16 nCur = 0;
+ while( nCur < nCount )
+ {
+ const SvLBoxItem& rBoxItem = pEntry->GetItem( nCur );
+ if (rBoxItem.GetType() == SvLBoxItemType::String)
+ {
+ if ( nCol == 0xffff )
+ {
+ if (!aResult.isEmpty())
+ aResult.append("\t");
+ aResult.append(static_cast<const SvLBoxString&>(rBoxItem).GetText());
+ }
+ else
+ {
+ if ( nCol == 0 )
+ {
+ OUString sRet = static_cast<const SvLBoxString&>(rBoxItem).GetText();
+ if ( sRet.isEmpty() )
+ sRet = VclResId( STR_SVT_ACC_EMPTY_FIELD );
+ return sRet;
+ }
+ --nCol;
+ }
+ }
+ ++nCur;
+ }
+ }
+ return aResult.makeStringAndClear();
+}
+
+SvTreeListEntry* SvTabListBox::GetEntryOnPos( sal_uInt32 _nEntryPos ) const
+{
+ SvTreeListEntry* pEntry = nullptr;
+ sal_uInt32 i, nPos = 0, nCount = GetLevelChildCount( nullptr );
+ for ( i = 0; i < nCount; ++i )
+ {
+ SvTreeListEntry* pParent = GetEntry(i);
+ if ( nPos == _nEntryPos )
+ {
+ pEntry = pParent;
+ break;
+ }
+ else
+ {
+ nPos++;
+ pEntry = GetChildOnPos( pParent, _nEntryPos, nPos );
+ if ( pEntry )
+ break;
+ }
+ }
+
+ return pEntry;
+}
+
+SvTreeListEntry* SvTabListBox::GetChildOnPos( SvTreeListEntry* _pParent, sal_uInt32 _nEntryPos, sal_uInt32& _rPos ) const
+{
+ sal_uInt32 i, nCount = GetLevelChildCount( _pParent );
+ for ( i = 0; i < nCount; ++i )
+ {
+ SvTreeListEntry* pParent = GetEntry( _pParent, i );
+ if ( _rPos == _nEntryPos )
+ return pParent;
+ else
+ {
+ _rPos++;
+ SvTreeListEntry* pEntry = GetChildOnPos( pParent, _nEntryPos, _rPos );
+ if ( pEntry )
+ return pEntry;
+ }
+ }
+
+ return nullptr;
+}
+
+void SvTabListBox::SetTabJustify( sal_uInt16 nTab, SvTabJustify eJustify)
+{
+ DBG_ASSERT(nTab<mvTabList.size(),"GetTabPos:Invalid Tab");
+ if( nTab >= mvTabList.size() )
+ return;
+ SvLBoxTab& rTab = mvTabList[ nTab ];
+ SvLBoxTabFlags nFlags = rTab.nFlags;
+ nFlags &= ~MYTABMASK;
+ // see SvLBoxTab::CalcOffset for force, which only matters for centering
+ nFlags |= static_cast<SvLBoxTabFlags>(eJustify) | SvLBoxTabFlags::FORCE;
+ rTab.nFlags = nFlags;
+ SvTreeListBox::nTreeFlags |= SvTreeFlags::RECALCTABS;
+ if( IsUpdateMode() )
+ Invalidate();
+}
+
+void SvTabListBox::SetTabEditable(sal_uInt16 nTab, bool bEditable)
+{
+ DBG_ASSERT(nTab<mvTabList.size(),"GetTabPos:Invalid Tab");
+ if( nTab >= mvTabList.size() )
+ return;
+ SvLBoxTab& rTab = mvTabList[ nTab ];
+ if (bEditable)
+ rTab.nFlags |= SvLBoxTabFlags::EDITABLE;
+ else
+ rTab.nFlags &= ~SvLBoxTabFlags::EDITABLE;
+}
+
+tools::Long SvTabListBox::GetLogicTab( sal_uInt16 nTab )
+{
+ if( SvTreeListBox::nTreeFlags & SvTreeFlags::RECALCTABS )
+ SetTabs();
+
+ DBG_ASSERT(nTab<mvTabList.size(),"GetTabPos:Invalid Tab");
+ return aTabs[ nTab ]->GetPos();
+}
+
+namespace vcl
+{
+ struct SvHeaderTabListBoxImpl
+ {
+ VclPtr<HeaderBar> m_pHeaderBar;
+ AccessibleFactoryAccess m_aFactoryAccess;
+
+ SvHeaderTabListBoxImpl() : m_pHeaderBar( nullptr ) { }
+ };
+}
+
+SvHeaderTabListBox::SvHeaderTabListBox( vcl::Window* pParent, WinBits nWinStyle )
+ : SvTabListBox(pParent, nWinStyle)
+ , m_bFirstPaint(true)
+ , m_pImpl(new ::vcl::SvHeaderTabListBoxImpl)
+ , m_pAccessible(nullptr)
+{
+}
+
+SvHeaderTabListBox::~SvHeaderTabListBox()
+{
+ disposeOnce();
+}
+
+void SvHeaderTabListBox::dispose()
+{
+ m_pImpl.reset();
+ SvTabListBox::dispose();
+}
+
+void SvHeaderTabListBox::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect )
+{
+ if (m_bFirstPaint)
+ {
+ m_bFirstPaint = false;
+ }
+ SvTabListBox::Paint(rRenderContext, rRect);
+}
+
+void SvHeaderTabListBox::InitHeaderBar( HeaderBar* pHeaderBar )
+{
+ DBG_ASSERT( !m_pImpl->m_pHeaderBar, "header bar already initialized" );
+ DBG_ASSERT( pHeaderBar, "invalid header bar initialization" );
+ m_pImpl->m_pHeaderBar = pHeaderBar;
+ SetScrolledHdl( LINK( this, SvHeaderTabListBox, ScrollHdl_Impl ) );
+ m_pImpl->m_pHeaderBar->SetCreateAccessibleHdl( LINK( this, SvHeaderTabListBox, CreateAccessibleHdl_Impl ) );
+}
+
+HeaderBar* SvHeaderTabListBox::GetHeaderBar()
+{
+ return m_pImpl ? m_pImpl->m_pHeaderBar : nullptr;
+}
+
+bool SvHeaderTabListBox::IsItemChecked( SvTreeListEntry* pEntry, sal_uInt16 nCol )
+{
+ SvButtonState eState = SvButtonState::Unchecked;
+ SvLBoxButton& rItem = static_cast<SvLBoxButton&>( pEntry->GetItem( nCol + 1 ) );
+
+ if (rItem.GetType() == SvLBoxItemType::Button)
+ {
+ SvItemStateFlags nButtonFlags = rItem.GetButtonFlags();
+ eState = SvLBoxButtonData::ConvertToButtonState( nButtonFlags );
+ }
+
+ return ( eState == SvButtonState::Checked );
+}
+
+SvTreeListEntry* SvHeaderTabListBox::InsertEntryToColumn(
+ const OUString& rStr, SvTreeListEntry* pParent, sal_uInt32 nPos, sal_uInt16 nCol, void* pUserData )
+{
+ SvTreeListEntry* pEntry = SvTabListBox::InsertEntryToColumn( rStr, pParent, nPos, nCol, pUserData );
+ RecalculateAccessibleChildren();
+ return pEntry;
+}
+
+sal_uInt32 SvHeaderTabListBox::Insert(
+ SvTreeListEntry* pEnt, SvTreeListEntry* pPar, sal_uInt32 nPos )
+{
+ sal_uInt32 n = SvTabListBox::Insert( pEnt, pPar, nPos );
+ RecalculateAccessibleChildren();
+ return n;
+}
+
+sal_uInt32 SvHeaderTabListBox::Insert( SvTreeListEntry* pEntry, sal_uInt32 nRootPos )
+{
+ sal_uInt32 nPos = SvTabListBox::Insert( pEntry, nRootPos );
+ RecalculateAccessibleChildren();
+ return nPos;
+}
+
+void SvHeaderTabListBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ SvTabListBox::DumpAsPropertyTree(rJsonWriter);
+
+ auto aHeaders = rJsonWriter.startArray("headers");
+
+ HeaderBar* pHeaderBar = GetHeaderBar();
+ for(sal_uInt16 i = 0; i < pHeaderBar->GetItemCount(); i++)
+ {
+ auto aNode = rJsonWriter.startStruct();
+ rJsonWriter.put("text", pHeaderBar->GetItemText(pHeaderBar->GetItemId(i)));
+ }
+}
+
+IMPL_LINK_NOARG(SvHeaderTabListBox, ScrollHdl_Impl, SvTreeListBox*, void)
+{
+ m_pImpl->m_pHeaderBar->SetOffset( -GetXOffset() );
+}
+
+IMPL_LINK_NOARG(SvHeaderTabListBox, CreateAccessibleHdl_Impl, HeaderBar*, void)
+{
+ vcl::Window* pParent = m_pImpl->m_pHeaderBar->GetAccessibleParentWindow();
+ DBG_ASSERT( pParent, "SvHeaderTabListBox..CreateAccessibleHdl_Impl - accessible parent not found" );
+ if ( pParent )
+ {
+ css::uno::Reference< XAccessible > xAccParent = pParent->GetAccessible();
+ if ( xAccParent.is() )
+ {
+ Reference< XAccessible > xAccessible = m_pImpl->m_aFactoryAccess.getFactory().createAccessibleBrowseBoxHeaderBar(
+ xAccParent, *this, AccessibleBrowseBoxObjType::ColumnHeaderBar );
+ m_pImpl->m_pHeaderBar->SetAccessible( xAccessible );
+ }
+ }
+}
+
+void SvHeaderTabListBox::RecalculateAccessibleChildren()
+{
+ if ( !m_aAccessibleChildren.empty() )
+ {
+ sal_uInt32 nCount = ( GetRowCount() + 1 ) * GetColumnCount();
+ if ( m_aAccessibleChildren.size() < nCount )
+ m_aAccessibleChildren.resize( nCount );
+ else
+ {
+ DBG_ASSERT( m_aAccessibleChildren.size() == nCount, "wrong children count" );
+ }
+ }
+}
+
+bool SvHeaderTabListBox::IsCellCheckBox( sal_Int32 _nRow, sal_uInt16 _nColumn, TriState& _rState ) const
+{
+ bool bRet = false;
+ SvTreeListEntry* pEntry = GetEntry( _nRow );
+ if ( pEntry )
+ {
+ sal_uInt16 nItemCount = pEntry->ItemCount();
+ if ( nItemCount > ( _nColumn + 1 ) )
+ {
+ SvLBoxItem& rItem = pEntry->GetItem( _nColumn + 1 );
+ if (rItem.GetType() == SvLBoxItemType::Button)
+ {
+ bRet = true;
+ _rState = ( ( static_cast<SvLBoxButton&>(rItem).GetButtonFlags() & SvItemStateFlags::UNCHECKED ) == SvItemStateFlags::NONE )
+ ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+ }
+ else
+ {
+ SAL_WARN( "svtools.contnr", "SvHeaderTabListBox::IsCellCheckBox(): column out of range" );
+ }
+ }
+ return bRet;
+}
+sal_Int32 SvHeaderTabListBox::GetRowCount() const
+{
+ return GetEntryCount();
+}
+
+sal_uInt16 SvHeaderTabListBox::GetColumnCount() const
+{
+ return m_pImpl->m_pHeaderBar->GetItemCount();
+}
+
+sal_Int32 SvHeaderTabListBox::GetCurrRow() const
+{
+ sal_Int32 nRet = -1;
+ SvTreeListEntry* pEntry = GetCurEntry();
+ if ( pEntry )
+ {
+ sal_uInt32 nCount = GetEntryCount();
+ for ( sal_uInt32 i = 0; i < nCount; ++i )
+ {
+ if ( pEntry == GetEntry(i) )
+ {
+ nRet = i;
+ break;
+ }
+ }
+ }
+
+ return nRet;
+}
+
+sal_uInt16 SvHeaderTabListBox::GetCurrColumn() const
+{
+ return 0;
+}
+
+OUString SvHeaderTabListBox::GetRowDescription( sal_Int32 _nRow ) const
+{
+ return GetEntryText( _nRow );
+}
+
+OUString SvHeaderTabListBox::GetColumnDescription( sal_uInt16 _nColumn ) const
+{
+ return m_pImpl->m_pHeaderBar->GetItemText( m_pImpl->m_pHeaderBar->GetItemId( _nColumn ) );
+}
+
+bool SvHeaderTabListBox::HasRowHeader() const
+{
+ return false;
+}
+
+bool SvHeaderTabListBox::GoToCell( sal_Int32 /*_nRow*/, sal_uInt16 /*_nColumn*/ )
+{
+ return false;
+}
+
+void SvHeaderTabListBox::SetNoSelection()
+{
+ SvTreeListBox::SelectAll(false);
+}
+
+void SvHeaderTabListBox::SelectAll()
+{
+ SvTreeListBox::SelectAll(true);
+}
+
+void SvHeaderTabListBox::SelectRow( sal_Int32 _nRow, bool _bSelect, bool )
+{
+ Select( GetEntry( _nRow ), _bSelect );
+}
+
+void SvHeaderTabListBox::SelectColumn( sal_uInt16, bool )
+{
+}
+
+sal_Int32 SvHeaderTabListBox::GetSelectedRowCount() const
+{
+ return GetSelectionCount();
+}
+
+sal_Int32 SvHeaderTabListBox::GetSelectedColumnCount() const
+{
+ return 0;
+}
+
+bool SvHeaderTabListBox::IsRowSelected( sal_Int32 _nRow ) const
+{
+ SvTreeListEntry* pEntry = GetEntry( _nRow );
+ return ( pEntry && IsSelected( pEntry ) );
+}
+
+bool SvHeaderTabListBox::IsColumnSelected( sal_Int32 ) const
+{
+ return false;
+}
+
+void SvHeaderTabListBox::GetAllSelectedRows( css::uno::Sequence< sal_Int32 >& ) const
+{
+}
+
+void SvHeaderTabListBox::GetAllSelectedColumns( css::uno::Sequence< sal_Int32 >& ) const
+{
+}
+
+bool SvHeaderTabListBox::IsCellVisible( sal_Int32, sal_uInt16 ) const
+{
+ return true;
+}
+
+OUString SvHeaderTabListBox::GetAccessibleCellText( sal_Int32 _nRow, sal_uInt16 _nColumnPos ) const
+{
+ return GetTabEntryText(_nRow, _nColumnPos);
+}
+
+tools::Rectangle SvHeaderTabListBox::calcHeaderRect( bool _bIsColumnBar, bool _bOnScreen )
+{
+ tools::Rectangle aRect;
+ if ( _bIsColumnBar )
+ {
+ vcl::Window* pParent = nullptr;
+ if ( !_bOnScreen )
+ pParent = m_pImpl->m_pHeaderBar->GetAccessibleParentWindow();
+
+ aRect = m_pImpl->m_pHeaderBar->GetWindowExtentsRelative( pParent );
+ }
+ return aRect;
+}
+
+tools::Rectangle SvHeaderTabListBox::calcTableRect( bool _bOnScreen )
+{
+ vcl::Window* pParent = nullptr;
+ if ( !_bOnScreen )
+ pParent = GetAccessibleParentWindow();
+
+ tools::Rectangle aRect( GetWindowExtentsRelative( pParent ) );
+ return aRect;
+}
+
+tools::Rectangle SvHeaderTabListBox::GetFieldRectPixelAbs( sal_Int32 _nRow, sal_uInt16 _nColumn, bool _bIsHeader, bool _bOnScreen )
+{
+ DBG_ASSERT( !_bIsHeader || 0 == _nRow, "invalid parameters" );
+ tools::Rectangle aRect;
+ SvTreeListEntry* pEntry = GetEntry( _nRow );
+ if ( pEntry )
+ {
+ aRect = _bIsHeader ? calcHeaderRect( true, false ) : GetBoundingRect( pEntry );
+ Point aTopLeft = aRect.TopLeft();
+ DBG_ASSERT( m_pImpl->m_pHeaderBar->GetItemCount() > _nColumn, "invalid column" );
+ tools::Rectangle aItemRect = m_pImpl->m_pHeaderBar->GetItemRect( m_pImpl->m_pHeaderBar->GetItemId( _nColumn ) );
+ aTopLeft.setX( aItemRect.Left() );
+ Size aSize = aItemRect.GetSize();
+ aRect = tools::Rectangle( aTopLeft, aSize );
+ vcl::Window* pParent = nullptr;
+ if ( !_bOnScreen )
+ pParent = GetAccessibleParentWindow();
+ aTopLeft = aRect.TopLeft();
+ aTopLeft += GetWindowExtentsRelative( pParent ).TopLeft();
+ aRect = tools::Rectangle( aTopLeft, aRect.GetSize() );
+ }
+
+ return aRect;
+}
+
+Reference< XAccessible > SvHeaderTabListBox::CreateAccessibleCell( sal_Int32 _nRow, sal_uInt16 _nColumnPos )
+{
+ OSL_ENSURE( m_pAccessible, "Invalid call: Accessible is null" );
+
+ Reference< XAccessible > xChild;
+
+ TriState eState = TRISTATE_INDET;
+ bool bIsCheckBox = IsCellCheckBox( _nRow, _nColumnPos, eState );
+ if ( bIsCheckBox )
+ xChild = m_pImpl->m_aFactoryAccess.getFactory().createAccessibleCheckBoxCell(
+ m_pAccessible->getHeaderBar(), *this, nullptr, _nRow, _nColumnPos, eState, false );
+ else
+ xChild = m_pImpl->m_aFactoryAccess.getFactory().createAccessibleBrowseBoxTableCell(
+ m_pAccessible->getHeaderBar(), *this, nullptr, _nRow, _nColumnPos, OFFSET_NONE );
+
+ return xChild;
+}
+
+Reference< XAccessible > SvHeaderTabListBox::CreateAccessibleRowHeader( sal_Int32 )
+{
+ Reference< XAccessible > xHeader;
+ return xHeader;
+}
+
+Reference< XAccessible > SvHeaderTabListBox::CreateAccessibleColumnHeader( sal_uInt16 _nColumn )
+{
+ // first call? -> initial list
+ if ( m_aAccessibleChildren.empty() )
+ {
+ const sal_uInt16 nColumnCount = GetColumnCount();
+ m_aAccessibleChildren.assign( nColumnCount, Reference< XAccessible >() );
+ }
+
+ // get header
+ Reference< XAccessible > xChild = m_aAccessibleChildren[ _nColumn ];
+ // already exists?
+ if ( !xChild.is() && m_pAccessible )
+ {
+ // no -> create new header cell
+ xChild = m_pImpl->m_aFactoryAccess.getFactory().createAccessibleBrowseBoxHeaderCell(
+ _nColumn, m_pAccessible->getHeaderBar(),
+ *this, nullptr, AccessibleBrowseBoxObjType::ColumnHeaderCell
+ );
+
+ // insert into list
+ m_aAccessibleChildren[ _nColumn ] = xChild;
+ }
+ return xChild;
+}
+
+sal_Int32 SvHeaderTabListBox::GetAccessibleControlCount() const
+{
+ return -1;
+}
+
+Reference< XAccessible > SvHeaderTabListBox::CreateAccessibleControl( sal_Int32 )
+{
+ Reference< XAccessible > xControl;
+ return xControl;
+}
+
+bool SvHeaderTabListBox::ConvertPointToControlIndex( sal_Int32&, const Point& )
+{
+ return false;
+}
+
+bool SvHeaderTabListBox::ConvertPointToCellAddress( sal_Int32&, sal_uInt16&, const Point& )
+{
+ return false;
+}
+
+bool SvHeaderTabListBox::ConvertPointToRowHeader( sal_Int32&, const Point& )
+{
+ return false;
+}
+
+bool SvHeaderTabListBox::ConvertPointToColumnHeader( sal_uInt16&, const Point& )
+{
+ return false;
+}
+
+OUString SvHeaderTabListBox::GetAccessibleObjectName( AccessibleBrowseBoxObjType _eType, sal_Int32 _nPos ) const
+{
+ OUString aRetText;
+ switch( _eType )
+ {
+ case AccessibleBrowseBoxObjType::BrowseBox:
+ case AccessibleBrowseBoxObjType::Table:
+ case AccessibleBrowseBoxObjType::ColumnHeaderBar:
+ // should be empty now (see #i63983)
+ aRetText.clear();
+ break;
+
+ case AccessibleBrowseBoxObjType::TableCell:
+ {
+ // here we need a valid pos, we can not handle -1
+ if ( _nPos >= 0 )
+ {
+ sal_uInt16 nColumnCount = GetColumnCount();
+ if (nColumnCount > 0)
+ {
+ sal_Int32 nRow = _nPos / nColumnCount;
+ sal_uInt16 nColumn = static_cast< sal_uInt16 >( _nPos % nColumnCount );
+ aRetText = GetCellText( nRow, nColumn );
+ }
+ }
+ break;
+ }
+ case AccessibleBrowseBoxObjType::CheckBoxCell:
+ {
+ break; // checkbox cells have no name
+ }
+ case AccessibleBrowseBoxObjType::ColumnHeaderCell:
+ {
+ aRetText = m_pImpl->m_pHeaderBar->GetItemText( m_pImpl->m_pHeaderBar->GetItemId( static_cast<sal_uInt16>(_nPos) ) );
+ break;
+ }
+
+ case AccessibleBrowseBoxObjType::RowHeaderBar:
+ case AccessibleBrowseBoxObjType::RowHeaderCell:
+ aRetText = "error";
+ break;
+
+ default:
+ OSL_FAIL("BrowseBox::GetAccessibleName: invalid enum!");
+ }
+ return aRetText;
+}
+
+OUString SvHeaderTabListBox::GetAccessibleObjectDescription( AccessibleBrowseBoxObjType _eType, sal_Int32 _nPos ) const
+{
+ OUString aRetText;
+
+ if( _eType == AccessibleBrowseBoxObjType::TableCell && _nPos != -1 )
+ {
+ sal_uInt16 nColumnCount = GetColumnCount();
+ if (nColumnCount > 0)
+ {
+ sal_Int32 nRow = _nPos / nColumnCount;
+ sal_uInt16 nColumn = static_cast< sal_uInt16 >( _nPos % nColumnCount );
+
+ OUString aText( VclResId(STR_SVT_ACC_DESC_TABLISTBOX) );
+ aText = aText.replaceFirst( "%1", OUString::number( nRow ) );
+ OUString sColHeader = m_pImpl->m_pHeaderBar->GetItemText( m_pImpl->m_pHeaderBar->GetItemId( nColumn ) );
+ if ( sColHeader.isEmpty() )
+ sColHeader = OUString::number( nColumn );
+ aText = aText.replaceFirst( "%2", sColHeader );
+ aRetText = aText;
+ }
+ }
+
+ return aRetText;
+}
+
+void SvHeaderTabListBox::FillAccessibleStateSet( ::utl::AccessibleStateSetHelper& _rStateSet, AccessibleBrowseBoxObjType _eType ) const
+{
+ switch( _eType )
+ {
+ case AccessibleBrowseBoxObjType::BrowseBox:
+ case AccessibleBrowseBoxObjType::Table:
+ {
+ _rStateSet.AddState( AccessibleStateType::FOCUSABLE );
+ if ( HasFocus() )
+ _rStateSet.AddState( AccessibleStateType::FOCUSED );
+ if ( IsActive() )
+ _rStateSet.AddState( AccessibleStateType::ACTIVE );
+ if ( IsEnabled() )
+ {
+ _rStateSet.AddState( AccessibleStateType::ENABLED );
+ _rStateSet.AddState( AccessibleStateType::SENSITIVE );
+ }
+ if ( IsReallyVisible() )
+ _rStateSet.AddState( AccessibleStateType::VISIBLE );
+ if ( _eType == AccessibleBrowseBoxObjType::Table )
+ {
+
+ _rStateSet.AddState( AccessibleStateType::MANAGES_DESCENDANTS );
+ _rStateSet.AddState( AccessibleStateType::MULTI_SELECTABLE );
+ }
+ break;
+ }
+
+ case AccessibleBrowseBoxObjType::ColumnHeaderBar:
+ {
+ sal_Int32 nCurRow = GetCurrRow();
+ sal_uInt16 nCurColumn = GetCurrColumn();
+ if ( IsCellVisible( nCurRow, nCurColumn ) )
+ _rStateSet.AddState( AccessibleStateType::VISIBLE );
+ if ( IsEnabled() )
+ _rStateSet.AddState( AccessibleStateType::ENABLED );
+ _rStateSet.AddState( AccessibleStateType::TRANSIENT );
+ break;
+ }
+
+ case AccessibleBrowseBoxObjType::RowHeaderCell:
+ case AccessibleBrowseBoxObjType::ColumnHeaderCell:
+ {
+ _rStateSet.AddState( AccessibleStateType::VISIBLE );
+ _rStateSet.AddState( AccessibleStateType::FOCUSABLE );
+ _rStateSet.AddState( AccessibleStateType::TRANSIENT );
+ if ( IsEnabled() )
+ _rStateSet.AddState( AccessibleStateType::ENABLED );
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void SvHeaderTabListBox::FillAccessibleStateSetForCell( ::utl::AccessibleStateSetHelper& _rStateSet, sal_Int32 _nRow, sal_uInt16 _nColumn ) const
+{
+ _rStateSet.AddState( AccessibleStateType::SELECTABLE );
+ _rStateSet.AddState( AccessibleStateType::TRANSIENT );
+
+ if ( IsCellVisible( _nRow, _nColumn ) )
+ {
+ _rStateSet.AddState( AccessibleStateType::VISIBLE );
+ _rStateSet.AddState( AccessibleStateType::ENABLED );
+ }
+
+ if ( IsRowSelected( _nRow ) )
+ {
+ _rStateSet.AddState( AccessibleStateType::ACTIVE );
+ _rStateSet.AddState( AccessibleStateType::SELECTED );
+ }
+ if ( IsEnabled() )
+ _rStateSet.AddState( AccessibleStateType::ENABLED );
+}
+
+void SvHeaderTabListBox::GrabTableFocus()
+{
+ GrabFocus();
+}
+
+bool SvHeaderTabListBox::GetGlyphBoundRects( const Point& rOrigin, const OUString& rStr, int nIndex, int nLen, std::vector< tools::Rectangle >& rVector )
+{
+ return GetOutDev()->GetGlyphBoundRects( rOrigin, rStr, nIndex, nLen, rVector );
+}
+
+tools::Rectangle SvHeaderTabListBox::GetWindowExtentsRelative(const vcl::Window *pRelativeWindow) const
+{
+ return Control::GetWindowExtentsRelative( pRelativeWindow );
+}
+
+void SvHeaderTabListBox::GrabFocus()
+{
+ Control::GrabFocus();
+}
+
+Reference< XAccessible > SvHeaderTabListBox::GetAccessible()
+{
+ return Control::GetAccessible();
+}
+
+vcl::Window* SvHeaderTabListBox::GetAccessibleParentWindow() const
+{
+ return Control::GetAccessibleParentWindow();
+}
+
+vcl::Window* SvHeaderTabListBox::GetWindowInstance()
+{
+ return this;
+}
+
+Reference< XAccessible > SvHeaderTabListBox::CreateAccessible()
+{
+ vcl::Window* pParent = GetAccessibleParentWindow();
+ DBG_ASSERT( pParent, "SvHeaderTabListBox::::CreateAccessible - accessible parent not found" );
+
+ Reference< XAccessible > xAccessible;
+ if ( m_pAccessible ) xAccessible = m_pAccessible->getMyself();
+
+ if( pParent && !m_pAccessible )
+ {
+ Reference< XAccessible > xAccParent = pParent->GetAccessible();
+ if ( xAccParent.is() )
+ {
+ m_pAccessible = m_pImpl->m_aFactoryAccess.getFactory().createAccessibleTabListBox( xAccParent, *this );
+ if ( m_pAccessible )
+ xAccessible = m_pAccessible->getMyself();
+ }
+ }
+ return xAccessible;
+}
+
+tools::Rectangle SvHeaderTabListBox::GetFieldCharacterBounds(sal_Int32,sal_Int32,sal_Int32)
+{
+ return tools::Rectangle();
+}
+
+sal_Int32 SvHeaderTabListBox::GetFieldIndexAtPoint(sal_Int32 _nRow,sal_Int32 _nColumnPos,const Point& _rPoint)
+{
+ OUString sText = GetAccessibleCellText( _nRow, static_cast< sal_uInt16 >( _nColumnPos ) );
+ std::vector< tools::Rectangle > aRects;
+ if ( GetGlyphBoundRects(Point(0,0), sText, 0, sText.getLength(), aRects) )
+ {
+ sal_Int32 nPos = 0;
+ for (auto const& rectangle : aRects)
+ {
+ if( rectangle.Contains(_rPoint) )
+ return nPos;
+ ++nPos;
+ }
+ }
+
+ return -1;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/transfer.cxx b/vcl/source/treelist/transfer.cxx
new file mode 100644
index 000000000..4506fa2ff
--- /dev/null
+++ b/vcl/source/treelist/transfer.cxx
@@ -0,0 +1,2227 @@
+/* -*- 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 .
+ */
+
+#ifdef _WIN32
+#include <prewin.h>
+#include <postwin.h>
+#include <shlobj.h>
+#endif
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <osl/mutex.hxx>
+#include <rtl/uri.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+#include <tools/urlobj.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <sot/exchange.hxx>
+#include <sot/storage.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/filter/SvmReader.hxx>
+#include <vcl/filter/SvmWriter.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <comphelper/fileformat.h>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/servicehelper.hxx>
+#include <comphelper/sequence.hxx>
+#include <sot/filelist.hxx>
+#include <cppuhelper/implbase.hxx>
+
+#include <comphelper/seqstream.hxx>
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
+#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
+#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/XMimeContentType.hpp>
+#include <com/sun/star/datatransfer/XTransferable2.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+
+#include <svl/urlbmk.hxx>
+#include <vcl/inetimg.hxx>
+#include <vcl/wmf.hxx>
+#include <vcl/imap.hxx>
+#include <vcl/transfer.hxx>
+#include <rtl/strbuf.hxx>
+#include <cstdio>
+#include <vcl/dibtools.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include <vcl/pngwrite.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <memory>
+#include <utility>
+#include <vcl/TypeSerializer.hxx>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::frame;
+using namespace ::com::sun::star::io;
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::clipboard;
+using namespace ::com::sun::star::datatransfer::dnd;
+using namespace std::literals::string_view_literals;
+
+
+#define TOD_SIG1 0x01234567
+#define TOD_SIG2 0x89abcdef
+
+SvStream& WriteTransferableObjectDescriptor( SvStream& rOStm, const TransferableObjectDescriptor& rObjDesc )
+{
+ const sal_uInt32 nFirstPos = rOStm.Tell(), nViewAspect = rObjDesc.mnViewAspect;
+ const sal_uInt32 nSig1 = TOD_SIG1, nSig2 = TOD_SIG2;
+
+ rOStm.SeekRel( 4 );
+ WriteSvGlobalName( rOStm, rObjDesc.maClassName );
+ rOStm.WriteUInt32( nViewAspect );
+ rOStm.WriteInt32( rObjDesc.maSize.Width() );
+ rOStm.WriteInt32( rObjDesc.maSize.Height() );
+ rOStm.WriteInt32( rObjDesc.maDragStartPos.X() );
+ rOStm.WriteInt32( rObjDesc.maDragStartPos.Y() );
+ rOStm.WriteUniOrByteString( rObjDesc.maTypeName, osl_getThreadTextEncoding() );
+ rOStm.WriteUniOrByteString( rObjDesc.maDisplayName, osl_getThreadTextEncoding() );
+ rOStm.WriteUInt32( nSig1 ).WriteUInt32( nSig2 );
+
+ const sal_uInt32 nLastPos = rOStm.Tell();
+
+ rOStm.Seek( nFirstPos );
+ rOStm.WriteUInt32( nLastPos - nFirstPos );
+ rOStm.Seek( nLastPos );
+
+ return rOStm;
+}
+
+
+// the reading of the parameter is done using the special service css::datatransfer::MimeContentType,
+// a similar approach should be implemented for creation of the mimetype string;
+// for now the set of acceptable characters has to be hardcoded, in future it should be part of the service that creates the mimetype
+
+static OUString ImplGetParameterString( const TransferableObjectDescriptor& rObjDesc )
+{
+ const OUString aClassName( rObjDesc.maClassName.GetHexName() );
+ OUString aParams;
+
+ if( !aClassName.isEmpty() )
+ {
+ aParams += ";classname=\"" + aClassName + "\"";
+ }
+
+ if( !rObjDesc.maTypeName.isEmpty() )
+ {
+ aParams += ";typename=\"" + rObjDesc.maTypeName + "\"";
+ }
+
+ if( !rObjDesc.maDisplayName.isEmpty() )
+ {
+ // the display name might contain unacceptable characters, encode all of them
+ // this seems to be the only parameter currently that might contain such characters
+ static constexpr auto pToAccept = rtl::createUriCharClass(
+ u8"()<>@,;:/[]?=!#$&'*+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}~. ");
+
+ aParams += ";displayname=\""
+ + rtl::Uri::encode(
+ rObjDesc.maDisplayName, pToAccept.data(), rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8)
+ + "\"";
+ }
+
+ aParams += ";viewaspect=\"" + OUString::number(rObjDesc.mnViewAspect)
+ + "\";width=\"" + OUString::number(rObjDesc.maSize.Width())
+ + "\";height=\"" + OUString::number(rObjDesc.maSize.Height())
+ + "\";posx=\"" + OUString::number(rObjDesc.maDragStartPos.X())
+ + "\";posy=\"" + OUString::number(rObjDesc.maDragStartPos.X()) + "\"";
+
+ return aParams;
+}
+
+
+static void ImplSetParameterString( TransferableObjectDescriptor& rObjDesc, const DataFlavorEx& rFlavorEx )
+{
+ Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
+
+ try
+ {
+ Reference< XMimeContentTypeFactory > xMimeFact = MimeContentTypeFactory::create( xContext );
+
+ Reference< XMimeContentType > xMimeType( xMimeFact->createMimeContentType( rFlavorEx.MimeType ) );
+
+ if( xMimeType.is() )
+ {
+ static const OUStringLiteral aClassNameString( u"classname" );
+ static const OUStringLiteral aTypeNameString( u"typename" );
+ static const OUStringLiteral aDisplayNameString( u"displayname" );
+ static const OUStringLiteral aViewAspectString( u"viewaspect" );
+ static const OUStringLiteral aWidthString( u"width" );
+ static const OUStringLiteral aHeightString( u"height" );
+ static const OUStringLiteral aPosXString( u"posx" );
+ static const OUStringLiteral aPosYString( u"posy" );
+
+ if( xMimeType->hasParameter( aClassNameString ) )
+ {
+ rObjDesc.maClassName.MakeId( xMimeType->getParameterValue( aClassNameString ) );
+ }
+
+ if( xMimeType->hasParameter( aTypeNameString ) )
+ {
+ rObjDesc.maTypeName = xMimeType->getParameterValue( aTypeNameString );
+ }
+
+ if( xMimeType->hasParameter( aDisplayNameString ) )
+ {
+ // the display name might contain unacceptable characters, in this case they should be encoded
+ // this seems to be the only parameter currently that might contain such characters
+ rObjDesc.maDisplayName = ::rtl::Uri::decode( xMimeType->getParameterValue( aDisplayNameString ), rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
+ }
+
+ if( xMimeType->hasParameter( aViewAspectString ) )
+ {
+ rObjDesc.mnViewAspect = static_cast< sal_uInt16 >( xMimeType->getParameterValue( aViewAspectString ).toInt32() );
+ }
+
+ if( xMimeType->hasParameter( aWidthString ) )
+ {
+ rObjDesc.maSize.setWidth( xMimeType->getParameterValue( aWidthString ).toInt32() );
+ }
+
+ if( xMimeType->hasParameter( aHeightString ) )
+ {
+ rObjDesc.maSize.setHeight( xMimeType->getParameterValue( aHeightString ).toInt32() );
+ }
+
+ if( xMimeType->hasParameter( aPosXString ) )
+ {
+ rObjDesc.maDragStartPos.setX( xMimeType->getParameterValue( aPosXString ).toInt32() );
+ }
+
+ if( xMimeType->hasParameter( aPosYString ) )
+ {
+ rObjDesc.maDragStartPos.setY( xMimeType->getParameterValue( aPosYString ).toInt32() );
+ }
+ }
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+
+TransferableHelper::TerminateListener::TerminateListener( TransferableHelper& rTransferableHelper ) :
+ mrParent( rTransferableHelper )
+{
+}
+
+
+TransferableHelper::TerminateListener::~TerminateListener()
+{
+}
+
+
+void SAL_CALL TransferableHelper::TerminateListener::disposing( const EventObject& )
+{
+}
+
+
+void SAL_CALL TransferableHelper::TerminateListener::queryTermination( const EventObject& )
+{
+}
+
+
+void SAL_CALL TransferableHelper::TerminateListener::notifyTermination( const EventObject& )
+{
+ mrParent.ImplFlush();
+}
+
+OUString SAL_CALL TransferableHelper::TerminateListener::getImplementationName()
+{
+ return "com.sun.star.comp.svt.TransferableHelperTerminateListener";
+}
+
+sal_Bool SAL_CALL TransferableHelper::TerminateListener::supportsService(const OUString& /*rServiceName*/)
+{
+ return false;
+}
+
+css::uno::Sequence<OUString> TransferableHelper::TerminateListener::getSupportedServiceNames()
+{
+ return {};
+}
+
+TransferableHelper::~TransferableHelper()
+{
+ css::uno::Reference<css::frame::XTerminateListener> listener;
+ {
+ const SolarMutexGuard aGuard;
+ std::swap(listener, mxTerminateListener);
+ }
+ if (listener.is()) {
+ Desktop::create(comphelper::getProcessComponentContext())->removeTerminateListener(
+ listener);
+ }
+}
+
+Any SAL_CALL TransferableHelper::getTransferData( const DataFlavor& rFlavor )
+{
+ return getTransferData2(rFlavor, OUString());
+}
+
+Any SAL_CALL TransferableHelper::getTransferData2( const DataFlavor& rFlavor, const OUString& rDestDoc )
+{
+ if( !maAny.hasValue() || maFormats.empty() || ( maLastFormat != rFlavor.MimeType ) )
+ {
+ const SolarMutexGuard aGuard;
+
+ maLastFormat = rFlavor.MimeType;
+ maAny = Any();
+
+ try
+ {
+ DataFlavor aSubstFlavor;
+ bool bDone = false;
+
+ // add formats if not already done
+ if (maFormats.empty())
+ AddSupportedFormats();
+
+ // check alien formats first and try to get a substitution format
+ if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aSubstFlavor ) &&
+ TransferableDataHelper::IsEqual( aSubstFlavor, rFlavor ) )
+ {
+ GetData(aSubstFlavor, rDestDoc);
+ bDone = maAny.hasValue();
+ }
+ else if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::BMP, aSubstFlavor )
+ && TransferableDataHelper::IsEqual( aSubstFlavor, rFlavor )
+ && SotExchange::GetFormatDataFlavor(SotClipboardFormatId::BITMAP, aSubstFlavor))
+ {
+ GetData(aSubstFlavor, rDestDoc);
+ bDone = true;
+ }
+ else if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EMF, aSubstFlavor ) &&
+ TransferableDataHelper::IsEqual( aSubstFlavor, rFlavor ) &&
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::GDIMETAFILE, aSubstFlavor ) )
+ {
+ GetData(aSubstFlavor, rDestDoc);
+
+ if( maAny.hasValue() )
+ {
+ Sequence< sal_Int8 > aSeq;
+
+ if( maAny >>= aSeq )
+ {
+ GDIMetaFile aMtf;
+ {
+ SvMemoryStream aSrcStm( aSeq.getArray(), aSeq.getLength(), StreamMode::WRITE | StreamMode::TRUNC );
+ SvmReader aReader( aSrcStm );
+ aReader.Read( aMtf );
+ }
+
+ Graphic aGraphic( aMtf );
+ SvMemoryStream aDstStm( 65535, 65535 );
+
+ if( GraphicConverter::Export( aDstStm, aGraphic, ConvertDataFormat::EMF ) == ERRCODE_NONE )
+ {
+ maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aDstStm.GetData() ),
+ aDstStm.TellEnd() );
+ bDone = true;
+ }
+ }
+ }
+ }
+ else if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::WMF, aSubstFlavor ) &&
+ TransferableDataHelper::IsEqual( aSubstFlavor, rFlavor ) &&
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::GDIMETAFILE, aSubstFlavor ) )
+ {
+ GetData(aSubstFlavor, rDestDoc);
+
+ if( maAny.hasValue() )
+ {
+ Sequence< sal_Int8 > aSeq;
+
+ if( maAny >>= aSeq )
+ {
+ GDIMetaFile aMtf;
+ {
+ SvMemoryStream aSrcStm( aSeq.getArray(), aSeq.getLength(), StreamMode::WRITE | StreamMode::TRUNC );
+ SvmReader aReader( aSrcStm );
+ aReader.Read( aMtf );
+ }
+
+ SvMemoryStream aDstStm( 65535, 65535 );
+
+ // taking wmf without file header
+ if ( ConvertGDIMetaFileToWMF( aMtf, aDstStm, nullptr, false ) )
+ {
+ maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aDstStm.GetData() ),
+ aDstStm.TellEnd() );
+ bDone = true;
+ }
+ }
+ }
+ }
+
+ // reset Any if substitute doesn't work
+ if( !bDone && maAny.hasValue() )
+ maAny = Any();
+
+ // if any is not yet filled, use standard format
+ if( !maAny.hasValue() )
+ GetData(rFlavor, rDestDoc);
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ if( !maAny.hasValue() )
+ throw UnsupportedFlavorException();
+ }
+
+ return maAny;
+}
+
+sal_Bool SAL_CALL TransferableHelper::isComplex()
+{
+ // By default everything is complex, until proven otherwise
+ // in the respective document type transferable handler.
+ return true;
+}
+
+Sequence< DataFlavor > SAL_CALL TransferableHelper::getTransferDataFlavors()
+{
+ const SolarMutexGuard aGuard;
+
+ try
+ {
+ if(maFormats.empty())
+ AddSupportedFormats();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ return comphelper::containerToSequence<DataFlavor>(maFormats);
+}
+
+
+sal_Bool SAL_CALL TransferableHelper::isDataFlavorSupported( const DataFlavor& rFlavor )
+{
+ const SolarMutexGuard aGuard;
+
+ try
+ {
+ if (maFormats.empty())
+ AddSupportedFormats();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ for (auto const& format : maFormats)
+ {
+ if( TransferableDataHelper::IsEqual( format, rFlavor ) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+void SAL_CALL TransferableHelper::lostOwnership( const Reference< XClipboard >&, const Reference< XTransferable >& )
+{
+ const SolarMutexGuard aGuard;
+
+ try
+ {
+ if( mxTerminateListener.is() )
+ {
+ Reference< XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
+ xDesktop->removeTerminateListener( mxTerminateListener );
+
+ mxTerminateListener.clear();
+ }
+
+ ObjectReleased();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+
+void SAL_CALL TransferableHelper::disposing( const EventObject& )
+{
+}
+
+
+void SAL_CALL TransferableHelper::dragDropEnd( const DragSourceDropEvent& rDSDE )
+{
+ const SolarMutexGuard aGuard;
+
+ try
+ {
+ DragFinished( rDSDE.DropSuccess ? ( rDSDE.DropAction & ~DNDConstants::ACTION_DEFAULT ) : DNDConstants::ACTION_NONE );
+ ObjectReleased();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+
+void SAL_CALL TransferableHelper::dragEnter( const DragSourceDragEvent& )
+{
+}
+
+
+void SAL_CALL TransferableHelper::dragExit( const DragSourceEvent& )
+{
+}
+
+
+void SAL_CALL TransferableHelper::dragOver( const DragSourceDragEvent& )
+{
+}
+
+
+void SAL_CALL TransferableHelper::dropActionChanged( const DragSourceDragEvent& )
+{
+}
+
+
+sal_Int64 SAL_CALL TransferableHelper::getSomething( const Sequence< sal_Int8 >& rId )
+{
+ return comphelper::getSomethingImpl(rId, this);
+}
+
+
+void TransferableHelper::ImplFlush()
+{
+ if( !mxClipboard.is() )
+ return;
+
+ Reference< XFlushableClipboard > xFlushableClipboard( mxClipboard, UNO_QUERY );
+ SolarMutexReleaser aReleaser;
+
+ try
+ {
+ if( xFlushableClipboard.is() )
+ xFlushableClipboard->flushClipboard();
+ }
+ catch( const css::uno::Exception& )
+ {
+ OSL_FAIL( "Could not flush clipboard" );
+ }
+}
+
+
+void TransferableHelper::AddFormat( SotClipboardFormatId nFormat )
+{
+ DataFlavor aFlavor;
+
+ if( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) )
+ AddFormat( aFlavor );
+}
+
+
+void TransferableHelper::AddFormat( const DataFlavor& rFlavor )
+{
+ bool bAdd = true;
+
+ for (auto & format : maFormats)
+ {
+ if( TransferableDataHelper::IsEqual( format, rFlavor ) )
+ {
+ // update MimeType for SotClipboardFormatId::OBJECTDESCRIPTOR in every case
+ if ((SotClipboardFormatId::OBJECTDESCRIPTOR == format.mnSotId) && mxObjDesc)
+ {
+ DataFlavor aObjDescFlavor;
+
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::OBJECTDESCRIPTOR, aObjDescFlavor );
+ format.MimeType = aObjDescFlavor.MimeType;
+ format.MimeType += ::ImplGetParameterString(*mxObjDesc);
+ }
+
+ bAdd = false;
+ break;
+ }
+ }
+
+ if( !bAdd )
+ return;
+
+ DataFlavorEx aFlavorEx;
+
+ aFlavorEx.MimeType = rFlavor.MimeType;
+ aFlavorEx.HumanPresentableName = rFlavor.HumanPresentableName;
+ aFlavorEx.DataType = rFlavor.DataType;
+ aFlavorEx.mnSotId = SotExchange::RegisterFormat( rFlavor );
+
+ if ((SotClipboardFormatId::OBJECTDESCRIPTOR == aFlavorEx.mnSotId) && mxObjDesc)
+ aFlavorEx.MimeType += ::ImplGetParameterString(*mxObjDesc);
+
+ maFormats.push_back(aFlavorEx);
+
+ if( SotClipboardFormatId::BITMAP == aFlavorEx.mnSotId )
+ {
+ AddFormat( SotClipboardFormatId::PNG );
+ AddFormat( SotClipboardFormatId::BMP );
+ }
+ else if( SotClipboardFormatId::GDIMETAFILE == aFlavorEx.mnSotId )
+ {
+ AddFormat( SotClipboardFormatId::EMF );
+ AddFormat( SotClipboardFormatId::WMF );
+ }
+}
+
+
+void TransferableHelper::RemoveFormat( SotClipboardFormatId nFormat )
+{
+ DataFlavor aFlavor;
+
+ if( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) )
+ RemoveFormat( aFlavor );
+}
+
+
+void TransferableHelper::RemoveFormat( const DataFlavor& rFlavor )
+{
+ DataFlavorExVector::iterator aIter(maFormats.begin());
+
+ while (aIter != maFormats.end())
+ {
+ if( TransferableDataHelper::IsEqual( *aIter, rFlavor ) )
+ aIter = maFormats.erase(aIter);
+ else
+ ++aIter;
+ }
+}
+
+
+bool TransferableHelper::HasFormat( SotClipboardFormatId nFormat )
+{
+ return std::any_of(maFormats.begin(), maFormats.end(),
+ [&](const DataFlavorEx& data) { return data.mnSotId == nFormat; });
+}
+
+
+void TransferableHelper::ClearFormats()
+{
+ maFormats.clear();
+ maAny.clear();
+}
+
+
+bool TransferableHelper::SetAny( const Any& rAny )
+{
+ maAny = rAny;
+ return maAny.hasValue();
+}
+
+
+bool TransferableHelper::SetString( const OUString& rString )
+{
+ maAny <<= rString;
+ return maAny.hasValue();
+}
+
+
+bool TransferableHelper::SetBitmapEx( const BitmapEx& rBitmapEx, const DataFlavor& rFlavor )
+{
+ if( !rBitmapEx.IsEmpty() )
+ {
+ SvMemoryStream aMemStm( 65535, 65535 );
+
+ if(rFlavor.MimeType.equalsIgnoreAsciiCase("image/png"))
+ {
+ // write a PNG
+ css::uno::Sequence<css::beans::PropertyValue> aFilterData;
+
+#ifdef IOS
+ // Use faster compression on slow devices
+ aFilterData.realloc(aFilterData.getLength() + 1);
+ aFilterData.getArray()[aFilterData.getLength() - 1].Name = "Compression";
+
+ // We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed. For a
+ // typical 15 megapixel image from a DSLR, we are talking about a difference of 17 s for
+ // the default compression level vs 4 s for best speed, on an iPad Pro from 2017.
+ //
+ // Sure, the best would be to not have to re-encode the image at all, but have access to
+ // the original JPEG or PNG when there is a such.
+
+ aFilterData.getArray()[aFilterData.getLength() - 1].Value <<= 1;
+#endif
+ vcl::PNGWriter aPNGWriter(rBitmapEx, &aFilterData);
+
+ aPNGWriter.Write(aMemStm);
+ }
+ else
+ {
+ // explicitly use Bitmap::Write with bCompressed = sal_False and bFileHeader = sal_True
+ WriteDIB(rBitmapEx.GetBitmap(), aMemStm, false, true);
+ }
+
+ maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() );
+ }
+
+ return maAny.hasValue();
+}
+
+
+bool TransferableHelper::SetGDIMetaFile( const GDIMetaFile& rMtf )
+{
+ if( rMtf.GetActionSize() )
+ {
+ SvMemoryStream aMemStm( 65535, 65535 );
+
+ SvmWriter aWriter( aMemStm );
+ aWriter.Write( rMtf );
+ maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() );
+ }
+
+ return maAny.hasValue();
+}
+
+
+bool TransferableHelper::SetGraphic( const Graphic& rGraphic )
+{
+ if( rGraphic.GetType() != GraphicType::NONE )
+ {
+ SvMemoryStream aMemStm( 65535, 65535 );
+
+ aMemStm.SetVersion( SOFFICE_FILEFORMAT_50 );
+ aMemStm.SetCompressMode( SvStreamCompressFlags::NATIVE );
+
+ TypeSerializer aSerializer(aMemStm);
+ aSerializer.writeGraphic(rGraphic);
+
+ maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() );
+ }
+
+ return maAny.hasValue();
+}
+
+
+bool TransferableHelper::SetImageMap( const ImageMap& rIMap )
+{
+ SvMemoryStream aMemStm( 8192, 8192 );
+
+ aMemStm.SetVersion( SOFFICE_FILEFORMAT_50 );
+ rIMap.Write( aMemStm );
+ maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() );
+
+ return maAny.hasValue();
+}
+
+
+bool TransferableHelper::SetTransferableObjectDescriptor( const TransferableObjectDescriptor& rDesc )
+{
+ PrepareOLE( rDesc );
+
+ SvMemoryStream aMemStm( 1024, 1024 );
+
+ WriteTransferableObjectDescriptor( aMemStm, rDesc );
+ maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.Tell() );
+
+ return maAny.hasValue();
+ }
+
+
+bool TransferableHelper::SetINetBookmark( const INetBookmark& rBmk,
+ const css::datatransfer::DataFlavor& rFlavor )
+{
+ rtl_TextEncoding eSysCSet = osl_getThreadTextEncoding();
+
+ switch( SotExchange::GetFormat( rFlavor ) )
+ {
+ case SotClipboardFormatId::SOLK:
+ {
+ OString sURL(OUStringToOString(rBmk.GetURL(), eSysCSet));
+ OString sDesc(OUStringToOString(rBmk.GetDescription(), eSysCSet));
+ OStringBuffer sOut;
+ sOut.append(sURL.getLength());
+ sOut.append("@" + sURL);
+ sOut.append(sDesc.getLength());
+ sOut.append("@" + sDesc);
+
+ Sequence< sal_Int8 > aSeq(sOut.getLength());
+ memcpy(aSeq.getArray(), sOut.getStr(), sOut.getLength());
+ maAny <<= aSeq;
+ }
+ break;
+
+ case SotClipboardFormatId::STRING:
+ case SotClipboardFormatId::UNIFORMRESOURCELOCATOR:
+ maAny <<= rBmk.GetURL();
+ break;
+
+ case SotClipboardFormatId::NETSCAPE_BOOKMARK:
+ {
+ Sequence< sal_Int8 > aSeq( 2048 );
+ char* pSeq = reinterpret_cast< char* >( aSeq.getArray() );
+
+ // strncpy fills the rest with nulls, as we need
+ strncpy( pSeq, OUStringToOString(rBmk.GetURL(), eSysCSet).getStr(), 1024 );
+ strncpy( pSeq + 1024, OUStringToOString(rBmk.GetDescription(), eSysCSet).getStr(), 1024 );
+
+ maAny <<= aSeq;
+ }
+ break;
+
+#ifdef _WIN32
+ case SotClipboardFormatId::FILEGRPDESCRIPTOR:
+ {
+ Sequence< sal_Int8 > aSeq( sizeof( FILEGROUPDESCRIPTORW ) );
+ FILEGROUPDESCRIPTORW* pFDesc = reinterpret_cast<FILEGROUPDESCRIPTORW*>(aSeq.getArray());
+ FILEDESCRIPTORW& rFDesc1 = pFDesc->fgd[ 0 ];
+
+ pFDesc->cItems = 1;
+ memset( &rFDesc1, 0, sizeof( rFDesc1 ) );
+ rFDesc1.dwFlags = FD_LINKUI;
+
+ OUStringBuffer aStr(rBmk.GetDescription());
+ for( size_t nChar = 0; (nChar = std::u16string_view(aStr).find_first_of(u"\\/:*?\"<>|"sv, nChar)) != std::u16string_view::npos; )
+ aStr.remove(nChar, 1);
+
+ aStr.insert(0, "Shortcut to ");
+ aStr.append(".URL");
+ wcscpy( rFDesc1.cFileName, o3tl::toW(aStr.getStr()) );
+
+ maAny <<= aSeq;
+ }
+ break;
+
+ case SotClipboardFormatId::FILECONTENT:
+ {
+ maAny <<= "[InternetShortcut]\x0aURL=" + rBmk.GetURL();
+ }
+ break;
+#endif
+
+ default:
+ break;
+ }
+
+ return maAny.hasValue();
+}
+
+
+bool TransferableHelper::SetINetImage( const INetImage& rINtImg,
+ const css::datatransfer::DataFlavor& rFlavor )
+{
+ SvMemoryStream aMemStm( 1024, 1024 );
+
+ aMemStm.SetVersion( SOFFICE_FILEFORMAT_50 );
+ rINtImg.Write( aMemStm, SotExchange::GetFormat( rFlavor ) );
+
+ maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() );
+
+ return maAny.hasValue();
+}
+
+
+bool TransferableHelper::SetObject( void* pUserObject, sal_uInt32 nUserObjectId, const DataFlavor& rFlavor )
+{
+ tools::SvRef<SotTempStream> xStm( new SotTempStream( OUString() ) );
+
+ xStm->SetVersion( SOFFICE_FILEFORMAT_50 );
+
+ if( pUserObject && WriteObject( xStm, pUserObject, nUserObjectId, rFlavor ) )
+ {
+ const sal_uInt32 nLen = xStm->TellEnd();
+ Sequence< sal_Int8 > aSeq( nLen );
+
+ xStm->Seek( STREAM_SEEK_TO_BEGIN );
+ xStm->ReadBytes(aSeq.getArray(), nLen);
+
+ if( nLen && ( SotExchange::GetFormat( rFlavor ) == SotClipboardFormatId::STRING ) )
+ {
+ //JP 24.7.2001: as I know was this only for the writer application and this
+ // writes now UTF16 format into the stream
+ //JP 6.8.2001: and now it writes UTF8 because then exist no problem with
+ // little / big endians! - Bug 88121
+ maAny <<= OUString( reinterpret_cast< const char* >( aSeq.getConstArray() ), nLen - 1, RTL_TEXTENCODING_UTF8 );
+ }
+ else
+ maAny <<= aSeq;
+ }
+
+ return maAny.hasValue();
+}
+
+
+bool TransferableHelper::WriteObject( tools::SvRef<SotTempStream>&, void*, sal_uInt32, const DataFlavor& )
+{
+ OSL_FAIL( "TransferableHelper::WriteObject( ... ) not implemented" );
+ return false;
+}
+
+
+void TransferableHelper::DragFinished( sal_Int8 )
+{
+}
+
+
+void TransferableHelper::ObjectReleased()
+{
+}
+
+
+void TransferableHelper::PrepareOLE( const TransferableObjectDescriptor& rObjDesc )
+{
+ mxObjDesc.reset(new TransferableObjectDescriptor(rObjDesc));
+
+ if( HasFormat( SotClipboardFormatId::OBJECTDESCRIPTOR ) )
+ AddFormat( SotClipboardFormatId::OBJECTDESCRIPTOR );
+}
+
+void TransferableHelper::CopyToClipboard(const Reference<XClipboard>& rClipboard) const
+{
+ if( rClipboard.is() )
+ mxClipboard = rClipboard;
+
+ if( !mxClipboard.is() || mxTerminateListener.is() )
+ return;
+
+ try
+ {
+ TransferableHelper* pThis = const_cast< TransferableHelper* >( this );
+ pThis->mxTerminateListener = new TerminateListener( *pThis );
+ Reference< XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
+ xDesktop->addTerminateListener( pThis->mxTerminateListener );
+
+ mxClipboard->setContents( pThis, pThis );
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+void TransferableHelper::CopyToClipboard( vcl::Window *pWindow ) const
+{
+ DBG_ASSERT( pWindow, "Window pointer is NULL" );
+ Reference< XClipboard > xClipboard;
+
+ if( pWindow )
+ xClipboard = pWindow->GetClipboard();
+
+ CopyToClipboard(xClipboard);
+}
+
+void TransferableHelper::CopyToSelection(const Reference<XClipboard>& rSelection) const
+{
+ if( !rSelection.is() || mxTerminateListener.is() )
+ return;
+
+ try
+ {
+ TransferableHelper* pThis = const_cast< TransferableHelper* >( this );
+ pThis->mxTerminateListener = new TerminateListener( *pThis );
+ Reference< XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
+ xDesktop->addTerminateListener( pThis->mxTerminateListener );
+
+ rSelection->setContents( pThis, pThis );
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+void TransferableHelper::CopyToPrimarySelection() const
+{
+ CopyToSelection(GetSystemPrimarySelection());
+}
+
+void TransferableHelper::StartDrag( vcl::Window* pWindow, sal_Int8 nDnDSourceActions )
+
+{
+ DBG_ASSERT( pWindow, "Window pointer is NULL" );
+ Reference< XDragSource > xDragSource( pWindow->GetDragSource() );
+
+ if( !xDragSource.is() )
+ return;
+
+ /*
+ * #96792# release mouse before actually starting DnD.
+ * This is necessary for the X11 DnD implementation to work.
+ */
+ if( pWindow->IsMouseCaptured() )
+ pWindow->ReleaseMouse();
+
+ const Point aPt( pWindow->GetPointerPosPixel() );
+
+ // On macOS we are forced to execute 'startDrag' synchronously
+ // contrary to the XDragSource interface specification because
+ // we can receive drag events from the system only in the main
+ // thread
+#if !defined(MACOSX)
+ SolarMutexReleaser aReleaser;
+#endif
+
+ try
+ {
+ DragGestureEvent aEvt;
+ aEvt.DragAction = DNDConstants::ACTION_COPY;
+ aEvt.DragOriginX = aPt.X();
+ aEvt.DragOriginY = aPt.Y();
+ aEvt.DragSource = xDragSource;
+
+ xDragSource->startDrag( aEvt, nDnDSourceActions, DND_POINTER_NONE, DND_IMAGE_NONE, this, this );
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+void TransferableHelper::ClearPrimarySelection()
+{
+ Reference< XClipboard > xSelection(GetSystemPrimarySelection());
+
+ if( xSelection.is() )
+ xSelection->setContents( nullptr, nullptr );
+}
+
+const Sequence< sal_Int8 >& TransferableHelper::getUnoTunnelId()
+{
+ static const comphelper::UnoIdInit theTransferableHelperUnoTunnelId;
+ return theTransferableHelperUnoTunnelId.getSeq();
+}
+
+namespace {
+
+class TransferableClipboardNotifier : public ::cppu::WeakImplHelper< XClipboardListener >
+{
+private:
+ ::osl::Mutex& mrMutex;
+ Reference< XClipboardNotifier > mxNotifier;
+ TransferableDataHelper* mpListener;
+
+protected:
+ // XClipboardListener
+ virtual void SAL_CALL changedContents( const clipboard::ClipboardEvent& event ) override;
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const EventObject& Source ) override;
+
+public:
+ TransferableClipboardNotifier( const Reference< XClipboard >& _rxClipboard, TransferableDataHelper& _rListener, ::osl::Mutex& _rMutex );
+
+ /// determines whether we're currently listening
+ bool isListening() const { return mpListener != nullptr; }
+
+ /// makes the instance non-functional
+ void dispose();
+};
+
+}
+
+TransferableClipboardNotifier::TransferableClipboardNotifier( const Reference< XClipboard >& _rxClipboard, TransferableDataHelper& _rListener, ::osl::Mutex& _rMutex )
+ :mrMutex( _rMutex )
+ ,mxNotifier( _rxClipboard, UNO_QUERY )
+ ,mpListener( &_rListener )
+{
+ osl_atomic_increment( &m_refCount );
+ {
+ if ( mxNotifier.is() )
+ mxNotifier->addClipboardListener( this );
+ else
+ // born dead
+ mpListener = nullptr;
+ }
+ osl_atomic_decrement( &m_refCount );
+}
+
+
+void SAL_CALL TransferableClipboardNotifier::changedContents( const clipboard::ClipboardEvent& event )
+{
+ SolarMutexGuard aSolarGuard;
+ // the SolarMutex here is necessary, since
+ // - we cannot call mpListener without our own mutex locked
+ // - Rebind respectively InitFormats (called by Rebind) will
+ // try to lock the SolarMutex, too
+ ::osl::MutexGuard aGuard( mrMutex );
+ if( mpListener )
+ mpListener->Rebind( event.Contents );
+}
+
+
+void SAL_CALL TransferableClipboardNotifier::disposing( const EventObject& )
+{
+ // clipboard is being disposed. Hmm. Okay, become disfunctional myself.
+ dispose();
+}
+
+
+void TransferableClipboardNotifier::dispose()
+{
+ ::osl::MutexGuard aGuard( mrMutex );
+
+ Reference< XClipboardListener > xKeepMeAlive( this );
+
+ if ( mxNotifier.is() )
+ mxNotifier->removeClipboardListener( this );
+ mxNotifier.clear();
+
+ mpListener = nullptr;
+}
+
+struct TransferableDataHelper_Impl
+{
+ ::osl::Mutex maMutex;
+ rtl::Reference<TransferableClipboardNotifier> mxClipboardListener;
+
+ TransferableDataHelper_Impl()
+ {
+ }
+};
+
+TransferableDataHelper::TransferableDataHelper()
+ : mxObjDesc(new TransferableObjectDescriptor)
+ , mxImpl(new TransferableDataHelper_Impl)
+{
+}
+
+TransferableDataHelper::TransferableDataHelper(const Reference< css::datatransfer::XTransferable >& rxTransferable)
+ : mxTransfer(rxTransferable)
+ , mxObjDesc(new TransferableObjectDescriptor)
+ , mxImpl(new TransferableDataHelper_Impl)
+{
+ InitFormats();
+}
+
+TransferableDataHelper::TransferableDataHelper(const TransferableDataHelper& rDataHelper)
+ : mxTransfer(rDataHelper.mxTransfer)
+ , mxClipboard(rDataHelper.mxClipboard)
+ , maFormats(rDataHelper.maFormats)
+ , mxObjDesc(new TransferableObjectDescriptor(*rDataHelper.mxObjDesc))
+ , mxImpl(new TransferableDataHelper_Impl)
+{
+}
+
+TransferableDataHelper::TransferableDataHelper(TransferableDataHelper&& rDataHelper) noexcept
+ : mxTransfer(std::move(rDataHelper.mxTransfer))
+ , mxClipboard(std::move(rDataHelper.mxClipboard))
+ , maFormats(std::move(rDataHelper.maFormats))
+ , mxObjDesc(std::move(rDataHelper.mxObjDesc))
+ , mxImpl(new TransferableDataHelper_Impl)
+{
+}
+
+TransferableDataHelper& TransferableDataHelper::operator=( const TransferableDataHelper& rDataHelper )
+{
+ if ( this != &rDataHelper )
+ {
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+
+ const bool bWasClipboardListening = mxImpl->mxClipboardListener.is();
+
+ if (bWasClipboardListening)
+ StopClipboardListening();
+
+ mxTransfer = rDataHelper.mxTransfer;
+ maFormats = rDataHelper.maFormats;
+ mxObjDesc.reset(new TransferableObjectDescriptor(*rDataHelper.mxObjDesc));
+ mxClipboard = rDataHelper.mxClipboard;
+
+ if (bWasClipboardListening)
+ StartClipboardListening();
+ }
+
+ return *this;
+}
+
+TransferableDataHelper& TransferableDataHelper::operator=(TransferableDataHelper&& rDataHelper)
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+
+ const bool bWasClipboardListening = mxImpl->mxClipboardListener.is();
+
+ if (bWasClipboardListening)
+ StopClipboardListening();
+
+ mxTransfer = std::move(rDataHelper.mxTransfer);
+ maFormats = std::move(rDataHelper.maFormats);
+ mxObjDesc = std::move(rDataHelper.mxObjDesc);
+ mxClipboard = std::move(rDataHelper.mxClipboard);
+
+ if (bWasClipboardListening)
+ StartClipboardListening();
+
+ return *this;
+}
+
+TransferableDataHelper::~TransferableDataHelper()
+{
+ StopClipboardListening( );
+ {
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+ maFormats.clear();
+ mxObjDesc.reset();
+ }
+}
+
+void TransferableDataHelper::FillDataFlavorExVector( const Sequence< DataFlavor >& rDataFlavorSeq,
+ DataFlavorExVector& rDataFlavorExVector )
+{
+ try
+ {
+ Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
+ Reference< XMimeContentTypeFactory > xMimeFact = MimeContentTypeFactory::create( xContext );
+ DataFlavorEx aFlavorEx;
+ static const OUStringLiteral aCharsetStr( u"charset" );
+
+
+ for (auto const& rFlavor : rDataFlavorSeq)
+ {
+ Reference< XMimeContentType > xMimeType;
+
+ try
+ {
+ if( !rFlavor.MimeType.isEmpty() )
+ xMimeType = xMimeFact->createMimeContentType( rFlavor.MimeType );
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ aFlavorEx.MimeType = rFlavor.MimeType;
+ aFlavorEx.HumanPresentableName = rFlavor.HumanPresentableName;
+ aFlavorEx.DataType = rFlavor.DataType;
+ aFlavorEx.mnSotId = SotExchange::RegisterFormat( rFlavor );
+
+ rDataFlavorExVector.push_back( aFlavorEx );
+
+ // add additional formats for special mime types
+ if(SotClipboardFormatId::BMP == aFlavorEx.mnSotId || SotClipboardFormatId::PNG == aFlavorEx.mnSotId || SotClipboardFormatId::JPEG == aFlavorEx.mnSotId)
+ {
+ if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::BITMAP, aFlavorEx ) )
+ {
+ aFlavorEx.mnSotId = SotClipboardFormatId::BITMAP;
+ rDataFlavorExVector.push_back( aFlavorEx );
+ }
+ }
+ else if( SotClipboardFormatId::WMF == aFlavorEx.mnSotId || SotClipboardFormatId::EMF == aFlavorEx.mnSotId )
+ {
+ if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::GDIMETAFILE, aFlavorEx ) )
+ {
+ aFlavorEx.mnSotId = SotClipboardFormatId::GDIMETAFILE;
+ rDataFlavorExVector.push_back( aFlavorEx );
+ }
+ }
+ else if ( SotClipboardFormatId::HTML_SIMPLE == aFlavorEx.mnSotId )
+ {
+ // #104735# HTML_SIMPLE may also be inserted without comments
+ aFlavorEx.mnSotId = SotClipboardFormatId::HTML_NO_COMMENT;
+ rDataFlavorExVector.push_back( aFlavorEx );
+ }
+ else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/plain" ) )
+ {
+ // add, if it is a UTF-8 byte buffer
+ if( xMimeType->hasParameter( aCharsetStr ) )
+ {
+ if( xMimeType->getParameterValue( aCharsetStr ).equalsIgnoreAsciiCase( "unicode" ) ||
+ xMimeType->getParameterValue( aCharsetStr ).equalsIgnoreAsciiCase( "utf-16" ) )
+ {
+ rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::STRING;
+
+ }
+ }
+ }
+ else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/rtf" ) )
+ {
+ rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::RTF;
+ }
+ else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/richtext" ) )
+ {
+ rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::RICHTEXT;
+ }
+ else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/html" ) )
+
+ {
+ rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::HTML;
+ }
+ else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/uri-list" ) )
+ {
+ rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::FILE_LIST;
+ }
+ else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "application/x-openoffice-objectdescriptor-xml" ) )
+ {
+ rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::OBJECTDESCRIPTOR;
+ }
+ }
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+void TransferableDataHelper::InitFormats()
+{
+ SolarMutexGuard aSolarGuard;
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+
+ maFormats.clear();
+ mxObjDesc.reset(new TransferableObjectDescriptor);
+
+ if( !mxTransfer.is() )
+ return;
+
+ TransferableDataHelper::FillDataFlavorExVector(mxTransfer->getTransferDataFlavors(), maFormats);
+
+ for (auto const& format : maFormats)
+ {
+ if( SotClipboardFormatId::OBJECTDESCRIPTOR == format.mnSotId )
+ {
+ ImplSetParameterString(*mxObjDesc, format);
+ break;
+ }
+ }
+}
+
+
+bool TransferableDataHelper::HasFormat( SotClipboardFormatId nFormat ) const
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+ return std::any_of(maFormats.begin(), maFormats.end(),
+ [&](const DataFlavorEx& data) { return data.mnSotId == nFormat; });
+}
+
+bool TransferableDataHelper::HasFormat( const DataFlavor& rFlavor ) const
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+ for (auto const& format : maFormats)
+ {
+ if( TransferableDataHelper::IsEqual( rFlavor, format ) )
+ return true;
+ }
+
+ return false;
+}
+
+sal_uInt32 TransferableDataHelper::GetFormatCount() const
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+ return maFormats.size();
+}
+
+SotClipboardFormatId TransferableDataHelper::GetFormat( sal_uInt32 nFormat ) const
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+ DBG_ASSERT(nFormat < maFormats.size(), "TransferableDataHelper::GetFormat: invalid format index");
+ return( ( nFormat < maFormats.size() ) ? maFormats[ nFormat ].mnSotId : SotClipboardFormatId::NONE );
+}
+
+DataFlavor TransferableDataHelper::GetFormatDataFlavor( sal_uInt32 nFormat ) const
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+ DBG_ASSERT(nFormat < maFormats.size(), "TransferableDataHelper::GetFormat: invalid format index");
+
+ DataFlavor aRet;
+
+ if (nFormat < maFormats.size())
+ aRet = maFormats[nFormat];
+
+ return aRet;
+}
+
+
+Reference< XTransferable > TransferableDataHelper::GetXTransferable() const
+{
+ Reference< XTransferable > xRet;
+
+ if( mxTransfer.is() )
+ {
+ try
+ {
+ xRet = mxTransfer;
+
+ // do a dummy call to check, if this interface is valid (nasty)
+ xRet->getTransferDataFlavors();
+
+ }
+ catch( const css::uno::Exception& )
+ {
+ xRet.clear();
+ }
+ }
+
+ return xRet;
+}
+
+
+Any TransferableDataHelper::GetAny( SotClipboardFormatId nFormat, const OUString& rDestDoc ) const
+{
+ Any aReturn;
+
+ DataFlavor aFlavor;
+ if ( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) )
+ aReturn = GetAny(aFlavor, rDestDoc);
+
+ return aReturn;
+}
+
+Any TransferableDataHelper::GetAny( const DataFlavor& rFlavor, const OUString& rDestDoc ) const
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+ Any aRet;
+
+ try
+ {
+ if( mxTransfer.is() )
+ {
+ const SotClipboardFormatId nRequestFormat = SotExchange::GetFormat( rFlavor );
+
+ Reference<css::datatransfer::XTransferable2> xTransfer2(mxTransfer, UNO_QUERY);
+
+ if( nRequestFormat != SotClipboardFormatId::NONE )
+ {
+ // try to get alien format first
+ for (auto const& format : maFormats)
+ {
+ if( ( nRequestFormat == format.mnSotId ) && !rFlavor.MimeType.equalsIgnoreAsciiCase( format.MimeType ) )
+ {
+// tdf#133365: only release solar mutex on Windows
+#ifdef _WIN32
+ // tdf#133527: first, make sure that we actually hold the mutex
+ SolarMutexGuard g;
+ // Our own thread may handle the nested IDataObject::GetData call,
+ // and try to acquire solar mutex
+ SolarMutexReleaser r;
+#endif // _WIN32
+
+ if (xTransfer2.is())
+ aRet = xTransfer2->getTransferData2(format, rDestDoc);
+ else
+ aRet = mxTransfer->getTransferData(format);
+ }
+
+ if( aRet.hasValue() )
+ break;
+ }
+ }
+
+ if( !aRet.hasValue() )
+ {
+// tdf#133365: only release solar mutex on Windows
+#ifdef _WIN32
+ // tdf#133527: first, make sure that we actually hold the mutex
+ SolarMutexGuard g;
+ // Our own thread may handle the nested IDataObject::GetData call,
+ // and try to acquire solar mutex
+ SolarMutexReleaser r;
+#endif // _WIN32
+
+ if (xTransfer2.is())
+ aRet = xTransfer2->getTransferData2(rFlavor, rDestDoc);
+ else
+ aRet = mxTransfer->getTransferData(rFlavor);
+ }
+ }
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ return aRet;
+}
+
+
+bool TransferableDataHelper::GetString( SotClipboardFormatId nFormat, OUString& rStr ) const
+{
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetString( aFlavor, rStr ) );
+}
+
+
+bool TransferableDataHelper::GetString( const DataFlavor& rFlavor, OUString& rStr ) const
+{
+ Any aAny = GetAny(rFlavor, OUString());
+ bool bRet = false;
+
+ if( aAny.hasValue() )
+ {
+ OUString aOUString;
+ Sequence< sal_Int8 > aSeq;
+
+ if( aAny >>= aOUString )
+ {
+ rStr = aOUString;
+ bRet = true;
+ }
+ else if( aAny >>= aSeq )
+ {
+
+ const char* pChars = reinterpret_cast< const char* >( aSeq.getConstArray() );
+ sal_Int32 nLen = aSeq.getLength();
+
+ //JP 10.10.2001: 92930 - don't copy the last zero character into the string.
+ //DVO 2002-05-27: strip _all_ trailing zeros
+ while( nLen && ( 0 == *( pChars + nLen - 1 ) ) )
+ --nLen;
+
+ rStr = OUString( pChars, nLen, osl_getThreadTextEncoding() );
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+
+bool TransferableDataHelper::GetBitmapEx( SotClipboardFormatId nFormat, BitmapEx& rBmpEx ) const
+{
+ if(SotClipboardFormatId::BITMAP == nFormat)
+ {
+ // try to get PNG first
+ DataFlavor aFlavor;
+
+ if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::PNG, aFlavor))
+ {
+ if(GetBitmapEx(aFlavor, rBmpEx))
+ {
+ return true;
+ }
+ }
+
+ // then JPEG
+ if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::JPEG, aFlavor))
+ {
+ if(GetBitmapEx(aFlavor, rBmpEx))
+ {
+ return true;
+ }
+ }
+ }
+
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetBitmapEx( aFlavor, rBmpEx ) );
+}
+
+
+bool TransferableDataHelper::GetBitmapEx( const DataFlavor& rFlavor, BitmapEx& rBmpEx ) const
+{
+ tools::SvRef<SotTempStream> xStm;
+ DataFlavor aSubstFlavor;
+ bool bRet(GetSotStorageStream(rFlavor, xStm));
+ bool bSuppressPNG(false); // #122982# If PNG stream not accessed, but BMP one, suppress trying to load PNG
+ bool bSuppressJPEG(false);
+
+ if(!bRet && HasFormat(SotClipboardFormatId::PNG) && SotExchange::GetFormatDataFlavor(SotClipboardFormatId::PNG, aSubstFlavor))
+ {
+ // when no direct success, try if PNG is available
+ bRet = GetSotStorageStream(aSubstFlavor, xStm);
+ bSuppressJPEG = bRet;
+ }
+
+ if(!bRet && HasFormat(SotClipboardFormatId::JPEG) && SotExchange::GetFormatDataFlavor(SotClipboardFormatId::JPEG, aSubstFlavor))
+ {
+ bRet = GetSotStorageStream(aSubstFlavor, xStm);
+ bSuppressPNG = bRet;
+ }
+
+ if(!bRet && HasFormat(SotClipboardFormatId::BMP) && SotExchange::GetFormatDataFlavor(SotClipboardFormatId::BMP, aSubstFlavor))
+ {
+ // when no direct success, try if BMP is available
+ bRet = GetSotStorageStream(aSubstFlavor, xStm);
+ bSuppressPNG = bRet;
+ bSuppressJPEG = bRet;
+ }
+
+ if(bRet)
+ {
+ if(!bSuppressPNG && rFlavor.MimeType.equalsIgnoreAsciiCase("image/png"))
+ {
+ // it's a PNG, import to BitmapEx
+ vcl::PngImageReader aPNGReader(*xStm);
+ rBmpEx = aPNGReader.read();
+ }
+ else if(!bSuppressJPEG && rFlavor.MimeType.equalsIgnoreAsciiCase("image/jpeg"))
+ {
+ // it's a JPEG, import to BitmapEx
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ Graphic aGraphic;
+ if (rFilter.ImportGraphic(aGraphic, u"", *xStm) == ERRCODE_NONE)
+ rBmpEx = aGraphic.GetBitmapEx();
+ }
+
+ if(rBmpEx.IsEmpty())
+ {
+ Bitmap aBitmap;
+ AlphaMask aMask;
+
+ // explicitly use Bitmap::Read with bFileHeader = sal_True
+ // #i124085# keep DIBV5 for read from clipboard, but should not happen
+ ReadDIBV5(aBitmap, aMask, *xStm);
+
+ if(aMask.GetBitmap().IsEmpty())
+ {
+ rBmpEx = aBitmap;
+ }
+ else
+ {
+ rBmpEx = BitmapEx(aBitmap, aMask);
+ }
+ }
+
+ bRet = (ERRCODE_NONE == xStm->GetError() && !rBmpEx.IsEmpty());
+
+ /* SJ: #110748# At the moment we are having problems with DDB inserted as DIB. The
+ problem is, that some graphics are inserted much too big because the nXPelsPerMeter
+ and nYPelsPerMeter of the bitmap fileheader isn't including the correct value.
+ Due to this reason the following code assumes that bitmaps with a logical size
+ greater than 50 cm aren't having the correct mapmode set.
+
+ The following code should be removed if DDBs and DIBs are supported via clipboard
+ properly.
+ */
+ if(bRet)
+ {
+ const MapMode aMapMode(rBmpEx.GetPrefMapMode());
+
+ if(MapUnit::MapPixel != aMapMode.GetMapUnit())
+ {
+ const Size aSize(OutputDevice::LogicToLogic(rBmpEx.GetPrefSize(), aMapMode, MapMode(MapUnit::Map100thMM)));
+
+ // #i122388# This wrongly corrects in the given case; changing from 5000 100th mm to
+ // the described 50 cm (which is 50000 100th mm)
+ if((aSize.Width() > 50000) || (aSize.Height() > 50000))
+ {
+ rBmpEx.SetPrefMapMode(MapMode(MapUnit::MapPixel));
+
+ // #i122388# also adapt size by applying the mew MapMode
+ const Size aNewSize(o3tl::convert(aSize, o3tl::Length::mm100, o3tl::Length::pt));
+ rBmpEx.SetPrefSize(aNewSize);
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+
+bool TransferableDataHelper::GetGDIMetaFile(SotClipboardFormatId nFormat, GDIMetaFile& rMtf, size_t nMaxActions) const
+{
+ DataFlavor aFlavor;
+ return SotExchange::GetFormatDataFlavor(nFormat, aFlavor) &&
+ GetGDIMetaFile(aFlavor, rMtf) &&
+ (nMaxActions == 0 || rMtf.GetActionSize() < nMaxActions);
+}
+
+
+bool TransferableDataHelper::GetGDIMetaFile( const DataFlavor& rFlavor, GDIMetaFile& rMtf ) const
+{
+ tools::SvRef<SotTempStream> xStm;
+ DataFlavor aSubstFlavor;
+ bool bRet = false;
+
+ if( GetSotStorageStream( rFlavor, xStm ) )
+ {
+ SvmReader aReader( *xStm );
+ aReader.Read( rMtf );
+ bRet = ( xStm->GetError() == ERRCODE_NONE );
+ }
+
+ if( !bRet &&
+ HasFormat( SotClipboardFormatId::EMF ) &&
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EMF, aSubstFlavor ) &&
+ GetSotStorageStream( aSubstFlavor, xStm ) )
+ {
+ Graphic aGraphic;
+
+ if( GraphicConverter::Import( *xStm, aGraphic ) == ERRCODE_NONE )
+ {
+ rMtf = aGraphic.GetGDIMetaFile();
+ bRet = true;
+ }
+ }
+
+ if( !bRet &&
+ HasFormat( SotClipboardFormatId::WMF ) &&
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::WMF, aSubstFlavor ) &&
+ GetSotStorageStream( aSubstFlavor, xStm ) )
+ {
+ Graphic aGraphic;
+
+ if( GraphicConverter::Import( *xStm, aGraphic ) == ERRCODE_NONE )
+ {
+ rMtf = aGraphic.GetGDIMetaFile();
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+
+bool TransferableDataHelper::GetGraphic( SotClipboardFormatId nFormat, Graphic& rGraphic ) const
+{
+ if(SotClipboardFormatId::BITMAP == nFormat)
+ {
+ // try to get PNG first
+ DataFlavor aFlavor;
+
+ if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::PNG, aFlavor))
+ {
+ if(GetGraphic(aFlavor, rGraphic))
+ {
+ return true;
+ }
+ }
+ }
+
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetGraphic( aFlavor, rGraphic ) );
+}
+
+
+bool TransferableDataHelper::GetGraphic( const css::datatransfer::DataFlavor& rFlavor, Graphic& rGraphic ) const
+{
+ DataFlavor aFlavor;
+ bool bRet = false;
+
+ if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::PNG, aFlavor) &&
+ TransferableDataHelper::IsEqual(aFlavor, rFlavor))
+ {
+ // try to get PNG first
+ BitmapEx aBmpEx;
+
+ bRet = GetBitmapEx( aFlavor, aBmpEx );
+ if( bRet )
+ rGraphic = aBmpEx;
+ }
+ else if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::PDF, aFlavor) &&
+ TransferableDataHelper::IsEqual(aFlavor, rFlavor))
+ {
+ Graphic aGraphic;
+ tools::SvRef<SotTempStream> xStm;
+ if (GetSotStorageStream(rFlavor, xStm))
+ {
+ if (GraphicConverter::Import(*xStm, aGraphic) == ERRCODE_NONE)
+ {
+ rGraphic = aGraphic;
+ bRet = true;
+ }
+ }
+ }
+ else if (SotExchange::GetFormatDataFlavor(SotClipboardFormatId::JPEG, aFlavor) && TransferableDataHelper::IsEqual(aFlavor, rFlavor))
+ {
+ BitmapEx aBitmapEx;
+
+ bRet = GetBitmapEx(aFlavor, aBitmapEx);
+ if (bRet)
+ rGraphic = aBitmapEx;
+ }
+ else if(SotExchange::GetFormatDataFlavor( SotClipboardFormatId::BITMAP, aFlavor ) &&
+ TransferableDataHelper::IsEqual( aFlavor, rFlavor ) )
+ {
+ BitmapEx aBmpEx;
+
+ bRet = GetBitmapEx( aFlavor, aBmpEx );
+ if( bRet )
+ rGraphic = aBmpEx;
+ }
+ else if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::GDIMETAFILE, aFlavor ) &&
+ TransferableDataHelper::IsEqual( aFlavor, rFlavor ) )
+ {
+ GDIMetaFile aMtf;
+
+ bRet = GetGDIMetaFile( aFlavor, aMtf );
+ if( bRet )
+ rGraphic = aMtf;
+ }
+ else
+ {
+ tools::SvRef<SotTempStream> xStm;
+
+ if( GetSotStorageStream( rFlavor, xStm ) )
+ {
+ TypeSerializer aSerializer(*xStm);
+ aSerializer.readGraphic(rGraphic);
+ bRet = ( xStm->GetError() == ERRCODE_NONE );
+ }
+ }
+
+ return bRet;
+}
+
+
+bool TransferableDataHelper::GetImageMap( SotClipboardFormatId nFormat, ImageMap& rIMap ) const
+{
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetImageMap( aFlavor, rIMap ) );
+}
+
+
+bool TransferableDataHelper::GetImageMap( const css::datatransfer::DataFlavor& rFlavor, ImageMap& rIMap ) const
+{
+ tools::SvRef<SotTempStream> xStm;
+ bool bRet = GetSotStorageStream( rFlavor, xStm );
+
+ if( bRet )
+ {
+ rIMap.Read( *xStm );
+ bRet = ( xStm->GetError() == ERRCODE_NONE );
+ }
+
+ return bRet;
+}
+
+
+bool TransferableDataHelper::GetTransferableObjectDescriptor( SotClipboardFormatId nFormat, TransferableObjectDescriptor& rDesc )
+{
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetTransferableObjectDescriptor( rDesc ) );
+}
+
+
+bool TransferableDataHelper::GetTransferableObjectDescriptor( TransferableObjectDescriptor& rDesc )
+{
+ rDesc = *mxObjDesc;
+ return true;
+}
+
+
+bool TransferableDataHelper::GetINetBookmark( SotClipboardFormatId nFormat, INetBookmark& rBmk ) const
+{
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetINetBookmark( aFlavor, rBmk ) );
+}
+
+
+bool TransferableDataHelper::GetINetBookmark( const css::datatransfer::DataFlavor& rFlavor, INetBookmark& rBmk ) const
+{
+ if( !HasFormat( rFlavor ))
+ return false;
+
+ bool bRet = false;
+ const SotClipboardFormatId nFormat = SotExchange::GetFormat( rFlavor );
+ switch( nFormat )
+ {
+ case SotClipboardFormatId::SOLK:
+ case SotClipboardFormatId::UNIFORMRESOURCELOCATOR:
+ {
+ OUString aString;
+ if( GetString( rFlavor, aString ) )
+ {
+ if( SotClipboardFormatId::UNIFORMRESOURCELOCATOR == nFormat )
+ {
+ rBmk = INetBookmark( aString, aString );
+ bRet = true;
+ }
+ else
+ {
+ OUString aURL, aDesc;
+ sal_Int32 nStart = aString.indexOf( '@' ), nLen = aString.toInt32();
+
+ if( !nLen && aString[ 0 ] != '0' )
+ {
+ SAL_INFO( "svtools", "SOLK: 1. len=0" );
+ }
+ if( nStart == -1 || nLen > aString.getLength() - nStart - 3 )
+ {
+ SAL_INFO( "svtools", "SOLK: 1. illegal start or wrong len" );
+ }
+ aURL = aString.copy( nStart + 1, nLen );
+
+ aString = aString.replaceAt( 0, nStart + 1 + nLen, u"" );
+ nStart = aString.indexOf( '@' );
+ nLen = aString.toInt32();
+
+ if( !nLen && aString[ 0 ] != '0' )
+ {
+ SAL_INFO( "svtools", "SOLK: 2. len=0" );
+ }
+ if( nStart == -1 || nLen > aString.getLength() - nStart - 1 )
+ {
+ SAL_INFO( "svtools", "SOLK: 2. illegal start or wrong len" );
+ }
+ aDesc = aString.copy( nStart+1, nLen );
+
+ rBmk = INetBookmark( aURL, aDesc );
+ bRet = true;
+ }
+ }
+ }
+ break;
+
+ case SotClipboardFormatId::NETSCAPE_BOOKMARK:
+ {
+ Sequence<sal_Int8> aSeq = GetSequence(rFlavor, OUString());
+
+ if (2048 == aSeq.getLength())
+ {
+ const char* p1 = reinterpret_cast< const char* >( aSeq.getConstArray() );
+ const char* p2 = reinterpret_cast< const char* >( aSeq.getConstArray() ) + 1024;
+ rBmk = INetBookmark( OUString( p1, strlen(p1), osl_getThreadTextEncoding() ),
+ OUString( p2, strlen(p2), osl_getThreadTextEncoding() ) );
+ bRet = true;
+ }
+ }
+ break;
+
+#ifdef _WIN32
+ case SotClipboardFormatId::FILEGRPDESCRIPTOR:
+ {
+ Sequence<sal_Int8> aSeq = GetSequence(rFlavor, OUString());
+
+ if (aSeq.getLength())
+ {
+ FILEGROUPDESCRIPTORW const * pFDesc = reinterpret_cast<FILEGROUPDESCRIPTORW const *>(aSeq.getConstArray());
+
+ if( pFDesc->cItems )
+ {
+ OUString aDesc( o3tl::toU(pFDesc->fgd[ 0 ].cFileName) );
+
+ if( ( aDesc.getLength() > 4 ) && aDesc.endsWithIgnoreAsciiCase(".URL") )
+ {
+ std::unique_ptr<SvStream> pStream(::utl::UcbStreamHelper::CreateStream( INetURLObject( aDesc ).GetMainURL( INetURLObject::DecodeMechanism::NONE ),
+ StreamMode::STD_READ ));
+
+ if( !pStream || pStream->GetError() )
+ {
+ DataFlavor aFileContentFlavor;
+
+ aSeq.realloc( 0 );
+ pStream.reset();
+
+ if (SotExchange::GetFormatDataFlavor(SotClipboardFormatId::FILECONTENT, aFileContentFlavor))
+ {
+ aSeq = GetSequence(aFileContentFlavor, OUString());
+ if (aSeq.getLength())
+ pStream.reset(new SvMemoryStream( const_cast<sal_Int8 *>(aSeq.getConstArray()), aSeq.getLength(), StreamMode::STD_READ ));
+ }
+ }
+
+ if( pStream )
+ {
+ OString aLine;
+ bool bInA = false, bInW = false, bAFound = false;
+
+ while( pStream->ReadLine( aLine ) )
+ {
+ if (aLine.startsWithIgnoreAsciiCase("[InternetShortcut", &aLine))
+ {
+ // May be [InternetShortcut], or [InternetShortcut.A], or
+ // [InternetShortcut.W] (the latter has UTF-7-encoded URL)
+ bInW = aLine.equalsIgnoreAsciiCase(".W]");
+ bInA = !bAFound && !bInW
+ && (aLine == "]" || aLine.equalsIgnoreAsciiCase(".A]"));
+ }
+ else if (aLine.startsWith("["))
+ {
+ bInA = bInW = false;
+ }
+ else if ((bInA || bInW) && aLine.startsWithIgnoreAsciiCase("URL="))
+ {
+ auto eTextEncoding = bInW ? RTL_TEXTENCODING_UTF7
+ : osl_getThreadTextEncoding();
+ rBmk = INetBookmark( OStringToOUString(aLine.subView(4), eTextEncoding),
+ aDesc.copy(0, aDesc.getLength() - 4) );
+ bRet = true;
+ if (bInW)
+ break;
+ else
+ bAFound = true; // Keep looking for "W"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+#endif
+ default: break;
+ }
+ return bRet;
+}
+
+
+bool TransferableDataHelper::GetINetImage( SotClipboardFormatId nFormat,
+ INetImage& rINtImg ) const
+{
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetINetImage( aFlavor, rINtImg ) );
+}
+
+
+bool TransferableDataHelper::GetINetImage(
+ const css::datatransfer::DataFlavor& rFlavor,
+ INetImage& rINtImg ) const
+{
+ tools::SvRef<SotTempStream> xStm;
+ bool bRet = GetSotStorageStream( rFlavor, xStm );
+
+ if( bRet )
+ bRet = rINtImg.Read( *xStm, SotExchange::GetFormat( rFlavor ) );
+ return bRet;
+}
+
+
+bool TransferableDataHelper::GetFileList( SotClipboardFormatId nFormat,
+ FileList& rFileList ) const
+{
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetFileList( rFileList ) );
+}
+
+
+bool TransferableDataHelper::GetFileList( FileList& rFileList ) const
+{
+ tools::SvRef<SotTempStream> xStm;
+ bool bRet = false;
+
+ for( sal_uInt32 i = 0, nFormatCount = GetFormatCount(); ( i < nFormatCount ) && !bRet; ++i )
+ {
+ if( SotClipboardFormatId::FILE_LIST == GetFormat( i ) )
+ {
+ const DataFlavor aFlavor( GetFormatDataFlavor( i ) );
+
+ if( GetSotStorageStream( aFlavor, xStm ) )
+ {
+ if( aFlavor.MimeType.indexOf( "text/uri-list" ) > -1 )
+ {
+ OStringBuffer aDiskString;
+
+ while( xStm->ReadLine( aDiskString ) )
+ if( !aDiskString.isEmpty() && aDiskString[0] != '#' )
+ rFileList.AppendFile( OStringToOUString(aDiskString, RTL_TEXTENCODING_UTF8) );
+
+ bRet = true;
+ }
+ else
+ bRet = ( ReadFileList( *xStm, rFileList ).GetError() == ERRCODE_NONE );
+ }
+ }
+ }
+
+ return bRet;
+}
+
+
+Sequence<sal_Int8> TransferableDataHelper::GetSequence( SotClipboardFormatId nFormat, const OUString& rDestDoc ) const
+{
+ DataFlavor aFlavor;
+ if (!SotExchange::GetFormatDataFlavor(nFormat, aFlavor))
+ return Sequence<sal_Int8>();
+
+ return GetSequence(aFlavor, rDestDoc);
+}
+
+Sequence<sal_Int8> TransferableDataHelper::GetSequence( const DataFlavor& rFlavor, const OUString& rDestDoc ) const
+{
+ const Any aAny = GetAny(rFlavor, rDestDoc);
+ Sequence<sal_Int8> aSeq;
+ if (aAny.hasValue())
+ aAny >>= aSeq;
+
+ return aSeq;
+}
+
+
+bool TransferableDataHelper::GetSotStorageStream( SotClipboardFormatId nFormat, tools::SvRef<SotTempStream>& rxStream ) const
+{
+ DataFlavor aFlavor;
+ return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetSotStorageStream( aFlavor, rxStream ) );
+}
+
+
+bool TransferableDataHelper::GetSotStorageStream( const DataFlavor& rFlavor, tools::SvRef<SotTempStream>& rxStream ) const
+{
+ Sequence<sal_Int8> aSeq = GetSequence(rFlavor, OUString());
+
+ if (aSeq.hasElements())
+ {
+ rxStream = new SotTempStream( "" );
+ rxStream->WriteBytes( aSeq.getConstArray(), aSeq.getLength() );
+ rxStream->Seek( 0 );
+ }
+
+ return aSeq.hasElements();
+}
+
+Reference<XInputStream> TransferableDataHelper::GetInputStream( SotClipboardFormatId nFormat, const OUString& rDestDoc ) const
+{
+ DataFlavor aFlavor;
+ if (!SotExchange::GetFormatDataFlavor(nFormat, aFlavor))
+ return Reference<XInputStream>();
+
+ return GetInputStream(aFlavor, rDestDoc);
+}
+
+Reference<XInputStream> TransferableDataHelper::GetInputStream( const DataFlavor& rFlavor, const OUString& rDestDoc ) const
+{
+ Sequence<sal_Int8> aSeq = GetSequence(rFlavor, rDestDoc);
+
+ if (!aSeq.hasElements())
+ return Reference<XInputStream>();
+
+ Reference<XInputStream> xStream(new comphelper::SequenceInputStream(aSeq));
+ return xStream;
+}
+
+void TransferableDataHelper::Rebind( const Reference< XTransferable >& _rxNewContent )
+{
+ mxTransfer = _rxNewContent;
+ InitFormats();
+}
+
+bool TransferableDataHelper::StartClipboardListening( )
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+
+ StopClipboardListening( );
+
+ mxImpl->mxClipboardListener = new TransferableClipboardNotifier(mxClipboard, *this, mxImpl->maMutex);
+
+ return mxImpl->mxClipboardListener->isListening();
+}
+
+void TransferableDataHelper::StopClipboardListening( )
+{
+ ::osl::MutexGuard aGuard(mxImpl->maMutex);
+
+ if (mxImpl->mxClipboardListener.is())
+ {
+ mxImpl->mxClipboardListener->dispose();
+ mxImpl->mxClipboardListener.clear();
+ }
+}
+
+TransferableDataHelper TransferableDataHelper::CreateFromClipboard(const css::uno::Reference<css::datatransfer::clipboard::XClipboard>& rClipboard)
+{
+ TransferableDataHelper aRet;
+
+ if( rClipboard.is() )
+ {
+ try
+ {
+ Reference< XTransferable > xTransferable( rClipboard->getContents() );
+
+ if( xTransferable.is() )
+ {
+ aRet = TransferableDataHelper( xTransferable );
+ // also copy the clipboard
+ aRet.mxClipboard = rClipboard;
+ }
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+ }
+
+ return aRet;
+}
+
+TransferableDataHelper TransferableDataHelper::CreateFromSystemClipboard( vcl::Window * pWindow )
+{
+ DBG_ASSERT( pWindow, "Window pointer is NULL" );
+
+ Reference< XClipboard > xClipboard;
+
+ if( pWindow )
+ xClipboard = pWindow->GetClipboard();
+
+ return CreateFromClipboard(xClipboard);
+}
+
+TransferableDataHelper TransferableDataHelper::CreateFromPrimarySelection()
+{
+ Reference< XClipboard > xSelection(GetSystemPrimarySelection());
+ TransferableDataHelper aRet;
+
+ if( xSelection.is() )
+ {
+ SolarMutexReleaser aReleaser;
+
+ try
+ {
+ Reference< XTransferable > xTransferable( xSelection->getContents() );
+
+ if( xTransferable.is() )
+ {
+ aRet = TransferableDataHelper( xTransferable );
+ aRet.mxClipboard = xSelection;
+ }
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+ }
+
+ return aRet;
+}
+
+bool TransferableDataHelper::IsEqual( const css::datatransfer::DataFlavor& rInternalFlavor,
+ const css::datatransfer::DataFlavor& rRequestFlavor )
+{
+ Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
+ bool bRet = false;
+
+ try
+ {
+ Reference< XMimeContentTypeFactory > xMimeFact = MimeContentTypeFactory::create( xContext );
+
+ Reference< XMimeContentType > xRequestType1( xMimeFact->createMimeContentType( rInternalFlavor.MimeType ) );
+ Reference< XMimeContentType > xRequestType2( xMimeFact->createMimeContentType( rRequestFlavor.MimeType ) );
+
+ if( xRequestType1.is() && xRequestType2.is() )
+ {
+ if( xRequestType1->getFullMediaType().equalsIgnoreAsciiCase( xRequestType2->getFullMediaType() ) )
+ {
+ if( xRequestType1->getFullMediaType().equalsIgnoreAsciiCase( "text/plain" ) )
+ {
+ // special handling for text/plain media types
+ static const OUStringLiteral aCharsetString( u"charset" );
+
+ if( !xRequestType2->hasParameter( aCharsetString ) ||
+ xRequestType2->getParameterValue( aCharsetString ).equalsIgnoreAsciiCase( "utf-16" ) ||
+ xRequestType2->getParameterValue( aCharsetString ).equalsIgnoreAsciiCase( "unicode" ) )
+ {
+ bRet = true;
+ }
+ }
+ else if( xRequestType1->getFullMediaType().equalsIgnoreAsciiCase( "application/x-openoffice" ) )
+ {
+ // special handling for application/x-openoffice media types
+ static const OUStringLiteral aFormatString( u"windows_formatname" );
+
+ if( xRequestType1->hasParameter( aFormatString ) &&
+ xRequestType2->hasParameter( aFormatString ) &&
+ xRequestType1->getParameterValue( aFormatString ).equalsIgnoreAsciiCase( xRequestType2->getParameterValue( aFormatString ) ) )
+ {
+ bRet = true;
+ }
+ }
+ else
+ bRet = true;
+ }
+ }
+ }
+ catch( const css::uno::Exception& )
+ {
+ bRet = rInternalFlavor.MimeType.equalsIgnoreAsciiCase( rRequestFlavor.MimeType );
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/transfer2.cxx b/vcl/source/treelist/transfer2.cxx
new file mode 100644
index 000000000..c63770d8a
--- /dev/null
+++ b/vcl/source/treelist/transfer2.cxx
@@ -0,0 +1,516 @@
+/* -*- 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 <config_vclplug.h>
+
+#include <osl/mutex.hxx>
+#include <sot/exchange.hxx>
+#include <tools/debug.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/datatransfer/clipboard/SystemClipboard.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTargetDragContext.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/uno/DeploymentException.hpp>
+#include <svl/urlbmk.hxx>
+#include <vcl/transfer.hxx>
+
+#include <svdata.hxx>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::io;
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::clipboard;
+using namespace ::com::sun::star::datatransfer::dnd;
+
+
+DragSourceHelper::DragGestureListener::DragGestureListener( DragSourceHelper& rDragSourceHelper ) :
+ mrParent( rDragSourceHelper )
+{
+}
+
+
+DragSourceHelper::DragGestureListener::~DragGestureListener()
+{
+}
+
+
+void SAL_CALL DragSourceHelper::DragGestureListener::disposing( const EventObject& )
+{
+}
+
+
+void SAL_CALL DragSourceHelper::DragGestureListener::dragGestureRecognized( const DragGestureEvent& rDGE )
+{
+ const SolarMutexGuard aGuard;
+
+ const Point aPtPixel( rDGE.DragOriginX, rDGE.DragOriginY );
+ mrParent.StartDrag( rDGE.DragAction, aPtPixel );
+}
+
+
+DragSourceHelper::DragSourceHelper( vcl::Window* pWindow ) :
+ mxDragGestureRecognizer( pWindow->GetDragGestureRecognizer() )
+{
+ if( mxDragGestureRecognizer.is() )
+ {
+ mxDragGestureListener = new DragSourceHelper::DragGestureListener( *this );
+ mxDragGestureRecognizer->addDragGestureListener( mxDragGestureListener );
+ }
+}
+
+
+void DragSourceHelper::dispose()
+{
+ Reference<XDragGestureRecognizer> xTmp;
+ {
+ std::scoped_lock aGuard( maMutex );
+ xTmp = std::move(mxDragGestureRecognizer);
+ }
+ if( xTmp.is() )
+ xTmp->removeDragGestureListener( mxDragGestureListener );
+}
+
+DragSourceHelper::~DragSourceHelper()
+{
+ dispose();
+}
+
+
+void DragSourceHelper::StartDrag( sal_Int8, const Point& )
+{
+}
+
+
+DropTargetHelper::DropTargetListener::DropTargetListener( DropTargetHelper& rDropTargetHelper ) :
+ mrParent( rDropTargetHelper )
+{
+}
+
+
+DropTargetHelper::DropTargetListener::~DropTargetListener()
+{
+}
+
+
+void SAL_CALL DropTargetHelper::DropTargetListener::disposing( const EventObject& )
+{
+}
+
+
+void SAL_CALL DropTargetHelper::DropTargetListener::drop( const DropTargetDropEvent& rDTDE )
+{
+ const SolarMutexGuard aGuard;
+
+ try
+ {
+ AcceptDropEvent aAcceptEvent;
+ ExecuteDropEvent aExecuteEvt( rDTDE.DropAction & ~DNDConstants::ACTION_DEFAULT, Point( rDTDE.LocationX, rDTDE.LocationY ), rDTDE );
+
+ aExecuteEvt.mbDefault = ( ( rDTDE.DropAction & DNDConstants::ACTION_DEFAULT ) != 0 );
+
+ // in case of a default action, call ::AcceptDrop first and use the returned
+ // accepted action as the execute action in the call to ::ExecuteDrop
+ aAcceptEvent.mnAction = aExecuteEvt.mnAction;
+ aAcceptEvent.maPosPixel = aExecuteEvt.maPosPixel;
+ static_cast<DropTargetEvent&>(const_cast<DropTargetDragEvent&>( aAcceptEvent.maDragEvent )) = rDTDE;
+ const_cast<DropTargetDragEvent&>( aAcceptEvent.maDragEvent ).DropAction = rDTDE.DropAction;
+ const_cast<DropTargetDragEvent&>( aAcceptEvent.maDragEvent ).LocationX = rDTDE.LocationX;
+ const_cast<DropTargetDragEvent&>( aAcceptEvent.maDragEvent ).LocationY = rDTDE.LocationY;
+ const_cast<DropTargetDragEvent&>( aAcceptEvent.maDragEvent ).SourceActions = rDTDE.SourceActions;
+ aAcceptEvent.mbLeaving = false;
+ aAcceptEvent.mbDefault = aExecuteEvt.mbDefault;
+
+ sal_Int8 nRet = mrParent.AcceptDrop( aAcceptEvent );
+
+ if( DNDConstants::ACTION_NONE != nRet )
+ {
+ rDTDE.Context->acceptDrop( nRet );
+
+ if( aExecuteEvt.mbDefault )
+ aExecuteEvt.mnAction = nRet;
+
+ nRet = mrParent.ExecuteDrop( aExecuteEvt );
+ }
+
+ rDTDE.Context->dropComplete( DNDConstants::ACTION_NONE != nRet );
+
+ mpLastDragOverEvent.reset();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+
+void SAL_CALL DropTargetHelper::DropTargetListener::dragEnter( const DropTargetDragEnterEvent& rDTDEE )
+{
+ const SolarMutexGuard aGuard;
+
+ try
+ {
+ mrParent.ImplBeginDrag( rDTDEE.SupportedDataFlavors );
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ dragOver( rDTDEE );
+}
+
+
+void SAL_CALL DropTargetHelper::DropTargetListener::dragOver( const DropTargetDragEvent& rDTDE )
+{
+ const SolarMutexGuard aGuard;
+
+ try
+ {
+ mpLastDragOverEvent.reset( new AcceptDropEvent( rDTDE.DropAction & ~DNDConstants::ACTION_DEFAULT, Point( rDTDE.LocationX, rDTDE.LocationY ), rDTDE ) );
+ mpLastDragOverEvent->mbDefault = ( ( rDTDE.DropAction & DNDConstants::ACTION_DEFAULT ) != 0 );
+
+ const sal_Int8 nRet = mrParent.AcceptDrop( *mpLastDragOverEvent );
+
+ if( DNDConstants::ACTION_NONE == nRet )
+ rDTDE.Context->rejectDrag();
+ else
+ rDTDE.Context->acceptDrag( nRet );
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+
+void SAL_CALL DropTargetHelper::DropTargetListener::dragExit( const DropTargetEvent& )
+{
+ const SolarMutexGuard aGuard;
+
+ try
+ {
+ if( mpLastDragOverEvent )
+ {
+ mpLastDragOverEvent->mbLeaving = true;
+ mrParent.AcceptDrop( *mpLastDragOverEvent );
+ mpLastDragOverEvent.reset();
+ }
+
+ mrParent.ImplEndDrag();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+
+void SAL_CALL DropTargetHelper::DropTargetListener::dropActionChanged( const DropTargetDragEvent& )
+{
+}
+
+
+DropTargetHelper::DropTargetHelper( vcl::Window* pWindow ) :
+ mxDropTarget( pWindow->GetDropTarget() )
+{
+ ImplConstruct();
+}
+
+
+DropTargetHelper::DropTargetHelper( const Reference< XDropTarget >& rxDropTarget ) :
+ mxDropTarget( rxDropTarget )
+{
+ ImplConstruct();
+}
+
+
+void DropTargetHelper::dispose()
+{
+ Reference< XDropTarget > xTmp;
+ {
+ std::scoped_lock aGuard( maMutex );
+ xTmp = std::move(mxDropTarget);
+ }
+ if( xTmp.is() )
+ xTmp->removeDropTargetListener( mxDropTargetListener );
+}
+
+DropTargetHelper::~DropTargetHelper()
+{
+ dispose();
+}
+
+
+void DropTargetHelper::ImplConstruct()
+{
+ if( mxDropTarget.is() )
+ {
+ mxDropTargetListener = new DropTargetHelper::DropTargetListener( *this );
+ mxDropTarget->addDropTargetListener( mxDropTargetListener );
+ mxDropTarget->setActive( true );
+ }
+}
+
+
+void DropTargetHelper::ImplBeginDrag( const Sequence< DataFlavor >& rSupportedDataFlavors )
+{
+ maFormats.clear();
+ TransferableDataHelper::FillDataFlavorExVector( rSupportedDataFlavors, maFormats );
+}
+
+
+void DropTargetHelper::ImplEndDrag()
+{
+ maFormats.clear();
+}
+
+
+sal_Int8 DropTargetHelper::AcceptDrop( const AcceptDropEvent& )
+{
+ return DNDConstants::ACTION_NONE;
+}
+
+
+sal_Int8 DropTargetHelper::ExecuteDrop( const ExecuteDropEvent& )
+{
+ return DNDConstants::ACTION_NONE;
+}
+
+
+bool DropTargetHelper::IsDropFormatSupported(SotClipboardFormatId nFormat) const
+{
+ return std::any_of(maFormats.begin(), maFormats.end(),
+ [&](const DataFlavorEx& data) { return data.mnSotId == nFormat; });
+}
+
+
+// TransferDataContainer
+
+namespace {
+
+struct TDataCntnrEntry_Impl
+{
+ css::uno::Any aAny;
+ SotClipboardFormatId nId;
+};
+
+}
+
+struct TransferDataContainer_Impl
+{
+ std::vector< TDataCntnrEntry_Impl > aFmtList;
+ Link<sal_Int8,void> aFinishedLnk;
+ std::unique_ptr<INetBookmark> pBookmk;
+
+ TransferDataContainer_Impl()
+ {
+ }
+};
+
+
+TransferDataContainer::TransferDataContainer()
+ : pImpl( new TransferDataContainer_Impl )
+{
+}
+
+
+TransferDataContainer::~TransferDataContainer()
+{
+}
+
+
+void TransferDataContainer::AddSupportedFormats()
+{
+}
+
+
+bool TransferDataContainer::GetData(
+ const css::datatransfer::DataFlavor& rFlavor, const OUString& /*rDestDoc*/ )
+{
+ bool bFnd = false;
+ SotClipboardFormatId nFmtId = SotExchange::GetFormat( rFlavor );
+
+ // test first the list
+ for (auto const& format : pImpl->aFmtList)
+ {
+ if( nFmtId == format.nId )
+ {
+ bFnd = SetAny( format.aAny );
+ break;
+ }
+ }
+
+ // test second the bookmark pointer
+ if( !bFnd )
+ switch( nFmtId )
+ {
+ case SotClipboardFormatId::STRING:
+ case SotClipboardFormatId::SOLK:
+ case SotClipboardFormatId::NETSCAPE_BOOKMARK:
+ case SotClipboardFormatId::FILECONTENT:
+ case SotClipboardFormatId::FILEGRPDESCRIPTOR:
+ case SotClipboardFormatId::UNIFORMRESOURCELOCATOR:
+ if( pImpl->pBookmk )
+ bFnd = SetINetBookmark( *pImpl->pBookmk, rFlavor );
+ break;
+
+ default: break;
+ }
+
+ return bFnd;
+}
+
+
+void TransferDataContainer::CopyINetBookmark( const INetBookmark& rBkmk )
+{
+ if( !pImpl->pBookmk )
+ pImpl->pBookmk.reset( new INetBookmark( rBkmk ) );
+ else
+ *pImpl->pBookmk = rBkmk;
+
+ AddFormat( SotClipboardFormatId::STRING );
+ AddFormat( SotClipboardFormatId::SOLK );
+ AddFormat( SotClipboardFormatId::NETSCAPE_BOOKMARK );
+ AddFormat( SotClipboardFormatId::FILECONTENT );
+ AddFormat( SotClipboardFormatId::FILEGRPDESCRIPTOR );
+ AddFormat( SotClipboardFormatId::UNIFORMRESOURCELOCATOR );
+}
+
+
+void TransferDataContainer::CopyAnyData( SotClipboardFormatId nFormatId,
+ const char* pData, sal_uLong nLen )
+{
+ if( nLen )
+ {
+ TDataCntnrEntry_Impl aEntry;
+ aEntry.nId = nFormatId;
+
+ Sequence< sal_Int8 > aSeq( nLen );
+ memcpy( aSeq.getArray(), pData, nLen );
+ aEntry.aAny <<= aSeq;
+ pImpl->aFmtList.push_back( aEntry );
+ AddFormat( nFormatId );
+ }
+}
+
+
+void TransferDataContainer::CopyByteString( SotClipboardFormatId nFormatId,
+ const OString& rStr )
+{
+ CopyAnyData( nFormatId, rStr.getStr(), rStr.getLength() );
+}
+
+
+void TransferDataContainer::CopyString( SotClipboardFormatId nFmt, const OUString& rStr )
+{
+ if( !rStr.isEmpty() )
+ {
+ TDataCntnrEntry_Impl aEntry;
+ aEntry.nId = nFmt;
+ aEntry.aAny <<= rStr;
+ pImpl->aFmtList.push_back( aEntry );
+ AddFormat( aEntry.nId );
+ }
+}
+
+
+void TransferDataContainer::CopyString( const OUString& rStr )
+{
+ CopyString( SotClipboardFormatId::STRING, rStr );
+}
+
+
+bool TransferDataContainer::HasAnyData() const
+{
+ return !pImpl->aFmtList.empty() ||
+ nullptr != pImpl->pBookmk;
+}
+
+
+void TransferDataContainer::StartDrag(
+ vcl::Window* pWindow, sal_Int8 nDragSourceActions,
+ const Link<sal_Int8,void>& rLnk )
+{
+ pImpl->aFinishedLnk = rLnk;
+ TransferableHelper::StartDrag( pWindow, nDragSourceActions );
+}
+
+
+void TransferDataContainer::DragFinished( sal_Int8 nDropAction )
+{
+ pImpl->aFinishedLnk.Call( nDropAction );
+}
+
+Reference<XClipboard> GetSystemClipboard()
+{
+ // On Windows, the css.datatransfer.clipboard.SystemClipboard UNO service is implemented as a
+ // single-instance service (dtrans_CWinClipboard_get_implementation in
+ // vcl/win/dtrans/WinClipboard.cxx) that needs timely disposing to join a spawned thread
+ // (done in DeInitVCL, vcl/source/app/svmain.cxx), while on other platforms it is implemented as
+ // a multi-instance service (ClipboardFactory, vcl/source/components/dtranscomp.cxx) so we
+ // should not hold on to a single instance here:
+#if defined _WIN32
+ DBG_TESTSOLARMUTEX();
+ auto const data = ImplGetSVData();
+ if (!data->m_xSystemClipboard.is())
+ {
+ try
+ {
+ data->m_xSystemClipboard = css::datatransfer::clipboard::SystemClipboard::create(
+ comphelper::getProcessComponentContext());
+ }
+ catch (DeploymentException const &) {}
+ }
+ return data->m_xSystemClipboard;
+#else
+ Reference<XClipboard> xClipboard;
+ try
+ {
+ xClipboard = css::datatransfer::clipboard::SystemClipboard::create(
+ comphelper::getProcessComponentContext());
+ }
+ catch (DeploymentException const &) {}
+ return xClipboard;
+#endif
+}
+
+Reference<XClipboard> GetSystemPrimarySelection()
+{
+ Reference<XClipboard> xSelection;
+ try
+ {
+ Reference<XComponentContext> xContext(comphelper::getProcessComponentContext());
+#if USING_X11
+ // A hack, making the primary selection available as an instance
+ // of the SystemClipboard service on X11:
+ Sequence< Any > args{ Any(OUString("PRIMARY")) };
+ xSelection.set(xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ "com.sun.star.datatransfer.clipboard.SystemClipboard", args, xContext), UNO_QUERY_THROW);
+#else
+ static Reference< XClipboard > s_xSelection(
+ xContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.datatransfer.clipboard.GenericClipboard", xContext), UNO_QUERY);
+ xSelection = s_xSelection;
+#endif
+ }
+ catch (RuntimeException const &) {}
+ return xSelection;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/treelist.cxx b/vcl/source/treelist/treelist.cxx
new file mode 100644
index 000000000..9e057e598
--- /dev/null
+++ b/vcl/source/treelist/treelist.cxx
@@ -0,0 +1,1524 @@
+/* -*- 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 <vcl/toolkit/treelist.hxx>
+#include <vcl/toolkit/treelistentry.hxx>
+#include <vcl/toolkit/viewdataentry.hxx>
+#include <tools/debug.hxx>
+#include <osl/diagnose.h>
+
+#include <memory>
+#include <unordered_map>
+
+
+typedef std::unordered_map<SvTreeListEntry*, std::unique_ptr<SvViewDataEntry>> SvDataTable;
+
+struct SvListView::Impl
+{
+ SvListView & m_rThis;
+
+ SvDataTable m_DataTable; // Mapping SvTreeListEntry -> ViewData
+
+ sal_uInt32 m_nVisibleCount;
+ sal_uInt32 m_nSelectionCount;
+ bool m_bVisPositionsValid;
+
+ explicit Impl(SvListView & rThis)
+ : m_rThis(rThis)
+ , m_nVisibleCount(0)
+ , m_nSelectionCount(0)
+ , m_bVisPositionsValid(false)
+ {}
+
+ void InitTable();
+ void RemoveViewData( SvTreeListEntry* pParent );
+
+ void ActionMoving(SvTreeListEntry* pEntry);
+ void ActionMoved();
+ void ActionInserted(SvTreeListEntry* pEntry);
+ void ActionInsertedTree(SvTreeListEntry* pEntry);
+ void ActionRemoving(SvTreeListEntry* pEntry);
+ void ActionClear();
+};
+
+
+SvTreeList::SvTreeList(SvListView& listView) :
+ mrOwnerListView(listView),
+ mbEnableInvalidate(true)
+{
+ nEntryCount = 0;
+ bAbsPositionsValid = false;
+ pRootItem.reset(new SvTreeListEntry);
+ eSortMode = SvSortMode::None;
+}
+
+SvTreeList::~SvTreeList()
+{
+}
+
+void SvTreeList::Broadcast(
+ SvListAction nActionId,
+ SvTreeListEntry* pEntry1,
+ SvTreeListEntry* pEntry2,
+ sal_uInt32 nPos
+)
+{
+ mrOwnerListView.ModelNotification(nActionId, pEntry1, pEntry2, nPos);
+}
+
+// an entry is visible if all parents are expanded
+bool SvTreeList::IsEntryVisible( const SvListView* pView, SvTreeListEntry* pEntry ) const
+{
+ DBG_ASSERT(pView&&pEntry,"IsVisible:Invalid Params");
+ bool bRetVal = false;
+ do
+ {
+ if ( pEntry == pRootItem.get() )
+ {
+ bRetVal = true;
+ break;
+ }
+ pEntry = pEntry->pParent;
+ } while( pView->IsExpanded( pEntry ) );
+ return bRetVal;
+}
+
+sal_uInt16 SvTreeList::GetDepth( const SvTreeListEntry* pEntry ) const
+{
+ if (!pEntry)
+ return 0;
+ DBG_ASSERT(pEntry && pEntry!=pRootItem.get(),"GetDepth:Bad Entry");
+ sal_uInt16 nDepth = 0;
+ while( pEntry && pEntry->pParent != pRootItem.get() )
+ {
+ nDepth++;
+ pEntry = pEntry->pParent;
+ }
+ return nDepth;
+}
+
+bool SvTreeList::IsAtRootDepth( const SvTreeListEntry* pEntry ) const
+{
+ return pEntry->pParent == pRootItem.get();
+}
+
+void SvTreeList::Clear()
+{
+ Broadcast( SvListAction::CLEARING );
+ pRootItem->ClearChildren();
+ nEntryCount = 0;
+ Broadcast( SvListAction::CLEARED );
+}
+
+bool SvTreeList::IsChild(const SvTreeListEntry* pParent, const SvTreeListEntry* pChild) const
+{
+ if ( !pParent )
+ pParent = pRootItem.get();
+
+ if (pParent->m_Children.empty())
+ return false;
+
+ for (auto const& it : pParent->m_Children)
+ {
+ const SvTreeListEntry* pThis = it.get();
+ if (pThis == pChild)
+ return true;
+ else
+ {
+ bool bIsChild = IsChild(pThis, pChild);
+ if (bIsChild)
+ return true;
+ }
+ }
+ return false;
+}
+
+namespace {
+
+class FindByPointer
+{
+ const SvTreeListEntry* mpEntry;
+public:
+ explicit FindByPointer(const SvTreeListEntry* p) : mpEntry(p) {}
+
+ bool operator() (std::unique_ptr<SvTreeListEntry> const& rpEntry) const
+ {
+ return mpEntry == rpEntry.get();
+ }
+};
+
+sal_uInt32 findEntryPosition(const SvTreeListEntries& rDst, const SvTreeListEntry* pEntry)
+{
+ SvTreeListEntries::const_iterator itPos = std::find_if(rDst.begin(), rDst.end(), FindByPointer(pEntry));
+ if (itPos == rDst.end())
+ return static_cast<sal_uInt32>(~0);
+
+ return static_cast<sal_uInt32>(std::distance(rDst.begin(), itPos));
+}
+
+}
+
+sal_uInt32 SvTreeList::Move(SvTreeListEntry* pSrcEntry,SvTreeListEntry* pTargetParent,sal_uInt32 nListPos)
+{
+ // pDest may be 0!
+ DBG_ASSERT(pSrcEntry,"Entry?");
+ if ( !pTargetParent )
+ pTargetParent = pRootItem.get();
+ DBG_ASSERT(pSrcEntry!=pTargetParent,"Move:Source=Target");
+
+ Broadcast( SvListAction::MOVING, pSrcEntry, pTargetParent, nListPos );
+
+ if ( pSrcEntry == pTargetParent )
+ // You can't move an entry onto itself as the parent. Just return its
+ // position and bail out.
+ return pSrcEntry->GetChildListPos();
+
+ bAbsPositionsValid = false;
+
+ SvTreeListEntries& rDst = pTargetParent->m_Children;
+ SvTreeListEntries& rSrc = pSrcEntry->pParent->m_Children;
+
+ bool bSameParent = pTargetParent == pSrcEntry->pParent;
+
+ // Find the position of the entry being moved in the source container.
+ SvTreeListEntries::iterator itSrcPos = rSrc.begin(), itEnd = rSrc.end();
+ for (; itSrcPos != itEnd; ++itSrcPos)
+ {
+ const SvTreeListEntry* p = (*itSrcPos).get();
+ if (p == pSrcEntry)
+ // Found
+ break;
+ }
+
+ if (itSrcPos == itEnd)
+ {
+ OSL_FAIL("Source entry not found! This should never happen.");
+ return pSrcEntry->GetChildListPos();
+ }
+
+ if (bSameParent)
+ {
+ // Moving within the same parent.
+
+ size_t nSrcPos = std::distance(rSrc.begin(), itSrcPos);
+ if (nSrcPos == nListPos)
+ // Nothing to move here.
+ return pSrcEntry->GetChildListPos();
+
+ if (nSrcPos < nListPos)
+ // Destination position shifts left after removing the original.
+ --nListPos;
+
+ // Release the original.
+ std::unique_ptr<SvTreeListEntry> pOriginal(std::move(*itSrcPos));
+ assert(pOriginal);
+ rSrc.erase(itSrcPos);
+
+ // Determine the insertion position.
+ SvTreeListEntries::iterator itDstPos = rSrc.end();
+ if (nListPos < rSrc.size())
+ {
+ itDstPos = rSrc.begin();
+ std::advance(itDstPos, nListPos);
+ }
+ rSrc.insert(itDstPos, std::move(pOriginal));
+ }
+ else
+ {
+ // Moving from one parent to another.
+ SvTreeListEntries::iterator itDstPos = rDst.end();
+ if (nListPos < rDst.size())
+ {
+ itDstPos = rDst.begin();
+ std::advance(itDstPos, nListPos);
+ }
+ std::unique_ptr<SvTreeListEntry> pOriginal(std::move(*itSrcPos));
+ assert(pOriginal);
+ rSrc.erase(itSrcPos);
+ rDst.insert(itDstPos, std::move(pOriginal));
+ }
+
+ // move parent (do this only now, because we need the parent for
+ // deleting the old child list!)
+ pSrcEntry->pParent = pTargetParent;
+
+ // correct list position in target list
+ SetListPositions(rDst);
+ if (!bSameParent)
+ SetListPositions(rSrc);
+
+ sal_uInt32 nRetVal = findEntryPosition(rDst, pSrcEntry);
+ OSL_ENSURE(nRetVal == pSrcEntry->GetChildListPos(), "ListPos not valid");
+ Broadcast( SvListAction::MOVED,pSrcEntry,pTargetParent,nRetVal);
+ return nRetVal;
+}
+
+sal_uInt32 SvTreeList::Copy(SvTreeListEntry* pSrcEntry,SvTreeListEntry* pTargetParent,sal_uInt32 nListPos)
+{
+ // pDest may be 0!
+ DBG_ASSERT(pSrcEntry,"Entry?");
+ if ( !pTargetParent )
+ pTargetParent = pRootItem.get();
+
+ bAbsPositionsValid = false;
+
+ sal_uInt32 nCloneCount = 0;
+ SvTreeListEntry* pClonedEntry = Clone( pSrcEntry, nCloneCount );
+ nEntryCount += nCloneCount;
+
+ SvTreeListEntries& rDst = pTargetParent->m_Children;
+
+ pClonedEntry->pParent = pTargetParent; // move parent
+
+ if (nListPos < rDst.size())
+ {
+ SvTreeListEntries::iterator itPos = rDst.begin(); // insertion position.
+ std::advance(itPos, nListPos);
+ rDst.insert(itPos, std::unique_ptr<SvTreeListEntry>(pClonedEntry));
+ }
+ else
+ rDst.push_back(std::unique_ptr<SvTreeListEntry>(pClonedEntry));
+
+ SetListPositions(rDst); // correct list position in target list
+
+ Broadcast( SvListAction::INSERTED_TREE, pClonedEntry );
+ sal_uInt32 nRetVal = findEntryPosition(rDst, pClonedEntry);
+ return nRetVal;
+}
+
+void SvTreeList::Move( SvTreeListEntry* pSrcEntry, SvTreeListEntry* pDstEntry )
+{
+ SvTreeListEntry* pParent;
+ sal_uInt32 nPos;
+
+ if ( !pDstEntry )
+ {
+ pParent = pRootItem.get();
+ nPos = 0;
+ }
+ else
+ {
+ pParent = pDstEntry->pParent;
+ nPos = pDstEntry->GetChildListPos();
+ nPos++; // (On screen:) insert _below_ pDstEntry
+ }
+ Move( pSrcEntry, pParent, nPos );
+}
+
+void SvTreeList::InsertTree(SvTreeListEntry* pSrcEntry,
+ SvTreeListEntry* pTargetParent,sal_uInt32 nListPos)
+{
+ DBG_ASSERT(pSrcEntry,"InsertTree:Entry?");
+ if ( !pSrcEntry )
+ return;
+
+ if ( !pTargetParent )
+ pTargetParent = pRootItem.get();
+
+ // take sorting into account
+ GetInsertionPos( pSrcEntry, pTargetParent, nListPos );
+
+ bAbsPositionsValid = false;
+
+ pSrcEntry->pParent = pTargetParent; // move parent
+ SvTreeListEntries& rDst = pTargetParent->m_Children;
+
+ if (nListPos < rDst.size())
+ {
+ SvTreeListEntries::iterator itPos = rDst.begin();
+ std::advance(itPos, nListPos);
+ rDst.insert(itPos, std::unique_ptr<SvTreeListEntry>(pSrcEntry));
+ }
+ else
+ rDst.push_back(std::unique_ptr<SvTreeListEntry>(pSrcEntry));
+
+ SetListPositions(rDst); // correct list position in target list
+ nEntryCount += GetChildCount( pSrcEntry );
+ nEntryCount++; // the parent is new, too
+
+ Broadcast(SvListAction::INSERTED_TREE, pSrcEntry );
+}
+
+SvTreeListEntry* SvTreeList::CloneEntry( SvTreeListEntry* pSource ) const
+{
+ if( aCloneLink.IsSet() )
+ return aCloneLink.Call( pSource );
+ SvTreeListEntry* pEntry = new SvTreeListEntry;
+ pEntry->Clone(pSource);
+ return pEntry;
+}
+
+SvTreeListEntry* SvTreeList::Clone( SvTreeListEntry* pEntry, sal_uInt32& nCloneCount ) const
+{
+ SvTreeListEntry* pClonedEntry = CloneEntry( pEntry );
+ nCloneCount = 1;
+ if (!pEntry->m_Children.empty())
+ // Clone the child entries.
+ CloneChildren(pClonedEntry->m_Children, nCloneCount, pEntry->m_Children, *pClonedEntry);
+
+ return pClonedEntry;
+}
+
+void SvTreeList::CloneChildren(
+ SvTreeListEntries& rDst, sal_uInt32& rCloneCount, SvTreeListEntries& rSrc, SvTreeListEntry& rNewParent) const
+{
+ SvTreeListEntries aClone;
+ for (auto const& elem : rSrc)
+ {
+ SvTreeListEntry& rEntry = *elem;
+ std::unique_ptr<SvTreeListEntry> pNewEntry(CloneEntry(&rEntry));
+ ++rCloneCount;
+ pNewEntry->pParent = &rNewParent;
+ if (!rEntry.m_Children.empty())
+ // Clone entries recursively.
+ CloneChildren(pNewEntry->m_Children, rCloneCount, rEntry.m_Children, *pNewEntry);
+
+ aClone.push_back(std::move(pNewEntry));
+ }
+
+ rDst.swap(aClone);
+}
+
+sal_uInt32 SvTreeList::GetChildCount( const SvTreeListEntry* pParent ) const
+{
+ if ( !pParent )
+ return GetEntryCount();
+
+ if (pParent->m_Children.empty())
+ return 0;
+
+ sal_uInt32 nCount = 0;
+ sal_uInt16 nRefDepth = GetDepth( pParent );
+ sal_uInt16 nActDepth = nRefDepth;
+ do
+ {
+ pParent = Next(const_cast<SvTreeListEntry*>(pParent), &nActDepth);
+ nCount++;
+ } while( pParent && nRefDepth < nActDepth );
+ nCount--;
+ return nCount;
+}
+
+sal_uInt32 SvTreeList::GetVisibleChildCount(const SvListView* pView, SvTreeListEntry* pParent) const
+{
+ DBG_ASSERT(pView,"GetVisChildCount:No View");
+ if ( !pParent )
+ pParent = pRootItem.get();
+
+ if (!pParent || !pView->IsExpanded(pParent) || pParent->m_Children.empty())
+ return 0;
+
+ sal_uInt32 nCount = 0;
+ sal_uInt16 nRefDepth = GetDepth( pParent );
+ sal_uInt16 nActDepth = nRefDepth;
+ do
+ {
+ pParent = NextVisible( pView, pParent, &nActDepth );
+ nCount++;
+ } while( pParent && nRefDepth < nActDepth );
+ nCount--;
+ return nCount;
+}
+
+sal_uInt32 SvTreeList::GetChildSelectionCount(const SvListView* pView,SvTreeListEntry* pParent) const
+{
+ DBG_ASSERT(pView,"GetChildSelCount:No View");
+ if ( !pParent )
+ pParent = pRootItem.get();
+
+ if (!pParent || pParent->m_Children.empty())
+ return 0;
+
+ sal_uInt32 nCount = 0;
+ sal_uInt16 nRefDepth = GetDepth( pParent );
+ sal_uInt16 nActDepth = nRefDepth;
+ do
+ {
+ pParent = Next( pParent, &nActDepth );
+ if( pParent && pView->IsSelected( pParent ) && nRefDepth < nActDepth)
+ nCount++;
+ } while( pParent && nRefDepth < nActDepth );
+// nCount--;
+ return nCount;
+}
+
+SvTreeListEntry* SvTreeList::First() const
+{
+ if ( nEntryCount )
+ return pRootItem->m_Children[0].get();
+ else
+ return nullptr;
+}
+
+SvTreeListEntry* SvTreeList::Next( SvTreeListEntry* pActEntry, sal_uInt16* pDepth ) const
+{
+ DBG_ASSERT( pActEntry && pActEntry->pParent, "SvTreeList::Next: invalid entry/parent!" );
+ if ( !pActEntry || !pActEntry->pParent )
+ return nullptr;
+
+ sal_uInt16 nDepth = 0;
+ bool bWithDepth = false;
+ if ( pDepth )
+ {
+ nDepth = *pDepth;
+ bWithDepth = true;
+ }
+
+ // Get the list where the current entry belongs to (from its parent).
+ SvTreeListEntries* pActualList = &pActEntry->pParent->m_Children;
+ sal_uInt32 nActualPos = pActEntry->GetChildListPos();
+
+ if (!pActEntry->m_Children.empty())
+ {
+ // The current entry has children. Get its first child entry.
+ nDepth++;
+ pActEntry = pActEntry->m_Children[0].get();
+ if ( bWithDepth )
+ *pDepth = nDepth;
+ return pActEntry;
+ }
+
+ if (pActualList->size() > (nActualPos+1))
+ {
+ // Get the next sibling of the current entry.
+ pActEntry = (*pActualList)[nActualPos+1].get();
+ if ( bWithDepth )
+ *pDepth = nDepth;
+ return pActEntry;
+ }
+
+ // Move up level(s) until we find the level where the next sibling exists.
+ SvTreeListEntry* pParent = pActEntry->pParent;
+ nDepth--;
+ while( pParent != pRootItem.get() && pParent != nullptr )
+ {
+ DBG_ASSERT(pParent!=nullptr,"TreeData corrupt!");
+ pActualList = &pParent->pParent->m_Children;
+ nActualPos = pParent->GetChildListPos();
+ if (pActualList->size() > (nActualPos+1))
+ {
+ pActEntry = (*pActualList)[nActualPos+1].get();
+ if ( bWithDepth )
+ *pDepth = nDepth;
+ return pActEntry;
+ }
+ pParent = pParent->pParent;
+ nDepth--;
+ }
+ return nullptr;
+}
+
+SvTreeListEntry* SvTreeList::Prev( SvTreeListEntry* pActEntry ) const
+{
+ DBG_ASSERT(pActEntry!=nullptr,"Entry?");
+
+ SvTreeListEntries* pActualList = &pActEntry->pParent->m_Children;
+ sal_uInt32 nActualPos = pActEntry->GetChildListPos();
+
+ if ( nActualPos > 0 )
+ {
+ pActEntry = (*pActualList)[nActualPos-1].get();
+ while (!pActEntry->m_Children.empty())
+ {
+ pActualList = &pActEntry->m_Children;
+ pActEntry = pActualList->back().get();
+ }
+ return pActEntry;
+ }
+ if ( pActEntry->pParent == pRootItem.get() )
+ return nullptr;
+
+ pActEntry = pActEntry->pParent;
+
+ if ( pActEntry )
+ {
+ return pActEntry;
+ }
+ return nullptr;
+}
+
+SvTreeListEntry* SvTreeList::Last() const
+{
+ SvTreeListEntries* pActList = &pRootItem->m_Children;
+ SvTreeListEntry* pEntry = nullptr;
+ while (!pActList->empty())
+ {
+ pEntry = pActList->back().get();
+ pActList = &pEntry->m_Children;
+ }
+ return pEntry;
+}
+
+sal_uInt32 SvTreeList::GetVisiblePos( const SvListView* pView, SvTreeListEntry const * pEntry ) const
+{
+ DBG_ASSERT(pView&&pEntry,"View/Entry?");
+
+ if (!pView->m_pImpl->m_bVisPositionsValid)
+ {
+ // to make GetVisibleCount refresh the positions
+ const_cast<SvListView*>(pView)->m_pImpl->m_nVisibleCount = 0;
+ GetVisibleCount( const_cast<SvListView*>(pView) );
+ }
+ const SvViewDataEntry* pViewData = pView->GetViewData( pEntry );
+ return pViewData->nVisPos;
+}
+
+sal_uInt32 SvTreeList::GetVisibleCount( SvListView* pView ) const
+{
+ assert(pView && "GetVisCount:No View");
+ if( !pView->HasViewData() )
+ return 0;
+ if (pView->m_pImpl->m_nVisibleCount)
+ return pView->m_pImpl->m_nVisibleCount;
+
+ sal_uInt32 nPos = 0;
+ SvTreeListEntry* pEntry = First(); // first entry is always visible
+ while ( pEntry )
+ {
+ SvViewDataEntry* pViewData = pView->GetViewData( pEntry );
+ pViewData->nVisPos = nPos;
+ nPos++;
+ pEntry = NextVisible( pView, pEntry );
+ }
+#ifdef DBG_UTIL
+ if( nPos > 10000000 )
+ {
+ OSL_FAIL("nVisibleCount bad");
+ }
+#endif
+ pView->m_pImpl->m_nVisibleCount = nPos;
+ pView->m_pImpl->m_bVisPositionsValid = true;
+ return nPos;
+}
+
+
+// For performance reasons, this function assumes that the passed entry is
+// already visible.
+SvTreeListEntry* SvTreeList::NextVisible(const SvListView* pView,SvTreeListEntry* pActEntry,sal_uInt16* pActDepth) const
+{
+ DBG_ASSERT(pView,"NextVisible:No View");
+ if ( !pActEntry )
+ return nullptr;
+
+ sal_uInt16 nDepth = 0;
+ bool bWithDepth = false;
+ if ( pActDepth )
+ {
+ nDepth = *pActDepth;
+ bWithDepth = true;
+ }
+
+ SvTreeListEntries* pActualList = &pActEntry->pParent->m_Children;
+ sal_uInt32 nActualPos = pActEntry->GetChildListPos();
+
+ if ( pView->IsExpanded(pActEntry) )
+ {
+ OSL_ENSURE(!pActEntry->m_Children.empty(), "Pass entry is supposed to have child entries.");
+
+ nDepth++;
+ pActEntry = pActEntry->m_Children[0].get();
+ if ( bWithDepth )
+ *pActDepth = nDepth;
+ return pActEntry;
+ }
+
+ nActualPos++;
+ if ( pActualList->size() > nActualPos )
+ {
+ pActEntry = (*pActualList)[nActualPos].get();
+ if ( bWithDepth )
+ *pActDepth = nDepth;
+ return pActEntry;
+ }
+
+ SvTreeListEntry* pParent = pActEntry->pParent;
+ nDepth--;
+ while( pParent != pRootItem.get() )
+ {
+ pActualList = &pParent->pParent->m_Children;
+ nActualPos = pParent->GetChildListPos();
+ nActualPos++;
+ if ( pActualList->size() > nActualPos )
+ {
+ pActEntry = (*pActualList)[nActualPos].get();
+ if ( bWithDepth )
+ *pActDepth = nDepth;
+ return pActEntry;
+ }
+ pParent = pParent->pParent;
+ nDepth--;
+ }
+ return nullptr;
+}
+
+
+// For performance reasons, this function assumes that the passed entry is
+// already visible.
+
+SvTreeListEntry* SvTreeList::PrevVisible(const SvListView* pView, SvTreeListEntry* pActEntry) const
+{
+ DBG_ASSERT(pView&&pActEntry,"PrevVis:View/Entry?");
+
+ SvTreeListEntries* pActualList = &pActEntry->pParent->m_Children;
+ sal_uInt32 nActualPos = pActEntry->GetChildListPos();
+
+ if ( nActualPos > 0 )
+ {
+ pActEntry = (*pActualList)[nActualPos-1].get();
+ while( pView->IsExpanded(pActEntry) )
+ {
+ pActualList = &pActEntry->m_Children;
+ pActEntry = pActualList->back().get();
+ }
+ return pActEntry;
+ }
+
+ if ( pActEntry->pParent == pRootItem.get() )
+ return nullptr;
+
+ pActEntry = pActEntry->pParent;
+ if ( pActEntry )
+ {
+ return pActEntry;
+ }
+ return nullptr;
+}
+
+SvTreeListEntry* SvTreeList::LastVisible( const SvListView* pView) const
+{
+ DBG_ASSERT(pView,"LastVis:No View");
+ SvTreeListEntry* pEntry = Last();
+ while( pEntry && !IsEntryVisible( pView, pEntry ) )
+ pEntry = PrevVisible( pView, pEntry );
+ return pEntry;
+}
+
+SvTreeListEntry* SvTreeList::NextVisible(const SvListView* pView,SvTreeListEntry* pEntry,sal_uInt16& nDelta) const
+{
+ DBG_ASSERT(pView&&pEntry&&IsEntryVisible(pView,pEntry),"NextVis:Wrong Prms/!Vis");
+
+ sal_uInt32 nVisPos = GetVisiblePos( pView, pEntry );
+ // nDelta entries existent?
+ // example: 0,1,2,3,4,5,6,7,8,9 nVisPos=5 nDelta=7
+ // nNewDelta = 10-nVisPos-1 == 4
+ if (nVisPos+nDelta >= pView->m_pImpl->m_nVisibleCount)
+ {
+ nDelta = static_cast<sal_uInt16>(pView->m_pImpl->m_nVisibleCount-nVisPos);
+ nDelta--;
+ }
+ sal_uInt16 nDeltaTmp = nDelta;
+ while( nDeltaTmp )
+ {
+ pEntry = NextVisible( pView, pEntry );
+ nDeltaTmp--;
+ DBG_ASSERT(pEntry,"Entry?");
+ }
+ return pEntry;
+}
+
+SvTreeListEntry* SvTreeList::PrevVisible( const SvListView* pView, SvTreeListEntry* pEntry, sal_uInt16& nDelta ) const
+{
+ DBG_ASSERT(pView&&pEntry&&IsEntryVisible(pView,pEntry),"PrevVis:Parms/!Vis");
+
+ sal_uInt32 nVisPos = GetVisiblePos( pView, pEntry );
+ // nDelta entries existent?
+ // example: 0,1,2,3,4,5,6,7,8,9 nVisPos=8 nDelta=20
+ // nNewDelta = nNewVisPos
+ if ( nDelta > nVisPos )
+ nDelta = static_cast<sal_uInt16>(nVisPos);
+ sal_uInt16 nDeltaTmp = nDelta;
+ while( nDeltaTmp )
+ {
+ pEntry = PrevVisible( pView, pEntry );
+ nDeltaTmp--;
+ DBG_ASSERT(pEntry,"Entry?");
+ }
+ return pEntry;
+}
+
+SvTreeListEntry* SvTreeList::FirstSelected( const SvListView* pView) const
+{
+ DBG_ASSERT(pView,"FirstSel:No View");
+ if( !pView )
+ return nullptr;
+ SvTreeListEntry* pActSelEntry = First();
+ while( pActSelEntry && !pView->IsSelected(pActSelEntry) )
+ pActSelEntry = NextVisible( pView, pActSelEntry );
+ return pActSelEntry;
+}
+
+
+SvTreeListEntry* SvTreeList::FirstChild( SvTreeListEntry* pParent ) const
+{
+ if ( !pParent )
+ pParent = pRootItem.get();
+ SvTreeListEntry* pResult;
+ if (!pParent->m_Children.empty())
+ pResult = pParent->m_Children[0].get();
+ else
+ pResult = nullptr;
+ return pResult;
+}
+
+SvTreeListEntry* SvTreeList::NextSelected( const SvListView* pView, SvTreeListEntry* pEntry ) const
+{
+ DBG_ASSERT(pView&&pEntry,"NextSel:View/Entry?");
+ pEntry = Next( pEntry );
+ while( pEntry && !pView->IsSelected(pEntry) )
+ pEntry = Next( pEntry );
+ return pEntry;
+}
+
+sal_uInt32 SvTreeList::Insert( SvTreeListEntry* pEntry,SvTreeListEntry* pParent,sal_uInt32 nPos )
+{
+ DBG_ASSERT( pEntry,"Entry?");
+
+ if ( !pParent )
+ pParent = pRootItem.get();
+
+ SvTreeListEntries& rList = pParent->m_Children;
+
+ // take sorting into account
+ GetInsertionPos( pEntry, pParent, nPos );
+
+ bAbsPositionsValid = false;
+ pEntry->pParent = pParent;
+
+ if (nPos < rList.size())
+ {
+ SvTreeListEntries::iterator itPos = rList.begin();
+ std::advance(itPos, nPos);
+ rList.insert(itPos, std::unique_ptr<SvTreeListEntry>(pEntry));
+ }
+ else
+ rList.push_back(std::unique_ptr<SvTreeListEntry>(pEntry));
+
+ nEntryCount++;
+ if (nPos != TREELIST_APPEND && (nPos != (rList.size()-1)))
+ SetListPositions(rList);
+ else
+ pEntry->nListPos = rList.size()-1;
+
+ Broadcast( SvListAction::INSERTED, pEntry );
+ return nPos; // pEntry->nListPos;
+}
+
+sal_uInt32 SvTreeList::GetAbsPos( const SvTreeListEntry* pEntry) const
+{
+ if ( !bAbsPositionsValid )
+ const_cast<SvTreeList*>(this)->SetAbsolutePositions();
+ return pEntry->nAbsPos;
+}
+
+sal_uInt32 SvTreeList::GetRelPos( const SvTreeListEntry* pChild )
+{
+ return pChild->GetChildListPos();
+}
+
+void SvTreeList::SetAbsolutePositions()
+{
+ sal_uInt32 nPos = 0;
+ SvTreeListEntry* pEntry = First();
+ while ( pEntry )
+ {
+ pEntry->nAbsPos = nPos;
+ nPos++;
+ pEntry = Next( pEntry );
+ }
+ bAbsPositionsValid = true;
+}
+
+void SvListView::ExpandListEntry( SvTreeListEntry* pEntry )
+{
+ DBG_ASSERT(pEntry,"Expand:View/Entry?");
+ if ( IsExpanded(pEntry) )
+ return;
+
+ DBG_ASSERT(!pEntry->m_Children.empty(), "SvTreeList::Expand: We expected to have child entries.");
+
+ SvViewDataEntry* pViewData = GetViewData(pEntry);
+ pViewData->SetExpanded(true);
+ SvTreeListEntry* pParent = pEntry->pParent;
+ // if parent is visible, invalidate status data
+ if ( IsExpanded( pParent ) )
+ {
+ m_pImpl->m_bVisPositionsValid = false;
+ m_pImpl->m_nVisibleCount = 0;
+ }
+}
+
+void SvListView::CollapseListEntry( SvTreeListEntry* pEntry )
+{
+ DBG_ASSERT(pEntry,"Collapse:View/Entry?");
+ if ( !IsExpanded(pEntry) )
+ return;
+
+ DBG_ASSERT(!pEntry->m_Children.empty(), "SvTreeList::Collapse: We expected to have child entries.");
+
+ SvViewDataEntry* pViewData = GetViewData( pEntry );
+ pViewData->SetExpanded(false);
+
+ SvTreeListEntry* pParent = pEntry->pParent;
+ if ( IsExpanded(pParent) )
+ {
+ m_pImpl->m_nVisibleCount = 0;
+ m_pImpl->m_bVisPositionsValid = false;
+ }
+}
+
+bool SvListView::SelectListEntry( SvTreeListEntry* pEntry, bool bSelect )
+{
+ DBG_ASSERT(pEntry,"Select:View/Entry?");
+ SvViewDataEntry* pViewData = GetViewData( pEntry );
+ if ( bSelect )
+ {
+ if ( pViewData->IsSelected() || !pViewData->IsSelectable() )
+ return false;
+ else
+ {
+ pViewData->SetSelected(true);
+ m_pImpl->m_nSelectionCount++;
+ }
+ }
+ else
+ {
+ if ( !pViewData->IsSelected() )
+ return false;
+ else
+ {
+ pViewData->SetSelected(false);
+ m_pImpl->m_nSelectionCount--;
+ }
+ }
+ return true;
+}
+
+bool SvTreeList::Remove( const SvTreeListEntry* pEntry )
+{
+ DBG_ASSERT(pEntry,"Cannot remove root, use clear");
+
+ if( !pEntry->pParent )
+ {
+ OSL_FAIL("Removing entry not in model!");
+ // Under certain circumstances (which?), the explorer deletes entries
+ // from the view that it hasn't inserted into the view. We don't want
+ // to crash, so we catch this case here.
+ return false;
+ }
+
+ Broadcast(SvListAction::REMOVING, const_cast<SvTreeListEntry*>(pEntry));
+ sal_uInt32 nRemoved = 1 + GetChildCount(pEntry);
+ bAbsPositionsValid = false;
+
+ SvTreeListEntry* pParent = pEntry->pParent;
+ SvTreeListEntries& rList = pParent->m_Children;
+ bool bLastEntry = false;
+
+ // Since we need the live instance of SvTreeListEntry for broadcasting,
+ // we first need to pop it from the container, broadcast it, then delete
+ // the instance manually at the end.
+
+ std::unique_ptr<SvTreeListEntry> pEntryDeleter;
+ if ( pEntry->HasChildListPos() )
+ {
+ size_t nListPos = pEntry->GetChildListPos();
+ bLastEntry = (nListPos == (rList.size()-1));
+ SvTreeListEntries::iterator it = rList.begin();
+ std::advance(it, nListPos);
+ pEntryDeleter = std::move(*it);
+ rList.erase(it);
+ }
+ else
+ {
+ SvTreeListEntries::iterator it =
+ std::find_if(rList.begin(), rList.end(), FindByPointer(pEntry));
+ if (it != rList.end())
+ {
+ pEntryDeleter = std::move(*it);
+ rList.erase(it);
+ }
+ }
+
+ if (!rList.empty() && !bLastEntry)
+ SetListPositions(rList);
+
+ nEntryCount -= nRemoved;
+ Broadcast(SvListAction::REMOVED, const_cast<SvTreeListEntry*>(pEntry));
+
+ return true;
+}
+
+SvTreeListEntry* SvTreeList::GetEntryAtAbsPos( sal_uInt32 nAbsPos ) const
+{
+ SvTreeListEntry* pEntry = First();
+ while ( nAbsPos && pEntry )
+ {
+ pEntry = Next( pEntry );
+ nAbsPos--;
+ }
+ return pEntry;
+}
+
+SvTreeListEntry* SvTreeList::GetEntryAtVisPos( const SvListView* pView, sal_uInt32 nVisPos ) const
+{
+ DBG_ASSERT(pView,"GetEntryAtVisPos:No View");
+ SvTreeListEntry* pEntry = First();
+ while ( nVisPos && pEntry )
+ {
+ pEntry = NextVisible( pView, pEntry );
+ nVisPos--;
+ }
+ return pEntry;
+}
+
+void SvTreeList::SetListPositions( SvTreeListEntries& rEntries )
+{
+ if (rEntries.empty())
+ return;
+
+ SvTreeListEntry& rFirst = *rEntries.front();
+ if (rFirst.pParent)
+ rFirst.pParent->InvalidateChildrensListPositions();
+}
+
+void SvTreeList::EnableInvalidate( bool bEnable )
+{
+ mbEnableInvalidate = bEnable;
+}
+
+void SvTreeList::InvalidateEntry( SvTreeListEntry* pEntry )
+{
+ if (!mbEnableInvalidate)
+ return;
+
+ Broadcast( SvListAction::INVALIDATE_ENTRY, pEntry );
+}
+
+SvTreeListEntry* SvTreeList::GetRootLevelParent( SvTreeListEntry* pEntry ) const
+{
+ DBG_ASSERT(pEntry,"GetRootLevelParent:No Entry");
+ SvTreeListEntry* pCurParent = nullptr;
+ if ( pEntry )
+ {
+ pCurParent = pEntry->pParent;
+ if ( pCurParent == pRootItem.get() )
+ return pEntry; // is its own parent
+ while( pCurParent && pCurParent->pParent != pRootItem.get() )
+ pCurParent = pCurParent->pParent;
+ }
+ return pCurParent;
+}
+
+SvListView::SvListView()
+ : m_pImpl(new Impl(*this))
+{
+ pModel.reset(new SvTreeList(*this));
+ m_pImpl->InitTable();
+}
+
+void SvListView::dispose()
+{
+ pModel.reset();
+}
+
+SvListView::~SvListView()
+{
+ m_pImpl->m_DataTable.clear();
+}
+
+sal_uInt32 SvListView::GetSelectionCount() const
+{ return m_pImpl->m_nSelectionCount; }
+
+bool SvListView::HasViewData() const
+{ return m_pImpl->m_DataTable.size() > 1; } // There's always a ROOT
+
+
+void SvListView::Impl::InitTable()
+{
+ DBG_ASSERT(m_rThis.pModel,"InitTable:No Model");
+ DBG_ASSERT(!m_nSelectionCount && !m_nVisibleCount && !m_bVisPositionsValid,
+ "InitTable: Not cleared!");
+
+ if (!m_DataTable.empty())
+ {
+ DBG_ASSERT(m_DataTable.size() == 1, "InitTable: TableCount != 1");
+ // Delete the view data allocated to the Clear in the root.
+ // Attention: The model belonging to the root entry (and thus the entry
+ // itself) might already be deleted.
+ m_DataTable.clear();
+ }
+
+ SvTreeListEntry* pEntry;
+
+ // insert root entry
+ pEntry = m_rThis.pModel->pRootItem.get();
+ std::unique_ptr<SvViewDataEntry> pViewData(new SvViewDataEntry);
+ pViewData->SetExpanded(true);
+ m_DataTable.insert(std::make_pair(pEntry, std::move(pViewData)));
+ // now all the other entries
+ pEntry = m_rThis.pModel->First();
+ while( pEntry )
+ {
+ pViewData = std::make_unique<SvViewDataEntry>();
+ m_rThis.InitViewData( pViewData.get(), pEntry );
+ m_DataTable.insert(std::make_pair(pEntry, std::move(pViewData)));
+ pEntry = m_rThis.pModel->Next( pEntry );
+ }
+}
+
+void SvListView::Clear()
+{
+ m_pImpl->m_DataTable.clear();
+ m_pImpl->m_nSelectionCount = 0;
+ m_pImpl->m_nVisibleCount = 0;
+ m_pImpl->m_bVisPositionsValid = false;
+ if( pModel )
+ {
+ // insert root entry
+ SvTreeListEntry* pEntry = pModel->pRootItem.get();
+ std::unique_ptr<SvViewDataEntry> pViewData(new SvViewDataEntry);
+ pViewData->SetExpanded(true);
+ m_pImpl->m_DataTable.insert(std::make_pair(pEntry, std::move(pViewData)));
+ }
+}
+
+void SvListView::ModelHasCleared()
+{
+}
+
+void SvListView::ModelHasInserted( SvTreeListEntry* )
+{
+}
+
+void SvListView::ModelHasInsertedTree( SvTreeListEntry* )
+{
+}
+
+void SvListView::ModelIsMoving( SvTreeListEntry* /* pSource */ )
+{
+}
+
+
+void SvListView::ModelHasMoved( SvTreeListEntry* )
+{
+}
+
+void SvListView::ModelIsRemoving( SvTreeListEntry* )
+{
+}
+
+void SvListView::ModelHasRemoved( SvTreeListEntry* )
+{
+ //WARNING WARNING WARNING
+ //The supplied pointer should have been deleted
+ //before this call. Be careful not to use it!!!
+}
+
+void SvListView::ModelHasEntryInvalidated( SvTreeListEntry*)
+{
+}
+
+void SvListView::Impl::ActionMoving( SvTreeListEntry* pEntry )
+{
+ SvTreeListEntry* pParent = pEntry->pParent;
+ DBG_ASSERT(pParent,"Model not consistent");
+ if (pParent != m_rThis.pModel->pRootItem.get() && pParent->m_Children.size() == 1)
+ {
+ SvViewDataEntry* pViewData = m_DataTable.find( pParent )->second.get();
+ pViewData->SetExpanded(false);
+ }
+ // preliminary
+ m_nVisibleCount = 0;
+ m_bVisPositionsValid = false;
+}
+
+void SvListView::Impl::ActionMoved()
+{
+ m_nVisibleCount = 0;
+ m_bVisPositionsValid = false;
+}
+
+void SvListView::Impl::ActionInserted( SvTreeListEntry* pEntry )
+{
+ DBG_ASSERT(pEntry,"Insert:No Entry");
+ std::unique_ptr<SvViewDataEntry> pData(new SvViewDataEntry());
+ m_rThis.InitViewData( pData.get(), pEntry );
+ std::pair<SvDataTable::iterator, bool> aSuccess =
+ m_DataTable.insert(std::make_pair(pEntry, std::move(pData)));
+ DBG_ASSERT(aSuccess.second,"Entry already in View");
+ if (m_nVisibleCount && m_rThis.pModel->IsEntryVisible(&m_rThis, pEntry))
+ {
+ m_nVisibleCount = 0;
+ m_bVisPositionsValid = false;
+ }
+}
+
+void SvListView::Impl::ActionInsertedTree( SvTreeListEntry* pEntry )
+{
+ if (m_rThis.pModel->IsEntryVisible(&m_rThis, pEntry))
+ {
+ m_nVisibleCount = 0;
+ m_bVisPositionsValid = false;
+ }
+ // iterate over entry and its children
+ SvTreeListEntry* pCurEntry = pEntry;
+ sal_uInt16 nRefDepth = m_rThis.pModel->GetDepth( pCurEntry );
+ while( pCurEntry )
+ {
+ DBG_ASSERT(m_DataTable.find(pCurEntry) != m_DataTable.end(),"Entry already in Table");
+ std::unique_ptr<SvViewDataEntry> pViewData(new SvViewDataEntry());
+ m_rThis.InitViewData( pViewData.get(), pEntry );
+ m_DataTable.insert(std::make_pair(pCurEntry, std::move(pViewData)));
+ pCurEntry = m_rThis.pModel->Next( pCurEntry );
+ if ( pCurEntry && m_rThis.pModel->GetDepth(pCurEntry) <= nRefDepth)
+ pCurEntry = nullptr;
+ }
+}
+
+void SvListView::Impl::RemoveViewData( SvTreeListEntry* pParent )
+{
+ for (auto const& it : pParent->m_Children)
+ {
+ SvTreeListEntry& rEntry = *it;
+ m_DataTable.erase(&rEntry);
+ if (rEntry.HasChildren())
+ RemoveViewData(&rEntry);
+ }
+}
+
+
+void SvListView::Impl::ActionRemoving( SvTreeListEntry* pEntry )
+{
+ DBG_ASSERT(pEntry,"Remove:No Entry");
+
+ SvViewDataEntry* pViewData = m_DataTable.find( pEntry )->second.get();
+ sal_uInt32 nSelRemoved = 0;
+ if ( pViewData->IsSelected() )
+ nSelRemoved = 1 + m_rThis.pModel->GetChildSelectionCount(&m_rThis, pEntry);
+ m_nSelectionCount -= nSelRemoved;
+ sal_uInt32 nVisibleRemoved = 0;
+ if (m_rThis.pModel->IsEntryVisible(&m_rThis, pEntry))
+ nVisibleRemoved = 1 + m_rThis.pModel->GetVisibleChildCount(&m_rThis, pEntry);
+ if( m_nVisibleCount )
+ {
+#ifdef DBG_UTIL
+ if (m_nVisibleCount < nVisibleRemoved)
+ {
+ OSL_FAIL("nVisibleRemoved bad");
+ }
+#endif
+ m_nVisibleCount -= nVisibleRemoved;
+ }
+ m_bVisPositionsValid = false;
+
+ m_DataTable.erase(pEntry);
+ RemoveViewData( pEntry );
+
+ SvTreeListEntry* pCurEntry = pEntry->pParent;
+ if (pCurEntry && pCurEntry != m_rThis.pModel->pRootItem.get() && pCurEntry->m_Children.size() == 1)
+ {
+ pViewData = m_DataTable.find(pCurEntry)->second.get();
+ pViewData->SetExpanded(false);
+ }
+}
+
+void SvListView::Impl::ActionClear()
+{
+ m_rThis.Clear();
+}
+
+void SvListView::ModelNotification( SvListAction nActionId, SvTreeListEntry* pEntry1,
+ SvTreeListEntry* /*pEntry2*/, sal_uInt32 /*nPos*/ )
+{
+
+ switch( nActionId )
+ {
+ case SvListAction::INSERTED:
+ m_pImpl->ActionInserted( pEntry1 );
+ ModelHasInserted( pEntry1 );
+ break;
+ case SvListAction::INSERTED_TREE:
+ m_pImpl->ActionInsertedTree( pEntry1 );
+ ModelHasInsertedTree( pEntry1 );
+ break;
+ case SvListAction::REMOVING:
+ ModelIsRemoving( pEntry1 );
+ m_pImpl->ActionRemoving( pEntry1 );
+ break;
+ case SvListAction::REMOVED:
+ ModelHasRemoved( pEntry1 );
+ break;
+ case SvListAction::MOVING:
+ ModelIsMoving( pEntry1 );
+ m_pImpl->ActionMoving( pEntry1 );
+ break;
+ case SvListAction::MOVED:
+ m_pImpl->ActionMoved();
+ ModelHasMoved( pEntry1 );
+ break;
+ case SvListAction::CLEARING:
+ m_pImpl->ActionClear();
+ ModelHasCleared(); // sic! for compatibility reasons!
+ break;
+ case SvListAction::CLEARED:
+ break;
+ case SvListAction::INVALIDATE_ENTRY:
+ // no action for the base class
+ ModelHasEntryInvalidated( pEntry1 );
+ break;
+ case SvListAction::RESORTED:
+ m_pImpl->m_bVisPositionsValid = false;
+ break;
+ case SvListAction::RESORTING:
+ break;
+ default:
+ OSL_FAIL("unknown ActionId");
+ }
+}
+
+void SvListView::InitViewData( SvViewDataEntry*, SvTreeListEntry* )
+{
+}
+
+bool SvListView::IsExpanded( SvTreeListEntry* pEntry ) const
+{
+ DBG_ASSERT(pEntry,"IsExpanded:No Entry");
+ SvDataTable::const_iterator itr = m_pImpl->m_DataTable.find(pEntry);
+ DBG_ASSERT(itr != m_pImpl->m_DataTable.end(),"Entry not in Table");
+ if (itr == m_pImpl->m_DataTable.end())
+ return false;
+ return itr->second->IsExpanded();
+}
+
+bool SvListView::IsAllExpanded( SvTreeListEntry* pEntry ) const
+{
+ DBG_ASSERT(pEntry,"IsAllExpanded:No Entry");
+ if (!IsExpanded(pEntry))
+ return false;
+ const SvTreeListEntries& rChildren = pEntry->GetChildEntries();
+ for (auto& rChild : rChildren)
+ {
+ if (rChild->HasChildren() || rChild->HasChildrenOnDemand())
+ {
+ if (!IsAllExpanded(rChild.get()))
+ return false;
+ }
+ }
+ return true;
+}
+
+bool SvListView::IsSelected(const SvTreeListEntry* pEntry) const
+{
+ DBG_ASSERT(pEntry,"IsExpanded:No Entry");
+ SvDataTable::const_iterator itr = m_pImpl->m_DataTable.find(const_cast<SvTreeListEntry*>(pEntry));
+ if (itr == m_pImpl->m_DataTable.end())
+ return false;
+ return itr->second->IsSelected();
+}
+
+void SvListView::SetEntryFocus( SvTreeListEntry* pEntry, bool bFocus )
+{
+ DBG_ASSERT(pEntry,"SetEntryFocus:No Entry");
+ SvDataTable::iterator itr = m_pImpl->m_DataTable.find(pEntry);
+ DBG_ASSERT(itr != m_pImpl->m_DataTable.end(),"Entry not in Table");
+ itr->second->SetFocus(bFocus);
+}
+
+const SvViewDataEntry* SvListView::GetViewData( const SvTreeListEntry* pEntry ) const
+{
+ SvDataTable::const_iterator itr =
+ m_pImpl->m_DataTable.find(const_cast<SvTreeListEntry*>(pEntry));
+ if (itr == m_pImpl->m_DataTable.end())
+ return nullptr;
+ return itr->second.get();
+}
+
+SvViewDataEntry* SvListView::GetViewData( SvTreeListEntry* pEntry )
+{
+ SvDataTable::iterator itr = m_pImpl->m_DataTable.find( pEntry );
+ DBG_ASSERT(itr != m_pImpl->m_DataTable.end(),"Entry not in model or wrong view");
+ return itr->second.get();
+}
+
+sal_Int32 SvTreeList::Compare(const SvTreeListEntry* pLeft, const SvTreeListEntry* pRight) const
+{
+ if( aCompareLink.IsSet())
+ {
+ SvSortData aSortData;
+ aSortData.pLeft = pLeft;
+ aSortData.pRight = pRight;
+ return aCompareLink.Call( aSortData );
+ }
+ return 0;
+}
+
+void SvTreeList::Resort()
+{
+ Broadcast( SvListAction::RESORTING );
+ bAbsPositionsValid = false;
+ ResortChildren( pRootItem.get() );
+ Broadcast( SvListAction::RESORTED );
+}
+
+namespace {
+
+class SortComparator
+{
+ SvTreeList& mrList;
+public:
+
+ explicit SortComparator( SvTreeList& rList ) : mrList(rList) {}
+
+ bool operator() (std::unique_ptr<SvTreeListEntry> const& rpLeft,
+ std::unique_ptr<SvTreeListEntry> const& rpRight) const
+ {
+ int nCompare = mrList.Compare(rpLeft.get(), rpRight.get());
+ if (nCompare != 0 && mrList.GetSortMode() == SvSortMode::Descending)
+ {
+ if( nCompare < 0 )
+ nCompare = 1;
+ else
+ nCompare = -1;
+ }
+ return nCompare < 0;
+ }
+};
+
+}
+
+void SvTreeList::ResortChildren( SvTreeListEntry* pParent )
+{
+ DBG_ASSERT(pParent,"Parent not set");
+
+ if (pParent->m_Children.empty())
+ return;
+
+ SortComparator aComp(*this);
+ std::sort(pParent->m_Children.begin(), pParent->m_Children.end(), aComp);
+
+ // Recursively sort child entries.
+ for (auto const& it : pParent->m_Children)
+ {
+ SvTreeListEntry& r = *it;
+ ResortChildren(&r);
+ }
+
+ SetListPositions(pParent->m_Children); // correct list position in target list
+}
+
+void SvTreeList::GetInsertionPos( SvTreeListEntry const * pEntry, SvTreeListEntry* pParent,
+ sal_uInt32& rPos )
+{
+ DBG_ASSERT(pEntry,"No Entry");
+
+ if( eSortMode == SvSortMode::None )
+ return;
+
+ rPos = TREELIST_ENTRY_NOTFOUND;
+ const SvTreeListEntries& rChildList = GetChildList(pParent);
+
+ if (rChildList.empty())
+ return;
+
+ tools::Long i = 0;
+ tools::Long j = rChildList.size()-1;
+ tools::Long k;
+ sal_Int32 nCompare = 1;
+
+ do
+ {
+ k = (i+j)/2;
+ const SvTreeListEntry* pTempEntry = rChildList[k].get();
+ nCompare = Compare( pEntry, pTempEntry );
+ if (nCompare != 0 && eSortMode == SvSortMode::Descending)
+ {
+ if( nCompare < 0 )
+ nCompare = 1;
+ else
+ nCompare = -1;
+ }
+ if( nCompare > 0 )
+ i = k + 1;
+ else
+ j = k - 1;
+ } while( (nCompare != 0) && (i <= j) );
+
+ if( nCompare != 0 )
+ {
+ if (i > static_cast<tools::Long>(rChildList.size()-1)) // not found, end of list
+ rPos = TREELIST_ENTRY_NOTFOUND;
+ else
+ rPos = i; // not found, middle of list
+ }
+ else
+ rPos = k;
+}
+
+SvTreeListEntry* SvTreeList::GetEntry( SvTreeListEntry* pParent, sal_uInt32 nPos ) const
+{ if ( !pParent )
+ pParent = pRootItem.get();
+ SvTreeListEntry* pRet = nullptr;
+ if (nPos < pParent->m_Children.size())
+ pRet = pParent->m_Children[nPos].get();
+ return pRet;
+}
+
+SvTreeListEntry* SvTreeList::GetEntry( sal_uInt32 nRootPos ) const
+{
+ SvTreeListEntry* pRet = nullptr;
+ if (nEntryCount && nRootPos < pRootItem->m_Children.size())
+ pRet = pRootItem->m_Children[nRootPos].get();
+ return pRet;
+}
+
+const SvTreeListEntries& SvTreeList::GetChildList( SvTreeListEntry* pParent ) const
+{
+ if ( !pParent )
+ pParent = pRootItem.get();
+ return pParent->m_Children;
+}
+
+SvTreeListEntries& SvTreeList::GetChildList( SvTreeListEntry* pParent )
+{
+ if ( !pParent )
+ pParent = pRootItem.get();
+ return pParent->m_Children;
+}
+
+const SvTreeListEntry* SvTreeList::GetParent( const SvTreeListEntry* pEntry ) const
+{
+ if (!pEntry)
+ return nullptr;
+ const SvTreeListEntry* pParent = pEntry->pParent;
+ if (pParent == pRootItem.get())
+ pParent = nullptr;
+ return pParent;
+}
+
+SvTreeListEntry* SvTreeList::GetParent( SvTreeListEntry* pEntry )
+{
+ if (!pEntry)
+ return nullptr;
+ SvTreeListEntry* pParent = pEntry->pParent;
+ if (pParent == pRootItem.get())
+ pParent = nullptr;
+ return pParent;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/treelistbox.cxx b/vcl/source/treelist/treelistbox.cxx
new file mode 100644
index 000000000..7260cb69f
--- /dev/null
+++ b/vcl/source/treelist/treelistbox.cxx
@@ -0,0 +1,3612 @@
+/* -*- 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 .
+ */
+
+/*
+ TODO:
+ - delete anchor in SelectionEngine when selecting manually
+ - SelectAll( false ) => only repaint the deselected entries
+*/
+
+#include <vcl/toolkit/treelistbox.hxx>
+#include <vcl/accessiblefactory.hxx>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <vcl/svapp.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <sot/formats.hxx>
+#include <unotools/accessiblestatesethelper.hxx>
+#include <comphelper/string.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/toolkit/svlbitm.hxx>
+#include <vcl/toolkit/treelistentry.hxx>
+#include <vcl/toolkit/viewdataentry.hxx>
+#include <accel.hxx>
+#include <svimpbox.hxx>
+
+#include <set>
+#include <string.h>
+#include <vector>
+
+using namespace css::accessibility;
+
+// Drag&Drop
+static VclPtr<SvTreeListBox> g_pDDSource;
+static VclPtr<SvTreeListBox> g_pDDTarget;
+
+#define SVLBOX_ACC_RETURN 1
+#define SVLBOX_ACC_ESCAPE 2
+
+class SvInplaceEdit2
+{
+ Link<SvInplaceEdit2&,void> aCallBackHdl;
+ Accelerator aAccReturn;
+ Accelerator aAccEscape;
+ Idle aIdle { "svtools::SvInplaceEdit2 aIdle" };
+ VclPtr<Edit> pEdit;
+ bool bCanceled;
+ bool bAlreadyInCallBack;
+
+ void CallCallBackHdl_Impl();
+ DECL_LINK( Timeout_Impl, Timer *, void );
+ DECL_LINK( ReturnHdl_Impl, Accelerator&, void );
+ DECL_LINK( EscapeHdl_Impl, Accelerator&, void );
+
+public:
+ SvInplaceEdit2( vcl::Window* pParent, const Point& rPos, const Size& rSize,
+ const OUString& rData, const Link<SvInplaceEdit2&,void>& rNotifyEditEnd,
+ const Selection& );
+ ~SvInplaceEdit2();
+ bool KeyInput( const KeyEvent& rKEvt );
+ void LoseFocus();
+ bool EditingCanceled() const { return bCanceled; }
+ OUString GetText() const;
+ OUString const & GetSavedValue() const;
+ void StopEditing( bool bCancel );
+ void Hide();
+};
+
+// ***************************************************************
+
+namespace {
+
+class MyEdit_Impl : public Edit
+{
+ SvInplaceEdit2* pOwner;
+public:
+ MyEdit_Impl( vcl::Window* pParent, SvInplaceEdit2* pOwner );
+ virtual ~MyEdit_Impl() override { disposeOnce(); }
+ virtual void dispose() override { pOwner = nullptr; Edit::dispose(); }
+ virtual void KeyInput( const KeyEvent& rKEvt ) override;
+ virtual void LoseFocus() override;
+};
+
+}
+
+MyEdit_Impl::MyEdit_Impl( vcl::Window* pParent, SvInplaceEdit2* _pOwner ) :
+
+ Edit( pParent, WB_LEFT ),
+
+ pOwner( _pOwner )
+
+{
+}
+
+void MyEdit_Impl::KeyInput( const KeyEvent& rKEvt )
+{
+ if( !pOwner->KeyInput( rKEvt ))
+ Edit::KeyInput( rKEvt );
+}
+
+void MyEdit_Impl::LoseFocus()
+{
+ if (pOwner)
+ pOwner->LoseFocus();
+}
+
+SvInplaceEdit2::SvInplaceEdit2
+(
+ vcl::Window* pParent, const Point& rPos,
+ const Size& rSize,
+ const OUString& rData,
+ const Link<SvInplaceEdit2&,void>& rNotifyEditEnd,
+ const Selection& rSelection
+) :
+
+ aCallBackHdl ( rNotifyEditEnd ),
+ bCanceled ( false ),
+ bAlreadyInCallBack ( false )
+
+{
+
+ pEdit = VclPtr<MyEdit_Impl>::Create( pParent, this );
+
+ vcl::Font aFont( pParent->GetFont() );
+ aFont.SetTransparent( false );
+ Color aColor( pParent->GetBackground().GetColor() );
+ aFont.SetFillColor(aColor );
+ pEdit->SetFont( aFont );
+ pEdit->SetBackground( pParent->GetBackground() );
+ pEdit->SetPosPixel( rPos );
+ pEdit->SetSizePixel( rSize );
+ pEdit->SetText( rData );
+ pEdit->SetSelection( rSelection );
+ pEdit->SaveValue();
+
+ aAccReturn.InsertItem( SVLBOX_ACC_RETURN, vcl::KeyCode(KEY_RETURN) );
+ aAccEscape.InsertItem( SVLBOX_ACC_ESCAPE, vcl::KeyCode(KEY_ESCAPE) );
+
+ aAccReturn.SetActivateHdl( LINK( this, SvInplaceEdit2, ReturnHdl_Impl) );
+ aAccEscape.SetActivateHdl( LINK( this, SvInplaceEdit2, EscapeHdl_Impl) );
+ Application::InsertAccel( &aAccReturn );
+ Application::InsertAccel( &aAccEscape );
+
+ pEdit->Show();
+ pEdit->GrabFocus();
+}
+
+SvInplaceEdit2::~SvInplaceEdit2()
+{
+ if( !bAlreadyInCallBack )
+ {
+ Application::RemoveAccel( &aAccReturn );
+ Application::RemoveAccel( &aAccEscape );
+ }
+ pEdit.disposeAndClear();
+}
+
+OUString const & SvInplaceEdit2::GetSavedValue() const
+{
+ return pEdit->GetSavedValue();
+}
+
+void SvInplaceEdit2::Hide()
+{
+ pEdit->Hide();
+}
+
+
+IMPL_LINK_NOARG(SvInplaceEdit2, ReturnHdl_Impl, Accelerator&, void)
+{
+ bCanceled = false;
+ CallCallBackHdl_Impl();
+}
+
+IMPL_LINK_NOARG(SvInplaceEdit2, EscapeHdl_Impl, Accelerator&, void)
+{
+ bCanceled = true;
+ CallCallBackHdl_Impl();
+}
+
+bool SvInplaceEdit2::KeyInput( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aCode = rKEvt.GetKeyCode();
+ sal_uInt16 nCode = aCode.GetCode();
+
+ switch ( nCode )
+ {
+ case KEY_ESCAPE:
+ bCanceled = true;
+ CallCallBackHdl_Impl();
+ return true;
+
+ case KEY_RETURN:
+ bCanceled = false;
+ CallCallBackHdl_Impl();
+ return true;
+ }
+ return false;
+}
+
+void SvInplaceEdit2::StopEditing( bool bCancel )
+{
+ if ( !bAlreadyInCallBack )
+ {
+ bCanceled = bCancel;
+ CallCallBackHdl_Impl();
+ }
+}
+
+void SvInplaceEdit2::LoseFocus()
+{
+ if ( !bAlreadyInCallBack
+ && ((!Application::GetFocusWindow()) || !pEdit->IsChild( Application::GetFocusWindow()) )
+ )
+ {
+ bCanceled = false;
+ aIdle.SetPriority(TaskPriority::REPAINT);
+ aIdle.SetInvokeHandler(LINK(this,SvInplaceEdit2,Timeout_Impl));
+ aIdle.Start();
+ }
+}
+
+IMPL_LINK_NOARG(SvInplaceEdit2, Timeout_Impl, Timer *, void)
+{
+ CallCallBackHdl_Impl();
+}
+
+void SvInplaceEdit2::CallCallBackHdl_Impl()
+{
+ aIdle.Stop();
+ if ( !bAlreadyInCallBack )
+ {
+ bAlreadyInCallBack = true;
+ Application::RemoveAccel( &aAccReturn );
+ Application::RemoveAccel( &aAccEscape );
+ pEdit->Hide();
+ aCallBackHdl.Call( *this );
+ }
+}
+
+OUString SvInplaceEdit2::GetText() const
+{
+ return pEdit->GetText();
+}
+
+// ***************************************************************
+// class SvLBoxTab
+// ***************************************************************
+
+
+SvLBoxTab::SvLBoxTab()
+{
+ nPos = 0;
+ nFlags = SvLBoxTabFlags::NONE;
+}
+
+SvLBoxTab::SvLBoxTab( tools::Long nPosition, SvLBoxTabFlags nTabFlags )
+{
+ nPos = nPosition;
+ nFlags = nTabFlags;
+}
+
+SvLBoxTab::SvLBoxTab( const SvLBoxTab& rTab )
+{
+ nPos = rTab.nPos;
+ nFlags = rTab.nFlags;
+}
+
+tools::Long SvLBoxTab::CalcOffset( tools::Long nItemWidth, tools::Long nTabWidth )
+{
+ tools::Long nOffset = 0;
+ if ( nFlags & SvLBoxTabFlags::ADJUST_RIGHT )
+ {
+ nOffset = nTabWidth - nItemWidth;
+ if( nOffset < 0 )
+ nOffset = 0;
+ }
+ else if ( nFlags & SvLBoxTabFlags::ADJUST_CENTER )
+ {
+ if( nFlags & SvLBoxTabFlags::FORCE )
+ {
+ // correct implementation of centering
+ nOffset = ( nTabWidth - nItemWidth ) / 2;
+ if( nOffset < 0 )
+ nOffset = 0;
+ }
+ else
+ {
+ // historically grown, wrong calculation of tabs which is needed by
+ // Abo-Tabbox, Tools/Options/Customize etc.
+ nItemWidth++;
+ nOffset = -( nItemWidth / 2 );
+ }
+ }
+ return nOffset;
+}
+
+// ***************************************************************
+// class SvLBoxItem
+// ***************************************************************
+
+
+SvLBoxItem::SvLBoxItem()
+ : mbDisabled(false)
+{
+}
+
+SvLBoxItem::~SvLBoxItem()
+{
+}
+
+int SvLBoxItem::GetWidth(const SvTreeListBox* pView, const SvTreeListEntry* pEntry) const
+{
+ const SvViewDataItem* pViewData = pView->GetViewDataItem( pEntry, this );
+ int nWidth = pViewData->mnWidth;
+ if (nWidth == -1)
+ {
+ nWidth = CalcWidth(pView);
+ const_cast<SvViewDataItem*>(pViewData)->mnWidth = nWidth;
+ }
+ return nWidth;
+}
+
+int SvLBoxItem::GetHeight(const SvTreeListBox* pView, const SvTreeListEntry* pEntry) const
+{
+ const SvViewDataItem* pViewData = pView->GetViewDataItem( pEntry, this );
+ return pViewData->mnHeight;
+}
+
+int SvLBoxItem::GetWidth(const SvTreeListBox* pView, const SvViewDataEntry* pData, sal_uInt16 nItemPos) const
+{
+ const SvViewDataItem& rIData = pData->GetItem(nItemPos);
+ int nWidth = rIData.mnWidth;
+ if (nWidth == -1)
+ {
+ nWidth = CalcWidth(pView);
+ const_cast<SvViewDataItem&>(rIData).mnWidth = nWidth;
+ }
+ return nWidth;
+}
+
+int SvLBoxItem::GetHeight(const SvViewDataEntry* pData, sal_uInt16 nItemPos)
+{
+ const SvViewDataItem& rIData = pData->GetItem(nItemPos);
+ return rIData.mnHeight;
+}
+
+int SvLBoxItem::CalcWidth(const SvTreeListBox* /*pView*/) const
+{
+ return 0;
+}
+
+struct SvTreeListBoxImpl
+{
+ bool m_bDoingQuickSelection:1;
+
+ vcl::QuickSelectionEngine m_aQuickSelectionEngine;
+
+ explicit SvTreeListBoxImpl(SvTreeListBox& _rBox) :
+ m_bDoingQuickSelection(false),
+ m_aQuickSelectionEngine(_rBox) {}
+};
+
+SvTreeListBox::SvTreeListBox(vcl::Window* pParent, WinBits nWinStyle) :
+ Control(pParent, nWinStyle | WB_CLIPCHILDREN),
+ DropTargetHelper(this),
+ DragSourceHelper(this),
+ mpImpl(new SvTreeListBoxImpl(*this)),
+ mbContextBmpExpanded(false),
+ mbAlternatingRowColors(false),
+ mbUpdateAlternatingRows(false),
+ mbQuickSearch(false),
+ mbActivateOnSingleClick(false),
+ mbHoverSelection(false),
+ mbSelectingByHover(false),
+ mnClicksToToggle(0), //at default clicking on a row won't toggle its default checkbox
+ eSelMode(SelectionMode::NONE),
+ nMinWidthInChars(0),
+ mnDragAction(DND_ACTION_COPYMOVE | DND_ACTION_LINK),
+ mbCenterAndClipText(false)
+{
+ nImpFlags = SvTreeListBoxFlags::NONE;
+ pTargetEntry = nullptr;
+ nDragDropMode = DragDropMode::NONE;
+ pModel->SetCloneLink( LINK(this, SvTreeListBox, CloneHdl_Impl ));
+ pHdlEntry = nullptr;
+ eSelMode = SelectionMode::Single;
+ nDragDropMode = DragDropMode::NONE;
+ SetType(WindowType::TREELISTBOX);
+
+ InitTreeView();
+ pImpl->SetModel( pModel.get() );
+
+ SetSublistOpenWithLeftRight();
+}
+
+void SvTreeListBox::Clear()
+{
+ if (pModel)
+ pModel->Clear(); // Model calls SvTreeListBox::ModelHasCleared()
+}
+
+IMPL_LINK( SvTreeListBox, CloneHdl_Impl, SvTreeListEntry*, pEntry, SvTreeListEntry* )
+{
+ return CloneEntry(pEntry);
+}
+
+sal_uInt32 SvTreeListBox::Insert( SvTreeListEntry* pEntry, SvTreeListEntry* pParent, sal_uInt32 nPos )
+{
+ sal_uInt32 nInsPos = pModel->Insert( pEntry, pParent, nPos );
+ pEntry->SetBackColor( GetBackground().GetColor() );
+ SetAlternatingRowColors( mbAlternatingRowColors );
+ return nInsPos;
+}
+
+sal_uInt32 SvTreeListBox::Insert( SvTreeListEntry* pEntry,sal_uInt32 nRootPos )
+{
+ sal_uInt32 nInsPos = pModel->Insert( pEntry, nRootPos );
+ pEntry->SetBackColor( GetBackground().GetColor() );
+ SetAlternatingRowColors( mbAlternatingRowColors );
+ return nInsPos;
+}
+
+bool SvTreeListBox::ExpandingHdl()
+{
+ return !aExpandingHdl.IsSet() || aExpandingHdl.Call( this );
+}
+
+void SvTreeListBox::ExpandedHdl()
+{
+ aExpandedHdl.Call( this );
+}
+
+void SvTreeListBox::SelectHdl()
+{
+ aSelectHdl.Call( this );
+}
+
+void SvTreeListBox::DeselectHdl()
+{
+ aDeselectHdl.Call( this );
+}
+
+bool SvTreeListBox::DoubleClickHdl()
+{
+ return !aDoubleClickHdl.IsSet() || aDoubleClickHdl.Call(this);
+}
+
+bool SvTreeListBox::CheckDragAndDropMode( SvTreeListBox const * pSource, sal_Int8 nAction )
+{
+ if ( pSource != this )
+ return false; // no drop
+
+ if ( !(nDragDropMode & DragDropMode::CTRL_MOVE) )
+ return false; // D&D locked within list
+
+ if( DND_ACTION_MOVE == nAction )
+ {
+ if ( !(nDragDropMode & DragDropMode::CTRL_MOVE) )
+ return false; // no local move
+ }
+ else
+ return false; // no local copy
+
+ return true;
+}
+
+
+/*
+ NotifyMoving/Copying
+ ====================
+
+ default behavior:
+
+ 1. target doesn't have children
+ - entry becomes sibling of target. entry comes after target
+ (->Window: below the target)
+ 2. target is an expanded parent
+ - entry inserted at the beginning of the target childlist
+ 3. target is a collapsed parent
+ - entry is inserted at the end of the target childlist
+*/
+TriState SvTreeListBox::NotifyMoving(
+ SvTreeListEntry* pTarget, // D&D dropping position in GetModel()
+ const SvTreeListEntry* pEntry, // entry that we want to move, from
+ // GetSourceListBox()->GetModel()
+ SvTreeListEntry*& rpNewParent, // new target parent
+ sal_uInt32& rNewChildPos) // position in childlist of target parent
+{
+ DBG_ASSERT(pEntry,"NotifyMoving:SourceEntry?");
+ if( !pTarget )
+ {
+ rpNewParent = nullptr;
+ rNewChildPos = 0;
+ return TRISTATE_TRUE;
+ }
+ if ( !pTarget->HasChildren() && !pTarget->HasChildrenOnDemand() )
+ {
+ // case 1
+ rpNewParent = GetParent( pTarget );
+ rNewChildPos = SvTreeList::GetRelPos( pTarget ) + 1;
+ rNewChildPos += nCurEntrySelPos;
+ nCurEntrySelPos++;
+ }
+ else
+ {
+ // cases 2 & 3
+ rpNewParent = pTarget;
+ if( IsExpanded(pTarget))
+ rNewChildPos = 0;
+ else
+ rNewChildPos = TREELIST_APPEND;
+ }
+ return TRISTATE_TRUE;
+}
+
+TriState SvTreeListBox::NotifyCopying(
+ SvTreeListEntry* pTarget, // D&D dropping position in GetModel()
+ const SvTreeListEntry* pEntry, // entry that we want to move, from
+ // GetSourceListBox()->GetModel()
+ SvTreeListEntry*& rpNewParent, // new target parent
+ sal_uInt32& rNewChildPos) // position in childlist of target parent
+{
+ return NotifyMoving(pTarget,pEntry,rpNewParent,rNewChildPos);
+}
+
+SvTreeListEntry* SvTreeListBox::FirstChild( SvTreeListEntry* pParent ) const
+{
+ return pModel->FirstChild(pParent);
+}
+
+// return: all entries copied
+bool SvTreeListBox::CopySelection( SvTreeListBox* pSource, SvTreeListEntry* pTarget )
+{
+ nCurEntrySelPos = 0; // selection counter for NotifyMoving/Copying
+ bool bSuccess = true;
+ std::vector<SvTreeListEntry*> aList;
+ bool bClone = ( pSource->GetModel() != GetModel() );
+ Link<SvTreeListEntry*,SvTreeListEntry*> aCloneLink( pModel->GetCloneLink() );
+ pModel->SetCloneLink( LINK(this, SvTreeListBox, CloneHdl_Impl ));
+
+ // cache selection to simplify iterating over the selection when doing a D&D
+ // exchange within the same listbox
+ SvTreeListEntry* pSourceEntry = pSource->FirstSelected();
+ while ( pSourceEntry )
+ {
+ // children are copied automatically
+ pSource->SelectChildren( pSourceEntry, false );
+ aList.push_back( pSourceEntry );
+ pSourceEntry = pSource->NextSelected( pSourceEntry );
+ }
+
+ for (auto const& elem : aList)
+ {
+ pSourceEntry = elem;
+ SvTreeListEntry* pNewParent = nullptr;
+ sal_uInt32 nInsertionPos = TREELIST_APPEND;
+ TriState nOk = NotifyCopying(pTarget,pSourceEntry,pNewParent,nInsertionPos);
+ if ( nOk )
+ {
+ if ( bClone )
+ {
+ sal_uInt32 nCloneCount = 0;
+ pSourceEntry = pModel->Clone(pSourceEntry, nCloneCount);
+ pModel->InsertTree(pSourceEntry, pNewParent, nInsertionPos);
+ }
+ else
+ {
+ sal_uInt32 nListPos = pModel->Copy(pSourceEntry, pNewParent, nInsertionPos);
+ pSourceEntry = GetEntry( pNewParent, nListPos );
+ }
+ }
+ else
+ bSuccess = false;
+
+ if (nOk == TRISTATE_INDET) // HACK: make visible moved entry
+ MakeVisible( pSourceEntry );
+ }
+ pModel->SetCloneLink( aCloneLink );
+ return bSuccess;
+}
+
+// return: all entries were moved
+bool SvTreeListBox::MoveSelectionCopyFallbackPossible( SvTreeListBox* pSource, SvTreeListEntry* pTarget, bool bAllowCopyFallback )
+{
+ nCurEntrySelPos = 0; // selection counter for NotifyMoving/Copying
+ bool bSuccess = true;
+ std::vector<SvTreeListEntry*> aList;
+ bool bClone = ( pSource->GetModel() != GetModel() );
+ Link<SvTreeListEntry*,SvTreeListEntry*> aCloneLink( pModel->GetCloneLink() );
+ if ( bClone )
+ pModel->SetCloneLink( LINK(this, SvTreeListBox, CloneHdl_Impl ));
+
+ SvTreeListEntry* pSourceEntry = pSource->FirstSelected();
+ while ( pSourceEntry )
+ {
+ // children are automatically moved
+ pSource->SelectChildren( pSourceEntry, false );
+ aList.push_back( pSourceEntry );
+ pSourceEntry = pSource->NextSelected( pSourceEntry );
+ }
+
+ for (auto const& elem : aList)
+ {
+ pSourceEntry = elem;
+ SvTreeListEntry* pNewParent = nullptr;
+ sal_uInt32 nInsertionPos = TREELIST_APPEND;
+ TriState nOk = NotifyMoving(pTarget,pSourceEntry,pNewParent,nInsertionPos);
+ TriState nCopyOk = nOk;
+ if ( !nOk && bAllowCopyFallback )
+ {
+ nInsertionPos = TREELIST_APPEND;
+ nCopyOk = NotifyCopying(pTarget,pSourceEntry,pNewParent,nInsertionPos);
+ }
+
+ if ( nOk || nCopyOk )
+ {
+ if ( bClone )
+ {
+ sal_uInt32 nCloneCount = 0;
+ pSourceEntry = pModel->Clone(pSourceEntry, nCloneCount);
+ pModel->InsertTree(pSourceEntry, pNewParent, nInsertionPos);
+ }
+ else
+ {
+ if ( nOk )
+ pModel->Move(pSourceEntry, pNewParent, nInsertionPos);
+ else
+ pModel->Copy(pSourceEntry, pNewParent, nInsertionPos);
+ }
+ }
+ else
+ bSuccess = false;
+
+ if (nOk == TRISTATE_INDET) // HACK: make moved entry visible
+ MakeVisible( pSourceEntry );
+ }
+ pModel->SetCloneLink( aCloneLink );
+ return bSuccess;
+}
+
+void SvTreeListBox::RemoveSelection()
+{
+ std::vector<const SvTreeListEntry*> aList;
+ // cache selection, as the implementation deselects everything on the first
+ // remove
+ SvTreeListEntry* pEntry = FirstSelected();
+ while ( pEntry )
+ {
+ aList.push_back( pEntry );
+ if ( pEntry->HasChildren() )
+ // remove deletes all children automatically
+ SelectChildren(pEntry, false);
+ pEntry = NextSelected( pEntry );
+ }
+
+ for (auto const& elem : aList)
+ pModel->Remove(elem);
+}
+
+void SvTreeListBox::RemoveEntry(SvTreeListEntry const * pEntry)
+{
+ pModel->Remove(pEntry);
+}
+
+void SvTreeListBox::RecalcViewData()
+{
+ SvTreeListEntry* pEntry = First();
+ while( pEntry )
+ {
+ sal_uInt16 nCount = pEntry->ItemCount();
+ sal_uInt16 nCurPos = 0;
+ while ( nCurPos < nCount )
+ {
+ SvLBoxItem& rItem = pEntry->GetItem( nCurPos );
+ rItem.InitViewData( this, pEntry );
+ nCurPos++;
+ }
+ pEntry = Next( pEntry );
+ }
+}
+
+void SvTreeListBox::ImplShowTargetEmphasis( SvTreeListEntry* pEntry, bool bShow)
+{
+ if ( bShow && (nImpFlags & SvTreeListBoxFlags::TARGEMPH_VIS) )
+ return;
+ if ( !bShow && !(nImpFlags & SvTreeListBoxFlags::TARGEMPH_VIS) )
+ return;
+ pImpl->PaintDDCursor( pEntry, bShow);
+ if( bShow )
+ nImpFlags |= SvTreeListBoxFlags::TARGEMPH_VIS;
+ else
+ nImpFlags &= ~SvTreeListBoxFlags::TARGEMPH_VIS;
+}
+
+void SvTreeListBox::OnCurrentEntryChanged()
+{
+ if ( !mpImpl->m_bDoingQuickSelection )
+ mpImpl->m_aQuickSelectionEngine.Reset();
+}
+
+SvTreeListEntry* SvTreeListBox::GetEntry( SvTreeListEntry* pParent, sal_uInt32 nPos ) const
+{
+ return pModel->GetEntry(pParent, nPos);
+}
+
+SvTreeListEntry* SvTreeListBox::GetEntry( sal_uInt32 nRootPos ) const
+{
+ return pModel->GetEntry(nRootPos);
+}
+
+SvTreeListEntry* SvTreeListBox::GetEntryFromPath( const ::std::deque< sal_Int32 >& _rPath ) const
+{
+
+ SvTreeListEntry* pEntry = nullptr;
+ SvTreeListEntry* pParent = nullptr;
+ for (auto const& elem : _rPath)
+ {
+ pEntry = GetEntry( pParent, elem );
+ if ( !pEntry )
+ break;
+ pParent = pEntry;
+ }
+
+ return pEntry;
+}
+
+void SvTreeListBox::FillEntryPath( SvTreeListEntry* pEntry, ::std::deque< sal_Int32 >& _rPath ) const
+{
+
+ if ( !pEntry )
+ return;
+
+ SvTreeListEntry* pParentEntry = GetParent( pEntry );
+ while ( true )
+ {
+ sal_uInt32 i, nCount = GetLevelChildCount( pParentEntry );
+ for ( i = 0; i < nCount; ++i )
+ {
+ SvTreeListEntry* pTemp = GetEntry( pParentEntry, i );
+ DBG_ASSERT( pEntry, "invalid entry" );
+ if ( pEntry == pTemp )
+ {
+ _rPath.push_front( static_cast<sal_Int32>(i) );
+ break;
+ }
+ }
+
+ if ( pParentEntry )
+ {
+ pEntry = pParentEntry;
+ pParentEntry = GetParent( pParentEntry );
+ }
+ else
+ break;
+ }
+}
+
+SvTreeListEntry* SvTreeListBox::GetParent( SvTreeListEntry* pEntry ) const
+{
+ return pModel->GetParent(pEntry);
+}
+
+sal_uInt32 SvTreeListBox::GetChildCount( SvTreeListEntry const * pParent ) const
+{
+ return pModel->GetChildCount(pParent);
+}
+
+sal_uInt32 SvTreeListBox::GetLevelChildCount( SvTreeListEntry* _pParent ) const
+{
+
+ //if _pParent is 0, then pEntry is the first child of the root.
+ SvTreeListEntry* pEntry = FirstChild( _pParent );
+
+ if( !pEntry )//there is only root, root don't have children
+ return 0;
+
+ if( !_pParent )//root and children of root
+ return pEntry->pParent->m_Children.size();
+
+ return _pParent->m_Children.size();
+}
+
+SvViewDataEntry* SvTreeListBox::GetViewDataEntry( SvTreeListEntry const * pEntry ) const
+{
+ return const_cast<SvViewDataEntry*>(SvListView::GetViewData(pEntry));
+}
+
+SvViewDataItem* SvTreeListBox::GetViewDataItem(SvTreeListEntry const * pEntry, SvLBoxItem const * pItem)
+{
+ return const_cast<SvViewDataItem*>(static_cast<const SvTreeListBox*>(this)->GetViewDataItem(pEntry, pItem));
+}
+
+const SvViewDataItem* SvTreeListBox::GetViewDataItem(const SvTreeListEntry* pEntry, const SvLBoxItem* pItem) const
+{
+ const SvViewDataEntry* pEntryData = SvListView::GetViewData(pEntry);
+ assert(pEntryData && "Entry not in View");
+ sal_uInt16 nItemPos = pEntry->GetPos(pItem);
+ return &pEntryData->GetItem(nItemPos);
+}
+
+void SvTreeListBox::InitViewData( SvViewDataEntry* pData, SvTreeListEntry* pEntry )
+{
+ SvTreeListEntry* pInhEntry = pEntry;
+ SvViewDataEntry* pEntryData = pData;
+
+ pEntryData->Init(pInhEntry->ItemCount());
+ sal_uInt16 nCount = pInhEntry->ItemCount();
+ sal_uInt16 nCurPos = 0;
+ while( nCurPos < nCount )
+ {
+ SvLBoxItem& rItem = pInhEntry->GetItem( nCurPos );
+ SvViewDataItem& rItemData = pEntryData->GetItem(nCurPos);
+ rItem.InitViewData( this, pInhEntry, &rItemData );
+ nCurPos++;
+ }
+}
+
+void SvTreeListBox::EnableSelectionAsDropTarget( bool bEnable )
+{
+ sal_uInt16 nRefDepth;
+ SvTreeListEntry* pTemp;
+
+ SvTreeListEntry* pSelEntry = FirstSelected();
+ while( pSelEntry )
+ {
+ if ( !bEnable )
+ {
+ pSelEntry->nEntryFlags |= SvTLEntryFlags::DISABLE_DROP;
+ nRefDepth = pModel->GetDepth( pSelEntry );
+ pTemp = Next( pSelEntry );
+ while( pTemp && pModel->GetDepth( pTemp ) > nRefDepth )
+ {
+ pTemp->nEntryFlags |= SvTLEntryFlags::DISABLE_DROP;
+ pTemp = Next( pTemp );
+ }
+ }
+ else
+ {
+ pSelEntry->nEntryFlags &= ~SvTLEntryFlags::DISABLE_DROP;
+ nRefDepth = pModel->GetDepth( pSelEntry );
+ pTemp = Next( pSelEntry );
+ while( pTemp && pModel->GetDepth( pTemp ) > nRefDepth )
+ {
+ pTemp->nEntryFlags &= ~SvTLEntryFlags::DISABLE_DROP;
+ pTemp = Next( pTemp );
+ }
+ }
+ pSelEntry = NextSelected( pSelEntry );
+ }
+}
+
+// ******************************************************************
+// InplaceEditing
+// ******************************************************************
+
+void SvTreeListBox::EditText( const OUString& rStr, const tools::Rectangle& rRect,
+ const Selection& rSel )
+{
+ pEdCtrl.reset();
+ nImpFlags |= SvTreeListBoxFlags::IN_EDT;
+ nImpFlags &= ~SvTreeListBoxFlags::EDTEND_CALLED;
+ HideFocus();
+ pEdCtrl.reset( new SvInplaceEdit2(
+ this, rRect.TopLeft(), rRect.GetSize(), rStr,
+ LINK( this, SvTreeListBox, TextEditEndedHdl_Impl ),
+ rSel ) );
+}
+
+IMPL_LINK_NOARG(SvTreeListBox, TextEditEndedHdl_Impl, SvInplaceEdit2&, void)
+{
+ if ( nImpFlags & SvTreeListBoxFlags::EDTEND_CALLED ) // avoid nesting
+ return;
+ nImpFlags |= SvTreeListBoxFlags::EDTEND_CALLED;
+ OUString aStr;
+ if ( !pEdCtrl->EditingCanceled() )
+ aStr = pEdCtrl->GetText();
+ else
+ aStr = pEdCtrl->GetSavedValue();
+ EditedText( aStr );
+ // Hide may only be called after the new text was put into the entry, so
+ // that we don't call the selection handler in the GetFocus of the listbox
+ // with the old entry text.
+ pEdCtrl->Hide();
+ nImpFlags &= ~SvTreeListBoxFlags::IN_EDT;
+ GrabFocus();
+}
+
+void SvTreeListBox::CancelTextEditing()
+{
+ if ( pEdCtrl )
+ pEdCtrl->StopEditing( true );
+ nImpFlags &= ~SvTreeListBoxFlags::IN_EDT;
+}
+
+void SvTreeListBox::EndEditing( bool bCancel )
+{
+ if( pEdCtrl )
+ pEdCtrl->StopEditing( bCancel );
+ nImpFlags &= ~SvTreeListBoxFlags::IN_EDT;
+}
+
+vcl::StringEntryIdentifier SvTreeListBox::CurrentEntry( OUString& _out_entryText ) const
+{
+ // always accept the current entry if there is one
+ SvTreeListEntry* pEntry( GetCurEntry() );
+ if (pEntry)
+ {
+ _out_entryText = GetEntryText(pEntry);
+ return pEntry;
+ }
+
+ pEntry = FirstSelected();
+ if ( !pEntry )
+ pEntry = First();
+
+ if ( pEntry )
+ _out_entryText = GetEntryText( pEntry );
+
+ return pEntry;
+}
+
+vcl::StringEntryIdentifier SvTreeListBox::NextEntry(vcl::StringEntryIdentifier _pCurrentSearchEntry, OUString& _out_entryText) const
+{
+ SvTreeListEntry* pEntry = const_cast< SvTreeListEntry* >( static_cast< const SvTreeListEntry* >( _pCurrentSearchEntry ) );
+
+ if ( ( ( GetChildCount( pEntry ) > 0 )
+ || ( pEntry->HasChildrenOnDemand() )
+ )
+ && !IsExpanded( pEntry )
+ )
+ {
+ SvTreeListEntry* pNextSiblingEntry = pEntry->NextSibling();
+ if ( !pNextSiblingEntry )
+ pEntry = Next( pEntry );
+ else
+ pEntry = pNextSiblingEntry;
+ }
+ else
+ {
+ pEntry = Next( pEntry );
+ }
+
+ if ( !pEntry )
+ pEntry = First();
+
+ if ( pEntry )
+ _out_entryText = GetEntryText( pEntry );
+
+ return pEntry;
+}
+
+void SvTreeListBox::SelectEntry(vcl::StringEntryIdentifier _pEntry)
+{
+ SvTreeListEntry* pEntry = const_cast< SvTreeListEntry* >( static_cast< const SvTreeListEntry* >( _pEntry ) );
+ DBG_ASSERT( pEntry, "SvTreeListBox::SelectSearchEntry: invalid entry!" );
+ if ( !pEntry )
+ return;
+
+ SelectAll( false );
+ SetCurEntry( pEntry );
+ Select( pEntry );
+}
+
+bool SvTreeListBox::HandleKeyInput( const KeyEvent& _rKEvt )
+{
+ if ( _rKEvt.GetKeyCode().IsMod1() )
+ return false;
+
+ if (mbQuickSearch)
+ {
+ mpImpl->m_bDoingQuickSelection = true;
+ const bool bHandled = mpImpl->m_aQuickSelectionEngine.HandleKeyEvent( _rKEvt );
+ mpImpl->m_bDoingQuickSelection = false;
+ if ( bHandled )
+ return true;
+ }
+
+ return false;
+}
+
+
+//JP 28.3.2001: new Drag & Drop API
+sal_Int8 SvTreeListBox::AcceptDrop( const AcceptDropEvent& rEvt )
+{
+ sal_Int8 nRet = DND_ACTION_NONE;
+
+ if (rEvt.mbLeaving || !CheckDragAndDropMode(g_pDDSource, rEvt.mnAction))
+ {
+ ImplShowTargetEmphasis( pTargetEntry, false );
+ }
+ else if( nDragDropMode == DragDropMode::NONE )
+ {
+ SAL_WARN( "svtools.contnr", "SvTreeListBox::QueryDrop(): no target" );
+ }
+ else
+ {
+ SvTreeListEntry* pEntry = GetDropTarget( rEvt.maPosPixel );
+ if( !IsDropFormatSupported( SotClipboardFormatId::TREELISTBOX ) )
+ {
+ SAL_WARN( "svtools.contnr", "SvTreeListBox::QueryDrop(): no format" );
+ }
+ else
+ {
+ DBG_ASSERT(g_pDDSource, "SvTreeListBox::QueryDrop(): SourceBox == 0");
+ if (!( pEntry && g_pDDSource->GetModel() == GetModel()
+ && DND_ACTION_MOVE == rEvt.mnAction
+ && (pEntry->nEntryFlags & SvTLEntryFlags::DISABLE_DROP)))
+ {
+ nRet = rEvt.mnAction;
+ }
+ }
+
+ // **** draw emphasis ****
+ if( DND_ACTION_NONE == nRet )
+ ImplShowTargetEmphasis( pTargetEntry, false );
+ else if( pEntry != pTargetEntry || !(nImpFlags & SvTreeListBoxFlags::TARGEMPH_VIS) )
+ {
+ ImplShowTargetEmphasis( pTargetEntry, false );
+ pTargetEntry = pEntry;
+ ImplShowTargetEmphasis( pTargetEntry, true );
+ }
+ }
+ return nRet;
+}
+
+sal_Int8 SvTreeListBox::ExecuteDrop( const ExecuteDropEvent& rEvt, SvTreeListBox* pSourceView )
+{
+ assert(pSourceView);
+ pSourceView->EnableSelectionAsDropTarget();
+
+ ImplShowTargetEmphasis( pTargetEntry, false );
+ g_pDDTarget = this;
+
+ TransferableDataHelper aData( rEvt.maDropEvent.Transferable );
+
+ sal_Int8 nRet;
+ if( aData.HasFormat( SotClipboardFormatId::TREELISTBOX ))
+ nRet = rEvt.mnAction;
+ else
+ nRet = DND_ACTION_NONE;
+
+ if( DND_ACTION_NONE != nRet )
+ {
+ nRet = DND_ACTION_NONE;
+
+ SvTreeListEntry* pTarget = pTargetEntry; // may be 0!
+
+ if( DND_ACTION_COPY == rEvt.mnAction )
+ {
+ if (CopySelection(g_pDDSource, pTarget))
+ nRet = rEvt.mnAction;
+ }
+ else if( DND_ACTION_MOVE == rEvt.mnAction )
+ {
+ if (MoveSelectionCopyFallbackPossible( g_pDDSource, pTarget, false ))
+ nRet = rEvt.mnAction;
+ }
+ else if( DND_ACTION_COPYMOVE == rEvt.mnAction )
+ {
+ if (MoveSelectionCopyFallbackPossible(g_pDDSource, pTarget, true))
+ nRet = rEvt.mnAction;
+ }
+ }
+ return nRet;
+}
+
+sal_Int8 SvTreeListBox::ExecuteDrop( const ExecuteDropEvent& rEvt )
+{
+ return ExecuteDrop( rEvt, g_pDDSource );
+}
+
+/**
+ * This sets the global variables used to determine the
+ * in-process drag source.
+ */
+void SvTreeListBox::SetupDragOrigin()
+{
+ g_pDDSource = this;
+ g_pDDTarget = nullptr;
+}
+
+void SvTreeListBox::StartDrag( sal_Int8, const Point& rPosPixel )
+{
+ if(!isDisposed())
+ {
+ // tdf#143114 do not start drag when a Button/Checkbox is in
+ // drag-before-ButtonUp mode (CaptureMouse() active)
+ if(pImpl->IsCaptureOnButtonActive())
+ return;
+ }
+
+ nOldDragMode = GetDragDropMode();
+ if ( nOldDragMode == DragDropMode::NONE )
+ return;
+
+ ReleaseMouse();
+
+ SvTreeListEntry* pEntry = GetEntry( rPosPixel ); // GetDropTarget( rPos );
+ if( !pEntry )
+ {
+ DragFinished( DND_ACTION_NONE );
+ return;
+ }
+
+ rtl::Reference<TransferDataContainer> xContainer = m_xTransferHelper;
+
+ if (!xContainer)
+ {
+ xContainer.set(new TransferDataContainer);
+ // apparently some (unused) content is needed
+ xContainer->CopyAnyData( SotClipboardFormatId::TREELISTBOX,
+ "unused", SAL_N_ELEMENTS("unused") );
+ }
+
+ nDragDropMode = NotifyStartDrag();
+ if( nDragDropMode == DragDropMode::NONE || 0 == GetSelectionCount() )
+ {
+ nDragDropMode = nOldDragMode;
+ DragFinished( DND_ACTION_NONE );
+ return;
+ }
+
+ SetupDragOrigin();
+
+ bool bOldUpdateMode = Control::IsUpdateMode();
+ Control::SetUpdateMode( true );
+ PaintImmediately();
+ Control::SetUpdateMode( bOldUpdateMode );
+
+ // Disallow using the selection and its children as drop targets.
+ // Important: If the selection of the SourceListBox is changed in the
+ // DropHandler, the entries have to be allowed as drop targets again:
+ // (GetSourceListBox()->EnableSelectionAsDropTarget( true, true );)
+ EnableSelectionAsDropTarget( false );
+
+ xContainer->StartDrag(this, mnDragAction, GetDragFinishedHdl());
+}
+
+void SvTreeListBox::SetDragHelper(const rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants)
+{
+ m_xTransferHelper = rHelper;
+ mnDragAction = eDNDConstants;
+}
+
+void SvTreeListBox::DragFinished( sal_Int8
+#ifndef UNX
+nAction
+#endif
+)
+{
+ EnableSelectionAsDropTarget();
+
+#ifndef UNX
+ if ( (nAction == DND_ACTION_MOVE)
+ && ( (g_pDDTarget && (g_pDDTarget->GetModel() != GetModel()))
+ || !g_pDDTarget))
+ {
+ RemoveSelection();
+ }
+#endif
+
+ UnsetDropTarget();
+ g_pDDSource = nullptr;
+ g_pDDTarget = nullptr;
+ nDragDropMode = nOldDragMode;
+}
+
+void SvTreeListBox::UnsetDropTarget()
+{
+ if (pTargetEntry)
+ {
+ ImplShowTargetEmphasis(pTargetEntry, false);
+ pTargetEntry = nullptr;
+ }
+}
+
+DragDropMode SvTreeListBox::NotifyStartDrag()
+{
+ return DragDropMode(0xffff);
+}
+
+// Handler and methods for Drag - finished handler.
+// The with get GetDragFinishedHdl() get link can set on the
+// TransferDataContainer. This link is a callback for the DragFinished
+// call. AddBox method is called from the GetDragFinishedHdl() and the
+// remove is called in link callback and in the destructor. So it can't
+// called to a deleted object.
+
+namespace
+{
+ // void* to avoid loplugin:vclwidgets, we don't need ownership here
+ std::set<const void*> gSortLBoxes;
+}
+
+void SvTreeListBox::AddBoxToDDList_Impl( const SvTreeListBox& rB )
+{
+ gSortLBoxes.insert( &rB );
+}
+
+void SvTreeListBox::RemoveBoxFromDDList_Impl( const SvTreeListBox& rB )
+{
+ gSortLBoxes.erase( &rB );
+}
+
+IMPL_LINK( SvTreeListBox, DragFinishHdl_Impl, sal_Int8, nAction, void )
+{
+ auto &rSortLBoxes = gSortLBoxes;
+ auto it = rSortLBoxes.find(this);
+ if( it != rSortLBoxes.end() )
+ {
+ DragFinished( nAction );
+ rSortLBoxes.erase( it );
+ }
+}
+
+Link<sal_Int8,void> SvTreeListBox::GetDragFinishedHdl() const
+{
+ AddBoxToDDList_Impl( *this );
+ return LINK( const_cast<SvTreeListBox*>(this), SvTreeListBox, DragFinishHdl_Impl );
+}
+
+/*
+ Bugs/TODO
+
+ - calculate rectangle when editing in-place (bug with some fonts)
+ - SetSpaceBetweenEntries: offset is not taken into account in SetEntryHeight
+*/
+
+#define SV_LBOX_DEFAULT_INDENT_PIXEL 20
+
+void SvTreeListBox::InitTreeView()
+{
+ pCheckButtonData = nullptr;
+ pEdEntry = nullptr;
+ pEdItem = nullptr;
+ nEntryHeight = 0;
+ pEdCtrl = nullptr;
+ nFirstSelTab = 0;
+ nLastSelTab = 0;
+ nFocusWidth = -1;
+ mnCheckboxItemWidth = 0;
+
+ nTreeFlags = SvTreeFlags::RECALCTABS;
+ nIndent = SV_LBOX_DEFAULT_INDENT_PIXEL;
+ nEntryHeightOffs = SV_ENTRYHEIGHTOFFS_PIXEL;
+ pImpl.reset( new SvImpLBox( this, GetModel(), GetStyle() ) );
+
+ mbContextBmpExpanded = true;
+ nContextBmpWidthMax = 0;
+
+ SetFont( GetFont() );
+ AdjustEntryHeightAndRecalc();
+
+ SetSpaceBetweenEntries( 0 );
+ GetOutDev()->SetLineColor();
+ InitSettings();
+ ImplInitStyle();
+ SetTabs();
+}
+
+OUString SvTreeListBox::SearchEntryTextWithHeadTitle( SvTreeListEntry* pEntry )
+{
+ assert(pEntry);
+ OUStringBuffer sRet;
+
+ sal_uInt16 nCount = pEntry->ItemCount();
+ sal_uInt16 nCur = 0;
+ while( nCur < nCount )
+ {
+ SvLBoxItem& rItem = pEntry->GetItem( nCur );
+ if ( (rItem.GetType() == SvLBoxItemType::String) &&
+ !static_cast<SvLBoxString&>( rItem ).GetText().isEmpty() )
+ {
+ sRet.append(static_cast<SvLBoxString&>( rItem ).GetText() + ",");
+ }
+ nCur++;
+ }
+
+ if (!sRet.isEmpty())
+ sRet.remove(sRet.getLength() - 1, 1);
+ return sRet.makeStringAndClear();
+}
+
+SvTreeListBox::~SvTreeListBox()
+{
+ disposeOnce();
+}
+
+void SvTreeListBox::dispose()
+{
+ if (IsMouseCaptured())
+ ReleaseMouse();
+
+ if( pImpl )
+ {
+ pImpl->CallEventListeners( VclEventId::ObjectDying );
+ pImpl.reset();
+ }
+ if( mpImpl )
+ {
+ ClearTabList();
+
+ pEdCtrl.reset();
+
+ SvListView::dispose();
+
+ SvTreeListBox::RemoveBoxFromDDList_Impl( *this );
+
+ if (this == g_pDDSource)
+ g_pDDSource = nullptr;
+ if (this == g_pDDTarget)
+ g_pDDTarget = nullptr;
+ mpImpl.reset();
+ }
+
+ DropTargetHelper::dispose();
+ DragSourceHelper::dispose();
+ Control::dispose();
+}
+
+void SvTreeListBox::SetNoAutoCurEntry( bool b )
+{
+ pImpl->SetNoAutoCurEntry( b );
+}
+
+void SvTreeListBox::SetSublistOpenWithLeftRight()
+{
+ pImpl->m_bSubLstOpLR = true;
+}
+
+void SvTreeListBox::Resize()
+{
+ if( IsEditingActive() )
+ EndEditing( true );
+
+ Control::Resize();
+
+ pImpl->Resize();
+ nFocusWidth = -1;
+ pImpl->ShowCursor( false );
+ pImpl->ShowCursor( true );
+}
+
+/* Cases:
+
+ A) entries have bitmaps
+ 0. no buttons
+ 1. node buttons (can optionally also be on root items)
+ 2. node buttons (can optionally also be on root items) + CheckButton
+ 3. CheckButton
+ B) entries don't have bitmaps (=>via WindowBits because of D&D!)
+ 0. no buttons
+ 1. node buttons (can optionally also be on root items)
+ 2. node buttons (can optionally also be on root items) + CheckButton
+ 3. CheckButton
+*/
+
+#define NO_BUTTONS 0
+#define NODE_BUTTONS 1
+#define NODE_AND_CHECK_BUTTONS 2
+#define CHECK_BUTTONS 3
+
+#define TABFLAGS_TEXT (SvLBoxTabFlags::DYNAMIC | \
+ SvLBoxTabFlags::ADJUST_LEFT | \
+ SvLBoxTabFlags::EDITABLE | \
+ SvLBoxTabFlags::SHOW_SELECTION)
+
+#define TABFLAGS_CONTEXTBMP (SvLBoxTabFlags::DYNAMIC | SvLBoxTabFlags::ADJUST_CENTER)
+
+#define TABFLAGS_CHECKBTN (SvLBoxTabFlags::DYNAMIC | \
+ SvLBoxTabFlags::ADJUST_CENTER)
+
+#define TAB_STARTPOS 2
+
+// take care of GetTextOffset when doing changes
+void SvTreeListBox::SetTabs()
+{
+ if( IsEditingActive() )
+ EndEditing( true );
+ nTreeFlags &= ~SvTreeFlags::RECALCTABS;
+ nFocusWidth = -1;
+ const WinBits nStyle( GetStyle() );
+ bool bHasButtons = (nStyle & WB_HASBUTTONS)!=0;
+ bool bHasButtonsAtRoot = (nStyle & (WB_HASLINESATROOT |
+ WB_HASBUTTONSATROOT))!=0;
+ tools::Long nStartPos = TAB_STARTPOS;
+ tools::Long nNodeWidthPixel = GetExpandedNodeBmp().GetSizePixel().Width();
+
+ // pCheckButtonData->Width() knows nothing about the native checkbox width,
+ // so we have mnCheckboxItemWidth which becomes valid when something is added.
+ tools::Long nCheckWidth = 0;
+ if( nTreeFlags & SvTreeFlags::CHKBTN )
+ nCheckWidth = mnCheckboxItemWidth;
+ tools::Long nCheckWidthDIV2 = nCheckWidth / 2;
+
+ tools::Long nContextWidth = nContextBmpWidthMax;
+ tools::Long nContextWidthDIV2 = nContextWidth / 2;
+
+ ClearTabList();
+
+ int nCase = NO_BUTTONS;
+ if( !(nTreeFlags & SvTreeFlags::CHKBTN) )
+ {
+ if( bHasButtons )
+ nCase = NODE_BUTTONS;
+ }
+ else
+ {
+ if( bHasButtons )
+ nCase = NODE_AND_CHECK_BUTTONS;
+ else
+ nCase = CHECK_BUTTONS;
+ }
+
+ switch( nCase )
+ {
+ case NO_BUTTONS :
+ nStartPos += nContextWidthDIV2; // because of centering
+ AddTab( nStartPos, TABFLAGS_CONTEXTBMP );
+ nStartPos += nContextWidthDIV2; // right edge of context bitmap
+ // only set a distance if there are bitmaps
+ if( nContextBmpWidthMax )
+ nStartPos += 5; // distance context bitmap to text
+ AddTab( nStartPos, TABFLAGS_TEXT );
+ break;
+
+ case NODE_BUTTONS :
+ if( bHasButtonsAtRoot )
+ nStartPos += ( nIndent + (nNodeWidthPixel/2) );
+ else
+ nStartPos += nContextWidthDIV2;
+ AddTab( nStartPos, TABFLAGS_CONTEXTBMP );
+ nStartPos += nContextWidthDIV2; // right edge of context bitmap
+ // only set a distance if there are bitmaps
+ if( nContextBmpWidthMax )
+ nStartPos += 5; // distance context bitmap to text
+ AddTab( nStartPos, TABFLAGS_TEXT );
+ break;
+
+ case NODE_AND_CHECK_BUTTONS :
+ if( bHasButtonsAtRoot )
+ nStartPos += ( nIndent + nNodeWidthPixel );
+ else
+ nStartPos += nCheckWidthDIV2;
+ AddTab( nStartPos, TABFLAGS_CHECKBTN );
+ nStartPos += nCheckWidthDIV2; // right edge of CheckButton
+ nStartPos += 3; // distance CheckButton to context bitmap
+ nStartPos += nContextWidthDIV2; // center of context bitmap
+ AddTab( nStartPos, TABFLAGS_CONTEXTBMP );
+ nStartPos += nContextWidthDIV2; // right edge of context bitmap
+ // only set a distance if there are bitmaps
+ if( nContextBmpWidthMax )
+ nStartPos += 5; // distance context bitmap to text
+ AddTab( nStartPos, TABFLAGS_TEXT );
+ break;
+
+ case CHECK_BUTTONS :
+ nStartPos += nCheckWidthDIV2;
+ AddTab( nStartPos, TABFLAGS_CHECKBTN );
+ nStartPos += nCheckWidthDIV2; // right edge of CheckButton
+ nStartPos += 3; // distance CheckButton to context bitmap
+ nStartPos += nContextWidthDIV2; // center of context bitmap
+ AddTab( nStartPos, TABFLAGS_CONTEXTBMP );
+ nStartPos += nContextWidthDIV2; // right edge of context bitmap
+ // only set a distance if there are bitmaps
+ if( nContextBmpWidthMax )
+ nStartPos += 5; // distance context bitmap to text
+ AddTab( nStartPos, TABFLAGS_TEXT );
+ break;
+ }
+ pImpl->NotifyTabsChanged();
+}
+
+void SvTreeListBox::InitEntry(SvTreeListEntry* pEntry,
+ const OUString& aStr, const Image& aCollEntryBmp, const Image& aExpEntryBmp)
+{
+ if( nTreeFlags & SvTreeFlags::CHKBTN )
+ {
+ pEntry->AddItem(std::make_unique<SvLBoxButton>(pCheckButtonData));
+ }
+
+ pEntry->AddItem(std::make_unique<SvLBoxContextBmp>( aCollEntryBmp,aExpEntryBmp, mbContextBmpExpanded));
+
+ pEntry->AddItem(std::make_unique<SvLBoxString>(aStr));
+}
+
+OUString SvTreeListBox::GetEntryText(SvTreeListEntry* pEntry) const
+{
+ assert(pEntry);
+ SvLBoxString* pItem = static_cast<SvLBoxString*>(pEntry->GetFirstItem(SvLBoxItemType::String));
+ if (pItem) // There may be entries without text items, e.g. in IconView
+ return pItem->GetText();
+ return {};
+}
+
+const Image& SvTreeListBox::GetExpandedEntryBmp(const SvTreeListEntry* pEntry)
+{
+ assert(pEntry);
+ const SvLBoxContextBmp* pItem = static_cast<const SvLBoxContextBmp*>(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp));
+ assert(pItem);
+ return pItem->GetBitmap2( );
+}
+
+const Image& SvTreeListBox::GetCollapsedEntryBmp( const SvTreeListEntry* pEntry )
+{
+ assert(pEntry);
+ const SvLBoxContextBmp* pItem = static_cast<const SvLBoxContextBmp*>(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp));
+ assert(pItem);
+ return pItem->GetBitmap1( );
+}
+
+IMPL_LINK( SvTreeListBox, CheckButtonClick, SvLBoxButtonData *, pData, void )
+{
+ pHdlEntry = pData->GetActEntry();
+ CheckButtonHdl();
+}
+
+SvTreeListEntry* SvTreeListBox::InsertEntry(
+ const OUString& rText,
+ SvTreeListEntry* pParent,
+ bool bChildrenOnDemand, sal_uInt32 nPos,
+ void* pUser
+)
+{
+ nTreeFlags |= SvTreeFlags::MANINS;
+
+ const Image& rDefExpBmp = pImpl->GetDefaultEntryExpBmp( );
+ const Image& rDefColBmp = pImpl->GetDefaultEntryColBmp( );
+
+ aCurInsertedExpBmp = rDefExpBmp;
+ aCurInsertedColBmp = rDefColBmp;
+
+ SvTreeListEntry* pEntry = new SvTreeListEntry;
+ pEntry->SetUserData( pUser );
+ InitEntry( pEntry, rText, rDefColBmp, rDefExpBmp );
+ pEntry->EnableChildrenOnDemand( bChildrenOnDemand );
+
+ if( !pParent )
+ Insert( pEntry, nPos );
+ else
+ Insert( pEntry, pParent, nPos );
+
+ aPrevInsertedExpBmp = rDefExpBmp;
+ aPrevInsertedColBmp = rDefColBmp;
+
+ nTreeFlags &= ~SvTreeFlags::MANINS;
+
+ return pEntry;
+}
+
+void SvTreeListBox::SetEntryText(SvTreeListEntry* pEntry, const OUString& rStr)
+{
+ SvLBoxString* pItem = static_cast<SvLBoxString*>(pEntry->GetFirstItem(SvLBoxItemType::String));
+ assert(pItem);
+ pItem->SetText(rStr);
+ pItem->InitViewData( this, pEntry );
+ GetModel()->InvalidateEntry( pEntry );
+}
+
+void SvTreeListBox::SetExpandedEntryBmp( SvTreeListEntry* pEntry, const Image& aBmp )
+{
+ SvLBoxContextBmp* pItem = static_cast<SvLBoxContextBmp*>(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp));
+
+ assert(pItem);
+ pItem->SetBitmap2( aBmp );
+
+ ModelHasEntryInvalidated(pEntry);
+ CalcEntryHeight( pEntry );
+ Size aSize = aBmp.GetSizePixel();
+ short nWidth = pImpl->UpdateContextBmpWidthVector( pEntry, static_cast<short>(aSize.Width()) );
+ if( nWidth > nContextBmpWidthMax )
+ {
+ nContextBmpWidthMax = nWidth;
+ SetTabs();
+ }
+}
+
+void SvTreeListBox::SetCollapsedEntryBmp(SvTreeListEntry* pEntry,const Image& aBmp )
+{
+ SvLBoxContextBmp* pItem = static_cast<SvLBoxContextBmp*>(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp));
+
+ assert(pItem);
+ pItem->SetBitmap1( aBmp );
+
+ ModelHasEntryInvalidated(pEntry);
+ CalcEntryHeight( pEntry );
+ Size aSize = aBmp.GetSizePixel();
+ short nWidth = pImpl->UpdateContextBmpWidthVector( pEntry, static_cast<short>(aSize.Width()) );
+ if( nWidth > nContextBmpWidthMax )
+ {
+ nContextBmpWidthMax = nWidth;
+ SetTabs();
+ }
+}
+
+void SvTreeListBox::CheckBoxInserted(SvTreeListEntry* pEntry)
+{
+ SvLBoxButton* pItem = static_cast<SvLBoxButton*>(pEntry->GetFirstItem(SvLBoxItemType::Button));
+ if( pItem )
+ {
+ auto nWidth = pItem->GetWidth(this, pEntry);
+ if( mnCheckboxItemWidth < nWidth )
+ {
+ mnCheckboxItemWidth = nWidth;
+ nTreeFlags |= SvTreeFlags::RECALCTABS;
+ }
+ }
+}
+
+void SvTreeListBox::ImpEntryInserted( SvTreeListEntry* pEntry )
+{
+
+ SvTreeListEntry* pParent = pModel->GetParent( pEntry );
+ if( pParent )
+ {
+ SvTLEntryFlags nFlags = pParent->GetFlags();
+ nFlags &= ~SvTLEntryFlags::NO_NODEBMP;
+ pParent->SetFlags( nFlags );
+ }
+
+ if(!((nTreeFlags & SvTreeFlags::MANINS) &&
+ (aPrevInsertedExpBmp == aCurInsertedExpBmp) &&
+ (aPrevInsertedColBmp == aCurInsertedColBmp) ))
+ {
+ Size aSize = GetCollapsedEntryBmp( pEntry ).GetSizePixel();
+ if( aSize.Width() > nContextBmpWidthMax )
+ {
+ nContextBmpWidthMax = static_cast<short>(aSize.Width());
+ nTreeFlags |= SvTreeFlags::RECALCTABS;
+ }
+ aSize = GetExpandedEntryBmp( pEntry ).GetSizePixel();
+ if( aSize.Width() > nContextBmpWidthMax )
+ {
+ nContextBmpWidthMax = static_cast<short>(aSize.Width());
+ nTreeFlags |= SvTreeFlags::RECALCTABS;
+ }
+ }
+ CalcEntryHeight( pEntry );
+
+ if( !(nTreeFlags & SvTreeFlags::CHKBTN) )
+ return;
+
+ CheckBoxInserted(pEntry);
+}
+
+void SvTreeListBox::SetCheckButtonState( SvTreeListEntry* pEntry, SvButtonState eState)
+{
+ if( !(nTreeFlags & SvTreeFlags::CHKBTN) )
+ return;
+
+ SvLBoxButton* pItem = static_cast<SvLBoxButton*>(pEntry->GetFirstItem(SvLBoxItemType::Button));
+ if(!pItem)
+ return ;
+ switch( eState )
+ {
+ case SvButtonState::Checked:
+ pItem->SetStateChecked();
+ break;
+
+ case SvButtonState::Unchecked:
+ pItem->SetStateUnchecked();
+ break;
+
+ case SvButtonState::Tristate:
+ pItem->SetStateTristate();
+ break;
+ }
+ InvalidateEntry( pEntry );
+}
+
+SvButtonState SvTreeListBox::GetCheckButtonState( SvTreeListEntry* pEntry ) const
+{
+ SvButtonState eState = SvButtonState::Unchecked;
+ if( pEntry && ( nTreeFlags & SvTreeFlags::CHKBTN ) )
+ {
+ SvLBoxButton* pItem = static_cast<SvLBoxButton*>(pEntry->GetFirstItem(SvLBoxItemType::Button));
+ if(!pItem)
+ return SvButtonState::Tristate;
+ SvItemStateFlags nButtonFlags = pItem->GetButtonFlags();
+ eState = SvLBoxButtonData::ConvertToButtonState( nButtonFlags );
+ }
+ return eState;
+}
+
+void SvTreeListBox::CheckButtonHdl()
+{
+ if ( pCheckButtonData )
+ pImpl->CallEventListeners( VclEventId::CheckboxToggle, static_cast<void*>(pCheckButtonData->GetActEntry()) );
+}
+
+
+// TODO: Currently all data is cloned so that they conform to the default tree
+// view format. Actually, the model should be used as a reference here. This
+// leads to us _not_ calling SvTreeListEntry::Clone, but only its base class
+// SvTreeListEntry.
+
+
+SvTreeListEntry* SvTreeListBox::CloneEntry( SvTreeListEntry* pSource )
+{
+ OUString aStr;
+ Image aCollEntryBmp;
+ Image aExpEntryBmp;
+
+ SvLBoxString* pStringItem = static_cast<SvLBoxString*>(pSource->GetFirstItem(SvLBoxItemType::String));
+ if( pStringItem )
+ aStr = pStringItem->GetText();
+ SvLBoxContextBmp* pBmpItem = static_cast<SvLBoxContextBmp*>(pSource->GetFirstItem(SvLBoxItemType::ContextBmp));
+ if( pBmpItem )
+ {
+ aCollEntryBmp = pBmpItem->GetBitmap1( );
+ aExpEntryBmp = pBmpItem->GetBitmap2( );
+ }
+ SvTreeListEntry* pClone = new SvTreeListEntry;
+ InitEntry( pClone, aStr, aCollEntryBmp, aExpEntryBmp );
+ pClone->SvTreeListEntry::Clone( pSource );
+ pClone->EnableChildrenOnDemand( pSource->HasChildrenOnDemand() );
+ pClone->SetUserData( pSource->GetUserData() );
+
+ return pClone;
+}
+
+const Image& SvTreeListBox::GetDefaultExpandedEntryBmp( ) const
+{
+ return pImpl->GetDefaultEntryExpBmp( );
+}
+
+const Image& SvTreeListBox::GetDefaultCollapsedEntryBmp( ) const
+{
+ return pImpl->GetDefaultEntryColBmp( );
+}
+
+void SvTreeListBox::SetDefaultExpandedEntryBmp( const Image& aBmp )
+{
+ Size aSize = aBmp.GetSizePixel();
+ if( aSize.Width() > nContextBmpWidthMax )
+ nContextBmpWidthMax = static_cast<short>(aSize.Width());
+ SetTabs();
+
+ pImpl->SetDefaultEntryExpBmp( aBmp );
+}
+
+void SvTreeListBox::SetDefaultCollapsedEntryBmp( const Image& aBmp )
+{
+ Size aSize = aBmp.GetSizePixel();
+ if( aSize.Width() > nContextBmpWidthMax )
+ nContextBmpWidthMax = static_cast<short>(aSize.Width());
+ SetTabs();
+
+ pImpl->SetDefaultEntryColBmp( aBmp );
+}
+
+void SvTreeListBox::EnableCheckButton( SvLBoxButtonData* pData )
+{
+ if( !pData )
+ nTreeFlags &= ~SvTreeFlags::CHKBTN;
+ else
+ {
+ SetCheckButtonData( pData );
+ nTreeFlags |= SvTreeFlags::CHKBTN;
+ pData->SetLink( LINK(this, SvTreeListBox, CheckButtonClick));
+ }
+
+ SetTabs();
+ if( IsUpdateMode() )
+ Invalidate();
+}
+
+void SvTreeListBox::SetCheckButtonData( SvLBoxButtonData* pData )
+{
+ if ( pData )
+ pCheckButtonData = pData;
+}
+
+const Image& SvTreeListBox::GetDefaultExpandedNodeImage( )
+{
+ return SvImpLBox::GetDefaultExpandedNodeImage( );
+}
+
+const Image& SvTreeListBox::GetDefaultCollapsedNodeImage( )
+{
+ return SvImpLBox::GetDefaultCollapsedNodeImage( );
+}
+
+void SvTreeListBox::SetNodeDefaultImages()
+{
+ SetExpandedNodeBmp(GetDefaultExpandedNodeImage());
+ SetCollapsedNodeBmp(GetDefaultCollapsedNodeImage());
+ SetTabs();
+}
+
+bool SvTreeListBox::EditingEntry( SvTreeListEntry* )
+{
+ return true;
+}
+
+bool SvTreeListBox::EditedEntry( SvTreeListEntry* /*pEntry*/,const OUString& /*rNewText*/)
+{
+ return true;
+}
+
+void SvTreeListBox::EnableInplaceEditing( bool bOn )
+{
+ if (bOn)
+ nImpFlags |= SvTreeListBoxFlags::EDT_ENABLED;
+ else
+ nImpFlags &= ~SvTreeListBoxFlags::EDT_ENABLED;
+}
+
+void SvTreeListBox::KeyInput( const KeyEvent& rKEvt )
+{
+ // under OS/2, we get key up/down even while editing
+ if( IsEditingActive() )
+ return;
+
+ if( !pImpl->KeyInput( rKEvt ) )
+ {
+ bool bHandled = HandleKeyInput( rKEvt );
+ if ( !bHandled )
+ Control::KeyInput( rKEvt );
+ }
+}
+
+void SvTreeListBox::RequestingChildren( SvTreeListEntry* pParent )
+{
+ if( !pParent->HasChildren() )
+ InsertEntry( "<dummy>", pParent );
+}
+
+void SvTreeListBox::GetFocus()
+{
+ //If there is no item in the tree, draw focus.
+ if( !First())
+ {
+ Invalidate();
+ }
+ pImpl->GetFocus();
+ Control::GetFocus();
+
+ SvTreeListEntry* pEntry = FirstSelected();
+ if ( !pEntry )
+ {
+ pEntry = pImpl->GetCurEntry();
+ }
+ if (pImpl->m_pCursor)
+ {
+ if (pEntry != pImpl->m_pCursor)
+ pEntry = pImpl->m_pCursor;
+ }
+ if ( pEntry )
+ pImpl->CallEventListeners( VclEventId::ListboxTreeFocus, pEntry );
+
+}
+
+void SvTreeListBox::LoseFocus()
+{
+ // If there is no item in the tree, delete visual focus.
+ if ( !First() )
+ Invalidate();
+ if ( pImpl )
+ pImpl->LoseFocus();
+ Control::LoseFocus();
+}
+
+void SvTreeListBox::ModelHasCleared()
+{
+ pImpl->m_pCursor = nullptr; // else we crash in GetFocus when editing in-place
+ pTargetEntry = nullptr;
+ pEdCtrl.reset();
+ pImpl->Clear();
+ nFocusWidth = -1;
+
+ nContextBmpWidthMax = 0;
+ SetDefaultExpandedEntryBmp( GetDefaultExpandedEntryBmp() );
+ SetDefaultCollapsedEntryBmp( GetDefaultCollapsedEntryBmp() );
+
+ if( !(nTreeFlags & SvTreeFlags::FIXEDHEIGHT ))
+ nEntryHeight = 0;
+ AdjustEntryHeight();
+ AdjustEntryHeight( GetDefaultExpandedEntryBmp() );
+ AdjustEntryHeight( GetDefaultCollapsedEntryBmp() );
+
+ SvListView::ModelHasCleared();
+}
+
+bool SvTreeListBox::PosOverBody(const Point& rPos) const
+{
+ if (rPos.X() < 0 || rPos.Y() < 0)
+ return false;
+ Size aSize(GetSizePixel());
+ if (rPos.X() > aSize.Width() || rPos.Y() > aSize.Height())
+ return false;
+ if (pImpl->m_aVerSBar->IsVisible())
+ {
+ tools::Rectangle aRect(pImpl->m_aVerSBar->GetPosPixel(), pImpl->m_aVerSBar->GetSizePixel());
+ if (aRect.Contains(rPos))
+ return false;
+ }
+ if (pImpl->m_aHorSBar->IsVisible())
+ {
+ tools::Rectangle aRect(pImpl->m_aHorSBar->GetPosPixel(), pImpl->m_aHorSBar->GetSizePixel());
+ if (aRect.Contains(rPos))
+ return false;
+ }
+ return true;
+}
+
+void SvTreeListBox::ScrollOutputArea( short nDeltaEntries )
+{
+ if( !nDeltaEntries || !pImpl->m_aVerSBar->IsVisible() )
+ return;
+
+ tools::Long nThumb = pImpl->m_aVerSBar->GetThumbPos();
+ tools::Long nMax = pImpl->m_aVerSBar->GetRange().Max();
+
+ if( nDeltaEntries < 0 )
+ {
+ // move window up
+ nDeltaEntries *= -1;
+ tools::Long nVis = pImpl->m_aVerSBar->GetVisibleSize();
+ tools::Long nTemp = nThumb + nVis;
+ if( nDeltaEntries > (nMax - nTemp) )
+ nDeltaEntries = static_cast<short>(nMax - nTemp);
+ pImpl->PageDown( static_cast<sal_uInt16>(nDeltaEntries) );
+ }
+ else
+ {
+ if( nDeltaEntries > nThumb )
+ nDeltaEntries = static_cast<short>(nThumb);
+ pImpl->PageUp( static_cast<sal_uInt16>(nDeltaEntries) );
+ }
+ pImpl->SyncVerThumb();
+}
+
+void SvTreeListBox::ScrollToAbsPos( tools::Long nPos )
+{
+ pImpl->ScrollToAbsPos( nPos );
+}
+
+void SvTreeListBox::SetSelectionMode( SelectionMode eSelectMode )
+{
+ eSelMode = eSelectMode;
+ pImpl->SetSelectionMode( eSelectMode );
+}
+
+void SvTreeListBox::SetDragDropMode( DragDropMode nDDMode )
+{
+ nDragDropMode = nDDMode;
+ pImpl->SetDragDropMode( nDDMode );
+}
+
+void SvTreeListBox::CalcEntryHeight( SvTreeListEntry const * pEntry )
+{
+ short nHeightMax=0;
+ sal_uInt16 nCount = pEntry->ItemCount();
+ sal_uInt16 nCur = 0;
+ SvViewDataEntry* pViewData = GetViewDataEntry( pEntry );
+ while( nCur < nCount )
+ {
+ auto nHeight = SvLBoxItem::GetHeight(pViewData, nCur);
+ if( nHeight > nHeightMax )
+ nHeightMax = nHeight;
+ nCur++;
+ }
+
+ if( nHeightMax > nEntryHeight )
+ {
+ nEntryHeight = nHeightMax;
+ Control::SetFont( GetFont() );
+ pImpl->SetEntryHeight();
+ }
+}
+
+void SvTreeListBox::SetEntryHeight( short nHeight )
+{
+ if( nHeight > nEntryHeight )
+ {
+ nEntryHeight = nHeight;
+ if( nEntryHeight )
+ nTreeFlags |= SvTreeFlags::FIXEDHEIGHT;
+ else
+ nTreeFlags &= ~SvTreeFlags::FIXEDHEIGHT;
+ Control::SetFont( GetFont() );
+ pImpl->SetEntryHeight();
+ }
+}
+
+void SvTreeListBox::SetEntryWidth( short nWidth )
+{
+ nEntryWidth = nWidth;
+}
+
+void SvTreeListBox::AdjustEntryHeight( const Image& rBmp )
+{
+ const Size aSize( rBmp.GetSizePixel() );
+ if( aSize.Height() > nEntryHeight )
+ {
+ nEntryHeight = static_cast<short>(aSize.Height()) + nEntryHeightOffs;
+ pImpl->SetEntryHeight();
+ }
+}
+
+void SvTreeListBox::AdjustEntryHeight()
+{
+ tools::Long nHeight = GetTextHeight();
+ if( nHeight > nEntryHeight )
+ {
+ nEntryHeight = static_cast<short>(nHeight) + nEntryHeightOffs;
+ pImpl->SetEntryHeight();
+ }
+}
+
+bool SvTreeListBox::Expand( SvTreeListEntry* pParent )
+{
+ pHdlEntry = pParent;
+ bool bExpanded = false;
+ SvTLEntryFlags nFlags;
+
+ if( pParent->HasChildrenOnDemand() )
+ RequestingChildren( pParent );
+ bool bExpandAllowed = pParent->HasChildren() && ExpandingHdl();
+ // double check if the expander callback ended up removing all children
+ if (pParent->HasChildren())
+ {
+ if (bExpandAllowed)
+ {
+ bExpanded = true;
+ ExpandListEntry( pParent );
+ pImpl->EntryExpanded( pParent );
+ pHdlEntry = pParent;
+ ExpandedHdl();
+ SetAlternatingRowColors( mbAlternatingRowColors );
+ }
+ nFlags = pParent->GetFlags();
+ nFlags &= ~SvTLEntryFlags::NO_NODEBMP;
+ nFlags |= SvTLEntryFlags::HAD_CHILDREN;
+ pParent->SetFlags( nFlags );
+ }
+ else
+ {
+ nFlags = pParent->GetFlags();
+ nFlags |= SvTLEntryFlags::NO_NODEBMP;
+ pParent->SetFlags( nFlags );
+ GetModel()->InvalidateEntry( pParent ); // repaint
+ }
+
+ // #i92103#
+ if ( bExpanded )
+ {
+ pImpl->CallEventListeners( VclEventId::ItemExpanded, pParent );
+ }
+
+ return bExpanded;
+}
+
+bool SvTreeListBox::Collapse( SvTreeListEntry* pParent )
+{
+ pHdlEntry = pParent;
+ bool bCollapsed = false;
+
+ if( ExpandingHdl() )
+ {
+ bCollapsed = true;
+ pImpl->CollapsingEntry( pParent );
+ CollapseListEntry( pParent );
+ pImpl->EntryCollapsed( pParent );
+ pHdlEntry = pParent;
+ ExpandedHdl();
+ SetAlternatingRowColors( mbAlternatingRowColors );
+ }
+
+ // #i92103#
+ if ( bCollapsed )
+ {
+ pImpl->CallEventListeners( VclEventId::ItemCollapsed, pParent );
+ }
+
+ return bCollapsed;
+}
+
+bool SvTreeListBox::Select( SvTreeListEntry* pEntry, bool bSelect )
+{
+ DBG_ASSERT(pEntry,"Select: Null-Ptr");
+ bool bRetVal = SelectListEntry( pEntry, bSelect );
+ DBG_ASSERT(IsSelected(pEntry)==bSelect,"Select failed");
+ if( bRetVal )
+ {
+ pImpl->EntrySelected( pEntry, bSelect );
+ pHdlEntry = pEntry;
+ if( bSelect )
+ {
+ SelectHdl();
+ CallEventListeners( VclEventId::ListboxTreeSelect, pEntry);
+ }
+ else
+ DeselectHdl();
+ }
+ return bRetVal;
+}
+
+sal_uInt32 SvTreeListBox::SelectChildren( SvTreeListEntry* pParent, bool bSelect )
+{
+ pImpl->DestroyAnchor();
+ sal_uInt32 nRet = 0;
+ if( !pParent->HasChildren() )
+ return 0;
+ sal_uInt16 nRefDepth = pModel->GetDepth( pParent );
+ SvTreeListEntry* pChild = FirstChild( pParent );
+ do {
+ nRet++;
+ Select( pChild, bSelect );
+ pChild = Next( pChild );
+ } while( pChild && pModel->GetDepth( pChild ) > nRefDepth );
+ return nRet;
+}
+
+void SvTreeListBox::SelectAll( bool bSelect )
+{
+ pImpl->SelAllDestrAnch(
+ bSelect,
+ true, // delete anchor,
+ true ); // even when using SelectionMode::Single, deselect the cursor
+}
+
+void SvTreeListBox::ModelHasInsertedTree( SvTreeListEntry* pEntry )
+{
+ sal_uInt16 nRefDepth = pModel->GetDepth( pEntry );
+ SvTreeListEntry* pTmp = pEntry;
+ do
+ {
+ ImpEntryInserted( pTmp );
+ pTmp = Next( pTmp );
+ } while( pTmp && nRefDepth < pModel->GetDepth( pTmp ) );
+ pImpl->TreeInserted( pEntry );
+}
+
+void SvTreeListBox::ModelHasInserted( SvTreeListEntry* pEntry )
+{
+ ImpEntryInserted( pEntry );
+ pImpl->EntryInserted( pEntry );
+}
+
+void SvTreeListBox::ModelIsMoving(SvTreeListEntry* pSource )
+{
+ pImpl->MovingEntry( pSource );
+}
+
+void SvTreeListBox::ModelHasMoved( SvTreeListEntry* pSource )
+{
+ pImpl->EntryMoved( pSource );
+}
+
+void SvTreeListBox::ModelIsRemoving( SvTreeListEntry* pEntry )
+{
+ if(pEdEntry == pEntry)
+ pEdEntry = nullptr;
+
+ pImpl->RemovingEntry( pEntry );
+}
+
+void SvTreeListBox::ModelHasRemoved( SvTreeListEntry* pEntry )
+{
+ if (pEntry == pHdlEntry)
+ pHdlEntry = nullptr;
+
+ if (pEntry == pTargetEntry)
+ pTargetEntry = nullptr;
+
+ pImpl->EntryRemoved();
+}
+
+void SvTreeListBox::SetCollapsedNodeBmp( const Image& rBmp)
+{
+ AdjustEntryHeight( rBmp );
+ pImpl->SetCollapsedNodeBmp( rBmp );
+}
+
+void SvTreeListBox::SetExpandedNodeBmp( const Image& rBmp )
+{
+ AdjustEntryHeight( rBmp );
+ pImpl->SetExpandedNodeBmp( rBmp );
+}
+
+
+void SvTreeListBox::SetFont( const vcl::Font& rFont )
+{
+ vcl::Font aTempFont( rFont );
+ vcl::Font aOrigFont( GetFont() );
+ aTempFont.SetTransparent( true );
+ if (aTempFont == aOrigFont)
+ return;
+ Control::SetFont( aTempFont );
+
+ aTempFont.SetColor(aOrigFont.GetColor());
+ aTempFont.SetFillColor(aOrigFont.GetFillColor());
+ aTempFont.SetTransparent(aOrigFont.IsTransparent());
+
+ if (aTempFont == aOrigFont)
+ return;
+
+ AdjustEntryHeightAndRecalc();
+}
+
+void SvTreeListBox::AdjustEntryHeightAndRecalc()
+{
+ AdjustEntryHeight();
+ // always invalidate, else things go wrong in SetEntryHeight
+ RecalcViewData();
+}
+
+void SvTreeListBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ Control::Paint(rRenderContext, rRect);
+ if (nTreeFlags & SvTreeFlags::RECALCTABS)
+ SetTabs();
+ pImpl->Paint(rRenderContext, rRect);
+
+ //Add visual focus draw
+ if (First())
+ return;
+
+ if (HasFocus())
+ {
+ tools::Long nHeight = rRenderContext.GetTextHeight();
+ tools::Rectangle aRect(Point(0, 0), Size(GetSizePixel().Width(), nHeight));
+ ShowFocus(aRect);
+ }
+ else
+ {
+ HideFocus();
+ }
+}
+
+void SvTreeListBox::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ // tdf#143114 remember the *correct* starting entry
+ pImpl->m_pCursorOld = (rMEvt.IsLeft() && (nTreeFlags & SvTreeFlags::CHKBTN) && mnClicksToToggle > 0)
+ ? GetEntry(rMEvt.GetPosPixel())
+ : nullptr;
+
+ pImpl->MouseButtonDown( rMEvt );
+}
+
+void SvTreeListBox::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ // tdf#116675 clicking on an entry should toggle its checkbox
+ // tdf#143114 use the already created starting entry and if it exists
+ if (nullptr != pImpl->m_pCursorOld)
+ {
+ const Point aPnt = rMEvt.GetPosPixel();
+ SvTreeListEntry* pEntry = GetEntry(aPnt);
+
+ // compare if MouseButtonUp *is* on the same entry, regardless of scrolling
+ // or other things
+ if (pEntry && pEntry->m_Items.size() > 0 && 1 == mnClicksToToggle && pEntry == pImpl->m_pCursorOld)
+ {
+ SvLBoxItem* pItem = GetItem(pEntry, aPnt.X());
+ // if the checkbox button was clicked, that will be toggled later, do not toggle here
+ // anyway users probably don't want to toggle the checkbox by clickink on another button
+ if (!pItem || pItem->GetType() != SvLBoxItemType::Button)
+ {
+ SvLBoxButton* pItemCheckBox
+ = static_cast<SvLBoxButton*>(pEntry->GetFirstItem(SvLBoxItemType::Button));
+ if (pItemCheckBox && GetItemPos(pEntry, 0).first < aPnt.X() - GetMapMode().GetOrigin().X())
+ {
+ pItemCheckBox->ClickHdl(pEntry);
+ InvalidateEntry(pEntry);
+ }
+ }
+ }
+ }
+
+ pImpl->MouseButtonUp( rMEvt );
+}
+
+void SvTreeListBox::MouseMove( const MouseEvent& rMEvt )
+{
+ pImpl->MouseMove( rMEvt );
+}
+
+void SvTreeListBox::SetUpdateMode( bool bUpdate )
+{
+ pImpl->SetUpdateMode( bUpdate );
+ mbUpdateAlternatingRows = bUpdate;
+ SetAlternatingRowColors( mbAlternatingRowColors );
+}
+
+void SvTreeListBox::SetSpaceBetweenEntries( short nOffsLogic )
+{
+ if( nOffsLogic != nEntryHeightOffs )
+ {
+ nEntryHeight = nEntryHeight - nEntryHeightOffs;
+ nEntryHeightOffs = nOffsLogic;
+ nEntryHeight = nEntryHeight + nOffsLogic;
+ AdjustEntryHeightAndRecalc();
+ pImpl->SetEntryHeight();
+ }
+}
+
+void SvTreeListBox::SetCurEntry( SvTreeListEntry* pEntry )
+{
+ pImpl->SetCurEntry( pEntry );
+}
+
+Image const & SvTreeListBox::GetExpandedNodeBmp( ) const
+{
+ return pImpl->GetExpandedNodeBmp( );
+}
+
+Point SvTreeListBox::GetEntryPosition(const SvTreeListEntry* pEntry) const
+{
+ return pImpl->GetEntryPosition( pEntry );
+}
+
+void SvTreeListBox::MakeVisible( SvTreeListEntry* pEntry )
+{
+ pImpl->MakeVisible(pEntry);
+}
+
+void SvTreeListBox::MakeVisible( SvTreeListEntry* pEntry, bool bMoveToTop )
+{
+ pImpl->MakeVisible( pEntry, bMoveToTop );
+}
+
+void SvTreeListBox::ModelHasEntryInvalidated( SvTreeListEntry* pEntry )
+{
+
+ // reinitialize the separate items of the entries
+ sal_uInt16 nCount = pEntry->ItemCount();
+ for( sal_uInt16 nIdx = 0; nIdx < nCount; nIdx++ )
+ {
+ SvLBoxItem& rItem = pEntry->GetItem( nIdx );
+ rItem.InitViewData( this, pEntry );
+ }
+
+ // repaint
+ pImpl->InvalidateEntry( pEntry );
+}
+
+void SvTreeListBox::EditItemText(SvTreeListEntry* pEntry, SvLBoxString* pItem, const Selection& rSelection)
+{
+ assert(pEntry && pItem);
+ if( IsSelected( pEntry ))
+ {
+ pImpl->ShowCursor( false );
+ SelectListEntry( pEntry, false );
+ pImpl->InvalidateEntry(pEntry);
+ SelectListEntry( pEntry, true );
+ pImpl->ShowCursor( true );
+ }
+ pEdEntry = pEntry;
+ pEdItem = pItem;
+ SvLBoxTab* pTab = GetTab( pEntry, pItem );
+ DBG_ASSERT(pTab,"EditItemText:Tab not found");
+
+ auto nItemHeight( pItem->GetHeight(this, pEntry) );
+ Point aPos = GetEntryPosition( pEntry );
+ aPos.AdjustY(( nEntryHeight - nItemHeight ) / 2 );
+ aPos.setX( GetTabPos( pEntry, pTab ) );
+ tools::Long nOutputWidth = pImpl->GetOutputSize().Width();
+ Size aSize( nOutputWidth - aPos.X(), nItemHeight );
+ sal_uInt16 nPos = std::find_if( aTabs.begin(), aTabs.end(),
+ [pTab](const std::unique_ptr<SvLBoxTab>& p) { return p.get() == pTab; })
+ - aTabs.begin();
+ if( nPos+1 < static_cast<sal_uInt16>(aTabs.size()) )
+ {
+ SvLBoxTab* pRightTab = aTabs[ nPos + 1 ].get();
+ tools::Long nRight = GetTabPos( pEntry, pRightTab );
+ if( nRight <= nOutputWidth )
+ aSize.setWidth( nRight - aPos.X() );
+ }
+ Point aOrigin( GetMapMode().GetOrigin() );
+ aPos += aOrigin; // convert to win coordinates
+ aSize.AdjustWidth( -(aOrigin.X()) );
+ tools::Rectangle aRect( aPos, aSize );
+ EditText( pItem->GetText(), aRect, rSelection );
+}
+
+void SvTreeListBox::EditEntry( SvTreeListEntry* pEntry )
+{
+ pImpl->m_aEditClickPos = Point( -1, -1 );
+ ImplEditEntry( pEntry );
+}
+
+void SvTreeListBox::ImplEditEntry( SvTreeListEntry* pEntry )
+{
+ if( IsEditingActive() )
+ EndEditing();
+ if( !pEntry )
+ pEntry = GetCurEntry();
+ if( !pEntry )
+ return;
+
+ tools::Long nClickX = pImpl->m_aEditClickPos.X();
+ bool bIsMouseTriggered = nClickX >= 0;
+
+ SvLBoxString* pItem = nullptr;
+ sal_uInt16 nCount = pEntry->ItemCount();
+ tools::Long nTabPos, nNextTabPos = 0;
+ for( sal_uInt16 i = 0 ; i < nCount ; i++ )
+ {
+ SvLBoxItem& rTmpItem = pEntry->GetItem( i );
+ if (rTmpItem.GetType() != SvLBoxItemType::String)
+ continue;
+
+ SvLBoxTab* pTab = GetTab( pEntry, &rTmpItem );
+ nNextTabPos = -1;
+ if( i < nCount - 1 )
+ {
+ SvLBoxItem& rNextItem = pEntry->GetItem( i + 1 );
+ SvLBoxTab* pNextTab = GetTab( pEntry, &rNextItem );
+ nNextTabPos = pNextTab->GetPos();
+ }
+
+ if( pTab && pTab->IsEditable() )
+ {
+ nTabPos = pTab->GetPos();
+ if( !bIsMouseTriggered || (nClickX > nTabPos && (nNextTabPos == -1 || nClickX < nNextTabPos ) ) )
+ {
+ pItem = static_cast<SvLBoxString*>( &rTmpItem );
+ break;
+ }
+ }
+ }
+
+ if( pItem && EditingEntry( pEntry ) )
+ {
+ Selection aSel( SELECTION_MIN, SELECTION_MAX );
+ SelectAll( false );
+ MakeVisible( pEntry );
+ EditItemText( pEntry, pItem, aSel );
+ }
+}
+
+void SvTreeListBox::EditedText( const OUString& rStr )
+
+{
+ if(pEdEntry) // we have to check if this entry is null that means that it is removed while editing
+ {
+ if( EditedEntry( pEdEntry, rStr ) )
+ {
+ static_cast<SvLBoxString*>(pEdItem)->SetText( rStr );
+ pModel->InvalidateEntry( pEdEntry );
+ }
+ if( GetSelectionCount() == 0 )
+ Select( pEdEntry );
+ if( GetSelectionMode() == SelectionMode::Multiple && !GetCurEntry() )
+ SetCurEntry( pEdEntry );
+ }
+}
+
+SvTreeListEntry* SvTreeListBox::GetDropTarget( const Point& rPos )
+{
+ // scroll
+ if( rPos.Y() < 12 )
+ {
+ ImplShowTargetEmphasis(pTargetEntry, false);
+ ScrollOutputArea( +1 );
+ }
+ else
+ {
+ Size aSize( pImpl->GetOutputSize() );
+ if( rPos.Y() > aSize.Height() - 12 )
+ {
+ ImplShowTargetEmphasis(pTargetEntry, false);
+ ScrollOutputArea( -1 );
+ }
+ }
+
+ SvTreeListEntry* pTarget = pImpl->GetEntry( rPos );
+ // when dropping in a vacant space, use the last entry
+ if( !pTarget )
+ return LastVisible();
+ else if( (GetDragDropMode() & DragDropMode::ENABLE_TOP) &&
+ pTarget == First() && rPos.Y() < 6 )
+ return nullptr;
+
+ return pTarget;
+}
+
+
+SvTreeListEntry* SvTreeListBox::GetEntry( const Point& rPos, bool bHit ) const
+{
+ SvTreeListEntry* pEntry = pImpl->GetEntry( rPos );
+ if( pEntry && bHit )
+ {
+ tools::Long nLine = pImpl->GetEntryLine( pEntry );
+ if( !(pImpl->EntryReallyHit( pEntry, rPos, nLine)) )
+ return nullptr;
+ }
+ return pEntry;
+}
+
+SvTreeListEntry* SvTreeListBox::GetCurEntry() const
+{
+ return pImpl ? pImpl->GetCurEntry() : nullptr;
+}
+
+void SvTreeListBox::ImplInitStyle()
+{
+ const WinBits nWindowStyle = GetStyle();
+
+ nTreeFlags |= SvTreeFlags::RECALCTABS;
+ if (nWindowStyle & WB_SORT)
+ {
+ GetModel()->SetSortMode(SvSortMode::Ascending);
+ GetModel()->SetCompareHdl(LINK(this, SvTreeListBox, DefaultCompare));
+ }
+ else
+ {
+ GetModel()->SetSortMode(SvSortMode::None);
+ GetModel()->SetCompareHdl(Link<const SvSortData&,sal_Int32>());
+ }
+ pImpl->SetStyle(nWindowStyle);
+ pImpl->Resize();
+ Invalidate();
+}
+
+void SvTreeListBox::InvalidateEntry(SvTreeListEntry* pEntry)
+{
+ DBG_ASSERT(pEntry,"InvalidateEntry:No Entry");
+ if (pEntry)
+ {
+ GetModel()->InvalidateEntry(pEntry);
+ }
+}
+
+void SvTreeListBox::PaintEntry1(SvTreeListEntry& rEntry, tools::Long nLine, vcl::RenderContext& rRenderContext)
+{
+ tools::Rectangle aRect; // multi purpose
+
+ bool bHorSBar = pImpl->HasHorScrollBar();
+
+ pImpl->UpdateContextBmpWidthMax(&rEntry);
+
+ if (nTreeFlags & SvTreeFlags::RECALCTABS)
+ SetTabs();
+
+ short nTempEntryHeight = GetEntryHeight();
+ tools::Long nWidth = pImpl->GetOutputSize().Width();
+
+ // Did we turn on the scrollbar within PreparePaints? If yes, we have to set
+ // the ClipRegion anew.
+ if (!bHorSBar && pImpl->HasHorScrollBar())
+ rRenderContext.SetClipRegion(vcl::Region(pImpl->GetClipRegionRect()));
+
+ Point aEntryPos(rRenderContext.GetMapMode().GetOrigin());
+ aEntryPos.setX( aEntryPos.X() * -1 ); // conversion document coordinates
+ tools::Long nMaxRight = nWidth + aEntryPos.X() - 1;
+
+ Color aBackupTextColor(rRenderContext.GetTextColor());
+ vcl::Font aBackupFont(rRenderContext.GetFont());
+ Color aBackupColor = rRenderContext.GetFillColor();
+
+ bool bCurFontIsSel = false;
+ // if a ClipRegion was set from outside, we don't have to reset it
+ const WinBits nWindowStyle = GetStyle();
+ const bool bHideSelection = (nWindowStyle & WB_HIDESELECTION) !=0 && !HasFocus();
+ const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ vcl::Font aHighlightFont(rRenderContext.GetFont());
+ const Color aHighlightTextColor(rSettings.GetHighlightTextColor());
+ aHighlightFont.SetColor(aHighlightTextColor);
+
+ Size aRectSize(0, nTempEntryHeight);
+
+ SvViewDataEntry* pViewDataEntry = GetViewDataEntry( &rEntry );
+ const bool bSeparator(rEntry.GetFlags() & SvTLEntryFlags::IS_SEPARATOR);
+
+ const size_t nTabCount = aTabs.size();
+ const size_t nItemCount = rEntry.ItemCount();
+ size_t nCurTab = 0;
+ size_t nCurItem = 0;
+
+ while (nCurTab < nTabCount && nCurItem < nItemCount)
+ {
+ SvLBoxTab* pTab = aTabs[nCurTab].get();
+ const size_t nNextTab = nCurTab + 1;
+ SvLBoxTab* pNextTab = nNextTab < nTabCount ? aTabs[nNextTab].get() : nullptr;
+ SvLBoxItem& rItem = rEntry.GetItem(nCurItem);
+
+ SvLBoxTabFlags nFlags = pTab->nFlags;
+ Size aSize(rItem.GetWidth(this, pViewDataEntry, nCurItem),
+ SvLBoxItem::GetHeight(pViewDataEntry, nCurItem));
+ tools::Long nTabPos = GetTabPos(&rEntry, pTab);
+
+ tools::Long nNextTabPos;
+ if (pNextTab)
+ nNextTabPos = GetTabPos(&rEntry, pNextTab);
+ else
+ {
+ nNextTabPos = nMaxRight;
+ if (nTabPos > nMaxRight)
+ nNextTabPos += 50;
+ }
+
+ tools::Long nX;
+ if( pTab->nFlags & SvLBoxTabFlags::ADJUST_RIGHT )
+ // avoid cutting the right edge off the tab separation
+ nX = nTabPos + pTab->CalcOffset(aSize.Width(), (nNextTabPos - SV_TAB_BORDER - 1) - nTabPos);
+ else
+ nX = nTabPos + pTab->CalcOffset(aSize.Width(), nNextTabPos - nTabPos);
+
+ aEntryPos.setX( nX );
+ aEntryPos.setY( nLine );
+
+ // set background pattern/color
+
+ Wallpaper aWallpaper = rRenderContext.GetBackground();
+
+ bool bSelTab = bool(nFlags & SvLBoxTabFlags::SHOW_SELECTION);
+
+ if (pViewDataEntry->IsHighlighted() && bSelTab)
+ {
+ Color aNewWallColor = rSettings.GetHighlightColor();
+ // if the face color is bright then the deactivate color is also bright
+ // -> so you can't see any deactivate selection
+ if (bHideSelection && !rSettings.GetFaceColor().IsBright()
+ && aWallpaper.GetColor().IsBright() != rSettings.GetDeactiveColor().IsBright())
+ {
+ aNewWallColor = rSettings.GetDeactiveColor();
+ }
+ // set font color to highlight
+ if (!bCurFontIsSel)
+ {
+ rRenderContext.SetTextColor(aHighlightTextColor);
+ rRenderContext.SetFont(aHighlightFont);
+ bCurFontIsSel = true;
+ }
+ aWallpaper.SetColor(aNewWallColor);
+ }
+ else // no selection
+ {
+ if (bCurFontIsSel || rEntry.GetTextColor())
+ {
+ bCurFontIsSel = false;
+ if (const auto & xCustomTextColor = rEntry.GetTextColor())
+ rRenderContext.SetTextColor(*xCustomTextColor);
+ else
+ rRenderContext.SetTextColor(aBackupTextColor);
+ rRenderContext.SetFont(aBackupFont);
+ }
+ else
+ {
+ aWallpaper.SetColor(rEntry.GetBackColor());
+ }
+ }
+
+ // draw background
+ if (!(nTreeFlags & SvTreeFlags::USESEL))
+ {
+ // only draw the area that is used by the item
+ aRectSize.setWidth( aSize.Width() );
+ aRect.SetPos(aEntryPos);
+ aRect.SetSize(aRectSize);
+ }
+ else
+ {
+ // draw from the current to the next tab
+ if (nCurTab != 0)
+ aRect.SetLeft( nTabPos );
+ else
+ // if we're in the 0th tab, always draw from column 0 --
+ // else we get problems with centered tabs
+ aRect.SetLeft( 0 );
+ aRect.SetTop( nLine );
+ aRect.SetBottom( nLine + nTempEntryHeight - 1 );
+ if (pNextTab)
+ {
+ tools::Long nRight;
+ nRight = GetTabPos(&rEntry, pNextTab) - 1;
+ if (nRight > nMaxRight)
+ nRight = nMaxRight;
+ aRect.SetRight( nRight );
+ }
+ else
+ {
+ aRect.SetRight( nMaxRight );
+ }
+ }
+ // A custom selection that starts at a tab position > 0, do not fill
+ // the background of the 0th item, else e.g. we might not be able to
+ // realize tab listboxes with lines.
+ if (!(nCurTab == 0 && (nTreeFlags & SvTreeFlags::USESEL) && nFirstSelTab))
+ {
+ Color aBackgroundColor = aWallpaper.GetColor();
+ if (aBackgroundColor != COL_TRANSPARENT)
+ {
+ rRenderContext.SetFillColor(aBackgroundColor);
+ // this case may occur for smaller horizontal resizes
+ if (aRect.Left() < aRect.Right())
+ rRenderContext.DrawRect(aRect);
+ }
+ }
+ // draw item
+ // center vertically
+ aEntryPos.AdjustY((nTempEntryHeight - aSize.Height()) / 2 );
+
+ rItem.Paint(aEntryPos, *this, rRenderContext, pViewDataEntry, rEntry);
+
+ // division line between tabs (but not if this is a separator line)
+ if (!bSeparator && pNextTab && rItem.GetType() == SvLBoxItemType::String &&
+ // not at the right edge of the window!
+ aRect.Right() < nMaxRight)
+ {
+ aRect.SetLeft( aRect.Right() - SV_TAB_BORDER );
+ rRenderContext.DrawRect(aRect);
+ }
+
+ rRenderContext.SetFillColor(aBackupColor);
+
+ nCurItem++;
+ nCurTab++;
+ }
+
+ if (pViewDataEntry->IsDragTarget())
+ {
+ rRenderContext.Push();
+ rRenderContext.SetLineColor(rSettings.GetDeactiveColor());
+ rRenderContext.SetFillColor(rSettings.GetDeactiveColor());
+
+ const bool bAsTree = GetStyle() & (WB_HASLINES | WB_HASLINESATROOT);
+ if (bAsTree)
+ {
+ rRenderContext.DrawRect(tools::Rectangle(Point(0, nLine + nTempEntryHeight - 2), Size(nWidth, 2)));
+ rRenderContext.DrawRect(tools::Rectangle(Point(0, nLine), Size(nWidth, 2)));
+ }
+ else
+ {
+ rRenderContext.DrawRect(tools::Rectangle(Point(0, nLine), Size(nWidth, 2)));
+ }
+
+ rRenderContext.Pop();
+ }
+
+ if (bCurFontIsSel || rEntry.GetTextColor())
+ {
+ rRenderContext.SetTextColor(aBackupTextColor);
+ rRenderContext.SetFont(aBackupFont);
+ }
+
+ sal_uInt16 nFirstDynTabPos(0);
+ SvLBoxTab* pFirstDynamicTab = GetFirstDynamicTab(nFirstDynTabPos);
+ tools::Long nDynTabPos = GetTabPos(&rEntry, pFirstDynamicTab);
+ nDynTabPos += pImpl->m_nNodeBmpTabDistance;
+ nDynTabPos += pImpl->m_nNodeBmpWidth / 2;
+ nDynTabPos += 4; // 4 pixels of buffer, so the node bitmap is not too close
+ // to the next tab
+
+ if( !((!(rEntry.GetFlags() & SvTLEntryFlags::NO_NODEBMP)) &&
+ (nWindowStyle & WB_HASBUTTONS) && pFirstDynamicTab &&
+ (rEntry.HasChildren() || rEntry.HasChildrenOnDemand())))
+ return;
+
+ // find first tab and check if the node bitmap extends into it
+ sal_uInt16 nNextTab = nFirstDynTabPos;
+ SvLBoxTab* pNextTab;
+ do
+ {
+ nNextTab++;
+ pNextTab = nNextTab < nTabCount ? aTabs[nNextTab].get() : nullptr;
+ } while (pNextTab && pNextTab->IsDynamic());
+
+ if (pNextTab && (GetTabPos( &rEntry, pNextTab ) <= nDynTabPos))
+ return;
+
+ if (!((nWindowStyle & WB_HASBUTTONSATROOT) || pModel->GetDepth(&rEntry) > 0))
+ return;
+
+ Point aPos(GetTabPos(&rEntry, pFirstDynamicTab), nLine);
+ aPos.AdjustX(pImpl->m_nNodeBmpTabDistance );
+
+ const Image* pImg = nullptr;
+
+ const bool bExpanded = IsExpanded(&rEntry);
+ if (bExpanded)
+ pImg = &pImpl->GetExpandedNodeBmp();
+ else
+ pImg = &pImpl->GetCollapsedNodeBmp();
+ const bool bDefaultImage = bExpanded ? *pImg == GetDefaultExpandedNodeImage()
+ : *pImg == GetDefaultCollapsedNodeImage();
+ aPos.AdjustY((nTempEntryHeight - pImg->GetSizePixel().Height()) / 2 );
+
+ if (!bDefaultImage)
+ {
+ // If it's a custom image then draw what was explicitly set to use
+ DrawImageFlags nStyle = DrawImageFlags::NONE;
+ if (!IsEnabled())
+ nStyle |= DrawImageFlags::Disable;
+ rRenderContext.DrawImage(aPos, *pImg, nStyle);
+ }
+ else
+ {
+ bool bNativeOK = false;
+ // native
+ if (rRenderContext.IsNativeControlSupported(ControlType::ListNode, ControlPart::Entire))
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aCtrlRegion(aPos, pImg->GetSizePixel());
+ ControlState nState = ControlState::NONE;
+
+ if (IsEnabled())
+ nState |= ControlState::ENABLED;
+
+ if (bExpanded)
+ aControlValue.setTristateVal(ButtonValue::On); //expanded node
+ else
+ {
+ if ((!rEntry.HasChildren()) && rEntry.HasChildrenOnDemand() &&
+ (!(rEntry.GetFlags() & SvTLEntryFlags::HAD_CHILDREN)))
+ {
+ aControlValue.setTristateVal( ButtonValue::DontKnow ); //don't know
+ }
+ else
+ {
+ aControlValue.setTristateVal( ButtonValue::Off ); //collapsed node
+ }
+ }
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::ListNode, ControlPart::Entire, aCtrlRegion, nState, aControlValue, OUString());
+ }
+ if (!bNativeOK)
+ {
+ DecorationView aDecoView(&rRenderContext);
+ DrawSymbolFlags nSymbolStyle = DrawSymbolFlags::NONE;
+ if (!IsEnabled())
+ nSymbolStyle |= DrawSymbolFlags::Disable;
+
+ Color aCol = aBackupTextColor;
+ if (pViewDataEntry->IsHighlighted())
+ aCol = aHighlightTextColor;
+
+ SymbolType eSymbol = bExpanded ? SymbolType::SPIN_DOWN : SymbolType::SPIN_RIGHT;
+ aDecoView.DrawSymbol(tools::Rectangle(aPos, pImg->GetSizePixel()), eSymbol, aCol, nSymbolStyle);
+ }
+ }
+}
+
+void SvTreeListBox::DrawCustomEntry(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, const SvTreeListEntry& rEntry)
+{
+ aCustomRenderHdl.Call(std::tuple<vcl::RenderContext&, const tools::Rectangle&, const SvTreeListEntry&>(rRenderContext, rRect, rEntry));
+}
+
+Size SvTreeListBox::MeasureCustomEntry(vcl::RenderContext& rRenderContext, const SvTreeListEntry& rEntry) const
+{
+ return aCustomMeasureHdl.Call(std::pair<vcl::RenderContext&, const SvTreeListEntry&>(rRenderContext, rEntry));
+}
+
+tools::Rectangle SvTreeListBox::GetFocusRect(const SvTreeListEntry* pEntry, tools::Long nLine )
+{
+ pImpl->UpdateContextBmpWidthMax( pEntry );
+
+ Size aSize;
+ tools::Rectangle aRect;
+ aRect.SetTop( nLine );
+ aSize.setHeight( GetEntryHeight() );
+
+ tools::Long nRealWidth = pImpl->GetOutputSize().Width();
+ nRealWidth -= GetMapMode().GetOrigin().X();
+
+ sal_uInt16 nCurTab;
+ SvLBoxTab* pTab = GetFirstTab( SvLBoxTabFlags::SHOW_SELECTION, nCurTab );
+ tools::Long nTabPos = 0;
+ if( pTab )
+ nTabPos = GetTabPos( pEntry, pTab );
+ tools::Long nNextTabPos;
+ if( pTab && nCurTab < aTabs.size() - 1 )
+ {
+ SvLBoxTab* pNextTab = aTabs[ nCurTab + 1 ].get();
+ nNextTabPos = GetTabPos( pEntry, pNextTab );
+ }
+ else
+ {
+ nNextTabPos = nRealWidth;
+ if( nTabPos > nRealWidth )
+ nNextTabPos += 50;
+ }
+
+ bool bUserSelection = bool( nTreeFlags & SvTreeFlags::USESEL );
+ if( !bUserSelection )
+ {
+ if( pTab && nCurTab < pEntry->ItemCount() )
+ {
+ const SvLBoxItem& rItem = pEntry->GetItem( nCurTab );
+ aSize.setWidth(rItem.GetWidth(this, pEntry));
+ if( !aSize.Width() )
+ aSize.setWidth( 15 );
+ tools::Long nX = nTabPos; //GetTabPos( pEntry, pTab );
+ // alignment
+ nX += pTab->CalcOffset( aSize.Width(), nNextTabPos - nTabPos );
+ aRect.SetLeft( nX );
+ // make sure that first and last letter aren't cut off slightly
+ aRect.SetSize( aSize );
+ if( aRect.Left() > 0 )
+ aRect.AdjustLeft( -1 );
+ aRect.AdjustRight( 1 );
+ }
+ }
+ else
+ {
+ // if SelTab != 0, we have to calculate also
+ if( nFocusWidth == -1 || nFirstSelTab )
+ {
+ SvLBoxTab* pLastTab = nullptr; // default to select whole width
+
+ sal_uInt16 nLastTab;
+ GetLastTab(SvLBoxTabFlags::SHOW_SELECTION,nLastTab);
+ nLastTab++;
+ if( nLastTab < aTabs.size() ) // is there another one?
+ pLastTab = aTabs[ nLastTab ].get();
+
+ aSize.setWidth( pLastTab ? pLastTab->GetPos() : 0x0fffffff );
+ nFocusWidth = static_cast<short>(aSize.Width());
+ if( pTab )
+ nFocusWidth = nFocusWidth - static_cast<short>(nTabPos); //pTab->GetPos();
+ }
+ else
+ {
+ aSize.setWidth( nFocusWidth );
+ if( pTab )
+ {
+ if( nCurTab )
+ aSize.AdjustWidth(nTabPos );
+ else
+ aSize.AdjustWidth(pTab->GetPos() ); // Tab0 always from the leftmost position
+ }
+ }
+ // if selection starts with 0th tab, draw from column 0 on
+ if( nCurTab != 0 )
+ {
+ aRect.SetLeft( nTabPos );
+ aSize.AdjustWidth( -nTabPos );
+ }
+ aRect.SetSize( aSize );
+ }
+ // adjust right edge because of clipping
+ if( aRect.Right() >= nRealWidth )
+ {
+ aRect.SetRight( nRealWidth-1 );
+ nFocusWidth = static_cast<short>(aRect.GetWidth());
+ }
+ return aRect;
+}
+
+sal_IntPtr SvTreeListBox::GetTabPos(const SvTreeListEntry* pEntry, const SvLBoxTab* pTab) const
+{
+ assert(pTab);
+ sal_IntPtr nPos = pTab->GetPos();
+ if( pTab->IsDynamic() )
+ {
+ sal_uInt16 nDepth = pModel->GetDepth( pEntry );
+ nDepth = nDepth * static_cast<sal_uInt16>(nIndent);
+ nPos += static_cast<sal_IntPtr>(nDepth);
+ }
+ return nPos + (pEntry->GetExtraIndent() * nIndent);
+}
+
+SvLBoxItem* SvTreeListBox::GetItem_Impl( SvTreeListEntry* pEntry, tools::Long nX,
+ SvLBoxTab** ppTab )
+{
+ SvLBoxItem* pItemClicked = nullptr;
+ sal_uInt16 nTabCount = aTabs.size();
+ sal_uInt16 nItemCount = pEntry->ItemCount();
+ SvLBoxTab* pTab = aTabs.front().get();
+ SvLBoxItem* pItem = &pEntry->GetItem(0);
+ sal_uInt16 nNextItem = 1;
+ nX -= GetMapMode().GetOrigin().X();
+ tools::Long nRealWidth = pImpl->GetOutputSize().Width();
+ nRealWidth -= GetMapMode().GetOrigin().X();
+
+ while( true )
+ {
+ SvLBoxTab* pNextTab=nNextItem<nTabCount ? aTabs[nNextItem].get() : nullptr;
+ tools::Long nStart = GetTabPos( pEntry, pTab );
+
+ tools::Long nNextTabPos;
+ if( pNextTab )
+ nNextTabPos = GetTabPos( pEntry, pNextTab );
+ else
+ {
+ nNextTabPos = nRealWidth;
+ if( nStart > nRealWidth )
+ nNextTabPos += 50;
+ }
+
+ auto nItemWidth(pItem->GetWidth(this, pEntry));
+ nStart += pTab->CalcOffset(nItemWidth, nNextTabPos - nStart);
+ auto nLen = nItemWidth;
+ if( pNextTab )
+ {
+ tools::Long nTabWidth = GetTabPos( pEntry, pNextTab ) - nStart;
+ if( nTabWidth < nLen )
+ nLen = nTabWidth;
+ }
+
+ if( nX >= nStart && nX < (nStart+nLen ) )
+ {
+ pItemClicked = pItem;
+ if( ppTab )
+ {
+ *ppTab = pTab;
+ break;
+ }
+ }
+ if( nNextItem >= nItemCount || nNextItem >= nTabCount)
+ break;
+ pTab = aTabs[ nNextItem ].get();
+ pItem = &pEntry->GetItem( nNextItem );
+ nNextItem++;
+ }
+ return pItemClicked;
+}
+
+std::pair<tools::Long, tools::Long> SvTreeListBox::GetItemPos(SvTreeListEntry* pEntry, sal_uInt16 nTabIdx)
+{
+ sal_uInt16 nTabCount = aTabs.size();
+ sal_uInt16 nItemCount = pEntry->ItemCount();
+ if (nTabIdx >= nItemCount || nTabIdx >= nTabCount)
+ return std::make_pair(-1, -1);
+
+ SvLBoxTab* pTab = aTabs.front().get();
+ SvLBoxItem* pItem = &pEntry->GetItem(nTabIdx);
+ sal_uInt16 nNextItem = nTabIdx + 1;
+
+ tools::Long nRealWidth = pImpl->GetOutputSize().Width();
+ nRealWidth -= GetMapMode().GetOrigin().X();
+
+ SvLBoxTab* pNextTab = nNextItem < nTabCount ? aTabs[nNextItem].get() : nullptr;
+ tools::Long nStart = GetTabPos(pEntry, pTab);
+
+ tools::Long nNextTabPos;
+ if (pNextTab)
+ nNextTabPos = GetTabPos(pEntry, pNextTab);
+ else
+ {
+ nNextTabPos = nRealWidth;
+ if (nStart > nRealWidth)
+ nNextTabPos += 50;
+ }
+
+ auto nItemWidth(pItem->GetWidth(this, pEntry));
+ nStart += pTab->CalcOffset(nItemWidth, nNextTabPos - nStart);
+ auto nLen = nItemWidth;
+ if (pNextTab)
+ {
+ tools::Long nTabWidth = GetTabPos(pEntry, pNextTab) - nStart;
+ if (nTabWidth < nLen)
+ nLen = nTabWidth;
+ }
+ return std::make_pair(nStart, nLen);
+}
+
+tools::Long SvTreeListBox::getPreferredDimensions(std::vector<tools::Long> &rWidths) const
+{
+ tools::Long nHeight = 0;
+ rWidths.clear();
+ SvTreeListEntry* pEntry = First();
+ while (pEntry)
+ {
+ sal_uInt16 nCount = pEntry->ItemCount();
+ sal_uInt16 nCurPos = 0;
+ if (nCount > rWidths.size())
+ rWidths.resize(nCount);
+ while (nCurPos < nCount)
+ {
+ SvLBoxItem& rItem = pEntry->GetItem( nCurPos );
+ auto nWidth = rItem.GetWidth(this, pEntry);
+ if (nWidth)
+ {
+ nWidth += SV_TAB_BORDER * 2;
+ if (nWidth > rWidths[nCurPos])
+ rWidths[nCurPos] = nWidth;
+ }
+ ++nCurPos;
+ }
+ pEntry = Next( pEntry );
+ nHeight += GetEntryHeight();
+ }
+ return nHeight;
+}
+
+Size SvTreeListBox::GetOptimalSize() const
+{
+ std::vector<tools::Long> aWidths;
+ Size aRet(0, getPreferredDimensions(aWidths));
+ for (tools::Long aWidth : aWidths)
+ aRet.AdjustWidth(aWidth );
+
+ sal_Int32 nLeftBorder(0), nTopBorder(0), nRightBorder(0), nBottomBorder(0);
+ GetBorder(nLeftBorder, nTopBorder, nRightBorder, nBottomBorder);
+ aRet.AdjustWidth(nLeftBorder + nRightBorder);
+ aRet.AdjustHeight(nTopBorder + nBottomBorder);
+
+ tools::Long nMinWidth = nMinWidthInChars * approximate_char_width();
+ aRet.setWidth( std::max(aRet.Width(), nMinWidth) );
+
+ if (GetStyle() & WB_VSCROLL)
+ aRet.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize());
+
+ return aRet;
+}
+
+void SvTreeListBox::SetAlternatingRowColors( bool bEnable )
+{
+ if( !mbUpdateAlternatingRows )
+ {
+ mbAlternatingRowColors = bEnable;
+ return;
+ }
+
+ if( bEnable )
+ {
+ SvTreeListEntry* pEntry = pModel->First();
+ for(size_t i = 0; pEntry; ++i)
+ {
+ pEntry->SetBackColor( i % 2 == 0 ? GetBackground().GetColor() : GetSettings().GetStyleSettings().GetAlternatingRowColor());
+ SvTreeListEntry *pNextEntry = nullptr;
+ if( IsExpanded( pEntry ) )
+ pNextEntry = pModel->FirstChild( pEntry );
+ else
+ pNextEntry = pEntry->NextSibling();
+
+ if( !pNextEntry )
+ pEntry = pModel->Next( pEntry );
+ else
+ pEntry = pNextEntry;
+ }
+ }
+ else if( mbAlternatingRowColors )
+ for(SvTreeListEntry* pEntry = pModel->First(); pEntry; pEntry = pModel->Next(pEntry))
+ pEntry->SetBackColor( GetBackground().GetColor() );
+
+ mbAlternatingRowColors = bEnable;
+ pImpl->UpdateAll(true);
+}
+
+void SvTreeListBox::SetForceMakeVisible( bool bEnable )
+{
+ pImpl->SetForceMakeVisible(bEnable);
+}
+
+SvLBoxItem* SvTreeListBox::GetItem(SvTreeListEntry* pEntry,tools::Long nX,SvLBoxTab** ppTab)
+{
+ return GetItem_Impl( pEntry, nX, ppTab );
+}
+
+SvLBoxItem* SvTreeListBox::GetItem(SvTreeListEntry* pEntry,tools::Long nX )
+{
+ SvLBoxTab* pDummyTab;
+ return GetItem_Impl( pEntry, nX, &pDummyTab );
+}
+
+void SvTreeListBox::AddTab(tools::Long nTabPos, SvLBoxTabFlags nFlags )
+{
+ nFocusWidth = -1;
+ SvLBoxTab* pTab = new SvLBoxTab( nTabPos, nFlags );
+ aTabs.emplace_back( pTab );
+ if( nTreeFlags & SvTreeFlags::USESEL )
+ {
+ sal_uInt16 nPos = aTabs.size() - 1;
+ if( nPos >= nFirstSelTab && nPos <= nLastSelTab )
+ pTab->nFlags |= SvLBoxTabFlags::SHOW_SELECTION;
+ else
+ // string items usually have to be selected -- turn this off
+ // explicitly
+ pTab->nFlags &= ~SvLBoxTabFlags::SHOW_SELECTION;
+ }
+}
+
+
+SvLBoxTab* SvTreeListBox::GetFirstDynamicTab( sal_uInt16& rPos ) const
+{
+ sal_uInt16 nCurTab = 0;
+ sal_uInt16 nTabCount = aTabs.size();
+ while( nCurTab < nTabCount )
+ {
+ SvLBoxTab* pTab = aTabs[nCurTab].get();
+ if( pTab->nFlags & SvLBoxTabFlags::DYNAMIC )
+ {
+ rPos = nCurTab;
+ return pTab;
+ }
+ nCurTab++;
+ }
+ return nullptr;
+}
+
+SvLBoxTab* SvTreeListBox::GetFirstDynamicTab() const
+{
+ sal_uInt16 nDummy;
+ return GetFirstDynamicTab( nDummy );
+}
+
+SvLBoxTab* SvTreeListBox::GetTab( SvTreeListEntry const * pEntry, SvLBoxItem const * pItem) const
+{
+ sal_uInt16 nPos = pEntry->GetPos( pItem );
+ return aTabs[ nPos ].get();
+}
+
+void SvTreeListBox::ClearTabList()
+{
+ aTabs.clear();
+}
+
+
+Size SvTreeListBox::GetOutputSizePixel() const
+{
+ Size aSize = pImpl->GetOutputSize();
+ return aSize;
+}
+
+void SvTreeListBox::NotifyScrolled()
+{
+ aScrolledHdl.Call( this );
+}
+
+void SvTreeListBox::Invalidate( InvalidateFlags nInvalidateFlags )
+{
+ if (!pImpl)
+ return;
+ if( nFocusWidth == -1 )
+ // to make sure that the control doesn't show the wrong focus rectangle
+ // after painting
+ pImpl->RecalcFocusRect();
+ Control::Invalidate( nInvalidateFlags );
+ pImpl->Invalidate();
+}
+
+void SvTreeListBox::Invalidate( const tools::Rectangle& rRect, InvalidateFlags nInvalidateFlags )
+{
+ if( nFocusWidth == -1 )
+ // to make sure that the control doesn't show the wrong focus rectangle
+ // after painting
+ pImpl->RecalcFocusRect();
+ Control::Invalidate( rRect, nInvalidateFlags );
+}
+
+
+void SvTreeListBox::SetHighlightRange( sal_uInt16 nStart, sal_uInt16 nEnd)
+{
+
+ nTreeFlags |= SvTreeFlags::USESEL;
+ if( nStart > nEnd )
+ std::swap(nStart, nEnd);
+ // select all tabs that lie within the area
+ nTreeFlags |= SvTreeFlags::RECALCTABS;
+ nFirstSelTab = nStart;
+ nLastSelTab = nEnd;
+ pImpl->RecalcFocusRect();
+}
+
+void SvTreeListBox::Command(const CommandEvent& rCEvt)
+{
+ if (!aPopupMenuHdl.Call(rCEvt))
+ pImpl->Command(rCEvt);
+ //pass at least alt press/release to parent impl
+ if (rCEvt.GetCommand() == CommandEventId::ModKeyChange)
+ Control::Command(rCEvt);
+}
+
+SvLBoxTab* SvTreeListBox::GetFirstTab( SvLBoxTabFlags nFlagMask, sal_uInt16& rPos )
+{
+ sal_uInt16 nTabCount = aTabs.size();
+ for( sal_uInt16 nPos = 0; nPos < nTabCount; nPos++ )
+ {
+ SvLBoxTab* pTab = aTabs[ nPos ].get();
+ if( pTab->nFlags & nFlagMask )
+ {
+ rPos = nPos;
+ return pTab;
+ }
+ }
+ rPos = 0xffff;
+ return nullptr;
+}
+
+void SvTreeListBox::GetLastTab( SvLBoxTabFlags nFlagMask, sal_uInt16& rTabPos )
+{
+ sal_uInt16 nPos = static_cast<sal_uInt16>(aTabs.size());
+ while( nPos )
+ {
+ --nPos;
+ SvLBoxTab* pTab = aTabs[ nPos ].get();
+ if( pTab->nFlags & nFlagMask )
+ {
+ rTabPos = nPos;
+ return;
+ }
+ }
+ rTabPos = 0xffff;
+}
+
+void SvTreeListBox::RequestHelp( const HelpEvent& rHEvt )
+{
+ if (aTooltipHdl.IsSet() && aTooltipHdl.Call(rHEvt))
+ return;
+
+ if( !pImpl->RequestHelp( rHEvt ) )
+ Control::RequestHelp( rHEvt );
+}
+
+sal_Int32 SvTreeListBox::DefaultCompare(const SvLBoxString* pLeftText, const SvLBoxString* pRightText)
+{
+ OUString aLeft = pLeftText ? pLeftText->GetText() : OUString();
+ OUString aRight = pRightText ? pRightText->GetText() : OUString();
+ pImpl->UpdateStringSorter();
+ return pImpl->m_pStringSorter->compare(aLeft, aRight);
+}
+
+IMPL_LINK( SvTreeListBox, DefaultCompare, const SvSortData&, rData, sal_Int32 )
+{
+ const SvTreeListEntry* pLeft = rData.pLeft;
+ const SvTreeListEntry* pRight = rData.pRight;
+ const SvLBoxString* pLeftText = static_cast<const SvLBoxString*>(pLeft->GetFirstItem(SvLBoxItemType::String));
+ const SvLBoxString* pRightText = static_cast<const SvLBoxString*>(pRight->GetFirstItem(SvLBoxItemType::String));
+ return DefaultCompare(pLeftText, pRightText);
+}
+
+void SvTreeListBox::ModelNotification( SvListAction nActionId, SvTreeListEntry* pEntry1,
+ SvTreeListEntry* pEntry2, sal_uInt32 nPos )
+{
+ SolarMutexGuard aSolarGuard;
+
+ if( nActionId == SvListAction::CLEARING )
+ CancelTextEditing();
+
+ SvListView::ModelNotification( nActionId, pEntry1, pEntry2, nPos );
+ switch( nActionId )
+ {
+ case SvListAction::INSERTED:
+ {
+ SvLBoxContextBmp* pBmpItem = static_cast< SvLBoxContextBmp* >( pEntry1->GetFirstItem( SvLBoxItemType::ContextBmp ) );
+ if ( !pBmpItem )
+ break;
+ const Image& rBitmap1( pBmpItem->GetBitmap1() );
+ const Image& rBitmap2( pBmpItem->GetBitmap2() );
+ short nMaxWidth = short( std::max( rBitmap1.GetSizePixel().Width(), rBitmap2.GetSizePixel().Width() ) );
+ nMaxWidth = pImpl->UpdateContextBmpWidthVector( pEntry1, nMaxWidth );
+ if( nMaxWidth > nContextBmpWidthMax )
+ {
+ nContextBmpWidthMax = nMaxWidth;
+ SetTabs();
+ }
+ if (get_width_request() == -1)
+ queue_resize();
+ }
+ break;
+
+ case SvListAction::RESORTING:
+ SetUpdateMode( false );
+ break;
+
+ case SvListAction::RESORTED:
+ // after a selection: show first entry and also keep the selection
+ MakeVisible( pModel->First(), true );
+ SetUpdateMode( true );
+ break;
+
+ case SvListAction::CLEARED:
+ if( IsUpdateMode() )
+ PaintImmediately();
+ break;
+
+ default: break;
+ }
+}
+
+SvTreeListEntry* SvTreeListBox::GetFirstEntryInView() const
+{
+ return GetEntry( Point() );
+}
+
+SvTreeListEntry* SvTreeListBox::GetNextEntryInView(SvTreeListEntry* pEntry ) const
+{
+ SvTreeListEntry* pNext = NextVisible( pEntry );
+ if( pNext )
+ {
+ Point aPos( GetEntryPosition(pNext) );
+ const Size& rSize = pImpl->GetOutputSize();
+ if( aPos.Y() < 0 || aPos.Y() >= rSize.Height() )
+ return nullptr;
+ }
+ return pNext;
+}
+
+
+void SvTreeListBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ if( (rDCEvt.GetType()==DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ nEntryHeight = 0; // _together_ with true of 1. par (bFont) of InitSettings() a zero-height
+ // forces complete recalc of heights!
+ InitSettings();
+ Invalidate();
+ }
+ else
+ Control::DataChanged( rDCEvt );
+}
+
+void SvTreeListBox::StateChanged( StateChangedType eType )
+{
+ if( eType == StateChangedType::Enable )
+ Invalidate( InvalidateFlags::Children );
+
+ Control::StateChanged( eType );
+
+ if ( eType == StateChangedType::Style )
+ ImplInitStyle();
+}
+
+void SvTreeListBox::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ SetPointFont(rRenderContext, GetPointFont(*GetOutDev()));
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
+ rRenderContext.SetTextFillColor();
+ rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
+
+ // always try to re-create default-SvLBoxButtonData
+ if (pCheckButtonData && pCheckButtonData->HasDefaultImages())
+ pCheckButtonData->SetDefaultImages(this);
+}
+
+void SvTreeListBox::InitSettings()
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ vcl::Font aFont = rStyleSettings.GetFieldFont();
+ SetPointFont(*GetOutDev(), aFont);
+ AdjustEntryHeightAndRecalc();
+
+ SetTextColor(rStyleSettings.GetFieldTextColor());
+ SetTextFillColor();
+
+ SetBackground(rStyleSettings.GetFieldColor());
+
+ // always try to re-create default-SvLBoxButtonData
+ if( pCheckButtonData && pCheckButtonData->HasDefaultImages() )
+ pCheckButtonData->SetDefaultImages(this);
+}
+
+css::uno::Reference< XAccessible > SvTreeListBox::CreateAccessible()
+{
+ vcl::Window* pParent = GetAccessibleParentWindow();
+ DBG_ASSERT( pParent, "SvTreeListBox::CreateAccessible - accessible parent not found" );
+
+ css::uno::Reference< XAccessible > xAccessible;
+ if ( pParent )
+ {
+ css::uno::Reference< XAccessible > xAccParent = pParent->GetAccessible();
+ if ( xAccParent.is() )
+ {
+ // need to be done here to get the vclxwindow later on in the accessible
+ css::uno::Reference< css::awt::XWindowPeer > xHoldAlive(GetComponentInterface());
+ xAccessible = pImpl->m_aFactoryAccess.getFactory().createAccessibleTreeListBox( *this, xAccParent );
+ }
+ }
+ return xAccessible;
+}
+
+void SvTreeListBox::FillAccessibleEntryStateSet( SvTreeListEntry* pEntry, ::utl::AccessibleStateSetHelper& rStateSet ) const
+{
+ assert(pEntry && "SvTreeListBox::FillAccessibleEntryStateSet: invalid entry");
+
+ if ( pEntry->HasChildrenOnDemand() || pEntry->HasChildren() )
+ {
+ rStateSet.AddState( AccessibleStateType::EXPANDABLE );
+ if ( IsExpanded( pEntry ) )
+ rStateSet.AddState( sal_Int16(AccessibleStateType::EXPANDED) );
+ }
+
+ if ( GetCheckButtonState( pEntry ) == SvButtonState::Checked )
+ rStateSet.AddState( AccessibleStateType::CHECKED );
+ if ( IsEntryVisible( pEntry ) )
+ rStateSet.AddState( AccessibleStateType::VISIBLE );
+ if ( IsSelected( pEntry ) )
+ rStateSet.AddState( AccessibleStateType::SELECTED );
+ if ( IsEnabled() )
+ {
+ rStateSet.AddState( AccessibleStateType::ENABLED );
+ rStateSet.AddState( AccessibleStateType::FOCUSABLE );
+ rStateSet.AddState( AccessibleStateType::SELECTABLE );
+ SvViewDataEntry* pViewDataNewCur = GetViewDataEntry(pEntry);
+ if (pViewDataNewCur && pViewDataNewCur->HasFocus())
+ rStateSet.AddState( AccessibleStateType::FOCUSED );
+ }
+}
+
+OUString SvTreeListBox::GetEntryAccessibleDescription(SvTreeListEntry* pEntry) const
+{
+ assert(pEntry);
+
+ //want to count the real column number in the list box.
+ sal_uInt16 iRealItemCount = 0;
+ for (size_t i = 0; i < pEntry->ItemCount(); ++i)
+ {
+ const SvLBoxItem& rItem = pEntry->GetItem(i);
+ if (rItem.GetType() == SvLBoxItemType::String &&
+ !static_cast<const SvLBoxString&>(rItem).GetText().isEmpty())
+ {
+ iRealItemCount++;
+ }
+ }
+ // No idea why <= 1; that was in AccessibleListBoxEntry::getAccessibleDescription
+ // since the "Integrate branch of IAccessible2" commit
+ if (iRealItemCount <= 1)
+ {
+ return {};
+ }
+ else
+ {
+ return SearchEntryTextWithHeadTitle(pEntry);
+ }
+}
+
+tools::Rectangle SvTreeListBox::GetBoundingRect(const SvTreeListEntry* pEntry)
+{
+ Point aPos = GetEntryPosition( pEntry );
+ tools::Rectangle aRect = GetFocusRect( pEntry, aPos.Y() );
+ return aRect;
+}
+
+void SvTreeListBox::CallImplEventListeners(VclEventId nEvent, void* pData)
+{
+ CallEventListeners(nEvent, pData);
+}
+
+void SvTreeListBox::set_min_width_in_chars(sal_Int32 nChars)
+{
+ nMinWidthInChars = nChars;
+ queue_resize();
+}
+
+bool SvTreeListBox::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "min-width-chars")
+ {
+ set_min_width_in_chars(rValue.toInt32());
+ }
+ else if (rKey == "enable-tree-lines")
+ {
+ auto nStyle = GetStyle();
+ nStyle &= ~(WB_HASLINES | WB_HASLINESATROOT);
+ if (toBool(rValue))
+ nStyle |= (WB_HASLINES | WB_HASLINESATROOT);
+ SetStyle(nStyle);
+ }
+ else if (rKey == "show-expanders")
+ {
+ auto nStyle = GetStyle();
+ nStyle &= ~(WB_HASBUTTONS | WB_HASBUTTONSATROOT);
+ if (toBool(rValue))
+ nStyle |= (WB_HASBUTTONS | WB_HASBUTTONSATROOT);
+ SetStyle(nStyle);
+ }
+ else if (rKey == "enable-search")
+ {
+ SetQuickSearch(toBool(rValue));
+ }
+ else if (rKey == "activate-on-single-click")
+ {
+ SetActivateOnSingleClick(toBool(rValue));
+ }
+ else if (rKey == "hover-selection")
+ {
+ SetHoverSelection(toBool(rValue));
+ }
+ else if (rKey == "reorderable")
+ {
+ if (toBool(rValue))
+ SetDragDropMode(DragDropMode::CTRL_MOVE | DragDropMode::ENABLE_TOP);
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+void SvTreeListBox::EnableRTL(bool bEnable)
+{
+ Control::EnableRTL(bEnable);
+ pImpl->m_aHorSBar->EnableRTL(bEnable);
+ pImpl->m_aVerSBar->EnableRTL(bEnable);
+ pImpl->m_aScrBarBox->EnableRTL(bEnable);
+}
+
+FactoryFunction SvTreeListBox::GetUITestFactory() const
+{
+ return TreeListUIObject::create;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/treelistentry.cxx b/vcl/source/treelist/treelistentry.cxx
new file mode 100644
index 000000000..c369b7332
--- /dev/null
+++ b/vcl/source/treelist/treelistentry.cxx
@@ -0,0 +1,235 @@
+/* -*- 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 <memory>
+#include <vcl/toolkit/treelistentry.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <tools/debug.hxx>
+
+void SvTreeListEntry::ClearChildren()
+{
+ m_Children.clear();
+}
+
+void SvTreeListEntry::SetListPositions()
+{
+ sal_uInt32 nCur = 0;
+ for (auto const& pEntry : m_Children)
+ {
+ SvTreeListEntry& rEntry = *pEntry;
+ rEntry.nListPos &= 0x80000000;
+ rEntry.nListPos |= nCur;
+ ++nCur;
+ }
+
+ nListPos &= (~0x80000000); // remove the invalid bit.
+}
+
+void SvTreeListEntry::InvalidateChildrensListPositions()
+{
+ nListPos |= 0x80000000;
+}
+
+SvTreeListEntry::SvTreeListEntry()
+ : pParent(nullptr)
+ , nAbsPos(0)
+ , nListPos(0)
+ , mnExtraIndent(0)
+ , pUserData(nullptr)
+ , nEntryFlags(SvTLEntryFlags::NONE)
+ , maBackColor(Application::GetSettings().GetStyleSettings().GetWindowColor())
+{
+}
+
+SvTreeListEntry::~SvTreeListEntry()
+{
+#ifdef DBG_UTIL
+ pParent = nullptr;
+#endif
+
+ m_Children.clear();
+ m_Items.clear();
+}
+
+bool SvTreeListEntry::HasChildren() const
+{
+ return !m_Children.empty();
+}
+
+bool SvTreeListEntry::HasChildListPos() const
+{
+ return pParent && !(pParent->nListPos & 0x80000000);
+}
+
+sal_uInt32 SvTreeListEntry::GetChildListPos() const
+{
+ if( pParent && (pParent->nListPos & 0x80000000) )
+ pParent->SetListPositions();
+ return ( nListPos & 0x7fffffff );
+}
+
+
+void SvTreeListEntry::Clone(SvTreeListEntry* pSource)
+{
+ nListPos &= 0x80000000;
+ nListPos |= ( pSource->nListPos & 0x7fffffff);
+ nAbsPos = pSource->nAbsPos;
+ mnExtraIndent = pSource->mnExtraIndent;
+
+ m_Items.clear();
+ for (auto const& it : pSource->m_Items)
+ {
+ SvLBoxItem* pItem = &(*it);
+ std::unique_ptr<SvLBoxItem> pNewItem(pItem->Clone(pItem));
+ m_Items.push_back(std::move(pNewItem));
+ }
+
+ pUserData = pSource->GetUserData();
+ nEntryFlags = pSource->nEntryFlags;
+}
+
+size_t SvTreeListEntry::ItemCount() const
+{
+ return m_Items.size();
+}
+
+void SvTreeListEntry::AddItem(std::unique_ptr<SvLBoxItem> pItem)
+{
+ m_Items.push_back(std::move(pItem));
+}
+
+void SvTreeListEntry::EnableChildrenOnDemand( bool bEnable )
+{
+ if ( bEnable )
+ nEntryFlags |= SvTLEntryFlags::CHILDREN_ON_DEMAND;
+ else
+ nEntryFlags &= ~SvTLEntryFlags::CHILDREN_ON_DEMAND;
+}
+
+void SvTreeListEntry::ReplaceItem(std::unique_ptr<SvLBoxItem> pNewItem, size_t const nPos)
+{
+ DBG_ASSERT(pNewItem,"ReplaceItem:No Item");
+ if (nPos >= m_Items.size())
+ {
+ // Out of bound. Bail out.
+ pNewItem.reset();
+ return;
+ }
+
+ m_Items.erase(m_Items.begin()+nPos);
+ m_Items.insert(m_Items.begin()+nPos, std::move(pNewItem));
+}
+
+const SvLBoxItem& SvTreeListEntry::GetItem( size_t nPos ) const
+{
+ return *m_Items[nPos];
+}
+
+SvLBoxItem& SvTreeListEntry::GetItem( size_t nPos )
+{
+ return *m_Items[nPos];
+}
+
+namespace {
+
+class FindByType
+{
+ SvLBoxItemType meType;
+public:
+ explicit FindByType(SvLBoxItemType eType) : meType(eType) {}
+ bool operator() (const std::unique_ptr<SvLBoxItem>& rpItem) const
+ {
+ return rpItem->GetType() == meType;
+ }
+};
+
+class FindByPointer
+{
+ const SvLBoxItem* mpItem;
+public:
+ explicit FindByPointer(const SvLBoxItem* p) : mpItem(p) {}
+ bool operator() (const std::unique_ptr<SvLBoxItem>& rpItem) const
+ {
+ return rpItem.get() == mpItem;
+ }
+};
+
+}
+
+const SvLBoxItem* SvTreeListEntry::GetFirstItem(SvLBoxItemType eType) const
+{
+ ItemsType::const_iterator it = std::find_if(m_Items.begin(), m_Items.end(), FindByType(eType));
+ return (it == m_Items.end()) ? nullptr : (*it).get();
+}
+
+SvLBoxItem* SvTreeListEntry::GetFirstItem(SvLBoxItemType eType)
+{
+ ItemsType::iterator it = std::find_if(m_Items.begin(), m_Items.end(), FindByType(eType));
+ return (it == m_Items.end()) ? nullptr : (*it).get();
+}
+
+size_t SvTreeListEntry::GetPos( const SvLBoxItem* pItem ) const
+{
+ ItemsType::const_iterator it = std::find_if(m_Items.begin(), m_Items.end(), FindByPointer(pItem));
+ return it == m_Items.end() ? ITEM_NOT_FOUND : std::distance(m_Items.begin(), it);
+}
+
+
+void SvTreeListEntry::SetUserData( void* pPtr )
+{
+ pUserData = pPtr;
+}
+
+bool SvTreeListEntry::HasChildrenOnDemand() const
+{
+ return static_cast<bool>(nEntryFlags & SvTLEntryFlags::CHILDREN_ON_DEMAND);
+}
+
+void SvTreeListEntry::SetFlags( SvTLEntryFlags nFlags )
+{
+ nEntryFlags = nFlags;
+}
+
+SvTreeListEntry* SvTreeListEntry::NextSibling() const
+{
+ SvTreeListEntries& rList = pParent->m_Children;
+ sal_uInt32 nPos = GetChildListPos();
+ nPos++;
+ return (nPos < rList.size()) ? rList[nPos].get() : nullptr;
+}
+
+SvTreeListEntry* SvTreeListEntry::PrevSibling() const
+{
+ SvTreeListEntries& rList = pParent->m_Children;
+ sal_uInt32 nPos = GetChildListPos();
+ if ( nPos == 0 )
+ return nullptr;
+ nPos--;
+ return rList[nPos].get();
+}
+
+
+SvTreeListEntry* SvTreeListEntry::LastSibling() const
+{
+ SvTreeListEntries& rChildren = pParent->m_Children;
+ return (rChildren.empty()) ? nullptr : rChildren.back().get();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/uiobject.cxx b/vcl/source/treelist/uiobject.cxx
new file mode 100644
index 000000000..ccc066804
--- /dev/null
+++ b/vcl/source/treelist/uiobject.cxx
@@ -0,0 +1,185 @@
+/* -*- 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/.
+ */
+
+#include <memory>
+#include <vcl/toolkit/svlbitm.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/toolkit/treelistbox.hxx>
+#include <vcl/toolkit/treelistentry.hxx>
+
+TreeListUIObject::TreeListUIObject(const VclPtr<SvTreeListBox>& xTreeList):
+ WindowUIObject(xTreeList),
+ mxTreeList(xTreeList)
+{
+}
+
+namespace {
+
+bool isCheckBoxList(const VclPtr<SvTreeListBox>& xTreeList)
+{
+ return (xTreeList->GetTreeFlags() & SvTreeFlags::CHKBTN) == SvTreeFlags::CHKBTN;
+}
+
+}
+
+StringMap TreeListUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+
+ aMap["SelectionCount"] = OUString::number(mxTreeList->GetSelectionCount());
+ aMap["VisibleCount"] = OUString::number(mxTreeList->GetVisibleCount());
+ aMap["Children"] = OUString::number(mxTreeList->GetChildCount(nullptr));
+ aMap["LevelChildren"] = OUString::number(mxTreeList->GetLevelChildCount(nullptr));
+ aMap["CheckBoxList"] = OUString::boolean(isCheckBoxList(mxTreeList));
+ SvTreeListEntry* pEntry = mxTreeList->FirstSelected();
+ aMap["SelectEntryText"] = pEntry ? mxTreeList->GetEntryText(pEntry) : OUString();
+
+ return aMap;
+}
+
+void TreeListUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction.isEmpty())
+ {
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+std::unique_ptr<UIObject> TreeListUIObject::get_child(const OUString& rID)
+{
+ sal_Int32 nID = rID.toInt32();
+ if (nID >= 0)
+ {
+ SvTreeListEntry* pEntry = mxTreeList->GetEntry(nullptr, nID);
+ if (!pEntry)
+ return nullptr;
+
+ return std::unique_ptr<UIObject>(new TreeListEntryUIObject(mxTreeList, pEntry));
+ }
+
+ return nullptr;
+}
+
+std::set<OUString> TreeListUIObject::get_children() const
+{
+ std::set<OUString> aChildren;
+
+ size_t nChildren = mxTreeList->GetLevelChildCount(nullptr);
+ for (size_t i = 0; i < nChildren; ++i)
+ {
+ aChildren.insert(OUString::number(i));
+ }
+
+ return aChildren;
+}
+
+OUString TreeListUIObject::get_name() const
+{
+ return "TreeListUIObject";
+}
+
+std::unique_ptr<UIObject> TreeListUIObject::create(vcl::Window* pWindow)
+{
+ SvTreeListBox* pTreeList = dynamic_cast<SvTreeListBox*>(pWindow);
+ assert(pTreeList);
+ return std::unique_ptr<UIObject>(new TreeListUIObject(pTreeList));
+}
+
+TreeListEntryUIObject::TreeListEntryUIObject(const VclPtr<SvTreeListBox>& xTreeList, SvTreeListEntry* pEntry):
+ mxTreeList(xTreeList),
+ mpEntry(pEntry)
+{
+}
+
+StringMap TreeListEntryUIObject::get_state()
+{
+ StringMap aMap;
+
+ aMap["Text"] = mxTreeList->GetEntryText(mpEntry);
+ aMap["Children"] = OUString::number(mxTreeList->GetLevelChildCount(mpEntry));
+ aMap["VisibleChildCount"] = OUString::number(mxTreeList->GetVisibleChildCount(mpEntry));
+ aMap["IsSelected"] = OUString::boolean(mxTreeList->IsSelected(mpEntry));
+
+ aMap["IsSemiTransparent"] = OUString::boolean(bool(mpEntry->GetFlags() & SvTLEntryFlags::SEMITRANSPARENT));
+
+ SvLBoxButton* pItem = static_cast<SvLBoxButton*>(mpEntry->GetFirstItem(SvLBoxItemType::Button));
+ if (pItem)
+ aMap["IsChecked"] = OUString::boolean(pItem->IsStateChecked());
+
+ return aMap;
+}
+
+void TreeListEntryUIObject::execute(const OUString& rAction, const StringMap& /*rParameters*/)
+{
+ if (rAction == "COLLAPSE")
+ {
+ mxTreeList->Collapse(mpEntry);
+ }
+ else if (rAction == "EXPAND")
+ {
+ mxTreeList->Expand(mpEntry);
+ }
+ else if (rAction == "SELECT")
+ {
+ mxTreeList->Select(mpEntry);
+ }
+ else if (rAction == "DESELECT")
+ {
+ mxTreeList->Select(mpEntry, false);
+ }
+ else if (rAction == "CLICK")
+ {
+ SvLBoxButton* pItem = static_cast<SvLBoxButton*>(mpEntry->GetFirstItem(SvLBoxItemType::Button));
+ if (!pItem)
+ return;
+ pItem->ClickHdl(mpEntry);
+ }
+ else if (rAction == "DOUBLECLICK")
+ {
+ mxTreeList->Select(mpEntry);
+ mxTreeList->DoubleClickHdl();
+ }
+}
+
+std::unique_ptr<UIObject> TreeListEntryUIObject::get_child(const OUString& rID)
+{
+ sal_Int32 nID = rID.toInt32();
+ if (nID >= 0)
+ {
+ SvTreeListEntry* pEntry = mxTreeList->GetEntry(mpEntry, nID);
+ if (!pEntry)
+ return nullptr;
+
+ return std::unique_ptr<UIObject>(new TreeListEntryUIObject(mxTreeList, pEntry));
+ }
+
+ return nullptr;
+}
+
+std::set<OUString> TreeListEntryUIObject::get_children() const
+{
+ std::set<OUString> aChildren;
+
+ size_t nChildren = mxTreeList->GetLevelChildCount(mpEntry);
+ for (size_t i = 0; i < nChildren; ++i)
+ {
+ aChildren.insert(OUString::number(i));
+ }
+
+ return aChildren;
+}
+
+OUString TreeListEntryUIObject::get_type() const
+{
+ return "TreeListEntry";
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/treelist/viewdataentry.cxx b/vcl/source/treelist/viewdataentry.cxx
new file mode 100644
index 000000000..b14d47931
--- /dev/null
+++ b/vcl/source/treelist/viewdataentry.cxx
@@ -0,0 +1,87 @@
+/* -*- 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 <vcl/toolkit/viewdataentry.hxx>
+
+SvViewDataEntry::SvViewDataEntry() :
+ nVisPos(0),
+ mbSelected(false),
+ mbHighlighted(false),
+ mbExpanded(false),
+ mbFocused(false),
+ mbSelectable(true),
+ mbDragTarget(false)
+{
+}
+
+SvViewDataEntry::SvViewDataEntry( const SvViewDataEntry& rData ) :
+ nVisPos(rData.nVisPos),
+ mbSelected(false),
+ mbHighlighted(false),
+ mbExpanded(rData.mbExpanded),
+ mbFocused(false),
+ mbSelectable(rData.mbSelectable),
+ mbDragTarget(false)
+{
+}
+
+SvViewDataEntry::~SvViewDataEntry()
+{
+#ifdef DBG_UTIL
+ nVisPos = 0x12345678;
+#endif
+}
+
+void SvViewDataEntry::SetFocus( bool bFocus )
+{
+ mbFocused = bFocus;
+}
+
+void SvViewDataEntry::SetSelected( bool bSelected )
+{
+ mbSelected = bSelected;
+ mbHighlighted = bSelected;
+}
+
+void SvViewDataEntry::SetExpanded( bool bExpanded )
+{
+ mbExpanded = bExpanded;
+}
+
+void SvViewDataEntry::SetSelectable( bool bSelectable )
+{
+ mbSelectable = bSelectable;
+}
+
+void SvViewDataEntry::Init(size_t nSize)
+{
+ maItems.resize(nSize);
+}
+
+const SvViewDataItem& SvViewDataEntry::GetItem(size_t nPos) const
+{
+ return maItems[nPos];
+}
+
+SvViewDataItem& SvViewDataEntry::GetItem(size_t nPos)
+{
+ return maItems[nPos];
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/uitest/logger.cxx b/vcl/source/uitest/logger.cxx
new file mode 100644
index 000000000..29520fd61
--- /dev/null
+++ b/vcl/source/uitest/logger.cxx
@@ -0,0 +1,624 @@
+/* -*- 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/.
+ */
+
+#include <config_folders.h>
+
+#include <vcl/uitest/logger.hxx>
+
+#include <rtl/bootstrap.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <osl/file.hxx>
+#include <vcl/ctrl.hxx>
+#include <vcl/event.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/uitest/eventdescription.hxx>
+#include <svdata.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <memory>
+
+namespace
+{
+bool isDialogWindow(vcl::Window const* pWindow)
+{
+ WindowType nType = pWindow->GetType();
+ if (nType == WindowType::DIALOG || nType == WindowType::MODELESSDIALOG)
+ return true;
+
+ // MESSBOX, INFOBOX, WARNINGBOX, ERRORBOX, QUERYBOX
+ if (nType >= WindowType::MESSBOX && nType <= WindowType::QUERYBOX)
+ return true;
+
+ if (nType == WindowType::TABDIALOG)
+ return true;
+
+ return false;
+}
+
+bool isTopWindow(vcl::Window const* pWindow)
+{
+ WindowType eType = pWindow->GetType();
+ if (eType == WindowType::FLOATINGWINDOW)
+ {
+ return pWindow->GetStyle() & WB_SYSTEMFLOATWIN;
+ }
+ return false;
+}
+
+vcl::Window* get_top_parent(vcl::Window* pWindow)
+{
+ if (isDialogWindow(pWindow) || isTopWindow(pWindow))
+ return pWindow;
+
+ vcl::Window* pParent = pWindow->GetParent();
+ if (!pParent)
+ return pWindow;
+
+ return get_top_parent(pParent);
+}
+}
+UITestLogger::UITestLogger()
+ : mbValid(false)
+{
+ static const char* pFile = std::getenv("LO_COLLECT_UIINFO");
+ if (pFile)
+ {
+ OUString aDirPath("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
+ "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/uitest/");
+ rtl::Bootstrap::expandMacros(aDirPath);
+ osl::Directory::createPath(aDirPath);
+ OUString aFilePath = aDirPath + OUString::fromUtf8(pFile);
+
+ maStream.Open(aFilePath, StreamMode::READWRITE | StreamMode::TRUNC);
+ mbValid = true;
+ }
+}
+
+void UITestLogger::logCommand(std::u16string_view rAction,
+ const css::uno::Sequence<css::beans::PropertyValue>& rArgs)
+{
+ if (!mbValid)
+ return;
+
+ OUStringBuffer aBuffer(rAction);
+
+ if (rArgs.hasElements())
+ {
+ aBuffer.append(" {");
+ for (const css::beans::PropertyValue& rProp : rArgs)
+ {
+ OUString aTypeName = rProp.Value.getValueTypeName();
+
+ if (aTypeName == "long" || aTypeName == "short")
+ {
+ sal_Int32 nValue = 0;
+ rProp.Value >>= nValue;
+ aBuffer.append("\"" + rProp.Name + "\": " + OUString::number(nValue) + ", ");
+ }
+ else if (aTypeName == "unsigned long")
+ {
+ sal_uInt32 nValue = 0;
+ rProp.Value >>= nValue;
+ aBuffer.append("\"" + rProp.Name + "\": " + OUString::number(nValue) + ", ");
+ }
+ else if (aTypeName == "boolean")
+ {
+ bool bValue = false;
+ rProp.Value >>= bValue;
+ aBuffer.append("\"" + rProp.Name + "\": ");
+ if (bValue)
+ aBuffer.append("True, ");
+ else
+ aBuffer.append("False, ");
+ }
+ }
+ aBuffer.append("}");
+ }
+
+ OUString aCommand(aBuffer.makeStringAndClear());
+ maStream.WriteLine(OUStringToOString(aCommand, RTL_TEXTENCODING_UTF8));
+}
+
+namespace
+{
+// most likely this should be recursive
+bool child_windows_have_focus(VclPtr<vcl::Window> const& xUIElement)
+{
+ sal_Int32 nCount = xUIElement->GetChildCount();
+ for (sal_Int32 i = 0; i < nCount; ++i)
+ {
+ vcl::Window* pChild = xUIElement->GetChild(i);
+ if (pChild->HasFocus())
+ {
+ return true;
+ }
+ if (child_windows_have_focus(VclPtr<vcl::Window>(pChild)))
+ return true;
+ }
+ return false;
+}
+}
+
+void UITestLogger::logAction(VclPtr<Control> const& xUIElement, VclEventId nEvent)
+{
+ if (!mbValid)
+ return;
+
+ if (xUIElement->get_id().isEmpty())
+ return;
+
+ std::unique_ptr<UIObject> pUIObject = xUIElement->GetUITestFactory()(xUIElement.get());
+ OUString aAction = pUIObject->get_action(nEvent);
+ if (!xUIElement->HasFocus() && !child_windows_have_focus(xUIElement))
+ {
+ return;
+ }
+
+ if (!aAction.isEmpty())
+ maStream.WriteLine(OUStringToOString(aAction, RTL_TEXTENCODING_UTF8));
+}
+
+void UITestLogger::logAction(vcl::Window* const& xUIWin, VclEventId nEvent)
+{
+ if (!mbValid)
+ return;
+
+ if (xUIWin->get_id().isEmpty())
+ return;
+
+ std::unique_ptr<UIObject> pUIObject = xUIWin->GetUITestFactory()(xUIWin);
+ OUString aAction = pUIObject->get_action(nEvent);
+
+ if (!aAction.isEmpty())
+ maStream.WriteLine(OUStringToOString(aAction, RTL_TEXTENCODING_UTF8));
+}
+
+void UITestLogger::log(std::u16string_view rString)
+{
+ if (!mbValid)
+ return;
+
+ if (rString.empty())
+ return;
+
+ maStream.WriteLine(OUStringToOString(rString, RTL_TEXTENCODING_UTF8));
+}
+
+void UITestLogger::logKeyInput(VclPtr<vcl::Window> const& xUIElement, const KeyEvent& rEvent)
+{
+ if (!mbValid)
+ return;
+
+ //We need to check for Parent's ID in case the UI Element is SubEdit of Combobox/SpinField
+ const OUString& rID
+ = xUIElement->get_id().isEmpty() ? xUIElement->GetParent()->get_id() : xUIElement->get_id();
+ if (rID.isEmpty())
+ return;
+
+ sal_Unicode nChar = rEvent.GetCharCode();
+ sal_uInt16 nKeyCode = rEvent.GetKeyCode().GetCode();
+ bool bShift = rEvent.GetKeyCode().IsShift();
+ bool bMod1 = rEvent.GetKeyCode().IsMod1();
+ bool bMod2 = rEvent.GetKeyCode().IsMod2();
+ bool bMod3 = rEvent.GetKeyCode().IsMod3();
+
+ std::map<OUString, sal_uInt16> aKeyMap
+ = { { "ESC", KEY_ESCAPE }, { "TAB", KEY_TAB }, { "DOWN", KEY_DOWN },
+ { "UP", KEY_UP }, { "LEFT", KEY_LEFT }, { "RIGHT", KEY_RIGHT },
+ { "DELETE", KEY_DELETE }, { "INSERT", KEY_INSERT }, { "BACKSPACE", KEY_BACKSPACE },
+ { "RETURN", KEY_RETURN }, { "HOME", KEY_HOME }, { "END", KEY_END },
+ { "PAGEUP", KEY_PAGEUP }, { "PAGEDOWN", KEY_PAGEDOWN } };
+
+ OUString aFound;
+ for (const auto& itr : aKeyMap)
+ {
+ if (itr.second == nKeyCode)
+ {
+ aFound = itr.first;
+ break;
+ }
+ }
+
+ OUString aKeyCode;
+ if (!aFound.isEmpty() || bShift || bMod1 || bMod2 || bMod3)
+ {
+ aKeyCode = "{\"KEYCODE\": \"";
+ if (bShift)
+ aKeyCode += "SHIFT+";
+
+ if (bMod1)
+ aKeyCode += "CTRL+";
+
+ if (bMod2)
+ aKeyCode += "ALT+";
+
+ if (aFound.isEmpty())
+ aKeyCode += OUStringChar(nChar) + "\"}";
+ else
+ aKeyCode += aFound + "\"}";
+ }
+ else
+ {
+ aKeyCode = "{\"TEXT\": \"" + OUStringChar(nChar) + "\"}";
+ }
+
+ std::unique_ptr<UIObject> pUIObject = xUIElement->GetUITestFactory()(xUIElement.get());
+
+ VclPtr<vcl::Window> pParent = xUIElement->GetParent();
+
+ while (pParent && !pParent->IsTopWindow())
+ {
+ pParent = pParent->GetParent();
+ }
+
+ OUString aParentID = pParent ? pParent->get_id() : OUString();
+
+ OUString aContent;
+
+ if (pUIObject->get_type() == "EditUIObject")
+ {
+ if (aParentID.isEmpty())
+ {
+ VclPtr<vcl::Window> pParent_top = get_top_parent(xUIElement);
+ aParentID = pParent_top->get_id();
+ }
+ if (aParentID.isEmpty())
+ {
+ aContent += "Type on '" + rID + "' " + aKeyCode;
+ }
+ else
+ {
+ aContent += "Type on '" + rID + "' " + aKeyCode + " from " + aParentID;
+ }
+ }
+ else if (pUIObject->get_type() == "SwEditWinUIObject" && rID == "writer_edit")
+ {
+ aContent = "Type on writer " + aKeyCode;
+ }
+ else if (pUIObject->get_type() == "ScGridWinUIObject" && rID == "grid_window")
+ {
+ aContent = "Type on current cell " + aKeyCode;
+ }
+ else if (pUIObject->get_type() == "ImpressWindowUIObject" && rID == "impress_win")
+ {
+ aContent = "Type on impress " + aKeyCode;
+ }
+ else if (pUIObject->get_type() == "WindowUIObject" && rID == "math_edit")
+ {
+ aContent = "Type on math " + aKeyCode;
+ }
+ else if (rID == "draw_win")
+ {
+ aContent = "Type on draw " + aKeyCode;
+ }
+ else
+ {
+ if (aParentID.isEmpty())
+ {
+ VclPtr<vcl::Window> pParent_top = get_top_parent(xUIElement);
+ aParentID = pParent_top->get_id();
+ }
+ if (aParentID.isEmpty())
+ {
+ aContent = "Type on '" + rID + "' " + aKeyCode;
+ }
+ else
+ {
+ aContent = "Type on '" + rID + "' " + aKeyCode + " from " + aParentID;
+ }
+ }
+ maStream.WriteLine(OUStringToOString(aContent, RTL_TEXTENCODING_UTF8));
+}
+
+namespace
+{
+OUString StringMapToOUString(const std::map<OUString, OUString>& rParameters)
+{
+ if (rParameters.empty())
+ return "";
+
+ OUStringBuffer aParameterString(static_cast<int>(rParameters.size() * 32));
+ aParameterString.append(" {");
+
+ for (std::map<OUString, OUString>::const_iterator itr = rParameters.begin();
+ itr != rParameters.end(); ++itr)
+ {
+ if (itr != rParameters.begin())
+ aParameterString.append(", ");
+ aParameterString.append("\"" + itr->first + "\": \"" + itr->second + "\"");
+ }
+
+ aParameterString.append("}");
+
+ return aParameterString.makeStringAndClear();
+}
+
+const OUString& GetValueInMapWithIndex(const std::map<OUString, OUString>& rParameters,
+ sal_Int32 index)
+{
+ sal_Int32 j = 0;
+
+ std::map<OUString, OUString>::const_iterator itr = rParameters.begin();
+
+ for (; itr != rParameters.end() && j < index; ++itr, ++j)
+ ;
+
+ assert(itr != rParameters.end());
+
+ return itr->second;
+}
+
+const OUString& GetKeyInMapWithIndex(const std::map<OUString, OUString>& rParameters,
+ sal_Int32 index)
+{
+ sal_Int32 j = 0;
+
+ std::map<OUString, OUString>::const_iterator itr = rParameters.begin();
+
+ for (; itr != rParameters.end() && j < index; ++itr, ++j)
+ ;
+
+ assert(itr != rParameters.end());
+
+ return itr->first;
+}
+}
+
+void UITestLogger::logEvent(const EventDescription& rDescription)
+{
+ OUString aParameterString = StringMapToOUString(rDescription.aParameters);
+
+ //here we will customize our statements depending on the caller of this function
+ OUString aLogLine;
+ //first check on general commands
+ if (rDescription.aAction == "SET")
+ {
+ aLogLine = "Set Zoom to " + GetValueInMapWithIndex(rDescription.aParameters, 0);
+ }
+ else if (rDescription.aAction == "SIDEBAR")
+ {
+ aLogLine = "From SIDEBAR Choose " + aParameterString;
+ }
+ else if (rDescription.aKeyWord == "ValueSet")
+ {
+ aLogLine = "Choose element with position "
+ + GetValueInMapWithIndex(rDescription.aParameters, 0) + " in '"
+ + rDescription.aID + "' from '" + rDescription.aParent + "'";
+ }
+ else if (rDescription.aAction == "SELECT" && rDescription.aID.isEmpty())
+ {
+ aLogLine = "Select " + aParameterString;
+ }
+ else if (rDescription.aID == "writer_edit")
+ {
+ if (rDescription.aAction == "GOTO")
+ {
+ aLogLine = "GOTO page number " + GetValueInMapWithIndex(rDescription.aParameters, 0);
+ }
+ else if (rDescription.aAction == "SELECT")
+ {
+ OUString to = GetValueInMapWithIndex(rDescription.aParameters, 0);
+ OUString from = GetValueInMapWithIndex(rDescription.aParameters, 1);
+ aLogLine = "Select from Pos " + from + " to Pos " + to;
+ }
+ else if (rDescription.aAction == "CREATE_TABLE")
+ {
+ OUString size = GetValueInMapWithIndex(rDescription.aParameters, 0);
+ aLogLine = "Create Table with " + size;
+ ;
+ }
+ else if (rDescription.aAction == "COPY")
+ {
+ aLogLine = "Copy the Selected Text";
+ }
+ else if (rDescription.aAction == "CUT")
+ {
+ aLogLine = "Cut the Selected Text";
+ }
+ else if (rDescription.aAction == "PASTE")
+ {
+ aLogLine = "Paste in the Current Cursor Location";
+ }
+ else if (rDescription.aAction == "BREAK_PAGE")
+ {
+ aLogLine = "Insert Break Page";
+ }
+ }
+ else if (rDescription.aID == "grid_window")
+ {
+ if (rDescription.aAction == "SELECT")
+ {
+ OUString type = GetKeyInMapWithIndex(rDescription.aParameters, 0);
+ if (type == "CELL" || type == "RANGE")
+ {
+ aLogLine = "Select from calc" + aParameterString;
+ }
+ else if (type == "TABLE")
+ {
+ aLogLine = "Switch to sheet number "
+ + GetValueInMapWithIndex(rDescription.aParameters, 0);
+ }
+ }
+ else if (rDescription.aAction == "LAUNCH")
+ {
+ aLogLine = "Launch" + GetKeyInMapWithIndex(rDescription.aParameters, 2) + " from Col "
+ + GetValueInMapWithIndex(rDescription.aParameters, 2) + " and Row "
+ + GetValueInMapWithIndex(rDescription.aParameters, 1);
+ }
+ else if (rDescription.aAction == "DELETE_CONTENT")
+ {
+ aLogLine = "Remove Content from This " + aParameterString;
+ }
+ else if (rDescription.aAction == "DELETE_CELLS")
+ {
+ aLogLine = "Delete The Cells in" + aParameterString;
+ }
+ else if (rDescription.aAction == "INSERT_CELLS")
+ {
+ aLogLine = "Insert Cell around the " + aParameterString;
+ }
+ else if (rDescription.aAction == "CUT")
+ {
+ aLogLine = "CUT the selected " + aParameterString;
+ }
+ else if (rDescription.aAction == "COPY")
+ {
+ aLogLine = "COPY the selected " + aParameterString;
+ }
+ else if (rDescription.aAction == "PASTE")
+ {
+ aLogLine = "Paste in the " + aParameterString;
+ }
+ else if (rDescription.aAction == "MERGE_CELLS")
+ {
+ aLogLine = "Merge " + aParameterString;
+ }
+ else if (rDescription.aAction == "UNMERGE_CELL")
+ {
+ aLogLine = "Delete the merged " + aParameterString;
+ }
+ else if (rDescription.aAction == "Rename_Sheet")
+ {
+ aLogLine = "Rename The Selected Tab to \""
+ + GetValueInMapWithIndex(rDescription.aParameters, 0) + "\"";
+ }
+ else if (rDescription.aAction == "InsertTab")
+ {
+ aLogLine = "Insert New Tab ";
+ }
+ else if (rDescription.aAction == "COMMENT")
+ {
+ OUString type = GetKeyInMapWithIndex(rDescription.aParameters, 0);
+ if (type == "OPEN")
+ {
+ aLogLine = "Open Comment";
+ }
+ else if (type == "CLOSE")
+ {
+ aLogLine = "Close Comment";
+ }
+ }
+ }
+ else if (rDescription.aID == "impress_win_or_draw_win")
+ {
+ if (rDescription.aAction == "Insert_New_Page_or_Slide")
+ {
+ if (UITestLogger::getInstance().getAppName() == "impress")
+ {
+ aLogLine = "Insert New Slide at Position "
+ + GetValueInMapWithIndex(rDescription.aParameters, 0);
+ }
+ else if (UITestLogger::getInstance().getAppName() == "draw")
+ {
+ aLogLine = "Insert New Page at Position "
+ + GetValueInMapWithIndex(rDescription.aParameters, 0);
+ }
+ }
+ else if (rDescription.aAction == "Delete_Slide_or_Page")
+ {
+ if (UITestLogger::getInstance().getAppName() == "impress")
+ {
+ aLogLine
+ = "Delete Slide number " + GetValueInMapWithIndex(rDescription.aParameters, 0);
+ }
+ else if (UITestLogger::getInstance().getAppName() == "draw")
+ {
+ aLogLine
+ = "Delete Page number " + GetValueInMapWithIndex(rDescription.aParameters, 0);
+ }
+ }
+ else if (rDescription.aAction == "Duplicate")
+ {
+ aLogLine = "Duplicate The Selected Slide ";
+ }
+ else if (rDescription.aAction == "RENAME")
+ {
+ if (UITestLogger::getInstance().getAppName() == "impress")
+ {
+ aLogLine = "Rename The Selected Slide from \""
+ + GetValueInMapWithIndex(rDescription.aParameters, 1) + "\" to \""
+ + GetValueInMapWithIndex(rDescription.aParameters, 0) + "\"";
+ }
+ else if (UITestLogger::getInstance().getAppName() == "draw")
+ {
+ aLogLine = "Rename The Selected Page from \""
+ + GetValueInMapWithIndex(rDescription.aParameters, 1) + "\" to \""
+ + GetValueInMapWithIndex(rDescription.aParameters, 0) + "\"";
+ }
+ }
+ }
+ else if (rDescription.aKeyWord == "SwEditWinUIObject")
+ {
+ if (rDescription.aAction == "LEAVE")
+ {
+ aLogLine = "Leave '" + rDescription.aID + "'";
+ }
+ else if (rDescription.aAction == "SHOW")
+ {
+ aLogLine = "Show '" + rDescription.aID + "'";
+ }
+ else if (rDescription.aAction == "HIDE")
+ {
+ aLogLine = "Hide '" + rDescription.aID + "'";
+ }
+ else if (rDescription.aAction == "DELETE")
+ {
+ aLogLine = "Delete '" + rDescription.aID + "'";
+ }
+ else if (rDescription.aAction == "SETRESOLVED")
+ {
+ aLogLine = "Resolve '" + rDescription.aID + "'";
+ }
+ }
+ else if (rDescription.aParent == "element_selector")
+ {
+ aLogLine = "Select element no " + rDescription.aID + " From " + rDescription.aParent;
+ }
+ else if (rDescription.aKeyWord == "MenuButton")
+ {
+ if (rDescription.aAction == "OPENLIST")
+ {
+ aLogLine = "Open List From " + rDescription.aID;
+ }
+ else if (rDescription.aAction == "CLOSELIST")
+ {
+ aLogLine = "Close List From " + rDescription.aID;
+ }
+ else if (rDescription.aAction == "OPENFROMLIST")
+ {
+ aLogLine = "Select item no " + GetValueInMapWithIndex(rDescription.aParameters, 0)
+ + " From List of " + rDescription.aID;
+ }
+ }
+ else if (rDescription.aKeyWord == "VerticalTab")
+ {
+ aLogLine = "Choose Tab number " + GetValueInMapWithIndex(rDescription.aParameters, 0)
+ + " in '" + rDescription.aID + "'";
+ }
+ else
+ {
+ aLogLine = rDescription.aKeyWord + " Action:" + rDescription.aAction + " Id:"
+ + rDescription.aID + " Parent:" + rDescription.aParent + aParameterString;
+ }
+ log(aLogLine);
+}
+
+UITestLogger& UITestLogger::getInstance()
+{
+ ImplSVData* const pSVData = ImplGetSVData();
+ assert(pSVData);
+
+ if (!pSVData->maFrameData.m_pUITestLogger)
+ {
+ pSVData->maFrameData.m_pUITestLogger.reset(new UITestLogger);
+ }
+
+ return *pSVData->maFrameData.m_pUITestLogger;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/uitest/uiobject.cxx b/vcl/source/uitest/uiobject.cxx
new file mode 100644
index 000000000..fa5c95346
--- /dev/null
+++ b/vcl/source/uitest/uiobject.cxx
@@ -0,0 +1,1858 @@
+/* -*- 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/.
+ */
+
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/uitest/metricfielduiobject.hxx>
+#include <vcl/uitest/formattedfielduiobject.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/toolkit/combobox.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/tabpage.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/toolkit/spin.hxx>
+#include <vcl/toolkit/fmtfield.hxx>
+#include <vcl/toolkit/spinfld.hxx>
+#include <vcl/toolkit/ivctrl.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/toolkit/field.hxx>
+#include <vcl/toolkit/treelistbox.hxx>
+#include <vcl/toolkit/treelistentry.hxx>
+#include <vcl/toolkit/svlbitm.hxx>
+#include <vcl/toolkit/menubtn.hxx>
+#include <vcl/toolkit/vclmedit.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <uiobject-internal.hxx>
+#include <verticaltabctrl.hxx>
+#include <vcl/toolbox.hxx>
+
+#include <comphelper/string.hxx>
+#include <comphelper/lok.hxx>
+
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+
+#include <iostream>
+#include <memory>
+#include <vector>
+
+UIObject::~UIObject()
+{
+}
+
+StringMap UIObject::get_state()
+{
+ StringMap aMap;
+ aMap["NotImplemented"] = "NotImplemented";
+ return aMap;
+}
+
+void UIObject::execute(const OUString& /*rAction*/,
+ const StringMap& /*rParameters*/)
+{
+ // should never be called
+ throw std::exception();
+}
+
+OUString UIObject::get_type() const
+{
+ return "Generic UIObject";
+}
+
+std::unique_ptr<UIObject> UIObject::get_child(const OUString&)
+{
+ return std::unique_ptr<UIObject>();
+}
+
+std::set<OUString> UIObject::get_children() const
+{
+ return std::set<OUString>();
+}
+
+OUString UIObject::dumpState() const
+{
+ return OUString();
+}
+
+OUString UIObject::dumpHierarchy() const
+{
+ return OUString();
+}
+
+OUString UIObject::get_action(VclEventId /*nEvent*/) const
+{
+ return OUString();
+}
+
+namespace {
+
+bool isDialogWindow(vcl::Window const * pWindow)
+{
+ WindowType nType = pWindow->GetType();
+ if (nType == WindowType::DIALOG || nType == WindowType::MODELESSDIALOG)
+ return true;
+
+ // MESSBOX, INFOBOX, WARNINGBOX, ERRORBOX, QUERYBOX
+ if (nType >= WindowType::MESSBOX && nType <= WindowType::QUERYBOX)
+ return true;
+
+ if (nType == WindowType::TABDIALOG)
+ return true;
+
+ return false;
+}
+
+bool isTopWindow(vcl::Window const * pWindow)
+{
+ WindowType eType = pWindow->GetType();
+ if (eType == WindowType::FLOATINGWINDOW)
+ {
+ return pWindow->GetStyle() & WB_SYSTEMFLOATWIN;
+ }
+ return false;
+}
+
+vcl::Window* get_top_parent(vcl::Window* pWindow)
+{
+ if (isDialogWindow(pWindow) || isTopWindow(pWindow))
+ return pWindow;
+
+ vcl::Window* pParent = pWindow->GetParent();
+ if (!pParent)
+ return pWindow;
+
+ return get_top_parent(pParent);
+}
+
+std::vector<KeyEvent> generate_key_events_from_text(const OUString& rStr)
+{
+ std::vector<KeyEvent> aEvents;
+ vcl::KeyCode aCode;
+ for (sal_Int32 i = 0, n = rStr.getLength();
+ i != n; ++i)
+ {
+ aEvents.emplace_back(rStr[i], aCode);
+ }
+ return aEvents;
+}
+
+sal_uInt16 get_key(sal_Unicode cChar, bool& bShift)
+{
+ bShift = false;
+ if (cChar >= 'a' && cChar <= 'z')
+ return KEY_A + (cChar - 'a');
+ else if (cChar >= 'A' && cChar <= 'Z')
+ {
+ bShift = true;
+ return KEY_A + (cChar - 'A');
+ }
+ else if (cChar >= '0' && cChar <= '9')
+ return KEY_0 + (cChar - 'A');
+
+ return cChar;
+}
+
+bool isFunctionKey(const OUString& rStr, sal_uInt16& rKeyCode)
+{
+ std::map<OUString, sal_uInt16> aFunctionKeyMap = {
+ {"F1", KEY_F1},
+ {"F2", KEY_F2},
+ {"F3", KEY_F3},
+ {"F4", KEY_F4},
+ {"F5", KEY_F5},
+ {"F6", KEY_F6},
+ {"F7", KEY_F7},
+ {"F8", KEY_F8},
+ {"F9", KEY_F9},
+ {"F10", KEY_F10},
+ {"F11", KEY_F11},
+ {"F12", KEY_F12}
+ };
+
+ rKeyCode = 0;
+ auto itr = aFunctionKeyMap.find(rStr);
+ if (itr == aFunctionKeyMap.end())
+ return false;
+
+ rKeyCode = itr->second;
+ return true;
+}
+
+std::vector<KeyEvent> generate_key_events_from_keycode(std::u16string_view rStr)
+{
+ std::vector<KeyEvent> aEvents;
+
+ std::map<OUString, sal_uInt16> aKeyMap = {
+ {"ESC", KEY_ESCAPE},
+ {"TAB", KEY_TAB},
+ {"DOWN", KEY_DOWN},
+ {"UP", KEY_UP},
+ {"LEFT", KEY_LEFT},
+ {"RIGHT", KEY_RIGHT},
+ {"DELETE", KEY_DELETE},
+ {"INSERT", KEY_INSERT},
+ {"SPACE", KEY_SPACE},
+ {"BACKSPACE", KEY_BACKSPACE},
+ {"RETURN", KEY_RETURN},
+ {"HOME", KEY_HOME},
+ {"END", KEY_END},
+ {"PAGEUP", KEY_PAGEUP},
+ {"PAGEDOWN", KEY_PAGEDOWN}
+ };
+
+ // split string along '+'
+ // then translate to keycodes
+ bool bShift = false;
+ bool bMod1 = false;
+ bool bMod2 = false;
+ OUString aRemainingText;
+
+ std::vector<OUString> aTokens = comphelper::string::split(rStr, '+');
+ for (auto const& token : aTokens)
+ {
+ OUString aToken = token.trim();
+ if (aToken == "CTRL")
+ {
+ bMod1 = true;
+ }
+ else if (aToken == "SHIFT")
+ {
+ bShift = true;
+ }
+ else if (aToken == "ALT")
+ {
+ bMod2 = true;
+ }
+ else
+ aRemainingText = aToken;
+ }
+
+ sal_uInt16 nFunctionKey = 0;
+ if (isFunctionKey(aRemainingText, nFunctionKey))
+ {
+ vcl::KeyCode aCode(nFunctionKey, bShift, bMod1, bMod2, false);
+ aEvents.emplace_back(0, aCode);
+ }
+ else if (aKeyMap.find(aRemainingText) != aKeyMap.end())
+ {
+ sal_uInt16 nKey = aKeyMap[aRemainingText];
+ vcl::KeyCode aCode(nKey, bShift, bMod1, bMod2, false);
+ aEvents.emplace_back( 'a', aCode);
+ }
+ else
+ {
+ for (sal_Int32 i = 0; i < aRemainingText.getLength(); ++i)
+ {
+ bool bShiftThroughKey = false;
+ sal_uInt16 nKey = get_key(aRemainingText[i], bShiftThroughKey);
+ vcl::KeyCode aCode(nKey, bShift || bShiftThroughKey, bMod1, bMod2, false);
+ aEvents.emplace_back(aRemainingText[i], aCode);
+ }
+ }
+
+ return aEvents;
+}
+
+OUString to_string(const Point& rPos)
+{
+ OUString sStr = OUString::number(rPos.X())
+ + "x"
+ + OUString::number(rPos.Y());
+
+ return sStr;
+}
+
+OUString to_string(const Size& rSize)
+{
+ OUString sStr = OUString::number(rSize.Width())
+ + "x"
+ + OUString::number(rSize.Height());
+
+ return sStr;
+}
+
+}
+
+WindowUIObject::WindowUIObject(const VclPtr<vcl::Window>& xWindow):
+ mxWindow(xWindow)
+{
+}
+
+StringMap WindowUIObject::get_state()
+{
+ // Double-buffering is not interesting for uitesting, but can result in direct paint for a
+ // double-buffered widget, which is incorrect.
+ if (mxWindow->SupportsDoubleBuffering())
+ mxWindow->RequestDoubleBuffering(false);
+
+ StringMap aMap;
+ aMap["Visible"] = OUString::boolean(mxWindow->IsVisible());
+ aMap["ReallyVisible"] = OUString::boolean(mxWindow->IsReallyVisible());
+ aMap["Enabled"] = OUString::boolean(mxWindow->IsEnabled());
+ aMap["HasFocus"] = OUString::boolean(mxWindow->HasChildPathFocus());
+ aMap["WindowType"] = OUString::number(static_cast<sal_uInt16>(mxWindow->GetType()), 16);
+
+ Point aPos = mxWindow->GetPosPixel();
+ aMap["RelPosition"] = to_string(aPos);
+ aMap["Size"] = to_string(mxWindow->GetSizePixel());
+ aMap["ID"] = mxWindow->get_id();
+ vcl::Window* pParent = mxWindow->GetParent();
+ if (pParent)
+ aMap["Parent"] = mxWindow->GetParent()->get_id();
+
+ bool bIgnoreAllExceptTop = isDialogWindow(mxWindow.get());
+ while(pParent)
+ {
+ Point aParentPos = pParent->GetPosPixel();
+ if (!bIgnoreAllExceptTop)
+ aPos += aParentPos;
+
+ if (isDialogWindow(pParent))
+ {
+ bIgnoreAllExceptTop = true;
+ }
+
+ pParent = pParent->GetParent();
+
+ if (!pParent && bIgnoreAllExceptTop)
+ aPos += aParentPos;
+ }
+ aMap["AbsPosition"] = to_string(aPos);
+ aMap["Text"] = mxWindow->GetText();
+ aMap["DisplayText"] = mxWindow->GetDisplayText();
+
+ return aMap;
+}
+
+void WindowUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction == "SET")
+ {
+ for (auto const& parameter : rParameters)
+ {
+ std::cout << parameter.first;
+ }
+ }
+ else if (rAction == "TYPE")
+ {
+ auto it = rParameters.find("TEXT");
+ if (it != rParameters.end())
+ {
+ const OUString& rText = it->second;
+ auto aKeyEvents = generate_key_events_from_text(rText);
+ for (auto const& keyEvent : aKeyEvents)
+ {
+ mxWindow->KeyInput(keyEvent);
+ }
+ }
+ else if (rParameters.find("KEYCODE") != rParameters.end())
+ {
+ auto itr = rParameters.find("KEYCODE");
+ const OUString rText = itr->second;
+ auto aKeyEvents = generate_key_events_from_keycode(rText);
+ for (auto const& keyEvent : aKeyEvents)
+ {
+ mxWindow->KeyInput(keyEvent);
+ }
+ }
+ else
+ {
+ OStringBuffer buf;
+ for (auto const & rPair : rParameters)
+ buf.append("," + rPair.first.toUtf8() + "=" + rPair.second.toUtf8());
+ SAL_WARN("vcl.uitest", "missing parameter TEXT to action TYPE "
+ << buf.makeStringAndClear());
+ throw std::logic_error("missing parameter TEXT to action TYPE");
+ }
+ }
+ else if (rAction == "FOCUS")
+ {
+ mxWindow->GrabFocus();
+ }
+ else
+ {
+ OStringBuffer buf;
+ for (auto const & rPair : rParameters)
+ buf.append("," + rPair.first.toUtf8() + "=" + rPair.second.toUtf8());
+ SAL_WARN("vcl.uitest", "unknown action for " << get_name()
+ << ". Action: " << rAction << buf.makeStringAndClear());
+ throw std::logic_error("unknown action");
+ }
+}
+
+OUString WindowUIObject::get_type() const
+{
+ return get_name();
+}
+
+namespace {
+
+vcl::Window* findChild(vcl::Window* pParent, const OUString& rID, bool bRequireVisible = false)
+{
+ if (!pParent || pParent->isDisposed())
+ return nullptr;
+
+ if (pParent->get_id() == rID)
+ return pParent;
+
+ size_t nCount = pParent->GetChildCount();
+ for (size_t i = 0; i < nCount; ++i)
+ {
+ vcl::Window* pChild = pParent->GetChild(i);
+ bool bCandidate = !bRequireVisible || pChild->IsVisible();
+ if (!bCandidate)
+ continue;
+
+ if (pChild->get_id() == rID)
+ return pChild;
+
+ vcl::Window* pResult = findChild(pChild, rID);
+ if (pResult)
+ return pResult;
+ }
+
+ return nullptr;
+}
+
+void addChildren(vcl::Window const * pParent, std::set<OUString>& rChildren)
+{
+ if (!pParent)
+ return;
+
+ size_t nCount = pParent->GetChildCount();
+ for (size_t i = 0; i < nCount; ++i)
+ {
+ vcl::Window* pChild = pParent->GetChild(i);
+ if (pChild)
+ {
+ OUString aId = pChild->get_id();
+ if (!aId.isEmpty())
+ {
+ auto ret = rChildren.insert(aId);
+ SAL_WARN_IF(!ret.second, "vcl.uitest", "duplicate ids '" << aId << "' for ui elements. violates locally unique requirement");
+ }
+
+ addChildren(pChild, rChildren);
+ }
+ }
+}
+
+}
+
+std::unique_ptr<UIObject> WindowUIObject::get_child(const OUString& rID)
+{
+ // in a first step try the real children before moving to the top level parent
+ // This makes it easier to handle cases with the same ID as there is a way
+ // to resolve conflicts
+ vcl::Window* pWindow = findChild(mxWindow.get(), rID);
+ if (!pWindow)
+ {
+ vcl::Window* pDialogParent = get_top_parent(mxWindow.get());
+ pWindow = findChild(pDialogParent, rID);
+ }
+
+ if (!pWindow)
+ throw css::uno::RuntimeException("Could not find child with id: " + rID);
+
+ FactoryFunction aFunction = pWindow->GetUITestFactory();
+ return aFunction(pWindow);
+}
+
+std::unique_ptr<UIObject> WindowUIObject::get_visible_child(const OUString& rID)
+{
+ // in a first step try the real children before moving to the top level parent
+ // This makes it easier to handle cases with the same ID as there is a way
+ // to resolve conflicts
+ vcl::Window* pWindow = findChild(mxWindow.get(), rID, true);
+ if (!pWindow)
+ {
+ vcl::Window* pDialogParent = get_top_parent(mxWindow.get());
+ pWindow = findChild(pDialogParent, rID, true);
+ }
+
+ if (!pWindow)
+ throw css::uno::RuntimeException("Could not find child with id: " + rID);
+
+ FactoryFunction aFunction = pWindow->GetUITestFactory();
+ return aFunction(pWindow);
+}
+
+std::set<OUString> WindowUIObject::get_children() const
+{
+ std::set<OUString> aChildren;
+ vcl::Window* pDialogParent = get_top_parent(mxWindow.get());
+ if (!pDialogParent->isDisposed())
+ {
+ aChildren.insert(pDialogParent->get_id());
+ addChildren(pDialogParent, aChildren);
+ }
+ return aChildren;
+}
+
+OUString WindowUIObject::get_name() const
+{
+ return "WindowUIObject";
+}
+
+namespace {
+
+OUString escape(const OUString& rStr)
+{
+ return rStr.replaceAll("\"", "\\\"");
+}
+
+}
+
+OUString WindowUIObject::dumpState() const
+{
+ OUStringBuffer aStateString = "{\"name\":\"" + mxWindow->get_id() + "\"";
+ aStateString.append(", \"ImplementationName\":\"").appendAscii(typeid(*mxWindow).name()).append("\"");
+ StringMap aState = const_cast<WindowUIObject*>(this)->get_state();
+ for (auto const& elem : aState)
+ {
+ OUString property = ",\"" + elem.first + "\":\"" + escape(elem.second) + "\"";
+ aStateString.append(property);
+ }
+
+ size_t nCount = mxWindow->GetChildCount();
+
+ if (nCount)
+ aStateString.append(",\"children\":[");
+
+ for (size_t i = 0; i < nCount; ++i)
+ {
+ if (i != 0)
+ {
+ aStateString.append(",");
+ }
+ vcl::Window* pChild = mxWindow->GetChild(i);
+ std::unique_ptr<UIObject> pChildWrapper =
+ pChild->GetUITestFactory()(pChild);
+ OUString children = pChildWrapper->dumpState();
+ aStateString.append(children);
+ }
+
+ if (nCount)
+ aStateString.append("]");
+
+ aStateString.append("}");
+
+ OUString aString = aStateString.makeStringAndClear();
+ return aString.replaceAll("\n", "\\n");
+}
+
+OUString WindowUIObject::dumpHierarchy() const
+{
+ vcl::Window* pDialogParent = get_top_parent(mxWindow.get());
+ std::unique_ptr<UIObject> pParentWrapper =
+ pDialogParent->GetUITestFactory()(pDialogParent);
+ return pParentWrapper->dumpState();
+}
+
+OUString WindowUIObject::get_action(VclEventId nEvent) const
+{
+
+ OUString aActionName;
+ switch (nEvent)
+ {
+ case VclEventId::ControlGetFocus:
+ case VclEventId::ControlLoseFocus:
+ return OUString();
+
+ case VclEventId::ButtonClick:
+ case VclEventId::CheckboxToggle:
+ aActionName = "CLICK";
+ break;
+
+ case VclEventId::EditModify:
+ aActionName = "TYPE";
+ break;
+ default:
+ aActionName = OUString::number(static_cast<int>(nEvent));
+ }
+ return "Action on element: " + mxWindow->get_id() + " with action : " + aActionName;
+}
+
+std::unique_ptr<UIObject> WindowUIObject::create(vcl::Window* pWindow)
+{
+ return std::unique_ptr<UIObject>(new WindowUIObject(pWindow));
+}
+
+ButtonUIObject::ButtonUIObject(const VclPtr<Button>& xButton):
+ WindowUIObject(xButton),
+ mxButton(xButton)
+{
+}
+
+ButtonUIObject::~ButtonUIObject()
+{
+}
+
+StringMap ButtonUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ // Move that to a Control base class
+ aMap["Label"] = mxButton->GetDisplayText();
+
+ return aMap;
+}
+
+void ButtonUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction == "CLICK")
+ {
+ //Click doesn't call toggle when it's a pushbutton tweaked to be a toggle-button
+ if (PushButton *pPushButton = (mxButton->GetStyle() & WB_TOGGLE) ? dynamic_cast<PushButton*>(mxButton.get()) : nullptr)
+ {
+ pPushButton->Check(!pPushButton->IsChecked());
+ pPushButton->Toggle();
+ return;
+ }
+ mxButton->Click();
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+OUString ButtonUIObject::get_name() const
+{
+ return "ButtonUIObject";
+}
+
+OUString ButtonUIObject::get_action(VclEventId nEvent) const
+{
+ if (nEvent == VclEventId::ButtonClick)
+ {
+ if(mxButton->get_id()=="writer_all")
+ {
+ UITestLogger::getInstance().setAppName("writer");
+ return "Start writer" ;
+ }
+ else if(mxButton->get_id()=="calc_all")
+ {
+ UITestLogger::getInstance().setAppName("calc");
+ return "Start calc" ;
+ }
+ else if(mxButton->get_id()=="impress_all")
+ {
+ UITestLogger::getInstance().setAppName("impress");
+ return "Start impress" ;
+ }
+ else if(mxButton->get_id()=="draw_all")
+ {
+ UITestLogger::getInstance().setAppName("draw");
+ return "Start draw" ;
+ }
+ else if(mxButton->get_id()=="math_all")
+ {
+ UITestLogger::getInstance().setAppName("math");
+ return "Start math" ;
+ }
+ else if(mxButton->get_id()=="database_all")
+ {
+ UITestLogger::getInstance().setAppName("database");
+ return "Start database" ;
+ }
+ else{
+ if (get_top_parent(mxButton)->get_id().isEmpty()){
+ //This part because if we don't have parent
+ return "Click on '" + mxButton->get_id() ;
+ }
+ return "Click on '" + mxButton->get_id() + "' from "+
+ get_top_parent(mxButton)->get_id();
+ }
+ }
+ else
+ return WindowUIObject::get_action(nEvent);
+}
+
+std::unique_ptr<UIObject> ButtonUIObject::create(vcl::Window* pWindow)
+{
+ Button* pButton = dynamic_cast<Button*>(pWindow);
+ assert(pButton);
+ return std::unique_ptr<UIObject>(new ButtonUIObject(pButton));
+}
+
+DialogUIObject::DialogUIObject(const VclPtr<Dialog>& xDialog):
+ WindowUIObject(xDialog),
+ mxDialog(xDialog)
+{
+}
+
+DialogUIObject::~DialogUIObject()
+{
+}
+
+StringMap DialogUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ aMap["Modal"] = OUString::boolean(mxDialog->IsModalInputMode());
+
+ return aMap;
+}
+
+OUString DialogUIObject::get_name() const
+{
+ return "DialogUIObject";
+}
+
+std::unique_ptr<UIObject> DialogUIObject::create(vcl::Window* pWindow)
+{
+ Dialog* pDialog = dynamic_cast<Dialog*>(pWindow);
+ assert(pDialog);
+ return std::unique_ptr<UIObject>(new DialogUIObject(pDialog));
+}
+
+EditUIObject::EditUIObject(const VclPtr<Edit>& xEdit):
+ WindowUIObject(xEdit),
+ mxEdit(xEdit)
+{
+}
+
+EditUIObject::~EditUIObject()
+{
+}
+
+void EditUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ bool bHandled = true;
+ if (rAction == "TYPE")
+ {
+ auto it = rParameters.find("TEXT");
+ if (it != rParameters.end())
+ {
+ const OUString& rText = it->second;
+ auto aKeyEvents = generate_key_events_from_text(rText);
+ for (auto const& keyEvent : aKeyEvents)
+ {
+ mxEdit->KeyInput(keyEvent);
+ }
+ }
+ else
+ {
+ bHandled = false;
+ }
+ }
+ else if (rAction == "SET")
+ {
+ auto it = rParameters.find("TEXT");
+ if (it != rParameters.end())
+ {
+ mxEdit->SetText(it->second);
+ mxEdit->Modify();
+ }
+ else
+ bHandled = false;
+ }
+ else if (rAction == "SELECT")
+ {
+ if (rParameters.find("FROM") != rParameters.end() &&
+ rParameters.find("TO") != rParameters.end())
+ {
+ tools::Long nMin = rParameters.find("FROM")->second.toInt32();
+ tools::Long nMax = rParameters.find("TO")->second.toInt32();
+ Selection aSelection(nMin, nMax);
+ mxEdit->SetSelection(aSelection);
+ }
+ }
+ else if (rAction == "CLEAR")
+ {
+ mxEdit->SetText("");
+ mxEdit->Modify();
+ bHandled = true;
+ }
+ else
+ {
+ bHandled = false;
+ }
+
+ if (!bHandled)
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+StringMap EditUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ aMap["MaxTextLength"] = OUString::number(mxEdit->GetMaxTextLen());
+ aMap["QuickHelpText"] = mxEdit->GetQuickHelpText();
+ aMap["SelectedText"] = mxEdit->GetSelected();
+ aMap["Text"] = mxEdit->GetText();
+
+ return aMap;
+}
+
+OUString EditUIObject::get_action(VclEventId nEvent) const
+{
+ if (nEvent == VclEventId::EditSelectionChanged)
+ {
+ const Selection& rSelection = mxEdit->GetSelection();
+ tools::Long nMin = rSelection.Min();
+ tools::Long nMax = rSelection.Max();
+ if(get_top_parent(mxEdit)->get_id().isEmpty()){
+ //This part because if we don't have parent
+ return "Select in '" +
+ mxEdit->get_id() +
+ "' {\"FROM\": \"" + OUString::number(nMin) + "\", \"TO\": \"" +
+ OUString::number(nMax) + "\"}"
+ ;
+ }
+ return "Select in '" +
+ mxEdit->get_id() +
+ "' {\"FROM\": \"" + OUString::number(nMin) + "\", \"TO\": \"" +
+ OUString::number(nMax) + "\"} from "
+ + get_top_parent(mxEdit)->get_id()
+ ;
+ }
+ else
+ return WindowUIObject::get_action(nEvent);
+}
+
+OUString EditUIObject::get_name() const
+{
+ return "EditUIObject";
+}
+
+std::unique_ptr<UIObject> EditUIObject::create(vcl::Window* pWindow)
+{
+ Edit* pEdit = dynamic_cast<Edit*>(pWindow);
+ assert(pEdit);
+ return std::unique_ptr<UIObject>(new EditUIObject(pEdit));
+}
+
+MultiLineEditUIObject::MultiLineEditUIObject(const VclPtr<VclMultiLineEdit>& xEdit):
+ WindowUIObject(xEdit),
+ mxEdit(xEdit)
+{
+}
+
+MultiLineEditUIObject::~MultiLineEditUIObject()
+{
+}
+
+void MultiLineEditUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ bool bHandled = true;
+ if (rAction == "TYPE")
+ {
+ WindowUIObject aChildObj(mxEdit->GetTextWindow());
+ aChildObj.execute(rAction, rParameters);
+ }
+ else if (rAction == "SELECT")
+ {
+ if (rParameters.find("FROM") != rParameters.end() &&
+ rParameters.find("TO") != rParameters.end())
+ {
+ tools::Long nMin = rParameters.find("FROM")->second.toInt32();
+ tools::Long nMax = rParameters.find("TO")->second.toInt32();
+ Selection aSelection(nMin, nMax);
+ mxEdit->SetSelection(aSelection);
+ }
+ }
+ else
+ {
+ bHandled = false;
+ }
+
+ if (!bHandled)
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+StringMap MultiLineEditUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ aMap["MaxTextLength"] = OUString::number(mxEdit->GetMaxTextLen());
+ aMap["SelectedText"] = mxEdit->GetSelected();
+ aMap["Text"] = mxEdit->GetText();
+
+ return aMap;
+}
+
+OUString MultiLineEditUIObject::get_name() const
+{
+ return "MultiLineEditUIObject";
+}
+
+std::unique_ptr<UIObject> MultiLineEditUIObject::create(vcl::Window* pWindow)
+{
+ VclMultiLineEdit* pEdit = dynamic_cast<VclMultiLineEdit*>(pWindow);
+ assert(pEdit);
+ return std::unique_ptr<UIObject>(new MultiLineEditUIObject(pEdit));
+}
+
+ExpanderUIObject::ExpanderUIObject(const VclPtr<VclExpander>& xExpander)
+ : WindowUIObject(xExpander)
+ , mxExpander(xExpander)
+{
+}
+
+ExpanderUIObject::~ExpanderUIObject()
+{
+}
+
+void ExpanderUIObject::execute(const OUString& rAction, const StringMap& rParameters)
+{
+ if (rAction == "EXPAND")
+ {
+ mxExpander->set_expanded(true);
+ }
+ else if (rAction == "COLLAPSE")
+ {
+ mxExpander->set_expanded(false);
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+StringMap ExpanderUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ aMap["Expanded"] = OUString::boolean(mxExpander->get_expanded());
+ return aMap;
+}
+
+OUString ExpanderUIObject::get_name() const
+{
+ return "ExpanderUIObject";
+}
+
+std::unique_ptr<UIObject> ExpanderUIObject::create(vcl::Window* pWindow)
+{
+ VclExpander* pVclExpander = dynamic_cast<VclExpander*>(pWindow);
+ assert(pVclExpander);
+ return std::unique_ptr<UIObject>(new ExpanderUIObject(pVclExpander));
+}
+
+CheckBoxUIObject::CheckBoxUIObject(const VclPtr<CheckBox>& xCheckbox):
+ WindowUIObject(xCheckbox),
+ mxCheckBox(xCheckbox)
+{
+}
+
+CheckBoxUIObject::~CheckBoxUIObject()
+{
+}
+
+void CheckBoxUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction == "CLICK")
+ {
+ // don't use toggle directly, it does not set the value
+ mxCheckBox->ImplCheck();
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+StringMap CheckBoxUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ aMap["Selected"] = OUString::boolean(mxCheckBox->IsChecked());
+ aMap["TriStateEnabled"] = OUString::boolean(mxCheckBox->IsTriStateEnabled());
+ return aMap;
+}
+
+OUString CheckBoxUIObject::get_name() const
+{
+ return "CheckBoxUIObject";
+}
+
+OUString CheckBoxUIObject::get_action(VclEventId nEvent) const
+{
+ if (nEvent == VclEventId::CheckboxToggle)
+ {
+ if(get_top_parent(mxCheckBox)->get_id().isEmpty()){
+ //This part because if we don't have parent
+ return "Toggle '" + mxCheckBox->get_id() + "' CheckBox";
+ }
+ return "Toggle '" + mxCheckBox->get_id() + "' CheckBox from " +
+ get_top_parent(mxCheckBox)->get_id();
+ }
+ else
+ return WindowUIObject::get_action(nEvent);
+}
+
+std::unique_ptr<UIObject> CheckBoxUIObject::create(vcl::Window* pWindow)
+{
+ CheckBox* pCheckBox = dynamic_cast<CheckBox*>(pWindow);
+ assert(pCheckBox);
+ return std::unique_ptr<UIObject>(new CheckBoxUIObject(pCheckBox));
+}
+
+RadioButtonUIObject::RadioButtonUIObject(const VclPtr<RadioButton>& xRadioButton):
+ WindowUIObject(xRadioButton),
+ mxRadioButton(xRadioButton)
+{
+}
+
+RadioButtonUIObject::~RadioButtonUIObject()
+{
+}
+
+void RadioButtonUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction == "CLICK")
+ {
+ mxRadioButton->ImplCallClick();
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+StringMap RadioButtonUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ aMap["Checked"] = OUString::boolean(mxRadioButton->IsChecked());
+
+ return aMap;
+}
+
+OUString RadioButtonUIObject::get_name() const
+{
+ return "RadioButtonUIObject";
+}
+
+OUString RadioButtonUIObject::get_action(VclEventId nEvent) const
+{
+ if (nEvent == VclEventId::RadiobuttonToggle)
+ {
+ if(get_top_parent(mxRadioButton)->get_id().isEmpty()){
+ //This part because if we don't have parent
+ return "Select '" + mxRadioButton->get_id() + "' RadioButton";
+ }
+ return "Select '" + mxRadioButton->get_id() + "' RadioButton from " +
+ get_top_parent(mxRadioButton)->get_id();
+ }
+ else
+ return WindowUIObject::get_action(nEvent);
+}
+
+std::unique_ptr<UIObject> RadioButtonUIObject::create(vcl::Window* pWindow)
+{
+ RadioButton* pRadioButton = dynamic_cast<RadioButton*>(pWindow);
+ assert(pRadioButton);
+ return std::unique_ptr<UIObject>(new RadioButtonUIObject(pRadioButton));
+}
+
+TabPageUIObject::TabPageUIObject(const VclPtr<TabPage>& xTabPage):
+ WindowUIObject(xTabPage),
+ mxTabPage(xTabPage)
+{
+}
+
+TabPageUIObject::~TabPageUIObject()
+{
+}
+
+void TabPageUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+StringMap TabPageUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+
+ return aMap;
+}
+
+OUString TabPageUIObject::get_name() const
+{
+ return "TabPageUIObject";
+}
+
+ListBoxUIObject::ListBoxUIObject(const VclPtr<ListBox>& xListBox):
+ WindowUIObject(xListBox),
+ mxListBox(xListBox)
+{
+}
+
+ListBoxUIObject::~ListBoxUIObject()
+{
+}
+
+void ListBoxUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (!mxListBox->IsEnabled())
+ return;
+
+ bool isTiledRendering = comphelper::LibreOfficeKit::isActive();
+ if (!isTiledRendering && !mxListBox->IsReallyVisible())
+ return;
+
+ if (rAction == "SELECT")
+ {
+ bool bSelect = true;
+ if (rParameters.find("POS") != rParameters.end())
+ {
+ auto itr = rParameters.find("POS");
+ OUString aVal = itr->second;
+ sal_Int32 nPos = aVal.toInt32();
+ mxListBox->SelectEntryPos(nPos, bSelect);
+ }
+ else if (rParameters.find("TEXT") != rParameters.end())
+ {
+ auto itr = rParameters.find("TEXT");
+ OUString aText = itr->second;
+ mxListBox->SelectEntry(aText, bSelect);
+ }
+ mxListBox->Select();
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+StringMap ListBoxUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ aMap["ReadOnly"] = OUString::boolean(mxListBox->IsReadOnly());
+ aMap["MultiSelect"] = OUString::boolean(mxListBox->IsMultiSelectionEnabled());
+ aMap["EntryCount"] = OUString::number(mxListBox->GetEntryCount());
+ aMap["SelectEntryCount"] = OUString::number(mxListBox->GetSelectedEntryCount());
+ aMap["SelectEntryPos"] = OUString::number(mxListBox->GetSelectedEntryPos());
+ aMap["SelectEntryText"] = mxListBox->GetSelectedEntry();
+
+ return aMap;
+}
+
+OUString ListBoxUIObject::get_name() const
+{
+ return "ListBoxUIObject";
+}
+
+OUString ListBoxUIObject::get_action(VclEventId nEvent) const
+{
+ if (nEvent == VclEventId::ListboxSelect)
+ {
+ sal_Int32 nPos = mxListBox->GetSelectedEntryPos();
+ if(get_top_parent(mxListBox)->get_id().isEmpty()){
+ //This part because if we don't have parent
+ return "Select element with position " + OUString::number(nPos) +
+ " in '" + mxListBox->get_id();
+ }
+ return "Select element with position " + OUString::number(nPos) +
+ " in '" + mxListBox->get_id() +"' from" + get_top_parent(mxListBox)->get_id() ;
+ }
+ else if (nEvent == VclEventId::ListboxFocus)
+ {
+ if(get_top_parent(mxListBox)->get_id().isEmpty())
+ {
+ //This part because if we don't have parent
+ return this->get_type() + " Action:FOCUS Id:" + mxListBox->get_id();
+ }
+ return this->get_type() + " Action:FOCUS Id:" + mxListBox->get_id() +
+ " Parent:" + get_top_parent(mxListBox)->get_id();
+ }
+ else
+ return WindowUIObject::get_action(nEvent);
+}
+
+std::unique_ptr<UIObject> ListBoxUIObject::create(vcl::Window* pWindow)
+{
+ ListBox* pListBox = dynamic_cast<ListBox*>(pWindow);
+ assert(pListBox);
+ return std::unique_ptr<UIObject>(new ListBoxUIObject(pListBox));
+}
+
+ComboBoxUIObject::ComboBoxUIObject(const VclPtr<ComboBox>& xComboBox):
+ WindowUIObject(xComboBox),
+ mxComboBox(xComboBox)
+{
+}
+
+ComboBoxUIObject::~ComboBoxUIObject()
+{
+}
+
+void ComboBoxUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction == "SELECT")
+ {
+ if (rParameters.find("POS") != rParameters.end())
+ {
+ auto itr = rParameters.find("POS");
+ OUString aVal = itr->second;
+ sal_Int32 nPos = aVal.toInt32();
+ mxComboBox->SelectEntryPos(nPos);
+ }
+ else if(rParameters.find("TEXT") != rParameters.end()){
+ auto itr = rParameters.find("TEXT");
+ OUString aVal = itr->second;
+ sal_Int32 nPos = mxComboBox->GetEntryPos(aVal);
+ mxComboBox->SelectEntryPos(nPos);
+ }
+ mxComboBox->Select();
+ }
+ else if ( rAction == "TYPE" || rAction == "SET" || rAction == "CLEAR" ){
+ if (mxComboBox->GetSubEdit())
+ {
+ Edit* pEdit = mxComboBox->GetSubEdit();
+ std::unique_ptr<UIObject> pObj = EditUIObject::create(pEdit);
+ pObj->execute(rAction, rParameters);
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+StringMap ComboBoxUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ aMap["SelectedText"] = mxComboBox->GetSelected();
+ return aMap;
+}
+
+OUString ComboBoxUIObject::get_name() const
+{
+ return "ComboBoxUIObject";
+}
+
+OUString ComboBoxUIObject::get_action(VclEventId nEvent) const
+{
+ if (nEvent == VclEventId::ComboboxSelect)
+ {
+ sal_Int32 nPos = mxComboBox->GetSelectedEntryPos();
+ if (get_top_parent(mxComboBox)->get_id().isEmpty()){
+ //This part because if we don't have parent
+ return "Select in '" + mxComboBox->get_id() +
+ "' ComboBox item number " + OUString::number(nPos);
+ }
+ return "Select in '" + mxComboBox->get_id() +
+ "' ComboBox item number " + OUString::number(nPos) +
+ " from " + get_top_parent(mxComboBox)->get_id();
+ }
+ else
+ return WindowUIObject::get_action(nEvent);
+}
+
+std::unique_ptr<UIObject> ComboBoxUIObject::create(vcl::Window* pWindow)
+{
+ ComboBox* pComboBox = dynamic_cast<ComboBox*>(pWindow);
+ assert(pComboBox);
+ return std::unique_ptr<UIObject>(new ComboBoxUIObject(pComboBox));
+}
+
+SpinUIObject::SpinUIObject(const VclPtr<SpinButton>& xSpinButton):
+ WindowUIObject(xSpinButton),
+ mxSpinButton(xSpinButton)
+{
+}
+
+SpinUIObject::~SpinUIObject()
+{
+}
+
+void SpinUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction == "UP")
+ {
+ mxSpinButton->Up();
+ }
+ else if (rAction == "DOWN")
+ {
+ mxSpinButton->Down();
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+StringMap SpinUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ aMap["Min"] = OUString::number(mxSpinButton->GetRangeMin());
+ aMap["Max"] = OUString::number(mxSpinButton->GetRangeMax());
+ aMap["Step"] = OUString::number(mxSpinButton->GetValueStep());
+ aMap["Value"] = OUString::number(mxSpinButton->GetValue());
+
+ return aMap;
+}
+
+OUString SpinUIObject::get_action(VclEventId nEvent) const
+{
+ if (nEvent == VclEventId::SpinbuttonUp)
+ {
+ return this->get_type() + " Action:UP Id:" + mxSpinButton->get_id() +
+ " Parent:" + get_top_parent(mxSpinButton)->get_id();
+ }
+ else if (nEvent == VclEventId::SpinbuttonDown)
+ {
+ return this->get_type() + " Action:DOWN Id:" + mxSpinButton->get_id() +
+ " Parent:" + get_top_parent(mxSpinButton)->get_id();
+ }
+ else
+ return WindowUIObject::get_action(nEvent);
+}
+
+OUString SpinUIObject::get_name() const
+{
+ return "SpinUIObject";
+}
+
+SpinFieldUIObject::SpinFieldUIObject(const VclPtr<SpinField>& xSpinField):
+ EditUIObject(xSpinField),
+ mxSpinField(xSpinField)
+{
+}
+
+SpinFieldUIObject::~SpinFieldUIObject()
+{
+}
+
+void SpinFieldUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction == "UP")
+ {
+ mxSpinField->Up();
+ }
+ else if (rAction == "DOWN")
+ {
+ mxSpinField->Down();
+ }
+ else if (rAction == "TYPE")
+ {
+ if (mxSpinField->GetSubEdit())
+ {
+ Edit* pSubEdit = mxSpinField->GetSubEdit();
+ EditUIObject aSubObject(pSubEdit);
+ aSubObject.execute(rAction, rParameters);
+ }
+ }
+ else
+ EditUIObject::execute(rAction, rParameters);
+}
+
+StringMap SpinFieldUIObject::get_state()
+{
+ StringMap aMap = EditUIObject::get_state();
+
+ return aMap;
+}
+
+OUString SpinFieldUIObject::get_action(VclEventId nEvent) const
+{
+ if (nEvent == VclEventId::SpinfieldUp)
+ {
+ if(get_top_parent(mxSpinField)->get_id().isEmpty())
+ {
+ //This part because if we don't have parent
+ return "Increase '" + mxSpinField->get_id();
+ }
+ return "Increase '" + mxSpinField->get_id() +
+ "' from " + get_top_parent(mxSpinField)->get_id();
+ }
+ else if (nEvent == VclEventId::SpinfieldDown)
+ {
+ if(get_top_parent(mxSpinField)->get_id().isEmpty())
+ {
+ //This part because if we don't have parent
+ return "Decrease '" + mxSpinField->get_id();
+ }
+ return "Decrease '" + mxSpinField->get_id() +
+ "' from " + get_top_parent(mxSpinField)->get_id();
+ }
+ else
+ return WindowUIObject::get_action(nEvent);
+}
+
+OUString SpinFieldUIObject::get_name() const
+{
+ return "SpinFieldUIObject";
+}
+
+std::unique_ptr<UIObject> SpinFieldUIObject::create(vcl::Window* pWindow)
+{
+ SpinField* pSpinField = dynamic_cast<SpinField*>(pWindow);
+ assert(pSpinField);
+ return std::unique_ptr<UIObject>(new SpinFieldUIObject(pSpinField));
+}
+
+
+MetricFieldUIObject::MetricFieldUIObject(const VclPtr<MetricField>& xMetricField):
+ SpinFieldUIObject(xMetricField),
+ mxMetricField(xMetricField)
+{
+}
+
+MetricFieldUIObject::~MetricFieldUIObject()
+{
+}
+
+void MetricFieldUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction == "VALUE")
+ {
+ auto itPos = rParameters.find("VALUE");
+ if (itPos != rParameters.end())
+ {
+ mxMetricField->SetValueFromString(itPos->second);
+ }
+ }
+ else
+ SpinFieldUIObject::execute(rAction, rParameters);
+}
+
+StringMap MetricFieldUIObject::get_state()
+{
+ StringMap aMap = EditUIObject::get_state();
+ aMap["Value"] = mxMetricField->GetValueString();
+
+ return aMap;
+}
+
+OUString MetricFieldUIObject::get_name() const
+{
+ return "MetricFieldUIObject";
+}
+
+std::unique_ptr<UIObject> MetricFieldUIObject::create(vcl::Window* pWindow)
+{
+ MetricField* pMetricField = dynamic_cast<MetricField*>(pWindow);
+ assert(pMetricField);
+ return std::unique_ptr<UIObject>(new MetricFieldUIObject(pMetricField));
+}
+
+FormattedFieldUIObject::FormattedFieldUIObject(const VclPtr<FormattedField>& xFormattedField):
+ SpinFieldUIObject(xFormattedField),
+ mxFormattedField(xFormattedField)
+{
+}
+
+FormattedFieldUIObject::~FormattedFieldUIObject()
+{
+}
+
+void FormattedFieldUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction == "VALUE")
+ {
+ auto itPos = rParameters.find("VALUE");
+ if (itPos != rParameters.end())
+ {
+ mxFormattedField->SetValueFromString(itPos->second);
+ }
+ }
+ else
+ SpinFieldUIObject::execute(rAction, rParameters);
+}
+
+StringMap FormattedFieldUIObject::get_state()
+{
+ StringMap aMap = EditUIObject::get_state();
+ aMap["Value"] = OUString::number(mxFormattedField->GetFormatter().GetValue());
+
+ return aMap;
+}
+
+OUString FormattedFieldUIObject::get_name() const
+{
+ return "FormattedFieldUIObject";
+}
+
+std::unique_ptr<UIObject> FormattedFieldUIObject::create(vcl::Window* pWindow)
+{
+ FormattedField* pFormattedField = dynamic_cast<FormattedField*>(pWindow);
+ assert(pFormattedField);
+ return std::unique_ptr<UIObject>(new FormattedFieldUIObject(pFormattedField));
+}
+
+TabControlUIObject::TabControlUIObject(const VclPtr<TabControl>& xTabControl):
+ WindowUIObject(xTabControl),
+ mxTabControl(xTabControl)
+{
+}
+
+TabControlUIObject::~TabControlUIObject()
+{
+}
+
+void TabControlUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction == "SELECT")
+ {
+ if (rParameters.find("POS") != rParameters.end())
+ {
+ auto itr = rParameters.find("POS");
+ sal_uInt32 nPos = itr->second.toUInt32();
+ std::vector<sal_uInt16> aIds = mxTabControl->GetPageIDs();
+ mxTabControl->SelectTabPage(aIds[nPos]);
+ }
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+StringMap TabControlUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ aMap["PageCount"] = OUString::number(mxTabControl->GetPageCount());
+
+ sal_uInt16 nPageId = mxTabControl->GetCurPageId();
+ aMap["CurrPageId"] = OUString::number(nPageId);
+ aMap["CurrPagePos"] = OUString::number(mxTabControl->GetPagePos(nPageId));
+
+ return aMap;
+}
+
+OUString TabControlUIObject::get_action(VclEventId nEvent) const
+{
+ if (nEvent == VclEventId::TabpageActivate)
+ {
+ sal_Int32 nPageId = mxTabControl->GetCurPageId();
+
+ if(get_top_parent(mxTabControl)->get_id().isEmpty()){
+ //This part because if we don't have parent
+ return "Choose Tab number " + OUString::number(mxTabControl->GetPagePos(nPageId)) +
+ " in '" + mxTabControl->get_id();
+ }
+ return "Choose Tab number " + OUString::number(mxTabControl->GetPagePos(nPageId)) +
+ " in '" + mxTabControl->get_id()+
+ "' from " + get_top_parent(mxTabControl)->get_id() ;
+ }
+ else
+ return WindowUIObject::get_action(nEvent);
+}
+
+OUString TabControlUIObject::get_name() const
+{
+ return "TabControlUIObject";
+}
+
+std::unique_ptr<UIObject> TabControlUIObject::create(vcl::Window* pWindow)
+{
+ TabControl* pTabControl = dynamic_cast<TabControl*>(pWindow);
+ assert(pTabControl);
+ return std::unique_ptr<UIObject>(new TabControlUIObject(pTabControl));
+}
+
+RoadmapWizardUIObject::RoadmapWizardUIObject(const VclPtr<vcl::RoadmapWizard>& xRoadmapWizard):
+ WindowUIObject(xRoadmapWizard),
+ mxRoadmapWizard(xRoadmapWizard)
+{
+}
+
+RoadmapWizardUIObject::~RoadmapWizardUIObject()
+{
+}
+
+void RoadmapWizardUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction == "SELECT")
+ {
+ if (rParameters.find("POS") != rParameters.end())
+ {
+ auto itr = rParameters.find("POS");
+ sal_uInt32 nPos = itr->second.toUInt32();
+ mxRoadmapWizard->SelectRoadmapItemByID(nPos);
+ }
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+StringMap RoadmapWizardUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+
+ aMap["CurrentStep"] = OUString::number(mxRoadmapWizard->GetCurrentRoadmapItemID());
+
+ return aMap;
+}
+
+OUString RoadmapWizardUIObject::get_name() const
+{
+ return "RoadmapWizardUIObject";
+}
+
+std::unique_ptr<UIObject> RoadmapWizardUIObject::create(vcl::Window* pWindow)
+{
+ vcl::RoadmapWizard* pRoadmapWizard = dynamic_cast<vcl::RoadmapWizard*>(pWindow);
+ assert(pRoadmapWizard);
+ return std::unique_ptr<UIObject>(new RoadmapWizardUIObject(pRoadmapWizard));
+}
+
+VerticalTabControlUIObject::VerticalTabControlUIObject(const VclPtr<VerticalTabControl>& xTabControl):
+ WindowUIObject(xTabControl),
+ mxTabControl(xTabControl)
+{
+}
+
+VerticalTabControlUIObject::~VerticalTabControlUIObject()
+{
+}
+
+void VerticalTabControlUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction == "SELECT")
+ {
+ if (rParameters.find("POS") != rParameters.end())
+ {
+ auto itr = rParameters.find("POS");
+ sal_uInt32 nPos = itr->second.toUInt32();
+ OString xid = mxTabControl->GetPageId(nPos);
+ mxTabControl->SetCurPageId(xid);
+ }
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+StringMap VerticalTabControlUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ aMap["PageCount"] = OUString::number(mxTabControl->GetPageCount());
+
+ OString nPageId = mxTabControl->GetCurPageId();
+ aMap["CurrPageTitel"] = mxTabControl->GetPageText(nPageId);
+ aMap["CurrPagePos"] = OUString::number(mxTabControl->GetPagePos(nPageId));
+
+ return aMap;
+}
+
+OUString VerticalTabControlUIObject::get_name() const
+{
+ return "VerticalTabControlUIObject";
+}
+
+std::unique_ptr<UIObject> VerticalTabControlUIObject::create(vcl::Window* pWindow)
+{
+ VerticalTabControl* pTabControl = dynamic_cast<VerticalTabControl*>(pWindow);
+ assert(pTabControl);
+ return std::unique_ptr<UIObject>(new VerticalTabControlUIObject(pTabControl));
+}
+
+
+ToolBoxUIObject::ToolBoxUIObject(const VclPtr<ToolBox>& xToolBox):
+ WindowUIObject(xToolBox),
+ mxToolBox(xToolBox)
+{
+}
+
+ToolBoxUIObject::~ToolBoxUIObject()
+{
+}
+
+void ToolBoxUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction == "CLICK")
+ {
+ if (rParameters.find("POS") != rParameters.end())
+ {
+ auto itr = rParameters.find("POS");
+ sal_uInt16 nPos = itr->second.toUInt32();
+ mxToolBox->SetCurItemId(mxToolBox->GetItemId(nPos));
+ mxToolBox->Click();
+ mxToolBox->Select();
+ }
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+OUString ToolBoxUIObject::get_action(VclEventId nEvent) const
+{
+ if (nEvent == VclEventId::ToolboxClick)
+ {
+ return "Click on item number " + OUString::number(sal_uInt16(mxToolBox->GetCurItemId())) +
+ " in " + mxToolBox->get_id();
+ }
+ else
+ return WindowUIObject::get_action(nEvent);
+}
+
+StringMap ToolBoxUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ aMap["CurrSelectedItemID"] = OUString::number(sal_uInt16(mxToolBox->GetCurItemId()));
+ aMap["CurrSelectedItemText"] = mxToolBox->GetItemText(mxToolBox->GetCurItemId());
+ aMap["CurrSelectedItemCommand"] = mxToolBox->GetItemCommand(mxToolBox->GetCurItemId());
+ aMap["ItemCount"] = OUString::number(mxToolBox->GetItemCount());
+ return aMap;
+}
+
+OUString ToolBoxUIObject::get_name() const
+{
+ return "ToolBoxUIObject";
+}
+
+std::unique_ptr<UIObject> ToolBoxUIObject::create(vcl::Window* pWindow)
+{
+ ToolBox* pToolBox = dynamic_cast<ToolBox*>(pWindow);
+ assert(pToolBox);
+ return std::unique_ptr<UIObject>(new ToolBoxUIObject(pToolBox));
+}
+
+MenuButtonUIObject::MenuButtonUIObject(const VclPtr<MenuButton>& xMenuButton):
+ WindowUIObject(xMenuButton),
+ mxMenuButton(xMenuButton)
+{
+}
+
+MenuButtonUIObject::~MenuButtonUIObject()
+{
+}
+
+StringMap MenuButtonUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ aMap["Label"] = mxMenuButton->GetDisplayText();
+ aMap["CurrentItem"] = OUString::createFromAscii(mxMenuButton->GetCurItemIdent());
+ return aMap;
+}
+
+void MenuButtonUIObject::execute(const OUString& rAction,
+ const StringMap& rParameters)
+{
+ if (rAction == "CLICK")
+ {
+ mxMenuButton->Check(!mxMenuButton->IsChecked());
+ mxMenuButton->Toggle();
+ }
+ else if (rAction == "OPENLIST")
+ {
+ mxMenuButton->ExecuteMenu();
+ }
+ else if (rAction == "OPENFROMLIST")
+ {
+ auto itr = rParameters.find("POS");
+ sal_uInt32 nPos = itr->second.toUInt32();
+
+ sal_uInt32 nId = mxMenuButton->GetPopupMenu()->GetItemId(nPos);
+ mxMenuButton->GetPopupMenu()->SetSelectedEntry(nId);
+ mxMenuButton->SetCurItemId();
+ mxMenuButton->Select();
+ }
+ else if (rAction == "CLOSELIST")
+ {
+ mxMenuButton->GetPopupMenu()->EndExecute();
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+OUString MenuButtonUIObject::get_name() const
+{
+ return "MenuButtonUIObject";
+}
+
+std::unique_ptr<UIObject> MenuButtonUIObject::create(vcl::Window* pWindow)
+{
+ MenuButton* pMenuButton = dynamic_cast<MenuButton*>(pWindow);
+ assert(pMenuButton);
+ return std::unique_ptr<UIObject>(new MenuButtonUIObject(pMenuButton));
+}
+
+DrawingAreaUIObject::DrawingAreaUIObject(const VclPtr<vcl::Window>& rDrawingArea)
+ : WindowUIObject(rDrawingArea)
+ , mxDrawingArea(dynamic_cast<VclDrawingArea*>(rDrawingArea.get()))
+{
+ assert(mxDrawingArea);
+ mpController = static_cast<weld::CustomWidgetController*>(mxDrawingArea->GetUserData());
+}
+
+DrawingAreaUIObject::~DrawingAreaUIObject()
+{
+}
+
+void DrawingAreaUIObject::execute(const OUString& rAction, const StringMap& rParameters)
+{
+ if (rAction == "CLICK")
+ {
+ // POSX and POSY are percentage of width/height dimensions
+ if (rParameters.find("POSX") != rParameters.end() &&
+ rParameters.find("POSY") != rParameters.end())
+ {
+ auto aPosX = rParameters.find("POSX");
+ auto aPosY = rParameters.find("POSY");
+
+ OString sPosX2 = OUStringToOString(aPosX->second, RTL_TEXTENCODING_ASCII_US);
+ OString sPoxY2 = OUStringToOString(aPosY->second, RTL_TEXTENCODING_ASCII_US);
+
+ if (!sPosX2.isEmpty() && !sPoxY2.isEmpty())
+ {
+ double fPosX = std::atof(sPosX2.getStr());
+ double fPosY = std::atof(sPoxY2.getStr());
+
+ fPosX = fPosX * mxDrawingArea->GetOutputSizePixel().Width();
+ fPosY = fPosY * mxDrawingArea->GetOutputSizePixel().Height();
+
+ MouseEvent aEvent(Point(fPosX, fPosY), 1, MouseEventModifiers::NONE, MOUSE_LEFT, 0);
+ mxDrawingArea->MouseButtonDown(aEvent);
+ mxDrawingArea->MouseButtonUp(aEvent);
+ }
+ }
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+std::unique_ptr<UIObject> DrawingAreaUIObject::create(vcl::Window* pWindow)
+{
+ VclDrawingArea* pVclDrawingArea = dynamic_cast<VclDrawingArea*>(pWindow);
+ assert(pVclDrawingArea);
+ return std::unique_ptr<UIObject>(new DrawingAreaUIObject(pVclDrawingArea));
+}
+
+IconViewUIObject::IconViewUIObject(const VclPtr<SvTreeListBox>& xIconView):
+ TreeListUIObject(xIconView)
+{
+}
+
+StringMap IconViewUIObject::get_state()
+{
+ StringMap aMap = TreeListUIObject::get_state();
+
+ SvTreeListEntry* pEntry = mxTreeList->FirstSelected();
+
+ OUString* pId = static_cast<OUString*>(pEntry->GetUserData());
+ if (pId)
+ aMap["SelectedItemId"] = *pId;
+
+ SvTreeList* pModel = mxTreeList->GetModel();
+ if (pModel)
+ aMap["SelectedItemPos"] = OUString::number(pModel->GetAbsPos(pEntry));
+
+ return aMap;
+}
+
+OUString IconViewUIObject::get_name() const
+{
+ return "IconViewUIObject";
+}
+
+std::unique_ptr<UIObject> IconViewUIObject::create(vcl::Window* pWindow)
+{
+ SvTreeListBox* pTreeList = dynamic_cast<SvTreeListBox*>(pWindow);
+ assert(pTreeList);
+ return std::unique_ptr<UIObject>(new IconViewUIObject(pTreeList));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/uitest/uitest.cxx b/vcl/source/uitest/uitest.cxx
new file mode 100644
index 000000000..f52e636f5
--- /dev/null
+++ b/vcl/source/uitest/uitest.cxx
@@ -0,0 +1,80 @@
+/* -*- 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/.
+ */
+
+#include <memory>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/uitest/uitest.hxx>
+#include <vcl/uitest/uiobject.hxx>
+
+#include <vcl/toolkit/dialog.hxx>
+
+#include <svdata.hxx>
+
+#include <comphelper/dispatchcommand.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+
+bool UITest::executeCommand(const OUString& rCommand)
+{
+ return comphelper::dispatchCommand(
+ rCommand,
+ {{"SynchronMode", -1, css::uno::Any(true),
+ css::beans::PropertyState_DIRECT_VALUE}});
+}
+
+bool UITest::executeCommandWithParameters(const OUString& rCommand,
+ const css::uno::Sequence< css::beans::PropertyValue >& rArgs)
+{
+ css::uno::Sequence< css::beans::PropertyValue > lNewArgs =
+ {{"SynchronMode", -1, css::uno::Any(true),
+ css::beans::PropertyState_DIRECT_VALUE}};
+
+ if ( rArgs.hasElements() )
+ {
+ sal_uInt32 nIndex( lNewArgs.getLength() );
+ lNewArgs.realloc( lNewArgs.getLength()+rArgs.getLength() );
+
+ std::copy(rArgs.begin(), rArgs.end(), std::next(lNewArgs.getArray(), nIndex));
+ }
+ return comphelper::dispatchCommand(rCommand,lNewArgs);
+}
+
+bool UITest::executeDialog(const OUString& rCommand)
+{
+ return comphelper::dispatchCommand(
+ rCommand,
+ {{"SynchronMode", -1, css::uno::Any(false),
+ css::beans::PropertyState_DIRECT_VALUE}});
+}
+
+std::unique_ptr<UIObject> UITest::getFocusTopWindow()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ ImplSVWinData& rWinData = *pSVData->mpWinData;
+
+ if (!rWinData.mpExecuteDialogs.empty())
+ {
+ return rWinData.mpExecuteDialogs.back()->GetUITestFactory()(rWinData.mpExecuteDialogs.back());
+ }
+
+ return pSVData->maFrameData.mpFirstFrame->GetUITestFactory()(pSVData->maFrameData.mpFirstFrame);
+}
+
+std::unique_ptr<UIObject> UITest::getFloatWindow()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ ImplSVWinData& rWinData = *pSVData->mpWinData;
+
+ VclPtr<vcl::Window> pFloatWin = rWinData.mpFirstFloat;
+ if (pFloatWin)
+ return pFloatWin->GetUITestFactory()(pFloatWin);
+
+ return nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/uitest/uno/uiobject_uno.cxx b/vcl/source/uitest/uno/uiobject_uno.cxx
new file mode 100644
index 000000000..042b2fcf5
--- /dev/null
+++ b/vcl/source/uitest/uno/uiobject_uno.cxx
@@ -0,0 +1,224 @@
+/* -*- 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/.
+ */
+
+#include <sal/config.h>
+
+#include <algorithm>
+#include <atomic>
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include "uiobject_uno.hxx"
+#include <utility>
+#include <comphelper/propertyvalue.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <tools/link.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/window.hxx>
+
+#include <set>
+
+class Timer;
+
+namespace {
+
+struct Notifier {
+ std::condition_variable cv;
+ std::mutex mMutex;
+ bool mReady = false;
+
+ DECL_LINK( NotifyHdl, Timer*, void );
+};
+
+}
+
+UIObjectUnoObj::UIObjectUnoObj(std::unique_ptr<UIObject> pObj):
+ UIObjectBase(m_aMutex),
+ mpObj(std::move(pObj))
+{
+}
+
+UIObjectUnoObj::~UIObjectUnoObj()
+{
+ SolarMutexGuard aGuard;
+ mpObj.reset();
+}
+
+css::uno::Reference<css::ui::test::XUIObject> SAL_CALL UIObjectUnoObj::getChild(const OUString& rID)
+{
+ if (!mpObj)
+ throw css::uno::RuntimeException();
+
+ SolarMutexGuard aGuard;
+ std::unique_ptr<UIObject> pObj = mpObj->get_child(rID);
+ return new UIObjectUnoObj(std::move(pObj));
+}
+
+IMPL_LINK_NOARG(Notifier, NotifyHdl, Timer*, void)
+{
+ std::scoped_lock<std::mutex> lk(mMutex);
+ mReady = true;
+ cv.notify_all();
+}
+
+namespace {
+
+class ExecuteWrapper
+{
+ std::function<void()> mFunc;
+ Link<Timer*, void> mHandler;
+ std::atomic<bool> mbSignal;
+
+public:
+
+ ExecuteWrapper(std::function<void()> func, Link<Timer*, void> handler):
+ mFunc(std::move(func)),
+ mHandler(handler),
+ mbSignal(false)
+ {
+ }
+
+ void setSignal()
+ {
+ mbSignal = true;
+ }
+
+ DECL_LINK( ExecuteActionHdl, Timer*, void );
+};
+
+
+IMPL_LINK_NOARG(ExecuteWrapper, ExecuteActionHdl, Timer*, void)
+{
+ {
+ Idle aIdle("UI Test Idle Handler2");
+ {
+ mFunc();
+ aIdle.SetPriority(TaskPriority::LOWEST);
+ aIdle.SetInvokeHandler(mHandler);
+ aIdle.Start();
+ }
+
+ while (!mbSignal) {
+ Application::Reschedule();
+ }
+ }
+ delete this;
+}
+
+}
+
+void SAL_CALL UIObjectUnoObj::executeAction(const OUString& rAction, const css::uno::Sequence<css::beans::PropertyValue>& rPropValues)
+{
+ if (!mpObj)
+ throw css::uno::RuntimeException();
+
+ auto aIdle = std::make_unique<Idle>("UI Test Idle Handler");
+ aIdle->SetPriority(TaskPriority::HIGHEST);
+
+ std::function<void()> func = [&rAction, &rPropValues, this](){
+
+ SolarMutexGuard aGuard;
+ StringMap aMap;
+ for (const auto& rPropVal : rPropValues)
+ {
+ OUString aVal;
+ if (!(rPropVal.Value >>= aVal))
+ continue;
+
+ aMap[rPropVal.Name] = aVal;
+ }
+ mpObj->execute(rAction, aMap);
+ };
+
+ Notifier notifier;
+ ExecuteWrapper* pWrapper = new ExecuteWrapper(std::move(func), LINK(&notifier, Notifier, NotifyHdl));
+ aIdle->SetInvokeHandler(LINK(pWrapper, ExecuteWrapper, ExecuteActionHdl));
+ {
+ SolarMutexGuard aGuard;
+ aIdle->Start();
+ }
+
+ {
+ std::unique_lock<std::mutex> lk(notifier.mMutex);
+ notifier.cv.wait(lk, [&notifier]{return notifier.mReady;});
+ }
+ pWrapper->setSignal();
+
+ SolarMutexGuard aGuard;
+ aIdle.reset();
+}
+
+css::uno::Sequence<css::beans::PropertyValue> UIObjectUnoObj::getState()
+{
+ if (!mpObj)
+ throw css::uno::RuntimeException();
+
+ SolarMutexGuard aGuard;
+ StringMap aMap = mpObj->get_state();
+ css::uno::Sequence<css::beans::PropertyValue> aProps(aMap.size());
+ std::transform(aMap.begin(), aMap.end(), aProps.getArray(),
+ [](auto const& elem)
+ { return comphelper::makePropertyValue(elem.first, elem.second); });
+
+ return aProps;
+}
+
+css::uno::Sequence<OUString> UIObjectUnoObj::getChildren()
+{
+ if (!mpObj)
+ throw css::uno::RuntimeException();
+
+ std::set<OUString> aChildren;
+
+ {
+ SolarMutexGuard aGuard;
+ aChildren = mpObj->get_children();
+ }
+
+ css::uno::Sequence<OUString> aRet(aChildren.size());
+ std::copy(aChildren.begin(), aChildren.end(), aRet.getArray());
+
+ return aRet;
+}
+
+OUString SAL_CALL UIObjectUnoObj::getType()
+{
+ if (!mpObj)
+ throw css::uno::RuntimeException();
+
+ SolarMutexGuard aGuard;
+ return mpObj->get_type();
+}
+
+OUString SAL_CALL UIObjectUnoObj::getImplementationName()
+{
+ return "org.libreoffice.uitest.UIObject";
+}
+
+sal_Bool UIObjectUnoObj::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> UIObjectUnoObj::getSupportedServiceNames()
+{
+ return { "com.sun.star.ui.test.UIObject" };
+}
+
+OUString SAL_CALL UIObjectUnoObj::getHierarchy()
+{
+ if (!mpObj)
+ throw css::uno::RuntimeException();
+
+ SolarMutexGuard aGuard;
+ return mpObj->dumpHierarchy();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/uitest/uno/uiobject_uno.hxx b/vcl/source/uitest/uno/uiobject_uno.hxx
new file mode 100644
index 000000000..47fc54696
--- /dev/null
+++ b/vcl/source/uitest/uno/uiobject_uno.hxx
@@ -0,0 +1,55 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/basemutex.hxx>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/ui/test/XUIObject.hpp>
+
+#include <memory>
+
+#include <vcl/uitest/uiobject.hxx>
+
+typedef ::cppu::WeakComponentImplHelper <
+ css::ui::test::XUIObject, css::lang::XServiceInfo
+ > UIObjectBase;
+
+class UIObjectUnoObj : public cppu::BaseMutex,
+ public UIObjectBase
+{
+private:
+ std::unique_ptr<UIObject> mpObj;
+
+public:
+
+ explicit UIObjectUnoObj(std::unique_ptr<UIObject> pObj);
+ virtual ~UIObjectUnoObj() override;
+
+ css::uno::Reference<css::ui::test::XUIObject> SAL_CALL getChild(const OUString& rID) override;
+
+ void SAL_CALL executeAction(const OUString& rAction, const css::uno::Sequence<css::beans::PropertyValue>& xPropValues) override;
+
+ css::uno::Sequence<css::beans::PropertyValue> SAL_CALL getState() override;
+
+ css::uno::Sequence<OUString> SAL_CALL getChildren() override;
+
+ OUString SAL_CALL getType() override;
+
+ OUString SAL_CALL getImplementationName() override;
+
+ sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override;
+
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+ OUString SAL_CALL getHierarchy() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/uitest/uno/uitest_uno.cxx b/vcl/source/uitest/uno/uitest_uno.cxx
new file mode 100644
index 000000000..4e5a6e0e0
--- /dev/null
+++ b/vcl/source/uitest/uno/uitest_uno.cxx
@@ -0,0 +1,121 @@
+/* -*- 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/.
+ */
+
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/ui/test/XUITest.hpp>
+
+#include <memory>
+
+#include <vcl/uitest/uitest.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+
+#include "uiobject_uno.hxx"
+
+namespace
+{
+ typedef ::cppu::WeakComponentImplHelper <
+ css::ui::test::XUITest, css::lang::XServiceInfo
+ > UITestBase;
+
+class UITestUnoObj : public cppu::BaseMutex,
+ public UITestBase
+{
+private:
+ std::unique_ptr<UITest> mpUITest;
+
+public:
+
+ UITestUnoObj();
+
+ sal_Bool SAL_CALL executeCommand(const OUString& rCommand) override;
+
+ sal_Bool SAL_CALL executeCommandWithParameters(const OUString& rCommand,
+ const css::uno::Sequence< css::beans::PropertyValue >& rArgs) override;
+
+ sal_Bool SAL_CALL executeDialog(const OUString& rCommand) override;
+
+ css::uno::Reference<css::ui::test::XUIObject> SAL_CALL getTopFocusWindow() override;
+
+ css::uno::Reference<css::ui::test::XUIObject> SAL_CALL getFloatWindow() override;
+
+ OUString SAL_CALL getImplementationName() override;
+
+ sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override;
+
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+};
+
+}
+
+UITestUnoObj::UITestUnoObj():
+ UITestBase(m_aMutex),
+ mpUITest(new UITest)
+{
+}
+
+sal_Bool SAL_CALL UITestUnoObj::executeCommand(const OUString& rCommand)
+{
+ SolarMutexGuard aGuard;
+ return UITest::executeCommand(rCommand);
+}
+
+sal_Bool SAL_CALL UITestUnoObj::executeCommandWithParameters(const OUString& rCommand,
+ const css::uno::Sequence< css::beans::PropertyValue >& rArgs)
+{
+ SolarMutexGuard aGuard;
+ return UITest::executeCommandWithParameters(rCommand,rArgs);
+}
+
+sal_Bool SAL_CALL UITestUnoObj::executeDialog(const OUString& rCommand)
+{
+ SolarMutexGuard aGuard;
+ return UITest::executeDialog(rCommand);
+}
+
+css::uno::Reference<css::ui::test::XUIObject> SAL_CALL UITestUnoObj::getTopFocusWindow()
+{
+ SolarMutexGuard aGuard;
+ std::unique_ptr<UIObject> pObj = UITest::getFocusTopWindow();
+ return new UIObjectUnoObj(std::move(pObj));
+}
+
+css::uno::Reference<css::ui::test::XUIObject> SAL_CALL UITestUnoObj::getFloatWindow()
+{
+ SolarMutexGuard aGuard;
+ std::unique_ptr<UIObject> pObj = UITest::getFloatWindow();
+ return new UIObjectUnoObj(std::move(pObj));
+}
+
+OUString SAL_CALL UITestUnoObj::getImplementationName()
+{
+ return "org.libreoffice.uitest.UITest";
+}
+
+sal_Bool UITestUnoObj::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> UITestUnoObj::getSupportedServiceNames()
+{
+ return { "com.sun.star.ui.test.UITest" };
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+UITest_get_implementation(css::uno::XComponentContext*, css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new UITestUnoObj());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/EnumContext.cxx b/vcl/source/window/EnumContext.cxx
new file mode 100644
index 000000000..6ca075eb6
--- /dev/null
+++ b/vcl/source/window/EnumContext.cxx
@@ -0,0 +1,215 @@
+/* -*- 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 <vcl/EnumContext.hxx>
+
+#include <osl/diagnose.h>
+#include <o3tl/enumarray.hxx>
+
+#include <map>
+
+namespace vcl {
+
+namespace {
+
+typedef ::std::map<OUString,EnumContext::Application> ApplicationMap;
+
+ApplicationMap maApplicationMap;
+o3tl::enumarray<EnumContext::Application, OUString> maApplicationVector;
+
+typedef ::std::map<OUString,EnumContext::Context> ContextMap;
+
+ContextMap maContextMap;
+o3tl::enumarray<EnumContext::Context, OUString> maContextVector;
+
+}
+
+const sal_Int32 EnumContext::NoMatch = 4;
+
+EnumContext::EnumContext()
+ : meApplication(Application::NONE),
+ meContext(Context::Unknown)
+{
+}
+
+EnumContext::EnumContext (
+ const Application eApplication,
+ const Context eContext)
+ : meApplication(eApplication),
+ meContext(eContext)
+{
+}
+
+sal_Int32 EnumContext::GetCombinedContext_DI() const
+{
+ return CombinedEnumContext(GetApplication_DI(), meContext);
+}
+
+EnumContext::Application EnumContext::GetApplication_DI() const
+{
+ switch (meApplication)
+ {
+ case Application::Draw:
+ case Application::Impress:
+ return Application::DrawImpress;
+
+ case Application::Writer:
+ case Application::WriterGlobal:
+ case Application::WriterWeb:
+ case Application::WriterXML:
+ case Application::WriterForm:
+ case Application::WriterReport:
+ return Application::WriterVariants;
+
+ default:
+ return meApplication;
+ }
+}
+
+bool EnumContext::operator== (const EnumContext& rOther) const
+{
+ return meApplication==rOther.meApplication
+ && meContext==rOther.meContext;
+}
+
+bool EnumContext::operator!= (const EnumContext& rOther) const
+{
+ return meApplication!=rOther.meApplication
+ || meContext!=rOther.meContext;
+}
+
+void EnumContext::AddEntry (const OUString& rsName, const Application eApplication)
+{
+ maApplicationMap[rsName] = eApplication;
+ OSL_ASSERT(eApplication<=Application::LAST);
+ maApplicationVector[eApplication]=rsName;
+}
+
+void EnumContext::ProvideApplicationContainers()
+{
+ if (!maApplicationMap.empty())
+ return;
+
+ AddEntry("com.sun.star.text.TextDocument", EnumContext::Application::Writer);
+ AddEntry("com.sun.star.text.GlobalDocument", EnumContext::Application::WriterGlobal);
+ AddEntry("com.sun.star.text.WebDocument", EnumContext::Application::WriterWeb);
+ AddEntry("com.sun.star.xforms.XMLFormDocument", EnumContext::Application::WriterXML);
+ AddEntry("com.sun.star.sdb.FormDesign", EnumContext::Application::WriterForm);
+ AddEntry("com.sun.star.sdb.TextReportDesign", EnumContext::Application::WriterReport);
+ AddEntry("com.sun.star.sheet.SpreadsheetDocument", EnumContext::Application::Calc);
+ AddEntry("com.sun.star.chart2.ChartDocument", EnumContext::Application::Chart);
+ AddEntry("com.sun.star.drawing.DrawingDocument", EnumContext::Application::Draw);
+ AddEntry("com.sun.star.presentation.PresentationDocument", EnumContext::Application::Impress);
+ AddEntry("com.sun.star.formula.FormulaProperties", EnumContext::Application::Formula);
+ AddEntry("com.sun.star.sdb.OfficeDatabaseDocument", EnumContext::Application::Base);
+ AddEntry("any", EnumContext::Application::Any);
+ AddEntry("none", EnumContext::Application::NONE);
+
+}
+
+EnumContext::Application EnumContext::GetApplicationEnum (const OUString& rsApplicationName)
+{
+ ProvideApplicationContainers();
+
+ ApplicationMap::const_iterator iApplication(
+ maApplicationMap.find(rsApplicationName));
+ if (iApplication != maApplicationMap.end())
+ return iApplication->second;
+ else
+ return EnumContext::Application::NONE;
+}
+
+const OUString& EnumContext::GetApplicationName (const Application eApplication)
+{
+ ProvideApplicationContainers();
+ return maApplicationVector[eApplication];
+}
+
+void EnumContext::AddEntry (const OUString& rsName, const Context eContext)
+{
+ maContextMap[rsName] = eContext;
+ maContextVector[eContext] = rsName;
+}
+
+void EnumContext::ProvideContextContainers()
+{
+ if (!maContextMap.empty())
+ return;
+
+ AddEntry("3DObject", Context::ThreeDObject);
+ AddEntry("Annotation", Context::Annotation);
+ AddEntry("Auditing", Context::Auditing);
+ AddEntry("Axis", Context::Axis);
+ AddEntry("Cell", Context::Cell);
+ AddEntry("Chart", Context::Chart);
+ AddEntry("ChartElements", Context::ChartElements);
+ AddEntry("Draw", Context::Draw);
+ AddEntry("DrawFontwork", Context::DrawFontwork);
+ AddEntry("DrawLine", Context::DrawLine);
+ AddEntry("DrawPage", Context::DrawPage);
+ AddEntry("DrawText", Context::DrawText);
+ AddEntry("EditCell", Context::EditCell);
+ AddEntry("ErrorBar", Context::ErrorBar);
+ AddEntry("Form", Context::Form);
+ AddEntry("Frame", Context::Frame);
+ AddEntry("Graphic", Context::Graphic);
+ AddEntry("Grid", Context::Grid);
+ AddEntry("HandoutPage", Context::HandoutPage);
+ AddEntry("MasterPage", Context::MasterPage);
+ AddEntry("Math", Context::Math);
+ AddEntry("Media", Context::Media);
+ AddEntry("MultiObject", Context::MultiObject);
+ AddEntry("NotesPage", Context::NotesPage);
+ AddEntry("OLE", Context::OLE);
+ AddEntry("OutlineText", Context::OutlineText);
+ AddEntry("Pivot", Context::Pivot);
+ AddEntry("Printpreview", Context::Printpreview);
+ AddEntry("Series", Context::Series);
+ AddEntry("SlidesorterPage", Context::SlidesorterPage);
+ AddEntry("Table", Context::Table);
+ AddEntry("Text", Context::Text);
+ AddEntry("TextObject", Context::TextObject);
+ AddEntry("Trendline", Context::Trendline);
+ AddEntry("Sparkline", Context::Sparkline);
+
+ // other general contexts
+ AddEntry("any", Context::Any);
+ AddEntry("default", Context::Default);
+ AddEntry("empty", Context::Empty);
+}
+
+EnumContext::Context EnumContext::GetContextEnum (const OUString& rsContextName)
+{
+ ProvideContextContainers();
+
+ ContextMap::const_iterator iContext( maContextMap.find(rsContextName) );
+ if (iContext != maContextMap.end())
+ return iContext->second;
+ else
+ return EnumContext::Context::Unknown;
+}
+
+const OUString& EnumContext::GetContextName (const Context eContext)
+{
+ ProvideContextContainers();
+ return maContextVector[eContext];
+}
+
+} // end of namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/NotebookBarAddonsMerger.cxx b/vcl/source/window/NotebookBarAddonsMerger.cxx
new file mode 100644
index 000000000..274b455bd
--- /dev/null
+++ b/vcl/source/window/NotebookBarAddonsMerger.cxx
@@ -0,0 +1,184 @@
+/* -*- 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 <cstddef>
+
+#include <vcl/notebookbar/NotebookBarAddonsMerger.hxx>
+#include <vcl/commandinfoprovider.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/vclenum.hxx>
+#include <vcl/toolbox.hxx>
+#include <IPrioritable.hxx>
+#include <OptionalBox.hxx>
+
+const char STYLE_TEXT[] = "Text";
+const char STYLE_ICON[] = "Icon";
+
+const char MERGE_NOTEBOOKBAR_URL[] = "URL";
+const char MERGE_NOTEBOOKBAR_TITLE[] = "Title";
+const char MERGE_NOTEBOOKBAR_CONTEXT[] = "Context";
+const char MERGE_NOTEBOOKBAR_TARGET[] = "Target";
+const char MERGE_NOTEBOOKBAR_CONTROLTYPE[] = "ControlType";
+const char MERGE_NOTEBOOKBAR_WIDTH[] = "Width";
+const char MERGE_NOTEBOOKBAR_STYLE[] = "Style";
+
+static void GetAddonNotebookBarItem(const css::uno::Sequence<css::beans::PropertyValue>& pExtension,
+ AddonNotebookBarItem& aAddonNotebookBarItem)
+{
+ for (const auto& i : pExtension)
+ {
+ if (i.Name == MERGE_NOTEBOOKBAR_URL)
+ i.Value >>= aAddonNotebookBarItem.sCommandURL;
+ else if (i.Name == MERGE_NOTEBOOKBAR_TITLE)
+ i.Value >>= aAddonNotebookBarItem.sLabel;
+ else if (i.Name == MERGE_NOTEBOOKBAR_CONTEXT)
+ i.Value >>= aAddonNotebookBarItem.sContext;
+ else if (i.Name == MERGE_NOTEBOOKBAR_TARGET)
+ i.Value >>= aAddonNotebookBarItem.sTarget;
+ else if (i.Name == MERGE_NOTEBOOKBAR_CONTROLTYPE)
+ i.Value >>= aAddonNotebookBarItem.sControlType;
+ else if (i.Name == MERGE_NOTEBOOKBAR_WIDTH)
+ i.Value >>= aAddonNotebookBarItem.nWidth;
+ else if (i.Name == MERGE_NOTEBOOKBAR_STYLE)
+ i.Value >>= aAddonNotebookBarItem.sStyle;
+ }
+}
+
+static void CreateNotebookBarToolBox(vcl::Window* pNotebookbarToolBox,
+ const css::uno::Reference<css::frame::XFrame>& m_xFrame,
+ const AddonNotebookBarItem& aAddonNotebookBarItem,
+ const std::vector<Image>& aImageVec, const tools::ULong nIter)
+{
+ ToolBoxItemId nItemId(0);
+ ToolBox* pToolbox = dynamic_cast<ToolBox*>(pNotebookbarToolBox);
+ if (!pToolbox)
+ return;
+
+ pToolbox->InsertSeparator();
+ pToolbox->Show();
+ Size aSize(0, 0);
+ Image sImage;
+ pToolbox->InsertItem(aAddonNotebookBarItem.sCommandURL, m_xFrame, ToolBoxItemBits::NONE, aSize);
+ nItemId = pToolbox->GetItemId(aAddonNotebookBarItem.sCommandURL);
+ pToolbox->SetItemCommand(nItemId, aAddonNotebookBarItem.sCommandURL);
+ pToolbox->SetQuickHelpText(nItemId, aAddonNotebookBarItem.sLabel);
+
+ if (nIter < aImageVec.size())
+ {
+ sImage = aImageVec[nIter];
+ if (!sImage)
+ {
+ sImage = vcl::CommandInfoProvider::GetImageForCommand(aAddonNotebookBarItem.sCommandURL,
+ m_xFrame);
+ }
+ }
+
+ if (aAddonNotebookBarItem.sStyle == STYLE_TEXT)
+ pToolbox->SetItemText(nItemId, aAddonNotebookBarItem.sLabel);
+ else if (aAddonNotebookBarItem.sStyle == STYLE_ICON)
+ pToolbox->SetItemImage(nItemId, sImage);
+ else
+ {
+ pToolbox->SetItemText(nItemId, aAddonNotebookBarItem.sLabel);
+ pToolbox->SetItemImage(nItemId, sImage);
+ }
+ pToolbox->Show();
+}
+
+namespace NotebookBarAddonsMerger
+{
+void MergeNotebookBarAddons(vcl::Window* pParent, const VclBuilder::customMakeWidget& pFunction,
+ const css::uno::Reference<css::frame::XFrame>& m_xFrame,
+ const NotebookBarAddonsItem& aNotebookBarAddonsItem,
+ VclBuilder::stringmap& rMap)
+{
+ std::vector<Image> aImageVec = aNotebookBarAddonsItem.aImageValues;
+ tools::ULong nIter = 0;
+ sal_uInt16 nPriorityIdx = aImageVec.size();
+ css::uno::Sequence<css::uno::Sequence<css::beans::PropertyValue>> aExtension;
+ for (std::size_t nIdx = 0; nIdx < aNotebookBarAddonsItem.aAddonValues.size(); nIdx++)
+ {
+ aExtension = aNotebookBarAddonsItem.aAddonValues[nIdx];
+
+ for (const css::uno::Sequence<css::beans::PropertyValue>& pExtension :
+ std::as_const(aExtension))
+ {
+ VclPtr<vcl::Window> pOptionalParent;
+ pOptionalParent = VclPtr<OptionalBox>::Create(pParent);
+ pOptionalParent->Show();
+
+ vcl::IPrioritable* pPrioritable
+ = dynamic_cast<vcl::IPrioritable*>(pOptionalParent.get());
+ if (pPrioritable)
+ pPrioritable->SetPriority(nPriorityIdx - nIter);
+
+ VclPtr<vcl::Window> pNotebookbarToolBox;
+ pFunction(pNotebookbarToolBox, pOptionalParent, rMap);
+
+ AddonNotebookBarItem aAddonNotebookBarItem;
+ GetAddonNotebookBarItem(pExtension, aAddonNotebookBarItem);
+
+ CreateNotebookBarToolBox(pNotebookbarToolBox, m_xFrame, aAddonNotebookBarItem,
+ aImageVec, nIter);
+ nIter++;
+ }
+ }
+}
+
+void MergeNotebookBarMenuAddons(Menu* pPopupMenu, sal_Int16 nItemId, const OString& sItemIdName,
+ NotebookBarAddonsItem& aNotebookBarAddonsItem)
+{
+ std::vector<Image> aImageVec = aNotebookBarAddonsItem.aImageValues;
+ tools::ULong nIter = 0;
+ css::uno::Sequence<css::uno::Sequence<css::beans::PropertyValue>> aExtension;
+ for (std::size_t nIdx = 0; nIdx < aNotebookBarAddonsItem.aAddonValues.size(); nIdx++)
+ {
+ aExtension = aNotebookBarAddonsItem.aAddonValues[nIdx];
+
+ for (int nSecIdx = 0; nSecIdx < aExtension.getLength(); nSecIdx++)
+ {
+ AddonNotebookBarItem aAddonNotebookBarItem;
+ Image sImage;
+ MenuItemBits nBits = MenuItemBits::ICON;
+ const css::uno::Sequence<css::beans::PropertyValue> pExtension = aExtension[nSecIdx];
+
+ GetAddonNotebookBarItem(pExtension, aAddonNotebookBarItem);
+
+ pPopupMenu->InsertItem(nItemId, aAddonNotebookBarItem.sLabel, nBits, sItemIdName);
+ pPopupMenu->SetItemCommand(nItemId, aAddonNotebookBarItem.sCommandURL);
+
+ if (nIter < aImageVec.size())
+ {
+ sImage = aImageVec[nIter];
+ nIter++;
+ }
+ pPopupMenu->SetItemImage(nItemId, sImage);
+
+ if (nSecIdx == aExtension.getLength() - 1)
+ pPopupMenu->InsertSeparator();
+
+ ++nItemId;
+ }
+ }
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/OptionalBox.cxx b/vcl/source/window/OptionalBox.cxx
new file mode 100644
index 000000000..28055f7e2
--- /dev/null
+++ b/vcl/source/window/OptionalBox.cxx
@@ -0,0 +1,62 @@
+/* -*- 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 <vcl/layout.hxx>
+#include <OptionalBox.hxx>
+
+/*
+ * OptionalBox - shows or hides the content. To use with PriorityHBox
+ * or PriorityMergedHBox
+ */
+
+OptionalBox::OptionalBox(vcl::Window* pParent)
+ : VclHBox(pParent)
+ , m_bInFullView(true)
+{
+}
+
+OptionalBox::~OptionalBox() { disposeOnce(); }
+
+void OptionalBox::HideContent()
+{
+ if (m_bInFullView)
+ {
+ m_bInFullView = false;
+
+ for (int i = 0; i < GetChildCount(); i++)
+ GetChild(i)->Hide();
+
+ SetOutputSizePixel(Size(10, GetSizePixel().Height()));
+ }
+}
+
+void OptionalBox::ShowContent()
+{
+ if (!m_bInFullView)
+ {
+ m_bInFullView = true;
+
+ for (int i = 0; i < GetChildCount(); i++)
+ GetChild(i)->Show();
+ }
+}
+
+bool OptionalBox::IsHidden() { return !m_bInFullView; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/abstdlg.cxx b/vcl/source/window/abstdlg.cxx
new file mode 100644
index 000000000..29d79b0cd
--- /dev/null
+++ b/vcl/source/window/abstdlg.cxx
@@ -0,0 +1,85 @@
+/* -*- 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 <osl/module.hxx>
+#include <vcl/abstdlg.hxx>
+#include <vcl/bitmapex.hxx>
+
+typedef VclAbstractDialogFactory*(SAL_CALL* FuncPtrCreateDialogFactory)();
+
+#ifndef DISABLE_DYNLOADING
+extern "C" {
+static void thisModule() {}
+}
+#else
+extern "C" VclAbstractDialogFactory* CreateDialogFactory();
+#endif
+
+VclAbstractDialogFactory* VclAbstractDialogFactory::Create()
+{
+ static auto fp = []() -> FuncPtrCreateDialogFactory {
+#ifndef DISABLE_DYNLOADING
+ ::osl::Module aDialogLibrary;
+ if (aDialogLibrary.loadRelative(&thisModule, CUI_DLL_NAME,
+ SAL_LOADMODULE_GLOBAL | SAL_LOADMODULE_LAZY))
+ {
+ auto const p = reinterpret_cast<FuncPtrCreateDialogFactory>(
+ aDialogLibrary.getFunctionSymbol("CreateDialogFactory"));
+ aDialogLibrary.release();
+ return p;
+ }
+ return nullptr;
+#else
+ return CreateDialogFactory;
+#endif
+ }();
+ if (fp)
+ return fp();
+ return nullptr;
+}
+
+VclAbstractDialog::~VclAbstractDialog() {}
+
+bool VclAbstractDialog::StartExecuteAsync(AsyncContext&)
+{
+ assert(false);
+ return false;
+}
+
+std::vector<OString> VclAbstractDialog::getAllPageUIXMLDescriptions() const
+{
+ // default has no pages
+ return std::vector<OString>();
+}
+
+bool VclAbstractDialog::selectPageByUIXMLDescription(const OString& /*rUIXMLDescription*/)
+{
+ // default cannot select a page (which is okay, return true)
+ return true;
+}
+
+BitmapEx VclAbstractDialog::createScreenshot() const
+{
+ // default returns empty bitmap
+ return BitmapEx();
+}
+
+VclAbstractDialogFactory::~VclAbstractDialogFactory() {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/accel.cxx b/vcl/source/window/accel.cxx
new file mode 100644
index 000000000..a20c289f2
--- /dev/null
+++ b/vcl/source/window/accel.cxx
@@ -0,0 +1,279 @@
+/* -*- 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/log.hxx>
+#include <osl/diagnose.h>
+#include <accel.hxx>
+
+#define ACCELENTRY_NOTFOUND (sal_uInt16(0xFFFF))
+
+static sal_uInt16 ImplAccelEntryGetIndex( const ImplAccelList* pList, sal_uInt16 nId,
+ sal_uInt16* pIndex = nullptr )
+{
+ size_t nLow;
+ size_t nHigh;
+ size_t nMid;
+ size_t nCount = pList->size();
+ sal_uInt16 nCompareId;
+
+ // check if first key is larger then the key to compare
+ if ( !nCount || (nId < (*pList)[ 0 ]->mnId) )
+ {
+ if ( pIndex )
+ *pIndex = 0;
+ return ACCELENTRY_NOTFOUND;
+ }
+
+ // Binary search
+ nLow = 0;
+ nHigh = nCount-1;
+ do
+ {
+ nMid = (nLow + nHigh) / 2;
+ nCompareId = (*pList)[ nMid ]->mnId;
+ if ( nId < nCompareId )
+ nHigh = nMid-1;
+ else
+ {
+ if ( nId > nCompareId )
+ nLow = nMid + 1;
+ else
+ return static_cast<sal_uInt16>(nMid);
+ }
+ }
+ while ( nLow <= nHigh );
+
+ if ( pIndex )
+ {
+ if ( nId > nCompareId )
+ *pIndex = static_cast<sal_uInt16>(nMid+1);
+ else
+ *pIndex = static_cast<sal_uInt16>(nMid);
+ }
+
+ return ACCELENTRY_NOTFOUND;
+}
+
+static void ImplAccelEntryInsert( ImplAccelList* pList, std::unique_ptr<ImplAccelEntry> pEntry )
+{
+ sal_uInt16 nInsIndex(0);
+ std::vector<ImplAccelEntry *>::size_type nIndex = ImplAccelEntryGetIndex( pList, pEntry->mnId, &nInsIndex );
+
+ if ( nIndex != ACCELENTRY_NOTFOUND )
+ {
+ do
+ {
+ nIndex++;
+ ImplAccelEntry* pTempEntry = nullptr;
+ if ( nIndex < pList->size() )
+ pTempEntry = (*pList)[ nIndex ].get();
+ if ( !pTempEntry || (pTempEntry->mnId != pEntry->mnId) )
+ break;
+ }
+ while ( nIndex < pList->size() );
+
+ if ( nIndex < pList->size() ) {
+ pList->insert( pList->begin() + nIndex, std::move(pEntry) );
+ } else {
+ pList->push_back( std::move(pEntry) );
+ }
+ }
+ else {
+ if ( nInsIndex < pList->size() ) {
+ pList->insert( pList->begin() + nInsIndex, std::move(pEntry) );
+ } else {
+ pList->push_back( std::move(pEntry) );
+ }
+ }
+}
+
+void Accelerator::ImplInit()
+{
+ mnCurId = 0;
+ mpDel = nullptr;
+}
+
+ImplAccelEntry* Accelerator::ImplGetAccelData( const vcl::KeyCode& rKeyCode ) const
+{
+ auto it = maKeyMap.find( rKeyCode.GetFullCode() );
+ if( it != maKeyMap.end() )
+ return it->second;
+ else
+ return nullptr;
+}
+
+void Accelerator::ImplCopyData( const Accelerator& rAccelData )
+{
+ // copy table
+ for (const std::unique_ptr<ImplAccelEntry>& i : rAccelData.maIdList)
+ {
+ std::unique_ptr<ImplAccelEntry> pEntry(new ImplAccelEntry( *i ));
+
+ // sequence accelerator, then copy also
+ if ( pEntry->mpAccel )
+ {
+ pEntry->mpAccel = new Accelerator( *(pEntry->mpAccel) );
+ pEntry->mpAutoAccel = pEntry->mpAccel;
+ }
+ else
+ pEntry->mpAutoAccel = nullptr;
+
+ maKeyMap.insert( std::make_pair( pEntry->maKeyCode.GetFullCode(), pEntry.get() ) );
+ maIdList.push_back( std::move(pEntry) );
+ }
+}
+
+void Accelerator::ImplDeleteData()
+{
+ // delete accelerator-entries using the id-table
+ for (const std::unique_ptr<ImplAccelEntry>& pEntry : maIdList) {
+ delete pEntry->mpAutoAccel;
+ }
+ maIdList.clear();
+}
+
+void Accelerator::ImplInsertAccel( sal_uInt16 nItemId, const vcl::KeyCode& rKeyCode,
+ bool bEnable, Accelerator* pAutoAccel )
+{
+ SAL_WARN_IF( !nItemId, "vcl", "Accelerator::InsertItem(): ItemId == 0" );
+
+ if ( rKeyCode.IsFunction() )
+ {
+ sal_uInt16 nCode1;
+ sal_uInt16 nCode2;
+ sal_uInt16 nCode3;
+ sal_uInt16 nCode4;
+ ImplGetKeyCode( rKeyCode.GetFunction(), nCode1, nCode2, nCode3, nCode4 );
+ if ( nCode1 )
+ ImplInsertAccel( nItemId, vcl::KeyCode( nCode1, nCode1 ), bEnable, pAutoAccel );
+ if ( nCode2 )
+ {
+ if ( pAutoAccel )
+ pAutoAccel = new Accelerator( *pAutoAccel );
+ ImplInsertAccel( nItemId, vcl::KeyCode( nCode2, nCode2 ), bEnable, pAutoAccel );
+ if ( nCode3 )
+ {
+ if ( pAutoAccel )
+ pAutoAccel = new Accelerator( *pAutoAccel );
+ ImplInsertAccel( nItemId, vcl::KeyCode( nCode3, nCode3 ), bEnable, pAutoAccel );
+ }
+ }
+ return;
+ }
+
+ // fetch and fill new entries
+ std::unique_ptr<ImplAccelEntry> pEntry(new ImplAccelEntry);
+ pEntry->mnId = nItemId;
+ pEntry->maKeyCode = rKeyCode;
+ pEntry->mpAccel = pAutoAccel;
+ pEntry->mpAutoAccel = pAutoAccel;
+ pEntry->mbEnabled = bEnable;
+
+ // now into the tables
+ sal_uLong nCode = rKeyCode.GetFullCode();
+ if ( !nCode )
+ {
+ OSL_FAIL( "Accelerator::InsertItem(): KeyCode with KeyCode 0 not allowed" );
+ }
+ else if ( !maKeyMap.insert( std::make_pair( nCode, pEntry.get() ) ).second )
+ {
+ SAL_WARN( "vcl", "Accelerator::InsertItem(): KeyCode (Key: " << nCode << ") already exists" );
+ }
+ else
+ ImplAccelEntryInsert( &maIdList, std::move(pEntry) );
+}
+
+Accelerator::Accelerator()
+{
+ ImplInit();
+}
+
+Accelerator::Accelerator(const Accelerator& rAccel)
+{
+ ImplInit();
+ ImplCopyData(rAccel);
+}
+
+Accelerator::~Accelerator()
+{
+
+ // inform AccelManager about deleting the Accelerator
+ if ( mpDel )
+ *mpDel = true;
+
+ ImplDeleteData();
+}
+
+void Accelerator::Activate()
+{
+ maActivateHdl.Call( *this );
+}
+
+void Accelerator::Select()
+{
+ maSelectHdl.Call( *this );
+}
+
+void Accelerator::InsertItem( sal_uInt16 nItemId, const vcl::KeyCode& rKeyCode )
+{
+ ImplInsertAccel( nItemId, rKeyCode, true, nullptr );
+}
+
+sal_uInt16 Accelerator::GetItemCount() const
+{
+ return static_cast<sal_uInt16>(maIdList.size());
+}
+
+sal_uInt16 Accelerator::GetItemId( sal_uInt16 nPos ) const
+{
+
+ ImplAccelEntry* pEntry = ( nPos < maIdList.size() ) ? maIdList[ nPos ].get() : nullptr;
+ if ( pEntry )
+ return pEntry->mnId;
+ else
+ return 0;
+}
+
+Accelerator* Accelerator::GetAccel( sal_uInt16 nItemId ) const
+{
+
+ sal_uInt16 nIndex = ImplAccelEntryGetIndex( &maIdList, nItemId );
+ if ( nIndex != ACCELENTRY_NOTFOUND )
+ return maIdList[ nIndex ]->mpAccel;
+ else
+ return nullptr;
+}
+
+Accelerator& Accelerator::operator=( const Accelerator& rAccel )
+{
+ if(this == &rAccel)
+ return *this;
+
+ // assign new data
+ mnCurId = 0;
+
+ // delete and copy tables
+ ImplDeleteData();
+ maKeyMap.clear();
+ ImplCopyData(rAccel);
+
+ return *this;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/accessibility.cxx b/vcl/source/window/accessibility.cxx
new file mode 100644
index 000000000..d332da62a
--- /dev/null
+++ b/vcl/source/window/accessibility.cxx
@@ -0,0 +1,612 @@
+/* -*- 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 <vcl/layout.hxx>
+#include <vcl/toolkit/fixed.hxx>
+#include <vcl/window.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/wrkwin.hxx>
+
+#include <window.h>
+#include <brdwin.hxx>
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+#include <com/sun/star/awt/XWindowPeer.hpp>
+
+#include <sal/log.hxx>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::datatransfer::clipboard;
+using namespace ::com::sun::star::datatransfer::dnd;
+using namespace ::com::sun::star;
+
+
+ImplAccessibleInfos::ImplAccessibleInfos()
+{
+ nAccessibleRole = 0xFFFF;
+ pLabeledByWindow = nullptr;
+ pLabelForWindow = nullptr;
+}
+
+ImplAccessibleInfos::~ImplAccessibleInfos()
+{
+}
+
+namespace vcl {
+
+css::uno::Reference< css::accessibility::XAccessible > Window::GetAccessible( bool bCreate )
+{
+ // do not optimize hierarchy for the top level border win (ie, when there is no parent)
+ /* // do not optimize accessible hierarchy at all to better reflect real VCL hierarchy
+ if ( GetParent() && ( GetType() == WindowType::BORDERWINDOW ) && ( GetChildCount() == 1 ) )
+ //if( !ImplIsAccessibleCandidate() )
+ {
+ vcl::Window* pChild = GetAccessibleChildWindow( 0 );
+ if ( pChild )
+ return pChild->GetAccessible();
+ }
+ */
+ if ( !mpWindowImpl )
+ return css::uno::Reference< css::accessibility::XAccessible >();
+ if ( !mpWindowImpl->mxAccessible.is() && bCreate )
+ mpWindowImpl->mxAccessible = CreateAccessible();
+
+ return mpWindowImpl->mxAccessible;
+}
+
+css::uno::Reference< css::accessibility::XAccessible > Window::CreateAccessible()
+{
+ css::uno::Reference< css::accessibility::XAccessible > xAcc( GetComponentInterface(), css::uno::UNO_QUERY );
+ return xAcc;
+}
+
+void Window::SetAccessible( const css::uno::Reference< css::accessibility::XAccessible >& x )
+{
+ if (!mpWindowImpl)
+ return;
+
+ mpWindowImpl->mxAccessible = x;
+}
+
+// skip all border windows that are not top level frames
+bool Window::ImplIsAccessibleCandidate() const
+{
+ if( !mpWindowImpl->mbBorderWin )
+ return true;
+ else
+ // #101741 do not check for WB_CLOSEABLE because undecorated floaters (like menus!) are closeable
+ if( mpWindowImpl->mbFrame && mpWindowImpl->mnStyle & (WB_MOVEABLE | WB_SIZEABLE) )
+ return true;
+ else
+ return false;
+}
+
+bool Window::ImplIsAccessibleNativeFrame() const
+{
+ if( mpWindowImpl->mbFrame )
+ // #101741 do not check for WB_CLOSEABLE because undecorated floaters (like menus!) are closeable
+ if( mpWindowImpl->mnStyle & (WB_MOVEABLE | WB_SIZEABLE) )
+ return true;
+ else
+ return false;
+ else
+ return false;
+}
+
+vcl::Window* Window::GetAccessibleParentWindow() const
+{
+ if (!mpWindowImpl || ImplIsAccessibleNativeFrame())
+ return nullptr;
+
+ vcl::Window* pParent = mpWindowImpl->mpParent;
+ if( GetType() == WindowType::MENUBARWINDOW )
+ {
+ // report the menubar as a child of THE workwindow
+ vcl::Window *pWorkWin = GetParent()->mpWindowImpl->mpFirstChild;
+ while( pWorkWin && (pWorkWin == this) )
+ pWorkWin = pWorkWin->mpWindowImpl->mpNext;
+ pParent = pWorkWin;
+ }
+ // If this is a floating window which has a native border window, then that border should be reported as
+ // the accessible parent
+ else if( GetType() == WindowType::FLOATINGWINDOW &&
+ mpWindowImpl->mpBorderWindow && mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame )
+ {
+ pParent = mpWindowImpl->mpBorderWindow;
+ }
+ else if( pParent && !pParent->ImplIsAccessibleCandidate() )
+ {
+ pParent = pParent->mpWindowImpl->mpParent;
+ }
+ return pParent;
+}
+
+sal_uInt16 Window::GetAccessibleChildWindowCount()
+{
+ if (!mpWindowImpl)
+ return 0;
+
+ sal_uInt16 nChildren = 0;
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while( pChild )
+ {
+ if( pChild->IsVisible() )
+ nChildren++;
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+
+ // report the menubarwindow as a child of THE workwindow
+ if( GetType() == WindowType::BORDERWINDOW )
+ {
+ ImplBorderWindow *pBorderWindow = static_cast<ImplBorderWindow*>(this);
+ if( pBorderWindow->mpMenuBarWindow &&
+ pBorderWindow->mpMenuBarWindow->IsVisible()
+ )
+ --nChildren;
+ }
+ else if( GetType() == WindowType::WORKWINDOW )
+ {
+ WorkWindow *pWorkWindow = static_cast<WorkWindow*>(this);
+ if( pWorkWindow->GetMenuBar() &&
+ pWorkWindow->GetMenuBar()->GetWindow() &&
+ pWorkWindow->GetMenuBar()->GetWindow()->IsVisible()
+ )
+ ++nChildren;
+ }
+
+ return nChildren;
+}
+
+vcl::Window* Window::GetAccessibleChildWindow( sal_uInt16 n )
+{
+ // report the menubarwindow as the first child of THE workwindow
+ if( GetType() == WindowType::WORKWINDOW && static_cast<WorkWindow *>(this)->GetMenuBar() )
+ {
+ if( n == 0 )
+ {
+ MenuBar *pMenuBar = static_cast<WorkWindow *>(this)->GetMenuBar();
+ if( pMenuBar->GetWindow() && pMenuBar->GetWindow()->IsVisible() )
+ return pMenuBar->GetWindow();
+ }
+ else
+ --n;
+ }
+
+ // transform n to child number including invisible children
+ sal_uInt16 nChildren = n;
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while( pChild )
+ {
+ if( pChild->IsVisible() )
+ {
+ if( ! nChildren )
+ break;
+ nChildren--;
+ }
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+
+ if( GetType() == WindowType::BORDERWINDOW && pChild && pChild->GetType() == WindowType::MENUBARWINDOW )
+ {
+ do pChild = pChild->mpWindowImpl->mpNext; while( pChild && ! pChild->IsVisible() );
+ SAL_WARN_IF( !pChild, "vcl", "GetAccessibleChildWindow(): wrong index in border window");
+ }
+
+ if ( pChild && ( pChild->GetType() == WindowType::BORDERWINDOW ) && ( pChild->GetChildCount() == 1 ) )
+ {
+ pChild = pChild->GetChild( 0 );
+ }
+ return pChild;
+}
+
+void Window::SetAccessibleRole( sal_uInt16 nRole )
+{
+ if ( !mpWindowImpl->mpAccessibleInfos )
+ mpWindowImpl->mpAccessibleInfos.reset( new ImplAccessibleInfos );
+
+ SAL_WARN_IF( mpWindowImpl->mpAccessibleInfos->nAccessibleRole != 0xFFFF, "vcl", "AccessibleRole already set!" );
+ mpWindowImpl->mpAccessibleInfos->nAccessibleRole = nRole;
+}
+
+sal_uInt16 Window::getDefaultAccessibleRole() const
+{
+ sal_uInt16 nRole = 0xFFFF;
+ switch ( GetType() )
+ {
+ case WindowType::MESSBOX: // MT: Would be nice to have special roles!
+ case WindowType::INFOBOX:
+ case WindowType::WARNINGBOX:
+ case WindowType::ERRORBOX:
+ case WindowType::QUERYBOX: nRole = accessibility::AccessibleRole::ALERT; break;
+
+ case WindowType::MODELESSDIALOG:
+ case WindowType::TABDIALOG:
+ case WindowType::BUTTONDIALOG:
+ case WindowType::DIALOG: nRole = accessibility::AccessibleRole::DIALOG; break;
+
+ case WindowType::PUSHBUTTON:
+ case WindowType::OKBUTTON:
+ case WindowType::CANCELBUTTON:
+ case WindowType::HELPBUTTON:
+ case WindowType::IMAGEBUTTON:
+ case WindowType::MOREBUTTON: nRole = accessibility::AccessibleRole::PUSH_BUTTON; break;
+ case WindowType::MENUBUTTON: nRole = accessibility::AccessibleRole::BUTTON_MENU; break;
+
+ case WindowType::RADIOBUTTON: nRole = accessibility::AccessibleRole::RADIO_BUTTON; break;
+ case WindowType::TRISTATEBOX:
+ case WindowType::CHECKBOX: nRole = accessibility::AccessibleRole::CHECK_BOX; break;
+
+ case WindowType::MULTILINEEDIT: nRole = accessibility::AccessibleRole::SCROLL_PANE; break;
+
+ case WindowType::PATTERNFIELD:
+ case WindowType::EDIT: nRole = static_cast<Edit const *>(this)->IsPassword() ? accessibility::AccessibleRole::PASSWORD_TEXT : accessibility::AccessibleRole::TEXT; break;
+
+ case WindowType::PATTERNBOX:
+ case WindowType::NUMERICBOX:
+ case WindowType::METRICBOX:
+ case WindowType::CURRENCYBOX:
+ case WindowType::LONGCURRENCYBOX:
+ case WindowType::COMBOBOX: nRole = accessibility::AccessibleRole::COMBO_BOX; break;
+
+ case WindowType::LISTBOX:
+ case WindowType::MULTILISTBOX: nRole = accessibility::AccessibleRole::LIST; break;
+
+ case WindowType::TREELISTBOX: nRole = accessibility::AccessibleRole::TREE; break;
+
+ case WindowType::FIXEDTEXT: nRole = accessibility::AccessibleRole::LABEL; break;
+ case WindowType::FIXEDLINE:
+ if( !GetText().isEmpty() )
+ nRole = accessibility::AccessibleRole::LABEL;
+ else
+ nRole = accessibility::AccessibleRole::SEPARATOR;
+ break;
+
+ case WindowType::FIXEDBITMAP:
+ case WindowType::FIXEDIMAGE: nRole = accessibility::AccessibleRole::ICON; break;
+ case WindowType::GROUPBOX: nRole = accessibility::AccessibleRole::GROUP_BOX; break;
+ case WindowType::SCROLLBAR: nRole = accessibility::AccessibleRole::SCROLL_BAR; break;
+
+ case WindowType::SLIDER:
+ case WindowType::SPLITTER:
+ case WindowType::SPLITWINDOW: nRole = accessibility::AccessibleRole::SPLIT_PANE; break;
+
+ case WindowType::DATEBOX:
+ case WindowType::TIMEBOX:
+ case WindowType::DATEFIELD:
+ case WindowType::TIMEFIELD: nRole = accessibility::AccessibleRole::DATE_EDITOR; break;
+
+ case WindowType::METRICFIELD:
+ case WindowType::CURRENCYFIELD:
+ case WindowType::SPINBUTTON:
+ case WindowType::SPINFIELD:
+ case WindowType::FORMATTEDFIELD: nRole = accessibility::AccessibleRole::SPIN_BOX; break;
+
+ case WindowType::TOOLBOX: nRole = accessibility::AccessibleRole::TOOL_BAR; break;
+ case WindowType::STATUSBAR: nRole = accessibility::AccessibleRole::STATUS_BAR; break;
+
+ case WindowType::TABPAGE: nRole = accessibility::AccessibleRole::PANEL; break;
+ case WindowType::TABCONTROL: nRole = accessibility::AccessibleRole::PAGE_TAB_LIST; break;
+
+ case WindowType::DOCKINGWINDOW: nRole = (mpWindowImpl->mbFrame) ? accessibility::AccessibleRole::FRAME :
+ accessibility::AccessibleRole::PANEL; break;
+
+ case WindowType::FLOATINGWINDOW: nRole = ( mpWindowImpl->mbFrame ||
+ (mpWindowImpl->mpBorderWindow && mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame) ||
+ (GetStyle() & WB_OWNERDRAWDECORATION) ) ? accessibility::AccessibleRole::FRAME :
+ accessibility::AccessibleRole::WINDOW; break;
+
+ case WindowType::WORKWINDOW: nRole = accessibility::AccessibleRole::ROOT_PANE; break;
+
+ case WindowType::SCROLLBARBOX: nRole = accessibility::AccessibleRole::FILLER; break;
+
+ case WindowType::HELPTEXTWINDOW: nRole = accessibility::AccessibleRole::TOOL_TIP; break;
+
+ case WindowType::RULER: nRole = accessibility::AccessibleRole::RULER; break;
+
+ case WindowType::SCROLLWINDOW: nRole = accessibility::AccessibleRole::SCROLL_PANE; break;
+
+ case WindowType::WINDOW:
+ case WindowType::CONTROL:
+ case WindowType::BORDERWINDOW:
+ case WindowType::SYSTEMCHILDWINDOW:
+ default:
+ if (ImplIsAccessibleNativeFrame() )
+ nRole = accessibility::AccessibleRole::FRAME;
+ else if( IsScrollable() )
+ nRole = accessibility::AccessibleRole::SCROLL_PANE;
+ else if( this->ImplGetWindow()->IsMenuFloatingWindow() )
+ nRole = accessibility::AccessibleRole::WINDOW; // #106002#, contextmenus are windows (i.e. toplevel)
+ else
+ // #104051# WINDOW seems to be a bad default role, use LAYEREDPANE instead
+ // a WINDOW is interpreted as a top-level window, which is typically not the case
+ //nRole = accessibility::AccessibleRole::WINDOW;
+ nRole = accessibility::AccessibleRole::PANEL;
+ }
+ return nRole;
+}
+
+sal_uInt16 Window::GetAccessibleRole() const
+{
+ if (!mpWindowImpl)
+ return 0;
+
+ sal_uInt16 nRole = mpWindowImpl->mpAccessibleInfos ? mpWindowImpl->mpAccessibleInfos->nAccessibleRole : 0xFFFF;
+ if ( nRole == 0xFFFF )
+ nRole = getDefaultAccessibleRole();
+ return nRole;
+}
+
+void Window::SetAccessibleName( const OUString& rName )
+{
+ if ( !mpWindowImpl->mpAccessibleInfos )
+ mpWindowImpl->mpAccessibleInfos.reset( new ImplAccessibleInfos );
+
+ OUString oldName = GetAccessibleName();
+
+ mpWindowImpl->mpAccessibleInfos->pAccessibleName = rName;
+
+ CallEventListeners( VclEventId::WindowFrameTitleChanged, &oldName );
+}
+
+OUString Window::GetAccessibleName() const
+{
+ if (!mpWindowImpl)
+ return OUString();
+
+ if (mpWindowImpl->mpAccessibleInfos && mpWindowImpl->mpAccessibleInfos->pAccessibleName)
+ return *mpWindowImpl->mpAccessibleInfos->pAccessibleName;
+ return getDefaultAccessibleName();
+}
+
+OUString Window::getDefaultAccessibleName() const
+{
+ OUString aAccessibleName;
+ switch ( GetType() )
+ {
+ case WindowType::MULTILINEEDIT:
+ case WindowType::PATTERNFIELD:
+ case WindowType::METRICFIELD:
+ case WindowType::CURRENCYFIELD:
+ case WindowType::EDIT:
+
+ case WindowType::DATEBOX:
+ case WindowType::TIMEBOX:
+ case WindowType::CURRENCYBOX:
+ case WindowType::LONGCURRENCYBOX:
+ case WindowType::DATEFIELD:
+ case WindowType::TIMEFIELD:
+ case WindowType::SPINFIELD:
+ case WindowType::FORMATTEDFIELD:
+
+ case WindowType::COMBOBOX:
+ case WindowType::LISTBOX:
+ case WindowType::MULTILISTBOX:
+ case WindowType::TREELISTBOX:
+ case WindowType::METRICBOX:
+ {
+ vcl::Window *pLabel = GetAccessibleRelationLabeledBy();
+ if ( pLabel && pLabel != this )
+ aAccessibleName = pLabel->GetText();
+ if (aAccessibleName.isEmpty())
+ aAccessibleName = GetQuickHelpText();
+ if (aAccessibleName.isEmpty())
+ aAccessibleName = GetText();
+ }
+ break;
+
+ case WindowType::IMAGEBUTTON:
+ case WindowType::PUSHBUTTON:
+ aAccessibleName = GetText();
+ if (aAccessibleName.isEmpty())
+ {
+ aAccessibleName = GetQuickHelpText();
+ if (aAccessibleName.isEmpty())
+ aAccessibleName = GetHelpText();
+ }
+ break;
+
+ case WindowType::TOOLBOX:
+ aAccessibleName = GetText();
+ break;
+
+ case WindowType::MOREBUTTON:
+ aAccessibleName = mpWindowImpl->maText;
+ break;
+
+ default:
+ aAccessibleName = GetText();
+ break;
+ }
+
+ return OutputDevice::GetNonMnemonicString( aAccessibleName );
+}
+
+void Window::SetAccessibleDescription( const OUString& rDescription )
+{
+ if ( ! mpWindowImpl->mpAccessibleInfos )
+ mpWindowImpl->mpAccessibleInfos.reset( new ImplAccessibleInfos );
+
+ std::optional<OUString>& rCurrentDescription = mpWindowImpl->mpAccessibleInfos->pAccessibleDescription;
+ SAL_WARN_IF( rCurrentDescription && *rCurrentDescription != rDescription, "vcl", "AccessibleDescription already set" );
+ rCurrentDescription = rDescription;
+}
+
+OUString Window::GetAccessibleDescription() const
+{
+ if (!mpWindowImpl)
+ return OUString();
+
+ OUString aAccessibleDescription;
+ if ( mpWindowImpl->mpAccessibleInfos && mpWindowImpl->mpAccessibleInfos->pAccessibleDescription )
+ {
+ aAccessibleDescription = *mpWindowImpl->mpAccessibleInfos->pAccessibleDescription;
+ }
+ else
+ {
+ // Special code for help text windows. ZT asks the border window for the
+ // description so we have to forward this request to our inner window.
+ const vcl::Window* pWin = this->ImplGetWindow();
+ if ( pWin->GetType() == WindowType::HELPTEXTWINDOW )
+ aAccessibleDescription = pWin->GetHelpText();
+ else
+ aAccessibleDescription = GetHelpText();
+ }
+
+ return aAccessibleDescription;
+}
+
+void Window::SetAccessibleRelationLabeledBy( vcl::Window* pLabeledBy )
+{
+ if ( !mpWindowImpl->mpAccessibleInfos )
+ mpWindowImpl->mpAccessibleInfos.reset( new ImplAccessibleInfos );
+ mpWindowImpl->mpAccessibleInfos->pLabeledByWindow = pLabeledBy;
+}
+
+void Window::SetAccessibleRelationLabelFor( vcl::Window* pLabelFor )
+{
+ if ( !mpWindowImpl->mpAccessibleInfos )
+ mpWindowImpl->mpAccessibleInfos.reset( new ImplAccessibleInfos );
+ mpWindowImpl->mpAccessibleInfos->pLabelForWindow = pLabelFor;
+}
+
+vcl::Window* Window::GetAccessibleRelationMemberOf() const
+{
+ if (!isContainerWindow(this) && !isContainerWindow(GetParent()))
+ return getLegacyNonLayoutAccessibleRelationMemberOf();
+
+ return nullptr;
+}
+
+vcl::Window* Window::getAccessibleRelationLabelFor() const
+{
+ if (mpWindowImpl->mpAccessibleInfos && mpWindowImpl->mpAccessibleInfos->pLabelForWindow)
+ return mpWindowImpl->mpAccessibleInfos->pLabelForWindow;
+
+ return nullptr;
+}
+
+vcl::Window* Window::GetAccessibleRelationLabelFor() const
+{
+ vcl::Window* pWindow = getAccessibleRelationLabelFor();
+
+ if (pWindow)
+ return pWindow;
+
+ if (!isContainerWindow(this) && !isContainerWindow(GetParent()))
+ return getLegacyNonLayoutAccessibleRelationLabelFor();
+
+ return nullptr;
+}
+
+vcl::Window* Window::GetAccessibleRelationLabeledBy() const
+{
+ if (mpWindowImpl->mpAccessibleInfos && mpWindowImpl->mpAccessibleInfos->pLabeledByWindow)
+ return mpWindowImpl->mpAccessibleInfos->pLabeledByWindow;
+
+ std::vector<VclPtr<FixedText> > aMnemonicLabels(list_mnemonic_labels());
+ if (!aMnemonicLabels.empty())
+ {
+ //if we have multiple labels, then prefer the first that is visible
+ for (auto const & rCandidate : aMnemonicLabels)
+ {
+ if (rCandidate->IsVisible())
+ return rCandidate;
+ }
+ return aMnemonicLabels[0];
+ }
+
+ if (!isContainerWindow(this) && !isContainerWindow(GetParent()))
+ return getLegacyNonLayoutAccessibleRelationLabeledBy();
+
+ return nullptr;
+}
+
+bool Window::IsAccessibilityEventsSuppressed( bool bTraverseParentPath )
+{
+ if( !bTraverseParentPath )
+ return mpWindowImpl->mbSuppressAccessibilityEvents;
+ else
+ {
+ vcl::Window *pParent = this;
+ while ( pParent && pParent->mpWindowImpl)
+ {
+ if( pParent->mpWindowImpl->mbSuppressAccessibilityEvents )
+ return true;
+ else
+ pParent = pParent->mpWindowImpl->mpParent; // do not use GetParent() to find borderwindows that are frames
+ }
+ return false;
+ }
+}
+
+void Window::SetAccessibilityEventsSuppressed(bool bSuppressed)
+{
+ mpWindowImpl->mbSuppressAccessibilityEvents = bSuppressed;
+}
+
+} /* namespace vcl */
+
+uno::Reference<accessibility::XAccessibleEditableText>
+FindFocusedEditableText(uno::Reference<accessibility::XAccessibleContext> const& xContext)
+{
+ if (!xContext.is())
+ return uno::Reference<accessibility::XAccessibleEditableText>();
+
+ uno::Reference<accessibility::XAccessibleStateSet> xState = xContext->getAccessibleStateSet();
+ if (xState.is())
+ {
+ if (xState->contains(accessibility::AccessibleStateType::FOCUSED))
+ {
+ uno::Reference<accessibility::XAccessibleEditableText> xText(xContext, uno::UNO_QUERY);
+ if (xText.is())
+ return xText;
+ if (xState->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS))
+ return uno::Reference<accessibility::XAccessibleEditableText>();
+ }
+ }
+
+ bool bSafeToIterate = true;
+ sal_Int32 nCount = xContext->getAccessibleChildCount();
+ if (nCount < 0 || nCount > SAL_MAX_UINT16 /* slow enough for anyone */)
+ bSafeToIterate = false;
+ if (!bSafeToIterate)
+ return uno::Reference<accessibility::XAccessibleEditableText>();
+
+ for (sal_Int32 i = 0; i < xContext->getAccessibleChildCount(); ++i)
+ {
+ uno::Reference<accessibility::XAccessible> xChild = xContext->getAccessibleChild(i);
+ if (!xChild.is())
+ continue;
+ uno::Reference<accessibility::XAccessibleContext> xChildContext
+ = xChild->getAccessibleContext();
+ if (!xChildContext.is())
+ continue;
+ uno::Reference<accessibility::XAccessibleEditableText> xText
+ = FindFocusedEditableText(xChildContext);
+ if (xText.is())
+ return xText;
+ }
+ return uno::Reference<accessibility::XAccessibleEditableText>();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/accmgr.cxx b/vcl/source/window/accmgr.cxx
new file mode 100644
index 000000000..26ea9846e
--- /dev/null
+++ b/vcl/source/window/accmgr.cxx
@@ -0,0 +1,228 @@
+/* -*- 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 <accel.hxx>
+#include <accmgr.hxx>
+
+#include <algorithm>
+
+ImplAccelManager::~ImplAccelManager()
+{
+}
+
+bool ImplAccelManager::InsertAccel( Accelerator* pAccel )
+{
+ if ( !mxAccelList ) {
+ mxAccelList.emplace();
+ } else {
+ for (Accelerator* i : *mxAccelList) {
+ if ( i == pAccel ) {
+ return false;
+ }
+ }
+ }
+
+ mxAccelList->insert( mxAccelList->begin(), pAccel );
+ return true;
+}
+
+void ImplAccelManager::RemoveAccel( Accelerator const * pAccel )
+{
+ // do we have a list ?
+ if ( !mxAccelList )
+ return;
+
+ //e.g. #i90599#. Someone starts typing a sequence in a dialog, but doesn't
+ //end it, and then closes the dialog, deleting the accelerators. So if
+ //we're removing an accelerator that a sub-accelerator which is in the
+ //sequence list, throw away the entire sequence
+ if ( mxSequenceList ) {
+ for (sal_uInt16 i = 0; i < pAccel->GetItemCount(); ++i) {
+ Accelerator* pSubAccel = pAccel->GetAccel( pAccel->GetItemId(i) );
+ for (Accelerator* j : *mxSequenceList) {
+ if ( j == pSubAccel ) {
+ EndSequence();
+ i = pAccel->GetItemCount();
+ break;
+ }
+ }
+ }
+ }
+
+ // throw it away
+ auto it = std::find(mxAccelList->begin(), mxAccelList->end(), pAccel);
+ if (it != mxAccelList->end())
+ mxAccelList->erase( it );
+}
+
+void ImplAccelManager::EndSequence()
+{
+ // are we in a list ?
+ if ( !mxSequenceList )
+ return;
+
+ for (Accelerator* pTempAccel : *mxSequenceList)
+ {
+ pTempAccel->mpDel = nullptr;
+ }
+
+ // delete sequence-list
+ mxSequenceList.reset();
+}
+
+bool ImplAccelManager::IsAccelKey( const vcl::KeyCode& rKeyCode )
+{
+ Accelerator* pAccel;
+
+ // do we have accelerators ??
+ if ( !mxAccelList )
+ return false;
+ if ( mxAccelList->empty() )
+ return false;
+
+ // are we in a sequence ?
+ if ( mxSequenceList )
+ {
+ pAccel = mxSequenceList->empty() ? nullptr : (*mxSequenceList)[ 0 ];
+
+ // not found ?
+ if ( !pAccel )
+ {
+ // abort sequence
+ FlushAccel();
+ return false;
+ }
+
+ // can the entry be found ?
+ ImplAccelEntry* pEntry = pAccel->ImplGetAccelData( rKeyCode );
+ if ( pEntry )
+ {
+ Accelerator* pNextAccel = pEntry->mpAccel;
+
+ // is an accelerator coupled ?
+ if ( pNextAccel )
+ {
+
+ mxSequenceList->insert( mxSequenceList->begin(), pNextAccel );
+
+ // call Activate-Handler of the new one
+ pNextAccel->Activate();
+ return true;
+ }
+ else
+ {
+ // it is there already !
+ if ( pEntry->mbEnabled )
+ {
+ // stop sequence (first call deactivate-handler)
+ EndSequence();
+
+ // set accelerator of the actual item
+ // and call the handler
+ bool bDel = false;
+ pAccel->mnCurId = pEntry->mnId;
+ pAccel->mpDel = &bDel;
+ pAccel->Select();
+
+ // did the accelerator survive the call
+ if ( !bDel )
+ {
+ pAccel->mnCurId = 0;
+ pAccel->mpDel = nullptr;
+ }
+
+ return true;
+ }
+ else
+ {
+ // stop sequence as the accelerator was disabled
+ // transfer the key (to the system)
+ FlushAccel();
+ return false;
+ }
+ }
+ }
+ else
+ {
+ // wrong key => stop sequence
+ FlushAccel();
+ return false;
+ }
+ }
+
+ // step through the list of accelerators
+ for (Accelerator* i : *mxAccelList)
+ {
+ pAccel = i;
+
+ // is the entry contained ?
+ ImplAccelEntry* pEntry = pAccel->ImplGetAccelData( rKeyCode );
+ if ( pEntry )
+ {
+ Accelerator* pNextAccel = pEntry->mpAccel;
+
+ // is an accelerator assigned ?
+ if ( pNextAccel )
+ {
+
+ // create sequence list
+ mxSequenceList.emplace();
+ mxSequenceList->insert( mxSequenceList->begin(), pAccel );
+ mxSequenceList->insert( mxSequenceList->begin(), pNextAccel );
+
+ // call activate-Handler of the new one
+ pNextAccel->Activate();
+
+ return true;
+ }
+ else
+ {
+ // already assigned !
+ if ( pEntry->mbEnabled )
+ {
+ // first call activate/deactivate-Handler
+ pAccel->Activate();
+
+ // define accelerator of the actual item
+ // and call the handler
+ bool bDel = false;
+ pAccel->mnCurId = pEntry->mnId;
+ pAccel->mpDel = &bDel;
+ pAccel->Select();
+
+ // if the accelerator did survive the call
+ if ( !bDel )
+ {
+ pAccel->mnCurId = 0;
+ pAccel->mpDel = nullptr;
+ }
+
+ return true;
+ }
+ else
+ return false;
+ }
+ }
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/brdwin.cxx b/vcl/source/window/brdwin.cxx
new file mode 100644
index 000000000..c003ed17b
--- /dev/null
+++ b/vcl/source/window/brdwin.cxx
@@ -0,0 +1,2003 @@
+/* -*- 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 <strings.hrc>
+#include <svdata.hxx>
+#include <brdwin.hxx>
+#include <window.h>
+
+#include <vcl/textrectinfo.hxx>
+#include <vcl/event.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/dockwin.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/help.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/ptrstyle.hxx>
+
+using namespace ::com::sun::star::uno;
+
+// useful caption height for title bar buttons
+#define MIN_CAPTION_HEIGHT 18
+
+namespace vcl {
+
+void Window::ImplCalcSymbolRect( tools::Rectangle& rRect )
+{
+ // Add border, not shown in the non-default representation,
+ // as we want to use it for small buttons
+ rRect.AdjustLeft( -1 );
+ rRect.AdjustTop( -1 );
+ rRect.AdjustRight( 1 );
+ rRect.AdjustBottom( 1 );
+
+ // we leave 5% room between the symbol and the button border
+ tools::Long nExtraWidth = ((rRect.GetWidth()*50)+500)/1000;
+ tools::Long nExtraHeight = ((rRect.GetHeight()*50)+500)/1000;
+ rRect.AdjustLeft(nExtraWidth );
+ rRect.AdjustRight( -nExtraWidth );
+ rRect.AdjustTop(nExtraHeight );
+ rRect.AdjustBottom( -nExtraHeight );
+}
+
+} /* namespace vcl */
+
+static void ImplDrawBrdWinSymbol( vcl::RenderContext* pDev,
+ const tools::Rectangle& rRect, SymbolType eSymbol )
+{
+ // we leave 5% room between the symbol and the button border
+ DecorationView aDecoView( pDev );
+ tools::Rectangle aTempRect = rRect;
+ vcl::Window::ImplCalcSymbolRect( aTempRect );
+ aDecoView.DrawSymbol( aTempRect, eSymbol,
+ pDev->GetSettings().GetStyleSettings().GetButtonTextColor() );
+}
+
+static void ImplDrawBrdWinSymbolButton( vcl::RenderContext* pDev,
+ const tools::Rectangle& rRect,
+ SymbolType eSymbol, DrawButtonFlags nState )
+{
+ bool bMouseOver(nState & DrawButtonFlags::Highlight);
+ nState &= ~DrawButtonFlags::Highlight;
+
+ tools::Rectangle aTempRect;
+ vcl::Window *pWin = pDev->GetOwnerWindow();
+ if( pWin )
+ {
+ if( bMouseOver )
+ {
+ // provide a bright background for selection effect
+ pDev->SetFillColor( pDev->GetSettings().GetStyleSettings().GetWindowColor() );
+ pDev->SetLineColor();
+ pDev->DrawRect( rRect );
+ pWin->DrawSelectionBackground( rRect, 2, bool(nState & DrawButtonFlags::Pressed),
+ true );
+ }
+ aTempRect = rRect;
+ aTempRect.AdjustLeft(3 );
+ aTempRect.AdjustRight( -4 );
+ aTempRect.AdjustTop(3 );
+ aTempRect.AdjustBottom( -4 );
+ }
+ else
+ {
+ DecorationView aDecoView( pDev );
+ aTempRect = aDecoView.DrawButton( rRect, nState|DrawButtonFlags::Flat );
+ }
+ ImplDrawBrdWinSymbol( pDev, aTempRect, eSymbol );
+}
+
+
+ImplBorderWindowView::~ImplBorderWindowView()
+{
+}
+
+bool ImplBorderWindowView::MouseMove( const MouseEvent& )
+{
+ return false;
+}
+
+bool ImplBorderWindowView::MouseButtonDown( const MouseEvent& )
+{
+ return false;
+}
+
+bool ImplBorderWindowView::Tracking( const TrackingEvent& )
+{
+ return false;
+}
+
+OUString ImplBorderWindowView::RequestHelp( const Point&, tools::Rectangle& )
+{
+ return OUString();
+}
+
+tools::Rectangle ImplBorderWindowView::GetMenuRect() const
+{
+ return tools::Rectangle();
+}
+
+void ImplBorderWindowView::ImplInitTitle(ImplBorderFrameData* pData)
+{
+ ImplBorderWindow* pBorderWindow = pData->mpBorderWindow;
+
+ if ( !(pBorderWindow->GetStyle() & (WB_MOVEABLE | WB_POPUP)) ||
+ (pData->mnTitleType == BorderWindowTitleType::NONE) )
+ {
+ pData->mnTitleType = BorderWindowTitleType::NONE;
+ pData->mnTitleHeight = 0;
+ }
+ else
+ {
+ const StyleSettings& rStyleSettings = pData->mpOutDev->GetSettings().GetStyleSettings();
+ if (pData->mnTitleType == BorderWindowTitleType::Tearoff)
+ pData->mnTitleHeight = ToolBox::ImplGetDragWidth(*pData->mpBorderWindow, false) + 2;
+ else
+ {
+ if (pData->mnTitleType == BorderWindowTitleType::Small)
+ {
+ pBorderWindow->SetPointFont(*pBorderWindow->GetOutDev(), rStyleSettings.GetFloatTitleFont() );
+ pData->mnTitleHeight = rStyleSettings.GetFloatTitleHeight();
+ }
+ else // pData->mnTitleType == BorderWindowTitleType::Normal
+ {
+ // FIXME RenderContext
+ pBorderWindow->SetPointFont(*pBorderWindow->GetOutDev(), rStyleSettings.GetTitleFont());
+ pData->mnTitleHeight = rStyleSettings.GetTitleHeight();
+ }
+ tools::Long nTextHeight = pBorderWindow->GetTextHeight();
+ if (nTextHeight > pData->mnTitleHeight)
+ pData->mnTitleHeight = nTextHeight;
+ }
+ }
+}
+
+BorderWindowHitTest ImplBorderWindowView::ImplHitTest( ImplBorderFrameData const * pData, const Point& rPos )
+{
+ ImplBorderWindow* pBorderWindow = pData->mpBorderWindow;
+
+ if ( pData->maTitleRect.Contains( rPos ) )
+ {
+ if ( pData->maCloseRect.Contains( rPos ) )
+ return BorderWindowHitTest::Close;
+ else if ( pData->maMenuRect.Contains( rPos ) )
+ return BorderWindowHitTest::Menu;
+ else if ( pData->maDockRect.Contains( rPos ) )
+ return BorderWindowHitTest::Dock;
+ else if ( pData->maHideRect.Contains( rPos ) )
+ return BorderWindowHitTest::Hide;
+ else if ( pData->maHelpRect.Contains( rPos ) )
+ return BorderWindowHitTest::Help;
+ else
+ return BorderWindowHitTest::Title;
+ }
+
+ if (pBorderWindow->GetStyle() & WB_SIZEABLE)
+ {
+ tools::Long nSizeWidth = pData->mnNoTitleTop+pData->mnTitleHeight;
+ if ( nSizeWidth < 16 )
+ nSizeWidth = 16;
+
+ // no corner resize for floating toolbars, which would lead to jumps while formatting
+ // setting nSizeWidth = 0 will only return pure left,top,right,bottom
+ if( pBorderWindow->GetStyle() & (WB_OWNERDRAWDECORATION | WB_POPUP) )
+ nSizeWidth = 0;
+
+ if ( rPos.X() < pData->mnLeftBorder )
+ {
+ if ( rPos.Y() < nSizeWidth )
+ return BorderWindowHitTest::TopLeft;
+ else if ( rPos.Y() >= pData->mnHeight-nSizeWidth )
+ return BorderWindowHitTest::BottomLeft;
+ else
+ return BorderWindowHitTest::Left;
+ }
+ else if ( rPos.X() >= pData->mnWidth-pData->mnRightBorder )
+ {
+ if ( rPos.Y() < nSizeWidth )
+ return BorderWindowHitTest::TopRight;
+ else if ( rPos.Y() >= pData->mnHeight-nSizeWidth )
+ return BorderWindowHitTest::BottomRight;
+ else
+ return BorderWindowHitTest::Right;
+ }
+ else if ( rPos.Y() < pData->mnNoTitleTop )
+ {
+ if ( rPos.X() < nSizeWidth )
+ return BorderWindowHitTest::TopLeft;
+ else if ( rPos.X() >= pData->mnWidth-nSizeWidth )
+ return BorderWindowHitTest::TopRight;
+ else
+ return BorderWindowHitTest::Top;
+ }
+ else if ( rPos.Y() >= pData->mnHeight-pData->mnBottomBorder )
+ {
+ if ( rPos.X() < nSizeWidth )
+ return BorderWindowHitTest::BottomLeft;
+ else if ( rPos.X() >= pData->mnWidth-nSizeWidth )
+ return BorderWindowHitTest::BottomRight;
+ else
+ return BorderWindowHitTest::Bottom;
+ }
+ }
+
+ return BorderWindowHitTest::NONE;
+}
+
+void ImplBorderWindowView::ImplMouseMove( ImplBorderFrameData* pData, const MouseEvent& rMEvt )
+{
+ DrawButtonFlags oldCloseState = pData->mnCloseState;
+ DrawButtonFlags oldMenuState = pData->mnMenuState;
+ pData->mnCloseState &= ~DrawButtonFlags::Highlight;
+ pData->mnMenuState &= ~DrawButtonFlags::Highlight;
+
+ Point aMousePos = rMEvt.GetPosPixel();
+ BorderWindowHitTest nHitTest = ImplHitTest( pData, aMousePos );
+ PointerStyle ePtrStyle = PointerStyle::Arrow;
+ if ( nHitTest & BorderWindowHitTest::Left )
+ ePtrStyle = PointerStyle::WindowWSize;
+ else if ( nHitTest & BorderWindowHitTest::Right )
+ ePtrStyle = PointerStyle::WindowESize;
+ else if ( nHitTest & BorderWindowHitTest::Top )
+ ePtrStyle = PointerStyle::WindowNSize;
+ else if ( nHitTest & BorderWindowHitTest::Bottom )
+ ePtrStyle = PointerStyle::WindowSSize;
+ else if ( nHitTest & BorderWindowHitTest::TopLeft )
+ ePtrStyle = PointerStyle::WindowNWSize;
+ else if ( nHitTest & BorderWindowHitTest::BottomRight )
+ ePtrStyle = PointerStyle::WindowSESize;
+ else if ( nHitTest & BorderWindowHitTest::TopRight )
+ ePtrStyle = PointerStyle::WindowNESize;
+ else if ( nHitTest & BorderWindowHitTest::BottomLeft )
+ ePtrStyle = PointerStyle::WindowSWSize;
+ else if ( nHitTest & BorderWindowHitTest::Close )
+ pData->mnCloseState |= DrawButtonFlags::Highlight;
+ else if ( nHitTest & BorderWindowHitTest::Menu )
+ pData->mnMenuState |= DrawButtonFlags::Highlight;
+ else if ( nHitTest & BorderWindowHitTest::Title &&
+ pData->mnTitleType == BorderWindowTitleType::Tearoff && !rMEvt.IsLeaveWindow() )
+ ePtrStyle = PointerStyle::Move;
+ pData->mpBorderWindow->SetPointer( ePtrStyle );
+
+ if( pData->mnCloseState != oldCloseState )
+ pData->mpBorderWindow->Invalidate( pData->maCloseRect );
+ if( pData->mnMenuState != oldMenuState )
+ pData->mpBorderWindow->Invalidate( pData->maMenuRect );
+}
+
+OUString ImplBorderWindowView::ImplRequestHelp( ImplBorderFrameData const * pData,
+ const Point& rPos,
+ tools::Rectangle& rHelpRect )
+{
+ TranslateId pHelpId;
+ OUString aHelpStr;
+ BorderWindowHitTest nHitTest = ImplHitTest( pData, rPos );
+ if ( nHitTest != BorderWindowHitTest::NONE )
+ {
+ if ( nHitTest & BorderWindowHitTest::Close )
+ {
+ pHelpId = SV_HELPTEXT_CLOSE;
+ rHelpRect = pData->maCloseRect;
+ }
+ else if ( nHitTest & BorderWindowHitTest::Dock )
+ {
+ pHelpId = SV_HELPTEXT_MAXIMIZE;
+ rHelpRect = pData->maDockRect;
+ }
+ else if ( nHitTest & BorderWindowHitTest::Hide )
+ {
+ pHelpId = SV_HELPTEXT_MINIMIZE;
+ rHelpRect = pData->maHideRect;
+ }
+ else if ( nHitTest & BorderWindowHitTest::Help )
+ {
+ pHelpId = SV_HELPTEXT_HELP;
+ rHelpRect = pData->maHelpRect;
+ }
+ else if ( nHitTest & BorderWindowHitTest::Title )
+ {
+ if( !pData->maTitleRect.IsEmpty() )
+ {
+ // tooltip only if title truncated
+ if( pData->mbTitleClipped )
+ {
+ rHelpRect = pData->maTitleRect;
+ // no help id, use window title as help string
+ aHelpStr = pData->mpBorderWindow->GetText();
+ }
+ }
+ }
+ }
+
+ if (pHelpId)
+ aHelpStr = VclResId(pHelpId);
+
+ return aHelpStr;
+}
+
+tools::Long ImplBorderWindowView::ImplCalcTitleWidth( const ImplBorderFrameData* pData )
+{
+ // title is not visible therefore no width
+ if ( !pData->mnTitleHeight )
+ return 0;
+
+ ImplBorderWindow* pBorderWindow = pData->mpBorderWindow;
+ tools::Long nTitleWidth = pBorderWindow->GetTextWidth( pBorderWindow->GetText() )+6;
+ nTitleWidth += pData->maCloseRect.GetWidth();
+ nTitleWidth += pData->maDockRect.GetWidth();
+ nTitleWidth += pData->maMenuRect.GetWidth();
+ nTitleWidth += pData->maHideRect.GetWidth();
+ nTitleWidth += pData->maHelpRect.GetWidth();
+ nTitleWidth += pData->mnLeftBorder+pData->mnRightBorder;
+ return nTitleWidth;
+}
+
+
+ImplNoBorderWindowView::ImplNoBorderWindowView()
+{
+}
+
+void ImplNoBorderWindowView::Init( OutputDevice*, tools::Long, tools::Long )
+{
+}
+
+void ImplNoBorderWindowView::GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const
+{
+ rLeftBorder = 0;
+ rTopBorder = 0;
+ rRightBorder = 0;
+ rBottomBorder = 0;
+}
+
+tools::Long ImplNoBorderWindowView::CalcTitleWidth() const
+{
+ return 0;
+}
+
+void ImplNoBorderWindowView::DrawWindow(vcl::RenderContext&, const Point*)
+{
+}
+
+ImplSmallBorderWindowView::ImplSmallBorderWindowView( ImplBorderWindow* pBorderWindow )
+ : mpBorderWindow(pBorderWindow)
+ , mpOutDev(nullptr)
+ , mnWidth(0)
+ , mnHeight(0)
+ , mnLeftBorder(0)
+ , mnTopBorder(0)
+ , mnRightBorder(0)
+ , mnBottomBorder(0)
+ , mbNWFBorder(false)
+{
+}
+
+void ImplSmallBorderWindowView::Init( OutputDevice* pDev, tools::Long nWidth, tools::Long nHeight )
+{
+ mpOutDev = pDev;
+ mnWidth = nWidth;
+ mnHeight = nHeight;
+ mbNWFBorder = false;
+
+ vcl::Window *pWin = mpOutDev->GetOwnerWindow();
+ vcl::Window *pCtrl = nullptr;
+ if (pWin)
+ pCtrl = mpBorderWindow->GetWindow(GetWindowType::Client);
+
+ tools::Long nOrigLeftBorder = mnLeftBorder;
+ tools::Long nOrigTopBorder = mnTopBorder;
+ tools::Long nOrigRightBorder = mnRightBorder;
+ tools::Long nOrigBottomBorder = mnBottomBorder;
+
+ WindowBorderStyle nBorderStyle = mpBorderWindow->GetBorderStyle();
+ if ( nBorderStyle & WindowBorderStyle::NOBORDER )
+ {
+ mnLeftBorder = 0;
+ mnTopBorder = 0;
+ mnRightBorder = 0;
+ mnBottomBorder = 0;
+ }
+ else
+ {
+ // FIXME: this is currently only on macOS, check with other
+ // platforms
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects && !( nBorderStyle & WindowBorderStyle::NWF ) )
+ {
+ // for native widget drawing we must find out what
+ // control this border belongs to
+ ControlType aCtrlType = ControlType::Generic;
+ ControlPart aCtrlPart = ControlPart::Entire;
+ if (pCtrl)
+ {
+ switch( pCtrl->GetType() )
+ {
+ case WindowType::LISTBOX:
+ if( pCtrl->GetStyle() & WB_DROPDOWN )
+ {
+ aCtrlType = ControlType::Listbox;
+ mbNWFBorder = true;
+ }
+ break;
+ case WindowType::LISTBOXWINDOW:
+ aCtrlType = ControlType::Listbox;
+ aCtrlPart = ControlPart::ListboxWindow;
+ mbNWFBorder = true;
+ break;
+ case WindowType::COMBOBOX:
+ if( pCtrl->GetStyle() & WB_DROPDOWN )
+ {
+ aCtrlType = ControlType::Combobox;
+ mbNWFBorder = true;
+ }
+ break;
+ case WindowType::MULTILINEEDIT:
+ aCtrlType = ControlType::MultilineEditbox;
+ mbNWFBorder = true;
+ break;
+ case WindowType::EDIT:
+ case WindowType::PATTERNFIELD:
+ case WindowType::METRICFIELD:
+ case WindowType::CURRENCYFIELD:
+ case WindowType::DATEFIELD:
+ case WindowType::TIMEFIELD:
+ case WindowType::SPINFIELD:
+ case WindowType::FORMATTEDFIELD:
+ mbNWFBorder = true;
+ if (pCtrl->GetStyle() & WB_SPIN)
+ aCtrlType = ControlType::Spinbox;
+ else
+ aCtrlType = ControlType::Editbox;
+ break;
+ default:
+ break;
+ }
+ }
+ if( mbNWFBorder )
+ {
+ ImplControlValue aControlValue;
+ Size aMinSize( mnWidth, mnHeight );
+ if( aMinSize.Width() < 10 ) aMinSize.setWidth( 10 );
+ if( aMinSize.Height() < 10 ) aMinSize.setHeight( 10 );
+ tools::Rectangle aCtrlRegion( Point(), aMinSize );
+ tools::Rectangle aBounds, aContent;
+ if( pWin->GetNativeControlRegion( aCtrlType, aCtrlPart, aCtrlRegion,
+ ControlState::ENABLED, aControlValue,
+ aBounds, aContent ) )
+ {
+ aBounds.AdjustLeft(mnLeftBorder);
+ aBounds.AdjustRight(-mnRightBorder);
+ aBounds.AdjustTop(mnTopBorder);
+ aBounds.AdjustBottom(-mnBottomBorder);
+ aContent.AdjustLeft(mnLeftBorder);
+ aContent.AdjustRight(-mnRightBorder);
+ aContent.AdjustTop(mnTopBorder);
+ aContent.AdjustBottom(-mnBottomBorder);
+ mnLeftBorder = aContent.Left() - aBounds.Left();
+ mnRightBorder = aBounds.Right() - aContent.Right();
+ mnTopBorder = aContent.Top() - aBounds.Top();
+ mnBottomBorder = aBounds.Bottom() - aContent.Bottom();
+ if( mnWidth && mnHeight )
+ {
+
+ mpBorderWindow->SetPaintTransparent( true );
+ mpBorderWindow->SetBackground();
+ if (!pCtrl->IsControlBackground())
+ pCtrl->SetPaintTransparent(true);
+
+ vcl::Window* pCompoundParent = nullptr;
+ if( pWin->GetParent() && pWin->GetParent()->IsCompoundControl() )
+ pCompoundParent = pWin->GetParent();
+
+ if( pCompoundParent )
+ pCompoundParent->SetPaintTransparent( true );
+
+ if( mnWidth < aBounds.GetWidth() || mnHeight < aBounds.GetHeight() )
+ {
+ if( ! pCompoundParent ) // compound controls have to fix themselves
+ {
+ Point aPos( mpBorderWindow->GetPosPixel() );
+ if( mnWidth < aBounds.GetWidth() )
+ aPos.AdjustX( -((aBounds.GetWidth() - mnWidth) / 2) );
+ if( mnHeight < aBounds.GetHeight() )
+ aPos.AdjustY( -((aBounds.GetHeight() - mnHeight) / 2) );
+ mpBorderWindow->SetPosSizePixel( aPos, aBounds.GetSize() );
+ }
+ }
+ }
+ }
+ else
+ mbNWFBorder = false;
+ }
+ }
+
+ if( ! mbNWFBorder )
+ {
+ DrawFrameStyle nStyle = DrawFrameStyle::NONE;
+ DrawFrameFlags nFlags = DrawFrameFlags::NoDraw;
+ // move border outside if border was converted or if the BorderWindow is a frame window,
+ if ( mpBorderWindow->mbSmallOutBorder )
+ nStyle = DrawFrameStyle::DoubleOut;
+ else if ( nBorderStyle & WindowBorderStyle::NWF )
+ nStyle = DrawFrameStyle::NWF;
+ else
+ nStyle = DrawFrameStyle::DoubleIn;
+ if ( nBorderStyle & WindowBorderStyle::MONO )
+ nFlags |= DrawFrameFlags::Mono;
+
+ DecorationView aDecoView( mpOutDev );
+ tools::Rectangle aRect( 0, 0, 10, 10 );
+ tools::Rectangle aCalcRect = aDecoView.DrawFrame( aRect, nStyle, nFlags );
+ mnLeftBorder = aCalcRect.Left();
+ mnTopBorder = aCalcRect.Top();
+ mnRightBorder = aRect.Right()-aCalcRect.Right();
+ mnBottomBorder = aRect.Bottom()-aCalcRect.Bottom();
+ }
+ }
+
+ if (pCtrl)
+ {
+ //fdo#57090 If the borders have changed, then trigger a queue_resize on
+ //the bordered window, which will resync its borders at that point
+ if (nOrigLeftBorder != mnLeftBorder ||
+ nOrigTopBorder != mnTopBorder ||
+ nOrigRightBorder != mnRightBorder ||
+ nOrigBottomBorder != mnBottomBorder)
+ {
+ pCtrl->queue_resize();
+ }
+ }
+}
+
+void ImplSmallBorderWindowView::GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const
+{
+ rLeftBorder = mnLeftBorder;
+ rTopBorder = mnTopBorder;
+ rRightBorder = mnRightBorder;
+ rBottomBorder = mnBottomBorder;
+}
+
+tools::Long ImplSmallBorderWindowView::CalcTitleWidth() const
+{
+ return 0;
+}
+
+void ImplSmallBorderWindowView::DrawWindow(vcl::RenderContext& rRenderContext, const Point*)
+{
+ WindowBorderStyle nBorderStyle = mpBorderWindow->GetBorderStyle();
+ if (nBorderStyle & WindowBorderStyle::NOBORDER)
+ return;
+
+ bool bNativeOK = false;
+ // for native widget drawing we must find out what
+ // control this border belongs to
+ vcl::Window* pCtrl = mpBorderWindow->GetWindow(GetWindowType::Client);
+
+ ControlType aCtrlType = ControlType::Generic;
+ ControlPart aCtrlPart = ControlPart::Entire;
+ if (pCtrl)
+ {
+ switch (pCtrl->GetType())
+ {
+ case WindowType::MULTILINEEDIT:
+ aCtrlType = ControlType::MultilineEditbox;
+ break;
+ case WindowType::EDIT:
+ case WindowType::PATTERNFIELD:
+ case WindowType::METRICFIELD:
+ case WindowType::CURRENCYFIELD:
+ case WindowType::DATEFIELD:
+ case WindowType::TIMEFIELD:
+ case WindowType::SPINFIELD:
+ case WindowType::FORMATTEDFIELD:
+ if (pCtrl->GetStyle() & WB_SPIN)
+ aCtrlType = ControlType::Spinbox;
+ else
+ aCtrlType = ControlType::Editbox;
+ break;
+
+ case WindowType::LISTBOX:
+ case WindowType::MULTILISTBOX:
+ case WindowType::TREELISTBOX:
+ aCtrlType = ControlType::Listbox;
+ if (pCtrl->GetStyle() & WB_DROPDOWN)
+ aCtrlPart = ControlPart::Entire;
+ else
+ aCtrlPart = ControlPart::ListboxWindow;
+ break;
+
+ case WindowType::LISTBOXWINDOW:
+ aCtrlType = ControlType::Listbox;
+ aCtrlPart = ControlPart::ListboxWindow;
+ break;
+
+ case WindowType::COMBOBOX:
+ case WindowType::PATTERNBOX:
+ case WindowType::NUMERICBOX:
+ case WindowType::METRICBOX:
+ case WindowType::CURRENCYBOX:
+ case WindowType::DATEBOX:
+ case WindowType::TIMEBOX:
+ case WindowType::LONGCURRENCYBOX:
+ if (pCtrl->GetStyle() & WB_DROPDOWN)
+ {
+ aCtrlType = ControlType::Combobox;
+ aCtrlPart = ControlPart::Entire;
+ }
+ else
+ {
+ aCtrlType = ControlType::Listbox;
+ aCtrlPart = ControlPart::ListboxWindow;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (aCtrlType != ControlType::Generic && pCtrl->IsNativeControlSupported(aCtrlType, aCtrlPart))
+ {
+ ImplControlValue aControlValue;
+ ControlState nState = ControlState::ENABLED;
+
+ if (!mpBorderWindow->IsEnabled())
+ nState &= ~ControlState::ENABLED;
+ if (mpBorderWindow->HasFocus())
+ nState |= ControlState::FOCUSED;
+ else if(mbNWFBorder)
+ {
+ // FIXME: this is currently only on macOS, see if other platforms can profit
+
+ // FIXME: for macOS focus rings all controls need to support GetNativeControlRegion
+ // for the dropdown style
+ if (pCtrl->HasFocus() || pCtrl->HasChildPathFocus())
+ nState |= ControlState::FOCUSED;
+ }
+
+ bool bMouseOver = false;
+ vcl::Window *pCtrlChild = pCtrl->GetWindow(GetWindowType::FirstChild);
+ while(pCtrlChild)
+ {
+ bMouseOver = pCtrlChild->IsMouseOver();
+ if (bMouseOver)
+ break;
+ pCtrlChild = pCtrlChild->GetWindow(GetWindowType::Next);
+ }
+
+ if (bMouseOver)
+ nState |= ControlState::ROLLOVER;
+
+ Point aPoint;
+ tools::Rectangle aCtrlRegion(aPoint, Size(mnWidth, mnHeight));
+
+ tools::Rectangle aBoundingRgn(aPoint, Size(mnWidth, mnHeight));
+ tools::Rectangle aContentRgn(aCtrlRegion);
+ if (!ImplGetSVData()->maNWFData.mbCanDrawWidgetAnySize &&
+ rRenderContext.GetNativeControlRegion(aCtrlType, aCtrlPart, aCtrlRegion,
+ nState, aControlValue,
+ aBoundingRgn, aContentRgn))
+ {
+ aCtrlRegion=aContentRgn;
+ }
+
+ Color aBackgroundColor = COL_AUTO;
+ if (pCtrl->IsControlBackground())
+ aBackgroundColor = pCtrl->GetBackgroundColor();
+ bNativeOK = rRenderContext.DrawNativeControl(aCtrlType, aCtrlPart, aCtrlRegion, nState, aControlValue, OUString(), aBackgroundColor);
+
+ // if the native theme draws the spinbuttons in one call, make sure the proper settings
+ // are passed, this might force a redraw though... (TODO: improve)
+ if ((aCtrlType == ControlType::Spinbox) && !pCtrl->IsNativeControlSupported(ControlType::Spinbox, ControlPart::ButtonUp))
+ {
+ Edit* pEdit = static_cast<Edit*>(pCtrl)->GetSubEdit();
+ if (pEdit && !pEdit->SupportsDoubleBuffering())
+ pCtrl->Paint(*pCtrl->GetOutDev(), tools::Rectangle()); // make sure the buttons are also drawn as they might overwrite the border
+ }
+ }
+
+ if (bNativeOK)
+ return;
+
+ DrawFrameStyle nStyle = DrawFrameStyle::NONE;
+ DrawFrameFlags nFlags = DrawFrameFlags::NONE;
+ // move border outside if border was converted or if the border window is a frame window,
+ if (mpBorderWindow->mbSmallOutBorder)
+ nStyle = DrawFrameStyle::DoubleOut;
+ else if (nBorderStyle & WindowBorderStyle::NWF)
+ nStyle = DrawFrameStyle::NWF;
+ else
+ nStyle = DrawFrameStyle::DoubleIn;
+ if (nBorderStyle & WindowBorderStyle::MONO)
+ nFlags |= DrawFrameFlags::Mono;
+ if (nBorderStyle & WindowBorderStyle::MENU)
+ nFlags |= DrawFrameFlags::Menu;
+ // tell DrawFrame that we're drawing a window border of a frame window to avoid round corners
+ if (mpBorderWindow == mpBorderWindow->ImplGetFrameWindow())
+ nFlags |= DrawFrameFlags::WindowBorder;
+
+ DecorationView aDecoView(&rRenderContext);
+ tools::Rectangle aInRect(Point(), Size(mnWidth, mnHeight));
+ aDecoView.DrawFrame(aInRect, nStyle, nFlags);
+}
+
+
+ImplStdBorderWindowView::ImplStdBorderWindowView( ImplBorderWindow* pBorderWindow )
+{
+ maFrameData.mpBorderWindow = pBorderWindow;
+ maFrameData.mbDragFull = false;
+ maFrameData.mnHitTest = BorderWindowHitTest::NONE;
+ maFrameData.mnCloseState = DrawButtonFlags::NONE;
+ maFrameData.mnDockState = DrawButtonFlags::NONE;
+ maFrameData.mnMenuState = DrawButtonFlags::NONE;
+ maFrameData.mnHideState = DrawButtonFlags::NONE;
+ maFrameData.mnHelpState = DrawButtonFlags::NONE;
+ maFrameData.mbTitleClipped = false;
+}
+
+ImplStdBorderWindowView::~ImplStdBorderWindowView()
+{
+}
+
+bool ImplStdBorderWindowView::MouseMove( const MouseEvent& rMEvt )
+{
+ ImplMouseMove( &maFrameData, rMEvt );
+ return true;
+}
+
+bool ImplStdBorderWindowView::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ ImplBorderWindow* pBorderWindow = maFrameData.mpBorderWindow;
+
+ if ( rMEvt.IsLeft() || rMEvt.IsRight() )
+ {
+ maFrameData.maMouseOff = rMEvt.GetPosPixel();
+ maFrameData.mnHitTest = ImplHitTest( &maFrameData, maFrameData.maMouseOff );
+ if ( maFrameData.mnHitTest != BorderWindowHitTest::NONE )
+ {
+ DragFullOptions nDragFullTest = DragFullOptions::NONE;
+ bool bTracking = true;
+ bool bHitTest = true;
+
+ if ( maFrameData.mnHitTest & BorderWindowHitTest::Close )
+ {
+ maFrameData.mnCloseState |= DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ else if ( maFrameData.mnHitTest & BorderWindowHitTest::Dock )
+ {
+ maFrameData.mnDockState |= DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ else if ( maFrameData.mnHitTest & BorderWindowHitTest::Menu )
+ {
+ maFrameData.mnMenuState |= DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+
+ // call handler already on mouse down
+ if ( pBorderWindow->ImplGetClientWindow()->IsSystemWindow() )
+ {
+ SystemWindow* pClientWindow = static_cast<SystemWindow*>(pBorderWindow->ImplGetClientWindow());
+ pClientWindow->TitleButtonClick( TitleButton::Menu );
+ }
+
+ maFrameData.mnMenuState &= ~DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+
+ bTracking = false;
+ }
+ else if ( maFrameData.mnHitTest & BorderWindowHitTest::Hide )
+ {
+ maFrameData.mnHideState |= DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ else if ( maFrameData.mnHitTest & BorderWindowHitTest::Help )
+ {
+ maFrameData.mnHelpState |= DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ else
+ {
+ if ( rMEvt.GetClicks() == 1 )
+ {
+ Point aPos = pBorderWindow->GetPosPixel();
+ Size aSize = pBorderWindow->GetOutputSizePixel();
+ maFrameData.mnTrackX = aPos.X();
+ maFrameData.mnTrackY = aPos.Y();
+ maFrameData.mnTrackWidth = aSize.Width();
+ maFrameData.mnTrackHeight = aSize.Height();
+
+ if (maFrameData.mnHitTest & BorderWindowHitTest::Title)
+ nDragFullTest = DragFullOptions::WindowMove;
+ else
+ nDragFullTest = DragFullOptions::WindowSize;
+ }
+ else
+ {
+ bTracking = false;
+
+ if ( (maFrameData.mnHitTest & BorderWindowHitTest::Title) &&
+ ((rMEvt.GetClicks() % 2) == 0) )
+ {
+ maFrameData.mnHitTest = BorderWindowHitTest::NONE;
+ bHitTest = false;
+
+ if ( pBorderWindow->ImplGetClientWindow()->IsSystemWindow() )
+ {
+ SystemWindow* pClientWindow = static_cast<SystemWindow*>(pBorderWindow->ImplGetClientWindow());
+ // always perform docking on double click, no button required
+ pClientWindow->TitleButtonClick( TitleButton::Docking );
+ }
+ }
+ }
+ }
+
+ if ( bTracking )
+ {
+ maFrameData.mbDragFull = false;
+ if ( nDragFullTest != DragFullOptions::NONE )
+ maFrameData.mbDragFull = true; // always fulldrag for proper docking, ignore system settings
+ pBorderWindow->StartTracking();
+ }
+ else if ( bHitTest )
+ maFrameData.mnHitTest = BorderWindowHitTest::NONE;
+ }
+ }
+
+ return true;
+}
+
+bool ImplStdBorderWindowView::Tracking( const TrackingEvent& rTEvt )
+{
+ ImplBorderWindow* pBorderWindow = maFrameData.mpBorderWindow;
+
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ BorderWindowHitTest nHitTest = maFrameData.mnHitTest;
+ maFrameData.mnHitTest = BorderWindowHitTest::NONE;
+
+ if ( nHitTest & BorderWindowHitTest::Close )
+ {
+ if ( maFrameData.mnCloseState & DrawButtonFlags::Pressed )
+ {
+ maFrameData.mnCloseState &= ~DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+
+ // do not call a Click-Handler when aborting
+ if ( !rTEvt.IsTrackingCanceled() )
+ {
+ // dispatch to correct window type (why is Close() not virtual ??? )
+ // TODO: make Close() virtual
+ VclPtr<vcl::Window> pWin = pBorderWindow->ImplGetClientWindow()->ImplGetWindow();
+ SystemWindow *pSysWin = dynamic_cast<SystemWindow* >(pWin.get());
+ DockingWindow *pDockWin = dynamic_cast<DockingWindow*>(pWin.get());
+ if ( pSysWin )
+ pSysWin->Close();
+ else if ( pDockWin )
+ pDockWin->Close();
+ }
+ }
+ }
+ else if ( nHitTest & BorderWindowHitTest::Dock )
+ {
+ if ( maFrameData.mnDockState & DrawButtonFlags::Pressed )
+ {
+ maFrameData.mnDockState &= ~DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+
+ // do not call a Click-Handler when aborting
+ if ( !rTEvt.IsTrackingCanceled() )
+ {
+ if ( pBorderWindow->ImplGetClientWindow()->IsSystemWindow() )
+ {
+ SystemWindow* pClientWindow = static_cast<SystemWindow*>(pBorderWindow->ImplGetClientWindow());
+ pClientWindow->TitleButtonClick( TitleButton::Docking );
+ }
+ }
+ }
+ }
+ else if ( nHitTest & BorderWindowHitTest::Menu )
+ {
+ if ( maFrameData.mnMenuState & DrawButtonFlags::Pressed )
+ {
+ maFrameData.mnMenuState &= ~DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+
+ // handler already called on mouse down
+ }
+ }
+ else if ( nHitTest & BorderWindowHitTest::Hide )
+ {
+ if ( maFrameData.mnHideState & DrawButtonFlags::Pressed )
+ {
+ maFrameData.mnHideState &= ~DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+
+ // do not call a Click-Handler when aborting
+ if ( !rTEvt.IsTrackingCanceled() )
+ {
+ if ( pBorderWindow->ImplGetClientWindow()->IsSystemWindow() )
+ {
+ SystemWindow* pClientWindow = static_cast<SystemWindow*>(pBorderWindow->ImplGetClientWindow());
+ pClientWindow->TitleButtonClick( TitleButton::Hide );
+ }
+ }
+ }
+ }
+ else if ( nHitTest & BorderWindowHitTest::Help )
+ {
+ if ( maFrameData.mnHelpState & DrawButtonFlags::Pressed )
+ {
+ maFrameData.mnHelpState &= ~DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ }
+ else
+ {
+ if ( maFrameData.mbDragFull )
+ {
+ // restore old state when aborting
+ if ( rTEvt.IsTrackingCanceled() )
+ pBorderWindow->SetPosSizePixel( Point( maFrameData.mnTrackX, maFrameData.mnTrackY ), Size( maFrameData.mnTrackWidth, maFrameData.mnTrackHeight ) );
+ }
+ else
+ {
+ pBorderWindow->HideTracking();
+ if ( !rTEvt.IsTrackingCanceled() )
+ pBorderWindow->SetPosSizePixel( Point( maFrameData.mnTrackX, maFrameData.mnTrackY ), Size( maFrameData.mnTrackWidth, maFrameData.mnTrackHeight ) );
+ }
+
+ if ( !rTEvt.IsTrackingCanceled() )
+ {
+ if ( pBorderWindow->ImplGetClientWindow()->ImplIsFloatingWindow() )
+ {
+ if ( static_cast<FloatingWindow*>(pBorderWindow->ImplGetClientWindow())->IsInPopupMode() )
+ static_cast<FloatingWindow*>(pBorderWindow->ImplGetClientWindow())->EndPopupMode( FloatWinPopupEndFlags::TearOff );
+ }
+ }
+ }
+ }
+ else if ( !rTEvt.GetMouseEvent().IsSynthetic() )
+ {
+ Point aMousePos = rTEvt.GetMouseEvent().GetPosPixel();
+
+ if ( maFrameData.mnHitTest & BorderWindowHitTest::Close )
+ {
+ if ( maFrameData.maCloseRect.Contains( aMousePos ) )
+ {
+ if ( !(maFrameData.mnCloseState & DrawButtonFlags::Pressed) )
+ {
+ maFrameData.mnCloseState |= DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ }
+ else
+ {
+ if ( maFrameData.mnCloseState & DrawButtonFlags::Pressed )
+ {
+ maFrameData.mnCloseState &= ~DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ }
+ }
+ else if ( maFrameData.mnHitTest & BorderWindowHitTest::Dock )
+ {
+ if ( maFrameData.maDockRect.Contains( aMousePos ) )
+ {
+ if ( !(maFrameData.mnDockState & DrawButtonFlags::Pressed) )
+ {
+ maFrameData.mnDockState |= DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ }
+ else
+ {
+ if ( maFrameData.mnDockState & DrawButtonFlags::Pressed )
+ {
+ maFrameData.mnDockState &= ~DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ }
+ }
+ else if ( maFrameData.mnHitTest & BorderWindowHitTest::Menu )
+ {
+ if ( maFrameData.maMenuRect.Contains( aMousePos ) )
+ {
+ if ( !(maFrameData.mnMenuState & DrawButtonFlags::Pressed) )
+ {
+ maFrameData.mnMenuState |= DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ }
+ else
+ {
+ if ( maFrameData.mnMenuState & DrawButtonFlags::Pressed )
+ {
+ maFrameData.mnMenuState &= ~DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ }
+ }
+ else if ( maFrameData.mnHitTest & BorderWindowHitTest::Hide )
+ {
+ if ( maFrameData.maHideRect.Contains( aMousePos ) )
+ {
+ if ( !(maFrameData.mnHideState & DrawButtonFlags::Pressed) )
+ {
+ maFrameData.mnHideState |= DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ }
+ else
+ {
+ if ( maFrameData.mnHideState & DrawButtonFlags::Pressed )
+ {
+ maFrameData.mnHideState &= ~DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ }
+ }
+ else if ( maFrameData.mnHitTest & BorderWindowHitTest::Help )
+ {
+ if ( maFrameData.maHelpRect.Contains( aMousePos ) )
+ {
+ if ( !(maFrameData.mnHelpState & DrawButtonFlags::Pressed) )
+ {
+ maFrameData.mnHelpState |= DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ }
+ else
+ {
+ if ( maFrameData.mnHelpState & DrawButtonFlags::Pressed )
+ {
+ maFrameData.mnHelpState &= ~DrawButtonFlags::Pressed;
+ pBorderWindow->InvalidateBorder();
+ }
+ }
+ }
+ else
+ {
+ aMousePos.AdjustX( -(maFrameData.maMouseOff.X()) );
+ aMousePos.AdjustY( -(maFrameData.maMouseOff.Y()) );
+
+ if ( maFrameData.mnHitTest & BorderWindowHitTest::Title )
+ {
+ maFrameData.mpBorderWindow->SetPointer( PointerStyle::Move );
+
+ Point aPos = pBorderWindow->GetPosPixel();
+ aPos.AdjustX(aMousePos.X() );
+ aPos.AdjustY(aMousePos.Y() );
+ if ( maFrameData.mbDragFull )
+ {
+ pBorderWindow->SetPosPixel( aPos );
+ pBorderWindow->ImplUpdateAll();
+ pBorderWindow->ImplGetFrameWindow()->ImplUpdateAll();
+ }
+ else
+ {
+ maFrameData.mnTrackX = aPos.X();
+ maFrameData.mnTrackY = aPos.Y();
+ pBorderWindow->ShowTracking( tools::Rectangle( pBorderWindow->ScreenToOutputPixel( aPos ), pBorderWindow->GetOutputSizePixel() ), ShowTrackFlags::Big );
+ }
+ }
+ else
+ {
+ Point aOldPos = pBorderWindow->GetPosPixel();
+ Size aSize = pBorderWindow->GetSizePixel();
+ tools::Rectangle aNewRect( aOldPos, aSize );
+ tools::Long nOldWidth = aSize.Width();
+ tools::Long nOldHeight = aSize.Height();
+ tools::Long nBorderWidth = maFrameData.mnLeftBorder+maFrameData.mnRightBorder;
+ tools::Long nBorderHeight = maFrameData.mnTopBorder+maFrameData.mnBottomBorder;
+ tools::Long nMinWidth = pBorderWindow->mnMinWidth+nBorderWidth;
+ tools::Long nMinHeight = pBorderWindow->mnMinHeight+nBorderHeight;
+ tools::Long nMinWidth2 = nBorderWidth;
+ tools::Long nMaxWidth = pBorderWindow->mnMaxWidth+nBorderWidth;
+ tools::Long nMaxHeight = pBorderWindow->mnMaxHeight+nBorderHeight;
+
+ if ( maFrameData.mnTitleHeight )
+ {
+ nMinWidth2 += 4;
+
+ if ( pBorderWindow->GetStyle() & WB_CLOSEABLE )
+ nMinWidth2 += maFrameData.maCloseRect.GetWidth();
+ }
+ if ( nMinWidth2 > nMinWidth )
+ nMinWidth = nMinWidth2;
+ if ( maFrameData.mnHitTest & (BorderWindowHitTest::Left | BorderWindowHitTest::TopLeft | BorderWindowHitTest::BottomLeft) )
+ {
+ aNewRect.AdjustLeft(aMousePos.X() );
+ if ( aNewRect.GetWidth() < nMinWidth )
+ aNewRect.SetLeft( aNewRect.Right()-nMinWidth+1 );
+ else if ( aNewRect.GetWidth() > nMaxWidth )
+ aNewRect.SetLeft( aNewRect.Right()-nMaxWidth+1 );
+ }
+ else if ( maFrameData.mnHitTest & (BorderWindowHitTest::Right | BorderWindowHitTest::TopRight | BorderWindowHitTest::BottomRight) )
+ {
+ aNewRect.AdjustRight(aMousePos.X() );
+ if ( aNewRect.GetWidth() < nMinWidth )
+ aNewRect.SetRight( aNewRect.Left()+nMinWidth+1 );
+ else if ( aNewRect.GetWidth() > nMaxWidth )
+ aNewRect.SetRight( aNewRect.Left()+nMaxWidth+1 );
+ }
+ if ( maFrameData.mnHitTest & (BorderWindowHitTest::Top | BorderWindowHitTest::TopLeft | BorderWindowHitTest::TopRight) )
+ {
+ aNewRect.AdjustTop(aMousePos.Y() );
+ if ( aNewRect.GetHeight() < nMinHeight )
+ aNewRect.SetTop( aNewRect.Bottom()-nMinHeight+1 );
+ else if ( aNewRect.GetHeight() > nMaxHeight )
+ aNewRect.SetTop( aNewRect.Bottom()-nMaxHeight+1 );
+ }
+ else if ( maFrameData.mnHitTest & (BorderWindowHitTest::Bottom | BorderWindowHitTest::BottomLeft | BorderWindowHitTest::BottomRight) )
+ {
+ aNewRect.AdjustBottom(aMousePos.Y() );
+ if ( aNewRect.GetHeight() < nMinHeight )
+ aNewRect.SetBottom( aNewRect.Top()+nMinHeight+1 );
+ else if ( aNewRect.GetHeight() > nMaxHeight )
+ aNewRect.SetBottom( aNewRect.Top()+nMaxHeight+1 );
+ }
+
+ // call Resizing-Handler for SystemWindows
+ if ( pBorderWindow->ImplGetClientWindow()->IsSystemWindow() )
+ {
+ // adjust size for Resizing-call
+ aSize = aNewRect.GetSize();
+ aSize.AdjustWidth( -nBorderWidth );
+ aSize.AdjustHeight( -nBorderHeight );
+ static_cast<SystemWindow*>(pBorderWindow->ImplGetClientWindow())->Resizing( aSize );
+ aSize.AdjustWidth(nBorderWidth );
+ aSize.AdjustHeight(nBorderHeight );
+ if ( aSize.Width() < nMinWidth )
+ aSize.setWidth( nMinWidth );
+ if ( aSize.Height() < nMinHeight )
+ aSize.setHeight( nMinHeight );
+ if ( aSize.Width() > nMaxWidth )
+ aSize.setWidth( nMaxWidth );
+ if ( aSize.Height() > nMaxHeight )
+ aSize.setHeight( nMaxHeight );
+ if ( maFrameData.mnHitTest & (BorderWindowHitTest::Left | BorderWindowHitTest::TopLeft | BorderWindowHitTest::BottomLeft) )
+ aNewRect.SetLeft( aNewRect.Right()-aSize.Width()+1 );
+ else
+ aNewRect.SetRight( aNewRect.Left()+aSize.Width()-1 );
+ if ( maFrameData.mnHitTest & (BorderWindowHitTest::Top | BorderWindowHitTest::TopLeft | BorderWindowHitTest::TopRight) )
+ aNewRect.SetTop( aNewRect.Bottom()-aSize.Height()+1 );
+ else
+ aNewRect.SetBottom( aNewRect.Top()+aSize.Height()-1 );
+ }
+
+ if ( maFrameData.mbDragFull )
+ {
+ // no move (only resize) if position did not change
+ if( aOldPos != aNewRect.TopLeft() )
+ pBorderWindow->setPosSizePixel( aNewRect.Left(), aNewRect.Top(),
+ aNewRect.GetWidth(), aNewRect.GetHeight() );
+ else
+ pBorderWindow->setPosSizePixel( aNewRect.Left(), aNewRect.Top(),
+ aNewRect.GetWidth(), aNewRect.GetHeight(), PosSizeFlags::Size );
+
+ pBorderWindow->ImplUpdateAll();
+ pBorderWindow->ImplGetFrameWindow()->ImplUpdateAll();
+ if ( maFrameData.mnHitTest & (BorderWindowHitTest::Right | BorderWindowHitTest::TopRight | BorderWindowHitTest::BottomRight) )
+ maFrameData.maMouseOff.AdjustX(aNewRect.GetWidth()-nOldWidth );
+ if ( maFrameData.mnHitTest & (BorderWindowHitTest::Bottom | BorderWindowHitTest::BottomLeft | BorderWindowHitTest::BottomRight) )
+ maFrameData.maMouseOff.AdjustY(aNewRect.GetHeight()-nOldHeight );
+ }
+ else
+ {
+ maFrameData.mnTrackX = aNewRect.Left();
+ maFrameData.mnTrackY = aNewRect.Top();
+ maFrameData.mnTrackWidth = aNewRect.GetWidth();
+ maFrameData.mnTrackHeight = aNewRect.GetHeight();
+ pBorderWindow->ShowTracking( tools::Rectangle( pBorderWindow->ScreenToOutputPixel( aNewRect.TopLeft() ), aNewRect.GetSize() ), ShowTrackFlags::Big );
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+OUString ImplStdBorderWindowView::RequestHelp( const Point& rPos, tools::Rectangle& rHelpRect )
+{
+ return ImplRequestHelp( &maFrameData, rPos, rHelpRect );
+}
+
+tools::Rectangle ImplStdBorderWindowView::GetMenuRect() const
+{
+ return maFrameData.maMenuRect;
+}
+
+void ImplStdBorderWindowView::Init( OutputDevice* pDev, tools::Long nWidth, tools::Long nHeight )
+{
+ ImplBorderFrameData* pData = &maFrameData;
+ ImplBorderWindow* pBorderWindow = maFrameData.mpBorderWindow;
+ const StyleSettings& rStyleSettings = pDev->GetSettings().GetStyleSettings();
+ DecorationView aDecoView( pDev );
+ tools::Rectangle aRect( 0, 0, 10, 10 );
+ tools::Rectangle aCalcRect = aDecoView.DrawFrame( aRect, DrawFrameStyle::DoubleOut, DrawFrameFlags::NoDraw );
+
+ pData->mpOutDev = pDev;
+ pData->mnWidth = nWidth;
+ pData->mnHeight = nHeight;
+
+ pData->mnTitleType = pBorderWindow->mnTitleType;
+
+ if ( !(pBorderWindow->GetStyle() & (WB_MOVEABLE | WB_POPUP)) || (pData->mnTitleType == BorderWindowTitleType::NONE) )
+ pData->mnBorderSize = 0;
+ else if ( pData->mnTitleType == BorderWindowTitleType::Tearoff )
+ pData->mnBorderSize = 0;
+ else
+ pData->mnBorderSize = StyleSettings::GetBorderSize();
+ pData->mnLeftBorder = aCalcRect.Left();
+ pData->mnTopBorder = aCalcRect.Top();
+ pData->mnRightBorder = aRect.Right()-aCalcRect.Right();
+ pData->mnBottomBorder = aRect.Bottom()-aCalcRect.Bottom();
+ pData->mnLeftBorder += pData->mnBorderSize;
+ pData->mnTopBorder += pData->mnBorderSize;
+ pData->mnRightBorder += pData->mnBorderSize;
+ pData->mnBottomBorder += pData->mnBorderSize;
+ pData->mnNoTitleTop = pData->mnTopBorder;
+
+ ImplInitTitle(&maFrameData);
+ if (pData->mnTitleHeight)
+ {
+ // to improve symbol display force a minimum title height
+ if (pData->mnTitleType != BorderWindowTitleType::Tearoff &&
+ pData->mnTitleHeight < MIN_CAPTION_HEIGHT)
+ pData->mnTitleHeight = MIN_CAPTION_HEIGHT;
+
+ // set a proper background for drawing
+ // highlighted buttons in the title
+ pBorderWindow->SetBackground( rStyleSettings.GetFaceColor() );
+
+ pData->maTitleRect.SetLeft( pData->mnLeftBorder );
+ pData->maTitleRect.SetRight( nWidth-pData->mnRightBorder-1 );
+ pData->maTitleRect.SetTop( pData->mnTopBorder );
+ pData->maTitleRect.SetBottom( pData->maTitleRect.Top()+pData->mnTitleHeight-1 );
+
+ if ( pData->mnTitleType & (BorderWindowTitleType::Normal | BorderWindowTitleType::Small) )
+ {
+ tools::Long nRight = pData->maTitleRect.Right() - 3;
+ tools::Long const nItemTop = pData->maTitleRect.Top() + 2;
+ tools::Long const nItemBottom = pData->maTitleRect.Bottom() - 2;
+
+ auto addSquareOnRight = [&nRight, nItemTop, nItemBottom](
+ tools::Rectangle & rect, tools::Long gap)
+ {
+ rect.SetTop( nItemTop );
+ rect.SetBottom( nItemBottom );
+ rect.SetRight( nRight );
+ rect.SetLeft( rect.Right() - rect.GetHeight() + 1 );
+ nRight -= rect.GetWidth() + gap;
+ };
+
+ if ( pBorderWindow->GetStyle() & WB_CLOSEABLE )
+ {
+ addSquareOnRight(pData->maCloseRect, 3);
+ }
+
+ if ( pBorderWindow->mbMenuBtn )
+ {
+ addSquareOnRight(pData->maMenuRect, 0);
+ }
+
+ if ( pBorderWindow->mbDockBtn )
+ {
+ addSquareOnRight(pData->maDockRect, 0);
+ }
+
+ if ( pBorderWindow->mbHideBtn )
+ {
+ addSquareOnRight(pData->maHideRect, 0);
+ }
+ }
+ else
+ {
+ pData->maCloseRect.SetEmpty();
+ pData->maDockRect.SetEmpty();
+ pData->maMenuRect.SetEmpty();
+ pData->maHideRect.SetEmpty();
+ pData->maHelpRect.SetEmpty();
+ }
+
+ pData->mnTopBorder += pData->mnTitleHeight;
+ }
+ else
+ {
+ pData->maTitleRect.SetEmpty();
+ pData->maCloseRect.SetEmpty();
+ pData->maDockRect.SetEmpty();
+ pData->maMenuRect.SetEmpty();
+ pData->maHideRect.SetEmpty();
+ pData->maHelpRect.SetEmpty();
+ }
+}
+
+void ImplStdBorderWindowView::GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const
+{
+ rLeftBorder = maFrameData.mnLeftBorder;
+ rTopBorder = maFrameData.mnTopBorder;
+ rRightBorder = maFrameData.mnRightBorder;
+ rBottomBorder = maFrameData.mnBottomBorder;
+}
+
+tools::Long ImplStdBorderWindowView::CalcTitleWidth() const
+{
+ return ImplCalcTitleWidth( &maFrameData );
+}
+
+void ImplStdBorderWindowView::DrawWindow(vcl::RenderContext& rRenderContext, const Point* pOffset)
+{
+ ImplBorderFrameData* pData = &maFrameData;
+ ImplBorderWindow* pBorderWindow = pData->mpBorderWindow;
+ Point aTmpPoint = pOffset ? *pOffset : Point();
+ tools::Rectangle aInRect( aTmpPoint, Size( pData->mnWidth, pData->mnHeight ) );
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ Color aFaceColor(rStyleSettings.GetFaceColor());
+ Color aFrameColor(aFaceColor);
+
+ aFrameColor.DecreaseContrast(sal_uInt8(0.5 * 255));
+
+ // Draw Frame
+ vcl::Region oldClipRgn(rRenderContext.GetClipRegion());
+
+ // for popups, don't draw part of the frame
+ if (!(pData->mnTitleType & (BorderWindowTitleType::Normal | BorderWindowTitleType::Small)))
+ {
+ FloatingWindow* pWin = dynamic_cast<FloatingWindow*>(pData->mpBorderWindow->GetWindow(GetWindowType::Client));
+ if (pWin)
+ {
+ vcl::Region aClipRgn(aInRect);
+ tools::Rectangle aItemClipRect(pWin->ImplGetItemEdgeClipRect());
+ if (!aItemClipRect.IsEmpty())
+ {
+ aItemClipRect.SetPos(pData->mpBorderWindow->AbsoluteScreenToOutputPixel(aItemClipRect.TopLeft()));
+ aClipRgn.Exclude(aItemClipRect);
+ rRenderContext.SetClipRegion(aClipRgn);
+ }
+ }
+ }
+
+ // single line frame
+ rRenderContext.SetLineColor(aFrameColor);
+ rRenderContext.SetFillColor();
+ rRenderContext.DrawRect(aInRect);
+ aInRect.AdjustLeft( 1 );
+ aInRect.AdjustRight( -1 );
+ aInRect.AdjustTop( 1 );
+ aInRect.AdjustBottom( -1 );
+
+ // restore
+ if (!(pData->mnTitleType & (BorderWindowTitleType::Normal | BorderWindowTitleType::Small)))
+ rRenderContext.SetClipRegion(oldClipRgn);
+
+ // Draw Border
+ rRenderContext.SetLineColor();
+ tools::Long nBorderSize = pData->mnBorderSize;
+ if (nBorderSize)
+ {
+ rRenderContext.SetFillColor(rStyleSettings.GetFaceColor());
+ rRenderContext.DrawRect(tools::Rectangle(Point(aInRect.Left(), aInRect.Top()),
+ Size(aInRect.GetWidth(), nBorderSize)));
+ rRenderContext.DrawRect(tools::Rectangle(Point(aInRect.Left(), aInRect.Top() + nBorderSize),
+ Size(nBorderSize, aInRect.GetHeight() - nBorderSize)));
+ rRenderContext.DrawRect(tools::Rectangle(Point(aInRect.Left(), aInRect.Bottom() - nBorderSize + 1),
+ Size(aInRect.GetWidth(), nBorderSize)));
+ rRenderContext.DrawRect(tools::Rectangle(Point(aInRect.Right()-nBorderSize + 1, aInRect.Top() + nBorderSize),
+ Size(nBorderSize, aInRect.GetHeight() - nBorderSize)));
+ }
+
+ // Draw Title
+ if (!pData->maTitleRect.IsEmpty())
+ {
+ aInRect = pData->maTitleRect;
+
+ // use no gradient anymore, just a static titlecolor
+ if (pData->mnTitleType == BorderWindowTitleType::Tearoff)
+ rRenderContext.SetFillColor(rStyleSettings.GetFaceGradientColor());
+ else if (pData->mnTitleType == BorderWindowTitleType::Popup)
+ rRenderContext.SetFillColor(aFaceColor);
+ else
+ rRenderContext.SetFillColor(aFrameColor);
+
+ rRenderContext.SetTextColor(rStyleSettings.GetButtonTextColor());
+ tools::Rectangle aTitleRect(pData->maTitleRect);
+ if(pOffset)
+ aTitleRect.Move(pOffset->X(), pOffset->Y());
+ rRenderContext.DrawRect(aTitleRect);
+
+ if (pData->mnTitleType != BorderWindowTitleType::Tearoff)
+ {
+ aInRect.AdjustLeft(2 );
+ aInRect.AdjustRight( -2 );
+
+ if (!pData->maHelpRect.IsEmpty())
+ aInRect.SetRight( pData->maHelpRect.Left() - 2 );
+ else if (!pData->maHideRect.IsEmpty())
+ aInRect.SetRight( pData->maHideRect.Left() - 2 );
+ else if (!pData->maDockRect.IsEmpty())
+ aInRect.SetRight( pData->maDockRect.Left() - 2 );
+ else if (!pData->maMenuRect.IsEmpty())
+ aInRect.SetRight( pData->maMenuRect.Left() - 2 );
+ else if (!pData->maCloseRect.IsEmpty())
+ aInRect.SetRight( pData->maCloseRect.Left() - 2 );
+
+ if (pOffset)
+ aInRect.Move(pOffset->X(), pOffset->Y());
+
+ DrawTextFlags nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter | DrawTextFlags::EndEllipsis | DrawTextFlags::Clip;
+
+ // must show tooltip ?
+ TextRectInfo aInfo;
+ rRenderContext.GetTextRect(aInRect, pBorderWindow->GetText(), nTextStyle, &aInfo);
+ pData->mbTitleClipped = aInfo.IsEllipses();
+
+ rRenderContext.DrawText(aInRect, pBorderWindow->GetText(), nTextStyle);
+ }
+ else
+ {
+ ToolBox::ImplDrawGrip(rRenderContext, aTitleRect, ToolBox::ImplGetDragWidth(rRenderContext, false),
+ WindowAlign::Left, false);
+ }
+ }
+
+ if (!pData->maCloseRect.IsEmpty())
+ {
+ tools::Rectangle aSymbolRect(pData->maCloseRect);
+ if (pOffset)
+ aSymbolRect.Move(pOffset->X(), pOffset->Y());
+ ImplDrawBrdWinSymbolButton(&rRenderContext, aSymbolRect, SymbolType::CLOSE, pData->mnCloseState);
+ }
+ if (!pData->maDockRect.IsEmpty())
+ {
+ tools::Rectangle aSymbolRect(pData->maDockRect);
+ if (pOffset)
+ aSymbolRect.Move(pOffset->X(), pOffset->Y());
+ ImplDrawBrdWinSymbolButton(&rRenderContext, aSymbolRect, SymbolType::DOCK, pData->mnDockState);
+ }
+ if (!pData->maMenuRect.IsEmpty())
+ {
+ tools::Rectangle aSymbolRect(pData->maMenuRect);
+ if (pOffset)
+ aSymbolRect.Move(pOffset->X(), pOffset->Y());
+ ImplDrawBrdWinSymbolButton(&rRenderContext, aSymbolRect, SymbolType::MENU, pData->mnMenuState);
+ }
+ if (!pData->maHideRect.IsEmpty())
+ {
+ tools::Rectangle aSymbolRect(pData->maHideRect);
+ if (pOffset)
+ aSymbolRect.Move(pOffset->X(), pOffset->Y());
+ ImplDrawBrdWinSymbolButton(&rRenderContext, aSymbolRect, SymbolType::HIDE, pData->mnHideState);
+ }
+
+ if (!pData->maHelpRect.IsEmpty())
+ {
+ tools::Rectangle aSymbolRect(pData->maHelpRect);
+ if (pOffset)
+ aSymbolRect.Move(pOffset->X(), pOffset->Y());
+ ImplDrawBrdWinSymbolButton(&rRenderContext, aSymbolRect, SymbolType::HELP, pData->mnHelpState);
+ }
+}
+
+void ImplBorderWindow::ImplInit( vcl::Window* pParent,
+ WinBits nStyle, BorderWindowStyle nTypeStyle,
+ SystemParentData* pSystemParentData
+ )
+{
+ // remove all unwanted WindowBits
+ WinBits nOrgStyle = nStyle;
+ WinBits nTestStyle = (WB_MOVEABLE | WB_SIZEABLE | WB_CLOSEABLE | WB_STANDALONE | WB_DIALOGCONTROL | WB_NODIALOGCONTROL | WB_SYSTEMFLOATWIN | WB_INTROWIN | WB_DEFAULTWIN | WB_TOOLTIPWIN | WB_NOSHADOW | WB_OWNERDRAWDECORATION | WB_SYSTEMCHILDWINDOW | WB_POPUP);
+ if ( nTypeStyle & BorderWindowStyle::App )
+ nTestStyle |= WB_APP;
+ nStyle &= nTestStyle;
+
+ mpWindowImpl->mbBorderWin = true;
+ mbSmallOutBorder = false;
+ if ( nTypeStyle & BorderWindowStyle::Frame )
+ {
+ if( nStyle & WB_SYSTEMCHILDWINDOW )
+ {
+ mpWindowImpl->mbOverlapWin = true;
+ mpWindowImpl->mbFrame = true;
+ mbFrameBorder = false;
+ }
+ else if( nStyle & (WB_OWNERDRAWDECORATION | WB_POPUP) )
+ {
+ mpWindowImpl->mbOverlapWin = true;
+ mpWindowImpl->mbFrame = true;
+ mbFrameBorder = (nOrgStyle & WB_NOBORDER) == 0;
+ }
+ else
+ {
+ mpWindowImpl->mbOverlapWin = true;
+ mpWindowImpl->mbFrame = true;
+ mbFrameBorder = false;
+ // closeable windows may have a border as well, eg. system floating windows without caption
+ if ( (nOrgStyle & (WB_BORDER | WB_NOBORDER | WB_MOVEABLE | WB_SIZEABLE/* | WB_CLOSEABLE*/)) == WB_BORDER )
+ mbSmallOutBorder = true;
+ }
+ }
+ else if ( nTypeStyle & BorderWindowStyle::Overlap )
+ {
+ mpWindowImpl->mbOverlapWin = true;
+ mbFrameBorder = true;
+ }
+ else
+ mbFrameBorder = false;
+
+ if ( nTypeStyle & BorderWindowStyle::Float )
+ mbFloatWindow = true;
+ else
+ mbFloatWindow = false;
+
+ Window::ImplInit( pParent, nStyle, pSystemParentData );
+ SetBackground();
+ SetTextFillColor();
+
+ mpMenuBarWindow = nullptr;
+ mnMinWidth = 0;
+ mnMinHeight = 0;
+ mnMaxWidth = SHRT_MAX;
+ mnMaxHeight = SHRT_MAX;
+ mnOrgMenuHeight = 0;
+ mbMenuHide = false;
+ mbDockBtn = false;
+ mbMenuBtn = false;
+ mbHideBtn = false;
+ mbDisplayActive = IsActive();
+
+ if ( nTypeStyle & BorderWindowStyle::Float )
+ mnTitleType = BorderWindowTitleType::Small;
+ else
+ mnTitleType = BorderWindowTitleType::Normal;
+ mnBorderStyle = WindowBorderStyle::NORMAL;
+ InitView();
+}
+
+ImplBorderWindow::ImplBorderWindow( vcl::Window* pParent,
+ SystemParentData* pSystemParentData,
+ WinBits nStyle, BorderWindowStyle nTypeStyle
+ ) : Window( WindowType::BORDERWINDOW )
+{
+ ImplInit( pParent, nStyle, nTypeStyle, pSystemParentData );
+}
+
+ImplBorderWindow::ImplBorderWindow( vcl::Window* pParent, WinBits nStyle ,
+ BorderWindowStyle nTypeStyle ) :
+ Window( WindowType::BORDERWINDOW )
+{
+ ImplInit( pParent, nStyle, nTypeStyle, nullptr );
+}
+
+ImplBorderWindow::~ImplBorderWindow()
+{
+ disposeOnce();
+}
+
+void ImplBorderWindow::dispose()
+{
+ mpBorderView.reset();
+ mpMenuBarWindow.clear();
+ mpNotebookBar.disposeAndClear();
+ vcl::Window::dispose();
+}
+
+void ImplBorderWindow::MouseMove( const MouseEvent& rMEvt )
+{
+ if (mpBorderView)
+ mpBorderView->MouseMove( rMEvt );
+}
+
+void ImplBorderWindow::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if (mpBorderView)
+ mpBorderView->MouseButtonDown( rMEvt );
+}
+
+void ImplBorderWindow::Tracking( const TrackingEvent& rTEvt )
+{
+ if (mpBorderView)
+ mpBorderView->Tracking( rTEvt );
+}
+
+void ImplBorderWindow::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ if (mpBorderView)
+ mpBorderView->DrawWindow(rRenderContext);
+}
+
+void ImplBorderWindow::Draw( OutputDevice* pOutDev, const Point& rPos )
+{
+ if (mpBorderView)
+ mpBorderView->DrawWindow(*pOutDev, &rPos);
+}
+
+void ImplBorderWindow::Activate()
+{
+ SetDisplayActive( true );
+ Window::Activate();
+}
+
+void ImplBorderWindow::Deactivate()
+{
+ // remove active windows from the ruler, also ignore the Deactivate
+ // if a menu becomes active
+ if (GetActivateMode() != ActivateModeFlags::NONE && !ImplGetSVData()->mpWinData->mbNoDeactivate)
+ SetDisplayActive( false );
+ Window::Deactivate();
+}
+
+void ImplBorderWindow::RequestHelp( const HelpEvent& rHEvt )
+{
+ // no keyboard help for border window
+ if ( rHEvt.GetMode() & (HelpEventMode::BALLOON | HelpEventMode::QUICK) && !rHEvt.KeyboardActivated() )
+ {
+ Point aMousePosPixel = ScreenToOutputPixel( rHEvt.GetMousePosPixel() );
+ tools::Rectangle aHelpRect;
+ OUString aHelpStr( mpBorderView->RequestHelp( aMousePosPixel, aHelpRect ) );
+
+ // retrieve rectangle
+ if ( !aHelpStr.isEmpty() )
+ {
+ aHelpRect.SetPos( OutputToScreenPixel( aHelpRect.TopLeft() ) );
+ if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
+ Help::ShowBalloon( this, aHelpRect.Center(), aHelpRect, aHelpStr );
+ else
+ Help::ShowQuickHelp( this, aHelpRect, aHelpStr );
+ return;
+ }
+ }
+
+ Window::RequestHelp( rHEvt );
+}
+
+void ImplBorderWindow::Resize()
+{
+ Size aSize = GetOutputSizePixel();
+
+ vcl::Window* pClientWindow = ImplGetClientWindow();
+
+ sal_Int32 nLeftBorder;
+ sal_Int32 nTopBorder;
+ sal_Int32 nRightBorder;
+ sal_Int32 nBottomBorder;
+ mpBorderView->GetBorder( nLeftBorder, nTopBorder, nRightBorder, nBottomBorder );
+
+ if (mpMenuBarWindow)
+ {
+ tools::Long nMenuHeight = mpMenuBarWindow->GetSizePixel().Height();
+ if ( mbMenuHide )
+ {
+ if ( nMenuHeight )
+ mnOrgMenuHeight = nMenuHeight;
+ nMenuHeight = 0;
+ }
+ else
+ {
+ if ( !nMenuHeight )
+ nMenuHeight = mnOrgMenuHeight;
+ }
+ mpMenuBarWindow->setPosSizePixel(
+ nLeftBorder, nTopBorder,
+ aSize.Width()-nLeftBorder-nRightBorder,
+ nMenuHeight);
+
+ // shift the notebookbar down accordingly
+ nTopBorder += nMenuHeight;
+ }
+
+ if (mpNotebookBar)
+ {
+ tools::Long nNotebookBarHeight = mpNotebookBar->GetSizePixel().Height();
+
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ const BitmapEx& aPersona = rStyleSettings.GetPersonaHeader();
+ // since size of notebookbar changes, to make common persona for menubar
+ // and notebookbar persona should be set again with changed coordinates
+ if (!aPersona.IsEmpty())
+ {
+ Wallpaper aWallpaper(aPersona);
+ aWallpaper.SetStyle(WallpaperStyle::TopRight);
+ aWallpaper.SetRect(tools::Rectangle(Point(0, -nTopBorder),
+ Size(aSize.Width() - nLeftBorder - nRightBorder,
+ nNotebookBarHeight + nTopBorder)));
+ mpNotebookBar->SetBackground(aWallpaper);
+ }
+
+ mpNotebookBar->setPosSizePixel(
+ nLeftBorder, nTopBorder,
+ aSize.Width() - nLeftBorder - nRightBorder,
+ nNotebookBarHeight);
+ }
+
+ GetBorder( pClientWindow->mpWindowImpl->mnLeftBorder, pClientWindow->mpWindowImpl->mnTopBorder,
+ pClientWindow->mpWindowImpl->mnRightBorder, pClientWindow->mpWindowImpl->mnBottomBorder );
+ pClientWindow->ImplPosSizeWindow( pClientWindow->mpWindowImpl->mnLeftBorder,
+ pClientWindow->mpWindowImpl->mnTopBorder,
+ aSize.Width()-pClientWindow->mpWindowImpl->mnLeftBorder-pClientWindow->mpWindowImpl->mnRightBorder,
+ aSize.Height()-pClientWindow->mpWindowImpl->mnTopBorder-pClientWindow->mpWindowImpl->mnBottomBorder,
+ PosSizeFlags::X | PosSizeFlags::Y |
+ PosSizeFlags::Width | PosSizeFlags::Height );
+
+ // UpdateView
+ mpBorderView->Init( GetOutDev(), aSize.Width(), aSize.Height() );
+ InvalidateBorder();
+
+ Window::Resize();
+}
+
+void ImplBorderWindow::StateChanged( StateChangedType nType )
+{
+ if ( (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::Data) )
+ {
+ if (IsReallyVisible() && mbFrameBorder)
+ InvalidateBorder();
+ }
+
+ Window::StateChanged( nType );
+}
+
+void ImplBorderWindow::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ if ( !mpWindowImpl->mbFrame || (GetStyle() & (WB_OWNERDRAWDECORATION | WB_POPUP)) )
+ UpdateView( true, ImplGetWindow()->GetOutputSizePixel() );
+ }
+
+ Window::DataChanged( rDCEvt );
+}
+
+void ImplBorderWindow::InitView()
+{
+ if ( mbSmallOutBorder )
+ mpBorderView.reset(new ImplSmallBorderWindowView( this ));
+ else if ( mpWindowImpl->mbFrame )
+ {
+ if( mbFrameBorder )
+ mpBorderView.reset(new ImplStdBorderWindowView( this ));
+ else
+ mpBorderView.reset(new ImplNoBorderWindowView);
+ }
+ else if ( !mbFrameBorder )
+ mpBorderView.reset(new ImplSmallBorderWindowView( this ));
+ else
+ mpBorderView.reset(new ImplStdBorderWindowView( this ));
+ Size aSize = GetOutputSizePixel();
+ mpBorderView->Init( GetOutDev(), aSize.Width(), aSize.Height() );
+}
+
+void ImplBorderWindow::UpdateView( bool bNewView, const Size& rNewOutSize )
+{
+ sal_Int32 nLeftBorder;
+ sal_Int32 nTopBorder;
+ sal_Int32 nRightBorder;
+ sal_Int32 nBottomBorder;
+ Size aOldSize = GetSizePixel();
+ Size aOutputSize = rNewOutSize;
+
+ if ( bNewView )
+ {
+ mpBorderView.reset();
+ InitView();
+ }
+ else
+ {
+ Size aSize = aOutputSize;
+ mpBorderView->GetBorder( nLeftBorder, nTopBorder, nRightBorder, nBottomBorder );
+ aSize.AdjustWidth(nLeftBorder+nRightBorder );
+ aSize.AdjustHeight(nTopBorder+nBottomBorder );
+ mpBorderView->Init( GetOutDev(), aSize.Width(), aSize.Height() );
+ }
+
+ vcl::Window* pClientWindow = ImplGetClientWindow();
+ if ( pClientWindow )
+ {
+ GetBorder( pClientWindow->mpWindowImpl->mnLeftBorder, pClientWindow->mpWindowImpl->mnTopBorder,
+ pClientWindow->mpWindowImpl->mnRightBorder, pClientWindow->mpWindowImpl->mnBottomBorder );
+ }
+ GetBorder( nLeftBorder, nTopBorder, nRightBorder, nBottomBorder );
+ if ( aOldSize.Width() || aOldSize.Height() )
+ {
+ aOutputSize.AdjustWidth(nLeftBorder+nRightBorder );
+ aOutputSize.AdjustHeight(nTopBorder+nBottomBorder );
+ if ( aOutputSize == GetSizePixel() )
+ InvalidateBorder();
+ else
+ SetSizePixel( aOutputSize );
+ }
+}
+
+void ImplBorderWindow::InvalidateBorder()
+{
+ if ( !IsReallyVisible() )
+ return;
+
+ // invalidate only if we have a border
+ sal_Int32 nLeftBorder;
+ sal_Int32 nTopBorder;
+ sal_Int32 nRightBorder;
+ sal_Int32 nBottomBorder;
+ mpBorderView->GetBorder( nLeftBorder, nTopBorder, nRightBorder, nBottomBorder );
+ if ( !(nLeftBorder || nTopBorder || nRightBorder || nBottomBorder) )
+ return;
+
+ tools::Rectangle aWinRect( Point( 0, 0 ), GetOutputSizePixel() );
+ vcl::Region aRegion( aWinRect );
+ aWinRect.AdjustLeft(nLeftBorder );
+ aWinRect.AdjustTop(nTopBorder );
+ aWinRect.AdjustRight( -nRightBorder );
+ aWinRect.AdjustBottom( -nBottomBorder );
+ // no output area anymore, now invalidate all
+ if ( (aWinRect.Right() < aWinRect.Left()) ||
+ (aWinRect.Bottom() < aWinRect.Top()) )
+ Invalidate( InvalidateFlags::NoChildren );
+ else
+ {
+ aRegion.Exclude( aWinRect );
+ Invalidate( aRegion, InvalidateFlags::NoChildren );
+ }
+}
+
+void ImplBorderWindow::SetDisplayActive( bool bActive )
+{
+ if ( mbDisplayActive != bActive )
+ {
+ mbDisplayActive = bActive;
+ if ( mbFrameBorder )
+ InvalidateBorder();
+ }
+}
+
+void ImplBorderWindow::SetTitleType( BorderWindowTitleType nTitleType, const Size& rSize )
+{
+ mnTitleType = nTitleType;
+ UpdateView( false, rSize );
+}
+
+void ImplBorderWindow::SetBorderStyle( WindowBorderStyle nStyle )
+{
+ if ( !mbFrameBorder && (mnBorderStyle != nStyle) )
+ {
+ mnBorderStyle = nStyle;
+ UpdateView( false, ImplGetWindow()->GetOutputSizePixel() );
+ }
+}
+
+void ImplBorderWindow::SetCloseButton()
+{
+ SetStyle( GetStyle() | WB_CLOSEABLE );
+ Size aSize = GetOutputSizePixel();
+ mpBorderView->Init( GetOutDev(), aSize.Width(), aSize.Height() );
+ InvalidateBorder();
+}
+
+void ImplBorderWindow::SetDockButton( bool bDockButton )
+{
+ mbDockBtn = bDockButton;
+ Size aSize = GetOutputSizePixel();
+ mpBorderView->Init( GetOutDev(), aSize.Width(), aSize.Height() );
+ InvalidateBorder();
+}
+
+void ImplBorderWindow::SetHideButton( bool bHideButton )
+{
+ mbHideBtn = bHideButton;
+ Size aSize = GetOutputSizePixel();
+ mpBorderView->Init( GetOutDev(), aSize.Width(), aSize.Height() );
+ InvalidateBorder();
+}
+
+void ImplBorderWindow::SetMenuButton( bool bMenuButton )
+{
+ mbMenuBtn = bMenuButton;
+ Size aSize = GetOutputSizePixel();
+ mpBorderView->Init( GetOutDev(), aSize.Width(), aSize.Height() );
+ InvalidateBorder();
+}
+
+void ImplBorderWindow::UpdateMenuHeight()
+{
+ Resize();
+}
+
+void ImplBorderWindow::SetMenuBarWindow( vcl::Window* pWindow )
+{
+ mpMenuBarWindow = pWindow;
+ UpdateMenuHeight();
+ if ( pWindow )
+ pWindow->Show();
+}
+
+void ImplBorderWindow::SetMenuBarMode( bool bHide )
+{
+ mbMenuHide = bHide;
+ UpdateMenuHeight();
+}
+
+void ImplBorderWindow::SetNotebookBar(const OUString& rUIXMLDescription,
+ const css::uno::Reference<css::frame::XFrame>& rFrame,
+ const NotebookBarAddonsItem& aNotebookBarAddonsItem)
+{
+ if (mpNotebookBar)
+ mpNotebookBar.disposeAndClear();
+ mpNotebookBar = VclPtr<NotebookBar>::Create(this, "NotebookBar", rUIXMLDescription, rFrame,
+ aNotebookBarAddonsItem);
+ Resize();
+}
+
+void ImplBorderWindow::CloseNotebookBar()
+{
+ if (mpNotebookBar)
+ mpNotebookBar.disposeAndClear();
+ mpNotebookBar = nullptr;
+ Resize();
+}
+
+void ImplBorderWindow::GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const
+{
+ mpBorderView->GetBorder(rLeftBorder, rTopBorder, rRightBorder, rBottomBorder);
+
+ if (mpMenuBarWindow && !mbMenuHide)
+ rTopBorder += mpMenuBarWindow->GetSizePixel().Height();
+
+ if (mpNotebookBar && mpNotebookBar->IsVisible())
+ rTopBorder += mpNotebookBar->GetSizePixel().Height();
+}
+
+tools::Long ImplBorderWindow::CalcTitleWidth() const
+{
+ return mpBorderView->CalcTitleWidth();
+}
+
+tools::Rectangle ImplBorderWindow::GetMenuRect() const
+{
+ return mpBorderView->GetMenuRect();
+}
+
+Size ImplBorderWindow::GetOptimalSize() const
+{
+ const vcl::Window* pClientWindow = ImplGetClientWindow();
+ if (pClientWindow)
+ return pClientWindow->GetOptimalSize();
+ return Size(mnMinWidth, mnMinHeight);
+}
+
+void ImplBorderWindow::queue_resize(StateChangedType eReason)
+{
+ //if we are floating, then we don't want to inform our parent that it needs
+ //to calculate a new layout allocation. Because while we are a child
+ //of our parent we are not embedded into the parent so it doesn't care
+ //about us.
+ if (mbFloatWindow)
+ return;
+ vcl::Window::queue_resize(eReason);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/bubblewindow.cxx b/vcl/source/window/bubblewindow.cxx
new file mode 100644
index 000000000..1a4a46a3f
--- /dev/null
+++ b/vcl/source/window/bubblewindow.cxx
@@ -0,0 +1,557 @@
+/* -*- 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 <rtl/ustrbuf.hxx>
+#include <vcl/menubarupdateicon.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <unotools/resmgr.hxx>
+#include <bubblewindow.hxx>
+#include <bitmaps.hlst>
+
+#define TIP_HEIGHT 15
+#define TIP_WIDTH 7
+#define TIP_RIGHT_OFFSET 18
+#define BUBBLE_BORDER 10
+#define TEXT_MAX_WIDTH 300
+#define TEXT_MAX_HEIGHT 200
+
+BubbleWindow::BubbleWindow( vcl::Window* pParent, const OUString& rTitle,
+ const OUString& rText, const Image& rImage )
+ : FloatingWindow( pParent, WB_SYSTEMWINDOW
+ | WB_OWNERDRAWDECORATION
+ | WB_NOBORDER
+ )
+ , maBubbleTitle( rTitle )
+ , maBubbleText( rText )
+ , maBubbleImage( rImage )
+ , maMaxTextSize( TEXT_MAX_WIDTH, TEXT_MAX_HEIGHT )
+ , mnTipOffset( 0 )
+{
+ SetBackground( Wallpaper( GetSettings().GetStyleSettings().GetHelpColor() ) );
+}
+
+void BubbleWindow::Resize()
+{
+ FloatingWindow::Resize();
+
+ Size aSize = GetSizePixel();
+
+ if ( ( aSize.Height() < 20 ) || ( aSize.Width() < 60 ) )
+ return;
+
+ tools::Rectangle aRect( 0, TIP_HEIGHT, aSize.Width(), aSize.Height() - TIP_HEIGHT );
+ maRectPoly = tools::Polygon( aRect, 6, 6 );
+ vcl::Region aRegion( maRectPoly );
+ tools::Long nTipOffset = aSize.Width() - TIP_RIGHT_OFFSET + mnTipOffset;
+
+ Point aPointArr[4];
+ aPointArr[0] = Point( nTipOffset, TIP_HEIGHT );
+ aPointArr[1] = Point( nTipOffset, 0 );
+ aPointArr[2] = Point( nTipOffset + TIP_WIDTH , TIP_HEIGHT );
+ aPointArr[3] = Point( nTipOffset, TIP_HEIGHT );
+ maTriPoly = tools::Polygon( 4, aPointArr );
+ vcl::Region aTriRegion( maTriPoly );
+
+ aRegion.Union( aTriRegion);
+ maBounds = aRegion;
+
+ SetWindowRegionPixel( maBounds );
+}
+
+void BubbleWindow::SetTitleAndText( const OUString& rTitle,
+ const OUString& rText,
+ const Image& rImage )
+{
+ maBubbleTitle = rTitle;
+ maBubbleText = rText;
+ maBubbleImage = rImage;
+
+ Resize();
+}
+
+void BubbleWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
+{
+ LineInfo aThickLine( LineStyle::Solid, 2 );
+
+ rRenderContext.DrawPolyLine( maRectPoly, aThickLine );
+ rRenderContext.DrawPolyLine( maTriPoly );
+
+ Color aOldLine = rRenderContext.GetLineColor();
+ Size aSize = GetSizePixel();
+ tools::Long nTipOffset = aSize.Width() - TIP_RIGHT_OFFSET + mnTipOffset;
+
+ rRenderContext.SetLineColor( GetSettings().GetStyleSettings().GetHelpColor() );
+ rRenderContext.DrawLine( Point( nTipOffset+2, TIP_HEIGHT ),
+ Point( nTipOffset + TIP_WIDTH -1 , TIP_HEIGHT ),
+ aThickLine );
+ rRenderContext.SetLineColor( aOldLine );
+
+ Size aImgSize = maBubbleImage.GetSizePixel();
+
+ rRenderContext.DrawImage( Point( BUBBLE_BORDER, BUBBLE_BORDER + TIP_HEIGHT ), maBubbleImage );
+
+ vcl::Font aOldFont = GetFont();
+ vcl::Font aBoldFont = aOldFont;
+ aBoldFont.SetWeight( WEIGHT_BOLD );
+
+ SetFont( aBoldFont );
+ tools::Rectangle aTitleRect = maTitleRect;
+ aTitleRect.Move( aImgSize.Width(), 0 );
+ rRenderContext.DrawText( aTitleRect, maBubbleTitle, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
+
+ SetFont( aOldFont );
+ tools::Rectangle aTextRect = maTextRect;
+ aTextRect.Move( aImgSize.Width(), 0 );
+ rRenderContext.DrawText( aTextRect, maBubbleText, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
+}
+
+void BubbleWindow::MouseButtonDown( const MouseEvent& )
+{
+ Show( false );
+}
+
+void BubbleWindow::Show( bool bVisible )
+{
+ if ( !bVisible )
+ {
+ FloatingWindow::Show( bVisible );
+ return;
+ }
+
+ // don't show bubbles without a text
+ if ( ( maBubbleTitle.isEmpty() ) && ( maBubbleText.isEmpty() ) )
+ return;
+
+ Size aWindowSize = GetSizePixel();
+
+ Size aImgSize = maBubbleImage.GetSizePixel();
+
+ RecalcTextRects();
+
+ aWindowSize.setHeight( maTitleRect.GetHeight() * 7 / 4+ maTextRect.GetHeight() +
+ 3 * BUBBLE_BORDER + TIP_HEIGHT );
+
+ if ( maTitleRect.GetWidth() > maTextRect.GetWidth() )
+ aWindowSize.setWidth( maTitleRect.GetWidth() );
+ else
+ aWindowSize.setWidth( maTextRect.GetWidth() );
+
+ aWindowSize.setWidth( aWindowSize.Width() + 3 * BUBBLE_BORDER + aImgSize.Width() );
+
+ if ( aWindowSize.Height() < aImgSize.Height() + TIP_HEIGHT + 2 * BUBBLE_BORDER )
+ aWindowSize.setHeight( aImgSize.Height() + TIP_HEIGHT + 2 * BUBBLE_BORDER );
+
+ Point aPos;
+ aPos.setX( maTipPos.X() - aWindowSize.Width() + TIP_RIGHT_OFFSET );
+ aPos.setY( maTipPos.Y() );
+ Point aScreenPos = GetParent()->OutputToAbsoluteScreenPixel( aPos );
+ if ( aScreenPos.X() < 0 )
+ {
+ mnTipOffset = aScreenPos.X();
+ aPos.AdjustX( -mnTipOffset );
+ }
+ SetPosSizePixel( aPos, aWindowSize );
+
+ FloatingWindow::Show( bVisible, ShowFlags::NoActivate );
+}
+
+void BubbleWindow::RecalcTextRects()
+{
+ Size aTotalSize;
+ bool bFinished = false;
+ vcl::Font aOldFont = GetFont();
+ vcl::Font aBoldFont = aOldFont;
+
+ aBoldFont.SetWeight( WEIGHT_BOLD );
+
+ while ( !bFinished )
+ {
+ SetFont( aBoldFont );
+
+ maTitleRect = GetTextRect( tools::Rectangle( Point( 0, 0 ), maMaxTextSize ),
+ maBubbleTitle,
+ DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
+
+ SetFont( aOldFont );
+ maTextRect = GetTextRect( tools::Rectangle( Point( 0, 0 ), maMaxTextSize ),
+ maBubbleText,
+ DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
+
+ if ( maTextRect.GetHeight() < 10 )
+ maTextRect.setHeight( 10 );
+
+ aTotalSize.setHeight( maTitleRect.GetHeight() +
+ aBoldFont.GetFontHeight() * 3 / 4 +
+ maTextRect.GetHeight() +
+ 3 * BUBBLE_BORDER + TIP_HEIGHT );
+ if ( aTotalSize.Height() > maMaxTextSize.Height() )
+ {
+ maMaxTextSize.setWidth( maMaxTextSize.Width() * 3 / 2 );
+ maMaxTextSize.setHeight( maMaxTextSize.Height() * 3 / 2 );
+ }
+ else
+ bFinished = true;
+ }
+ maTitleRect.Move( 2*BUBBLE_BORDER, BUBBLE_BORDER + TIP_HEIGHT );
+ maTextRect.Move( 2*BUBBLE_BORDER, BUBBLE_BORDER + TIP_HEIGHT + maTitleRect.GetHeight() + aBoldFont.GetFontHeight() * 3 / 4 );
+}
+
+MenuBarUpdateIconManager::MenuBarUpdateIconManager()
+ : maTimeoutTimer("MenuBarUpdateIconManager")
+ , maWaitIdle("vcl MenuBarUpdateIconManager maWaitIdle")
+ , mnIconID (0)
+ , mbShowMenuIcon(false)
+ , mbShowBubble(false)
+ , mbBubbleChanged( false )
+{
+ maTimeoutTimer.SetTimeout( 10000 );
+ maTimeoutTimer.SetInvokeHandler(LINK(this, MenuBarUpdateIconManager, TimeOutHdl));
+
+ maWaitIdle.SetPriority( TaskPriority::LOWEST );
+ maWaitIdle.SetInvokeHandler(LINK(this, MenuBarUpdateIconManager, WaitTimeOutHdl));
+
+ maApplicationEventHdl = LINK(this, MenuBarUpdateIconManager, ApplicationEventHdl);
+ Application::AddEventListener( maApplicationEventHdl );
+
+ maWindowEventHdl = LINK(this, MenuBarUpdateIconManager, WindowEventHdl);
+}
+
+VclPtr<BubbleWindow> MenuBarUpdateIconManager::GetBubbleWindow()
+{
+ if ( !mpIconSysWin )
+ return nullptr;
+
+ tools::Rectangle aIconRect = mpIconMBar->GetMenuBarButtonRectPixel( mnIconID );
+ if( aIconRect.IsEmpty() )
+ return nullptr;
+
+ auto pBubbleWin = mpBubbleWin;
+
+ if ( !pBubbleWin ) {
+ pBubbleWin = VclPtr<BubbleWindow>::Create( mpIconSysWin, maBubbleTitle,
+ maBubbleText, maBubbleImage );
+ mbBubbleChanged = false;
+ }
+ else if ( mbBubbleChanged ) {
+ pBubbleWin->SetTitleAndText( maBubbleTitle, maBubbleText,
+ maBubbleImage );
+ mbBubbleChanged = false;
+ }
+
+ Point aWinPos = aIconRect.BottomCenter();
+
+ pBubbleWin->SetTipPosPixel( aWinPos );
+
+ return pBubbleWin;
+}
+
+IMPL_LINK_NOARG(MenuBarUpdateIconManager, TimeOutHdl, Timer *, void)
+{
+ RemoveBubbleWindow( false );
+}
+
+IMPL_LINK(MenuBarUpdateIconManager, WindowEventHdl, VclWindowEvent&, rEvent, void)
+{
+ VclEventId nEventID = rEvent.GetId();
+
+ if ( VclEventId::ObjectDying == nEventID )
+ {
+ if ( mpIconSysWin == rEvent.GetWindow() )
+ {
+ mpIconSysWin->RemoveEventListener( maWindowEventHdl );
+ RemoveBubbleWindow( true );
+ }
+ }
+ else if ( VclEventId::WindowMenubarAdded == nEventID )
+ {
+ vcl::Window *pWindow = rEvent.GetWindow();
+ if ( pWindow )
+ {
+ SystemWindow *pSysWin = pWindow->GetSystemWindow();
+ if ( pSysWin )
+ {
+ AddMenuBarIcon( pSysWin, false );
+ }
+ }
+ }
+ else if ( VclEventId::WindowMenubarRemoved == nEventID )
+ {
+ MenuBar *pMBar = static_cast<MenuBar*>(rEvent.GetData());
+ if ( pMBar && ( pMBar == mpIconMBar ) )
+ RemoveBubbleWindow( true );
+ }
+ else if ( ( nEventID == VclEventId::WindowMove ) ||
+ ( nEventID == VclEventId::WindowResize ) )
+ {
+ if ( ( mpIconSysWin == rEvent.GetWindow() ) &&
+ mpBubbleWin && ( mpIconMBar != nullptr ) )
+ {
+ tools::Rectangle aIconRect = mpIconMBar->GetMenuBarButtonRectPixel( mnIconID );
+ Point aWinPos = aIconRect.BottomCenter();
+ mpBubbleWin->SetTipPosPixel( aWinPos );
+ if ( mpBubbleWin->IsVisible() )
+ mpBubbleWin->Show(); // This will recalc the screen position of the bubble
+ }
+ }
+}
+
+IMPL_LINK(MenuBarUpdateIconManager, ApplicationEventHdl, VclSimpleEvent&, rEvent, void)
+{
+ switch (rEvent.GetId())
+ {
+ case VclEventId::WindowShow:
+ case VclEventId::WindowActivate:
+ case VclEventId::WindowGetFocus: {
+
+ vcl::Window *pWindow = static_cast< VclWindowEvent * >(&rEvent)->GetWindow();
+ if ( pWindow && pWindow->IsTopWindow() )
+ {
+ SystemWindow *pSysWin = pWindow->GetSystemWindow();
+ MenuBar *pMBar = pSysWin ? pSysWin->GetMenuBar() : nullptr;
+ if (pMBar)
+ {
+ AddMenuBarIcon( pSysWin, true );
+ }
+ }
+ break;
+ }
+ default: break;
+ }
+}
+
+IMPL_LINK_NOARG(MenuBarUpdateIconManager, UserEventHdl, void*, void)
+{
+ vcl::Window *pTopWin = Application::GetFirstTopLevelWindow();
+ vcl::Window *pActiveWin = Application::GetActiveTopWindow();
+ SystemWindow *pActiveSysWin = nullptr;
+
+ vcl::Window *pBubbleWin = nullptr;
+ if ( mpBubbleWin )
+ pBubbleWin = mpBubbleWin;
+
+ if ( pActiveWin && ( pActiveWin != pBubbleWin ) && pActiveWin->IsTopWindow() )
+ pActiveSysWin = pActiveWin->GetSystemWindow();
+
+ if ( pActiveWin == pBubbleWin )
+ pActiveSysWin = nullptr;
+
+ while ( !pActiveSysWin && pTopWin )
+ {
+ if ( ( pTopWin != pBubbleWin ) && pTopWin->IsTopWindow() )
+ pActiveSysWin = pTopWin->GetSystemWindow();
+ if ( !pActiveSysWin )
+ pTopWin = Application::GetNextTopLevelWindow( pTopWin );
+ }
+
+ if ( pActiveSysWin )
+ AddMenuBarIcon( pActiveSysWin, true );
+}
+
+IMPL_LINK_NOARG(MenuBarUpdateIconManager, ClickHdl, MenuBarButtonCallbackArg&, bool)
+{
+ maWaitIdle.Stop();
+ if ( mpBubbleWin )
+ mpBubbleWin->Show( false );
+
+ maClickHdl.Call(nullptr);
+
+ return false;
+}
+
+IMPL_LINK(MenuBarUpdateIconManager, HighlightHdl, MenuBarButtonCallbackArg&, rData, bool)
+{
+ if ( rData.bHighlight )
+ maWaitIdle.Start();
+ else
+ RemoveBubbleWindow(false);
+
+ return false;
+}
+
+IMPL_LINK_NOARG(MenuBarUpdateIconManager, WaitTimeOutHdl, Timer *, void)
+{
+ mpBubbleWin = GetBubbleWindow();
+
+ if ( mpBubbleWin )
+ {
+ mpBubbleWin->Show();
+ }
+}
+
+MenuBarUpdateIconManager::~MenuBarUpdateIconManager()
+{
+ Application::RemoveEventListener( maApplicationEventHdl );
+
+ RemoveBubbleWindow(true);
+}
+
+void MenuBarUpdateIconManager::SetShowMenuIcon(bool bShowMenuIcon)
+{
+ if ( bShowMenuIcon != mbShowMenuIcon )
+ {
+ mbShowMenuIcon = bShowMenuIcon;
+ if ( bShowMenuIcon )
+ Application::PostUserEvent(LINK(this, MenuBarUpdateIconManager, UserEventHdl));
+ else
+ RemoveBubbleWindow( true );
+ }
+}
+
+void MenuBarUpdateIconManager::SetShowBubble(bool bShowBubble)
+{
+ mbShowBubble = bShowBubble;
+ if ( mbShowBubble )
+ Application::PostUserEvent(LINK(this, MenuBarUpdateIconManager, UserEventHdl));
+ else if ( mpBubbleWin )
+ mpBubbleWin->Show( false );
+}
+
+void MenuBarUpdateIconManager::SetBubbleChanged()
+{
+ mbBubbleChanged = true;
+ if (mbBubbleChanged && mpBubbleWin)
+ mpBubbleWin->Show( false );
+}
+
+void MenuBarUpdateIconManager::SetBubbleImage(const Image& rImage)
+{
+ maBubbleImage = rImage;
+ SetBubbleChanged();
+}
+
+void MenuBarUpdateIconManager::SetBubbleTitle(const OUString& rTitle)
+{
+ if (rTitle != maBubbleTitle)
+ {
+ maBubbleTitle = rTitle;
+ SetBubbleChanged();
+ }
+}
+
+void MenuBarUpdateIconManager::SetBubbleText(const OUString& rText)
+{
+ if (rText != maBubbleText)
+ {
+ maBubbleText = rText;
+ SetBubbleChanged();
+ }
+}
+
+namespace {
+Image GetMenuBarIcon( MenuBar const * pMBar )
+{
+ OUString sResID;
+ vcl::Window *pMBarWin = pMBar->GetWindow();
+ sal_uInt32 nMBarHeight = 20;
+
+ if ( pMBarWin )
+ nMBarHeight = pMBarWin->GetOutputSizePixel().getHeight();
+
+ if (nMBarHeight >= 35)
+ sResID = RID_UPDATE_AVAILABLE_26;
+ else
+ sResID = RID_UPDATE_AVAILABLE_16;
+
+ return Image(StockImage::Yes, sResID);
+}
+}
+
+void MenuBarUpdateIconManager::AddMenuBarIcon(SystemWindow *pSysWin, bool bAddEventHdl)
+{
+ if ( ! mbShowMenuIcon )
+ return;
+
+ MenuBar *pActiveMBar = pSysWin->GetMenuBar();
+ if ( ( pSysWin != mpIconSysWin ) || ( pActiveMBar != mpIconMBar ) )
+ {
+ if ( bAddEventHdl && mpIconSysWin )
+ mpIconSysWin->RemoveEventListener( maWindowEventHdl );
+
+ RemoveBubbleWindow( true );
+
+ if ( pActiveMBar )
+ {
+ OUStringBuffer aBuf;
+ if( !maBubbleTitle.isEmpty() )
+ aBuf.append( maBubbleTitle );
+ if( !maBubbleText.isEmpty() )
+ {
+ if( !maBubbleTitle.isEmpty() )
+ aBuf.append( "\n\n" );
+ aBuf.append( maBubbleText );
+ }
+
+ Image aImage = GetMenuBarIcon( pActiveMBar );
+ mnIconID = pActiveMBar->AddMenuBarButton( aImage,
+ LINK( this, MenuBarUpdateIconManager, ClickHdl ),
+ aBuf.makeStringAndClear()
+ );
+ pActiveMBar->SetMenuBarButtonHighlightHdl( mnIconID,
+ LINK( this, MenuBarUpdateIconManager, HighlightHdl ) );
+ }
+ mpIconMBar = pActiveMBar;
+ mpIconSysWin = pSysWin;
+ if ( bAddEventHdl && mpIconSysWin )
+ mpIconSysWin->AddEventListener( maWindowEventHdl );
+ }
+
+ if ( mbShowBubble && pActiveMBar )
+ {
+ mpBubbleWin = GetBubbleWindow();
+ if ( mpBubbleWin )
+ {
+ mpBubbleWin->Show();
+ maTimeoutTimer.Start();
+ }
+ mbShowBubble = false;
+ }
+}
+
+void MenuBarUpdateIconManager::RemoveBubbleWindow( bool bRemoveIcon )
+{
+ maWaitIdle.Stop();
+ maTimeoutTimer.Stop();
+
+ if ( mpBubbleWin )
+ {
+ mpBubbleWin.disposeAndClear();
+ }
+
+ if ( !bRemoveIcon )
+ return;
+
+ try {
+ if ( mpIconMBar && ( mnIconID != 0 ) )
+ {
+ mpIconMBar->RemoveMenuBarButton( mnIconID );
+ mpIconMBar = nullptr;
+ mnIconID = 0;
+ }
+ }
+ catch ( ... ) {
+ mpIconMBar = nullptr;
+ mnIconID = 0;
+ }
+
+ mpIconSysWin = nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/bufferdevice.cxx b/vcl/source/window/bufferdevice.cxx
new file mode 100644
index 000000000..188fbb1ac
--- /dev/null
+++ b/vcl/source/window/bufferdevice.cxx
@@ -0,0 +1,45 @@
+/* -*- 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/.
+ */
+
+#include "bufferdevice.hxx"
+
+namespace vcl
+{
+BufferDevice::BufferDevice(const VclPtr<vcl::Window>& pWindow, vcl::RenderContext& rRenderContext)
+ : m_pBuffer(VclPtr<VirtualDevice>::Create(rRenderContext))
+ , m_pWindow(pWindow)
+ , m_rRenderContext(rRenderContext)
+{
+ m_pBuffer->SetOutputSizePixel(pWindow->GetOutputSizePixel(), false);
+ m_pBuffer->SetTextColor(rRenderContext.GetTextColor());
+ m_pBuffer->DrawOutDev(Point(0, 0), pWindow->GetOutputSizePixel(), Point(0, 0),
+ pWindow->GetOutputSizePixel(), rRenderContext);
+ m_pBuffer->EnableRTL(rRenderContext.IsRTLEnabled());
+}
+
+void BufferDevice::Dispose()
+{
+ if (m_bDisposed)
+ {
+ return;
+ }
+
+ m_rRenderContext.DrawOutDev(Point(0, 0), m_pWindow->GetOutputSizePixel(), Point(0, 0),
+ m_pWindow->GetOutputSizePixel(), *m_pBuffer);
+ m_bDisposed = true;
+}
+
+BufferDevice::~BufferDevice() { Dispose(); }
+
+vcl::RenderContext* BufferDevice::operator->() { return m_pBuffer.get(); }
+
+vcl::RenderContext& BufferDevice::operator*() { return *m_pBuffer; }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/bufferdevice.hxx b/vcl/source/window/bufferdevice.hxx
new file mode 100644
index 000000000..eafc829e5
--- /dev/null
+++ b/vcl/source/window/bufferdevice.hxx
@@ -0,0 +1,35 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+
+namespace vcl
+{
+/// Buffers drawing on a vcl::RenderContext using a VirtualDevice.
+class VCL_DLLPUBLIC BufferDevice
+{
+ ScopedVclPtr<VirtualDevice> m_pBuffer;
+ VclPtr<vcl::Window> m_pWindow;
+ vcl::RenderContext& m_rRenderContext;
+ bool m_bDisposed = false;
+
+public:
+ BufferDevice(const VclPtr<vcl::Window>& pWindow, vcl::RenderContext& rRenderContext);
+ ~BufferDevice();
+ void Dispose();
+
+ vcl::RenderContext* operator->();
+ vcl::RenderContext& operator*();
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/builder.cxx b/vcl/source/window/builder.cxx
new file mode 100644
index 000000000..d25426ced
--- /dev/null
+++ b/vcl/source/window/builder.cxx
@@ -0,0 +1,4402 @@
+/* -*- 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/.
+ */
+
+#include <config_feature_desktop.h>
+#include <config_options.h>
+
+#include <memory>
+#include <string_view>
+#include <unordered_map>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+
+#include <comphelper/lok.hxx>
+#include <i18nutil/unicode.hxx>
+#include <jsdialog/enabled.hxx>
+#include <o3tl/string_view.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <osl/module.hxx>
+#include <sal/log.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/resmgr.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/dialoghelper.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/toolkit/field.hxx>
+#include <vcl/fieldvalues.hxx>
+#include <vcl/toolkit/fmtfield.hxx>
+#include <vcl/toolkit/fixed.hxx>
+#include <vcl/toolkit/fixedhyper.hxx>
+#include <vcl/headbar.hxx>
+#include <vcl/notebookbar/NotebookBarAddonsMerger.hxx>
+#include <vcl/toolkit/ivctrl.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/toolkit/menubtn.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/toolkit/prgsbar.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/split.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/toolkit/svtabbx.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/tabpage.hxx>
+#include <vcl/toolkit/throbber.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/toolkit/treelistentry.hxx>
+#include <vcl/toolkit/vclmedit.hxx>
+#include <vcl/settings.hxx>
+#include <slider.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/weldutils.hxx>
+#include <vcl/commandinfoprovider.hxx>
+#include <iconview.hxx>
+#include <svdata.hxx>
+#include <bitmaps.hlst>
+#include <managedmenubutton.hxx>
+#include <messagedialog.hxx>
+#include <ContextVBox.hxx>
+#include <DropdownBox.hxx>
+#include <IPrioritable.hxx>
+#include <OptionalBox.hxx>
+#include <PriorityMergedHBox.hxx>
+#include <PriorityHBox.hxx>
+#include <window.h>
+#include <xmlreader/xmlreader.hxx>
+#include <desktop/crashreport.hxx>
+#include <calendar.hxx>
+#include <menutogglebutton.hxx>
+#include <salinst.hxx>
+#include <strings.hrc>
+#include <treeglue.hxx>
+#include <tools/diagnose_ex.h>
+#include <verticaltabctrl.hxx>
+#include <wizdlg.hxx>
+#include <tools/svlibrary.h>
+#include <jsdialog/jsdialogbuilder.hxx>
+
+#if defined(DISABLE_DYNLOADING) || defined(LINUX)
+#include <dlfcn.h>
+#endif
+
+static bool toBool(std::string_view rValue)
+{
+ return (!rValue.empty() && (rValue[0] == 't' || rValue[0] == 'T' || rValue[0] == '1'));
+}
+
+namespace
+{
+ OUString mapStockToImageResource(std::u16string_view sType)
+ {
+ if (sType == u"view-refresh")
+ return SV_RESID_BITMAP_REFRESH;
+ else if (sType == u"dialog-error")
+ return IMG_ERROR;
+ else if (sType == u"list-add")
+ return IMG_ADD;
+ else if (sType == u"list-remove")
+ return IMG_REMOVE;
+ else if (sType == u"edit-copy")
+ return IMG_COPY;
+ else if (sType == u"edit-paste")
+ return IMG_PASTE;
+ else if (sType == u"document-open")
+ return IMG_OPEN;
+ else if (sType == u"open-menu-symbolic")
+ return IMG_MENU;
+ else if (sType == u"window-close-symbolic")
+ return SV_RESID_BITMAP_CLOSEDOC;
+ else if (sType == u"x-office-calendar")
+ return IMG_CALENDAR;
+ return OUString();
+ }
+
+}
+
+SymbolType VclBuilder::mapStockToSymbol(std::u16string_view sType)
+{
+ SymbolType eRet = SymbolType::DONTKNOW;
+ if (sType == u"media-skip-forward")
+ eRet = SymbolType::NEXT;
+ else if (sType == u"media-skip-backward")
+ eRet = SymbolType::PREV;
+ else if (sType == u"media-playback-start")
+ eRet = SymbolType::PLAY;
+ else if (sType == u"media-playback-stop")
+ eRet = SymbolType::STOP;
+ else if (sType == u"go-first")
+ eRet = SymbolType::FIRST;
+ else if (sType == u"go-last")
+ eRet = SymbolType::LAST;
+ else if (sType == u"go-previous")
+ eRet = SymbolType::ARROW_LEFT;
+ else if (sType == u"go-next")
+ eRet = SymbolType::ARROW_RIGHT;
+ else if (sType == u"go-up")
+ eRet = SymbolType::ARROW_UP;
+ else if (sType == u"go-down")
+ eRet = SymbolType::ARROW_DOWN;
+ else if (sType == u"missing-image")
+ eRet = SymbolType::IMAGE;
+ else if (sType == u"help-browser" || sType == u"help-browser-symbolic")
+ eRet = SymbolType::HELP;
+ else if (sType == u"window-close")
+ eRet = SymbolType::CLOSE;
+ else if (sType == u"document-new")
+ eRet = SymbolType::PLUS;
+ else if (sType == u"pan-down-symbolic")
+ eRet = SymbolType::SPIN_DOWN;
+ else if (sType == u"pan-up-symbolic")
+ eRet = SymbolType::SPIN_UP;
+ else if (!mapStockToImageResource(sType).isEmpty())
+ eRet = SymbolType::IMAGE;
+ return eRet;
+}
+
+namespace
+{
+ void setupFromActionName(Button *pButton, VclBuilder::stringmap &rMap, const css::uno::Reference<css::frame::XFrame>& rFrame);
+
+#if defined SAL_LOG_WARN
+ bool isButtonType(WindowType nType)
+ {
+ return nType == WindowType::PUSHBUTTON ||
+ nType == WindowType::OKBUTTON ||
+ nType == WindowType::CANCELBUTTON ||
+ nType == WindowType::HELPBUTTON ||
+ nType == WindowType::IMAGEBUTTON ||
+ nType == WindowType::MENUBUTTON ||
+ nType == WindowType::MOREBUTTON ||
+ nType == WindowType::SPINBUTTON;
+ }
+#endif
+
+}
+
+std::unique_ptr<weld::Builder> Application::CreateBuilder(weld::Widget* pParent, const OUString &rUIFile, bool bMobile, sal_uInt64 nLOKWindowId)
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ if (jsdialog::isBuilderEnabledForSidebar(rUIFile))
+ return JSInstanceBuilder::CreateSidebarBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile, nLOKWindowId);
+ else if (jsdialog::isBuilderEnabledForPopup(rUIFile))
+ return JSInstanceBuilder::CreatePopupBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile);
+ else if (jsdialog::isBuilderEnabled(rUIFile, bMobile))
+ return JSInstanceBuilder::CreateDialogBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile);
+ }
+
+ return ImplGetSVData()->mpDefInst->CreateBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile);
+}
+
+std::unique_ptr<weld::Builder> Application::CreateInterimBuilder(vcl::Window* pParent, const OUString &rUIFile, bool bAllowCycleFocusOut, sal_uInt64 nLOKWindowId)
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // Notebookbar sub controls
+ if (jsdialog::isInterimBuilderEnabledForNotebookbar(rUIFile))
+ return JSInstanceBuilder::CreateNotebookbarBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile, css::uno::Reference<css::frame::XFrame>(), nLOKWindowId);
+ else if (rUIFile == u"modules/scalc/ui/inputbar.ui")
+ return JSInstanceBuilder::CreateFormulabarBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile, nLOKWindowId);
+ }
+
+ return ImplGetSVData()->mpDefInst->CreateInterimBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile, bAllowCycleFocusOut, nLOKWindowId);
+}
+
+weld::MessageDialog* Application::CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType,
+ VclButtonsType eButtonType, const OUString& rPrimaryMessage,
+ bool /*bMobile*/)
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return JSInstanceBuilder::CreateMessageDialog(pParent, eMessageType, eButtonType, rPrimaryMessage);
+ else
+ return ImplGetSVData()->mpDefInst->CreateMessageDialog(pParent, eMessageType, eButtonType, rPrimaryMessage);
+}
+
+weld::Window* Application::GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow)
+{
+ return ImplGetSVData()->mpDefInst->GetFrameWeld(rWindow);
+}
+
+namespace weld
+{
+ OUString MetricSpinButton::MetricToString(FieldUnit rUnit)
+ {
+ const FieldUnitStringList& rList = ImplGetFieldUnits();
+ // return unit's default string (ie, the first one )
+ auto it = std::find_if(
+ rList.begin(), rList.end(),
+ [&rUnit](const std::pair<OUString, FieldUnit>& rItem) { return rItem.second == rUnit; });
+ if (it != rList.end())
+ return it->first;
+
+ return OUString();
+ }
+
+ IMPL_LINK_NOARG(MetricSpinButton, spin_button_value_changed, SpinButton&, void)
+ {
+ signal_value_changed();
+ }
+
+ IMPL_LINK(MetricSpinButton, spin_button_output, SpinButton&, rSpinButton, void)
+ {
+ OUString sNewText(format_number(rSpinButton.get_value()));
+ if (sNewText != rSpinButton.get_text())
+ rSpinButton.set_text(sNewText);
+ }
+
+ void MetricSpinButton::update_width_chars()
+ {
+ sal_Int64 min, max;
+ m_xSpinButton->get_range(min, max);
+ auto width = std::max(m_xSpinButton->get_pixel_size(format_number(min)).Width(),
+ m_xSpinButton->get_pixel_size(format_number(max)).Width());
+ int chars = ceil(width / m_xSpinButton->get_approximate_digit_width());
+ m_xSpinButton->set_width_chars(chars);
+ }
+
+ unsigned int SpinButton::Power10(unsigned int n)
+ {
+ unsigned int nValue = 1;
+ for (unsigned int i = 0; i < n; ++i)
+ nValue *= 10;
+ return nValue;
+ }
+
+ sal_Int64 SpinButton::denormalize(sal_Int64 nValue) const
+ {
+ const int nFactor = Power10(get_digits());
+
+ if ((nValue < (std::numeric_limits<sal_Int64>::min() + nFactor)) ||
+ (nValue > (std::numeric_limits<sal_Int64>::max() - nFactor)))
+ {
+ return nValue / nFactor;
+ }
+
+ const int nHalf = nFactor / 2;
+
+ if (nValue < 0)
+ return (nValue - nHalf) / nFactor;
+ return (nValue + nHalf) / nFactor;
+ }
+
+ OUString MetricSpinButton::format_number(sal_Int64 nValue) const
+ {
+ OUString aStr;
+
+ const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
+
+ unsigned int nDecimalDigits = m_xSpinButton->get_digits();
+ //pawn percent off to icu to decide whether percent is separated from its number for this locale
+ if (m_eSrcUnit == FieldUnit::PERCENT)
+ {
+ double fValue = nValue;
+ fValue /= SpinButton::Power10(nDecimalDigits);
+ aStr = unicode::formatPercent(fValue, rLocaleData.getLanguageTag());
+ }
+ else
+ {
+ aStr = rLocaleData.getNum(nValue, nDecimalDigits, true, true);
+ OUString aSuffix = MetricToString(m_eSrcUnit);
+ if (m_eSrcUnit != FieldUnit::NONE && m_eSrcUnit != FieldUnit::DEGREE && m_eSrcUnit != FieldUnit::INCH && m_eSrcUnit != FieldUnit::FOOT)
+ aStr += " ";
+ if (m_eSrcUnit == FieldUnit::INCH)
+ {
+ OUString sDoublePrime = u"\u2033";
+ if (aSuffix != "\"" && aSuffix != sDoublePrime)
+ aStr += " ";
+ else
+ aSuffix = sDoublePrime;
+ }
+ else if (m_eSrcUnit == FieldUnit::FOOT)
+ {
+ OUString sPrime = u"\u2032";
+ if (aSuffix != "'" && aSuffix != sPrime)
+ aStr += " ";
+ else
+ aSuffix = sPrime;
+ }
+
+ assert(m_eSrcUnit != FieldUnit::PERCENT);
+ aStr += aSuffix;
+ }
+
+ return aStr;
+ }
+
+ void MetricSpinButton::set_digits(unsigned int digits)
+ {
+ int step, page;
+ get_increments(step, page, m_eSrcUnit);
+ sal_Int64 value = get_value(m_eSrcUnit);
+ m_xSpinButton->set_digits(digits);
+ set_increments(step, page, m_eSrcUnit);
+ set_value(value, m_eSrcUnit);
+ update_width_chars();
+ }
+
+ void MetricSpinButton::set_unit(FieldUnit eUnit)
+ {
+ if (eUnit != m_eSrcUnit)
+ {
+ int step, page;
+ get_increments(step, page, m_eSrcUnit);
+ sal_Int64 value = get_value(m_eSrcUnit);
+ m_eSrcUnit = eUnit;
+ set_increments(step, page, m_eSrcUnit);
+ set_value(value, m_eSrcUnit);
+ spin_button_output(*m_xSpinButton);
+ update_width_chars();
+ }
+ }
+
+ sal_Int64 MetricSpinButton::ConvertValue(sal_Int64 nValue, FieldUnit eInUnit, FieldUnit eOutUnit) const
+ {
+ return vcl::ConvertValue(nValue, 0, m_xSpinButton->get_digits(), eInUnit, eOutUnit);
+ }
+
+ IMPL_LINK(MetricSpinButton, spin_button_input, int*, result, bool)
+ {
+ const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
+ double fResult(0.0);
+ bool bRet = vcl::TextToValue(get_text(), fResult, 0, m_xSpinButton->get_digits(), rLocaleData, m_eSrcUnit);
+ if (bRet)
+ {
+ if (fResult > SAL_MAX_INT32)
+ fResult = SAL_MAX_INT32;
+ else if (fResult < SAL_MIN_INT32)
+ fResult = SAL_MIN_INT32;
+ *result = fResult;
+ }
+ return bRet;
+ }
+
+ EntryTreeView::EntryTreeView(std::unique_ptr<Entry> xEntry, std::unique_ptr<TreeView> xTreeView)
+ : m_xEntry(std::move(xEntry))
+ , m_xTreeView(std::move(xTreeView))
+ {
+ m_xTreeView->connect_changed(LINK(this, EntryTreeView, ClickHdl));
+ m_xEntry->connect_changed(LINK(this, EntryTreeView, ModifyHdl));
+ }
+
+ IMPL_LINK(EntryTreeView, ClickHdl, weld::TreeView&, rView, void)
+ {
+ m_xEntry->set_text(rView.get_selected_text());
+ m_aChangeHdl.Call(*this);
+ }
+
+ IMPL_LINK_NOARG(EntryTreeView, ModifyHdl, weld::Entry&, void)
+ {
+ m_aChangeHdl.Call(*this);
+ }
+
+ void EntryTreeView::set_height_request_by_rows(int nRows)
+ {
+ int nHeight = nRows == -1 ? -1 : m_xTreeView->get_height_rows(nRows);
+ m_xTreeView->set_size_request(m_xTreeView->get_size_request().Width(), nHeight);
+ }
+
+ size_t GetAbsPos(const weld::TreeView& rTreeView, const weld::TreeIter& rIter)
+ {
+ size_t nAbsPos = 0;
+
+ std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator(&rIter));
+ if (!rTreeView.get_iter_first(*xEntry))
+ xEntry.reset();
+
+ while (xEntry && rTreeView.iter_compare(*xEntry, rIter) != 0)
+ {
+ if (!rTreeView.iter_next(*xEntry))
+ xEntry.reset();
+ nAbsPos++;
+ }
+
+ return nAbsPos;
+ }
+
+ bool IsEntryVisible(const weld::TreeView& rTreeView, const weld::TreeIter& rIter)
+ {
+ // short circuit for the common case
+ if (rTreeView.get_iter_depth(rIter) == 0)
+ return true;
+
+ std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator(&rIter));
+ bool bRetVal = false;
+ do
+ {
+ if (rTreeView.get_iter_depth(*xEntry) == 0)
+ {
+ bRetVal = true;
+ break;
+ }
+ } while (rTreeView.iter_parent(*xEntry) && rTreeView.get_row_expanded(*xEntry));
+ return bRetVal;
+ }
+}
+
+VclBuilder::VclBuilder(vcl::Window* pParent, const OUString& sUIDir, const OUString& sUIFile,
+ const OString& sID, const css::uno::Reference<css::frame::XFrame>& rFrame,
+ bool bLegacy, const NotebookBarAddonsItem* pNotebookBarAddonsItem)
+ : m_pNotebookBarAddonsItem(pNotebookBarAddonsItem
+ ? new NotebookBarAddonsItem(*pNotebookBarAddonsItem)
+ : new NotebookBarAddonsItem{})
+ , m_sID(sID)
+ , m_sHelpRoot(OUStringToOString(sUIFile, RTL_TEXTENCODING_UTF8))
+ , m_pStringReplace(Translate::GetReadStringHook())
+ , m_pParent(pParent)
+ , m_bToplevelParentFound(false)
+ , m_bLegacy(bLegacy)
+ , m_pParserState(new ParserState)
+ , m_xFrame(rFrame)
+{
+ m_bToplevelHasDeferredInit = pParent &&
+ ((pParent->IsSystemWindow() && static_cast<SystemWindow*>(pParent)->isDeferredInit()) ||
+ (pParent->IsDockingWindow() && static_cast<DockingWindow*>(pParent)->isDeferredInit()));
+ m_bToplevelHasDeferredProperties = m_bToplevelHasDeferredInit;
+
+ sal_Int32 nIdx = m_sHelpRoot.lastIndexOf('.');
+ if (nIdx != -1)
+ m_sHelpRoot = m_sHelpRoot.copy(0, nIdx);
+ m_sHelpRoot += "/";
+
+ OUString sUri = sUIDir + sUIFile;
+
+ try
+ {
+ xmlreader::XmlReader reader(sUri);
+
+ handleChild(pParent, nullptr, reader);
+ }
+ catch (const css::uno::Exception &rExcept)
+ {
+ DBG_UNHANDLED_EXCEPTION("vcl.builder", "Unable to read .ui file");
+ CrashReporter::addKeyValue("VclBuilderException", "Unable to read .ui file: " + rExcept.Message, CrashReporter::Write);
+ throw;
+ }
+
+ //Set Mnemonic widgets when everything has been imported
+ for (auto const& mnemonicWidget : m_pParserState->m_aMnemonicWidgetMaps)
+ {
+ FixedText *pOne = get<FixedText>(mnemonicWidget.m_sID);
+ vcl::Window *pOther = get(mnemonicWidget.m_sValue.toUtf8());
+ SAL_WARN_IF(!pOne || !pOther, "vcl", "missing either source " << mnemonicWidget.m_sID
+ << " or target " << mnemonicWidget.m_sValue << " member of Mnemonic Widget Mapping");
+ if (pOne && pOther)
+ pOne->set_mnemonic_widget(pOther);
+ }
+
+ //Set a11y relations and role when everything has been imported
+ for (auto const& elemAtk : m_pParserState->m_aAtkInfo)
+ {
+ vcl::Window *pSource = elemAtk.first;
+ const stringmap &rMap = elemAtk.second;
+
+ for (auto const& elemMap : rMap)
+ {
+ const OString &rType = elemMap.first;
+ const OUString &rParam = elemMap.second;
+ if (rType == "role")
+ {
+ sal_Int16 role = BuilderUtils::getRoleFromName(rParam.toUtf8());
+ if (role != com::sun::star::accessibility::AccessibleRole::UNKNOWN)
+ pSource->SetAccessibleRole(role);
+ }
+ else
+ {
+ vcl::Window *pTarget = get(rParam.toUtf8());
+ SAL_WARN_IF(!pTarget, "vcl", "missing parameter of a11y relation: " << rParam);
+ if (!pTarget)
+ continue;
+ if (rType == "labelled-by")
+ pSource->SetAccessibleRelationLabeledBy(pTarget);
+ else if (rType == "label-for")
+ pSource->SetAccessibleRelationLabelFor(pTarget);
+ else
+ {
+ SAL_WARN("vcl.builder", "unhandled a11y relation :" << rType);
+ }
+ }
+ }
+ }
+
+ //Set radiobutton groups when everything has been imported
+ for (auto const& elem : m_pParserState->m_aGroupMaps)
+ {
+ RadioButton *pOne = get<RadioButton>(elem.m_sID);
+ RadioButton *pOther = get<RadioButton>(elem.m_sValue);
+ SAL_WARN_IF(!pOne || !pOther, "vcl", "missing member of radiobutton group");
+ if (pOne && pOther)
+ {
+ if (m_bLegacy)
+ pOne->group(*pOther);
+ else
+ {
+ pOther->group(*pOne);
+ std::stable_sort(pOther->m_xGroup->begin(), pOther->m_xGroup->end(), sortIntoBestTabTraversalOrder(this));
+ }
+ }
+ }
+
+#ifndef NDEBUG
+ o3tl::sorted_vector<OUString> models;
+#endif
+ //Set ComboBox models when everything has been imported
+ for (auto const& elem : m_pParserState->m_aModelMaps)
+ {
+ assert(models.insert(elem.m_sValue).second && "a liststore or treestore is used in duplicate widgets");
+ vcl::Window* pTarget = get(elem.m_sID);
+ ListBox *pListBoxTarget = dynamic_cast<ListBox*>(pTarget);
+ ComboBox *pComboBoxTarget = dynamic_cast<ComboBox*>(pTarget);
+ SvTabListBox *pTreeBoxTarget = dynamic_cast<SvTabListBox*>(pTarget);
+ // pStore may be empty
+ const ListStore *pStore = get_model_by_name(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pListBoxTarget && !pComboBoxTarget && !pTreeBoxTarget && !dynamic_cast<IconView*>(pTarget), "vcl", "missing elements of combobox");
+ if (pListBoxTarget && pStore)
+ mungeModel(*pListBoxTarget, *pStore, elem.m_nActiveId);
+ else if (pComboBoxTarget && pStore)
+ mungeModel(*pComboBoxTarget, *pStore, elem.m_nActiveId);
+ else if (pTreeBoxTarget && pStore)
+ mungeModel(*pTreeBoxTarget, *pStore, elem.m_nActiveId);
+ }
+
+ //Set TextView buffers when everything has been imported
+ for (auto const& elem : m_pParserState->m_aTextBufferMaps)
+ {
+ VclMultiLineEdit *pTarget = get<VclMultiLineEdit>(elem.m_sID);
+ const TextBuffer *pBuffer = get_buffer_by_name(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget || !pBuffer, "vcl", "missing elements of textview/textbuffer");
+ if (pTarget && pBuffer)
+ mungeTextBuffer(*pTarget, *pBuffer);
+ }
+
+ //Set SpinButton adjustments when everything has been imported
+ for (auto const& elem : m_pParserState->m_aNumericFormatterAdjustmentMaps)
+ {
+ NumericFormatter *pTarget = dynamic_cast<NumericFormatter*>(get(elem.m_sID));
+ const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget, "vcl", "missing NumericFormatter element of spinbutton/adjustment");
+ SAL_WARN_IF(!pAdjustment, "vcl", "missing Adjustment element of spinbutton/adjustment");
+ if (pTarget && pAdjustment)
+ mungeAdjustment(*pTarget, *pAdjustment);
+ }
+
+ for (auto const& elem : m_pParserState->m_aFormattedFormatterAdjustmentMaps)
+ {
+ FormattedField *pTarget = dynamic_cast<FormattedField*>(get(elem.m_sID));
+ const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget, "vcl", "missing FormattedField element of spinbutton/adjustment");
+ SAL_WARN_IF(!pAdjustment, "vcl", "missing Adjustment element of spinbutton/adjustment");
+ if (pTarget && pAdjustment)
+ mungeAdjustment(*pTarget, *pAdjustment);
+ }
+
+ //Set ScrollBar adjustments when everything has been imported
+ for (auto const& elem : m_pParserState->m_aScrollAdjustmentMaps)
+ {
+ ScrollBar *pTarget = get<ScrollBar>(elem.m_sID);
+ const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget || !pAdjustment, "vcl", "missing elements of scrollbar/adjustment");
+ if (pTarget && pAdjustment)
+ mungeAdjustment(*pTarget, *pAdjustment);
+ }
+
+ //Set Scale(Slider) adjustments
+ for (auto const& elem : m_pParserState->m_aSliderAdjustmentMaps)
+ {
+ Slider* pTarget = dynamic_cast<Slider*>(get(elem.m_sID));
+ const Adjustment* pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget || !pAdjustment, "vcl", "missing elements of scale(slider)/adjustment");
+ if (pTarget && pAdjustment)
+ {
+ mungeAdjustment(*pTarget, *pAdjustment);
+ }
+ }
+
+ //Set size-groups when all widgets have been imported
+ for (auto const& sizeGroup : m_pParserState->m_aSizeGroups)
+ {
+ std::shared_ptr<VclSizeGroup> xGroup(std::make_shared<VclSizeGroup>());
+
+ for (auto const& elem : sizeGroup.m_aProperties)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+ xGroup->set_property(rKey, rValue);
+ }
+
+ for (auto const& elem : sizeGroup.m_aWidgets)
+ {
+ vcl::Window* pWindow = get(elem.getStr());
+ pWindow->add_to_size_group(xGroup);
+ }
+ }
+
+ //Set button images when everything has been imported
+ std::set<OUString> aImagesToBeRemoved;
+ for (auto const& elem : m_pParserState->m_aButtonImageWidgetMaps)
+ {
+ PushButton *pTargetButton = nullptr;
+ RadioButton *pTargetRadio = nullptr;
+ Button *pTarget = nullptr;
+
+ if (!elem.m_bRadio)
+ {
+ pTargetButton = get<PushButton>(elem.m_sID);
+ pTarget = pTargetButton;
+ }
+ else
+ {
+ pTargetRadio = get<RadioButton>(elem.m_sID);
+ pTarget = pTargetRadio;
+ }
+
+ FixedImage *pImage = get<FixedImage>(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget || !pImage,
+ "vcl", "missing elements of button/image/stock");
+ if (!pTarget || !pImage)
+ continue;
+ aImagesToBeRemoved.insert(elem.m_sValue);
+
+ if (!elem.m_bRadio)
+ {
+ const Image& rImage = pImage->GetImage();
+ SymbolType eSymbol = mapStockToSymbol(rImage.GetStock());
+ if (eSymbol != SymbolType::IMAGE && eSymbol != SymbolType::DONTKNOW)
+ {
+ pTargetButton->SetSymbol(eSymbol);
+ //fdo#76457 keep symbol images small e.g. tools->customize->menu
+ //but images the right size. Really the PushButton::CalcMinimumSize
+ //and PushButton::ImplDrawPushButton are the better place to handle
+ //this, but its such a train-wreck
+ pTargetButton->SetStyle(pTargetButton->GetStyle() | WB_SMALLSTYLE);
+ }
+ else
+ {
+ pTargetButton->SetModeImage(rImage);
+ if (pImage->GetStyle() & WB_SMALLSTYLE)
+ {
+ Size aSz(rImage.GetSizePixel());
+ aSz.AdjustWidth(6);
+ aSz.AdjustHeight(6);
+ if (pTargetButton->get_width_request() == -1)
+ pTargetButton->set_width_request(aSz.Width());
+ if (pTargetButton->get_height_request() == -1)
+ pTargetButton->set_height_request(aSz.Height());
+ }
+ }
+ }
+ else
+ pTargetRadio->SetModeRadioImage(pImage->GetImage());
+
+ auto aFind = m_pParserState->m_aImageSizeMap.find(elem.m_sValue.toUtf8());
+ if (aFind != m_pParserState->m_aImageSizeMap.end())
+ {
+ switch (aFind->second)
+ {
+ case 1:
+ pTarget->SetSmallSymbol();
+ break;
+ case 2:
+ assert(pImage->GetStyle() & WB_SMALLSTYLE);
+ pTarget->SetStyle(pTarget->GetStyle() | WB_SMALLSTYLE);
+ break;
+ case 3:
+ pTarget->SetStyle(pTarget->GetStyle() | WB_SMALLSTYLE);
+ // large toolbar, make bigger than normal (4)
+ pTarget->set_width_request(pTarget->GetOptimalSize().Width() * 1.5);
+ pTarget->set_height_request(pTarget->GetOptimalSize().Height() * 1.5);
+ break;
+ case 4:
+ break;
+ default:
+ SAL_WARN("vcl.builder", "unsupported image size " << aFind->second);
+ break;
+ }
+ m_pParserState->m_aImageSizeMap.erase(aFind);
+ }
+ }
+
+ //There may be duplicate use of an Image, so we used a set to collect and
+ //now we can remove them from the tree after their final munge
+ for (auto const& elem : aImagesToBeRemoved)
+ {
+ delete_by_name(elem.toUtf8());
+ }
+
+ //Set button menus when everything has been imported
+ for (auto const& elem : m_pParserState->m_aButtonMenuMaps)
+ {
+ MenuButton *pTarget = get<MenuButton>(elem.m_sID);
+ PopupMenu *pMenu = get_menu(elem.m_sValue.toUtf8());
+ SAL_WARN_IF(!pTarget || !pMenu,
+ "vcl", "missing elements of button/menu");
+ if (!pTarget || !pMenu)
+ continue;
+ pTarget->SetPopupMenu(pMenu);
+ }
+
+ //Remove ScrollWindow parent widgets whose children in vcl implement scrolling
+ //internally.
+ for (auto const& elem : m_pParserState->m_aRedundantParentWidgets)
+ {
+ delete_by_window(elem.first);
+ }
+
+ //fdo#67378 merge the label into the disclosure button
+ for (auto const& elem : m_pParserState->m_aExpanderWidgets)
+ {
+ vcl::Window *pChild = elem->get_child();
+ vcl::Window* pLabel = elem->GetWindow(GetWindowType::LastChild);
+ if (pLabel && pLabel != pChild && pLabel->GetType() == WindowType::FIXEDTEXT)
+ {
+ FixedText *pLabelWidget = static_cast<FixedText*>(pLabel);
+ elem->set_label(pLabelWidget->GetText());
+ if (pLabelWidget->IsControlFont())
+ elem->get_label_widget()->SetControlFont(pLabelWidget->GetControlFont());
+ delete_by_window(pLabel);
+ }
+ }
+
+ // create message dialog message area now
+ for (auto const& elem : m_pParserState->m_aMessageDialogs)
+ elem->create_message_area();
+
+ //drop maps, etc. that we don't need again
+ m_pParserState.reset();
+
+ SAL_WARN_IF(!m_sID.isEmpty() && (!m_bToplevelParentFound && !get_by_name(m_sID)), "vcl.builder",
+ "Requested top level widget \"" << m_sID << "\" not found in " << sUIFile);
+
+#if defined SAL_LOG_WARN
+ if (m_bToplevelParentFound && m_pParent->IsDialog())
+ {
+ int nButtons = 0;
+ bool bHasDefButton = false;
+ for (auto const& child : m_aChildren)
+ {
+ if (isButtonType(child.m_pWindow->GetType()))
+ {
+ ++nButtons;
+ if (child.m_pWindow->GetStyle() & WB_DEFBUTTON)
+ {
+ bHasDefButton = true;
+ break;
+ }
+ }
+ }
+ SAL_WARN_IF(nButtons && !bHasDefButton, "vcl.builder", "No default button defined in " << sUIFile);
+ }
+#endif
+
+ const bool bHideHelp = comphelper::LibreOfficeKit::isActive() &&
+ officecfg::Office::Common::Help::HelpRootURL::get().isEmpty();
+ if (bHideHelp)
+ {
+ if (vcl::Window *pHelpButton = get("help"))
+ pHelpButton->Hide();
+ }
+}
+
+VclBuilder::~VclBuilder()
+{
+ disposeBuilder();
+}
+
+void VclBuilder::disposeBuilder()
+{
+ for (std::vector<WinAndId>::reverse_iterator aI = m_aChildren.rbegin(),
+ aEnd = m_aChildren.rend(); aI != aEnd; ++aI)
+ {
+ aI->m_pWindow.disposeAndClear();
+ }
+ m_aChildren.clear();
+
+ for (std::vector<MenuAndId>::reverse_iterator aI = m_aMenus.rbegin(),
+ aEnd = m_aMenus.rend(); aI != aEnd; ++aI)
+ {
+ aI->m_pMenu.disposeAndClear();
+ }
+ m_aMenus.clear();
+ m_pParent.clear();
+}
+
+namespace
+{
+ bool extractHasFrame(VclBuilder::stringmap& rMap)
+ {
+ bool bHasFrame = true;
+ VclBuilder::stringmap::iterator aFind = rMap.find("has-frame");
+ if (aFind != rMap.end())
+ {
+ bHasFrame = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bHasFrame;
+ }
+
+ bool extractDrawValue(VclBuilder::stringmap& rMap)
+ {
+ bool bDrawValue = true;
+ VclBuilder::stringmap::iterator aFind = rMap.find("draw-value");
+ if (aFind != rMap.end())
+ {
+ bDrawValue = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bDrawValue;
+ }
+
+ OUString extractPopupMenu(VclBuilder::stringmap& rMap)
+ {
+ OUString sRet;
+ VclBuilder::stringmap::iterator aFind = rMap.find("popup");
+ if (aFind != rMap.end())
+ {
+ sRet = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sRet;
+ }
+
+ OUString extractWidgetName(VclBuilder::stringmap& rMap)
+ {
+ OUString sRet;
+ VclBuilder::stringmap::iterator aFind = rMap.find("name");
+ if (aFind != rMap.end())
+ {
+ sRet = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sRet;
+ }
+
+ OUString extractValuePos(VclBuilder::stringmap& rMap)
+ {
+ OUString sRet("top");
+ VclBuilder::stringmap::iterator aFind = rMap.find("value-pos");
+ if (aFind != rMap.end())
+ {
+ sRet = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sRet;
+ }
+
+ OUString extractTypeHint(VclBuilder::stringmap &rMap)
+ {
+ OUString sRet("normal");
+ VclBuilder::stringmap::iterator aFind = rMap.find("type-hint");
+ if (aFind != rMap.end())
+ {
+ sRet = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sRet;
+ }
+
+ bool extractResizable(VclBuilder::stringmap &rMap)
+ {
+ bool bResizable = true;
+ VclBuilder::stringmap::iterator aFind = rMap.find("resizable");
+ if (aFind != rMap.end())
+ {
+ bResizable = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bResizable;
+ }
+
+#if HAVE_FEATURE_DESKTOP
+ bool extractModal(VclBuilder::stringmap &rMap)
+ {
+ bool bModal = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find("modal");
+ if (aFind != rMap.end())
+ {
+ bModal = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bModal;
+ }
+#endif
+
+ bool extractDecorated(VclBuilder::stringmap &rMap)
+ {
+ bool bDecorated = true;
+ VclBuilder::stringmap::iterator aFind = rMap.find("decorated");
+ if (aFind != rMap.end())
+ {
+ bDecorated = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bDecorated;
+ }
+
+ bool extractCloseable(VclBuilder::stringmap &rMap)
+ {
+ bool bCloseable = true;
+ VclBuilder::stringmap::iterator aFind = rMap.find("deletable");
+ if (aFind != rMap.end())
+ {
+ bCloseable = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bCloseable;
+ }
+
+ bool extractEntry(VclBuilder::stringmap &rMap)
+ {
+ bool bHasEntry = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find("has-entry");
+ if (aFind != rMap.end())
+ {
+ bHasEntry = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bHasEntry;
+ }
+
+ bool extractOrientation(VclBuilder::stringmap &rMap)
+ {
+ bool bVertical = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find("orientation");
+ if (aFind != rMap.end())
+ {
+ bVertical = aFind->second.equalsIgnoreAsciiCase("vertical");
+ rMap.erase(aFind);
+ }
+ return bVertical;
+ }
+
+ bool extractVerticalTabPos(VclBuilder::stringmap &rMap)
+ {
+ bool bVertical = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find("tab-pos");
+ if (aFind != rMap.end())
+ {
+ bVertical = aFind->second.equalsIgnoreAsciiCase("left") ||
+ aFind->second.equalsIgnoreAsciiCase("right");
+ rMap.erase(aFind);
+ }
+ return bVertical;
+ }
+
+ bool extractInconsistent(VclBuilder::stringmap &rMap)
+ {
+ bool bInconsistent = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find("inconsistent");
+ if (aFind != rMap.end())
+ {
+ bInconsistent = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bInconsistent;
+ }
+
+ OUString extractIconName(VclBuilder::stringmap &rMap)
+ {
+ OUString sIconName;
+ // allow pixbuf, but prefer icon-name
+ {
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("pixbuf"));
+ if (aFind != rMap.end())
+ {
+ sIconName = aFind->second;
+ rMap.erase(aFind);
+ }
+ }
+ {
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("icon-name"));
+ if (aFind != rMap.end())
+ {
+ sIconName = aFind->second;
+ rMap.erase(aFind);
+ }
+ }
+ if (sIconName == "missing-image")
+ return OUString();
+ OUString sReplace = mapStockToImageResource(sIconName);
+ return !sReplace.isEmpty() ? sReplace : sIconName;
+ }
+
+ WinBits extractRelief(VclBuilder::stringmap &rMap)
+ {
+ WinBits nBits = WB_3DLOOK;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("relief"));
+ if (aFind != rMap.end())
+ {
+ if (aFind->second == "half")
+ nBits = WB_FLATBUTTON | WB_BEVELBUTTON;
+ else if (aFind->second == "none")
+ nBits = WB_FLATBUTTON;
+ rMap.erase(aFind);
+ }
+ return nBits;
+ }
+
+ OUString extractLabel(VclBuilder::stringmap &rMap)
+ {
+ OUString sType;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("label"));
+ if (aFind != rMap.end())
+ {
+ sType = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sType;
+ }
+
+ OUString extractActionName(VclBuilder::stringmap &rMap)
+ {
+ OUString sActionName;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("action-name"));
+ if (aFind != rMap.end())
+ {
+ sActionName = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sActionName;
+ }
+
+ bool extractVisible(VclBuilder::stringmap &rMap)
+ {
+ bool bRet = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("visible"));
+ if (aFind != rMap.end())
+ {
+ bRet = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bRet;
+ }
+
+ Size extractSizeRequest(VclBuilder::stringmap &rMap)
+ {
+ OUString sWidthRequest("0");
+ OUString sHeightRequest("0");
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("width-request"));
+ if (aFind != rMap.end())
+ {
+ sWidthRequest = aFind->second;
+ rMap.erase(aFind);
+ }
+ aFind = rMap.find("height-request");
+ if (aFind != rMap.end())
+ {
+ sHeightRequest = aFind->second;
+ rMap.erase(aFind);
+ }
+ return Size(sWidthRequest.toInt32(), sHeightRequest.toInt32());
+ }
+
+ OUString extractTooltipText(VclBuilder::stringmap &rMap)
+ {
+ OUString sTooltipText;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("tooltip-text"));
+ if (aFind == rMap.end())
+ aFind = rMap.find(OString("tooltip-markup"));
+ if (aFind != rMap.end())
+ {
+ sTooltipText = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sTooltipText;
+ }
+
+ float extractAlignment(VclBuilder::stringmap &rMap)
+ {
+ float f = 0.0;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("alignment"));
+ if (aFind != rMap.end())
+ {
+ f = aFind->second.toFloat();
+ rMap.erase(aFind);
+ }
+ return f;
+ }
+
+ OUString extractTitle(VclBuilder::stringmap &rMap)
+ {
+ OUString sTitle;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("title"));
+ if (aFind != rMap.end())
+ {
+ sTitle = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sTitle;
+ }
+
+ bool extractHeadersVisible(VclBuilder::stringmap &rMap)
+ {
+ bool bHeadersVisible = true;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("headers-visible"));
+ if (aFind != rMap.end())
+ {
+ bHeadersVisible = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bHeadersVisible;
+ }
+
+ bool extractSortIndicator(VclBuilder::stringmap &rMap)
+ {
+ bool bSortIndicator = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("sort-indicator"));
+ if (aFind != rMap.end())
+ {
+ bSortIndicator = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bSortIndicator;
+ }
+
+ bool extractClickable(VclBuilder::stringmap &rMap)
+ {
+ bool bClickable = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("clickable"));
+ if (aFind != rMap.end())
+ {
+ bClickable = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bClickable;
+ }
+
+ void setupFromActionName(Button *pButton, VclBuilder::stringmap &rMap, const css::uno::Reference<css::frame::XFrame>& rFrame)
+ {
+ if (!rFrame.is())
+ return;
+
+ OUString aCommand(extractActionName(rMap));
+ if (aCommand.isEmpty())
+ return;
+
+ OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(rFrame));
+ auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommand, aModuleName);
+ OUString aLabel(vcl::CommandInfoProvider::GetLabelForCommand(aProperties));
+ if (!aLabel.isEmpty())
+ pButton->SetText(aLabel);
+
+ OUString aTooltip(vcl::CommandInfoProvider::GetTooltipForCommand(aCommand, aProperties, rFrame));
+ if (!aTooltip.isEmpty())
+ pButton->SetQuickHelpText(aTooltip);
+
+ Image aImage(vcl::CommandInfoProvider::GetImageForCommand(aCommand, rFrame));
+ pButton->SetModeImage(aImage);
+
+ pButton->SetCommandHandler(aCommand);
+ }
+
+ VclPtr<Button> extractStockAndBuildPushButton(vcl::Window *pParent, VclBuilder::stringmap &rMap, bool bToggle)
+ {
+ WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER;
+ if (bToggle)
+ nBits |= WB_TOGGLE;
+
+ nBits |= extractRelief(rMap);
+
+ VclPtr<Button> xWindow = VclPtr<PushButton>::Create(pParent, nBits);
+ return xWindow;
+ }
+
+ VclPtr<MenuButton> extractStockAndBuildMenuButton(vcl::Window *pParent, VclBuilder::stringmap &rMap)
+ {
+ WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_3DLOOK;
+
+ nBits |= extractRelief(rMap);
+
+ VclPtr<MenuButton> xWindow = VclPtr<MenuButton>::Create(pParent, nBits);
+ return xWindow;
+ }
+
+ VclPtr<MenuButton> extractStockAndBuildMenuToggleButton(vcl::Window *pParent, VclBuilder::stringmap &rMap)
+ {
+ WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_3DLOOK;
+
+ nBits |= extractRelief(rMap);
+
+ VclPtr<MenuButton> xWindow = VclPtr<MenuToggleButton>::Create(pParent, nBits);
+ return xWindow;
+ }
+
+ WinBits extractDeferredBits(VclBuilder::stringmap &rMap)
+ {
+ WinBits nBits = WB_3DLOOK|WB_HIDE;
+ if (extractResizable(rMap))
+ nBits |= WB_SIZEABLE;
+ if (extractCloseable(rMap))
+ nBits |= WB_CLOSEABLE;
+ if (!extractDecorated(rMap))
+ nBits |= WB_OWNERDRAWDECORATION;
+ OUString sType(extractTypeHint(rMap));
+ if (sType == "utility")
+ nBits |= WB_SYSTEMWINDOW | WB_DIALOGCONTROL | WB_MOVEABLE;
+ else if (sType == "popup-menu")
+ nBits |= WB_SYSTEMWINDOW | WB_DIALOGCONTROL | WB_POPUP;
+ else if (sType == "dock")
+ nBits |= WB_DOCKABLE | WB_MOVEABLE;
+ else
+ nBits |= WB_MOVEABLE;
+ return nBits;
+ }
+}
+
+void VclBuilder::extractGroup(const OString &id, stringmap &rMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("group"));
+ if (aFind != rMap.end())
+ {
+ OUString sID = aFind->second;
+ sal_Int32 nDelim = sID.indexOf(':');
+ if (nDelim != -1)
+ sID = sID.copy(0, nDelim);
+ m_pParserState->m_aGroupMaps.emplace_back(id, sID.toUtf8());
+ rMap.erase(aFind);
+ }
+}
+
+void VclBuilder::connectNumericFormatterAdjustment(const OString &id, const OUString &rAdjustment)
+{
+ if (!rAdjustment.isEmpty())
+ m_pParserState->m_aNumericFormatterAdjustmentMaps.emplace_back(id, rAdjustment);
+}
+
+void VclBuilder::connectFormattedFormatterAdjustment(const OString &id, const OUString &rAdjustment)
+{
+ if (!rAdjustment.isEmpty())
+ m_pParserState->m_aFormattedFormatterAdjustmentMaps.emplace_back(id, rAdjustment);
+}
+
+bool VclBuilder::extractAdjustmentToMap(const OString& id, VclBuilder::stringmap& rMap, std::vector<WidgetAdjustmentMap>& rAdjustmentMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("adjustment"));
+ if (aFind != rMap.end())
+ {
+ rAdjustmentMap.emplace_back(id, aFind->second);
+ rMap.erase(aFind);
+ return true;
+ }
+ return false;
+}
+
+namespace
+{
+ sal_Int32 extractActive(VclBuilder::stringmap &rMap)
+ {
+ sal_Int32 nActiveId = 0;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("active"));
+ if (aFind != rMap.end())
+ {
+ nActiveId = aFind->second.toInt32();
+ rMap.erase(aFind);
+ }
+ return nActiveId;
+ }
+
+ bool extractSelectable(VclBuilder::stringmap &rMap)
+ {
+ bool bSelectable = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("selectable"));
+ if (aFind != rMap.end())
+ {
+ bSelectable = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bSelectable;
+ }
+
+ OUString extractAdjustment(VclBuilder::stringmap &rMap)
+ {
+ OUString sAdjustment;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("adjustment"));
+ if (aFind != rMap.end())
+ {
+ sAdjustment= aFind->second;
+ rMap.erase(aFind);
+ return sAdjustment;
+ }
+ return sAdjustment;
+ }
+
+ bool extractDrawIndicator(VclBuilder::stringmap &rMap)
+ {
+ bool bDrawIndicator = false;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("draw-indicator"));
+ if (aFind != rMap.end())
+ {
+ bDrawIndicator = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bDrawIndicator;
+ }
+}
+
+void VclBuilder::extractModel(const OString &id, stringmap &rMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("model"));
+ if (aFind != rMap.end())
+ {
+ m_pParserState->m_aModelMaps.emplace_back(id, aFind->second,
+ extractActive(rMap));
+ rMap.erase(aFind);
+ }
+}
+
+void VclBuilder::extractBuffer(const OString &id, stringmap &rMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("buffer"));
+ if (aFind != rMap.end())
+ {
+ m_pParserState->m_aTextBufferMaps.emplace_back(id, aFind->second);
+ rMap.erase(aFind);
+ }
+}
+
+int VclBuilder::getImageSize(const stringmap &rMap)
+{
+ int nSize = 4;
+ auto aFind = rMap.find(OString("icon-size"));
+ if (aFind != rMap.end())
+ nSize = aFind->second.toInt32();
+ return nSize;
+}
+
+void VclBuilder::extractButtonImage(const OString &id, stringmap &rMap, bool bRadio)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("image"));
+ if (aFind != rMap.end())
+ {
+ m_pParserState->m_aButtonImageWidgetMaps.emplace_back(id, aFind->second, bRadio);
+ rMap.erase(aFind);
+ }
+}
+
+void VclBuilder::extractMnemonicWidget(const OString &rLabelID, stringmap &rMap)
+{
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("mnemonic-widget"));
+ if (aFind != rMap.end())
+ {
+ OUString sID = aFind->second;
+ sal_Int32 nDelim = sID.indexOf(':');
+ if (nDelim != -1)
+ sID = sID.copy(0, nDelim);
+ m_pParserState->m_aMnemonicWidgetMaps.emplace_back(rLabelID, sID);
+ rMap.erase(aFind);
+ }
+}
+
+vcl::Window* VclBuilder::prepareWidgetOwnScrolling(vcl::Window *pParent, WinBits &rWinStyle)
+{
+ //For Widgets that manage their own scrolling, if one appears as a child of
+ //a scrolling window shoehorn that scrolling settings to this widget and
+ //return the real parent to use
+ if (pParent && pParent->GetType() == WindowType::SCROLLWINDOW)
+ {
+ WinBits nScrollBits = pParent->GetStyle();
+ nScrollBits &= (WB_AUTOHSCROLL|WB_HSCROLL|WB_AUTOVSCROLL|WB_VSCROLL);
+ rWinStyle |= nScrollBits;
+ if (static_cast<VclScrolledWindow*>(pParent)->HasVisibleBorder())
+ rWinStyle |= WB_BORDER;
+ pParent = pParent->GetParent();
+ }
+
+ return pParent;
+}
+
+void VclBuilder::cleanupWidgetOwnScrolling(vcl::Window *pScrollParent, vcl::Window *pWindow, stringmap &rMap)
+{
+ //remove the redundant scrolling parent
+ sal_Int32 nWidthReq = pScrollParent->get_width_request();
+ rMap[OString("width-request")] = OUString::number(nWidthReq);
+ sal_Int32 nHeightReq = pScrollParent->get_height_request();
+ rMap[OString("height-request")] = OUString::number(nHeightReq);
+
+ m_pParserState->m_aRedundantParentWidgets[pScrollParent] = pWindow;
+}
+
+#ifndef DISABLE_DYNLOADING
+
+extern "C" { static void thisModule() {} }
+
+namespace {
+
+// Don't unload the module on destruction
+class NoAutoUnloadModule : public osl::Module
+{
+public:
+ ~NoAutoUnloadModule() { release(); }
+};
+
+}
+
+typedef std::map<OUString, std::shared_ptr<NoAutoUnloadModule>> ModuleMap;
+static ModuleMap g_aModuleMap;
+
+#if ENABLE_MERGELIBS
+static std::shared_ptr<NoAutoUnloadModule> g_pMergedLib = std::make_shared<NoAutoUnloadModule>();
+#endif
+
+#ifndef SAL_DLLPREFIX
+# define SAL_DLLPREFIX ""
+#endif
+
+#endif
+
+namespace vcl {
+
+void VclBuilderPreload()
+{
+#ifndef DISABLE_DYNLOADING
+
+#if ENABLE_MERGELIBS
+ g_pMergedLib->loadRelative(&thisModule, SVLIBRARY("merged"));
+#else
+// find -name '*ui*' | xargs grep 'class=".*lo-' |
+// sed 's/.*class="//' | sed 's/-.*$//' | sort | uniq
+ static const char *aWidgetLibs[] = {
+ "sfxlo", "svtlo"
+ };
+ for (const auto & lib : aWidgetLibs)
+ {
+ std::unique_ptr<NoAutoUnloadModule> pModule(new NoAutoUnloadModule);
+ OUString sModule = SAL_DLLPREFIX + OUString::createFromAscii(lib) + SAL_DLLEXTENSION;
+ if (pModule->loadRelative(&thisModule, sModule))
+ g_aModuleMap.insert(std::make_pair(sModule, std::move(pModule)));
+ }
+#endif // ENABLE_MERGELIBS
+#endif // DISABLE_DYNLOADING
+}
+
+}
+
+#if defined DISABLE_DYNLOADING && !HAVE_FEATURE_DESKTOP
+extern "C" VclBuilder::customMakeWidget lo_get_custom_widget_func(const char* name);
+#endif
+
+namespace
+{
+// Takes a string like "sfxlo-NotebookbarToolBox"
+VclBuilder::customMakeWidget GetCustomMakeWidget(const OString& rName)
+{
+ const OString name = rName == "sfxlo-SidebarToolBox" ? "sfxlo-NotebookbarToolBox" : rName;
+ VclBuilder::customMakeWidget pFunction = nullptr;
+ if (sal_Int32 nDelim = name.indexOf('-'); nDelim != -1)
+ {
+ const OString aFunction(OString::Concat("make") + name.subView(nDelim + 1));
+ const OUString sFunction(OStringToOUString(aFunction, RTL_TEXTENCODING_UTF8));
+
+#ifndef DISABLE_DYNLOADING
+ const OUString sModule = SAL_DLLPREFIX
+ + OStringToOUString(name.subView(0, nDelim), RTL_TEXTENCODING_UTF8)
+ + SAL_DLLEXTENSION;
+ ModuleMap::iterator aI = g_aModuleMap.find(sModule);
+ if (aI == g_aModuleMap.end())
+ {
+ std::shared_ptr<NoAutoUnloadModule> pModule;
+#if ENABLE_MERGELIBS
+ if (!g_pMergedLib->is())
+ g_pMergedLib->loadRelative(&thisModule, SVLIBRARY("merged"));
+ if ((pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(
+ g_pMergedLib->getFunctionSymbol(sFunction))))
+ pModule = g_pMergedLib;
+#endif
+ if (!pFunction)
+ {
+ pModule = std::make_shared<NoAutoUnloadModule>();
+ bool ok = pModule->loadRelative(&thisModule, sModule);
+ if (!ok)
+ {
+#ifdef LINUX
+ // in the case of preloading, we don't have eg. the
+ // libcuilo.so, but still need to dlsym the symbols -
+ // which are already in-process
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(dlsym(RTLD_DEFAULT, aFunction.getStr()));
+ ok = !!pFunction;
+ assert(ok && "couldn't even directly dlsym the sFunction (available via preload)");
+ }
+#endif
+ assert(ok && "bad module name in .ui");
+ }
+ else
+ {
+ pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(
+ pModule->getFunctionSymbol(sFunction));
+ }
+ }
+ g_aModuleMap.insert(std::make_pair(sModule, pModule));
+ }
+ else
+ pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(
+ aI->second->getFunctionSymbol(sFunction));
+#elif !HAVE_FEATURE_DESKTOP
+ pFunction = lo_get_custom_widget_func(sFunction.toUtf8().getStr());
+ SAL_WARN_IF(!pFunction, "vcl.builder", "Could not find " << sFunction);
+ assert(pFunction);
+#else
+ pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(
+ osl_getFunctionSymbol((oslModule)RTLD_DEFAULT, sFunction.pData));
+#endif
+ }
+ return pFunction;
+}
+}
+
+VclPtr<vcl::Window> VclBuilder::makeObject(vcl::Window *pParent, const OString &name, const OString &id,
+ stringmap &rMap)
+{
+ bool bIsPlaceHolder = name.isEmpty();
+ bool bVertical = false;
+
+ if (pParent && (pParent->GetType() == WindowType::TABCONTROL ||
+ pParent->GetType() == WindowType::VERTICALTABCONTROL))
+ {
+ bool bTopLevel(name == "GtkDialog" || name == "GtkMessageDialog" ||
+ name == "GtkWindow" || name == "GtkPopover" || name == "GtkAssistant");
+ if (!bTopLevel)
+ {
+ if (pParent->GetType() == WindowType::TABCONTROL)
+ {
+ //We have to add a page
+ //make default pageid == position
+ TabControl *pTabControl = static_cast<TabControl*>(pParent);
+ sal_uInt16 nNewPageCount = pTabControl->GetPageCount()+1;
+ sal_uInt16 nNewPageId = nNewPageCount;
+ pTabControl->InsertPage(nNewPageId, OUString());
+ pTabControl->SetCurPageId(nNewPageId);
+ SAL_WARN_IF(bIsPlaceHolder, "vcl.builder", "we should have no placeholders for tabpages");
+ if (!bIsPlaceHolder)
+ {
+ VclPtrInstance<TabPage> pPage(pTabControl);
+ pPage->Show();
+
+ //Make up a name for it
+ OString sTabPageId = get_by_window(pParent) +
+ "-page" +
+ OString::number(nNewPageCount);
+ m_aChildren.emplace_back(sTabPageId, pPage, false);
+ pPage->SetHelpId(m_sHelpRoot + sTabPageId);
+
+ pParent = pPage;
+
+ pTabControl->SetTabPage(nNewPageId, pPage);
+ }
+ }
+ else
+ {
+ VerticalTabControl *pTabControl = static_cast<VerticalTabControl*>(pParent);
+ SAL_WARN_IF(bIsPlaceHolder, "vcl.builder", "we should have no placeholders for tabpages");
+ if (!bIsPlaceHolder)
+ pParent = pTabControl->GetPageParent();
+ }
+ }
+ }
+
+ if (bIsPlaceHolder || name == "GtkTreeSelection")
+ return nullptr;
+
+ ToolBox *pToolBox = (pParent && pParent->GetType() == WindowType::TOOLBOX) ? static_cast<ToolBox*>(pParent) : nullptr;
+
+ extractButtonImage(id, rMap, name == "GtkRadioButton");
+
+ VclPtr<vcl::Window> xWindow;
+ if (name == "GtkDialog" || name == "GtkAssistant")
+ {
+ // WB_ALLOWMENUBAR because we don't know in advance if we will encounter
+ // a menubar, and menubars need a BorderWindow in the toplevel, and
+ // such border windows need to be in created during the dialog ctor
+ WinBits nBits = WB_MOVEABLE|WB_3DLOOK|WB_ALLOWMENUBAR;
+ if (extractResizable(rMap))
+ nBits |= WB_SIZEABLE;
+ if (extractCloseable(rMap))
+ nBits |= WB_CLOSEABLE;
+ Dialog::InitFlag eInit = !pParent ? Dialog::InitFlag::NoParent : Dialog::InitFlag::Default;
+ if (name == "GtkAssistant")
+ xWindow = VclPtr<vcl::RoadmapWizard>::Create(pParent, nBits, eInit);
+ else
+ xWindow = VclPtr<Dialog>::Create(pParent, nBits, eInit);
+#if HAVE_FEATURE_DESKTOP
+ if (!extractModal(rMap))
+ xWindow->SetType(WindowType::MODELESSDIALOG);
+#endif
+ }
+ else if (name == "GtkMessageDialog")
+ {
+ WinBits nBits = WB_MOVEABLE|WB_3DLOOK|WB_CLOSEABLE;
+ if (extractResizable(rMap))
+ nBits |= WB_SIZEABLE;
+ VclPtr<MessageDialog> xDialog(VclPtr<MessageDialog>::Create(pParent, nBits));
+ m_pParserState->m_aMessageDialogs.push_back(xDialog);
+ xWindow = xDialog;
+#if defined _WIN32
+ xWindow->set_border_width(3);
+#else
+ xWindow->set_border_width(12);
+#endif
+ }
+ else if (name == "GtkBox" || name == "GtkStatusbar")
+ {
+ bVertical = extractOrientation(rMap);
+ if (bVertical)
+ xWindow = VclPtr<VclVBox>::Create(pParent);
+ else
+ xWindow = VclPtr<VclHBox>::Create(pParent);
+ }
+ else if (name == "GtkPaned")
+ {
+ bVertical = extractOrientation(rMap);
+ if (bVertical)
+ xWindow = VclPtr<VclVPaned>::Create(pParent);
+ else
+ xWindow = VclPtr<VclHPaned>::Create(pParent);
+ }
+ else if (name == "GtkHBox")
+ xWindow = VclPtr<VclHBox>::Create(pParent);
+ else if (name == "GtkVBox")
+ xWindow = VclPtr<VclVBox>::Create(pParent);
+ else if (name == "GtkButtonBox")
+ {
+ bVertical = extractOrientation(rMap);
+ if (bVertical)
+ xWindow = VclPtr<VclVButtonBox>::Create(pParent);
+ else
+ xWindow = VclPtr<VclHButtonBox>::Create(pParent);
+ }
+ else if (name == "GtkHButtonBox")
+ xWindow = VclPtr<VclHButtonBox>::Create(pParent);
+ else if (name == "GtkVButtonBox")
+ xWindow = VclPtr<VclVButtonBox>::Create(pParent);
+ else if (name == "GtkGrid")
+ xWindow = VclPtr<VclGrid>::Create(pParent);
+ else if (name == "GtkFrame")
+ xWindow = VclPtr<VclFrame>::Create(pParent);
+ else if (name == "GtkExpander")
+ {
+ VclPtrInstance<VclExpander> pExpander(pParent);
+ m_pParserState->m_aExpanderWidgets.push_back(pExpander);
+ xWindow = pExpander;
+ }
+ else if (name == "GtkButton" || (!m_bLegacy && name == "GtkToggleButton"))
+ {
+ VclPtr<Button> xButton;
+ OUString sMenu = BuilderUtils::extractCustomProperty(rMap);
+ if (sMenu.isEmpty())
+ xButton = extractStockAndBuildPushButton(pParent, rMap, name == "GtkToggleButton");
+ else
+ {
+ assert(m_bLegacy && "use GtkMenuButton");
+ xButton = extractStockAndBuildMenuButton(pParent, rMap);
+ m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
+ }
+ xButton->SetImageAlign(ImageAlign::Left); //default to left
+ setupFromActionName(xButton, rMap, m_xFrame);
+ xWindow = xButton;
+ }
+ else if (name == "GtkMenuButton")
+ {
+ VclPtr<MenuButton> xButton;
+
+ OUString sMenu = extractPopupMenu(rMap);
+ if (!sMenu.isEmpty())
+ m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
+
+ OUString sType = extractWidgetName(rMap);
+ if (sType.isEmpty())
+ {
+ xButton = extractStockAndBuildMenuButton(pParent, rMap);
+ xButton->SetAccessibleRole(css::accessibility::AccessibleRole::BUTTON_MENU);
+ }
+ else
+ {
+ xButton = extractStockAndBuildMenuToggleButton(pParent, rMap);
+ }
+
+ xButton->SetImageAlign(ImageAlign::Left); //default to left
+
+ if (!extractDrawIndicator(rMap))
+ xButton->SetDropDown(PushButtonDropdownStyle::NONE);
+
+ setupFromActionName(xButton, rMap, m_xFrame);
+ xWindow = xButton;
+ }
+ else if (name == "GtkToggleButton" && m_bLegacy)
+ {
+ VclPtr<Button> xButton;
+ OUString sMenu = BuilderUtils::extractCustomProperty(rMap);
+ assert(sMenu.getLength() && "not implemented yet");
+ xButton = extractStockAndBuildMenuToggleButton(pParent, rMap);
+ m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
+ xButton->SetImageAlign(ImageAlign::Left); //default to left
+ setupFromActionName(xButton, rMap, m_xFrame);
+ xWindow = xButton;
+ }
+ else if (name == "GtkRadioButton")
+ {
+ extractGroup(id, rMap);
+ WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK;
+ VclPtr<RadioButton> xButton = VclPtr<RadioButton>::Create(pParent, true, nBits);
+ xButton->SetImageAlign(ImageAlign::Left); //default to left
+ xWindow = xButton;
+ }
+ else if (name == "GtkCheckButton")
+ {
+ WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK;
+ bool bIsTriState = extractInconsistent(rMap);
+ VclPtr<CheckBox> xCheckBox = VclPtr<CheckBox>::Create(pParent, nBits);
+ if (bIsTriState)
+ {
+ xCheckBox->EnableTriState(true);
+ xCheckBox->SetState(TRISTATE_INDET);
+ }
+ xCheckBox->SetImageAlign(ImageAlign::Left); //default to left
+
+ xWindow = xCheckBox;
+ }
+ else if (name == "GtkSpinButton")
+ {
+ OUString sAdjustment = extractAdjustment(rMap);
+
+ WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_3DLOOK|WB_SPIN|WB_REPEAT;
+ if (extractHasFrame(rMap))
+ nBits |= WB_BORDER;
+
+ connectFormattedFormatterAdjustment(id, sAdjustment);
+ VclPtrInstance<FormattedField> xField(pParent, nBits);
+ xField->GetFormatter().SetMinValue(0);
+ xWindow = xField;
+ }
+ else if (name == "GtkLinkButton")
+ xWindow = VclPtr<FixedHyperlink>::Create(pParent, WB_CENTER|WB_VCENTER|WB_3DLOOK|WB_NOLABEL);
+ else if (name == "GtkComboBox" || name == "GtkComboBoxText")
+ {
+ extractModel(id, rMap);
+
+ WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK;
+
+ bool bDropdown = BuilderUtils::extractDropdown(rMap);
+
+ if (bDropdown)
+ nBits |= WB_DROPDOWN;
+
+ if (extractEntry(rMap))
+ {
+ VclPtrInstance<ComboBox> xComboBox(pParent, nBits);
+ xComboBox->EnableAutoSize(true);
+ xWindow = xComboBox;
+ }
+ else
+ {
+ VclPtrInstance<ListBox> xListBox(pParent, nBits|WB_SIMPLEMODE);
+ xListBox->EnableAutoSize(true);
+ xWindow = xListBox;
+ }
+ }
+ else if (name == "VclOptionalBox" || name == "sfxlo-OptionalBox")
+ {
+ // tdf#135495 fallback sfxlo-OptionalBox to VclOptionalBox as a stopgap
+ xWindow = VclPtr<OptionalBox>::Create(pParent);
+ }
+ else if (name == "svtlo-ManagedMenuButton")
+ {
+ // like tdf#135495 keep the name svtlo-ManagedMenuButton even though it's a misnomer
+ // and is not dlsymed from the svt library
+ xWindow = VclPtr<ManagedMenuButton>::Create(pParent, WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_FLATBUTTON);
+ OUString sMenu = BuilderUtils::extractCustomProperty(rMap);
+ if (!sMenu.isEmpty())
+ m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
+ setupFromActionName(static_cast<Button*>(xWindow.get()), rMap, m_xFrame);
+ }
+ else if (name == "sfxlo-PriorityMergedHBox")
+ {
+ // like tdf#135495 above, keep the sfxlo-PriorityMergedHBox even though its not in sfx anymore
+ xWindow = VclPtr<PriorityMergedHBox>::Create(pParent);
+ }
+ else if (name == "sfxlo-PriorityHBox")
+ {
+ // like tdf#135495 above, keep the sfxlo-PriorityMergedHBox even though its not in sfx anymore
+ xWindow = VclPtr<PriorityHBox>::Create(pParent);
+ }
+ else if (name == "sfxlo-DropdownBox")
+ {
+ // like tdf#135495 above, keep the sfxlo-PriorityMergedHBox even though its not in sfx anymore
+ xWindow = VclPtr<DropdownBox>::Create(pParent);
+ }
+ else if (name == "sfxlo-ContextVBox")
+ {
+ // like tdf#135495 above, keep the sfxlo-PriorityMergedHBox even though its not in sfx anymore
+ xWindow = VclPtr<ContextVBox>::Create(pParent);
+ }
+ else if (name == "GtkIconView")
+ {
+ assert(rMap.find(OString("model")) != rMap.end() && "GtkIconView must have a model");
+
+ //window we want to apply the packing props for this GtkIconView to
+ VclPtr<vcl::Window> xWindowForPackingProps;
+ extractModel(id, rMap);
+ WinBits nWinStyle = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK;
+ //IconView manages its own scrolling,
+ vcl::Window *pRealParent = prepareWidgetOwnScrolling(pParent, nWinStyle);
+
+ VclPtr<IconView> xBox = VclPtr<IconView>::Create(pRealParent, nWinStyle);
+ xWindowForPackingProps = xBox;
+
+ xWindow = xBox;
+ xBox->SetNoAutoCurEntry(true);
+ xBox->SetQuickSearch(true);
+
+ if (pRealParent != pParent)
+ cleanupWidgetOwnScrolling(pParent, xWindowForPackingProps, rMap);
+ }
+ else if (name == "GtkTreeView")
+ {
+ if (!m_bLegacy)
+ {
+ assert(rMap.find(OString("model")) != rMap.end() && "GtkTreeView must have a model");
+ }
+
+ //window we want to apply the packing props for this GtkTreeView to
+ VclPtr<vcl::Window> xWindowForPackingProps;
+ //To-Do
+ //a) make SvHeaderTabListBox/SvTabListBox the default target for GtkTreeView
+ //b) remove the non-drop down mode of ListBox and convert
+ // everything over to SvHeaderTabListBox/SvTabListBox
+ extractModel(id, rMap);
+ WinBits nWinStyle = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK;
+ if (m_bLegacy)
+ {
+ OUString sBorder = BuilderUtils::extractCustomProperty(rMap);
+ if (!sBorder.isEmpty())
+ nWinStyle |= WB_BORDER;
+ }
+ else
+ {
+ nWinStyle |= WB_HASBUTTONS | WB_HASBUTTONSATROOT;
+ }
+ //ListBox/SvHeaderTabListBox manages its own scrolling,
+ vcl::Window *pRealParent = prepareWidgetOwnScrolling(pParent, nWinStyle);
+ if (m_bLegacy)
+ {
+ xWindow = VclPtr<ListBox>::Create(pRealParent, nWinStyle | WB_SIMPLEMODE);
+ xWindowForPackingProps = xWindow;
+ }
+ else
+ {
+ VclPtr<SvTabListBox> xBox;
+ bool bHeadersVisible = extractHeadersVisible(rMap);
+ if (bHeadersVisible)
+ {
+ VclPtr<VclVBox> xContainer = VclPtr<VclVBox>::Create(pRealParent);
+ OString containerid(id + "-container");
+ xContainer->SetHelpId(m_sHelpRoot + containerid);
+ m_aChildren.emplace_back(containerid, xContainer, true);
+
+ VclPtrInstance<HeaderBar> xHeader(xContainer, WB_BUTTONSTYLE | WB_BORDER | WB_TABSTOP | WB_3DLOOK);
+ xHeader->set_width_request(0); // let the headerbar width not affect the size request
+ OString headerid(id + "-header");
+ xHeader->SetHelpId(m_sHelpRoot + headerid);
+ m_aChildren.emplace_back(headerid, xHeader, true);
+
+ VclPtr<LclHeaderTabListBox> xHeaderBox = VclPtr<LclHeaderTabListBox>::Create(xContainer, nWinStyle);
+ xHeaderBox->InitHeaderBar(xHeader);
+ xContainer->set_expand(true);
+ xHeader->Show();
+ xContainer->Show();
+ xBox = xHeaderBox;
+ xWindowForPackingProps = xContainer;
+ }
+ else
+ {
+ xBox = VclPtr<LclTabListBox>::Create(pRealParent, nWinStyle);
+ xWindowForPackingProps = xBox;
+ }
+ xWindow = xBox;
+ xBox->SetNoAutoCurEntry(true);
+ xBox->SetQuickSearch(true);
+ xBox->SetSpaceBetweenEntries(3);
+ xBox->SetEntryHeight(16);
+ xBox->SetHighlightRange(); // select over the whole width
+ }
+ if (pRealParent != pParent)
+ cleanupWidgetOwnScrolling(pParent, xWindowForPackingProps, rMap);
+ }
+ else if (name == "GtkTreeViewColumn")
+ {
+ if (!m_bLegacy)
+ {
+ SvHeaderTabListBox* pTreeView = dynamic_cast<SvHeaderTabListBox*>(pParent);
+ if (HeaderBar* pHeaderBar = pTreeView ? pTreeView->GetHeaderBar() : nullptr)
+ {
+ HeaderBarItemBits nBits = HeaderBarItemBits::LEFTIMAGE;
+ if (extractClickable(rMap))
+ nBits |= HeaderBarItemBits::CLICKABLE;
+ if (extractSortIndicator(rMap))
+ nBits |= HeaderBarItemBits::DOWNARROW;
+ float fAlign = extractAlignment(rMap);
+ if (fAlign == 0.0)
+ nBits |= HeaderBarItemBits::LEFT;
+ else if (fAlign == 1.0)
+ nBits |= HeaderBarItemBits::RIGHT;
+ else if (fAlign == 0.5)
+ nBits |= HeaderBarItemBits::CENTER;
+ auto nItemId = pHeaderBar->GetItemCount() + 1;
+ OUString sTitle(extractTitle(rMap));
+ pHeaderBar->InsertItem(nItemId, sTitle, 100, nBits);
+ }
+ }
+ }
+ else if (name == "GtkLabel")
+ {
+ WinBits nWinStyle = WB_CENTER|WB_VCENTER|WB_3DLOOK;
+ extractMnemonicWidget(id, rMap);
+ if (extractSelectable(rMap))
+ xWindow = VclPtr<SelectableFixedText>::Create(pParent, nWinStyle);
+ else
+ xWindow = VclPtr<FixedText>::Create(pParent, nWinStyle);
+ }
+ else if (name == "GtkImage")
+ {
+ VclPtr<FixedImage> xFixedImage = VclPtr<FixedImage>::Create(pParent, WB_CENTER|WB_VCENTER|WB_3DLOOK|WB_SCALE);
+ OUString sIconName = extractIconName(rMap);
+ if (!sIconName.isEmpty())
+ xFixedImage->SetImage(FixedImage::loadThemeImage(sIconName));
+ m_pParserState->m_aImageSizeMap[id] = getImageSize(rMap);
+ xWindow = xFixedImage;
+ //such parentless GtkImages are temps used to set icons on buttons
+ //default them to hidden to stop e.g. insert->index entry flicking temp
+ //full screen windows
+ if (!pParent)
+ {
+ rMap["visible"] = "false";
+ }
+ }
+ else if (name == "GtkSeparator")
+ {
+ bVertical = extractOrientation(rMap);
+ xWindow = VclPtr<FixedLine>::Create(pParent, bVertical ? WB_VERT : WB_HORZ);
+ }
+ else if (name == "GtkScrollbar")
+ {
+ extractAdjustmentToMap(id, rMap, m_pParserState->m_aScrollAdjustmentMaps);
+ bVertical = extractOrientation(rMap);
+ xWindow = VclPtr<ScrollBar>::Create(pParent, bVertical ? WB_VERT : WB_HORZ);
+ }
+ else if (name == "GtkProgressBar")
+ {
+ extractAdjustmentToMap(id, rMap, m_pParserState->m_aScrollAdjustmentMaps);
+ bVertical = extractOrientation(rMap);
+ xWindow = VclPtr<ProgressBar>::Create(pParent, bVertical ? WB_VERT : WB_HORZ);
+ }
+ else if (name == "GtkScrolledWindow")
+ {
+ xWindow = VclPtr<VclScrolledWindow>::Create(pParent);
+ }
+ else if (name == "GtkViewport")
+ {
+ xWindow = VclPtr<VclViewport>::Create(pParent);
+ }
+ else if (name == "GtkEventBox")
+ {
+ xWindow = VclPtr<VclEventBox>::Create(pParent);
+ }
+ else if (name == "GtkEntry")
+ {
+ WinBits nWinStyle = WB_LEFT|WB_VCENTER|WB_3DLOOK;
+ if (extractHasFrame(rMap))
+ nWinStyle |= WB_BORDER;
+ xWindow = VclPtr<Edit>::Create(pParent, nWinStyle);
+ BuilderUtils::ensureDefaultWidthChars(rMap);
+ }
+ else if (name == "GtkNotebook")
+ {
+ if (!extractVerticalTabPos(rMap))
+ xWindow = VclPtr<TabControl>::Create(pParent, WB_STDTABCONTROL|WB_3DLOOK);
+ else
+ xWindow = VclPtr<VerticalTabControl>::Create(pParent);
+ }
+ else if (name == "GtkDrawingArea")
+ {
+ xWindow = VclPtr<VclDrawingArea>::Create(pParent, WB_TABSTOP);
+ }
+ else if (name == "GtkTextView")
+ {
+ extractBuffer(id, rMap);
+
+ WinBits nWinStyle = WB_CLIPCHILDREN|WB_LEFT;
+ //VclMultiLineEdit manages its own scrolling,
+ vcl::Window *pRealParent = prepareWidgetOwnScrolling(pParent, nWinStyle);
+ xWindow = VclPtr<VclMultiLineEdit>::Create(pRealParent, nWinStyle);
+ if (pRealParent != pParent)
+ cleanupWidgetOwnScrolling(pParent, xWindow, rMap);
+ }
+ else if (name == "GtkSpinner")
+ {
+ xWindow = VclPtr<Throbber>::Create(pParent, WB_3DLOOK);
+ }
+ else if (name == "GtkScale")
+ {
+ extractAdjustmentToMap(id, rMap, m_pParserState->m_aSliderAdjustmentMaps);
+ bool bDrawValue = extractDrawValue(rMap);
+ if (bDrawValue)
+ {
+ OUString sValuePos = extractValuePos(rMap);
+ (void)sValuePos;
+ }
+ bVertical = extractOrientation(rMap);
+
+ WinBits nWinStyle = bVertical ? WB_VERT : WB_HORZ;
+
+ xWindow = VclPtr<Slider>::Create(pParent, nWinStyle);
+ }
+ else if (name == "GtkToolbar")
+ {
+ xWindow = VclPtr<ToolBox>::Create(pParent, WB_3DLOOK | WB_TABSTOP);
+ }
+ else if(name == "NotebookBarAddonsToolMergePoint")
+ {
+ customMakeWidget pFunction = GetCustomMakeWidget("sfxlo-NotebookbarToolBox");
+ if(pFunction != nullptr)
+ NotebookBarAddonsMerger::MergeNotebookBarAddons(pParent, pFunction, m_xFrame, *m_pNotebookBarAddonsItem, rMap);
+ return nullptr;
+ }
+ else if (name == "GtkToolButton" || name == "GtkMenuToolButton" ||
+ name == "GtkToggleToolButton" || name == "GtkRadioToolButton" || name == "GtkToolItem")
+ {
+ if (pToolBox)
+ {
+ OUString aCommand(extractActionName(rMap));
+
+ ToolBoxItemId nItemId(0);
+ ToolBoxItemBits nBits = ToolBoxItemBits::NONE;
+ if (name == "GtkMenuToolButton")
+ nBits |= ToolBoxItemBits::DROPDOWN;
+ else if (name == "GtkToggleToolButton")
+ nBits |= ToolBoxItemBits::AUTOCHECK | ToolBoxItemBits::CHECKABLE;
+ else if (name == "GtkRadioToolButton")
+ nBits |= ToolBoxItemBits::AUTOCHECK | ToolBoxItemBits::RADIOCHECK;
+
+ if (!aCommand.isEmpty() && m_xFrame.is())
+ {
+ pToolBox->InsertItem(aCommand, m_xFrame, nBits, extractSizeRequest(rMap));
+ nItemId = pToolBox->GetItemId(aCommand);
+ }
+ else
+ {
+ nItemId = ToolBoxItemId(pToolBox->GetItemCount() + 1);
+ //TODO: ImplToolItems::size_type -> sal_uInt16!
+ pToolBox->InsertItem(nItemId, extractLabel(rMap), nBits);
+ if (aCommand.isEmpty() && !m_bLegacy)
+ aCommand = OUString::fromUtf8(id);
+ pToolBox->SetItemCommand(nItemId, aCommand);
+ }
+
+ pToolBox->SetHelpId(nItemId, m_sHelpRoot + id);
+ OUString sTooltip(extractTooltipText(rMap));
+ if (!sTooltip.isEmpty())
+ pToolBox->SetQuickHelpText(nItemId, sTooltip);
+
+ OUString sIconName(extractIconName(rMap));
+ if (!sIconName.isEmpty())
+ pToolBox->SetItemImage(nItemId, FixedImage::loadThemeImage(sIconName));
+
+ if (!extractVisible(rMap))
+ pToolBox->HideItem(nItemId);
+
+ m_pParserState->m_nLastToolbarId = nItemId;
+
+ return nullptr; // no widget to be created
+ }
+ }
+ else if (name == "GtkSeparatorToolItem")
+ {
+ if (pToolBox)
+ {
+ pToolBox->InsertSeparator();
+ return nullptr; // no widget to be created
+ }
+ }
+ else if (name == "GtkWindow")
+ {
+ WinBits nBits = extractDeferredBits(rMap);
+ if (nBits & WB_DOCKABLE)
+ xWindow = VclPtr<DockingWindow>::Create(pParent, nBits|WB_MOVEABLE);
+ else
+ xWindow = VclPtr<FloatingWindow>::Create(pParent, nBits|WB_MOVEABLE);
+ }
+ else if (name == "GtkPopover")
+ {
+ WinBits nBits = extractDeferredBits(rMap);
+ xWindow = VclPtr<DockingWindow>::Create(pParent, nBits|WB_DOCKABLE|WB_MOVEABLE);
+ }
+ else if (name == "GtkCalendar")
+ {
+ WinBits nBits = extractDeferredBits(rMap);
+ xWindow = VclPtr<Calendar>::Create(pParent, nBits);
+ }
+ else
+ {
+ if (customMakeWidget pFunction = GetCustomMakeWidget(name))
+ {
+ pFunction(xWindow, pParent, rMap);
+ if (xWindow->GetType() == WindowType::PUSHBUTTON)
+ setupFromActionName(static_cast<Button*>(xWindow.get()), rMap, m_xFrame);
+ else if (xWindow->GetType() == WindowType::MENUBUTTON)
+ {
+ OUString sMenu = BuilderUtils::extractCustomProperty(rMap);
+ if (!sMenu.isEmpty())
+ m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
+ setupFromActionName(static_cast<Button*>(xWindow.get()), rMap, m_xFrame);
+ }
+ }
+ }
+
+ SAL_INFO_IF(!xWindow, "vcl.builder", "probably need to implement " << name << " or add a make" << name << " function");
+ if (xWindow)
+ {
+ // child windows of disabled windows are made disabled by vcl by default, we don't want that
+ WindowImpl *pWindowImpl = xWindow->ImplGetWindowImpl();
+ pWindowImpl->mbDisabled = false;
+
+ xWindow->SetHelpId(m_sHelpRoot + id);
+ SAL_INFO("vcl.builder", "for name '" << name << "' and id '" << id <<
+ "', created " << xWindow.get() << " child of " <<
+ pParent << "(" << xWindow->ImplGetWindowImpl()->mpParent.get() << "/" <<
+ xWindow->ImplGetWindowImpl()->mpRealParent.get() << "/" <<
+ xWindow->ImplGetWindowImpl()->mpBorderWindow.get() << ") with helpid " <<
+ xWindow->GetHelpId());
+ m_aChildren.emplace_back(id, xWindow, bVertical);
+
+ // if the parent was a toolbox set it as an itemwindow for the latest itemid
+ if (pToolBox)
+ {
+ Size aSize(xWindow->GetSizePixel());
+ aSize.setHeight(xWindow->get_preferred_size().Height());
+ xWindow->SetSizePixel(aSize);
+ pToolBox->SetItemWindow(m_pParserState->m_nLastToolbarId, xWindow);
+ pToolBox->SetItemExpand(m_pParserState->m_nLastToolbarId, true);
+ }
+ }
+ return xWindow;
+}
+
+namespace
+{
+ //return true for window types which exist in vcl but are not themselves
+ //represented in the .ui format, i.e. only their children exist.
+ bool isConsideredGtkPseudo(vcl::Window const *pWindow)
+ {
+ return pWindow->GetType() == WindowType::TABPAGE;
+ }
+}
+
+//Any properties from .ui load we couldn't set because of potential virtual methods
+//during ctor are applied here
+void VclBuilder::setDeferredProperties()
+{
+ if (!m_bToplevelHasDeferredProperties)
+ return;
+ stringmap aDeferredProperties;
+ aDeferredProperties.swap(m_aDeferredProperties);
+ m_bToplevelHasDeferredProperties = false;
+ BuilderUtils::set_properties(m_pParent, aDeferredProperties);
+}
+
+namespace BuilderUtils
+{
+ void set_properties(vcl::Window *pWindow, const VclBuilder::stringmap &rProps)
+ {
+ for (auto const& prop : rProps)
+ {
+ const OString &rKey = prop.first;
+ const OUString &rValue = prop.second;
+ pWindow->set_property(rKey, rValue);
+ }
+ }
+
+ OUString convertMnemonicMarkup(std::u16string_view rIn)
+ {
+ OUStringBuffer aRet(rIn);
+ for (sal_Int32 nI = 0; nI < aRet.getLength(); ++nI)
+ {
+ if (aRet[nI] == '_' && nI+1 < aRet.getLength())
+ {
+ if (aRet[nI+1] != '_')
+ aRet[nI] = MNEMONIC_CHAR;
+ else
+ aRet.remove(nI, 1);
+ ++nI;
+ }
+ }
+ return aRet.makeStringAndClear();
+ }
+
+ OUString extractCustomProperty(VclBuilder::stringmap &rMap)
+ {
+ OUString sCustomProperty;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("customproperty"));
+ if (aFind != rMap.end())
+ {
+ sCustomProperty = aFind->second;
+ rMap.erase(aFind);
+ }
+ return sCustomProperty;
+ }
+
+ void ensureDefaultWidthChars(VclBuilder::stringmap &rMap)
+ {
+ OString sWidthChars("width-chars");
+ VclBuilder::stringmap::iterator aFind = rMap.find(sWidthChars);
+ if (aFind == rMap.end())
+ rMap[sWidthChars] = "20";
+ }
+
+ bool extractDropdown(VclBuilder::stringmap &rMap)
+ {
+ bool bDropdown = true;
+ VclBuilder::stringmap::iterator aFind = rMap.find(OString("dropdown"));
+ if (aFind != rMap.end())
+ {
+ bDropdown = toBool(aFind->second);
+ rMap.erase(aFind);
+ }
+ return bDropdown;
+ }
+
+ void reorderWithinParent(vcl::Window &rWindow, sal_uInt16 nNewPosition)
+ {
+ WindowImpl *pWindowImpl = rWindow.ImplGetWindowImpl();
+ if (pWindowImpl->mpParent != pWindowImpl->mpRealParent)
+ {
+ assert(pWindowImpl->mpBorderWindow == pWindowImpl->mpParent);
+ assert(pWindowImpl->mpBorderWindow->ImplGetWindowImpl()->mpParent == pWindowImpl->mpRealParent);
+ reorderWithinParent(*pWindowImpl->mpBorderWindow, nNewPosition);
+ return;
+ }
+ rWindow.reorderWithinParent(nNewPosition);
+ }
+
+ void reorderWithinParent(std::vector<vcl::Window*>& rChilds, bool bIsButtonBox)
+ {
+ for (size_t i = 0; i < rChilds.size(); ++i)
+ {
+ reorderWithinParent(*rChilds[i], i);
+
+ if (!bIsButtonBox)
+ continue;
+
+ //The first member of the group for legacy code needs WB_GROUP set and the
+ //others not
+ WinBits nBits = rChilds[i]->GetStyle();
+ nBits &= ~WB_GROUP;
+ if (i == 0)
+ nBits |= WB_GROUP;
+ rChilds[i]->SetStyle(nBits);
+ }
+ }
+
+ sal_Int16 getRoleFromName(const OString& roleName)
+ {
+ using namespace com::sun::star::accessibility;
+
+ static const std::unordered_map<OString, sal_Int16> aAtkRoleToAccessibleRole = {
+ /* This is in atkobject.h's AtkRole order */
+ { "invalid", AccessibleRole::UNKNOWN },
+ { "accelerator label", AccessibleRole::UNKNOWN },
+ { "alert", AccessibleRole::ALERT },
+ { "animation", AccessibleRole::UNKNOWN },
+ { "arrow", AccessibleRole::UNKNOWN },
+ { "calendar", AccessibleRole::UNKNOWN },
+ { "canvas", AccessibleRole::CANVAS },
+ { "check box", AccessibleRole::CHECK_BOX },
+ { "check menu item", AccessibleRole::CHECK_MENU_ITEM },
+ { "color chooser", AccessibleRole::COLOR_CHOOSER },
+ { "column header", AccessibleRole::COLUMN_HEADER },
+ { "combo box", AccessibleRole::COMBO_BOX },
+ { "date editor", AccessibleRole::DATE_EDITOR },
+ { "desktop icon", AccessibleRole::DESKTOP_ICON },
+ { "desktop frame", AccessibleRole::DESKTOP_PANE }, // ?
+ { "dial", AccessibleRole::UNKNOWN },
+ { "dialog", AccessibleRole::DIALOG },
+ { "directory pane", AccessibleRole::DIRECTORY_PANE },
+ { "drawing area", AccessibleRole::UNKNOWN },
+ { "file chooser", AccessibleRole::FILE_CHOOSER },
+ { "filler", AccessibleRole::FILLER },
+ { "font chooser", AccessibleRole::FONT_CHOOSER },
+ { "frame", AccessibleRole::FRAME },
+ { "glass pane", AccessibleRole::GLASS_PANE },
+ { "html container", AccessibleRole::UNKNOWN },
+ { "icon", AccessibleRole::ICON },
+ { "image", AccessibleRole::GRAPHIC },
+ { "internal frame", AccessibleRole::INTERNAL_FRAME },
+ { "label", AccessibleRole::LABEL },
+ { "layered pane", AccessibleRole::LAYERED_PANE },
+ { "list", AccessibleRole::LIST },
+ { "list item", AccessibleRole::LIST_ITEM },
+ { "menu", AccessibleRole::MENU },
+ { "menu bar", AccessibleRole::MENU_BAR },
+ { "menu item", AccessibleRole::MENU_ITEM },
+ { "option pane", AccessibleRole::OPTION_PANE },
+ { "page tab", AccessibleRole::PAGE_TAB },
+ { "page tab list", AccessibleRole::PAGE_TAB_LIST },
+ { "panel", AccessibleRole::PANEL }, // or SHAPE or TEXT_FRAME ?
+ { "password text", AccessibleRole::PASSWORD_TEXT },
+ { "popup menu", AccessibleRole::POPUP_MENU },
+ { "progress bar", AccessibleRole::PROGRESS_BAR },
+ { "push button", AccessibleRole::PUSH_BUTTON }, // or BUTTON_DROPDOWN or BUTTON_MENU
+ { "radio button", AccessibleRole::RADIO_BUTTON },
+ { "radio menu item", AccessibleRole::RADIO_MENU_ITEM },
+ { "root pane", AccessibleRole::ROOT_PANE },
+ { "row header", AccessibleRole::ROW_HEADER },
+ { "scroll bar", AccessibleRole::SCROLL_BAR },
+ { "scroll pane", AccessibleRole::SCROLL_PANE },
+ { "separator", AccessibleRole::SEPARATOR },
+ { "slider", AccessibleRole::SLIDER },
+ { "split pane", AccessibleRole::SPLIT_PANE },
+ { "spin button", AccessibleRole::SPIN_BOX }, // ?
+ { "statusbar", AccessibleRole::STATUS_BAR },
+ { "table", AccessibleRole::TABLE },
+ { "table cell", AccessibleRole::TABLE_CELL },
+ { "table column header", AccessibleRole::COLUMN_HEADER }, // approximate
+ { "table row header", AccessibleRole::ROW_HEADER }, // approximate
+ { "tear off menu item", AccessibleRole::UNKNOWN },
+ { "terminal", AccessibleRole::UNKNOWN },
+ { "text", AccessibleRole::TEXT },
+ { "toggle button", AccessibleRole::TOGGLE_BUTTON },
+ { "tool bar", AccessibleRole::TOOL_BAR },
+ { "tool tip", AccessibleRole::TOOL_TIP },
+ { "tree", AccessibleRole::TREE },
+ { "tree table", AccessibleRole::TREE_TABLE },
+ { "unknown", AccessibleRole::UNKNOWN },
+ { "viewport", AccessibleRole::VIEW_PORT },
+ { "window", AccessibleRole::WINDOW },
+ { "header", AccessibleRole::HEADER },
+ { "footer", AccessibleRole::FOOTER },
+ { "paragraph", AccessibleRole::PARAGRAPH },
+ { "ruler", AccessibleRole::RULER },
+ { "application", AccessibleRole::UNKNOWN },
+ { "autocomplete", AccessibleRole::UNKNOWN },
+ { "edit bar", AccessibleRole::EDIT_BAR },
+ { "embedded", AccessibleRole::EMBEDDED_OBJECT },
+ { "entry", AccessibleRole::UNKNOWN },
+ { "chart", AccessibleRole::CHART },
+ { "caption", AccessibleRole::CAPTION },
+ { "document frame", AccessibleRole::DOCUMENT },
+ { "heading", AccessibleRole::HEADING },
+ { "page", AccessibleRole::PAGE },
+ { "section", AccessibleRole::SECTION },
+ { "redundant object", AccessibleRole::UNKNOWN },
+ { "form", AccessibleRole::FORM },
+ { "link", AccessibleRole::HYPER_LINK },
+ { "input method window", AccessibleRole::UNKNOWN },
+ { "table row", AccessibleRole::UNKNOWN },
+ { "tree item", AccessibleRole::TREE_ITEM },
+ { "document spreadsheet", AccessibleRole::DOCUMENT_SPREADSHEET },
+ { "document presentation", AccessibleRole::DOCUMENT_PRESENTATION },
+ { "document text", AccessibleRole::DOCUMENT_TEXT },
+ { "document web", AccessibleRole::DOCUMENT }, // approximate
+ { "document email", AccessibleRole::DOCUMENT }, // approximate
+ { "comment", AccessibleRole::COMMENT }, // or NOTE or END_NOTE or FOOTNOTE or SCROLL_PANE
+ { "list box", AccessibleRole::UNKNOWN },
+ { "grouping", AccessibleRole::GROUP_BOX },
+ { "image map", AccessibleRole::IMAGE_MAP },
+ { "notification", AccessibleRole::UNKNOWN },
+ { "info bar", AccessibleRole::UNKNOWN },
+ { "level bar", AccessibleRole::UNKNOWN },
+ { "title bar", AccessibleRole::UNKNOWN },
+ { "block quote", AccessibleRole::UNKNOWN },
+ { "audio", AccessibleRole::UNKNOWN },
+ { "video", AccessibleRole::UNKNOWN },
+ { "definition", AccessibleRole::UNKNOWN },
+ { "article", AccessibleRole::UNKNOWN },
+ { "landmark", AccessibleRole::UNKNOWN },
+ { "log", AccessibleRole::UNKNOWN },
+ { "marquee", AccessibleRole::UNKNOWN },
+ { "math", AccessibleRole::UNKNOWN },
+ { "rating", AccessibleRole::UNKNOWN },
+ { "timer", AccessibleRole::UNKNOWN },
+ { "description list", AccessibleRole::UNKNOWN },
+ { "description term", AccessibleRole::UNKNOWN },
+ { "description value", AccessibleRole::UNKNOWN },
+ { "static", AccessibleRole::STATIC },
+ { "math fraction", AccessibleRole::UNKNOWN },
+ { "math root", AccessibleRole::UNKNOWN },
+ { "subscript", AccessibleRole::UNKNOWN },
+ { "superscript", AccessibleRole::UNKNOWN },
+ { "footnote", AccessibleRole::FOOTNOTE },
+ };
+
+ auto it = aAtkRoleToAccessibleRole.find(roleName);
+ if (it == aAtkRoleToAccessibleRole.end())
+ return AccessibleRole::UNKNOWN;
+ return it->second;
+ }
+}
+
+VclPtr<vcl::Window> VclBuilder::insertObject(vcl::Window *pParent, const OString &rClass,
+ const OString &rID, stringmap &rProps, stringmap &rPango, stringmap &rAtk)
+{
+ VclPtr<vcl::Window> pCurrentChild;
+
+ if (m_pParent && !isConsideredGtkPseudo(m_pParent) && !m_sID.isEmpty() && rID == m_sID)
+ {
+ pCurrentChild = m_pParent;
+
+ //toplevels default to resizable and apparently you can't change them
+ //afterwards, so we need to wait until now before we can truly
+ //initialize the dialog.
+ if (pParent && pParent->IsSystemWindow())
+ {
+ SystemWindow *pSysWin = static_cast<SystemWindow*>(pCurrentChild.get());
+ pSysWin->doDeferredInit(extractDeferredBits(rProps));
+ m_bToplevelHasDeferredInit = false;
+ }
+ else if (pParent && pParent->IsDockingWindow())
+ {
+ DockingWindow *pDockWin = static_cast<DockingWindow*>(pCurrentChild.get());
+ pDockWin->doDeferredInit(extractDeferredBits(rProps));
+ m_bToplevelHasDeferredInit = false;
+ }
+
+ if (pCurrentChild->GetHelpId().isEmpty())
+ {
+ pCurrentChild->SetHelpId(m_sHelpRoot + m_sID);
+ SAL_INFO("vcl.builder", "for toplevel dialog " << this << " " <<
+ rID << ", set helpid " << pCurrentChild->GetHelpId());
+ }
+ m_bToplevelParentFound = true;
+ }
+ else
+ {
+ //if we're being inserting under a toplevel dialog whose init is
+ //deferred due to waiting to encounter it in this .ui, and it hasn't
+ //been seen yet, then make unattached widgets parent-less toplevels
+ if (pParent == m_pParent.get() && m_bToplevelHasDeferredInit)
+ pParent = nullptr;
+ pCurrentChild = makeObject(pParent, rClass, rID, rProps);
+ }
+
+ if (pCurrentChild)
+ {
+ pCurrentChild->set_id(OStringToOUString(rID, RTL_TEXTENCODING_UTF8));
+ if (pCurrentChild == m_pParent.get() && m_bToplevelHasDeferredProperties)
+ m_aDeferredProperties = rProps;
+ else
+ BuilderUtils::set_properties(pCurrentChild, rProps);
+
+ // tdf#119827 handle size before scale so we can trivially
+ // scale on the current font size whether size is present
+ // or not.
+ VclBuilder::stringmap::iterator aSize = rPango.find(OString("size"));
+ if (aSize != rPango.end())
+ {
+ pCurrentChild->set_font_attribute(aSize->first, aSize->second);
+ rPango.erase(aSize);
+ }
+ for (auto const& elem : rPango)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+ pCurrentChild->set_font_attribute(rKey, rValue);
+ }
+
+ m_pParserState->m_aAtkInfo[pCurrentChild] = rAtk;
+ }
+
+ rProps.clear();
+ rPango.clear();
+ rAtk.clear();
+
+ if (!pCurrentChild)
+ {
+ bool bToolbarParent = (pParent && pParent->GetType() == WindowType::TOOLBOX);
+ pCurrentChild = (m_aChildren.empty() || bToolbarParent) ? pParent : m_aChildren.back().m_pWindow.get();
+ }
+ return pCurrentChild;
+}
+
+void VclBuilder::handleTabChild(vcl::Window *pParent, xmlreader::XmlReader &reader)
+{
+ TabControl *pTabControl = pParent && pParent->GetType() == WindowType::TABCONTROL ?
+ static_cast<TabControl*>(pParent) : nullptr;
+
+ std::vector<OString> sIDs;
+
+ int nLevel = 1;
+ stringmap aProperties;
+ stringmap aAtkProperties;
+ std::vector<vcl::EnumContext::Context> context;
+
+ while(true)
+ {
+ xmlreader::Span name;
+ int nsId;
+
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ ++nLevel;
+ if (name == "object")
+ {
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "id")
+ {
+ name = reader.getAttributeValue(false);
+ OString sID(name.begin, name.length);
+ sal_Int32 nDelim = sID.indexOf(':');
+ if (nDelim != -1)
+ {
+ OString sPattern = sID.copy(nDelim+1);
+ aProperties[OString("customproperty")] = OUString::fromUtf8(sPattern);
+ sID = sID.copy(0, nDelim);
+ }
+ sIDs.push_back(sID);
+ }
+ }
+ }
+ else if (name == "style")
+ {
+ int nPriority = 0;
+ context = handleStyle(reader, nPriority);
+ --nLevel;
+ }
+ else if (name == "property")
+ collectProperty(reader, aProperties);
+ else if (pTabControl && name == "child")
+ {
+ // just to collect the atk properties (if any) for the label
+ handleChild(nullptr, &aAtkProperties, reader);
+ --nLevel;
+ }
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ --nLevel;
+
+ if (!nLevel)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+ }
+
+ if (!pParent)
+ return;
+
+ VerticalTabControl *pVerticalTabControl = pParent->GetType() == WindowType::VERTICALTABCONTROL ?
+ static_cast<VerticalTabControl*>(pParent) : nullptr;
+ assert(pTabControl || pVerticalTabControl);
+ VclBuilder::stringmap::iterator aFind = aProperties.find(OString("label"));
+ if (aFind != aProperties.end())
+ {
+ OUString sTooltip(extractTooltipText(aProperties));
+ if (pTabControl)
+ {
+ sal_uInt16 nPageId = pTabControl->GetCurPageId();
+ pTabControl->SetPageText(nPageId, aFind->second);
+ pTabControl->SetPageName(nPageId, sIDs.back());
+ pTabControl->SetHelpText(nPageId, sTooltip);
+ if (!context.empty())
+ {
+ TabPage* pPage = pTabControl->GetTabPage(nPageId);
+ pPage->SetContext(std::move(context));
+ }
+
+ for (auto const& prop : aAtkProperties)
+ {
+ const OString &rKey = prop.first;
+ const OUString &rValue = prop.second;
+
+ if (rKey == "AtkObject::accessible-name")
+ pTabControl->SetAccessibleName(nPageId, rValue);
+ else if (rKey == "AtkObject::accessible-description")
+ pTabControl->SetAccessibleDescription(nPageId, rValue);
+ else
+ SAL_INFO("vcl.builder", "unhandled atk property: " << rKey);
+ }
+
+ }
+ else
+ {
+ OUString sLabel(BuilderUtils::convertMnemonicMarkup(aFind->second));
+ OUString sIconName(extractIconName(aProperties));
+ pVerticalTabControl->InsertPage(sIDs.front(), sLabel, FixedImage::loadThemeImage(sIconName), sTooltip,
+ pVerticalTabControl->GetPageParent()->GetWindow(GetWindowType::LastChild));
+ }
+ }
+ else
+ {
+ if (pTabControl)
+ pTabControl->RemovePage(pTabControl->GetCurPageId());
+ }
+}
+
+//so that tabbing between controls goes in a visually sensible sequence
+//we sort these into a best-tab-order sequence
+bool VclBuilder::sortIntoBestTabTraversalOrder::operator()(const vcl::Window *pA, const vcl::Window *pB) const
+{
+ //sort child order within parent list by grid position
+ sal_Int32 nTopA = pA->get_grid_top_attach();
+ sal_Int32 nTopB = pB->get_grid_top_attach();
+ if (nTopA < nTopB)
+ return true;
+ if (nTopA > nTopB)
+ return false;
+ sal_Int32 nLeftA = pA->get_grid_left_attach();
+ sal_Int32 nLeftB = pB->get_grid_left_attach();
+ if (nLeftA < nLeftB)
+ return true;
+ if (nLeftA > nLeftB)
+ return false;
+ //sort into two groups of pack start and pack end
+ VclPackType ePackA = pA->get_pack_type();
+ VclPackType ePackB = pB->get_pack_type();
+ if (ePackA < ePackB)
+ return true;
+ if (ePackA > ePackB)
+ return false;
+ bool bVerticalContainer = m_pBuilder->get_window_packing_data(pA->GetParent()).m_bVerticalOrient;
+ bool bPackA = pA->get_secondary();
+ bool bPackB = pB->get_secondary();
+ if (!bVerticalContainer)
+ {
+ //for horizontal boxes group secondaries before primaries
+ if (bPackA > bPackB)
+ return true;
+ if (bPackA < bPackB)
+ return false;
+ }
+ else
+ {
+ //for vertical boxes group secondaries after primaries
+ if (bPackA < bPackB)
+ return true;
+ if (bPackA > bPackB)
+ return false;
+ }
+ //honour relative box positions with pack group, (numerical order is reversed
+ //for VclPackType::End, they are packed from the end back, but here we need
+ //them in visual layout order so that tabbing works as expected)
+ sal_Int32 nPackA = m_pBuilder->get_window_packing_data(pA).m_nPosition;
+ sal_Int32 nPackB = m_pBuilder->get_window_packing_data(pB).m_nPosition;
+ if (nPackA < nPackB)
+ return ePackA == VclPackType::Start;
+ if (nPackA > nPackB)
+ return ePackA != VclPackType::Start;
+ //sort labels of Frames before body
+ if (pA->GetParent() == pB->GetParent())
+ {
+ const VclFrame *pFrameParent = dynamic_cast<const VclFrame*>(pA->GetParent());
+ if (pFrameParent)
+ {
+ const vcl::Window *pLabel = pFrameParent->get_label_widget();
+ int nFramePosA = (pA == pLabel) ? 0 : 1;
+ int nFramePosB = (pB == pLabel) ? 0 : 1;
+ return nFramePosA < nFramePosB;
+ }
+ }
+ return false;
+}
+
+void VclBuilder::handleChild(vcl::Window *pParent, stringmap* pAtkProps, xmlreader::XmlReader &reader)
+{
+ vcl::Window *pCurrentChild = nullptr;
+
+ xmlreader::Span name;
+ int nsId;
+ OString sType, sInternalChild;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "type")
+ {
+ name = reader.getAttributeValue(false);
+ sType = OString(name.begin, name.length);
+ }
+ else if (name == "internal-child")
+ {
+ name = reader.getAttributeValue(false);
+ sInternalChild = OString(name.begin, name.length);
+ }
+ }
+
+ if (sType == "tab")
+ {
+ handleTabChild(pParent, reader);
+ return;
+ }
+
+ int nLevel = 1;
+ while(true)
+ {
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ if (name == "object" || name == "placeholder")
+ {
+ pCurrentChild = handleObject(pParent, pAtkProps, reader).get();
+
+ bool bObjectInserted = pCurrentChild && pParent != pCurrentChild;
+
+ if (bObjectInserted)
+ {
+ //Internal-children default in glade to not having their visible bits set
+ //even though they are visible (generally anyway)
+ if (!sInternalChild.isEmpty())
+ pCurrentChild->Show();
+
+ //Select the first page if it's a notebook
+ if (pCurrentChild->GetType() == WindowType::TABCONTROL)
+ {
+ TabControl *pTabControl = static_cast<TabControl*>(pCurrentChild);
+ pTabControl->SetCurPageId(pTabControl->GetPageId(0));
+
+ //To-Do add reorder capability to the TabControl
+ }
+ else
+ {
+ // We want to sort labels before contents of frames
+ // for keyboard traversal, especially if there
+ // are multiple widgets using the same mnemonic
+ if (sType == "label")
+ {
+ if (VclFrame *pFrameParent = dynamic_cast<VclFrame*>(pParent))
+ pFrameParent->designate_label(pCurrentChild);
+ }
+ if (sInternalChild.startsWith("vbox") || sInternalChild.startsWith("messagedialog-vbox"))
+ {
+ if (Dialog *pBoxParent = dynamic_cast<Dialog*>(pParent))
+ pBoxParent->set_content_area(static_cast<VclBox*>(pCurrentChild)); // FIXME-VCLPTR
+ }
+ else if (sInternalChild.startsWith("action_area") || sInternalChild.startsWith("messagedialog-action_area"))
+ {
+ vcl::Window *pContentArea = pCurrentChild->GetParent();
+ if (Dialog *pBoxParent = dynamic_cast<Dialog*>(pContentArea ? pContentArea->GetParent() : nullptr))
+ {
+ pBoxParent->set_action_area(static_cast<VclButtonBox*>(pCurrentChild)); // FIXME-VCLPTR
+ }
+ }
+
+ bool bIsButtonBox = dynamic_cast<VclButtonBox*>(pCurrentChild) != nullptr;
+
+ //To-Do make reorder a virtual in Window, move this foo
+ //there and see above
+ std::vector<vcl::Window*> aChilds;
+ for (vcl::Window* pChild = pCurrentChild->GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (bIsButtonBox)
+ {
+ if (PushButton* pPushButton = dynamic_cast<PushButton*>(pChild))
+ pPushButton->setAction(true);
+ }
+
+ aChilds.push_back(pChild);
+ }
+
+ //sort child order within parent so that tabbing
+ //between controls goes in a visually sensible sequence
+ std::stable_sort(aChilds.begin(), aChilds.end(), sortIntoBestTabTraversalOrder(this));
+ BuilderUtils::reorderWithinParent(aChilds, bIsButtonBox);
+ }
+ }
+ }
+ else if (name == "packing")
+ {
+ handlePacking(pCurrentChild, pParent, reader);
+ }
+ else if (name == "interface")
+ {
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "domain")
+ {
+ name = reader.getAttributeValue(false);
+ sType = OString(name.begin, name.length);
+ m_pParserState->m_aResLocale = Translate::Create(sType);
+ }
+ }
+ ++nLevel;
+ }
+ else
+ ++nLevel;
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ --nLevel;
+
+ if (!nLevel)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+ }
+}
+
+void VclBuilder::collectPangoAttribute(xmlreader::XmlReader &reader, stringmap &rMap)
+{
+ xmlreader::Span span;
+ int nsId;
+
+ OString sProperty;
+ OString sValue;
+
+ while (reader.nextAttribute(&nsId, &span))
+ {
+ if (span == "name")
+ {
+ span = reader.getAttributeValue(false);
+ sProperty = OString(span.begin, span.length);
+ }
+ else if (span == "value")
+ {
+ span = reader.getAttributeValue(false);
+ sValue = OString(span.begin, span.length);
+ }
+ }
+
+ if (!sProperty.isEmpty())
+ rMap[sProperty] = OUString::fromUtf8(sValue);
+}
+
+void VclBuilder::collectAtkRelationAttribute(xmlreader::XmlReader &reader, stringmap &rMap)
+{
+ xmlreader::Span span;
+ int nsId;
+
+ OString sProperty;
+ OString sValue;
+
+ while (reader.nextAttribute(&nsId, &span))
+ {
+ if (span == "type")
+ {
+ span = reader.getAttributeValue(false);
+ sProperty = OString(span.begin, span.length);
+ }
+ else if (span == "target")
+ {
+ span = reader.getAttributeValue(false);
+ sValue = OString(span.begin, span.length);
+ sal_Int32 nDelim = sValue.indexOf(':');
+ if (nDelim != -1)
+ sValue = sValue.copy(0, nDelim);
+ }
+ }
+
+ if (!sProperty.isEmpty())
+ rMap[sProperty] = OUString::fromUtf8(sValue);
+}
+
+void VclBuilder::collectAtkRoleAttribute(xmlreader::XmlReader &reader, stringmap &rMap)
+{
+ xmlreader::Span span;
+ int nsId;
+
+ OString sProperty;
+
+ while (reader.nextAttribute(&nsId, &span))
+ {
+ if (span == "type")
+ {
+ span = reader.getAttributeValue(false);
+ sProperty = OString(span.begin, span.length);
+ }
+ }
+
+ if (!sProperty.isEmpty())
+ rMap["role"] = OUString::fromUtf8(sProperty);
+}
+
+void VclBuilder::handleRow(xmlreader::XmlReader &reader, const OString &rID)
+{
+ int nLevel = 1;
+
+ ListStore::row aRow;
+
+ while(true)
+ {
+ xmlreader::Span name;
+ int nsId;
+
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ ++nLevel;
+ if (name == "col")
+ {
+ bool bTranslated = false;
+ sal_uInt32 nId = 0;
+ OString sContext;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "id")
+ {
+ name = reader.getAttributeValue(false);
+ nId = OString(name.begin, name.length).toUInt32();
+ }
+ else if (nId == 0 && name == "translatable" && reader.getAttributeValue(false) == "yes")
+ {
+ bTranslated = true;
+ }
+ else if (name == "context")
+ {
+ name = reader.getAttributeValue(false);
+ sContext = OString(name.begin, name.length);
+ }
+ }
+
+ (void)reader.nextItem(
+ xmlreader::XmlReader::Text::Raw, &name, &nsId);
+
+ OString sValue(name.begin, name.length);
+ OUString sFinalValue;
+ if (bTranslated)
+ {
+ sFinalValue = Translate::get(TranslateId{sContext.getStr(), sValue.getStr()}, m_pParserState->m_aResLocale);
+ }
+ else
+ sFinalValue = OUString::fromUtf8(sValue);
+
+
+ if (aRow.size() < nId+1)
+ aRow.resize(nId+1);
+ aRow[nId] = sFinalValue;
+ }
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+
+ m_pParserState->m_aModels[rID].m_aEntries.push_back(aRow);
+}
+
+void VclBuilder::handleListStore(xmlreader::XmlReader &reader, const OString &rID, std::string_view rClass)
+{
+ int nLevel = 1;
+
+ while(true)
+ {
+ xmlreader::Span name;
+ int nsId;
+
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ if (name == "row")
+ {
+ bool bNotTreeStore = rClass != "GtkTreeStore";
+ if (bNotTreeStore)
+ handleRow(reader, rID);
+ assert(bNotTreeStore && "gtk, as the time of writing, doesn't support data in GtkTreeStore serialization");
+ }
+ else
+ ++nLevel;
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+}
+
+VclBuilder::stringmap VclBuilder::handleAtkObject(xmlreader::XmlReader &reader) const
+{
+ int nLevel = 1;
+
+ stringmap aProperties;
+
+ while (true)
+ {
+ xmlreader::Span name;
+ int nsId;
+
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ ++nLevel;
+ if (name == "property")
+ collectProperty(reader, aProperties);
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+
+ return aProperties;
+}
+
+void VclBuilder::applyAtkProperties(vcl::Window *pWindow, const stringmap& rProperties)
+{
+ assert(pWindow);
+ for (auto const& prop : rProperties)
+ {
+ const OString &rKey = prop.first;
+ const OUString &rValue = prop.second;
+
+ if (pWindow && rKey.match("AtkObject::"))
+ pWindow->set_property(rKey.copy(RTL_CONSTASCII_LENGTH("AtkObject::")), rValue);
+ else
+ SAL_WARN("vcl.builder", "unhandled atk prop: " << rKey);
+ }
+}
+
+std::vector<ComboBoxTextItem> VclBuilder::handleItems(xmlreader::XmlReader &reader) const
+{
+ int nLevel = 1;
+
+ std::vector<ComboBoxTextItem> aItems;
+
+ while(true)
+ {
+ xmlreader::Span name;
+ int nsId;
+
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ ++nLevel;
+ if (name == "item")
+ {
+ bool bTranslated = false;
+ OString sContext, sId;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "translatable" && reader.getAttributeValue(false) == "yes")
+ {
+ bTranslated = true;
+ }
+ else if (name == "context")
+ {
+ name = reader.getAttributeValue(false);
+ sContext = OString(name.begin, name.length);
+ }
+ else if (name == "id")
+ {
+ name = reader.getAttributeValue(false);
+ sId = OString(name.begin, name.length);
+ }
+ }
+
+ (void)reader.nextItem(
+ xmlreader::XmlReader::Text::Raw, &name, &nsId);
+
+ OString sValue(name.begin, name.length);
+ OUString sFinalValue;
+ if (bTranslated)
+ {
+ sFinalValue = Translate::get(TranslateId{sContext.getStr(), sValue.getStr()}, m_pParserState->m_aResLocale);
+ }
+ else
+ sFinalValue = OUString::fromUtf8(sValue);
+
+ if (m_pStringReplace)
+ sFinalValue = (*m_pStringReplace)(sFinalValue);
+
+ aItems.emplace_back(sFinalValue, sId);
+ }
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+
+ return aItems;
+}
+
+VclPtr<Menu> VclBuilder::handleMenu(xmlreader::XmlReader &reader, const OString &rID, bool bMenuBar)
+{
+ VclPtr<Menu> pCurrentMenu;
+ if (bMenuBar)
+ pCurrentMenu = VclPtr<MenuBar>::Create();
+ else
+ pCurrentMenu = VclPtr<PopupMenu>::Create();
+
+ pCurrentMenu->set_id(OStringToOUString(rID, RTL_TEXTENCODING_UTF8));
+
+ int nLevel = 1;
+
+ stringmap aProperties;
+
+ while(true)
+ {
+ xmlreader::Span name;
+ int nsId;
+
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ if (name == "child")
+ {
+ handleMenuChild(pCurrentMenu, reader);
+ }
+ else
+ {
+ ++nLevel;
+ if (name == "property")
+ collectProperty(reader, aProperties);
+ }
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+
+ m_aMenus.emplace_back(rID, pCurrentMenu);
+
+ return pCurrentMenu;
+}
+
+void VclBuilder::handleMenuChild(Menu *pParent, xmlreader::XmlReader &reader)
+{
+ xmlreader::Span name;
+ int nsId;
+
+ int nLevel = 1;
+ while(true)
+ {
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ if (name == "object" || name == "placeholder")
+ {
+ handleMenuObject(pParent, reader);
+ }
+ else
+ ++nLevel;
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ --nLevel;
+
+ if (!nLevel)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+ }
+}
+
+void VclBuilder::handleMenuObject(Menu *pParent, xmlreader::XmlReader &reader)
+{
+ OString sClass;
+ OString sID;
+ OUString sCustomProperty;
+ PopupMenu *pSubMenu = nullptr;
+
+ xmlreader::Span name;
+ int nsId;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "class")
+ {
+ name = reader.getAttributeValue(false);
+ sClass = OString(name.begin, name.length);
+ }
+ else if (name == "id")
+ {
+ name = reader.getAttributeValue(false);
+ sID = OString(name.begin, name.length);
+ if (m_bLegacy)
+ {
+ sal_Int32 nDelim = sID.indexOf(':');
+ if (nDelim != -1)
+ {
+ sCustomProperty = OUString::fromUtf8(sID.subView(nDelim+1));
+ sID = sID.copy(0, nDelim);
+ }
+ }
+ }
+ }
+
+ int nLevel = 1;
+
+ stringmap aProperties;
+ stringmap aAtkProperties;
+ accelmap aAccelerators;
+
+ if (!sCustomProperty.isEmpty())
+ aProperties[OString("customproperty")] = sCustomProperty;
+
+ while(true)
+ {
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ if (name == "child")
+ {
+ size_t nChildMenuIdx = m_aMenus.size();
+ handleChild(nullptr, &aAtkProperties, reader);
+ bool bSubMenuInserted = m_aMenus.size() > nChildMenuIdx;
+ if (bSubMenuInserted)
+ pSubMenu = dynamic_cast<PopupMenu*>(m_aMenus[nChildMenuIdx].m_pMenu.get());
+ }
+ else
+ {
+ ++nLevel;
+ if (name == "property")
+ collectProperty(reader, aProperties);
+ else if (name == "accelerator")
+ collectAccelerator(reader, aAccelerators);
+ }
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+
+ insertMenuObject(pParent, pSubMenu, sClass, sID, aProperties, aAtkProperties, aAccelerators);
+}
+
+void VclBuilder::handleSizeGroup(xmlreader::XmlReader &reader)
+{
+ m_pParserState->m_aSizeGroups.emplace_back();
+ SizeGroup &rSizeGroup = m_pParserState->m_aSizeGroups.back();
+
+ int nLevel = 1;
+
+ while(true)
+ {
+ xmlreader::Span name;
+ int nsId;
+
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ ++nLevel;
+ if (name == "widget")
+ {
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "name")
+ {
+ name = reader.getAttributeValue(false);
+ OString sWidget(name.begin, name.length);
+ sal_Int32 nDelim = sWidget.indexOf(':');
+ if (nDelim != -1)
+ sWidget = sWidget.copy(0, nDelim);
+ rSizeGroup.m_aWidgets.push_back(sWidget);
+ }
+ }
+ }
+ else
+ {
+ if (name == "property")
+ collectProperty(reader, rSizeGroup.m_aProperties);
+ }
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+}
+
+namespace
+{
+ vcl::KeyCode makeKeyCode(const std::pair<OString,OString> &rKey)
+ {
+ bool bShift = rKey.second.indexOf("GDK_SHIFT_MASK") != -1;
+ bool bMod1 = rKey.second.indexOf("GDK_CONTROL_MASK") != -1;
+ bool bMod2 = rKey.second.indexOf("GDK_ALT_MASK") != -1;
+ bool bMod3 = rKey.second.indexOf("GDK_MOD2_MASK") != -1;
+
+ if (rKey.first == "Insert")
+ return vcl::KeyCode(KEY_INSERT, bShift, bMod1, bMod2, bMod3);
+ else if (rKey.first == "Delete")
+ return vcl::KeyCode(KEY_DELETE, bShift, bMod1, bMod2, bMod3);
+ else if (rKey.first == "Return")
+ return vcl::KeyCode(KEY_RETURN, bShift, bMod1, bMod2, bMod3);
+ else if (rKey.first == "Up")
+ return vcl::KeyCode(KEY_UP, bShift, bMod1, bMod2, bMod3);
+ else if (rKey.first == "Down")
+ return vcl::KeyCode(KEY_DOWN, bShift, bMod1, bMod2, bMod3);
+ else if (rKey.first == "Left")
+ return vcl::KeyCode(KEY_LEFT, bShift, bMod1, bMod2, bMod3);
+ else if (rKey.first == "Right")
+ return vcl::KeyCode(KEY_RIGHT, bShift, bMod1, bMod2, bMod3);
+ else if (rKey.first == "asterisk")
+ return vcl::KeyCode(KEY_MULTIPLY, bShift, bMod1, bMod2, bMod3);
+ else if (rKey.first.getLength() > 1 && rKey.first[0] == 'F')
+ {
+ sal_uInt32 nIndex = o3tl::toUInt32(rKey.first.subView(1));
+ assert(nIndex >= 1 && nIndex <= 26);
+ return vcl::KeyCode(KEY_F1 + nIndex - 1, bShift, bMod1, bMod2, bMod3);
+ }
+
+ assert (rKey.first.getLength() == 1);
+ char cChar = rKey.first.toChar();
+
+ if (cChar >= 'a' && cChar <= 'z')
+ return vcl::KeyCode(KEY_A + (cChar - 'a'), bShift, bMod1, bMod2, bMod3);
+ else if (cChar >= 'A' && cChar <= 'Z')
+ return vcl::KeyCode(KEY_A + (cChar - 'A'), bShift, bMod1, bMod2, bMod3);
+ else if (cChar >= '0' && cChar <= '9')
+ return vcl::KeyCode(KEY_0 + (cChar - 'A'), bShift, bMod1, bMod2, bMod3);
+
+ return vcl::KeyCode(cChar, bShift, bMod1, bMod2, bMod3);
+ }
+}
+
+void VclBuilder::insertMenuObject(Menu *pParent, PopupMenu *pSubMenu, const OString &rClass, const OString &rID,
+ stringmap &rProps, stringmap &rAtkProps, accelmap &rAccels)
+{
+ sal_uInt16 nOldCount = pParent->GetItemCount();
+ sal_uInt16 nNewId = ++m_pParserState->m_nLastMenuItemId;
+
+ if(rClass == "NotebookBarAddonsMenuMergePoint")
+ {
+ NotebookBarAddonsMerger::MergeNotebookBarMenuAddons(pParent, nNewId, rID, *m_pNotebookBarAddonsItem);
+ m_pParserState->m_nLastMenuItemId = pParent->GetItemCount();
+ }
+ else if (rClass == "GtkMenuItem")
+ {
+ OUString sLabel(BuilderUtils::convertMnemonicMarkup(extractLabel(rProps)));
+ OUString aCommand(extractActionName(rProps));
+ pParent->InsertItem(nNewId, sLabel, MenuItemBits::NONE , rID);
+ pParent->SetItemCommand(nNewId, aCommand);
+ if (pSubMenu)
+ pParent->SetPopupMenu(nNewId, pSubMenu);
+ }
+ else if (rClass == "GtkCheckMenuItem")
+ {
+ OUString sLabel(BuilderUtils::convertMnemonicMarkup(extractLabel(rProps)));
+ OUString aCommand(extractActionName(rProps));
+ pParent->InsertItem(nNewId, sLabel, MenuItemBits::CHECKABLE, rID);
+ pParent->SetItemCommand(nNewId, aCommand);
+ }
+ else if (rClass == "GtkRadioMenuItem")
+ {
+ OUString sLabel(BuilderUtils::convertMnemonicMarkup(extractLabel(rProps)));
+ OUString aCommand(extractActionName(rProps));
+ pParent->InsertItem(nNewId, sLabel, MenuItemBits::AUTOCHECK | MenuItemBits::RADIOCHECK, rID);
+ pParent->SetItemCommand(nNewId, aCommand);
+ }
+ else if (rClass == "GtkSeparatorMenuItem")
+ {
+ pParent->InsertSeparator(rID);
+ }
+
+ SAL_WARN_IF(nOldCount == pParent->GetItemCount(), "vcl.builder", "probably need to implement " << rClass);
+
+ if (nOldCount != pParent->GetItemCount())
+ {
+ pParent->SetHelpId(nNewId, m_sHelpRoot + rID);
+ if (!extractVisible(rProps))
+ pParent->HideItem(nNewId);
+
+ for (auto const& prop : rProps)
+ {
+ const OString &rKey = prop.first;
+ const OUString &rValue = prop.second;
+
+ if (rKey == "tooltip-markup")
+ pParent->SetTipHelpText(nNewId, rValue);
+ else if (rKey == "tooltip-text")
+ pParent->SetTipHelpText(nNewId, rValue);
+ else
+ SAL_INFO("vcl.builder", "unhandled property: " << rKey);
+ }
+
+ for (auto const& prop : rAtkProps)
+ {
+ const OString &rKey = prop.first;
+ const OUString &rValue = prop.second;
+
+ if (rKey == "AtkObject::accessible-name")
+ pParent->SetAccessibleName(nNewId, rValue);
+ else if (rKey == "AtkObject::accessible-description")
+ pParent->SetAccessibleDescription(nNewId, rValue);
+ else
+ SAL_INFO("vcl.builder", "unhandled atk property: " << rKey);
+ }
+
+ for (auto const& accel : rAccels)
+ {
+ const OString &rSignal = accel.first;
+ const auto &rValue = accel.second;
+
+ if (rSignal == "activate")
+ pParent->SetAccelKey(nNewId, makeKeyCode(rValue));
+ else
+ SAL_INFO("vcl.builder", "unhandled accelerator for: " << rSignal);
+ }
+ }
+
+ rProps.clear();
+}
+
+/// Insert items to a ComboBox or a ListBox.
+/// They have no common ancestor that would have 'InsertEntry()', so use a template.
+template<typename T> static bool insertItems(vcl::Window *pWindow, VclBuilder::stringmap &rMap,
+ std::vector<std::unique_ptr<OUString>>& rUserData,
+ const std::vector<ComboBoxTextItem> &rItems)
+{
+ T *pContainer = dynamic_cast<T*>(pWindow);
+ if (!pContainer)
+ return false;
+
+ sal_uInt16 nActiveId = extractActive(rMap);
+ for (auto const& item : rItems)
+ {
+ sal_Int32 nPos = pContainer->InsertEntry(item.m_sItem);
+ if (!item.m_sId.isEmpty())
+ {
+ rUserData.emplace_back(std::make_unique<OUString>(OUString::fromUtf8(item.m_sId)));
+ pContainer->SetEntryData(nPos, rUserData.back().get());
+ }
+ }
+ if (nActiveId < rItems.size())
+ pContainer->SelectEntryPos(nActiveId);
+
+ return true;
+}
+
+VclPtr<vcl::Window> VclBuilder::handleObject(vcl::Window *pParent, stringmap *pAtkProps, xmlreader::XmlReader &reader)
+{
+ OString sClass;
+ OString sID;
+ OUString sCustomProperty;
+
+ xmlreader::Span name;
+ int nsId;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "class")
+ {
+ name = reader.getAttributeValue(false);
+ sClass = OString(name.begin, name.length);
+ }
+ else if (name == "id")
+ {
+ name = reader.getAttributeValue(false);
+ sID = OString(name.begin, name.length);
+ if (m_bLegacy)
+ {
+ sal_Int32 nDelim = sID.indexOf(':');
+ if (nDelim != -1)
+ {
+ sCustomProperty = OUString::fromUtf8(sID.subView(nDelim+1));
+ sID = sID.copy(0, nDelim);
+ }
+ }
+ }
+ }
+
+ if (sClass == "GtkListStore" || sClass == "GtkTreeStore")
+ {
+ handleListStore(reader, sID, sClass);
+ return nullptr;
+ }
+ else if (sClass == "GtkMenu")
+ {
+ handleMenu(reader, sID, false);
+ return nullptr;
+ }
+ else if (sClass == "GtkMenuBar")
+ {
+ VclPtr<Menu> xMenu = handleMenu(reader, sID, true);
+ if (SystemWindow* pTopLevel = pParent ? pParent->GetSystemWindow() : nullptr)
+ pTopLevel->SetMenuBar(dynamic_cast<MenuBar*>(xMenu.get()));
+ return nullptr;
+ }
+ else if (sClass == "GtkSizeGroup")
+ {
+ handleSizeGroup(reader);
+ return nullptr;
+ }
+ else if (sClass == "AtkObject")
+ {
+ assert((pParent || pAtkProps) && "must have one set");
+ assert(!(pParent && pAtkProps) && "must not have both");
+ auto aAtkProperties = handleAtkObject(reader);
+ if (pParent)
+ applyAtkProperties(pParent, aAtkProperties);
+ if (pAtkProps)
+ *pAtkProps = aAtkProperties;
+ return nullptr;
+ }
+
+ int nLevel = 1;
+
+ stringmap aProperties, aPangoAttributes;
+ stringmap aAtkAttributes;
+ std::vector<ComboBoxTextItem> aItems;
+
+ if (!sCustomProperty.isEmpty())
+ aProperties[OString("customproperty")] = sCustomProperty;
+
+ VclPtr<vcl::Window> pCurrentChild;
+ while(true)
+ {
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ if (name == "child")
+ {
+ if (!pCurrentChild)
+ {
+ pCurrentChild = insertObject(pParent, sClass, sID,
+ aProperties, aPangoAttributes, aAtkAttributes);
+ }
+ handleChild(pCurrentChild, nullptr, reader);
+ }
+ else if (name == "items")
+ aItems = handleItems(reader);
+ else if (name == "style")
+ {
+ int nPriority = 0;
+ std::vector<vcl::EnumContext::Context> aContext = handleStyle(reader, nPriority);
+ if (nPriority != 0)
+ {
+ vcl::IPrioritable* pPrioritable = dynamic_cast<vcl::IPrioritable*>(pCurrentChild.get());
+ SAL_WARN_IF(!pPrioritable, "vcl", "priority set for not supported item");
+ if (pPrioritable)
+ pPrioritable->SetPriority(nPriority);
+ }
+ if (!aContext.empty())
+ {
+ vcl::IContext* pContextControl = dynamic_cast<vcl::IContext*>(pCurrentChild.get());
+ SAL_WARN_IF(!pContextControl, "vcl", "context set for not supported item");
+ if (pContextControl)
+ pContextControl->SetContext(std::move(aContext));
+ }
+ }
+ else
+ {
+ ++nLevel;
+ if (name == "property")
+ collectProperty(reader, aProperties);
+ else if (name == "attribute")
+ collectPangoAttribute(reader, aPangoAttributes);
+ else if (name == "relation")
+ collectAtkRelationAttribute(reader, aAtkAttributes);
+ else if (name == "role")
+ collectAtkRoleAttribute(reader, aAtkAttributes);
+ else if (name == "action-widget")
+ handleActionWidget(reader);
+ }
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+
+ if (sClass == "GtkAdjustment")
+ {
+ m_pParserState->m_aAdjustments[sID] = aProperties;
+ return nullptr;
+ }
+ else if (sClass == "GtkTextBuffer")
+ {
+ m_pParserState->m_aTextBuffers[sID] = aProperties;
+ return nullptr;
+ }
+
+ if (!pCurrentChild)
+ {
+ pCurrentChild = insertObject(pParent, sClass, sID, aProperties,
+ aPangoAttributes, aAtkAttributes);
+ }
+
+ if (!aItems.empty())
+ {
+ // try to fill-in the items
+ if (!insertItems<ComboBox>(pCurrentChild, aProperties, m_aUserData, aItems))
+ insertItems<ListBox>(pCurrentChild, aProperties, m_aUserData, aItems);
+ }
+
+ return pCurrentChild;
+}
+
+void VclBuilder::handlePacking(vcl::Window *pCurrent, vcl::Window *pParent, xmlreader::XmlReader &reader)
+{
+ xmlreader::Span name;
+ int nsId;
+
+ int nLevel = 1;
+
+ while(true)
+ {
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ ++nLevel;
+ if (name == "property")
+ applyPackingProperty(pCurrent, pParent, reader);
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+}
+
+void VclBuilder::applyPackingProperty(vcl::Window *pCurrent,
+ vcl::Window *pParent,
+ xmlreader::XmlReader &reader)
+{
+ if (!pCurrent)
+ return;
+
+ //ToolBoxItems are not true widgets just elements
+ //of the ToolBox itself
+ ToolBox *pToolBoxParent = nullptr;
+ if (pCurrent == pParent)
+ pToolBoxParent = dynamic_cast<ToolBox*>(pParent);
+
+ xmlreader::Span name;
+ int nsId;
+
+ if (pCurrent->GetType() == WindowType::SCROLLWINDOW)
+ {
+ auto aFind = m_pParserState->m_aRedundantParentWidgets.find(VclPtr<vcl::Window>(pCurrent));
+ if (aFind != m_pParserState->m_aRedundantParentWidgets.end())
+ {
+ pCurrent = aFind->second;
+ assert(pCurrent);
+ }
+ }
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "name")
+ {
+ name = reader.getAttributeValue(false);
+ OString sKey(name.begin, name.length);
+ sKey = sKey.replace('_', '-');
+ (void)reader.nextItem(
+ xmlreader::XmlReader::Text::Raw, &name, &nsId);
+ OString sValue(name.begin, name.length);
+
+ if (sKey == "expand" || sKey == "resize")
+ {
+ bool bTrue = (!sValue.isEmpty() && (sValue[0] == 't' || sValue[0] == 'T' || sValue[0] == '1'));
+ if (pToolBoxParent)
+ pToolBoxParent->SetItemExpand(m_pParserState->m_nLastToolbarId, bTrue);
+ else
+ pCurrent->set_expand(bTrue);
+ continue;
+ }
+
+ if (pToolBoxParent)
+ continue;
+
+ if (sKey == "fill")
+ {
+ bool bTrue = (!sValue.isEmpty() && (sValue[0] == 't' || sValue[0] == 'T' || sValue[0] == '1'));
+ pCurrent->set_fill(bTrue);
+ }
+ else if (sKey == "pack-type")
+ {
+ VclPackType ePackType = (!sValue.isEmpty() && (sValue[0] == 'e' || sValue[0] == 'E')) ? VclPackType::End : VclPackType::Start;
+ pCurrent->set_pack_type(ePackType);
+ }
+ else if (sKey == "left-attach")
+ {
+ pCurrent->set_grid_left_attach(sValue.toInt32());
+ }
+ else if (sKey == "top-attach")
+ {
+ pCurrent->set_grid_top_attach(sValue.toInt32());
+ }
+ else if (sKey == "width")
+ {
+ pCurrent->set_grid_width(sValue.toInt32());
+ }
+ else if (sKey == "height")
+ {
+ pCurrent->set_grid_height(sValue.toInt32());
+ }
+ else if (sKey == "padding")
+ {
+ pCurrent->set_padding(sValue.toInt32());
+ }
+ else if (sKey == "position")
+ {
+ set_window_packing_position(pCurrent, sValue.toInt32());
+ }
+ else if (sKey == "secondary")
+ {
+ pCurrent->set_secondary(toBool(sValue));
+ }
+ else if (sKey == "non-homogeneous")
+ {
+ pCurrent->set_non_homogeneous(toBool(sValue));
+ }
+ else if (sKey == "homogeneous")
+ {
+ pCurrent->set_non_homogeneous(!toBool(sValue));
+ }
+ else
+ {
+ SAL_WARN_IF(sKey != "shrink", "vcl.builder", "unknown packing: " << sKey);
+ }
+ }
+ }
+}
+
+std::vector<vcl::EnumContext::Context> VclBuilder::handleStyle(xmlreader::XmlReader &reader, int &nPriority)
+{
+ std::vector<vcl::EnumContext::Context> aContext;
+
+ xmlreader::Span name;
+ int nsId;
+
+ int nLevel = 1;
+
+ while(true)
+ {
+ xmlreader::XmlReader::Result res = reader.nextItem(
+ xmlreader::XmlReader::Text::NONE, &name, &nsId);
+
+ if (res == xmlreader::XmlReader::Result::Done)
+ break;
+
+ if (res == xmlreader::XmlReader::Result::Begin)
+ {
+ ++nLevel;
+ if (name == "class")
+ {
+ OString classStyle = getStyleClass(reader);
+
+ if (classStyle.startsWith("context-"))
+ {
+ OString sContext = classStyle.copy(classStyle.indexOf('-') + 1);
+ OUString sContext2(sContext.getStr(), sContext.getLength(), RTL_TEXTENCODING_UTF8);
+ aContext.push_back(vcl::EnumContext::GetContextEnum(sContext2));
+ }
+ else if (classStyle.startsWith("priority-"))
+ {
+ OString aPriority = classStyle.copy(classStyle.indexOf('-') + 1);
+ OUString aPriority2(aPriority.getStr(), aPriority.getLength(), RTL_TEXTENCODING_UTF8);
+ nPriority = aPriority2.toInt32();
+ }
+ else if (classStyle != "small-button" && classStyle != "destructive-action" && classStyle != "suggested-action")
+ {
+ SAL_WARN("vcl.builder", "unknown class: " << classStyle);
+ }
+ }
+ }
+
+ if (res == xmlreader::XmlReader::Result::End)
+ {
+ --nLevel;
+ }
+
+ if (!nLevel)
+ break;
+ }
+
+ return aContext;
+}
+
+OString VclBuilder::getStyleClass(xmlreader::XmlReader &reader)
+{
+ xmlreader::Span name;
+ int nsId;
+ OString aRet;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "name")
+ {
+ name = reader.getAttributeValue(false);
+ aRet = OString (name.begin, name.length);
+ }
+ }
+
+ return aRet;
+}
+
+void VclBuilder::collectProperty(xmlreader::XmlReader &reader, stringmap &rMap) const
+{
+ xmlreader::Span name;
+ int nsId;
+
+ OString sProperty, sContext;
+
+ bool bTranslated = false;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "name")
+ {
+ name = reader.getAttributeValue(false);
+ sProperty = OString(name.begin, name.length);
+ }
+ else if (name == "context")
+ {
+ name = reader.getAttributeValue(false);
+ sContext = OString(name.begin, name.length);
+ }
+ else if (name == "translatable" && reader.getAttributeValue(false) == "yes")
+ {
+ bTranslated = true;
+ }
+ }
+
+ (void)reader.nextItem(xmlreader::XmlReader::Text::Raw, &name, &nsId);
+ OString sValue(name.begin, name.length);
+ OUString sFinalValue;
+ if (bTranslated)
+ {
+ sFinalValue = Translate::get(TranslateId{sContext.getStr(), sValue.getStr()}, m_pParserState->m_aResLocale);
+ }
+ else
+ sFinalValue = OUString::fromUtf8(sValue);
+
+ if (!sProperty.isEmpty())
+ {
+ sProperty = sProperty.replace('_', '-');
+ if (m_pStringReplace)
+ sFinalValue = (*m_pStringReplace)(sFinalValue);
+ rMap[sProperty] = sFinalValue;
+ }
+}
+
+void VclBuilder::handleActionWidget(xmlreader::XmlReader &reader)
+{
+ xmlreader::Span name;
+ int nsId;
+
+ OString sResponse;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "response")
+ {
+ name = reader.getAttributeValue(false);
+ sResponse = OString(name.begin, name.length);
+ }
+ }
+
+ (void)reader.nextItem(xmlreader::XmlReader::Text::Raw, &name, &nsId);
+ OString sID(name.begin, name.length);
+ sal_Int32 nDelim = sID.indexOf(':');
+ if (nDelim != -1)
+ sID = sID.copy(0, nDelim);
+ set_response(sID, sResponse.toInt32());
+}
+
+void VclBuilder::collectAccelerator(xmlreader::XmlReader &reader, accelmap &rMap)
+{
+ xmlreader::Span name;
+ int nsId;
+
+ OString sProperty;
+ OString sValue;
+ OString sModifiers;
+
+ while (reader.nextAttribute(&nsId, &name))
+ {
+ if (name == "key")
+ {
+ name = reader.getAttributeValue(false);
+ sValue = OString(name.begin, name.length);
+ }
+ else if (name == "signal")
+ {
+ name = reader.getAttributeValue(false);
+ sProperty = OString(name.begin, name.length);
+ }
+ else if (name == "modifiers")
+ {
+ name = reader.getAttributeValue(false);
+ sModifiers = OString(name.begin, name.length);
+ }
+ }
+
+ if (!sProperty.isEmpty() && !sValue.isEmpty())
+ {
+ rMap[sProperty] = std::make_pair(sValue, sModifiers);
+ }
+}
+
+vcl::Window *VclBuilder::get_widget_root()
+{
+ return m_aChildren.empty() ? nullptr : m_aChildren[0].m_pWindow.get();
+}
+
+vcl::Window *VclBuilder::get_by_name(std::string_view sID)
+{
+ for (auto const& child : m_aChildren)
+ {
+ if (child.m_sID == sID)
+ return child.m_pWindow;
+ }
+
+ return nullptr;
+}
+
+PopupMenu *VclBuilder::get_menu(std::string_view sID)
+{
+ for (auto const& menu : m_aMenus)
+ {
+ if (menu.m_sID == sID)
+ return dynamic_cast<PopupMenu*>(menu.m_pMenu.get());
+ }
+
+ return nullptr;
+}
+
+void VclBuilder::set_response(std::string_view sID, short nResponse)
+{
+ switch (nResponse)
+ {
+ case -5:
+ nResponse = RET_OK;
+ break;
+ case -6:
+ nResponse = RET_CANCEL;
+ break;
+ case -7:
+ nResponse = RET_CLOSE;
+ break;
+ case -8:
+ nResponse = RET_YES;
+ break;
+ case -9:
+ nResponse = RET_NO;
+ break;
+ case -11:
+ nResponse = RET_HELP;
+ break;
+ default:
+ assert(nResponse >= 100 && "keep non-canned responses in range 100+ to avoid collision with vcl RET_*");
+ break;
+ }
+
+ for (const auto & child : m_aChildren)
+ {
+ if (child.m_sID == sID)
+ {
+ PushButton* pPushButton = dynamic_cast<PushButton*>(child.m_pWindow.get());
+ assert(pPushButton);
+ Dialog* pDialog = pPushButton->GetParentDialog();
+ assert(pDialog);
+ pDialog->add_button(pPushButton, nResponse, false);
+ return;
+ }
+ }
+
+ assert(false);
+}
+
+void VclBuilder::delete_by_name(const OString& sID)
+{
+ auto aI = std::find_if(m_aChildren.begin(), m_aChildren.end(),
+ [&sID](WinAndId& rItem) { return rItem.m_sID == sID; });
+ if (aI != m_aChildren.end())
+ {
+ aI->m_pWindow.disposeAndClear();
+ m_aChildren.erase(aI);
+ }
+}
+
+void VclBuilder::delete_by_window(vcl::Window *pWindow)
+{
+ drop_ownership(pWindow);
+ pWindow->disposeOnce();
+}
+
+void VclBuilder::drop_ownership(const vcl::Window *pWindow)
+{
+ auto aI = std::find_if(m_aChildren.begin(), m_aChildren.end(),
+ [&pWindow](WinAndId& rItem) { return rItem.m_pWindow == pWindow; });
+ if (aI != m_aChildren.end())
+ m_aChildren.erase(aI);
+}
+
+OString VclBuilder::get_by_window(const vcl::Window *pWindow) const
+{
+ for (auto const& child : m_aChildren)
+ {
+ if (child.m_pWindow == pWindow)
+ return child.m_sID;
+ }
+
+ return OString();
+}
+
+VclBuilder::PackingData VclBuilder::get_window_packing_data(const vcl::Window *pWindow) const
+{
+ //We've stored the return of new Control, some of these get
+ //border windows placed around them which are what you get
+ //from GetChild, so scoot up a level if necessary to get the
+ //window whose position value we have
+ const vcl::Window *pPropHolder = pWindow->ImplGetWindow();
+
+ for (auto const& child : m_aChildren)
+ {
+ if (child.m_pWindow == pPropHolder)
+ return child.m_aPackingData;
+ }
+
+ return PackingData();
+}
+
+void VclBuilder::set_window_packing_position(const vcl::Window *pWindow, sal_Int32 nPosition)
+{
+ for (auto & child : m_aChildren)
+ {
+ if (child.m_pWindow == pWindow)
+ child.m_aPackingData.m_nPosition = nPosition;
+ }
+}
+
+const VclBuilder::ListStore *VclBuilder::get_model_by_name(const OString& sID) const
+{
+ std::map<OString, ListStore>::const_iterator aI = m_pParserState->m_aModels.find(sID);
+ if (aI != m_pParserState->m_aModels.end())
+ return &(aI->second);
+ return nullptr;
+}
+
+const VclBuilder::TextBuffer *VclBuilder::get_buffer_by_name(const OString& sID) const
+{
+ std::map<OString, TextBuffer>::const_iterator aI = m_pParserState->m_aTextBuffers.find(sID);
+ if (aI != m_pParserState->m_aTextBuffers.end())
+ return &(aI->second);
+ return nullptr;
+}
+
+const VclBuilder::Adjustment *VclBuilder::get_adjustment_by_name(const OString& sID) const
+{
+ std::map<OString, Adjustment>::const_iterator aI = m_pParserState->m_aAdjustments.find(sID);
+ if (aI != m_pParserState->m_aAdjustments.end())
+ return &(aI->second);
+ return nullptr;
+}
+
+void VclBuilder::mungeModel(ComboBox &rTarget, const ListStore &rStore, sal_uInt16 nActiveId)
+{
+ for (auto const& entry : rStore.m_aEntries)
+ {
+ const ListStore::row &rRow = entry;
+ sal_uInt16 nEntry = rTarget.InsertEntry(rRow[0]);
+ if (rRow.size() > 1)
+ {
+ if (m_bLegacy)
+ {
+ sal_Int32 nValue = rRow[1].toInt32();
+ rTarget.SetEntryData(nEntry, reinterpret_cast<void*>(nValue));
+ }
+ else
+ {
+ if (!rRow[1].isEmpty())
+ {
+ m_aUserData.emplace_back(std::make_unique<OUString>(rRow[1]));
+ rTarget.SetEntryData(nEntry, m_aUserData.back().get());
+ }
+ }
+ }
+ }
+ if (nActiveId < rStore.m_aEntries.size())
+ rTarget.SelectEntryPos(nActiveId);
+}
+
+void VclBuilder::mungeModel(ListBox &rTarget, const ListStore &rStore, sal_uInt16 nActiveId)
+{
+ for (auto const& entry : rStore.m_aEntries)
+ {
+ const ListStore::row &rRow = entry;
+ sal_uInt16 nEntry = rTarget.InsertEntry(rRow[0]);
+ if (rRow.size() > 1)
+ {
+ if (m_bLegacy)
+ {
+ sal_Int32 nValue = rRow[1].toInt32();
+ rTarget.SetEntryData(nEntry, reinterpret_cast<void*>(nValue));
+ }
+ else
+ {
+ if (!rRow[1].isEmpty())
+ {
+ m_aUserData.emplace_back(std::make_unique<OUString>(rRow[1]));
+ rTarget.SetEntryData(nEntry, m_aUserData.back().get());
+ }
+ }
+ }
+ }
+ if (nActiveId < rStore.m_aEntries.size())
+ rTarget.SelectEntryPos(nActiveId);
+}
+
+void VclBuilder::mungeModel(SvTabListBox& rTarget, const ListStore &rStore, sal_uInt16 nActiveId)
+{
+ for (auto const& entry : rStore.m_aEntries)
+ {
+ const ListStore::row &rRow = entry;
+ auto pEntry = rTarget.InsertEntry(rRow[0]);
+ if (rRow.size() > 1)
+ {
+ if (m_bLegacy)
+ {
+ sal_Int32 nValue = rRow[1].toInt32();
+ pEntry->SetUserData(reinterpret_cast<void*>(nValue));
+ }
+ else
+ {
+ if (!rRow[1].isEmpty())
+ {
+ m_aUserData.emplace_back(std::make_unique<OUString>(rRow[1]));
+ pEntry->SetUserData(m_aUserData.back().get());
+ }
+ }
+ }
+ }
+ if (nActiveId < rStore.m_aEntries.size())
+ {
+ SvTreeListEntry* pEntry = rTarget.GetEntry(nullptr, nActiveId);
+ rTarget.Select(pEntry);
+ }
+}
+
+void VclBuilder::mungeAdjustment(NumericFormatter &rTarget, const Adjustment &rAdjustment)
+{
+ int nMul = rtl_math_pow10Exp(1, rTarget.GetDecimalDigits());
+
+ for (auto const& elem : rAdjustment)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+
+ if (rKey == "upper")
+ {
+ sal_Int64 nUpper = rValue.toDouble() * nMul;
+ rTarget.SetMax(nUpper);
+ rTarget.SetLast(nUpper);
+ }
+ else if (rKey == "lower")
+ {
+ sal_Int64 nLower = rValue.toDouble() * nMul;
+ rTarget.SetMin(nLower);
+ rTarget.SetFirst(nLower);
+ }
+ else if (rKey == "value")
+ {
+ sal_Int64 nValue = rValue.toDouble() * nMul;
+ rTarget.SetValue(nValue);
+ }
+ else if (rKey == "step-increment")
+ {
+ sal_Int64 nSpinSize = rValue.toDouble() * nMul;
+ rTarget.SetSpinSize(nSpinSize);
+ }
+ else
+ {
+ SAL_INFO("vcl.builder", "unhandled property :" << rKey);
+ }
+ }
+}
+
+void VclBuilder::mungeAdjustment(FormattedField &rTarget, const Adjustment &rAdjustment)
+{
+ double nMaxValue = 0, nMinValue = 0, nValue = 0, nSpinSize = 0;
+
+ for (auto const& elem : rAdjustment)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+
+ if (rKey == "upper")
+ nMaxValue = rValue.toDouble();
+ else if (rKey == "lower")
+ nMinValue = rValue.toDouble();
+ else if (rKey == "value")
+ nValue = rValue.toDouble();
+ else if (rKey == "step-increment")
+ nSpinSize = rValue.toDouble();
+ else
+ SAL_INFO("vcl.builder", "unhandled property :" << rKey);
+ }
+
+ Formatter& rFormatter = rTarget.GetFormatter();
+ rFormatter.SetMinValue(nMinValue);
+ rFormatter.SetMaxValue(nMaxValue);
+ rFormatter.SetValue(nValue);
+ rFormatter.SetSpinSize(nSpinSize);
+}
+
+void VclBuilder::mungeAdjustment(ScrollBar &rTarget, const Adjustment &rAdjustment)
+{
+ for (auto const& elem : rAdjustment)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+
+ if (rKey == "upper")
+ rTarget.SetRangeMax(rValue.toInt32());
+ else if (rKey == "lower")
+ rTarget.SetRangeMin(rValue.toInt32());
+ else if (rKey == "value")
+ rTarget.SetThumbPos(rValue.toInt32());
+ else if (rKey == "step-increment")
+ rTarget.SetLineSize(rValue.toInt32());
+ else if (rKey == "page-increment")
+ rTarget.SetPageSize(rValue.toInt32());
+ else
+ {
+ SAL_INFO("vcl.builder", "unhandled property :" << rKey);
+ }
+ }
+}
+
+void VclBuilder::mungeAdjustment(Slider& rTarget, const Adjustment& rAdjustment)
+{
+ for (auto const& elem : rAdjustment)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+
+ if (rKey == "upper")
+ rTarget.SetRangeMax(rValue.toInt32());
+ else if (rKey == "lower")
+ rTarget.SetRangeMin(rValue.toInt32());
+ else if (rKey == "value")
+ rTarget.SetThumbPos(rValue.toInt32());
+ else if (rKey == "step-increment")
+ rTarget.SetLineSize(rValue.toInt32());
+ else if (rKey == "page-increment")
+ rTarget.SetPageSize(rValue.toInt32());
+ else
+ {
+ SAL_INFO("vcl.builder", "unhandled property :" << rKey);
+ }
+ }
+}
+
+void VclBuilder::mungeTextBuffer(VclMultiLineEdit &rTarget, const TextBuffer &rTextBuffer)
+{
+ for (auto const& elem : rTextBuffer)
+ {
+ const OString &rKey = elem.first;
+ const OUString &rValue = elem.second;
+
+ if (rKey == "text")
+ rTarget.SetText(rValue);
+ else
+ {
+ SAL_INFO("vcl.builder", "unhandled property :" << rKey);
+ }
+ }
+}
+
+VclBuilder::ParserState::ParserState()
+ : m_nLastToolbarId(0)
+ , m_nLastMenuItemId(0)
+{}
+
+VclBuilder::MenuAndId::MenuAndId(const OString &rId, Menu *pMenu)
+ : m_sID(rId)
+ , m_pMenu(pMenu)
+{}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/clipping.cxx b/vcl/source/window/clipping.cxx
new file mode 100644
index 000000000..f55283cff
--- /dev/null
+++ b/vcl/source/window/clipping.cxx
@@ -0,0 +1,709 @@
+/* -*- 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 <vcl/window.hxx>
+#include <vcl/virdev.hxx>
+
+#include <tools/debug.hxx>
+
+#include <salobj.hxx>
+#include <window.h>
+
+namespace vcl {
+
+vcl::Region WindowOutputDevice::GetOutputBoundsClipRegion() const
+{
+ vcl::Region aClip(GetClipRegion());
+ aClip.Intersect(tools::Rectangle(Point(), GetOutputSize()));
+
+ return aClip;
+}
+
+void WindowOutputDevice::InitClipRegion()
+{
+ DBG_TESTSOLARMUTEX();
+
+ vcl::Region aRegion;
+
+ if ( mxOwnerWindow->mpWindowImpl->mbInPaint )
+ aRegion = *(mxOwnerWindow->mpWindowImpl->mpPaintRegion);
+ else
+ {
+ aRegion = mxOwnerWindow->ImplGetWinChildClipRegion();
+ // only this region is in frame coordinates, so re-mirror it
+ // the mpWindowImpl->mpPaintRegion above is already correct (see ImplCallPaint()) !
+ if( ImplIsAntiparallel() )
+ ReMirror ( aRegion );
+ }
+ if ( mbClipRegion )
+ aRegion.Intersect( ImplPixelToDevicePixel( maRegion ) );
+ if ( aRegion.IsEmpty() )
+ mbOutputClipped = true;
+ else
+ {
+ mbOutputClipped = false;
+ SelectClipRegion( aRegion );
+ }
+ mbClipRegionSet = true;
+
+ mbInitClipRegion = false;
+}
+
+void Window::SetParentClipMode( ParentClipMode nMode )
+{
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->SetParentClipMode( nMode );
+ else
+ {
+ if ( !ImplIsOverlapWindow() )
+ {
+ mpWindowImpl->mnParentClipMode = nMode;
+ if ( nMode & ParentClipMode::Clip )
+ mpWindowImpl->mpParent->mpWindowImpl->mbClipChildren = true;
+ }
+ }
+}
+
+ParentClipMode Window::GetParentClipMode() const
+{
+ if ( mpWindowImpl->mpBorderWindow )
+ return mpWindowImpl->mpBorderWindow->GetParentClipMode();
+ else
+ return mpWindowImpl->mnParentClipMode;
+}
+
+void Window::ExpandPaintClipRegion( const vcl::Region& rRegion )
+{
+ if( !mpWindowImpl->mpPaintRegion )
+ return;
+
+ vcl::Region aPixRegion = LogicToPixel( rRegion );
+ vcl::Region aDevPixRegion = GetOutDev()->ImplPixelToDevicePixel( aPixRegion );
+
+ vcl::Region aWinChildRegion = ImplGetWinChildClipRegion();
+ // only this region is in frame coordinates, so re-mirror it
+ if( GetOutDev()->ImplIsAntiparallel() )
+ {
+ const OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReMirror( aWinChildRegion );
+ }
+
+ aDevPixRegion.Intersect( aWinChildRegion );
+ if( ! aDevPixRegion.IsEmpty() )
+ {
+ mpWindowImpl->mpPaintRegion->Union( aDevPixRegion );
+ GetOutDev()->mbInitClipRegion = true;
+ }
+}
+
+vcl::Region Window::GetWindowClipRegionPixel() const
+{
+ vcl::Region aWinClipRegion;
+
+ if ( mpWindowImpl->mbInitWinClipRegion )
+ const_cast<vcl::Window*>(this)->ImplInitWinClipRegion();
+ aWinClipRegion = mpWindowImpl->maWinClipRegion;
+
+ vcl::Region aWinRegion( GetOutputRectPixel() );
+
+ if ( aWinRegion == aWinClipRegion )
+ aWinClipRegion.SetNull();
+
+ aWinClipRegion.Move( -GetOutDev()->mnOutOffX, -GetOutDev()->mnOutOffY );
+
+ return aWinClipRegion;
+}
+
+
+vcl::Region WindowOutputDevice::GetActiveClipRegion() const
+{
+ vcl::Region aRegion(true);
+
+ if ( mxOwnerWindow->mpWindowImpl->mbInPaint )
+ {
+ aRegion = *(mxOwnerWindow->mpWindowImpl->mpPaintRegion);
+ aRegion.Move( -mnOutOffX, -mnOutOffY );
+ }
+
+ if ( mbClipRegion )
+ aRegion.Intersect( maRegion );
+
+ return PixelToLogic( aRegion );
+}
+
+void WindowOutputDevice::ClipToPaintRegion(tools::Rectangle& rDstRect)
+{
+ const vcl::Region aPaintRgn(mxOwnerWindow->GetPaintRegion());
+
+ if (!aPaintRgn.IsNull())
+ rDstRect.Intersection(LogicToPixel(aPaintRgn.GetBoundRect()));
+}
+
+void Window::EnableClipSiblings( bool bClipSiblings )
+{
+
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->EnableClipSiblings( bClipSiblings );
+
+ mpWindowImpl->mbClipSiblings = bClipSiblings;
+}
+
+void Window::ImplClipBoundaries( vcl::Region& rRegion, bool bThis, bool bOverlaps )
+{
+ if ( bThis )
+ ImplIntersectWindowClipRegion( rRegion );
+ else if ( ImplIsOverlapWindow() )
+ {
+ // clip to frame if required
+ if ( !mpWindowImpl->mbFrame )
+ rRegion.Intersect( tools::Rectangle( Point( 0, 0 ), mpWindowImpl->mpFrameWindow->GetOutputSizePixel() ) );
+
+ if ( bOverlaps && !rRegion.IsEmpty() )
+ {
+ // Clip Overlap Siblings
+ vcl::Window* pStartOverlapWindow = this;
+ while ( !pStartOverlapWindow->mpWindowImpl->mbFrame )
+ {
+ vcl::Window* pOverlapWindow = pStartOverlapWindow->mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap;
+ while ( pOverlapWindow && (pOverlapWindow != pStartOverlapWindow) )
+ {
+ pOverlapWindow->ImplExcludeOverlapWindows2( rRegion );
+ pOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext;
+ }
+ pStartOverlapWindow = pStartOverlapWindow->mpWindowImpl->mpOverlapWindow;
+ }
+
+ // Clip Child Overlap Windows
+ ImplExcludeOverlapWindows( rRegion );
+ }
+ }
+ else
+ ImplGetParent()->ImplIntersectWindowClipRegion( rRegion );
+}
+
+bool Window::ImplClipChildren( vcl::Region& rRegion ) const
+{
+ bool bOtherClip = false;
+ vcl::Window* pWindow = mpWindowImpl->mpFirstChild;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbReallyVisible )
+ {
+ // read-out ParentClipMode-Flags
+ ParentClipMode nClipMode = pWindow->GetParentClipMode();
+ if ( !(nClipMode & ParentClipMode::NoClip) &&
+ ((nClipMode & ParentClipMode::Clip) || (GetStyle() & WB_CLIPCHILDREN)) )
+ pWindow->ImplExcludeWindowRegion( rRegion );
+ else
+ bOtherClip = true;
+ }
+
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+
+ return bOtherClip;
+}
+
+void Window::ImplClipAllChildren( vcl::Region& rRegion ) const
+{
+ vcl::Window* pWindow = mpWindowImpl->mpFirstChild;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbReallyVisible )
+ pWindow->ImplExcludeWindowRegion( rRegion );
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplClipSiblings( vcl::Region& rRegion ) const
+{
+ vcl::Window* pWindow = ImplGetParent()->mpWindowImpl->mpFirstChild;
+ while ( pWindow )
+ {
+ if ( pWindow == this )
+ break;
+
+ if ( pWindow->mpWindowImpl->mbReallyVisible )
+ pWindow->ImplExcludeWindowRegion( rRegion );
+
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplInitWinClipRegion()
+{
+ // Build Window Region
+ mpWindowImpl->maWinClipRegion = GetOutputRectPixel();
+ if ( mpWindowImpl->mbWinRegion )
+ mpWindowImpl->maWinClipRegion.Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) );
+
+ // ClipSiblings
+ if ( mpWindowImpl->mbClipSiblings && !ImplIsOverlapWindow() )
+ ImplClipSiblings( mpWindowImpl->maWinClipRegion );
+
+ // Clip Parent Boundaries
+ ImplClipBoundaries( mpWindowImpl->maWinClipRegion, false, true );
+
+ // Clip Children
+ if ( (GetStyle() & WB_CLIPCHILDREN) || mpWindowImpl->mbClipChildren )
+ mpWindowImpl->mbInitChildRegion = true;
+
+ mpWindowImpl->mbInitWinClipRegion = false;
+}
+
+void Window::ImplInitWinChildClipRegion()
+{
+ if ( !mpWindowImpl->mpFirstChild )
+ {
+ mpWindowImpl->mpChildClipRegion.reset();
+ }
+ else
+ {
+ if ( !mpWindowImpl->mpChildClipRegion )
+ mpWindowImpl->mpChildClipRegion.reset( new vcl::Region( mpWindowImpl->maWinClipRegion ) );
+ else
+ *mpWindowImpl->mpChildClipRegion = mpWindowImpl->maWinClipRegion;
+
+ ImplClipChildren( *mpWindowImpl->mpChildClipRegion );
+ }
+
+ mpWindowImpl->mbInitChildRegion = false;
+}
+
+Region& Window::ImplGetWinChildClipRegion()
+{
+ if ( mpWindowImpl->mbInitWinClipRegion )
+ ImplInitWinClipRegion();
+ if ( mpWindowImpl->mbInitChildRegion )
+ ImplInitWinChildClipRegion();
+ if ( mpWindowImpl->mpChildClipRegion )
+ return *mpWindowImpl->mpChildClipRegion;
+ return mpWindowImpl->maWinClipRegion;
+}
+
+bool Window::ImplSysObjClip( const vcl::Region* pOldRegion )
+{
+ bool bUpdate = true;
+
+ if ( mpWindowImpl->mpSysObj )
+ {
+ bool bVisibleState = mpWindowImpl->mbReallyVisible;
+
+ if ( bVisibleState )
+ {
+ vcl::Region& rWinChildClipRegion = ImplGetWinChildClipRegion();
+
+ if (!rWinChildClipRegion.IsEmpty())
+ {
+ if ( pOldRegion )
+ {
+ vcl::Region aNewRegion = rWinChildClipRegion;
+ rWinChildClipRegion.Intersect(*pOldRegion);
+ bUpdate = aNewRegion == rWinChildClipRegion;
+ }
+
+ vcl::Region aRegion = rWinChildClipRegion;
+ vcl::Region aWinRectRegion( GetOutputRectPixel() );
+
+ if ( aRegion == aWinRectRegion )
+ mpWindowImpl->mpSysObj->ResetClipRegion();
+ else
+ {
+ aRegion.Move( -GetOutDev()->mnOutOffX, -GetOutDev()->mnOutOffY );
+
+ // set/update clip region
+ RectangleVector aRectangles;
+ aRegion.GetRegionRectangles(aRectangles);
+ mpWindowImpl->mpSysObj->BeginSetClipRegion(aRectangles.size());
+
+ for (auto const& rectangle : aRectangles)
+ {
+ mpWindowImpl->mpSysObj->UnionClipRegion(
+ rectangle.Left(),
+ rectangle.Top(),
+ rectangle.GetWidth(), // orig nWidth was ((R - L) + 1), same as GetWidth does
+ rectangle.GetHeight()); // same for height
+ }
+
+ mpWindowImpl->mpSysObj->EndSetClipRegion();
+ }
+ }
+ else
+ bVisibleState = false;
+ }
+
+ // update visible status
+ mpWindowImpl->mpSysObj->Show( bVisibleState );
+ }
+
+ return bUpdate;
+}
+
+void Window::ImplUpdateSysObjChildrenClip()
+{
+ if ( mpWindowImpl->mpSysObj && mpWindowImpl->mbInitWinClipRegion )
+ ImplSysObjClip( nullptr );
+
+ vcl::Window* pWindow = mpWindowImpl->mpFirstChild;
+ while ( pWindow )
+ {
+ pWindow->ImplUpdateSysObjChildrenClip();
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplUpdateSysObjOverlapsClip()
+{
+ ImplUpdateSysObjChildrenClip();
+
+ vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pWindow )
+ {
+ pWindow->ImplUpdateSysObjOverlapsClip();
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplUpdateSysObjClip()
+{
+ if ( !ImplIsOverlapWindow() )
+ {
+ ImplUpdateSysObjChildrenClip();
+
+ // siblings should recalculate their clip region
+ if ( mpWindowImpl->mbClipSiblings )
+ {
+ vcl::Window* pWindow = mpWindowImpl->mpNext;
+ while ( pWindow )
+ {
+ pWindow->ImplUpdateSysObjChildrenClip();
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+ }
+ }
+ else
+ mpWindowImpl->mpFrameWindow->ImplUpdateSysObjOverlapsClip();
+}
+
+bool Window::ImplSetClipFlagChildren( bool bSysObjOnlySmaller )
+{
+ bool bUpdate = true;
+ if ( mpWindowImpl->mpSysObj )
+ {
+ std::unique_ptr<vcl::Region> pOldRegion;
+ if ( bSysObjOnlySmaller && !mpWindowImpl->mbInitWinClipRegion )
+ pOldRegion.reset(new vcl::Region( mpWindowImpl->maWinClipRegion ));
+
+ GetOutDev()->mbInitClipRegion = true;
+ mpWindowImpl->mbInitWinClipRegion = true;
+
+ vcl::Window* pWindow = mpWindowImpl->mpFirstChild;
+ while ( pWindow )
+ {
+ if ( !pWindow->ImplSetClipFlagChildren( bSysObjOnlySmaller ) )
+ bUpdate = false;
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+
+ if ( !ImplSysObjClip( pOldRegion.get() ) )
+ {
+ GetOutDev()->mbInitClipRegion = true;
+ mpWindowImpl->mbInitWinClipRegion = true;
+ bUpdate = false;
+ }
+ }
+ else
+ {
+ GetOutDev()->mbInitClipRegion = true;
+ mpWindowImpl->mbInitWinClipRegion = true;
+
+ vcl::Window* pWindow = mpWindowImpl->mpFirstChild;
+ while ( pWindow )
+ {
+ if ( !pWindow->ImplSetClipFlagChildren( bSysObjOnlySmaller ) )
+ bUpdate = false;
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+ }
+ return bUpdate;
+}
+
+bool Window::ImplSetClipFlagOverlapWindows( bool bSysObjOnlySmaller )
+{
+ bool bUpdate = ImplSetClipFlagChildren( bSysObjOnlySmaller );
+
+ vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pWindow )
+ {
+ if ( !pWindow->ImplSetClipFlagOverlapWindows( bSysObjOnlySmaller ) )
+ bUpdate = false;
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+
+ return bUpdate;
+}
+
+bool Window::ImplSetClipFlag( bool bSysObjOnlySmaller )
+{
+ if ( !ImplIsOverlapWindow() )
+ {
+ bool bUpdate = ImplSetClipFlagChildren( bSysObjOnlySmaller );
+
+ vcl::Window* pParent = ImplGetParent();
+ if ( pParent &&
+ ((pParent->GetStyle() & WB_CLIPCHILDREN) || (mpWindowImpl->mnParentClipMode & ParentClipMode::Clip)) )
+ {
+ pParent->GetOutDev()->mbInitClipRegion = true;
+ pParent->mpWindowImpl->mbInitChildRegion = true;
+ }
+
+ // siblings should recalculate their clip region
+ if ( mpWindowImpl->mbClipSiblings )
+ {
+ vcl::Window* pWindow = mpWindowImpl->mpNext;
+ while ( pWindow )
+ {
+ if ( !pWindow->ImplSetClipFlagChildren( bSysObjOnlySmaller ) )
+ bUpdate = false;
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+ }
+
+ return bUpdate;
+ }
+ else
+ return mpWindowImpl->mpFrameWindow->ImplSetClipFlagOverlapWindows( bSysObjOnlySmaller );
+}
+
+void Window::ImplIntersectWindowClipRegion( vcl::Region& rRegion )
+{
+ if ( mpWindowImpl->mbInitWinClipRegion )
+ ImplInitWinClipRegion();
+
+ rRegion.Intersect( mpWindowImpl->maWinClipRegion );
+}
+
+void Window::ImplIntersectWindowRegion( vcl::Region& rRegion )
+{
+ rRegion.Intersect( GetOutputRectPixel() );
+ if ( mpWindowImpl->mbWinRegion )
+ rRegion.Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) );
+}
+
+void Window::ImplExcludeWindowRegion( vcl::Region& rRegion )
+{
+ if ( mpWindowImpl->mbWinRegion )
+ {
+ vcl::Region aRegion( GetOutputRectPixel() );
+ aRegion.Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) );
+ rRegion.Exclude( aRegion );
+ }
+ else
+ {
+ rRegion.Exclude( GetOutputRectPixel() );
+ }
+}
+
+void Window::ImplExcludeOverlapWindows( vcl::Region& rRegion ) const
+{
+ vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbReallyVisible )
+ {
+ pWindow->ImplExcludeWindowRegion( rRegion );
+ pWindow->ImplExcludeOverlapWindows( rRegion );
+ }
+
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplExcludeOverlapWindows2( vcl::Region& rRegion )
+{
+ if ( mpWindowImpl->mbReallyVisible )
+ ImplExcludeWindowRegion( rRegion );
+
+ ImplExcludeOverlapWindows( rRegion );
+}
+
+void Window::ImplIntersectAndUnionOverlapWindows( const vcl::Region& rInterRegion, vcl::Region& rRegion ) const
+{
+ vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbReallyVisible )
+ {
+ vcl::Region aTempRegion( rInterRegion );
+ pWindow->ImplIntersectWindowRegion( aTempRegion );
+ rRegion.Union( aTempRegion );
+ pWindow->ImplIntersectAndUnionOverlapWindows( rInterRegion, rRegion );
+ }
+
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplIntersectAndUnionOverlapWindows2( const vcl::Region& rInterRegion, vcl::Region& rRegion )
+{
+ if ( mpWindowImpl->mbReallyVisible )
+ {
+ vcl::Region aTempRegion( rInterRegion );
+ ImplIntersectWindowRegion( aTempRegion );
+ rRegion.Union( aTempRegion );
+ }
+
+ ImplIntersectAndUnionOverlapWindows( rInterRegion, rRegion );
+}
+
+void Window::ImplCalcOverlapRegionOverlaps( const vcl::Region& rInterRegion, vcl::Region& rRegion ) const
+{
+ // Clip Overlap Siblings
+ vcl::Window const * pStartOverlapWindow;
+ if ( !ImplIsOverlapWindow() )
+ pStartOverlapWindow = mpWindowImpl->mpOverlapWindow;
+ else
+ pStartOverlapWindow = this;
+ while ( !pStartOverlapWindow->mpWindowImpl->mbFrame )
+ {
+ vcl::Window* pOverlapWindow = pStartOverlapWindow->mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap;
+ while ( pOverlapWindow && (pOverlapWindow != pStartOverlapWindow) )
+ {
+ pOverlapWindow->ImplIntersectAndUnionOverlapWindows2( rInterRegion, rRegion );
+ pOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext;
+ }
+ pStartOverlapWindow = pStartOverlapWindow->mpWindowImpl->mpOverlapWindow;
+ }
+
+ // Clip Child Overlap Windows
+ if ( !ImplIsOverlapWindow() )
+ mpWindowImpl->mpOverlapWindow->ImplIntersectAndUnionOverlapWindows( rInterRegion, rRegion );
+ else
+ ImplIntersectAndUnionOverlapWindows( rInterRegion, rRegion );
+}
+
+void Window::ImplCalcOverlapRegion( const tools::Rectangle& rSourceRect, vcl::Region& rRegion,
+ bool bChildren, bool bSiblings )
+{
+ vcl::Region aRegion( rSourceRect );
+ if ( mpWindowImpl->mbWinRegion )
+ rRegion.Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) );
+ vcl::Region aTempRegion;
+ vcl::Window* pWindow;
+
+ ImplCalcOverlapRegionOverlaps( aRegion, rRegion );
+
+ // Parent-Boundaries
+ pWindow = this;
+ if ( !ImplIsOverlapWindow() )
+ {
+ pWindow = ImplGetParent();
+ do
+ {
+ aTempRegion = aRegion;
+ pWindow->ImplExcludeWindowRegion( aTempRegion );
+ rRegion.Union( aTempRegion );
+ if ( pWindow->ImplIsOverlapWindow() )
+ break;
+ pWindow = pWindow->ImplGetParent();
+ }
+ while ( pWindow );
+ }
+ if ( pWindow && !pWindow->mpWindowImpl->mbFrame )
+ {
+ aTempRegion = aRegion;
+ aTempRegion.Exclude( tools::Rectangle( Point( 0, 0 ), mpWindowImpl->mpFrameWindow->GetOutputSizePixel() ) );
+ rRegion.Union( aTempRegion );
+ }
+
+ // Siblings
+ if ( bSiblings && !ImplIsOverlapWindow() )
+ {
+ pWindow = mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild;
+ do
+ {
+ if ( pWindow->mpWindowImpl->mbReallyVisible && (pWindow != this) )
+ {
+ aTempRegion = aRegion;
+ pWindow->ImplIntersectWindowRegion( aTempRegion );
+ rRegion.Union( aTempRegion );
+ }
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+ while ( pWindow );
+ }
+
+ if ( !bChildren )
+ return;
+
+ pWindow = mpWindowImpl->mpFirstChild;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbReallyVisible )
+ {
+ aTempRegion = aRegion;
+ pWindow->ImplIntersectWindowRegion( aTempRegion );
+ rRegion.Union( aTempRegion );
+ }
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+}
+
+void WindowOutputDevice::SaveBackground(VirtualDevice& rSaveDevice, const Point& rPos, const Size& rSize, const Size&) const
+{
+ MapMode aTempMap(GetMapMode());
+ aTempMap.SetOrigin(Point());
+ rSaveDevice.SetMapMode(aTempMap);
+
+ if ( mxOwnerWindow->mpWindowImpl->mpPaintRegion )
+ {
+ vcl::Region aClip( *mxOwnerWindow->mpWindowImpl->mpPaintRegion );
+ const Point aPixPos( LogicToPixel( rPos ) );
+
+ aClip.Move( -mnOutOffX, -mnOutOffY );
+ aClip.Intersect( tools::Rectangle( aPixPos, LogicToPixel( rSize ) ) );
+
+ if ( !aClip.IsEmpty() )
+ {
+ const vcl::Region aOldClip( rSaveDevice.GetClipRegion() );
+ const Point aPixOffset( rSaveDevice.LogicToPixel( Point() ) );
+ const bool bMap = rSaveDevice.IsMapModeEnabled();
+
+ // move clip region to have the same distance to DestOffset
+ aClip.Move( aPixOffset.X() - aPixPos.X(), aPixOffset.Y() - aPixPos.Y() );
+
+ // set pixel clip region
+ rSaveDevice.EnableMapMode( false );
+ rSaveDevice.SetClipRegion( aClip );
+ rSaveDevice.EnableMapMode( bMap );
+ rSaveDevice.DrawOutDev( Point(), rSize, rPos, rSize, *this );
+ rSaveDevice.SetClipRegion( aOldClip );
+ }
+ }
+ else
+ {
+ rSaveDevice.DrawOutDev( Point(), rSize, rPos, rSize, *this );
+ }
+
+ rSaveDevice.SetMapMode(MapMode());
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/commandevent.cxx b/vcl/source/window/commandevent.cxx
new file mode 100644
index 000000000..812409ca1
--- /dev/null
+++ b/vcl/source/window/commandevent.cxx
@@ -0,0 +1,198 @@
+/* -*- 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 <string.h>
+
+#include <vcl/commandevent.hxx>
+
+CommandExtTextInputData::CommandExtTextInputData( const OUString& rText,
+ const ExtTextInputAttr* pTextAttr, sal_Int32 nCursorPos, sal_uInt16 nCursorFlags,
+ bool bOnlyCursor)
+ : maText(rText)
+{
+ if ( pTextAttr && !maText.isEmpty() )
+ {
+ mpTextAttr.reset( new ExtTextInputAttr[maText.getLength()] );
+ memcpy( mpTextAttr.get(), pTextAttr, maText.getLength()*sizeof(ExtTextInputAttr) );
+ }
+
+ mnCursorPos = nCursorPos;
+ mnCursorFlags = nCursorFlags;
+ mbOnlyCursor = bOnlyCursor;
+}
+
+CommandExtTextInputData::CommandExtTextInputData( const CommandExtTextInputData& rData ) :
+ maText( rData.maText )
+{
+ if ( rData.mpTextAttr && !maText.isEmpty() )
+ {
+ mpTextAttr.reset( new ExtTextInputAttr[maText.getLength()] );
+ memcpy( mpTextAttr.get(), rData.mpTextAttr.get(), maText.getLength()*sizeof(ExtTextInputAttr) );
+ }
+
+ mnCursorPos = rData.mnCursorPos;
+ mnCursorFlags = rData.mnCursorFlags;
+ mbOnlyCursor = rData.mbOnlyCursor;
+}
+
+CommandExtTextInputData::~CommandExtTextInputData()
+{
+}
+
+CommandWheelData::CommandWheelData()
+{
+ mnDelta = 0;
+ mnNotchDelta = 0;
+ mnLines = 0.0;
+ mnWheelMode = CommandWheelMode::NONE;
+ mnCode = 0;
+ mbHorz = false;
+ mbDeltaIsPixel = false;
+}
+
+CommandWheelData::CommandWheelData( tools::Long nWheelDelta, tools::Long nWheelNotchDelta,
+ double nScrollLines,
+ CommandWheelMode nWheelMode, sal_uInt16 nKeyModifier,
+ bool bHorz, bool bDeltaIsPixel )
+{
+ mnDelta = nWheelDelta;
+ mnNotchDelta = nWheelNotchDelta;
+ mnLines = nScrollLines;
+ mnWheelMode = nWheelMode;
+ mnCode = nKeyModifier;
+ mbHorz = bHorz;
+ mbDeltaIsPixel = bDeltaIsPixel;
+}
+
+CommandScrollData::CommandScrollData( tools::Long nDeltaX, tools::Long nDeltaY )
+{
+ mnDeltaX = nDeltaX;
+ mnDeltaY = nDeltaY;
+}
+
+CommandModKeyData::CommandModKeyData( ModKeyFlags nCode, bool bDown )
+{
+ mbDown = bDown;
+ mnCode = nCode;
+}
+
+CommandSelectionChangeData::CommandSelectionChangeData( sal_uLong nStart, sal_uLong nEnd )
+{
+ mnStart = nStart;
+ mnEnd = nEnd;
+}
+
+CommandEvent::CommandEvent()
+{
+ mpData = nullptr;
+ mnCommand = CommandEventId::NONE;
+ mbMouseEvent = false;
+}
+
+CommandEvent::CommandEvent( const Point& rMousePos,
+ CommandEventId nCmd, bool bMEvt, const void* pCmdData ) :
+ maPos( rMousePos )
+{
+ mpData = const_cast<void*>(pCmdData);
+ mnCommand = nCmd;
+ mbMouseEvent = bMEvt;
+}
+
+const CommandExtTextInputData* CommandEvent::GetExtTextInputData() const
+{
+ if ( mnCommand == CommandEventId::ExtTextInput )
+ return static_cast<const CommandExtTextInputData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandWheelData* CommandEvent::GetWheelData() const
+{
+ if ( mnCommand == CommandEventId::Wheel )
+ return static_cast<const CommandWheelData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandScrollData* CommandEvent::GetAutoScrollData() const
+{
+ if ( mnCommand == CommandEventId::AutoScroll )
+ return static_cast<const CommandScrollData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandModKeyData* CommandEvent::GetModKeyData() const
+{
+ if( mnCommand == CommandEventId::ModKeyChange )
+ return static_cast<const CommandModKeyData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandDialogData* CommandEvent::GetDialogData() const
+{
+ if( mnCommand == CommandEventId::ShowDialog )
+ return static_cast<const CommandDialogData*>(mpData);
+ else
+ return nullptr;
+}
+
+CommandMediaData* CommandEvent::GetMediaData() const
+{
+ if( mnCommand == CommandEventId::Media )
+ return static_cast<CommandMediaData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandSelectionChangeData* CommandEvent::GetSelectionChangeData() const
+{
+ if( mnCommand == CommandEventId::SelectionChange )
+ return static_cast<const CommandSelectionChangeData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandSwipeData* CommandEvent::GetSwipeData() const
+{
+ if( mnCommand == CommandEventId::Swipe )
+ return static_cast<const CommandSwipeData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandLongPressData* CommandEvent::GetLongPressData() const
+{
+ if( mnCommand == CommandEventId::LongPress )
+ return static_cast<const CommandLongPressData*>(mpData);
+ else
+ return nullptr;
+}
+
+const CommandGestureData* CommandEvent::GetGestureData() const
+{
+ if (mnCommand == CommandEventId::Gesture)
+ return static_cast<const CommandGestureData*>(mpData);
+ else
+ return nullptr;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/cursor.cxx b/vcl/source/window/cursor.cxx
new file mode 100644
index 000000000..406491ed1
--- /dev/null
+++ b/vcl/source/window/cursor.cxx
@@ -0,0 +1,485 @@
+/* -*- 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 <memory>
+
+#include <comphelper/lok.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/window.hxx>
+#include <vcl/cursor.hxx>
+
+#include <window.h>
+
+#include <tools/poly.hxx>
+
+struct ImplCursorData
+{
+ AutoTimer maTimer { "vcl ImplCursorData maTimer" }; // Timer
+ Point maPixPos; // Pixel-Position
+ Point maPixRotOff; // Pixel-Offset-Position
+ Size maPixSize; // Pixel-Size
+ Degree10 mnOrientation; // Pixel-Orientation
+ CursorDirection mnDirection; // indicates writing direction
+ sal_uInt16 mnStyle; // Cursor-Style
+ bool mbCurVisible; // Is cursor currently visible
+ VclPtr<vcl::Window> mpWindow; // assigned window
+};
+
+namespace
+{
+const char* pDisableCursorIndicator(getenv("SAL_DISABLE_CURSOR_INDICATOR"));
+bool bDisableCursorIndicator(nullptr != pDisableCursorIndicator);
+}
+
+static tools::Rectangle ImplCursorInvert(vcl::RenderContext* pRenderContext, ImplCursorData const * pData)
+{
+ tools::Rectangle aPaintRect;
+
+ bool bMapMode = pRenderContext->IsMapModeEnabled();
+ pRenderContext->EnableMapMode( false );
+ InvertFlags nInvertStyle;
+ if ( pData->mnStyle & CURSOR_SHADOW )
+ nInvertStyle = InvertFlags::N50;
+ else
+ nInvertStyle = InvertFlags::NONE;
+
+ tools::Rectangle aRect( pData->maPixPos, pData->maPixSize );
+ if ( pData->mnDirection != CursorDirection::NONE || pData->mnOrientation )
+ {
+ tools::Polygon aPoly( aRect );
+ if( aPoly.GetSize() == 5 )
+ {
+ aPoly[1].AdjustX(1 ); // include the right border
+ aPoly[2].AdjustX(1 );
+
+ // apply direction flag after slant to use the correct shape
+ if (!bDisableCursorIndicator && pData->mnDirection != CursorDirection::NONE)
+ {
+ Point pAry[7];
+ // Related system settings for "delta" could be:
+ // gtk cursor-aspect-ratio and windows SPI_GETCARETWIDTH
+ int delta = (aRect.getHeight() * 4 / 100) + 1;
+ if( pData->mnDirection == CursorDirection::LTR )
+ {
+ // left-to-right
+ pAry[0] = aPoly.GetPoint( 0 );
+ pAry[1] = aPoly.GetPoint( 1 );
+ pAry[2] = pAry[1];
+ pAry[2].AdjustX(delta);
+ pAry[2].AdjustY(delta);
+ pAry[3] = pAry[1];
+ pAry[3].AdjustY(delta * 2);
+ pAry[4] = aPoly.GetPoint( 2 );
+ pAry[5] = aPoly.GetPoint( 3 );
+ pAry[6] = aPoly.GetPoint( 4 );
+ }
+ else if( pData->mnDirection == CursorDirection::RTL )
+ {
+ // right-to-left
+ pAry[0] = aPoly.GetPoint( 0 );
+ pAry[1] = aPoly.GetPoint( 1 );
+ pAry[2] = aPoly.GetPoint( 2 );
+ pAry[3] = aPoly.GetPoint( 3 );
+ pAry[4] = pAry[0];
+ pAry[4].AdjustY(delta*2);
+ pAry[5] = pAry[0];
+ pAry[5].AdjustX(-delta);
+ pAry[5].AdjustY(delta);
+ pAry[6] = aPoly.GetPoint( 4 );
+ }
+ aPoly = tools::Polygon( 7, pAry);
+ }
+
+ if ( pData->mnOrientation )
+ aPoly.Rotate( pData->maPixRotOff, pData->mnOrientation );
+ pRenderContext->Invert( aPoly, nInvertStyle );
+ aPaintRect = aPoly.GetBoundRect();
+ }
+ }
+ else
+ {
+ pRenderContext->Invert( aRect, nInvertStyle );
+ aPaintRect = aRect;
+ }
+ pRenderContext->EnableMapMode( bMapMode );
+ return aPaintRect;
+}
+
+static void ImplCursorInvert(vcl::Window* pWindow, ImplCursorData const * pData)
+{
+ if (!pWindow || pWindow->isDisposed())
+ return;
+
+ vcl::PaintBufferGuardPtr pGuard;
+ const bool bDoubleBuffering = pWindow->SupportsDoubleBuffering();
+ if (bDoubleBuffering)
+ pGuard.reset(new vcl::PaintBufferGuard(pWindow->ImplGetWindowImpl()->mpFrameData, pWindow));
+
+ vcl::RenderContext* pRenderContext = bDoubleBuffering ? pGuard->GetRenderContext() : pWindow->GetOutDev();
+
+ tools::Rectangle aPaintRect = ImplCursorInvert(pRenderContext, pData);
+ if (bDoubleBuffering)
+ pGuard->SetPaintRect(pRenderContext->PixelToLogic(aPaintRect));
+}
+
+bool vcl::Cursor::ImplPrepForDraw(const OutputDevice* pDevice, ImplCursorData& rData)
+{
+ if (pDevice && !rData.mbCurVisible)
+ {
+ rData.maPixPos = pDevice->LogicToPixel( maPos );
+ rData.maPixSize = pDevice->LogicToPixel( maSize );
+ rData.mnOrientation = mnOrientation;
+ rData.mnDirection = mnDirection;
+
+ // correct the position with the offset
+ rData.maPixRotOff = rData.maPixPos;
+
+ // use width (as set in Settings) if size is 0,
+ if (!rData.maPixSize.Width())
+ rData.maPixSize.setWidth(pDevice->GetSettings().GetStyleSettings().GetCursorSize());
+ return true;
+ }
+ return false;
+}
+
+void vcl::Cursor::ImplDraw()
+{
+ if (mpData && mpData->mpWindow)
+ {
+ // calculate output area
+ if (ImplPrepForDraw(mpData->mpWindow->GetOutDev(), *mpData))
+ {
+ // display
+ ImplCursorInvert(mpData->mpWindow, mpData.get());
+ mpData->mbCurVisible = true;
+ }
+ }
+}
+
+void vcl::Cursor::DrawToDevice(OutputDevice& rRenderContext)
+{
+ ImplCursorData aData;
+ aData.mnStyle = 0;
+ aData.mbCurVisible = false;
+ // calculate output area
+ if (ImplPrepForDraw(&rRenderContext, aData))
+ {
+ // display
+ ImplCursorInvert(&rRenderContext, &aData);
+ }
+}
+
+void vcl::Cursor::ImplRestore()
+{
+ assert( mpData && mpData->mbCurVisible );
+
+ ImplCursorInvert(mpData->mpWindow, mpData.get());
+ mpData->mbCurVisible = false;
+}
+
+void vcl::Cursor::ImplDoShow( bool bDrawDirect, bool bRestore )
+{
+ if ( !mbVisible )
+ return;
+
+ vcl::Window* pWindow;
+ if ( mpWindow )
+ pWindow = mpWindow;
+ else
+ {
+ // show the cursor, if there is an active window and the cursor
+ // has been selected in this window
+ pWindow = Application::GetFocusWindow();
+ if (!pWindow || !pWindow->mpWindowImpl || (pWindow->mpWindowImpl->mpCursor != this)
+ || pWindow->mpWindowImpl->mbInPaint
+ || !pWindow->mpWindowImpl->mpFrameData->mbHasFocus)
+ pWindow = nullptr;
+ }
+
+ if ( !pWindow )
+ return;
+
+ if ( !mpData )
+ {
+ mpData.reset( new ImplCursorData );
+ mpData->mbCurVisible = false;
+ mpData->maTimer.SetInvokeHandler( LINK( this, Cursor, ImplTimerHdl ) );
+ }
+
+ mpData->mpWindow = pWindow;
+ mpData->mnStyle = mnStyle;
+ if ( bDrawDirect || bRestore )
+ ImplDraw();
+
+ if ( !mpWindow && (bDrawDirect || !mpData->maTimer.IsActive()) )
+ {
+ mpData->maTimer.SetTimeout( pWindow->GetSettings().GetStyleSettings().GetCursorBlinkTime() );
+ if ( mpData->maTimer.GetTimeout() != STYLE_CURSOR_NOBLINKTIME )
+ mpData->maTimer.Start();
+ else if ( !mpData->mbCurVisible )
+ ImplDraw();
+ LOKNotify( pWindow, "cursor_invalidate" );
+ LOKNotify( pWindow, "cursor_visible" );
+ }
+}
+
+void vcl::Cursor::LOKNotify( vcl::Window* pWindow, const OUString& rAction )
+{
+ VclPtr<vcl::Window> pParent = pWindow->GetParentWithLOKNotifier();
+ if (!pParent)
+ return;
+
+ assert(pWindow && "Cannot notify without a window");
+ assert(mpData && "Require ImplCursorData");
+ assert(comphelper::LibreOfficeKit::isActive());
+
+ if (comphelper::LibreOfficeKit::isDialogPainting())
+ return;
+
+ const vcl::ILibreOfficeKitNotifier* pNotifier = pParent->GetLOKNotifier();
+ std::vector<vcl::LOKPayloadItem> aItems;
+ if (rAction == "cursor_visible")
+ aItems.emplace_back("visible", mpData->mbCurVisible ? "true" : "false");
+ else if (rAction == "cursor_invalidate")
+ {
+ const tools::Long nX = pWindow->GetOutOffXPixel() + pWindow->LogicToPixel(GetPos()).X() - pParent->GetOutOffXPixel();
+ const tools::Long nY = pWindow->GetOutOffYPixel() + pWindow->LogicToPixel(GetPos()).Y() - pParent->GetOutOffYPixel();
+ Size aSize = pWindow->LogicToPixel(GetSize());
+ if (!aSize.Width())
+ aSize.setWidth( pWindow->GetSettings().GetStyleSettings().GetCursorSize() );
+
+ Point aPos(nX, nY);
+
+ if (pWindow->IsRTLEnabled() && pWindow->GetOutDev() && pParent->GetOutDev()
+ && !pWindow->GetOutDev()->ImplIsAntiparallel())
+ pParent->GetOutDev()->ReMirror(aPos);
+
+ if (!pWindow->IsRTLEnabled() && pWindow->GetOutDev() && pParent->GetOutDev()
+ && pWindow->GetOutDev()->ImplIsAntiparallel())
+ {
+ pWindow->GetOutDev()->ReMirror(aPos);
+ pParent->GetOutDev()->ReMirror(aPos);
+ }
+
+ const tools::Rectangle aRect(aPos, aSize);
+ aItems.emplace_back("rectangle", aRect.toString());
+ }
+
+ pNotifier->notifyWindow(pParent->GetLOKWindowId(), rAction, aItems);
+}
+
+bool vcl::Cursor::ImplDoHide( bool bSuspend )
+{
+ bool bWasCurVisible = false;
+ if ( mpData && mpData->mpWindow )
+ {
+ bWasCurVisible = mpData->mbCurVisible;
+ if ( mpData->mbCurVisible )
+ ImplRestore();
+
+ if ( !bSuspend )
+ {
+ LOKNotify( mpData->mpWindow, "cursor_visible" );
+ mpData->maTimer.Stop();
+ mpData->mpWindow = nullptr;
+ }
+ }
+ return bWasCurVisible;
+}
+
+void vcl::Cursor::ImplShow()
+{
+ ImplDoShow( true/*bDrawDirect*/, false );
+}
+
+void vcl::Cursor::ImplHide()
+{
+ ImplDoHide( false );
+}
+
+void vcl::Cursor::ImplResume( bool bRestore )
+{
+ ImplDoShow( false, bRestore );
+}
+
+bool vcl::Cursor::ImplSuspend()
+{
+ return ImplDoHide( true );
+}
+
+void vcl::Cursor::ImplNew()
+{
+ if ( !(mbVisible && mpData && mpData->mpWindow) )
+ return;
+
+ if ( mpData->mbCurVisible )
+ ImplRestore();
+
+ ImplDraw();
+ if ( !mpWindow )
+ {
+ LOKNotify( mpData->mpWindow, "cursor_invalidate" );
+ if ( mpData->maTimer.GetTimeout() != STYLE_CURSOR_NOBLINKTIME )
+ mpData->maTimer.Start();
+ }
+}
+
+IMPL_LINK_NOARG(vcl::Cursor, ImplTimerHdl, Timer *, void)
+{
+ if ( mpData->mbCurVisible )
+ ImplRestore();
+ else
+ ImplDraw();
+}
+
+vcl::Cursor::Cursor()
+{
+ mpData = nullptr;
+ mpWindow = nullptr;
+ mnOrientation = 0_deg10;
+ mnDirection = CursorDirection::NONE;
+ mnStyle = 0;
+ mbVisible = false;
+}
+
+vcl::Cursor::Cursor( const Cursor& rCursor ) :
+ maSize( rCursor.maSize ),
+ maPos( rCursor.maPos )
+{
+ mpData = nullptr;
+ mpWindow = nullptr;
+ mnOrientation = rCursor.mnOrientation;
+ mnDirection = rCursor.mnDirection;
+ mnStyle = 0;
+ mbVisible = rCursor.mbVisible;
+}
+
+vcl::Cursor::~Cursor()
+{
+ if (mpData && mpData->mbCurVisible)
+ ImplRestore();
+}
+
+void vcl::Cursor::SetStyle( sal_uInt16 nStyle )
+{
+ if ( mnStyle != nStyle )
+ {
+ mnStyle = nStyle;
+ ImplNew();
+ }
+}
+
+void vcl::Cursor::Show()
+{
+ if ( !mbVisible )
+ {
+ mbVisible = true;
+ ImplShow();
+ }
+}
+
+void vcl::Cursor::Hide()
+{
+ if ( mbVisible )
+ {
+ mbVisible = false;
+ ImplHide();
+ }
+}
+
+void vcl::Cursor::SetWindow( vcl::Window* pWindow )
+{
+ if ( mpWindow.get() != pWindow )
+ {
+ mpWindow = pWindow;
+ ImplNew();
+ }
+}
+
+void vcl::Cursor::SetPos( const Point& rPoint )
+{
+ if ( maPos != rPoint )
+ {
+ maPos = rPoint;
+ ImplNew();
+ }
+}
+
+void vcl::Cursor::SetSize( const Size& rSize )
+{
+ if ( maSize != rSize )
+ {
+ maSize = rSize;
+ ImplNew();
+ }
+}
+
+void vcl::Cursor::SetWidth( tools::Long nNewWidth )
+{
+ if ( maSize.Width() != nNewWidth )
+ {
+ maSize.setWidth( nNewWidth );
+ ImplNew();
+ }
+}
+
+void vcl::Cursor::SetOrientation( Degree10 nNewOrientation )
+{
+ if ( mnOrientation != nNewOrientation )
+ {
+ mnOrientation = nNewOrientation;
+ ImplNew();
+ }
+}
+
+void vcl::Cursor::SetDirection( CursorDirection nNewDirection )
+{
+ if ( mnDirection != nNewDirection )
+ {
+ mnDirection = nNewDirection;
+ ImplNew();
+ }
+}
+
+vcl::Cursor& vcl::Cursor::operator=( const vcl::Cursor& rCursor )
+{
+ maPos = rCursor.maPos;
+ maSize = rCursor.maSize;
+ mnOrientation = rCursor.mnOrientation;
+ mnDirection = rCursor.mnDirection;
+ mbVisible = rCursor.mbVisible;
+ ImplNew();
+
+ return *this;
+}
+
+bool vcl::Cursor::operator==( const vcl::Cursor& rCursor ) const
+{
+ return
+ ((maPos == rCursor.maPos) &&
+ (maSize == rCursor.maSize) &&
+ (mnOrientation == rCursor.mnOrientation) &&
+ (mnDirection == rCursor.mnDirection) &&
+ (mbVisible == rCursor.mbVisible))
+ ;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/debug.cxx b/vcl/source/window/debug.cxx
new file mode 100644
index 000000000..346456923
--- /dev/null
+++ b/vcl/source/window/debug.cxx
@@ -0,0 +1,48 @@
+/* -*- 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 <vcl/window.hxx>
+#include <tools/debug.hxx>
+
+#include <window.h>
+
+#ifdef DBG_UTIL
+const char* ImplDbgCheckWindow(const void* pObj)
+{
+ DBG_TESTSOLARMUTEX();
+
+ const vcl::Window* pWindow = static_cast<vcl::Window const*>(pObj);
+
+ if ((pWindow->GetType() < WindowType::FIRST) || (pWindow->GetType() > WindowType::LAST))
+ return "Window data overwrite";
+
+ // check window-chain
+ vcl::Window* pChild = pWindow->mpWindowImpl->mpFirstChild;
+ while (pChild)
+ {
+ if (pChild->mpWindowImpl->mpParent != pWindow)
+ return "Child-Window-Parent wrong";
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+
+ return nullptr;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/debugevent.cxx b/vcl/source/window/debugevent.cxx
new file mode 100644
index 000000000..f9f6978f0
--- /dev/null
+++ b/vcl/source/window/debugevent.cxx
@@ -0,0 +1,271 @@
+/* -*- 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/.
+ */
+
+#include <comphelper/random.hxx>
+#include <o3tl/string_view.hxx>
+#include <rtl/string.hxx>
+#include <sal/log.hxx>
+#include <vcl/keycodes.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/menu.hxx>
+#include <debugevent.hxx>
+#include <window.h>
+#include <salwtype.hxx>
+
+DebugEventInjector::DebugEventInjector( sal_uInt32 nMaxEvents) :
+ Timer("debug event injector")
+ , mnEventsLeft( nMaxEvents )
+{
+ SetTimeout( 1000 /* ms */ );
+ Start();
+}
+
+static double getRandom()
+{
+ return comphelper::rng::uniform_real_distribution();
+}
+
+vcl::Window *DebugEventInjector::ChooseWindow()
+{
+ vcl::Window *pParent;
+
+ if (getRandom() < 0.80)
+ if (vcl::Window * pWindow = Application::GetFocusWindow())
+ return pWindow;
+
+ if (getRandom() > 0.50 ||
+ !(pParent = Application::GetActiveTopWindow()))
+ {
+ // select a top window at random
+ tools::Long nIdx = Application::GetTopWindowCount() * getRandom();
+ pParent = Application::GetTopWindow( nIdx );
+ }
+ assert (pParent != nullptr);
+
+ std::vector< vcl::Window *> aChildren;
+ pParent->CollectChildren( aChildren );
+
+ return aChildren[ aChildren.size() * getRandom() ];
+}
+
+
+static void CollectMenuItemIds( Menu *pMenu, std::vector< SalMenuEvent > &rIds )
+{
+ sal_uInt16 nItems = pMenu->GetItemCount();
+ for (sal_uInt16 i = 0; i < nItems; i++)
+ {
+ if (pMenu->GetItemType( i ) != MenuItemType::SEPARATOR || getRandom() < 0.01)
+ rIds.emplace_back( pMenu->GetItemId( i ), pMenu );
+ PopupMenu *pPopup = pMenu->GetPopupMenu( i );
+ if (pPopup)
+ CollectMenuItemIds( pPopup, rIds );
+ }
+}
+
+void DebugEventInjector::InjectMenuEvent()
+{
+ vcl::Window *pFocus = Application::GetFocusWindow();
+ if (!pFocus)
+ return;
+
+ SystemWindow *pSysWin = pFocus->GetSystemWindow();
+ if (!pSysWin)
+ return;
+
+ MenuBar *pMenuBar = pSysWin->GetMenuBar();
+ if (!pMenuBar)
+ return;
+
+ SalEvent nEvents[] = {
+ SalEvent::MenuCommand,
+ SalEvent::MenuCommand,
+ SalEvent::MenuActivate,
+ SalEvent::MenuDeactivate,
+ SalEvent::MenuHighlight,
+ SalEvent::MenuCommand,
+ SalEvent::MenuCommand,
+ SalEvent::MenuCommand,
+ SalEvent::MenuButtonCommand,
+ SalEvent::MenuButtonCommand,
+ };
+
+ std::vector< SalMenuEvent > aIds;
+ CollectMenuItemIds( pMenuBar, aIds );
+
+ SalEvent nEvent = nEvents[ static_cast<int>(getRandom() * SAL_N_ELEMENTS( nEvents )) ];
+ SalMenuEvent aEvent = aIds[ getRandom() * aIds.size() ];
+ bool bHandled = ImplWindowFrameProc( pSysWin, nEvent, &aEvent);
+
+ SAL_INFO( "vcl.debugevent",
+ "Injected menu event " << aEvent.mpMenu
+ << " (" << aEvent.mnId << ") '"
+ << static_cast<Menu *>(aEvent.mpMenu)->GetItemText( aEvent.mnId ) << "' -> "
+ << bHandled );
+}
+
+static void InitKeyEvent( SalKeyEvent &rKeyEvent )
+{
+ if (getRandom() < 0.01)
+ rKeyEvent.mnRepeat = getRandom() * 20;
+ else
+ rKeyEvent.mnRepeat = 0;
+}
+
+void DebugEventInjector::InjectTextEvent()
+{
+ SalKeyEvent aKeyEvent;
+ vcl::Window *pWindow = ChooseWindow();
+
+ InitKeyEvent( aKeyEvent );
+
+ if (getRandom() < 0.10) // Occasionally a truly random event
+ {
+ aKeyEvent.mnCode = getRandom() * KEY_CODE_MASK;
+ aKeyEvent.mnCharCode = getRandom() * 0xffff;
+ }
+ else
+ {
+ static struct {
+ sal_uInt16 nCodeStart, nCodeEnd;
+ char aCharStart;
+ } const nTextCodes[] = {
+ { KEY_0, KEY_9, '0' },
+ { KEY_A, KEY_Z, 'a' }
+ };
+
+ size_t i = getRandom() * SAL_N_ELEMENTS( nTextCodes );
+ int offset = int( getRandom() * ( nTextCodes[i].nCodeEnd - nTextCodes[i].nCodeStart ) );
+ aKeyEvent.mnCode = nTextCodes[i].nCodeStart + offset;
+ aKeyEvent.mnCharCode = nTextCodes[i].aCharStart + offset;
+// fprintf( stderr, "Char '%c' offset %d into record %d base '%c'\n",
+// aKeyEvent.mnCharCode, offset, (int)i, nTextCodes[i].aCharStart );
+ }
+
+ if( getRandom() < 0.05 ) // modifier
+ aKeyEvent.mnCode |= static_cast<sal_uInt16>( getRandom() * KEY_MODIFIERS_MASK ) & KEY_MODIFIERS_MASK;
+
+ bool bHandled = ImplWindowFrameProc( pWindow, SalEvent::KeyInput, &aKeyEvent);
+
+ SAL_INFO( "vcl.debugevent",
+ "Injected key 0x" << std::hex << static_cast<int>(aKeyEvent.mnCode) << std::dec
+ << " -> " << bHandled
+ << " win " << pWindow );
+
+ ImplWindowFrameProc( pWindow, SalEvent::KeyUp, &aKeyEvent );
+}
+
+/*
+ * The more heuristics we have to inform this the better,
+ * key-bindings, menu entries, allowable entry types etc.
+ */
+void DebugEventInjector::InjectEvent()
+{
+// fprintf( stderr, "%6d - ", (int)mnEventsLeft );
+
+ double nRand = getRandom();
+ if (nRand < 0.30)
+ {
+ int nEvents = getRandom() * 10;
+ for (int i = 0; i < nEvents; i++)
+ InjectTextEvent();
+ }
+ else if (nRand < 0.60)
+ InjectKeyNavEdit();
+ else if (nRand < 0.95)
+ InjectMenuEvent();
+}
+
+void DebugEventInjector::InjectKeyNavEdit()
+{
+ vcl::Window *pWindow = ChooseWindow();
+
+ static struct {
+ double mnProb;
+ sal_uInt16 mnKey;
+ } const nWeights[] = {
+ // edit / escape etc. - 50%
+ { 0.20, KEY_SPACE },
+ { 0.10, KEY_TAB },
+ { 0.07, KEY_RETURN },
+ { 0.05, KEY_DELETE },
+ { 0.05, KEY_BACKSPACE },
+
+ // navigate - 45%
+ { 0.15, KEY_LEFT },
+ { 0.10, KEY_RIGHT },
+ { 0.05, KEY_UP },
+ { 0.05, KEY_DOWN },
+ { 0.05, KEY_PAGEUP },
+ { 0.05, KEY_PAGEDOWN },
+
+ // other
+ { 0.01, KEY_INSERT },
+ { 0.02, KEY_HOME },
+ { 0.02, KEY_END },
+ };
+
+ double d = 0.0, nRand = getRandom();
+ sal_uInt16 nKey = KEY_SPACE;
+ for (auto & rWeight : nWeights)
+ {
+ d += rWeight.mnProb;
+ assert (d < 1.01);
+ if ( nRand < d )
+ {
+ nKey = rWeight.mnKey;
+ break;
+ }
+ }
+
+ SalKeyEvent aKeyEvent;
+ InitKeyEvent( aKeyEvent );
+ aKeyEvent.mnCode = nKey;
+
+ if (getRandom() < 0.15) // modifier
+ aKeyEvent.mnCode |= static_cast<sal_uInt16>(getRandom() * KEY_MODIFIERS_MASK) & KEY_MODIFIERS_MASK;
+
+ aKeyEvent.mnCharCode = 0x0; // hopefully unused.
+
+ bool bHandled = ImplWindowFrameProc( pWindow, SalEvent::KeyInput, &aKeyEvent );
+
+ SAL_INFO( "vcl.debugevent",
+ "Injected edit / move key 0x" << std::hex << static_cast<int>(aKeyEvent.mnCode) << std::dec
+ << " -> " << bHandled
+ << " win " << pWindow );
+ ImplWindowFrameProc( pWindow, SalEvent::KeyUp, &aKeyEvent );
+}
+
+void DebugEventInjector::Invoke()
+{
+ InjectEvent();
+ mnEventsLeft--;
+ if (mnEventsLeft > 0)
+ {
+ SetTimeout( 1 );
+ Start();
+ }
+ else
+ Application::Quit();
+}
+
+DebugEventInjector *DebugEventInjector::getCreate()
+{
+ sal_uInt32 nEvents;
+ const char *pEvents = getenv("VCL_EVENT_INJECTION");
+ if (!pEvents)
+ return nullptr;
+ nEvents = o3tl::toUInt32( pEvents );
+ if (nEvents > 0)
+ return new DebugEventInjector( nEvents );
+ else
+ return nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/decoview.cxx b/vcl/source/window/decoview.cxx
new file mode 100644
index 000000000..71481200d
--- /dev/null
+++ b/vcl/source/window/decoview.cxx
@@ -0,0 +1,1046 @@
+/* -*- 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 <vcl/settings.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/window.hxx>
+#include <vcl/ctrl.hxx>
+
+constexpr auto BUTTON_DRAW_FLATTEST = DrawButtonFlags::Flat |
+ DrawButtonFlags::Pressed |
+ DrawButtonFlags::Checked |
+ DrawButtonFlags::Highlight;
+
+namespace {
+
+tools::Long AdjustRectToSquare( tools::Rectangle &rRect )
+{
+ const tools::Long nWidth = rRect.GetWidth();
+ const tools::Long nHeight = rRect.GetHeight();
+ tools::Long nSide = std::min( nWidth, nHeight );
+
+ if ( nSide && !(nSide & 1) )
+ {
+ // we prefer an odd size
+ --nSide;
+ }
+
+ // Make the rectangle a square
+ rRect.SetSize( Size( nSide, nSide ) );
+
+ // and place it at the center of the original rectangle
+ rRect.Move( (nWidth-nSide)/2, (nHeight-nSide)/2 );
+
+ return nSide;
+}
+
+void ImplDrawSymbol( OutputDevice* pDev, tools::Rectangle nRect, const SymbolType eType )
+{
+ const tools::Long nSide = AdjustRectToSquare( nRect );
+
+ if ( !nSide ) return;
+ if ( nSide==1 )
+ {
+ pDev->DrawPixel( Point( nRect.Left(), nRect.Top() ) );
+ return;
+ }
+
+ // Precalculate some values
+ const tools::Long n2 = nSide/2;
+ const tools::Long n4 = (n2+1)/2;
+ const tools::Long n8 = (n4+1)/2;
+ const tools::Long n16 = (n8+1)/2;
+ const Point aCenter = nRect.Center();
+
+ switch ( eType )
+ {
+ case SymbolType::ARROW_UP:
+ {
+ tools::Polygon arrow(7);
+ arrow.SetPoint( Point( aCenter.X(), nRect.Top()), 0 );
+ arrow.SetPoint( Point( aCenter.X() - n2, nRect.Top() + n2 ), 1 );
+ arrow.SetPoint( Point( aCenter.X() - n8, nRect.Top() + n2 ), 2 );
+ arrow.SetPoint( Point( aCenter.X() - n8, nRect.Bottom()), 3 );
+ arrow.SetPoint( Point( aCenter.X() + n8, nRect.Bottom()), 4 );
+ arrow.SetPoint( Point( aCenter.X() + n8, nRect.Top() + n2 ), 5 );
+ arrow.SetPoint( Point( aCenter.X() + n2, nRect.Top() + n2 ), 6 );
+ pDev->Push(vcl::PushFlags::LINECOLOR);
+ pDev->SetLineColor();
+ pDev->DrawPolygon( arrow );
+ pDev->Pop();
+ break;
+ }
+
+ case SymbolType::ARROW_DOWN:
+ {
+ tools::Polygon arrow(7);
+ arrow.SetPoint( Point( aCenter.X(), nRect.Bottom()), 0 );
+ arrow.SetPoint( Point( aCenter.X() - n2, nRect.Bottom() - n2 ), 1 );
+ arrow.SetPoint( Point( aCenter.X() - n8, nRect.Bottom() - n2 ), 2 );
+ arrow.SetPoint( Point( aCenter.X() - n8, nRect.Top()), 3 );
+ arrow.SetPoint( Point( aCenter.X() + n8, nRect.Top()), 4 );
+ arrow.SetPoint( Point( aCenter.X() + n8, nRect.Bottom() - n2 ), 5 );
+ arrow.SetPoint( Point( aCenter.X() + n2, nRect.Bottom() - n2 ), 6 );
+ pDev->Push(vcl::PushFlags::LINECOLOR);
+ pDev->SetLineColor();
+ pDev->DrawPolygon( arrow );
+ pDev->Pop();
+ break;
+ }
+
+ case SymbolType::ARROW_LEFT:
+ {
+ tools::Polygon arrow(7);
+ arrow.SetPoint( Point( nRect.Left(), aCenter.Y()), 0 );
+ arrow.SetPoint( Point( nRect.Left() + n2, aCenter.Y() - n2 ), 1 );
+ arrow.SetPoint( Point( nRect.Left() + n2, aCenter.Y() - n8 ), 2 );
+ arrow.SetPoint( Point( nRect.Right(), aCenter.Y() - n8 ), 3 );
+ arrow.SetPoint( Point( nRect.Right(), aCenter.Y() + n8 ), 4 );
+ arrow.SetPoint( Point( nRect.Left() + n2, aCenter.Y() + n8 ), 5 );
+ arrow.SetPoint( Point( nRect.Left() + n2, aCenter.Y() + n2 ), 6 );
+ pDev->Push(vcl::PushFlags::LINECOLOR);
+ pDev->SetLineColor();
+ pDev->DrawPolygon( arrow );
+ pDev->Pop();
+ break;
+ }
+
+ case SymbolType::ARROW_RIGHT:
+ {
+ tools::Polygon arrow(7);
+ arrow.SetPoint( Point( nRect.Right(), aCenter.Y()), 0 );
+ arrow.SetPoint( Point( nRect.Right() - n2, aCenter.Y() - n2 ), 1 );
+ arrow.SetPoint( Point( nRect.Right() - n2, aCenter.Y() - n8 ), 2 );
+ arrow.SetPoint( Point( nRect.Left(), aCenter.Y() - n8 ), 3 );
+ arrow.SetPoint( Point( nRect.Left(), aCenter.Y() + n8 ), 4 );
+ arrow.SetPoint( Point( nRect.Right() - n2, aCenter.Y() + n8 ), 5 );
+ arrow.SetPoint( Point( nRect.Right() - n2, aCenter.Y() + n2 ), 6 );
+ pDev->Push(vcl::PushFlags::LINECOLOR);
+ pDev->SetLineColor();
+ pDev->DrawPolygon( arrow );
+ pDev->Pop();
+ break;
+ }
+
+ case SymbolType::SPIN_UP:
+ {
+ tools::Polygon triangle( 3 );
+ triangle.SetPoint( Point( aCenter.X(), nRect.Top() + n4 ), 0 );
+ triangle.SetPoint( Point( aCenter.X() - n2, nRect.Top() + n4 + n2 ), 1 );
+ triangle.SetPoint( Point( aCenter.X() + n2, nRect.Top() + n4 + n2 ), 2 );
+ pDev->Push(vcl::PushFlags::LINECOLOR);
+ pDev->SetLineColor();
+ pDev->DrawPolygon( triangle );
+ pDev->Pop();
+ break;
+ }
+
+ case SymbolType::SPIN_DOWN:
+ {
+ tools::Polygon triangle( 3 );
+ triangle.SetPoint( Point( aCenter.X(), nRect.Bottom() - n4 ), 0 );
+ triangle.SetPoint( Point( aCenter.X() - n2, nRect.Bottom() - n4 - n2 ), 1 );
+ triangle.SetPoint( Point( aCenter.X() + n2, nRect.Bottom() - n4 - n2 ), 2 );
+ pDev->Push(vcl::PushFlags::LINECOLOR);
+ pDev->SetLineColor();
+ pDev->DrawPolygon( triangle );
+ pDev->Pop();
+ break;
+ }
+
+ case SymbolType::SPIN_LEFT:
+ case SymbolType::FIRST:
+ case SymbolType::PREV:
+ {
+ nRect.AdjustLeft(n4 );
+ if ( eType==SymbolType::FIRST )
+ {
+ pDev->DrawLine( Point( nRect.Left(), nRect.Top() ),
+ Point( nRect.Left(), nRect.Bottom() ) );
+ nRect.AdjustLeft( 1 );
+ }
+
+ tools::Polygon aTriangle(3);
+ aTriangle.SetPoint(Point(nRect.Left() + n2, aCenter.Y() - n2), 0);
+ aTriangle.SetPoint(Point(nRect.Left(), aCenter.Y()), 1);
+ aTriangle.SetPoint(Point(nRect.Left() + n2, aCenter.Y() + n2), 2);
+
+ pDev->Push(vcl::PushFlags::LINECOLOR);
+ pDev->SetLineColor();
+ pDev->DrawPolygon(aTriangle);
+ pDev->Pop();
+
+ break;
+ }
+
+ case SymbolType::SPIN_RIGHT:
+ case SymbolType::LAST:
+ case SymbolType::NEXT:
+ case SymbolType::PLAY:
+ {
+ nRect.AdjustRight( -n4 );
+ if ( eType==SymbolType::LAST )
+ {
+ pDev->DrawLine( Point( nRect.Right(), nRect.Top() ),
+ Point( nRect.Right(), nRect.Bottom() ) );
+ nRect.AdjustRight( -1 );
+ }
+
+ tools::Polygon aTriangle(3);
+ aTriangle.SetPoint(Point(nRect.Right() - n2, aCenter.Y() - n2), 0);
+ aTriangle.SetPoint(Point(nRect.Right(), aCenter.Y()), 1);
+ aTriangle.SetPoint(Point(nRect.Right() - n2, aCenter.Y() + n2), 2);
+
+ pDev->Push(vcl::PushFlags::LINECOLOR);
+ pDev->SetLineColor();
+ pDev->DrawPolygon(aTriangle);
+ pDev->Pop();
+ break;
+ }
+
+ case SymbolType::PAGEUP:
+ {
+ tools::Polygon triangle( 3 );
+ triangle.SetPoint( Point( aCenter.X(), nRect.Top()), 0 );
+ triangle.SetPoint( Point( aCenter.X() - n2, nRect.Top() + n2 ), 1 );
+ triangle.SetPoint( Point( aCenter.X() + n2, nRect.Top() + n2 ), 2 );
+ pDev->Push(vcl::PushFlags::LINECOLOR);
+ pDev->SetLineColor();
+ pDev->DrawPolygon( triangle );
+ triangle.Move( 0, n2 );
+ pDev->DrawPolygon( triangle );
+ pDev->Pop();
+ break;
+ }
+
+ case SymbolType::PAGEDOWN:
+ {
+ tools::Polygon triangle( 3 );
+ triangle.SetPoint( Point( aCenter.X(), nRect.Bottom()), 0 );
+ triangle.SetPoint( Point( aCenter.X() - n2, nRect.Bottom() - n2 ), 1 );
+ triangle.SetPoint( Point( aCenter.X() + n2, nRect.Bottom() - n2 ), 2 );
+ pDev->Push(vcl::PushFlags::LINECOLOR);
+ pDev->SetLineColor();
+ pDev->DrawPolygon( triangle );
+ triangle.Move( 0, -n2 );
+ pDev->DrawPolygon( triangle );
+ pDev->Pop();
+ break;
+ }
+
+ case SymbolType::RADIOCHECKMARK:
+ {
+ pDev->DrawEllipse(nRect);
+ break;
+ }
+
+ case SymbolType::STOP:
+ pDev->DrawRect( nRect );
+ break;
+
+ case SymbolType::CLOSE:
+ {
+ const tools::Long diff = std::max<tools::Long>( 0, n8 - 1 );
+ tools::Polygon cross( 16 );
+ cross.SetPoint( Point( nRect.Left(), nRect.Top()), 0 );
+ cross.SetPoint( Point( nRect.Left(), nRect.Top() + diff ), 1 );
+ cross.SetPoint( Point( aCenter.X() - diff, aCenter.Y()), 2 );
+ cross.SetPoint( Point( nRect.Left(), nRect.Bottom() - diff ), 3 );
+ cross.SetPoint( Point( nRect.Left(), nRect.Bottom()), 4 );
+ cross.SetPoint( Point( nRect.Left() + diff, nRect.Bottom()), 5 );
+ cross.SetPoint( Point( aCenter.X(), aCenter.Y() + diff ), 6 );
+ cross.SetPoint( Point( nRect.Right() - diff, nRect.Bottom()), 7 );
+ cross.SetPoint( Point( nRect.Right(), nRect.Bottom()), 8 );
+ cross.SetPoint( Point( nRect.Right(), nRect.Bottom() - diff ), 9 );
+ cross.SetPoint( Point( aCenter.X() + diff, aCenter.Y()), 10 );
+ cross.SetPoint( Point( nRect.Right(), nRect.Top() + diff ), 11 );
+ cross.SetPoint( Point( nRect.Right(), nRect.Top()), 12 );
+ cross.SetPoint( Point( nRect.Right() - diff, nRect.Top()), 13 );
+ cross.SetPoint( Point( aCenter.X(), aCenter.Y() - diff ), 14 );
+ cross.SetPoint( Point( nRect.Left() + diff, nRect.Top()), 15 );
+ pDev->DrawPolygon( cross );
+ break;
+ }
+
+ case SymbolType::CHECKMARK:
+ {
+ tools::Long n3 = nSide/3;
+ nRect.AdjustTop( -(n3/2) );
+ nRect.AdjustBottom( -(n3/2) );
+ tools::Polygon checkmark(6);
+ // #106953# never mirror checkmarks
+ if ( pDev->HasMirroredGraphics() && pDev->IsRTLEnabled() )
+ {
+ // Draw a mirrored checkmark so that it looks "normal" in a
+ // mirrored graphics device (double mirroring!)
+ checkmark.SetPoint( Point( nRect.Right(), nRect.Bottom()-n3 ), 0 );
+ checkmark.SetPoint( Point( nRect.Right()-n3, nRect.Bottom()), 1 );
+ checkmark.SetPoint( Point( nRect.Left(), nRect.Top()+n3 ), 2 );
+ checkmark.SetPoint( Point( nRect.Left(), nRect.Top()+n3 + 1 ), 3 );
+ checkmark.SetPoint( Point( nRect.Right()-n3, nRect.Bottom() + 1 ), 4 );
+ checkmark.SetPoint( Point( nRect.Right(), nRect.Bottom()-n3 + 1 ), 5 );
+ }
+ else
+ {
+ checkmark.SetPoint( Point( nRect.Left(), nRect.Bottom()-n3 ), 0 );
+ checkmark.SetPoint( Point( nRect.Left()+n3, nRect.Bottom()), 1 );
+ checkmark.SetPoint( Point( nRect.Right(), nRect.Top()+n3 ), 2 );
+ checkmark.SetPoint( Point( nRect.Right(), nRect.Top()+n3 + 1 ), 3 );
+ checkmark.SetPoint( Point( nRect.Left()+n3, nRect.Bottom() + 1 ), 4 );
+ checkmark.SetPoint( Point( nRect.Left(), nRect.Bottom()-n3 + 1 ), 5 );
+ }
+ pDev->DrawPolygon( checkmark );
+ }
+ break;
+
+ case SymbolType::FLOAT:
+ nRect.AdjustRight( -n4 );
+ nRect.AdjustTop(n4+1 );
+ pDev->DrawRect( tools::Rectangle( nRect.Left(), nRect.Top(),
+ nRect.Right(), nRect.Top()+n8 ) );
+ pDev->DrawLine( Point( nRect.Left(), nRect.Top()+n8 ),
+ Point( nRect.Left(), nRect.Bottom() ) );
+ pDev->DrawLine( Point( nRect.Left(), nRect.Bottom() ),
+ Point( nRect.Right(), nRect.Bottom() ) );
+ pDev->DrawLine( Point( nRect.Right(), nRect.Top()+n8 ),
+ Point( nRect.Right(), nRect.Bottom() ) );
+ nRect.AdjustRight(n4 );
+ nRect.AdjustTop( -(n4+1) );
+ nRect.AdjustLeft(n4 );
+ nRect.AdjustBottom( -(n4+1) );
+ pDev->DrawRect( tools::Rectangle( nRect.Left(), nRect.Top(),
+ nRect.Right(), nRect.Top()+n8 ) );
+ pDev->DrawLine( Point( nRect.Left(), nRect.Top()+n8 ),
+ Point( nRect.Left(), nRect.Bottom() ) );
+ pDev->DrawLine( Point( nRect.Left(), nRect.Bottom() ),
+ Point( nRect.Right(), nRect.Bottom() ) );
+ pDev->DrawLine( Point( nRect.Right(), nRect.Top()+n8 ),
+ Point( nRect.Right(), nRect.Bottom() ) );
+ break;
+
+ case SymbolType::DOCK:
+ pDev->DrawLine( Point( nRect.Left(), nRect.Top() ),
+ Point( nRect.Right(), nRect.Top() ) );
+ pDev->DrawLine( Point( nRect.Left(), nRect.Top() ),
+ Point( nRect.Left(), nRect.Bottom() ) );
+ pDev->DrawLine( Point( nRect.Left(), nRect.Bottom() ),
+ Point( nRect.Right(), nRect.Bottom() ) );
+ pDev->DrawLine( Point( nRect.Right(), nRect.Top() ),
+ Point( nRect.Right(), nRect.Bottom() ) );
+ break;
+
+ case SymbolType::HIDE:
+ pDev->DrawRect( tools::Rectangle( nRect.Left()+n8, nRect.Bottom()-n8,
+ nRect.Right()-n8, nRect.Bottom() ) );
+ break;
+
+ case SymbolType::PLUS:
+ pDev->DrawRect( tools::Rectangle( nRect.Left(), aCenter.Y()-n16,
+ nRect.Right(), aCenter.Y()+n16 ) );
+ pDev->DrawRect( tools::Rectangle( aCenter.X()-n16, nRect.Top(),
+ aCenter.X()+n16, nRect.Bottom() ) );
+ break;
+ case SymbolType::DONTKNOW:
+ case SymbolType::IMAGE:
+ case SymbolType::HELP: break;
+ }
+}
+
+void ImplDrawDPILineRect( OutputDevice *const pDev, tools::Rectangle& rRect,
+ const Color *const pColor, const bool bRound = false )
+{
+ tools::Long nLineWidth = pDev->GetDPIX()/300;
+ tools::Long nLineHeight = pDev->GetDPIY()/300;
+ if ( !nLineWidth )
+ nLineWidth = 1;
+ if ( !nLineHeight )
+ nLineHeight = 1;
+
+ if ( pColor )
+ {
+ if ( (nLineWidth == 1) && (nLineHeight == 1) )
+ {
+ pDev->SetLineColor( *pColor );
+ if( bRound )
+ {
+ pDev->DrawLine( Point( rRect.Left()+1, rRect.Top()), Point( rRect.Right()-1, rRect.Top()) );
+ pDev->DrawLine( Point( rRect.Left()+1, rRect.Bottom()), Point( rRect.Right()-1, rRect.Bottom()) );
+ pDev->DrawLine( Point( rRect.Left(), rRect.Top()+1), Point( rRect.Left(), rRect.Bottom()-1) );
+ pDev->DrawLine( Point( rRect.Right(), rRect.Top()+1), Point( rRect.Right(), rRect.Bottom()-1) );
+ }
+ else
+ {
+ pDev->SetFillColor();
+ pDev->DrawRect( rRect );
+ }
+ }
+ else
+ {
+ const tools::Long nWidth = rRect.GetWidth();
+ const tools::Long nHeight = rRect.GetHeight();
+ pDev->SetLineColor();
+ pDev->SetFillColor( *pColor );
+ pDev->DrawRect( tools::Rectangle( rRect.TopLeft(), Size( nWidth, nLineHeight ) ) );
+ pDev->DrawRect( tools::Rectangle( rRect.TopLeft(), Size( nLineWidth, nHeight ) ) );
+ pDev->DrawRect( tools::Rectangle( Point( rRect.Left(), rRect.Bottom()-nLineHeight ),
+ Size( nWidth, nLineHeight ) ) );
+ pDev->DrawRect( tools::Rectangle( Point( rRect.Right()-nLineWidth, rRect.Top() ),
+ Size( nLineWidth, nHeight ) ) );
+ }
+ }
+
+ rRect.AdjustLeft(nLineWidth );
+ rRect.AdjustTop(nLineHeight );
+ rRect.AdjustRight( -nLineWidth );
+ rRect.AdjustBottom( -nLineHeight );
+}
+
+void ImplDraw2ColorFrame( OutputDevice *const pDev, tools::Rectangle& rRect,
+ const Color& rLeftTopColor, const Color& rRightBottomColor )
+{
+ pDev->SetLineColor( rLeftTopColor );
+ pDev->DrawLine( rRect.TopLeft(), rRect.BottomLeft() );
+ pDev->DrawLine( rRect.TopLeft(), rRect.TopRight() );
+ pDev->SetLineColor( rRightBottomColor );
+ pDev->DrawLine( rRect.BottomLeft(), rRect.BottomRight() );
+ pDev->DrawLine( rRect.TopRight(), rRect.BottomRight() );
+
+ // reduce drawing area
+ rRect.AdjustLeft( 1 );
+ rRect.AdjustTop( 1 );
+ rRect.AdjustRight( -1 );
+ rRect.AdjustBottom( -1 );
+}
+
+void ImplDrawButton( OutputDevice *const pDev, tools::Rectangle aFillRect,
+ const DrawButtonFlags nStyle )
+{
+ const StyleSettings& rStyleSettings = pDev->GetSettings().GetStyleSettings();
+
+ if ( (nStyle & DrawButtonFlags::Mono) ||
+ (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) )
+ {
+ const Color aBlackColor(COL_BLACK);
+
+ if ( nStyle & DrawButtonFlags::Default )
+ {
+ // default selection shows a wider border
+ ImplDrawDPILineRect( pDev, aFillRect, &aBlackColor );
+ }
+
+ ImplDrawDPILineRect( pDev, aFillRect, &aBlackColor );
+
+ Size aBrdSize(pDev->GetButtonBorderSize());
+
+ pDev->SetLineColor();
+ pDev->SetFillColor( aBlackColor );
+ const tools::Rectangle aOrigFillRect(aFillRect);
+ if ( nStyle & (DrawButtonFlags::Pressed | DrawButtonFlags::Checked) )
+ {
+ // shrink fill rect
+ aFillRect.AdjustLeft(aBrdSize.Width() );
+ aFillRect.AdjustTop(aBrdSize.Height() );
+ // draw top and left borders (aOrigFillRect-aFillRect)
+ pDev->DrawRect( tools::Rectangle( aOrigFillRect.Left(), aOrigFillRect.Top(),
+ aOrigFillRect.Right(), aFillRect.Top()-1 ) );
+ pDev->DrawRect( tools::Rectangle( aOrigFillRect.Left(), aOrigFillRect.Top(),
+ aFillRect.Left()-1, aOrigFillRect.Bottom() ) );
+ }
+ else
+ {
+ // shrink fill rect
+ aFillRect.AdjustRight( -(aBrdSize.Width()) );
+ aFillRect.AdjustBottom( -(aBrdSize.Height()) );
+ // draw bottom and right borders (aOrigFillRect-aFillRect)
+ pDev->DrawRect( tools::Rectangle( aOrigFillRect.Left(), aFillRect.Bottom()+1,
+ aOrigFillRect.Right(), aOrigFillRect.Bottom() ) );
+ pDev->DrawRect( tools::Rectangle( aFillRect.Right()+1, aOrigFillRect.Top(),
+ aOrigFillRect.Right(), aOrigFillRect.Bottom() ) );
+ }
+
+ // Hack: in monochrome mode on printers we like to have grey buttons
+ pDev->SetFillColor(pDev->GetMonochromeButtonColor());
+ pDev->DrawRect( aFillRect );
+ }
+ else
+ {
+ if ( nStyle & DrawButtonFlags::Default )
+ {
+ const Color aDefBtnColor = rStyleSettings.GetDarkShadowColor();
+ ImplDrawDPILineRect( pDev, aFillRect, &aDefBtnColor );
+ }
+
+ if ( nStyle & DrawButtonFlags::NoLeftLightBorder )
+ {
+ pDev->SetLineColor( rStyleSettings.GetLightBorderColor() );
+ pDev->DrawLine( Point( aFillRect.Left(), aFillRect.Top() ),
+ Point( aFillRect.Left(), aFillRect.Bottom() ) );
+ aFillRect.AdjustLeft( 1 );
+ }
+
+ Color aColor1;
+ Color aColor2;
+ if ( nStyle & (DrawButtonFlags::Pressed | DrawButtonFlags::Checked) )
+ {
+ aColor1 = rStyleSettings.GetDarkShadowColor();
+ aColor2 = rStyleSettings.GetLightColor();
+ }
+ else
+ {
+ if ( nStyle & DrawButtonFlags::NoLightBorder )
+ aColor1 = rStyleSettings.GetLightBorderColor();
+ else
+ aColor1 = rStyleSettings.GetLightColor();
+ if ( (nStyle & BUTTON_DRAW_FLATTEST) == DrawButtonFlags::Flat )
+ aColor2 = rStyleSettings.GetShadowColor();
+ else
+ aColor2 = rStyleSettings.GetDarkShadowColor();
+ }
+
+ ImplDraw2ColorFrame( pDev, aFillRect, aColor1, aColor2 );
+
+ if ( (nStyle & BUTTON_DRAW_FLATTEST) != DrawButtonFlags::Flat )
+ {
+ if ( nStyle & (DrawButtonFlags::Pressed | DrawButtonFlags::Checked) )
+ {
+ aColor1 = rStyleSettings.GetShadowColor();
+ aColor2 = rStyleSettings.GetLightBorderColor();
+ }
+ else
+ {
+ if ( nStyle & DrawButtonFlags::NoLightBorder )
+ aColor1 = rStyleSettings.GetLightColor();
+ else
+ aColor1 = rStyleSettings.GetLightBorderColor();
+ aColor2 = rStyleSettings.GetShadowColor();
+ }
+ ImplDraw2ColorFrame( pDev, aFillRect, aColor1, aColor2 );
+ }
+
+ pDev->SetLineColor();
+ if ( nStyle & (DrawButtonFlags::Checked | DrawButtonFlags::DontKnow) )
+ pDev->SetFillColor( rStyleSettings.GetCheckedColor() );
+ else
+ pDev->SetFillColor( rStyleSettings.GetFaceColor() );
+ pDev->DrawRect( aFillRect );
+ }
+}
+
+void ImplDrawFrame( OutputDevice *const pDev, tools::Rectangle& rRect,
+ const StyleSettings& rStyleSettings, DrawFrameStyle nStyle, DrawFrameFlags nFlags )
+{
+ vcl::Window * pWin = pDev->GetOwnerWindow();
+
+ const bool bMenuStyle(nFlags & DrawFrameFlags::Menu);
+
+ // UseFlatBorders disables 3D style for all frames except menus
+ // menus may use different border colors (eg on XP)
+ // normal frames will be drawn using the shadow color
+ // whereas window frame borders will use black
+ bool bFlatBorders = !bMenuStyle && rStyleSettings.GetUseFlatBorders();
+
+ // no flat borders for standard VCL controls (ie formcontrols that keep their classic look)
+ // will not affect frame windows (like dropdowns)
+ if( bFlatBorders && pWin && pWin->GetType() == WindowType::BORDERWINDOW && (pWin != pWin->ImplGetFrameWindow()) )
+ {
+ // check for formcontrol, i.e., a control without NWF enabled
+ Control *const pControl = dynamic_cast< Control* >( pWin->GetWindow( GetWindowType::Client ) );
+ if( !pControl || !pControl->IsNativeWidgetEnabled() )
+ bFlatBorders = false;
+ }
+
+ const bool bNoDraw(nFlags & DrawFrameFlags::NoDraw);
+
+ if ( (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) ||
+ (pDev->GetOutDevType() == OUTDEV_PRINTER) ||
+ bFlatBorders )
+ nFlags |= DrawFrameFlags::Mono;
+
+ if( nStyle != DrawFrameStyle::NWF &&
+ pWin && pWin->IsNativeControlSupported(ControlType::Frame, ControlPart::Border) )
+ {
+ tools::Long nControlFlags = static_cast<tools::Long>(nStyle);
+ nControlFlags |= static_cast<tools::Long>(nFlags);
+ nControlFlags |= static_cast<tools::Long>(pWin->GetType() == WindowType::BORDERWINDOW ?
+ DrawFrameFlags::BorderWindowBorder : DrawFrameFlags::NONE);
+ ImplControlValue aControlValue( nControlFlags );
+
+ tools::Rectangle aBound, aContent;
+ tools::Rectangle aNatRgn( rRect );
+ if( pWin->GetNativeControlRegion(ControlType::Frame, ControlPart::Border,
+ aNatRgn, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ // if bNoDraw is true then don't call the drawing routine
+ // but just update the target rectangle
+ if( bNoDraw ||
+ pWin->GetOutDev()->DrawNativeControl( ControlType::Frame, ControlPart::Border, aBound, ControlState::ENABLED,
+ aControlValue, OUString()) )
+ {
+ rRect = aContent;
+ return;
+ }
+ }
+ }
+
+ if ( nFlags & DrawFrameFlags::Mono )
+ {
+ // no round corners for window frame borders
+ const bool bRound = bFlatBorders && !(nFlags & DrawFrameFlags::WindowBorder);
+
+ if ( bNoDraw )
+ {
+ ImplDrawDPILineRect( pDev, rRect, nullptr, bRound );
+ }
+ else
+ {
+ Color aColor = bRound ? rStyleSettings.GetShadowColor()
+ : pDev->GetSettings().GetStyleSettings().GetMonoColor();
+ // when the MonoColor wasn't set, check face color
+ if (
+ (bRound && aColor.IsDark()) ||
+ (
+ (aColor == COL_BLACK) &&
+ pDev->GetSettings().GetStyleSettings().GetFaceColor().IsDark()
+ )
+ )
+ {
+ aColor = COL_WHITE;
+ }
+ ImplDrawDPILineRect( pDev, rRect, &aColor, bRound );
+ }
+ }
+ else
+ {
+ if ( bNoDraw )
+ {
+ switch ( nStyle )
+ {
+ case DrawFrameStyle::In:
+ case DrawFrameStyle::Out:
+ rRect.AdjustLeft( 1 );
+ rRect.AdjustTop( 1 );
+ rRect.AdjustRight( -1 );
+ rRect.AdjustBottom( -1 );
+ break;
+
+ case DrawFrameStyle::Group:
+ case DrawFrameStyle::DoubleIn:
+ case DrawFrameStyle::DoubleOut:
+ rRect.AdjustLeft(2 );
+ rRect.AdjustTop(2 );
+ rRect.AdjustRight( -2 );
+ rRect.AdjustBottom( -2 );
+ break;
+
+ case DrawFrameStyle::NWF:
+ // enough space for the native rendering
+ rRect.AdjustLeft(4 );
+ rRect.AdjustTop(4 );
+ rRect.AdjustRight( -4 );
+ rRect.AdjustBottom( -4 );
+ break;
+ default: break;
+ }
+ }
+ else
+ {
+ switch ( nStyle )
+ {
+ case DrawFrameStyle::Group:
+ pDev->SetFillColor();
+ pDev->SetLineColor( rStyleSettings.GetLightColor() );
+ pDev->DrawRect( tools::Rectangle( rRect.Left()+1, rRect.Top()+1,
+ rRect.Right(), rRect.Bottom() ) );
+ pDev->SetLineColor( rStyleSettings.GetShadowColor() );
+ pDev->DrawRect( tools::Rectangle( rRect.Left(), rRect.Top(),
+ rRect.Right()-1, rRect.Bottom()-1 ) );
+
+ // adjust target rectangle
+ rRect.AdjustLeft(2 );
+ rRect.AdjustTop(2 );
+ rRect.AdjustRight( -2 );
+ rRect.AdjustBottom( -2 );
+ break;
+
+ case DrawFrameStyle::In:
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetShadowColor(),
+ rStyleSettings.GetLightColor() );
+ break;
+
+ case DrawFrameStyle::Out:
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetLightColor(),
+ rStyleSettings.GetShadowColor() );
+ break;
+
+ case DrawFrameStyle::DoubleIn:
+ if( bFlatBorders )
+ {
+ // no 3d effect
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetShadowColor(),
+ rStyleSettings.GetShadowColor() );
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetFaceColor(),
+ rStyleSettings.GetFaceColor() );
+ }
+ else
+ {
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetShadowColor(),
+ rStyleSettings.GetLightColor() );
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetDarkShadowColor(),
+ rStyleSettings.GetLightBorderColor() );
+ }
+ break;
+
+ case DrawFrameStyle::DoubleOut:
+ if( bMenuStyle )
+ {
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetMenuBorderColor(),
+ rStyleSettings.GetDarkShadowColor() );
+ if ( !rStyleSettings.GetUseFlatMenus() )
+ {
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetLightColor(),
+ rStyleSettings.GetShadowColor() );
+ }
+ }
+ else
+ {
+ ImplDraw2ColorFrame( pDev, rRect,
+ bFlatBorders ? // no 3d effect
+ rStyleSettings.GetDarkShadowColor() :
+ rStyleSettings.GetLightBorderColor(),
+ rStyleSettings.GetDarkShadowColor() );
+ ImplDraw2ColorFrame( pDev, rRect,
+ rStyleSettings.GetLightColor(),
+ rStyleSettings.GetShadowColor() );
+ }
+ break;
+
+ case DrawFrameStyle::NWF:
+ // no rendering, just enough space for the native rendering
+ rRect.AdjustLeft(4 );
+ rRect.AdjustTop(4 );
+ rRect.AdjustRight( -4 );
+ rRect.AdjustBottom( -4 );
+ break;
+ default: break;
+ }
+ }
+ }
+}
+
+} // end anonymous namespace
+
+DecorationView::DecorationView(OutputDevice* pOutDev) :
+ mpOutDev(pOutDev)
+{}
+
+void DecorationView::DrawSymbol( const tools::Rectangle& rRect, SymbolType eType,
+ const Color& rColor, DrawSymbolFlags nStyle )
+{
+ const StyleSettings& rStyleSettings = mpOutDev->GetSettings().GetStyleSettings();
+ const tools::Rectangle aRect = mpOutDev->LogicToPixel( rRect );
+ const Color aOldLineColor = mpOutDev->GetLineColor();
+ const Color aOldFillColor = mpOutDev->GetFillColor();
+ const bool bOldMapMode = mpOutDev->IsMapModeEnabled();
+ Color nColor(rColor);
+ mpOutDev->EnableMapMode( false );
+
+ if ( (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) ||
+ (mpOutDev->GetOutDevType() == OUTDEV_PRINTER) )
+ nStyle |= DrawSymbolFlags::Mono;
+
+ if ( nStyle & DrawSymbolFlags::Mono )
+ {
+ // Monochrome: set color to black if enabled, to gray if disabled
+ nColor = ( nStyle & DrawSymbolFlags::Disable ) ? COL_GRAY : COL_BLACK;
+ }
+ else
+ {
+ if ( nStyle & DrawSymbolFlags::Disable )
+ {
+ // Draw shifted and brighter symbol for embossed look
+ mpOutDev->SetLineColor( rStyleSettings.GetLightColor() );
+ mpOutDev->SetFillColor( rStyleSettings.GetLightColor() );
+ ImplDrawSymbol( mpOutDev, aRect + Point(1, 1) , eType );
+ nColor = rStyleSettings.GetShadowColor();
+ }
+ }
+
+ // Set selected color and draw the symbol
+ mpOutDev->SetLineColor( nColor );
+ mpOutDev->SetFillColor( nColor );
+ ImplDrawSymbol( mpOutDev, aRect, eType );
+
+ // Restore previous settings
+ mpOutDev->SetLineColor( aOldLineColor );
+ mpOutDev->SetFillColor( aOldFillColor );
+ mpOutDev->EnableMapMode( bOldMapMode );
+}
+
+void DecorationView::DrawFrame( const tools::Rectangle& rRect,
+ const Color& rLeftTopColor,
+ const Color& rRightBottomColor )
+{
+ tools::Rectangle aRect = mpOutDev->LogicToPixel( rRect );
+ const Color aOldLineColor = mpOutDev->GetLineColor();
+ const bool bOldMapMode = mpOutDev->IsMapModeEnabled();
+ mpOutDev->EnableMapMode( false );
+ ImplDraw2ColorFrame( mpOutDev, aRect, rLeftTopColor, rRightBottomColor );
+ mpOutDev->SetLineColor( aOldLineColor );
+ mpOutDev->EnableMapMode( bOldMapMode );
+}
+
+void DecorationView::DrawHighlightFrame( const tools::Rectangle& rRect,
+ DrawHighlightFrameStyle nStyle )
+{
+ const StyleSettings& rStyleSettings = mpOutDev->GetSettings().GetStyleSettings();
+ Color aLightColor = rStyleSettings.GetLightColor();
+ Color aShadowColor = rStyleSettings.GetShadowColor();
+
+ if ( (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) ||
+ (mpOutDev->GetOutDevType() == OUTDEV_PRINTER) )
+ {
+ aLightColor = COL_BLACK;
+ aShadowColor = COL_BLACK;
+ }
+ else
+ {
+ Wallpaper aBackground = mpOutDev->GetBackground();
+ if ( aBackground.IsBitmap() || aBackground.IsGradient() )
+ {
+ aLightColor = rStyleSettings.GetFaceColor();
+ aShadowColor = COL_BLACK;
+ }
+ else
+ {
+ Color aBackColor = aBackground.GetColor();
+ if ( (aLightColor.GetColorError( aBackColor ) < 96) ||
+ (aShadowColor.GetColorError( aBackColor ) < 96) )
+ {
+ aLightColor = COL_WHITE;
+ aShadowColor = COL_BLACK;
+
+ if ( aLightColor.GetColorError( aBackColor ) < 96 )
+ aLightColor.DecreaseLuminance( 64 );
+ if ( aShadowColor.GetColorError( aBackColor ) < 96 )
+ aShadowColor.IncreaseLuminance( 64 );
+ }
+ }
+ }
+
+ if ( nStyle == DrawHighlightFrameStyle::In )
+ {
+ Color aTempColor = aLightColor;
+ aLightColor = aShadowColor;
+ aShadowColor = aTempColor;
+ }
+
+ DrawFrame( rRect, aLightColor, aShadowColor );
+}
+
+tools::Rectangle DecorationView::DrawFrame( const tools::Rectangle& rRect, DrawFrameStyle nStyle, DrawFrameFlags nFlags )
+{
+ tools::Rectangle aRect = rRect;
+ bool bOldMap = mpOutDev->IsMapModeEnabled();
+ if ( bOldMap )
+ {
+ aRect = mpOutDev->LogicToPixel( aRect );
+ mpOutDev->EnableMapMode( false );
+ }
+
+ if ( !rRect.IsEmpty() )
+ {
+ if ( nFlags & DrawFrameFlags::NoDraw )
+ ImplDrawFrame( mpOutDev, aRect, mpOutDev->GetSettings().GetStyleSettings(), nStyle, nFlags );
+ else
+ {
+ Color aOldLineColor = mpOutDev->GetLineColor();
+ Color aOldFillColor = mpOutDev->GetFillColor();
+ ImplDrawFrame( mpOutDev, aRect, mpOutDev->GetSettings().GetStyleSettings(), nStyle, nFlags );
+ mpOutDev->SetLineColor( aOldLineColor );
+ mpOutDev->SetFillColor( aOldFillColor );
+ }
+ }
+
+ if ( bOldMap )
+ {
+ mpOutDev->EnableMapMode( bOldMap );
+ aRect = mpOutDev->PixelToLogic( aRect );
+ }
+
+ return aRect;
+}
+
+tools::Rectangle DecorationView::DrawButton( const tools::Rectangle& rRect, DrawButtonFlags nStyle )
+{
+ if ( rRect.IsEmpty() )
+ {
+ return rRect;
+ }
+
+ tools::Rectangle aRect = rRect;
+ const bool bOldMap = mpOutDev->IsMapModeEnabled();
+
+ if ( bOldMap )
+ {
+ aRect = mpOutDev->LogicToPixel( aRect );
+ mpOutDev->EnableMapMode( false );
+ }
+
+ const Color aOldLineColor = mpOutDev->GetLineColor();
+ const Color aOldFillColor = mpOutDev->GetFillColor();
+ ImplDrawButton( mpOutDev, aRect, nStyle );
+ mpOutDev->SetLineColor( aOldLineColor );
+ mpOutDev->SetFillColor( aOldFillColor );
+
+ // keep border free, although it is used at default representation
+ aRect.AdjustLeft( 1 );
+ aRect.AdjustTop( 1 );
+ aRect.AdjustRight( -1 );
+ aRect.AdjustBottom( -1 );
+
+ if ( nStyle & DrawButtonFlags::NoLightBorder )
+ {
+ aRect.AdjustLeft( 1 );
+ aRect.AdjustTop( 1 );
+ }
+ else if ( nStyle & DrawButtonFlags::NoLeftLightBorder )
+ {
+ aRect.AdjustLeft( 1 );
+ }
+
+ if ( nStyle & DrawButtonFlags::Pressed )
+ {
+ if ( (aRect.GetHeight() > 10) && (aRect.GetWidth() > 10) )
+ {
+ aRect.AdjustLeft(4 );
+ aRect.AdjustTop(4 );
+ aRect.AdjustRight( -1 );
+ aRect.AdjustBottom( -1 );
+ }
+ else
+ {
+ aRect.AdjustLeft(3 );
+ aRect.AdjustTop(3 );
+ aRect.AdjustRight( -2 );
+ aRect.AdjustBottom( -2 );
+ }
+ }
+ else if ( nStyle & DrawButtonFlags::Checked )
+ {
+ aRect.AdjustLeft(3 );
+ aRect.AdjustTop(3 );
+ aRect.AdjustRight( -2 );
+ aRect.AdjustBottom( -2 );
+ }
+ else
+ {
+ aRect.AdjustLeft(2 );
+ aRect.AdjustTop(2 );
+ aRect.AdjustRight( -3 );
+ aRect.AdjustBottom( -3 );
+ }
+
+ if ( bOldMap )
+ {
+ mpOutDev->EnableMapMode( bOldMap );
+ aRect = mpOutDev->PixelToLogic( aRect );
+ }
+
+ return aRect;
+}
+
+void DecorationView::DrawSeparator( const Point& rStart, const Point& rStop, bool bVertical )
+{
+ Point aStart( rStart ), aStop( rStop );
+ const StyleSettings& rStyleSettings = mpOutDev->GetSettings().GetStyleSettings();
+ vcl::Window *const pWin = mpOutDev->GetOwnerWindow();
+ if(pWin)
+ {
+ ControlPart nPart = ( bVertical ? ControlPart::SeparatorVert : ControlPart::SeparatorHorz );
+ bool nativeSupported = pWin->IsNativeControlSupported( ControlType::Fixedline, nPart );
+ ImplControlValue aValue;
+ tools::Rectangle aRect(rStart,rStop);
+ if(nativeSupported && pWin->GetOutDev()->DrawNativeControl(ControlType::Fixedline,nPart,aRect,ControlState::NONE,aValue,OUString()))
+ return;
+ }
+
+ mpOutDev->Push( vcl::PushFlags::LINECOLOR );
+ if ( rStyleSettings.GetOptions() & StyleSettingsOptions::Mono )
+ mpOutDev->SetLineColor( COL_BLACK );
+ else
+ mpOutDev->SetLineColor( rStyleSettings.GetShadowColor() );
+
+ mpOutDev->DrawLine( aStart, aStop );
+ if ( !(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) )
+ {
+ mpOutDev->SetLineColor( rStyleSettings.GetLightColor() );
+ if( bVertical )
+ {
+ aStart.AdjustX( 1 );
+ aStop.AdjustX( 1 );
+ }
+ else
+ {
+ aStart.AdjustY( 1 );
+ aStop.AdjustY( 1 );
+ }
+ mpOutDev->DrawLine( aStart, aStop );
+ }
+ mpOutDev->Pop();
+}
+
+void DecorationView::DrawHandle(const tools::Rectangle& rRect)
+{
+ const StyleSettings& rStyleSettings = mpOutDev->GetSettings().GetStyleSettings();
+
+ Size aOutputSize = rRect.GetSize();
+
+ mpOutDev->SetLineColor(rStyleSettings.GetDarkShadowColor());
+ mpOutDev->SetFillColor(rStyleSettings.GetDarkShadowColor());
+
+ const sal_Int32 nNumberOfPoints = 3;
+
+ tools::Long nHalfWidth = aOutputSize.Width() / 2.0f;
+
+ float fDistance = aOutputSize.Height();
+ fDistance /= (nNumberOfPoints + 1);
+
+ tools::Long nRadius = aOutputSize.Width();
+ nRadius /= (nNumberOfPoints + 2);
+
+ for (tools::Long i = 1; i <= nNumberOfPoints; i++)
+ {
+ tools::Rectangle aLocation(nHalfWidth - nRadius,
+ round(fDistance * i) - nRadius,
+ nHalfWidth + nRadius,
+ round(fDistance * i) + nRadius);
+ mpOutDev->DrawEllipse(aLocation);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dialog.cxx b/vcl/source/window/dialog.cxx
new file mode 100644
index 000000000..b1f18dc50
--- /dev/null
+++ b/vcl/source/window/dialog.cxx
@@ -0,0 +1,1711 @@
+/* -*- 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 <config_feature_desktop.h>
+
+#ifdef IOS
+#include <premac.h>
+#include <UIKit/UIKit.h>
+#include <postmac.h>
+#endif
+
+#include <com/sun/star/frame/theGlobalEventBroadcaster.hpp>
+#include <comphelper/lok.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <comphelper/processfactory.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <osl/diagnose.h>
+
+#include <svdata.hxx>
+#include <window.h>
+#include <accel.hxx>
+#include <brdwin.hxx>
+
+#include <rtl/bootstrap.hxx>
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/abstdlg.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/event.hxx>
+#include <vcl/locktoplevels.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/dialoghelper.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <vcl/IDialogRenderable.hxx>
+#include <messagedialog.hxx>
+#include <salframe.hxx>
+#include <tools/json_writer.hxx>
+
+#include <iostream>
+#include <stack>
+#include <utility>
+#include <vector>
+
+static OString ImplGetDialogText( Dialog* pDialog )
+{
+ OUString aErrorStr(pDialog->GetText());
+
+ OUString sMessage;
+ if (MessageDialog* pMessDialog = dynamic_cast<MessageDialog*>(pDialog))
+ {
+ sMessage = pMessDialog->get_primary_text();
+ }
+
+ if (!sMessage.isEmpty())
+ {
+ aErrorStr += ", " + sMessage;
+ }
+ return OUStringToOString(aErrorStr, RTL_TEXTENCODING_UTF8);
+}
+
+static bool ImplIsMnemonicCtrl( vcl::Window* pWindow )
+{
+ if( ! pWindow->GetSettings().GetStyleSettings().GetAutoMnemonic() )
+ return false;
+
+ if ( (pWindow->GetType() == WindowType::RADIOBUTTON) ||
+ (pWindow->GetType() == WindowType::CHECKBOX) ||
+ (pWindow->GetType() == WindowType::TRISTATEBOX) ||
+ (pWindow->GetType() == WindowType::PUSHBUTTON) )
+ return true;
+
+ if ( pWindow->GetType() == WindowType::FIXEDTEXT )
+ {
+ FixedText *pText = static_cast<FixedText*>(pWindow);
+ if (pText->get_mnemonic_widget())
+ return true;
+ //This is the legacy pre-layout logic which we retain
+ //until we can be sure we can remove it
+ if (pWindow->GetStyle() & WB_NOLABEL)
+ return false;
+ vcl::Window* pNextWindow = pWindow->GetWindow( GetWindowType::Next );
+ if ( !pNextWindow )
+ return false;
+ pNextWindow = pNextWindow->GetWindow( GetWindowType::Client );
+ return !(!(pNextWindow->GetStyle() & WB_TABSTOP) ||
+ (pNextWindow->GetType() == WindowType::FIXEDTEXT) ||
+ (pNextWindow->GetType() == WindowType::GROUPBOX) ||
+ (pNextWindow->GetType() == WindowType::RADIOBUTTON) ||
+ (pNextWindow->GetType() == WindowType::CHECKBOX) ||
+ (pNextWindow->GetType() == WindowType::TRISTATEBOX) ||
+ (pNextWindow->GetType() == WindowType::PUSHBUTTON));
+ }
+
+ return false;
+}
+
+// Called by native error dialog popup implementations
+void ImplHideSplash()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->mpIntroWindow )
+ pSVData->mpIntroWindow->Hide();
+}
+
+vcl::Window * nextLogicalChildOfParent(const vcl::Window *pTopLevel, const vcl::Window *pChild)
+{
+ const vcl::Window *pLastChild = pChild;
+
+ if (pChild->GetType() == WindowType::SCROLLWINDOW)
+ pChild = static_cast<const VclScrolledWindow*>(pChild)->get_child();
+ else if (isContainerWindow(*pChild))
+ pChild = pChild->GetWindow(GetWindowType::FirstChild);
+ else
+ pChild = pChild->GetWindow(GetWindowType::Next);
+
+ while (!pChild)
+ {
+ vcl::Window *pParent = pLastChild->GetParent();
+ if (!pParent)
+ return nullptr;
+ if (pParent == pTopLevel)
+ return nullptr;
+ pLastChild = pParent;
+ pChild = pParent->GetWindow(GetWindowType::Next);
+ }
+
+ if (isContainerWindow(*pChild))
+ pChild = nextLogicalChildOfParent(pTopLevel, pChild);
+
+ return const_cast<vcl::Window *>(pChild);
+}
+
+vcl::Window * prevLogicalChildOfParent(const vcl::Window *pTopLevel, const vcl::Window *pChild)
+{
+ const vcl::Window *pLastChild = pChild;
+
+ if (pChild->GetType() == WindowType::SCROLLWINDOW)
+ pChild = static_cast<const VclScrolledWindow*>(pChild)->get_child();
+ else if (isContainerWindow(*pChild))
+ pChild = pChild->GetWindow(GetWindowType::LastChild);
+ else
+ pChild = pChild->GetWindow(GetWindowType::Prev);
+
+ while (!pChild)
+ {
+ vcl::Window *pParent = pLastChild->GetParent();
+ if (!pParent)
+ return nullptr;
+ if (pParent == pTopLevel)
+ return nullptr;
+ pLastChild = pParent;
+ pChild = pParent->GetWindow(GetWindowType::Prev);
+ }
+
+ if (isContainerWindow(*pChild))
+ pChild = prevLogicalChildOfParent(pTopLevel, pChild);
+
+ return const_cast<vcl::Window *>(pChild);
+}
+
+vcl::Window * firstLogicalChildOfParent(const vcl::Window *pTopLevel)
+{
+ const vcl::Window *pChild = pTopLevel->GetWindow(GetWindowType::FirstChild);
+ if (pChild && isContainerWindow(*pChild))
+ pChild = nextLogicalChildOfParent(pTopLevel, pChild);
+ return const_cast<vcl::Window *>(pChild);
+}
+
+vcl::Window * lastLogicalChildOfParent(const vcl::Window *pTopLevel)
+{
+ const vcl::Window *pChild = pTopLevel->GetWindow(GetWindowType::LastChild);
+ if (pChild && isContainerWindow(*pChild))
+ pChild = prevLogicalChildOfParent(pTopLevel, pChild);
+ return const_cast<vcl::Window *>(pChild);
+}
+
+void GenerateAutoMnemonicsOnHierarchy(const vcl::Window* pWindow)
+{
+ MnemonicGenerator aMnemonicGenerator;
+ vcl::Window* pGetChild;
+ vcl::Window* pChild;
+
+ // register the assigned mnemonics
+ pGetChild = pWindow->GetWindow( GetWindowType::FirstChild );
+ while ( pGetChild )
+ {
+ pChild = pGetChild->ImplGetWindow();
+ aMnemonicGenerator.RegisterMnemonic( pChild->GetText() );
+ pGetChild = nextLogicalChildOfParent(pWindow, pGetChild);
+ }
+
+ // take the Controls of the dialog into account for TabPages
+ if ( pWindow->GetType() == WindowType::TABPAGE )
+ {
+ vcl::Window* pParent = pWindow->GetParent();
+ if (pParent && pParent->GetType() == WindowType::TABCONTROL )
+ pParent = pParent->GetParent();
+
+ if (pParent && (pParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL )
+ {
+ pGetChild = pParent->GetWindow( GetWindowType::FirstChild );
+ while ( pGetChild )
+ {
+ pChild = pGetChild->ImplGetWindow();
+ aMnemonicGenerator.RegisterMnemonic( pChild->GetText() );
+ pGetChild = nextLogicalChildOfParent(pWindow, pGetChild);
+ }
+ }
+ }
+
+ // assign mnemonics to Controls which have none
+ pGetChild = pWindow->GetWindow( GetWindowType::FirstChild );
+ while ( pGetChild )
+ {
+ pChild = pGetChild->ImplGetWindow();
+ if ( ImplIsMnemonicCtrl( pChild ) )
+ {
+ OUString aText = pChild->GetText();
+ OUString aNewText = aMnemonicGenerator.CreateMnemonic( aText );
+ if ( aText != aNewText )
+ pChild->SetText( aNewText );
+ }
+
+ pGetChild = nextLogicalChildOfParent(pWindow, pGetChild);
+ }
+}
+
+static VclButtonBox* getActionArea(Dialog const *pDialog)
+{
+ VclButtonBox *pButtonBox = nullptr;
+ if (pDialog->isLayoutEnabled())
+ {
+ vcl::Window *pBox = pDialog->GetWindow(GetWindowType::FirstChild);
+ vcl::Window *pChild = pBox->GetWindow(GetWindowType::LastChild);
+ while (pChild)
+ {
+ pButtonBox = dynamic_cast<VclButtonBox*>(pChild);
+ if (pButtonBox)
+ break;
+ pChild = pChild->GetWindow(GetWindowType::Prev);
+ }
+ }
+ return pButtonBox;
+}
+
+static vcl::Window* getActionAreaButtonList(Dialog const *pDialog)
+{
+ VclButtonBox* pButtonBox = getActionArea(pDialog);
+ if (pButtonBox)
+ return pButtonBox->GetWindow(GetWindowType::FirstChild);
+ return pDialog->GetWindow(GetWindowType::FirstChild);
+}
+
+static PushButton* ImplGetDefaultButton( Dialog const * pDialog )
+{
+ vcl::Window* pChild = getActionAreaButtonList(pDialog);
+ while ( pChild )
+ {
+ if ( pChild->ImplIsPushButton() )
+ {
+ PushButton* pPushButton = static_cast<PushButton*>(pChild);
+ if ( pPushButton->ImplIsDefButton() )
+ return pPushButton;
+ }
+
+ pChild = pChild->GetWindow( GetWindowType::Next );
+ }
+
+ return nullptr;
+}
+
+static PushButton* ImplGetOKButton( Dialog const * pDialog )
+{
+ vcl::Window* pChild = getActionAreaButtonList(pDialog);
+ while ( pChild )
+ {
+ if ( pChild->GetType() == WindowType::OKBUTTON )
+ return static_cast<PushButton*>(pChild);
+
+ pChild = pChild->GetWindow( GetWindowType::Next );
+ }
+
+ return nullptr;
+}
+
+static PushButton* ImplGetCancelButton( Dialog const * pDialog )
+{
+ vcl::Window* pChild = getActionAreaButtonList(pDialog);
+
+ while ( pChild )
+ {
+ if ( pChild->GetType() == WindowType::CANCELBUTTON )
+ return static_cast<PushButton*>(pChild);
+
+ pChild = pChild->GetWindow( GetWindowType::Next );
+ }
+
+ return nullptr;
+}
+
+static void ImplMouseAutoPos( Dialog* pDialog )
+{
+ MouseSettingsOptions nMouseOptions = pDialog->GetSettings().GetMouseSettings().GetOptions();
+ if ( nMouseOptions & MouseSettingsOptions::AutoCenterPos )
+ {
+ Size aSize = pDialog->GetOutputSizePixel();
+ pDialog->SetPointerPosPixel( Point( aSize.Width()/2, aSize.Height()/2 ) );
+ }
+ else if ( nMouseOptions & MouseSettingsOptions::AutoDefBtnPos )
+ {
+ vcl::Window* pWindow = ImplGetDefaultButton( pDialog );
+ if ( !pWindow )
+ pWindow = ImplGetOKButton( pDialog );
+ if ( !pWindow )
+ pWindow = ImplGetCancelButton( pDialog );
+ if ( !pWindow )
+ pWindow = pDialog;
+ Size aSize = pWindow->GetOutputSizePixel();
+ pWindow->SetPointerPosPixel( Point( aSize.Width()/2, aSize.Height()/2 ) );
+ }
+}
+
+struct DialogImpl
+{
+ std::vector<VclPtr<PushButton>> maOwnedButtons;
+ std::map<VclPtr<vcl::Window>, short> maResponses;
+ tools::Long mnResult;
+ bool mbStartedModal;
+ VclAbstractDialog::AsyncContext maEndCtx;
+ Link<const CommandEvent&, bool> m_aPopupMenuHdl;
+ Link<void*, vcl::ILibreOfficeKitNotifier*> m_aInstallLOKNotifierHdl;
+ bool m_bLOKTunneling;
+
+ DialogImpl() : mnResult( -1 ), mbStartedModal( false ), m_bLOKTunneling( true ) {}
+
+#ifndef NDEBUG
+ short get_response(vcl::Window *pWindow) const
+ {
+ auto aFind = maResponses.find(pWindow);
+ if (aFind != maResponses.end())
+ return aFind->second;
+ return RET_CANCEL;
+ }
+#endif
+
+ ~DialogImpl()
+ {
+ for (VclPtr<PushButton> & pOwnedButton : maOwnedButtons)
+ pOwnedButton.disposeAndClear();
+ }
+};
+
+void Dialog::disposeOwnedButtons()
+{
+ for (VclPtr<PushButton> & pOwnedButton : mpDialogImpl->maOwnedButtons)
+ pOwnedButton.disposeAndClear();
+}
+
+void Dialog::ImplInitDialogData()
+{
+ mpWindowImpl->mbDialog = true;
+ mbInExecute = false;
+ mbInSyncExecute = false;
+ mbInClose = false;
+ mbModalMode = false;
+ mpContentArea.clear();
+ mpActionArea.clear();
+ mnMousePositioned = 0;
+ mpDialogImpl.reset(new DialogImpl);
+}
+
+void Dialog::PixelInvalidate(const tools::Rectangle* pRectangle)
+{
+ if (!mpDialogImpl->m_bLOKTunneling)
+ return;
+
+ Window::PixelInvalidate(pRectangle);
+}
+
+vcl::Window* Dialog::GetDefaultParent(WinBits nStyle)
+{
+ vcl::Window* pParent = Dialog::GetDefDialogParent();
+ if (!pParent && !(nStyle & WB_SYSTEMWINDOW))
+ pParent = ImplGetSVData()->maFrameData.mpAppWin;
+
+ // If Parent is disabled, then we search for a modal dialog
+ // in this frame
+ if (pParent && (!pParent->IsInputEnabled() || pParent->IsInModalMode()))
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ auto& rExecuteDialogs = pSVData->mpWinData->mpExecuteDialogs;
+ auto it = std::find_if(rExecuteDialogs.rbegin(), rExecuteDialogs.rend(),
+ [&pParent](VclPtr<Dialog>& rDialogPtr) {
+ return pParent->ImplGetFirstOverlapWindow() &&
+ pParent->ImplGetFirstOverlapWindow()->IsWindowOrChild(rDialogPtr, true) &&
+ rDialogPtr->IsReallyVisible() && rDialogPtr->IsEnabled() &&
+ rDialogPtr->IsInputEnabled() && !rDialogPtr->IsInModalMode(); });
+ if (it != rExecuteDialogs.rend())
+ pParent = it->get();
+ }
+
+ return pParent;
+}
+
+VclPtr<vcl::Window> Dialog::AddBorderWindow(vcl::Window* pParent, WinBits nStyle)
+{
+ VclPtrInstance<ImplBorderWindow> pBorderWin( pParent, nStyle, BorderWindowStyle::Frame );
+ ImplInit( pBorderWin, nStyle & ~WB_BORDER, nullptr );
+ pBorderWin->mpWindowImpl->mpClientWindow = this;
+ pBorderWin->GetBorder( mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder );
+ mpWindowImpl->mpBorderWindow = pBorderWin;
+ mpWindowImpl->mpRealParent = pParent;
+
+ return pBorderWin;
+}
+
+void Dialog::ImplInitDialog( vcl::Window* pParent, WinBits nStyle, InitFlag eFlag )
+{
+ SystemWindowFlags nSysWinMode = Application::GetSystemWindowMode();
+
+ if ( !(nStyle & WB_NODIALOGCONTROL) )
+ nStyle |= WB_DIALOGCONTROL;
+
+ // Now, all Dialogs are per default system windows !!!
+ nStyle |= WB_SYSTEMWINDOW;
+
+ if (InitFlag::NoParent == eFlag)
+ {
+ pParent = nullptr;
+ }
+ else if (!pParent) // parent is NULL: get the default Dialog parent
+ {
+ pParent = Dialog::GetDefaultParent(nStyle);
+ }
+
+ if ( !pParent || (nStyle & WB_SYSTEMWINDOW) ||
+ (pParent->mpWindowImpl->mpFrameData->mbNeedSysWindow && !(nSysWinMode & SystemWindowFlags::NOAUTOMODE)) ||
+ (nSysWinMode & SystemWindowFlags::DIALOG) )
+ {
+ // create window with a small border ?
+ if ((nStyle & WB_ALLOWMENUBAR) || ((nStyle & (WB_BORDER | WB_NOBORDER | WB_MOVEABLE | WB_SIZEABLE | WB_CLOSEABLE)) == WB_BORDER))
+ {
+ AddBorderWindow(pParent, nStyle);
+ }
+ else
+ {
+ mpWindowImpl->mbFrame = true;
+ mpWindowImpl->mbOverlapWin = true;
+ ImplInit( pParent, (nStyle & (WB_MOVEABLE | WB_SIZEABLE | WB_STANDALONE)) | WB_CLOSEABLE, nullptr );
+ // Now set all style bits
+ mpWindowImpl->mnStyle = nStyle;
+ }
+ }
+ else
+ {
+ VclPtrInstance<ImplBorderWindow> pBorderWin( pParent, nStyle, BorderWindowStyle::Overlap );
+ ImplInit( pBorderWin, nStyle & ~WB_BORDER, nullptr );
+ pBorderWin->mpWindowImpl->mpClientWindow = this;
+ pBorderWin->GetBorder( mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder );
+ mpWindowImpl->mpBorderWindow = pBorderWin;
+ mpWindowImpl->mpRealParent = pParent;
+ }
+
+ SetActivateMode( ActivateModeFlags::GrabFocus );
+
+ ImplInitSettings();
+}
+
+void Dialog::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ if (IsControlBackground())
+ {
+ // user override
+ SetBackground(GetControlBackground());
+ }
+ else if (rRenderContext.IsNativeControlSupported(ControlType::WindowBackground, ControlPart::BackgroundDialog))
+ {
+ // NWF background
+ mpWindowImpl->mnNativeBackground = ControlPart::BackgroundDialog;
+ EnableChildTransparentMode();
+ }
+ else
+ {
+ // fallback to settings color
+ rRenderContext.SetBackground(GetSettings().GetStyleSettings().GetDialogColor());
+ }
+}
+
+void Dialog::ImplInitSettings()
+{
+ // user override
+ if (IsControlBackground())
+ SetBackground(GetControlBackground());
+ // NWF background
+ else if( IsNativeControlSupported(ControlType::WindowBackground, ControlPart::BackgroundDialog))
+ {
+ mpWindowImpl->mnNativeBackground = ControlPart::BackgroundDialog;
+ EnableChildTransparentMode();
+ }
+ // fallback to settings color
+ else
+ SetBackground(GetSettings().GetStyleSettings().GetDialogColor());
+}
+
+void Dialog::ImplLOKNotifier(vcl::Window* pParent)
+{
+ if (comphelper::LibreOfficeKit::isActive() && pParent)
+ {
+ if (VclPtr<vcl::Window> pWin = pParent->GetParentWithLOKNotifier())
+ {
+ SetLOKNotifier(pWin->GetLOKNotifier());
+ }
+ }
+}
+
+Dialog::Dialog( WindowType nType )
+ : SystemWindow( nType, "vcl::Dialog maLayoutIdle" )
+ , mnInitFlag(InitFlag::Default)
+{
+ ImplInitDialogData();
+}
+
+void VclBuilderContainer::disposeBuilder()
+{
+ if (m_pUIBuilder)
+ m_pUIBuilder->disposeBuilder();
+}
+
+OUString AllSettings::GetUIRootDir()
+{
+ OUString sShareLayer("$BRAND_BASE_DIR/$BRAND_SHARE_SUBDIR/config/soffice.cfg/");
+ rtl::Bootstrap::expandMacros(sShareLayer);
+ return sShareLayer;
+}
+
+//we can't change sizeable after the fact, so need to defer until we know and then
+//do the init. Find the real parent stashed in mpDialogParent.
+void Dialog::doDeferredInit(WinBits nBits)
+{
+ VclPtr<vcl::Window> pParent = mpDialogParent;
+ mpDialogParent = nullptr;
+ ImplInitDialog(pParent, nBits | WB_BORDER, mnInitFlag);
+ mbIsDeferredInit = false;
+}
+
+Dialog::Dialog(vcl::Window* pParent, std::u16string_view rID, const OUString& rUIXMLDescription)
+ : SystemWindow(WindowType::DIALOG, "vcl::Dialog maLayoutIdle")
+ , mnInitFlag(InitFlag::Default)
+{
+ ImplLOKNotifier(pParent);
+ ImplInitDialogData();
+ loadUI(pParent, OUStringToOString(rID, RTL_TEXTENCODING_UTF8), rUIXMLDescription);
+}
+
+Dialog::Dialog(vcl::Window* pParent, WinBits nStyle, InitFlag eFlag)
+ : SystemWindow(WindowType::DIALOG, "vcl::Dialog maLayoutIdle")
+ , mnInitFlag(eFlag)
+{
+ ImplLOKNotifier(pParent);
+ ImplInitDialogData();
+ ImplInitDialog( pParent, nStyle, eFlag );
+}
+
+void Dialog::set_action_area(VclButtonBox* pBox)
+{
+ mpActionArea.set(pBox);
+ if (pBox)
+ {
+ const DialogStyle& rDialogStyle =
+ GetSettings().GetStyleSettings().GetDialogStyle();
+ pBox->set_border_width(rDialogStyle.action_area_border);
+ }
+}
+
+void Dialog::set_content_area(VclBox* pBox)
+{
+ mpContentArea.set(pBox);
+}
+
+void Dialog::settingOptimalLayoutSize(Window *pBox)
+{
+ const DialogStyle& rDialogStyle =
+ GetSettings().GetStyleSettings().GetDialogStyle();
+ VclBox * pBox2 = static_cast<VclBox*>(pBox);
+ pBox2->set_border_width(rDialogStyle.content_area_border);
+}
+
+Dialog::~Dialog()
+{
+ disposeOnce();
+}
+
+void Dialog::dispose()
+{
+ bool bTunnelingEnabled = mpDialogImpl->m_bLOKTunneling;
+
+ mpDialogImpl.reset();
+ RemoveFromDlgList();
+ mpActionArea.clear();
+ mpContentArea.clear();
+
+ css::uno::Reference< css::uno::XComponentContext > xContext(
+ comphelper::getProcessComponentContext() );
+ css::uno::Reference<css::frame::XGlobalEventBroadcaster> xEventBroadcaster(css::frame::theGlobalEventBroadcaster::get(xContext), css::uno::UNO_SET_THROW);
+ css::document::DocumentEvent aObject;
+ aObject.EventName = "DialogClosed";
+ xEventBroadcaster->documentEventOccured(aObject);
+ UITestLogger::getInstance().log(u"Close Dialog");
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ if(const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
+ {
+ if (bTunnelingEnabled)
+ pNotifier->notifyWindow(GetLOKWindowId(), "close");
+ ReleaseLOKNotifier();
+ }
+ }
+
+ SystemWindow::dispose();
+}
+
+IMPL_LINK_NOARG(Dialog, ImplAsyncCloseHdl, void*, void)
+{
+ Close();
+}
+
+bool Dialog::EventNotify( NotifyEvent& rNEvt )
+{
+ // first call the base class due to Tab control
+ bool bRet = SystemWindow::EventNotify( rNEvt );
+ if ( !bRet )
+ {
+ if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ const KeyEvent* pKEvt = rNEvt.GetKeyEvent();
+ vcl::KeyCode aKeyCode = pKEvt->GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+
+ if ( (nKeyCode == KEY_ESCAPE) &&
+ ((GetStyle() & WB_CLOSEABLE) || ImplGetCancelButton( this ) || ImplGetOKButton( this )) )
+ {
+ // #i89505# for the benefit of slightly mentally challenged implementations
+ // like e.g. SfxModelessDialog which destroy themselves inside Close()
+ // post this Close asynchronous so we can leave our key handler before
+ // we get destroyed
+ PostUserEvent( LINK( this, Dialog, ImplAsyncCloseHdl ), nullptr, true);
+ return true;
+ }
+ }
+ else if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ {
+ // make sure the dialog is still modal
+ // changing focus between application frames may
+ // have re-enabled input for our parent
+ if( mbInExecute && mbModalMode )
+ {
+ ImplSetModalInputMode( false );
+ ImplSetModalInputMode( true );
+
+ // #93022# def-button might have changed after show
+ if( !mnMousePositioned )
+ {
+ mnMousePositioned = 1;
+ ImplMouseAutoPos( this );
+ }
+
+ }
+ }
+ }
+
+ return bRet;
+}
+
+//What we really want here is something that gives the available width and
+//height of a users screen, taking away the space taken up the OS
+//taskbar, menus, etc.
+Size bestmaxFrameSizeForScreenSize(const Size &rScreenSize)
+{
+#ifndef IOS
+ tools::Long w = rScreenSize.Width();
+ if (w <= 800)
+ w -= 15;
+ else if (w <= 1024)
+ w -= 65;
+ else
+ w -= 115;
+
+ tools::Long h = rScreenSize.Height();
+ if (h <= 768)
+ h -= 50;
+ else
+ h -= 100;
+
+ return Size(std::max<tools::Long>(w, 640 - 15),
+ std::max<tools::Long>(h, 480 - 50));
+#else
+ // Don't bother with ancient magic numbers of unclear relevance on non-desktop apps anyway. It
+ // seems that at least currently in the iOS app, this function is called just once per dialog,
+ // with a rScreenSize parameter of 1x1 (!). This would lead to always returning 625x430 which is
+ // a bit random and needlessly small on an iPad at least. We want something that closely will
+ // just fit on the display in either orientation.
+
+ // We ignore the rScreenSize as it will be the dummy 1x1 from iosinst.cxx (see "Totally wrong of course").
+ (void) rScreenSize;
+
+ const int n = std::min<CGFloat>([[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height);
+ return Size(n-10, n-10);
+#endif
+}
+
+void Dialog::SetPopupMenuHdl(const Link<const CommandEvent&, bool>& rLink)
+{
+ mpDialogImpl->m_aPopupMenuHdl = rLink;
+}
+
+void Dialog::SetInstallLOKNotifierHdl(const Link<void*, vcl::ILibreOfficeKitNotifier*>& rLink)
+{
+ mpDialogImpl->m_aInstallLOKNotifierHdl = rLink;
+}
+
+void Dialog::SetLOKTunnelingState(bool bEnabled)
+{
+ mpDialogImpl->m_bLOKTunneling = bEnabled;
+}
+
+void Dialog::StateChanged( StateChangedType nType )
+{
+ bool bTunnelingEnabled = mpDialogImpl->m_bLOKTunneling;
+
+ if (nType == StateChangedType::InitShow)
+ {
+ DoInitialLayout();
+
+ const bool bKitActive = comphelper::LibreOfficeKit::isActive();
+ if (bKitActive && bTunnelingEnabled)
+ {
+ std::vector<vcl::LOKPayloadItem> aItems;
+ aItems.emplace_back("type", "dialog");
+ aItems.emplace_back("size", GetSizePixel().toString());
+ if (!GetText().isEmpty())
+ aItems.emplace_back("title", GetText().toUtf8());
+
+ if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
+ {
+ pNotifier->notifyWindow(GetLOKWindowId(), "created", aItems);
+ pNotifier->notifyWindow(GetLOKWindowId(), "created", aItems);
+ }
+ else
+ {
+ vcl::ILibreOfficeKitNotifier* pViewShell = mpDialogImpl->m_aInstallLOKNotifierHdl.Call(nullptr);
+ if (pViewShell)
+ {
+ SetLOKNotifier(pViewShell);
+ pViewShell->notifyWindow(GetLOKWindowId(), "created", aItems);
+ }
+ }
+ }
+
+ if ( !HasChildPathFocus() || HasFocus() )
+ GrabFocusToFirstControl();
+ if ( !(GetStyle() & WB_CLOSEABLE) )
+ {
+ if ( ImplGetCancelButton( this ) || ImplGetOKButton( this ) )
+ {
+ if ( ImplGetBorderWindow() )
+ static_cast<ImplBorderWindow*>(ImplGetBorderWindow())->SetCloseButton();
+ }
+ }
+
+ ImplMouseAutoPos( this );
+ }
+ else if (nType == StateChangedType::Text)
+ {
+ const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier();
+ if (pNotifier && bTunnelingEnabled)
+ {
+ std::vector<vcl::LOKPayloadItem> aPayload;
+ aPayload.emplace_back("title", GetText().toUtf8());
+ pNotifier->notifyWindow(GetLOKWindowId(), "title_changed", aPayload);
+ }
+ }
+
+ SystemWindow::StateChanged( nType );
+
+ if (nType == StateChangedType::ControlBackground)
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+
+ if (!mbModalMode && nType == StateChangedType::Visible)
+ {
+ const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier();
+ if (pNotifier && bTunnelingEnabled)
+ {
+ std::vector<vcl::LOKPayloadItem> aPayload;
+ aPayload.emplace_back("title", GetText().toUtf8());
+ pNotifier->notifyWindow(GetLOKWindowId(), IsVisible()? OUString("show"): OUString("hide"), aPayload);
+ }
+ }
+}
+
+void Dialog::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ SystemWindow::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+}
+
+bool Dialog::Close()
+{
+ VclPtr<vcl::Window> xWindow = this;
+ CallEventListeners( VclEventId::WindowClose );
+ if ( xWindow->isDisposed() )
+ return false;
+
+ if ( mpWindowImpl->mxWindowPeer.is() && IsCreatedWithToolkit() && !IsInExecute() )
+ return false;
+
+ // If there's a cancel button with a custom handler, then always give it a chance to
+ // handle Dialog::Close
+ PushButton* pCustomCancelButton;
+ PushButton* pCancelButton = dynamic_cast<PushButton*>(get_widget_for_response(RET_CANCEL));
+ if (!mbInClose && pCancelButton && pCancelButton->GetClickHdl().IsSet())
+ pCustomCancelButton = pCancelButton;
+ else
+ pCustomCancelButton = nullptr;
+
+ mbInClose = true;
+
+ if (pCustomCancelButton)
+ {
+ pCustomCancelButton->Click();
+ if (xWindow->isDisposed())
+ return true;
+ mbInClose = false;
+ return false;
+ }
+
+ if ( !(GetStyle() & WB_CLOSEABLE) )
+ {
+ bool bRet = true;
+ PushButton* pButton = ImplGetCancelButton( this );
+ if ( pButton )
+ pButton->Click();
+ else
+ {
+ pButton = ImplGetOKButton( this );
+ if ( pButton )
+ pButton->Click();
+ else
+ bRet = false;
+ }
+ if ( xWindow->isDisposed() )
+ return true;
+ return bRet;
+ }
+
+ if (IsInExecute() || mpDialogImpl->maEndCtx.isSet())
+ {
+ EndDialog();
+ mbInClose = false;
+ return true;
+ }
+ else
+ {
+ mbInClose = false;
+ return SystemWindow::Close();
+ }
+}
+
+bool Dialog::ImplStartExecute()
+{
+ setDeferredProperties();
+
+ if (IsInExecute() || mpDialogImpl->maEndCtx.isSet())
+ {
+#ifdef DBG_UTIL
+ SAL_WARN( "vcl", "Dialog::StartExecuteModal() is called in Dialog::StartExecuteModal(): "
+ << ImplGetDialogText(this) );
+#endif
+ return false;
+ }
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ const bool bKitActive = comphelper::LibreOfficeKit::isActive();
+
+ const bool bModal = GetType() != WindowType::MODELESSDIALOG;
+
+ if (bModal)
+ {
+ if (bKitActive && !GetLOKNotifier())
+ SetLOKNotifier(mpDialogImpl->m_aInstallLOKNotifierHdl.Call(nullptr));
+
+ switch ( Application::GetDialogCancelMode() )
+ {
+ case DialogCancelMode::Off:
+ break;
+ case DialogCancelMode::Silent:
+ if (bModal && GetLOKNotifier())
+ {
+ // check if there's already some dialog being ::Execute()d
+ const bool bDialogExecuting = std::any_of(pSVData->mpWinData->mpExecuteDialogs.begin(),
+ pSVData->mpWinData->mpExecuteDialogs.end(),
+ [](const Dialog* pDialog) {
+ return pDialog->IsInSyncExecute();
+ });
+ if (!(bDialogExecuting && IsInSyncExecute()))
+ break;
+ else
+ SAL_WARN("lok.dialog", "Dialog \"" << ImplGetDialogText(this) << "\" is being synchronously executed over an existing synchronously executing dialog.");
+ }
+
+ SAL_INFO(
+ "vcl",
+ "Dialog \"" << ImplGetDialogText(this)
+ << "\"cancelled in silent mode");
+ return false;
+
+ case DialogCancelMode::LOKSilent:
+ return false;
+
+ default: // default cannot happen
+ case DialogCancelMode::Fatal:
+ std::abort();
+ }
+
+#ifdef DBG_UTIL
+ vcl::Window* pParent = GetParent();
+ if ( pParent )
+ {
+ pParent = pParent->ImplGetFirstOverlapWindow();
+ if (pParent)
+ {
+ SAL_WARN_IF( !pParent->IsReallyVisible(), "vcl",
+ "Dialog::StartExecuteModal() - Parent not visible" );
+ SAL_WARN_IF( !pParent->IsInputEnabled(), "vcl",
+ "Dialog::StartExecuteModal() - Parent input disabled, use another parent to ensure modality!" );
+ SAL_WARN_IF( pParent->IsInModalMode(), "vcl",
+ "Dialog::StartExecuteModal() - Parent already modally disabled, use another parent to ensure modality!" );
+ }
+ }
+#endif
+
+ // link all dialogs which are being executed
+ pSVData->mpWinData->mpExecuteDialogs.push_back(this);
+
+ // stop capturing, in order to have control over the dialog
+ if (pSVData->mpWinData->mpTrackWin)
+ pSVData->mpWinData->mpTrackWin->EndTracking(TrackingEventFlags::Cancel);
+ if (pSVData->mpWinData->mpCaptureWin)
+ pSVData->mpWinData->mpCaptureWin->ReleaseMouse();
+ EnableInput();
+ }
+
+ mbInExecute = true;
+ // no real modality in LibreOfficeKit
+ if (!bKitActive && bModal)
+ SetModalInputMode(true);
+
+ // FIXME: no layouting, workaround some clipping issues
+ ImplAdjustNWFSizes();
+
+ css::uno::Reference< css::uno::XComponentContext > xContext(
+ comphelper::getProcessComponentContext());
+ bool bForceFocusAndToFront(officecfg::Office::Common::View::NewDocumentHandling::ForceFocusAndToFront::get());
+ ShowFlags showFlags = bForceFocusAndToFront ? ShowFlags::ForegroundTask : ShowFlags::NONE;
+ Show(true, showFlags);
+
+ if (bModal)
+ pSVData->maAppData.mnModalMode++;
+
+ css::uno::Reference<css::frame::XGlobalEventBroadcaster> xEventBroadcaster(
+ css::frame::theGlobalEventBroadcaster::get(xContext), css::uno::UNO_SET_THROW);
+ css::document::DocumentEvent aObject;
+ aObject.EventName = "DialogExecute";
+ xEventBroadcaster->documentEventOccured(aObject);
+ if (bModal)
+ UITestLogger::getInstance().log(OUStringConcatenation("Open Modal " + get_id()));
+ else
+ UITestLogger::getInstance().log(OUStringConcatenation("Open Modeless " + get_id()));
+
+ bool bTunnelingEnabled = mpDialogImpl->m_bLOKTunneling;
+ if (comphelper::LibreOfficeKit::isActive() && bTunnelingEnabled)
+ {
+ if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
+ {
+ // Dialog boxes don't get the Resize call and they
+ // can have invalid size at 'created' message above.
+ // If there is no difference, the client should detect it and ignore us,
+ // otherwise, this should make sure that the window has the correct size.
+ std::vector<vcl::LOKPayloadItem> aItems;
+ aItems.emplace_back("size", GetSizePixel().toString());
+ pNotifier->notifyWindow(GetLOKWindowId(), "size_changed", aItems);
+ }
+ }
+
+ return true;
+}
+
+void Dialog::ImplEndExecuteModal()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mnModalMode--;
+}
+
+short Dialog::Execute()
+{
+ VclPtr<vcl::Window> xWindow = this;
+
+ mbInSyncExecute = true;
+ comphelper::ScopeGuard aGuard([&]() {
+ mbInSyncExecute = false;
+ });
+
+ if ( !ImplStartExecute() )
+ return 0;
+
+ // Yield util EndDialog is called or dialog gets destroyed
+ // (the latter should not happen, but better safe than sorry
+ while ( !xWindow->isDisposed() && mbInExecute && !Application::IsQuit() )
+ Application::Yield();
+
+ ImplEndExecuteModal();
+#ifdef DBG_UTIL
+ assert (!mpDialogParent || !mpDialogParent->isDisposed());
+#endif
+ if ( !xWindow->isDisposed() )
+ xWindow.clear();
+ else
+ {
+ OSL_FAIL( "Dialog::Execute() - Dialog destroyed in Execute()" );
+ }
+
+ assert(mpDialogImpl);
+
+ if (mpDialogImpl)
+ {
+ tools::Long nRet = mpDialogImpl->mnResult;
+ mpDialogImpl->mnResult = -1;
+
+ return static_cast<short>(nRet);
+ }
+ else
+ {
+ SAL_WARN( "vcl", "Dialog::Execute() : missing mpDialogImpl " );
+ return 0;
+ }
+}
+
+// virtual
+bool Dialog::StartExecuteAsync( VclAbstractDialog::AsyncContext &rCtx )
+{
+ const bool bModal = GetType() != WindowType::MODELESSDIALOG;
+ if (!ImplStartExecute())
+ {
+ rCtx.mxOwner.disposeAndClear();
+ rCtx.mxOwnerDialogController.reset();
+ rCtx.mxOwnerSelf.reset();
+ return false;
+ }
+
+ mpDialogImpl->maEndCtx = rCtx;
+ mpDialogImpl->mbStartedModal = bModal;
+
+ return true;
+}
+
+void Dialog::RemoveFromDlgList()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ auto& rExecuteDialogs = pSVData->mpWinData->mpExecuteDialogs;
+
+ // remove dialog from the list of dialogs which are being executed
+ rExecuteDialogs.erase(std::remove_if(rExecuteDialogs.begin(), rExecuteDialogs.end(), [this](VclPtr<Dialog>& dialog){ return dialog.get() == this; }), rExecuteDialogs.end());
+}
+
+void Dialog::EndDialog( tools::Long nResult )
+{
+ if (!mbInExecute || isDisposed())
+ return;
+
+ const bool bModal = GetType() != WindowType::MODELESSDIALOG;
+
+ Hide();
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ if(const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
+ {
+ pNotifier->notifyWindow(GetLOKWindowId(), "close");
+ ReleaseLOKNotifier();
+ }
+ }
+
+ if (bModal)
+ {
+ SetModalInputMode(false);
+
+ RemoveFromDlgList();
+
+ // set focus to previous modal dialog if it is modal for
+ // the same frame parent (or NULL)
+ ImplSVData* pSVData = ImplGetSVData();
+ if (!pSVData->mpWinData->mpExecuteDialogs.empty())
+ {
+ VclPtr<Dialog> pPrevious = pSVData->mpWinData->mpExecuteDialogs.back();
+
+ vcl::Window* pFrameParent = ImplGetFrameWindow()->ImplGetParent();
+ vcl::Window* pPrevFrameParent = pPrevious->ImplGetFrameWindow()? pPrevious->ImplGetFrameWindow()->ImplGetParent(): nullptr;
+ if( ( !pFrameParent && !pPrevFrameParent ) ||
+ ( pFrameParent && pPrevFrameParent && pFrameParent->ImplGetFrame() == pPrevFrameParent->ImplGetFrame() )
+ )
+ {
+ pPrevious->GrabFocus();
+ }
+ }
+ }
+
+ mpDialogImpl->mnResult = nResult;
+
+ if ( mpDialogImpl->mbStartedModal )
+ ImplEndExecuteModal();
+
+ if ( mpDialogImpl && mpDialogImpl->maEndCtx.isSet() )
+ {
+ auto fn = std::move(mpDialogImpl->maEndCtx.maEndDialogFn);
+ // std::move leaves maEndDialogFn in a valid state with unspecified
+ // value. For the SwSyncBtnDlg case gcc and msvc left maEndDialogFn
+ // unset, but clang left maEndDialogFn at its original value, keeping
+ // an extra reference to the DialogController in its lambda giving
+ // an inconsistent lifecycle for the dialog. Force it to be unset.
+ mpDialogImpl->maEndCtx.maEndDialogFn = nullptr;
+ fn(nResult);
+ }
+
+ if ( mpDialogImpl && mpDialogImpl->mbStartedModal )
+ {
+ mpDialogImpl->mbStartedModal = false;
+ mpDialogImpl->mnResult = -1;
+ }
+ mbInExecute = false;
+
+ if ( mpDialogImpl )
+ {
+ // Destroy ourselves (if we have a context with VclPtr owner)
+ std::shared_ptr<weld::DialogController> xOwnerDialogController = std::move(mpDialogImpl->maEndCtx.mxOwnerDialogController);
+ std::shared_ptr<weld::Dialog> xOwnerSelf = std::move(mpDialogImpl->maEndCtx.mxOwnerSelf);
+ mpDialogImpl->maEndCtx.mxOwner.disposeAndClear();
+ xOwnerDialogController.reset();
+ xOwnerSelf.reset();
+ }
+}
+
+namespace vcl
+{
+ void EndAllDialogs( vcl::Window const * pParent )
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ auto& rExecuteDialogs = pSVData->mpWinData->mpExecuteDialogs;
+
+ for (auto it = rExecuteDialogs.rbegin(); it != rExecuteDialogs.rend(); ++it)
+ {
+ if (!pParent || pParent->IsWindowOrChild(*it, true))
+ {
+ (*it)->EndDialog();
+ (*it)->PostUserEvent(Link<void*, void>());
+ }
+ }
+ }
+
+ void EnableDialogInput(vcl::Window* pWindow)
+ {
+ if (Dialog* pDialog = dynamic_cast<Dialog*>(pWindow))
+ {
+ pDialog->EnableInput();
+ }
+ }
+
+ void CloseTopLevel(vcl::Window* pWindow)
+ {
+ if (Dialog* pDialog = dynamic_cast<Dialog*>(pWindow))
+ pDialog->Close();
+ else if (FloatingWindow* pFloatWin = dynamic_cast<FloatingWindow*>(pWindow))
+ pFloatWin->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll);
+ }
+}
+
+void Dialog::SetModalInputMode( bool bModal )
+{
+ if ( bModal == mbModalMode )
+ return;
+
+ ImplGetFrame()->SetModal(bModal);
+
+ if (GetParent())
+ {
+ SalFrame* pFrame = GetParent()->ImplGetFrame();
+ pFrame->NotifyModalHierarchy(bModal);
+ }
+
+ ImplSetModalInputMode(bModal);
+}
+
+void Dialog::ImplSetModalInputMode( bool bModal )
+{
+ if ( bModal == mbModalMode )
+ return;
+
+ // previously Execute()'d dialog - the one below the top-most one
+ VclPtr<Dialog> pPrevious;
+ ImplSVData* pSVData = ImplGetSVData();
+ auto& rExecuteDialogs = pSVData->mpWinData->mpExecuteDialogs;
+ if (rExecuteDialogs.size() > 1)
+ pPrevious = rExecuteDialogs[rExecuteDialogs.size() - 2];
+
+ mbModalMode = bModal;
+ if ( bModal )
+ {
+ // Disable the prev Modal Dialog, because our dialog must close at first,
+ // before the other dialog can be closed (because the other dialog
+ // is on stack since our dialog returns)
+ if (pPrevious && !pPrevious->IsWindowOrChild(this, true))
+ pPrevious->EnableInput(false, this);
+
+ // determine next overlap dialog parent
+ vcl::Window* pParent = GetParent();
+ if ( pParent )
+ {
+ // #103716# dialogs should always be modal to the whole frame window
+ // #115933# disable the whole frame hierarchy, useful if our parent
+ // is a modeless dialog
+ mpDialogParent = pParent->mpWindowImpl->mpFrameWindow;
+ mpDialogParent->IncModalCount();
+ }
+ }
+ else
+ {
+ if ( mpDialogParent )
+ {
+ // #115933# re-enable the whole frame hierarchy again (see above)
+ // note that code in getfocus assures that we do not accidentally enable
+ // windows that were disabled before
+ mpDialogParent->DecModalCount();
+ }
+
+ // Enable the prev Modal Dialog
+ if (pPrevious && !pPrevious->IsWindowOrChild(this, true))
+ {
+ pPrevious->EnableInput(true, this);
+
+ // ensure continued modality of prev dialog
+ // do not change modality counter
+
+ // #i119994# need find the last modal dialog before reactive it
+ if (pPrevious->IsModalInputMode() || !pPrevious->IsWindowOrChild(this, true))
+ {
+ pPrevious->ImplSetModalInputMode(false);
+ pPrevious->ImplSetModalInputMode(true);
+ }
+ }
+ }
+}
+
+vcl::Window* Dialog::GetFirstControlForFocus()
+{
+ vcl::Window* pFocusControl = nullptr;
+ vcl::Window* pFirstOverlapWindow = ImplGetFirstOverlapWindow();
+
+ // find focus control, even if the dialog has focus
+ if (!HasFocus() && pFirstOverlapWindow && pFirstOverlapWindow->mpWindowImpl)
+ {
+ // prefer a child window which had focus before
+ pFocusControl = ImplGetFirstOverlapWindow()->mpWindowImpl->mpLastFocusWindow;
+ // find the control out of the dialog control
+ if ( pFocusControl )
+ pFocusControl = ImplFindDlgCtrlWindow( pFocusControl );
+ }
+ // no control had the focus before or the control is not
+ // part of the tab-control, now give focus to the
+ // first control in the tab-control
+ if ( !pFocusControl ||
+ !(pFocusControl->GetStyle() & WB_TABSTOP) ||
+ !isVisibleInLayout(pFocusControl) ||
+ !isEnabledInLayout(pFocusControl) || !pFocusControl->IsInputEnabled() )
+ {
+ pFocusControl = ImplGetDlgWindow( 0, GetDlgWindowType::First );
+ }
+
+ return pFocusControl;
+}
+
+void Dialog::GrabFocusToFirstControl()
+{
+ vcl::Window* pFocusControl = GetFirstControlForFocus();
+ if ( pFocusControl )
+ pFocusControl->ImplControlFocus( GetFocusFlags::Init );
+}
+
+void Dialog::GetDrawWindowBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder, sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const
+{
+ ScopedVclPtrInstance<ImplBorderWindow> aImplWin( static_cast<vcl::Window*>(const_cast<Dialog *>(this)), WB_BORDER|WB_STDWORK, BorderWindowStyle::Overlap );
+ aImplWin->GetBorder( rLeftBorder, rTopBorder, rRightBorder, rBottomBorder );
+}
+
+void Dialog::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags )
+{
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+
+ Wallpaper aWallpaper = GetBackground();
+ if ( !aWallpaper.IsBitmap() )
+ ImplInitSettings();
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetLineColor();
+
+ if ( aWallpaper.IsBitmap() )
+ pDev->DrawBitmapEx( aPos, aSize, aWallpaper.GetBitmap() );
+ else
+ {
+ pDev->SetFillColor( aWallpaper.GetColor() );
+ pDev->DrawRect( tools::Rectangle( aPos, aSize ) );
+ }
+
+ if (!( GetStyle() & WB_NOBORDER ))
+ {
+ ScopedVclPtrInstance< ImplBorderWindow > aImplWin( this, WB_BORDER|WB_STDWORK, BorderWindowStyle::Overlap );
+ aImplWin->SetText( GetText() );
+ aImplWin->setPosSizePixel( aPos.X(), aPos.Y(), aSize.Width(), aSize.Height() );
+ aImplWin->SetDisplayActive( true );
+ aImplWin->InitView();
+
+ aImplWin->Draw( pDev, aPos );
+ }
+
+ pDev->Pop();
+}
+
+void Dialog::queue_resize(StateChangedType eReason)
+{
+ if (IsInClose())
+ return;
+ SystemWindow::queue_resize(eReason);
+}
+
+void Dialog::Resize()
+{
+ SystemWindow::Resize();
+
+ if (comphelper::LibreOfficeKit::isDialogPainting())
+ return;
+
+ bool bTunnelingEnabled = mpDialogImpl->m_bLOKTunneling;
+ const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier();
+ if (pNotifier && bTunnelingEnabled)
+ {
+ std::vector<vcl::LOKPayloadItem> aItems;
+ aItems.emplace_back("size", GetSizePixel().toString());
+ pNotifier->notifyWindow(GetLOKWindowId(), "size_changed", aItems);
+ }
+}
+
+bool Dialog::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "border-width")
+ set_border_width(rValue.toInt32());
+ else
+ return SystemWindow::set_property(rKey, rValue);
+ return true;
+}
+
+FactoryFunction Dialog::GetUITestFactory() const
+{
+ return DialogUIObject::create;
+}
+
+IMPL_LINK(Dialog, ResponseHdl, Button*, pButton, void)
+{
+ auto aFind = mpDialogImpl->maResponses.find(pButton);
+ if (aFind == mpDialogImpl->maResponses.end())
+ return;
+ short nResponse = aFind->second;
+ if (nResponse == RET_HELP)
+ {
+ vcl::Window* pFocusWin = Application::GetFocusWindow();
+ if (!pFocusWin || comphelper::LibreOfficeKit::isActive())
+ pFocusWin = pButton;
+ HelpEvent aEvt(pFocusWin->GetPointerPosPixel(), HelpEventMode::CONTEXT);
+ pFocusWin->RequestHelp(aEvt);
+ return;
+ }
+ EndDialog(nResponse);
+}
+
+void Dialog::add_button(PushButton* pButton, int response, bool bTransferOwnership)
+{
+ if (bTransferOwnership)
+ mpDialogImpl->maOwnedButtons.push_back(pButton);
+ mpDialogImpl->maResponses[pButton] = response;
+ switch (pButton->GetType())
+ {
+ case WindowType::PUSHBUTTON:
+ {
+ if (!pButton->GetClickHdl().IsSet())
+ pButton->SetClickHdl(LINK(this, Dialog, ResponseHdl));
+ break;
+ }
+ //insist that the response ids match the default actions for those
+ //widgets, and leave their default handlers in place
+ case WindowType::OKBUTTON:
+ assert(mpDialogImpl->get_response(pButton) == RET_OK);
+ break;
+ case WindowType::CANCELBUTTON:
+ assert(mpDialogImpl->get_response(pButton) == RET_CANCEL || mpDialogImpl->get_response(pButton) == RET_CLOSE);
+ break;
+ case WindowType::HELPBUTTON:
+ assert(mpDialogImpl->get_response(pButton) == RET_HELP);
+ break;
+ default:
+ SAL_WARN("vcl.layout", "The type of widget " <<
+ pButton->GetHelpId() << " is currently not handled");
+ break;
+ }
+}
+
+vcl::Window* Dialog::get_widget_for_response(int response)
+{
+ //copy explicit responses
+ std::map<VclPtr<vcl::Window>, short> aResponses(mpDialogImpl->maResponses);
+
+ if (mpActionArea)
+ {
+ //add implicit responses
+ for (vcl::Window* pChild = mpActionArea->GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (aResponses.find(pChild) != aResponses.end())
+ continue;
+ switch (pChild->GetType())
+ {
+ case WindowType::OKBUTTON:
+ aResponses[pChild] = RET_OK;
+ break;
+ case WindowType::CANCELBUTTON:
+ aResponses[pChild] = RET_CANCEL;
+ break;
+ case WindowType::HELPBUTTON:
+ aResponses[pChild] = RET_HELP;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ for (const auto& a : aResponses)
+ {
+ if (a.second == response)
+ return a.first;
+ }
+
+ return nullptr;
+}
+
+int Dialog::get_default_response() const
+{
+ //copy explicit responses
+ std::map<VclPtr<vcl::Window>, short> aResponses(mpDialogImpl->maResponses);
+
+ if (mpActionArea)
+ {
+ //add implicit responses
+ for (vcl::Window* pChild = mpActionArea->GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (aResponses.find(pChild) != aResponses.end())
+ continue;
+ switch (pChild->GetType())
+ {
+ case WindowType::OKBUTTON:
+ aResponses[pChild] = RET_OK;
+ break;
+ case WindowType::CANCELBUTTON:
+ aResponses[pChild] = RET_CANCEL;
+ break;
+ case WindowType::HELPBUTTON:
+ aResponses[pChild] = RET_HELP;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ for (const auto& a : aResponses)
+ {
+ if (a.first->GetStyle() & WB_DEFBUTTON)
+ {
+ return a.second;
+ }
+ }
+ return RET_CANCEL;
+}
+
+void Dialog::set_default_response(int response)
+{
+ //copy explicit responses
+ std::map<VclPtr<vcl::Window>, short> aResponses(mpDialogImpl->maResponses);
+
+ if (mpActionArea)
+ {
+ //add implicit responses
+ for (vcl::Window* pChild = mpActionArea->GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (aResponses.find(pChild) != aResponses.end())
+ continue;
+ switch (pChild->GetType())
+ {
+ case WindowType::OKBUTTON:
+ aResponses[pChild] = RET_OK;
+ break;
+ case WindowType::CANCELBUTTON:
+ aResponses[pChild] = RET_CANCEL;
+ break;
+ case WindowType::HELPBUTTON:
+ aResponses[pChild] = RET_HELP;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ for (auto& a : aResponses)
+ {
+ if (a.second == response)
+ {
+ a.first->SetStyle(a.first->GetStyle() | WB_DEFBUTTON);
+ a.first->GrabFocus();
+ }
+ else
+ {
+ a.first->SetStyle(a.first->GetStyle() & ~WB_DEFBUTTON);
+ }
+ }
+}
+
+VclBuilderContainer::VclBuilderContainer()
+{
+}
+
+void VclBuilderContainer::setDeferredProperties()
+{
+ if (!m_pUIBuilder)
+ return;
+ m_pUIBuilder->setDeferredProperties();
+}
+
+VclBuilderContainer::~VclBuilderContainer()
+{
+}
+
+void Dialog::Activate()
+{
+ if (GetType() == WindowType::MODELESSDIALOG)
+ {
+ css::uno::Reference< css::uno::XComponentContext > xContext(
+ comphelper::getProcessComponentContext() );
+ css::uno::Reference<css::frame::XGlobalEventBroadcaster> xEventBroadcaster(css::frame::theGlobalEventBroadcaster::get(xContext), css::uno::UNO_SET_THROW);
+ css::document::DocumentEvent aObject;
+ aObject.EventName = "ModelessDialogVisible";
+ xEventBroadcaster->documentEventOccured(aObject);
+ }
+ SystemWindow::Activate();
+}
+
+void Dialog::Command(const CommandEvent& rCEvt)
+{
+ if (mpDialogImpl && mpDialogImpl->m_aPopupMenuHdl.Call(rCEvt))
+ return;
+ SystemWindow::Command(rCEvt);
+}
+
+struct TopLevelWindowLockerImpl
+{
+ std::stack<std::vector<VclPtr<vcl::Window>>> m_aBusyStack;
+};
+
+TopLevelWindowLocker::TopLevelWindowLocker()
+ : m_xImpl(std::make_unique<TopLevelWindowLockerImpl>())
+{
+}
+
+void TopLevelWindowLocker::incBusy(const weld::Widget* pIgnore)
+{
+ // lock any toplevel windows from being closed until busy is over
+ std::vector<VclPtr<vcl::Window>> aTopLevels;
+ vcl::Window *pTopWin = Application::GetFirstTopLevelWindow();
+ while (pTopWin)
+ {
+ vcl::Window* pCandidate = pTopWin;
+ if (pCandidate->GetType() == WindowType::BORDERWINDOW)
+ pCandidate = pCandidate->GetWindow(GetWindowType::FirstChild);
+ // tdf#125266 ignore HelpTextWindows
+ if (pCandidate &&
+ pCandidate->GetType() != WindowType::HELPTEXTWINDOW &&
+ pCandidate->GetType() != WindowType::FLOATINGWINDOW &&
+ pCandidate->GetFrameWeld() != pIgnore)
+ {
+ aTopLevels.push_back(pCandidate);
+ }
+ pTopWin = Application::GetNextTopLevelWindow(pTopWin);
+ }
+ for (auto& a : aTopLevels)
+ {
+ a->IncModalCount();
+ a->ImplGetFrame()->NotifyModalHierarchy(true);
+ }
+ m_xImpl->m_aBusyStack.push(aTopLevels);
+}
+
+void TopLevelWindowLocker::decBusy()
+{
+ // unlock locked toplevel windows from being closed now busy is over
+ for (auto& a : m_xImpl->m_aBusyStack.top())
+ {
+ if (a->isDisposed())
+ continue;
+ a->DecModalCount();
+ a->ImplGetFrame()->NotifyModalHierarchy(false);
+ }
+ m_xImpl->m_aBusyStack.pop();
+}
+
+bool TopLevelWindowLocker::isBusy() const
+{
+ return !m_xImpl->m_aBusyStack.empty();
+}
+
+TopLevelWindowLocker::~TopLevelWindowLocker()
+{
+}
+
+void Dialog::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ SystemWindow::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("title", GetText());
+ if (vcl::Window* pActionArea = get_action_area())
+ {
+ if (!pActionArea->IsVisible())
+ rJsonWriter.put("collapsed", "true");
+ }
+
+ OUString sDialogId = OStringToOUString(GetHelpId(), RTL_TEXTENCODING_ASCII_US);
+ sal_Int32 nStartPos = sDialogId.lastIndexOf('/');
+ nStartPos = nStartPos >= 0 ? nStartPos + 1 : 0;
+ rJsonWriter.put("dialogid", sDialogId.copy(nStartPos));
+
+ {
+ auto aResponses = rJsonWriter.startArray("responses");
+ for (const auto& rResponse : mpDialogImpl->maResponses)
+ {
+ auto aResponse = rJsonWriter.startStruct();
+ rJsonWriter.put("id", rResponse.first->get_id());
+ rJsonWriter.put("response", rResponse.second);
+ }
+ }
+
+ vcl::Window* pFocusControl = GetFirstControlForFocus();
+ if (pFocusControl)
+ rJsonWriter.put("init_focus_id", pFocusControl->get_id());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dlgctrl.cxx b/vcl/source/window/dlgctrl.cxx
new file mode 100644
index 000000000..5aac19d02
--- /dev/null
+++ b/vcl/source/window/dlgctrl.cxx
@@ -0,0 +1,1159 @@
+/* -*- 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 <svdata.hxx>
+#include <window.h>
+
+#include "dlgctrl.hxx"
+#include <vcl/event.hxx>
+#include <vcl/toolkit/fixed.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/tabpage.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/settings.hxx>
+#include <sal/log.hxx>
+#include <i18nlangtag/languagetag.hxx>
+
+#include <com/sun/star/i18n/XCharacterClassification.hpp>
+
+using namespace ::com::sun::star;
+
+static bool ImplHasIndirectTabParent( vcl::Window* pWindow )
+{
+ // The window has indirect tab parent if it is included in tab hierarchy
+ // of the indirect parent window
+
+ vcl::Window* pNonLayoutParent = getNonLayoutParent(pWindow);
+ return ( pNonLayoutParent
+ && ( pNonLayoutParent->ImplGetWindow()->GetStyle() & WB_CHILDDLGCTRL ) );
+}
+
+static vcl::Window* ImplGetTopParentOfTabHierarchy( vcl::Window* pParent )
+{
+ // The method allows to find the most close parent containing all the
+ // window from the current tab-hierarchy
+ // The direct parent should be provided as a parameter here
+
+ vcl::Window* pResult = pParent;
+
+ if ( pResult )
+ {
+ vcl::Window* pNonLayoutParent = getNonLayoutParent(pResult);
+ while ( pNonLayoutParent && ( pResult->ImplGetWindow()->GetStyle() & WB_CHILDDLGCTRL ) )
+ {
+ pResult = pNonLayoutParent;
+ pNonLayoutParent = getNonLayoutParent(pResult);
+ }
+ }
+
+ return pResult;
+}
+
+static vcl::Window* ImplGetCurTabWindow(const vcl::Window* pWindow)
+{
+ assert(pWindow->GetType() == WindowType::TABCONTROL);
+ const TabControl* pTabControl = static_cast<const TabControl*>(pWindow);
+ // Check if the TabPage is a Child of the TabControl and still exists (by
+ // walking all child windows); because it could be that the TabPage has been
+ // destroyed already by a Dialog-Dtor, event that the TabControl still exists.
+ const TabPage* pTempTabPage = pTabControl->GetTabPage(pTabControl->GetCurPageId());
+ if (pTempTabPage)
+ {
+ vcl::Window* pTempWindow = pTabControl->GetWindow(GetWindowType::FirstChild);
+ while (pTempWindow)
+ {
+ if (pTempWindow->ImplGetWindow() == pTempTabPage)
+ {
+ return const_cast<TabPage*>(pTempTabPage);
+ }
+ pTempWindow = nextLogicalChildOfParent(pTabControl, pTempWindow);
+ }
+ }
+
+ return nullptr;
+}
+
+static vcl::Window* ImplGetSubChildWindow( vcl::Window* pParent, sal_uInt16 n, sal_uInt16& nIndex )
+{
+ // ignore all windows with mpClientWindow set
+ for (vcl::Window *pNewParent = pParent->ImplGetWindow();
+ pParent != pNewParent; pParent = pNewParent);
+
+ vcl::Window* pFoundWindow = nullptr;
+ vcl::Window* pWindow = firstLogicalChildOfParent(pParent);
+ vcl::Window* pNextWindow = pWindow;
+
+ // process just the current page of a tab control
+ if (pWindow && pParent->GetType() == WindowType::TABCONTROL)
+ {
+ pWindow = ImplGetCurTabWindow(pParent);
+ pNextWindow = lastLogicalChildOfParent(pParent);
+ }
+
+ while (pWindow)
+ {
+ pWindow = pWindow->ImplGetWindow();
+
+ // skip invisible and disabled windows
+ if (isVisibleInLayout(pWindow))
+ {
+ // return the TabControl itself, before handling its page
+ if (pWindow->GetType() == WindowType::TABCONTROL)
+ {
+ if (n == nIndex)
+ return pWindow;
+ ++nIndex;
+ }
+ if (pWindow->GetStyle() & (WB_DIALOGCONTROL | WB_CHILDDLGCTRL))
+ pFoundWindow = ImplGetSubChildWindow(pWindow, n, nIndex);
+ else
+ pFoundWindow = pWindow;
+
+ if (n == nIndex)
+ return pFoundWindow;
+ ++nIndex;
+ }
+
+ pWindow = nextLogicalChildOfParent(pParent, pNextWindow);
+ pNextWindow = pWindow;
+ }
+
+ --nIndex;
+ assert(!pFoundWindow || (pFoundWindow == pFoundWindow->ImplGetWindow()));
+ return pFoundWindow;
+}
+
+vcl::Window* ImplGetChildWindow( vcl::Window* pParent, sal_uInt16 n, sal_uInt16& nIndex, bool bTestEnable )
+{
+ pParent = ImplGetTopParentOfTabHierarchy( pParent );
+
+ nIndex = 0;
+ vcl::Window* pWindow = ImplGetSubChildWindow( pParent, n, nIndex );
+ if ( bTestEnable )
+ {
+ sal_uInt16 n2 = nIndex;
+ while ( pWindow && (!isEnabledInLayout(pWindow) || !pWindow->IsInputEnabled()) )
+ {
+ n2 = nIndex+1;
+ nIndex = 0;
+ pWindow = ImplGetSubChildWindow( pParent, n2, nIndex );
+ if ( nIndex < n2 )
+ break;
+ }
+
+ if ( (nIndex < n2) && n )
+ {
+ do
+ {
+ n--;
+ nIndex = 0;
+ pWindow = ImplGetSubChildWindow( pParent, n, nIndex );
+ }
+ while ( pWindow && n && (!isEnabledInLayout(pWindow) || !pWindow->IsInputEnabled()) );
+ }
+ }
+ return pWindow;
+}
+
+static vcl::Window* ImplGetNextWindow( vcl::Window* pParent, sal_uInt16 n, sal_uInt16& nIndex, bool bTestEnable )
+{
+ vcl::Window* pWindow = ImplGetChildWindow( pParent, n+1, nIndex, bTestEnable );
+ if ( n == nIndex )
+ {
+ n = 0;
+ pWindow = ImplGetChildWindow( pParent, n, nIndex, bTestEnable );
+ }
+ return pWindow;
+}
+
+namespace vcl {
+
+static bool lcl_ToolBoxTabStop( Window* pWindow )
+{
+ ToolBox* pToolBoxWindow = static_cast<ToolBox*>( pWindow );
+
+ for ( ToolBox::ImplToolItems::size_type nPos = 0; nPos < pToolBoxWindow->GetItemCount(); nPos++ )
+ {
+ ToolBoxItemId nId = pToolBoxWindow->GetItemId( nPos );
+ if ( pToolBoxWindow->IsItemVisible( nId ) && pToolBoxWindow->IsItemEnabled( nId ) )
+ return true;
+ }
+
+ return false;
+}
+
+vcl::Window* Window::ImplGetDlgWindow( sal_uInt16 nIndex, GetDlgWindowType nType,
+ sal_uInt16 nFormStart, sal_uInt16 nFormEnd,
+ sal_uInt16* pIndex )
+{
+ SAL_WARN_IF( (nIndex < nFormStart) || (nIndex > nFormEnd), "vcl",
+ "Window::ImplGetDlgWindow() - nIndex not in Form" );
+
+ vcl::Window* pWindow = nullptr;
+ sal_uInt16 i;
+ sal_uInt16 nTemp;
+ sal_uInt16 nStartIndex;
+
+ if ( nType == GetDlgWindowType::Prev )
+ {
+ i = nIndex;
+ do
+ {
+ if ( i > nFormStart )
+ i--;
+ else
+ i = nFormEnd;
+ pWindow = ImplGetChildWindow( this, i, nTemp, true );
+ if ( !pWindow )
+ break;
+ if ( (i == nTemp) && (pWindow->GetStyle() & WB_TABSTOP) )
+ {
+ if ( WindowType::TOOLBOX == pWindow->GetType() )
+ {
+ if ( lcl_ToolBoxTabStop( pWindow ) )
+ break;
+ }
+ else
+ break;
+ }
+ }
+ while ( i != nIndex );
+ }
+ else
+ {
+ i = nIndex;
+ pWindow = ImplGetChildWindow( this, i, i, (nType == GetDlgWindowType::First) );
+ if ( pWindow )
+ {
+ nStartIndex = i;
+
+ if ( nType == GetDlgWindowType::Next )
+ {
+ if ( i < nFormEnd )
+ {
+ pWindow = ImplGetNextWindow( this, i, i, true );
+ if ( (i > nFormEnd) || (i < nFormStart) )
+ pWindow = ImplGetChildWindow( this, nFormStart, i, true );
+ }
+ else
+ pWindow = ImplGetChildWindow( this, nFormStart, i, true );
+ }
+
+ if (i <= nFormEnd && pWindow)
+ {
+ // carry the 2nd index, in case all controls are disabled
+ sal_uInt16 nStartIndex2 = i;
+ sal_uInt16 nOldIndex = i+1;
+
+ do
+ {
+ if ( pWindow->GetStyle() & WB_TABSTOP )
+ {
+ if ( WindowType::TOOLBOX == pWindow->GetType() )
+ {
+ if ( lcl_ToolBoxTabStop( pWindow ) )
+ break;
+ }
+ else
+ break;
+ }
+ if( i == nOldIndex ) // only disabled controls ?
+ {
+ i = nStartIndex2;
+ break;
+ }
+ nOldIndex = i;
+ if ( (i > nFormEnd) || (i < nFormStart) )
+ pWindow = ImplGetChildWindow( this, nFormStart, i, true );
+ else
+ pWindow = ImplGetNextWindow( this, i, i, true );
+ }
+ while (i != nStartIndex && i != nStartIndex2 && pWindow);
+
+ if ( (i == nStartIndex2) && pWindow &&
+ (!(pWindow->GetStyle() & WB_TABSTOP) || !isEnabledInLayout(pWindow)) )
+ i = nStartIndex;
+ }
+ }
+
+ if ( nType == GetDlgWindowType::First )
+ {
+ if ( pWindow )
+ {
+ if ( pWindow->GetType() == WindowType::TABCONTROL )
+ {
+ vcl::Window* pNextWindow = ImplGetDlgWindow( i, GetDlgWindowType::Next );
+ if ( pNextWindow )
+ {
+ if ( pWindow->IsChild( pNextWindow ) )
+ pWindow = pNextWindow;
+ }
+ }
+
+ if ( !(pWindow->GetStyle() & WB_TABSTOP) )
+ pWindow = nullptr;
+ }
+ }
+ }
+
+ if ( pIndex )
+ *pIndex = i;
+
+ return pWindow;
+}
+
+} /* namespace vcl */
+
+vcl::Window* ImplFindDlgCtrlWindow( vcl::Window* pParent, vcl::Window* pWindow, sal_uInt16& rIndex,
+ sal_uInt16& rFormStart, sal_uInt16& rFormEnd )
+{
+ vcl::Window* pSWindow;
+ vcl::Window* pSecondWindow = nullptr;
+ vcl::Window* pTempWindow = nullptr;
+ sal_uInt16 i;
+ sal_uInt16 nSecond_i = 0;
+ sal_uInt16 nFormStart = 0;
+ sal_uInt16 nSecondFormStart = 0;
+ sal_uInt16 nFormEnd;
+
+ // find focus window in the child list
+ vcl::Window* pFirstChildWindow = pSWindow = ImplGetChildWindow( pParent, 0, i, false );
+
+ if( pWindow == nullptr )
+ pWindow = pSWindow;
+
+ while ( pSWindow )
+ {
+ // the DialogControlStart mark is only accepted for the direct children
+ if ( !ImplHasIndirectTabParent( pSWindow )
+ && pSWindow->ImplGetWindow()->IsDialogControlStart() )
+ nFormStart = i;
+
+ // SecondWindow for composite controls like ComboBoxes and arrays
+ if ( pSWindow->ImplIsWindowOrChild( pWindow ) )
+ {
+ pSecondWindow = pSWindow;
+ nSecond_i = i;
+ nSecondFormStart = nFormStart;
+ if ( pSWindow == pWindow )
+ break;
+ }
+
+ pSWindow = ImplGetNextWindow( pParent, i, i, false );
+ if ( !i )
+ pSWindow = nullptr;
+ }
+
+ if ( !pSWindow )
+ {
+ // Window not found; we cannot handle it
+ if ( !pSecondWindow )
+ return nullptr;
+ else
+ {
+ pSWindow = pSecondWindow;
+ i = nSecond_i;
+ nFormStart = nSecondFormStart;
+ }
+ }
+
+ // initialize
+ rIndex = i;
+ rFormStart = nFormStart;
+
+ // find end of template
+ sal_Int32 nIteration = 0;
+ do
+ {
+ nFormEnd = i;
+ pTempWindow = ImplGetNextWindow( pParent, i, i, false );
+
+ // the DialogControlStart mark is only accepted for the direct children
+ if ( !i
+ || ( pTempWindow && !ImplHasIndirectTabParent( pTempWindow )
+ && pTempWindow->ImplGetWindow()->IsDialogControlStart() ) )
+ break;
+
+ if ( pTempWindow && pTempWindow == pFirstChildWindow )
+ {
+ // It is possible to go through the begin of hierarchy once
+ // while looking for DialogControlStart mark.
+ // If it happens second time, it looks like an endless loop,
+ // that should be impossible, but just for the case...
+ nIteration++;
+ if ( nIteration >= 2 )
+ {
+ // this is an unexpected scenario
+ SAL_WARN( "vcl", "It seems to be an endless loop!" );
+ rFormStart = 0;
+ break;
+ }
+ }
+ }
+ while ( pTempWindow );
+ rFormEnd = nFormEnd;
+
+ return pSWindow;
+}
+
+vcl::Window* ImplFindAccelWindow( vcl::Window* pParent, sal_uInt16& rIndex, sal_Unicode cCharCode,
+ sal_uInt16 nFormStart, sal_uInt16 nFormEnd, bool bCheckEnable )
+{
+ SAL_WARN_IF( (rIndex < nFormStart) || (rIndex > nFormEnd), "vcl",
+ "Window::ImplFindAccelWindow() - rIndex not in Form" );
+
+ sal_Unicode cCompareChar;
+ sal_uInt16 nStart = rIndex;
+ sal_uInt16 i = rIndex;
+ vcl::Window* pWindow;
+
+ uno::Reference<i18n::XCharacterClassification> const& xCharClass(ImplGetCharClass());
+
+ const css::lang::Locale& rLocale = Application::GetSettings().GetUILanguageTag().getLocale();
+ cCharCode = xCharClass->toUpper( OUString(cCharCode), 0, 1, rLocale )[0];
+
+ if ( i < nFormEnd )
+ pWindow = ImplGetNextWindow( pParent, i, i, true );
+ else
+ pWindow = ImplGetChildWindow( pParent, nFormStart, i, true );
+ while( pWindow )
+ {
+ const OUString aStr = pWindow->GetText();
+ sal_Int32 nPos = aStr.indexOf( '~' );
+ while (nPos != -1)
+ {
+ cCompareChar = aStr[nPos+1];
+ cCompareChar = xCharClass->toUpper( OUString(cCompareChar), 0, 1, rLocale )[0];
+ if ( cCompareChar == cCharCode )
+ {
+ if (pWindow->GetType() == WindowType::FIXEDTEXT)
+ {
+ FixedText *pFixedText = static_cast<FixedText*>(pWindow);
+ vcl::Window *pMnemonicWidget = pFixedText->get_mnemonic_widget();
+ SAL_WARN_IF(isContainerWindow(pFixedText->GetParent()) && !pMnemonicWidget,
+ "vcl.a11y", "label missing mnemonic_widget?");
+ if (pMnemonicWidget)
+ return pMnemonicWidget;
+ }
+
+ // skip Static-Controls
+ if ( (pWindow->GetType() == WindowType::FIXEDTEXT) ||
+ (pWindow->GetType() == WindowType::FIXEDLINE) ||
+ (pWindow->GetType() == WindowType::GROUPBOX) )
+ pWindow = pParent->ImplGetDlgWindow( i, GetDlgWindowType::Next );
+ rIndex = i;
+ return pWindow;
+ }
+ nPos = aStr.indexOf( '~', nPos+1 );
+ }
+
+ // #i93011# it would have made sense to have this really recursive
+ // right from the start. However this would cause unpredictable side effects now
+ // so instead we have a style bit for some child windows, that want their
+ // children checked for accelerators
+ if( (pWindow->GetStyle() & WB_CHILDDLGCTRL) != 0 )
+ {
+ sal_uInt16 nChildIndex;
+ sal_uInt16 nChildFormStart;
+ sal_uInt16 nChildFormEnd;
+
+ // get form start and end
+ ::ImplFindDlgCtrlWindow( pWindow, nullptr,
+ nChildIndex, nChildFormStart, nChildFormEnd );
+ vcl::Window* pAccelWin = ImplFindAccelWindow( pWindow, nChildIndex, cCharCode,
+ nChildFormStart, nChildFormEnd,
+ bCheckEnable );
+ if( pAccelWin )
+ return pAccelWin;
+ }
+
+ if ( i == nStart )
+ break;
+
+ if ( i < nFormEnd )
+ {
+ pWindow = ImplGetNextWindow( pParent, i, i, bCheckEnable );
+ if( ! pWindow )
+ pWindow = ImplGetChildWindow( pParent, nFormStart, i, bCheckEnable );
+ }
+ else
+ pWindow = ImplGetChildWindow( pParent, nFormStart, i, bCheckEnable );
+ }
+
+ return nullptr;
+}
+
+namespace vcl {
+
+void Window::SetMnemonicActivateHdl(const Link<vcl::Window&, bool>& rLink)
+{
+ if (mpWindowImpl) // may be called after dispose
+ {
+ mpWindowImpl->maMnemonicActivateHdl = rLink;
+ }
+}
+
+void Window::ImplControlFocus( GetFocusFlags nFlags )
+{
+ if ( nFlags & GetFocusFlags::Mnemonic )
+ {
+ if (mpWindowImpl->maMnemonicActivateHdl.Call(*this))
+ return;
+
+ const bool bUniqueMnemonic(nFlags & GetFocusFlags::UniqueMnemonic);
+
+ if ( GetType() == WindowType::RADIOBUTTON )
+ {
+ if (bUniqueMnemonic && !static_cast<RadioButton*>(this)->IsChecked())
+ static_cast<RadioButton*>(this)->ImplCallClick( true, nFlags );
+ else
+ ImplGrabFocus( nFlags );
+ }
+ else
+ {
+ ImplGrabFocus( nFlags );
+ if (bUniqueMnemonic)
+ {
+ if ( GetType() == WindowType::CHECKBOX )
+ static_cast<CheckBox*>(this)->ImplCheck();
+ else if ( mpWindowImpl->mbPushButton )
+ {
+ static_cast<PushButton*>(this)->SetPressed( true );
+ static_cast<PushButton*>(this)->SetPressed( false );
+ static_cast<PushButton*>(this)->Click();
+ }
+ }
+ }
+ }
+ else
+ {
+ if ( GetType() == WindowType::RADIOBUTTON )
+ {
+ if ( !static_cast<RadioButton*>(this)->IsChecked() )
+ static_cast<RadioButton*>(this)->ImplCallClick( true, nFlags );
+ else
+ ImplGrabFocus( nFlags );
+ }
+ else
+ ImplGrabFocus( nFlags );
+ }
+}
+
+} /* namespace vcl */
+
+namespace
+{
+ bool isSuitableDestination(vcl::Window const *pWindow)
+ {
+ return (pWindow && isVisibleInLayout(pWindow) &&
+ isEnabledInLayout(pWindow) && pWindow->IsInputEnabled() &&
+ //Pure window shouldn't get window after controls such as
+ //buttons.
+ (pWindow->GetType() != WindowType::WINDOW &&
+ pWindow->GetType() != WindowType::WORKWINDOW && pWindow->GetType() != WindowType::CONTROL)
+ );
+ }
+
+ bool focusNextInGroup(const std::vector<VclPtr<RadioButton> >::iterator& aStart, std::vector<VclPtr<RadioButton> > &rGroup)
+ {
+ std::vector<VclPtr<RadioButton> >::iterator aI(aStart);
+
+ if (aStart != rGroup.end())
+ ++aI;
+
+ aI = std::find_if(aI, rGroup.end(), isSuitableDestination);
+ if (aI != rGroup.end())
+ {
+ vcl::Window *pWindow = *aI;
+ pWindow->ImplControlFocus( GetFocusFlags::CURSOR | GetFocusFlags::Forward );
+ return true;
+ }
+ aI = std::find_if(rGroup.begin(), aStart, isSuitableDestination);
+ if (aI != aStart)
+ {
+ vcl::Window *pWindow = *aI;
+ pWindow->ImplControlFocus( GetFocusFlags::CURSOR | GetFocusFlags::Forward );
+ return true;
+ }
+ return false;
+ }
+
+ bool nextInGroup(RadioButton *pSourceWindow, bool bBackward)
+ {
+ std::vector<VclPtr<RadioButton> > aGroup(pSourceWindow->GetRadioButtonGroup());
+
+ if (aGroup.size() < 2) // have to have at last 2 buttons to be a useful group
+ return false;
+
+ if (bBackward)
+ std::reverse(aGroup.begin(), aGroup.end());
+
+ auto aStart(std::find(aGroup.begin(), aGroup.end(), VclPtr<RadioButton>(pSourceWindow)));
+
+ assert(aStart != aGroup.end());
+
+ return focusNextInGroup(aStart, aGroup);
+ }
+}
+
+namespace vcl {
+
+bool Window::ImplDlgCtrl( const KeyEvent& rKEvt, bool bKeyInput )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+ vcl::Window* pSWindow;
+ vcl::Window* pTempWindow;
+ vcl::Window* pButtonWindow;
+ sal_uInt16 i;
+ sal_uInt16 iButton;
+ sal_uInt16 iButtonStart;
+ sal_uInt16 iTemp;
+ sal_uInt16 nIndex;
+ sal_uInt16 nFormStart;
+ sal_uInt16 nFormEnd;
+ DialogControlFlags nDlgCtrlFlags;
+
+ // we cannot take over control without Focus-window
+ vcl::Window* pFocusWindow = Application::GetFocusWindow();
+ if ( !pFocusWindow || !ImplIsWindowOrChild( pFocusWindow ) )
+ return false;
+
+ // find Focus-Window in the child list
+ pSWindow = ::ImplFindDlgCtrlWindow( this, pFocusWindow,
+ nIndex, nFormStart, nFormEnd );
+ if ( !pSWindow )
+ return false;
+ i = nIndex;
+
+ nDlgCtrlFlags = DialogControlFlags::NONE;
+ pTempWindow = pSWindow;
+ do
+ {
+ nDlgCtrlFlags |= pTempWindow->GetDialogControlFlags();
+ if ( pTempWindow == this )
+ break;
+ pTempWindow = pTempWindow->ImplGetParent();
+ }
+ while ( pTempWindow );
+
+ pButtonWindow = nullptr;
+
+ if ( nKeyCode == KEY_RETURN )
+ {
+ // search first for a DefPushButton/CancelButton
+ pButtonWindow = ImplGetChildWindow( this, nFormStart, iButton, true );
+ iButtonStart = iButton;
+ while ( pButtonWindow )
+ {
+ if ( (pButtonWindow->GetStyle() & WB_DEFBUTTON) &&
+ pButtonWindow->mpWindowImpl->mbPushButton )
+ break;
+
+ pButtonWindow = ImplGetNextWindow( this, iButton, iButton, true );
+ if ( (iButton <= iButtonStart) || (iButton > nFormEnd) )
+ pButtonWindow = nullptr;
+ }
+
+ if ( bKeyInput && !pButtonWindow && (nDlgCtrlFlags & DialogControlFlags::Return) )
+ {
+ GetDlgWindowType nType;
+ GetFocusFlags nGetFocusFlags = GetFocusFlags::Tab;
+ sal_uInt16 nNewIndex;
+ sal_uInt16 iStart;
+ if ( aKeyCode.IsShift() )
+ {
+ nType = GetDlgWindowType::Prev;
+ nGetFocusFlags |= GetFocusFlags::Backward;
+ }
+ else
+ {
+ nType = GetDlgWindowType::Next;
+ nGetFocusFlags |= GetFocusFlags::Forward;
+ }
+ iStart = i;
+ pTempWindow = ImplGetDlgWindow( i, nType, nFormStart, nFormEnd, &nNewIndex );
+ while ( pTempWindow && (pTempWindow != pSWindow) )
+ {
+ if ( !pTempWindow->mpWindowImpl->mbPushButton )
+ {
+ // get Around-Flag
+ if ( nType == GetDlgWindowType::Prev )
+ {
+ if ( nNewIndex > iStart )
+ nGetFocusFlags |= GetFocusFlags::Around;
+ }
+ else
+ {
+ if ( nNewIndex < iStart )
+ nGetFocusFlags |= GetFocusFlags::Around;
+ }
+ pTempWindow->ImplControlFocus( nGetFocusFlags );
+ return true;
+ }
+ else
+ {
+ i = nNewIndex;
+ pTempWindow = ImplGetDlgWindow( i, nType, nFormStart, nFormEnd, &nNewIndex );
+ }
+ if ( (i <= iStart) || (i > nFormEnd) )
+ pTempWindow = nullptr;
+ }
+ // if this is the same window, simulate a Get/LoseFocus,
+ // in case AROUND is being processed
+ if ( pTempWindow && (pTempWindow == pSWindow) )
+ {
+ NotifyEvent aNEvt1( MouseNotifyEvent::LOSEFOCUS, pSWindow );
+ if ( !ImplCallPreNotify( aNEvt1 ) )
+ pSWindow->CompatLoseFocus();
+ pSWindow->mpWindowImpl->mnGetFocusFlags = nGetFocusFlags | GetFocusFlags::Around;
+ NotifyEvent aNEvt2( MouseNotifyEvent::GETFOCUS, pSWindow );
+ if ( !ImplCallPreNotify( aNEvt2 ) )
+ pSWindow->CompatGetFocus();
+ pSWindow->mpWindowImpl->mnGetFocusFlags = GetFocusFlags::NONE;
+ return true;
+ }
+ }
+ }
+ else if ( nKeyCode == KEY_ESCAPE )
+ {
+ // search first for a DefPushButton/CancelButton
+ pButtonWindow = ImplGetChildWindow( this, nFormStart, iButton, true );
+ iButtonStart = iButton;
+ while ( pButtonWindow )
+ {
+ if ( pButtonWindow->GetType() == WindowType::CANCELBUTTON )
+ break;
+
+ pButtonWindow = ImplGetNextWindow( this, iButton, iButton, true );
+ if ( (iButton <= iButtonStart) || (iButton > nFormEnd) )
+ pButtonWindow = nullptr;
+ }
+
+ if ( bKeyInput && mpWindowImpl->mpDlgCtrlDownWindow )
+ {
+ if ( mpWindowImpl->mpDlgCtrlDownWindow.get() != pButtonWindow )
+ {
+ static_cast<PushButton*>(mpWindowImpl->mpDlgCtrlDownWindow.get())->SetPressed( false );
+ mpWindowImpl->mpDlgCtrlDownWindow = nullptr;
+ return true;
+ }
+ }
+ }
+ else if ( bKeyInput )
+ {
+ if ( nKeyCode == KEY_TAB )
+ {
+ // do not skip Alt key, for MS Windows
+ if ( !aKeyCode.IsMod2() )
+ {
+ sal_uInt16 nNewIndex;
+ bool bForm = false;
+
+ // for Ctrl-Tab check if we want to jump to next template
+ if ( aKeyCode.IsMod1() )
+ {
+ // search group
+ vcl::Window* pFormFirstWindow = nullptr;
+ vcl::Window* pLastFormFirstWindow = nullptr;
+ pTempWindow = ImplGetChildWindow( this, 0, iTemp, false );
+ vcl::Window* pPrevFirstFormFirstWindow = nullptr;
+ vcl::Window* pFirstFormFirstWindow = pTempWindow;
+ while ( pTempWindow )
+ {
+ if ( pTempWindow->ImplGetWindow()->IsDialogControlStart() )
+ {
+ if ( iTemp != 0 )
+ bForm = true;
+ if ( aKeyCode.IsShift() )
+ {
+ if ( iTemp <= nIndex )
+ pFormFirstWindow = pPrevFirstFormFirstWindow;
+ pPrevFirstFormFirstWindow = pTempWindow;
+ }
+ else
+ {
+ if ( (iTemp > nIndex) && !pFormFirstWindow )
+ pFormFirstWindow = pTempWindow;
+ }
+ pLastFormFirstWindow = pTempWindow;
+ }
+
+ pTempWindow = ImplGetNextWindow( this, iTemp, iTemp, false );
+ if ( !iTemp )
+ pTempWindow = nullptr;
+ }
+
+ if ( bForm )
+ {
+ if ( !pFormFirstWindow )
+ {
+ if ( aKeyCode.IsShift() )
+ pFormFirstWindow = pLastFormFirstWindow;
+ else
+ pFormFirstWindow = pFirstFormFirstWindow;
+ }
+
+ sal_uInt16 nFoundFormStart = 0;
+ sal_uInt16 nFoundFormEnd = 0;
+ sal_uInt16 nTempIndex = 0;
+ if ( ::ImplFindDlgCtrlWindow( this, pFormFirstWindow, nTempIndex,
+ nFoundFormStart, nFoundFormEnd ) )
+ {
+ nTempIndex = nFoundFormStart;
+ pFormFirstWindow = ImplGetDlgWindow( nTempIndex, GetDlgWindowType::First, nFoundFormStart, nFoundFormEnd );
+ if ( pFormFirstWindow )
+ {
+ pFormFirstWindow->ImplControlFocus();
+ return true;
+ }
+ }
+ }
+ }
+
+ if ( !bForm )
+ {
+ // Only use Ctrl-TAB if it was allowed for the whole
+ // dialog or for the current control (#103667#)
+ if (!aKeyCode.IsMod1() || (pSWindow->GetStyle() & WB_NODIALOGCONTROL))
+ {
+ GetDlgWindowType nType;
+ GetFocusFlags nGetFocusFlags = GetFocusFlags::Tab;
+ if ( aKeyCode.IsShift() )
+ {
+ nType = GetDlgWindowType::Prev;
+ nGetFocusFlags |= GetFocusFlags::Backward;
+ }
+ else
+ {
+ nType = GetDlgWindowType::Next;
+ nGetFocusFlags |= GetFocusFlags::Forward;
+ }
+ vcl::Window* pWindow = ImplGetDlgWindow( i, nType, nFormStart, nFormEnd, &nNewIndex );
+ // if this is the same window, simulate a Get/LoseFocus,
+ // in case AROUND is being processed
+ if ( pWindow == pSWindow )
+ {
+ NotifyEvent aNEvt1( MouseNotifyEvent::LOSEFOCUS, pSWindow );
+ if ( !ImplCallPreNotify( aNEvt1 ) )
+ pSWindow->CompatLoseFocus();
+ pSWindow->mpWindowImpl->mnGetFocusFlags = nGetFocusFlags | GetFocusFlags::Around;
+ NotifyEvent aNEvt2( MouseNotifyEvent::GETFOCUS, pSWindow );
+ if ( !ImplCallPreNotify( aNEvt2 ) )
+ pSWindow->CompatGetFocus();
+ pSWindow->mpWindowImpl->mnGetFocusFlags = GetFocusFlags::NONE;
+ return true;
+ }
+ else if ( pWindow )
+ {
+ // get Around-Flag
+ if ( nType == GetDlgWindowType::Prev )
+ {
+ if ( nNewIndex > i )
+ nGetFocusFlags |= GetFocusFlags::Around;
+ }
+ else
+ {
+ if ( nNewIndex < i )
+ nGetFocusFlags |= GetFocusFlags::Around;
+ }
+ pWindow->ImplControlFocus( nGetFocusFlags );
+ return true;
+ }
+ }
+ }
+ }
+ }
+ else if ( (nKeyCode == KEY_LEFT) || (nKeyCode == KEY_UP) )
+ {
+ if (pSWindow->GetType() == WindowType::RADIOBUTTON)
+ return nextInGroup(static_cast<RadioButton*>(pSWindow), true);
+ else
+ {
+ WinBits nStyle = pSWindow->GetStyle();
+ if ( !(nStyle & WB_GROUP) )
+ {
+ vcl::Window* pWindow = prevLogicalChildOfParent(this, pSWindow);
+ while ( pWindow )
+ {
+ pWindow = pWindow->ImplGetWindow();
+
+ nStyle = pWindow->GetStyle();
+
+ if (isSuitableDestination(pWindow))
+ {
+ if ( pWindow != pSWindow )
+ pWindow->ImplControlFocus( GetFocusFlags::CURSOR | GetFocusFlags::Backward );
+ return true;
+ }
+
+ if ( nStyle & WB_GROUP )
+ break;
+
+ pWindow = prevLogicalChildOfParent(this, pWindow);
+ }
+ }
+ }
+ }
+ else if ( (nKeyCode == KEY_RIGHT) || (nKeyCode == KEY_DOWN) )
+ {
+ if (pSWindow->GetType() == WindowType::RADIOBUTTON)
+ return nextInGroup(static_cast<RadioButton*>(pSWindow), false);
+ else
+ {
+ vcl::Window* pWindow = nextLogicalChildOfParent(this, pSWindow);
+ while ( pWindow )
+ {
+ pWindow = pWindow->ImplGetWindow();
+
+ WinBits nStyle = pWindow->GetStyle();
+
+ if ( nStyle & WB_GROUP )
+ break;
+
+ if (isSuitableDestination(pWindow))
+ {
+ pWindow->ImplControlFocus( GetFocusFlags::CURSOR | GetFocusFlags::Backward );
+ return true;
+ }
+
+ pWindow = nextLogicalChildOfParent(this, pWindow);
+ }
+ }
+ }
+ else if (aKeyCode.IsMod2()) // tdf#151385
+ {
+ sal_Unicode c = rKEvt.GetCharCode();
+ if ( c )
+ {
+ pSWindow = ::ImplFindAccelWindow( this, i, c, nFormStart, nFormEnd );
+ if ( pSWindow )
+ {
+ GetFocusFlags nGetFocusFlags = GetFocusFlags::Mnemonic;
+ if ( pSWindow == ::ImplFindAccelWindow( this, i, c, nFormStart, nFormEnd ) )
+ nGetFocusFlags |= GetFocusFlags::UniqueMnemonic;
+ pSWindow->ImplControlFocus( nGetFocusFlags );
+ return true;
+ }
+ }
+ }
+ }
+
+ if (isSuitableDestination(pButtonWindow))
+ {
+ if ( bKeyInput )
+ {
+ if ( mpWindowImpl->mpDlgCtrlDownWindow && (mpWindowImpl->mpDlgCtrlDownWindow.get() != pButtonWindow) )
+ {
+ static_cast<PushButton*>(mpWindowImpl->mpDlgCtrlDownWindow.get())->SetPressed( false );
+ mpWindowImpl->mpDlgCtrlDownWindow = nullptr;
+ }
+
+ static_cast<PushButton*>(pButtonWindow)->SetPressed( true );
+ mpWindowImpl->mpDlgCtrlDownWindow = pButtonWindow;
+ }
+ else if ( mpWindowImpl->mpDlgCtrlDownWindow.get() == pButtonWindow )
+ {
+ mpWindowImpl->mpDlgCtrlDownWindow = nullptr;
+ static_cast<PushButton*>(pButtonWindow)->SetPressed( false );
+ static_cast<PushButton*>(pButtonWindow)->Click();
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+// checks if this window has dialog control
+bool Window::ImplHasDlgCtrl() const
+{
+ vcl::Window* pDlgCtrlParent;
+
+ // lookup window for dialog control
+ pDlgCtrlParent = ImplGetParent();
+ while ( pDlgCtrlParent &&
+ !pDlgCtrlParent->ImplIsOverlapWindow() &&
+ ((pDlgCtrlParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) != WB_DIALOGCONTROL) )
+ pDlgCtrlParent = pDlgCtrlParent->ImplGetParent();
+
+ return pDlgCtrlParent && ((pDlgCtrlParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL);
+}
+
+void Window::ImplDlgCtrlNextWindow()
+{
+ vcl::Window* pDlgCtrlParent;
+ vcl::Window* pDlgCtrl;
+ vcl::Window* pSWindow;
+ sal_uInt16 nIndex;
+ sal_uInt16 nFormStart;
+ sal_uInt16 nFormEnd;
+
+ // lookup window for dialog control
+ pDlgCtrl = this;
+ pDlgCtrlParent = ImplGetParent();
+ while ( pDlgCtrlParent &&
+ !pDlgCtrlParent->ImplIsOverlapWindow() &&
+ ((pDlgCtrlParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) != WB_DIALOGCONTROL) )
+ pDlgCtrlParent = pDlgCtrlParent->ImplGetParent();
+
+ if ( !pDlgCtrlParent || (GetStyle() & WB_NODIALOGCONTROL) || ((pDlgCtrlParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) != WB_DIALOGCONTROL) )
+ return;
+
+ // lookup window in child list
+ pSWindow = ::ImplFindDlgCtrlWindow( pDlgCtrlParent, pDlgCtrl,
+ nIndex, nFormStart, nFormEnd );
+ if ( !pSWindow )
+ return;
+
+ vcl::Window* pWindow = pDlgCtrlParent->ImplGetDlgWindow( nIndex, GetDlgWindowType::Next, nFormStart, nFormEnd );
+ if ( pWindow && (pWindow != pSWindow) )
+ pWindow->ImplControlFocus();
+}
+
+static void ImplDlgCtrlUpdateDefButton( vcl::Window* pParent, vcl::Window* pFocusWindow,
+ bool bGetFocus )
+{
+ PushButton* pOldDefButton = nullptr;
+ PushButton* pNewDefButton = nullptr;
+ vcl::Window* pSWindow;
+ sal_uInt16 i;
+ sal_uInt16 nFormStart;
+ sal_uInt16 nFormEnd;
+
+ // find template
+ pSWindow = ::ImplFindDlgCtrlWindow( pParent, pFocusWindow, i, nFormStart, nFormEnd );
+ if ( !pSWindow )
+ {
+ nFormStart = 0;
+ nFormEnd = 0xFFFF;
+ }
+
+ pSWindow = ImplGetChildWindow( pParent, nFormStart, i, false );
+ while ( pSWindow )
+ {
+ if ( pSWindow->ImplIsPushButton() )
+ {
+ PushButton* pPushButton = static_cast<PushButton*>(pSWindow);
+ if ( pPushButton->ImplIsDefButton() )
+ pOldDefButton = pPushButton;
+ if ( pPushButton->HasChildPathFocus() )
+ pNewDefButton = pPushButton;
+ else if ( !pNewDefButton && (pPushButton->GetStyle() & WB_DEFBUTTON) )
+ pNewDefButton = pPushButton;
+ }
+
+ pSWindow = ImplGetNextWindow( pParent, i, i, false );
+ if ( !i || (i > nFormEnd) )
+ pSWindow = nullptr;
+ }
+
+ if ( !bGetFocus )
+ {
+ sal_uInt16 nDummy;
+ vcl::Window* pNewFocusWindow = Application::GetFocusWindow();
+ if ( !pNewFocusWindow || !pParent->ImplIsWindowOrChild( pNewFocusWindow ) )
+ pNewDefButton = nullptr;
+ else if ( !::ImplFindDlgCtrlWindow( pParent, pNewFocusWindow, i, nDummy, nDummy ) ||
+ (i < nFormStart) || (i > nFormEnd) )
+ pNewDefButton = nullptr;
+ }
+
+ if ( pOldDefButton != pNewDefButton )
+ {
+ if ( pOldDefButton )
+ pOldDefButton->ImplSetDefButton( false );
+ if ( pNewDefButton )
+ pNewDefButton->ImplSetDefButton( true );
+ }
+}
+
+void Window::ImplDlgCtrlFocusChanged( vcl::Window* pWindow, bool bGetFocus )
+{
+ if ( mpWindowImpl->mpDlgCtrlDownWindow && !bGetFocus )
+ {
+ static_cast<PushButton*>(mpWindowImpl->mpDlgCtrlDownWindow.get())->SetPressed( false );
+ mpWindowImpl->mpDlgCtrlDownWindow = nullptr;
+ }
+
+ ImplDlgCtrlUpdateDefButton( this, pWindow, bGetFocus );
+}
+
+vcl::Window* Window::ImplFindDlgCtrlWindow( vcl::Window* pWindow )
+{
+ sal_uInt16 nIndex;
+ sal_uInt16 nFormStart;
+ sal_uInt16 nFormEnd;
+
+ // find Focus-Window in the Child-List and return
+ return ::ImplFindDlgCtrlWindow( this, pWindow, nIndex, nFormStart, nFormEnd );
+}
+
+KeyEvent Window::GetActivationKey() const
+{
+ KeyEvent aKeyEvent;
+
+ sal_Unicode nAccel = getAccel( GetText() );
+ if( ! nAccel )
+ {
+ vcl::Window* pWindow = GetAccessibleRelationLabeledBy();
+ if( pWindow )
+ nAccel = getAccel( pWindow->GetText() );
+ }
+ if( nAccel )
+ {
+ sal_uInt16 nCode = 0;
+ if( nAccel >= 'a' && nAccel <= 'z' )
+ nCode = KEY_A + (nAccel-'a');
+ else if( nAccel >= 'A' && nAccel <= 'Z' )
+ nCode = KEY_A + (nAccel-'A');
+ else if( nAccel >= '0' && nAccel <= '9' )
+ nCode = KEY_0 + (nAccel-'0');
+ else if( nAccel == '.' )
+ nCode = KEY_POINT;
+ else if( nAccel == '-' )
+ nCode = KEY_SUBTRACT;
+ vcl::KeyCode aKeyCode( nCode, false, false, true, false );
+ aKeyEvent = KeyEvent( nAccel, aKeyCode );
+ }
+ return aKeyEvent;
+}
+
+} /* namespace vcl */
+
+sal_Unicode getAccel( const OUString& rStr )
+{
+ sal_Unicode nChar = 0;
+ sal_Int32 nPos = 0;
+ do
+ {
+ nPos = rStr.indexOf( '~', nPos );
+ if( nPos != -1 && nPos < rStr.getLength() )
+ nChar = rStr[ ++nPos ];
+ else
+ nChar = 0;
+ } while( nChar == '~' );
+ return nChar;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dlgctrl.hxx b/vcl/source/window/dlgctrl.hxx
new file mode 100644
index 000000000..805099b69
--- /dev/null
+++ b/vcl/source/window/dlgctrl.hxx
@@ -0,0 +1,34 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <vcl/window.hxx>
+
+vcl::Window* ImplGetChildWindow( vcl::Window* pParent, sal_uInt16 n, sal_uInt16& nIndex, bool bTestEnable );
+
+vcl::Window* ImplFindDlgCtrlWindow( vcl::Window* pParent, vcl::Window* pWindow, sal_uInt16& rIndex,
+ sal_uInt16& rFormStart, sal_uInt16& rFormEnd );
+
+vcl::Window* ImplFindAccelWindow( vcl::Window* pParent, sal_uInt16& rIndex, sal_Unicode cCharCode,
+ sal_uInt16 nFormStart, sal_uInt16 nFormEnd, bool bCheckEnable = true );
+
+sal_Unicode getAccel( const OUString& rStr );
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dndeventdispatcher.cxx b/vcl/source/window/dndeventdispatcher.cxx
new file mode 100644
index 000000000..b8190a118
--- /dev/null
+++ b/vcl/source/window/dndeventdispatcher.cxx
@@ -0,0 +1,412 @@
+/* -*- 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 <dndeventdispatcher.hxx>
+#include <dndlistenercontainer.hxx>
+#include <sal/log.hxx>
+
+#include <osl/mutex.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+
+using namespace ::cppu;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::dnd;
+
+DNDEventDispatcher::DNDEventDispatcher( vcl::Window * pTopWindow ):
+ m_pTopWindow( pTopWindow ),
+ m_pCurrentWindow( nullptr )
+{
+}
+
+DNDEventDispatcher::~DNDEventDispatcher()
+{
+ designate_currentwindow(nullptr);
+}
+
+vcl::Window* DNDEventDispatcher::findTopLevelWindow(Point location)
+{
+ SolarMutexGuard aSolarGuard;
+
+ // find the window that is toplevel for this coordinates
+ // because those coordinates come from outside, they must be mirrored if RTL layout is active
+ if( AllSettings::GetLayoutRTL() )
+ m_pTopWindow->ImplMirrorFramePos( location );
+ vcl::Window * pChildWindow = m_pTopWindow->ImplFindWindow( location );
+
+ if( nullptr == pChildWindow )
+ pChildWindow = m_pTopWindow;
+
+ while( pChildWindow->ImplGetClientWindow() )
+ pChildWindow = pChildWindow->ImplGetClientWindow();
+
+ if( pChildWindow->GetOutDev()->ImplIsAntiparallel() )
+ {
+ const OutputDevice *pChildWinOutDev = pChildWindow->GetOutDev();
+ pChildWinOutDev->ReMirror( location );
+ }
+
+ return pChildWindow;
+}
+
+IMPL_LINK(DNDEventDispatcher, WindowEventListener, VclWindowEvent&, rEvent, void)
+{
+ if (rEvent.GetId() == VclEventId::ObjectDying)
+ {
+ designate_currentwindow(nullptr);
+ }
+}
+
+void DNDEventDispatcher::designate_currentwindow(vcl::Window *pWindow)
+{
+ if (m_pCurrentWindow)
+ m_pCurrentWindow->RemoveEventListener(LINK(this, DNDEventDispatcher, WindowEventListener));
+ m_pCurrentWindow = pWindow;
+ if (m_pCurrentWindow)
+ m_pCurrentWindow->AddEventListener(LINK(this, DNDEventDispatcher, WindowEventListener));
+}
+
+void SAL_CALL DNDEventDispatcher::drop( const DropTargetDropEvent& dtde )
+{
+ std::scoped_lock aImplGuard( m_aMutex );
+
+ Point location( dtde.LocationX, dtde.LocationY );
+
+ vcl::Window* pChildWindow = findTopLevelWindow(location);
+
+ // handle the case that drop is in another vcl window than the last dragOver
+ if( pChildWindow != m_pCurrentWindow.get() )
+ {
+ // fire dragExit on listeners of previous window
+ fireDragExitEvent( m_pCurrentWindow );
+
+ fireDragEnterEvent( pChildWindow, static_cast < XDropTargetDragContext * > (this),
+ dtde.DropAction, location, dtde.SourceActions, m_aDataFlavorList );
+ }
+
+ // send drop event to the child window
+ sal_Int32 nListeners = fireDropEvent( pChildWindow, dtde.Context, dtde.DropAction,
+ location, dtde.SourceActions, dtde.Transferable );
+
+ // reject drop if no listeners found
+ if( nListeners == 0 ) {
+ SAL_WARN( "vcl", "rejecting drop due to missing listeners." );
+ dtde.Context->rejectDrop();
+ }
+
+ // this is a drop -> no further drag overs
+ designate_currentwindow(nullptr);
+ m_aDataFlavorList.realloc( 0 );
+}
+
+void SAL_CALL DNDEventDispatcher::dragEnter( const DropTargetDragEnterEvent& dtdee )
+{
+ std::scoped_lock aImplGuard( m_aMutex );
+ Point location( dtdee.LocationX, dtdee.LocationY );
+
+ vcl::Window * pChildWindow = findTopLevelWindow(location);
+
+ // assume pointer write operation to be atomic
+ designate_currentwindow(pChildWindow);
+ m_aDataFlavorList = dtdee.SupportedDataFlavors;
+
+ // fire dragEnter on listeners of current window
+ sal_Int32 nListeners = fireDragEnterEvent( pChildWindow, dtdee.Context, dtdee.DropAction, location,
+ dtdee.SourceActions, dtdee.SupportedDataFlavors );
+
+ // reject drag if no listener found
+ if( nListeners == 0 ) {
+ SAL_WARN( "vcl", "rejecting drag enter due to missing listeners." );
+ dtdee.Context->rejectDrag();
+ }
+
+}
+
+void SAL_CALL DNDEventDispatcher::dragExit( const DropTargetEvent& /*dte*/ )
+{
+ std::scoped_lock aImplGuard( m_aMutex );
+
+ fireDragExitEvent( m_pCurrentWindow );
+
+ // reset member values
+ designate_currentwindow(nullptr);
+ m_aDataFlavorList.realloc( 0 );
+}
+
+void SAL_CALL DNDEventDispatcher::dragOver( const DropTargetDragEvent& dtde )
+{
+ std::scoped_lock aImplGuard( m_aMutex );
+
+ Point location( dtde.LocationX, dtde.LocationY );
+ sal_Int32 nListeners;
+
+ vcl::Window * pChildWindow = findTopLevelWindow(location);
+
+ if( pChildWindow != m_pCurrentWindow.get() )
+ {
+ // fire dragExit on listeners of previous window
+ fireDragExitEvent( m_pCurrentWindow );
+
+ // remember new window
+ designate_currentwindow(pChildWindow);
+
+ // fire dragEnter on listeners of current window
+ nListeners = fireDragEnterEvent( pChildWindow, dtde.Context, dtde.DropAction, location,
+ dtde.SourceActions, m_aDataFlavorList );
+ }
+ else
+ {
+ // fire dragOver on listeners of current window
+ nListeners = fireDragOverEvent( pChildWindow, dtde.Context, dtde.DropAction, location,
+ dtde.SourceActions );
+ }
+
+ // reject drag if no listener found
+ if( nListeners == 0 )
+ {
+ SAL_WARN( "vcl", "rejecting drag over due to missing listeners." );
+ dtde.Context->rejectDrag();
+ }
+}
+
+void SAL_CALL DNDEventDispatcher::dropActionChanged( const DropTargetDragEvent& dtde )
+{
+ std::scoped_lock aImplGuard( m_aMutex );
+
+ Point location( dtde.LocationX, dtde.LocationY );
+ sal_Int32 nListeners;
+
+ vcl::Window* pChildWindow = findTopLevelWindow(location);
+
+ if( pChildWindow != m_pCurrentWindow.get() )
+ {
+ // fire dragExit on listeners of previous window
+ fireDragExitEvent( m_pCurrentWindow );
+
+ // remember new window
+ designate_currentwindow(pChildWindow);
+
+ // fire dragEnter on listeners of current window
+ nListeners = fireDragEnterEvent( pChildWindow, dtde.Context, dtde.DropAction, location,
+ dtde.SourceActions, m_aDataFlavorList );
+ }
+ else
+ {
+ // fire dropActionChanged on listeners of current window
+ nListeners = fireDropActionChangedEvent( pChildWindow, dtde.Context, dtde.DropAction, location,
+ dtde.SourceActions );
+ }
+
+ // reject drag if no listener found
+ if( nListeners == 0 )
+ {
+ SAL_WARN( "vcl", "rejecting dropActionChanged due to missing listeners." );
+ dtde.Context->rejectDrag();
+ }
+}
+
+void SAL_CALL DNDEventDispatcher::dragGestureRecognized( const DragGestureEvent& dge )
+{
+ std::scoped_lock aImplGuard( m_aMutex );
+
+ Point origin( dge.DragOriginX, dge.DragOriginY );
+
+ vcl::Window* pChildWindow = findTopLevelWindow(origin);
+
+ fireDragGestureEvent( pChildWindow, dge.DragSource, dge.Event, origin, dge.DragAction );
+}
+
+void SAL_CALL DNDEventDispatcher::disposing( const EventObject& )
+{
+}
+
+void SAL_CALL DNDEventDispatcher::acceptDrag( sal_Int8 /*dropAction*/ )
+{
+}
+
+void SAL_CALL DNDEventDispatcher::rejectDrag()
+{
+}
+
+sal_Int32 DNDEventDispatcher::fireDragEnterEvent( vcl::Window *pWindow,
+ const Reference< XDropTargetDragContext >& xContext, const sal_Int8 nDropAction,
+ const Point& rLocation, const sal_Int8 nSourceActions, const Sequence< DataFlavor >& aFlavorList
+)
+{
+ sal_Int32 n = 0;
+
+ if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() )
+ {
+ SolarMutexClearableGuard aSolarGuard;
+
+ // query DropTarget from window
+ Reference< XDropTarget > xDropTarget = pWindow->GetDropTarget();
+
+ if( xDropTarget.is() )
+ {
+ // retrieve relative mouse position
+ Point relLoc = pWindow->ImplFrameToOutput( rLocation );
+ aSolarGuard.clear();
+
+ n = static_cast < DNDListenerContainer * > ( xDropTarget.get() )->fireDragEnterEvent(
+ xContext, nDropAction, relLoc.X(), relLoc.Y(), nSourceActions, aFlavorList );
+ }
+ }
+
+ return n;
+}
+
+sal_Int32 DNDEventDispatcher::fireDragOverEvent( vcl::Window *pWindow,
+ const Reference< XDropTargetDragContext >& xContext, const sal_Int8 nDropAction,
+ const Point& rLocation, const sal_Int8 nSourceActions
+)
+{
+ sal_Int32 n = 0;
+
+ if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() )
+ {
+ SolarMutexClearableGuard aSolarGuard;
+
+ // query DropTarget from window
+ Reference< XDropTarget > xDropTarget = pWindow->GetDropTarget();
+
+ if( xDropTarget.is() )
+ {
+ // retrieve relative mouse position
+ Point relLoc = pWindow->ImplFrameToOutput( rLocation );
+ aSolarGuard.clear();
+
+ n = static_cast < DNDListenerContainer * > ( xDropTarget.get() )->fireDragOverEvent(
+ xContext, nDropAction, relLoc.X(), relLoc.Y(), nSourceActions );
+ }
+ }
+
+ return n;
+}
+
+sal_Int32 DNDEventDispatcher::fireDragExitEvent( vcl::Window *pWindow )
+{
+ sal_Int32 n = 0;
+
+ if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() )
+ {
+ SolarMutexClearableGuard aGuard;
+
+ // query DropTarget from window
+ Reference< XDropTarget > xDropTarget = pWindow->GetDropTarget();
+
+ aGuard.clear();
+
+ if( xDropTarget.is() )
+ n = static_cast < DNDListenerContainer * > ( xDropTarget.get() )->fireDragExitEvent();
+ }
+
+ return n;
+}
+
+sal_Int32 DNDEventDispatcher::fireDropActionChangedEvent( vcl::Window *pWindow,
+ const Reference< XDropTargetDragContext >& xContext, const sal_Int8 nDropAction,
+ const Point& rLocation, const sal_Int8 nSourceActions
+)
+{
+ sal_Int32 n = 0;
+
+ if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() )
+ {
+ SolarMutexClearableGuard aGuard;
+
+ // query DropTarget from window
+ Reference< XDropTarget > xDropTarget = pWindow->GetDropTarget();
+
+ if( xDropTarget.is() )
+ {
+ // retrieve relative mouse position
+ Point relLoc = pWindow->ImplFrameToOutput( rLocation );
+ aGuard.clear();
+
+ n = static_cast < DNDListenerContainer * > ( xDropTarget.get() )->fireDropActionChangedEvent(
+ xContext, nDropAction, relLoc.X(), relLoc.Y(), nSourceActions );
+ }
+ }
+
+ return n;
+}
+
+sal_Int32 DNDEventDispatcher::fireDropEvent( vcl::Window *pWindow,
+ const Reference< XDropTargetDropContext >& xContext, const sal_Int8 nDropAction, const Point& rLocation,
+ const sal_Int8 nSourceActions, const Reference< XTransferable >& xTransferable
+)
+{
+ sal_Int32 n = 0;
+
+ if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() )
+ {
+ SolarMutexClearableGuard aGuard;
+
+ // query DropTarget from window
+ Reference< XDropTarget > xDropTarget = pWindow->GetDropTarget();
+
+ // window may be destroyed in drop event handler
+ VclPtr<vcl::Window> xPreventDelete = pWindow;
+
+ if( xDropTarget.is() )
+ {
+ // retrieve relative mouse position
+ Point relLoc = pWindow->ImplFrameToOutput( rLocation );
+ aGuard.clear();
+
+ n = static_cast < DNDListenerContainer * > ( xDropTarget.get() )->fireDropEvent(
+ xContext, nDropAction, relLoc.X(), relLoc.Y(), nSourceActions, xTransferable );
+ }
+ }
+
+ return n;
+}
+
+sal_Int32 DNDEventDispatcher::fireDragGestureEvent( vcl::Window *pWindow,
+ const Reference< XDragSource >& xSource, const Any& event,
+ const Point& rOrigin, const sal_Int8 nDragAction
+)
+{
+ sal_Int32 n = 0;
+
+ if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() )
+ {
+ SolarMutexClearableGuard aGuard;
+
+ // query DropTarget from window
+ Reference< XDragGestureRecognizer > xDragGestureRecognizer = pWindow->GetDragGestureRecognizer();
+
+ if( xDragGestureRecognizer.is() )
+ {
+ // retrieve relative mouse position
+ Point relLoc = pWindow->ImplFrameToOutput( rOrigin );
+ aGuard.clear();
+
+ n = static_cast < DNDListenerContainer * > ( xDragGestureRecognizer.get() )->fireDragGestureEvent(
+ nDragAction, relLoc.X(), relLoc.Y(), xSource, event );
+ }
+ }
+
+ return n;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dndlistenercontainer.cxx b/vcl/source/window/dndlistenercontainer.cxx
new file mode 100644
index 000000000..d3147aac5
--- /dev/null
+++ b/vcl/source/window/dndlistenercontainer.cxx
@@ -0,0 +1,487 @@
+/* -*- 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 <dndlistenercontainer.hxx>
+
+using namespace ::cppu;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::datatransfer::dnd;
+
+DNDListenerContainer::DNDListenerContainer( sal_Int8 nDefaultActions )
+ : WeakComponentImplHelper< XDragGestureRecognizer, XDropTargetDragContext, XDropTargetDropContext, XDropTarget >(m_aMutex)
+{
+ m_bActive = true;
+ m_nDefaultActions = nDefaultActions;
+}
+
+DNDListenerContainer::~DNDListenerContainer()
+{
+}
+
+void SAL_CALL DNDListenerContainer::addDragGestureListener( const Reference< XDragGestureListener >& dgl )
+{
+ rBHelper.addListener( cppu::UnoType<XDragGestureListener>::get(), dgl );
+}
+
+void SAL_CALL DNDListenerContainer::removeDragGestureListener( const Reference< XDragGestureListener >& dgl )
+{
+ rBHelper.removeListener( cppu::UnoType<XDragGestureListener>::get(), dgl );
+}
+
+void SAL_CALL DNDListenerContainer::resetRecognizer( )
+{
+}
+
+void SAL_CALL DNDListenerContainer::addDropTargetListener( const Reference< XDropTargetListener >& dtl )
+{
+ rBHelper.addListener( cppu::UnoType<XDropTargetListener>::get(), dtl );
+}
+
+void SAL_CALL DNDListenerContainer::removeDropTargetListener( const Reference< XDropTargetListener >& dtl )
+{
+ rBHelper.removeListener( cppu::UnoType<XDropTargetListener>::get(), dtl );
+}
+
+sal_Bool SAL_CALL DNDListenerContainer::isActive( )
+{
+ return m_bActive;
+}
+
+void SAL_CALL DNDListenerContainer::setActive( sal_Bool active )
+{
+ m_bActive = active;
+}
+
+sal_Int8 SAL_CALL DNDListenerContainer::getDefaultActions( )
+{
+ return m_nDefaultActions;
+}
+
+void SAL_CALL DNDListenerContainer::setDefaultActions( sal_Int8 actions )
+{
+ m_nDefaultActions = actions;
+}
+
+sal_uInt32 DNDListenerContainer::fireDropEvent( const Reference< XDropTargetDropContext >& context,
+ sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions,
+ const Reference< XTransferable >& transferable )
+{
+ sal_uInt32 nRet = 0;
+
+ // fire DropTargetDropEvent on all XDropTargetListeners
+ OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+
+ if( pContainer && m_bActive )
+ {
+ OInterfaceIteratorHelper aIterator( *pContainer );
+
+ // remember context to use in own context methods
+ m_xDropTargetDropContext = context;
+
+ // do not construct the event before you are sure at least one listener is registered
+ DropTargetDropEvent aEvent( static_cast < XDropTarget * > (this), 0,
+ static_cast < XDropTargetDropContext * > (this), dropAction,
+ locationX, locationY, sourceActions, transferable );
+
+ while (aIterator.hasMoreElements())
+ {
+ // FIXME: this can be simplified as soon as the Iterator has a remove method
+ Reference< XInterface > xElement( aIterator.next() );
+
+ try
+ {
+ // this may result in a runtime exception
+ Reference < XDropTargetListener > xListener( xElement, UNO_QUERY );
+
+ if( xListener.is() )
+ {
+ // fire drop until the first one has accepted
+ if( m_xDropTargetDropContext.is() )
+ xListener->drop( aEvent );
+ else
+ {
+ DropTargetEvent aDTEvent( static_cast < XDropTarget * > (this), 0 );
+ xListener->dragExit( aDTEvent );
+ }
+
+ nRet++;
+ }
+ }
+ catch (const RuntimeException&)
+ {
+ pContainer->removeInterface( xElement );
+ }
+ }
+
+ // if context still valid, then reject drop
+ if( m_xDropTargetDropContext.is() )
+ {
+ m_xDropTargetDropContext.clear();
+
+ try
+ {
+ context->rejectDrop();
+ }
+ catch (const RuntimeException&)
+ {
+ }
+ }
+ }
+
+ return nRet;
+}
+
+sal_uInt32 DNDListenerContainer::fireDragExitEvent()
+{
+ sal_uInt32 nRet = 0;
+
+ // fire DropTargetDropEvent on all XDropTargetListeners
+ OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+
+ if( pContainer && m_bActive )
+ {
+ OInterfaceIteratorHelper aIterator( *pContainer );
+
+ // do not construct the event before you are sure at least one listener is registered
+ DropTargetEvent aEvent( static_cast < XDropTarget * > (this), 0 );
+
+ while (aIterator.hasMoreElements())
+ {
+ // FIXME: this can be simplified as soon as the Iterator has a remove method
+ Reference< XInterface > xElement( aIterator.next() );
+
+ try
+ {
+ // this may result in a runtime exception
+ Reference < XDropTargetListener > xListener( xElement, UNO_QUERY );
+
+ if( xListener.is() )
+ {
+ xListener->dragExit( aEvent );
+ nRet++;
+ }
+ }
+ catch (const RuntimeException&)
+ {
+ pContainer->removeInterface( xElement );
+ }
+ }
+ }
+
+ return nRet;
+}
+
+sal_uInt32 DNDListenerContainer::fireDragOverEvent( const Reference< XDropTargetDragContext >& context,
+ sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions )
+{
+ sal_uInt32 nRet = 0;
+
+ // fire DropTargetDropEvent on all XDropTargetListeners
+ OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+
+ if( pContainer && m_bActive )
+ {
+ OInterfaceIteratorHelper aIterator( *pContainer );
+
+ // remember context to use in own context methods
+ m_xDropTargetDragContext = context;
+
+ // do not construct the event before you are sure at least one listener is registered
+ DropTargetDragEvent aEvent( static_cast < XDropTarget * > (this), 0,
+ static_cast < XDropTargetDragContext * > (this),
+ dropAction, locationX, locationY, sourceActions );
+
+ while (aIterator.hasMoreElements())
+ {
+ // FIXME: this can be simplified as soon as the Iterator has a remove method
+ Reference< XInterface > xElement( aIterator.next() );
+
+ try
+ {
+ // this may result in a runtime exception
+ Reference < XDropTargetListener > xListener( xElement, UNO_QUERY );
+
+ if( xListener.is() )
+ {
+ if( m_xDropTargetDragContext.is() )
+ xListener->dragOver( aEvent );
+ nRet++;
+ }
+ }
+ catch (const RuntimeException&)
+ {
+ pContainer->removeInterface( xElement );
+ }
+ }
+
+ // if context still valid, then reject drag
+ if( m_xDropTargetDragContext.is() )
+ {
+ m_xDropTargetDragContext.clear();
+
+ try
+ {
+ context->rejectDrag();
+ }
+ catch (const RuntimeException&)
+ {
+ }
+ }
+ }
+
+ return nRet;
+}
+
+sal_uInt32 DNDListenerContainer::fireDragEnterEvent( const Reference< XDropTargetDragContext >& context,
+ sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions,
+ const Sequence< DataFlavor >& dataFlavors )
+{
+ sal_uInt32 nRet = 0;
+
+ // fire DropTargetDropEvent on all XDropTargetListeners
+ OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+
+ if( pContainer && m_bActive )
+ {
+ OInterfaceIteratorHelper aIterator( *pContainer );
+
+ // remember context to use in own context methods
+ m_xDropTargetDragContext = context;
+
+ // do not construct the event before you are sure at least one listener is registered
+ DropTargetDragEnterEvent aEvent( static_cast < XDropTarget * > (this), 0,
+ static_cast < XDropTargetDragContext * > (this),
+ dropAction, locationX, locationY, sourceActions, dataFlavors );
+
+ while (aIterator.hasMoreElements())
+ {
+ // FIXME: this can be simplified as soon as the Iterator has a remove method
+ Reference< XInterface > xElement( aIterator.next() );
+
+ try
+ {
+ // this may result in a runtime exception
+ Reference < XDropTargetListener > xListener( xElement, UNO_QUERY );
+
+ if( xListener.is() )
+ {
+ if( m_xDropTargetDragContext.is() )
+ xListener->dragEnter( aEvent );
+ nRet++;
+ }
+ }
+ catch (const RuntimeException&)
+ {
+ pContainer->removeInterface( xElement );
+ }
+ }
+
+ // if context still valid, then reject drag
+ if( m_xDropTargetDragContext.is() )
+ {
+ m_xDropTargetDragContext.clear();
+
+ try
+ {
+ context->rejectDrag();
+ }
+ catch (const RuntimeException&)
+ {
+ }
+ }
+ }
+
+ return nRet;
+}
+
+sal_uInt32 DNDListenerContainer::fireDropActionChangedEvent( const Reference< XDropTargetDragContext >& context,
+ sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions )
+{
+ sal_uInt32 nRet = 0;
+
+ // fire DropTargetDropEvent on all XDropTargetListeners
+ OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+
+ if( pContainer && m_bActive )
+ {
+ OInterfaceIteratorHelper aIterator( *pContainer );
+
+ // remember context to use in own context methods
+ m_xDropTargetDragContext = context;
+
+ // do not construct the event before you are sure at least one listener is registered
+ DropTargetDragEvent aEvent( static_cast < XDropTarget * > (this), 0,
+ static_cast < XDropTargetDragContext * > (this),
+ dropAction, locationX, locationY, sourceActions );
+
+ while (aIterator.hasMoreElements())
+ {
+ // FIXME: this can be simplified as soon as the Iterator has a remove method
+ Reference< XInterface > xElement( aIterator.next() );
+
+ try
+ {
+ // this may result in a runtime exception
+ Reference < XDropTargetListener > xListener( xElement, UNO_QUERY );
+
+ if( xListener.is() )
+ {
+ if( m_xDropTargetDragContext.is() )
+ xListener->dropActionChanged( aEvent );
+ nRet++;
+ }
+ }
+ catch (const RuntimeException&)
+ {
+ pContainer->removeInterface( xElement );
+ }
+ }
+
+ // if context still valid, then reject drag
+ if( m_xDropTargetDragContext.is() )
+ {
+ m_xDropTargetDragContext.clear();
+
+ try
+ {
+ context->rejectDrag();
+ }
+ catch (const RuntimeException&)
+ {
+ }
+ }
+ }
+
+ return nRet;
+}
+
+sal_uInt32 DNDListenerContainer::fireDragGestureEvent( sal_Int8 dragAction, sal_Int32 dragOriginX,
+ sal_Int32 dragOriginY, const Reference< XDragSource >& dragSource, const Any& triggerEvent )
+{
+ sal_uInt32 nRet = 0;
+
+ // fire DropTargetDropEvent on all XDropTargetListeners
+ OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDragGestureListener>::get());
+
+ if( pContainer )
+ {
+ OInterfaceIteratorHelper aIterator( *pContainer );
+
+ // do not construct the event before you are sure at least one listener is registered
+ DragGestureEvent aEvent( static_cast < XDragGestureRecognizer * > (this), dragAction,
+ dragOriginX, dragOriginY, dragSource, triggerEvent );
+
+ while( aIterator.hasMoreElements() )
+ {
+ // FIXME: this can be simplified as soon as the Iterator has a remove method
+ Reference< XInterface > xElement( aIterator.next() );
+
+ try
+ {
+ // this may result in a runtime exception
+ Reference < XDragGestureListener > xListener( xElement, UNO_QUERY );
+
+ if( xListener.is() )
+ {
+ xListener->dragGestureRecognized( aEvent );
+ nRet++;
+ }
+ }
+ catch (const RuntimeException&)
+ {
+ pContainer->removeInterface( xElement );
+ }
+ }
+ }
+
+ return nRet;
+}
+
+void SAL_CALL DNDListenerContainer::acceptDrag( sal_Int8 dragOperation )
+{
+ if( m_xDropTargetDragContext.is() )
+ {
+ m_xDropTargetDragContext->acceptDrag( dragOperation );
+ m_xDropTargetDragContext.clear();
+ }
+}
+
+void SAL_CALL DNDListenerContainer::rejectDrag( )
+{
+ // nothing to do here
+}
+
+void SAL_CALL DNDListenerContainer::acceptDrop( sal_Int8 dropOperation )
+{
+ if( m_xDropTargetDropContext.is() )
+ m_xDropTargetDropContext->acceptDrop( dropOperation );
+}
+
+void SAL_CALL DNDListenerContainer::rejectDrop( )
+{
+ // nothing to do here
+}
+
+void SAL_CALL DNDListenerContainer::dropComplete( sal_Bool success )
+{
+ if( m_xDropTargetDropContext.is() )
+ {
+ m_xDropTargetDropContext->dropComplete( success );
+ m_xDropTargetDropContext.clear();
+ }
+}
+
+/*
+ * GenericDropTargetDropContext
+ */
+
+GenericDropTargetDropContext::GenericDropTargetDropContext()
+{
+}
+
+void GenericDropTargetDropContext::acceptDrop( sal_Int8 /*dragOperation*/ )
+{
+}
+
+void GenericDropTargetDropContext::rejectDrop()
+{
+}
+
+void GenericDropTargetDropContext::dropComplete( sal_Bool /*success*/ )
+{
+}
+
+/*
+ * GenericDropTargetDragContext
+ */
+
+GenericDropTargetDragContext::GenericDropTargetDragContext()
+{
+}
+
+void GenericDropTargetDragContext::acceptDrag( sal_Int8 /*dragOperation*/ )
+{
+}
+
+void GenericDropTargetDragContext::rejectDrag()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dockingarea.cxx b/vcl/source/window/dockingarea.cxx
new file mode 100644
index 000000000..be3f6ef99
--- /dev/null
+++ b/vcl/source/window/dockingarea.cxx
@@ -0,0 +1,257 @@
+/* -*- 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 <vcl/dockingarea.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/event.hxx>
+#include <toolbarvalue.hxx>
+
+#include <svdata.hxx>
+
+#include <map>
+
+class DockingAreaWindow::ImplData
+{
+public:
+ ImplData();
+
+ WindowAlign meAlign;
+};
+
+DockingAreaWindow::ImplData::ImplData()
+{
+ meAlign = WindowAlign::Top;
+}
+
+DockingAreaWindow::DockingAreaWindow( vcl::Window* pParent ) :
+ Window( WindowType::DOCKINGAREA )
+{
+ ImplInit( pParent, WB_CLIPCHILDREN|WB_3DLOOK, nullptr );
+
+ mpImplData.reset(new ImplData);
+}
+
+DockingAreaWindow::~DockingAreaWindow()
+{
+ disposeOnce();
+}
+
+void DockingAreaWindow::dispose()
+{
+ mpImplData.reset();
+ Window::dispose();
+}
+
+void DockingAreaWindow::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Window::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ Invalidate();
+ }
+}
+
+static void ImplInvalidateMenubar( DockingAreaWindow const * pThis )
+{
+ // due to a possible common gradient covering menubar and top dockingarea
+ // the menubar must be repainted if the top dockingarea changes size or visibility
+ if( ImplGetSVData()->maNWFData.mbMenuBarDockingAreaCommonBG &&
+ (pThis->GetAlign() == WindowAlign::Top)
+ && pThis->IsNativeControlSupported( ControlType::Toolbar, ControlPart::Entire )
+ && pThis->IsNativeControlSupported( ControlType::Menubar, ControlPart::Entire ) )
+ {
+ SystemWindow *pSysWin = pThis->GetSystemWindow();
+ if( pSysWin && pSysWin->GetMenuBar() )
+ {
+ vcl::Window *pMenubarWin = pSysWin->GetMenuBar()->GetWindow();
+ if( pMenubarWin )
+ pMenubarWin->Invalidate();
+ }
+ }
+}
+
+void DockingAreaWindow::StateChanged( StateChangedType nType )
+{
+ Window::StateChanged( nType );
+
+ if ( nType == StateChangedType::Visible )
+ ImplInvalidateMenubar( this );
+}
+
+bool DockingAreaWindow::IsHorizontal() const
+{
+ return ( mpImplData->meAlign == WindowAlign::Top || mpImplData->meAlign == WindowAlign::Bottom );
+}
+
+void DockingAreaWindow::SetAlign( WindowAlign eNewAlign )
+{
+ if( eNewAlign != mpImplData->meAlign )
+ {
+ mpImplData->meAlign = eNewAlign;
+ Invalidate();
+ }
+}
+
+WindowAlign DockingAreaWindow::GetAlign() const
+{
+ return mpImplData->meAlign;
+}
+
+void DockingAreaWindow::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings rSetting = rRenderContext.GetSettings().GetStyleSettings();
+ const BitmapEx& rPersonaBitmap = (GetAlign() == WindowAlign::Top) ? rSetting.GetPersonaHeader() : rSetting.GetPersonaFooter();
+
+ if (!rPersonaBitmap.IsEmpty() && (GetAlign() == WindowAlign::Top || GetAlign()==WindowAlign::Bottom))
+ {
+ Wallpaper aWallpaper(rPersonaBitmap);
+ if (GetAlign() == WindowAlign::Top)
+ aWallpaper.SetStyle(WallpaperStyle::TopRight);
+ else
+ aWallpaper.SetStyle(WallpaperStyle::BottomRight);
+ aWallpaper.SetColor(rSetting.GetWorkspaceColor());
+
+ // we need to shift the bitmap vertically so that it spans over the
+ // menubar conveniently
+ SystemWindow* pSysWin = GetSystemWindow();
+ MenuBar* pMenuBar = pSysWin ? pSysWin->GetMenuBar() : nullptr;
+ int nMenubarHeight = pMenuBar ? pMenuBar->GetMenuBarHeight() : 0;
+ aWallpaper.SetRect(tools::Rectangle(Point(0, -nMenubarHeight),
+ Size(rRenderContext.GetOutputWidthPixel(),
+ rRenderContext.GetOutputHeightPixel() + nMenubarHeight)));
+
+ rRenderContext.SetBackground(aWallpaper);
+ }
+ else if (!rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Entire))
+ {
+ Wallpaper aWallpaper;
+ aWallpaper.SetStyle(WallpaperStyle::ApplicationGradient);
+ rRenderContext.SetBackground(aWallpaper);
+ }
+ else
+ rRenderContext.SetBackground(Wallpaper(rSetting.GetFaceColor()));
+
+}
+
+void DockingAreaWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ const StyleSettings rSetting = rRenderContext.GetSettings().GetStyleSettings();
+
+ EnableNativeWidget(); // only required because the toolkit currently switches this flag off
+ if (!rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Entire))
+ return;
+
+ ToolbarValue aControlValue;
+
+ if (GetAlign() == WindowAlign::Top && ImplGetSVData()->maNWFData.mbMenuBarDockingAreaCommonBG)
+ {
+ // give NWF a hint that this dockingarea is adjacent to the menubar
+ // useful for special gradient effects that should cover both windows
+ aControlValue.mbIsTopDockingArea = true;
+ }
+
+ ControlState nState = ControlState::ENABLED;
+ const bool isFooter = GetAlign() == WindowAlign::Bottom && !rSetting.GetPersonaFooter().IsEmpty();
+
+ if ((GetAlign() == WindowAlign::Top && !rSetting.GetPersonaHeader().IsEmpty() ) || isFooter)
+ Erase(rRenderContext);
+ else if (!ImplGetSVData()->maNWFData.mbDockingAreaSeparateTB)
+ {
+ // draw a single toolbar background covering the whole docking area
+ tools::Rectangle aCtrlRegion(Point(), GetOutputSizePixel());
+
+ rRenderContext.DrawNativeControl(ControlType::Toolbar, IsHorizontal() ? ControlPart::DrawBackgroundHorz : ControlPart::DrawBackgroundVert,
+ aCtrlRegion, nState, aControlValue, OUString() );
+
+ if (!ImplGetSVData()->maNWFData.mbDockingAreaAvoidTBFrames)
+ {
+ // each toolbar gets a thin border to better recognize its borders on the homogeneous docking area
+ sal_uInt16 nChildren = GetChildCount();
+ for (sal_uInt16 n = 0; n < nChildren; n++)
+ {
+ vcl::Window* pChild = GetChild(n);
+ if (pChild->IsVisible())
+ {
+ Point aPos = pChild->GetPosPixel();
+ Size aSize = pChild->GetSizePixel();
+ tools::Rectangle aRect(aPos, aSize);
+
+ rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetLightColor());
+ rRenderContext.DrawLine(aRect.TopLeft(), aRect.TopRight());
+ rRenderContext.DrawLine(aRect.TopLeft(), aRect.BottomLeft());
+
+ rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetSeparatorColor());
+ rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight());
+ rRenderContext.DrawLine(aRect.TopRight(), aRect.BottomRight());
+ }
+ }
+ }
+ }
+ else
+ {
+ // create map to find toolbar lines
+ Size aOutSz(GetOutputSizePixel());
+ std::map<int, int> ranges;
+ sal_uInt16 nChildren = GetChildCount();
+ for (sal_uInt16 n = 0; n < nChildren; n++)
+ {
+ vcl::Window* pChild = GetChild(n);
+ Point aPos = pChild->GetPosPixel();
+ Size aSize = pChild->GetSizePixel();
+ if (IsHorizontal())
+ ranges[aPos.Y()] = aSize.Height();
+ else
+ ranges[aPos.X()] = aSize.Width();
+ }
+
+ // draw multiple toolbar backgrounds, i.e., one for each toolbar line
+ for (auto const& range : ranges)
+ {
+ tools::Rectangle aTBRect;
+ if (IsHorizontal())
+ {
+ aTBRect.SetLeft( 0 );
+ aTBRect.SetRight( aOutSz.Width() - 1 );
+ aTBRect.SetTop( range.first );
+ aTBRect.SetBottom( range.first + range.second - 1 );
+ }
+ else
+ {
+ aTBRect.SetLeft( range.first );
+ aTBRect.SetRight( range.first + range.second - 1 );
+ aTBRect.SetTop( 0 );
+ aTBRect.SetBottom( aOutSz.Height() - 1 );
+ }
+ rRenderContext.DrawNativeControl(ControlType::Toolbar, IsHorizontal() ? ControlPart::DrawBackgroundHorz : ControlPart::DrawBackgroundVert,
+ aTBRect, nState, aControlValue, OUString());
+ }
+ }
+}
+
+void DockingAreaWindow::Resize()
+{
+ ImplInvalidateMenubar( this );
+ if (IsNativeControlSupported(ControlType::Toolbar, ControlPart::Entire))
+ Invalidate();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dockmgr.cxx b/vcl/source/window/dockmgr.cxx
new file mode 100644
index 000000000..f836ff2f1
--- /dev/null
+++ b/vcl/source/window/dockmgr.cxx
@@ -0,0 +1,1075 @@
+/* -*- 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 <tools/time.hxx>
+#include <sal/log.hxx>
+#include <o3tl/deleter.hxx>
+
+#include <brdwin.hxx>
+#include <svdata.hxx>
+#include <window.h>
+
+#include <vcl/event.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/dockwin.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/settings.hxx>
+
+#include "impldockingwrapper.hxx"
+
+#define DOCKWIN_FLOATSTYLES (WB_SIZEABLE | WB_MOVEABLE | WB_CLOSEABLE | WB_STANDALONE)
+
+namespace {
+
+class ImplDockFloatWin2 : public FloatingWindow
+{
+private:
+ ImplDockingWindowWrapper* mpDockWin;
+ sal_uInt64 mnLastTicks;
+ Timer m_aDockTimer;
+ Timer m_aEndDockTimer;
+ Point maDockPos;
+ tools::Rectangle maDockRect;
+ bool mbInMove;
+ ImplSVEvent * mnLastUserEvent;
+
+ DECL_LINK(DockingHdl, void *, void);
+ DECL_LINK(DockTimerHdl, Timer *, void);
+ DECL_LINK(EndDockTimerHdl, Timer *, void);
+public:
+ ImplDockFloatWin2( vcl::Window* pParent, WinBits nWinBits,
+ ImplDockingWindowWrapper* pDockingWin );
+ virtual ~ImplDockFloatWin2() override;
+ virtual void dispose() override;
+
+ virtual void Move() override;
+ virtual void Resize() override;
+ virtual void TitleButtonClick( TitleButton nButton ) override;
+ virtual void Resizing( Size& rSize ) override;
+ virtual bool Close() override;
+};
+
+}
+
+ImplDockFloatWin2::ImplDockFloatWin2( vcl::Window* pParent, WinBits nWinBits,
+ ImplDockingWindowWrapper* pDockingWin ) :
+ FloatingWindow( pParent, nWinBits ),
+ mpDockWin( pDockingWin ),
+ mnLastTicks( tools::Time::GetSystemTicks() ),
+ m_aDockTimer("vcl::ImplDockFloatWin2 m_aDockTimer"),
+ m_aEndDockTimer( "vcl::ImplDockFloatWin2 m_aEndDockTimer" ),
+ mbInMove( false ),
+ mnLastUserEvent( nullptr )
+{
+ // copy state of DockingWindow
+ if ( pDockingWin )
+ {
+ GetOutDev()->SetSettings( pDockingWin->GetWindow()->GetSettings() );
+ Enable( pDockingWin->GetWindow()->IsEnabled(), false );
+ EnableInput( pDockingWin->GetWindow()->IsInputEnabled(), false );
+ AlwaysEnableInput( pDockingWin->GetWindow()->IsAlwaysEnableInput(), false );
+ EnableAlwaysOnTop( pDockingWin->GetWindow()->IsAlwaysOnTopEnabled() );
+ SetActivateMode( pDockingWin->GetWindow()->GetActivateMode() );
+ }
+
+ SetBackground( GetSettings().GetStyleSettings().GetFaceColor() );
+
+ m_aDockTimer.SetInvokeHandler( LINK( this, ImplDockFloatWin2, DockTimerHdl ) );
+ m_aDockTimer.SetPriority( TaskPriority::HIGH_IDLE );
+ m_aDockTimer.SetTimeout( 50 );
+
+ m_aEndDockTimer.SetInvokeHandler( LINK( this, ImplDockFloatWin2, EndDockTimerHdl ) );
+ m_aEndDockTimer.SetPriority( TaskPriority::HIGH_IDLE );
+ m_aEndDockTimer.SetTimeout( 50 );
+}
+
+ImplDockFloatWin2::~ImplDockFloatWin2()
+{
+ disposeOnce();
+}
+
+void ImplDockFloatWin2::dispose()
+{
+ if( mnLastUserEvent )
+ Application::RemoveUserEvent( mnLastUserEvent );
+ FloatingWindow::dispose();
+}
+
+IMPL_LINK_NOARG(ImplDockFloatWin2, DockTimerHdl, Timer *, void)
+{
+ SAL_WARN_IF( !mpDockWin->IsFloatingMode(), "vcl", "docktimer called but not floating" );
+
+ PointerState aState = GetPointerState();
+
+ if( aState.mnState & KEY_MOD1 )
+ {
+ // i43499 CTRL disables docking now
+ mpDockWin->GetWindow()->GetParent()->ImplGetFrameWindow()->HideTracking();
+ if( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) )
+ m_aDockTimer.Start();
+ }
+ else if( ! ( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) ) )
+ {
+ mpDockWin->GetWindow()->GetParent()->ImplGetFrameWindow()->HideTracking();
+ mpDockWin->EndDocking( maDockRect, false );
+ }
+ else
+ {
+ mpDockWin->GetWindow()->GetParent()->ImplGetFrameWindow()->ShowTracking( maDockRect, ShowTrackFlags::Big | ShowTrackFlags::TrackWindow );
+ m_aDockTimer.Start();
+ }
+}
+
+IMPL_LINK_NOARG(ImplDockFloatWin2, EndDockTimerHdl, Timer *, void)
+{
+ SAL_WARN_IF( !mpDockWin->IsFloatingMode(), "vcl", "enddocktimer called but not floating" );
+
+ PointerState aState = GetPointerState();
+ if( ! ( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) ) )
+ {
+ mpDockWin->GetWindow()->GetParent()->ImplGetFrameWindow()->HideTracking();
+ mpDockWin->EndDocking( maDockRect, true );
+ }
+ else
+ m_aEndDockTimer.Start();
+}
+
+IMPL_LINK_NOARG(ImplDockFloatWin2, DockingHdl, void*, void)
+{
+ // called during move of a floating window
+ mnLastUserEvent = nullptr;
+
+ vcl::Window *pDockingArea = mpDockWin->GetWindow()->GetParent();
+ PointerState aState = pDockingArea->GetPointerState();
+
+ bool bRealMove = true;
+ if( GetStyle() & WB_OWNERDRAWDECORATION )
+ {
+ // for windows with ownerdraw decoration
+ // we allow docking only when the window was moved
+ // by dragging its caption
+ // and ignore move request due to resizing
+ vcl::Window *pBorder = GetWindow( GetWindowType::Border );
+ if( pBorder != this )
+ {
+ tools::Rectangle aBorderRect( Point(), pBorder->GetSizePixel() );
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ GetBorder( nLeft, nTop, nRight, nBottom );
+ // limit borderrect to the caption part only and without the resizing borders
+ aBorderRect.SetBottom( aBorderRect.Top() + nTop );
+ aBorderRect.AdjustLeft(nLeft );
+ aBorderRect.AdjustRight( -nRight );
+
+ PointerState aBorderState = pBorder->GetPointerState();
+ bRealMove = aBorderRect.Contains( aBorderState.maPos );
+ }
+ }
+
+ if( mpDockWin->GetWindow()->IsVisible() &&
+ (tools::Time::GetSystemTicks() - mnLastTicks > 500) &&
+ ( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) ) &&
+ !(aState.mnState & KEY_MOD1) && // i43499 CTRL disables docking now
+ bRealMove )
+ {
+ maDockPos = pDockingArea->OutputToScreenPixel( pDockingArea->AbsoluteScreenToOutputPixel( OutputToAbsoluteScreenPixel( Point() ) ) );
+ maDockRect = tools::Rectangle( maDockPos, mpDockWin->GetSizePixel() );
+
+ // mouse pos in screen pixels
+ Point aMousePos = pDockingArea->OutputToScreenPixel( aState.maPos );
+
+ if( ! mpDockWin->IsDocking() )
+ mpDockWin->StartDocking( aMousePos, maDockRect );
+
+ bool bFloatMode = mpDockWin->Docking( aMousePos, maDockRect );
+
+ if( ! bFloatMode )
+ {
+ // indicates that the window could be docked at maDockRect
+ maDockRect.SetPos( mpDockWin->GetWindow()->GetParent()->ImplGetFrameWindow()->ScreenToOutputPixel(
+ maDockRect.TopLeft() ) );
+ mpDockWin->GetWindow()->GetParent()->ImplGetFrameWindow()->ShowTracking( maDockRect, ShowTrackFlags::Big | ShowTrackFlags::TrackWindow );
+ m_aEndDockTimer.Stop();
+ m_aDockTimer.Invoke();
+ }
+ else
+ {
+ mpDockWin->GetWindow()->GetParent()->ImplGetFrameWindow()->HideTracking();
+ m_aDockTimer.Stop();
+ m_aEndDockTimer.Invoke();
+ }
+ }
+ mbInMove = false;
+}
+
+void ImplDockFloatWin2::Move()
+{
+ if( mbInMove )
+ return;
+
+ mbInMove = true;
+ FloatingWindow::Move();
+ mpDockWin->GetWindow()->Move();
+
+ /*
+ * note: the window should only dock if KEY_MOD1 is pressed
+ * and the user releases all mouse buttons. The real problem here
+ * is that we don't get mouse events (at least not on X)
+ * if the mouse is on the decoration. So we have to start an
+ * awkward timer based process that polls the modifier/buttons
+ * to see whether they are in the right condition shortly after the
+ * last Move message.
+ */
+ if( ! mnLastUserEvent )
+ mnLastUserEvent = Application::PostUserEvent( LINK( this, ImplDockFloatWin2, DockingHdl ), nullptr, true );
+}
+
+void ImplDockFloatWin2::Resize()
+{
+ // forwarding of resize only required if we have no borderwindow ( GetWindow() then returns 'this' )
+ if( GetWindow( GetWindowType::Border ) == this )
+ {
+ FloatingWindow::Resize();
+ Size aSize( GetSizePixel() );
+ mpDockWin->GetWindow()->ImplPosSizeWindow( 0, 0, aSize.Width(), aSize.Height(), PosSizeFlags::PosSize ); // TODO: is this needed ???
+ }
+}
+
+void ImplDockFloatWin2::TitleButtonClick( TitleButton nButton )
+{
+ FloatingWindow::TitleButtonClick( nButton );
+ mpDockWin->TitleButtonClick( nButton );
+}
+
+void ImplDockFloatWin2::Resizing( Size& rSize )
+{
+ FloatingWindow::Resizing( rSize );
+ mpDockWin->Resizing( rSize );
+}
+
+bool ImplDockFloatWin2::Close()
+{
+ return true;
+}
+
+DockingManager::DockingManager()
+{
+}
+
+DockingManager::~DockingManager()
+{
+}
+
+ImplDockingWindowWrapper* DockingManager::GetDockingWindowWrapper( const vcl::Window *pWindow )
+{
+ for( const auto& xWrapper : mvDockingWindows )
+ {
+ if (xWrapper && xWrapper->mpDockingWindow == pWindow)
+ return xWrapper.get();
+ }
+ return nullptr;
+}
+
+bool DockingManager::IsDockable( const vcl::Window *pWindow )
+{
+ ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow );
+
+ /*
+ if( pWindow->HasDockingHandler() )
+ return true;
+ */
+ return (pWrapper != nullptr);
+}
+
+bool DockingManager::IsFloating( const vcl::Window *pWindow )
+{
+ ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow );
+ if( pWrapper )
+ return pWrapper->IsFloatingMode();
+ else
+ return false;
+}
+
+bool DockingManager::IsLocked( const vcl::Window *pWindow )
+{
+ ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow );
+ return pWrapper && pWrapper->IsLocked();
+}
+
+void DockingManager::Lock( const vcl::Window *pWindow )
+{
+ ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow );
+ if( pWrapper )
+ pWrapper->Lock();
+}
+
+void DockingManager::Unlock( const vcl::Window *pWindow )
+{
+ ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow );
+ if( pWrapper )
+ pWrapper->Unlock();
+}
+
+void DockingManager::SetFloatingMode( const vcl::Window *pWindow, bool bFloating )
+{
+ ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow );
+ if( pWrapper )
+ pWrapper->SetFloatingMode( bFloating );
+}
+
+void DockingManager::StartPopupMode( const vcl::Window *pWindow, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags )
+{
+ ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow );
+ if( pWrapper )
+ pWrapper->StartPopupMode( rRect, nFlags );
+}
+
+void DockingManager::StartPopupMode( ToolBox *pParentToolBox, const vcl::Window *pWindow, FloatWinPopupFlags nFlags )
+{
+ ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow );
+ if( pWrapper )
+ pWrapper->StartPopupMode( pParentToolBox, nFlags );
+}
+
+void DockingManager::StartPopupMode( ToolBox *pParentToolBox, const vcl::Window *pWindow )
+{
+ StartPopupMode( pParentToolBox, pWindow, FloatWinPopupFlags::AllowTearOff |
+ FloatWinPopupFlags::AllMouseButtonClose |
+ FloatWinPopupFlags::NoMouseUpClose );
+}
+
+bool DockingManager::IsInPopupMode( const vcl::Window *pWindow )
+{
+ ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow );
+ return pWrapper && pWrapper->IsInPopupMode();
+}
+
+void DockingManager::EndPopupMode( const vcl::Window *pWin )
+{
+ ImplDockingWindowWrapper *pWrapper = GetDockingWindowWrapper( pWin );
+ if( pWrapper && pWrapper->GetFloatingWindow() && static_cast<FloatingWindow*>(pWrapper->GetFloatingWindow())->IsInPopupMode() )
+ static_cast<FloatingWindow*>(pWrapper->GetFloatingWindow())->EndPopupMode();
+}
+
+SystemWindow* DockingManager::GetFloatingWindow(const vcl::Window *pWin)
+{
+ ImplDockingWindowWrapper *pWrapper = GetDockingWindowWrapper( pWin );
+ if (pWrapper)
+ return pWrapper->GetFloatingWindow();
+ return nullptr;
+}
+
+void DockingManager::SetPopupModeEndHdl( const vcl::Window *pWindow, const Link<FloatingWindow*,void>& rLink )
+{
+ ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow );
+ if( pWrapper )
+ pWrapper->SetPopupModeEndHdl(rLink);
+}
+
+void DockingManager::AddWindow( const vcl::Window *pWindow )
+{
+ ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow );
+ if( pWrapper )
+ return;
+ mvDockingWindows.emplace_back( new ImplDockingWindowWrapper( pWindow ) );
+}
+
+void DockingManager::RemoveWindow( const vcl::Window *pWindow )
+{
+ for( auto it = mvDockingWindows.begin(); it != mvDockingWindows.end(); ++it )
+ {
+ const auto& xWrapper = *it;
+ if (xWrapper && xWrapper->mpDockingWindow == pWindow)
+ {
+ // deleting wrappers calls set of actions which may want to use
+ // wrapper we want to delete - avoid crash using temporary owner
+ // while erasing
+ auto pTemporaryOwner = std::move(*it);
+ mvDockingWindows.erase( it );
+ break;
+ }
+ }
+}
+
+void DockingManager::SetPosSizePixel( vcl::Window const *pWindow, tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ PosSizeFlags nFlags )
+{
+ ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow );
+ if( pWrapper )
+ pWrapper->setPosSizePixel( nX, nY, nWidth, nHeight, nFlags );
+}
+
+tools::Rectangle DockingManager::GetPosSizePixel( const vcl::Window *pWindow )
+{
+ tools::Rectangle aRect;
+ ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow );
+ if( pWrapper )
+ aRect = tools::Rectangle( pWrapper->GetPosPixel(), pWrapper->GetSizePixel() );
+
+ return aRect;
+}
+
+class ImplPopupFloatWin : public FloatingWindow
+{
+private:
+ bool mbToolBox;
+
+public:
+ ImplPopupFloatWin( vcl::Window* pParent, bool bToolBox );
+ virtual ~ImplPopupFloatWin() override;
+ virtual css::uno::Reference< css::accessibility::XAccessible > CreateAccessible() override;
+};
+
+ImplPopupFloatWin::ImplPopupFloatWin( vcl::Window* pParent, bool bToolBox ) :
+ FloatingWindow( pParent, bToolBox ? WB_BORDER | WB_POPUP | WB_SYSTEMWINDOW | WB_NOSHADOW : WB_STDPOPUP ),
+ mbToolBox( bToolBox )
+{
+ if ( bToolBox )
+ {
+ // indicate window type, required for accessibility
+ // which should not see this window as a toplevel window
+ mpWindowImpl->mbToolbarFloatingWindow = true;
+ }
+}
+
+ImplPopupFloatWin::~ImplPopupFloatWin()
+{
+ disposeOnce();
+}
+
+css::uno::Reference< css::accessibility::XAccessible > ImplPopupFloatWin::CreateAccessible()
+{
+ if ( !mbToolBox )
+ return FloatingWindow::CreateAccessible();
+
+ // switch off direct accessibility support for this window
+
+ // this is to avoid appearance of this window as standalone window in the accessibility hierarchy
+ // as this window is only used as a helper for subtoolbars that are not teared-off, the parent toolbar
+ // has to provide accessibility support (as implemented in the toolkit)
+ // so the contained toolbar should appear as child of the corresponding toolbar item of the parent toolbar
+ return css::uno::Reference< css::accessibility::XAccessible >();
+}
+
+ImplDockingWindowWrapper::ImplDockingWindowWrapper( const vcl::Window *pWindow )
+ : mpDockingWindow(const_cast<vcl::Window*>(pWindow))
+ , mpFloatWin(nullptr)
+ , mpOldBorderWin(nullptr)
+ , mpParent(pWindow->GetParent())
+ , maMaxOutSize( SHRT_MAX, SHRT_MAX )
+ , mnTrackX(0)
+ , mnTrackY(0)
+ , mnTrackWidth(0)
+ , mnTrackHeight(0)
+ , mnDockLeft(0)
+ , mnDockTop(0)
+ , mnDockRight(0)
+ , mnDockBottom(0)
+ , mnFloatBits(WB_BORDER | WB_CLOSEABLE | WB_SIZEABLE | (pWindow->GetStyle() & DOCKWIN_FLOATSTYLES))
+ , mbDockCanceled(false)
+ , mbDocking(false)
+ , mbLastFloatMode(false)
+ , mbDockBtn(false)
+ , mbHideBtn(false)
+ // must be enabled in Window::Notify to prevent permanent docking during mouse move
+ , mbStartDockingEnabled(false)
+ , mbLocked(false)
+{
+ assert(mpDockingWindow);
+ DockingWindow *pDockWin = dynamic_cast< DockingWindow* > ( mpDockingWindow.get() );
+ if( pDockWin )
+ mnFloatBits = pDockWin->GetFloatStyle();
+}
+
+ImplDockingWindowWrapper::~ImplDockingWindowWrapper()
+{
+ if ( IsFloatingMode() )
+ {
+ GetWindow()->Show( false, ShowFlags::NoFocusChange );
+ SetFloatingMode(false);
+ }
+}
+
+void ImplDockingWindowWrapper::ImplStartDocking( const Point& rPos )
+{
+ if( !mbStartDockingEnabled )
+ return;
+
+ maMouseOff = rPos;
+ mbDocking = true;
+ mbLastFloatMode = IsFloatingMode();
+
+ // calculate FloatingBorder
+ VclPtr<FloatingWindow> pWin;
+ if ( mpFloatWin )
+ pWin = mpFloatWin;
+ else
+ pWin = VclPtr<ImplDockFloatWin2>::Create( mpParent, mnFloatBits, nullptr );
+ pWin->GetBorder( mnDockLeft, mnDockTop, mnDockRight, mnDockBottom );
+ if ( !mpFloatWin )
+ pWin.disposeAndClear();
+
+ Point aPos = GetWindow()->ImplOutputToFrame( Point() );
+ Size aSize = GetWindow()->GetOutputSizePixel();
+ mnTrackX = aPos.X();
+ mnTrackY = aPos.Y();
+ mnTrackWidth = aSize.Width();
+ mnTrackHeight = aSize.Height();
+
+ if ( mbLastFloatMode )
+ {
+ maMouseOff.AdjustX(mnDockLeft );
+ maMouseOff.AdjustY(mnDockTop );
+ mnTrackX -= mnDockLeft;
+ mnTrackY -= mnDockTop;
+ mnTrackWidth += mnDockLeft+mnDockRight;
+ mnTrackHeight += mnDockTop+mnDockBottom;
+ }
+
+ vcl::Window *pDockingArea = GetWindow()->GetParent();
+ vcl::Window::PointerState aState = pDockingArea->GetPointerState();
+
+ // mouse pos in screen pixels
+ Point aMousePos = pDockingArea->OutputToScreenPixel( aState.maPos );
+ Point aDockPos = pDockingArea->AbsoluteScreenToOutputPixel( GetWindow()->OutputToAbsoluteScreenPixel( GetWindow()->GetPosPixel() ) );
+ tools::Rectangle aDockRect( aDockPos, GetWindow()->GetSizePixel() );
+ StartDocking( aMousePos, aDockRect );
+
+ GetWindow()->ImplUpdateAll();
+ GetWindow()->ImplGetFrameWindow()->ImplUpdateAll();
+
+ GetWindow()->StartTracking( StartTrackingFlags::KeyMod );
+}
+
+void ImplDockingWindowWrapper::Tracking( const TrackingEvent& rTEvt )
+{
+ // used during docking of a currently docked window
+ if ( !mbDocking )
+ return;
+
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ mbDocking = false;
+ GetWindow()->HideTracking();
+ if ( rTEvt.IsTrackingCanceled() )
+ {
+ mbDockCanceled = true;
+ EndDocking( tools::Rectangle( Point( mnTrackX, mnTrackY ), Size( mnTrackWidth, mnTrackHeight ) ), mbLastFloatMode );
+ mbDockCanceled = false;
+ }
+ else
+ EndDocking( tools::Rectangle( Point( mnTrackX, mnTrackY ), Size( mnTrackWidth, mnTrackHeight ) ), mbLastFloatMode );
+ }
+ // Docking only upon non-synthetic MouseEvents
+ else if ( !rTEvt.GetMouseEvent().IsSynthetic() || rTEvt.GetMouseEvent().IsModifierChanged() )
+ {
+ Point aMousePos = rTEvt.GetMouseEvent().GetPosPixel();
+ Point aFrameMousePos = GetWindow()->ImplOutputToFrame( aMousePos );
+ Size aFrameSize = GetWindow()->ImplGetFrameWindow()->GetOutputSizePixel();
+ if ( aFrameMousePos.X() < 0 )
+ aFrameMousePos.setX( 0 );
+ if ( aFrameMousePos.Y() < 0 )
+ aFrameMousePos.setY( 0 );
+ if ( aFrameMousePos.X() > aFrameSize.Width()-1 )
+ aFrameMousePos.setX( aFrameSize.Width()-1 );
+ if ( aFrameMousePos.Y() > aFrameSize.Height()-1 )
+ aFrameMousePos.setY( aFrameSize.Height()-1 );
+ aMousePos = GetWindow()->ImplFrameToOutput( aFrameMousePos );
+ aMousePos.AdjustX( -(maMouseOff.X()) );
+ aMousePos.AdjustY( -(maMouseOff.Y()) );
+ Point aPos = GetWindow()->ImplOutputToFrame( aMousePos );
+ tools::Rectangle aTrackRect( aPos, Size( mnTrackWidth, mnTrackHeight ) );
+ tools::Rectangle aCompRect = aTrackRect;
+ aPos.AdjustX(maMouseOff.X() );
+ aPos.AdjustY(maMouseOff.Y() );
+
+ bool bFloatMode = Docking( aPos, aTrackRect );
+
+ if ( mbLastFloatMode != bFloatMode )
+ {
+ if ( bFloatMode )
+ {
+ aTrackRect.AdjustLeft( -mnDockLeft );
+ aTrackRect.AdjustTop( -mnDockTop );
+ aTrackRect.AdjustRight(mnDockRight );
+ aTrackRect.AdjustBottom(mnDockBottom );
+ }
+ else
+ {
+ if ( aCompRect == aTrackRect )
+ {
+ aTrackRect.AdjustLeft(mnDockLeft );
+ aTrackRect.AdjustTop(mnDockTop );
+ aTrackRect.AdjustRight( -mnDockRight );
+ aTrackRect.AdjustBottom( -mnDockBottom );
+ }
+ }
+ mbLastFloatMode = bFloatMode;
+ }
+
+ ShowTrackFlags nTrackStyle;
+ if ( bFloatMode )
+ nTrackStyle = ShowTrackFlags::Object;
+ else
+ nTrackStyle = ShowTrackFlags::Big;
+ tools::Rectangle aShowTrackRect = aTrackRect;
+ aShowTrackRect.SetPos( GetWindow()->ImplFrameToOutput( aShowTrackRect.TopLeft() ) );
+
+ GetWindow()->ShowTracking( aShowTrackRect, nTrackStyle );
+
+ // calculate mouse offset again, as the rectangle was changed
+ maMouseOff.setX( aPos.X() - aTrackRect.Left() );
+ maMouseOff.setY( aPos.Y() - aTrackRect.Top() );
+
+ mnTrackX = aTrackRect.Left();
+ mnTrackY = aTrackRect.Top();
+ mnTrackWidth = aTrackRect.GetWidth();
+ mnTrackHeight = aTrackRect.GetHeight();
+ }
+}
+
+void ImplDockingWindowWrapper::StartDocking( const Point& rPoint, tools::Rectangle const & rRect )
+{
+ DockingData data( rPoint, rRect, IsFloatingMode() );
+
+ GetWindow()->CallEventListeners( VclEventId::WindowStartDocking, &data );
+ mbDocking = true;
+}
+
+bool ImplDockingWindowWrapper::Docking( const Point& rPoint, tools::Rectangle& rRect )
+{
+ DockingData data( rPoint, rRect, IsFloatingMode() );
+
+ GetWindow()->CallEventListeners( VclEventId::WindowDocking, &data );
+ rRect = data.maTrackRect;
+ return data.mbFloating;
+}
+
+void ImplDockingWindowWrapper::EndDocking( const tools::Rectangle& rRect, bool bFloatMode )
+{
+ tools::Rectangle aRect( rRect );
+
+ bool bOrigDockCanceled = mbDockCanceled;
+ if (bFloatMode && !StyleSettings::GetDockingFloatsSupported())
+ mbDockCanceled = true;
+
+ if ( !IsDockingCanceled() )
+ {
+ bool bShow = false;
+ if ( bFloatMode != IsFloatingMode() )
+ {
+ GetWindow()->Show( false, ShowFlags::NoFocusChange );
+ SetFloatingMode( bFloatMode );
+ bShow = true;
+ if ( bFloatMode )
+ {
+ // #i44800# always use outputsize - as in all other places
+ mpFloatWin->SetOutputSizePixel( aRect.GetSize() );
+ mpFloatWin->SetPosPixel( aRect.TopLeft() );
+ }
+ }
+ if ( !bFloatMode )
+ {
+ Point aPos = aRect.TopLeft();
+ aPos = GetWindow()->GetParent()->ScreenToOutputPixel( aPos );
+ GetWindow()->SetPosSizePixel( aPos, aRect.GetSize() );
+ }
+
+ if ( bShow )
+ GetWindow()->Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate );
+ }
+
+ EndDockingData data( aRect, IsFloatingMode(), IsDockingCanceled() );
+ GetWindow()->CallEventListeners( VclEventId::WindowEndDocking, &data );
+
+ mbDocking = false;
+
+ // must be enabled in Window::Notify to prevent permanent docking during mouse move
+ mbStartDockingEnabled = false;
+
+ mbDockCanceled = bOrigDockCanceled;
+}
+
+bool ImplDockingWindowWrapper::PrepareToggleFloatingMode()
+{
+ bool bFloating = true;
+ GetWindow()->CallEventListeners( VclEventId::WindowPrepareToggleFloating, &bFloating );
+ return bFloating;
+}
+
+void ImplDockingWindowWrapper::ToggleFloatingMode()
+{
+ // notify dockingwindow/toolbox
+ // note: this must be done *before* notifying the
+ // listeners to have the toolbox in the proper state
+ if( GetWindow()->IsDockingWindow() )
+ static_cast<DockingWindow*>(GetWindow())->ToggleFloatingMode();
+
+ // now notify listeners
+ GetWindow()->CallEventListeners( VclEventId::WindowToggleFloating );
+
+ // must be enabled in Window::Notify to prevent permanent docking during mouse move
+ mbStartDockingEnabled = false;
+}
+
+void ImplDockingWindowWrapper::TitleButtonClick( TitleButton nType )
+{
+ if( nType == TitleButton::Menu )
+ {
+ ToolBox *pToolBox = dynamic_cast< ToolBox* >( GetWindow() );
+ if( pToolBox )
+ {
+ pToolBox->ExecuteCustomMenu();
+ }
+ }
+ if( nType == TitleButton::Docking )
+ {
+ SetFloatingMode( !IsFloatingMode() );
+ }
+}
+
+void ImplDockingWindowWrapper::Resizing( Size& rSize )
+{
+ // TODO: add virtual Resizing() to class Window, so we can get rid of class DockingWindow
+ DockingWindow *pDockingWindow = dynamic_cast< DockingWindow* >( GetWindow() );
+ if( pDockingWindow )
+ pDockingWindow->Resizing( rSize );
+}
+
+void ImplDockingWindowWrapper::ShowMenuTitleButton( bool bVisible )
+{
+ if ( mpFloatWin )
+ mpFloatWin->ShowTitleButton( TitleButton::Menu, bVisible );
+}
+
+void ImplDockingWindowWrapper::ImplPreparePopupMode()
+{
+ VclPtr<vcl::Window> xWindow = GetWindow();
+ xWindow->Show( false, ShowFlags::NoFocusChange );
+
+ // prepare reparenting
+ vcl::Window* pRealParent = xWindow->GetWindow( GetWindowType::Parent );
+ mpOldBorderWin = xWindow->GetWindow( GetWindowType::Border );
+ if( mpOldBorderWin.get() == xWindow )
+ mpOldBorderWin = nullptr; // no border window found
+
+ // the new parent for popup mode
+ VclPtrInstance<ImplPopupFloatWin> pWin( mpParent, xWindow->GetType() == WindowType::TOOLBOX );
+ pWin->SetPopupModeEndHdl( LINK( this, ImplDockingWindowWrapper, PopupModeEnd ) );
+
+ // At least for DockingWindow, GetText() has a side effect of setting deferred
+ // properties. This must be done before setting the border window (see below),
+ // so that the border width will end up in mpWindowImpl->mnBorderWidth, not in
+ // the border window (See DockingWindow::setPosSizeOnContainee() and
+ // DockingWindow::GetOptimalSize()).
+ pWin->SetText( xWindow->GetText() );
+ pWin->SetOutputSizePixel( xWindow->GetSizePixel() );
+
+ xWindow->mpWindowImpl->mpBorderWindow = nullptr;
+ xWindow->mpWindowImpl->mnLeftBorder = 0;
+ xWindow->mpWindowImpl->mnTopBorder = 0;
+ xWindow->mpWindowImpl->mnRightBorder = 0;
+ xWindow->mpWindowImpl->mnBottomBorder = 0;
+
+ // reparent borderwindow and window
+ if ( mpOldBorderWin )
+ mpOldBorderWin->SetParent( pWin );
+ xWindow->SetParent( pWin );
+
+ // correct border window pointers
+ xWindow->mpWindowImpl->mpBorderWindow = pWin;
+ pWin->mpWindowImpl->mpClientWindow = xWindow;
+ xWindow->mpWindowImpl->mpRealParent = pRealParent;
+
+ // set mpFloatWin not until all window positioning is done !!!
+ // (SetPosPixel etc. check for valid mpFloatWin pointer)
+ mpFloatWin = pWin;
+}
+
+void ImplDockingWindowWrapper::StartPopupMode( ToolBox *pParentToolBox, FloatWinPopupFlags nFlags )
+{
+ // do nothing if window is floating
+ if( IsFloatingMode() )
+ return;
+
+ ImplPreparePopupMode();
+
+ // don't allow tearoff, if globally disabled
+ if( !StyleSettings::GetDockingFloatsSupported() )
+ nFlags &= ~FloatWinPopupFlags::AllowTearOff;
+
+ // if the subtoolbar was opened via keyboard make sure that key events
+ // will go into subtoolbar
+ if( pParentToolBox->IsKeyEvent() )
+ nFlags |= FloatWinPopupFlags::GrabFocus;
+
+ mpFloatWin->StartPopupMode( pParentToolBox, nFlags );
+ GetWindow()->Show();
+
+ if( pParentToolBox->IsKeyEvent() )
+ {
+ // send HOME key to subtoolbar in order to select first item
+ KeyEvent aEvent( 0, vcl::KeyCode( KEY_HOME ) );
+ GetWindow()->KeyInput(aEvent);
+ }
+}
+
+void ImplDockingWindowWrapper::StartPopupMode( const tools::Rectangle& rRect, FloatWinPopupFlags nFlags )
+{
+ // do nothing if window is floating
+ if( IsFloatingMode() )
+ return;
+
+ ImplPreparePopupMode();
+ mpFloatWin->StartPopupMode( rRect, nFlags );
+ GetWindow()->Show();
+}
+
+IMPL_LINK_NOARG(ImplDockingWindowWrapper, PopupModeEnd, FloatingWindow*, void)
+{
+ VclPtr<vcl::Window> xWindow = GetWindow();
+ xWindow->Show( false, ShowFlags::NoFocusChange );
+
+ // set parameter for handler before destroying floating window
+ EndPopupModeData aData( mpFloatWin->GetWindow( GetWindowType::Border )->GetPosPixel(), mpFloatWin->IsPopupModeTearOff() );
+
+ // before deleting change parent back, so we can delete the floating window alone
+ vcl::Window* pRealParent = xWindow->GetWindow( GetWindowType::Parent );
+ xWindow->mpWindowImpl->mpBorderWindow = nullptr;
+ if ( mpOldBorderWin )
+ {
+ xWindow->SetParent( mpOldBorderWin );
+ static_cast<ImplBorderWindow*>(mpOldBorderWin.get())->GetBorder(
+ xWindow->mpWindowImpl->mnLeftBorder, xWindow->mpWindowImpl->mnTopBorder,
+ xWindow->mpWindowImpl->mnRightBorder, xWindow->mpWindowImpl->mnBottomBorder );
+ mpOldBorderWin->Resize();
+ }
+ xWindow->mpWindowImpl->mpBorderWindow = mpOldBorderWin;
+ xWindow->SetParent( pRealParent );
+ xWindow->mpWindowImpl->mpRealParent = pRealParent;
+
+ // take ownership to local variable to protect against maPopupModeEndHdl destroying this object
+ auto xFloatWin = std::move(mpFloatWin);
+ maPopupModeEndHdl.Call(xFloatWin);
+ xFloatWin.disposeAndClear();
+
+ // call handler - which will destroy the window and thus the wrapper as well !
+ xWindow->CallEventListeners( VclEventId::WindowEndPopupMode, &aData );
+}
+
+bool ImplDockingWindowWrapper::IsInPopupMode() const
+{
+ if( GetFloatingWindow() )
+ return static_cast<FloatingWindow*>(GetFloatingWindow())->IsInPopupMode();
+ else
+ return false;
+}
+
+void ImplDockingWindowWrapper::SetFloatingMode( bool bFloatMode )
+{
+ // do nothing if window is docked and locked
+ if( !IsFloatingMode() && IsLocked() )
+ return;
+
+ if ( IsFloatingMode() == bFloatMode )
+ return;
+
+ if ( !PrepareToggleFloatingMode() )
+ return;
+
+ bool bVisible = GetWindow()->IsVisible();
+
+ if ( bFloatMode )
+ {
+ GetWindow()->Show( false, ShowFlags::NoFocusChange );
+
+ maDockPos = GetWindow()->GetPosPixel();
+
+ vcl::Window* pRealParent = GetWindow()->GetWindow( GetWindowType::Parent );
+ mpOldBorderWin = GetWindow()->GetWindow( GetWindowType::Border );
+ if( mpOldBorderWin == mpDockingWindow )
+ mpOldBorderWin = nullptr; // no border window found
+
+ VclPtrInstance<ImplDockFloatWin2> pWin(
+ mpParent,
+ mnFloatBits & ( WB_MOVEABLE | WB_SIZEABLE | WB_CLOSEABLE ) ?
+ mnFloatBits | WB_SYSTEMWINDOW
+ | WB_OWNERDRAWDECORATION
+ : mnFloatBits,
+ this );
+
+ // At least for DockingWindow, GetText() has a side effect of setting deferred
+ // properties. This must be done before setting the border window (see below),
+ // so that the border width will end up in mpWindowImpl->mnBorderWidth, not in
+ // the border window (See DockingWindow::setPosSizeOnContainee() and
+ // DockingWindow::GetOptimalSize()).
+ pWin->SetText( GetWindow()->GetText() );
+
+ GetWindow()->mpWindowImpl->mpBorderWindow = nullptr;
+ GetWindow()->mpWindowImpl->mnLeftBorder = 0;
+ GetWindow()->mpWindowImpl->mnTopBorder = 0;
+ GetWindow()->mpWindowImpl->mnRightBorder = 0;
+ GetWindow()->mpWindowImpl->mnBottomBorder = 0;
+
+ // if the parent gets destroyed, we also have to reset the parent of the BorderWindow
+ if ( mpOldBorderWin )
+ mpOldBorderWin->SetParent( pWin );
+ GetWindow()->SetParent( pWin );
+ pWin->SetPosPixel( Point() );
+
+ GetWindow()->mpWindowImpl->mpBorderWindow = pWin;
+ pWin->mpWindowImpl->mpClientWindow = mpDockingWindow;
+ GetWindow()->mpWindowImpl->mpRealParent = pRealParent;
+
+ pWin->SetOutputSizePixel( GetWindow()->GetSizePixel() );
+ pWin->SetPosPixel( maFloatPos );
+ // pass on DockingData to FloatingWindow
+ pWin->ShowTitleButton( TitleButton::Docking, mbDockBtn );
+ pWin->ShowTitleButton( TitleButton::Hide, mbHideBtn );
+ pWin->SetMinOutputSizePixel( maMinOutSize );
+ pWin->SetMaxOutputSizePixel( maMaxOutSize );
+
+ mpFloatWin = pWin;
+
+ if ( bVisible )
+ GetWindow()->Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate );
+
+ ToggleFloatingMode();
+ }
+ else
+ {
+ GetWindow()->Show( false, ShowFlags::NoFocusChange );
+
+ // store FloatingData in FloatingWindow
+ maFloatPos = mpFloatWin->GetPosPixel();
+ mbDockBtn = mpFloatWin->IsTitleButtonVisible( TitleButton::Docking );
+ mbHideBtn = mpFloatWin->IsTitleButtonVisible( TitleButton::Hide );
+ maMinOutSize = mpFloatWin->GetMinOutputSizePixel();
+ maMaxOutSize = mpFloatWin->GetMaxOutputSizePixel();
+
+ vcl::Window* pRealParent = GetWindow()->GetWindow( GetWindowType::Parent ); //mpWindowImpl->mpRealParent;
+ GetWindow()->mpWindowImpl->mpBorderWindow = nullptr;
+ if ( mpOldBorderWin )
+ {
+ GetWindow()->SetParent( mpOldBorderWin );
+ static_cast<ImplBorderWindow*>(mpOldBorderWin.get())->GetBorder(
+ GetWindow()->mpWindowImpl->mnLeftBorder, GetWindow()->mpWindowImpl->mnTopBorder,
+ GetWindow()->mpWindowImpl->mnRightBorder, GetWindow()->mpWindowImpl->mnBottomBorder );
+ mpOldBorderWin->Resize();
+ }
+ GetWindow()->mpWindowImpl->mpBorderWindow = mpOldBorderWin;
+ GetWindow()->SetParent( pRealParent );
+ GetWindow()->mpWindowImpl->mpRealParent = pRealParent;
+
+ mpFloatWin.disposeAndClear();
+ GetWindow()->SetPosPixel( maDockPos );
+
+ if ( bVisible )
+ GetWindow()->Show();
+
+ ToggleFloatingMode();
+
+ }
+}
+
+void ImplDockingWindowWrapper::SetFloatStyle( WinBits nStyle )
+{
+ mnFloatBits = nStyle;
+}
+
+
+void ImplDockingWindowWrapper::setPosSizePixel( tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ PosSizeFlags nFlags )
+{
+ if ( mpFloatWin )
+ mpFloatWin->setPosSizePixel( nX, nY, nWidth, nHeight, nFlags );
+ else
+ GetWindow()->setPosSizePixel( nX, nY, nWidth, nHeight, nFlags );
+}
+
+Point ImplDockingWindowWrapper::GetPosPixel() const
+{
+ if ( mpFloatWin )
+ return mpFloatWin->GetPosPixel();
+ else
+ return mpDockingWindow->GetPosPixel();
+}
+
+Size ImplDockingWindowWrapper::GetSizePixel() const
+{
+ if ( mpFloatWin )
+ return mpFloatWin->GetSizePixel();
+ else
+ return mpDockingWindow->GetSizePixel();
+}
+
+// old inlines from DockingWindow
+
+void ImplDockingWindowWrapper::SetMinOutputSizePixel( const Size& rSize )
+{
+ if ( mpFloatWin )
+ mpFloatWin->SetMinOutputSizePixel( rSize );
+ maMinOutSize = rSize;
+}
+
+void ImplDockingWindowWrapper::SetMaxOutputSizePixel( const Size& rSize )
+{
+ if ( mpFloatWin )
+ mpFloatWin->SetMaxOutputSizePixel( rSize );
+ maMaxOutSize = rSize;
+}
+
+bool ImplDockingWindowWrapper::IsFloatingMode() const
+{
+ return (mpFloatWin != nullptr);
+}
+
+void ImplDockingWindowWrapper::SetDragArea( const tools::Rectangle& rRect )
+{
+ maDragArea = rRect;
+}
+
+
+void ImplDockingWindowWrapper::Lock()
+{
+ mbLocked = true;
+ // only toolbars support locking
+ ToolBox *pToolBox = dynamic_cast< ToolBox * >( GetWindow() );
+ if( pToolBox )
+ pToolBox->Lock( mbLocked );
+}
+
+void ImplDockingWindowWrapper::Unlock()
+{
+ mbLocked = false;
+ // only toolbars support locking
+ ToolBox *pToolBox = dynamic_cast< ToolBox * >( GetWindow() );
+ if( pToolBox )
+ pToolBox->Lock( mbLocked );
+}
+
+SystemWindow* ImplDockingWindowWrapper::GetFloatingWindow() const
+{
+ return mpFloatWin;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/dockwin.cxx b/vcl/source/window/dockwin.cxx
new file mode 100644
index 000000000..fb216913b
--- /dev/null
+++ b/vcl/source/window/dockwin.cxx
@@ -0,0 +1,1147 @@
+/* -*- 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 <tools/time.hxx>
+#include <sal/log.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/dockwin.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/settings.hxx>
+#include <comphelper/lok.hxx>
+
+#include <accel.hxx>
+#include <svdata.hxx>
+#include <window.h>
+#include <brdwin.hxx>
+
+#include "impldockingwrapper.hxx"
+
+#define DOCKWIN_FLOATSTYLES (WB_SIZEABLE | WB_MOVEABLE | WB_CLOSEABLE | WB_STANDALONE)
+
+class DockingWindow::ImplData
+{
+public:
+ ImplData();
+
+ VclPtr<vcl::Window> mpParent;
+ Size maMaxOutSize;
+};
+
+DockingWindow::ImplData::ImplData()
+{
+ mpParent = nullptr;
+ maMaxOutSize = Size( SHRT_MAX, SHRT_MAX );
+}
+
+namespace {
+
+class ImplDockFloatWin : public FloatingWindow
+{
+private:
+ VclPtr<DockingWindow> mpDockWin;
+ sal_uInt64 mnLastTicks;
+ Idle maDockIdle;
+ Point maDockPos;
+ tools::Rectangle maDockRect;
+ bool mbInMove;
+ ImplSVEvent * mnLastUserEvent;
+
+ DECL_LINK(DockingHdl, void *, void);
+ DECL_LINK(DockTimerHdl, Timer *, void);
+public:
+ ImplDockFloatWin( vcl::Window* pParent, WinBits nWinBits,
+ DockingWindow* pDockingWin );
+ virtual ~ImplDockFloatWin() override;
+ virtual void dispose() override;
+
+ virtual void Move() override;
+ virtual void Resize() override;
+ virtual void Resizing( Size& rSize ) override;
+ virtual bool Close() override;
+};
+
+}
+
+ImplDockFloatWin::ImplDockFloatWin( vcl::Window* pParent, WinBits nWinBits,
+ DockingWindow* pDockingWin ) :
+ FloatingWindow( pParent, nWinBits ),
+ mpDockWin( pDockingWin ),
+ mnLastTicks( tools::Time::GetSystemTicks() ),
+ maDockIdle( "vcl::ImplDockFloatWin maDockIdle" ),
+ mbInMove( false ),
+ mnLastUserEvent( nullptr )
+{
+ // copy settings of DockingWindow
+ if ( pDockingWin )
+ {
+ GetOutDev()->SetSettings( pDockingWin->GetSettings() );
+ Enable( pDockingWin->IsEnabled(), false );
+ EnableInput( pDockingWin->IsInputEnabled(), false );
+ AlwaysEnableInput( pDockingWin->IsAlwaysEnableInput(), false );
+ EnableAlwaysOnTop( pDockingWin->IsAlwaysOnTopEnabled() );
+ SetActivateMode( pDockingWin->GetActivateMode() );
+ }
+
+ SetBackground();
+
+ maDockIdle.SetInvokeHandler( LINK( this, ImplDockFloatWin, DockTimerHdl ) );
+ maDockIdle.SetPriority( TaskPriority::HIGH_IDLE );
+}
+
+ImplDockFloatWin::~ImplDockFloatWin()
+{
+ disposeOnce();
+}
+
+void ImplDockFloatWin::dispose()
+{
+ if( mnLastUserEvent )
+ Application::RemoveUserEvent( mnLastUserEvent );
+
+ disposeBuilder();
+
+ mpDockWin.clear();
+ FloatingWindow::dispose();
+}
+
+IMPL_LINK_NOARG(ImplDockFloatWin, DockTimerHdl, Timer *, void)
+{
+ SAL_WARN_IF( !mpDockWin->IsFloatingMode(), "vcl", "docktimer called but not floating" );
+
+ maDockIdle.Stop();
+ PointerState aState = GetPointerState();
+
+ if( aState.mnState & KEY_MOD1 )
+ {
+ // i43499 CTRL disables docking now
+ mpDockWin->GetParent()->ImplGetFrameWindow()->HideTracking();
+ mpDockWin->EndDocking( maDockRect, true );
+ if( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) )
+ maDockIdle.Start();
+ }
+ else if( ! ( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) ) )
+ {
+ mpDockWin->GetParent()->ImplGetFrameWindow()->HideTracking();
+ mpDockWin->EndDocking( maDockRect, false );
+ }
+ else
+ {
+ mpDockWin->GetParent()->ImplGetFrameWindow()->ShowTracking( maDockRect, ShowTrackFlags::Big | ShowTrackFlags::TrackWindow );
+ maDockIdle.Start();
+ }
+}
+
+IMPL_LINK_NOARG(ImplDockFloatWin, DockingHdl, void*, void)
+{
+ PointerState aState = mpDockWin->GetParent()->GetPointerState();
+
+ mnLastUserEvent = nullptr;
+ if( mpDockWin->IsDockable() &&
+ (tools::Time::GetSystemTicks() - mnLastTicks > 500) &&
+ ( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) ) &&
+ !(aState.mnState & KEY_MOD1) ) // i43499 CTRL disables docking now
+ {
+ maDockPos = mpDockWin->GetParent()->AbsoluteScreenToOutputPixel( OutputToAbsoluteScreenPixel( Point() ) );
+ maDockPos = mpDockWin->GetParent()->OutputToScreenPixel( maDockPos ); // sfx expects screen coordinates
+
+ if( ! mpDockWin->IsDocking() )
+ mpDockWin->StartDocking();
+ maDockRect = tools::Rectangle( maDockPos, mpDockWin->GetSizePixel() );
+
+ // mouse pos also in screen pixels
+ Point aMousePos = mpDockWin->GetParent()->OutputToScreenPixel( aState.maPos );
+
+ bool bFloatMode = mpDockWin->Docking( aMousePos, maDockRect );
+ if( ! bFloatMode )
+ {
+ mpDockWin->GetParent()->ImplGetFrameWindow()->ShowTracking( maDockRect, ShowTrackFlags::Object | ShowTrackFlags::TrackWindow );
+ DockTimerHdl( nullptr );
+ }
+ else
+ {
+ mpDockWin->GetParent()->ImplGetFrameWindow()->HideTracking();
+ maDockIdle.Stop();
+ mpDockWin->EndDocking( maDockRect, true );
+ }
+ }
+ mbInMove = false;
+}
+
+void ImplDockFloatWin::Move()
+{
+ if( mbInMove )
+ return;
+
+ mbInMove = true;
+ FloatingWindow::Move();
+ mpDockWin->Move();
+
+ /*
+ * note: the window should only dock if
+ * the user releases all mouse buttons. The real problem here
+ * is that we don't get mouse events (at least not on X)
+ * if the mouse is on the decoration. So we have to start an
+ * awkward timer based process that polls the modifier/buttons
+ * to see whether they are in the right condition shortly after the
+ * last Move message.
+ */
+ if( ! mnLastUserEvent )
+ mnLastUserEvent = Application::PostUserEvent( LINK( this, ImplDockFloatWin, DockingHdl ), nullptr, true );
+}
+
+void ImplDockFloatWin::Resize()
+{
+ FloatingWindow::Resize();
+ Size aSize( GetSizePixel() );
+ mpDockWin->ImplPosSizeWindow( 0, 0, aSize.Width(), aSize.Height(), PosSizeFlags::PosSize );
+}
+
+void ImplDockFloatWin::Resizing( Size& rSize )
+{
+ FloatingWindow::Resizing( rSize );
+ mpDockWin->Resizing( rSize );
+}
+
+bool ImplDockFloatWin::Close()
+{
+ return mpDockWin->Close();
+}
+
+void DockingWindow::ImplStartDocking( const Point& rPos )
+{
+ if ( !mbDockable )
+ return;
+
+ maMouseOff = rPos;
+ mbDocking = true;
+ mbLastFloatMode = IsFloatingMode();
+ mbStartFloat = mbLastFloatMode;
+
+ // calculate FloatingBorder
+ VclPtr<FloatingWindow> pWin;
+ if ( mpFloatWin )
+ pWin = mpFloatWin;
+ else
+ pWin = VclPtr<ImplDockFloatWin>::Create( mpImplData->mpParent, mnFloatBits, nullptr );
+ pWin->GetBorder( mnDockLeft, mnDockTop, mnDockRight, mnDockBottom );
+ if ( !mpFloatWin )
+ pWin.disposeAndClear();
+
+ Point aPos = ImplOutputToFrame( Point() );
+ Size aSize = Window::GetOutputSizePixel();
+ mnTrackX = aPos.X();
+ mnTrackY = aPos.Y();
+ mnTrackWidth = aSize.Width();
+ mnTrackHeight = aSize.Height();
+
+ if ( mbLastFloatMode )
+ {
+ maMouseOff.AdjustX(mnDockLeft );
+ maMouseOff.AdjustY(mnDockTop );
+ mnTrackX -= mnDockLeft;
+ mnTrackY -= mnDockTop;
+ mnTrackWidth += mnDockLeft+mnDockRight;
+ mnTrackHeight += mnDockTop+mnDockBottom;
+ }
+
+ if ( GetSettings().GetStyleSettings().GetDragFullOptions() & DragFullOptions::Docking &&
+ !( mnFloatBits & ( WB_MOVEABLE | WB_SIZEABLE | WB_CLOSEABLE ) ) ) // no full drag when migrating to system window
+ mbDragFull = true;
+ else
+ {
+ StartDocking();
+ mbDragFull = false;
+ ImplUpdateAll();
+ ImplGetFrameWindow()->ImplUpdateAll();
+ }
+
+ StartTracking( StartTrackingFlags::KeyMod );
+}
+
+void DockingWindow::ImplInitDockingWindowData()
+{
+ mpWindowImpl->mbDockWin = true;
+ mpFloatWin = nullptr;
+ mpOldBorderWin = nullptr;
+ mpImplData.reset(new ImplData);
+ mnTrackX = 0;
+ mnTrackY = 0;
+ mnTrackWidth = 0;
+ mnTrackHeight = 0;
+ mnDockLeft = 0;
+ mnDockTop = 0;
+ mnDockRight = 0;
+ mnDockBottom = 0;
+ mnFloatBits = 0;
+ mbDockCanceled = false;
+ mbDockable = false;
+ mbDocking = false;
+ mbDragFull = false;
+ mbLastFloatMode = false;
+ mbStartFloat = false;
+ mbDockBtn = false;
+ mbHideBtn = false;
+ mbIsDeferredInit = false;
+ mbIsCalculatingInitialLayoutSize = false;
+ mpDialogParent = nullptr;
+
+ //To-Do, reuse maResizeTimer
+ maLayoutIdle.SetPriority(TaskPriority::RESIZE);
+ maLayoutIdle.SetInvokeHandler( LINK( this, DockingWindow, ImplHandleLayoutTimerHdl ) );
+}
+
+void DockingWindow::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ if ( !(nStyle & WB_NODIALOGCONTROL) )
+ nStyle |= WB_DIALOGCONTROL;
+
+ mpImplData->mpParent = pParent;
+ mbDockable = (nStyle & WB_DOCKABLE) != 0;
+ mnFloatBits = WB_BORDER | (nStyle & DOCKWIN_FLOATSTYLES);
+ nStyle &= ~(DOCKWIN_FLOATSTYLES | WB_BORDER);
+
+ Window::ImplInit( pParent, nStyle, nullptr );
+
+ ImplInitSettings();
+}
+
+void DockingWindow::ImplInitSettings()
+{
+ // Hack: to be able to build DockingWindows w/o background before switching
+ // TODO: Hack
+ if ( !IsBackground() )
+ return;
+
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+
+ Color aColor;
+ if ( IsControlBackground() )
+ aColor = GetControlBackground();
+ else if ( Window::GetStyle() & WB_3DLOOK )
+ aColor = rStyleSettings.GetFaceColor();
+ else
+ aColor = rStyleSettings.GetWindowColor();
+ SetBackground( aColor );
+}
+
+DockingWindow::DockingWindow( WindowType nType, const char* pIdleDebugName ) :
+ Window(nType),
+ maLayoutIdle( pIdleDebugName )
+{
+ ImplInitDockingWindowData();
+}
+
+DockingWindow::DockingWindow( vcl::Window* pParent, WinBits nStyle, const char* pIdleDebugName ) :
+ Window( WindowType::DOCKINGWINDOW ),
+ maLayoutIdle( pIdleDebugName )
+{
+ ImplInitDockingWindowData();
+ ImplInit( pParent, nStyle );
+}
+
+//Find the real parent stashed in mpDialogParent.
+void DockingWindow::doDeferredInit(WinBits nBits)
+{
+ vcl::Window *pParent = mpDialogParent;
+ mpDialogParent = nullptr;
+ ImplInit(pParent, nBits);
+ mbIsDeferredInit = false;
+}
+
+void DockingWindow::loadUI(vcl::Window* pParent, const OString& rID, const OUString& rUIXMLDescription,
+ const css::uno::Reference<css::frame::XFrame> &rFrame)
+{
+ mbIsDeferredInit = true;
+ mpDialogParent = pParent; //should be unset in doDeferredInit
+ m_pUIBuilder.reset( new VclBuilder(this, AllSettings::GetUIRootDir(), rUIXMLDescription, rID, rFrame) );
+}
+
+DockingWindow::DockingWindow(vcl::Window* pParent, const OString& rID,
+ const OUString& rUIXMLDescription, const char* pIdleDebugName,
+ const css::uno::Reference<css::frame::XFrame> &rFrame)
+ : Window(WindowType::DOCKINGWINDOW),
+ maLayoutIdle( pIdleDebugName )
+{
+ ImplInitDockingWindowData();
+
+ loadUI(pParent, rID, rUIXMLDescription, rFrame);
+}
+
+DockingWindow::~DockingWindow()
+{
+ disposeOnce();
+}
+
+void DockingWindow::dispose()
+{
+ if ( IsFloatingMode() )
+ {
+ Show( false, ShowFlags::NoFocusChange );
+ SetFloatingMode(false);
+ }
+ mpImplData.reset();
+ mpFloatWin.clear();
+ mpOldBorderWin.clear();
+ mpDialogParent.clear();
+ disposeBuilder();
+ Window::dispose();
+}
+
+void DockingWindow::Tracking( const TrackingEvent& rTEvt )
+{
+ if( GetDockingManager()->IsDockable( this ) ) // new docking interface
+ return Window::Tracking( rTEvt );
+
+ if ( !mbDocking )
+ return;
+
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ mbDocking = false;
+ if ( mbDragFull )
+ {
+ // reset old state on Cancel
+ if ( rTEvt.IsTrackingCanceled() )
+ {
+ StartDocking();
+ tools::Rectangle aRect( Point( mnTrackX, mnTrackY ), Size( mnTrackWidth, mnTrackHeight ) );
+ EndDocking( aRect, mbStartFloat );
+ }
+ }
+ else
+ {
+ HideTracking();
+ if ( rTEvt.IsTrackingCanceled() )
+ {
+ mbDockCanceled = true;
+ EndDocking( tools::Rectangle( Point( mnTrackX, mnTrackY ), Size( mnTrackWidth, mnTrackHeight ) ), mbLastFloatMode );
+ mbDockCanceled = false;
+ }
+ else
+ EndDocking( tools::Rectangle( Point( mnTrackX, mnTrackY ), Size( mnTrackWidth, mnTrackHeight ) ), mbLastFloatMode );
+ }
+ }
+ // dock only for non-synthetic MouseEvents
+ else if ( !rTEvt.GetMouseEvent().IsSynthetic() || rTEvt.GetMouseEvent().IsModifierChanged() )
+ {
+ Point aMousePos = rTEvt.GetMouseEvent().GetPosPixel();
+ Point aFrameMousePos = ImplOutputToFrame( aMousePos );
+ Size aFrameSize = mpWindowImpl->mpFrameWindow->GetOutputSizePixel();
+ if ( aFrameMousePos.X() < 0 )
+ aFrameMousePos.setX( 0 );
+ if ( aFrameMousePos.Y() < 0 )
+ aFrameMousePos.setY( 0 );
+ if ( aFrameMousePos.X() > aFrameSize.Width()-1 )
+ aFrameMousePos.setX( aFrameSize.Width()-1 );
+ if ( aFrameMousePos.Y() > aFrameSize.Height()-1 )
+ aFrameMousePos.setY( aFrameSize.Height()-1 );
+ aMousePos = ImplFrameToOutput( aFrameMousePos );
+ aMousePos.AdjustX( -(maMouseOff.X()) );
+ aMousePos.AdjustY( -(maMouseOff.Y()) );
+ Point aFramePos = ImplOutputToFrame( aMousePos );
+ tools::Rectangle aTrackRect( aFramePos, Size( mnTrackWidth, mnTrackHeight ) );
+ tools::Rectangle aCompRect = aTrackRect;
+ aFramePos.AdjustX(maMouseOff.X() );
+ aFramePos.AdjustY(maMouseOff.Y() );
+ if ( mbDragFull )
+ StartDocking();
+ bool bFloatMode = Docking( aFramePos, aTrackRect );
+ if ( mbLastFloatMode != bFloatMode )
+ {
+ if ( bFloatMode )
+ {
+ aTrackRect.AdjustLeft( -mnDockLeft );
+ aTrackRect.AdjustTop( -mnDockTop );
+ aTrackRect.AdjustRight(mnDockRight );
+ aTrackRect.AdjustBottom(mnDockBottom );
+ }
+ else
+ {
+ if ( aCompRect == aTrackRect )
+ {
+ aTrackRect.AdjustLeft(mnDockLeft );
+ aTrackRect.AdjustTop(mnDockTop );
+ aTrackRect.AdjustRight( -mnDockRight );
+ aTrackRect.AdjustBottom( -mnDockBottom );
+ }
+ }
+ mbLastFloatMode = bFloatMode;
+ }
+ if ( mbDragFull )
+ {
+ Point aOldPos = OutputToScreenPixel( Point() );
+ EndDocking( aTrackRect, mbLastFloatMode );
+ // repaint if state or position has changed
+ if ( aOldPos != OutputToScreenPixel( Point() ) )
+ {
+ ImplUpdateAll();
+ ImplGetFrameWindow()->ImplUpdateAll();
+ }
+// EndDocking( aTrackRect, mbLastFloatMode );
+ }
+ else
+ {
+ ShowTrackFlags nTrackStyle;
+ if ( bFloatMode )
+ nTrackStyle = ShowTrackFlags::Big;
+ else
+ nTrackStyle = ShowTrackFlags::Object;
+ tools::Rectangle aShowTrackRect = aTrackRect;
+ aShowTrackRect.SetPos( ImplFrameToOutput( aShowTrackRect.TopLeft() ) );
+ ShowTracking( aShowTrackRect, nTrackStyle );
+
+ // recalculate mouse offset, as the rectangle was changed
+ maMouseOff.setX( aFramePos.X() - aTrackRect.Left() );
+ maMouseOff.setY( aFramePos.Y() - aTrackRect.Top() );
+ }
+
+ mnTrackX = aTrackRect.Left();
+ mnTrackY = aTrackRect.Top();
+ mnTrackWidth = aTrackRect.GetWidth();
+ mnTrackHeight = aTrackRect.GetHeight();
+ }
+}
+
+bool DockingWindow::EventNotify( NotifyEvent& rNEvt )
+{
+ if( GetDockingManager()->IsDockable( this ) ) // new docking interface
+ return Window::EventNotify( rNEvt );
+
+ if ( mbDockable )
+ {
+ const bool bDockingSupportCrippled = !StyleSettings::GetDockingFloatsSupported();
+
+ if ( rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ const MouseEvent* pMEvt = rNEvt.GetMouseEvent();
+ if ( pMEvt->IsLeft() )
+ {
+ if (!bDockingSupportCrippled && pMEvt->IsMod1() && (pMEvt->GetClicks() == 2) )
+ {
+ SetFloatingMode( !IsFloatingMode() );
+ if ( IsFloatingMode() )
+ ToTop( ToTopFlags::GrabFocusOnly );
+ return true;
+ }
+ else if ( pMEvt->GetClicks() == 1 )
+ {
+ // check if window is floating standalone (IsFloating())
+ // or only partially floating and still docked with one border
+ // ( !mpWindowImpl->mbFrame)
+ if( ! IsFloatingMode() || ! mpFloatWin->mpWindowImpl->mbFrame )
+ {
+ Point aPos = pMEvt->GetPosPixel();
+ vcl::Window* pWindow = rNEvt.GetWindow();
+ if ( pWindow != this )
+ {
+ aPos = pWindow->OutputToScreenPixel( aPos );
+ aPos = ScreenToOutputPixel( aPos );
+ }
+ ImplStartDocking( aPos );
+ }
+ return true;
+ }
+ }
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ const vcl::KeyCode& rKey = rNEvt.GetKeyEvent()->GetKeyCode();
+ if( rKey.GetCode() == KEY_F10 && rKey.GetModifier() &&
+ rKey.IsShift() && rKey.IsMod1() && !bDockingSupportCrippled )
+ {
+ SetFloatingMode( !IsFloatingMode() );
+ if ( IsFloatingMode() )
+ ToTop( ToTopFlags::GrabFocusOnly );
+ return true;
+ }
+ }
+ }
+
+ return Window::EventNotify( rNEvt );
+}
+
+void DockingWindow::StartDocking()
+{
+ mbDocking = true;
+}
+
+bool DockingWindow::Docking( const Point&, tools::Rectangle& )
+{
+ return IsFloatingMode();
+}
+
+void DockingWindow::EndDocking( const tools::Rectangle& rRect, bool bFloatMode )
+{
+ bool bOrigDockCanceled = mbDockCanceled;
+ if (bFloatMode && !StyleSettings::GetDockingFloatsSupported())
+ mbDockCanceled = true;
+
+ if ( !IsDockingCanceled() )
+ {
+ if ( bFloatMode != IsFloatingMode() )
+ {
+ SetFloatingMode( bFloatMode );
+ if ( IsFloatingMode() )
+ ToTop( ToTopFlags::GrabFocusOnly );
+ if ( bFloatMode && mpFloatWin )
+ mpFloatWin->SetPosSizePixel( rRect.TopLeft(), rRect.GetSize() );
+ }
+ if ( !bFloatMode )
+ {
+ Point aPos = rRect.TopLeft();
+ aPos = GetParent()->ScreenToOutputPixel( aPos );
+ Window::SetPosSizePixel( aPos, rRect.GetSize() );
+ }
+ }
+ mbDocking = false;
+ mbDockCanceled = bOrigDockCanceled;
+}
+
+bool DockingWindow::PrepareToggleFloatingMode()
+{
+ return true;
+}
+
+bool DockingWindow::Close()
+{
+ VclPtr<vcl::Window> xWindow = this;
+ CallEventListeners( VclEventId::WindowClose );
+ if ( xWindow->isDisposed() )
+ return false;
+
+ if ( mpWindowImpl->mxWindowPeer.is() && IsCreatedWithToolkit() )
+ return false;
+
+ Show( false, ShowFlags::NoFocusChange );
+ return true;
+}
+
+void DockingWindow::ToggleFloatingMode()
+{
+}
+
+void DockingWindow::Resizing( Size& )
+{
+}
+
+void DockingWindow::DoInitialLayout()
+{
+ if (GetSettings().GetStyleSettings().GetAutoMnemonic())
+ GenerateAutoMnemonicsOnHierarchy(this);
+
+ if (isLayoutEnabled())
+ {
+ mbIsCalculatingInitialLayoutSize = true;
+ setDeferredProperties();
+ if (IsFloatingMode())
+ setOptimalLayoutSize();
+ mbIsCalculatingInitialLayoutSize = false;
+ }
+}
+
+void DockingWindow::StateChanged( StateChangedType nType )
+{
+ switch(nType)
+ {
+ case StateChangedType::InitShow:
+ DoInitialLayout();
+ break;
+
+ case StateChangedType::ControlBackground:
+ ImplInitSettings();
+ Invalidate();
+ break;
+
+ case StateChangedType::Style:
+ mbDockable = (GetStyle() & WB_DOCKABLE) != 0;
+ break;
+
+ default:
+ break;
+ }
+
+ Window::StateChanged( nType );
+}
+
+void DockingWindow::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+ else
+ Window::DataChanged( rDCEvt );
+}
+
+void DockingWindow::SetFloatingMode( bool bFloatMode )
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ {
+ pWrapper->SetFloatingMode( bFloatMode );
+ return;
+ }
+ if ( IsFloatingMode() == bFloatMode )
+ return;
+
+ if ( !PrepareToggleFloatingMode() ) // changes to floating mode can be vetoed
+ return;
+
+ bool bVisible = IsVisible();
+
+ if ( bFloatMode )
+ {
+ // set deferred properties early, so border width will end up
+ // in our mpWindowImpl->mnBorderWidth, not in mpBorderWindow.
+ // (see its usage in setPosSizeOnContainee and GetOptimalSize.)
+ setDeferredProperties();
+
+ Show( false, ShowFlags::NoFocusChange );
+
+ maDockPos = Window::GetPosPixel();
+
+ vcl::Window* pRealParent = mpWindowImpl->mpRealParent;
+ mpOldBorderWin = mpWindowImpl->mpBorderWindow;
+
+ VclPtrInstance<ImplDockFloatWin> pWin(
+ mpImplData->mpParent,
+ mnFloatBits & ( WB_MOVEABLE | WB_SIZEABLE | WB_CLOSEABLE ) ? mnFloatBits | WB_SYSTEMWINDOW : mnFloatBits,
+ this );
+ mpFloatWin = pWin;
+ mpWindowImpl->mpBorderWindow = nullptr;
+ mpWindowImpl->mnLeftBorder = 0;
+ mpWindowImpl->mnTopBorder = 0;
+ mpWindowImpl->mnRightBorder = 0;
+ mpWindowImpl->mnBottomBorder = 0;
+ // if the parent gets destroyed, we also have to reset the parent of the BorderWindow
+ if ( mpOldBorderWin )
+ mpOldBorderWin->SetParent( pWin );
+
+ // #i123765# reset the buffered DropTargets when undocking, else it may not
+ // be correctly initialized
+ mpWindowImpl->mxDNDListenerContainer.clear();
+
+ SetParent( pWin );
+ SetPosPixel( Point() );
+ mpWindowImpl->mpBorderWindow = pWin;
+ pWin->mpWindowImpl->mpClientWindow = this;
+ mpWindowImpl->mpRealParent = pRealParent;
+ pWin->SetText( Window::GetText() );
+ Size aSize(Window::GetSizePixel());
+ pWin->SetOutputSizePixel(aSize);
+ pWin->SetPosPixel( maFloatPos );
+ // pass on DockingData to FloatingWindow
+ pWin->ShowTitleButton( TitleButton::Docking, mbDockBtn );
+ pWin->ShowTitleButton( TitleButton::Hide, mbHideBtn );
+ pWin->SetMinOutputSizePixel( maMinOutSize );
+
+ pWin->SetMaxOutputSizePixel( mpImplData->maMaxOutSize );
+
+ ToggleFloatingMode();
+
+ if ( bVisible )
+ Show();
+ }
+ else
+ {
+ Show( false, ShowFlags::NoFocusChange );
+
+ // store FloatingData in FloatingWindow
+ maFloatPos = mpFloatWin->GetPosPixel();
+ mbDockBtn = mpFloatWin->IsTitleButtonVisible( TitleButton::Docking );
+ mbHideBtn = mpFloatWin->IsTitleButtonVisible( TitleButton::Hide );
+ maMinOutSize = mpFloatWin->GetMinOutputSizePixel();
+ mpImplData->maMaxOutSize = mpFloatWin->GetMaxOutputSizePixel();
+
+ vcl::Window* pRealParent = mpWindowImpl->mpRealParent;
+ mpWindowImpl->mpBorderWindow = nullptr;
+ if ( mpOldBorderWin )
+ {
+ SetParent( mpOldBorderWin );
+ static_cast<ImplBorderWindow*>(mpOldBorderWin.get())->GetBorder( mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder );
+ mpOldBorderWin->Resize();
+ }
+ mpWindowImpl->mpBorderWindow = mpOldBorderWin;
+ SetParent( pRealParent );
+ mpWindowImpl->mpRealParent = pRealParent;
+ mpFloatWin.disposeAndClear();
+ SetPosPixel( maDockPos );
+
+ ToggleFloatingMode();
+
+ if ( bVisible )
+ Show();
+ }
+}
+
+void DockingWindow::SetFloatStyle( WinBits nStyle )
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ {
+ pWrapper->SetFloatStyle( nStyle );
+ return;
+ }
+
+ mnFloatBits = nStyle;
+}
+
+WinBits DockingWindow::GetFloatStyle() const
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ {
+ return pWrapper->GetFloatStyle();
+ }
+
+ return mnFloatBits;
+}
+
+void DockingWindow::setPosSizePixel( tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ PosSizeFlags nFlags )
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if (pWrapper)
+ {
+ if (!pWrapper->mpFloatWin)
+ Window::setPosSizePixel( nX, nY, nWidth, nHeight, nFlags );
+ }
+ else
+ {
+ if (!mpFloatWin)
+ Window::setPosSizePixel( nX, nY, nWidth, nHeight, nFlags );
+ else if (comphelper::LibreOfficeKit::isActive())
+ {
+ mpFloatWin->SetOutputSizePixel(Size(nWidth, nHeight));
+ mpFloatWin->SetPosPixel(Point(nX, nY));
+ }
+ }
+
+ if (::isLayoutEnabled(this))
+ setPosSizeOnContainee();
+}
+
+Point DockingWindow::GetPosPixel() const
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ {
+ if ( pWrapper->mpFloatWin )
+ return pWrapper->mpFloatWin->GetPosPixel();
+ else
+ return Window::GetPosPixel();
+ }
+
+ if ( mpFloatWin )
+ return mpFloatWin->GetPosPixel();
+ else
+ return Window::GetPosPixel();
+}
+
+Size DockingWindow::GetSizePixel() const
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ {
+ if ( pWrapper->mpFloatWin )
+ return pWrapper->mpFloatWin->GetSizePixel();
+ else
+ return Window::GetSizePixel();
+ }
+
+ if ( mpFloatWin )
+ return mpFloatWin->GetSizePixel();
+ else
+ return Window::GetSizePixel();
+}
+
+void DockingWindow::SetOutputSizePixel( const Size& rNewSize )
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ {
+ if ( pWrapper->mpFloatWin )
+ pWrapper->mpFloatWin->SetOutputSizePixel( rNewSize );
+ else
+ Window::SetOutputSizePixel( rNewSize );
+ return;
+ }
+
+ if ( mpFloatWin )
+ mpFloatWin->SetOutputSizePixel( rNewSize );
+ else
+ Window::SetOutputSizePixel( rNewSize );
+}
+
+Size DockingWindow::GetOutputSizePixel() const
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ {
+ if ( pWrapper->mpFloatWin )
+ return pWrapper->mpFloatWin->GetOutputSizePixel();
+ else
+ return Window::GetOutputSizePixel();
+ }
+
+ if ( mpFloatWin )
+ return mpFloatWin->GetOutputSizePixel();
+ else
+ return Window::GetOutputSizePixel();
+}
+
+Point DockingWindow::GetFloatingPos() const
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ {
+ if ( pWrapper->mpFloatWin )
+ {
+ WindowStateData aData;
+ aData.SetMask( WindowStateMask::Pos );
+ pWrapper->mpFloatWin->GetWindowStateData( aData );
+ Point aPos( aData.GetX(), aData.GetY() );
+ // LOK needs logic coordinates not absolute screen position for autofilter menu
+ if (!comphelper::LibreOfficeKit::isActive() || get_id() != "check_list_menu")
+ aPos = pWrapper->mpFloatWin->GetParent()->ImplGetFrameWindow()->AbsoluteScreenToOutputPixel( aPos );
+ return aPos;
+ }
+ else
+ return maFloatPos;
+ }
+
+ if ( mpFloatWin )
+ {
+ WindowStateData aData;
+ aData.SetMask( WindowStateMask::Pos );
+ mpFloatWin->GetWindowStateData( aData );
+ Point aPos( aData.GetX(), aData.GetY() );
+ aPos = mpFloatWin->GetParent()->ImplGetFrameWindow()->AbsoluteScreenToOutputPixel( aPos );
+ return aPos;
+ }
+ else
+ return maFloatPos;
+}
+
+bool DockingWindow::IsFloatingMode() const
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ return pWrapper->IsFloatingMode();
+ else
+ return (mpFloatWin != nullptr);
+}
+
+void DockingWindow::SetMaxOutputSizePixel( const Size& rSize )
+{
+ if ( mpFloatWin )
+ mpFloatWin->SetMaxOutputSizePixel( rSize );
+ mpImplData->maMaxOutSize = rSize;
+}
+
+void DockingWindow::SetText(const OUString& rStr)
+{
+ setDeferredProperties();
+ Window::SetText(rStr);
+}
+
+OUString DockingWindow::GetText() const
+{
+ const_cast<DockingWindow*>(this)->setDeferredProperties();
+ return Window::GetText();
+}
+
+bool DockingWindow::isLayoutEnabled() const
+{
+ //pre dtor called, and single child is a container => we're layout enabled
+ return mpImplData && ::isLayoutEnabled(this);
+}
+
+void DockingWindow::setOptimalLayoutSize()
+{
+ maLayoutIdle.Stop();
+
+ //resize DockingWindow to fit requisition on initial show
+ Size aSize = get_preferred_size();
+
+ Size aMax(bestmaxFrameSizeForScreenSize(GetDesktopRectPixel().GetSize()));
+
+ aSize.setWidth( std::min(aMax.Width(), aSize.Width()) );
+ aSize.setHeight( std::min(aMax.Height(), aSize.Height()) );
+
+ SetMinOutputSizePixel(aSize);
+ setPosSizeOnContainee();
+}
+
+void DockingWindow::setPosSizeOnContainee()
+{
+ Size aSize = GetOutputSizePixel();
+
+ // Don't make the border width accessible via get_border_width(),
+ // otherwise the floating window will handle the border as well.
+ sal_Int32 nBorderWidth = mpWindowImpl->mnBorderWidth;
+
+ aSize.AdjustWidth( -(2 * nBorderWidth) );
+ aSize.AdjustHeight( -(2 * nBorderWidth) );
+
+ Window* pBox = GetWindow(GetWindowType::FirstChild);
+ assert(pBox);
+ VclContainer::setLayoutAllocation(*pBox, Point(nBorderWidth, nBorderWidth), aSize);
+}
+
+Size DockingWindow::GetOptimalSize() const
+{
+ if (!isLayoutEnabled())
+ return Window::GetOptimalSize();
+
+ Size aSize = VclContainer::getLayoutRequisition(*GetWindow(GetWindowType::FirstChild));
+
+ // Don't make the border width accessible via get_border_width(),
+ // otherwise the floating window will handle the border as well.
+ sal_Int32 nBorderWidth = mpWindowImpl->mnBorderWidth;
+
+ aSize.AdjustHeight(2 * nBorderWidth );
+ aSize.AdjustWidth(2 * nBorderWidth );
+
+ return aSize;
+}
+
+void DockingWindow::queue_resize(StateChangedType eReason)
+{
+ bool bTriggerLayout = true;
+ if (maLayoutIdle.IsActive() || mbIsCalculatingInitialLayoutSize)
+ {
+ bTriggerLayout = false;
+ }
+ if (!isLayoutEnabled())
+ {
+ bTriggerLayout = false;
+ }
+ if (bTriggerLayout)
+ {
+ InvalidateSizeCache();
+ maLayoutIdle.Start();
+ }
+ vcl::Window::queue_resize(eReason);
+}
+
+IMPL_LINK_NOARG(DockingWindow, ImplHandleLayoutTimerHdl, Timer*, void)
+{
+ if (!isLayoutEnabled())
+ {
+ SAL_WARN_IF(GetWindow(GetWindowType::FirstChild), "vcl.layout", "DockingWindow has become non-layout because extra children have been added directly to it.");
+ return;
+ }
+ setPosSizeOnContainee();
+}
+
+void DockingWindow::SetMinOutputSizePixel( const Size& rSize )
+{
+ if ( mpFloatWin )
+ mpFloatWin->SetMinOutputSizePixel( rSize );
+ maMinOutSize = rSize;
+}
+
+const Size& DockingWindow::GetMinOutputSizePixel() const
+{
+ if ( mpFloatWin )
+ return mpFloatWin->GetMinOutputSizePixel();
+ return maMinOutSize;
+}
+
+void DockingWindow::SetFloatingPos( const Point& rNewPos )
+{
+ if ( mpFloatWin )
+ mpFloatWin->SetPosPixel( rNewPos );
+ else
+ maFloatPos = rNewPos;
+}
+
+SystemWindow* DockingWindow::GetFloatingWindow() const
+{
+ return mpFloatWin;
+}
+
+DropdownDockingWindow::DropdownDockingWindow(vcl::Window* pParent, const css::uno::Reference<css::frame::XFrame>& rFrame, bool bTearable)
+ : DockingWindow(pParent,
+ !bTearable ? OString("InterimDockParent") : OString("InterimTearableParent"),
+ !bTearable ? OUString("vcl/ui/interimdockparent.ui") : OUString("vcl/ui/interimtearableparent.ui"),
+ "vcl::DropdownDockingWindow maLayoutIdle",
+ rFrame)
+ , m_xBox(m_pUIBuilder->get("box"))
+{
+}
+
+DropdownDockingWindow::~DropdownDockingWindow()
+{
+ disposeOnce();
+}
+
+void DropdownDockingWindow::dispose()
+{
+ m_xBox.clear();
+ DockingWindow::dispose();
+}
+
+ResizableDockingWindow::ResizableDockingWindow(vcl::Window* pParent, const css::uno::Reference<css::frame::XFrame>& rFrame)
+ : DockingWindow(pParent, "DockingWindow", "vcl/ui/dockingwindow.ui", "vcl::ResizableDockingWindow maLayoutIdle", rFrame)
+ , m_xBox(m_pUIBuilder->get("box"))
+{
+}
+
+ResizableDockingWindow::ResizableDockingWindow(vcl::Window* pParent, WinBits nStyle)
+ : DockingWindow(pParent, nStyle, "vcl::ResizableDockingWindow maLayoutIdle")
+{
+}
+
+// needed to blow away the cached size of the boundary between vcl and hosted child widget
+void ResizableDockingWindow::InvalidateChildSizeCache()
+{
+ // find the bottom vcl::Window of the hierarchy and queue_resize on that
+ // one will invalidate all the size caches upwards
+ vcl::Window* pChild = GetWindow(GetWindowType::FirstChild);
+ while (true)
+ {
+ vcl::Window* pSubChild = pChild->GetWindow(GetWindowType::FirstChild);
+ if (!pSubChild)
+ break;
+ pChild = pSubChild;
+ }
+ pChild->queue_resize();
+}
+
+ResizableDockingWindow::~ResizableDockingWindow()
+{
+ disposeOnce();
+}
+
+void ResizableDockingWindow::dispose()
+{
+ m_xBox.clear();
+ DockingWindow::dispose();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/errinf.cxx b/vcl/source/window/errinf.cxx
new file mode 100644
index 000000000..71191122b
--- /dev/null
+++ b/vcl/source/window/errinf.cxx
@@ -0,0 +1,329 @@
+/* -*- 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 <osl/diagnose.h>
+#include <sal/log.hxx>
+
+#include <tools/debug.hxx>
+#include <vcl/errinf.hxx>
+
+#include <algorithm>
+#include <vector>
+
+class ErrorHandler;
+
+namespace {
+
+ ErrorRegistry& GetErrorRegistry()
+ {
+ static ErrorRegistry gErrorRegistry;
+ return gErrorRegistry;
+ }
+
+}
+
+bool ErrorStringFactory::CreateString(const ErrorInfo* pInfo, OUString& rStr)
+{
+ for(const ErrorHandler *pHdlr : GetErrorRegistry().errorHandlers)
+ {
+ if(pHdlr->CreateString(pInfo, rStr))
+ return true;
+ }
+ return false;
+}
+
+ErrorRegistry::ErrorRegistry()
+ : pDsp(nullptr)
+ , bIsWindowDsp(false)
+ , m_bLock(false)
+ , nNextError(0)
+{
+ for(DynamicErrorInfo*& rp : ppDynErrInfo)
+ rp = nullptr;
+}
+
+void ErrorRegistry::RegisterDisplay(BasicDisplayErrorFunc *aDsp)
+{
+ ErrorRegistry &rData = GetErrorRegistry();
+ rData.bIsWindowDsp = false;
+ rData.pDsp = reinterpret_cast< DisplayFnPtr >(aDsp);
+}
+
+void ErrorRegistry::RegisterDisplay(WindowDisplayErrorFunc *aDsp)
+{
+ ErrorRegistry &rData = GetErrorRegistry();
+ rData.bIsWindowDsp = true;
+ rData.pDsp = reinterpret_cast< DisplayFnPtr >(aDsp);
+}
+
+void ErrorRegistry::SetLock(bool bLock)
+{
+ ErrorRegistry& rData = GetErrorRegistry();
+ rData.m_bLock = bLock;
+}
+
+bool ErrorRegistry::GetLock()
+{
+ ErrorRegistry& rData = GetErrorRegistry();
+ return rData.m_bLock;
+}
+
+void ErrorRegistry::Reset()
+{
+ ErrorRegistry &rData = GetErrorRegistry();
+ rData = ErrorRegistry();
+}
+
+static void aDspFunc(const OUString &rErr, const OUString &rAction)
+{
+ SAL_WARN("vcl", "Action: " << rAction << " Error: " << rErr);
+}
+
+ErrorHandler::ErrorHandler()
+{
+ ErrorRegistry &rData = GetErrorRegistry();
+ rData.errorHandlers.insert(rData.errorHandlers.begin(), this);
+
+ if(!rData.pDsp)
+ ErrorRegistry::RegisterDisplay(&aDspFunc);
+}
+
+ErrorHandler::~ErrorHandler()
+{
+ auto &rErrorHandlers = GetErrorRegistry().errorHandlers;
+ rErrorHandlers.erase( ::std::remove(rErrorHandlers.begin(), rErrorHandlers.end(), this),
+ rErrorHandlers.end());
+}
+
+bool ErrorHandler::GetErrorString(ErrCode nErrCodeId, OUString& rErrStr)
+{
+ OUString aErr;
+
+ if(!nErrCodeId || nErrCodeId == ERRCODE_ABORT)
+ return false;
+
+ std::unique_ptr<ErrorInfo> pInfo = ErrorInfo::GetErrorInfo(nErrCodeId);
+
+ if (ErrorStringFactory::CreateString(pInfo.get(),aErr))
+ {
+ rErrStr = aErr;
+ return true;
+ }
+
+ return false;
+}
+
+DialogMask ErrorHandler::HandleError(ErrCode nErrCodeId, weld::Window *pParent, DialogMask nFlags)
+{
+ if (nErrCodeId == ERRCODE_NONE || nErrCodeId == ERRCODE_ABORT)
+ return DialogMask::NONE;
+
+ ErrorRegistry &rData = GetErrorRegistry();
+ std::unique_ptr<ErrorInfo> pInfo = ErrorInfo::GetErrorInfo(nErrCodeId);
+ OUString aAction;
+
+ if (!rData.contexts.empty())
+ {
+ rData.contexts.front()->GetString(pInfo->GetErrorCode(), aAction);
+
+ for(ErrorContext *pCtx : rData.contexts)
+ {
+ if(pCtx->GetParent())
+ {
+ pParent = pCtx->GetParent();
+ break;
+ }
+ }
+ }
+
+ bool bWarning = nErrCodeId.IsWarning();
+ DialogMask nErrFlags = DialogMask::ButtonDefaultsOk | DialogMask::ButtonsOk;
+ if (bWarning)
+ nErrFlags |= DialogMask::MessageWarning;
+ else
+ nErrFlags |= DialogMask::MessageError;
+
+ DynamicErrorInfo* pDynPtr = dynamic_cast<DynamicErrorInfo*>(pInfo.get());
+ if(pDynPtr)
+ {
+ DialogMask nDynFlags = pDynPtr->GetDialogMask();
+ if( nDynFlags != DialogMask::NONE )
+ nErrFlags = nDynFlags;
+ }
+
+ OUString aErr;
+ if (ErrorStringFactory::CreateString(pInfo.get(), aErr))
+ {
+ if (!rData.pDsp || rData.m_bLock)
+ {
+ SAL_WARN( "vcl", "Action: " << aAction << "Error: " << aErr);
+ }
+ else
+ {
+ if(!rData.bIsWindowDsp)
+ {
+ (*reinterpret_cast<BasicDisplayErrorFunc*>(rData.pDsp))(aErr,aAction);
+ return DialogMask::NONE;
+ }
+ else
+ {
+ if (nFlags != DialogMask::MAX)
+ nErrFlags = nFlags;
+
+ return (*reinterpret_cast<WindowDisplayErrorFunc*>(rData.pDsp))(
+ pParent, nErrFlags, aErr, aAction);
+ }
+ }
+ }
+
+ SAL_WARN( "vcl", "Error not handled " << pInfo->GetErrorCode());
+ // Error 1 (ERRCODE_ABORT) is classified as a General Error in sfx
+ if (pInfo->GetErrorCode() != ERRCODE_ABORT)
+ HandleError(ERRCODE_ABORT);
+ else
+ OSL_FAIL("ERRCODE_ABORT not handled");
+
+ return DialogMask::NONE;
+}
+
+struct ImplErrorContext
+{
+ weld::Window *pWin;
+};
+
+ErrorContext::ErrorContext(weld::Window *pWinP)
+ : pImpl( new ImplErrorContext )
+{
+ pImpl->pWin = pWinP;
+ GetErrorRegistry().contexts.insert(GetErrorRegistry().contexts.begin(), this);
+}
+
+ErrorContext::~ErrorContext()
+{
+ auto &rContexts = GetErrorRegistry().contexts;
+ rContexts.erase( ::std::remove(rContexts.begin(), rContexts.end(), this), rContexts.end());
+}
+
+ErrorContext *ErrorContext::GetContext()
+{
+ return GetErrorRegistry().contexts.empty() ? nullptr : GetErrorRegistry().contexts.front();
+}
+
+weld::Window* ErrorContext::GetParent()
+{
+ return pImpl ? pImpl->pWin : nullptr;
+}
+
+class ImplDynamicErrorInfo
+{
+ friend class DynamicErrorInfo;
+ friend class ErrorInfo;
+
+private:
+ explicit ImplDynamicErrorInfo(DialogMask nInMask)
+ : nMask(nInMask)
+ {
+ }
+ void RegisterError(DynamicErrorInfo *);
+ static void UnRegisterError(DynamicErrorInfo const *);
+ static std::unique_ptr<ErrorInfo> GetDynamicErrorInfo(ErrCode nId);
+
+ ErrCode nErrId;
+ DialogMask nMask;
+
+};
+
+void ImplDynamicErrorInfo::RegisterError(DynamicErrorInfo *pDynErrInfo)
+{
+ // Register dynamic identifier
+ ErrorRegistry& rData = GetErrorRegistry();
+ nErrId = ErrCode(((sal_uInt32(rData.nNextError) + 1) << ERRCODE_DYNAMIC_SHIFT) +
+ sal_uInt32(pDynErrInfo->GetErrorCode()));
+
+ if(rData.ppDynErrInfo[rData.nNextError])
+ delete rData.ppDynErrInfo[rData.nNextError];
+
+ rData.ppDynErrInfo[rData.nNextError] = pDynErrInfo;
+
+ if(++rData.nNextError>=ERRCODE_DYNAMIC_COUNT)
+ rData.nNextError=0;
+}
+
+void ImplDynamicErrorInfo::UnRegisterError(DynamicErrorInfo const *pDynErrInfo)
+{
+ DynamicErrorInfo **ppDynErrInfo = GetErrorRegistry().ppDynErrInfo;
+ sal_uInt32 nIdx = ErrCode(*pDynErrInfo).GetDynamic() - 1;
+ DBG_ASSERT(ppDynErrInfo[nIdx] == pDynErrInfo, "ErrHdl: Error not found");
+
+ if(ppDynErrInfo[nIdx]==pDynErrInfo)
+ ppDynErrInfo[nIdx]=nullptr;
+}
+
+std::unique_ptr<ErrorInfo> ImplDynamicErrorInfo::GetDynamicErrorInfo(ErrCode nId)
+{
+ sal_uInt32 nIdx = nId.GetDynamic() - 1;
+ DynamicErrorInfo* pDynErrInfo = GetErrorRegistry().ppDynErrInfo[nIdx];
+
+ if(pDynErrInfo && ErrCode(*pDynErrInfo)==nId)
+ return std::unique_ptr<ErrorInfo>(pDynErrInfo);
+ else
+ return std::make_unique<ErrorInfo>(nId.StripDynamic());
+}
+
+std::unique_ptr<ErrorInfo> ErrorInfo::GetErrorInfo(ErrCode nId)
+{
+ if(nId.IsDynamic())
+ return ImplDynamicErrorInfo::GetDynamicErrorInfo(nId);
+ else
+ return std::make_unique<ErrorInfo>(nId);
+}
+
+ErrorInfo::~ErrorInfo()
+{
+}
+
+DynamicErrorInfo::DynamicErrorInfo(ErrCode nArgUserId, DialogMask nMask)
+: ErrorInfo(nArgUserId),
+ pImpl(new ImplDynamicErrorInfo(nMask))
+{
+ pImpl->RegisterError(this);
+}
+
+DynamicErrorInfo::~DynamicErrorInfo()
+{
+ ImplDynamicErrorInfo::UnRegisterError(this);
+}
+
+DynamicErrorInfo::operator ErrCode() const
+{
+ return pImpl->nErrId;
+}
+
+DialogMask DynamicErrorInfo::GetDialogMask() const
+{
+ return pImpl->nMask;
+}
+
+StringErrorInfo::StringErrorInfo(
+ ErrCode nArgUserId, const OUString& aStringP, DialogMask nMask)
+: DynamicErrorInfo(nArgUserId, nMask), aString(aStringP)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/event.cxx b/vcl/source/window/event.cxx
new file mode 100644
index 000000000..e11d032b9
--- /dev/null
+++ b/vcl/source/window/event.cxx
@@ -0,0 +1,673 @@
+/* -*- 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 <vcl/event.hxx>
+#include <vcl/window.hxx>
+#include <vcl/dockwin.hxx>
+#include <vcl/layout.hxx>
+#include <sal/log.hxx>
+
+#include <window.h>
+#include <svdata.hxx>
+#include <salframe.hxx>
+#include <config_features.h>
+#include <comphelper/scopeguard.hxx>
+
+#include "impldockingwrapper.hxx"
+
+namespace vcl {
+
+void Window::DataChanged( const DataChangedEvent& )
+{
+}
+
+void Window::NotifyAllChildren( DataChangedEvent& rDCEvt )
+{
+ CompatDataChanged( rDCEvt );
+
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->NotifyAllChildren( rDCEvt );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+}
+
+bool Window::PreNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+ if ( mpWindowImpl->mpParent && !ImplIsOverlapWindow() )
+ bDone = mpWindowImpl->mpParent->CompatPreNotify( rNEvt );
+
+ if ( !bDone )
+ {
+ if( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ {
+ bool bCompoundFocusChanged = false;
+ if ( mpWindowImpl->mbCompoundControl && !mpWindowImpl->mbCompoundControlHasFocus && HasChildPathFocus() )
+ {
+ mpWindowImpl->mbCompoundControlHasFocus = true;
+ bCompoundFocusChanged = true;
+ }
+
+ if ( bCompoundFocusChanged || ( rNEvt.GetWindow() == this ) )
+ CallEventListeners( VclEventId::WindowGetFocus );
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ bool bCompoundFocusChanged = false;
+ if ( mpWindowImpl->mbCompoundControl && mpWindowImpl->mbCompoundControlHasFocus && !HasChildPathFocus() )
+ {
+ mpWindowImpl->mbCompoundControlHasFocus = false ;
+ bCompoundFocusChanged = true;
+ }
+
+ if ( bCompoundFocusChanged || ( rNEvt.GetWindow() == this ) )
+ CallEventListeners( VclEventId::WindowLoseFocus );
+ }
+
+ // #82968# mouse and key events will be notified after processing ( in ImplNotifyKeyMouseCommandEventListeners() )!
+ // see also ImplHandleMouseEvent(), ImplHandleKey()
+
+ }
+
+ return bDone;
+}
+
+namespace
+{
+ bool parentNotDialogControl(Window* pWindow)
+ {
+ vcl::Window* pParent = getNonLayoutParent(pWindow);
+ if (!pParent)
+ return true;
+ return ((pParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) != WB_DIALOGCONTROL);
+ }
+}
+
+bool Window::EventNotify( NotifyEvent& rNEvt )
+{
+ bool bRet = false;
+
+ if (isDisposed())
+ return false;
+
+ // check for docking window
+ // but do nothing if window is docked and locked
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if ((GetStyle() & WB_DOCKABLE) &&
+ pWrapper && ( pWrapper->IsFloatingMode() || !pWrapper->IsLocked() ))
+ {
+ const bool bDockingSupportCrippled = !StyleSettings::GetDockingFloatsSupported();
+
+ if ( rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ const MouseEvent* pMEvt = rNEvt.GetMouseEvent();
+ bool bHit = pWrapper->GetDragArea().Contains( pMEvt->GetPosPixel() );
+ if ( pMEvt->IsLeft() )
+ {
+ if (!bDockingSupportCrippled && pMEvt->IsMod1() && (pMEvt->GetClicks() == 2))
+ {
+ // ctrl double click toggles floating mode
+ pWrapper->SetFloatingMode( !pWrapper->IsFloatingMode() );
+ return true;
+ }
+ else if ( pMEvt->GetClicks() == 1 && bHit)
+ {
+ // allow start docking during mouse move
+ pWrapper->ImplEnableStartDocking();
+ return true;
+ }
+ }
+ }
+ else if ( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMEvt = rNEvt.GetMouseEvent();
+ bool bHit = pWrapper->GetDragArea().Contains( pMEvt->GetPosPixel() );
+ if ( pMEvt->IsLeft() )
+ {
+ // check if a single click initiated this sequence ( ImplStartDockingEnabled() )
+ // check if window is docked and
+ if( pWrapper->ImplStartDockingEnabled() && !pWrapper->IsFloatingMode() &&
+ !pWrapper->IsDocking() && bHit )
+ {
+ Point aPos = pMEvt->GetPosPixel();
+ vcl::Window* pWindow = rNEvt.GetWindow();
+ if ( pWindow != this )
+ {
+ aPos = pWindow->OutputToScreenPixel( aPos );
+ aPos = ScreenToOutputPixel( aPos );
+ }
+ pWrapper->ImplStartDocking( aPos );
+ }
+ return true;
+ }
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ const vcl::KeyCode& rKey = rNEvt.GetKeyEvent()->GetKeyCode();
+ if (rKey.GetCode() == KEY_F10 && rKey.GetModifier() &&
+ rKey.IsShift() && rKey.IsMod1() && !bDockingSupportCrippled)
+ {
+ pWrapper->SetFloatingMode( !pWrapper->IsFloatingMode() );
+ /* At this point the floating toolbar frame does not have the
+ * input focus since these frames don't get the focus per default
+ * To enable keyboard handling of this toolbar set the input focus
+ * to the frame. This needs to be done with ToTop since GrabFocus
+ * would not notice any change since "this" already has the focus.
+ */
+ if( pWrapper->IsFloatingMode() )
+ ToTop( ToTopFlags::GrabFocusOnly );
+ return true;
+ }
+ }
+ }
+
+ // manage the dialogs
+ if ( (GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL )
+ {
+ // if the parent also has dialog control activated, the parent takes over control
+ if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) || (rNEvt.GetType() == MouseNotifyEvent::KEYUP) )
+ {
+ // ScGridWindow has WB_DIALOGCONTROL set, so pressing tab in ScCheckListMenuControl won't
+ // get processed here by the toplevel DockingWindow of ScCheckListMenuControl by
+ // just checking if parentNotDialogControl is true
+ bool bTopLevelFloatingWindow = (pWrapper && pWrapper->IsFloatingMode());
+ if (ImplIsOverlapWindow() || parentNotDialogControl(this) || bTopLevelFloatingWindow)
+ {
+ bRet = ImplDlgCtrl( *rNEvt.GetKeyEvent(), rNEvt.GetType() == MouseNotifyEvent::KEYINPUT );
+ }
+ }
+ else if ( (rNEvt.GetType() == MouseNotifyEvent::GETFOCUS) || (rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS) )
+ {
+ ImplDlgCtrlFocusChanged( rNEvt.GetWindow(), rNEvt.GetType() == MouseNotifyEvent::GETFOCUS );
+ if ( (rNEvt.GetWindow() == this) && (rNEvt.GetType() == MouseNotifyEvent::GETFOCUS) &&
+ !(GetStyle() & WB_TABSTOP) && !(mpWindowImpl->mnDlgCtrlFlags & DialogControlFlags::WantFocus) )
+ {
+ vcl::Window* pFirstChild = ImplGetDlgWindow( 0, GetDlgWindowType::First );
+ if ( pFirstChild )
+ pFirstChild->ImplControlFocus();
+ }
+ }
+ }
+
+ if ( !bRet )
+ {
+ if ( mpWindowImpl->mpParent && !ImplIsOverlapWindow() )
+ bRet = mpWindowImpl->mpParent->CompatNotify( rNEvt );
+ }
+
+ return bRet;
+}
+
+void Window::CallEventListeners( VclEventId nEvent, void* pData )
+{
+ VclWindowEvent aEvent( this, nEvent, pData );
+
+ VclPtr<vcl::Window> xWindow = this;
+
+ Application::ImplCallEventListeners( aEvent );
+
+ // if we have ObjectDying, then the bIsDisposed flag has already been set,
+ // but we still need to let listeners know.
+ const bool bIgnoreDisposed = nEvent == VclEventId::ObjectDying;
+
+ if ( !bIgnoreDisposed && xWindow->isDisposed() )
+ return;
+
+ // If maEventListeners is empty, the XVCLWindow has not yet been initialized.
+ // Calling GetComponentInterface will do that.
+ if (mpWindowImpl->maEventListeners.empty() && pData)
+ xWindow->GetComponentInterface();
+
+ if (!mpWindowImpl->maEventListeners.empty())
+ {
+ // Copy the list, because this can be destroyed when calling a Link...
+ std::vector<Link<VclWindowEvent&,void>> aCopy( mpWindowImpl->maEventListeners );
+ // we use an iterating counter/flag and a set of deleted Link's to avoid O(n^2) behaviour
+ mpWindowImpl->mnEventListenersIteratingCount++;
+ auto& rWindowImpl = *mpWindowImpl;
+ comphelper::ScopeGuard aGuard(
+ [&rWindowImpl, &xWindow, &bIgnoreDisposed]()
+ {
+ if (bIgnoreDisposed || !xWindow->isDisposed())
+ {
+ rWindowImpl.mnEventListenersIteratingCount--;
+ if (rWindowImpl.mnEventListenersIteratingCount == 0)
+ rWindowImpl.maEventListenersDeleted.clear();
+ }
+ }
+ );
+ for ( const Link<VclWindowEvent&,void>& rLink : aCopy )
+ {
+ if (!bIgnoreDisposed && xWindow->isDisposed()) break;
+ // check this hasn't been removed in some re-enterancy scenario fdo#47368
+ if( rWindowImpl.maEventListenersDeleted.find(rLink) == rWindowImpl.maEventListenersDeleted.end() )
+ rLink.Call( aEvent );
+ }
+ }
+
+ while ( xWindow )
+ {
+
+ if ( !bIgnoreDisposed && xWindow->isDisposed() )
+ return;
+
+ auto& rWindowImpl = *xWindow->mpWindowImpl;
+ if (!rWindowImpl.maChildEventListeners.empty())
+ {
+ // Copy the list, because this can be destroyed when calling a Link...
+ std::vector<Link<VclWindowEvent&,void>> aCopy( rWindowImpl.maChildEventListeners );
+ // we use an iterating counter/flag and a set of deleted Link's to avoid O(n^2) behaviour
+ rWindowImpl.mnChildEventListenersIteratingCount++;
+ comphelper::ScopeGuard aGuard(
+ [&rWindowImpl, &xWindow, &bIgnoreDisposed]()
+ {
+ if (bIgnoreDisposed || !xWindow->isDisposed())
+ {
+ rWindowImpl.mnChildEventListenersIteratingCount--;
+ if (rWindowImpl.mnChildEventListenersIteratingCount == 0)
+ rWindowImpl.maChildEventListenersDeleted.clear();
+ }
+ }
+ );
+ for ( const Link<VclWindowEvent&,void>& rLink : aCopy )
+ {
+ if (!bIgnoreDisposed && xWindow->isDisposed())
+ return;
+ // Check this hasn't been removed in some re-enterancy scenario fdo#47368.
+ if( rWindowImpl.maChildEventListenersDeleted.find(rLink) == rWindowImpl.maChildEventListenersDeleted.end() )
+ rLink.Call( aEvent );
+ }
+ }
+
+ if ( !bIgnoreDisposed && xWindow->isDisposed() )
+ return;
+
+ xWindow = xWindow->GetParent();
+ }
+}
+
+void Window::AddEventListener( const Link<VclWindowEvent&,void>& rEventListener )
+{
+ mpWindowImpl->maEventListeners.push_back( rEventListener );
+}
+
+void Window::RemoveEventListener( const Link<VclWindowEvent&,void>& rEventListener )
+{
+ if (mpWindowImpl)
+ {
+ auto& rListeners = mpWindowImpl->maEventListeners;
+ rListeners.erase( std::remove(rListeners.begin(), rListeners.end(), rEventListener ), rListeners.end() );
+ if (mpWindowImpl->mnEventListenersIteratingCount)
+ mpWindowImpl->maEventListenersDeleted.insert(rEventListener);
+ }
+}
+
+void Window::AddChildEventListener( const Link<VclWindowEvent&,void>& rEventListener )
+{
+ mpWindowImpl->maChildEventListeners.push_back( rEventListener );
+}
+
+void Window::RemoveChildEventListener( const Link<VclWindowEvent&,void>& rEventListener )
+{
+ if (mpWindowImpl)
+ {
+ auto& rListeners = mpWindowImpl->maChildEventListeners;
+ rListeners.erase( std::remove(rListeners.begin(), rListeners.end(), rEventListener ), rListeners.end() );
+ if (mpWindowImpl->mnChildEventListenersIteratingCount)
+ mpWindowImpl->maChildEventListenersDeleted.insert(rEventListener);
+ }
+}
+
+ImplSVEvent * Window::PostUserEvent( const Link<void*,void>& rLink, void* pCaller, bool bReferenceLink )
+{
+ std::unique_ptr<ImplSVEvent> pSVEvent(new ImplSVEvent);
+ pSVEvent->mpData = pCaller;
+ pSVEvent->maLink = rLink;
+ pSVEvent->mpWindow = this;
+ pSVEvent->mbCall = true;
+ if (bReferenceLink)
+ {
+ pSVEvent->mpInstanceRef = static_cast<vcl::Window *>(rLink.GetInstance());
+ }
+
+ auto pTmpEvent = pSVEvent.get();
+ if (!mpWindowImpl->mpFrame->PostEvent( std::move(pSVEvent) ))
+ return nullptr;
+ return pTmpEvent;
+}
+
+void Window::RemoveUserEvent( ImplSVEvent * nUserEvent )
+{
+ SAL_WARN_IF( nUserEvent->mpWindow.get() != this, "vcl",
+ "Window::RemoveUserEvent(): Event doesn't send to this window or is already removed" );
+ SAL_WARN_IF( !nUserEvent->mbCall, "vcl",
+ "Window::RemoveUserEvent(): Event is already removed" );
+
+ if ( nUserEvent->mpWindow )
+ {
+ nUserEvent->mpWindow = nullptr;
+ }
+
+ nUserEvent->mbCall = false;
+}
+
+
+static MouseEvent ImplTranslateMouseEvent( const MouseEvent& rE, vcl::Window const * pSource, vcl::Window const * pDest )
+{
+ // the mouse event occurred in a different window, we need to translate the coordinates of
+ // the mouse cursor within that (source) window to the coordinates the mouse cursor would
+ // be in the destination window
+ Point aPos = pSource->OutputToScreenPixel( rE.GetPosPixel() );
+ return MouseEvent( pDest->ScreenToOutputPixel( aPos ), rE.GetClicks(), rE.GetMode(), rE.GetButtons(), rE.GetModifier() );
+}
+
+void Window::ImplNotifyKeyMouseCommandEventListeners( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::COMMAND )
+ {
+ const CommandEvent* pCEvt = rNEvt.GetCommandEvent();
+ if ( pCEvt->GetCommand() != CommandEventId::ContextMenu )
+ // non context menu events are not to be notified up the chain
+ // so we return immediately
+ return;
+
+ if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) )
+ {
+ // not interested: The event listeners are already called in ::Command,
+ // and calling them here a second time doesn't make sense
+ if ( rNEvt.GetWindow() != this )
+ {
+ CommandEvent aCommandEvent;
+
+ if ( !pCEvt->IsMouseEvent() )
+ {
+ aCommandEvent = *pCEvt;
+ }
+ else
+ {
+ // the mouse event occurred in a different window, we need to translate the coordinates of
+ // the mouse cursor within that window to the coordinates the mouse cursor would be in the
+ // current window
+ vcl::Window* pSource = rNEvt.GetWindow();
+ Point aPos = pSource->OutputToScreenPixel( pCEvt->GetMousePosPixel() );
+ aCommandEvent = CommandEvent( ScreenToOutputPixel( aPos ), pCEvt->GetCommand(), pCEvt->IsMouseEvent(), pCEvt->GetEventData() );
+ }
+
+ CallEventListeners( VclEventId::WindowCommand, &aCommandEvent );
+ }
+ }
+ }
+
+ // #82968# notify event listeners for mouse and key events separately and
+ // not in PreNotify ( as for focus listeners )
+ // this allows for processing those events internally first and pass it to
+ // the toolkit later
+
+ VclPtr<vcl::Window> xWindow = this;
+
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) )
+ {
+ if ( rNEvt.GetWindow() == this )
+ CallEventListeners( VclEventId::WindowMouseMove, const_cast<MouseEvent *>(rNEvt.GetMouseEvent()) );
+ else
+ {
+ MouseEvent aMouseEvent = ImplTranslateMouseEvent( *rNEvt.GetMouseEvent(), rNEvt.GetWindow(), this );
+ CallEventListeners( VclEventId::WindowMouseMove, &aMouseEvent );
+ }
+ }
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONUP )
+ {
+ if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) )
+ {
+ if ( rNEvt.GetWindow() == this )
+ CallEventListeners( VclEventId::WindowMouseButtonUp, const_cast<MouseEvent *>(rNEvt.GetMouseEvent()) );
+ else
+ {
+ MouseEvent aMouseEvent = ImplTranslateMouseEvent( *rNEvt.GetMouseEvent(), rNEvt.GetWindow(), this );
+ CallEventListeners( VclEventId::WindowMouseButtonUp, &aMouseEvent );
+ }
+ }
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) )
+ {
+ if ( rNEvt.GetWindow() == this )
+ CallEventListeners( VclEventId::WindowMouseButtonDown, const_cast<MouseEvent *>(rNEvt.GetMouseEvent()) );
+ else
+ {
+ MouseEvent aMouseEvent = ImplTranslateMouseEvent( *rNEvt.GetMouseEvent(), rNEvt.GetWindow(), this );
+ CallEventListeners( VclEventId::WindowMouseButtonDown, &aMouseEvent );
+ }
+ }
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) )
+ CallEventListeners( VclEventId::WindowKeyInput, const_cast<KeyEvent *>(rNEvt.GetKeyEvent()) );
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::KEYUP )
+ {
+ if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) )
+ CallEventListeners( VclEventId::WindowKeyUp, const_cast<KeyEvent *>(rNEvt.GetKeyEvent()) );
+ }
+
+ if ( xWindow->isDisposed() )
+ return;
+
+ // #106721# check if we're part of a compound control and notify
+ vcl::Window *pParent = ImplGetParent();
+ while( pParent )
+ {
+ if( pParent->IsCompoundControl() )
+ {
+ pParent->ImplNotifyKeyMouseCommandEventListeners( rNEvt );
+ break;
+ }
+ pParent = pParent->ImplGetParent();
+ }
+}
+
+void Window::ImplCallInitShow()
+{
+ mpWindowImpl->mbReallyShown = true;
+ mpWindowImpl->mbInInitShow = true;
+ CompatStateChanged( StateChangedType::InitShow );
+ mpWindowImpl->mbInInitShow = false;
+
+ vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbVisible )
+ pWindow->ImplCallInitShow();
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+
+ pWindow = mpWindowImpl->mpFirstChild;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbVisible )
+ pWindow->ImplCallInitShow();
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+}
+
+
+void Window::ImplCallResize()
+{
+ mpWindowImpl->mbCallResize = false;
+
+ // Normally we avoid blanking on re-size unless people might notice:
+ if( GetBackground().IsGradient() )
+ Invalidate();
+
+ Resize();
+
+ // #88419# Most classes don't call the base class in Resize() and Move(),
+ // => Call ImpleResize/Move instead of Resize/Move directly...
+ CallEventListeners( VclEventId::WindowResize );
+}
+
+void Window::ImplCallMove()
+{
+ mpWindowImpl->mbCallMove = false;
+
+ if( mpWindowImpl->mbFrame )
+ {
+ // update frame position
+ SalFrame *pParentFrame = nullptr;
+ vcl::Window *pParent = ImplGetParent();
+ while( pParent )
+ {
+ if( pParent->mpWindowImpl &&
+ pParent->mpWindowImpl->mpFrame != mpWindowImpl->mpFrame )
+ {
+ pParentFrame = pParent->mpWindowImpl->mpFrame;
+ break;
+ }
+ pParent = pParent->GetParent();
+ }
+
+ SalFrameGeometry g = mpWindowImpl->mpFrame->GetGeometry();
+ mpWindowImpl->maPos = Point( g.nX, g.nY );
+ if( pParentFrame )
+ {
+ g = pParentFrame->GetGeometry();
+ mpWindowImpl->maPos -= Point( g.nX, g.nY );
+ }
+ // the client window and all its subclients have the same position as the borderframe
+ // this is important for floating toolbars where the borderwindow is a floating window
+ // which has another borderwindow (ie the system floating window)
+ vcl::Window *pClientWin = mpWindowImpl->mpClientWindow;
+ while( pClientWin )
+ {
+ pClientWin->mpWindowImpl->maPos = mpWindowImpl->maPos;
+ pClientWin = pClientWin->mpWindowImpl->mpClientWindow;
+ }
+ }
+
+ Move();
+
+ CallEventListeners( VclEventId::WindowMove );
+}
+
+void Window::ImplCallFocusChangeActivate( vcl::Window* pNewOverlapWindow,
+ vcl::Window* pOldOverlapWindow )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pNewRealWindow;
+ vcl::Window* pOldRealWindow;
+ bool bCallActivate = true;
+ bool bCallDeactivate = true;
+
+ if (!pOldOverlapWindow)
+ {
+ return;
+ }
+
+ pOldRealWindow = pOldOverlapWindow->ImplGetWindow();
+ if (!pNewOverlapWindow)
+ {
+ return;
+ }
+
+ pNewRealWindow = pNewOverlapWindow->ImplGetWindow();
+ if ( (pOldRealWindow->GetType() != WindowType::FLOATINGWINDOW) ||
+ pOldRealWindow->GetActivateMode() != ActivateModeFlags::NONE )
+ {
+ if ( (pNewRealWindow->GetType() == WindowType::FLOATINGWINDOW) &&
+ pNewRealWindow->GetActivateMode() == ActivateModeFlags::NONE)
+ {
+ pSVData->mpWinData->mpLastDeacWin = pOldOverlapWindow;
+ bCallDeactivate = false;
+ }
+ }
+ else if ( (pNewRealWindow->GetType() != WindowType::FLOATINGWINDOW) ||
+ pNewRealWindow->GetActivateMode() != ActivateModeFlags::NONE )
+ {
+ if (pSVData->mpWinData->mpLastDeacWin)
+ {
+ if (pSVData->mpWinData->mpLastDeacWin.get() == pNewOverlapWindow)
+ bCallActivate = false;
+ else
+ {
+ vcl::Window* pLastRealWindow = pSVData->mpWinData->mpLastDeacWin->ImplGetWindow();
+ pSVData->mpWinData->mpLastDeacWin->mpWindowImpl->mbActive = false;
+ pSVData->mpWinData->mpLastDeacWin->Deactivate();
+ if (pLastRealWindow != pSVData->mpWinData->mpLastDeacWin.get())
+ {
+ pLastRealWindow->mpWindowImpl->mbActive = true;
+ pLastRealWindow->Activate();
+ }
+ }
+ pSVData->mpWinData->mpLastDeacWin = nullptr;
+ }
+ }
+
+ if ( bCallDeactivate )
+ {
+ if( pOldOverlapWindow->mpWindowImpl->mbActive )
+ {
+ pOldOverlapWindow->mpWindowImpl->mbActive = false;
+ pOldOverlapWindow->Deactivate();
+ }
+ if ( pOldRealWindow != pOldOverlapWindow )
+ {
+ if( pOldRealWindow->mpWindowImpl->mbActive )
+ {
+ pOldRealWindow->mpWindowImpl->mbActive = false;
+ pOldRealWindow->Deactivate();
+ }
+ }
+ }
+ if ( !bCallActivate || pNewOverlapWindow->mpWindowImpl->mbActive )
+ return;
+
+ pNewOverlapWindow->mpWindowImpl->mbActive = true;
+ pNewOverlapWindow->Activate();
+
+ if ( pNewRealWindow != pNewOverlapWindow )
+ {
+ if( ! pNewRealWindow->mpWindowImpl->mbActive )
+ {
+ pNewRealWindow->mpWindowImpl->mbActive = true;
+ pNewRealWindow->Activate();
+ }
+ }
+}
+
+} /* namespace vcl */
+
+
+NotifyEvent::NotifyEvent( MouseNotifyEvent nEventType, vcl::Window* pWindow,
+ const void* pEvent )
+{
+ mpWindow = pWindow;
+ mpData = const_cast<void*>(pEvent);
+ mnEventType = nEventType;
+}
+
+NotifyEvent::~NotifyEvent() = default;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/floatwin.cxx b/vcl/source/window/floatwin.cxx
new file mode 100644
index 000000000..094a3ef18
--- /dev/null
+++ b/vcl/source/window/floatwin.cxx
@@ -0,0 +1,973 @@
+/* -*- 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 <svdata.hxx>
+#include <brdwin.hxx>
+#include <window.h>
+#include <salframe.hxx>
+#include <helpwin.hxx>
+
+#include <comphelper/lok.hxx>
+#include <sal/log.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/IDialogRenderable.hxx>
+
+class FloatingWindow::ImplData
+{
+public:
+ ImplData();
+
+ VclPtr<ToolBox> mpBox;
+ tools::Rectangle maItemEdgeClipRect; // used to clip the common edge between a toolbar item and the border of this window
+ Point maPos; // position of the floating window wrt. parent
+ Point maLOKTwipsPos; ///< absolute position of the floating window in the document - in twips (for toplevel floating windows).
+};
+
+FloatingWindow::ImplData::ImplData()
+{
+ mpBox = nullptr;
+}
+
+tools::Rectangle& FloatingWindow::ImplGetItemEdgeClipRect()
+{
+ return mpImplData->maItemEdgeClipRect;
+}
+
+void FloatingWindow::ImplInitFloating( vcl::Window* pParent, WinBits nStyle )
+{
+ mpImplData.reset(new ImplData);
+
+ mpWindowImpl->mbFloatWin = true;
+ mbInCleanUp = false;
+ mbGrabFocus = false;
+
+ SAL_WARN_IF(!pParent, "vcl", "FloatWindow::FloatingWindow(): - pParent == NULL!");
+
+ if (!pParent)
+ pParent = ImplGetSVData()->maFrameData.mpAppWin;
+
+ SAL_WARN_IF(!pParent, "vcl", "FloatWindow::FloatingWindow(): - pParent == NULL and no AppWindow exists");
+
+ // no Border, then we don't need a border window
+ if (!nStyle)
+ {
+ mpWindowImpl->mbOverlapWin = true;
+ nStyle |= WB_DIALOGCONTROL;
+ ImplInit(pParent, nStyle, nullptr);
+ }
+ else
+ {
+ if (!(nStyle & WB_NODIALOGCONTROL))
+ nStyle |= WB_DIALOGCONTROL;
+
+ if (nStyle & (WB_MOVEABLE | WB_SIZEABLE | WB_CLOSEABLE | WB_STANDALONE)
+ && !(nStyle & WB_OWNERDRAWDECORATION))
+ {
+ WinBits nFloatWinStyle = nStyle;
+ // #99154# floaters are not closeable by default anymore, eg fullscreen floater
+ // nFloatWinStyle |= WB_CLOSEABLE;
+ mpWindowImpl->mbFrame = true;
+ mpWindowImpl->mbOverlapWin = true;
+ ImplInit(pParent, nFloatWinStyle & ~WB_BORDER, nullptr);
+ }
+ else
+ {
+ VclPtr<ImplBorderWindow> pBorderWin;
+ BorderWindowStyle nBorderStyle = BorderWindowStyle::Float;
+
+ if (nStyle & WB_OWNERDRAWDECORATION)
+ nBorderStyle |= BorderWindowStyle::Frame;
+ else
+ nBorderStyle |= BorderWindowStyle::Overlap;
+
+ if ((nStyle & WB_SYSTEMWINDOW) && !(nStyle & (WB_MOVEABLE | WB_SIZEABLE)))
+ {
+ nBorderStyle |= BorderWindowStyle::Frame;
+ nStyle |= WB_CLOSEABLE; // make undecorated floaters closeable
+ }
+ pBorderWin = VclPtr<ImplBorderWindow>::Create(pParent, nStyle, nBorderStyle);
+ ImplInit(pBorderWin, nStyle & ~WB_BORDER, nullptr);
+ pBorderWin->mpWindowImpl->mpClientWindow = this;
+ pBorderWin->GetBorder(mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder,
+ mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder);
+ pBorderWin->SetDisplayActive(true);
+ mpWindowImpl->mpBorderWindow = pBorderWin;
+ mpWindowImpl->mpRealParent = pParent;
+ }
+ }
+ SetActivateMode( ActivateModeFlags::NONE );
+
+ mpNextFloat = nullptr;
+ mpFirstPopupModeWin = nullptr;
+ mnPostId = nullptr;
+ mnTitle = (nStyle & (WB_MOVEABLE | WB_POPUP)) ? FloatWinTitleType::Normal : FloatWinTitleType::NONE;
+ mnOldTitle = mnTitle;
+ mnPopupModeFlags = FloatWinPopupFlags::NONE;
+ mbInPopupMode = false;
+ mbPopupMode = false;
+ mbPopupModeCanceled = false;
+ mbPopupModeTearOff = false;
+ mbMouseDown = false;
+
+ ImplInitSettings();
+}
+
+void FloatingWindow::ImplInitSettings()
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+
+ Color aColor;
+ if (IsControlBackground())
+ aColor = GetControlBackground();
+ else if (Window::GetStyle() & WB_3DLOOK)
+ aColor = rStyleSettings.GetFaceColor();
+ else
+ aColor = rStyleSettings.GetWindowColor();
+ SetBackground(aColor);
+}
+
+FloatingWindow::FloatingWindow(vcl::Window* pParent, WinBits nStyle) :
+ SystemWindow(WindowType::FLOATINGWINDOW, "vcl::FloatingWindow maLayoutIdle")
+{
+ ImplInitFloating(pParent, nStyle);
+}
+
+FloatingWindow::FloatingWindow(vcl::Window* pParent, const OString& rID, const OUString& rUIXMLDescription, const css::uno::Reference<css::frame::XFrame> &rFrame)
+ : SystemWindow(WindowType::FLOATINGWINDOW, "vcl::FloatingWindow maLayoutIdle")
+ , mpNextFloat(nullptr)
+ , mpFirstPopupModeWin(nullptr)
+ , mnPostId(nullptr)
+ , mnPopupModeFlags(FloatWinPopupFlags::NONE)
+ , mnTitle(FloatWinTitleType::Unknown)
+ , mnOldTitle(FloatWinTitleType::Unknown)
+ , mbInPopupMode(false)
+ , mbPopupMode(false)
+ , mbPopupModeCanceled(false)
+ , mbPopupModeTearOff(false)
+ , mbMouseDown(false)
+ , mbGrabFocus(false)
+ , mbInCleanUp(false)
+{
+ loadUI(pParent, rID, rUIXMLDescription, rFrame);
+}
+
+//Find the real parent stashed in mpDialogParent.
+void FloatingWindow::doDeferredInit(WinBits nBits)
+{
+ vcl::Window *pParent = mpDialogParent;
+ mpDialogParent = nullptr;
+ ImplInitFloating(pParent, nBits);
+ mbIsDeferredInit = false;
+}
+
+void FloatingWindow::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ Color aColor;
+ if (Window::GetStyle() & WB_3DLOOK)
+ aColor = rStyleSettings.GetFaceColor();
+ else
+ aColor = rStyleSettings.GetWindowColor();
+
+ ApplyControlBackground(rRenderContext, aColor);
+}
+
+FloatingWindow::~FloatingWindow()
+{
+ disposeOnce();
+ assert (!mnPostId);
+}
+
+void FloatingWindow::dispose()
+{
+ ReleaseLOKNotifier();
+
+ if (mpImplData)
+ {
+ if( mbPopupModeCanceled )
+ // indicates that ESC key was pressed
+ // will be handled in Window::ImplGrabFocus()
+ SetDialogControlFlags( GetDialogControlFlags() | DialogControlFlags::FloatWinPopupModeEndCancel );
+
+ if ( IsInPopupMode() )
+ EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll | FloatWinPopupEndFlags::DontCallHdl );
+
+ if ( mnPostId )
+ Application::RemoveUserEvent( mnPostId );
+ mnPostId = nullptr;
+ }
+
+ mpImplData.reset();
+
+ mpNextFloat.clear();
+ mpFirstPopupModeWin.clear();
+ mxPrevFocusWin.clear();
+ SystemWindow::dispose();
+}
+
+Point FloatingWindow::ImplCalcPos(vcl::Window* pWindow,
+ const tools::Rectangle& rRect, FloatWinPopupFlags nFlags,
+ sal_uInt16& rArrangeIndex, Point* pLOKTwipsPos)
+{
+ // get window position
+ Point aPos;
+ Size aSize = ::isLayoutEnabled(pWindow) ? pWindow->get_preferred_size() : pWindow->GetSizePixel();
+ tools::Rectangle aScreenRect = pWindow->ImplGetFrameWindow()->GetDesktopRectPixel();
+ FloatingWindow *pFloatingWindow = dynamic_cast<FloatingWindow*>( pWindow );
+
+ // convert...
+ vcl::Window* pW = pWindow;
+ if ( pW->mpWindowImpl->mpRealParent )
+ pW = pW->mpWindowImpl->mpRealParent;
+
+ tools::Rectangle normRect( rRect ); // rRect is already relative to top-level window
+ normRect.SetPos( pW->ScreenToOutputPixel( normRect.TopLeft() ) );
+
+ bool bRTL = AllSettings::GetLayoutRTL();
+
+ tools::Rectangle devRect( pW->OutputToAbsoluteScreenPixel( normRect.TopLeft() ),
+ pW->OutputToAbsoluteScreenPixel( normRect.BottomRight() ) );
+
+ tools::Rectangle devRectRTL( devRect );
+ if( bRTL )
+ // create a rect that can be compared to desktop coordinates
+ devRectRTL = pW->ImplOutputToUnmirroredAbsoluteScreenPixel( normRect );
+ if( Application::GetScreenCount() > 1 && Application::IsUnifiedDisplay() )
+ aScreenRect = Application::GetScreenPosSizePixel(
+ Application::GetBestScreen( bRTL ? devRectRTL : devRect ) );
+
+ FloatWinPopupFlags nArrangeAry[5];
+ sal_uInt16 nArrangeAttempts = 5;
+ Point e1,e2; // the common edge between the item rect and the floating window
+
+ if ( nFlags & FloatWinPopupFlags::Left )
+ {
+ nArrangeAry[0] = FloatWinPopupFlags::Left;
+ nArrangeAry[1] = FloatWinPopupFlags::Right;
+ nArrangeAry[2] = FloatWinPopupFlags::Up;
+ nArrangeAry[3] = FloatWinPopupFlags::Down;
+ nArrangeAry[4] = FloatWinPopupFlags::Left;
+ }
+ else if ( nFlags & FloatWinPopupFlags::Right )
+ {
+ nArrangeAry[0] = FloatWinPopupFlags::Right;
+ nArrangeAry[1] = FloatWinPopupFlags::Left;
+ nArrangeAry[2] = FloatWinPopupFlags::Up;
+ nArrangeAry[3] = FloatWinPopupFlags::Down;
+ nArrangeAry[4] = FloatWinPopupFlags::Right;
+ }
+ else if ( nFlags & FloatWinPopupFlags::Up )
+ {
+ nArrangeAry[0] = FloatWinPopupFlags::Up;
+ nArrangeAry[1] = FloatWinPopupFlags::Down;
+ if (nFlags & FloatWinPopupFlags::NoHorzPlacement)
+ {
+ nArrangeAry[2] = FloatWinPopupFlags::Up;
+ nArrangeAttempts = 3;
+ }
+ else
+ {
+ nArrangeAry[2] = FloatWinPopupFlags::Right;
+ nArrangeAry[3] = FloatWinPopupFlags::Left;
+ nArrangeAry[4] = FloatWinPopupFlags::Up;
+ }
+ }
+ else
+ {
+ nArrangeAry[0] = FloatWinPopupFlags::Down;
+ nArrangeAry[1] = FloatWinPopupFlags::Up;
+ if (nFlags & FloatWinPopupFlags::NoHorzPlacement)
+ {
+ nArrangeAry[2] = FloatWinPopupFlags::Down;
+ nArrangeAttempts = 3;
+ }
+ else
+ {
+ nArrangeAry[2] = FloatWinPopupFlags::Right;
+ nArrangeAry[3] = FloatWinPopupFlags::Left;
+ nArrangeAry[4] = FloatWinPopupFlags::Down;
+ }
+ }
+
+ sal_uInt16 nArrangeIndex = 0;
+ const bool bLOKActive = comphelper::LibreOfficeKit::isActive();
+
+ for ( ; nArrangeIndex < nArrangeAttempts; nArrangeIndex++ )
+ {
+ bool bBreak = true;
+ switch ( nArrangeAry[nArrangeIndex] )
+ {
+
+ case FloatWinPopupFlags::Left:
+ aPos.setX( devRect.Left()-aSize.Width()+1 );
+ aPos.setY( devRect.Top() );
+ aPos.AdjustY( -(pWindow->mpWindowImpl->mnTopBorder) );
+ if( bRTL )
+ {
+ if( (devRectRTL.Right()+aSize.Width()) > aScreenRect.Right() )
+ bBreak = false;
+ }
+ else
+ {
+ if ( aPos.X() < aScreenRect.Left() )
+ bBreak = false;
+ }
+ if (bBreak || bLOKActive)
+ {
+ e1 = devRect.TopLeft();
+ e2 = devRect.BottomLeft();
+ // set non-zero width
+ e2.AdjustX( 1 );
+ // don't clip corners
+ e1.AdjustY( 1 );
+ e2.AdjustY( -1 );
+ }
+ break;
+ case FloatWinPopupFlags::Right:
+ aPos = devRect.TopRight();
+ aPos.AdjustY( -(pWindow->mpWindowImpl->mnTopBorder) );
+ if( bRTL )
+ {
+ if( (devRectRTL.Left() - aSize.Width()) < aScreenRect.Left() )
+ bBreak = false;
+ }
+ else
+ {
+ if ( aPos.X()+aSize.Width() > aScreenRect.Right() )
+ bBreak = false;
+ }
+ if (bBreak || bLOKActive)
+ {
+ e1 = devRect.TopRight();
+ e2 = devRect.BottomRight();
+ // set non-zero width
+ e2.AdjustX( 1 );
+ // don't clip corners
+ e1.AdjustY( 1 );
+ e2.AdjustY( -1 );
+ }
+ break;
+ case FloatWinPopupFlags::Up:
+ aPos.setX( devRect.Left() );
+ aPos.setY( devRect.Top()-aSize.Height()+1 );
+ if ( aPos.Y() < aScreenRect.Top() )
+ bBreak = false;
+ if (bBreak || bLOKActive)
+ {
+ e1 = devRect.TopLeft();
+ e2 = devRect.TopRight();
+ // set non-zero height
+ e2.AdjustY( 1 );
+ // don't clip corners
+ e1.AdjustX( 1 );
+ e2.AdjustX( -1 );
+ }
+ break;
+ case FloatWinPopupFlags::Down:
+ aPos = devRect.BottomLeft();
+ if ( aPos.Y()+aSize.Height() > aScreenRect.Bottom() )
+ bBreak = false;
+ if (bBreak || bLOKActive)
+ {
+ e1 = devRect.BottomLeft();
+ e2 = devRect.BottomRight();
+ // set non-zero height
+ e2.AdjustY( 1 );
+ // don't clip corners
+ e1.AdjustX( 1 );
+ e2.AdjustX( -1 );
+ }
+ break;
+ default: break;
+ }
+
+ // no further adjustment for LibreOfficeKit
+ if (bLOKActive)
+ break;
+
+ // adjust if necessary
+ if (bBreak)
+ {
+ if ( (nArrangeAry[nArrangeIndex] == FloatWinPopupFlags::Left) ||
+ (nArrangeAry[nArrangeIndex] == FloatWinPopupFlags::Right) )
+ {
+ if ( aPos.Y()+aSize.Height() > aScreenRect.Bottom() )
+ {
+ aPos.setY( devRect.Bottom()-aSize.Height()+1 );
+ if ( aPos.Y() < aScreenRect.Top() )
+ aPos.setY( aScreenRect.Top() );
+ }
+ }
+ else
+ {
+ if( bRTL )
+ {
+ if( devRectRTL.Right()-aSize.Width()+1 < aScreenRect.Left() )
+ aPos.AdjustX( -(aScreenRect.Left() - devRectRTL.Right() + aSize.Width() - 1) );
+ }
+ else if ( aPos.X()+aSize.Width() > aScreenRect.Right() )
+ {
+ aPos.setX( devRect.Right()-aSize.Width()+1 );
+ if ( aPos.X() < aScreenRect.Left() )
+ aPos.setX( aScreenRect.Left() );
+ }
+ }
+ }
+
+ if ( bBreak )
+ break;
+ }
+ if (nArrangeIndex >= nArrangeAttempts)
+ nArrangeIndex = nArrangeAttempts - 1;
+
+ rArrangeIndex = nArrangeIndex;
+
+ aPos = pW->AbsoluteScreenToOutputPixel( aPos );
+
+ // store a cliprect that can be used to clip the common edge of the itemrect and the floating window
+ if( pFloatingWindow && pFloatingWindow->mpImplData->mpBox )
+ {
+ pFloatingWindow->mpImplData->maItemEdgeClipRect =
+ tools::Rectangle( e1, e2 );
+ }
+
+ if (bLOKActive && pLOKTwipsPos)
+ {
+ if (pW->IsMapModeEnabled() || pW->GetMapMode().GetMapUnit() == MapUnit::MapPixel)
+ {
+ // if we use pW->LogicToLogic(aPos, pW->GetMapMode(), MapMode(MapUnit::MapTwip)),
+ // for pixel conversions when map mode is not enabled, we get
+ // a 20 twips per pixel conversion since LogicToLogic uses
+ // a fixed 72 dpi value, instead of a correctly computed output
+ // device dpi or at least the most commonly used 96 dpi value;
+ // and anyway the following is what we already do in
+ // ScGridWindow::LogicInvalidate when map mode is not enabled.
+
+ *pLOKTwipsPos = pW->PixelToLogic(aPos, MapMode(MapUnit::MapTwip));
+ }
+ else
+ {
+ *pLOKTwipsPos = OutputDevice::LogicToLogic(aPos, pW->GetMapMode(), MapMode(MapUnit::MapTwip));
+ }
+ }
+
+ // caller expects coordinates relative to top-level win
+ return pW->OutputToScreenPixel( aPos );
+}
+
+Point FloatingWindow::ImplConvertToAbsPos(vcl::Window* pReference, const Point& rPos)
+{
+ Point aAbsolute( rPos );
+
+ const OutputDevice *pWindowOutDev = pReference->GetOutDev();
+
+ // compare coordinates in absolute screen coordinates
+ if( pWindowOutDev->HasMirroredGraphics() )
+ {
+ if(!pReference->IsRTLEnabled() )
+ pWindowOutDev->ReMirror( aAbsolute );
+
+ tools::Rectangle aRect( pReference->ScreenToOutputPixel(aAbsolute), Size(1,1) ) ;
+ aRect = pReference->ImplOutputToUnmirroredAbsoluteScreenPixel( aRect );
+ aAbsolute = aRect.TopLeft();
+ }
+ else
+ aAbsolute = pReference->OutputToAbsoluteScreenPixel(
+ pReference->ScreenToOutputPixel(rPos) );
+
+ return aAbsolute;
+}
+
+tools::Rectangle FloatingWindow::ImplConvertToAbsPos(vcl::Window* pReference, const tools::Rectangle& rRect)
+{
+ tools::Rectangle aFloatRect = rRect;
+
+ const OutputDevice *pParentWinOutDev = pReference->GetOutDev();
+
+ // compare coordinates in absolute screen coordinates
+ // Keep in sync with FloatingWindow::ImplFloatHitTest, e.g. fdo#33509
+ if( pParentWinOutDev->HasMirroredGraphics() && !comphelper::LibreOfficeKit::isActive() )
+ {
+ if(!pReference->IsRTLEnabled() )
+ pParentWinOutDev->ReMirror(aFloatRect);
+
+ aFloatRect.SetPos(pReference->ScreenToOutputPixel(aFloatRect.TopLeft()));
+ aFloatRect = pReference->ImplOutputToUnmirroredAbsoluteScreenPixel(aFloatRect);
+ }
+ else
+ aFloatRect.SetPos(pReference->OutputToAbsoluteScreenPixel(pReference->ScreenToOutputPixel(rRect.TopLeft())));
+
+ return aFloatRect;
+}
+
+tools::Rectangle FloatingWindow::ImplConvertToRelPos(vcl::Window* pReference, const tools::Rectangle& rRect)
+{
+ tools::Rectangle aFloatRect = rRect;
+
+ const OutputDevice *pParentWinOutDev = pReference->GetOutDev();
+
+ // compare coordinates in absolute screen coordinates
+ // Keep in sync with FloatingWindow::ImplFloatHitTest, e.g. fdo#33509
+ if( pParentWinOutDev->HasMirroredGraphics() )
+ {
+ aFloatRect = pReference->ImplUnmirroredAbsoluteScreenToOutputPixel(aFloatRect);
+ aFloatRect.SetPos(pReference->OutputToScreenPixel(aFloatRect.TopLeft()));
+
+ if(!pReference->IsRTLEnabled() )
+ pParentWinOutDev->ReMirror(aFloatRect);
+ }
+ else
+ aFloatRect.SetPos(pReference->OutputToScreenPixel(pReference->AbsoluteScreenToOutputPixel(rRect.TopLeft())));
+
+ return aFloatRect;
+}
+
+FloatingWindow* FloatingWindow::ImplFloatHitTest( vcl::Window* pReference, const Point& rPos, bool& rbHitTestInsideRect )
+{
+ FloatingWindow* pWin = this;
+ rbHitTestInsideRect = false;
+
+ Point aAbsolute(FloatingWindow::ImplConvertToAbsPos(pReference, rPos));
+
+ do
+ {
+ // compute the floating window's size in absolute screen coordinates
+
+ // use the border window to have the exact position
+ vcl::Window *pBorderWin = pWin->GetWindow( GetWindowType::Border );
+ if (!pBorderWin)
+ break;
+
+ // the top-left corner in output coordinates ie (0,0)
+ tools::Rectangle devRect( pBorderWin->ImplOutputToUnmirroredAbsoluteScreenPixel( tools::Rectangle( Point(), pBorderWin->GetSizePixel()) ) ) ;
+ if ( devRect.Contains( aAbsolute ) )
+ {
+ // inside the window
+ return pWin;
+ }
+
+ // test, if mouse is in rectangle, (this is typically the rect of the active
+ // toolbox item or similar)
+ // note: maFloatRect is set in FloatingWindow::StartPopupMode() and
+ // is already in absolute device coordinates
+ if ( pWin->maFloatRect.Contains( aAbsolute ) )
+ {
+ rbHitTestInsideRect = true;
+ return pWin;
+ }
+
+ pWin = pWin->mpNextFloat;
+ }
+ while ( pWin );
+
+ return nullptr;
+}
+
+FloatingWindow* FloatingWindow::ImplFindLastLevelFloat()
+{
+ FloatingWindow* pWin = this;
+ FloatingWindow* pLastFoundWin = pWin;
+
+ do
+ {
+ if ( pWin->GetPopupModeFlags() & FloatWinPopupFlags::NewLevel )
+ pLastFoundWin = pWin;
+
+ pWin = pWin->mpNextFloat;
+ }
+ while ( pWin );
+
+ return pLastFoundWin;
+}
+
+bool FloatingWindow::ImplIsFloatPopupModeWindow( const vcl::Window* pWindow )
+{
+ FloatingWindow* pWin = this;
+
+ do
+ {
+ if ( pWin->mpFirstPopupModeWin == pWindow )
+ return true;
+
+ pWin = pWin->mpNextFloat;
+ }
+ while ( pWin );
+
+ return false;
+}
+
+IMPL_LINK_NOARG(FloatingWindow, ImplEndPopupModeHdl, void*, void)
+{
+ VclPtr<FloatingWindow> pThis(this);
+ mnPostId = nullptr;
+ mnPopupModeFlags = FloatWinPopupFlags::NONE;
+ mbPopupMode = false;
+ PopupModeEnd();
+}
+
+bool FloatingWindow::EventNotify( NotifyEvent& rNEvt )
+{
+ // call Base Class first for tab control
+ bool bRet = SystemWindow::EventNotify( rNEvt );
+ if ( !bRet )
+ {
+ if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ const KeyEvent* pKEvt = rNEvt.GetKeyEvent();
+ vcl::KeyCode aKeyCode = pKEvt->GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+
+ if ( (nKeyCode == KEY_ESCAPE) && (GetStyle() & WB_CLOSEABLE) )
+ {
+ Close();
+ return true;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+void FloatingWindow::PixelInvalidate(const tools::Rectangle* /*pRectangle*/)
+{
+ if (VclPtr<vcl::Window> pParent = GetParentWithLOKNotifier())
+ {
+ const tools::Rectangle aRect(Point(0,0), Size(GetSizePixel().Width()+1, GetSizePixel().Height()+1));
+ std::vector<vcl::LOKPayloadItem> aPayload
+ {
+ std::make_pair(OString("rectangle"), aRect.toString())
+ };
+ const vcl::ILibreOfficeKitNotifier* pNotifier = pParent->GetLOKNotifier();
+ pNotifier->notifyWindow(GetLOKWindowId(), "invalidate", aPayload);
+ }
+}
+
+void FloatingWindow::StateChanged( StateChangedType nType )
+{
+ if (nType == StateChangedType::InitShow)
+ {
+ DoInitialLayout();
+ }
+
+ SystemWindow::StateChanged( nType );
+
+ VclPtr<vcl::Window> pParent = GetParentWithLOKNotifier();
+ if (pParent)
+ {
+ if (nType == StateChangedType::InitShow)
+ {
+ std::vector<vcl::LOKPayloadItem> aItems;
+ if (pParent == this)
+ {
+ // we are a toplevel window, let's so far pretend to be a
+ // dialog - but maybe we'll need a separate type for this
+ // later
+ if (mbInPopupMode)
+ aItems.emplace_back("type", "dropdown");
+ else
+ aItems.emplace_back("type", "dialog");
+ aItems.emplace_back("position", mpImplData->maLOKTwipsPos.toString()); // twips
+ }
+ else
+ {
+ SetLOKNotifier(pParent->GetLOKNotifier());
+ if (dynamic_cast<HelpTextWindow*>(this))
+ aItems.emplace_back("type", "tooltip");
+ else
+ aItems.emplace_back("type", "child");
+
+ aItems.emplace_back("parentId", OString::number(pParent->GetLOKWindowId()));
+ if (mbInPopupMode)
+ aItems.emplace_back("position", mpImplData->maPos.toString()); // pixels
+ else // mpImplData->maPos is not set
+ aItems.emplace_back("position", GetPosPixel().toString());
+
+ }
+ aItems.emplace_back("size", GetSizePixel().toString());
+ GetLOKNotifier()->notifyWindow(GetLOKWindowId(), "created", aItems);
+ }
+ else if (!IsVisible() && nType == StateChangedType::Visible)
+ {
+ if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
+ {
+ pNotifier->notifyWindow(GetLOKWindowId(), "close");
+ ReleaseLOKNotifier();
+ }
+ }
+ }
+
+ if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+}
+
+void FloatingWindow::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ SystemWindow::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+}
+
+void FloatingWindow::ImplCallPopupModeEnd()
+{
+ // PopupMode is finished
+ mbInPopupMode = false;
+
+ // call Handler asynchronously.
+ if ( mpImplData && !mnPostId )
+ mnPostId = Application::PostUserEvent(LINK(this, FloatingWindow, ImplEndPopupModeHdl));
+}
+
+void FloatingWindow::PopupModeEnd()
+{
+ maPopupModeEndHdl.Call( this );
+}
+
+void FloatingWindow::SetTitleType( FloatWinTitleType nTitle )
+{
+ if ( (mnTitle == nTitle) || !mpWindowImpl->mpBorderWindow )
+ return;
+
+ mnTitle = nTitle;
+ Size aOutSize = GetOutputSizePixel();
+ BorderWindowTitleType nTitleStyle;
+ if ( nTitle == FloatWinTitleType::Normal )
+ nTitleStyle = BorderWindowTitleType::Small;
+ else if ( nTitle == FloatWinTitleType::TearOff )
+ nTitleStyle = BorderWindowTitleType::Tearoff;
+ else if ( nTitle == FloatWinTitleType::Popup )
+ nTitleStyle = BorderWindowTitleType::Popup;
+ else // nTitle == FloatWinTitleType::NONE
+ nTitleStyle = BorderWindowTitleType::NONE;
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetTitleType( nTitleStyle, aOutSize );
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->GetBorder( mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder );
+}
+
+void FloatingWindow::StartPopupMode( const tools::Rectangle& rRect, FloatWinPopupFlags nFlags )
+{
+ // remove title
+ mnOldTitle = mnTitle;
+ if ( ( mpWindowImpl->mnStyle & WB_POPUP ) && !GetText().isEmpty() )
+ SetTitleType( FloatWinTitleType::Popup );
+ else if ( nFlags & FloatWinPopupFlags::AllowTearOff )
+ SetTitleType( FloatWinTitleType::TearOff );
+ else
+ SetTitleType( FloatWinTitleType::NONE );
+
+ // avoid close on focus change for decorated floating windows only
+ if( mpWindowImpl->mbFrame && (GetStyle() & WB_MOVEABLE) )
+ nFlags |= FloatWinPopupFlags::NoAppFocusClose;
+
+ // compute window position according to flags and arrangement
+ sal_uInt16 nArrangeIndex;
+ DoInitialLayout();
+ mpImplData->maPos = ImplCalcPos(this, rRect, nFlags, nArrangeIndex, &mpImplData->maLOKTwipsPos);
+ SetPosPixel( mpImplData->maPos );
+ ImplGetFrame()->PositionByToolkit(rRect, nFlags);
+
+ // set data and display window
+ // convert maFloatRect to absolute device coordinates
+ // so they can be compared across different frames
+ // !!! rRect is expected to be in screen coordinates of the parent frame window !!!
+ maFloatRect = FloatingWindow::ImplConvertToAbsPos(GetParent(), rRect);
+
+ maFloatRect.AdjustLeft( -2 );
+ maFloatRect.AdjustTop( -2 );
+ maFloatRect.AdjustRight(2 );
+ maFloatRect.AdjustBottom(2 );
+ mnPopupModeFlags = nFlags;
+ mbInPopupMode = true;
+ mbPopupMode = true;
+ mbPopupModeCanceled = false;
+ mbPopupModeTearOff = false;
+ mbMouseDown = false;
+
+ // add FloatingWindow to list of windows that are in popup mode
+ ImplSVData* pSVData = ImplGetSVData();
+ mpNextFloat = pSVData->mpWinData->mpFirstFloat;
+ pSVData->mpWinData->mpFirstFloat = this;
+ bool bGrabFocus(nFlags & FloatWinPopupFlags::GrabFocus);
+ if (bGrabFocus)
+ {
+ // force key input even without focus (useful for menus)
+ mbGrabFocus = true;
+ mxPrevFocusWin = Window::SaveFocus();
+ mpWindowImpl->mpFrameData->mbHasFocus = true;
+ }
+ Show( true, ShowFlags::NoActivate );
+ if (bGrabFocus)
+ GrabFocus();
+}
+
+void FloatingWindow::StartPopupMode( ToolBox* pBox, FloatWinPopupFlags nFlags )
+{
+ mpImplData->mpBox = pBox;
+
+ // get selected button
+ ToolBoxItemId nItemId = pBox->GetDownItemId();
+
+ if ( nItemId )
+ pBox->ImplFloatControl( true, this );
+
+ // retrieve some data from the ToolBox
+ tools::Rectangle aRect = nItemId ? pBox->GetItemRect( nItemId ) : pBox->GetOverflowRect();
+
+ // convert to parent's screen coordinates
+ mpImplData->maPos = GetParent()->OutputToScreenPixel( GetParent()->AbsoluteScreenToOutputPixel( pBox->OutputToAbsoluteScreenPixel( aRect.TopLeft() ) ) );
+ aRect.SetPos( mpImplData->maPos );
+
+ nFlags |=
+ FloatWinPopupFlags::AllMouseButtonClose |
+ FloatWinPopupFlags::NoMouseUpClose;
+
+ // set Flags for positioning
+ if ( !(nFlags & (FloatWinPopupFlags::Down | FloatWinPopupFlags::Up |
+ FloatWinPopupFlags::Left | FloatWinPopupFlags::Right)) )
+ {
+ if ( pBox->IsHorizontal() )
+ nFlags |= FloatWinPopupFlags::Down;
+ else
+ nFlags |= FloatWinPopupFlags::Right;
+ }
+
+ // start FloatingMode
+ StartPopupMode( aRect, nFlags );
+}
+
+void FloatingWindow::ImplEndPopupMode( FloatWinPopupEndFlags nFlags, const VclPtr<vcl::Window>& xFocusId )
+{
+ if ( !mbInPopupMode )
+ return;
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ mbInCleanUp = true; // prevent killing this window due to focus change while working with it
+
+ if (!(nFlags & FloatWinPopupEndFlags::NoCloseChildren))
+ {
+ // stop the PopupMode also for all PopupMode windows created after us
+ std::vector<VclPtr<FloatingWindow>> aCancelFloats;
+ // stop the PopupMode also for all following PopupMode windows
+ for (auto pFloat = pSVData->mpWinData->mpFirstFloat;
+ pFloat != nullptr && pFloat != this;
+ pFloat = pFloat->mpNextFloat)
+ aCancelFloats.push_back(pFloat);
+ for (auto & it : aCancelFloats)
+ it->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::NoCloseChildren);
+ }
+
+ // delete window from the list
+ pSVData->mpWinData->mpFirstFloat = mpNextFloat;
+ mpNextFloat = nullptr;
+
+ FloatWinPopupFlags nPopupModeFlags = mnPopupModeFlags;
+ mbPopupModeTearOff = nFlags & FloatWinPopupEndFlags::TearOff &&
+ nPopupModeFlags & FloatWinPopupFlags::AllowTearOff;
+
+ // hide window again if it was not deleted
+ if (!mbPopupModeTearOff)
+ Show( false, ShowFlags::NoFocusChange );
+
+ if (HasChildPathFocus() && xFocusId != nullptr)
+ {
+ // restore focus to previous focus window if we still have the focus
+ Window::EndSaveFocus(xFocusId);
+ }
+ else if ( pSVData->mpWinData->mpFocusWin && pSVData->mpWinData->mpFirstFloat &&
+ ImplIsWindowOrChild( pSVData->mpWinData->mpFocusWin ) )
+ {
+ // maybe pass focus on to a suitable FloatingWindow
+ pSVData->mpWinData->mpFirstFloat->GrabFocus();
+ }
+
+ mbPopupModeCanceled = bool(nFlags & FloatWinPopupEndFlags::Cancel);
+
+ // redo title
+ SetTitleType( mnOldTitle );
+
+ // set ToolBox again to normal
+ if (mpImplData && mpImplData->mpBox)
+ {
+ mpImplData->mpBox->ImplFloatControl( false, this );
+ // if the parent ToolBox is in popup mode, it should be closed too.
+ if ( GetDockingManager()->IsInPopupMode( mpImplData->mpBox ) )
+ nFlags |= FloatWinPopupEndFlags::CloseAll;
+
+ mpImplData->mpBox = nullptr;
+ }
+
+ // call PopupModeEnd-Handler depending on parameter
+ if ( !(nFlags & FloatWinPopupEndFlags::DontCallHdl) )
+ ImplCallPopupModeEnd();
+
+ // close all other windows depending on parameter
+ if ( nFlags & FloatWinPopupEndFlags::CloseAll )
+ {
+ if ( !(nPopupModeFlags & FloatWinPopupFlags::NewLevel) )
+ {
+ if (pSVData->mpWinData->mpFirstFloat)
+ {
+ FloatingWindow* pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat();
+ pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll );
+ }
+ }
+ }
+
+ mbInCleanUp = false;
+}
+
+void FloatingWindow::EndPopupMode( FloatWinPopupEndFlags nFlags )
+{
+ ImplEndPopupMode(nFlags, mxPrevFocusWin);
+}
+
+void FloatingWindow::AddPopupModeWindow(vcl::Window* pWindow)
+{
+ // !!! up-to-now only 1 window and not yet a list
+ mpFirstPopupModeWin = pWindow;
+}
+
+bool SystemWindow::UpdatePositionData()
+{
+ auto pWin = ImplGetParent();
+ if (pWin)
+ {
+ // Simulate Move, so the relative position of the floating window will be recalculated
+ pWin->ImplCallMove();
+ return true;
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/globalization.cxx b/vcl/source/window/globalization.cxx
new file mode 100644
index 000000000..952182978
--- /dev/null
+++ b/vcl/source/window/globalization.cxx
@@ -0,0 +1,44 @@
+/* -*- 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 <vcl/window.hxx>
+#include <vcl/outdev.hxx>
+#include <windowdev.hxx>
+#include <window.h>
+
+namespace vcl {
+
+void WindowOutputDevice::EnableRTL ( bool bEnable )
+{
+ if (mbEnableRTL != bEnable)
+ mxOwnerWindow->ImplEnableRTL(bEnable);
+}
+
+void Window::ImplEnableRTL( bool bEnable )
+{
+ if (mpWindowImpl->mxOutDev->mbEnableRTL != bEnable)
+ {
+ CompatStateChanged( StateChangedType::Mirroring );
+ mpWindowImpl->mxOutDev->OutputDevice::EnableRTL(bEnable);
+ }
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/impldockingwrapper.hxx b/vcl/source/window/impldockingwrapper.hxx
new file mode 100644
index 000000000..671510e3c
--- /dev/null
+++ b/vcl/source/window/impldockingwrapper.hxx
@@ -0,0 +1,131 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <vcl/dockwin.hxx>
+#include <memory>
+#include <vector>
+
+/** ImplDockingWindowWrapper
+ *
+ * ImplDockingWindowWrapper obsoletes the DockingWindow class.
+ * It is better because it can make a "normal window" dockable.
+ * All DockingWindows should be converted the new class.
+ */
+
+class ImplDockingWindowWrapper final
+{
+ friend class ::vcl::Window;
+ friend class DockingManager;
+ friend class DockingWindow;
+
+private:
+
+ // the original 'Docking'window
+ VclPtr<vcl::Window> mpDockingWindow;
+
+ // the original DockingWindow members
+ VclPtr<FloatingWindow> mpFloatWin;
+ VclPtr<vcl::Window> mpOldBorderWin;
+ VclPtr<vcl::Window> mpParent;
+ Link<FloatingWindow*,void> maPopupModeEndHdl;
+ Point maFloatPos;
+ Point maDockPos;
+ Point maMouseOff;
+ Size maMinOutSize;
+ Size maMaxOutSize;
+ tools::Rectangle maDragArea;
+ tools::Long mnTrackX;
+ tools::Long mnTrackY;
+ tools::Long mnTrackWidth;
+ tools::Long mnTrackHeight;
+ sal_Int32 mnDockLeft;
+ sal_Int32 mnDockTop;
+ sal_Int32 mnDockRight;
+ sal_Int32 mnDockBottom;
+ WinBits mnFloatBits;
+ bool mbDockCanceled:1,
+ mbDocking:1,
+ mbLastFloatMode:1,
+ mbDockBtn:1,
+ mbHideBtn:1,
+ mbStartDockingEnabled:1,
+ mbLocked:1;
+
+ DECL_LINK( PopupModeEnd, FloatingWindow*, void );
+ void ImplEnableStartDocking() { mbStartDockingEnabled = true; }
+ bool ImplStartDockingEnabled() const { return mbStartDockingEnabled; }
+ void ImplPreparePopupMode();
+
+public:
+ ImplDockingWindowWrapper( const vcl::Window *pWindow );
+ ~ImplDockingWindowWrapper();
+
+ vcl::Window* GetWindow() { return mpDockingWindow; }
+ void ImplStartDocking( const Point& rPos );
+
+ // those methods actually call the corresponding handlers
+ void StartDocking( const Point& rPos, tools::Rectangle const & rRect );
+ bool Docking( const Point& rPos, tools::Rectangle& rRect );
+ void EndDocking( const tools::Rectangle& rRect, bool bFloatMode );
+ bool PrepareToggleFloatingMode();
+ void ToggleFloatingMode();
+
+ void SetDragArea( const tools::Rectangle& rRect );
+ const tools::Rectangle& GetDragArea() const { return maDragArea;}
+
+ void Lock();
+ void Unlock();
+ bool IsLocked() const { return mbLocked;}
+
+ void StartPopupMode( const tools::Rectangle& rRect, FloatWinPopupFlags nPopupModeFlags );
+ void StartPopupMode( ToolBox* pParentToolBox, FloatWinPopupFlags nPopupModeFlags );
+ bool IsInPopupMode() const;
+
+ void SetPopupModeEndHdl( const Link<FloatingWindow*,void>& rLink ) { maPopupModeEndHdl = rLink; }
+
+ void TitleButtonClick( TitleButton nButton );
+ void Resizing( Size& rSize );
+ void Tracking( const TrackingEvent& rTEvt );
+
+ void ShowMenuTitleButton( bool bVisible );
+
+ void SetMinOutputSizePixel( const Size& rSize );
+
+ void SetMaxOutputSizePixel( const Size& rSize );
+
+ bool IsDocking() const { return mbDocking; }
+ bool IsDockingCanceled() const { return mbDockCanceled; }
+
+ void SetFloatingMode( bool bFloatMode );
+ bool IsFloatingMode() const;
+ SystemWindow* GetFloatingWindow() const;
+
+ void SetFloatStyle( WinBits nWinStyle );
+ WinBits GetFloatStyle() const { return mnFloatBits;}
+
+ void setPosSizePixel( tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ PosSizeFlags nFlags );
+ Point GetPosPixel() const;
+ Size GetSizePixel() const;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/introwin.cxx b/vcl/source/window/introwin.cxx
new file mode 100644
index 000000000..3ac75eab0
--- /dev/null
+++ b/vcl/source/window/introwin.cxx
@@ -0,0 +1,49 @@
+/* -*- 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 <vcl/wrkwin.hxx>
+#include <vcl/introwin.hxx>
+
+#include <svdata.hxx>
+
+void IntroWindow::ImplInitIntroWindowData()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->mpIntroWindow = this;
+}
+
+IntroWindow::IntroWindow()
+ : WorkWindow(WindowType::INTROWINDOW)
+{
+ ImplInitIntroWindowData();
+ WorkWindow::ImplInit(nullptr, WB_INTROWIN);
+}
+
+IntroWindow::~IntroWindow() { disposeOnce(); }
+
+void IntroWindow::dispose()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->mpIntroWindow.get() == this)
+ pSVData->mpIntroWindow = nullptr;
+
+ WorkWindow::dispose();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/keycod.cxx b/vcl/source/window/keycod.cxx
new file mode 100644
index 000000000..cc3fca580
--- /dev/null
+++ b/vcl/source/window/keycod.cxx
@@ -0,0 +1,95 @@
+/* -*- 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 <accel.hxx>
+#include <salframe.hxx>
+#include <svdata.hxx>
+
+#include <vcl/window.hxx>
+#include <vcl/keycod.hxx>
+
+const sal_uInt16 aImplKeyFuncTab[(static_cast<int>(KeyFuncType::DELETE)+1)*4] =
+{
+ 0, 0, 0, 0, // KeyFuncType::DONTKNOW
+ KEY_X | KEY_MOD1, KEY_DELETE | KEY_SHIFT, KEY_CUT, 0, // KeyFuncType::CUT
+ KEY_C | KEY_MOD1, KEY_INSERT | KEY_MOD1, KEY_COPY, 0, // KeyFuncType::COPY
+ KEY_V | KEY_MOD1, KEY_INSERT | KEY_SHIFT, KEY_PASTE, 0, // KeyFuncType::PASTE
+ KEY_Z | KEY_MOD1, KEY_BACKSPACE | KEY_MOD2, KEY_UNDO, 0, // KeyFuncType::UNDO
+ KEY_Y | KEY_MOD1, KEY_UNDO | KEY_SHIFT, 0, 0, // KeyFuncType::REDO
+ KEY_DELETE, 0, 0, 0 // KeyFuncType::DELETE
+};
+
+bool ImplGetKeyCode( KeyFuncType eFunc, sal_uInt16& rCode1, sal_uInt16& rCode2, sal_uInt16& rCode3, sal_uInt16& rCode4 )
+{
+ size_t nIndex = static_cast<size_t>(eFunc);
+ nIndex *= 4;
+
+ assert(nIndex + 3 < SAL_N_ELEMENTS(aImplKeyFuncTab) && "bad key code index");
+ if (nIndex + 3 >= SAL_N_ELEMENTS(aImplKeyFuncTab))
+ {
+ rCode1 = rCode2 = rCode3 = rCode4 = 0;
+ return false;
+ }
+
+ rCode1 = aImplKeyFuncTab[nIndex];
+ rCode2 = aImplKeyFuncTab[nIndex+1];
+ rCode3 = aImplKeyFuncTab[nIndex+2];
+ rCode4 = aImplKeyFuncTab[nIndex+3];
+ return true;
+}
+
+vcl::KeyCode::KeyCode( KeyFuncType eFunction )
+{
+ sal_uInt16 nDummy;
+ ImplGetKeyCode( eFunction, nKeyCodeAndModifiers, nDummy, nDummy, nDummy );
+ eFunc = eFunction;
+}
+
+OUString vcl::KeyCode::GetName() const
+{
+ vcl::Window* pWindow = ImplGetDefaultWindow();
+ return pWindow ? pWindow->ImplGetFrame()->GetKeyName( GetFullCode() ) : "";
+}
+
+KeyFuncType vcl::KeyCode::GetFunction() const
+{
+ if ( eFunc != KeyFuncType::DONTKNOW )
+ return eFunc;
+
+ sal_uInt16 nCompCode = GetModifier() | GetCode();
+ if ( nCompCode )
+ {
+ for ( sal_uInt16 i = sal_uInt16(KeyFuncType::CUT); i <= sal_uInt16(KeyFuncType::DELETE); i++ )
+ {
+ sal_uInt16 nKeyCode1;
+ sal_uInt16 nKeyCode2;
+ sal_uInt16 nKeyCode3;
+ sal_uInt16 nKeyCode4;
+ ImplGetKeyCode( static_cast<KeyFuncType>(i), nKeyCode1, nKeyCode2, nKeyCode3, nKeyCode4 );
+ if ( (nCompCode == nKeyCode1) || (nCompCode == nKeyCode2) || (nCompCode == nKeyCode3) || (nCompCode == nKeyCode4) )
+ return static_cast<KeyFuncType>(i);
+ }
+ }
+
+ return KeyFuncType::DONTKNOW;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/keyevent.cxx b/vcl/source/window/keyevent.cxx
new file mode 100644
index 000000000..eca00d411
--- /dev/null
+++ b/vcl/source/window/keyevent.cxx
@@ -0,0 +1,67 @@
+/* -*- 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 <vcl/event.hxx>
+
+KeyEvent KeyEvent::LogicalTextDirectionality (TextDirectionality eMode) const
+{
+ KeyEvent aClone(*this);
+
+ sal_uInt16 nCode = maKeyCode.GetCode();
+ sal_uInt16 nMod = maKeyCode.GetModifier();
+
+ switch (eMode)
+ {
+ case TextDirectionality::RightToLeft_TopToBottom:
+ switch (nCode)
+ {
+ case KEY_LEFT: aClone.maKeyCode = vcl::KeyCode(KEY_RIGHT, nMod); break;
+ case KEY_RIGHT: aClone.maKeyCode = vcl::KeyCode(KEY_LEFT, nMod); break;
+ }
+ break;
+
+ case TextDirectionality::TopToBottom_RightToLeft:
+ switch (nCode)
+ {
+ case KEY_DOWN: aClone.maKeyCode = vcl::KeyCode(KEY_RIGHT, nMod); break;
+ case KEY_UP: aClone.maKeyCode = vcl::KeyCode(KEY_LEFT, nMod); break;
+ case KEY_LEFT: aClone.maKeyCode = vcl::KeyCode(KEY_DOWN, nMod); break;
+ case KEY_RIGHT: aClone.maKeyCode = vcl::KeyCode(KEY_UP, nMod); break;
+ }
+ break;
+
+ case TextDirectionality::BottomToTop_LeftToRight:
+ switch (nCode)
+ {
+ case KEY_DOWN: aClone.maKeyCode = vcl::KeyCode(KEY_LEFT, nMod); break;
+ case KEY_UP: aClone.maKeyCode = vcl::KeyCode(KEY_RIGHT, nMod); break;
+ case KEY_LEFT: aClone.maKeyCode = vcl::KeyCode(KEY_UP, nMod); break;
+ case KEY_RIGHT: aClone.maKeyCode = vcl::KeyCode(KEY_DOWN, nMod); break;
+ }
+ break;
+
+ case TextDirectionality::LeftToRight_TopToBottom:
+ /* do nothing */
+ break;
+ }
+
+ return aClone;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/layout.cxx b/vcl/source/window/layout.cxx
new file mode 100644
index 000000000..946e2cce2
--- /dev/null
+++ b/vcl/source/window/layout.cxx
@@ -0,0 +1,3028 @@
+/* -*- 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/.
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <config_features.h>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <comphelper/base64.hxx>
+#include <comphelper/lok.hxx>
+#include <o3tl/enumarray.hxx>
+#include <o3tl/enumrange.hxx>
+#include <o3tl/string_view.hxx>
+#include <tools/stream.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/help.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/stdtext.hxx>
+#include <vcl/split.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+#include <bitmaps.hlst>
+#include <messagedialog.hxx>
+#include <svdata.hxx>
+#include <window.h>
+#include <boost/multi_array.hpp>
+#include <vcl/toolkit/vclmedit.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <sal/log.hxx>
+#include <tools/json_writer.hxx>
+
+VclContainer::VclContainer(vcl::Window *pParent, WinBits nStyle)
+ : Window(WindowType::CONTAINER)
+ , m_bLayoutDirty(true)
+{
+ ImplInit(pParent, nStyle, nullptr);
+ EnableChildTransparentMode();
+ SetPaintTransparent(true);
+ SetBackground();
+}
+
+sal_uInt16 VclContainer::getDefaultAccessibleRole() const
+{
+ return css::accessibility::AccessibleRole::PANEL;
+}
+
+Size VclContainer::GetOptimalSize() const
+{
+ return calculateRequisition();
+}
+
+void VclContainer::setLayoutPosSize(vcl::Window &rWindow, const Point &rPos, const Size &rSize)
+{
+ sal_Int32 nBorderWidth = rWindow.get_border_width();
+ sal_Int32 nLeft = rWindow.get_margin_start() + nBorderWidth;
+ sal_Int32 nTop = rWindow.get_margin_top() + nBorderWidth;
+ sal_Int32 nRight = rWindow.get_margin_end() + nBorderWidth;
+ sal_Int32 nBottom = rWindow.get_margin_bottom() + nBorderWidth;
+ Point aPos(rPos.X() + nLeft, rPos.Y() + nTop);
+ Size aSize(rSize.Width() - nLeft - nRight, rSize.Height() - nTop - nBottom);
+ rWindow.SetPosSizePixel(aPos, aSize);
+}
+
+void VclContainer::setLayoutAllocation(vcl::Window &rChild, const Point &rAllocPos, const Size &rChildAlloc)
+{
+ VclAlign eHalign = rChild.get_halign();
+ VclAlign eValign = rChild.get_valign();
+
+ //typical case
+ if (eHalign == VclAlign::Fill && eValign == VclAlign::Fill)
+ {
+ setLayoutPosSize(rChild, rAllocPos, rChildAlloc);
+ return;
+ }
+
+ Point aChildPos(rAllocPos);
+ Size aChildSize(rChildAlloc);
+ Size aChildPreferredSize(getLayoutRequisition(rChild));
+
+ switch (eHalign)
+ {
+ case VclAlign::Fill:
+ break;
+ case VclAlign::Start:
+ if (aChildPreferredSize.Width() < rChildAlloc.Width())
+ aChildSize.setWidth( aChildPreferredSize.Width() );
+ break;
+ case VclAlign::End:
+ if (aChildPreferredSize.Width() < rChildAlloc.Width())
+ aChildSize.setWidth( aChildPreferredSize.Width() );
+ aChildPos.AdjustX(rChildAlloc.Width() );
+ aChildPos.AdjustX( -(aChildSize.Width()) );
+ break;
+ case VclAlign::Center:
+ if (aChildPreferredSize.Width() < aChildSize.Width())
+ aChildSize.setWidth( aChildPreferredSize.Width() );
+ aChildPos.AdjustX((rChildAlloc.Width() - aChildSize.Width()) / 2 );
+ break;
+ }
+
+ switch (eValign)
+ {
+ case VclAlign::Fill:
+ break;
+ case VclAlign::Start:
+ if (aChildPreferredSize.Height() < rChildAlloc.Height())
+ aChildSize.setHeight( aChildPreferredSize.Height() );
+ break;
+ case VclAlign::End:
+ if (aChildPreferredSize.Height() < rChildAlloc.Height())
+ aChildSize.setHeight( aChildPreferredSize.Height() );
+ aChildPos.AdjustY(rChildAlloc.Height() );
+ aChildPos.AdjustY( -(aChildSize.Height()) );
+ break;
+ case VclAlign::Center:
+ if (aChildPreferredSize.Height() < aChildSize.Height())
+ aChildSize.setHeight( aChildPreferredSize.Height() );
+ aChildPos.AdjustY((rChildAlloc.Height() - aChildSize.Height()) / 2 );
+ break;
+ }
+
+ setLayoutPosSize(rChild, aChildPos, aChildSize);
+}
+
+namespace
+{
+ Size subtractBorder(const vcl::Window &rWindow, const Size& rSize)
+ {
+ sal_Int32 nBorderWidth = rWindow.get_border_width();
+ sal_Int32 nLeft = rWindow.get_margin_start() + nBorderWidth;
+ sal_Int32 nTop = rWindow.get_margin_top() + nBorderWidth;
+ sal_Int32 nRight = rWindow.get_margin_end() + nBorderWidth;
+ sal_Int32 nBottom = rWindow.get_margin_bottom() + nBorderWidth;
+ Size aSize(rSize);
+ return Size(aSize.Width() + nLeft + nRight, aSize.Height() + nTop + nBottom);
+ }
+}
+
+Size VclContainer::getLayoutRequisition(const vcl::Window &rWindow)
+{
+ return subtractBorder(rWindow, rWindow.get_preferred_size());
+}
+
+void VclContainer::SetPosSizePixel(const Point& rAllocPos, const Size& rAllocation)
+{
+ bool bSizeChanged = rAllocation != GetOutputSizePixel();
+ Window::SetPosSizePixel(rAllocPos, rAllocation);
+ if (m_bLayoutDirty || bSizeChanged)
+ {
+ m_bLayoutDirty = false;
+ setAllocation(rAllocation);
+ }
+}
+
+void VclContainer::SetPosPixel(const Point& rAllocPos)
+{
+ Point aAllocPos = rAllocPos;
+ sal_Int32 nBorderWidth = get_border_width();
+ aAllocPos.AdjustX(nBorderWidth + get_margin_start() );
+ aAllocPos.AdjustY(nBorderWidth + get_margin_top() );
+
+ if (aAllocPos != GetPosPixel())
+ Window::SetPosPixel(aAllocPos);
+}
+
+void VclContainer::SetSizePixel(const Size& rAllocation)
+{
+ Size aAllocation = rAllocation;
+ sal_Int32 nBorderWidth = get_border_width();
+ aAllocation.AdjustWidth( -(nBorderWidth*2 + get_margin_start() + get_margin_end()) );
+ aAllocation.AdjustHeight( -(nBorderWidth*2 + get_margin_top() + get_margin_bottom()) );
+ bool bSizeChanged = aAllocation != GetSizePixel();
+ if (bSizeChanged)
+ Window::SetSizePixel(aAllocation);
+ if (m_bLayoutDirty || bSizeChanged)
+ {
+ m_bLayoutDirty = false;
+ setAllocation(aAllocation);
+ }
+}
+
+void VclContainer::queue_resize(StateChangedType eReason)
+{
+ m_bLayoutDirty = true;
+ Window::queue_resize(eReason);
+}
+
+// support for screenshot context menu
+void VclContainer::Command(const CommandEvent& rCEvt)
+{
+ if (CommandEventId::ContextMenu == rCEvt.GetCommand())
+ {
+ auto pParent = GetParent();
+ if (pParent)
+ {
+ CommandEvent aCEvt(rCEvt.GetMousePosPixel() + GetPosPixel(), rCEvt.GetCommand(), rCEvt.IsMouseEvent(), rCEvt.GetEventData());
+ pParent->Command(aCEvt);
+ return;
+ }
+ }
+
+ // call parent (do not consume)
+ Window::Command(rCEvt);
+}
+
+void VclBox::accumulateMaxes(const Size &rChildSize, Size &rSize) const
+{
+ tools::Long nSecondaryChildDimension = getSecondaryDimension(rChildSize);
+ tools::Long nSecondaryBoxDimension = getSecondaryDimension(rSize);
+ setSecondaryDimension(rSize, std::max(nSecondaryChildDimension, nSecondaryBoxDimension));
+
+ tools::Long nPrimaryChildDimension = getPrimaryDimension(rChildSize);
+ tools::Long nPrimaryBoxDimension = getPrimaryDimension(rSize);
+ if (m_bHomogeneous)
+ setPrimaryDimension(rSize, std::max(nPrimaryBoxDimension, nPrimaryChildDimension));
+ else
+ setPrimaryDimension(rSize, nPrimaryBoxDimension + nPrimaryChildDimension);
+}
+
+Size VclBox::calculateRequisition() const
+{
+ sal_uInt16 nVisibleChildren = 0;
+
+ Size aSize;
+ for (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ ++nVisibleChildren;
+ Size aChildSize = getLayoutRequisition(*pChild);
+
+ tools::Long nPrimaryDimension = getPrimaryDimension(aChildSize);
+ nPrimaryDimension += pChild->get_padding() * 2;
+ setPrimaryDimension(aChildSize, nPrimaryDimension);
+
+ accumulateMaxes(aChildSize, aSize);
+ }
+
+ return finalizeMaxes(aSize, nVisibleChildren);
+}
+
+void VclBox::setAllocation(const Size &rAllocation)
+{
+ sal_uInt16 nVisibleChildren = 0, nExpandChildren = 0;
+ for (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ ++nVisibleChildren;
+ bool bExpand = getPrimaryDimensionChildExpand(*pChild);
+ if (bExpand)
+ ++nExpandChildren;
+ }
+
+ if (!nVisibleChildren)
+ return;
+
+ tools::Long nAllocPrimaryDimension = getPrimaryDimension(rAllocation);
+
+ tools::Long nHomogeneousDimension = 0, nExtraSpace = 0;
+ if (m_bHomogeneous)
+ {
+ nHomogeneousDimension = (nAllocPrimaryDimension -
+ (nVisibleChildren - 1) * m_nSpacing) / nVisibleChildren;
+ }
+ else if (nExpandChildren)
+ {
+ Size aRequisition = calculateRequisition();
+ nExtraSpace = (getPrimaryDimension(rAllocation) - getPrimaryDimension(aRequisition)) / nExpandChildren;
+// In mobile, the screen size is small and extraSpace can become negative
+// Though the dialogs are rendered in javascript for LOK Android some widgets like weld::DrawingArea
+// is sent as bitmap but it is rendered from only the visible part
+// when it gets negative, it shrinks instead of expands and it becomes invisible
+
+ if (nExtraSpace < 0)
+ {
+ SAL_WARN("vcl.layout", "nExtraSpace went negative for VclBox: " << GetHelpId());
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ nExtraSpace = 0;
+ }
+ }
+ }
+
+ //Split into those we pack from the start onwards, and those we pack from the end backwards
+ o3tl::enumarray<VclPackType,std::vector<vcl::Window*>> aWindows;
+ for (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+
+ VclPackType ePacking = pChild->get_pack_type();
+ aWindows[ePacking].push_back(pChild);
+ }
+
+ //See VclBuilder::sortIntoBestTabTraversalOrder for why they are in visual
+ //order under the parent which requires us to reverse them here to
+ //pack from the end back
+ std::reverse(aWindows[VclPackType::End].begin(),aWindows[VclPackType::End].end());
+
+ for (VclPackType ePackType : o3tl::enumrange<VclPackType>())
+ {
+ Point aPos(0, 0);
+ if (ePackType == VclPackType::End)
+ {
+ tools::Long nPrimaryCoordinate = getPrimaryCoordinate(aPos);
+ setPrimaryCoordinate(aPos, nPrimaryCoordinate + nAllocPrimaryDimension);
+ }
+
+ for (auto const& window : aWindows[ePackType])
+ {
+ vcl::Window *pChild = window;
+
+ tools::Long nPadding = pChild->get_padding();
+
+ Size aBoxSize;
+ if (m_bHomogeneous)
+ setPrimaryDimension(aBoxSize, nHomogeneousDimension);
+ else
+ {
+ aBoxSize = getLayoutRequisition(*pChild);
+ tools::Long nPrimaryDimension = getPrimaryDimension(aBoxSize);
+ nPrimaryDimension += nPadding * 2;
+ if (getPrimaryDimensionChildExpand(*pChild))
+ nPrimaryDimension += nExtraSpace;
+ setPrimaryDimension(aBoxSize, nPrimaryDimension);
+ }
+ setSecondaryDimension(aBoxSize, getSecondaryDimension(rAllocation));
+
+ Point aChildPos(aPos);
+ Size aChildSize(aBoxSize);
+ tools::Long nPrimaryCoordinate = getPrimaryCoordinate(aPos);
+
+ bool bFill = pChild->get_fill();
+ if (bFill)
+ {
+ setPrimaryDimension(aChildSize, std::max(static_cast<tools::Long>(1),
+ std::min(getPrimaryDimension(rAllocation), getPrimaryDimension(aBoxSize) - nPadding * 2)));
+
+ setPrimaryCoordinate(aChildPos, nPrimaryCoordinate + nPadding);
+ }
+ else
+ {
+ setPrimaryDimension(aChildSize,
+ getPrimaryDimension(getLayoutRequisition(*pChild)));
+
+ setPrimaryCoordinate(aChildPos, nPrimaryCoordinate +
+ (getPrimaryDimension(aBoxSize) - getPrimaryDimension(aChildSize)) / 2);
+ }
+
+ tools::Long nDiff = getPrimaryDimension(aBoxSize) + m_nSpacing;
+ if (ePackType == VclPackType::Start)
+ setPrimaryCoordinate(aPos, nPrimaryCoordinate + nDiff);
+ else
+ {
+ setPrimaryCoordinate(aPos, nPrimaryCoordinate - nDiff);
+ setPrimaryCoordinate(aChildPos, getPrimaryCoordinate(aChildPos) -
+ getPrimaryDimension(aBoxSize));
+ }
+
+ setLayoutAllocation(*pChild, aChildPos, aChildSize);
+ }
+ }
+}
+
+bool VclBox::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "spacing")
+ set_spacing(rValue.toInt32());
+ else if (rKey == "homogeneous")
+ set_homogeneous(toBool(rValue));
+ else
+ return VclContainer::set_property(rKey, rValue);
+ return true;
+}
+
+void VclBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ VclContainer::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("vertical", m_bVerticalContainer);
+}
+
+sal_uInt16 VclBox::getDefaultAccessibleRole() const
+{
+#if defined(_WIN32)
+ //fdo#74284 call Boxes Panels, keep then as "Filler" under
+ //at least Linux seeing as that's what Gtk does for GtkBoxes
+ return css::accessibility::AccessibleRole::PANEL;
+#else
+ return css::accessibility::AccessibleRole::FILLER;
+#endif
+}
+
+#define DEFAULT_CHILD_MIN_WIDTH 85
+#define DEFAULT_CHILD_MIN_HEIGHT 27
+
+Size VclBox::finalizeMaxes(const Size &rSize, sal_uInt16 nVisibleChildren) const
+{
+ Size aRet;
+
+ if (nVisibleChildren)
+ {
+ tools::Long nPrimaryDimension = getPrimaryDimension(rSize);
+ if (m_bHomogeneous)
+ nPrimaryDimension *= nVisibleChildren;
+ setPrimaryDimension(aRet, nPrimaryDimension + m_nSpacing * (nVisibleChildren-1));
+ setSecondaryDimension(aRet, getSecondaryDimension(rSize));
+ }
+
+ return aRet;
+}
+
+Size VclButtonBox::addReqGroups(const VclButtonBox::Requisition &rReq) const
+{
+ Size aRet;
+
+ tools::Long nMainGroupDimension = getPrimaryDimension(rReq.m_aMainGroupSize);
+ tools::Long nSubGroupDimension = getPrimaryDimension(rReq.m_aSubGroupSize);
+
+ setPrimaryDimension(aRet, nMainGroupDimension + nSubGroupDimension);
+
+ setSecondaryDimension(aRet,
+ std::max(getSecondaryDimension(rReq.m_aMainGroupSize),
+ getSecondaryDimension(rReq.m_aSubGroupSize)));
+
+ return aRet;
+}
+
+static tools::Long getMaxNonOutlier(const std::vector<tools::Long> &rG, tools::Long nAvgDimension)
+{
+ tools::Long nMaxDimensionNonOutlier = 0;
+ for (auto const& nPrimaryChildDimension : rG)
+ {
+ if (nPrimaryChildDimension < nAvgDimension * 1.5)
+ {
+ nMaxDimensionNonOutlier = std::max(nPrimaryChildDimension,
+ nMaxDimensionNonOutlier);
+ }
+ }
+ return nMaxDimensionNonOutlier;
+}
+
+static std::vector<tools::Long> setButtonSizes(const std::vector<tools::Long> &rG,
+ const std::vector<bool> &rNonHomogeneous,
+ tools::Long nAvgDimension, tools::Long nMaxNonOutlier, tools::Long nMinWidth)
+{
+ std::vector<tools::Long> aVec;
+ //set everything < 1.5 times the average to the same width, leave the
+ //outliers un-touched
+ std::vector<bool>::const_iterator aJ = rNonHomogeneous.begin();
+ auto nNonOutlierWidth = std::max(nMaxNonOutlier, nMinWidth);
+ for (auto const& nPrimaryChildDimension : rG)
+ {
+ bool bNonHomogeneous = *aJ;
+ if (!bNonHomogeneous && nPrimaryChildDimension < nAvgDimension * 1.5)
+ {
+ aVec.push_back(nNonOutlierWidth);
+ }
+ else
+ {
+ aVec.push_back(std::max(nPrimaryChildDimension, nMinWidth));
+ }
+ ++aJ;
+ }
+ return aVec;
+}
+
+VclButtonBox::Requisition VclButtonBox::calculatePrimarySecondaryRequisitions() const
+{
+ Requisition aReq;
+
+ Size aMainGroupSize(DEFAULT_CHILD_MIN_WIDTH, DEFAULT_CHILD_MIN_HEIGHT); //to-do, pull from theme
+ Size aSubGroupSize(DEFAULT_CHILD_MIN_WIDTH, DEFAULT_CHILD_MIN_HEIGHT); //to-do, pull from theme
+
+ tools::Long nMinMainGroupPrimary = getPrimaryDimension(aMainGroupSize);
+ tools::Long nMinSubGroupPrimary = getPrimaryDimension(aSubGroupSize);
+ tools::Long nMainGroupSecondary = getSecondaryDimension(aMainGroupSize);
+ tools::Long nSubGroupSecondary = getSecondaryDimension(aSubGroupSize);
+
+ bool bIgnoreSecondaryPacking = (m_eLayoutStyle == VclButtonBoxStyle::Spread || m_eLayoutStyle == VclButtonBoxStyle::Center);
+
+ std::vector<tools::Long> aMainGroupSizes;
+ std::vector<bool> aMainGroupNonHomogeneous;
+ std::vector<tools::Long> aSubGroupSizes;
+ std::vector<bool> aSubGroupNonHomogeneous;
+
+ for (const vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ Size aChildSize = getLayoutRequisition(*pChild);
+ if (bIgnoreSecondaryPacking || !pChild->get_secondary())
+ {
+ //set the max secondary dimension
+ nMainGroupSecondary = std::max(nMainGroupSecondary, getSecondaryDimension(aChildSize));
+ //collect the primary dimensions
+ aMainGroupSizes.push_back(getPrimaryDimension(aChildSize));
+ aMainGroupNonHomogeneous.push_back(pChild->get_non_homogeneous());
+ }
+ else
+ {
+ nSubGroupSecondary = std::max(nSubGroupSecondary, getSecondaryDimension(aChildSize));
+ aSubGroupSizes.push_back(getPrimaryDimension(aChildSize));
+ aSubGroupNonHomogeneous.push_back(pChild->get_non_homogeneous());
+ }
+ }
+
+ if (m_bHomogeneous)
+ {
+ tools::Long nMaxMainDimension = aMainGroupSizes.empty() ? 0 :
+ *std::max_element(aMainGroupSizes.begin(), aMainGroupSizes.end());
+ nMaxMainDimension = std::max(nMaxMainDimension, nMinMainGroupPrimary);
+ tools::Long nMaxSubDimension = aSubGroupSizes.empty() ? 0 :
+ *std::max_element(aSubGroupSizes.begin(), aSubGroupSizes.end());
+ nMaxSubDimension = std::max(nMaxSubDimension, nMinSubGroupPrimary);
+ tools::Long nMaxDimension = std::max(nMaxMainDimension, nMaxSubDimension);
+ aReq.m_aMainGroupDimensions.resize(aMainGroupSizes.size(), nMaxDimension);
+ aReq.m_aSubGroupDimensions.resize(aSubGroupSizes.size(), nMaxDimension);
+ }
+ else
+ {
+ //Ideally set everything to the same size, but find outlier widgets
+ //that are way wider than the average and leave them
+ //at their natural size and set the remainder to share the
+ //max size of the remaining members of the buttonbox
+ tools::Long nAccDimension = std::accumulate(aMainGroupSizes.begin(),
+ aMainGroupSizes.end(), 0);
+ nAccDimension = std::accumulate(aSubGroupSizes.begin(),
+ aSubGroupSizes.end(), nAccDimension);
+
+ size_t nTotalSize = aMainGroupSizes.size() + aSubGroupSizes.size();
+
+ tools::Long nAvgDimension = nTotalSize ? nAccDimension / nTotalSize : 0;
+
+ tools::Long nMaxMainNonOutlier = getMaxNonOutlier(aMainGroupSizes,
+ nAvgDimension);
+ tools::Long nMaxSubNonOutlier = getMaxNonOutlier(aSubGroupSizes,
+ nAvgDimension);
+ tools::Long nMaxNonOutlier = std::max(nMaxMainNonOutlier, nMaxSubNonOutlier);
+
+ aReq.m_aMainGroupDimensions = setButtonSizes(aMainGroupSizes,
+ aMainGroupNonHomogeneous,
+ nAvgDimension, nMaxNonOutlier, nMinMainGroupPrimary);
+ aReq.m_aSubGroupDimensions = setButtonSizes(aSubGroupSizes,
+ aSubGroupNonHomogeneous,
+ nAvgDimension, nMaxNonOutlier, nMinSubGroupPrimary);
+ }
+
+ if (!aReq.m_aMainGroupDimensions.empty())
+ {
+ setSecondaryDimension(aReq.m_aMainGroupSize, nMainGroupSecondary);
+ setPrimaryDimension(aReq.m_aMainGroupSize,
+ std::accumulate(aReq.m_aMainGroupDimensions.begin(),
+ aReq.m_aMainGroupDimensions.end(), 0));
+ }
+ if (!aReq.m_aSubGroupDimensions.empty())
+ {
+ setSecondaryDimension(aReq.m_aSubGroupSize, nSubGroupSecondary);
+ setPrimaryDimension(aReq.m_aSubGroupSize,
+ std::accumulate(aReq.m_aSubGroupDimensions.begin(),
+ aReq.m_aSubGroupDimensions.end(), 0));
+ }
+
+ return aReq;
+}
+
+Size VclButtonBox::addSpacing(const Size &rSize, sal_uInt16 nVisibleChildren) const
+{
+ Size aRet;
+
+ if (nVisibleChildren)
+ {
+ tools::Long nPrimaryDimension = getPrimaryDimension(rSize);
+ setPrimaryDimension(aRet,
+ nPrimaryDimension + m_nSpacing * (nVisibleChildren-1));
+ setSecondaryDimension(aRet, getSecondaryDimension(rSize));
+ }
+
+ return aRet;
+}
+
+Size VclButtonBox::calculateRequisition() const
+{
+ Requisition aReq(calculatePrimarySecondaryRequisitions());
+ sal_uInt16 nVisibleChildren = aReq.m_aMainGroupDimensions.size() +
+ aReq.m_aSubGroupDimensions.size();
+ return addSpacing(addReqGroups(aReq), nVisibleChildren);
+}
+
+bool VclButtonBox::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "layout-style")
+ {
+ VclButtonBoxStyle eStyle = VclButtonBoxStyle::Default;
+ if (rValue == "spread")
+ eStyle = VclButtonBoxStyle::Spread;
+ else if (rValue == "edge")
+ eStyle = VclButtonBoxStyle::Edge;
+ else if (rValue == "start")
+ eStyle = VclButtonBoxStyle::Start;
+ else if (rValue == "end")
+ eStyle = VclButtonBoxStyle::End;
+ else if (rValue == "center")
+ eStyle = VclButtonBoxStyle::Center;
+ else
+ {
+ SAL_WARN("vcl.layout", "unknown layout style " << rValue);
+ }
+ m_eLayoutStyle = eStyle;
+ }
+ else
+ return VclBox::set_property(rKey, rValue);
+ return true;
+}
+
+void VclButtonBox::setAllocation(const Size &rAllocation)
+{
+ Requisition aReq(calculatePrimarySecondaryRequisitions());
+
+ if (aReq.m_aMainGroupDimensions.empty() && aReq.m_aSubGroupDimensions.empty())
+ return;
+
+ tools::Long nAllocPrimaryDimension = getPrimaryDimension(rAllocation);
+
+ Point aMainGroupPos, aOtherGroupPos;
+ int nSpacing = m_nSpacing;
+
+ //To-Do, other layout styles
+ switch (m_eLayoutStyle)
+ {
+ case VclButtonBoxStyle::Start:
+ if (!aReq.m_aSubGroupDimensions.empty())
+ {
+ tools::Long nOtherPrimaryDimension = getPrimaryDimension(
+ addSpacing(aReq.m_aSubGroupSize, aReq.m_aSubGroupDimensions.size()));
+ setPrimaryCoordinate(aOtherGroupPos,
+ nAllocPrimaryDimension - nOtherPrimaryDimension);
+ }
+ break;
+ case VclButtonBoxStyle::Spread:
+ if (!aReq.m_aMainGroupDimensions.empty())
+ {
+ tools::Long nMainPrimaryDimension = getPrimaryDimension(
+ addSpacing(aReq.m_aMainGroupSize, aReq.m_aMainGroupDimensions.size()));
+ tools::Long nExtraSpace = nAllocPrimaryDimension - nMainPrimaryDimension;
+ nExtraSpace += (aReq.m_aMainGroupDimensions.size()-1) * nSpacing;
+ nSpacing = nExtraSpace/(aReq.m_aMainGroupDimensions.size()+1);
+ setPrimaryCoordinate(aMainGroupPos, nSpacing);
+ }
+ break;
+ case VclButtonBoxStyle::Center:
+ if (!aReq.m_aMainGroupDimensions.empty())
+ {
+ tools::Long nMainPrimaryDimension = getPrimaryDimension(
+ addSpacing(aReq.m_aMainGroupSize, aReq.m_aMainGroupDimensions.size()));
+ tools::Long nExtraSpace = nAllocPrimaryDimension - nMainPrimaryDimension;
+ setPrimaryCoordinate(aMainGroupPos, nExtraSpace/2);
+ }
+ break;
+ default:
+ SAL_WARN("vcl.layout", "todo unimplemented layout style");
+ [[fallthrough]];
+ case VclButtonBoxStyle::Default:
+ case VclButtonBoxStyle::End:
+ if (!aReq.m_aMainGroupDimensions.empty())
+ {
+ tools::Long nMainPrimaryDimension = getPrimaryDimension(
+ addSpacing(aReq.m_aMainGroupSize, aReq.m_aMainGroupDimensions.size()));
+ setPrimaryCoordinate(aMainGroupPos,
+ nAllocPrimaryDimension - nMainPrimaryDimension);
+ }
+ break;
+ }
+
+ Size aChildSize;
+ setSecondaryDimension(aChildSize, getSecondaryDimension(rAllocation));
+
+ std::vector<tools::Long>::const_iterator aPrimaryI = aReq.m_aMainGroupDimensions.begin();
+ std::vector<tools::Long>::const_iterator aSecondaryI = aReq.m_aSubGroupDimensions.begin();
+ bool bIgnoreSecondaryPacking = (m_eLayoutStyle == VclButtonBoxStyle::Spread || m_eLayoutStyle == VclButtonBoxStyle::Center);
+ for (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+
+ if (bIgnoreSecondaryPacking || !pChild->get_secondary())
+ {
+ tools::Long nMainGroupPrimaryDimension = *aPrimaryI++;
+ setPrimaryDimension(aChildSize, nMainGroupPrimaryDimension);
+ setLayoutAllocation(*pChild, aMainGroupPos, aChildSize);
+ tools::Long nPrimaryCoordinate = getPrimaryCoordinate(aMainGroupPos);
+ setPrimaryCoordinate(aMainGroupPos, nPrimaryCoordinate + nMainGroupPrimaryDimension + nSpacing);
+ }
+ else
+ {
+ tools::Long nSubGroupPrimaryDimension = *aSecondaryI++;
+ setPrimaryDimension(aChildSize, nSubGroupPrimaryDimension);
+ setLayoutAllocation(*pChild, aOtherGroupPos, aChildSize);
+ tools::Long nPrimaryCoordinate = getPrimaryCoordinate(aOtherGroupPos);
+ setPrimaryCoordinate(aOtherGroupPos, nPrimaryCoordinate + nSubGroupPrimaryDimension + nSpacing);
+ }
+ }
+}
+
+void VclButtonBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ VclBox::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("type", "buttonbox");
+
+ switch(m_eLayoutStyle)
+ {
+ case VclButtonBoxStyle::Default:
+ rJsonWriter.put("layoutstyle", "default");
+ break;
+
+ case VclButtonBoxStyle::Spread:
+ rJsonWriter.put("layoutstyle", "spread");
+ break;
+
+ case VclButtonBoxStyle::Edge:
+ rJsonWriter.put("layoutstyle", "edge");
+ break;
+
+ case VclButtonBoxStyle::Center:
+ rJsonWriter.put("layoutstyle", "center");
+ break;
+
+ case VclButtonBoxStyle::Start:
+ rJsonWriter.put("layoutstyle", "start");
+ break;
+
+ case VclButtonBoxStyle::End:
+ rJsonWriter.put("layoutstyle", "end");
+ break;
+ }
+}
+
+namespace {
+
+struct ButtonOrder
+{
+ std::u16string_view m_aType;
+ int m_nPriority;
+};
+
+}
+
+static int getButtonPriority(std::u16string_view rType)
+{
+ static const size_t N_TYPES = 6;
+ static const ButtonOrder aDiscardCancelSave[N_TYPES] =
+ {
+ { u"discard", 0 },
+ { u"cancel", 1 },
+ { u"no", 2 },
+ { u"save", 3 },
+ { u"yes", 3 },
+ { u"ok", 3 }
+ };
+
+ static const ButtonOrder aSaveDiscardCancel[N_TYPES] =
+ {
+ { u"save", 0 },
+ { u"yes", 0 },
+ { u"ok", 0 },
+ { u"discard", 1 },
+ { u"no", 1 },
+ { u"cancel", 2 }
+ };
+
+ const ButtonOrder* pOrder = &aDiscardCancelSave[0];
+
+ const OUString &rEnv = Application::GetDesktopEnvironment();
+
+ if (rEnv.equalsIgnoreAsciiCase("windows") ||
+ rEnv.equalsIgnoreAsciiCase("lxqt") ||
+ rEnv.startsWithIgnoreAsciiCase("plasma"))
+ {
+ pOrder = &aSaveDiscardCancel[0];
+ }
+
+ for (size_t i = 0; i < N_TYPES; ++i, ++pOrder)
+ {
+ if (rType == pOrder->m_aType)
+ return pOrder->m_nPriority;
+ }
+
+ return -1;
+}
+
+namespace {
+
+class sortButtons
+{
+ bool m_bVerticalContainer;
+public:
+ explicit sortButtons(bool bVerticalContainer)
+ : m_bVerticalContainer(bVerticalContainer)
+ {
+ }
+ bool operator()(const vcl::Window *pA, const vcl::Window *pB) const;
+};
+
+}
+
+bool sortButtons::operator()(const vcl::Window *pA, const vcl::Window *pB) const
+{
+ //sort into two groups of pack start and pack end
+ VclPackType ePackA = pA->get_pack_type();
+ VclPackType ePackB = pB->get_pack_type();
+ if (ePackA < ePackB)
+ return true;
+ if (ePackA > ePackB)
+ return false;
+ bool bPackA = pA->get_secondary();
+ bool bPackB = pB->get_secondary();
+ if (!m_bVerticalContainer)
+ {
+ //for horizontal boxes group secondaries before primaries
+ if (bPackA > bPackB)
+ return true;
+ if (bPackA < bPackB)
+ return false;
+ }
+ else
+ {
+ //for vertical boxes group secondaries after primaries
+ if (bPackA < bPackB)
+ return true;
+ if (bPackA > bPackB)
+ return false;
+ }
+
+ //now order within groups according to platform rules
+ return getButtonPriority(pA->get_id()) < getButtonPriority(pB->get_id());
+}
+
+void sort_native_button_order(const VclBox& rContainer)
+{
+ std::vector<vcl::Window*> aChilds;
+ for (vcl::Window* pChild = rContainer.GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ aChilds.push_back(pChild);
+ }
+
+ //sort child order within parent so that we match the platform
+ //button order
+ std::stable_sort(aChilds.begin(), aChilds.end(), sortButtons(rContainer.get_orientation()));
+ BuilderUtils::reorderWithinParent(aChilds, true);
+}
+
+namespace {
+
+struct GridEntry
+{
+ VclPtr<vcl::Window> pChild;
+ sal_Int32 nSpanWidth;
+ sal_Int32 nSpanHeight;
+ int x;
+ int y;
+ GridEntry()
+ : pChild(nullptr)
+ , nSpanWidth(0)
+ , nSpanHeight(0)
+ , x(-1)
+ , y(-1)
+ {
+ }
+};
+
+}
+
+typedef boost::multi_array<GridEntry, 2> array_type;
+
+static array_type assembleGrid(const VclGrid &rGrid);
+static bool isNullGrid(const array_type& A);
+static void calcMaxs(const array_type &A, std::vector<VclGrid::Value> &rWidths, std::vector<VclGrid::Value> &rHeights);
+
+array_type assembleGrid(const VclGrid &rGrid)
+{
+ array_type A;
+
+ for (vcl::Window* pChild = rGrid.GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ sal_Int32 nLeftAttach = std::max<sal_Int32>(pChild->get_grid_left_attach(), 0);
+ sal_Int32 nWidth = pChild->get_grid_width();
+ sal_Int32 nMaxXPos = nLeftAttach+nWidth-1;
+
+ sal_Int32 nTopAttach = std::max<sal_Int32>(pChild->get_grid_top_attach(), 0);
+ sal_Int32 nHeight = pChild->get_grid_height();
+ sal_Int32 nMaxYPos = nTopAttach+nHeight-1;
+
+ sal_Int32 nCurrentMaxXPos = A.shape()[0]-1;
+ sal_Int32 nCurrentMaxYPos = A.shape()[1]-1;
+ if (nMaxXPos > nCurrentMaxXPos || nMaxYPos > nCurrentMaxYPos)
+ {
+ nCurrentMaxXPos = std::max(nMaxXPos, nCurrentMaxXPos);
+ nCurrentMaxYPos = std::max(nMaxYPos, nCurrentMaxYPos);
+ A.resize(boost::extents[nCurrentMaxXPos+1][nCurrentMaxYPos+1]);
+ }
+
+ GridEntry &rEntry = A[nLeftAttach][nTopAttach];
+ rEntry.pChild = pChild;
+ rEntry.nSpanWidth = nWidth;
+ rEntry.nSpanHeight = nHeight;
+ rEntry.x = nLeftAttach;
+ rEntry.y = nTopAttach;
+
+ for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
+ {
+ for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
+ {
+ GridEntry &rSpan = A[nLeftAttach+nSpanX][nTopAttach+nSpanY];
+ rSpan.x = nLeftAttach;
+ rSpan.y = nTopAttach;
+ }
+ }
+ }
+
+ //see if we have any empty rows/cols
+ sal_Int32 nMaxX = A.shape()[0];
+ sal_Int32 nMaxY = A.shape()[1];
+
+ std::vector<bool> aNonEmptyCols(nMaxX);
+ std::vector<bool> aNonEmptyRows(nMaxY);
+
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ {
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ {
+ const GridEntry &rEntry = A[x][y];
+ const vcl::Window *pChild = rEntry.pChild;
+ if (pChild && pChild->IsVisible())
+ {
+ aNonEmptyCols[x] = true;
+ if (rGrid.get_column_homogeneous())
+ {
+ for (sal_Int32 nSpanX = 1; nSpanX < rEntry.nSpanWidth; ++nSpanX)
+ aNonEmptyCols[x+nSpanX] = true;
+ }
+ aNonEmptyRows[y] = true;
+ if (rGrid.get_row_homogeneous())
+ {
+ for (sal_Int32 nSpanY = 1; nSpanY < rEntry.nSpanHeight; ++nSpanY)
+ aNonEmptyRows[y+nSpanY] = true;
+ }
+ }
+ }
+ }
+
+ if (!rGrid.get_column_homogeneous())
+ {
+ //reduce the spans of elements that span empty columns
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ {
+ std::set<GridEntry*> candidates;
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ {
+ if (aNonEmptyCols[x])
+ continue;
+ GridEntry &rSpan = A[x][y];
+ //cell x/y is spanned by the widget at cell rSpan.x/rSpan.y,
+ //just points back to itself if there's no cell spanning
+ if ((rSpan.x == -1) || (rSpan.y == -1))
+ {
+ //there is no entry for this cell, i.e. this is a cell
+ //with no widget in it, or spanned by any other widget
+ continue;
+ }
+ GridEntry &rEntry = A[rSpan.x][rSpan.y];
+ candidates.insert(&rEntry);
+ }
+ for (auto const& candidate : candidates)
+ {
+ GridEntry *pEntry = candidate;
+ --pEntry->nSpanWidth;
+ }
+ }
+ }
+
+ if (!rGrid.get_row_homogeneous())
+ {
+ //reduce the spans of elements that span empty rows
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ {
+ std::set<GridEntry*> candidates;
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ {
+ if (aNonEmptyRows[y])
+ continue;
+ GridEntry &rSpan = A[x][y];
+ //cell x/y is spanned by the widget at cell rSpan.x/rSpan.y,
+ //just points back to itself if there's no cell spanning
+ if ((rSpan.x == -1) || (rSpan.y == -1))
+ {
+ //there is no entry for this cell, i.e. this is a cell
+ //with no widget in it, or spanned by any other widget
+ continue;
+ }
+ GridEntry &rEntry = A[rSpan.x][rSpan.y];
+ candidates.insert(&rEntry);
+ }
+ for (auto const& candidate : candidates)
+ {
+ GridEntry *pEntry = candidate;
+ --pEntry->nSpanHeight;
+ }
+ }
+ }
+
+ sal_Int32 nNonEmptyCols = std::count(aNonEmptyCols.begin(), aNonEmptyCols.end(), true);
+ sal_Int32 nNonEmptyRows = std::count(aNonEmptyRows.begin(), aNonEmptyRows.end(), true);
+
+ //make new grid without empty rows and columns
+ array_type B(boost::extents[nNonEmptyCols][nNonEmptyRows]);
+ for (sal_Int32 x = 0, x2 = 0; x < nMaxX; ++x)
+ {
+ if (!aNonEmptyCols[x])
+ continue;
+ for (sal_Int32 y = 0, y2 = 0; y < nMaxY; ++y)
+ {
+ if (!aNonEmptyRows[y])
+ continue;
+ GridEntry &rEntry = A[x][y];
+ B[x2][y2++] = rEntry;
+ }
+ ++x2;
+ }
+
+ return B;
+}
+
+static bool isNullGrid(const array_type &A)
+{
+ sal_Int32 nMaxX = A.shape()[0];
+ sal_Int32 nMaxY = A.shape()[1];
+
+ return !nMaxX || !nMaxY;
+}
+
+static void calcMaxs(const array_type &A, std::vector<VclGrid::Value> &rWidths, std::vector<VclGrid::Value> &rHeights)
+{
+ sal_Int32 nMaxX = A.shape()[0];
+ sal_Int32 nMaxY = A.shape()[1];
+
+ rWidths.resize(nMaxX);
+ rHeights.resize(nMaxY);
+
+ //first use the non spanning entries to set default width/heights
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ {
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ {
+ const GridEntry &rEntry = A[x][y];
+ const vcl::Window *pChild = rEntry.pChild;
+ if (!pChild || !pChild->IsVisible())
+ continue;
+
+ sal_Int32 nWidth = rEntry.nSpanWidth;
+ sal_Int32 nHeight = rEntry.nSpanHeight;
+
+ for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
+ rWidths[x+nSpanX].m_bExpand |= pChild->get_hexpand();
+
+ for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
+ rHeights[y+nSpanY].m_bExpand |= pChild->get_vexpand();
+
+ if (nWidth == 1 || nHeight == 1)
+ {
+ Size aChildSize = VclContainer::getLayoutRequisition(*pChild);
+ if (nWidth == 1)
+ rWidths[x].m_nValue = std::max(rWidths[x].m_nValue, aChildSize.Width());
+ if (nHeight == 1)
+ rHeights[y].m_nValue = std::max(rHeights[y].m_nValue, aChildSize.Height());
+ }
+ }
+ }
+
+ //now use the spanning entries and split any extra sizes across expanding rows/cols
+ //where possible
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ {
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ {
+ const GridEntry &rEntry = A[x][y];
+ const vcl::Window *pChild = rEntry.pChild;
+ if (!pChild || !pChild->IsVisible())
+ continue;
+
+ sal_Int32 nWidth = rEntry.nSpanWidth;
+ sal_Int32 nHeight = rEntry.nSpanHeight;
+
+ if (nWidth == 1 && nHeight == 1)
+ continue;
+
+ Size aChildSize = VclContainer::getLayoutRequisition(*pChild);
+
+ if (nWidth > 1)
+ {
+ sal_Int32 nExistingWidth = 0;
+ for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
+ nExistingWidth += rWidths[x+nSpanX].m_nValue;
+
+ sal_Int32 nExtraWidth = aChildSize.Width() - nExistingWidth;
+
+ if (nExtraWidth > 0)
+ {
+ bool bForceExpandAll = false;
+ sal_Int32 nExpandables = 0;
+ for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
+ if (rWidths[x+nSpanX].m_bExpand)
+ ++nExpandables;
+ if (nExpandables == 0)
+ {
+ nExpandables = nWidth;
+ bForceExpandAll = true;
+ }
+
+ for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
+ {
+ if (rWidths[x+nSpanX].m_bExpand || bForceExpandAll)
+ rWidths[x+nSpanX].m_nValue += nExtraWidth/nExpandables;
+ }
+ }
+ }
+
+ if (nHeight > 1)
+ {
+ sal_Int32 nExistingHeight = 0;
+ for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
+ nExistingHeight += rHeights[y+nSpanY].m_nValue;
+
+ sal_Int32 nExtraHeight = aChildSize.Height() - nExistingHeight;
+
+ if (nExtraHeight > 0)
+ {
+ bool bForceExpandAll = false;
+ sal_Int32 nExpandables = 0;
+ for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
+ if (rHeights[y+nSpanY].m_bExpand)
+ ++nExpandables;
+ if (nExpandables == 0)
+ {
+ nExpandables = nHeight;
+ bForceExpandAll = true;
+ }
+
+ for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
+ {
+ if (rHeights[y+nSpanY].m_bExpand || bForceExpandAll)
+ rHeights[y+nSpanY].m_nValue += nExtraHeight/nExpandables;
+ }
+ }
+ }
+ }
+ }
+}
+
+static bool compareValues(const VclGrid::Value &i, const VclGrid::Value &j)
+{
+ return i.m_nValue < j.m_nValue;
+}
+
+static VclGrid::Value accumulateValues(const VclGrid::Value &i, const VclGrid::Value &j)
+{
+ VclGrid::Value aRet;
+ aRet.m_nValue = i.m_nValue + j.m_nValue;
+ aRet.m_bExpand = i.m_bExpand || j.m_bExpand;
+ return aRet;
+}
+
+Size VclGrid::calculateRequisition() const
+{
+ return calculateRequisitionForSpacings(get_row_spacing(), get_column_spacing());
+}
+
+Size VclGrid::calculateRequisitionForSpacings(sal_Int32 nRowSpacing, sal_Int32 nColSpacing) const
+{
+ array_type A = assembleGrid(*this);
+
+ if (isNullGrid(A))
+ return Size();
+
+ std::vector<Value> aWidths;
+ std::vector<Value> aHeights;
+ calcMaxs(A, aWidths, aHeights);
+
+ tools::Long nTotalWidth = 0;
+ if (get_column_homogeneous())
+ {
+ nTotalWidth = std::max_element(aWidths.begin(), aWidths.end(), compareValues)->m_nValue;
+ nTotalWidth *= aWidths.size();
+ }
+ else
+ {
+ nTotalWidth = std::accumulate(aWidths.begin(), aWidths.end(), Value(), accumulateValues).m_nValue;
+ }
+
+ nTotalWidth += nColSpacing * (aWidths.size()-1);
+
+ tools::Long nTotalHeight = 0;
+ if (get_row_homogeneous())
+ {
+ nTotalHeight = std::max_element(aHeights.begin(), aHeights.end(), compareValues)->m_nValue;
+ nTotalHeight *= aHeights.size();
+ }
+ else
+ {
+ nTotalHeight = std::accumulate(aHeights.begin(), aHeights.end(), Value(), accumulateValues).m_nValue;
+ }
+
+ nTotalHeight += nRowSpacing * (aHeights.size()-1);
+
+ return Size(nTotalWidth, nTotalHeight);
+}
+
+void VclGrid::setAllocation(const Size& rAllocation)
+{
+ array_type A = assembleGrid(*this);
+
+ if (isNullGrid(A))
+ return;
+
+ sal_Int32 nMaxX = A.shape()[0];
+ sal_Int32 nMaxY = A.shape()[1];
+
+ Size aRequisition;
+ std::vector<Value> aWidths(nMaxX);
+ std::vector<Value> aHeights(nMaxY);
+ if (!get_column_homogeneous() || !get_row_homogeneous())
+ {
+ aRequisition = calculateRequisition();
+ calcMaxs(A, aWidths, aHeights);
+ }
+
+ sal_Int32 nColSpacing(get_column_spacing());
+ sal_Int32 nRowSpacing(get_row_spacing());
+
+ tools::Long nAvailableWidth = rAllocation.Width();
+ if (nMaxX)
+ nAvailableWidth -= nColSpacing * (nMaxX - 1);
+ if (get_column_homogeneous())
+ {
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ aWidths[x].m_nValue = nAvailableWidth/nMaxX;
+ }
+ else if (rAllocation.Width() != aRequisition.Width())
+ {
+ sal_Int32 nExpandables = 0;
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ if (aWidths[x].m_bExpand)
+ ++nExpandables;
+ tools::Long nExtraWidthForExpanders = nExpandables ? (rAllocation.Width() - aRequisition.Width()) / nExpandables : 0;
+
+ //We don't fit and there is no volunteer to be shrunk
+ if (!nExpandables && rAllocation.Width() < aRequisition.Width())
+ {
+ //first reduce spacing
+ while (nColSpacing)
+ {
+ nColSpacing /= 2;
+ aRequisition = calculateRequisitionForSpacings(nRowSpacing, nColSpacing);
+ if (aRequisition.Width() <= rAllocation.Width())
+ break;
+ }
+
+ //share out the remaining pain to everyone
+ tools::Long nExtraWidth = (rAllocation.Width() - aRequisition.Width()) / nMaxX;
+
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ aWidths[x].m_nValue += nExtraWidth;
+ }
+
+ if (nExtraWidthForExpanders)
+ {
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ if (aWidths[x].m_bExpand)
+ aWidths[x].m_nValue += nExtraWidthForExpanders;
+ }
+ }
+
+ tools::Long nAvailableHeight = rAllocation.Height();
+ if (nMaxY)
+ nAvailableHeight -= nRowSpacing * (nMaxY - 1);
+ if (get_row_homogeneous())
+ {
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ aHeights[y].m_nValue = nAvailableHeight/nMaxY;
+ }
+ else if (rAllocation.Height() != aRequisition.Height())
+ {
+ sal_Int32 nExpandables = 0;
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ if (aHeights[y].m_bExpand)
+ ++nExpandables;
+ tools::Long nExtraHeightForExpanders = nExpandables ? (rAllocation.Height() - aRequisition.Height()) / nExpandables : 0;
+
+ //We don't fit and there is no volunteer to be shrunk
+ if (!nExpandables && rAllocation.Height() < aRequisition.Height())
+ {
+ //first reduce spacing
+ while (nRowSpacing)
+ {
+ nRowSpacing /= 2;
+ aRequisition = calculateRequisitionForSpacings(nRowSpacing, nColSpacing);
+ if (aRequisition.Height() <= rAllocation.Height())
+ break;
+ }
+
+ //share out the remaining pain to everyone
+ tools::Long nExtraHeight = (rAllocation.Height() - aRequisition.Height()) / nMaxY;
+
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ aHeights[y].m_nValue += nExtraHeight;
+ }
+
+ if (nExtraHeightForExpanders)
+ {
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ if (aHeights[y].m_bExpand)
+ aHeights[y].m_nValue += nExtraHeightForExpanders;
+ }
+ }
+
+ Point aAllocPos(0, 0);
+ for (sal_Int32 x = 0; x < nMaxX; ++x)
+ {
+ for (sal_Int32 y = 0; y < nMaxY; ++y)
+ {
+ GridEntry &rEntry = A[x][y];
+ vcl::Window *pChild = rEntry.pChild;
+ if (pChild)
+ {
+ Size aChildAlloc(0, 0);
+
+ sal_Int32 nWidth = rEntry.nSpanWidth;
+ for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
+ aChildAlloc.AdjustWidth(aWidths[x+nSpanX].m_nValue );
+ aChildAlloc.AdjustWidth(nColSpacing*(nWidth-1) );
+
+ sal_Int32 nHeight = rEntry.nSpanHeight;
+ for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
+ aChildAlloc.AdjustHeight(aHeights[y+nSpanY].m_nValue );
+ aChildAlloc.AdjustHeight(nRowSpacing*(nHeight-1) );
+
+ setLayoutAllocation(*pChild, aAllocPos, aChildAlloc);
+ }
+ aAllocPos.AdjustY(aHeights[y].m_nValue + nRowSpacing );
+ }
+ aAllocPos.AdjustX(aWidths[x].m_nValue + nColSpacing );
+ aAllocPos.setY( 0 );
+ }
+}
+
+void VclGrid::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ VclContainer::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("type", "grid");
+}
+
+bool toBool(std::u16string_view rValue)
+{
+ return (!rValue.empty() && (rValue[0] == 't' || rValue[0] == 'T' || rValue[0] == '1'));
+}
+
+bool VclGrid::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "row-spacing")
+ set_row_spacing(rValue.toInt32());
+ else if (rKey == "column-spacing")
+ set_column_spacing(rValue.toInt32());
+ else if (rKey == "row-homogeneous")
+ m_bRowHomogeneous = toBool(rValue);
+ else if (rKey == "column-homogeneous")
+ m_bColumnHomogeneous = toBool(rValue);
+ else if (rKey == "n-rows")
+ /*nothing to do*/;
+ else
+ return VclContainer::set_property(rKey, rValue);
+ return true;
+}
+
+const vcl::Window *VclBin::get_child() const
+{
+ const WindowImpl* pWindowImpl = ImplGetWindowImpl();
+
+ return pWindowImpl->mpFirstChild;
+}
+
+vcl::Window *VclBin::get_child()
+{
+ return const_cast<vcl::Window*>(const_cast<const VclBin*>(this)->get_child());
+}
+
+Size VclBin::calculateRequisition() const
+{
+ const vcl::Window *pChild = get_child();
+ if (pChild && pChild->IsVisible())
+ return getLayoutRequisition(*pChild);
+ return Size(0, 0);
+}
+
+void VclBin::setAllocation(const Size &rAllocation)
+{
+ vcl::Window *pChild = get_child();
+ if (pChild && pChild->IsVisible())
+ setLayoutAllocation(*pChild, Point(0, 0), rAllocation);
+}
+
+VclFrame::~VclFrame()
+{
+ disposeOnce();
+}
+
+void VclFrame::dispose()
+{
+ m_pLabel.clear();
+ VclBin::dispose();
+}
+
+//To-Do, hook a DecorationView into VclFrame ?
+
+Size VclFrame::calculateRequisition() const
+{
+ Size aRet(0, 0);
+
+ const vcl::Window *pChild = get_child();
+ const vcl::Window *pLabel = get_label_widget();
+
+ if (pChild && pChild->IsVisible())
+ aRet = getLayoutRequisition(*pChild);
+
+ if (pLabel && pLabel->IsVisible())
+ {
+ Size aLabelSize = getLayoutRequisition(*pLabel);
+ aRet.AdjustHeight(aLabelSize.Height() );
+ aRet.setWidth( std::max(aLabelSize.Width(), aRet.Width()) );
+ }
+
+ return aRet;
+}
+
+void VclFrame::setAllocation(const Size &rAllocation)
+{
+ //SetBackground( Color(0xFF, 0x00, 0xFF) );
+
+ Size aAllocation(rAllocation);
+ Point aChildPos;
+
+ vcl::Window *pChild = get_child();
+ vcl::Window *pLabel = get_label_widget();
+
+ if (pLabel && pLabel->IsVisible())
+ {
+ Size aLabelSize = getLayoutRequisition(*pLabel);
+ aLabelSize.setHeight( std::min(aLabelSize.Height(), aAllocation.Height()) );
+ aLabelSize.setWidth( std::min(aLabelSize.Width(), aAllocation.Width()) );
+ setLayoutAllocation(*pLabel, aChildPos, aLabelSize);
+ aAllocation.AdjustHeight( -(aLabelSize.Height()) );
+ aChildPos.AdjustY(aLabelSize.Height() );
+ }
+
+ if (pChild && pChild->IsVisible())
+ setLayoutAllocation(*pChild, aChildPos, aAllocation);
+}
+
+IMPL_LINK(VclFrame, WindowEventListener, VclWindowEvent&, rEvent, void)
+{
+ if (rEvent.GetId() == VclEventId::ObjectDying)
+ designate_label(nullptr);
+}
+
+void VclFrame::designate_label(vcl::Window *pWindow)
+{
+ assert(!pWindow || pWindow->GetParent() == this);
+ if (m_pLabel)
+ m_pLabel->RemoveEventListener(LINK(this, VclFrame, WindowEventListener));
+ m_pLabel = pWindow;
+ if (m_pLabel)
+ m_pLabel->AddEventListener(LINK(this, VclFrame, WindowEventListener));
+}
+
+const vcl::Window *VclFrame::get_label_widget() const
+{
+ if (m_pLabel)
+ return m_pLabel;
+ assert(GetChildCount() <= 2);
+ //The label widget is normally the first (of two) children
+ const WindowImpl* pWindowImpl = ImplGetWindowImpl();
+ if (pWindowImpl->mpFirstChild == pWindowImpl->mpLastChild) //no label exists
+ return nullptr;
+ return pWindowImpl->mpFirstChild;
+}
+
+vcl::Window *VclFrame::get_label_widget()
+{
+ return const_cast<vcl::Window*>(const_cast<const VclFrame*>(this)->get_label_widget());
+}
+
+const vcl::Window *VclFrame::get_child() const
+{
+ //The child widget is the normally the last (of two) children
+ const WindowImpl* pWindowImpl = ImplGetWindowImpl();
+ assert(GetChildCount() == 2 || pWindowImpl->mbInDispose);
+ if (!m_pLabel)
+ return pWindowImpl->mpLastChild;
+ if (pWindowImpl->mpFirstChild == pWindowImpl->mpLastChild) //only label exists
+ return nullptr;
+ return pWindowImpl->mpLastChild;
+}
+
+vcl::Window *VclFrame::get_child()
+{
+ return const_cast<vcl::Window*>(const_cast<const VclFrame*>(this)->get_child());
+}
+
+void VclFrame::set_label(const OUString &rLabel)
+{
+ vcl::Window *pLabel = get_label_widget();
+ assert(pLabel);
+ pLabel->SetText(rLabel);
+}
+
+OUString VclFrame::get_label() const
+{
+ const vcl::Window *pLabel = get_label_widget();
+ assert(pLabel);
+ return pLabel->GetText();
+}
+
+OUString VclFrame::getDefaultAccessibleName() const
+{
+ const vcl::Window *pLabel = get_label_widget();
+ if (pLabel)
+ return pLabel->GetAccessibleName();
+ return VclBin::getDefaultAccessibleName();
+}
+
+void VclFrame::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ VclBin::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("type", "frame");
+}
+
+class DisclosureButton final : public CheckBox
+{
+ virtual void ImplDrawCheckBoxState(vcl::RenderContext& rRenderContext) override
+ {
+ /* HACK: DisclosureButton is currently assuming, that the disclosure sign
+ will fit into the rectangle occupied by a normal checkbox on all themes.
+ If this does not hold true for some theme, ImplGetCheckImageSize
+ would have to be overridden for DisclosureButton; also GetNativeControlRegion
+ for ControlType::ListNode would have to be implemented and taken into account
+ */
+
+ tools::Rectangle aStateRect(GetStateRect());
+
+ ImplControlValue aControlValue(GetState() == TRISTATE_TRUE ? ButtonValue::On : ButtonValue::Off);
+ tools::Rectangle aCtrlRegion(aStateRect);
+ ControlState nState = ControlState::NONE;
+
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+ if (GetButtonState() & DrawButtonFlags::Default)
+ nState |= ControlState::DEFAULT;
+ if (Window::IsEnabled())
+ nState |= ControlState::ENABLED;
+ if (IsMouseOver() && GetMouseRect().Contains(GetPointerPosPixel()))
+ nState |= ControlState::ROLLOVER;
+
+ if (rRenderContext.DrawNativeControl(ControlType::ListNode, ControlPart::Entire, aCtrlRegion,
+ nState, aControlValue, OUString()))
+ return;
+
+ ImplSVCtrlData& rCtrlData(ImplGetSVData()->maCtrlData);
+ if (!rCtrlData.mpDisclosurePlus)
+ rCtrlData.mpDisclosurePlus.reset(new Image(StockImage::Yes, SV_DISCLOSURE_PLUS));
+ if (!rCtrlData.mpDisclosureMinus)
+ rCtrlData.mpDisclosureMinus.reset(new Image(StockImage::Yes, SV_DISCLOSURE_MINUS));
+
+ Image* pImg
+ = IsChecked() ? rCtrlData.mpDisclosureMinus.get() : rCtrlData.mpDisclosurePlus.get();
+
+ DrawImageFlags nStyle = DrawImageFlags::NONE;
+ if (!IsEnabled())
+ nStyle |= DrawImageFlags::Disable;
+
+ Size aSize(aStateRect.GetSize());
+ Size aImgSize(pImg->GetSizePixel());
+ Point aOff((aSize.Width() - aImgSize.Width()) / 2,
+ (aSize.Height() - aImgSize.Height()) / 2);
+ aOff += aStateRect.TopLeft();
+ rRenderContext.DrawImage(aOff, *pImg, nStyle);
+ }
+
+public:
+ explicit DisclosureButton(vcl::Window* pParent)
+ : CheckBox(pParent, 0)
+ {
+ }
+
+ virtual void KeyInput( const KeyEvent& rKEvt ) override
+ {
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ if( !aKeyCode.GetModifier() &&
+ ( ( aKeyCode.GetCode() == KEY_ADD ) ||
+ ( aKeyCode.GetCode() == KEY_SUBTRACT ) )
+ )
+ {
+ Check( aKeyCode.GetCode() == KEY_ADD );
+ }
+ else
+ CheckBox::KeyInput( rKEvt );
+ }
+};
+
+VclExpander::VclExpander(vcl::Window *pParent)
+ : VclBin(pParent)
+ , m_bResizeTopLevel(false)
+ , m_pDisclosureButton(VclPtr<DisclosureButton>::Create(this))
+{
+ m_pDisclosureButton->SetToggleHdl(LINK(this, VclExpander, ClickHdl));
+ m_pDisclosureButton->Show();
+}
+
+VclExpander::~VclExpander()
+{
+ disposeOnce();
+}
+
+bool VclExpander::get_expanded() const
+{
+ return m_pDisclosureButton->IsChecked();
+}
+
+void VclExpander::set_expanded(bool bExpanded)
+{
+ m_pDisclosureButton->Check(bExpanded);
+}
+
+void VclExpander::set_label(const OUString& rLabel)
+{
+ m_pDisclosureButton->SetText(rLabel);
+}
+
+OUString VclExpander::get_label() const
+{
+ return m_pDisclosureButton->GetText();
+}
+
+void VclExpander::dispose()
+{
+ m_pDisclosureButton.disposeAndClear();
+ VclBin::dispose();
+}
+
+const vcl::Window *VclExpander::get_child() const
+{
+ const WindowImpl* pWindowImpl = ImplGetWindowImpl();
+
+ assert(pWindowImpl->mpFirstChild == m_pDisclosureButton);
+
+ return pWindowImpl->mpFirstChild->GetWindow(GetWindowType::Next);
+}
+
+vcl::Window *VclExpander::get_child()
+{
+ return const_cast<vcl::Window*>(const_cast<const VclExpander*>(this)->get_child());
+}
+
+Size VclExpander::calculateRequisition() const
+{
+ Size aRet(0, 0);
+
+ WindowImpl* pWindowImpl = ImplGetWindowImpl();
+
+ const vcl::Window *pChild = get_child();
+ const vcl::Window *pLabel = pChild != pWindowImpl->mpLastChild ? pWindowImpl->mpLastChild.get() : nullptr;
+
+ if (pChild && pChild->IsVisible() && m_pDisclosureButton->IsChecked())
+ aRet = getLayoutRequisition(*pChild);
+
+ Size aExpanderSize = getLayoutRequisition(*m_pDisclosureButton);
+
+ if (pLabel && pLabel->IsVisible())
+ {
+ Size aLabelSize = getLayoutRequisition(*pLabel);
+ aExpanderSize.setHeight( std::max(aExpanderSize.Height(), aLabelSize.Height()) );
+ aExpanderSize.AdjustWidth(aLabelSize.Width() );
+ }
+
+ aRet.AdjustHeight(aExpanderSize.Height() );
+ aRet.setWidth( std::max(aExpanderSize.Width(), aRet.Width()) );
+
+ return aRet;
+}
+
+void VclExpander::setAllocation(const Size &rAllocation)
+{
+ Size aAllocation(rAllocation);
+ Point aChildPos;
+
+ WindowImpl* pWindowImpl = ImplGetWindowImpl();
+
+ //The label widget is the last (of two) children
+ vcl::Window *pChild = get_child();
+ vcl::Window *pLabel = pChild != pWindowImpl->mpLastChild.get() ? pWindowImpl->mpLastChild.get() : nullptr;
+
+ Size aButtonSize = getLayoutRequisition(*m_pDisclosureButton);
+ Size aLabelSize;
+ Size aExpanderSize = aButtonSize;
+ if (pLabel && pLabel->IsVisible())
+ {
+ aLabelSize = getLayoutRequisition(*pLabel);
+ aExpanderSize.setHeight( std::max(aExpanderSize.Height(), aLabelSize.Height()) );
+ aExpanderSize.AdjustWidth(aLabelSize.Width() );
+ }
+
+ aExpanderSize.setHeight( std::min(aExpanderSize.Height(), aAllocation.Height()) );
+ aExpanderSize.setWidth( std::min(aExpanderSize.Width(), aAllocation.Width()) );
+
+ aButtonSize.setHeight( std::min(aButtonSize.Height(), aExpanderSize.Height()) );
+ aButtonSize.setWidth( std::min(aButtonSize.Width(), aExpanderSize.Width()) );
+
+ tools::Long nExtraExpanderHeight = aExpanderSize.Height() - aButtonSize.Height();
+ Point aButtonPos(aChildPos.X(), aChildPos.Y() + nExtraExpanderHeight/2);
+ setLayoutAllocation(*m_pDisclosureButton, aButtonPos, aButtonSize);
+
+ if (pLabel && pLabel->IsVisible())
+ {
+ aLabelSize.setHeight( std::min(aLabelSize.Height(), aExpanderSize.Height()) );
+ aLabelSize.setWidth( std::min(aLabelSize.Width(),
+ aExpanderSize.Width() - aButtonSize.Width()) );
+
+ tools::Long nExtraLabelHeight = aExpanderSize.Height() - aLabelSize.Height();
+ Point aLabelPos(aChildPos.X() + aButtonSize.Width(), aChildPos.Y() + nExtraLabelHeight/2);
+ setLayoutAllocation(*pLabel, aLabelPos, aLabelSize);
+ }
+
+ aAllocation.AdjustHeight( -(aExpanderSize.Height()) );
+ aChildPos.AdjustY(aExpanderSize.Height() );
+
+ if (pChild && pChild->IsVisible())
+ {
+ if (!m_pDisclosureButton->IsChecked())
+ aAllocation = Size();
+ setLayoutAllocation(*pChild, aChildPos, aAllocation);
+ }
+}
+
+bool VclExpander::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "expanded")
+ set_expanded(toBool(rValue));
+ else if (rKey == "resize-toplevel")
+ m_bResizeTopLevel = toBool(rValue);
+ else
+ return VclBin::set_property(rKey, rValue);
+ return true;
+}
+
+void VclExpander::StateChanged(StateChangedType nType)
+{
+ VclBin::StateChanged( nType );
+
+ if (nType == StateChangedType::InitShow)
+ {
+ vcl::Window *pChild = get_child();
+ if (pChild)
+ pChild->Show(m_pDisclosureButton->IsChecked());
+ }
+}
+
+const vcl::Window *VclExpander::get_label_widget() const
+{
+ return m_pDisclosureButton;
+}
+
+vcl::Window *VclExpander::get_label_widget()
+{
+ return const_cast<vcl::Window*>(const_cast<const VclExpander*>(this)->get_label_widget());
+}
+
+void VclExpander::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ VclContainer::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("type", "expander");
+}
+
+FactoryFunction VclExpander::GetUITestFactory() const
+{
+ return ExpanderUIObject::create;
+}
+
+IMPL_LINK( VclExpander, ClickHdl, CheckBox&, rBtn, void )
+{
+ vcl::Window *pChild = get_child();
+ if (pChild)
+ {
+ pChild->Show(rBtn.IsChecked());
+ queue_resize();
+ Dialog* pResizeDialog = m_bResizeTopLevel ? GetParentDialog() : nullptr;
+ if (pResizeDialog)
+ pResizeDialog->setOptimalLayoutSize(true);
+ }
+ maExpandedHdl.Call(*this);
+}
+
+VclScrolledWindow::VclScrolledWindow(vcl::Window *pParent)
+ : VclBin(pParent, WB_HIDE | WB_CLIPCHILDREN | WB_AUTOHSCROLL | WB_AUTOVSCROLL | WB_TABSTOP)
+ , m_bUserManagedScrolling(false)
+ , m_eDrawFrameStyle(DrawFrameStyle::NONE)
+ , m_eDrawFrameFlags(DrawFrameFlags::WindowBorder)
+ , m_pVScroll(VclPtr<ScrollBar>::Create(this, WB_HIDE | WB_VERT))
+ , m_pHScroll(VclPtr<ScrollBar>::Create(this, WB_HIDE | WB_HORZ))
+ , m_aScrollBarBox(VclPtr<ScrollBarBox>::Create(this, WB_HIDE))
+{
+ SetType(WindowType::SCROLLWINDOW);
+
+ AllSettings aAllSettings = GetSettings();
+ StyleSettings aStyle = aAllSettings.GetStyleSettings();
+ aStyle.SetMonoColor(aStyle.GetShadowColor());
+ aAllSettings.SetStyleSettings(aStyle);
+ GetOutDev()->SetSettings(aAllSettings);
+
+ Link<ScrollBar*,void> aLink( LINK( this, VclScrolledWindow, ScrollBarHdl ) );
+ m_pVScroll->SetScrollHdl(aLink);
+ m_pHScroll->SetScrollHdl(aLink);
+
+ m_nBorderWidth = CalcBorderWidth();
+}
+
+int VclScrolledWindow::CalcBorderWidth() const
+{
+ if (m_eDrawFrameStyle == DrawFrameStyle::NONE)
+ return 0;
+ const tools::Rectangle aRect(tools::Rectangle(Point(0, 0), Size(100, 100)));
+ DecorationView aDecoView(const_cast<OutputDevice*>(GetOutDev()));
+ // don't actually draw anything, just measure what size it would be and the diff is the desired border size to reserve
+ const tools::Rectangle aContentRect = aDecoView.DrawFrame(aRect, m_eDrawFrameStyle, m_eDrawFrameFlags | DrawFrameFlags::NoDraw);
+ const auto nBorderWidth = (aRect.GetWidth() - aContentRect.GetWidth()) / 2;
+ return std::max<int>(nBorderWidth, 1);
+}
+
+void VclScrolledWindow::dispose()
+{
+ m_pVScroll.disposeAndClear();
+ m_pHScroll.disposeAndClear();
+ m_aScrollBarBox.disposeAndClear();
+ VclBin::dispose();
+}
+
+IMPL_LINK_NOARG(VclScrolledWindow, ScrollBarHdl, ScrollBar*, void)
+{
+ vcl::Window *pChild = get_child();
+ if (!pChild)
+ return;
+
+ assert(dynamic_cast<VclViewport*>(pChild) && "scrolledwindow child should be a Viewport");
+
+ pChild = pChild->GetWindow(GetWindowType::FirstChild);
+
+ if (!pChild)
+ return;
+
+ Point aWinPos(-m_pHScroll->GetThumbPos(), -m_pVScroll->GetThumbPos());
+ pChild->SetPosPixel(aWinPos);
+}
+
+const vcl::Window *VclScrolledWindow::get_child() const
+{
+ const WindowImpl* pWindowImpl = ImplGetWindowImpl();
+ assert(GetChildCount() == 4 || pWindowImpl->mbInDispose);
+ return pWindowImpl->mpLastChild;
+}
+
+vcl::Window *VclScrolledWindow::get_child()
+{
+ return const_cast<vcl::Window*>(const_cast<const VclScrolledWindow*>(this)->get_child());
+}
+
+Size VclScrolledWindow::calculateRequisition() const
+{
+ Size aRet(0, 0);
+
+ const vcl::Window *pChild = get_child();
+ if (pChild && pChild->IsVisible())
+ aRet = getLayoutRequisition(*pChild);
+
+ if (GetStyle() & WB_VSCROLL)
+ aRet.AdjustWidth(getLayoutRequisition(*m_pVScroll).Width() );
+
+ if (GetStyle() & WB_HSCROLL)
+ aRet.AdjustHeight(getLayoutRequisition(*m_pHScroll).Height() );
+
+ aRet.AdjustHeight(2 * m_nBorderWidth);
+ aRet.AdjustWidth(2 * m_nBorderWidth);
+
+ return aRet;
+}
+
+void VclScrolledWindow::InitScrollBars(const Size &rRequest)
+{
+ const vcl::Window *pChild = get_child();
+ if (!pChild || !pChild->IsVisible())
+ return;
+
+ Size aOutSize(getVisibleChildSize());
+
+ m_pVScroll->SetRangeMax(rRequest.Height());
+ m_pVScroll->SetVisibleSize(aOutSize.Height());
+ m_pVScroll->SetPageSize(16);
+
+ m_pHScroll->SetRangeMax(rRequest.Width());
+ m_pHScroll->SetVisibleSize(aOutSize.Width());
+ m_pHScroll->SetPageSize(16);
+
+ m_pVScroll->Scroll();
+ m_pHScroll->Scroll();
+}
+
+void VclScrolledWindow::doSetAllocation(const Size &rAllocation, bool bRetryOnFailure)
+{
+ Size aChildReq;
+
+ vcl::Window *pChild = get_child();
+ if (pChild && pChild->IsVisible())
+ aChildReq = getLayoutRequisition(*pChild);
+
+ tools::Long nAvailHeight = rAllocation.Height() - 2 * m_nBorderWidth;
+ tools::Long nAvailWidth = rAllocation.Width() - 2 * m_nBorderWidth;
+
+ // vert. ScrollBar
+ bool bShowVScroll;
+ if (GetStyle() & WB_AUTOVSCROLL)
+ bShowVScroll = nAvailHeight < aChildReq.Height();
+ else
+ bShowVScroll = (GetStyle() & WB_VSCROLL) != 0;
+
+ if (bShowVScroll)
+ nAvailWidth -= getLayoutRequisition(*m_pVScroll).Width();
+
+ // horz. ScrollBar
+ bool bShowHScroll;
+ if (GetStyle() & WB_AUTOHSCROLL)
+ {
+ bShowHScroll = nAvailWidth < aChildReq.Width();
+
+ if (bShowHScroll)
+ nAvailHeight -= getLayoutRequisition(*m_pHScroll).Height();
+
+ if (GetStyle() & WB_AUTOVSCROLL)
+ bShowVScroll = nAvailHeight < aChildReq.Height();
+ }
+ else
+ bShowHScroll = (GetStyle() & WB_HSCROLL) != 0;
+
+ if (m_pHScroll->IsVisible() != bShowHScroll)
+ m_pHScroll->Show(bShowHScroll);
+ if (m_pVScroll->IsVisible() != bShowVScroll)
+ m_pVScroll->Show(bShowVScroll);
+
+ Size aInnerSize(rAllocation);
+ aInnerSize.AdjustWidth(-2 * m_nBorderWidth);
+ aInnerSize.AdjustHeight(-2 * m_nBorderWidth);
+
+ bool bBothVisible = m_pVScroll->IsVisible() && m_pHScroll->IsVisible();
+ auto nScrollBarWidth = getLayoutRequisition(*m_pVScroll).Width();
+ auto nScrollBarHeight = getLayoutRequisition(*m_pHScroll).Height();
+
+ if (m_pVScroll->IsVisible())
+ {
+ Point aScrollPos(rAllocation.Width() - nScrollBarWidth - m_nBorderWidth, m_nBorderWidth);
+ Size aScrollSize(nScrollBarWidth, rAllocation.Height() - 2 * m_nBorderWidth);
+ if (bBothVisible)
+ aScrollSize.AdjustHeight(-nScrollBarHeight);
+ setLayoutAllocation(*m_pVScroll, aScrollPos, aScrollSize);
+ aInnerSize.AdjustWidth( -nScrollBarWidth );
+ }
+
+ if (m_pHScroll->IsVisible())
+ {
+ Point aScrollPos(m_nBorderWidth, rAllocation.Height() - nScrollBarHeight);
+ Size aScrollSize(rAllocation.Width() - 2 * m_nBorderWidth, nScrollBarHeight);
+ if (bBothVisible)
+ aScrollSize.AdjustWidth(-nScrollBarWidth);
+ setLayoutAllocation(*m_pHScroll, aScrollPos, aScrollSize);
+ aInnerSize.AdjustHeight( -nScrollBarHeight );
+ }
+
+ if (bBothVisible)
+ {
+ Point aBoxPos(aInnerSize.Width() + m_nBorderWidth, aInnerSize.Height() + m_nBorderWidth);
+ m_aScrollBarBox->SetPosSizePixel(aBoxPos, Size(nScrollBarWidth, nScrollBarHeight));
+ m_aScrollBarBox->Show();
+ }
+ else
+ {
+ m_aScrollBarBox->Hide();
+ }
+
+ if (pChild && pChild->IsVisible())
+ {
+ assert(dynamic_cast<VclViewport*>(pChild) && "scrolledwindow child should be a Viewport");
+
+ WinBits nOldBits = (GetStyle() & (WB_AUTOVSCROLL | WB_VSCROLL | WB_AUTOHSCROLL | WB_HSCROLL));
+
+ setLayoutAllocation(*pChild, Point(m_nBorderWidth, m_nBorderWidth), aInnerSize);
+
+ // tdf#128758 if the layout allocation triggered some callback that
+ // immediately invalidates the layout by adding scrollbars then
+ // normally this would simply retrigger layout and another toplevel
+ // attempt is made later. But the initial layout attempt blocks
+ // relayouts, so just make another single effort here.
+ WinBits nNewBits = (GetStyle() & (WB_AUTOVSCROLL | WB_VSCROLL | WB_AUTOHSCROLL | WB_HSCROLL));
+ if (nOldBits != nNewBits && bRetryOnFailure)
+ {
+ doSetAllocation(rAllocation, false);
+ return;
+ }
+ }
+
+ if (!m_bUserManagedScrolling)
+ InitScrollBars(aChildReq);
+}
+
+void VclScrolledWindow::setAllocation(const Size &rAllocation)
+{
+ doSetAllocation(rAllocation, true);
+}
+
+Size VclScrolledWindow::getVisibleChildSize() const
+{
+ Size aRet(GetSizePixel());
+ if (m_pVScroll->IsVisible())
+ aRet.AdjustWidth( -(m_pVScroll->GetSizePixel().Width()) );
+ if (m_pHScroll->IsVisible())
+ aRet.AdjustHeight( -(m_pHScroll->GetSizePixel().Height()) );
+ aRet.AdjustHeight(-2 * m_nBorderWidth);
+ aRet.AdjustWidth(-2 * m_nBorderWidth);
+ return aRet;
+}
+
+bool VclScrolledWindow::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "shadow-type" || rKey == "name")
+ {
+ if (rKey == "shadow-type")
+ {
+ // despite the style names, this looks like the best mapping
+ if (rValue == "in")
+ m_eDrawFrameStyle = DrawFrameStyle::Out;
+ else if (rValue == "out")
+ m_eDrawFrameStyle = DrawFrameStyle::In;
+ else if (rValue == "etched-in")
+ m_eDrawFrameStyle = DrawFrameStyle::DoubleOut;
+ else if (rValue == "etched-out")
+ m_eDrawFrameStyle = DrawFrameStyle::DoubleIn;
+ else if (rValue == "none")
+ m_eDrawFrameStyle = DrawFrameStyle::NONE;
+ }
+ else if (rKey == "name")
+ {
+ m_eDrawFrameFlags = DrawFrameFlags::WindowBorder;
+ if (rValue == "monoborder")
+ m_eDrawFrameFlags |= DrawFrameFlags::Mono;
+ }
+
+ auto nBorderWidth = CalcBorderWidth();
+ if (m_nBorderWidth != nBorderWidth)
+ {
+ m_nBorderWidth = nBorderWidth;
+ queue_resize();
+ }
+
+ return true;
+ }
+
+ bool bRet = VclBin::set_property(rKey, rValue);
+ m_pVScroll->Show((GetStyle() & WB_VSCROLL) != 0);
+ m_pHScroll->Show((GetStyle() & WB_HSCROLL) != 0);
+ return bRet;
+}
+
+bool VclScrolledWindow::EventNotify(NotifyEvent& rNEvt)
+{
+ bool bDone = false;
+ if ( rNEvt.GetType() == MouseNotifyEvent::COMMAND )
+ {
+ const CommandEvent& rCEvt = *rNEvt.GetCommandEvent();
+ if ( rCEvt.GetCommand() == CommandEventId::Wheel )
+ {
+ const CommandWheelData* pData = rCEvt.GetWheelData();
+ if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) )
+ {
+ // tdf#140537 only handle scroll commands in the valid shown scrollbars
+ bDone = HandleScrollCommand(rCEvt,
+ m_pHScroll->IsVisible() ? m_pHScroll : nullptr,
+ m_pVScroll->IsVisible() ? m_pVScroll : nullptr);
+ }
+ }
+ }
+
+ return bDone || VclBin::EventNotify( rNEvt );
+}
+
+void VclScrolledWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ VclBin::Paint(rRenderContext, rRect);
+ if (m_eDrawFrameStyle == DrawFrameStyle::NONE)
+ return;
+ const tools::Rectangle aRect(tools::Rectangle(Point(0,0), GetSizePixel()));
+ DecorationView aDecoView(&rRenderContext);
+ const tools::Rectangle aContentRect = aDecoView.DrawFrame(aRect, m_eDrawFrameStyle, m_eDrawFrameFlags);
+ const auto nBorderWidth = (aRect.GetWidth() - aContentRect.GetWidth()) / 2;
+ SAL_WARN_IF(nBorderWidth > m_nBorderWidth, "vcl.layout", "desired border at paint " <<
+ nBorderWidth << " is larger than expected " << m_nBorderWidth);
+}
+
+void VclViewport::setAllocation(const Size &rAllocation)
+{
+ vcl::Window *pChild = get_child();
+ if (!(pChild && pChild->IsVisible()))
+ return;
+
+ Size aReq(getLayoutRequisition(*pChild));
+ aReq.setWidth( std::max(aReq.Width(), rAllocation.Width()) );
+ aReq.setHeight( std::max(aReq.Height(), rAllocation.Height()) );
+ Point aKeepPos(pChild->GetPosPixel());
+ if (m_bInitialAllocation)
+ {
+ aKeepPos = Point(0, 0);
+ m_bInitialAllocation = false;
+ }
+ setLayoutAllocation(*pChild, aKeepPos, aReq);
+}
+
+const vcl::Window *VclEventBox::get_child() const
+{
+ const WindowImpl* pWindowImpl = ImplGetWindowImpl();
+
+ assert(pWindowImpl->mpFirstChild.get() == m_aEventBoxHelper.get());
+
+ return pWindowImpl->mpFirstChild->GetWindow(GetWindowType::Next);
+}
+
+vcl::Window *VclEventBox::get_child()
+{
+ return const_cast<vcl::Window*>(const_cast<const VclEventBox*>(this)->get_child());
+}
+
+void VclEventBox::setAllocation(const Size& rAllocation)
+{
+ Point aChildPos(0, 0);
+ for (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ setLayoutAllocation(*pChild, aChildPos, rAllocation);
+ }
+}
+
+Size VclEventBox::calculateRequisition() const
+{
+ Size aRet(0, 0);
+
+ for (const vcl::Window* pChild = get_child(); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ Size aChildSize = getLayoutRequisition(*pChild);
+ aRet.setWidth( std::max(aRet.Width(), aChildSize.Width()) );
+ aRet.setHeight( std::max(aRet.Height(), aChildSize.Height()) );
+ }
+
+ return aRet;
+}
+
+void VclEventBox::Command(const CommandEvent&)
+{
+ //discard events by default to block them reaching children
+}
+
+VclEventBox::~VclEventBox()
+{
+ disposeOnce();
+}
+
+void VclEventBox::dispose()
+{
+ m_aEventBoxHelper.disposeAndClear();
+ VclBin::dispose();
+}
+
+void VclSizeGroup::trigger_queue_resize()
+{
+ //sufficient to trigger one widget to trigger all of them
+ if (!m_aWindows.empty())
+ {
+ (*m_aWindows.begin())->queue_resize();
+ }
+}
+
+void VclSizeGroup::set_ignore_hidden(bool bIgnoreHidden)
+{
+ if (bIgnoreHidden != m_bIgnoreHidden)
+ {
+ m_bIgnoreHidden = bIgnoreHidden;
+ trigger_queue_resize();
+ }
+}
+
+void VclSizeGroup::set_mode(VclSizeGroupMode eMode)
+{
+ if (eMode != m_eMode)
+ {
+ m_eMode = eMode;
+ trigger_queue_resize();
+ }
+
+}
+
+void VclSizeGroup::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "ignore-hidden")
+ set_ignore_hidden(toBool(rValue));
+ else if (rKey == "mode")
+ {
+ VclSizeGroupMode eMode = VclSizeGroupMode::Horizontal;
+ if (rValue == "none")
+ eMode = VclSizeGroupMode::NONE;
+ else if (rValue == "horizontal")
+ eMode = VclSizeGroupMode::Horizontal;
+ else if (rValue == "vertical")
+ eMode = VclSizeGroupMode::Vertical;
+ else if (rValue == "both")
+ eMode = VclSizeGroupMode::Both;
+ else
+ {
+ SAL_WARN("vcl.layout", "unknown size group mode" << rValue);
+ }
+ set_mode(eMode);
+ }
+ else
+ {
+ SAL_INFO("vcl.layout", "unhandled property: " << rKey);
+ }
+}
+
+void MessageDialog::create_message_area()
+{
+ setDeferredProperties();
+
+ if (m_pGrid)
+ return;
+
+ VclContainer *pContainer = get_content_area();
+ assert(pContainer);
+
+ m_pGrid.set( VclPtr<VclGrid>::Create(pContainer) );
+ m_pGrid->reorderWithinParent(0);
+ m_pGrid->set_column_spacing(12);
+ m_pMessageBox.set(VclPtr<VclVBox>::Create(m_pGrid));
+ m_pMessageBox->set_grid_left_attach(1);
+ m_pMessageBox->set_grid_top_attach(0);
+ m_pMessageBox->set_spacing(GetTextHeight());
+
+ m_pImage = VclPtr<FixedImage>::Create(m_pGrid, WB_CENTER | WB_VCENTER | WB_3DLOOK);
+ switch (m_eMessageType)
+ {
+ case VclMessageType::Info:
+ m_pImage->SetImage(GetStandardInfoBoxImage());
+ break;
+ case VclMessageType::Warning:
+ m_pImage->SetImage(GetStandardWarningBoxImage());
+ break;
+ case VclMessageType::Question:
+ m_pImage->SetImage(GetStandardQueryBoxImage());
+ break;
+ case VclMessageType::Error:
+ m_pImage->SetImage(GetStandardErrorBoxImage());
+ break;
+ case VclMessageType::Other:
+ break;
+ }
+ m_pImage->set_grid_left_attach(0);
+ m_pImage->set_grid_top_attach(0);
+ m_pImage->set_valign(VclAlign::Start);
+ m_pImage->Show(m_eMessageType != VclMessageType::Other);
+
+ WinBits nWinStyle = WB_CLIPCHILDREN | WB_LEFT | WB_VCENTER | WB_NOLABEL | WB_NOTABSTOP;
+
+ bool bHasSecondaryText = !m_sSecondaryString.isEmpty();
+
+ m_pPrimaryMessage = VclPtr<VclMultiLineEdit>::Create(m_pMessageBox, nWinStyle);
+ m_pPrimaryMessage->SetPaintTransparent(true);
+ m_pPrimaryMessage->EnableCursor(false);
+
+ m_pPrimaryMessage->set_hexpand(true);
+ m_pPrimaryMessage->SetText(m_sPrimaryString);
+ m_pPrimaryMessage->Show(!m_sPrimaryString.isEmpty());
+
+ m_pSecondaryMessage = VclPtr<VclMultiLineEdit>::Create(m_pMessageBox, nWinStyle);
+ m_pSecondaryMessage->SetPaintTransparent(true);
+ m_pSecondaryMessage->EnableCursor(false);
+ m_pSecondaryMessage->set_hexpand(true);
+ m_pSecondaryMessage->SetText(m_sSecondaryString);
+ m_pSecondaryMessage->Show(bHasSecondaryText);
+
+ MessageDialog::SetMessagesWidths(this, m_pPrimaryMessage, bHasSecondaryText ? m_pSecondaryMessage.get() : nullptr);
+
+ VclButtonBox *pButtonBox = get_action_area();
+ assert(pButtonBox);
+
+ VclPtr<PushButton> pBtn;
+ short nDefaultResponse = get_default_response();
+ switch (m_eButtonsType)
+ {
+ case VclButtonsType::NONE:
+ break;
+ case VclButtonsType::Ok:
+ pBtn.set( VclPtr<OKButton>::Create(pButtonBox) );
+ pBtn->SetStyle(pBtn->GetStyle() & WB_DEFBUTTON);
+ pBtn->Show();
+ pBtn->set_id("ok");
+ add_button(pBtn, RET_OK, true);
+ nDefaultResponse = RET_OK;
+ break;
+ case VclButtonsType::Close:
+ pBtn.set( VclPtr<CloseButton>::Create(pButtonBox) );
+ pBtn->SetStyle(pBtn->GetStyle() & WB_DEFBUTTON);
+ pBtn->Show();
+ pBtn->set_id("close");
+ add_button(pBtn, RET_CLOSE, true);
+ nDefaultResponse = RET_CLOSE;
+ break;
+ case VclButtonsType::Cancel:
+ pBtn.set( VclPtr<CancelButton>::Create(pButtonBox) );
+ pBtn->SetStyle(pBtn->GetStyle() & WB_DEFBUTTON);
+ pBtn->Show();
+ pBtn->set_id("cancel");
+ add_button(pBtn, RET_CANCEL, true);
+ nDefaultResponse = RET_CANCEL;
+ break;
+ case VclButtonsType::YesNo:
+ pBtn = VclPtr<PushButton>::Create(pButtonBox);
+ pBtn->SetText(GetStandardText(StandardButtonType::Yes));
+ pBtn->Show();
+ pBtn->set_id("yes");
+ add_button(pBtn, RET_YES, true);
+
+ pBtn.set( VclPtr<PushButton>::Create(pButtonBox) );
+ pBtn->SetText(GetStandardText(StandardButtonType::No));
+ pBtn->Show();
+ pBtn->set_id("no");
+ add_button(pBtn, RET_NO, true);
+ nDefaultResponse = RET_NO;
+ break;
+ case VclButtonsType::OkCancel:
+ pBtn.set( VclPtr<OKButton>::Create(pButtonBox) );
+ pBtn->Show();
+ pBtn->set_id("ok");
+ add_button(pBtn, RET_OK, true);
+
+ pBtn.set( VclPtr<CancelButton>::Create(pButtonBox) );
+ pBtn->Show();
+ pBtn->set_id("cancel");
+ add_button(pBtn, RET_CANCEL, true);
+ nDefaultResponse = RET_CANCEL;
+ break;
+ }
+ set_default_response(nDefaultResponse);
+ sort_native_button_order(*pButtonBox);
+ m_pMessageBox->Show();
+ m_pGrid->Show();
+}
+
+void MessageDialog::create_owned_areas()
+{
+#if defined _WIN32
+ set_border_width(3);
+#else
+ set_border_width(12);
+#endif
+ m_pOwnedContentArea.set(VclPtr<VclVBox>::Create(this, false, 24));
+ set_content_area(m_pOwnedContentArea);
+ m_pOwnedContentArea->Show();
+ m_pOwnedActionArea.set( VclPtr<VclHButtonBox>::Create(m_pOwnedContentArea) );
+ set_action_area(m_pOwnedActionArea);
+ m_pOwnedActionArea->Show();
+}
+
+MessageDialog::MessageDialog(vcl::Window* pParent, WinBits nStyle)
+ : Dialog(pParent, nStyle)
+ , m_eButtonsType(VclButtonsType::NONE)
+ , m_eMessageType(VclMessageType::Info)
+ , m_pOwnedContentArea(nullptr)
+ , m_pOwnedActionArea(nullptr)
+ , m_pGrid(nullptr)
+ , m_pMessageBox(nullptr)
+ , m_pImage(nullptr)
+ , m_pPrimaryMessage(nullptr)
+ , m_pSecondaryMessage(nullptr)
+{
+ SetType(WindowType::MESSBOX);
+}
+
+MessageDialog::MessageDialog(vcl::Window* pParent,
+ const OUString &rMessage,
+ VclMessageType eMessageType,
+ VclButtonsType eButtonsType)
+ : Dialog(pParent, WB_MOVEABLE | WB_3DLOOK | WB_CLOSEABLE)
+ , m_eButtonsType(eButtonsType)
+ , m_eMessageType(eMessageType)
+ , m_pGrid(nullptr)
+ , m_pMessageBox(nullptr)
+ , m_pImage(nullptr)
+ , m_pPrimaryMessage(nullptr)
+ , m_pSecondaryMessage(nullptr)
+ , m_sPrimaryString(rMessage)
+{
+ SetType(WindowType::MESSBOX);
+ create_owned_areas();
+ create_message_area();
+
+ switch (m_eMessageType)
+ {
+ case VclMessageType::Info:
+ SetText(GetStandardInfoBoxText());
+ break;
+ case VclMessageType::Warning:
+ SetText(GetStandardWarningBoxText());
+ break;
+ case VclMessageType::Question:
+ SetText(GetStandardQueryBoxText());
+ break;
+ case VclMessageType::Error:
+ SetText(GetStandardErrorBoxText());
+ break;
+ case VclMessageType::Other:
+ SetText(Application::GetDisplayName());
+ break;
+ }
+}
+
+void MessageDialog::dispose()
+{
+ disposeOwnedButtons();
+ m_pPrimaryMessage.disposeAndClear();
+ m_pSecondaryMessage.disposeAndClear();
+ m_pImage.disposeAndClear();
+ m_pMessageBox.disposeAndClear();
+ m_pGrid.disposeAndClear();
+ m_pOwnedActionArea.disposeAndClear();
+ m_pOwnedContentArea.disposeAndClear();
+ Dialog::dispose();
+}
+
+MessageDialog::~MessageDialog()
+{
+ disposeOnce();
+}
+
+void MessageDialog::SetMessagesWidths(vcl::Window const *pParent,
+ VclMultiLineEdit *pPrimaryMessage, VclMultiLineEdit *pSecondaryMessage)
+{
+ if (pSecondaryMessage)
+ {
+ assert(pPrimaryMessage);
+ vcl::Font aFont = pParent->GetSettings().GetStyleSettings().GetLabelFont();
+ aFont.SetFontSize(Size(0, aFont.GetFontSize().Height() * 1.2));
+ aFont.SetWeight(WEIGHT_BOLD);
+ pPrimaryMessage->SetControlFont(aFont);
+ pPrimaryMessage->SetMaxTextWidth(pPrimaryMessage->approximate_char_width() * 44);
+ pSecondaryMessage->SetMaxTextWidth(pSecondaryMessage->approximate_char_width() * 60);
+ }
+ else
+ pPrimaryMessage->SetMaxTextWidth(pPrimaryMessage->approximate_char_width() * 60);
+}
+
+OUString const & MessageDialog::get_primary_text() const
+{
+ const_cast<MessageDialog*>(this)->setDeferredProperties();
+
+ return m_sPrimaryString;
+}
+
+OUString const & MessageDialog::get_secondary_text() const
+{
+ const_cast<MessageDialog*>(this)->setDeferredProperties();
+
+ return m_sSecondaryString;
+}
+
+bool MessageDialog::set_property(const OString &rKey, const OUString &rValue)
+{
+ if (rKey == "text")
+ set_primary_text(rValue);
+ else if (rKey == "secondary-text")
+ set_secondary_text(rValue);
+ else if (rKey == "message-type")
+ {
+ VclMessageType eMode = VclMessageType::Info;
+ if (rValue == "info")
+ eMode = VclMessageType::Info;
+ else if (rValue == "warning")
+ eMode = VclMessageType::Warning;
+ else if (rValue == "question")
+ eMode = VclMessageType::Question;
+ else if (rValue == "error")
+ eMode = VclMessageType::Error;
+ else if (rValue == "other")
+ eMode = VclMessageType::Other;
+ else
+ {
+ SAL_WARN("vcl.layout", "unknown message type mode" << rValue);
+ }
+ m_eMessageType = eMode;
+ }
+ else if (rKey == "buttons")
+ {
+ VclButtonsType eMode = VclButtonsType::NONE;
+ if (rValue == "none")
+ eMode = VclButtonsType::NONE;
+ else if (rValue == "ok")
+ eMode = VclButtonsType::Ok;
+ else if (rValue == "cancel")
+ eMode = VclButtonsType::Cancel;
+ else if (rValue == "close")
+ eMode = VclButtonsType::Close;
+ else if (rValue == "yes-no")
+ eMode = VclButtonsType::YesNo;
+ else if (rValue == "ok-cancel")
+ eMode = VclButtonsType::OkCancel;
+ else
+ {
+ SAL_WARN("vcl.layout", "unknown buttons type mode" << rValue);
+ }
+ m_eButtonsType = eMode;
+ }
+ else
+ return Dialog::set_property(rKey, rValue);
+ return true;
+}
+
+void MessageDialog::set_primary_text(const OUString &rPrimaryString)
+{
+ m_sPrimaryString = rPrimaryString;
+ if (m_pPrimaryMessage)
+ {
+ m_pPrimaryMessage->SetText(m_sPrimaryString);
+ m_pPrimaryMessage->Show(!m_sPrimaryString.isEmpty());
+ MessageDialog::SetMessagesWidths(this, m_pPrimaryMessage, !m_sSecondaryString.isEmpty() ? m_pSecondaryMessage.get() : nullptr);
+ }
+}
+
+void MessageDialog::set_secondary_text(const OUString &rSecondaryString)
+{
+ m_sSecondaryString = rSecondaryString;
+ if (m_pSecondaryMessage)
+ {
+ m_pSecondaryMessage->SetText("\n" + m_sSecondaryString);
+ m_pSecondaryMessage->Show(!m_sSecondaryString.isEmpty());
+ MessageDialog::SetMessagesWidths(this, m_pPrimaryMessage, !m_sSecondaryString.isEmpty() ? m_pSecondaryMessage.get() : nullptr);
+ }
+}
+
+void MessageDialog::StateChanged(StateChangedType nType)
+{
+ Dialog::StateChanged(nType);
+ if (nType == StateChangedType::InitShow)
+ {
+ // MessageBox should be at least as wide as to see the title
+ auto nTitleWidth = CalcTitleWidth();
+ // Extra-Width for Close button
+ nTitleWidth += mpWindowImpl->mnTopBorder;
+ if (get_preferred_size().Width() < nTitleWidth)
+ {
+ set_width_request(nTitleWidth);
+ DoInitialLayout();
+ }
+ }
+}
+
+VclPaned::VclPaned(vcl::Window *pParent, bool bVertical)
+ : VclContainer(pParent, WB_HIDE | WB_CLIPCHILDREN)
+ , m_pSplitter(VclPtr<Splitter>::Create(this, bVertical ? WB_VSCROLL : WB_HSCROLL))
+ , m_nPosition(-1)
+{
+ m_pSplitter->SetBackground(Wallpaper(Application::GetSettings().GetStyleSettings().GetFaceColor()));
+ m_pSplitter->Show();
+}
+
+void VclPaned::dispose()
+{
+ m_pSplitter.disposeAndClear();
+ VclContainer::dispose();
+}
+
+VclVPaned::VclVPaned(vcl::Window *pParent)
+ : VclPaned(pParent, true)
+{
+ m_pSplitter->SetSplitHdl(LINK(this, VclVPaned, SplitHdl));
+}
+
+IMPL_LINK(VclVPaned, SplitHdl, Splitter*, pSplitter, void)
+{
+ tools::Long nSize = pSplitter->GetSplitPosPixel();
+ Size aSplitterSize(m_pSplitter->GetSizePixel());
+ Size aAllocation(GetSizePixel());
+ arrange(aAllocation, nSize, aAllocation.Height() - nSize - aSplitterSize.Height());
+}
+
+void VclVPaned::arrange(const Size& rAllocation, tools::Long nFirstHeight, tools::Long nSecondHeight)
+{
+ Size aSplitterSize(rAllocation.Width(), getLayoutRequisition(*m_pSplitter).Height());
+ Size aFirstChildSize(rAllocation.Width(), nFirstHeight);
+ Size aSecondChildSize(rAllocation.Width(), nSecondHeight);
+ int nElement = 0;
+ for (vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ if (nElement == 0)
+ {
+ Point aSplitterPos(0, aFirstChildSize.Height());
+ setLayoutAllocation(*m_pSplitter, aSplitterPos, aSplitterSize);
+ m_nPosition = aSplitterPos.Y() + aSplitterSize.Height() / 2;
+ }
+ else if (nElement == 1)
+ {
+ Point aChildPos(0, 0);
+ setLayoutAllocation(*pChild, aChildPos, aFirstChildSize);
+ }
+ else if (nElement == 2)
+ {
+ Point aChildPos(0, aFirstChildSize.Height() + aSplitterSize.Height());
+ setLayoutAllocation(*pChild, aChildPos, aSecondChildSize);
+ }
+ ++nElement;
+ }
+}
+
+void VclVPaned::set_position(tools::Long nPosition)
+{
+ VclPaned::set_position(nPosition);
+
+ Size aAllocation(GetSizePixel());
+ Size aSplitterSize(m_pSplitter->GetSizePixel());
+
+ nPosition -= aSplitterSize.Height() / 2;
+
+ arrange(aAllocation, nPosition, aAllocation.Height() - nPosition - aSplitterSize.Height());
+}
+
+void VclVPaned::setAllocation(const Size& rAllocation)
+{
+ //supporting "shrink" could be done by adjusting the allowed drag rectangle
+ m_pSplitter->SetDragRectPixel(tools::Rectangle(Point(0, 0), rAllocation));
+ Size aSplitterSize(rAllocation.Width(), getLayoutRequisition(*m_pSplitter).Height());
+ const tools::Long nHeight = rAllocation.Height() - aSplitterSize.Height();
+
+ tools::Long nFirstHeight = 0;
+ tools::Long nSecondHeight = 0;
+ bool bFirstCanResize = true;
+ bool bSecondCanResize = true;
+ const bool bInitialAllocation = get_position() < 0;
+ int nElement = 0;
+ for (const vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ if (nElement == 1)
+ {
+ if (bInitialAllocation)
+ nFirstHeight = getLayoutRequisition(*pChild).Height();
+ else
+ nFirstHeight = pChild->GetSizePixel().Height() + pChild->get_margin_top() + pChild->get_margin_bottom();
+ bFirstCanResize = pChild->get_expand();
+ }
+ else if (nElement == 2)
+ {
+ if (bInitialAllocation)
+ nSecondHeight = getLayoutRequisition(*pChild).Height();
+ else
+ nSecondHeight = pChild->GetSizePixel().Height() + pChild->get_margin_top() + pChild->get_margin_bottom();
+ bSecondCanResize = pChild->get_expand();
+ }
+ ++nElement;
+ }
+ tools::Long nHeightRequest = nFirstHeight + nSecondHeight;
+ tools::Long nHeightDiff = nHeight - nHeightRequest;
+ if (bFirstCanResize == bSecondCanResize)
+ nFirstHeight += nHeightDiff/2;
+ else if (bFirstCanResize)
+ nFirstHeight += nHeightDiff;
+ arrange(rAllocation, nFirstHeight, rAllocation.Height() - nFirstHeight - aSplitterSize.Height());
+}
+
+Size VclVPaned::calculateRequisition() const
+{
+ Size aRet(0, 0);
+
+ for (const vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ Size aChildSize = getLayoutRequisition(*pChild);
+ aRet.setWidth( std::max(aRet.Width(), aChildSize.Width()) );
+ aRet.AdjustHeight(aChildSize.Height() );
+ }
+
+ return aRet;
+}
+
+VclHPaned::VclHPaned(vcl::Window *pParent)
+ : VclPaned(pParent, false)
+{
+ m_pSplitter->SetSplitHdl(LINK(this, VclHPaned, SplitHdl));
+}
+
+IMPL_LINK(VclHPaned, SplitHdl, Splitter*, pSplitter, void)
+{
+ tools::Long nSize = pSplitter->GetSplitPosPixel();
+ Size aSplitterSize(m_pSplitter->GetSizePixel());
+ Size aAllocation(GetSizePixel());
+ arrange(aAllocation, nSize, aAllocation.Width() - nSize - aSplitterSize.Width());
+}
+
+void VclHPaned::arrange(const Size& rAllocation, tools::Long nFirstWidth, tools::Long nSecondWidth)
+{
+ Size aSplitterSize(getLayoutRequisition(*m_pSplitter).Width(), rAllocation.Height());
+ Size aFirstChildSize(nFirstWidth, rAllocation.Height());
+ Size aSecondChildSize(nSecondWidth, rAllocation.Height());
+ int nElement = 0;
+ for (vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ if (nElement == 0)
+ {
+ Point aSplitterPos(aFirstChildSize.Width(), 0);
+ setLayoutAllocation(*m_pSplitter, aSplitterPos, aSplitterSize);
+ m_nPosition = aSplitterPos.X() + aSplitterSize.Width() / 2;
+ }
+ else if (nElement == 1)
+ {
+ Point aChildPos(0, 0);
+ setLayoutAllocation(*pChild, aChildPos, aFirstChildSize);
+ }
+ else if (nElement == 2)
+ {
+ Point aChildPos(aFirstChildSize.Width() + aSplitterSize.Width(), 0);
+ setLayoutAllocation(*pChild, aChildPos, aSecondChildSize);
+ }
+ ++nElement;
+ }
+}
+
+void VclHPaned::set_position(tools::Long nPosition)
+{
+ VclPaned::set_position(nPosition);
+
+ Size aAllocation(GetSizePixel());
+ Size aSplitterSize(m_pSplitter->GetSizePixel());
+
+ nPosition -= aSplitterSize.Width() / 2;
+
+ arrange(aAllocation, nPosition, aAllocation.Width() - nPosition - aSplitterSize.Width());
+}
+
+void VclHPaned::setAllocation(const Size& rAllocation)
+{
+ //supporting "shrink" could be done by adjusting the allowed drag rectangle
+ m_pSplitter->SetDragRectPixel(tools::Rectangle(Point(0, 0), rAllocation));
+ Size aSplitterSize(getLayoutRequisition(*m_pSplitter).Width(), rAllocation.Height());
+ const tools::Long nWidth = rAllocation.Width() - aSplitterSize.Width();
+
+ tools::Long nFirstWidth = 0;
+ tools::Long nSecondWidth = 0;
+ bool bFirstCanResize = true;
+ bool bSecondCanResize = true;
+ const bool bInitialAllocation = get_position() < 0;
+ int nElement = 0;
+ for (const vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ if (nElement == 1)
+ {
+ if (bInitialAllocation)
+ nFirstWidth = getLayoutRequisition(*pChild).Width();
+ else
+ nFirstWidth = pChild->GetSizePixel().Width() + pChild->get_margin_start() + pChild->get_margin_end();
+ bFirstCanResize = pChild->get_expand();
+ }
+ else if (nElement == 2)
+ {
+ if (bInitialAllocation)
+ nSecondWidth = getLayoutRequisition(*pChild).Width();
+ else
+ nSecondWidth = pChild->GetSizePixel().Width() + pChild->get_margin_start() + pChild->get_margin_end();
+ bSecondCanResize = pChild->get_expand();
+ }
+ ++nElement;
+ }
+ tools::Long nWidthRequest = nFirstWidth + nSecondWidth;
+ tools::Long nWidthDiff = nWidth - nWidthRequest;
+ if (bFirstCanResize == bSecondCanResize)
+ nFirstWidth += nWidthDiff/2;
+ else if (bFirstCanResize)
+ nFirstWidth += nWidthDiff;
+ arrange(rAllocation, nFirstWidth, rAllocation.Width() - nFirstWidth - aSplitterSize.Width());
+}
+
+Size VclHPaned::calculateRequisition() const
+{
+ Size aRet(0, 0);
+
+ for (const vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+ Size aChildSize = getLayoutRequisition(*pChild);
+ aRet.setHeight( std::max(aRet.Height(), aChildSize.Height()) );
+ aRet.AdjustWidth(aChildSize.Width() );
+ }
+
+ return aRet;
+}
+
+Size getLegacyBestSizeForChildren(const vcl::Window &rWindow)
+{
+ tools::Rectangle aBounds;
+
+ for (const vcl::Window* pChild = rWindow.GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ if (!pChild->IsVisible())
+ continue;
+
+ tools::Rectangle aChildBounds(pChild->GetPosPixel(), pChild->GetSizePixel());
+ aBounds.Union(aChildBounds);
+ }
+
+ if (aBounds.IsEmpty())
+ return rWindow.GetSizePixel();
+
+ Size aRet(aBounds.GetSize());
+ Point aTopLeft(aBounds.TopLeft());
+ aRet.AdjustWidth(aTopLeft.X()*2 );
+ aRet.AdjustHeight(aTopLeft.Y()*2 );
+
+ return aRet;
+}
+
+vcl::Window* getNonLayoutParent(vcl::Window *pWindow)
+{
+ while (pWindow)
+ {
+ pWindow = pWindow->GetParent();
+ if (!pWindow || !isContainerWindow(*pWindow))
+ break;
+ }
+ return pWindow;
+}
+
+bool isVisibleInLayout(const vcl::Window *pWindow)
+{
+ bool bVisible = true;
+ while (bVisible)
+ {
+ bVisible = pWindow->IsVisible();
+ pWindow = pWindow->GetParent();
+ if (!pWindow || !isContainerWindow(*pWindow))
+ break;
+ }
+ return bVisible;
+}
+
+bool isEnabledInLayout(const vcl::Window *pWindow)
+{
+ bool bEnabled = true;
+ while (bEnabled)
+ {
+ bEnabled = pWindow->IsEnabled();
+ pWindow = pWindow->GetParent();
+ if (!pWindow || !isContainerWindow(*pWindow))
+ break;
+ }
+ return bEnabled;
+}
+
+bool isLayoutEnabled(const vcl::Window *pWindow)
+{
+ //Child is a container => we're layout enabled
+ const vcl::Window *pChild = pWindow ? pWindow->GetWindow(GetWindowType::FirstChild) : nullptr;
+ return pChild && isContainerWindow(*pChild) && !pChild->GetWindow(GetWindowType::Next);
+}
+
+void VclDrawingArea::RequestHelp(const HelpEvent& rHelpEvent)
+{
+ if (!(rHelpEvent.GetMode() & (HelpEventMode::QUICK | HelpEventMode::BALLOON)))
+ return;
+
+ Point aPos(ScreenToOutputPixel(rHelpEvent.GetMousePosPixel()));
+ tools::Rectangle aHelpArea(aPos.X(), aPos.Y());
+ OUString sHelpTip = m_aQueryTooltipHdl.Call(aHelpArea);
+ if (sHelpTip.isEmpty())
+ return;
+ Point aPt = OutputToScreenPixel(aHelpArea.TopLeft());
+ aHelpArea.SetLeft(aPt.X());
+ aHelpArea.SetTop(aPt.Y());
+ aPt = OutputToScreenPixel(aHelpArea.BottomRight());
+ aHelpArea.SetRight(aPt.X());
+ aHelpArea.SetBottom(aPt.Y());
+ // tdf#125369 recover newline support of tdf#101779
+ QuickHelpFlags eHelpWinStyle = sHelpTip.indexOf('\n') != -1 ? QuickHelpFlags::TipStyleBalloon : QuickHelpFlags::NONE;
+ Help::ShowQuickHelp(this, aHelpArea, sHelpTip, eHelpWinStyle);
+}
+
+void VclDrawingArea::StartDrag(sal_Int8, const Point&)
+{
+ if (m_aStartDragHdl.Call(this))
+ return;
+
+ rtl::Reference<TransferDataContainer> xContainer = m_xTransferHelper;
+ if (!m_xTransferHelper.is())
+ return;
+
+ xContainer->StartDrag(this, m_nDragAction);
+}
+
+OUString VclDrawingArea::GetSurroundingText() const
+{
+ if (!m_aGetSurroundingHdl.IsSet())
+ return Control::GetSurroundingText();
+ OUString sSurroundingText;
+ m_aGetSurroundingHdl.Call(sSurroundingText);
+ return sSurroundingText;
+}
+
+Selection VclDrawingArea::GetSurroundingTextSelection() const
+{
+ if (!m_aGetSurroundingHdl.IsSet())
+ return Control::GetSurroundingTextSelection();
+ OUString sSurroundingText;
+ int nCursor = m_aGetSurroundingHdl.Call(sSurroundingText);
+ return Selection(nCursor, nCursor);
+}
+
+bool VclDrawingArea::DeleteSurroundingText(const Selection& rSelection)
+{
+ if (!m_aDeleteSurroundingHdl.IsSet())
+ return Control::DeleteSurroundingText(rSelection);
+ return m_aDeleteSurroundingHdl.Call(rSelection);
+}
+
+VclHPaned::~VclHPaned()
+{
+}
+
+VclVPaned::~VclVPaned()
+{
+}
+
+VclPaned::~VclPaned()
+{
+ disposeOnce();
+}
+
+VclScrolledWindow::~VclScrolledWindow()
+{
+ disposeOnce();
+}
+
+void VclDrawingArea::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("type", "drawingarea");
+
+ ScopedVclPtrInstance<VirtualDevice> pDevice;
+ pDevice->SetOutputSize( GetSizePixel() );
+ tools::Rectangle aRect(Point(0,0), GetSizePixel());
+ Paint(*pDevice, aRect);
+ BitmapEx aImage = pDevice->GetBitmapEx( Point(0,0), GetSizePixel() );
+ SvMemoryStream aOStm(65535, 65535);
+ if(GraphicConverter::Export(aOStm, aImage, ConvertDataFormat::PNG) == ERRCODE_NONE)
+ {
+ css::uno::Sequence<sal_Int8> aSeq( static_cast<sal_Int8 const *>(aOStm.GetData()), aOStm.Tell());
+ OUStringBuffer aBuffer("data:image/png;base64,");
+ ::comphelper::Base64::encode(aBuffer, aSeq);
+ rJsonWriter.put("image", aBuffer.makeStringAndClear());
+ }
+ rJsonWriter.put("text", GetQuickHelpText());
+}
+
+FactoryFunction VclDrawingArea::GetUITestFactory() const
+{
+ if (m_pFactoryFunction)
+ return m_pFactoryFunction;
+ return DrawingAreaUIObject::create;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/legacyaccessibility.cxx b/vcl/source/window/legacyaccessibility.cxx
new file mode 100644
index 000000000..346e1fdc8
--- /dev/null
+++ b/vcl/source/window/legacyaccessibility.cxx
@@ -0,0 +1,241 @@
+/* -*- 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 <window.h>
+
+#include "dlgctrl.hxx"
+
+using namespace ::com::sun::star;
+
+
+static vcl::Window* ImplGetLabelFor( vcl::Window* pFrameWindow, WindowType nMyType, vcl::Window* pLabel, sal_Unicode nAccel )
+{
+ vcl::Window* pWindow = nullptr;
+
+ if( nMyType == WindowType::FIXEDTEXT ||
+ nMyType == WindowType::FIXEDLINE ||
+ nMyType == WindowType::GROUPBOX )
+ {
+ // #i100833# MT 2010/02: Group box and fixed lines can also label a fixed text.
+ // See tools/options/print for example.
+ bool bThisIsAGroupControl = (nMyType == WindowType::GROUPBOX) || (nMyType == WindowType::FIXEDLINE);
+ // get index, form start and form end
+ sal_uInt16 nIndex=0, nFormStart=0, nFormEnd=0;
+ ::ImplFindDlgCtrlWindow( pFrameWindow,
+ pLabel,
+ nIndex,
+ nFormStart,
+ nFormEnd );
+ if( nAccel )
+ {
+ // find the accelerated window
+ pWindow = ::ImplFindAccelWindow( pFrameWindow,
+ nIndex,
+ nAccel,
+ nFormStart,
+ nFormEnd,
+ false );
+ }
+ else
+ {
+ // find the next control; if that is a fixed text
+ // fixed line or group box, then return NULL
+ while( nIndex < nFormEnd )
+ {
+ nIndex++;
+ vcl::Window* pSWindow = ::ImplGetChildWindow( pFrameWindow,
+ nIndex,
+ nIndex,
+ false );
+ if( pSWindow && isVisibleInLayout(pSWindow) && ! (pSWindow->GetStyle() & WB_NOLABEL) )
+ {
+ WindowType nType = pSWindow->GetType();
+ if( nType != WindowType::FIXEDTEXT &&
+ nType != WindowType::FIXEDLINE &&
+ nType != WindowType::GROUPBOX )
+ {
+ pWindow = pSWindow;
+ }
+ else if( bThisIsAGroupControl && ( nType == WindowType::FIXEDTEXT ) )
+ {
+ pWindow = pSWindow;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ return pWindow;
+}
+
+namespace vcl {
+
+Window* Window::getLegacyNonLayoutAccessibleRelationLabelFor() const
+{
+ Window* pFrameWindow = ImplGetFrameWindow();
+
+ WinBits nFrameStyle = pFrameWindow->GetStyle();
+ if( ! ( nFrameStyle & WB_DIALOGCONTROL )
+ || ( nFrameStyle & WB_NODIALOGCONTROL )
+ )
+ return nullptr;
+
+ sal_Unicode nAccel = getAccel( GetText() );
+
+ Window* pWindow = ImplGetLabelFor( pFrameWindow, GetType(), const_cast<Window*>(this), nAccel );
+ if( ! pWindow && mpWindowImpl->mpRealParent )
+ pWindow = ImplGetLabelFor( mpWindowImpl->mpRealParent, GetType(), const_cast<Window*>(this), nAccel );
+ return pWindow;
+}
+
+static Window* ImplGetLabeledBy( Window* pFrameWindow, WindowType nMyType, Window* pLabeled )
+{
+ Window* pWindow = nullptr;
+ if ( (nMyType != WindowType::GROUPBOX) && (nMyType != WindowType::FIXEDLINE) )
+ {
+ // search for a control that labels this window
+ // a label is considered the last fixed text, fixed line or group box
+ // that comes before this control; with the exception of push buttons
+ // which are labeled only if the fixed text, fixed line or group box
+ // is directly before the control
+
+ // get form start and form end and index of this control
+ sal_uInt16 nIndex, nFormStart, nFormEnd;
+ Window* pSWindow = ::ImplFindDlgCtrlWindow( pFrameWindow,
+ pLabeled,
+ nIndex,
+ nFormStart,
+ nFormEnd );
+ if( pSWindow && nIndex != nFormStart )
+ {
+ if( nMyType == WindowType::PUSHBUTTON ||
+ nMyType == WindowType::HELPBUTTON ||
+ nMyType == WindowType::OKBUTTON ||
+ nMyType == WindowType::CANCELBUTTON )
+ {
+ nFormStart = nIndex-1;
+ }
+ for( sal_uInt16 nSearchIndex = nIndex-1; nSearchIndex >= nFormStart; nSearchIndex-- )
+ {
+ sal_uInt16 nFoundIndex = 0;
+ pSWindow = ::ImplGetChildWindow( pFrameWindow,
+ nSearchIndex,
+ nFoundIndex,
+ false );
+ if( pSWindow && isVisibleInLayout(pSWindow) && !(pSWindow->GetStyle() & WB_NOLABEL) )
+ {
+ WindowType nType = pSWindow->GetType();
+ if ( nType == WindowType::FIXEDTEXT ||
+ nType == WindowType::FIXEDLINE ||
+ nType == WindowType::GROUPBOX )
+ {
+ // a fixed text can't be labelled by a fixed text.
+ if ( ( nMyType != WindowType::FIXEDTEXT ) || ( nType != WindowType::FIXEDTEXT ) )
+ pWindow = pSWindow;
+ break;
+ }
+ }
+ if( nFoundIndex > nSearchIndex || nSearchIndex == 0 )
+ break;
+ }
+ }
+ }
+ return pWindow;
+}
+
+Window* Window::getLegacyNonLayoutAccessibleRelationLabeledBy() const
+{
+ Window* pFrameWindow = ImplGetFrameWindow();
+
+ // #i62723#, #104191# checkboxes and radiobuttons are not supposed to have labels
+ if( GetType() == WindowType::CHECKBOX || GetType() == WindowType::RADIOBUTTON )
+ return nullptr;
+
+// if( ! ( GetType() == WindowType::FIXEDTEXT ||
+// GetType() == WindowType::FIXEDLINE ||
+// GetType() == WindowType::GROUPBOX ) )
+ // #i100833# MT 2010/02: Group box and fixed lines can also label a fixed text.
+ // See tools/options/print for example.
+
+ Window* pWindow = ImplGetLabeledBy( pFrameWindow, GetType(), const_cast<Window*>(this) );
+ if( ! pWindow && mpWindowImpl->mpRealParent )
+ pWindow = ImplGetLabeledBy( mpWindowImpl->mpRealParent, GetType(), const_cast<Window*>(this) );
+
+ return pWindow;
+}
+
+Window* Window::getLegacyNonLayoutAccessibleRelationMemberOf() const
+{
+ Window* pWindow = nullptr;
+ Window* pFrameWindow = GetParent();
+ if ( !pFrameWindow )
+ {
+ pFrameWindow = ImplGetFrameWindow();
+ }
+ // if( ! ( GetType() == WindowType::FIXEDTEXT ||
+ if( GetType() != WindowType::FIXEDLINE && GetType() != WindowType::GROUPBOX )
+ {
+ // search for a control that makes member of this window
+ // it is considered the last fixed line or group box
+ // that comes before this control; with the exception of push buttons
+ // which are labeled only if the fixed line or group box
+ // is directly before the control
+ // get form start and form end and index of this control
+ sal_uInt16 nIndex, nFormStart, nFormEnd;
+ Window* pSWindow = ::ImplFindDlgCtrlWindow( pFrameWindow,
+ const_cast<Window*>(this),
+ nIndex,
+ nFormStart,
+ nFormEnd );
+ if( pSWindow && nIndex != nFormStart )
+ {
+ if( GetType() == WindowType::PUSHBUTTON ||
+ GetType() == WindowType::HELPBUTTON ||
+ GetType() == WindowType::OKBUTTON ||
+ GetType() == WindowType::CANCELBUTTON )
+ {
+ nFormStart = nIndex-1;
+ }
+ for( sal_uInt16 nSearchIndex = nIndex-1; nSearchIndex >= nFormStart; nSearchIndex-- )
+ {
+ sal_uInt16 nFoundIndex = 0;
+ pSWindow = ::ImplGetChildWindow( pFrameWindow,
+ nSearchIndex,
+ nFoundIndex,
+ false );
+ if( pSWindow && pSWindow->IsVisible() &&
+ ( pSWindow->GetType() == WindowType::FIXEDLINE ||
+ pSWindow->GetType() == WindowType::GROUPBOX ) )
+ {
+ pWindow = pSWindow;
+ break;
+ }
+ if( nFoundIndex > nSearchIndex || nSearchIndex == 0 )
+ break;
+ }
+ }
+ }
+ return pWindow;
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menu.cxx b/vcl/source/window/menu.cxx
new file mode 100644
index 000000000..89bd56720
--- /dev/null
+++ b/vcl/source/window/menu.cxx
@@ -0,0 +1,3077 @@
+/* -*- 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 <tools/diagnose_ex.h>
+#include <sal/log.hxx>
+
+#include <comphelper/lok.hxx>
+#include <vcl/dialoghelper.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/image.hxx>
+#include <vcl/event.hxx>
+#include <vcl/help.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/taskpanelist.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/commandinfoprovider.hxx>
+
+#include <salinst.hxx>
+#include <svdata.hxx>
+#include <strings.hrc>
+#include <window.h>
+#include <salmenu.hxx>
+#include <salframe.hxx>
+
+#include "menubarwindow.hxx"
+#include "menufloatingwindow.hxx"
+#include "menuitemlist.hxx"
+
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/accessibility/XAccessible.hpp>
+#include <vcl/toolkit/unowrap.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <configsettings.hxx>
+
+#include <map>
+#include <string_view>
+#include <vector>
+
+namespace vcl
+{
+
+struct MenuLayoutData : public ControlLayoutData
+{
+ std::vector< sal_uInt16 > m_aLineItemIds;
+ std::map< sal_uInt16, tools::Rectangle > m_aVisibleItemBoundRects;
+};
+
+}
+
+using namespace vcl;
+
+#define EXTRAITEMHEIGHT 4
+#define SPACE_AROUND_TITLE 4
+
+static bool ImplAccelDisabled()
+{
+ // display of accelerator strings may be suppressed via configuration
+ static int nAccelDisabled = -1;
+
+ if( nAccelDisabled == -1 )
+ {
+ OUString aStr =
+ vcl::SettingsConfigItem::get()->
+ getValue( "Menu", "SuppressAccelerators" );
+ nAccelDisabled = aStr.equalsIgnoreAsciiCase("true") ? 1 : 0;
+ }
+ return nAccelDisabled == 1;
+}
+
+static void ImplSetMenuItemData( MenuItemData* pData )
+{
+ // convert data
+ if ( !pData->aImage )
+ pData->eType = MenuItemType::STRING;
+ else if ( pData->aText.isEmpty() )
+ pData->eType = MenuItemType::IMAGE;
+ else
+ pData->eType = MenuItemType::STRINGIMAGE;
+}
+
+namespace {
+
+void ImplClosePopupToolBox( const VclPtr<vcl::Window>& pWin )
+{
+ if ( pWin->GetType() == WindowType::TOOLBOX && ImplGetDockingManager()->IsInPopupMode( pWin ) )
+ {
+ SystemWindow* pFloatingWindow = ImplGetDockingManager()->GetFloatingWindow(pWin);
+ if (pFloatingWindow)
+ static_cast<FloatingWindow*>(pFloatingWindow)->EndPopupMode( FloatWinPopupEndFlags::CloseAll );
+ }
+}
+
+// TODO: Move to common code with the same function in toolbox
+// Draw the ">>" - more indicator at the coordinates
+void lclDrawMoreIndicator(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ rRenderContext.Push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
+ rRenderContext.SetLineColor();
+
+ if (rRenderContext.GetSettings().GetStyleSettings().GetFaceColor().IsDark())
+ rRenderContext.SetFillColor(COL_WHITE);
+ else
+ rRenderContext.SetFillColor(COL_BLACK);
+ float fScaleFactor = rRenderContext.GetDPIScaleFactor();
+
+ int linewidth = 1 * fScaleFactor;
+ int space = 4 * fScaleFactor;
+
+ tools::Long width = 8 * fScaleFactor;
+ tools::Long height = 5 * fScaleFactor;
+
+ //Keep odd b/c drawing code works better
+ if ( height % 2 == 0 )
+ height--;
+
+ tools::Long heightOrig = height;
+
+ tools::Long x = rRect.Left() + (rRect.getWidth() - width)/2 + 1;
+ tools::Long y = rRect.Top() + (rRect.getHeight() - height)/2 + 1;
+ while( height >= 1)
+ {
+ rRenderContext.DrawRect( tools::Rectangle( x, y, x + linewidth, y ) );
+ x += space;
+ rRenderContext.DrawRect( tools::Rectangle( x, y, x + linewidth, y ) );
+ x -= space;
+ y++;
+ if( height <= heightOrig / 2 + 1) x--;
+ else x++;
+ height--;
+ }
+ rRenderContext.Pop();
+}
+
+} // end anonymous namespace
+
+
+Menu::Menu()
+ : mpFirstDel(nullptr),
+ pItemList(new MenuItemList),
+ pStartedFrom(nullptr),
+ pWindow(nullptr),
+ nTitleHeight(0),
+ nEventId(nullptr),
+ mnHighlightedItemPos(ITEMPOS_INVALID),
+ nMenuFlags(MenuFlags::NONE),
+ nSelectedId(0),
+ nImgOrChkPos(0),
+ nTextPos(0),
+ bCanceled(false),
+ bInCallback(false),
+ bKilled(false)
+{
+}
+
+Menu::~Menu()
+{
+ disposeOnce();
+}
+
+void Menu::dispose()
+{
+ ImplCallEventListeners( VclEventId::ObjectDying, ITEMPOS_INVALID );
+
+ // at the window free the reference to the accessible component
+ // and make sure the MenuFloatingWindow knows about our destruction
+ if ( pWindow )
+ {
+ MenuFloatingWindow* pFloat = static_cast<MenuFloatingWindow*>(pWindow.get());
+ if( pFloat->pMenu.get() == this )
+ pFloat->pMenu.clear();
+ pWindow->SetAccessible( css::uno::Reference< css::accessibility::XAccessible >() );
+ }
+
+ // dispose accessible components
+ if ( mxAccessible.is() )
+ {
+ css::uno::Reference< css::lang::XComponent> xComponent( mxAccessible, css::uno::UNO_QUERY );
+ if ( xComponent.is() )
+ xComponent->dispose();
+ }
+
+ if ( nEventId )
+ Application::RemoveUserEvent( nEventId );
+
+ // Notify deletion of this menu
+ ImplMenuDelData* pDelData = mpFirstDel;
+ while ( pDelData )
+ {
+ pDelData->mpMenu = nullptr;
+ pDelData = pDelData->mpNext;
+ }
+
+ bKilled = true;
+
+ // tdf#140225 when clearing pItemList, keep SalMenu in sync with
+ // their removal during menu teardown
+ for (size_t n = pItemList->size(); n;)
+ {
+ --n;
+ if (mpSalMenu)
+ mpSalMenu->RemoveItem(n);
+ pItemList->Remove(n);
+ }
+
+ assert(!pItemList->size());
+
+ mpLayoutData.reset();
+
+ // Native-support: destroy SalMenu
+ mpSalMenu.reset();
+
+ pStartedFrom.clear();
+ pWindow.clear();
+ VclReferenceBase::dispose();
+}
+
+void Menu::CreateAutoMnemonics()
+{
+ MnemonicGenerator aMnemonicGenerator;
+ size_t n;
+ for ( n = 0; n < pItemList->size(); n++ )
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos( n );
+ if ( ! (pData->nBits & MenuItemBits::NOSELECT ) )
+ aMnemonicGenerator.RegisterMnemonic( pData->aText );
+ }
+ for ( n = 0; n < pItemList->size(); n++ )
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos( n );
+ if ( ! (pData->nBits & MenuItemBits::NOSELECT ) )
+ pData->aText = aMnemonicGenerator.CreateMnemonic( pData->aText );
+ }
+}
+
+void Menu::Activate()
+{
+ bInCallback = true;
+
+ ImplMenuDelData aDelData( this );
+
+ ImplCallEventListeners( VclEventId::MenuActivate, ITEMPOS_INVALID );
+
+ if( !aDelData.isDeleted() )
+ {
+ if ( !aActivateHdl.Call( this ) )
+ {
+ if( !aDelData.isDeleted() )
+ {
+ Menu* pStartMenu = ImplGetStartMenu();
+ if ( pStartMenu && ( pStartMenu != this ) )
+ {
+ pStartMenu->bInCallback = true;
+ // MT 11/01: Call EventListener here? I don't know...
+ pStartMenu->aActivateHdl.Call( this );
+ pStartMenu->bInCallback = false;
+ }
+ }
+ }
+ bInCallback = false;
+ }
+
+ if (!aDelData.isDeleted() && !(nMenuFlags & MenuFlags::NoAutoMnemonics))
+ CreateAutoMnemonics();
+}
+
+void Menu::Deactivate()
+{
+ for ( size_t n = pItemList->size(); n; )
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos( --n );
+ if ( pData->bIsTemporary )
+ {
+ if ( ImplGetSalMenu() )
+ ImplGetSalMenu()->RemoveItem( n );
+
+ pItemList->Remove( n );
+ }
+ }
+
+ bInCallback = true;
+
+ ImplMenuDelData aDelData( this );
+
+ Menu* pStartMenu = ImplGetStartMenu();
+ ImplCallEventListeners( VclEventId::MenuDeactivate, ITEMPOS_INVALID );
+
+ if( !aDelData.isDeleted() )
+ {
+ if ( !aDeactivateHdl.Call( this ) )
+ {
+ if( !aDelData.isDeleted() )
+ {
+ if ( pStartMenu && ( pStartMenu != this ) )
+ {
+ pStartMenu->bInCallback = true;
+ pStartMenu->aDeactivateHdl.Call( this );
+ pStartMenu->bInCallback = false;
+ }
+ }
+ }
+ }
+
+ if( !aDelData.isDeleted() )
+ {
+ bInCallback = false;
+ }
+}
+
+void Menu::ImplSelect()
+{
+ MenuItemData* pData = GetItemList()->GetData( nSelectedId );
+ if ( pData && (pData->nBits & MenuItemBits::AUTOCHECK) )
+ {
+ bool bChecked = IsItemChecked( nSelectedId );
+ if ( pData->nBits & MenuItemBits::RADIOCHECK )
+ {
+ if ( !bChecked )
+ CheckItem( nSelectedId );
+ }
+ else
+ CheckItem( nSelectedId, !bChecked );
+ }
+
+ // call select
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mpActivePopupMenu = nullptr; // if new execute in select()
+ nEventId = Application::PostUserEvent( LINK( this, Menu, ImplCallSelect ) );
+}
+
+void Menu::Select()
+{
+ ImplMenuDelData aDelData( this );
+
+ ImplCallEventListeners( VclEventId::MenuSelect, GetItemPos( GetCurItemId() ) );
+ if (aDelData.isDeleted())
+ return;
+ if (aSelectHdl.Call(this))
+ return;
+ if (aDelData.isDeleted())
+ return;
+ Menu* pStartMenu = ImplGetStartMenu();
+ if (!pStartMenu || (pStartMenu == this))
+ return;
+ pStartMenu->nSelectedId = nSelectedId;
+ pStartMenu->sSelectedIdent = sSelectedIdent;
+ pStartMenu->aSelectHdl.Call( this );
+}
+
+#if defined(MACOSX)
+void Menu::ImplSelectWithStart( Menu* pSMenu )
+{
+ auto pOldStartedFrom = pStartedFrom;
+ pStartedFrom = pSMenu;
+ auto pOldStartedStarted = pOldStartedFrom ? pOldStartedFrom->pStartedFrom : VclPtr<Menu>();
+ Select();
+ if( pOldStartedFrom )
+ pOldStartedFrom->pStartedFrom = pOldStartedStarted;
+ pStartedFrom = pOldStartedFrom;
+}
+#endif
+
+void Menu::ImplCallEventListeners( VclEventId nEvent, sal_uInt16 nPos )
+{
+ ImplMenuDelData aDelData( this );
+
+ VclMenuEvent aEvent( this, nEvent, nPos );
+
+ // This is needed by atk accessibility bridge
+ if ( nEvent == VclEventId::MenuHighlight )
+ {
+ Application::ImplCallEventListeners( aEvent );
+ }
+
+ if ( !aDelData.isDeleted() )
+ {
+ // Copy the list, because this can be destroyed when calling a Link...
+ std::list<Link<VclMenuEvent&,void>> aCopy( maEventListeners );
+ for ( const auto& rLink : aCopy )
+ {
+ if( std::find(maEventListeners.begin(), maEventListeners.end(), rLink) != maEventListeners.end() )
+ rLink.Call( aEvent );
+ }
+ }
+}
+
+void Menu::AddEventListener( const Link<VclMenuEvent&,void>& rEventListener )
+{
+ maEventListeners.push_back( rEventListener );
+}
+
+void Menu::RemoveEventListener( const Link<VclMenuEvent&,void>& rEventListener )
+{
+ maEventListeners.remove( rEventListener );
+}
+
+MenuItemData* Menu::NbcInsertItem(sal_uInt16 nId, MenuItemBits nBits,
+ const OUString& rStr, Menu* pMenu,
+ size_t nPos, const OString &rIdent)
+{
+ // put Item in MenuItemList
+ MenuItemData* pData = pItemList->Insert(nId, MenuItemType::STRING,
+ nBits, rStr, pMenu, nPos, rIdent);
+
+ // update native menu
+ if (ImplGetSalMenu() && pData->pSalMenuItem)
+ ImplGetSalMenu()->InsertItem(pData->pSalMenuItem.get(), nPos);
+
+ return pData;
+}
+
+void Menu::InsertItem(sal_uInt16 nItemId, const OUString& rStr, MenuItemBits nItemBits,
+ const OString &rIdent, sal_uInt16 nPos)
+{
+ SAL_WARN_IF( !nItemId, "vcl", "Menu::InsertItem(): ItemId == 0" );
+ SAL_WARN_IF( GetItemPos( nItemId ) != MENU_ITEM_NOTFOUND, "vcl",
+ "Menu::InsertItem(): ItemId already exists" );
+
+ // if Position > ItemCount, append
+ if ( nPos >= pItemList->size() )
+ nPos = MENU_APPEND;
+
+ // put Item in MenuItemList
+ NbcInsertItem(nItemId, nItemBits, rStr, this, nPos, rIdent);
+
+ vcl::Window* pWin = ImplGetWindow();
+ mpLayoutData.reset();
+ if ( pWin )
+ {
+ ImplCalcSize( pWin );
+ if ( pWin->IsVisible() )
+ pWin->Invalidate();
+ }
+ ImplCallEventListeners( VclEventId::MenuInsertItem, nPos );
+}
+
+void Menu::InsertItem(sal_uInt16 nItemId, const Image& rImage,
+ MenuItemBits nItemBits, const OString &rIdent, sal_uInt16 nPos)
+{
+ InsertItem(nItemId, OUString(), nItemBits, rIdent, nPos);
+ SetItemImage( nItemId, rImage );
+}
+
+void Menu::InsertItem(sal_uInt16 nItemId, const OUString& rStr,
+ const Image& rImage, MenuItemBits nItemBits,
+ const OString &rIdent, sal_uInt16 nPos)
+{
+ InsertItem(nItemId, rStr, nItemBits, rIdent, nPos);
+ SetItemImage( nItemId, rImage );
+}
+
+void Menu::InsertSeparator(const OString &rIdent, sal_uInt16 nPos)
+{
+ // do nothing if it's a menu bar
+ if (IsMenuBar())
+ return;
+
+ // if position > ItemCount, append
+ if ( nPos >= pItemList->size() )
+ nPos = MENU_APPEND;
+
+ // put separator in item list
+ pItemList->InsertSeparator(rIdent, nPos);
+
+ // update native menu
+ size_t itemPos = ( nPos != MENU_APPEND ) ? nPos : pItemList->size() - 1;
+ MenuItemData *pData = pItemList->GetDataFromPos( itemPos );
+ if( ImplGetSalMenu() && pData && pData->pSalMenuItem )
+ ImplGetSalMenu()->InsertItem( pData->pSalMenuItem.get(), nPos );
+
+ mpLayoutData.reset();
+
+ ImplCallEventListeners( VclEventId::MenuInsertItem, nPos );
+}
+
+void Menu::RemoveItem( sal_uInt16 nPos )
+{
+ bool bRemove = false;
+
+ if ( nPos < GetItemCount() )
+ {
+ // update native menu
+ if( ImplGetSalMenu() )
+ ImplGetSalMenu()->RemoveItem( nPos );
+
+ pItemList->Remove( nPos );
+ bRemove = true;
+ }
+
+ vcl::Window* pWin = ImplGetWindow();
+ if ( pWin )
+ {
+ ImplCalcSize( pWin );
+ if ( pWin->IsVisible() )
+ pWin->Invalidate();
+ }
+ mpLayoutData.reset();
+
+ if ( bRemove )
+ ImplCallEventListeners( VclEventId::MenuRemoveItem, nPos );
+}
+
+static void ImplCopyItem( Menu* pThis, const Menu& rMenu, sal_uInt16 nPos, sal_uInt16 nNewPos )
+{
+ MenuItemType eType = rMenu.GetItemType( nPos );
+
+ if ( eType == MenuItemType::DONTKNOW )
+ return;
+
+ if ( eType == MenuItemType::SEPARATOR )
+ pThis->InsertSeparator( OString(), nNewPos );
+ else
+ {
+ sal_uInt16 nId = rMenu.GetItemId( nPos );
+
+ SAL_WARN_IF( pThis->GetItemPos( nId ) != MENU_ITEM_NOTFOUND, "vcl",
+ "Menu::CopyItem(): ItemId already exists" );
+
+ MenuItemData* pData = rMenu.GetItemList()->GetData( nId );
+
+ if (!pData)
+ return;
+
+ if ( eType == MenuItemType::STRINGIMAGE )
+ pThis->InsertItem( nId, pData->aText, pData->aImage, pData->nBits, pData->sIdent, nNewPos );
+ else if ( eType == MenuItemType::STRING )
+ pThis->InsertItem( nId, pData->aText, pData->nBits, pData->sIdent, nNewPos );
+ else
+ pThis->InsertItem( nId, pData->aImage, pData->nBits, pData->sIdent, nNewPos );
+
+ if ( rMenu.IsItemChecked( nId ) )
+ pThis->CheckItem( nId );
+ if ( !rMenu.IsItemEnabled( nId ) )
+ pThis->EnableItem( nId, false );
+ pThis->SetHelpId( nId, pData->aHelpId );
+ pThis->SetHelpText( nId, pData->aHelpText );
+ pThis->SetAccelKey( nId, pData->aAccelKey );
+ pThis->SetItemCommand( nId, pData->aCommandStr );
+ pThis->SetHelpCommand( nId, pData->aHelpCommandStr );
+
+ PopupMenu* pSubMenu = rMenu.GetPopupMenu( nId );
+ if ( pSubMenu )
+ {
+ // create auto-copy
+ VclPtr<PopupMenu> pNewMenu = VclPtr<PopupMenu>::Create( *pSubMenu );
+ pThis->SetPopupMenu( nId, pNewMenu );
+ }
+ }
+}
+
+void Menu::Clear()
+{
+ for ( sal_uInt16 i = GetItemCount(); i; i-- )
+ RemoveItem( 0 );
+}
+
+sal_uInt16 Menu::GetItemCount() const
+{
+ return static_cast<sal_uInt16>(pItemList->size());
+}
+
+sal_uInt16 Menu::ImplGetVisibleItemCount() const
+{
+ sal_uInt16 nItems = 0;
+ for ( size_t n = pItemList->size(); n; )
+ {
+ if ( ImplIsVisible( --n ) )
+ nItems++;
+ }
+ return nItems;
+}
+
+sal_uInt16 Menu::ImplGetFirstVisible() const
+{
+ for ( size_t n = 0; n < pItemList->size(); n++ )
+ {
+ if ( ImplIsVisible( n ) )
+ return n;
+ }
+ return ITEMPOS_INVALID;
+}
+
+sal_uInt16 Menu::ImplGetPrevVisible( sal_uInt16 nPos ) const
+{
+ for ( size_t n = nPos; n; )
+ {
+ if (ImplIsVisible(--n))
+ return n;
+ }
+ return ITEMPOS_INVALID;
+}
+
+sal_uInt16 Menu::ImplGetNextVisible( sal_uInt16 nPos ) const
+{
+ for ( size_t n = nPos+1; n < pItemList->size(); n++ )
+ {
+ if ( ImplIsVisible( n ) )
+ return n;
+ }
+ return ITEMPOS_INVALID;
+}
+
+sal_uInt16 Menu::GetItemId(sal_uInt16 nPos) const
+{
+ MenuItemData* pData = pItemList->GetDataFromPos( nPos );
+
+ if ( pData )
+ return pData->nId;
+ else
+ return 0;
+}
+
+sal_uInt16 Menu::GetItemId(std::string_view rIdent) const
+{
+ for (size_t n = 0; n < pItemList->size(); ++n)
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos(n);
+ if (pData && pData->sIdent == rIdent)
+ return pData->nId;
+ }
+ return MENU_ITEM_NOTFOUND;
+}
+
+sal_uInt16 Menu::GetItemPos( sal_uInt16 nItemId ) const
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ if ( pData )
+ return static_cast<sal_uInt16>(nPos);
+ else
+ return MENU_ITEM_NOTFOUND;
+}
+
+MenuItemType Menu::GetItemType( sal_uInt16 nPos ) const
+{
+ MenuItemData* pData = pItemList->GetDataFromPos( nPos );
+
+ if ( pData )
+ return pData->eType;
+ else
+ return MenuItemType::DONTKNOW;
+}
+
+OString Menu::GetItemIdent(sal_uInt16 nId) const
+{
+ const MenuItemData* pData = pItemList->GetData(nId);
+ return pData ? pData->sIdent : OString();
+}
+
+void Menu::SetItemBits( sal_uInt16 nItemId, MenuItemBits nBits )
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData(nItemId, nPos);
+
+ if (pData && (pData->nBits != nBits))
+ {
+ pData->nBits = nBits;
+
+ // update native menu
+ if (ImplGetSalMenu())
+ ImplGetSalMenu()->SetItemBits(nPos, nBits);
+ }
+}
+
+MenuItemBits Menu::GetItemBits( sal_uInt16 nItemId ) const
+{
+ MenuItemBits nBits = MenuItemBits::NONE;
+ MenuItemData* pData = pItemList->GetData( nItemId );
+ if ( pData )
+ nBits = pData->nBits;
+ return nBits;
+}
+
+void Menu::SetUserValue(sal_uInt16 nItemId, void* nUserValue, MenuUserDataReleaseFunction aFunc)
+{
+ MenuItemData* pData = pItemList->GetData(nItemId);
+ if (pData)
+ {
+ if (pData->aUserValueReleaseFunc)
+ pData->aUserValueReleaseFunc(pData->nUserValue);
+ pData->aUserValueReleaseFunc = aFunc;
+ pData->nUserValue = nUserValue;
+ }
+}
+
+void* Menu::GetUserValue( sal_uInt16 nItemId ) const
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+ return pData ? pData->nUserValue : nullptr;
+}
+
+void Menu::SetPopupMenu( sal_uInt16 nItemId, PopupMenu* pMenu )
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ // Item does not exist -> return NULL
+ if ( !pData )
+ return;
+
+ // same menu, nothing to do
+ if ( static_cast<PopupMenu*>(pData->pSubMenu.get()) == pMenu )
+ return;
+
+ // remove old menu
+ auto oldSubMenu = pData->pSubMenu;
+
+ // data exchange
+ pData->pSubMenu = pMenu;
+
+ // #112023# Make sure pStartedFrom does not point to invalid (old) data
+ if ( pData->pSubMenu )
+ pData->pSubMenu->pStartedFrom = nullptr;
+
+ // set native submenu
+ if( ImplGetSalMenu() && pData->pSalMenuItem )
+ {
+ if( pMenu )
+ ImplGetSalMenu()->SetSubMenu( pData->pSalMenuItem.get(), pMenu->ImplGetSalMenu(), nPos );
+ else
+ ImplGetSalMenu()->SetSubMenu( pData->pSalMenuItem.get(), nullptr, nPos );
+ }
+
+ oldSubMenu.disposeAndClear();
+
+ ImplCallEventListeners( VclEventId::MenuSubmenuChanged, nPos );
+}
+
+PopupMenu* Menu::GetPopupMenu( sal_uInt16 nItemId ) const
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ return static_cast<PopupMenu*>(pData->pSubMenu.get());
+ else
+ return nullptr;
+}
+
+void Menu::SetAccelKey( sal_uInt16 nItemId, const KeyCode& rKeyCode )
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ if ( !pData )
+ return;
+
+ if ( pData->aAccelKey == rKeyCode )
+ return;
+
+ pData->aAccelKey = rKeyCode;
+
+ // update native menu
+ if( ImplGetSalMenu() && pData->pSalMenuItem )
+ ImplGetSalMenu()->SetAccelerator( nPos, pData->pSalMenuItem.get(), rKeyCode, rKeyCode.GetName() );
+}
+
+KeyCode Menu::GetAccelKey( sal_uInt16 nItemId ) const
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ return pData->aAccelKey;
+ else
+ return KeyCode();
+}
+
+KeyEvent Menu::GetActivationKey( sal_uInt16 nItemId ) const
+{
+ KeyEvent aRet;
+ MenuItemData* pData = pItemList->GetData( nItemId );
+ if( pData )
+ {
+ sal_Int32 nPos = pData->aText.indexOf( '~' );
+ if( nPos != -1 && nPos < pData->aText.getLength()-1 )
+ {
+ sal_uInt16 nCode = 0;
+ sal_Unicode cAccel = pData->aText[nPos+1];
+ if( cAccel >= 'a' && cAccel <= 'z' )
+ nCode = KEY_A + (cAccel-'a');
+ else if( cAccel >= 'A' && cAccel <= 'Z' )
+ nCode = KEY_A + (cAccel-'A');
+ else if( cAccel >= '0' && cAccel <= '9' )
+ nCode = KEY_0 + (cAccel-'0');
+
+ aRet = KeyEvent( cAccel, KeyCode( nCode, KEY_MOD2 ) );
+ }
+
+ }
+ return aRet;
+}
+
+void Menu::CheckItem( sal_uInt16 nItemId, bool bCheck )
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ if ( !pData || pData->bChecked == bCheck )
+ return;
+
+ // if radio-check, then uncheck previous
+ if ( bCheck && (pData->nBits & MenuItemBits::AUTOCHECK) &&
+ (pData->nBits & MenuItemBits::RADIOCHECK) )
+ {
+ MenuItemData* pGroupData;
+ sal_uInt16 nGroupPos;
+ sal_uInt16 nItemCount = GetItemCount();
+ bool bFound = false;
+
+ nGroupPos = nPos;
+ while ( nGroupPos )
+ {
+ pGroupData = pItemList->GetDataFromPos( nGroupPos-1 );
+ if ( pGroupData->nBits & MenuItemBits::RADIOCHECK )
+ {
+ if ( IsItemChecked( pGroupData->nId ) )
+ {
+ CheckItem( pGroupData->nId, false );
+ bFound = true;
+ break;
+ }
+ }
+ else
+ break;
+ nGroupPos--;
+ }
+
+ if ( !bFound )
+ {
+ nGroupPos = nPos+1;
+ while ( nGroupPos < nItemCount )
+ {
+ pGroupData = pItemList->GetDataFromPos( nGroupPos );
+ if ( pGroupData->nBits & MenuItemBits::RADIOCHECK )
+ {
+ if ( IsItemChecked( pGroupData->nId ) )
+ {
+ CheckItem( pGroupData->nId, false );
+ break;
+ }
+ }
+ else
+ break;
+ nGroupPos++;
+ }
+ }
+ }
+
+ pData->bChecked = bCheck;
+
+ // update native menu
+ if( ImplGetSalMenu() )
+ ImplGetSalMenu()->CheckItem( nPos, bCheck );
+
+ ImplCallEventListeners( bCheck ? VclEventId::MenuItemChecked : VclEventId::MenuItemUnchecked, nPos );
+}
+
+void Menu::CheckItem( std::string_view rIdent , bool bCheck )
+{
+ CheckItem( GetItemId( rIdent ), bCheck );
+}
+
+bool Menu::IsItemChecked( sal_uInt16 nItemId ) const
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ if ( !pData )
+ return false;
+
+ return pData->bChecked;
+}
+
+void Menu::EnableItem( sal_uInt16 nItemId, bool bEnable )
+{
+ size_t nPos;
+ MenuItemData* pItemData = pItemList->GetData( nItemId, nPos );
+
+ if ( !(pItemData && ( pItemData->bEnabled != bEnable )) )
+ return;
+
+ pItemData->bEnabled = bEnable;
+
+ vcl::Window* pWin = ImplGetWindow();
+ if ( pWin && pWin->IsVisible() )
+ {
+ SAL_WARN_IF(!IsMenuBar(), "vcl", "Menu::EnableItem - Popup visible!" );
+ tools::Long nX = 0;
+ size_t nCount = pItemList->size();
+ for ( size_t n = 0; n < nCount; n++ )
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos( n );
+ if ( n == nPos )
+ {
+ pWin->Invalidate( tools::Rectangle( Point( nX, 0 ), Size( pData->aSz.Width(), pData->aSz.Height() ) ) );
+ break;
+ }
+ nX += pData->aSz.Width();
+ }
+ }
+ // update native menu
+ if( ImplGetSalMenu() )
+ ImplGetSalMenu()->EnableItem( nPos, bEnable );
+
+ ImplCallEventListeners( bEnable ? VclEventId::MenuEnable : VclEventId::MenuDisable, nPos );
+}
+
+bool Menu::IsItemEnabled( sal_uInt16 nItemId ) const
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ if ( !pData )
+ return false;
+
+ return pData->bEnabled;
+}
+
+void Menu::ShowItem( sal_uInt16 nItemId, bool bVisible )
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ SAL_WARN_IF(IsMenuBar() && !bVisible , "vcl", "Menu::ShowItem - ignored for menu bar entries!");
+ if (IsMenuBar() || !pData || (pData->bVisible == bVisible))
+ return;
+
+ vcl::Window* pWin = ImplGetWindow();
+ if ( pWin && pWin->IsVisible() )
+ {
+ SAL_WARN( "vcl", "Menu::ShowItem - ignored for visible popups!" );
+ return;
+ }
+ pData->bVisible = bVisible;
+
+ // update native menu
+ if( ImplGetSalMenu() )
+ ImplGetSalMenu()->ShowItem( nPos, bVisible );
+}
+
+void Menu::SetItemText( sal_uInt16 nItemId, const OUString& rStr )
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ if ( !pData )
+ return;
+
+ if ( rStr == pData->aText )
+ return;
+
+ pData->aText = rStr;
+ // Clear layout for aText.
+ pData->aTextGlyphs.Invalidate();
+ ImplSetMenuItemData( pData );
+ // update native menu
+ if( ImplGetSalMenu() && pData->pSalMenuItem )
+ ImplGetSalMenu()->SetItemText( nPos, pData->pSalMenuItem.get(), rStr );
+
+ vcl::Window* pWin = ImplGetWindow();
+ mpLayoutData.reset();
+ if (pWin && IsMenuBar())
+ {
+ ImplCalcSize( pWin );
+ if ( pWin->IsVisible() )
+ pWin->Invalidate();
+ }
+
+ ImplCallEventListeners( VclEventId::MenuItemTextChanged, nPos );
+}
+
+OUString Menu::GetItemText( sal_uInt16 nItemId ) const
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ if ( pData )
+ return pData->aText;
+
+ return OUString();
+}
+
+void Menu::SetItemImage( sal_uInt16 nItemId, const Image& rImage )
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ if ( !pData )
+ return;
+
+ pData->aImage = rImage;
+ ImplSetMenuItemData( pData );
+
+ // update native menu
+ if( ImplGetSalMenu() && pData->pSalMenuItem )
+ ImplGetSalMenu()->SetItemImage( nPos, pData->pSalMenuItem.get(), rImage );
+}
+
+Image Menu::GetItemImage( sal_uInt16 nItemId ) const
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ return pData->aImage;
+ else
+ return Image();
+}
+
+void Menu::SetItemCommand( sal_uInt16 nItemId, const OUString& rCommand )
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ if ( pData )
+ pData->aCommandStr = rCommand;
+}
+
+OUString Menu::GetItemCommand( sal_uInt16 nItemId ) const
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if (pData)
+ return pData->aCommandStr;
+
+ return OUString();
+}
+
+void Menu::SetHelpCommand( sal_uInt16 nItemId, const OUString& rStr )
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ pData->aHelpCommandStr = rStr;
+}
+
+OUString Menu::GetHelpCommand( sal_uInt16 nItemId ) const
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ return pData->aHelpCommandStr;
+
+ return OUString();
+}
+
+void Menu::SetHelpText( sal_uInt16 nItemId, const OUString& rStr )
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ pData->aHelpText = rStr;
+}
+
+OUString Menu::ImplGetHelpText( sal_uInt16 nItemId ) const
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if (!pData)
+ return OUString();
+
+ if ( pData->aHelpText.isEmpty() &&
+ (( !pData->aHelpId.isEmpty() ) || ( !pData->aCommandStr.isEmpty() )))
+ {
+ Help* pHelp = Application::GetHelp();
+ if ( pHelp )
+ {
+ if (!pData->aCommandStr.isEmpty())
+ pData->aHelpText = pHelp->GetHelpText( pData->aCommandStr, static_cast<weld::Widget*>(nullptr) );
+ if (pData->aHelpText.isEmpty() && !pData->aHelpId.isEmpty())
+ pData->aHelpText = pHelp->GetHelpText( OStringToOUString( pData->aHelpId, RTL_TEXTENCODING_UTF8 ), static_cast<weld::Widget*>(nullptr) );
+ }
+ }
+
+ return pData->aHelpText;
+}
+
+OUString Menu::GetHelpText( sal_uInt16 nItemId ) const
+{
+ return ImplGetHelpText( nItemId );
+}
+
+void Menu::SetTipHelpText( sal_uInt16 nItemId, const OUString& rStr )
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ pData->aTipHelpText = rStr;
+}
+
+OUString Menu::GetTipHelpText( sal_uInt16 nItemId ) const
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ return pData->aTipHelpText;
+
+ return OUString();
+}
+
+void Menu::SetHelpId( sal_uInt16 nItemId, const OString& rHelpId )
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ pData->aHelpId = rHelpId;
+}
+
+OString Menu::GetHelpId( sal_uInt16 nItemId ) const
+{
+ OString aRet;
+
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ {
+ if ( !pData->aHelpId.isEmpty() )
+ aRet = pData->aHelpId;
+ else
+ aRet = OUStringToOString( pData->aCommandStr, RTL_TEXTENCODING_UTF8 );
+ }
+
+ return aRet;
+}
+
+Menu& Menu::operator=( const Menu& rMenu )
+{
+ if(this == &rMenu)
+ return *this;
+
+ // clean up
+ Clear();
+
+ // copy items
+ sal_uInt16 nCount = rMenu.GetItemCount();
+ for ( sal_uInt16 i = 0; i < nCount; i++ )
+ ImplCopyItem( this, rMenu, i, MENU_APPEND );
+
+ aActivateHdl = rMenu.aActivateHdl;
+ aDeactivateHdl = rMenu.aDeactivateHdl;
+ aSelectHdl = rMenu.aSelectHdl;
+ aTitleText = rMenu.aTitleText;
+ nTitleHeight = rMenu.nTitleHeight;
+
+ return *this;
+}
+
+// Returns true if the item is completely hidden on the GUI and shouldn't
+// be possible to interact with
+bool Menu::ImplCurrentlyHiddenOnGUI(sal_uInt16 nPos) const
+{
+ MenuItemData* pData = pItemList->GetDataFromPos(nPos);
+ if (pData)
+ {
+ MenuItemData* pPreviousData = pItemList->GetDataFromPos( nPos - 1 );
+ if (pPreviousData && pPreviousData->bHiddenOnGUI)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Menu::ImplIsVisible( sal_uInt16 nPos ) const
+{
+ bool bVisible = true;
+
+ MenuItemData* pData = pItemList->GetDataFromPos( nPos );
+ // check general visibility first
+ if( pData && !pData->bVisible )
+ bVisible = false;
+
+ if ( bVisible && pData && pData->eType == MenuItemType::SEPARATOR )
+ {
+ if( nPos == 0 ) // no separator should be shown at the very beginning
+ bVisible = false;
+ else
+ {
+ // always avoid adjacent separators
+ size_t nCount = pItemList->size();
+ size_t n;
+ MenuItemData* pNextData = nullptr;
+ // search next visible item
+ for( n = nPos + 1; n < nCount; n++ )
+ {
+ pNextData = pItemList->GetDataFromPos( n );
+ if( pNextData && pNextData->bVisible )
+ {
+ if( pNextData->eType == MenuItemType::SEPARATOR || ImplIsVisible(n) )
+ break;
+ }
+ }
+ if( n == nCount ) // no next visible item
+ bVisible = false;
+ // check for separator
+ if( pNextData && pNextData->bVisible && pNextData->eType == MenuItemType::SEPARATOR )
+ bVisible = false;
+
+ if( bVisible )
+ {
+ for( n = nPos; n > 0; n-- )
+ {
+ pNextData = pItemList->GetDataFromPos( n-1 );
+ if( pNextData && pNextData->bVisible )
+ {
+ if( pNextData->eType != MenuItemType::SEPARATOR && ImplIsVisible(n-1) )
+ break;
+ }
+ }
+ if( n == 0 ) // no previous visible item
+ bVisible = false;
+ }
+ }
+ }
+
+ // not allowed for menubar, as I do not know
+ // whether a menu-entry will disappear or will appear
+ if (bVisible && !IsMenuBar() && (nMenuFlags & MenuFlags::HideDisabledEntries) &&
+ !(nMenuFlags & MenuFlags::AlwaysShowDisabledEntries))
+ {
+ if( !pData ) // e.g. nPos == ITEMPOS_INVALID
+ bVisible = false;
+ else if ( pData->eType != MenuItemType::SEPARATOR ) // separators handled above
+ {
+ // tdf#86850 Always display clipboard functions
+ if ( pData->aCommandStr == ".uno:Cut" || pData->aCommandStr == ".uno:Copy" || pData->aCommandStr == ".uno:Paste" ||
+ pData->sIdent == ".uno:Cut" || pData->sIdent == ".uno:Copy" || pData->sIdent == ".uno:Paste" )
+ bVisible = true;
+ else
+ // bVisible = pData->bEnabled && ( !pData->pSubMenu || pData->pSubMenu->HasValidEntries( true ) );
+ bVisible = pData->bEnabled; // do not check submenus as they might be filled at Activate().
+ }
+ }
+
+ return bVisible;
+}
+
+bool Menu::IsItemPosVisible( sal_uInt16 nItemPos ) const
+{
+ return IsMenuVisible() && ImplIsVisible( nItemPos );
+}
+
+bool Menu::IsMenuVisible() const
+{
+ return pWindow && pWindow->IsReallyVisible();
+}
+
+bool Menu::ImplIsSelectable( sal_uInt16 nPos ) const
+{
+ bool bSelectable = true;
+
+ MenuItemData* pData = pItemList->GetDataFromPos( nPos );
+ // check general visibility first
+ if ( pData && ( pData->nBits & MenuItemBits::NOSELECT ) )
+ bSelectable = false;
+
+ return bSelectable;
+}
+
+css::uno::Reference<css::accessibility::XAccessible> Menu::GetAccessible()
+{
+ // Since PopupMenu are sometimes shared by different instances of MenuBar, the mxAccessible member gets
+ // overwritten and may contain a disposed object when the initial menubar gets set again. So use the
+ // mxAccessible member only for sub menus.
+ if (pStartedFrom && pStartedFrom != this)
+ {
+ for ( sal_uInt16 i = 0, nCount = pStartedFrom->GetItemCount(); i < nCount; ++i )
+ {
+ sal_uInt16 nItemId = pStartedFrom->GetItemId( i );
+ if ( static_cast< Menu* >( pStartedFrom->GetPopupMenu( nItemId ) ) == this )
+ {
+ css::uno::Reference<css::accessibility::XAccessible> xParent = pStartedFrom->GetAccessible();
+ if ( xParent.is() )
+ {
+ css::uno::Reference<css::accessibility::XAccessibleContext> xParentContext( xParent->getAccessibleContext() );
+ if (xParentContext.is())
+ return xParentContext->getAccessibleChild( i );
+ }
+ }
+ }
+ }
+ else if ( !mxAccessible.is() )
+ {
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper();
+ if ( pWrapper )
+ mxAccessible = pWrapper->CreateAccessible(this, IsMenuBar());
+ }
+
+ return mxAccessible;
+}
+
+void Menu::SetAccessible(const css::uno::Reference<css::accessibility::XAccessible>& rxAccessible )
+{
+ mxAccessible = rxAccessible;
+}
+
+Size Menu::ImplGetNativeCheckAndRadioSize(vcl::RenderContext const & rRenderContext, tools::Long& rCheckHeight, tools::Long& rRadioHeight ) const
+{
+ tools::Long nCheckWidth = 0, nRadioWidth = 0;
+ rCheckHeight = rRadioHeight = 0;
+
+ if (!IsMenuBar())
+ {
+ ImplControlValue aVal;
+ tools::Rectangle aNativeBounds;
+ tools::Rectangle aNativeContent;
+
+ tools::Rectangle aCtrlRegion(tools::Rectangle(Point(), Size(100, 15)));
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItemCheckMark))
+ {
+ if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::MenuItemCheckMark,
+ aCtrlRegion, ControlState::ENABLED, aVal,
+ aNativeBounds, aNativeContent))
+ {
+ rCheckHeight = aNativeBounds.GetHeight() - 1;
+ nCheckWidth = aNativeContent.GetWidth() - 1;
+ }
+ }
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItemRadioMark))
+ {
+ if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::MenuItemRadioMark,
+ aCtrlRegion, ControlState::ENABLED, aVal,
+ aNativeBounds, aNativeContent))
+ {
+ rRadioHeight = aNativeBounds.GetHeight() - 1;
+ nRadioWidth = aNativeContent.GetWidth() - 1;
+ }
+ }
+ }
+ return Size(std::max(nCheckWidth, nRadioWidth), std::max(rCheckHeight, rRadioHeight));
+}
+
+bool Menu::ImplGetNativeSubmenuArrowSize(vcl::RenderContext const & rRenderContext, Size& rArrowSize, tools::Long& rArrowSpacing)
+{
+ ImplControlValue aVal;
+ tools::Rectangle aCtrlRegion(tools::Rectangle(Point(), Size(100, 15)));
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::SubmenuArrow))
+ {
+ tools::Rectangle aNativeContent;
+ tools::Rectangle aNativeBounds;
+ if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::SubmenuArrow,
+ aCtrlRegion, ControlState::ENABLED,
+ aVal, aNativeBounds, aNativeContent))
+ {
+ Size aSize(aNativeContent.GetWidth(), aNativeContent.GetHeight());
+ rArrowSize = aSize;
+ rArrowSpacing = aNativeBounds.GetWidth() - aNativeContent.GetWidth();
+ return true;
+ }
+ }
+ return false;
+}
+
+void Menu::ImplAddDel( ImplMenuDelData& rDel )
+{
+ SAL_WARN_IF( rDel.mpMenu, "vcl", "Menu::ImplAddDel(): cannot add ImplMenuDelData twice !" );
+ if( !rDel.mpMenu )
+ {
+ rDel.mpMenu = this;
+ rDel.mpNext = mpFirstDel;
+ mpFirstDel = &rDel;
+ }
+}
+
+void Menu::ImplRemoveDel( ImplMenuDelData& rDel )
+{
+ rDel.mpMenu = nullptr;
+ if ( mpFirstDel == &rDel )
+ {
+ mpFirstDel = rDel.mpNext;
+ }
+ else
+ {
+ ImplMenuDelData* pData = mpFirstDel;
+ while ( pData && (pData->mpNext != &rDel) )
+ pData = pData->mpNext;
+
+ SAL_WARN_IF( !pData, "vcl", "Menu::ImplRemoveDel(): ImplMenuDelData not registered !" );
+ if( pData )
+ pData->mpNext = rDel.mpNext;
+ }
+}
+
+Size Menu::ImplCalcSize( vcl::Window* pWin )
+{
+ // | Check/Radio/Image| Text| Accel/Popup|
+
+ // for symbols: nFontHeight x nFontHeight
+ tools::Long nFontHeight = pWin->GetTextHeight();
+ tools::Long nExtra = nFontHeight/4;
+
+ tools::Long nMinMenuItemHeight = nFontHeight;
+ tools::Long nCheckHeight = 0, nRadioHeight = 0;
+ Size aMarkSize = ImplGetNativeCheckAndRadioSize(*pWin->GetOutDev(), nCheckHeight, nRadioHeight);
+ if( aMarkSize.Height() > nMinMenuItemHeight )
+ nMinMenuItemHeight = aMarkSize.Height();
+
+ tools::Long aMaxImgWidth = 0;
+
+ const StyleSettings& rSettings = pWin->GetSettings().GetStyleSettings();
+ if ( rSettings.GetUseImagesInMenus() )
+ {
+ if ( 16 > nMinMenuItemHeight )
+ nMinMenuItemHeight = 16;
+ for ( size_t i = pItemList->size(); i; )
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos( --i );
+ if ( ImplIsVisible( i )
+ && ( ( pData->eType == MenuItemType::IMAGE )
+ || ( pData->eType == MenuItemType::STRINGIMAGE )
+ )
+ )
+ {
+ Size aImgSz = pData->aImage.GetSizePixel();
+ if ( aImgSz.Width() > aMaxImgWidth )
+ aMaxImgWidth = aImgSz.Width();
+ if ( aImgSz.Height() > nMinMenuItemHeight )
+ nMinMenuItemHeight = aImgSz.Height();
+ break;
+ }
+ }
+ }
+
+ Size aSz;
+ tools::Long nMaxWidth = 0;
+
+ for ( size_t n = pItemList->size(); n; )
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos( --n );
+
+ pData->aSz.setHeight( 0 );
+ pData->aSz.setWidth( 0 );
+
+ if ( ImplIsVisible( n ) )
+ {
+ tools::Long nWidth = 0;
+
+ // Separator
+ if (!IsMenuBar()&& (pData->eType == MenuItemType::SEPARATOR))
+ {
+ pData->aSz.setHeight( 4 );
+ }
+
+ // Image:
+ if (!IsMenuBar() && ((pData->eType == MenuItemType::IMAGE) || (pData->eType == MenuItemType::STRINGIMAGE)))
+ {
+ tools::Long aImgHeight = pData->aImage.GetSizePixel().Height();
+
+ aImgHeight += 4; // add a border for native marks
+ if (aImgHeight > pData->aSz.Height())
+ pData->aSz.setHeight(aImgHeight);
+ }
+
+ // Check Buttons:
+ if (!IsMenuBar() && pData->HasCheck())
+ {
+ // checks / images take the same place
+ if( ( pData->eType != MenuItemType::IMAGE ) && ( pData->eType != MenuItemType::STRINGIMAGE ) )
+ {
+ nWidth += aMarkSize.Width() + nExtra * 2;
+ if (aMarkSize.Height() > pData->aSz.Height())
+ pData->aSz.setHeight(aMarkSize.Height());
+ }
+ }
+
+ // Text:
+ if ( (pData->eType == MenuItemType::STRING) || (pData->eType == MenuItemType::STRINGIMAGE) )
+ {
+ const SalLayoutGlyphs* pGlyphs = pData->GetTextGlyphs(pWin->GetOutDev());
+ tools::Long nTextWidth = pWin->GetOutDev()->GetCtrlTextWidth(pData->aText, pGlyphs);
+ tools::Long nTextHeight = pWin->GetTextHeight() + EXTRAITEMHEIGHT;
+
+ if (IsMenuBar())
+ {
+ if ( nTextHeight > pData->aSz.Height() )
+ pData->aSz.setHeight( nTextHeight );
+
+ pData->aSz.setWidth( nTextWidth + 4*nExtra );
+ aSz.AdjustWidth(pData->aSz.Width() );
+ }
+ else
+ pData->aSz.setHeight( std::max( std::max( nTextHeight, pData->aSz.Height() ), nMinMenuItemHeight ) );
+
+ nWidth += nTextWidth;
+ }
+
+ // Accel
+ if (!IsMenuBar()&& pData->aAccelKey.GetCode() && !ImplAccelDisabled())
+ {
+ OUString aName = pData->aAccelKey.GetName();
+ tools::Long nAccWidth = pWin->GetTextWidth( aName );
+ nAccWidth += nExtra;
+ nWidth += nAccWidth;
+ }
+
+ // SubMenu?
+ if (!IsMenuBar() && pData->pSubMenu)
+ {
+ if ( nFontHeight > nWidth )
+ nWidth += nFontHeight;
+
+ pData->aSz.setHeight( std::max( std::max( nFontHeight, pData->aSz.Height() ), nMinMenuItemHeight ) );
+ }
+
+ if (!IsMenuBar())
+ aSz.AdjustHeight(pData->aSz.Height() );
+
+ if ( nWidth > nMaxWidth )
+ nMaxWidth = nWidth;
+
+ }
+ }
+
+ // Additional space for title
+ nTitleHeight = 0;
+ if (!IsMenuBar() && aTitleText.getLength() > 0) {
+ // Set expected font
+ pWin->GetOutDev()->Push(PushFlags::FONT);
+ vcl::Font aFont = pWin->GetFont();
+ aFont.SetWeight(WEIGHT_BOLD);
+ pWin->SetFont(aFont);
+
+ // Compute text bounding box
+ tools::Rectangle aTextBoundRect;
+ pWin->GetOutDev()->GetTextBoundRect(aTextBoundRect, aTitleText);
+
+ // Vertically, one height of char + extra space for decoration
+ nTitleHeight = aTextBoundRect.GetSize().Height() + 4 * SPACE_AROUND_TITLE ;
+ aSz.AdjustHeight(nTitleHeight );
+
+ tools::Long nWidth = aTextBoundRect.GetSize().Width() + 4 * SPACE_AROUND_TITLE;
+ pWin->GetOutDev()->Pop();
+ if ( nWidth > nMaxWidth )
+ nMaxWidth = nWidth;
+ }
+
+ if (!IsMenuBar())
+ {
+ // popup menus should not be wider than half the screen
+ // except on rather small screens
+ // TODO: move GetScreenNumber from SystemWindow to Window ?
+ // currently we rely on internal privileges
+ unsigned int nDisplayScreen = pWin->ImplGetWindowImpl()->mpFrame->maGeometry.nDisplayScreenNumber;
+ tools::Rectangle aDispRect( Application::GetScreenPosSizePixel( nDisplayScreen ) );
+ tools::Long nScreenWidth = aDispRect.GetWidth() >= 800 ? aDispRect.GetWidth() : 800;
+ if( nMaxWidth > nScreenWidth/2 )
+ nMaxWidth = nScreenWidth/2;
+
+ sal_uInt16 gfxExtra = static_cast<sal_uInt16>(std::max( nExtra, tools::Long(7) )); // #107710# increase space between checkmarks/images/text
+ nImgOrChkPos = static_cast<sal_uInt16>(nExtra);
+ tools::Long nImgOrChkWidth = 0;
+ if( aMarkSize.Height() > 0 ) // NWF case
+ nImgOrChkWidth = aMarkSize.Height() + nExtra;
+ else // non NWF case
+ nImgOrChkWidth = nFontHeight/2 + gfxExtra;
+ nImgOrChkWidth = std::max( nImgOrChkWidth, aMaxImgWidth + gfxExtra );
+ nTextPos = static_cast<sal_uInt16>(nImgOrChkPos + nImgOrChkWidth);
+ nTextPos = nTextPos + gfxExtra;
+
+ aSz.setWidth( nTextPos + nMaxWidth + nExtra );
+ aSz.AdjustWidth(4*nExtra ); // a _little_ more ...
+
+ aSz.AdjustWidth(2*ImplGetSVData()->maNWFData.mnMenuFormatBorderX );
+ aSz.AdjustHeight(2*ImplGetSVData()->maNWFData.mnMenuFormatBorderY );
+ }
+ else
+ {
+ nTextPos = static_cast<sal_uInt16>(2*nExtra);
+ aSz.setHeight( nFontHeight+6 );
+
+ // get menubar height from native methods if supported
+ if( pWindow->IsNativeControlSupported( ControlType::Menubar, ControlPart::Entire ) )
+ {
+ ImplControlValue aVal;
+ tools::Rectangle aNativeBounds;
+ tools::Rectangle aNativeContent;
+ Point tmp( 0, 0 );
+ tools::Rectangle aCtrlRegion( tmp, Size( 100, 15 ) );
+ if( pWindow->GetNativeControlRegion( ControlType::Menubar,
+ ControlPart::Entire,
+ aCtrlRegion,
+ ControlState::ENABLED,
+ aVal,
+ aNativeBounds,
+ aNativeContent )
+ )
+ {
+ int nNativeHeight = aNativeBounds.GetHeight();
+ if( nNativeHeight > aSz.Height() )
+ aSz.setHeight( nNativeHeight );
+ }
+ }
+
+ // account for the size of the close button, which actually is a toolbox
+ // due to NWF this is variable
+ tools::Long nCloseButtonHeight = static_cast<MenuBarWindow*>(pWindow.get())->MinCloseButtonSize().Height();
+ if (aSz.Height() < nCloseButtonHeight)
+ aSz.setHeight( nCloseButtonHeight );
+ }
+
+ return aSz;
+}
+
+static void ImplPaintCheckBackground(vcl::RenderContext & rRenderContext, vcl::Window const & rWindow, const tools::Rectangle& i_rRect, bool i_bHighlight)
+{
+ bool bNativeOk = false;
+ if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Button))
+ {
+ ImplControlValue aControlValue;
+ aControlValue.setTristateVal(ButtonValue::On);
+ tools::Rectangle r = i_rRect;
+ r.AdjustBottom(1);
+
+ bNativeOk = rRenderContext.DrawNativeControl(ControlType::Toolbar, ControlPart::Button,
+ r,
+ ControlState::PRESSED | ControlState::ENABLED,
+ aControlValue,
+ OUString());
+ }
+
+ if (!bNativeOk)
+ {
+ const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
+ Color aColor( i_bHighlight ? rSettings.GetMenuHighlightTextColor() : rSettings.GetHighlightColor() );
+ RenderTools::DrawSelectionBackground(rRenderContext, rWindow, i_rRect, 0, i_bHighlight, true, false, nullptr, 2, &aColor);
+ }
+}
+
+static OUString getShortenedString( const OUString& i_rLong, vcl::RenderContext const & rRenderContext, tools::Long i_nMaxWidth )
+{
+ sal_Int32 nPos = -1;
+ OUString aNonMnem(OutputDevice::GetNonMnemonicString(i_rLong, nPos));
+ aNonMnem = rRenderContext.GetEllipsisString( aNonMnem, i_nMaxWidth, DrawTextFlags::CenterEllipsis);
+ // re-insert mnemonic
+ if (nPos != -1)
+ {
+ if (nPos < aNonMnem.getLength() && i_rLong[nPos+1] == aNonMnem[nPos])
+ {
+ OUString aTmp = OUString::Concat(aNonMnem.subView(0, nPos)) + "~" + aNonMnem.subView(nPos);
+ aNonMnem = aTmp;
+ }
+ }
+ return aNonMnem;
+}
+
+void Menu::ImplPaintMenuTitle(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) const
+{
+ // Save previous graphical settings, set new one
+ rRenderContext.Push(PushFlags::FONT | PushFlags::FILLCOLOR);
+ Wallpaper aOldBackground = rRenderContext.GetBackground();
+
+ Color aBackgroundColor = rRenderContext.GetSettings().GetStyleSettings().GetMenuBarColor();
+ rRenderContext.SetBackground(Wallpaper(aBackgroundColor));
+ rRenderContext.SetFillColor(aBackgroundColor);
+ vcl::Font aFont = rRenderContext.GetFont();
+ aFont.SetWeight(WEIGHT_BOLD);
+ rRenderContext.SetFont(aFont);
+
+ // Draw background rectangle
+ tools::Rectangle aBgRect(rRect);
+ int nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX;
+ aBgRect.Move(SPACE_AROUND_TITLE, SPACE_AROUND_TITLE);
+ aBgRect.setWidth(aBgRect.getWidth() - 2 * SPACE_AROUND_TITLE - 2 * nOuterSpaceX);
+ aBgRect.setHeight(nTitleHeight - 2 * SPACE_AROUND_TITLE);
+ rRenderContext.DrawRect(aBgRect);
+
+ // Draw the text centered
+ Point aTextTopLeft(aBgRect.TopLeft());
+ tools::Rectangle aTextBoundRect;
+ rRenderContext.GetTextBoundRect( aTextBoundRect, aTitleText );
+ aTextTopLeft.AdjustX((aBgRect.getWidth() - aTextBoundRect.GetSize().Width()) / 2 );
+ aTextTopLeft.AdjustY((aBgRect.GetHeight() - aTextBoundRect.GetSize().Height()) / 2
+ - aTextBoundRect.Top() );
+ rRenderContext.DrawText(aTextTopLeft, aTitleText, 0, aTitleText.getLength());
+
+ // Restore
+ rRenderContext.Pop();
+ rRenderContext.SetBackground(aOldBackground);
+}
+
+void Menu::ImplPaint(vcl::RenderContext& rRenderContext, Size const & rSize,
+ sal_uInt16 nBorder, tools::Long nStartY, MenuItemData const * pThisItemOnly,
+ bool bHighlighted, bool bLayout, bool bRollover) const
+{
+ // for symbols: nFontHeight x nFontHeight
+ tools::Long nFontHeight = rRenderContext.GetTextHeight();
+ tools::Long nExtra = nFontHeight / 4;
+
+ tools::Long nCheckHeight = 0, nRadioHeight = 0;
+ ImplGetNativeCheckAndRadioSize(rRenderContext, nCheckHeight, nRadioHeight);
+
+ DecorationView aDecoView(&rRenderContext);
+ const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ Point aTopLeft, aTmpPos;
+
+ int nOuterSpaceX = 0;
+ if (!IsMenuBar())
+ {
+ nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX;
+ aTopLeft.AdjustX(nOuterSpaceX );
+ aTopLeft.AdjustY(ImplGetSVData()->maNWFData.mnMenuFormatBorderY );
+ }
+
+ // for the computations, use size of the underlying window, not of RenderContext
+ Size aOutSz(rSize);
+
+ size_t nCount = pItemList->size();
+ if (bLayout)
+ mpLayoutData->m_aVisibleItemBoundRects.clear();
+
+ // Paint title
+ if (!pThisItemOnly && !IsMenuBar() && nTitleHeight > 0)
+ ImplPaintMenuTitle(rRenderContext, tools::Rectangle(aTopLeft, aOutSz));
+
+ bool bHiddenItems = false; // are any items on the GUI hidden
+
+ for (size_t n = 0; n < nCount; n++)
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos( n );
+ if (ImplIsVisible(n) && (!pThisItemOnly || (pData == pThisItemOnly)))
+ {
+ if (pThisItemOnly)
+ {
+ if (IsMenuBar())
+ {
+ if (!ImplGetSVData()->maNWFData.mbRolloverMenubar)
+ {
+ if (bRollover)
+ rRenderContext.SetTextColor(rSettings.GetMenuBarRolloverTextColor());
+ else if (bHighlighted)
+ rRenderContext.SetTextColor(rSettings.GetMenuBarHighlightTextColor());
+ }
+ else
+ {
+ if (bHighlighted)
+ rRenderContext.SetTextColor(rSettings.GetMenuBarHighlightTextColor());
+ else if (bRollover)
+ rRenderContext.SetTextColor(rSettings.GetMenuBarRolloverTextColor());
+ }
+ if (!bRollover && !bHighlighted)
+ rRenderContext.SetTextColor(rSettings.GetMenuBarTextColor());
+ }
+ else if (bHighlighted)
+ rRenderContext.SetTextColor(rSettings.GetMenuHighlightTextColor());
+ }
+
+ Point aPos(aTopLeft);
+ aPos.AdjustY(nBorder );
+ aPos.AdjustY(nStartY );
+
+ if (aPos.Y() >= 0)
+ {
+ tools::Long nTextOffsetY = (pData->aSz.Height() - nFontHeight) / 2;
+ if (IsMenuBar())
+ nTextOffsetY += (aOutSz.Height()-pData->aSz.Height()) / 2;
+ DrawTextFlags nTextStyle = DrawTextFlags::NONE;
+ DrawSymbolFlags nSymbolStyle = DrawSymbolFlags::NONE;
+ DrawImageFlags nImageStyle = DrawImageFlags::NONE;
+
+ // submenus without items are not disabled when no items are
+ // contained. The application itself should check for this!
+ // Otherwise it could happen entries are disabled due to
+ // asynchronous loading
+ if (!pData->bEnabled || !pWindow->IsEnabled())
+ {
+ nTextStyle |= DrawTextFlags::Disable;
+ nSymbolStyle |= DrawSymbolFlags::Disable;
+ nImageStyle |= DrawImageFlags::Disable;
+ }
+
+ // Separator
+ if (!bLayout && !IsMenuBar() && (pData->eType == MenuItemType::SEPARATOR))
+ {
+ bool bNativeOk = false;
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Separator))
+ {
+ ControlState nState = ControlState::NONE;
+ if (pData->bEnabled && pWindow->IsEnabled())
+ nState |= ControlState::ENABLED;
+ if (bHighlighted)
+ nState |= ControlState::SELECTED;
+ Size aSz(pData->aSz);
+ aSz.setWidth( aOutSz.Width() - 2*nOuterSpaceX );
+ tools::Rectangle aItemRect(aPos, aSz);
+ MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect);
+ bNativeOk = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Separator,
+ aItemRect, nState, aVal, OUString());
+ }
+ if (!bNativeOk)
+ {
+ aTmpPos.setY( aPos.Y() + ((pData->aSz.Height() - 2) / 2) );
+ aTmpPos.setX( aPos.X() + 2 + nOuterSpaceX );
+ rRenderContext.SetLineColor(rSettings.GetShadowColor());
+ rRenderContext.DrawLine(aTmpPos, Point(aOutSz.Width() - 3 - 2 * nOuterSpaceX, aTmpPos.Y()));
+ aTmpPos.AdjustY( 1 );
+ rRenderContext.SetLineColor(rSettings.GetLightColor());
+ rRenderContext.DrawLine(aTmpPos, Point(aOutSz.Width() - 3 - 2 * nOuterSpaceX, aTmpPos.Y()));
+ rRenderContext.SetLineColor();
+ }
+ }
+
+ tools::Rectangle aOuterCheckRect(Point(aPos.X()+nImgOrChkPos, aPos.Y()),
+ Size(pData->aSz.Height(), pData->aSz.Height()));
+
+ // CheckMark
+ if (!bLayout && !IsMenuBar() && pData->HasCheck())
+ {
+ // draw selection transparent marker if checked
+ // onto that either a checkmark or the item image
+ // will be painted
+ // however do not do this if native checks will be painted since
+ // the selection color too often does not fit the theme's check and/or radio
+
+ if( (pData->eType != MenuItemType::IMAGE) && (pData->eType != MenuItemType::STRINGIMAGE))
+ {
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup,
+ (pData->nBits & MenuItemBits::RADIOCHECK)
+ ? ControlPart::MenuItemCheckMark
+ : ControlPart::MenuItemRadioMark))
+ {
+ ControlPart nPart = ((pData->nBits & MenuItemBits::RADIOCHECK)
+ ? ControlPart::MenuItemRadioMark
+ : ControlPart::MenuItemCheckMark);
+
+ ControlState nState = ControlState::NONE;
+
+ if (pData->bChecked)
+ nState |= ControlState::PRESSED;
+
+ if (pData->bEnabled && pWindow->IsEnabled())
+ nState |= ControlState::ENABLED;
+
+ if (bHighlighted)
+ nState |= ControlState::SELECTED;
+
+ tools::Long nCtrlHeight = (pData->nBits & MenuItemBits::RADIOCHECK) ? nCheckHeight : nRadioHeight;
+ aTmpPos.setX( aOuterCheckRect.Left() + (aOuterCheckRect.GetWidth() - nCtrlHeight) / 2 );
+ aTmpPos.setY( aOuterCheckRect.Top() + (aOuterCheckRect.GetHeight() - nCtrlHeight) / 2 );
+
+ tools::Rectangle aCheckRect(aTmpPos, Size(nCtrlHeight, nCtrlHeight));
+ Size aSz(pData->aSz);
+ aSz.setWidth( aOutSz.Width() - 2 * nOuterSpaceX );
+ tools::Rectangle aItemRect(aPos, aSz);
+ MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect);
+ rRenderContext.DrawNativeControl(ControlType::MenuPopup, nPart, aCheckRect,
+ nState, aVal, OUString());
+ }
+ else if (pData->bChecked) // by default do nothing for unchecked items
+ {
+ ImplPaintCheckBackground(rRenderContext, *pWindow, aOuterCheckRect, pThisItemOnly && bHighlighted);
+
+ SymbolType eSymbol;
+ Size aSymbolSize;
+ if (pData->nBits & MenuItemBits::RADIOCHECK)
+ {
+ eSymbol = SymbolType::RADIOCHECKMARK;
+ aSymbolSize = Size(nFontHeight / 2, nFontHeight / 2);
+ }
+ else
+ {
+ eSymbol = SymbolType::CHECKMARK;
+ aSymbolSize = Size((nFontHeight * 25) / 40, nFontHeight / 2);
+ }
+ aTmpPos.setX( aOuterCheckRect.Left() + (aOuterCheckRect.GetWidth() - aSymbolSize.Width()) / 2 );
+ aTmpPos.setY( aOuterCheckRect.Top() + (aOuterCheckRect.GetHeight() - aSymbolSize.Height()) / 2 );
+ tools::Rectangle aRect(aTmpPos, aSymbolSize);
+ aDecoView.DrawSymbol(aRect, eSymbol, rRenderContext.GetTextColor(), nSymbolStyle);
+ }
+ }
+ }
+
+ // Image:
+ if (!bLayout && !IsMenuBar() && ((pData->eType == MenuItemType::IMAGE) || (pData->eType == MenuItemType::STRINGIMAGE)))
+ {
+ // Don't render an image for a check thing
+ if (pData->bChecked)
+ ImplPaintCheckBackground(rRenderContext, *pWindow, aOuterCheckRect, pThisItemOnly && bHighlighted);
+
+ Image aImage = pData->aImage;
+
+ aTmpPos = aOuterCheckRect.TopLeft();
+ aTmpPos.AdjustX((aOuterCheckRect.GetWidth() - aImage.GetSizePixel().Width()) / 2 );
+ aTmpPos.AdjustY((aOuterCheckRect.GetHeight() - aImage.GetSizePixel().Height()) / 2 );
+ rRenderContext.DrawImage(aTmpPos, aImage, nImageStyle);
+ }
+
+ // Text:
+ if ((pData->eType == MenuItemType::STRING ) || (pData->eType == MenuItemType::STRINGIMAGE))
+ {
+ aTmpPos.setX( aPos.X() + nTextPos );
+ aTmpPos.setY( aPos.Y() );
+ aTmpPos.AdjustY(nTextOffsetY );
+ DrawTextFlags nStyle = nTextStyle | DrawTextFlags::Mnemonic;
+
+ if (pData->bIsTemporary)
+ nStyle |= DrawTextFlags::Disable;
+ std::vector< tools::Rectangle >* pVector = bLayout ? &mpLayoutData->m_aUnicodeBoundRects : nullptr;
+ OUString* pDisplayText = bLayout ? &mpLayoutData->m_aDisplayText : nullptr;
+ if (bLayout)
+ {
+ mpLayoutData->m_aLineIndices.push_back(mpLayoutData->m_aDisplayText.getLength());
+ mpLayoutData->m_aLineItemIds.push_back(pData->nId);
+ }
+ // #i47946# with NWF painted menus the background is transparent
+ // since DrawCtrlText can depend on the background (e.g. for
+ // DrawTextFlags::Disable), temporarily set a background which
+ // hopefully matches the NWF background since it is read
+ // from the system style settings
+ bool bSetTmpBackground = !rRenderContext.IsBackground()
+ && rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire);
+ if (bSetTmpBackground)
+ {
+ Color aBg = IsMenuBar() ? rRenderContext.GetSettings().GetStyleSettings().GetMenuBarColor()
+ : rRenderContext.GetSettings().GetStyleSettings().GetMenuColor();
+ rRenderContext.SetBackground(Wallpaper(aBg));
+ }
+ // how much space is there for the text?
+ tools::Long nMaxItemTextWidth = aOutSz.Width() - aTmpPos.X() - nExtra - nOuterSpaceX;
+ if (!IsMenuBar() && pData->aAccelKey.GetCode() && !ImplAccelDisabled())
+ {
+ OUString aAccText = pData->aAccelKey.GetName();
+ nMaxItemTextWidth -= rRenderContext.GetTextWidth(aAccText) + 3 * nExtra;
+ }
+ if (!IsMenuBar() && pData->pSubMenu)
+ {
+ nMaxItemTextWidth -= nFontHeight - nExtra;
+ }
+
+ OUString aItemText(pData->aText);
+ pData->bHiddenOnGUI = false;
+
+ if (IsMenuBar()) // In case of menubar if we are out of bounds we shouldn't paint the item
+ {
+ if (nMaxItemTextWidth < rRenderContext.GetTextWidth(aItemText))
+ {
+ aItemText = "";
+ pData->bHiddenOnGUI = true;
+ bHiddenItems = true;
+ }
+ }
+ else
+ {
+ aItemText = getShortenedString(aItemText, rRenderContext, nMaxItemTextWidth);
+ pData->bHiddenOnGUI = false;
+ }
+
+ const SalLayoutGlyphs* pGlyphs = pData->GetTextGlyphs(&rRenderContext);
+ if (aItemText != pData->aText)
+ // Can't use pre-computed glyphs, item text was
+ // changed.
+ pGlyphs = nullptr;
+ rRenderContext.DrawCtrlText(aTmpPos, aItemText, 0, aItemText.getLength(),
+ nStyle, pVector, pDisplayText, pGlyphs);
+ if (bSetTmpBackground)
+ rRenderContext.SetBackground();
+ }
+
+ // Accel
+ if (!bLayout && !IsMenuBar() && pData->aAccelKey.GetCode() && !ImplAccelDisabled())
+ {
+ OUString aAccText = pData->aAccelKey.GetName();
+ aTmpPos.setX( aOutSz.Width() - rRenderContext.GetTextWidth(aAccText) );
+ aTmpPos.AdjustX( -(4 * nExtra) );
+
+ aTmpPos.AdjustX( -nOuterSpaceX );
+ aTmpPos.setY( aPos.Y() );
+ aTmpPos.AdjustY(nTextOffsetY );
+ rRenderContext.DrawCtrlText(aTmpPos, aAccText, 0, aAccText.getLength(), nTextStyle);
+ }
+
+ // SubMenu?
+ if (!bLayout && !IsMenuBar() && pData->pSubMenu)
+ {
+ bool bNativeOk = false;
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::SubmenuArrow))
+ {
+ ControlState nState = ControlState::NONE;
+ Size aTmpSz(0, 0);
+ tools::Long aSpacing = 0;
+
+ if (!ImplGetNativeSubmenuArrowSize(rRenderContext, aTmpSz, aSpacing))
+ {
+ aTmpSz = Size(nFontHeight, nFontHeight);
+ aSpacing = nOuterSpaceX;
+ }
+
+ if (pData->bEnabled && pWindow->IsEnabled())
+ nState |= ControlState::ENABLED;
+ if (bHighlighted)
+ nState |= ControlState::SELECTED;
+
+ aTmpPos.setX( aOutSz.Width() - aTmpSz.Width() - aSpacing - nOuterSpaceX );
+ aTmpPos.setY( aPos.Y() + ( pData->aSz.Height() - aTmpSz.Height() ) / 2 );
+ aTmpPos.AdjustY(nExtra / 2 );
+
+ tools::Rectangle aItemRect(aTmpPos, aTmpSz);
+ MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect);
+ bNativeOk = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::SubmenuArrow,
+ aItemRect, nState, aVal, OUString());
+ }
+ if (!bNativeOk)
+ {
+ aTmpPos.setX( aOutSz.Width() - nFontHeight + nExtra - nOuterSpaceX );
+ aTmpPos.setY( aPos.Y() );
+ aTmpPos.AdjustY(nExtra/2 );
+ aTmpPos.AdjustY((pData->aSz.Height() / 2) - (nFontHeight / 4) );
+ if (pData->nBits & MenuItemBits::POPUPSELECT)
+ {
+ rRenderContext.SetTextColor(rSettings.GetMenuTextColor());
+ Point aTmpPos2(aPos);
+ aTmpPos2.setX( aOutSz.Width() - nFontHeight - nFontHeight/4 );
+ aDecoView.DrawFrame(tools::Rectangle(aTmpPos2, Size(nFontHeight + nFontHeight / 4,
+ pData->aSz.Height())),
+ DrawFrameStyle::Group);
+ }
+ aDecoView.DrawSymbol(tools::Rectangle(aTmpPos, Size(nFontHeight / 2, nFontHeight / 2)),
+ SymbolType::SPIN_RIGHT, rRenderContext.GetTextColor(), nSymbolStyle);
+ }
+ }
+
+ if (pThisItemOnly && bHighlighted)
+ {
+ // This restores the normal menu or menu bar text
+ // color for when it is no longer highlighted.
+ if (IsMenuBar())
+ rRenderContext.SetTextColor(rSettings.GetMenuBarTextColor());
+ else
+ rRenderContext.SetTextColor(rSettings.GetMenuTextColor());
+ }
+ }
+ if( bLayout )
+ {
+ if (!IsMenuBar())
+ mpLayoutData->m_aVisibleItemBoundRects[ n ] = tools::Rectangle(aTopLeft, Size(aOutSz.Width(), pData->aSz.Height()));
+ else
+ mpLayoutData->m_aVisibleItemBoundRects[ n ] = tools::Rectangle(aTopLeft, pData->aSz);
+ }
+ }
+
+ if (!IsMenuBar())
+ aTopLeft.AdjustY(pData->aSz.Height() );
+ else
+ aTopLeft.AdjustX(pData->aSz.Width() );
+ }
+
+ // draw "more" (">>") indicator if some items have been hidden as they go out of visible area
+ if (bHiddenItems)
+ {
+ sal_Int32 nSize = nFontHeight;
+ tools::Rectangle aRectangle(Point(aOutSz.Width() - nSize, (aOutSz.Height() / 2) - (nSize / 2)), Size(nSize, nSize));
+ lclDrawMoreIndicator(rRenderContext, aRectangle);
+ }
+}
+
+Menu* Menu::ImplGetStartMenu()
+{
+ Menu* pStart = this;
+ while ( pStart && pStart->pStartedFrom && ( pStart->pStartedFrom != pStart ) )
+ pStart = pStart->pStartedFrom;
+ return pStart;
+}
+
+void Menu::ImplCallHighlight(sal_uInt16 nItem)
+{
+ ImplMenuDelData aDelData( this );
+
+ nSelectedId = 0;
+ sSelectedIdent.clear();
+ MenuItemData* pData = pItemList->GetDataFromPos(nItem);
+ if (pData)
+ {
+ nSelectedId = pData->nId;
+ sSelectedIdent = pData->sIdent;
+ }
+ ImplCallEventListeners( VclEventId::MenuHighlight, GetItemPos( GetCurItemId() ) );
+
+ if( !aDelData.isDeleted() )
+ {
+ nSelectedId = 0;
+ sSelectedIdent.clear();
+ }
+}
+
+IMPL_LINK_NOARG(Menu, ImplCallSelect, void*, void)
+{
+ nEventId = nullptr;
+ Select();
+}
+
+Menu* Menu::ImplFindSelectMenu()
+{
+ Menu* pSelMenu = nEventId ? this : nullptr;
+
+ for ( size_t n = GetItemList()->size(); n && !pSelMenu; )
+ {
+ MenuItemData* pData = GetItemList()->GetDataFromPos( --n );
+
+ if ( pData->pSubMenu )
+ pSelMenu = pData->pSubMenu->ImplFindSelectMenu();
+ }
+
+ return pSelMenu;
+}
+
+Menu* Menu::ImplFindMenu( sal_uInt16 nItemId )
+{
+ Menu* pSelMenu = nullptr;
+
+ for ( size_t n = GetItemList()->size(); n && !pSelMenu; )
+ {
+ MenuItemData* pData = GetItemList()->GetDataFromPos( --n );
+
+ if( pData->nId == nItemId )
+ pSelMenu = this;
+ else if ( pData->pSubMenu )
+ pSelMenu = pData->pSubMenu->ImplFindMenu( nItemId );
+ }
+
+ return pSelMenu;
+}
+
+void Menu::RemoveDisabledEntries( bool bRemoveEmptyPopups )
+{
+ for ( sal_uInt16 n = 0; n < GetItemCount(); n++ )
+ {
+ bool bRemove = false;
+ MenuItemData* pItem = pItemList->GetDataFromPos( n );
+ if ( pItem->eType == MenuItemType::SEPARATOR )
+ {
+ if ( !n || ( GetItemType( n-1 ) == MenuItemType::SEPARATOR ) )
+ bRemove = true;
+ }
+ else
+ bRemove = !pItem->bEnabled;
+
+ if ( pItem->pSubMenu )
+ {
+ pItem->pSubMenu->RemoveDisabledEntries();
+ if ( bRemoveEmptyPopups && !pItem->pSubMenu->GetItemCount() )
+ bRemove = true;
+ }
+
+ if ( bRemove )
+ RemoveItem( n-- );
+ }
+
+ if ( GetItemCount() )
+ {
+ sal_uInt16 nLast = GetItemCount() - 1;
+ MenuItemData* pItem = pItemList->GetDataFromPos( nLast );
+ if ( pItem->eType == MenuItemType::SEPARATOR )
+ RemoveItem( nLast );
+ }
+ mpLayoutData.reset();
+}
+
+void Menu::UpdateNativeMenu()
+{
+ if ( ImplGetSalMenu() )
+ ImplGetSalMenu()->Update();
+}
+
+void Menu::MenuBarKeyInput(const KeyEvent&)
+{
+}
+
+void Menu::ImplKillLayoutData() const
+{
+ mpLayoutData.reset();
+}
+
+void Menu::ImplFillLayoutData() const
+{
+ if (!(pWindow && pWindow->IsReallyVisible()))
+ return;
+
+ mpLayoutData.reset(new MenuLayoutData);
+ if (IsMenuBar())
+ {
+ ImplPaint(*pWindow->GetOutDev(), pWindow->GetOutputSizePixel(), 0, 0, nullptr, false, true); // FIXME
+ }
+ else
+ {
+ MenuFloatingWindow* pFloat = static_cast<MenuFloatingWindow*>(pWindow.get());
+ ImplPaint(*pWindow->GetOutDev(), pWindow->GetOutputSizePixel(), pFloat->nScrollerHeight, pFloat->ImplGetStartY(),
+ nullptr, false, true); //FIXME
+ }
+}
+
+tools::Rectangle Menu::GetCharacterBounds( sal_uInt16 nItemID, tools::Long nIndex ) const
+{
+ tools::Long nItemIndex = -1;
+ if( ! mpLayoutData )
+ ImplFillLayoutData();
+ if( mpLayoutData )
+ {
+ for( size_t i = 0; i < mpLayoutData->m_aLineItemIds.size(); i++ )
+ {
+ if( mpLayoutData->m_aLineItemIds[i] == nItemID )
+ {
+ nItemIndex = mpLayoutData->m_aLineIndices[i];
+ break;
+ }
+ }
+ }
+ return (mpLayoutData && nItemIndex != -1) ? mpLayoutData->GetCharacterBounds( nItemIndex+nIndex ) : tools::Rectangle();
+}
+
+tools::Long Menu::GetIndexForPoint( const Point& rPoint, sal_uInt16& rItemID ) const
+{
+ tools::Long nIndex = -1;
+ rItemID = 0;
+ if( ! mpLayoutData )
+ ImplFillLayoutData();
+ if( mpLayoutData )
+ {
+ nIndex = mpLayoutData->GetIndexForPoint( rPoint );
+ for( size_t i = 0; i < mpLayoutData->m_aLineIndices.size(); i++ )
+ {
+ if( mpLayoutData->m_aLineIndices[i] <= nIndex &&
+ (i == mpLayoutData->m_aLineIndices.size()-1 || mpLayoutData->m_aLineIndices[i+1] > nIndex) )
+ {
+ // make index relative to item
+ nIndex -= mpLayoutData->m_aLineIndices[i];
+ rItemID = mpLayoutData->m_aLineItemIds[i];
+ break;
+ }
+ }
+ }
+ return nIndex;
+}
+
+tools::Rectangle Menu::GetBoundingRectangle( sal_uInt16 nPos ) const
+{
+ tools::Rectangle aRet;
+
+ if (!mpLayoutData )
+ ImplFillLayoutData();
+ if (mpLayoutData)
+ {
+ std::map< sal_uInt16, tools::Rectangle >::const_iterator it = mpLayoutData->m_aVisibleItemBoundRects.find( nPos );
+ if( it != mpLayoutData->m_aVisibleItemBoundRects.end() )
+ aRet = it->second;
+ }
+ return aRet;
+}
+
+void Menu::SetAccessibleName( sal_uInt16 nItemId, const OUString& rStr )
+{
+ size_t nPos;
+ MenuItemData* pData = pItemList->GetData( nItemId, nPos );
+
+ if (pData && !rStr.equals(pData->aAccessibleName))
+ {
+ pData->aAccessibleName = rStr;
+ ImplCallEventListeners(VclEventId::MenuAccessibleNameChanged, nPos);
+ }
+}
+
+OUString Menu::GetAccessibleName( sal_uInt16 nItemId ) const
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ return pData->aAccessibleName;
+
+ return OUString();
+}
+
+void Menu::SetAccessibleDescription( sal_uInt16 nItemId, const OUString& rStr )
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if ( pData )
+ pData->aAccessibleDescription = rStr;
+}
+
+OUString Menu::GetAccessibleDescription( sal_uInt16 nItemId ) const
+{
+ MenuItemData* pData = pItemList->GetData( nItemId );
+
+ if (pData && !pData->aAccessibleDescription.isEmpty())
+ return pData->aAccessibleDescription;
+
+ return GetHelpText(nItemId);
+}
+
+void Menu::GetSystemMenuData( SystemMenuData* pData ) const
+{
+ Menu* pMenu = const_cast<Menu*>(this);
+ if( pData && pMenu->ImplGetSalMenu() )
+ {
+ pMenu->ImplGetSalMenu()->GetSystemMenuData( pData );
+ }
+}
+
+bool Menu::IsHighlighted( sal_uInt16 nItemPos ) const
+{
+ bool bRet = false;
+
+ if( pWindow )
+ {
+ if (IsMenuBar())
+ bRet = ( nItemPos == static_cast< MenuBarWindow * > (pWindow.get())->GetHighlightedItem() );
+ else
+ bRet = ( nItemPos == static_cast< MenuFloatingWindow * > (pWindow.get())->GetHighlightedItem() );
+ }
+
+ return bRet;
+}
+
+void Menu::HighlightItem( sal_uInt16 nItemPos )
+{
+ if ( !pWindow )
+ return;
+
+ if (IsMenuBar())
+ {
+ MenuBarWindow* pMenuWin = static_cast< MenuBarWindow* >( pWindow.get() );
+ pMenuWin->SetAutoPopup( false );
+ pMenuWin->ChangeHighlightItem( nItemPos, false );
+ }
+ else
+ {
+ static_cast< MenuFloatingWindow* >( pWindow.get() )->ChangeHighlightItem( nItemPos, false );
+ }
+}
+
+MenuBarWindow* MenuBar::getMenuBarWindow()
+{
+ // so far just a dynamic_cast, hopefully to be turned into something saner
+ // at some stage
+ MenuBarWindow *pWin = dynamic_cast<MenuBarWindow*>(pWindow.get());
+ //either there is no window (fdo#87663) or it is a MenuBarWindow
+ assert(!pWindow || pWin);
+ return pWin;
+}
+
+MenuBar::MenuBar()
+ : mbCloseBtnVisible(false),
+ mbFloatBtnVisible(false),
+ mbHideBtnVisible(false),
+ mbDisplayable(true)
+{
+ mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(true, this);
+}
+
+MenuBar::MenuBar( const MenuBar& rMenu )
+ : mbCloseBtnVisible(false),
+ mbFloatBtnVisible(false),
+ mbHideBtnVisible(false),
+ mbDisplayable(true)
+{
+ mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(true, this);
+ *this = rMenu;
+}
+
+MenuBar::~MenuBar()
+{
+ disposeOnce();
+}
+
+void MenuBar::dispose()
+{
+ ImplDestroy( this, true );
+ Menu::dispose();
+}
+
+void MenuBar::ClosePopup(Menu *pMenu)
+{
+ MenuBarWindow* pMenuWin = getMenuBarWindow();
+ if (!pMenuWin)
+ return;
+ pMenuWin->PopupClosed(pMenu);
+}
+
+void MenuBar::MenuBarKeyInput(const KeyEvent& rEvent)
+{
+ pWindow->KeyInput(rEvent);
+}
+
+void MenuBar::ShowCloseButton(bool bShow)
+{
+ ShowButtons( bShow, mbFloatBtnVisible, mbHideBtnVisible );
+}
+
+void MenuBar::ShowButtons( bool bClose, bool bFloat, bool bHide )
+{
+ if ((bClose != mbCloseBtnVisible) ||
+ (bFloat != mbFloatBtnVisible) ||
+ (bHide != mbHideBtnVisible))
+ {
+ mbCloseBtnVisible = bClose;
+ mbFloatBtnVisible = bFloat;
+ mbHideBtnVisible = bHide;
+ MenuBarWindow* pMenuWin = getMenuBarWindow();
+ if (pMenuWin)
+ pMenuWin->ShowButtons(bClose, bFloat, bHide);
+ }
+}
+
+void MenuBar::LayoutChanged()
+{
+ MenuBarWindow* pMenuWin = getMenuBarWindow();
+ if (pMenuWin)
+ pMenuWin->LayoutChanged();
+}
+
+void MenuBar::SetDisplayable( bool bDisplayable )
+{
+ if( bDisplayable != mbDisplayable )
+ {
+ if ( ImplGetSalMenu() )
+ ImplGetSalMenu()->ShowMenuBar( bDisplayable );
+
+ mbDisplayable = bDisplayable;
+ LayoutChanged();
+ }
+}
+
+VclPtr<vcl::Window> MenuBar::ImplCreate(vcl::Window* pParent, vcl::Window* pWindow, MenuBar* pMenu)
+{
+ VclPtr<MenuBarWindow> pMenuBarWindow = dynamic_cast<MenuBarWindow*>(pWindow);
+ if (!pMenuBarWindow)
+ {
+ pWindow = pMenuBarWindow = VclPtr<MenuBarWindow>::Create( pParent );
+ }
+
+ pMenu->pStartedFrom = nullptr;
+ pMenu->pWindow = pWindow;
+ pMenuBarWindow->SetMenu(pMenu);
+ tools::Long nHeight = pWindow ? pMenu->ImplCalcSize(pWindow).Height() : 0;
+
+ // depending on the native implementation or the displayable flag
+ // the menubar windows is suppressed (ie, height=0)
+ if (!pMenu->IsDisplayable() || (pMenu->ImplGetSalMenu() && pMenu->ImplGetSalMenu()->VisibleMenuBar()))
+ {
+ nHeight = 0;
+ }
+
+ pMenuBarWindow->SetHeight(nHeight);
+ return pWindow;
+}
+
+void MenuBar::ImplDestroy( MenuBar* pMenu, bool bDelete )
+{
+ vcl::Window *pWindow = pMenu->ImplGetWindow();
+ if (pWindow && bDelete)
+ {
+ MenuBarWindow* pMenuWin = pMenu->getMenuBarWindow();
+ if (pMenuWin)
+ pMenuWin->KillActivePopup();
+ pWindow->disposeOnce();
+ }
+ pMenu->pWindow = nullptr;
+ if (pMenu->mpSalMenu) {
+ pMenu->mpSalMenu->ShowMenuBar(false);
+ }
+}
+
+bool MenuBar::ImplHandleKeyEvent( const KeyEvent& rKEvent )
+{
+ // No keyboard processing when our menubar is invisible
+ if (!IsDisplayable())
+ return false;
+
+ // No keyboard processing when system handles the menu.
+ SalMenu *pNativeMenu = ImplGetSalMenu();
+ if (pNativeMenu && pNativeMenu->VisibleMenuBar())
+ {
+ // Except when the event is the F6 cycle pane event and we can put our
+ // focus into it (i.e. the gtk3 menubar case but not the mac/unity case
+ // where it's not part of the application window)
+ if (!TaskPaneList::IsCycleKey(rKEvent.GetKeyCode()))
+ return false;
+ if (!pNativeMenu->CanGetFocus())
+ return false;
+ }
+
+ bool bDone = false;
+ // check for enabled, if this method is called from another window...
+ vcl::Window* pWin = ImplGetWindow();
+ if (pWin && pWin->IsEnabled() && pWin->IsInputEnabled() && !pWin->IsInModalMode())
+ {
+ MenuBarWindow* pMenuWin = getMenuBarWindow();
+ bDone = pMenuWin && pMenuWin->HandleKeyEvent(rKEvent, false/*bFromMenu*/);
+ }
+ return bDone;
+}
+
+void MenuBar::SelectItem(sal_uInt16 nId)
+{
+ if (!pWindow)
+ return;
+
+ pWindow->GrabFocus();
+ nId = GetItemPos( nId );
+
+ MenuBarWindow* pMenuWin = getMenuBarWindow();
+ if (pMenuWin)
+ {
+ // #99705# popup the selected menu
+ pMenuWin->SetAutoPopup( true );
+ if (ITEMPOS_INVALID != pMenuWin->GetHighlightedItem())
+ {
+ pMenuWin->KillActivePopup();
+ pMenuWin->ChangeHighlightItem( ITEMPOS_INVALID, false );
+ }
+ if (nId != ITEMPOS_INVALID)
+ pMenuWin->ChangeHighlightItem( nId, false );
+ }
+}
+
+// handler for native menu selection and command events
+bool Menu::HandleMenuActivateEvent( Menu *pMenu ) const
+{
+ if( pMenu )
+ {
+ ImplMenuDelData aDelData( this );
+
+ pMenu->pStartedFrom = const_cast<Menu*>(this);
+ pMenu->bInCallback = true;
+ pMenu->Activate();
+
+ if( !aDelData.isDeleted() )
+ pMenu->bInCallback = false;
+ }
+ return true;
+}
+
+bool Menu::HandleMenuDeActivateEvent( Menu *pMenu ) const
+{
+ if( pMenu )
+ {
+ ImplMenuDelData aDelData( this );
+
+ pMenu->pStartedFrom = const_cast<Menu*>(this);
+ pMenu->bInCallback = true;
+ pMenu->Deactivate();
+ if( !aDelData.isDeleted() )
+ pMenu->bInCallback = false;
+ }
+ return true;
+}
+
+bool MenuBar::HandleMenuHighlightEvent( Menu *pMenu, sal_uInt16 nHighlightEventId ) const
+{
+ if( !pMenu )
+ pMenu = const_cast<MenuBar*>(this)->ImplFindMenu(nHighlightEventId);
+ if( pMenu )
+ {
+ ImplMenuDelData aDelData( pMenu );
+
+ if( mnHighlightedItemPos != ITEMPOS_INVALID )
+ pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, mnHighlightedItemPos );
+
+ if( !aDelData.isDeleted() )
+ {
+ pMenu->mnHighlightedItemPos = pMenu->GetItemPos( nHighlightEventId );
+ pMenu->nSelectedId = nHighlightEventId;
+ pMenu->sSelectedIdent = pMenu->GetItemIdent( nHighlightEventId );
+ pMenu->pStartedFrom = const_cast<MenuBar*>(this);
+ pMenu->ImplCallHighlight( pMenu->mnHighlightedItemPos );
+ }
+ return true;
+ }
+ else
+ return false;
+}
+
+bool Menu::HandleMenuCommandEvent( Menu *pMenu, sal_uInt16 nCommandEventId ) const
+{
+ if( !pMenu )
+ pMenu = const_cast<Menu*>(this)->ImplFindMenu(nCommandEventId);
+ if( pMenu )
+ {
+ pMenu->nSelectedId = nCommandEventId;
+ pMenu->sSelectedIdent = pMenu->GetItemIdent(nCommandEventId);
+ pMenu->pStartedFrom = const_cast<Menu*>(this);
+ pMenu->ImplSelect();
+ return true;
+ }
+ else
+ return false;
+}
+
+sal_uInt16 MenuBar::AddMenuBarButton( const Image& i_rImage, const Link<MenuBarButtonCallbackArg&,bool>& i_rLink, const OUString& i_rToolTip )
+{
+ MenuBarWindow* pMenuWin = getMenuBarWindow();
+ return pMenuWin ? pMenuWin->AddMenuBarButton(i_rImage, i_rLink, i_rToolTip) : 0;
+}
+
+void MenuBar::SetMenuBarButtonHighlightHdl( sal_uInt16 nId, const Link<MenuBarButtonCallbackArg&,bool>& rLink )
+{
+ MenuBarWindow* pMenuWin = getMenuBarWindow();
+ if (!pMenuWin)
+ return;
+ pMenuWin->SetMenuBarButtonHighlightHdl(nId, rLink);
+}
+
+void MenuBar::RemoveMenuBarButton( sal_uInt16 nId )
+{
+ MenuBarWindow* pMenuWin = getMenuBarWindow();
+ if (!pMenuWin)
+ return;
+ pMenuWin->RemoveMenuBarButton(nId);
+}
+
+tools::Rectangle MenuBar::GetMenuBarButtonRectPixel( sal_uInt16 nId )
+{
+ MenuBarWindow* pMenuWin = getMenuBarWindow();
+ return pMenuWin ? pMenuWin->GetMenuBarButtonRectPixel(nId) : tools::Rectangle();
+}
+
+bool MenuBar::HandleMenuButtonEvent( sal_uInt16 i_nButtonId )
+{
+ MenuBarWindow* pMenuWin = getMenuBarWindow();
+ return pMenuWin && pMenuWin->HandleMenuButtonEvent(i_nButtonId);
+}
+
+int MenuBar::GetMenuBarHeight() const
+{
+ MenuBar* pMenuBar = const_cast<MenuBar*>(this);
+ const SalMenu *pNativeMenu = pMenuBar->ImplGetSalMenu();
+ int nMenubarHeight;
+ if (pNativeMenu)
+ nMenubarHeight = pNativeMenu->GetMenuBarHeight();
+ else
+ {
+ vcl::Window* pMenubarWin = GetWindow();
+ nMenubarHeight = pMenubarWin ? pMenubarWin->GetOutputSizePixel().Height() : 0;
+ }
+ return nMenubarHeight;
+}
+
+// bool PopupMenu::bAnyPopupInExecute = false;
+
+MenuFloatingWindow * PopupMenu::ImplGetFloatingWindow() const {
+ return static_cast<MenuFloatingWindow *>(Menu::ImplGetWindow());
+}
+
+PopupMenu::PopupMenu()
+{
+ mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(false, this);
+}
+
+PopupMenu::PopupMenu( const PopupMenu& rMenu )
+{
+ mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(false, this);
+ *this = rMenu;
+}
+
+PopupMenu::~PopupMenu()
+{
+ disposeOnce();
+}
+
+void PopupMenu::ClosePopup(Menu* pMenu)
+{
+ MenuFloatingWindow* p = dynamic_cast<MenuFloatingWindow*>(ImplGetWindow());
+ PopupMenu *pPopup = dynamic_cast<PopupMenu*>(pMenu);
+ if (p && pPopup)
+ p->KillActivePopup(pPopup);
+}
+
+namespace vcl
+{
+ bool IsInPopupMenuExecute()
+ {
+ return PopupMenu::GetActivePopupMenu() != nullptr;
+ }
+}
+
+PopupMenu* PopupMenu::GetActivePopupMenu()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ return pSVData->maAppData.mpActivePopupMenu;
+}
+
+void PopupMenu::EndExecute()
+{
+ if ( ImplGetWindow() )
+ ImplGetFloatingWindow()->EndExecute( 0 );
+}
+
+void PopupMenu::SelectItem(sal_uInt16 nId)
+{
+ if ( !ImplGetWindow() )
+ return;
+
+ if( nId != ITEMPOS_INVALID )
+ {
+ size_t nPos = 0;
+ MenuItemData* pData = GetItemList()->GetData( nId, nPos );
+ if (pData && pData->pSubMenu)
+ ImplGetFloatingWindow()->ChangeHighlightItem( nPos, true );
+ else
+ ImplGetFloatingWindow()->EndExecute( nId );
+ }
+ else
+ {
+ MenuFloatingWindow* pFloat = ImplGetFloatingWindow();
+ pFloat->GrabFocus();
+
+ for( size_t nPos = 0; nPos < GetItemList()->size(); nPos++ )
+ {
+ MenuItemData* pData = GetItemList()->GetDataFromPos( nPos );
+ if( pData->pSubMenu )
+ {
+ pFloat->KillActivePopup();
+ }
+ }
+ pFloat->ChangeHighlightItem( ITEMPOS_INVALID, false );
+ }
+}
+
+void PopupMenu::SetSelectedEntry( sal_uInt16 nId )
+{
+ nSelectedId = nId;
+ sSelectedIdent = GetItemIdent(nId);
+}
+
+sal_uInt16 PopupMenu::Execute( vcl::Window* pExecWindow, const Point& rPopupPos )
+{
+ return Execute( pExecWindow, tools::Rectangle( rPopupPos, rPopupPos ), PopupMenuFlags::ExecuteDown );
+}
+
+sal_uInt16 PopupMenu::Execute( vcl::Window* pExecWindow, const tools::Rectangle& rRect, PopupMenuFlags nFlags )
+{
+ ENSURE_OR_RETURN( pExecWindow, "PopupMenu::Execute: need a non-NULL window!", 0 );
+
+ FloatWinPopupFlags nPopupModeFlags = FloatWinPopupFlags::NONE;
+ if ( nFlags & PopupMenuFlags::ExecuteDown )
+ nPopupModeFlags = FloatWinPopupFlags::Down;
+ else if ( nFlags & PopupMenuFlags::ExecuteUp )
+ nPopupModeFlags = FloatWinPopupFlags::Up;
+ else if ( nFlags & PopupMenuFlags::ExecuteRight )
+ nPopupModeFlags = FloatWinPopupFlags::Right;
+ else
+ nPopupModeFlags = FloatWinPopupFlags::Down;
+
+ if (nFlags & PopupMenuFlags::NoMouseUpClose ) // allow popup menus to stay open on mouse button up
+ nPopupModeFlags |= FloatWinPopupFlags::NoMouseUpClose; // useful if the menu was opened on mousebutton down (eg toolbox configuration)
+
+ return ImplExecute( pExecWindow, rRect, nPopupModeFlags, nullptr, false );
+}
+
+void PopupMenu::ImplFlushPendingSelect()
+{
+ // is there still Select?
+ Menu* pSelect = ImplFindSelectMenu();
+ if (pSelect)
+ {
+ // Select should be called prior to leaving execute in a popup menu!
+ Application::RemoveUserEvent( pSelect->nEventId );
+ pSelect->nEventId = nullptr;
+ pSelect->Select();
+ }
+}
+
+sal_uInt16 PopupMenu::ImplExecute( const VclPtr<vcl::Window>& pW, const tools::Rectangle& rRect, FloatWinPopupFlags nPopupModeFlags, Menu* pSFrom, bool bPreSelectFirst )
+{
+ if ( !pSFrom && ( vcl::IsInPopupMenuExecute() || !GetItemCount() ) )
+ return 0;
+
+ mpLayoutData.reset();
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ pStartedFrom = pSFrom;
+ nSelectedId = 0;
+ sSelectedIdent.clear();
+ bCanceled = false;
+
+ VclPtr<vcl::Window> xFocusId;
+ bool bRealExecute = false;
+ if ( !pStartedFrom )
+ {
+ pSVData->mpWinData->mbNoDeactivate = true;
+ xFocusId = Window::SaveFocus();
+ bRealExecute = true;
+ }
+ else
+ {
+ // assure that only one menu is open at a time
+ if (pStartedFrom->IsMenuBar() && pSVData->mpWinData->mpFirstFloat)
+ pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel
+ | FloatWinPopupEndFlags::CloseAll);
+ }
+
+ SAL_WARN_IF( ImplGetWindow(), "vcl", "Win?!" );
+ tools::Rectangle aRect( rRect );
+ aRect.SetPos( pW->OutputToScreenPixel( aRect.TopLeft() ) );
+
+ if (bRealExecute)
+ nPopupModeFlags |= FloatWinPopupFlags::NewLevel;
+ nPopupModeFlags |= FloatWinPopupFlags::NoKeyClose | FloatWinPopupFlags::AllMouseButtonClose;
+
+ bInCallback = true; // set it here, if Activate overridden
+ Activate();
+ bInCallback = false;
+
+ if ( pW->isDisposed() )
+ return 0; // Error
+
+ if ( bCanceled || bKilled )
+ return 0;
+
+ if ( !GetItemCount() )
+ return 0;
+
+ // The flag MenuFlags::HideDisabledEntries is inherited.
+ if ( pSFrom )
+ {
+ if ( pSFrom->nMenuFlags & MenuFlags::HideDisabledEntries )
+ nMenuFlags |= MenuFlags::HideDisabledEntries;
+ else
+ nMenuFlags &= ~MenuFlags::HideDisabledEntries;
+ }
+ else
+ // #102790# context menus shall never show disabled entries
+ nMenuFlags |= MenuFlags::HideDisabledEntries;
+
+ sal_uInt16 nVisibleEntries = ImplGetVisibleItemCount();
+ if ( !nVisibleEntries )
+ {
+ OUString aTmpEntryText(VclResId(SV_RESID_STRING_NOSELECTIONPOSSIBLE));
+
+ MenuItemData* pData = NbcInsertItem(0xFFFF, MenuItemBits::NONE, aTmpEntryText, nullptr, 0xFFFF, OString());
+ size_t nPos = 0;
+ pData = pItemList->GetData( pData->nId, nPos );
+ assert(pData);
+ if (pData)
+ {
+ pData->bIsTemporary = true;
+ }
+ ImplCallEventListeners(VclEventId::MenuSubmenuChanged, nPos);
+ }
+
+ VclPtrInstance<MenuFloatingWindow> pWin( this, pW, WB_BORDER | WB_SYSTEMWINDOW );
+ if (comphelper::LibreOfficeKit::isActive() && get_id() == "editviewspellmenu")
+ {
+ VclPtr<vcl::Window> xNotifierParent = pW->GetParentWithLOKNotifier();
+ assert(xNotifierParent && xNotifierParent->GetLOKNotifier() && "editview menu without LOKNotifier");
+ pWin->SetLOKNotifier(xNotifierParent->GetLOKNotifier());
+ }
+
+ if( pSVData->maNWFData.mbFlatMenu )
+ pWin->SetBorderStyle( WindowBorderStyle::NOBORDER );
+ else
+ pWin->SetBorderStyle( pWin->GetBorderStyle() | WindowBorderStyle::MENU );
+ pWindow = pWin;
+
+ Size aSz = ImplCalcSize( pWin );
+
+ tools::Rectangle aDesktopRect(pWin->GetDesktopRectPixel());
+ if( Application::GetScreenCount() > 1 && Application::IsUnifiedDisplay() )
+ {
+ vcl::Window* pDeskW = pWindow->GetWindow( GetWindowType::RealParent );
+ if( ! pDeskW )
+ pDeskW = pWindow;
+ Point aDesktopTL( pDeskW->OutputToAbsoluteScreenPixel( aRect.TopLeft() ) );
+ aDesktopRect = Application::GetScreenPosSizePixel(
+ Application::GetBestScreen( tools::Rectangle( aDesktopTL, aRect.GetSize() ) ));
+ }
+
+ tools::Long nMaxHeight = aDesktopRect.GetHeight();
+
+ //rhbz#1021915. If a menu won't fit in the desired location the default
+ //mode is to place it somewhere it will fit. e.g. above, left, right. For
+ //some cases, e.g. menubars, it's desirable to limit the options to
+ //above/below and force the menu to scroll if it won't fit
+ if (nPopupModeFlags & FloatWinPopupFlags::NoHorzPlacement)
+ {
+ vcl::Window* pRef = pWin;
+ if ( pRef->GetParent() )
+ pRef = pRef->GetParent();
+
+ tools::Rectangle devRect( pRef->OutputToAbsoluteScreenPixel( aRect.TopLeft() ),
+ pRef->OutputToAbsoluteScreenPixel( aRect.BottomRight() ) );
+
+ tools::Long nHeightAbove = devRect.Top() - aDesktopRect.Top();
+ tools::Long nHeightBelow = aDesktopRect.Bottom() - devRect.Bottom();
+ nMaxHeight = std::min(nMaxHeight, std::max(nHeightAbove, nHeightBelow));
+ }
+
+ // In certain cases this might be misdetected with a height of 0, leading to menus not being displayed.
+ // So assume that the available screen size matches at least the system requirements
+ SAL_WARN_IF(nMaxHeight < 768, "vcl",
+ "Available height misdetected as " << nMaxHeight
+ << "px. Setting to 768px instead.");
+ nMaxHeight = std::max(nMaxHeight, tools::Long(768));
+
+ if (pStartedFrom && pStartedFrom->IsMenuBar())
+ nMaxHeight -= pW->GetSizePixel().Height();
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ pWindow->GetBorder( nLeft, nTop, nRight, nBottom );
+ nMaxHeight -= nTop+nBottom;
+ if ( aSz.Height() > nMaxHeight )
+ {
+ pWin->EnableScrollMenu( true );
+ sal_uInt16 nStart = ImplGetFirstVisible();
+ sal_uInt16 nEntries = ImplCalcVisEntries( nMaxHeight, nStart );
+ aSz.setHeight( ImplCalcHeight( nEntries ) );
+ }
+
+ // tdf#126054 hold this until after function completes
+ VclPtr<PopupMenu> xThis(this);
+
+ pWin->SetFocusId( xFocusId );
+ pWin->SetOutputSizePixel( aSz );
+ if ( GetItemCount() )
+ {
+ SalMenu* pMenu = ImplGetSalMenu();
+ if( pMenu && bRealExecute && pMenu->ShowNativePopupMenu( pWin, aRect, nPopupModeFlags | FloatWinPopupFlags::GrabFocus ) )
+ {
+ pWin->StopExecute();
+ pWin->doShutdown();
+ pWindow.disposeAndClear();
+ ImplClosePopupToolBox(pW);
+ ImplFlushPendingSelect();
+ return nSelectedId;
+ }
+ else
+ {
+ pWin->StartPopupMode( aRect, nPopupModeFlags | FloatWinPopupFlags::GrabFocus );
+ }
+ if( pSFrom )
+ {
+ sal_uInt16 aPos;
+ if (pSFrom->IsMenuBar())
+ aPos = static_cast<MenuBarWindow *>(pSFrom->pWindow.get())->GetHighlightedItem();
+ else
+ aPos = static_cast<MenuFloatingWindow *>(pSFrom->pWindow.get())->GetHighlightedItem();
+
+ pWin->SetPosInParent( aPos ); // store position to be sent in SUBMENUDEACTIVATE
+ pSFrom->ImplCallEventListeners( VclEventId::MenuSubmenuActivate, aPos );
+ }
+ }
+ if ( bPreSelectFirst )
+ {
+ size_t nCount = pItemList->size();
+ for ( size_t n = 0; n < nCount; n++ )
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos( n );
+ if ( ( pData->bEnabled
+ || !Application::GetSettings().GetStyleSettings().GetSkipDisabledInMenus()
+ )
+ && ( pData->eType != MenuItemType::SEPARATOR )
+ && ImplIsVisible( n )
+ && ImplIsSelectable( n )
+ )
+ {
+ pWin->ChangeHighlightItem( n, false );
+ break;
+ }
+ }
+ }
+ if ( bRealExecute )
+ {
+ pWin->Execute();
+ if (pWin->isDisposed())
+ return 0;
+
+ xFocusId = pWin->GetFocusId();
+ assert(xFocusId == nullptr && "Focus should already be restored by MenuFloatingWindow::End");
+ pWin->ImplEndPopupMode(FloatWinPopupEndFlags::NONE, xFocusId);
+
+ if ( nSelectedId ) // then clean up .. ( otherwise done by TH )
+ {
+ PopupMenu* pSub = pWin->GetActivePopup();
+ while ( pSub )
+ {
+ pSub->ImplGetFloatingWindow()->EndPopupMode();
+ pSub = pSub->ImplGetFloatingWindow()->GetActivePopup();
+ }
+ }
+ pWin->doShutdown();
+ pWindow.disposeAndClear();
+ ImplClosePopupToolBox(pW);
+ ImplFlushPendingSelect();
+ }
+
+ return bRealExecute ? nSelectedId : 0;
+}
+
+sal_uInt16 PopupMenu::ImplCalcVisEntries( tools::Long nMaxHeight, sal_uInt16 nStartEntry, sal_uInt16* pLastVisible ) const
+{
+ nMaxHeight -= 2 * ImplGetFloatingWindow()->GetScrollerHeight();
+
+ tools::Long nHeight = 0;
+ size_t nEntries = pItemList->size();
+ sal_uInt16 nVisEntries = 0;
+
+ if ( pLastVisible )
+ *pLastVisible = 0;
+
+ for ( size_t n = nStartEntry; n < nEntries; n++ )
+ {
+ if ( ImplIsVisible( n ) )
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos( n );
+ nHeight += pData->aSz.Height();
+ if ( nHeight > nMaxHeight )
+ break;
+
+ if ( pLastVisible )
+ *pLastVisible = n;
+ nVisEntries++;
+ }
+ }
+ return nVisEntries;
+}
+
+tools::Long PopupMenu::ImplCalcHeight( sal_uInt16 nEntries ) const
+{
+ tools::Long nHeight = 0;
+
+ sal_uInt16 nFound = 0;
+ for ( size_t n = 0; ( nFound < nEntries ) && ( n < pItemList->size() ); n++ )
+ {
+ if ( ImplIsVisible( static_cast<sal_uInt16>(n) ) )
+ {
+ MenuItemData* pData = pItemList->GetDataFromPos( n );
+ nHeight += pData->aSz.Height();
+ nFound++;
+ }
+ }
+
+ nHeight += 2*ImplGetFloatingWindow()->GetScrollerHeight();
+
+ return nHeight;
+}
+
+css::uno::Reference<css::awt::XPopupMenu> PopupMenu::CreateMenuInterface()
+{
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper();
+ if ( pWrapper )
+ return pWrapper->CreateMenuInterface(this);
+ return nullptr;
+}
+
+ImplMenuDelData::ImplMenuDelData( const Menu* pMenu )
+: mpNext( nullptr )
+, mpMenu( nullptr )
+{
+ if( pMenu )
+ const_cast< Menu* >( pMenu )->ImplAddDel( *this );
+}
+
+ImplMenuDelData::~ImplMenuDelData()
+{
+ if( mpMenu )
+ const_cast< Menu* >( mpMenu.get() )->ImplRemoveDel( *this );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menubarwindow.cxx b/vcl/source/window/menubarwindow.cxx
new file mode 100644
index 000000000..51bad55d4
--- /dev/null
+++ b/vcl/source/window/menubarwindow.cxx
@@ -0,0 +1,1222 @@
+/* -*- 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 "menubarwindow.hxx"
+#include "menuitemlist.hxx"
+#include "menufloatingwindow.hxx"
+
+#include <vcl/dockingarea.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/taskpanelist.hxx>
+#include <sal/log.hxx>
+
+#include <salframe.hxx>
+#include <salmenu.hxx>
+#include <svdata.hxx>
+#include <strings.hrc>
+#include <bitmaps.hlst>
+#include <window.h>
+#include "bufferdevice.hxx"
+
+// document closing button
+#define IID_DOCUMENTCLOSE 1
+
+DecoToolBox::DecoToolBox( vcl::Window* pParent ) :
+ ToolBox( pParent, 0 ),
+ lastSize(-1)
+{
+ calcMinSize();
+}
+
+void DecoToolBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Window::DataChanged( rDCEvt );
+
+ if ( rDCEvt.GetFlags() & AllSettingsFlags::STYLE )
+ {
+ calcMinSize();
+ SetBackground();
+ SetImages( 0, true);
+ }
+}
+
+void DecoToolBox::calcMinSize()
+{
+ ScopedVclPtrInstance<ToolBox> aTbx( GetParent() );
+ if( GetItemCount() == 0 )
+ {
+ aTbx->InsertItem(ToolBoxItemId(IID_DOCUMENTCLOSE), Image(StockImage::Yes, SV_RESID_BITMAP_CLOSEDOC));
+ }
+ else
+ {
+ ImplToolItems::size_type nItems = GetItemCount();
+ for( ImplToolItems::size_type i = 0; i < nItems; i++ )
+ {
+ ToolBoxItemId nId = GetItemId( i );
+ aTbx->InsertItem( nId, GetItemImage( nId ) );
+ }
+ }
+ maMinSize = aTbx->CalcWindowSizePixel();
+
+ aTbx.disposeAndClear();
+}
+
+void DecoToolBox::SetImages( tools::Long nMaxHeight, bool bForce )
+{
+ tools::Long border = getMinSize().Height() - maImage.GetSizePixel().Height();
+
+ if( !nMaxHeight && lastSize != -1 )
+ nMaxHeight = lastSize + border; // don't change anything if called with 0
+
+ if( nMaxHeight < getMinSize().Height() )
+ nMaxHeight = getMinSize().Height();
+
+ if( (lastSize == nMaxHeight - border) && !bForce )
+ return;
+
+ lastSize = nMaxHeight - border;
+
+ Color aEraseColor( ColorTransparency, 255, 255, 255, 255 );
+ BitmapEx aBmpExDst( maImage.GetBitmapEx() );
+ BitmapEx aBmpExSrc( aBmpExDst );
+
+ aEraseColor.SetAlpha( 0 );
+ aBmpExDst.Erase( aEraseColor );
+ aBmpExDst.Scale( Size( lastSize, lastSize ) );
+
+ tools::Rectangle aSrcRect( Point(0,0), maImage.GetSizePixel() );
+ tools::Rectangle aDestRect( Point((lastSize - maImage.GetSizePixel().Width())/2,
+ (lastSize - maImage.GetSizePixel().Height())/2 ),
+ maImage.GetSizePixel() );
+
+ aBmpExDst.CopyPixel( aDestRect, aSrcRect, &aBmpExSrc );
+ SetItemImage( ToolBoxItemId(IID_DOCUMENTCLOSE), Image( aBmpExDst ) );
+
+}
+
+MenuBarWindow::MenuBarWindow( vcl::Window* pParent ) :
+ Window( pParent, 0 ),
+ m_aCloseBtn(VclPtr<DecoToolBox>::Create(this)),
+ m_aFloatBtn(VclPtr<PushButton>::Create(this, WB_NOPOINTERFOCUS | WB_SMALLSTYLE | WB_RECTSTYLE)),
+ m_aHideBtn(VclPtr<PushButton>::Create(this, WB_NOPOINTERFOCUS | WB_SMALLSTYLE | WB_RECTSTYLE))
+{
+ SetType(WindowType::MENUBARWINDOW);
+ m_pMenu = nullptr;
+ m_pActivePopup = nullptr;
+ m_nHighlightedItem = ITEMPOS_INVALID;
+ m_nRolloveredItem = ITEMPOS_INVALID;
+ mbAutoPopup = true;
+ m_bIgnoreFirstMove = true;
+
+ m_aCloseBtn->maImage = Image(StockImage::Yes, SV_RESID_BITMAP_CLOSEDOC);
+
+ m_aCloseBtn->SetBackground();
+ m_aCloseBtn->SetPaintTransparent(true);
+ m_aCloseBtn->SetParentClipMode(ParentClipMode::NoClip);
+
+ m_aCloseBtn->InsertItem(ToolBoxItemId(IID_DOCUMENTCLOSE), m_aCloseBtn->maImage);
+ m_aCloseBtn->SetSelectHdl(LINK(this, MenuBarWindow, CloseHdl));
+ m_aCloseBtn->AddEventListener(LINK(this, MenuBarWindow, ToolboxEventHdl));
+ m_aCloseBtn->SetQuickHelpText(ToolBoxItemId(IID_DOCUMENTCLOSE), VclResId(SV_HELPTEXT_CLOSEDOCUMENT));
+
+ m_aFloatBtn->SetSymbol( SymbolType::FLOAT );
+ m_aFloatBtn->SetQuickHelpText(VclResId(SV_HELPTEXT_RESTORE));
+
+ m_aHideBtn->SetSymbol( SymbolType::HIDE );
+ m_aHideBtn->SetQuickHelpText(VclResId(SV_HELPTEXT_MINIMIZE));
+
+ ImplInitStyleSettings();
+
+ AddEventListener(LINK(this, MenuBarWindow, ShowHideListener));
+}
+
+MenuBarWindow::~MenuBarWindow()
+{
+ disposeOnce();
+}
+
+void MenuBarWindow::dispose()
+{
+ m_aCloseBtn->RemoveEventListener(LINK(this, MenuBarWindow, ToolboxEventHdl));
+ RemoveEventListener(LINK(this, MenuBarWindow, ShowHideListener));
+
+ mpParentPopup.disposeAndClear();
+ m_aHideBtn.disposeAndClear();
+ m_aFloatBtn.disposeAndClear();
+ m_aCloseBtn.disposeAndClear();
+ m_pMenu.clear();
+ m_pActivePopup.clear();
+ m_xSaveFocusId.clear();
+
+ Window::dispose();
+}
+
+void MenuBarWindow::SetMenu( MenuBar* pMen )
+{
+ m_pMenu = pMen;
+ KillActivePopup();
+ m_nHighlightedItem = ITEMPOS_INVALID;
+ if (pMen)
+ {
+ m_aCloseBtn->ShowItem(ToolBoxItemId(IID_DOCUMENTCLOSE), pMen->HasCloseButton());
+ m_aCloseBtn->Show(pMen->HasCloseButton() || !m_aAddButtons.empty());
+ m_aFloatBtn->Show(pMen->HasFloatButton());
+ m_aHideBtn->Show(pMen->HasHideButton());
+ }
+ Invalidate();
+
+ // show and connect native menubar
+ if( m_pMenu && m_pMenu->ImplGetSalMenu() )
+ {
+ if( m_pMenu->ImplGetSalMenu()->VisibleMenuBar() )
+ ImplGetFrame()->SetMenu( m_pMenu->ImplGetSalMenu() );
+
+ m_pMenu->ImplGetSalMenu()->SetFrame( ImplGetFrame() );
+ m_pMenu->ImplGetSalMenu()->ShowMenuBar(true);
+ }
+}
+
+void MenuBarWindow::SetHeight(tools::Long nHeight)
+{
+ setPosSizePixel(0, 0, 0, nHeight, PosSizeFlags::Height);
+}
+
+void MenuBarWindow::ShowButtons( bool bClose, bool bFloat, bool bHide )
+{
+ m_aCloseBtn->ShowItem(ToolBoxItemId(IID_DOCUMENTCLOSE), bClose);
+ m_aCloseBtn->Show(bClose || !m_aAddButtons.empty());
+ if (m_pMenu->mpSalMenu)
+ m_pMenu->mpSalMenu->ShowCloseButton(bClose);
+ m_aFloatBtn->Show( bFloat );
+ m_aHideBtn->Show( bHide );
+ Resize();
+}
+
+Size const & MenuBarWindow::MinCloseButtonSize() const
+{
+ return m_aCloseBtn->getMinSize();
+}
+
+IMPL_LINK_NOARG(MenuBarWindow, CloseHdl, ToolBox *, void)
+{
+ if( ! m_pMenu )
+ return;
+
+ if( m_aCloseBtn->GetCurItemId() == ToolBoxItemId(IID_DOCUMENTCLOSE) )
+ {
+ // #i106052# call close hdl asynchronously to ease handler implementation
+ // this avoids still being in the handler while the DecoToolBox already
+ // gets destroyed
+ Application::PostUserEvent(static_cast<MenuBar*>(m_pMenu.get())->GetCloseButtonClickHdl());
+ }
+ else
+ {
+ std::map<sal_uInt16,AddButtonEntry>::iterator it = m_aAddButtons.find(sal_uInt16(m_aCloseBtn->GetCurItemId()));
+ if( it != m_aAddButtons.end() )
+ {
+ MenuBarButtonCallbackArg aArg;
+ aArg.nId = it->first;
+ aArg.bHighlight = (sal_uInt16(m_aCloseBtn->GetHighlightItemId()) == it->first);
+ it->second.m_aSelectLink.Call( aArg );
+ }
+ }
+}
+
+IMPL_LINK( MenuBarWindow, ToolboxEventHdl, VclWindowEvent&, rEvent, void )
+{
+ if( ! m_pMenu )
+ return;
+
+ MenuBarButtonCallbackArg aArg;
+ aArg.nId = 0xffff;
+ aArg.bHighlight = (rEvent.GetId() == VclEventId::ToolboxHighlight);
+ if( rEvent.GetId() == VclEventId::ToolboxHighlight )
+ aArg.nId =sal_uInt16(m_aCloseBtn->GetHighlightItemId());
+ else if( rEvent.GetId() == VclEventId::ToolboxHighlightOff )
+ {
+ auto nPos = static_cast<ToolBox::ImplToolItems::size_type>(reinterpret_cast<sal_IntPtr>(rEvent.GetData()));
+ aArg.nId = sal_uInt16(m_aCloseBtn->GetItemId(nPos));
+ }
+ std::map< sal_uInt16, AddButtonEntry >::iterator it = m_aAddButtons.find( aArg.nId );
+ if( it != m_aAddButtons.end() )
+ {
+ it->second.m_aHighlightLink.Call( aArg );
+ }
+}
+
+IMPL_LINK( MenuBarWindow, ShowHideListener, VclWindowEvent&, rEvent, void )
+{
+ if( ! m_pMenu )
+ return;
+
+ if( rEvent.GetId() == VclEventId::WindowShow )
+ m_pMenu->ImplCallEventListeners( VclEventId::MenuShow, ITEMPOS_INVALID );
+ else if( rEvent.GetId() == VclEventId::WindowHide )
+ m_pMenu->ImplCallEventListeners( VclEventId::MenuHide, ITEMPOS_INVALID );
+}
+
+void MenuBarWindow::ImplCreatePopup( bool bPreSelectFirst )
+{
+ MenuItemData* pItemData = m_pMenu ? m_pMenu->GetItemList()->GetDataFromPos( m_nHighlightedItem ) : nullptr;
+ if ( !pItemData )
+ return;
+
+ m_bIgnoreFirstMove = true;
+ if ( m_pActivePopup && ( m_pActivePopup != pItemData->pSubMenu ) )
+ {
+ KillActivePopup();
+ }
+ if ( !(pItemData->bEnabled && pItemData->pSubMenu && ( m_nHighlightedItem != ITEMPOS_INVALID ) &&
+ ( pItemData->pSubMenu != m_pActivePopup )) )
+ return;
+
+ m_pActivePopup = static_cast<PopupMenu*>(pItemData->pSubMenu.get());
+ tools::Long nX = 0;
+ MenuItemData* pData = nullptr;
+ for ( sal_uLong n = 0; n < m_nHighlightedItem; n++ )
+ {
+ pData = m_pMenu->GetItemList()->GetDataFromPos( n );
+ nX += pData->aSz.Width();
+ }
+ pData = m_pMenu->pItemList->GetDataFromPos( m_nHighlightedItem );
+ Point aItemTopLeft( nX, 0 );
+ Point aItemBottomRight( aItemTopLeft );
+ aItemBottomRight.AdjustX(pData->aSz.Width() );
+
+ if (pData->bHiddenOnGUI)
+ {
+ mpParentPopup.disposeAndClear();
+ mpParentPopup = VclPtr<PopupMenu>::Create();
+ m_pActivePopup = mpParentPopup.get();
+
+ for (sal_uInt16 i = m_nHighlightedItem; i < m_pMenu->GetItemCount(); ++i)
+ {
+ sal_uInt16 nId = m_pMenu->GetItemId(i);
+
+ MenuItemData* pParentItemData = m_pMenu->GetItemList()->GetData(nId);
+ assert(pParentItemData);
+ mpParentPopup->InsertItem(nId, pParentItemData->aText, pParentItemData->nBits, pParentItemData->sIdent);
+ mpParentPopup->SetHelpId(nId, pParentItemData->aHelpId);
+ mpParentPopup->SetHelpText(nId, pParentItemData->aHelpText);
+ mpParentPopup->SetAccelKey(nId, pParentItemData->aAccelKey);
+ mpParentPopup->SetItemCommand(nId, pParentItemData->aCommandStr);
+ mpParentPopup->SetHelpCommand(nId, pParentItemData->aHelpCommandStr);
+
+ PopupMenu* pPopup = m_pMenu->GetPopupMenu(nId);
+ mpParentPopup->SetPopupMenu(nId, pPopup);
+ }
+ }
+ // the menu bar could have height 0 in fullscreen mode:
+ // so do not use always WindowHeight, as ItemHeight < WindowHeight.
+ if ( GetSizePixel().Height() )
+ {
+ // #107747# give menuitems the height of the menubar
+ aItemBottomRight.AdjustY(GetOutputSizePixel().Height()-1 );
+ }
+
+ // ImplExecute is not modal...
+ // #99071# do not grab the focus, otherwise it will be restored to the menubar
+ // when the frame is reactivated later
+ //GrabFocus();
+ m_pActivePopup->ImplExecute( this, tools::Rectangle( aItemTopLeft, aItemBottomRight ), FloatWinPopupFlags::Down | FloatWinPopupFlags::NoHorzPlacement, m_pMenu, bPreSelectFirst );
+ // does not have a window, if aborted before or if there are no entries
+ if ( m_pActivePopup->ImplGetFloatingWindow() )
+ m_pActivePopup->ImplGetFloatingWindow()->AddPopupModeWindow( this );
+ else
+ m_pActivePopup = nullptr;
+}
+
+void MenuBarWindow::KillActivePopup()
+{
+ if ( !m_pActivePopup )
+ return;
+
+ if( m_pActivePopup->pWindow )
+ if( static_cast<FloatingWindow *>(m_pActivePopup->pWindow.get())->IsInCleanUp() )
+ return; // kill it later
+
+ if ( m_pActivePopup->bInCallback )
+ m_pActivePopup->bCanceled = true;
+
+ m_pActivePopup->bInCallback = true;
+ m_pActivePopup->Deactivate();
+ m_pActivePopup->bInCallback = false;
+ // check for pActivePopup, if stopped by deactivate...
+ if ( m_pActivePopup->ImplGetWindow() )
+ {
+ if (mpParentPopup)
+ {
+ for (sal_uInt16 i = 0; i < mpParentPopup->GetItemCount(); ++i)
+ {
+ sal_uInt16 nId = mpParentPopup->GetItemId(i);
+ MenuItemData* pParentItemData = mpParentPopup->GetItemList()->GetData(nId);
+ assert(pParentItemData);
+ pParentItemData->pSubMenu = nullptr;
+ }
+ }
+ m_pActivePopup->ImplGetFloatingWindow()->StopExecute();
+ m_pActivePopup->ImplGetFloatingWindow()->doShutdown();
+ m_pActivePopup->pWindow.disposeAndClear();
+ }
+ m_pActivePopup = nullptr;
+}
+
+void MenuBarWindow::PopupClosed( Menu const * pPopup )
+{
+ if ( pPopup == m_pActivePopup )
+ {
+ KillActivePopup();
+ ChangeHighlightItem( ITEMPOS_INVALID, false, ImplGetFrameWindow()->ImplGetFrameData()->mbHasFocus, false );
+ }
+}
+
+void MenuBarWindow::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ mbAutoPopup = true;
+ sal_uInt16 nEntry = ImplFindEntry( rMEvt.GetPosPixel() );
+ if ( ( nEntry != ITEMPOS_INVALID ) && !m_pActivePopup )
+ {
+ ChangeHighlightItem( nEntry, false );
+ }
+ else
+ {
+ KillActivePopup();
+ ChangeHighlightItem( ITEMPOS_INVALID, false );
+ }
+}
+
+void MenuBarWindow::MouseButtonUp( const MouseEvent& )
+{
+}
+
+void MenuBarWindow::MouseMove( const MouseEvent& rMEvt )
+{
+ if ( rMEvt.IsSynthetic() || rMEvt.IsEnterWindow() )
+ return;
+
+ if ( rMEvt.IsLeaveWindow() )
+ {
+ if ( m_nRolloveredItem != ITEMPOS_INVALID && m_nRolloveredItem != m_nHighlightedItem )
+ Invalidate(); //HighlightItem( nRolloveredItem, false );
+
+ m_nRolloveredItem = ITEMPOS_INVALID;
+ return;
+ }
+
+ sal_uInt16 nEntry = ImplFindEntry( rMEvt.GetPosPixel() );
+ if ( m_nHighlightedItem == ITEMPOS_INVALID )
+ {
+ if ( m_nRolloveredItem != nEntry )
+ {
+ if ( m_nRolloveredItem != ITEMPOS_INVALID )
+ Invalidate(); //HighlightItem( nRolloveredItem, false );
+
+ m_nRolloveredItem = nEntry;
+ Invalidate(); //HighlightItem( nRolloveredItem, true );
+ }
+ return;
+ }
+ m_nRolloveredItem = nEntry;
+
+ if( m_bIgnoreFirstMove )
+ {
+ m_bIgnoreFirstMove = false;
+ return;
+ }
+
+ if ( ( nEntry != ITEMPOS_INVALID )
+ && ( nEntry != m_nHighlightedItem ) )
+ ChangeHighlightItem( nEntry, false );
+}
+
+void MenuBarWindow::ChangeHighlightItem( sal_uInt16 n, bool bSelectEntry, bool bAllowRestoreFocus, bool bDefaultToDocument)
+{
+ if( ! m_pMenu )
+ return;
+
+ // #57934# close active popup if applicable, as TH's background storage works.
+ MenuItemData* pNextData = m_pMenu->pItemList->GetDataFromPos( n );
+ if ( m_pActivePopup && m_pActivePopup->ImplGetWindow() && ( !pNextData || ( m_pActivePopup != pNextData->pSubMenu ) ) )
+ KillActivePopup(); // pActivePopup when applicable without pWin, if Rescheduled in Activate()
+
+ // activate menubar only ones per cycle...
+ bool bJustActivated = false;
+ if ( ( m_nHighlightedItem == ITEMPOS_INVALID ) && ( n != ITEMPOS_INVALID ) )
+ {
+ ImplGetSVData()->mpWinData->mbNoDeactivate = true;
+ // #105406# avoid saving the focus when we already have the focus
+ bool bNoSaveFocus = (this == ImplGetSVData()->mpWinData->mpFocusWin.get());
+
+ if( m_xSaveFocusId != nullptr )
+ {
+ if (!ImplGetSVData()->mpWinData->mbNoSaveFocus)
+ {
+ m_xSaveFocusId = nullptr;
+ if( !bNoSaveFocus )
+ m_xSaveFocusId = Window::SaveFocus(); // only save focus when initially activated
+ }
+ else {
+ ; // do nothing: we 're activated again from taskpanelist, focus was already saved
+ }
+ }
+ else
+ {
+ if( !bNoSaveFocus )
+ m_xSaveFocusId = Window::SaveFocus(); // only save focus when initially activated
+ }
+ m_pMenu->bInCallback = true; // set here if Activate overridden
+ m_pMenu->Activate();
+ m_pMenu->bInCallback = false;
+ bJustActivated = true;
+ }
+ else if ( ( m_nHighlightedItem != ITEMPOS_INVALID ) && ( n == ITEMPOS_INVALID ) )
+ {
+ m_pMenu->bInCallback = true;
+ m_pMenu->Deactivate();
+ m_pMenu->bInCallback = false;
+ ImplGetSVData()->mpWinData->mbNoDeactivate = false;
+ if (!ImplGetSVData()->mpWinData->mbNoSaveFocus)
+ {
+ VclPtr<vcl::Window> xTempFocusId;
+ if (m_xSaveFocusId && !m_xSaveFocusId->isDisposed())
+ xTempFocusId = m_xSaveFocusId;
+ m_xSaveFocusId = nullptr;
+
+ if (bAllowRestoreFocus)
+ {
+ // tdf#115227 the popup is already killed, so temporarily set us as the
+ // focus window, so we could avoid sending superfluous activate events
+ // to top window listeners.
+ if (xTempFocusId || bDefaultToDocument)
+ ImplGetSVData()->mpWinData->mpFocusWin = this;
+
+ // #105406# restore focus to document if we could not save focus before
+ if (!xTempFocusId && bDefaultToDocument)
+ GrabFocusToDocument();
+ else
+ Window::EndSaveFocus(xTempFocusId);
+ }
+ }
+ }
+
+ if ( m_nHighlightedItem != ITEMPOS_INVALID )
+ {
+ if ( m_nHighlightedItem != m_nRolloveredItem )
+ Invalidate(); //HighlightItem( nHighlightedItem, false );
+
+ m_pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, m_nHighlightedItem );
+ }
+
+ m_nHighlightedItem = n;
+ SAL_WARN_IF( ( m_nHighlightedItem != ITEMPOS_INVALID ) && !m_pMenu->ImplIsVisible( m_nHighlightedItem ), "vcl", "ChangeHighlightItem: Not visible!" );
+ if ( m_nHighlightedItem != ITEMPOS_INVALID )
+ Invalidate(); //HighlightItem( nHighlightedItem, true );
+ else if ( m_nRolloveredItem != ITEMPOS_INVALID )
+ Invalidate(); //HighlightItem( nRolloveredItem, true );
+ m_pMenu->ImplCallHighlight(m_nHighlightedItem);
+
+ if( mbAutoPopup )
+ ImplCreatePopup( bSelectEntry );
+
+ // #58935# #73659# Focus, if no popup underneath...
+ if ( bJustActivated && !m_pActivePopup )
+ GrabFocus();
+}
+
+static int ImplGetTopDockingAreaHeight( vcl::Window const *pWindow )
+{
+ // find docking area that is top aligned and return its height
+ // note: dockingareas are direct children of the SystemWindow
+ if( pWindow->ImplGetFrameWindow() )
+ {
+ vcl::Window *pWin = pWindow->ImplGetFrameWindow()->GetWindow( GetWindowType::FirstChild ); //mpWindowImpl->mpFirstChild;
+ while( pWin )
+ {
+ if( pWin->IsSystemWindow() )
+ {
+ vcl::Window *pChildWin = pWin->GetWindow( GetWindowType::FirstChild ); //mpWindowImpl->mpFirstChild;
+ while( pChildWin )
+ {
+ DockingAreaWindow *pDockingArea = nullptr;
+ if ( pChildWin->GetType() == WindowType::DOCKINGAREA )
+ pDockingArea = static_cast< DockingAreaWindow* >( pChildWin );
+
+ if( pDockingArea && pDockingArea->GetAlign() == WindowAlign::Top &&
+ pDockingArea->IsVisible() && pDockingArea->GetOutputSizePixel().Height() != 0 )
+ {
+ return pDockingArea->GetOutputSizePixel().Height();
+ }
+
+ pChildWin = pChildWin->GetWindow( GetWindowType::Next ); //mpWindowImpl->mpNext;
+ }
+
+ }
+
+ pWin = pWin->GetWindow( GetWindowType::Next ); //mpWindowImpl->mpNext;
+ }
+ }
+ return 0;
+}
+
+static void ImplAddNWFSeparator(vcl::RenderContext& rRenderContext, const Size& rSize, const MenubarValue& rMenubarValue)
+{
+ // add a separator if
+ // - we have an adjacent docking area
+ // - and if toolbars would draw them as well (mbDockingAreaSeparateTB must not be set, see dockingarea.cxx)
+ if (rMenubarValue.maTopDockingAreaHeight
+ && !ImplGetSVData()->maNWFData.mbDockingAreaSeparateTB
+ && !ImplGetSVData()->maNWFData.mbDockingAreaAvoidTBFrames)
+ {
+ // note: the menubar only provides the upper (dark) half of it, the rest (bright part) is drawn by the docking area
+
+ rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetSeparatorColor());
+ tools::Rectangle aRect(Point(), rSize);
+ rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight());
+ }
+}
+
+void MenuBarWindow::HighlightItem(vcl::RenderContext& rRenderContext, sal_uInt16 nPos)
+{
+ if (!m_pMenu)
+ return;
+
+ tools::Long nX = 0;
+ size_t nCount = m_pMenu->pItemList->size();
+
+ Size aOutputSize = GetOutputSizePixel();
+ aOutputSize.AdjustWidth( -(m_aCloseBtn->GetSizePixel().Width()) );
+
+ for (size_t n = 0; n < nCount; n++)
+ {
+ MenuItemData* pData = m_pMenu->pItemList->GetDataFromPos( n );
+ if (n == nPos)
+ {
+ if (pData->eType != MenuItemType::SEPARATOR)
+ {
+ // #107747# give menuitems the height of the menubar
+ tools::Rectangle aRect(Point(nX, 1), Size(pData->aSz.Width(), aOutputSize.Height() - 2));
+ rRenderContext.Push(vcl::PushFlags::CLIPREGION);
+ rRenderContext.IntersectClipRegion(aRect);
+ bool bRollover, bHighlight;
+ if (!ImplGetSVData()->maNWFData.mbRolloverMenubar)
+ {
+ bHighlight = true;
+ bRollover = nPos != m_nHighlightedItem;
+ }
+ else
+ {
+ bRollover = nPos == m_nRolloveredItem;
+ bHighlight = nPos == m_nHighlightedItem;
+ }
+ if (rRenderContext.IsNativeControlSupported(ControlType::Menubar, ControlPart::MenuItem) &&
+ rRenderContext.IsNativeControlSupported(ControlType::Menubar, ControlPart::Entire))
+ {
+ // draw background (transparency)
+ MenubarValue aControlValue;
+ aControlValue.maTopDockingAreaHeight = ImplGetTopDockingAreaHeight( this );
+
+ if (!Application::GetSettings().GetStyleSettings().GetPersonaHeader().IsEmpty() )
+ Erase(rRenderContext);
+ else
+ {
+ tools::Rectangle aBgRegion(Point(), aOutputSize);
+ rRenderContext.DrawNativeControl(ControlType::Menubar, ControlPart::Entire, aBgRegion,
+ ControlState::ENABLED, aControlValue, OUString());
+ }
+
+ ImplAddNWFSeparator(rRenderContext, aOutputSize, aControlValue);
+
+ // draw selected item
+ ControlState nState = ControlState::ENABLED;
+ if (bRollover)
+ nState |= ControlState::ROLLOVER;
+ else
+ nState |= ControlState::SELECTED;
+ rRenderContext.DrawNativeControl(ControlType::Menubar, ControlPart::MenuItem,
+ aRect, nState, aControlValue, OUString() );
+ }
+ else
+ {
+ if (bRollover)
+ rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuBarRolloverColor());
+ else
+ rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuHighlightColor());
+ rRenderContext.SetLineColor();
+ rRenderContext.DrawRect(aRect);
+ }
+ rRenderContext.Pop();
+
+ m_pMenu->ImplPaint(rRenderContext, aOutputSize, 0, 0, pData, bHighlight, false, bRollover);
+ }
+ return;
+ }
+
+ nX += pData->aSz.Width();
+ }
+}
+
+tools::Rectangle MenuBarWindow::ImplGetItemRect( sal_uInt16 nPos ) const
+{
+ tools::Rectangle aRect;
+ if( m_pMenu )
+ {
+ tools::Long nX = 0;
+ size_t nCount = m_pMenu->pItemList->size();
+ for ( size_t n = 0; n < nCount; n++ )
+ {
+ MenuItemData* pData = m_pMenu->pItemList->GetDataFromPos( n );
+ if ( n == nPos )
+ {
+ if ( pData->eType != MenuItemType::SEPARATOR )
+ // #107747# give menuitems the height of the menubar
+ aRect = tools::Rectangle( Point( nX, 1 ), Size( pData->aSz.Width(), GetOutputSizePixel().Height()-2 ) );
+ break;
+ }
+
+ nX += pData->aSz.Width();
+ }
+ }
+ return aRect;
+}
+
+void MenuBarWindow::KeyInput( const KeyEvent& rKEvent )
+{
+ if ( !HandleKeyEvent( rKEvent ) )
+ Window::KeyInput( rKEvent );
+}
+
+bool MenuBarWindow::HandleKeyEvent( const KeyEvent& rKEvent, bool bFromMenu )
+{
+ if (!m_pMenu)
+ return false;
+
+ if (m_pMenu->bInCallback)
+ return true; // swallow
+
+ bool bDone = false;
+ sal_uInt16 nCode = rKEvent.GetKeyCode().GetCode();
+
+ if( GetParent() )
+ {
+ if( GetParent()->GetWindow( GetWindowType::Client )->IsSystemWindow() )
+ {
+ SystemWindow *pSysWin = static_cast<SystemWindow*>(GetParent()->GetWindow( GetWindowType::Client ));
+ if( pSysWin->GetTaskPaneList() )
+ if( pSysWin->GetTaskPaneList()->HandleKeyEvent( rKEvent ) )
+ return true;
+ }
+ }
+
+ // no key events if native menus
+ if (m_pMenu->ImplGetSalMenu() && m_pMenu->ImplGetSalMenu()->VisibleMenuBar())
+ {
+ return false;
+ }
+
+ if ( nCode == KEY_MENU && !rKEvent.GetKeyCode().IsShift() ) // only F10, not Shift-F10
+ {
+ mbAutoPopup = false;
+ if ( m_nHighlightedItem == ITEMPOS_INVALID )
+ {
+ ChangeHighlightItem( 0, false );
+ GrabFocus();
+ }
+ else
+ {
+ ChangeHighlightItem( ITEMPOS_INVALID, false );
+ m_xSaveFocusId = nullptr;
+ }
+ bDone = true;
+ }
+ else if ( bFromMenu )
+ {
+ if ( ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) ||
+ ( nCode == KEY_HOME ) || ( nCode == KEY_END ) )
+ {
+ sal_uInt16 n = m_nHighlightedItem;
+ if ( n == ITEMPOS_INVALID )
+ {
+ if ( nCode == KEY_LEFT)
+ n = 0;
+ else
+ n = m_pMenu->GetItemCount()-1;
+ }
+
+ sal_uInt16 nLoop = n;
+
+ if( nCode == KEY_HOME )
+ { n = sal_uInt16(-1); nLoop = n+1; }
+ if( nCode == KEY_END )
+ { n = m_pMenu->GetItemCount(); nLoop = n-1; }
+
+ do
+ {
+ if ( nCode == KEY_LEFT || nCode == KEY_END )
+ {
+ if ( n )
+ n--;
+ else
+ n = m_pMenu->GetItemCount()-1;
+ }
+ if ( nCode == KEY_RIGHT || nCode == KEY_HOME )
+ {
+ n++;
+ if ( n >= m_pMenu->GetItemCount() )
+ n = 0;
+ }
+
+ MenuItemData* pData = m_pMenu->GetItemList()->GetDataFromPos( n );
+ if (pData->eType != MenuItemType::SEPARATOR &&
+ m_pMenu->ImplIsVisible(n) &&
+ !m_pMenu->ImplCurrentlyHiddenOnGUI(n))
+ {
+ ChangeHighlightItem( n, true );
+ break;
+ }
+ } while ( n != nLoop );
+ bDone = true;
+ }
+ else if ( nCode == KEY_RETURN )
+ {
+ if( m_pActivePopup ) KillActivePopup();
+ else
+ if ( !mbAutoPopup )
+ {
+ ImplCreatePopup( true );
+ mbAutoPopup = true;
+ }
+ bDone = true;
+ }
+ else if ( ( nCode == KEY_UP ) || ( nCode == KEY_DOWN ) )
+ {
+ if ( !mbAutoPopup )
+ {
+ ImplCreatePopup( true );
+ mbAutoPopup = true;
+ }
+ bDone = true;
+ }
+ else if ( nCode == KEY_ESCAPE || ( nCode == KEY_F6 && rKEvent.GetKeyCode().IsMod1() ) )
+ {
+ if( m_pActivePopup )
+ {
+ // hide the menu and remove the focus...
+ mbAutoPopup = false;
+ KillActivePopup();
+ }
+
+ ChangeHighlightItem( ITEMPOS_INVALID, false );
+
+ if( nCode == KEY_F6 && rKEvent.GetKeyCode().IsMod1() )
+ {
+ // put focus into document
+ GrabFocusToDocument();
+ }
+
+ bDone = true;
+ }
+ }
+
+ if ( !bDone && ( bFromMenu || rKEvent.GetKeyCode().IsMod2() ) )
+ {
+ sal_Unicode nCharCode = rKEvent.GetCharCode();
+ if ( nCharCode )
+ {
+ size_t nEntry, nDuplicates;
+ MenuItemData* pData = m_pMenu->GetItemList()->SearchItem( nCharCode, rKEvent.GetKeyCode(), nEntry, nDuplicates, m_nHighlightedItem );
+ if ( pData && (nEntry != ITEMPOS_INVALID) )
+ {
+ mbAutoPopup = true;
+ ChangeHighlightItem( nEntry, true );
+ bDone = true;
+ }
+ }
+ }
+
+ return bDone;
+}
+
+void MenuBarWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ if (!m_pMenu)
+ return;
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ Size aOutputSize = GetOutputSizePixel();
+
+ // no VCL paint if native menus
+ if (m_pMenu->ImplGetSalMenu() && m_pMenu->ImplGetSalMenu()->VisibleMenuBar())
+ {
+ ImplGetFrame()->DrawMenuBar();
+ return;
+ }
+
+ // Make sure that all actual rendering happens in one go to avoid flicker.
+ vcl::BufferDevice pBuffer(this, rRenderContext);
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::Menubar, ControlPart::Entire))
+ {
+ MenubarValue aMenubarValue;
+ aMenubarValue.maTopDockingAreaHeight = ImplGetTopDockingAreaHeight(this);
+
+ if (!rStyleSettings.GetPersonaHeader().IsEmpty())
+ Erase(*pBuffer);
+ else
+ {
+ tools::Rectangle aCtrlRegion( Point(), aOutputSize );
+
+ pBuffer->DrawNativeControl(ControlType::Menubar, ControlPart::Entire, aCtrlRegion,
+ ControlState::ENABLED, aMenubarValue, OUString());
+ }
+
+ ImplAddNWFSeparator(*pBuffer, aOutputSize, aMenubarValue);
+ }
+
+ // shrink the area of the buttons
+ aOutputSize.AdjustWidth( -(m_aCloseBtn->GetSizePixel().Width()) );
+
+ pBuffer->SetFillColor(rStyleSettings.GetMenuColor());
+ m_pMenu->ImplPaint(*pBuffer, aOutputSize, 0);
+
+ if (m_nHighlightedItem != ITEMPOS_INVALID && m_pMenu && !m_pMenu->GetItemList()->GetDataFromPos(m_nHighlightedItem)->bHiddenOnGUI)
+ HighlightItem(*pBuffer, m_nHighlightedItem);
+ else if (m_nRolloveredItem != ITEMPOS_INVALID)
+ HighlightItem(*pBuffer, m_nRolloveredItem);
+
+ // in high contrast mode draw a separating line on the lower edge
+ if (!rRenderContext.IsNativeControlSupported( ControlType::Menubar, ControlPart::Entire) &&
+ rStyleSettings.GetHighContrastMode())
+ {
+ pBuffer->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::MAPMODE);
+ pBuffer->SetLineColor(COL_WHITE);
+ pBuffer->SetMapMode(MapMode(MapUnit::MapPixel));
+ Size aSize = GetSizePixel();
+ pBuffer->DrawLine(Point(0, aSize.Height() - 1),
+ Point(aSize.Width() - 1, aSize.Height() - 1));
+ pBuffer->Pop();
+ }
+}
+
+void MenuBarWindow::Resize()
+{
+ Size aOutSz = GetOutputSizePixel();
+ tools::Long n = aOutSz.Height()-4;
+ tools::Long nX = aOutSz.Width()-3;
+ tools::Long nY = 2;
+
+ if ( m_aCloseBtn->IsVisible() )
+ {
+ m_aCloseBtn->Hide();
+ m_aCloseBtn->SetImages(n);
+ Size aTbxSize( m_aCloseBtn->CalcWindowSizePixel() );
+ nX -= aTbxSize.Width();
+ tools::Long nTbxY = (aOutSz.Height() - aTbxSize.Height())/2;
+ m_aCloseBtn->setPosSizePixel(nX, nTbxY, aTbxSize.Width(), aTbxSize.Height());
+ nX -= 3;
+ m_aCloseBtn->Show();
+ }
+ if ( m_aFloatBtn->IsVisible() )
+ {
+ nX -= n;
+ m_aFloatBtn->setPosSizePixel( nX, nY, n, n );
+ }
+ if ( m_aHideBtn->IsVisible() )
+ {
+ nX -= n;
+ m_aHideBtn->setPosSizePixel( nX, nY, n, n );
+ }
+
+ m_aFloatBtn->SetSymbol( SymbolType::FLOAT );
+ m_aHideBtn->SetSymbol( SymbolType::HIDE );
+
+ Invalidate();
+}
+
+sal_uInt16 MenuBarWindow::ImplFindEntry( const Point& rMousePos ) const
+{
+ if( m_pMenu )
+ {
+ tools::Long nX = 0;
+ size_t nCount = m_pMenu->pItemList->size();
+ for ( size_t n = 0; n < nCount; n++ )
+ {
+ MenuItemData* pData = m_pMenu->pItemList->GetDataFromPos( n );
+ if ( m_pMenu->ImplIsVisible( n ) )
+ {
+ nX += pData->aSz.Width();
+ if ( nX > rMousePos.X() )
+ return static_cast<sal_uInt16>(n);
+ }
+ }
+ }
+ return ITEMPOS_INVALID;
+}
+
+void MenuBarWindow::RequestHelp( const HelpEvent& rHEvt )
+{
+ sal_uInt16 nId = m_nHighlightedItem;
+ if ( rHEvt.GetMode() & HelpEventMode::CONTEXT )
+ ChangeHighlightItem( ITEMPOS_INVALID, true );
+
+ tools::Rectangle aHighlightRect( ImplGetItemRect( m_nHighlightedItem ) );
+ if( !ImplHandleHelpEvent( this, m_pMenu, nId, rHEvt, aHighlightRect ) )
+ Window::RequestHelp( rHEvt );
+}
+
+void MenuBarWindow::StateChanged( StateChangedType nType )
+{
+ Window::StateChanged( nType );
+
+ if (nType == StateChangedType::ControlForeground ||
+ nType == StateChangedType::ControlBackground)
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ else if (nType == StateChangedType::Enable)
+ {
+ Invalidate();
+ }
+ else if(m_pMenu)
+ {
+ m_pMenu->ImplKillLayoutData();
+ }
+}
+
+void MenuBarWindow::LayoutChanged()
+{
+ if (!m_pMenu)
+ return;
+
+ ApplySettings(*GetOutDev());
+
+ // if the font was changed.
+ tools::Long nHeight = m_pMenu->ImplCalcSize(this).Height();
+
+ // depending on the native implementation or the displayable flag
+ // the menubar windows is suppressed (ie, height=0)
+ if (!static_cast<MenuBar*>(m_pMenu.get())->IsDisplayable() ||
+ (m_pMenu->ImplGetSalMenu() && m_pMenu->ImplGetSalMenu()->VisibleMenuBar()))
+ {
+ nHeight = 0;
+ }
+ setPosSizePixel(0, 0, 0, nHeight, PosSizeFlags::Height);
+ GetParent()->Resize();
+ Invalidate();
+ Resize();
+
+ m_pMenu->ImplKillLayoutData();
+}
+
+void MenuBarWindow::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ Window::ApplySettings(rRenderContext);
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ SetPointFont(rRenderContext, rStyleSettings.GetMenuFont());
+
+ const BitmapEx& rPersonaBitmap = Application::GetSettings().GetStyleSettings().GetPersonaHeader();
+ SalMenu *pNativeMenu = m_pMenu ? m_pMenu->ImplGetSalMenu() : nullptr;
+ if (pNativeMenu)
+ pNativeMenu->ApplyPersona();
+ if (!rPersonaBitmap.IsEmpty())
+ {
+ Wallpaper aWallpaper(rPersonaBitmap);
+ aWallpaper.SetStyle(WallpaperStyle::TopRight);
+ aWallpaper.SetColor(Application::GetSettings().GetStyleSettings().GetWorkspaceColor());
+
+ rRenderContext.SetBackground(aWallpaper);
+ SetPaintTransparent(false);
+ SetParentClipMode();
+ }
+ else if (rRenderContext.IsNativeControlSupported(ControlType::Menubar, ControlPart::Entire))
+ {
+ rRenderContext.SetBackground(); // background will be drawn by NWF
+ }
+ else
+ {
+ Wallpaper aWallpaper;
+ aWallpaper.SetStyle(WallpaperStyle::ApplicationGradient);
+ rRenderContext.SetBackground(aWallpaper);
+ SetPaintTransparent(false);
+ SetParentClipMode();
+ }
+
+ rRenderContext.SetTextColor(rStyleSettings.GetMenuBarTextColor());
+ rRenderContext.SetTextFillColor();
+ rRenderContext.SetLineColor();
+}
+
+void MenuBarWindow::ImplInitStyleSettings()
+{
+ if (!(IsNativeControlSupported(ControlType::Menubar, ControlPart::MenuItem) &&
+ IsNativeControlSupported(ControlType::Menubar, ControlPart::Entire)))
+ return;
+
+ AllSettings aSettings(GetSettings());
+ ImplGetFrame()->UpdateSettings(aSettings); // to update persona
+ StyleSettings aStyle(aSettings.GetStyleSettings());
+ Color aHighlightTextColor = ImplGetSVData()->maNWFData.maMenuBarHighlightTextColor;
+ if (aHighlightTextColor != COL_TRANSPARENT)
+ {
+ aStyle.SetMenuHighlightTextColor(aHighlightTextColor);
+ }
+ aSettings.SetStyleSettings(aStyle);
+ GetOutDev()->SetSettings(aSettings);
+}
+
+void MenuBarWindow::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Window::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ApplySettings(*GetOutDev());
+ ImplInitStyleSettings();
+ LayoutChanged();
+ }
+}
+
+void MenuBarWindow::LoseFocus()
+{
+ if ( !HasChildPathFocus( true ) )
+ ChangeHighlightItem( ITEMPOS_INVALID, false, false );
+}
+
+void MenuBarWindow::GetFocus()
+{
+ SalMenu *pNativeMenu = m_pMenu ? m_pMenu->ImplGetSalMenu() : nullptr;
+ if (pNativeMenu && pNativeMenu->TakeFocus())
+ return;
+
+ if ( m_nHighlightedItem == ITEMPOS_INVALID )
+ {
+ mbAutoPopup = false; // do not open menu when activated by focus handling like taskpane cycling
+ ChangeHighlightItem( 0, false );
+ }
+}
+
+css::uno::Reference<css::accessibility::XAccessible> MenuBarWindow::CreateAccessible()
+{
+ css::uno::Reference<css::accessibility::XAccessible> xAcc;
+
+ if (m_pMenu)
+ xAcc = m_pMenu->GetAccessible();
+
+ return xAcc;
+}
+
+sal_uInt16 MenuBarWindow::AddMenuBarButton( const Image& i_rImage, const Link<MenuBarButtonCallbackArg&,bool>& i_rLink, const OUString& i_rToolTip )
+{
+ // find first free button id
+ sal_uInt16 nId = IID_DOCUMENTCLOSE;
+ std::map< sal_uInt16, AddButtonEntry >::const_iterator it;
+ do
+ {
+ nId++;
+ it = m_aAddButtons.find( nId );
+ } while( it != m_aAddButtons.end() && nId < 128 );
+ SAL_WARN_IF( nId >= 128, "vcl", "too many addbuttons in menubar" );
+ AddButtonEntry& rNewEntry = m_aAddButtons[nId];
+ rNewEntry.m_aSelectLink = i_rLink;
+ m_aCloseBtn->InsertItem(ToolBoxItemId(nId), i_rImage, ToolBoxItemBits::NONE, 0);
+ m_aCloseBtn->calcMinSize();
+ ShowButtons(m_aCloseBtn->IsItemVisible(ToolBoxItemId(IID_DOCUMENTCLOSE)), m_aFloatBtn->IsVisible(), m_aHideBtn->IsVisible());
+ LayoutChanged();
+
+ if( m_pMenu->mpSalMenu )
+ m_pMenu->mpSalMenu->AddMenuBarButton( SalMenuButtonItem( nId, i_rImage, i_rToolTip ) );
+
+ return nId;
+}
+
+void MenuBarWindow::SetMenuBarButtonHighlightHdl( sal_uInt16 nId, const Link<MenuBarButtonCallbackArg&,bool>& rLink )
+{
+ std::map< sal_uInt16, AddButtonEntry >::iterator it = m_aAddButtons.find( nId );
+ if( it != m_aAddButtons.end() )
+ it->second.m_aHighlightLink = rLink;
+}
+
+tools::Rectangle MenuBarWindow::GetMenuBarButtonRectPixel( sal_uInt16 nId )
+{
+ tools::Rectangle aRect;
+ if( m_aAddButtons.find( nId ) != m_aAddButtons.end() )
+ {
+ if( m_pMenu->mpSalMenu )
+ {
+ aRect = m_pMenu->mpSalMenu->GetMenuBarButtonRectPixel( nId, ImplGetWindowImpl()->mpFrame );
+ if( aRect == tools::Rectangle( Point( -1, -1 ), Size( 1, 1 ) ) )
+ {
+ // system menu button is somewhere but location cannot be determined
+ return tools::Rectangle();
+ }
+ }
+
+ if( aRect.IsEmpty() )
+ {
+ aRect = m_aCloseBtn->GetItemRect(ToolBoxItemId(nId));
+ Point aOffset = m_aCloseBtn->OutputToScreenPixel(Point());
+ aRect.Move( aOffset.X(), aOffset.Y() );
+ }
+ }
+ return aRect;
+}
+
+void MenuBarWindow::RemoveMenuBarButton( sal_uInt16 nId )
+{
+ ToolBox::ImplToolItems::size_type nPos = m_aCloseBtn->GetItemPos(ToolBoxItemId(nId));
+ m_aCloseBtn->RemoveItem(nPos);
+ m_aAddButtons.erase( nId );
+ m_aCloseBtn->calcMinSize();
+ LayoutChanged();
+
+ if( m_pMenu->mpSalMenu )
+ m_pMenu->mpSalMenu->RemoveMenuBarButton( nId );
+}
+
+bool MenuBarWindow::HandleMenuButtonEvent( sal_uInt16 i_nButtonId )
+{
+ std::map< sal_uInt16, AddButtonEntry >::iterator it = m_aAddButtons.find( i_nButtonId );
+ if( it != m_aAddButtons.end() )
+ {
+ MenuBarButtonCallbackArg aArg;
+ aArg.nId = it->first;
+ aArg.bHighlight = true;
+ return it->second.m_aSelectLink.Call( aArg );
+ }
+ return false;
+}
+
+bool MenuBarWindow::CanGetFocus() const
+{
+ /* #i83908# do not use the menubar if it is native or invisible
+ this relies on MenuBar::ImplCreate setting the height of the menubar
+ to 0 in this case
+ */
+ SalMenu *pNativeMenu = m_pMenu ? m_pMenu->ImplGetSalMenu() : nullptr;
+ if (pNativeMenu && pNativeMenu->VisibleMenuBar())
+ return pNativeMenu->CanGetFocus();
+ return GetSizePixel().Height() > 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menubarwindow.hxx b/vcl/source/window/menubarwindow.hxx
new file mode 100644
index 000000000..cc7963a1b
--- /dev/null
+++ b/vcl/source/window/menubarwindow.hxx
@@ -0,0 +1,142 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include "menuwindow.hxx"
+
+#include <vcl/toolkit/button.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/window.hxx>
+
+#include <map>
+
+class Button;
+
+/** Toolbox that holds the close button (right hand side of the menubar).
+
+This is also used by the online update check; when an update is available, it
+inserts here the button that leads to the download of the update.
+*/
+class DecoToolBox : public ToolBox
+{
+ tools::Long lastSize;
+ Size maMinSize;
+
+public:
+ explicit DecoToolBox(vcl::Window* pParent);
+
+ void DataChanged( const DataChangedEvent& rDCEvt ) override;
+
+ void SetImages( tools::Long nMaxHeight, bool bForce = false );
+
+ void calcMinSize();
+ const Size& getMinSize() const { return maMinSize;}
+
+ Image maImage;
+};
+
+
+/** Class that implements the actual window of the menu bar.
+*/
+class MenuBarWindow : public vcl::Window, public MenuWindow
+{
+ friend class MenuBar;
+ friend class Menu;
+
+private:
+ struct AddButtonEntry
+ {
+ Link<MenuBarButtonCallbackArg&,bool> m_aSelectLink;
+ Link<MenuBarButtonCallbackArg&,bool> m_aHighlightLink;
+ };
+
+ VclPtr<Menu> m_pMenu;
+ VclPtr<PopupMenu> m_pActivePopup;
+ VclPtr<PopupMenu> mpParentPopup;
+ sal_uInt16 m_nHighlightedItem;
+ sal_uInt16 m_nRolloveredItem;
+ VclPtr<vcl::Window> m_xSaveFocusId;
+ bool mbAutoPopup;
+ bool m_bIgnoreFirstMove;
+
+ VclPtr<DecoToolBox> m_aCloseBtn;
+ VclPtr<PushButton> m_aFloatBtn;
+ VclPtr<PushButton> m_aHideBtn;
+
+ std::map< sal_uInt16, AddButtonEntry > m_aAddButtons;
+
+ void HighlightItem(vcl::RenderContext& rRenderContext, sal_uInt16 nPos);
+ void ChangeHighlightItem(sal_uInt16 n, bool bSelectPopupEntry, bool bAllowRestoreFocus = true, bool bDefaultToDocument = true);
+
+ sal_uInt16 ImplFindEntry( const Point& rMousePos ) const;
+ void ImplCreatePopup( bool bPreSelectFirst );
+ bool HandleKeyEvent(const KeyEvent& rKEvent, bool bFromMenu = true);
+ tools::Rectangle ImplGetItemRect( sal_uInt16 nPos ) const;
+
+ void ImplInitStyleSettings();
+
+ virtual void ApplySettings(vcl::RenderContext& rRenderContext) override;
+
+ DECL_LINK( CloseHdl, ToolBox*, void );
+ DECL_LINK( ToolboxEventHdl, VclWindowEvent&, void );
+ DECL_LINK( ShowHideListener, VclWindowEvent&, void );
+
+ void StateChanged( StateChangedType nType ) override;
+ void DataChanged( const DataChangedEvent& rDCEvt ) override;
+ void LoseFocus() override;
+ void GetFocus() override;
+
+public:
+ explicit MenuBarWindow( vcl::Window* pParent );
+ virtual ~MenuBarWindow() override;
+ virtual void dispose() override;
+
+ void ShowButtons(bool bClose, bool bFloat, bool bHide);
+
+ virtual void MouseMove( const MouseEvent& rMEvt ) override;
+ virtual void MouseButtonDown( const MouseEvent& rMEvt ) override;
+ virtual void MouseButtonUp( const MouseEvent& rMEvt ) override;
+ virtual void KeyInput( const KeyEvent& rKEvent ) override;
+ virtual void Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) override;
+ virtual void Resize() override;
+ virtual void RequestHelp( const HelpEvent& rHEvt ) override;
+
+ void SetMenu(MenuBar* pMenu);
+ void SetHeight(tools::Long nHeight);
+ void KillActivePopup();
+ void PopupClosed(Menu const * pMenu);
+ sal_uInt16 GetHighlightedItem() const { return m_nHighlightedItem; }
+ virtual css::uno::Reference<css::accessibility::XAccessible> CreateAccessible() override;
+
+ void SetAutoPopup(bool bAuto) { mbAutoPopup = bAuto; }
+ void LayoutChanged();
+ Size const & MinCloseButtonSize() const;
+
+ /// Add an arbitrary button to the menubar that will appear next to the close button.
+ sal_uInt16 AddMenuBarButton(const Image&, const Link<MenuBarButtonCallbackArg&,bool>&, const OUString&);
+ void SetMenuBarButtonHighlightHdl(sal_uInt16 nId, const Link<MenuBarButtonCallbackArg&,bool>&);
+ tools::Rectangle GetMenuBarButtonRectPixel(sal_uInt16 nId);
+ void RemoveMenuBarButton(sal_uInt16 nId);
+ bool HandleMenuButtonEvent(sal_uInt16 i_nButtonId);
+ bool CanGetFocus() const;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menufloatingwindow.cxx b/vcl/source/window/menufloatingwindow.cxx
new file mode 100644
index 000000000..cfd6a6ae1
--- /dev/null
+++ b/vcl/source/window/menufloatingwindow.cxx
@@ -0,0 +1,1318 @@
+/* -*- 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 "menufloatingwindow.hxx"
+#include "menuitemlist.hxx"
+#include "bufferdevice.hxx"
+
+#include <sal/log.hxx>
+#include <salframe.hxx>
+#include <svdata.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/settings.hxx>
+#include <window.h>
+
+MenuFloatingWindow::MenuFloatingWindow( Menu* pMen, vcl::Window* pParent, WinBits nStyle ) :
+ FloatingWindow( pParent, nStyle ),
+ pMenu(pMen),
+ aHighlightChangedTimer("vcl::MenuFloatingWindow aHighlightChangedTimer"),
+ aSubmenuCloseTimer( "vcl::MenuFloatingWindow aSubmenuCloseTimer" ),
+ aScrollTimer( "vcl::MenuFloatingWindow aScrollTimer" ),
+ nHighlightedItem(ITEMPOS_INVALID),
+ nMBDownPos(ITEMPOS_INVALID),
+ nScrollerHeight(0),
+ nFirstEntry(0),
+ nPosInParent(ITEMPOS_INVALID),
+ bInExecute(false),
+ bScrollMenu(false),
+ bScrollUp(false),
+ bScrollDown(false),
+ bIgnoreFirstMove(true),
+ bKeyInput(false)
+{
+ mpWindowImpl->mbMenuFloatingWindow= true;
+
+ ApplySettings(*GetOutDev());
+
+ SetPopupModeEndHdl( LINK( this, MenuFloatingWindow, PopupEnd ) );
+
+ aHighlightChangedTimer.SetInvokeHandler( LINK( this, MenuFloatingWindow, HighlightChanged ) );
+ aHighlightChangedTimer.SetTimeout( GetSettings().GetMouseSettings().GetMenuDelay() );
+
+ aSubmenuCloseTimer.SetTimeout( GetSettings().GetMouseSettings().GetMenuDelay() );
+ aSubmenuCloseTimer.SetInvokeHandler( LINK( this, MenuFloatingWindow, SubmenuClose ) );
+
+ aScrollTimer.SetInvokeHandler( LINK( this, MenuFloatingWindow, AutoScroll ) );
+
+ AddEventListener( LINK( this, MenuFloatingWindow, ShowHideListener ) );
+}
+
+void MenuFloatingWindow::doShutdown()
+{
+ if( !pMenu )
+ return;
+
+ // #105373# notify toolkit that highlight was removed
+ // otherwise the entry will not be read when the menu is opened again
+ if( nHighlightedItem != ITEMPOS_INVALID )
+ pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, nHighlightedItem );
+ if (!bKeyInput && pMenu && pMenu->pStartedFrom && !pMenu->pStartedFrom->IsMenuBar())
+ {
+ // #102461# remove highlight in parent
+ size_t i, nCount = pMenu->pStartedFrom->pItemList->size();
+ for(i = 0; i < nCount; i++)
+ {
+ MenuItemData* pData = pMenu->pStartedFrom->pItemList->GetDataFromPos( i );
+ if( pData && ( pData->pSubMenu == pMenu ) )
+ break;
+ }
+ if( i < nCount )
+ {
+ MenuFloatingWindow* pPWin = static_cast<MenuFloatingWindow*>(pMenu->pStartedFrom->ImplGetWindow());
+ if (pPWin)
+ pPWin->InvalidateItem(i);
+ }
+ }
+
+ // free the reference to the accessible component
+ SetAccessible( css::uno::Reference< css::accessibility::XAccessible >() );
+
+ aHighlightChangedTimer.Stop();
+
+ // #95056# invalidate screen area covered by system window
+ // so this can be taken into account if the commandhandler performs a scroll operation
+ if( GetParent() )
+ {
+ tools::Rectangle aInvRect( GetWindowExtentsRelative( GetParent() ) );
+ GetParent()->Invalidate( aInvRect );
+ }
+ pMenu = nullptr;
+ RemoveEventListener( LINK( this, MenuFloatingWindow, ShowHideListener ) );
+
+ aScrollTimer.Stop();
+ aSubmenuCloseTimer.Stop();
+ aSubmenuCloseTimer.Stop();
+ aHighlightChangedTimer.Stop();
+ aHighlightChangedTimer.Stop();
+
+}
+
+MenuFloatingWindow::~MenuFloatingWindow()
+{
+ disposeOnce();
+}
+
+void MenuFloatingWindow::dispose()
+{
+ doShutdown();
+ pMenu.clear();
+ pActivePopup.clear();
+ xSaveFocusId.clear();
+ FloatingWindow::dispose();
+}
+
+void MenuFloatingWindow::Resize()
+{
+ InitMenuClipRegion(*GetOutDev()); // FIXME
+}
+
+void MenuFloatingWindow::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ FloatingWindow::ApplySettings(rRenderContext);
+
+ if (IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItem) &&
+ IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
+ {
+ AllSettings aSettings(GetSettings());
+ ImplGetFrame()->UpdateSettings(aSettings); // Update theme colors.
+ StyleSettings aStyle(aSettings.GetStyleSettings());
+ Color aHighlightTextColor = ImplGetSVData()->maNWFData.maMenuBarHighlightTextColor;
+ if (aHighlightTextColor != COL_TRANSPARENT)
+ {
+ aStyle.SetMenuHighlightTextColor(aHighlightTextColor);
+ }
+ aSettings.SetStyleSettings(aStyle);
+ GetOutDev()->SetSettings(aSettings);
+ }
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ SetPointFont(rRenderContext, rStyleSettings.GetMenuFont());
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
+ {
+ rRenderContext.SetBackground(); // background will be drawn by NWF
+ }
+ else
+ rRenderContext.SetBackground(Wallpaper(rStyleSettings.GetMenuColor()));
+
+ rRenderContext.SetTextColor(rStyleSettings.GetMenuTextColor());
+ rRenderContext.SetTextFillColor();
+ rRenderContext.SetLineColor();
+}
+
+/// Get a negative pixel offset for an offset menu
+tools::Long MenuFloatingWindow::ImplGetStartY() const
+{
+ tools::Long nY = 0;
+ if( pMenu )
+ {
+ // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686)
+ if ( nFirstEntry > 0 && !pMenu->GetItemList()->GetDataFromPos(nFirstEntry - 1) )
+ {
+ return 0;
+ }
+
+ for ( sal_uInt16 n = 0; n < nFirstEntry; n++ )
+ nY += pMenu->GetItemList()->GetDataFromPos( n )->aSz.Height();
+ nY -= pMenu->GetTitleHeight();
+ }
+ return -nY;
+}
+
+vcl::Region MenuFloatingWindow::ImplCalcClipRegion() const
+{
+ Size aOutSz = GetOutputSizePixel();
+ tools::Rectangle aRect( Point(), aOutSz );
+ aRect.AdjustTop(nScrollerHeight );
+ aRect.AdjustBottom( -nScrollerHeight );
+
+ vcl::Region aRegion(aRect);
+
+ return aRegion;
+}
+
+void MenuFloatingWindow::InitMenuClipRegion(vcl::RenderContext& rRenderContext)
+{
+ if (IsScrollMenu())
+ {
+ rRenderContext.SetClipRegion(ImplCalcClipRegion());
+ }
+ else
+ {
+ rRenderContext.SetClipRegion();
+ }
+}
+
+void MenuFloatingWindow::ImplHighlightItem( const MouseEvent& rMEvt, bool bMBDown )
+{
+ if( ! pMenu )
+ return;
+
+ tools::Long nY = GetInitialItemY();
+ tools::Long nMouseY = rMEvt.GetPosPixel().Y();
+ Size aOutSz = GetOutputSizePixel();
+ if ( ( nMouseY >= nY ) && ( nMouseY < aOutSz.Height() ) )
+ {
+ bool bHighlighted = false;
+ size_t nCount = pMenu->pItemList->size();
+ for ( size_t n = 0; !bHighlighted && ( n < nCount ); n++ )
+ {
+ if ( pMenu->ImplIsVisible( n ) )
+ {
+ MenuItemData* pItemData = pMenu->pItemList->GetDataFromPos( n );
+ tools::Long nOldY = nY;
+ nY += pItemData->aSz.Height();
+ if ( ( nOldY <= nMouseY ) && ( nY > nMouseY ) && pMenu->ImplIsSelectable( n ) )
+ {
+ bool bPopupArea = true;
+ if ( pItemData->nBits & MenuItemBits::POPUPSELECT )
+ {
+ // only when clicked over the arrow...
+ Size aSz = GetOutputSizePixel();
+ tools::Long nFontHeight = GetTextHeight();
+ bPopupArea = ( rMEvt.GetPosPixel().X() >= ( aSz.Width() - nFontHeight - nFontHeight/4 ) );
+ }
+
+ if ( bMBDown )
+ {
+ if ( n != nHighlightedItem )
+ {
+ ChangeHighlightItem( static_cast<sal_uInt16>(n), false );
+ }
+
+ bool bAllowNewPopup = true;
+ if ( pActivePopup )
+ {
+ MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n );
+ bAllowNewPopup = pData && ( pData->pSubMenu != pActivePopup );
+ if ( bAllowNewPopup )
+ KillActivePopup();
+ }
+
+ if ( bPopupArea && bAllowNewPopup )
+ {
+ HighlightChanged( nullptr );
+ }
+ }
+ else
+ {
+ if ( n != nHighlightedItem )
+ {
+ ChangeHighlightItem( static_cast<sal_uInt16>(n), true );
+ }
+ else if ( pItemData->nBits & MenuItemBits::POPUPSELECT )
+ {
+ if ( bPopupArea && ( pActivePopup != pItemData->pSubMenu ) )
+ HighlightChanged( nullptr );
+ }
+ }
+ bHighlighted = true;
+ }
+ }
+ }
+ if ( !bHighlighted )
+ ChangeHighlightItem( ITEMPOS_INVALID, true );
+ }
+ else
+ {
+ ImplScroll( rMEvt.GetPosPixel() );
+ ChangeHighlightItem( ITEMPOS_INVALID, true );
+ }
+}
+
+IMPL_LINK_NOARG(MenuFloatingWindow, PopupEnd, FloatingWindow*, void)
+{
+ // "this" will be deleted before the end of this method!
+ Menu* pM = pMenu;
+ if ( bInExecute )
+ {
+ End();
+ if ( pActivePopup )
+ {
+ KillActivePopup(); // should be ok to just remove it
+ //pActivePopup->bCanceled = true;
+ }
+ pMenu->bInCallback = true;
+ pMenu->Deactivate();
+ pMenu->bInCallback = false;
+ }
+ else
+ {
+ if (pMenu && pMenu->pStartedFrom)
+ pMenu->pStartedFrom->ClosePopup(pMenu);
+ }
+
+ if ( pM )
+ pM->pStartedFrom = nullptr;
+}
+
+IMPL_LINK_NOARG(MenuFloatingWindow, AutoScroll, Timer *, void)
+{
+ ImplScroll( GetPointerPosPixel() );
+}
+
+IMPL_LINK( MenuFloatingWindow, HighlightChanged, Timer*, pTimer, void )
+{
+ if( ! pMenu )
+ return;
+
+ MenuItemData* pItemData = pMenu->pItemList->GetDataFromPos( nHighlightedItem );
+ if ( !pItemData )
+ return;
+
+ if ( pActivePopup && ( pActivePopup != pItemData->pSubMenu ) )
+ {
+ FloatWinPopupFlags nOldFlags = GetPopupModeFlags();
+ SetPopupModeFlags( GetPopupModeFlags() | FloatWinPopupFlags::NoAppFocusClose );
+ KillActivePopup();
+ SetPopupModeFlags( nOldFlags );
+ }
+ if ( !(pItemData->bEnabled && pItemData->pSubMenu && pItemData->pSubMenu->GetItemCount() && ( pItemData->pSubMenu != pActivePopup )) )
+ return;
+
+ pActivePopup = static_cast<PopupMenu*>(pItemData->pSubMenu.get());
+ tools::Long nY = nScrollerHeight+ImplGetStartY();
+ MenuItemData* pData = nullptr;
+ for ( sal_uLong n = 0; n < nHighlightedItem; n++ )
+ {
+ pData = pMenu->pItemList->GetDataFromPos( n );
+ nY += pData->aSz.Height();
+ }
+ pData = pMenu->pItemList->GetDataFromPos( nHighlightedItem );
+ Size MySize = GetOutputSizePixel();
+ Point aItemTopLeft( 0, nY );
+ Point aItemBottomRight( aItemTopLeft );
+ aItemBottomRight.AdjustX(MySize.Width() );
+ aItemBottomRight.AdjustY(pData->aSz.Height() );
+
+ // shift the popups a little
+ aItemTopLeft.AdjustX(2 );
+ aItemBottomRight.AdjustX( -2 );
+ if ( nHighlightedItem )
+ aItemTopLeft.AdjustY( -2 );
+ else
+ {
+ sal_Int32 nL, nT, nR, nB;
+ GetBorder( nL, nT, nR, nB );
+ aItemTopLeft.AdjustY( -nT );
+ }
+
+ // pTest: crash due to Reschedule() in call of Activate()
+ // Also it is prevented that submenus are displayed which
+ // were for long in Activate Rescheduled and which should not be
+ // displayed now.
+ Menu* pTest = pActivePopup;
+ FloatWinPopupFlags nOldFlags = GetPopupModeFlags();
+ SetPopupModeFlags( GetPopupModeFlags() | FloatWinPopupFlags::NoAppFocusClose );
+ sal_uInt16 nRet = pActivePopup->ImplExecute( this, tools::Rectangle( aItemTopLeft, aItemBottomRight ), FloatWinPopupFlags::Right, pMenu, pTimer == nullptr );
+ SetPopupModeFlags( nOldFlags );
+
+ // nRet != 0, if it was stopped during Activate()...
+ if ( !nRet && ( pActivePopup == pTest ) && pActivePopup->ImplGetWindow() )
+ pActivePopup->ImplGetFloatingWindow()->AddPopupModeWindow( this );
+}
+
+IMPL_LINK_NOARG(MenuFloatingWindow, SubmenuClose, Timer *, void)
+{
+ if( pMenu && pMenu->pStartedFrom )
+ {
+ MenuFloatingWindow* pWin = static_cast<MenuFloatingWindow*>(pMenu->pStartedFrom->GetWindow());
+ if( pWin )
+ pWin->KillActivePopup();
+ }
+}
+
+IMPL_LINK( MenuFloatingWindow, ShowHideListener, VclWindowEvent&, rEvent, void )
+{
+ if( ! pMenu )
+ return;
+
+ if( rEvent.GetId() == VclEventId::WindowShow )
+ pMenu->ImplCallEventListeners( VclEventId::MenuShow, ITEMPOS_INVALID );
+ else if( rEvent.GetId() == VclEventId::WindowHide )
+ pMenu->ImplCallEventListeners( VclEventId::MenuHide, ITEMPOS_INVALID );
+}
+
+void MenuFloatingWindow::EnableScrollMenu( bool b )
+{
+ bScrollMenu = b;
+ nScrollerHeight = b ? static_cast<sal_uInt16>(GetSettings().GetStyleSettings().GetScrollBarSize()) /2 : 0;
+ bScrollDown = true;
+ InitMenuClipRegion(*GetOutDev());
+}
+
+void MenuFloatingWindow::Start()
+{
+ if (bInExecute)
+ return;
+ bInExecute = true;
+ if (GetParent())
+ GetParent()->IncModalCount();
+}
+
+bool MenuFloatingWindow::MenuInHierarchyHasFocus() const
+{
+ if (HasChildPathFocus())
+ return true;
+ PopupMenu* pSub = GetActivePopup();
+ if (!pSub)
+ return false;
+ return pSub->ImplGetFloatingWindow()->HasChildPathFocus();
+}
+
+void MenuFloatingWindow::End()
+{
+ if (!bInExecute)
+ return;
+
+ if (GetParent() && !GetParent()->isDisposed())
+ GetParent()->DecModalCount();
+
+ // restore focus to previous window if we still have the focus
+ VclPtr<vcl::Window> xFocusId(xSaveFocusId);
+ xSaveFocusId = nullptr;
+ if (xFocusId != nullptr && MenuInHierarchyHasFocus())
+ {
+ ImplGetSVData()->mpWinData->mbNoDeactivate = false;
+ Window::EndSaveFocus(xFocusId);
+ }
+
+ bInExecute = false;
+}
+
+void MenuFloatingWindow::Execute()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ pSVData->maAppData.mpActivePopupMenu = static_cast<PopupMenu*>(pMenu.get());
+
+ Start();
+
+ while (bInExecute && !Application::IsQuit())
+ Application::Yield();
+
+ pSVData->maAppData.mpActivePopupMenu = nullptr;
+}
+
+void MenuFloatingWindow::StopExecute()
+{
+ End();
+
+ ImplEndPopupMode(FloatWinPopupEndFlags::NONE, xSaveFocusId);
+
+ aHighlightChangedTimer.Stop();
+ if (pActivePopup)
+ {
+ KillActivePopup();
+ }
+ // notify parent, needed for accessibility
+ if( pMenu && pMenu->pStartedFrom )
+ pMenu->pStartedFrom->ImplCallEventListeners( VclEventId::MenuSubmenuDeactivate, nPosInParent );
+}
+
+void MenuFloatingWindow::KillActivePopup( PopupMenu* pThisOnly )
+{
+ if ( !pActivePopup || ( pThisOnly && ( pThisOnly != pActivePopup ) ) )
+ return;
+
+ if( pActivePopup->pWindow )
+ if( static_cast<FloatingWindow *>(pActivePopup->pWindow.get())->IsInCleanUp() )
+ return; // kill it later
+ if ( pActivePopup->bInCallback )
+ pActivePopup->bCanceled = true;
+
+ // For all actions pActivePopup = 0, if e.g.
+ // PopupModeEndHdl the popups to destroy were called synchronous
+ PopupMenu* pPopup = pActivePopup;
+ pActivePopup = nullptr;
+ pPopup->bInCallback = true;
+ pPopup->Deactivate();
+ pPopup->bInCallback = false;
+ if ( pPopup->ImplGetWindow() )
+ {
+ pPopup->ImplGetFloatingWindow()->StopExecute();
+ pPopup->ImplGetFloatingWindow()->doShutdown();
+ pPopup->pWindow.disposeAndClear();
+
+ PaintImmediately();
+ }
+}
+
+void MenuFloatingWindow::EndExecute()
+{
+ Menu* pStart = pMenu ? pMenu->ImplGetStartMenu() : nullptr;
+
+ // if started elsewhere, cleanup there as well
+ MenuFloatingWindow* pCleanUpFrom = this;
+ MenuFloatingWindow* pWin = this;
+ while (pWin && !pWin->bInExecute &&
+ pWin->pMenu->pStartedFrom && !pWin->pMenu->pStartedFrom->IsMenuBar())
+ {
+ pWin = static_cast<PopupMenu*>(pWin->pMenu->pStartedFrom.get())->ImplGetFloatingWindow();
+ }
+ if ( pWin )
+ pCleanUpFrom = pWin;
+
+ // this window will be destroyed => store date locally...
+ Menu* pM = pMenu;
+ sal_uInt16 nItem = nHighlightedItem;
+
+ pCleanUpFrom->StopExecute();
+
+ if ( !(nItem != ITEMPOS_INVALID && pM) )
+ return;
+
+ MenuItemData* pItemData = pM->GetItemList()->GetDataFromPos( nItem );
+ if ( pItemData && !pItemData->bIsTemporary )
+ {
+ pM->nSelectedId = pItemData->nId;
+ pM->sSelectedIdent = pItemData->sIdent;
+ if (pStart)
+ {
+ pStart->nSelectedId = pItemData->nId;
+ pStart->sSelectedIdent = pItemData->sIdent;
+ }
+
+ pM->ImplSelect();
+ }
+}
+
+void MenuFloatingWindow::EndExecute( sal_uInt16 nId )
+{
+ size_t nPos;
+ if ( pMenu && pMenu->GetItemList()->GetData( nId, nPos ) )
+ nHighlightedItem = nPos;
+ else
+ nHighlightedItem = ITEMPOS_INVALID;
+
+ EndExecute();
+}
+
+void MenuFloatingWindow::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ // TH creates a ToTop on this window, but the active popup
+ // should stay on top...
+ // due to focus change this would close all menus -> don't do it (#94123)
+ //if ( pActivePopup && pActivePopup->ImplGetWindow() && !pActivePopup->ImplGetFloatingWindow()->pActivePopup )
+ // pActivePopup->ImplGetFloatingWindow()->ToTop( ToTopFlags::NoGrabFocus );
+
+ ImplHighlightItem( rMEvt, true );
+
+ nMBDownPos = nHighlightedItem;
+}
+
+void MenuFloatingWindow::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ MenuItemData* pData = pMenu ? pMenu->GetItemList()->GetDataFromPos( nHighlightedItem ) : nullptr;
+ // nMBDownPos store in local variable and reset immediately,
+ // as it will be too late after EndExecute
+ sal_uInt16 _nMBDownPos = nMBDownPos;
+ nMBDownPos = ITEMPOS_INVALID;
+ if ( !(pData && pData->bEnabled && ( pData->eType != MenuItemType::SEPARATOR )) )
+ return;
+
+ if ( !pData->pSubMenu )
+ {
+ EndExecute();
+ }
+ else if ( ( pData->nBits & MenuItemBits::POPUPSELECT ) && ( nHighlightedItem == _nMBDownPos ) && ( rMEvt.GetClicks() == 2 ) )
+ {
+ // not when clicked over the arrow...
+ Size aSz = GetOutputSizePixel();
+ tools::Long nFontHeight = GetTextHeight();
+ if ( rMEvt.GetPosPixel().X() < ( aSz.Width() - nFontHeight - nFontHeight/4 ) )
+ EndExecute();
+ }
+
+}
+
+void MenuFloatingWindow::MouseMove( const MouseEvent& rMEvt )
+{
+ if ( !IsVisible() || rMEvt.IsSynthetic() || rMEvt.IsEnterWindow() )
+ return;
+
+ if ( rMEvt.IsLeaveWindow() )
+ {
+ // #102461# do not remove highlight if a popup menu is open at this position
+ MenuItemData* pData = pMenu ? pMenu->pItemList->GetDataFromPos( nHighlightedItem ) : nullptr;
+ // close popup with some delayed if we leave somewhere else
+ if( pActivePopup && pData && pData->pSubMenu != pActivePopup )
+ pActivePopup->ImplGetFloatingWindow()->aSubmenuCloseTimer.Start();
+
+ if( !pActivePopup || (pData && pData->pSubMenu != pActivePopup ) )
+ ChangeHighlightItem( ITEMPOS_INVALID, false );
+
+ if ( IsScrollMenu() )
+ ImplScroll( rMEvt.GetPosPixel() );
+ }
+ else
+ {
+ aSubmenuCloseTimer.Stop();
+ if( bIgnoreFirstMove )
+ bIgnoreFirstMove = false;
+ else
+ ImplHighlightItem( rMEvt, false );
+ }
+}
+
+void MenuFloatingWindow::ImplScroll( bool bUp )
+{
+ KillActivePopup();
+ PaintImmediately();
+
+ if (!pMenu)
+ return;
+
+ Invalidate();
+
+ pMenu->ImplKillLayoutData();
+
+ if ( bScrollUp && bUp )
+ {
+ nFirstEntry = pMenu->ImplGetPrevVisible( nFirstEntry );
+ SAL_WARN_IF( nFirstEntry == ITEMPOS_INVALID, "vcl", "Scroll?!" );
+
+ // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686)
+ const auto pItemData = pMenu->GetItemList()->GetDataFromPos( nFirstEntry );
+ if ( pItemData )
+ {
+ tools::Long nScrollEntryHeight = pItemData->aSz.Height();
+
+ if ( !bScrollDown )
+ {
+ bScrollDown = true;
+ Invalidate();
+ }
+
+ if ( pMenu->ImplGetPrevVisible( nFirstEntry ) == ITEMPOS_INVALID )
+ {
+ bScrollUp = false;
+ Invalidate();
+ }
+
+ Scroll( 0, nScrollEntryHeight, ImplCalcClipRegion().GetBoundRect(), ScrollFlags::Clip );
+ }
+ }
+ else if ( bScrollDown && !bUp )
+ {
+ // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686)
+ const auto pItemData = pMenu->GetItemList()->GetDataFromPos( nFirstEntry );
+ if ( pItemData )
+ {
+ tools::Long nScrollEntryHeight = pItemData->aSz.Height();
+
+ nFirstEntry = pMenu->ImplGetNextVisible( nFirstEntry );
+ SAL_WARN_IF( nFirstEntry == ITEMPOS_INVALID, "vcl", "Scroll?!" );
+
+ if ( !bScrollUp )
+ {
+ bScrollUp = true;
+ Invalidate();
+ }
+
+ tools::Long nHeight = GetOutputSizePixel().Height();
+ sal_uInt16 nLastVisible;
+ static_cast<PopupMenu*>(pMenu.get())->ImplCalcVisEntries( nHeight, nFirstEntry, &nLastVisible );
+ if ( pMenu->ImplGetNextVisible( nLastVisible ) == ITEMPOS_INVALID )
+ {
+ bScrollDown = false;
+ Invalidate();
+ }
+
+ Scroll( 0, -nScrollEntryHeight, ImplCalcClipRegion().GetBoundRect(), ScrollFlags::Clip );
+ }
+ }
+
+ Invalidate();
+}
+
+void MenuFloatingWindow::ImplScroll( const Point& rMousePos )
+{
+ Size aOutSz = GetOutputSizePixel();
+
+ tools::Long nY = nScrollerHeight;
+ tools::Long nMouseY = rMousePos.Y();
+ tools::Long nDelta = 0;
+
+ if ( bScrollUp && ( nMouseY < nY ) )
+ {
+ ImplScroll( true );
+ nDelta = nY - nMouseY;
+ }
+ else if ( bScrollDown && ( nMouseY > ( aOutSz.Height() - nY ) ) )
+ {
+ ImplScroll( false );
+ nDelta = nMouseY - ( aOutSz.Height() - nY );
+ }
+
+ if ( !nDelta )
+ return;
+
+ aScrollTimer.Stop(); // if scrolled through MouseMove.
+ tools::Long nTimeout;
+ if ( nDelta < 3 )
+ nTimeout = 200;
+ else if ( nDelta < 5 )
+ nTimeout = 100;
+ else if ( nDelta < 8 )
+ nTimeout = 70;
+ else if ( nDelta < 12 )
+ nTimeout = 40;
+ else
+ nTimeout = 20;
+ aScrollTimer.SetTimeout( nTimeout );
+ aScrollTimer.Start();
+}
+void MenuFloatingWindow::ChangeHighlightItem( sal_uInt16 n, bool bStartPopupTimer )
+{
+ // #57934# if necessary, immediately close the active, as TH's backgroundstorage works.
+ // #65750# we prefer to refrain from the background storage of small lines.
+ // otherwise the menus are difficult to operate.
+ // MenuItemData* pNextData = pMenu->pItemList->GetDataFromPos( n );
+ // if ( pActivePopup && pNextData && ( pActivePopup != pNextData->pSubMenu ) )
+ // KillActivePopup();
+
+ aSubmenuCloseTimer.Stop();
+ if( ! pMenu )
+ return;
+
+ if ( nHighlightedItem != ITEMPOS_INVALID )
+ {
+ InvalidateItem(nHighlightedItem);
+ pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, nHighlightedItem );
+ }
+
+ nHighlightedItem = n;
+ SAL_WARN_IF( !pMenu->ImplIsVisible( nHighlightedItem ) && nHighlightedItem != ITEMPOS_INVALID, "vcl", "ChangeHighlightItem: Not visible!" );
+ if( nHighlightedItem != ITEMPOS_INVALID )
+ {
+ if (pMenu->pStartedFrom && !pMenu->pStartedFrom->IsMenuBar())
+ {
+ // #102461# make sure parent entry is highlighted as well
+ size_t i, nCount = pMenu->pStartedFrom->pItemList->size();
+ for(i = 0; i < nCount; i++)
+ {
+ MenuItemData* pData = pMenu->pStartedFrom->pItemList->GetDataFromPos( i );
+ if( pData && ( pData->pSubMenu == pMenu ) )
+ break;
+ }
+ if( i < nCount )
+ {
+ MenuFloatingWindow* pPWin = static_cast<MenuFloatingWindow*>(pMenu->pStartedFrom->ImplGetWindow());
+ if( pPWin && pPWin->nHighlightedItem != i )
+ {
+ pPWin->InvalidateItem(i);
+ pPWin->nHighlightedItem = i;
+ }
+ }
+ }
+ InvalidateItem(nHighlightedItem);
+ pMenu->ImplCallHighlight( nHighlightedItem );
+ }
+ else
+ {
+ pMenu->nSelectedId = 0;
+ pMenu->sSelectedIdent.clear();
+ }
+
+ if ( bStartPopupTimer )
+ {
+ // #102438# Menu items are not selectable
+ // If a menu item is selected by an AT-tool via the XAccessibleAction, XAccessibleValue
+ // or XAccessibleSelection interface, and the parent popup menus are not executed yet,
+ // the parent popup menus must be executed SYNCHRONOUSLY, before the menu item is selected.
+ if ( GetSettings().GetMouseSettings().GetMenuDelay() )
+ aHighlightChangedTimer.Start();
+ else
+ HighlightChanged( &aHighlightChangedTimer );
+ }
+}
+
+/// Calculate the initial vertical pixel offset of the first item.
+/// May be negative for scrolled windows.
+tools::Long MenuFloatingWindow::GetInitialItemY(tools::Long *pStartY) const
+{
+ tools::Long nStartY = ImplGetStartY();
+ if (pStartY)
+ *pStartY = nStartY;
+ return nScrollerHeight + nStartY +
+ ImplGetSVData()->maNWFData.mnMenuFormatBorderY;
+}
+
+/// Emit an Invalidate just for this item's area
+void MenuFloatingWindow::InvalidateItem(sal_uInt16 nPos)
+{
+ if (!pMenu)
+ return;
+
+ tools::Long nY = GetInitialItemY();
+ size_t nCount = pMenu->pItemList->size();
+ for (size_t n = 0; n < nCount; n++)
+ {
+ MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n );
+ tools::Long nHeight = pData->aSz.Height();
+ if (n == nPos)
+ {
+ Size aWidth( GetSizePixel() );
+ tools::Rectangle aRect(Point(0, nY), Size(aWidth.Width(), nHeight));
+ Invalidate( aRect );
+ }
+ nY += nHeight;
+ }
+}
+
+void MenuFloatingWindow::RenderHighlightItem(vcl::RenderContext& rRenderContext, sal_uInt16 nPos)
+{
+ if (!pMenu)
+ return;
+
+ Size aSz(GetOutputSizePixel());
+
+ tools::Long nX = 0;
+ tools::Long nStartY;
+ tools::Long nY = GetInitialItemY(&nStartY);
+
+ int nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX;
+
+ size_t nCount = pMenu->pItemList->size();
+ for (size_t n = 0; n < nCount; n++)
+ {
+ MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n );
+ if (n == nPos)
+ {
+ SAL_WARN_IF(!pMenu->ImplIsVisible(n), "vcl", "Highlight: Item not visible!");
+ if (pData->eType != MenuItemType::SEPARATOR)
+ {
+ bool bRestoreLineColor = false;
+ Color oldLineColor;
+ bool bDrawItemRect = true;
+
+ tools::Rectangle aItemRect(Point(nX + nOuterSpaceX, nY), Size(aSz.Width() - 2 * nOuterSpaceX, pData->aSz.Height()));
+ if (pData->nBits & MenuItemBits::POPUPSELECT)
+ {
+ tools::Long nFontHeight = GetTextHeight();
+ aItemRect.AdjustRight( -(nFontHeight + nFontHeight / 4) );
+ }
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
+ {
+ Size aPxSize(GetOutputSizePixel());
+ rRenderContext.Push(vcl::PushFlags::CLIPREGION);
+ rRenderContext.IntersectClipRegion(tools::Rectangle(Point(nX, nY), Size(aSz.Width(), pData->aSz.Height())));
+ tools::Rectangle aCtrlRect(Point(nX, 0), Size(aPxSize.Width()-nX, aPxSize.Height()));
+ MenupopupValue aVal(pMenu->nTextPos-GUTTERBORDER, aItemRect);
+ rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire,
+ aCtrlRect, ControlState::ENABLED, aVal, OUString());
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItem))
+ {
+ bDrawItemRect = false;
+ if (!rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::MenuItem, aItemRect,
+ ControlState::SELECTED | (pData->bEnabled
+ ? ControlState::ENABLED
+ : ControlState::NONE),
+ aVal, OUString()))
+ {
+ bDrawItemRect = true;
+ }
+ }
+ else
+ bDrawItemRect = true;
+ rRenderContext.Pop();
+ }
+ if (bDrawItemRect)
+ {
+ if (pData->bEnabled)
+ rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuHighlightColor());
+ else
+ {
+ rRenderContext.SetFillColor();
+ oldLineColor = rRenderContext.GetLineColor();
+ rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuHighlightColor());
+ bRestoreLineColor = true;
+ }
+
+ rRenderContext.DrawRect(aItemRect);
+ }
+ pMenu->ImplPaint(rRenderContext, GetOutputSizePixel(), nScrollerHeight, nStartY, pData, true/*bHighlight*/);
+ if (bRestoreLineColor)
+ rRenderContext.SetLineColor(oldLineColor);
+ }
+ return;
+ }
+
+ nY += pData->aSz.Height();
+ }
+}
+
+tools::Rectangle MenuFloatingWindow::ImplGetItemRect( sal_uInt16 nPos ) const
+{
+ if( ! pMenu )
+ return tools::Rectangle();
+
+ tools::Rectangle aRect;
+ Size aSz = GetOutputSizePixel();
+ tools::Long nStartY = ImplGetStartY();
+ tools::Long nY = nScrollerHeight+nStartY;
+
+ size_t nCount = pMenu->pItemList->size();
+ for ( size_t n = 0; n < nCount; n++ )
+ {
+ MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n );
+ if ( n == nPos )
+ {
+ SAL_WARN_IF( !pMenu->ImplIsVisible( n ), "vcl", "ImplGetItemRect: Item not visible!" );
+ if ( pData->eType != MenuItemType::SEPARATOR )
+ {
+ aRect = tools::Rectangle( Point( 0, nY ), Size( aSz.Width(), pData->aSz.Height() ) );
+ if ( pData->nBits & MenuItemBits::POPUPSELECT )
+ {
+ tools::Long nFontHeight = GetTextHeight();
+ aRect.AdjustRight( -(nFontHeight + nFontHeight/4) );
+ }
+ }
+ break;
+ }
+ nY += pData->aSz.Height();
+ }
+ return aRect;
+}
+
+void MenuFloatingWindow::ImplCursorUpDown( bool bUp, bool bHomeEnd )
+{
+ if( ! pMenu )
+ return;
+
+ const StyleSettings& rSettings = GetSettings().GetStyleSettings();
+
+ sal_uInt16 n = nHighlightedItem;
+ if ( n == ITEMPOS_INVALID )
+ {
+ if ( bUp )
+ n = 0;
+ else
+ n = pMenu->GetItemCount()-1;
+ }
+
+ sal_uInt16 nLoop = n;
+
+ if( bHomeEnd )
+ {
+ // absolute positioning
+ if( bUp )
+ {
+ n = pMenu->GetItemCount();
+ nLoop = n-1;
+ }
+ else
+ {
+ n = sal_uInt16(-1);
+ nLoop = n+1;
+ }
+ }
+
+ do
+ {
+ if ( bUp )
+ {
+ if ( n )
+ n--;
+ else
+ if ( !IsScrollMenu() || ( nHighlightedItem == ITEMPOS_INVALID ) )
+ n = pMenu->GetItemCount()-1;
+ else
+ break;
+ }
+ else
+ {
+ n++;
+ if ( n >= pMenu->GetItemCount() )
+ {
+ if ( !IsScrollMenu() || ( nHighlightedItem == ITEMPOS_INVALID ) )
+ n = 0;
+ else
+ break;
+ }
+ }
+
+ MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( n );
+ if ( ( pData->bEnabled || !rSettings.GetSkipDisabledInMenus() )
+ && ( pData->eType != MenuItemType::SEPARATOR ) && pMenu->ImplIsVisible( n ) && pMenu->ImplIsSelectable( n ) )
+ {
+ // Is selection in visible area?
+ if ( IsScrollMenu() )
+ {
+ ChangeHighlightItem( ITEMPOS_INVALID, false );
+
+ while ( n < nFirstEntry )
+ ImplScroll( true );
+
+ Size aOutSz = GetOutputSizePixel();
+ sal_uInt16 nLastVisible;
+ static_cast<PopupMenu*>(pMenu.get())->ImplCalcVisEntries( aOutSz.Height(), nFirstEntry, &nLastVisible );
+ while ( n > nLastVisible )
+ {
+ ImplScroll( false );
+ static_cast<PopupMenu*>(pMenu.get())->ImplCalcVisEntries( aOutSz.Height(), nFirstEntry, &nLastVisible );
+ }
+ }
+ ChangeHighlightItem( n, false );
+ break;
+ }
+ } while ( n != nLoop );
+}
+
+void MenuFloatingWindow::KeyInput( const KeyEvent& rKEvent )
+{
+ VclPtr<vcl::Window> xWindow = this;
+
+ sal_uInt16 nCode = rKEvent.GetKeyCode().GetCode();
+ bKeyInput = true;
+ switch ( nCode )
+ {
+ case KEY_UP:
+ case KEY_DOWN:
+ {
+ ImplCursorUpDown( nCode == KEY_UP );
+ }
+ break;
+ case KEY_END:
+ case KEY_HOME:
+ {
+ ImplCursorUpDown( nCode == KEY_END, true );
+ }
+ break;
+ case KEY_F6:
+ case KEY_ESCAPE:
+ {
+ // Ctrl-F6 acts like ESC here, the menu bar however will then put the focus in the document
+ if( nCode == KEY_F6 && !rKEvent.GetKeyCode().IsMod1() )
+ break;
+ if( pMenu )
+ {
+ if ( !pMenu->pStartedFrom )
+ {
+ StopExecute();
+ KillActivePopup();
+ }
+ else if (pMenu->pStartedFrom->IsMenuBar())
+ {
+ pMenu->pStartedFrom->MenuBarKeyInput(rKEvent);
+ }
+ else
+ {
+ StopExecute();
+ PopupMenu* pPopupMenu = static_cast<PopupMenu*>(pMenu->pStartedFrom.get());
+ MenuFloatingWindow* pFloat = pPopupMenu->ImplGetFloatingWindow();
+ pFloat->GrabFocus();
+ pFloat->KillActivePopup();
+ pPopupMenu->ImplCallHighlight(pFloat->nHighlightedItem);
+ }
+ }
+ }
+ break;
+ case KEY_LEFT:
+ {
+ if ( pMenu && pMenu->pStartedFrom )
+ {
+ StopExecute();
+ if (pMenu->pStartedFrom->IsMenuBar())
+ {
+ pMenu->pStartedFrom->MenuBarKeyInput(rKEvent);
+ }
+ else
+ {
+ MenuFloatingWindow* pFloat = static_cast<PopupMenu*>(pMenu->pStartedFrom.get())->ImplGetFloatingWindow();
+ pFloat->GrabFocus();
+ pFloat->KillActivePopup();
+ sal_uInt16 highlightItem = pFloat->GetHighlightedItem();
+ pFloat->ChangeHighlightItem(highlightItem, false);
+ }
+ }
+ }
+ break;
+ case KEY_RIGHT:
+ {
+ if( pMenu )
+ {
+ bool bDone = false;
+ if ( nHighlightedItem != ITEMPOS_INVALID )
+ {
+ MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( nHighlightedItem );
+ if ( pData && pData->pSubMenu )
+ {
+ HighlightChanged( nullptr );
+ bDone = true;
+ }
+ }
+ if ( !bDone )
+ {
+ Menu* pStart = pMenu->ImplGetStartMenu();
+ if (pStart && pStart->IsMenuBar())
+ {
+ // Forward...
+ pStart->ImplGetWindow()->KeyInput( rKEvent );
+ }
+ }
+ }
+ }
+ break;
+ case KEY_RETURN:
+ {
+ if( pMenu )
+ {
+ MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( nHighlightedItem );
+ if ( pData && pData->bEnabled )
+ {
+ if ( pData->pSubMenu )
+ HighlightChanged( nullptr );
+ else
+ EndExecute();
+ }
+ else
+ StopExecute();
+ }
+ }
+ break;
+ case KEY_MENU:
+ {
+ if( pMenu )
+ {
+ Menu* pStart = pMenu->ImplGetStartMenu();
+ if (pStart && pStart->IsMenuBar())
+ {
+ // Forward...
+ pStart->ImplGetWindow()->KeyInput( rKEvent );
+ }
+ }
+ }
+ break;
+ default:
+ {
+ sal_Unicode nCharCode = rKEvent.GetCharCode();
+ size_t nPos = 0;
+ size_t nDuplicates = 0;
+ MenuItemData* pData = (nCharCode && pMenu) ?
+ pMenu->GetItemList()->SearchItem(nCharCode, rKEvent.GetKeyCode(), nPos, nDuplicates, nHighlightedItem) : nullptr;
+ if (pData)
+ {
+ if ( pData->pSubMenu || nDuplicates > 1 )
+ {
+ ChangeHighlightItem( nPos, false );
+ HighlightChanged( nullptr );
+ }
+ else
+ {
+ nHighlightedItem = nPos;
+ EndExecute();
+ }
+ }
+ else
+ FloatingWindow::KeyInput( rKEvent );
+ }
+ }
+
+ // #105474# check if menu window was not destroyed
+ if ( !xWindow->isDisposed() )
+ {
+ bKeyInput = false;
+ }
+}
+
+void MenuFloatingWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle &rPaintRect)
+{
+ if (!pMenu)
+ return;
+
+ // Set the clip before the buffering starts: rPaintRect may be larger than the current clip,
+ // this way the buffer -> render context copy happens with this clip.
+ rRenderContext.Push(vcl::PushFlags::CLIPREGION);
+ rRenderContext.SetClipRegion(vcl::Region(rPaintRect));
+
+ // Make sure that all actual rendering happens in one go to avoid flicker.
+ vcl::BufferDevice pBuffer(this, rRenderContext);
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
+ {
+ pBuffer->SetClipRegion();
+ tools::Long nX = 0;
+ Size aPxSize(GetOutputSizePixel());
+ aPxSize.AdjustWidth( -nX );
+ ImplControlValue aVal(pMenu->nTextPos - GUTTERBORDER);
+ pBuffer->DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire,
+ tools::Rectangle(Point(nX, 0), aPxSize), ControlState::ENABLED,
+ aVal, OUString());
+ InitMenuClipRegion(*pBuffer);
+ }
+ if (IsScrollMenu())
+ {
+ ImplDrawScroller(*pBuffer, true);
+ ImplDrawScroller(*pBuffer, false);
+ }
+ pBuffer->SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuColor());
+ pMenu->ImplPaint(*pBuffer, GetOutputSizePixel(), nScrollerHeight, ImplGetStartY());
+ if (nHighlightedItem != ITEMPOS_INVALID)
+ RenderHighlightItem(*pBuffer, nHighlightedItem);
+
+ pBuffer.Dispose();
+ rRenderContext.Pop();
+}
+
+void MenuFloatingWindow::ImplDrawScroller(vcl::RenderContext& rRenderContext, bool bUp)
+{
+ if (!pMenu)
+ return;
+
+ rRenderContext.SetClipRegion();
+
+ Size aOutSz(GetOutputSizePixel());
+ tools::Long nY = bUp ? 0 : (aOutSz.Height() - nScrollerHeight);
+ tools::Long nX = 0;
+ tools::Rectangle aRect(Point(nX, nY), Size(aOutSz.Width() - nX, nScrollerHeight));
+
+ DecorationView aDecoView(&rRenderContext);
+ SymbolType eSymbol = bUp ? SymbolType::SPIN_UP : SymbolType::SPIN_DOWN;
+
+ DrawSymbolFlags nStyle = DrawSymbolFlags::NONE;
+ if ((bUp && !bScrollUp) || (!bUp && !bScrollDown))
+ nStyle |= DrawSymbolFlags::Disable;
+
+ aDecoView.DrawSymbol(aRect, eSymbol, rRenderContext.GetSettings().GetStyleSettings().GetButtonTextColor(), nStyle);
+
+ InitMenuClipRegion(rRenderContext);
+}
+
+void MenuFloatingWindow::RequestHelp( const HelpEvent& rHEvt )
+{
+ sal_uInt16 nId = nHighlightedItem;
+ Menu* pM = pMenu;
+ vcl::Window* pW = this;
+
+ // #102618# Get item rect before destroying the window in EndExecute() call
+ tools::Rectangle aHighlightRect( ImplGetItemRect( nHighlightedItem ) );
+
+ if ( rHEvt.GetMode() & HelpEventMode::CONTEXT )
+ {
+ nHighlightedItem = ITEMPOS_INVALID;
+ EndExecute();
+ pW = nullptr;
+ }
+
+ if( !ImplHandleHelpEvent( pW, pM, nId, rHEvt, aHighlightRect ) )
+ Window::RequestHelp( rHEvt );
+}
+
+void MenuFloatingWindow::StateChanged( StateChangedType nType )
+{
+ FloatingWindow::StateChanged( nType );
+
+ if ( ( nType == StateChangedType::ControlForeground ) || ( nType == StateChangedType::ControlBackground ) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+void MenuFloatingWindow::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ FloatingWindow::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+void MenuFloatingWindow::Command( const CommandEvent& rCEvt )
+{
+ if ( rCEvt.GetCommand() == CommandEventId::Wheel )
+ {
+ const CommandWheelData* pData = rCEvt.GetWheelData();
+ if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) )
+ {
+ ImplScroll( pData->GetDelta() > 0 );
+ MouseMove( MouseEvent( GetPointerPosPixel(), 0 ) );
+ }
+ }
+}
+
+css::uno::Reference<css::accessibility::XAccessible> MenuFloatingWindow::CreateAccessible()
+{
+ css::uno::Reference<css::accessibility::XAccessible> xAcc;
+
+ if (pMenu && !pMenu->pStartedFrom)
+ xAcc = pMenu->GetAccessible();
+
+ return xAcc;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menufloatingwindow.hxx b/vcl/source/window/menufloatingwindow.hxx
new file mode 100644
index 000000000..f26fb5037
--- /dev/null
+++ b/vcl/source/window/menufloatingwindow.hxx
@@ -0,0 +1,128 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include "menuwindow.hxx"
+
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/menu.hxx>
+
+#define EXTRASPACEY 2
+#define GUTTERBORDER 8
+
+/** Class that implements the actual window of the floating menu.
+*/
+class MenuFloatingWindow : public FloatingWindow, public MenuWindow
+{
+ friend void Menu::ImplFillLayoutData() const;
+ friend void Menu::dispose();
+
+private:
+ VclPtr<Menu> pMenu;
+ VclPtr<PopupMenu> pActivePopup;
+ Timer aHighlightChangedTimer;
+ Timer aSubmenuCloseTimer;
+ Timer aScrollTimer;
+ VclPtr<vcl::Window> xSaveFocusId;
+ sal_uInt16 nHighlightedItem; // highlighted/selected Item
+ sal_uInt16 nMBDownPos;
+ sal_uInt16 nScrollerHeight;
+ sal_uInt16 nFirstEntry;
+ sal_uInt16 nPosInParent;
+
+ bool bInExecute : 1;
+ bool bScrollMenu : 1;
+ bool bScrollUp : 1;
+ bool bScrollDown : 1;
+ bool bIgnoreFirstMove : 1;
+ bool bKeyInput : 1;
+
+ DECL_LINK( PopupEnd, FloatingWindow*, void );
+ DECL_LINK( HighlightChanged, Timer*, void );
+ DECL_LINK( SubmenuClose, Timer *, void );
+ DECL_LINK( AutoScroll, Timer *, void );
+ DECL_LINK( ShowHideListener, VclWindowEvent&, void );
+
+ virtual void StateChanged( StateChangedType nType ) override;
+ virtual void DataChanged( const DataChangedEvent& rDCEvt ) override;
+
+ void InitMenuClipRegion(vcl::RenderContext& rRenderContext);
+
+ void Start();
+ void End();
+
+protected:
+ vcl::Region ImplCalcClipRegion() const;
+ void ImplDrawScroller(vcl::RenderContext& rRenderContext, bool bUp);
+ using Window::ImplScroll;
+ void ImplScroll( const Point& rMousePos );
+ void ImplScroll( bool bUp );
+ void ImplCursorUpDown( bool bUp, bool bHomeEnd = false );
+ void ImplHighlightItem( const MouseEvent& rMEvt, bool bMBDown );
+ tools::Long ImplGetStartY() const;
+ tools::Rectangle ImplGetItemRect( sal_uInt16 nPos ) const;
+ void RenderHighlightItem( vcl::RenderContext& rRenderContext, sal_uInt16 nPos );
+ tools::Long GetInitialItemY( tools::Long *pOptStartY = nullptr ) const;
+ void InvalidateItem( sal_uInt16 nPos );
+
+public:
+ MenuFloatingWindow(Menu* pMenu, vcl::Window* pParent, WinBits nStyle);
+ virtual ~MenuFloatingWindow() override;
+
+ virtual void dispose() override;
+ void doShutdown();
+
+ virtual void MouseMove(const MouseEvent& rMEvt) override;
+ virtual void MouseButtonDown(const MouseEvent& rMEvt) override;
+ virtual void MouseButtonUp(const MouseEvent& rMEvt) override;
+ virtual void KeyInput(const KeyEvent& rKEvent) override;
+ virtual void Command(const CommandEvent& rCEvt) override;
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override;
+ virtual void RequestHelp( const HelpEvent& rHEvt ) override;
+ virtual void Resize() override;
+
+ virtual void ApplySettings(vcl::RenderContext& rRenderContext) override;
+
+ void SetFocusId( const VclPtr<vcl::Window>& xId ) { xSaveFocusId = xId; }
+ const VclPtr<vcl::Window>& GetFocusId() const { return xSaveFocusId; }
+
+ void EnableScrollMenu( bool b );
+ bool IsScrollMenu() const { return bScrollMenu; }
+ sal_uInt16 GetScrollerHeight() const { return nScrollerHeight; }
+
+ void Execute();
+ void StopExecute();
+ void EndExecute();
+ void EndExecute( sal_uInt16 nSelectId );
+
+ PopupMenu* GetActivePopup() const { return pActivePopup; }
+ void KillActivePopup( PopupMenu* pThisOnly = nullptr );
+
+ void ChangeHighlightItem(sal_uInt16 n, bool bStartPopupTimer);
+ sal_uInt16 GetHighlightedItem() const { return nHighlightedItem; }
+
+ void SetPosInParent( sal_uInt16 nPos ) { nPosInParent = nPos; }
+
+ bool MenuInHierarchyHasFocus() const;
+
+ virtual css::uno::Reference<css::accessibility::XAccessible> CreateAccessible() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menuitemlist.cxx b/vcl/source/window/menuitemlist.cxx
new file mode 100644
index 000000000..173a6204e
--- /dev/null
+++ b/vcl/source/window/menuitemlist.cxx
@@ -0,0 +1,314 @@
+/* -*- 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 "menuitemlist.hxx"
+
+#include <salframe.hxx>
+#include <salinst.hxx>
+#include <salmenu.hxx>
+#include <svdata.hxx>
+#include <vcl/i18nhelp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/vcllayout.hxx>
+#include <vcl/window.hxx>
+
+using namespace css;
+using namespace vcl;
+
+MenuItemData::~MenuItemData()
+{
+ if (aUserValueReleaseFunc)
+ aUserValueReleaseFunc(nUserValue);
+ pSalMenuItem.reset();
+ pSubMenu.disposeAndClear();
+}
+
+SalLayoutGlyphs* MenuItemData::GetTextGlyphs(const OutputDevice* pOutputDevice)
+{
+ if (aTextGlyphs.IsValid())
+ // Use pre-calculated result.
+ return &aTextGlyphs;
+
+ OUString aNonMnemonicString = OutputDevice::GetNonMnemonicString(aText);
+ std::unique_ptr<SalLayout> pLayout
+ = pOutputDevice->ImplLayout(aNonMnemonicString, 0, aNonMnemonicString.getLength(),
+ Point(0, 0), 0, {}, SalLayoutFlags::GlyphItemsOnly);
+ if (!pLayout)
+ return nullptr;
+
+ // Remember the calculation result.
+ aTextGlyphs = pLayout->GetGlyphs();
+
+ return &aTextGlyphs;
+}
+
+MenuItemList::~MenuItemList()
+{
+}
+
+MenuItemData* MenuItemList::Insert(
+ sal_uInt16 nId,
+ MenuItemType eType,
+ MenuItemBits nBits,
+ const OUString& rStr,
+ Menu* pMenu,
+ size_t nPos,
+ const OString &rIdent
+)
+{
+ MenuItemData* pData = new MenuItemData( rStr );
+ pData->nId = nId;
+ pData->sIdent = rIdent;
+ pData->eType = eType;
+ pData->nBits = nBits;
+ pData->pSubMenu = nullptr;
+ pData->nUserValue = nullptr;
+ pData->bChecked = false;
+ pData->bEnabled = true;
+ pData->bVisible = true;
+ pData->bIsTemporary = false;
+
+ SalItemParams aSalMIData;
+ aSalMIData.nId = nId;
+ aSalMIData.eType = eType;
+ aSalMIData.nBits = nBits;
+ aSalMIData.pMenu = pMenu;
+ aSalMIData.aText = rStr;
+
+ // Native-support: returns NULL if not supported
+ pData->pSalMenuItem = ImplGetSVData()->mpDefInst->CreateMenuItem( aSalMIData );
+
+ if( nPos < maItemList.size() ) {
+ maItemList.insert( maItemList.begin() + nPos, std::unique_ptr<MenuItemData>(pData) );
+ } else {
+ maItemList.emplace_back( pData );
+ }
+ return pData;
+}
+
+void MenuItemList::InsertSeparator(const OString &rIdent, size_t nPos)
+{
+ MenuItemData* pData = new MenuItemData;
+ pData->nId = 0;
+ pData->sIdent = rIdent;
+ pData->eType = MenuItemType::SEPARATOR;
+ pData->nBits = MenuItemBits::NONE;
+ pData->pSubMenu = nullptr;
+ pData->nUserValue = nullptr;
+ pData->bChecked = false;
+ pData->bEnabled = true;
+ pData->bVisible = true;
+ pData->bIsTemporary = false;
+
+ SalItemParams aSalMIData;
+ aSalMIData.nId = 0;
+ aSalMIData.eType = MenuItemType::SEPARATOR;
+ aSalMIData.nBits = MenuItemBits::NONE;
+ aSalMIData.pMenu = nullptr;
+ aSalMIData.aText.clear();
+ aSalMIData.aImage = Image();
+
+ // Native-support: returns NULL if not supported
+ pData->pSalMenuItem = ImplGetSVData()->mpDefInst->CreateMenuItem( aSalMIData );
+
+ if( nPos < maItemList.size() ) {
+ maItemList.insert( maItemList.begin() + nPos, std::unique_ptr<MenuItemData>(pData) );
+ } else {
+ maItemList.emplace_back( pData );
+ }
+}
+
+void MenuItemList::Remove( size_t nPos )
+{
+ if( nPos < maItemList.size() )
+ {
+ maItemList.erase( maItemList.begin() + nPos );
+ }
+}
+
+void MenuItemList::Clear()
+{
+ maItemList.clear();
+}
+
+MenuItemData* MenuItemList::GetData( sal_uInt16 nSVId, size_t& rPos ) const
+{
+ for( size_t i = 0, n = maItemList.size(); i < n; ++i )
+ {
+ if ( maItemList[ i ]->nId == nSVId )
+ {
+ rPos = i;
+ return maItemList[ i ].get();
+ }
+ }
+ return nullptr;
+}
+
+MenuItemData* MenuItemList::SearchItem(
+ sal_Unicode cSelectChar,
+ KeyCode aKeyCode,
+ size_t& rPos,
+ size_t& nDuplicates,
+ size_t nCurrentPos
+) const
+{
+ const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
+
+ size_t nListCount = maItemList.size();
+
+ // try character code first
+ nDuplicates = GetItemCount( cSelectChar ); // return number of duplicates
+ if( nDuplicates )
+ {
+ MenuItemData* pFirstMatch = nullptr;
+ size_t nFirstPos(0);
+ for ( rPos = 0; rPos < nListCount; rPos++)
+ {
+ MenuItemData* pData = maItemList[ rPos ].get();
+ if ( pData->bEnabled && rI18nHelper.MatchMnemonic( pData->aText, cSelectChar ) )
+ {
+ if (nDuplicates == 1)
+ return pData;
+ if (rPos > nCurrentPos)
+ return pData; // select next entry with the same mnemonic
+ if (!pFirstMatch) // stash the first match for use if nothing follows nCurrentPos
+ {
+ pFirstMatch = pData;
+ nFirstPos = rPos;
+ }
+ }
+ }
+ if (pFirstMatch)
+ {
+ rPos = nFirstPos;
+ return pFirstMatch;
+ }
+ }
+
+ // nothing found, try keycode instead
+ nDuplicates = GetItemCount( aKeyCode ); // return number of duplicates
+
+ if( nDuplicates )
+ {
+ char ascii = 0;
+ if( aKeyCode.GetCode() >= KEY_A && aKeyCode.GetCode() <= KEY_Z )
+ ascii = sal::static_int_cast<char>('A' + (aKeyCode.GetCode() - KEY_A));
+
+ MenuItemData* pFirstMatch = nullptr;
+ size_t nFirstPos(0);
+ for ( rPos = 0; rPos < nListCount; rPos++)
+ {
+ MenuItemData* pData = maItemList[ rPos ].get();
+ if ( pData->bEnabled )
+ {
+ sal_Int32 n = pData->aText.indexOf('~');
+ if ( n != -1 )
+ {
+ KeyCode nKeyCode;
+ sal_Unicode nUnicode = pData->aText[n+1];
+ vcl::Window* pDefWindow = ImplGetDefaultWindow();
+ if( ( pDefWindow
+ && pDefWindow->ImplGetFrame()->MapUnicodeToKeyCode( nUnicode,
+ Application::GetSettings().GetUILanguageTag().getLanguageType(), nKeyCode )
+ && aKeyCode.GetCode() == nKeyCode.GetCode()
+ )
+ || ( ascii
+ && rI18nHelper.MatchMnemonic( pData->aText, ascii )
+ )
+ )
+ {
+ if (nDuplicates == 1)
+ return pData;
+ if (rPos > nCurrentPos)
+ return pData; // select next entry with the same mnemonic
+ if (!pFirstMatch) // stash the first match for use if nothing follows nCurrentPos
+ {
+ pFirstMatch = pData;
+ nFirstPos = rPos;
+ }
+ }
+ }
+ }
+ }
+ if (pFirstMatch)
+ {
+ rPos = nFirstPos;
+ return pFirstMatch;
+ }
+ }
+
+ return nullptr;
+}
+
+size_t MenuItemList::GetItemCount( sal_Unicode cSelectChar ) const
+{
+ // returns number of entries with same mnemonic
+ const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
+
+ size_t nItems = 0;
+ for ( size_t nPos = maItemList.size(); nPos; )
+ {
+ MenuItemData* pData = maItemList[ --nPos ].get();
+ if ( pData->bEnabled && rI18nHelper.MatchMnemonic( pData->aText, cSelectChar ) )
+ nItems++;
+ }
+
+ return nItems;
+}
+
+size_t MenuItemList::GetItemCount( KeyCode aKeyCode ) const
+{
+ // returns number of entries with same mnemonic
+ // uses key codes instead of character codes
+ const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
+ char ascii = 0;
+ if( aKeyCode.GetCode() >= KEY_A && aKeyCode.GetCode() <= KEY_Z )
+ ascii = sal::static_int_cast<char>('A' + (aKeyCode.GetCode() - KEY_A));
+
+ size_t nItems = 0;
+ for ( size_t nPos = maItemList.size(); nPos; )
+ {
+ MenuItemData* pData = maItemList[ --nPos ].get();
+ if ( pData->bEnabled )
+ {
+ sal_Int32 n = pData->aText.indexOf('~');
+ if (n != -1)
+ {
+ KeyCode nKeyCode;
+ // if MapUnicodeToKeyCode fails or is unsupported we try the pure ascii mapping of the keycodes
+ // so we have working shortcuts when ascii mnemonics are used
+ vcl::Window* pDefWindow = ImplGetDefaultWindow();
+ if( ( pDefWindow
+ && pDefWindow->ImplGetFrame()->MapUnicodeToKeyCode( pData->aText[n+1],
+ Application::GetSettings().GetUILanguageTag().getLanguageType(), nKeyCode )
+ && aKeyCode.GetCode() == nKeyCode.GetCode()
+ )
+ || ( ascii
+ && rI18nHelper.MatchMnemonic( pData->aText, ascii )
+ )
+ )
+ nItems++;
+ }
+ }
+ }
+
+ return nItems;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menuitemlist.hxx b/vcl/source/window/menuitemlist.hxx
new file mode 100644
index 000000000..25af6fc6d
--- /dev/null
+++ b/vcl/source/window/menuitemlist.hxx
@@ -0,0 +1,150 @@
+/* -*- 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 <vcl/vclenum.hxx>
+#include <vcl/glyphitem.hxx>
+#include <vcl/image.hxx>
+#include <vcl/keycod.hxx>
+#include <vcl/menu.hxx>
+#include <salmenu.hxx>
+
+#include <memory>
+#include <vector>
+
+class SalMenuItem;
+
+struct MenuItemData
+{
+ sal_uInt16 nId; // SV Id
+ MenuItemType eType; // MenuItem-Type
+ MenuItemBits nBits; // MenuItem-Bits
+ VclPtr<Menu> pSubMenu; // Pointer to SubMenu
+ OUString aText; // Menu-Text
+ SalLayoutGlyphs aTextGlyphs; ///< Text layout of aText.
+ OUString aHelpText; // Help-String
+ OUString aTipHelpText; // TipHelp-String (eg, expanded filenames)
+ OUString aCommandStr; // CommandString
+ OUString aHelpCommandStr; // Help command string (to reference external help)
+ OString sIdent;
+ OString aHelpId; // Help-Id
+ void* nUserValue; // User value
+ MenuUserDataReleaseFunction aUserValueReleaseFunc; // called when MenuItemData is destroyed
+ Image aImage; // Image
+ vcl::KeyCode aAccelKey; // Accelerator-Key
+ bool bChecked; // Checked
+ bool bEnabled; // Enabled
+ bool bVisible; // Visible (note: this flag will not override MenuFlags::HideDisabledEntries when true)
+ bool bIsTemporary; // Temporary inserted ('No selection possible')
+ bool bHiddenOnGUI;
+ Size aSz; // only temporarily valid
+ OUString aAccessibleName; // accessible name
+ OUString aAccessibleDescription; // accessible description
+
+ std::unique_ptr<SalMenuItem> pSalMenuItem; // access to native menu
+
+ MenuItemData()
+ : nId(0)
+ , eType(MenuItemType::DONTKNOW)
+ , nBits(MenuItemBits::NONE)
+ , pSubMenu(nullptr)
+ , nUserValue(nullptr)
+ , aUserValueReleaseFunc(nullptr)
+ , bChecked(false)
+ , bEnabled(false)
+ , bVisible(false)
+ , bIsTemporary(false)
+ , bHiddenOnGUI(false)
+ {
+ }
+ MenuItemData( const OUString& rStr )
+ : nId(0)
+ , eType(MenuItemType::DONTKNOW)
+ , nBits(MenuItemBits::NONE)
+ , pSubMenu(nullptr)
+ , aText(rStr)
+ , nUserValue(nullptr)
+ , aUserValueReleaseFunc(nullptr)
+ , aImage()
+ , bChecked(false)
+ , bEnabled(false)
+ , bVisible(false)
+ , bIsTemporary(false)
+ , bHiddenOnGUI(false)
+ {
+ }
+ ~MenuItemData();
+
+ /// Computes aText's text layout (glyphs), cached in aTextGlyphs.
+ SalLayoutGlyphs* GetTextGlyphs(const OutputDevice* pOutputDevice);
+
+ bool HasCheck() const
+ {
+ return bChecked || ( nBits & ( MenuItemBits::RADIOCHECK | MenuItemBits::CHECKABLE | MenuItemBits::AUTOCHECK ) );
+ }
+};
+
+class MenuItemList
+{
+private:
+ ::std::vector< std::unique_ptr<MenuItemData> > maItemList;
+
+public:
+ MenuItemList() {}
+ ~MenuItemList();
+
+ MenuItemData* Insert(
+ sal_uInt16 nId,
+ MenuItemType eType,
+ MenuItemBits nBits,
+ const OUString& rStr,
+ Menu* pMenu,
+ size_t nPos,
+ const OString &rIdent
+ );
+ void InsertSeparator(const OString &rIdent, size_t nPos);
+ void Remove( size_t nPos );
+ void Clear();
+
+ MenuItemData* GetData( sal_uInt16 nSVId, size_t& rPos ) const;
+ MenuItemData* GetData( sal_uInt16 nSVId ) const
+ {
+ size_t nTemp;
+ return GetData( nSVId, nTemp );
+ }
+ MenuItemData* GetDataFromPos( size_t nPos ) const
+ {
+ return ( nPos < maItemList.size() ) ? maItemList[ nPos ].get() : nullptr;
+ }
+
+ MenuItemData* SearchItem(
+ sal_Unicode cSelectChar,
+ vcl::KeyCode aKeyCode,
+ size_t& rPos,
+ size_t& nDuplicates,
+ size_t nCurrentPos
+ ) const;
+ size_t GetItemCount( sal_Unicode cSelectChar ) const;
+ size_t GetItemCount( vcl::KeyCode aKeyCode ) const;
+ size_t size() const
+ {
+ return maItemList.size();
+ }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menuwindow.cxx b/vcl/source/window/menuwindow.cxx
new file mode 100644
index 000000000..a3412e077
--- /dev/null
+++ b/vcl/source/window/menuwindow.cxx
@@ -0,0 +1,111 @@
+/* -*- 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 "menuwindow.hxx"
+#include "menuitemlist.hxx"
+
+#include <vcl/help.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+
+static sal_uLong ImplChangeTipTimeout( sal_uLong nTimeout, vcl::Window *pWindow )
+{
+ AllSettings aAllSettings( pWindow->GetSettings() );
+ HelpSettings aHelpSettings( aAllSettings.GetHelpSettings() );
+ sal_uLong nRet = aHelpSettings.GetTipTimeout();
+ aHelpSettings.SetTipTimeout( nTimeout );
+ aAllSettings.SetHelpSettings( aHelpSettings );
+ pWindow->GetOutDev()->SetSettings( aAllSettings );
+ return nRet;
+}
+
+bool MenuWindow::ImplHandleHelpEvent(vcl::Window* pMenuWindow, Menu const * pMenu, sal_uInt16 nHighlightedItem,
+ const HelpEvent& rHEvt, const tools::Rectangle &rHighlightRect)
+{
+ if( ! pMenu )
+ return false;
+
+ bool bDone = false;
+ sal_uInt16 nId = 0;
+
+ if ( nHighlightedItem != ITEMPOS_INVALID )
+ {
+ MenuItemData* pItemData = pMenu->GetItemList()->GetDataFromPos( nHighlightedItem );
+ if ( pItemData )
+ nId = pItemData->nId;
+ }
+
+ if ( ( rHEvt.GetMode() & HelpEventMode::BALLOON ) && pMenuWindow )
+ {
+ Point aPos;
+ if( rHEvt.KeyboardActivated() )
+ aPos = rHighlightRect.Center();
+ else
+ aPos = rHEvt.GetMousePosPixel();
+
+ tools::Rectangle aRect( aPos, Size() );
+ if (!pMenu->GetHelpText(nId).isEmpty())
+ Help::ShowBalloon( pMenuWindow, aPos, aRect, pMenu->GetHelpText( nId ) );
+ else
+ {
+ // give user a chance to read the full filename
+ sal_uLong oldTimeout=ImplChangeTipTimeout( 60000, pMenuWindow );
+ // call always, even when strlen==0 to correctly remove tip
+ Help::ShowQuickHelp( pMenuWindow, aRect, pMenu->GetTipHelpText( nId ) );
+ ImplChangeTipTimeout( oldTimeout, pMenuWindow );
+ }
+ bDone = true;
+ }
+ else if ( ( rHEvt.GetMode() &HelpEventMode::QUICK ) && pMenuWindow )
+ {
+ Point aPos = rHEvt.GetMousePosPixel();
+ tools::Rectangle aRect( aPos, Size() );
+ // give user a chance to read the full filename
+ sal_uLong oldTimeout=ImplChangeTipTimeout( 60000, pMenuWindow );
+ // call always, even when strlen==0 to correctly remove tip
+ Help::ShowQuickHelp( pMenuWindow, aRect, pMenu->GetTipHelpText( nId ) );
+ ImplChangeTipTimeout( oldTimeout, pMenuWindow );
+ bDone = true;
+ }
+ else if ( rHEvt.GetMode() & HelpEventMode::CONTEXT )
+ {
+ // is help in the application selected
+ Help* pHelp = Application::GetHelp();
+ if ( pHelp )
+ {
+ // is an id available, then call help with the id, otherwise
+ // use help-index
+ OUString aCommand = pMenu->GetItemCommand( nId );
+ OString aHelpId( pMenu->GetHelpId( nId ) );
+ if( aHelpId.isEmpty() )
+ aHelpId = OOO_HELP_INDEX;
+
+ if ( !aCommand.isEmpty() )
+ pHelp->Start(aCommand);
+ else
+ pHelp->Start(OStringToOUString(aHelpId, RTL_TEXTENCODING_UTF8));
+ }
+ bDone = true;
+ }
+ return bDone;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/menuwindow.hxx b/vcl/source/window/menuwindow.hxx
new file mode 100644
index 000000000..df0606b88
--- /dev/null
+++ b/vcl/source/window/menuwindow.hxx
@@ -0,0 +1,56 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <sal/types.h>
+#include <vcl/event.hxx>
+
+class HelpEvent;
+class Image;
+class Menu;
+class MenuBar;
+namespace tools { class Rectangle; }
+namespace vcl { class Window; }
+
+/** Common ancestor for MenuFloatingWindow and MenuBarWindow.
+
+The menu can be a floating window, or a menu bar. Even though this has
+'Window' in the name, it is not derived from the VCL's Window class, as the
+MenuFloatingWindow's or MenuBarWindow's already are VCL Windows.
+
+TODO: move here stuff that was a mentioned previously when there was no
+common class for MenuFloatingWindow and MenuBarWindow:
+
+// a basic class for both (due to pActivePopup, Timer,...) would be nice,
+// but a container class should have been created then, as they
+// would be derived from different windows
+// In most functions we would have to create exceptions for
+// menubar, popupmenu, hence we made two classes
+
+*/
+class MenuWindow
+{
+protected:
+ /// Show the appropriate help tooltip.
+ static bool ImplHandleHelpEvent(vcl::Window* pMenuWindow, Menu const * pMenu, sal_uInt16 nHighlightedItem,
+ const HelpEvent& rHEvt, const tools::Rectangle &rHighlightRect);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/mnemonic.cxx b/vcl/source/window/mnemonic.cxx
new file mode 100644
index 000000000..6e4b155c2
--- /dev/null
+++ b/vcl/source/window/mnemonic.cxx
@@ -0,0 +1,348 @@
+/* -*- 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 <string.h>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/mnemonic.hxx>
+
+#include <vcl/unohelp.hxx>
+#include <com/sun/star/i18n/XCharacterClassification.hpp>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <rtl/character.hxx>
+#include <sal/log.hxx>
+
+using namespace ::com::sun::star;
+
+MnemonicGenerator::MnemonicGenerator(sal_Unicode cMnemonic)
+ : m_cMnemonic(cMnemonic)
+{
+ memset( maMnemonics, 1, sizeof( maMnemonics ) );
+}
+
+sal_uInt16 MnemonicGenerator::ImplGetMnemonicIndex( sal_Unicode c )
+{
+ static sal_uInt16 const aImplMnemonicRangeTab[MNEMONIC_RANGES*2] =
+ {
+ MNEMONIC_RANGE_1_START, MNEMONIC_RANGE_1_END,
+ MNEMONIC_RANGE_2_START, MNEMONIC_RANGE_2_END,
+ MNEMONIC_RANGE_3_START, MNEMONIC_RANGE_3_END,
+ MNEMONIC_RANGE_4_START, MNEMONIC_RANGE_4_END
+ };
+
+ sal_uInt16 nMnemonicIndex = 0;
+ for ( sal_uInt16 i = 0; i < MNEMONIC_RANGES; i++ )
+ {
+ if ( (c >= aImplMnemonicRangeTab[i*2]) &&
+ (c <= aImplMnemonicRangeTab[i*2+1]) )
+ return nMnemonicIndex+c-aImplMnemonicRangeTab[i*2];
+
+ nMnemonicIndex += aImplMnemonicRangeTab[i*2+1]-aImplMnemonicRangeTab[i*2];
+ }
+
+ return MNEMONIC_INDEX_NOTFOUND;
+}
+
+sal_Unicode MnemonicGenerator::ImplFindMnemonic( const OUString& rKey )
+{
+ sal_Int32 nIndex = 0;
+ while ( (nIndex = rKey.indexOf( m_cMnemonic, nIndex )) != -1 )
+ {
+ if (nIndex == rKey.getLength() - 1) {
+ SAL_WARN("vcl", "key \"" << rKey << "\" ends in lone mnemonic prefix");
+ break;
+ }
+ sal_Unicode cMnemonic = rKey[ nIndex+1 ];
+ if ( cMnemonic != m_cMnemonic )
+ return cMnemonic;
+ nIndex += 2;
+ }
+
+ return 0;
+}
+
+void MnemonicGenerator::RegisterMnemonic( const OUString& rKey )
+{
+ uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass();
+
+ // Don't crash even when we don't have access to i18n service
+ if ( !xCharClass.is() )
+ return;
+
+ OUString aKey = xCharClass->toLower(rKey, 0, rKey.getLength(), css::lang::Locale());
+
+ // If we find a Mnemonic, set the flag. In other case count the
+ // characters, because we need this to set most as possible
+ // Mnemonics
+ sal_Unicode cMnemonic = ImplFindMnemonic( aKey );
+ if ( cMnemonic )
+ {
+ sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( cMnemonic );
+ if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
+ maMnemonics[nMnemonicIndex] = 0;
+ }
+ else
+ {
+ sal_Int32 nIndex = 0;
+ sal_Int32 nLen = aKey.getLength();
+ while ( nIndex < nLen )
+ {
+ sal_Unicode c = aKey[ nIndex ];
+
+ sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( c );
+ if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
+ {
+ if ( maMnemonics[nMnemonicIndex] && (maMnemonics[nMnemonicIndex] < 0xFF) )
+ maMnemonics[nMnemonicIndex]++;
+ }
+
+ nIndex++;
+ }
+ }
+}
+
+OUString MnemonicGenerator::CreateMnemonic( const OUString& _rKey )
+{
+ if ( _rKey.isEmpty() || ImplFindMnemonic( _rKey ) )
+ return _rKey;
+
+ uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass();
+
+ // Don't crash even when we don't have access to i18n service
+ if ( !xCharClass.is() )
+ return _rKey;
+
+ OUString aKey = xCharClass->toLower(_rKey, 0, _rKey.getLength(), css::lang::Locale());
+
+ bool bChanged = false;
+ sal_Int32 nLen = aKey.getLength();
+
+ bool bCJK = MsLangId::isCJK(Application::GetSettings().GetUILanguageTag().getLanguageType());
+
+ // #107889# in CJK versions ALL strings (even those that contain latin characters)
+ // will get mnemonics in the form: xyz (M)
+ // thus steps 1) and 2) are skipped for CJK locales
+
+ // #110720#, avoid CJK-style mnemonics for latin-only strings that do not contain useful mnemonic chars
+ if( bCJK )
+ {
+ bool bLatinOnly = true;
+ bool bMnemonicIndexFound = false;
+ sal_Unicode c;
+ sal_Int32 nIndex;
+
+ for( nIndex=0; nIndex < nLen; nIndex++ )
+ {
+ c = aKey[ nIndex ];
+ if ( ((c >= 0x3000) && (c <= 0xD7FF)) || // cjk
+ ((c >= 0xFF61) && (c <= 0xFFDC)) ) // halfwidth forms
+ {
+ bLatinOnly = false;
+ break;
+ }
+ if( ImplGetMnemonicIndex( c ) != MNEMONIC_INDEX_NOTFOUND )
+ bMnemonicIndexFound = true;
+ }
+ if( bLatinOnly && !bMnemonicIndexFound )
+ return _rKey;
+ }
+
+ OUString rKey(_rKey);
+ int nCJK = 0;
+ sal_uInt16 nMnemonicIndex;
+ sal_Unicode c;
+ sal_Int32 nIndex = 0;
+ if( !bCJK )
+ {
+ // 1) first try the first character of a word
+ do
+ {
+ c = aKey[ nIndex ];
+
+ if ( nCJK != 2 )
+ {
+ if ( ((c >= 0x3000) && (c <= 0xD7FF)) || // cjk
+ ((c >= 0xFF61) && (c <= 0xFFDC)) ) // halfwidth forms
+ nCJK = 1;
+ else if ( ((c >= 0x0030) && (c <= 0x0039)) || // digits
+ ((c >= 0x0041) && (c <= 0x005A)) || // latin capitals
+ ((c >= 0x0061) && (c <= 0x007A)) || // latin small
+ ((c >= 0x0370) && (c <= 0x037F)) || // greek numeral signs
+ ((c >= 0x0400) && (c <= 0x04FF)) ) // cyrillic
+ nCJK = 2;
+ }
+
+ nMnemonicIndex = ImplGetMnemonicIndex( c );
+ if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
+ {
+ if ( maMnemonics[nMnemonicIndex] )
+ {
+ maMnemonics[nMnemonicIndex] = 0;
+ rKey = rKey.replaceAt( nIndex, 0, rtl::OUStringChar(m_cMnemonic) );
+ bChanged = true;
+ break;
+ }
+ }
+
+ // Search for next word
+ nIndex++;
+ while ( nIndex < nLen )
+ {
+ c = aKey[ nIndex ];
+ if ( c == ' ' )
+ break;
+ nIndex++;
+ }
+ nIndex++;
+ }
+ while ( nIndex < nLen );
+
+ // 2) search for a unique/uncommon character
+ if ( !bChanged )
+ {
+ sal_uInt16 nBestCount = 0xFFFF;
+ sal_uInt16 nBestMnemonicIndex = 0;
+ sal_Int32 nBestIndex = 0;
+ nIndex = 0;
+ do
+ {
+ c = aKey[ nIndex ];
+ nMnemonicIndex = ImplGetMnemonicIndex( c );
+ if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
+ {
+ if ( maMnemonics[nMnemonicIndex] )
+ {
+ if ( maMnemonics[nMnemonicIndex] < nBestCount )
+ {
+ nBestCount = maMnemonics[nMnemonicIndex];
+ nBestIndex = nIndex;
+ nBestMnemonicIndex = nMnemonicIndex;
+ if ( nBestCount == 2 )
+ break;
+ }
+ }
+ }
+
+ nIndex++;
+ }
+ while ( nIndex < nLen );
+
+ if ( nBestCount != 0xFFFF )
+ {
+ maMnemonics[nBestMnemonicIndex] = 0;
+ rKey = rKey.replaceAt( nBestIndex, 0, rtl::OUStringChar(m_cMnemonic) );
+ bChanged = true;
+ }
+ }
+ }
+ else
+ nCJK = 1;
+
+ // 3) Add English Mnemonic for CJK Text
+ if ( !bChanged && (nCJK == 1) && !rKey.isEmpty() )
+ {
+ // Append Ascii Mnemonic
+ for ( c = MNEMONIC_RANGE_2_START; c <= MNEMONIC_RANGE_2_END; c++ )
+ {
+ nMnemonicIndex = ImplGetMnemonicIndex(c);
+ if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
+ {
+ if ( maMnemonics[nMnemonicIndex] )
+ {
+ maMnemonics[nMnemonicIndex] = 0;
+ OUString aStr = OUString::Concat("(") + OUStringChar(m_cMnemonic) +
+ OUStringChar(sal_Unicode(rtl::toAsciiUpperCase(c))) +
+ ")";
+ nIndex = rKey.getLength();
+ if( nIndex >= 2 )
+ {
+ if ( ( rKey[nIndex-2] == '>' && rKey[nIndex-1] == '>' ) ||
+ ( rKey[nIndex-2] == 0xFF1E && rKey[nIndex-1] == 0xFF1E ) )
+ nIndex -= 2;
+ }
+ if( nIndex >= 3 )
+ {
+ if ( ( rKey[nIndex-3] == '.' && rKey[nIndex-2] == '.' && rKey[nIndex-1] == '.' ) ||
+ ( rKey[nIndex-3] == 0xFF0E && rKey[nIndex-2] == 0xFF0E && rKey[nIndex-1] == 0xFF0E ) )
+ nIndex -= 3;
+ }
+ if( nIndex >= 1)
+ {
+ sal_Unicode cLastChar = rKey[ nIndex-1 ];
+ if ( (cLastChar == ':') || (cLastChar == 0xFF1A) ||
+ (cLastChar == '.') || (cLastChar == 0xFF0E) ||
+ (cLastChar == '?') || (cLastChar == 0xFF1F) ||
+ (cLastChar == ' ') )
+ nIndex--;
+ }
+ rKey = rKey.replaceAt( nIndex, 0, aStr );
+ break;
+ }
+ }
+ }
+ }
+
+ return rKey;
+}
+
+uno::Reference< i18n::XCharacterClassification > const & MnemonicGenerator::GetCharClass()
+{
+ if ( !mxCharClass.is() )
+ mxCharClass = vcl::unohelper::CreateCharacterClassification();
+ return mxCharClass;
+}
+
+OUString MnemonicGenerator::EraseAllMnemonicChars( const OUString& rStr )
+{
+ OUString aStr = rStr;
+ sal_Int32 nLen = aStr.getLength();
+ sal_Int32 i = 0;
+
+ while ( i < nLen )
+ {
+ if ( aStr[ i ] == '~' )
+ {
+ // check for CJK-style mnemonic
+ if( i > 0 && (i+2) < nLen )
+ {
+ sal_Unicode c = sal_Unicode(rtl::toAsciiLowerCase(aStr[i+1]));
+ if( aStr[ i-1 ] == '(' &&
+ aStr[ i+2 ] == ')' &&
+ c >= MNEMONIC_RANGE_2_START && c <= MNEMONIC_RANGE_2_END )
+ {
+ aStr = aStr.replaceAt( i-1, 4, u"" );
+ nLen -= 4;
+ i--;
+ continue;
+ }
+ }
+
+ // remove standard mnemonics
+ aStr = aStr.replaceAt( i, 1, u"" );
+ nLen--;
+ }
+ else
+ i++;
+ }
+
+ return aStr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/mouse.cxx b/vcl/source/window/mouse.cxx
new file mode 100644
index 000000000..de88cae1d
--- /dev/null
+++ b/vcl/source/window/mouse.cxx
@@ -0,0 +1,768 @@
+/* -*- 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 <config_feature_desktop.h>
+#include <config_vclplug.h>
+
+#include <tools/time.hxx>
+
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+
+#include <vcl/ITiledRenderable.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/event.hxx>
+
+#include <sal/types.h>
+
+#include <window.h>
+#include <svdata.hxx>
+#include <salobj.hxx>
+#include <salgdi.hxx>
+#include <salframe.hxx>
+#include <salinst.hxx>
+
+#include <dndlistenercontainer.hxx>
+#include <dndeventdispatcher.hxx>
+
+#include <com/sun/star/datatransfer/dnd/XDragSource.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <comphelper/processfactory.hxx>
+
+using namespace ::com::sun::star::uno;
+
+namespace vcl {
+
+WindowHitTest Window::ImplHitTest( const Point& rFramePos )
+{
+ Point aFramePos( rFramePos );
+ if( GetOutDev()->ImplIsAntiparallel() )
+ {
+ const OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReMirror( aFramePos );
+ }
+ if ( !GetOutputRectPixel().Contains( aFramePos ) )
+ return WindowHitTest::NONE;
+ if ( mpWindowImpl->mbWinRegion )
+ {
+ Point aTempPos = aFramePos;
+ aTempPos.AdjustX( -GetOutDev()->mnOutOffX );
+ aTempPos.AdjustY( -GetOutDev()->mnOutOffY );
+ if ( !mpWindowImpl->maWinRegion.Contains( aTempPos ) )
+ return WindowHitTest::NONE;
+ }
+
+ WindowHitTest nHitTest = WindowHitTest::Inside;
+ if ( mpWindowImpl->mbMouseTransparent )
+ nHitTest |= WindowHitTest::Transparent;
+ return nHitTest;
+}
+
+bool Window::ImplTestMousePointerSet()
+{
+ // as soon as mouse is captured, switch mouse-pointer
+ if ( IsMouseCaptured() )
+ return true;
+
+ // if the mouse is over the window, switch it
+ tools::Rectangle aClientRect( Point( 0, 0 ), GetOutputSizePixel() );
+ return aClientRect.Contains( GetPointerPosPixel() );
+}
+
+PointerStyle Window::ImplGetMousePointer() const
+{
+ PointerStyle ePointerStyle;
+ bool bWait = false;
+
+ if ( IsEnabled() && IsInputEnabled() && ! IsInModalMode() )
+ ePointerStyle = GetPointer();
+ else
+ ePointerStyle = PointerStyle::Arrow;
+
+ const vcl::Window* pWindow = this;
+ do
+ {
+ // when the pointer is not visible stop the search, as
+ // this status should not be overwritten
+ if ( pWindow->mpWindowImpl->mbNoPtrVisible )
+ return PointerStyle::Null;
+
+ if ( !bWait )
+ {
+ if ( pWindow->mpWindowImpl->mnWaitCount )
+ {
+ ePointerStyle = PointerStyle::Wait;
+ bWait = true;
+ }
+ else
+ {
+ if ( pWindow->mpWindowImpl->mbChildPtrOverwrite )
+ ePointerStyle = pWindow->GetPointer();
+ }
+ }
+
+ if ( pWindow->ImplIsOverlapWindow() )
+ break;
+
+ pWindow = pWindow->ImplGetParent();
+ }
+ while ( pWindow );
+
+ return ePointerStyle;
+}
+
+void Window::ImplCallMouseMove( sal_uInt16 nMouseCode, bool bModChanged )
+{
+ if ( !(mpWindowImpl->mpFrameData->mbMouseIn && mpWindowImpl->mpFrameWindow->mpWindowImpl->mbReallyVisible) )
+ return;
+
+ sal_uInt64 nTime = tools::Time::GetSystemTicks();
+ tools::Long nX = mpWindowImpl->mpFrameData->mnLastMouseX;
+ tools::Long nY = mpWindowImpl->mpFrameData->mnLastMouseY;
+ sal_uInt16 nCode = nMouseCode;
+ MouseEventModifiers nMode = mpWindowImpl->mpFrameData->mnMouseMode;
+ bool bLeave;
+ // check for MouseLeave
+ bLeave = ((nX < 0) || (nY < 0) ||
+ (nX >= mpWindowImpl->mpFrameWindow->GetOutDev()->mnOutWidth) ||
+ (nY >= mpWindowImpl->mpFrameWindow->GetOutDev()->mnOutHeight)) &&
+ !ImplGetSVData()->mpWinData->mpCaptureWin;
+ nMode |= MouseEventModifiers::SYNTHETIC;
+ if ( bModChanged )
+ nMode |= MouseEventModifiers::MODIFIERCHANGED;
+ ImplHandleMouseEvent( mpWindowImpl->mpFrameWindow, MouseNotifyEvent::MOUSEMOVE, bLeave, nX, nY, nTime, nCode, nMode );
+}
+
+void Window::ImplGenerateMouseMove()
+{
+ if ( mpWindowImpl && mpWindowImpl->mpFrameData &&
+ !mpWindowImpl->mpFrameData->mnMouseMoveId )
+ mpWindowImpl->mpFrameData->mnMouseMoveId = Application::PostUserEvent( LINK( mpWindowImpl->mpFrameWindow, Window, ImplGenerateMouseMoveHdl ), nullptr, true );
+}
+
+IMPL_LINK_NOARG(Window, ImplGenerateMouseMoveHdl, void*, void)
+{
+ mpWindowImpl->mpFrameData->mnMouseMoveId = nullptr;
+ vcl::Window* pCaptureWin = ImplGetSVData()->mpWinData->mpCaptureWin;
+ if( ! pCaptureWin ||
+ (pCaptureWin->mpWindowImpl && pCaptureWin->mpWindowImpl->mpFrame == mpWindowImpl->mpFrame)
+ )
+ {
+ ImplCallMouseMove( mpWindowImpl->mpFrameData->mnMouseCode );
+ }
+}
+
+void Window::ImplInvertFocus( const tools::Rectangle& rRect )
+{
+ InvertTracking( rRect, ShowTrackFlags::Small | ShowTrackFlags::TrackWindow );
+}
+
+static bool IsWindowFocused(const WindowImpl& rWinImpl)
+{
+ if (rWinImpl.mpSysObj)
+ return true;
+
+ if (rWinImpl.mpFrameData->mbHasFocus)
+ return true;
+
+ if (rWinImpl.mbFakeFocusSet)
+ return true;
+
+ return false;
+}
+
+void Window::ImplGrabFocus( GetFocusFlags nFlags )
+{
+ // #143570# no focus for destructing windows
+ if( !mpWindowImpl || mpWindowImpl->mbInDispose )
+ return;
+
+ // some event listeners do really bad stuff
+ // => prepare for the worst
+ VclPtr<vcl::Window> xWindow( this );
+
+ // Currently the client window should always get the focus
+ // Should the border window at some point be focusable
+ // we need to change all GrabFocus() instances in VCL,
+ // e.g. in ToTop()
+
+ if ( mpWindowImpl->mpClientWindow )
+ {
+ // For a lack of design we need a little hack here to
+ // ensure that dialogs on close pass the focus back to
+ // the correct window
+ if ( mpWindowImpl->mpLastFocusWindow && (mpWindowImpl->mpLastFocusWindow.get() != this) &&
+ !(mpWindowImpl->mnDlgCtrlFlags & DialogControlFlags::WantFocus) &&
+ mpWindowImpl->mpLastFocusWindow->IsEnabled() &&
+ mpWindowImpl->mpLastFocusWindow->IsInputEnabled() &&
+ ! mpWindowImpl->mpLastFocusWindow->IsInModalMode()
+ )
+ mpWindowImpl->mpLastFocusWindow->GrabFocus();
+ else
+ mpWindowImpl->mpClientWindow->GrabFocus();
+ return;
+ }
+ else if ( mpWindowImpl->mbFrame )
+ {
+ // For a lack of design we need a little hack here to
+ // ensure that dialogs on close pass the focus back to
+ // the correct window
+ if ( mpWindowImpl->mpLastFocusWindow && (mpWindowImpl->mpLastFocusWindow.get() != this) &&
+ !(mpWindowImpl->mnDlgCtrlFlags & DialogControlFlags::WantFocus) &&
+ mpWindowImpl->mpLastFocusWindow->IsEnabled() &&
+ mpWindowImpl->mpLastFocusWindow->IsInputEnabled() &&
+ ! mpWindowImpl->mpLastFocusWindow->IsInModalMode()
+ )
+ {
+ mpWindowImpl->mpLastFocusWindow->GrabFocus();
+ return;
+ }
+ }
+
+ // If the Window is disabled, then we don't change the focus
+ if ( !IsEnabled() || !IsInputEnabled() || IsInModalMode() )
+ return;
+
+ // we only need to set the focus if it is not already set
+ // note: if some other frame is waiting for an asynchronous focus event
+ // we also have to post an asynchronous focus event for this frame
+ // which is done using ToTop
+ ImplSVData* pSVData = ImplGetSVData();
+
+ bool bAsyncFocusWaiting = false;
+ vcl::Window *pFrame = pSVData->maFrameData.mpFirstFrame;
+ while( pFrame && pFrame->mpWindowImpl && pFrame->mpWindowImpl->mpFrameData )
+ {
+ if( pFrame != mpWindowImpl->mpFrameWindow.get() && pFrame->mpWindowImpl->mpFrameData->mnFocusId )
+ {
+ bAsyncFocusWaiting = true;
+ break;
+ }
+ pFrame = pFrame->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+
+ bool bHasFocus = IsWindowFocused(*mpWindowImpl);
+
+ bool bMustNotGrabFocus = false;
+ // #100242#, check parent hierarchy if some floater prohibits grab focus
+
+ vcl::Window *pParent = this;
+ while( pParent )
+ {
+ if ((pParent->GetStyle() & WB_SYSTEMFLOATWIN) && !(pParent->GetStyle() & WB_MOVEABLE))
+ {
+ bMustNotGrabFocus = true;
+ break;
+ }
+ if (!pParent->mpWindowImpl)
+ break;
+ pParent = pParent->mpWindowImpl->mpParent;
+ }
+
+ if ( !(( pSVData->mpWinData->mpFocusWin.get() != this &&
+ !mpWindowImpl->mbInDispose ) ||
+ ( bAsyncFocusWaiting && !bHasFocus && !bMustNotGrabFocus )) )
+ return;
+
+ // EndExtTextInput if it is not the same window
+ if (pSVData->mpWinData->mpExtTextInputWin
+ && (pSVData->mpWinData->mpExtTextInputWin.get() != this))
+ pSVData->mpWinData->mpExtTextInputWin->EndExtTextInput();
+
+ // mark this windows as the last FocusWindow
+ vcl::Window* pOverlapWindow = ImplGetFirstOverlapWindow();
+ if (pOverlapWindow->mpWindowImpl)
+ pOverlapWindow->mpWindowImpl->mpLastFocusWindow = this;
+ mpWindowImpl->mpFrameData->mpFocusWin = this;
+
+ if( !bHasFocus )
+ {
+ // menu windows never get the system focus
+ // the application will keep the focus
+ if( bMustNotGrabFocus )
+ return;
+ else
+ {
+ // here we already switch focus as ToTop()
+ // should not give focus to another window
+ mpWindowImpl->mpFrame->ToTop( SalFrameToTop::GrabFocus | SalFrameToTop::GrabFocusOnly );
+ return;
+ }
+ }
+
+ VclPtr<vcl::Window> pOldFocusWindow = pSVData->mpWinData->mpFocusWin;
+
+ pSVData->mpWinData->mpFocusWin = this;
+
+ if ( pOldFocusWindow && pOldFocusWindow->mpWindowImpl )
+ {
+ // Cursor hidden
+ if ( pOldFocusWindow->mpWindowImpl->mpCursor )
+ pOldFocusWindow->mpWindowImpl->mpCursor->ImplHide();
+ }
+
+ // !!!!! due to old SV-Office Activate/Deactivate handling
+ // !!!!! first as before
+ if ( pOldFocusWindow )
+ {
+ // remember Focus
+ vcl::Window* pOldOverlapWindow = pOldFocusWindow->ImplGetFirstOverlapWindow();
+ vcl::Window* pNewOverlapWindow = ImplGetFirstOverlapWindow();
+ if ( pOldOverlapWindow != pNewOverlapWindow )
+ ImplCallFocusChangeActivate( pNewOverlapWindow, pOldOverlapWindow );
+ }
+ else
+ {
+ vcl::Window* pNewOverlapWindow = ImplGetFirstOverlapWindow();
+ if ( pNewOverlapWindow && pNewOverlapWindow->mpWindowImpl )
+ {
+ vcl::Window* pNewRealWindow = pNewOverlapWindow->ImplGetWindow();
+ pNewOverlapWindow->mpWindowImpl->mbActive = true;
+ pNewOverlapWindow->Activate();
+ if ( pNewRealWindow != pNewOverlapWindow && pNewRealWindow && pNewRealWindow->mpWindowImpl )
+ {
+ pNewRealWindow->mpWindowImpl->mbActive = true;
+ pNewRealWindow->Activate();
+ }
+ }
+ }
+
+ // call Get- and LoseFocus
+ if ( pOldFocusWindow && ! pOldFocusWindow->isDisposed() )
+ {
+ NotifyEvent aNEvt( MouseNotifyEvent::LOSEFOCUS, pOldFocusWindow );
+ if ( !ImplCallPreNotify( aNEvt ) )
+ pOldFocusWindow->CompatLoseFocus();
+ pOldFocusWindow->ImplCallDeactivateListeners( this );
+ }
+
+ if (pSVData->mpWinData->mpFocusWin.get() == this)
+ {
+ if ( mpWindowImpl->mpSysObj )
+ {
+ mpWindowImpl->mpFrameData->mpFocusWin = this;
+ if ( !mpWindowImpl->mpFrameData->mbInSysObjFocusHdl )
+ mpWindowImpl->mpSysObj->GrabFocus();
+ }
+
+ if (pSVData->mpWinData->mpFocusWin.get() == this)
+ {
+ if ( mpWindowImpl->mpCursor )
+ mpWindowImpl->mpCursor->ImplShow();
+ mpWindowImpl->mbInFocusHdl = true;
+ mpWindowImpl->mnGetFocusFlags = nFlags;
+ // if we're changing focus due to closing a popup floating window
+ // notify the new focus window so it can restore the inner focus
+ // eg, toolboxes can select their recent active item
+ if( pOldFocusWindow &&
+ ! pOldFocusWindow->isDisposed() &&
+ ( pOldFocusWindow->GetDialogControlFlags() & DialogControlFlags::FloatWinPopupModeEndCancel ) )
+ mpWindowImpl->mnGetFocusFlags |= GetFocusFlags::FloatWinPopupModeEndCancel;
+ NotifyEvent aNEvt( MouseNotifyEvent::GETFOCUS, this );
+ if ( !ImplCallPreNotify( aNEvt ) && !xWindow->isDisposed() )
+ CompatGetFocus();
+ if( !xWindow->isDisposed() )
+ ImplCallActivateListeners( (pOldFocusWindow && ! pOldFocusWindow->isDisposed()) ? pOldFocusWindow : nullptr );
+ if( !xWindow->isDisposed() )
+ {
+ mpWindowImpl->mnGetFocusFlags = GetFocusFlags::NONE;
+ mpWindowImpl->mbInFocusHdl = false;
+ }
+ }
+ }
+
+ ImplNewInputContext();
+
+}
+
+void Window::ImplGrabFocusToDocument( GetFocusFlags nFlags )
+{
+ vcl::Window *pWin = this;
+ while( pWin )
+ {
+ if( !pWin->GetParent() )
+ {
+ pWin->mpWindowImpl->mpFrame->GrabFocus();
+ pWin->ImplGetFrameWindow()->GetWindow( GetWindowType::Client )->ImplGrabFocus(nFlags);
+ return;
+ }
+ pWin = pWin->GetParent();
+ }
+}
+
+void Window::MouseMove( const MouseEvent& rMEvt )
+{
+ NotifyEvent aNEvt( MouseNotifyEvent::MOUSEMOVE, this, &rMEvt );
+ EventNotify(aNEvt);
+}
+
+void Window::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ NotifyEvent aNEvt( MouseNotifyEvent::MOUSEBUTTONDOWN, this, &rMEvt );
+ if (!EventNotify(aNEvt))
+ mpWindowImpl->mbMouseButtonDown = true;
+}
+
+void Window::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ NotifyEvent aNEvt( MouseNotifyEvent::MOUSEBUTTONUP, this, &rMEvt );
+ if (!EventNotify(aNEvt))
+ mpWindowImpl->mbMouseButtonUp = true;
+}
+
+void Window::SetMouseTransparent( bool bTransparent )
+{
+
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->SetMouseTransparent( bTransparent );
+
+ if( mpWindowImpl->mpSysObj )
+ mpWindowImpl->mpSysObj->SetMouseTransparent( bTransparent );
+
+ mpWindowImpl->mbMouseTransparent = bTransparent;
+}
+
+void Window::LocalStartDrag()
+{
+ ImplGetFrameData()->mbDragging = true;
+}
+
+void Window::CaptureMouse()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // possibly stop tracking
+ if (pSVData->mpWinData->mpTrackWin.get() != this)
+ {
+ if (pSVData->mpWinData->mpTrackWin)
+ pSVData->mpWinData->mpTrackWin->EndTracking(TrackingEventFlags::Cancel);
+ }
+
+ if (pSVData->mpWinData->mpCaptureWin.get() != this)
+ {
+ pSVData->mpWinData->mpCaptureWin = this;
+ mpWindowImpl->mpFrame->CaptureMouse( true );
+ }
+}
+
+void Window::ReleaseMouse()
+{
+ if (IsMouseCaptured())
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->mpWinData->mpCaptureWin = nullptr;
+ if (mpWindowImpl && mpWindowImpl->mpFrame)
+ mpWindowImpl->mpFrame->CaptureMouse( false );
+ ImplGenerateMouseMove();
+ }
+}
+
+bool Window::IsMouseCaptured() const
+{
+ return (this == ImplGetSVData()->mpWinData->mpCaptureWin);
+}
+
+void Window::SetPointer( PointerStyle nPointer )
+{
+ if ( mpWindowImpl->maPointer == nPointer )
+ return;
+
+ mpWindowImpl->maPointer = nPointer;
+
+ // possibly immediately move pointer
+ if ( !mpWindowImpl->mpFrameData->mbInMouseMove && ImplTestMousePointerSet() )
+ mpWindowImpl->mpFrame->SetPointer( ImplGetMousePointer() );
+
+ VclPtr<vcl::Window> pWin = GetParentWithLOKNotifier();
+ if (!pWin)
+ return;
+
+ PointerStyle aPointer = GetPointer();
+ // We don't map all possible pointers hence we need a default
+ OString aPointerString = "default";
+ auto aIt = vcl::gaLOKPointerMap.find(aPointer);
+ if (aIt != vcl::gaLOKPointerMap.end())
+ {
+ aPointerString = aIt->second;
+ }
+
+ // issue mouse pointer events only for document windows
+ // Doc windows' immediate parent SfxFrameViewWindow_Impl is the one with
+ // parent notifier set during initialization
+ if ((ImplGetFrameData()->mbDragging &&
+ ImplGetFrameData()->mpMouseDownWin == this) ||
+ (GetParent()->ImplGetWindowImpl()->mbLOKParentNotifier &&
+ GetParent()->ImplGetWindowImpl()->mnLOKWindowId == 0))
+ {
+ pWin->GetLOKNotifier()->libreOfficeKitViewCallback(LOK_CALLBACK_MOUSE_POINTER, aPointerString.getStr());
+ }
+}
+
+void Window::EnableChildPointerOverwrite( bool bOverwrite )
+{
+
+ if ( mpWindowImpl->mbChildPtrOverwrite == bOverwrite )
+ return;
+
+ mpWindowImpl->mbChildPtrOverwrite = bOverwrite;
+
+ // possibly immediately move pointer
+ if ( !mpWindowImpl->mpFrameData->mbInMouseMove && ImplTestMousePointerSet() )
+ mpWindowImpl->mpFrame->SetPointer( ImplGetMousePointer() );
+}
+
+void Window::SetPointerPosPixel( const Point& rPos )
+{
+ Point aPos = ImplOutputToFrame( rPos );
+ const OutputDevice *pOutDev = GetOutDev();
+ if( pOutDev->HasMirroredGraphics() )
+ {
+ if( !IsRTLEnabled() )
+ {
+ pOutDev->ReMirror( aPos );
+ }
+ // mirroring is required here, SetPointerPos bypasses SalGraphics
+ aPos.setX( GetOutDev()->mpGraphics->mirror2( aPos.X(), *GetOutDev() ) );
+ }
+ else if( GetOutDev()->ImplIsAntiparallel() )
+ {
+ pOutDev->ReMirror( aPos );
+ }
+ mpWindowImpl->mpFrame->SetPointerPos( aPos.X(), aPos.Y() );
+}
+
+void Window::SetLastMousePos(const Point& rPos)
+{
+ // Do this conversion, so when GetPointerPosPixel() calls
+ // ImplFrameToOutput(), we get back the original position.
+ Point aPos = ImplOutputToFrame(rPos);
+ mpWindowImpl->mpFrameData->mnLastMouseX = aPos.X();
+ mpWindowImpl->mpFrameData->mnLastMouseY = aPos.Y();
+}
+
+Point Window::GetPointerPosPixel()
+{
+
+ Point aPos( mpWindowImpl->mpFrameData->mnLastMouseX, mpWindowImpl->mpFrameData->mnLastMouseY );
+ if( GetOutDev()->ImplIsAntiparallel() )
+ {
+ const OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReMirror( aPos );
+ }
+ return ImplFrameToOutput( aPos );
+}
+
+Point Window::GetLastPointerPosPixel()
+{
+
+ Point aPos( mpWindowImpl->mpFrameData->mnBeforeLastMouseX, mpWindowImpl->mpFrameData->mnBeforeLastMouseY );
+ if( GetOutDev()->ImplIsAntiparallel() )
+ {
+ const OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReMirror( aPos );
+ }
+ return ImplFrameToOutput( aPos );
+}
+
+void Window::ShowPointer( bool bVisible )
+{
+
+ if ( mpWindowImpl->mbNoPtrVisible != !bVisible )
+ {
+ mpWindowImpl->mbNoPtrVisible = !bVisible;
+
+ // possibly immediately move pointer
+ if ( !mpWindowImpl->mpFrameData->mbInMouseMove && ImplTestMousePointerSet() )
+ mpWindowImpl->mpFrame->SetPointer( ImplGetMousePointer() );
+ }
+}
+
+Window::PointerState Window::GetPointerState()
+{
+ PointerState aState;
+ aState.mnState = 0;
+
+ if (mpWindowImpl->mpFrame)
+ {
+ SalFrame::SalPointerState aSalPointerState = mpWindowImpl->mpFrame->GetPointerState();
+ if( GetOutDev()->ImplIsAntiparallel() )
+ {
+ const OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReMirror( aSalPointerState.maPos );
+ }
+ aState.maPos = ImplFrameToOutput( aSalPointerState.maPos );
+ aState.mnState = aSalPointerState.mnState;
+ }
+ return aState;
+}
+
+bool Window::IsMouseOver() const
+{
+ return ImplGetWinData()->mbMouseOver;
+}
+
+void Window::EnterWait()
+{
+
+ mpWindowImpl->mnWaitCount++;
+
+ if ( mpWindowImpl->mnWaitCount == 1 )
+ {
+ // possibly immediately move pointer
+ if ( !mpWindowImpl->mpFrameData->mbInMouseMove && ImplTestMousePointerSet() )
+ mpWindowImpl->mpFrame->SetPointer( ImplGetMousePointer() );
+ }
+}
+
+void Window::LeaveWait()
+{
+ if( !mpWindowImpl )
+ return;
+
+ if ( mpWindowImpl->mnWaitCount )
+ {
+ mpWindowImpl->mnWaitCount--;
+
+ if ( !mpWindowImpl->mnWaitCount )
+ {
+ // possibly immediately move pointer
+ if ( !mpWindowImpl->mpFrameData->mbInMouseMove && ImplTestMousePointerSet() )
+ mpWindowImpl->mpFrame->SetPointer( ImplGetMousePointer() );
+ }
+ }
+}
+
+bool Window::ImplStopDnd()
+{
+ bool bRet = false;
+ if( mpWindowImpl->mpFrameData && mpWindowImpl->mpFrameData->mxDropTargetListener.is() )
+ {
+ bRet = true;
+ mpWindowImpl->mpFrameData->mxDropTarget.clear();
+ mpWindowImpl->mpFrameData->mxDragSource.clear();
+ mpWindowImpl->mpFrameData->mxDropTargetListener.clear();
+ }
+
+ return bRet;
+}
+
+void Window::ImplStartDnd()
+{
+ GetDropTarget();
+}
+
+Reference< css::datatransfer::dnd::XDropTarget > Window::GetDropTarget()
+{
+ if( !mpWindowImpl )
+ return Reference< css::datatransfer::dnd::XDropTarget >();
+
+ if( ! mpWindowImpl->mxDNDListenerContainer.is() )
+ {
+ sal_Int8 nDefaultActions = 0;
+
+ if( mpWindowImpl->mpFrameData )
+ {
+ if( ! mpWindowImpl->mpFrameData->mxDropTarget.is() )
+ {
+ // initialization is done in GetDragSource
+ GetDragSource();
+ }
+
+ if( mpWindowImpl->mpFrameData->mxDropTarget.is() )
+ {
+ nDefaultActions = mpWindowImpl->mpFrameData->mxDropTarget->getDefaultActions();
+
+ if( ! mpWindowImpl->mpFrameData->mxDropTargetListener.is() )
+ {
+ mpWindowImpl->mpFrameData->mxDropTargetListener = new DNDEventDispatcher( mpWindowImpl->mpFrameWindow );
+
+ try
+ {
+ mpWindowImpl->mpFrameData->mxDropTarget->addDropTargetListener( mpWindowImpl->mpFrameData->mxDropTargetListener );
+
+ // register also as drag gesture listener if directly supported by drag source
+ Reference< css::datatransfer::dnd::XDragGestureRecognizer > xDragGestureRecognizer(
+ mpWindowImpl->mpFrameData->mxDragSource, UNO_QUERY);
+
+ if( xDragGestureRecognizer.is() )
+ {
+ xDragGestureRecognizer->addDragGestureListener(
+ Reference< css::datatransfer::dnd::XDragGestureListener > (mpWindowImpl->mpFrameData->mxDropTargetListener, UNO_QUERY));
+ }
+ else
+ mpWindowImpl->mpFrameData->mbInternalDragGestureRecognizer = true;
+
+ }
+ catch (const RuntimeException&)
+ {
+ // release all instances
+ mpWindowImpl->mpFrameData->mxDropTarget.clear();
+ mpWindowImpl->mpFrameData->mxDragSource.clear();
+ }
+ }
+ }
+
+ }
+
+ mpWindowImpl->mxDNDListenerContainer = static_cast < css::datatransfer::dnd::XDropTarget * > ( new DNDListenerContainer( nDefaultActions ) );
+ }
+
+ // this object is located in the same process, so there will be no runtime exception
+ return Reference< css::datatransfer::dnd::XDropTarget > ( mpWindowImpl->mxDNDListenerContainer, UNO_QUERY );
+}
+
+Reference< css::datatransfer::dnd::XDragSource > Window::GetDragSource()
+{
+#if HAVE_FEATURE_DESKTOP
+ const SystemEnvData* pEnvData = GetSystemData();
+ if (!mpWindowImpl->mpFrameData || !pEnvData)
+ return Reference<css::datatransfer::dnd::XDragSource>();
+ if (mpWindowImpl->mpFrameData->mxDragSource.is())
+ return mpWindowImpl->mpFrameData->mxDragSource;
+
+ try
+ {
+ SalInstance* pInst = ImplGetSVData()->mpDefInst;
+ mpWindowImpl->mpFrameData->mxDragSource.set(pInst->CreateDragSource(pEnvData), UNO_QUERY);
+ mpWindowImpl->mpFrameData->mxDropTarget.set(pInst->CreateDropTarget(pEnvData), UNO_QUERY);
+ }
+ catch (const Exception&)
+ {
+ mpWindowImpl->mpFrameData->mxDropTarget.clear();
+ mpWindowImpl->mpFrameData->mxDragSource.clear();
+ }
+ return mpWindowImpl->mpFrameData->mxDragSource;
+#else
+ return Reference< css::datatransfer::dnd::XDragSource > ();
+#endif
+}
+
+Reference< css::datatransfer::dnd::XDragGestureRecognizer > Window::GetDragGestureRecognizer()
+{
+ return Reference< css::datatransfer::dnd::XDragGestureRecognizer > ( GetDropTarget(), UNO_QUERY );
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/paint.cxx b/vcl/source/window/paint.cxx
new file mode 100644
index 000000000..a70f1c0e4
--- /dev/null
+++ b/vcl/source/window/paint.cxx
@@ -0,0 +1,1809 @@
+/* -*- 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 <config_features.h>
+#include <vcl/gdimtf.hxx>
+#include <vcl/window.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/syswin.hxx>
+
+#include <sal/types.h>
+#include <sal/log.hxx>
+
+#include <window.h>
+#include <salgdi.hxx>
+#include <salframe.hxx>
+#include <svdata.hxx>
+#include <comphelper/lok.hxx>
+#include <comphelper/profilezone.hxx>
+#if HAVE_FEATURE_OPENGL
+#include <vcl/opengl/OpenGLHelper.hxx>
+#endif
+
+// PaintBufferGuard
+
+namespace vcl
+{
+PaintBufferGuard::PaintBufferGuard(ImplFrameData* pFrameData, vcl::Window* pWindow)
+ : mpFrameData(pFrameData),
+ m_pWindow(pWindow),
+ mbBackground(false),
+ mnOutOffX(0),
+ mnOutOffY(0)
+{
+ if (!pFrameData->mpBuffer)
+ return;
+
+ // transfer various settings
+ // FIXME: this must disappear as we move to RenderContext only,
+ // the painting must become state-less, so that no actual
+ // vcl::Window setting affects this
+ mbBackground = pFrameData->mpBuffer->IsBackground();
+ if (pWindow->IsBackground())
+ {
+ maBackground = pFrameData->mpBuffer->GetBackground();
+ pFrameData->mpBuffer->SetBackground(pWindow->GetBackground());
+ }
+ //else
+ //SAL_WARN("vcl.window", "the root of the double-buffering hierarchy should not have a transparent background");
+
+ vcl::PushFlags nFlags = vcl::PushFlags::NONE;
+ nFlags |= vcl::PushFlags::CLIPREGION;
+ nFlags |= vcl::PushFlags::FILLCOLOR;
+ nFlags |= vcl::PushFlags::FONT;
+ nFlags |= vcl::PushFlags::LINECOLOR;
+ nFlags |= vcl::PushFlags::MAPMODE;
+ maSettings = pFrameData->mpBuffer->GetSettings();
+ nFlags |= vcl::PushFlags::REFPOINT;
+ nFlags |= vcl::PushFlags::TEXTCOLOR;
+ nFlags |= vcl::PushFlags::TEXTLINECOLOR;
+ nFlags |= vcl::PushFlags::OVERLINECOLOR;
+ nFlags |= vcl::PushFlags::TEXTFILLCOLOR;
+ nFlags |= vcl::PushFlags::TEXTALIGN;
+ nFlags |= vcl::PushFlags::RASTEROP;
+ nFlags |= vcl::PushFlags::TEXTLAYOUTMODE;
+ nFlags |= vcl::PushFlags::TEXTLANGUAGE;
+ pFrameData->mpBuffer->Push(nFlags);
+ auto& rDev = *pWindow->GetOutDev();
+ pFrameData->mpBuffer->SetClipRegion(rDev.GetClipRegion());
+ pFrameData->mpBuffer->SetFillColor(rDev.GetFillColor());
+ pFrameData->mpBuffer->SetFont(pWindow->GetFont());
+ pFrameData->mpBuffer->SetLineColor(rDev.GetLineColor());
+ pFrameData->mpBuffer->SetMapMode(pWindow->GetMapMode());
+ pFrameData->mpBuffer->SetRefPoint(rDev.GetRefPoint());
+ pFrameData->mpBuffer->SetSettings(pWindow->GetSettings());
+ pFrameData->mpBuffer->SetTextColor(pWindow->GetTextColor());
+ pFrameData->mpBuffer->SetTextLineColor(pWindow->GetTextLineColor());
+ pFrameData->mpBuffer->SetOverlineColor(pWindow->GetOverlineColor());
+ pFrameData->mpBuffer->SetTextFillColor(pWindow->GetTextFillColor());
+ pFrameData->mpBuffer->SetTextAlign(pWindow->GetTextAlign());
+ pFrameData->mpBuffer->SetRasterOp(rDev.GetRasterOp());
+ pFrameData->mpBuffer->SetLayoutMode(rDev.GetLayoutMode());
+ pFrameData->mpBuffer->SetDigitLanguage(rDev.GetDigitLanguage());
+
+ mnOutOffX = pFrameData->mpBuffer->GetOutOffXPixel();
+ mnOutOffY = pFrameData->mpBuffer->GetOutOffYPixel();
+ pFrameData->mpBuffer->SetOutOffXPixel(pWindow->GetOutOffXPixel());
+ pFrameData->mpBuffer->SetOutOffYPixel(pWindow->GetOutOffYPixel());
+ pFrameData->mpBuffer->EnableRTL(pWindow->IsRTLEnabled());
+}
+
+PaintBufferGuard::~PaintBufferGuard() COVERITY_NOEXCEPT_FALSE
+{
+ if (!mpFrameData->mpBuffer)
+ return;
+
+ if (!m_aPaintRect.IsEmpty())
+ {
+ // copy the buffer content to the actual window
+ // export VCL_DOUBLEBUFFERING_AVOID_PAINT=1 to see where we are
+ // painting directly instead of using Invalidate()
+ // [ie. everything you can see was painted directly to the
+ // window either above or in eg. an event handler]
+ if (!getenv("VCL_DOUBLEBUFFERING_AVOID_PAINT"))
+ {
+ // Make sure that the +1 value GetSize() adds to the size is in pixels.
+ Size aPaintRectSize;
+ if (m_pWindow->GetMapMode().GetMapUnit() == MapUnit::MapPixel)
+ {
+ aPaintRectSize = m_aPaintRect.GetSize();
+ }
+ else
+ {
+ tools::Rectangle aRectanglePixel = m_pWindow->LogicToPixel(m_aPaintRect);
+ aPaintRectSize = m_pWindow->PixelToLogic(aRectanglePixel.GetSize());
+ }
+
+ m_pWindow->GetOutDev()->DrawOutDev(m_aPaintRect.TopLeft(), aPaintRectSize, m_aPaintRect.TopLeft(), aPaintRectSize, *mpFrameData->mpBuffer);
+ }
+ }
+
+ // Restore buffer state.
+ mpFrameData->mpBuffer->SetOutOffXPixel(mnOutOffX);
+ mpFrameData->mpBuffer->SetOutOffYPixel(mnOutOffY);
+
+ mpFrameData->mpBuffer->Pop();
+ mpFrameData->mpBuffer->SetSettings(maSettings);
+ if (mbBackground)
+ mpFrameData->mpBuffer->SetBackground(maBackground);
+ else
+ mpFrameData->mpBuffer->SetBackground();
+}
+
+void PaintBufferGuard::SetPaintRect(const tools::Rectangle& rRectangle)
+{
+ m_aPaintRect = rRectangle;
+}
+
+vcl::RenderContext* PaintBufferGuard::GetRenderContext()
+{
+ if (mpFrameData->mpBuffer)
+ return mpFrameData->mpBuffer;
+ else
+ return m_pWindow->GetOutDev();
+}
+}
+
+class PaintHelper
+{
+private:
+ VclPtr<vcl::Window> m_pWindow;
+ std::unique_ptr<vcl::Region> m_pChildRegion;
+ tools::Rectangle m_aSelectionRect;
+ tools::Rectangle m_aPaintRect;
+ vcl::Region m_aPaintRegion;
+ ImplPaintFlags m_nPaintFlags;
+ bool m_bPop : 1;
+ bool m_bRestoreCursor : 1;
+ bool m_bStartedBufferedPaint : 1; ///< This PaintHelper started a buffered paint, and should paint it on the screen when being destructed.
+public:
+ PaintHelper(vcl::Window* pWindow, ImplPaintFlags nPaintFlags);
+ void SetPop()
+ {
+ m_bPop = true;
+ }
+ void SetPaintRect(const tools::Rectangle& rRect)
+ {
+ m_aPaintRect = rRect;
+ }
+ void SetSelectionRect(const tools::Rectangle& rRect)
+ {
+ m_aSelectionRect = rRect;
+ }
+ void SetRestoreCursor(bool bRestoreCursor)
+ {
+ m_bRestoreCursor = bRestoreCursor;
+ }
+ bool GetRestoreCursor() const
+ {
+ return m_bRestoreCursor;
+ }
+ ImplPaintFlags GetPaintFlags() const
+ {
+ return m_nPaintFlags;
+ }
+ vcl::Region& GetPaintRegion()
+ {
+ return m_aPaintRegion;
+ }
+ void DoPaint(const vcl::Region* pRegion);
+
+ /// Start buffered paint: set it up to have the same settings as m_pWindow.
+ void StartBufferedPaint();
+
+ /// Paint the content of the buffer to the current m_pWindow.
+ void PaintBuffer();
+
+ ~PaintHelper();
+};
+
+PaintHelper::PaintHelper(vcl::Window *pWindow, ImplPaintFlags nPaintFlags)
+ : m_pWindow(pWindow)
+ , m_nPaintFlags(nPaintFlags)
+ , m_bPop(false)
+ , m_bRestoreCursor(false)
+ , m_bStartedBufferedPaint(false)
+{
+}
+
+void PaintHelper::StartBufferedPaint()
+{
+ ImplFrameData* pFrameData = m_pWindow->mpWindowImpl->mpFrameData;
+ assert(!pFrameData->mbInBufferedPaint);
+
+ pFrameData->mbInBufferedPaint = true;
+ pFrameData->maBufferedRect = tools::Rectangle();
+ m_bStartedBufferedPaint = true;
+}
+
+void PaintHelper::PaintBuffer()
+{
+ ImplFrameData* pFrameData = m_pWindow->mpWindowImpl->mpFrameData;
+ assert(pFrameData->mbInBufferedPaint);
+ assert(m_bStartedBufferedPaint);
+
+ vcl::PaintBufferGuard aGuard(pFrameData, m_pWindow);
+ aGuard.SetPaintRect(pFrameData->maBufferedRect);
+}
+
+void PaintHelper::DoPaint(const vcl::Region* pRegion)
+{
+ WindowImpl* pWindowImpl = m_pWindow->ImplGetWindowImpl();
+
+ vcl::Region& rWinChildClipRegion = m_pWindow->ImplGetWinChildClipRegion();
+ ImplFrameData* pFrameData = m_pWindow->mpWindowImpl->mpFrameData;
+ if (pWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll || pFrameData->mbInBufferedPaint)
+ {
+ pWindowImpl->maInvalidateRegion = rWinChildClipRegion;
+ }
+ else
+ {
+ if (pRegion)
+ pWindowImpl->maInvalidateRegion.Union( *pRegion );
+
+ if (pWindowImpl->mpWinData && pWindowImpl->mbTrackVisible)
+ /* #98602# need to repaint all children within the
+ * tracking rectangle, so the following invert
+ * operation takes places without traces of the previous
+ * one.
+ */
+ pWindowImpl->maInvalidateRegion.Union(*pWindowImpl->mpWinData->mpTrackRect);
+
+ if (pWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAllChildren)
+ m_pChildRegion.reset( new vcl::Region(pWindowImpl->maInvalidateRegion) );
+ pWindowImpl->maInvalidateRegion.Intersect(rWinChildClipRegion);
+ }
+ pWindowImpl->mnPaintFlags = ImplPaintFlags::NONE;
+ if (pWindowImpl->maInvalidateRegion.IsEmpty())
+ return;
+
+#if HAVE_FEATURE_OPENGL
+ VCL_GL_INFO("PaintHelper::DoPaint on " <<
+ typeid( *m_pWindow ).name() << " '" << m_pWindow->GetText() << "' begin");
+#endif
+ // double-buffering: setup the buffer if it does not exist
+ if (!pFrameData->mbInBufferedPaint && m_pWindow->SupportsDoubleBuffering())
+ StartBufferedPaint();
+
+ // double-buffering: if this window does not support double-buffering,
+ // but we are in the middle of double-buffered paint, we might be
+ // losing information
+ if (pFrameData->mbInBufferedPaint && !m_pWindow->SupportsDoubleBuffering())
+ SAL_WARN("vcl.window", "non-double buffered window in the double-buffered hierarchy, painting directly: " << typeid(*m_pWindow.get()).name());
+
+ if (pFrameData->mbInBufferedPaint && m_pWindow->SupportsDoubleBuffering())
+ {
+ // double-buffering
+ vcl::PaintBufferGuard g(pFrameData, m_pWindow);
+ m_pWindow->ApplySettings(*pFrameData->mpBuffer);
+
+ m_pWindow->PushPaintHelper(this, *pFrameData->mpBuffer);
+ m_pWindow->Paint(*pFrameData->mpBuffer, m_aPaintRect);
+ pFrameData->maBufferedRect.Union(m_aPaintRect);
+ }
+ else
+ {
+ // direct painting
+ Wallpaper aBackground = m_pWindow->GetBackground();
+ m_pWindow->ApplySettings(*m_pWindow->GetOutDev());
+ // Restore bitmap background if it was lost.
+ if (aBackground.IsBitmap() && !m_pWindow->GetBackground().IsBitmap())
+ {
+ m_pWindow->SetBackground(aBackground);
+ }
+ m_pWindow->PushPaintHelper(this, *m_pWindow->GetOutDev());
+ m_pWindow->Paint(*m_pWindow->GetOutDev(), m_aPaintRect);
+ }
+#if HAVE_FEATURE_OPENGL
+ VCL_GL_INFO("PaintHelper::DoPaint end on " <<
+ typeid( *m_pWindow ).name() << " '" << m_pWindow->GetText() << "'");
+#endif
+}
+
+namespace vcl
+{
+
+void RenderTools::DrawSelectionBackground(vcl::RenderContext& rRenderContext, vcl::Window const & rWindow,
+ const tools::Rectangle& rRect, sal_uInt16 nHighlight,
+ bool bChecked, bool bDrawBorder, bool bDrawExtBorderOnly,
+ Color* pSelectionTextColor, tools::Long nCornerRadius, Color const * pPaintColor)
+{
+ if (rRect.IsEmpty())
+ return;
+
+ bool bRoundEdges = nCornerRadius > 0;
+
+ const StyleSettings& rStyles = rRenderContext.GetSettings().GetStyleSettings();
+
+ // colors used for item highlighting
+ Color aSelectionBorderColor(pPaintColor ? *pPaintColor : rStyles.GetHighlightColor());
+ Color aSelectionFillColor(aSelectionBorderColor);
+
+ bool bDark = rStyles.GetFaceColor().IsDark();
+ bool bBright = ( rStyles.GetFaceColor() == COL_WHITE );
+
+ int c1 = aSelectionBorderColor.GetLuminance();
+ int c2 = rWindow.GetBackgroundColor().GetLuminance();
+
+ if (!bDark && !bBright && std::abs(c2 - c1) < (pPaintColor ? 40 : 75))
+ {
+ // contrast too low
+ sal_uInt16 h, s, b;
+ aSelectionFillColor.RGBtoHSB( h, s, b );
+ if( b > 50 ) b -= 40;
+ else b += 40;
+ aSelectionFillColor = Color::HSBtoRGB( h, s, b );
+ aSelectionBorderColor = aSelectionFillColor;
+ }
+
+ if (bRoundEdges)
+ {
+ if (aSelectionBorderColor.IsDark())
+ aSelectionBorderColor.IncreaseLuminance(128);
+ else
+ aSelectionBorderColor.DecreaseLuminance(128);
+ }
+
+ tools::Rectangle aRect(rRect);
+ if (bDrawExtBorderOnly)
+ {
+ aRect.AdjustLeft( -1 );
+ aRect.AdjustTop( -1 );
+ aRect.AdjustRight(1 );
+ aRect.AdjustBottom(1 );
+ }
+ rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR);
+
+ if (bDrawBorder)
+ rRenderContext.SetLineColor(bDark ? COL_WHITE : (bBright ? COL_BLACK : aSelectionBorderColor));
+ else
+ rRenderContext.SetLineColor();
+
+ sal_uInt16 nPercent = 0;
+ if (!nHighlight)
+ {
+ if (bDark)
+ aSelectionFillColor = COL_BLACK;
+ else
+ nPercent = 80; // just checked (light)
+ }
+ else
+ {
+ if (bChecked && nHighlight == 2)
+ {
+ if (bDark)
+ aSelectionFillColor = COL_LIGHTGRAY;
+ else if (bBright)
+ {
+ aSelectionFillColor = COL_BLACK;
+ rRenderContext.SetLineColor(COL_BLACK);
+ nPercent = 0;
+ }
+ else
+ nPercent = bRoundEdges ? 40 : 20; // selected, pressed or checked ( very dark )
+ }
+ else if (bChecked || nHighlight == 1)
+ {
+ if (bDark)
+ aSelectionFillColor = COL_GRAY;
+ else if (bBright)
+ {
+ aSelectionFillColor = COL_BLACK;
+ rRenderContext.SetLineColor(COL_BLACK);
+ nPercent = 0;
+ }
+ else
+ nPercent = bRoundEdges ? 60 : 35; // selected, pressed or checked ( very dark )
+ }
+ else
+ {
+ if (bDark)
+ aSelectionFillColor = COL_LIGHTGRAY;
+ else if (bBright)
+ {
+ aSelectionFillColor = COL_BLACK;
+ rRenderContext.SetLineColor(COL_BLACK);
+ if (nHighlight == 3)
+ nPercent = 80;
+ else
+ nPercent = 0;
+ }
+ else
+ nPercent = 70; // selected ( dark )
+ }
+ }
+
+ if (bDark && bDrawExtBorderOnly)
+ {
+ rRenderContext.SetFillColor();
+ if (pSelectionTextColor)
+ *pSelectionTextColor = rStyles.GetHighlightTextColor();
+ }
+ else
+ {
+ rRenderContext.SetFillColor(aSelectionFillColor);
+ if (pSelectionTextColor)
+ {
+ Color aTextColor = rWindow.IsControlBackground() ? rWindow.GetControlForeground() : rStyles.GetButtonTextColor();
+ Color aHLTextColor = rStyles.GetHighlightTextColor();
+ int nTextDiff = std::abs(aSelectionFillColor.GetLuminance() - aTextColor.GetLuminance());
+ int nHLDiff = std::abs(aSelectionFillColor.GetLuminance() - aHLTextColor.GetLuminance());
+ *pSelectionTextColor = (nHLDiff >= nTextDiff) ? aHLTextColor : aTextColor;
+ }
+ }
+
+ if (bDark)
+ {
+ rRenderContext.DrawRect(aRect);
+ }
+ else
+ {
+ if (bRoundEdges)
+ {
+ tools::Polygon aPoly(aRect, nCornerRadius, nCornerRadius);
+ tools::PolyPolygon aPolyPoly(aPoly);
+ rRenderContext.DrawTransparent(aPolyPoly, nPercent);
+ }
+ else
+ {
+ tools::Polygon aPoly(aRect);
+ tools::PolyPolygon aPolyPoly(aPoly);
+ rRenderContext.DrawTransparent(aPolyPoly, nPercent);
+ }
+ }
+
+ rRenderContext.Pop(); // LINECOLOR | FILLCOLOR
+}
+
+void Window::PushPaintHelper(PaintHelper *pHelper, vcl::RenderContext& rRenderContext)
+{
+ pHelper->SetPop();
+
+ if ( mpWindowImpl->mpCursor )
+ pHelper->SetRestoreCursor(mpWindowImpl->mpCursor->ImplSuspend());
+
+ GetOutDev()->mbInitClipRegion = true;
+ mpWindowImpl->mbInPaint = true;
+
+ // restore Paint-Region
+ vcl::Region &rPaintRegion = pHelper->GetPaintRegion();
+ rPaintRegion = mpWindowImpl->maInvalidateRegion;
+ tools::Rectangle aPaintRect = rPaintRegion.GetBoundRect();
+
+ // RTL: re-mirror paint rect and region at this window
+ if (GetOutDev()->ImplIsAntiparallel())
+ {
+ rRenderContext.ReMirror(aPaintRect);
+ rRenderContext.ReMirror(rPaintRegion);
+ }
+ aPaintRect = GetOutDev()->ImplDevicePixelToLogic(aPaintRect);
+ mpWindowImpl->mpPaintRegion = &rPaintRegion;
+ mpWindowImpl->maInvalidateRegion.SetEmpty();
+
+ if ((pHelper->GetPaintFlags() & ImplPaintFlags::Erase) && rRenderContext.IsBackground())
+ {
+ if (rRenderContext.IsClipRegion())
+ {
+ vcl::Region aOldRegion = rRenderContext.GetClipRegion();
+ rRenderContext.SetClipRegion();
+ Erase(rRenderContext);
+ rRenderContext.SetClipRegion(aOldRegion);
+ }
+ else
+ Erase(rRenderContext);
+ }
+
+ // #98943# trigger drawing of toolbox selection after all children are painted
+ if (mpWindowImpl->mbDrawSelectionBackground)
+ pHelper->SetSelectionRect(aPaintRect);
+ pHelper->SetPaintRect(aPaintRect);
+}
+
+void Window::PopPaintHelper(PaintHelper const *pHelper)
+{
+ if (mpWindowImpl->mpWinData)
+ {
+ if (mpWindowImpl->mbFocusVisible)
+ ImplInvertFocus(*mpWindowImpl->mpWinData->mpFocusRect);
+ }
+ mpWindowImpl->mbInPaint = false;
+ GetOutDev()->mbInitClipRegion = true;
+ mpWindowImpl->mpPaintRegion = nullptr;
+ if (mpWindowImpl->mpCursor)
+ mpWindowImpl->mpCursor->ImplResume(pHelper->GetRestoreCursor());
+}
+
+} /* namespace vcl */
+
+PaintHelper::~PaintHelper()
+{
+ WindowImpl* pWindowImpl = m_pWindow->ImplGetWindowImpl();
+ if (m_bPop)
+ {
+ m_pWindow->PopPaintHelper(this);
+ }
+
+ ImplFrameData* pFrameData = m_pWindow->mpWindowImpl->mpFrameData;
+ if ( m_nPaintFlags & (ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren) )
+ {
+ // Paint from the bottom child window and frontward.
+ vcl::Window* pTempWindow = pWindowImpl->mpLastChild;
+ while (pTempWindow)
+ {
+ if (pTempWindow->mpWindowImpl->mbVisible)
+ pTempWindow->ImplCallPaint(m_pChildRegion.get(), m_nPaintFlags);
+ pTempWindow = pTempWindow->mpWindowImpl->mpPrev;
+ }
+ }
+
+ if ( pWindowImpl->mpWinData && pWindowImpl->mbTrackVisible && (pWindowImpl->mpWinData->mnTrackFlags & ShowTrackFlags::TrackWindow) )
+ /* #98602# need to invert the tracking rect AFTER
+ * the children have painted
+ */
+ m_pWindow->InvertTracking( *pWindowImpl->mpWinData->mpTrackRect, pWindowImpl->mpWinData->mnTrackFlags );
+
+ // double-buffering: paint in case we created the buffer, the children are
+ // already painted inside
+ if (m_bStartedBufferedPaint && pFrameData->mbInBufferedPaint)
+ {
+ PaintBuffer();
+ pFrameData->mbInBufferedPaint = false;
+ pFrameData->maBufferedRect = tools::Rectangle();
+ }
+
+ // #98943# draw toolbox selection
+ if( !m_aSelectionRect.IsEmpty() )
+ m_pWindow->DrawSelectionBackground( m_aSelectionRect, 3, false, true );
+}
+
+namespace vcl {
+
+void Window::ImplCallPaint(const vcl::Region* pRegion, ImplPaintFlags nPaintFlags)
+{
+ // call PrePaint. PrePaint may add to the invalidate region as well as
+ // other parameters used below.
+ PrePaint(*GetOutDev());
+
+ mpWindowImpl->mbPaintFrame = false;
+
+ if (nPaintFlags & ImplPaintFlags::PaintAllChildren)
+ mpWindowImpl->mnPaintFlags |= ImplPaintFlags::Paint | ImplPaintFlags::PaintAllChildren | (nPaintFlags & ImplPaintFlags::PaintAll);
+ if (nPaintFlags & ImplPaintFlags::PaintChildren)
+ mpWindowImpl->mnPaintFlags |= ImplPaintFlags::PaintChildren;
+ if (nPaintFlags & ImplPaintFlags::Erase)
+ mpWindowImpl->mnPaintFlags |= ImplPaintFlags::Erase;
+ if (nPaintFlags & ImplPaintFlags::CheckRtl)
+ mpWindowImpl->mnPaintFlags |= ImplPaintFlags::CheckRtl;
+ if (!mpWindowImpl->mpFirstChild)
+ mpWindowImpl->mnPaintFlags &= ~ImplPaintFlags::PaintAllChildren;
+
+ // If tiled rendering is used, windows are only invalidated, never painted to.
+ if (mpWindowImpl->mbPaintDisabled || comphelper::LibreOfficeKit::isActive())
+ {
+ if (mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll)
+ Invalidate(InvalidateFlags::NoChildren | InvalidateFlags::NoErase | InvalidateFlags::NoTransparent | InvalidateFlags::NoClipChildren);
+ else if ( pRegion )
+ Invalidate(*pRegion, InvalidateFlags::NoChildren | InvalidateFlags::NoErase | InvalidateFlags::NoTransparent | InvalidateFlags::NoClipChildren);
+
+ // call PostPaint before returning
+ PostPaint(*GetOutDev());
+
+ return;
+ }
+
+ nPaintFlags = mpWindowImpl->mnPaintFlags & ~ImplPaintFlags::Paint;
+
+ PaintHelper aHelper(this, nPaintFlags);
+
+ if (mpWindowImpl->mnPaintFlags & ImplPaintFlags::Paint)
+ aHelper.DoPaint(pRegion);
+ else
+ mpWindowImpl->mnPaintFlags = ImplPaintFlags::NONE;
+
+ // call PostPaint
+ PostPaint(*GetOutDev());
+}
+
+void Window::ImplCallOverlapPaint()
+{
+ if (!mpWindowImpl)
+ return;
+
+ // emit overlapping windows first
+ vcl::Window* pTempWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pTempWindow )
+ {
+ if ( pTempWindow->mpWindowImpl->mbReallyVisible )
+ pTempWindow->ImplCallOverlapPaint();
+ pTempWindow = pTempWindow->mpWindowImpl->mpNext;
+ }
+
+ // only then ourself
+ if ( mpWindowImpl->mnPaintFlags & (ImplPaintFlags::Paint | ImplPaintFlags::PaintChildren) )
+ {
+ // RTL: notify ImplCallPaint to check for re-mirroring
+ // because we were called from the Sal layer
+ ImplCallPaint(nullptr, mpWindowImpl->mnPaintFlags /*| ImplPaintFlags::CheckRtl */);
+ }
+}
+
+IMPL_LINK_NOARG(Window, ImplHandlePaintHdl, Timer *, void)
+{
+ comphelper::ProfileZone aZone("VCL idle re-paint");
+
+ // save paint events until layout is done
+ if (IsSystemWindow() && static_cast<const SystemWindow*>(this)->hasPendingLayout())
+ {
+ mpWindowImpl->mpFrameData->maPaintIdle.Start();
+ return;
+ }
+
+ // save paint events until resizing or initial sizing done
+ if (mpWindowImpl->mbFrame &&
+ mpWindowImpl->mpFrameData->maResizeIdle.IsActive())
+ {
+ mpWindowImpl->mpFrameData->maPaintIdle.Start();
+ }
+ else if ( mpWindowImpl->mbReallyVisible )
+ {
+ ImplCallOverlapPaint();
+ if (comphelper::LibreOfficeKit::isActive() &&
+ mpWindowImpl->mpFrameData->maPaintIdle.IsActive())
+ mpWindowImpl->mpFrameData->maPaintIdle.Stop();
+ }
+}
+
+IMPL_LINK_NOARG(Window, ImplHandleResizeTimerHdl, Timer *, void)
+{
+ comphelper::ProfileZone aZone("VCL idle resize");
+
+ if( mpWindowImpl->mbReallyVisible )
+ {
+ ImplCallResize();
+ if( mpWindowImpl->mpFrameData->maPaintIdle.IsActive() )
+ {
+ mpWindowImpl->mpFrameData->maPaintIdle.Stop();
+ mpWindowImpl->mpFrameData->maPaintIdle.Invoke( nullptr );
+ }
+ }
+}
+
+void Window::ImplInvalidateFrameRegion( const vcl::Region* pRegion, InvalidateFlags nFlags )
+{
+ // set PAINTCHILDREN for all parent windows till the first OverlapWindow
+ if ( !ImplIsOverlapWindow() )
+ {
+ vcl::Window* pTempWindow = this;
+ ImplPaintFlags nTranspPaint = IsPaintTransparent() ? ImplPaintFlags::Paint : ImplPaintFlags::NONE;
+ do
+ {
+ pTempWindow = pTempWindow->ImplGetParent();
+ if ( pTempWindow->mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintChildren )
+ break;
+ pTempWindow->mpWindowImpl->mnPaintFlags |= ImplPaintFlags::PaintChildren | nTranspPaint;
+ if( ! pTempWindow->IsPaintTransparent() )
+ nTranspPaint = ImplPaintFlags::NONE;
+ }
+ while ( !pTempWindow->ImplIsOverlapWindow() );
+ }
+
+ // set Paint-Flags
+ mpWindowImpl->mnPaintFlags |= ImplPaintFlags::Paint;
+ if ( nFlags & InvalidateFlags::Children )
+ mpWindowImpl->mnPaintFlags |= ImplPaintFlags::PaintAllChildren;
+ if ( !(nFlags & InvalidateFlags::NoErase) )
+ mpWindowImpl->mnPaintFlags |= ImplPaintFlags::Erase;
+
+ if ( !pRegion )
+ mpWindowImpl->mnPaintFlags |= ImplPaintFlags::PaintAll;
+ else if ( !(mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll) )
+ {
+ // if not everything has to be redrawn, add the region to it
+ mpWindowImpl->maInvalidateRegion.Union( *pRegion );
+ }
+
+ // Handle transparent windows correctly: invalidate must be done on the first opaque parent
+ if( ((IsPaintTransparent() && !(nFlags & InvalidateFlags::NoTransparent)) || (nFlags & InvalidateFlags::Transparent) )
+ && ImplGetParent() )
+ {
+ vcl::Window *pParent = ImplGetParent();
+ while( pParent && pParent->IsPaintTransparent() )
+ pParent = pParent->ImplGetParent();
+ if( pParent )
+ {
+ vcl::Region *pChildRegion;
+ if ( mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll )
+ // invalidate the whole child window region in the parent
+ pChildRegion = &ImplGetWinChildClipRegion();
+ else
+ // invalidate the same region in the parent that has to be repainted in the child
+ pChildRegion = &mpWindowImpl->maInvalidateRegion;
+
+ nFlags |= InvalidateFlags::Children; // paint should also be done on all children
+ nFlags &= ~InvalidateFlags::NoErase; // parent should paint and erase to create proper background
+ pParent->ImplInvalidateFrameRegion( pChildRegion, nFlags );
+ }
+ }
+
+ if ( !mpWindowImpl->mpFrameData->maPaintIdle.IsActive() )
+ mpWindowImpl->mpFrameData->maPaintIdle.Start();
+}
+
+void Window::ImplInvalidateOverlapFrameRegion( const vcl::Region& rRegion )
+{
+ vcl::Region aRegion = rRegion;
+
+ ImplClipBoundaries( aRegion, true, true );
+ if ( !aRegion.IsEmpty() )
+ ImplInvalidateFrameRegion( &aRegion, InvalidateFlags::Children );
+
+ // now we invalidate the overlapping windows
+ vcl::Window* pTempWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pTempWindow )
+ {
+ if ( pTempWindow->IsVisible() )
+ pTempWindow->ImplInvalidateOverlapFrameRegion( rRegion );
+
+ pTempWindow = pTempWindow->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplInvalidateParentFrameRegion( const vcl::Region& rRegion )
+{
+ if ( mpWindowImpl->mbOverlapWin )
+ mpWindowImpl->mpFrameWindow->ImplInvalidateOverlapFrameRegion( rRegion );
+ else
+ {
+ if( ImplGetParent() )
+ ImplGetParent()->ImplInvalidateFrameRegion( &rRegion, InvalidateFlags::Children );
+ }
+}
+
+void Window::ImplInvalidate( const vcl::Region* pRegion, InvalidateFlags nFlags )
+{
+ // check what has to be redrawn
+ bool bInvalidateAll = !pRegion;
+
+ // take Transparent-Invalidate into account
+ vcl::Window* pOpaqueWindow = this;
+ if ( (mpWindowImpl->mbPaintTransparent && !(nFlags & InvalidateFlags::NoTransparent)) || (nFlags & InvalidateFlags::Transparent) )
+ {
+ vcl::Window* pTempWindow = pOpaqueWindow->ImplGetParent();
+ while ( pTempWindow )
+ {
+ if ( !pTempWindow->IsPaintTransparent() )
+ {
+ pOpaqueWindow = pTempWindow;
+ nFlags |= InvalidateFlags::Children;
+ bInvalidateAll = false;
+ break;
+ }
+
+ if ( pTempWindow->ImplIsOverlapWindow() )
+ break;
+
+ pTempWindow = pTempWindow->ImplGetParent();
+ }
+ }
+
+ // assemble region
+ InvalidateFlags nOrgFlags = nFlags;
+ if ( !(nFlags & (InvalidateFlags::Children | InvalidateFlags::NoChildren)) )
+ {
+ if ( GetStyle() & WB_CLIPCHILDREN )
+ nFlags |= InvalidateFlags::NoChildren;
+ else
+ nFlags |= InvalidateFlags::Children;
+ }
+ if ( (nFlags & InvalidateFlags::NoChildren) && mpWindowImpl->mpFirstChild )
+ bInvalidateAll = false;
+ if ( bInvalidateAll )
+ ImplInvalidateFrameRegion( nullptr, nFlags );
+ else
+ {
+ vcl::Region aRegion( GetOutputRectPixel() );
+ if ( pRegion )
+ {
+ // RTL: remirror region before intersecting it
+ if ( GetOutDev()->ImplIsAntiparallel() )
+ {
+ const OutputDevice *pOutDev = GetOutDev();
+
+ vcl::Region aRgn( *pRegion );
+ pOutDev->ReMirror( aRgn );
+ aRegion.Intersect( aRgn );
+ }
+ else
+ aRegion.Intersect( *pRegion );
+ }
+ ImplClipBoundaries( aRegion, true, true );
+ if ( nFlags & InvalidateFlags::NoChildren )
+ {
+ nFlags &= ~InvalidateFlags::Children;
+ if ( !(nFlags & InvalidateFlags::NoClipChildren) )
+ {
+ if ( nOrgFlags & InvalidateFlags::NoChildren )
+ ImplClipAllChildren( aRegion );
+ else
+ {
+ if ( ImplClipChildren( aRegion ) )
+ nFlags |= InvalidateFlags::Children;
+ }
+ }
+ }
+ if ( !aRegion.IsEmpty() )
+ ImplInvalidateFrameRegion( &aRegion, nFlags ); // transparency is handled here, pOpaqueWindow not required
+ }
+
+ if ( nFlags & InvalidateFlags::Update )
+ pOpaqueWindow->PaintImmediately(); // start painting at the opaque parent
+}
+
+void Window::ImplMoveInvalidateRegion( const tools::Rectangle& rRect,
+ tools::Long nHorzScroll, tools::Long nVertScroll,
+ bool bChildren )
+{
+ if ( (mpWindowImpl->mnPaintFlags & (ImplPaintFlags::Paint | ImplPaintFlags::PaintAll)) == ImplPaintFlags::Paint )
+ {
+ vcl::Region aTempRegion = mpWindowImpl->maInvalidateRegion;
+ aTempRegion.Intersect( rRect );
+ aTempRegion.Move( nHorzScroll, nVertScroll );
+ mpWindowImpl->maInvalidateRegion.Union( aTempRegion );
+ }
+
+ if ( bChildren && (mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintChildren) )
+ {
+ vcl::Window* pWindow = mpWindowImpl->mpFirstChild;
+ while ( pWindow )
+ {
+ pWindow->ImplMoveInvalidateRegion( rRect, nHorzScroll, nVertScroll, true );
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+ }
+}
+
+void Window::ImplMoveAllInvalidateRegions( const tools::Rectangle& rRect,
+ tools::Long nHorzScroll, tools::Long nVertScroll,
+ bool bChildren )
+{
+ // also shift Paint-Region when paints need processing
+ ImplMoveInvalidateRegion( rRect, nHorzScroll, nVertScroll, bChildren );
+ // Paint-Region should be shifted, as drawn by the parents
+ if ( ImplIsOverlapWindow() )
+ return;
+
+ vcl::Region aPaintAllRegion;
+ vcl::Window* pPaintAllWindow = this;
+ do
+ {
+ pPaintAllWindow = pPaintAllWindow->ImplGetParent();
+ if ( pPaintAllWindow->mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAllChildren )
+ {
+ if ( pPaintAllWindow->mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll )
+ {
+ aPaintAllRegion.SetEmpty();
+ break;
+ }
+ else
+ aPaintAllRegion.Union( pPaintAllWindow->mpWindowImpl->maInvalidateRegion );
+ }
+ }
+ while ( !pPaintAllWindow->ImplIsOverlapWindow() );
+ if ( !aPaintAllRegion.IsEmpty() )
+ {
+ aPaintAllRegion.Move( nHorzScroll, nVertScroll );
+ InvalidateFlags nPaintFlags = InvalidateFlags::NONE;
+ if ( bChildren )
+ nPaintFlags |= InvalidateFlags::Children;
+ ImplInvalidateFrameRegion( &aPaintAllRegion, nPaintFlags );
+ }
+}
+
+void Window::ImplValidateFrameRegion( const vcl::Region* pRegion, ValidateFlags nFlags )
+{
+ if ( !pRegion )
+ mpWindowImpl->maInvalidateRegion.SetEmpty();
+ else
+ {
+ // when all child windows have to be drawn we need to invalidate them before doing so
+ if ( (mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAllChildren) && mpWindowImpl->mpFirstChild )
+ {
+ vcl::Region aChildRegion = mpWindowImpl->maInvalidateRegion;
+ if ( mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll )
+ {
+ aChildRegion = GetOutputRectPixel();
+ }
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->Invalidate( aChildRegion, InvalidateFlags::Children | InvalidateFlags::NoTransparent );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+ }
+ if ( mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll )
+ {
+ mpWindowImpl->maInvalidateRegion = GetOutputRectPixel();
+ }
+ mpWindowImpl->maInvalidateRegion.Exclude( *pRegion );
+ }
+ mpWindowImpl->mnPaintFlags &= ~ImplPaintFlags::PaintAll;
+
+ if ( nFlags & ValidateFlags::Children )
+ {
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->ImplValidateFrameRegion( pRegion, nFlags );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+ }
+}
+
+void Window::ImplValidate()
+{
+ // assemble region
+ bool bValidateAll = true;
+ ValidateFlags nFlags = ValidateFlags::NONE;
+ if ( GetStyle() & WB_CLIPCHILDREN )
+ nFlags |= ValidateFlags::NoChildren;
+ else
+ nFlags |= ValidateFlags::Children;
+ if ( (nFlags & ValidateFlags::NoChildren) && mpWindowImpl->mpFirstChild )
+ bValidateAll = false;
+ if ( bValidateAll )
+ ImplValidateFrameRegion( nullptr, nFlags );
+ else
+ {
+ vcl::Region aRegion( GetOutputRectPixel() );
+ ImplClipBoundaries( aRegion, true, true );
+ if ( nFlags & ValidateFlags::NoChildren )
+ {
+ nFlags &= ~ValidateFlags::Children;
+ if ( ImplClipChildren( aRegion ) )
+ nFlags |= ValidateFlags::Children;
+ }
+ if ( !aRegion.IsEmpty() )
+ ImplValidateFrameRegion( &aRegion, nFlags );
+ }
+}
+
+void Window::ImplUpdateAll()
+{
+ if ( !mpWindowImpl || !mpWindowImpl->mbReallyVisible )
+ return;
+
+ bool bFlush = false;
+ if ( mpWindowImpl->mpFrameWindow->mpWindowImpl->mbPaintFrame )
+ {
+ Point aPoint( 0, 0 );
+ vcl::Region aRegion( tools::Rectangle( aPoint, GetOutputSizePixel() ) );
+ ImplInvalidateOverlapFrameRegion( aRegion );
+ if ( mpWindowImpl->mbFrame || (mpWindowImpl->mpBorderWindow && mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame) )
+ bFlush = true;
+ }
+
+ // an update changes the OverlapWindow, such that for later paints
+ // not too much has to be drawn, if ALLCHILDREN etc. is set
+ vcl::Window* pWindow = ImplGetFirstOverlapWindow();
+ pWindow->ImplCallOverlapPaint();
+
+ if ( bFlush )
+ GetOutDev()->Flush();
+}
+
+void Window::PrePaint(vcl::RenderContext& /*rRenderContext*/)
+{
+}
+
+void Window::PostPaint(vcl::RenderContext& /*rRenderContext*/)
+{
+}
+
+void Window::Paint(vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& rRect)
+{
+ CallEventListeners(VclEventId::WindowPaint, const_cast<tools::Rectangle *>(&rRect));
+}
+
+void Window::SetPaintTransparent( bool bTransparent )
+{
+ // transparency is not useful for frames as the background would have to be provided by a different frame
+ if( bTransparent && mpWindowImpl->mbFrame )
+ return;
+
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->SetPaintTransparent( bTransparent );
+
+ mpWindowImpl->mbPaintTransparent = bTransparent;
+}
+
+void Window::SetWindowRegionPixel()
+{
+
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->SetWindowRegionPixel();
+ else if( mpWindowImpl->mbFrame )
+ {
+ mpWindowImpl->maWinRegion = vcl::Region(true);
+ mpWindowImpl->mbWinRegion = false;
+ mpWindowImpl->mpFrame->ResetClipRegion();
+ }
+ else
+ {
+ if ( mpWindowImpl->mbWinRegion )
+ {
+ mpWindowImpl->maWinRegion = vcl::Region(true);
+ mpWindowImpl->mbWinRegion = false;
+ ImplSetClipFlag();
+
+ if ( IsReallyVisible() )
+ {
+ vcl::Region aRegion( GetOutputRectPixel() );
+ ImplInvalidateParentFrameRegion( aRegion );
+ }
+ }
+ }
+}
+
+void Window::SetWindowRegionPixel( const vcl::Region& rRegion )
+{
+
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->SetWindowRegionPixel( rRegion );
+ else if( mpWindowImpl->mbFrame )
+ {
+ if( !rRegion.IsNull() )
+ {
+ mpWindowImpl->maWinRegion = rRegion;
+ mpWindowImpl->mbWinRegion = ! rRegion.IsEmpty();
+
+ if( mpWindowImpl->mbWinRegion )
+ {
+ // set/update ClipRegion
+ RectangleVector aRectangles;
+ mpWindowImpl->maWinRegion.GetRegionRectangles(aRectangles);
+ mpWindowImpl->mpFrame->BeginSetClipRegion(aRectangles.size());
+
+ for (auto const& rectangle : aRectangles)
+ {
+ mpWindowImpl->mpFrame->UnionClipRegion(
+ rectangle.Left(),
+ rectangle.Top(),
+ rectangle.GetWidth(), // orig nWidth was ((R - L) + 1), same as GetWidth does
+ rectangle.GetHeight()); // same for height
+ }
+
+ mpWindowImpl->mpFrame->EndSetClipRegion();
+ }
+ else
+ SetWindowRegionPixel();
+ }
+ else
+ SetWindowRegionPixel();
+ }
+ else
+ {
+ if ( rRegion.IsNull() )
+ {
+ if ( mpWindowImpl->mbWinRegion )
+ {
+ mpWindowImpl->maWinRegion = vcl::Region(true);
+ mpWindowImpl->mbWinRegion = false;
+ ImplSetClipFlag();
+ }
+ }
+ else
+ {
+ mpWindowImpl->maWinRegion = rRegion;
+ mpWindowImpl->mbWinRegion = true;
+ ImplSetClipFlag();
+ }
+
+ if ( IsReallyVisible() )
+ {
+ vcl::Region aRegion( GetOutputRectPixel() );
+ ImplInvalidateParentFrameRegion( aRegion );
+ }
+ }
+}
+
+vcl::Region Window::GetPaintRegion() const
+{
+
+ if ( mpWindowImpl->mpPaintRegion )
+ {
+ vcl::Region aRegion = *mpWindowImpl->mpPaintRegion;
+ aRegion.Move( -GetOutDev()->mnOutOffX, -GetOutDev()->mnOutOffY );
+ return PixelToLogic( aRegion );
+ }
+ else
+ {
+ vcl::Region aPaintRegion(true);
+ return aPaintRegion;
+ }
+}
+
+void Window::Invalidate( InvalidateFlags nFlags )
+{
+ if ( !comphelper::LibreOfficeKit::isActive() && (!GetOutDev()->IsDeviceOutputNecessary() || !GetOutDev()->mnOutWidth || !GetOutDev()->mnOutHeight) )
+ return;
+
+ ImplInvalidate( nullptr, nFlags );
+ LogicInvalidate(nullptr);
+}
+
+void Window::Invalidate( const tools::Rectangle& rRect, InvalidateFlags nFlags )
+{
+ if ( !comphelper::LibreOfficeKit::isActive() && (!GetOutDev()->IsDeviceOutputNecessary() || !GetOutDev()->mnOutWidth || !GetOutDev()->mnOutHeight) )
+ return;
+
+ OutputDevice *pOutDev = GetOutDev();
+ tools::Rectangle aRect = pOutDev->ImplLogicToDevicePixel( rRect );
+ if ( !aRect.IsEmpty() )
+ {
+ vcl::Region aRegion( aRect );
+ ImplInvalidate( &aRegion, nFlags );
+ tools::Rectangle aLogicRectangle(rRect);
+ LogicInvalidate(&aLogicRectangle);
+ }
+}
+
+void Window::Invalidate( const vcl::Region& rRegion, InvalidateFlags nFlags )
+{
+ if ( !comphelper::LibreOfficeKit::isActive() && (!GetOutDev()->IsDeviceOutputNecessary() || !GetOutDev()->mnOutWidth || !GetOutDev()->mnOutHeight) )
+ return;
+
+ if ( rRegion.IsNull() )
+ {
+ ImplInvalidate( nullptr, nFlags );
+ LogicInvalidate(nullptr);
+ }
+ else
+ {
+ vcl::Region aRegion = GetOutDev()->ImplPixelToDevicePixel( LogicToPixel( rRegion ) );
+ if ( !aRegion.IsEmpty() )
+ {
+ ImplInvalidate( &aRegion, nFlags );
+ tools::Rectangle aLogicRectangle = rRegion.GetBoundRect();
+ LogicInvalidate(&aLogicRectangle);
+ }
+ }
+}
+
+void Window::LogicInvalidate(const tools::Rectangle* pRectangle)
+{
+ if(pRectangle)
+ {
+ tools::Rectangle aRect = GetOutDev()->ImplLogicToDevicePixel( *pRectangle );
+ PixelInvalidate(&aRect);
+ }
+ else
+ PixelInvalidate(nullptr);
+}
+
+void Window::PixelInvalidate(const tools::Rectangle* pRectangle)
+{
+ if (comphelper::LibreOfficeKit::isDialogPainting() || !comphelper::LibreOfficeKit::isActive())
+ return;
+
+ Size aSize = GetSizePixel();
+ if (aSize.IsEmpty())
+ return;
+
+ if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
+ {
+ // In case we are routing the window, notify the client
+ std::vector<vcl::LOKPayloadItem> aPayload;
+ tools::Rectangle aRect(Point(0, 0), aSize);
+ if (pRectangle)
+ aRect = *pRectangle;
+
+ if (IsRTLEnabled() && GetOutDev() && !GetOutDev()->ImplIsAntiparallel())
+ GetOutDev()->ReMirror(aRect);
+
+ aPayload.emplace_back("rectangle", aRect.toString());
+
+ pNotifier->notifyWindow(GetLOKWindowId(), "invalidate", aPayload);
+ }
+ // Added for dialog items. Pass invalidation to the parent window.
+ else if (VclPtr<vcl::Window> pParent = GetParentWithLOKNotifier())
+ {
+ const tools::Rectangle aRect(Point(GetOutOffXPixel(), GetOutOffYPixel()), GetSizePixel());
+ pParent->PixelInvalidate(&aRect);
+ }
+}
+
+void Window::Validate()
+{
+ if ( !comphelper::LibreOfficeKit::isActive() && (!GetOutDev()->IsDeviceOutputNecessary() || !GetOutDev()->mnOutWidth || !GetOutDev()->mnOutHeight) )
+ return;
+
+ ImplValidate();
+}
+
+bool Window::HasPaintEvent() const
+{
+
+ if ( !mpWindowImpl->mbReallyVisible )
+ return false;
+
+ if ( mpWindowImpl->mpFrameWindow->mpWindowImpl->mbPaintFrame )
+ return true;
+
+ if ( mpWindowImpl->mnPaintFlags & ImplPaintFlags::Paint )
+ return true;
+
+ if ( !ImplIsOverlapWindow() )
+ {
+ const vcl::Window* pTempWindow = this;
+ do
+ {
+ pTempWindow = pTempWindow->ImplGetParent();
+ if ( pTempWindow->mpWindowImpl->mnPaintFlags & (ImplPaintFlags::PaintChildren | ImplPaintFlags::PaintAllChildren) )
+ return true;
+ }
+ while ( !pTempWindow->ImplIsOverlapWindow() );
+ }
+
+ return false;
+}
+
+void Window::PaintImmediately()
+{
+ if (!mpWindowImpl)
+ return;
+
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ mpWindowImpl->mpBorderWindow->PaintImmediately();
+ return;
+ }
+
+ if ( !mpWindowImpl->mbReallyVisible )
+ return;
+
+ bool bFlush = false;
+ if ( mpWindowImpl->mpFrameWindow->mpWindowImpl->mbPaintFrame )
+ {
+ Point aPoint( 0, 0 );
+ vcl::Region aRegion( tools::Rectangle( aPoint, GetOutputSizePixel() ) );
+ ImplInvalidateOverlapFrameRegion( aRegion );
+ if ( mpWindowImpl->mbFrame || (mpWindowImpl->mpBorderWindow && mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame) )
+ bFlush = true;
+ }
+
+ // First we should skip all windows which are Paint-Transparent
+ vcl::Window* pUpdateWindow = this;
+ vcl::Window* pWindow = pUpdateWindow;
+ while ( !pWindow->ImplIsOverlapWindow() )
+ {
+ if ( !pWindow->mpWindowImpl->mbPaintTransparent )
+ {
+ pUpdateWindow = pWindow;
+ break;
+ }
+ pWindow = pWindow->ImplGetParent();
+ }
+ // In order to limit drawing, an update only draws the window which
+ // has PAINTALLCHILDREN set
+ pWindow = pUpdateWindow;
+ do
+ {
+ if ( pWindow->mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAllChildren )
+ pUpdateWindow = pWindow;
+ if ( pWindow->ImplIsOverlapWindow() )
+ break;
+ pWindow = pWindow->ImplGetParent();
+ }
+ while ( pWindow );
+
+ // if there is something to paint, trigger a Paint
+ if ( pUpdateWindow->mpWindowImpl->mnPaintFlags & (ImplPaintFlags::Paint | ImplPaintFlags::PaintChildren) )
+ {
+ VclPtr<vcl::Window> xWindow(this);
+
+ // trigger an update also for system windows on top of us,
+ // otherwise holes would remain
+ vcl::Window* pUpdateOverlapWindow = ImplGetFirstOverlapWindow();
+ if (pUpdateOverlapWindow->mpWindowImpl)
+ pUpdateOverlapWindow = pUpdateOverlapWindow->mpWindowImpl->mpFirstOverlap;
+ else
+ pUpdateOverlapWindow = nullptr;
+ while ( pUpdateOverlapWindow )
+ {
+ pUpdateOverlapWindow->PaintImmediately();
+ pUpdateOverlapWindow = pUpdateOverlapWindow->mpWindowImpl->mpNext;
+ }
+
+ pUpdateWindow->ImplCallPaint(nullptr, pUpdateWindow->mpWindowImpl->mnPaintFlags);
+
+ if (comphelper::LibreOfficeKit::isActive() && pUpdateWindow->GetParentDialog())
+ pUpdateWindow->LogicInvalidate(nullptr);
+
+ if (xWindow->isDisposed())
+ return;
+
+ bFlush = true;
+ }
+
+ if ( bFlush )
+ GetOutDev()->Flush();
+}
+
+void Window::ImplPaintToDevice( OutputDevice* i_pTargetOutDev, const Point& i_rPos )
+{
+ // Special drawing when called through LOKit
+ // TODO: Move to its own method
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ VclPtrInstance<VirtualDevice> pDevice(*i_pTargetOutDev);
+ pDevice->EnableRTL(IsRTLEnabled());
+
+ Size aSize(GetOutputSizePixel());
+ pDevice->SetOutputSizePixel(aSize);
+
+ vcl::Font aCopyFont = GetFont();
+ pDevice->SetFont(aCopyFont);
+
+ pDevice->SetTextColor(GetTextColor());
+ if (GetOutDev()->IsLineColor())
+ pDevice->SetLineColor(GetOutDev()->GetLineColor());
+ else
+ pDevice->SetLineColor();
+
+ if (GetOutDev()->IsFillColor())
+ pDevice->SetFillColor(GetOutDev()->GetFillColor());
+ else
+ pDevice->SetFillColor();
+
+ if (IsTextLineColor())
+ pDevice->SetTextLineColor(GetTextLineColor());
+ else
+ pDevice->SetTextLineColor();
+
+ if (IsOverlineColor())
+ pDevice->SetOverlineColor(GetOverlineColor());
+ else
+ pDevice->SetOverlineColor();
+
+ if (IsTextFillColor())
+ pDevice->SetTextFillColor(GetTextFillColor());
+ else
+ pDevice->SetTextFillColor();
+
+ pDevice->SetTextAlign(GetTextAlign());
+ pDevice->SetRasterOp(GetOutDev()->GetRasterOp());
+
+ tools::Rectangle aPaintRect(Point(), GetOutputSizePixel());
+
+ vcl::Region aClipRegion(GetOutDev()->GetClipRegion());
+ pDevice->SetClipRegion();
+ aClipRegion.Intersect(aPaintRect);
+ pDevice->SetClipRegion(aClipRegion);
+
+ if (!IsPaintTransparent() && IsBackground() && ! (GetParentClipMode() & ParentClipMode::NoClip))
+ Erase(*pDevice);
+
+ pDevice->SetMapMode(GetMapMode());
+
+ Paint(*pDevice, tools::Rectangle(Point(), GetOutputSizePixel()));
+
+ i_pTargetOutDev->DrawOutDev(i_rPos, aSize, Point(), pDevice->PixelToLogic(aSize), *pDevice);
+
+ bool bHasMirroredGraphics = pDevice->HasMirroredGraphics();
+
+ // get rid of virtual device now so they don't pile up during recursive calls
+ pDevice.disposeAndClear();
+
+
+ for( vcl::Window* pChild = mpWindowImpl->mpFirstChild; pChild; pChild = pChild->mpWindowImpl->mpNext )
+ {
+ if( pChild->mpWindowImpl->mpFrame == mpWindowImpl->mpFrame && pChild->IsVisible() )
+ {
+ tools::Long nDeltaX = pChild->GetOutDev()->mnOutOffX - GetOutDev()->mnOutOffX;
+ if( bHasMirroredGraphics )
+ nDeltaX = GetOutDev()->mnOutWidth - nDeltaX - pChild->GetOutDev()->mnOutWidth;
+
+ tools::Long nDeltaY = pChild->GetOutOffYPixel() - GetOutOffYPixel();
+
+ Point aPos( i_rPos );
+ aPos += Point(nDeltaX, nDeltaY);
+
+ pChild->ImplPaintToDevice( i_pTargetOutDev, aPos );
+ }
+ }
+ return;
+ }
+
+
+ bool bRVisible = mpWindowImpl->mbReallyVisible;
+ mpWindowImpl->mbReallyVisible = mpWindowImpl->mbVisible;
+ bool bDevOutput = GetOutDev()->mbDevOutput;
+ GetOutDev()->mbDevOutput = true;
+
+ const OutputDevice *pOutDev = GetOutDev();
+ tools::Long nOldDPIX = pOutDev->GetDPIX();
+ tools::Long nOldDPIY = pOutDev->GetDPIY();
+ GetOutDev()->mnDPIX = i_pTargetOutDev->GetDPIX();
+ GetOutDev()->mnDPIY = i_pTargetOutDev->GetDPIY();
+ bool bOutput = GetOutDev()->IsOutputEnabled();
+ GetOutDev()->EnableOutput();
+
+ SAL_WARN_IF( GetMapMode().GetMapUnit() != MapUnit::MapPixel, "vcl.window", "MapMode must be PIXEL based" );
+ if ( GetMapMode().GetMapUnit() != MapUnit::MapPixel )
+ return;
+
+ // preserve graphicsstate
+ GetOutDev()->Push();
+ vcl::Region aClipRegion( GetOutDev()->GetClipRegion() );
+ GetOutDev()->SetClipRegion();
+
+ GDIMetaFile* pOldMtf = GetOutDev()->GetConnectMetaFile();
+ GDIMetaFile aMtf;
+ GetOutDev()->SetConnectMetaFile( &aMtf );
+
+ // put a push action to metafile
+ GetOutDev()->Push();
+ // copy graphics state to metafile
+ vcl::Font aCopyFont = GetFont();
+ if( nOldDPIX != GetOutDev()->mnDPIX || nOldDPIY != GetOutDev()->mnDPIY )
+ {
+ aCopyFont.SetFontHeight( aCopyFont.GetFontHeight() * GetOutDev()->mnDPIY / nOldDPIY );
+ aCopyFont.SetAverageFontWidth( aCopyFont.GetAverageFontWidth() * GetOutDev()->mnDPIX / nOldDPIX );
+ }
+ SetFont( aCopyFont );
+ SetTextColor( GetTextColor() );
+ if( GetOutDev()->IsLineColor() )
+ GetOutDev()->SetLineColor( GetOutDev()->GetLineColor() );
+ else
+ GetOutDev()->SetLineColor();
+ if( GetOutDev()->IsFillColor() )
+ GetOutDev()->SetFillColor( GetOutDev()->GetFillColor() );
+ else
+ GetOutDev()->SetFillColor();
+ if( IsTextLineColor() )
+ SetTextLineColor( GetTextLineColor() );
+ else
+ SetTextLineColor();
+ if( IsOverlineColor() )
+ SetOverlineColor( GetOverlineColor() );
+ else
+ SetOverlineColor();
+ if( IsTextFillColor() )
+ SetTextFillColor( GetTextFillColor() );
+ else
+ SetTextFillColor();
+ SetTextAlign( GetTextAlign() );
+ GetOutDev()->SetRasterOp( GetOutDev()->GetRasterOp() );
+ if( GetOutDev()->IsRefPoint() )
+ GetOutDev()->SetRefPoint( GetOutDev()->GetRefPoint() );
+ else
+ GetOutDev()->SetRefPoint();
+ GetOutDev()->SetLayoutMode( GetOutDev()->GetLayoutMode() );
+
+ GetOutDev()->SetDigitLanguage( GetOutDev()->GetDigitLanguage() );
+ tools::Rectangle aPaintRect(Point(0, 0), GetOutputSizePixel());
+ aClipRegion.Intersect( aPaintRect );
+ GetOutDev()->SetClipRegion( aClipRegion );
+
+ // do the actual paint
+
+ // background
+ if( ! IsPaintTransparent() && IsBackground() && ! (GetParentClipMode() & ParentClipMode::NoClip ) )
+ {
+ Erase(*GetOutDev());
+ }
+ // foreground
+ Paint(*GetOutDev(), aPaintRect);
+ // put a pop action to metafile
+ GetOutDev()->Pop();
+
+ GetOutDev()->SetConnectMetaFile( pOldMtf );
+ GetOutDev()->EnableOutput( bOutput );
+ mpWindowImpl->mbReallyVisible = bRVisible;
+
+ // paint metafile to VDev
+ VclPtrInstance<VirtualDevice> pMaskedDevice(*i_pTargetOutDev,
+ DeviceFormat::DEFAULT,
+ DeviceFormat::DEFAULT);
+
+ pMaskedDevice->SetOutputSizePixel( GetOutputSizePixel() );
+ pMaskedDevice->EnableRTL( IsRTLEnabled() );
+ aMtf.WindStart();
+ aMtf.Play(*pMaskedDevice);
+ BitmapEx aBmpEx( pMaskedDevice->GetBitmapEx( Point( 0, 0 ), aPaintRect.GetSize() ) );
+ i_pTargetOutDev->DrawBitmapEx( i_rPos, aBmpEx );
+ // get rid of virtual device now so they don't pile up during recursive calls
+ pMaskedDevice.disposeAndClear();
+
+ for( vcl::Window* pChild = mpWindowImpl->mpFirstChild; pChild; pChild = pChild->mpWindowImpl->mpNext )
+ {
+ if( pChild->mpWindowImpl->mpFrame == mpWindowImpl->mpFrame && pChild->IsVisible() )
+ {
+ tools::Long nDeltaX = pChild->GetOutDev()->mnOutOffX - GetOutDev()->mnOutOffX;
+
+ if( pOutDev->HasMirroredGraphics() )
+ nDeltaX = GetOutDev()->mnOutWidth - nDeltaX - pChild->GetOutDev()->mnOutWidth;
+ tools::Long nDeltaY = pChild->GetOutOffYPixel() - GetOutOffYPixel();
+ Point aPos( i_rPos );
+ Point aDelta( nDeltaX, nDeltaY );
+ aPos += aDelta;
+ pChild->ImplPaintToDevice( i_pTargetOutDev, aPos );
+ }
+ }
+
+ // restore graphics state
+ GetOutDev()->Pop();
+
+ GetOutDev()->EnableOutput( bOutput );
+ mpWindowImpl->mbReallyVisible = bRVisible;
+ GetOutDev()->mbDevOutput = bDevOutput;
+ GetOutDev()->mnDPIX = nOldDPIX;
+ GetOutDev()->mnDPIY = nOldDPIY;
+}
+
+void Window::PaintToDevice(OutputDevice* pDev, const Point& rPos)
+{
+ if( !mpWindowImpl )
+ return;
+
+ SAL_WARN_IF( pDev->HasMirroredGraphics(), "vcl.window", "PaintToDevice to mirroring graphics" );
+ SAL_WARN_IF( pDev->IsRTLEnabled(), "vcl.window", "PaintToDevice to mirroring device" );
+
+ vcl::Window* pRealParent = nullptr;
+ if( ! mpWindowImpl->mbVisible )
+ {
+ vcl::Window* pTempParent = ImplGetDefaultWindow();
+ pTempParent->EnableChildTransparentMode();
+ pRealParent = GetParent();
+ SetParent( pTempParent );
+ // trigger correct visibility flags for children
+ Show();
+ Hide();
+ }
+
+ bool bVisible = mpWindowImpl->mbVisible;
+ mpWindowImpl->mbVisible = true;
+
+ if( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->ImplPaintToDevice( pDev, rPos );
+ else
+ ImplPaintToDevice( pDev, rPos );
+
+ mpWindowImpl->mbVisible = bVisible;
+
+ if( pRealParent )
+ SetParent( pRealParent );
+}
+
+void Window::Erase(vcl::RenderContext& rRenderContext)
+{
+ if (!GetOutDev()->IsDeviceOutputNecessary() || GetOutDev()->ImplIsRecordLayout())
+ return;
+
+ bool bNativeOK = false;
+
+ ControlPart aCtrlPart = ImplGetWindowImpl()->mnNativeBackground;
+
+ if (aCtrlPart == ControlPart::Entire && IsControlBackground())
+ {
+ // nothing to do here; background is drawn in corresponding drawNativeControl implementation
+ bNativeOK = true;
+ }
+ else if (aCtrlPart != ControlPart::NONE && ! IsControlBackground())
+ {
+ tools::Rectangle aCtrlRegion(Point(), GetOutputSizePixel());
+ ControlState nState = ControlState::NONE;
+
+ if (IsEnabled())
+ nState |= ControlState::ENABLED;
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::WindowBackground, aCtrlPart, aCtrlRegion,
+ nState, ImplControlValue(), OUString());
+ }
+
+ if (GetOutDev()->mbBackground && !bNativeOK)
+ {
+ RasterOp eRasterOp = GetOutDev()->GetRasterOp();
+ if (eRasterOp != RasterOp::OverPaint)
+ GetOutDev()->SetRasterOp(RasterOp::OverPaint);
+ rRenderContext.DrawWallpaper(0, 0, GetOutDev()->mnOutWidth, GetOutDev()->mnOutHeight, GetOutDev()->maBackground);
+ if (eRasterOp != RasterOp::OverPaint)
+ rRenderContext.SetRasterOp(eRasterOp);
+ }
+
+ if (GetOutDev()->mpAlphaVDev)
+ GetOutDev()->mpAlphaVDev->Erase();
+}
+
+void Window::ImplScroll( const tools::Rectangle& rRect,
+ tools::Long nHorzScroll, tools::Long nVertScroll, ScrollFlags nFlags )
+{
+ if ( !GetOutDev()->IsDeviceOutputNecessary() )
+ return;
+
+ nHorzScroll = GetOutDev()->ImplLogicWidthToDevicePixel( nHorzScroll );
+ nVertScroll = GetOutDev()->ImplLogicHeightToDevicePixel( nVertScroll );
+
+ if ( !nHorzScroll && !nVertScroll )
+ return;
+
+ // There will be no CopyArea() call below, so invalidate the whole visible
+ // area, not only the smaller one that was just scrolled in.
+ // Do this when we have a double buffer anyway, or (tdf#152094) the device has a map mode enabled which
+ // makes the conversion to pixel inaccurate
+ const bool bCopyExistingAreaAndElideInvalidate = !SupportsDoubleBuffering() && !GetOutDev()->IsMapModeEnabled();
+
+ if ( mpWindowImpl->mpCursor )
+ mpWindowImpl->mpCursor->ImplSuspend();
+
+ ScrollFlags nOrgFlags = nFlags;
+ if ( !(nFlags & (ScrollFlags::Children | ScrollFlags::NoChildren)) )
+ {
+ if ( GetStyle() & WB_CLIPCHILDREN )
+ nFlags |= ScrollFlags::NoChildren;
+ else
+ nFlags |= ScrollFlags::Children;
+ }
+
+ vcl::Region aInvalidateRegion;
+ bool bScrollChildren(nFlags & ScrollFlags::Children);
+
+ if ( !mpWindowImpl->mpFirstChild )
+ bScrollChildren = false;
+
+ OutputDevice *pOutDev = GetOutDev();
+
+ // RTL: check if this window requires special action
+ bool bReMirror = GetOutDev()->ImplIsAntiparallel();
+
+ tools::Rectangle aRectMirror( rRect );
+ if( bReMirror )
+ {
+ // make sure the invalidate region of this window is
+ // computed in the same coordinate space as the one from the overlap windows
+ pOutDev->ReMirror( aRectMirror );
+ }
+
+ // adapt paint areas
+ ImplMoveAllInvalidateRegions( aRectMirror, nHorzScroll, nVertScroll, bScrollChildren );
+
+ ImplCalcOverlapRegion( aRectMirror, aInvalidateRegion, !bScrollChildren, false );
+
+ // if the scrolling on the device is performed in the opposite direction
+ // then move the overlaps in that direction to compute the invalidate region
+ // on the correct side, i.e., revert nHorzScroll
+ if (!aInvalidateRegion.IsEmpty())
+ {
+ aInvalidateRegion.Move(bReMirror ? -nHorzScroll : nHorzScroll, nVertScroll);
+ }
+
+ tools::Rectangle aDestRect(aRectMirror);
+ aDestRect.Move(bReMirror ? -nHorzScroll : nHorzScroll, nVertScroll);
+ vcl::Region aWinInvalidateRegion(aRectMirror);
+ if (bCopyExistingAreaAndElideInvalidate)
+ aWinInvalidateRegion.Exclude(aDestRect);
+
+ aInvalidateRegion.Union(aWinInvalidateRegion);
+
+ vcl::Region aRegion( GetOutputRectPixel() );
+ if ( nFlags & ScrollFlags::Clip )
+ aRegion.Intersect( rRect );
+ if ( mpWindowImpl->mbWinRegion )
+ aRegion.Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) );
+
+ aRegion.Exclude( aInvalidateRegion );
+
+ ImplClipBoundaries( aRegion, false, true );
+ if ( !bScrollChildren )
+ {
+ if ( nOrgFlags & ScrollFlags::NoChildren )
+ ImplClipAllChildren( aRegion );
+ else
+ ImplClipChildren( aRegion );
+ }
+ if ( GetOutDev()->mbClipRegion && (nFlags & ScrollFlags::UseClipRegion) )
+ aRegion.Intersect( GetOutDev()->maRegion );
+ if ( !aRegion.IsEmpty() )
+ {
+ if ( mpWindowImpl->mpWinData )
+ {
+ if ( mpWindowImpl->mbFocusVisible )
+ ImplInvertFocus( *mpWindowImpl->mpWinData->mpFocusRect );
+ if ( mpWindowImpl->mbTrackVisible && (mpWindowImpl->mpWinData->mnTrackFlags & ShowTrackFlags::TrackWindow) )
+ InvertTracking( *mpWindowImpl->mpWinData->mpTrackRect, mpWindowImpl->mpWinData->mnTrackFlags );
+ }
+#ifndef IOS
+ // This seems completely unnecessary with tiled rendering, and
+ // causes the "AquaSalGraphics::copyArea() for non-layered
+ // graphics" message. Presumably we should bypass this on all
+ // platforms when dealing with a "window" that uses tiled
+ // rendering at the moment. Unclear how to figure that out,
+ // though. Also unclear whether we actually could just not
+ // create a "frame window", whatever that exactly is, in the
+ // tiled rendering case, or at least for platforms where tiles
+ // rendering is all there is.
+
+ SalGraphics* pGraphics = ImplGetFrameGraphics();
+ // The invalidation area contains the area what would be copied here,
+ // so avoid copying in case of double buffering.
+ if (pGraphics && bCopyExistingAreaAndElideInvalidate)
+ {
+ if( bReMirror )
+ {
+ pOutDev->ReMirror( aRegion );
+ }
+
+ pOutDev->SelectClipRegion( aRegion, pGraphics );
+ pGraphics->CopyArea( rRect.Left()+nHorzScroll, rRect.Top()+nVertScroll,
+ rRect.Left(), rRect.Top(),
+ rRect.GetWidth(), rRect.GetHeight(),
+ *GetOutDev() );
+ }
+#endif
+ if ( mpWindowImpl->mpWinData )
+ {
+ if ( mpWindowImpl->mbFocusVisible )
+ ImplInvertFocus( *mpWindowImpl->mpWinData->mpFocusRect );
+ if ( mpWindowImpl->mbTrackVisible && (mpWindowImpl->mpWinData->mnTrackFlags & ShowTrackFlags::TrackWindow) )
+ InvertTracking( *mpWindowImpl->mpWinData->mpTrackRect, mpWindowImpl->mpWinData->mnTrackFlags );
+ }
+ }
+
+ if ( !aInvalidateRegion.IsEmpty() )
+ {
+ // RTL: the invalidate region for this windows is already computed in frame coordinates
+ // so it has to be re-mirrored before calling the Paint-handler
+ mpWindowImpl->mnPaintFlags |= ImplPaintFlags::CheckRtl;
+
+ if ( !bScrollChildren )
+ {
+ if ( nOrgFlags & ScrollFlags::NoChildren )
+ ImplClipAllChildren( aInvalidateRegion );
+ else
+ ImplClipChildren( aInvalidateRegion );
+ }
+ ImplInvalidateFrameRegion( &aInvalidateRegion, InvalidateFlags::Children );
+ }
+
+ if ( bScrollChildren )
+ {
+ vcl::Window* pWindow = mpWindowImpl->mpFirstChild;
+ while ( pWindow )
+ {
+ Point aPos = pWindow->GetPosPixel();
+ aPos += Point( nHorzScroll, nVertScroll );
+ pWindow->SetPosPixel( aPos );
+
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+ }
+
+ if ( nFlags & ScrollFlags::Update )
+ PaintImmediately();
+
+ if ( mpWindowImpl->mpCursor )
+ mpWindowImpl->mpCursor->ImplResume();
+}
+
+} /* namespace vcl */
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/printdlg.cxx b/vcl/source/window/printdlg.cxx
new file mode 100644
index 000000000..c50bd3ae3
--- /dev/null
+++ b/vcl/source/window/printdlg.cxx
@@ -0,0 +1,2267 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <o3tl/safeint.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <rtl/ustrbuf.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <officecfg/Office/Common.hxx>
+
+#include <vcl/QueueInfo.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/help.hxx>
+#include <vcl/naturalsort.hxx>
+#include <vcl/print.hxx>
+#include <vcl/printer/Options.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/wall.hxx>
+#include <vcl/weldutils.hxx>
+#include <vcl/windowstate.hxx>
+
+#include <bitmaps.hlst>
+#include <configsettings.hxx>
+#include <printdlg.hxx>
+#include <strings.hrc>
+#include <svdata.hxx>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+
+using namespace vcl;
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::container;
+using namespace com::sun::star::beans;
+
+enum
+{
+ ORIENTATION_AUTOMATIC,
+ ORIENTATION_PORTRAIT,
+ ORIENTATION_LANDSCAPE
+};
+
+namespace {
+ bool lcl_ListBoxCompare( const OUString& rStr1, const OUString& rStr2 )
+ {
+ return vcl::NaturalSortCompare( rStr1, rStr2 ) < 0;
+ }
+}
+
+PrintDialog::PrintPreviewWindow::PrintPreviewWindow(PrintDialog* pDialog)
+ : mpDialog(pDialog)
+ , maOrigSize( 10, 10 )
+ , mnDPIX(Application::GetDefaultDevice()->GetDPIX())
+ , mnDPIY(Application::GetDefaultDevice()->GetDPIY())
+ , mbGreyscale( false )
+{
+}
+
+PrintDialog::PrintPreviewWindow::~PrintPreviewWindow()
+{
+}
+
+void PrintDialog::PrintPreviewWindow::Resize()
+{
+ Size aNewSize(GetOutputSizePixel());
+ tools::Long nTextHeight = GetDrawingArea()->get_text_height();
+ // leave small space for decoration
+ aNewSize.AdjustWidth( -(nTextHeight + 2) );
+ aNewSize.AdjustHeight( -(nTextHeight + 2) );
+ Size aScaledSize;
+ double fScale = 1.0;
+
+ // #i106435# catch corner case of Size(0,0)
+ Size aOrigSize( maOrigSize );
+ if( aOrigSize.Width() < 1 )
+ aOrigSize.setWidth( aNewSize.Width() );
+ if( aOrigSize.Height() < 1 )
+ aOrigSize.setHeight( aNewSize.Height() );
+ if( aOrigSize.Width() > aOrigSize.Height() )
+ {
+ aScaledSize = Size( aNewSize.Width(), aNewSize.Width() * aOrigSize.Height() / aOrigSize.Width() );
+ if( aScaledSize.Height() > aNewSize.Height() )
+ fScale = double(aNewSize.Height())/double(aScaledSize.Height());
+ }
+ else
+ {
+ aScaledSize = Size( aNewSize.Height() * aOrigSize.Width() / aOrigSize.Height(), aNewSize.Height() );
+ if( aScaledSize.Width() > aNewSize.Width() )
+ fScale = double(aNewSize.Width())/double(aScaledSize.Width());
+ }
+ aScaledSize.setWidth( tools::Long(aScaledSize.Width()*fScale) );
+ aScaledSize.setHeight( tools::Long(aScaledSize.Height()*fScale) );
+
+ maPreviewSize = aScaledSize;
+
+ // check and evtl. recreate preview bitmap
+ preparePreviewBitmap();
+}
+
+void PrintDialog::PrintPreviewWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ rRenderContext.Push();
+ weld::SetPointFont(rRenderContext, rRenderContext.GetSettings().GetStyleSettings().GetLabelFont());
+
+ rRenderContext.SetBackground(Wallpaper(Application::GetSettings().GetStyleSettings().GetDialogColor()));
+ rRenderContext.Erase();
+
+ auto nTextHeight = rRenderContext.GetTextHeight();
+ Size aSize(GetOutputSizePixel());
+ Point aOffset((aSize.Width() - maPreviewSize.Width() + nTextHeight) / 2,
+ (aSize.Height() - maPreviewSize.Height() + nTextHeight) / 2);
+
+ // horizontal line
+ {
+ auto nWidth = rRenderContext.GetTextWidth(maHorzText);
+
+ auto nStart = aOffset.X() + (maPreviewSize.Width() - nWidth) / 2;
+ rRenderContext.DrawText(Point(nStart, aOffset.Y() - nTextHeight), maHorzText, 0, maHorzText.getLength());
+
+ DecorationView aDecoView(&rRenderContext);
+ auto nTop = aOffset.Y() - (nTextHeight / 2);
+ aDecoView.DrawSeparator(Point(aOffset.X(), nTop), Point(nStart - 2, nTop), false);
+ aDecoView.DrawSeparator(Point(nStart + nWidth + 2, nTop), Point(aOffset.X() + maPreviewSize.Width(), nTop), false);
+ }
+
+ // vertical line
+ {
+ rRenderContext.Push(PushFlags::FONT);
+ vcl::Font aFont(rRenderContext.GetFont());
+ aFont.SetOrientation(900_deg10);
+ rRenderContext.SetFont(aFont);
+
+ auto nLeft = aOffset.X() - nTextHeight;
+
+ auto nWidth = rRenderContext.GetTextWidth(maVertText);
+ auto nStart = aOffset.Y() + (maPreviewSize.Height() + nWidth) / 2;
+
+ rRenderContext.DrawText(Point(nLeft, nStart), maVertText, 0, maVertText.getLength());
+
+ DecorationView aDecoView(&rRenderContext);
+ nLeft = aOffset.X() - (nTextHeight / 2);
+ aDecoView.DrawSeparator(Point(nLeft, aOffset.Y()), Point(nLeft, nStart - nWidth - 2), true);
+ aDecoView.DrawSeparator(Point(nLeft, nStart + 2), Point(nLeft, aOffset.Y() + maPreviewSize.Height()), true);
+
+ rRenderContext.Pop();
+ }
+
+ if (!maReplacementString.isEmpty())
+ {
+ // replacement is active
+ tools::Rectangle aTextRect(aOffset + Point(2, 2), Size(maPreviewSize.Width() - 4, maPreviewSize.Height() - 4));
+ rRenderContext.DrawText(aTextRect, maReplacementString,
+ DrawTextFlags::Center | DrawTextFlags::VCenter |
+ DrawTextFlags::WordBreak | DrawTextFlags::MultiLine);
+ }
+ else
+ {
+ BitmapEx aPreviewBitmap(maPreviewBitmap);
+
+ // This explicit force-to-scale allows us to get the
+ // mentioned best quality here. Unfortunately this is
+ // currently not sure when using just ::DrawBitmap with
+ // a defined size or ::DrawOutDev
+ aPreviewBitmap.Scale(maPreviewSize, BmpScaleFlag::BestQuality);
+ rRenderContext.DrawBitmapEx(aOffset, aPreviewBitmap);
+ }
+
+ tools::Rectangle aFrameRect(aOffset + Point(-1, -1), Size(maPreviewSize.Width() + 2, maPreviewSize.Height() + 2));
+ DecorationView aDecorationView(&rRenderContext);
+ aDecorationView.DrawFrame(aFrameRect, DrawFrameStyle::Group);
+
+ rRenderContext.Pop();
+}
+
+bool PrintDialog::PrintPreviewWindow::Command( const CommandEvent& rEvt )
+{
+ if( rEvt.GetCommand() == CommandEventId::Wheel )
+ {
+ const CommandWheelData* pWheelData = rEvt.GetWheelData();
+ if(pWheelData->GetDelta() > 0)
+ mpDialog->previewForward();
+ else if (pWheelData->GetDelta() < 0)
+ mpDialog->previewBackward();
+ return true;
+ }
+ return CustomWidgetController::Command(rEvt);
+}
+
+void PrintDialog::PrintPreviewWindow::setPreview( const GDIMetaFile& i_rNewPreview,
+ const Size& i_rOrigSize,
+ std::u16string_view i_rPaperName,
+ const OUString& i_rReplacement,
+ sal_Int32 i_nDPIX,
+ sal_Int32 i_nDPIY,
+ bool i_bGreyscale
+ )
+{
+ maMtf = i_rNewPreview;
+ mnDPIX = i_nDPIX;
+ mnDPIY = i_nDPIY;
+ maOrigSize = i_rOrigSize;
+ maReplacementString = i_rReplacement;
+ mbGreyscale = i_bGreyscale;
+
+ // use correct measurements
+ const LocaleDataWrapper& rLocWrap(Application::GetSettings().GetLocaleDataWrapper());
+ o3tl::Length eUnit = o3tl::Length::mm;
+ int nDigits = 0;
+ if( rLocWrap.getMeasurementSystemEnum() == MeasurementSystem::US )
+ {
+ eUnit = o3tl::Length::in100;
+ nDigits = 2;
+ }
+ Size aLogicPaperSize(o3tl::convert(i_rOrigSize, o3tl::Length::mm100, eUnit));
+ OUString aNumText( rLocWrap.getNum( aLogicPaperSize.Width(), nDigits ) );
+ OUStringBuffer aBuf;
+ aBuf.append( aNumText + " " );
+ aBuf.appendAscii( eUnit == o3tl::Length::mm ? "mm" : "in" );
+ if( !i_rPaperName.empty() )
+ {
+ aBuf.append( " (" );
+ aBuf.append( i_rPaperName );
+ aBuf.append( ')' );
+ }
+ maHorzText = aBuf.makeStringAndClear();
+
+ aNumText = rLocWrap.getNum( aLogicPaperSize.Height(), nDigits );
+ aBuf.append( aNumText + " " );
+ aBuf.appendAscii( eUnit == o3tl::Length::mm ? "mm" : "in" );
+ maVertText = aBuf.makeStringAndClear();
+
+ // We have a new Metafile and evtl. a new page, so we need to reset
+ // the PreviewBitmap to force new creation
+ maPreviewBitmap = Bitmap();
+
+ // sets/calculates e.g. maPreviewSize
+ // also triggers preparePreviewBitmap()
+ Resize();
+
+ Invalidate();
+}
+
+void PrintDialog::PrintPreviewWindow::preparePreviewBitmap()
+{
+ if(maPreviewSize.IsEmpty())
+ {
+ // not yet fully initialized, no need to prepare anything
+ return;
+ }
+
+ // define an allowed number of pixels, also see
+ // defaults for primitive renderers and similar. This
+ // might be centralized and made dependent of 32/64bit
+ const sal_uInt32 nMaxSquarePixels(500000);
+
+ // check how big (squarePixels) the preview is currently (with
+ // max value of MaxSquarePixels)
+ const sal_uInt32 nCurrentSquarePixels(
+ std::min(
+ nMaxSquarePixels,
+ static_cast<sal_uInt32>(maPreviewBitmap.GetSizePixel().getWidth())
+ * static_cast<sal_uInt32>(maPreviewBitmap.GetSizePixel().getHeight())));
+
+ // check how big (squarePixels) the preview needs to be (with
+ // max value of MaxSquarePixels)
+ const sal_uInt32 nRequiredSquarePixels(
+ std::min(
+ nMaxSquarePixels,
+ static_cast<sal_uInt32>(maPreviewSize.getWidth())
+ * static_cast<sal_uInt32>(maPreviewSize.getHeight())));
+
+ // check if preview is big enough. Use a scaling value in
+ // the comparison to not get bigger at the last possible moment
+ // what may look awkward and pixelated (again). This means
+ // to use a percentage value - if we have at least
+ // that value of required pixels, we are good.
+ static const double fPreventAwkwardFactor(1.35); // 35%
+ if(nCurrentSquarePixels >= static_cast<sal_uInt32>(nRequiredSquarePixels * fPreventAwkwardFactor))
+ {
+ // at this place we also could add a mechanism to let the preview
+ // bitmap 'shrink' again if it is currently 'too big' -> bigger
+ // than required. I think this is not necessary for now.
+
+ // already sufficient, done.
+ return;
+ }
+
+ // check if we have enough square pixels e.g for 8x8 pixels
+ if(nRequiredSquarePixels < 64)
+ {
+ // too small preview - let it empty
+ return;
+ }
+
+ // Calculate nPlannedSquarePixels which is the required size
+ // expanded by a percentage (with max value of MaxSquarePixels)
+ static const double fExtraSpaceFactor(1.65); // 65%
+ const sal_uInt32 nPlannedSquarePixels(
+ std::min(
+ nMaxSquarePixels,
+ static_cast<sal_uInt32>(maPreviewSize.getWidth() * fExtraSpaceFactor)
+ * static_cast<sal_uInt32>(maPreviewSize.getHeight() * fExtraSpaceFactor)));
+
+ // calculate back new width and height - it might have been
+ // truncated by MaxSquarePixels.
+ // We know that w*h == nPlannedSquarePixels and w/h == ratio
+ const double fRatio(static_cast<double>(maPreviewSize.getWidth()) / static_cast<double>(maPreviewSize.getHeight()));
+ const double fNewWidth(sqrt(static_cast<double>(nPlannedSquarePixels) * fRatio));
+ const double fNewHeight(sqrt(static_cast<double>(nPlannedSquarePixels) / fRatio));
+ const Size aScaledSize(basegfx::fround(fNewWidth), basegfx::fround(fNewHeight));
+
+ // check if this eventual maximum is already reached
+ // due to having hit the MaxSquarePixels. Due to using
+ // an integer AspectRatio, we cannot make a numeric exact
+ // comparison - we need to compare if we are close
+ const double fScaledSizeSquare(static_cast<double>(aScaledSize.getWidth() * aScaledSize.getHeight()));
+ const double fPreviewSizeSquare(static_cast<double>(maPreviewBitmap.GetSizePixel().getWidth() * maPreviewBitmap.GetSizePixel().getHeight()));
+
+ // test as equal up to 0.1% (0.001)
+ if(fPreviewSizeSquare != 0.0 && fabs((fScaledSizeSquare / fPreviewSizeSquare) - 1.0) < 0.001)
+ {
+ // maximum is reached, avoid bigger scaling
+ return;
+ }
+
+ // create temporary VDev with requested Size and DPI.
+ // CAUTION: DPI *is* important here - it DIFFERS from 75x75, usually 600x600 is used
+ ScopedVclPtrInstance<VirtualDevice> pPrerenderVDev(*Application::GetDefaultDevice());
+ pPrerenderVDev->SetOutputSizePixel(aScaledSize, false);
+ pPrerenderVDev->SetReferenceDevice( mnDPIX, mnDPIY );
+
+ // calculate needed Scale for Metafile (using Size and DPI from VDev)
+ Size aLogicSize( pPrerenderVDev->PixelToLogic( pPrerenderVDev->GetOutputSizePixel(), MapMode( MapUnit::Map100thMM ) ) );
+ Size aOrigSize( maOrigSize );
+ if( aOrigSize.Width() < 1 )
+ aOrigSize.setWidth( aLogicSize.Width() );
+ if( aOrigSize.Height() < 1 )
+ aOrigSize.setHeight( aLogicSize.Height() );
+ double fScale = double(aLogicSize.Width())/double(aOrigSize.Width());
+
+ // tdf#141761
+ // The display quality of the Preview is pretty ugly when
+ // FormControls are used. I made a deep-dive why this happens,
+ // and in principle the reason is the Mteafile::Scale used
+ // below. Since Metafile actions are integer, that floating point
+ // scale leads to rounding errors that make the lines painting
+ // the FormControls disappear in the surrounding ClipRegions.
+ // That Scale cannot be avoided since the Metafile contains it's
+ // own SetMapMode commands which *will* be executed on ::Play,
+ // so the ::Scale is the only possibility fr Metafile currently:
+ // Giving a Size as parameter in ::Play will *not* work due to
+ // the relativeMapMode that gets created will fail on
+ // ::SetMapMode actions in the Metafile - and FormControls DO
+ // use ::SetMapMode(MapPixel).
+ // This can only be solved better in the future using Primitives
+ // which would allow any scale by embedding to a Transformation,
+ // but that would be a bigger rework.
+ // Until then, use this little 'trick' to improve quality.
+ // It uses the fact to empirically having tested that the quality
+ // gets really bad for FormControls starting by a scale factor
+ // smaller than 0.2 - that makes the ClipRegion overlap start.
+ // So - for now - try not to go below that.
+ static const double fMinimumScale(0.2);
+ double fFactor(0.0);
+ if(fScale < fMinimumScale)
+ {
+ fFactor = fMinimumScale / fScale;
+ fScale = fMinimumScale;
+
+ double fWidth(aScaledSize.getWidth() * fFactor);
+ double fHeight(aScaledSize.getHeight() * fFactor);
+ const double fNewNeededPixels(fWidth * fHeight);
+
+ // to not risk using too big bitmaps and running into
+ // memory problems, still limit to a useful factor is
+ // necessary, also empirically estimated to
+ // avoid the quality from collapsing (using a direct
+ // in-between , ceil'd result)
+ static const double fMaximumQualitySquare(1396221.0);
+
+ if(fNewNeededPixels > fMaximumQualitySquare)
+ {
+ const double fCorrection(fMaximumQualitySquare/fNewNeededPixels);
+ fWidth *= fCorrection;
+ fHeight *= fCorrection;
+ fScale *= fCorrection;
+ }
+
+ const Size aScaledSize2(basegfx::fround(fWidth), basegfx::fround(fHeight));
+ pPrerenderVDev->SetOutputSizePixel(aScaledSize2, false);
+ aLogicSize = pPrerenderVDev->PixelToLogic( aScaledSize2, MapMode( MapUnit::Map100thMM ) );
+ }
+
+ pPrerenderVDev->EnableOutput();
+ pPrerenderVDev->SetBackground( Wallpaper(COL_WHITE) );
+ pPrerenderVDev->Erase();
+ pPrerenderVDev->SetMapMode(MapMode(MapUnit::Map100thMM));
+ if( mbGreyscale )
+ pPrerenderVDev->SetDrawMode( pPrerenderVDev->GetDrawMode() |
+ ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
+ DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
+
+ // Copy, Scale and Paint Metafile
+ GDIMetaFile aMtf( maMtf );
+ aMtf.WindStart();
+ aMtf.Scale( fScale, fScale );
+ aMtf.WindStart();
+ aMtf.Play(*pPrerenderVDev, Point(0, 0), aLogicSize);
+
+ pPrerenderVDev->SetMapMode(MapMode(MapUnit::MapPixel));
+ maPreviewBitmap = pPrerenderVDev->GetBitmapEx(Point(0, 0), pPrerenderVDev->GetOutputSizePixel());
+
+ if(0.0 != fFactor)
+ {
+ // Correct to needed size, BmpScaleFlag::Interpolate is acceptable,
+ // but BmpScaleFlag::BestQuality is just better. In case of time
+ // constraints, change to Interpolate would be possible
+ maPreviewBitmap.Scale(aScaledSize, BmpScaleFlag::BestQuality);
+ }
+}
+
+PrintDialog::ShowNupOrderWindow::ShowNupOrderWindow()
+ : mnOrderMode( NupOrderType::LRTB )
+ , mnRows( 1 )
+ , mnColumns( 1 )
+{
+}
+
+void PrintDialog::ShowNupOrderWindow::SetDrawingArea(weld::DrawingArea* pDrawingArea)
+{
+ Size aSize(70, 70);
+ pDrawingArea->set_size_request(aSize.Width(), aSize.Height());
+ CustomWidgetController::SetDrawingArea(pDrawingArea);
+ SetOutputSizePixel(aSize);
+}
+
+void PrintDialog::ShowNupOrderWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*i_rRect*/)
+{
+ rRenderContext.SetMapMode(MapMode(MapUnit::MapPixel));
+ rRenderContext.SetTextColor(rRenderContext.GetSettings().GetStyleSettings().GetFieldTextColor());
+ rRenderContext.SetBackground(Wallpaper(Application::GetSettings().GetStyleSettings().GetFieldColor()));
+ rRenderContext.Erase();
+
+ int nPages = mnRows * mnColumns;
+ Font aFont(rRenderContext.GetSettings().GetStyleSettings().GetFieldFont());
+ aFont.SetFontSize(Size(0, 24));
+ rRenderContext.SetFont(aFont);
+ Size aSampleTextSize(rRenderContext.GetTextWidth(OUString::number(nPages + 1)), rRenderContext.GetTextHeight());
+ Size aOutSize(GetOutputSizePixel());
+ Size aSubSize(aOutSize.Width() / mnColumns, aOutSize.Height() / mnRows);
+ // calculate font size: shrink the sample text so it fits
+ double fX = double(aSubSize.Width()) / double(aSampleTextSize.Width());
+ double fY = double(aSubSize.Height()) / double(aSampleTextSize.Height());
+ double fScale = (fX < fY) ? fX : fY;
+ tools::Long nFontHeight = tools::Long(24.0 * fScale) - 3;
+ if (nFontHeight < 5)
+ nFontHeight = 5;
+ aFont.SetFontSize(Size( 0, nFontHeight));
+ rRenderContext.SetFont(aFont);
+ tools::Long nTextHeight = rRenderContext.GetTextHeight();
+ for (int i = 0; i < nPages; i++)
+ {
+ OUString aPageText(OUString::number(i + 1));
+ int nX = 0, nY = 0;
+ switch (mnOrderMode)
+ {
+ case NupOrderType::LRTB:
+ nX = (i % mnColumns);
+ nY = (i / mnColumns);
+ break;
+ case NupOrderType::TBLR:
+ nX = (i / mnRows);
+ nY = (i % mnRows);
+ break;
+ case NupOrderType::RLTB:
+ nX = mnColumns - 1 - (i % mnColumns);
+ nY = (i / mnColumns);
+ break;
+ case NupOrderType::TBRL:
+ nX = mnColumns - 1 - (i / mnRows);
+ nY = (i % mnRows);
+ break;
+ }
+ Size aTextSize(rRenderContext.GetTextWidth(aPageText), nTextHeight);
+ int nDeltaX = (aSubSize.Width() - aTextSize.Width()) / 2;
+ int nDeltaY = (aSubSize.Height() - aTextSize.Height()) / 2;
+ rRenderContext.DrawText(Point(nX * aSubSize.Width() + nDeltaX,
+ nY * aSubSize.Height() + nDeltaY), aPageText);
+ }
+ DecorationView aDecorationView(&rRenderContext);
+ aDecorationView.DrawFrame(tools::Rectangle(Point(0, 0), aOutSize), DrawFrameStyle::Group);
+}
+
+Size const & PrintDialog::getJobPageSize()
+{
+ if( maFirstPageSize.IsEmpty() )
+ {
+ maFirstPageSize = maNupPortraitSize;
+ GDIMetaFile aMtf;
+ if( maPController->getPageCountProtected() > 0 )
+ {
+ PrinterController::PageSize aPageSize = maPController->getPageFile( 0, aMtf, true );
+ maFirstPageSize = aPageSize.aSize;
+ }
+ }
+ return maFirstPageSize;
+}
+
+PrintDialog::PrintDialog(weld::Window* i_pWindow, const std::shared_ptr<PrinterController>& i_rController)
+ : GenericDialogController(i_pWindow, "vcl/ui/printdialog.ui", "PrintDialog")
+ , maPController( i_rController )
+ , mxTabCtrl(m_xBuilder->weld_notebook("tabcontrol"))
+ , mxScrolledWindow(m_xBuilder->weld_scrolled_window("scrolledwindow"))
+ , mxPageLayoutFrame(m_xBuilder->weld_frame("layoutframe"))
+ , mxPrinters(m_xBuilder->weld_combo_box("printersbox"))
+ , mxStatusTxt(m_xBuilder->weld_label("status"))
+ , mxSetupButton(m_xBuilder->weld_button("setup"))
+ , mxCopyCountField(m_xBuilder->weld_spin_button("copycount"))
+ , mxCollateBox(m_xBuilder->weld_check_button("collate"))
+ , mxCollateImage(m_xBuilder->weld_image("collateimage"))
+ , mxPageRangeEdit(m_xBuilder->weld_entry("pagerange"))
+ , mxPageRangesRadioButton(m_xBuilder->weld_radio_button("rbRangePages"))
+ , mxPaperSidesBox(m_xBuilder->weld_combo_box("sidesbox"))
+ , mxSingleJobsBox(m_xBuilder->weld_check_button("singlejobs"))
+ , mxReverseOrderBox(m_xBuilder->weld_check_button("reverseorder"))
+ , mxOKButton(m_xBuilder->weld_button("ok"))
+ , mxCancelButton(m_xBuilder->weld_button("cancel"))
+ , mxHelpButton(m_xBuilder->weld_button("help"))
+ , mxMoreOptionsBtn(m_xBuilder->weld_button("moreoptionsbtn"))
+ , mxBackwardBtn(m_xBuilder->weld_button("backward"))
+ , mxForwardBtn(m_xBuilder->weld_button("forward"))
+ , mxFirstBtn(m_xBuilder->weld_button("btnFirst"))
+ , mxLastBtn(m_xBuilder->weld_button("btnLast"))
+ , mxPreviewBox(m_xBuilder->weld_check_button("previewbox"))
+ , mxNumPagesText(m_xBuilder->weld_label("totalnumpages"))
+ , mxPreview(new PrintPreviewWindow(this))
+ , mxPreviewWindow(new weld::CustomWeld(*m_xBuilder, "preview", *mxPreview))
+ , mxPageEdit(m_xBuilder->weld_entry("pageedit"))
+ , mxPagesBtn(m_xBuilder->weld_radio_button("pagespersheetbtn"))
+ , mxBrochureBtn(m_xBuilder->weld_radio_button("brochure"))
+ , mxPagesBoxTitleTxt(m_xBuilder->weld_label("pagespersheettxt"))
+ , mxNupPagesBox(m_xBuilder->weld_combo_box("pagespersheetbox"))
+ , mxNupNumPagesTxt(m_xBuilder->weld_label("pagestxt"))
+ , mxNupColEdt(m_xBuilder->weld_spin_button("pagecols"))
+ , mxNupTimesTxt(m_xBuilder->weld_label("by"))
+ , mxNupRowsEdt(m_xBuilder->weld_spin_button("pagerows"))
+ , mxPageMarginTxt1(m_xBuilder->weld_label("pagemargintxt1"))
+ , mxPageMarginEdt(m_xBuilder->weld_metric_spin_button("pagemarginsb", FieldUnit::MM))
+ , mxPageMarginTxt2(m_xBuilder->weld_label("pagemargintxt2"))
+ , mxSheetMarginTxt1(m_xBuilder->weld_label("sheetmargintxt1"))
+ , mxSheetMarginEdt(m_xBuilder->weld_metric_spin_button("sheetmarginsb", FieldUnit::MM))
+ , mxSheetMarginTxt2(m_xBuilder->weld_label("sheetmargintxt2"))
+ , mxPaperSizeBox(m_xBuilder->weld_combo_box("papersizebox"))
+ , mxOrientationBox(m_xBuilder->weld_combo_box("pageorientationbox"))
+ , mxNupOrderTxt(m_xBuilder->weld_label("labelorder"))
+ , mxNupOrderBox(m_xBuilder->weld_combo_box("orderbox"))
+ , mxNupOrder(new ShowNupOrderWindow)
+ , mxNupOrderWin(new weld::CustomWeld(*m_xBuilder, "orderpreview", *mxNupOrder))
+ , mxBorderCB(m_xBuilder->weld_check_button("bordercb"))
+ , mxRangeExpander(m_xBuilder->weld_expander("exRangeExpander"))
+ , mxLayoutExpander(m_xBuilder->weld_expander("exLayoutExpander"))
+ , mxCustom(m_xBuilder->weld_widget("customcontents"))
+ , maPrintToFileText( VclResId( SV_PRINT_TOFILE_TXT ) )
+ , maDefPrtText( VclResId( SV_PRINT_DEFPRT_TXT ) )
+ , maNoPageStr( VclResId( SV_PRINT_NOPAGES ) )
+ , maNoPreviewStr( VclResId( SV_PRINT_NOPREVIEW ) )
+ , mnCurPage( 0 )
+ , mnCachedPages( 0 )
+ , mbCollateAlwaysOff(false)
+ , mbShowLayoutFrame( true )
+ , maUpdatePreviewIdle("Print Dialog Update Preview Idle")
+ , maUpdatePreviewNoCacheIdle("Print Dialog Update Preview (no cache) Idle")
+{
+ // save printbutton text, gets exchanged occasionally with print to file
+ maPrintText = mxOKButton->get_label();
+
+ maPageStr = mxNumPagesText->get_label();
+
+ Printer::updatePrinters();
+
+ mxPrinters->append_text(maPrintToFileText);
+ // fill printer listbox
+ std::vector< OUString > rQueues( Printer::GetPrinterQueues() );
+ std::sort( rQueues.begin(), rQueues.end(), lcl_ListBoxCompare );
+ for( const auto& rQueue : rQueues )
+ {
+ mxPrinters->append_text(rQueue);
+ }
+ // select current printer
+ if (mxPrinters->find_text(maPController->getPrinter()->GetName()) != -1)
+ mxPrinters->set_active_text(maPController->getPrinter()->GetName());
+ else
+ {
+ // fall back to last printer
+ SettingsConfigItem* pItem = SettingsConfigItem::get();
+ OUString aValue( pItem->getValue( "PrintDialog",
+ "LastPrinter" ) );
+ if (mxPrinters->find_text(aValue) != -1)
+ {
+ mxPrinters->set_active_text(aValue);
+ maPController->setPrinter( VclPtrInstance<Printer>( aValue ) );
+ }
+ else
+ {
+ // fall back to default printer
+ mxPrinters->set_active_text(Printer::GetDefaultPrinterName());
+ maPController->setPrinter( VclPtrInstance<Printer>( Printer::GetDefaultPrinterName() ) );
+ }
+ }
+
+ // not printing to file
+ maPController->resetPrinterOptions( false );
+
+ // update the text fields for the printer
+ updatePrinterText();
+
+ // setup dependencies
+ checkControlDependencies();
+
+ // setup paper sides box
+ setupPaperSidesBox();
+
+ // set initial focus to "Number of copies"
+ mxCopyCountField->grab_focus();
+ mxCopyCountField->select_region(0, -1);
+
+ // setup sizes for N-Up
+ Size aNupSize( maPController->getPrinter()->PixelToLogic(
+ maPController->getPrinter()->GetPaperSizePixel(), MapMode( MapUnit::Map100thMM ) ) );
+ if( maPController->getPrinter()->GetOrientation() == Orientation::Landscape )
+ {
+ maNupLandscapeSize = aNupSize;
+ // coverity[swapped_arguments : FALSE] - this is in the correct order
+ maNupPortraitSize = Size( aNupSize.Height(), aNupSize.Width() );
+ }
+ else
+ {
+ maNupPortraitSize = aNupSize;
+ // coverity[swapped_arguments : FALSE] - this is in the correct order
+ maNupLandscapeSize = Size( aNupSize.Height(), aNupSize.Width() );
+ }
+
+ maUpdatePreviewIdle.SetPriority(TaskPriority::POST_PAINT);
+ maUpdatePreviewIdle.SetInvokeHandler(LINK( this, PrintDialog, updatePreviewIdle));
+ maUpdatePreviewNoCacheIdle.SetPriority(TaskPriority::POST_PAINT);
+ maUpdatePreviewNoCacheIdle.SetInvokeHandler(LINK(this, PrintDialog, updatePreviewNoCacheIdle));
+
+ initFromMultiPageSetup( maPController->getMultipage() );
+
+ // setup optional UI options set by application
+ setupOptionalUI();
+
+ // hide layout frame if unwanted
+ mxPageLayoutFrame->set_visible(mbShowLayoutFrame);
+
+ // restore settings from last run
+ readFromSettings();
+
+ // setup click hdl
+ mxOKButton->connect_clicked(LINK(this, PrintDialog, ClickHdl));
+ mxCancelButton->connect_clicked(LINK(this, PrintDialog, ClickHdl));
+ mxHelpButton->connect_clicked(LINK(this, PrintDialog, ClickHdl));
+ mxSetupButton->connect_clicked( LINK( this, PrintDialog, ClickHdl ) );
+ mxBackwardBtn->connect_clicked(LINK(this, PrintDialog, ClickHdl));
+ mxForwardBtn->connect_clicked(LINK(this, PrintDialog, ClickHdl));
+ mxFirstBtn->connect_clicked(LINK(this, PrintDialog, ClickHdl));
+ mxLastBtn->connect_clicked( LINK( this, PrintDialog, ClickHdl ) );
+
+ // setup toggle hdl
+ mxReverseOrderBox->connect_toggled( LINK( this, PrintDialog, ToggleHdl ) );
+ mxCollateBox->connect_toggled( LINK( this, PrintDialog, ToggleHdl ) );
+ mxSingleJobsBox->connect_toggled( LINK( this, PrintDialog, ToggleHdl ) );
+ mxBrochureBtn->connect_toggled( LINK( this, PrintDialog, ToggleHdl ) );
+ mxPreviewBox->connect_toggled( LINK( this, PrintDialog, ToggleHdl ) );
+ mxBorderCB->connect_toggled( LINK( this, PrintDialog, ToggleHdl ) );
+
+ // setup select hdl
+ mxPrinters->connect_changed( LINK( this, PrintDialog, SelectHdl ) );
+ mxPaperSidesBox->connect_changed( LINK( this, PrintDialog, SelectHdl ) );
+ mxNupPagesBox->connect_changed( LINK( this, PrintDialog, SelectHdl ) );
+ mxOrientationBox->connect_changed( LINK( this, PrintDialog, SelectHdl ) );
+ mxNupOrderBox->connect_changed( LINK( this, PrintDialog, SelectHdl ) );
+ mxPaperSizeBox->connect_changed( LINK( this, PrintDialog, SelectHdl ) );
+
+ // setup modify hdl
+ mxPageEdit->connect_activate( LINK( this, PrintDialog, ActivateHdl ) );
+ mxPageEdit->connect_focus_out( LINK( this, PrintDialog, FocusOutHdl ) );
+ mxCopyCountField->connect_value_changed( LINK( this, PrintDialog, SpinModifyHdl ) );
+ mxNupColEdt->connect_value_changed( LINK( this, PrintDialog, SpinModifyHdl ) );
+ mxNupRowsEdt->connect_value_changed( LINK( this, PrintDialog, SpinModifyHdl ) );
+ mxPageMarginEdt->connect_value_changed( LINK( this, PrintDialog, MetricSpinModifyHdl ) );
+ mxSheetMarginEdt->connect_value_changed( LINK( this, PrintDialog, MetricSpinModifyHdl ) );
+
+ updateNupFromPages();
+
+ // tdf#129180 Delay setting the default value in the Paper Size list
+ // set paper sizes listbox
+ setPaperSizes();
+
+ mxRangeExpander->set_expanded(
+ officecfg::Office::Common::Print::Dialog::RangeSectionExpanded::get());
+ mxLayoutExpander->set_expanded(
+ officecfg::Office::Common::Print::Dialog::LayoutSectionExpanded::get());
+
+ // lock the dialog height, regardless of later expander state
+ mxScrolledWindow->set_size_request(
+ mxScrolledWindow->get_preferred_size().Width() + mxScrolledWindow->get_scroll_thickness(),
+ 450);
+
+ m_xDialog->set_centered_on_parent(true);
+}
+
+PrintDialog::~PrintDialog()
+{
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Print::Dialog::RangeSectionExpanded::set(mxRangeExpander->get_expanded(), batch);
+ officecfg::Office::Common::Print::Dialog::LayoutSectionExpanded::set(mxLayoutExpander->get_expanded(), batch);
+ batch->commit();
+}
+
+void PrintDialog::setupPaperSidesBox()
+{
+ DuplexMode eDuplex = maPController->getPrinter()->GetDuplexMode();
+
+ if ( eDuplex == DuplexMode::Unknown || isPrintToFile() )
+ {
+ mxPaperSidesBox->set_active( 0 );
+ mxPaperSidesBox->set_sensitive( false );
+ }
+ else
+ {
+ mxPaperSidesBox->set_active( static_cast<sal_Int32>(eDuplex) - 1 );
+ mxPaperSidesBox->set_sensitive( true );
+ }
+}
+
+void PrintDialog::storeToSettings()
+{
+ SettingsConfigItem* pItem = SettingsConfigItem::get();
+
+ pItem->setValue( "PrintDialog",
+ "LastPrinter",
+ isPrintToFile() ? Printer::GetDefaultPrinterName()
+ : mxPrinters->get_active_text() );
+
+ pItem->setValue( "PrintDialog",
+ "LastPage",
+ mxTabCtrl->get_tab_label_text(mxTabCtrl->get_current_page_ident()));
+
+ pItem->setValue( "PrintDialog",
+ "WindowState",
+ OStringToOUString(m_xDialog->get_window_state(WindowStateMask::All), RTL_TEXTENCODING_UTF8) );
+
+ pItem->setValue( "PrintDialog",
+ "CopyCount",
+ mxCopyCountField->get_text() );
+
+ pItem->setValue( "PrintDialog",
+ "Collate",
+ mxCollateBox->get_active() ? OUString("true") :
+ OUString("false") );
+
+ pItem->setValue( "PrintDialog",
+ "CollateSingleJobs",
+ mxSingleJobsBox->get_active() ? OUString("true") :
+ OUString("false") );
+
+ pItem->setValue( "PrintDialog",
+ "HasPreview",
+ hasPreview() ? OUString("true") :
+ OUString("false") );
+
+ pItem->Commit();
+}
+
+void PrintDialog::readFromSettings()
+{
+ SettingsConfigItem* pItem = SettingsConfigItem::get();
+
+ // read last selected tab page; if it exists, activate it
+ OUString aValue = pItem->getValue( "PrintDialog",
+ "LastPage" );
+ sal_uInt16 nCount = mxTabCtrl->get_n_pages();
+ for (sal_uInt16 i = 0; i < nCount; ++i)
+ {
+ OString sPageId = mxTabCtrl->get_page_ident(i);
+ if (aValue == mxTabCtrl->get_tab_label_text(sPageId))
+ {
+ mxTabCtrl->set_current_page(sPageId);
+ break;
+ }
+ }
+
+ // persistent window state
+ aValue = pItem->getValue( "PrintDialog",
+ "WindowState" );
+ if (!aValue.isEmpty())
+ m_xDialog->set_window_state(OUStringToOString(aValue, RTL_TEXTENCODING_UTF8));
+
+ // collate
+ aValue = pItem->getValue( "PrintDialog",
+ "CollateBox" );
+ if( aValue.equalsIgnoreAsciiCase("alwaysoff") )
+ {
+ mbCollateAlwaysOff = true;
+ mxCollateBox->set_active( false );
+ mxCollateBox->set_sensitive( false );
+ }
+ else
+ {
+ mbCollateAlwaysOff = false;
+ aValue = pItem->getValue( "PrintDialog",
+ "Collate" );
+ mxCollateBox->set_active( aValue.equalsIgnoreAsciiCase("true") );
+ }
+
+ // collate single jobs
+ aValue = pItem->getValue( "PrintDialog",
+ "CollateSingleJobs" );
+ mxSingleJobsBox->set_active(aValue.equalsIgnoreAsciiCase("true"));
+
+ // preview box
+ aValue = pItem->getValue( "PrintDialog",
+ "HasPreview" );
+ if ( aValue.equalsIgnoreAsciiCase("false") )
+ mxPreviewBox->set_active( false );
+ else
+ mxPreviewBox->set_active( true );
+
+}
+
+void PrintDialog::setPaperSizes()
+{
+ mxPaperSizeBox->clear();
+
+ VclPtr<Printer> aPrt( maPController->getPrinter() );
+ mePaper = aPrt->GetPaper();
+
+ if ( isPrintToFile() )
+ {
+ mxPaperSizeBox->set_sensitive( false );
+ }
+ else
+ {
+ Size aSizeOfPaper = aPrt->GetSizeOfPaper();
+ PaperInfo aPaperInfo(aSizeOfPaper.getWidth(), aSizeOfPaper.getHeight());
+ const LocaleDataWrapper& rLocWrap(Application::GetSettings().GetLocaleDataWrapper());
+ o3tl::Length eUnit = o3tl::Length::mm;
+ int nDigits = 0;
+ if( rLocWrap.getMeasurementSystemEnum() == MeasurementSystem::US )
+ {
+ eUnit = o3tl::Length::in100;
+ nDigits = 2;
+ }
+ for (int nPaper = 0; nPaper < aPrt->GetPaperInfoCount(); nPaper++)
+ {
+ PaperInfo aInfo = aPrt->GetPaperInfo( nPaper );
+ aInfo.doSloppyFit(true);
+ Paper ePaper = aInfo.getPaper();
+
+ Size aSize = aPrt->GetPaperSize( nPaper );
+ Size aLogicPaperSize( o3tl::convert(aSize, o3tl::Length::mm100, eUnit) );
+
+ OUString aWidth( rLocWrap.getNum( aLogicPaperSize.Width(), nDigits ) );
+ OUString aHeight( rLocWrap.getNum( aLogicPaperSize.Height(), nDigits ) );
+ OUString aUnit = eUnit == o3tl::Length::mm ? OUString("mm") : OUString("in");
+ OUString aPaperName;
+
+ // Paper sizes that we don't know of but the system printer driver lists are not "User
+ // Defined". Displaying them as such is just confusing.
+ if (ePaper != PAPER_USER)
+ aPaperName = Printer::GetPaperName( ePaper ) + " ";
+
+ aPaperName += aWidth + aUnit + " x " + aHeight + aUnit;
+
+ mxPaperSizeBox->append_text(aPaperName);
+
+ if ( (ePaper != PAPER_USER && ePaper == mePaper) ||
+ (ePaper == PAPER_USER && aInfo.sloppyEqual(aPaperInfo) ) )
+ mxPaperSizeBox->set_active( nPaper );
+ }
+
+ mxPaperSizeBox->set_sensitive( true );
+ }
+}
+
+void PrintDialog::updatePrinterText()
+{
+ const OUString aDefPrt( Printer::GetDefaultPrinterName() );
+ const QueueInfo* pInfo = Printer::GetQueueInfo( mxPrinters->get_active_text(), true );
+ if( pInfo )
+ {
+ // FIXME: status text
+ OUString aStatus;
+ if( aDefPrt == pInfo->GetPrinterName() )
+ aStatus = maDefPrtText;
+ mxStatusTxt->set_label( aStatus );
+ }
+ else
+ {
+ mxStatusTxt->set_label( OUString() );
+ }
+}
+
+void PrintDialog::setPreviewText()
+{
+ OUString aNewText( maPageStr.replaceFirst( "%n", OUString::number( mnCachedPages ) ) );
+ mxNumPagesText->set_label( aNewText );
+}
+
+IMPL_LINK_NOARG(PrintDialog, updatePreviewIdle, Timer*, void)
+{
+ preparePreview(true);
+}
+
+IMPL_LINK_NOARG(PrintDialog, updatePreviewNoCacheIdle, Timer*, void)
+{
+ preparePreview(false);
+}
+
+void PrintDialog::preparePreview( bool i_bMayUseCache )
+{
+ VclPtr<Printer> aPrt( maPController->getPrinter() );
+ Size aCurPageSize = aPrt->PixelToLogic( aPrt->GetPaperSizePixel(), MapMode( MapUnit::Map100thMM ) );
+ // tdf#123076 Get paper size for the preview top label
+ mePaper = aPrt->GetPaper();
+ GDIMetaFile aMtf;
+
+ // page range may have changed depending on options
+ sal_Int32 nPages = maPController->getFilteredPageCount();
+ mnCachedPages = nPages;
+
+ setPreviewText();
+
+ if ( !hasPreview() )
+ {
+ mxPreview->setPreview( aMtf, aCurPageSize,
+ Printer::GetPaperName( mePaper ),
+ maNoPreviewStr,
+ aPrt->GetDPIX(), aPrt->GetDPIY(),
+ aPrt->GetPrinterOptions().IsConvertToGreyscales()
+ );
+
+ mxForwardBtn->set_sensitive( false );
+ mxBackwardBtn->set_sensitive( false );
+ mxFirstBtn->set_sensitive( false );
+ mxLastBtn->set_sensitive( false );
+
+ mxPageEdit->set_sensitive( false );
+
+ return;
+ }
+
+ if( mnCurPage >= nPages )
+ mnCurPage = nPages-1;
+ if( mnCurPage < 0 )
+ mnCurPage = 0;
+ mxPageEdit->set_text(OUString::number(mnCurPage + 1));
+
+ if( nPages > 0 )
+ {
+ PrinterController::PageSize aPageSize =
+ maPController->getFilteredPageFile( mnCurPage, aMtf, i_bMayUseCache );
+ aCurPageSize = aPrt->PixelToLogic(aPrt->GetPaperSizePixel(), MapMode(MapUnit::Map100thMM));
+ if( ! aPageSize.bFullPaper )
+ {
+ const MapMode aMapMode( MapUnit::Map100thMM );
+ Point aOff( aPrt->PixelToLogic( aPrt->GetPageOffsetPixel(), aMapMode ) );
+ aMtf.Move( aOff.X(), aOff.Y() );
+ }
+ // tdf#150561: page size may have changed so sync mePaper with it
+ mePaper = aPrt->GetPaper();
+ }
+
+ mxPreview->setPreview( aMtf, aCurPageSize,
+ Printer::GetPaperName( mePaper ),
+ nPages > 0 ? OUString() : maNoPageStr,
+ aPrt->GetDPIX(), aPrt->GetDPIY(),
+ aPrt->GetPrinterOptions().IsConvertToGreyscales()
+ );
+
+ mxForwardBtn->set_sensitive( mnCurPage < nPages-1 );
+ mxBackwardBtn->set_sensitive( mnCurPage != 0 );
+ mxFirstBtn->set_sensitive( mnCurPage != 0 );
+ mxLastBtn->set_sensitive( mnCurPage < nPages-1 );
+ mxPageEdit->set_sensitive( nPages > 1 );
+}
+
+void PrintDialog::updateOrientationBox( const bool bAutomatic )
+{
+ if ( !bAutomatic )
+ {
+ Orientation eOrientation = maPController->getPrinter()->GetOrientation();
+ mxOrientationBox->set_active( static_cast<sal_Int32>(eOrientation) + 1 );
+ }
+ else if ( hasOrientationChanged() )
+ {
+ mxOrientationBox->set_active( ORIENTATION_AUTOMATIC );
+ }
+}
+
+bool PrintDialog::hasOrientationChanged() const
+{
+ const int nOrientation = mxOrientationBox->get_active();
+ const Orientation eOrientation = maPController->getPrinter()->GetOrientation();
+
+ return (nOrientation == ORIENTATION_LANDSCAPE && eOrientation == Orientation::Portrait)
+ || (nOrientation == ORIENTATION_PORTRAIT && eOrientation == Orientation::Landscape);
+}
+
+// Always use this function to set paper orientation to make sure everything behaves well
+void PrintDialog::setPaperOrientation( Orientation eOrientation, bool fromUser )
+{
+ VclPtr<Printer> aPrt( maPController->getPrinter() );
+ aPrt->SetOrientation( eOrientation );
+ maPController->setOrientationFromUser( eOrientation, fromUser );
+}
+
+void PrintDialog::checkControlDependencies()
+{
+ if (mxCopyCountField->get_value() > 1)
+ {
+ mxCollateBox->set_sensitive( !mbCollateAlwaysOff );
+ mxSingleJobsBox->set_sensitive( mxCollateBox->get_active() );
+ }
+ else
+ {
+ mxCollateBox->set_sensitive( false );
+ mxSingleJobsBox->set_sensitive( false );
+ }
+
+ OUString aImg(mxCollateBox->get_active() ? OUString(SV_PRINT_COLLATE_BMP) : OUString(SV_PRINT_NOCOLLATE_BMP));
+
+ mxCollateImage->set_from_icon_name(aImg);
+
+ // enable setup button only for printers that can be setup
+ bool bHaveSetup = maPController->getPrinter()->HasSupport( PrinterSupport::SetupDialog );
+ mxSetupButton->set_sensitive(bHaveSetup);
+}
+
+void PrintDialog::checkOptionalControlDependencies()
+{
+ for( const auto& rEntry : maControlToPropertyMap )
+ {
+ bool bShouldbeEnabled = maPController->isUIOptionEnabled( rEntry.second );
+
+ if (bShouldbeEnabled && dynamic_cast<weld::RadioButton*>(rEntry.first))
+ {
+ auto r_it = maControlToNumValMap.find( rEntry.first );
+ if( r_it != maControlToNumValMap.end() )
+ {
+ bShouldbeEnabled = maPController->isUIChoiceEnabled( rEntry.second, r_it->second );
+ }
+ }
+
+ bool bIsEnabled = rEntry.first->get_sensitive();
+ // Enable does not do a change check first, so can be less cheap than expected
+ if (bShouldbeEnabled != bIsEnabled)
+ rEntry.first->set_sensitive( bShouldbeEnabled );
+ }
+}
+
+void PrintDialog::initFromMultiPageSetup( const vcl::PrinterController::MultiPageSetup& i_rMPS )
+{
+ mxNupOrderWin->show();
+ mxPagesBtn->set_active(true);
+ mxBrochureBtn->hide();
+
+ // setup field units for metric fields
+ const LocaleDataWrapper& rLocWrap(Application::GetSettings().GetLocaleDataWrapper());
+ FieldUnit eUnit = FieldUnit::MM;
+ sal_uInt16 nDigits = 0;
+ if( rLocWrap.getMeasurementSystemEnum() == MeasurementSystem::US )
+ {
+ eUnit = FieldUnit::INCH;
+ nDigits = 2;
+ }
+ // set units
+ mxPageMarginEdt->set_unit( eUnit );
+ mxSheetMarginEdt->set_unit( eUnit );
+
+ // set precision
+ mxPageMarginEdt->set_digits( nDigits );
+ mxSheetMarginEdt->set_digits( nDigits );
+
+ mxSheetMarginEdt->set_value( mxSheetMarginEdt->normalize( i_rMPS.nLeftMargin ), FieldUnit::MM_100TH );
+ mxPageMarginEdt->set_value( mxPageMarginEdt->normalize( i_rMPS.nHorizontalSpacing ), FieldUnit::MM_100TH );
+ mxBorderCB->set_active( i_rMPS.bDrawBorder );
+ mxNupRowsEdt->set_value( i_rMPS.nRows );
+ mxNupColEdt->set_value( i_rMPS.nColumns );
+ mxNupOrderBox->set_active( static_cast<sal_Int32>(i_rMPS.nOrder) );
+ if( i_rMPS.nRows != 1 || i_rMPS.nColumns != 1 )
+ {
+ mxNupPagesBox->set_active( mxNupPagesBox->get_count()-1 );
+ showAdvancedControls( true );
+ mxNupOrder->setValues( i_rMPS.nOrder, i_rMPS.nColumns, i_rMPS.nRows );
+ }
+}
+
+void PrintDialog::updateNup( bool i_bMayUseCache )
+{
+ int nRows = mxNupRowsEdt->get_value();
+ int nCols = mxNupColEdt->get_value();
+ tools::Long nPageMargin = mxPageMarginEdt->denormalize(mxPageMarginEdt->get_value( FieldUnit::MM_100TH ));
+ tools::Long nSheetMargin = mxSheetMarginEdt->denormalize(mxSheetMarginEdt->get_value( FieldUnit::MM_100TH ));
+
+ PrinterController::MultiPageSetup aMPS;
+ aMPS.nRows = nRows;
+ aMPS.nColumns = nCols;
+ aMPS.nLeftMargin =
+ aMPS.nTopMargin =
+ aMPS.nRightMargin =
+ aMPS.nBottomMargin = nSheetMargin;
+
+ aMPS.nHorizontalSpacing =
+ aMPS.nVerticalSpacing = nPageMargin;
+
+ aMPS.bDrawBorder = mxBorderCB->get_active();
+
+ aMPS.nOrder = static_cast<NupOrderType>(mxNupOrderBox->get_active());
+
+ int nOrientationMode = mxOrientationBox->get_active();
+ if( nOrientationMode == ORIENTATION_LANDSCAPE )
+ aMPS.aPaperSize = maNupLandscapeSize;
+ else if( nOrientationMode == ORIENTATION_PORTRAIT )
+ aMPS.aPaperSize = maNupPortraitSize;
+ else // automatic mode
+ {
+ // get size of first real page to see if it is portrait or landscape
+ // we assume same page sizes for all the pages for this
+ Size aPageSize = getJobPageSize();
+
+ Size aMultiSize( aPageSize.Width() * nCols, aPageSize.Height() * nRows );
+ if( aMultiSize.Width() > aMultiSize.Height() ) // fits better on landscape
+ {
+ aMPS.aPaperSize = maNupLandscapeSize;
+ setPaperOrientation( Orientation::Landscape, false );
+ }
+ else
+ {
+ aMPS.aPaperSize = maNupPortraitSize;
+ setPaperOrientation( Orientation::Portrait, false );
+ }
+ }
+
+ maPController->setMultipage( aMPS );
+
+ mxNupOrder->setValues( aMPS.nOrder, nCols, nRows );
+
+ if (i_bMayUseCache)
+ maUpdatePreviewIdle.Start();
+ else
+ maUpdatePreviewNoCacheIdle.Start();
+}
+
+void PrintDialog::updateNupFromPages( bool i_bMayUseCache )
+{
+ int nPages = mxNupPagesBox->get_active_id().toInt32();
+ int nRows = mxNupRowsEdt->get_value();
+ int nCols = mxNupColEdt->get_value();
+ tools::Long nPageMargin = mxPageMarginEdt->denormalize(mxPageMarginEdt->get_value( FieldUnit::MM_100TH ));
+ tools::Long nSheetMargin = mxSheetMarginEdt->denormalize(mxSheetMarginEdt->get_value( FieldUnit::MM_100TH ));
+ bool bCustom = false;
+
+ if( nPages == 1 )
+ {
+ nRows = nCols = 1;
+ nSheetMargin = 0;
+ nPageMargin = 0;
+ }
+ else if( nPages == 2 || nPages == 4 || nPages == 6 || nPages == 9 || nPages == 16 )
+ {
+ Size aJobPageSize( getJobPageSize() );
+ bool bPortrait = aJobPageSize.Width() < aJobPageSize.Height();
+ if( nPages == 2 )
+ {
+ if( bPortrait )
+ {
+ nRows = 1;
+ nCols = 2;
+ }
+ else
+ {
+ nRows = 2;
+ nCols = 1;
+ }
+ }
+ else if( nPages == 4 )
+ nRows = nCols = 2;
+ else if( nPages == 6 )
+ {
+ if( bPortrait )
+ {
+ nRows = 2;
+ nCols = 3;
+ }
+ else
+ {
+ nRows = 3;
+ nCols = 2;
+ }
+ }
+ else if( nPages == 9 )
+ nRows = nCols = 3;
+ else if( nPages == 16 )
+ nRows = nCols = 4;
+ nPageMargin = 0;
+ nSheetMargin = 0;
+ }
+ else
+ bCustom = true;
+
+ if( nPages > 1 )
+ {
+ // set upper limits for margins based on job page size and rows/columns
+ Size aSize( getJobPageSize() );
+
+ // maximum sheet distance: 1/2 sheet
+ tools::Long nHorzMax = aSize.Width()/2;
+ tools::Long nVertMax = aSize.Height()/2;
+ if( nSheetMargin > nHorzMax )
+ nSheetMargin = nHorzMax;
+ if( nSheetMargin > nVertMax )
+ nSheetMargin = nVertMax;
+
+ mxSheetMarginEdt->set_max(
+ mxSheetMarginEdt->normalize(
+ std::min(nHorzMax, nVertMax) ), FieldUnit::MM_100TH );
+
+ // maximum page distance
+ nHorzMax = (aSize.Width() - 2*nSheetMargin);
+ if( nCols > 1 )
+ nHorzMax /= (nCols-1);
+ nVertMax = (aSize.Height() - 2*nSheetMargin);
+ if( nRows > 1 )
+ nHorzMax /= (nRows-1);
+
+ if( nPageMargin > nHorzMax )
+ nPageMargin = nHorzMax;
+ if( nPageMargin > nVertMax )
+ nPageMargin = nVertMax;
+
+ mxPageMarginEdt->set_max(
+ mxSheetMarginEdt->normalize(
+ std::min(nHorzMax, nVertMax ) ), FieldUnit::MM_100TH );
+ }
+
+ mxNupRowsEdt->set_value( nRows );
+ mxNupColEdt->set_value( nCols );
+ mxPageMarginEdt->set_value( mxPageMarginEdt->normalize( nPageMargin ), FieldUnit::MM_100TH );
+ mxSheetMarginEdt->set_value( mxSheetMarginEdt->normalize( nSheetMargin ), FieldUnit::MM_100TH );
+
+ showAdvancedControls( bCustom );
+ updateNup( i_bMayUseCache );
+}
+
+void PrintDialog::enableNupControls( bool bEnable )
+{
+ mxNupPagesBox->set_sensitive( bEnable );
+ mxNupNumPagesTxt->set_sensitive( bEnable );
+ mxNupColEdt->set_sensitive( bEnable );
+ mxNupTimesTxt->set_sensitive( bEnable );
+ mxNupRowsEdt->set_sensitive( bEnable );
+ mxPageMarginTxt1->set_sensitive( bEnable );
+ mxPageMarginEdt->set_sensitive( bEnable );
+ mxPageMarginTxt2->set_sensitive( bEnable );
+ mxSheetMarginTxt1->set_sensitive( bEnable );
+ mxSheetMarginEdt->set_sensitive( bEnable );
+ mxSheetMarginTxt2->set_sensitive( bEnable );
+ mxNupOrderTxt->set_sensitive( bEnable );
+ mxNupOrderBox->set_sensitive( bEnable );
+ mxNupOrderWin->set_sensitive( bEnable );
+ mxBorderCB->set_sensitive( bEnable );
+}
+
+void PrintDialog::showAdvancedControls( bool i_bShow )
+{
+ mxNupNumPagesTxt->set_visible( i_bShow );
+ mxNupColEdt->set_visible( i_bShow );
+ mxNupTimesTxt->set_visible( i_bShow );
+ mxNupRowsEdt->set_visible( i_bShow );
+ mxPageMarginTxt1->set_visible( i_bShow );
+ mxPageMarginEdt->set_visible( i_bShow );
+ mxPageMarginTxt2->set_visible( i_bShow );
+ mxSheetMarginTxt1->set_visible( i_bShow );
+ mxSheetMarginEdt->set_visible( i_bShow );
+ mxSheetMarginTxt2->set_visible( i_bShow );
+}
+
+namespace
+{
+ void setHelpId( weld::Widget* i_pWindow, const Sequence< OUString >& i_rHelpIds, sal_Int32 i_nIndex )
+ {
+ if( i_nIndex >= 0 && i_nIndex < i_rHelpIds.getLength() )
+ i_pWindow->set_help_id( OUStringToOString( i_rHelpIds.getConstArray()[i_nIndex], RTL_TEXTENCODING_UTF8 ) );
+ }
+
+ void setHelpText( weld::Widget* i_pWindow, const Sequence< OUString >& i_rHelpTexts, sal_Int32 i_nIndex )
+ {
+ // without a help text set and the correct smartID,
+ // help texts will be retrieved from the online help system
+ if( i_nIndex >= 0 && i_nIndex < i_rHelpTexts.getLength() )
+ i_pWindow->set_tooltip_text(i_rHelpTexts.getConstArray()[i_nIndex]);
+ }
+}
+
+void PrintDialog::setupOptionalUI()
+{
+ const Sequence< PropertyValue >& rOptions( maPController->getUIOptions() );
+ for( const auto& rOption : rOptions )
+ {
+ if (rOption.Name == "OptionsUIFile")
+ {
+ OUString sOptionsUIFile;
+ rOption.Value >>= sOptionsUIFile;
+ mxCustomOptionsUIBuilder = Application::CreateBuilder(mxCustom.get(), sOptionsUIFile);
+ std::unique_ptr<weld::Container> xWindow = mxCustomOptionsUIBuilder->weld_container("box");
+ xWindow->show();
+ continue;
+ }
+
+ Sequence< beans::PropertyValue > aOptProp;
+ rOption.Value >>= aOptProp;
+
+ // extract ui element
+ OUString aCtrlType;
+ OString aID;
+ OUString aText;
+ OUString aPropertyName;
+ Sequence< OUString > aChoices;
+ Sequence< sal_Bool > aChoicesDisabled;
+ Sequence< OUString > aHelpTexts;
+ Sequence< OUString > aIDs;
+ Sequence< OUString > aHelpIds;
+ sal_Int64 nMinValue = 0, nMaxValue = 0;
+ OUString aGroupingHint;
+
+ for( const beans::PropertyValue& rEntry : std::as_const(aOptProp) )
+ {
+ if ( rEntry.Name == "ID" )
+ {
+ rEntry.Value >>= aIDs;
+ aID = OUStringToOString(aIDs[0], RTL_TEXTENCODING_UTF8);
+ }
+ if ( rEntry.Name == "Text" )
+ {
+ rEntry.Value >>= aText;
+ }
+ else if ( rEntry.Name == "ControlType" )
+ {
+ rEntry.Value >>= aCtrlType;
+ }
+ else if ( rEntry.Name == "Choices" )
+ {
+ rEntry.Value >>= aChoices;
+ }
+ else if ( rEntry.Name == "ChoicesDisabled" )
+ {
+ rEntry.Value >>= aChoicesDisabled;
+ }
+ else if ( rEntry.Name == "Property" )
+ {
+ PropertyValue aVal;
+ rEntry.Value >>= aVal;
+ aPropertyName = aVal.Name;
+ }
+ else if ( rEntry.Name == "Enabled" )
+ {
+ }
+ else if ( rEntry.Name == "GroupingHint" )
+ {
+ rEntry.Value >>= aGroupingHint;
+ }
+ else if ( rEntry.Name == "DependsOnName" )
+ {
+ }
+ else if ( rEntry.Name == "DependsOnEntry" )
+ {
+ }
+ else if ( rEntry.Name == "AttachToDependency" )
+ {
+ }
+ else if ( rEntry.Name == "MinValue" )
+ {
+ rEntry.Value >>= nMinValue;
+ }
+ else if ( rEntry.Name == "MaxValue" )
+ {
+ rEntry.Value >>= nMaxValue;
+ }
+ else if ( rEntry.Name == "HelpText" )
+ {
+ if( ! (rEntry.Value >>= aHelpTexts) )
+ {
+ OUString aHelpText;
+ if( rEntry.Value >>= aHelpText )
+ {
+ aHelpTexts.realloc( 1 );
+ *aHelpTexts.getArray() = aHelpText;
+ }
+ }
+ }
+ else if ( rEntry.Name == "HelpId" )
+ {
+ if( ! (rEntry.Value >>= aHelpIds ) )
+ {
+ OUString aHelpId;
+ if( rEntry.Value >>= aHelpId )
+ {
+ aHelpIds.realloc( 1 );
+ *aHelpIds.getArray() = aHelpId;
+ }
+ }
+ }
+ else if ( rEntry.Name == "HintNoLayoutPage" )
+ {
+ bool bHasLayoutFrame = false;
+ rEntry.Value >>= bHasLayoutFrame;
+ mbShowLayoutFrame = !bHasLayoutFrame;
+ }
+ }
+
+ if (aCtrlType == "Group")
+ {
+ aID = "custom";
+
+ weld::Container* pPage = mxTabCtrl->get_page(aID);
+ if (!pPage)
+ continue;
+
+ mxTabCtrl->set_tab_label_text(aID, aText);
+
+ // set help id
+ if (aHelpIds.hasElements())
+ pPage->set_help_id(OUStringToOString(aHelpIds.getConstArray()[0], RTL_TEXTENCODING_UTF8));
+
+ // set help text
+ if (aHelpTexts.hasElements())
+ pPage->set_tooltip_text(aHelpTexts.getConstArray()[0]);
+
+ pPage->show();
+ }
+ else if (aCtrlType == "Subgroup" && !aID.isEmpty())
+ {
+ std::unique_ptr<weld::Widget> xWidget;
+ // since 'New Print Dialog Design' fromwhich in calc is not a frame anymore
+ if (aID == "fromwhich")
+ {
+ std::unique_ptr<weld::Label> xLabel = m_xBuilder->weld_label(aID);
+ xLabel->set_label(aText);
+ xWidget = std::move(xLabel);
+ }
+ else
+ {
+ std::unique_ptr<weld::Frame> xFrame = m_xBuilder->weld_frame(aID);
+ if (!xFrame && mxCustomOptionsUIBuilder)
+ xFrame = mxCustomOptionsUIBuilder->weld_frame(aID);
+ if (xFrame)
+ {
+ xFrame->set_label(aText);
+ xWidget = std::move(xFrame);
+ }
+ }
+
+ if (!xWidget)
+ continue;
+
+ // set help id
+ setHelpId(xWidget.get(), aHelpIds, 0);
+ // set help text
+ setHelpText(xWidget.get(), aHelpTexts, 0);
+
+ xWidget->show();
+ }
+ // EVIL
+ else if( aCtrlType == "Bool" && aGroupingHint == "LayoutPage" && aPropertyName == "PrintProspect" )
+ {
+ mxBrochureBtn->set_label(aText);
+ mxBrochureBtn->show();
+
+ bool bVal = false;
+ PropertyValue* pVal = maPController->getValue( aPropertyName );
+ if( pVal )
+ pVal->Value >>= bVal;
+ mxBrochureBtn->set_active( bVal );
+ mxBrochureBtn->set_sensitive( maPController->isUIOptionEnabled( aPropertyName ) && pVal != nullptr );
+
+ maPropertyToWindowMap[aPropertyName].emplace_back(mxBrochureBtn.get());
+ maControlToPropertyMap[mxBrochureBtn.get()] = aPropertyName;
+
+ // set help id
+ setHelpId( mxBrochureBtn.get(), aHelpIds, 0 );
+ // set help text
+ setHelpText( mxBrochureBtn.get(), aHelpTexts, 0 );
+ }
+ else if (aCtrlType == "Bool")
+ {
+ // add a check box
+ std::unique_ptr<weld::CheckButton> xNewBox = m_xBuilder->weld_check_button(aID);
+ if (!xNewBox && mxCustomOptionsUIBuilder)
+ xNewBox = mxCustomOptionsUIBuilder->weld_check_button(aID);
+ if (!xNewBox)
+ continue;
+
+ xNewBox->set_label( aText );
+ xNewBox->show();
+
+ bool bVal = false;
+ PropertyValue* pVal = maPController->getValue( aPropertyName );
+ if( pVal )
+ pVal->Value >>= bVal;
+ xNewBox->set_active( bVal );
+ xNewBox->connect_toggled( LINK( this, PrintDialog, UIOption_CheckHdl ) );
+
+ maExtraControls.emplace_back(std::move(xNewBox));
+
+ weld::Widget* pWidget = maExtraControls.back().get();
+
+ maPropertyToWindowMap[aPropertyName].emplace_back(pWidget);
+ maControlToPropertyMap[pWidget] = aPropertyName;
+
+ // set help id
+ setHelpId(pWidget, aHelpIds, 0);
+ // set help text
+ setHelpText(pWidget, aHelpTexts, 0);
+ }
+ else if (aCtrlType == "Radio")
+ {
+ sal_Int32 nCurHelpText = 0;
+
+ // iterate options
+ sal_Int32 nSelectVal = 0;
+ PropertyValue* pVal = maPController->getValue( aPropertyName );
+ if( pVal && pVal->Value.hasValue() )
+ pVal->Value >>= nSelectVal;
+ for( sal_Int32 m = 0; m < aChoices.getLength(); m++ )
+ {
+ aID = OUStringToOString(aIDs[m], RTL_TEXTENCODING_UTF8);
+ std::unique_ptr<weld::RadioButton> xBtn = m_xBuilder->weld_radio_button(aID);
+ if (!xBtn && mxCustomOptionsUIBuilder)
+ xBtn = mxCustomOptionsUIBuilder->weld_radio_button(aID);
+ if (!xBtn)
+ continue;
+
+ xBtn->set_label( aChoices[m] );
+ xBtn->set_active( m == nSelectVal );
+ xBtn->connect_toggled( LINK( this, PrintDialog, UIOption_RadioHdl ) );
+ if( aChoicesDisabled.getLength() > m && aChoicesDisabled[m] )
+ xBtn->set_sensitive( false );
+ xBtn->show();
+
+ maExtraControls.emplace_back(std::move(xBtn));
+
+ weld::Widget* pWidget = maExtraControls.back().get();
+
+ maPropertyToWindowMap[ aPropertyName ].emplace_back(pWidget);
+ maControlToPropertyMap[pWidget] = aPropertyName;
+ maControlToNumValMap[pWidget] = m;
+
+ // set help id
+ setHelpId( pWidget, aHelpIds, nCurHelpText );
+ // set help text
+ setHelpText( pWidget, aHelpTexts, nCurHelpText );
+ nCurHelpText++;
+ }
+ }
+ else if ( aCtrlType == "List" )
+ {
+ std::unique_ptr<weld::ComboBox> xList = m_xBuilder->weld_combo_box(aID);
+ if (!xList && mxCustomOptionsUIBuilder)
+ xList = mxCustomOptionsUIBuilder->weld_combo_box(aID);
+ if (!xList)
+ continue;
+
+ // iterate options
+ for( const auto& rChoice : std::as_const(aChoices) )
+ xList->append_text(rChoice);
+
+ sal_Int32 nSelectVal = 0;
+ PropertyValue* pVal = maPController->getValue( aPropertyName );
+ if( pVal && pVal->Value.hasValue() )
+ pVal->Value >>= nSelectVal;
+ xList->set_active(nSelectVal);
+ xList->connect_changed( LINK( this, PrintDialog, UIOption_SelectHdl ) );
+ xList->show();
+
+ maExtraControls.emplace_back(std::move(xList));
+
+ weld::Widget* pWidget = maExtraControls.back().get();
+
+ maPropertyToWindowMap[ aPropertyName ].emplace_back(pWidget);
+ maControlToPropertyMap[pWidget] = aPropertyName;
+
+ // set help id
+ setHelpId( pWidget, aHelpIds, 0 );
+ // set help text
+ setHelpText( pWidget, aHelpTexts, 0 );
+ }
+ else if ( aCtrlType == "Range" )
+ {
+ std::unique_ptr<weld::SpinButton> xField = m_xBuilder->weld_spin_button(aID);
+ if (!xField && mxCustomOptionsUIBuilder)
+ xField = mxCustomOptionsUIBuilder->weld_spin_button(aID);
+ if (!xField)
+ continue;
+
+ // set min/max and current value
+ if(nMinValue != nMaxValue)
+ xField->set_range(nMinValue, nMaxValue);
+
+ sal_Int64 nCurVal = 0;
+ PropertyValue* pVal = maPController->getValue( aPropertyName );
+ if( pVal && pVal->Value.hasValue() )
+ pVal->Value >>= nCurVal;
+ xField->set_value( nCurVal );
+ xField->connect_value_changed( LINK( this, PrintDialog, UIOption_SpinModifyHdl ) );
+ xField->show();
+
+ maExtraControls.emplace_back(std::move(xField));
+
+ weld::Widget* pWidget = maExtraControls.back().get();
+
+ maPropertyToWindowMap[ aPropertyName ].emplace_back(pWidget);
+ maControlToPropertyMap[pWidget] = aPropertyName;
+
+ // set help id
+ setHelpId( pWidget, aHelpIds, 0 );
+ // set help text
+ setHelpText( pWidget, aHelpTexts, 0 );
+ }
+ else if (aCtrlType == "Edit")
+ {
+ std::unique_ptr<weld::Entry> xField = m_xBuilder->weld_entry(aID);
+ if (!xField && mxCustomOptionsUIBuilder)
+ xField = mxCustomOptionsUIBuilder->weld_entry(aID);
+ if (!xField)
+ continue;
+
+ OUString aCurVal;
+ PropertyValue* pVal = maPController->getValue( aPropertyName );
+ if( pVal && pVal->Value.hasValue() )
+ pVal->Value >>= aCurVal;
+ xField->set_text( aCurVal );
+ xField->connect_changed( LINK( this, PrintDialog, UIOption_EntryModifyHdl ) );
+ xField->show();
+
+ maExtraControls.emplace_back(std::move(xField));
+
+ weld::Widget* pWidget = maExtraControls.back().get();
+
+ maPropertyToWindowMap[ aPropertyName ].emplace_back(pWidget);
+ maControlToPropertyMap[pWidget] = aPropertyName;
+
+ // set help id
+ setHelpId( pWidget, aHelpIds, 0 );
+ // set help text
+ setHelpText( pWidget, aHelpTexts, 0 );
+ }
+ else
+ {
+ SAL_WARN( "vcl", "Unsupported UI option: \"" << aCtrlType << '"');
+ }
+ }
+
+ // #i106506# if no brochure button, then the singular Pages radio button
+ // makes no sense, so replace it by a FixedText label
+ if (!mxBrochureBtn->get_visible() && mxPagesBtn->get_visible())
+ {
+ mxPagesBoxTitleTxt->set_label(mxPagesBtn->get_label());
+ mxPagesBoxTitleTxt->show();
+ mxPagesBtn->hide();
+
+ mxNupPagesBox->set_accessible_relation_labeled_by(mxPagesBoxTitleTxt.get());
+ }
+
+ // update enable states
+ checkOptionalControlDependencies();
+
+ // print range not shown (currently math only) -> hide spacer line and reverse order
+ if (!mxPageRangeEdit->get_visible())
+ {
+ mxReverseOrderBox->hide();
+ }
+
+ if (!mxCustomOptionsUIBuilder)
+ mxTabCtrl->remove_page(mxTabCtrl->get_page_ident(1));
+}
+
+void PrintDialog::makeEnabled( weld::Widget* i_pWindow )
+{
+ auto it = maControlToPropertyMap.find( i_pWindow );
+ if( it != maControlToPropertyMap.end() )
+ {
+ OUString aDependency( maPController->makeEnabled( it->second ) );
+ if( !aDependency.isEmpty() )
+ updateWindowFromProperty( aDependency );
+ }
+}
+
+void PrintDialog::updateWindowFromProperty( const OUString& i_rProperty )
+{
+ beans::PropertyValue* pValue = maPController->getValue( i_rProperty );
+ auto it = maPropertyToWindowMap.find( i_rProperty );
+ if( !(pValue && it != maPropertyToWindowMap.end()) )
+ return;
+
+ const auto& rWindows( it->second );
+ if( rWindows.empty() )
+ return;
+
+ bool bVal = false;
+ sal_Int32 nVal = -1;
+ if( pValue->Value >>= bVal )
+ {
+ // we should have a CheckBox for this one
+ weld::CheckButton* pBox = dynamic_cast<weld::CheckButton*>(rWindows.front());
+ if( pBox )
+ {
+ pBox->set_active( bVal );
+ }
+ else if ( i_rProperty == "PrintProspect" )
+ {
+ // EVIL special case
+ if( bVal )
+ mxBrochureBtn->set_active(true);
+ else
+ mxPagesBtn->set_active(true);
+ }
+ else
+ {
+ SAL_WARN( "vcl", "missing a checkbox" );
+ }
+ }
+ else if( pValue->Value >>= nVal )
+ {
+ // this could be a ListBox or a RadioButtonGroup
+ weld::ComboBox* pList = dynamic_cast<weld::ComboBox*>(rWindows.front());
+ if( pList )
+ {
+ pList->set_active( static_cast< sal_uInt16 >(nVal) );
+ }
+ else if( nVal >= 0 && o3tl::make_unsigned(nVal) < rWindows.size() )
+ {
+ weld::RadioButton* pBtn = dynamic_cast<weld::RadioButton*>(rWindows[nVal]);
+ SAL_WARN_IF( !pBtn, "vcl", "unexpected control for property" );
+ if( pBtn )
+ pBtn->set_active(true);
+ }
+ }
+}
+
+bool PrintDialog::isPrintToFile() const
+{
+ return ( mxPrinters->get_active() == 0 );
+}
+
+bool PrintDialog::isCollate() const
+{
+ return mxCopyCountField->get_value() > 1 && mxCollateBox->get_active();
+}
+
+bool PrintDialog::isSingleJobs() const
+{
+ return mxSingleJobsBox->get_active();
+}
+
+bool PrintDialog::hasPreview() const
+{
+ return mxPreviewBox->get_active();
+}
+
+PropertyValue* PrintDialog::getValueForWindow( weld::Widget* i_pWindow ) const
+{
+ PropertyValue* pVal = nullptr;
+ auto it = maControlToPropertyMap.find( i_pWindow );
+ if( it != maControlToPropertyMap.end() )
+ {
+ pVal = maPController->getValue( it->second );
+ SAL_WARN_IF( !pVal, "vcl", "property value not found" );
+ }
+ else
+ {
+ OSL_FAIL( "changed control not in property map" );
+ }
+ return pVal;
+}
+
+IMPL_LINK(PrintDialog, ToggleHdl, weld::Toggleable&, rButton, void)
+{
+ if (&rButton == mxPreviewBox.get())
+ {
+ maUpdatePreviewIdle.Start();
+ }
+ else if( &rButton == mxBorderCB.get() )
+ {
+ updateNup();
+ }
+ else if (&rButton == mxSingleJobsBox.get())
+ {
+ maPController->setValue( "SinglePrintJobs",
+ Any( isSingleJobs() ) );
+ checkControlDependencies();
+ }
+ else if( &rButton == mxCollateBox.get() )
+ {
+ maPController->setValue( "Collate",
+ Any( isCollate() ) );
+ checkControlDependencies();
+ }
+ else if( &rButton == mxReverseOrderBox.get() )
+ {
+ bool bChecked = mxReverseOrderBox->get_active();
+ maPController->setReversePrint( bChecked );
+ maPController->setValue( "PrintReverse",
+ Any( bChecked ) );
+ maUpdatePreviewIdle.Start();
+ }
+ else if (&rButton == mxBrochureBtn.get())
+ {
+ PropertyValue* pVal = getValueForWindow(mxBrochureBtn.get());
+ if( pVal )
+ {
+ bool bVal = mxBrochureBtn->get_active();
+ pVal->Value <<= bVal;
+
+ checkOptionalControlDependencies();
+
+ // update preview and page settings
+ maUpdatePreviewNoCacheIdle.Start();
+ }
+ if (mxBrochureBtn->get_active())
+ {
+ mxOrientationBox->set_sensitive( false );
+ mxOrientationBox->set_active( ORIENTATION_LANDSCAPE );
+ mxNupPagesBox->set_active( 0 );
+ updateNupFromPages();
+ showAdvancedControls( false );
+ enableNupControls( false );
+ }
+ else
+ {
+ mxOrientationBox->set_sensitive( true );
+ mxOrientationBox->set_active( ORIENTATION_AUTOMATIC );
+ enableNupControls( true );
+ updateNupFromPages();
+ }
+
+ }
+}
+
+IMPL_LINK(PrintDialog, ClickHdl, weld::Button&, rButton, void)
+{
+ if (&rButton == mxOKButton.get() || &rButton == mxCancelButton.get())
+ {
+ storeToSettings();
+ m_xDialog->response(&rButton == mxOKButton.get() ? RET_OK : RET_CANCEL);
+ }
+ else if( &rButton == mxHelpButton.get() )
+ {
+ // start help system
+ Help* pHelp = Application::GetHelp();
+ if( pHelp )
+ {
+ pHelp->Start("vcl/ui/printdialog/PrintDialog", mxOKButton.get());
+ }
+ }
+ else if( &rButton == mxForwardBtn.get() )
+ {
+ previewForward();
+ }
+ else if( &rButton == mxBackwardBtn.get() )
+ {
+ previewBackward();
+ }
+ else if( &rButton == mxFirstBtn.get() )
+ {
+ previewFirst();
+ }
+ else if( &rButton == mxLastBtn.get() )
+ {
+ previewLast();
+ }
+ else
+ {
+ if( &rButton == mxSetupButton.get() )
+ {
+ maPController->setupPrinter(m_xDialog.get());
+
+ if ( !isPrintToFile() )
+ {
+ VclPtr<Printer> aPrt( maPController->getPrinter() );
+ mePaper = aPrt->GetPaper();
+
+ for (int nPaper = 0; nPaper < aPrt->GetPaperInfoCount(); nPaper++ )
+ {
+ PaperInfo aInfo = aPrt->GetPaperInfo( nPaper );
+ aInfo.doSloppyFit(true);
+ Paper ePaper = aInfo.getPaper();
+
+ if ( mePaper == ePaper )
+ {
+ mxPaperSizeBox->set_active( nPaper );
+ break;
+ }
+ }
+ }
+
+ updateOrientationBox( false );
+ setupPaperSidesBox();
+
+ // tdf#63905 don't use cache: page size may change
+ maUpdatePreviewNoCacheIdle.Start();
+ }
+ checkControlDependencies();
+ }
+
+}
+
+IMPL_LINK( PrintDialog, SelectHdl, weld::ComboBox&, rBox, void )
+{
+ if (&rBox == mxPrinters.get())
+ {
+ if ( !isPrintToFile() )
+ {
+ OUString aNewPrinter(rBox.get_active_text());
+ // set new printer
+ maPController->setPrinter( VclPtrInstance<Printer>( aNewPrinter ) );
+ maPController->resetPrinterOptions( false );
+
+ updateOrientationBox();
+
+ // update text fields
+ mxOKButton->set_label(maPrintText);
+ updatePrinterText();
+ setPaperSizes();
+ maUpdatePreviewIdle.Start();
+ }
+ else // print to file
+ {
+ // use the default printer or FIXME: the last used one?
+ maPController->setPrinter( VclPtrInstance<Printer>( Printer::GetDefaultPrinterName() ) );
+ mxOKButton->set_label(maPrintToFileText);
+ maPController->resetPrinterOptions( true );
+
+ setPaperSizes();
+ updateOrientationBox();
+ maUpdatePreviewIdle.Start();
+ }
+
+ setupPaperSidesBox();
+ }
+ else if ( &rBox == mxPaperSidesBox.get() )
+ {
+ DuplexMode eDuplex = static_cast<DuplexMode>(mxPaperSidesBox->get_active() + 1);
+ maPController->getPrinter()->SetDuplexMode( eDuplex );
+ }
+ else if( &rBox == mxOrientationBox.get() )
+ {
+ int nOrientation = mxOrientationBox->get_active();
+ if ( nOrientation != ORIENTATION_AUTOMATIC )
+ setPaperOrientation( static_cast<Orientation>( nOrientation - 1 ), true );
+
+ updateNup( false );
+ }
+ else if ( &rBox == mxNupOrderBox.get() )
+ {
+ updateNup();
+ }
+ else if( &rBox == mxNupPagesBox.get() )
+ {
+ if( !mxPagesBtn->get_active() )
+ mxPagesBtn->set_active(true);
+ updateNupFromPages( false );
+ }
+ else if ( &rBox == mxPaperSizeBox.get() )
+ {
+ VclPtr<Printer> aPrt( maPController->getPrinter() );
+ PaperInfo aInfo = aPrt->GetPaperInfo( rBox.get_active() );
+ aInfo.doSloppyFit(true);
+ mePaper = aInfo.getPaper();
+
+ if ( mePaper == PAPER_USER )
+ aPrt->SetPaperSizeUser( Size( aInfo.getWidth(), aInfo.getHeight() ) );
+ else
+ aPrt->SetPaper( mePaper );
+
+ maPController->setPaperSizeFromUser( Size( aInfo.getWidth(), aInfo.getHeight() ) );
+
+ maUpdatePreviewIdle.Start();
+ }
+}
+
+IMPL_LINK_NOARG(PrintDialog, MetricSpinModifyHdl, weld::MetricSpinButton&, void)
+{
+ checkControlDependencies();
+ updateNupFromPages();
+}
+
+IMPL_LINK_NOARG(PrintDialog, FocusOutHdl, weld::Widget&, void)
+{
+ ActivateHdl(*mxPageEdit);
+}
+
+IMPL_LINK_NOARG(PrintDialog, ActivateHdl, weld::Entry&, bool)
+{
+ sal_Int32 nPage = mxPageEdit->get_text().toInt32();
+ if (nPage < 1)
+ {
+ nPage = 1;
+ mxPageEdit->set_text("1");
+ }
+ else if (nPage > mnCachedPages)
+ {
+ nPage = mnCachedPages;
+ mxPageEdit->set_text(OUString::number(mnCachedPages));
+ }
+ int nNewCurPage = nPage - 1;
+ if (nNewCurPage != mnCurPage)
+ {
+ mnCurPage = nNewCurPage;
+ maUpdatePreviewIdle.Start();
+ }
+ return true;
+}
+
+IMPL_LINK( PrintDialog, SpinModifyHdl, weld::SpinButton&, rEdit, void )
+{
+ checkControlDependencies();
+ if (&rEdit == mxNupRowsEdt.get() || &rEdit == mxNupColEdt.get())
+ {
+ updateNupFromPages();
+ }
+ else if( &rEdit == mxCopyCountField.get() )
+ {
+ maPController->setValue( "CopyCount",
+ Any( sal_Int32(mxCopyCountField->get_value()) ) );
+ maPController->setValue( "Collate",
+ Any( isCollate() ) );
+ }
+}
+
+IMPL_LINK( PrintDialog, UIOption_CheckHdl, weld::Toggleable&, i_rBox, void )
+{
+ PropertyValue* pVal = getValueForWindow( &i_rBox );
+ if( pVal )
+ {
+ makeEnabled( &i_rBox );
+
+ bool bVal = i_rBox.get_active();
+ pVal->Value <<= bVal;
+
+ checkOptionalControlDependencies();
+
+ // update preview and page settings
+ maUpdatePreviewNoCacheIdle.Start();
+ }
+}
+
+IMPL_LINK( PrintDialog, UIOption_RadioHdl, weld::Toggleable&, i_rBtn, void )
+{
+ // this handler gets called for all radiobuttons that get unchecked, too
+ // however we only want one notification for the new value (that is for
+ // the button that gets checked)
+ if( !i_rBtn.get_active() )
+ return;
+
+ PropertyValue* pVal = getValueForWindow( &i_rBtn );
+ auto it = maControlToNumValMap.find( &i_rBtn );
+ if( !(pVal && it != maControlToNumValMap.end()) )
+ return;
+
+ makeEnabled( &i_rBtn );
+
+ sal_Int32 nVal = it->second;
+ pVal->Value <<= nVal;
+
+ updateOrientationBox();
+
+ checkOptionalControlDependencies();
+
+ // tdf#41205 give focus to the page range edit if the corresponding radio button was selected
+ if (pVal->Name == "PrintContent" && mxPageRangesRadioButton->get_active())
+ mxPageRangeEdit->grab_focus();
+
+ // update preview and page settings
+ maUpdatePreviewNoCacheIdle.Start();
+}
+
+IMPL_LINK( PrintDialog, UIOption_SelectHdl, weld::ComboBox&, i_rBox, void )
+{
+ PropertyValue* pVal = getValueForWindow( &i_rBox );
+ if( !pVal )
+ return;
+
+ makeEnabled( &i_rBox );
+
+ sal_Int32 nVal( i_rBox.get_active() );
+ pVal->Value <<= nVal;
+
+ //If we are in impress we start in print slides mode and get a
+ //maFirstPageSize for slides which are usually landscape mode, if we
+ //change to notes which are usually in portrait mode, and then visit
+ //n-up print, we will assume notes are in landscape unless we throw
+ //away maFirstPageSize when we change page content type
+ if (pVal->Name == "PageContentType")
+ maFirstPageSize = Size();
+
+ checkOptionalControlDependencies();
+
+ // update preview and page settings
+ maUpdatePreviewNoCacheIdle.Start();
+}
+
+IMPL_LINK( PrintDialog, UIOption_SpinModifyHdl, weld::SpinButton&, i_rBox, void )
+{
+ PropertyValue* pVal = getValueForWindow( &i_rBox );
+ if( pVal )
+ {
+ makeEnabled( &i_rBox );
+
+ sal_Int64 nVal = i_rBox.get_value();
+ pVal->Value <<= nVal;
+
+ checkOptionalControlDependencies();
+
+ // update preview and page settings
+ maUpdatePreviewNoCacheIdle.Start();
+ }
+}
+
+IMPL_LINK( PrintDialog, UIOption_EntryModifyHdl, weld::Entry&, i_rBox, void )
+{
+ PropertyValue* pVal = getValueForWindow( &i_rBox );
+ if( pVal )
+ {
+ makeEnabled( &i_rBox );
+
+ OUString aVal( i_rBox.get_text() );
+ pVal->Value <<= aVal;
+
+ checkOptionalControlDependencies();
+
+ // update preview and page settings
+ maUpdatePreviewNoCacheIdle.Start();
+ }
+}
+
+void PrintDialog::previewForward()
+{
+ sal_Int32 nValue = mxPageEdit->get_text().toInt32() + 1;
+ if (nValue <= mnCachedPages)
+ {
+ mxPageEdit->set_text(OUString::number(nValue));
+ ActivateHdl(*mxPageEdit);
+ }
+}
+
+void PrintDialog::previewBackward()
+{
+ sal_Int32 nValue = mxPageEdit->get_text().toInt32() - 1;
+ if (nValue >= 1)
+ {
+ mxPageEdit->set_text(OUString::number(nValue));
+ ActivateHdl(*mxPageEdit);
+ }
+}
+
+void PrintDialog::previewFirst()
+{
+ mxPageEdit->set_text("1");
+ ActivateHdl(*mxPageEdit);
+}
+
+void PrintDialog::previewLast()
+{
+ mxPageEdit->set_text(OUString::number(mnCachedPages));
+ ActivateHdl(*mxPageEdit);
+}
+
+
+static OUString getNewLabel(const OUString& aLabel, int i_nCurr, int i_nMax)
+{
+ OUString aNewText( aLabel.replaceFirst( "%p", OUString::number( i_nCurr ) ) );
+ aNewText = aNewText.replaceFirst( "%n", OUString::number( i_nMax ) );
+
+ return aNewText;
+}
+
+// PrintProgressDialog
+PrintProgressDialog::PrintProgressDialog(weld::Window* i_pParent, int i_nMax)
+ : GenericDialogController(i_pParent, "vcl/ui/printprogressdialog.ui", "PrintProgressDialog")
+ , mbCanceled(false)
+ , mnCur(0)
+ , mnMax(i_nMax)
+ , mxText(m_xBuilder->weld_label("label"))
+ , mxProgress(m_xBuilder->weld_progress_bar("progressbar"))
+ , mxButton(m_xBuilder->weld_button("cancel"))
+{
+ if( mnMax < 1 )
+ mnMax = 1;
+
+ maStr = mxText->get_label();
+
+ //just multiply largest value by 10 and take the width of that string as
+ //the max size we will want
+ mxText->set_label(getNewLabel(maStr, mnMax * 10, mnMax * 10));
+ mxText->set_size_request(mxText->get_preferred_size().Width(), -1);
+
+ //Pick a useful max width
+ mxProgress->set_size_request(mxProgress->get_approximate_digit_width() * 25, -1);
+
+ mxButton->connect_clicked( LINK( this, PrintProgressDialog, ClickHdl ) );
+
+ // after this patch f7157f04fab298423e2c4f6a7e5f8e361164b15f, we have seen the calc Max string (sometimes) look above
+ // now init to the right start values
+ mxText->set_label(getNewLabel(maStr, mnCur, mnMax));
+}
+
+PrintProgressDialog::~PrintProgressDialog()
+{
+}
+
+IMPL_LINK_NOARG(PrintProgressDialog, ClickHdl, weld::Button&, void)
+{
+ mbCanceled = true;
+}
+
+void PrintProgressDialog::setProgress( int i_nCurrent )
+{
+ mnCur = i_nCurrent;
+
+ if( mnMax < 1 )
+ mnMax = 1;
+
+ mxText->set_label(getNewLabel(maStr, mnCur, mnMax));
+
+ // here view the dialog, with the right label
+ mxProgress->set_percentage(mnCur*100/mnMax);
+}
+
+void PrintProgressDialog::tick()
+{
+ if( mnCur < mnMax )
+ setProgress( ++mnCur );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/scrwnd.cxx b/vcl/source/window/scrwnd.cxx
new file mode 100644
index 000000000..6376ea0be
--- /dev/null
+++ b/vcl/source/window/scrwnd.cxx
@@ -0,0 +1,379 @@
+/* -*- 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 <limits.h>
+
+#include <o3tl/float_int_conversion.hxx>
+#include <tools/time.hxx>
+
+#include <bitmaps.hlst>
+#include <svdata.hxx>
+#include <scrwnd.hxx>
+
+#include <vcl/timer.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <sal/log.hxx>
+
+#include <math.h>
+
+#define WHEEL_WIDTH 25
+#define WHEEL_RADIUS ((WHEEL_WIDTH) >> 1 )
+#define MAX_TIME 300
+#define MIN_TIME 20
+#define DEF_TIMEOUT 50
+
+ImplWheelWindow::ImplWheelWindow( vcl::Window* pParent ) :
+ FloatingWindow ( pParent, 0 ),
+ mnRepaintTime ( 1 ),
+ mnTimeout ( DEF_TIMEOUT ),
+ mnWheelMode ( WheelMode::NONE ),
+ mnActDist ( 0 ),
+ mnActDeltaX ( 0 ),
+ mnActDeltaY ( 0 )
+{
+ // we need a parent
+ SAL_WARN_IF( !pParent, "vcl", "ImplWheelWindow::ImplWheelWindow(): Parent not set!" );
+
+ const Size aSize( pParent->GetOutputSizePixel() );
+ const StartAutoScrollFlags nFlags = ImplGetSVData()->mpWinData->mnAutoScrollFlags;
+ const bool bHorz( nFlags & StartAutoScrollFlags::Horz );
+ const bool bVert( nFlags & StartAutoScrollFlags::Vert );
+
+ // calculate maximum speed distance
+ mnMaxWidth = static_cast<sal_uLong>( 0.4 * hypot( static_cast<double>(aSize.Width()), aSize.Height() ) );
+
+ // create wheel window
+ SetTitleType( FloatWinTitleType::NONE );
+ ImplCreateImageList();
+ BitmapEx aBmp(SV_RESID_BITMAP_SCROLLMSK);
+ ImplSetRegion(aBmp.GetBitmap());
+
+ // set wheel mode
+ if( bHorz && bVert )
+ ImplSetWheelMode( WheelMode::VH );
+ else if( bHorz )
+ ImplSetWheelMode( WheelMode::H );
+ else
+ ImplSetWheelMode( WheelMode::V );
+
+ // init timer
+ mpTimer.reset(new Timer("WheelWindowTimer"));
+ mpTimer->SetInvokeHandler( LINK( this, ImplWheelWindow, ImplScrollHdl ) );
+ mpTimer->SetTimeout( mnTimeout );
+ mpTimer->Start();
+
+ CaptureMouse();
+}
+
+ImplWheelWindow::~ImplWheelWindow()
+{
+ disposeOnce();
+}
+
+void ImplWheelWindow::dispose()
+{
+ ImplStop();
+ mpTimer.reset();
+ FloatingWindow::dispose();
+}
+
+void ImplWheelWindow::ImplStop()
+{
+ ReleaseMouse();
+ mpTimer->Stop();
+ Show(false);
+}
+
+void ImplWheelWindow::ImplSetRegion( const Bitmap& rRegionBmp )
+{
+ Point aPos( GetPointerPosPixel() );
+ const Size aSize( rRegionBmp.GetSizePixel() );
+ const tools::Rectangle aRect( Point(), aSize );
+
+ maCenter = maLastMousePos = aPos;
+ aPos.AdjustX( -(aSize.Width() >> 1) );
+ aPos.AdjustY( -(aSize.Height() >> 1) );
+
+ SetPosSizePixel( aPos, aSize );
+ SetWindowRegionPixel( rRegionBmp.CreateRegion( COL_BLACK, aRect ) );
+}
+
+void ImplWheelWindow::ImplCreateImageList()
+{
+ maImgList.emplace_back(StockImage::Yes, SV_RESID_BITMAP_SCROLLVH);
+ maImgList.emplace_back(StockImage::Yes, SV_RESID_BITMAP_SCROLLV);
+ maImgList.emplace_back(StockImage::Yes, SV_RESID_BITMAP_SCROLLH);
+ maImgList.emplace_back(StockImage::Yes, SV_RESID_BITMAP_WHEELVH);
+ maImgList.emplace_back(StockImage::Yes, SV_RESID_BITMAP_WHEELV);
+ maImgList.emplace_back(StockImage::Yes, SV_RESID_BITMAP_WHEELH);
+}
+
+void ImplWheelWindow::ImplSetWheelMode( WheelMode nWheelMode )
+{
+ if( nWheelMode == mnWheelMode )
+ return;
+
+ mnWheelMode = nWheelMode;
+
+ if( WheelMode::NONE == mnWheelMode )
+ {
+ if( IsVisible() )
+ Hide();
+ }
+ else
+ {
+ if( !IsVisible() )
+ Show();
+
+ Invalidate();
+ }
+}
+
+void ImplWheelWindow::ImplDrawWheel(vcl::RenderContext& rRenderContext)
+{
+ int nIndex;
+
+ switch (mnWheelMode)
+ {
+ case WheelMode::VH:
+ nIndex = 0;
+ break;
+ case WheelMode::V:
+ nIndex = 1;
+ break;
+ case WheelMode::H:
+ nIndex = 2;
+ break;
+ case WheelMode::ScrollVH:
+ nIndex = 3;
+ break;
+ case WheelMode::ScrollV:
+ nIndex = 4;
+ break;
+ case WheelMode::ScrollH:
+ nIndex = 5;
+ break;
+ default:
+ nIndex = -1;
+ break;
+ }
+
+ if (nIndex >= 0)
+ rRenderContext.DrawImage(Point(), maImgList[nIndex]);
+}
+
+void ImplWheelWindow::ImplRecalcScrollValues()
+{
+ if( mnActDist < WHEEL_RADIUS )
+ {
+ mnActDeltaX = mnActDeltaY = 0;
+ mnTimeout = DEF_TIMEOUT;
+ }
+ else
+ {
+ sal_uInt64 nCurTime;
+
+ // calc current time
+ if( mnMaxWidth )
+ {
+ const double fExp = ( static_cast<double>(mnActDist) / mnMaxWidth ) * log10( double(MAX_TIME) / MIN_TIME );
+ nCurTime = static_cast<sal_uInt64>( MAX_TIME / pow( 10., fExp ) );
+ }
+ else
+ nCurTime = MAX_TIME;
+
+ if( !nCurTime )
+ nCurTime = 1;
+
+ if( mnRepaintTime <= nCurTime )
+ mnTimeout = nCurTime - mnRepaintTime;
+ else
+ {
+ sal_uInt64 nMult = mnRepaintTime / nCurTime;
+
+ if( !( mnRepaintTime % nCurTime ) )
+ mnTimeout = 0;
+ else
+ mnTimeout = ++nMult * nCurTime - mnRepaintTime;
+
+ double fValX = static_cast<double>(mnActDeltaX) * nMult;
+ double fValY = static_cast<double>(mnActDeltaY) * nMult;
+
+ mnActDeltaX = o3tl::saturating_cast<tools::Long>(fValX);
+ mnActDeltaY = o3tl::saturating_cast<tools::Long>(fValY);
+ }
+ }
+}
+
+PointerStyle ImplWheelWindow::ImplGetMousePointer( tools::Long nDistX, tools::Long nDistY ) const
+{
+ PointerStyle eStyle;
+ const StartAutoScrollFlags nFlags = ImplGetSVData()->mpWinData->mnAutoScrollFlags;
+ const bool bHorz( nFlags & StartAutoScrollFlags::Horz );
+ const bool bVert( nFlags & StartAutoScrollFlags::Vert );
+
+ if( bHorz || bVert )
+ {
+ if( mnActDist < WHEEL_RADIUS )
+ {
+ if( bHorz && bVert )
+ eStyle = PointerStyle::AutoScrollNSWE;
+ else if( bHorz )
+ eStyle = PointerStyle::AutoScrollWE;
+ else
+ eStyle = PointerStyle::AutoScrollNS;
+ }
+ else
+ {
+ double fAngle = basegfx::rad2deg(atan2(static_cast<double>(-nDistY), nDistX));
+
+ if( fAngle < 0.0 )
+ fAngle += 360.;
+
+ if( bHorz && bVert )
+ {
+ if( fAngle >= 22.5 && fAngle <= 67.5 )
+ eStyle = PointerStyle::AutoScrollNE;
+ else if( fAngle >= 67.5 && fAngle <= 112.5 )
+ eStyle = PointerStyle::AutoScrollN;
+ else if( fAngle >= 112.5 && fAngle <= 157.5 )
+ eStyle = PointerStyle::AutoScrollNW;
+ else if( fAngle >= 157.5 && fAngle <= 202.5 )
+ eStyle = PointerStyle::AutoScrollW;
+ else if( fAngle >= 202.5 && fAngle <= 247.5 )
+ eStyle = PointerStyle::AutoScrollSW;
+ else if( fAngle >= 247.5 && fAngle <= 292.5 )
+ eStyle = PointerStyle::AutoScrollS;
+ else if( fAngle >= 292.5 && fAngle <= 337.5 )
+ eStyle = PointerStyle::AutoScrollSE;
+ else
+ eStyle = PointerStyle::AutoScrollE;
+ }
+ else if( bHorz )
+ {
+ if( fAngle >= 270. || fAngle <= 90. )
+ eStyle = PointerStyle::AutoScrollE;
+ else
+ eStyle = PointerStyle::AutoScrollW;
+ }
+ else
+ {
+ if( fAngle >= 0. && fAngle <= 180. )
+ eStyle = PointerStyle::AutoScrollN;
+ else
+ eStyle = PointerStyle::AutoScrollS;
+ }
+ }
+ }
+ else
+ eStyle = PointerStyle::Arrow;
+
+ return eStyle;
+}
+
+void ImplWheelWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ ImplDrawWheel(rRenderContext);
+}
+
+void ImplWheelWindow::MouseMove( const MouseEvent& rMEvt )
+{
+ FloatingWindow::MouseMove( rMEvt );
+
+ const Point aMousePos( OutputToScreenPixel( rMEvt.GetPosPixel() ) );
+ const tools::Long nDistX = aMousePos.X() - maCenter.X();
+ const tools::Long nDistY = aMousePos.Y() - maCenter.Y();
+
+ mnActDist = static_cast<sal_uLong>(hypot( static_cast<double>(nDistX), nDistY ));
+
+ const PointerStyle eActStyle = ImplGetMousePointer( nDistX, nDistY );
+ const StartAutoScrollFlags nFlags = ImplGetSVData()->mpWinData->mnAutoScrollFlags;
+ const bool bHorz( nFlags & StartAutoScrollFlags::Horz );
+ const bool bVert( nFlags & StartAutoScrollFlags::Vert );
+ const bool bOuter = mnActDist > WHEEL_RADIUS;
+
+ if( bOuter && ( maLastMousePos != aMousePos ) )
+ {
+ switch( eActStyle )
+ {
+ case PointerStyle::AutoScrollN: mnActDeltaX = +0; mnActDeltaY = +1; break;
+ case PointerStyle::AutoScrollS: mnActDeltaX = +0; mnActDeltaY = -1; break;
+ case PointerStyle::AutoScrollW: mnActDeltaX = +1; mnActDeltaY = +0; break;
+ case PointerStyle::AutoScrollE: mnActDeltaX = -1; mnActDeltaY = +0; break;
+ case PointerStyle::AutoScrollNW: mnActDeltaX = +1; mnActDeltaY = +1; break;
+ case PointerStyle::AutoScrollNE: mnActDeltaX = -1; mnActDeltaY = +1; break;
+ case PointerStyle::AutoScrollSW: mnActDeltaX = +1; mnActDeltaY = -1; break;
+ case PointerStyle::AutoScrollSE: mnActDeltaX = -1; mnActDeltaY = -1; break;
+
+ default:
+ break;
+ }
+ }
+
+ ImplRecalcScrollValues();
+ maLastMousePos = aMousePos;
+ SetPointer( eActStyle );
+
+ if( bHorz && bVert )
+ ImplSetWheelMode( bOuter ? WheelMode::ScrollVH : WheelMode::VH );
+ else if( bHorz )
+ ImplSetWheelMode( bOuter ? WheelMode::ScrollH : WheelMode::H );
+ else
+ ImplSetWheelMode( bOuter ? WheelMode::ScrollV : WheelMode::V );
+}
+
+void ImplWheelWindow::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ if( mnActDist > WHEEL_RADIUS )
+ GetParent()->EndAutoScroll();
+ else
+ FloatingWindow::MouseButtonUp( rMEvt );
+}
+
+IMPL_LINK_NOARG(ImplWheelWindow, ImplScrollHdl, Timer *, void)
+{
+ if ( mnActDeltaX || mnActDeltaY )
+ {
+ vcl::Window* pWindow = GetParent();
+ const Point aMousePos( pWindow->OutputToScreenPixel( pWindow->GetPointerPosPixel() ) );
+ Point aCmdMousePos( pWindow->ImplFrameToOutput( aMousePos ) );
+ CommandScrollData aScrollData( mnActDeltaX, mnActDeltaY );
+ CommandEvent aCEvt( aCmdMousePos, CommandEventId::AutoScroll, true, &aScrollData );
+ NotifyEvent aNCmdEvt( MouseNotifyEvent::COMMAND, pWindow, &aCEvt );
+
+ if ( !ImplCallPreNotify( aNCmdEvt ) )
+ {
+ const sal_uInt64 nTime = tools::Time::GetSystemTicks();
+ VclPtr<ImplWheelWindow> xWin(this);
+ pWindow->Command( aCEvt );
+ if( xWin->isDisposed() )
+ return;
+ mnRepaintTime = std::max( tools::Time::GetSystemTicks() - nTime, sal_uInt64(1) );
+ ImplRecalcScrollValues();
+ }
+ }
+
+ if ( mnTimeout != mpTimer->GetTimeout() )
+ mpTimer->SetTimeout( mnTimeout );
+ mpTimer->Start();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/seleng.cxx b/vcl/source/window/seleng.cxx
new file mode 100644
index 000000000..f81ffe6cd
--- /dev/null
+++ b/vcl/source/window/seleng.cxx
@@ -0,0 +1,421 @@
+/* -*- 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 <vcl/commandevent.hxx>
+#include <vcl/window.hxx>
+#include <vcl/seleng.hxx>
+#include <comphelper/lok.hxx>
+#include <sal/log.hxx>
+
+FunctionSet::~FunctionSet()
+{
+}
+
+inline bool SelectionEngine::ShouldDeselect( bool bModifierKey1 ) const
+{
+ return eSelMode != SelectionMode::Multiple || !bModifierKey1;
+}
+
+// TODO: throw out FunctionSet::SelectAtPoint
+
+SelectionEngine::SelectionEngine( vcl::Window* pWindow, FunctionSet* pFuncSet ) :
+ pWin( pWindow ),
+ aWTimer( "vcl::SelectionEngine aWTimer" ),
+ nUpdateInterval( SELENG_AUTOREPEAT_INTERVAL )
+{
+ eSelMode = SelectionMode::Single;
+ pFunctionSet = pFuncSet;
+ nFlags = SelectionEngineFlags::EXPANDONMOVE;
+ nLockedMods = 0;
+
+ aWTimer.SetInvokeHandler( LINK( this, SelectionEngine, ImpWatchDog ) );
+ aWTimer.SetTimeout( nUpdateInterval );
+}
+
+SelectionEngine::~SelectionEngine()
+{
+ aWTimer.Stop();
+}
+
+IMPL_LINK_NOARG(SelectionEngine, ImpWatchDog, Timer *, void)
+{
+ if ( !aArea.Contains( aLastMove.GetPosPixel() ) )
+ SelMouseMove( aLastMove );
+}
+
+void SelectionEngine::SetSelectionMode( SelectionMode eMode )
+{
+ eSelMode = eMode;
+}
+
+void SelectionEngine::CursorPosChanging( bool bShift, bool bMod1 )
+{
+ if ( !pFunctionSet )
+ return;
+
+ if ( bShift && eSelMode != SelectionMode::Single )
+ {
+ if ( IsAddMode() )
+ {
+ if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
+ {
+ pFunctionSet->CreateAnchor();
+ nFlags |= SelectionEngineFlags::HAS_ANCH;
+ }
+ }
+ else
+ {
+ if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
+ {
+ if( ShouldDeselect( bMod1 ) )
+ pFunctionSet->DeselectAll();
+ pFunctionSet->CreateAnchor();
+ nFlags |= SelectionEngineFlags::HAS_ANCH;
+ }
+ }
+ }
+ else
+ {
+ if ( IsAddMode() )
+ {
+ if ( nFlags & SelectionEngineFlags::HAS_ANCH )
+ {
+ // pFunctionSet->CreateCursor();
+ pFunctionSet->DestroyAnchor();
+ nFlags &= ~SelectionEngineFlags::HAS_ANCH;
+ }
+ }
+ else
+ {
+ if( ShouldDeselect( bMod1 ) )
+ pFunctionSet->DeselectAll();
+ else
+ pFunctionSet->DestroyAnchor();
+ nFlags &= ~SelectionEngineFlags::HAS_ANCH;
+ }
+ }
+}
+
+bool SelectionEngine::SelMouseButtonDown( const MouseEvent& rMEvt )
+{
+ nFlags &= ~SelectionEngineFlags::CMDEVT;
+ if ( !pFunctionSet || rMEvt.GetClicks() > 1 )
+ return false;
+
+ sal_uInt16 nModifier = rMEvt.GetModifier() | nLockedMods;
+ bool nSwap = comphelper::LibreOfficeKit::isActive() && (nModifier & KEY_MOD1) && (nModifier & KEY_MOD2);
+
+ if ( !nSwap && (nModifier & KEY_MOD2) )
+ return false;
+ // in SingleSelection: filter Control-Key,
+ // so that a D&D can be also started with a Ctrl-Click
+ if ( nModifier == KEY_MOD1 && eSelMode == SelectionMode::Single )
+ nModifier = 0;
+
+ Point aPos = rMEvt.GetPosPixel();
+ aLastMove = rMEvt;
+
+ if( !rMEvt.IsRight() )
+ {
+ CaptureMouse();
+ nFlags |= SelectionEngineFlags::IN_SEL;
+ }
+ else
+ {
+ nModifier = 0;
+ }
+
+ if (nSwap)
+ {
+ pFunctionSet->CreateAnchor();
+ pFunctionSet->SetCursorAtPoint( aPos );
+ return true;
+ }
+
+ switch ( nModifier )
+ {
+ case 0: // KEY_NO_KEY
+ {
+ bool bSelAtPoint = pFunctionSet->IsSelectionAtPoint( aPos );
+ nFlags &= ~SelectionEngineFlags::IN_ADD;
+ if ( (nFlags & SelectionEngineFlags::DRG_ENAB) && bSelAtPoint )
+ {
+ nFlags |= SelectionEngineFlags::WAIT_UPEVT;
+ nFlags &= ~SelectionEngineFlags::IN_SEL;
+ ReleaseMouse();
+ return true; // wait for STARTDRAG-Command-Event
+ }
+ if ( eSelMode != SelectionMode::Single )
+ {
+ if( !IsAddMode() )
+ pFunctionSet->DeselectAll();
+ else
+ pFunctionSet->DestroyAnchor();
+ nFlags &= ~SelectionEngineFlags::HAS_ANCH; // bHasAnchor = false;
+ }
+ pFunctionSet->SetCursorAtPoint( aPos );
+ // special case Single-Selection, to enable simple Select+Drag
+ if (eSelMode == SelectionMode::Single && (nFlags & SelectionEngineFlags::DRG_ENAB))
+ nFlags |= SelectionEngineFlags::WAIT_UPEVT;
+ return true;
+ }
+
+ case KEY_SHIFT:
+ if ( eSelMode == SelectionMode::Single )
+ {
+ ReleaseMouse();
+ nFlags &= ~SelectionEngineFlags::IN_SEL;
+ return false;
+ }
+ if ( nFlags & SelectionEngineFlags::ADD_ALW )
+ nFlags |= SelectionEngineFlags::IN_ADD;
+ else
+ nFlags &= ~SelectionEngineFlags::IN_ADD;
+
+ if( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
+ {
+ if ( !(nFlags & SelectionEngineFlags::IN_ADD) )
+ pFunctionSet->DeselectAll();
+ pFunctionSet->CreateAnchor();
+ nFlags |= SelectionEngineFlags::HAS_ANCH;
+ }
+ pFunctionSet->SetCursorAtPoint( aPos );
+ return true;
+
+ case KEY_MOD1:
+ // allow Control only for Multi-Select
+ if ( eSelMode != SelectionMode::Multiple )
+ {
+ nFlags &= ~SelectionEngineFlags::IN_SEL;
+ ReleaseMouse();
+ return true; // skip Mouse-Click
+ }
+ if ( nFlags & SelectionEngineFlags::HAS_ANCH )
+ {
+ // pFunctionSet->CreateCursor();
+ pFunctionSet->DestroyAnchor();
+ nFlags &= ~SelectionEngineFlags::HAS_ANCH;
+ }
+ if ( pFunctionSet->IsSelectionAtPoint( aPos ) )
+ {
+ pFunctionSet->DeselectAtPoint( aPos );
+ pFunctionSet->SetCursorAtPoint( aPos, true );
+ }
+ else
+ {
+ pFunctionSet->SetCursorAtPoint( aPos );
+ }
+ return true;
+
+ case KEY_SHIFT + KEY_MOD1:
+ if ( eSelMode != SelectionMode::Multiple )
+ {
+ ReleaseMouse();
+ nFlags &= ~SelectionEngineFlags::IN_SEL;
+ return false;
+ }
+ nFlags |= SelectionEngineFlags::IN_ADD; //bIsInAddMode = true;
+ if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
+ {
+ pFunctionSet->CreateAnchor();
+ nFlags |= SelectionEngineFlags::HAS_ANCH;
+ }
+ pFunctionSet->SetCursorAtPoint( aPos );
+ return true;
+ }
+
+ return false;
+}
+
+bool SelectionEngine::SelMouseButtonUp( const MouseEvent& rMEvt )
+{
+ aWTimer.Stop();
+ if (!pFunctionSet)
+ {
+ const SelectionEngineFlags nMask = SelectionEngineFlags::CMDEVT | SelectionEngineFlags::WAIT_UPEVT | SelectionEngineFlags::IN_SEL;
+ nFlags &= ~nMask;
+ return false;
+ }
+
+ if (!rMEvt.IsRight())
+ ReleaseMouse();
+
+#if defined IOS || defined ANDROID
+ const bool bDoMessWithSelection = !rMEvt.IsRight();
+#else
+ constexpr bool bDoMessWithSelection = true;
+#endif
+
+ if( (nFlags & SelectionEngineFlags::WAIT_UPEVT) && !(nFlags & SelectionEngineFlags::CMDEVT) &&
+ eSelMode != SelectionMode::Single)
+ {
+ // MouseButtonDown in Sel but no CommandEvent yet
+ // ==> deselect
+ sal_uInt16 nModifier = aLastMove.GetModifier() | nLockedMods;
+ if( nModifier == KEY_MOD1 || IsAlwaysAdding() )
+ {
+ if( !(nModifier & KEY_SHIFT) )
+ {
+ pFunctionSet->DestroyAnchor();
+ nFlags &= ~SelectionEngineFlags::HAS_ANCH; // uncheck anchor
+ }
+ pFunctionSet->DeselectAtPoint( aLastMove.GetPosPixel() );
+ nFlags &= ~SelectionEngineFlags::HAS_ANCH; // uncheck anchor
+ if (bDoMessWithSelection)
+ pFunctionSet->SetCursorAtPoint( aLastMove.GetPosPixel(), true );
+ }
+ else
+ {
+ if (bDoMessWithSelection)
+ pFunctionSet->DeselectAll();
+ nFlags &= ~SelectionEngineFlags::HAS_ANCH; // uncheck anchor
+ if (bDoMessWithSelection)
+ pFunctionSet->SetCursorAtPoint( aLastMove.GetPosPixel() );
+ }
+ }
+
+ const SelectionEngineFlags nMask = SelectionEngineFlags::CMDEVT | SelectionEngineFlags::WAIT_UPEVT | SelectionEngineFlags::IN_SEL;
+ nFlags &= ~nMask;
+ return true;
+}
+
+void SelectionEngine::ReleaseMouse()
+{
+ if (!pWin || !pWin->IsMouseCaptured())
+ return;
+ pWin->ReleaseMouse();
+}
+
+void SelectionEngine::CaptureMouse()
+{
+ if (!pWin || pWin->IsMouseCaptured())
+ return;
+ pWin->CaptureMouse();
+}
+
+bool SelectionEngine::SelMouseMove( const MouseEvent& rMEvt )
+{
+
+ if ( !pFunctionSet || !(nFlags & SelectionEngineFlags::IN_SEL) ||
+ (nFlags & (SelectionEngineFlags::CMDEVT | SelectionEngineFlags::WAIT_UPEVT)) )
+ return false;
+
+ if( !(nFlags & SelectionEngineFlags::EXPANDONMOVE) )
+ return false; // wait for DragEvent!
+
+ aLastMove = rMEvt;
+ // if the mouse is outside the area, the frequency of
+ // SetCursorAtPoint() is only set by the Timer
+ if( aWTimer.IsActive() && !aArea.Contains( rMEvt.GetPosPixel() ))
+ return true;
+
+ aWTimer.SetTimeout( nUpdateInterval );
+ if (!comphelper::LibreOfficeKit::isActive())
+ // Generating fake mouse moves does not work with LOK.
+ aWTimer.Start();
+ if ( eSelMode != SelectionMode::Single )
+ {
+ if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) )
+ {
+ pFunctionSet->CreateAnchor();
+ nFlags |= SelectionEngineFlags::HAS_ANCH;
+ }
+ }
+
+ pFunctionSet->SetCursorAtPoint( rMEvt.GetPosPixel() );
+
+ return true;
+}
+
+void SelectionEngine::SetWindow( vcl::Window* pNewWin )
+{
+ if( pNewWin != pWin )
+ {
+ if (nFlags & SelectionEngineFlags::IN_SEL)
+ ReleaseMouse();
+ pWin = pNewWin;
+ if (nFlags & SelectionEngineFlags::IN_SEL)
+ CaptureMouse();
+ }
+}
+
+void SelectionEngine::Reset()
+{
+ aWTimer.Stop();
+ if (nFlags & SelectionEngineFlags::IN_SEL)
+ ReleaseMouse();
+ nFlags &= ~SelectionEngineFlags(SelectionEngineFlags::HAS_ANCH | SelectionEngineFlags::IN_SEL);
+ nLockedMods = 0;
+}
+
+bool SelectionEngine::Command( const CommandEvent& rCEvt )
+{
+ // Timer aWTimer is active during enlarging a selection
+ if ( !pFunctionSet || aWTimer.IsActive() )
+ return false;
+ aWTimer.Stop();
+ if ( rCEvt.GetCommand() != CommandEventId::StartDrag )
+ return false;
+
+ nFlags |= SelectionEngineFlags::CMDEVT;
+ if ( nFlags & SelectionEngineFlags::DRG_ENAB )
+ {
+ SAL_WARN_IF( !rCEvt.IsMouseEvent(), "vcl", "STARTDRAG: Not a MouseEvent" );
+ if ( pFunctionSet->IsSelectionAtPoint( rCEvt.GetMousePosPixel() ) )
+ {
+ aLastMove = MouseEvent( rCEvt.GetMousePosPixel(),
+ aLastMove.GetClicks(), aLastMove.GetMode(),
+ aLastMove.GetButtons(), aLastMove.GetModifier() );
+ pFunctionSet->BeginDrag();
+ const SelectionEngineFlags nMask = SelectionEngineFlags::CMDEVT|SelectionEngineFlags::WAIT_UPEVT|SelectionEngineFlags::IN_SEL;
+ nFlags &= ~nMask;
+ }
+ else
+ nFlags &= ~SelectionEngineFlags::CMDEVT;
+ }
+ else
+ nFlags &= ~SelectionEngineFlags::CMDEVT;
+ return true;
+}
+
+void SelectionEngine::SetUpdateInterval( sal_uLong nInterval )
+{
+ if (nInterval < SELENG_AUTOREPEAT_INTERVAL_MIN)
+ // Set a lower threshold. On Windows, setting this value too low
+ // would cause selection to get updated indefinitely.
+ nInterval = SELENG_AUTOREPEAT_INTERVAL_MIN;
+
+ if (nUpdateInterval == nInterval)
+ // no update needed.
+ return;
+
+ if (aWTimer.IsActive())
+ {
+ // reset the timer right away on interval change.
+ aWTimer.Stop();
+ aWTimer.SetTimeout(nInterval);
+ aWTimer.Start();
+ }
+ else
+ aWTimer.SetTimeout(nInterval);
+
+ nUpdateInterval = nInterval;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/settings.cxx b/vcl/source/window/settings.cxx
new file mode 100644
index 000000000..f9af6982a
--- /dev/null
+++ b/vcl/source/window/settings.cxx
@@ -0,0 +1,266 @@
+/* -*- 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 <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/settings.hxx>
+
+#include <unotools/configmgr.hxx>
+#include <unotools/confignode.hxx>
+
+#include <comphelper/processfactory.hxx>
+
+#include <salframe.hxx>
+#include <brdwin.hxx>
+
+#include <window.h>
+
+namespace vcl {
+
+void WindowOutputDevice::SetSettings( const AllSettings& rSettings )
+{
+ SetSettings( rSettings, false );
+}
+
+void WindowOutputDevice::SetSettings( const AllSettings& rSettings, bool bChild )
+{
+
+ if ( auto pBorderWindow = mxOwnerWindow->mpWindowImpl->mpBorderWindow.get() )
+ {
+ static_cast<vcl::WindowOutputDevice*>(pBorderWindow->GetOutDev())->SetSettings( rSettings, false );
+ if ( (pBorderWindow->GetType() == WindowType::BORDERWINDOW) &&
+ static_cast<ImplBorderWindow*>(pBorderWindow)->mpMenuBarWindow )
+ static_cast<vcl::WindowOutputDevice*>(static_cast<ImplBorderWindow*>(pBorderWindow)->mpMenuBarWindow->GetOutDev())->SetSettings( rSettings, true );
+ }
+
+ AllSettings aOldSettings(*mxSettings);
+ OutputDevice::SetSettings( rSettings );
+ AllSettingsFlags nChangeFlags = aOldSettings.GetChangeFlags( rSettings );
+
+ // recalculate AppFont-resolution and DPI-resolution
+ mxOwnerWindow->ImplInitResolutionSettings();
+
+ if ( bool(nChangeFlags) )
+ {
+ DataChangedEvent aDCEvt( DataChangedEventType::SETTINGS, &aOldSettings, nChangeFlags );
+ mxOwnerWindow->DataChanged( aDCEvt );
+ }
+
+ if ( bChild )
+ {
+ vcl::Window* pChild = mxOwnerWindow->mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ static_cast<vcl::WindowOutputDevice*>(pChild->GetOutDev())->SetSettings( rSettings, bChild );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+ }
+}
+
+void Window::UpdateSettings( const AllSettings& rSettings, bool bChild )
+{
+
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ mpWindowImpl->mpBorderWindow->UpdateSettings( rSettings );
+ if ( (mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW) &&
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow->UpdateSettings( rSettings, true );
+ }
+
+ AllSettings aOldSettings(*mpWindowImpl->mxOutDev->mxSettings);
+ AllSettingsFlags nChangeFlags = mpWindowImpl->mxOutDev->mxSettings->Update( AllSettings::GetWindowUpdate(), rSettings );
+
+ // recalculate AppFont-resolution and DPI-resolution
+ ImplInitResolutionSettings();
+
+ /* #i73785#
+ * do not overwrite a WheelBehavior with false
+ * this looks kind of a hack, but WheelBehavior
+ * is always a local change, not a system property,
+ * so we can spare all our users the hassle of reacting on
+ * this in their respective DataChanged.
+ */
+ MouseSettings aSet( mpWindowImpl->mxOutDev->mxSettings->GetMouseSettings() );
+ aSet.SetWheelBehavior( aOldSettings.GetMouseSettings().GetWheelBehavior() );
+ mpWindowImpl->mxOutDev->mxSettings->SetMouseSettings( aSet );
+
+ if( (nChangeFlags & AllSettingsFlags::STYLE) && IsBackground() )
+ {
+ Wallpaper aWallpaper = GetBackground();
+ if( !aWallpaper.IsBitmap() && !aWallpaper.IsGradient() )
+ {
+ if ( mpWindowImpl->mnStyle & WB_3DLOOK )
+ {
+ if (aOldSettings.GetStyleSettings().GetFaceColor() != rSettings.GetStyleSettings().GetFaceColor())
+ SetBackground( Wallpaper( rSettings.GetStyleSettings().GetFaceColor() ) );
+ }
+ else
+ {
+ if (aOldSettings.GetStyleSettings().GetWindowColor() != rSettings.GetStyleSettings().GetWindowColor())
+ SetBackground( Wallpaper( rSettings.GetStyleSettings().GetWindowColor() ) );
+ }
+ }
+ }
+
+ if ( bool(nChangeFlags) )
+ {
+ DataChangedEvent aDCEvt( DataChangedEventType::SETTINGS, &aOldSettings, nChangeFlags );
+ DataChanged( aDCEvt );
+ // notify data change handler
+ CallEventListeners( VclEventId::WindowDataChanged, &aDCEvt);
+ }
+
+ if ( bChild )
+ {
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->UpdateSettings( rSettings, bChild );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+ }
+}
+
+void Window::ImplUpdateGlobalSettings( AllSettings& rSettings, bool bCallHdl ) const
+{
+ StyleSettings aTmpSt( rSettings.GetStyleSettings() );
+ aTmpSt.SetHighContrastMode( false );
+ rSettings.SetStyleSettings( aTmpSt );
+ ImplGetFrame()->UpdateSettings( rSettings );
+
+ StyleSettings aStyleSettings = rSettings.GetStyleSettings();
+
+ vcl::Font aFont = aStyleSettings.GetMenuFont();
+ int defFontheight = aFont.GetFontHeight();
+
+ // if the UI is korean, chinese or another locale
+ // where the system font size is known to be often too small to
+ // generate readable fonts enforce a minimum font size of 9 points
+ bool bBrokenLangFontHeight = MsLangId::isCJK(Application::GetSettings().GetUILanguageTag().getLanguageType());
+ if (bBrokenLangFontHeight)
+ defFontheight = std::max(9, defFontheight);
+
+ // i22098, toolfont will be scaled differently to avoid bloated rulers and status bars for big fonts
+ int toolfontheight = defFontheight;
+ if( toolfontheight > 9 )
+ toolfontheight = (defFontheight+8) / 2;
+
+ aFont = aStyleSettings.GetAppFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetAppFont( aFont );
+ aFont = aStyleSettings.GetTitleFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetTitleFont( aFont );
+ aFont = aStyleSettings.GetFloatTitleFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetFloatTitleFont( aFont );
+ // keep menu and help font size from system unless in broken locale size
+ if( bBrokenLangFontHeight )
+ {
+ aFont = aStyleSettings.GetMenuFont();
+ if( aFont.GetFontHeight() < defFontheight )
+ {
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetMenuFont( aFont );
+ }
+ aFont = aStyleSettings.GetHelpFont();
+ if( aFont.GetFontHeight() < defFontheight )
+ {
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetHelpFont( aFont );
+ }
+ }
+
+ // use different height for toolfont
+ aFont = aStyleSettings.GetToolFont();
+ aFont.SetFontHeight( toolfontheight );
+ aStyleSettings.SetToolFont( aFont );
+
+ aFont = aStyleSettings.GetLabelFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetLabelFont( aFont );
+ aFont = aStyleSettings.GetRadioCheckFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetRadioCheckFont( aFont );
+ aFont = aStyleSettings.GetPushButtonFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetPushButtonFont( aFont );
+ aFont = aStyleSettings.GetFieldFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetFieldFont( aFont );
+ aFont = aStyleSettings.GetIconFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetIconFont( aFont );
+ aFont = aStyleSettings.GetTabFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetTabFont( aFont );
+ aFont = aStyleSettings.GetGroupFont();
+ aFont.SetFontHeight( defFontheight );
+ aStyleSettings.SetGroupFont( aFont );
+
+ rSettings.SetStyleSettings( aStyleSettings );
+
+ bool bForceHCMode = false;
+
+ // auto detect HC mode; if the system already set it to "yes"
+ // (see above) then accept that
+ if (!rSettings.GetStyleSettings().GetHighContrastMode() && !utl::ConfigManager::IsFuzzing())
+ {
+ bool bAutoHCMode = true;
+ utl::OConfigurationNode aNode = utl::OConfigurationTreeRoot::tryCreateWithComponentContext(
+ comphelper::getProcessComponentContext(),
+ "org.openoffice.Office.Common/Accessibility" ); // note: case sensitive !
+ if ( aNode.isValid() )
+ {
+ css::uno::Any aValue = aNode.getNodeValue( "AutoDetectSystemHC" );
+ bool bTmp = false;
+ if( aValue >>= bTmp )
+ bAutoHCMode = bTmp;
+ }
+ if( bAutoHCMode )
+ {
+ if( rSettings.GetStyleSettings().GetFaceColor().IsDark() ||
+ rSettings.GetStyleSettings().GetWindowColor().IsDark() )
+ bForceHCMode = true;
+ }
+ }
+
+ static const char* pEnvHC = getenv( "SAL_FORCE_HC" );
+ if( pEnvHC && *pEnvHC )
+ bForceHCMode = true;
+
+ if( bForceHCMode )
+ {
+ aStyleSettings = rSettings.GetStyleSettings();
+ aStyleSettings.SetHighContrastMode( true );
+ rSettings.SetStyleSettings( aStyleSettings );
+ }
+
+ if ( bCallHdl )
+ GetpApp()->OverrideSystemSettings( rSettings );
+}
+
+} /*namespace vcl*/
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/split.cxx b/vcl/source/window/split.cxx
new file mode 100644
index 000000000..df631b270
--- /dev/null
+++ b/vcl/source/window/split.cxx
@@ -0,0 +1,698 @@
+/* -*- 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 <tools/poly.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/split.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/taskpanelist.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <vcl/lazydelete.hxx>
+
+#include <window.h>
+
+namespace
+{
+ Wallpaper& ImplBlackWall()
+ {
+ static vcl::DeleteOnDeinit< Wallpaper > SINGLETON(COL_BLACK);
+ return *SINGLETON.get();
+ }
+ Wallpaper& ImplWhiteWall()
+ {
+ static vcl::DeleteOnDeinit< Wallpaper > SINGLETON(COL_LIGHTGRAY);
+ return *SINGLETON.get();
+ }
+}
+
+// Should only be called from an ImplInit method for initialization or
+// after checking bNew is different from the current mbHorzSplit value.
+// The public method that does that check is Splitter::SetHorizontal().
+void Splitter::ImplInitHorVer(bool bNew)
+{
+ mbHorzSplit = bNew;
+
+ PointerStyle ePointerStyle;
+ const StyleSettings& rSettings = GetSettings().GetStyleSettings();
+
+ if ( mbHorzSplit )
+ {
+ ePointerStyle = PointerStyle::HSplit;
+ SetSizePixel( Size( StyleSettings::GetSplitSize(), rSettings.GetScrollBarSize() ) );
+ }
+ else
+ {
+ ePointerStyle = PointerStyle::VSplit;
+ SetSizePixel( Size( rSettings.GetScrollBarSize(), StyleSettings::GetSplitSize() ) );
+ }
+
+ SetPointer( ePointerStyle );
+}
+
+void Splitter::ImplInit( vcl::Window* pParent, WinBits nWinStyle )
+{
+ Window::ImplInit( pParent, nWinStyle, nullptr );
+
+ mpRefWin = pParent;
+
+ ImplInitHorVer(nWinStyle & WB_HSCROLL);
+
+ if( GetSettings().GetStyleSettings().GetFaceColor().IsDark() )
+ SetBackground( ImplWhiteWall() );
+ else
+ SetBackground( ImplBlackWall() );
+
+ TaskPaneList *pTList = GetSystemWindow()->GetTaskPaneList();
+ pTList->AddWindow( this );
+}
+
+void Splitter::ImplSplitMousePos( Point& rPos )
+{
+ if ( mbHorzSplit )
+ {
+ if ( rPos.X() > maDragRect.Right()-1 )
+ rPos.setX( maDragRect.Right()-1 );
+ if ( rPos.X() < maDragRect.Left()+1 )
+ rPos.setX( maDragRect.Left()+1 );
+ }
+ else
+ {
+ if ( rPos.Y() > maDragRect.Bottom()-1 )
+ rPos.setY( maDragRect.Bottom()-1 );
+ if ( rPos.Y() < maDragRect.Top()+1 )
+ rPos.setY( maDragRect.Top()+1 );
+ }
+}
+
+void Splitter::ImplDrawSplitter()
+{
+ tools::Rectangle aInvRect( maDragRect );
+
+ if ( mbHorzSplit )
+ {
+ aInvRect.SetLeft( maDragPos.X() - 1 );
+ aInvRect.SetRight( maDragPos.X() + 1 );
+ }
+ else
+ {
+ aInvRect.SetTop( maDragPos.Y() - 1 );
+ aInvRect.SetBottom( maDragPos.Y() + 1 );
+ }
+
+ mpRefWin->InvertTracking( mpRefWin->PixelToLogic(aInvRect), ShowTrackFlags::Split );
+}
+
+Splitter::Splitter( vcl::Window* pParent, WinBits nStyle ) :
+ Window( WindowType::SPLITTER ),
+ mpRefWin( nullptr ),
+ mnSplitPos( 0 ),
+ mnLastSplitPos( 0 ),
+ mnStartSplitPos( 0 ),
+ mbDragFull( false ),
+ mbKbdSplitting( false ),
+ mbInKeyEvent( false ),
+ mnKeyboardStepSize( SPLITTER_DEFAULTSTEPSIZE )
+{
+ ImplGetWindowImpl()->mbSplitter = true;
+
+ ImplInit( pParent, nStyle );
+
+ GetOutDev()->SetLineColor();
+ GetOutDev()->SetFillColor();
+}
+
+Splitter::~Splitter()
+{
+ disposeOnce();
+}
+
+void Splitter::dispose()
+{
+ SystemWindow *pSysWin = GetSystemWindow();
+ if(pSysWin)
+ {
+ TaskPaneList *pTList = pSysWin->GetTaskPaneList();
+ pTList->RemoveWindow(this);
+ }
+ mpRefWin.clear();
+ Window::dispose();
+}
+
+void Splitter::SetHorizontal(bool bNew)
+{
+ if(bNew != mbHorzSplit)
+ {
+ ImplInitHorVer(bNew);
+ }
+}
+
+void Splitter::SetKeyboardStepSize( tools::Long nStepSize )
+{
+ mnKeyboardStepSize = nStepSize;
+}
+
+Splitter* Splitter::ImplFindSibling()
+{
+ // look for another splitter with the same parent but different orientation
+ vcl::Window *pWin = GetParent()->GetWindow( GetWindowType::FirstChild );
+ Splitter *pSplitter = nullptr;
+ while( pWin )
+ {
+ if( pWin->ImplIsSplitter() )
+ {
+ pSplitter = static_cast<Splitter*>(pWin);
+ if( pSplitter != this && IsHorizontal() != pSplitter->IsHorizontal() )
+ return pSplitter;
+ }
+ pWin = pWin->GetWindow( GetWindowType::Next );
+ }
+ return nullptr;
+}
+
+bool Splitter::ImplSplitterActive()
+{
+ // is splitter in document or at scrollbar handle ?
+
+ bool bActive = true;
+ const StyleSettings& rSettings = GetSettings().GetStyleSettings();
+ tools::Long nA = rSettings.GetScrollBarSize();
+ tools::Long nB = StyleSettings::GetSplitSize();
+
+ Size aSize = GetOutDev()->GetOutputSize();
+ if ( mbHorzSplit )
+ {
+ if( aSize.Width() == nB && aSize.Height() == nA )
+ bActive = false;
+ }
+ else
+ {
+ if( aSize.Width() == nA && aSize.Height() == nB )
+ bActive = false;
+ }
+ return bActive;
+}
+
+void Splitter::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( rMEvt.GetClicks() == 2 )
+ {
+ if ( mnLastSplitPos != mnSplitPos )
+ {
+ StartSplit();
+ Point aPos = rMEvt.GetPosPixel();
+ if ( mbHorzSplit )
+ aPos.setX( mnLastSplitPos );
+ else
+ aPos.setY( mnLastSplitPos );
+ ImplSplitMousePos( aPos );
+ tools::Long nTemp = mnSplitPos;
+ if ( mbHorzSplit )
+ SetSplitPosPixel( aPos.X() );
+ else
+ SetSplitPosPixel( aPos.Y() );
+ mnLastSplitPos = nTemp;
+ Split();
+ EndSplit();
+ }
+ }
+ else
+ StartDrag();
+}
+
+void Splitter::Tracking( const TrackingEvent& rTEvt )
+{
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ if ( !mbDragFull )
+ ImplDrawSplitter();
+
+ if ( !rTEvt.IsTrackingCanceled() )
+ {
+ tools::Long nNewPos;
+ if ( mbHorzSplit )
+ nNewPos = maDragPos.X();
+ else
+ nNewPos = maDragPos.Y();
+ if ( nNewPos != mnStartSplitPos )
+ {
+ SetSplitPosPixel( nNewPos );
+ mnLastSplitPos = 0;
+ Split();
+ }
+ EndSplit();
+ }
+ else if ( mbDragFull )
+ {
+ SetSplitPosPixel( mnStartSplitPos );
+ Split();
+ }
+ mnStartSplitPos = 0;
+ }
+ else
+ {
+ //Point aNewPos = mpRefWin->ScreenToOutputPixel( OutputToScreenPixel( rTEvt.GetMouseEvent().GetPosPixel() ) );
+ Point aNewPos = mpRefWin->NormalizedScreenToOutputPixel( OutputToNormalizedScreenPixel( rTEvt.GetMouseEvent().GetPosPixel() ) );
+ ImplSplitMousePos( aNewPos );
+
+ if ( mbHorzSplit )
+ {
+ if ( aNewPos.X() == maDragPos.X() )
+ return;
+ }
+ else
+ {
+ if ( aNewPos.Y() == maDragPos.Y() )
+ return;
+ }
+
+ if ( mbDragFull )
+ {
+ maDragPos = aNewPos;
+ tools::Long nNewPos;
+ if ( mbHorzSplit )
+ nNewPos = maDragPos.X();
+ else
+ nNewPos = maDragPos.Y();
+ if ( nNewPos != mnSplitPos )
+ {
+ SetSplitPosPixel( nNewPos );
+ mnLastSplitPos = 0;
+ Split();
+ }
+
+ GetParent()->PaintImmediately();
+ }
+ else
+ {
+ ImplDrawSplitter();
+ maDragPos = aNewPos;
+ ImplDrawSplitter();
+ }
+ }
+}
+
+void Splitter::ImplKbdTracking( vcl::KeyCode aKeyCode )
+{
+ sal_uInt16 nCode = aKeyCode.GetCode();
+ if ( nCode == KEY_ESCAPE || nCode == KEY_RETURN )
+ {
+ if( !mbKbdSplitting )
+ return;
+ else
+ mbKbdSplitting = false;
+
+ if ( nCode != KEY_ESCAPE )
+ {
+ tools::Long nNewPos;
+ if ( mbHorzSplit )
+ nNewPos = maDragPos.X();
+ else
+ nNewPos = maDragPos.Y();
+ if ( nNewPos != mnStartSplitPos )
+ {
+ SetSplitPosPixel( nNewPos );
+ mnLastSplitPos = 0;
+ Split();
+ }
+ }
+ else
+ {
+ SetSplitPosPixel( mnStartSplitPos );
+ Split();
+ EndSplit();
+ }
+ mnStartSplitPos = 0;
+ }
+ else
+ {
+ Point aNewPos;
+ Size aSize = mpRefWin->GetOutDev()->GetOutputSize();
+ Point aPos = GetPosPixel();
+ // depending on the position calc allows continuous moves or snaps to row/columns
+ // continuous mode is active when position is at the origin or end of the splitter
+ // otherwise snap mode is active
+ // default here is snap, holding shift sets continuous mode
+ if( mbHorzSplit )
+ aNewPos = Point( ImplSplitterActive() ? aPos.X() : mnSplitPos, aKeyCode.IsShift() ? 0 : aSize.Height()/2);
+ else
+ aNewPos = Point( aKeyCode.IsShift() ? 0 : aSize.Width()/2, ImplSplitterActive() ? aPos.Y() : mnSplitPos );
+
+ Point aOldWindowPos = GetPosPixel();
+
+ int maxiter = 500; // avoid endless loop
+ int delta=0;
+ int delta_step = mbHorzSplit ? aSize.Width()/10 : aSize.Height()/10;
+
+ // use the specified step size if it was set
+ if( mnKeyboardStepSize != SPLITTER_DEFAULTSTEPSIZE )
+ delta_step = mnKeyboardStepSize;
+
+ while( maxiter-- && aOldWindowPos == GetPosPixel() )
+ {
+ // inc/dec position until application performs changes
+ // thus a single key press really moves the splitter
+ if( aKeyCode.IsShift() )
+ delta++;
+ else
+ delta += delta_step;
+
+ switch( nCode )
+ {
+ case KEY_LEFT:
+ aNewPos.AdjustX( -delta );
+ break;
+ case KEY_RIGHT:
+ aNewPos.AdjustX(delta );
+ break;
+ case KEY_UP:
+ aNewPos.AdjustY( -delta );
+ break;
+ case KEY_DOWN:
+ aNewPos.AdjustY(delta );
+ break;
+ default:
+ maxiter = 0; // leave loop
+ break;
+ }
+ ImplSplitMousePos( aNewPos );
+
+ if ( mbHorzSplit )
+ {
+ if ( aNewPos.X() == maDragPos.X() )
+ continue;
+ }
+ else
+ {
+ if ( aNewPos.Y() == maDragPos.Y() )
+ continue;
+ }
+
+ maDragPos = aNewPos;
+ tools::Long nNewPos;
+ if ( mbHorzSplit )
+ nNewPos = maDragPos.X();
+ else
+ nNewPos = maDragPos.Y();
+ if ( nNewPos != mnSplitPos )
+ {
+ SetSplitPosPixel( nNewPos );
+ mnLastSplitPos = 0;
+ Split();
+ }
+ GetParent()->PaintImmediately();
+ }
+ }
+}
+
+void Splitter::StartSplit()
+{
+ maStartSplitHdl.Call( this );
+}
+
+void Splitter::Split()
+{
+ maSplitHdl.Call( this );
+}
+
+void Splitter::EndSplit()
+{
+ maEndSplitHdl.Call( this );
+}
+
+void Splitter::SetDragRectPixel( const tools::Rectangle& rDragRect, vcl::Window* _pRefWin )
+{
+ maDragRect = rDragRect;
+ if ( !_pRefWin )
+ mpRefWin = GetParent();
+ else
+ mpRefWin = _pRefWin;
+}
+
+void Splitter::SetSplitPosPixel( tools::Long nNewPos )
+{
+ mnSplitPos = nNewPos;
+}
+
+void Splitter::StartDrag()
+{
+ if ( IsTracking() )
+ return;
+
+ StartSplit();
+
+ // Tracking starten
+ StartTracking();
+
+ // Start-Position ermitteln
+ maDragPos = mpRefWin->GetPointerPosPixel();
+ ImplSplitMousePos( maDragPos );
+ if ( mbHorzSplit )
+ mnStartSplitPos = maDragPos.X();
+ else
+ mnStartSplitPos = maDragPos.Y();
+
+ mbDragFull = bool(Application::GetSettings().GetStyleSettings().GetDragFullOptions() & DragFullOptions::Split);
+ if ( !mbDragFull )
+ ImplDrawSplitter();
+}
+
+void Splitter::ImplStartKbdSplitting()
+{
+ if( mbKbdSplitting )
+ return;
+
+ mbKbdSplitting = true;
+
+ StartSplit();
+
+ // determine start position
+ // because we have no mouse position we take either the position
+ // of the splitter window or the last split position
+ // the other coordinate is just the center of the reference window
+ Size aSize = mpRefWin->GetOutDev()->GetOutputSize();
+ Point aPos = GetPosPixel();
+ if( mbHorzSplit )
+ maDragPos = Point( ImplSplitterActive() ? aPos.X() : mnSplitPos, aSize.Height()/2 );
+ else
+ maDragPos = Point( aSize.Width()/2, ImplSplitterActive() ? aPos.Y() : mnSplitPos );
+ ImplSplitMousePos( maDragPos );
+ if ( mbHorzSplit )
+ mnStartSplitPos = maDragPos.X();
+ else
+ mnStartSplitPos = maDragPos.Y();
+}
+
+void Splitter::ImplRestoreSplitter()
+{
+ // set splitter in the center of the ref window
+ StartSplit();
+ Size aSize = mpRefWin->GetOutDev()->GetOutputSize();
+ Point aPos( aSize.Width()/2 , aSize.Height()/2);
+ if ( mnLastSplitPos != mnSplitPos && mnLastSplitPos > 5 )
+ {
+ // restore last pos if it was a useful position (>5)
+ if ( mbHorzSplit )
+ aPos.setX( mnLastSplitPos );
+ else
+ aPos.setY( mnLastSplitPos );
+ }
+
+ ImplSplitMousePos( aPos );
+ tools::Long nTemp = mnSplitPos;
+ if ( mbHorzSplit )
+ SetSplitPosPixel( aPos.X() );
+ else
+ SetSplitPosPixel( aPos.Y() );
+ mnLastSplitPos = nTemp;
+ Split();
+ EndSplit();
+}
+
+void Splitter::GetFocus()
+{
+ if( !ImplSplitterActive() )
+ ImplRestoreSplitter();
+
+ Invalidate();
+}
+
+void Splitter::LoseFocus()
+{
+ if( mbKbdSplitting )
+ {
+ vcl::KeyCode aReturnKey( KEY_RETURN );
+ ImplKbdTracking( aReturnKey );
+ mbKbdSplitting = false;
+ }
+ Invalidate();
+}
+
+void Splitter::KeyInput( const KeyEvent& rKEvt )
+{
+ if( mbInKeyEvent )
+ return;
+
+ mbInKeyEvent = true;
+
+ Splitter *pSibling = ImplFindSibling();
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+ sal_uInt16 nCode = aKeyCode.GetCode();
+ switch ( nCode )
+ {
+ case KEY_UP:
+ case KEY_DOWN:
+ if( !mbHorzSplit )
+ {
+ ImplStartKbdSplitting();
+ ImplKbdTracking( aKeyCode );
+ }
+ else
+ {
+ if( pSibling )
+ {
+ pSibling->GrabFocus();
+ pSibling->KeyInput( rKEvt );
+ }
+ }
+ break;
+ case KEY_RIGHT:
+ case KEY_LEFT:
+ if( mbHorzSplit )
+ {
+ ImplStartKbdSplitting();
+ ImplKbdTracking( aKeyCode );
+ }
+ else
+ {
+ if( pSibling )
+ {
+ pSibling->GrabFocus();
+ pSibling->KeyInput( rKEvt );
+ }
+ }
+ break;
+
+ case KEY_DELETE:
+ if( ImplSplitterActive() )
+ {
+ if( mbKbdSplitting )
+ {
+ vcl::KeyCode aKey( KEY_ESCAPE );
+ ImplKbdTracking( aKey );
+ }
+
+ StartSplit();
+ Point aPos;
+ if ( mbHorzSplit )
+ aPos.setX( 0 );
+ else
+ aPos.setY( 0 );
+ ImplSplitMousePos( aPos );
+ tools::Long nTemp = mnSplitPos;
+ if ( mbHorzSplit )
+ SetSplitPosPixel( aPos.X() );
+ else
+ SetSplitPosPixel( aPos.Y() );
+ mnLastSplitPos = nTemp;
+ Split();
+ EndSplit();
+
+ // Shift-Del deletes both splitters
+ if( aKeyCode.IsShift() && pSibling )
+ pSibling->KeyInput( rKEvt );
+
+ GrabFocusToDocument();
+ }
+ break;
+
+ case KEY_ESCAPE:
+ if( mbKbdSplitting )
+ ImplKbdTracking( aKeyCode );
+ else
+ GrabFocusToDocument();
+ break;
+
+ case KEY_RETURN:
+ ImplKbdTracking( aKeyCode );
+ GrabFocusToDocument();
+ break;
+ default: // let any key input fix the splitter
+ Window::KeyInput( rKEvt );
+ GrabFocusToDocument();
+ break;
+ }
+ mbInKeyEvent = false;
+}
+
+void Splitter::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Window::DataChanged( rDCEvt );
+ if( rDCEvt.GetType() != DataChangedEventType::SETTINGS )
+ return;
+
+ const AllSettings* pOldSettings = rDCEvt.GetOldSettings();
+ if(!pOldSettings)
+ return;
+
+ Color oldFaceColor = pOldSettings->GetStyleSettings().GetFaceColor();
+ Color newFaceColor = Application::GetSettings().GetStyleSettings().GetFaceColor();
+ if( oldFaceColor.IsDark() != newFaceColor.IsDark() )
+ {
+ if( newFaceColor.IsDark() )
+ SetBackground( ImplWhiteWall() );
+ else
+ SetBackground( ImplBlackWall() );
+ }
+}
+
+void Splitter::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rPaintRect)
+{
+ rRenderContext.DrawRect(rPaintRect);
+
+ tools::Polygon aPoly(rPaintRect);
+ tools::PolyPolygon aPolyPoly(aPoly);
+ rRenderContext.DrawTransparent(aPolyPoly, 85);
+
+ if (mbKbdSplitting)
+ {
+ LineInfo aInfo( LineStyle::Dash );
+ //aInfo.SetDashLen( 2 );
+ //aInfo.SetDashCount( 1 );
+ aInfo.SetDistance( 1 );
+ aInfo.SetDotLen( 2 );
+ aInfo.SetDotCount( 3 );
+
+ rRenderContext.DrawPolyLine( aPoly, aInfo );
+ }
+ else
+ {
+ rRenderContext.DrawRect(rPaintRect);
+ }
+}
+
+Size Splitter::GetOptimalSize() const
+{
+ return LogicToPixel(Size(3, 3), MapMode(MapUnit::MapAppFont));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/splitwin.cxx b/vcl/source/window/splitwin.cxx
new file mode 100644
index 000000000..1cb8389fa
--- /dev/null
+++ b/vcl/source/window/splitwin.cxx
@@ -0,0 +1,2719 @@
+/* -*- 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 <string.h>
+
+#include <o3tl/safeint.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/wall.hxx>
+#include <vcl/help.hxx>
+#include <vcl/splitwin.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/ptrstyle.hxx>
+
+#include <svdata.hxx>
+#include <strings.hrc>
+
+
+#define SPLITWIN_SPLITSIZEEX 4
+#define SPLITWIN_SPLITSIZEAUTOHIDE 72
+#define SPLITWIN_SPLITSIZEFADE 72
+
+#define SPLIT_HORZ (sal_uInt16(0x0001))
+#define SPLIT_VERT (sal_uInt16(0x0002))
+#define SPLIT_WINDOW (sal_uInt16(0x0004))
+#define SPLIT_NOSPLIT (sal_uInt16(0x8000))
+
+namespace {
+
+class ImplSplitItem
+{
+public:
+ ImplSplitItem();
+
+ tools::Long mnSize;
+ tools::Long mnPixSize;
+ tools::Long mnLeft;
+ tools::Long mnTop;
+ tools::Long mnWidth;
+ tools::Long mnHeight;
+ tools::Long mnSplitPos;
+ tools::Long mnSplitSize;
+ tools::Long mnOldSplitPos;
+ tools::Long mnOldSplitSize;
+ tools::Long mnOldWidth;
+ tools::Long mnOldHeight;
+ std::unique_ptr<ImplSplitSet> mpSet;
+ VclPtr<vcl::Window> mpWindow;
+ VclPtr<vcl::Window> mpOrgParent;
+ sal_uInt16 mnId;
+ SplitWindowItemFlags mnBits;
+ bool mbFixed;
+ bool mbSubSize;
+ /// Minimal width or height of the item. -1 means no restriction.
+ tools::Long mnMinSize;
+ /// Maximal width or height of the item. -1 means no restriction.
+ tools::Long mnMaxSize;
+};
+
+}
+
+class ImplSplitSet
+{
+public:
+ ImplSplitSet();
+
+ std::vector< ImplSplitItem > mvItems;
+ tools::Long mnLastSize;
+ tools::Long mnSplitSize;
+ sal_uInt16 mnId;
+ bool mbCalcPix;
+};
+
+ImplSplitItem::ImplSplitItem()
+ : mnSize(0)
+ , mnPixSize(0)
+ , mnLeft(0)
+ , mnTop(0)
+ , mnWidth(0)
+ , mnHeight(0)
+ , mnSplitPos(0)
+ , mnSplitSize(0)
+ , mnOldSplitPos(0)
+ , mnOldSplitSize(0)
+ , mnOldWidth(0)
+ , mnOldHeight(0)
+ , mnId(0)
+ , mnBits(SplitWindowItemFlags::NONE)
+ , mbFixed(false)
+ , mbSubSize(false)
+ , mnMinSize(-1)
+ , mnMaxSize(-1)
+{
+}
+
+ImplSplitSet::ImplSplitSet() :
+ mnLastSize( 0 ),
+ mnSplitSize( SPLITWIN_SPLITSIZE ),
+ mnId( 0 ),
+ mbCalcPix( true )
+{
+}
+
+/** Check whether the given size is inside the valid range defined by
+ [rItem.mnMinSize,rItem.mnMaxSize]. When it is not inside it then return
+ the upper or lower bound, respectively. Otherwise return the given size
+ unmodified.
+ Note that either mnMinSize and/or mnMaxSize can be -1 in which case the
+ size has not lower or upper bound.
+*/
+namespace {
+ tools::Long ValidateSize (const tools::Long nSize, const ImplSplitItem & rItem)
+ {
+ if (rItem.mnMinSize>=0 && nSize<rItem.mnMinSize)
+ return rItem.mnMinSize;
+ else if (rItem.mnMaxSize>0 && nSize>rItem.mnMaxSize)
+ return rItem.mnMaxSize;
+ else
+ return nSize;
+ }
+}
+
+static void ImplCalcBorder( WindowAlign eAlign,
+ tools::Long& rLeft, tools::Long& rTop,
+ tools::Long& rRight, tools::Long& rBottom )
+{
+ switch ( eAlign )
+ {
+ case WindowAlign::Top:
+ rLeft = 2;
+ rTop = 2;
+ rRight = 2;
+ rBottom = 0;
+ break;
+ case WindowAlign::Left:
+ rLeft = 0;
+ rTop = 2;
+ rRight = 2;
+ rBottom = 2;
+ break;
+ case WindowAlign::Bottom:
+ rLeft = 2;
+ rTop = 0;
+ rRight = 2;
+ rBottom = 2;
+ break;
+ default:
+ rLeft = 0;
+ rTop = 2;
+ rRight = 2;
+ rBottom = 2;
+ break;
+ }
+}
+
+void SplitWindow::ImplDrawBorder(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ tools::Long nDX = mnDX;
+ tools::Long nDY = mnDY;
+
+ switch (meAlign)
+ {
+ case WindowAlign::Bottom:
+ break;
+ case WindowAlign::Top:
+ break;
+ case WindowAlign::Left:
+ break;
+ default:
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(Point(0, 0), Point( 0, nDY));
+ rRenderContext.DrawLine(Point(0, nDY), Point(nDX, nDY));
+ }
+}
+
+void SplitWindow::ImplDrawBorderLine(vcl::RenderContext& rRenderContext)
+{
+ if (!mbFadeOut)
+ return;
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ tools::Long nDX = mnDX;
+ tools::Long nDY = mnDY;
+
+ switch (meAlign)
+ {
+ case WindowAlign::Left:
+ rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() );
+ rRenderContext.DrawLine( Point( nDX-SPLITWIN_SPLITSIZEEXLN-1, 1 ), Point( nDX-SPLITWIN_SPLITSIZEEXLN-1, nDY-2 ) );
+
+ rRenderContext.SetLineColor( rStyleSettings.GetLightColor() );
+ rRenderContext.DrawLine( Point( nDX-SPLITWIN_SPLITSIZEEXLN, 1 ), Point( nDX-SPLITWIN_SPLITSIZEEXLN, nDY-3 ) );
+ break;
+ case WindowAlign::Right:
+ break;
+ case WindowAlign::Top:
+ rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() );
+ rRenderContext.DrawLine( Point( 0, nDY-SPLITWIN_SPLITSIZEEXLN-1 ), Point( nDX-1, nDY-SPLITWIN_SPLITSIZEEXLN-1 ) );
+
+ rRenderContext.SetLineColor( rStyleSettings.GetLightColor() );
+ rRenderContext.DrawLine( Point( 0, nDY-SPLITWIN_SPLITSIZEEXLN ), Point( nDX-1, nDY-SPLITWIN_SPLITSIZEEXLN ) );
+ break;
+ case WindowAlign::Bottom:
+ rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() );
+ rRenderContext.DrawLine( Point( 0, 5 ), Point( nDX-1, 5 ) );
+
+ rRenderContext.SetLineColor( rStyleSettings.GetLightColor() );
+ rRenderContext.DrawLine( Point( 0, SPLITWIN_SPLITSIZEEXLN ), Point( nDX-1, SPLITWIN_SPLITSIZEEXLN ) );
+ break;
+ }
+}
+
+static ImplSplitSet* ImplFindSet( ImplSplitSet* pSet, sal_uInt16 nId )
+{
+ if ( pSet->mnId == nId )
+ return pSet;
+
+ std::vector< ImplSplitItem >& rItems = pSet->mvItems;
+
+ for ( const auto& rItem : rItems )
+ {
+ if ( rItem.mnId == nId )
+ return rItem.mpSet.get();
+ }
+
+ for ( auto& rItem : rItems )
+ {
+ if ( rItem.mpSet )
+ {
+ ImplSplitSet* pFindSet = ImplFindSet( rItem.mpSet.get(), nId );
+ if ( pFindSet )
+ return pFindSet;
+ }
+ }
+
+ return nullptr;
+}
+
+static ImplSplitSet* ImplFindItem( ImplSplitSet* pSet, sal_uInt16 nId, sal_uInt16& rPos )
+{
+ size_t nItems = pSet->mvItems.size();
+ std::vector< ImplSplitItem >& rItems = pSet->mvItems;
+
+ for ( size_t i = 0; i < nItems; i++ )
+ {
+ if ( rItems[i].mnId == nId )
+ {
+ rPos = i;
+ return pSet;
+ }
+ }
+
+ for ( auto& rItem : rItems )
+ {
+ if ( rItem.mpSet )
+ {
+ ImplSplitSet* pFindSet = ImplFindItem( rItem.mpSet.get(), nId, rPos );
+ if ( pFindSet )
+ return pFindSet;
+ }
+ }
+
+ return nullptr;
+}
+
+static sal_uInt16 ImplFindItem( ImplSplitSet* pSet, vcl::Window* pWindow )
+{
+ std::vector< ImplSplitItem >& rItems = pSet->mvItems;
+
+ for ( auto& rItem : rItems )
+ {
+ if ( rItem.mpWindow == pWindow )
+ return rItem.mnId;
+ else
+ {
+ if ( rItem.mpSet )
+ {
+ sal_uInt16 nId = ImplFindItem( rItem.mpSet.get(), pWindow );
+ if ( nId )
+ return nId;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static sal_uInt16 ImplFindItem( ImplSplitSet* pSet, const Point& rPos,
+ bool bRows, bool bDown = true )
+{
+ std::vector< ImplSplitItem >& rItems = pSet->mvItems;
+
+ for ( auto& rItem : rItems )
+ {
+ if ( rItem.mnWidth && rItem.mnHeight )
+ {
+ Point aPoint( rItem.mnLeft, rItem.mnTop );
+ Size aSize( rItem.mnWidth, rItem.mnHeight );
+ tools::Rectangle aRect( aPoint, aSize );
+ if ( bRows )
+ {
+ if ( bDown )
+ aRect.AdjustBottom(pSet->mnSplitSize );
+ else
+ aRect.AdjustTop( -(pSet->mnSplitSize) );
+ }
+ else
+ {
+ if ( bDown )
+ aRect.AdjustRight(pSet->mnSplitSize );
+ else
+ aRect.AdjustLeft( -(pSet->mnSplitSize) );
+ }
+
+ if ( aRect.Contains( rPos ) )
+ {
+ if ( rItem.mpSet && !rItem.mpSet->mvItems.empty() )
+ {
+ return ImplFindItem( rItem.mpSet.get(), rPos,
+ !(rItem.mnBits & SplitWindowItemFlags::ColSet) );
+ }
+ else
+ return rItem.mnId;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void ImplCalcSet( ImplSplitSet* pSet,
+ tools::Long nSetLeft, tools::Long nSetTop,
+ tools::Long nSetWidth, tools::Long nSetHeight,
+ bool bRows, bool bDown = true )
+{
+ if ( pSet->mvItems.empty() )
+ return;
+
+ sal_uInt16 nMins;
+ sal_uInt16 nCalcItems;
+ size_t nItems = pSet->mvItems.size();
+ sal_uInt16 nAbsItems;
+ tools::Long nCalcSize;
+ tools::Long nPos;
+ tools::Long nMaxPos;
+ std::vector< ImplSplitItem >& rItems = pSet->mvItems;
+ bool bEmpty;
+
+ // calculate sizes
+ if ( bRows )
+ nCalcSize = nSetHeight;
+ else
+ nCalcSize = nSetWidth;
+ nCalcSize -= (rItems.size()-1)*pSet->mnSplitSize;
+ if ( pSet->mbCalcPix || (pSet->mnLastSize != nCalcSize) )
+ {
+ tools::Long nPercentFactor = 10;
+ tools::Long nRelCount = 0;
+ tools::Long nPercent = 0;
+ tools::Long nRelPercent = 0;
+ tools::Long nAbsSize = 0;
+ tools::Long nCurSize = 0;
+ for ( const auto& rItem : rItems )
+ {
+ if ( rItem.mnBits & SplitWindowItemFlags::RelativeSize )
+ nRelCount += rItem.mnSize;
+ else if ( rItem.mnBits & SplitWindowItemFlags::PercentSize )
+ nPercent += rItem.mnSize;
+ else
+ nAbsSize += rItem.mnSize;
+ }
+ // map relative values to percentages (percentage here one tenth of a percent)
+ nPercent *= nPercentFactor;
+ if ( nRelCount )
+ {
+ tools::Long nRelPercentBase = 1000;
+ while ( (nRelCount > nRelPercentBase) && (nPercentFactor < 100000) )
+ {
+ nRelPercentBase *= 10;
+ nPercentFactor *= 10;
+ }
+ if ( nPercent < nRelPercentBase )
+ {
+ nRelPercent = (nRelPercentBase-nPercent)/nRelCount;
+ nPercent += nRelPercent*nRelCount;
+ }
+ else
+ nRelPercent = 0;
+ }
+ if ( !nPercent )
+ nPercent = 1;
+ tools::Long nSizeDelta = nCalcSize-nAbsSize;
+ for ( auto& rItem : rItems )
+ {
+ if ( rItem.mnBits & SplitWindowItemFlags::RelativeSize )
+ {
+ if ( nSizeDelta <= 0 )
+ rItem.mnPixSize = 0;
+ else
+ rItem.mnPixSize = (nSizeDelta*rItem.mnSize*nRelPercent)/nPercent;
+ }
+ else if ( rItem.mnBits & SplitWindowItemFlags::PercentSize )
+ {
+ if ( nSizeDelta <= 0 )
+ rItem.mnPixSize = 0;
+ else
+ rItem.mnPixSize = (nSizeDelta*rItem.mnSize*nPercentFactor)/nPercent;
+ }
+ else
+ rItem.mnPixSize = rItem.mnSize;
+ nCurSize += rItem.mnPixSize;
+ }
+
+ pSet->mbCalcPix = false;
+ pSet->mnLastSize = nCalcSize;
+
+ // adapt window
+ nSizeDelta = nCalcSize-nCurSize;
+ if ( nSizeDelta )
+ {
+ nAbsItems = 0;
+ tools::Long nSizeWinSize = 0;
+
+ // first resize absolute items relative
+ for ( const auto& rItem : rItems )
+ {
+ if ( !(rItem.mnBits & (SplitWindowItemFlags::RelativeSize | SplitWindowItemFlags::PercentSize)) )
+ {
+ nAbsItems++;
+ nSizeWinSize += rItem.mnPixSize;
+ }
+ }
+ // do not compensate rounding errors here
+ if ( (nAbsItems < o3tl::make_unsigned(std::abs( nSizeDelta ))) && nSizeWinSize )
+ {
+ tools::Long nNewSizeWinSize = 0;
+
+ for ( auto& rItem : rItems )
+ {
+ if ( !(rItem.mnBits & (SplitWindowItemFlags::RelativeSize | SplitWindowItemFlags::PercentSize)) )
+ {
+ rItem.mnPixSize += (nSizeDelta*rItem.mnPixSize)/nSizeWinSize;
+ nNewSizeWinSize += rItem.mnPixSize;
+ }
+ }
+
+ nSizeDelta -= nNewSizeWinSize-nSizeWinSize;
+ }
+
+ // compensate rounding errors now
+ sal_uInt16 j = 0;
+ nMins = 0;
+ while ( nSizeDelta && (nItems != nMins) )
+ {
+ // determine which items we can calculate
+ nCalcItems = 0;
+ while ( !nCalcItems )
+ {
+ for ( auto& rItem : rItems )
+ {
+ rItem.mbSubSize = false;
+
+ if ( j >= 2 )
+ rItem.mbSubSize = true;
+ else
+ {
+ if ( (nSizeDelta > 0) || rItem.mnPixSize )
+ {
+ if ( j >= 1 )
+ rItem.mbSubSize = true;
+ else
+ {
+ if ( (j == 0) && (rItem.mnBits & (SplitWindowItemFlags::RelativeSize | SplitWindowItemFlags::PercentSize)) )
+ rItem.mbSubSize = true;
+ }
+ }
+ }
+
+ if ( rItem.mbSubSize )
+ nCalcItems++;
+ }
+
+ j++;
+ }
+
+ // subtract size of individual items
+ tools::Long nErrorSum = nSizeDelta % nCalcItems;
+ tools::Long nCurSizeDelta = nSizeDelta / nCalcItems;
+ nMins = 0;
+ for ( auto& rItem : rItems )
+ {
+ if ( rItem.mbSubSize )
+ {
+ tools::Long* pSize = &(rItem.mnPixSize);
+ tools::Long nTempErr;
+
+ if ( nErrorSum )
+ {
+ if ( nErrorSum < 0 )
+ nTempErr = -1;
+ else
+ nTempErr = 1;
+ }
+ else
+ nTempErr = 0;
+
+ if ( (*pSize+nCurSizeDelta+nTempErr) <= 0 )
+ {
+ tools::Long nTemp = *pSize;
+ if ( nTemp )
+ {
+ *pSize -= nTemp;
+ nSizeDelta += nTemp;
+ }
+ nMins++;
+ }
+ else
+ {
+ *pSize += nCurSizeDelta;
+ nSizeDelta -= nCurSizeDelta;
+ if ( nTempErr && (*pSize || (nTempErr > 0)) )
+ {
+ *pSize += nTempErr;
+ nSizeDelta -= nTempErr;
+ nErrorSum -= nTempErr;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // calculate maximum size
+ if ( bRows )
+ {
+ nPos = nSetTop;
+ if ( !bDown )
+ nMaxPos = nSetTop-nSetHeight;
+ else
+ nMaxPos = nSetTop+nSetHeight;
+ }
+ else
+ {
+ nPos = nSetLeft;
+ if ( !bDown )
+ nMaxPos = nSetLeft-nSetWidth;
+ else
+ nMaxPos = nSetLeft+nSetWidth;
+ }
+
+ // order windows and adapt values
+ for ( size_t i = 0; i < nItems; i++ )
+ {
+ rItems[i].mnOldSplitPos = rItems[i].mnSplitPos;
+ rItems[i].mnOldSplitSize = rItems[i].mnSplitSize;
+ rItems[i].mnOldWidth = rItems[i].mnWidth;
+ rItems[i].mnOldHeight = rItems[i].mnHeight;
+
+ bEmpty = false;
+ if ( bDown )
+ {
+ if ( nPos+rItems[i].mnPixSize > nMaxPos )
+ bEmpty = true;
+ }
+ else
+ {
+ nPos -= rItems[i].mnPixSize;
+ if ( nPos < nMaxPos )
+ bEmpty = true;
+ }
+
+ if ( bEmpty )
+ {
+ rItems[i].mnWidth = 0;
+ rItems[i].mnHeight = 0;
+ rItems[i].mnSplitSize = 0;
+ }
+ else
+ {
+ if ( bRows )
+ {
+ rItems[i].mnLeft = nSetLeft;
+ rItems[i].mnTop = nPos;
+ rItems[i].mnWidth = nSetWidth;
+ rItems[i].mnHeight = rItems[i].mnPixSize;
+ }
+ else
+ {
+ rItems[i].mnLeft = nPos;
+ rItems[i].mnTop = nSetTop;
+ rItems[i].mnWidth = rItems[i].mnPixSize;
+ rItems[i].mnHeight = nSetHeight;
+ }
+
+ if ( i > nItems-1 )
+ rItems[i].mnSplitSize = 0;
+ else
+ {
+ rItems[i].mnSplitSize = pSet->mnSplitSize;
+ if ( bDown )
+ {
+ rItems[i].mnSplitPos = nPos+rItems[i].mnPixSize;
+ if ( rItems[i].mnSplitPos+rItems[i].mnSplitSize > nMaxPos )
+ rItems[i].mnSplitSize = nMaxPos-rItems[i].mnSplitPos;
+ }
+ else
+ {
+ rItems[i].mnSplitPos = nPos-pSet->mnSplitSize;
+ if ( rItems[i].mnSplitPos < nMaxPos )
+ rItems[i].mnSplitSize = rItems[i].mnSplitPos+pSet->mnSplitSize-nMaxPos;
+ }
+ }
+ }
+
+ if ( !bDown )
+ nPos -= pSet->mnSplitSize;
+ else
+ nPos += rItems[i].mnPixSize+pSet->mnSplitSize;
+ }
+
+ // calculate Sub-Set's
+ for ( auto& rItem : rItems )
+ {
+ if ( rItem.mpSet && rItem.mnWidth && rItem.mnHeight )
+ {
+ ImplCalcSet( rItem.mpSet.get(),
+ rItem.mnLeft, rItem.mnTop,
+ rItem.mnWidth, rItem.mnHeight,
+ !(rItem.mnBits & SplitWindowItemFlags::ColSet) );
+ }
+ }
+
+ // set fixed
+ for ( auto& rItem : rItems )
+ {
+ rItem.mbFixed = false;
+ if ( rItem.mnBits & SplitWindowItemFlags::Fixed )
+ rItem.mbFixed = true;
+ else
+ {
+ // this item is also fixed if Child-Set is available,
+ // if a child is fixed
+ if ( rItem.mpSet )
+ {
+ for ( auto const & j: rItem.mpSet->mvItems )
+ {
+ if ( j.mbFixed )
+ {
+ rItem.mbFixed = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+void SplitWindow::ImplCalcSet2( SplitWindow* pWindow, ImplSplitSet* pSet, bool bHide,
+ bool bRows )
+{
+ std::vector< ImplSplitItem >& rItems = pSet->mvItems;
+
+ if ( pWindow->IsReallyVisible() && pWindow->IsUpdateMode() && pWindow->mbInvalidate )
+ {
+ for ( const auto& rItem : rItems )
+ {
+ if ( rItem.mnSplitSize )
+ {
+ // invalidate all, if applicable or only a small part
+ if ( (rItem.mnOldSplitPos != rItem.mnSplitPos) ||
+ (rItem.mnOldSplitSize != rItem.mnSplitSize) ||
+ (rItem.mnOldWidth != rItem.mnWidth) ||
+ (rItem.mnOldHeight != rItem.mnHeight) )
+ {
+ tools::Rectangle aRect;
+
+ // invalidate old rectangle
+ if ( bRows )
+ {
+ aRect.SetLeft( rItem.mnLeft );
+ aRect.SetRight( rItem.mnLeft+rItem.mnOldWidth-1 );
+ aRect.SetTop( rItem.mnOldSplitPos );
+ aRect.SetBottom( aRect.Top() + rItem.mnOldSplitSize );
+ }
+ else
+ {
+ aRect.SetTop( rItem.mnTop );
+ aRect.SetBottom( rItem.mnTop+rItem.mnOldHeight-1 );
+ aRect.SetLeft( rItem.mnOldSplitPos );
+ aRect.SetRight( aRect.Left() + rItem.mnOldSplitSize );
+ }
+ pWindow->Invalidate( aRect );
+ // invalidate new rectangle
+ if ( bRows )
+ {
+ aRect.SetLeft( rItem.mnLeft );
+ aRect.SetRight( rItem.mnLeft+rItem.mnWidth-1 );
+ aRect.SetTop( rItem.mnSplitPos );
+ aRect.SetBottom( aRect.Top() + rItem.mnSplitSize );
+ }
+ else
+ {
+ aRect.SetTop( rItem.mnTop );
+ aRect.SetBottom( rItem.mnTop+rItem.mnHeight-1 );
+ aRect.SetLeft( rItem.mnSplitPos );
+ aRect.SetRight( aRect.Left() + rItem.mnSplitSize );
+ }
+ pWindow->Invalidate( aRect );
+
+ // invalidate complete set, as these areas
+ // are not cluttered by windows
+ if ( rItem.mpSet && rItem.mpSet->mvItems.empty() )
+ {
+ aRect.SetLeft( rItem.mnLeft );
+ aRect.SetTop( rItem.mnTop );
+ aRect.SetRight( rItem.mnLeft+rItem.mnWidth-1 );
+ aRect.SetBottom( rItem.mnTop+rItem.mnHeight-1 );
+ pWindow->Invalidate( aRect );
+ }
+ }
+ }
+ }
+ }
+
+ // position windows
+ for ( auto& rItem : rItems )
+ {
+ if ( rItem.mpSet )
+ {
+ bool bTempHide = bHide;
+ if ( !rItem.mnWidth || !rItem.mnHeight )
+ bTempHide = true;
+ ImplCalcSet2( pWindow, rItem.mpSet.get(), bTempHide,
+ !(rItem.mnBits & SplitWindowItemFlags::ColSet) );
+ }
+ else
+ {
+ if ( rItem.mnWidth && rItem.mnHeight && !bHide )
+ {
+ Point aPos( rItem.mnLeft, rItem.mnTop );
+ Size aSize( rItem.mnWidth, rItem.mnHeight );
+ rItem.mpWindow->SetPosSizePixel( aPos, aSize );
+ }
+ else
+ rItem.mpWindow->Hide();
+ }
+ }
+
+ // show windows and reset flag
+ for ( auto& rItem : rItems )
+ {
+ if ( rItem.mpWindow && rItem.mnWidth && rItem.mnHeight && !bHide )
+ rItem.mpWindow->Show();
+ }
+}
+
+static void ImplCalcLogSize( std::vector< ImplSplitItem > & rItems, size_t nItems )
+{
+ // update original sizes
+ size_t i;
+ tools::Long nRelSize = 0;
+ tools::Long nPerSize = 0;
+
+ for ( i = 0; i < nItems; i++ )
+ {
+ if ( rItems[i].mnBits & SplitWindowItemFlags::RelativeSize )
+ nRelSize += rItems[i].mnPixSize;
+ else if ( rItems[i].mnBits & SplitWindowItemFlags::PercentSize )
+ nPerSize += rItems[i].mnPixSize;
+ }
+ nPerSize += nRelSize;
+ for ( i = 0; i < nItems; i++ )
+ {
+ if ( rItems[i].mnBits & SplitWindowItemFlags::RelativeSize )
+ {
+ if ( nRelSize )
+ rItems[i].mnSize = (rItems[i].mnPixSize+(nRelSize/2))/nRelSize;
+ else
+ rItems[i].mnSize = 1;
+ }
+ else if ( rItems[i].mnBits & SplitWindowItemFlags::PercentSize )
+ {
+ if ( nPerSize )
+ rItems[i].mnSize = (rItems[i].mnPixSize*100)/nPerSize;
+ else
+ rItems[i].mnSize = 1;
+ }
+ else
+ rItems[i].mnSize = rItems[i].mnPixSize;
+ }
+}
+
+static void ImplDrawSplit(vcl::RenderContext& rRenderContext, ImplSplitSet* pSet, bool bRows, bool bDown)
+{
+ if (pSet->mvItems.empty())
+ return;
+
+ size_t nItems = pSet->mvItems.size();
+ tools::Long nPos;
+ tools::Long nTop;
+ tools::Long nBottom;
+ std::vector< ImplSplitItem >& rItems = pSet->mvItems;
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ for (size_t i = 0; i < nItems-1; i++)
+ {
+ if (rItems[i].mnSplitSize)
+ {
+ nPos = rItems[i].mnSplitPos;
+
+ tools::Long nItemSplitSize = rItems[i].mnSplitSize;
+ tools::Long nSplitSize = pSet->mnSplitSize;
+ if (bRows)
+ {
+ nTop = rItems[i].mnLeft;
+ nBottom = rItems[i].mnLeft+rItems[i].mnWidth-1;
+
+ if (bDown || (nItemSplitSize >= nSplitSize))
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ rRenderContext.DrawLine(Point(nTop, nPos + 1), Point(nBottom, nPos + 1));
+ }
+ nPos += nSplitSize-2;
+ if ((!bDown && (nItemSplitSize >= 2)) ||
+ (bDown && (nItemSplitSize >= nSplitSize - 1)))
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(Point(nTop, nPos), Point(nBottom, nPos));
+ }
+ nPos++;
+ if (!bDown || (nItemSplitSize >= nSplitSize))
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
+ rRenderContext.DrawLine(Point(nTop, nPos), Point(nBottom, nPos));
+ }
+ }
+ else
+ {
+ nTop = rItems[i].mnTop;
+ nBottom = rItems[i].mnTop+pSet->mvItems[i].mnHeight-1;
+
+ if (bDown || (nItemSplitSize >= nSplitSize))
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ rRenderContext.DrawLine(Point(nPos + 1, nTop), Point(nPos+1, nBottom));
+ }
+ nPos += pSet->mnSplitSize - 2;
+ if ((!bDown && (nItemSplitSize >= 2)) ||
+ (bDown && (nItemSplitSize >= nSplitSize - 1)))
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(Point(nPos, nTop), Point(nPos, nBottom));
+ }
+ nPos++;
+ if (!bDown || (nItemSplitSize >= nSplitSize))
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
+ rRenderContext.DrawLine(Point(nPos, nTop), Point(nPos, nBottom));
+ }
+ }
+ }
+ }
+
+ for ( auto& rItem : rItems )
+ {
+ if (rItem.mpSet && rItem.mnWidth && rItem.mnHeight)
+ {
+ ImplDrawSplit(rRenderContext, rItem.mpSet.get(), !(rItem.mnBits & SplitWindowItemFlags::ColSet), true/*bDown*/);
+ }
+ }
+}
+
+sal_uInt16 SplitWindow::ImplTestSplit( ImplSplitSet* pSet, const Point& rPos,
+ tools::Long& rMouseOff, ImplSplitSet** ppFoundSet, sal_uInt16& rFoundPos,
+ bool bRows )
+{
+ if ( pSet->mvItems.empty() )
+ return 0;
+
+ sal_uInt16 nSplitTest;
+ size_t nItems = pSet->mvItems.size();
+ tools::Long nMPos1;
+ tools::Long nMPos2;
+ tools::Long nPos;
+ tools::Long nTop;
+ tools::Long nBottom;
+ std::vector< ImplSplitItem >& rItems = pSet->mvItems;
+
+ if ( bRows )
+ {
+ nMPos1 = rPos.X();
+ nMPos2 = rPos.Y();
+ }
+ else
+ {
+ nMPos1 = rPos.Y();
+ nMPos2 = rPos.X();
+ }
+
+ for ( size_t i = 0; i < nItems-1; i++ )
+ {
+ if ( rItems[i].mnSplitSize )
+ {
+ if ( bRows )
+ {
+ nTop = rItems[i].mnLeft;
+ nBottom = rItems[i].mnLeft+rItems[i].mnWidth-1;
+ }
+ else
+ {
+ nTop = rItems[i].mnTop;
+ nBottom = rItems[i].mnTop+rItems[i].mnHeight-1;
+ }
+ nPos = rItems[i].mnSplitPos;
+
+ if ( (nMPos1 >= nTop) && (nMPos1 <= nBottom) &&
+ (nMPos2 >= nPos) && (nMPos2 <= nPos+rItems[i].mnSplitSize) )
+ {
+ if ( !rItems[i].mbFixed && !rItems[i+1].mbFixed )
+ {
+ rMouseOff = nMPos2-nPos;
+ *ppFoundSet = pSet;
+ rFoundPos = i;
+ if ( bRows )
+ return SPLIT_VERT;
+ else
+ return SPLIT_HORZ;
+ }
+ else
+ return SPLIT_NOSPLIT;
+ }
+ }
+ }
+
+ for ( auto& rItem : rItems )
+ {
+ if ( rItem.mpSet )
+ {
+ nSplitTest = ImplTestSplit( rItem.mpSet.get(), rPos,
+ rMouseOff, ppFoundSet, rFoundPos,
+ !(rItem.mnBits & SplitWindowItemFlags::ColSet) );
+ if ( nSplitTest )
+ return nSplitTest;
+ }
+ }
+
+ return 0;
+}
+
+sal_uInt16 SplitWindow::ImplTestSplit( const SplitWindow* pWindow, const Point& rPos,
+ tools::Long& rMouseOff, ImplSplitSet** ppFoundSet, sal_uInt16& rFoundPos )
+{
+ // Resizable SplitWindow should be treated different
+ if ( pWindow->mnWinStyle & WB_SIZEABLE )
+ {
+ tools::Long nTPos;
+ tools::Long nPos;
+ tools::Long nBorder;
+
+ if ( pWindow->mbHorz )
+ {
+ if ( pWindow->mbBottomRight )
+ {
+ nBorder = pWindow->mnBottomBorder;
+ nPos = 0;
+ }
+ else
+ {
+ nBorder = pWindow->mnTopBorder;
+ nPos = pWindow->mnDY-nBorder;
+ }
+ nTPos = rPos.Y();
+ }
+ else
+ {
+ if ( pWindow->mbBottomRight )
+ {
+ nBorder = pWindow->mnRightBorder;
+ nPos = 0;
+ }
+ else
+ {
+ nBorder = pWindow->mnLeftBorder;
+ nPos = pWindow->mnDX-nBorder;
+ }
+ nTPos = rPos.X();
+ }
+ tools::Long nSplitSize = pWindow->mpMainSet->mnSplitSize-2;
+ if (pWindow->mbFadeOut)
+ nSplitSize += SPLITWIN_SPLITSIZEEXLN;
+ if ( !pWindow->mbBottomRight )
+ nPos -= nSplitSize;
+ if ( (nTPos >= nPos) && (nTPos <= nPos+nSplitSize+nBorder) )
+ {
+ rMouseOff = nTPos-nPos;
+ *ppFoundSet = pWindow->mpMainSet.get();
+ if ( !pWindow->mpMainSet->mvItems.empty() )
+ rFoundPos = pWindow->mpMainSet->mvItems.size() - 1;
+ else
+ rFoundPos = 0;
+ if ( pWindow->mbHorz )
+ return SPLIT_VERT | SPLIT_WINDOW;
+ else
+ return SPLIT_HORZ | SPLIT_WINDOW;
+ }
+ }
+
+ return ImplTestSplit( pWindow->mpMainSet.get(), rPos, rMouseOff, ppFoundSet, rFoundPos,
+ pWindow->mbHorz );
+}
+
+void SplitWindow::ImplDrawSplitTracking(const Point& rPos)
+{
+ tools::Rectangle aRect;
+
+ if (mnSplitTest & SPLIT_HORZ)
+ {
+ aRect.SetTop( maDragRect.Top() );
+ aRect.SetBottom( maDragRect.Bottom() );
+ aRect.SetLeft( rPos.X() );
+ aRect.SetRight( aRect.Left() + mpSplitSet->mnSplitSize - 1 );
+ if (!(mnWinStyle & WB_NOSPLITDRAW))
+ aRect.AdjustRight( -1 );
+ if ((mnSplitTest & SPLIT_WINDOW) && mbFadeOut)
+ {
+ aRect.AdjustLeft(SPLITWIN_SPLITSIZEEXLN );
+ aRect.AdjustRight(SPLITWIN_SPLITSIZEEXLN );
+ }
+ }
+ else
+ {
+ aRect.SetLeft( maDragRect.Left() );
+ aRect.SetRight( maDragRect.Right() );
+ aRect.SetTop( rPos.Y() );
+ aRect.SetBottom( aRect.Top() + mpSplitSet->mnSplitSize - 1 );
+ if (!(mnWinStyle & WB_NOSPLITDRAW))
+ aRect.AdjustBottom( -1 );
+ if ((mnSplitTest & SPLIT_WINDOW) && mbFadeOut)
+ {
+ aRect.AdjustTop(SPLITWIN_SPLITSIZEEXLN );
+ aRect.AdjustBottom(SPLITWIN_SPLITSIZEEXLN );
+ }
+ }
+ ShowTracking(aRect, ShowTrackFlags::Split);
+}
+
+void SplitWindow::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ mpMainSet.reset(new ImplSplitSet());
+ mpBaseSet = mpMainSet.get();
+ mpSplitSet = nullptr;
+ mpLastSizes = nullptr;
+ mnDX = 0;
+ mnDY = 0;
+ mnLeftBorder = 0;
+ mnTopBorder = 0;
+ mnRightBorder = 0;
+ mnBottomBorder = 0;
+ mnMaxSize = 0;
+ mnMouseOff = 0;
+ meAlign = WindowAlign::Top;
+ mnWinStyle = nStyle;
+ mnSplitTest = 0;
+ mnSplitPos = 0;
+ mnMouseModifier = 0;
+ mnMStartPos = 0;
+ mnMSplitPos = 0;
+ mbDragFull = false;
+ mbHorz = true;
+ mbBottomRight = false;
+ mbCalc = false;
+ mbRecalc = true;
+ mbInvalidate = true;
+ mbFadeIn = false;
+ mbFadeOut = false;
+ mbFadeInDown = false;
+ mbFadeOutDown = false;
+ mbFadeInPressed = false;
+ mbFadeOutPressed = false;
+ mbFadeNoButtonMode = false;
+
+ if ( nStyle & WB_NOSPLITDRAW )
+ {
+ mpMainSet->mnSplitSize -= 2;
+ mbInvalidate = false;
+ }
+
+ if ( nStyle & WB_BORDER )
+ {
+ ImplCalcBorder( meAlign, mnLeftBorder, mnTopBorder,
+ mnRightBorder, mnBottomBorder );
+ }
+ else
+ {
+ mnLeftBorder = 0;
+ mnTopBorder = 0;
+ mnRightBorder = 0;
+ mnBottomBorder = 0;
+ }
+
+ DockingWindow::ImplInit( pParent, (nStyle | WB_CLIPCHILDREN) & ~(WB_BORDER | WB_SIZEABLE) );
+
+ ImplInitSettings();
+}
+
+void SplitWindow::ImplInitSettings()
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+
+ Color aColor;
+ if ( IsControlBackground() )
+ aColor = GetControlBackground();
+ else if ( Window::GetStyle() & WB_3DLOOK )
+ aColor = rStyleSettings.GetFaceColor();
+ else
+ aColor = rStyleSettings.GetWindowColor();
+ SetBackground( aColor );
+}
+
+SplitWindow::SplitWindow( vcl::Window* pParent, WinBits nStyle ) :
+ DockingWindow( WindowType::SPLITWINDOW, "vcl::SplitWindow maLayoutIdle" )
+{
+ ImplInit( pParent, nStyle );
+}
+
+SplitWindow::~SplitWindow()
+{
+ disposeOnce();
+}
+
+void SplitWindow::dispose()
+{
+ // delete Sets
+ mpMainSet.reset();
+ DockingWindow::dispose();
+}
+
+void SplitWindow::ImplSetWindowSize( tools::Long nDelta )
+{
+ if ( !nDelta )
+ return;
+
+ Size aSize = GetSizePixel();
+ switch ( meAlign )
+ {
+ case WindowAlign::Top:
+ aSize.AdjustHeight(nDelta );
+ SetSizePixel( aSize );
+ break;
+ case WindowAlign::Bottom:
+ {
+ maDragRect.AdjustTop(nDelta );
+ Point aPos = GetPosPixel();
+ aPos.AdjustY( -nDelta );
+ aSize.AdjustHeight(nDelta );
+ SetPosSizePixel( aPos, aSize );
+ break;
+ }
+ case WindowAlign::Left:
+ aSize.AdjustWidth(nDelta );
+ SetSizePixel( aSize );
+ break;
+ case WindowAlign::Right:
+ default:
+ {
+ maDragRect.AdjustLeft(nDelta );
+ Point aPos = GetPosPixel();
+ aPos.AdjustX( -nDelta );
+ aSize.AdjustWidth(nDelta );
+ SetPosSizePixel( aPos, aSize );
+ break;
+ }
+ }
+
+ SplitResize();
+}
+
+Size SplitWindow::CalcLayoutSizePixel( const Size& aNewSize )
+{
+ Size aSize( aNewSize );
+ tools::Long nSplitSize = mpMainSet->mnSplitSize-2;
+
+ if (mbFadeOut)
+ nSplitSize += SPLITWIN_SPLITSIZEEXLN;
+
+ // if the window is sizeable and if it does not contain a relative window,
+ // the size is determined according to MainSet
+ if ( mnWinStyle & WB_SIZEABLE )
+ {
+ tools::Long nCalcSize = 0;
+ std::vector< ImplSplitItem* >::size_type i;
+
+ for ( i = 0; i < mpMainSet->mvItems.size(); i++ )
+ {
+ if ( mpMainSet->mvItems[i].mnBits & (SplitWindowItemFlags::RelativeSize | SplitWindowItemFlags::PercentSize) )
+ break;
+ else
+ nCalcSize += mpMainSet->mvItems[i].mnSize;
+ }
+
+ if ( i == mpMainSet->mvItems.size() )
+ {
+ tools::Long nDelta = 0;
+ tools::Long nCurSize;
+
+ if ( mbHorz )
+ nCurSize = aNewSize.Height()-mnTopBorder-mnBottomBorder;
+ else
+ nCurSize = aNewSize.Width()-mnLeftBorder-mnRightBorder;
+ nCurSize -= nSplitSize;
+ nCurSize -= (mpMainSet->mvItems.size()-1)*mpMainSet->mnSplitSize;
+
+ nDelta = nCalcSize-nCurSize;
+ if ( !nDelta )
+ return aSize;
+
+ switch ( meAlign )
+ {
+ case WindowAlign::Top:
+ aSize.AdjustHeight(nDelta );
+ break;
+ case WindowAlign::Bottom:
+ aSize.AdjustHeight(nDelta );
+ break;
+ case WindowAlign::Left:
+ aSize.AdjustWidth(nDelta );
+ break;
+ case WindowAlign::Right:
+ default:
+ aSize.AdjustWidth(nDelta );
+ break;
+ }
+ }
+ }
+
+ return aSize;
+}
+
+void SplitWindow::ImplCalcLayout()
+{
+ if ( !mbCalc || !mbRecalc || mpMainSet->mvItems.empty() )
+ return;
+
+ tools::Long nSplitSize = mpMainSet->mnSplitSize-2;
+ if (mbFadeOut)
+ nSplitSize += SPLITWIN_SPLITSIZEEXLN;
+
+ // if the window is sizeable and if it does not contain a relative window,
+ // the size is determined according to MainSet
+ if ( mnWinStyle & WB_SIZEABLE )
+ {
+ tools::Long nCalcSize = 0;
+ std::vector<ImplSplitItem *>::size_type i;
+
+ for ( i = 0; i < mpMainSet->mvItems.size(); i++ )
+ {
+ if ( mpMainSet->mvItems[i].mnBits & (SplitWindowItemFlags::RelativeSize | SplitWindowItemFlags::PercentSize) )
+ break;
+ else
+ nCalcSize += mpMainSet->mvItems[i].mnSize;
+ }
+
+ if ( i == mpMainSet->mvItems.size() )
+ {
+ tools::Long nCurSize;
+ if ( mbHorz )
+ nCurSize = mnDY-mnTopBorder-mnBottomBorder;
+ else
+ nCurSize = mnDX-mnLeftBorder-mnRightBorder;
+ nCurSize -= nSplitSize;
+ nCurSize -= (mpMainSet->mvItems.size()-1)*mpMainSet->mnSplitSize;
+
+ mbRecalc = false;
+ ImplSetWindowSize( nCalcSize-nCurSize );
+ mbRecalc = true;
+ }
+ }
+
+ if ( (mnDX <= 0) || (mnDY <= 0) )
+ return;
+
+ // pre-calculate sizes/position
+ tools::Long nL;
+ tools::Long nT;
+ tools::Long nW;
+ tools::Long nH;
+
+ if ( mbHorz )
+ {
+ if ( mbBottomRight )
+ nT = mnDY-mnBottomBorder;
+ else
+ nT = mnTopBorder;
+ nL = mnLeftBorder;
+ }
+ else
+ {
+ if ( mbBottomRight )
+ nL = mnDX-mnRightBorder;
+ else
+ nL = mnLeftBorder;
+ nT = mnTopBorder;
+ }
+ nW = mnDX-mnLeftBorder-mnRightBorder;
+ nH = mnDY-mnTopBorder-mnBottomBorder;
+ if ( mnWinStyle & WB_SIZEABLE )
+ {
+ if ( mbHorz )
+ nH -= nSplitSize;
+ else
+ nW -= nSplitSize;
+ }
+
+ // calculate sets recursive
+ ImplCalcSet( mpMainSet.get(), nL, nT, nW, nH, mbHorz, !mbBottomRight );
+ ImplCalcSet2( this, mpMainSet.get(), false, mbHorz );
+ mbCalc = false;
+}
+
+void SplitWindow::ImplUpdate()
+{
+ mbCalc = true;
+
+ if ( IsReallyShown() && IsUpdateMode() && mbRecalc )
+ {
+ if ( !mpMainSet->mvItems.empty() )
+ ImplCalcLayout();
+ else
+ Invalidate();
+ }
+}
+
+void SplitWindow::ImplSplitMousePos( Point& rMousePos )
+{
+ if ( mnSplitTest & SPLIT_HORZ )
+ {
+ rMousePos.AdjustX( -mnMouseOff );
+ if ( rMousePos.X() < maDragRect.Left() )
+ rMousePos.setX( maDragRect.Left() );
+ else if ( rMousePos.X()+mpSplitSet->mnSplitSize+1 > maDragRect.Right() )
+ rMousePos.setX( maDragRect.Right()-mpSplitSet->mnSplitSize+1 );
+ // store in screen coordinates due to FullDrag
+ mnMSplitPos = OutputToScreenPixel( rMousePos ).X();
+ }
+ else
+ {
+ rMousePos.AdjustY( -mnMouseOff );
+ if ( rMousePos.Y() < maDragRect.Top() )
+ rMousePos.setY( maDragRect.Top() );
+ else if ( rMousePos.Y()+mpSplitSet->mnSplitSize+1 > maDragRect.Bottom() )
+ rMousePos.setY( maDragRect.Bottom()-mpSplitSet->mnSplitSize+1 );
+ mnMSplitPos = OutputToScreenPixel( rMousePos ).Y();
+ }
+}
+
+void SplitWindow::ImplGetButtonRect( tools::Rectangle& rRect, bool bTest ) const
+{
+ tools::Long nSplitSize = mpMainSet->mnSplitSize-1;
+ if (mbFadeOut || mbFadeIn)
+ nSplitSize += SPLITWIN_SPLITSIZEEX;
+
+ tools::Long nButtonSize = 0;
+ if ( mbFadeIn )
+ nButtonSize += SPLITWIN_SPLITSIZEFADE+1;
+ if ( mbFadeOut )
+ nButtonSize += SPLITWIN_SPLITSIZEFADE+1;
+ tools::Long nCenterEx = 0;
+ if ( mbHorz )
+ nCenterEx += ((mnDX-mnLeftBorder-mnRightBorder)-nButtonSize)/2;
+ else
+ nCenterEx += ((mnDY-mnTopBorder-mnBottomBorder)-nButtonSize)/2;
+ tools::Long nEx = 0;
+ if ( nCenterEx > 0 )
+ nEx += nCenterEx;
+
+ switch ( meAlign )
+ {
+ case WindowAlign::Top:
+ rRect.SetLeft( mnLeftBorder+nEx );
+ rRect.SetTop( mnDY-mnBottomBorder-nSplitSize );
+ rRect.SetRight( rRect.Left()+SPLITWIN_SPLITSIZEAUTOHIDE );
+ rRect.SetBottom( mnDY-mnBottomBorder-1 );
+ if ( bTest )
+ {
+ rRect.AdjustTop( -mnTopBorder );
+ rRect.AdjustBottom(mnBottomBorder );
+ }
+ break;
+ case WindowAlign::Bottom:
+ rRect.SetLeft( mnLeftBorder+nEx );
+ rRect.SetTop( mnTopBorder );
+ rRect.SetRight( rRect.Left()+SPLITWIN_SPLITSIZEAUTOHIDE );
+ rRect.SetBottom( mnTopBorder+nSplitSize-1 );
+ if ( bTest )
+ {
+ rRect.AdjustTop( -mnTopBorder );
+ rRect.AdjustBottom(mnBottomBorder );
+ }
+ break;
+ case WindowAlign::Left:
+ rRect.SetLeft( mnDX-mnRightBorder-nSplitSize );
+ rRect.SetTop( mnTopBorder+nEx );
+ rRect.SetRight( mnDX-mnRightBorder-1 );
+ rRect.SetBottom( rRect.Top()+SPLITWIN_SPLITSIZEAUTOHIDE );
+ if ( bTest )
+ {
+ rRect.AdjustLeft( -mnLeftBorder );
+ rRect.AdjustRight(mnRightBorder );
+ }
+ break;
+ case WindowAlign::Right:
+ rRect.SetLeft( mnLeftBorder );
+ rRect.SetTop( mnTopBorder+nEx );
+ rRect.SetRight( mnLeftBorder+nSplitSize-1 );
+ rRect.SetBottom( rRect.Top()+SPLITWIN_SPLITSIZEAUTOHIDE );
+ if ( bTest )
+ {
+ rRect.AdjustLeft( -mnLeftBorder );
+ rRect.AdjustRight(mnRightBorder );
+ }
+ break;
+ }
+}
+
+void SplitWindow::ImplGetFadeInRect( tools::Rectangle& rRect, bool bTest ) const
+{
+ tools::Rectangle aRect;
+
+ if ( mbFadeIn )
+ ImplGetButtonRect( aRect, bTest );
+
+ rRect = aRect;
+}
+
+void SplitWindow::ImplGetFadeOutRect( tools::Rectangle& rRect ) const
+{
+ tools::Rectangle aRect;
+
+ if ( mbFadeOut )
+ ImplGetButtonRect( aRect, false );
+
+ rRect = aRect;
+}
+
+void SplitWindow::ImplDrawGrip(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, bool bHorizontal, bool bLeft)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ Color aColor;
+
+ if (rRect.Contains(GetPointerPosPixel()))
+ {
+ vcl::RenderTools::DrawSelectionBackground(rRenderContext, *this, rRect, 2, false, false, false);
+
+ aColor = rStyleSettings.GetDarkShadowColor();
+ }
+ else
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
+ rRenderContext.SetFillColor(rStyleSettings.GetDarkShadowColor());
+
+ rRenderContext.DrawRect(rRect);
+
+ aColor = rStyleSettings.GetFaceColor();
+ }
+
+ AntialiasingFlags nAA = rRenderContext.GetAntialiasing();
+ rRenderContext.SetAntialiasing(nAA | AntialiasingFlags::PixelSnapHairline | AntialiasingFlags::Enable);
+
+ tools::Long nWidth = rRect.getWidth();
+ tools::Long nWidthHalf = nWidth / 2;
+ tools::Long nHeight = rRect.getHeight();
+ tools::Long nHeightHalf = nHeight / 2;
+
+ tools::Long nLeft = rRect.Left();
+ tools::Long nRight = rRect.Right();
+ tools::Long nTop = rRect.Top();
+ tools::Long nBottom = rRect.Bottom();
+ tools::Long nMargin = 1;
+
+ rRenderContext.SetLineColor(aColor);
+ rRenderContext.SetFillColor(aColor);
+
+ tools::Polygon aPoly(3);
+
+ if (bHorizontal)
+ {
+ tools::Long nCenter = nLeft + nWidthHalf;
+
+ if (bLeft)
+ {
+ aPoly.SetPoint(Point(nCenter, nTop + nMargin), 0);
+ aPoly.SetPoint(Point(nCenter - nHeightHalf, nBottom - nMargin), 1);
+ aPoly.SetPoint(Point(nCenter - nHeightHalf, nBottom - nMargin), 2);
+ }
+ else
+ {
+ aPoly.SetPoint(Point(nCenter, nBottom - nMargin), 0);
+ aPoly.SetPoint(Point(nCenter - nHeightHalf, nTop + nMargin), 1);
+ aPoly.SetPoint(Point(nCenter + nHeightHalf, nTop + nMargin), 2);
+ }
+ rRenderContext.DrawPolygon(aPoly);
+ }
+ else
+ {
+ tools::Long nCenter = nTop + nHeightHalf;
+
+ if (bLeft)
+ {
+ aPoly.SetPoint(Point(nLeft + nMargin, nCenter), 0);
+ aPoly.SetPoint(Point(nRight - nMargin, nCenter - nWidthHalf), 1);
+ aPoly.SetPoint(Point(nRight - nMargin, nCenter + nWidthHalf), 2);
+ }
+ else
+ {
+ aPoly.SetPoint(Point(nRight - nMargin, nCenter), 0);
+ aPoly.SetPoint(Point(nLeft + nMargin, nCenter - nWidthHalf), 1);
+ aPoly.SetPoint(Point(nLeft + nMargin, nCenter + nWidthHalf), 2);
+ }
+ rRenderContext.DrawPolygon(aPoly);
+ }
+
+ rRenderContext.SetAntialiasing(nAA);
+}
+
+void SplitWindow::ImplDrawFadeIn(vcl::RenderContext& rRenderContext)
+{
+ if (!mbFadeIn)
+ return;
+
+ tools::Rectangle aTempRect;
+ ImplGetFadeInRect(aTempRect);
+
+ bool bLeft = true;
+ switch (meAlign)
+ {
+ case WindowAlign::Top:
+ case WindowAlign::Left:
+ bLeft = false;
+ break;
+ case WindowAlign::Bottom:
+ case WindowAlign::Right:
+ default:
+ bLeft = true;
+ break;
+ }
+
+ ImplDrawGrip(rRenderContext, aTempRect, (meAlign == WindowAlign::Top) || (meAlign == WindowAlign::Bottom), bLeft);
+}
+
+void SplitWindow::ImplDrawFadeOut(vcl::RenderContext& rRenderContext)
+{
+ if (!mbFadeOut)
+ return;
+
+ tools::Rectangle aTempRect;
+ ImplGetFadeOutRect(aTempRect);
+
+ bool bLeft = true;
+ switch (meAlign)
+ {
+ case WindowAlign::Bottom:
+ case WindowAlign::Right:
+ bLeft = false;
+ break;
+ case WindowAlign::Top:
+ case WindowAlign::Left:
+ default:
+ bLeft = true;
+ break;
+ }
+
+ ImplDrawGrip(rRenderContext, aTempRect, (meAlign == WindowAlign::Top) || (meAlign == WindowAlign::Bottom), bLeft);
+}
+
+void SplitWindow::ImplStartSplit( const MouseEvent& rMEvt )
+{
+ Point aMousePosPixel = rMEvt.GetPosPixel();
+ mnSplitTest = ImplTestSplit( this, aMousePosPixel, mnMouseOff, &mpSplitSet, mnSplitPos );
+
+ if ( !mnSplitTest || (mnSplitTest & SPLIT_NOSPLIT) )
+ return;
+
+ ImplSplitItem* pSplitItem;
+ tools::Long nCurMaxSize;
+ bool bPropSmaller;
+
+ mnMouseModifier = rMEvt.GetModifier();
+ bPropSmaller = (mnMouseModifier & KEY_SHIFT) && (o3tl::make_unsigned(mnSplitPos+1) < mpSplitSet->mvItems.size());
+
+ // here we can set the maximum size
+ StartSplit();
+
+ if ( mnMaxSize )
+ nCurMaxSize = mnMaxSize;
+ else
+ {
+ Size aSize = GetParent()->GetOutputSizePixel();
+ if ( mbHorz )
+ nCurMaxSize = aSize.Height();
+ else
+ nCurMaxSize = aSize.Width();
+ }
+
+ if ( !mpSplitSet->mvItems.empty() )
+ {
+ bool bDown = true;
+ if ( (mpSplitSet == mpMainSet.get()) && mbBottomRight )
+ bDown = false;
+
+ pSplitItem = &mpSplitSet->mvItems[mnSplitPos];
+ maDragRect.SetLeft( pSplitItem->mnLeft );
+ maDragRect.SetTop( pSplitItem->mnTop );
+ maDragRect.SetRight( pSplitItem->mnLeft+pSplitItem->mnWidth-1 );
+ maDragRect.SetBottom( pSplitItem->mnTop+pSplitItem->mnHeight-1 );
+
+ if ( mnSplitTest & SPLIT_HORZ )
+ {
+ if ( bDown )
+ maDragRect.AdjustRight(mpSplitSet->mnSplitSize );
+ else
+ maDragRect.AdjustLeft( -(mpSplitSet->mnSplitSize) );
+ }
+ else
+ {
+ if ( bDown )
+ maDragRect.AdjustBottom(mpSplitSet->mnSplitSize );
+ else
+ maDragRect.AdjustTop( -(mpSplitSet->mnSplitSize) );
+ }
+
+ if ( mnSplitPos )
+ {
+ tools::Long nTemp = mnSplitPos;
+ while ( nTemp )
+ {
+ pSplitItem = &mpSplitSet->mvItems[nTemp-1];
+ if ( pSplitItem->mbFixed )
+ break;
+ else
+ {
+ if ( mnSplitTest & SPLIT_HORZ )
+ {
+ if ( bDown )
+ maDragRect.AdjustLeft( -(pSplitItem->mnPixSize) );
+ else
+ maDragRect.AdjustRight(pSplitItem->mnPixSize );
+ }
+ else
+ {
+ if ( bDown )
+ maDragRect.AdjustTop( -(pSplitItem->mnPixSize) );
+ else
+ maDragRect.AdjustBottom(pSplitItem->mnPixSize );
+ }
+ }
+ nTemp--;
+ }
+ }
+
+ if ( (mpSplitSet == mpMainSet.get()) && (mnWinStyle & WB_SIZEABLE) && !bPropSmaller )
+ {
+ if ( bDown )
+ {
+ if ( mbHorz )
+ maDragRect.AdjustBottom(nCurMaxSize-mnDY-mnTopBorder );
+ else
+ maDragRect.AdjustRight(nCurMaxSize-mnDX-mnLeftBorder );
+ }
+ else
+ {
+ if ( mbHorz )
+ maDragRect.AdjustTop( -(nCurMaxSize-mnDY-mnBottomBorder) );
+ else
+ maDragRect.AdjustLeft( -(nCurMaxSize-mnDX-mnRightBorder) );
+ }
+ }
+ else
+ {
+ std::vector<ImplSplitItem *>::size_type nTemp = mnSplitPos+1;
+ while ( nTemp < mpSplitSet->mvItems.size() )
+ {
+ pSplitItem = &mpSplitSet->mvItems[nTemp];
+ if ( pSplitItem->mbFixed )
+ break;
+ else
+ {
+ if ( mnSplitTest & SPLIT_HORZ )
+ {
+ if ( bDown )
+ maDragRect.AdjustRight(pSplitItem->mnPixSize );
+ else
+ maDragRect.AdjustLeft( -(pSplitItem->mnPixSize) );
+ }
+ else
+ {
+ if ( bDown )
+ maDragRect.AdjustBottom(pSplitItem->mnPixSize );
+ else
+ maDragRect.AdjustTop( -(pSplitItem->mnPixSize) );
+ }
+ }
+ nTemp++;
+ }
+ }
+ }
+ else
+ {
+ maDragRect.SetLeft( mnLeftBorder );
+ maDragRect.SetTop( mnTopBorder );
+ maDragRect.SetRight( mnDX-mnRightBorder-1 );
+ maDragRect.SetBottom( mnDY-mnBottomBorder-1 );
+ if ( mbHorz )
+ {
+ if ( mbBottomRight )
+ maDragRect.AdjustTop( -(nCurMaxSize-mnDY-mnBottomBorder) );
+ else
+ maDragRect.AdjustBottom(nCurMaxSize-mnDY-mnTopBorder );
+ }
+ else
+ {
+ if ( mbBottomRight )
+ maDragRect.AdjustLeft( -(nCurMaxSize-mnDX-mnRightBorder) );
+ else
+ maDragRect.AdjustRight(nCurMaxSize-mnDX-mnLeftBorder );
+ }
+ }
+
+ StartTracking();
+
+ mbDragFull = bool(GetSettings().GetStyleSettings().GetDragFullOptions() & DragFullOptions::Split);
+
+ ImplSplitMousePos( aMousePosPixel );
+
+ if (!mbDragFull)
+ {
+ ImplDrawSplitTracking(aMousePosPixel);
+ }
+ else
+ {
+ std::vector< ImplSplitItem >& rItems = mpSplitSet->mvItems;
+ sal_uInt16 nItems = mpSplitSet->mvItems.size();
+ mpLastSizes.reset(new tools::Long[nItems*2]);
+ for ( sal_uInt16 i = 0; i < nItems; i++ )
+ {
+ mpLastSizes[i*2] = rItems[i].mnSize;
+ mpLastSizes[i*2+1] = rItems[i].mnPixSize;
+ }
+ }
+ mnMStartPos = mnMSplitPos;
+
+ PointerStyle eStyle = PointerStyle::Arrow;
+ if ( mnSplitTest & SPLIT_HORZ )
+ eStyle = PointerStyle::HSplit;
+ else if ( mnSplitTest & SPLIT_VERT )
+ eStyle = PointerStyle::VSplit;
+
+ SetPointer( eStyle );
+}
+
+void SplitWindow::StartSplit()
+{
+}
+
+void SplitWindow::Split()
+{
+ maSplitHdl.Call( this );
+}
+
+void SplitWindow::SplitResize()
+{
+}
+
+void SplitWindow::FadeIn()
+{
+}
+
+void SplitWindow::FadeOut()
+{
+}
+
+void SplitWindow::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( !rMEvt.IsLeft() || rMEvt.IsMod2() )
+ {
+ DockingWindow::MouseButtonDown( rMEvt );
+ return;
+ }
+
+ Point aMousePosPixel = rMEvt.GetPosPixel();
+ tools::Rectangle aTestRect;
+
+ mbFadeNoButtonMode = false;
+
+ ImplGetFadeOutRect( aTestRect );
+ if ( aTestRect.Contains( aMousePosPixel ) )
+ {
+ mbFadeOutDown = true;
+ mbFadeOutPressed = true;
+ Invalidate();
+ }
+ else
+ {
+ ImplGetFadeInRect( aTestRect, true );
+ if ( aTestRect.Contains( aMousePosPixel ) )
+ {
+ mbFadeInDown = true;
+ mbFadeInPressed = true;
+ Invalidate();
+ }
+ else if ( !aTestRect.IsEmpty() && !(mnWinStyle & WB_SIZEABLE) )
+ {
+ mbFadeNoButtonMode = true;
+ FadeIn();
+ return;
+ }
+ }
+
+ if ( mbFadeInDown || mbFadeOutDown )
+ StartTracking();
+ else
+ ImplStartSplit( rMEvt );
+}
+
+void SplitWindow::MouseMove( const MouseEvent& rMEvt )
+{
+ if ( IsTracking() )
+ return;
+
+ Point aPos = rMEvt.GetPosPixel();
+ tools::Long nTemp;
+ ImplSplitSet* pTempSplitSet;
+ sal_uInt16 nTempSplitPos;
+ sal_uInt16 nSplitTest = ImplTestSplit( this, aPos, nTemp, &pTempSplitSet, nTempSplitPos );
+ PointerStyle eStyle = PointerStyle::Arrow;
+ tools::Rectangle aFadeInRect;
+ tools::Rectangle aFadeOutRect;
+
+ ImplGetFadeInRect( aFadeInRect );
+ ImplGetFadeOutRect( aFadeOutRect );
+ if ( !aFadeInRect.Contains( aPos ) &&
+ !aFadeOutRect.Contains( aPos ) )
+ {
+ if ( nSplitTest && !(nSplitTest & SPLIT_NOSPLIT) )
+ {
+ if ( nSplitTest & SPLIT_HORZ )
+ eStyle = PointerStyle::HSplit;
+ else if ( nSplitTest & SPLIT_VERT )
+ eStyle = PointerStyle::VSplit;
+ }
+ }
+
+ SetPointer( eStyle );
+}
+
+void SplitWindow::Tracking( const TrackingEvent& rTEvt )
+{
+ Point aMousePosPixel = rTEvt.GetMouseEvent().GetPosPixel();
+
+ if ( mbFadeInDown )
+ {
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ mbFadeInDown = false;
+ if ( mbFadeInPressed )
+ {
+ mbFadeInPressed = false;
+ Invalidate();
+
+ if ( !rTEvt.IsTrackingCanceled() )
+ FadeIn();
+ }
+ }
+ else
+ {
+ tools::Rectangle aTestRect;
+ ImplGetFadeInRect( aTestRect, true );
+ bool bNewPressed = aTestRect.Contains( aMousePosPixel );
+ if ( bNewPressed != mbFadeInPressed )
+ {
+ mbFadeInPressed = bNewPressed;
+ Invalidate();
+ }
+ }
+ }
+ else if ( mbFadeOutDown )
+ {
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ mbFadeOutDown = false;
+ if ( mbFadeOutPressed )
+ {
+ mbFadeOutPressed = false;
+ Invalidate();
+
+ if ( !rTEvt.IsTrackingCanceled() )
+ FadeOut();
+ }
+ }
+ else
+ {
+ tools::Rectangle aTestRect;
+ ImplGetFadeOutRect( aTestRect );
+ bool bNewPressed = aTestRect.Contains( aMousePosPixel );
+ if ( !bNewPressed )
+ {
+ mbFadeOutPressed = bNewPressed;
+ Invalidate();
+
+ // We need a mouseevent with a position inside the button for the
+ // ImplStartSplit function!
+ MouseEvent aOrgMEvt = rTEvt.GetMouseEvent();
+ MouseEvent aNewMEvt( aTestRect.Center(), aOrgMEvt.GetClicks(),
+ aOrgMEvt.GetMode(), aOrgMEvt.GetButtons(),
+ aOrgMEvt.GetModifier() );
+
+ ImplStartSplit( aNewMEvt );
+ mbFadeOutDown = false;
+ }
+ }
+ }
+ else
+ {
+ ImplSplitMousePos( aMousePosPixel );
+ bool bSplit = true;
+ if ( mbDragFull )
+ {
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ if ( rTEvt.IsTrackingCanceled() )
+ {
+ std::vector< ImplSplitItem >& rItems = mpSplitSet->mvItems;
+ size_t nItems = rItems.size();
+ for ( size_t i = 0; i < nItems; i++ )
+ {
+ rItems[i].mnSize = mpLastSizes[i*2];
+ rItems[i].mnPixSize = mpLastSizes[i*2+1];
+ }
+ ImplUpdate();
+ Split();
+ }
+ bSplit = false;
+ }
+ }
+ else
+ {
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ HideTracking();
+ bSplit = !rTEvt.IsTrackingCanceled();
+ }
+ else
+ {
+ ImplDrawSplitTracking(aMousePosPixel);
+ bSplit = false;
+ }
+ }
+
+ if ( bSplit )
+ {
+ bool bPropSmaller = (mnMouseModifier & KEY_SHIFT) != 0;
+ bool bPropGreater = (mnMouseModifier & KEY_MOD1) != 0;
+ tools::Long nDelta = mnMSplitPos-mnMStartPos;
+
+ if ( (mnSplitTest & SPLIT_WINDOW) && mpMainSet->mvItems.empty() )
+ {
+ if ( (mpSplitSet == mpMainSet.get()) && mbBottomRight )
+ nDelta *= -1;
+ ImplSetWindowSize( nDelta );
+ }
+ else
+ {
+ tools::Long nNewSize = mpSplitSet->mvItems[mnSplitPos].mnPixSize;
+ if ( (mpSplitSet == mpMainSet.get()) && mbBottomRight )
+ nNewSize -= nDelta;
+ else
+ nNewSize += nDelta;
+ SplitItem( mpSplitSet->mvItems[mnSplitPos].mnId, nNewSize,
+ bPropSmaller, bPropGreater );
+ }
+
+ Split();
+
+ if ( mbDragFull )
+ {
+ PaintImmediately();
+ mnMStartPos = mnMSplitPos;
+ }
+ }
+
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ mpLastSizes.reset();
+ mpSplitSet = nullptr;
+ mnMouseOff = 0;
+ mnMStartPos = 0;
+ mnMSplitPos = 0;
+ mnMouseModifier = 0;
+ mnSplitTest = 0;
+ mnSplitPos = 0;
+ }
+ }
+}
+
+bool SplitWindow::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
+ {
+ // trigger redraw if mouse over state has changed
+ tools::Rectangle aFadeInRect;
+ tools::Rectangle aFadeOutRect;
+ ImplGetFadeInRect( aFadeInRect );
+ ImplGetFadeOutRect( aFadeOutRect );
+
+ if ( aFadeInRect.Contains( GetPointerPosPixel() ) != aFadeInRect.Contains( GetLastPointerPosPixel() ) )
+ Invalidate( aFadeInRect );
+ if ( aFadeOutRect.Contains( GetPointerPosPixel() ) != aFadeOutRect.Contains( GetLastPointerPosPixel() ) )
+ Invalidate( aFadeOutRect );
+
+ if( pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow() )
+ {
+ Invalidate( aFadeInRect );
+ Invalidate( aFadeOutRect );
+ }
+ }
+ }
+ return Window::PreNotify( rNEvt );
+}
+
+void SplitWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ if (mnWinStyle & WB_BORDER)
+ ImplDrawBorder(rRenderContext);
+
+ ImplDrawBorderLine(rRenderContext);
+ ImplDrawFadeOut(rRenderContext);
+ ImplDrawFadeIn(rRenderContext);
+
+ // draw splitter
+ if (!(mnWinStyle & WB_NOSPLITDRAW))
+ {
+ ImplDrawSplit(rRenderContext, mpMainSet.get(), mbHorz, !mbBottomRight);
+ }
+}
+
+void SplitWindow::Resize()
+{
+ Size aSize = GetOutputSizePixel();
+ mnDX = aSize.Width();
+ mnDY = aSize.Height();
+
+ ImplUpdate();
+ Invalidate();
+}
+
+void SplitWindow::RequestHelp( const HelpEvent& rHEvt )
+{
+ // no keyboard help for splitwin
+ if ( rHEvt.GetMode() & (HelpEventMode::BALLOON | HelpEventMode::QUICK) && !rHEvt.KeyboardActivated() )
+ {
+ Point aMousePosPixel = ScreenToOutputPixel( rHEvt.GetMousePosPixel() );
+ tools::Rectangle aHelpRect;
+ TranslateId pHelpResId;
+
+ ImplGetFadeInRect( aHelpRect, true );
+ if ( aHelpRect.Contains( aMousePosPixel ) )
+ pHelpResId = SV_HELPTEXT_FADEIN;
+ else
+ {
+ ImplGetFadeOutRect( aHelpRect );
+ if ( aHelpRect.Contains( aMousePosPixel ) )
+ pHelpResId = SV_HELPTEXT_FADEOUT;
+ }
+
+ // get rectangle
+ if (pHelpResId)
+ {
+ Point aPt = OutputToScreenPixel( aHelpRect.TopLeft() );
+ aHelpRect.SetLeft( aPt.X() );
+ aHelpRect.SetTop( aPt.Y() );
+ aPt = OutputToScreenPixel( aHelpRect.BottomRight() );
+ aHelpRect.SetRight( aPt.X() );
+ aHelpRect.SetBottom( aPt.Y() );
+
+ // get and draw text
+ OUString aStr = VclResId(pHelpResId);
+ if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
+ Help::ShowBalloon( this, aHelpRect.Center(), aHelpRect, aStr );
+ else
+ Help::ShowQuickHelp( this, aHelpRect, aStr );
+ return;
+ }
+ }
+
+ DockingWindow::RequestHelp( rHEvt );
+}
+
+void SplitWindow::StateChanged( StateChangedType nType )
+{
+ switch ( nType )
+ {
+ case StateChangedType::InitShow:
+ if ( IsUpdateMode() )
+ ImplCalcLayout();
+ break;
+ case StateChangedType::UpdateMode:
+ if ( IsUpdateMode() && IsReallyShown() )
+ ImplCalcLayout();
+ break;
+ case StateChangedType::ControlBackground:
+ ImplInitSettings();
+ Invalidate();
+ break;
+ default:;
+ }
+
+ DockingWindow::StateChanged( nType );
+}
+
+void SplitWindow::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+ else
+ DockingWindow::DataChanged( rDCEvt );
+}
+
+void SplitWindow::InsertItem( sal_uInt16 nId, vcl::Window* pWindow, tools::Long nSize,
+ sal_uInt16 nPos, sal_uInt16 nIntoSetId,
+ SplitWindowItemFlags nBits )
+{
+#ifdef DBG_UTIL
+ sal_uInt16 nDbgDummy;
+ SAL_WARN_IF( ImplFindItem( mpMainSet.get(), nId, nDbgDummy ), "vcl", "SplitWindow::InsertItem() - Id already exists" );
+#endif
+
+ // Size has to be at least 1.
+ if ( nSize < 1 )
+ nSize = 1;
+
+ ImplSplitSet* pSet = ImplFindSet( mpMainSet.get(), nIntoSetId );
+#ifdef DBG_UTIL
+ SAL_WARN_IF( !pSet, "vcl", "SplitWindow::InsertItem() - Set not exists" );
+#endif
+ if(!pSet)
+ {
+ return;
+ }
+
+ // Don't insert further than the end
+ if ( nPos > pSet->mvItems.size() )
+ nPos = pSet->mvItems.size();
+
+ // Insert in set
+ pSet->mvItems.emplace( pSet->mvItems.begin() + nPos );
+
+ // init new item
+ ImplSplitItem & aItem = pSet->mvItems[nPos];
+ aItem.mnSize = nSize;
+ aItem.mnPixSize = 0;
+ aItem.mnId = nId;
+ aItem.mnBits = nBits;
+ aItem.mnMinSize=-1;
+ aItem.mnMaxSize=-1;
+
+ if ( pWindow )
+ {
+ // New VclPtr reference
+ aItem.mpWindow = pWindow;
+ aItem.mpOrgParent = pWindow->GetParent();
+
+ // Attach window to SplitWindow.
+ pWindow->Hide();
+ pWindow->SetParent( this );
+ }
+ else
+ {
+ ImplSplitSet * pNewSet = new ImplSplitSet();
+ pNewSet->mnId = nId;
+ pNewSet->mnSplitSize = pSet->mnSplitSize;
+
+ aItem.mpSet.reset(pNewSet);
+ }
+
+ pSet->mbCalcPix = true;
+
+ ImplUpdate();
+}
+
+void SplitWindow::InsertItem( sal_uInt16 nId, tools::Long nSize,
+ sal_uInt16 nPos, sal_uInt16 nIntoSetId,
+ SplitWindowItemFlags nBits )
+{
+ InsertItem( nId, nullptr, nSize, nPos, nIntoSetId, nBits );
+}
+
+void SplitWindow::RemoveItem( sal_uInt16 nId )
+{
+#ifdef DBG_UTIL
+ sal_uInt16 nDbgDummy;
+ SAL_WARN_IF( !ImplFindItem( mpMainSet.get(), nId, nDbgDummy ), "vcl", "SplitWindow::RemoveItem() - Id not found" );
+#endif
+
+ // search set
+ sal_uInt16 nPos;
+ ImplSplitSet* pSet = ImplFindItem( mpMainSet.get(), nId, nPos );
+
+ if (!pSet)
+ return;
+
+ ImplSplitItem* pItem = &pSet->mvItems[nPos];
+ VclPtr<vcl::Window> pWindow = pItem->mpWindow;
+ VclPtr<vcl::Window> pOrgParent = pItem->mpOrgParent;
+
+ // delete set if required
+ if ( !pWindow )
+ pItem->mpSet.reset();
+
+ // remove item
+ pSet->mbCalcPix = true;
+ pSet->mvItems.erase( pSet->mvItems.begin() + nPos );
+
+ ImplUpdate();
+
+ // to have the least amounts of paints delete window only here
+ if ( pWindow )
+ {
+ // restore window
+ pWindow->Hide();
+ pWindow->SetParent( pOrgParent );
+ }
+
+ // Clear and delete
+ pWindow.clear();
+ pOrgParent.clear();
+}
+
+void SplitWindow::SplitItem( sal_uInt16 nId, tools::Long nNewSize,
+ bool bPropSmall, bool bPropGreat )
+{
+ sal_uInt16 nPos;
+ ImplSplitSet* pSet = ImplFindItem( mpBaseSet, nId, nPos );
+
+ if (!pSet)
+ return;
+
+ size_t nItems = pSet->mvItems.size();
+ std::vector< ImplSplitItem >& rItems = pSet->mvItems;
+
+ // When there is an explicit minimum or maximum size then move nNewSize
+ // into that range (when it is not yet already in it.)
+ nNewSize = ValidateSize(nNewSize, rItems[nPos]);
+
+ if ( mbCalc )
+ {
+ rItems[nPos].mnSize = nNewSize;
+ return;
+ }
+
+ tools::Long nDelta = nNewSize-rItems[nPos].mnPixSize;
+ if ( !nDelta )
+ return;
+
+ // calculate area, which could be affected by splitting
+ sal_uInt16 nMin = 0;
+ sal_uInt16 nMax = nItems;
+ for (size_t i = 0; i < nItems; ++i)
+ {
+ if ( rItems[i].mbFixed )
+ {
+ if ( i < nPos )
+ nMin = i+1;
+ else
+ nMax = i;
+ }
+ }
+
+ // treat TopSet different if the window is sizeable
+ bool bSmall = true;
+ bool bGreat = true;
+ if ( (pSet == mpMainSet.get()) && (mnWinStyle & WB_SIZEABLE) )
+ {
+ if ( nPos < pSet->mvItems.size()-1 )
+ {
+ if ( !((bPropSmall && bPropGreat) ||
+ ((nDelta > 0) && bPropSmall) ||
+ ((nDelta < 0) && bPropGreat)) )
+ {
+ if ( nDelta < 0 )
+ bGreat = false;
+ else
+ bSmall = false;
+ }
+ }
+ else
+ {
+ if ( nDelta < 0 )
+ bGreat = false;
+ else
+ bSmall = false;
+ }
+ }
+ else if ( nPos >= nMax )
+ {
+ bSmall = false;
+ bGreat = false;
+ }
+ else if ( nPos && (nPos >= pSet->mvItems.size()-1) )
+ {
+ nPos--;
+ nDelta *= -1;
+ bool bTemp = bPropSmall;
+ bPropSmall = bPropGreat;
+ bPropGreat = bTemp;
+ }
+
+ sal_uInt16 n;
+ // now splitt the windows
+ if ( nDelta < 0 )
+ {
+ if ( bGreat )
+ {
+ if ( bPropGreat )
+ {
+ tools::Long nTempDelta = nDelta;
+ do
+ {
+ n = nPos+1;
+ do
+ {
+ if ( nTempDelta )
+ {
+ rItems[n].mnPixSize++;
+ nTempDelta++;
+ }
+ n++;
+ }
+ while ( n < nMax );
+ }
+ while ( nTempDelta );
+ }
+ else
+ rItems[nPos+1].mnPixSize -= nDelta;
+ }
+
+ if ( bSmall )
+ {
+ if ( bPropSmall )
+ {
+ do
+ {
+ n = nPos+1;
+ do
+ {
+ if ( nDelta && rItems[n-1].mnPixSize )
+ {
+ rItems[n-1].mnPixSize--;
+ nDelta++;
+ }
+
+ n--;
+ }
+ while ( n > nMin );
+ }
+ while ( nDelta );
+ }
+ else
+ {
+ n = nPos+1;
+ do
+ {
+ if ( rItems[n-1].mnPixSize+nDelta < 0 )
+ {
+ nDelta += rItems[n-1].mnPixSize;
+ rItems[n-1].mnPixSize = 0;
+ }
+ else
+ {
+ rItems[n-1].mnPixSize += nDelta;
+ break;
+ }
+ n--;
+ }
+ while ( n > nMin );
+ }
+ }
+ }
+ else
+ {
+ if ( bGreat )
+ {
+ if ( bPropGreat )
+ {
+ tools::Long nTempDelta = nDelta;
+ do
+ {
+ n = nPos+1;
+ do
+ {
+ if ( nTempDelta )
+ {
+ rItems[n-1].mnPixSize++;
+ nTempDelta--;
+ }
+ n--;
+ }
+ while ( n > nMin );
+ }
+ while ( nTempDelta );
+ }
+ else
+ rItems[nPos].mnPixSize += nDelta;
+ }
+
+ if ( bSmall )
+ {
+ if ( bPropSmall )
+ {
+ do
+ {
+ n = nPos+1;
+ do
+ {
+ if ( nDelta && rItems[n].mnPixSize )
+ {
+ rItems[n].mnPixSize--;
+ nDelta--;
+ }
+
+ n++;
+ }
+ while ( n < nMax );
+ }
+ while ( nDelta );
+ }
+ else
+ {
+ n = nPos+1;
+ do
+ {
+ if ( rItems[n].mnPixSize-nDelta < 0 )
+ {
+ nDelta -= rItems[n].mnPixSize;
+ rItems[n].mnPixSize = 0;
+ }
+ else
+ {
+ rItems[n].mnPixSize -= nDelta;
+ break;
+ }
+ n++;
+ }
+ while ( n < nMax );
+ }
+ }
+ }
+
+ // update original sizes
+ ImplCalcLogSize( rItems, nItems );
+
+ ImplUpdate();
+}
+
+void SplitWindow::SetItemSize( sal_uInt16 nId, tools::Long nNewSize )
+{
+ sal_uInt16 nPos;
+ ImplSplitSet* pSet = ImplFindItem( mpBaseSet, nId, nPos );
+ ImplSplitItem* pItem;
+
+ if ( !pSet )
+ return;
+
+ // check if size is changed
+ pItem = &pSet->mvItems[nPos];
+ if ( pItem->mnSize != nNewSize )
+ {
+ // set new size and re-calculate
+ pItem->mnSize = nNewSize;
+ pSet->mbCalcPix = true;
+ ImplUpdate();
+ }
+}
+
+tools::Long SplitWindow::GetItemSize( sal_uInt16 nId ) const
+{
+ sal_uInt16 nPos;
+ ImplSplitSet* pSet = ImplFindItem( mpBaseSet, nId, nPos );
+
+ if ( pSet )
+ return pSet->mvItems[nPos].mnSize;
+ else
+ return 0;
+}
+
+tools::Long SplitWindow::GetItemSize( sal_uInt16 nId, SplitWindowItemFlags nBits ) const
+{
+ sal_uInt16 nPos;
+ ImplSplitSet* pSet = ImplFindItem( mpBaseSet, nId, nPos );
+
+ if ( pSet )
+ {
+ if ( nBits == pSet->mvItems[nPos].mnBits )
+ return pSet->mvItems[nPos].mnSize;
+ else
+ {
+ const_cast<SplitWindow*>(this)->ImplCalcLayout();
+
+ tools::Long nRelSize = 0;
+ tools::Long nPerSize = 0;
+ size_t nItems;
+ SplitWindowItemFlags nTempBits;
+ nItems = pSet->mvItems.size();
+ std::vector< ImplSplitItem >& rItems = pSet->mvItems;
+ for ( size_t i = 0; i < nItems; i++ )
+ {
+ if ( i == nPos )
+ nTempBits = nBits;
+ else
+ nTempBits = rItems[i].mnBits;
+ if ( nTempBits & SplitWindowItemFlags::RelativeSize )
+ nRelSize += rItems[i].mnPixSize;
+ else if ( nTempBits & SplitWindowItemFlags::PercentSize )
+ nPerSize += rItems[i].mnPixSize;
+ }
+ nPerSize += nRelSize;
+ if ( nBits & SplitWindowItemFlags::RelativeSize )
+ {
+ if ( nRelSize )
+ return (rItems[nPos].mnPixSize+(nRelSize/2))/nRelSize;
+ else
+ return 1;
+ }
+ else if ( nBits & SplitWindowItemFlags::PercentSize )
+ {
+ if ( nPerSize )
+ return (rItems[nPos].mnPixSize*100)/nPerSize;
+ else
+ return 1;
+ }
+ else
+ return rItems[nPos].mnPixSize;
+ }
+ }
+ else
+ return 0;
+}
+
+void SplitWindow::SetItemSizeRange (sal_uInt16 nId, const Range& rRange)
+{
+ sal_uInt16 nPos;
+ ImplSplitSet* pSet = ImplFindItem(mpBaseSet, nId, nPos);
+
+ if (pSet != nullptr)
+ {
+ pSet->mvItems[nPos].mnMinSize = rRange.Min();
+ pSet->mvItems[nPos].mnMaxSize = rRange.Max();
+ }
+}
+
+sal_uInt16 SplitWindow::GetSet( sal_uInt16 nId ) const
+{
+ sal_uInt16 nPos;
+ ImplSplitSet* pSet = ImplFindItem( mpBaseSet, nId, nPos );
+
+ if ( pSet )
+ return pSet->mnId;
+ else
+ return 0;
+}
+
+bool SplitWindow::IsItemValid( sal_uInt16 nId ) const
+{
+ sal_uInt16 nPos;
+ ImplSplitSet* pSet = mpBaseSet ? ImplFindItem(mpBaseSet, nId, nPos) : nullptr;
+
+ return pSet != nullptr;
+}
+
+sal_uInt16 SplitWindow::GetItemId( vcl::Window* pWindow ) const
+{
+ return ImplFindItem( mpBaseSet, pWindow );
+}
+
+sal_uInt16 SplitWindow::GetItemId( const Point& rPos ) const
+{
+ return ImplFindItem( mpBaseSet, rPos, mbHorz, !mbBottomRight );
+}
+
+sal_uInt16 SplitWindow::GetItemPos( sal_uInt16 nId, sal_uInt16 nSetId ) const
+{
+ ImplSplitSet* pSet = ImplFindSet( mpBaseSet, nSetId );
+ sal_uInt16 nPos = SPLITWINDOW_ITEM_NOTFOUND;
+
+ if ( pSet )
+ {
+ for ( size_t i = 0; i < pSet->mvItems.size(); i++ )
+ {
+ if ( pSet->mvItems[i].mnId == nId )
+ {
+ nPos = i;
+ break;
+ }
+ }
+ }
+
+ return nPos;
+}
+
+sal_uInt16 SplitWindow::GetItemId( sal_uInt16 nPos ) const
+{
+ ImplSplitSet* pSet = ImplFindSet( mpBaseSet, 0/*nSetId*/ );
+ if ( pSet && (nPos < pSet->mvItems.size()) )
+ return pSet->mvItems[nPos].mnId;
+ else
+ return 0;
+}
+
+sal_uInt16 SplitWindow::GetItemCount( sal_uInt16 nSetId ) const
+{
+ ImplSplitSet* pSet = ImplFindSet( mpBaseSet, nSetId );
+ if ( pSet )
+ return pSet->mvItems.size();
+ else
+ return 0;
+}
+
+void SplitWindow::ImplNewAlign()
+{
+ switch ( meAlign )
+ {
+ case WindowAlign::Top:
+ mbHorz = true;
+ mbBottomRight = false;
+ break;
+ case WindowAlign::Bottom:
+ mbHorz = true;
+ mbBottomRight = true;
+ break;
+ case WindowAlign::Left:
+ mbHorz = false;
+ mbBottomRight = false;
+ break;
+ case WindowAlign::Right:
+ mbHorz = false;
+ mbBottomRight = true;
+ break;
+ }
+
+ if ( mnWinStyle & WB_BORDER )
+ {
+ ImplCalcBorder( meAlign, mnLeftBorder, mnTopBorder,
+ mnRightBorder, mnBottomBorder );
+ }
+
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+ ImplUpdate();
+}
+
+void SplitWindow::SetAlign( WindowAlign eNewAlign )
+{
+ if ( meAlign != eNewAlign )
+ {
+ meAlign = eNewAlign;
+ ImplNewAlign();
+ }
+}
+
+void SplitWindow::ShowFadeInHideButton()
+{
+ mbFadeIn = true;
+ ImplUpdate();
+}
+
+void SplitWindow::ShowFadeOutButton()
+{
+ mbFadeOut = true;
+ ImplUpdate();
+}
+
+tools::Long SplitWindow::GetFadeInSize() const
+{
+ tools::Long n = 0;
+
+ if ( mbHorz )
+ n = mnTopBorder+mnBottomBorder;
+ else
+ n = mnLeftBorder+mnRightBorder;
+
+ return n+SPLITWIN_SPLITSIZE+SPLITWIN_SPLITSIZEEX-2;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/stacking.cxx b/vcl/source/window/stacking.cxx
new file mode 100644
index 000000000..928c456ee
--- /dev/null
+++ b/vcl/source/window/stacking.cxx
@@ -0,0 +1,1151 @@
+/* -*- 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 <vcl/syswin.hxx>
+#include <vcl/window.hxx>
+#include <vcl/taskpanelist.hxx>
+#include <sal/log.hxx>
+
+#include <salframe.hxx>
+#include <salobj.hxx>
+#include <svdata.hxx>
+#include <window.h>
+#include <brdwin.hxx>
+
+#include <com/sun/star/awt/XTopWindow.hpp>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::datatransfer::clipboard;
+using namespace ::com::sun::star::datatransfer::dnd;
+using namespace ::com::sun::star;
+
+using ::com::sun::star::awt::XTopWindow;
+
+struct ImplCalcToTopData
+{
+ std::unique_ptr<ImplCalcToTopData> mpNext;
+ VclPtr<vcl::Window> mpWindow;
+ std::unique_ptr<vcl::Region> mpInvalidateRegion;
+};
+
+namespace vcl {
+
+vcl::Window* Window::ImplGetTopmostFrameWindow()
+{
+ vcl::Window *pTopmostParent = this;
+ while( pTopmostParent->ImplGetParent() )
+ pTopmostParent = pTopmostParent->ImplGetParent();
+ return pTopmostParent->mpWindowImpl->mpFrameWindow;
+}
+
+void Window::ImplInsertWindow( vcl::Window* pParent )
+{
+ mpWindowImpl->mpParent = pParent;
+ mpWindowImpl->mpRealParent = pParent;
+
+ if ( !pParent || mpWindowImpl->mbFrame )
+ return;
+
+ // search frame window and set window frame data
+ vcl::Window* pFrameParent = pParent->mpWindowImpl->mpFrameWindow;
+ mpWindowImpl->mpFrameData = pFrameParent->mpWindowImpl->mpFrameData;
+ if (mpWindowImpl->mpFrame != pFrameParent->mpWindowImpl->mpFrame)
+ {
+ mpWindowImpl->mpFrame = pFrameParent->mpWindowImpl->mpFrame;
+ if (mpWindowImpl->mpSysObj)
+ mpWindowImpl->mpSysObj->Reparent(mpWindowImpl->mpFrame);
+ }
+ mpWindowImpl->mpFrameWindow = pFrameParent;
+ mpWindowImpl->mbFrame = false;
+
+ // search overlap window and insert window in list
+ if ( ImplIsOverlapWindow() )
+ {
+ vcl::Window* pFirstOverlapParent = pParent;
+ while ( !pFirstOverlapParent->ImplIsOverlapWindow() )
+ pFirstOverlapParent = pFirstOverlapParent->ImplGetParent();
+ mpWindowImpl->mpOverlapWindow = pFirstOverlapParent;
+
+ mpWindowImpl->mpNextOverlap = mpWindowImpl->mpFrameData->mpFirstOverlap;
+ mpWindowImpl->mpFrameData->mpFirstOverlap = this;
+
+ // Overlap-Windows are by default the uppermost
+ mpWindowImpl->mpNext = pFirstOverlapParent->mpWindowImpl->mpFirstOverlap;
+ pFirstOverlapParent->mpWindowImpl->mpFirstOverlap = this;
+ if ( !pFirstOverlapParent->mpWindowImpl->mpLastOverlap )
+ pFirstOverlapParent->mpWindowImpl->mpLastOverlap = this;
+ else
+ mpWindowImpl->mpNext->mpWindowImpl->mpPrev = this;
+ }
+ else
+ {
+ if ( pParent->ImplIsOverlapWindow() )
+ mpWindowImpl->mpOverlapWindow = pParent;
+ else
+ mpWindowImpl->mpOverlapWindow = pParent->mpWindowImpl->mpOverlapWindow;
+ mpWindowImpl->mpPrev = pParent->mpWindowImpl->mpLastChild;
+ pParent->mpWindowImpl->mpLastChild = this;
+ if ( !pParent->mpWindowImpl->mpFirstChild )
+ pParent->mpWindowImpl->mpFirstChild = this;
+ else
+ mpWindowImpl->mpPrev->mpWindowImpl->mpNext = this;
+ }
+}
+
+void Window::ImplRemoveWindow( bool bRemoveFrameData )
+{
+ // remove window from the lists
+ if ( !mpWindowImpl->mbFrame )
+ {
+ if ( ImplIsOverlapWindow() )
+ {
+ if ( mpWindowImpl->mpFrameData->mpFirstOverlap.get() == this )
+ mpWindowImpl->mpFrameData->mpFirstOverlap = mpWindowImpl->mpNextOverlap;
+ else
+ {
+ vcl::Window* pTempWin = mpWindowImpl->mpFrameData->mpFirstOverlap;
+ while ( pTempWin->mpWindowImpl->mpNextOverlap.get() != this )
+ pTempWin = pTempWin->mpWindowImpl->mpNextOverlap;
+ pTempWin->mpWindowImpl->mpNextOverlap = mpWindowImpl->mpNextOverlap;
+ }
+
+ if ( mpWindowImpl->mpPrev )
+ mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext;
+ else
+ mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap = mpWindowImpl->mpNext;
+ if ( mpWindowImpl->mpNext )
+ mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev;
+ else
+ mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap = mpWindowImpl->mpPrev;
+ }
+ else
+ {
+ if ( mpWindowImpl->mpPrev )
+ mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext;
+ else if ( mpWindowImpl->mpParent )
+ mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild = mpWindowImpl->mpNext;
+ if ( mpWindowImpl->mpNext )
+ mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev;
+ else if ( mpWindowImpl->mpParent )
+ mpWindowImpl->mpParent->mpWindowImpl->mpLastChild = mpWindowImpl->mpPrev;
+ }
+
+ mpWindowImpl->mpPrev = nullptr;
+ mpWindowImpl->mpNext = nullptr;
+ }
+
+ if ( bRemoveFrameData )
+ {
+ // release the graphic
+ OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReleaseGraphics();
+ }
+}
+
+void Window::reorderWithinParent(sal_uInt16 nNewPosition)
+{
+ sal_uInt16 nChildCount = 0;
+ vcl::Window *pSource = mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild;
+ while (pSource)
+ {
+ if (nChildCount == nNewPosition)
+ break;
+ pSource = pSource->mpWindowImpl->mpNext;
+ nChildCount++;
+ }
+
+ if (pSource == this) //already at the right place
+ return;
+
+ ImplRemoveWindow(false);
+
+ if (pSource)
+ {
+ mpWindowImpl->mpNext = pSource;
+ mpWindowImpl->mpPrev = pSource->mpWindowImpl->mpPrev;
+ pSource->mpWindowImpl->mpPrev = this;
+ }
+ else
+ mpWindowImpl->mpParent->mpWindowImpl->mpLastChild = this;
+
+ if (mpWindowImpl->mpPrev)
+ mpWindowImpl->mpPrev->mpWindowImpl->mpNext = this;
+ else
+ mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild = this;
+}
+
+void Window::ImplToBottomChild()
+{
+ if ( ImplIsOverlapWindow() || mpWindowImpl->mbReallyVisible || (mpWindowImpl->mpParent->mpWindowImpl->mpLastChild.get() == this) )
+ return;
+
+ // put the window to the end of the list
+ if ( mpWindowImpl->mpPrev )
+ mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext;
+ else
+ mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild = mpWindowImpl->mpNext;
+ mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev;
+ mpWindowImpl->mpPrev = mpWindowImpl->mpParent->mpWindowImpl->mpLastChild;
+ mpWindowImpl->mpParent->mpWindowImpl->mpLastChild = this;
+ mpWindowImpl->mpPrev->mpWindowImpl->mpNext = this;
+ mpWindowImpl->mpNext = nullptr;
+}
+
+void Window::ImplCalcToTop( ImplCalcToTopData* pPrevData )
+{
+ SAL_WARN_IF( !ImplIsOverlapWindow(), "vcl", "Window::ImplCalcToTop(): Is not an OverlapWindow" );
+
+ if ( mpWindowImpl->mbFrame )
+ return;
+
+ if ( !IsReallyVisible() )
+ return;
+
+ // calculate region, where the window overlaps with other windows
+ vcl::Region aRegion( GetOutputRectPixel() );
+ vcl::Region aInvalidateRegion;
+ ImplCalcOverlapRegionOverlaps( aRegion, aInvalidateRegion );
+
+ if ( !aInvalidateRegion.IsEmpty() )
+ {
+ ImplCalcToTopData* pData = new ImplCalcToTopData;
+ pPrevData->mpNext.reset(pData);
+ pData->mpWindow = this;
+ pData->mpInvalidateRegion.reset(new vcl::Region( aInvalidateRegion ));
+ }
+}
+
+void Window::ImplToTop( ToTopFlags nFlags )
+{
+ SAL_WARN_IF( !ImplIsOverlapWindow(), "vcl", "Window::ImplToTop(): Is not an OverlapWindow" );
+
+ if ( mpWindowImpl->mbFrame )
+ {
+ // on a mouse click in the external window, it is the latter's
+ // responsibility to assure our frame is put in front
+ if ( !mpWindowImpl->mpFrameData->mbHasFocus &&
+ !mpWindowImpl->mpFrameData->mbSysObjFocus &&
+ !mpWindowImpl->mpFrameData->mbInSysObjFocusHdl &&
+ !mpWindowImpl->mpFrameData->mbInSysObjToTopHdl )
+ {
+ // do not bring floating windows on the client to top
+ if( !ImplGetClientWindow() || !(ImplGetClientWindow()->GetStyle() & WB_SYSTEMFLOATWIN) )
+ {
+ SalFrameToTop nSysFlags = SalFrameToTop::NONE;
+ if ( nFlags & ToTopFlags::RestoreWhenMin )
+ nSysFlags |= SalFrameToTop::RestoreWhenMin;
+ if ( nFlags & ToTopFlags::ForegroundTask )
+ nSysFlags |= SalFrameToTop::ForegroundTask;
+ if ( nFlags & ToTopFlags::GrabFocusOnly )
+ nSysFlags |= SalFrameToTop::GrabFocusOnly;
+ mpWindowImpl->mpFrame->ToTop( nSysFlags );
+ }
+ }
+ }
+ else
+ {
+ if ( mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap.get() != this )
+ {
+ // remove window from the list
+ mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext;
+ if ( mpWindowImpl->mpNext )
+ mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev;
+ else
+ mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap = mpWindowImpl->mpPrev;
+
+ // take AlwaysOnTop into account
+ bool bOnTop = IsAlwaysOnTopEnabled();
+ vcl::Window* pNextWin = mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap;
+ if ( !bOnTop )
+ {
+ while ( pNextWin )
+ {
+ if ( !pNextWin->IsAlwaysOnTopEnabled() )
+ break;
+ pNextWin = pNextWin->mpWindowImpl->mpNext;
+ }
+ }
+
+ // add the window to the list again
+ mpWindowImpl->mpNext = pNextWin;
+ if ( pNextWin )
+ {
+ mpWindowImpl->mpPrev = pNextWin->mpWindowImpl->mpPrev;
+ pNextWin->mpWindowImpl->mpPrev = this;
+ }
+ else
+ {
+ mpWindowImpl->mpPrev = mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap;
+ mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap = this;
+ }
+ if ( mpWindowImpl->mpPrev )
+ mpWindowImpl->mpPrev->mpWindowImpl->mpNext = this;
+ else
+ mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap = this;
+
+ // recalculate ClipRegion of this and all overlapping windows
+ if ( IsReallyVisible() )
+ {
+ mpWindowImpl->mpOverlapWindow->ImplSetClipFlagOverlapWindows();
+ }
+ }
+ }
+}
+
+void Window::ImplStartToTop( ToTopFlags nFlags )
+{
+ ImplCalcToTopData aStartData;
+ ImplCalcToTopData* pCurData;
+ vcl::Window* pOverlapWindow;
+ if ( ImplIsOverlapWindow() )
+ pOverlapWindow = this;
+ else
+ pOverlapWindow = mpWindowImpl->mpOverlapWindow;
+
+ // first calculate paint areas
+ vcl::Window* pTempOverlapWindow = pOverlapWindow;
+ aStartData.mpNext = nullptr;
+ pCurData = &aStartData;
+ do
+ {
+ pTempOverlapWindow->ImplCalcToTop( pCurData );
+ if ( pCurData->mpNext )
+ pCurData = pCurData->mpNext.get();
+ pTempOverlapWindow = pTempOverlapWindow->mpWindowImpl->mpOverlapWindow;
+ }
+ while ( !pTempOverlapWindow->mpWindowImpl->mbFrame );
+ // next calculate the paint areas of the ChildOverlap windows
+ pTempOverlapWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pTempOverlapWindow )
+ {
+ pTempOverlapWindow->ImplCalcToTop( pCurData );
+ if ( pCurData->mpNext )
+ pCurData = pCurData->mpNext.get();
+ pTempOverlapWindow = pTempOverlapWindow->mpWindowImpl->mpNext;
+ }
+
+ // and next change the windows list
+ pTempOverlapWindow = pOverlapWindow;
+ do
+ {
+ pTempOverlapWindow->ImplToTop( nFlags );
+ pTempOverlapWindow = pTempOverlapWindow->mpWindowImpl->mpOverlapWindow;
+ }
+ while ( !pTempOverlapWindow->mpWindowImpl->mbFrame );
+ // as last step invalidate the invalid areas
+ pCurData = aStartData.mpNext.get();
+ while ( pCurData )
+ {
+ pCurData->mpWindow->ImplInvalidateFrameRegion( pCurData->mpInvalidateRegion.get(), InvalidateFlags::Children );
+ pCurData = pCurData->mpNext.get();
+ }
+}
+
+void Window::ImplFocusToTop( ToTopFlags nFlags, bool bReallyVisible )
+{
+ // do we need to fetch the focus?
+ if ( !(nFlags & ToTopFlags::NoGrabFocus) )
+ {
+ // first window with GrabFocus-Activate gets the focus
+ vcl::Window* pFocusWindow = this;
+ while ( !pFocusWindow->ImplIsOverlapWindow() )
+ {
+ // if the window has no BorderWindow, we
+ // should always find the belonging BorderWindow
+ if ( !pFocusWindow->mpWindowImpl->mpBorderWindow )
+ {
+ if ( pFocusWindow->mpWindowImpl->mnActivateMode & ActivateModeFlags::GrabFocus )
+ break;
+ }
+ pFocusWindow = pFocusWindow->ImplGetParent();
+ }
+ if ( (pFocusWindow->mpWindowImpl->mnActivateMode & ActivateModeFlags::GrabFocus) &&
+ !pFocusWindow->HasChildPathFocus( true ) )
+ pFocusWindow->GrabFocus();
+ }
+
+ if ( bReallyVisible )
+ ImplGenerateMouseMove();
+}
+
+void Window::ImplShowAllOverlaps()
+{
+ vcl::Window* pOverlapWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pOverlapWindow )
+ {
+ if ( pOverlapWindow->mpWindowImpl->mbOverlapVisible )
+ {
+ pOverlapWindow->Show( true, ShowFlags::NoActivate );
+ pOverlapWindow->mpWindowImpl->mbOverlapVisible = false;
+ }
+
+ pOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplHideAllOverlaps()
+{
+ vcl::Window* pOverlapWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pOverlapWindow )
+ {
+ if ( pOverlapWindow->IsVisible() )
+ {
+ pOverlapWindow->mpWindowImpl->mbOverlapVisible = true;
+ pOverlapWindow->Show( false );
+ }
+
+ pOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ToTop( ToTopFlags nFlags )
+{
+ if (!mpWindowImpl)
+ return;
+
+ ImplStartToTop( nFlags );
+ ImplFocusToTop( nFlags, IsReallyVisible() );
+}
+
+void Window::SetZOrder( vcl::Window* pRefWindow, ZOrderFlags nFlags )
+{
+
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ mpWindowImpl->mpBorderWindow->SetZOrder( pRefWindow, nFlags );
+ return;
+ }
+
+ if ( nFlags & ZOrderFlags::First )
+ {
+ if ( ImplIsOverlapWindow() )
+ pRefWindow = mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap;
+ else
+ pRefWindow = mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild;
+ nFlags |= ZOrderFlags::Before;
+ }
+ else if ( nFlags & ZOrderFlags::Last )
+ {
+ if ( ImplIsOverlapWindow() )
+ pRefWindow = mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap;
+ else
+ pRefWindow = mpWindowImpl->mpParent->mpWindowImpl->mpLastChild;
+ nFlags |= ZOrderFlags::Behind;
+ }
+
+ while ( pRefWindow && pRefWindow->mpWindowImpl->mpBorderWindow )
+ pRefWindow = pRefWindow->mpWindowImpl->mpBorderWindow;
+ if (!pRefWindow || pRefWindow == this || mpWindowImpl->mbFrame)
+ return;
+
+ SAL_WARN_IF( pRefWindow->mpWindowImpl->mpParent != mpWindowImpl->mpParent, "vcl", "Window::SetZOrder() - pRefWindow has other parent" );
+ if ( nFlags & ZOrderFlags::Before )
+ {
+ if ( pRefWindow->mpWindowImpl->mpPrev.get() == this )
+ return;
+
+ if ( ImplIsOverlapWindow() )
+ {
+ if ( mpWindowImpl->mpPrev )
+ mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext;
+ else
+ mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap = mpWindowImpl->mpNext;
+ if ( mpWindowImpl->mpNext )
+ mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev;
+ else
+ mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap = mpWindowImpl->mpPrev;
+ if ( !pRefWindow->mpWindowImpl->mpPrev )
+ mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap = this;
+ }
+ else
+ {
+ if ( mpWindowImpl->mpPrev )
+ mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext;
+ else
+ mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild = mpWindowImpl->mpNext;
+ if ( mpWindowImpl->mpNext )
+ mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev;
+ else
+ mpWindowImpl->mpParent->mpWindowImpl->mpLastChild = mpWindowImpl->mpPrev;
+ if ( !pRefWindow->mpWindowImpl->mpPrev )
+ mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild = this;
+ }
+
+ mpWindowImpl->mpPrev = pRefWindow->mpWindowImpl->mpPrev;
+ mpWindowImpl->mpNext = pRefWindow;
+ if ( mpWindowImpl->mpPrev )
+ mpWindowImpl->mpPrev->mpWindowImpl->mpNext = this;
+ mpWindowImpl->mpNext->mpWindowImpl->mpPrev = this;
+ }
+ else if ( nFlags & ZOrderFlags::Behind )
+ {
+ if ( pRefWindow->mpWindowImpl->mpNext.get() == this )
+ return;
+
+ if ( ImplIsOverlapWindow() )
+ {
+ if ( mpWindowImpl->mpPrev )
+ mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext;
+ else
+ mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap = mpWindowImpl->mpNext;
+ if ( mpWindowImpl->mpNext )
+ mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev;
+ else
+ mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap = mpWindowImpl->mpPrev;
+ if ( !pRefWindow->mpWindowImpl->mpNext )
+ mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap = this;
+ }
+ else
+ {
+ if ( mpWindowImpl->mpPrev )
+ mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext;
+ else
+ mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild = mpWindowImpl->mpNext;
+ if ( mpWindowImpl->mpNext )
+ mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev;
+ else
+ mpWindowImpl->mpParent->mpWindowImpl->mpLastChild = mpWindowImpl->mpPrev;
+ if ( !pRefWindow->mpWindowImpl->mpNext )
+ mpWindowImpl->mpParent->mpWindowImpl->mpLastChild = this;
+ }
+
+ mpWindowImpl->mpPrev = pRefWindow;
+ mpWindowImpl->mpNext = pRefWindow->mpWindowImpl->mpNext;
+ if ( mpWindowImpl->mpNext )
+ mpWindowImpl->mpNext->mpWindowImpl->mpPrev = this;
+ mpWindowImpl->mpPrev->mpWindowImpl->mpNext = this;
+ }
+
+ if ( !IsReallyVisible() )
+ return;
+
+ if ( !mpWindowImpl->mbInitWinClipRegion && mpWindowImpl->maWinClipRegion.IsEmpty() )
+ return;
+
+ bool bInitWinClipRegion = mpWindowImpl->mbInitWinClipRegion;
+ ImplSetClipFlag();
+
+ // When ClipRegion was not initialised, assume
+ // the window has not been sent, therefore do not
+ // trigger any Invalidates. This is an optimization
+ // for HTML documents with many controls. If this
+ // check gives problems, a flag should be introduced
+ // which tracks whether the window has already been
+ // emitted after Show
+ if ( bInitWinClipRegion )
+ return;
+
+ // Invalidate all windows which are next to each other
+ // Is INCOMPLETE !!!
+ tools::Rectangle aWinRect = GetOutputRectPixel();
+ vcl::Window* pWindow = nullptr;
+ if ( ImplIsOverlapWindow() )
+ {
+ if ( mpWindowImpl->mpOverlapWindow )
+ pWindow = mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap;
+ }
+ else
+ pWindow = ImplGetParent()->mpWindowImpl->mpFirstChild;
+ // Invalidate all windows in front of us and which are covered by us
+ while ( pWindow )
+ {
+ if ( pWindow == this )
+ break;
+ tools::Rectangle aCompRect = pWindow->GetOutputRectPixel();
+ if ( aWinRect.Overlaps( aCompRect ) )
+ pWindow->Invalidate( InvalidateFlags::Children | InvalidateFlags::NoTransparent );
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+
+ // If we are covered by a window in the background
+ // we should redraw it
+ while ( pWindow )
+ {
+ if ( pWindow != this )
+ {
+ tools::Rectangle aCompRect = pWindow->GetOutputRectPixel();
+ if ( aWinRect.Overlaps( aCompRect ) )
+ {
+ Invalidate( InvalidateFlags::Children | InvalidateFlags::NoTransparent );
+ break;
+ }
+ }
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::EnableAlwaysOnTop( bool bEnable )
+{
+
+ mpWindowImpl->mbAlwaysOnTop = bEnable;
+
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->EnableAlwaysOnTop( bEnable );
+ else if ( bEnable && IsReallyVisible() )
+ ToTop();
+
+ if ( mpWindowImpl->mbFrame )
+ mpWindowImpl->mpFrame->SetAlwaysOnTop( bEnable );
+}
+
+bool Window::IsTopWindow() const
+{
+ if ( !mpWindowImpl || mpWindowImpl->mbInDispose )
+ return false;
+
+ // topwindows must be frames or they must have a borderwindow which is a frame
+ if( !mpWindowImpl->mbFrame && (!mpWindowImpl->mpBorderWindow || !mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame ) )
+ return false;
+
+ ImplGetWinData();
+ if( mpWindowImpl->mpWinData->mnIsTopWindow == sal_uInt16(~0)) // still uninitialized
+ {
+ // #113722#, cache result of expensive queryInterface call
+ vcl::Window *pThisWin = const_cast<vcl::Window*>(this);
+ uno::Reference< XTopWindow > xTopWindow( pThisWin->GetComponentInterface(), UNO_QUERY );
+ pThisWin->mpWindowImpl->mpWinData->mnIsTopWindow = xTopWindow.is() ? 1 : 0;
+ }
+ return mpWindowImpl->mpWinData->mnIsTopWindow == 1;
+}
+
+vcl::Window* Window::ImplFindWindow( const Point& rFramePos )
+{
+ vcl::Window* pTempWindow;
+ vcl::Window* pFindWindow;
+
+ // first check all overlapping windows
+ pTempWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pTempWindow )
+ {
+ pFindWindow = pTempWindow->ImplFindWindow( rFramePos );
+ if ( pFindWindow )
+ return pFindWindow;
+ pTempWindow = pTempWindow->mpWindowImpl->mpNext;
+ }
+
+ // then we check our window
+ if ( !mpWindowImpl->mbVisible )
+ return nullptr;
+
+ WindowHitTest nHitTest = ImplHitTest( rFramePos );
+ if ( nHitTest & WindowHitTest::Inside )
+ {
+ // and then we check all child windows
+ pTempWindow = mpWindowImpl->mpFirstChild;
+ while ( pTempWindow )
+ {
+ pFindWindow = pTempWindow->ImplFindWindow( rFramePos );
+ if ( pFindWindow )
+ return pFindWindow;
+ pTempWindow = pTempWindow->mpWindowImpl->mpNext;
+ }
+
+ if ( nHitTest & WindowHitTest::Transparent )
+ return nullptr;
+ else
+ return this;
+ }
+
+ return nullptr;
+}
+
+bool Window::ImplIsRealParentPath( const vcl::Window* pWindow ) const
+{
+ pWindow = pWindow->GetParent();
+ while ( pWindow )
+ {
+ if ( pWindow == this )
+ return true;
+ pWindow = pWindow->GetParent();
+ }
+
+ return false;
+}
+
+bool Window::ImplIsChild( const vcl::Window* pWindow, bool bSystemWindow ) const
+{
+ do
+ {
+ if ( !bSystemWindow && pWindow->ImplIsOverlapWindow() )
+ break;
+
+ pWindow = pWindow->ImplGetParent();
+
+ if ( pWindow == this )
+ return true;
+ }
+ while ( pWindow );
+
+ return false;
+}
+
+bool Window::ImplIsWindowOrChild( const vcl::Window* pWindow, bool bSystemWindow ) const
+{
+ if ( this == pWindow )
+ return true;
+ return ImplIsChild( pWindow, bSystemWindow );
+}
+
+void Window::ImplResetReallyVisible()
+{
+ bool bBecameReallyInvisible = mpWindowImpl->mbReallyVisible;
+
+ GetOutDev()->mbDevOutput = false;
+ mpWindowImpl->mbReallyVisible = false;
+ mpWindowImpl->mbReallyShown = false;
+
+ // the SHOW/HIDE events serve as indicators to send child creation/destroy events to the access bridge.
+ // For this, the data member of the event must not be NULL.
+ // Previously, we did this in Window::Show, but there some events got lost in certain situations.
+ if( bBecameReallyInvisible && ImplIsAccessibleCandidate() )
+ CallEventListeners( VclEventId::WindowHide, this );
+ // TODO. It's kind of a hack that we're re-using the VclEventId::WindowHide. Normally, we should
+ // introduce another event which explicitly triggers the Accessibility implementations.
+
+ vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbReallyVisible )
+ pWindow->ImplResetReallyVisible();
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+
+ pWindow = mpWindowImpl->mpFirstChild;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbReallyVisible )
+ pWindow->ImplResetReallyVisible();
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplUpdateWindowPtr( vcl::Window* pWindow )
+{
+ if ( mpWindowImpl->mpFrameWindow != pWindow->mpWindowImpl->mpFrameWindow )
+ {
+ // release graphic
+ OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReleaseGraphics();
+ }
+
+ mpWindowImpl->mpFrameData = pWindow->mpWindowImpl->mpFrameData;
+ if (mpWindowImpl->mpFrame != pWindow->mpWindowImpl->mpFrame)
+ {
+ mpWindowImpl->mpFrame = pWindow->mpWindowImpl->mpFrame;
+ if (mpWindowImpl->mpSysObj)
+ mpWindowImpl->mpSysObj->Reparent(mpWindowImpl->mpFrame);
+ }
+ mpWindowImpl->mpFrameWindow = pWindow->mpWindowImpl->mpFrameWindow;
+ if ( pWindow->ImplIsOverlapWindow() )
+ mpWindowImpl->mpOverlapWindow = pWindow;
+ else
+ mpWindowImpl->mpOverlapWindow = pWindow->mpWindowImpl->mpOverlapWindow;
+
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->ImplUpdateWindowPtr( pWindow );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplUpdateWindowPtr()
+{
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->ImplUpdateWindowPtr( this );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplUpdateOverlapWindowPtr( bool bNewFrame )
+{
+ bool bVisible = IsVisible();
+ Show( false );
+ ImplRemoveWindow( bNewFrame );
+ vcl::Window* pRealParent = mpWindowImpl->mpRealParent;
+ ImplInsertWindow( ImplGetParent() );
+ mpWindowImpl->mpRealParent = pRealParent;
+ ImplUpdateWindowPtr();
+ if ( ImplUpdatePos() )
+ ImplUpdateSysObjPos();
+
+ if ( bNewFrame )
+ {
+ vcl::Window* pOverlapWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pOverlapWindow )
+ {
+ vcl::Window* pNextOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext;
+ pOverlapWindow->ImplUpdateOverlapWindowPtr( bNewFrame );
+ pOverlapWindow = pNextOverlapWindow;
+ }
+ }
+
+ if ( bVisible )
+ Show();
+}
+
+SystemWindow* Window::GetSystemWindow() const
+{
+
+ const vcl::Window* pWin = this;
+ while ( pWin && !pWin->IsSystemWindow() )
+ pWin = pWin->GetParent();
+ return static_cast<SystemWindow*>(const_cast<Window*>(pWin));
+}
+
+static SystemWindow *ImplGetLastSystemWindow( vcl::Window *pWin )
+{
+ // get the most top-level system window, the one that contains the taskpanelist
+ SystemWindow *pSysWin = nullptr;
+ if( !pWin )
+ return pSysWin;
+ vcl::Window *pMyParent = pWin;
+ while ( pMyParent )
+ {
+ if ( pMyParent->IsSystemWindow() )
+ pSysWin = static_cast<SystemWindow*>(pMyParent);
+ pMyParent = pMyParent->GetParent();
+ }
+ return pSysWin;
+}
+
+void Window::SetParent( vcl::Window* pNewParent )
+{
+ SAL_WARN_IF( !pNewParent, "vcl", "Window::SetParent(): pParent == NULL" );
+ SAL_WARN_IF( pNewParent == this, "vcl", "someone tried to reparent a window to itself" );
+
+ if( !pNewParent || pNewParent == this )
+ return;
+
+ // check if the taskpanelist would change and move the window pointer accordingly
+ SystemWindow *pSysWin = ImplGetLastSystemWindow(this);
+ SystemWindow *pNewSysWin = nullptr;
+ bool bChangeTaskPaneList = false;
+ if( pSysWin && pSysWin->ImplIsInTaskPaneList( this ) )
+ {
+ pNewSysWin = ImplGetLastSystemWindow( pNewParent );
+ if( pNewSysWin && pNewSysWin != pSysWin )
+ {
+ bChangeTaskPaneList = true;
+ pSysWin->GetTaskPaneList()->RemoveWindow( this );
+ }
+ }
+ // remove ownerdraw decorated windows from list in the top-most frame window
+ if( (GetStyle() & WB_OWNERDRAWDECORATION) && mpWindowImpl->mbFrame )
+ {
+ ::std::vector< VclPtr<vcl::Window> >& rList = ImplGetOwnerDrawList();
+ auto p = ::std::find( rList.begin(), rList.end(), VclPtr<vcl::Window>(this) );
+ if( p != rList.end() )
+ rList.erase( p );
+ }
+
+ ImplSetFrameParent( pNewParent );
+
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ mpWindowImpl->mpRealParent = pNewParent;
+ mpWindowImpl->mpBorderWindow->SetParent( pNewParent );
+ return;
+ }
+
+ if ( mpWindowImpl->mpParent.get() == pNewParent )
+ return;
+
+ if ( mpWindowImpl->mbFrame )
+ mpWindowImpl->mpFrame->SetParent( pNewParent->mpWindowImpl->mpFrame );
+
+ bool bVisible = IsVisible();
+ Show( false, ShowFlags::NoFocusChange );
+
+ // check if the overlap window changes
+ vcl::Window* pOldOverlapWindow;
+ vcl::Window* pNewOverlapWindow = nullptr;
+ if ( ImplIsOverlapWindow() )
+ pOldOverlapWindow = nullptr;
+ else
+ {
+ pNewOverlapWindow = pNewParent->ImplGetFirstOverlapWindow();
+ if ( mpWindowImpl->mpOverlapWindow.get() != pNewOverlapWindow )
+ pOldOverlapWindow = mpWindowImpl->mpOverlapWindow;
+ else
+ pOldOverlapWindow = nullptr;
+ }
+
+ // convert windows in the hierarchy
+ bool bFocusOverlapWin = HasChildPathFocus( true );
+ bool bFocusWin = HasChildPathFocus();
+ bool bNewFrame = pNewParent->mpWindowImpl->mpFrameWindow != mpWindowImpl->mpFrameWindow;
+ if ( bNewFrame )
+ {
+ if ( mpWindowImpl->mpFrameData->mpFocusWin )
+ {
+ if ( IsWindowOrChild( mpWindowImpl->mpFrameData->mpFocusWin ) )
+ mpWindowImpl->mpFrameData->mpFocusWin = nullptr;
+ }
+ if ( mpWindowImpl->mpFrameData->mpMouseMoveWin )
+ {
+ if ( IsWindowOrChild( mpWindowImpl->mpFrameData->mpMouseMoveWin ) )
+ mpWindowImpl->mpFrameData->mpMouseMoveWin = nullptr;
+ }
+ if ( mpWindowImpl->mpFrameData->mpMouseDownWin )
+ {
+ if ( IsWindowOrChild( mpWindowImpl->mpFrameData->mpMouseDownWin ) )
+ mpWindowImpl->mpFrameData->mpMouseDownWin = nullptr;
+ }
+ }
+ ImplRemoveWindow( bNewFrame );
+ ImplInsertWindow( pNewParent );
+ if ( mpWindowImpl->mnParentClipMode & ParentClipMode::Clip )
+ pNewParent->mpWindowImpl->mbClipChildren = true;
+ ImplUpdateWindowPtr();
+ if ( ImplUpdatePos() )
+ ImplUpdateSysObjPos();
+
+ // If the Overlap-Window has changed, we need to test whether
+ // OverlapWindows that had the Child window as their parent
+ // need to be put into the window hierarchy.
+ if ( ImplIsOverlapWindow() )
+ {
+ if ( bNewFrame )
+ {
+ vcl::Window* pOverlapWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pOverlapWindow )
+ {
+ vcl::Window* pNextOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext;
+ pOverlapWindow->ImplUpdateOverlapWindowPtr( bNewFrame );
+ pOverlapWindow = pNextOverlapWindow;
+ }
+ }
+ }
+ else if ( pOldOverlapWindow )
+ {
+ // reset Focus-Save
+ if ( bFocusWin ||
+ (pOldOverlapWindow->mpWindowImpl->mpLastFocusWindow &&
+ IsWindowOrChild( pOldOverlapWindow->mpWindowImpl->mpLastFocusWindow )) )
+ pOldOverlapWindow->mpWindowImpl->mpLastFocusWindow = nullptr;
+
+ vcl::Window* pOverlapWindow = pOldOverlapWindow->mpWindowImpl->mpFirstOverlap;
+ while ( pOverlapWindow )
+ {
+ vcl::Window* pNextOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext;
+ if ( ImplIsRealParentPath( pOverlapWindow->ImplGetWindow() ) )
+ pOverlapWindow->ImplUpdateOverlapWindowPtr( bNewFrame );
+ pOverlapWindow = pNextOverlapWindow;
+ }
+
+ // update activate-status at next overlap window
+ if ( HasChildPathFocus( true ) )
+ ImplCallFocusChangeActivate( pNewOverlapWindow, pOldOverlapWindow );
+ }
+
+ // also convert Activate-Status
+ if ( bNewFrame )
+ {
+ if ( (GetType() == WindowType::BORDERWINDOW) &&
+ (ImplGetWindow()->GetType() == WindowType::FLOATINGWINDOW) )
+ static_cast<ImplBorderWindow*>(this)->SetDisplayActive( mpWindowImpl->mpFrameData->mbHasFocus );
+ }
+
+ // when required give focus to new frame if
+ // FocusWindow is changed with SetParent()
+ if ( bFocusOverlapWin )
+ {
+ mpWindowImpl->mpFrameData->mpFocusWin = Application::GetFocusWindow();
+ if ( !mpWindowImpl->mpFrameData->mbHasFocus )
+ {
+ mpWindowImpl->mpFrame->ToTop( SalFrameToTop::NONE );
+ }
+ }
+
+ // Assure DragSource and DropTarget members are created
+ if ( bNewFrame )
+ {
+ GetDropTarget();
+ }
+
+ if( bChangeTaskPaneList )
+ pNewSysWin->GetTaskPaneList()->AddWindow( this );
+
+ if( (GetStyle() & WB_OWNERDRAWDECORATION) && mpWindowImpl->mbFrame )
+ ImplGetOwnerDrawList().emplace_back(this );
+
+ if ( bVisible )
+ Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate );
+}
+
+bool Window::IsAncestorOf( const vcl::Window& rWindow ) const
+{
+ return ImplIsRealParentPath(&rWindow);
+}
+
+sal_uInt16 Window::GetChildCount() const
+{
+ if (!mpWindowImpl)
+ return 0;
+
+ sal_uInt16 nChildCount = 0;
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ nChildCount++;
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+
+ return nChildCount;
+}
+
+vcl::Window* Window::GetChild( sal_uInt16 nChild ) const
+{
+ if (!mpWindowImpl)
+ return nullptr;
+
+ sal_uInt16 nChildCount = 0;
+ vcl::Window* pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ if ( nChild == nChildCount )
+ return pChild;
+ pChild = pChild->mpWindowImpl->mpNext;
+ nChildCount++;
+ }
+
+ return nullptr;
+}
+
+vcl::Window* Window::GetWindow( GetWindowType nType ) const
+{
+ if (!mpWindowImpl)
+ return nullptr;
+
+ switch ( nType )
+ {
+ case GetWindowType::Parent:
+ return mpWindowImpl->mpRealParent;
+
+ case GetWindowType::FirstChild:
+ return mpWindowImpl->mpFirstChild;
+
+ case GetWindowType::LastChild:
+ return mpWindowImpl->mpLastChild;
+
+ case GetWindowType::Prev:
+ return mpWindowImpl->mpPrev;
+
+ case GetWindowType::Next:
+ return mpWindowImpl->mpNext;
+
+ case GetWindowType::FirstOverlap:
+ return mpWindowImpl->mpFirstOverlap;
+
+ case GetWindowType::Overlap:
+ if ( ImplIsOverlapWindow() )
+ return const_cast<vcl::Window*>(this);
+ else
+ return mpWindowImpl->mpOverlapWindow;
+
+ case GetWindowType::ParentOverlap:
+ if ( ImplIsOverlapWindow() )
+ return mpWindowImpl->mpOverlapWindow;
+ else
+ return mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpOverlapWindow;
+
+ case GetWindowType::Client:
+ return this->ImplGetWindow();
+
+ case GetWindowType::RealParent:
+ return ImplGetParent();
+
+ case GetWindowType::Frame:
+ return mpWindowImpl->mpFrameWindow;
+
+ case GetWindowType::Border:
+ if ( mpWindowImpl->mpBorderWindow )
+ return mpWindowImpl->mpBorderWindow->GetWindow( GetWindowType::Border );
+ return const_cast<vcl::Window*>(this);
+
+ case GetWindowType::FirstTopWindowChild:
+ return ImplGetWinData()->maTopWindowChildren.empty() ? nullptr : (*ImplGetWinData()->maTopWindowChildren.begin()).get();
+
+ case GetWindowType::NextTopWindowSibling:
+ {
+ if ( !mpWindowImpl->mpRealParent )
+ return nullptr;
+ const ::std::list< VclPtr<vcl::Window> >& rTopWindows( mpWindowImpl->mpRealParent->ImplGetWinData()->maTopWindowChildren );
+ ::std::list< VclPtr<vcl::Window> >::const_iterator myPos =
+ ::std::find( rTopWindows.begin(), rTopWindows.end(), this );
+ if ( ( myPos == rTopWindows.end() ) || ( ++myPos == rTopWindows.end() ) )
+ return nullptr;
+ return *myPos;
+ }
+
+ }
+
+ return nullptr;
+}
+
+bool Window::IsChild( const vcl::Window* pWindow ) const
+{
+ do
+ {
+ if ( pWindow->ImplIsOverlapWindow() )
+ break;
+
+ pWindow = pWindow->ImplGetParent();
+
+ if ( pWindow == this )
+ return true;
+ }
+ while ( pWindow );
+
+ return false;
+}
+
+bool Window::IsWindowOrChild( const vcl::Window* pWindow, bool bSystemWindow ) const
+{
+
+ if ( this == pWindow )
+ return true;
+ return ImplIsChild( pWindow, bSystemWindow );
+}
+
+void Window::ImplSetFrameParent( const vcl::Window* pParent )
+{
+ vcl::Window* pFrameWindow = ImplGetSVData()->maFrameData.mpFirstFrame;
+ while( pFrameWindow )
+ {
+ // search all frames that are children of this window
+ // and reparent them
+ if( ImplIsRealParentPath( pFrameWindow ) )
+ {
+ SAL_WARN_IF( mpWindowImpl->mpFrame == pFrameWindow->mpWindowImpl->mpFrame, "vcl", "SetFrameParent to own" );
+ SAL_WARN_IF( !mpWindowImpl->mpFrame, "vcl", "no frame" );
+ SalFrame* pParentFrame = pParent ? pParent->mpWindowImpl->mpFrame : nullptr;
+ pFrameWindow->mpWindowImpl->mpFrame->SetParent( pParentFrame );
+ }
+ pFrameWindow = pFrameWindow->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/status.cxx b/vcl/source/window/status.cxx
new file mode 100644
index 000000000..8c701b496
--- /dev/null
+++ b/vcl/source/window/status.cxx
@@ -0,0 +1,1464 @@
+/* -*- 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/log.hxx>
+#include <comphelper/string.hxx>
+#include <vcl/event.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/glyphitemcache.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/help.hxx>
+#include <vcl/vcllayout.hxx>
+#include <vcl/status.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/settings.hxx>
+#include <config_features.h>
+#include <svdata.hxx>
+#include <window.h>
+
+#define STATUSBAR_OFFSET_X STATUSBAR_OFFSET
+#define STATUSBAR_OFFSET_Y 2
+#define STATUSBAR_OFFSET_TEXTY 3
+
+#define STATUSBAR_PRGS_OFFSET 3
+#define STATUSBAR_PRGS_COUNT 100
+#define STATUSBAR_PRGS_MIN 5
+
+class StatusBar::ImplData
+{
+public:
+ ImplData();
+
+ VclPtr<VirtualDevice> mpVirDev;
+};
+
+StatusBar::ImplData::ImplData()
+{
+ mpVirDev = nullptr;
+}
+
+struct ImplStatusItem
+{
+ sal_uInt16 mnId;
+ StatusBarItemBits mnBits;
+ tools::Long mnWidth;
+ tools::Long mnOffset;
+ tools::Long mnExtraWidth;
+ tools::Long mnX;
+ OUString maText;
+ OUString maHelpText;
+ OUString maQuickHelpText;
+ OString maHelpId;
+ void* mpUserData;
+ bool mbVisible;
+ OUString maAccessibleName;
+ OUString maCommand;
+ std::optional<SalLayoutGlyphs> mLayoutGlyphsCache;
+ SalLayoutGlyphs* GetTextGlyphs(const OutputDevice* pOutputDevice);
+};
+
+SalLayoutGlyphs* ImplStatusItem::GetTextGlyphs(const OutputDevice* outputDevice)
+{
+ if(!mLayoutGlyphsCache.has_value())
+ {
+ std::unique_ptr<SalLayout> pSalLayout = outputDevice->ImplLayout(
+ maText, 0, -1, Point(0, 0), 0, {}, SalLayoutFlags::GlyphItemsOnly);
+ mLayoutGlyphsCache = pSalLayout ? pSalLayout->GetGlyphs() : SalLayoutGlyphs();
+ }
+ return mLayoutGlyphsCache->IsValid() ? &mLayoutGlyphsCache.value() : nullptr;
+}
+
+static tools::Long ImplCalcProgressWidth( sal_uInt16 nMax, tools::Long nSize )
+{
+ return ((nMax*(nSize+(nSize/2)))-(nSize/2)+(STATUSBAR_PRGS_OFFSET*2));
+}
+
+static Point ImplGetItemTextPos( const Size& rRectSize, const Size& rTextSize,
+ StatusBarItemBits nStyle )
+{
+ tools::Long nX;
+ tools::Long nY;
+ tools::Long delta = (rTextSize.Height()/4) + 1;
+ if( delta + rTextSize.Width() > rRectSize.Width() )
+ delta = 0;
+
+ if ( nStyle & StatusBarItemBits::Left )
+ nX = delta;
+ else if ( nStyle & StatusBarItemBits::Right )
+ nX = rRectSize.Width()-rTextSize.Width()-delta;
+ else // StatusBarItemBits::Center
+ nX = (rRectSize.Width()-rTextSize.Width())/2;
+ nY = (rRectSize.Height()-rTextSize.Height())/2 + 1;
+ return Point( nX, nY );
+}
+
+bool StatusBar::ImplIsItemUpdate() const
+{
+ return !mbProgressMode && IsReallyVisible() && IsUpdateMode();
+}
+
+void StatusBar::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ mpImplData.reset(new ImplData);
+
+ // default: RightAlign
+ if ( !(nStyle & (WB_LEFT | WB_RIGHT)) )
+ nStyle |= WB_RIGHT;
+
+ Window::ImplInit( pParent, nStyle & ~WB_BORDER, nullptr );
+
+ // remember WinBits
+ mpImplData->mpVirDev = VclPtr<VirtualDevice>::Create( *GetOutDev() );
+ mnCurItemId = 0;
+ mbFormat = true;
+ mbProgressMode = false;
+ mbInUserDraw = false;
+ mbAdjustHiDPI = false;
+ mnItemsWidth = STATUSBAR_OFFSET_X;
+ mnDX = 0;
+ mnDY = 0;
+ mnCalcHeight = 0;
+ mnTextY = STATUSBAR_OFFSET_TEXTY;
+
+ ImplInitSettings();
+
+ SetOutputSizePixel( CalcWindowSizePixel() );
+}
+
+StatusBar::StatusBar( vcl::Window* pParent, WinBits nStyle ) :
+ Window( WindowType::STATUSBAR ),
+ mnLastProgressPaint_ms(osl_getGlobalTimer())
+{
+ ImplInit( pParent, nStyle );
+}
+
+StatusBar::~StatusBar()
+{
+ disposeOnce();
+}
+
+void StatusBar::dispose()
+{
+ // delete all items
+ mvItemList.clear();
+
+ // delete VirtualDevice
+ mpImplData->mpVirDev.disposeAndClear();
+ mpImplData.reset();
+ Window::dispose();
+}
+
+void StatusBar::AdjustItemWidthsForHiDPI()
+{
+ mbAdjustHiDPI = true;
+}
+
+void StatusBar::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.SetLineColor();
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ ApplyControlFont(rRenderContext, rStyleSettings.GetToolFont());
+
+ Color aColor;
+ if (IsControlForeground())
+ aColor = GetControlForeground();
+ else if (GetStyle() & WB_3DLOOK)
+ aColor = rStyleSettings.GetButtonTextColor();
+ else
+ aColor = rStyleSettings.GetWindowTextColor();
+ rRenderContext.SetTextColor(aColor);
+
+ rRenderContext.SetTextFillColor();
+
+ if (IsControlBackground())
+ aColor = GetControlBackground();
+ else if (GetStyle() & WB_3DLOOK)
+ aColor = rStyleSettings.GetFaceColor();
+ else
+ aColor = rStyleSettings.GetWindowColor();
+ rRenderContext.SetBackground(aColor);
+
+ // NWF background
+ if (!IsControlBackground() &&
+ rRenderContext.IsNativeControlSupported(ControlType::WindowBackground, ControlPart::BackgroundWindow))
+ {
+ ImplGetWindowImpl()->mnNativeBackground = ControlPart::BackgroundWindow;
+ EnableChildTransparentMode();
+ }
+}
+
+void StatusBar::ImplInitSettings()
+{
+ ApplySettings(*GetOutDev());
+
+ mpImplData->mpVirDev->SetFont(GetFont());
+ mpImplData->mpVirDev->SetTextColor(GetTextColor());
+ mpImplData->mpVirDev->SetTextAlign(GetTextAlign());
+ mpImplData->mpVirDev->SetTextFillColor();
+ mpImplData->mpVirDev->SetBackground(GetBackground());
+}
+
+void StatusBar::ImplFormat()
+{
+ tools::Long nExtraWidth;
+ tools::Long nExtraWidth2;
+ tools::Long nX;
+ sal_uInt16 nAutoSizeItems;
+ bool bChanged;
+
+ do {
+ // sum up widths
+ nAutoSizeItems = 0;
+ mnItemsWidth = STATUSBAR_OFFSET_X;
+ bChanged = false;
+ tools::Long nOffset = 0;
+ for ( const auto & pItem : mvItemList ) {
+ if ( pItem->mbVisible )
+ {
+ if ( pItem->mnBits & StatusBarItemBits::AutoSize ) {
+ nAutoSizeItems++;
+ }
+
+ mnItemsWidth += pItem->mnWidth + nOffset;
+ nOffset = pItem->mnOffset;
+ }
+ }
+
+ if ( mnDX > 0 && mnDX < mnItemsWidth )
+ {
+ // Total width of items is more than available width
+ // Try to hide secondary elements, if any
+ for ( auto & pItem : mvItemList )
+ {
+ if ( pItem->mbVisible && !(pItem->mnBits & StatusBarItemBits::Mandatory) )
+ {
+ pItem->mbVisible = false;
+ bChanged = true;
+ break;
+ }
+ }
+ }
+ else if ( mnDX > mnItemsWidth )
+ {
+ // Width of statusbar is sufficient.
+ // Try to restore hidden items, if any
+ for ( auto & pItem : mvItemList )
+ {
+ if ( !pItem->mbVisible &&
+ !(pItem->mnBits & StatusBarItemBits::Mandatory) &&
+ pItem->mnWidth + nOffset + mnItemsWidth < mnDX )
+ {
+ pItem->mbVisible = true;
+ bChanged = true;
+ break;
+ }
+ }
+ }
+ } while ( bChanged );
+
+ if ( GetStyle() & WB_RIGHT )
+ {
+ // AutoSize isn't computed for right-alignment,
+ // because we show the text that is declared by SetText on the left side
+ nX = mnDX - mnItemsWidth;
+ nExtraWidth = 0;
+ nExtraWidth2 = 0;
+ }
+ else
+ {
+ mnItemsWidth += STATUSBAR_OFFSET_X;
+
+ // calling AutoSize is potentially necessary for left-aligned text,
+ if ( nAutoSizeItems && (mnDX > (mnItemsWidth - STATUSBAR_OFFSET)) )
+ {
+ nExtraWidth = (mnDX - mnItemsWidth - 1) / nAutoSizeItems;
+ nExtraWidth2 = (mnDX - mnItemsWidth - 1) % nAutoSizeItems;
+ }
+ else
+ {
+ nExtraWidth = 0;
+ nExtraWidth2 = 0;
+ }
+ nX = STATUSBAR_OFFSET_X;
+
+ if( GetOutDev()->HasMirroredGraphics() && IsRTLEnabled() )
+ nX += ImplGetSVData()->maNWFData.mnStatusBarLowerRightOffset;
+ }
+
+ for (auto & pItem : mvItemList) {
+ if ( pItem->mbVisible ) {
+ if ( pItem->mnBits & StatusBarItemBits::AutoSize ) {
+ pItem->mnExtraWidth = nExtraWidth;
+ if ( nExtraWidth2 ) {
+ pItem->mnExtraWidth++;
+ nExtraWidth2--;
+ }
+ } else {
+ pItem->mnExtraWidth = 0;
+ }
+
+ pItem->mnX = nX;
+ nX += pItem->mnWidth + pItem->mnExtraWidth + pItem->mnOffset;
+ }
+ }
+
+ mbFormat = false;
+}
+
+tools::Rectangle StatusBar::ImplGetItemRectPos( sal_uInt16 nPos ) const
+{
+ tools::Rectangle aRect;
+ ImplStatusItem* pItem = ( nPos < mvItemList.size() ) ? mvItemList[ nPos ].get() : nullptr;
+ if ( pItem && pItem->mbVisible )
+ {
+ aRect.SetLeft( pItem->mnX );
+ aRect.SetRight( aRect.Left() + pItem->mnWidth + pItem->mnExtraWidth );
+ aRect.SetTop( STATUSBAR_OFFSET_Y );
+ aRect.SetBottom( mnCalcHeight - STATUSBAR_OFFSET_Y );
+ }
+
+ return aRect;
+}
+
+sal_uInt16 StatusBar::ImplGetFirstVisiblePos() const
+{
+ for( size_t nPos = 0; nPos < mvItemList.size(); nPos++ )
+ {
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+ if ( pItem->mbVisible )
+ return sal_uInt16(nPos);
+ }
+
+ return SAL_MAX_UINT16;
+}
+
+void StatusBar::ImplDrawText(vcl::RenderContext& rRenderContext)
+{
+ // prevent item box from being overwritten
+ tools::Rectangle aTextRect;
+ aTextRect.SetLeft( STATUSBAR_OFFSET_X + 1 );
+ aTextRect.SetTop( mnTextY );
+ if (GetStyle() & WB_RIGHT)
+ aTextRect.SetRight( mnDX - mnItemsWidth - 1 );
+ else
+ aTextRect.SetRight( mnDX - 1 );
+ if (aTextRect.Right() > aTextRect.Left())
+ {
+ // compute position
+ OUString aStr = GetText();
+ sal_Int32 nPos = aStr.indexOf('\n');
+ if (nPos != -1)
+ aStr = aStr.copy(0, nPos);
+
+ aTextRect.SetBottom( aTextRect.Top()+GetTextHeight()+1 );
+
+ rRenderContext.DrawText(aTextRect, aStr, DrawTextFlags::Left | DrawTextFlags::Top | DrawTextFlags::Clip | DrawTextFlags::EndEllipsis);
+ }
+}
+
+void StatusBar::ImplDrawItem(vcl::RenderContext& rRenderContext, bool bOffScreen, sal_uInt16 nPos)
+{
+ tools::Rectangle aRect = ImplGetItemRectPos(nPos);
+
+ if (aRect.IsEmpty())
+ return;
+
+ // compute output region
+ ImplStatusItem* pItem = mvItemList[nPos].get();
+ tools::Long nW = 1;
+ tools::Rectangle aTextRect(aRect.Left() + nW, aRect.Top() + nW,
+ aRect.Right() - nW, aRect.Bottom() - nW);
+
+ Size aTextRectSize(aTextRect.GetSize());
+
+ if (bOffScreen)
+ {
+ mpImplData->mpVirDev->SetOutputSizePixel(aTextRectSize);
+ }
+ else
+ {
+ vcl::Region aRegion(aTextRect);
+ rRenderContext.SetClipRegion(aRegion);
+ }
+
+ // if the framework code is drawing status, let it do all the work
+ if (!(pItem->mnBits & StatusBarItemBits::UserDraw))
+ {
+ SalLayoutGlyphs* pGlyphs = pItem->GetTextGlyphs(&rRenderContext);
+ Size aTextSize(rRenderContext.GetTextWidth(pItem->maText,0,-1,nullptr,pGlyphs),
+ rRenderContext.GetTextHeight());
+ Point aTextPos = ImplGetItemTextPos(aTextRectSize, aTextSize, pItem->mnBits);
+
+ if (bOffScreen)
+ {
+ mpImplData->mpVirDev->DrawText(
+ aTextPos,
+ pItem->maText,
+ 0, -1, nullptr, nullptr,
+ pGlyphs );
+ }
+ else
+ {
+ aTextPos.AdjustX(aTextRect.Left() );
+ aTextPos.AdjustY(aTextRect.Top() );
+ rRenderContext.DrawText(
+ aTextPos,
+ pItem->maText,
+ 0, -1, nullptr, nullptr,
+ pGlyphs );
+ }
+ }
+
+ // call DrawItem if necessary
+ if (pItem->mnBits & StatusBarItemBits::UserDraw)
+ {
+ if (bOffScreen)
+ {
+ mbInUserDraw = true;
+ mpImplData->mpVirDev->EnableRTL( IsRTLEnabled() );
+ UserDrawEvent aODEvt(mpImplData->mpVirDev, tools::Rectangle(Point(), aTextRectSize), pItem->mnId);
+ UserDraw(aODEvt);
+ mpImplData->mpVirDev->EnableRTL(false);
+ mbInUserDraw = false;
+ }
+ else
+ {
+ UserDrawEvent aODEvt(&rRenderContext, aTextRect, pItem->mnId);
+ UserDraw(aODEvt);
+ }
+ }
+
+ if (bOffScreen)
+ rRenderContext.DrawOutDev(aTextRect.TopLeft(), aTextRectSize, Point(), aTextRectSize, *mpImplData->mpVirDev);
+ else
+ rRenderContext.SetClipRegion();
+
+ if (nPos != ImplGetFirstVisiblePos())
+ {
+ // draw separator
+ Point aFrom(aRect.TopLeft());
+ aFrom.AdjustX( -4 );
+ aFrom.AdjustY( 1 );
+ Point aTo(aRect.BottomLeft());
+ aTo.AdjustX( -4 );
+ aTo.AdjustY( -1 );
+
+ DecorationView aDecoView(&rRenderContext);
+ aDecoView.DrawSeparator(aFrom, aTo);
+ }
+
+ if (!rRenderContext.ImplIsRecordLayout())
+ CallEventListeners(VclEventId::StatusbarDrawItem, reinterpret_cast<void*>(pItem->mnId));
+}
+
+void DrawProgress(vcl::Window* pWindow, vcl::RenderContext& rRenderContext, const Point& rPos,
+ tools::Long nOffset, tools::Long nPrgsWidth, tools::Long nPrgsHeight,
+ sal_uInt16 nPercent1, sal_uInt16 nPercent2, sal_uInt16 nPercentCount,
+ const tools::Rectangle& rFramePosSize)
+{
+ if (rRenderContext.IsNativeControlSupported(ControlType::Progress, ControlPart::Entire))
+ {
+ bool bNeedErase = ImplGetSVData()->maNWFData.mbProgressNeedsErase;
+
+ tools::Long nFullWidth = (nPrgsWidth + nOffset) * (10000 / nPercentCount);
+ tools::Long nPerc = std::min<sal_uInt16>(nPercent2, 10000);
+ ImplControlValue aValue(nFullWidth * nPerc / 10000);
+ tools::Rectangle aDrawRect(rPos, Size(nFullWidth, nPrgsHeight));
+ tools::Rectangle aControlRegion(aDrawRect);
+
+ if(bNeedErase)
+ {
+ vcl::Window* pEraseWindow = pWindow;
+ while (pEraseWindow->IsPaintTransparent() && !pEraseWindow->ImplGetWindowImpl()->mbFrame)
+ {
+ pEraseWindow = pEraseWindow->ImplGetWindowImpl()->mpParent;
+ }
+
+ if (pEraseWindow == pWindow)
+ {
+ // restore background of pWindow
+ rRenderContext.Erase(rFramePosSize);
+ }
+ else
+ {
+ // restore transparent background
+ Point aTL(pWindow->OutputToAbsoluteScreenPixel(rFramePosSize.TopLeft()));
+ aTL = pEraseWindow->AbsoluteScreenToOutputPixel(aTL);
+ tools::Rectangle aRect(aTL, rFramePosSize.GetSize());
+ pEraseWindow->Invalidate(aRect, InvalidateFlags::NoChildren |
+ InvalidateFlags::NoClipChildren |
+ InvalidateFlags::Transparent);
+ pEraseWindow->PaintImmediately();
+ }
+ rRenderContext.Push(vcl::PushFlags::CLIPREGION);
+ rRenderContext.IntersectClipRegion(rFramePosSize);
+ }
+
+ bool bNativeOK = rRenderContext.DrawNativeControl(ControlType::Progress, ControlPart::Entire, aControlRegion,
+ ControlState::ENABLED, aValue, OUString());
+ if (bNeedErase)
+ rRenderContext.Pop();
+ if (bNativeOK)
+ return;
+ }
+
+ // precompute values
+ sal_uInt16 nPerc1 = nPercent1 / nPercentCount;
+ sal_uInt16 nPerc2 = nPercent2 / nPercentCount;
+
+ if (nPerc1 > nPerc2)
+ {
+ // support progress that can also decrease
+
+ // compute rectangle
+ tools::Long nDX = nPrgsWidth + nOffset;
+ tools::Long nLeft = rPos.X() + ((nPerc1 - 1) * nDX);
+ tools::Rectangle aRect(nLeft, rPos.Y(), nLeft + nPrgsWidth, rPos.Y() + nPrgsHeight);
+
+ do
+ {
+ rRenderContext.Erase(aRect);
+ aRect.AdjustLeft( -nDX );
+ aRect.AdjustRight( -nDX );
+ nPerc1--;
+ }
+ while (nPerc1 > nPerc2);
+ }
+ else if (nPerc1 < nPerc2)
+ {
+ // draw Percent rectangle
+ // if Percent2 greater than 100%, adapt values
+ if (nPercent2 > 10000)
+ {
+ nPerc2 = 10000 / nPercentCount;
+ if (nPerc1 >= nPerc2)
+ nPerc1 = nPerc2 - 1;
+ }
+
+ // compute rectangle
+ tools::Long nDX = nPrgsWidth + nOffset;
+ tools::Long nLeft = rPos.X() + (nPerc1 * nDX);
+ tools::Rectangle aRect(nLeft, rPos.Y(), nLeft + nPrgsWidth, rPos.Y() + nPrgsHeight);
+
+ do
+ {
+ rRenderContext.DrawRect(aRect);
+ aRect.AdjustLeft(nDX );
+ aRect.AdjustRight(nDX );
+ nPerc1++;
+ }
+ while (nPerc1 < nPerc2);
+
+ // if greater than 100%, set rectangle to blink
+ if (nPercent2 > 10000)
+ {
+ // define on/off status
+ if (((nPercent2 / nPercentCount) & 0x01) == (nPercentCount & 0x01))
+ {
+ aRect.AdjustLeft( -nDX );
+ aRect.AdjustRight( -nDX );
+ rRenderContext.Erase(aRect);
+ }
+ }
+ }
+}
+
+void StatusBar::ImplDrawProgress(vcl::RenderContext& rRenderContext, sal_uInt16 nPercent2)
+{
+ bool bNative = rRenderContext.IsNativeControlSupported(ControlType::Progress, ControlPart::Entire);
+ // bPaint: draw text also, else only update progress
+ rRenderContext.DrawText(maPrgsTxtPos, maPrgsTxt);
+ if (!bNative)
+ {
+ DecorationView aDecoView(&rRenderContext);
+ aDecoView.DrawFrame(maPrgsFrameRect, DrawFrameStyle::In);
+ }
+
+ Point aPos(maPrgsFrameRect.Left() + STATUSBAR_PRGS_OFFSET,
+ maPrgsFrameRect.Top() + STATUSBAR_PRGS_OFFSET);
+ tools::Long nPrgsHeight = mnPrgsSize;
+ if (bNative)
+ {
+ aPos = maPrgsFrameRect.TopLeft();
+ nPrgsHeight = maPrgsFrameRect.GetHeight();
+ }
+ DrawProgress(this, rRenderContext, aPos, mnPrgsSize / 2, mnPrgsSize, nPrgsHeight,
+ 0, nPercent2 * 100, mnPercentCount, maPrgsFrameRect);
+}
+
+void StatusBar::ImplCalcProgressRect()
+{
+ // calculate text size
+ Size aPrgsTxtSize( GetTextWidth( maPrgsTxt ), GetTextHeight() );
+ maPrgsTxtPos.setX( STATUSBAR_OFFSET_X+1 );
+
+ // calculate progress frame
+ maPrgsFrameRect.SetLeft( maPrgsTxtPos.X()+aPrgsTxtSize.Width()+STATUSBAR_OFFSET );
+ maPrgsFrameRect.SetTop( STATUSBAR_OFFSET_Y );
+ maPrgsFrameRect.SetBottom( mnCalcHeight - STATUSBAR_OFFSET_Y );
+
+ // calculate size of progress rects
+ mnPrgsSize = maPrgsFrameRect.Bottom()-maPrgsFrameRect.Top()-(STATUSBAR_PRGS_OFFSET*2);
+ sal_uInt16 nMaxPercent = STATUSBAR_PRGS_COUNT;
+
+ tools::Long nMaxWidth = mnDX-STATUSBAR_OFFSET-1;
+
+ // make smaller if there are too many rects
+ while ( maPrgsFrameRect.Left()+ImplCalcProgressWidth( nMaxPercent, mnPrgsSize ) > nMaxWidth )
+ {
+ nMaxPercent--;
+ if ( nMaxPercent <= STATUSBAR_PRGS_MIN )
+ break;
+ }
+ maPrgsFrameRect.SetRight( maPrgsFrameRect.Left() + ImplCalcProgressWidth( nMaxPercent, mnPrgsSize ) );
+
+ // save the divisor for later
+ mnPercentCount = 10000 / nMaxPercent;
+ bool bNativeOK = false;
+ if( IsNativeControlSupported( ControlType::Progress, ControlPart::Entire ) )
+ {
+ ImplControlValue aValue;
+ tools::Rectangle aControlRegion( tools::Rectangle( Point(), maPrgsFrameRect.GetSize() ) );
+ tools::Rectangle aNativeControlRegion, aNativeContentRegion;
+ if( (bNativeOK = GetNativeControlRegion( ControlType::Progress, ControlPart::Entire, aControlRegion,
+ ControlState::ENABLED, aValue,
+ aNativeControlRegion, aNativeContentRegion ) ) )
+ {
+ tools::Long nProgressHeight = aNativeControlRegion.GetHeight();
+ if( nProgressHeight > maPrgsFrameRect.GetHeight() )
+ {
+ tools::Long nDelta = nProgressHeight - maPrgsFrameRect.GetHeight();
+ maPrgsFrameRect.AdjustTop( -(nDelta - nDelta/2) );
+ maPrgsFrameRect.AdjustBottom(nDelta/2 );
+ }
+ maPrgsTxtPos.setY( maPrgsFrameRect.Top() + (nProgressHeight - GetTextHeight())/2 );
+ }
+ }
+ if( ! bNativeOK )
+ maPrgsTxtPos.setY( mnTextY );
+}
+
+void StatusBar::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ // trigger toolbox only for left mouse button
+ if ( !rMEvt.IsLeft() )
+ return;
+
+ Point aMousePos = rMEvt.GetPosPixel();
+
+ // search for clicked item
+ for ( size_t i = 0; i < mvItemList.size(); ++i )
+ {
+ ImplStatusItem* pItem = mvItemList[ i ].get();
+ // check item for being clicked
+ if ( ImplGetItemRectPos( sal_uInt16(i) ).Contains( aMousePos ) )
+ {
+ mnCurItemId = pItem->mnId;
+ if ( rMEvt.GetClicks() == 2 )
+ DoubleClick();
+ else
+ Click();
+ mnCurItemId = 0;
+
+ // Item found
+ return;
+ }
+ }
+
+ // if there's no item, trigger Click or DoubleClick
+ if ( rMEvt.GetClicks() == 2 )
+ DoubleClick();
+ else
+ Click();
+}
+
+void StatusBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (mbFormat)
+ ImplFormat();
+
+ sal_uInt16 nItemCount = sal_uInt16( mvItemList.size() );
+
+ if (mbProgressMode)
+ {
+ rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR);
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ Color aProgressColor = rStyleSettings.GetHighlightColor();
+ if (aProgressColor == rStyleSettings.GetFaceColor())
+ aProgressColor = rStyleSettings.GetDarkShadowColor();
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor(aProgressColor);
+
+ ImplDrawProgress(rRenderContext, mnPercent);
+
+ rRenderContext.Pop();
+ }
+ else
+ {
+ // draw text
+ if (GetStyle() & WB_RIGHT)
+ ImplDrawText(rRenderContext);
+
+ // draw items
+
+ // Do offscreen only when we are not recording layout...
+ bool bOffscreen = !rRenderContext.ImplIsRecordLayout();
+
+ if (!bOffscreen)
+ rRenderContext.Erase(rRect);
+
+ for (sal_uInt16 i = 0; i < nItemCount; i++)
+ ImplDrawItem(rRenderContext, bOffscreen, i);
+ }
+
+ // draw line at the top of the status bar (to visually distinguish it from
+ // shell / docking area)
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(Point(0, 0), Point(mnDX-1, 0));
+}
+
+void StatusBar::Resize()
+{
+ // save width and height
+ Size aSize = GetOutputSizePixel();
+ mnDX = aSize.Width() - ImplGetSVData()->maNWFData.mnStatusBarLowerRightOffset;
+ mnDY = aSize.Height();
+ mnCalcHeight = mnDY;
+
+ mnTextY = (mnCalcHeight-GetTextHeight())/2;
+
+ // provoke re-formatting
+ mbFormat = true;
+
+ if ( mbProgressMode )
+ ImplCalcProgressRect();
+
+ Invalidate();
+}
+
+void StatusBar::RequestHelp( const HelpEvent& rHEvt )
+{
+ // no keyboard help in status bar
+ if( rHEvt.KeyboardActivated() )
+ return;
+
+ sal_uInt16 nItemId = GetItemId( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) );
+
+ if ( nItemId )
+ {
+ tools::Rectangle aItemRect = GetItemRect( nItemId );
+ Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
+ aItemRect.SetLeft( aPt.X() );
+ aItemRect.SetTop( aPt.Y() );
+ aPt = OutputToScreenPixel( aItemRect.BottomRight() );
+ aItemRect.SetRight( aPt.X() );
+ aItemRect.SetBottom( aPt.Y() );
+
+ if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
+ {
+ OUString aStr = GetHelpText( nItemId );
+ Help::ShowBalloon( this, aItemRect.Center(), aItemRect, aStr );
+ return;
+ }
+ else if ( rHEvt.GetMode() & HelpEventMode::QUICK )
+ {
+ OUString aStr(GetQuickHelpText(nItemId));
+ // show quickhelp if available
+ if (!aStr.isEmpty())
+ {
+ Help::ShowQuickHelp( this, aItemRect, aStr );
+ return;
+ }
+ aStr = GetItemText( nItemId );
+ // show a quick help if item text doesn't fit
+ if ( GetTextWidth( aStr ) > aItemRect.GetWidth() )
+ {
+ Help::ShowQuickHelp( this, aItemRect, aStr );
+ return;
+ }
+ }
+ }
+
+ Window::RequestHelp( rHEvt );
+}
+
+void StatusBar::StateChanged( StateChangedType nType )
+{
+ Window::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ ImplFormat();
+ else if ( nType == StateChangedType::UpdateMode )
+ Invalidate();
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ mbFormat = true;
+ ImplInitSettings();
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+
+ //invalidate layout cache
+ for (auto & pItem : mvItemList)
+ {
+ pItem->mLayoutGlyphsCache.reset();
+ }
+
+}
+
+void StatusBar::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Window::DataChanged( rDCEvt );
+
+ if ( !((rDCEvt.GetType() == DataChangedEventType::DISPLAY )
+ || (rDCEvt.GetType() == DataChangedEventType::FONTS )
+ || (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION)
+ || ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS)
+ && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE )
+ ))
+ )
+ return;
+
+ mbFormat = true;
+ ImplInitSettings();
+ tools::Long nFudge = GetTextHeight() / 4;
+ for (auto & pItem : mvItemList)
+ {
+ tools::Long nWidth = GetTextWidth( pItem->maText ) + nFudge;
+ if( nWidth > pItem->mnWidth + STATUSBAR_OFFSET )
+ pItem->mnWidth = nWidth + STATUSBAR_OFFSET;
+
+ pItem->mLayoutGlyphsCache.reset();
+ }
+ Size aSize = GetSizePixel();
+ // do not disturb current width, since
+ // CalcWindowSizePixel calculates a minimum width
+ aSize.setHeight( CalcWindowSizePixel().Height() );
+ SetSizePixel( aSize );
+ Invalidate();
+}
+
+void StatusBar::Click()
+{
+ maClickHdl.Call( this );
+}
+
+void StatusBar::DoubleClick()
+{
+ maDoubleClickHdl.Call( this );
+}
+
+void StatusBar::UserDraw( const UserDrawEvent& )
+{
+}
+
+void StatusBar::InsertItem( sal_uInt16 nItemId, sal_uLong nWidth,
+ StatusBarItemBits nBits,
+ tools::Long nOffset, sal_uInt16 nPos )
+{
+ SAL_WARN_IF( !nItemId, "vcl", "StatusBar::InsertItem(): ItemId == 0" );
+ SAL_WARN_IF( GetItemPos( nItemId ) != STATUSBAR_ITEM_NOTFOUND, "vcl",
+ "StatusBar::InsertItem(): ItemId already exists" );
+
+ // default: IN and CENTER
+ if ( !(nBits & (StatusBarItemBits::In | StatusBarItemBits::Out | StatusBarItemBits::Flat)) )
+ nBits |= StatusBarItemBits::In;
+ if ( !(nBits & (StatusBarItemBits::Left | StatusBarItemBits::Right | StatusBarItemBits::Center)) )
+ nBits |= StatusBarItemBits::Center;
+
+ // create item
+ if (mbAdjustHiDPI)
+ {
+ nWidth *= GetDPIScaleFactor();
+ }
+ tools::Long nFudge = GetTextHeight()/4;
+ std::unique_ptr<ImplStatusItem> pItem(new ImplStatusItem);
+ pItem->mnId = nItemId;
+ pItem->mnBits = nBits;
+ pItem->mnWidth = static_cast<tools::Long>(nWidth)+nFudge+STATUSBAR_OFFSET;
+ pItem->mnOffset = nOffset;
+ pItem->mpUserData = nullptr;
+ pItem->mbVisible = true;
+
+ // add item to list
+ if ( nPos < mvItemList.size() ) {
+ mvItemList.insert( mvItemList.begin() + nPos, std::move(pItem) );
+ } else {
+ mvItemList.push_back( std::move(pItem) );
+ }
+
+ mbFormat = true;
+ if ( ImplIsItemUpdate() )
+ Invalidate();
+
+ CallEventListeners( VclEventId::StatusbarItemAdded, reinterpret_cast<void*>(nItemId) );
+}
+
+void StatusBar::RemoveItem( sal_uInt16 nItemId )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ {
+ mvItemList.erase( mvItemList.begin() + nPos );
+
+ mbFormat = true;
+ if ( ImplIsItemUpdate() )
+ Invalidate();
+
+ CallEventListeners( VclEventId::StatusbarItemRemoved, reinterpret_cast<void*>(nItemId) );
+ }
+}
+
+void StatusBar::ShowItem( sal_uInt16 nItemId )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos == STATUSBAR_ITEM_NOTFOUND )
+ return;
+
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+ if ( !pItem->mbVisible )
+ {
+ pItem->mbVisible = true;
+
+ mbFormat = true;
+ if ( ImplIsItemUpdate() )
+ Invalidate();
+
+ CallEventListeners( VclEventId::StatusbarShowItem, reinterpret_cast<void*>(nItemId) );
+ }
+}
+
+void StatusBar::HideItem( sal_uInt16 nItemId )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos == STATUSBAR_ITEM_NOTFOUND )
+ return;
+
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+ if ( pItem->mbVisible )
+ {
+ pItem->mbVisible = false;
+
+ mbFormat = true;
+ if ( ImplIsItemUpdate() )
+ Invalidate();
+
+ CallEventListeners( VclEventId::StatusbarHideItem, reinterpret_cast<void*>(nItemId) );
+ }
+}
+
+bool StatusBar::IsItemVisible( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ return mvItemList[ nPos ]->mbVisible;
+ else
+ return false;
+}
+
+void StatusBar::Clear()
+{
+ // delete all items
+ mvItemList.clear();
+
+ mbFormat = true;
+ if ( ImplIsItemUpdate() )
+ Invalidate();
+
+ CallEventListeners( VclEventId::StatusbarAllItemsRemoved );
+}
+
+sal_uInt16 StatusBar::GetItemCount() const
+{
+ return static_cast<sal_uInt16>(mvItemList.size());
+}
+
+sal_uInt16 StatusBar::GetItemId( sal_uInt16 nPos ) const
+{
+ if ( nPos < mvItemList.size() )
+ return mvItemList[ nPos ]->mnId;
+ return 0;
+}
+
+sal_uInt16 StatusBar::GetItemPos( sal_uInt16 nItemId ) const
+{
+ for ( size_t i = 0, n = mvItemList.size(); i < n; ++i ) {
+ if ( mvItemList[ i ]->mnId == nItemId ) {
+ return sal_uInt16( i );
+ }
+ }
+
+ return STATUSBAR_ITEM_NOTFOUND;
+}
+
+sal_uInt16 StatusBar::GetItemId( const Point& rPos ) const
+{
+ if ( !mbFormat )
+ {
+ sal_uInt16 nItemCount = GetItemCount();
+ sal_uInt16 nPos;
+ for ( nPos = 0; nPos < nItemCount; nPos++ )
+ {
+ // get rectangle
+ tools::Rectangle aRect = ImplGetItemRectPos( nPos );
+ if ( aRect.Contains( rPos ) )
+ return mvItemList[ nPos ]->mnId;
+ }
+ }
+
+ return 0;
+}
+
+tools::Rectangle StatusBar::GetItemRect( sal_uInt16 nItemId ) const
+{
+ tools::Rectangle aRect;
+
+ if ( !mbFormat )
+ {
+ sal_uInt16 nPos = GetItemPos( nItemId );
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ {
+ // get rectangle and subtract frame
+ aRect = ImplGetItemRectPos( nPos );
+ tools::Long nW = 1;
+ aRect.AdjustTop(nW-1 );
+ aRect.AdjustBottom( -(nW-1) );
+ aRect.AdjustLeft(nW );
+ aRect.AdjustRight( -nW );
+ return aRect;
+ }
+ }
+
+ return aRect;
+}
+
+Point StatusBar::GetItemTextPos( sal_uInt16 nItemId ) const
+{
+ if ( !mbFormat )
+ {
+ sal_uInt16 nPos = GetItemPos( nItemId );
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ {
+ // get rectangle
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+ tools::Rectangle aRect = ImplGetItemRectPos( nPos );
+ tools::Long nW = 1;
+ tools::Rectangle aTextRect( aRect.Left()+nW, aRect.Top()+nW,
+ aRect.Right()-nW, aRect.Bottom()-nW );
+ Point aPos = ImplGetItemTextPos( aTextRect.GetSize(),
+ Size( GetTextWidth( pItem->maText ), GetTextHeight() ),
+ pItem->mnBits );
+ if ( !mbInUserDraw )
+ {
+ aPos.AdjustX(aTextRect.Left() );
+ aPos.AdjustY(aTextRect.Top() );
+ }
+ return aPos;
+ }
+ }
+
+ return Point();
+}
+
+sal_uLong StatusBar::GetItemWidth( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ return mvItemList[ nPos ]->mnWidth;
+
+ return 0;
+}
+
+StatusBarItemBits StatusBar::GetItemBits( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ return mvItemList[ nPos ]->mnBits;
+
+ return StatusBarItemBits::NONE;
+}
+
+tools::Long StatusBar::GetItemOffset( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ return mvItemList[ nPos ]->mnOffset;
+
+ return 0;
+}
+
+void StatusBar::SetItemText( sal_uInt16 nItemId, const OUString& rText, int nCharsWidth )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos == STATUSBAR_ITEM_NOTFOUND )
+ return;
+
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+
+ if ( pItem->maText == rText )
+ return;
+
+ pItem->maText = rText;
+
+ // adjust item width - see also DataChanged()
+ tools::Long nFudge = GetTextHeight()/4;
+
+ tools::Long nWidth;
+ if (nCharsWidth != -1)
+ {
+ nWidth = GetTextWidth("0",0,-1,nullptr,
+ SalLayoutGlyphsCache::self()->GetLayoutGlyphs(GetOutDev(),"0"));
+ nWidth = nWidth * nCharsWidth + nFudge;
+ }
+ else
+ {
+ pItem->mLayoutGlyphsCache.reset();
+ nWidth = GetTextWidth( pItem->maText,0,-1,nullptr, pItem->GetTextGlyphs(GetOutDev())) + nFudge;
+ }
+
+ if( (nWidth > pItem->mnWidth + STATUSBAR_OFFSET) ||
+ ((nWidth < pItem->mnWidth) && (mnDX - STATUSBAR_OFFSET) < mnItemsWidth ))
+ {
+ pItem->mnWidth = nWidth + STATUSBAR_OFFSET;
+ ImplFormat();
+ Invalidate();
+ }
+
+ // re-draw item if StatusBar is visible and UpdateMode active
+ if ( pItem->mbVisible && !mbFormat && ImplIsItemUpdate() )
+ {
+ tools::Rectangle aRect = ImplGetItemRectPos(nPos);
+ Invalidate(aRect);
+ PaintImmediately();
+ }
+}
+
+const OUString& StatusBar::GetItemText( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ assert( nPos != STATUSBAR_ITEM_NOTFOUND );
+
+ return mvItemList[ nPos ]->maText;
+}
+
+void StatusBar::SetItemCommand( sal_uInt16 nItemId, const OUString& rCommand )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ {
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+
+ if ( pItem->maCommand != rCommand )
+ pItem->maCommand = rCommand;
+ }
+}
+
+OUString StatusBar::GetItemCommand( sal_uInt16 nItemId )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ return mvItemList[ nPos ]->maCommand;
+
+ return OUString();
+}
+
+void StatusBar::SetItemData( sal_uInt16 nItemId, void* pNewData )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos == STATUSBAR_ITEM_NOTFOUND )
+ return;
+
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+ // invalidate cache
+ pItem->mLayoutGlyphsCache.reset();
+ pItem->mpUserData = pNewData;
+
+ // call Draw-Item if it's a User-Item
+ if ( (pItem->mnBits & StatusBarItemBits::UserDraw) && pItem->mbVisible &&
+ !mbFormat && ImplIsItemUpdate() )
+ {
+ tools::Rectangle aRect = ImplGetItemRectPos(nPos);
+ Invalidate(aRect, InvalidateFlags::NoErase);
+ PaintImmediately();
+ }
+}
+
+void* StatusBar::GetItemData( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ return mvItemList[ nPos ]->mpUserData;
+
+ return nullptr;
+}
+
+void StatusBar::RedrawItem(sal_uInt16 nItemId)
+{
+ if ( mbFormat )
+ return;
+
+ sal_uInt16 nPos = GetItemPos(nItemId);
+ if ( nPos == STATUSBAR_ITEM_NOTFOUND )
+ return;
+
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+ if ((pItem->mnBits & StatusBarItemBits::UserDraw) &&
+ pItem->mbVisible && ImplIsItemUpdate())
+ {
+ tools::Rectangle aRect = ImplGetItemRectPos(nPos);
+ Invalidate(aRect);
+ PaintImmediately();
+ }
+}
+
+void StatusBar::SetHelpText( sal_uInt16 nItemId, const OUString& rText )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ mvItemList[ nPos ]->maHelpText = rText;
+}
+
+const OUString& StatusBar::GetHelpText( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ assert ( nPos != STATUSBAR_ITEM_NOTFOUND );
+
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+ if ( pItem->maHelpText.isEmpty() && ( !pItem->maHelpId.isEmpty() || !pItem->maCommand.isEmpty() ))
+ {
+ Help* pHelp = Application::GetHelp();
+ if ( pHelp )
+ {
+ if ( !pItem->maCommand.isEmpty() )
+ pItem->maHelpText = pHelp->GetHelpText( pItem->maCommand, this );
+ if ( pItem->maHelpText.isEmpty() && !pItem->maHelpId.isEmpty() )
+ pItem->maHelpText = pHelp->GetHelpText( OStringToOUString( pItem->maHelpId, RTL_TEXTENCODING_UTF8 ), this );
+ }
+ }
+
+ return pItem->maHelpText;
+}
+
+void StatusBar::SetQuickHelpText( sal_uInt16 nItemId, const OUString& rText )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ mvItemList[ nPos ]->maQuickHelpText = rText;
+}
+
+const OUString& StatusBar::GetQuickHelpText( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ assert ( nPos != STATUSBAR_ITEM_NOTFOUND );
+
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+ return pItem->maQuickHelpText;
+}
+
+void StatusBar::SetHelpId( sal_uInt16 nItemId, const OString& rHelpId )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ mvItemList[ nPos ]->maHelpId = rHelpId;
+}
+
+void StatusBar::StartProgressMode( const OUString& rText )
+{
+ SAL_WARN_IF( mbProgressMode, "vcl", "StatusBar::StartProgressMode(): progress mode is active" );
+
+ mbProgressMode = true;
+ mnPercent = 0;
+ maPrgsTxt = rText;
+
+ // compute size
+ ImplCalcProgressRect();
+
+ // trigger Paint, which draws text and frame
+ if ( IsReallyVisible() )
+ {
+ Invalidate();
+ PaintImmediately();
+ }
+}
+
+void StatusBar::SetProgressValue( sal_uInt16 nNewPercent )
+{
+ SAL_WARN_IF( !mbProgressMode, "vcl", "StatusBar::SetProgressValue(): no progress mode" );
+ SAL_WARN_IF( nNewPercent > 100, "vcl", "StatusBar::SetProgressValue(): nPercent > 100" );
+
+ bool bInvalidate = mbProgressMode && IsReallyVisible() && (!mnPercent || (mnPercent != nNewPercent));
+
+ mnPercent = nNewPercent;
+
+ if (bInvalidate)
+ {
+ // Rate limit how often we paint, otherwise in some loading scenarios we can spend significant
+ // time just painting progress bars.
+ sal_uInt32 nTime_ms = osl_getGlobalTimer();
+ if ((nTime_ms - mnLastProgressPaint_ms) > 100)
+ {
+ Invalidate(maPrgsFrameRect);
+ PaintImmediately();
+ mnLastProgressPaint_ms = nTime_ms;
+ }
+ }
+}
+
+void StatusBar::EndProgressMode()
+{
+ SAL_WARN_IF( !mbProgressMode, "vcl", "StatusBar::EndProgressMode(): no progress mode" );
+
+ mbProgressMode = false;
+ maPrgsTxt.clear();
+
+ if ( IsReallyVisible() )
+ {
+ Invalidate();
+ PaintImmediately();
+ }
+}
+
+void StatusBar::SetText(const OUString& rText)
+{
+ if ((GetStyle() & WB_RIGHT) && !mbProgressMode && IsReallyVisible() && IsUpdateMode())
+ {
+ if (mbFormat)
+ {
+ Invalidate();
+ Window::SetText(rText);
+ }
+ else
+ {
+ Invalidate();
+ Window::SetText(rText);
+ PaintImmediately();
+ }
+ }
+ else if (mbProgressMode)
+ {
+ maPrgsTxt = rText;
+ if (IsReallyVisible())
+ {
+ Invalidate();
+ PaintImmediately();
+ }
+ }
+ else
+ {
+ Window::SetText(rText);
+ }
+}
+
+Size StatusBar::CalcWindowSizePixel() const
+{
+ size_t i = 0;
+ size_t nCount = mvItemList.size();
+ tools::Long nOffset = 0;
+ tools::Long nCalcWidth = STATUSBAR_OFFSET_X*2;
+ tools::Long nCalcHeight;
+
+ while ( i < nCount )
+ {
+ ImplStatusItem* pItem = mvItemList[ i ].get();
+ nCalcWidth += pItem->mnWidth + nOffset;
+ nOffset = pItem->mnOffset;
+ i++;
+ }
+
+ tools::Long nMinHeight = GetTextHeight();
+ const tools::Long nBarTextOffset = STATUSBAR_OFFSET_TEXTY*2;
+ tools::Long nProgressHeight = nMinHeight + nBarTextOffset;
+
+ if( IsNativeControlSupported( ControlType::Progress, ControlPart::Entire ) )
+ {
+ ImplControlValue aValue;
+ tools::Rectangle aControlRegion( Point(), Size( nCalcWidth, nMinHeight ) );
+ tools::Rectangle aNativeControlRegion, aNativeContentRegion;
+ if( GetNativeControlRegion( ControlType::Progress, ControlPart::Entire,
+ aControlRegion, ControlState::ENABLED, aValue,
+ aNativeControlRegion, aNativeContentRegion ) )
+ {
+ nProgressHeight = aNativeControlRegion.GetHeight();
+ }
+ }
+
+ nCalcHeight = nMinHeight+nBarTextOffset;
+ if( nCalcHeight < nProgressHeight+2 )
+ nCalcHeight = nProgressHeight+2;
+
+ return Size( nCalcWidth, nCalcHeight );
+}
+
+void StatusBar::SetAccessibleName( sal_uInt16 nItemId, const OUString& rName )
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ if ( nPos != STATUSBAR_ITEM_NOTFOUND )
+ {
+ ImplStatusItem* pItem = mvItemList[ nPos ].get();
+
+ if ( pItem->maAccessibleName != rName )
+ {
+ pItem->maAccessibleName = rName;
+ CallEventListeners( VclEventId::StatusbarNameChanged, reinterpret_cast<void*>(pItem->mnId) );
+ }
+ }
+}
+
+const OUString& StatusBar::GetAccessibleName( sal_uInt16 nItemId ) const
+{
+ sal_uInt16 nPos = GetItemPos( nItemId );
+
+ assert ( nPos != STATUSBAR_ITEM_NOTFOUND );
+
+ return mvItemList[ nPos ]->maAccessibleName;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/syschild.cxx b/vcl/source/window/syschild.cxx
new file mode 100644
index 000000000..644aa1f09
--- /dev/null
+++ b/vcl/source/window/syschild.cxx
@@ -0,0 +1,200 @@
+/* -*- 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 <vcl/window.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/syschild.hxx>
+
+#include <window.h>
+#include <salframe.hxx>
+#include <salinst.hxx>
+#include <salobj.hxx>
+#include <svdata.hxx>
+
+using namespace ::com::sun::star;
+
+static void ImplSysChildProc( SystemChildWindow* pInst, SalObjEvent nEvent )
+{
+ VclPtr<SystemChildWindow> pWindow = pInst;
+
+ switch ( nEvent )
+ {
+ case SalObjEvent::GetFocus:
+ // get focus, such that all handlers are called,
+ // as if this window gets the focus assuring
+ // that the frame does not steal it
+ pWindow->ImplGetFrameData()->mbSysObjFocus = true;
+ pWindow->ImplGetFrameData()->mbInSysObjToTopHdl = true;
+ pWindow->ToTop( ToTopFlags::NoGrabFocus );
+ if( pWindow->isDisposed() )
+ break;
+ pWindow->ImplGetFrameData()->mbInSysObjToTopHdl = false;
+ pWindow->ImplGetFrameData()->mbInSysObjFocusHdl = true;
+ pWindow->GrabFocus();
+ if( pWindow->isDisposed() )
+ break;
+ pWindow->ImplGetFrameData()->mbInSysObjFocusHdl = false;
+ break;
+
+ case SalObjEvent::LoseFocus:
+ // trigger a LoseFocus which matches the status
+ // of the window with matching Activate-Status
+ if (pWindow->isDisposed())
+ break;
+ pWindow->ImplGetFrameData()->mbSysObjFocus = false;
+ if ( !pWindow->ImplGetFrameData()->mnFocusId )
+ {
+ pWindow->ImplGetFrameData()->mbStartFocusState = true;
+ pWindow->ImplGetFrameData()->mnFocusId = Application::PostUserEvent( LINK( pWindow->ImplGetFrameWindow(), vcl::Window, ImplAsyncFocusHdl ), nullptr, true );
+ }
+ break;
+
+ case SalObjEvent::ToTop:
+ pWindow->ImplGetFrameData()->mbInSysObjToTopHdl = true;
+ if ( !Application::GetFocusWindow() || pWindow->HasChildPathFocus() )
+ pWindow->ToTop( ToTopFlags::NoGrabFocus );
+ else
+ pWindow->ToTop();
+ if( pWindow->isDisposed() )
+ break;
+ pWindow->GrabFocus();
+ if( pWindow->isDisposed() )
+ break;
+ pWindow->ImplGetFrameData()->mbInSysObjToTopHdl = false;
+ break;
+
+ default: break;
+ }
+}
+
+void SystemChildWindow::ImplInitSysChild( vcl::Window* pParent, WinBits nStyle, SystemWindowData *pData, bool bShow )
+{
+ mpWindowImpl->mpSysObj = ImplGetSVData()->mpDefInst->CreateObject( pParent->ImplGetFrame(), pData, bShow );
+
+ Window::ImplInit( pParent, nStyle, nullptr );
+
+ // we do not paint if it is the right SysChild
+ if ( GetSystemData() )
+ {
+ mpWindowImpl->mpSysObj->SetCallback( this, ImplSysChildProc );
+ SetParentClipMode( ParentClipMode::Clip );
+ SetBackground();
+ }
+}
+
+SystemChildWindow::SystemChildWindow( vcl::Window* pParent, WinBits nStyle ) :
+ Window( WindowType::SYSTEMCHILDWINDOW )
+{
+ ImplInitSysChild( pParent, nStyle, nullptr );
+}
+
+SystemChildWindow::SystemChildWindow( vcl::Window* pParent, WinBits nStyle, SystemWindowData *pData, bool bShow ) :
+ Window( WindowType::SYSTEMCHILDWINDOW )
+{
+ ImplInitSysChild( pParent, nStyle, pData, bShow );
+}
+
+SystemChildWindow::~SystemChildWindow()
+{
+ disposeOnce();
+}
+
+void SystemChildWindow::dispose()
+{
+ Hide();
+ if ( mpWindowImpl && mpWindowImpl->mpSysObj )
+ {
+ ImplGetSVData()->mpDefInst->DestroyObject( mpWindowImpl->mpSysObj );
+ mpWindowImpl->mpSysObj = nullptr;
+ }
+ Window::dispose();
+}
+
+const SystemEnvData* SystemChildWindow::GetSystemData() const
+{
+ if ( mpWindowImpl->mpSysObj )
+ return mpWindowImpl->mpSysObj->GetSystemData();
+ else
+ return nullptr;
+}
+
+void SystemChildWindow::EnableEraseBackground( bool bEnable )
+{
+ if ( mpWindowImpl->mpSysObj )
+ mpWindowImpl->mpSysObj->EnableEraseBackground( bEnable );
+}
+
+Size SystemChildWindow::GetOptimalSize() const
+{
+ if (mpWindowImpl->mpSysObj)
+ return mpWindowImpl->mpSysObj->GetOptimalSize();
+ return vcl::Window::GetOptimalSize();
+}
+
+void SystemChildWindow::SetLeaveEnterBackgrounds(const css::uno::Sequence<css::uno::Any>& rLeaveArgs, const css::uno::Sequence<css::uno::Any>& rEnterArgs)
+{
+ if (mpWindowImpl->mpSysObj)
+ mpWindowImpl->mpSysObj->SetLeaveEnterBackgrounds(rLeaveArgs, rEnterArgs);
+}
+
+void SystemChildWindow::SetForwardKey( bool bEnable )
+{
+ if ( mpWindowImpl->mpSysObj )
+ mpWindowImpl->mpSysObj->SetForwardKey( bEnable );
+}
+
+sal_IntPtr SystemChildWindow::GetParentWindowHandle() const
+{
+ sal_IntPtr nRet = 0;
+
+#if defined(_WIN32)
+ nRet = reinterpret_cast< sal_IntPtr >( GetSystemData()->hWnd );
+#elif defined MACOSX
+ // FIXME: this is wrong
+ nRet = reinterpret_cast< sal_IntPtr >( GetSystemData()->mpNSView );
+#elif defined ANDROID
+ // Nothing
+#elif defined IOS
+ // Nothing
+#elif defined UNX
+ nRet = GetSystemData()->GetWindowHandle(ImplGetFrame());
+#endif
+
+ return nRet;
+}
+
+void* SystemChildWindow::CreateGStreamerSink()
+{
+ return ImplGetSVData()->mpDefInst->CreateGStreamerSink(this);
+}
+
+#if defined( MACOSX )
+#elif defined( ANDROID )
+#elif defined( IOS )
+#elif defined( UNX )
+sal_uIntPtr SystemEnvData::GetWindowHandle(const SalFrame* pReference) const
+{
+ if (!aWindow && pReference)
+ pReference->ResolveWindowHandle(const_cast<SystemEnvData&>(*this));
+ return aWindow;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/syswin.cxx b/vcl/source/window/syswin.cxx
new file mode 100644
index 000000000..40fff00aa
--- /dev/null
+++ b/vcl/source/window/syswin.cxx
@@ -0,0 +1,1183 @@
+/* -*- 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 <memory>
+
+#include <o3tl/safeint.hxx>
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <vcl/layout.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/event.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/taskpanelist.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/tabpage.hxx>
+#include <vcl/virdev.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <accel.hxx>
+#include <salframe.hxx>
+#include <svdata.hxx>
+#include <brdwin.hxx>
+#include <window.h>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+
+class SystemWindow::ImplData
+{
+public:
+ ImplData();
+
+ std::unique_ptr<TaskPaneList>
+ mpTaskPaneList;
+ Size maMaxOutSize;
+ OUString maRepresentedURL;
+ Link<SystemWindow&,void> maCloseHdl;
+};
+
+SystemWindow::ImplData::ImplData()
+{
+ mpTaskPaneList = nullptr;
+ maMaxOutSize = Size( SHRT_MAX, SHRT_MAX );
+}
+
+SystemWindow::SystemWindow(WindowType nType, const char* pIdleDebugName)
+ : Window(nType)
+ , mbDockBtn(false)
+ , mbHideBtn(false)
+ , mbSysChild(false)
+ , mbIsCalculatingInitialLayoutSize(false)
+ , mbInitialLayoutSizeCalculated(false)
+ , mbPaintComplete(false)
+ , mnMenuBarMode(MenuBarMode::Normal)
+ , mnIcon(0)
+ , mpImplData(new ImplData)
+ , maLayoutIdle( pIdleDebugName )
+ , mbIsDeferredInit(false)
+{
+ mpWindowImpl->mbSysWin = true;
+ mpWindowImpl->mnActivateMode = ActivateModeFlags::GrabFocus;
+
+ //To-Do, reuse maResizeTimer
+ maLayoutIdle.SetPriority(TaskPriority::RESIZE);
+ maLayoutIdle.SetInvokeHandler( LINK( this, SystemWindow, ImplHandleLayoutTimerHdl ) );
+}
+
+void SystemWindow::loadUI(vcl::Window* pParent, const OString& rID, const OUString& rUIXMLDescription,
+ const css::uno::Reference<css::frame::XFrame> &rFrame)
+{
+ mbIsDeferredInit = true;
+ mpDialogParent = pParent; //should be unset in doDeferredInit
+ m_pUIBuilder.reset( new VclBuilder(this, AllSettings::GetUIRootDir(), rUIXMLDescription, rID, rFrame) );
+}
+
+SystemWindow::~SystemWindow()
+{
+ disposeOnce();
+}
+
+void SystemWindow::dispose()
+{
+ maLayoutIdle.Stop();
+ mpImplData.reset();
+
+ // Hack to make sure code called from base ~Window does not interpret this
+ // as a SystemWindow (which it no longer is by then):
+ mpWindowImpl->mbSysWin = false;
+ disposeBuilder();
+ mpDialogParent.clear();
+ mpMenuBar.clear();
+ Window::dispose();
+}
+
+static void ImplHandleControlAccelerator( const vcl::Window* pWindow, bool bShow )
+{
+ Control *pControl = dynamic_cast<Control*>(pWindow->ImplGetWindow());
+ if (pControl && pControl->GetText().indexOf('~') != -1)
+ {
+ pControl->SetShowAccelerator( bShow );
+ pControl->Invalidate(InvalidateFlags::Update);
+ }
+}
+
+namespace
+{
+ void processChildren(const vcl::Window *pParent, bool bShowAccel)
+ {
+ // go through its children
+ vcl::Window* pChild = firstLogicalChildOfParent(pParent);
+ while (pChild)
+ {
+ if (pChild->GetType() == WindowType::TABCONTROL)
+ {
+ // find currently shown tab page
+ TabControl* pTabControl = static_cast<TabControl*>(pChild);
+ TabPage* pTabPage = pTabControl->GetTabPage( pTabControl->GetCurPageId() );
+ processChildren(pTabPage, bShowAccel);
+ }
+ else if (pChild->GetType() == WindowType::TABPAGE)
+ {
+ // bare tabpage without tabcontrol parent (options dialog)
+ processChildren(pChild, bShowAccel);
+ }
+ else if ((pChild->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL)
+ {
+ // special controls that manage their children outside of widget layout
+ processChildren(pChild, bShowAccel);
+ }
+ else
+ {
+ ImplHandleControlAccelerator(pChild, bShowAccel);
+ }
+ pChild = nextLogicalChildOfParent(pParent, pChild);
+ }
+ }
+}
+
+namespace
+{
+ bool ToggleMnemonicsOnHierarchy(const CommandEvent& rCEvent, const vcl::Window *pWindow)
+ {
+ if (rCEvent.GetCommand() == CommandEventId::ModKeyChange && ImplGetSVData()->maNWFData.mbAutoAccel)
+ {
+ const CommandModKeyData *pCData = rCEvent.GetModKeyData();
+ const bool bShowAccel = pCData && pCData->IsMod2() && pCData->IsDown();
+ processChildren(pWindow, bShowAccel);
+ return true;
+ }
+ return false;
+ }
+}
+
+bool SystemWindow::EventNotify( NotifyEvent& rNEvt )
+{
+ if (rNEvt.GetType() == MouseNotifyEvent::COMMAND)
+ ToggleMnemonicsOnHierarchy(*rNEvt.GetCommandEvent(), this);
+
+ // capture KeyEvents for menu handling
+ if (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT)
+ {
+ MenuBar* pMBar = mpMenuBar;
+ if ( !pMBar && ( GetType() == WindowType::FLOATINGWINDOW ) )
+ {
+ vcl::Window* pWin = ImplGetFrameWindow()->ImplGetWindow();
+ if( pWin && pWin->IsSystemWindow() )
+ pMBar = static_cast<SystemWindow*>(pWin)->GetMenuBar();
+ }
+ if (pMBar && pMBar->ImplHandleKeyEvent(*rNEvt.GetKeyEvent()))
+ return true;
+ }
+
+ return Window::EventNotify( rNEvt );
+}
+
+bool SystemWindow::PreNotify( NotifyEvent& rNEvt )
+{
+ // capture KeyEvents for taskpane cycling
+ if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ if( rNEvt.GetKeyEvent()->GetKeyCode().GetCode() == KEY_F6 &&
+ rNEvt.GetKeyEvent()->GetKeyCode().IsMod1() &&
+ !rNEvt.GetKeyEvent()->GetKeyCode().IsShift() )
+ {
+ // Ctrl-F6 goes directly to the document
+ GrabFocusToDocument();
+ return true;
+ }
+ else
+ {
+ TaskPaneList *pTList = mpImplData->mpTaskPaneList.get();
+ if( !pTList && ( GetType() == WindowType::FLOATINGWINDOW ) )
+ {
+ vcl::Window* pWin = ImplGetFrameWindow()->ImplGetWindow();
+ if( pWin && pWin->IsSystemWindow() )
+ pTList = static_cast<SystemWindow*>(pWin)->mpImplData->mpTaskPaneList.get();
+ }
+ if( !pTList )
+ {
+ // search topmost system window which is the one to handle dialog/toolbar cycling
+ SystemWindow *pSysWin = this;
+ vcl::Window *pWin = this;
+ while( pWin )
+ {
+ pWin = pWin->GetParent();
+ if( pWin && pWin->IsSystemWindow() )
+ pSysWin = static_cast<SystemWindow*>(pWin);
+ }
+ pTList = pSysWin->mpImplData->mpTaskPaneList.get();
+ }
+ if( pTList && pTList->HandleKeyEvent( *rNEvt.GetKeyEvent() ) )
+ return true;
+ }
+ }
+ return Window::PreNotify( rNEvt );
+}
+
+TaskPaneList* SystemWindow::GetTaskPaneList()
+{
+ if( !mpImplData )
+ return nullptr;
+ if( mpImplData->mpTaskPaneList )
+ return mpImplData->mpTaskPaneList.get();
+ else
+ {
+ mpImplData->mpTaskPaneList.reset( new TaskPaneList );
+ MenuBar* pMBar = mpMenuBar;
+ if ( !pMBar && ( GetType() == WindowType::FLOATINGWINDOW ) )
+ {
+ vcl::Window* pWin = ImplGetFrameWindow()->ImplGetWindow();
+ if ( pWin && pWin->IsSystemWindow() )
+ pMBar = static_cast<SystemWindow*>(pWin)->GetMenuBar();
+ }
+ if( pMBar )
+ mpImplData->mpTaskPaneList->AddWindow( pMBar->ImplGetWindow() );
+ return mpImplData->mpTaskPaneList.get();
+ }
+}
+
+bool SystemWindow::Close()
+{
+ VclPtr<vcl::Window> xWindow = this;
+ CallEventListeners( VclEventId::WindowClose );
+ if ( xWindow->isDisposed() )
+ return false;
+
+ if ( mpWindowImpl->mxWindowPeer.is() && IsCreatedWithToolkit() )
+ return false;
+
+ // Is Window not closeable, ignore close
+ vcl::Window* pBorderWin = ImplGetBorderWindow();
+ WinBits nStyle;
+ if ( pBorderWin )
+ nStyle = pBorderWin->GetStyle();
+ else
+ nStyle = GetStyle();
+ if ( !(nStyle & WB_CLOSEABLE) )
+ return false;
+
+ Hide();
+
+ return true;
+}
+
+void SystemWindow::TitleButtonClick( TitleButton )
+{
+}
+
+void SystemWindow::Resizing( Size& )
+{
+}
+
+void SystemWindow::SetRepresentedURL( const OUString& i_rURL )
+{
+ bool bChanged = (i_rURL != mpImplData->maRepresentedURL);
+ mpImplData->maRepresentedURL = i_rURL;
+ if ( !mbSysChild && bChanged )
+ {
+ const vcl::Window* pWindow = this;
+ while ( pWindow->mpWindowImpl->mpBorderWindow )
+ pWindow = pWindow->mpWindowImpl->mpBorderWindow;
+
+ if ( pWindow->mpWindowImpl->mbFrame )
+ pWindow->mpWindowImpl->mpFrame->SetRepresentedURL( i_rURL );
+ }
+}
+
+void SystemWindow::SetIcon( sal_uInt16 nIcon )
+{
+ if ( mnIcon == nIcon )
+ return;
+
+ mnIcon = nIcon;
+
+ if ( !mbSysChild )
+ {
+ const vcl::Window* pWindow = this;
+ while ( pWindow->mpWindowImpl->mpBorderWindow )
+ pWindow = pWindow->mpWindowImpl->mpBorderWindow;
+
+ if ( pWindow->mpWindowImpl->mbFrame )
+ pWindow->mpWindowImpl->mpFrame->SetIcon( nIcon );
+ }
+}
+
+void SystemWindow::ShowTitleButton( TitleButton nButton, bool bVisible )
+{
+ if ( nButton == TitleButton::Docking )
+ {
+ if ( mbDockBtn != bVisible )
+ {
+ mbDockBtn = bVisible;
+ if ( mpWindowImpl->mpBorderWindow )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetDockButton( bVisible );
+ }
+ }
+ else if ( nButton == TitleButton::Hide )
+ {
+ if ( mbHideBtn != bVisible )
+ {
+ mbHideBtn = bVisible;
+ if ( mpWindowImpl->mpBorderWindow )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetHideButton( bVisible );
+ }
+ }
+ else if ( nButton == TitleButton::Menu )
+ {
+ if ( mpWindowImpl->mpBorderWindow )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMenuButton( bVisible );
+ }
+ else
+ return;
+}
+
+bool SystemWindow::IsTitleButtonVisible( TitleButton nButton ) const
+{
+ if ( nButton == TitleButton::Docking )
+ return mbDockBtn;
+ else /* if ( nButton == TitleButton::Hide ) */
+ return mbHideBtn;
+}
+
+void SystemWindow::SetMinOutputSizePixel( const Size& rSize )
+{
+ maMinOutSize = rSize;
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMinOutputSize( rSize.Width(), rSize.Height() );
+ if ( mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame )
+ mpWindowImpl->mpBorderWindow->mpWindowImpl->mpFrame->SetMinClientSize( rSize.Width(), rSize.Height() );
+ }
+ else if ( mpWindowImpl->mbFrame )
+ mpWindowImpl->mpFrame->SetMinClientSize( rSize.Width(), rSize.Height() );
+}
+
+void SystemWindow::SetMaxOutputSizePixel( const Size& rSize )
+{
+ Size aSize( rSize );
+ if( aSize.Width() > SHRT_MAX || aSize.Width() <= 0 )
+ aSize.setWidth( SHRT_MAX );
+ if( aSize.Height() > SHRT_MAX || aSize.Height() <= 0 )
+ aSize.setHeight( SHRT_MAX );
+
+ mpImplData->maMaxOutSize = aSize;
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMaxOutputSize( aSize.Width(), aSize.Height() );
+ if ( mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame )
+ mpWindowImpl->mpBorderWindow->mpWindowImpl->mpFrame->SetMaxClientSize( aSize.Width(), aSize.Height() );
+ }
+ else if ( mpWindowImpl->mbFrame )
+ mpWindowImpl->mpFrame->SetMaxClientSize( aSize.Width(), aSize.Height() );
+}
+
+const Size& SystemWindow::GetMaxOutputSizePixel() const
+{
+ return mpImplData->maMaxOutSize;
+}
+
+void ImplWindowStateFromStr(WindowStateData& rData, std::string_view rStr)
+{
+ WindowStateMask nValidMask = WindowStateMask::NONE;
+ sal_Int32 nIndex = 0;
+
+ std::string_view aTokenStr = o3tl::getToken(rStr, 0, ',', nIndex);
+ if (!aTokenStr.empty())
+ {
+ rData.SetX(o3tl::toInt32(aTokenStr));
+ if( rData.GetX() > -16384 && rData.GetX() < 16384 )
+ nValidMask |= WindowStateMask::X;
+ else
+ rData.SetX( 0 );
+ }
+ else
+ rData.SetX( 0 );
+ aTokenStr = o3tl::getToken(rStr, 0, ',', nIndex);
+ if (!aTokenStr.empty())
+ {
+ rData.SetY(o3tl::toInt32(aTokenStr));
+ if( rData.GetY() > -16384 && rData.GetY() < 16384 )
+ nValidMask |= WindowStateMask::Y;
+ else
+ rData.SetY( 0 );
+ }
+ else
+ rData.SetY( 0 );
+ aTokenStr = o3tl::getToken(rStr, 0, ',', nIndex);
+ if (!aTokenStr.empty())
+ {
+ rData.SetWidth(o3tl::toInt32(aTokenStr));
+ if( rData.GetWidth() > 0 && rData.GetWidth() < 16384 )
+ nValidMask |= WindowStateMask::Width;
+ else
+ rData.SetWidth( 0 );
+ }
+ else
+ rData.SetWidth( 0 );
+ aTokenStr = o3tl::getToken(rStr, 0, ';', nIndex);
+ if (!aTokenStr.empty())
+ {
+ rData.SetHeight(o3tl::toInt32(aTokenStr));
+ if( rData.GetHeight() > 0 && rData.GetHeight() < 16384 )
+ nValidMask |= WindowStateMask::Height;
+ else
+ rData.SetHeight( 0 );
+ }
+ else
+ rData.SetHeight( 0 );
+ aTokenStr = o3tl::getToken(rStr, 0, ';', nIndex);
+ if (!aTokenStr.empty())
+ {
+ // #94144# allow Minimize again, should be masked out when read from configuration
+ // 91625 - ignore Minimize
+ WindowStateState nState = static_cast<WindowStateState>(o3tl::toInt32(aTokenStr));
+ //nState &= ~(WindowStateState::Minimized);
+ rData.SetState( nState );
+ nValidMask |= WindowStateMask::State;
+ }
+ else
+ rData.SetState( WindowStateState::NONE );
+
+ // read maximized pos/size
+ aTokenStr = o3tl::getToken(rStr, 0, ',', nIndex);
+ if (!aTokenStr.empty())
+ {
+ rData.SetMaximizedX(o3tl::toInt32(aTokenStr));
+ if( rData.GetMaximizedX() > -16384 && rData.GetMaximizedX() < 16384 )
+ nValidMask |= WindowStateMask::MaximizedX;
+ else
+ rData.SetMaximizedX( 0 );
+ }
+ else
+ rData.SetMaximizedX( 0 );
+ aTokenStr = o3tl::getToken(rStr, 0, ',', nIndex);
+ if (!aTokenStr.empty())
+ {
+ rData.SetMaximizedY(o3tl::toInt32(aTokenStr));
+ if( rData.GetMaximizedY() > -16384 && rData.GetMaximizedY() < 16384 )
+ nValidMask |= WindowStateMask::MaximizedY;
+ else
+ rData.SetMaximizedY( 0 );
+ }
+ else
+ rData.SetMaximizedY( 0 );
+ aTokenStr = o3tl::getToken(rStr, 0, ',', nIndex);
+ if (!aTokenStr.empty())
+ {
+ rData.SetMaximizedWidth(o3tl::toInt32(aTokenStr));
+ if( rData.GetMaximizedWidth() > 0 && rData.GetMaximizedWidth() < 16384 )
+ nValidMask |= WindowStateMask::MaximizedWidth;
+ else
+ rData.SetMaximizedWidth( 0 );
+ }
+ else
+ rData.SetMaximizedWidth( 0 );
+ aTokenStr = o3tl::getToken(rStr, 0, ';', nIndex);
+ if (!aTokenStr.empty())
+ {
+ rData.SetMaximizedHeight(o3tl::toInt32(aTokenStr));
+ if( rData.GetMaximizedHeight() > 0 && rData.GetMaximizedHeight() < 16384 )
+ nValidMask |= WindowStateMask::MaximizedHeight;
+ else
+ rData.SetMaximizedHeight( 0 );
+ }
+ else
+ rData.SetMaximizedHeight( 0 );
+
+ // mark valid fields
+ rData.SetMask( nValidMask );
+}
+
+OString WindowStateData::ToStr() const
+{
+ const WindowStateMask nValidMask = GetMask();
+ if ( nValidMask == WindowStateMask::NONE )
+ return OString();
+
+ OStringBuffer rStrBuf(64);
+
+ if ( nValidMask & WindowStateMask::X )
+ rStrBuf.append(static_cast<sal_Int32>(GetX()));
+ rStrBuf.append(',');
+ if ( nValidMask & WindowStateMask::Y )
+ rStrBuf.append(static_cast<sal_Int32>(GetY()));
+ rStrBuf.append(',');
+ if ( nValidMask & WindowStateMask::Width )
+ rStrBuf.append(static_cast<sal_Int32>(GetWidth()));
+ rStrBuf.append(',');
+ if ( nValidMask & WindowStateMask::Height )
+ rStrBuf.append(static_cast<sal_Int32>(GetHeight()));
+ rStrBuf.append( ';' );
+ if ( nValidMask & WindowStateMask::State )
+ {
+ // #94144# allow Minimize again, should be masked out when read from configuration
+ // 91625 - ignore Minimize
+ WindowStateState nState = GetState();
+ rStrBuf.append(static_cast<sal_Int32>(nState));
+ }
+ rStrBuf.append(';');
+ if ( nValidMask & WindowStateMask::MaximizedX )
+ rStrBuf.append(static_cast<sal_Int32>(GetMaximizedX()));
+ rStrBuf.append(',');
+ if ( nValidMask & WindowStateMask::MaximizedY )
+ rStrBuf.append(static_cast<sal_Int32>(GetMaximizedY()));
+ rStrBuf.append( ',' );
+ if ( nValidMask & WindowStateMask::MaximizedWidth )
+ rStrBuf.append(static_cast<sal_Int32>(GetMaximizedWidth()));
+ rStrBuf.append(',');
+ if ( nValidMask & WindowStateMask::MaximizedHeight )
+ rStrBuf.append(static_cast<sal_Int32>(GetMaximizedHeight()));
+ rStrBuf.append(';');
+
+ return rStrBuf.makeStringAndClear();
+}
+
+void SystemWindow::ImplMoveToScreen( tools::Long& io_rX, tools::Long& io_rY, tools::Long i_nWidth, tools::Long i_nHeight, vcl::Window const * i_pConfigureWin )
+{
+ tools::Rectangle aScreenRect;
+ if( !Application::IsUnifiedDisplay() )
+ aScreenRect = Application::GetScreenPosSizePixel( GetScreenNumber() );
+ else
+ {
+ aScreenRect = Application::GetScreenPosSizePixel( 0 );
+ for( unsigned int i = 1; i < Application::GetScreenCount(); i++ )
+ aScreenRect.Union( Application::GetScreenPosSizePixel( i ) );
+ }
+ // unfortunately most of the time width and height are not really known
+ if( i_nWidth < 1 )
+ i_nWidth = 50;
+ if( i_nHeight < 1 )
+ i_nHeight = 50;
+
+ // check left border
+ bool bMove = false;
+ if( io_rX + i_nWidth < aScreenRect.Left() )
+ {
+ bMove = true;
+ io_rX = aScreenRect.Left();
+ }
+ // check right border
+ if( io_rX > aScreenRect.Right() - i_nWidth )
+ {
+ bMove = true;
+ io_rX = aScreenRect.Right() - i_nWidth;
+ }
+ // check top border
+ if( io_rY + i_nHeight < aScreenRect.Top() )
+ {
+ bMove = true;
+ io_rY = aScreenRect.Top();
+ }
+ // check bottom border
+ if( io_rY > aScreenRect.Bottom() - i_nHeight )
+ {
+ bMove = true;
+ io_rY = aScreenRect.Bottom() - i_nHeight;
+ }
+ vcl::Window* pParent = i_pConfigureWin->GetParent();
+ if( bMove && pParent )
+ {
+ // calculate absolute screen pos here, since that is what is contained in WindowState
+ Point aParentAbsPos( pParent->OutputToAbsoluteScreenPixel( Point(0,0) ) );
+ Size aParentSizePixel( pParent->GetOutputSizePixel() );
+ Point aPos( (aParentSizePixel.Width() - i_nWidth) / 2,
+ (aParentSizePixel.Height() - i_nHeight) / 2 );
+ io_rX = aParentAbsPos.X() + aPos.X();
+ io_rY = aParentAbsPos.Y() + aPos.Y();
+ }
+}
+
+void SystemWindow::SetWindowStateData( const WindowStateData& rData )
+{
+ const WindowStateMask nValidMask = rData.GetMask();
+ if ( nValidMask == WindowStateMask::NONE )
+ return;
+
+ if ( mbSysChild )
+ return;
+
+ vcl::Window* pWindow = this;
+ while ( pWindow->mpWindowImpl->mpBorderWindow )
+ pWindow = pWindow->mpWindowImpl->mpBorderWindow;
+
+ if ( pWindow->mpWindowImpl->mbFrame )
+ {
+ const WindowStateState nState = rData.GetState();
+ SalFrameState aState;
+ aState.mnMask = rData.GetMask();
+ aState.mnX = rData.GetX();
+ aState.mnY = rData.GetY();
+ aState.mnWidth = rData.GetWidth();
+ aState.mnHeight = rData.GetHeight();
+
+ if( rData.GetMask() & (WindowStateMask::Width|WindowStateMask::Height) )
+ {
+ // #i43799# adjust window state sizes if a minimal output size was set
+ // otherwise the frame and the client might get different sizes
+ if( maMinOutSize.Width() > aState.mnWidth )
+ aState.mnWidth = maMinOutSize.Width();
+ if( maMinOutSize.Height() > aState.mnHeight )
+ aState.mnHeight = maMinOutSize.Height();
+ }
+
+ aState.mnMaximizedX = rData.GetMaximizedX();
+ aState.mnMaximizedY = rData.GetMaximizedY();
+ aState.mnMaximizedWidth = rData.GetMaximizedWidth();
+ aState.mnMaximizedHeight = rData.GetMaximizedHeight();
+ // #94144# allow Minimize again, should be masked out when read from configuration
+ // 91625 - ignore Minimize
+ //nState &= ~(WindowStateState::Minimized);
+ aState.mnState = nState & WindowStateState::SystemMask;
+
+ // normalize window positions onto screen
+ ImplMoveToScreen( aState.mnX, aState.mnY, aState.mnWidth, aState.mnHeight, pWindow );
+ ImplMoveToScreen( aState.mnMaximizedX, aState.mnMaximizedY, aState.mnMaximizedWidth, aState.mnMaximizedHeight, pWindow );
+
+ // #96568# avoid having multiple frames at the same screen location
+ // do the check only if not maximized
+ if( !((rData.GetMask() & WindowStateMask::State) && (nState & WindowStateState::Maximized)) )
+ if( rData.GetMask() & (WindowStateMask::Pos|WindowStateMask::Width|WindowStateMask::Height) )
+ {
+ tools::Rectangle aDesktop = GetDesktopRectPixel();
+ ImplSVData *pSVData = ImplGetSVData();
+ vcl::Window *pWin = pSVData->maFrameData.mpFirstFrame;
+ bool bWrapped = false;
+ while( pWin )
+ {
+ if( !pWin->ImplIsRealParentPath( this ) && ( pWin != this ) &&
+ pWin->ImplGetWindow()->IsTopWindow() && pWin->mpWindowImpl->mbReallyVisible )
+ {
+ SalFrameGeometry g = pWin->mpWindowImpl->mpFrame->GetGeometry();
+ if( std::abs(g.nX-aState.mnX) < 2 && std::abs(g.nY-aState.mnY) < 5 )
+ {
+ tools::Long displacement = g.nTopDecoration ? g.nTopDecoration : 20;
+ if( static_cast<tools::Long>(aState.mnX + displacement + aState.mnWidth + g.nRightDecoration) > aDesktop.Right() ||
+ static_cast<tools::Long>(aState.mnY + displacement + aState.mnHeight + g.nBottomDecoration) > aDesktop.Bottom() )
+ {
+ // displacing would leave screen
+ aState.mnX = g.nLeftDecoration ? g.nLeftDecoration : 10; // should result in (0,0)
+ aState.mnY = displacement;
+ if( bWrapped ||
+ static_cast<tools::Long>(aState.mnX + displacement + aState.mnWidth + g.nRightDecoration) > aDesktop.Right() ||
+ static_cast<tools::Long>(aState.mnY + displacement + aState.mnHeight + g.nBottomDecoration) > aDesktop.Bottom() )
+ break; // further displacement not possible -> break
+ // avoid endless testing
+ bWrapped = true;
+ }
+ else
+ {
+ // displace
+ aState.mnX += displacement;
+ aState.mnY += displacement;
+ }
+ pWin = pSVData->maFrameData.mpFirstFrame; // check new pos again
+ }
+ }
+ pWin = pWin->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+ }
+
+ mpWindowImpl->mpFrame->SetWindowState( &aState );
+
+ // do a synchronous resize for layout reasons
+ // but use rData only when the window is not to be maximized (#i38089#)
+ // otherwise we have no useful size information
+ if( (rData.GetMask() & WindowStateMask::State) && (nState & WindowStateState::Maximized) )
+ {
+ // query maximized size from frame
+ SalFrameGeometry aGeometry = mpWindowImpl->mpFrame->GetGeometry();
+
+ // but use it only if it is different from the restore size (rData)
+ // as currently only on windows the exact size of a maximized window
+ // can be computed without actually showing the window
+ if( aGeometry.nWidth != rData.GetWidth() || aGeometry.nHeight != rData.GetHeight() )
+ ImplHandleResize( pWindow, aGeometry.nWidth, aGeometry.nHeight );
+ }
+ else
+ if( rData.GetMask() & (WindowStateMask::Width|WindowStateMask::Height) )
+ ImplHandleResize( pWindow, aState.mnWidth, aState.mnHeight ); // #i43799# use aState and not rData, see above
+ }
+ else
+ {
+ PosSizeFlags nPosSize = PosSizeFlags::NONE;
+ if ( nValidMask & WindowStateMask::X )
+ nPosSize |= PosSizeFlags::X;
+ if ( nValidMask & WindowStateMask::Y )
+ nPosSize |= PosSizeFlags::Y;
+ if ( nValidMask & WindowStateMask::Width )
+ nPosSize |= PosSizeFlags::Width;
+ if ( nValidMask & WindowStateMask::Height )
+ nPosSize |= PosSizeFlags::Height;
+
+ tools::Long nX = rData.GetX();
+ tools::Long nY = rData.GetY();
+ tools::Long nWidth = rData.GetWidth();
+ tools::Long nHeight = rData.GetHeight();
+ const SalFrameGeometry& rGeom = pWindow->mpWindowImpl->mpFrame->GetGeometry();
+ if( nX < 0 )
+ nX = 0;
+ if( nX + nWidth > static_cast<tools::Long>(rGeom.nWidth) )
+ nX = rGeom.nWidth - nWidth;
+ if( nY < 0 )
+ nY = 0;
+ if( nY + nHeight > static_cast<tools::Long>(rGeom.nHeight) )
+ nY = rGeom.nHeight - nHeight;
+ setPosSizePixel( nX, nY, nWidth, nHeight, nPosSize );
+ }
+
+ // tdf#146648 if an explicit size state was set, then use it as the preferred
+ // size for layout
+ if (nValidMask & WindowStateMask::Size)
+ mbInitialLayoutSizeCalculated = true;
+}
+
+void SystemWindow::GetWindowStateData( WindowStateData& rData ) const
+{
+ WindowStateMask nValidMask = rData.GetMask();
+ if ( nValidMask == WindowStateMask::NONE )
+ return;
+
+ if ( mbSysChild )
+ return;
+
+ const vcl::Window* pWindow = this;
+ while ( pWindow->mpWindowImpl->mpBorderWindow )
+ pWindow = pWindow->mpWindowImpl->mpBorderWindow;
+
+ if ( pWindow->mpWindowImpl->mbFrame )
+ {
+ SalFrameState aState;
+ aState.mnMask = WindowStateMask::All;
+ if ( mpWindowImpl->mpFrame->GetWindowState( &aState ) )
+ {
+ if ( nValidMask & WindowStateMask::X )
+ rData.SetX( aState.mnX );
+ if ( nValidMask & WindowStateMask::Y )
+ rData.SetY( aState.mnY );
+ if ( nValidMask & WindowStateMask::Width )
+ rData.SetWidth( aState.mnWidth );
+ if ( nValidMask & WindowStateMask::Height )
+ rData.SetHeight( aState.mnHeight );
+ if ( aState.mnMask & WindowStateMask::MaximizedX )
+ {
+ rData.SetMaximizedX( aState.mnMaximizedX );
+ nValidMask |= WindowStateMask::MaximizedX;
+ }
+ if ( aState.mnMask & WindowStateMask::MaximizedY )
+ {
+ rData.SetMaximizedY( aState.mnMaximizedY );
+ nValidMask |= WindowStateMask::MaximizedY;
+ }
+ if ( aState.mnMask & WindowStateMask::MaximizedWidth )
+ {
+ rData.SetMaximizedWidth( aState.mnMaximizedWidth );
+ nValidMask |= WindowStateMask::MaximizedWidth;
+ }
+ if ( aState.mnMask & WindowStateMask::MaximizedHeight )
+ {
+ rData.SetMaximizedHeight( aState.mnMaximizedHeight );
+ nValidMask |= WindowStateMask::MaximizedHeight;
+ }
+ if ( nValidMask & WindowStateMask::State )
+ {
+ // #94144# allow Minimize again, should be masked out when read from configuration
+ // 91625 - ignore Minimize
+ if ( !(nValidMask&WindowStateMask::Minimized) )
+ aState.mnState &= ~WindowStateState::Minimized;
+ rData.SetState( aState.mnState );
+ }
+ rData.SetMask( nValidMask );
+ }
+ else
+ rData.SetMask( WindowStateMask::NONE );
+ }
+ else
+ {
+ Point aPos = GetPosPixel();
+ Size aSize = GetSizePixel();
+ WindowStateState nState = WindowStateState::NONE;
+
+ if ( nValidMask & WindowStateMask::X )
+ rData.SetX( aPos.X() );
+ if ( nValidMask & WindowStateMask::Y )
+ rData.SetY( aPos.Y() );
+ if ( nValidMask & WindowStateMask::Width )
+ rData.SetWidth( aSize.Width() );
+ if ( nValidMask & WindowStateMask::Height )
+ rData.SetHeight( aSize.Height() );
+ if ( nValidMask & WindowStateMask::State )
+ rData.SetState( nState );
+ }
+}
+
+void SystemWindow::SetWindowState(std::string_view rStr)
+{
+ if (rStr.empty())
+ return;
+
+ WindowStateData aData;
+ ImplWindowStateFromStr( aData, rStr );
+ SetWindowStateData( aData );
+}
+
+OString SystemWindow::GetWindowState( WindowStateMask nMask ) const
+{
+ WindowStateData aData;
+ aData.SetMask( nMask );
+ GetWindowStateData( aData );
+
+ return aData.ToStr();
+}
+
+void SystemWindow::SetMenuBar(MenuBar* pMenuBar)
+{
+ if ( mpMenuBar == pMenuBar )
+ return;
+
+ MenuBar* pOldMenuBar = mpMenuBar;
+ vcl::Window* pOldWindow = nullptr;
+ VclPtr<vcl::Window> pNewWindow;
+ mpMenuBar = pMenuBar;
+
+ if ( mpWindowImpl->mpBorderWindow && (mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW) )
+ {
+ if ( pOldMenuBar )
+ pOldWindow = pOldMenuBar->ImplGetWindow();
+ else
+ pOldWindow = nullptr;
+ if ( pOldWindow )
+ {
+ CallEventListeners( VclEventId::WindowMenubarRemoved, static_cast<void*>(pOldMenuBar) );
+ pOldWindow->SetAccessible( css::uno::Reference< css::accessibility::XAccessible >() );
+ }
+ if ( pMenuBar )
+ {
+ SAL_WARN_IF( pMenuBar->pWindow, "vcl", "SystemWindow::SetMenuBar() - MenuBars can only set in one SystemWindow at time" );
+
+ pNewWindow = MenuBar::ImplCreate(mpWindowImpl->mpBorderWindow, pOldWindow, pMenuBar);
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMenuBarWindow(pNewWindow);
+
+ CallEventListeners( VclEventId::WindowMenubarAdded, static_cast<void*>(pMenuBar) );
+ }
+ else
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMenuBarWindow( nullptr );
+ ImplToBottomChild();
+ if ( pOldMenuBar )
+ {
+ bool bDelete = (pMenuBar == nullptr);
+ if( bDelete && pOldWindow )
+ {
+ if( mpImplData->mpTaskPaneList )
+ mpImplData->mpTaskPaneList->RemoveWindow( pOldWindow );
+ }
+ MenuBar::ImplDestroy( pOldMenuBar, bDelete );
+ if( bDelete )
+ pOldWindow = nullptr; // will be deleted in MenuBar::ImplDestroy,
+ }
+
+ }
+ else
+ {
+ if( pMenuBar )
+ pNewWindow = pMenuBar->ImplGetWindow();
+ if( pOldMenuBar )
+ pOldWindow = pOldMenuBar->ImplGetWindow();
+ }
+
+ // update taskpane list to make menubar accessible
+ if( mpImplData->mpTaskPaneList )
+ {
+ if( pOldWindow )
+ mpImplData->mpTaskPaneList->RemoveWindow( pOldWindow );
+ if( pNewWindow )
+ mpImplData->mpTaskPaneList->AddWindow( pNewWindow );
+ }
+}
+
+void SystemWindow::SetNotebookBar(const OUString& rUIXMLDescription,
+ const css::uno::Reference<css::frame::XFrame>& rFrame,
+ const NotebookBarAddonsItem& aNotebookBarAddonsItem,
+ bool bReloadNotebookbar)
+{
+ if (rUIXMLDescription != maNotebookBarUIFile || bReloadNotebookbar)
+ {
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())
+ ->SetNotebookBar(rUIXMLDescription, rFrame, aNotebookBarAddonsItem);
+ maNotebookBarUIFile = rUIXMLDescription;
+ if(GetNotebookBar())
+ GetNotebookBar()->SetSystemWindow(this);
+ }
+}
+
+void SystemWindow::CloseNotebookBar()
+{
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->CloseNotebookBar();
+ maNotebookBarUIFile.clear();
+}
+
+VclPtr<NotebookBar> const & SystemWindow::GetNotebookBar() const
+{
+ return static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->GetNotebookBar();
+}
+
+void SystemWindow::SetMenuBarMode( MenuBarMode nMode )
+{
+ if ( mnMenuBarMode != nMode )
+ {
+ mnMenuBarMode = nMode;
+ if ( mpWindowImpl->mpBorderWindow && (mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW) )
+ {
+ if ( nMode == MenuBarMode::Hide )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMenuBarMode( true );
+ else
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMenuBarMode( false );
+ }
+ }
+}
+
+bool SystemWindow::ImplIsInTaskPaneList( vcl::Window* pWin )
+{
+ if( mpImplData && mpImplData->mpTaskPaneList )
+ return mpImplData->mpTaskPaneList->IsInList( pWin );
+ return false;
+}
+
+unsigned int SystemWindow::GetScreenNumber() const
+{
+ return mpWindowImpl->mpFrame->maGeometry.nDisplayScreenNumber;
+}
+
+void SystemWindow::SetScreenNumber(unsigned int nDisplayScreen)
+{
+ mpWindowImpl->mpFrame->SetScreenNumber( nDisplayScreen );
+}
+
+void SystemWindow::SetApplicationID(const OUString &rApplicationID)
+{
+ mpWindowImpl->mpFrame->SetApplicationID( rApplicationID );
+}
+
+void SystemWindow::SetCloseHdl(const Link<SystemWindow&,void>& rLink)
+{
+ mpImplData->maCloseHdl = rLink;
+}
+
+const Link<SystemWindow&,void>& SystemWindow::GetCloseHdl() const
+{
+ return mpImplData->maCloseHdl;
+}
+
+void SystemWindow::queue_resize(StateChangedType /*eReason*/)
+{
+ if (!isLayoutEnabled())
+ return;
+ if (isCalculatingInitialLayoutSize())
+ return;
+ InvalidateSizeCache();
+ if (hasPendingLayout())
+ return;
+ maLayoutIdle.Start();
+}
+
+void SystemWindow::Resize()
+{
+ queue_resize();
+}
+
+bool SystemWindow::isLayoutEnabled() const
+{
+ //pre dtor called, and single child is a container => we're layout enabled
+ return mpImplData && ::isLayoutEnabled(this);
+}
+
+Size SystemWindow::GetOptimalSize() const
+{
+ if (!isLayoutEnabled())
+ return Window::GetOptimalSize();
+
+ Window *pBox = GetWindow(GetWindowType::FirstChild);
+ // tdf#141318 Do the same as SystemWindow::setOptimalLayoutSize in case we're called before initial layout
+ const_cast<SystemWindow*>(this)->settingOptimalLayoutSize(pBox);
+ Size aSize = VclContainer::getLayoutRequisition(*pBox);
+
+ sal_Int32 nBorderWidth = get_border_width();
+
+ aSize.AdjustHeight(2 * nBorderWidth );
+ aSize.AdjustWidth(2 * nBorderWidth );
+
+ return Window::CalcWindowSize(aSize);
+}
+
+void SystemWindow::setPosSizeOnContainee(Size aSize, Window &rBox)
+{
+ sal_Int32 nBorderWidth = get_border_width();
+
+ aSize.AdjustWidth( -(2 * nBorderWidth) );
+ aSize.AdjustHeight( -(2 * nBorderWidth) );
+
+ Point aPos(nBorderWidth, nBorderWidth);
+ VclContainer::setLayoutAllocation(rBox, aPos, CalcOutputSize(aSize));
+}
+
+IMPL_LINK_NOARG( SystemWindow, ImplHandleLayoutTimerHdl, Timer*, void )
+{
+ Window *pBox = GetWindow(GetWindowType::FirstChild);
+ if (!isLayoutEnabled())
+ {
+ SAL_WARN_IF(pBox, "vcl.layout", "SystemWindow has become non-layout because extra children have been added directly to it.");
+ return;
+ }
+ assert(pBox);
+ setPosSizeOnContainee(GetSizePixel(), *pBox);
+}
+
+void SystemWindow::SetText(const OUString& rStr)
+{
+ setDeferredProperties();
+ Window::SetText(rStr);
+}
+
+OUString SystemWindow::GetText() const
+{
+ const_cast<SystemWindow*>(this)->setDeferredProperties();
+ return Window::GetText();
+}
+
+void SystemWindow::settingOptimalLayoutSize(Window* /*pBox*/)
+{
+}
+
+void SystemWindow::setOptimalLayoutSize(bool bAllowWindowShrink)
+{
+ maLayoutIdle.Stop();
+
+ //resize SystemWindow to fit requisition on initial show
+ Window *pBox = GetWindow(GetWindowType::FirstChild);
+
+ settingOptimalLayoutSize(pBox);
+
+ Size aSize = get_preferred_size();
+
+ Size aMax(bestmaxFrameSizeForScreenSize(GetDesktopRectPixel().GetSize()));
+
+ aSize.setWidth( std::min(aMax.Width(), aSize.Width()) );
+ aSize.setHeight( std::min(aMax.Height(), aSize.Height()) );
+
+ SetMinOutputSizePixel(aSize);
+
+ if (!bAllowWindowShrink)
+ {
+ Size aCurrentSize = GetSizePixel();
+ aSize.setWidth(std::max(aSize.Width(), aCurrentSize.Width()));
+ aSize.setHeight(std::max(aSize.Height(), aCurrentSize.Height()));
+ }
+
+ SetSizePixel(aSize);
+ setPosSizeOnContainee(aSize, *pBox);
+}
+
+void SystemWindow::DoInitialLayout()
+{
+ if (GetSettings().GetStyleSettings().GetAutoMnemonic())
+ GenerateAutoMnemonicsOnHierarchy(this);
+
+ if (isLayoutEnabled())
+ {
+ mbIsCalculatingInitialLayoutSize = true;
+ setDeferredProperties();
+ setOptimalLayoutSize(!mbInitialLayoutSizeCalculated);
+ mbInitialLayoutSizeCalculated = true;
+ mbIsCalculatingInitialLayoutSize = false;
+ }
+}
+
+void SystemWindow::doDeferredInit(WinBits /*nBits*/)
+{
+ SAL_WARN("vcl.layout", "SystemWindow in layout without doDeferredInit impl");
+}
+
+VclPtr<VirtualDevice> SystemWindow::createScreenshot()
+{
+ // same prerequisites as in Execute()
+ setDeferredProperties();
+ ImplAdjustNWFSizes();
+ Show();
+ ToTop();
+ ensureRepaint();
+
+ Size aSize(GetOutputSizePixel());
+
+ VclPtr<VirtualDevice> xOutput(VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT));
+ xOutput->SetOutputSizePixel(aSize);
+
+ Point aPos;
+ xOutput->DrawOutDev(aPos, aSize, aPos, aSize, *GetOutDev());
+
+ return xOutput;
+}
+
+void SystemWindow::PrePaint(vcl::RenderContext& rRenderContext)
+{
+ Window::PrePaint(rRenderContext);
+ mbPaintComplete = false;
+}
+
+void SystemWindow::PostPaint(vcl::RenderContext& rRenderContext)
+{
+ Window::PostPaint(rRenderContext);
+ mbPaintComplete = true;
+}
+
+void SystemWindow::ensureRepaint()
+{
+ // ensure repaint
+ Invalidate();
+ mbPaintComplete = false;
+
+ while (!mbPaintComplete && !Application::IsQuit())
+ {
+ Application::Yield();
+ }
+}
+
+void SystemWindow::CollectMenuBarMnemonics(MnemonicGenerator& rMnemonicGenerator) const
+{
+ if (MenuBar* pMenu = GetMenuBar())
+ {
+ sal_uInt16 nMenuItems = pMenu->GetItemCount();
+ for ( sal_uInt16 i = 0; i < nMenuItems; ++i )
+ rMnemonicGenerator.RegisterMnemonic( pMenu->GetItemText( pMenu->GetItemId( i ) ) );
+ }
+}
+
+int SystemWindow::GetMenuBarHeight() const
+{
+ if (MenuBar* pMenuBar = GetMenuBar())
+ return pMenuBar->GetMenuBarHeight();
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/tabdlg.cxx b/vcl/source/window/tabdlg.cxx
new file mode 100644
index 000000000..f132300a0
--- /dev/null
+++ b/vcl/source/window/tabdlg.cxx
@@ -0,0 +1,184 @@
+/* -*- 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 <vcl/toolkit/fixed.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/toolkit/tabdlg.hxx>
+#include <vcl/tabpage.hxx>
+
+void TabDialog::ImplInitTabDialogData()
+{
+ mpFixedLine = nullptr;
+ mbPosControls = true;
+}
+
+void TabDialog::ImplPosControls()
+{
+ if (isLayoutEnabled())
+ return;
+
+ Size aCtrlSize( IMPL_MINSIZE_BUTTON_WIDTH, IMPL_MINSIZE_BUTTON_HEIGHT );
+ tools::Long nDownCtrl = 0;
+ tools::Long nOffY = 0;
+ vcl::Window* pTabControl = nullptr;
+
+ vcl::Window* pChild = GetWindow( GetWindowType::FirstChild );
+ while ( pChild )
+ {
+ if ( pChild->IsVisible() )
+ {
+ if (pChild->GetType() == WindowType::TABCONTROL || isContainerWindow(*pChild))
+ pTabControl = pChild;
+ else if ( pTabControl )
+ {
+ Size aOptimalSize(pChild->get_preferred_size());
+ tools::Long nTxtWidth = aOptimalSize.Width();
+ if ( nTxtWidth > aCtrlSize.Width() )
+ aCtrlSize.setWidth( nTxtWidth );
+ tools::Long nTxtHeight = aOptimalSize.Height();
+ if ( nTxtHeight > aCtrlSize.Height() )
+ aCtrlSize.setHeight( nTxtHeight );
+ nDownCtrl++;
+ }
+ else
+ {
+ tools::Long nHeight = pChild->GetSizePixel().Height();
+ if ( nHeight > nOffY )
+ nOffY = nHeight;
+ }
+ }
+
+ pChild = pChild->GetWindow( GetWindowType::Next );
+ }
+
+ // do we have a TabControl ?
+ if ( pTabControl )
+ {
+ // adapt offset for other controls by an extra distance
+ if ( nOffY )
+ nOffY += IMPL_DIALOG_BAR_OFFSET*2 + 2;
+
+ Point aTabOffset( IMPL_DIALOG_OFFSET, IMPL_DIALOG_OFFSET+nOffY );
+
+ if (isContainerWindow(*pTabControl))
+ pTabControl->SetSizePixel(pTabControl->get_preferred_size());
+
+ Size aTabSize = pTabControl->GetSizePixel();
+
+ Size aDlgSize( aTabSize.Width() + IMPL_DIALOG_OFFSET*2,
+ aTabSize.Height() + IMPL_DIALOG_OFFSET*2 + nOffY );
+
+ // adapt positioning
+ pTabControl->SetPosPixel( aTabOffset );
+
+ // position all other Children
+ bool bTabCtrl = false;
+ int nLines = 0;
+ tools::Long nX;
+ tools::Long nY = aDlgSize.Height();
+ tools::Long nTopX = IMPL_DIALOG_OFFSET;
+
+ // all buttons are right aligned under Windows 95
+ nX = IMPL_DIALOG_OFFSET;
+ tools::Long nCtrlBarWidth = ((aCtrlSize.Width()+IMPL_DIALOG_OFFSET)*nDownCtrl)-IMPL_DIALOG_OFFSET;
+ if ( nCtrlBarWidth <= aTabSize.Width() )
+ nX = aTabSize.Width() - nCtrlBarWidth + IMPL_DIALOG_OFFSET;
+
+ vcl::Window* pChild2 = GetWindow( GetWindowType::FirstChild );
+ while ( pChild2 )
+ {
+ if ( pChild2->IsVisible() )
+ {
+ if ( pChild2 == pTabControl )
+ bTabCtrl = true;
+ else if ( bTabCtrl )
+ {
+ if ( !nLines )
+ nLines = 1;
+
+ if ( nX+aCtrlSize.Width()-IMPL_DIALOG_OFFSET > aTabSize.Width() )
+ {
+ nY += aCtrlSize.Height()+IMPL_DIALOG_OFFSET;
+ nX = IMPL_DIALOG_OFFSET;
+ nLines++;
+ }
+
+ pChild2->SetPosSizePixel( Point( nX, nY ), aCtrlSize );
+ nX += aCtrlSize.Width()+IMPL_DIALOG_OFFSET;
+ }
+ else
+ {
+ Size aChildSize = pChild2->GetSizePixel();
+ pChild2->SetPosPixel( Point( nTopX, (nOffY-aChildSize.Height())/2 ) );
+ nTopX += aChildSize.Width()+2;
+ }
+ }
+
+ pChild2 = pChild2->GetWindow( GetWindowType::Next );
+ }
+
+ aDlgSize.AdjustHeight(nLines * (aCtrlSize.Height()+IMPL_DIALOG_OFFSET) );
+ SetOutputSizePixel( aDlgSize );
+ }
+
+ // store offset
+ if ( nOffY )
+ {
+ Size aDlgSize = GetOutputSizePixel();
+ if ( !mpFixedLine )
+ mpFixedLine = VclPtr<FixedLine>::Create( this );
+ mpFixedLine->SetPosSizePixel( Point( 0, nOffY ),
+ Size( aDlgSize.Width(), 2 ) );
+ mpFixedLine->Show();
+ }
+
+ mbPosControls = false;
+}
+
+TabDialog::TabDialog( vcl::Window* pParent, WinBits nStyle ) :
+ Dialog( WindowType::TABDIALOG )
+{
+ ImplInitTabDialogData();
+ ImplInitDialog( pParent, nStyle );
+}
+
+TabDialog::~TabDialog()
+{
+ disposeOnce();
+}
+
+void TabDialog::dispose()
+{
+ mpFixedLine.disposeAndClear();
+ Dialog::dispose();
+}
+
+void TabDialog::StateChanged( StateChangedType nType )
+{
+ if ( nType == StateChangedType::InitShow )
+ {
+ // Calculate the Layout only for the initialized state
+ if ( mbPosControls )
+ ImplPosControls();
+ }
+ Dialog::StateChanged( nType );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/tabpage.cxx b/vcl/source/window/tabpage.cxx
new file mode 100644
index 000000000..34f52d8b3
--- /dev/null
+++ b/vcl/source/window/tabpage.cxx
@@ -0,0 +1,310 @@
+/* -*- 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 <vcl/event.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/tabpage.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/scrbar.hxx>
+#include <svdata.hxx>
+
+void TabPage::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ if ( !(nStyle & WB_NODIALOGCONTROL) )
+ nStyle |= WB_DIALOGCONTROL;
+
+ Window::ImplInit( pParent, nStyle, nullptr );
+
+ mbHasHoriBar = false;
+ mbHasVertBar = false;
+
+ Link<ScrollBar*,void> aLink( LINK( this, TabPage, ScrollBarHdl ) );
+
+ if ( nStyle & ( WB_AUTOHSCROLL | WB_AUTOVSCROLL ) )
+ {
+ if ( nStyle & WB_AUTOHSCROLL )
+ {
+ mbHasHoriBar = true;
+ m_pHScroll.set(VclPtr<ScrollBar>::Create(this, (WB_HSCROLL | WB_DRAG)));
+ m_pHScroll->Show();
+ m_pHScroll->SetScrollHdl(aLink);
+ }
+ if ( nStyle & WB_AUTOVSCROLL )
+ {
+ mbHasVertBar = true;
+ m_pVScroll.set(VclPtr<ScrollBar>::Create(this, (WB_VSCROLL | WB_DRAG)));
+ m_pVScroll->Show();
+ m_pVScroll->SetScrollHdl(aLink);
+ }
+ }
+
+ if ( mbHasHoriBar || mbHasVertBar )
+ {
+ SetStyle( GetStyle() | WB_CLIPCHILDREN );
+ }
+
+ mnScrWidth = Application::GetSettings().GetStyleSettings().GetScrollBarSize();
+
+ ImplInitSettings();
+
+ // if the tabpage is drawn (ie filled) by a native widget, make sure all controls will have transparent background
+ // otherwise they will paint with a wrong background
+ if( IsNativeControlSupported(ControlType::TabBody, ControlPart::Entire) && GetParent() && (GetParent()->GetType() == WindowType::TABCONTROL) )
+ EnableChildTransparentMode();
+}
+
+void TabPage::ImplInitSettings()
+{
+ vcl::Window* pParent = GetParent();
+ if (pParent && pParent->IsChildTransparentModeEnabled() && !IsControlBackground())
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode( ParentClipMode::NoClip );
+ SetPaintTransparent( true );
+ SetBackground();
+ }
+ else
+ {
+ EnableChildTransparentMode( false );
+ SetParentClipMode();
+ SetPaintTransparent( false );
+
+ if (IsControlBackground() || !pParent)
+ SetBackground( GetControlBackground() );
+ else
+ SetBackground( pParent->GetBackground() );
+ }
+}
+
+TabPage::TabPage( vcl::Window* pParent, WinBits nStyle ) :
+ Window( WindowType::TABPAGE )
+{
+ ImplInit( pParent, nStyle );
+}
+
+TabPage::~TabPage()
+{
+ disposeOnce();
+}
+
+void TabPage::dispose()
+{
+ m_pVScroll.disposeAndClear();
+ m_pHScroll.disposeAndClear();
+ vcl::Window::dispose();
+}
+
+void TabPage::StateChanged( StateChangedType nType )
+{
+ Window::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ {
+ if (GetSettings().GetStyleSettings().GetAutoMnemonic())
+ GenerateAutoMnemonicsOnHierarchy(this);
+ // FIXME: no layouting, workaround some clipping issues
+ ImplAdjustNWFSizes();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+}
+
+void TabPage::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Window::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+}
+
+void TabPage::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ // draw native tabpage only inside tabcontrols, standalone tabpages look ugly (due to bad dialog design)
+ if( !(IsNativeControlSupported(ControlType::TabBody, ControlPart::Entire) && GetParent() && (GetParent()->GetType() == WindowType::TABCONTROL)) )
+ return;
+
+ const ImplControlValue aControlValue;
+
+ ControlState nState = ControlState::ENABLED;
+ if ( !IsEnabled() )
+ nState &= ~ControlState::ENABLED;
+ if ( HasFocus() )
+ nState |= ControlState::FOCUSED;
+ // pass the whole window region to NWF as the tab body might be a gradient or bitmap
+ // that has to be scaled properly, clipping makes sure that we do not paint too much
+ tools::Rectangle aCtrlRegion( Point(), GetOutputSizePixel() );
+ rRenderContext.DrawNativeControl( ControlType::TabBody, ControlPart::Entire, aCtrlRegion, nState,
+ aControlValue, OUString() );
+}
+
+void TabPage::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags )
+{
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+
+ Wallpaper aWallpaper = GetBackground();
+ if ( !aWallpaper.IsBitmap() )
+ ImplInitSettings();
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetLineColor();
+
+ if ( aWallpaper.IsBitmap() )
+ pDev->DrawBitmapEx( aPos, aSize, aWallpaper.GetBitmap() );
+ else
+ {
+ if( aWallpaper.GetColor() == COL_AUTO )
+ pDev->SetFillColor( GetSettings().GetStyleSettings().GetDialogColor() );
+ else
+ pDev->SetFillColor( aWallpaper.GetColor() );
+ pDev->DrawRect( tools::Rectangle( aPos, aSize ) );
+ }
+
+ pDev->Pop();
+}
+
+Size TabPage::GetOptimalSize() const
+{
+ if (isLayoutEnabled(this))
+ return VclContainer::getLayoutRequisition(*GetWindow(GetWindowType::FirstChild));
+ return getLegacyBestSizeForChildren(*this);
+}
+
+void TabPage::SetPosSizePixel(const Point& rAllocPos, const Size& rAllocation)
+{
+ Window::SetPosSizePixel(rAllocPos, rAllocation);
+ if (isLayoutEnabled(this) && rAllocation.Width() && rAllocation.Height())
+ VclContainer::setLayoutAllocation(*GetWindow(GetWindowType::FirstChild), Point(0, 0), rAllocation);
+}
+
+void TabPage::SetSizePixel(const Size& rAllocation)
+{
+ Window::SetSizePixel(rAllocation);
+ if (isLayoutEnabled(this) && rAllocation.Width() && rAllocation.Height())
+ VclContainer::setLayoutAllocation(*GetWindow(GetWindowType::FirstChild), Point(0, 0), rAllocation);
+}
+
+void TabPage::SetPosPixel(const Point& rAllocPos)
+{
+ Window::SetPosPixel(rAllocPos);
+ Size aAllocation(GetOutputSizePixel());
+ if (isLayoutEnabled(this) && aAllocation.Width() && aAllocation.Height())
+ {
+ VclContainer::setLayoutAllocation(*GetWindow(GetWindowType::FirstChild), Point(0, 0), aAllocation);
+ }
+}
+
+void TabPage::lcl_Scroll( tools::Long nX, tools::Long nY )
+{
+ tools::Long nXScroll = mnScrollPos.X() - nX;
+ tools::Long nYScroll = mnScrollPos.Y() - nY;
+ mnScrollPos = Point( nX, nY );
+
+ tools::Rectangle aScrollableArea( 0, 0, maScrollArea.Width(), maScrollArea.Height() );
+ Scroll(nXScroll, nYScroll, aScrollableArea );
+ // Manually scroll all children ( except the scrollbars )
+ for ( int index = 0; index < GetChildCount(); ++index )
+ {
+ vcl::Window* pChild = GetChild( index );
+ if ( pChild && pChild != m_pVScroll.get() && pChild != m_pHScroll.get() )
+ {
+ Point aPos = pChild->GetPosPixel();
+ aPos += Point( nXScroll, nYScroll );
+ pChild->SetPosPixel( aPos );
+ }
+ }
+}
+
+IMPL_LINK( TabPage, ScrollBarHdl, ScrollBar*, pSB, void )
+{
+ sal_uInt16 nPos = static_cast<sal_uInt16>(pSB->GetThumbPos());
+ if( pSB == m_pVScroll.get() )
+ lcl_Scroll(mnScrollPos.X(), nPos );
+ else if( pSB == m_pHScroll.get() )
+ lcl_Scroll(nPos, mnScrollPos.Y() );
+}
+
+void TabPage::SetScrollTop( tools::Long nTop )
+{
+ Point aOld = mnScrollPos;
+ lcl_Scroll( mnScrollPos.X() , mnScrollPos.Y() - nTop );
+ if( m_pHScroll )
+ m_pHScroll->SetThumbPos( 0 );
+ // new pos is 0,0
+ mnScrollPos = aOld;
+}
+void TabPage::SetScrollLeft( tools::Long nLeft )
+{
+ Point aOld = mnScrollPos;
+ lcl_Scroll( mnScrollPos.X() - nLeft , mnScrollPos.Y() );
+ if( m_pVScroll )
+ m_pVScroll->SetThumbPos( 0 );
+ // new pos is 0,0
+ mnScrollPos = aOld;
+}
+
+void TabPage::SetScrollWidth( tools::Long nWidth )
+{
+ maScrollArea.setWidth( nWidth );
+ ResetScrollBars();
+}
+
+void TabPage::SetScrollHeight( tools::Long nHeight )
+{
+ maScrollArea.setHeight( nHeight );
+ ResetScrollBars();
+}
+
+void TabPage::Resize()
+{
+ ResetScrollBars();
+}
+
+void TabPage::ResetScrollBars()
+{
+ Size aOutSz = GetOutputSizePixel();
+
+ Point aVPos( aOutSz.Width() - mnScrWidth, 0 );
+ Point aHPos( 0, aOutSz.Height() - mnScrWidth );
+
+ if( m_pVScroll )
+ {
+ m_pVScroll->SetPosSizePixel( aVPos, Size( mnScrWidth, GetSizePixel().Height() - mnScrWidth ) );
+ m_pVScroll->SetRangeMax( maScrollArea.Height() + mnScrWidth );
+ m_pVScroll->SetVisibleSize( GetSizePixel().Height() );
+ }
+
+ if( m_pHScroll )
+ {
+ m_pHScroll->SetPosSizePixel( aHPos, Size( GetSizePixel().Width() - mnScrWidth, mnScrWidth ) );
+ m_pHScroll->SetRangeMax( maScrollArea.Width() + mnScrWidth );
+ m_pHScroll->SetVisibleSize( GetSizePixel().Width() );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/taskpanelist.cxx b/vcl/source/window/taskpanelist.cxx
new file mode 100644
index 000000000..833c82a0a
--- /dev/null
+++ b/vcl/source/window/taskpanelist.cxx
@@ -0,0 +1,287 @@
+/* -*- 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 <vcl/dockwin.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/taskpanelist.hxx>
+
+#include <svdata.hxx>
+#include "menubarwindow.hxx"
+
+#include <algorithm>
+
+namespace {
+
+Point ImplTaskPaneListGetPos( const vcl::Window *w )
+{
+ Point pos;
+ if( w->IsDockingWindow() )
+ {
+ pos = static_cast<const DockingWindow*>(w)->GetPosPixel();
+ vcl::Window *pF = static_cast<const DockingWindow*>(w)->GetFloatingWindow();
+ if( pF )
+ pos = pF->OutputToAbsoluteScreenPixel( pF->ScreenToOutputPixel( pos ) );
+ else
+ pos = w->OutputToAbsoluteScreenPixel( pos );
+ }
+ else
+ pos = w->OutputToAbsoluteScreenPixel( w->GetPosPixel() );
+
+ return pos;
+}
+
+// compares window pos left-to-right
+struct LTRSort
+{
+ bool operator()( const vcl::Window* w1, const vcl::Window* w2 ) const
+ {
+ Point pos1(ImplTaskPaneListGetPos( w1 ));
+ Point pos2(ImplTaskPaneListGetPos( w2 ));
+
+ if( pos1.X() == pos2.X() )
+ return ( pos1.Y() < pos2.Y() );
+ else
+ return ( pos1.X() < pos2.X() );
+ }
+};
+
+}
+
+static void ImplTaskPaneListGrabFocus( vcl::Window *pWindow, bool bForward )
+{
+ // put focus in child of floating windows which is typically a toolbar
+ // that can deal with the focus
+ if( pWindow->ImplIsFloatingWindow() && pWindow->GetWindow( GetWindowType::FirstChild ) )
+ pWindow = pWindow->GetWindow( GetWindowType::FirstChild );
+ pWindow->ImplGrabFocus( GetFocusFlags::F6 | (bForward ? GetFocusFlags::Forward : GetFocusFlags::Backward));
+}
+
+TaskPaneList::TaskPaneList()
+{
+}
+
+TaskPaneList::~TaskPaneList()
+{
+}
+
+void TaskPaneList::AddWindow( vcl::Window *pWindow )
+{
+ if( !pWindow )
+ return;
+
+ auto insertionPos = dynamic_cast<MenuBarWindow*>(pWindow) ? mTaskPanes.begin() : mTaskPanes.end();
+ for ( auto p = mTaskPanes.begin(); p != mTaskPanes.end(); ++p )
+ {
+ if ( *p == pWindow )
+ // avoid duplicates
+ return;
+
+ // If the new window is the child of an existing pane window, or vice versa,
+ // ensure that in our pane list, *first* the child window appears, *then*
+ // the ancestor window.
+ // This is necessary for HandleKeyEvent: There, the list is traveled from the
+ // beginning, until the first window is found which has the ChildPathFocus. Now
+ // if this would be the ancestor window of another pane window, this would fudge
+ // the result
+ if ( pWindow->IsWindowOrChild( *p ) )
+ {
+ insertionPos = p + 1;
+ break;
+ }
+ if ( (*p)->IsWindowOrChild( pWindow ) )
+ {
+ insertionPos = p;
+ break;
+ }
+ }
+
+ mTaskPanes.insert( insertionPos, pWindow );
+ pWindow->ImplIsInTaskPaneList( true );
+}
+
+void TaskPaneList::RemoveWindow( vcl::Window *pWindow )
+{
+ auto p = ::std::find( mTaskPanes.begin(), mTaskPanes.end(), VclPtr<vcl::Window>(pWindow) );
+ if( p != mTaskPanes.end() )
+ {
+ mTaskPanes.erase( p );
+ pWindow->ImplIsInTaskPaneList( false );
+ }
+}
+
+bool TaskPaneList::IsInList( vcl::Window *pWindow )
+{
+ auto p = ::std::find( mTaskPanes.begin(), mTaskPanes.end(), VclPtr<vcl::Window>(pWindow) );
+ return p != mTaskPanes.end();
+}
+
+bool TaskPaneList::IsCycleKey(const vcl::KeyCode& rKeyCode)
+{
+ return rKeyCode.GetCode() == KEY_F6 && !rKeyCode.IsMod2(); // F6
+}
+
+bool TaskPaneList::HandleKeyEvent(const KeyEvent& rKeyEvent)
+{
+
+ // F6 cycles through everything and works always
+
+ // MAV, #i104204#
+ // The old design was the following one:
+ // < Ctrl-TAB cycles through Menubar, Toolbars and Floatingwindows only and is
+ // < only active if one of those items has the focus
+
+ // Since the design of Ctrl-Tab looks to be inconsistent ( non-modal dialogs are not reachable
+ // and the shortcut conflicts with tab-control shortcut ), it is no more supported
+ vcl::KeyCode aKeyCode = rKeyEvent.GetKeyCode();
+ bool bForward = !aKeyCode.IsShift();
+ if (TaskPaneList::IsCycleKey(aKeyCode))
+ {
+ bool bSplitterOnly = aKeyCode.IsMod1() && aKeyCode.IsShift();
+
+ // is the focus in the list ?
+ auto p = std::find_if(mTaskPanes.begin(), mTaskPanes.end(),
+ [](const VclPtr<vcl::Window>& rWinPtr) { return rWinPtr->HasChildPathFocus( true ); });
+ if( p != mTaskPanes.end() )
+ {
+ vcl::Window *pWin = p->get();
+
+ // Ctrl-F6 goes directly to the document
+ if( !pWin->IsDialog() && aKeyCode.IsMod1() && !aKeyCode.IsShift() )
+ {
+ pWin->ImplGrabFocusToDocument( GetFocusFlags::F6 );
+ return true;
+ }
+
+ // activate next task pane
+ vcl::Window *pNextWin = nullptr;
+
+ if( bSplitterOnly )
+ pNextWin = FindNextSplitter( *p );
+ else
+ pNextWin = FindNextFloat( *p, bForward );
+
+ if( pNextWin != pWin )
+ {
+ ImplGetSVData()->mpWinData->mbNoSaveFocus = true;
+ ImplTaskPaneListGrabFocus( pNextWin, bForward );
+ ImplGetSVData()->mpWinData->mbNoSaveFocus = false;
+ }
+ else
+ {
+ // forward key if no splitter found
+ if( bSplitterOnly )
+ return false;
+
+ // we did not find another taskpane, so
+ // put focus back into document
+ pWin->ImplGrabFocusToDocument( GetFocusFlags::F6 | (bForward ? GetFocusFlags::Forward : GetFocusFlags::Backward));
+ }
+
+ return true;
+ }
+
+ // the focus is not in the list: activate first float if F6 was pressed
+ vcl::Window *pWin;
+ if( bSplitterOnly )
+ pWin = FindNextSplitter( nullptr );
+ else
+ pWin = FindNextFloat( nullptr, bForward );
+ if( pWin )
+ {
+ ImplTaskPaneListGrabFocus( pWin, bForward );
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// returns next splitter
+vcl::Window* TaskPaneList::FindNextSplitter( vcl::Window *pWindow )
+{
+ ::std::stable_sort( mTaskPanes.begin(), mTaskPanes.end(), LTRSort() );
+
+ auto p = mTaskPanes.begin();
+ if( pWindow )
+ p = std::find(mTaskPanes.begin(), mTaskPanes.end(), pWindow);
+
+ if( p != mTaskPanes.end() )
+ {
+ unsigned n = mTaskPanes.size();
+ while( --n )
+ {
+ if( pWindow ) // increment before test
+ ++p;
+ if( p == mTaskPanes.end() )
+ p = mTaskPanes.begin();
+ if( (*p)->ImplIsSplitter() && (*p)->IsReallyVisible() && !(*p)->IsDialog() && (*p)->GetParent()->HasChildPathFocus() )
+ {
+ pWindow = (*p).get();
+ break;
+ }
+ if( !pWindow ) // increment after test, otherwise first element is skipped
+ ++p;
+ }
+ }
+
+ return pWindow;
+}
+
+// returns first valid item (regardless of type) if pWindow==0, otherwise returns next valid float
+vcl::Window* TaskPaneList::FindNextFloat( vcl::Window *pWindow, bool bForward )
+{
+ ::std::stable_sort( mTaskPanes.begin(), mTaskPanes.end(), LTRSort() );
+
+ if ( !bForward )
+ ::std::reverse( mTaskPanes.begin(), mTaskPanes.end() );
+
+ auto p = mTaskPanes.begin();
+ if( pWindow )
+ p = std::find(mTaskPanes.begin(), mTaskPanes.end(), pWindow);
+
+ while( p != mTaskPanes.end() )
+ {
+ if( pWindow ) // increment before test
+ ++p;
+ if( p == mTaskPanes.end() )
+ break; // do not wrap, send focus back to document at end of list
+ /* #i83908# do not use the menubar if it is native and invisible
+ */
+
+ bool bSkip = false; // used to skip infobar when it has no children
+ if( (*p)->GetType() == WindowType::WINDOW && (*p)->GetChildCount() == 0 )
+ bSkip = true;
+
+ if( !bSkip && (*p)->IsReallyVisible() && !(*p)->ImplIsSplitter() &&
+ ( (*p)->GetType() != WindowType::MENUBARWINDOW || static_cast<MenuBarWindow*>(p->get())->CanGetFocus() ) )
+ {
+ pWindow = (*p).get();
+ break;
+ }
+ if( !pWindow ) // increment after test, otherwise first element is skipped
+ ++p;
+ }
+
+ if ( !bForward )
+ ::std::reverse( mTaskPanes.begin(), mTaskPanes.end() );
+
+ return pWindow;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/toolbox.cxx b/vcl/source/window/toolbox.cxx
new file mode 100644
index 000000000..64879c682
--- /dev/null
+++ b/vcl/source/window/toolbox.cxx
@@ -0,0 +1,4846 @@
+/* -*- 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 <vcl/toolbox.hxx>
+#include <vcl/commandinfoprovider.hxx>
+#include <vcl/event.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/help.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/settings.hxx>
+#include <vclstatuslistener.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <bitmaps.hlst>
+#include <toolbarvalue.hxx>
+
+#include <tools/poly.hxx>
+#include <svl/imageitm.hxx>
+#include <sal/log.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+
+#include <accel.hxx>
+#include <svdata.hxx>
+#include <window.h>
+#include <toolbox.h>
+#include <spin.hxx>
+#if defined(_WIN32)
+#include <svsys.h>
+#endif
+
+#include <cstdlib>
+#include <map>
+#include <string_view>
+#include <vector>
+#include <math.h>
+
+#include "impldockingwrapper.hxx"
+
+#define SMALLBUTTON_HSIZE 7
+#define SMALLBUTTON_VSIZE 7
+
+#define SMALLBUTTON_OFF_NORMAL_X 3
+#define SMALLBUTTON_OFF_NORMAL_Y 3
+
+#define TB_TEXTOFFSET 2
+#define TB_IMAGETEXTOFFSET 3
+#define TB_LINESPACING 3
+#define TB_SPIN_SIZE 14
+#define TB_SPIN_OFFSET 2
+#define TB_BORDER_OFFSET1 4
+#define TB_BORDER_OFFSET2 2
+#define TB_MAXLINES 5
+#define TB_MAXNOSCROLL 32765
+
+#define TB_DRAGWIDTH 8 // the default width of the drag grip
+
+#define TB_CALCMODE_HORZ 1
+#define TB_CALCMODE_VERT 2
+#define TB_CALCMODE_FLOAT 3
+
+#define TB_WBLINESIZING (WB_SIZEABLE | WB_DOCKABLE | WB_SCROLL)
+
+#define DOCK_LINEHSIZE (sal_uInt16(0x0001))
+#define DOCK_LINEVSIZE (sal_uInt16(0x0002))
+#define DOCK_LINERIGHT (sal_uInt16(0x1000))
+#define DOCK_LINEBOTTOM (sal_uInt16(0x2000))
+#define DOCK_LINELEFT (sal_uInt16(0x4000))
+#define DOCK_LINETOP (sal_uInt16(0x8000))
+#define DOCK_LINEOFFSET 3
+
+class ImplTBDragMgr
+{
+private:
+ VclPtr<ToolBox> mpDragBox;
+ Point maMouseOff;
+ tools::Rectangle maRect;
+ tools::Rectangle maStartRect;
+ Accelerator maAccel;
+ sal_uInt16 mnLineMode;
+ ToolBox::ImplToolItems::size_type mnStartLines;
+
+ ImplTBDragMgr(const ImplTBDragMgr&) = delete;
+ ImplTBDragMgr& operator=(const ImplTBDragMgr&) = delete;
+
+public:
+ ImplTBDragMgr();
+
+ void StartDragging( ToolBox* pDragBox, const Point& rPos, const tools::Rectangle& rRect, sal_uInt16 nLineMode );
+ void Dragging( const Point& rPos );
+ void EndDragging( bool bOK = true );
+ DECL_LINK( SelectHdl, Accelerator&, void );
+};
+
+
+static ImplTBDragMgr* ImplGetTBDragMgr()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData->maCtrlData.mpTBDragMgr )
+ pSVData->maCtrlData.mpTBDragMgr = new ImplTBDragMgr;
+ return pSVData->maCtrlData.mpTBDragMgr;
+}
+
+int ToolBox::ImplGetDragWidth( const vcl::Window& rWindow, bool bHorz )
+{
+ return ImplGetDragWidth(*rWindow.GetOutDev(), bHorz);
+}
+int ToolBox::ImplGetDragWidth( const vcl::RenderContext& rRenderContext, bool bHorz )
+{
+ int nWidth = TB_DRAGWIDTH;
+ if( rRenderContext.IsNativeControlSupported( ControlType::Toolbar, ControlPart::Entire ) )
+ {
+
+ ImplControlValue aControlValue;
+ tools::Rectangle aContent, aBound;
+ tools::Rectangle aArea( Point(), rRenderContext.GetOutputSizePixel() );
+
+ if ( rRenderContext.GetNativeControlRegion(ControlType::Toolbar,
+ bHorz ? ControlPart::ThumbVert : ControlPart::ThumbHorz,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ nWidth = bHorz ? aContent.GetWidth() : aContent.GetHeight();
+ }
+ }
+
+ // increase the hit area of the drag handle according to DPI scale factor
+ nWidth *= rRenderContext.GetDPIScaleFactor();
+
+ return nWidth;
+}
+
+int ToolBox::ImplGetDragWidth() const
+{
+ return ToolBox::ImplGetDragWidth( *this, mbHorz );
+}
+
+static ButtonType determineButtonType( ImplToolItem const * pItem, ButtonType defaultType )
+{
+ ButtonType tmpButtonType = defaultType;
+ ToolBoxItemBits nBits = pItem->mnBits & ( ToolBoxItemBits::TEXT_ONLY | ToolBoxItemBits::ICON_ONLY );
+ if ( nBits != ToolBoxItemBits::NONE ) // item has custom setting
+ {
+ tmpButtonType = ButtonType::SYMBOLTEXT;
+ if ( nBits == ToolBoxItemBits::TEXT_ONLY )
+ tmpButtonType = ButtonType::TEXT;
+ else if ( nBits == ToolBoxItemBits::ICON_ONLY )
+ tmpButtonType = ButtonType::SYMBOLONLY;
+ }
+ return tmpButtonType;
+}
+
+void ToolBox::ImplUpdateDragArea() const
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ {
+ if ( ImplIsFloatingMode() || pWrapper->IsLocked() )
+ pWrapper->SetDragArea( tools::Rectangle() );
+ else
+ {
+ if( meAlign == WindowAlign::Top || meAlign == WindowAlign::Bottom )
+ pWrapper->SetDragArea( tools::Rectangle( 0, 0, ImplGetDragWidth(), GetOutputSizePixel().Height() ) );
+ else
+ pWrapper->SetDragArea( tools::Rectangle( 0, 0, GetOutputSizePixel().Width(), ImplGetDragWidth() ) );
+ }
+ }
+}
+
+void ToolBox::ImplCalcBorder( WindowAlign eAlign, tools::Long& rLeft, tools::Long& rTop,
+ tools::Long& rRight, tools::Long& rBottom ) const
+{
+ if( ImplIsFloatingMode() || !(mnWinStyle & WB_BORDER) )
+ {
+ // no border in floating mode
+ rLeft = rTop = rRight = rBottom = 0;
+ return;
+ }
+
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+
+ // reserve DragArea only for dockable toolbars
+ int dragwidth = ( pWrapper && !pWrapper->IsLocked() ) ? ImplGetDragWidth() : 0;
+
+ // no shadow border for dockable toolbars and toolbars with WB_NOSHADOW bit set, e.g. Calc's formulabar
+ int borderwidth = ( pWrapper || mnWinStyle & WB_NOSHADOW ) ? 0 : 2;
+
+ if ( eAlign == WindowAlign::Top )
+ {
+ rLeft = borderwidth+dragwidth;
+ rTop = borderwidth;
+ rRight = borderwidth;
+ rBottom = 0;
+ }
+ else if ( eAlign == WindowAlign::Left )
+ {
+ rLeft = borderwidth;
+ rTop = borderwidth+dragwidth;
+ rRight = 0;
+ rBottom = borderwidth;
+ }
+ else if ( eAlign == WindowAlign::Bottom )
+ {
+ rLeft = borderwidth+dragwidth;
+ rTop = 0;
+ rRight = borderwidth;
+ rBottom = borderwidth;
+ }
+ else
+ {
+ rLeft = 0;
+ rTop = borderwidth+dragwidth;
+ rRight = borderwidth;
+ rBottom = borderwidth;
+ }
+}
+
+void ToolBox::ImplCheckUpdate()
+{
+ // remove any pending invalidates to avoid
+ // have them triggered when paint is locked (see mpData->mbIsPaintLocked)
+ // which would result in erasing the background only and not painting any items
+ // this must not be done when we're already in Paint()
+
+ // this is only required for transparent toolbars (see ImplDrawTransparentBackground() )
+ if( !IsBackground() && HasPaintEvent() && !IsInPaint() )
+ PaintImmediately();
+}
+
+void ToolBox::ImplDrawGrip(vcl::RenderContext& rRenderContext,
+ const tools::Rectangle &aDragArea, int nDragWidth, WindowAlign eAlign, bool bHorz)
+{
+ bool bNativeOk = false;
+ const ControlPart ePart = bHorz ? ControlPart::ThumbVert : ControlPart::ThumbHorz;
+ const Size aSz( rRenderContext.GetOutputSizePixel() );
+ if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ePart))
+ {
+ ToolbarValue aToolbarValue;
+ aToolbarValue.maGripRect = aDragArea;
+
+ tools::Rectangle aCtrlRegion(Point(), aSz);
+
+ bNativeOk = rRenderContext.DrawNativeControl( ControlType::Toolbar, ePart,
+ aCtrlRegion, ControlState::ENABLED, aToolbarValue, OUString() );
+ }
+
+ if( bNativeOk )
+ return;
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.SetFillColor(rStyleSettings.GetShadowColor());
+
+ float fScaleFactor = rRenderContext.GetDPIScaleFactor();
+
+ if (eAlign == WindowAlign::Top || eAlign == WindowAlign::Bottom)
+ {
+ int height = static_cast<int>(0.6 * aSz.Height() + 0.5);
+ int i = (aSz.Height() - height) / 2;
+ height += i;
+ while (i <= height)
+ {
+ int x = nDragWidth / 2;
+ rRenderContext.DrawEllipse(tools::Rectangle(Point(x, i), Size(2 * fScaleFactor, 2 * fScaleFactor)));
+ i += 4 * fScaleFactor;
+ }
+ }
+ else
+ {
+ int width = static_cast<int>(0.6 * aSz.Width() + 0.5);
+ int i = (aSz.Width() - width) / 2;
+ width += i;
+ while (i <= width)
+ {
+ int y = nDragWidth / 2;
+ rRenderContext.DrawEllipse(tools::Rectangle(Point(i, y), Size(2 * fScaleFactor, 2 * fScaleFactor)));
+ i += 4 * fScaleFactor;
+ }
+ }
+}
+
+void ToolBox::ImplDrawGrip(vcl::RenderContext& rRenderContext)
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper(this);
+ if( pWrapper && !pWrapper->GetDragArea().IsEmpty() )
+ {
+ // execute pending paint requests
+ ImplCheckUpdate();
+ ImplDrawGrip( rRenderContext, pWrapper->GetDragArea(),
+ ImplGetDragWidth(), meAlign, mbHorz );
+ }
+}
+
+void ToolBox::ImplDrawGradientBackground(vcl::RenderContext& rRenderContext)
+{
+ // draw a nice gradient
+
+ Color startCol, endCol;
+ const StyleSettings rSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ startCol = rSettings.GetFaceGradientColor();
+ endCol = rSettings.GetFaceColor();
+ if (rSettings.GetHighContrastMode())
+ // no 'extreme' gradient when high contrast
+ startCol = endCol;
+
+ Gradient g;
+ g.SetAngle(Degree10(mbHorz ? 0 : 900));
+ g.SetStyle(GradientStyle::Linear);
+
+ g.SetStartColor(startCol);
+ g.SetEndColor(endCol);
+
+ bool bLineColor = rRenderContext.IsLineColor();
+ Color aOldCol = rRenderContext.GetLineColor();
+ rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetShadowColor());
+
+ Size aFullSz(GetOutputSizePixel());
+ Size aLineSz(aFullSz);
+
+ // use the linesize only when floating
+ // full window height is used when docked (single line)
+ if (ImplIsFloatingMode())
+ {
+ tools::Long nLineSize;
+ if (mbHorz)
+ {
+ nLineSize = mnMaxItemHeight;
+ if (mnWinHeight > mnMaxItemHeight)
+ nLineSize = mnWinHeight;
+
+ aLineSz.setHeight( nLineSize );
+ }
+ else
+ {
+ nLineSize = mnMaxItemWidth;
+ aLineSz.setWidth( nLineSize );
+ }
+ }
+
+ tools::Long nLeft, nTop, nRight, nBottom;
+ ImplCalcBorder(meAlign, nLeft, nTop, nRight, nBottom);
+
+ Size aTopLineSz(aLineSz);
+ Size aBottomLineSz(aLineSz);
+
+ if (mnWinStyle & WB_BORDER)
+ {
+ if (mbHorz)
+ {
+ aTopLineSz.AdjustHeight(TB_BORDER_OFFSET2 + nTop );
+ aBottomLineSz.AdjustHeight(TB_BORDER_OFFSET2 + nBottom );
+
+ if (mnCurLines == 1)
+ aTopLineSz.AdjustHeight(TB_BORDER_OFFSET2 + nBottom );
+ }
+ else
+ {
+ aTopLineSz.AdjustWidth(TB_BORDER_OFFSET1 + nLeft );
+ aBottomLineSz.AdjustWidth(TB_BORDER_OFFSET1 + nRight );
+
+ if (mnCurLines == 1)
+ aTopLineSz.AdjustWidth(TB_BORDER_OFFSET1 + nLeft );
+ }
+ }
+
+ if (mbLineSpacing)
+ {
+ if (mbHorz)
+ {
+ aLineSz.AdjustHeight(TB_LINESPACING );
+ if (mnCurLines > 1)
+ aTopLineSz.AdjustHeight(TB_LINESPACING );
+ }
+ else
+ {
+ aLineSz.AdjustWidth(TB_LINESPACING );
+ if (mnCurLines > 1)
+ aTopLineSz.AdjustWidth(TB_LINESPACING );
+ }
+ }
+
+ if (mbHorz)
+ {
+ tools::Long y = 0;
+
+ rRenderContext.DrawGradient(tools::Rectangle(0, y, aTopLineSz.Width(), y + aTopLineSz.Height()), g);
+ y += aTopLineSz.Height();
+
+ while (y < (mnDY - aBottomLineSz.Height()))
+ {
+ rRenderContext.DrawGradient(tools::Rectangle(0, y, aLineSz.Width(), y + aLineSz.Height()), g);
+ y += aLineSz.Height();
+ }
+
+ rRenderContext.DrawGradient(tools::Rectangle(0, y, aBottomLineSz.Width(), y + aBottomLineSz.Height()), g);
+ }
+ else
+ {
+ tools::Long x = 0;
+
+ rRenderContext.DrawGradient(tools::Rectangle(x, 0, x + aTopLineSz.Width(), aTopLineSz.Height()), g);
+ x += aTopLineSz.Width();
+
+ while (x < (mnDX - aBottomLineSz.Width()))
+ {
+ rRenderContext.DrawGradient(tools::Rectangle(x, 0, x + aLineSz.Width(), aLineSz.Height()), g);
+ x += aLineSz.Width();
+ }
+
+ rRenderContext.DrawGradient(tools::Rectangle( x, 0, x + aBottomLineSz.Width(), aBottomLineSz.Height()), g);
+ }
+
+ if( bLineColor )
+ rRenderContext.SetLineColor( aOldCol );
+
+}
+
+bool ToolBox::ImplDrawNativeBackground(vcl::RenderContext& rRenderContext) const
+{
+ // use NWF
+ tools::Rectangle aCtrlRegion(Point(), GetOutputSizePixel());
+
+ return rRenderContext.DrawNativeControl( ControlType::Toolbar, mbHorz ? ControlPart::DrawBackgroundHorz : ControlPart::DrawBackgroundVert,
+ aCtrlRegion, ControlState::ENABLED, ImplControlValue(), OUString() );
+}
+
+void ToolBox::ImplDrawTransparentBackground(const vcl::Region &rRegion)
+{
+ // just invalidate to trigger paint of the parent
+ const bool bOldPaintLock = mpData->mbIsPaintLocked;
+ mpData->mbIsPaintLocked = true;
+
+ // send an invalidate to the first opaque parent and invalidate the whole hierarchy from there (noclipchildren)
+ Invalidate(rRegion, InvalidateFlags::Update | InvalidateFlags::NoClipChildren);
+
+ mpData->mbIsPaintLocked = bOldPaintLock;
+}
+
+void ToolBox::ImplDrawConstantBackground(vcl::RenderContext& rRenderContext, const vcl::Region &rRegion, bool bIsInPopupMode)
+{
+ // draw a constant color
+ if (!bIsInPopupMode)
+ {
+ // default background
+ rRenderContext.Erase(rRegion.GetBoundRect());
+ }
+ else
+ {
+ // use different color in popupmode
+ const StyleSettings rSettings = rRenderContext.GetSettings().GetStyleSettings();
+ Wallpaper aWallpaper(rSettings.GetFaceGradientColor());
+ rRenderContext.DrawWallpaper(rRegion.GetBoundRect(), aWallpaper);
+ }
+}
+
+void ToolBox::ImplDrawBackground(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ // execute pending paint requests
+ ImplCheckUpdate();
+
+ ImplDockingWindowWrapper* pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper(this);
+ bool bIsInPopupMode = ImplIsInPopupMode();
+
+ vcl::Region aPaintRegion(rRect);
+
+ // make sure we do not invalidate/erase too much
+ if (IsInPaint())
+ aPaintRegion.Intersect(GetOutDev()->GetActiveClipRegion());
+
+ rRenderContext.Push(vcl::PushFlags::CLIPREGION);
+ rRenderContext.IntersectClipRegion( aPaintRegion );
+
+ if (!pWrapper)
+ {
+ // no gradient for ordinary toolbars (not dockable)
+ if( !IsBackground() && !IsInPaint() )
+ ImplDrawTransparentBackground(aPaintRegion);
+ else
+ ImplDrawConstantBackground(rRenderContext, aPaintRegion, bIsInPopupMode);
+ }
+ else
+ {
+ // toolbars known to the dockingmanager will be drawn using NWF or a gradient
+ // docked toolbars are transparent and NWF is already used in the docking area which is their common background
+ // so NWF is used here for floating toolbars only
+ bool bNativeOk = false;
+ if( ImplIsFloatingMode() && rRenderContext.IsNativeControlSupported( ControlType::Toolbar, ControlPart::Entire) )
+ bNativeOk = ImplDrawNativeBackground(rRenderContext);
+ if (!bNativeOk)
+ {
+ const StyleSettings rSetting = Application::GetSettings().GetStyleSettings();
+ const bool isHeader = GetAlign() == WindowAlign::Top && !rSetting.GetPersonaHeader().IsEmpty();
+ const bool isFooter = GetAlign() == WindowAlign::Bottom && !rSetting.GetPersonaFooter().IsEmpty();
+ if (!IsBackground() || isHeader || isFooter)
+ {
+ if (!IsInPaint())
+ ImplDrawTransparentBackground(aPaintRegion);
+ }
+ else
+ ImplDrawGradientBackground(rRenderContext);
+ }
+ }
+
+ // restore clip region
+ rRenderContext.Pop();
+}
+
+void ToolBox::ImplErase(vcl::RenderContext& rRenderContext, const tools::Rectangle &rRect, bool bHighlight, bool bHasOpenPopup)
+{
+ // the background of non NWF buttons is painted in a constant color
+ // to have the same highlight color (transparency in DrawSelectionBackground())
+ // items with open popups will also painted using a constant color
+ if (!mpData->mbNativeButtons &&
+ (bHighlight || !(GetStyle() & WB_3DLOOK)))
+ {
+ if (GetStyle() & WB_3DLOOK)
+ {
+ rRenderContext.Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR);
+ rRenderContext.SetLineColor();
+ if (bHasOpenPopup)
+ // choose the same color as the popup will use
+ rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetFaceGradientColor());
+ else
+ rRenderContext.SetFillColor(COL_WHITE);
+
+ rRenderContext.DrawRect(rRect);
+ rRenderContext.Pop();
+ }
+ else
+ ImplDrawBackground(rRenderContext, rRect);
+ }
+ else
+ ImplDrawBackground(rRenderContext, rRect);
+}
+
+void ToolBox::ImplDrawBorder(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ tools::Long nDX = mnDX;
+ tools::Long nDY = mnDY;
+
+ ImplDockingWindowWrapper* pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper(this);
+
+ // draw borders for ordinary toolbars only (not dockable), do not draw borders for toolbars with WB_NOSHADOW bit set,
+ // e.g. Calc's formulabar
+
+ if( pWrapper || mnWinStyle & WB_NOSHADOW )
+ return;
+
+ if (meAlign == WindowAlign::Bottom)
+ {
+ // draw bottom border
+ rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() );
+ rRenderContext.DrawLine( Point( 0, nDY-2 ), Point( nDX-1, nDY-2 ) );
+ rRenderContext.SetLineColor( rStyleSettings.GetLightColor() );
+ rRenderContext.DrawLine( Point( 0, nDY-1 ), Point( nDX-1, nDY-1 ) );
+ }
+ else
+ {
+ // draw top border
+ rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() );
+ rRenderContext.DrawLine( Point( 0, 0 ), Point( nDX-1, 0 ) );
+ rRenderContext.SetLineColor( rStyleSettings.GetLightColor() );
+ rRenderContext.DrawLine( Point( 0, 1 ), Point( nDX-1, 1 ) );
+
+ if (meAlign == WindowAlign::Left || meAlign == WindowAlign::Right)
+ {
+ if (meAlign == WindowAlign::Left)
+ {
+ // draw left-bottom border
+ rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() );
+ rRenderContext.DrawLine( Point( 0, 0 ), Point( 0, nDY-1 ) );
+ rRenderContext.DrawLine( Point( 0, nDY-2 ), Point( nDX-1, nDY-2 ) );
+ rRenderContext.SetLineColor( rStyleSettings.GetLightColor() );
+ rRenderContext.DrawLine( Point( 1, 1 ), Point( 1, nDY-3 ) );
+ rRenderContext.DrawLine( Point( 0, nDY-1 ), Point( nDX-1, nDY-1 ) );
+ }
+ else
+ {
+ // draw right-bottom border
+ rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() );
+ rRenderContext.DrawLine( Point( nDX-2, 0 ), Point( nDX-2, nDY-3 ) );
+ rRenderContext.DrawLine( Point( 0, nDY-2 ), Point( nDX-2, nDY-2 ) );
+ rRenderContext.SetLineColor( rStyleSettings.GetLightColor() );
+ rRenderContext.DrawLine( Point( nDX-1, 0 ), Point( nDX-1, nDY-1 ) );
+ rRenderContext.DrawLine( Point( 0, nDY-1 ), Point( nDX-1, nDY-1 ) );
+ }
+ }
+ }
+
+ if ( meAlign == WindowAlign::Bottom || meAlign == WindowAlign::Top )
+ {
+ // draw right border
+ rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() );
+ rRenderContext.DrawLine( Point( nDX-2, 0 ), Point( nDX-2, nDY-1 ) );
+ rRenderContext.SetLineColor( rStyleSettings.GetLightColor() );
+ rRenderContext.DrawLine( Point( nDX-1, 0 ), Point( nDX-1, nDY-1 ) );
+ }
+}
+
+static bool ImplIsFixedControl( const ImplToolItem *pItem )
+{
+ return ( pItem->mpWindow &&
+ (pItem->mbNonInteractiveWindow ||
+ pItem->mpWindow->GetType() == WindowType::FIXEDTEXT ||
+ pItem->mpWindow->GetType() == WindowType::FIXEDLINE ||
+ pItem->mpWindow->GetType() == WindowType::GROUPBOX) );
+}
+
+const ImplToolItem *ToolBox::ImplGetFirstClippedItem() const
+{
+ for (auto & item : mpData->m_aItems)
+ {
+ if( item.IsClipped() )
+ return &item;
+ }
+ return nullptr;
+}
+
+Size ToolBox::ImplCalcSize( ImplToolItems::size_type nCalcLines, sal_uInt16 nCalcMode )
+{
+ sal_Int32 nMax;
+ tools::Long nLeft = 0;
+ tools::Long nTop = 0;
+ tools::Long nRight = 0;
+ tools::Long nBottom = 0;
+ Size aSize;
+ WindowAlign eOldAlign = meAlign;
+ bool bOldHorz = mbHorz;
+ bool bOldAssumeDocked = mpData->mbAssumeDocked;
+ bool bOldAssumeFloating = mpData->mbAssumeFloating;
+
+ if ( nCalcMode )
+ {
+ bool bOldFloatingMode = ImplIsFloatingMode();
+
+ mpData->mbAssumeDocked = false;
+ mpData->mbAssumeFloating = false;
+
+ if ( nCalcMode == TB_CALCMODE_HORZ )
+ {
+ mpData->mbAssumeDocked = true; // force non-floating mode during calculation
+ ImplCalcBorder( WindowAlign::Top, nLeft, nTop, nRight, nBottom );
+ mbHorz = true;
+ if ( mbHorz != bOldHorz )
+ meAlign = WindowAlign::Top;
+ }
+ else if ( nCalcMode == TB_CALCMODE_VERT )
+ {
+ mpData->mbAssumeDocked = true; // force non-floating mode during calculation
+ ImplCalcBorder( WindowAlign::Left, nLeft, nTop, nRight, nBottom );
+ mbHorz = false;
+ if ( mbHorz != bOldHorz )
+ meAlign = WindowAlign::Left;
+ }
+ else if ( nCalcMode == TB_CALCMODE_FLOAT )
+ {
+ mpData->mbAssumeFloating = true; // force non-floating mode during calculation
+ nLeft = nTop = nRight = nBottom = 0;
+ mbHorz = true;
+ if ( mbHorz != bOldHorz )
+ meAlign = WindowAlign::Top;
+ }
+
+ if ( (meAlign != eOldAlign) || (mbHorz != bOldHorz) ||
+ (ImplIsFloatingMode() != bOldFloatingMode ) )
+ mbCalc = true;
+ }
+ else
+ ImplCalcBorder( meAlign, nLeft, nTop, nRight, nBottom );
+
+ ImplCalcItem();
+
+ if( !nCalcMode && ImplIsFloatingMode() )
+ {
+ aSize = ImplCalcFloatSize( nCalcLines );
+ }
+ else
+ {
+ if ( mbHorz )
+ {
+ if ( mnWinHeight > mnMaxItemHeight )
+ aSize.setHeight( nCalcLines * mnWinHeight );
+ else
+ aSize.setHeight( nCalcLines * mnMaxItemHeight );
+
+ if ( mbLineSpacing )
+ aSize.AdjustHeight((nCalcLines-1)*TB_LINESPACING );
+
+ if ( mnWinStyle & WB_BORDER )
+ aSize.AdjustHeight((TB_BORDER_OFFSET2*2) + nTop + nBottom );
+
+ nMax = 0;
+ ImplCalcBreaks( TB_MAXNOSCROLL, &nMax, mbHorz );
+ if ( nMax )
+ aSize.AdjustWidth(nMax );
+
+ if ( mnWinStyle & WB_BORDER )
+ aSize.AdjustWidth((TB_BORDER_OFFSET1*2) + nLeft + nRight );
+ }
+ else
+ {
+ aSize.setWidth( nCalcLines * mnMaxItemWidth );
+
+ if ( mbLineSpacing )
+ aSize.AdjustWidth((nCalcLines-1)*TB_LINESPACING );
+
+ if ( mnWinStyle & WB_BORDER )
+ aSize.AdjustWidth((TB_BORDER_OFFSET2*2) + nLeft + nRight );
+
+ nMax = 0;
+ ImplCalcBreaks( TB_MAXNOSCROLL, &nMax, mbHorz );
+ if ( nMax )
+ aSize.AdjustHeight(nMax );
+
+ if ( mnWinStyle & WB_BORDER )
+ aSize.AdjustHeight((TB_BORDER_OFFSET1*2) + nTop + nBottom );
+ }
+ }
+ // restore previous values
+ if ( nCalcMode )
+ {
+ mpData->mbAssumeDocked = bOldAssumeDocked;
+ mpData->mbAssumeFloating = bOldAssumeFloating;
+ if ( (meAlign != eOldAlign) || (mbHorz != bOldHorz) )
+ {
+ meAlign = eOldAlign;
+ mbHorz = bOldHorz;
+ mbCalc = true;
+ }
+ }
+
+ return aSize;
+}
+
+void ToolBox::ImplCalcFloatSizes()
+{
+ if ( !maFloatSizes.empty() )
+ return;
+
+ // calculate the minimal size, i.e. where the biggest item just fits
+ tools::Long nCalcSize = 0;
+
+ for (auto const& item : mpData->m_aItems)
+ {
+ if ( item.mbVisible )
+ {
+ if ( item.mpWindow )
+ {
+ tools::Long nTempSize = item.mpWindow->GetSizePixel().Width();
+ if ( nTempSize > nCalcSize )
+ nCalcSize = nTempSize;
+ }
+ else
+ {
+ if( item.maItemSize.Width() > nCalcSize )
+ nCalcSize = item.maItemSize.Width();
+ }
+ }
+ }
+
+ // calc an upper bound for ImplCalcBreaks below
+ tools::Long upperBoundWidth = nCalcSize * mpData->m_aItems.size();
+
+ ImplToolItems::size_type nLines;
+ ImplToolItems::size_type nCalcLines;
+ ImplToolItems::size_type nTempLines;
+ sal_Int32 nMaxLineWidth;
+ nCalcLines = ImplCalcBreaks( nCalcSize, &nMaxLineWidth, true );
+
+ maFloatSizes.reserve( nCalcLines );
+
+ nTempLines = nLines = nCalcLines;
+ while ( nLines )
+ {
+ tools::Long nHeight = ImplCalcSize( nTempLines, TB_CALCMODE_FLOAT ).Height();
+
+ ImplToolSize aSize;
+ aSize.mnWidth = nMaxLineWidth+(TB_BORDER_OFFSET1*2);
+ aSize.mnHeight = nHeight;
+ aSize.mnLines = nTempLines;
+ maFloatSizes.push_back( aSize );
+ nLines--;
+ if ( nLines )
+ {
+ do
+ {
+ nCalcSize += mnMaxItemWidth;
+ nTempLines = ImplCalcBreaks( nCalcSize, &nMaxLineWidth, true );
+ }
+ while ((nCalcSize < upperBoundWidth) && (nLines < nTempLines)); // implies nTempLines>1
+ if ( nTempLines < nLines )
+ nLines = nTempLines;
+ }
+ }
+}
+
+Size ToolBox::ImplCalcFloatSize( ImplToolItems::size_type& rLines )
+{
+ ImplCalcFloatSizes();
+
+ if ( !rLines )
+ {
+ rLines = mnFloatLines;
+ if ( !rLines )
+ rLines = mnLines;
+ }
+
+ sal_uInt16 i = 0;
+ while ( i + 1u < maFloatSizes.size() && rLines < maFloatSizes[i].mnLines )
+ {
+ i++;
+ }
+
+ Size aSize( maFloatSizes[i].mnWidth, maFloatSizes[i].mnHeight );
+ rLines = maFloatSizes[i].mnLines;
+
+ return aSize;
+}
+
+void ToolBox::ImplCalcMinMaxFloatSize( Size& rMinSize, Size& rMaxSize )
+{
+ ImplCalcFloatSizes();
+
+ sal_uInt16 i = 0;
+ rMinSize = Size( maFloatSizes[i].mnWidth, maFloatSizes[i].mnHeight );
+ rMaxSize = Size( maFloatSizes[i].mnWidth, maFloatSizes[i].mnHeight );
+ while ( ++i < maFloatSizes.size() )
+ {
+ if( maFloatSizes[i].mnWidth < rMinSize.Width() )
+ rMinSize.setWidth( maFloatSizes[i].mnWidth );
+ if( maFloatSizes[i].mnHeight < rMinSize.Height() )
+ rMinSize.setHeight( maFloatSizes[i].mnHeight );
+
+ if( maFloatSizes[i].mnWidth > rMaxSize.Width() )
+ rMaxSize.setWidth( maFloatSizes[i].mnWidth );
+ if( maFloatSizes[i].mnHeight > rMaxSize.Height() )
+ rMaxSize.setHeight( maFloatSizes[i].mnHeight );
+ }
+}
+
+void ToolBox::ImplSetMinMaxFloatSize()
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ Size aMinSize, aMaxSize;
+ ImplCalcMinMaxFloatSize( aMinSize, aMaxSize );
+ if( pWrapper )
+ {
+ pWrapper->SetMinOutputSizePixel( aMinSize );
+ pWrapper->SetMaxOutputSizePixel( aMaxSize );
+ pWrapper->ShowMenuTitleButton( bool( GetMenuType() & ToolBoxMenuType::Customize) );
+ }
+ else
+ {
+ // TODO: change SetMinOutputSizePixel to be not inline
+ SetMinOutputSizePixel( aMinSize );
+ SetMaxOutputSizePixel( aMaxSize );
+ }
+}
+
+ToolBox::ImplToolItems::size_type ToolBox::ImplCalcLines( tools::Long nToolSize ) const
+{
+ tools::Long nLineHeight;
+
+ if ( mbHorz )
+ {
+ if ( mnWinHeight > mnMaxItemHeight )
+ nLineHeight = mnWinHeight;
+ else
+ nLineHeight = mnMaxItemHeight;
+ }
+ else
+ nLineHeight = mnMaxItemWidth;
+
+ if ( mnWinStyle & WB_BORDER )
+ nToolSize -= TB_BORDER_OFFSET2*2;
+
+ if ( mbLineSpacing )
+ {
+ nLineHeight += TB_LINESPACING;
+ nToolSize += TB_LINESPACING;
+ }
+
+ // #i91917# always report at least one line
+ tools::Long nLines = nToolSize/nLineHeight;
+ if( nLines < 1 )
+ nLines = 1;
+
+ return nLines;
+}
+
+sal_uInt16 ToolBox::ImplTestLineSize( const Point& rPos ) const
+{
+ if ( !ImplIsFloatingMode() &&
+ (!mbScroll || (mnLines > 1) || (mnCurLines > mnVisLines)) )
+ {
+ WindowAlign eAlign = GetAlign();
+
+ if ( eAlign == WindowAlign::Left )
+ {
+ if ( rPos.X() > mnDX-DOCK_LINEOFFSET )
+ return DOCK_LINEHSIZE | DOCK_LINERIGHT;
+ }
+ else if ( eAlign == WindowAlign::Top )
+ {
+ if ( rPos.Y() > mnDY-DOCK_LINEOFFSET )
+ return DOCK_LINEVSIZE | DOCK_LINEBOTTOM;
+ }
+ else if ( eAlign == WindowAlign::Right )
+ {
+ if ( rPos.X() < DOCK_LINEOFFSET )
+ return DOCK_LINEHSIZE | DOCK_LINELEFT;
+ }
+ else if ( eAlign == WindowAlign::Bottom )
+ {
+ if ( rPos.Y() < DOCK_LINEOFFSET )
+ return DOCK_LINEVSIZE | DOCK_LINETOP;
+ }
+ }
+
+ return 0;
+}
+
+void ToolBox::ImplLineSizing( const Point& rPos, tools::Rectangle& rRect, sal_uInt16 nLineMode )
+{
+ bool bHorz;
+ tools::Long nOneLineSize;
+ tools::Long nCurSize;
+ tools::Long nMaxSize;
+ tools::Long nSize;
+ Size aSize;
+
+ if ( nLineMode & DOCK_LINERIGHT )
+ {
+ nCurSize = rPos.X() - rRect.Left();
+ bHorz = false;
+ }
+ else if ( nLineMode & DOCK_LINEBOTTOM )
+ {
+ nCurSize = rPos.Y() - rRect.Top();
+ bHorz = true;
+ }
+ else if ( nLineMode & DOCK_LINELEFT )
+ {
+ nCurSize = rRect.Right() - rPos.X();
+ bHorz = false;
+ }
+ else if ( nLineMode & DOCK_LINETOP )
+ {
+ nCurSize = rRect.Bottom() - rPos.Y();
+ bHorz = true;
+ }
+ else {
+ OSL_FAIL( "ImplLineSizing: Trailing else" );
+ nCurSize = 0;
+ bHorz = false;
+ }
+
+ Size aWinSize = GetSizePixel();
+ ImplToolItems::size_type nMaxLines = std::max(mnLines, mnCurLines);
+ if ( nMaxLines > TB_MAXLINES )
+ nMaxLines = TB_MAXLINES;
+ if ( bHorz )
+ {
+ nOneLineSize = ImplCalcSize( 1 ).Height();
+ nMaxSize = - 20;
+ if ( nMaxSize < aWinSize.Height() )
+ nMaxSize = aWinSize.Height();
+ }
+ else
+ {
+ nOneLineSize = ImplCalcSize( 1 ).Width();
+ nMaxSize = - 20;
+ if ( nMaxSize < aWinSize.Width() )
+ nMaxSize = aWinSize.Width();
+ }
+
+ ImplToolItems::size_type i = 1;
+ if ( nCurSize <= nOneLineSize )
+ nSize = nOneLineSize;
+ else
+ {
+ nSize = 0;
+ while ( (nSize < nCurSize) && (i < nMaxLines) )
+ {
+ i++;
+ aSize = ImplCalcSize( i );
+ if ( bHorz )
+ nSize = aSize.Height();
+ else
+ nSize = aSize.Width();
+ if ( nSize > nMaxSize )
+ {
+ i--;
+ aSize = ImplCalcSize( i );
+ if ( bHorz )
+ nSize = aSize.Height();
+ else
+ nSize = aSize.Width();
+ break;
+ }
+ }
+ }
+
+ if ( nLineMode & DOCK_LINERIGHT )
+ rRect.SetRight( rRect.Left()+nSize-1 );
+ else if ( nLineMode & DOCK_LINEBOTTOM )
+ rRect.SetBottom( rRect.Top()+nSize-1 );
+ else if ( nLineMode & DOCK_LINELEFT )
+ rRect.SetLeft( rRect.Right()-nSize );
+ else
+ rRect.SetTop( rRect.Bottom()-nSize );
+
+ mnDockLines = i;
+}
+
+ImplTBDragMgr::ImplTBDragMgr()
+ : mpDragBox(nullptr)
+ , mnLineMode(0)
+ , mnStartLines(0)
+{
+ maAccel.InsertItem( KEY_RETURN, vcl::KeyCode( KEY_RETURN ) );
+ maAccel.InsertItem( KEY_ESCAPE, vcl::KeyCode( KEY_ESCAPE ) );
+ maAccel.SetSelectHdl( LINK( this, ImplTBDragMgr, SelectHdl ) );
+}
+
+void ImplTBDragMgr::StartDragging( ToolBox* pToolBox,
+ const Point& rPos, const tools::Rectangle& rRect,
+ sal_uInt16 nDragLineMode )
+{
+ mpDragBox = pToolBox;
+ pToolBox->CaptureMouse();
+ pToolBox->mbDragging = true;
+ Application::InsertAccel( &maAccel );
+
+ mnLineMode = nDragLineMode;
+ mnStartLines = pToolBox->mnDockLines;
+
+ // calculate MouseOffset
+ maMouseOff.setX( rRect.Left() - rPos.X() );
+ maMouseOff.setY( rRect.Top() - rPos.Y() );
+ maRect = rRect;
+ maStartRect = rRect;
+ pToolBox->ShowTracking( maRect );
+}
+
+void ImplTBDragMgr::Dragging( const Point& rPos )
+{
+ mpDragBox->ImplLineSizing( rPos, maRect, mnLineMode );
+ Point aOff = mpDragBox->OutputToScreenPixel( Point() );
+ maRect.Move( aOff.X(), aOff.Y() );
+ mpDragBox->Docking( rPos, maRect );
+ maRect.Move( -aOff.X(), -aOff.Y() );
+ mpDragBox->ShowTracking( maRect );
+}
+
+void ImplTBDragMgr::EndDragging( bool bOK )
+{
+ mpDragBox->HideTracking();
+ if (mpDragBox->IsMouseCaptured())
+ mpDragBox->ReleaseMouse();
+ mpDragBox->mbDragging = false;
+ Application::RemoveAccel( &maAccel );
+
+ if ( !bOK )
+ {
+ mpDragBox->mnDockLines = mnStartLines;
+ mpDragBox->EndDocking( maStartRect, false );
+ }
+ else
+ mpDragBox->EndDocking( maRect, false );
+ mnStartLines = 0;
+
+ mpDragBox = nullptr;
+}
+
+IMPL_LINK( ImplTBDragMgr, SelectHdl, Accelerator&, rAccel, void )
+{
+ if ( rAccel.GetCurItemId() == KEY_ESCAPE )
+ EndDragging( false );
+ else
+ EndDragging();
+}
+
+void ToolBox::ImplInitToolBoxData()
+{
+ // initialize variables
+ ImplGetWindowImpl()->mbToolBox = true;
+ mpData.reset(new ImplToolBoxPrivateData);
+
+ mpFloatWin = nullptr;
+ mnDX = 0;
+ mnDY = 0;
+ mnMaxItemWidth = 0;
+ mnMaxItemHeight = 0;
+ mnWinHeight = 0;
+ mnLeftBorder = 0;
+ mnTopBorder = 0;
+ mnRightBorder = 0;
+ mnBottomBorder = 0;
+ mnLastResizeDY = 0;
+ mnHighItemId = ToolBoxItemId(0);
+ mnCurItemId = ToolBoxItemId(0);
+ mnDownItemId = ToolBoxItemId(0);
+ mnCurPos = ITEM_NOTFOUND;
+ mnLines = 1;
+ mnCurLine = 1;
+ mnCurLines = 1;
+ mnVisLines = 1;
+ mnFloatLines = 0;
+ mnDockLines = 0;
+ mnMouseModifier = 0;
+ mbDrag = false;
+ mbUpper = false;
+ mbLower = false;
+ mbIn = false;
+ mbCalc = true;
+ mbFormat = false;
+ mbFullPaint = false;
+ mbHorz = true;
+ mbScroll = false;
+ mbLastFloatMode = false;
+ mbCustomize = false;
+ mbDragging = false;
+ mbIsKeyEvent = false;
+ mbChangingHighlight = false;
+ mbImagesMirrored = false;
+ mbLineSpacing = false;
+ mbIsArranged = false;
+ meButtonType = ButtonType::SYMBOLONLY;
+ meAlign = WindowAlign::Top;
+ meDockAlign = WindowAlign::Top;
+ meLastStyle = PointerStyle::Arrow;
+ mnWinStyle = 0;
+ meLayoutMode = ToolBoxLayoutMode::Normal;
+ meTextPosition = ToolBoxTextPosition::Right;
+ mnLastFocusItemId = ToolBoxItemId(0);
+ mnActivateCount = 0;
+ mnImagesRotationAngle = 0_deg10;
+
+ mpStatusListener = new VclStatusListener<ToolBox>(this, ".uno:ImageOrientation");
+ mpStatusListener->startListening();
+
+ mpIdle.reset(new Idle("vcl::ToolBox maIdle update"));
+ mpIdle->SetPriority( TaskPriority::RESIZE );
+ mpIdle->SetInvokeHandler( LINK( this, ToolBox, ImplUpdateHdl ) );
+
+ // set timeout and handler for dropdown items
+ mpData->maDropdownTimer.SetTimeout( 250 );
+ mpData->maDropdownTimer.SetInvokeHandler( LINK( this, ToolBox, ImplDropdownLongClickHdl ) );
+}
+
+void ToolBox::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ // initialize variables
+ mbScroll = (nStyle & WB_SCROLL) != 0;
+ mnWinStyle = nStyle;
+
+ DockingWindow::ImplInit( pParent, nStyle & ~WB_BORDER );
+
+ // dockingwindow's ImplInit removes some bits, so restore them here to allow keyboard handling for toolbars
+ ImplGetWindowImpl()->mnStyle |= WB_TABSTOP|WB_NODIALOGCONTROL; // always set WB_TABSTOP for ToolBars
+ ImplGetWindowImpl()->mnStyle &= ~WB_DIALOGCONTROL;
+
+ ImplInitSettings(true, true, true);
+}
+
+void ToolBox::ApplyForegroundSettings(vcl::RenderContext& rRenderContext, const StyleSettings& rStyleSettings)
+{
+ Color aColor;
+ if (IsControlForeground())
+ aColor = GetControlForeground();
+ else if (Window::GetStyle() & WB_3DLOOK)
+ aColor = rStyleSettings.GetButtonTextColor();
+ else
+ aColor = rStyleSettings.GetWindowTextColor();
+ rRenderContext.SetTextColor(aColor);
+ rRenderContext.SetTextFillColor();
+}
+
+void ToolBox::ApplyBackgroundSettings(vcl::RenderContext& rRenderContext, const StyleSettings& rStyleSettings)
+{
+ if (IsControlBackground())
+ {
+ rRenderContext.SetBackground(GetControlBackground());
+ SetPaintTransparent(false);
+ SetParentClipMode();
+ }
+ else
+ {
+ if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Entire)
+ || (GetAlign() == WindowAlign::Top && !Application::GetSettings().GetStyleSettings().GetPersonaHeader().IsEmpty())
+ || (GetAlign() == WindowAlign::Bottom && !Application::GetSettings().GetStyleSettings().GetPersonaFooter().IsEmpty()))
+ {
+ rRenderContext.SetBackground();
+ rRenderContext.SetTextColor(rStyleSettings.GetToolTextColor());
+ SetPaintTransparent(true);
+ SetParentClipMode(ParentClipMode::NoClip);
+ mpData->maDisplayBackground = Wallpaper(rStyleSettings.GetFaceColor());
+ }
+ else
+ {
+ Color aColor;
+ if (Window::GetStyle() & WB_3DLOOK)
+ aColor = rStyleSettings.GetFaceColor();
+ else
+ aColor = rStyleSettings.GetWindowColor();
+ rRenderContext.SetBackground(aColor);
+ SetPaintTransparent(false);
+ SetParentClipMode();
+ }
+ }
+}
+
+void ToolBox::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ mpData->mbNativeButtons = rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Button);
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ ApplyControlFont(rRenderContext, rStyleSettings.GetToolFont());
+ ApplyForegroundSettings(rRenderContext, rStyleSettings);
+ ApplyBackgroundSettings(rRenderContext, rStyleSettings);
+}
+
+void ToolBox::ImplInitSettings(bool bFont, bool bForeground, bool bBackground)
+{
+ mpData->mbNativeButtons = IsNativeControlSupported( ControlType::Toolbar, ControlPart::Button );
+
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+
+ if (bFont)
+ ApplyControlFont(*GetOutDev(), rStyleSettings.GetToolFont());
+ if (bForeground || bFont)
+ ApplyForegroundSettings(*GetOutDev(), rStyleSettings);
+ if (bBackground)
+ {
+ ApplyBackgroundSettings(*GetOutDev(), rStyleSettings);
+ EnableChildTransparentMode(IsPaintTransparent());
+ }
+}
+
+void ToolBox::doDeferredInit(WinBits nBits)
+{
+ VclPtr<vcl::Window> pParent = mpDialogParent;
+ mpDialogParent = nullptr;
+ ImplInit(pParent, nBits);
+ mbIsDeferredInit = false;
+}
+
+void ToolBox::queue_resize(StateChangedType eReason)
+{
+ Window::queue_resize(eReason);
+}
+
+ToolBox::ToolBox( vcl::Window* pParent, WinBits nStyle ) :
+ DockingWindow( WindowType::TOOLBOX, "vcl::ToolBox maLayoutIdle" )
+{
+ ImplInitToolBoxData();
+ ImplInit( pParent, nStyle );
+}
+
+ToolBox::ToolBox(vcl::Window* pParent, const OString& rID,
+ const OUString& rUIXMLDescription, const css::uno::Reference<css::frame::XFrame> &rFrame)
+ : DockingWindow(WindowType::TOOLBOX, "vcl::ToolBox maLayoutIdle")
+{
+ ImplInitToolBoxData();
+
+ loadUI(pParent, rID, rUIXMLDescription, rFrame);
+
+ // calculate size of floating windows and switch if the
+ // toolbox is initially in floating mode
+ if ( ImplIsFloatingMode() )
+ mbHorz = true;
+ else
+ Resize();
+
+ if (!(GetStyle() & WB_HIDE))
+ Show();
+}
+
+ToolBox::~ToolBox()
+{
+ disposeOnce();
+}
+
+void ToolBox::dispose()
+{
+ // #103005# make sure our activate/deactivate balance is right
+ while( mnActivateCount > 0 )
+ Deactivate();
+
+ // terminate popupmode if the floating window is
+ // still connected
+ if ( mpFloatWin )
+ mpFloatWin->EndPopupMode( FloatWinPopupEndFlags::Cancel );
+ mpFloatWin = nullptr;
+
+ // delete private data
+ mpData.reset();
+
+ ImplSVData* pSVData = ImplGetSVData();
+ delete pSVData->maCtrlData.mpTBDragMgr;
+ pSVData->maCtrlData.mpTBDragMgr = nullptr;
+
+ if (mpStatusListener.is())
+ mpStatusListener->dispose();
+
+ mpFloatWin.clear();
+
+ mpIdle.reset();
+
+ DockingWindow::dispose();
+}
+
+ImplToolItem* ToolBox::ImplGetItem( ToolBoxItemId nItemId ) const
+{
+ if (!mpData)
+ return nullptr;
+
+ for (auto & item : mpData->m_aItems)
+ {
+ if ( item.mnId == nItemId )
+ return &item;
+ }
+
+ return nullptr;
+}
+
+static void ImplAddButtonBorder( tools::Long &rWidth, tools::Long& rHeight, bool bNativeButtons )
+{
+ rWidth += SMALLBUTTON_HSIZE;
+ rHeight += SMALLBUTTON_VSIZE;
+
+ if( bNativeButtons )
+ {
+ // give more border space for rounded buttons
+ rWidth += 2;
+ rHeight += 4;
+ }
+}
+
+bool ToolBox::ImplCalcItem()
+{
+ // recalc required ?
+ if ( !mbCalc )
+ return false;
+
+ OutputDevice *pDefault = Application::GetDefaultDevice();
+ float fScaleFactor = pDefault ? pDefault->GetDPIScaleFactor() : 1.0;
+
+ tools::Long nDefWidth;
+ tools::Long nDefHeight;
+ tools::Long nMaxWidth = 0;
+ tools::Long nMaxHeight = 0;
+ tools::Long nMinWidth = 6;
+ tools::Long nMinHeight = 6;
+ tools::Long nDropDownArrowWidth = TB_DROPDOWNARROWWIDTH * fScaleFactor;
+#ifdef IOS
+ nDropDownArrowWidth *= 3;
+#endif
+
+ // set defaults if image or text is needed but empty
+ nDefWidth = GetDefaultImageSize().Width();
+ nDefHeight = GetDefaultImageSize().Height();
+
+ mnWinHeight = 0;
+ // determine minimum size necessary in NWF
+ {
+ tools::Rectangle aRect( Point( 0, 0 ), Size( nMinWidth, nMinHeight ) );
+ tools::Rectangle aReg( aRect );
+ ImplControlValue aVal;
+ tools::Rectangle aNativeBounds, aNativeContent;
+ if( IsNativeControlSupported( ControlType::Toolbar, ControlPart::Button ) )
+ {
+ if( GetNativeControlRegion( ControlType::Toolbar, ControlPart::Button,
+ aReg,
+ ControlState::ENABLED | ControlState::ROLLOVER,
+ aVal,
+ aNativeBounds, aNativeContent ) )
+ {
+ aRect = aNativeBounds;
+ if( aRect.GetWidth() > nMinWidth )
+ nMinWidth = aRect.GetWidth();
+ if( aRect.GetHeight() > nMinHeight )
+ nMinHeight = aRect.GetHeight();
+ if( nDropDownArrowWidth < nMinWidth )
+ nDropDownArrowWidth = nMinWidth;
+ if( nMinWidth > mpData->mnMenuButtonWidth )
+ mpData->mnMenuButtonWidth = nMinWidth;
+ else if( nMinWidth < TB_MENUBUTTON_SIZE )
+ mpData->mnMenuButtonWidth = TB_MENUBUTTON_SIZE;
+ }
+ }
+
+ // also calculate the area for comboboxes, drop down list boxes and spinfields
+ // as these are often inserted into toolboxes; set mnWinHeight to the
+ // greater of those values to prevent toolbar flickering (#i103385#)
+ aRect = tools::Rectangle( Point( 0, 0 ), Size( nMinWidth, nMinHeight ) );
+ aReg = aRect;
+ if( GetNativeControlRegion( ControlType::Combobox, ControlPart::Entire,
+ aReg,
+ ControlState::ENABLED | ControlState::ROLLOVER,
+ aVal,
+ aNativeBounds, aNativeContent ) )
+ {
+ aRect = aNativeBounds;
+ if( aRect.GetHeight() > mnWinHeight )
+ mnWinHeight = aRect.GetHeight();
+ }
+ aRect = tools::Rectangle( Point( 0, 0 ), Size( nMinWidth, nMinHeight ) );
+ aReg = aRect;
+ if( GetNativeControlRegion( ControlType::Listbox, ControlPart::Entire,
+ aReg,
+ ControlState::ENABLED | ControlState::ROLLOVER,
+ aVal,
+ aNativeBounds, aNativeContent ) )
+ {
+ aRect = aNativeBounds;
+ if( aRect.GetHeight() > mnWinHeight )
+ mnWinHeight = aRect.GetHeight();
+ }
+ aRect = tools::Rectangle( Point( 0, 0 ), Size( nMinWidth, nMinHeight ) );
+ aReg = aRect;
+ if( GetNativeControlRegion( ControlType::Spinbox, ControlPart::Entire,
+ aReg,
+ ControlState::ENABLED | ControlState::ROLLOVER,
+ aVal,
+ aNativeBounds, aNativeContent ) )
+ {
+ aRect = aNativeBounds;
+ if( aRect.GetHeight() > mnWinHeight )
+ mnWinHeight = aRect.GetHeight();
+ }
+ }
+
+ if ( ! mpData->m_aItems.empty() )
+ {
+ for (auto & item : mpData->m_aItems)
+ {
+ item.mbVisibleText = false; // indicates if text will definitely be drawn, influences dropdown pos
+
+ if ( item.meType == ToolBoxItemType::BUTTON )
+ {
+ bool bImage;
+ bool bText;
+
+ // check if image and/or text exists
+ bImage = !!item.maImage;
+ bText = !item.maText.isEmpty();
+ ButtonType tmpButtonType = determineButtonType( &item, meButtonType ); // default to toolbox setting
+ if ( bImage || bText )
+ {
+
+ item.mbEmptyBtn = false;
+
+ if ( tmpButtonType == ButtonType::SYMBOLONLY )
+ {
+ // we're drawing images only
+ if ( bImage || !bText )
+ {
+ item.maItemSize = item.maImage.GetSizePixel();
+ }
+ else
+ {
+ item.maItemSize = Size( GetOutDev()->GetCtrlTextWidth( item.maText )+TB_TEXTOFFSET,
+ GetTextHeight() );
+ item.mbVisibleText = true;
+ }
+ }
+ else if ( tmpButtonType == ButtonType::TEXT )
+ {
+ // we're drawing text only
+ if ( bText || !bImage )
+ {
+ item.maItemSize = Size( GetOutDev()->GetCtrlTextWidth( item.maText )+TB_TEXTOFFSET,
+ GetTextHeight() );
+ item.mbVisibleText = true;
+ }
+ else
+ {
+ item.maItemSize = item.maImage.GetSizePixel();
+ }
+ }
+ else
+ {
+ // we're drawing images and text
+ item.maItemSize.setWidth( bText ? GetOutDev()->GetCtrlTextWidth( item.maText )+TB_TEXTOFFSET : 0 );
+ item.maItemSize.setHeight( bText ? GetTextHeight() : 0 );
+
+ if ( meTextPosition == ToolBoxTextPosition::Right )
+ {
+ // leave space between image and text
+ if( bText )
+ item.maItemSize.AdjustWidth(TB_IMAGETEXTOFFSET );
+
+ // image and text side by side
+ item.maItemSize.AdjustWidth(item.maImage.GetSizePixel().Width() );
+ if ( item.maImage.GetSizePixel().Height() > item.maItemSize.Height() )
+ item.maItemSize.setHeight( item.maImage.GetSizePixel().Height() );
+ }
+ else
+ {
+ // leave space between image and text
+ if( bText )
+ item.maItemSize.AdjustHeight(TB_IMAGETEXTOFFSET );
+
+ // text below image
+ item.maItemSize.AdjustHeight(item.maImage.GetSizePixel().Height() );
+ if ( item.maImage.GetSizePixel().Width() > item.maItemSize.Width() )
+ item.maItemSize.setWidth( item.maImage.GetSizePixel().Width() );
+ }
+
+ item.mbVisibleText = bText;
+ }
+ }
+ else
+ { // no image and no text
+ item.maItemSize = Size( nDefWidth, nDefHeight );
+ item.mbEmptyBtn = true;
+ }
+
+ // save the content size
+ item.maContentSize = item.maItemSize;
+
+ // if required, take window height into consideration
+ if ( item.mpWindow )
+ {
+ tools::Long nHeight = item.mpWindow->GetSizePixel().Height();
+ if ( nHeight > mnWinHeight )
+ mnWinHeight = nHeight;
+ }
+
+ // add in drop down arrow
+ if( item.mnBits & ToolBoxItemBits::DROPDOWN )
+ {
+ item.maItemSize.AdjustWidth(nDropDownArrowWidth );
+ item.mnDropDownArrowWidth = nDropDownArrowWidth;
+ }
+
+ // text items will be rotated in vertical mode
+ // -> swap width and height
+ if( item.mbVisibleText && !mbHorz )
+ {
+ tools::Long tmp = item.maItemSize.Width();
+ item.maItemSize.setWidth( item.maItemSize.Height() );
+ item.maItemSize.setHeight( tmp );
+
+ tmp = item.maContentSize.Width();
+ item.maContentSize.setWidth( item.maContentSize.Height() );
+ item.maContentSize.setHeight( tmp );
+ }
+ }
+ else if ( item.meType == ToolBoxItemType::SPACE )
+ {
+ item.maItemSize = Size( nDefWidth, nDefHeight );
+ item.maContentSize = item.maItemSize;
+ }
+
+ if ( item.meType == ToolBoxItemType::BUTTON || item.meType == ToolBoxItemType::SPACE )
+ {
+ // add borders
+ tools::Long w = item.maItemSize.Width();
+ tools::Long h = item.maItemSize.Height();
+ ImplAddButtonBorder( w, h, mpData->mbNativeButtons );
+ item.maItemSize.setWidth(w);
+ item.maItemSize.setHeight(h);
+
+ if( item.meType == ToolBoxItemType::BUTTON )
+ {
+ tools::Long nMinW = std::max(nMinWidth, item.maMinimalItemSize.Width());
+ tools::Long nMinH = std::max(nMinHeight, item.maMinimalItemSize.Height());
+
+ tools::Long nGrowContentWidth = 0;
+ tools::Long nGrowContentHeight = 0;
+
+ if( item.maItemSize.Width() < nMinW )
+ {
+ nGrowContentWidth = nMinW - item.maItemSize.Width();
+ item.maItemSize.setWidth( nMinW );
+ }
+ if( item.maItemSize.Height() < nMinH )
+ {
+ nGrowContentHeight = nMinH - item.maItemSize.Height();
+ item.maItemSize.setHeight( nMinH );
+ }
+
+ // grow the content size by the additional available space
+ item.maContentSize.AdjustWidth(nGrowContentWidth );
+ item.maContentSize.AdjustHeight(nGrowContentHeight );
+ }
+
+ // keep track of max item size
+ if ( item.maItemSize.Width() > nMaxWidth )
+ nMaxWidth = item.maItemSize.Width();
+ if ( item.maItemSize.Height() > nMaxHeight )
+ nMaxHeight = item.maItemSize.Height();
+ }
+ }
+ }
+ else
+ {
+ nMaxWidth = nDefWidth;
+ nMaxHeight = nDefHeight;
+
+ ImplAddButtonBorder( nMaxWidth, nMaxHeight, mpData->mbNativeButtons );
+ }
+
+ if( !ImplIsFloatingMode() && GetToolboxButtonSize() != ToolBoxButtonSize::DontCare
+ && ( meTextPosition == ToolBoxTextPosition::Right ) )
+ {
+ // make sure all vertical toolbars have the same width and horizontal have the same height
+ // this depends on the used button sizes
+ // as this is used for alignment of multiple toolbars
+ // it is only required for docked toolbars
+
+ tools::Long nFixedWidth = nDefWidth+nDropDownArrowWidth;
+ tools::Long nFixedHeight = nDefHeight;
+ ImplAddButtonBorder( nFixedWidth, nFixedHeight, mpData->mbNativeButtons );
+
+ if( mbHorz )
+ nMaxHeight = nFixedHeight;
+ else
+ nMaxWidth = nFixedWidth;
+ }
+
+ mbCalc = false;
+ mbFormat = true;
+
+ // do we have to recalc the sizes ?
+ if ( (nMaxWidth != mnMaxItemWidth) || (nMaxHeight != mnMaxItemHeight) )
+ {
+ mnMaxItemWidth = nMaxWidth;
+ mnMaxItemHeight = nMaxHeight;
+
+ return true;
+ }
+ else
+ return false;
+}
+
+ToolBox::ImplToolItems::size_type ToolBox::ImplCalcBreaks( tools::Long nWidth, sal_Int32* pMaxLineWidth, bool bCalcHorz ) const
+{
+ sal_uLong nLineStart = 0;
+ sal_uLong nGroupStart = 0;
+ tools::Long nLineWidth = 0;
+ tools::Long nCurWidth;
+ tools::Long nLastGroupLineWidth = 0;
+ tools::Long nMaxLineWidth = 0;
+ ImplToolItems::size_type nLines = 1;
+ bool bWindow;
+ bool bBreak = false;
+ tools::Long nWidthTotal = nWidth;
+ tools::Long nMenuWidth = 0;
+
+ // when docked the menubutton will be in the first line
+ if( IsMenuEnabled() && !ImplIsFloatingMode() )
+ nMenuWidth = mpData->maMenubuttonItem.maItemSize.Width();
+
+ // we need to know which item is the last visible one to be able to add
+ // the menu width in case we are unable to show all the items
+ ImplToolItems::iterator it, lastVisible;
+ for ( it = mpData->m_aItems.begin(); it != mpData->m_aItems.end(); ++it )
+ {
+ if ( it->mbVisible )
+ lastVisible = it;
+ }
+
+ it = mpData->m_aItems.begin();
+ while ( it != mpData->m_aItems.end() )
+ {
+ it->mbBreak = bBreak;
+ bBreak = false;
+
+ if ( it->mbVisible )
+ {
+ bWindow = false;
+ bBreak = false;
+ nCurWidth = 0;
+
+ if ( it->meType == ToolBoxItemType::BUTTON || it->meType == ToolBoxItemType::SPACE )
+ {
+ if ( bCalcHorz )
+ nCurWidth = it->maItemSize.Width();
+ else
+ nCurWidth = it->maItemSize.Height();
+
+ if ( it->mpWindow && bCalcHorz )
+ {
+ tools::Long nWinItemWidth = it->mpWindow->GetSizePixel().Width();
+ if ( !mbScroll || (nWinItemWidth <= nWidthTotal) )
+ {
+ nCurWidth = nWinItemWidth;
+ bWindow = true;
+ }
+ else
+ {
+ if ( it->mbEmptyBtn )
+ {
+ nCurWidth = 0;
+ }
+ }
+ }
+
+ // in case we are able to show all the items, we do not want
+ // to show the toolbar's menu; otherwise yes
+ if ( ( ( it == lastVisible ) && (nLineWidth+nCurWidth > nWidthTotal) && mbScroll ) ||
+ ( ( it != lastVisible ) && (nLineWidth+nCurWidth+nMenuWidth > nWidthTotal) && mbScroll ) )
+ bBreak = true;
+ }
+ else if ( it->meType == ToolBoxItemType::SEPARATOR )
+ {
+ nCurWidth = it->mnSepSize;
+ if ( !ImplIsFloatingMode() && ( it != lastVisible ) && (nLineWidth+nCurWidth+nMenuWidth > nWidthTotal) )
+ bBreak = true;
+ }
+ // treat breaks as separators, except when using old style toolbars (ie. no menu button)
+ else if ( (it->meType == ToolBoxItemType::BREAK) && !IsMenuEnabled() )
+ bBreak = true;
+
+ if ( bBreak )
+ {
+ nLines++;
+
+ // Add break before the entire group or take group apart?
+ if ( (it->meType == ToolBoxItemType::BREAK) ||
+ (nLineStart == nGroupStart) )
+ {
+ if ( nLineWidth > nMaxLineWidth )
+ nMaxLineWidth = nLineWidth;
+
+ nLineWidth = 0;
+ nLineStart = it - mpData->m_aItems.begin();
+ nGroupStart = nLineStart;
+ it->mbBreak = true;
+ bBreak = false;
+ }
+ else
+ {
+ if ( nLastGroupLineWidth > nMaxLineWidth )
+ nMaxLineWidth = nLastGroupLineWidth;
+
+ // if the break is added before the group, set it to
+ // beginning of line and re-calculate
+ nLineWidth = 0;
+ nLineStart = nGroupStart;
+ it = mpData->m_aItems.begin() + nGroupStart;
+ continue;
+ }
+ }
+ else
+ {
+ if( ImplIsFloatingMode() || !IsMenuEnabled() ) // no group breaking when being docked single-line
+ {
+ if ( (it->meType != ToolBoxItemType::BUTTON) || bWindow )
+ {
+ // found separator or break
+ nLastGroupLineWidth = nLineWidth;
+ nGroupStart = it - mpData->m_aItems.begin();
+ if ( !bWindow )
+ nGroupStart++;
+ }
+ }
+ }
+
+ nLineWidth += nCurWidth;
+ }
+
+ ++it;
+ }
+
+ if ( pMaxLineWidth )
+ {
+ if ( nLineWidth > nMaxLineWidth )
+ nMaxLineWidth = nLineWidth;
+
+ if( ImplIsFloatingMode() && !ImplIsInPopupMode() )
+ {
+ // leave enough space to display buttons in the decoration
+ tools::Long aMinWidth = 2 * GetSettings().GetStyleSettings().GetFloatTitleHeight();
+ if( nMaxLineWidth < aMinWidth )
+ nMaxLineWidth = aMinWidth;
+ }
+ *pMaxLineWidth = nMaxLineWidth;
+ }
+
+ return nLines;
+}
+
+Size ToolBox::ImplGetOptimalFloatingSize()
+{
+ if( !ImplIsFloatingMode() )
+ return Size();
+
+ Size aCurrentSize( mnDX, mnDY );
+ Size aSize1( aCurrentSize );
+ Size aSize2( aCurrentSize );
+
+ // try to preserve current height
+
+ // calc number of floating lines for current window height
+ ImplToolItems::size_type nFloatLinesHeight = ImplCalcLines( mnDY );
+ // calc window size according to this number
+ aSize1 = ImplCalcFloatSize( nFloatLinesHeight );
+
+ if( aCurrentSize == aSize1 )
+ return aSize1;
+
+ // try to preserve current width
+
+ tools::Long nLineHeight = std::max( mnWinHeight, mnMaxItemHeight );
+ int nBorderX = 2*TB_BORDER_OFFSET1 + mnLeftBorder + mnRightBorder;
+ int nBorderY = 2*TB_BORDER_OFFSET2 + mnTopBorder + mnBottomBorder;
+ Size aSz( aCurrentSize );
+ sal_Int32 maxX;
+ ImplToolItems::size_type nLines = ImplCalcBreaks( aSz.Width()-nBorderX, &maxX, mbHorz );
+
+ ImplToolItems::size_type manyLines = 1000;
+ Size aMinimalFloatSize = ImplCalcFloatSize( manyLines );
+
+ aSz.setHeight( nBorderY + nLineHeight * nLines );
+ // line space when more than one line
+ if ( mbLineSpacing )
+ aSz.AdjustHeight((nLines-1)*TB_LINESPACING );
+
+ aSz.setWidth( nBorderX + maxX );
+
+ // avoid clipping of any items
+ if( aSz.Width() < aMinimalFloatSize.Width() )
+ aSize2 = ImplCalcFloatSize( nLines );
+ else
+ aSize2 = aSz;
+
+ if( aCurrentSize == aSize2 )
+ return aSize2;
+
+ // set the size with the smallest delta as the current size
+ tools::Long dx1 = std::abs( mnDX - aSize1.Width() );
+ tools::Long dy1 = std::abs( mnDY - aSize1.Height() );
+
+ tools::Long dx2 = std::abs( mnDX - aSize2.Width() );
+ tools::Long dy2 = std::abs( mnDY - aSize2.Height() );
+
+ if( dx1*dy1 < dx2*dy2 )
+ aCurrentSize = aSize1;
+ else
+ aCurrentSize = aSize2;
+
+ return aCurrentSize;
+}
+
+namespace
+{
+void lcl_hideDoubleSeparators( ToolBox::ImplToolItems& rItems )
+{
+ bool bLastSep( true );
+ ToolBox::ImplToolItems::iterator it;
+ for ( it = rItems.begin(); it != rItems.end(); ++it )
+ {
+ if ( it->meType == ToolBoxItemType::SEPARATOR )
+ {
+ it->mbVisible = false;
+ if ( !bLastSep )
+ {
+ // check if any visible items have to appear behind it
+ if (std::any_of(it + 1, rItems.end(), [](const ImplToolItem& rItem) {
+ return (rItem.meType == ToolBoxItemType::BUTTON) && rItem.mbVisible; }))
+ it->mbVisible = true;
+ }
+ bLastSep = true;
+ }
+ else if ( it->mbVisible )
+ bLastSep = false;
+ }
+}
+}
+
+void ToolBox::ImplFormat( bool bResize )
+{
+ // Has to re-formatted
+ if ( !mbFormat )
+ return;
+
+ mpData->ImplClearLayoutData();
+
+ // recalculate positions and sizes
+ tools::Rectangle aEmptyRect;
+ tools::Long nLineSize;
+ tools::Long nLeft;
+ tools::Long nTop;
+ tools::Long nMax; // width of layoutarea in pixels
+ ImplToolItems::size_type nFormatLine;
+ bool bMustFullPaint;
+
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ bool bIsInPopupMode = ImplIsInPopupMode();
+
+ maFloatSizes.clear();
+
+ // compute border sizes
+ ImplCalcBorder( meAlign, mnLeftBorder, mnTopBorder, mnRightBorder, mnBottomBorder );
+
+ // update drag area (where the 'grip' will be placed)
+ tools::Rectangle aOldDragRect;
+ if( pWrapper )
+ aOldDragRect = pWrapper->GetDragArea();
+ ImplUpdateDragArea();
+
+ bMustFullPaint = ImplCalcItem();
+
+ // calculate new size during interactive resize or
+ // set computed size when formatting only
+ if ( ImplIsFloatingMode() )
+ {
+ if ( bResize )
+ mnFloatLines = ImplCalcLines( mnDY );
+ else
+ SetOutputSizePixel( ImplGetOptimalFloatingSize() );
+ }
+
+ // Horizontal
+ if ( mbHorz )
+ {
+ tools::Long nBottom;
+ // nLineSize: height of a single line, will fit highest item
+ nLineSize = mnMaxItemHeight;
+
+ if ( mnWinHeight > mnMaxItemHeight )
+ nLineSize = mnWinHeight;
+
+ if ( mbScroll )
+ {
+ nMax = mnDX;
+ mnVisLines = ImplCalcLines( mnDY );
+ }
+ else
+ {
+ // layout over all lines
+ mnVisLines = mnLines;
+ nMax = TB_MAXNOSCROLL;
+ }
+
+ // add in all border offsets
+ if ( mnWinStyle & WB_BORDER )
+ {
+ nLeft = TB_BORDER_OFFSET1 + mnLeftBorder;
+ nTop = TB_BORDER_OFFSET2 + mnTopBorder;
+ nBottom = TB_BORDER_OFFSET1 + mnBottomBorder;
+ nMax -= nLeft + TB_BORDER_OFFSET1 + mnRightBorder;
+ }
+ else
+ {
+ nLeft = 0;
+ nTop = 0;
+ nBottom = 0;
+ }
+
+ // adjust linesize if docked in single-line mode (i.e. when using a clipped item menu)
+ // we have to center all items in the window height
+ if( IsMenuEnabled() && !ImplIsFloatingMode() )
+ {
+ tools::Long nWinHeight = mnDY - nTop - nBottom;
+ if( nWinHeight > nLineSize )
+ nLineSize = nWinHeight;
+ }
+ }
+ else
+ {
+ tools::Long nRight;
+ nLineSize = mnMaxItemWidth;
+
+ if ( mbScroll )
+ {
+ mnVisLines = ImplCalcLines( mnDX );
+ nMax = mnDY;
+ }
+ else
+ {
+ mnVisLines = mnLines;
+ nMax = TB_MAXNOSCROLL;
+ }
+
+ if ( mnWinStyle & WB_BORDER )
+ {
+ nTop = TB_BORDER_OFFSET1 + mnTopBorder;
+ nLeft = TB_BORDER_OFFSET2 + mnLeftBorder;
+ nRight = TB_BORDER_OFFSET2 + mnRightBorder;
+ nMax -= nTop + TB_BORDER_OFFSET1 + mnBottomBorder;
+ }
+ else
+ {
+ nLeft = 0;
+ nTop = 0;
+ nRight = 0;
+ }
+
+ // adjust linesize if docked in single-line mode (i.e. when using a clipped item menu)
+ // we have to center all items in the window height
+ if( !ImplIsFloatingMode() && IsMenuEnabled() )
+ {
+ tools::Long nWinWidth = mnDX - nLeft - nRight;
+ if( nWinWidth > nLineSize )
+ nLineSize = nWinWidth;
+ }
+ }
+
+ // no calculation if the window has no size (nMax=0)
+ // non scrolling toolboxes must be computed though
+ if ( (nMax <= 0) && mbScroll )
+ {
+ mnVisLines = 1;
+ mnCurLine = 1;
+ mnCurLines = 1;
+
+ for (auto & item : mpData->m_aItems)
+ {
+ item.maRect = aEmptyRect;
+ }
+
+ maLowerRect = aEmptyRect;
+ maUpperRect = aEmptyRect;
+ }
+ else
+ {
+ // init start values
+ tools::Long nX = nLeft; // top-left offset
+ tools::Long nY = nTop;
+ nFormatLine = 1;
+
+ // save old scroll rectangles and reset them
+ tools::Rectangle aOldLowerRect = maLowerRect;
+ tools::Rectangle aOldUpperRect = maUpperRect;
+ tools::Rectangle aOldMenubuttonRect = mpData->maMenubuttonItem.maRect;
+ maUpperRect = aEmptyRect;
+ maLowerRect = aEmptyRect;
+ mpData->maMenubuttonItem.maRect = aEmptyRect;
+
+ // do we have any toolbox items at all ?
+ if ( !mpData->m_aItems.empty() || IsMenuEnabled() )
+ {
+ lcl_hideDoubleSeparators( mpData->m_aItems );
+
+ // compute line breaks and visible lines give the current window width (nMax)
+ // the break indicators will be stored within each item (it->mbBreak)
+ mnCurLines = ImplCalcBreaks( nMax, nullptr, mbHorz );
+
+ // check for scrollbar buttons or dropdown menu
+ // (if a menu is enabled, this will be used to store clipped
+ // items and no scroll buttons will appear)
+ if ( (!ImplIsFloatingMode() && (mnCurLines > mnVisLines) && mbScroll ) ||
+ IsMenuEnabled() )
+ {
+ // compute linebreaks again, incorporating scrollbar buttons
+ if( !IsMenuEnabled() )
+ {
+ nMax -= TB_SPIN_SIZE+TB_SPIN_OFFSET;
+ mnCurLines = ImplCalcBreaks( nMax, nullptr, mbHorz );
+ }
+
+ // compute scroll rectangles or menu button
+ if ( mbHorz )
+ {
+ if( IsMenuEnabled() && !ImplHasExternalMenubutton() && !bIsInPopupMode )
+ {
+ if( !ImplIsFloatingMode() )
+ {
+ mpData->maMenubuttonItem.maRect.SetRight( mnDX - 2 );
+ mpData->maMenubuttonItem.maRect.SetTop( nTop );
+ mpData->maMenubuttonItem.maRect.SetBottom( mnDY-mnBottomBorder-TB_BORDER_OFFSET2-1 );
+ }
+ else
+ {
+ mpData->maMenubuttonItem.maRect.SetRight( mnDX - mnRightBorder-TB_BORDER_OFFSET1-1 );
+ mpData->maMenubuttonItem.maRect.SetTop( nTop );
+ mpData->maMenubuttonItem.maRect.SetBottom( mnDY-mnBottomBorder-TB_BORDER_OFFSET2-1 );
+ }
+ mpData->maMenubuttonItem.maRect.SetLeft( mpData->maMenubuttonItem.maRect.Right() - mpData->mnMenuButtonWidth );
+ }
+ else
+ {
+ maUpperRect.SetLeft( nLeft+nMax+TB_SPIN_OFFSET );
+ maUpperRect.SetRight( maUpperRect.Left()+TB_SPIN_SIZE-1 );
+ maUpperRect.SetTop( nTop );
+ maLowerRect.SetBottom( mnDY-mnBottomBorder-TB_BORDER_OFFSET2-1 );
+ maLowerRect.SetLeft( maUpperRect.Left() );
+ maLowerRect.SetRight( maUpperRect.Right() );
+ maUpperRect.SetBottom( maUpperRect.Top() +
+ (maLowerRect.Bottom()-maUpperRect.Top())/2 );
+ maLowerRect.SetTop( maUpperRect.Bottom() );
+ }
+ }
+ else
+ {
+ if( IsMenuEnabled() && !ImplHasExternalMenubutton() && !bIsInPopupMode )
+ {
+ if( !ImplIsFloatingMode() )
+ {
+ mpData->maMenubuttonItem.maRect.SetBottom( mnDY - 2 );
+ mpData->maMenubuttonItem.maRect.SetLeft( nLeft );
+ mpData->maMenubuttonItem.maRect.SetRight( mnDX-mnRightBorder-TB_BORDER_OFFSET2-1 );
+ }
+ else
+ {
+ mpData->maMenubuttonItem.maRect.SetBottom( mnDY - mnBottomBorder-TB_BORDER_OFFSET1-1 );
+ mpData->maMenubuttonItem.maRect.SetLeft( nLeft );
+ mpData->maMenubuttonItem.maRect.SetRight( mnDX-mnRightBorder-TB_BORDER_OFFSET2-1 );
+ }
+ mpData->maMenubuttonItem.maRect.SetTop( mpData->maMenubuttonItem.maRect.Bottom() - mpData->mnMenuButtonWidth );
+ }
+ else
+ {
+ maUpperRect.SetTop( nTop+nMax+TB_SPIN_OFFSET );
+ maUpperRect.SetBottom( maUpperRect.Top()+TB_SPIN_SIZE-1 );
+ maUpperRect.SetLeft( nLeft );
+ maLowerRect.SetRight( mnDX-mnRightBorder-TB_BORDER_OFFSET2-1 );
+ maLowerRect.SetTop( maUpperRect.Top() );
+ maLowerRect.SetBottom( maUpperRect.Bottom() );
+ maUpperRect.SetRight( maUpperRect.Left() +
+ (maLowerRect.Right()-maUpperRect.Left())/2 );
+ maLowerRect.SetLeft( maUpperRect.Right() );
+ }
+ }
+ }
+
+ // no scrolling when there is a "more"-menu
+ // anything will "fit" in a single line then
+ if( IsMenuEnabled() )
+ mnCurLines = 1;
+
+ // determine the currently visible line
+ if ( mnVisLines >= mnCurLines )
+ mnCurLine = 1;
+ else if ( mnCurLine+mnVisLines-1 > mnCurLines )
+ mnCurLine = mnCurLines - (mnVisLines-1);
+
+ tools::Long firstItemCenter = 0;
+ for (auto & item : mpData->m_aItems)
+ {
+ item.mbShowWindow = false;
+
+ // check for line break and advance nX/nY accordingly
+ if ( item.mbBreak )
+ {
+ nFormatLine++;
+
+ // increment starting with the second line
+ if ( nFormatLine > mnCurLine )
+ {
+ if ( mbHorz )
+ {
+ nX = nLeft;
+ if ( mbLineSpacing )
+ nY += nLineSize+TB_LINESPACING;
+ else
+ nY += nLineSize;
+ }
+ else
+ {
+ nY = nTop;
+ if ( mbLineSpacing )
+ nX += nLineSize+TB_LINESPACING;
+ else
+ nX += nLineSize;
+ }
+ }
+ }
+
+ if ( !item.mbVisible || (nFormatLine < mnCurLine) ||
+ (nFormatLine > mnCurLine+mnVisLines-1) )
+ // item is not visible
+ item.maCalcRect = aEmptyRect;
+ else
+ {
+ // 1. determine current item width/height
+ // take window size and orientation into account, because this affects the size of item windows
+
+ Size aCurrentItemSize( item.GetSize( mbHorz, mbScroll, nMax, Size(mnMaxItemWidth, mnMaxItemHeight) ) );
+
+ // 2. position item rect and use size from step 1
+ // items will be centered horizontally (if mbHorz) or vertically
+ // advance nX and nY accordingly
+
+ if ( mbHorz )
+ {
+ // In special mode Locked horizontal positions of all items remain unchanged.
+
+ if ( mbIsArranged && meLayoutMode == ToolBoxLayoutMode::Locked && mnLines == 1 && item.maRect.Left() > 0 )
+ nX = item.maRect.Left();
+ item.maCalcRect.SetLeft( nX );
+
+ // In special mode Locked first item's vertical position remains unchanged. Consecutive items vertical
+ // positions are centered around first item's vertical position. If an item's height exceeds available
+ // space, item's vertical position remains unchanged too.
+
+ if ( mbIsArranged && meLayoutMode == ToolBoxLayoutMode::Locked && mnLines == 1 )
+ if ( firstItemCenter > 0 )
+ if ( firstItemCenter-aCurrentItemSize.Height()/2 > nY )
+ item.maCalcRect.SetTop( firstItemCenter-aCurrentItemSize.Height()/2 );
+ else
+ item.maCalcRect.SetTop( item.maRect.Top() );
+ else
+ {
+ item.maCalcRect.SetTop( item.maRect.Top() );
+ firstItemCenter = item.maRect.Top()+aCurrentItemSize.Height()/2;
+ }
+ else
+ item.maCalcRect.SetTop( nY+(nLineSize-aCurrentItemSize.Height())/2 );
+ item.maCalcRect.SetRight( nX+aCurrentItemSize.Width()-1 );
+ item.maCalcRect.SetBottom( item.maCalcRect.Top()+aCurrentItemSize.Height()-1 );
+ nX += aCurrentItemSize.Width();
+ }
+ else
+ {
+ item.maCalcRect.SetLeft( nX+(nLineSize-aCurrentItemSize.Width())/2 );
+ item.maCalcRect.SetTop( nY );
+ item.maCalcRect.SetRight( item.maCalcRect.Left()+aCurrentItemSize.Width()-1 );
+ item.maCalcRect.SetBottom( nY+aCurrentItemSize.Height()-1 );
+ nY += aCurrentItemSize.Height();
+ }
+ }
+
+ // position window items into calculated item rect
+ if ( item.mpWindow )
+ {
+ if ( item.mbShowWindow )
+ {
+ Point aPos( item.maCalcRect.Left(), item.maCalcRect.Top() );
+
+ assert( item.maCalcRect.Top() >= 0 );
+
+ item.mpWindow->SetPosPixel( aPos );
+ item.mpWindow->Show();
+ }
+ else
+ item.mpWindow->Hide();
+ }
+ } // end of loop over all items
+ mbIsArranged = true;
+ }
+ else
+ // we have no toolbox items
+ mnCurLines = 1;
+
+ if( IsMenuEnabled() && ImplIsFloatingMode() && !ImplHasExternalMenubutton() && !bIsInPopupMode )
+ {
+ // custom menu will be the last button in floating mode
+ ImplToolItem &rIt = mpData->maMenubuttonItem;
+
+ if ( mbHorz )
+ {
+ rIt.maRect.SetLeft( nX+TB_MENUBUTTON_OFFSET );
+ rIt.maRect.SetTop( nY );
+ rIt.maRect.SetRight( rIt.maRect.Left() + mpData->mnMenuButtonWidth );
+ rIt.maRect.SetBottom( nY+nLineSize-1 );
+ nX += rIt.maItemSize.Width();
+ }
+ else
+ {
+ rIt.maRect.SetLeft( nX );
+ rIt.maRect.SetTop( nY+TB_MENUBUTTON_OFFSET );
+ rIt.maRect.SetRight( nX+nLineSize-1 );
+ rIt.maRect.SetBottom( rIt.maRect.Top() + mpData->mnMenuButtonWidth );
+ nY += rIt.maItemSize.Height();
+ }
+ }
+
+ // if toolbox visible trigger paint for changed regions
+ if ( IsVisible() && !mbFullPaint )
+ {
+ if ( bMustFullPaint )
+ {
+ maPaintRect = tools::Rectangle( mnLeftBorder, mnTopBorder,
+ mnDX-mnRightBorder, mnDY-mnBottomBorder );
+ }
+ else
+ {
+ if ( aOldLowerRect != maLowerRect )
+ {
+ maPaintRect.Union( maLowerRect );
+ maPaintRect.Union( aOldLowerRect );
+ }
+ if ( aOldUpperRect != maUpperRect )
+ {
+ maPaintRect.Union( maUpperRect );
+ maPaintRect.Union( aOldUpperRect );
+ }
+ if ( aOldMenubuttonRect != mpData->maMenubuttonItem.maRect )
+ {
+ maPaintRect.Union( mpData->maMenubuttonItem.maRect );
+ maPaintRect.Union( aOldMenubuttonRect );
+ }
+ if ( pWrapper && aOldDragRect != pWrapper->GetDragArea() )
+ {
+ maPaintRect.Union( pWrapper->GetDragArea() );
+ maPaintRect.Union( aOldDragRect );
+ }
+
+ for (auto const& item : mpData->m_aItems)
+ {
+ if ( item.maRect != item.maCalcRect )
+ {
+ maPaintRect.Union( item.maRect );
+ maPaintRect.Union( item.maCalcRect );
+ }
+ }
+ }
+
+ Invalidate( maPaintRect );
+ }
+
+ // store the new calculated item rects
+ maPaintRect = aEmptyRect;
+ for (auto & item : mpData->m_aItems)
+ item.maRect = item.maCalcRect;
+ }
+
+ // indicate formatting is done
+ mbFormat = false;
+}
+
+IMPL_LINK_NOARG(ToolBox, ImplDropdownLongClickHdl, Timer *, void)
+{
+ if (mnCurPos == ITEM_NOTFOUND ||
+ !(mpData->m_aItems[ mnCurPos ].mnBits & ToolBoxItemBits::DROPDOWN))
+ return;
+
+ mpData->mbDropDownByKeyboard = false;
+ mpData->maDropdownClickHdl.Call( this );
+
+ // do not reset data if the dropdown handler opened a floating window
+ // see ImplFloatControl()
+ if( !mpFloatWin )
+ {
+ // no floater was opened
+ Deactivate();
+ InvalidateItem(mnCurPos);
+
+ mnCurPos = ITEM_NOTFOUND;
+ mnCurItemId = ToolBoxItemId(0);
+ mnDownItemId = ToolBoxItemId(0);
+ mnMouseModifier = 0;
+ mnHighItemId = ToolBoxItemId(0);
+ }
+}
+
+IMPL_LINK_NOARG(ToolBox, ImplUpdateHdl, Timer *, void)
+{
+
+ if( mbFormat && mpData )
+ ImplFormat();
+}
+
+static void ImplDrawMoreIndicator(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ const Image pImage(StockImage::Yes, CHEVRON);
+ Size aImageSize = pImage.GetSizePixel();
+ tools::Long x = rRect.Left() + (rRect.getWidth() - aImageSize.Width())/2;
+ tools::Long y = rRect.Top() + (rRect.getHeight() - aImageSize.Height())/2;
+ DrawImageFlags nImageStyle = DrawImageFlags::NONE;
+
+ rRenderContext.DrawImage(Point(x,y), pImage, nImageStyle);
+}
+
+static void ImplDrawDropdownArrow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rDropDownRect, bool bSetColor, bool bRotate )
+{
+ bool bLineColor = rRenderContext.IsLineColor();
+ bool bFillColor = rRenderContext.IsFillColor();
+ Color aOldFillColor = rRenderContext.GetFillColor();
+ Color aOldLineColor = rRenderContext.GetLineColor();
+ rRenderContext.SetLineColor();
+
+ if ( bSetColor )
+ {
+ if (rRenderContext.GetSettings().GetStyleSettings().GetFaceColor().IsDark())
+ rRenderContext.SetFillColor(COL_WHITE);
+ else
+ rRenderContext.SetFillColor(COL_BLACK);
+ }
+
+ tools::Polygon aPoly(4);
+
+ // the assumption is, that the width always specifies the size of the expected arrow.
+ const tools::Long nMargin = round(2 * rRenderContext.GetDPIScaleFactor());
+ const tools::Long nSize = rDropDownRect.getWidth() - 2 * nMargin;
+ const tools::Long nHalfSize = (nSize + 1) / 2;
+ const tools::Long x = rDropDownRect.Left() + nMargin + (bRotate ? (rDropDownRect.getWidth() - nHalfSize) / 2 : 0);
+ const tools::Long y = rDropDownRect.Top() + nMargin + (rDropDownRect.getHeight() - (bRotate ? nSize : nHalfSize)) / 2;
+
+ aPoly.SetPoint(Point(x, y), 0);
+ if (bRotate) // >
+ {
+ aPoly.SetPoint(Point(x, y + nSize), 1);
+ aPoly.SetPoint(Point(x + nHalfSize, y + nHalfSize), 2);
+ }
+ else // v
+ {
+ aPoly.SetPoint(Point(x + nHalfSize, y + nHalfSize), 1);
+ aPoly.SetPoint(Point(x + nSize, y), 2);
+ }
+ aPoly.SetPoint(Point(x, y), 3);
+
+ auto aaflags = rRenderContext.GetAntialiasing();
+ rRenderContext.SetAntialiasing(AntialiasingFlags::Enable);
+ rRenderContext.DrawPolygon( aPoly );
+ rRenderContext.SetAntialiasing(aaflags);
+
+ if( bFillColor )
+ rRenderContext.SetFillColor(aOldFillColor);
+ else
+ rRenderContext.SetFillColor();
+ if( bLineColor )
+ rRenderContext.SetLineColor(aOldLineColor);
+ else
+ rRenderContext.SetLineColor();
+}
+
+void ToolBox::ImplDrawMenuButton(vcl::RenderContext& rRenderContext, bool bHighlight)
+{
+ if (mpData->maMenubuttonItem.maRect.IsEmpty())
+ return;
+
+ // #i53937# paint menu button only if necessary
+ if (!ImplHasClippedItems())
+ return;
+
+ // execute pending paint requests
+ ImplCheckUpdate();
+
+ rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR);
+
+ // draw the 'more' indicator / button (>>)
+ ImplErase(rRenderContext, mpData->maMenubuttonItem.maRect, bHighlight);
+
+ if (bHighlight)
+ ImplDrawButton(rRenderContext, mpData->maMenubuttonItem.maRect, 2, false, true, false );
+
+ if (ImplHasClippedItems())
+ ImplDrawMoreIndicator(rRenderContext, mpData->maMenubuttonItem.maRect);
+
+ // store highlight state
+ mpData->mbMenubuttonSelected = bHighlight;
+
+ // restore colors
+ rRenderContext.Pop();
+}
+
+void ToolBox::ImplDrawSpin(vcl::RenderContext& rRenderContext)
+{
+ bool bTmpUpper;
+ bool bTmpLower;
+
+ if ( maUpperRect.IsEmpty() || maLowerRect.IsEmpty() )
+ return;
+
+ bTmpUpper = mnCurLine > 1;
+
+ bTmpLower = mnCurLine+mnVisLines-1 < mnCurLines;
+
+ if ( !IsEnabled() )
+ {
+ bTmpUpper = false;
+ bTmpLower = false;
+ }
+
+ ImplDrawUpDownButtons(rRenderContext, maUpperRect, maLowerRect,
+ false/*bUpperIn*/, false/*bLowerIn*/, bTmpUpper, bTmpLower, !mbHorz);
+}
+
+void ToolBox::ImplDrawSeparator(vcl::RenderContext& rRenderContext, ImplToolItems::size_type nPos, const tools::Rectangle& rRect)
+{
+ if ( nPos >= mpData->m_aItems.size() - 1 )
+ // no separator if it's the last item
+ return;
+
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+ ImplToolItem* pPreviousItem = &mpData->m_aItems[nPos-1];
+ ImplToolItem* pNextItem = &mpData->m_aItems[nPos+1];
+
+ if ( ( pPreviousItem->mbShowWindow && pNextItem->mbShowWindow ) || pNextItem->mbBreak )
+ // no separator between two windows or before a break
+ return;
+
+ bool bNativeOk = false;
+ ControlPart nPart = IsHorizontal() ? ControlPart::SeparatorVert : ControlPart::SeparatorHorz;
+ if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, nPart))
+ {
+ ImplControlValue aControlValue;
+ bNativeOk = rRenderContext.DrawNativeControl(ControlType::Toolbar, nPart, rRect, ControlState::NONE, aControlValue, OUString());
+ }
+
+ /* Draw the widget only if it can't be drawn natively. */
+ if (bNativeOk)
+ return;
+
+ tools::Long nCenterPos, nSlim;
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ rRenderContext.SetLineColor(rStyleSettings.GetSeparatorColor());
+ if (IsHorizontal())
+ {
+ nSlim = (pItem->maRect.Bottom() - pItem->maRect.Top ()) / 4;
+ nCenterPos = pItem->maRect.Center().X();
+ rRenderContext.DrawLine(Point(nCenterPos, pItem->maRect.Top() + nSlim),
+ Point(nCenterPos, pItem->maRect.Bottom() - nSlim));
+ }
+ else
+ {
+ nSlim = (pItem->maRect.Right() - pItem->maRect.Left ()) / 4;
+ nCenterPos = pItem->maRect.Center().Y();
+ rRenderContext.DrawLine(Point(pItem->maRect.Left() + nSlim, nCenterPos),
+ Point(pItem->maRect.Right() - nSlim, nCenterPos));
+ }
+}
+
+void ToolBox::ImplDrawButton(vcl::RenderContext& rRenderContext, const tools::Rectangle &rRect, sal_uInt16 highlight,
+ bool bChecked, bool bEnabled, bool bIsWindow )
+{
+ // draws toolbar button background either native or using a coloured selection
+ // if bIsWindow is true, the corresponding item is a control and only a selection border will be drawn
+
+ bool bNativeOk = false;
+ if( !bIsWindow && rRenderContext.IsNativeControlSupported( ControlType::Toolbar, ControlPart::Button ) )
+ {
+ ImplControlValue aControlValue;
+ ControlState nState = ControlState::NONE;
+
+ if ( highlight == 1 ) nState |= ControlState::PRESSED;
+ if ( highlight == 2 ) nState |= ControlState::ROLLOVER;
+ if ( bEnabled ) nState |= ControlState::ENABLED;
+
+ aControlValue.setTristateVal( bChecked ? ButtonValue::On : ButtonValue::Off );
+
+ bNativeOk = rRenderContext.DrawNativeControl( ControlType::Toolbar, ControlPart::Button,
+ rRect, nState, aControlValue, OUString() );
+ }
+
+ if (!bNativeOk)
+ vcl::RenderTools::DrawSelectionBackground(rRenderContext, *this, rRect, bIsWindow ? 3 : highlight,
+ bChecked, true, bIsWindow, nullptr, 2);
+}
+
+void ToolBox::ImplDrawItem(vcl::RenderContext& rRenderContext, ImplToolItems::size_type nPos, sal_uInt16 nHighlight)
+{
+ if (nPos >= mpData->m_aItems.size())
+ return;
+
+ // execute pending paint requests
+ ImplCheckUpdate();
+
+ rRenderContext.SetFillColor();
+
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+
+ if (!pItem->mbEnabled)
+ nHighlight = 0;
+
+ // if the rectangle is outside visible area
+ if (pItem->maRect.IsEmpty())
+ return;
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ // no gradient background for items that have a popup open
+ bool bHasOpenPopup = mpFloatWin && (mnDownItemId==pItem->mnId);
+
+ bool bHighContrastWhite = false;
+ // check the face color as highcontrast indicator
+ // because the toolbox itself might have a gradient
+ if (rStyleSettings.GetFaceColor() == COL_WHITE)
+ bHighContrastWhite = true;
+
+ // Compute buttons area.
+ Size aBtnSize = pItem->maRect.GetSize();
+
+ /* Compute the button/separator rectangle here, we'll need it for
+ * both the buttons and the separators. */
+ tools::Rectangle aButtonRect( pItem->maRect.TopLeft(), aBtnSize );
+ tools::Long nOffX = SMALLBUTTON_OFF_NORMAL_X;
+ tools::Long nOffY = SMALLBUTTON_OFF_NORMAL_Y;
+ tools::Long nImageOffX = 0;
+ tools::Long nImageOffY = 0;
+ DrawButtonFlags nStyle = DrawButtonFlags::NONE;
+
+ // draw separators
+ if ( (pItem->meType == ToolBoxItemType::SEPARATOR) && nPos > 0 )
+ {
+ ImplDrawSeparator(rRenderContext, nPos, aButtonRect);
+ }
+
+ // do nothing if item is no button or will be displayed as window
+ if ( (pItem->meType != ToolBoxItemType::BUTTON) || pItem->mbShowWindow )
+ return;
+
+ if ( pItem->meState == TRISTATE_TRUE )
+ {
+ nStyle |= DrawButtonFlags::Checked;
+ }
+ else if ( pItem->meState == TRISTATE_INDET )
+ {
+ nStyle |= DrawButtonFlags::DontKnow;
+ }
+ if ( nHighlight == 1 )
+ {
+ nStyle |= DrawButtonFlags::Pressed;
+ }
+
+ ImplErase(rRenderContext, pItem->maRect, nHighlight != 0, bHasOpenPopup );
+
+ nOffX += pItem->maRect.Left();
+ nOffY += pItem->maRect.Top();
+
+ // determine what has to be drawn on the button: image, text or both
+ bool bImage;
+ bool bText;
+ ButtonType tmpButtonType = determineButtonType( pItem, meButtonType ); // default to toolbox setting
+ pItem->DetermineButtonDrawStyle( tmpButtonType, bImage, bText );
+
+ // compute output values
+ tools::Long nBtnWidth = aBtnSize.Width()-SMALLBUTTON_HSIZE;
+ tools::Long nBtnHeight = aBtnSize.Height()-SMALLBUTTON_VSIZE;
+ Size aImageSize;
+
+ const bool bDropDown = (pItem->mnBits & ToolBoxItemBits::DROPDOWN) == ToolBoxItemBits::DROPDOWN;
+ tools::Rectangle aDropDownRect;
+ if (bDropDown)
+ aDropDownRect = pItem->GetDropDownRect(mbHorz);
+
+ if ( bImage )
+ {
+ const Image* pImage = &(pItem->maImage);
+ aImageSize = pImage->GetSizePixel();
+
+ // determine drawing flags
+ DrawImageFlags nImageStyle = DrawImageFlags::NONE;
+
+ if ( !pItem->mbEnabled || !IsEnabled() )
+ nImageStyle |= DrawImageFlags::Disable;
+
+ // #i35563# the dontknow state indicates different states at the same time
+ // which should not be rendered disabled but normal
+
+ // draw the image
+ nImageOffX = nOffX;
+ nImageOffY = nOffY;
+ if ( ( (pItem->mnBits & (ToolBoxItemBits::LEFT|ToolBoxItemBits::DROPDOWN)) || bText )
+ && ( meTextPosition == ToolBoxTextPosition::Right ) )
+ {
+ // left align also to leave space for drop down arrow
+ // and when drawing text+image
+ // just center in y, except for vertical (ie rotated text)
+ if( mbHorz || !bText )
+ nImageOffY += (nBtnHeight-aImageSize.Height())/2;
+ }
+ else
+ {
+ nImageOffX += (nBtnWidth-(bDropDown ? aDropDownRect.getWidth() : 0)+SMALLBUTTON_OFF_NORMAL_X-aImageSize.Width())/2;
+ if ( meTextPosition == ToolBoxTextPosition::Right )
+ nImageOffY += (nBtnHeight-aImageSize.Height())/2;
+ }
+ if ( nHighlight != 0 || (pItem->meState == TRISTATE_TRUE) )
+ {
+ if( bHasOpenPopup )
+ ImplDrawFloatwinBorder(rRenderContext, pItem);
+ else
+ ImplDrawButton(rRenderContext, aButtonRect, nHighlight, pItem->meState == TRISTATE_TRUE,
+ pItem->mbEnabled && IsEnabled(), pItem->mbShowWindow);
+
+ if( nHighlight != 0 )
+ {
+ if( bHighContrastWhite )
+ nImageStyle |= DrawImageFlags::ColorTransform;
+ }
+ }
+ rRenderContext.DrawImage(Point( nImageOffX, nImageOffY ), *pImage, nImageStyle);
+ }
+
+ // draw the text
+ bool bRotate = false;
+ if ( bText )
+ {
+ const Size aTxtSize(GetOutDev()->GetCtrlTextWidth(pItem->maText), GetTextHeight());
+ tools::Long nTextOffX = nOffX;
+ tools::Long nTextOffY = nOffY;
+
+ // rotate text when vertically docked
+ vcl::Font aOldFont = rRenderContext.GetFont();
+ if( pItem->mbVisibleText && !ImplIsFloatingMode() &&
+ ((meAlign == WindowAlign::Left) || (meAlign == WindowAlign::Right)) )
+ {
+ bRotate = true;
+
+ vcl::Font aRotateFont = aOldFont;
+ aRotateFont.SetOrientation( 2700_deg10 );
+
+ // center horizontally
+ nTextOffX += aTxtSize.Height();
+ nTextOffX += (nBtnWidth-aTxtSize.Height())/2;
+
+ // add in image offset
+ if( bImage )
+ nTextOffY = nImageOffY + aImageSize.Height() + TB_IMAGETEXTOFFSET;
+
+ rRenderContext.SetFont(aRotateFont);
+ }
+ else
+ {
+ if ( meTextPosition == ToolBoxTextPosition::Right )
+ {
+ // center vertically
+ nTextOffY += (nBtnHeight-aTxtSize.Height())/2;
+
+ // add in image offset
+ if( bImage )
+ nTextOffX = nImageOffX + aImageSize.Width() + TB_IMAGETEXTOFFSET;
+ }
+ else
+ {
+ // center horizontally
+ nTextOffX += (nBtnWidth-(bDropDown ? aDropDownRect.getWidth() : 0)+SMALLBUTTON_OFF_NORMAL_X-aTxtSize.Width() - TB_IMAGETEXTOFFSET)/2;
+ // set vertical position
+ nTextOffY += nBtnHeight - aTxtSize.Height();
+ }
+ }
+
+ // draw selection only if not already drawn during image output (see above)
+ if ( !bImage && (nHighlight != 0 || (pItem->meState == TRISTATE_TRUE) ) )
+ {
+ if( bHasOpenPopup )
+ ImplDrawFloatwinBorder(rRenderContext, pItem);
+ else
+ ImplDrawButton(rRenderContext, pItem->maRect, nHighlight, pItem->meState == TRISTATE_TRUE,
+ pItem->mbEnabled && IsEnabled(), pItem->mbShowWindow );
+ }
+
+ DrawTextFlags nTextStyle = DrawTextFlags::NONE;
+ if ( !pItem->mbEnabled )
+ nTextStyle |= DrawTextFlags::Disable;
+ rRenderContext.DrawCtrlText( Point( nTextOffX, nTextOffY ), pItem->maText,
+ 0, pItem->maText.getLength(), nTextStyle );
+ if ( bRotate )
+ SetFont( aOldFont );
+ }
+
+ // paint optional drop down arrow
+ if (!bDropDown)
+ return;
+
+ bool bSetColor = true;
+ if ( !pItem->mbEnabled || !IsEnabled() )
+ {
+ bSetColor = false;
+ rRenderContext.SetFillColor(rStyleSettings.GetShadowColor());
+ }
+
+ // dropdown only will be painted without inner border
+ if( (pItem->mnBits & ToolBoxItemBits::DROPDOWNONLY) != ToolBoxItemBits::DROPDOWNONLY )
+ {
+ ImplErase(rRenderContext, aDropDownRect, nHighlight != 0, bHasOpenPopup);
+
+ if( nHighlight != 0 || (pItem->meState == TRISTATE_TRUE) )
+ {
+ if( bHasOpenPopup )
+ ImplDrawFloatwinBorder(rRenderContext, pItem);
+ else
+ ImplDrawButton(rRenderContext, aDropDownRect, nHighlight, pItem->meState == TRISTATE_TRUE,
+ pItem->mbEnabled && IsEnabled(), false);
+ }
+ }
+ ImplDrawDropdownArrow(rRenderContext, aDropDownRect, bSetColor, bRotate);
+}
+
+void ToolBox::ImplDrawFloatwinBorder(vcl::RenderContext& rRenderContext, ImplToolItem const * pItem)
+{
+ if ( pItem->maRect.IsEmpty() )
+ return;
+
+ tools::Rectangle aRect( mpFloatWin->ImplGetItemEdgeClipRect() );
+ aRect.SetPos( AbsoluteScreenToOutputPixel( aRect.TopLeft() ) );
+ rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetShadowColor());
+ Point p1, p2;
+
+ p1 = pItem->maRect.TopLeft();
+ p1.AdjustX( 1 );
+ p2 = pItem->maRect.TopRight();
+ p2.AdjustX( -1 );
+ rRenderContext.DrawLine( p1, p2);
+ p1 = pItem->maRect.BottomLeft();
+ p1.AdjustX( 1 );
+ p2 = pItem->maRect.BottomRight();
+ p2.AdjustX( -1 );
+ rRenderContext.DrawLine( p1, p2);
+
+ p1 = pItem->maRect.TopLeft();
+ p1.AdjustY( 1 );
+ p2 = pItem->maRect.BottomLeft();
+ p2.AdjustY( -1 );
+ rRenderContext.DrawLine( p1, p2);
+ p1 = pItem->maRect.TopRight();
+ p1.AdjustY( 1 );
+ p2 = pItem->maRect.BottomRight();
+ p2.AdjustY( -1 );
+ rRenderContext.DrawLine( p1, p2);
+
+}
+
+void ToolBox::ImplFloatControl( bool bStart, FloatingWindow* pFloatWindow )
+{
+
+ if ( bStart )
+ {
+ mpFloatWin = pFloatWindow;
+
+ // redraw item, to trigger drawing of a special border
+ InvalidateItem(mnCurPos);
+
+ mbDrag = false;
+ EndTracking();
+ if (IsMouseCaptured())
+ ReleaseMouse();
+ }
+ else
+ {
+ mpFloatWin = nullptr;
+
+ // if focus is still in this toolbox, then the floater was opened by keyboard
+ // draw current item with highlight and keep old state
+ bool bWasKeyboardActivate = mpData->mbDropDownByKeyboard;
+
+ if ( mnCurPos != ITEM_NOTFOUND )
+ InvalidateItem(mnCurPos);
+ Deactivate();
+
+ if( !bWasKeyboardActivate )
+ {
+ mnCurPos = ITEM_NOTFOUND;
+ mnCurItemId = ToolBoxItemId(0);
+ mnHighItemId = ToolBoxItemId(0);
+ }
+ mnDownItemId = ToolBoxItemId(0);
+
+ }
+}
+
+void ToolBox::ShowLine( bool bNext )
+{
+ mbFormat = true;
+
+ if ( bNext )
+ mnCurLine++;
+ else
+ mnCurLine--;
+
+ ImplFormat();
+}
+
+bool ToolBox::ImplHandleMouseMove( const MouseEvent& rMEvt, bool bRepeat )
+{
+ Point aMousePos = rMEvt.GetPosPixel();
+
+ if ( !mpData )
+ return false;
+
+ // ToolBox active?
+ if ( mbDrag && mnCurPos != ITEM_NOTFOUND )
+ {
+ // is the cursor over the item?
+ ImplToolItem* pItem = &mpData->m_aItems[mnCurPos];
+ if ( pItem->maRect.Contains( aMousePos ) )
+ {
+ if ( !mnCurItemId )
+ {
+ InvalidateItem(mnCurPos);
+ mnCurItemId = pItem->mnId;
+ Highlight();
+ }
+
+ if ( (pItem->mnBits & ToolBoxItemBits::REPEAT) && bRepeat )
+ Select();
+ }
+ else
+ {
+ if ( mnCurItemId )
+ {
+ InvalidateItem(mnCurPos);
+ mnCurItemId = ToolBoxItemId(0);
+ InvalidateItem(mnCurPos);
+ Highlight();
+ }
+ }
+
+ return true;
+ }
+
+ if ( mbUpper )
+ {
+ bool bNewIn = maUpperRect.Contains( aMousePos );
+ if ( bNewIn != mbIn )
+ {
+ mbIn = bNewIn;
+ InvalidateSpin(true, false);
+ }
+ return true;
+ }
+
+ if ( mbLower )
+ {
+ bool bNewIn = maLowerRect.Contains( aMousePos );
+ if ( bNewIn != mbIn )
+ {
+ mbIn = bNewIn;
+ InvalidateSpin(false);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool ToolBox::ImplHandleMouseButtonUp( const MouseEvent& rMEvt, bool bCancel )
+{
+ if ( !mpData )
+ return false;
+
+ // stop eventual running dropdown timer
+ if( mnCurPos < mpData->m_aItems.size() &&
+ (mpData->m_aItems[mnCurPos].mnBits & ToolBoxItemBits::DROPDOWN ) )
+ {
+ mpData->maDropdownTimer.Stop();
+ }
+
+ if ( mbDrag )
+ {
+ Deactivate();
+
+ if ( mbDrag )
+ mbDrag = false;
+ else
+ {
+ if ( mnCurPos == ITEM_NOTFOUND )
+ return true;
+ }
+
+ // has mouse been released on top of item?
+ if( mnCurPos < mpData->m_aItems.size() )
+ {
+ ImplToolItem* pItem = &mpData->m_aItems[mnCurPos];
+ if ( pItem->maRect.Contains( rMEvt.GetPosPixel() ) )
+ {
+ mnCurItemId = pItem->mnId;
+ if ( !bCancel )
+ {
+ // execute AutoCheck if required
+ if ( pItem->mnBits & ToolBoxItemBits::AUTOCHECK )
+ {
+ if ( pItem->mnBits & ToolBoxItemBits::RADIOCHECK )
+ {
+ if ( pItem->meState != TRISTATE_TRUE )
+ SetItemState( pItem->mnId, TRISTATE_TRUE );
+ }
+ else
+ {
+ if ( pItem->meState != TRISTATE_TRUE )
+ pItem->meState = TRISTATE_TRUE;
+ else
+ pItem->meState = TRISTATE_FALSE;
+ }
+ }
+
+ // do not call Select when Repeat is active, as in this
+ // case that was triggered already in MouseButtonDown
+ if ( !(pItem->mnBits & ToolBoxItemBits::REPEAT) )
+ {
+ // prevent from being destroyed in the select handler
+ VclPtr<vcl::Window> xWindow = this;
+ Select();
+ if ( xWindow->isDisposed() )
+ return true;
+ }
+ }
+
+ {
+ }
+
+ // Items not destroyed, in Select handler
+ if ( mnCurItemId )
+ {
+ // Get current pos for the case that items are inserted/removed
+ // in the toolBox
+ mnCurPos = GetItemPos( mnCurItemId );
+ if ( mnCurPos != ITEM_NOTFOUND )
+ {
+ InvalidateItem(mnCurPos);
+ GetOutDev()->Flush();
+ }
+ }
+ }
+ }
+
+ mnCurPos = ITEM_NOTFOUND;
+ mnCurItemId = ToolBoxItemId(0);
+ mnDownItemId = ToolBoxItemId(0);
+ mnMouseModifier = 0;
+ return true;
+ }
+ else if ( mbUpper || mbLower )
+ {
+ if ( mbIn )
+ ShowLine( !mbUpper );
+ mbUpper = false;
+ mbLower = false;
+ mbIn = false;
+ InvalidateSpin();
+ return true;
+ }
+
+ return false;
+}
+
+void ToolBox::MouseMove( const MouseEvent& rMEvt )
+{
+ // pressing a modifier generates synthetic mouse moves
+ // ignore it if keyboard selection is active
+ if( HasFocus() && ( rMEvt.GetMode() & MouseEventModifiers::MODIFIERCHANGED ) )
+ return;
+
+ if ( ImplHandleMouseMove( rMEvt ) )
+ return;
+
+ Point aMousePos = rMEvt.GetPosPixel();
+
+ // only highlight when the focus is not inside a child window of a toolbox
+ // eg, in an edit control
+ // and do not highlight when focus is in a different toolbox
+ bool bDrawHotSpot = true;
+ vcl::Window *pFocusWin = Application::GetFocusWindow();
+
+ bool bFocusWindowIsAToolBoxChild = false;
+ if (pFocusWin)
+ {
+ vcl::Window *pWin = pFocusWin->GetParent();
+ while (pWin)
+ {
+ if(pWin->ImplGetWindowImpl() && pWin->ImplGetWindowImpl()->mbToolBox)
+ {
+ bFocusWindowIsAToolBoxChild = true;
+ break;
+ }
+ pWin = pWin->GetParent();
+ }
+ }
+
+ if( bFocusWindowIsAToolBoxChild || (pFocusWin && pFocusWin->ImplGetWindowImpl() && pFocusWin->ImplGetWindowImpl()->mbToolBox && pFocusWin != this) )
+ bDrawHotSpot = false;
+
+ if ( mbDragging )
+ {
+ ImplTBDragMgr* pMgr = ImplGetTBDragMgr();
+ pMgr->Dragging( aMousePos );
+ return;
+ }
+
+ PointerStyle eStyle = PointerStyle::Arrow;
+
+ // change mouse cursor over drag area
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper && pWrapper->GetDragArea().Contains( rMEvt.GetPosPixel() ) )
+ eStyle = PointerStyle::Move;
+
+ if ( (mnWinStyle & TB_WBLINESIZING) == TB_WBLINESIZING )
+ {
+ if ( rMEvt.GetMode() & MouseEventModifiers::SIMPLEMOVE )
+ {
+ sal_uInt16 nLinePtr = ImplTestLineSize( rMEvt.GetPosPixel() );
+ if ( nLinePtr & DOCK_LINEHSIZE )
+ {
+ if ( meAlign == WindowAlign::Left )
+ eStyle = PointerStyle::WindowESize;
+ else
+ eStyle = PointerStyle::WindowWSize;
+ }
+ else if ( nLinePtr & DOCK_LINEVSIZE )
+ {
+ if ( meAlign == WindowAlign::Top )
+ eStyle = PointerStyle::WindowSSize;
+ else
+ eStyle = PointerStyle::WindowNSize;
+ }
+ }
+ }
+
+ if ( bDrawHotSpot )
+ {
+ bool bClearHigh = true;
+ if ( !rMEvt.IsLeaveWindow() && (mnCurPos == ITEM_NOTFOUND) )
+ {
+ ImplToolItems::size_type nTempPos = 0;
+ for (auto const& item : mpData->m_aItems)
+ {
+ if ( item.maRect.Contains( aMousePos ) )
+ {
+ if ( (item.meType == ToolBoxItemType::BUTTON) && item.mbEnabled )
+ {
+ bClearHigh = false;
+ if ( mnHighItemId != item.mnId )
+ {
+ if ( mnHighItemId )
+ {
+ ImplHideFocus();
+ ImplToolItems::size_type nPos = GetItemPos( mnHighItemId );
+ InvalidateItem(nPos);
+ CallEventListeners( VclEventId::ToolboxHighlightOff, reinterpret_cast< void* >( nPos ) );
+ }
+ if ( mpData->mbMenubuttonSelected )
+ {
+ // remove highlight from menubutton
+ InvalidateMenuButton();
+ }
+ mnHighItemId = item.mnId;
+ InvalidateItem(nTempPos);
+ ImplShowFocus();
+ CallEventListeners( VclEventId::ToolboxHighlight );
+ }
+ }
+ break;
+ }
+ ++nTempPos;
+ }
+ }
+
+ // only clear highlight when focus is not in toolbar
+ bool bMenuButtonHit = mpData->maMenubuttonItem.maRect.Contains( aMousePos ) && ImplHasClippedItems();
+ if ( !HasFocus() && (bClearHigh || bMenuButtonHit) )
+ {
+ if ( !bMenuButtonHit && mpData->mbMenubuttonSelected )
+ {
+ // remove highlight from menubutton
+ InvalidateMenuButton();
+ }
+
+ if( mnHighItemId )
+ {
+ ImplToolItems::size_type nClearPos = GetItemPos( mnHighItemId );
+ if ( nClearPos != ITEM_NOTFOUND )
+ {
+ InvalidateItem(nClearPos);
+ if( nClearPos != mnCurPos )
+ CallEventListeners( VclEventId::ToolboxHighlightOff, reinterpret_cast< void* >( nClearPos ) );
+ }
+ ImplHideFocus();
+ mnHighItemId = ToolBoxItemId(0);
+ }
+
+ if( bMenuButtonHit )
+ {
+ InvalidateMenuButton();
+ }
+ }
+ }
+
+ if ( meLastStyle != eStyle )
+ {
+ meLastStyle = eStyle;
+ SetPointer( eStyle );
+ }
+
+ DockingWindow::MouseMove( rMEvt );
+}
+
+void ToolBox::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ // only trigger toolbox for left mouse button and when
+ // we're not in normal operation
+ if ( rMEvt.IsLeft() && !mbDrag && (mnCurPos == ITEM_NOTFOUND) )
+ {
+ // call activate already here, as items could
+ // be exchanged
+ Activate();
+
+ // update ToolBox here, such that user knows it
+ if ( mbFormat )
+ {
+ ImplFormat();
+ PaintImmediately();
+ }
+
+ Point aMousePos = rMEvt.GetPosPixel();
+ ImplToolItems::size_type i = 0;
+ ImplToolItems::size_type nNewPos = ITEM_NOTFOUND;
+
+ // search for item that was clicked
+ for (auto const& item : mpData->m_aItems)
+ {
+ // is this the item?
+ if ( item.maRect.Contains( aMousePos ) )
+ {
+ // do nothing if it is a separator or
+ // if the item has been disabled
+ if ( (item.meType == ToolBoxItemType::BUTTON) &&
+ !item.mbShowWindow )
+ nNewPos = i;
+
+ break;
+ }
+
+ i++;
+ }
+
+ // item found
+ if ( nNewPos != ITEM_NOTFOUND )
+ {
+ if ( !mpData->m_aItems[nNewPos].mbEnabled )
+ {
+ Deactivate();
+ return;
+ }
+
+ // update actual data
+ StartTrackingFlags nTrackFlags = StartTrackingFlags::NONE;
+ mnCurPos = i;
+ mnCurItemId = mpData->m_aItems[nNewPos].mnId;
+ mnDownItemId = mnCurItemId;
+ mnMouseModifier = rMEvt.GetModifier();
+ if ( mpData->m_aItems[nNewPos].mnBits & ToolBoxItemBits::REPEAT )
+ nTrackFlags |= StartTrackingFlags::ButtonRepeat;
+
+ // update bDrag here, as it is evaluated in the EndSelection
+ mbDrag = true;
+
+ // on double-click: only call the handler, but do so before the button
+ // is hit, as in the handler dragging
+ // can be terminated
+ if ( rMEvt.GetClicks() == 2 )
+ DoubleClick();
+
+ if ( mbDrag )
+ {
+ InvalidateItem(mnCurPos);
+ Highlight();
+ }
+
+ // was dropdown arrow pressed
+ if( mpData->m_aItems[nNewPos].mnBits & ToolBoxItemBits::DROPDOWN )
+ {
+ if( ( (mpData->m_aItems[nNewPos].mnBits & ToolBoxItemBits::DROPDOWNONLY) == ToolBoxItemBits::DROPDOWNONLY)
+ || mpData->m_aItems[nNewPos].GetDropDownRect( mbHorz ).Contains( aMousePos ))
+ {
+ // dropdownonly always triggers the dropdown handler, over the whole button area
+
+ // the drop down arrow should not trigger the item action
+ mpData->mbDropDownByKeyboard = false;
+ mpData->maDropdownClickHdl.Call( this );
+
+ // do not reset data if the dropdown handler opened a floating window
+ // see ImplFloatControl()
+ if( !mpFloatWin )
+ {
+ // no floater was opened
+ Deactivate();
+ InvalidateItem(mnCurPos);
+
+ mnCurPos = ITEM_NOTFOUND;
+ mnCurItemId = ToolBoxItemId(0);
+ mnDownItemId = ToolBoxItemId(0);
+ mnMouseModifier = 0;
+ mnHighItemId = ToolBoxItemId(0);
+ }
+ return;
+ }
+ else // activate long click timer
+ mpData->maDropdownTimer.Start();
+ }
+
+ // call Click handler
+ if ( rMEvt.GetClicks() != 2 )
+ Click();
+
+ // also call Select handler at repeat
+ if ( nTrackFlags & StartTrackingFlags::ButtonRepeat )
+ Select();
+
+ // if the actions was not aborted in Click handler
+ if ( mbDrag )
+ StartTracking( nTrackFlags );
+
+ // if mouse was clicked over an item we
+ // can abort here
+ return;
+ }
+
+ Deactivate();
+
+ // menu button hit ?
+ if( mpData->maMenubuttonItem.maRect.Contains( aMousePos ) && ImplHasClippedItems() )
+ {
+ if ( maMenuButtonHdl.IsSet() )
+ maMenuButtonHdl.Call( this );
+ else
+ ExecuteCustomMenu( mpData->maMenubuttonItem.maRect );
+ return;
+ }
+
+ // check scroll- and next-buttons here
+ if ( maUpperRect.Contains( aMousePos ) )
+ {
+ if ( mnCurLine > 1 )
+ {
+ StartTracking();
+ mbUpper = true;
+ mbIn = true;
+ InvalidateSpin(true, false);
+ }
+ return;
+ }
+ if ( maLowerRect.Contains( aMousePos ) )
+ {
+ if ( mnCurLine+mnVisLines-1 < mnCurLines )
+ {
+ StartTracking();
+ mbLower = true;
+ mbIn = true;
+ InvalidateSpin(false);
+ }
+ return;
+ }
+
+ // Linesizing testen
+ if ( (mnWinStyle & TB_WBLINESIZING) == TB_WBLINESIZING )
+ {
+ sal_uInt16 nLineMode = ImplTestLineSize( aMousePos );
+ if ( nLineMode )
+ {
+ ImplTBDragMgr* pMgr = ImplGetTBDragMgr();
+
+ // call handler, such that we can set the
+ // dock rectangles
+ StartDocking();
+
+ Point aPos = GetParent()->OutputToScreenPixel( GetPosPixel() );
+ Size aSize = GetSizePixel();
+ aPos = ScreenToOutputPixel( aPos );
+
+ // start dragging
+ pMgr->StartDragging( this, aMousePos, tools::Rectangle( aPos, aSize ),
+ nLineMode );
+ return;
+ }
+ }
+
+ // no item, then only click or double click
+ if ( rMEvt.GetClicks() == 2 )
+ DoubleClick();
+ else
+ Click();
+ }
+
+ if ( !mbDrag && (mnCurPos == ITEM_NOTFOUND) )
+ DockingWindow::MouseButtonDown( rMEvt );
+}
+
+void ToolBox::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ if ( ImplHandleMouseButtonUp( rMEvt ) )
+ return;
+
+ if ( mbDragging && rMEvt.IsLeft() )
+ {
+ ImplTBDragMgr* pMgr = ImplGetTBDragMgr();
+ pMgr->EndDragging();
+ return;
+ }
+
+ DockingWindow::MouseButtonUp( rMEvt );
+}
+
+void ToolBox::Tracking( const TrackingEvent& rTEvt )
+{
+ VclPtr<vcl::Window> xWindow = this;
+
+ if ( rTEvt.IsTrackingEnded() )
+ ImplHandleMouseButtonUp( rTEvt.GetMouseEvent(), rTEvt.IsTrackingCanceled() );
+ else
+ ImplHandleMouseMove( rTEvt.GetMouseEvent(), rTEvt.IsTrackingRepeat() );
+
+ if ( xWindow->isDisposed() )
+ // toolbox was deleted
+ return;
+ DockingWindow::Tracking( rTEvt );
+}
+
+void ToolBox::InvalidateItem(ImplToolItems::size_type nPosition)
+{
+ if (mpData && nPosition < mpData->m_aItems.size())
+ {
+ ImplToolItem* pItem = &mpData->m_aItems[nPosition];
+ Invalidate(pItem->maRect);
+ }
+}
+
+void ToolBox::InvalidateMenuButton()
+{
+ if (!mpData->maMenubuttonItem.maRect.IsEmpty())
+ Invalidate(mpData->maMenubuttonItem.maRect);
+}
+
+void ToolBox::InvalidateSpin(bool bUpperIn, bool bLowerIn)
+{
+ if (bUpperIn && !maUpperRect.IsEmpty())
+ Invalidate(maUpperRect);
+
+ if (bLowerIn && !maLowerRect.IsEmpty())
+ Invalidate(maLowerRect);
+}
+
+void ToolBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rPaintRect)
+{
+ if( mpData->mbIsPaintLocked )
+ return;
+
+ if (rPaintRect == tools::Rectangle(0, 0, mnDX-1, mnDY-1))
+ mbFullPaint = true;
+ ImplFormat();
+ mbFullPaint = false;
+
+ ImplDrawBackground(rRenderContext, rPaintRect);
+
+ if ( (mnWinStyle & WB_BORDER) && !ImplIsFloatingMode() )
+ ImplDrawBorder(rRenderContext);
+
+ if( !ImplIsFloatingMode() )
+ ImplDrawGrip(rRenderContext);
+
+ ImplDrawMenuButton(rRenderContext, mpData->mbMenubuttonSelected);
+
+ // draw SpinButtons
+ if (mnWinStyle & WB_SCROLL)
+ {
+ if (mnCurLines > mnLines)
+ ImplDrawSpin(rRenderContext);
+ }
+
+ // draw buttons
+ ImplToolItems::size_type nHighPos;
+ if ( mnHighItemId )
+ nHighPos = GetItemPos( mnHighItemId );
+ else
+ nHighPos = ITEM_NOTFOUND;
+
+ ImplToolItems::size_type nCount = mpData->m_aItems.size();
+ for( ImplToolItems::size_type i = 0; i < nCount; i++ )
+ {
+ ImplToolItem* pItem = &mpData->m_aItems[i];
+
+ // only draw when the rectangle is in the draw rectangle
+ if ( !pItem->maRect.IsEmpty() && rPaintRect.Overlaps( pItem->maRect ) )
+ {
+ sal_uInt16 nHighlight = 0;
+ if ( i == mnCurPos )
+ nHighlight = 1;
+ else if ( i == nHighPos )
+ nHighlight = 2;
+ ImplDrawItem(rRenderContext, i, nHighlight);
+ }
+ }
+ ImplShowFocus();
+}
+
+void ToolBox::Resize()
+{
+ Size aSize = GetOutputSizePixel();
+ // #i31422# some WindowManagers send (0,0) sizes when
+ // switching virtual desktops - ignore this and avoid reformatting
+ if( !aSize.Width() && !aSize.Height() )
+ return;
+
+ tools::Long nOldDX = mnDX;
+ tools::Long nOldDY = mnDY;
+ mnDX = aSize.Width();
+ mnDY = aSize.Height();
+
+ mnLastResizeDY = 0;
+
+ // invalidate everything to have gradient backgrounds properly drawn
+ Invalidate();
+
+ // If we have any expandable entries, then force a reformat first using
+ // their optimal sizes, then share out the excess space evenly across those
+ // expandables and reformat again
+ std::vector<size_t> aExpandables;
+ for (size_t i = 0; i < mpData->m_aItems.size(); ++i)
+ {
+ if (mpData->m_aItems[i].mbExpand)
+ {
+ vcl::Window *pWindow = mpData->m_aItems[i].mpWindow;
+ SAL_INFO_IF(!pWindow, "vcl.layout", "only tabitems with window supported at the moment");
+ if (!pWindow)
+ continue;
+ Size aWinSize(pWindow->GetSizePixel());
+ Size aPrefSize(pWindow->get_preferred_size());
+ aWinSize.setWidth( aPrefSize.Width() );
+ pWindow->SetSizePixel(aWinSize);
+ aExpandables.push_back(i);
+ }
+ }
+
+ // re-format or re-draw
+ if ( mbScroll || !aExpandables.empty() )
+ {
+ if ( !mbFormat || !aExpandables.empty() )
+ {
+ mbFormat = true;
+ if( IsReallyVisible() || !aExpandables.empty() )
+ {
+ ImplFormat(true);
+
+ if (!aExpandables.empty())
+ {
+ //Get how big the optimal size is
+ tools::Rectangle aBounds;
+ for (const ImplToolItem & rItem : mpData->m_aItems)
+ {
+ aBounds.Union( rItem.maRect );
+ }
+
+ auto nOptimalWidth = aBounds.GetWidth();
+ auto nDiff = aSize.Width() - nOptimalWidth;
+ decltype(nDiff) nExpandablesSize = aExpandables.size();
+ nDiff /= nExpandablesSize;
+
+ //share out the diff from optimal to real across
+ //expandable entries
+ for (size_t nIndex : aExpandables)
+ {
+ vcl::Window *pWindow = mpData->m_aItems[nIndex].mpWindow;
+ Size aWinSize(pWindow->GetSizePixel());
+ Size aPrefSize(pWindow->get_preferred_size());
+ aWinSize.setWidth( aPrefSize.Width() + nDiff );
+ pWindow->SetSizePixel(aWinSize);
+ }
+
+ //now reformat with final sizes
+ mbFormat = true;
+ ImplFormat(true);
+ }
+ }
+ }
+ }
+
+ // redraw border
+ if ( !(mnWinStyle & WB_BORDER) )
+ return;
+
+ // as otherwise, when painting we might think we have to re-draw everything
+ if ( mbFormat && IsReallyVisible() )
+ Invalidate();
+ else
+ {
+ if ( mnRightBorder )
+ {
+ if ( nOldDX > mnDX )
+ Invalidate( tools::Rectangle( mnDX-mnRightBorder-1, 0, mnDX, mnDY ) );
+ else
+ Invalidate( tools::Rectangle( nOldDX-mnRightBorder-1, 0, nOldDX, nOldDY ) );
+ }
+
+ if ( mnBottomBorder )
+ {
+ if ( nOldDY > mnDY )
+ Invalidate( tools::Rectangle( 0, mnDY-mnBottomBorder-1, mnDX, mnDY ) );
+ else
+ Invalidate( tools::Rectangle( 0, nOldDY-mnBottomBorder-1, nOldDX, nOldDY ) );
+ }
+ }
+}
+
+namespace
+{
+ bool DispatchableCommand(std::u16string_view rName)
+ {
+ return o3tl::starts_with(rName, u".uno") ||
+ o3tl::starts_with(rName, u"slot:") ||
+ o3tl::starts_with(rName, u"macro:") ||
+ o3tl::starts_with(rName, u"vnd.sun.star.script");
+ }
+}
+
+const OUString& ToolBox::ImplGetHelpText( ToolBoxItemId nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ assert( pItem );
+
+ if ( pItem->maHelpText.isEmpty() && ( !pItem->maHelpId.isEmpty() || pItem->maCommandStr.getLength() ))
+ {
+ Help* pHelp = Application::GetHelp();
+ if ( pHelp )
+ {
+ if (DispatchableCommand(pItem->maCommandStr))
+ pItem->maHelpText = pHelp->GetHelpText( pItem->maCommandStr, this );
+ if ( pItem->maHelpText.isEmpty() && !pItem->maHelpId.isEmpty() )
+ pItem->maHelpText = pHelp->GetHelpText( OStringToOUString( pItem->maHelpId, RTL_TEXTENCODING_UTF8 ), this );
+ }
+ }
+
+ return pItem->maHelpText;
+}
+
+void ToolBox::RequestHelp( const HelpEvent& rHEvt )
+{
+ ToolBoxItemId nItemId;
+ Point aHelpPos;
+
+ if( !rHEvt.KeyboardActivated() )
+ {
+ nItemId = GetItemId( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) );
+ aHelpPos = rHEvt.GetMousePosPixel();
+ }
+ else
+ {
+ if( !mnHighItemId )
+ return;
+ else
+ nItemId = mnHighItemId;
+ tools::Rectangle aRect( GetItemRect( nItemId ) );
+ if( aRect.IsEmpty() )
+ return;
+ else
+ aHelpPos = OutputToScreenPixel( aRect.Center() );
+ }
+
+ if ( nItemId )
+ {
+ if ( rHEvt.GetMode() & (HelpEventMode::BALLOON | HelpEventMode::QUICK) )
+ {
+ // get rectangle
+ tools::Rectangle aTempRect = GetItemRect( nItemId );
+ Point aPt = OutputToScreenPixel( aTempRect.TopLeft() );
+ aTempRect.SetLeft( aPt.X() );
+ aTempRect.SetTop( aPt.Y() );
+ aPt = OutputToScreenPixel( aTempRect.BottomRight() );
+ aTempRect.SetRight( aPt.X() );
+ aTempRect.SetBottom( aPt.Y() );
+
+ // get text and display it
+ OUString aStr = GetQuickHelpText( nItemId );
+ if (aStr.isEmpty())
+ aStr = MnemonicGenerator::EraseAllMnemonicChars( GetItemText( nItemId ) );
+ if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
+ {
+ const OUString& rHelpStr = GetHelpText( nItemId );
+ if (!rHelpStr.isEmpty())
+ aStr = rHelpStr;
+ Help::ShowBalloon( this, aHelpPos, aTempRect, aStr );
+ }
+ else
+ Help::ShowQuickHelp( this, aTempRect, aStr, QuickHelpFlags::CtrlText );
+ return;
+ }
+ }
+
+ DockingWindow::RequestHelp( rHEvt );
+}
+
+bool ToolBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
+ {
+ KeyEvent aKEvt = *rNEvt.GetKeyEvent();
+ vcl::KeyCode aKeyCode = aKEvt.GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+ switch( nKeyCode )
+ {
+ case KEY_TAB:
+ {
+ // internal TAB cycling only if parent is not a dialog or if we are the only child
+ // otherwise the dialog control will take over
+ vcl::Window *pParent = ImplGetParent();
+ bool bOldSchoolContainer =
+ ((pParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL &&
+ pParent->GetChildCount() != 1);
+ bool bNoTabCycling = bOldSchoolContainer || isContainerWindow(pParent);
+
+ if( bNoTabCycling )
+ return DockingWindow::EventNotify( rNEvt );
+ else if( ImplChangeHighlightUpDn( aKeyCode.IsShift() , bNoTabCycling ) )
+ return true;
+ else
+ return DockingWindow::EventNotify( rNEvt );
+ }
+ default:
+ break;
+ }
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
+ {
+ if( rNEvt.GetWindow() == this )
+ {
+ // the toolbar itself got the focus
+ if( mnLastFocusItemId != ToolBoxItemId(0) || mpData->mbMenubuttonWasLastSelected )
+ {
+ // restore last item
+ if( mpData->mbMenubuttonWasLastSelected )
+ {
+ ImplChangeHighlight( nullptr );
+ mpData->mbMenubuttonSelected = true;
+ InvalidateMenuButton();
+ }
+ else
+ {
+ ImplChangeHighlight( ImplGetItem( mnLastFocusItemId ) );
+ mnLastFocusItemId = ToolBoxItemId(0);
+ }
+ }
+ else if( (GetGetFocusFlags() & (GetFocusFlags::Backward|GetFocusFlags::Tab) ) == (GetFocusFlags::Backward|GetFocusFlags::Tab))
+ // Shift-TAB was pressed in the parent
+ ImplChangeHighlightUpDn( false );
+ else
+ ImplChangeHighlightUpDn( true );
+
+ mnLastFocusItemId = ToolBoxItemId(0);
+
+ return true;
+ }
+ else
+ {
+ // a child window got the focus so update current item to
+ // allow for proper lose focus handling in keyboard navigation
+ for (auto const& item : mpData->m_aItems)
+ {
+ if ( item.mbVisible )
+ {
+ if ( item.mpWindow && item.mpWindow->ImplIsWindowOrChild( rNEvt.GetWindow() ) )
+ {
+ mnHighItemId = item.mnId;
+ break;
+ }
+ }
+ }
+ return DockingWindow::EventNotify( rNEvt );
+ }
+ }
+ else if( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
+ {
+ // deselect
+ ImplHideFocus();
+ mpData->mbMenubuttonWasLastSelected = false;
+ mnHighItemId = ToolBoxItemId(0);
+ mnCurPos = ITEM_NOTFOUND;
+ }
+
+ return DockingWindow::EventNotify( rNEvt );
+}
+
+void ToolBox::Command( const CommandEvent& rCEvt )
+{
+ if ( rCEvt.GetCommand() == CommandEventId::Wheel )
+ {
+ if ( (mnCurLine > 1) || (mnCurLine+mnVisLines-1 < mnCurLines) )
+ {
+ const CommandWheelData* pData = rCEvt.GetWheelData();
+ if ( pData->GetMode() == CommandWheelMode::SCROLL )
+ {
+ if ( (mnCurLine > 1) && (pData->GetDelta() > 0) )
+ ShowLine( false );
+ else if ( (mnCurLine+mnVisLines-1 < mnCurLines) && (pData->GetDelta() < 0) )
+ ShowLine( true );
+ InvalidateSpin();
+ return;
+ }
+ }
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::ContextMenu )
+ {
+ ExecuteCustomMenu( tools::Rectangle( rCEvt.GetMousePosPixel(), rCEvt.GetMousePosPixel() ) );
+ return;
+ }
+
+ DockingWindow::Command( rCEvt );
+}
+
+void ToolBox::StateChanged( StateChangedType nType )
+{
+ DockingWindow::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ ImplFormat();
+ else if ( nType == StateChangedType::Enable )
+ ImplUpdateItem();
+ else if ( nType == StateChangedType::UpdateMode )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ mbCalc = true;
+ mbFormat = true;
+ ImplInitSettings( true, false, false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false, true, false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( false, false, true ); // font, foreground, background
+ Invalidate();
+ }
+
+ maStateChangedHandler.Call( &nType );
+}
+
+void ToolBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ DockingWindow::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::DISPLAY) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ mbCalc = true;
+ mbFormat = true;
+ ImplInitSettings( true, true, true );
+ Invalidate();
+ }
+
+ maDataChangedHandler.Call( &rDCEvt );
+}
+
+void ToolBox::statusChanged( const css::frame::FeatureStateEvent& Event )
+{
+ // Update image mirroring/rotation
+ if ( Event.FeatureURL.Complete != ".uno:ImageOrientation" )
+ return;
+
+ SfxImageItem aItem( 1 );
+ aItem.PutValue( Event.State, 0 );
+
+ mbImagesMirrored = aItem.IsMirrored();
+ mnImagesRotationAngle = aItem.GetRotation();
+
+ // update image orientation
+ OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(mpStatusListener->getFrame()));
+ for (auto const& item : mpData->m_aItems)
+ {
+ if (vcl::CommandInfoProvider::IsMirrored(item.maCommandStr, aModuleName))
+ SetItemImageMirrorMode(item.mnId, mbImagesMirrored);
+ if (vcl::CommandInfoProvider::IsRotated(item.maCommandStr, aModuleName))
+ SetItemImageAngle(item.mnId, mnImagesRotationAngle);
+ }
+}
+
+void ToolBox::SetStyle(WinBits nNewStyle)
+{
+ mnWinStyle = nNewStyle;
+ if (!ImplIsFloatingMode())
+ {
+ bool bOldScroll = mbScroll;
+ mbScroll = (mnWinStyle & WB_SCROLL) != 0;
+ if (mbScroll != bOldScroll)
+ {
+ mbFormat = true;
+ ImplFormat();
+ }
+ }
+}
+
+void ToolBox::ToggleFloatingMode()
+{
+ DockingWindow::ToggleFloatingMode();
+
+ if (!mpData)
+ return;
+
+ bool bOldHorz = mbHorz;
+
+ if ( ImplIsFloatingMode() )
+ {
+ mbHorz = true;
+ meAlign = WindowAlign::Top;
+ mbScroll = true;
+
+ if( bOldHorz != mbHorz )
+ mbCalc = true; // orientation was changed !
+
+ ImplSetMinMaxFloatSize();
+ SetOutputSizePixel( ImplCalcFloatSize( mnFloatLines ) );
+ }
+ else
+ {
+ mbScroll = (mnWinStyle & WB_SCROLL) != 0;
+ if ( (meAlign == WindowAlign::Top) || (meAlign == WindowAlign::Bottom) )
+ mbHorz = true;
+ else
+ mbHorz = false;
+
+ // set focus back to document
+ ImplGetFrameWindow()->GetWindow( GetWindowType::Client )->GrabFocus();
+ }
+
+ if( bOldHorz != mbHorz )
+ {
+ // if orientation changes, the toolbox has to be initialized again
+ // to update the direction of the gradient
+ mbCalc = true;
+ ImplInitSettings( true, true, true );
+ }
+
+ mbFormat = true;
+ ImplFormat();
+}
+
+void ToolBox::StartDocking()
+{
+ meDockAlign = meAlign;
+ mnDockLines = mnLines;
+ mbLastFloatMode = ImplIsFloatingMode();
+ DockingWindow::StartDocking();
+}
+
+bool ToolBox::Docking( const Point& rPos, tools::Rectangle& rRect )
+{
+ // do nothing during dragging, it was calculated before
+ if ( mbDragging )
+ return false;
+
+ bool bFloatMode = false;
+
+ DockingWindow::Docking( rPos, rRect );
+
+ // if the mouse is outside the area, it can only become a floating window
+ tools::Rectangle aDockingRect( rRect );
+ if ( !ImplIsFloatingMode() )
+ {
+ // don't use tracking rectangle for alignment check, because it will be too large
+ // to get a floating mode as result - switch to floating size
+ // so the calculation only depends on the position of the rectangle, not the current
+ // docking state of the window
+ ImplToolItems::size_type nTemp = 0;
+ aDockingRect.SetSize( ImplCalcFloatSize( nTemp ) );
+
+ // in this mode docking is never done by keyboard, so it's OK to use the mouse position
+ aDockingRect.SetPos( ImplGetFrameWindow()->GetPointerPosPixel() );
+ }
+
+ bFloatMode = true;
+
+ meDockAlign = meAlign;
+ if ( !mbLastFloatMode )
+ {
+ ImplToolItems::size_type nTemp = 0;
+ aDockingRect.SetSize( ImplCalcFloatSize( nTemp ) );
+ }
+
+ rRect = aDockingRect;
+ mbLastFloatMode = bFloatMode;
+
+ return bFloatMode;
+}
+
+void ToolBox::EndDocking( const tools::Rectangle& rRect, bool bFloatMode )
+{
+ if ( !IsDockingCanceled() )
+ {
+ if ( mnLines != mnDockLines )
+ SetLineCount( mnDockLines );
+ if ( meAlign != meDockAlign )
+ SetAlign( meDockAlign );
+ }
+ if ( bFloatMode || (bFloatMode != ImplIsFloatingMode()) )
+ DockingWindow::EndDocking( rRect, bFloatMode );
+}
+
+void ToolBox::Resizing( Size& rSize )
+{
+ ImplToolItems::size_type nCalcLines;
+ ImplToolItems::size_type nTemp;
+
+ // calculate all floating sizes
+ ImplCalcFloatSizes();
+
+ if ( !mnLastResizeDY )
+ mnLastResizeDY = mnDY;
+
+ // is vertical resizing needed
+ if ( (mnLastResizeDY != rSize.Height()) && (mnDY != rSize.Height()) )
+ {
+ nCalcLines = ImplCalcLines( rSize.Height() );
+ if ( nCalcLines < 1 )
+ nCalcLines = 1;
+ rSize = ImplCalcFloatSize( nCalcLines );
+ }
+ else
+ {
+ nCalcLines = 1;
+ nTemp = nCalcLines;
+ Size aTempSize = ImplCalcFloatSize( nTemp );
+ while ( (aTempSize.Width() > rSize.Width()) &&
+ (nCalcLines <= maFloatSizes[0].mnLines) )
+ {
+ nCalcLines++;
+ nTemp = nCalcLines;
+ aTempSize = ImplCalcFloatSize( nTemp );
+ }
+ rSize = aTempSize;
+ }
+
+ mnLastResizeDY = rSize.Height();
+}
+
+Size ToolBox::GetOptimalSize() const
+{
+ // If we have any expandable entries, then force them to their
+ // optimal sizes, then reset them afterwards
+ std::map<vcl::Window*, Size> aExpandables;
+ for (const ImplToolItem & rItem : mpData->m_aItems)
+ {
+ if (rItem.mbExpand)
+ {
+ vcl::Window *pWindow = rItem.mpWindow;
+ SAL_INFO_IF(!pWindow, "vcl.layout", "only tabitems with window supported at the moment");
+ if (!pWindow)
+ continue;
+ Size aWinSize(pWindow->GetSizePixel());
+ aExpandables[pWindow] = aWinSize;
+ Size aPrefSize(pWindow->get_preferred_size());
+ aWinSize.setWidth( aPrefSize.Width() );
+ pWindow->SetSizePixel(aWinSize);
+ }
+ }
+
+ Size aSize(const_cast<ToolBox *>(this)->ImplCalcSize( mnLines ));
+
+ for (auto const& [pWindow, aWinSize] : aExpandables)
+ pWindow->SetSizePixel(aWinSize);
+
+ return aSize;
+}
+
+Size ToolBox::CalcWindowSizePixel( ImplToolItems::size_type nCalcLines )
+{
+ return ImplCalcSize( nCalcLines );
+}
+
+Size ToolBox::CalcWindowSizePixel( ImplToolItems::size_type nCalcLines, WindowAlign eAlign )
+{
+ return ImplCalcSize( nCalcLines,
+ (eAlign == WindowAlign::Top || eAlign == WindowAlign::Bottom) ? TB_CALCMODE_HORZ : TB_CALCMODE_VERT );
+}
+
+ToolBox::ImplToolItems::size_type ToolBox::ImplCountLineBreaks() const
+{
+ ImplToolItems::size_type nLines = 0;
+
+ for (auto const& item : mpData->m_aItems)
+ {
+ if( item.meType == ToolBoxItemType::BREAK )
+ ++nLines;
+ }
+ return nLines;
+}
+
+Size ToolBox::CalcPopupWindowSizePixel()
+{
+ // count number of breaks and calc corresponding floating window size
+ ImplToolItems::size_type nLines = ImplCountLineBreaks();
+
+ if( nLines )
+ ++nLines; // add the first line
+ else
+ {
+ // no breaks found: use quadratic layout
+ nLines = static_cast<ImplToolItems::size_type>(ceil( sqrt( static_cast<double>(GetItemCount()) ) ));
+ }
+
+ bool bPopup = mpData->mbAssumePopupMode;
+ mpData->mbAssumePopupMode = true;
+
+ Size aSize = CalcFloatingWindowSizePixel( nLines );
+
+ mpData->mbAssumePopupMode = bPopup;
+ return aSize;
+}
+
+Size ToolBox::CalcFloatingWindowSizePixel()
+{
+ ImplToolItems::size_type nLines = ImplCountLineBreaks();
+ ++nLines; // add the first line
+ return CalcFloatingWindowSizePixel( nLines );
+}
+
+Size ToolBox::CalcFloatingWindowSizePixel( ImplToolItems::size_type nCalcLines )
+{
+ bool bFloat = mpData->mbAssumeFloating;
+ bool bDocking = mpData->mbAssumeDocked;
+
+ // simulate floating mode and force reformat before calculating
+ mpData->mbAssumeFloating = true;
+ mpData->mbAssumeDocked = false;
+
+ Size aSize = ImplCalcFloatSize( nCalcLines );
+
+ mbFormat = true;
+ mpData->mbAssumeFloating = bFloat;
+ mpData->mbAssumeDocked = bDocking;
+
+ return aSize;
+}
+
+Size ToolBox::CalcMinimumWindowSizePixel()
+{
+ if( ImplIsFloatingMode() )
+ return ImplCalcSize( mnFloatLines );
+ else
+ {
+ // create dummy toolbox for measurements
+ VclPtrInstance< ToolBox > pToolBox( GetParent(), GetStyle() );
+
+ // copy until first useful item
+ for (auto const& item : mpData->m_aItems)
+ {
+ pToolBox->CopyItem( *this, item.mnId );
+ if( (item.meType == ToolBoxItemType::BUTTON) &&
+ item.mbVisible && !ImplIsFixedControl( &item ) )
+ break;
+ }
+
+ // add to docking manager if required to obtain a drag area
+ // (which is accounted for in calcwindowsizepixel)
+ if( ImplGetDockingManager()->GetDockingWindowWrapper( this ) )
+ ImplGetDockingManager()->AddWindow( pToolBox );
+
+ // account for menu
+ if( IsMenuEnabled() )
+ pToolBox->SetMenuType( GetMenuType() );
+
+ pToolBox->SetAlign( GetAlign() );
+ Size aSize = pToolBox->CalcWindowSizePixel( 1 );
+
+ ImplGetDockingManager()->RemoveWindow( pToolBox );
+ pToolBox->Clear();
+
+ pToolBox.disposeAndClear();
+
+ return aSize;
+ }
+}
+
+void ToolBox::EnableCustomize( bool bEnable )
+{
+ mbCustomize = bEnable;
+}
+
+void ToolBox::LoseFocus()
+{
+ ImplChangeHighlight( nullptr, true );
+
+ DockingWindow::LoseFocus();
+}
+
+// performs the action associated with an item, ie simulates clicking the item
+void ToolBox::TriggerItem( ToolBoxItemId nItemId )
+{
+ mnHighItemId = nItemId;
+ vcl::KeyCode aKeyCode( 0, 0 );
+ ImplActivateItem( aKeyCode );
+}
+
+// calls the button's action handler
+// returns true if action was called
+bool ToolBox::ImplActivateItem( vcl::KeyCode aKeyCode )
+{
+ bool bRet = true;
+ if( mnHighItemId )
+ {
+ ImplToolItem *pToolItem = ImplGetItem( mnHighItemId );
+
+ // #107712#, activate can also be called for disabled entries
+ if( pToolItem && !pToolItem->mbEnabled )
+ return true;
+
+ if( pToolItem && pToolItem->mpWindow && HasFocus() )
+ {
+ ImplHideFocus();
+ mbChangingHighlight = true; // avoid focus change due to loss of focus
+ pToolItem->mpWindow->ImplControlFocus( GetFocusFlags::Tab );
+ mbChangingHighlight = false;
+ }
+ else
+ {
+ mnDownItemId = mnCurItemId = mnHighItemId;
+ if (pToolItem && (pToolItem->mnBits & ToolBoxItemBits::AUTOCHECK))
+ {
+ if ( pToolItem->mnBits & ToolBoxItemBits::RADIOCHECK )
+ {
+ if ( pToolItem->meState != TRISTATE_TRUE )
+ SetItemState( pToolItem->mnId, TRISTATE_TRUE );
+ }
+ else
+ {
+ if ( pToolItem->meState != TRISTATE_TRUE )
+ pToolItem->meState = TRISTATE_TRUE;
+ else
+ pToolItem->meState = TRISTATE_FALSE;
+ }
+ }
+ mnMouseModifier = aKeyCode.GetModifier();
+ mbIsKeyEvent = true;
+ Activate();
+ Click();
+
+ // #107776# we might be destroyed in the selecthandler
+ VclPtr<vcl::Window> xWindow = this;
+ Select();
+ if ( xWindow->isDisposed() )
+ return bRet;
+
+ Deactivate();
+ mbIsKeyEvent = false;
+ mnMouseModifier = 0;
+ }
+ }
+ else
+ bRet = false;
+ return bRet;
+}
+
+static bool ImplCloseLastPopup( vcl::Window const *pParent )
+{
+ // close last popup toolbox (see also:
+ // ImplHandleMouseFloatMode(...) in winproc.cxx )
+
+ if (ImplGetSVData()->mpWinData->mpFirstFloat)
+ {
+ FloatingWindow* pLastLevelFloat = ImplGetSVData()->mpWinData->mpFirstFloat->ImplFindLastLevelFloat();
+ // only close the floater if it is not our direct parent, which would kill ourself
+ if( pLastLevelFloat && pLastLevelFloat != pParent )
+ {
+ pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll );
+ return true;
+ }
+ }
+ return false;
+}
+
+// opens a drop down toolbox item
+// returns true if item was opened
+bool ToolBox::ImplOpenItem( vcl::KeyCode aKeyCode )
+{
+ sal_uInt16 nCode = aKeyCode.GetCode();
+ bool bRet = true;
+
+ // arrow keys should work only in the opposite direction of alignment (to not break cursor travelling)
+ if ( ((nCode == KEY_LEFT || nCode == KEY_RIGHT) && IsHorizontal())
+ || ((nCode == KEY_UP || nCode == KEY_DOWN) && !IsHorizontal()) )
+ return false;
+
+ if( mpData->mbMenubuttonSelected )
+ {
+ if( ImplCloseLastPopup( GetParent() ) )
+ return bRet;
+ mbIsKeyEvent = true;
+ if ( maMenuButtonHdl.IsSet() )
+ maMenuButtonHdl.Call( this );
+ else
+ ExecuteCustomMenu( mpData->maMenubuttonItem.maRect );
+ mpData->mbMenubuttonWasLastSelected = true;
+ mbIsKeyEvent = false;
+ }
+ else if( mnHighItemId && ImplGetItem( mnHighItemId ) &&
+ (ImplGetItem( mnHighItemId )->mnBits & ToolBoxItemBits::DROPDOWN) )
+ {
+ mnDownItemId = mnCurItemId = mnHighItemId;
+ mnCurPos = GetItemPos( mnCurItemId );
+ mnLastFocusItemId = mnCurItemId; // save item id for possible later focus restore
+ mnMouseModifier = aKeyCode.GetModifier();
+ mbIsKeyEvent = true;
+ Activate();
+
+ mpData->mbDropDownByKeyboard = true;
+ mpData->maDropdownClickHdl.Call( this );
+
+ mbIsKeyEvent = false;
+ mnMouseModifier = 0;
+ }
+ else
+ bRet = false;
+
+ return bRet;
+}
+
+void ToolBox::KeyInput( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+ sal_uInt16 nCode = aKeyCode.GetCode();
+
+ vcl::Window *pParent = ImplGetParent();
+ bool bOldSchoolContainer = ((pParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL);
+ bool bParentIsContainer = bOldSchoolContainer || isContainerWindow(pParent);
+
+ bool bForwardKey = false;
+ bool bGrabFocusToDocument = false;
+
+ // #107776# we might be destroyed in the keyhandler
+ VclPtr<vcl::Window> xWindow = this;
+
+ switch ( nCode )
+ {
+ case KEY_UP:
+ {
+ // Ctrl-Cursor activates next toolbox, indicated by a blue arrow pointing to the left/up
+ if( aKeyCode.GetModifier() ) // allow only pure cursor keys
+ break;
+ if( !IsHorizontal() )
+ ImplChangeHighlightUpDn( true );
+ else
+ ImplOpenItem( aKeyCode );
+ }
+ break;
+ case KEY_LEFT:
+ {
+ if( aKeyCode.GetModifier() ) // allow only pure cursor keys
+ break;
+ if( IsHorizontal() )
+ ImplChangeHighlightUpDn( true );
+ else
+ ImplOpenItem( aKeyCode );
+ }
+ break;
+ case KEY_DOWN:
+ {
+ if( aKeyCode.GetModifier() ) // allow only pure cursor keys
+ break;
+ if( !IsHorizontal() )
+ ImplChangeHighlightUpDn( false );
+ else
+ ImplOpenItem( aKeyCode );
+ }
+ break;
+ case KEY_RIGHT:
+ {
+ if( aKeyCode.GetModifier() ) // allow only pure cursor keys
+ break;
+ if( IsHorizontal() )
+ ImplChangeHighlightUpDn( false );
+ else
+ ImplOpenItem( aKeyCode );
+ }
+ break;
+ case KEY_PAGEUP:
+ if ( mnCurLine > 1 )
+ {
+ if( mnCurLine > mnVisLines )
+ mnCurLine = mnCurLine - mnVisLines;
+ else
+ mnCurLine = 1;
+ mbFormat = true;
+ ImplFormat();
+ InvalidateSpin();
+ ImplChangeHighlight( ImplGetFirstValidItem( mnCurLine ) );
+ }
+ break;
+ case KEY_PAGEDOWN:
+ if ( mnCurLine+mnVisLines-1 < mnCurLines )
+ {
+ if( mnCurLine + 2*mnVisLines-1 < mnCurLines )
+ mnCurLine = mnCurLine + mnVisLines;
+ else
+ mnCurLine = mnCurLines;
+ mbFormat = true;
+ ImplFormat();
+ InvalidateSpin();
+ ImplChangeHighlight( ImplGetFirstValidItem( mnCurLine ) );
+ }
+ break;
+ case KEY_END:
+ {
+ ImplChangeHighlight( nullptr );
+ ImplChangeHighlightUpDn( false );
+ }
+ break;
+ case KEY_HOME:
+ {
+ ImplChangeHighlight( nullptr );
+ ImplChangeHighlightUpDn( true );
+ }
+ break;
+ case KEY_ESCAPE:
+ {
+ if( !ImplIsFloatingMode() && bParentIsContainer )
+ DockingWindow::KeyInput( rKEvt );
+ else
+ {
+ // send focus to document pane
+ vcl::Window *pWin = this;
+ while( pWin )
+ {
+ if( !pWin->GetParent() )
+ {
+ pWin->ImplGetFrameWindow()->GetWindow( GetWindowType::Client )->GrabFocus();
+ break;
+ }
+ pWin = pWin->GetParent();
+ }
+ }
+ }
+ break;
+ case KEY_RETURN:
+ {
+ // #107712#, disabled entries are selectable now
+ // leave toolbox and move focus to document
+ if( mnHighItemId )
+ {
+ ImplToolItem *pItem = ImplGetItem(mnHighItemId);
+ if (!pItem || !pItem->mbEnabled)
+ {
+ bGrabFocusToDocument = true;
+ }
+ }
+ if( !bGrabFocusToDocument )
+ bForwardKey = !ImplActivateItem( aKeyCode );
+ }
+ break;
+ case KEY_SPACE:
+ {
+ ImplOpenItem( aKeyCode );
+ }
+ break;
+ default:
+ {
+ sal_uInt16 aKeyGroup = aKeyCode.GetGroup();
+ ImplToolItem *pItem = nullptr;
+ if( mnHighItemId )
+ pItem = ImplGetItem( mnHighItemId );
+ // #i13931# forward alphanum keyinput into embedded control
+ if( (aKeyGroup == KEYGROUP_NUM || aKeyGroup == KEYGROUP_ALPHA ) && pItem && pItem->mpWindow && pItem->mbEnabled )
+ {
+ vcl::Window *pFocusWindow = Application::GetFocusWindow();
+ ImplHideFocus();
+ mbChangingHighlight = true; // avoid focus change due to loss of focus
+ pItem->mpWindow->ImplControlFocus( GetFocusFlags::Tab );
+ mbChangingHighlight = false;
+ if( pFocusWindow != Application::GetFocusWindow() )
+ Application::GetFocusWindow()->KeyInput( rKEvt );
+ }
+ else
+ {
+ // do nothing to avoid key presses going into the document
+ // while the toolbox has the focus
+ // just forward function and special keys and combinations with Alt-key
+ if( aKeyGroup == KEYGROUP_FKEYS || aKeyGroup == KEYGROUP_MISC || aKeyCode.IsMod2() )
+ bForwardKey = true;
+ }
+ }
+ }
+
+ if ( xWindow->isDisposed() )
+ return;
+
+ // #107251# move focus away if this toolbox was disabled during keyinput
+ if (HasFocus() && mpData->mbKeyInputDisabled && bParentIsContainer)
+ {
+ vcl::Window *pFocusControl = pParent->ImplGetDlgWindow( 0, GetDlgWindowType::First );
+ if ( pFocusControl && pFocusControl != this )
+ pFocusControl->ImplControlFocus( GetFocusFlags::Init );
+ }
+
+ // #107712#, leave toolbox
+ if( bGrabFocusToDocument )
+ {
+ GrabFocusToDocument();
+ return;
+ }
+
+ if( bForwardKey )
+ DockingWindow::KeyInput( rKEvt );
+}
+
+// returns the current toolbox line of the item
+ToolBox::ImplToolItems::size_type ToolBox::ImplGetItemLine( ImplToolItem const * pCurrentItem )
+{
+ ImplToolItems::size_type nLine = 1;
+ for (auto const& item : mpData->m_aItems)
+ {
+ if ( item.mbBreak )
+ ++nLine;
+ if( &item == pCurrentItem)
+ break;
+ }
+ return nLine;
+}
+
+// returns the first displayable item in the given line
+ImplToolItem* ToolBox::ImplGetFirstValidItem( ImplToolItems::size_type nLine )
+{
+ if( !nLine || nLine > mnCurLines )
+ return nullptr;
+
+ nLine--;
+
+ ImplToolItems::iterator it = mpData->m_aItems.begin();
+ while( it != mpData->m_aItems.end() )
+ {
+ // find correct line
+ if ( it->mbBreak )
+ nLine--;
+ if( !nLine )
+ {
+ // find first useful item
+ while( it != mpData->m_aItems.end() && ((it->meType != ToolBoxItemType::BUTTON) ||
+ /*!it->mbEnabled ||*/ !it->mbVisible || ImplIsFixedControl( &(*it) )) )
+ {
+ ++it;
+ if( it == mpData->m_aItems.end() || it->mbBreak )
+ return nullptr; // no valid items in this line
+ }
+ return &(*it);
+ }
+ ++it;
+ }
+
+ return (it == mpData->m_aItems.end()) ? nullptr : &(*it);
+}
+
+ToolBox::ImplToolItems::size_type ToolBox::ImplFindItemPos( const ImplToolItem* pItem, const ImplToolItems& rList )
+{
+ if( pItem )
+ {
+ for( ImplToolItems::size_type nPos = 0; nPos < rList.size(); ++nPos )
+ if( &rList[ nPos ] == pItem )
+ return nPos;
+ }
+ return ITEM_NOTFOUND;
+}
+
+void ToolBox::ChangeHighlight( ImplToolItems::size_type nPos )
+{
+ if ( nPos < GetItemCount() ) {
+ ImplGrabFocus( GetFocusFlags::NONE );
+ ImplChangeHighlight ( ImplGetItem ( GetItemId ( nPos ) ) );
+ }
+}
+
+void ToolBox::ImplChangeHighlight( ImplToolItem const * pItem, bool bNoGrabFocus )
+{
+ // avoid recursion due to focus change
+ if( mbChangingHighlight )
+ return;
+
+ mbChangingHighlight = true;
+
+ ImplToolItem* pOldItem = nullptr;
+
+ if ( mnHighItemId )
+ {
+ ImplHideFocus();
+ ImplToolItems::size_type nPos = GetItemPos( mnHighItemId );
+ pOldItem = ImplGetItem( mnHighItemId );
+ // #i89962# ImplDrawItem can cause Invalidate/Update
+ // which will in turn ImplShowFocus again
+ // set mnHighItemId to 0 already to prevent this hen/egg problem
+ mnHighItemId = ToolBoxItemId(0);
+ InvalidateItem(nPos);
+ CallEventListeners( VclEventId::ToolboxHighlightOff, reinterpret_cast< void* >( nPos ) );
+ }
+
+ if( !bNoGrabFocus && pItem != pOldItem && pOldItem && pOldItem->mpWindow )
+ {
+ // move focus into toolbox
+ GrabFocus();
+ }
+
+ if( pItem )
+ {
+ ImplToolItems::size_type aPos = ToolBox::ImplFindItemPos( pItem, mpData->m_aItems );
+ if( aPos != ITEM_NOTFOUND)
+ {
+ // check for line breaks
+ ImplToolItems::size_type nLine = ImplGetItemLine( pItem );
+
+ if( nLine >= mnCurLine + mnVisLines )
+ {
+ mnCurLine = nLine - mnVisLines + 1;
+ mbFormat = true;
+ }
+ else if ( nLine < mnCurLine )
+ {
+ mnCurLine = nLine;
+ mbFormat = true;
+ }
+
+ if( mbFormat )
+ {
+ ImplFormat();
+ }
+
+ mnHighItemId = pItem->mnId;
+ InvalidateItem(aPos);
+
+ ImplShowFocus();
+
+ if( pItem->mpWindow )
+ pItem->mpWindow->GrabFocus();
+ if( pItem != pOldItem )
+ CallEventListeners( VclEventId::ToolboxHighlight );
+ }
+ }
+ else
+ {
+ ImplHideFocus();
+ mnHighItemId = ToolBoxItemId(0);
+ mnCurPos = ITEM_NOTFOUND;
+ }
+
+ mbChangingHighlight = false;
+}
+
+// check for keyboard accessible items
+static bool ImplIsValidItem( const ImplToolItem* pItem, bool bNotClipped )
+{
+ bool bValid = (pItem && pItem->meType == ToolBoxItemType::BUTTON && pItem->mbVisible && !ImplIsFixedControl( pItem )
+ && pItem->mbEnabled);
+ if( bValid && bNotClipped && pItem->IsClipped() )
+ bValid = false;
+ return bValid;
+}
+
+bool ToolBox::ImplChangeHighlightUpDn( bool bUp, bool bNoCycle )
+{
+ ImplToolItem* pToolItem = ImplGetItem( mnHighItemId );
+
+ if( !pToolItem || !mnHighItemId )
+ {
+ // menubutton highlighted ?
+ if( mpData->mbMenubuttonSelected )
+ {
+ mpData->mbMenubuttonSelected = false;
+ if( bUp )
+ {
+ // select last valid non-clipped item
+ ImplToolItem* pItem = nullptr;
+ auto it = std::find_if(mpData->m_aItems.rbegin(), mpData->m_aItems.rend(),
+ [](const ImplToolItem& rItem) { return ImplIsValidItem( &rItem, true ); });
+ if( it != mpData->m_aItems.rend() )
+ pItem = &(*it);
+
+ InvalidateMenuButton();
+ ImplChangeHighlight( pItem );
+ }
+ else
+ {
+ // select first valid non-clipped item
+ ImplToolItems::iterator it = std::find_if(mpData->m_aItems.begin(), mpData->m_aItems.end(),
+ [](const ImplToolItem& rItem) { return ImplIsValidItem( &rItem, true ); });
+ if( it != mpData->m_aItems.end() )
+ {
+ InvalidateMenuButton();
+ ImplChangeHighlight( &(*it) );
+ }
+ }
+ return true;
+ }
+
+ if( bUp )
+ {
+ // Select first valid item
+ ImplToolItems::iterator it = std::find_if(mpData->m_aItems.begin(), mpData->m_aItems.end(),
+ [](const ImplToolItem& rItem) { return ImplIsValidItem( &rItem, false ); });
+
+ // select the menu button if a clipped item would be selected
+ if( (it != mpData->m_aItems.end() && &(*it) == ImplGetFirstClippedItem()) && IsMenuEnabled() )
+ {
+ ImplChangeHighlight( nullptr );
+ mpData->mbMenubuttonSelected = true;
+ InvalidateMenuButton();
+ }
+ else
+ ImplChangeHighlight( (it != mpData->m_aItems.end()) ? &(*it) : nullptr );
+ return true;
+ }
+ else
+ {
+ // Select last valid item
+
+ // docked toolbars have the menubutton as last item - if this button is enabled
+ if( ImplHasClippedItems() && IsMenuEnabled() && !ImplIsFloatingMode() )
+ {
+ ImplChangeHighlight( nullptr );
+ mpData->mbMenubuttonSelected = true;
+ InvalidateMenuButton();
+ }
+ else
+ {
+ ImplToolItem* pItem = nullptr;
+ auto it = std::find_if(mpData->m_aItems.rbegin(), mpData->m_aItems.rend(),
+ [](const ImplToolItem& rItem) { return ImplIsValidItem( &rItem, false ); });
+ if( it != mpData->m_aItems.rend() )
+ pItem = &(*it);
+
+ ImplChangeHighlight( pItem );
+ }
+ return true;
+ }
+ }
+
+ assert(pToolItem);
+
+ ImplToolItems::size_type pos = ToolBox::ImplFindItemPos( pToolItem, mpData->m_aItems );
+ ImplToolItems::size_type nCount = mpData->m_aItems.size();
+
+ ImplToolItems::size_type i=0;
+ do
+ {
+ if( bUp )
+ {
+ if( !pos-- )
+ {
+ if( bNoCycle )
+ return false;
+
+ // highlight the menu button if it is the last item
+ if( ImplHasClippedItems() && IsMenuEnabled() && !ImplIsFloatingMode() )
+ {
+ ImplChangeHighlight( nullptr );
+ mpData->mbMenubuttonSelected = true;
+ InvalidateMenuButton();
+ return true;
+ }
+ else
+ pos = nCount-1;
+ }
+ }
+ else
+ {
+ if( ++pos >= nCount )
+ {
+ if( bNoCycle )
+ return false;
+
+ // highlight the menu button if it is the last item
+ if( ImplHasClippedItems() && IsMenuEnabled() && !ImplIsFloatingMode() )
+ {
+ ImplChangeHighlight( nullptr );
+ mpData->mbMenubuttonSelected = true;
+ InvalidateMenuButton();
+ return true;
+ }
+ else
+ pos = 0;
+ }
+ }
+
+ pToolItem = &mpData->m_aItems[pos];
+
+ if ( ImplIsValidItem( pToolItem, false ) )
+ break;
+
+ } while( ++i < nCount);
+
+ if( pToolItem->IsClipped() && IsMenuEnabled() )
+ {
+ // select the menu button if a clipped item would be selected
+ ImplChangeHighlight( nullptr );
+ mpData->mbMenubuttonSelected = true;
+ InvalidateMenuButton();
+ }
+ else if( i != nCount )
+ ImplChangeHighlight( pToolItem );
+ else
+ return false;
+
+ return true;
+}
+
+void ToolBox::ImplShowFocus()
+{
+ if( mnHighItemId && HasFocus() )
+ {
+ ImplToolItem* pItem = ImplGetItem( mnHighItemId );
+ if (pItem && pItem->mpWindow && !pItem->mpWindow->isDisposed())
+ {
+ vcl::Window *pWin = pItem->mpWindow->ImplGetWindowImpl()->mpBorderWindow ? pItem->mpWindow->ImplGetWindowImpl()->mpBorderWindow.get() : pItem->mpWindow.get();
+ pWin->ImplGetWindowImpl()->mbDrawSelectionBackground = true;
+ pWin->Invalidate();
+ }
+ }
+}
+
+void ToolBox::ImplHideFocus()
+{
+ if( mnHighItemId )
+ {
+ mpData->mbMenubuttonWasLastSelected = false;
+ ImplToolItem* pItem = ImplGetItem( mnHighItemId );
+ if( pItem && pItem->mpWindow )
+ {
+ vcl::Window *pWin = pItem->mpWindow->ImplGetWindowImpl()->mpBorderWindow ? pItem->mpWindow->ImplGetWindowImpl()->mpBorderWindow.get() : pItem->mpWindow.get();
+ pWin->ImplGetWindowImpl()->mbDrawSelectionBackground = false;
+ pWin->Invalidate();
+ }
+ }
+
+ if ( mpData && mpData->mbMenubuttonSelected )
+ {
+ mpData->mbMenubuttonWasLastSelected = true;
+ // remove highlight from menubutton
+ mpData->mbMenubuttonSelected = false;
+ InvalidateMenuButton();
+ }
+}
+
+void ToolBox::SetToolbarLayoutMode( ToolBoxLayoutMode eLayout )
+{
+ if ( meLayoutMode != eLayout )
+ meLayoutMode = eLayout;
+}
+
+void ToolBox::SetToolBoxTextPosition( ToolBoxTextPosition ePosition )
+{
+ meTextPosition = ePosition;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/toolbox2.cxx b/vcl/source/window/toolbox2.cxx
new file mode 100644
index 000000000..5a1112da5
--- /dev/null
+++ b/vcl/source/window/toolbox2.cxx
@@ -0,0 +1,1781 @@
+/* -*- 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 <vcl/uitest/logger.hxx>
+#include <sal/log.hxx>
+
+#include <comphelper/base64.hxx>
+#include <comphelper/processfactory.hxx>
+#include <boost/property_tree/ptree.hpp>
+
+#include <vcl/cvtgrf.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/IconThemeInfo.hxx>
+#include <vcl/commandinfoprovider.hxx>
+
+#include <svdata.hxx>
+#include <brdwin.hxx>
+#include <toolbox.h>
+
+#include <unotools/confignode.hxx>
+#include <tools/json_writer.hxx>
+
+#include <vcl/uitest/uiobject.hxx>
+
+#include "impldockingwrapper.hxx"
+
+using namespace vcl;
+
+#define TB_SEP_SIZE 8 // Separator size
+
+
+ImplToolBoxPrivateData::ImplToolBoxPrivateData()
+{
+ meButtonSize = ToolBoxButtonSize::DontCare;
+ mpMenu = VclPtr<PopupMenu>::Create();
+
+ maMenuType = ToolBoxMenuType::NONE;
+ maMenubuttonItem.maItemSize = Size( TB_MENUBUTTON_SIZE+TB_MENUBUTTON_OFFSET, TB_MENUBUTTON_SIZE+TB_MENUBUTTON_OFFSET );
+ maMenubuttonItem.meState = TRISTATE_FALSE;
+ mnMenuButtonWidth = TB_MENUBUTTON_SIZE;
+
+ mbIsLocked = false;
+ mbNativeButtons = false;
+ mbIsPaintLocked = false;
+ mbAssumeDocked = false;
+ mbAssumePopupMode = false;
+ mbAssumeFloating = false;
+ mbKeyInputDisabled = false;
+ mbMenubuttonSelected = false;
+ mbMenubuttonWasLastSelected = false;
+ mbWillUsePopupMode = false;
+ mbDropDownByKeyboard = false;
+}
+
+ImplToolBoxPrivateData::~ImplToolBoxPrivateData()
+{
+ m_pLayoutData.reset();
+ mpMenu.disposeAndClear();
+}
+
+void ImplToolItem::init(ToolBoxItemId nItemId, ToolBoxItemBits nItemBits,
+ bool bEmptyBtn)
+{
+ mnId = nItemId;
+ mpWindow = nullptr;
+ mbNonInteractiveWindow = false;
+ mpUserData = nullptr;
+ meType = ToolBoxItemType::BUTTON;
+ mnBits = nItemBits;
+ meState = TRISTATE_FALSE;
+ mbEnabled = true;
+ mbVisible = true;
+ mbEmptyBtn = bEmptyBtn;
+ mbShowWindow = false;
+ mbBreak = false;
+ mnSepSize = TB_SEP_SIZE;
+ mnDropDownArrowWidth = TB_DROPDOWNARROWWIDTH;
+ mnImageAngle = 0_deg10;
+ mbMirrorMode = false;
+ mbVisibleText = false;
+ mbExpand = false;
+}
+
+ImplToolItem::ImplToolItem()
+{
+ init(ToolBoxItemId(0), ToolBoxItemBits::NONE, true);
+}
+
+ImplToolItem::ImplToolItem( ToolBoxItemId nItemId, const Image& rImage,
+ ToolBoxItemBits nItemBits ) :
+ maImage( rImage )
+{
+ init(nItemId, nItemBits, false);
+}
+
+ImplToolItem::ImplToolItem( ToolBoxItemId nItemId, const OUString& rText,
+ ToolBoxItemBits nItemBits ) :
+ maText( rText )
+{
+ init(nItemId, nItemBits, false);
+}
+
+ImplToolItem::ImplToolItem( ToolBoxItemId nItemId, const Image& rImage,
+ const OUString& rText, ToolBoxItemBits nItemBits ) :
+ maImage( rImage ),
+ maText( rText )
+{
+ init(nItemId, nItemBits, false);
+}
+
+Size ImplToolItem::GetSize( bool bHorz, bool bCheckMaxWidth, tools::Long maxWidth, const Size& rDefaultSize )
+{
+ Size aSize( rDefaultSize ); // the size of 'standard' toolbox items
+ // non-standard items are eg windows or buttons with text
+
+ if ( (meType == ToolBoxItemType::BUTTON) || (meType == ToolBoxItemType::SPACE) )
+ {
+ aSize = maItemSize;
+
+ if ( mpWindow && bHorz )
+ {
+ // get size of item window and check if it fits
+ // no windows in vertical toolbars (the default is mbShowWindow=false)
+ Size aWinSize = mpWindow->GetSizePixel();
+
+ if (mpWindow->GetStyle() & WB_NOLABEL)
+ // Window wants no label? Then don't check width, it'll be just
+ // clipped.
+ bCheckMaxWidth = false;
+
+ if ( !bCheckMaxWidth || (aWinSize.Width() <= maxWidth) )
+ {
+ aSize.setWidth( aWinSize.Width() );
+ aSize.setHeight( aWinSize.Height() );
+ mbShowWindow = true;
+ }
+ else
+ {
+ if ( mbEmptyBtn )
+ {
+ aSize.setWidth( 0 );
+ aSize.setHeight( 0 );
+ }
+ }
+ }
+ }
+ else if ( meType == ToolBoxItemType::SEPARATOR )
+ {
+ if ( bHorz )
+ {
+ aSize.setWidth( mnSepSize );
+ aSize.setHeight( rDefaultSize.Height() );
+ }
+ else
+ {
+ aSize.setWidth( rDefaultSize.Width() );
+ aSize.setHeight( mnSepSize );
+ }
+ }
+ else if ( meType == ToolBoxItemType::BREAK )
+ {
+ aSize.setWidth( 0 );
+ aSize.setHeight( 0 );
+ }
+
+ return aSize;
+}
+
+void ImplToolItem::DetermineButtonDrawStyle( ButtonType eButtonType, bool& rbImage, bool& rbText ) const
+{
+ if ( meType != ToolBoxItemType::BUTTON )
+ {
+ // no button -> draw nothing
+ rbImage = rbText = false;
+ return;
+ }
+
+ bool bHasImage;
+ bool bHasText;
+
+ // check for image and/or text
+ bHasImage = !!maImage;
+ bHasText = !maText.isEmpty();
+
+ // prefer images if symbolonly buttons are drawn
+ // prefer texts if textonly buttons are drawn
+
+ if ( eButtonType == ButtonType::SYMBOLONLY ) // drawing icons only
+ {
+ if( bHasImage || !bHasText )
+ {
+ rbImage = true;
+ rbText = false;
+ }
+ else
+ {
+ rbImage = false;
+ rbText = true;
+ }
+ }
+ else if ( eButtonType == ButtonType::TEXT ) // drawing text only
+ {
+ if( bHasText || !bHasImage )
+ {
+ rbImage = false;
+ rbText = true;
+ }
+ else
+ {
+ rbImage = true;
+ rbText = false;
+ }
+ }
+ else // drawing icons and text both
+ {
+ rbImage = true;
+ rbText = true;
+ }
+}
+
+tools::Rectangle ImplToolItem::GetDropDownRect( bool bHorz ) const
+{
+ tools::Rectangle aRect;
+ if( (mnBits & ToolBoxItemBits::DROPDOWN) && !maRect.IsEmpty() )
+ {
+ aRect = maRect;
+ if( mbVisibleText && !bHorz )
+ // item will be rotated -> place dropdown to the bottom
+ aRect.SetTop( aRect.Bottom() - mnDropDownArrowWidth );
+ else
+ // place dropdown to the right
+ aRect.SetLeft( aRect.Right() - mnDropDownArrowWidth );
+ }
+ return aRect;
+}
+
+bool ImplToolItem::IsClipped() const
+{
+ return ( meType == ToolBoxItemType::BUTTON && mbVisible && maRect.IsEmpty() );
+}
+
+bool ImplToolItem::IsItemHidden() const
+{
+ return ( meType == ToolBoxItemType::BUTTON && !mbVisible );
+}
+
+void ToolBox::ImplInvalidate( bool bNewCalc, bool bFullPaint )
+{
+ ImplUpdateInputEnable();
+
+ if ( bNewCalc )
+ mbCalc = true;
+
+ if ( bFullPaint )
+ {
+ mbFormat = true;
+
+ // do we need to redraw?
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ Invalidate( tools::Rectangle( mnLeftBorder, mnTopBorder,
+ mnDX-mnRightBorder-1, mnDY-mnBottomBorder-1 ) );
+ mpIdle->Stop();
+ }
+ }
+ else
+ {
+ if ( !mbFormat )
+ {
+ mbFormat = true;
+
+ // do we need to redraw?
+ if ( IsReallyVisible() && IsUpdateMode() )
+ mpIdle->Start();
+ }
+ }
+
+ // request new layout by layoutmanager
+ CallEventListeners( VclEventId::ToolboxFormatChanged );
+}
+
+void ToolBox::ImplUpdateItem( ImplToolItems::size_type nIndex )
+{
+ // do we need to redraw?
+ if ( !(IsReallyVisible() && IsUpdateMode()) )
+ return;
+
+ if ( nIndex == ITEM_NOTFOUND )
+ {
+ // #i52217# no immediate draw as this might lead to paint problems
+ Invalidate( tools::Rectangle( mnLeftBorder, mnTopBorder, mnDX-mnRightBorder-1, mnDY-mnBottomBorder-1 ) );
+ }
+ else
+ {
+ if ( !mbFormat )
+ {
+ // #i52217# no immediate draw as this might lead to paint problems
+ Invalidate( mpData->m_aItems[nIndex].maRect );
+ }
+ else
+ maPaintRect.Union( mpData->m_aItems[nIndex].maRect );
+ }
+}
+
+void ToolBox::Click()
+{
+ CallEventListeners( VclEventId::ToolboxClick );
+ maClickHdl.Call( this );
+ UITestLogger::getInstance().logAction( this, VclEventId::ToolboxClick);
+}
+
+void ToolBox::DoubleClick()
+{
+ CallEventListeners( VclEventId::ToolboxDoubleClick );
+ maDoubleClickHdl.Call( this );
+}
+
+void ToolBox::Activate()
+{
+ mnActivateCount++;
+ CallEventListeners( VclEventId::ToolboxActivate );
+ maActivateHdl.Call( this );
+}
+
+void ToolBox::Deactivate()
+{
+ mnActivateCount--;
+ CallEventListeners( VclEventId::ToolboxDeactivate );
+ maDeactivateHdl.Call( this );
+}
+
+void ToolBox::Highlight()
+{
+ CallEventListeners( VclEventId::ToolboxHighlight );
+}
+
+FactoryFunction ToolBox::GetUITestFactory() const
+{
+ return ToolBoxUIObject::create;
+}
+
+void ToolBox::Select()
+{
+ VclPtr<vcl::Window> xWindow = this;
+
+ CallEventListeners( VclEventId::ToolboxSelect );
+ maSelectHdl.Call( this );
+
+ if ( xWindow->isDisposed() )
+ return;
+
+ // TODO: GetFloatingWindow in DockingWindow is currently inline, change it to check dockingwrapper
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper && pWrapper->GetFloatingWindow() && static_cast<FloatingWindow*>(pWrapper->GetFloatingWindow())->IsInPopupMode() )
+ static_cast<FloatingWindow*>(pWrapper->GetFloatingWindow())->EndPopupMode();
+}
+
+void ToolBox::InsertItem( ToolBoxItemId nItemId, const Image& rImage, ToolBoxItemBits nBits, ImplToolItems::size_type nPos )
+{
+ SAL_WARN_IF( !nItemId, "vcl", "ToolBox::InsertItem(): ItemId == 0" );
+ SAL_WARN_IF( GetItemPos( nItemId ) != ITEM_NOTFOUND, "vcl",
+ "ToolBox::InsertItem(): ItemId already exists" );
+
+ // create item and add to list
+ mpData->m_aItems.insert( (nPos < mpData->m_aItems.size()) ? mpData->m_aItems.begin()+nPos : mpData->m_aItems.end(),
+ ImplToolItem( nItemId, rImage, nBits ) );
+ mpData->ImplClearLayoutData();
+
+ ImplInvalidate( true );
+
+ // Notify
+ ImplToolItems::size_type nNewPos = ( nPos == APPEND ) ? ( mpData->m_aItems.size() - 1 ) : nPos;
+ CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >(nNewPos ) );
+}
+
+void ToolBox::InsertItem( ToolBoxItemId nItemId, const Image& rImage, const OUString& rText, ToolBoxItemBits nBits,
+ ImplToolItems::size_type nPos )
+{
+ SAL_WARN_IF( !nItemId, "vcl", "ToolBox::InsertItem(): ItemId == 0" );
+ SAL_WARN_IF( GetItemPos( nItemId ) != ITEM_NOTFOUND, "vcl",
+ "ToolBox::InsertItem(): ItemId already exists" );
+
+ // create item and add to list
+ mpData->m_aItems.insert( (nPos < mpData->m_aItems.size()) ? mpData->m_aItems.begin()+nPos : mpData->m_aItems.end(),
+ ImplToolItem( nItemId, rImage, MnemonicGenerator::EraseAllMnemonicChars(rText), nBits ) );
+ mpData->ImplClearLayoutData();
+
+ ImplInvalidate( true );
+
+ // Notify
+ ImplToolItems::size_type nNewPos = ( nPos == APPEND ) ? ( mpData->m_aItems.size() - 1 ) : nPos;
+ CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >( nNewPos ) );
+}
+
+void ToolBox::InsertItem( ToolBoxItemId nItemId, const OUString& rText, ToolBoxItemBits nBits, ImplToolItems::size_type nPos )
+{
+ SAL_WARN_IF( !nItemId, "vcl", "ToolBox::InsertItem(): ItemId == 0" );
+ SAL_WARN_IF( GetItemPos( nItemId ) != ITEM_NOTFOUND, "vcl",
+ "ToolBox::InsertItem(): ItemId already exists" );
+
+ // create item and add to list
+ mpData->m_aItems.insert( (nPos < mpData->m_aItems.size()) ? mpData->m_aItems.begin()+nPos : mpData->m_aItems.end(),
+ ImplToolItem( nItemId, MnemonicGenerator::EraseAllMnemonicChars(rText), nBits ) );
+ mpData->ImplClearLayoutData();
+
+ ImplInvalidate( true );
+
+ // Notify
+ ImplToolItems::size_type nNewPos = ( nPos == APPEND ) ? ( mpData->m_aItems.size() - 1 ) : nPos;
+ CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >( nNewPos ) );
+}
+
+void ToolBox::InsertItem(const OUString& rCommand, const css::uno::Reference<css::frame::XFrame>& rFrame, ToolBoxItemBits nBits,
+ const Size& rRequestedSize, ImplToolItems::size_type nPos)
+{
+ OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(rFrame));
+ auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rCommand, aModuleName);
+ OUString aLabel(vcl::CommandInfoProvider::GetLabelForCommand(aProperties));
+ OUString aTooltip(vcl::CommandInfoProvider::GetTooltipForCommand(rCommand, aProperties, rFrame));
+ Image aImage(CommandInfoProvider::GetImageForCommand(rCommand, rFrame, GetImageSize()));
+
+ ToolBoxItemId nItemId(GetItemCount() + 1);
+ //TODO: ImplToolItems::size_type -> sal_uInt16!
+ InsertItem(nItemId, aImage, aLabel, nBits, nPos);
+ SetItemCommand(nItemId, rCommand);
+ SetQuickHelpText(nItemId, aTooltip);
+
+ // set the minimal size
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+ if ( pItem )
+ pItem->maMinimalItemSize = rRequestedSize;
+}
+
+void ToolBox::InsertWindow( ToolBoxItemId nItemId, vcl::Window* pWindow,
+ ToolBoxItemBits nBits, ImplToolItems::size_type nPos )
+{
+ SAL_WARN_IF( !nItemId, "vcl", "ToolBox::InsertWindow(): ItemId == 0" );
+ SAL_WARN_IF( GetItemPos( nItemId ) != ITEM_NOTFOUND, "vcl",
+ "ToolBox::InsertWindow(): ItemId already exists" );
+
+ // create item and add to list
+ ImplToolItem aItem;
+ aItem.mnId = nItemId;
+ aItem.meType = ToolBoxItemType::BUTTON;
+ aItem.mnBits = nBits;
+ aItem.mpWindow = pWindow;
+ mpData->m_aItems.insert( (nPos < mpData->m_aItems.size()) ? mpData->m_aItems.begin()+nPos : mpData->m_aItems.end(), aItem );
+ mpData->ImplClearLayoutData();
+
+ if ( pWindow )
+ pWindow->Hide();
+
+ ImplInvalidate( true );
+
+ // Notify
+ ImplToolItems::size_type nNewPos = ( nPos == APPEND ) ? ( mpData->m_aItems.size() - 1 ) : nPos;
+ CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >( nNewPos ) );
+}
+
+void ToolBox::InsertSpace()
+{
+ // create item and add to list
+ ImplToolItem aItem;
+ aItem.meType = ToolBoxItemType::SPACE;
+ aItem.mbEnabled = false;
+ mpData->m_aItems.push_back( aItem );
+ mpData->ImplClearLayoutData();
+
+ ImplInvalidate();
+
+ // Notify
+ ImplToolItems::size_type nNewPos = mpData->m_aItems.size() - 1;
+ CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >( nNewPos ) );
+}
+
+void ToolBox::InsertSeparator( ImplToolItems::size_type nPos, sal_uInt16 nPixSize )
+{
+ // create item and add to list
+ ImplToolItem aItem;
+ aItem.meType = ToolBoxItemType::SEPARATOR;
+ aItem.mbEnabled = false;
+ if ( nPixSize )
+ aItem.mnSepSize = nPixSize;
+ mpData->m_aItems.insert( (nPos < mpData->m_aItems.size()) ? mpData->m_aItems.begin()+nPos : mpData->m_aItems.end(), aItem );
+ mpData->ImplClearLayoutData();
+
+ ImplInvalidate();
+
+ // Notify
+ ImplToolItems::size_type nNewPos = ( nPos == APPEND ) ? ( mpData->m_aItems.size() - 1 ) : nPos;
+ CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >( nNewPos ) );
+}
+
+void ToolBox::InsertBreak( ImplToolItems::size_type nPos )
+{
+ // create item and add to list
+ ImplToolItem aItem;
+ aItem.meType = ToolBoxItemType::BREAK;
+ aItem.mbEnabled = false;
+ mpData->m_aItems.insert( (nPos < mpData->m_aItems.size()) ? mpData->m_aItems.begin()+nPos : mpData->m_aItems.end(), aItem );
+ mpData->ImplClearLayoutData();
+
+ ImplInvalidate();
+
+ // Notify
+ ImplToolItems::size_type nNewPos = ( nPos == APPEND ) ? ( mpData->m_aItems.size() - 1 ) : nPos;
+ CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >( nNewPos ) );
+}
+
+void ToolBox::RemoveItem( ImplToolItems::size_type nPos )
+{
+ if( nPos >= mpData->m_aItems.size() )
+ return;
+
+ bool bMustCalc;
+ bMustCalc = mpData->m_aItems[nPos].meType == ToolBoxItemType::BUTTON;
+
+ if ( mpData->m_aItems[nPos].mpWindow )
+ mpData->m_aItems[nPos].mpWindow->Hide();
+
+ // add the removed item to PaintRect
+ maPaintRect.Union( mpData->m_aItems[nPos].maRect );
+
+ // ensure not to delete in the Select-Handler
+ if ( mpData->m_aItems[nPos].mnId == mnCurItemId )
+ mnCurItemId = ToolBoxItemId(0);
+ if ( mpData->m_aItems[nPos].mnId == mnHighItemId )
+ mnHighItemId = ToolBoxItemId(0);
+
+ ImplInvalidate( bMustCalc );
+
+ mpData->m_aItems.erase( mpData->m_aItems.begin()+nPos );
+ mpData->ImplClearLayoutData();
+
+ // Notify
+ CallEventListeners( VclEventId::ToolboxItemRemoved, reinterpret_cast< void* >( nPos ) );
+}
+
+void ToolBox::CopyItem( const ToolBox& rToolBox, ToolBoxItemId nItemId )
+{
+ SAL_WARN_IF( GetItemPos( nItemId ) != ITEM_NOTFOUND, "vcl",
+ "ToolBox::CopyItem(): ItemId already exists" );
+
+ ImplToolItems::size_type nPos = rToolBox.GetItemPos( nItemId );
+
+ // found item
+ if ( nPos == ITEM_NOTFOUND )
+ return;
+
+ // push ToolBox item onto the list
+ ImplToolItem aNewItem = rToolBox.mpData->m_aItems[nPos];
+ // reset state
+ aNewItem.mpWindow = nullptr;
+ aNewItem.mbShowWindow = false;
+
+ mpData->m_aItems.push_back( aNewItem );
+ mpData->ImplClearLayoutData();
+ // redraw ToolBox
+ ImplInvalidate();
+
+ // Notify
+ ImplToolItems::size_type nNewPos2 = mpData->m_aItems.size() - 1;
+ CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >( nNewPos2 ) );
+}
+
+void ToolBox::Clear()
+{
+ mpData->m_aItems.clear();
+ mpData->ImplClearLayoutData();
+
+ // ensure not to delete in the Select-Handler
+ mnCurItemId = ToolBoxItemId(0);
+ mnHighItemId = ToolBoxItemId(0);
+
+ ImplInvalidate( true, true );
+
+ // Notify
+ CallEventListeners( VclEventId::ToolboxAllItemsChanged );
+}
+
+void ToolBox::SetButtonType( ButtonType eNewType )
+{
+ if ( meButtonType != eNewType )
+ {
+ meButtonType = eNewType;
+
+ // better redraw everything, as otherwise there might be problems
+ // with regions that were copied with CopyBits
+ ImplInvalidate( true );
+ }
+}
+
+void ToolBox::SetToolboxButtonSize( ToolBoxButtonSize eSize )
+{
+ if( mpData->meButtonSize != eSize )
+ {
+ mpData->meButtonSize = eSize;
+ mbCalc = true;
+ mbFormat = true;
+ }
+}
+
+ToolBoxButtonSize ToolBox::GetToolboxButtonSize() const
+{
+ return mpData->meButtonSize;
+}
+
+ImageType ToolBox::GetImageSize() const
+{
+ ImageType eImageType = ImageType::Size16;
+ if (mpData->meButtonSize == ToolBoxButtonSize::Large)
+ eImageType = ImageType::Size26;
+ else if (mpData->meButtonSize == ToolBoxButtonSize::Size32)
+ eImageType = ImageType::Size32;
+
+ return eImageType;
+}
+
+/*static*/ Size ToolBox::GetDefaultImageSize(ToolBoxButtonSize eToolBoxButtonSize)
+{
+ OutputDevice *pDefault = Application::GetDefaultDevice();
+ float fScaleFactor = pDefault ? pDefault->GetDPIScaleFactor() : 1.0;
+
+ Size aUnscaledSize(16, 16);
+
+ if (eToolBoxButtonSize == ToolBoxButtonSize::Large)
+ {
+ OUString iconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ aUnscaledSize = vcl::IconThemeInfo::SizeByThemeName(iconTheme);
+ }
+ else if (eToolBoxButtonSize == ToolBoxButtonSize::Size32)
+ {
+ aUnscaledSize = Size(32, 32);
+ }
+ return Size(aUnscaledSize.Width() * fScaleFactor,
+ aUnscaledSize.Height() * fScaleFactor);
+}
+
+Size ToolBox::GetDefaultImageSize() const
+{
+ return GetDefaultImageSize(GetToolboxButtonSize());
+}
+
+void ToolBox::SetAlign( WindowAlign eNewAlign )
+{
+ if ( meAlign == eNewAlign )
+ return;
+
+ meAlign = eNewAlign;
+
+ if ( ImplIsFloatingMode() )
+ return;
+
+ // set horizontal/vertical alignment
+ if ( (eNewAlign == WindowAlign::Left) || (eNewAlign == WindowAlign::Right) )
+ mbHorz = false;
+ else
+ mbHorz = true;
+
+ // Update the background according to Persona if necessary
+ ImplInitSettings( false, false, true );
+
+ // redraw everything, as the border has changed
+ mbCalc = true;
+ mbFormat = true;
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+}
+
+void ToolBox::SetLineCount( ImplToolItems::size_type nNewLines )
+{
+ if ( !nNewLines )
+ nNewLines = 1;
+
+ if ( mnLines != nNewLines )
+ {
+ mnLines = nNewLines;
+
+ // better redraw everything, as otherwise there might be problems
+ // with regions that were copied with CopyBits
+ Invalidate();
+ }
+}
+
+ToolBox::ImplToolItems::size_type ToolBox::GetItemCount() const
+{
+ return mpData ? mpData->m_aItems.size() : 0;
+}
+
+ToolBoxItemType ToolBox::GetItemType( ImplToolItems::size_type nPos ) const
+{
+ return (nPos < mpData->m_aItems.size()) ? mpData->m_aItems[nPos].meType : ToolBoxItemType::DONTKNOW;
+}
+
+ToolBox::ImplToolItems::size_type ToolBox::GetItemPos( ToolBoxItemId nItemId ) const
+{
+ if (mpData)
+ {
+ ImplToolItems::size_type nCount = mpData->m_aItems.size();
+ for( ImplToolItems::size_type nPos = 0; nPos < nCount; nPos++ )
+ if( mpData->m_aItems[nPos].mnId == nItemId )
+ return nPos;
+ }
+ return ITEM_NOTFOUND;
+}
+
+ToolBox::ImplToolItems::size_type ToolBox::GetItemPos( const Point& rPos ) const
+{
+ // search the item position on the given point
+ auto it = std::find_if(mpData->m_aItems.begin(), mpData->m_aItems.end(),
+ [&rPos](const ImplToolItem& rItem) { return rItem.maRect.Contains( rPos ); });
+
+ if( it != mpData->m_aItems.end() )
+ return std::distance(mpData->m_aItems.begin(), it);
+
+ return ITEM_NOTFOUND;
+}
+
+ToolBoxItemId ToolBox::GetItemId( ImplToolItems::size_type nPos ) const
+{
+ return (nPos < mpData->m_aItems.size()) ? mpData->m_aItems[nPos].mnId : ToolBoxItemId(0);
+}
+
+ToolBoxItemId ToolBox::GetItemId( const Point& rPos ) const
+{
+ // find item that was clicked
+ auto it = std::find_if(mpData->m_aItems.begin(), mpData->m_aItems.end(),
+ [&rPos](const ImplToolItem& rItem) { return rItem.maRect.Contains( rPos ); });
+
+ if( (it != mpData->m_aItems.end()) && (it->meType == ToolBoxItemType::BUTTON) )
+ return it->mnId;
+
+ return ToolBoxItemId(0);
+}
+
+Size ToolBox::GetItemContentSize( ToolBoxItemId nItemId )
+{
+ if ( mbCalc || mbFormat )
+ ImplFormat();
+
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+ if ( nPos < mpData->m_aItems.size() )
+ return mpData->m_aItems[nPos].maContentSize;
+ else
+ return Size();
+}
+
+ToolBoxItemId ToolBox::GetItemId(const OUString &rCommand) const
+{
+ if (!mpData)
+ return ToolBoxItemId(0);
+
+ auto it = std::find_if(mpData->m_aItems.begin(), mpData->m_aItems.end(),
+ [&rCommand](const ImplToolItem& rItem) { return rItem.maCommandStr == rCommand; });
+ if (it != mpData->m_aItems.end())
+ return it->mnId;
+
+ return ToolBoxItemId(0);
+}
+
+Point ToolBox::ImplGetPopupPosition( const tools::Rectangle& rRect ) const
+{
+ Point aPos;
+ if( !rRect.IsEmpty() )
+ {
+ tools::Rectangle aScreen = GetDesktopRectPixel();
+
+ // the popup should be positioned so that it will not cover
+ // the item rect and that it fits the desktop
+ // the preferred direction is always towards the center of
+ // the application window
+
+ Point devPos; // the position in device coordinates for screen comparison
+ switch( meAlign )
+ {
+ case WindowAlign::Top:
+ aPos = rRect.BottomLeft();
+ aPos.AdjustY( 1 );
+ devPos = OutputToAbsoluteScreenPixel( aPos );
+ if( devPos.Y() >= aScreen.Bottom() )
+ aPos.setY( rRect.Top() );
+ break;
+ case WindowAlign::Bottom:
+ aPos = rRect.TopLeft();
+ aPos.AdjustY( -1 );
+ devPos = OutputToAbsoluteScreenPixel( aPos );
+ if( devPos.Y() <= aScreen.Top() )
+ aPos.setY( rRect.Bottom() );
+ break;
+ case WindowAlign::Left:
+ aPos = rRect.TopRight();
+ aPos.AdjustX( 1 );
+ devPos = OutputToAbsoluteScreenPixel( aPos );
+ if( devPos.X() >= aScreen.Right() )
+ aPos.setX( rRect.Left() );
+ break;
+ case WindowAlign::Right:
+ aPos = rRect.TopLeft();
+ aPos.AdjustX( -1 );
+ devPos = OutputToAbsoluteScreenPixel( aPos );
+ if( devPos.X() <= aScreen.Left() )
+ aPos.setX( rRect.Right() );
+ break;
+ default:
+ break;
+ }
+ }
+ return aPos;
+}
+
+tools::Rectangle ToolBox::GetItemRect( ToolBoxItemId nItemId )
+{
+ if ( mbCalc || mbFormat )
+ ImplFormat();
+
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+ return GetItemPosRect( nPos );
+}
+
+tools::Rectangle ToolBox::GetItemPosRect( ImplToolItems::size_type nPos )
+{
+ if ( mbCalc || mbFormat )
+ ImplFormat();
+
+ if ( nPos < mpData->m_aItems.size() )
+ return mpData->m_aItems[nPos].maRect;
+ else
+ return tools::Rectangle();
+}
+
+tools::Rectangle const & ToolBox::GetOverflowRect() const
+{
+ return mpData->maMenubuttonItem.maRect;
+}
+
+bool ToolBox::ImplHasExternalMenubutton() const
+{
+ // check if the borderwindow (i.e. the decoration) provides the menu button
+ bool bRet = false;
+ if( ImplIsFloatingMode() )
+ {
+ // custom menu is placed in the decoration
+ ImplBorderWindow *pBorderWin = dynamic_cast<ImplBorderWindow*>( GetWindow( GetWindowType::Border ) );
+ if( pBorderWin && !pBorderWin->GetMenuRect().IsEmpty() )
+ bRet = true;
+ }
+ return bRet;
+}
+
+void ToolBox::SetItemBits( ToolBoxItemId nItemId, ToolBoxItemBits nBits )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos < GetItemCount() )
+ {
+ ToolBoxItemBits nOldBits = mpData->m_aItems[nPos].mnBits;
+ mpData->m_aItems[nPos].mnBits = nBits;
+ nBits &= ToolBoxItemBits::LEFT | ToolBoxItemBits::AUTOSIZE | ToolBoxItemBits::DROPDOWN;
+ nOldBits &= ToolBoxItemBits::LEFT | ToolBoxItemBits::AUTOSIZE | ToolBoxItemBits::DROPDOWN;
+ // trigger reformat when the item width has changed (dropdown arrow)
+ bool bFormat = ToolBoxItemBits(nBits & ToolBoxItemBits::DROPDOWN) != ToolBoxItemBits(nOldBits & ToolBoxItemBits::DROPDOWN);
+ if ( nBits != nOldBits )
+ ImplInvalidate( true, bFormat );
+ }
+}
+
+void ToolBox::SetItemWindowNonInteractive(ToolBoxItemId nItemId, bool bNonInteractive)
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos < GetItemCount() )
+ {
+ mpData->m_aItems[nPos].mbNonInteractiveWindow = bNonInteractive;
+ }
+}
+
+ToolBoxItemBits ToolBox::GetItemBits( ToolBoxItemId nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ return pItem->mnBits;
+ else
+ return ToolBoxItemBits::NONE;
+}
+
+void ToolBox::SetItemExpand( ToolBoxItemId nItemId, bool bExpand )
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+ if (!pItem)
+ return;
+
+ if (pItem->mbExpand != bExpand)
+ {
+ pItem->mbExpand = bExpand;
+ ImplInvalidate(true, true);
+ }
+}
+
+void ToolBox::SetItemData( ToolBoxItemId nItemId, void* pNewData )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos < mpData->m_aItems.size() )
+ {
+ mpData->m_aItems[nPos].mpUserData = pNewData;
+ ImplUpdateItem( nPos );
+ }
+}
+
+void* ToolBox::GetItemData( ToolBoxItemId nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ return pItem->mpUserData;
+ else
+ return nullptr;
+}
+
+void ToolBox::SetItemImage( ToolBoxItemId nItemId, const Image& rImage )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos == ITEM_NOTFOUND )
+ return;
+
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+ Size aOldSize = pItem->maImage.GetSizePixel();
+
+ pItem->maImage = rImage;
+
+ // only once all is calculated, do extra work
+ if (!mbCalc)
+ {
+ if (aOldSize != pItem->maImage.GetSizePixel())
+ ImplInvalidate( true );
+ else
+ ImplUpdateItem( nPos );
+ }
+}
+
+static Image ImplRotImage( const Image& rImage, Degree10 nAngle10 )
+{
+ BitmapEx aRotBitmapEx( rImage.GetBitmapEx() );
+
+ aRotBitmapEx.Rotate( nAngle10, COL_WHITE );
+
+ return Image( aRotBitmapEx );
+}
+
+void ToolBox::SetItemImageAngle( ToolBoxItemId nItemId, Degree10 nAngle10 )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos == ITEM_NOTFOUND )
+ return;
+
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+ Size aOldSize = pItem->maImage.GetSizePixel();
+
+ Degree10 nDeltaAngle = (nAngle10 - pItem->mnImageAngle) % 3600_deg10;
+ while( nDeltaAngle < 0_deg10 )
+ nDeltaAngle += 3600_deg10;
+
+ pItem->mnImageAngle = nAngle10;
+ if( nDeltaAngle && !!pItem->maImage )
+ {
+ pItem->maImage = ImplRotImage( pItem->maImage, nDeltaAngle );
+ }
+
+ if (!mbCalc)
+ {
+ if (aOldSize != pItem->maImage.GetSizePixel())
+ ImplInvalidate(true);
+ else
+ ImplUpdateItem(nPos);
+ }
+}
+
+static Image ImplMirrorImage( const Image& rImage )
+{
+ BitmapEx aMirrBitmapEx( rImage.GetBitmapEx() );
+
+ aMirrBitmapEx.Mirror( BmpMirrorFlags::Horizontal );
+
+ return Image( aMirrBitmapEx );
+}
+
+void ToolBox::SetItemImageMirrorMode( ToolBoxItemId nItemId, bool bMirror )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos == ITEM_NOTFOUND )
+ return;
+
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+
+ if (pItem->mbMirrorMode != bMirror)
+ {
+ pItem->mbMirrorMode = bMirror;
+ if (!!pItem->maImage)
+ {
+ pItem->maImage = ImplMirrorImage(pItem->maImage);
+ }
+
+ if (!mbCalc)
+ ImplUpdateItem(nPos);
+ }
+}
+
+Image ToolBox::GetItemImage(ToolBoxItemId nItemId) const
+{
+ ImplToolItem* pItem = ImplGetItem(nItemId);
+ return pItem ? pItem->maImage : Image();
+}
+
+void ToolBox::SetItemText( ToolBoxItemId nItemId, const OUString& rText )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos == ITEM_NOTFOUND )
+ return;
+
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+ // only once all is calculated, do extra work
+ if ( !mbCalc &&
+ ((meButtonType != ButtonType::SYMBOLONLY) || !pItem->maImage) )
+ {
+ tools::Long nOldWidth = GetOutDev()->GetCtrlTextWidth( pItem->maText );
+ pItem->maText = MnemonicGenerator::EraseAllMnemonicChars(rText);
+ mpData->ImplClearLayoutData();
+ if ( nOldWidth != GetOutDev()->GetCtrlTextWidth( pItem->maText ) )
+ ImplInvalidate( true );
+ else
+ ImplUpdateItem( nPos );
+ }
+ else
+ pItem->maText = MnemonicGenerator::EraseAllMnemonicChars(rText);
+
+ // Notify button changed event to prepare accessibility bridge
+ CallEventListeners( VclEventId::ToolboxButtonStateChanged, reinterpret_cast< void* >( nPos ) );
+
+ // Notify
+ CallEventListeners( VclEventId::ToolboxItemTextChanged, reinterpret_cast< void* >( nPos ) );
+}
+
+const OUString& ToolBox::GetItemText( ToolBoxItemId nItemId ) const
+{
+
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ assert( pItem );
+
+ return pItem->maText;
+}
+
+void ToolBox::SetItemWindow( ToolBoxItemId nItemId, vcl::Window* pNewWindow )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos != ITEM_NOTFOUND )
+ {
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+ pItem->mpWindow = pNewWindow;
+ if ( pNewWindow )
+ pNewWindow->Hide();
+ ImplInvalidate( true );
+ CallEventListeners( VclEventId::ToolboxItemWindowChanged, reinterpret_cast< void* >( nPos ) );
+ }
+}
+
+vcl::Window* ToolBox::GetItemWindow( ToolBoxItemId nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ return pItem->mpWindow;
+ else
+ return nullptr;
+}
+
+void ToolBox::EndSelection()
+{
+ if ( mbDrag )
+ {
+ // reset
+ mbDrag = false;
+ if (mnCurPos != ITEM_NOTFOUND)
+ InvalidateItem(mnCurPos);
+ EndTracking();
+ if (IsMouseCaptured())
+ ReleaseMouse();
+ Deactivate();
+ }
+
+ mnCurPos = ITEM_NOTFOUND;
+ mnCurItemId = ToolBoxItemId(0);
+ mnDownItemId = ToolBoxItemId(0);
+ mnMouseModifier = 0;
+}
+
+void ToolBox::SetItemDown( ToolBoxItemId nItemId, bool bDown )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos == ITEM_NOTFOUND )
+ return;
+
+ if ( bDown )
+ {
+ if ( nPos != mnCurPos )
+ {
+ mnCurPos = nPos;
+ InvalidateItem(mnCurPos);
+ GetOutDev()->Flush();
+ }
+ }
+ else
+ {
+ if ( nPos == mnCurPos )
+ {
+ InvalidateItem(mnCurPos);
+ GetOutDev()->Flush();
+ mnCurPos = ITEM_NOTFOUND;
+ }
+ }
+
+ if ( mbDrag )
+ {
+ mbDrag = false;
+ EndTracking();
+ if (IsMouseCaptured())
+ ReleaseMouse();
+ Deactivate();
+ }
+
+ mnCurItemId = ToolBoxItemId(0);
+ mnDownItemId = ToolBoxItemId(0);
+ mnMouseModifier = 0;
+}
+
+void ToolBox::SetItemState( ToolBoxItemId nItemId, TriState eState )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos == ITEM_NOTFOUND )
+ return;
+
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+
+ // the state has changed
+ if ( pItem->meState == eState )
+ return;
+
+ // if RadioCheck, un-check the previous
+ if ( (eState == TRISTATE_TRUE) && (pItem->mnBits & ToolBoxItemBits::AUTOCHECK) &&
+ (pItem->mnBits & ToolBoxItemBits::RADIOCHECK) )
+ {
+ ImplToolItem* pGroupItem;
+ ImplToolItems::size_type nGroupPos;
+ ImplToolItems::size_type nItemCount = GetItemCount();
+
+ nGroupPos = nPos;
+ while ( nGroupPos )
+ {
+ pGroupItem = &mpData->m_aItems[nGroupPos-1];
+ if ( pGroupItem->mnBits & ToolBoxItemBits::RADIOCHECK )
+ {
+ if ( pGroupItem->meState != TRISTATE_FALSE )
+ SetItemState( pGroupItem->mnId, TRISTATE_FALSE );
+ }
+ else
+ break;
+ nGroupPos--;
+ }
+
+ nGroupPos = nPos+1;
+ while ( nGroupPos < nItemCount )
+ {
+ pGroupItem = &mpData->m_aItems[nGroupPos];
+ if ( pGroupItem->mnBits & ToolBoxItemBits::RADIOCHECK )
+ {
+ if ( pGroupItem->meState != TRISTATE_FALSE )
+ SetItemState( pGroupItem->mnId, TRISTATE_FALSE );
+ }
+ else
+ break;
+ nGroupPos++;
+ }
+ }
+
+ pItem->meState = eState;
+ ImplUpdateItem( nPos );
+
+ // Notify button changed event to prepare accessibility bridge
+ CallEventListeners( VclEventId::ToolboxButtonStateChanged, reinterpret_cast< void* >( nPos ) );
+
+ // Call accessible listener to notify state_changed event
+ CallEventListeners( VclEventId::ToolboxItemUpdated, reinterpret_cast< void* >(nPos) );
+}
+
+TriState ToolBox::GetItemState( ToolBoxItemId nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ return pItem->meState;
+ else
+ return TRISTATE_FALSE;
+}
+
+void ToolBox::EnableItem( ToolBoxItemId nItemId, bool bEnable )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+
+ if ( nPos == ITEM_NOTFOUND )
+ return;
+
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+ if ( pItem->mbEnabled == bEnable )
+ return;
+
+ pItem->mbEnabled = bEnable;
+
+ // if existing, also redraw the window
+ if ( pItem->mpWindow )
+ pItem->mpWindow->Enable( pItem->mbEnabled );
+
+ // update item
+ ImplUpdateItem( nPos );
+
+ ImplUpdateInputEnable();
+
+ // Notify button changed event to prepare accessibility bridge
+ CallEventListeners( VclEventId::ToolboxButtonStateChanged, reinterpret_cast< void* >( nPos ) );
+
+ CallEventListeners( bEnable ? VclEventId::ToolboxItemEnabled : VclEventId::ToolboxItemDisabled, reinterpret_cast< void* >( nPos ) );
+}
+
+bool ToolBox::IsItemEnabled( ToolBoxItemId nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ return pItem->mbEnabled;
+ else
+ return false;
+}
+
+void ToolBox::ShowItem( ToolBoxItemId nItemId, bool bVisible )
+{
+ ImplToolItems::size_type nPos = GetItemPos( nItemId );
+ mpData->ImplClearLayoutData();
+
+ if ( nPos != ITEM_NOTFOUND )
+ {
+ ImplToolItem* pItem = &mpData->m_aItems[nPos];
+ if ( pItem->mbVisible != bVisible )
+ {
+ pItem->mbVisible = bVisible;
+ ImplInvalidate();
+ }
+ }
+}
+
+bool ToolBox::IsItemClipped( ToolBoxItemId nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ return pItem->IsClipped();
+ else
+ return false;
+}
+
+bool ToolBox::IsItemVisible( ToolBoxItemId nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ return pItem->mbVisible;
+ else
+ return false;
+}
+
+bool ToolBox::IsItemReallyVisible( ToolBoxItemId nItemId ) const
+{
+ // is the item on the visible area of the toolbox?
+ bool bRet = false;
+ tools::Rectangle aRect( mnLeftBorder, mnTopBorder, mnDX-mnRightBorder, mnDY-mnBottomBorder );
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem && pItem->mbVisible &&
+ !pItem->maRect.IsEmpty() && aRect.Overlaps( pItem->maRect ) )
+ {
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+void ToolBox::SetItemCommand(ToolBoxItemId nItemId, const OUString& rCommand)
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if (pItem)
+ pItem->maCommandStr = rCommand;
+}
+
+OUString ToolBox::GetItemCommand( ToolBoxItemId nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if (pItem)
+ return pItem->maCommandStr;
+
+ return OUString();
+}
+
+void ToolBox::SetQuickHelpText( ToolBoxItemId nItemId, const OUString& rText )
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ pItem->maQuickHelpText = rText;
+}
+
+OUString ToolBox::GetQuickHelpText( ToolBoxItemId nItemId ) const
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ return pItem->maQuickHelpText;
+ else
+ return OUString();
+}
+
+void ToolBox::SetHelpText( ToolBoxItemId nItemId, const OUString& rText )
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ pItem->maHelpText = rText;
+}
+
+const OUString& ToolBox::GetHelpText( ToolBoxItemId nItemId ) const
+{
+ return ImplGetHelpText( nItemId );
+}
+
+void ToolBox::SetHelpId( ToolBoxItemId nItemId, const OString& rHelpId )
+{
+ ImplToolItem* pItem = ImplGetItem( nItemId );
+
+ if ( pItem )
+ pItem->maHelpId = rHelpId;
+}
+
+// disable key input if all items are disabled
+void ToolBox::ImplUpdateInputEnable()
+{
+ mpData->mbKeyInputDisabled = std::none_of(mpData->m_aItems.begin(), mpData->m_aItems.end(),
+ [](const ImplToolItem& rItem) {
+ // at least one useful entry
+ return rItem.mbEnabled;
+ });
+}
+
+void ToolBox::ImplFillLayoutData()
+{
+ mpData->m_pLayoutData.emplace();
+
+ ImplToolItems::size_type nCount = mpData->m_aItems.size();
+ for( ImplToolItems::size_type i = 0; i < nCount; i++ )
+ {
+ ImplToolItem* pItem = &mpData->m_aItems[i];
+
+ // only draw, if the rectangle is within PaintRectangle
+ if (!pItem->maRect.IsEmpty())
+ InvalidateItem(i);
+ }
+}
+
+OUString ToolBox::GetDisplayText() const
+{
+ if( ! mpData->m_pLayoutData )
+ const_cast<ToolBox *>(this)->ImplFillLayoutData();
+ return mpData->m_pLayoutData ? mpData->m_pLayoutData->m_aDisplayText : OUString();
+}
+
+tools::Rectangle ToolBox::GetCharacterBounds( ToolBoxItemId nItemID, tools::Long nIndex )
+{
+ tools::Long nItemIndex = -1;
+ if( ! mpData->m_pLayoutData )
+ ImplFillLayoutData();
+ if( mpData->m_pLayoutData )
+ {
+ for( size_t i = 0; i < mpData->m_pLayoutData->m_aLineItemIds.size(); i++ )
+ {
+ if( mpData->m_pLayoutData->m_aLineItemIds[i] == nItemID )
+ {
+ nItemIndex = mpData->m_pLayoutData->m_aLineIndices[i];
+ break;
+ }
+ }
+ }
+ return (mpData->m_pLayoutData && nItemIndex != -1) ? mpData->m_pLayoutData->GetCharacterBounds( nItemIndex+nIndex ) : tools::Rectangle();
+}
+
+tools::Long ToolBox::GetIndexForPoint( const Point& rPoint, ToolBoxItemId& rItemID )
+{
+ tools::Long nIndex = -1;
+ rItemID = ToolBoxItemId(0);
+ if( ! mpData->m_pLayoutData )
+ ImplFillLayoutData();
+ if( mpData->m_pLayoutData )
+ {
+ nIndex = mpData->m_pLayoutData->GetIndexForPoint( rPoint );
+ for( size_t i = 0; i < mpData->m_pLayoutData->m_aLineIndices.size(); i++ )
+ {
+ if( mpData->m_pLayoutData->m_aLineIndices[i] <= nIndex &&
+ (i == mpData->m_pLayoutData->m_aLineIndices.size()-1 || mpData->m_pLayoutData->m_aLineIndices[i+1] > nIndex) )
+ {
+ rItemID = mpData->m_pLayoutData->m_aLineItemIds[i];
+ break;
+ }
+ }
+ }
+ return nIndex;
+}
+
+void ToolBox::SetDropdownClickHdl( const Link<ToolBox *, void>& rLink )
+{
+ if (mpData != nullptr) {
+ mpData->maDropdownClickHdl = rLink;
+ }
+}
+
+void ToolBox::SetMenuType( ToolBoxMenuType aType )
+{
+ if( aType == mpData->maMenuType )
+ return;
+
+ mpData->maMenuType = aType;
+ if( IsFloatingMode() )
+ {
+ // the menu button may have to be moved into the decoration which changes the layout
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ pWrapper->ShowMenuTitleButton( bool( aType & ToolBoxMenuType::Customize) );
+
+ mbFormat = true;
+ ImplFormat();
+ ImplSetMinMaxFloatSize();
+ }
+ else
+ {
+ // trigger redraw of menu button
+ if( !mpData->maMenubuttonItem.maRect.IsEmpty() )
+ Invalidate(mpData->maMenubuttonItem.maRect);
+ }
+}
+
+ToolBoxMenuType ToolBox::GetMenuType() const
+{
+ return mpData->maMenuType;
+}
+
+bool ToolBox::IsMenuEnabled() const
+{
+ return mpData->maMenuType != ToolBoxMenuType::NONE;
+}
+
+PopupMenu* ToolBox::GetMenu() const
+{
+ return mpData == nullptr ? nullptr : mpData->mpMenu;
+}
+
+void ToolBox::SetMenuExecuteHdl( const Link<ToolBox *, void>& rLink )
+{
+ mpData->maMenuButtonHdl = rLink;
+}
+
+bool ToolBox::ImplHasClippedItems()
+{
+ // are any items currently clipped ?
+ ImplFormat();
+ return std::any_of(mpData->m_aItems.begin(), mpData->m_aItems.end(),
+ [](const ImplToolItem& rItem) { return rItem.IsClipped(); });
+}
+
+namespace
+{
+ MenuItemBits ConvertBitsFromToolBoxToMenu(ToolBoxItemBits nToolItemBits)
+ {
+ MenuItemBits nMenuItemBits = MenuItemBits::NONE;
+ if ((nToolItemBits & ToolBoxItemBits::CHECKABLE) ||
+ (nToolItemBits & ToolBoxItemBits::DROPDOWN))
+ {
+ nMenuItemBits |= MenuItemBits::CHECKABLE;
+ }
+ return nMenuItemBits;
+ }
+}
+
+void ToolBox::UpdateCustomMenu()
+{
+ // fill clipped items into menu
+ PopupMenu *pMenu = GetMenu();
+ pMenu->Clear();
+
+ // add menu items: first the overflow items, then hidden items, both in the
+ // order they would usually appear in the toolbar. Separators that would be
+ // in the toolbar are ignored as they would introduce too much clutter,
+ // instead we have a single separator to help distinguish between overflow
+ // and hidden items.
+ if ( mpData->m_aItems.empty() )
+ return;
+
+ // nStartPos will hold the number of clipped items appended from first loop
+ for ( const auto& rItem : mpData->m_aItems )
+ {
+ if( rItem.IsClipped() )
+ {
+ sal_uInt16 id = sal_uInt16(rItem.mnId) + TOOLBOX_MENUITEM_START;
+ MenuItemBits nMenuItemBits = ConvertBitsFromToolBoxToMenu(rItem.mnBits);
+ pMenu->InsertItem( id, rItem.maText, rItem.maImage, nMenuItemBits);
+ pMenu->SetItemCommand( id, rItem.maCommandStr );
+ pMenu->EnableItem( id, rItem.mbEnabled );
+ pMenu->CheckItem ( id, rItem.meState == TRISTATE_TRUE );
+ }
+ }
+
+ // add a separator below the inserted clipped-items
+ pMenu->InsertSeparator();
+
+ // now append the items that are explicitly disabled
+ for ( const auto& rItem : mpData->m_aItems )
+ {
+ if( rItem.IsItemHidden() )
+ {
+ sal_uInt16 id = sal_uInt16(rItem.mnId) + TOOLBOX_MENUITEM_START;
+ MenuItemBits nMenuItemBits = ConvertBitsFromToolBoxToMenu(rItem.mnBits);
+ pMenu->InsertItem( id, rItem.maText, rItem.maImage, nMenuItemBits );
+ pMenu->SetItemCommand( id, rItem.maCommandStr );
+ pMenu->EnableItem( id, rItem.mbEnabled );
+ pMenu->CheckItem( id, rItem.meState == TRISTATE_TRUE );
+ }
+ }
+}
+
+IMPL_LINK( ToolBox, ImplCustomMenuListener, VclMenuEvent&, rEvent, void )
+{
+ if( rEvent.GetMenu() == GetMenu() && rEvent.GetId() == VclEventId::MenuSelect )
+ {
+ sal_uInt16 id = GetMenu()->GetItemId( rEvent.GetItemPos() );
+ if( id >= TOOLBOX_MENUITEM_START )
+ TriggerItem( ToolBoxItemId(id - TOOLBOX_MENUITEM_START) );
+ }
+}
+
+void ToolBox::ExecuteCustomMenu( const tools::Rectangle& rRect )
+{
+ if ( !IsMenuEnabled() || ImplIsInPopupMode() )
+ return;
+
+ UpdateCustomMenu();
+
+ if( GetMenuType() & ToolBoxMenuType::Customize )
+ // call button handler to allow for menu customization
+ mpData->maMenuButtonHdl.Call( this );
+
+ GetMenu()->AddEventListener( LINK( this, ToolBox, ImplCustomMenuListener ) );
+
+ // make sure all disabled entries will be shown
+ GetMenu()->SetMenuFlags(
+ GetMenu()->GetMenuFlags() | MenuFlags::AlwaysShowDisabledEntries );
+
+ // toolbox might be destroyed during execute
+ bool bBorderDel = false;
+
+ VclPtr<vcl::Window> pWin = this;
+ tools::Rectangle aMenuRect = rRect;
+ VclPtr<ImplBorderWindow> pBorderWin;
+ if( aMenuRect.IsEmpty() && IsFloatingMode() )
+ {
+ // custom menu is placed in the decoration
+ pBorderWin = dynamic_cast<ImplBorderWindow*>( GetWindow( GetWindowType::Border ) );
+ if( pBorderWin && !pBorderWin->GetMenuRect().IsEmpty() )
+ {
+ pWin = pBorderWin;
+ aMenuRect = pBorderWin->GetMenuRect();
+ bBorderDel = true;
+ }
+ }
+
+ sal_uInt16 uId = GetMenu()->Execute( pWin, tools::Rectangle( ImplGetPopupPosition( aMenuRect ), Size() ),
+ PopupMenuFlags::ExecuteDown | PopupMenuFlags::NoMouseUpClose );
+
+ if ( pWin->isDisposed() )
+ return;
+
+ if( GetMenu() )
+ GetMenu()->RemoveEventListener( LINK( this, ToolBox, ImplCustomMenuListener ) );
+ if( bBorderDel )
+ {
+ if( pBorderWin->isDisposed() )
+ return;
+ }
+
+ pWin->Invalidate( aMenuRect );
+
+ if( uId )
+ GrabFocusToDocument();
+}
+
+// checks override first, useful during calculation of sizes
+bool ToolBox::ImplIsFloatingMode() const
+{
+ SAL_WARN_IF( mpData->mbAssumeDocked && mpData->mbAssumeFloating, "vcl",
+ "cannot assume docked and floating" );
+
+ if( mpData->mbAssumeDocked )
+ return false;
+ else if( mpData->mbAssumeFloating )
+ return true;
+ else
+ return IsFloatingMode();
+}
+
+// checks override first, useful during calculation of sizes
+bool ToolBox::ImplIsInPopupMode() const
+{
+ if( mpData->mbAssumePopupMode )
+ return true;
+ else
+ {
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ return ( pWrapper && pWrapper->GetFloatingWindow() && static_cast<FloatingWindow*>(pWrapper->GetFloatingWindow())->IsInPopupMode() );
+ }
+}
+
+void ToolBox::Lock( bool bLock )
+{
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( !pWrapper )
+ return;
+ if( mpData->mbIsLocked != bLock )
+ {
+ mpData->mbIsLocked = bLock;
+ if( !ImplIsFloatingMode() )
+ {
+ mbCalc = true;
+ mbFormat = true;
+ SetSizePixel( CalcWindowSizePixel(1) );
+ Invalidate();
+ }
+ }
+}
+
+bool ToolBox::AlwaysLocked()
+{
+ // read config item to determine toolbox behaviour, used for subtoolbars
+
+ static int nAlwaysLocked = -1;
+
+ if( nAlwaysLocked == -1 )
+ {
+ nAlwaysLocked = 0; // ask configuration only once
+
+ utl::OConfigurationNode aNode = utl::OConfigurationTreeRoot::tryCreateWithComponentContext(
+ comphelper::getProcessComponentContext(),
+ "/org.openoffice.Office.UI.GlobalSettings/Toolbars" ); // note: case sensitive !
+ if ( aNode.isValid() )
+ {
+ // feature enabled ?
+ bool bStatesEnabled = bool();
+ css::uno::Any aValue = aNode.getNodeValue( "StatesEnabled" );
+ if( aValue >>= bStatesEnabled )
+ {
+ if( bStatesEnabled )
+ {
+ // now read the locking state
+ utl::OConfigurationNode aNode2 = utl::OConfigurationTreeRoot::tryCreateWithComponentContext(
+ comphelper::getProcessComponentContext(),
+ "/org.openoffice.Office.UI.GlobalSettings/Toolbars/States" ); // note: case sensitive !
+
+ bool bLocked = bool();
+ css::uno::Any aValue2 = aNode2.getNodeValue( "Locked" );
+ if( aValue2 >>= bLocked )
+ nAlwaysLocked = bLocked ? 1 : 0;
+ }
+ }
+ }
+ }
+
+ return nAlwaysLocked == 1;
+}
+
+bool ToolBox::WillUsePopupMode() const
+{
+ return mpData->mbWillUsePopupMode;
+}
+
+void ToolBox::WillUsePopupMode( bool b )
+{
+ mpData->mbWillUsePopupMode = b;
+}
+
+void ToolBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ DockingWindow::DumpAsPropertyTree(rJsonWriter);
+
+ auto childrenNode = rJsonWriter.startArray("children");
+ for (ToolBox::ImplToolItems::size_type i = 0; i < GetItemCount(); ++i)
+ {
+ auto childNode = rJsonWriter.startStruct();
+ ToolBoxItemId nId = GetItemId(i);
+
+ vcl::Window* pWindow = GetItemWindow(nId);
+ if (pWindow)
+ {
+ pWindow->DumpAsPropertyTree(rJsonWriter);
+ }
+ else
+ {
+ OUString sCommand = GetItemCommand(nId);
+ rJsonWriter.put("type", "toolitem");
+ rJsonWriter.put("text", GetItemText(nId));
+ rJsonWriter.put("command", sCommand);
+ if (IsItemChecked(nId))
+ rJsonWriter.put("selected", true);
+ if (!IsItemVisible(nId))
+ rJsonWriter.put("visible", false);
+ if (GetItemBits(nId) & ToolBoxItemBits::DROPDOWN)
+ rJsonWriter.put("dropdown", true);
+ if (!IsItemEnabled(nId))
+ rJsonWriter.put("enabled", false);
+
+ Image aImage = GetItemImage(nId);
+ if (!sCommand.startsWith(".uno:") && !!aImage)
+ {
+ SvMemoryStream aOStm(6535, 6535);
+ if(GraphicConverter::Export(aOStm, aImage.GetBitmapEx(), ConvertDataFormat::PNG) == ERRCODE_NONE)
+ {
+ css::uno::Sequence<sal_Int8> aSeq( static_cast<sal_Int8 const *>(aOStm.GetData()), aOStm.Tell());
+ OUStringBuffer aBuffer("data:image/png;base64,");
+ ::comphelper::Base64::encode(aBuffer, aSeq);
+ rJsonWriter.put("image", aBuffer.makeStringAndClear());
+ }
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/window.cxx b/vcl/source/window/window.cxx
new file mode 100644
index 000000000..aabd1778c
--- /dev/null
+++ b/vcl/source/window/window.cxx
@@ -0,0 +1,3978 @@
+/* -*- 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 <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+
+#include <sal/types.h>
+#include <tools/diagnose_ex.h>
+#include <vcl/salgtype.hxx>
+#include <vcl/event.hxx>
+#include <vcl/help.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/transfer.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/window.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/dockwin.hxx>
+#include <vcl/wall.hxx>
+#include <vcl/toolkit/fixed.hxx>
+#include <vcl/taskpanelist.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+#include <vcl/lazydelete.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <vcl/IDialogRenderable.hxx>
+
+#include <vcl/uitest/uiobject.hxx>
+
+#include <ImplOutDevData.hxx>
+#include <impfontcache.hxx>
+#include <salframe.hxx>
+#include <salobj.hxx>
+#include <salinst.hxx>
+#include <salgdi.hxx>
+#include <svdata.hxx>
+#include <window.h>
+#include <toolbox.h>
+#include <brdwin.hxx>
+#include <helpwin.hxx>
+
+#include <com/sun/star/accessibility/AccessibleRelation.hpp>
+#include <com/sun/star/accessibility/XAccessible.hpp>
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+#include <com/sun/star/awt/XWindowPeer.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/rendering/CanvasFactory.hpp>
+#include <com/sun/star/rendering/XSpriteCanvas.hpp>
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <unotools/configmgr.hxx>
+#include <osl/diagnose.h>
+#include <tools/debug.hxx>
+#include <tools/json_writer.hxx>
+#include <boost/property_tree/ptree.hpp>
+
+#include <cassert>
+#include <typeinfo>
+
+#ifdef _WIN32 // see #140456#
+#include <win/salframe.h>
+#endif
+
+#include "impldockingwrapper.hxx"
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::datatransfer::clipboard;
+using namespace ::com::sun::star::datatransfer::dnd;
+
+namespace vcl {
+
+Window::Window( WindowType nType )
+ : mpWindowImpl(new WindowImpl( *this, nType ))
+{
+ // true: this outdev will be mirrored if RTL window layout (UI mirroring) is globally active
+ mpWindowImpl->mxOutDev->mbEnableRTL = AllSettings::GetLayoutRTL();
+}
+
+Window::Window( vcl::Window* pParent, WinBits nStyle )
+ : mpWindowImpl(new WindowImpl( *this, WindowType::WINDOW ))
+{
+ // true: this outdev will be mirrored if RTL window layout (UI mirroring) is globally active
+ mpWindowImpl->mxOutDev->mbEnableRTL = AllSettings::GetLayoutRTL();
+
+ ImplInit( pParent, nStyle, nullptr );
+}
+
+#if OSL_DEBUG_LEVEL > 0
+namespace
+{
+ OString lcl_createWindowInfo(const vcl::Window* pWindow)
+ {
+ // skip border windows, they do not carry information that
+ // would help with diagnosing the problem
+ const vcl::Window* pTempWin( pWindow );
+ while ( pTempWin && pTempWin->GetType() == WindowType::BORDERWINDOW ) {
+ pTempWin = pTempWin->GetWindow( GetWindowType::FirstChild );
+ }
+ // check if pTempWin is not null, otherwise use the
+ // original address
+ if ( pTempWin ) {
+ pWindow = pTempWin;
+ }
+
+ return OString::Concat(" ") +
+ typeid( *pWindow ).name() +
+ "(" +
+ OUStringToOString(
+ pWindow->GetText(),
+ RTL_TEXTENCODING_UTF8
+ ) +
+ ")";
+ }
+}
+#endif
+
+void Window::dispose()
+{
+ assert( mpWindowImpl );
+ assert( !mpWindowImpl->mbInDispose ); // should only be called from disposeOnce()
+ assert( (!mpWindowImpl->mpParent ||
+ mpWindowImpl->mpParent->mpWindowImpl) &&
+ "vcl::Window child should have its parent disposed first" );
+
+ // remove Key and Mouse events issued by Application::PostKey/MouseEvent
+ Application::RemoveMouseAndKeyEvents( this );
+
+ // Dispose of the canvas implementation (which, currently, has an
+ // own wrapper window as a child to this one.
+ GetOutDev()->ImplDisposeCanvas();
+
+ mpWindowImpl->mbInDispose = true;
+
+ CallEventListeners( VclEventId::ObjectDying );
+
+ // do not send child events for frames that were registered as native frames
+ if( !ImplIsAccessibleNativeFrame() && mpWindowImpl->mbReallyVisible )
+ if ( ImplIsAccessibleCandidate() && GetAccessibleParentWindow() )
+ GetAccessibleParentWindow()->CallEventListeners( VclEventId::WindowChildDestroyed, this );
+
+ // remove associated data structures from dockingmanager
+ ImplGetDockingManager()->RemoveWindow( this );
+
+ // remove ownerdraw decorated windows from list in the top-most frame window
+ if( (GetStyle() & WB_OWNERDRAWDECORATION) && mpWindowImpl->mbFrame )
+ {
+ ::std::vector< VclPtr<vcl::Window> >& rList = ImplGetOwnerDrawList();
+ auto p = ::std::find( rList.begin(), rList.end(), VclPtr<vcl::Window>(this) );
+ if( p != rList.end() )
+ rList.erase( p );
+ }
+
+ // shutdown drag and drop
+ Reference < XComponent > xDnDComponent( mpWindowImpl->mxDNDListenerContainer, UNO_QUERY );
+
+ if( xDnDComponent.is() )
+ xDnDComponent->dispose();
+
+ if( mpWindowImpl->mbFrame && mpWindowImpl->mpFrameData )
+ {
+ try
+ {
+ // deregister drop target listener
+ if( mpWindowImpl->mpFrameData->mxDropTargetListener.is() )
+ {
+ Reference< XDragGestureRecognizer > xDragGestureRecognizer(mpWindowImpl->mpFrameData->mxDragSource, UNO_QUERY);
+ if( xDragGestureRecognizer.is() )
+ {
+ xDragGestureRecognizer->removeDragGestureListener(
+ Reference< XDragGestureListener > (mpWindowImpl->mpFrameData->mxDropTargetListener, UNO_QUERY));
+ }
+
+ mpWindowImpl->mpFrameData->mxDropTarget->removeDropTargetListener( mpWindowImpl->mpFrameData->mxDropTargetListener );
+ mpWindowImpl->mpFrameData->mxDropTargetListener.clear();
+ }
+
+ // shutdown drag and drop for this frame window
+ Reference< XComponent > xComponent( mpWindowImpl->mpFrameData->mxDropTarget, UNO_QUERY );
+
+ // DNDEventDispatcher does not hold a reference of the DropTarget,
+ // so it's ok if it does not support XComponent
+ if( xComponent.is() )
+ xComponent->dispose();
+ }
+ catch (const Exception&)
+ {
+ // can be safely ignored here.
+ }
+ }
+
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper( false );
+ if ( pWrapper )
+ pWrapper->WindowDestroyed( this );
+
+ // MT: Must be called after WindowDestroyed!
+ // Otherwise, if the accessible is a VCLXWindow, it will try to destroy this window again!
+ // But accessibility implementations from applications need this dispose.
+ if ( mpWindowImpl->mxAccessible.is() )
+ {
+ Reference< XComponent> xC( mpWindowImpl->mxAccessible, UNO_QUERY );
+ if ( xC.is() )
+ xC->dispose();
+ }
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( ImplGetSVHelpData().mpHelpWin && (ImplGetSVHelpData().mpHelpWin->GetParent() == this) )
+ ImplDestroyHelpWindow( true );
+
+ SAL_WARN_IF(pSVData->mpWinData->mpTrackWin.get() == this, "vcl.window",
+ "Window::~Window(): Window is in TrackingMode");
+ SAL_WARN_IF(IsMouseCaptured(), "vcl.window",
+ "Window::~Window(): Window has the mouse captured");
+
+ // due to old compatibility
+ if (pSVData->mpWinData->mpTrackWin == this)
+ EndTracking();
+ if (IsMouseCaptured())
+ ReleaseMouse();
+
+#if OSL_DEBUG_LEVEL > 0
+ // always perform these tests in debug builds
+ {
+ OStringBuffer aErrorStr;
+ bool bError = false;
+ vcl::Window* pTempWin;
+
+ if ( mpWindowImpl->mpFirstChild )
+ {
+ OStringBuffer aTempStr = "Window (" +
+ lcl_createWindowInfo(this) +
+ ") with live children destroyed: ";
+ pTempWin = mpWindowImpl->mpFirstChild;
+ while ( pTempWin )
+ {
+ aTempStr.append(lcl_createWindowInfo(pTempWin));
+ pTempWin = pTempWin->mpWindowImpl->mpNext;
+ }
+ OSL_FAIL( aTempStr.getStr() );
+ Application::Abort(OStringToOUString(aTempStr, RTL_TEXTENCODING_UTF8));
+ }
+
+ if (mpWindowImpl->mpFrameData != nullptr)
+ {
+ pTempWin = mpWindowImpl->mpFrameData->mpFirstOverlap;
+ while ( pTempWin )
+ {
+ if ( ImplIsRealParentPath( pTempWin ) )
+ {
+ bError = true;
+ aErrorStr.append(lcl_createWindowInfo(pTempWin));
+ }
+ pTempWin = pTempWin->mpWindowImpl->mpNextOverlap;
+ }
+ if ( bError )
+ {
+ OString aTempStr =
+ "Window (" +
+ lcl_createWindowInfo(this) +
+ ") with live SystemWindows destroyed: " +
+ aErrorStr;
+ OSL_FAIL(aTempStr.getStr());
+ Application::Abort(OStringToOUString(aTempStr, RTL_TEXTENCODING_UTF8));
+ }
+ }
+
+ bError = false;
+ pTempWin = pSVData->maFrameData.mpFirstFrame;
+ while ( pTempWin )
+ {
+ if ( ImplIsRealParentPath( pTempWin ) )
+ {
+ bError = true;
+ aErrorStr.append(lcl_createWindowInfo(pTempWin));
+ }
+ pTempWin = pTempWin->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+ if ( bError )
+ {
+ OString aTempStr = "Window (" +
+ lcl_createWindowInfo(this) +
+ ") with live SystemWindows destroyed: " +
+ aErrorStr;
+ OSL_FAIL( aTempStr.getStr() );
+ Application::Abort(OStringToOUString(aTempStr, RTL_TEXTENCODING_UTF8));
+ }
+
+ if ( mpWindowImpl->mpFirstOverlap )
+ {
+ OStringBuffer aTempStr = "Window (" +
+ lcl_createWindowInfo(this) +
+ ") with live SystemWindows destroyed: ";
+ pTempWin = mpWindowImpl->mpFirstOverlap;
+ while ( pTempWin )
+ {
+ aTempStr.append(lcl_createWindowInfo(pTempWin));
+ pTempWin = pTempWin->mpWindowImpl->mpNext;
+ }
+ OSL_FAIL( aTempStr.getStr() );
+ Application::Abort(OStringToOUString(aTempStr, RTL_TEXTENCODING_UTF8));
+ }
+
+ vcl::Window* pMyParent = GetParent();
+ SystemWindow* pMySysWin = nullptr;
+
+ while ( pMyParent )
+ {
+ if ( pMyParent->IsSystemWindow() )
+ {
+ pMySysWin = dynamic_cast<SystemWindow *>(pMyParent);
+ }
+ pMyParent = pMyParent->GetParent();
+ }
+ if ( pMySysWin && pMySysWin->ImplIsInTaskPaneList( this ) )
+ {
+ OString aTempStr = "Window (" +
+ lcl_createWindowInfo(this) +
+ ") still in TaskPanelList!";
+ OSL_FAIL( aTempStr.getStr() );
+ Application::Abort(OStringToOUString(aTempStr, RTL_TEXTENCODING_UTF8));
+ }
+ }
+#endif
+
+ if( mpWindowImpl->mbIsInTaskPaneList )
+ {
+ vcl::Window* pMyParent = GetParent();
+ SystemWindow* pMySysWin = nullptr;
+
+ while ( pMyParent )
+ {
+ if ( pMyParent->IsSystemWindow() )
+ {
+ pMySysWin = dynamic_cast<SystemWindow *>(pMyParent);
+ }
+ pMyParent = pMyParent->GetParent();
+ }
+ if ( pMySysWin && pMySysWin->ImplIsInTaskPaneList( this ) )
+ {
+ pMySysWin->GetTaskPaneList()->RemoveWindow( this );
+ }
+ else
+ {
+ SAL_WARN( "vcl", "Window (" << GetText() << ") not found in TaskPanelList");
+ }
+ }
+
+ // remove from size-group if necessary
+ remove_from_all_size_groups();
+
+ // clear mnemonic labels
+ std::vector<VclPtr<FixedText> > aMnemonicLabels(list_mnemonic_labels());
+ for (auto const& mnemonicLabel : aMnemonicLabels)
+ {
+ remove_mnemonic_label(mnemonicLabel);
+ }
+
+ // hide window in order to trigger the Paint-Handling
+ Hide();
+
+ // EndExtTextInputMode
+ if (pSVData->mpWinData->mpExtTextInputWin == this)
+ {
+ EndExtTextInput();
+ if (pSVData->mpWinData->mpExtTextInputWin == this)
+ pSVData->mpWinData->mpExtTextInputWin = nullptr;
+ }
+
+ // check if the focus window is our child
+ bool bHasFocusedChild = false;
+ if (pSVData->mpWinData->mpFocusWin && ImplIsRealParentPath(pSVData->mpWinData->mpFocusWin))
+ {
+ // #122232#, this must not happen and is an application bug ! but we try some cleanup to hopefully avoid crashes, see below
+ bHasFocusedChild = true;
+#if OSL_DEBUG_LEVEL > 0
+ OUString aTempStr = "Window (" + GetText() +
+ ") with focused child window destroyed ! THIS WILL LEAD TO CRASHES AND MUST BE FIXED !";
+ SAL_WARN( "vcl", aTempStr );
+ Application::Abort(aTempStr);
+#endif
+ }
+
+ // if we get focus pass focus to another window
+ vcl::Window* pOverlapWindow = ImplGetFirstOverlapWindow();
+ if (pSVData->mpWinData->mpFocusWin == this
+ || bHasFocusedChild) // #122232#, see above, try some cleanup
+ {
+ if ( mpWindowImpl->mbFrame )
+ {
+ pSVData->mpWinData->mpFocusWin = nullptr;
+ pOverlapWindow->mpWindowImpl->mpLastFocusWindow = nullptr;
+ }
+ else
+ {
+ vcl::Window* pParent = GetParent();
+ vcl::Window* pBorderWindow = mpWindowImpl->mpBorderWindow;
+ // when windows overlap, give focus to the parent
+ // of the next FrameWindow
+ if ( pBorderWindow )
+ {
+ if ( pBorderWindow->ImplIsOverlapWindow() )
+ pParent = pBorderWindow->mpWindowImpl->mpOverlapWindow;
+ }
+ else if ( ImplIsOverlapWindow() )
+ pParent = mpWindowImpl->mpOverlapWindow;
+
+ if ( pParent && pParent->IsEnabled() && pParent->IsInputEnabled() && ! pParent->IsInModalMode() )
+ pParent->GrabFocus();
+ else
+ mpWindowImpl->mpFrameWindow->GrabFocus();
+
+ // If the focus was set back to 'this' set it to nothing
+ if (pSVData->mpWinData->mpFocusWin == this)
+ {
+ pSVData->mpWinData->mpFocusWin = nullptr;
+ pOverlapWindow->mpWindowImpl->mpLastFocusWindow = nullptr;
+ }
+ }
+ }
+
+ if ( pOverlapWindow != nullptr &&
+ pOverlapWindow->mpWindowImpl->mpLastFocusWindow == this )
+ pOverlapWindow->mpWindowImpl->mpLastFocusWindow = nullptr;
+
+ // reset hint for DefModalDialogParent
+ if( pSVData->maFrameData.mpActiveApplicationFrame == this )
+ pSVData->maFrameData.mpActiveApplicationFrame = nullptr;
+
+ // reset hint of what was the last wheeled window
+ if (pSVData->mpWinData->mpLastWheelWindow == this)
+ pSVData->mpWinData->mpLastWheelWindow = nullptr;
+
+ // reset marked windows
+ if ( mpWindowImpl->mpFrameData != nullptr )
+ {
+ if ( mpWindowImpl->mpFrameData->mpFocusWin == this )
+ mpWindowImpl->mpFrameData->mpFocusWin = nullptr;
+ if ( mpWindowImpl->mpFrameData->mpMouseMoveWin == this )
+ mpWindowImpl->mpFrameData->mpMouseMoveWin = nullptr;
+ if ( mpWindowImpl->mpFrameData->mpMouseDownWin == this )
+ mpWindowImpl->mpFrameData->mpMouseDownWin = nullptr;
+ }
+
+ // reset Deactivate-Window
+ if (pSVData->mpWinData->mpLastDeacWin == this)
+ pSVData->mpWinData->mpLastDeacWin = nullptr;
+
+ if ( mpWindowImpl->mbFrame && mpWindowImpl->mpFrameData )
+ {
+ if ( mpWindowImpl->mpFrameData->mnFocusId )
+ Application::RemoveUserEvent( mpWindowImpl->mpFrameData->mnFocusId );
+ mpWindowImpl->mpFrameData->mnFocusId = nullptr;
+ if ( mpWindowImpl->mpFrameData->mnMouseMoveId )
+ Application::RemoveUserEvent( mpWindowImpl->mpFrameData->mnMouseMoveId );
+ mpWindowImpl->mpFrameData->mnMouseMoveId = nullptr;
+ }
+
+ // release SalGraphics
+ VclPtr<OutputDevice> pOutDev = GetOutDev();
+ pOutDev->ReleaseGraphics();
+
+ // remove window from the lists
+ ImplRemoveWindow( true );
+
+ // de-register as "top window child" at our parent, if necessary
+ if ( mpWindowImpl->mbFrame )
+ {
+ bool bIsTopWindow
+ = mpWindowImpl->mpWinData && (mpWindowImpl->mpWinData->mnIsTopWindow == 1);
+ if ( mpWindowImpl->mpRealParent && bIsTopWindow )
+ {
+ ImplWinData* pParentWinData = mpWindowImpl->mpRealParent->ImplGetWinData();
+
+ auto myPos = ::std::find( pParentWinData->maTopWindowChildren.begin(),
+ pParentWinData->maTopWindowChildren.end(), VclPtr<vcl::Window>(this) );
+ SAL_WARN_IF( myPos == pParentWinData->maTopWindowChildren.end(), "vcl.window", "Window::~Window: inconsistency in top window chain!" );
+ if ( myPos != pParentWinData->maTopWindowChildren.end() )
+ pParentWinData->maTopWindowChildren.erase( myPos );
+ }
+ }
+
+ mpWindowImpl->mpWinData.reset();
+
+ // remove BorderWindow or Frame window data
+ mpWindowImpl->mpBorderWindow.disposeAndClear();
+ if ( mpWindowImpl->mbFrame )
+ {
+ if ( pSVData->maFrameData.mpFirstFrame == this )
+ pSVData->maFrameData.mpFirstFrame = mpWindowImpl->mpFrameData->mpNextFrame;
+ else
+ {
+ sal_Int32 nWindows = 0;
+ vcl::Window* pSysWin = pSVData->maFrameData.mpFirstFrame;
+ while ( pSysWin && pSysWin->mpWindowImpl->mpFrameData->mpNextFrame.get() != this )
+ {
+ pSysWin = pSysWin->mpWindowImpl->mpFrameData->mpNextFrame;
+ nWindows++;
+ }
+
+ if ( pSysWin )
+ {
+ assert (mpWindowImpl->mpFrameData->mpNextFrame.get() != pSysWin);
+ pSysWin->mpWindowImpl->mpFrameData->mpNextFrame = mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+ else // if it is not in the list, we can't remove it.
+ SAL_WARN("vcl.window", "Window " << this << " marked as frame window, "
+ "is missing from list of " << nWindows << " frames");
+ }
+ if (mpWindowImpl->mpFrame) // otherwise exception during init
+ {
+ mpWindowImpl->mpFrame->SetCallback( nullptr, nullptr );
+ pSVData->mpDefInst->DestroyFrame( mpWindowImpl->mpFrame );
+ }
+ assert (mpWindowImpl->mpFrameData->mnFocusId == nullptr);
+ assert (mpWindowImpl->mpFrameData->mnMouseMoveId == nullptr);
+
+ mpWindowImpl->mpFrameData->mpBuffer.disposeAndClear();
+ delete mpWindowImpl->mpFrameData;
+ mpWindowImpl->mpFrameData = nullptr;
+ }
+
+ if (mpWindowImpl->mxWindowPeer)
+ mpWindowImpl->mxWindowPeer->dispose();
+
+ // should be the last statements
+ mpWindowImpl.reset();
+
+ pOutDev.disposeAndClear();
+ // just to make loplugin:vclwidgets happy
+ VclReferenceBase::dispose();
+}
+
+Window::~Window()
+{
+ disposeOnce();
+}
+
+// We will eventually being removing the inheritance of OutputDevice
+// from Window. It will be replaced with a transient relationship such
+// that the OutputDevice is only live for the scope of the Paint method.
+// In the meantime this can help move us towards a Window use an
+// OutputDevice, not being one.
+
+::OutputDevice const* Window::GetOutDev() const
+{
+ return mpWindowImpl->mxOutDev.get();
+}
+
+::OutputDevice* Window::GetOutDev()
+{
+ return mpWindowImpl->mxOutDev.get();
+}
+
+Color WindowOutputDevice::GetBackgroundColor() const
+{
+ return mxOwnerWindow->GetDisplayBackground().GetColor();
+}
+
+bool WindowOutputDevice::CanEnableNativeWidget() const
+{
+ return mxOwnerWindow->IsNativeWidgetEnabled();
+}
+
+} /* namespace vcl */
+
+WindowImpl::WindowImpl( vcl::Window& rWindow, WindowType nType )
+{
+ mxOutDev = VclPtr<vcl::WindowOutputDevice>::Create(rWindow);
+ maZoom = Fraction( 1, 1 );
+ mfPartialScrollX = 0.0;
+ mfPartialScrollY = 0.0;
+ maWinRegion = vcl::Region(true);
+ maWinClipRegion = vcl::Region(true);
+ mpWinData = nullptr; // Extra Window Data, that we don't need for all windows
+ mpFrameData = nullptr; // Frame Data
+ mpFrame = nullptr; // Pointer to frame window
+ mpSysObj = nullptr;
+ mpFrameWindow = nullptr; // window to top level parent (same as frame window)
+ mpOverlapWindow = nullptr; // first overlap parent
+ mpBorderWindow = nullptr; // Border-Window
+ mpClientWindow = nullptr; // Client-Window of a FrameWindow
+ mpParent = nullptr; // parent (incl. BorderWindow)
+ mpRealParent = nullptr; // real parent (excl. BorderWindow)
+ mpFirstChild = nullptr; // first child window
+ mpLastChild = nullptr; // last child window
+ mpFirstOverlap = nullptr; // first overlap window (only set in overlap windows)
+ mpLastOverlap = nullptr; // last overlap window (only set in overlap windows)
+ mpPrev = nullptr; // prev window
+ mpNext = nullptr; // next window
+ mpNextOverlap = nullptr; // next overlap window of frame
+ mpLastFocusWindow = nullptr; // window for focus restore
+ mpDlgCtrlDownWindow = nullptr; // window for dialog control
+ mnEventListenersIteratingCount = 0;
+ mnChildEventListenersIteratingCount = 0;
+ mpCursor = nullptr; // cursor
+ maPointer = PointerStyle::Arrow;
+ mpVCLXWindow = nullptr;
+ mpAccessibleInfos = nullptr;
+ maControlForeground = COL_TRANSPARENT; // no foreground set
+ maControlBackground = COL_TRANSPARENT; // no background set
+ mnLeftBorder = 0; // left border
+ mnTopBorder = 0; // top border
+ mnRightBorder = 0; // right border
+ mnBottomBorder = 0; // bottom border
+ mnWidthRequest = -1; // width request
+ mnHeightRequest = -1; // height request
+ mnOptimalWidthCache = -1; // optimal width cache
+ mnOptimalHeightCache = -1; // optimal height cache
+ mnX = 0; // X-Position to Parent
+ mnY = 0; // Y-Position to Parent
+ mnAbsScreenX = 0; // absolute X-position on screen, used for RTL window positioning
+ mpChildClipRegion = nullptr; // Child-Clip-Region when ClipChildren
+ mpPaintRegion = nullptr; // Paint-ClipRegion
+ mnStyle = 0; // style (init in ImplInitWindow)
+ mnPrevStyle = 0; // prevstyle (set in SetStyle)
+ mnExtendedStyle = WindowExtendedStyle::NONE; // extended style (init in ImplInitWindow)
+ mnType = nType; // type
+ mnGetFocusFlags = GetFocusFlags::NONE; // Flags for GetFocus()-Call
+ mnWaitCount = 0; // Wait-Count (>1 == "wait" mouse pointer)
+ mnPaintFlags = ImplPaintFlags::NONE; // Flags for ImplCallPaint
+ mnParentClipMode = ParentClipMode::NONE; // Flags for Parent-ClipChildren-Mode
+ mnActivateMode = ActivateModeFlags::NONE; // Will be converted in System/Overlap-Windows
+ mnDlgCtrlFlags = DialogControlFlags::NONE; // DialogControl-Flags
+ meAlwaysInputMode = AlwaysInputNone; // AlwaysEnableInput not called
+ meHalign = VclAlign::Fill;
+ meValign = VclAlign::Fill;
+ mePackType = VclPackType::Start;
+ mnPadding = 0;
+ mnGridHeight = 1;
+ mnGridLeftAttach = -1;
+ mnGridTopAttach = -1;
+ mnGridWidth = 1;
+ mnBorderWidth = 0;
+ mnMarginLeft = 0;
+ mnMarginRight = 0;
+ mnMarginTop = 0;
+ mnMarginBottom = 0;
+ mbFrame = false; // true: Window is a frame window
+ mbBorderWin = false; // true: Window is a border window
+ mbOverlapWin = false; // true: Window is an overlap window
+ mbSysWin = false; // true: SystemWindow is the base class
+ mbDialog = false; // true: Dialog is the base class
+ mbDockWin = false; // true: DockingWindow is the base class
+ mbFloatWin = false; // true: FloatingWindow is the base class
+ mbPushButton = false; // true: PushButton is the base class
+ mbToolBox = false; // true: ToolBox is the base class
+ mbMenuFloatingWindow = false; // true: MenuFloatingWindow is the base class
+ mbToolbarFloatingWindow = false; // true: ImplPopupFloatWin is the base class, used for subtoolbars
+ mbSplitter = false; // true: Splitter is the base class
+ mbVisible = false; // true: Show( true ) called
+ mbOverlapVisible = false; // true: Hide called for visible window from ImplHideAllOverlapWindow()
+ mbDisabled = false; // true: Enable( false ) called
+ mbInputDisabled = false; // true: EnableInput( false ) called
+ mbNoUpdate = false; // true: SetUpdateMode( false ) called
+ mbNoParentUpdate = false; // true: SetParentUpdateMode( false ) called
+ mbActive = false; // true: Window Active
+ mbReallyVisible = false; // true: this and all parents to an overlapped window are visible
+ mbReallyShown = false; // true: this and all parents to an overlapped window are shown
+ mbInInitShow = false; // true: we are in InitShow
+ mbChildPtrOverwrite = false; // true: PointerStyle overwrites Child-Pointer
+ mbNoPtrVisible = false; // true: ShowPointer( false ) called
+ mbPaintFrame = false; // true: Paint is visible, but not painted
+ mbInPaint = false; // true: Inside PaintHdl
+ mbMouseButtonDown = false; // true: BaseMouseButtonDown called
+ mbMouseButtonUp = false; // true: BaseMouseButtonUp called
+ mbKeyInput = false; // true: BaseKeyInput called
+ mbKeyUp = false; // true: BaseKeyUp called
+ mbCommand = false; // true: BaseCommand called
+ mbDefPos = true; // true: Position is not Set
+ mbDefSize = true; // true: Size is not Set
+ mbCallMove = true; // true: Move must be called by Show
+ mbCallResize = true; // true: Resize must be called by Show
+ mbWaitSystemResize = true; // true: Wait for System-Resize
+ mbInitWinClipRegion = true; // true: Calc Window Clip Region
+ mbInitChildRegion = false; // true: InitChildClipRegion
+ mbWinRegion = false; // true: Window Region
+ mbClipChildren = false; // true: Child-window should be clipped
+ mbClipSiblings = false; // true: Adjacent Child-window should be clipped
+ mbChildTransparent = false; // true: Child-windows are allowed to switch to transparent (incl. Parent-CLIPCHILDREN)
+ mbPaintTransparent = false; // true: Paints should be executed on the Parent
+ mbMouseTransparent = false; // true: Window is transparent for Mouse
+ mbDlgCtrlStart = false; // true: From here on own Dialog-Control
+ mbFocusVisible = false; // true: Focus Visible
+ mbUseNativeFocus = false;
+ mbNativeFocusVisible = false; // true: native Focus Visible
+ mbInShowFocus = false; // prevent recursion
+ mbInHideFocus = false; // prevent recursion
+ mbTrackVisible = false; // true: Tracking Visible
+ mbControlForeground = false; // true: Foreground-Property set
+ mbControlBackground = false; // true: Background-Property set
+ mbAlwaysOnTop = false; // true: always visible for all others windows
+ mbCompoundControl = false; // true: Composite Control => Listener...
+ mbCompoundControlHasFocus = false; // true: Composite Control has focus somewhere
+ mbPaintDisabled = false; // true: Paint should not be executed
+ mbAllResize = false; // true: Also sent ResizeEvents with 0,0
+ mbInDispose = false; // true: We're still in Window::dispose()
+ mbExtTextInput = false; // true: ExtTextInput-Mode is active
+ mbInFocusHdl = false; // true: Within GetFocus-Handler
+ mbCreatedWithToolkit = false;
+ mbSuppressAccessibilityEvents = false; // true: do not send any accessibility events
+ mbDrawSelectionBackground = false; // true: draws transparent window background to indicate (toolbox) selection
+ mbIsInTaskPaneList = false; // true: window was added to the taskpanelist in the topmost system window
+ mnNativeBackground = ControlPart::NONE; // initialize later, depends on type
+ mbHelpTextDynamic = false; // true: append help id in HELP_DEBUG case
+ mbFakeFocusSet = false; // true: pretend as if the window has focus.
+ mbHexpand = false;
+ mbVexpand = false;
+ mbExpand = false;
+ mbFill = true;
+ mbSecondary = false;
+ mbNonHomogeneous = false;
+ static bool bDoubleBuffer = getenv("VCL_DOUBLEBUFFERING_FORCE_ENABLE");
+ mbDoubleBufferingRequested = bDoubleBuffer; // when we are not sure, assume it cannot do double-buffering via RenderContext
+ mpLOKNotifier = nullptr;
+ mnLOKWindowId = 0;
+ mbLOKParentNotifier = false;
+ mbUseFrameData = false;
+}
+
+WindowImpl::~WindowImpl()
+{
+ mpChildClipRegion.reset();
+ mpAccessibleInfos.reset();
+}
+
+ImplWinData::ImplWinData() :
+ mnCursorExtWidth(0),
+ mbVertical(false),
+ mnCompositionCharRects(0),
+ mnTrackFlags(ShowTrackFlags::NONE),
+ mnIsTopWindow(sal_uInt16(~0)), // not initialized yet, 0/1 will indicate TopWindow (see IsTopWindow())
+ mbMouseOver(false),
+ mbEnableNativeWidget(false)
+{
+}
+
+ImplWinData::~ImplWinData()
+{
+ mpCompositionCharRects.reset();
+}
+
+ImplFrameData::ImplFrameData( vcl::Window *pWindow )
+ : maPaintIdle( "vcl::Window maPaintIdle" ),
+ maResizeIdle( "vcl::Window maResizeIdle" )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ assert (pSVData->maFrameData.mpFirstFrame.get() != pWindow);
+ mpNextFrame = pSVData->maFrameData.mpFirstFrame;
+ pSVData->maFrameData.mpFirstFrame = pWindow;
+ mpFirstOverlap = nullptr;
+ mpFocusWin = nullptr;
+ mpMouseMoveWin = nullptr;
+ mpMouseDownWin = nullptr;
+ mpTrackWin = nullptr;
+ mxFontCollection = pSVData->maGDIData.mxScreenFontList;
+ mxFontCache = pSVData->maGDIData.mxScreenFontCache;
+ mnFocusId = nullptr;
+ mnMouseMoveId = nullptr;
+ mnLastMouseX = -1;
+ mnLastMouseY = -1;
+ mnBeforeLastMouseX = -1;
+ mnBeforeLastMouseY = -1;
+ mnFirstMouseX = -1;
+ mnFirstMouseY = -1;
+ mnLastMouseWinX = -1;
+ mnLastMouseWinY = -1;
+ mnModalMode = 0;
+ mnMouseDownTime = 0;
+ mnClickCount = 0;
+ mnFirstMouseCode = 0;
+ mnMouseCode = 0;
+ mnMouseMode = MouseEventModifiers::NONE;
+ mbHasFocus = false;
+ mbInMouseMove = false;
+ mbMouseIn = false;
+ mbStartDragCalled = false;
+ mbNeedSysWindow = false;
+ mbMinimized = false;
+ mbStartFocusState = false;
+ mbInSysObjFocusHdl = false;
+ mbInSysObjToTopHdl = false;
+ mbSysObjFocus = false;
+ maPaintIdle.SetPriority( TaskPriority::REPAINT );
+ maPaintIdle.SetInvokeHandler( LINK( pWindow, vcl::Window, ImplHandlePaintHdl ) );
+ maResizeIdle.SetPriority( TaskPriority::RESIZE );
+ maResizeIdle.SetInvokeHandler( LINK( pWindow, vcl::Window, ImplHandleResizeTimerHdl ) );
+ mbInternalDragGestureRecognizer = false;
+ mbDragging = false;
+ mbInBufferedPaint = false;
+ mnDPIX = 96;
+ mnDPIY = 96;
+ mnTouchPanPosition = -1;
+}
+
+namespace vcl {
+
+bool WindowOutputDevice::AcquireGraphics() const
+{
+ DBG_TESTSOLARMUTEX();
+
+ if (isDisposed())
+ return false;
+
+ if (mpGraphics)
+ return true;
+
+ mbInitLineColor = true;
+ mbInitFillColor = true;
+ mbInitFont = true;
+ mbInitTextColor = true;
+ mbInitClipRegion = true;
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ mpGraphics = mxOwnerWindow->mpWindowImpl->mpFrame->AcquireGraphics();
+ // try harder if no wingraphics was available directly
+ if ( !mpGraphics )
+ {
+ // find another output device in the same frame
+ vcl::WindowOutputDevice* pReleaseOutDev = pSVData->maGDIData.mpLastWinGraphics.get();
+ while ( pReleaseOutDev )
+ {
+ if ( pReleaseOutDev->mxOwnerWindow && pReleaseOutDev->mxOwnerWindow->mpWindowImpl->mpFrame == mxOwnerWindow->mpWindowImpl->mpFrame )
+ break;
+ pReleaseOutDev = static_cast<vcl::WindowOutputDevice*>(pReleaseOutDev->mpPrevGraphics.get());
+ }
+
+ if ( pReleaseOutDev )
+ {
+ // steal the wingraphics from the other outdev
+ mpGraphics = pReleaseOutDev->mpGraphics;
+ pReleaseOutDev->ReleaseGraphics( false );
+ }
+ else
+ {
+ // if needed retry after releasing least recently used wingraphics
+ while ( !mpGraphics )
+ {
+ if ( !pSVData->maGDIData.mpLastWinGraphics )
+ break;
+ pSVData->maGDIData.mpLastWinGraphics->ReleaseGraphics();
+ mpGraphics = mxOwnerWindow->mpWindowImpl->mpFrame->AcquireGraphics();
+ }
+ }
+ }
+
+ if ( mpGraphics )
+ {
+ // update global LRU list of wingraphics
+ mpNextGraphics = pSVData->maGDIData.mpFirstWinGraphics.get();
+ pSVData->maGDIData.mpFirstWinGraphics = const_cast<vcl::WindowOutputDevice*>(this);
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = const_cast<vcl::WindowOutputDevice*>(this);
+ if ( !pSVData->maGDIData.mpLastWinGraphics )
+ pSVData->maGDIData.mpLastWinGraphics = const_cast<vcl::WindowOutputDevice*>(this);
+
+ mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp), RasterOp::Invert == meRasterOp );
+ mpGraphics->setAntiAlias(bool(mnAntialiasing & AntialiasingFlags::Enable));
+ }
+
+ return mpGraphics != nullptr;
+}
+
+void WindowOutputDevice::ReleaseGraphics( bool bRelease )
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( !mpGraphics )
+ return;
+
+ // release the fonts of the physically released graphics device
+ if( bRelease )
+ ImplReleaseFonts();
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ vcl::Window* pWindow = mxOwnerWindow.get();
+ if (!pWindow)
+ return;
+
+ if ( bRelease )
+ pWindow->mpWindowImpl->mpFrame->ReleaseGraphics( mpGraphics );
+ // remove from global LRU list of window graphics
+ if ( mpPrevGraphics )
+ mpPrevGraphics->mpNextGraphics = mpNextGraphics;
+ else
+ pSVData->maGDIData.mpFirstWinGraphics = static_cast<vcl::WindowOutputDevice*>(mpNextGraphics.get());
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = mpPrevGraphics;
+ else
+ pSVData->maGDIData.mpLastWinGraphics = static_cast<vcl::WindowOutputDevice*>(mpPrevGraphics.get());
+
+ mpGraphics = nullptr;
+ mpPrevGraphics = nullptr;
+ mpNextGraphics = nullptr;
+}
+
+static sal_Int32 CountDPIScaleFactor(sal_Int32 nDPI)
+{
+#ifndef MACOSX
+ // Setting of HiDPI is unfortunately all only a heuristic; and to add
+ // insult to an injury, the system is constantly lying to us about
+ // the DPI and whatnot
+ // eg. fdo#77059 - set the value from which we do consider the
+ // screen HiDPI to greater than 168
+ if (nDPI > 216) // 96 * 2 + 96 / 4
+ return 250;
+ else if (nDPI > 168) // 96 * 2 - 96 / 4
+ return 200;
+ else if (nDPI > 120) // 96 * 1.5 - 96 / 4
+ return 150;
+#else
+ (void)nDPI;
+#endif
+
+ return 100;
+}
+
+void Window::ImplInit( vcl::Window* pParent, WinBits nStyle, SystemParentData* pSystemParentData )
+{
+ SAL_WARN_IF( !mpWindowImpl->mbFrame && !pParent && GetType() != WindowType::FIXEDIMAGE, "vcl.window",
+ "Window::Window(): pParent == NULL" );
+
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pRealParent = pParent;
+
+ // inherit 3D look
+ if ( !mpWindowImpl->mbOverlapWin && pParent && (pParent->GetStyle() & WB_3DLOOK) )
+ nStyle |= WB_3DLOOK;
+
+ // create border window if necessary
+ if ( !mpWindowImpl->mbFrame && !mpWindowImpl->mbBorderWin && !mpWindowImpl->mpBorderWindow
+ && (nStyle & (WB_BORDER | WB_SYSTEMCHILDWINDOW) ) )
+ {
+ BorderWindowStyle nBorderTypeStyle = BorderWindowStyle::NONE;
+ if( nStyle & WB_SYSTEMCHILDWINDOW )
+ {
+ // handle WB_SYSTEMCHILDWINDOW
+ // these should be analogous to a top level frame; meaning they
+ // should have a border window with style BorderWindowStyle::Frame
+ // which controls their size
+ nBorderTypeStyle |= BorderWindowStyle::Frame;
+ nStyle |= WB_BORDER;
+ }
+ VclPtrInstance<ImplBorderWindow> pBorderWin( pParent, nStyle & (WB_BORDER | WB_DIALOGCONTROL | WB_NODIALOGCONTROL), nBorderTypeStyle );
+ static_cast<vcl::Window*>(pBorderWin)->mpWindowImpl->mpClientWindow = this;
+ pBorderWin->GetBorder( mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder );
+ mpWindowImpl->mpBorderWindow = pBorderWin;
+ pParent = mpWindowImpl->mpBorderWindow;
+ }
+ else if( !mpWindowImpl->mbFrame && ! pParent )
+ {
+ mpWindowImpl->mbOverlapWin = true;
+ mpWindowImpl->mbFrame = true;
+ }
+
+ // insert window in list
+ ImplInsertWindow( pParent );
+ mpWindowImpl->mnStyle = nStyle;
+
+ if( pParent && ! mpWindowImpl->mbFrame )
+ mpWindowImpl->mxOutDev->mbEnableRTL = AllSettings::GetLayoutRTL();
+
+ // test for frame creation
+ if ( mpWindowImpl->mbFrame )
+ {
+ // create frame
+ SalFrameStyleFlags nFrameStyle = SalFrameStyleFlags::NONE;
+
+ if ( nStyle & WB_MOVEABLE )
+ nFrameStyle |= SalFrameStyleFlags::MOVEABLE;
+ if ( nStyle & WB_SIZEABLE )
+ nFrameStyle |= SalFrameStyleFlags::SIZEABLE;
+ if ( nStyle & WB_CLOSEABLE )
+ nFrameStyle |= SalFrameStyleFlags::CLOSEABLE;
+ if ( nStyle & WB_APP )
+ nFrameStyle |= SalFrameStyleFlags::DEFAULT;
+ // check for undecorated floating window
+ if( // 1. floating windows that are not moveable/sizeable (only closeable allowed)
+ ( !(nFrameStyle & ~SalFrameStyleFlags::CLOSEABLE) &&
+ ( mpWindowImpl->mbFloatWin || ((GetType() == WindowType::BORDERWINDOW) && static_cast<ImplBorderWindow*>(this)->mbFloatWindow) || (nStyle & WB_SYSTEMFLOATWIN) ) ) ||
+ // 2. borderwindows of floaters with ownerdraw decoration
+ ((GetType() == WindowType::BORDERWINDOW) && static_cast<ImplBorderWindow*>(this)->mbFloatWindow && (nStyle & WB_OWNERDRAWDECORATION) ) )
+ {
+ nFrameStyle = SalFrameStyleFlags::FLOAT;
+ if( nStyle & WB_OWNERDRAWDECORATION )
+ nFrameStyle |= SalFrameStyleFlags::OWNERDRAWDECORATION | SalFrameStyleFlags::NOSHADOW;
+ }
+ else if( mpWindowImpl->mbFloatWin )
+ nFrameStyle |= SalFrameStyleFlags::TOOLWINDOW;
+
+ if( nStyle & WB_INTROWIN )
+ nFrameStyle |= SalFrameStyleFlags::INTRO;
+ if( nStyle & WB_TOOLTIPWIN )
+ nFrameStyle |= SalFrameStyleFlags::TOOLTIP;
+
+ if( nStyle & WB_NOSHADOW )
+ nFrameStyle |= SalFrameStyleFlags::NOSHADOW;
+
+ if( nStyle & WB_SYSTEMCHILDWINDOW )
+ nFrameStyle |= SalFrameStyleFlags::SYSTEMCHILD;
+
+ switch (mpWindowImpl->mnType)
+ {
+ case WindowType::DIALOG:
+ case WindowType::TABDIALOG:
+ case WindowType::MODELESSDIALOG:
+ case WindowType::MESSBOX:
+ case WindowType::INFOBOX:
+ case WindowType::WARNINGBOX:
+ case WindowType::ERRORBOX:
+ case WindowType::QUERYBOX:
+ nFrameStyle |= SalFrameStyleFlags::DIALOG;
+ break;
+ default:
+ break;
+ }
+
+ // tdf#144624 for the DefaultWindow, which is never visible, don't
+ // create an icon for it so construction of a DefaultWindow cannot
+ // trigger creation of a VirtualDevice which itself requires a
+ // DefaultWindow to exist
+ if( nStyle & WB_DEFAULTWIN )
+ nFrameStyle |= SalFrameStyleFlags::NOICON;
+
+ SalFrame* pParentFrame = nullptr;
+ if ( pParent )
+ pParentFrame = pParent->mpWindowImpl->mpFrame;
+ SalFrame* pFrame;
+ if ( pSystemParentData )
+ pFrame = pSVData->mpDefInst->CreateChildFrame( pSystemParentData, nFrameStyle | SalFrameStyleFlags::PLUG );
+ else
+ pFrame = pSVData->mpDefInst->CreateFrame( pParentFrame, nFrameStyle );
+ if ( !pFrame )
+ {
+ // do not abort but throw an exception, may be the current thread terminates anyway (plugin-scenario)
+ throw RuntimeException(
+ "Could not create system window!",
+ Reference< XInterface >() );
+ }
+
+ pFrame->SetCallback( this, ImplWindowFrameProc );
+
+ // set window frame data
+ mpWindowImpl->mpFrameData = new ImplFrameData( this );
+ mpWindowImpl->mpFrame = pFrame;
+ mpWindowImpl->mpFrameWindow = this;
+ mpWindowImpl->mpOverlapWindow = this;
+
+ if (!(nStyle & WB_DEFAULTWIN) && mpWindowImpl->mbDoubleBufferingRequested)
+ RequestDoubleBuffering(true);
+
+ if ( pRealParent && IsTopWindow() )
+ {
+ ImplWinData* pParentWinData = pRealParent->ImplGetWinData();
+ pParentWinData->maTopWindowChildren.emplace_back(this );
+ }
+ }
+
+ // init data
+ mpWindowImpl->mpRealParent = pRealParent;
+
+ // #99318: make sure fontcache and list is available before call to SetSettings
+ mpWindowImpl->mxOutDev->mxFontCollection = mpWindowImpl->mpFrameData->mxFontCollection;
+ mpWindowImpl->mxOutDev->mxFontCache = mpWindowImpl->mpFrameData->mxFontCache;
+
+ if ( mpWindowImpl->mbFrame )
+ {
+ if ( pParent )
+ {
+ mpWindowImpl->mpFrameData->mnDPIX = pParent->mpWindowImpl->mpFrameData->mnDPIX;
+ mpWindowImpl->mpFrameData->mnDPIY = pParent->mpWindowImpl->mpFrameData->mnDPIY;
+ }
+ else
+ {
+ OutputDevice *pOutDev = GetOutDev();
+ if ( pOutDev->AcquireGraphics() )
+ {
+ mpWindowImpl->mxOutDev->mpGraphics->GetResolution( mpWindowImpl->mpFrameData->mnDPIX, mpWindowImpl->mpFrameData->mnDPIY );
+ }
+ }
+
+ // add ownerdraw decorated frame windows to list in the top-most frame window
+ // so they can be hidden on lose focus
+ if( nStyle & WB_OWNERDRAWDECORATION )
+ ImplGetOwnerDrawList().emplace_back(this );
+
+ // delay settings initialization until first "real" frame
+ // this relies on the IntroWindow not needing any system settings
+ if ( !pSVData->maAppData.mbSettingsInit &&
+ ! (nStyle & (WB_INTROWIN|WB_DEFAULTWIN))
+ )
+ {
+ // side effect: ImplUpdateGlobalSettings does an ImplGetFrame()->UpdateSettings
+ ImplUpdateGlobalSettings( *pSVData->maAppData.mxSettings );
+ mpWindowImpl->mxOutDev->SetSettings( *pSVData->maAppData.mxSettings );
+ pSVData->maAppData.mbSettingsInit = true;
+ }
+
+ // If we create a Window with default size, query this
+ // size directly, because we want resize all Controls to
+ // the correct size before we display the window
+ if ( nStyle & (WB_MOVEABLE | WB_SIZEABLE | WB_APP) )
+ mpWindowImpl->mpFrame->GetClientSize( mpWindowImpl->mxOutDev->mnOutWidth, mpWindowImpl->mxOutDev->mnOutHeight );
+ }
+ else
+ {
+ if ( pParent )
+ {
+ if ( !ImplIsOverlapWindow() )
+ {
+ mpWindowImpl->mbDisabled = pParent->mpWindowImpl->mbDisabled;
+ mpWindowImpl->mbInputDisabled = pParent->mpWindowImpl->mbInputDisabled;
+ mpWindowImpl->meAlwaysInputMode = pParent->mpWindowImpl->meAlwaysInputMode;
+ }
+
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ // we don't want to call the WindowOutputDevice override of this because
+ // it calls back into us.
+ mpWindowImpl->mxOutDev->OutputDevice::SetSettings( pParent->GetSettings() );
+ }
+ }
+
+ }
+
+ // setup the scale factor for HiDPI displays
+ mpWindowImpl->mxOutDev->mnDPIScalePercentage = CountDPIScaleFactor(mpWindowImpl->mpFrameData->mnDPIY);
+ mpWindowImpl->mxOutDev->mnDPIX = mpWindowImpl->mpFrameData->mnDPIX;
+ mpWindowImpl->mxOutDev->mnDPIY = mpWindowImpl->mpFrameData->mnDPIY;
+
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ const StyleSettings& rStyleSettings = mpWindowImpl->mxOutDev->mxSettings->GetStyleSettings();
+ mpWindowImpl->mxOutDev->maFont = rStyleSettings.GetAppFont();
+
+ if ( nStyle & WB_3DLOOK )
+ {
+ SetTextColor( rStyleSettings.GetButtonTextColor() );
+ SetBackground( Wallpaper( rStyleSettings.GetFaceColor() ) );
+ }
+ else
+ {
+ SetTextColor( rStyleSettings.GetWindowTextColor() );
+ SetBackground( Wallpaper( rStyleSettings.GetWindowColor() ) );
+ }
+ }
+ else
+ {
+ mpWindowImpl->mxOutDev->maFont = OutputDevice::GetDefaultFont( DefaultFontType::FIXED, LANGUAGE_ENGLISH_US, GetDefaultFontFlags::NONE );
+ }
+
+ ImplPointToLogic(*GetOutDev(), mpWindowImpl->mxOutDev->maFont);
+
+ (void)ImplUpdatePos();
+
+ // calculate app font res (except for the Intro Window or the default window)
+ if ( mpWindowImpl->mbFrame && !pSVData->maGDIData.mnAppFontX && ! (nStyle & (WB_INTROWIN|WB_DEFAULTWIN)) )
+ ImplInitAppFontData( this );
+}
+
+void Window::ImplInitAppFontData( vcl::Window const * pWindow )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ tools::Long nTextHeight = pWindow->GetTextHeight();
+ tools::Long nTextWidth = pWindow->approximate_char_width() * 8;
+ tools::Long nSymHeight = nTextHeight*4;
+ // Make the basis wider if the font is too narrow
+ // such that the dialog looks symmetrical and does not become too narrow.
+ // Add some extra space when the dialog has the same width,
+ // as a little more space is better.
+ if ( nSymHeight > nTextWidth )
+ nTextWidth = nSymHeight;
+ else if ( nSymHeight+5 > nTextWidth )
+ nTextWidth = nSymHeight+5;
+ pSVData->maGDIData.mnAppFontX = nTextWidth * 10 / 8;
+ pSVData->maGDIData.mnAppFontY = nTextHeight * 10;
+
+#ifdef MACOSX
+ // FIXME: this is currently only on macOS, check with other
+ // platforms
+ if( pSVData->maNWFData.mbNoFocusRects )
+ {
+ // try to find out whether there is a large correction
+ // of control sizes, if yes, make app font scalings larger
+ // so dialog positioning is not completely off
+ ImplControlValue aControlValue;
+ tools::Rectangle aCtrlRegion( Point(), Size( nTextWidth < 10 ? 10 : nTextWidth, nTextHeight < 10 ? 10 : nTextHeight ) );
+ tools::Rectangle aBoundingRgn( aCtrlRegion );
+ tools::Rectangle aContentRgn( aCtrlRegion );
+ if( pWindow->GetNativeControlRegion( ControlType::Editbox, ControlPart::Entire, aCtrlRegion,
+ ControlState::ENABLED, aControlValue,
+ aBoundingRgn, aContentRgn ) )
+ {
+ // comment: the magical +6 is for the extra border in bordered
+ // (which is the standard) edit fields
+ if( aContentRgn.GetHeight() - nTextHeight > (nTextHeight+4)/4 )
+ pSVData->maGDIData.mnAppFontY = (aContentRgn.GetHeight()-4) * 10;
+ }
+ }
+#endif
+}
+
+ImplWinData* Window::ImplGetWinData() const
+{
+ if (!mpWindowImpl->mpWinData)
+ {
+ static const char* pNoNWF = getenv( "SAL_NO_NWF" );
+
+ const_cast<vcl::Window*>(this)->mpWindowImpl->mpWinData.reset(new ImplWinData);
+ mpWindowImpl->mpWinData->mbEnableNativeWidget = !(pNoNWF && *pNoNWF); // true: try to draw this control with native theme API
+ }
+
+ return mpWindowImpl->mpWinData.get();
+}
+
+
+void WindowOutputDevice::CopyDeviceArea( SalTwoRect& aPosAry, bool bWindowInvalidate )
+{
+ if (aPosAry.mnSrcWidth == 0 || aPosAry.mnSrcHeight == 0 || aPosAry.mnDestWidth == 0 || aPosAry.mnDestHeight == 0)
+ return;
+
+ if (bWindowInvalidate)
+ {
+ const tools::Rectangle aSrcRect(Point(aPosAry.mnSrcX, aPosAry.mnSrcY),
+ Size(aPosAry.mnSrcWidth, aPosAry.mnSrcHeight));
+
+ mxOwnerWindow->ImplMoveAllInvalidateRegions(aSrcRect,
+ aPosAry.mnDestX-aPosAry.mnSrcX,
+ aPosAry.mnDestY-aPosAry.mnSrcY,
+ false);
+
+ mpGraphics->CopyArea(aPosAry.mnDestX, aPosAry.mnDestY,
+ aPosAry.mnSrcX, aPosAry.mnSrcY,
+ aPosAry.mnSrcWidth, aPosAry.mnSrcHeight,
+ *this);
+
+ return;
+ }
+
+ OutputDevice::CopyDeviceArea(aPosAry, bWindowInvalidate);
+}
+
+const OutputDevice* WindowOutputDevice::DrawOutDevDirectCheck(const OutputDevice& rSrcDev) const
+{
+ const OutputDevice* pSrcDevChecked;
+ if ( this == &rSrcDev )
+ pSrcDevChecked = nullptr;
+ else if (GetOutDevType() != rSrcDev.GetOutDevType())
+ pSrcDevChecked = &rSrcDev;
+ else if (mxOwnerWindow->mpWindowImpl->mpFrameWindow == static_cast<const vcl::WindowOutputDevice&>(rSrcDev).mxOwnerWindow->mpWindowImpl->mpFrameWindow)
+ pSrcDevChecked = nullptr;
+ else
+ pSrcDevChecked = &rSrcDev;
+
+ return pSrcDevChecked;
+}
+
+void WindowOutputDevice::DrawOutDevDirectProcess( const OutputDevice& rSrcDev, SalTwoRect& rPosAry, SalGraphics* pSrcGraphics )
+{
+ if (pSrcGraphics)
+ mpGraphics->CopyBits(rPosAry, *pSrcGraphics, *this, rSrcDev);
+ else
+ mpGraphics->CopyBits(rPosAry, *this);
+}
+
+SalGraphics* Window::ImplGetFrameGraphics() const
+{
+ if ( mpWindowImpl->mpFrameWindow->GetOutDev()->mpGraphics )
+ {
+ mpWindowImpl->mpFrameWindow->GetOutDev()->mbInitClipRegion = true;
+ }
+ else
+ {
+ OutputDevice* pFrameWinOutDev = mpWindowImpl->mpFrameWindow->GetOutDev();
+ if ( ! pFrameWinOutDev->AcquireGraphics() )
+ {
+ return nullptr;
+ }
+ }
+ mpWindowImpl->mpFrameWindow->GetOutDev()->mpGraphics->ResetClipRegion();
+ return mpWindowImpl->mpFrameWindow->GetOutDev()->mpGraphics;
+}
+
+void Window::ImplSetReallyVisible()
+{
+ // #i43594# it is possible that INITSHOW was never send, because the visibility state changed between
+ // ImplCallInitShow() and ImplSetReallyVisible() when called from Show()
+ // mbReallyShown is a useful indicator
+ if( !mpWindowImpl->mbReallyShown )
+ ImplCallInitShow();
+
+ bool bBecameReallyVisible = !mpWindowImpl->mbReallyVisible;
+
+ GetOutDev()->mbDevOutput = true;
+ mpWindowImpl->mbReallyVisible = true;
+ mpWindowImpl->mbReallyShown = true;
+
+ // the SHOW/HIDE events serve as indicators to send child creation/destroy events to the access bridge.
+ // For this, the data member of the event must not be NULL.
+ // Previously, we did this in Window::Show, but there some events got lost in certain situations. Now
+ // we're doing it when the visibility really changes
+ if( bBecameReallyVisible && ImplIsAccessibleCandidate() )
+ CallEventListeners( VclEventId::WindowShow, this );
+ // TODO. It's kind of a hack that we're re-using the VclEventId::WindowShow. Normally, we should
+ // introduce another event which explicitly triggers the Accessibility implementations.
+
+ vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbVisible )
+ pWindow->ImplSetReallyVisible();
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+
+ pWindow = mpWindowImpl->mpFirstChild;
+ while ( pWindow )
+ {
+ if ( pWindow->mpWindowImpl->mbVisible )
+ pWindow->ImplSetReallyVisible();
+ pWindow = pWindow->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplInitResolutionSettings()
+{
+ // recalculate AppFont-resolution and DPI-resolution
+ if (mpWindowImpl->mbFrame)
+ {
+ GetOutDev()->mnDPIX = mpWindowImpl->mpFrameData->mnDPIX;
+ GetOutDev()->mnDPIY = mpWindowImpl->mpFrameData->mnDPIY;
+
+ // setup the scale factor for HiDPI displays
+ GetOutDev()->mnDPIScalePercentage = CountDPIScaleFactor(mpWindowImpl->mpFrameData->mnDPIY);
+ const StyleSettings& rStyleSettings = GetOutDev()->mxSettings->GetStyleSettings();
+ SetPointFont(*GetOutDev(), rStyleSettings.GetAppFont());
+ }
+ else if ( mpWindowImpl->mpParent )
+ {
+ GetOutDev()->mnDPIX = mpWindowImpl->mpParent->GetOutDev()->mnDPIX;
+ GetOutDev()->mnDPIY = mpWindowImpl->mpParent->GetOutDev()->mnDPIY;
+ GetOutDev()->mnDPIScalePercentage = mpWindowImpl->mpParent->GetOutDev()->mnDPIScalePercentage;
+ }
+
+ // update the recalculated values for logical units
+ // and also tools belonging to the values
+ if (IsMapModeEnabled())
+ {
+ MapMode aMapMode = GetMapMode();
+ SetMapMode();
+ SetMapMode( aMapMode );
+ }
+}
+
+void Window::ImplPointToLogic(vcl::RenderContext const & rRenderContext, vcl::Font& rFont) const
+{
+ Size aSize = rFont.GetFontSize();
+
+ if (aSize.Width())
+ {
+ aSize.setWidth( aSize.Width() * ( mpWindowImpl->mpFrameData->mnDPIX) );
+ aSize.AdjustWidth(72 / 2 );
+ aSize.setWidth( aSize.Width() / 72 );
+ }
+ aSize.setHeight( aSize.Height() * ( mpWindowImpl->mpFrameData->mnDPIY) );
+ aSize.AdjustHeight(72/2 );
+ aSize.setHeight( aSize.Height() / 72 );
+
+ if (rRenderContext.IsMapModeEnabled())
+ aSize = rRenderContext.PixelToLogic(aSize);
+
+ rFont.SetFontSize(aSize);
+}
+
+void Window::ImplLogicToPoint(vcl::RenderContext const & rRenderContext, vcl::Font& rFont) const
+{
+ Size aSize = rFont.GetFontSize();
+
+ if (rRenderContext.IsMapModeEnabled())
+ aSize = rRenderContext.LogicToPixel(aSize);
+
+ if (aSize.Width())
+ {
+ aSize.setWidth( aSize.Width() * 72 );
+ aSize.AdjustWidth(mpWindowImpl->mpFrameData->mnDPIX / 2 );
+ aSize.setWidth( aSize.Width() / ( mpWindowImpl->mpFrameData->mnDPIX) );
+ }
+ aSize.setHeight( aSize.Height() * 72 );
+ aSize.AdjustHeight(mpWindowImpl->mpFrameData->mnDPIY / 2 );
+ aSize.setHeight( aSize.Height() / ( mpWindowImpl->mpFrameData->mnDPIY) );
+
+ rFont.SetFontSize(aSize);
+}
+
+bool Window::ImplUpdatePos()
+{
+ bool bSysChild = false;
+
+ if ( ImplIsOverlapWindow() )
+ {
+ GetOutDev()->mnOutOffX = mpWindowImpl->mnX;
+ GetOutDev()->mnOutOffY = mpWindowImpl->mnY;
+ }
+ else
+ {
+ vcl::Window* pParent = ImplGetParent();
+
+ GetOutDev()->mnOutOffX = mpWindowImpl->mnX + pParent->GetOutDev()->mnOutOffX;
+ GetOutDev()->mnOutOffY = mpWindowImpl->mnY + pParent->GetOutDev()->mnOutOffY;
+ }
+
+ VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ if ( pChild->ImplUpdatePos() )
+ bSysChild = true;
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+
+ if ( mpWindowImpl->mpSysObj )
+ bSysChild = true;
+
+ return bSysChild;
+}
+
+void Window::ImplUpdateSysObjPos()
+{
+ if ( mpWindowImpl->mpSysObj )
+ mpWindowImpl->mpSysObj->SetPosSize( GetOutDev()->mnOutOffX, GetOutDev()->mnOutOffY, GetOutDev()->mnOutWidth, GetOutDev()->mnOutHeight );
+
+ VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->ImplUpdateSysObjPos();
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::ImplPosSizeWindow( tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight, PosSizeFlags nFlags )
+{
+ bool bNewPos = false;
+ bool bNewSize = false;
+ bool bCopyBits = false;
+ tools::Long nOldOutOffX = GetOutDev()->mnOutOffX;
+ tools::Long nOldOutOffY = GetOutDev()->mnOutOffY;
+ tools::Long nOldOutWidth = GetOutDev()->mnOutWidth;
+ tools::Long nOldOutHeight = GetOutDev()->mnOutHeight;
+ std::unique_ptr<vcl::Region> pOverlapRegion;
+ std::unique_ptr<vcl::Region> pOldRegion;
+
+ if ( IsReallyVisible() )
+ {
+ tools::Rectangle aOldWinRect( Point( nOldOutOffX, nOldOutOffY ),
+ Size( nOldOutWidth, nOldOutHeight ) );
+ pOldRegion.reset( new vcl::Region( aOldWinRect ) );
+ if ( mpWindowImpl->mbWinRegion )
+ pOldRegion->Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) );
+
+ if ( GetOutDev()->mnOutWidth && GetOutDev()->mnOutHeight && !mpWindowImpl->mbPaintTransparent &&
+ !mpWindowImpl->mbInitWinClipRegion && !mpWindowImpl->maWinClipRegion.IsEmpty() &&
+ !HasPaintEvent() )
+ bCopyBits = true;
+ }
+
+ bool bnXRecycled = false; // avoid duplicate mirroring in RTL case
+ if ( nFlags & PosSizeFlags::Width )
+ {
+ if(!( nFlags & PosSizeFlags::X ))
+ {
+ nX = mpWindowImpl->mnX;
+ nFlags |= PosSizeFlags::X;
+ bnXRecycled = true; // we're using a mnX which was already mirrored in RTL case
+ }
+
+ if ( nWidth < 0 )
+ nWidth = 0;
+ if ( nWidth != GetOutDev()->mnOutWidth )
+ {
+ GetOutDev()->mnOutWidth = nWidth;
+ bNewSize = true;
+ bCopyBits = false;
+ }
+ }
+ if ( nFlags & PosSizeFlags::Height )
+ {
+ if ( nHeight < 0 )
+ nHeight = 0;
+ if ( nHeight != GetOutDev()->mnOutHeight )
+ {
+ GetOutDev()->mnOutHeight = nHeight;
+ bNewSize = true;
+ bCopyBits = false;
+ }
+ }
+
+ if ( nFlags & PosSizeFlags::X )
+ {
+ tools::Long nOrgX = nX;
+ Point aPtDev( Point( nX+GetOutDev()->mnOutOffX, 0 ) );
+ OutputDevice *pOutDev = GetOutDev();
+ if( pOutDev->HasMirroredGraphics() )
+ {
+ aPtDev.setX( GetOutDev()->mpGraphics->mirror2( aPtDev.X(), *GetOutDev() ) );
+
+ // #106948# always mirror our pos if our parent is not mirroring, even
+ // if we are also not mirroring
+ // RTL: check if parent is in different coordinates
+ if( !bnXRecycled && mpWindowImpl->mpParent && !mpWindowImpl->mpParent->mpWindowImpl->mbFrame && mpWindowImpl->mpParent->GetOutDev()->ImplIsAntiparallel() )
+ {
+ nX = mpWindowImpl->mpParent->GetOutDev()->mnOutWidth - GetOutDev()->mnOutWidth - nX;
+ }
+ /* #i99166# An LTR window in RTL UI that gets sized only would be
+ expected to not moved its upper left point
+ */
+ if( bnXRecycled )
+ {
+ if( GetOutDev()->ImplIsAntiparallel() )
+ {
+ aPtDev.setX( mpWindowImpl->mnAbsScreenX );
+ nOrgX = mpWindowImpl->maPos.X();
+ }
+ }
+ }
+ else if( !bnXRecycled && mpWindowImpl->mpParent && !mpWindowImpl->mpParent->mpWindowImpl->mbFrame && mpWindowImpl->mpParent->GetOutDev()->ImplIsAntiparallel() )
+ {
+ // mirrored window in LTR UI
+ nX = mpWindowImpl->mpParent->GetOutDev()->mnOutWidth - GetOutDev()->mnOutWidth - nX;
+ }
+
+ // check maPos as well, as it could have been changed for client windows (ImplCallMove())
+ if ( mpWindowImpl->mnAbsScreenX != aPtDev.X() || nX != mpWindowImpl->mnX || nOrgX != mpWindowImpl->maPos.X() )
+ {
+ if ( bCopyBits && !pOverlapRegion )
+ {
+ pOverlapRegion.reset( new vcl::Region() );
+ ImplCalcOverlapRegion( GetOutputRectPixel(),
+ *pOverlapRegion, false, true );
+ }
+ mpWindowImpl->mnX = nX;
+ mpWindowImpl->maPos.setX( nOrgX );
+ mpWindowImpl->mnAbsScreenX = aPtDev.X();
+ bNewPos = true;
+ }
+ }
+ if ( nFlags & PosSizeFlags::Y )
+ {
+ // check maPos as well, as it could have been changed for client windows (ImplCallMove())
+ if ( nY != mpWindowImpl->mnY || nY != mpWindowImpl->maPos.Y() )
+ {
+ if ( bCopyBits && !pOverlapRegion )
+ {
+ pOverlapRegion.reset( new vcl::Region() );
+ ImplCalcOverlapRegion( GetOutputRectPixel(),
+ *pOverlapRegion, false, true );
+ }
+ mpWindowImpl->mnY = nY;
+ mpWindowImpl->maPos.setY( nY );
+ bNewPos = true;
+ }
+ }
+
+ if ( !(bNewPos || bNewSize) )
+ return;
+
+ bool bUpdateSysObjPos = false;
+ if ( bNewPos )
+ bUpdateSysObjPos = ImplUpdatePos();
+
+ // the borderwindow always specifies the position for its client window
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->maPos = mpWindowImpl->mpBorderWindow->mpWindowImpl->maPos;
+
+ if ( mpWindowImpl->mpClientWindow )
+ {
+ mpWindowImpl->mpClientWindow->ImplPosSizeWindow( mpWindowImpl->mpClientWindow->mpWindowImpl->mnLeftBorder,
+ mpWindowImpl->mpClientWindow->mpWindowImpl->mnTopBorder,
+ GetOutDev()->mnOutWidth - mpWindowImpl->mpClientWindow->mpWindowImpl->mnLeftBorder-mpWindowImpl->mpClientWindow->mpWindowImpl->mnRightBorder,
+ GetOutDev()->mnOutHeight - mpWindowImpl->mpClientWindow->mpWindowImpl->mnTopBorder-mpWindowImpl->mpClientWindow->mpWindowImpl->mnBottomBorder,
+ PosSizeFlags::X | PosSizeFlags::Y |
+ PosSizeFlags::Width | PosSizeFlags::Height );
+ // If we have a client window, then this is the position
+ // of the Application's floating windows
+ mpWindowImpl->mpClientWindow->mpWindowImpl->maPos = mpWindowImpl->maPos;
+ if ( bNewPos )
+ {
+ if ( mpWindowImpl->mpClientWindow->IsVisible() )
+ {
+ mpWindowImpl->mpClientWindow->ImplCallMove();
+ }
+ else
+ {
+ mpWindowImpl->mpClientWindow->mpWindowImpl->mbCallMove = true;
+ }
+ }
+ }
+
+ // Move()/Resize() will be called only for Show(), such that
+ // at least one is called before Show()
+ if ( IsVisible() )
+ {
+ if ( bNewPos )
+ {
+ ImplCallMove();
+ }
+ if ( bNewSize )
+ {
+ ImplCallResize();
+ }
+ }
+ else
+ {
+ if ( bNewPos )
+ mpWindowImpl->mbCallMove = true;
+ if ( bNewSize )
+ mpWindowImpl->mbCallResize = true;
+ }
+
+ bool bUpdateSysObjClip = false;
+ if ( IsReallyVisible() )
+ {
+ if ( bNewPos || bNewSize )
+ {
+ // set Clip-Flag
+ bUpdateSysObjClip = !ImplSetClipFlag( true );
+ }
+
+ // invalidate window content ?
+ if ( bNewPos || (GetOutDev()->mnOutWidth > nOldOutWidth) || (GetOutDev()->mnOutHeight > nOldOutHeight) )
+ {
+ if ( bNewPos )
+ {
+ bool bInvalidate = false;
+ bool bParentPaint = true;
+ if ( !ImplIsOverlapWindow() )
+ bParentPaint = mpWindowImpl->mpParent->IsPaintEnabled();
+ if ( bCopyBits && bParentPaint && !HasPaintEvent() )
+ {
+ vcl::Region aRegion( GetOutputRectPixel() );
+ if ( mpWindowImpl->mbWinRegion )
+ aRegion.Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) );
+ ImplClipBoundaries( aRegion, false, true );
+ if ( !pOverlapRegion->IsEmpty() )
+ {
+ pOverlapRegion->Move( GetOutDev()->mnOutOffX - nOldOutOffX, GetOutDev()->mnOutOffY - nOldOutOffY );
+ aRegion.Exclude( *pOverlapRegion );
+ }
+ if ( !aRegion.IsEmpty() )
+ {
+ // adapt Paint areas
+ ImplMoveAllInvalidateRegions( tools::Rectangle( Point( nOldOutOffX, nOldOutOffY ),
+ Size( nOldOutWidth, nOldOutHeight ) ),
+ GetOutDev()->mnOutOffX - nOldOutOffX, GetOutDev()->mnOutOffY - nOldOutOffY,
+ true );
+ SalGraphics* pGraphics = ImplGetFrameGraphics();
+ if ( pGraphics )
+ {
+
+ OutputDevice *pOutDev = GetOutDev();
+ const bool bSelectClipRegion = pOutDev->SelectClipRegion( aRegion, pGraphics );
+ if ( bSelectClipRegion )
+ {
+ pGraphics->CopyArea( GetOutDev()->mnOutOffX, GetOutDev()->mnOutOffY,
+ nOldOutOffX, nOldOutOffY,
+ nOldOutWidth, nOldOutHeight,
+ *GetOutDev() );
+ }
+ else
+ bInvalidate = true;
+ }
+ else
+ bInvalidate = true;
+ if ( !bInvalidate )
+ {
+ if ( !pOverlapRegion->IsEmpty() )
+ ImplInvalidateFrameRegion( pOverlapRegion.get(), InvalidateFlags::Children );
+ }
+ }
+ else
+ bInvalidate = true;
+ }
+ else
+ bInvalidate = true;
+ if ( bInvalidate )
+ ImplInvalidateFrameRegion( nullptr, InvalidateFlags::Children );
+ }
+ else
+ {
+ vcl::Region aRegion( GetOutputRectPixel() );
+ aRegion.Exclude( *pOldRegion );
+ if ( mpWindowImpl->mbWinRegion )
+ aRegion.Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) );
+ ImplClipBoundaries( aRegion, false, true );
+ if ( !aRegion.IsEmpty() )
+ ImplInvalidateFrameRegion( &aRegion, InvalidateFlags::Children );
+ }
+ }
+
+ // invalidate Parent or Overlaps
+ if ( bNewPos ||
+ (GetOutDev()->mnOutWidth < nOldOutWidth) || (GetOutDev()->mnOutHeight < nOldOutHeight) )
+ {
+ vcl::Region aRegion( *pOldRegion );
+ if ( !mpWindowImpl->mbPaintTransparent )
+ ImplExcludeWindowRegion( aRegion );
+ ImplClipBoundaries( aRegion, false, true );
+ if ( !aRegion.IsEmpty() && !mpWindowImpl->mpBorderWindow )
+ ImplInvalidateParentFrameRegion( aRegion );
+ }
+ }
+
+ // adapt system objects
+ if ( bUpdateSysObjClip )
+ ImplUpdateSysObjClip();
+ if ( bUpdateSysObjPos )
+ ImplUpdateSysObjPos();
+ if ( bNewSize && mpWindowImpl->mpSysObj )
+ mpWindowImpl->mpSysObj->SetPosSize( GetOutDev()->mnOutOffX, GetOutDev()->mnOutOffY, GetOutDev()->mnOutWidth, GetOutDev()->mnOutHeight );
+}
+
+void Window::ImplNewInputContext()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pFocusWin = pSVData->mpWinData->mpFocusWin;
+ if ( !pFocusWin || !pFocusWin->mpWindowImpl || pFocusWin->isDisposed() )
+ return;
+
+ // Is InputContext changed?
+ const InputContext& rInputContext = pFocusWin->GetInputContext();
+ if ( rInputContext == pFocusWin->mpWindowImpl->mpFrameData->maOldInputContext )
+ return;
+
+ pFocusWin->mpWindowImpl->mpFrameData->maOldInputContext = rInputContext;
+
+ SalInputContext aNewContext;
+ const vcl::Font& rFont = rInputContext.GetFont();
+ const OUString& rFontName = rFont.GetFamilyName();
+ rtl::Reference<LogicalFontInstance> pFontInstance;
+ aNewContext.mpFont = nullptr;
+ if (!rFontName.isEmpty())
+ {
+ OutputDevice *pFocusWinOutDev = pFocusWin->GetOutDev();
+ Size aSize = pFocusWinOutDev->ImplLogicToDevicePixel( rFont.GetFontSize() );
+ if ( !aSize.Height() )
+ {
+ // only set default sizes if the font height in logical
+ // coordinates equals 0
+ if ( rFont.GetFontSize().Height() )
+ aSize.setHeight( 1 );
+ else
+ aSize.setHeight( (12*pFocusWin->GetOutDev()->mnDPIY)/72 );
+ }
+ pFontInstance = pFocusWin->GetOutDev()->mxFontCache->GetFontInstance(
+ pFocusWin->GetOutDev()->mxFontCollection.get(),
+ rFont, aSize, static_cast<float>(aSize.Height()) );
+ if ( pFontInstance )
+ aNewContext.mpFont = pFontInstance;
+ }
+ aNewContext.mnOptions = rInputContext.GetOptions();
+ pFocusWin->ImplGetFrame()->SetInputContext( &aNewContext );
+}
+
+void Window::SetDumpAsPropertyTreeHdl(const Link<tools::JsonWriter&, void>& rLink)
+{
+ if (mpWindowImpl) // may be called after dispose
+ {
+ mpWindowImpl->maDumpAsPropertyTreeHdl = rLink;
+ }
+}
+
+void Window::SetModalHierarchyHdl(const Link<bool, void>& rLink)
+{
+ ImplGetFrame()->SetModalHierarchyHdl(rLink);
+}
+
+KeyIndicatorState Window::GetIndicatorState() const
+{
+ return mpWindowImpl->mpFrame->GetIndicatorState();
+}
+
+void Window::SimulateKeyPress( sal_uInt16 nKeyCode ) const
+{
+ mpWindowImpl->mpFrame->SimulateKeyPress(nKeyCode);
+}
+
+void Window::KeyInput( const KeyEvent& rKEvt )
+{
+ KeyCode cod = rKEvt.GetKeyCode ();
+ bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
+
+ // do not respond to accelerators unless Alt or Ctrl is held
+ if (cod.GetCode () >= 0x200 && cod.GetCode () <= 0x219)
+ {
+ if (autoacc && cod.GetModifier () != KEY_MOD2 && !(cod.GetModifier() & KEY_MOD1))
+ return;
+ }
+
+ NotifyEvent aNEvt( MouseNotifyEvent::KEYINPUT, this, &rKEvt );
+ if ( !CompatNotify( aNEvt ) )
+ mpWindowImpl->mbKeyInput = true;
+}
+
+void Window::KeyUp( const KeyEvent& rKEvt )
+{
+ NotifyEvent aNEvt( MouseNotifyEvent::KEYUP, this, &rKEvt );
+ if ( !CompatNotify( aNEvt ) )
+ mpWindowImpl->mbKeyUp = true;
+}
+
+void Window::Draw( OutputDevice*, const Point&, SystemTextColorFlags )
+{
+}
+
+void Window::Move() {}
+
+void Window::Resize() {}
+
+void Window::Activate() {}
+
+void Window::Deactivate() {}
+
+void Window::GetFocus()
+{
+ if ( HasFocus() && mpWindowImpl->mpLastFocusWindow && !(mpWindowImpl->mnDlgCtrlFlags & DialogControlFlags::WantFocus) )
+ {
+ VclPtr<vcl::Window> xWindow(this);
+ mpWindowImpl->mpLastFocusWindow->GrabFocus();
+ if( xWindow->isDisposed() )
+ return;
+ }
+
+ NotifyEvent aNEvt( MouseNotifyEvent::GETFOCUS, this );
+ CompatNotify( aNEvt );
+}
+
+void Window::LoseFocus()
+{
+ NotifyEvent aNEvt( MouseNotifyEvent::LOSEFOCUS, this );
+ CompatNotify( aNEvt );
+}
+
+void Window::SetHelpHdl(const Link<vcl::Window&, bool>& rLink)
+{
+ if (mpWindowImpl) // may be called after dispose
+ {
+ mpWindowImpl->maHelpRequestHdl = rLink;
+ }
+}
+
+void Window::RequestHelp( const HelpEvent& rHEvt )
+{
+ // if Balloon-Help is requested, show the balloon
+ // with help text set
+ if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
+ {
+ OUString rStr = GetHelpText();
+ if ( rStr.isEmpty() )
+ rStr = GetQuickHelpText();
+ if ( rStr.isEmpty() && ImplGetParent() && !ImplIsOverlapWindow() )
+ ImplGetParent()->RequestHelp( rHEvt );
+ else
+ {
+ Point aPos = GetPosPixel();
+ if ( ImplGetParent() && !ImplIsOverlapWindow() )
+ aPos = OutputToScreenPixel(Point(0, 0));
+ tools::Rectangle aRect( aPos, GetSizePixel() );
+
+ Help::ShowBalloon( this, rHEvt.GetMousePosPixel(), aRect, rStr );
+ }
+ }
+ else if ( rHEvt.GetMode() & HelpEventMode::QUICK )
+ {
+ const OUString& rStr = GetQuickHelpText();
+ if ( rStr.isEmpty() && ImplGetParent() && !ImplIsOverlapWindow() )
+ ImplGetParent()->RequestHelp( rHEvt );
+ else
+ {
+ Point aPos = GetPosPixel();
+ if ( ImplGetParent() && !ImplIsOverlapWindow() )
+ aPos = OutputToScreenPixel(Point(0, 0));
+ tools::Rectangle aRect( aPos, GetSizePixel() );
+ Help::ShowQuickHelp( this, aRect, rStr, QuickHelpFlags::CtrlText );
+ }
+ }
+ else if (!mpWindowImpl->maHelpRequestHdl.IsSet() || mpWindowImpl->maHelpRequestHdl.Call(*this))
+ {
+ OUString aStrHelpId( OStringToOUString( GetHelpId(), RTL_TEXTENCODING_UTF8 ) );
+ if ( aStrHelpId.isEmpty() && ImplGetParent() )
+ ImplGetParent()->RequestHelp( rHEvt );
+ else
+ {
+ Help* pHelp = Application::GetHelp();
+ if ( pHelp )
+ {
+ if( !aStrHelpId.isEmpty() )
+ pHelp->Start( aStrHelpId, this );
+ else
+ pHelp->Start( OOO_HELP_INDEX, this );
+ }
+ }
+ }
+}
+
+void Window::Command( const CommandEvent& rCEvt )
+{
+ CallEventListeners( VclEventId::WindowCommand, const_cast<CommandEvent *>(&rCEvt) );
+
+ NotifyEvent aNEvt( MouseNotifyEvent::COMMAND, this, &rCEvt );
+ if ( !CompatNotify( aNEvt ) )
+ mpWindowImpl->mbCommand = true;
+}
+
+void Window::Tracking( const TrackingEvent& rTEvt )
+{
+
+ ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this );
+ if( pWrapper )
+ pWrapper->Tracking( rTEvt );
+}
+
+void Window::StateChanged(StateChangedType eType)
+{
+ switch (eType)
+ {
+ //stuff that doesn't invalidate the layout
+ case StateChangedType::ControlForeground:
+ case StateChangedType::ControlBackground:
+ case StateChangedType::UpdateMode:
+ case StateChangedType::ReadOnly:
+ case StateChangedType::Enable:
+ case StateChangedType::State:
+ case StateChangedType::Data:
+ case StateChangedType::InitShow:
+ case StateChangedType::ControlFocus:
+ break;
+ //stuff that does invalidate the layout
+ default:
+ queue_resize(eType);
+ break;
+ }
+}
+
+void Window::SetStyle( WinBits nStyle )
+{
+ if ( mpWindowImpl && mpWindowImpl->mnStyle != nStyle )
+ {
+ mpWindowImpl->mnPrevStyle = mpWindowImpl->mnStyle;
+ mpWindowImpl->mnStyle = nStyle;
+ CompatStateChanged( StateChangedType::Style );
+ }
+}
+
+void Window::SetExtendedStyle( WindowExtendedStyle nExtendedStyle )
+{
+
+ if ( mpWindowImpl->mnExtendedStyle == nExtendedStyle )
+ return;
+
+ vcl::Window* pWindow = ImplGetBorderWindow();
+ if( ! pWindow )
+ pWindow = this;
+ if( pWindow->mpWindowImpl->mbFrame )
+ {
+ SalExtStyle nExt = 0;
+ if( nExtendedStyle & WindowExtendedStyle::Document )
+ nExt |= SAL_FRAME_EXT_STYLE_DOCUMENT;
+ if( nExtendedStyle & WindowExtendedStyle::DocModified )
+ nExt |= SAL_FRAME_EXT_STYLE_DOCMODIFIED;
+
+ pWindow->ImplGetFrame()->SetExtendedFrameStyle( nExt );
+ }
+ mpWindowImpl->mnExtendedStyle = nExtendedStyle;
+}
+
+void Window::SetBorderStyle( WindowBorderStyle nBorderStyle )
+{
+
+ if ( !mpWindowImpl->mpBorderWindow )
+ return;
+
+ if( nBorderStyle == WindowBorderStyle::REMOVEBORDER &&
+ ! mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame &&
+ mpWindowImpl->mpBorderWindow->mpWindowImpl->mpParent
+ )
+ {
+ // this is a little awkward: some controls (e.g. svtools ProgressBar)
+ // cannot avoid getting constructed with WB_BORDER but want to disable
+ // borders in case of NWF drawing. So they need a method to remove their border window
+ VclPtr<vcl::Window> pBorderWin = mpWindowImpl->mpBorderWindow;
+ // remove us as border window's client
+ pBorderWin->mpWindowImpl->mpClientWindow = nullptr;
+ mpWindowImpl->mpBorderWindow = nullptr;
+ mpWindowImpl->mpRealParent = pBorderWin->mpWindowImpl->mpParent;
+ // reparent us above the border window
+ SetParent( pBorderWin->mpWindowImpl->mpParent );
+ // set us to the position and size of our previous border
+ Point aBorderPos( pBorderWin->GetPosPixel() );
+ Size aBorderSize( pBorderWin->GetSizePixel() );
+ setPosSizePixel( aBorderPos.X(), aBorderPos.Y(), aBorderSize.Width(), aBorderSize.Height() );
+ // release border window
+ pBorderWin.disposeAndClear();
+
+ // set new style bits
+ SetStyle( GetStyle() & (~WB_BORDER) );
+ }
+ else
+ {
+ if ( mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetBorderStyle( nBorderStyle );
+ else
+ mpWindowImpl->mpBorderWindow->SetBorderStyle( nBorderStyle );
+ }
+}
+
+WindowBorderStyle Window::GetBorderStyle() const
+{
+
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ if ( mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW )
+ return static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->GetBorderStyle();
+ else
+ return mpWindowImpl->mpBorderWindow->GetBorderStyle();
+ }
+
+ return WindowBorderStyle::NONE;
+}
+
+tools::Long Window::CalcTitleWidth() const
+{
+
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ if ( mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW )
+ return static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->CalcTitleWidth();
+ else
+ return mpWindowImpl->mpBorderWindow->CalcTitleWidth();
+ }
+ else if ( mpWindowImpl->mbFrame && (mpWindowImpl->mnStyle & WB_MOVEABLE) )
+ {
+ // we guess the width for frame windows as we do not know the
+ // border of external dialogs
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ vcl::Font aFont = GetFont();
+ const_cast<vcl::Window*>(this)->SetPointFont(const_cast<::OutputDevice&>(*GetOutDev()), rStyleSettings.GetTitleFont());
+ tools::Long nTitleWidth = GetTextWidth( GetText() );
+ const_cast<vcl::Window*>(this)->SetFont( aFont );
+ nTitleWidth += rStyleSettings.GetTitleHeight() * 3;
+ nTitleWidth += StyleSettings::GetBorderSize() * 2;
+ nTitleWidth += 10;
+ return nTitleWidth;
+ }
+
+ return 0;
+}
+
+void Window::SetInputContext( const InputContext& rInputContext )
+{
+
+ mpWindowImpl->maInputContext = rInputContext;
+ if ( !mpWindowImpl->mbInFocusHdl && HasFocus() )
+ ImplNewInputContext();
+}
+
+void Window::PostExtTextInputEvent(VclEventId nType, const OUString& rText)
+{
+ switch (nType)
+ {
+ case VclEventId::ExtTextInput:
+ {
+ std::unique_ptr<ExtTextInputAttr[]> pAttr(new ExtTextInputAttr[rText.getLength()]);
+ for (int i = 0; i < rText.getLength(); ++i) {
+ pAttr[i] = ExtTextInputAttr::Underline;
+ }
+ SalExtTextInputEvent aEvent { rText, pAttr.get(), rText.getLength(), EXTTEXTINPUT_CURSOR_OVERWRITE };
+ ImplWindowFrameProc(this, SalEvent::ExtTextInput, &aEvent);
+ }
+ break;
+ case VclEventId::EndExtTextInput:
+ ImplWindowFrameProc(this, SalEvent::EndExtTextInput, nullptr);
+ break;
+ default:
+ assert(false);
+ }
+}
+
+void Window::EndExtTextInput()
+{
+ if ( mpWindowImpl->mbExtTextInput )
+ ImplGetFrame()->EndExtTextInput( EndExtTextInputFlags::Complete );
+}
+
+void Window::SetCursorRect( const tools::Rectangle* pRect, tools::Long nExtTextInputWidth )
+{
+
+ ImplWinData* pWinData = ImplGetWinData();
+ if ( pWinData->mpCursorRect )
+ {
+ if ( pRect )
+ pWinData->mpCursorRect = *pRect;
+ else
+ pWinData->mpCursorRect.reset();
+ }
+ else
+ {
+ if ( pRect )
+ pWinData->mpCursorRect = *pRect;
+ }
+
+ pWinData->mnCursorExtWidth = nExtTextInputWidth;
+
+}
+
+const tools::Rectangle* Window::GetCursorRect() const
+{
+
+ ImplWinData* pWinData = ImplGetWinData();
+ return pWinData->mpCursorRect ? &*pWinData->mpCursorRect : nullptr;
+}
+
+tools::Long Window::GetCursorExtTextInputWidth() const
+{
+
+ ImplWinData* pWinData = ImplGetWinData();
+ return pWinData->mnCursorExtWidth;
+}
+
+void Window::SetCompositionCharRect( const tools::Rectangle* pRect, tools::Long nCompositionLength, bool bVertical ) {
+
+ ImplWinData* pWinData = ImplGetWinData();
+ pWinData->mpCompositionCharRects.reset();
+ pWinData->mbVertical = bVertical;
+ pWinData->mnCompositionCharRects = nCompositionLength;
+ if ( pRect && (nCompositionLength > 0) )
+ {
+ pWinData->mpCompositionCharRects.reset( new tools::Rectangle[nCompositionLength] );
+ for (tools::Long i = 0; i < nCompositionLength; ++i)
+ pWinData->mpCompositionCharRects[i] = pRect[i];
+ }
+}
+
+void Window::CollectChildren(::std::vector<vcl::Window *>& rAllChildren )
+{
+ rAllChildren.push_back( this );
+
+ VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->CollectChildren( rAllChildren );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+}
+
+void Window::SetPointFont(vcl::RenderContext& rRenderContext, const vcl::Font& rFont)
+{
+ vcl::Font aFont = rFont;
+ ImplPointToLogic(rRenderContext, aFont);
+ rRenderContext.SetFont(aFont);
+}
+
+vcl::Font Window::GetPointFont(vcl::RenderContext const & rRenderContext) const
+{
+ vcl::Font aFont = rRenderContext.GetFont();
+ ImplLogicToPoint(rRenderContext, aFont);
+ return aFont;
+}
+
+void Window::Show(bool bVisible, ShowFlags nFlags)
+{
+ if ( !mpWindowImpl || mpWindowImpl->mbVisible == bVisible )
+ return;
+
+ VclPtr<vcl::Window> xWindow(this);
+
+ bool bRealVisibilityChanged = false;
+ mpWindowImpl->mbVisible = bVisible;
+
+ if ( !bVisible )
+ {
+ ImplHideAllOverlaps();
+ if( !xWindow->mpWindowImpl )
+ return;
+
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ bool bOldUpdate = mpWindowImpl->mpBorderWindow->mpWindowImpl->mbNoParentUpdate;
+ if ( mpWindowImpl->mbNoParentUpdate )
+ mpWindowImpl->mpBorderWindow->mpWindowImpl->mbNoParentUpdate = true;
+ mpWindowImpl->mpBorderWindow->Show( false, nFlags );
+ mpWindowImpl->mpBorderWindow->mpWindowImpl->mbNoParentUpdate = bOldUpdate;
+ }
+ else if ( mpWindowImpl->mbFrame )
+ {
+ mpWindowImpl->mbSuppressAccessibilityEvents = true;
+ mpWindowImpl->mpFrame->Show( false );
+ }
+
+ CompatStateChanged( StateChangedType::Visible );
+
+ if ( mpWindowImpl->mbReallyVisible )
+ {
+ if ( mpWindowImpl->mbInitWinClipRegion )
+ ImplInitWinClipRegion();
+
+ vcl::Region aInvRegion = mpWindowImpl->maWinClipRegion;
+
+ if( !xWindow->mpWindowImpl )
+ return;
+
+ bRealVisibilityChanged = mpWindowImpl->mbReallyVisible;
+ ImplResetReallyVisible();
+ ImplSetClipFlag();
+
+ if ( ImplIsOverlapWindow() && !mpWindowImpl->mbFrame )
+ {
+ // convert focus
+ if ( !(nFlags & ShowFlags::NoFocusChange) && HasChildPathFocus() )
+ {
+ if ( mpWindowImpl->mpOverlapWindow->IsEnabled() &&
+ mpWindowImpl->mpOverlapWindow->IsInputEnabled() &&
+ ! mpWindowImpl->mpOverlapWindow->IsInModalMode()
+ )
+ mpWindowImpl->mpOverlapWindow->GrabFocus();
+ }
+ }
+
+ if ( !mpWindowImpl->mbFrame )
+ {
+ if (mpWindowImpl->mpWinData && mpWindowImpl->mpWinData->mbEnableNativeWidget)
+ {
+ /*
+ * #i48371# native theming: some themes draw outside the control
+ * area we tell them to (bad thing, but we cannot do much about it ).
+ * On hiding these controls they get invalidated with their window rectangle
+ * which leads to the parts outside the control area being left and not
+ * invalidated. Workaround: invalidate an area on the parent, too
+ */
+ const int workaround_border = 5;
+ tools::Rectangle aBounds( aInvRegion.GetBoundRect() );
+ aBounds.AdjustLeft( -workaround_border );
+ aBounds.AdjustTop( -workaround_border );
+ aBounds.AdjustRight(workaround_border );
+ aBounds.AdjustBottom(workaround_border );
+ aInvRegion = aBounds;
+ }
+ if ( !mpWindowImpl->mbNoParentUpdate )
+ {
+ if ( !aInvRegion.IsEmpty() )
+ ImplInvalidateParentFrameRegion( aInvRegion );
+ }
+ ImplGenerateMouseMove();
+ }
+ }
+ }
+ else
+ {
+ // inherit native widget flag for form controls
+ // required here, because frames never show up in the child hierarchy - which should be fixed...
+ // eg, the drop down of a combobox which is a system floating window
+ if( mpWindowImpl->mbFrame && GetParent() && !GetParent()->isDisposed() &&
+ GetParent()->IsCompoundControl() &&
+ GetParent()->IsNativeWidgetEnabled() != IsNativeWidgetEnabled() &&
+ !(GetStyle() & WB_TOOLTIPWIN) )
+ {
+ EnableNativeWidget( GetParent()->IsNativeWidgetEnabled() );
+ }
+
+ if ( mpWindowImpl->mbCallMove )
+ {
+ ImplCallMove();
+ }
+ if ( mpWindowImpl->mbCallResize )
+ {
+ ImplCallResize();
+ }
+
+ CompatStateChanged( StateChangedType::Visible );
+
+ vcl::Window* pTestParent;
+ if ( ImplIsOverlapWindow() )
+ pTestParent = mpWindowImpl->mpOverlapWindow;
+ else
+ pTestParent = ImplGetParent();
+ if ( mpWindowImpl->mbFrame || pTestParent->mpWindowImpl->mbReallyVisible )
+ {
+ // if a window becomes visible, send all child windows a StateChange,
+ // such that these can initialise themselves
+ ImplCallInitShow();
+
+ // If it is a SystemWindow it automatically pops up on top of
+ // all other windows if needed.
+ if ( ImplIsOverlapWindow() && !(nFlags & ShowFlags::NoActivate) )
+ {
+ ImplStartToTop(( nFlags & ShowFlags::ForegroundTask ) ? ToTopFlags::ForegroundTask : ToTopFlags::NONE );
+ ImplFocusToTop( ToTopFlags::NONE, false );
+ }
+
+ // adjust mpWindowImpl->mbReallyVisible
+ bRealVisibilityChanged = !mpWindowImpl->mbReallyVisible;
+ ImplSetReallyVisible();
+
+ // assure clip rectangles will be recalculated
+ ImplSetClipFlag();
+
+ if ( !mpWindowImpl->mbFrame )
+ {
+ InvalidateFlags nInvalidateFlags = InvalidateFlags::Children;
+ if( ! IsPaintTransparent() )
+ nInvalidateFlags |= InvalidateFlags::NoTransparent;
+ ImplInvalidate( nullptr, nInvalidateFlags );
+ ImplGenerateMouseMove();
+ }
+ }
+
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->Show( true, nFlags );
+ else if ( mpWindowImpl->mbFrame )
+ {
+ // #106431#, hide SplashScreen
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData->mpIntroWindow )
+ {
+ // The right way would be just to call this (not even in the 'if')
+ auto pApp = GetpApp();
+ if ( pApp )
+ pApp->InitFinished();
+ }
+ else if ( !ImplIsWindowOrChild( pSVData->mpIntroWindow ) )
+ {
+ // ... but the VCL splash is broken, and it needs this
+ // (for ./soffice .uno:NewDoc)
+ pSVData->mpIntroWindow->Hide();
+ }
+
+ //SAL_WARN_IF( mpWindowImpl->mbSuppressAccessibilityEvents, "vcl", "Window::Show() - Frame reactivated");
+ mpWindowImpl->mbSuppressAccessibilityEvents = false;
+
+ mpWindowImpl->mbPaintFrame = true;
+ if (!Application::IsHeadlessModeEnabled())
+ {
+ bool bNoActivate(nFlags & (ShowFlags::NoActivate|ShowFlags::NoFocusChange));
+ mpWindowImpl->mpFrame->Show( true, bNoActivate );
+ }
+ if( !xWindow->mpWindowImpl )
+ return;
+
+ // Query the correct size of the window, if we are waiting for
+ // a system resize
+ if ( mpWindowImpl->mbWaitSystemResize )
+ {
+ tools::Long nOutWidth;
+ tools::Long nOutHeight;
+ mpWindowImpl->mpFrame->GetClientSize( nOutWidth, nOutHeight );
+ ImplHandleResize( this, nOutWidth, nOutHeight );
+ }
+
+ if (mpWindowImpl->mpFrameData->mpBuffer && mpWindowImpl->mpFrameData->mpBuffer->GetOutputSizePixel() != GetOutputSizePixel())
+ // Make sure that the buffer size matches the window size, even if no resize was needed.
+ mpWindowImpl->mpFrameData->mpBuffer->SetOutputSizePixel(GetOutputSizePixel());
+ }
+
+ if( !xWindow->mpWindowImpl )
+ return;
+
+ ImplShowAllOverlaps();
+ }
+
+ if( !xWindow->mpWindowImpl )
+ return;
+
+ // the SHOW/HIDE events also serve as indicators to send child creation/destroy events to the access bridge
+ // However, the access bridge only uses this event if the data member is not NULL (it's kind of a hack that
+ // we re-use the SHOW/HIDE events this way, with this particular semantics).
+ // Since #104887#, the notifications for the access bridge are done in Impl(Set|Reset)ReallyVisible. Here, we
+ // now only notify with a NULL data pointer, for all other clients except the access bridge.
+ if ( !bRealVisibilityChanged )
+ CallEventListeners( mpWindowImpl->mbVisible ? VclEventId::WindowShow : VclEventId::WindowHide );
+ if( xWindow->isDisposed() )
+ return;
+
+}
+
+Size Window::GetSizePixel() const
+{
+ if (!mpWindowImpl)
+ {
+ SAL_WARN("vcl.layout", "WTF no windowimpl");
+ return Size(0,0);
+ }
+
+ // #i43257# trigger pending resize handler to assure correct window sizes
+ if( mpWindowImpl->mpFrameData->maResizeIdle.IsActive() )
+ {
+ VclPtr<vcl::Window> xWindow( const_cast<Window*>(this) );
+ mpWindowImpl->mpFrameData->maResizeIdle.Stop();
+ mpWindowImpl->mpFrameData->maResizeIdle.Invoke( nullptr );
+ if( xWindow->isDisposed() )
+ return Size(0,0);
+ }
+
+ return Size( GetOutDev()->mnOutWidth + mpWindowImpl->mnLeftBorder+mpWindowImpl->mnRightBorder,
+ GetOutDev()->mnOutHeight + mpWindowImpl->mnTopBorder+mpWindowImpl->mnBottomBorder );
+}
+
+void Window::GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder,
+ sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const
+{
+ rLeftBorder = mpWindowImpl->mnLeftBorder;
+ rTopBorder = mpWindowImpl->mnTopBorder;
+ rRightBorder = mpWindowImpl->mnRightBorder;
+ rBottomBorder = mpWindowImpl->mnBottomBorder;
+}
+
+void Window::Enable( bool bEnable, bool bChild )
+{
+ if ( isDisposed() )
+ return;
+
+ if ( !bEnable )
+ {
+ // the tracking mode will be stopped or the capture will be stolen
+ // when a window is disabled,
+ if ( IsTracking() )
+ EndTracking( TrackingEventFlags::Cancel );
+ if ( IsMouseCaptured() )
+ ReleaseMouse();
+ // try to pass focus to the next control
+ // if the window has focus and is contained in the dialog control
+ // mpWindowImpl->mbDisabled should only be set after a call of ImplDlgCtrlNextWindow().
+ // Otherwise ImplDlgCtrlNextWindow() should be used
+ if ( HasFocus() )
+ ImplDlgCtrlNextWindow();
+ }
+
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ mpWindowImpl->mpBorderWindow->Enable( bEnable, false );
+ if ( (mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW) &&
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow->Enable( bEnable );
+ }
+
+ // #i56102# restore app focus win in case the
+ // window was disabled when the frame focus changed
+ ImplSVData* pSVData = ImplGetSVData();
+ if (bEnable && pSVData->mpWinData->mpFocusWin == nullptr
+ && mpWindowImpl->mpFrameData->mbHasFocus && mpWindowImpl->mpFrameData->mpFocusWin == this)
+ pSVData->mpWinData->mpFocusWin = this;
+
+ if ( mpWindowImpl->mbDisabled != !bEnable )
+ {
+ mpWindowImpl->mbDisabled = !bEnable;
+ if ( mpWindowImpl->mpSysObj )
+ mpWindowImpl->mpSysObj->Enable( bEnable && !mpWindowImpl->mbInputDisabled );
+ CompatStateChanged( StateChangedType::Enable );
+
+ CallEventListeners( bEnable ? VclEventId::WindowEnabled : VclEventId::WindowDisabled );
+ }
+
+ if ( bChild )
+ {
+ VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->Enable( bEnable, bChild );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+ }
+
+ if ( IsReallyVisible() )
+ ImplGenerateMouseMove();
+}
+
+void Window::EnableInput( bool bEnable, bool bChild )
+{
+ if (!mpWindowImpl)
+ return;
+
+ if ( mpWindowImpl->mpBorderWindow )
+ {
+ mpWindowImpl->mpBorderWindow->EnableInput( bEnable, false );
+ if ( (mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW) &&
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow )
+ static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow->EnableInput( bEnable );
+ }
+
+ if ( (!bEnable && mpWindowImpl->meAlwaysInputMode != AlwaysInputEnabled) || bEnable )
+ {
+ // automatically stop the tracking mode or steal capture
+ // if the window is disabled
+ if ( !bEnable )
+ {
+ if ( IsTracking() )
+ EndTracking( TrackingEventFlags::Cancel );
+ if ( IsMouseCaptured() )
+ ReleaseMouse();
+ }
+
+ if ( mpWindowImpl->mbInputDisabled != !bEnable )
+ {
+ mpWindowImpl->mbInputDisabled = !bEnable;
+ if ( mpWindowImpl->mpSysObj )
+ mpWindowImpl->mpSysObj->Enable( !mpWindowImpl->mbDisabled && bEnable );
+ }
+ }
+
+ // #i56102# restore app focus win in case the
+ // window was disabled when the frame focus changed
+ ImplSVData* pSVData = ImplGetSVData();
+ if (bEnable && pSVData->mpWinData->mpFocusWin == nullptr
+ && mpWindowImpl->mpFrameData->mbHasFocus && mpWindowImpl->mpFrameData->mpFocusWin == this)
+ pSVData->mpWinData->mpFocusWin = this;
+
+ if ( bChild )
+ {
+ VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->EnableInput( bEnable, bChild );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+ }
+
+ if ( IsReallyVisible() )
+ ImplGenerateMouseMove();
+}
+
+void Window::EnableInput( bool bEnable, const vcl::Window* pExcludeWindow )
+{
+ if (!mpWindowImpl)
+ return;
+
+ EnableInput( bEnable );
+
+ // pExecuteWindow is the first Overlap-Frame --> if this
+ // shouldn't be the case, then this must be changed in dialog.cxx
+ if( pExcludeWindow )
+ pExcludeWindow = pExcludeWindow->ImplGetFirstOverlapWindow();
+ vcl::Window* pSysWin = mpWindowImpl->mpFrameWindow->mpWindowImpl->mpFrameData->mpFirstOverlap;
+ while ( pSysWin )
+ {
+ // Is Window in the path from this window
+ if ( ImplGetFirstOverlapWindow()->ImplIsWindowOrChild( pSysWin, true ) )
+ {
+ // Is Window not in the exclude window path or not the
+ // exclude window, then change the status
+ if ( !pExcludeWindow || !pExcludeWindow->ImplIsWindowOrChild( pSysWin, true ) )
+ pSysWin->EnableInput( bEnable );
+ }
+ pSysWin = pSysWin->mpWindowImpl->mpNextOverlap;
+ }
+
+ // enable/disable floating system windows as well
+ vcl::Window* pFrameWin = ImplGetSVData()->maFrameData.mpFirstFrame;
+ while ( pFrameWin )
+ {
+ if( pFrameWin->ImplIsFloatingWindow() )
+ {
+ // Is Window in the path from this window
+ if ( ImplGetFirstOverlapWindow()->ImplIsWindowOrChild( pFrameWin, true ) )
+ {
+ // Is Window not in the exclude window path or not the
+ // exclude window, then change the status
+ if ( !pExcludeWindow || !pExcludeWindow->ImplIsWindowOrChild( pFrameWin, true ) )
+ pFrameWin->EnableInput( bEnable );
+ }
+ }
+ pFrameWin = pFrameWin->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+
+ // the same for ownerdraw floating windows
+ if( !mpWindowImpl->mbFrame )
+ return;
+
+ ::std::vector< VclPtr<vcl::Window> >& rList = mpWindowImpl->mpFrameData->maOwnerDrawList;
+ for (auto const& elem : rList)
+ {
+ // Is Window in the path from this window
+ if ( ImplGetFirstOverlapWindow()->ImplIsWindowOrChild( elem, true ) )
+ {
+ // Is Window not in the exclude window path or not the
+ // exclude window, then change the status
+ if ( !pExcludeWindow || !pExcludeWindow->ImplIsWindowOrChild( elem, true ) )
+ elem->EnableInput( bEnable );
+ }
+ }
+}
+
+void Window::AlwaysEnableInput( bool bAlways, bool bChild )
+{
+
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->AlwaysEnableInput( bAlways, false );
+
+ if( bAlways && mpWindowImpl->meAlwaysInputMode != AlwaysInputEnabled )
+ {
+ mpWindowImpl->meAlwaysInputMode = AlwaysInputEnabled;
+ EnableInput(true, false);
+ }
+ else if( ! bAlways && mpWindowImpl->meAlwaysInputMode == AlwaysInputEnabled )
+ {
+ mpWindowImpl->meAlwaysInputMode = AlwaysInputNone;
+ }
+
+ if ( bChild )
+ {
+ VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild;
+ while ( pChild )
+ {
+ pChild->AlwaysEnableInput( bAlways, bChild );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+ }
+}
+
+void Window::SetActivateMode( ActivateModeFlags nMode )
+{
+
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->SetActivateMode( nMode );
+
+ if ( mpWindowImpl->mnActivateMode == nMode )
+ return;
+
+ mpWindowImpl->mnActivateMode = nMode;
+
+ // possibly trigger Deactivate/Activate
+ if ( mpWindowImpl->mnActivateMode != ActivateModeFlags::NONE )
+ {
+ if ( (mpWindowImpl->mbActive || (GetType() == WindowType::BORDERWINDOW)) &&
+ !HasChildPathFocus( true ) )
+ {
+ mpWindowImpl->mbActive = false;
+ Deactivate();
+ }
+ }
+ else
+ {
+ if ( !mpWindowImpl->mbActive || (GetType() == WindowType::BORDERWINDOW) )
+ {
+ mpWindowImpl->mbActive = true;
+ Activate();
+ }
+ }
+}
+
+void Window::setPosSizePixel( tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight, PosSizeFlags nFlags )
+{
+ bool bHasValidSize = !mpWindowImpl->mbDefSize;
+
+ if ( nFlags & PosSizeFlags::Pos )
+ mpWindowImpl->mbDefPos = false;
+ if ( nFlags & PosSizeFlags::Size )
+ mpWindowImpl->mbDefSize = false;
+
+ // The top BorderWindow is the window which is to be positioned
+ VclPtr<vcl::Window> pWindow = this;
+ while ( pWindow->mpWindowImpl->mpBorderWindow )
+ pWindow = pWindow->mpWindowImpl->mpBorderWindow;
+
+ if ( pWindow->mpWindowImpl->mbFrame )
+ {
+ // Note: if we're positioning a frame, the coordinates are interpreted
+ // as being the top-left corner of the window's client area and NOT
+ // as the position of the border ! (due to limitations of several UNIX window managers)
+ tools::Long nOldWidth = pWindow->GetOutDev()->mnOutWidth;
+
+ if ( !(nFlags & PosSizeFlags::Width) )
+ nWidth = pWindow->GetOutDev()->mnOutWidth;
+ if ( !(nFlags & PosSizeFlags::Height) )
+ nHeight = pWindow->GetOutDev()->mnOutHeight;
+
+ sal_uInt16 nSysFlags=0;
+ VclPtr<vcl::Window> pParent = GetParent();
+ VclPtr<vcl::Window> pWinParent = pWindow->GetParent();
+
+ if( nFlags & PosSizeFlags::Width )
+ nSysFlags |= SAL_FRAME_POSSIZE_WIDTH;
+ if( nFlags & PosSizeFlags::Height )
+ nSysFlags |= SAL_FRAME_POSSIZE_HEIGHT;
+ if( nFlags & PosSizeFlags::X )
+ {
+ nSysFlags |= SAL_FRAME_POSSIZE_X;
+ if( pWinParent && (pWindow->GetStyle() & WB_SYSTEMCHILDWINDOW) )
+ {
+ nX += pWinParent->GetOutDev()->mnOutOffX;
+ }
+ if( pParent && pParent->GetOutDev()->ImplIsAntiparallel() )
+ {
+ tools::Rectangle aRect( Point ( nX, nY ), Size( nWidth, nHeight ) );
+ const OutputDevice *pParentOutDev = pParent->GetOutDev();
+ if (!comphelper::LibreOfficeKit::isActive())
+ pParentOutDev->ReMirror( aRect );
+ nX = aRect.Left();
+ }
+ }
+ if( !comphelper::LibreOfficeKit::isActive() &&
+ !(nFlags & PosSizeFlags::X) && bHasValidSize &&
+ pWindow->mpWindowImpl->mpFrame->maGeometry.nWidth )
+ {
+ // RTL: make sure the old right aligned position is not changed
+ // system windows will always grow to the right
+ if ( pWinParent )
+ {
+ OutputDevice *pParentOutDev = pWinParent->GetOutDev();
+ if( pParentOutDev->HasMirroredGraphics() )
+ {
+ const SalFrameGeometry& aSysGeometry = mpWindowImpl->mpFrame->GetUnmirroredGeometry();
+ const SalFrameGeometry& aParentSysGeometry =
+ pWinParent->mpWindowImpl->mpFrame->GetUnmirroredGeometry();
+ tools::Long myWidth = nOldWidth;
+ if( !myWidth )
+ myWidth = aSysGeometry.nWidth;
+ if( !myWidth )
+ myWidth = nWidth;
+ nFlags |= PosSizeFlags::X;
+ nSysFlags |= SAL_FRAME_POSSIZE_X;
+ nX = aParentSysGeometry.nX - aSysGeometry.nLeftDecoration + aParentSysGeometry.nWidth
+ - myWidth - 1 - aSysGeometry.nX;
+ }
+ }
+ }
+ if( nFlags & PosSizeFlags::Y )
+ {
+ nSysFlags |= SAL_FRAME_POSSIZE_Y;
+ if( pWinParent && (pWindow->GetStyle() & WB_SYSTEMCHILDWINDOW) )
+ {
+ nY += pWinParent->GetOutDev()->mnOutOffY;
+ }
+ }
+
+ if( nSysFlags & (SAL_FRAME_POSSIZE_WIDTH|SAL_FRAME_POSSIZE_HEIGHT) )
+ {
+ // check for min/max client size and adjust size accordingly
+ // otherwise it may happen that the resize event is ignored, i.e. the old size remains
+ // unchanged but ImplHandleResize() is called with the wrong size
+ SystemWindow *pSystemWindow = dynamic_cast< SystemWindow* >( pWindow.get() );
+ if( pSystemWindow )
+ {
+ Size aMinSize = pSystemWindow->GetMinOutputSizePixel();
+ Size aMaxSize = pSystemWindow->GetMaxOutputSizePixel();
+ if( nWidth < aMinSize.Width() )
+ nWidth = aMinSize.Width();
+ if( nHeight < aMinSize.Height() )
+ nHeight = aMinSize.Height();
+
+ if( nWidth > aMaxSize.Width() )
+ nWidth = aMaxSize.Width();
+ if( nHeight > aMaxSize.Height() )
+ nHeight = aMaxSize.Height();
+ }
+ }
+
+ pWindow->mpWindowImpl->mpFrame->SetPosSize( nX, nY, nWidth, nHeight, nSysFlags );
+
+ // Adjust resize with the hack of different client size and frame geometries to fix
+ // native menu bars. Eventually this should be replaced by proper mnTopBorder usage.
+ pWindow->mpWindowImpl->mpFrame->GetClientSize(nWidth, nHeight);
+
+ // Resize should be called directly. If we haven't
+ // set the correct size, we get a second resize from
+ // the system with the correct size. This can be happened
+ // if the size is too small or too large.
+ ImplHandleResize( pWindow, nWidth, nHeight );
+ }
+ else
+ {
+ pWindow->ImplPosSizeWindow( nX, nY, nWidth, nHeight, nFlags );
+ if ( IsReallyVisible() )
+ ImplGenerateMouseMove();
+ }
+}
+
+Point Window::GetPosPixel() const
+{
+ return mpWindowImpl->maPos;
+}
+
+tools::Rectangle Window::GetDesktopRectPixel() const
+{
+ tools::Rectangle rRect;
+ mpWindowImpl->mpFrameWindow->mpWindowImpl->mpFrame->GetWorkArea( rRect );
+ return rRect;
+}
+
+Point Window::OutputToScreenPixel( const Point& rPos ) const
+{
+ // relative to top level parent
+ return Point( rPos.X() + GetOutDev()->mnOutOffX, rPos.Y() + GetOutDev()->mnOutOffY );
+}
+
+Point Window::ScreenToOutputPixel( const Point& rPos ) const
+{
+ // relative to top level parent
+ return Point( rPos.X() - GetOutDev()->mnOutOffX, rPos.Y() - GetOutDev()->mnOutOffY );
+}
+
+tools::Long Window::ImplGetUnmirroredOutOffX()
+{
+ // revert mnOutOffX changes that were potentially made in ImplPosSizeWindow
+ tools::Long offx = GetOutDev()->mnOutOffX;
+ OutputDevice *pOutDev = GetOutDev();
+ if( pOutDev->HasMirroredGraphics() )
+ {
+ if( mpWindowImpl->mpParent && !mpWindowImpl->mpParent->mpWindowImpl->mbFrame && mpWindowImpl->mpParent->GetOutDev()->ImplIsAntiparallel() )
+ {
+ if ( !ImplIsOverlapWindow() )
+ offx -= mpWindowImpl->mpParent->GetOutDev()->mnOutOffX;
+
+ offx = mpWindowImpl->mpParent->GetOutDev()->mnOutWidth - GetOutDev()->mnOutWidth - offx;
+
+ if ( !ImplIsOverlapWindow() )
+ offx += mpWindowImpl->mpParent->GetOutDev()->mnOutOffX;
+
+ }
+ }
+ return offx;
+}
+
+// normalized screen pixel are independent of mirroring
+Point Window::OutputToNormalizedScreenPixel( const Point& rPos ) const
+{
+ // relative to top level parent
+ tools::Long offx = const_cast<vcl::Window*>(this)->ImplGetUnmirroredOutOffX();
+ return Point( rPos.X()+offx, rPos.Y() + GetOutDev()->mnOutOffY );
+}
+
+Point Window::NormalizedScreenToOutputPixel( const Point& rPos ) const
+{
+ // relative to top level parent
+ tools::Long offx = const_cast<vcl::Window*>(this)->ImplGetUnmirroredOutOffX();
+ return Point( rPos.X()-offx, rPos.Y() - GetOutDev()->mnOutOffY );
+}
+
+Point Window::OutputToAbsoluteScreenPixel( const Point& rPos ) const
+{
+ // relative to the screen
+ Point p = OutputToScreenPixel( rPos );
+ SalFrameGeometry g = mpWindowImpl->mpFrame->GetGeometry();
+ p.AdjustX(g.nX );
+ p.AdjustY(g.nY );
+ return p;
+}
+
+Point Window::AbsoluteScreenToOutputPixel( const Point& rPos ) const
+{
+ // relative to the screen
+ Point p = ScreenToOutputPixel( rPos );
+ SalFrameGeometry g = mpWindowImpl->mpFrame->GetGeometry();
+ p.AdjustX( -(g.nX) );
+ p.AdjustY( -(g.nY) );
+ return p;
+}
+
+tools::Rectangle Window::ImplOutputToUnmirroredAbsoluteScreenPixel( const tools::Rectangle &rRect ) const
+{
+ // this method creates unmirrored screen coordinates to be compared with the desktop
+ // and is used for positioning of RTL popup windows correctly on the screen
+ SalFrameGeometry g = mpWindowImpl->mpFrame->GetUnmirroredGeometry();
+
+ Point p1 = rRect.TopRight();
+ p1 = OutputToScreenPixel(p1);
+ p1.setX( g.nX+g.nWidth-p1.X() );
+ p1.AdjustY(g.nY );
+
+ Point p2 = rRect.BottomLeft();
+ p2 = OutputToScreenPixel(p2);
+ p2.setX( g.nX+g.nWidth-p2.X() );
+ p2.AdjustY(g.nY );
+
+ return tools::Rectangle( p1, p2 );
+}
+
+tools::Rectangle Window::ImplUnmirroredAbsoluteScreenToOutputPixel( const tools::Rectangle &rRect ) const
+{
+ // undo ImplOutputToUnmirroredAbsoluteScreenPixel
+ SalFrameGeometry g = mpWindowImpl->mpFrame->GetUnmirroredGeometry();
+
+ Point p1 = rRect.TopRight();
+ p1.AdjustY(-g.nY );
+ p1.setX( g.nX+g.nWidth-p1.X() );
+ p1 = ScreenToOutputPixel(p1);
+
+ Point p2 = rRect.BottomLeft();
+ p2.AdjustY(-g.nY);
+ p2.setX( g.nX+g.nWidth-p2.X() );
+ p2 = ScreenToOutputPixel(p2);
+
+ return tools::Rectangle( p1, p2 );
+}
+
+
+tools::Rectangle Window::GetWindowExtentsRelative(const vcl::Window *pRelativeWindow) const
+{
+ // with decoration
+ return ImplGetWindowExtentsRelative( pRelativeWindow );
+}
+
+tools::Rectangle Window::ImplGetWindowExtentsRelative(const vcl::Window *pRelativeWindow) const
+{
+ SalFrameGeometry g = mpWindowImpl->mpFrame->GetGeometry();
+ // make sure we use the extent of our border window,
+ // otherwise we miss a few pixels
+ const vcl::Window *pWin = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow : this;
+
+ Point aPos( pWin->OutputToScreenPixel( Point(0,0) ) );
+ aPos.AdjustX(g.nX );
+ aPos.AdjustY(g.nY );
+ Size aSize ( pWin->GetSizePixel() );
+ // #104088# do not add decoration to the workwindow to be compatible to java accessibility api
+ if( mpWindowImpl->mbFrame || (mpWindowImpl->mpBorderWindow && mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame && GetType() != WindowType::WORKWINDOW) )
+ {
+ aPos.AdjustX( -sal_Int32(g.nLeftDecoration) );
+ aPos.AdjustY( -sal_Int32(g.nTopDecoration) );
+ aSize.AdjustWidth(g.nLeftDecoration + g.nRightDecoration );
+ aSize.AdjustHeight(g.nTopDecoration + g.nBottomDecoration );
+ }
+ if( pRelativeWindow )
+ {
+ // #106399# express coordinates relative to borderwindow
+ const vcl::Window *pRelWin = pRelativeWindow->mpWindowImpl->mpBorderWindow ? pRelativeWindow->mpWindowImpl->mpBorderWindow.get() : pRelativeWindow;
+ aPos = pRelWin->AbsoluteScreenToOutputPixel( aPos );
+ }
+ return tools::Rectangle( aPos, aSize );
+}
+
+void Window::Scroll( tools::Long nHorzScroll, tools::Long nVertScroll, ScrollFlags nFlags )
+{
+
+ ImplScroll( GetOutputRectPixel(),
+ nHorzScroll, nVertScroll, nFlags & ~ScrollFlags::Clip );
+}
+
+void Window::Scroll( tools::Long nHorzScroll, tools::Long nVertScroll,
+ const tools::Rectangle& rRect, ScrollFlags nFlags )
+{
+ OutputDevice *pOutDev = GetOutDev();
+ tools::Rectangle aRect = pOutDev->ImplLogicToDevicePixel( rRect );
+ aRect.Intersection( GetOutputRectPixel() );
+ if ( !aRect.IsEmpty() )
+ ImplScroll( aRect, nHorzScroll, nVertScroll, nFlags );
+}
+
+void WindowOutputDevice::Flush()
+{
+ if (mxOwnerWindow->mpWindowImpl)
+ mxOwnerWindow->mpWindowImpl->mpFrame->Flush( GetOutputRectPixel() );
+}
+
+void Window::SetUpdateMode( bool bUpdate )
+{
+ if (mpWindowImpl)
+ {
+ mpWindowImpl->mbNoUpdate = !bUpdate;
+ CompatStateChanged( StateChangedType::UpdateMode );
+ }
+}
+
+void Window::GrabFocus()
+{
+ ImplGrabFocus( GetFocusFlags::NONE );
+}
+
+bool Window::HasFocus() const
+{
+ return (this == ImplGetSVData()->mpWinData->mpFocusWin);
+}
+
+void Window::GrabFocusToDocument()
+{
+ ImplGrabFocusToDocument(GetFocusFlags::NONE);
+}
+
+VclPtr<vcl::Window> Window::GetFocusedWindow() const
+{
+ if (mpWindowImpl && mpWindowImpl->mpFrameData)
+ return mpWindowImpl->mpFrameData->mpFocusWin;
+ else
+ return VclPtr<vcl::Window>();
+}
+
+void Window::SetFakeFocus( bool bFocus )
+{
+ ImplGetWindowImpl()->mbFakeFocusSet = bFocus;
+}
+
+bool Window::HasChildPathFocus( bool bSystemWindow ) const
+{
+
+ vcl::Window* pFocusWin = ImplGetSVData()->mpWinData->mpFocusWin;
+ if ( pFocusWin )
+ return ImplIsWindowOrChild( pFocusWin, bSystemWindow );
+ return false;
+}
+
+void Window::SetCursor( vcl::Cursor* pCursor )
+{
+
+ if ( mpWindowImpl->mpCursor != pCursor )
+ {
+ if ( mpWindowImpl->mpCursor )
+ mpWindowImpl->mpCursor->ImplHide();
+ mpWindowImpl->mpCursor = pCursor;
+ if ( pCursor )
+ pCursor->ImplShow();
+ }
+}
+
+void Window::SetText( const OUString& rStr )
+{
+ if (!mpWindowImpl || rStr == mpWindowImpl->maText)
+ return;
+
+ OUString oldTitle( mpWindowImpl->maText );
+ mpWindowImpl->maText = rStr;
+
+ if ( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->SetText( rStr );
+ else if ( mpWindowImpl->mbFrame )
+ mpWindowImpl->mpFrame->SetTitle( rStr );
+
+ CallEventListeners( VclEventId::WindowFrameTitleChanged, &oldTitle );
+
+ // #107247# needed for accessibility
+ // The VclEventId::WindowFrameTitleChanged is (mis)used to notify accessible name changes.
+ // Therefore a window, which is labeled by this window, must also notify an accessible
+ // name change.
+ if ( IsReallyVisible() )
+ {
+ vcl::Window* pWindow = GetAccessibleRelationLabelFor();
+ if ( pWindow && pWindow != this )
+ pWindow->CallEventListeners( VclEventId::WindowFrameTitleChanged, &oldTitle );
+ }
+
+ CompatStateChanged( StateChangedType::Text );
+}
+
+OUString Window::GetText() const
+{
+
+ return mpWindowImpl->maText;
+}
+
+OUString Window::GetDisplayText() const
+{
+
+ return GetText();
+}
+
+const Wallpaper& Window::GetDisplayBackground() const
+{
+ // FIXME: fix issue 52349, need to fix this really in
+ // all NWF enabled controls
+ const ToolBox* pTB = dynamic_cast<const ToolBox*>(this);
+ if( pTB && IsNativeWidgetEnabled() )
+ return pTB->ImplGetToolBoxPrivateData()->maDisplayBackground;
+
+ if( !IsBackground() )
+ {
+ if( mpWindowImpl->mpParent )
+ return mpWindowImpl->mpParent->GetDisplayBackground();
+ }
+
+ const Wallpaper& rBack = GetBackground();
+ if( ! rBack.IsBitmap() &&
+ ! rBack.IsGradient() &&
+ rBack.GetColor()== COL_TRANSPARENT &&
+ mpWindowImpl->mpParent )
+ return mpWindowImpl->mpParent->GetDisplayBackground();
+ return rBack;
+}
+
+const OUString& Window::GetHelpText() const
+{
+ OUString aStrHelpId( OStringToOUString( GetHelpId(), RTL_TEXTENCODING_UTF8 ) );
+ bool bStrHelpId = !aStrHelpId.isEmpty();
+
+ if ( !mpWindowImpl->maHelpText.getLength() && bStrHelpId )
+ {
+ if ( !IsDialog() && (mpWindowImpl->mnType != WindowType::TABPAGE) && (mpWindowImpl->mnType != WindowType::FLOATINGWINDOW) )
+ {
+ Help* pHelp = Application::GetHelp();
+ if ( pHelp )
+ {
+ mpWindowImpl->maHelpText = pHelp->GetHelpText(aStrHelpId, this);
+ mpWindowImpl->mbHelpTextDynamic = false;
+ }
+ }
+ }
+ else if( mpWindowImpl->mbHelpTextDynamic && bStrHelpId )
+ {
+ static const char* pEnv = getenv( "HELP_DEBUG" );
+ if( pEnv && *pEnv )
+ {
+ OUString aTxt = mpWindowImpl->maHelpText + "\n------------------\n" + aStrHelpId;
+ mpWindowImpl->maHelpText = aTxt;
+ }
+ mpWindowImpl->mbHelpTextDynamic = false;
+ }
+
+ //Fallback to Window::GetAccessibleDescription without reentry to GetHelpText()
+ if (mpWindowImpl->maHelpText.isEmpty() && mpWindowImpl->mpAccessibleInfos && mpWindowImpl->mpAccessibleInfos->pAccessibleDescription)
+ return *mpWindowImpl->mpAccessibleInfos->pAccessibleDescription;
+ return mpWindowImpl->maHelpText;
+}
+
+void Window::SetWindowPeer( Reference< css::awt::XWindowPeer > const & xPeer, VCLXWindow* pVCLXWindow )
+{
+ if (!mpWindowImpl || mpWindowImpl->mbInDispose)
+ return;
+
+ // be safe against re-entrance: first clear the old ref, then assign the new one
+ if (mpWindowImpl->mxWindowPeer)
+ {
+ // first, disconnect the peer from ourself, otherwise disposing it, will dispose us
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper();
+ SAL_WARN_IF( !pWrapper, "vcl.window", "SetComponentInterface: No Wrapper!" );
+ if ( pWrapper )
+ pWrapper->SetWindowInterface( nullptr, mpWindowImpl->mxWindowPeer );
+ mpWindowImpl->mxWindowPeer->dispose();
+ mpWindowImpl->mxWindowPeer.clear();
+ }
+ mpWindowImpl->mxWindowPeer = xPeer;
+
+ mpWindowImpl->mpVCLXWindow = pVCLXWindow;
+}
+
+Reference< css::awt::XWindowPeer > Window::GetComponentInterface( bool bCreate )
+{
+ if ( !mpWindowImpl->mxWindowPeer.is() && bCreate )
+ {
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper();
+ if ( pWrapper )
+ mpWindowImpl->mxWindowPeer = pWrapper->GetWindowInterface( this );
+ }
+ return mpWindowImpl->mxWindowPeer;
+}
+
+void Window::SetComponentInterface( Reference< css::awt::XWindowPeer > const & xIFace )
+{
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper();
+ SAL_WARN_IF( !pWrapper, "vcl.window", "SetComponentInterface: No Wrapper!" );
+ if ( pWrapper )
+ pWrapper->SetWindowInterface( this, xIFace );
+}
+
+typedef std::map<vcl::LOKWindowId, VclPtr<vcl::Window>> LOKWindowsMap;
+
+namespace {
+
+LOKWindowsMap& GetLOKWindowsMap()
+{
+ // Map to remember the LOKWindowId <-> Window binding.
+ static LOKWindowsMap s_aLOKWindowsMap;
+
+ return s_aLOKWindowsMap;
+}
+
+}
+
+void Window::SetLOKNotifier(const vcl::ILibreOfficeKitNotifier* pNotifier, bool bParent)
+{
+ // don't allow setting this twice
+ assert(mpWindowImpl->mpLOKNotifier == nullptr);
+ assert(pNotifier);
+ // never use this in the desktop case
+ assert(comphelper::LibreOfficeKit::isActive());
+
+ if (!bParent)
+ {
+ // Counter to be able to have unique id's for each window.
+ static vcl::LOKWindowId sLastLOKWindowId = 1;
+
+ // assign the LOK window id
+ assert(mpWindowImpl->mnLOKWindowId == 0);
+ mpWindowImpl->mnLOKWindowId = sLastLOKWindowId++;
+ GetLOKWindowsMap().emplace(mpWindowImpl->mnLOKWindowId, this);
+ }
+ else
+ mpWindowImpl->mbLOKParentNotifier = true;
+
+ mpWindowImpl->mpLOKNotifier = pNotifier;
+}
+
+VclPtr<Window> Window::FindLOKWindow(vcl::LOKWindowId nWindowId)
+{
+ const auto it = GetLOKWindowsMap().find(nWindowId);
+ if (it != GetLOKWindowsMap().end())
+ return it->second;
+
+ return VclPtr<Window>();
+}
+
+bool Window::IsLOKWindowsEmpty()
+{
+ return GetLOKWindowsMap().empty();
+}
+
+void Window::ReleaseLOKNotifier()
+{
+ // unregister the LOK window binding
+ if (mpWindowImpl->mnLOKWindowId > 0)
+ GetLOKWindowsMap().erase(mpWindowImpl->mnLOKWindowId);
+
+ mpWindowImpl->mpLOKNotifier = nullptr;
+ mpWindowImpl->mnLOKWindowId = 0;
+}
+
+ILibreOfficeKitNotifier::~ILibreOfficeKitNotifier()
+{
+ if (!comphelper::LibreOfficeKit::isActive())
+ {
+ return;
+ }
+
+ for (auto it = GetLOKWindowsMap().begin(); it != GetLOKWindowsMap().end();)
+ {
+ WindowImpl* pWindowImpl = it->second->ImplGetWindowImpl();
+ if (pWindowImpl && pWindowImpl->mpLOKNotifier == this)
+ {
+ pWindowImpl->mpLOKNotifier = nullptr;
+ pWindowImpl->mnLOKWindowId = 0;
+ it = GetLOKWindowsMap().erase(it);
+ continue;
+ }
+
+ ++it;
+ }
+}
+
+const vcl::ILibreOfficeKitNotifier* Window::GetLOKNotifier() const
+{
+ return mpWindowImpl ? mpWindowImpl->mpLOKNotifier : nullptr;
+}
+
+vcl::LOKWindowId Window::GetLOKWindowId() const
+{
+ return mpWindowImpl ? mpWindowImpl->mnLOKWindowId : 0;
+}
+
+VclPtr<vcl::Window> Window::GetParentWithLOKNotifier()
+{
+ VclPtr<vcl::Window> pWindow(this);
+
+ while (pWindow && !pWindow->GetLOKNotifier())
+ pWindow = pWindow->GetParent();
+
+ return pWindow;
+}
+
+namespace
+{
+
+const char* windowTypeName(WindowType nWindowType)
+{
+ switch (nWindowType)
+ {
+ case WindowType::NONE: return "none";
+ case WindowType::MESSBOX: return "messagebox";
+ case WindowType::INFOBOX: return "infobox";
+ case WindowType::WARNINGBOX: return "warningbox";
+ case WindowType::ERRORBOX: return "errorbox";
+ case WindowType::QUERYBOX: return "querybox";
+ case WindowType::WINDOW: return "window";
+ case WindowType::WORKWINDOW: return "workwindow";
+ case WindowType::CONTAINER: return "container";
+ case WindowType::FLOATINGWINDOW: return "floatingwindow";
+ case WindowType::DIALOG: return "dialog";
+ case WindowType::MODELESSDIALOG: return "modelessdialog";
+ case WindowType::CONTROL: return "control";
+ case WindowType::PUSHBUTTON: return "pushbutton";
+ case WindowType::OKBUTTON: return "okbutton";
+ case WindowType::CANCELBUTTON: return "cancelbutton";
+ case WindowType::HELPBUTTON: return "helpbutton";
+ case WindowType::IMAGEBUTTON: return "imagebutton";
+ case WindowType::MENUBUTTON: return "menubutton";
+ case WindowType::MOREBUTTON: return "morebutton";
+ case WindowType::SPINBUTTON: return "spinbutton";
+ case WindowType::RADIOBUTTON: return "radiobutton";
+ case WindowType::CHECKBOX: return "checkbox";
+ case WindowType::TRISTATEBOX: return "tristatebox";
+ case WindowType::EDIT: return "edit";
+ case WindowType::MULTILINEEDIT: return "multilineedit";
+ case WindowType::COMBOBOX: return "combobox";
+ case WindowType::LISTBOX: return "listbox";
+ case WindowType::MULTILISTBOX: return "multilistbox";
+ case WindowType::FIXEDTEXT: return "fixedtext";
+ case WindowType::FIXEDLINE: return "fixedline";
+ case WindowType::FIXEDBITMAP: return "fixedbitmap";
+ case WindowType::FIXEDIMAGE: return "fixedimage";
+ case WindowType::GROUPBOX: return "groupbox";
+ case WindowType::SCROLLBAR: return "scrollbar";
+ case WindowType::SCROLLBARBOX: return "scrollbarbox";
+ case WindowType::SPLITTER: return "splitter";
+ case WindowType::SPLITWINDOW: return "splitwindow";
+ case WindowType::SPINFIELD: return "spinfield";
+ case WindowType::PATTERNFIELD: return "patternfield";
+ case WindowType::METRICFIELD: return "metricfield";
+ case WindowType::FORMATTEDFIELD: return "formattedfield";
+ case WindowType::CURRENCYFIELD: return "currencyfield";
+ case WindowType::DATEFIELD: return "datefield";
+ case WindowType::TIMEFIELD: return "timefield";
+ case WindowType::PATTERNBOX: return "patternbox";
+ case WindowType::NUMERICBOX: return "numericbox";
+ case WindowType::METRICBOX: return "metricbox";
+ case WindowType::CURRENCYBOX: return "currencybox";
+ case WindowType::DATEBOX: return "datebox";
+ case WindowType::TIMEBOX: return "timebox";
+ case WindowType::LONGCURRENCYBOX: return "longcurrencybox";
+ case WindowType::SCROLLWINDOW: return "scrollwindow";
+ case WindowType::TOOLBOX: return "toolbox";
+ case WindowType::DOCKINGWINDOW: return "dockingwindow";
+ case WindowType::STATUSBAR: return "statusbar";
+ case WindowType::TABPAGE: return "tabpage";
+ case WindowType::TABCONTROL: return "tabcontrol";
+ case WindowType::TABDIALOG: return "tabdialog";
+ case WindowType::BORDERWINDOW: return "borderwindow";
+ case WindowType::BUTTONDIALOG: return "buttondialog";
+ case WindowType::SYSTEMCHILDWINDOW: return "systemchildwindow";
+ case WindowType::SLIDER: return "slider";
+ case WindowType::MENUBARWINDOW: return "menubarwindow";
+ case WindowType::TREELISTBOX: return "treelistbox";
+ case WindowType::HELPTEXTWINDOW: return "helptextwindow";
+ case WindowType::INTROWINDOW: return "introwindow";
+ case WindowType::LISTBOXWINDOW: return "listboxwindow";
+ case WindowType::DOCKINGAREA: return "dockingarea";
+ case WindowType::RULER: return "ruler";
+ case WindowType::HEADERBAR: return "headerbar";
+ case WindowType::VERTICALTABCONTROL: return "verticaltabcontrol";
+
+ // nothing to do here, but for completeness
+ case WindowType::TOOLKIT_FRAMEWINDOW: return "toolkit_framewindow";
+ case WindowType::TOOLKIT_SYSTEMCHILDWINDOW: return "toolkit_systemchildwindow";
+ }
+
+ return "none";
+}
+
+}
+
+void Window::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ if (!mpWindowImpl)
+ return;
+
+ rJsonWriter.put("id", get_id()); // TODO could be missing - sort out
+ rJsonWriter.put("type", windowTypeName(GetType()));
+ rJsonWriter.put("text", GetText());
+ rJsonWriter.put("enabled", IsEnabled());
+ if (!IsVisible())
+ rJsonWriter.put("visible", false);
+
+ if (vcl::Window* pChild = mpWindowImpl->mpFirstChild)
+ {
+ auto childrenNode = rJsonWriter.startArray("children");
+ while (pChild)
+ {
+ {
+ auto childNode = rJsonWriter.startStruct();
+ pChild->DumpAsPropertyTree(rJsonWriter);
+ sal_Int32 nLeft = pChild->get_grid_left_attach();
+ sal_Int32 nTop = pChild->get_grid_top_attach();
+ if (nLeft != -1 && nTop != -1)
+ {
+ rJsonWriter.put("left", nLeft);
+ rJsonWriter.put("top", nTop);
+ }
+
+ sal_Int32 nWidth = pChild->get_grid_width();
+ if (nWidth > 1)
+ rJsonWriter.put("width", nWidth);
+ }
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+ }
+
+ mpWindowImpl->maDumpAsPropertyTreeHdl.Call(rJsonWriter);
+}
+
+void Window::ImplCallDeactivateListeners( vcl::Window *pNew )
+{
+ // no deactivation if the newly activated window is my child
+ if ( !pNew || !ImplIsChild( pNew ) )
+ {
+ VclPtr<vcl::Window> xWindow(this);
+ CallEventListeners( VclEventId::WindowDeactivate, pNew );
+ if( !xWindow->mpWindowImpl )
+ return;
+
+ // #100759#, avoid walking the wrong frame's hierarchy
+ // eg, undocked docking windows (ImplDockFloatWin)
+ if ( ImplGetParent() && ImplGetParent()->mpWindowImpl &&
+ mpWindowImpl->mpFrameWindow == ImplGetParent()->mpWindowImpl->mpFrameWindow )
+ ImplGetParent()->ImplCallDeactivateListeners( pNew );
+ }
+}
+
+void Window::ImplCallActivateListeners( vcl::Window *pOld )
+{
+ // no activation if the old active window is my child
+ if ( pOld && ImplIsChild( pOld ))
+ return;
+
+ VclPtr<vcl::Window> xWindow(this);
+ CallEventListeners( VclEventId::WindowActivate, pOld );
+ if( !xWindow->mpWindowImpl )
+ return;
+
+ if ( ImplGetParent() )
+ ImplGetParent()->ImplCallActivateListeners( pOld );
+ else if( (mpWindowImpl->mnStyle & WB_INTROWIN) == 0 )
+ {
+ // top level frame reached: store hint for DefModalDialogParent
+ ImplGetSVData()->maFrameData.mpActiveApplicationFrame = mpWindowImpl->mpFrameWindow;
+ }
+}
+
+void Window::SetClipboard(Reference<XClipboard> const & xClipboard)
+{
+ if (mpWindowImpl->mpFrameData)
+ mpWindowImpl->mpFrameData->mxClipboard = xClipboard;
+}
+
+Reference< XClipboard > Window::GetClipboard()
+{
+ if (!mpWindowImpl->mpFrameData)
+ return static_cast<XClipboard*>(nullptr);
+ if (!mpWindowImpl->mpFrameData->mxClipboard.is())
+ mpWindowImpl->mpFrameData->mxClipboard = GetSystemClipboard();
+ return mpWindowImpl->mpFrameData->mxClipboard;
+}
+
+void Window::RecordLayoutData( vcl::ControlLayoutData* pLayout, const tools::Rectangle& rRect )
+{
+ assert(GetOutDev()->mpOutDevData);
+ GetOutDev()->mpOutDevData->mpRecordLayout = pLayout;
+ GetOutDev()->mpOutDevData->maRecordRect = rRect;
+ Paint(*GetOutDev(), rRect);
+ GetOutDev()->mpOutDevData->mpRecordLayout = nullptr;
+}
+
+void Window::DrawSelectionBackground( const tools::Rectangle& rRect,
+ sal_uInt16 highlight,
+ bool bChecked,
+ bool bDrawBorder
+ )
+{
+ if( rRect.IsEmpty() )
+ return;
+
+ const StyleSettings& rStyles = GetSettings().GetStyleSettings();
+
+ // colors used for item highlighting
+ Color aSelectionBorderCol( rStyles.GetHighlightColor() );
+ Color aSelectionFillCol( aSelectionBorderCol );
+
+ bool bDark = rStyles.GetFaceColor().IsDark();
+ bool bBright = ( rStyles.GetFaceColor() == COL_WHITE );
+
+ int c1 = aSelectionBorderCol.GetLuminance();
+ int c2 = GetBackgroundColor().GetLuminance();
+
+ if( !bDark && !bBright && abs( c2-c1 ) < 75 )
+ {
+ // contrast too low
+ sal_uInt16 h,s,b;
+ aSelectionFillCol.RGBtoHSB( h, s, b );
+ if( b > 50 ) b -= 40;
+ else b += 40;
+ aSelectionFillCol = Color::HSBtoRGB( h, s, b );
+ aSelectionBorderCol = aSelectionFillCol;
+ }
+
+ tools::Rectangle aRect( rRect );
+ Color oldFillCol = GetOutDev()->GetFillColor();
+ Color oldLineCol = GetOutDev()->GetLineColor();
+
+ if( bDrawBorder )
+ GetOutDev()->SetLineColor( bDark ? COL_WHITE : ( bBright ? COL_BLACK : aSelectionBorderCol ) );
+ else
+ GetOutDev()->SetLineColor();
+
+ sal_uInt16 nPercent = 0;
+ if( !highlight )
+ {
+ if( bDark )
+ aSelectionFillCol = COL_BLACK;
+ else
+ nPercent = 80; // just checked (light)
+ }
+ else
+ {
+ if( bChecked && highlight == 2 )
+ {
+ if( bDark )
+ aSelectionFillCol = COL_LIGHTGRAY;
+ else if ( bBright )
+ {
+ aSelectionFillCol = COL_BLACK;
+ GetOutDev()->SetLineColor( COL_BLACK );
+ nPercent = 0;
+ }
+ else
+ nPercent = 20; // selected, pressed or checked ( very dark )
+ }
+ else if( bChecked || highlight == 1 )
+ {
+ if( bDark )
+ aSelectionFillCol = COL_GRAY;
+ else if ( bBright )
+ {
+ aSelectionFillCol = COL_BLACK;
+ GetOutDev()->SetLineColor( COL_BLACK );
+ nPercent = 0;
+ }
+ else
+ nPercent = 35; // selected, pressed or checked ( very dark )
+ }
+ else
+ {
+ if( bDark )
+ aSelectionFillCol = COL_LIGHTGRAY;
+ else if ( bBright )
+ {
+ aSelectionFillCol = COL_BLACK;
+ GetOutDev()->SetLineColor( COL_BLACK );
+ if( highlight == 3 )
+ nPercent = 80;
+ else
+ nPercent = 0;
+ }
+ else
+ nPercent = 70; // selected ( dark )
+ }
+ }
+
+ GetOutDev()->SetFillColor( aSelectionFillCol );
+
+ if( bDark )
+ {
+ GetOutDev()->DrawRect( aRect );
+ }
+ else
+ {
+ tools::Polygon aPoly( aRect );
+ tools::PolyPolygon aPolyPoly( aPoly );
+ GetOutDev()->DrawTransparent( aPolyPoly, nPercent );
+ }
+
+ GetOutDev()->SetFillColor( oldFillCol );
+ GetOutDev()->SetLineColor( oldLineCol );
+}
+
+bool Window::IsScrollable() const
+{
+ // check for scrollbars
+ VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild;
+ while( pChild )
+ {
+ if( pChild->GetType() == WindowType::SCROLLBAR )
+ return true;
+ else
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+ return false;
+}
+
+void Window::ImplMirrorFramePos( Point &pt ) const
+{
+ pt.setX( mpWindowImpl->mpFrame->maGeometry.nWidth-1-pt.X() );
+}
+
+// frame based modal counter (dialogs are not modal to the whole application anymore)
+bool Window::IsInModalMode() const
+{
+ return (mpWindowImpl->mpFrameWindow->mpWindowImpl->mpFrameData->mnModalMode != 0);
+}
+
+void Window::IncModalCount()
+{
+ vcl::Window* pFrameWindow = mpWindowImpl->mpFrameWindow;
+ vcl::Window* pParent = pFrameWindow;
+ while( pFrameWindow )
+ {
+ pFrameWindow->mpWindowImpl->mpFrameData->mnModalMode++;
+ while( pParent && pParent->mpWindowImpl->mpFrameWindow == pFrameWindow )
+ {
+ pParent = pParent->GetParent();
+ }
+ pFrameWindow = pParent ? pParent->mpWindowImpl->mpFrameWindow.get() : nullptr;
+ }
+}
+void Window::DecModalCount()
+{
+ vcl::Window* pFrameWindow = mpWindowImpl->mpFrameWindow;
+ vcl::Window* pParent = pFrameWindow;
+ while( pFrameWindow )
+ {
+ pFrameWindow->mpWindowImpl->mpFrameData->mnModalMode--;
+ while( pParent && pParent->mpWindowImpl->mpFrameWindow == pFrameWindow )
+ {
+ pParent = pParent->GetParent();
+ }
+ pFrameWindow = pParent ? pParent->mpWindowImpl->mpFrameWindow.get() : nullptr;
+ }
+}
+
+void Window::ImplIsInTaskPaneList( bool mbIsInTaskList )
+{
+ mpWindowImpl->mbIsInTaskPaneList = mbIsInTaskList;
+}
+
+void Window::ImplNotifyIconifiedState( bool bIconified )
+{
+ mpWindowImpl->mpFrameWindow->CallEventListeners( bIconified ? VclEventId::WindowMinimize : VclEventId::WindowNormalize );
+ // #109206# notify client window as well to have toolkit topwindow listeners notified
+ if( mpWindowImpl->mpFrameWindow->mpWindowImpl->mpClientWindow && mpWindowImpl->mpFrameWindow != mpWindowImpl->mpFrameWindow->mpWindowImpl->mpClientWindow )
+ mpWindowImpl->mpFrameWindow->mpWindowImpl->mpClientWindow->CallEventListeners( bIconified ? VclEventId::WindowMinimize : VclEventId::WindowNormalize );
+}
+
+bool Window::HasActiveChildFrame() const
+{
+ bool bRet = false;
+ vcl::Window *pFrameWin = ImplGetSVData()->maFrameData.mpFirstFrame;
+ while( pFrameWin )
+ {
+ if( pFrameWin != mpWindowImpl->mpFrameWindow )
+ {
+ bool bDecorated = false;
+ VclPtr< vcl::Window > pChildFrame = pFrameWin->ImplGetWindow();
+ // #i15285# unfortunately WB_MOVEABLE is the same as WB_TABSTOP which can
+ // be removed for ToolBoxes to influence the keyboard accessibility
+ // thus WB_MOVEABLE is no indicator for decoration anymore
+ // but FloatingWindows carry this information in their TitleType...
+ // TODO: avoid duplicate WinBits !!!
+ if( pChildFrame && pChildFrame->ImplIsFloatingWindow() )
+ bDecorated = static_cast<FloatingWindow*>(pChildFrame.get())->GetTitleType() != FloatWinTitleType::NONE;
+ if( bDecorated || (pFrameWin->mpWindowImpl->mnStyle & (WB_MOVEABLE | WB_SIZEABLE) ) )
+ if( pChildFrame && pChildFrame->IsVisible() && pChildFrame->IsActive() )
+ {
+ if( ImplIsChild( pChildFrame, true ) )
+ {
+ bRet = true;
+ break;
+ }
+ }
+ }
+ pFrameWin = pFrameWin->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+ return bRet;
+}
+
+LanguageType Window::GetInputLanguage() const
+{
+ return mpWindowImpl->mpFrame->GetInputLanguage();
+}
+
+void Window::EnableNativeWidget( bool bEnable )
+{
+ static const char* pNoNWF = getenv( "SAL_NO_NWF" );
+ if( pNoNWF && *pNoNWF )
+ bEnable = false;
+
+ if( bEnable != ImplGetWinData()->mbEnableNativeWidget )
+ {
+ ImplGetWinData()->mbEnableNativeWidget = bEnable;
+
+ // send datachanged event to allow for internal changes required for NWF
+ // like clipmode, transparency, etc.
+ DataChangedEvent aDCEvt( DataChangedEventType::SETTINGS, GetOutDev()->mxSettings.get(), AllSettingsFlags::STYLE );
+ CompatDataChanged( aDCEvt );
+
+ // sometimes the borderwindow is queried, so keep it in sync
+ if( mpWindowImpl->mpBorderWindow )
+ mpWindowImpl->mpBorderWindow->ImplGetWinData()->mbEnableNativeWidget = bEnable;
+ }
+
+ // push down, useful for compound controls
+ VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild;
+ while( pChild )
+ {
+ pChild->EnableNativeWidget( bEnable );
+ pChild = pChild->mpWindowImpl->mpNext;
+ }
+}
+
+bool Window::IsNativeWidgetEnabled() const
+{
+ return mpWindowImpl && ImplGetWinData()->mbEnableNativeWidget;
+}
+
+Reference< css::rendering::XCanvas > WindowOutputDevice::ImplGetCanvas( bool bSpriteCanvas ) const
+{
+ // Feed any with operating system's window handle
+
+ // common: first any is VCL pointer to window (for VCL canvas)
+ Sequence< Any > aArg{
+ Any(reinterpret_cast<sal_Int64>(this)),
+ Any(css::awt::Rectangle( mnOutOffX, mnOutOffY, mnOutWidth, mnOutHeight )),
+ Any(mxOwnerWindow->mpWindowImpl->mbAlwaysOnTop),
+ Any(Reference< css::awt::XWindow >(
+ mxOwnerWindow->GetComponentInterface(),
+ UNO_QUERY )),
+ GetSystemGfxDataAny()
+ };
+
+ Reference< XComponentContext > xContext = comphelper::getProcessComponentContext();
+
+ // Create canvas instance with window handle
+
+ static vcl::DeleteUnoReferenceOnDeinit<XMultiComponentFactory> xStaticCanvasFactory(
+ css::rendering::CanvasFactory::create( xContext ) );
+ Reference<XMultiComponentFactory> xCanvasFactory(xStaticCanvasFactory.get());
+ Reference< css::rendering::XCanvas > xCanvas;
+
+ if(xCanvasFactory.is())
+ {
+#ifdef _WIN32
+ // see #140456# - if we're running on a multiscreen setup,
+ // request special, multi-screen safe sprite canvas
+ // implementation (not DX5 canvas, as it cannot cope with
+ // surfaces spanning multiple displays). Note: canvas
+ // (without sprite) stays the same)
+ const sal_uInt32 nDisplay = static_cast< WinSalFrame* >( mxOwnerWindow->mpWindowImpl->mpFrame )->mnDisplay;
+ if( nDisplay >= Application::GetScreenCount() )
+ {
+ xCanvas.set( xCanvasFactory->createInstanceWithArgumentsAndContext(
+ bSpriteCanvas ?
+ OUString( "com.sun.star.rendering.SpriteCanvas.MultiScreen" ) :
+ OUString( "com.sun.star.rendering.Canvas.MultiScreen" ),
+ aArg,
+ xContext ),
+ UNO_QUERY );
+
+ }
+ else
+#endif
+ {
+ xCanvas.set( xCanvasFactory->createInstanceWithArgumentsAndContext(
+ bSpriteCanvas ?
+ OUString( "com.sun.star.rendering.SpriteCanvas" ) :
+ OUString( "com.sun.star.rendering.Canvas" ),
+ aArg,
+ xContext ),
+ UNO_QUERY );
+
+ }
+ }
+
+ // no factory??? Empty reference, then.
+ return xCanvas;
+}
+
+OUString Window::GetSurroundingText() const
+{
+ return OUString();
+}
+
+Selection Window::GetSurroundingTextSelection() const
+{
+ return Selection( 0, 0 );
+}
+
+namespace
+{
+ using namespace com::sun::star;
+
+ uno::Reference<accessibility::XAccessibleEditableText> lcl_GetxText(vcl::Window *pFocusWin)
+ {
+ uno::Reference<accessibility::XAccessibleEditableText> xText;
+ try
+ {
+ uno::Reference< accessibility::XAccessible > xAccessible( pFocusWin->GetAccessible() );
+ if (xAccessible.is())
+ xText = FindFocusedEditableText(xAccessible->getAccessibleContext());
+ }
+ catch(const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "vcl.gtk3", "Exception in getting input method surrounding text");
+ }
+ return xText;
+ }
+}
+
+// this is a rubbish implementation using a11y, ideally all subclasses implementing
+// GetSurroundingText/GetSurroundingTextSelection should implement this and then this
+// should be removed in favor of a stub that returns false
+bool Window::DeleteSurroundingText(const Selection& rSelection)
+{
+ uno::Reference<accessibility::XAccessibleEditableText> xText = lcl_GetxText(this);
+ if (xText.is())
+ {
+ sal_Int32 nPosition = xText->getCaretPosition();
+ // #i111768# range checking
+ sal_Int32 nDeletePos = rSelection.Min();
+ sal_Int32 nDeleteEnd = rSelection.Max();
+ if (nDeletePos < 0)
+ nDeletePos = 0;
+ if (nDeleteEnd < 0)
+ nDeleteEnd = 0;
+ if (nDeleteEnd > xText->getCharacterCount())
+ nDeleteEnd = xText->getCharacterCount();
+
+ xText->deleteText(nDeletePos, nDeleteEnd);
+ //tdf91641 adjust cursor if deleted chars shift it forward (normal case)
+ if (nDeletePos < nPosition)
+ {
+ if (nDeleteEnd <= nPosition)
+ nPosition = nPosition - (nDeleteEnd - nDeletePos);
+ else
+ nPosition = nDeletePos;
+
+ if (xText->getCharacterCount() >= nPosition)
+ xText->setCaretPosition( nPosition );
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool WindowOutputDevice::UsePolyPolygonForComplexGradient()
+{
+ return meRasterOp != RasterOp::OverPaint;
+}
+
+void Window::ApplySettings(vcl::RenderContext& /*rRenderContext*/)
+{
+}
+
+const SystemEnvData* Window::GetSystemData() const
+{
+
+ return mpWindowImpl->mpFrame ? mpWindowImpl->mpFrame->GetSystemData() : nullptr;
+}
+
+bool Window::SupportsDoubleBuffering() const
+{
+ return mpWindowImpl->mpFrameData->mpBuffer;
+}
+
+void Window::RequestDoubleBuffering(bool bRequest)
+{
+ if (bRequest)
+ {
+ mpWindowImpl->mpFrameData->mpBuffer = VclPtrInstance<VirtualDevice>();
+ // Make sure that the buffer size matches the frame size.
+ mpWindowImpl->mpFrameData->mpBuffer->SetOutputSizePixel(mpWindowImpl->mpFrameWindow->GetOutputSizePixel());
+ }
+ else
+ mpWindowImpl->mpFrameData->mpBuffer.reset();
+}
+
+/*
+ * The rationale here is that we moved destructors to
+ * dispose and this altered a lot of code paths, that
+ * are better left unchanged for now.
+ */
+void Window::CompatGetFocus()
+{
+ if (!mpWindowImpl || mpWindowImpl->mbInDispose)
+ Window::GetFocus();
+ else
+ GetFocus();
+}
+
+void Window::CompatLoseFocus()
+{
+ if (!mpWindowImpl || mpWindowImpl->mbInDispose)
+ Window::LoseFocus();
+ else
+ LoseFocus();
+}
+
+void Window::CompatStateChanged( StateChangedType nStateChange )
+{
+ if (!mpWindowImpl || mpWindowImpl->mbInDispose)
+ Window::StateChanged(nStateChange);
+ else
+ StateChanged(nStateChange);
+}
+
+void Window::CompatDataChanged( const DataChangedEvent& rDCEvt )
+{
+ if (!mpWindowImpl || mpWindowImpl->mbInDispose)
+ Window::DataChanged(rDCEvt);
+ else
+ DataChanged(rDCEvt);
+}
+
+bool Window::CompatPreNotify( NotifyEvent& rNEvt )
+{
+ if (!mpWindowImpl || mpWindowImpl->mbInDispose)
+ return Window::PreNotify( rNEvt );
+ else
+ return PreNotify( rNEvt );
+}
+
+bool Window::CompatNotify( NotifyEvent& rNEvt )
+{
+ if (!mpWindowImpl || mpWindowImpl->mbInDispose)
+ return Window::EventNotify( rNEvt );
+ else
+ return EventNotify( rNEvt );
+}
+
+void Window::set_id(const OUString& rID)
+{
+ mpWindowImpl->maID = rID;
+}
+
+const OUString& Window::get_id() const
+{
+ static OUString empty;
+ return mpWindowImpl ? mpWindowImpl->maID : empty;
+}
+
+FactoryFunction Window::GetUITestFactory() const
+{
+ return WindowUIObject::create;
+}
+
+WindowOutputDevice::WindowOutputDevice(vcl::Window& rOwnerWindow) :
+ ::OutputDevice(OUTDEV_WINDOW),
+ mxOwnerWindow(&rOwnerWindow)
+{
+ assert(mxOwnerWindow);
+}
+
+WindowOutputDevice::~WindowOutputDevice()
+{
+ disposeOnce();
+}
+
+void WindowOutputDevice::dispose()
+{
+ assert((!mxOwnerWindow || mxOwnerWindow->isDisposed()) && "This belongs to the associated window and must be disposed after it");
+ ::OutputDevice::dispose();
+ // need to do this after OutputDevice::dispose so that the call to WindowOutputDevice::ReleaseGraphics
+ // can release the graphics properly
+ mxOwnerWindow.clear();
+}
+
+css::awt::DeviceInfo WindowOutputDevice::GetDeviceInfo() const
+{
+ css::awt::DeviceInfo aInfo = GetCommonDeviceInfo(mxOwnerWindow->GetSizePixel());
+ mxOwnerWindow->GetBorder(aInfo.LeftInset, aInfo.TopInset, aInfo.RightInset, aInfo.BottomInset);
+ return aInfo;
+}
+
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/window2.cxx b/vcl/source/window/window2.cxx
new file mode 100644
index 000000000..ad7677195
--- /dev/null
+++ b/vcl/source/window/window2.cxx
@@ -0,0 +1,2043 @@
+/* -*- 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 <limits.h>
+
+#include <o3tl/float_int_conversion.hxx>
+#include <sal/log.hxx>
+
+#include <tools/helpers.hxx>
+
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/fixed.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/window.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/dockwin.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/builder.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <window.h>
+#include <svdata.hxx>
+#include <salgdi.hxx>
+#include <salframe.hxx>
+#include <scrwnd.hxx>
+
+#include <com/sun/star/accessibility/AccessibleRelation.hpp>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+
+using namespace com::sun::star;
+
+namespace vcl {
+
+void Window::ShowFocus( const tools::Rectangle& rRect )
+{
+ if( mpWindowImpl->mbInShowFocus )
+ return;
+ mpWindowImpl->mbInShowFocus = true;
+
+ ImplWinData* pWinData = ImplGetWinData();
+
+ // native themeing suggest not to use focus rects
+ if( ! ( mpWindowImpl->mbUseNativeFocus &&
+ IsNativeWidgetEnabled() ) )
+ {
+ if ( !mpWindowImpl->mbInPaint )
+ {
+ if ( mpWindowImpl->mbFocusVisible )
+ {
+ if ( *pWinData->mpFocusRect == rRect )
+ {
+ mpWindowImpl->mbInShowFocus = false;
+ return;
+ }
+
+ ImplInvertFocus( *pWinData->mpFocusRect );
+ }
+
+ ImplInvertFocus( rRect );
+ }
+ pWinData->mpFocusRect = rRect;
+ mpWindowImpl->mbFocusVisible = true;
+ }
+ else
+ {
+ if( ! mpWindowImpl->mbNativeFocusVisible )
+ {
+ mpWindowImpl->mbNativeFocusVisible = true;
+ if ( !mpWindowImpl->mbInPaint )
+ Invalidate();
+ }
+ }
+ mpWindowImpl->mbInShowFocus = false;
+}
+
+void Window::HideFocus()
+{
+
+ if( mpWindowImpl->mbInHideFocus )
+ return;
+ mpWindowImpl->mbInHideFocus = true;
+
+ // native themeing can suggest not to use focus rects
+ if( ! ( mpWindowImpl->mbUseNativeFocus &&
+ IsNativeWidgetEnabled() ) )
+ {
+ if ( !mpWindowImpl->mbFocusVisible )
+ {
+ mpWindowImpl->mbInHideFocus = false;
+ return;
+ }
+
+ if ( !mpWindowImpl->mbInPaint )
+ ImplInvertFocus( *ImplGetWinData()->mpFocusRect );
+ mpWindowImpl->mbFocusVisible = false;
+ }
+ else
+ {
+ if( mpWindowImpl->mbNativeFocusVisible )
+ {
+ mpWindowImpl->mbNativeFocusVisible = false;
+ if ( !mpWindowImpl->mbInPaint )
+ Invalidate();
+ }
+ }
+ mpWindowImpl->mbInHideFocus = false;
+}
+
+void Window::ShowTracking( const tools::Rectangle& rRect, ShowTrackFlags nFlags )
+{
+ ImplWinData* pWinData = ImplGetWinData();
+
+ if ( !mpWindowImpl->mbInPaint || !(nFlags & ShowTrackFlags::TrackWindow) )
+ {
+ if ( mpWindowImpl->mbTrackVisible )
+ {
+ if ( (*pWinData->mpTrackRect == rRect) &&
+ (pWinData->mnTrackFlags == nFlags) )
+ return;
+
+ InvertTracking( *pWinData->mpTrackRect, pWinData->mnTrackFlags );
+ }
+
+ InvertTracking( rRect, nFlags );
+ }
+
+ pWinData->mpTrackRect = rRect;
+ pWinData->mnTrackFlags = nFlags;
+ mpWindowImpl->mbTrackVisible = true;
+}
+
+void Window::HideTracking()
+{
+ if ( mpWindowImpl->mbTrackVisible )
+ {
+ ImplWinData* pWinData = ImplGetWinData();
+ if ( !mpWindowImpl->mbInPaint || !(pWinData->mnTrackFlags & ShowTrackFlags::TrackWindow) )
+ InvertTracking( *pWinData->mpTrackRect, pWinData->mnTrackFlags );
+ mpWindowImpl->mbTrackVisible = false;
+ }
+}
+
+void Window::InvertTracking( const tools::Rectangle& rRect, ShowTrackFlags nFlags )
+{
+ OutputDevice *pOutDev = GetOutDev();
+ tools::Rectangle aRect( pOutDev->ImplLogicToDevicePixel( rRect ) );
+
+ if ( aRect.IsEmpty() )
+ return;
+ aRect.Justify();
+
+ SalGraphics* pGraphics;
+
+ if ( nFlags & ShowTrackFlags::TrackWindow )
+ {
+ if ( !GetOutDev()->IsDeviceOutputNecessary() )
+ return;
+
+ // we need a graphics
+ if ( !GetOutDev()->mpGraphics )
+ {
+ if ( !pOutDev->AcquireGraphics() )
+ return;
+ }
+
+ if ( GetOutDev()->mbInitClipRegion )
+ GetOutDev()->InitClipRegion();
+
+ if ( GetOutDev()->mbOutputClipped )
+ return;
+
+ pGraphics = GetOutDev()->mpGraphics;
+ }
+ else
+ {
+ pGraphics = ImplGetFrameGraphics();
+
+ if ( nFlags & ShowTrackFlags::Clip )
+ {
+ vcl::Region aRegion( GetOutputRectPixel() );
+ ImplClipBoundaries( aRegion, false, false );
+ pOutDev->SelectClipRegion( aRegion, pGraphics );
+ }
+ }
+
+ ShowTrackFlags nStyle = nFlags & ShowTrackFlags::StyleMask;
+ if ( nStyle == ShowTrackFlags::Object )
+ pGraphics->Invert( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), SalInvert::TrackFrame, *GetOutDev() );
+ else if ( nStyle == ShowTrackFlags::Split )
+ pGraphics->Invert( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), SalInvert::N50, *GetOutDev() );
+ else
+ {
+ tools::Long nBorder = 1;
+ if ( nStyle == ShowTrackFlags::Big )
+ nBorder = 5;
+ pGraphics->Invert( aRect.Left(), aRect.Top(), aRect.GetWidth(), nBorder, SalInvert::N50, *GetOutDev() );
+ pGraphics->Invert( aRect.Left(), aRect.Bottom()-nBorder+1, aRect.GetWidth(), nBorder, SalInvert::N50, *GetOutDev() );
+ pGraphics->Invert( aRect.Left(), aRect.Top()+nBorder, nBorder, aRect.GetHeight()-(nBorder*2), SalInvert::N50, *GetOutDev() );
+ pGraphics->Invert( aRect.Right()-nBorder+1, aRect.Top()+nBorder, nBorder, aRect.GetHeight()-(nBorder*2), SalInvert::N50, *GetOutDev() );
+ }
+}
+
+IMPL_LINK( Window, ImplTrackTimerHdl, Timer*, pTimer, void )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // if Button-Repeat we have to change the timeout
+ if ( pSVData->mpWinData->mnTrackFlags & StartTrackingFlags::ButtonRepeat )
+ pTimer->SetTimeout( GetSettings().GetMouseSettings().GetButtonRepeat() );
+
+ // create Tracking-Event
+ Point aMousePos( mpWindowImpl->mpFrameData->mnLastMouseX, mpWindowImpl->mpFrameData->mnLastMouseY );
+ if( GetOutDev()->ImplIsAntiparallel() )
+ {
+ // re-mirror frame pos at pChild
+ const OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReMirror( aMousePos );
+ }
+ MouseEvent aMEvt( ImplFrameToOutput( aMousePos ),
+ mpWindowImpl->mpFrameData->mnClickCount, MouseEventModifiers::NONE,
+ mpWindowImpl->mpFrameData->mnMouseCode,
+ mpWindowImpl->mpFrameData->mnMouseCode );
+ TrackingEvent aTEvt( aMEvt, TrackingEventFlags::Repeat );
+ Tracking( aTEvt );
+}
+
+void Window::SetUseFrameData(bool bUseFrameData)
+{
+ if (mpWindowImpl)
+ mpWindowImpl->mbUseFrameData = bUseFrameData;
+}
+
+void Window::StartTracking( StartTrackingFlags nFlags )
+{
+ if (!mpWindowImpl)
+ return;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ VclPtr<vcl::Window> pTrackWin = mpWindowImpl->mbUseFrameData ?
+ mpWindowImpl->mpFrameData->mpTrackWin :
+ pSVData->mpWinData->mpTrackWin;
+
+ if ( pTrackWin.get() != this )
+ {
+ if ( pTrackWin )
+ pTrackWin->EndTracking( TrackingEventFlags::Cancel );
+ }
+
+ if ( !mpWindowImpl->mbUseFrameData &&
+ (nFlags & (StartTrackingFlags::ScrollRepeat | StartTrackingFlags::ButtonRepeat)) )
+ {
+ pSVData->mpWinData->mpTrackTimer = new AutoTimer("vcl::Window pSVData->mpWinData->mpTrackTimer");
+
+ if ( nFlags & StartTrackingFlags::ScrollRepeat )
+ pSVData->mpWinData->mpTrackTimer->SetTimeout( MouseSettings::GetScrollRepeat() );
+ else
+ pSVData->mpWinData->mpTrackTimer->SetTimeout( MouseSettings::GetButtonStartRepeat() );
+ pSVData->mpWinData->mpTrackTimer->SetInvokeHandler( LINK( this, Window, ImplTrackTimerHdl ) );
+ pSVData->mpWinData->mpTrackTimer->Start();
+ }
+
+ if (mpWindowImpl->mbUseFrameData)
+ {
+ mpWindowImpl->mpFrameData->mpTrackWin = this;
+ }
+ else
+ {
+ pSVData->mpWinData->mpTrackWin = this;
+ pSVData->mpWinData->mnTrackFlags = nFlags;
+ CaptureMouse();
+ }
+}
+
+void Window::EndTracking( TrackingEventFlags nFlags )
+{
+ if (!mpWindowImpl)
+ return;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ VclPtr<vcl::Window> pTrackWin = mpWindowImpl->mbUseFrameData ?
+ mpWindowImpl->mpFrameData->mpTrackWin :
+ pSVData->mpWinData->mpTrackWin;
+
+ if ( pTrackWin.get() != this )
+ return;
+
+ if ( !mpWindowImpl->mbUseFrameData && pSVData->mpWinData->mpTrackTimer )
+ {
+ delete pSVData->mpWinData->mpTrackTimer;
+ pSVData->mpWinData->mpTrackTimer = nullptr;
+ }
+
+ mpWindowImpl->mpFrameData->mpTrackWin = pSVData->mpWinData->mpTrackWin = nullptr;
+ pSVData->mpWinData->mnTrackFlags = StartTrackingFlags::NONE;
+ ReleaseMouse();
+
+ // call EndTracking if required
+ if (mpWindowImpl->mpFrameData)
+ {
+ Point aMousePos( mpWindowImpl->mpFrameData->mnLastMouseX, mpWindowImpl->mpFrameData->mnLastMouseY );
+ if( GetOutDev()->ImplIsAntiparallel() )
+ {
+ // re-mirror frame pos at pChild
+ const OutputDevice *pOutDev = GetOutDev();
+ pOutDev->ReMirror( aMousePos );
+ }
+
+ MouseEvent aMEvt( ImplFrameToOutput( aMousePos ),
+ mpWindowImpl->mpFrameData->mnClickCount, MouseEventModifiers::NONE,
+ mpWindowImpl->mpFrameData->mnMouseCode,
+ mpWindowImpl->mpFrameData->mnMouseCode );
+ TrackingEvent aTEvt( aMEvt, nFlags | TrackingEventFlags::End );
+ // CompatTracking effectively
+ if (!mpWindowImpl || mpWindowImpl->mbInDispose)
+ return Window::Tracking( aTEvt );
+ else
+ return Tracking( aTEvt );
+ }
+}
+
+bool Window::IsTracking() const
+{
+ return (mpWindowImpl->mbUseFrameData ?
+ mpWindowImpl->mpFrameData->mpTrackWin == this :
+ ImplGetSVData()->mpWinData->mpTrackWin == this);
+}
+
+void Window::StartAutoScroll( StartAutoScrollFlags nFlags )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( pSVData->mpWinData->mpAutoScrollWin.get() != this )
+ {
+ if ( pSVData->mpWinData->mpAutoScrollWin )
+ pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll();
+ }
+
+ pSVData->mpWinData->mpAutoScrollWin = this;
+ pSVData->mpWinData->mnAutoScrollFlags = nFlags;
+ pSVData->maAppData.mpWheelWindow = VclPtr<ImplWheelWindow>::Create( this );
+}
+
+void Window::EndAutoScroll()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( pSVData->mpWinData->mpAutoScrollWin.get() == this )
+ {
+ pSVData->mpWinData->mpAutoScrollWin = nullptr;
+ pSVData->mpWinData->mnAutoScrollFlags = StartAutoScrollFlags::NONE;
+ pSVData->maAppData.mpWheelWindow->ImplStop();
+ pSVData->maAppData.mpWheelWindow.disposeAndClear();
+ }
+}
+
+VclPtr<vcl::Window> Window::SaveFocus()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( pSVData->mpWinData->mpFocusWin )
+ {
+ return pSVData->mpWinData->mpFocusWin;
+ }
+ else
+ return nullptr;
+}
+
+void Window::EndSaveFocus(const VclPtr<vcl::Window>& xFocusWin)
+{
+ if (xFocusWin && !xFocusWin->isDisposed())
+ {
+ xFocusWin->GrabFocus();
+ }
+}
+
+void Window::SetZoom( const Fraction& rZoom )
+{
+ if ( mpWindowImpl && mpWindowImpl->maZoom != rZoom )
+ {
+ mpWindowImpl->maZoom = rZoom;
+ CompatStateChanged( StateChangedType::Zoom );
+ }
+}
+
+void Window::SetZoomedPointFont(vcl::RenderContext& rRenderContext, const vcl::Font& rFont)
+{
+ const Fraction& rZoom = GetZoom();
+ if (rZoom.GetNumerator() != rZoom.GetDenominator())
+ {
+ vcl::Font aFont(rFont);
+ Size aSize = aFont.GetFontSize();
+ aSize.setWidth( FRound(double(aSize.Width() * rZoom)) );
+ aSize.setHeight( FRound(double(aSize.Height() * rZoom)) );
+ aFont.SetFontSize(aSize);
+ SetPointFont(rRenderContext, aFont);
+ }
+ else
+ {
+ SetPointFont(rRenderContext, rFont);
+ }
+}
+
+tools::Long Window::CalcZoom( tools::Long nCalc ) const
+{
+
+ const Fraction& rZoom = GetZoom();
+ if ( rZoom.GetNumerator() != rZoom.GetDenominator() )
+ {
+ double n = double(nCalc * rZoom);
+ nCalc = FRound( n );
+ }
+ return nCalc;
+}
+
+void Window::SetControlFont()
+{
+ if (mpWindowImpl && mpWindowImpl->mpControlFont)
+ {
+ mpWindowImpl->mpControlFont.reset();
+ CompatStateChanged(StateChangedType::ControlFont);
+ }
+}
+
+void Window::SetControlFont(const vcl::Font& rFont)
+{
+ if (rFont == vcl::Font())
+ {
+ SetControlFont();
+ return;
+ }
+
+ if (mpWindowImpl->mpControlFont)
+ {
+ if (*mpWindowImpl->mpControlFont == rFont)
+ return;
+ *mpWindowImpl->mpControlFont = rFont;
+ }
+ else
+ mpWindowImpl->mpControlFont = rFont;
+
+ CompatStateChanged(StateChangedType::ControlFont);
+}
+
+vcl::Font Window::GetControlFont() const
+{
+ if (mpWindowImpl->mpControlFont)
+ return *mpWindowImpl->mpControlFont;
+ else
+ {
+ vcl::Font aFont;
+ return aFont;
+ }
+}
+
+void Window::ApplyControlFont(vcl::RenderContext& rRenderContext, const vcl::Font& rFont)
+{
+ vcl::Font aFont(rFont);
+ if (IsControlFont())
+ aFont.Merge(GetControlFont());
+ SetZoomedPointFont(rRenderContext, aFont);
+}
+
+void Window::SetControlForeground()
+{
+ if (mpWindowImpl->mbControlForeground)
+ {
+ mpWindowImpl->maControlForeground = COL_TRANSPARENT;
+ mpWindowImpl->mbControlForeground = false;
+ CompatStateChanged(StateChangedType::ControlForeground);
+ }
+}
+
+void Window::SetControlForeground(const Color& rColor)
+{
+ if (rColor.IsTransparent())
+ {
+ if (mpWindowImpl->mbControlForeground)
+ {
+ mpWindowImpl->maControlForeground = COL_TRANSPARENT;
+ mpWindowImpl->mbControlForeground = false;
+ CompatStateChanged(StateChangedType::ControlForeground);
+ }
+ }
+ else
+ {
+ if (mpWindowImpl->maControlForeground != rColor)
+ {
+ mpWindowImpl->maControlForeground = rColor;
+ mpWindowImpl->mbControlForeground = true;
+ CompatStateChanged(StateChangedType::ControlForeground);
+ }
+ }
+}
+
+void Window::ApplyControlForeground(vcl::RenderContext& rRenderContext, const Color& rDefaultColor)
+{
+ Color aTextColor(rDefaultColor);
+ if (IsControlForeground())
+ aTextColor = GetControlForeground();
+ rRenderContext.SetTextColor(aTextColor);
+}
+
+void Window::SetControlBackground()
+{
+ if (mpWindowImpl->mbControlBackground)
+ {
+ mpWindowImpl->maControlBackground = COL_TRANSPARENT;
+ mpWindowImpl->mbControlBackground = false;
+ CompatStateChanged(StateChangedType::ControlBackground);
+ }
+}
+
+void Window::SetControlBackground(const Color& rColor)
+{
+ if (rColor.IsTransparent())
+ {
+ if (mpWindowImpl->mbControlBackground)
+ {
+ mpWindowImpl->maControlBackground = COL_TRANSPARENT;
+ mpWindowImpl->mbControlBackground = false;
+ CompatStateChanged(StateChangedType::ControlBackground);
+ }
+ }
+ else
+ {
+ if (mpWindowImpl->maControlBackground != rColor)
+ {
+ mpWindowImpl->maControlBackground = rColor;
+ mpWindowImpl->mbControlBackground = true;
+ CompatStateChanged(StateChangedType::ControlBackground);
+ }
+ }
+}
+
+void Window::ApplyControlBackground(vcl::RenderContext& rRenderContext, const Color& rDefaultColor)
+{
+ Color aColor(rDefaultColor);
+ if (IsControlBackground())
+ aColor = GetControlBackground();
+ rRenderContext.SetBackground(aColor);
+}
+
+Size Window::CalcWindowSize( const Size& rOutSz ) const
+{
+ Size aSz = rOutSz;
+ aSz.AdjustWidth(mpWindowImpl->mnLeftBorder+mpWindowImpl->mnRightBorder );
+ aSz.AdjustHeight(mpWindowImpl->mnTopBorder+mpWindowImpl->mnBottomBorder );
+ return aSz;
+}
+
+Size Window::CalcOutputSize( const Size& rWinSz ) const
+{
+ Size aSz = rWinSz;
+ aSz.AdjustWidth( -(mpWindowImpl->mnLeftBorder+mpWindowImpl->mnRightBorder) );
+ aSz.AdjustHeight( -(mpWindowImpl->mnTopBorder+mpWindowImpl->mnBottomBorder) );
+ return aSz;
+}
+
+vcl::Font Window::GetDrawPixelFont(OutputDevice const * pDev) const
+{
+ vcl::Font aFont = GetPointFont(*GetOutDev());
+ Size aFontSize = aFont.GetFontSize();
+ MapMode aPtMapMode(MapUnit::MapPoint);
+ aFontSize = pDev->LogicToPixel( aFontSize, aPtMapMode );
+ aFont.SetFontSize( aFontSize );
+ return aFont;
+}
+
+tools::Long Window::GetDrawPixel( OutputDevice const * pDev, tools::Long nPixels ) const
+{
+ tools::Long nP = nPixels;
+ if ( pDev->GetOutDevType() != OUTDEV_WINDOW )
+ {
+ MapMode aMap( MapUnit::Map100thMM );
+ Size aSz( nP, 0 );
+ aSz = PixelToLogic( aSz, aMap );
+ aSz = pDev->LogicToPixel( aSz, aMap );
+ nP = aSz.Width();
+ }
+ return nP;
+}
+
+// returns how much was actually scrolled (so that abs(retval) <= abs(nN))
+static double lcl_HandleScrollHelper( ScrollBar* pScrl, double nN, bool isMultiplyByLineSize )
+{
+ if ( !pScrl || !nN || !pScrl->IsEnabled() || !pScrl->IsInputEnabled() || pScrl->IsInModalMode() )
+ return 0.0;
+
+ tools::Long nNewPos = pScrl->GetThumbPos();
+ double scrolled = nN;
+
+ if ( nN == double(-LONG_MAX) )
+ nNewPos += pScrl->GetPageSize();
+ else if ( nN == double(LONG_MAX) )
+ nNewPos -= pScrl->GetPageSize();
+ else
+ {
+ // allowing both chunked and continuous scrolling
+ if(isMultiplyByLineSize){
+ nN*=pScrl->GetLineSize();
+ }
+
+ // compute how many quantized units to scroll
+ tools::Long magnitude = o3tl::saturating_cast<tools::Long>(abs(nN));
+ tools::Long change = copysign(magnitude, nN);
+
+ nNewPos = nNewPos - change;
+
+ scrolled = double(change);
+ // convert back to chunked/continuous
+ if(isMultiplyByLineSize){
+ scrolled /= pScrl->GetLineSize();
+ }
+ }
+
+ pScrl->DoScroll( nNewPos );
+
+ return scrolled;
+}
+
+bool Window::HandleScrollCommand( const CommandEvent& rCmd,
+ ScrollBar* pHScrl, ScrollBar* pVScrl )
+{
+ bool bRet = false;
+
+ if ( pHScrl || pVScrl )
+ {
+ switch( rCmd.GetCommand() )
+ {
+ case CommandEventId::StartAutoScroll:
+ {
+ StartAutoScrollFlags nFlags = StartAutoScrollFlags::NONE;
+ if ( pHScrl )
+ {
+ if ( (pHScrl->GetVisibleSize() < pHScrl->GetRangeMax()) &&
+ pHScrl->IsEnabled() && pHScrl->IsInputEnabled() && ! pHScrl->IsInModalMode() )
+ nFlags |= StartAutoScrollFlags::Horz;
+ }
+ if ( pVScrl )
+ {
+ if ( (pVScrl->GetVisibleSize() < pVScrl->GetRangeMax()) &&
+ pVScrl->IsEnabled() && pVScrl->IsInputEnabled() && ! pVScrl->IsInModalMode() )
+ nFlags |= StartAutoScrollFlags::Vert;
+ }
+
+ if ( nFlags != StartAutoScrollFlags::NONE )
+ {
+ StartAutoScroll( nFlags );
+ bRet = true;
+ }
+ }
+ break;
+
+ case CommandEventId::Wheel:
+ {
+ const CommandWheelData* pData = rCmd.GetWheelData();
+
+ if ( pData && (CommandWheelMode::SCROLL == pData->GetMode()) )
+ {
+ if (!pData->IsDeltaPixel())
+ {
+ double nScrollLines = pData->GetScrollLines();
+ double nLines;
+ double* partialScroll = pData->IsHorz()
+ ? &mpWindowImpl->mfPartialScrollX
+ : &mpWindowImpl->mfPartialScrollY;
+ if ( nScrollLines == COMMAND_WHEEL_PAGESCROLL )
+ {
+ if ( pData->GetDelta() < 0 )
+ nLines = double(-LONG_MAX);
+ else
+ nLines = double(LONG_MAX);
+ }
+ else
+ nLines = *partialScroll + pData->GetNotchDelta() * nScrollLines;
+ if ( nLines )
+ {
+ ScrollBar* pScrl = pData->IsHorz() ? pHScrl : pVScrl;
+ double scrolled = lcl_HandleScrollHelper( pScrl, nLines, true );
+ *partialScroll = nLines - scrolled;
+ bRet = true;
+ }
+ }
+ else
+ {
+ // Mobile / touch scrolling section
+ const Point & deltaPoint = rCmd.GetMousePosPixel();
+
+ double deltaXInPixels = double(deltaPoint.X());
+ double deltaYInPixels = double(deltaPoint.Y());
+ Size winSize = GetOutputSizePixel();
+
+ if(pHScrl)
+ {
+ double visSizeX = double(pHScrl->GetVisibleSize());
+ double ratioX = deltaXInPixels / double(winSize.getWidth());
+ tools::Long deltaXInLogic = tools::Long(visSizeX * ratioX);
+ // Touch need to work by pixels. Did not apply this to
+ // Android, as android code may require adaptations
+ // to work with this scrolling code
+#ifndef IOS
+ tools::Long lineSizeX = pHScrl->GetLineSize();
+
+ if(lineSizeX)
+ {
+ deltaXInLogic /= lineSizeX;
+ }
+ else
+ {
+ deltaXInLogic = 0;
+ }
+#endif
+ if ( deltaXInLogic)
+ {
+#ifndef IOS
+ bool const isMultiplyByLineSize = true;
+#else
+ bool const isMultiplyByLineSize = false;
+#endif
+ lcl_HandleScrollHelper( pHScrl, deltaXInLogic, isMultiplyByLineSize );
+ bRet = true;
+ }
+ }
+ if(pVScrl)
+ {
+ double visSizeY = double(pVScrl->GetVisibleSize());
+ double ratioY = deltaYInPixels / double(winSize.getHeight());
+ tools::Long deltaYInLogic = tools::Long(visSizeY * ratioY);
+
+ // Touch need to work by pixels. Did not apply this to
+ // Android, as android code may require adaptations
+ // to work with this scrolling code
+#ifndef IOS
+ tools::Long lineSizeY = pVScrl->GetLineSize();
+ if(lineSizeY)
+ {
+ deltaYInLogic /= lineSizeY;
+ }
+ else
+ {
+ deltaYInLogic = 0;
+ }
+#endif
+ if ( deltaYInLogic )
+ {
+#ifndef IOS
+ bool const isMultiplyByLineSize = true;
+#else
+ bool const isMultiplyByLineSize = false;
+#endif
+ lcl_HandleScrollHelper( pVScrl, deltaYInLogic, isMultiplyByLineSize );
+
+ bRet = true;
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case CommandEventId::Gesture:
+ {
+ if (pVScrl)
+ {
+ const CommandGestureData* pData = rCmd.GetGestureData();
+ if (pData->meEventType == GestureEventType::PanningBegin)
+ {
+ mpWindowImpl->mpFrameData->mnTouchPanPosition = pVScrl->GetThumbPos();
+ }
+ else if(pData->meEventType == GestureEventType::PanningUpdate)
+ {
+ tools::Long nOriginalPosition = mpWindowImpl->mpFrameData->mnTouchPanPosition;
+ pVScrl->DoScroll(nOriginalPosition + (pData->mfOffset / pVScrl->GetVisibleSize()));
+ }
+ if (pData->meEventType == GestureEventType::PanningEnd)
+ {
+ mpWindowImpl->mpFrameData->mnTouchPanPosition = -1;
+ }
+ bRet = true;
+ }
+ break;
+ }
+
+ case CommandEventId::AutoScroll:
+ {
+ const CommandScrollData* pData = rCmd.GetAutoScrollData();
+ if ( pData && (pData->GetDeltaX() || pData->GetDeltaY()) )
+ {
+ ImplHandleScroll( pHScrl, pData->GetDeltaX(),
+ pVScrl, pData->GetDeltaY() );
+ bRet = true;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return bRet;
+}
+
+void Window::ImplHandleScroll( ScrollBar* pHScrl, double nX,
+ ScrollBar* pVScrl, double nY )
+{
+ lcl_HandleScrollHelper( pHScrl, nX, true );
+ lcl_HandleScrollHelper( pVScrl, nY, true );
+}
+
+DockingManager* Window::GetDockingManager()
+{
+ return ImplGetDockingManager();
+}
+
+void Window::EnableDocking( bool bEnable )
+{
+ // update list of dockable windows
+ if( bEnable )
+ ImplGetDockingManager()->AddWindow( this );
+ else
+ ImplGetDockingManager()->RemoveWindow( this );
+}
+
+// retrieves the list of owner draw decorated windows for this window hierarchy
+::std::vector<VclPtr<vcl::Window> >& Window::ImplGetOwnerDrawList()
+{
+ return ImplGetTopmostFrameWindow()->mpWindowImpl->mpFrameData->maOwnerDrawList;
+}
+
+void Window::SetHelpId( const OString& rHelpId )
+{
+ mpWindowImpl->maHelpId = rHelpId;
+}
+
+const OString& Window::GetHelpId() const
+{
+ return mpWindowImpl->maHelpId;
+}
+
+// --------- old inline methods ---------------
+
+vcl::Window* Window::ImplGetWindow() const
+{
+ if ( mpWindowImpl->mpClientWindow )
+ return mpWindowImpl->mpClientWindow;
+ else
+ return const_cast<vcl::Window*>(this);
+}
+
+ImplFrameData* Window::ImplGetFrameData()
+{
+ return mpWindowImpl ? mpWindowImpl->mpFrameData : nullptr;
+}
+
+SalFrame* Window::ImplGetFrame() const
+{
+ return mpWindowImpl ? mpWindowImpl->mpFrame : nullptr;
+}
+
+weld::Window* Window::GetFrameWeld() const
+{
+ SalFrame* pFrame = ImplGetFrame();
+ return pFrame ? pFrame->GetFrameWeld() : nullptr;
+}
+
+vcl::Window* Window::GetFrameWindow() const
+{
+ SalFrame* pFrame = ImplGetFrame();
+ return pFrame ? pFrame->GetWindow() : nullptr;
+}
+
+vcl::Window* Window::ImplGetParent() const
+{
+ return mpWindowImpl ? mpWindowImpl->mpParent.get() : nullptr;
+}
+
+vcl::Window* Window::ImplGetClientWindow() const
+{
+ return mpWindowImpl ? mpWindowImpl->mpClientWindow.get() : nullptr;
+}
+
+vcl::Window* Window::ImplGetBorderWindow() const
+{
+ return mpWindowImpl ? mpWindowImpl->mpBorderWindow.get() : nullptr;
+}
+
+vcl::Window* Window::ImplGetFirstOverlapWindow()
+{
+ if (!mpWindowImpl)
+ {
+ return nullptr;
+ }
+
+ if ( mpWindowImpl->mbOverlapWin )
+ return this;
+ else
+ return mpWindowImpl->mpOverlapWindow;
+}
+
+const vcl::Window* Window::ImplGetFirstOverlapWindow() const
+{
+ if (!mpWindowImpl)
+ {
+ return nullptr;
+ }
+
+ if ( mpWindowImpl->mbOverlapWin )
+ return this;
+ else
+ return mpWindowImpl->mpOverlapWindow;
+}
+
+vcl::Window* Window::ImplGetFrameWindow() const
+{
+ return mpWindowImpl ? mpWindowImpl->mpFrameWindow.get() : nullptr;
+}
+
+bool Window::IsDockingWindow() const
+{
+ return mpWindowImpl && mpWindowImpl->mbDockWin;
+}
+
+bool Window::ImplIsFloatingWindow() const
+{
+ return mpWindowImpl && mpWindowImpl->mbFloatWin;
+}
+
+bool Window::ImplIsSplitter() const
+{
+ return mpWindowImpl && mpWindowImpl->mbSplitter;
+}
+
+bool Window::ImplIsPushButton() const
+{
+ return mpWindowImpl && mpWindowImpl->mbPushButton;
+}
+
+bool Window::ImplIsOverlapWindow() const
+{
+ return mpWindowImpl && mpWindowImpl->mbOverlapWin;
+}
+
+void Window::ImplSetMouseTransparent( bool bTransparent )
+{
+ if (mpWindowImpl)
+ mpWindowImpl->mbMouseTransparent = bTransparent;
+}
+
+Point Window::ImplOutputToFrame( const Point& rPos )
+{
+ return Point( rPos.X()+GetOutDev()->mnOutOffX, rPos.Y()+GetOutDev()->mnOutOffY );
+}
+
+Point Window::ImplFrameToOutput( const Point& rPos )
+{
+ return Point( rPos.X()-GetOutDev()->mnOutOffX, rPos.Y()-GetOutDev()->mnOutOffY );
+}
+
+void Window::SetCompoundControl( bool bCompound )
+{
+ if (mpWindowImpl)
+ mpWindowImpl->mbCompoundControl = bCompound;
+}
+
+WinBits Window::GetStyle() const
+{
+ return mpWindowImpl ? mpWindowImpl->mnStyle : 0;
+}
+
+WinBits Window::GetPrevStyle() const
+{
+ return mpWindowImpl ? mpWindowImpl->mnPrevStyle : 0;
+}
+
+WindowExtendedStyle Window::GetExtendedStyle() const
+{
+ return mpWindowImpl ? mpWindowImpl->mnExtendedStyle : WindowExtendedStyle::NONE;
+}
+
+void Window::SetType( WindowType nType )
+{
+ if (mpWindowImpl)
+ mpWindowImpl->mnType = nType;
+}
+
+WindowType Window::GetType() const
+{
+ if (mpWindowImpl)
+ return mpWindowImpl->mnType;
+ else
+ return WindowType::NONE;
+}
+
+Dialog* Window::GetParentDialog() const
+{
+ const vcl::Window *pWindow = this;
+
+ while( pWindow )
+ {
+ if( pWindow->IsDialog() )
+ break;
+
+ pWindow = pWindow->GetParent();
+ }
+
+ return const_cast<Dialog *>(dynamic_cast<const Dialog*>(pWindow));
+}
+
+bool Window::IsSystemWindow() const
+{
+ return mpWindowImpl && mpWindowImpl->mbSysWin;
+}
+
+bool Window::IsDialog() const
+{
+ return mpWindowImpl && mpWindowImpl->mbDialog;
+}
+
+bool Window::IsMenuFloatingWindow() const
+{
+ return mpWindowImpl && mpWindowImpl->mbMenuFloatingWindow;
+}
+
+bool Window::IsToolbarFloatingWindow() const
+{
+ return mpWindowImpl && mpWindowImpl->mbToolbarFloatingWindow;
+}
+
+void Window::EnableAllResize()
+{
+ mpWindowImpl->mbAllResize = true;
+}
+
+void Window::EnableChildTransparentMode( bool bEnable )
+{
+ mpWindowImpl->mbChildTransparent = bEnable;
+}
+
+bool Window::IsChildTransparentModeEnabled() const
+{
+ return mpWindowImpl && mpWindowImpl->mbChildTransparent;
+}
+
+bool Window::IsMouseTransparent() const
+{
+ return mpWindowImpl && mpWindowImpl->mbMouseTransparent;
+}
+
+bool Window::IsPaintTransparent() const
+{
+ return mpWindowImpl && mpWindowImpl->mbPaintTransparent;
+}
+
+void Window::SetDialogControlStart( bool bStart )
+{
+ mpWindowImpl->mbDlgCtrlStart = bStart;
+}
+
+bool Window::IsDialogControlStart() const
+{
+ return mpWindowImpl && mpWindowImpl->mbDlgCtrlStart;
+}
+
+void Window::SetDialogControlFlags( DialogControlFlags nFlags )
+{
+ mpWindowImpl->mnDlgCtrlFlags = nFlags;
+}
+
+DialogControlFlags Window::GetDialogControlFlags() const
+{
+ return mpWindowImpl->mnDlgCtrlFlags;
+}
+
+const InputContext& Window::GetInputContext() const
+{
+ return mpWindowImpl->maInputContext;
+}
+
+bool Window::IsControlFont() const
+{
+ return bool(mpWindowImpl->mpControlFont);
+}
+
+const Color& Window::GetControlForeground() const
+{
+ return mpWindowImpl->maControlForeground;
+}
+
+bool Window::IsControlForeground() const
+{
+ return mpWindowImpl->mbControlForeground;
+}
+
+const Color& Window::GetControlBackground() const
+{
+ return mpWindowImpl->maControlBackground;
+}
+
+bool Window::IsControlBackground() const
+{
+ return mpWindowImpl->mbControlBackground;
+}
+
+bool Window::IsInPaint() const
+{
+ return mpWindowImpl && mpWindowImpl->mbInPaint;
+}
+
+vcl::Window* Window::GetParent() const
+{
+ return mpWindowImpl ? mpWindowImpl->mpRealParent.get() : nullptr;
+}
+
+bool Window::IsVisible() const
+{
+ return mpWindowImpl && mpWindowImpl->mbVisible;
+}
+
+bool Window::IsReallyVisible() const
+{
+ return mpWindowImpl && mpWindowImpl->mbReallyVisible;
+}
+
+bool Window::IsReallyShown() const
+{
+ return mpWindowImpl && mpWindowImpl->mbReallyShown;
+}
+
+bool Window::IsInInitShow() const
+{
+ return mpWindowImpl->mbInInitShow;
+}
+
+bool Window::IsEnabled() const
+{
+ return mpWindowImpl && !mpWindowImpl->mbDisabled;
+}
+
+bool Window::IsInputEnabled() const
+{
+ return mpWindowImpl && !mpWindowImpl->mbInputDisabled;
+}
+
+bool Window::IsAlwaysEnableInput() const
+{
+ return mpWindowImpl->meAlwaysInputMode == AlwaysInputEnabled;
+}
+
+ActivateModeFlags Window::GetActivateMode() const
+{
+ return mpWindowImpl->mnActivateMode;
+
+}
+
+bool Window::IsAlwaysOnTopEnabled() const
+{
+ return mpWindowImpl->mbAlwaysOnTop;
+}
+
+bool Window::IsDefaultPos() const
+{
+ return mpWindowImpl->mbDefPos;
+}
+
+bool Window::IsDefaultSize() const
+{
+ return mpWindowImpl->mbDefSize;
+}
+
+Point Window::GetOffsetPixelFrom(const vcl::Window& rWindow) const
+{
+ return Point(GetOutOffXPixel() - rWindow.GetOutOffXPixel(), GetOutOffYPixel() - rWindow.GetOutOffYPixel());
+}
+
+void Window::EnablePaint( bool bEnable )
+{
+ mpWindowImpl->mbPaintDisabled = !bEnable;
+}
+
+bool Window::IsPaintEnabled() const
+{
+ return !mpWindowImpl->mbPaintDisabled;
+}
+
+bool Window::IsUpdateMode() const
+{
+ return !mpWindowImpl->mbNoUpdate;
+}
+
+void Window::SetParentUpdateMode( bool bUpdate )
+{
+ mpWindowImpl->mbNoParentUpdate = !bUpdate;
+}
+
+bool Window::IsActive() const
+{
+ return mpWindowImpl->mbActive;
+}
+
+GetFocusFlags Window::GetGetFocusFlags() const
+{
+ return mpWindowImpl->mnGetFocusFlags;
+}
+
+bool Window::IsCompoundControl() const
+{
+ return mpWindowImpl && mpWindowImpl->mbCompoundControl;
+}
+
+bool Window::IsWait() const
+{
+ return (mpWindowImpl->mnWaitCount != 0);
+}
+
+vcl::Cursor* Window::GetCursor() const
+{
+ if (!mpWindowImpl)
+ return nullptr;
+ return mpWindowImpl->mpCursor;
+}
+
+const Fraction& Window::GetZoom() const
+{
+ return mpWindowImpl->maZoom;
+}
+
+bool Window::IsZoom() const
+{
+ return mpWindowImpl->maZoom.GetNumerator() != mpWindowImpl->maZoom.GetDenominator();
+}
+
+void Window::SetHelpText( const OUString& rHelpText )
+{
+ mpWindowImpl->maHelpText = rHelpText;
+ mpWindowImpl->mbHelpTextDynamic = true;
+}
+
+void Window::SetQuickHelpText( const OUString& rHelpText )
+{
+ if (mpWindowImpl)
+ mpWindowImpl->maQuickHelpText = rHelpText;
+}
+
+const OUString& Window::GetQuickHelpText() const
+{
+ return mpWindowImpl->maQuickHelpText;
+}
+
+bool Window::IsCreatedWithToolkit() const
+{
+ return mpWindowImpl->mbCreatedWithToolkit;
+}
+
+void Window::SetCreatedWithToolkit( bool b )
+{
+ mpWindowImpl->mbCreatedWithToolkit = b;
+}
+
+PointerStyle Window::GetPointer() const
+{
+ return mpWindowImpl->maPointer;
+}
+
+VCLXWindow* Window::GetWindowPeer() const
+{
+ return mpWindowImpl ? mpWindowImpl->mpVCLXWindow : nullptr;
+}
+
+void Window::SetPosPixel( const Point& rNewPos )
+{
+ setPosSizePixel( rNewPos.X(), rNewPos.Y(), 0, 0, PosSizeFlags::Pos );
+}
+
+void Window::SetSizePixel( const Size& rNewSize )
+{
+ setPosSizePixel( 0, 0, rNewSize.Width(), rNewSize.Height(),
+ PosSizeFlags::Size );
+}
+
+void Window::SetPosSizePixel( const Point& rNewPos, const Size& rNewSize )
+{
+ setPosSizePixel( rNewPos.X(), rNewPos.Y(),
+ rNewSize.Width(), rNewSize.Height());
+}
+
+void Window::SetOutputSizePixel( const Size& rNewSize )
+{
+ SetSizePixel( Size( rNewSize.Width()+mpWindowImpl->mnLeftBorder+mpWindowImpl->mnRightBorder,
+ rNewSize.Height()+mpWindowImpl->mnTopBorder+mpWindowImpl->mnBottomBorder ) );
+}
+
+//When a widget wants to renegotiate layout, get toplevel parent dialog and call
+//resize on it. Mark all intermediate containers (or container-alike) widgets
+//as dirty for the size remains unchanged, but layout changed circumstances
+namespace
+{
+ bool queue_ungrouped_resize(vcl::Window const *pOrigWindow)
+ {
+ bool bSomeoneCares = false;
+
+ vcl::Window *pWindow = pOrigWindow->GetParent();
+ if (pWindow)
+ {
+ if (isContainerWindow(*pWindow))
+ {
+ bSomeoneCares = true;
+ }
+ else if (pWindow->GetType() == WindowType::TABCONTROL)
+ {
+ bSomeoneCares = true;
+ }
+ pWindow->queue_resize();
+ }
+
+ return bSomeoneCares;
+ }
+}
+
+void Window::InvalidateSizeCache()
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mnOptimalWidthCache = -1;
+ pWindowImpl->mnOptimalHeightCache = -1;
+}
+
+static bool HasParentDockingWindow(const vcl::Window* pWindow)
+{
+ while( pWindow )
+ {
+ if( pWindow->IsDockingWindow() )
+ return true;
+
+ pWindow = pWindow->GetParent();
+ }
+
+ return false;
+}
+
+void Window::queue_resize(StateChangedType eReason)
+{
+ if (isDisposed())
+ return;
+
+ bool bSomeoneCares = queue_ungrouped_resize(this);
+
+ if (eReason != StateChangedType::Visible)
+ {
+ InvalidateSizeCache();
+ }
+
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ if (pWindowImpl->m_xSizeGroup && pWindowImpl->m_xSizeGroup->get_mode() != VclSizeGroupMode::NONE)
+ {
+ std::set<VclPtr<vcl::Window> > &rWindows = pWindowImpl->m_xSizeGroup->get_widgets();
+ for (VclPtr<vcl::Window> const & pOther : rWindows)
+ {
+ if (pOther == this)
+ continue;
+ queue_ungrouped_resize(pOther);
+ }
+ }
+
+ if (bSomeoneCares && !isDisposed())
+ {
+ //fdo#57090 force a resync of the borders of the borderwindow onto this
+ //window in case they have changed
+ vcl::Window* pBorderWindow = ImplGetBorderWindow();
+ if (pBorderWindow)
+ pBorderWindow->Resize();
+ }
+ if (VclPtr<vcl::Window> pParent = GetParentWithLOKNotifier())
+ {
+ Size aSize = GetSizePixel();
+ if (!aSize.IsEmpty() && !pParent->IsInInitShow()
+ && (GetParentDialog() || HasParentDockingWindow(this)))
+ LogicInvalidate(nullptr);
+ }
+}
+
+namespace
+{
+ VclAlign toAlign(std::u16string_view rValue)
+ {
+ VclAlign eRet = VclAlign::Fill;
+
+ if (rValue == u"fill")
+ eRet = VclAlign::Fill;
+ else if (rValue == u"start")
+ eRet = VclAlign::Start;
+ else if (rValue == u"end")
+ eRet = VclAlign::End;
+ else if (rValue == u"center")
+ eRet = VclAlign::Center;
+ return eRet;
+ }
+}
+
+bool Window::set_font_attribute(const OString &rKey, std::u16string_view rValue)
+{
+ if (rKey == "weight")
+ {
+ vcl::Font aFont(GetControlFont());
+ if (rValue == u"thin")
+ aFont.SetWeight(WEIGHT_THIN);
+ else if (rValue == u"ultralight")
+ aFont.SetWeight(WEIGHT_ULTRALIGHT);
+ else if (rValue == u"light")
+ aFont.SetWeight(WEIGHT_LIGHT);
+ else if (rValue == u"book")
+ aFont.SetWeight(WEIGHT_SEMILIGHT);
+ else if (rValue == u"normal")
+ aFont.SetWeight(WEIGHT_NORMAL);
+ else if (rValue == u"medium")
+ aFont.SetWeight(WEIGHT_MEDIUM);
+ else if (rValue == u"semibold")
+ aFont.SetWeight(WEIGHT_SEMIBOLD);
+ else if (rValue == u"bold")
+ aFont.SetWeight(WEIGHT_BOLD);
+ else if (rValue == u"ultrabold")
+ aFont.SetWeight(WEIGHT_ULTRABOLD);
+ else
+ aFont.SetWeight(WEIGHT_BLACK);
+ SetControlFont(aFont);
+ }
+ else if (rKey == "style")
+ {
+ vcl::Font aFont(GetControlFont());
+ if (rValue == u"normal")
+ aFont.SetItalic(ITALIC_NONE);
+ else if (rValue == u"oblique")
+ aFont.SetItalic(ITALIC_OBLIQUE);
+ else if (rValue == u"italic")
+ aFont.SetItalic(ITALIC_NORMAL);
+ SetControlFont(aFont);
+ }
+ else if (rKey == "underline")
+ {
+ vcl::Font aFont(GetControlFont());
+ aFont.SetUnderline(toBool(rValue) ? LINESTYLE_SINGLE : LINESTYLE_NONE);
+ SetControlFont(aFont);
+ }
+ else if (rKey == "scale")
+ {
+ // if no control font was set yet, take the underlying font from the device
+ vcl::Font aFont(IsControlFont() ? GetControlFont() : GetPointFont(*GetOutDev()));
+ aFont.SetFontHeight(aFont.GetFontHeight() * o3tl::toDouble(rValue));
+ SetControlFont(aFont);
+ }
+ else if (rKey == "size")
+ {
+ vcl::Font aFont(GetControlFont());
+ sal_Int32 nHeight = o3tl::toInt32(rValue) / 1000;
+ aFont.SetFontHeight(nHeight);
+ SetControlFont(aFont);
+ }
+ else
+ {
+ SAL_INFO("vcl.layout", "unhandled font attribute: " << rKey);
+ return false;
+ }
+ return true;
+}
+
+bool Window::set_property(const OString &rKey, const OUString &rValue)
+{
+ if ((rKey == "label") || (rKey == "title") || (rKey == "text") )
+ {
+ SetText(BuilderUtils::convertMnemonicMarkup(rValue));
+ }
+ else if (rKey == "visible")
+ Show(toBool(rValue));
+ else if (rKey == "sensitive")
+ Enable(toBool(rValue));
+ else if (rKey == "resizable")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~WB_SIZEABLE;
+ if (toBool(rValue))
+ nBits |= WB_SIZEABLE;
+ SetStyle(nBits);
+ }
+ else if (rKey == "xalign")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_LEFT | WB_CENTER | WB_RIGHT);
+
+ float f = rValue.toFloat();
+ assert(f == 0.0 || f == 1.0 || f == 0.5);
+ if (f == 0.0)
+ nBits |= WB_LEFT;
+ else if (f == 1.0)
+ nBits |= WB_RIGHT;
+ else if (f == 0.5)
+ nBits |= WB_CENTER;
+
+ SetStyle(nBits);
+ }
+ else if (rKey == "justification")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_LEFT | WB_CENTER | WB_RIGHT);
+
+ if (rValue == "left")
+ nBits |= WB_LEFT;
+ else if (rValue == "right")
+ nBits |= WB_RIGHT;
+ else if (rValue == "center")
+ nBits |= WB_CENTER;
+
+ SetStyle(nBits);
+ }
+ else if (rKey == "yalign")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_TOP | WB_VCENTER | WB_BOTTOM);
+
+ float f = rValue.toFloat();
+ assert(f == 0.0 || f == 1.0 || f == 0.5);
+ if (f == 0.0)
+ nBits |= WB_TOP;
+ else if (f == 1.0)
+ nBits |= WB_BOTTOM;
+ else if (f == 0.5)
+ nBits |= WB_VCENTER;
+
+ SetStyle(nBits);
+ }
+ else if (rKey == "wrap")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~WB_WORDBREAK;
+ if (toBool(rValue))
+ nBits |= WB_WORDBREAK;
+ SetStyle(nBits);
+ }
+ else if (rKey == "height-request")
+ set_height_request(rValue.toInt32());
+ else if (rKey == "width-request")
+ set_width_request(rValue.toInt32());
+ else if (rKey == "hexpand")
+ set_hexpand(toBool(rValue));
+ else if (rKey == "vexpand")
+ set_vexpand(toBool(rValue));
+ else if (rKey == "halign")
+ set_halign(toAlign(rValue));
+ else if (rKey == "valign")
+ set_valign(toAlign(rValue));
+ else if (rKey == "tooltip-markup")
+ SetQuickHelpText(rValue);
+ else if (rKey == "tooltip-text")
+ SetQuickHelpText(rValue);
+ else if (rKey == "border-width")
+ set_border_width(rValue.toInt32());
+ else if (rKey == "margin-start" || rKey == "margin-left")
+ {
+ assert(rKey == "margin-start" && "margin-left deprecated in favor of margin-start");
+ set_margin_start(rValue.toInt32());
+ }
+ else if (rKey == "margin-end" || rKey == "margin-right")
+ {
+ assert(rKey == "margin-end" && "margin-right deprecated in favor of margin-end");
+ set_margin_end(rValue.toInt32());
+ }
+ else if (rKey == "margin-top")
+ set_margin_top(rValue.toInt32());
+ else if (rKey == "margin-bottom")
+ set_margin_bottom(rValue.toInt32());
+ else if (rKey == "hscrollbar-policy")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_AUTOHSCROLL|WB_HSCROLL);
+ if (rValue == "always")
+ nBits |= WB_HSCROLL;
+ else if (rValue == "automatic")
+ nBits |= WB_AUTOHSCROLL;
+ SetStyle(nBits);
+ }
+ else if (rKey == "vscrollbar-policy")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_AUTOVSCROLL|WB_VSCROLL);
+ if (rValue == "always")
+ nBits |= WB_VSCROLL;
+ else if (rValue == "automatic")
+ nBits |= WB_AUTOVSCROLL;
+ SetStyle(nBits);
+ }
+ else if (rKey == "accessible-name")
+ {
+ SetAccessibleName(rValue);
+ }
+ else if (rKey == "accessible-description")
+ {
+ SetAccessibleDescription(rValue);
+ }
+ else if (rKey == "accessible-role")
+ {
+ sal_Int16 role = BuilderUtils::getRoleFromName(rValue.toUtf8());
+ if (role != com::sun::star::accessibility::AccessibleRole::UNKNOWN)
+ SetAccessibleRole(role);
+ }
+ else if (rKey == "use-markup")
+ {
+ //https://live.gnome.org/GnomeGoals/RemoveMarkupInMessages
+ SAL_WARN_IF(toBool(rValue), "vcl.layout", "Use pango attributes instead of mark-up");
+ }
+ else if (rKey == "has-focus")
+ {
+ if (toBool(rValue))
+ GrabFocus();
+ }
+ else if (rKey == "can-focus")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_TABSTOP|WB_NOTABSTOP);
+ if (toBool(rValue))
+ nBits |= WB_TABSTOP;
+ else
+ nBits |= WB_NOTABSTOP;
+ SetStyle(nBits);
+ }
+ else
+ {
+ SAL_INFO("vcl.layout", "unhandled property: " << rKey);
+ return false;
+ }
+ return true;
+}
+
+void Window::set_height_request(sal_Int32 nHeightRequest)
+{
+ if (!mpWindowImpl)
+ return;
+
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+
+ if ( pWindowImpl->mnHeightRequest != nHeightRequest )
+ {
+ pWindowImpl->mnHeightRequest = nHeightRequest;
+ queue_resize();
+ }
+}
+
+void Window::set_width_request(sal_Int32 nWidthRequest)
+{
+ if (!mpWindowImpl)
+ return;
+
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+
+ if ( pWindowImpl->mnWidthRequest != nWidthRequest )
+ {
+ pWindowImpl->mnWidthRequest = nWidthRequest;
+ queue_resize();
+ }
+}
+
+Size Window::get_ungrouped_preferred_size() const
+{
+ Size aRet(get_width_request(), get_height_request());
+ if (aRet.Width() == -1 || aRet.Height() == -1)
+ {
+ //cache gets blown away by queue_resize
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ if (pWindowImpl->mnOptimalWidthCache == -1 || pWindowImpl->mnOptimalHeightCache == -1)
+ {
+ Size aOptimal(GetOptimalSize());
+ pWindowImpl->mnOptimalWidthCache = aOptimal.Width();
+ pWindowImpl->mnOptimalHeightCache = aOptimal.Height();
+ }
+
+ if (aRet.Width() == -1)
+ aRet.setWidth( pWindowImpl->mnOptimalWidthCache );
+ if (aRet.Height() == -1)
+ aRet.setHeight( pWindowImpl->mnOptimalHeightCache );
+ }
+ return aRet;
+}
+
+Size Window::get_preferred_size() const
+{
+ Size aRet(get_ungrouped_preferred_size());
+
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ if (pWindowImpl->m_xSizeGroup)
+ {
+ const VclSizeGroupMode eMode = pWindowImpl->m_xSizeGroup->get_mode();
+ if (eMode != VclSizeGroupMode::NONE)
+ {
+ const bool bIgnoreInHidden = pWindowImpl->m_xSizeGroup->get_ignore_hidden();
+ const std::set<VclPtr<vcl::Window> > &rWindows = pWindowImpl->m_xSizeGroup->get_widgets();
+ for (auto const& window : rWindows)
+ {
+ const vcl::Window *pOther = window;
+ if (pOther == this)
+ continue;
+ if (bIgnoreInHidden && !pOther->IsVisible())
+ continue;
+ Size aOtherSize = pOther->get_ungrouped_preferred_size();
+ if (eMode == VclSizeGroupMode::Both || eMode == VclSizeGroupMode::Horizontal)
+ aRet.setWidth( std::max(aRet.Width(), aOtherSize.Width()) );
+ if (eMode == VclSizeGroupMode::Both || eMode == VclSizeGroupMode::Vertical)
+ aRet.setHeight( std::max(aRet.Height(), aOtherSize.Height()) );
+ }
+ }
+ }
+
+ return aRet;
+}
+
+VclAlign Window::get_halign() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->meHalign;
+}
+
+void Window::set_halign(VclAlign eAlign)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->meHalign = eAlign;
+}
+
+VclAlign Window::get_valign() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->meValign;
+}
+
+void Window::set_valign(VclAlign eAlign)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->meValign = eAlign;
+}
+
+bool Window::get_hexpand() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mbHexpand;
+}
+
+void Window::set_hexpand(bool bExpand)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mbHexpand = bExpand;
+}
+
+bool Window::get_vexpand() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mbVexpand;
+}
+
+void Window::set_vexpand(bool bExpand)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mbVexpand = bExpand;
+}
+
+bool Window::get_expand() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mbExpand;
+}
+
+void Window::set_expand(bool bExpand)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mbExpand = bExpand;
+}
+
+VclPackType Window::get_pack_type() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mePackType;
+}
+
+void Window::set_pack_type(VclPackType ePackType)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mePackType = ePackType;
+}
+
+sal_Int32 Window::get_padding() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnPadding;
+}
+
+void Window::set_padding(sal_Int32 nPadding)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mnPadding = nPadding;
+}
+
+bool Window::get_fill() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mbFill;
+}
+
+void Window::set_fill(bool bFill)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mbFill = bFill;
+}
+
+sal_Int32 Window::get_grid_width() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnGridWidth;
+}
+
+void Window::set_grid_width(sal_Int32 nCols)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mnGridWidth = nCols;
+}
+
+sal_Int32 Window::get_grid_left_attach() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnGridLeftAttach;
+}
+
+void Window::set_grid_left_attach(sal_Int32 nAttach)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mnGridLeftAttach = nAttach;
+}
+
+sal_Int32 Window::get_grid_height() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnGridHeight;
+}
+
+void Window::set_grid_height(sal_Int32 nRows)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mnGridHeight = nRows;
+}
+
+sal_Int32 Window::get_grid_top_attach() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnGridTopAttach;
+}
+
+void Window::set_grid_top_attach(sal_Int32 nAttach)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mnGridTopAttach = nAttach;
+}
+
+void Window::set_border_width(sal_Int32 nBorderWidth)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mnBorderWidth = nBorderWidth;
+}
+
+sal_Int32 Window::get_border_width() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnBorderWidth;
+}
+
+void Window::set_margin_start(sal_Int32 nWidth)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ if (pWindowImpl->mnMarginLeft != nWidth)
+ {
+ pWindowImpl->mnMarginLeft = nWidth;
+ queue_resize();
+ }
+}
+
+sal_Int32 Window::get_margin_start() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnMarginLeft;
+}
+
+void Window::set_margin_end(sal_Int32 nWidth)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ if (pWindowImpl->mnMarginRight != nWidth)
+ {
+ pWindowImpl->mnMarginRight = nWidth;
+ queue_resize();
+ }
+}
+
+sal_Int32 Window::get_margin_end() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnMarginRight;
+}
+
+void Window::set_margin_top(sal_Int32 nWidth)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ if (pWindowImpl->mnMarginTop != nWidth)
+ {
+ pWindowImpl->mnMarginTop = nWidth;
+ queue_resize();
+ }
+}
+
+sal_Int32 Window::get_margin_top() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnMarginTop;
+}
+
+void Window::set_margin_bottom(sal_Int32 nWidth)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ if (pWindowImpl->mnMarginBottom != nWidth)
+ {
+ pWindowImpl->mnMarginBottom = nWidth;
+ queue_resize();
+ }
+}
+
+sal_Int32 Window::get_margin_bottom() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnMarginBottom;
+}
+
+sal_Int32 Window::get_height_request() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnHeightRequest;
+}
+
+sal_Int32 Window::get_width_request() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mnWidthRequest;
+}
+
+bool Window::get_secondary() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mbSecondary;
+}
+
+void Window::set_secondary(bool bSecondary)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mbSecondary = bSecondary;
+}
+
+bool Window::get_non_homogeneous() const
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ return pWindowImpl->mbNonHomogeneous;
+}
+
+void Window::set_non_homogeneous(bool bNonHomogeneous)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ pWindowImpl->mbNonHomogeneous = bNonHomogeneous;
+}
+
+void Window::add_to_size_group(const std::shared_ptr<VclSizeGroup>& xGroup)
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ //To-Do, multiple groups
+ pWindowImpl->m_xSizeGroup = xGroup;
+ pWindowImpl->m_xSizeGroup->insert(this);
+ if (VclSizeGroupMode::NONE != pWindowImpl->m_xSizeGroup->get_mode())
+ queue_resize();
+}
+
+void Window::remove_from_all_size_groups()
+{
+ WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get();
+ //To-Do, multiple groups
+ if (pWindowImpl->m_xSizeGroup)
+ {
+ if (VclSizeGroupMode::NONE != pWindowImpl->m_xSizeGroup->get_mode())
+ queue_resize();
+ pWindowImpl->m_xSizeGroup->erase(this);
+ pWindowImpl->m_xSizeGroup.reset();
+ }
+}
+
+void Window::add_mnemonic_label(FixedText *pLabel)
+{
+ std::vector<VclPtr<FixedText> >& v = mpWindowImpl->m_aMnemonicLabels;
+ if (std::find(v.begin(), v.end(), VclPtr<FixedText>(pLabel)) != v.end())
+ return;
+ v.emplace_back(pLabel);
+ pLabel->set_mnemonic_widget(this);
+}
+
+void Window::remove_mnemonic_label(FixedText *pLabel)
+{
+ std::vector<VclPtr<FixedText> >& v = mpWindowImpl->m_aMnemonicLabels;
+ auto aFind = std::find(v.begin(), v.end(), VclPtr<FixedText>(pLabel));
+ if (aFind == v.end())
+ return;
+ v.erase(aFind);
+ pLabel->set_mnemonic_widget(nullptr);
+}
+
+const std::vector<VclPtr<FixedText> >& Window::list_mnemonic_labels() const
+{
+ return mpWindowImpl->m_aMnemonicLabels;
+}
+
+} /* namespace vcl */
+
+void InvertFocusRect(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ const int nBorder = 1;
+ rRenderContext.Invert(tools::Rectangle(Point(rRect.Left(), rRect.Top()), Size(rRect.GetWidth(), nBorder)), InvertFlags::N50);
+ rRenderContext.Invert(tools::Rectangle(Point(rRect.Left(), rRect.Bottom()-nBorder+1), Size(rRect.GetWidth(), nBorder)), InvertFlags::N50);
+ rRenderContext.Invert(tools::Rectangle(Point(rRect.Left(), rRect.Top()+nBorder), Size(nBorder, rRect.GetHeight()-(nBorder*2))), InvertFlags::N50);
+ rRenderContext.Invert(tools::Rectangle(Point(rRect.Right()-nBorder+1, rRect.Top()+nBorder), Size(nBorder, rRect.GetHeight()-(nBorder*2))), InvertFlags::N50);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/window3.cxx b/vcl/source/window/window3.cxx
new file mode 100644
index 000000000..5b8dd5cff
--- /dev/null
+++ b/vcl/source/window/window3.cxx
@@ -0,0 +1,220 @@
+/* -*- 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 <vcl/window.hxx>
+#include <window.h>
+#include <vcl/cursor.hxx>
+
+namespace vcl
+{
+Size Window::GetOptimalSize() const { return Size(); }
+
+void Window::ImplAdjustNWFSizes()
+{
+ for (Window* pWin = GetWindow(GetWindowType::FirstChild); pWin;
+ pWin = pWin->GetWindow(GetWindowType::Next))
+ pWin->ImplAdjustNWFSizes();
+}
+
+void WindowOutputDevice::ImplClearFontData(bool bNewFontLists)
+{
+ OutputDevice::ImplClearFontData(bNewFontLists);
+ for (Window* pChild = mxOwnerWindow->mpWindowImpl->mpFirstChild; pChild;
+ pChild = pChild->mpWindowImpl->mpNext)
+ pChild->GetOutDev()->ImplClearFontData(bNewFontLists);
+}
+
+void WindowOutputDevice::ImplRefreshFontData(bool bNewFontLists)
+{
+ OutputDevice::ImplRefreshFontData(bNewFontLists);
+ for (Window* pChild = mxOwnerWindow->mpWindowImpl->mpFirstChild; pChild;
+ pChild = pChild->mpWindowImpl->mpNext)
+ pChild->GetOutDev()->ImplRefreshFontData(bNewFontLists);
+}
+
+void WindowOutputDevice::ImplInitMapModeObjects()
+{
+ OutputDevice::ImplInitMapModeObjects();
+ if (mxOwnerWindow->mpWindowImpl->mpCursor)
+ mxOwnerWindow->mpWindowImpl->mpCursor->ImplNew();
+}
+
+const Font& Window::GetFont() const { return GetOutDev()->GetFont(); }
+void Window::SetFont(Font const& font) { return GetOutDev()->SetFont(font); }
+
+float Window::approximate_char_width() const { return GetOutDev()->approximate_char_width(); }
+
+const Wallpaper& Window::GetBackground() const { return GetOutDev()->GetBackground(); }
+bool Window::IsBackground() const { return GetOutDev()->IsBackground(); }
+tools::Long Window::GetTextHeight() const { return GetOutDev()->GetTextHeight(); }
+tools::Long Window::GetTextWidth(const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen,
+ vcl::text::TextLayoutCache const* pCache,
+ SalLayoutGlyphs const* const pLayoutCache) const
+{
+ return GetOutDev()->GetTextWidth(rStr, nIndex, nLen, pCache, pLayoutCache);
+}
+float Window::approximate_digit_width() const { return GetOutDev()->approximate_digit_width(); }
+
+bool Window::IsNativeControlSupported(ControlType nType, ControlPart nPart) const
+{
+ return GetOutDev()->IsNativeControlSupported(nType, nPart);
+}
+
+bool Window::GetNativeControlRegion(ControlType nType, ControlPart nPart,
+ const tools::Rectangle& rControlRegion, ControlState nState,
+ const ImplControlValue& aValue,
+ tools::Rectangle& rNativeBoundingRegion,
+ tools::Rectangle& rNativeContentRegion) const
+{
+ return GetOutDev()->GetNativeControlRegion(nType, nPart, rControlRegion, nState, aValue,
+ rNativeBoundingRegion, rNativeContentRegion);
+}
+
+Size Window::GetOutputSizePixel() const { return GetOutDev()->GetOutputSizePixel(); }
+
+tools::Rectangle Window::GetOutputRectPixel() const { return GetOutDev()->GetOutputRectPixel(); }
+
+void Window::SetTextLineColor() { GetOutDev()->SetTextLineColor(); }
+void Window::SetTextLineColor(const Color& rColor) { GetOutDev()->SetTextLineColor(rColor); }
+void Window::SetOverlineColor() { GetOutDev()->SetOverlineColor(); }
+void Window::SetOverlineColor(const Color& rColor) { GetOutDev()->SetOverlineColor(rColor); }
+void Window::SetTextFillColor() { GetOutDev()->SetTextFillColor(); }
+void Window::SetTextFillColor(const Color& rColor) { GetOutDev()->SetTextFillColor(rColor); }
+const MapMode& Window::GetMapMode() const { return GetOutDev()->GetMapMode(); }
+void Window::SetBackground() { GetOutDev()->SetBackground(); }
+void Window::SetBackground(const Wallpaper& rBackground)
+{
+ GetOutDev()->SetBackground(rBackground);
+}
+void Window::EnableMapMode(bool bEnable) { GetOutDev()->EnableMapMode(bEnable); }
+bool Window::IsMapModeEnabled() const { return GetOutDev()->IsMapModeEnabled(); }
+
+void Window::SetTextColor(const Color& rColor) { GetOutDev()->SetTextColor(rColor); }
+const Color& Window::GetTextColor() const { return GetOutDev()->GetTextColor(); }
+const Color& Window::GetTextLineColor() const { return GetOutDev()->GetTextLineColor(); }
+
+bool Window::IsTextLineColor() const { return GetOutDev()->IsTextLineColor(); }
+
+Color Window::GetTextFillColor() const { return GetOutDev()->GetTextFillColor(); }
+
+bool Window::IsTextFillColor() const { return GetOutDev()->IsTextFillColor(); }
+
+const Color& Window::GetOverlineColor() const { return GetOutDev()->GetOverlineColor(); }
+bool Window::IsOverlineColor() const { return GetOutDev()->IsOverlineColor(); }
+void Window::SetTextAlign(TextAlign eAlign) { GetOutDev()->SetTextAlign(eAlign); }
+
+float Window::GetDPIScaleFactor() const { return GetOutDev()->GetDPIScaleFactor(); }
+tools::Long Window::GetOutOffXPixel() const { return GetOutDev()->GetOutOffXPixel(); }
+tools::Long Window::GetOutOffYPixel() const { return GetOutDev()->GetOutOffYPixel(); }
+void Window::SetMapMode() { GetOutDev()->SetMapMode(); }
+void Window::SetMapMode(const MapMode& rNewMapMode) { GetOutDev()->SetMapMode(rNewMapMode); }
+bool Window::IsRTLEnabled() const { return GetOutDev()->IsRTLEnabled(); }
+TextAlign Window::GetTextAlign() const { return GetOutDev()->GetTextAlign(); }
+const AllSettings& Window::GetSettings() const { return GetOutDev()->GetSettings(); }
+
+Point Window::LogicToPixel(const Point& rLogicPt) const
+{
+ return GetOutDev()->LogicToPixel(rLogicPt);
+}
+Size Window::LogicToPixel(const Size& rLogicSize) const
+{
+ return GetOutDev()->LogicToPixel(rLogicSize);
+}
+tools::Rectangle Window::LogicToPixel(const tools::Rectangle& rLogicRect) const
+{
+ return GetOutDev()->LogicToPixel(rLogicRect);
+}
+vcl::Region Window::LogicToPixel(const vcl::Region& rLogicRegion) const
+{
+ return GetOutDev()->LogicToPixel(rLogicRegion);
+}
+Point Window::LogicToPixel(const Point& rLogicPt, const MapMode& rMapMode) const
+{
+ return GetOutDev()->LogicToPixel(rLogicPt, rMapMode);
+}
+Size Window::LogicToPixel(const Size& rLogicSize, const MapMode& rMapMode) const
+{
+ return GetOutDev()->LogicToPixel(rLogicSize, rMapMode);
+}
+tools::Rectangle Window::LogicToPixel(const tools::Rectangle& rLogicRect,
+ const MapMode& rMapMode) const
+{
+ return GetOutDev()->LogicToPixel(rLogicRect, rMapMode);
+}
+
+Point Window::PixelToLogic(const Point& rDevicePt) const
+{
+ return GetOutDev()->PixelToLogic(rDevicePt);
+}
+Size Window::PixelToLogic(const Size& rDeviceSize) const
+{
+ return GetOutDev()->PixelToLogic(rDeviceSize);
+}
+tools::Rectangle Window::PixelToLogic(const tools::Rectangle& rDeviceRect) const
+{
+ return GetOutDev()->PixelToLogic(rDeviceRect);
+}
+tools::PolyPolygon Window::PixelToLogic(const tools::PolyPolygon& rDevicePolyPoly) const
+{
+ return GetOutDev()->PixelToLogic(rDevicePolyPoly);
+}
+vcl::Region Window::PixelToLogic(const vcl::Region& rDeviceRegion) const
+{
+ return GetOutDev()->PixelToLogic(rDeviceRegion);
+}
+Point Window::PixelToLogic(const Point& rDevicePt, const MapMode& rMapMode) const
+{
+ return GetOutDev()->PixelToLogic(rDevicePt, rMapMode);
+}
+Size Window::PixelToLogic(const Size& rDeviceSize, const MapMode& rMapMode) const
+{
+ return GetOutDev()->PixelToLogic(rDeviceSize, rMapMode);
+}
+tools::Rectangle Window::PixelToLogic(const tools::Rectangle& rDeviceRect,
+ const MapMode& rMapMode) const
+{
+ return GetOutDev()->PixelToLogic(rDeviceRect, rMapMode);
+}
+
+Size Window::LogicToLogic(const Size& rSzSource, const MapMode* pMapModeSource,
+ const MapMode* pMapModeDest) const
+{
+ return GetOutDev()->LogicToLogic(rSzSource, pMapModeSource, pMapModeDest);
+}
+
+tools::Rectangle Window::GetTextRect(const tools::Rectangle& rRect, const OUString& rStr,
+ DrawTextFlags nStyle, TextRectInfo* pInfo,
+ const vcl::ITextLayout* _pTextLayout) const
+{
+ return GetOutDev()->GetTextRect(rRect, rStr, nStyle, pInfo, _pTextLayout);
+}
+
+void Window::SetSettings(const AllSettings& rSettings) { GetOutDev()->SetSettings(rSettings); }
+void Window::SetSettings(const AllSettings& rSettings, bool bChild)
+{
+ static_cast<vcl::WindowOutputDevice*>(GetOutDev())->SetSettings(rSettings, bChild);
+}
+
+Color Window::GetBackgroundColor() const { return GetOutDev()->GetBackgroundColor(); }
+
+void Window::EnableRTL(bool bEnable) { GetOutDev()->EnableRTL(bEnable); }
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/winproc.cxx b/vcl/source/window/winproc.cxx
new file mode 100644
index 000000000..5a1b36ea8
--- /dev/null
+++ b/vcl/source/window/winproc.cxx
@@ -0,0 +1,2884 @@
+/* -*- 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 <o3tl/safeint.hxx>
+#include <tools/debug.hxx>
+#include <tools/time.hxx>
+#include <sal/log.hxx>
+
+#include <unotools/localedatawrapper.hxx>
+
+#include <dndeventdispatcher.hxx>
+#include <comphelper/lok.hxx>
+#include <vcl/QueueInfo.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/event.hxx>
+#include <vcl/GestureEvent.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/help.hxx>
+#include <vcl/dockwin.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <vcl/ptrstyle.hxx>
+
+#include <svdata.hxx>
+#include <salwtype.hxx>
+#include <salframe.hxx>
+#include <accmgr.hxx>
+#include <print.h>
+#include <window.h>
+#include <helpwin.hxx>
+#include <brdwin.hxx>
+#include <dndlistenercontainer.hxx>
+
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragSource.hpp>
+#include <com/sun/star/awt/MouseEvent.hpp>
+
+#define IMPL_MIN_NEEDSYSWIN 49
+
+bool ImplCallPreNotify( NotifyEvent& rEvt )
+{
+ return rEvt.GetWindow()->CompatPreNotify( rEvt );
+}
+
+static bool ImplHandleMouseFloatMode( vcl::Window* pChild, const Point& rMousePos,
+ sal_uInt16 nCode, MouseNotifyEvent nSVEvent,
+ bool bMouseLeave )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if (pSVData->mpWinData->mpFirstFloat && !pSVData->mpWinData->mpCaptureWin
+ && !pSVData->mpWinData->mpFirstFloat->ImplIsFloatPopupModeWindow(pChild))
+ {
+ /*
+ * #93895# since floats are system windows, coordinates have
+ * to be converted to float relative for the hittest
+ */
+ bool bHitTestInsideRect = false;
+ FloatingWindow* pFloat = pSVData->mpWinData->mpFirstFloat->ImplFloatHitTest( pChild, rMousePos, bHitTestInsideRect );
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ {
+ if ( bMouseLeave )
+ return true;
+
+ if ( !pFloat || bHitTestInsideRect )
+ {
+ if ( ImplGetSVHelpData().mpHelpWin && !ImplGetSVHelpData().mbKeyboardHelp )
+ ImplDestroyHelpWindow( true );
+ pChild->ImplGetFrame()->SetPointer( PointerStyle::Arrow );
+ return true;
+ }
+ }
+ else
+ {
+ if ( nCode & MOUSE_LEFT )
+ {
+ if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ if ( !pFloat )
+ {
+ FloatingWindow* pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat();
+ pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll );
+ return true;
+ }
+ else if ( bHitTestInsideRect )
+ {
+ pFloat->ImplSetMouseDown();
+ return true;
+ }
+ }
+ else
+ {
+ if ( pFloat )
+ {
+ if ( bHitTestInsideRect )
+ {
+ if ( pFloat->ImplIsMouseDown() )
+ pFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel );
+ return true;
+ }
+ }
+ else
+ {
+ FloatingWindow* pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat();
+ FloatWinPopupFlags nPopupFlags = pLastLevelFloat->GetPopupModeFlags();
+ if ( !(nPopupFlags & FloatWinPopupFlags::NoMouseUpClose) )
+ {
+ pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll );
+ return true;
+ }
+ }
+ }
+ }
+ else
+ {
+ if ( !pFloat )
+ {
+ FloatingWindow* pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat();
+ FloatWinPopupFlags nPopupFlags = pLastLevelFloat->GetPopupModeFlags();
+ if ( nPopupFlags & FloatWinPopupFlags::AllMouseButtonClose )
+ {
+ if ( (nPopupFlags & FloatWinPopupFlags::NoMouseUpClose) &&
+ (nSVEvent == MouseNotifyEvent::MOUSEBUTTONUP) )
+ return true;
+ pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll );
+ return true;
+ }
+ else
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+static void ImplHandleMouseHelpRequest( vcl::Window* pChild, const Point& rMousePos )
+{
+ ImplSVHelpData& aHelpData = ImplGetSVHelpData();
+ if ( aHelpData.mpHelpWin &&
+ ( aHelpData.mpHelpWin->IsWindowOrChild( pChild ) ||
+ pChild->IsWindowOrChild( aHelpData.mpHelpWin ) ))
+ return;
+
+ HelpEventMode nHelpMode = HelpEventMode::NONE;
+ if ( aHelpData.mbQuickHelp )
+ nHelpMode = HelpEventMode::QUICK;
+ if ( aHelpData.mbBalloonHelp )
+ nHelpMode |= HelpEventMode::BALLOON;
+ if ( !(bool(nHelpMode)) )
+ return;
+
+ if ( pChild->IsInputEnabled() && !pChild->IsInModalMode() )
+ {
+ HelpEvent aHelpEvent( rMousePos, nHelpMode );
+ aHelpData.mbRequestingHelp = true;
+ pChild->RequestHelp( aHelpEvent );
+ aHelpData.mbRequestingHelp = false;
+ }
+ // #104172# do not kill keyboard activated tooltips
+ else if ( aHelpData.mpHelpWin && !aHelpData.mbKeyboardHelp)
+ {
+ ImplDestroyHelpWindow( true );
+ }
+}
+
+static void ImplSetMousePointer( vcl::Window const * pChild )
+{
+ if ( ImplGetSVHelpData().mbExtHelpMode )
+ pChild->ImplGetFrame()->SetPointer( PointerStyle::Help );
+ else
+ pChild->ImplGetFrame()->SetPointer( pChild->ImplGetMousePointer() );
+}
+
+static bool ImplCallCommand( const VclPtr<vcl::Window>& pChild, CommandEventId nEvt, void const * pData = nullptr,
+ bool bMouse = false, Point const * pPos = nullptr )
+{
+ Point aPos;
+ if ( pPos )
+ aPos = *pPos;
+ else
+ {
+ if( bMouse )
+ aPos = pChild->GetPointerPosPixel();
+ else
+ {
+ // simulate mouseposition at center of window
+ Size aSize( pChild->GetOutputSizePixel() );
+ aPos = Point( aSize.getWidth()/2, aSize.getHeight()/2 );
+ }
+ }
+
+ CommandEvent aCEvt( aPos, nEvt, bMouse, pData );
+ NotifyEvent aNCmdEvt( MouseNotifyEvent::COMMAND, pChild, &aCEvt );
+ bool bPreNotify = ImplCallPreNotify( aNCmdEvt );
+ if ( pChild->isDisposed() )
+ return false;
+ if ( !bPreNotify )
+ {
+ pChild->ImplGetWindowImpl()->mbCommand = false;
+ pChild->Command( aCEvt );
+
+ if( pChild->isDisposed() )
+ return false;
+ pChild->ImplNotifyKeyMouseCommandEventListeners( aNCmdEvt );
+ if ( pChild->isDisposed() )
+ return false;
+ if ( pChild->ImplGetWindowImpl()->mbCommand )
+ return true;
+ }
+
+ return false;
+}
+
+/* #i34277# delayed context menu activation;
+* necessary if there already was a popup menu running.
+*/
+
+namespace {
+
+struct ContextMenuEvent
+{
+ VclPtr<vcl::Window> pWindow;
+ Point aChildPos;
+};
+
+}
+
+static void ContextMenuEventLink( void* pCEvent, void* )
+{
+ ContextMenuEvent* pEv = static_cast<ContextMenuEvent*>(pCEvent);
+
+ if( ! pEv->pWindow->isDisposed() )
+ {
+ ImplCallCommand( pEv->pWindow, CommandEventId::ContextMenu, nullptr, true, &pEv->aChildPos );
+ }
+ delete pEv;
+}
+
+bool ImplHandleMouseEvent( const VclPtr<vcl::Window>& xWindow, MouseNotifyEvent nSVEvent, bool bMouseLeave,
+ tools::Long nX, tools::Long nY, sal_uInt64 nMsgTime,
+ sal_uInt16 nCode, MouseEventModifiers nMode )
+{
+ SAL_INFO( "vcl.debugevent",
+ "mouse event "
+ "(MouseNotifyEvent " << static_cast<sal_uInt16>(nSVEvent) << ") "
+ "(MouseLeave " << bMouseLeave << ") "
+ "(X, Y " << nX << ", " << nY << ") "
+ "(Code " << nCode << ") "
+ "(Modifiers " << static_cast<sal_uInt16>(nMode) << ")");
+ ImplSVHelpData& aHelpData = ImplGetSVHelpData();
+ ImplSVData* pSVData = ImplGetSVData();
+ Point aMousePos( nX, nY );
+ VclPtr<vcl::Window> pChild;
+ bool bRet(false);
+ sal_uInt16 nClicks(0);
+ ImplFrameData* pWinFrameData = xWindow->ImplGetFrameData();
+ sal_uInt16 nOldCode = pWinFrameData->mnMouseCode;
+
+ if (comphelper::LibreOfficeKit::isActive() && AllSettings::GetLayoutRTL()
+ && xWindow->GetOutDev() && !xWindow->GetOutDev()->ImplIsAntiparallel())
+ {
+ xWindow->GetOutDev()->ReMirror(aMousePos);
+ nX = aMousePos.X();
+ nY = aMousePos.Y();
+ }
+
+ // we need a mousemove event, before we get a mousebuttondown or a
+ // mousebuttonup event
+ if ( (nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN) || (nSVEvent == MouseNotifyEvent::MOUSEBUTTONUP) )
+ {
+ if ( (nSVEvent == MouseNotifyEvent::MOUSEBUTTONUP) && aHelpData.mbExtHelpMode )
+ Help::EndExtHelp();
+ if ( aHelpData.mpHelpWin )
+ {
+ if( xWindow->ImplGetWindow() == aHelpData.mpHelpWin )
+ {
+ ImplDestroyHelpWindow( false );
+ return true; // xWindow is dead now - avoid crash!
+ }
+ else
+ ImplDestroyHelpWindow( true );
+ }
+
+ if ( (pWinFrameData->mnLastMouseX != nX) ||
+ (pWinFrameData->mnLastMouseY != nY) )
+ {
+ sal_uInt16 nMoveCode = nCode & ~(MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE);
+ ImplHandleMouseEvent(xWindow, MouseNotifyEvent::MOUSEMOVE, false, nX, nY, nMsgTime, nMoveCode, nMode);
+ }
+ }
+
+ // update frame data
+ pWinFrameData->mnBeforeLastMouseX = pWinFrameData->mnLastMouseX;
+ pWinFrameData->mnBeforeLastMouseY = pWinFrameData->mnLastMouseY;
+ pWinFrameData->mnLastMouseX = nX;
+ pWinFrameData->mnLastMouseY = nY;
+ pWinFrameData->mnMouseCode = nCode;
+ MouseEventModifiers const nTmpMask = MouseEventModifiers::SYNTHETIC | MouseEventModifiers::MODIFIERCHANGED;
+ pWinFrameData->mnMouseMode = nMode & ~nTmpMask;
+ if ( bMouseLeave )
+ {
+ pWinFrameData->mbMouseIn = false;
+ if ( ImplGetSVHelpData().mpHelpWin && !ImplGetSVHelpData().mbKeyboardHelp )
+ {
+ ImplDestroyHelpWindow( true );
+
+ if ( xWindow->isDisposed() )
+ return true; // xWindow is dead now - avoid crash! (#122045#)
+ }
+ }
+ else
+ pWinFrameData->mbMouseIn = true;
+
+ DBG_ASSERT(!pSVData->mpWinData->mpTrackWin
+ || (pSVData->mpWinData->mpTrackWin == pSVData->mpWinData->mpCaptureWin),
+ "ImplHandleMouseEvent: TrackWin != CaptureWin");
+
+ // AutoScrollMode
+ if (pSVData->mpWinData->mpAutoScrollWin && (nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN))
+ {
+ pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll();
+ return true;
+ }
+
+ // find mouse window
+ if (pSVData->mpWinData->mpCaptureWin)
+ {
+ pChild = pSVData->mpWinData->mpCaptureWin;
+
+ SAL_WARN_IF( xWindow != pChild->ImplGetFrameWindow(), "vcl",
+ "ImplHandleMouseEvent: mouse event is not sent to capture window" );
+
+ // java client cannot capture mouse correctly
+ if ( xWindow != pChild->ImplGetFrameWindow() )
+ return false;
+
+ if ( bMouseLeave )
+ return false;
+ }
+ else
+ {
+ if ( bMouseLeave )
+ pChild = nullptr;
+ else
+ pChild = xWindow->ImplFindWindow( aMousePos );
+ }
+
+ // test this because mouse events are buffered in the remote version
+ // and size may not be in sync
+ if ( !pChild && !bMouseLeave )
+ return false;
+
+ // execute a few tests and catch the message or implement the status
+ if ( pChild )
+ {
+ if( pChild->GetOutDev()->ImplIsAntiparallel() )
+ {
+ // re-mirror frame pos at pChild
+ const OutputDevice *pChildWinOutDev = pChild->GetOutDev();
+ pChildWinOutDev->ReMirror( aMousePos );
+ }
+
+ // no mouse messages to disabled windows
+ // #106845# if the window was disabled during capturing we have to pass the mouse events to release capturing
+ if (pSVData->mpWinData->mpCaptureWin.get() != pChild
+ && (!pChild->IsEnabled() || !pChild->IsInputEnabled() || pChild->IsInModalMode()))
+ {
+ ImplHandleMouseFloatMode( pChild, aMousePos, nCode, nSVEvent, bMouseLeave );
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ {
+ ImplHandleMouseHelpRequest( pChild, aMousePos );
+ if( pWinFrameData->mpMouseMoveWin.get() != pChild )
+ nMode |= MouseEventModifiers::ENTERWINDOW;
+ }
+
+ // Call the hook also, if Window is disabled
+
+ if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ return true;
+ else
+ {
+ // Set normal MousePointer for disabled windows
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ ImplSetMousePointer( pChild );
+
+ return false;
+ }
+ }
+
+ // End ExtTextInput-Mode, if the user click in the same TopLevel Window
+ if (pSVData->mpWinData->mpExtTextInputWin
+ && ((nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN)
+ || (nSVEvent == MouseNotifyEvent::MOUSEBUTTONUP)))
+ pSVData->mpWinData->mpExtTextInputWin->EndExtTextInput();
+ }
+
+ // determine mouse event data
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ {
+ // check if MouseMove belongs to same window and if the
+ // status did not change
+ if ( pChild )
+ {
+ Point aChildMousePos = pChild->ImplFrameToOutput( aMousePos );
+ if ( !bMouseLeave &&
+ (pChild == pWinFrameData->mpMouseMoveWin) &&
+ (aChildMousePos.X() == pWinFrameData->mnLastMouseWinX) &&
+ (aChildMousePos.Y() == pWinFrameData->mnLastMouseWinY) &&
+ (nOldCode == pWinFrameData->mnMouseCode) )
+ {
+ // set mouse pointer anew, as it could have changed
+ // due to the mode switch
+ ImplSetMousePointer( pChild );
+ return false;
+ }
+
+ pWinFrameData->mnLastMouseWinX = aChildMousePos.X();
+ pWinFrameData->mnLastMouseWinY = aChildMousePos.Y();
+ }
+
+ // mouse click
+ nClicks = pWinFrameData->mnClickCount;
+
+ // call Start-Drag handler if required
+ // Warning: should be called before Move, as otherwise during
+ // fast mouse movements the applications move to the selection state
+ vcl::Window* pMouseDownWin = pWinFrameData->mpMouseDownWin;
+ if ( pMouseDownWin )
+ {
+ // check for matching StartDrag mode. We only compare
+ // the status of the mouse buttons, such that e. g. Mod1 can
+ // change immediately to the copy mode
+ const MouseSettings& rMSettings = pMouseDownWin->GetSettings().GetMouseSettings();
+ if ( (nCode & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)) ==
+ (MouseSettings::GetStartDragCode() & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)) )
+ {
+ if ( !pMouseDownWin->ImplGetFrameData()->mbStartDragCalled )
+ {
+ tools::Long nDragW = rMSettings.GetStartDragWidth();
+ tools::Long nDragH = rMSettings.GetStartDragHeight();
+ //long nMouseX = nX;
+ //long nMouseY = nY;
+ tools::Long nMouseX = aMousePos.X(); // #106074# use the possibly re-mirrored coordinates (RTL) ! nX,nY are unmodified !
+ tools::Long nMouseY = aMousePos.Y();
+ if ( (((nMouseX-nDragW) > pMouseDownWin->ImplGetFrameData()->mnFirstMouseX) ||
+ ((nMouseX+nDragW) < pMouseDownWin->ImplGetFrameData()->mnFirstMouseX)) ||
+ (((nMouseY-nDragH) > pMouseDownWin->ImplGetFrameData()->mnFirstMouseY) ||
+ ((nMouseY+nDragH) < pMouseDownWin->ImplGetFrameData()->mnFirstMouseY)) )
+ {
+ pMouseDownWin->ImplGetFrameData()->mbStartDragCalled = true;
+
+ // Check if drag source provides its own recognizer
+ if( pMouseDownWin->ImplGetFrameData()->mbInternalDragGestureRecognizer )
+ {
+ // query DropTarget from child window
+ css::uno::Reference< css::datatransfer::dnd::XDragGestureRecognizer > xDragGestureRecognizer(
+ pMouseDownWin->ImplGetWindowImpl()->mxDNDListenerContainer,
+ css::uno::UNO_QUERY );
+
+ if( xDragGestureRecognizer.is() )
+ {
+ // retrieve mouse position relative to mouse down window
+ Point relLoc = pMouseDownWin->ImplFrameToOutput( Point(
+ pMouseDownWin->ImplGetFrameData()->mnFirstMouseX,
+ pMouseDownWin->ImplGetFrameData()->mnFirstMouseY ) );
+
+ // create a UNO mouse event out of the available data
+ css::awt::MouseEvent aMouseEvent( static_cast < css::uno::XInterface * > ( nullptr ),
+#ifdef MACOSX
+ nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3),
+#else
+ nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2),
+#endif
+ nCode & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE),
+ nMouseX,
+ nMouseY,
+ nClicks,
+ false );
+
+ SolarMutexReleaser aReleaser;
+
+ // FIXME: where do I get Action from ?
+ css::uno::Reference< css::datatransfer::dnd::XDragSource > xDragSource = pMouseDownWin->GetDragSource();
+
+ if( xDragSource.is() )
+ {
+ static_cast < DNDListenerContainer * > ( xDragGestureRecognizer.get() )->fireDragGestureEvent( 0,
+ relLoc.X(), relLoc.Y(), xDragSource, css::uno::Any( aMouseEvent ) );
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ pMouseDownWin->ImplGetFrameData()->mbStartDragCalled = true;
+ }
+
+ if (xWindow->isDisposed())
+ return true;
+ // test for mouseleave and mouseenter
+ VclPtr<vcl::Window> pMouseMoveWin = pWinFrameData->mpMouseMoveWin;
+ if ( pChild != pMouseMoveWin )
+ {
+ if ( pMouseMoveWin )
+ {
+ Point aLeaveMousePos = pMouseMoveWin->ImplFrameToOutput( aMousePos );
+ MouseEvent aMLeaveEvt( aLeaveMousePos, nClicks, nMode | MouseEventModifiers::LEAVEWINDOW, nCode, nCode );
+ NotifyEvent aNLeaveEvt( MouseNotifyEvent::MOUSEMOVE, pMouseMoveWin, &aMLeaveEvt );
+ pWinFrameData->mbInMouseMove = true;
+ pMouseMoveWin->ImplGetWinData()->mbMouseOver = false;
+
+ // A MouseLeave can destroy this window
+ if ( !ImplCallPreNotify( aNLeaveEvt ) )
+ {
+ pMouseMoveWin->MouseMove( aMLeaveEvt );
+ if( !pMouseMoveWin->isDisposed() )
+ aNLeaveEvt.GetWindow()->ImplNotifyKeyMouseCommandEventListeners( aNLeaveEvt );
+ }
+
+ pWinFrameData->mpMouseMoveWin = nullptr;
+ pWinFrameData->mbInMouseMove = false;
+
+ if ( pChild && pChild->isDisposed() )
+ pChild = nullptr;
+ if ( pMouseMoveWin->isDisposed() )
+ return true;
+ }
+
+ nMode |= MouseEventModifiers::ENTERWINDOW;
+ }
+ pWinFrameData->mpMouseMoveWin = pChild;
+ if( pChild )
+ pChild->ImplGetWinData()->mbMouseOver = true;
+
+ // MouseLeave
+ if ( !pChild )
+ return false;
+ }
+ else
+ {
+ if (pChild)
+ {
+ // mouse click
+ if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ const MouseSettings& rMSettings = pChild->GetSettings().GetMouseSettings();
+ sal_uInt64 nDblClkTime = rMSettings.GetDoubleClickTime();
+ tools::Long nDblClkW = rMSettings.GetDoubleClickWidth();
+ tools::Long nDblClkH = rMSettings.GetDoubleClickHeight();
+ //long nMouseX = nX;
+ //long nMouseY = nY;
+ tools::Long nMouseX = aMousePos.X(); // #106074# use the possibly re-mirrored coordinates (RTL) ! nX,nY are unmodified !
+ tools::Long nMouseY = aMousePos.Y();
+
+ if ( (pChild == pChild->ImplGetFrameData()->mpMouseDownWin) &&
+ (nCode == pChild->ImplGetFrameData()->mnFirstMouseCode) &&
+ ((nMsgTime-pChild->ImplGetFrameData()->mnMouseDownTime) < nDblClkTime) &&
+ ((nMouseX-nDblClkW) <= pChild->ImplGetFrameData()->mnFirstMouseX) &&
+ ((nMouseX+nDblClkW) >= pChild->ImplGetFrameData()->mnFirstMouseX) &&
+ ((nMouseY-nDblClkH) <= pChild->ImplGetFrameData()->mnFirstMouseY) &&
+ ((nMouseY+nDblClkH) >= pChild->ImplGetFrameData()->mnFirstMouseY) )
+ {
+ pChild->ImplGetFrameData()->mnClickCount++;
+ pChild->ImplGetFrameData()->mbStartDragCalled = true;
+ }
+ else
+ {
+ pChild->ImplGetFrameData()->mpMouseDownWin = pChild;
+ pChild->ImplGetFrameData()->mnClickCount = 1;
+ pChild->ImplGetFrameData()->mnFirstMouseX = nMouseX;
+ pChild->ImplGetFrameData()->mnFirstMouseY = nMouseY;
+ pChild->ImplGetFrameData()->mnFirstMouseCode = nCode;
+ pChild->ImplGetFrameData()->mbStartDragCalled = (nCode & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)) !=
+ (MouseSettings::GetStartDragCode() & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE));
+ }
+ pChild->ImplGetFrameData()->mnMouseDownTime = nMsgTime;
+ }
+ nClicks = pChild->ImplGetFrameData()->mnClickCount;
+ }
+
+ pSVData->maAppData.mnLastInputTime = tools::Time::GetSystemTicks();
+ }
+
+ SAL_WARN_IF( !pChild, "vcl", "ImplHandleMouseEvent: pChild == NULL" );
+
+ if (!pChild)
+ return false;
+
+ // create mouse event
+ Point aChildPos = pChild->ImplFrameToOutput( aMousePos );
+ MouseEvent aMEvt( aChildPos, nClicks, nMode, nCode, nCode );
+
+
+ // tracking window gets the mouse events
+ if (pSVData->mpWinData->mpTrackWin)
+ pChild = pSVData->mpWinData->mpTrackWin;
+
+ // handle FloatingMode
+ if (!pSVData->mpWinData->mpTrackWin && pSVData->mpWinData->mpFirstFloat)
+ {
+ if ( ImplHandleMouseFloatMode( pChild, aMousePos, nCode, nSVEvent, bMouseLeave ) )
+ {
+ if ( !pChild->isDisposed() )
+ {
+ pChild->ImplGetFrameData()->mbStartDragCalled = true;
+ }
+ return true;
+ }
+ }
+
+ // call handler
+ bool bCallHelpRequest = true;
+ SAL_WARN_IF( !pChild, "vcl", "ImplHandleMouseEvent: pChild is NULL" );
+
+ if (!pChild)
+ return false;
+
+ NotifyEvent aNEvt( nSVEvent, pChild, &aMEvt );
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ pChild->ImplGetFrameData()->mbInMouseMove = true;
+
+ // bring window into foreground on mouseclick
+ if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ if (!pSVData->mpWinData->mpFirstFloat
+ && // totop for floating windows in popup would change the focus and would close them immediately
+ !(pChild->ImplGetFrameWindow()->GetStyle()
+ & WB_OWNERDRAWDECORATION)) // ownerdrawdecorated windows must never grab focus
+ pChild->ToTop();
+ if ( pChild->isDisposed() )
+ return true;
+ }
+
+ if ( ImplCallPreNotify( aNEvt ) || pChild->isDisposed() )
+ bRet = true;
+ else
+ {
+ bRet = false;
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ {
+ if (pSVData->mpWinData->mpTrackWin)
+ {
+ TrackingEvent aTEvt( aMEvt );
+ pChild->Tracking( aTEvt );
+ if ( !pChild->isDisposed() )
+ {
+ // When ScrollRepeat, we restart the timer
+ if (pSVData->mpWinData->mpTrackTimer
+ && (pSVData->mpWinData->mnTrackFlags & StartTrackingFlags::ScrollRepeat))
+ pSVData->mpWinData->mpTrackTimer->Start();
+ }
+ bCallHelpRequest = false;
+ bRet = true;
+ }
+ else
+ {
+ if( pChild->isDisposed() )
+ bCallHelpRequest = false;
+ else
+ {
+ // if the MouseMove handler changes the help window's visibility
+ // the HelpRequest handler should not be called anymore
+ vcl::Window* pOldHelpTextWin = ImplGetSVHelpData().mpHelpWin;
+ pChild->MouseMove( aMEvt );
+ if ( pOldHelpTextWin != ImplGetSVHelpData().mpHelpWin )
+ bCallHelpRequest = false;
+ }
+ }
+ }
+ else if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ if ( pSVData->mpWinData->mpTrackWin )
+ bRet = true;
+ else
+ {
+ pChild->ImplGetWindowImpl()->mbMouseButtonDown = false;
+ pChild->MouseButtonDown( aMEvt );
+ }
+ }
+ else
+ {
+ if (pSVData->mpWinData->mpTrackWin)
+ {
+ pChild->EndTracking();
+ bRet = true;
+ }
+ else
+ {
+ pChild->ImplGetWindowImpl()->mbMouseButtonUp = false;
+ pChild->MouseButtonUp( aMEvt );
+ }
+ }
+
+ assert(aNEvt.GetWindow() == pChild);
+
+ if (!pChild->isDisposed())
+ pChild->ImplNotifyKeyMouseCommandEventListeners( aNEvt );
+ }
+
+ if (pChild->isDisposed())
+ return true;
+
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ pChild->ImplGetWindowImpl()->mpFrameData->mbInMouseMove = false;
+
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ {
+ if ( bCallHelpRequest && !ImplGetSVHelpData().mbKeyboardHelp )
+ ImplHandleMouseHelpRequest( pChild, pChild->OutputToScreenPixel( aMEvt.GetPosPixel() ) );
+ bRet = true;
+ }
+ else if ( !bRet )
+ {
+ if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN )
+ {
+ if ( !pChild->ImplGetWindowImpl()->mbMouseButtonDown )
+ bRet = true;
+ }
+ else
+ {
+ if ( !pChild->ImplGetWindowImpl()->mbMouseButtonUp )
+ bRet = true;
+ }
+ }
+
+ if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE )
+ {
+ // set new mouse pointer
+ if ( !bMouseLeave )
+ ImplSetMousePointer( pChild );
+ }
+ else if ( (nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN) || (nSVEvent == MouseNotifyEvent::MOUSEBUTTONUP) )
+ {
+ // Command-Events
+ if ( /*!bRet &&*/ (nClicks == 1) && (nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN) &&
+ (nCode == MOUSE_MIDDLE) )
+ {
+ MouseMiddleButtonAction nMiddleAction = pChild->GetSettings().GetMouseSettings().GetMiddleButtonAction();
+ if ( nMiddleAction == MouseMiddleButtonAction::AutoScroll )
+ bRet = !ImplCallCommand( pChild, CommandEventId::StartAutoScroll, nullptr, true, &aChildPos );
+ else if ( nMiddleAction == MouseMiddleButtonAction::PasteSelection )
+ bRet = !ImplCallCommand( pChild, CommandEventId::PasteSelection, nullptr, true, &aChildPos );
+ }
+ else
+ {
+ // ContextMenu
+ if ( (nCode == MouseSettings::GetContextMenuCode()) &&
+ (nClicks == MouseSettings::GetContextMenuClicks()) )
+ {
+ bool bContextMenu = (nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN);
+ if ( bContextMenu )
+ {
+ if( pSVData->maAppData.mpActivePopupMenu )
+ {
+ /* #i34277# there already is a context menu open
+ * that was probably just closed with EndPopupMode.
+ * We need to give the eventual corresponding
+ * PopupMenu::Execute a chance to end properly.
+ * Therefore delay context menu command and
+ * issue only after popping one frame of the
+ * Yield stack.
+ */
+ ContextMenuEvent* pEv = new ContextMenuEvent;
+ pEv->pWindow = pChild;
+ pEv->aChildPos = aChildPos;
+ Application::PostUserEvent( Link<void*,void>( pEv, ContextMenuEventLink ) );
+ }
+ else
+ bRet = ! ImplCallCommand( pChild, CommandEventId::ContextMenu, nullptr, true, &aChildPos );
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+bool ImplLOKHandleMouseEvent(const VclPtr<vcl::Window>& xWindow, MouseNotifyEvent nEvent, bool /*bMouseLeave*/,
+ tools::Long nX, tools::Long nY, sal_uInt64 /*nMsgTime*/,
+ sal_uInt16 nCode, MouseEventModifiers nMode, sal_uInt16 nClicks)
+{
+ Point aMousePos(nX, nY);
+
+ if (!xWindow)
+ return false;
+
+ if (xWindow->isDisposed())
+ return false;
+
+ ImplFrameData* pFrameData = xWindow->ImplGetFrameData();
+ if (!pFrameData)
+ return false;
+
+ Point aWinPos = xWindow->ImplFrameToOutput(aMousePos);
+
+ pFrameData->mnLastMouseX = nX;
+ pFrameData->mnLastMouseY = nY;
+ pFrameData->mnClickCount = nClicks;
+ pFrameData->mnMouseCode = nCode;
+ pFrameData->mbMouseIn = false;
+
+ vcl::Window* pDragWin = pFrameData->mpMouseDownWin;
+ if (pDragWin &&
+ nEvent == MouseNotifyEvent::MOUSEMOVE &&
+ pFrameData->mbDragging)
+ {
+ css::uno::Reference<css::datatransfer::dnd::XDropTargetDragContext> xDropTargetDragContext =
+ new GenericDropTargetDragContext();
+ css::uno::Reference<css::datatransfer::dnd::XDropTarget> xDropTarget(
+ pDragWin->ImplGetWindowImpl()->mxDNDListenerContainer, css::uno::UNO_QUERY);
+
+ if (!xDropTarget.is() ||
+ !xDropTargetDragContext.is() ||
+ (nCode & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)) !=
+ (MouseSettings::GetStartDragCode() & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)))
+ {
+ pFrameData->mbStartDragCalled = pFrameData->mbDragging = false;
+ return false;
+ }
+
+ static_cast<DNDListenerContainer *>(xDropTarget.get())->fireDragOverEvent(
+ xDropTargetDragContext,
+ css::datatransfer::dnd::DNDConstants::ACTION_MOVE,
+ aWinPos.X(),
+ aWinPos.Y(),
+ (css::datatransfer::dnd::DNDConstants::ACTION_COPY |
+ css::datatransfer::dnd::DNDConstants::ACTION_MOVE |
+ css::datatransfer::dnd::DNDConstants::ACTION_LINK));
+
+ return true;
+ }
+
+ if (pDragWin &&
+ nEvent == MouseNotifyEvent::MOUSEBUTTONUP &&
+ pFrameData->mbDragging)
+ {
+ css::uno::Reference<css::datatransfer::XTransferable> xTransfer;
+ css::uno::Reference<css::datatransfer::dnd::XDropTargetDropContext> xDropTargetDropContext =
+ new GenericDropTargetDropContext();
+ css::uno::Reference<css::datatransfer::dnd::XDropTarget> xDropTarget(
+ pDragWin->ImplGetWindowImpl()->mxDNDListenerContainer, css::uno::UNO_QUERY);
+
+ if (!xDropTarget.is() || !xDropTargetDropContext.is())
+ {
+ pFrameData->mbStartDragCalled = pFrameData->mbDragging = false;
+ return false;
+ }
+
+ Point dragOverPos = pDragWin->ImplFrameToOutput(aMousePos);
+ static_cast<DNDListenerContainer *>(xDropTarget.get())->fireDropEvent(
+ xDropTargetDropContext,
+ css::datatransfer::dnd::DNDConstants::ACTION_MOVE,
+ dragOverPos.X(),
+ dragOverPos.Y(),
+ (css::datatransfer::dnd::DNDConstants::ACTION_COPY |
+ css::datatransfer::dnd::DNDConstants::ACTION_MOVE |
+ css::datatransfer::dnd::DNDConstants::ACTION_LINK),
+ xTransfer);
+
+ pFrameData->mbStartDragCalled = pFrameData->mbDragging = false;
+ return true;
+ }
+
+ if (pFrameData->mbDragging)
+ {
+ // wrong status, reset
+ pFrameData->mbStartDragCalled = pFrameData->mbDragging = false;
+ return false;
+ }
+
+ vcl::Window* pDownWin = pFrameData->mpMouseDownWin;
+ if (pDownWin && nEvent == MouseNotifyEvent::MOUSEMOVE)
+ {
+ const MouseSettings& aSettings = pDownWin->GetSettings().GetMouseSettings();
+ if ((nCode & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)) ==
+ (MouseSettings::GetStartDragCode() & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)) )
+ {
+ if (!pFrameData->mbStartDragCalled)
+ {
+ tools::Long nDragWidth = aSettings.GetStartDragWidth();
+ tools::Long nDragHeight = aSettings.GetStartDragHeight();
+ tools::Long nMouseX = aMousePos.X();
+ tools::Long nMouseY = aMousePos.Y();
+
+ if ((((nMouseX - nDragWidth) > pFrameData->mnFirstMouseX) ||
+ ((nMouseX + nDragWidth) < pFrameData->mnFirstMouseX)) ||
+ (((nMouseY - nDragHeight) > pFrameData->mnFirstMouseY) ||
+ ((nMouseY + nDragHeight) < pFrameData->mnFirstMouseY)))
+ {
+ pFrameData->mbStartDragCalled = true;
+
+ if (pFrameData->mbInternalDragGestureRecognizer)
+ {
+ // query DropTarget from child window
+ css::uno::Reference< css::datatransfer::dnd::XDragGestureRecognizer > xDragGestureRecognizer(
+ pDownWin->ImplGetWindowImpl()->mxDNDListenerContainer,
+ css::uno::UNO_QUERY );
+
+ if (xDragGestureRecognizer.is())
+ {
+ // create a UNO mouse event out of the available data
+ css::awt::MouseEvent aEvent(
+ static_cast < css::uno::XInterface * > ( nullptr ),
+ #ifdef MACOSX
+ nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3),
+ #else
+ nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2),
+ #endif
+ nCode & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE),
+ nMouseX,
+ nMouseY,
+ nClicks,
+ false);
+ css::uno::Reference< css::datatransfer::dnd::XDragSource > xDragSource =
+ pDownWin->GetDragSource();
+
+ if (xDragSource.is())
+ {
+ static_cast<DNDListenerContainer *>(xDragGestureRecognizer.get())->
+ fireDragGestureEvent(
+ 0,
+ aWinPos.X(),
+ aWinPos.Y(),
+ xDragSource,
+ css::uno::Any(aEvent));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ MouseEvent aMouseEvent(aWinPos, nClicks, nMode, nCode, nCode);
+ if (nEvent == MouseNotifyEvent::MOUSEMOVE)
+ {
+ if (pFrameData->mpTrackWin)
+ {
+ TrackingEvent aTrackingEvent(aMouseEvent);
+ pFrameData->mpTrackWin->Tracking(aTrackingEvent);
+ }
+ else
+ xWindow->MouseMove(aMouseEvent);
+ }
+ else if (nEvent == MouseNotifyEvent::MOUSEBUTTONDOWN &&
+ !pFrameData->mpTrackWin)
+ {
+ pFrameData->mpMouseDownWin = xWindow;
+ pFrameData->mnFirstMouseX = aMousePos.X();
+ pFrameData->mnFirstMouseY = aMousePos.Y();
+
+ xWindow->MouseButtonDown(aMouseEvent);
+ }
+ else
+ {
+ if (pFrameData->mpTrackWin)
+ {
+ pFrameData->mpTrackWin->EndTracking();
+ }
+
+ pFrameData->mpMouseDownWin = nullptr;
+ pFrameData->mpMouseMoveWin = nullptr;
+ pFrameData->mbStartDragCalled = false;
+ xWindow->MouseButtonUp(aMouseEvent);
+ }
+
+ if (nEvent == MouseNotifyEvent::MOUSEBUTTONDOWN)
+ {
+ // ContextMenu
+ if ( (nCode == MouseSettings::GetContextMenuCode()) &&
+ (nClicks == MouseSettings::GetContextMenuClicks()) )
+ {
+ ImplCallCommand(xWindow, CommandEventId::ContextMenu, nullptr, true, &aWinPos);
+ }
+ }
+
+ return true;
+}
+
+static vcl::Window* ImplGetKeyInputWindow( vcl::Window* pWindow )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // determine last input time
+ pSVData->maAppData.mnLastInputTime = tools::Time::GetSystemTicks();
+
+ // #127104# workaround for destroyed windows
+ if( pWindow->ImplGetWindowImpl() == nullptr )
+ return nullptr;
+
+ // find window - is every time the window which has currently the
+ // focus or the last time the focus.
+
+ // the first floating window always has the focus, try it, or any parent floating windows, first
+ vcl::Window* pChild = pSVData->mpWinData->mpFirstFloat;
+ while (pChild)
+ {
+ if (pChild->ImplGetWindowImpl())
+ {
+ if (pChild->ImplGetWindowImpl()->mbFloatWin)
+ {
+ if (static_cast<FloatingWindow *>(pChild)->GrabsFocus())
+ break;
+ }
+ else if (pChild->ImplGetWindowImpl()->mbDockWin)
+ {
+ vcl::Window* pParent = pChild->GetWindow(GetWindowType::RealParent);
+ if (pParent && pParent->ImplGetWindowImpl()->mbFloatWin &&
+ static_cast<FloatingWindow *>(pParent)->GrabsFocus())
+ break;
+ }
+ }
+ pChild = pChild->GetParent();
+ }
+
+ if (!pChild)
+ pChild = pWindow;
+
+ pChild = pChild->ImplGetWindowImpl() && pChild->ImplGetWindowImpl()->mpFrameData ? pChild->ImplGetWindowImpl()->mpFrameData->mpFocusWin.get() : nullptr;
+
+ // no child - then no input
+ if ( !pChild )
+ return nullptr;
+
+ // We call also KeyInput if we haven't the focus, because on Unix
+ // system this is often the case when a Lookup Choice Window has
+ // the focus - because this windows send the KeyInput directly to
+ // the window without resetting the focus
+
+ // no keyinput to disabled windows
+ if ( !pChild->IsEnabled() || !pChild->IsInputEnabled() || pChild->IsInModalMode() )
+ return nullptr;
+
+ return pChild;
+}
+
+static bool ImplHandleKey( vcl::Window* pWindow, MouseNotifyEvent nSVEvent,
+ sal_uInt16 nKeyCode, sal_uInt16 nCharCode, sal_uInt16 nRepeat, bool bForward )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::KeyCode aKeyCode( nKeyCode, nKeyCode );
+ sal_uInt16 nEvCode = aKeyCode.GetCode();
+
+ // allow application key listeners to remove the key event
+ // but make sure we're not forwarding external KeyEvents, (ie where bForward is false)
+ // because those are coming back from the listener itself and MUST be processed
+ if( bForward )
+ {
+ VclEventId nVCLEvent;
+ switch( nSVEvent )
+ {
+ case MouseNotifyEvent::KEYINPUT:
+ nVCLEvent = VclEventId::WindowKeyInput;
+ break;
+ case MouseNotifyEvent::KEYUP:
+ nVCLEvent = VclEventId::WindowKeyUp;
+ break;
+ default:
+ nVCLEvent = VclEventId::NONE;
+ break;
+ }
+ KeyEvent aKeyEvent(static_cast<sal_Unicode>(nCharCode), aKeyCode, nRepeat);
+ if (nVCLEvent != VclEventId::NONE && Application::HandleKey(nVCLEvent, pWindow, &aKeyEvent))
+ return true;
+ }
+
+ bool bCtrlF6 = (aKeyCode.GetCode() == KEY_F6) && aKeyCode.IsMod1();
+
+ // determine last input time
+ pSVData->maAppData.mnLastInputTime = tools::Time::GetSystemTicks();
+
+ // handle tracking window
+ if ( nSVEvent == MouseNotifyEvent::KEYINPUT )
+ {
+ if ( ImplGetSVHelpData().mbExtHelpMode )
+ {
+ Help::EndExtHelp();
+ if ( nEvCode == KEY_ESCAPE )
+ return true;
+ }
+ if ( ImplGetSVHelpData().mpHelpWin )
+ ImplDestroyHelpWindow( false );
+
+ // AutoScrollMode
+ if (pSVData->mpWinData->mpAutoScrollWin)
+ {
+ pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll();
+ if ( nEvCode == KEY_ESCAPE )
+ return true;
+ }
+
+ if (pSVData->mpWinData->mpTrackWin)
+ {
+ sal_uInt16 nOrigCode = aKeyCode.GetCode();
+
+ if ( nOrigCode == KEY_ESCAPE )
+ {
+ pSVData->mpWinData->mpTrackWin->EndTracking( TrackingEventFlags::Cancel | TrackingEventFlags::Key );
+ if (pSVData->mpWinData->mpFirstFloat)
+ {
+ FloatingWindow* pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat();
+ if ( !(pLastLevelFloat->GetPopupModeFlags() & FloatWinPopupFlags::NoKeyClose) )
+ {
+ sal_uInt16 nEscCode = aKeyCode.GetCode();
+
+ if ( nEscCode == KEY_ESCAPE )
+ pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll );
+ }
+ }
+ return true;
+ }
+ else if ( nOrigCode == KEY_RETURN )
+ {
+ pSVData->mpWinData->mpTrackWin->EndTracking( TrackingEventFlags::Key );
+ return true;
+ }
+ else
+ return true;
+ }
+
+ // handle FloatingMode
+ if (pSVData->mpWinData->mpFirstFloat)
+ {
+ FloatingWindow* pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat();
+ if ( !(pLastLevelFloat->GetPopupModeFlags() & FloatWinPopupFlags::NoKeyClose) )
+ {
+ sal_uInt16 nCode = aKeyCode.GetCode();
+
+ if ( (nCode == KEY_ESCAPE) || bCtrlF6)
+ {
+ pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll );
+ if( !bCtrlF6 )
+ return true;
+ }
+ }
+ }
+
+ // test for accel
+ if ( pSVData->maAppData.mpAccelMgr )
+ {
+ if ( pSVData->maAppData.mpAccelMgr->IsAccelKey( aKeyCode ) )
+ return true;
+ }
+ }
+
+ // find window
+ VclPtr<vcl::Window> pChild = ImplGetKeyInputWindow( pWindow );
+ if ( !pChild )
+ return false;
+
+ // #i1820# use locale specific decimal separator
+ if (nEvCode == KEY_DECIMAL)
+ {
+ // tdf#138932: don't modify the meaning of the key for password box
+ bool bPass = false;
+ if (auto pEdit = dynamic_cast<Edit*>(pChild.get()))
+ bPass = pEdit->IsPassword();
+ if (!bPass && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
+ {
+ OUString aSep(pWindow->GetSettings().GetLocaleDataWrapper().getNumDecimalSep());
+ nCharCode = static_cast<sal_uInt16>(aSep[0]);
+ }
+ }
+
+ // RTL: mirror cursor keys
+ if( (aKeyCode.GetCode() == KEY_LEFT || aKeyCode.GetCode() == KEY_RIGHT) &&
+ pChild->IsRTLEnabled() && pChild->GetOutDev()->HasMirroredGraphics() )
+ aKeyCode = vcl::KeyCode( aKeyCode.GetCode() == KEY_LEFT ? KEY_RIGHT : KEY_LEFT, aKeyCode.GetModifier() );
+
+ KeyEvent aKeyEvt( static_cast<sal_Unicode>(nCharCode), aKeyCode, nRepeat );
+ NotifyEvent aNotifyEvt( nSVEvent, pChild, &aKeyEvt );
+ bool bKeyPreNotify = ImplCallPreNotify( aNotifyEvt );
+ bool bRet = true;
+
+ if ( !bKeyPreNotify && !pChild->isDisposed() )
+ {
+ if ( nSVEvent == MouseNotifyEvent::KEYINPUT )
+ {
+ UITestLogger::getInstance().logKeyInput(pChild, aKeyEvt);
+ pChild->ImplGetWindowImpl()->mbKeyInput = false;
+ pChild->KeyInput( aKeyEvt );
+ }
+ else
+ {
+ pChild->ImplGetWindowImpl()->mbKeyUp = false;
+ pChild->KeyUp( aKeyEvt );
+ }
+ if( !pChild->isDisposed() )
+ aNotifyEvt.GetWindow()->ImplNotifyKeyMouseCommandEventListeners( aNotifyEvt );
+ }
+
+ if ( pChild->isDisposed() )
+ return true;
+
+ if ( nSVEvent == MouseNotifyEvent::KEYINPUT )
+ {
+ if ( !bKeyPreNotify && pChild->ImplGetWindowImpl()->mbKeyInput )
+ {
+ sal_uInt16 nCode = aKeyCode.GetCode();
+
+ // #101999# is focus in or below toolbox
+ bool bToolboxFocus=false;
+ if( (nCode == KEY_F1) && aKeyCode.IsShift() )
+ {
+ vcl::Window *pWin = pWindow->ImplGetWindowImpl()->mpFrameData->mpFocusWin;
+ while( pWin )
+ {
+ if( pWin->ImplGetWindowImpl()->mbToolBox )
+ {
+ bToolboxFocus = true;
+ break;
+ }
+ else
+ pWin = pWin->GetParent();
+ }
+ }
+
+ // ContextMenu
+ if ( (nCode == KEY_CONTEXTMENU) || ((nCode == KEY_F10) && aKeyCode.IsShift() && !aKeyCode.IsMod1() && !aKeyCode.IsMod2() ) )
+ bRet = !ImplCallCommand( pChild, CommandEventId::ContextMenu );
+ else if ( ( (nCode == KEY_F2) && aKeyCode.IsShift() ) || ( (nCode == KEY_F1) && aKeyCode.IsMod1() ) ||
+ // #101999# no active help when focus in toolbox, simulate BalloonHelp instead
+ ( (nCode == KEY_F1) && aKeyCode.IsShift() && bToolboxFocus ) )
+ {
+ // TipHelp via Keyboard (Shift-F2 or Ctrl-F1)
+ // simulate mouseposition at center of window
+
+ Size aSize = pChild->GetOutDev()->GetOutputSize();
+ Point aPos( aSize.getWidth()/2, aSize.getHeight()/2 );
+ aPos = pChild->OutputToScreenPixel( aPos );
+
+ HelpEvent aHelpEvent( aPos, HelpEventMode::BALLOON );
+ aHelpEvent.SetKeyboardActivated( true );
+ ImplGetSVHelpData().mbSetKeyboardHelp = true;
+ pChild->RequestHelp( aHelpEvent );
+ ImplGetSVHelpData().mbSetKeyboardHelp = false;
+ }
+ else if ( (nCode == KEY_F1) || (nCode == KEY_HELP) )
+ {
+ if ( !aKeyCode.GetModifier() )
+ {
+ if ( ImplGetSVHelpData().mbContextHelp )
+ {
+ Point aMousePos = pChild->OutputToScreenPixel( pChild->GetPointerPosPixel() );
+ HelpEvent aHelpEvent( aMousePos, HelpEventMode::CONTEXT );
+ pChild->RequestHelp( aHelpEvent );
+ }
+ else
+ bRet = false;
+ }
+ else if ( aKeyCode.IsShift() )
+ {
+ if ( ImplGetSVHelpData().mbExtHelp )
+ Help::StartExtHelp();
+ else
+ bRet = false;
+ }
+ }
+ else
+ bRet = false;
+ }
+ }
+ else
+ {
+ if ( !bKeyPreNotify && pChild->ImplGetWindowImpl()->mbKeyUp )
+ bRet = false;
+ }
+
+ // #105591# send keyinput to parent if we are a floating window and the key was not processed yet
+ if( !bRet && pWindow->ImplGetWindowImpl() && pWindow->ImplGetWindowImpl()->mbFloatWin && pWindow->GetParent() && (pWindow->ImplGetWindowImpl()->mpFrame != pWindow->GetParent()->ImplGetWindowImpl()->mpFrame) )
+ {
+ pChild = pWindow->GetParent();
+
+ // call handler
+ NotifyEvent aNEvt( nSVEvent, pChild, &aKeyEvt );
+ bool bPreNotify = ImplCallPreNotify( aNEvt );
+ if ( pChild->isDisposed() )
+ return true;
+
+ if ( !bPreNotify )
+ {
+ if ( nSVEvent == MouseNotifyEvent::KEYINPUT )
+ {
+ pChild->ImplGetWindowImpl()->mbKeyInput = false;
+ pChild->KeyInput( aKeyEvt );
+ }
+ else
+ {
+ pChild->ImplGetWindowImpl()->mbKeyUp = false;
+ pChild->KeyUp( aKeyEvt );
+ }
+
+ if( !pChild->isDisposed() )
+ aNEvt.GetWindow()->ImplNotifyKeyMouseCommandEventListeners( aNEvt );
+ if ( pChild->isDisposed() )
+ return true;
+ }
+
+ if( bPreNotify || !pChild->ImplGetWindowImpl()->mbKeyInput )
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+static bool ImplHandleExtTextInput( vcl::Window* pWindow,
+ const OUString& rText,
+ const ExtTextInputAttr* pTextAttr,
+ sal_Int32 nCursorPos, sal_uInt16 nCursorFlags )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pChild = nullptr;
+
+ int nTries = 200;
+ while( nTries-- )
+ {
+ pChild = pSVData->mpWinData->mpExtTextInputWin;
+ if ( !pChild )
+ {
+ pChild = ImplGetKeyInputWindow( pWindow );
+ if ( !pChild )
+ return false;
+ }
+ if( !pChild->ImplGetWindowImpl()->mpFrameData->mnFocusId )
+ break;
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ SAL_WARN("vcl", "Failed to get ext text input context");
+ break;
+ }
+ Application::Yield();
+ }
+
+ // If it is the first ExtTextInput call, we inform the information
+ // and allocate the data, which we must store in this mode
+ ImplWinData* pWinData = pChild->ImplGetWinData();
+ if ( !pChild->ImplGetWindowImpl()->mbExtTextInput )
+ {
+ pChild->ImplGetWindowImpl()->mbExtTextInput = true;
+ pWinData->mpExtOldText = OUString();
+ pWinData->mpExtOldAttrAry.reset();
+ pSVData->mpWinData->mpExtTextInputWin = pChild;
+ ImplCallCommand( pChild, CommandEventId::StartExtTextInput );
+ }
+
+ // be aware of being recursively called in StartExtTextInput
+ if ( !pChild->ImplGetWindowImpl()->mbExtTextInput )
+ return false;
+
+ // Test for changes
+ bool bOnlyCursor = false;
+ sal_Int32 nMinLen = std::min( pWinData->mpExtOldText->getLength(), rText.getLength() );
+ sal_Int32 nDeltaStart = 0;
+ while ( nDeltaStart < nMinLen )
+ {
+ if ( (*pWinData->mpExtOldText)[nDeltaStart] != rText[nDeltaStart] )
+ break;
+ nDeltaStart++;
+ }
+ if ( pWinData->mpExtOldAttrAry || pTextAttr )
+ {
+ if ( !pWinData->mpExtOldAttrAry || !pTextAttr )
+ nDeltaStart = 0;
+ else
+ {
+ sal_Int32 i = 0;
+ while ( i < nDeltaStart )
+ {
+ if ( pWinData->mpExtOldAttrAry[i] != pTextAttr[i] )
+ {
+ nDeltaStart = i;
+ break;
+ }
+ i++;
+ }
+ }
+ }
+ if ( (nDeltaStart >= nMinLen) &&
+ (pWinData->mpExtOldText->getLength() == rText.getLength()) )
+ bOnlyCursor = true;
+
+ // Call Event and store the information
+ CommandExtTextInputData aData( rText, pTextAttr,
+ nCursorPos, nCursorFlags,
+ bOnlyCursor );
+ *pWinData->mpExtOldText = rText;
+ pWinData->mpExtOldAttrAry.reset();
+ if ( pTextAttr )
+ {
+ pWinData->mpExtOldAttrAry.reset( new ExtTextInputAttr[rText.getLength()] );
+ memcpy( pWinData->mpExtOldAttrAry.get(), pTextAttr, rText.getLength()*sizeof( ExtTextInputAttr ) );
+ }
+ return !ImplCallCommand( pChild, CommandEventId::ExtTextInput, &aData );
+}
+
+static bool ImplHandleEndExtTextInput()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pChild = pSVData->mpWinData->mpExtTextInputWin;
+ bool bRet = false;
+
+ if ( pChild )
+ {
+ pChild->ImplGetWindowImpl()->mbExtTextInput = false;
+ pSVData->mpWinData->mpExtTextInputWin = nullptr;
+ ImplWinData* pWinData = pChild->ImplGetWinData();
+ pWinData->mpExtOldText.reset();
+ pWinData->mpExtOldAttrAry.reset();
+ bRet = !ImplCallCommand( pChild, CommandEventId::EndExtTextInput );
+ }
+
+ return bRet;
+}
+
+static void ImplHandleExtTextInputPos( vcl::Window* pWindow,
+ tools::Rectangle& rRect, tools::Long& rInputWidth,
+ bool * pVertical )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pChild = pSVData->mpWinData->mpExtTextInputWin;
+
+ if ( !pChild )
+ pChild = ImplGetKeyInputWindow( pWindow );
+ else
+ {
+ // Test, if the Window is related to the frame
+ if ( !pWindow->ImplIsWindowOrChild( pChild ) )
+ pChild = ImplGetKeyInputWindow( pWindow );
+ }
+
+ if ( pChild )
+ {
+ const OutputDevice *pChildOutDev = pChild->GetOutDev();
+ ImplCallCommand( pChild, CommandEventId::CursorPos );
+ const tools::Rectangle* pRect = pChild->GetCursorRect();
+ if ( pRect )
+ rRect = pChildOutDev->ImplLogicToDevicePixel( *pRect );
+ else
+ {
+ vcl::Cursor* pCursor = pChild->GetCursor();
+ if ( pCursor )
+ {
+ Point aPos = pChildOutDev->ImplLogicToDevicePixel( pCursor->GetPos() );
+ Size aSize = pChild->LogicToPixel( pCursor->GetSize() );
+ if ( !aSize.Width() )
+ aSize.setWidth( pChild->GetSettings().GetStyleSettings().GetCursorSize() );
+ rRect = tools::Rectangle( aPos, aSize );
+ }
+ else
+ rRect = tools::Rectangle( Point( pChild->GetOutOffXPixel(), pChild->GetOutOffYPixel() ), Size() );
+ }
+ rInputWidth = pChild->GetOutDev()->ImplLogicWidthToDevicePixel( pChild->GetCursorExtTextInputWidth() );
+ if ( !rInputWidth )
+ rInputWidth = rRect.GetWidth();
+ }
+ if (pVertical != nullptr)
+ *pVertical
+ = pChild != nullptr && pChild->GetInputContext().GetFont().IsVertical();
+}
+
+static bool ImplHandleInputContextChange( vcl::Window* pWindow )
+{
+ vcl::Window* pChild = ImplGetKeyInputWindow( pWindow );
+ CommandInputContextData aData;
+ return !ImplCallCommand( pChild, CommandEventId::InputContextChange, &aData );
+}
+
+static bool ImplCallWheelCommand( const VclPtr<vcl::Window>& pWindow, const Point& rPos,
+ const CommandWheelData* pWheelData )
+{
+ Point aCmdMousePos = pWindow->ImplFrameToOutput( rPos );
+ CommandEvent aCEvt( aCmdMousePos, CommandEventId::Wheel, true, pWheelData );
+ NotifyEvent aNCmdEvt( MouseNotifyEvent::COMMAND, pWindow, &aCEvt );
+ bool bPreNotify = ImplCallPreNotify( aNCmdEvt );
+ if ( pWindow->isDisposed() )
+ return false;
+ if ( !bPreNotify )
+ {
+ pWindow->ImplGetWindowImpl()->mbCommand = false;
+ pWindow->Command( aCEvt );
+ if ( pWindow->isDisposed() )
+ return false;
+ if ( pWindow->ImplGetWindowImpl()->mbCommand )
+ return true;
+ }
+ return false;
+}
+
+static bool acceptableWheelScrollTarget(const vcl::Window *pMouseWindow)
+{
+ return (pMouseWindow && !pMouseWindow->isDisposed() && pMouseWindow->IsInputEnabled() && !pMouseWindow->IsInModalMode());
+}
+
+//If the last event at the same absolute screen position was handled by a
+//different window then reuse that window if the event occurs within 1/2 a
+//second, i.e. so scrolling down something like the calc sidebar that contains
+//widgets that respond to wheel events will continue to send the event to the
+//scrolling widget in favour of the widget that happens to end up under the
+//mouse.
+static bool shouldReusePreviousMouseWindow(const SalWheelMouseEvent& rPrevEvt, const SalWheelMouseEvent& rEvt)
+{
+ return (rEvt.mnX == rPrevEvt.mnX && rEvt.mnY == rPrevEvt.mnY && rEvt.mnTime-rPrevEvt.mnTime < 500/*ms*/);
+}
+
+namespace {
+
+class HandleGestureEventBase
+{
+protected:
+ ImplSVData* m_pSVData;
+ VclPtr<vcl::Window> m_pWindow;
+ Point m_aMousePos;
+
+public:
+ HandleGestureEventBase(vcl::Window *pWindow, const Point &rMousePos)
+ : m_pSVData(ImplGetSVData())
+ , m_pWindow(pWindow)
+ , m_aMousePos(rMousePos)
+ {
+ }
+ bool Setup();
+ vcl::Window* FindTarget();
+ vcl::Window* Dispatch(vcl::Window* pTarget);
+ virtual bool CallCommand(vcl::Window *pWindow, const Point &rMousePos) = 0;
+ virtual ~HandleGestureEventBase() {}
+};
+
+}
+
+bool HandleGestureEventBase::Setup()
+{
+
+ if (m_pSVData->mpWinData->mpAutoScrollWin)
+ m_pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll();
+ if (ImplGetSVHelpData().mpHelpWin)
+ ImplDestroyHelpWindow( true );
+ return !m_pWindow->isDisposed();
+}
+
+vcl::Window* HandleGestureEventBase::FindTarget()
+{
+ // first check any floating window ( eg. drop down listboxes)
+ vcl::Window *pMouseWindow = nullptr;
+
+ if (m_pSVData->mpWinData->mpFirstFloat && !m_pSVData->mpWinData->mpCaptureWin &&
+ !m_pSVData->mpWinData->mpFirstFloat->ImplIsFloatPopupModeWindow( m_pWindow ) )
+ {
+ bool bHitTestInsideRect = false;
+ pMouseWindow = m_pSVData->mpWinData->mpFirstFloat->ImplFloatHitTest( m_pWindow, m_aMousePos, bHitTestInsideRect );
+ if (!pMouseWindow)
+ pMouseWindow = m_pSVData->mpWinData->mpFirstFloat;
+ }
+ // then try the window directly beneath the mouse
+ if( !pMouseWindow )
+ {
+ pMouseWindow = m_pWindow->ImplFindWindow( m_aMousePos );
+ }
+ else
+ {
+ // transform coordinates to float window frame coordinates
+ pMouseWindow = pMouseWindow->ImplFindWindow(
+ pMouseWindow->OutputToScreenPixel(
+ pMouseWindow->AbsoluteScreenToOutputPixel(
+ m_pWindow->OutputToAbsoluteScreenPixel(
+ m_pWindow->ScreenToOutputPixel( m_aMousePos ) ) ) ) );
+ }
+
+ while (acceptableWheelScrollTarget(pMouseWindow))
+ {
+ if (pMouseWindow->IsEnabled())
+ break;
+ //try the parent if this one is disabled
+ pMouseWindow = pMouseWindow->GetParent();
+ }
+
+ return pMouseWindow;
+}
+
+vcl::Window *HandleGestureEventBase::Dispatch(vcl::Window* pMouseWindow)
+{
+ vcl::Window *pDispatchedTo = nullptr;
+
+ if (acceptableWheelScrollTarget(pMouseWindow) && pMouseWindow->IsEnabled())
+ {
+ // transform coordinates to float window frame coordinates
+ Point aRelMousePos( pMouseWindow->OutputToScreenPixel(
+ pMouseWindow->AbsoluteScreenToOutputPixel(
+ m_pWindow->OutputToAbsoluteScreenPixel(
+ m_pWindow->ScreenToOutputPixel( m_aMousePos ) ) ) ) );
+ bool bPropagate = CallCommand(pMouseWindow, aRelMousePos);
+ if (!bPropagate)
+ pDispatchedTo = pMouseWindow;
+ }
+
+ // if the command was not handled try the focus window
+ if (!pDispatchedTo)
+ {
+ vcl::Window* pFocusWindow = m_pWindow->ImplGetWindowImpl()->mpFrameData->mpFocusWin;
+ if ( pFocusWindow && (pFocusWindow != pMouseWindow) &&
+ (pFocusWindow == m_pSVData->mpWinData->mpFocusWin) )
+ {
+ // no wheel-messages to disabled windows
+ if ( pFocusWindow->IsEnabled() && pFocusWindow->IsInputEnabled() && ! pFocusWindow->IsInModalMode() )
+ {
+ // transform coordinates to focus window frame coordinates
+ Point aRelMousePos( pFocusWindow->OutputToScreenPixel(
+ pFocusWindow->AbsoluteScreenToOutputPixel(
+ m_pWindow->OutputToAbsoluteScreenPixel(
+ m_pWindow->ScreenToOutputPixel( m_aMousePos ) ) ) ) );
+ bool bPropagate = CallCommand(pFocusWindow, aRelMousePos);
+ if (!bPropagate)
+ pDispatchedTo = pMouseWindow;
+ }
+ }
+ }
+ return pDispatchedTo;
+}
+
+namespace {
+
+class HandleWheelEvent : public HandleGestureEventBase
+{
+private:
+ CommandWheelData m_aWheelData;
+public:
+ HandleWheelEvent(vcl::Window *pWindow, const SalWheelMouseEvent& rEvt)
+ : HandleGestureEventBase(pWindow, Point(rEvt.mnX, rEvt.mnY))
+ {
+ CommandWheelMode nMode;
+ sal_uInt16 nCode = rEvt.mnCode;
+ bool bHorz = rEvt.mbHorz;
+ bool bPixel = rEvt.mbDeltaIsPixel;
+ if ( nCode & KEY_MOD1 )
+ nMode = CommandWheelMode::ZOOM;
+ else if ( nCode & KEY_MOD2 )
+ nMode = CommandWheelMode::DATAZOOM;
+ else
+ {
+ nMode = CommandWheelMode::SCROLL;
+ // #i85450# interpret shift-wheel as horizontal wheel action
+ if( (nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)) == KEY_SHIFT )
+ bHorz = true;
+ }
+
+ m_aWheelData = CommandWheelData(rEvt.mnDelta, rEvt.mnNotchDelta, rEvt.mnScrollLines, nMode, nCode, bHorz, bPixel);
+
+ }
+ virtual bool CallCommand(vcl::Window *pWindow, const Point &rMousePos) override
+ {
+ return ImplCallWheelCommand(pWindow, rMousePos, &m_aWheelData);
+ }
+ bool HandleEvent(const SalWheelMouseEvent& rEvt);
+};
+
+}
+
+bool HandleWheelEvent::HandleEvent(const SalWheelMouseEvent& rEvt)
+{
+ if (!Setup())
+ return false;
+
+ VclPtr<vcl::Window> xMouseWindow = FindTarget();
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // avoid the problem that scrolling via wheel to this point brings a widget
+ // under the mouse that also accepts wheel commands, so stick with the old
+ // widget if the time gap is very small
+ if (shouldReusePreviousMouseWindow(pSVData->mpWinData->maLastWheelEvent, rEvt) &&
+ acceptableWheelScrollTarget(pSVData->mpWinData->mpLastWheelWindow))
+ {
+ xMouseWindow = pSVData->mpWinData->mpLastWheelWindow;
+ }
+
+ pSVData->mpWinData->maLastWheelEvent = rEvt;
+
+ pSVData->mpWinData->mpLastWheelWindow = Dispatch(xMouseWindow);
+
+ return pSVData->mpWinData->mpLastWheelWindow;
+}
+
+namespace {
+
+class HandleGestureEvent : public HandleGestureEventBase
+{
+public:
+ HandleGestureEvent(vcl::Window *pWindow, const Point &rMousePos)
+ : HandleGestureEventBase(pWindow, rMousePos)
+ {
+ }
+ bool HandleEvent();
+};
+
+}
+
+bool HandleGestureEvent::HandleEvent()
+{
+ if (!Setup())
+ return false;
+
+ vcl::Window *pTarget = FindTarget();
+
+ bool bHandled = Dispatch(pTarget) != nullptr;
+ return bHandled;
+}
+
+static bool ImplHandleWheelEvent(vcl::Window* pWindow, const SalWheelMouseEvent& rEvt)
+{
+ HandleWheelEvent aHandler(pWindow, rEvt);
+ return aHandler.HandleEvent(rEvt);
+}
+
+namespace {
+
+class HandleSwipeEvent : public HandleGestureEvent
+{
+private:
+ CommandSwipeData m_aSwipeData;
+public:
+ HandleSwipeEvent(vcl::Window *pWindow, const SalSwipeEvent& rEvt)
+ : HandleGestureEvent(pWindow, Point(rEvt.mnX, rEvt.mnY)),
+ m_aSwipeData(rEvt.mnVelocityX)
+ {
+ }
+ virtual bool CallCommand(vcl::Window *pWindow, const Point &/*rMousePos*/) override
+ {
+ return ImplCallCommand(pWindow, CommandEventId::Swipe, &m_aSwipeData);
+ }
+};
+
+}
+
+static bool ImplHandleSwipe(vcl::Window *pWindow, const SalSwipeEvent& rEvt)
+{
+ HandleSwipeEvent aHandler(pWindow, rEvt);
+ return aHandler.HandleEvent();
+}
+
+namespace {
+
+class HandleLongPressEvent : public HandleGestureEvent
+{
+private:
+ CommandLongPressData m_aLongPressData;
+public:
+ HandleLongPressEvent(vcl::Window *pWindow, const SalLongPressEvent& rEvt)
+ : HandleGestureEvent(pWindow, Point(rEvt.mnX, rEvt.mnY)),
+ m_aLongPressData(rEvt.mnX, rEvt.mnY)
+ {
+ }
+ virtual bool CallCommand(vcl::Window *pWindow, const Point &/*rMousePos*/) override
+ {
+ return ImplCallCommand(pWindow, CommandEventId::LongPress, &m_aLongPressData);
+ }
+};
+
+}
+
+static bool ImplHandleLongPress(vcl::Window *pWindow, const SalLongPressEvent& rEvt)
+{
+ HandleLongPressEvent aHandler(pWindow, rEvt);
+ return aHandler.HandleEvent();
+}
+
+namespace {
+
+class HandleGeneralGestureEvent : public HandleGestureEvent
+{
+private:
+ CommandGestureData m_aGestureData;
+
+public:
+ HandleGeneralGestureEvent(vcl::Window* pWindow, const SalGestureEvent& rEvent)
+ : HandleGestureEvent(pWindow, Point(rEvent.mnX, rEvent.mnY))
+ , m_aGestureData(rEvent.mnX, rEvent.mnY, rEvent.meEventType, rEvent.mfOffset, rEvent.meOrientation)
+ {
+ }
+
+ virtual bool CallCommand(vcl::Window* pWindow, const Point& /*rMousePos*/) override
+ {
+ return ImplCallCommand(pWindow, CommandEventId::Gesture, &m_aGestureData);
+ }
+};
+
+}
+
+static bool ImplHandleGestureEvent(vcl::Window* pWindow, const SalGestureEvent& rEvent)
+{
+ HandleGeneralGestureEvent aHandler(pWindow, rEvent);
+ return aHandler.HandleEvent();
+}
+
+static void ImplHandlePaint( vcl::Window* pWindow, const tools::Rectangle& rBoundRect, bool bImmediateUpdate )
+{
+ // system paint events must be checked for re-mirroring
+ pWindow->ImplGetWindowImpl()->mnPaintFlags |= ImplPaintFlags::CheckRtl;
+
+ // trigger paint for all windows that live in the new paint region
+ vcl::Region aRegion( rBoundRect );
+ pWindow->ImplInvalidateOverlapFrameRegion( aRegion );
+ if( bImmediateUpdate )
+ {
+ // #i87663# trigger possible pending resize notifications
+ // (GetSizePixel does that for us)
+ pWindow->GetSizePixel();
+ // force drawing immediately
+ pWindow->PaintImmediately();
+ }
+}
+
+static void KillOwnPopups( vcl::Window const * pWindow )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window *pParent = pWindow->ImplGetWindowImpl()->mpFrameWindow;
+ vcl::Window *pChild = pSVData->mpWinData->mpFirstFloat;
+ if ( pChild && pParent->ImplIsWindowOrChild( pChild, true ) )
+ {
+ if (!(pSVData->mpWinData->mpFirstFloat->GetPopupModeFlags()
+ & FloatWinPopupFlags::NoAppFocusClose))
+ pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel
+ | FloatWinPopupEndFlags::CloseAll);
+ }
+}
+
+void ImplHandleResize( vcl::Window* pWindow, tools::Long nNewWidth, tools::Long nNewHeight )
+{
+ const bool bChanged = (nNewWidth != pWindow->GetOutputSizePixel().Width()) || (nNewHeight != pWindow->GetOutDev()->GetOutputHeightPixel());
+ if (bChanged && pWindow->GetStyle() & (WB_MOVEABLE|WB_SIZEABLE))
+ {
+ KillOwnPopups( pWindow );
+ if( pWindow->ImplGetWindow() != ImplGetSVHelpData().mpHelpWin )
+ ImplDestroyHelpWindow( true );
+ }
+
+ if (
+ (nNewWidth > 0 && nNewHeight > 0) ||
+ pWindow->ImplGetWindow()->ImplGetWindowImpl()->mbAllResize
+ )
+ {
+ if (bChanged)
+ {
+ pWindow->GetOutDev()->mnOutWidth = nNewWidth;
+ pWindow->GetOutDev()->mnOutHeight = nNewHeight;
+ pWindow->ImplGetWindowImpl()->mbWaitSystemResize = false;
+ if ( pWindow->IsReallyVisible() )
+ pWindow->ImplSetClipFlag();
+ if ( pWindow->IsVisible() || pWindow->ImplGetWindow()->ImplGetWindowImpl()->mbAllResize ||
+ ( pWindow->ImplGetWindowImpl()->mbFrame && pWindow->ImplGetWindowImpl()->mpClientWindow ) ) // propagate resize for system border windows
+ {
+ bool bStartTimer = true;
+ // use resize buffering for user resizes
+ // ownerdraw decorated windows and floating windows can be resized immediately (i.e. synchronously)
+ if( pWindow->ImplGetWindowImpl()->mbFrame && (pWindow->GetStyle() & WB_SIZEABLE)
+ && !(pWindow->GetStyle() & WB_OWNERDRAWDECORATION) // synchronous resize for ownerdraw decorated windows (toolbars)
+ && !pWindow->ImplGetWindowImpl()->mbFloatWin ) // synchronous resize for floating windows, #i43799#
+ {
+ if( pWindow->ImplGetWindowImpl()->mpClientWindow )
+ {
+ // #i42750# presentation wants to be informed about resize
+ // as early as possible
+ WorkWindow* pWorkWindow = dynamic_cast<WorkWindow*>(pWindow->ImplGetWindowImpl()->mpClientWindow.get());
+ if( ! pWorkWindow || pWorkWindow->IsPresentationMode() )
+ bStartTimer = false;
+ }
+ else
+ {
+ WorkWindow* pWorkWindow = dynamic_cast<WorkWindow*>(pWindow);
+ if( ! pWorkWindow || pWorkWindow->IsPresentationMode() )
+ bStartTimer = false;
+ }
+ }
+ else
+ bStartTimer = false;
+
+ if( bStartTimer )
+ pWindow->ImplGetWindowImpl()->mpFrameData->maResizeIdle.Start();
+ else
+ pWindow->ImplCallResize(); // otherwise menus cannot be positioned
+ }
+ else
+ pWindow->ImplGetWindowImpl()->mbCallResize = true;
+
+ if (pWindow->SupportsDoubleBuffering() && pWindow->ImplGetWindowImpl()->mbFrame)
+ {
+ // Propagate resize for the frame's buffer.
+ pWindow->ImplGetWindowImpl()->mpFrameData->mpBuffer->SetOutputSizePixel(pWindow->GetOutputSizePixel());
+ }
+ }
+ }
+
+ pWindow->ImplGetWindowImpl()->mpFrameData->mbNeedSysWindow = (nNewWidth < IMPL_MIN_NEEDSYSWIN) ||
+ (nNewHeight < IMPL_MIN_NEEDSYSWIN);
+ bool bMinimized = (nNewWidth <= 0) || (nNewHeight <= 0);
+ if( bMinimized != pWindow->ImplGetWindowImpl()->mpFrameData->mbMinimized )
+ pWindow->ImplGetWindowImpl()->mpFrameWindow->ImplNotifyIconifiedState( bMinimized );
+ pWindow->ImplGetWindowImpl()->mpFrameData->mbMinimized = bMinimized;
+}
+
+static void ImplHandleMove( vcl::Window* pWindow )
+{
+ if( pWindow->ImplGetWindowImpl()->mbFrame && pWindow->ImplIsFloatingWindow() && pWindow->IsReallyVisible() )
+ {
+ static_cast<FloatingWindow*>(pWindow)->EndPopupMode( FloatWinPopupEndFlags::TearOff );
+ pWindow->ImplCallMove();
+ }
+
+ if( pWindow->GetStyle() & (WB_MOVEABLE|WB_SIZEABLE) )
+ {
+ KillOwnPopups( pWindow );
+ if( pWindow->ImplGetWindow() != ImplGetSVHelpData().mpHelpWin )
+ ImplDestroyHelpWindow( true );
+ }
+
+ if ( pWindow->IsVisible() )
+ pWindow->ImplCallMove();
+ else
+ pWindow->ImplGetWindowImpl()->mbCallMove = true; // make sure the framepos will be updated on the next Show()
+
+ if ( pWindow->ImplGetWindowImpl()->mbFrame && pWindow->ImplGetWindowImpl()->mpClientWindow )
+ pWindow->ImplGetWindowImpl()->mpClientWindow->ImplCallMove(); // notify client to update geometry
+
+}
+
+static void ImplHandleMoveResize( vcl::Window* pWindow, tools::Long nNewWidth, tools::Long nNewHeight )
+{
+ ImplHandleMove( pWindow );
+ ImplHandleResize( pWindow, nNewWidth, nNewHeight );
+}
+
+static void ImplActivateFloatingWindows( vcl::Window const * pWindow, bool bActive )
+{
+ // First check all overlapping windows
+ vcl::Window* pTempWindow = pWindow->ImplGetWindowImpl()->mpFirstOverlap;
+ while ( pTempWindow )
+ {
+ if ( pTempWindow->GetActivateMode() == ActivateModeFlags::NONE )
+ {
+ if ( (pTempWindow->GetType() == WindowType::BORDERWINDOW) &&
+ (pTempWindow->ImplGetWindow()->GetType() == WindowType::FLOATINGWINDOW) )
+ static_cast<ImplBorderWindow*>(pTempWindow)->SetDisplayActive( bActive );
+ }
+
+ ImplActivateFloatingWindows( pTempWindow, bActive );
+ pTempWindow = pTempWindow->ImplGetWindowImpl()->mpNext;
+ }
+}
+
+IMPL_LINK_NOARG(vcl::Window, ImplAsyncFocusHdl, void*, void)
+{
+ if (!ImplGetWindowImpl() || !ImplGetWindowImpl()->mpFrameData)
+ return;
+
+ ImplGetWindowImpl()->mpFrameData->mnFocusId = nullptr;
+
+ // If the status has been preserved, because we got back the focus
+ // in the meantime, we do nothing
+ bool bHasFocus = ImplGetWindowImpl()->mpFrameData->mbHasFocus || ImplGetWindowImpl()->mpFrameData->mbSysObjFocus;
+
+ // next execute the delayed functions
+ if ( bHasFocus )
+ {
+ // redraw all floating windows inactive
+ if ( ImplGetWindowImpl()->mpFrameData->mbStartFocusState != bHasFocus )
+ ImplActivateFloatingWindows( this, bHasFocus );
+
+ if ( ImplGetWindowImpl()->mpFrameData->mpFocusWin )
+ {
+ bool bHandled = false;
+ if ( ImplGetWindowImpl()->mpFrameData->mpFocusWin->IsInputEnabled() &&
+ ! ImplGetWindowImpl()->mpFrameData->mpFocusWin->IsInModalMode() )
+ {
+ if ( ImplGetWindowImpl()->mpFrameData->mpFocusWin->IsEnabled() )
+ {
+ ImplGetWindowImpl()->mpFrameData->mpFocusWin->GrabFocus();
+ bHandled = true;
+ }
+ else if( ImplGetWindowImpl()->mpFrameData->mpFocusWin->ImplHasDlgCtrl() )
+ {
+ // #109094# if the focus is restored to a disabled dialog control (was disabled meanwhile)
+ // try to move it to the next control
+ ImplGetWindowImpl()->mpFrameData->mpFocusWin->ImplDlgCtrlNextWindow();
+ bHandled = true;
+ }
+ }
+ if ( !bHandled )
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pTopLevelWindow = ImplGetWindowImpl()->mpFrameData->mpFocusWin->ImplGetFirstOverlapWindow();
+
+ if ((!pTopLevelWindow->IsInputEnabled() || pTopLevelWindow->IsInModalMode())
+ && !pSVData->mpWinData->mpExecuteDialogs.empty())
+ pSVData->mpWinData->mpExecuteDialogs.back()->ToTop(ToTopFlags::RestoreWhenMin | ToTopFlags::GrabFocusOnly);
+ else
+ pTopLevelWindow->GrabFocus();
+ }
+ }
+ else
+ GrabFocus();
+ }
+ else
+ {
+ vcl::Window* pFocusWin = ImplGetWindowImpl()->mpFrameData->mpFocusWin;
+ if ( pFocusWin )
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if (pSVData->mpWinData->mpFocusWin == pFocusWin)
+ {
+ // transfer the FocusWindow
+ vcl::Window* pOverlapWindow = pFocusWin->ImplGetFirstOverlapWindow();
+ if ( pOverlapWindow && pOverlapWindow->ImplGetWindowImpl() )
+ pOverlapWindow->ImplGetWindowImpl()->mpLastFocusWindow = pFocusWin;
+ pSVData->mpWinData->mpFocusWin = nullptr;
+
+ if ( pFocusWin->ImplGetWindowImpl() && pFocusWin->ImplGetWindowImpl()->mpCursor )
+ pFocusWin->ImplGetWindowImpl()->mpCursor->ImplHide();
+
+ // call the Deactivate
+ vcl::Window* pOldOverlapWindow = pFocusWin->ImplGetFirstOverlapWindow();
+ vcl::Window* pOldRealWindow = pOldOverlapWindow->ImplGetWindow();
+
+ if (pOldOverlapWindow && pOldOverlapWindow->ImplGetWindowImpl() &&
+ pOldRealWindow && pOldRealWindow->ImplGetWindowImpl())
+ {
+ pOldOverlapWindow->ImplGetWindowImpl()->mbActive = false;
+ pOldOverlapWindow->Deactivate();
+ if ( pOldRealWindow != pOldOverlapWindow )
+ {
+ pOldRealWindow->ImplGetWindowImpl()->mbActive = false;
+ pOldRealWindow->Deactivate();
+ }
+ }
+
+ // TrackingMode is ended in ImplHandleLoseFocus
+#ifdef _WIN32
+ // To avoid problems with the Unix IME
+ pFocusWin->EndExtTextInput();
+#endif
+
+ NotifyEvent aNEvt(MouseNotifyEvent::LOSEFOCUS, pFocusWin);
+ if (!ImplCallPreNotify(aNEvt))
+ pFocusWin->CompatLoseFocus();
+ pFocusWin->ImplCallDeactivateListeners(nullptr);
+ }
+ }
+
+ // Redraw all floating window inactive
+ if ( ImplGetWindowImpl()->mpFrameData->mbStartFocusState != bHasFocus )
+ ImplActivateFloatingWindows( this, bHasFocus );
+ }
+}
+
+static void ImplHandleGetFocus( vcl::Window* pWindow )
+{
+ if (!pWindow || !pWindow->ImplGetWindowImpl() || !pWindow->ImplGetWindowImpl()->mpFrameData)
+ return;
+
+ pWindow->ImplGetWindowImpl()->mpFrameData->mbHasFocus = true;
+
+ // execute Focus-Events after a delay, such that SystemChildWindows
+ // do not blink when they receive focus
+ if ( !pWindow->ImplGetWindowImpl()->mpFrameData->mnFocusId )
+ {
+ pWindow->ImplGetWindowImpl()->mpFrameData->mbStartFocusState = !pWindow->ImplGetWindowImpl()->mpFrameData->mbHasFocus;
+ pWindow->ImplGetWindowImpl()->mpFrameData->mnFocusId = Application::PostUserEvent( LINK( pWindow, vcl::Window, ImplAsyncFocusHdl ), nullptr, true);
+ vcl::Window* pFocusWin = pWindow->ImplGetWindowImpl()->mpFrameData->mpFocusWin;
+ if ( pFocusWin && pFocusWin->ImplGetWindowImpl()->mpCursor )
+ pFocusWin->ImplGetWindowImpl()->mpCursor->ImplShow();
+ }
+}
+
+static void ImplHandleLoseFocus( vcl::Window* pWindow )
+{
+ if (!pWindow)
+ return;
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // Abort the autoscroll if the frame loses focus
+ if (pSVData->mpWinData->mpAutoScrollWin)
+ pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll();
+
+ // Abort tracking if the frame loses focus
+ if (pSVData->mpWinData->mpTrackWin)
+ {
+ if (pSVData->mpWinData->mpTrackWin->ImplGetWindowImpl() &&
+ pSVData->mpWinData->mpTrackWin->ImplGetWindowImpl()->mpFrameWindow == pWindow)
+ pSVData->mpWinData->mpTrackWin->EndTracking(TrackingEventFlags::Cancel);
+ }
+
+ if (pWindow->ImplGetWindowImpl() && pWindow->ImplGetWindowImpl()->mpFrameData)
+ {
+ pWindow->ImplGetWindowImpl()->mpFrameData->mbHasFocus = false;
+
+ // execute Focus-Events after a delay, such that SystemChildWindows
+ // do not flicker when they receive focus
+ if ( !pWindow->ImplGetWindowImpl()->mpFrameData->mnFocusId )
+ {
+ pWindow->ImplGetWindowImpl()->mpFrameData->mbStartFocusState = !pWindow->ImplGetWindowImpl()->mpFrameData->mbHasFocus;
+ pWindow->ImplGetWindowImpl()->mpFrameData->mnFocusId = Application::PostUserEvent( LINK( pWindow, vcl::Window, ImplAsyncFocusHdl ), nullptr, true );
+ }
+
+ vcl::Window* pFocusWin = pWindow->ImplGetWindowImpl()->mpFrameData->mpFocusWin;
+ if ( pFocusWin && pFocusWin->ImplGetWindowImpl()->mpCursor )
+ pFocusWin->ImplGetWindowImpl()->mpCursor->ImplHide();
+ }
+
+ // Make sure that no menu is visible when a toplevel window loses focus.
+ VclPtr<FloatingWindow> pFirstFloat = pSVData->mpWinData->mpFirstFloat;
+ if (pFirstFloat && pFirstFloat->IsMenuFloatingWindow() && !pWindow->GetParent())
+ {
+ pFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll);
+ }
+}
+
+namespace {
+
+struct DelayedCloseEvent
+{
+ VclPtr<vcl::Window> pWindow;
+};
+
+}
+
+static void DelayedCloseEventLink( void* pCEvent, void* )
+{
+ DelayedCloseEvent* pEv = static_cast<DelayedCloseEvent*>(pCEvent);
+
+ if( ! pEv->pWindow->isDisposed() )
+ {
+ // dispatch to correct window type
+ if( pEv->pWindow->IsSystemWindow() )
+ static_cast<SystemWindow*>(pEv->pWindow.get())->Close();
+ else if( pEv->pWindow->IsDockingWindow() )
+ static_cast<DockingWindow*>(pEv->pWindow.get())->Close();
+ }
+ delete pEv;
+}
+
+static void ImplHandleClose( const vcl::Window* pWindow )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ bool bWasPopup = false;
+ if( pWindow->ImplIsFloatingWindow() &&
+ static_cast<const FloatingWindow*>(pWindow)->ImplIsInPrivatePopupMode() )
+ {
+ bWasPopup = true;
+ }
+
+ // on Close stop all floating modes and end popups
+ if (pSVData->mpWinData->mpFirstFloat)
+ {
+ FloatingWindow* pLastLevelFloat;
+ pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat();
+ pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll );
+ }
+ if ( ImplGetSVHelpData().mbExtHelpMode )
+ Help::EndExtHelp();
+ if ( ImplGetSVHelpData().mpHelpWin )
+ ImplDestroyHelpWindow( false );
+ // AutoScrollMode
+ if (pSVData->mpWinData->mpAutoScrollWin)
+ pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll();
+
+ if (pSVData->mpWinData->mpTrackWin)
+ pSVData->mpWinData->mpTrackWin->EndTracking( TrackingEventFlags::Cancel | TrackingEventFlags::Key );
+
+ if (bWasPopup)
+ return;
+
+ vcl::Window *pWin = pWindow->ImplGetWindow();
+ SystemWindow* pSysWin = dynamic_cast<SystemWindow*>(pWin);
+ if (pSysWin)
+ {
+ // See if the custom close handler is set.
+ const Link<SystemWindow&,void>& rLink = pSysWin->GetCloseHdl();
+ if (rLink.IsSet())
+ {
+ rLink.Call(*pSysWin);
+ return;
+ }
+ }
+
+ // check whether close is allowed
+ if ( pWin->IsEnabled() && pWin->IsInputEnabled() && !pWin->IsInModalMode() )
+ {
+ DelayedCloseEvent* pEv = new DelayedCloseEvent;
+ pEv->pWindow = pWin;
+ Application::PostUserEvent( Link<void*,void>( pEv, DelayedCloseEventLink ) );
+ }
+}
+
+static void ImplHandleUserEvent( ImplSVEvent* pSVEvent )
+{
+ if ( pSVEvent )
+ {
+ if ( pSVEvent->mbCall )
+ {
+ pSVEvent->maLink.Call( pSVEvent->mpData );
+ }
+
+ delete pSVEvent;
+ }
+}
+
+MouseEventModifiers ImplGetMouseMoveMode( SalMouseEvent const * pEvent )
+{
+ MouseEventModifiers nMode = MouseEventModifiers::NONE;
+ if ( !pEvent->mnCode )
+ nMode |= MouseEventModifiers::SIMPLEMOVE;
+ if ( (pEvent->mnCode & MOUSE_LEFT) && !(pEvent->mnCode & KEY_MOD1) )
+ nMode |= MouseEventModifiers::DRAGMOVE;
+ if ( (pEvent->mnCode & MOUSE_LEFT) && (pEvent->mnCode & KEY_MOD1) )
+ nMode |= MouseEventModifiers::DRAGCOPY;
+ return nMode;
+}
+
+MouseEventModifiers ImplGetMouseButtonMode( SalMouseEvent const * pEvent )
+{
+ MouseEventModifiers nMode = MouseEventModifiers::NONE;
+ if ( pEvent->mnButton == MOUSE_LEFT )
+ nMode |= MouseEventModifiers::SIMPLECLICK;
+ if ( (pEvent->mnButton == MOUSE_LEFT) && !(pEvent->mnCode & (MOUSE_MIDDLE | MOUSE_RIGHT)) )
+ nMode |= MouseEventModifiers::SELECT;
+ if ( (pEvent->mnButton == MOUSE_LEFT) && (pEvent->mnCode & KEY_MOD1) &&
+ !(pEvent->mnCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_SHIFT)) )
+ nMode |= MouseEventModifiers::MULTISELECT;
+ if ( (pEvent->mnButton == MOUSE_LEFT) && (pEvent->mnCode & KEY_SHIFT) &&
+ !(pEvent->mnCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_MOD1)) )
+ nMode |= MouseEventModifiers::RANGESELECT;
+ return nMode;
+}
+
+static bool ImplHandleSalMouseLeave( vcl::Window* pWindow, SalMouseEvent const * pEvent )
+{
+ return ImplHandleMouseEvent( pWindow, MouseNotifyEvent::MOUSEMOVE, true,
+ pEvent->mnX, pEvent->mnY,
+ pEvent->mnTime, pEvent->mnCode,
+ ImplGetMouseMoveMode( pEvent ) );
+}
+
+static bool ImplHandleSalMouseMove( vcl::Window* pWindow, SalMouseEvent const * pEvent )
+{
+ return ImplHandleMouseEvent( pWindow, MouseNotifyEvent::MOUSEMOVE, false,
+ pEvent->mnX, pEvent->mnY,
+ pEvent->mnTime, pEvent->mnCode,
+ ImplGetMouseMoveMode( pEvent ) );
+}
+
+static bool ImplHandleSalMouseButtonDown( vcl::Window* pWindow, SalMouseEvent const * pEvent )
+{
+ return ImplHandleMouseEvent( pWindow, MouseNotifyEvent::MOUSEBUTTONDOWN, false,
+ pEvent->mnX, pEvent->mnY,
+ pEvent->mnTime,
+#ifdef MACOSX
+ pEvent->mnButton | (pEvent->mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)),
+#else
+ pEvent->mnButton | (pEvent->mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2)),
+#endif
+ ImplGetMouseButtonMode( pEvent ) );
+}
+
+static bool ImplHandleSalMouseButtonUp( vcl::Window* pWindow, SalMouseEvent const * pEvent )
+{
+ return ImplHandleMouseEvent( pWindow, MouseNotifyEvent::MOUSEBUTTONUP, false,
+ pEvent->mnX, pEvent->mnY,
+ pEvent->mnTime,
+#ifdef MACOSX
+ pEvent->mnButton | (pEvent->mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)),
+#else
+ pEvent->mnButton | (pEvent->mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2)),
+#endif
+ ImplGetMouseButtonMode( pEvent ) );
+}
+
+static bool ImplHandleMenuEvent( vcl::Window const * pWindow, SalMenuEvent* pEvent, SalEvent nEvent )
+{
+ // Find SystemWindow and its Menubar and let it dispatch the command
+ bool bRet = false;
+ vcl::Window *pWin = pWindow->ImplGetWindowImpl()->mpFirstChild;
+ while ( pWin )
+ {
+ if ( pWin->ImplGetWindowImpl()->mbSysWin )
+ break;
+ pWin = pWin->ImplGetWindowImpl()->mpNext;
+ }
+ if( pWin )
+ {
+ MenuBar *pMenuBar = static_cast<SystemWindow*>(pWin)->GetMenuBar();
+ if( pMenuBar )
+ {
+ switch( nEvent )
+ {
+ case SalEvent::MenuActivate:
+ pMenuBar->HandleMenuActivateEvent( static_cast<Menu*>(pEvent->mpMenu) );
+ bRet = true;
+ break;
+ case SalEvent::MenuDeactivate:
+ pMenuBar->HandleMenuDeActivateEvent( static_cast<Menu*>(pEvent->mpMenu) );
+ bRet = true;
+ break;
+ case SalEvent::MenuHighlight:
+ bRet = pMenuBar->HandleMenuHighlightEvent( static_cast<Menu*>(pEvent->mpMenu), pEvent->mnId );
+ break;
+ case SalEvent::MenuButtonCommand:
+ bRet = pMenuBar->HandleMenuButtonEvent( pEvent->mnId );
+ break;
+ case SalEvent::MenuCommand:
+ bRet = pMenuBar->HandleMenuCommandEvent( static_cast<Menu*>(pEvent->mpMenu), pEvent->mnId );
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return bRet;
+}
+
+static void ImplHandleSalKeyMod( vcl::Window* pWindow, SalKeyModEvent const * pEvent )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pTrackWin = pSVData->mpWinData->mpTrackWin;
+ if ( pTrackWin )
+ pWindow = pTrackWin;
+#ifdef MACOSX
+ sal_uInt16 nOldCode = pWindow->ImplGetWindowImpl()->mpFrameData->mnMouseCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3);
+#else
+ sal_uInt16 nOldCode = pWindow->ImplGetWindowImpl()->mpFrameData->mnMouseCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2);
+#endif
+ sal_uInt16 nNewCode = pEvent->mnCode;
+ if ( nOldCode != nNewCode )
+ {
+#ifdef MACOSX
+ nNewCode |= pWindow->ImplGetWindowImpl()->mpFrameData->mnMouseCode & ~(KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3);
+#else
+ nNewCode |= pWindow->ImplGetWindowImpl()->mpFrameData->mnMouseCode & ~(KEY_SHIFT | KEY_MOD1 | KEY_MOD2);
+#endif
+ pWindow->ImplGetWindowImpl()->mpFrameWindow->ImplCallMouseMove( nNewCode, true );
+ }
+
+ // #105224# send commandevent to allow special treatment of Ctrl-LeftShift/Ctrl-RightShift etc.
+ // + auto-accelerator feature, tdf#92630
+
+ // try to find a key input window...
+ vcl::Window* pChild = ImplGetKeyInputWindow( pWindow );
+ //...otherwise fail safe...
+ if (!pChild)
+ pChild = pWindow;
+
+ CommandModKeyData data( pEvent->mnModKeyCode, pEvent->mbDown );
+ ImplCallCommand( pChild, CommandEventId::ModKeyChange, &data );
+}
+
+static void ImplHandleInputLanguageChange( vcl::Window* pWindow )
+{
+ // find window
+ vcl::Window* pChild = ImplGetKeyInputWindow( pWindow );
+ if ( !pChild )
+ return;
+
+ ImplCallCommand( pChild, CommandEventId::InputLanguageChange );
+}
+
+static void ImplHandleSalSettings( SalEvent nEvent )
+{
+ Application* pApp = GetpApp();
+ if ( !pApp )
+ return;
+
+ if ( nEvent == SalEvent::SettingsChanged )
+ {
+ AllSettings aSettings = Application::GetSettings();
+ Application::MergeSystemSettings( aSettings );
+ pApp->OverrideSystemSettings( aSettings );
+ Application::SetSettings( aSettings );
+ }
+ else
+ {
+ DataChangedEventType nType;
+ switch ( nEvent )
+ {
+ case SalEvent::PrinterChanged:
+ ImplDeletePrnQueueList();
+ nType = DataChangedEventType::PRINTER;
+ break;
+ case SalEvent::DisplayChanged:
+ nType = DataChangedEventType::DISPLAY;
+ break;
+ case SalEvent::FontChanged:
+ OutputDevice::ImplUpdateAllFontData( true );
+ nType = DataChangedEventType::FONTS;
+ break;
+ default:
+ nType = DataChangedEventType::NONE;
+ break;
+ }
+
+ if ( nType != DataChangedEventType::NONE )
+ {
+ DataChangedEvent aDCEvt( nType );
+ Application::ImplCallEventListenersApplicationDataChanged(&aDCEvt);
+ Application::NotifyAllWindows( aDCEvt );
+ }
+ }
+}
+
+static void ImplHandleSalExtTextInputPos( vcl::Window* pWindow, SalExtTextInputPosEvent* pEvt )
+{
+ tools::Rectangle aCursorRect;
+ ImplHandleExtTextInputPos( pWindow, aCursorRect, pEvt->mnExtWidth, &pEvt->mbVertical );
+ if ( aCursorRect.IsEmpty() )
+ {
+ pEvt->mnX = -1;
+ pEvt->mnY = -1;
+ pEvt->mnWidth = -1;
+ pEvt->mnHeight = -1;
+ }
+ else
+ {
+ pEvt->mnX = aCursorRect.Left();
+ pEvt->mnY = aCursorRect.Top();
+ pEvt->mnWidth = aCursorRect.GetWidth();
+ pEvt->mnHeight = aCursorRect.GetHeight();
+ }
+}
+
+static bool ImplHandleShowDialog( vcl::Window* pWindow, ShowDialogId nDialogId )
+{
+ if( ! pWindow )
+ return false;
+
+ if( pWindow->GetType() == WindowType::BORDERWINDOW )
+ {
+ vcl::Window* pWrkWin = pWindow->GetWindow( GetWindowType::Client );
+ if( pWrkWin )
+ pWindow = pWrkWin;
+ }
+ CommandDialogData aCmdData( nDialogId );
+ return ImplCallCommand( pWindow, CommandEventId::ShowDialog, &aCmdData );
+}
+
+static void ImplHandleSurroundingTextRequest( vcl::Window *pWindow,
+ OUString& rText,
+ Selection &rSelRange )
+{
+ vcl::Window* pChild = ImplGetKeyInputWindow( pWindow );
+
+ if ( !pChild )
+ {
+ rText.clear();
+ rSelRange.setMin( 0 );
+ rSelRange.setMax( 0 );
+ }
+ else
+ {
+ rText = pChild->GetSurroundingText();
+ Selection aSel = pChild->GetSurroundingTextSelection();
+ rSelRange.setMin( aSel.Min() );
+ rSelRange.setMax( aSel.Max() );
+ }
+}
+
+static void ImplHandleSalSurroundingTextRequest( vcl::Window *pWindow,
+ SalSurroundingTextRequestEvent *pEvt )
+{
+ Selection aSelRange;
+ ImplHandleSurroundingTextRequest( pWindow, pEvt->maText, aSelRange );
+
+ aSelRange.Justify();
+
+ if( aSelRange.Min() < 0 )
+ pEvt->mnStart = 0;
+ else if( aSelRange.Min() > pEvt->maText.getLength() )
+ pEvt->mnStart = pEvt->maText.getLength();
+ else
+ pEvt->mnStart = aSelRange.Min();
+
+ if( aSelRange.Max() < 0 )
+ pEvt->mnStart = 0;
+ else if( aSelRange.Max() > pEvt->maText.getLength() )
+ pEvt->mnEnd = pEvt->maText.getLength();
+ else
+ pEvt->mnEnd = aSelRange.Max();
+}
+
+static void ImplHandleSalDeleteSurroundingTextRequest( vcl::Window *pWindow,
+ SalSurroundingTextSelectionChangeEvent *pEvt )
+{
+ vcl::Window* pChild = ImplGetKeyInputWindow( pWindow );
+
+ Selection aSelection(pEvt->mnStart, pEvt->mnEnd);
+ if (pChild && pChild->DeleteSurroundingText(aSelection))
+ {
+ pEvt->mnStart = aSelection.Min();
+ pEvt->mnEnd = aSelection.Max();
+ }
+ else
+ {
+ pEvt->mnStart = pEvt->mnEnd = SAL_MAX_UINT32;
+ }
+}
+
+static void ImplHandleSurroundingTextSelectionChange( vcl::Window *pWindow,
+ sal_uLong nStart,
+ sal_uLong nEnd )
+{
+ vcl::Window* pChild = ImplGetKeyInputWindow( pWindow );
+ if( pChild )
+ {
+ CommandSelectionChangeData data( nStart, nEnd );
+ ImplCallCommand( pChild, CommandEventId::SelectionChange, &data );
+ }
+}
+
+static void ImplHandleStartReconversion( vcl::Window *pWindow )
+{
+ vcl::Window* pChild = ImplGetKeyInputWindow( pWindow );
+ if( pChild )
+ ImplCallCommand( pChild, CommandEventId::PrepareReconversion );
+}
+
+static void ImplHandleSalQueryCharPosition( vcl::Window *pWindow,
+ SalQueryCharPositionEvent *pEvt )
+{
+ pEvt->mbValid = false;
+ pEvt->mbVertical = false;
+ pEvt->mnCursorBoundX = 0;
+ pEvt->mnCursorBoundY = 0;
+ pEvt->mnCursorBoundWidth = 0;
+ pEvt->mnCursorBoundHeight = 0;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ vcl::Window* pChild = pSVData->mpWinData->mpExtTextInputWin;
+
+ if ( !pChild )
+ pChild = ImplGetKeyInputWindow( pWindow );
+ else
+ {
+ // Test, if the Window is related to the frame
+ if ( !pWindow->ImplIsWindowOrChild( pChild ) )
+ pChild = ImplGetKeyInputWindow( pWindow );
+ }
+
+ if( !pChild )
+ return;
+
+ ImplCallCommand( pChild, CommandEventId::QueryCharPosition );
+
+ ImplWinData* pWinData = pChild->ImplGetWinData();
+ if ( !(pWinData->mpCompositionCharRects && pEvt->mnCharPos < o3tl::make_unsigned( pWinData->mnCompositionCharRects )) )
+ return;
+
+ const OutputDevice *pChildOutDev = pChild->GetOutDev();
+ const tools::Rectangle& aRect = pWinData->mpCompositionCharRects[ pEvt->mnCharPos ];
+ tools::Rectangle aDeviceRect = pChildOutDev->ImplLogicToDevicePixel( aRect );
+ Point aAbsScreenPos = pChild->OutputToAbsoluteScreenPixel( pChild->ScreenToOutputPixel(aDeviceRect.TopLeft()) );
+ pEvt->mnCursorBoundX = aAbsScreenPos.X();
+ pEvt->mnCursorBoundY = aAbsScreenPos.Y();
+ pEvt->mnCursorBoundWidth = aDeviceRect.GetWidth();
+ pEvt->mnCursorBoundHeight = aDeviceRect.GetHeight();
+ pEvt->mbVertical = pWinData->mbVertical;
+ pEvt->mbValid = true;
+}
+
+bool ImplWindowFrameProc( vcl::Window* _pWindow, SalEvent nEvent, const void* pEvent )
+{
+ DBG_TESTSOLARMUTEX();
+
+ // Ensure the window survives during this method.
+ VclPtr<vcl::Window> pWindow( _pWindow );
+
+ bool bRet = false;
+
+ // #119709# for some unknown reason it is possible to receive events (in this case key events)
+ // although the corresponding VCL window must have been destroyed already
+ // at least ImplGetWindowImpl() was NULL in these cases, so check this here
+ if( pWindow->ImplGetWindowImpl() == nullptr )
+ return false;
+
+ switch ( nEvent )
+ {
+ case SalEvent::MouseMove:
+ bRet = ImplHandleSalMouseMove( pWindow, static_cast<SalMouseEvent const *>(pEvent) );
+ break;
+ case SalEvent::ExternalMouseMove:
+ {
+ MouseEvent const * pMouseEvt = static_cast<MouseEvent const *>(pEvent);
+ SalMouseEvent aSalMouseEvent;
+
+ aSalMouseEvent.mnTime = tools::Time::GetSystemTicks();
+ aSalMouseEvent.mnX = pMouseEvt->GetPosPixel().X();
+ aSalMouseEvent.mnY = pMouseEvt->GetPosPixel().Y();
+ aSalMouseEvent.mnButton = 0;
+ aSalMouseEvent.mnCode = pMouseEvt->GetButtons() | pMouseEvt->GetModifier();
+
+ bRet = ImplHandleSalMouseMove( pWindow, &aSalMouseEvent );
+ }
+ break;
+ case SalEvent::MouseLeave:
+ bRet = ImplHandleSalMouseLeave( pWindow, static_cast<SalMouseEvent const *>(pEvent) );
+ break;
+ case SalEvent::MouseButtonDown:
+ bRet = ImplHandleSalMouseButtonDown( pWindow, static_cast<SalMouseEvent const *>(pEvent) );
+ break;
+ case SalEvent::ExternalMouseButtonDown:
+ {
+ MouseEvent const * pMouseEvt = static_cast<MouseEvent const *>(pEvent);
+ SalMouseEvent aSalMouseEvent;
+
+ aSalMouseEvent.mnTime = tools::Time::GetSystemTicks();
+ aSalMouseEvent.mnX = pMouseEvt->GetPosPixel().X();
+ aSalMouseEvent.mnY = pMouseEvt->GetPosPixel().Y();
+ aSalMouseEvent.mnButton = pMouseEvt->GetButtons();
+ aSalMouseEvent.mnCode = pMouseEvt->GetButtons() | pMouseEvt->GetModifier();
+
+ bRet = ImplHandleSalMouseButtonDown( pWindow, &aSalMouseEvent );
+ }
+ break;
+ case SalEvent::MouseButtonUp:
+ bRet = ImplHandleSalMouseButtonUp( pWindow, static_cast<SalMouseEvent const *>(pEvent) );
+ break;
+ case SalEvent::ExternalMouseButtonUp:
+ {
+ MouseEvent const * pMouseEvt = static_cast<MouseEvent const *>(pEvent);
+ SalMouseEvent aSalMouseEvent;
+
+ aSalMouseEvent.mnTime = tools::Time::GetSystemTicks();
+ aSalMouseEvent.mnX = pMouseEvt->GetPosPixel().X();
+ aSalMouseEvent.mnY = pMouseEvt->GetPosPixel().Y();
+ aSalMouseEvent.mnButton = pMouseEvt->GetButtons();
+ aSalMouseEvent.mnCode = pMouseEvt->GetButtons() | pMouseEvt->GetModifier();
+
+ bRet = ImplHandleSalMouseButtonUp( pWindow, &aSalMouseEvent );
+ }
+ break;
+ case SalEvent::MouseActivate:
+ bRet = false;
+ break;
+ case SalEvent::KeyInput:
+ {
+ SalKeyEvent const * pKeyEvt = static_cast<SalKeyEvent const *>(pEvent);
+ bRet = ImplHandleKey( pWindow, MouseNotifyEvent::KEYINPUT,
+ pKeyEvt->mnCode, pKeyEvt->mnCharCode, pKeyEvt->mnRepeat, true );
+ }
+ break;
+ case SalEvent::ExternalKeyInput:
+ {
+ KeyEvent const * pKeyEvt = static_cast<KeyEvent const *>(pEvent);
+ bRet = ImplHandleKey( pWindow, MouseNotifyEvent::KEYINPUT,
+ pKeyEvt->GetKeyCode().GetFullCode(), pKeyEvt->GetCharCode(), pKeyEvt->GetRepeat(), false );
+ }
+ break;
+ case SalEvent::KeyUp:
+ {
+ SalKeyEvent const * pKeyEvt = static_cast<SalKeyEvent const *>(pEvent);
+ bRet = ImplHandleKey( pWindow, MouseNotifyEvent::KEYUP,
+ pKeyEvt->mnCode, pKeyEvt->mnCharCode, pKeyEvt->mnRepeat, true );
+ }
+ break;
+ case SalEvent::ExternalKeyUp:
+ {
+ KeyEvent const * pKeyEvt = static_cast<KeyEvent const *>(pEvent);
+ bRet = ImplHandleKey( pWindow, MouseNotifyEvent::KEYUP,
+ pKeyEvt->GetKeyCode().GetFullCode(), pKeyEvt->GetCharCode(), pKeyEvt->GetRepeat(), false );
+ }
+ break;
+ case SalEvent::KeyModChange:
+ ImplHandleSalKeyMod( pWindow, static_cast<SalKeyModEvent const *>(pEvent) );
+ break;
+
+ case SalEvent::InputLanguageChange:
+ ImplHandleInputLanguageChange( pWindow );
+ break;
+
+ case SalEvent::MenuActivate:
+ case SalEvent::MenuDeactivate:
+ case SalEvent::MenuHighlight:
+ case SalEvent::MenuCommand:
+ case SalEvent::MenuButtonCommand:
+ bRet = ImplHandleMenuEvent( pWindow, const_cast<SalMenuEvent *>(static_cast<SalMenuEvent const *>(pEvent)), nEvent );
+ break;
+
+ case SalEvent::WheelMouse:
+ bRet = ImplHandleWheelEvent( pWindow, *static_cast<const SalWheelMouseEvent*>(pEvent));
+ break;
+
+ case SalEvent::Paint:
+ {
+ SalPaintEvent const * pPaintEvt = static_cast<SalPaintEvent const *>(pEvent);
+
+ if( AllSettings::GetLayoutRTL() )
+ {
+ SalFrame* pSalFrame = pWindow->ImplGetWindowImpl()->mpFrame;
+ const_cast<SalPaintEvent *>(pPaintEvt)->mnBoundX = pSalFrame->maGeometry.nWidth-pPaintEvt->mnBoundWidth-pPaintEvt->mnBoundX;
+ }
+
+ tools::Rectangle aBoundRect( Point( pPaintEvt->mnBoundX, pPaintEvt->mnBoundY ),
+ Size( pPaintEvt->mnBoundWidth, pPaintEvt->mnBoundHeight ) );
+ ImplHandlePaint( pWindow, aBoundRect, pPaintEvt->mbImmediateUpdate );
+ }
+ break;
+
+ case SalEvent::Move:
+ ImplHandleMove( pWindow );
+ break;
+
+ case SalEvent::Resize:
+ {
+ tools::Long nNewWidth;
+ tools::Long nNewHeight;
+ pWindow->ImplGetWindowImpl()->mpFrame->GetClientSize( nNewWidth, nNewHeight );
+ ImplHandleResize( pWindow, nNewWidth, nNewHeight );
+ }
+ break;
+
+ case SalEvent::MoveResize:
+ {
+ SalFrameGeometry g = pWindow->ImplGetWindowImpl()->mpFrame->GetGeometry();
+ ImplHandleMoveResize( pWindow, g.nWidth, g.nHeight );
+ }
+ break;
+
+ case SalEvent::ClosePopups:
+ {
+ KillOwnPopups( pWindow );
+ }
+ break;
+
+ case SalEvent::GetFocus:
+ ImplHandleGetFocus( pWindow );
+ break;
+ case SalEvent::LoseFocus:
+ ImplHandleLoseFocus( pWindow );
+ break;
+
+ case SalEvent::Close:
+ ImplHandleClose( pWindow );
+ break;
+
+ case SalEvent::Shutdown:
+ {
+ static bool bInQueryExit = false;
+ if( !bInQueryExit )
+ {
+ bInQueryExit = true;
+ if ( GetpApp()->QueryExit() )
+ {
+ // end the message loop
+ Application::Quit();
+ return false;
+ }
+ else
+ {
+ bInQueryExit = false;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ case SalEvent::SettingsChanged:
+ case SalEvent::PrinterChanged:
+ case SalEvent::DisplayChanged:
+ case SalEvent::FontChanged:
+ ImplHandleSalSettings( nEvent );
+ break;
+
+ case SalEvent::UserEvent:
+ ImplHandleUserEvent( const_cast<ImplSVEvent *>(static_cast<ImplSVEvent const *>(pEvent)) );
+ break;
+
+ case SalEvent::ExtTextInput:
+ {
+ SalExtTextInputEvent const * pEvt = static_cast<SalExtTextInputEvent const *>(pEvent);
+ bRet = ImplHandleExtTextInput( pWindow,
+ pEvt->maText, pEvt->mpTextAttr,
+ pEvt->mnCursorPos, pEvt->mnCursorFlags );
+ }
+ break;
+ case SalEvent::EndExtTextInput:
+ bRet = ImplHandleEndExtTextInput();
+ break;
+ case SalEvent::ExtTextInputPos:
+ ImplHandleSalExtTextInputPos( pWindow, const_cast<SalExtTextInputPosEvent *>(static_cast<SalExtTextInputPosEvent const *>(pEvent)) );
+ break;
+ case SalEvent::InputContextChange:
+ bRet = ImplHandleInputContextChange( pWindow );
+ break;
+ case SalEvent::ShowDialog:
+ {
+ ShowDialogId nLOKWindowId = static_cast<ShowDialogId>(reinterpret_cast<sal_IntPtr>(pEvent));
+ bRet = ImplHandleShowDialog( pWindow, nLOKWindowId );
+ }
+ break;
+ case SalEvent::SurroundingTextRequest:
+ ImplHandleSalSurroundingTextRequest( pWindow, const_cast<SalSurroundingTextRequestEvent *>(static_cast<SalSurroundingTextRequestEvent const *>(pEvent)) );
+ break;
+ case SalEvent::DeleteSurroundingTextRequest:
+ ImplHandleSalDeleteSurroundingTextRequest( pWindow, const_cast<SalSurroundingTextSelectionChangeEvent *>(static_cast<SalSurroundingTextSelectionChangeEvent const *>(pEvent)) );
+ break;
+ case SalEvent::SurroundingTextSelectionChange:
+ {
+ SalSurroundingTextSelectionChangeEvent const * pEvt
+ = static_cast<SalSurroundingTextSelectionChangeEvent const *>(pEvent);
+ ImplHandleSurroundingTextSelectionChange( pWindow,
+ pEvt->mnStart,
+ pEvt->mnEnd );
+ [[fallthrough]]; // TODO: Fallthrough really intended?
+ }
+ case SalEvent::StartReconversion:
+ ImplHandleStartReconversion( pWindow );
+ break;
+
+ case SalEvent::QueryCharPosition:
+ ImplHandleSalQueryCharPosition( pWindow, const_cast<SalQueryCharPositionEvent *>(static_cast<SalQueryCharPositionEvent const *>(pEvent)) );
+ break;
+
+ case SalEvent::Swipe:
+ bRet = ImplHandleSwipe(pWindow, *static_cast<const SalSwipeEvent*>(pEvent));
+ break;
+
+ case SalEvent::LongPress:
+ bRet = ImplHandleLongPress(pWindow, *static_cast<const SalLongPressEvent*>(pEvent));
+ break;
+
+ case SalEvent::ExternalGesture:
+ {
+ auto const * pGestureEvent = static_cast<GestureEvent const *>(pEvent);
+
+ SalGestureEvent aSalGestureEvent;
+ aSalGestureEvent.mfOffset = pGestureEvent->mnOffset;
+ aSalGestureEvent.mnX = pGestureEvent->mnX;
+ aSalGestureEvent.mnY = pGestureEvent->mnY;
+ aSalGestureEvent.meEventType = pGestureEvent->meEventType;
+ aSalGestureEvent.meOrientation = pGestureEvent->meOrientation;
+
+ bRet = ImplHandleGestureEvent(pWindow, aSalGestureEvent);
+ }
+ break;
+ case SalEvent::Gesture:
+ {
+ auto const * aSalGestureEvent = static_cast<SalGestureEvent const *>(pEvent);
+ bRet = ImplHandleGestureEvent(pWindow, *aSalGestureEvent);
+ }
+ break;
+ default:
+ SAL_WARN( "vcl.layout", "ImplWindowFrameProc(): unknown event (" << static_cast<int>(nEvent) << ")" );
+ break;
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/window/wrkwin.cxx b/vcl/source/window/wrkwin.cxx
new file mode 100644
index 000000000..6dfdd4c0d
--- /dev/null
+++ b/vcl/source/window/wrkwin.cxx
@@ -0,0 +1,272 @@
+/* -*- 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/log.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/wrkwin.hxx>
+// declare system types in sysdata.hxx
+#include <vcl/sysdata.hxx>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/rendering/XCanvas.hpp>
+
+#include <svdata.hxx>
+#include <salframe.hxx>
+#include <brdwin.hxx>
+#include <window.h>
+
+void WorkWindow::ImplInitWorkWindowData()
+{
+ mnIcon = 0; // Should be removed in the next top level update - now in SystemWindow
+
+ mnPresentationFlags = PresentationFlags::NONE;
+ mbPresentationMode = false;
+ mbPresentationVisible = false;
+ mbPresentationFull = false;
+ mbFullScreenMode = false;
+}
+
+void WorkWindow::ImplInit( vcl::Window* pParent, WinBits nStyle, SystemParentData* pSystemParentData )
+{
+ BorderWindowStyle nFrameStyle = BorderWindowStyle::Frame;
+ if ( nStyle & WB_APP )
+ nFrameStyle |= BorderWindowStyle::App;
+
+ VclPtrInstance<ImplBorderWindow> pBorderWin( pParent, pSystemParentData, nStyle, nFrameStyle );
+ Window::ImplInit( pBorderWin, nStyle & (WB_3DLOOK | WB_CLIPCHILDREN | WB_DIALOGCONTROL | WB_SYSTEMFLOATWIN), nullptr );
+ pBorderWin->mpWindowImpl->mpClientWindow = this;
+ pBorderWin->GetBorder( mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder );
+ mpWindowImpl->mpBorderWindow = pBorderWin;
+
+ // mpWindowImpl->mpRealParent = pParent; // should actually be set, but is not set due to errors with the menubar!!
+
+ if ( nStyle & WB_APP )
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ SAL_WARN_IF(pSVData->maFrameData.mpAppWin, "vcl",
+ "WorkWindow::WorkWindow(): More than one window with style WB_APP");
+ pSVData->maFrameData.mpAppWin = this;
+ }
+
+ SetActivateMode( ActivateModeFlags::GrabFocus );
+}
+
+void WorkWindow::ImplInit( vcl::Window* pParent, WinBits nStyle, const css::uno::Any& aSystemWorkWindowToken )
+{
+ if( aSystemWorkWindowToken.hasValue() )
+ {
+ css::uno::Sequence< sal_Int8 > aSeq;
+ aSystemWorkWindowToken >>= aSeq;
+ SystemParentData* pData = reinterpret_cast<SystemParentData*>(aSeq.getArray());
+ SAL_WARN_IF( aSeq.getLength() != sizeof( SystemParentData ) || pData->nSize != sizeof( SystemParentData ), "vcl", "WorkWindow::WorkWindow( vcl::Window*, const Any&, WinBits ) called with invalid Any" );
+ // init with style 0 as does WorkWindow::WorkWindow( SystemParentData* );
+ ImplInit( pParent, 0, pData );
+ }
+ else
+ ImplInit( pParent, nStyle );
+}
+
+WorkWindow::WorkWindow( WindowType nType ) :
+ SystemWindow( nType, "vcl::WorkWindow maLayoutIdle" )
+{
+ ImplInitWorkWindowData();
+}
+
+WorkWindow::WorkWindow( vcl::Window* pParent, WinBits nStyle ) :
+ SystemWindow( WindowType::WORKWINDOW, "vcl::WorkWindow maLayoutIdle" )
+{
+ ImplInitWorkWindowData();
+ ImplInit( pParent, nStyle );
+}
+
+WorkWindow::WorkWindow( vcl::Window* pParent, const css::uno::Any& aSystemWorkWindowToken, WinBits nStyle ) :
+ SystemWindow( WindowType::WORKWINDOW, "vcl::WorkWindow maLayoutIdle" )
+{
+ ImplInitWorkWindowData();
+ mbSysChild = true;
+ ImplInit( pParent, nStyle, aSystemWorkWindowToken );
+}
+
+WorkWindow::WorkWindow( SystemParentData* pParent ) :
+ SystemWindow( WindowType::WORKWINDOW, "vcl::WorkWindow maLayoutIdle" )
+{
+ ImplInitWorkWindowData();
+ mbSysChild = true;
+ ImplInit( nullptr, 0, pParent );
+}
+
+WorkWindow::~WorkWindow()
+{
+ disposeOnce();
+}
+
+void WorkWindow::dispose()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->maFrameData.mpAppWin == this)
+ {
+ pSVData->maFrameData.mpAppWin = nullptr;
+ Application::Quit();
+ }
+ SystemWindow::dispose();
+}
+
+void WorkWindow::ShowFullScreenMode( bool bFullScreenMode )
+{
+ return ShowFullScreenMode( bFullScreenMode, GetScreenNumber());
+}
+
+void WorkWindow::ShowFullScreenMode( bool bFullScreenMode, sal_Int32 nDisplayScreen )
+{
+ if ( !mbFullScreenMode == !bFullScreenMode )
+ return;
+
+ mbFullScreenMode = bFullScreenMode;
+ if ( mbSysChild )
+ return;
+
+ // Dispose of the canvas implementation, which might rely on
+ // screen-specific system data.
+ GetOutDev()->ImplDisposeCanvas();
+
+ mpWindowImpl->mpFrameWindow->mpWindowImpl->mbWaitSystemResize = true;
+ ImplGetFrame()->ShowFullScreen( bFullScreenMode, nDisplayScreen );
+}
+
+void WorkWindow::StartPresentationMode( PresentationFlags nFlags )
+{
+ return StartPresentationMode( false/*bPresentation*/, nFlags, GetScreenNumber());
+}
+
+void WorkWindow::StartPresentationMode( bool bPresentation, PresentationFlags nFlags, sal_Int32 nDisplayScreen )
+{
+ if ( !bPresentation == !mbPresentationMode )
+ return;
+
+ if ( bPresentation )
+ {
+ mbPresentationMode = true;
+ mbPresentationVisible = IsVisible();
+ mbPresentationFull = mbFullScreenMode;
+ mnPresentationFlags = nFlags;
+
+ ShowFullScreenMode( true, nDisplayScreen );
+ if ( !mbSysChild )
+ {
+ if ( mnPresentationFlags & PresentationFlags::HideAllApps )
+ mpWindowImpl->mpFrame->SetAlwaysOnTop( true );
+ ToTop();
+ mpWindowImpl->mpFrame->StartPresentation( true );
+ }
+
+ Show();
+ }
+ else
+ {
+ Show( mbPresentationVisible );
+ if ( !mbSysChild )
+ {
+ mpWindowImpl->mpFrame->StartPresentation( false );
+ if ( mnPresentationFlags & PresentationFlags::HideAllApps )
+ mpWindowImpl->mpFrame->SetAlwaysOnTop( false );
+ }
+ ShowFullScreenMode( mbPresentationFull, nDisplayScreen );
+
+ mbPresentationMode = false;
+ mbPresentationVisible = false;
+ mbPresentationFull = false;
+ mnPresentationFlags = PresentationFlags::NONE;
+ }
+}
+
+bool WorkWindow::IsMinimized() const
+{
+ //return mpWindowImpl->mpFrameData->mbMinimized;
+ SalFrameState aState;
+ if (mpWindowImpl->mpFrame->GetWindowState(&aState))
+ return bool(aState.mnState & WindowStateState::Minimized);
+ else
+ return false;
+}
+
+void WorkWindow::SetPluginParent( SystemParentData* pParent )
+{
+ SAL_WARN_IF( mbPresentationMode || mbFullScreenMode, "vcl", "SetPluginParent in fullscreen or presentation mode !" );
+
+ bool bWasDnd = Window::ImplStopDnd();
+
+ bool bShown = IsVisible();
+ Show( false );
+ mpWindowImpl->mpFrame->SetPluginParent( pParent );
+ Show( bShown );
+
+ if( bWasDnd )
+ Window::ImplStartDnd();
+}
+
+void WorkWindow::ImplSetFrameState( WindowStateState aFrameState )
+{
+ SalFrameState aState;
+ aState.mnMask = WindowStateMask::State;
+ aState.mnState = aFrameState;
+ mpWindowImpl->mpFrame->SetWindowState( &aState );
+}
+
+void WorkWindow::Minimize()
+{
+ ImplSetFrameState( WindowStateState::Minimized );
+}
+
+void WorkWindow::Restore()
+{
+ ImplSetFrameState( WindowStateState::Normal );
+}
+
+bool WorkWindow::Close()
+{
+ bool bCanClose = SystemWindow::Close();
+
+ // if it's the application window then close the application
+ if (bCanClose && (ImplGetSVData()->maFrameData.mpAppWin == this))
+ Application::Quit();
+
+ return bCanClose;
+}
+
+void WorkWindow::Maximize( bool bMaximize )
+{
+ ImplSetFrameState( bMaximize ? WindowStateState::Maximized : WindowStateState::Normal );
+}
+
+bool WorkWindow::IsMaximized() const
+{
+ bool bRet = false;
+
+ SalFrameState aState;
+ if( mpWindowImpl->mpFrame->GetWindowState( &aState ) )
+ {
+ if( aState.mnState & (WindowStateState::Maximized |
+ WindowStateState::MaximizedHorz |
+ WindowStateState::MaximizedVert ) )
+ bRet = true;
+ }
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */